1 /*
2 +----------------------------------------------------------------------+
3 | Zend OPcache |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 1998-2018 The PHP Group |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
15 | Authors: Andi Gutmans <andi@zend.com> |
16 | Zeev Suraski <zeev@zend.com> |
17 | Stanislav Malyshev <stas@zend.com> |
18 | Dmitry Stogov <dmitry@zend.com> |
19 +----------------------------------------------------------------------+
20 */
21
22 #include "ZendAccelerator.h"
23 #include "zend_shared_alloc.h"
24 #include "zend_accelerator_util_funcs.h"
25 #include "tsrm_win32.h"
26 #include <winbase.h>
27 #include <process.h>
28 #include <LMCONS.H>
29
30 #define ACCEL_FILEMAP_NAME "ZendOPcache.SharedMemoryArea"
31 #define ACCEL_MUTEX_NAME "ZendOPcache.SharedMemoryMutex"
32 #define ACCEL_FILEMAP_BASE_DEFAULT 0x01000000
33 #define ACCEL_FILEMAP_BASE "ZendOPcache.MemoryBase"
34 #define ACCEL_EVENT_SOURCE "Zend OPcache"
35
36 static HANDLE memfile = NULL, memory_mutex = NULL;
37 static void *mapping_base;
38
39 #define MAX_MAP_RETRIES 25
40
zend_win_error_message(int type,char * msg,int err)41 static void zend_win_error_message(int type, char *msg, int err)
42 {
43 LPVOID lpMsgBuf;
44 HANDLE h;
45 char *ev_msgs[2];
46
47 FormatMessage(
48 FORMAT_MESSAGE_ALLOCATE_BUFFER |
49 FORMAT_MESSAGE_FROM_SYSTEM |
50 FORMAT_MESSAGE_IGNORE_INSERTS,
51 NULL,
52 err,
53 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
54 (LPTSTR) &lpMsgBuf,
55 0,
56 NULL
57 );
58
59 h = RegisterEventSource(NULL, TEXT(ACCEL_EVENT_SOURCE));
60 ev_msgs[0] = msg;
61 ev_msgs[1] = lpMsgBuf;
62 ReportEvent(h, // event log handle
63 EVENTLOG_ERROR_TYPE, // event type
64 0, // category zero
65 err, // event identifier
66 NULL, // no user security identifier
67 2, // one substitution string
68 0, // no data
69 ev_msgs, // pointer to string array
70 NULL); // pointer to data
71 DeregisterEventSource(h);
72
73 LocalFree( lpMsgBuf );
74
75 zend_accel_error(type, "%s", msg);
76 }
77
create_name_with_username(char * name)78 static char *create_name_with_username(char *name)
79 {
80 static char newname[MAXPATHLEN + UNLEN + 4 + 1 + 32];
81 char *uname;
82
83 uname = php_win32_get_username();
84 if (!uname) {
85 return NULL;
86 }
87 snprintf(newname, sizeof(newname) - 1, "%s@%s@%.32s", name, uname, ZCG(system_id));
88
89 free(uname);
90
91 return newname;
92 }
93
get_mmap_base_file(void)94 static char *get_mmap_base_file(void)
95 {
96 static char windir[MAXPATHLEN+UNLEN + 3 + sizeof("\\\\@") + 1 + 32];
97 char *uname;
98 int l;
99
100 uname = php_win32_get_username();
101 if (!uname) {
102 return NULL;
103 }
104 GetTempPath(MAXPATHLEN, windir);
105 l = strlen(windir);
106 if ('\\' == windir[l-1]) {
107 l--;
108 }
109 snprintf(windir + l, sizeof(windir) - l - 1, "\\%s@%s@%.32s", ACCEL_FILEMAP_BASE, uname, ZCG(system_id));
110
111 free(uname);
112
113 return windir;
114 }
115
zend_shared_alloc_create_lock(void)116 void zend_shared_alloc_create_lock(void)
117 {
118 memory_mutex = CreateMutex(NULL, FALSE, create_name_with_username(ACCEL_MUTEX_NAME));
119 if (!memory_mutex) {
120 zend_accel_error(ACCEL_LOG_FATAL, "Cannot create mutex");
121 return;
122 }
123 ReleaseMutex(memory_mutex);
124 }
125
zend_shared_alloc_lock_win32(void)126 void zend_shared_alloc_lock_win32(void)
127 {
128 DWORD waitRes = WaitForSingleObject(memory_mutex, INFINITE);
129
130 if (waitRes == WAIT_FAILED) {
131 zend_accel_error(ACCEL_LOG_ERROR, "Cannot lock mutex");
132 }
133 }
134
zend_shared_alloc_unlock_win32(void)135 void zend_shared_alloc_unlock_win32(void)
136 {
137 ReleaseMutex(memory_mutex);
138 }
139
zend_shared_alloc_reattach(size_t requested_size,char ** error_in)140 static int zend_shared_alloc_reattach(size_t requested_size, char **error_in)
141 {
142 int err;
143 void *wanted_mapping_base;
144 char *mmap_base_file = get_mmap_base_file();
145 FILE *fp = fopen(mmap_base_file, "r");
146 MEMORY_BASIC_INFORMATION info;
147
148 if (!fp) {
149 err = GetLastError();
150 zend_win_error_message(ACCEL_LOG_WARNING, mmap_base_file, err);
151 zend_win_error_message(ACCEL_LOG_FATAL, "Unable to open base address file", err);
152 *error_in="fopen";
153 return ALLOC_FAILURE;
154 }
155 if (!fscanf(fp, "%p", &wanted_mapping_base)) {
156 err = GetLastError();
157 zend_win_error_message(ACCEL_LOG_FATAL, "Unable to read base address", err);
158 *error_in="read mapping base";
159 fclose(fp);
160 return ALLOC_FAILURE;
161 }
162 fclose(fp);
163
164 if (0 > win32_utime(mmap_base_file, NULL)) {
165 err = GetLastError();
166 zend_win_error_message(ACCEL_LOG_WARNING, mmap_base_file, err);
167 }
168
169 /* Check if the requested address space is free */
170 if (VirtualQuery(wanted_mapping_base, &info, sizeof(info)) == 0 ||
171 info.State != MEM_FREE ||
172 info.RegionSize < requested_size) {
173 #if ENABLE_FILE_CACHE_FALLBACK
174 if (ZCG(accel_directives).file_cache && ZCG(accel_directives).file_cache_fallback) {
175 size_t pre_size, wanted_mb_save;
176
177 wanted_mb_save = (size_t)wanted_mapping_base;
178
179 err = ERROR_INVALID_ADDRESS;
180 zend_win_error_message(ACCEL_LOG_WARNING, "Base address marks unusable memory region (fall-back to file cache)", err);
181
182 pre_size = ZEND_ALIGNED_SIZE(sizeof(zend_smm_shared_globals)) + ZEND_ALIGNED_SIZE(sizeof(zend_shared_segment)) + ZEND_ALIGNED_SIZE(sizeof(void *)) + ZEND_ALIGNED_SIZE(sizeof(int));
183 /* Map only part of SHM to have access opcache shared globals */
184 mapping_base = MapViewOfFileEx(memfile, FILE_MAP_ALL_ACCESS, 0, 0, pre_size + ZEND_ALIGNED_SIZE(sizeof(zend_accel_shared_globals)), NULL);
185 if (mapping_base == NULL) {
186 err = GetLastError();
187 zend_win_error_message(ACCEL_LOG_FATAL, "Unable to reattach to opcache shared globals", err);
188 return ALLOC_FAILURE;
189 }
190 accel_shared_globals = (zend_accel_shared_globals *)((char *)((zend_smm_shared_globals *)mapping_base)->app_shared_globals + ((char *)mapping_base - (char *)wanted_mb_save));
191
192 return ALLOC_FALLBACK;
193 }
194 #endif
195 err = ERROR_INVALID_ADDRESS;
196 zend_win_error_message(ACCEL_LOG_FATAL, "Base address marks unusable memory region. Please setup opcache.file_cache and opcache.file_cache_fallback directives for more convenient Opcache usage", err);
197 return ALLOC_FAILURE;
198 }
199
200 mapping_base = MapViewOfFileEx(memfile, FILE_MAP_ALL_ACCESS, 0, 0, 0, wanted_mapping_base);
201
202 if (mapping_base == NULL) {
203 err = GetLastError();
204 if (err == ERROR_INVALID_ADDRESS) {
205 zend_win_error_message(ACCEL_LOG_FATAL, "Unable to reattach to base address", err);
206 return ALLOC_FAILURE;
207 }
208 return ALLOC_FAIL_MAPPING;
209 }
210 smm_shared_globals = (zend_smm_shared_globals *) mapping_base;
211
212 return SUCCESSFULLY_REATTACHED;
213 }
214
create_segments(size_t requested_size,zend_shared_segment *** shared_segments_p,int * shared_segments_count,char ** error_in)215 static int create_segments(size_t requested_size, zend_shared_segment ***shared_segments_p, int *shared_segments_count, char **error_in)
216 {
217 int err = 0, ret;
218 zend_shared_segment *shared_segment;
219 int map_retries = 0;
220 void *default_mapping_base_set[] = { 0, 0 };
221 /* TODO:
222 improve fixed addresses on x64. It still makes no sense to do it as Windows addresses are virtual per se and can or should be randomized anyway
223 through Address Space Layout Radomization (ASLR). We can still let the OS do its job and be sure that each process gets the same address if
224 desired. Not done yet, @zend refused but did not remember the exact reason, pls add info here if one of you know why :)
225 */
226 #if defined(_WIN64)
227 void *vista_mapping_base_set[] = { (void *) 0x0000100000000000, (void *) 0x0000200000000000, (void *) 0x0000300000000000, (void *) 0x0000700000000000, 0 };
228 DWORD size_high = (requested_size >> 32), size_low = (requested_size & 0xffffffff);
229 #else
230 void *vista_mapping_base_set[] = { (void *) 0x20000000, (void *) 0x21000000, (void *) 0x30000000, (void *) 0x31000000, (void *) 0x50000000, 0 };
231 DWORD size_high = 0, size_low = requested_size;
232 #endif
233 void **wanted_mapping_base = default_mapping_base_set;
234
235 zend_shared_alloc_lock_win32();
236 /* Mapping retries: When Apache2 restarts, the parent process startup routine
237 can be called before the child process is killed. In this case, the map will fail
238 and we have to sleep some time (until the child releases the mapping object) and retry.*/
239 do {
240 memfile = OpenFileMapping(FILE_MAP_WRITE, 0, create_name_with_username(ACCEL_FILEMAP_NAME));
241 if (memfile == NULL) {
242 err = GetLastError();
243 break;
244 }
245
246 ret = zend_shared_alloc_reattach(requested_size, error_in);
247 if (ret == ALLOC_FAIL_MAPPING) {
248 err = GetLastError();
249 /* Mapping failed, wait for mapping object to get freed and retry */
250 CloseHandle(memfile);
251 memfile = NULL;
252 if (++map_retries >= MAX_MAP_RETRIES) {
253 break;
254 }
255 zend_shared_alloc_unlock_win32();
256 Sleep(1000 * (map_retries + 1));
257 zend_shared_alloc_lock_win32();
258 } else {
259 zend_shared_alloc_unlock_win32();
260 return ret;
261 }
262 } while (1);
263
264 if (map_retries == MAX_MAP_RETRIES) {
265 zend_shared_alloc_unlock_win32();
266 zend_win_error_message(ACCEL_LOG_FATAL, "Unable to open file mapping", err);
267 *error_in = "OpenFileMapping";
268 return ALLOC_FAILURE;
269 }
270
271 /* creating segment here */
272 *shared_segments_count = 1;
273 *shared_segments_p = (zend_shared_segment **) calloc(1, sizeof(zend_shared_segment)+sizeof(void *));
274 if (!*shared_segments_p) {
275 err = GetLastError();
276 zend_shared_alloc_unlock_win32();
277 zend_win_error_message(ACCEL_LOG_FATAL, "calloc() failed", err);
278 *error_in = "calloc";
279 return ALLOC_FAILURE;
280 }
281 shared_segment = (zend_shared_segment *)((char *)(*shared_segments_p) + sizeof(void *));
282 (*shared_segments_p)[0] = shared_segment;
283
284 memfile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, size_high, size_low,
285 create_name_with_username(ACCEL_FILEMAP_NAME));
286 if (memfile == NULL) {
287 err = GetLastError();
288 zend_shared_alloc_unlock_win32();
289 zend_win_error_message(ACCEL_LOG_FATAL, "Unable to create file mapping", err);
290 *error_in = "CreateFileMapping";
291 return ALLOC_FAILURE;
292 }
293
294 /* Starting from windows Vista, heap randomization occurs which might cause our mapping base to
295 be taken (fail to map). So under Vista, we try to map into a hard coded predefined addresses
296 in high memory. */
297 if (!ZCG(accel_directives).mmap_base || !*ZCG(accel_directives).mmap_base) {
298 wanted_mapping_base = vista_mapping_base_set;
299 } else {
300 char *s = ZCG(accel_directives).mmap_base;
301
302 /* skip leading 0x, %p assumes hexdeciaml format anyway */
303 if (*s == '0' && *(s + 1) == 'x') {
304 s += 2;
305 }
306 if (sscanf(s, "%p", &default_mapping_base_set[0]) != 1) {
307 zend_shared_alloc_unlock_win32();
308 zend_win_error_message(ACCEL_LOG_FATAL, "Bad mapping address specified in opcache.mmap_base", err);
309 return ALLOC_FAILURE;
310 }
311 }
312
313 do {
314 shared_segment->p = mapping_base = MapViewOfFileEx(memfile, FILE_MAP_ALL_ACCESS, 0, 0, 0, *wanted_mapping_base);
315 if (*wanted_mapping_base == NULL) { /* Auto address (NULL) is the last option on the array */
316 break;
317 }
318 wanted_mapping_base++;
319 } while (!mapping_base);
320
321 if (mapping_base == NULL) {
322 err = GetLastError();
323 zend_shared_alloc_unlock_win32();
324 zend_win_error_message(ACCEL_LOG_FATAL, "Unable to create view for file mapping", err);
325 *error_in = "MapViewOfFile";
326 return ALLOC_FAILURE;
327 } else {
328 char *mmap_base_file = get_mmap_base_file();
329 FILE *fp = fopen(mmap_base_file, "w");
330 if (!fp) {
331 err = GetLastError();
332 zend_shared_alloc_unlock_win32();
333 zend_win_error_message(ACCEL_LOG_WARNING, mmap_base_file, err);
334 zend_win_error_message(ACCEL_LOG_FATAL, "Unable to write base address", err);
335 return ALLOC_FAILURE;
336 }
337 fprintf(fp, "%p\n", mapping_base);
338 fclose(fp);
339 }
340
341 shared_segment->pos = 0;
342 shared_segment->size = requested_size;
343
344 zend_shared_alloc_unlock_win32();
345
346 return ALLOC_SUCCESS;
347 }
348
detach_segment(zend_shared_segment * shared_segment)349 static int detach_segment(zend_shared_segment *shared_segment)
350 {
351 zend_shared_alloc_lock_win32();
352 if (mapping_base) {
353 UnmapViewOfFile(mapping_base);
354 mapping_base = NULL;
355 }
356 CloseHandle(memfile);
357 memfile = NULL;
358 zend_shared_alloc_unlock_win32();
359 CloseHandle(memory_mutex);
360 memory_mutex = NULL;
361 return 0;
362 }
363
segment_type_size(void)364 static size_t segment_type_size(void)
365 {
366 return sizeof(zend_shared_segment);
367 }
368
369 zend_shared_memory_handlers zend_alloc_win32_handlers = {
370 create_segments,
371 detach_segment,
372 segment_type_size
373 };
374