1/*
2   +----------------------------------------------------------------------+
3   | Zend OPcache                                                         |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 1998-2016 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 <errno.h>
23#include "ZendAccelerator.h"
24#include "zend_shared_alloc.h"
25#ifdef HAVE_UNISTD_H
26# include <unistd.h>
27#endif
28#include <fcntl.h>
29#ifndef ZEND_WIN32
30# include <sys/types.h>
31# include <dirent.h>
32# include <signal.h>
33# include <sys/stat.h>
34# include <stdio.h>
35#endif
36
37#ifdef HAVE_MPROTECT
38# include "sys/mman.h"
39#endif
40
41#define TMP_DIR "/tmp"
42#define SEM_FILENAME_PREFIX ".ZendSem."
43#define S_H(s) g_shared_alloc_handler->s
44
45/* True globals */
46/* old/new mapping. We can use true global even for ZTS because its usage
47   is wrapped with exclusive lock anyway */
48static const zend_shared_memory_handlers *g_shared_alloc_handler = NULL;
49static const char *g_shared_model;
50/* pointer to globals allocated in SHM and shared across processes */
51zend_smm_shared_globals *smm_shared_globals;
52
53#ifndef ZEND_WIN32
54#ifdef ZTS
55static MUTEX_T zts_lock;
56#endif
57int lock_file;
58static char lockfile_name[sizeof(TMP_DIR) + sizeof(SEM_FILENAME_PREFIX) + 8];
59#endif
60
61static const zend_shared_memory_handler_entry handler_table[] = {
62#ifdef USE_MMAP
63	{ "mmap", &zend_alloc_mmap_handlers },
64#endif
65#ifdef USE_SHM
66	{ "shm", &zend_alloc_shm_handlers },
67#endif
68#ifdef USE_SHM_OPEN
69	{ "posix", &zend_alloc_posix_handlers },
70#endif
71#ifdef ZEND_WIN32
72	{ "win32", &zend_alloc_win32_handlers },
73#endif
74	{ NULL, NULL}
75};
76
77#ifndef ZEND_WIN32
78void zend_shared_alloc_create_lock(void)
79{
80	int val;
81
82#ifdef ZTS
83    zts_lock = tsrm_mutex_alloc();
84#endif
85
86	sprintf(lockfile_name, "%s/%sXXXXXX", TMP_DIR, SEM_FILENAME_PREFIX);
87	lock_file = mkstemp(lockfile_name);
88	fchmod(lock_file, 0666);
89
90	if (lock_file == -1) {
91		zend_accel_error(ACCEL_LOG_FATAL, "Unable to create lock file: %s (%d)", strerror(errno), errno);
92	}
93	val = fcntl(lock_file, F_GETFD, 0);
94	val |= FD_CLOEXEC;
95	fcntl(lock_file, F_SETFD, val);
96
97	unlink(lockfile_name);
98}
99#endif
100
101static void no_memory_bailout(size_t allocate_size, char *error)
102{
103	zend_accel_error(ACCEL_LOG_FATAL, "Unable to allocate shared memory segment of %ld bytes: %s: %s (%d)", allocate_size, error?error:"unknown", strerror(errno), errno );
104}
105
106static void copy_shared_segments(void *to, void *from, int count, int size)
107{
108	zend_shared_segment **shared_segments_v = (zend_shared_segment **)to;
109	void *shared_segments_to_p = ((char *)to + count*(sizeof(void *)));
110	void *shared_segments_from_p = from;
111	int i;
112
113	for (i = 0; i < count; i++) {
114		shared_segments_v[i] = 	shared_segments_to_p;
115		memcpy(shared_segments_to_p, shared_segments_from_p, size);
116		shared_segments_to_p = ((char *)shared_segments_to_p + size);
117		shared_segments_from_p = ((char *)shared_segments_from_p + size);
118	}
119}
120
121static int zend_shared_alloc_try(const zend_shared_memory_handler_entry *he, size_t requested_size, zend_shared_segment ***shared_segments_p, int *shared_segments_count, char **error_in)
122{
123	int res;
124	g_shared_alloc_handler = he->handler;
125	g_shared_model = he->name;
126	ZSMMG(shared_segments) = NULL;
127	ZSMMG(shared_segments_count) = 0;
128
129	res = S_H(create_segments)(requested_size, shared_segments_p, shared_segments_count, error_in);
130
131	if (res) {
132		/* this model works! */
133		return res;
134	}
135	if (*shared_segments_p) {
136		int i;
137		/* cleanup */
138		for (i = 0; i < *shared_segments_count; i++) {
139			if ((*shared_segments_p)[i]->p && (*shared_segments_p)[i]->p != (void *)-1) {
140				S_H(detach_segment)((*shared_segments_p)[i]);
141			}
142		}
143		free(*shared_segments_p);
144		*shared_segments_p = NULL;
145	}
146	g_shared_alloc_handler = NULL;
147	return ALLOC_FAILURE;
148}
149
150int zend_shared_alloc_startup(size_t requested_size)
151{
152	zend_shared_segment **tmp_shared_segments;
153	size_t shared_segments_array_size;
154	zend_smm_shared_globals tmp_shared_globals, *p_tmp_shared_globals;
155	char *error_in = NULL;
156	const zend_shared_memory_handler_entry *he;
157	int res = ALLOC_FAILURE;
158
159
160	/* shared_free must be valid before we call zend_shared_alloc()
161	 * - make it temporarily point to a local variable
162	 */
163	smm_shared_globals = &tmp_shared_globals;
164	ZSMMG(shared_free) = requested_size; /* goes to tmp_shared_globals.shared_free */
165
166	zend_shared_alloc_create_lock();
167
168	if (ZCG(accel_directives).memory_model && ZCG(accel_directives).memory_model[0]) {
169		char *model = ZCG(accel_directives).memory_model;
170		/* "cgi" is really "shm"... */
171		if (strncmp(ZCG(accel_directives).memory_model, "cgi", sizeof("cgi")) == 0) {
172			model = "shm";
173		}
174
175		for (he = handler_table; he->name; he++) {
176			if (strcmp(model, he->name) == 0) {
177				res = zend_shared_alloc_try(he, requested_size, &ZSMMG(shared_segments), &ZSMMG(shared_segments_count), &error_in);
178				if (res) {
179					/* this model works! */
180				}
181				break;
182			}
183		}
184	}
185
186	if (res == FAILED_REATTACHED) {
187		smm_shared_globals = NULL;
188		return res;
189	}
190#if ENABLE_FILE_CACHE_FALLBACK
191	if (ALLOC_FALLBACK == res) {
192		return ALLOC_FALLBACK;
193	}
194#endif
195
196	if (!g_shared_alloc_handler) {
197		/* try memory handlers in order */
198		for (he = handler_table; he->name; he++) {
199			res = zend_shared_alloc_try(he, requested_size, &ZSMMG(shared_segments), &ZSMMG(shared_segments_count), &error_in);
200			if (res) {
201				/* this model works! */
202				break;
203			}
204		}
205	}
206
207	if (!g_shared_alloc_handler) {
208		no_memory_bailout(requested_size, error_in);
209		return ALLOC_FAILURE;
210	}
211
212	if (res == SUCCESSFULLY_REATTACHED) {
213		return res;
214	}
215#if ENABLE_FILE_CACHE_FALLBACK
216	if (ALLOC_FALLBACK == res) {
217		return ALLOC_FALLBACK;
218	}
219#endif
220
221	shared_segments_array_size = ZSMMG(shared_segments_count) * S_H(segment_type_size)();
222
223	/* move shared_segments and shared_free to shared memory */
224	ZCG(locked) = 1; /* no need to perform a real lock at this point */
225	p_tmp_shared_globals = (zend_smm_shared_globals *) zend_shared_alloc(sizeof(zend_smm_shared_globals));
226	if (!p_tmp_shared_globals) {
227		zend_accel_error(ACCEL_LOG_FATAL, "Insufficient shared memory!");
228		return ALLOC_FAILURE;;
229	}
230
231	tmp_shared_segments = zend_shared_alloc(shared_segments_array_size + ZSMMG(shared_segments_count) * sizeof(void *));
232	if (!tmp_shared_segments) {
233		zend_accel_error(ACCEL_LOG_FATAL, "Insufficient shared memory!");
234		return ALLOC_FAILURE;;
235	}
236
237	copy_shared_segments(tmp_shared_segments, ZSMMG(shared_segments)[0], ZSMMG(shared_segments_count), S_H(segment_type_size)());
238
239	*p_tmp_shared_globals = tmp_shared_globals;
240	smm_shared_globals = p_tmp_shared_globals;
241
242	free(ZSMMG(shared_segments));
243	ZSMMG(shared_segments) = tmp_shared_segments;
244
245	ZSMMG(shared_memory_state).positions = (int *)zend_shared_alloc(sizeof(int) * ZSMMG(shared_segments_count));
246	if (!ZSMMG(shared_memory_state).positions) {
247		zend_accel_error(ACCEL_LOG_FATAL, "Insufficient shared memory!");
248		return ALLOC_FAILURE;;
249	}
250
251	ZCG(locked) = 0;
252
253	return res;
254}
255
256void zend_shared_alloc_shutdown(void)
257{
258	zend_shared_segment **tmp_shared_segments;
259	size_t shared_segments_array_size;
260	zend_smm_shared_globals tmp_shared_globals;
261	int i;
262
263	tmp_shared_globals = *smm_shared_globals;
264	smm_shared_globals = &tmp_shared_globals;
265	shared_segments_array_size = ZSMMG(shared_segments_count) * (S_H(segment_type_size)() + sizeof(void *));
266	tmp_shared_segments = emalloc(shared_segments_array_size);
267	copy_shared_segments(tmp_shared_segments, ZSMMG(shared_segments)[0], ZSMMG(shared_segments_count), S_H(segment_type_size)());
268	ZSMMG(shared_segments) = tmp_shared_segments;
269
270	for (i = 0; i < ZSMMG(shared_segments_count); i++) {
271		S_H(detach_segment)(ZSMMG(shared_segments)[i]);
272	}
273	efree(ZSMMG(shared_segments));
274	ZSMMG(shared_segments) = NULL;
275	g_shared_alloc_handler = NULL;
276#ifndef ZEND_WIN32
277	close(lock_file);
278#endif
279}
280
281static size_t zend_shared_alloc_get_largest_free_block(void)
282{
283	int i;
284	size_t largest_block_size = 0;
285
286	for (i = 0; i < ZSMMG(shared_segments_count); i++) {
287		size_t block_size = ZSMMG(shared_segments)[i]->size - ZSMMG(shared_segments)[i]->pos;
288
289		if (block_size>largest_block_size) {
290			largest_block_size = block_size;
291		}
292	}
293	return largest_block_size;
294}
295
296#define MIN_FREE_MEMORY 64*1024
297
298#define SHARED_ALLOC_FAILED() do {		\
299		zend_accel_error(ACCEL_LOG_WARNING, "Not enough free shared space to allocate %pd bytes (%pd bytes free)", (zend_long)size, (zend_long)ZSMMG(shared_free)); \
300		if (zend_shared_alloc_get_largest_free_block() < MIN_FREE_MEMORY) { \
301			ZSMMG(memory_exhausted) = 1; \
302		} \
303	} while (0)
304
305void *zend_shared_alloc(size_t size)
306{
307	int i;
308	unsigned int block_size = ZEND_ALIGNED_SIZE(size);
309
310#if 1
311	if (!ZCG(locked)) {
312		zend_accel_error(ACCEL_LOG_ERROR, "Shared memory lock not obtained");
313	}
314#endif
315	if (block_size > ZSMMG(shared_free)) { /* No hope to find a big-enough block */
316		SHARED_ALLOC_FAILED();
317		return NULL;
318	}
319	for (i = 0; i < ZSMMG(shared_segments_count); i++) {
320		if (ZSMMG(shared_segments)[i]->size - ZSMMG(shared_segments)[i]->pos >= block_size) { /* found a valid block */
321			void *retval = (void *) (((char *) ZSMMG(shared_segments)[i]->p) + ZSMMG(shared_segments)[i]->pos);
322
323			ZSMMG(shared_segments)[i]->pos += block_size;
324			ZSMMG(shared_free) -= block_size;
325			memset(retval, 0, block_size);
326			ZEND_ASSERT(((zend_uintptr_t)retval & 0x7) == 0); /* should be 8 byte aligned */
327			return retval;
328		}
329	}
330	SHARED_ALLOC_FAILED();
331	return NULL;
332}
333
334int zend_shared_memdup_size(void *source, size_t size)
335{
336	void *old_p;
337
338	if ((old_p = zend_hash_index_find_ptr(&ZCG(xlat_table), (zend_ulong)source)) != NULL) {
339		/* we already duplicated this pointer */
340		return 0;
341	}
342	zend_shared_alloc_register_xlat_entry(source, source);
343	return ZEND_ALIGNED_SIZE(size);
344}
345
346void *_zend_shared_memdup(void *source, size_t size, zend_bool free_source)
347{
348	void *old_p, *retval;
349
350	if ((old_p = zend_hash_index_find_ptr(&ZCG(xlat_table), (zend_ulong)source)) != NULL) {
351		/* we already duplicated this pointer */
352		return old_p;
353	}
354	retval = ZCG(mem);
355	ZCG(mem) = (void*)(((char*)ZCG(mem)) + ZEND_ALIGNED_SIZE(size));
356	memcpy(retval, source, size);
357	zend_shared_alloc_register_xlat_entry(source, retval);
358	if (free_source) {
359		efree(source);
360	}
361	return retval;
362}
363
364void zend_shared_alloc_safe_unlock(void)
365{
366	if (ZCG(locked)) {
367		zend_shared_alloc_unlock();
368	}
369}
370
371#ifndef ZEND_WIN32
372/* name l_type l_whence l_start l_len */
373static FLOCK_STRUCTURE(mem_write_lock, F_WRLCK, SEEK_SET, 0, 1);
374static FLOCK_STRUCTURE(mem_write_unlock, F_UNLCK, SEEK_SET, 0, 1);
375#endif
376
377void zend_shared_alloc_lock(void)
378{
379#ifndef ZEND_WIN32
380
381#ifdef ZTS
382	tsrm_mutex_lock(zts_lock);
383#endif
384
385#if 0
386	/* this will happen once per process, and will un-globalize mem_write_lock */
387	if (mem_write_lock.l_pid == -1) {
388		mem_write_lock.l_pid = getpid();
389	}
390#endif
391
392	while (1) {
393		if (fcntl(lock_file, F_SETLKW, &mem_write_lock) == -1) {
394			if (errno == EINTR) {
395				continue;
396			}
397			zend_accel_error(ACCEL_LOG_ERROR, "Cannot create lock - %s (%d)", strerror(errno), errno);
398		}
399		break;
400	}
401#else
402	zend_shared_alloc_lock_win32();
403#endif
404
405	ZCG(locked) = 1;
406}
407
408void zend_shared_alloc_unlock(void)
409{
410	ZCG(locked) = 0;
411
412#ifndef ZEND_WIN32
413	if (fcntl(lock_file, F_SETLK, &mem_write_unlock) == -1) {
414		zend_accel_error(ACCEL_LOG_ERROR, "Cannot remove lock - %s (%d)", strerror(errno), errno);
415	}
416#ifdef ZTS
417	tsrm_mutex_unlock(zts_lock);
418#endif
419#else
420	zend_shared_alloc_unlock_win32();
421#endif
422}
423
424void zend_shared_alloc_init_xlat_table(void)
425{
426
427	/* Prepare translation table
428	 *
429	 * Make it persistent so that it uses malloc() and allocated blocks
430	 * won't be taken from space which is freed by efree in memdup.
431	 * Otherwise it leads to false matches in memdup check.
432	 */
433	zend_hash_init(&ZCG(xlat_table), 128, NULL, NULL, 1);
434}
435
436void zend_shared_alloc_destroy_xlat_table(void)
437{
438	/* Destroy translation table */
439	zend_hash_destroy(&ZCG(xlat_table));
440}
441
442void zend_shared_alloc_clear_xlat_table(void)
443{
444	zend_hash_clean(&ZCG(xlat_table));
445}
446
447void zend_shared_alloc_register_xlat_entry(const void *old, const void *new)
448{
449	zend_hash_index_add_new_ptr(&ZCG(xlat_table), (zend_ulong)old, (void*)new);
450}
451
452void *zend_shared_alloc_get_xlat_entry(const void *old)
453{
454	void *retval;
455
456	if ((retval = zend_hash_index_find_ptr(&ZCG(xlat_table), (zend_ulong)old)) == NULL) {
457		return NULL;
458	}
459	return retval;
460}
461
462size_t zend_shared_alloc_get_free_memory(void)
463{
464	return ZSMMG(shared_free);
465}
466
467void zend_shared_alloc_save_state(void)
468{
469	int i;
470
471	for (i = 0; i < ZSMMG(shared_segments_count); i++) {
472		ZSMMG(shared_memory_state).positions[i] = ZSMMG(shared_segments)[i]->pos;
473	}
474	ZSMMG(shared_memory_state).shared_free = ZSMMG(shared_free);
475}
476
477void zend_shared_alloc_restore_state(void)
478{
479	int i;
480
481	for (i = 0; i < ZSMMG(shared_segments_count); i++) {
482		ZSMMG(shared_segments)[i]->pos = ZSMMG(shared_memory_state).positions[i];
483	}
484	ZSMMG(shared_free) = ZSMMG(shared_memory_state).shared_free;
485	ZSMMG(memory_exhausted) = 0;
486	ZSMMG(wasted_shared_memory) = 0;
487}
488
489const char *zend_accel_get_shared_model(void)
490{
491	return g_shared_model;
492}
493
494void zend_accel_shared_protect(int mode)
495{
496#ifdef HAVE_MPROTECT
497	int i;
498
499	if (mode) {
500		mode = PROT_READ;
501	} else {
502		mode = PROT_READ|PROT_WRITE;
503	}
504
505	for (i = 0; i < ZSMMG(shared_segments_count); i++) {
506		mprotect(ZSMMG(shared_segments)[i]->p, ZSMMG(shared_segments)[i]->size, mode);
507	}
508#endif
509}
510