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