tuxdock/tuxreaperdasm.c
mrkmntal f568624956
Some checks failed
Build & Upload tux-dock / build (push) Has been cancelled
Finish of changes for 0.4, unique non POSIX signal handling for nginx and php-fpm added
2026-08-29 17:59:30 -04:00

1054 lines
29 KiB
C

struct timespec {
long tv_sec;
long tv_nsec;
};
struct k_sigaction {
void (*handler)(int);
unsigned long flags;
void (*restorer)(void);
unsigned long mask;
};
#if defined(__x86_64__)
#define SA_RESTORER_FLAG 0x04000000UL
void restore_rt(void);
#elif defined(__aarch64__)
#define SA_RESTORER_FLAG 0UL
#define restore_rt ((void (*)(void))0)
#endif
#if defined(__x86_64__)
static inline long sys_write(int fd, const void *buf, long n) {
long ret;
__asm__ volatile (
"syscall"
: "=a"(ret)
: "a"(1), "D"((long)fd), "S"(buf), "d"(n)
: "rcx", "r11", "memory"
);
return ret;
}
static inline long sys_exit_group(int status) {
long ret;
__asm__ volatile (
"syscall"
: "=a"(ret)
: "a"(231), "D"((long)status)
: "rcx", "r11", "memory"
);
return ret;
}
static inline long sys_fork(void) {
long ret;
__asm__ volatile (
"syscall"
: "=a"(ret)
: "a"(57)
: "rcx", "r11", "memory"
);
return ret;
}
static inline long sys_execve(const char *path, char *const argv[], char *const envp[]) {
long ret;
__asm__ volatile (
"syscall"
: "=a"(ret)
: "a"(59), "D"(path), "S"(argv), "d"(envp)
: "rcx", "r11", "memory"
);
return ret;
}
static inline long sys_wait4(long pid, int *status, int options, void *rusage) {
long ret;
register long r10 __asm__("r10") = (long)rusage;
__asm__ volatile (
"syscall"
: "=a"(ret)
: "a"(61), "D"(pid), "S"(status), "d"(options), "r"(r10)
: "rcx", "r11", "memory"
);
return ret;
}
static inline long sys_kill(long pid, int sig) {
long ret;
__asm__ volatile (
"syscall"
: "=a"(ret)
: "a"(62), "D"(pid), "S"((long)sig)
: "rcx", "r11", "memory"
);
return ret;
}
static inline long sys_prctl(int option, unsigned long arg2, unsigned long arg3,
unsigned long arg4, unsigned long arg5) {
long ret;
register long r10 __asm__("r10") = (long)arg4;
register long r8 __asm__("r8") = (long)arg5;
__asm__ volatile (
"syscall"
: "=a"(ret)
: "a"(157), "D"((long)option), "S"(arg2), "d"(arg3), "r"(r10), "r"(r8)
: "rcx", "r11", "memory"
);
return ret;
}
static inline long sys_rt_sigaction(int sig, const struct k_sigaction *act,
struct k_sigaction *oldact, unsigned long sigsetsize) {
long ret;
register long r10 __asm__("r10") = (long)sigsetsize;
__asm__ volatile (
"syscall"
: "=a"(ret)
: "a"(13), "D"((long)sig), "S"(act), "d"(oldact), "r"(r10)
: "rcx", "r11", "memory"
);
return ret;
}
static inline long sys_rt_sigprocmask(int how, const unsigned long *set,
unsigned long *oldset, unsigned long sigsetsize) {
long ret;
register long r10 __asm__("r10") = (long)sigsetsize;
__asm__ volatile (
"syscall"
: "=a"(ret)
: "a"(14), "D"((long)how), "S"(set), "d"(oldset), "r"(r10)
: "rcx", "r11", "memory"
);
return ret;
}
static inline long sys_rt_sigsuspend(const unsigned long *mask, unsigned long sigsetsize) {
long ret;
__asm__ volatile (
"syscall"
: "=a"(ret)
: "a"(130), "D"(mask), "S"(sigsetsize)
: "rcx", "r11", "memory"
);
return ret;
}
static inline long sys_setpgid(long pid, long pgid) {
long ret;
__asm__ volatile (
"syscall"
: "=a"(ret)
: "a"(109), "D"(pid), "S"(pgid)
: "rcx", "r11", "memory"
);
return ret;
}
static inline long sys_openat(int dirfd, const char *path, int flags, int mode) {
long ret;
register long r10 __asm__("r10") = mode;
__asm__ volatile (
"syscall"
: "=a"(ret)
: "a"(257), "D"((long)dirfd), "S"(path), "d"(flags), "r"(r10)
: "rcx", "r11", "memory"
);
return ret;
}
static inline long sys_getdents64(int fd, void *dirp, unsigned int count) {
long ret;
__asm__ volatile (
"syscall"
: "=a"(ret)
: "a"(217), "D"((long)fd), "S"(dirp), "d"(count)
: "rcx", "r11", "memory"
);
return ret;
}
static inline long sys_readlink(const char *path, char *buf, long bufsiz) {
long ret;
__asm__ volatile (
"syscall"
: "=a"(ret)
: "a"(89), "D"(path), "S"(buf), "d"(bufsiz)
: "rcx", "r11", "memory"
);
return ret;
}
static inline long sys_close(int fd) {
long ret;
__asm__ volatile (
"syscall"
: "=a"(ret)
: "a"(3), "D"(fd)
: "rcx", "r11", "memory"
);
return ret;
}
static inline long sys_clock_gettime(int clk_id, struct timespec *tp) {
long ret;
__asm__ volatile (
"syscall"
: "=a"(ret)
: "a"(228), "D"((long)clk_id), "S"(tp)
: "rcx", "r11", "memory"
);
return ret;
}
static inline long sys_nanosleep(const struct timespec *req, struct timespec *rem) {
long ret;
__asm__ volatile (
"syscall"
: "=a"(ret)
: "a"(35), "D"(req), "S"(rem)
: "rcx", "r11", "memory"
);
return ret;
}
__asm__(
".text\n"
".globl restore_rt\n"
"restore_rt:\n"
" movq $15, %rax\n"
" syscall\n"
);
__asm__(
".text\n"
".globl _start\n"
"_start:\n"
" xorl %ebp, %ebp\n"
" movq (%rsp), %rdi\n" /* argc */
" leaq 8(%rsp), %rsi\n" /* argv */
" movq %rsi, %rdx\n" /* scan pointer = argv */
"1:\n"
" addq $8, %rdx\n"
" cmpq $0, (%rdx)\n"
" jne 1b\n"
" addq $8, %rdx\n" /* envp */
" andq $-16, %rsp\n" /* align stack */
" call main\n"
" movq %rax, %rdi\n"
" movq $231, %rax\n" /* exit_group */
" syscall\n"
);
#elif defined(__aarch64__)
static inline long sys_write(int fd, const void *buf, long n) {
register long x0 __asm__("x0") = fd;
register long x1 __asm__("x1") = (long)buf;
register long x2 __asm__("x2") = n;
register long x8 __asm__("x8") = 64;
__asm__ volatile (
"svc #0"
: "+r"(x0)
: "r"(x1), "r"(x2), "r"(x8)
: "memory", "cc"
);
return x0;
}
static inline long sys_exit_group(int status) {
register long x0 __asm__("x0") = status;
register long x8 __asm__("x8") = 94;
__asm__ volatile (
"svc #0"
: "+r"(x0)
: "r"(x8)
: "memory", "cc"
);
return x0;
}
static inline long sys_fork(void) {
/* AArch64 has no legacy fork syscall; clone(SIGCHLD, 0) gives fork semantics. */
register long x0 __asm__("x0") = 17; /* SIGCHLD */
register long x1 __asm__("x1") = 0; /* newsp = 0 */
register long x8 __asm__("x8") = 220; /* __NR_clone */
__asm__ volatile (
"svc #0"
: "+r"(x0)
: "r"(x1), "r"(x8)
: "memory", "cc"
);
return x0;
}
static inline long sys_execve(const char *path, char *const argv[], char *const envp[]) {
register long x0 __asm__("x0") = (long)path;
register long x1 __asm__("x1") = (long)argv;
register long x2 __asm__("x2") = (long)envp;
register long x8 __asm__("x8") = 221;
__asm__ volatile (
"svc #0"
: "+r"(x0)
: "r"(x1), "r"(x2), "r"(x8)
: "memory", "cc"
);
return x0;
}
static inline long sys_wait4(long pid, int *status, int options, void *rusage) {
register long x0 __asm__("x0") = pid;
register long x1 __asm__("x1") = (long)status;
register long x2 __asm__("x2") = options;
register long x3 __asm__("x3") = (long)rusage;
register long x8 __asm__("x8") = 260;
__asm__ volatile (
"svc #0"
: "+r"(x0)
: "r"(x1), "r"(x2), "r"(x3), "r"(x8)
: "memory", "cc"
);
return x0;
}
static inline long sys_kill(long pid, int sig) {
register long x0 __asm__("x0") = pid;
register long x1 __asm__("x1") = sig;
register long x8 __asm__("x8") = 129;
__asm__ volatile (
"svc #0"
: "+r"(x0)
: "r"(x1), "r"(x8)
: "memory", "cc"
);
return x0;
}
static inline long sys_prctl(int option, unsigned long arg2, unsigned long arg3,
unsigned long arg4, unsigned long arg5) {
register long x0 __asm__("x0") = option;
register long x1 __asm__("x1") = arg2;
register long x2 __asm__("x2") = arg3;
register long x3 __asm__("x3") = arg4;
register long x4 __asm__("x4") = arg5;
register long x8 __asm__("x8") = 167;
__asm__ volatile (
"svc #0"
: "+r"(x0)
: "r"(x1), "r"(x2), "r"(x3), "r"(x4), "r"(x8)
: "memory", "cc"
);
return x0;
}
static inline long sys_rt_sigaction(int sig, const struct k_sigaction *act,
struct k_sigaction *oldact, unsigned long sigsetsize) {
register long x0 __asm__("x0") = sig;
register long x1 __asm__("x1") = (long)act;
register long x2 __asm__("x2") = (long)oldact;
register long x3 __asm__("x3") = sigsetsize;
register long x8 __asm__("x8") = 134;
__asm__ volatile (
"svc #0"
: "+r"(x0)
: "r"(x1), "r"(x2), "r"(x3), "r"(x8)
: "memory", "cc"
);
return x0;
}
static inline long sys_rt_sigprocmask(int how, const unsigned long *set,
unsigned long *oldset, unsigned long sigsetsize) {
register long x0 __asm__("x0") = how;
register long x1 __asm__("x1") = (long)set;
register long x2 __asm__("x2") = (long)oldset;
register long x3 __asm__("x3") = sigsetsize;
register long x8 __asm__("x8") = 135;
__asm__ volatile (
"svc #0"
: "+r"(x0)
: "r"(x1), "r"(x2), "r"(x3), "r"(x8)
: "memory", "cc"
);
return x0;
}
static inline long sys_rt_sigsuspend(const unsigned long *mask, unsigned long sigsetsize) {
register long x0 __asm__("x0") = (long)mask;
register long x1 __asm__("x1") = sigsetsize;
register long x8 __asm__("x8") = 133;
__asm__ volatile (
"svc #0"
: "+r"(x0)
: "r"(x1), "r"(x8)
: "memory", "cc"
);
return x0;
}
static inline long sys_setpgid(long pid, long pgid) {
register long x0 __asm__("x0") = pid;
register long x1 __asm__("x1") = pgid;
register long x8 __asm__("x8") = 154;
__asm__ volatile (
"svc #0"
: "+r"(x0)
: "r"(x1), "r"(x8)
: "memory", "cc"
);
return x0;
}
static inline long sys_openat(int dirfd, const char *path, int flags, int mode) {
register long x0 __asm__("x0") = dirfd;
register long x1 __asm__("x1") = (long)path;
register long x2 __asm__("x2") = flags;
register long x3 __asm__("x3") = mode;
register long x8 __asm__("x8") = 56;
__asm__ volatile (
"svc #0"
: "+r"(x0)
: "r"(x1), "r"(x2), "r"(x3), "r"(x8)
: "memory", "cc"
);
return x0;
}
static inline long sys_getdents64(int fd, void *dirp, unsigned int count) {
register long x0 __asm__("x0") = fd;
register long x1 __asm__("x1") = (long)dirp;
register long x2 __asm__("x2") = count;
register long x8 __asm__("x8") = 61;
__asm__ volatile (
"svc #0"
: "+r"(x0)
: "r"(x1), "r"(x2), "r"(x8)
: "memory", "cc"
);
return x0;
}
static inline long sys_readlink(const char *path, char *buf, long bufsiz) {
/* AArch64 has no plain readlink syscall; readlinkat is the ABI. */
register long x0 __asm__("x0") = -100; /* AT_FDCWD */
register long x1 __asm__("x1") = (long)path;
register long x2 __asm__("x2") = (long)buf;
register long x3 __asm__("x3") = bufsiz;
register long x8 __asm__("x8") = 78; /* __NR_readlinkat */
__asm__ volatile (
"svc #0"
: "+r"(x0)
: "r"(x1), "r"(x2), "r"(x3), "r"(x8)
: "memory", "cc"
);
return x0;
}
static inline long sys_close(int fd) {
register long x0 __asm__("x0") = fd;
register long x8 __asm__("x8") = 57;
__asm__ volatile (
"svc #0"
: "+r"(x0)
: "r"(x8)
: "memory", "cc"
);
return x0;
}
static inline long sys_clock_gettime(int clk_id, struct timespec *tp) {
register long x0 __asm__("x0") = (long)clk_id;
register long x1 __asm__("x1") = (long)tp;
register long x8 __asm__("x8") = 113; /* __NR_clock_gettime */
__asm__ volatile (
"svc #0"
: "+r"(x0)
: "r"(x1), "r"(x8)
: "memory", "cc"
);
return x0;
}
static inline long sys_nanosleep(const struct timespec *req, struct timespec *rem) {
register long x0 __asm__("x0") = (long)req;
register long x1 __asm__("x1") = (long)rem;
register long x8 __asm__("x8") = 101; /* __NR_nanosleep */
__asm__ volatile (
"svc #0"
: "+r"(x0)
: "r"(x1), "r"(x8)
: "memory", "cc"
);
return x0;
}
__asm__(
".text\n"
".globl _start\n"
"_start:\n"
" ldr x0, [sp]\n" /* argc */
" add x1, sp, #8\n" /* argv */
" mov x2, x1\n" /* scan pointer = argv */
"1:\n"
" ldr x3, [x2]\n"
" add x2, x2, #8\n"
" cbnz x3, 1b\n" /* x2 now points to envp */
" mov x4, sp\n"
" bic x4, x4, #15\n" /* align stack via scratch register */
" mov sp, x4\n"
" bl main\n"
" mov x8, #94\n" /* exit_group */
" svc #0\n"
);
#else
#error "tuxreaperdasm.c supports x86_64 and aarch64 only"
#endif
static long my_strlen(const char *s) {
long n = 0;
while (s[n]) n++;
return n;
}
static int my_strncmp(const char *a, const char *b, long n) {
for (long i = 0; i < n; i++) {
unsigned char ca = a[i];
unsigned char cb = b[i];
if (ca != cb) return (int)ca - (int)cb;
if (ca == 0) return 0;
}
return 0;
}
static const char *my_strchr(const char *s, char c) {
while (*s) {
if (*s == c) return s;
s++;
}
return 0;
}
static void my_memcpy(char *dst, const char *src, long n) {
for (long i = 0; i < n; i++) dst[i] = src[i];
}
static const char *my_getenv(const char *name, char *const *envp) {
long nlen = my_strlen(name);
for (int i = 0; envp[i]; i++) {
if (my_strncmp(envp[i], name, nlen) == 0 && envp[i][nlen] == '=') {
return envp[i] + nlen + 1;
}
}
return 0;
}
static void my_execvp(const char *file, char *const argv[], char *const envp[]) {
if (my_strchr(file, '/')) {
sys_execve(file, argv, envp);
return;
}
const char *path = my_getenv("PATH", envp);
if (!path) path = "/bin:/usr/bin";
long flen = my_strlen(file);
const char *p = path;
while (*p) {
const char *colon = my_strchr(p, ':');
long dirlen = colon ? (colon - p) : my_strlen(p);
char full[512];
if (dirlen + 1 + flen + 1 <= (long)sizeof(full)) {
my_memcpy(full, p, dirlen);
full[dirlen] = '/';
my_memcpy(full + dirlen + 1, file, flen);
full[dirlen + 1 + flen] = '\0';
sys_execve(full, argv, envp);
}
if (!colon) break;
p = colon + 1;
}
}
/* Apache hijacks SIGWINCH for graceful shutdown. When the outside world
sends SIGTERM, we translate it to SIGWINCH for Apache processes.
Nginx and PHP-FPM both use SIGQUIT for graceful shutdown, so we
translate SIGTERM to SIGQUIT for those processes. Everyone else
receives the original signal. */
static const char * const apache_exes[] = {
"/usr/sbin/apache2",
"/usr/sbin/httpd",
"/usr/local/apache2/bin/httpd",
0
};
#define APACHE_IN_SIG 15 /* SIGTERM */
#define APACHE_OUT_SIG 28 /* SIGWINCH */
static const char * const nginx_exes[] = {
"/usr/sbin/nginx",
"/usr/local/nginx/sbin/nginx",
0
};
#define NGINX_IN_SIG 15 /* SIGTERM */
#define NGINX_OUT_SIG 3 /* SIGQUIT */
/* PHP-FPM installs versioned binaries like /usr/sbin/php-fpm8.4, so these
rules use prefix matching to catch any version without maintaining a list. */
static const char * const phpfpm_exes[] = {
"/usr/sbin/php-fpm",
"/usr/local/sbin/php-fpm",
0
};
#define PHPFPM_IN_SIG 15 /* SIGTERM */
#define PHPFPM_OUT_SIG 3 /* SIGQUIT */
struct sig_rule {
const char *const *target_exes;
int out_sig;
int prefix_match; /* 0 = exact, 1 = prefix */
};
#define DESCENDANT_TIMEOUT_SECONDS 60
#define BROADCAST_SCAN_DELAY_US 100000
#define DESCENDANT_POLL_INTERVAL_US 50000
#define AT_FDCWD -100
#define O_RDONLY 0
#if defined(__x86_64__)
#define O_DIRECTORY 00200000 /* 0x10000 */
#elif defined(__aarch64__)
#define O_DIRECTORY 040000 /* 0x4000 */
#else
#error "tuxreaperdasm.c: unknown O_DIRECTORY for this architecture"
#endif
struct linux_dirent64 {
unsigned long long d_ino;
long long d_off;
unsigned short d_reclen;
unsigned char d_type;
char d_name[];
};
#ifdef TUXREAPERD_DEBUG
static void debug_write(const char *s) {
sys_write(2, s, my_strlen(s));
}
static void debug_write_int(long n) {
char buf[32];
int i = 0;
if (n < 0) {
buf[i++] = '-';
n = -n;
}
int start = i;
do {
buf[i++] = '0' + (n % 10);
n /= 10;
} while (n > 0);
for (int j = start, k = i - 1; j < k; j++, k--) {
char tmp = buf[j];
buf[j] = buf[k];
buf[k] = tmp;
}
sys_write(2, buf, i);
}
static void debug_msg(const char *msg) {
debug_write("[tuxreaperd] ");
debug_write(msg);
debug_write("\n");
}
#else
static void debug_write(const char *s) { (void)s; }
static void debug_write_int(long n) { (void)n; }
static void debug_msg(const char *msg) { (void)msg; }
#endif
static int my_strcmp(const char *a, const char *b) {
while (*a && (*a == *b)) {
a++;
b++;
}
return (unsigned char)*a - (unsigned char)*b;
}
static int is_digit(char c) {
return c >= '0' && c <= '9';
}
static long parse_pid(const char *s) {
long pid = 0;
while (*s) {
if (!is_digit(*s)) return -1;
pid = pid * 10 + (*s - '0');
s++;
}
return pid;
}
/* Iterate /proc, read each process's /proc/<pid>/exe symlink, and send
the first matching rule's out_sig to matching PIDs while sending in_sig
to everyone else. Skip PID 1 (the reaper itself). */
static void proc_remap_signal(int in_sig, const struct sig_rule *rules, int num_rules) {
int procfd = (int)sys_openat(AT_FDCWD, "/proc", O_RDONLY | O_DIRECTORY, 0);
#ifdef TUXREAPERD_DEBUG
debug_write("[tuxreaperd] proc_remap_signal openat fd=");
debug_write_int(procfd);
debug_write("\n");
#endif
if (procfd < 0) {
debug_msg("opendir(/proc) failed");
return;
}
char dirbuf[4096];
for (;;) {
long n = sys_getdents64(procfd, dirbuf, sizeof(dirbuf));
#ifdef TUXREAPERD_DEBUG
debug_write("[tuxreaperd] proc_remap getdents64 n=");
debug_write_int(n);
debug_write("\n");
#endif
if (n <= 0) break;
for (long pos = 0; pos < n;) {
struct linux_dirent64 *de = (struct linux_dirent64 *)(dirbuf + pos);
pos += de->d_reclen;
const char *name = de->d_name;
if (name[0] == '.') continue;
long pid = parse_pid(name);
if (pid <= 1) continue;
char path[32];
char *p = path;
const char *prefix = "/proc/";
for (long i = 0; prefix[i]; i++) *p++ = prefix[i];
const char *np = name;
while (*np) *p++ = *np++;
const char *suffix = "/exe";
for (long i = 0; suffix[i]; i++) *p++ = suffix[i];
*p = '\0';
char linkbuf[256];
long linklen = sys_readlink(path, linkbuf, sizeof(linkbuf) - 1);
#ifdef TUXREAPERD_DEBUG
debug_write("[tuxreaperd] proc_remap pid=");
debug_write_int(pid);
debug_write(" path=");
debug_write(path);
debug_write(" linklen=");
debug_write_int(linklen);
debug_write("\n");
#endif
if (linklen <= 0) continue;
linkbuf[linklen] = '\0';
int sent_sig = in_sig;
for (int r = 0; r < num_rules; r++) {
int matched = 0;
for (int i = 0; rules[r].target_exes[i]; i++) {
long target_len = my_strlen(rules[r].target_exes[i]);
if (rules[r].prefix_match) {
if (linklen >= target_len &&
my_strncmp(linkbuf, rules[r].target_exes[i], target_len) == 0) {
matched = 1;
break;
}
} else {
if (linklen == target_len &&
my_strcmp(linkbuf, rules[r].target_exes[i]) == 0) {
matched = 1;
break;
}
}
}
if (matched) {
sent_sig = rules[r].out_sig;
break;
}
}
#ifdef TUXREAPERD_DEBUG
debug_write("[tuxreaperd] scan pid=");
debug_write_int(pid);
debug_write(" exe=");
debug_write(linkbuf);
debug_write(" sig=");
debug_write_int(sent_sig);
debug_write("\n");
#endif
sys_kill(pid, sent_sig);
}
}
sys_close(procfd);
}
static void sleep_us(long us) {
struct timespec req;
req.tv_sec = us / 1000000;
req.tv_nsec = (us % 1000000) * 1000;
sys_nanosleep(&req, 0);
}
static void broadcast_signal(int in_sig, const struct sig_rule *rules, int num_rules) {
#ifdef TUXREAPERD_DEBUG
debug_write("[tuxreaperd] broadcasting in_sig=");
debug_write_int(in_sig);
debug_write("\n");
#endif
proc_remap_signal(in_sig, rules, num_rules);
sleep_us(BROADCAST_SCAN_DELAY_US);
proc_remap_signal(in_sig, rules, num_rules);
}
static int count_descendants(void) {
int procfd = (int)sys_openat(AT_FDCWD, "/proc", O_RDONLY | O_DIRECTORY, 0);
#ifdef TUXREAPERD_DEBUG
debug_write("[tuxreaperd] count_descendants openat fd=");
debug_write_int(procfd);
debug_write(" flags=0x");
debug_write_int(O_RDONLY | O_DIRECTORY);
debug_write("\n");
#endif
if (procfd < 0) return 0;
int count = 0;
char dirbuf[4096];
for (;;) {
long n = sys_getdents64(procfd, dirbuf, sizeof(dirbuf));
#ifdef TUXREAPERD_DEBUG
debug_write("[tuxreaperd] getdents64 n=");
debug_write_int(n);
debug_write("\n");
#endif
if (n <= 0) break;
for (long pos = 0; pos < n;) {
struct linux_dirent64 *de = (struct linux_dirent64 *)(dirbuf + pos);
pos += de->d_reclen;
const char *name = de->d_name;
#ifdef TUXREAPERD_DEBUG
debug_write("[tuxreaperd] dirent name=");
debug_write(name);
debug_write(" reclen=");
debug_write_int(de->d_reclen);
debug_write(" d_type=");
debug_write_int(de->d_type);
debug_write("\n");
#endif
if (name[0] == '.') continue;
long pid = parse_pid(name);
if (pid <= 1) continue;
count++;
}
}
sys_close(procfd);
return count;
}
static long long monotonic_ms(void) {
struct timespec ts;
if (sys_clock_gettime(1 /* CLOCK_MONOTONIC */, &ts) < 0) return 0;
return (long long)ts.tv_sec * 1000LL + ts.tv_nsec / 1000000LL;
}
static volatile long g_main_child = 0;
static volatile int g_child_exited = 0;
static volatile int g_main_status = 0;
static volatile int g_pending_signals = 0;
static void proxy_sig_handler(int sig) {
if (sig >= 1 && sig <= 31) {
g_pending_signals |= (1U << (sig - 1));
}
}
static void sigchld_handler(int sig) {
(void)sig;
int status;
long pid;
while ((pid = sys_wait4(-1, &status, 1 /* WNOHANG */, 0)) > 0) {
if (pid == g_main_child) {
g_child_exited = 1;
g_main_status = status;
}
}
}
static void drain_zombies(void) {
int status;
while (sys_wait4(-1, &status, 1 /* WNOHANG */, 0) > 0);
}
static void handle_pending_signals(void) {
int pending = g_pending_signals;
g_pending_signals = 0;
for (int sig = 1; sig <= 31; sig++) {
if (!(pending & (1U << (sig - 1)))) continue;
#ifdef TUXREAPERD_DEBUG
debug_write("[tuxreaperd] handling pending signal ");
debug_write_int(sig);
debug_write("\n");
#endif
if (sig == APACHE_IN_SIG || sig == NGINX_IN_SIG || sig == PHPFPM_IN_SIG) {
struct sig_rule rules[3];
int num_rules = 0;
if (sig == APACHE_IN_SIG) {
rules[num_rules].target_exes = apache_exes;
rules[num_rules].out_sig = APACHE_OUT_SIG;
rules[num_rules].prefix_match = 0;
num_rules++;
}
if (sig == NGINX_IN_SIG) {
rules[num_rules].target_exes = nginx_exes;
rules[num_rules].out_sig = NGINX_OUT_SIG;
rules[num_rules].prefix_match = 0;
num_rules++;
}
if (sig == PHPFPM_IN_SIG) {
rules[num_rules].target_exes = phpfpm_exes;
rules[num_rules].out_sig = PHPFPM_OUT_SIG;
rules[num_rules].prefix_match = 1;
num_rules++;
}
broadcast_signal(sig, rules, num_rules);
} else {
broadcast_signal(sig, 0, 0);
}
}
}
#define WIFEXITED(s) (((s) & 0x7f) == 0)
#define WEXITSTATUS(s) (((s) >> 8) & 0xff)
#define WIFSIGNALED(s) ((((s) & 0x7f) != 0) && (((s) & 0x7f) != 0x7f))
#define WTERMSIG(s) ((s) & 0x7f)
int main(int argc, char **argv, char **envp) {
if (argc < 2) {
static const char usage[] = "Usage: tuxreaperd <command> [args...]\n";
sys_write(2, usage, sizeof(usage) - 1);
return 1;
}
if (sys_prctl(36 /* PR_SET_CHILD_SUBREAPER */, 1, 0, 0, 0) < 0) {
static const char err[] = "prctl(PR_SET_CHILD_SUBREAPER) failed\n";
sys_write(2, err, sizeof(err) - 1);
return 1;
}
struct k_sigaction sa;
sa.handler = sigchld_handler;
sa.flags = 0x10000000 /* SA_RESTART */ | 1 /* SA_NOCLDSTOP */ | SA_RESTORER_FLAG;
sa.restorer = restore_rt;
sa.mask = 0;
sys_rt_sigaction(17 /* SIGCHLD */, &sa, 0, sizeof(sa.mask));
sa.handler = proxy_sig_handler;
sa.flags = 0x10000000 /* SA_RESTART */ | SA_RESTORER_FLAG;
sa.restorer = restore_rt;
sa.mask = 0;
sys_rt_sigaction(15 /* SIGTERM */, &sa, 0, sizeof(sa.mask));
sys_rt_sigaction(3 /* SIGQUIT */, &sa, 0, sizeof(sa.mask));
sys_rt_sigaction(2 /* SIGINT */, &sa, 0, sizeof(sa.mask));
sys_rt_sigaction(1 /* SIGHUP */, &sa, 0, sizeof(sa.mask));
sys_rt_sigaction(10 /* SIGUSR1 */, &sa, 0, sizeof(sa.mask));
sys_rt_sigaction(12 /* SIGUSR2 */, &sa, 0, sizeof(sa.mask));
unsigned long block_mask =
(1UL << (17 - 1)) | /* SIGCHLD */
(1UL << (15 - 1)) | /* SIGTERM */
(1UL << (3 - 1)) | /* SIGQUIT */
(1UL << (2 - 1)) | /* SIGINT */
(1UL << (1 - 1)) | /* SIGHUP */
(1UL << (10 - 1)) | /* SIGUSR1 */
(1UL << (12 - 1)); /* SIGUSR2 */
unsigned long old_mask;
sys_rt_sigprocmask(0 /* SIG_BLOCK */, &block_mask, &old_mask, sizeof(block_mask));
g_main_child = sys_fork();
if (g_main_child == 0) {
/* Become the leader of a new process group so signals can be
broadcast to the whole workload tree. */
sys_setpgid(0, 0);
sys_rt_sigprocmask(2 /* SIG_SETMASK */, &old_mask, 0, sizeof(old_mask));
my_execvp(argv[1], &argv[1], envp);
static const char err[] = "execvp failed\n";
sys_write(2, err, sizeof(err) - 1);
sys_exit_group(127);
}
#ifdef TUXREAPERD_DEBUG
debug_write("[tuxreaperd] started main_child=");
debug_write_int(g_main_child);
debug_write("\n");
#endif
long long child_exit_time_ms = 0;
while (1) {
if (g_child_exited) {
long long now = monotonic_ms();
if (child_exit_time_ms == 0) {
child_exit_time_ms = now;
debug_msg("main child exited, waiting up to 60s for descendants");
}
drain_zombies();
int descendants = count_descendants();
#ifdef TUXREAPERD_DEBUG
debug_write("[tuxreaperd] descendants=");
debug_write_int(descendants);
debug_write("\n");
#endif
if (descendants == 0) {
debug_msg("no descendants remaining, exiting cleanly");
break;
}
if (now - child_exit_time_ms >= DESCENDANT_TIMEOUT_SECONDS * 1000LL) {
debug_msg("descendant timeout reached, exiting");
break;
}
/* Poll briefly so the 60-second deadline can tick even if no
SIGCHLD or external signal arrives. handle_pending_signals()
catches any signal that was delivered while we slept. */
sleep_us(DESCENDANT_POLL_INTERVAL_US);
handle_pending_signals();
} else {
/* Workload active: block atomically with zero CPU wakeups until a
signal arrives. */
sys_rt_sigsuspend(&old_mask, sizeof(old_mask));
drain_zombies();
handle_pending_signals();
}
}
drain_zombies();
if (WIFEXITED(g_main_status)) {
return WEXITSTATUS(g_main_status);
} else if (WIFSIGNALED(g_main_status)) {
return 128 + WTERMSIG(g_main_status);
}
return 0;
}