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