204 lines
5.8 KiB
C
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;
|
|
}
|