kill grep on error; small cleanups
[forkaftergrep.git] / fag.c
1 /* forkaftergrep (C) 2017 Tobias Girstmair, GPLv3 */
2
3 #define _XOPEN_SOURCE 500
4 #define _DEFAULT_SOURCE
5 #include <errno.h>
6 #include <fcntl.h>
7 #include <signal.h>
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
17 int main (int argc, char** argv) {
18 struct opt opts = {0, 0, 0, NULL, NULL, STDOUT_FILENO, "-q"};
19 /* `-q': don't print anything; exit with 0 on match; with 1 on error. used to interface with `grep' */
20 int opt;
21 opterr = 0;
22
23 /* generate grep options string */
24 char* p = opts.grepopt+2; /* move cursor behind `q' */
25
26 signal (SIGPIPE, SIG_IGN); /* ignore broken pipe between fag and grep */
27
28
29 /* the `+' forces getopt to stop at the first non-option */
30 while ((opt = getopt (argc, argv, "+t:k::eVhvEFGPiwxyU")) != -1) {
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;
41 case 'V':
42 opts.verbose = 1;
43 break;
44 case 'h':
45 fprintf (stderr, VERTEXT USAGE
46 "Options:\n"
47 "\t-t N\ttimeout after N seconds\n"
48 "\t-k[M]\tsend signal M to child after timeout (default: 15/SIGTERM)\n"
49 "\t-e\tgrep on stderr instead of stdout\n"
50 "\t-V\tbe verbose; print PROGRAM's stdout/stderr to stderr\n"
51 "\t-[EFP]\tgrep: matcher selection\n"
52 "\t-[iwxU]\tgrep: matching control\n", argv[0]);
53 return EX_OK;
54 case 'v':
55 fprintf (stderr, VERTEXT);
56 return EX_OK;
57 /* `grep' options (Note: missing `-e:', `-f:') */
58 case 'E': case 'F': case 'G': case 'P':
59 case 'i': case 'y': case 'w': case 'x':
60 case 'U': *(p++)=opt; break;
61
62 default:
63 fprintf (stderr, "Unrecognized option: %c\n" USAGE, optopt, argv[0]);
64 return EX_USAGE;
65 }
66 }
67 *p = '\0'; /* terminate grep_options string */
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
85 int retval = fork_after_grep (opts);
86
87 return retval;
88 }
89
90 int fork_after_grep (struct opt opts) {
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) {
101 fprintf (stderr, "pipe error (userprog)\n");
102 return EX_OSERR;
103 }
104
105 if ((cpid = fork()) == -1) {
106 fprintf (stderr, "fork error (userprog): %s", strerror (errno));
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) {
119 fprintf (stderr, "setsid error (userprog): %s", strerror (errno));
120 _exit (EX_OSERR);
121 }
122
123 execvp (opts.argv[0], opts.argv);
124 fprintf (stderr, "exec error (userprog): %s", strerror (errno));
125 _exit (EX_UNAVAILABLE);
126 } else {
127 pid_t grep_cpid;
128 int grep_pipefd[2];
129 int grep_status;
130
131 close (pipefd[1]);
132 fcntl (pipefd[0], F_SETFL, fcntl (pipefd[0], F_GETFL, 0) | O_NONBLOCK);
133
134 gettimeofday (&begin, NULL); /* for timeout */
135
136 if (pipe(grep_pipefd) == -1) {
137 fprintf (stderr, "pipe error (grep)\n");
138 close (pipefd[0]);
139 return EX_OSERR;
140 }
141
142 if ((grep_cpid = fork()) == -1) {
143 fprintf (stderr, "fork error (grep): %s", strerror (errno));
144 close (pipefd[0]);
145 close (grep_pipefd[0]);
146 close (grep_pipefd[1]);
147 return EX_OSERR;
148 }
149
150 if (grep_cpid == 0) {
151 close (grep_pipefd[1]);
152 dup2 (grep_pipefd[0], STDIN_FILENO);
153 close (grep_pipefd[0]);
154
155 close (STDERR_FILENO);
156 close (STDOUT_FILENO);
157
158 execlp ("grep", "grep", opts.grepopt, opts.pattern, NULL);
159 fprintf (stderr, "exec error (grep): %s", strerror (errno));
160 _exit (EX_SOFTWARE);
161 } else {
162 close (grep_pipefd[0]);
163 for (;;) {
164 usleep (20000);
165 nbytes = read (pipefd[0], buf, BUF_SIZE);
166 if (nbytes == -1) {
167 switch (errno) {
168 case EAGAIN:
169 break;
170 default:
171 fprintf (stderr, "read error (userprog): %s", strerror (errno));
172 close (pipefd[0]);
173 close (grep_pipefd[1]);
174 kill (grep_cpid, SIGTERM);
175 return EX_IOERR;
176 }
177 } else if (nbytes == 0) {
178 fprintf (stderr, "Child program exited prematurely (userprog).\n");
179 close (pipefd[0]);
180 close (grep_pipefd[1]);
181 kill (grep_cpid, SIGTERM);
182 if (waitpid (cpid, &status, WNOHANG) > 0 && WIFEXITED (status)) {
183 return WEXITSTATUS (status);
184 }
185 return EX_UNAVAILABLE;
186 } else {
187 /* have new userprog-data, send it to grep */
188 if (opts.verbose) {
189 write(STDERR_FILENO, buf, nbytes);
190 }
191
192 write(grep_pipefd[1], buf, nbytes); /* can cause SIGPIPE if grep exited, therefore signal will be ignored */
193 }
194
195 if (waitpid (grep_cpid, &grep_status, WNOHANG) > 0 && WIFEXITED (grep_status)) {
196 close (grep_pipefd[1]);
197
198 if (WEXITSTATUS(grep_status) == 0) {
199 /* grep exited with match found */
200 printf ("%d\n", cpid);
201
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]);
206 _exit(0);
207 }
208 close (pipefd[0]);
209 return EX_OK;
210 } else {
211 /* grep exited due to an error */
212 fprintf (stderr, "grep exited due to an error.\n");
213 close (pipefd[0]);
214 close (grep_pipefd[1]);
215 return EX_IOERR;
216 }
217 }
218
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]);
226 close (grep_pipefd[1]);
227 kill (grep_cpid, SIGTERM);
228 return EX_UNAVAILABLE;
229 }
230 }
231 }
232 }
233 }
234 }
Imprint / Impressum