kill grep on error; small cleanups
[forkaftergrep.git] / fag.c
CommitLineData
4f83e12d 1/* forkaftergrep (C) 2017 Tobias Girstmair, GPLv3 */
2
3#define _XOPEN_SOURCE 500
34f76df0 4#define _DEFAULT_SOURCE
4f83e12d 5#include <errno.h>
6#include <fcntl.h>
cebe28dd 7#include <signal.h>
4f83e12d 8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
11#include <sysexits.h>
12#include <sys/time.h>
13#include <sys/wait.h>
14#include <unistd.h>
15#include "fag.h"
16
4f83e12d 17int main (int argc, char** argv) {
fdb3674a 18 struct opt opts = {0, 0, 0, NULL, NULL, STDOUT_FILENO, "-q"};
ac6eee9c 19 /* `-q': don't print anything; exit with 0 on match; with 1 on error. used to interface with `grep' */
4f83e12d 20 int opt;
21 opterr = 0;
22
fdb3674a 23 /* generate grep options string */
fdb3674a 24 char* p = opts.grepopt+2; /* move cursor behind `q' */
25
ac6eee9c 26 signal (SIGPIPE, SIG_IGN); /* ignore broken pipe between fag and grep */
27
fdb3674a 28
4f83e12d 29 /* the `+' forces getopt to stop at the first non-option */
1e23aa5b 30 while ((opt = getopt (argc, argv, "+t:k::eVhvEFGPiwxyU")) != -1) {
4f83e12d 31 switch (opt) {
32 case 't':
33 opts.timeout = atoi (optarg);
34 break;
35 case 'k':
36 opts.kill_sig = optarg ? atoi (optarg) : SIGTERM;
37 break;
38 case 'e':
39 opts.stream = STDERR_FILENO;
40 break;
ff6565ef 41 case 'V':
42 opts.verbose = 1;
43 break;
4f83e12d 44 case 'h':
45 fprintf (stderr, VERTEXT USAGE
46 "Options:\n"
47 "\t-t N\ttimeout after N seconds\n"
ac6eee9c 48 "\t-k[M]\tsend signal M to child after timeout (default: 15/SIGTERM)\n"
329d9605 49 "\t-e\tgrep on stderr instead of stdout\n"
05257d9b 50 "\t-V\tbe verbose; print PROGRAM's stdout/stderr to stderr\n"
ac6eee9c 51 "\t-[EFP]\tgrep: matcher selection\n"
52 "\t-[iwxU]\tgrep: matching control\n", argv[0]);
4f83e12d 53 return EX_OK;
54 case 'v':
55 fprintf (stderr, VERTEXT);
56 return EX_OK;
1e23aa5b 57 /* `grep' options (Note: missing `-e:', `-f:') */
fdb3674a 58 case 'E': case 'F': case 'G': case 'P':
59 case 'i': case 'y': case 'w': case 'x':
60 case 'U': *(p++)=opt; break;
1e23aa5b 61
4f83e12d 62 default:
63 fprintf (stderr, "Unrecognized option: %c\n" USAGE, optopt, argv[0]);
64 return EX_USAGE;
65 }
66 }
ac6eee9c 67 *p = '\0'; /* terminate grep_options string */
4f83e12d 68
69 /* the first non-option argument is the search string */
70 if (optind < argc) {
71 opts.pattern = argv[optind++];
72 } else {
73 fprintf (stderr, USAGE "(Missing PATTERN)\n", argv[0]);
74 return EX_USAGE;
75 }
76
77 /* the remaining are the program to be run */
78 if (optind < argc) {
79 opts.argv = &(argv[optind]);
80 } else {
81 fprintf (stderr, USAGE "(Missing PROGRAM)\n", argv[0]);
82 return EX_USAGE;
83 }
84
fdb3674a 85 int retval = fork_after_grep (opts);
4f83e12d 86
87 return retval;
88}
89
fdb3674a 90int fork_after_grep (struct opt opts) {
4f83e12d 91 int pipefd[2];
92 pid_t cpid;
93 int status;
94
95 char buf[BUF_SIZE];
96 int nbytes;
97
98 struct timeval begin, now, diff;
99
100 if (pipe(pipefd) == -1) {
f27db2a6 101 fprintf (stderr, "pipe error (userprog)\n");
4f83e12d 102 return EX_OSERR;
103 }
104
105 if ((cpid = fork()) == -1) {
f27db2a6 106 fprintf (stderr, "fork error (userprog): %s", strerror (errno));
4f83e12d 107 close (pipefd[0]);
108 close (pipefd[1]);
109 return EX_OSERR;
110 }
111
112 if (cpid == 0) {
113 close (pipefd[0]);
114 dup2 (pipefd[1], opts.stream);
115 close (pipefd[1]);
116 close (opts.stream==STDOUT_FILENO?STDERR_FILENO:STDOUT_FILENO);
117
118 if (setsid () == -1) {
f27db2a6 119 fprintf (stderr, "setsid error (userprog): %s", strerror (errno));
4f83e12d 120 _exit (EX_OSERR);
121 }
122
123 execvp (opts.argv[0], opts.argv);
f27db2a6 124 fprintf (stderr, "exec error (userprog): %s", strerror (errno));
4f83e12d 125 _exit (EX_UNAVAILABLE);
126 } else {
52503080 127 pid_t grep_cpid;
1e23aa5b 128 int grep_pipefd[2];
f27db2a6 129 int grep_status;
52503080 130
4f83e12d 131 close (pipefd[1]);
132 fcntl (pipefd[0], F_SETFL, fcntl (pipefd[0], F_GETFL, 0) | O_NONBLOCK);
133
d8642d91 134 gettimeofday (&begin, NULL); /* for timeout */
4f83e12d 135
1e23aa5b 136 if (pipe(grep_pipefd) == -1) {
f27db2a6 137 fprintf (stderr, "pipe error (grep)\n");
d8642d91 138 close (pipefd[0]);
52503080 139 return EX_OSERR;
140 }
f27db2a6 141
52503080 142 if ((grep_cpid = fork()) == -1) {
f27db2a6 143 fprintf (stderr, "fork error (grep): %s", strerror (errno));
52503080 144 close (pipefd[0]);
1e23aa5b 145 close (grep_pipefd[0]);
146 close (grep_pipefd[1]);
52503080 147 return EX_OSERR;
148 }
f27db2a6 149
52503080 150 if (grep_cpid == 0) {
1e23aa5b 151 close (grep_pipefd[1]);
152 dup2 (grep_pipefd[0], STDIN_FILENO);
153 close (grep_pipefd[0]);
f27db2a6 154
155 close (STDERR_FILENO);
156 close (STDOUT_FILENO);
157
fdb3674a 158 execlp ("grep", "grep", opts.grepopt, opts.pattern, NULL);
f27db2a6 159 fprintf (stderr, "exec error (grep): %s", strerror (errno));
05257d9b 160 _exit (EX_SOFTWARE);
52503080 161 } else {
1e23aa5b 162 close (grep_pipefd[0]);
52503080 163 for (;;) {
164 usleep (20000);
52503080 165 nbytes = read (pipefd[0], buf, BUF_SIZE);
166 if (nbytes == -1) {
167 switch (errno) {
168 case EAGAIN:
f27db2a6 169 break;
52503080 170 default:
f27db2a6 171 fprintf (stderr, "read error (userprog): %s", strerror (errno));
52503080 172 close (pipefd[0]);
1e23aa5b 173 close (grep_pipefd[1]);
ac6eee9c 174 kill (grep_cpid, SIGTERM);
52503080 175 return EX_IOERR;
176 }
177 } else if (nbytes == 0) {
f27db2a6 178 fprintf (stderr, "Child program exited prematurely (userprog).\n");
4f83e12d 179 close (pipefd[0]);
1e23aa5b 180 close (grep_pipefd[1]);
ac6eee9c 181 kill (grep_cpid, SIGTERM);
52503080 182 if (waitpid (cpid, &status, WNOHANG) > 0 && WIFEXITED (status)) {
183 return WEXITSTATUS (status);
184 }
185 return EX_UNAVAILABLE;
f27db2a6 186 } else {
187 /* have new userprog-data, send it to grep */
188 if (opts.verbose) {
1e23aa5b 189 write(STDERR_FILENO, buf, nbytes);
f27db2a6 190 }
191
cebe28dd 192 write(grep_pipefd[1], buf, nbytes); /* can cause SIGPIPE if grep exited, therefore signal will be ignored */
4f83e12d 193 }
f27db2a6 194
195 if (waitpid (grep_cpid, &grep_status, WNOHANG) > 0 && WIFEXITED (grep_status)) {
1e23aa5b 196 close (grep_pipefd[1]);
d8642d91 197
f27db2a6 198 if (WEXITSTATUS(grep_status) == 0) {
199 /* grep exited with match found */
200 printf ("%d\n", cpid);
d8642d91 201
f27db2a6 202 /* create a new child to keep pipe alive (will exit with exec'd program) */
203 if (!fork ()) {
204 while (kill(cpid, 0) != -1 && errno != ESRCH ) sleep (1);
205 close (pipefd[0]);
f27db2a6 206 _exit(0);
207 }
208 close (pipefd[0]);
f27db2a6 209 return EX_OK;
210 } else {
211 /* grep exited due to an error */
fdb3674a 212 fprintf (stderr, "grep exited due to an error.\n");
52503080 213 close (pipefd[0]);
1e23aa5b 214 close (grep_pipefd[1]);
f27db2a6 215 return EX_IOERR;
52503080 216 }
4f83e12d 217 }
4f83e12d 218
52503080 219 if (opts.timeout > 0) {
220 gettimeofday (&now, NULL);
221 timersub (&now, &begin, &diff);
222 if (diff.tv_sec >= opts.timeout) {
223 fprintf (stderr, "Timeout reached. \n");
224 if (opts.kill_sig > 0) kill (cpid, opts.kill_sig);
225 close (pipefd[0]);
1e23aa5b 226 close (grep_pipefd[1]);
ac6eee9c 227 kill (grep_cpid, SIGTERM);
52503080 228 return EX_UNAVAILABLE;
229 }
4f83e12d 230 }
231 }
232 }
233 }
234}
Imprint / Impressum