1054 lines
29 KiB
C
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;
|
|
}
|