properly daemonize, also smaller fixes
[forkaftergrep.git] / fag.c
CommitLineData
4f83e12d 1/* forkaftergrep (C) 2017 Tobias Girstmair, GPLv3 */
30a78ef4 2//TODO: if grep exits with an error, fag thinks a match was found (e.g. grep -BEP, killall grep)
259f7382 3//TODO: allow redirect of both streams to files (have to simulate `tee(1)' in the pipe-passer and the dummy-proc; instead of closing nontracked fd redirect to logfile/devnull)
4//TODO: grep is missing `-e' and `-f' options; fag's `-e' collides with grep's
4f83e12d 5
6#define _XOPEN_SOURCE 500
34f76df0 7#define _DEFAULT_SOURCE
4f83e12d 8#include <errno.h>
9#include <fcntl.h>
cebe28dd 10#include <signal.h>
4f83e12d 11#include <stdio.h>
12#include <stdlib.h>
13#include <string.h>
14#include <sysexits.h>
259f7382 15#include <sys/stat.h>
4f83e12d 16#include <sys/time.h>
17#include <sys/wait.h>
18#include <unistd.h>
19#include "fag.h"
20
7966a08a 21pid_t cpid = 0;
22
23void term_child(int s) {
259f7382 24 if (cpid != 0) kill (cpid, s);
7966a08a 25 exit(1);
26}
27
4f83e12d 28int main (int argc, char** argv) {
259f7382 29 struct opt opts = {0, 0, 0, NULL, NULL, "/dev/null", "/dev/null", STDOUT_FILENO, "-q"};
ac6eee9c 30 /* `-q': don't print anything; exit with 0 on match; with 1 on error. used to interface with `grep' */
4f83e12d 31 int opt;
32 opterr = 0;
33
fdb3674a 34 /* generate grep options string */
fdb3674a 35 char* p = opts.grepopt+2; /* move cursor behind `q' */
36
ac6eee9c 37 signal (SIGPIPE, SIG_IGN); /* ignore broken pipe between fag and grep */
7966a08a 38 struct sigaction sa = {0}; /* terminate child if fag gets interrupted/terminated */
39 sa.sa_handler = &term_child; /* NOTE: will also be inherited by keep-pipe-alive-child */
40 sigaction (SIGINT, &sa, NULL);
41 sigaction (SIGTERM, &sa, NULL);
ac6eee9c 42
fdb3674a 43
4f83e12d 44 /* the `+' forces getopt to stop at the first non-option */
259f7382 45 while ((opt = getopt (argc, argv, "+t:k::el:L:VhvEFGPiwxyU")) != -1) {
4f83e12d 46 switch (opt) {
47 case 't':
48 opts.timeout = atoi (optarg);
49 break;
50 case 'k':
51 opts.kill_sig = optarg ? atoi (optarg) : SIGTERM;
52 break;
53 case 'e':
54 opts.stream = STDERR_FILENO;
55 break;
259f7382 56 case 'l':
57 opts.logout = optarg;
58 break;
59 case 'L':
60 opts.logerr = optarg;
61 break;
ff6565ef 62 case 'V':
63 opts.verbose = 1;
64 break;
4f83e12d 65 case 'h':
66 fprintf (stderr, VERTEXT USAGE
67 "Options:\n"
68 "\t-t N\ttimeout after N seconds\n"
259f7382 69 "\t-k[N]\tsend signal N to child after timeout (default: 15/SIGTERM)\n"
329d9605 70 "\t-e\tgrep on stderr instead of stdout\n"
259f7382 71 "\t-l FILE\tlog PROGRAM's standard output to FILE\n"
72 "\t-L FILE\tlog PROGRAM's standard error to FILE\n"
05257d9b 73 "\t-V\tbe verbose; print PROGRAM's stdout/stderr to stderr\n"
ac6eee9c 74 "\t-[EFP]\tgrep: matcher selection\n"
75 "\t-[iwxU]\tgrep: matching control\n", argv[0]);
4f83e12d 76 return EX_OK;
77 case 'v':
78 fprintf (stderr, VERTEXT);
79 return EX_OK;
1e23aa5b 80 /* `grep' options (Note: missing `-e:', `-f:') */
fdb3674a 81 case 'E': case 'F': case 'G': case 'P':
82 case 'i': case 'y': case 'w': case 'x':
83 case 'U': *(p++)=opt; break;
1e23aa5b 84
4f83e12d 85 default:
86 fprintf (stderr, "Unrecognized option: %c\n" USAGE, optopt, argv[0]);
87 return EX_USAGE;
88 }
89 }
ac6eee9c 90 *p = '\0'; /* terminate grep_options string */
4f83e12d 91
92 /* the first non-option argument is the search string */
93 if (optind < argc) {
94 opts.pattern = argv[optind++];
95 } else {
96 fprintf (stderr, USAGE "(Missing PATTERN)\n", argv[0]);
97 return EX_USAGE;
98 }
99
100 /* the remaining are the program to be run */
101 if (optind < argc) {
102 opts.argv = &(argv[optind]);
103 } else {
104 fprintf (stderr, USAGE "(Missing PROGRAM)\n", argv[0]);
105 return EX_USAGE;
106 }
107
fdb3674a 108 int retval = fork_after_grep (opts);
4f83e12d 109
110 return retval;
111}
112
fdb3674a 113int fork_after_grep (struct opt opts) {
4f83e12d 114 int pipefd[2];
259f7382 115 int pipefd_cpid[2]; /* IPC to extract PID from daemonized userprog */
116 pid_t tmp_cpid;
4f83e12d 117 int status;
118
119 char buf[BUF_SIZE];
120 int nbytes;
121
122 struct timeval begin, now, diff;
123
124 if (pipe(pipefd) == -1) {
f27db2a6 125 fprintf (stderr, "pipe error (userprog)\n");
4f83e12d 126 return EX_OSERR;
127 }
128
259f7382 129 if(pipe(pipefd_cpid) == -1) {
130 fprintf (stderr, "pipe error (cpid-ipc)\n");
131 close (pipefd[0]);
132 close (pipefd[1]);
133 return EX_OSERR;
134 }
135
136 if ((tmp_cpid = fork()) == -1) {
137 fprintf (stderr, "fork error (daemonizer): %s", strerror (errno));
4f83e12d 138 close (pipefd[0]);
139 close (pipefd[1]);
259f7382 140 close (pipefd_cpid[0]);
141 close (pipefd_cpid[1]);
4f83e12d 142 return EX_OSERR;
143 }
144
259f7382 145 if (tmp_cpid == 0) {
146 pid_t cpid_userprog;
147
4f83e12d 148 close (pipefd[0]);
149 dup2 (pipefd[1], opts.stream);
150 close (pipefd[1]);
1151d861 151 //dup2 (open("/dev/null", O_WRONLY), opts.stream==STDOUT_FILENO?STDERR_FILENO:STDOUT_FILENO);
4f83e12d 152 close (opts.stream==STDOUT_FILENO?STDERR_FILENO:STDOUT_FILENO);
153
154 if (setsid () == -1) {
259f7382 155 fprintf (stderr, "setsid error (daemonizer): %s", strerror (errno));
156 _exit (EX_OSERR);
157 }
158
159 if ((cpid_userprog = fork()) == -1) {
160 fprintf (stderr, "fork error (userprog): %s", strerror (errno));
4f83e12d 161 _exit (EX_OSERR);
162 }
259f7382 163 if (cpid_userprog == 0) {
164 close(pipefd_cpid[0]);
165 close(pipefd_cpid[1]);
4f83e12d 166
259f7382 167 execvp (opts.argv[0], opts.argv);
168 fprintf (stderr, "exec error (userprog): %s", strerror (errno));
169 } else {
170 /* only way to get final child's pid to main is through IPC */
171 write(pipefd_cpid[1], &cpid_userprog, sizeof(pid_t));
172 close(pipefd_cpid[0]);
173 close(pipefd_cpid[1]);
174 }
4f83e12d 175 _exit (EX_UNAVAILABLE);
176 } else {
52503080 177 pid_t grep_cpid;
1e23aa5b 178 int grep_pipefd[2];
f27db2a6 179 int grep_status;
52503080 180
259f7382 181 /* read userprog's PID from IPC: */
182 read(pipefd_cpid[0], &cpid, sizeof(pid_t));
183 close(pipefd_cpid[0]);
184 close(pipefd_cpid[1]);
185
4f83e12d 186 close (pipefd[1]);
187 fcntl (pipefd[0], F_SETFL, fcntl (pipefd[0], F_GETFL, 0) | O_NONBLOCK);
188
d8642d91 189 gettimeofday (&begin, NULL); /* for timeout */
4f83e12d 190
1e23aa5b 191 if (pipe(grep_pipefd) == -1) {
f27db2a6 192 fprintf (stderr, "pipe error (grep)\n");
d8642d91 193 close (pipefd[0]);
52503080 194 return EX_OSERR;
195 }
f27db2a6 196
52503080 197 if ((grep_cpid = fork()) == -1) {
f27db2a6 198 fprintf (stderr, "fork error (grep): %s", strerror (errno));
52503080 199 close (pipefd[0]);
1e23aa5b 200 close (grep_pipefd[0]);
201 close (grep_pipefd[1]);
52503080 202 return EX_OSERR;
203 }
f27db2a6 204
52503080 205 if (grep_cpid == 0) {
1e23aa5b 206 close (grep_pipefd[1]);
207 dup2 (grep_pipefd[0], STDIN_FILENO);
208 close (grep_pipefd[0]);
f27db2a6 209
f27db2a6 210 close (STDOUT_FILENO);
211
b98f2a91 212 execlp ("grep", "grep", opts.grepopt, "--", opts.pattern, NULL);
f27db2a6 213 fprintf (stderr, "exec error (grep): %s", strerror (errno));
05257d9b 214 _exit (EX_SOFTWARE);
52503080 215 } else {
1e23aa5b 216 close (grep_pipefd[0]);
52503080 217 for (;;) {
218 usleep (20000);
52503080 219 nbytes = read (pipefd[0], buf, BUF_SIZE);
220 if (nbytes == -1) {
221 switch (errno) {
222 case EAGAIN:
f27db2a6 223 break;
52503080 224 default:
f27db2a6 225 fprintf (stderr, "read error (userprog): %s", strerror (errno));
52503080 226 close (pipefd[0]);
1e23aa5b 227 close (grep_pipefd[1]);
ac6eee9c 228 kill (grep_cpid, SIGTERM);
52503080 229 return EX_IOERR;
230 }
231 } else if (nbytes == 0) {
f27db2a6 232 fprintf (stderr, "Child program exited prematurely (userprog).\n");
4f83e12d 233 close (pipefd[0]);
1e23aa5b 234 close (grep_pipefd[1]);
ac6eee9c 235 kill (grep_cpid, SIGTERM);
52503080 236 if (waitpid (cpid, &status, WNOHANG) > 0 && WIFEXITED (status)) {
237 return WEXITSTATUS (status);
238 }
239 return EX_UNAVAILABLE;
f27db2a6 240 } else {
241 /* have new userprog-data, send it to grep */
242 if (opts.verbose) {
1e23aa5b 243 write(STDERR_FILENO, buf, nbytes);
f27db2a6 244 }
245
cebe28dd 246 write(grep_pipefd[1], buf, nbytes); /* can cause SIGPIPE if grep exited, therefore signal will be ignored */
4f83e12d 247 }
f27db2a6 248
249 if (waitpid (grep_cpid, &grep_status, WNOHANG) > 0 && WIFEXITED (grep_status)) {
1e23aa5b 250 close (grep_pipefd[1]);
d8642d91 251
f27db2a6 252 if (WEXITSTATUS(grep_status) == 0) {
253 /* grep exited with match found */
254 printf ("%d\n", cpid);
259f7382 255 fflush (stdout);
d8642d91 256
259f7382 257 /* create a new child to keep pipe alive, empty it and write log files (will exit with exec'd program) */
258 if (!fork()){setsid();if(!fork()) {
259 close(0); close(1); close(2); umask(0); chdir("/");
1151d861 260 int devnull = open("/dev/null", O_WRONLY);
261 dup2 (devnull, pipefd[0]);
262 close (devnull);
f27db2a6 263 while (kill(cpid, 0) != -1 && errno != ESRCH ) sleep (1);
264 close (pipefd[0]);
f27db2a6 265 _exit(0);
259f7382 266 }}
f27db2a6 267 close (pipefd[0]);
f27db2a6 268 return EX_OK;
269 } else {
270 /* grep exited due to an error */
fdb3674a 271 fprintf (stderr, "grep exited due to an error.\n");
52503080 272 close (pipefd[0]);
1e23aa5b 273 close (grep_pipefd[1]);
f27db2a6 274 return EX_IOERR;
52503080 275 }
4f83e12d 276 }
4f83e12d 277
52503080 278 if (opts.timeout > 0) {
279 gettimeofday (&now, NULL);
280 timersub (&now, &begin, &diff);
281 if (diff.tv_sec >= opts.timeout) {
282 fprintf (stderr, "Timeout reached. \n");
283 if (opts.kill_sig > 0) kill (cpid, opts.kill_sig);
284 close (pipefd[0]);
1e23aa5b 285 close (grep_pipefd[1]);
ac6eee9c 286 kill (grep_cpid, SIGTERM);
52503080 287 return EX_UNAVAILABLE;
288 }
4f83e12d 289 }
290 }
291 }
292 }
293}
Imprint / Impressum