xref: /PHP-5.5/ext/opcache/shared_alloc_win32.c (revision 73c1be26)
1 /*
2    +----------------------------------------------------------------------+
3    | Zend OPcache                                                         |
4    +----------------------------------------------------------------------+
5    | Copyright (c) 1998-2015 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];
80 	char uname[UNLEN + 1];
81 	DWORD unsize = UNLEN;
82 
83 	GetUserName(uname, &unsize);
84 	snprintf(newname, sizeof(newname) - 1, "%s@%s", name, uname);
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("\\\\@")];
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 	snprintf(windir + l, sizeof(windir) - l - 1, "\\%s@%s", ACCEL_FILEMAP_BASE, uname);
99 	return windir;
100 }
101 
zend_shared_alloc_create_lock(void)102 void zend_shared_alloc_create_lock(void)
103 {
104 	memory_mutex = CreateMutex(NULL, FALSE, create_name_with_username(ACCEL_MUTEX_NAME));
105 	if (!memory_mutex) {
106 		zend_accel_error(ACCEL_LOG_FATAL, "Cannot create mutex");
107 		return;
108 	}
109 	ReleaseMutex(memory_mutex);
110 }
111 
zend_shared_alloc_lock_win32(void)112 void zend_shared_alloc_lock_win32(void)
113 {
114 	DWORD waitRes = WaitForSingleObject(memory_mutex, INFINITE);
115 
116 	if (waitRes == WAIT_FAILED) {
117 		zend_accel_error(ACCEL_LOG_ERROR, "Cannot lock mutex");
118 	}
119 }
120 
zend_shared_alloc_unlock_win32(void)121 void zend_shared_alloc_unlock_win32(void)
122 {
123 	ReleaseMutex(memory_mutex);
124 }
125 
zend_shared_alloc_reattach(size_t requested_size,char ** error_in)126 static int zend_shared_alloc_reattach(size_t requested_size, char **error_in)
127 {
128 	int err;
129 	void *wanted_mapping_base;
130 	char *mmap_base_file = get_mmap_base_file();
131 	FILE *fp = fopen(mmap_base_file, "r");
132 	MEMORY_BASIC_INFORMATION info;
133 
134 	err = GetLastError();
135 	if (!fp) {
136 		zend_win_error_message(ACCEL_LOG_WARNING, mmap_base_file, err);
137 		zend_win_error_message(ACCEL_LOG_FATAL, "Unable to open base address file", err);
138 		*error_in="fopen";
139 		return ALLOC_FAILURE;
140 	}
141 	if (!fscanf(fp, "%p", &wanted_mapping_base)) {
142 		err = GetLastError();
143 		zend_win_error_message(ACCEL_LOG_FATAL, "Unable to read base address", err);
144 		*error_in="read mapping base";
145 		fclose(fp);
146 		return ALLOC_FAILURE;
147 	}
148 	fclose(fp);
149 
150 	/* Check if the requested address space is free */
151 	if (VirtualQuery(wanted_mapping_base, &info, sizeof(info)) == 0 ||
152 	    info.State != MEM_FREE ||
153 	    info.RegionSize < requested_size) {
154 	    err = ERROR_INVALID_ADDRESS;
155 		zend_win_error_message(ACCEL_LOG_FATAL, "Unable to reattach to base address", err);
156 		return ALLOC_FAILURE;
157    	}
158 
159 	mapping_base = MapViewOfFileEx(memfile, FILE_MAP_ALL_ACCESS, 0, 0, 0, wanted_mapping_base);
160 	err = GetLastError();
161 
162 	if (mapping_base == NULL) {
163 		if (err == ERROR_INVALID_ADDRESS) {
164 			zend_win_error_message(ACCEL_LOG_FATAL, "Unable to reattach to base address", err);
165 			return ALLOC_FAILURE;
166 		}
167 		return ALLOC_FAIL_MAPPING;
168 	}
169 	smm_shared_globals = (zend_smm_shared_globals *) mapping_base;
170 
171 	return SUCCESSFULLY_REATTACHED;
172 }
173 
create_segments(size_t requested_size,zend_shared_segment *** shared_segments_p,int * shared_segments_count,char ** error_in)174 static int create_segments(size_t requested_size, zend_shared_segment ***shared_segments_p, int *shared_segments_count, char **error_in)
175 {
176 	int err, ret;
177 	zend_shared_segment *shared_segment;
178 	int map_retries = 0;
179 	void *default_mapping_base_set[] = { 0, 0 };
180 	/* TODO:
181 	  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
182 	  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
183 	  desired. Not done yet, @zend refused but did not remember the exact reason, pls add info here if one of you know why :)
184 	*/
185 #if defined(_WIN64)
186 	void *vista_mapping_base_set[] = { (void *) 0x0000100000000000, (void *) 0x0000200000000000, (void *) 0x0000300000000000, (void *) 0x0000700000000000, 0 };
187 #else
188 	void *vista_mapping_base_set[] = { (void *) 0x20000000, (void *) 0x21000000, (void *) 0x30000000, (void *) 0x31000000, (void *) 0x50000000, 0 };
189 #endif
190 	void **wanted_mapping_base = default_mapping_base_set;
191 	TSRMLS_FETCH();
192 
193 	zend_shared_alloc_lock_win32();
194 	/* Mapping retries: When Apache2 restarts, the parent process startup routine
195 	   can be called before the child process is killed. In this case, the map will fail
196 	   and we have to sleep some time (until the child releases the mapping object) and retry.*/
197 	do {
198 		memfile = OpenFileMapping(FILE_MAP_WRITE, 0, create_name_with_username(ACCEL_FILEMAP_NAME));
199 		err = GetLastError();
200 		if (memfile == NULL) {
201 			break;
202 		}
203 
204 		ret =  zend_shared_alloc_reattach(requested_size, error_in);
205 		err = GetLastError();
206 		if (ret == ALLOC_FAIL_MAPPING) {
207 			/* Mapping failed, wait for mapping object to get freed and retry */
208             CloseHandle(memfile);
209 			memfile = NULL;
210 			Sleep(1000 * (map_retries + 1));
211 		} else {
212 			zend_shared_alloc_unlock_win32();
213 			return ret;
214 		}
215 	} while (++map_retries < MAX_MAP_RETRIES);
216 
217 	if (map_retries == MAX_MAP_RETRIES) {
218 		zend_shared_alloc_unlock_win32();
219 		zend_win_error_message(ACCEL_LOG_FATAL, "Unable to open file mapping", err);
220 		*error_in = "OpenFileMapping";
221 		return ALLOC_FAILURE;
222 	}
223 
224 	/* creating segment here */
225 	*shared_segments_count = 1;
226 	*shared_segments_p = (zend_shared_segment **) calloc(1, sizeof(zend_shared_segment)+sizeof(void *));
227 	if (!*shared_segments_p) {
228 		zend_shared_alloc_unlock_win32();
229 		zend_win_error_message(ACCEL_LOG_FATAL, "calloc() failed", GetLastError());
230 		*error_in = "calloc";
231 		return ALLOC_FAILURE;
232 	}
233 	shared_segment = (zend_shared_segment *)((char *)(*shared_segments_p) + sizeof(void *));
234 	(*shared_segments_p)[0] = shared_segment;
235 
236 	memfile	= CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, requested_size,
237 								create_name_with_username(ACCEL_FILEMAP_NAME));
238 	err = GetLastError();
239 	if (memfile == NULL) {
240 		zend_shared_alloc_unlock_win32();
241 		zend_win_error_message(ACCEL_LOG_FATAL, "Unable to create file mapping", err);
242 		*error_in = "CreateFileMapping";
243 		return ALLOC_FAILURE;
244 	}
245 
246 	/* Starting from windows Vista, heap randomization occurs which might cause our mapping base to
247 	   be taken (fail to map). So under Vista, we try to map into a hard coded predefined addresses
248 	   in high memory. */
249 	if (!ZCG(accel_directives).mmap_base || !*ZCG(accel_directives).mmap_base) {
250 		do {
251 			OSVERSIONINFOEX osvi;
252 			SYSTEM_INFO si;
253 
254 			ZeroMemory(&si, sizeof(SYSTEM_INFO));
255 			ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
256 
257 			osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
258 
259 			if (! GetVersionEx ((OSVERSIONINFO *) &osvi)) {
260 				osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
261 				if (!GetVersionEx((OSVERSIONINFO *)&osvi)) {
262 					break;
263 				}
264 			}
265 
266 			GetSystemInfo(&si);
267 
268 			/* Are we running Vista ? */
269 			if (osvi.dwPlatformId == VER_PLATFORM_WIN32_NT && osvi.dwMajorVersion == 6) {
270 				wanted_mapping_base = vista_mapping_base_set;
271 			}
272 		} while (0);
273 	} else {
274 		char *s = ZCG(accel_directives).mmap_base;
275 
276 		/* skip leading 0x, %p assumes hexdeciaml format anyway */
277 		if (*s == '0' && *(s + 1) == 'x') {
278 			s += 2;
279 		}
280 		if (sscanf(s, "%p", &default_mapping_base_set[0]) != 1) {
281 			zend_shared_alloc_unlock_win32();
282 			zend_win_error_message(ACCEL_LOG_FATAL, "Bad mapping address specified in opcache.mmap_base", err);
283 			return ALLOC_FAILURE;
284 		}
285 	}
286 
287 	do {
288 		shared_segment->p = mapping_base = MapViewOfFileEx(memfile, FILE_MAP_ALL_ACCESS, 0, 0, 0, *wanted_mapping_base);
289 		if (*wanted_mapping_base == NULL) { /* Auto address (NULL) is the last option on the array */
290 			break;
291 		}
292 		wanted_mapping_base++;
293 	} while (!mapping_base);
294 
295 	err = GetLastError();
296 	if (mapping_base == NULL) {
297 		zend_shared_alloc_unlock_win32();
298 		zend_win_error_message(ACCEL_LOG_FATAL, "Unable to create view for file mapping", err);
299 		*error_in = "MapViewOfFile";
300 		return ALLOC_FAILURE;
301 	} else {
302 		char *mmap_base_file = get_mmap_base_file();
303 		FILE *fp = fopen(mmap_base_file, "w");
304 		err = GetLastError();
305 		if (!fp) {
306 			zend_shared_alloc_unlock_win32();
307 			zend_win_error_message(ACCEL_LOG_WARNING, mmap_base_file, err);
308 			zend_win_error_message(ACCEL_LOG_FATAL, "Unable to write base address", err);
309 			return ALLOC_FAILURE;
310 		}
311 		fprintf(fp, "%p\n", mapping_base);
312 		fclose(fp);
313 	}
314 
315 	shared_segment->pos = 0;
316 	shared_segment->size = requested_size;
317 
318 	zend_shared_alloc_unlock_win32();
319 
320 	return ALLOC_SUCCESS;
321 }
322 
detach_segment(zend_shared_segment * shared_segment)323 static int detach_segment(zend_shared_segment *shared_segment)
324 {
325 	zend_shared_alloc_lock_win32();
326 	if (mapping_base) {
327 		UnmapViewOfFile(mapping_base);
328 	}
329 	CloseHandle(memfile);
330 	zend_shared_alloc_unlock_win32();
331 	CloseHandle(memory_mutex);
332 	return 0;
333 }
334 
segment_type_size(void)335 static size_t segment_type_size(void)
336 {
337 	return sizeof(zend_shared_segment);
338 }
339 
340 zend_shared_memory_handlers zend_alloc_win32_handlers = {
341 	create_segments,
342 	detach_segment,
343 	segment_type_size
344 };
345