Line data Source code
1 : #ifdef HAVE_CONFIG_H
2 : #include "config.h"
3 : #endif
4 :
5 : #include <errno.h>
6 : #include <time.h>
7 : #include <lua.h>
8 : #include <lauxlib.h>
9 :
10 : #include "php.h"
11 : #include "php_luasandbox.h"
12 : #include "luasandbox_timer.h"
13 :
14 : #ifndef LUASANDBOX_NO_CLOCK
15 : #include <semaphore.h>
16 : #include <pthread.h>
17 : #endif
18 :
19 : char luasandbox_timeout_message[] = "The maximum execution time for this script was exceeded";
20 :
21 : #ifdef LUASANDBOX_NO_CLOCK
22 :
23 : void luasandbox_timer_minit() {}
24 : void luasandbox_timer_mshutdown() {}
25 :
26 : void luasandbox_timer_create(luasandbox_timer_set * lts,
27 : php_luasandbox_obj * sandbox) {
28 : lts->is_paused = 0;
29 : }
30 : void luasandbox_timer_set_limit(luasandbox_timer_set * lts,
31 : struct timespec * timeout) {}
32 : int luasandbox_timer_enable_profiler(luasandbox_timer_set * lts, struct timespec * period) {
33 : return 0;
34 : }
35 : int luasandbox_timer_start(luasandbox_timer_set * lts) {
36 : return 1;
37 : }
38 : void luasandbox_timer_stop(luasandbox_timer_set * lts) {}
39 : void luasandbox_timer_destroy(luasandbox_timer_set * lts) {}
40 :
41 : void luasandbox_timer_get_usage(luasandbox_timer_set * lts, struct timespec * ts) {
42 : ts->tv_sec = ts->tv_nsec = 0;
43 : }
44 :
45 : void luasandbox_timer_pause(luasandbox_timer_set * lts) {
46 : lts->is_paused = 1;
47 : }
48 : void luasandbox_timer_unpause(luasandbox_timer_set * lts) {
49 : lts->is_paused = 0;
50 : }
51 : int luasandbox_timer_is_paused(luasandbox_timer_set * lts) {
52 : return lts->is_paused;
53 : }
54 :
55 : void luasandbox_timer_timeout_error(lua_State *L) {}
56 : int luasandbox_timer_is_expired(luasandbox_timer_set * lts) {
57 : return 0;
58 : }
59 :
60 :
61 : #else
62 :
63 : ZEND_EXTERN_MODULE_GLOBALS(luasandbox);
64 :
65 : enum {
66 : LUASANDBOX_TIMER_LIMITER,
67 : LUASANDBOX_TIMER_PROFILER
68 : };
69 :
70 : // Value 0 to 1. Lower means more reallocating, higher means slower lookup.
71 : #define TIMER_HASH_LOAD_FACTOR 0.75
72 : pthread_rwlock_t timer_hash_rwlock;
73 : luasandbox_timer **timer_hash;
74 : size_t timer_hash_entries, timer_hash_size;
75 : int timer_next_id = 1;
76 :
77 : static void luasandbox_timer_handle_event(union sigval sv);
78 : static void luasandbox_timer_handle_profiler(luasandbox_timer * lt);
79 : static void luasandbox_timer_handle_limiter(luasandbox_timer * lt);
80 : static luasandbox_timer * luasandbox_timer_create_one(
81 : php_luasandbox_obj * sandbox, int type);
82 : static luasandbox_timer * luasandbox_timer_alloc();
83 : static luasandbox_timer * luasandbox_timer_lookup(int id);
84 : static void luasandbox_timer_free(luasandbox_timer *lt);
85 : static void luasandbox_timer_timeout_hook(lua_State *L, lua_Debug *ar);
86 : static void luasandbox_timer_profiler_hook(lua_State *L, lua_Debug *ar);
87 : static void luasandbox_timer_set_one_time(luasandbox_timer * lt, struct timespec * ts);
88 : static void luasandbox_timer_set_periodic(luasandbox_timer * lt, struct timespec * period);
89 : static void luasandbox_timer_stop_one(luasandbox_timer * lt, struct timespec * remaining);
90 : static void luasandbox_update_usage(luasandbox_timer_set * lts);
91 :
92 1273 : static inline void luasandbox_timer_zero(struct timespec * ts)
93 : {
94 1273 : ts->tv_sec = ts->tv_nsec = 0;
95 1273 : }
96 :
97 1388702 : static inline int luasandbox_timer_is_zero(struct timespec * ts)
98 : {
99 1388702 : return ts->tv_sec == 0 && ts->tv_nsec == 0;
100 : }
101 :
102 2773548 : static inline void luasandbox_timer_subtract(
103 : struct timespec * a, const struct timespec * b)
104 : {
105 2773548 : a->tv_sec -= b->tv_sec;
106 2773548 : if (a->tv_nsec < b->tv_nsec) {
107 10 : a->tv_sec--;
108 10 : a->tv_nsec += 1000000000L - b->tv_nsec;
109 : } else {
110 2773538 : a->tv_nsec -= b->tv_nsec;
111 : }
112 2773548 : }
113 :
114 1386945 : static inline void luasandbox_timer_add(
115 : struct timespec * a, const struct timespec * b)
116 : {
117 1386945 : a->tv_sec += b->tv_sec;
118 1386945 : a->tv_nsec += b->tv_nsec;
119 1386945 : if (a->tv_nsec > 1000000000L) {
120 6 : a->tv_nsec -= 1000000000L;
121 6 : a->tv_sec++;
122 : }
123 1386945 : }
124 :
125 : // Note this function is not async-signal safe. If you need to call this from a
126 : // signal handler, you'll need to refactor the "Set a hook" part into a
127 : // separate function and call that from the signal handler instead.
128 90 : static void luasandbox_timer_handle_event(union sigval sv)
129 : {
130 : luasandbox_timer * lt;
131 :
132 : while (1) {
133 90 : lt = luasandbox_timer_lookup(sv.sival_int);
134 :
135 90 : if (!lt || !lt->sandbox) { // lt is invalid
136 0 : return;
137 : }
138 :
139 90 : if (!sem_wait(<->semaphore)) { // Got the semaphore!
140 90 : break;
141 : }
142 :
143 0 : if (errno != EINTR) { // Unexpected error, abort
144 0 : return;
145 : }
146 : }
147 :
148 90 : if (lt->type == LUASANDBOX_TIMER_PROFILER) {
149 66 : luasandbox_timer_handle_profiler(lt);
150 : } else {
151 24 : luasandbox_timer_handle_limiter(lt);
152 : }
153 90 : sem_post(<->semaphore);
154 : }
155 :
156 66 : static void luasandbox_timer_handle_profiler(luasandbox_timer * lt)
157 : {
158 : // It's necessary to leave the timer running while we're not actually in
159 : // Lua, and just ignore signals that occur outside it, because Linux timers
160 : // don't fire with any kind of precision. Timer delivery is routinely delayed
161 : // by milliseconds regardless of how short a time you ask for, and
162 : // timer_gettime() just returns 1ns after the timer should have been delivered.
163 : // So if a call takes 100us, there's no way to start a timer and have it be
164 : // reliably delivered to within the function body, regardless of what you set
165 : // it_value to.
166 66 : php_luasandbox_obj * sandbox = lt->sandbox;
167 66 : if (!sandbox || !sandbox->in_lua) {
168 1 : return;
169 : }
170 :
171 : // Set a hook which will record profiling data (but don't overwrite the timeout hook)
172 65 : if (!sandbox->timed_out) {
173 : int overrun;
174 65 : lua_State * L = sandbox->state;
175 65 : lua_sethook(L, luasandbox_timer_profiler_hook,
176 : LUA_MASKCOUNT | LUA_MASKCALL | LUA_MASKRET | LUA_MASKLINE, 1);
177 65 : overrun = timer_getoverrun(sandbox->timer.profiler_timer->timer);
178 65 : sandbox->timer.profiler_signal_count += overrun + 1;
179 65 : sandbox->timer.overrun_count += overrun;
180 :
181 : // Reset the hook if a timeout just occurred
182 65 : if (sandbox->timed_out) {
183 0 : lua_sethook(L, luasandbox_timer_timeout_hook,
184 : LUA_MASKCOUNT | LUA_MASKCALL | LUA_MASKRET | LUA_MASKLINE, 1);
185 : }
186 : }
187 : }
188 :
189 24 : static void luasandbox_timer_handle_limiter(luasandbox_timer * lt)
190 : {
191 24 : lua_State * L = lt->sandbox->state;
192 :
193 24 : luasandbox_timer_set * lts = <->sandbox->timer;
194 24 : if (luasandbox_timer_is_paused(lts)) {
195 : // The timer is paused. luasandbox_timer_unpause will reschedule when unpaused.
196 : // Note that we need to use lt->clock_id here since CLOCK_THREAD_CPUTIME_ID
197 : // would get the time usage of the timer thread rather than the Lua thread.
198 6 : clock_gettime(lt->clock_id, <s->limiter_expired_at);
199 18 : } else if (!luasandbox_timer_is_zero(<s->pause_delta)) {
200 : // The timer is not paused, but we have a pause delta. Reschedule.
201 4 : luasandbox_timer_subtract(<s->usage, <s->pause_delta);
202 4 : lts->limiter_remaining = lts->pause_delta;
203 4 : luasandbox_timer_zero(<s->pause_delta);
204 4 : luasandbox_timer_set_one_time(lts->limiter_timer, <s->limiter_remaining);
205 : } else {
206 : // Set a hook which will terminate the script execution in a graceful way
207 14 : lt->sandbox->timed_out = 1;
208 14 : lua_sethook(L, luasandbox_timer_timeout_hook,
209 : LUA_MASKCOUNT | LUA_MASKCALL | LUA_MASKRET | LUA_MASKLINE, 1);
210 : }
211 24 : }
212 :
213 14 : static void luasandbox_timer_timeout_hook(lua_State *L, lua_Debug *ar)
214 : {
215 : // Avoid infinite recursion
216 14 : lua_sethook(L, luasandbox_timer_timeout_hook, 0, 0);
217 : // Do a longjmp to report the timeout error
218 14 : luasandbox_timer_timeout_error(L);
219 0 : }
220 :
221 14 : void luasandbox_timer_timeout_error(lua_State *L)
222 : {
223 14 : lua_pushstring(L, luasandbox_timeout_message);
224 14 : luasandbox_wrap_fatal(L);
225 14 : lua_error(L);
226 0 : }
227 :
228 15 : static char * luasandbox_timer_get_cfunction_name(lua_State *L)
229 : {
230 : static char buffer[1024];
231 :
232 15 : lua_CFunction f = lua_tocfunction(L, -1);
233 15 : if (!f) {
234 0 : return NULL;
235 : }
236 15 : if (f != luasandbox_call_php) {
237 15 : return NULL;
238 : }
239 :
240 0 : lua_getupvalue(L, -1, 1);
241 :
242 0 : zval * callback_p = (zval*)lua_touserdata(L, -1);
243 0 : if (!callback_p) {
244 0 : return NULL;
245 : }
246 : zend_string * callback_name;
247 0 : zend_bool ok = zend_is_callable(callback_p, 0, &callback_name);
248 :
249 0 : if (ok) {
250 0 : snprintf(buffer, sizeof(buffer), "%s", ZSTR_VAL(callback_name));
251 0 : return buffer;
252 : } else {
253 0 : return NULL;
254 : }
255 : }
256 :
257 65 : static void luasandbox_timer_profiler_hook(lua_State *L, lua_Debug *ar)
258 : {
259 65 : lua_sethook(L, luasandbox_timer_profiler_hook, 0, 0);
260 :
261 65 : php_luasandbox_obj * sandbox = luasandbox_get_php_obj(L);
262 : lua_Debug debug;
263 65 : memset(&debug, 0, sizeof(debug));
264 :
265 : // Get and zero the signal count
266 : // If a signal occurs within this critical section, be careful not to lose the overrun count
267 65 : long signal_count = sandbox->timer.profiler_signal_count;
268 65 : sandbox->timer.profiler_signal_count -= signal_count;
269 :
270 65 : lua_getinfo(L, "Snlf", ar);
271 65 : const char * name = NULL;
272 65 : if (ar->what[0] == 'C') {
273 15 : name = luasandbox_timer_get_cfunction_name(L);
274 : }
275 65 : if (!name) {
276 65 : if (ar->namewhat[0] != '\0') {
277 65 : name = ar->name;
278 0 : } else if (ar->what[0] == 'm') {
279 0 : name = "[main chunk]";
280 : }
281 : }
282 65 : size_t prof_name_size = strlen(ar->short_src)
283 : + sizeof(ar->linedefined) * 4 + sizeof(" <:>");
284 65 : if (name) {
285 65 : prof_name_size += strlen(name);
286 : }
287 :
288 65 : zend_string *zstr = zend_string_alloc(prof_name_size, 0);
289 65 : char *prof_name = ZSTR_VAL(zstr);
290 :
291 65 : if (!name) {
292 0 : if (ar->linedefined > 0) {
293 0 : snprintf(prof_name, prof_name_size, "<%s:%d>", ar->short_src, ar->linedefined);
294 : } else {
295 0 : strcpy(prof_name, "?");
296 : }
297 : } else {
298 65 : if (ar->what[0] == 'm') {
299 0 : snprintf(prof_name, prof_name_size, "%s <%s>", name, ar->short_src);
300 65 : } else if (ar->linedefined > 0) {
301 50 : snprintf(prof_name, prof_name_size, "%s <%s:%d>", name, ar->short_src, ar->linedefined);
302 : } else {
303 15 : snprintf(prof_name, prof_name_size, "%s", name);
304 : }
305 : }
306 :
307 65 : luasandbox_timer_set * lts = &sandbox->timer;
308 65 : HashTable * ht = lts->function_counts;
309 65 : ZSTR_LEN(zstr) = strlen(prof_name);
310 65 : zval *elt = zend_hash_find(ht, zstr);
311 65 : if (elt != NULL) {
312 60 : ZVAL_LONG(elt, Z_LVAL_P(elt) + signal_count);
313 : } else {
314 : zval v;
315 5 : ZVAL_LONG(&v, signal_count);
316 5 : zend_hash_add(ht, zstr, &v);
317 : }
318 : zend_string_release(zstr);
319 :
320 65 : lts->total_count += signal_count;
321 65 : }
322 :
323 23 : void luasandbox_timer_minit()
324 : {
325 23 : timer_hash = NULL;
326 23 : timer_hash_entries = 0;
327 23 : timer_hash_size = 0;
328 :
329 23 : if (pthread_rwlock_init(&timer_hash_rwlock, NULL) != 0) {
330 0 : php_error_docref(NULL, E_ERROR,
331 0 : "Unable to allocate timer rwlock: %s", strerror(errno));
332 : }
333 23 : }
334 :
335 23 : void luasandbox_timer_mshutdown()
336 : {
337 : int status;
338 : size_t i;
339 :
340 23 : status = pthread_rwlock_wrlock(&timer_hash_rwlock);
341 23 : if (status != 0) {
342 : // Some other error
343 0 : php_error_docref(NULL, E_WARNING,
344 0 : "Unable to acquire timer rwlock for writing, leaking timers: %s", strerror(errno));
345 0 : return;
346 : }
347 :
348 23 : if (timer_hash) {
349 121 : for (i = 0; i < timer_hash_size; i++) {
350 110 : if (timer_hash[i]) {
351 0 : pefree(timer_hash[i], 1);
352 : }
353 : }
354 11 : pefree(timer_hash, 1);
355 : }
356 :
357 : // Ideally when this is called no other threads are trying to use the mutex...
358 23 : pthread_rwlock_unlock(&timer_hash_rwlock);
359 23 : pthread_rwlock_destroy(&timer_hash_rwlock);
360 : }
361 :
362 4 : int luasandbox_timer_enable_profiler(luasandbox_timer_set * lts, struct timespec * period)
363 : {
364 4 : if (lts->profiler_running) {
365 2 : luasandbox_timer_stop_one(lts->profiler_timer, NULL);
366 2 : lts->profiler_running = 0;
367 : }
368 4 : lts->profiler_period = *period;
369 4 : if (lts->function_counts) {
370 2 : zend_hash_destroy(lts->function_counts);
371 2 : FREE_HASHTABLE(lts->function_counts);
372 2 : lts->function_counts = NULL;
373 : }
374 4 : lts->total_count = 0;
375 4 : lts->overrun_count = 0;
376 4 : if (period->tv_sec || period->tv_nsec) {
377 3 : ALLOC_HASHTABLE(lts->function_counts);
378 3 : zend_hash_init(lts->function_counts, 0, NULL, NULL, 0);
379 3 : luasandbox_timer * timer = luasandbox_timer_create_one(
380 3 : lts->sandbox, LUASANDBOX_TIMER_PROFILER);
381 3 : if (!timer) {
382 0 : return 0;
383 : }
384 3 : lts->profiler_running = 1;
385 3 : lts->profiler_timer = timer;
386 3 : luasandbox_timer_set_periodic(timer, <s->profiler_period);
387 : }
388 4 : return 1;
389 : }
390 :
391 125 : void luasandbox_timer_create(luasandbox_timer_set * lts, php_luasandbox_obj * sandbox)
392 : {
393 125 : luasandbox_timer_zero(<s->usage);
394 125 : luasandbox_timer_zero(<s->limiter_limit);
395 125 : luasandbox_timer_zero(<s->limiter_remaining);
396 125 : luasandbox_timer_zero(<s->pause_start);
397 125 : luasandbox_timer_zero(<s->pause_delta);
398 125 : luasandbox_timer_zero(<s->limiter_expired_at);
399 125 : luasandbox_timer_zero(<s->profiler_period);
400 125 : lts->is_running = 0;
401 125 : lts->limiter_running = 0;
402 125 : lts->profiler_running = 0;
403 125 : lts->sandbox = sandbox;
404 125 : }
405 :
406 77 : void luasandbox_timer_set_limit(luasandbox_timer_set * lts,
407 : struct timespec * timeout)
408 : {
409 77 : int was_running = 0;
410 77 : int was_paused = luasandbox_timer_is_paused(lts);
411 77 : if (lts->is_running) {
412 1 : was_running = 1;
413 1 : luasandbox_timer_stop(lts);
414 : }
415 77 : lts->limiter_remaining = lts->limiter_limit = *timeout;
416 77 : luasandbox_timer_zero(<s->limiter_expired_at);
417 :
418 77 : if (was_running) {
419 1 : luasandbox_timer_start(lts);
420 : }
421 77 : if (was_paused) {
422 1 : luasandbox_timer_pause(lts);
423 : }
424 77 : }
425 :
426 176 : int luasandbox_timer_start(luasandbox_timer_set * lts)
427 : {
428 176 : if (lts->is_running) {
429 : // Already running
430 0 : return 1;
431 : }
432 176 : lts->is_running = 1;
433 : // Initialise usage timer
434 176 : clock_gettime(LUASANDBOX_CLOCK_ID, <s->usage_start);
435 :
436 : // Create limiter timer if requested
437 176 : if (!luasandbox_timer_is_zero(<s->limiter_remaining)) {
438 99 : luasandbox_timer * timer = luasandbox_timer_create_one(
439 99 : lts->sandbox, LUASANDBOX_TIMER_LIMITER);
440 99 : if (!timer) {
441 0 : lts->limiter_running = 0;
442 0 : return 0;
443 : }
444 99 : lts->limiter_timer = timer;
445 99 : lts->limiter_running = 1;
446 99 : luasandbox_timer_set_one_time(timer, <s->limiter_remaining);
447 : } else {
448 77 : lts->limiter_running = 0;
449 : }
450 176 : return 1;
451 : }
452 :
453 102 : static luasandbox_timer * luasandbox_timer_create_one(
454 : php_luasandbox_obj * sandbox, int type)
455 : {
456 : struct sigevent ev;
457 102 : luasandbox_timer * lt = luasandbox_timer_alloc();
458 102 : if (!lt) {
459 0 : return NULL;
460 : }
461 :
462 : // Make valgrind happy
463 102 : memset(&ev, 0, sizeof(ev));
464 :
465 102 : if (sem_init(<->semaphore, 0, 1) != 0) {
466 0 : php_error_docref(NULL, E_WARNING,
467 0 : "Unable to create semaphore: %s", strerror(errno));
468 0 : luasandbox_timer_free(lt);
469 0 : return NULL;
470 : }
471 102 : ev.sigev_notify = SIGEV_THREAD;
472 102 : ev.sigev_notify_function = luasandbox_timer_handle_event;
473 102 : lt->type = type;
474 102 : lt->sandbox = sandbox;
475 102 : ev.sigev_value.sival_int = lt->id;
476 :
477 102 : if (pthread_getcpuclockid(pthread_self(), <->clock_id) != 0) {
478 0 : php_error_docref(NULL, E_WARNING,
479 0 : "Unable to get thread clock ID: %s", strerror(errno));
480 0 : luasandbox_timer_free(lt);
481 0 : return NULL;
482 : }
483 :
484 102 : if (timer_create(lt->clock_id, &ev, <->timer) != 0) {
485 0 : php_error_docref(NULL, E_WARNING,
486 0 : "Unable to create timer: %s", strerror(errno));
487 0 : luasandbox_timer_free(lt);
488 0 : return NULL;
489 : }
490 102 : return lt;
491 : }
492 :
493 :
494 : /**
495 : * Set an interval timer.
496 : */
497 109 : static void luasandbox_timer_set_one_time(luasandbox_timer * lt, struct timespec * ts)
498 : {
499 : struct itimerspec its;
500 109 : luasandbox_timer_zero(&its.it_interval);
501 109 : its.it_value = *ts;
502 109 : if (luasandbox_timer_is_zero(&its.it_value)) {
503 : // Sanity check: make sure there is at least 1 nanosecond on the timer.
504 0 : its.it_value.tv_nsec = 1;
505 : }
506 109 : timer_settime(lt->timer, 0, &its, NULL);
507 109 : }
508 :
509 : /**
510 : * Set a periodic timer.
511 : */
512 3 : static void luasandbox_timer_set_periodic(luasandbox_timer * lt, struct timespec * period)
513 : {
514 : struct itimerspec its;
515 3 : its.it_interval = *period;
516 3 : its.it_value = *period;
517 3 : if (timer_settime(lt->timer, 0, &its, NULL) != SUCCESS) {
518 0 : php_error_docref(NULL, E_WARNING,
519 0 : "timer_settime(): %s", strerror(errno));
520 : }
521 3 : }
522 :
523 302 : void luasandbox_timer_stop(luasandbox_timer_set * lts)
524 : {
525 : struct timespec usage, delta;
526 :
527 302 : if (lts->is_running) {
528 176 : lts->is_running = 0;
529 : } else {
530 126 : return;
531 : }
532 :
533 : // Make sure timers aren't paused, and extract the delta
534 176 : luasandbox_timer_unpause(lts);
535 176 : delta = lts->pause_delta;
536 176 : luasandbox_timer_zero(<s->pause_delta);
537 :
538 : // Stop the limiter and save the time remaining
539 176 : if (lts->limiter_running) {
540 99 : luasandbox_timer_stop_one(lts->limiter_timer, <s->limiter_remaining);
541 99 : lts->limiter_running = 0;
542 99 : luasandbox_timer_add(<s->limiter_remaining, &delta);
543 : }
544 :
545 : // Update the usage
546 176 : luasandbox_update_usage(lts);
547 176 : clock_gettime(LUASANDBOX_CLOCK_ID, &usage);
548 176 : luasandbox_timer_subtract(&usage, <s->usage_start);
549 176 : luasandbox_timer_add(<s->usage, &usage);
550 176 : luasandbox_timer_subtract(<s->usage, &delta);
551 : }
552 :
553 102 : static void luasandbox_timer_stop_one(luasandbox_timer * lt, struct timespec * remaining)
554 : {
555 : static struct timespec zero = {0, 0};
556 : struct itimerspec its;
557 102 : timer_gettime(lt->timer, &its);
558 102 : if (remaining) {
559 99 : *remaining = its.it_value;
560 : }
561 :
562 102 : its.it_value = zero;
563 102 : its.it_interval = zero;
564 102 : if (timer_settime(lt->timer, 0, &its, NULL) != SUCCESS) {
565 0 : php_error_docref(NULL, E_WARNING,
566 0 : "timer_settime(): %s", strerror(errno));
567 : }
568 :
569 : // Invalidate the callback structure, delete the timer
570 102 : lt->sandbox = NULL;
571 : // If the timer event handler is running, wait for it to finish
572 : // before returning to the caller, otherwise the timer event handler
573 : // may find itself with a dangling pointer in its local scope.
574 : while (1) {
575 102 : if (sem_wait(<->semaphore) == SUCCESS) {
576 102 : sem_destroy(<->semaphore);
577 102 : break;
578 : }
579 0 : if (errno != EINTR) {
580 0 : php_error_docref(NULL, E_WARNING,
581 0 : "sem_wait(): %s", strerror(errno));
582 0 : break;
583 : }
584 : }
585 102 : if (timer_delete(lt->timer) != SUCCESS) {
586 0 : php_error_docref(NULL, E_WARNING,
587 0 : "timer_delete(): %s", strerror(errno));
588 : }
589 102 : luasandbox_timer_free(lt);
590 102 : }
591 :
592 294 : static inline int hash_for_id(int id)
593 : {
594 294 : return id * 131071;
595 : }
596 :
597 : // Don't call this directly, use luasandbox_timer_alloc()
598 102 : static void timer_hash_insert(luasandbox_timer *lt)
599 : {
600 : off_t i;
601 :
602 102 : for (i = hash_for_id(lt->id) % timer_hash_size; timer_hash[i]; i = (i + 1) % timer_hash_size);
603 102 : timer_hash[i] = lt;
604 102 : }
605 :
606 102 : static luasandbox_timer * luasandbox_timer_alloc()
607 : {
608 : int status;
609 : size_t old_hash_size;
610 : off_t i;
611 : luasandbox_timer *lt;
612 : luasandbox_timer **old_hash;
613 :
614 102 : status = pthread_rwlock_wrlock(&timer_hash_rwlock);
615 102 : if (status != SUCCESS) {
616 : // Some other error
617 0 : php_error_docref(NULL, E_WARNING,
618 0 : "Unable to acquire timer rwlock for writing: %s", strerror(errno));
619 0 : return NULL;
620 : }
621 :
622 : // Allocate the timer and set its id
623 102 : lt = (luasandbox_timer*)pecalloc(1, sizeof(*lt), 1);
624 102 : lt->id = timer_next_id;
625 102 : if (++timer_next_id < 0) {
626 0 : timer_next_id = 1;
627 : }
628 :
629 : // Is it time to increase the hash table size?
630 102 : timer_hash_entries++;
631 102 : if (timer_hash_entries >= timer_hash_size * TIMER_HASH_LOAD_FACTOR) {
632 11 : old_hash = timer_hash;
633 11 : old_hash_size = timer_hash_size;
634 11 : if (timer_hash_size == 0) {
635 11 : timer_hash_size = 10;
636 : } else {
637 0 : timer_hash_size = timer_hash_size * 2;
638 : }
639 11 : timer_hash = (luasandbox_timer**)pecalloc(timer_hash_size, sizeof(*timer_hash), 1);
640 11 : for (i = 0; i < old_hash_size; i++) {
641 0 : if (old_hash[i]) {
642 0 : timer_hash_insert(old_hash[i]);
643 : }
644 : }
645 : }
646 :
647 : // Insert the new timer into the hash table
648 102 : timer_hash_insert(lt);
649 :
650 102 : pthread_rwlock_unlock(&timer_hash_rwlock);
651 102 : return lt;
652 : }
653 :
654 90 : static luasandbox_timer *luasandbox_timer_lookup(int id)
655 : {
656 : off_t i;
657 : int status;
658 :
659 90 : if ( id <= 0 ) {
660 0 : return NULL;
661 : }
662 :
663 90 : status = pthread_rwlock_rdlock(&timer_hash_rwlock);
664 90 : if (status != SUCCESS) {
665 : // Some other error
666 0 : php_error_docref(NULL, E_WARNING,
667 0 : "Unable to acquire timer rwlock for reading: %s", strerror(errno));
668 0 : return NULL;
669 : }
670 :
671 90 : for (i = hash_for_id(id) % timer_hash_size; timer_hash[i]; i = (i + 1) % timer_hash_size) {
672 90 : if (timer_hash[i]->id == id) {
673 90 : pthread_rwlock_unlock(&timer_hash_rwlock);
674 90 : return timer_hash[i];
675 : }
676 : }
677 :
678 0 : pthread_rwlock_unlock(&timer_hash_rwlock);
679 0 : return NULL;
680 : }
681 :
682 102 : static void luasandbox_timer_free(luasandbox_timer *lt)
683 : {
684 : int id;
685 102 : off_t cur, remove = -1, wanted;
686 : int status;
687 :
688 102 : id = lt->id;
689 102 : lt->id = 0;
690 :
691 102 : status = pthread_rwlock_wrlock(&timer_hash_rwlock);
692 102 : if (status != SUCCESS) {
693 : // Some other error
694 0 : php_error_docref(NULL, E_WARNING,
695 0 : "Unable to acquire timer semaphore: %s", strerror(errno));
696 0 : return;
697 : }
698 :
699 : // Find the location of this timer in the hash, then move any applicable
700 : // timers after it over it.
701 204 : for (cur = hash_for_id(id) % timer_hash_size; timer_hash[cur]; cur = (cur + 1) % timer_hash_size) {
702 102 : if (timer_hash[cur] == lt) {
703 : // Found it! Start moving subsequent items in the cluster
704 102 : remove = cur;
705 0 : } else if (remove >= 0) {
706 : // Moving! The plan is to end the current cluster at 'remove', so
707 : // if the current element should live at or before 'remove'
708 : // (keeping wraparound in mind) move it there and remove this one
709 : // instead.
710 0 : wanted = hash_for_id(timer_hash[cur]->id) % timer_hash_size;
711 0 : if ( (remove <= cur) ? (wanted <= remove || wanted > cur) : (wanted <= remove && wanted > cur) ) {
712 0 : timer_hash[remove] = timer_hash[cur];
713 0 : remove = cur;
714 : }
715 : }
716 : }
717 102 : if (remove >= 0) {
718 102 : timer_hash[remove] = NULL;
719 102 : timer_hash_entries--;
720 : }
721 :
722 102 : pefree(lt, 1);
723 :
724 102 : pthread_rwlock_unlock(&timer_hash_rwlock);
725 : }
726 :
727 1386504 : void luasandbox_timer_get_usage(luasandbox_timer_set * lts, struct timespec * ts)
728 : {
729 : struct timespec delta;
730 :
731 1386504 : if (lts->is_running) {
732 1386474 : luasandbox_update_usage(lts);
733 : }
734 1386504 : *ts = lts->usage;
735 : // Subtract the pause delta from the usage
736 1386504 : luasandbox_timer_subtract(ts, <s->pause_delta);
737 : // If currently paused, subtract the time-since-pause too
738 1386504 : if (!luasandbox_timer_is_zero(<s->pause_start)) {
739 0 : clock_gettime(LUASANDBOX_CLOCK_ID, &delta);
740 0 : luasandbox_timer_subtract(&delta, <s->pause_start);
741 0 : luasandbox_timer_subtract(ts, &delta);
742 : }
743 1386504 : }
744 :
745 20 : void luasandbox_timer_pause(luasandbox_timer_set * lts) {
746 20 : if (luasandbox_timer_is_zero(<s->pause_start)) {
747 20 : clock_gettime(LUASANDBOX_CLOCK_ID, <s->pause_start);
748 : }
749 20 : }
750 :
751 951 : void luasandbox_timer_unpause(luasandbox_timer_set * lts) {
752 : struct timespec delta;
753 :
754 951 : if (!luasandbox_timer_is_zero(<s->pause_start)) {
755 20 : clock_gettime(LUASANDBOX_CLOCK_ID, &delta);
756 20 : luasandbox_timer_subtract(&delta, <s->pause_start);
757 :
758 20 : if (luasandbox_timer_is_zero(<s->limiter_expired_at)) {
759 : // Easy case, timer didn't expire while paused. Throw the whole delta
760 : // into pause_delta for later timer and usage adjustment.
761 14 : luasandbox_timer_add(<s->pause_delta, &delta);
762 14 : luasandbox_timer_zero(<s->pause_start);
763 : } else {
764 : // If the limit expired, we need to fold the whole
765 : // accumulated delta into usage immediately, and then restart the
766 : // timer with the portion before the expiry.
767 :
768 : // adjust usage
769 6 : luasandbox_timer_subtract(<s->usage, &delta);
770 6 : luasandbox_timer_subtract(<s->usage, <s->pause_delta);
771 :
772 : // calculate timer delta
773 6 : delta = lts->limiter_expired_at;
774 6 : luasandbox_timer_subtract(&delta, <s->pause_start);
775 6 : luasandbox_timer_add(&delta, <s->pause_delta);
776 :
777 : // Zero out pause vars and expired timestamp (since we handled it)
778 6 : luasandbox_timer_zero(<s->pause_start);
779 6 : luasandbox_timer_zero(<s->pause_delta);
780 6 : luasandbox_timer_zero(<s->limiter_expired_at);
781 :
782 : // Restart timer
783 6 : lts->limiter_remaining = delta;
784 6 : luasandbox_timer_set_one_time(lts->limiter_timer, <s->limiter_remaining);
785 : }
786 : }
787 951 : }
788 :
789 631 : int luasandbox_timer_is_paused(luasandbox_timer_set * lts) {
790 631 : return !luasandbox_timer_is_zero(<s->pause_start);
791 : }
792 :
793 175 : int luasandbox_timer_is_expired(luasandbox_timer_set * lts)
794 : {
795 175 : if (!luasandbox_timer_is_zero(<s->limiter_limit)) {
796 98 : if (luasandbox_timer_is_zero(<s->limiter_remaining)) {
797 0 : return 1;
798 : }
799 : }
800 175 : return 0;
801 : }
802 :
803 1386650 : static void luasandbox_update_usage(luasandbox_timer_set * lts)
804 : {
805 : struct timespec current, usage;
806 1386650 : clock_gettime(LUASANDBOX_CLOCK_ID, ¤t);
807 1386650 : usage = current;
808 1386650 : luasandbox_timer_subtract(&usage, <s->usage_start);
809 1386650 : luasandbox_timer_add(<s->usage, &usage);
810 1386650 : lts->usage_start = current;
811 1386650 : }
812 :
813 126 : void luasandbox_timer_destroy(luasandbox_timer_set * lts)
814 : {
815 126 : luasandbox_timer_stop(lts);
816 126 : if (lts->profiler_running) {
817 1 : luasandbox_timer_stop_one(lts->profiler_timer, NULL);
818 1 : lts->profiler_running = 0;
819 : }
820 126 : if (lts->function_counts) {
821 1 : zend_hash_destroy(lts->function_counts);
822 1 : FREE_HASHTABLE(lts->function_counts);
823 1 : lts->function_counts = NULL;
824 : }
825 126 : }
826 :
827 : #endif
|