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