xref: /PHP-8.3/ext/sysvsem/sysvsem.c (revision 94ee4f98)
1 /*
2    +----------------------------------------------------------------------+
3    | Copyright (c) The PHP Group                                          |
4    +----------------------------------------------------------------------+
5    | This source file is subject to version 3.01 of the PHP license,      |
6    | that is bundled with this package in the file LICENSE, and is        |
7    | available through the world-wide-web at the following url:           |
8    | https://www.php.net/license/3_01.txt                                 |
9    | If you did not receive a copy of the PHP license and are unable to   |
10    | obtain it through the world-wide-web, please send a note to          |
11    | license@php.net so we can mail you a copy immediately.               |
12    +----------------------------------------------------------------------+
13    | Authors: Tom May <tom@go2net.com>                                    |
14    |          Gavin Sherry <gavin@linuxworld.com.au>                      |
15    +----------------------------------------------------------------------+
16  */
17 
18 #ifdef HAVE_CONFIG_H
19 #include "config.h"
20 #endif
21 
22 #include "php.h"
23 
24 #ifdef HAVE_SYSVSEM
25 
26 #include <sys/types.h>
27 #include <sys/ipc.h>
28 #include <sys/sem.h>
29 #include <errno.h>
30 
31 #include "sysvsem_arginfo.h"
32 #include "php_sysvsem.h"
33 #include "ext/standard/info.h"
34 
35 #if !HAVE_SEMUN
36 
37 union semun {
38 	int val;                    /* value for SETVAL */
39 	struct semid_ds *buf;       /* buffer for IPC_STAT, IPC_SET */
40 	unsigned short int *array;  /* array for GETALL, SETALL */
41 	struct seminfo *__buf;      /* buffer for IPC_INFO */
42 };
43 
44 #undef HAVE_SEMUN
45 #define HAVE_SEMUN 1
46 
47 #endif
48 
49 /* {{{ sysvsem_module_entry */
50 zend_module_entry sysvsem_module_entry = {
51 	STANDARD_MODULE_HEADER,
52 	"sysvsem",
53 	ext_functions,
54 	PHP_MINIT(sysvsem),
55 	NULL,
56 	NULL,
57 	NULL,
58 	PHP_MINFO(sysvsem),
59 	PHP_SYSVSEM_VERSION,
60 	STANDARD_MODULE_PROPERTIES
61 };
62 /* }}} */
63 
64 #ifdef COMPILE_DL_SYSVSEM
65 ZEND_GET_MODULE(sysvsem)
66 #endif
67 
68 /* Semaphore functions using System V semaphores.  Each semaphore
69  * actually consists of three semaphores allocated as a unit under the
70  * same key.  Semaphore 0 (SYSVSEM_SEM) is the actual semaphore, it is
71  * initialized to max_acquire and decremented as processes acquire it.
72  * The value of semaphore 1 (SYSVSEM_USAGE) is a count of the number
73  * of processes using the semaphore.  After calling semget(), if a
74  * process finds that the usage count is 1, it will set the value of
75  * SYSVSEM_SEM to max_acquire.  This allows max_acquire to be set and
76  * track the PHP code without having a global init routine or external
77  * semaphore init code.  Except see the bug regarding a race condition
78  * php_sysvsem_get().  Semaphore 2 (SYSVSEM_SETVAL) serializes the
79  * calls to GETVAL SYSVSEM_USAGE and SETVAL SYSVSEM_SEM.  It can be
80  * acquired only when it is zero.
81  */
82 
83 #define SYSVSEM_SEM		0
84 #define SYSVSEM_USAGE	1
85 #define SYSVSEM_SETVAL	2
86 
87 /* SysvSemaphore class */
88 
89 zend_class_entry *sysvsem_ce;
90 static zend_object_handlers sysvsem_object_handlers;
91 
sysvsem_from_obj(zend_object * obj)92 static inline sysvsem_sem *sysvsem_from_obj(zend_object *obj) {
93 	return (sysvsem_sem *)((char *)(obj) - XtOffsetOf(sysvsem_sem, std));
94 }
95 
96 #define Z_SYSVSEM_P(zv) sysvsem_from_obj(Z_OBJ_P(zv))
97 
sysvsem_create_object(zend_class_entry * class_type)98 static zend_object *sysvsem_create_object(zend_class_entry *class_type) {
99 	sysvsem_sem *intern = zend_object_alloc(sizeof(sysvsem_sem), class_type);
100 
101 	zend_object_std_init(&intern->std, class_type);
102 	object_properties_init(&intern->std, class_type);
103 
104 	return &intern->std;
105 }
106 
sysvsem_get_constructor(zend_object * object)107 static zend_function *sysvsem_get_constructor(zend_object *object) {
108 	zend_throw_error(NULL, "Cannot directly construct SysvSemaphore, use sem_get() instead");
109 	return NULL;
110 }
111 
sysvsem_free_obj(zend_object * object)112 static void sysvsem_free_obj(zend_object *object)
113 {
114 	sysvsem_sem *sem_ptr = sysvsem_from_obj(object);
115 	struct sembuf sop[2];
116 	int opcount = 1;
117 
118 	/*
119 	 * if count == -1, semaphore has been removed
120 	 * Need better way to handle this
121 	 */
122 	if (sem_ptr->count == -1 || !sem_ptr->auto_release) {
123 		zend_object_std_dtor(&sem_ptr->std);
124 		return;
125 	}
126 	/* Decrement the usage count. */
127 
128 	sop[0].sem_num = SYSVSEM_USAGE;
129 	sop[0].sem_op  = -1;
130 	sop[0].sem_flg = SEM_UNDO;
131 
132 	/* Release the semaphore if it has been acquired but not released. */
133 
134 	if (sem_ptr->count) {
135 
136 		sop[1].sem_num = SYSVSEM_SEM;
137 		sop[1].sem_op  = sem_ptr->count;
138 		sop[1].sem_flg = SEM_UNDO;
139 
140 		opcount++;
141 	}
142 
143 	semop(sem_ptr->semid, sop, opcount);
144 
145 	zend_object_std_dtor(&sem_ptr->std);
146 }
147 /* }}} */
148 
149 /* {{{ PHP_MINIT_FUNCTION */
PHP_MINIT_FUNCTION(sysvsem)150 PHP_MINIT_FUNCTION(sysvsem)
151 {
152 	sysvsem_ce = register_class_SysvSemaphore();
153 	sysvsem_ce->create_object = sysvsem_create_object;
154 	sysvsem_ce->default_object_handlers = &sysvsem_object_handlers;
155 
156 	memcpy(&sysvsem_object_handlers, &std_object_handlers, sizeof(zend_object_handlers));
157 	sysvsem_object_handlers.offset = XtOffsetOf(sysvsem_sem, std);
158 	sysvsem_object_handlers.free_obj = sysvsem_free_obj;
159 	sysvsem_object_handlers.get_constructor = sysvsem_get_constructor;
160 	sysvsem_object_handlers.clone_obj = NULL;
161 	sysvsem_object_handlers.compare = zend_objects_not_comparable;
162 
163 	return SUCCESS;
164 }
165 /* }}} */
166 
167 /* {{{ PHP_MINFO_FUNCTION */
PHP_MINFO_FUNCTION(sysvsem)168 PHP_MINFO_FUNCTION(sysvsem)
169 {
170 	php_info_print_table_start();
171 	php_info_print_table_row(2, "sysvsem support", "enabled");
172 	php_info_print_table_end();
173 }
174 /* }}} */
175 
176 #define SETVAL_WANTS_PTR
177 
178 #if defined(_AIX)
179 #undef SETVAL_WANTS_PTR
180 #endif
181 
182 /* {{{ Return an id for the semaphore with the given key, and allow max_acquire (default 1) processes to acquire it simultaneously */
PHP_FUNCTION(sem_get)183 PHP_FUNCTION(sem_get)
184 {
185 	zend_long key, max_acquire = 1, perm = 0666;
186 	bool auto_release = 1;
187 	int semid;
188 	struct sembuf sop[3];
189 	int count;
190 	sysvsem_sem *sem_ptr;
191 
192 	if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "l|llb", &key, &max_acquire, &perm, &auto_release)) {
193 		RETURN_THROWS();
194 	}
195 
196 	/* Get/create the semaphore.  Note that we rely on the semaphores
197 	 * being zeroed when they are created.  Despite the fact that
198 	 * the(?)  Linux semget() man page says they are not initialized,
199 	 * the kernel versions 2.0.x and 2.1.z do in fact zero them.
200 	 */
201 
202 	semid = semget(key, 3, perm|IPC_CREAT);
203 	if (semid == -1) {
204 		php_error_docref(NULL, E_WARNING, "Failed for key 0x" ZEND_XLONG_FMT ": %s", key, strerror(errno));
205 		RETURN_FALSE;
206 	}
207 
208 	/* Find out how many processes are using this semaphore.  Note
209 	 * that on Linux (at least) there is a race condition here because
210 	 * semaphore undo on process exit is not atomic, so we could
211 	 * acquire SYSVSEM_SETVAL before a crashed process has decremented
212 	 * SYSVSEM_USAGE in which case count will be greater than it
213 	 * should be and we won't set max_acquire.  Fortunately this
214 	 * doesn't actually matter in practice.
215 	 */
216 
217 	/* Wait for sem 1 to be zero . . . */
218 
219 	sop[0].sem_num = SYSVSEM_SETVAL;
220 	sop[0].sem_op  = 0;
221 	sop[0].sem_flg = 0;
222 
223 	/* . . . and increment it so it becomes non-zero . . . */
224 
225 	sop[1].sem_num = SYSVSEM_SETVAL;
226 	sop[1].sem_op  = 1;
227 	sop[1].sem_flg = SEM_UNDO;
228 
229 	/* . . . and increment the usage count. */
230 
231 	sop[2].sem_num = SYSVSEM_USAGE;
232 	sop[2].sem_op  = 1;
233 	sop[2].sem_flg = SEM_UNDO;
234 	while (semop(semid, sop, 3) == -1) {
235 		if (errno != EINTR) {
236 			php_error_docref(NULL, E_WARNING, "Failed acquiring SYSVSEM_SETVAL for key 0x" ZEND_XLONG_FMT ": %s", key, strerror(errno));
237 			break;
238 		}
239 	}
240 
241 	/* Get the usage count. */
242 	count = semctl(semid, SYSVSEM_USAGE, GETVAL, NULL);
243 	if (count == -1) {
244 		php_error_docref(NULL, E_WARNING, "Failed for key 0x" ZEND_XLONG_FMT ": %s", key, strerror(errno));
245 	}
246 
247 	/* If we are the only user, then take this opportunity to set the max. */
248 
249 	if (count == 1) {
250 #if HAVE_SEMUN
251 		/* This is correct for Linux which has union semun. */
252 		union semun semarg;
253 		semarg.val = max_acquire;
254 		if (semctl(semid, SYSVSEM_SEM, SETVAL, semarg) == -1) {
255 			php_error_docref(NULL, E_WARNING, "Failed for key 0x" ZEND_XLONG_FMT ": %s", key, strerror(errno));
256 		}
257 #elif defined(SETVAL_WANTS_PTR)
258 		/* This is correct for Solaris 2.6 which does not have union semun. */
259 		if (semctl(semid, SYSVSEM_SEM, SETVAL, &max_acquire) == -1) {
260 			php_error_docref(NULL, E_WARNING, "Failed for key 0x%lx: %s", key, strerror(errno));
261 		}
262 #else
263 		/* This works for i.e. AIX */
264 		if (semctl(semid, SYSVSEM_SEM, SETVAL, max_acquire) == -1) {
265 			php_error_docref(NULL, E_WARNING, "Failed for key 0x%lx: %s", key, strerror(errno));
266 		}
267 #endif
268 	}
269 
270 	/* Set semaphore 1 back to zero. */
271 
272 	sop[0].sem_num = SYSVSEM_SETVAL;
273 	sop[0].sem_op  = -1;
274 	sop[0].sem_flg = SEM_UNDO;
275 	while (semop(semid, sop, 1) == -1) {
276 		if (errno != EINTR) {
277 			php_error_docref(NULL, E_WARNING, "Failed releasing SYSVSEM_SETVAL for key 0x" ZEND_XLONG_FMT ": %s", key, strerror(errno));
278 			break;
279 		}
280 	}
281 
282 	object_init_ex(return_value, sysvsem_ce);
283 
284 	sem_ptr = Z_SYSVSEM_P(return_value);
285 	sem_ptr->key   = key;
286 	sem_ptr->semid = semid;
287 	sem_ptr->count = 0;
288 	sem_ptr->auto_release = (int) auto_release;
289 }
290 /* }}} */
291 
292 /* {{{ php_sysvsem_semop */
php_sysvsem_semop(INTERNAL_FUNCTION_PARAMETERS,int acquire)293 static void php_sysvsem_semop(INTERNAL_FUNCTION_PARAMETERS, int acquire)
294 {
295 	zval *arg_id;
296 	bool nowait = 0;
297 	sysvsem_sem *sem_ptr;
298 	struct sembuf sop;
299 
300 	if (acquire) {
301 		if (zend_parse_parameters(ZEND_NUM_ARGS(), "O|b", &arg_id, sysvsem_ce, &nowait) == FAILURE) {
302 			RETURN_THROWS();
303 		}
304 	} else {
305 		if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &arg_id, sysvsem_ce) == FAILURE) {
306 			RETURN_THROWS();
307 		}
308 	}
309 
310 	sem_ptr = Z_SYSVSEM_P(arg_id);
311 
312 	if (!acquire && sem_ptr->count == 0) {
313 		php_error_docref(NULL, E_WARNING, "SysV semaphore for key 0x%x is not currently acquired", sem_ptr->key);
314 		RETURN_FALSE;
315 	}
316 
317 	sop.sem_num = SYSVSEM_SEM;
318 	sop.sem_op  = acquire ? -1 : 1;
319 	sop.sem_flg = SEM_UNDO | (nowait ? IPC_NOWAIT : 0);
320 
321 	while (semop(sem_ptr->semid, &sop, 1) == -1) {
322 		if (errno != EINTR) {
323 			if (errno != EAGAIN) {
324 				php_error_docref(NULL, E_WARNING, "Failed to %s key 0x%x: %s", acquire ? "acquire" : "release", sem_ptr->key, strerror(errno));
325 			}
326 			RETURN_FALSE;
327 		}
328 	}
329 
330 	sem_ptr->count -= acquire ? -1 : 1;
331 	RETURN_TRUE;
332 }
333 /* }}} */
334 
335 /* {{{ Acquires the semaphore with the given id, blocking if necessary */
PHP_FUNCTION(sem_acquire)336 PHP_FUNCTION(sem_acquire)
337 {
338 	php_sysvsem_semop(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
339 }
340 /* }}} */
341 
342 /* {{{ Releases the semaphore with the given id */
PHP_FUNCTION(sem_release)343 PHP_FUNCTION(sem_release)
344 {
345 	php_sysvsem_semop(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
346 }
347 /* }}} */
348 
349 /* {{{ Removes semaphore from Unix systems */
350 
351 /*
352  * contributed by Gavin Sherry gavin@linuxworld.com.au
353  * Fri Mar 16 00:50:13 EST 2001
354  */
355 
PHP_FUNCTION(sem_remove)356 PHP_FUNCTION(sem_remove)
357 {
358 	zval *arg_id;
359 	sysvsem_sem *sem_ptr;
360 #if HAVE_SEMUN
361 	union semun un;
362 	struct semid_ds buf;
363 #endif
364 
365 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &arg_id, sysvsem_ce) == FAILURE) {
366 		RETURN_THROWS();
367 	}
368 
369 	sem_ptr = Z_SYSVSEM_P(arg_id);
370 
371 #if HAVE_SEMUN
372 	un.buf = &buf;
373 	if (semctl(sem_ptr->semid, 0, IPC_STAT, un) < 0) {
374 #else
375 	if (semctl(sem_ptr->semid, 0, IPC_STAT, NULL) < 0) {
376 #endif
377 		php_error_docref(NULL, E_WARNING, "SysV semaphore for key 0x%x does not (any longer) exist", sem_ptr->key);
378 		RETURN_FALSE;
379 	}
380 
381 #if HAVE_SEMUN
382 	if (semctl(sem_ptr->semid, 0, IPC_RMID, un) < 0) {
383 #else
384 	if (semctl(sem_ptr->semid, 0, IPC_RMID, NULL) < 0) {
385 #endif
386 		php_error_docref(NULL, E_WARNING, "Failed for SysV semaphore for key 0x%x: %s", sem_ptr->key, strerror(errno));
387 		RETURN_FALSE;
388 	}
389 
390 	/* let release_sysvsem_sem know we have removed
391 	 * the semaphore to avoid issues with releasing.
392 	 */
393 
394 	sem_ptr->count = -1;
395 	RETURN_TRUE;
396 }
397 
398 /* }}} */
399 
400 #endif /* HAVE_SYSVSEM */
401