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