/* +----------------------------------------------------------------------+ | Copyright (c) The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | https://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Kévin Dunglas | +----------------------------------------------------------------------+ */ #ifdef ZEND_MAX_EXECUTION_TIMERS #include #include #include #include #include #include #include # ifdef __FreeBSD__ # include # endif #include "zend.h" #include "zend_globals.h" #include "zend_portability.h" #if __has_feature(memory_sanitizer) # include #endif // Musl Libc defines this macro, glibc does not // According to "man 2 timer_create" this field should always be available, but it's not: https://sourceware.org/bugzilla/show_bug.cgi?id=27417 # ifndef sigev_notify_thread_id # define sigev_notify_thread_id _sigev_un._tid # endif // FreeBSD doesn't support CLOCK_BOOTTIME # ifdef __FreeBSD__ # define ZEND_MAX_EXECUTION_TIMERS_CLOCK CLOCK_MONOTONIC # else # define ZEND_MAX_EXECUTION_TIMERS_CLOCK CLOCK_BOOTTIME # endif ZEND_API void zend_max_execution_timer_init(void) /* {{{ */ { pid_t pid = getpid(); if (EG(pid) == pid) { return; } struct sigevent sev; sev.sigev_notify = SIGEV_THREAD_ID; sev.sigev_value.sival_ptr = &EG(max_execution_timer_timer); sev.sigev_signo = SIGRTMIN; # ifdef __FreeBSD__ sev.sigev_notify_thread_id = pthread_getthreadid_np(); # else sev.sigev_notify_thread_id = (pid_t) syscall(SYS_gettid); # endif #if __has_feature(memory_sanitizer) /* MSan does not intercept timer_create() */ __msan_unpoison(&EG(max_execution_timer_timer), sizeof(EG(max_execution_timer_timer))); #endif // Measure wall time instead of CPU time as originally planned now that it is possible https://github.com/php/php-src/pull/6504#issuecomment-1370303727 if (timer_create(ZEND_MAX_EXECUTION_TIMERS_CLOCK, &sev, &EG(max_execution_timer_timer)) != 0) { zend_strerror_noreturn(E_ERROR, errno, "Could not create timer"); } EG(pid) = pid; # ifdef MAX_EXECUTION_TIMERS_DEBUG fprintf(stderr, "Timer %#jx created on thread %d\n", (uintmax_t) EG(max_execution_timer_timer), sev.sigev_notify_thread_id); # endif sigaction(sev.sigev_signo, NULL, &EG(oldact)); } /* }}} */ void zend_max_execution_timer_settime(zend_long seconds) /* {{{ }*/ { /* Timer not initialized or shutdown. */ if (!EG(pid)) { return; } timer_t timer = EG(max_execution_timer_timer); // Prevent EINVAL error if (seconds < 0 || seconds > 999999999) { seconds = 0; } struct itimerspec its; its.it_value.tv_sec = seconds; its.it_value.tv_nsec = its.it_interval.tv_sec = its.it_interval.tv_nsec = 0; # ifdef MAX_EXECUTION_TIMERS_DEBUG fprintf(stderr, "Setting timer %#jx on thread %d (%ld seconds)...\n", (uintmax_t) timer, (pid_t) syscall(SYS_gettid), seconds); # endif if (timer_settime(timer, 0, &its, NULL) != 0) { zend_strerror_noreturn(E_ERROR, errno, "Could not set timer"); } } /* }}} */ void zend_max_execution_timer_shutdown(void) /* {{{ */ { /* Don't try to delete a timer created before a call to fork() */ if (EG(pid) != getpid()) { return; } EG(pid) = 0; timer_t timer = EG(max_execution_timer_timer); # ifdef MAX_EXECUTION_TIMERS_DEBUG fprintf(stderr, "Deleting timer %#jx on thread %d...\n", (uintmax_t) timer, (pid_t) syscall(SYS_gettid)); # endif int err = timer_delete(timer); if (err != 0) { zend_strerror_noreturn(E_ERROR, errno, "Could not delete timer"); } } /* }}}} */ #endif