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 | Author: Kévin Dunglas <kevin@dunglas.dev> |
14 +----------------------------------------------------------------------+
15 */
16
17 #ifdef ZEND_MAX_EXECUTION_TIMERS
18
19 #include <stdio.h>
20 #include <signal.h>
21 #include <time.h>
22 #include <unistd.h>
23 #include <errno.h>
24 #include <sys/syscall.h>
25 #include <sys/types.h>
26 # ifdef __FreeBSD__
27 # include <pthread_np.h>
28 # endif
29
30 #include "zend.h"
31 #include "zend_globals.h"
32
33 // Musl Libc defines this macro, glibc does not
34 // 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
35 # ifndef sigev_notify_thread_id
36 # define sigev_notify_thread_id _sigev_un._tid
37 # endif
38
39 // FreeBSD doesn't support CLOCK_BOOTTIME
40 # ifdef __FreeBSD__
41 # define ZEND_MAX_EXECUTION_TIMERS_CLOCK CLOCK_MONOTONIC
42 # else
43 # define ZEND_MAX_EXECUTION_TIMERS_CLOCK CLOCK_BOOTTIME
44 # endif
45
zend_max_execution_timer_init(void)46 ZEND_API void zend_max_execution_timer_init(void) /* {{{ */
47 {
48 pid_t pid = getpid();
49
50 if (EG(pid) == pid) {
51 return;
52 }
53
54 struct sigevent sev;
55 sev.sigev_notify = SIGEV_THREAD_ID;
56 sev.sigev_value.sival_ptr = &EG(max_execution_timer_timer);
57 sev.sigev_signo = SIGRTMIN;
58 # ifdef __FreeBSD__
59 sev.sigev_notify_thread_id = pthread_getthreadid_np();
60 # else
61 sev.sigev_notify_thread_id = (pid_t) syscall(SYS_gettid);
62 # endif
63
64 // 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
65 if (timer_create(ZEND_MAX_EXECUTION_TIMERS_CLOCK, &sev, &EG(max_execution_timer_timer)) != 0) {
66 zend_strerror_noreturn(E_ERROR, errno, "Could not create timer");
67 }
68
69 EG(pid) = pid;
70
71 # ifdef MAX_EXECUTION_TIMERS_DEBUG
72 fprintf(stderr, "Timer %#jx created on thread %d\n", (uintmax_t) EG(max_execution_timer_timer), sev.sigev_notify_thread_id);
73 # endif
74
75 sigaction(sev.sigev_signo, NULL, &EG(oldact));
76 }
77 /* }}} */
78
zend_max_execution_timer_settime(zend_long seconds)79 void zend_max_execution_timer_settime(zend_long seconds) /* {{{ }*/
80 {
81 /* Timer not initialized or shutdown. */
82 if (!EG(pid)) {
83 return;
84 }
85
86 timer_t timer = EG(max_execution_timer_timer);
87
88 // Prevent EINVAL error
89 if (seconds < 0 || seconds > 999999999) {
90 seconds = 0;
91 }
92
93 struct itimerspec its;
94 its.it_value.tv_sec = seconds;
95 its.it_value.tv_nsec = its.it_interval.tv_sec = its.it_interval.tv_nsec = 0;
96
97 # ifdef MAX_EXECUTION_TIMERS_DEBUG
98 fprintf(stderr, "Setting timer %#jx on thread %d (%ld seconds)...\n", (uintmax_t) timer, (pid_t) syscall(SYS_gettid), seconds);
99 # endif
100
101 if (timer_settime(timer, 0, &its, NULL) != 0) {
102 zend_strerror_noreturn(E_ERROR, errno, "Could not set timer");
103 }
104 }
105 /* }}} */
106
zend_max_execution_timer_shutdown(void)107 void zend_max_execution_timer_shutdown(void) /* {{{ */
108 {
109 /* Don't try to delete a timer created before a call to fork() */
110 if (EG(pid) != getpid()) {
111 return;
112 }
113
114 EG(pid) = 0;
115
116 timer_t timer = EG(max_execution_timer_timer);
117
118 # ifdef MAX_EXECUTION_TIMERS_DEBUG
119 fprintf(stderr, "Deleting timer %#jx on thread %d...\n", (uintmax_t) timer, (pid_t) syscall(SYS_gettid));
120 # endif
121
122 int err = timer_delete(timer);
123 if (err != 0) {
124 zend_strerror_noreturn(E_ERROR, errno, "Could not delete timer");
125 }
126 }
127 /* }}}} */
128
129 #endif
130