]> git.gir.st - forkaftergrep.git/blob - fag.c
terminate PROGRAM when fag gets interrupted/terminated
[forkaftergrep.git] / fag.c
1 /* forkaftergrep (C) 2017 Tobias Girstmair, GPLv3 */
2 //TODO: if grep exits with an error, fag thinks a match was found
3 //TODO: allow redirect of both streams to files
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 pid_t cpid = 0;
20
21 void term_child(int s) {
22 (void)s; /* squash -Werror=unused-parameter */
23 if (cpid != 0) kill (cpid, SIGTERM);
24 exit(1);
25 }
26
27 int main (int argc, char** argv) {
28 struct opt opts = {0, 0, 0, NULL, NULL, STDOUT_FILENO, "-q"};
29 /* `-q': don't print anything; exit with 0 on match; with 1 on error. used to interface with `grep' */
30 int opt;
31 opterr = 0;
32
33 /* generate grep options string */
34 char* p = opts.grepopt+2; /* move cursor behind `q' */
35
36 signal (SIGPIPE, SIG_IGN); /* ignore broken pipe between fag and grep */
37 struct sigaction sa = {0}; /* terminate child if fag gets interrupted/terminated */
38 sa.sa_handler = &term_child; /* NOTE: will also be inherited by keep-pipe-alive-child */
39 sigaction (SIGINT, &sa, NULL);
40 sigaction (SIGTERM, &sa, NULL);
41
42
43 /* the `+' forces getopt to stop at the first non-option */
44 while ((opt = getopt (argc, argv, "+t:k::er:VhvEFGPiwxyU")) != -1) {
45 switch (opt) {
46 case 't':
47 opts.timeout = atoi (optarg);
48 break;
49 case 'k':
50 opts.kill_sig = optarg ? atoi (optarg) : SIGTERM;
51 break;
52 case 'e':
53 opts.stream = STDERR_FILENO;
54 break;
55 case 'V':
56 opts.verbose = 1;
57 break;
58 case 'h':
59 fprintf (stderr, VERTEXT USAGE
60 "Options:\n"
61 "\t-t N\ttimeout after N seconds\n"
62 "\t-k[M]\tsend signal M to child after timeout (default: 15/SIGTERM)\n"
63 "\t-e\tgrep on stderr instead of stdout\n"
64 "\t-V\tbe verbose; print PROGRAM's stdout/stderr to stderr\n"
65 "\t-[EFP]\tgrep: matcher selection\n"
66 "\t-[iwxU]\tgrep: matching control\n", argv[0]);
67 return EX_OK;
68 case 'v':
69 fprintf (stderr, VERTEXT);
70 return EX_OK;
71 /* `grep' options (Note: missing `-e:', `-f:') */
72 case 'E': case 'F': case 'G': case 'P':
73 case 'i': case 'y': case 'w': case 'x':
74 case 'U': *(p++)=opt; break;
75
76 default:
77 fprintf (stderr, "Unrecognized option: %c\n" USAGE, optopt, argv[0]);
78 return EX_USAGE;
79 }
80 }
81 *p = '\0'; /* terminate grep_options string */
82
83 /* the first non-option argument is the search string */
84 if (optind < argc) {
85 opts.pattern = argv[optind++];
86 } else {
87 fprintf (stderr, USAGE "(Missing PATTERN)\n", argv[0]);
88 return EX_USAGE;
89 }
90
91 /* the remaining are the program to be run */
92 if (optind < argc) {
93 opts.argv = &(argv[optind]);
94 } else {
95 fprintf (stderr, USAGE "(Missing PROGRAM)\n", argv[0]);
96 return EX_USAGE;
97 }
98
99 int retval = fork_after_grep (opts);
100
101 return retval;
102 }
103
104 int fork_after_grep (struct opt opts) {
105 int pipefd[2];
106 //pid_t cpid;
107 int status;
108
109 char buf[BUF_SIZE];
110 int nbytes;
111
112 struct timeval begin, now, diff;
113
114 if (pipe(pipefd) == -1) {
115 fprintf (stderr, "pipe error (userprog)\n");
116 return EX_OSERR;
117 }
118
119 if ((cpid = fork()) == -1) {
120 fprintf (stderr, "fork error (userprog): %s", strerror (errno));
121 close (pipefd[0]);
122 close (pipefd[1]);
123 return EX_OSERR;
124 }
125
126 if (cpid == 0) {
127 close (pipefd[0]);
128 dup2 (pipefd[1], opts.stream);
129 close (pipefd[1]);
130 //dup2 (open("/dev/null", O_WRONLY), opts.stream==STDOUT_FILENO?STDERR_FILENO:STDOUT_FILENO);
131 close (opts.stream==STDOUT_FILENO?STDERR_FILENO:STDOUT_FILENO);
132
133 if (setsid () == -1) {
134 fprintf (stderr, "setsid error (userprog): %s", strerror (errno));
135 _exit (EX_OSERR);
136 }
137
138 execvp (opts.argv[0], opts.argv);
139 fprintf (stderr, "exec error (userprog): %s", strerror (errno));
140 _exit (EX_UNAVAILABLE);
141 } else {
142 pid_t grep_cpid;
143 int grep_pipefd[2];
144 int grep_status;
145
146 close (pipefd[1]);
147 fcntl (pipefd[0], F_SETFL, fcntl (pipefd[0], F_GETFL, 0) | O_NONBLOCK);
148
149 gettimeofday (&begin, NULL); /* for timeout */
150
151 if (pipe(grep_pipefd) == -1) {
152 fprintf (stderr, "pipe error (grep)\n");
153 close (pipefd[0]);
154 return EX_OSERR;
155 }
156
157 if ((grep_cpid = fork()) == -1) {
158 fprintf (stderr, "fork error (grep): %s", strerror (errno));
159 close (pipefd[0]);
160 close (grep_pipefd[0]);
161 close (grep_pipefd[1]);
162 return EX_OSERR;
163 }
164
165 if (grep_cpid == 0) {
166 close (grep_pipefd[1]);
167 dup2 (grep_pipefd[0], STDIN_FILENO);
168 close (grep_pipefd[0]);
169
170 close (STDERR_FILENO);
171 close (STDOUT_FILENO);
172
173 execlp ("grep", "grep", opts.grepopt, opts.pattern, NULL);
174 fprintf (stderr, "exec error (grep): %s", strerror (errno));
175 _exit (EX_SOFTWARE);
176 } else {
177 close (grep_pipefd[0]);
178 for (;;) {
179 usleep (20000);
180 nbytes = read (pipefd[0], buf, BUF_SIZE);
181 if (nbytes == -1) {
182 switch (errno) {
183 case EAGAIN:
184 break;
185 default:
186 fprintf (stderr, "read error (userprog): %s", strerror (errno));
187 close (pipefd[0]);
188 close (grep_pipefd[1]);
189 kill (grep_cpid, SIGTERM);
190 return EX_IOERR;
191 }
192 } else if (nbytes == 0) {
193 fprintf (stderr, "Child program exited prematurely (userprog).\n");
194 close (pipefd[0]);
195 close (grep_pipefd[1]);
196 kill (grep_cpid, SIGTERM);
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 write(STDERR_FILENO, buf, nbytes);
205 }
206
207 write(grep_pipefd[1], buf, nbytes); /* can cause SIGPIPE if grep exited, therefore signal will be ignored */
208 }
209
210 // TODO: exits with `0' even if `grep' exits with code > 0 !
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 int devnull = open("/dev/null", O_WRONLY);
221 dup2 (devnull, pipefd[0]);
222 close (devnull);
223 while (kill(cpid, 0) != -1 && errno != ESRCH ) sleep (1);
224 close (pipefd[0]);
225 _exit(0);
226 }
227 close (pipefd[0]);
228 return EX_OK;
229 } else {
230 /* grep exited due to an error */
231 fprintf (stderr, "grep exited due to an error.\n");
232 close (pipefd[0]);
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 (grep_pipefd[1]);
246 kill (grep_cpid, SIGTERM);
247 return EX_UNAVAILABLE;
248 }
249 }
250 }
251 }
252 }
253 }
Imprint / Impressum