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