]> git.gir.st - forkaftergrep.git/blob - fag.c
change -e to -r, more grep options, stub for grep -e and -f
[forkaftergrep.git] / fag.c
1 /* forkaftergrep (C) 2017 Tobias Girstmair, GPLv3 */
2 //TODO: allow redirect of both streams to files (have to simulate `tee(1)' in the pipe-passer and the dummy-proc; instead of closing nontracked fd redirect to logfile/devnull)
3 //TODO: grep is missing `-e' and `-f' options
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/stat.h>
15 #include <sys/time.h>
16 #include <sys/wait.h>
17 #include <unistd.h>
18 #include "fag.h"
19
20 pid_t cpid = 0;
21
22 void term_child(int s) {
23 if (cpid != 0) kill (cpid, s);
24 exit(1);
25 }
26
27 int main (int argc, char** argv) {
28 struct opt opts = {0, 0, 0, NULL, NULL, "/dev/null", "/dev/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::rl:L:VhvEFGPiwxyUZJe:f:")) != -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 'r':
53 opts.stream = STDERR_FILENO;
54 break;
55 case 'l':
56 fprintf (stderr, "WARNING: `-l' not currently implemented\n");
57 opts.logout = optarg;
58 break;
59 case 'L':
60 fprintf (stderr, "WARNING: `-L' not currently implemented\n");
61 opts.logerr = optarg;
62 break;
63 case 'V':
64 opts.verbose = 1;
65 break;
66 case 'h':
67 fprintf (stderr, VERTEXT USAGE
68 "Options:\n"
69 "\t-t N\ttimeout after N seconds\n"
70 "\t-k[N]\tsend signal N to child after timeout (default: 15/SIGTERM)\n"
71 "\t-r\tgrep on stderr instead of stdout\n"
72 "\t-l FILE\tlog PROGRAM's stdout to FILE\n"
73 "\t-L FILE\tlog PROGRAM's stderr to FILE\n"
74 "\t-V\tbe verbose; print monitored stream to stderr\n"
75 "\t-[EFGP]\tgrep: matcher selection\n"
76 "\t-[iwxU]\tgrep: matching control\n"
77 "\t-[ZJ]\tgrep: decompression control\n", argv[0]);
78 return EX_OK;
79 case 'v':
80 fprintf (stderr, VERTEXT);
81 return EX_OK;
82 case 'e': case 'f':
83 fprintf (stderr, "`grep -e' and `-f' are not implemented yet!\n");
84 return EX_SOFTWARE;
85 break;
86 case 'E': case 'F': case 'G': case 'P':
87 case 'i': case 'y': case 'w': case 'x':
88 case 'U': case 'Z': case 'J':
89 *(p++)=opt;
90 break;
91
92 default:
93 fprintf (stderr, "Unrecognized option: %c\n" USAGE, optopt, argv[0]);
94 return EX_USAGE;
95 }
96 }
97 *p = '\0'; /* terminate grep_options string */
98
99 /* the first non-option argument is the search string */
100 if (optind < argc) {
101 opts.pattern = argv[optind++];
102 } else {
103 fprintf (stderr, USAGE "(Missing PATTERN)\n", argv[0]);
104 return EX_USAGE;
105 }
106
107 /* the remaining are the program to be run */
108 if (optind < argc) {
109 opts.argv = &(argv[optind]);
110 } else {
111 fprintf (stderr, USAGE "(Missing PROGRAM)\n", argv[0]);
112 return EX_USAGE;
113 }
114
115 int retval = fork_after_grep (opts);
116
117 return retval;
118 }
119
120 int fork_after_grep (struct opt opts) {
121 int pipefd[2];
122 int pipefd_cpid[2]; /* IPC to extract PID from daemonized userprog */
123 pid_t tmp_cpid;
124 int status;
125
126 char buf[BUF_SIZE];
127 int nbytes;
128
129 struct timeval begin, now, diff;
130
131 if (pipe(pipefd) == -1) {
132 fprintf (stderr, "pipe error (userprog)\n");
133 return EX_OSERR;
134 }
135
136 if(pipe(pipefd_cpid) == -1) {
137 fprintf (stderr, "pipe error (cpid-ipc)\n");
138 close (pipefd[0]);
139 close (pipefd[1]);
140 return EX_OSERR;
141 }
142
143 if ((tmp_cpid = fork()) == -1) {
144 fprintf (stderr, "fork error (daemonizer): %s", strerror (errno));
145 close (pipefd[0]);
146 close (pipefd[1]);
147 close (pipefd_cpid[0]);
148 close (pipefd_cpid[1]);
149 return EX_OSERR;
150 }
151
152 if (tmp_cpid == 0) {
153 pid_t cpid_userprog;
154
155 close (pipefd[0]);
156 dup2 (pipefd[1], opts.stream);
157 close (pipefd[1]);
158 //dup2 (open("/dev/null", O_WRONLY), opts.stream==STDOUT_FILENO?STDERR_FILENO:STDOUT_FILENO);
159 close (opts.stream==STDOUT_FILENO?STDERR_FILENO:STDOUT_FILENO);
160
161 if (setsid () == -1) {
162 fprintf (stderr, "setsid error (daemonizer): %s", strerror (errno));
163 _exit (EX_OSERR);
164 }
165
166 if ((cpid_userprog = fork()) == -1) {
167 fprintf (stderr, "fork error (userprog): %s", strerror (errno));
168 _exit (EX_OSERR);
169 }
170 if (cpid_userprog == 0) {
171 close(pipefd_cpid[0]);
172 close(pipefd_cpid[1]);
173
174 execvp (opts.argv[0], opts.argv);
175 fprintf (stderr, "exec error (userprog): %s", strerror (errno));
176 } else {
177 /* only way to get final child's pid to main is through IPC */
178 write(pipefd_cpid[1], &cpid_userprog, sizeof(pid_t));
179 close(pipefd_cpid[0]);
180 close(pipefd_cpid[1]);
181 }
182 _exit (EX_UNAVAILABLE);
183 } else {
184 pid_t grep_cpid;
185 int grep_pipefd[2];
186 int grep_status;
187
188 /* read userprog's PID from IPC: */
189 read(pipefd_cpid[0], &cpid, sizeof(pid_t));
190 close(pipefd_cpid[0]);
191 close(pipefd_cpid[1]);
192
193 close (pipefd[1]);
194 fcntl (pipefd[0], F_SETFL, fcntl (pipefd[0], F_GETFL, 0) | O_NONBLOCK);
195
196 gettimeofday (&begin, NULL); /* for timeout */
197
198 if (pipe(grep_pipefd) == -1) {
199 fprintf (stderr, "pipe error (grep)\n");
200 close (pipefd[0]);
201 return EX_OSERR;
202 }
203
204 if ((grep_cpid = fork()) == -1) {
205 fprintf (stderr, "fork error (grep): %s", strerror (errno));
206 close (pipefd[0]);
207 close (grep_pipefd[0]);
208 close (grep_pipefd[1]);
209 return EX_OSERR;
210 }
211
212 if (grep_cpid == 0) {
213 close (grep_pipefd[1]);
214 dup2 (grep_pipefd[0], STDIN_FILENO);
215 close (grep_pipefd[0]);
216
217 close (STDOUT_FILENO);
218
219 execlp ("grep", "grep", opts.grepopt, "--", opts.pattern, NULL);
220 fprintf (stderr, "exec error (grep): %s", strerror (errno));
221 _exit (EX_SOFTWARE);
222 } else {
223 close (grep_pipefd[0]);
224 for (;;) {
225 usleep (20000);
226 nbytes = read (pipefd[0], buf, BUF_SIZE);
227 if (nbytes == -1) {
228 switch (errno) {
229 case EAGAIN:
230 break;
231 default:
232 fprintf (stderr, "read error (userprog): %s", strerror (errno));
233 close (pipefd[0]);
234 close (grep_pipefd[1]);
235 kill (grep_cpid, SIGTERM);
236 return EX_IOERR;
237 }
238 } else if (nbytes == 0) {
239 fprintf (stderr, "Child program exited prematurely (userprog).\n");
240 close (pipefd[0]);
241 close (grep_pipefd[1]);
242 kill (grep_cpid, SIGTERM);
243 if (waitpid (cpid, &status, WNOHANG) > 0 && WIFEXITED (status)) {
244 return WEXITSTATUS (status);
245 }
246 return EX_UNAVAILABLE;
247 } else {
248 /* have new userprog-data, send it to grep */
249 if (opts.verbose) {
250 write(STDERR_FILENO, buf, nbytes);
251 }
252
253 write(grep_pipefd[1], buf, nbytes); /* can cause SIGPIPE if grep exited, therefore signal will be ignored */
254 }
255
256 if (waitpid (grep_cpid, &grep_status, WNOHANG) > 0 && WIFEXITED (grep_status)) {
257 close (grep_pipefd[1]);
258
259 if (WEXITSTATUS(grep_status) == 0) {
260 /* grep exited with match found */
261 printf ("%d\n", cpid);
262 fflush (stdout);
263
264 /* create a new child to keep pipe alive, empty it and write log files (will exit with exec'd program) */
265 if (!fork()){setsid();if(!fork()) {
266 close(0); close(1); close(2); umask(0); chdir("/");
267 int devnull = open("/dev/null", O_WRONLY);
268 dup2 (devnull, pipefd[0]);
269 close (devnull);
270 while (kill(cpid, 0) != -1 && errno != ESRCH ) sleep (1);
271 close (pipefd[0]);
272 _exit(0);
273 }}
274 close (pipefd[0]);
275 return EX_OK;
276 } else {
277 /* grep exited due to an error */
278 fprintf (stderr, "grep exited due to an error.\n");
279 close (pipefd[0]);
280 close (grep_pipefd[1]);
281 return EX_IOERR;
282 }
283 }
284
285 if (opts.timeout > 0) {
286 gettimeofday (&now, NULL);
287 timersub (&now, &begin, &diff);
288 if (diff.tv_sec >= opts.timeout) {
289 fprintf (stderr, "Timeout reached. \n");
290 if (opts.kill_sig > 0) kill (cpid, opts.kill_sig);
291 close (pipefd[0]);
292 close (grep_pipefd[1]);
293 kill (grep_cpid, SIGTERM);
294 return EX_UNAVAILABLE;
295 }
296 }
297 }
298 }
299 }
300 }
Imprint / Impressum