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