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