├── .gitignore ├── COPYING-WTFPL ├── Makefile ├── README.md ├── pact.c └── test.sh /.gitignore: -------------------------------------------------------------------------------- 1 | pact 2 | -------------------------------------------------------------------------------- /COPYING-WTFPL: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS = -O2 -Wall -Werror -std=c99 2 | ifeq ($(debug),1) 3 | CFLAGS += -DPRINT_DEBUG=1 4 | endif 5 | 6 | pact: pact.c 7 | gcc pact.c ${CFLAGS} -o pact 8 | 9 | install: pact 10 | cp pact /bin/pact 11 | 12 | clean: 13 | rm -f pact 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pact 2 | 3 | Give pact several process IDs. When one process ID dies, pact will kill all 4 | provided PIDs. 5 | 6 | Example usage: 7 | 8 | #!/bin/bash 9 | 10 | cmd1 & 11 | PID1=$! 12 | cmd2 --some-arg & 13 | PID2=$! 14 | 15 | ./pact $PID1 $PID2 16 | 17 | ##Advanced usage 18 | 19 | pact also accepts a one character modifier before the process ID. 20 | It can be either M or K. M monitors the process without killing it, and 21 | K will not monitor the process, but it will kill the process if another 22 | monitored process dies. 23 | 24 | Example: 25 | 26 | #!/bin/bash 27 | 28 | (sleep 5)& 29 | PID1=$! 30 | (sleep 24h)& 31 | PID2=$! 32 | 33 | # If the shell dies, kill the other processes. If one of the 34 | # processes die, don't kill the shell. 35 | ./pact $PID1 $PID2 M$$ 36 | 37 | # Pause, because otherwise the shell will immediately die and pact 38 | # will kill the subprocesses. 39 | pause 40 | -------------------------------------------------------------------------------- /pact.c: -------------------------------------------------------------------------------- 1 | #define _POSIX_SOURCE 2 | #define _POSIX_C_SOURCE 199309L 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | //#include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | 17 | #ifdef PRINT_DEBUG 18 | #define DEBUG(...) fprintf(stdout, __VA_ARGS__) 19 | #else 20 | #define DEBUG(...) 21 | #endif 22 | #define ERROR(...) fprintf(stderr, __VA_ARGS__) 23 | 24 | struct child_proc { 25 | pid_t pid; 26 | bool monitorProc; 27 | bool killProc; 28 | bool procChanged; 29 | }; 30 | 31 | bool killAll = false; 32 | 33 | void on_SIGTERM(int signal) { 34 | killAll = true; 35 | } 36 | 37 | 38 | int main(int argc, char *argv[]) { 39 | // Trap SIGTERM 40 | struct sigaction action; 41 | memset(&action, 0, sizeof(struct sigaction)); 42 | action.sa_handler = on_SIGTERM; 43 | sigaction(SIGTERM, &action, NULL); 44 | 45 | int numProcs = argc - 1; 46 | // Check the arguments are sane and that every argument corresponds 47 | // to a process 48 | if(argc < 2) { 49 | ERROR("You must give pact some PID's!\n"); 50 | ERROR("Usage: %s [K/M]PID ...\n", argv[0]); 51 | return 42; 52 | } 53 | struct child_proc procs[numProcs]; 54 | memset(&procs, 0, sizeof(procs)); 55 | for(int i = 0; i < numProcs; i++) { 56 | char* currentArg = argv[i + 1]; 57 | int pid; 58 | char modifier; 59 | int ret; 60 | bool hasModifier = !isdigit(currentArg[0]); 61 | if(hasModifier) { 62 | DEBUG("Arg has modifier\n"); 63 | modifier = currentArg[0]; 64 | currentArg++; 65 | ret = sscanf(currentArg, "%d", &pid); 66 | switch (modifier) { 67 | case 'K': 68 | procs[i].monitorProc = false; 69 | procs[i].killProc = true; 70 | break; 71 | case 'M': 72 | procs[i].monitorProc = true; 73 | procs[i].killProc = false; 74 | break; 75 | default: 76 | ERROR("Unrecognized modifier %c\n", modifier); 77 | break; 78 | } 79 | } else { 80 | DEBUG("Arg has no modifier\n"); 81 | ret = sscanf(currentArg, "%d", &pid); 82 | procs[i].monitorProc = true; 83 | procs[i].killProc = true; 84 | } 85 | if(ret != 1) { 86 | ERROR("Error - \"%s\" is not a valid argument.\n", currentArg); 87 | killAll = true; 88 | } else { 89 | procs[i].pid = pid; 90 | 91 | } 92 | } 93 | // Fork and let the child handle the work 94 | int ret = fork(); 95 | if(ret > 0) { 96 | exit(0); 97 | } else if(ret < 0) { 98 | ERROR("Cannot fork()\n"); 99 | } else if(ret == 0) { 100 | // We're the child, continue with the program 101 | } 102 | // Wait for one of those processes to die 103 | while(!killAll) { 104 | for(int i = 0; i < numProcs; i++) { 105 | pid_t pid = procs[i].pid; 106 | if(kill(pid, 0) == -1) { 107 | // Process no longer exists, don't kill it 108 | // later 109 | procs[i].procChanged = true; 110 | // Don't start killing processes if we got 111 | // the K modifier for this process 112 | if(procs[i].monitorProc) { 113 | killAll = true; 114 | break; 115 | } 116 | } 117 | } 118 | if(!killAll) { 119 | DEBUG("Sleeping\n"); 120 | struct timespec ts; 121 | ts.tv_sec = 1; 122 | ts.tv_nsec = 0; 123 | nanosleep(&ts, NULL); 124 | } 125 | } 126 | // Kill all other processes 127 | for(int i = 0; i < numProcs; i++) { 128 | pid_t pid = procs[i].pid; 129 | if(!procs[i].killProc || procs[i].procChanged || pid == 0) { 130 | continue; 131 | } 132 | DEBUG("Killing %d\n", pid); 133 | kill(pid, SIGTERM); 134 | } 135 | return 0; 136 | } 137 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "shell at $$" 4 | 5 | (sleep 24h)& 6 | PID1=$! 7 | echo "sleep1 at $PID1" 8 | (sleep 1)& 9 | PID2=$! 10 | echo "sleep2 at $PID2" 11 | 12 | ./pact $PID1 $PID2 13 | 14 | 15 | ps -A | grep $PID1 16 | ps -A | grep $PID2 17 | 18 | #read 19 | 20 | #ps -A | grep $PID1 21 | #ps -A | grep $PID2 22 | --------------------------------------------------------------------------------