Files
die-with-parent/die-with-parent.c
2026-01-14 03:59:36 -08:00

204 lines
5.8 KiB
C

#define _XOPEN_SOURCE
#define _POSIX_C_SOURCE 200112L
#include "signal-names.h"
#include <ctype.h>
#include <errno.h>
#include <signal.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <unistd.h>
#define PID_MAX ((((size_t) 1) << (sizeof(pid_t) * 8)) - 1)
static const char *EXEC_NAME;
int eprintf(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
int c = 0;
c += fprintf(stderr, "%s: ", EXEC_NAME);
c += vfprintf(stderr, fmt, args);
c += fputc('\n', stderr);
va_end(args);
return c;
}
/*
* Arrange for the current process to be sent SIGNAL when its parent dies.
* This should return false on error and print a diagnostic message.
*/
bool die_with_parent(int signal);
#ifdef __linux__
# include <linux/prctl.h>
# include <sys/prctl.h>
bool die_with_parent(int signal) {
if (prctl(PR_SET_PDEATHSIG, signal) < 0) {
eprintf("prctl PR_SET_PDEATHSIG: %s", strerror(errno));
return false;
}
return true;
}
#else
# error "Unsuported operating system"
#endif
/*
* Parse OPT into an integer signal number, or return -1 on error.
*/
int parse_signal(const char *opt) {
if (*opt >= '0' && *opt <= '9') {
char *endptr;
long signum = strtol(opt, &endptr, 10);
if (*endptr) {
return -1;
}
return signum;
} else if (strncasecmp(opt, "SIG", 3) == 0) {
opt += 3;
}
if (strncasecmp(opt, "RT", 2) == 0) {
int reference = 0;
if (strncasecmp(opt + 2, "MIN", 3) == 0) {
reference = SIGRTMIN;
} else if (strncasecmp(opt + 2, "MAX", 3) == 0) {
reference = SIGRTMAX;
}
if (reference > 0) {
if (!opt[5]) {
return reference;
}
char *endptr;
long signum = strtol(opt + 5, &endptr, 10);
if ((signum > 0 && reference != SIGRTMIN)
|| (signum < 0 && reference != SIGRTMAX)) {
return -1;
}
long abs_signum = signum < 0 ? -signum : signum;
return !*endptr && abs_signum <= SIGRTMAX - SIGRTMIN
? reference + signum
: -1;
}
}
for (size_t i = 0; i < MAX_SIGNUM; ++i) {
if (SIGNAL_NAMES[i] && strcasecmp(opt, SIGNAL_NAMES[i] + 3) == 0) {
return i;
}
}
return -1;
}
void print_help(FILE *stream) {
fprintf(stream,
"usage: %s [-h] [-s SIGNAL] [-n] [-p PID] COMMAND ARGS...\n",
EXEC_NAME);
fprintf(stream, "Arrange for COMMAND to be executed with ARGS, arranging "
"for it to have a signal\nsent when its parent dies.\n\n");
fprintf(stream, "Options:\n");
fprintf(stream, " -h print this message, then exit\n");
fprintf(
stream,
" -s arrage for SIGNAL to be sent instead of the default SIGHUP\n");
fprintf(stream, " -n don't search $PATH for COMMAND\n");
fprintf(stream, " -p specify the parent processes process-id as PID\n");
}
int main(int argc, char *const *argv) {
EXEC_NAME = argv[0];
opterr = false;
int signal_num = SIGHUP;
bool no_search = false;
bool had_error = false;
pid_t init_ppid = 0;
int c;
const char *old_posixly_correct = getenv("POSIXLY_CORRECT");
putenv("POSIXLY_CORRECT=1");
while ((c = getopt(argc, argv, ":hs:np:")) >= 0) {
switch (c) {
case 'h':
print_help(stdout);
return 0;
case 's':
signal_num = parse_signal(optarg);
if (signal_num < 0) {
eprintf("unknown signal: %s", optarg);
had_error = true;
}
break;
case 'n':
no_search = true;
break;
case 'p': {
char *endptr;
long lpid = strtol(optarg, &endptr, 10);
if (!endptr) {
eprintf("invalid pid: %s", optarg);
had_error = true;
} else if (lpid < 1 || lpid > PID_MAX) {
eprintf("out of range: %s", optarg);
had_error = true;
}
init_ppid = lpid;
} break;
case ':':
if (isprint(optopt)) {
eprintf("option requires argument: %c", optopt);
} else {
eprintf("option requires argument: 0x%x", optopt);
}
had_error = true;
break;
case '?':
if (isprint(optopt)) {
eprintf("unknown option: %c", optopt);
} else {
eprintf("unknown option: 0x%x", optopt);
}
break;
}
}
if (!argv[optind]) {
eprintf("expected command as positional arguments");
return 1;
}
if (!old_posixly_correct) {
unsetenv("POSIXLY_CORRECT");
} else {
setenv("POSIXLY_CORRECT", old_posixly_correct, true);
}
if (had_error) {
return 1;
}
if (!die_with_parent(signal_num)) {
return 1;
}
// we now check if the parent exited before this point
if (init_ppid > 0) {
pid_t new_ppid = getppid();
if (new_ppid != init_ppid) {
raise(signal_num);
// if we got here that means the signal in question was ignored
// which is probably what the user intended, so just go on with
// execing the new process
}
}
const char *func;
if (no_search) {
execv(argv[optind], &argv[optind]);
func = "execv";
} else {
execvp(argv[optind], &argv[optind]);
func = "execvp";
}
// if we got here it means an error occurred
eprintf("%s: %s", func, strerror(errno));
return 1;
}