xref: /PHP-7.2/ext/opcache/shared_alloc_win32.c (revision f26172f9)
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