LCOV - code coverage report
Current view: top level - src - timer.c (source / functions) Hit Total Coverage
Test: mediawiki/php/luasandbox test coverage report Lines: 323 404 80.0 %
Date: 2023-12-12 07:35:14 Functions: 34 34 100.0 %

          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(&lt->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(&lt->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 = &lt->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, &lts->limiter_expired_at);
     199          18 :     } else if (!luasandbox_timer_is_zero(&lts->pause_delta)) {
     200             :         // The timer is not paused, but we have a pause delta. Reschedule.
     201           4 :         luasandbox_timer_subtract(&lts->usage, &lts->pause_delta);
     202           4 :         lts->limiter_remaining = lts->pause_delta;
     203           4 :         luasandbox_timer_zero(&lts->pause_delta);
     204           4 :         luasandbox_timer_set_one_time(lts->limiter_timer, &lts->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, &lts->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(&lts->usage);
     394         125 :     luasandbox_timer_zero(&lts->limiter_limit);
     395         125 :     luasandbox_timer_zero(&lts->limiter_remaining);
     396         125 :     luasandbox_timer_zero(&lts->pause_start);
     397         125 :     luasandbox_timer_zero(&lts->pause_delta);
     398         125 :     luasandbox_timer_zero(&lts->limiter_expired_at);
     399         125 :     luasandbox_timer_zero(&lts->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(&lts->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, &lts->usage_start);
     435             : 
     436             :     // Create limiter timer if requested
     437         176 :     if (!luasandbox_timer_is_zero(&lts->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, &lts->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(&lt->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(), &lt->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, &lt->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(&lts->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, &lts->limiter_remaining);
     541          99 :         lts->limiter_running = 0;
     542          99 :         luasandbox_timer_add(&lts->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, &lts->usage_start);
     549         176 :     luasandbox_timer_add(&lts->usage, &usage);
     550         176 :     luasandbox_timer_subtract(&lts->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(&lt->semaphore) == SUCCESS) {
     576         102 :             sem_destroy(&lt->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, &lts->pause_delta);
     737             :     // If currently paused, subtract the time-since-pause too
     738     1386504 :     if (!luasandbox_timer_is_zero(&lts->pause_start)) {
     739           0 :         clock_gettime(LUASANDBOX_CLOCK_ID, &delta);
     740           0 :         luasandbox_timer_subtract(&delta, &lts->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(&lts->pause_start)) {
     747          20 :         clock_gettime(LUASANDBOX_CLOCK_ID, &lts->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(&lts->pause_start)) {
     755          20 :         clock_gettime(LUASANDBOX_CLOCK_ID, &delta);
     756          20 :         luasandbox_timer_subtract(&delta, &lts->pause_start);
     757             : 
     758          20 :         if (luasandbox_timer_is_zero(&lts->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(&lts->pause_delta, &delta);
     762          14 :             luasandbox_timer_zero(&lts->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(&lts->usage, &delta);
     770           6 :             luasandbox_timer_subtract(&lts->usage, &lts->pause_delta);
     771             : 
     772             :             // calculate timer delta
     773           6 :             delta = lts->limiter_expired_at;
     774           6 :             luasandbox_timer_subtract(&delta, &lts->pause_start);
     775           6 :             luasandbox_timer_add(&delta, &lts->pause_delta);
     776             : 
     777             :             // Zero out pause vars and expired timestamp (since we handled it)
     778           6 :             luasandbox_timer_zero(&lts->pause_start);
     779           6 :             luasandbox_timer_zero(&lts->pause_delta);
     780           6 :             luasandbox_timer_zero(&lts->limiter_expired_at);
     781             : 
     782             :             // Restart timer
     783           6 :             lts->limiter_remaining = delta;
     784           6 :             luasandbox_timer_set_one_time(lts->limiter_timer, &lts->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(&lts->pause_start);
     791             : }
     792             : 
     793         175 : int luasandbox_timer_is_expired(luasandbox_timer_set * lts)
     794             : {
     795         175 :     if (!luasandbox_timer_is_zero(&lts->limiter_limit)) {
     796          98 :         if (luasandbox_timer_is_zero(&lts->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, &current);
     807     1386650 :     usage = current;
     808     1386650 :     luasandbox_timer_subtract(&usage, &lts->usage_start);
     809     1386650 :     luasandbox_timer_add(&lts->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

Generated by: LCOV version 1.13