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