[
  {
    "path": ".gitignore",
    "content": "pact\n"
  },
  {
    "path": "COPYING-WTFPL",
    "content": "            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE\n                    Version 2, December 2004\n\n Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>\n\n Everyone is permitted to copy and distribute verbatim or modified\n copies of this license document, and changing it is allowed as long\n as the name is changed.\n\n            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE\n   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n\n  0. You just DO WHAT THE FUCK YOU WANT TO.\n"
  },
  {
    "path": "Makefile",
    "content": "CFLAGS = -O2 -Wall -Werror -std=c99\nifeq ($(debug),1)\n\tCFLAGS += -DPRINT_DEBUG=1\nendif\n\npact: pact.c\n\tgcc pact.c ${CFLAGS} -o pact\n\ninstall: pact\n\tcp pact /bin/pact\n\nclean:\n\trm -f pact\n"
  },
  {
    "path": "README.md",
    "content": "# pact\n\nGive pact several process IDs. When one process ID dies, pact will kill all\nprovided PIDs.\n\nExample usage:\n\n    #!/bin/bash\n    \n    cmd1 &\n    PID1=$!\n    cmd2 --some-arg &\n    PID2=$!\n    \n    ./pact $PID1 $PID2\n\n##Advanced usage\n\npact also accepts a one character modifier before the process ID.\nIt can be either M or K. M monitors the process without killing it, and \nK will not monitor the process, but it will kill the process if another\nmonitored process dies.\n\nExample:\n\n    #!/bin/bash\n    \n    (sleep 5)&\n    PID1=$!\n    (sleep 24h)&\n    PID2=$!\n\n    # If the shell dies, kill the other processes. If one of the\n    # processes die, don't kill the shell.\n    ./pact $PID1 $PID2 M$$\n    \n    # Pause, because otherwise the shell will immediately die and pact\n    # will kill the subprocesses.\n    pause\n"
  },
  {
    "path": "pact.c",
    "content": "#define _POSIX_SOURCE\n#define _POSIX_C_SOURCE 199309L\n\n#include <stdbool.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <unistd.h>\n//#include <sys/wait.h>\n#include <sys/types.h>\n#include <signal.h>\n#include <ctype.h>\n#include <string.h>\n#include <time.h>\n#include <sys/time.h>\n\n\n#ifdef PRINT_DEBUG\n#define DEBUG(...) fprintf(stdout, __VA_ARGS__)\n#else\n#define DEBUG(...)\n#endif\n#define ERROR(...) fprintf(stderr, __VA_ARGS__)\n\nstruct child_proc {\n\tpid_t pid;\n\tbool monitorProc;\n\tbool killProc;\n\tbool procChanged;\n};\n\nbool killAll = false;\n\nvoid on_SIGTERM(int signal) {\n\tkillAll = true;\n}\n\n\nint main(int argc, char *argv[]) {\n\t// Trap SIGTERM\n\tstruct sigaction action;\n\tmemset(&action, 0, sizeof(struct sigaction));\n\taction.sa_handler = on_SIGTERM;\n\tsigaction(SIGTERM, &action, NULL);\n\n\tint numProcs = argc - 1;\n\t// Check the arguments are sane and that every argument corresponds\n\t// to a process\n\tif(argc < 2) {\n\t\tERROR(\"You must give pact some PID's!\\n\");\n\t\tERROR(\"Usage: %s [K/M]PID ...\\n\", argv[0]);\n\t\treturn 42;\n\t}\n\tstruct child_proc procs[numProcs];\n\tmemset(&procs, 0, sizeof(procs));\n\tfor(int i = 0; i < numProcs; i++) {\n\t\tchar* currentArg = argv[i + 1];\n\t\tint pid;\n\t\tchar modifier;\n\t\tint ret;\n\t\tbool hasModifier = !isdigit(currentArg[0]);\n\t\tif(hasModifier) {\n\t\t\tDEBUG(\"Arg has modifier\\n\");\n\t\t\tmodifier = currentArg[0];\n\t\t\tcurrentArg++;\n\t\t\tret = sscanf(currentArg, \"%d\", &pid);\n\t\t\tswitch (modifier) {\n\t\t\tcase 'K':\n\t\t\t\tprocs[i].monitorProc = false;\n\t\t\t\tprocs[i].killProc = true;\n\t\t\t\tbreak;\n\t\t\tcase 'M':\n\t\t\t\tprocs[i].monitorProc = true;\n\t\t\t\tprocs[i].killProc = false;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tERROR(\"Unrecognized modifier %c\\n\", modifier);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t} else {\n\t\t\tDEBUG(\"Arg has no modifier\\n\");\n\t\t\tret = sscanf(currentArg, \"%d\", &pid);\n\t\t\tprocs[i].monitorProc = true;\n\t\t\tprocs[i].killProc = true;\n\t\t}\n\t\tif(ret != 1) {\n\t\t\tERROR(\"Error - \\\"%s\\\" is not a valid argument.\\n\", currentArg);\n\t\t\tkillAll = true;\n\t\t} else {\n\t\t\tprocs[i].pid = pid;\n\n\t\t}\n\t}\n\t// Fork and let the child handle the work\n\tint ret = fork();\n\tif(ret > 0) {\n\t\texit(0);\n\t} else if(ret < 0) {\n\t\tERROR(\"Cannot fork()\\n\");\n\t} else if(ret == 0) {\n\t\t// We're the child, continue with the program\n\t}\n\t// Wait for one of those processes to die\n\twhile(!killAll) {\n\t\tfor(int i = 0; i < numProcs; i++) {\n\t\t\tpid_t pid = procs[i].pid;\n\t\t\tif(kill(pid, 0) == -1) {\n\t\t\t\t// Process no longer exists, don't kill it\n\t\t\t\t// later\n\t\t\t\tprocs[i].procChanged = true;\n\t\t\t\t// Don't start killing processes if we got\n\t\t\t\t// the K modifier for this process\n\t\t\t\tif(procs[i].monitorProc) {\n\t\t\t\t\tkillAll = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif(!killAll) {\n\t\t\tDEBUG(\"Sleeping\\n\");\n\t\t\tstruct timespec ts;\n\t\t\tts.tv_sec = 1;\n\t\t\tts.tv_nsec = 0;\n\t\t\tnanosleep(&ts, NULL);\n\t\t}\n\t}\n\t// Kill all other processes\n\tfor(int i = 0; i < numProcs; i++) {\n\t\tpid_t pid = procs[i].pid;\n\t\tif(!procs[i].killProc || procs[i].procChanged || pid == 0) {\n\t\t\tcontinue;\n\t\t}\n\t\tDEBUG(\"Killing %d\\n\", pid);\n\t\tkill(pid, SIGTERM);\n\t}\n\treturn 0;\n}\n"
  },
  {
    "path": "test.sh",
    "content": "#!/bin/bash\n\necho \"shell at $$\"\n\n(sleep 24h)&\nPID1=$!\necho \"sleep1 at $PID1\"\n(sleep 1)&\nPID2=$!\necho \"sleep2 at $PID2\"\n\n./pact $PID1 $PID2\n\n\nps -A | grep $PID1\nps -A | grep $PID2\n\n#read\n\n#ps -A | grep $PID1\n#ps -A | grep $PID2\n"
  }
]