├── .gitignore ├── CHANGELOG.md ├── Makefile ├── README.md ├── examples ├── node.js ├── perl.pl └── shell.sh └── phook.c /.gitignore: -------------------------------------------------------------------------------- 1 | CMakeLists.txt 2 | phook 3 | .idea/ 4 | cmake-build-debug -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.0.8 4 | 5 | - Fix for macOS Monterey. [@martinlau](https://github.com/martinlau) 6 | 7 | ## 0.0.7 8 | 9 | - Compiler optimization is removed, since it creates broken binary with clang 10 | 11 | ## 0.0.6 12 | 13 | - Added `process` argument for waiting on non-parent processes [@jayrhynas](https://github.com/jayrhynas) 14 | - Added event handling for SIGPIPE event. [@shishirchawla](https://github.com/shishirchawla) 15 | 16 | ## 0.0.5 17 | 18 | - Removed dead code 19 | - Added examples 20 | 21 | ## 0.0.4 22 | 23 | - Initial release 24 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | prefix=/usr/local 2 | 3 | phook: phook.c 4 | gcc -O0 -o phook phook.c 5 | 6 | install: phook 7 | install -m 0755 phook $(prefix)/bin 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # phook 2 | 3 | Runs a command after a parent process has finished. 4 | 5 | ``` 6 | Usage: phook [OPTION]... 7 | Runs a command after a parent process has finished. 8 | 9 | With no OPTION provided, will do nothing and exit 10 | 11 | Mandatory arguments to long options are mandatory for short options too 12 | -a, --after=COMMAND executes command after the parent process has ended 13 | -e, --execute=COMMAND executes command on start 14 | -p, --process=PID waits for PID to exit instead of parent process 15 | -h, --help display this help and exit 16 | --version output version information and exit 17 | ``` 18 | 19 | ## Installation 20 | 21 | You need macOS for this to work. 22 | 23 | ``` 24 | wget https://github.com/drinchev/phook/archive/v0.0.8.tar.gz 25 | tar -xzf v0.0.8.tar.gz 26 | cd phook-0.0.8 27 | make install 28 | ``` 29 | 30 | Alternative option would be to 31 | use [TheFox](https://github.com/TheFox)'s [homebrew tap](https://github.com/TheFox/homebrew-brewery) : 32 | 33 | ``` 34 | brew tap thefox/brewery 35 | brew install phook 36 | ``` 37 | 38 | ## Usage 39 | 40 | See [examples](https://github.com/drinchev/phook/tree/master/examples). 41 | 42 | ## Contributors 43 | 44 | - [@jayrhynas](https://github.com/jayrhynas) 45 | - [@shishirchawla](https://github.com/shishirchawla) 46 | - [@martinlau](https://github.com/martinlau) 47 | 48 | --- 49 | 50 | Credits goes to http://serverfault.com/a/641004/152846 51 | 52 | --- 53 | 54 | Copyright (C) 2017 Ivan Drinchev. 55 | 56 | This software may be modified and distributed under the terms of the MIT license. 57 | 58 | 59 | -------------------------------------------------------------------------------- /examples/node.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Will output : 4 | 5 | 1 6 | 2 7 | 3 8 | 4 9 | 10 | */ 11 | 12 | var spawn = require("child_process").spawn; 13 | 14 | console.log("1"); 15 | spawn( "../phook", [ "-e", "echo 2", "-a", "echo 4" ], { 16 | stdio: [process.stdin, process.stdout, process.stderr] 17 | } ); 18 | setTimeout( function() { 19 | console.log("3"); 20 | process.exit(0); 21 | }, 1000 ); 22 | -------------------------------------------------------------------------------- /examples/perl.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | # 4 | # Will output : 5 | # 6 | # 1 7 | # 2 8 | # 3 9 | # 4 10 | # 11 | 12 | print "1\n"; 13 | system("../phook", "-e", "echo 2", "-a", "echo 4"); 14 | sleep 1; 15 | print "3\n"; 16 | exit 0; 17 | -------------------------------------------------------------------------------- /examples/shell.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Will output : 5 | # 6 | # 1 7 | # 2 8 | # 3 9 | # 4 10 | # 11 | 12 | echo 1 13 | ../phook -e 'echo 2' -a 'echo 4' 14 | sleep 1 15 | echo 3 16 | exit 0 17 | -------------------------------------------------------------------------------- /phook.c: -------------------------------------------------------------------------------- 1 | /* phook -- runs commands around a parent process 2 | * 3 | * Copyright (C) 2017 Ivan Drinchev 4 | * 5 | * This software may be modified and distributed under the terms 6 | * of the MIT license. See the LICENSE file for details. 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #define PROGRAM_NAME "phook" 17 | #define PROGRAM_VERSION "0.08" 18 | 19 | const char *after = NULL; 20 | 21 | static struct option const longopts[] = {{"help", no_argument, NULL, 'h'}, 22 | {"version", no_argument, NULL, 1}, 23 | {"after", required_argument, NULL, 'a'}, 24 | {"execute", required_argument, NULL, 'e'}, 25 | {"process", required_argument, NULL, 'p'}, 26 | {NULL, 0, NULL, 0}}; 27 | 28 | void copyright() { 29 | printf( 30 | "Copyright (C) 2017 Ivan Drinchev\n\ 31 | This software may be modified and distributed\n\ 32 | under the terms of the MIT license.\n\ 33 | "); 34 | } 35 | 36 | void usage(int status) { 37 | if (status != EXIT_SUCCESS) 38 | fprintf(stderr, "Try '%s --help' for more information.\n", PROGRAM_NAME); 39 | else { 40 | printf("Usage: %s [OPTION]...\n", PROGRAM_NAME); 41 | printf("Runs a command after a parent process has finished."); 42 | printf("\n\nWith no OPTION provided, will do nothing and exit\n\n"); 43 | printf("\ 44 | Mandatory arguments to long options are mandatory for short options too\n\ 45 | -a, --after=COMMAND executes command after the parent process has ended\n\ 46 | -e, --execute=COMMAND executes command on start\n\ 47 | -p, --process=PID waits for PID to exit instead of parent process\n\ 48 | -h, --help display this help and exit\n\ 49 | --version output version information and exit\n\n\ 50 | "); 51 | copyright(); 52 | } 53 | exit(status); 54 | } 55 | 56 | void version() { 57 | printf("%s %s\n", PROGRAM_NAME, PROGRAM_VERSION); 58 | copyright(); 59 | exit(EXIT_SUCCESS); 60 | } 61 | 62 | void sigint_handler() { 63 | system(after); 64 | exit(EXIT_SUCCESS); 65 | } 66 | 67 | int main(int argc, char **argv) { 68 | 69 | const char *execute = NULL; 70 | int acode = 0; 71 | int ecode = 0; 72 | int optc; 73 | pid_t ppid = 0, fpid; 74 | struct kevent kev; 75 | int kq; 76 | int kret; 77 | struct timespec timeout; 78 | 79 | signal(SIGINT, sigint_handler); 80 | signal(SIGPIPE, sigint_handler); 81 | 82 | while ((optc = getopt_long(argc, argv, "ha:e:p:", longopts, NULL)) != -1) { 83 | switch (optc) { 84 | case 'a': 85 | after = optarg; 86 | break; 87 | case 'e': 88 | execute = optarg; 89 | break; 90 | case 'p': { 91 | char *end; 92 | long p = strtol(optarg, &end, 10); 93 | if (errno != 0) { 94 | perror("process"); 95 | } else if (end == optarg || *end != '\0') { 96 | fprintf(stderr, "process: Invalid argument\n"); 97 | } else if (p < 0) { 98 | fprintf(stderr, "process: Cannot be negative\n"); 99 | } else if (p > INT_MAX) { 100 | fprintf(stderr, "process: Result too large\n"); 101 | } else if (p == 0) { 102 | fprintf(stderr, "process: Cannot watch pid 0\n"); 103 | } else { 104 | ppid = (pid_t) p; 105 | break; 106 | } 107 | usage(EXIT_FAILURE); 108 | break; 109 | } 110 | case 'h': 111 | usage(EXIT_SUCCESS); 112 | break; 113 | case 1: 114 | version(); 115 | break; 116 | default: 117 | usage(EXIT_FAILURE); 118 | } 119 | } 120 | 121 | if (execute) 122 | ecode = system(execute); 123 | 124 | if (ecode > 0) 125 | exit(EXIT_FAILURE); 126 | 127 | if (!after) 128 | exit(EXIT_SUCCESS); 129 | 130 | if (ppid == 0) 131 | ppid = getppid(); 132 | 133 | fpid = fork(); 134 | 135 | if (fpid != 0) 136 | exit(EXIT_SUCCESS); 137 | 138 | EV_SET(&kev, ppid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, 0); 139 | 140 | kq = kqueue(); 141 | 142 | if (kq == -1) { 143 | perror("kqueue"); 144 | exit(EXIT_FAILURE); 145 | } 146 | 147 | kret = kevent(kq, &kev, 1, NULL, 0, NULL); 148 | 149 | if (kret == -1) { 150 | perror("kevent"); 151 | exit(EXIT_FAILURE); 152 | } 153 | 154 | timeout.tv_sec = (8 * 60 * 60); 155 | timeout.tv_nsec = 0; 156 | 157 | kret = kevent(kq, NULL, 0, &kev, 1, &timeout); 158 | 159 | if (kret == -1) { 160 | perror("kevent"); 161 | exit(EXIT_FAILURE); 162 | } 163 | 164 | if (kret > 0) 165 | acode = system(after); 166 | 167 | if (acode > 0) 168 | exit(EXIT_FAILURE); 169 | 170 | exit(EXIT_SUCCESS); 171 | 172 | } 173 | --------------------------------------------------------------------------------