]>
git.gir.st - forkaftergrep.git/blob - fag.c
1 /* forkaftergrep (C) 2017 Tobias Girstmair, GPLv3 */
2 //TODO: grep is missing `-e' and `-f' options
4 #define _XOPEN_SOURCE 500
5 #define _DEFAULT_SOURCE
22 void term_child(int s
) {
23 if (cpid
!= 0) kill (cpid
, s
);
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' */
33 /* generate grep options string */
34 char* p
= opts
.grepopt
+2; /* move cursor behind `q' */
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
);
43 /* the `+' forces getopt to stop at the first non-option */
44 while ((opt
= getopt (argc
, argv
, "+t:k::rl:L:VhvEFGPiwxyUZJe:f:")) != -1) {
47 opts
.timeout
= atoi (optarg
);
50 opts
.kill_sig
= optarg
? atoi (optarg
) : SIGTERM
;
53 opts
.stream
= STDERR_FILENO
;
65 fprintf (stderr
, VERTEXT USAGE
67 "\t-t N\ttimeout after N seconds\n"
68 "\t-k[N]\tsend signal N to child after timeout (default: 15/SIGTERM)\n"
69 "\t-r\tgrep on stderr instead of stdout\n"
70 "\t-l FILE\tlog PROGRAM's stdout to FILE\n"
71 "\t-L FILE\tlog PROGRAM's stderr to FILE\n"
72 "\t-V\tbe verbose; print monitored stream to stderr\n"
73 "\t-[EFGP]\tgrep: matcher selection\n"
74 "\t-[iwxU]\tgrep: matching control\n"
75 "\t-[ZJ]\tgrep: decompression control\n", argv
[0]);
78 fprintf (stderr
, VERTEXT
);
81 fprintf (stderr
, "`grep -e' and `-f' are not implemented yet!\n");
84 case 'E': case 'F': case 'G': case 'P':
85 case 'i': case 'y': case 'w': case 'x':
86 case 'U': case 'Z': case 'J':
91 fprintf (stderr
, "Unrecognized option: %c\n" USAGE
, optopt
, argv
[0]);
95 *p
= '\0'; /* terminate grep_options string */
97 /* the first non-option argument is the search string */
99 opts
.pattern
= argv
[optind
++];
101 fprintf (stderr
, USAGE
"(Missing PATTERN)\n", argv
[0]);
105 /* the remaining are the program to be run */
107 opts
.argv
= &(argv
[optind
]);
109 fprintf (stderr
, USAGE
"(Missing PROGRAM)\n", argv
[0]);
113 int retval
= fork_after_grep (opts
);
118 int fork_after_grep (struct opt opts
) {
120 int pipefd_cpid
[2]; /* IPC to extract PID from daemonized userprog */
125 int secondary_logfile
;
130 struct timeval begin
, now
, diff
;
132 if (pipe(pipefd
) == -1) {
133 fprintf (stderr
, "pipe error (userprog)\n");
137 if(pipe(pipefd_cpid
) == -1) {
138 fprintf (stderr
, "pipe error (cpid-ipc)\n");
144 /* Writing the -l and -L log files is a bit messy. We can easily just pipe the stream we aren't monitoring
145 into the secondary_logfile, but we can't do that for the primary_logfile. This one we write to from the piping
146 process, where -V is also handled at and in the keep-pipe-alive-daemon. */
147 primary_logfile
= open((opts
.stream
==STDOUT_FILENO
?opts
.logout
:opts
.logerr
), O_WRONLY
|O_APPEND
|O_CREAT
, 0600);
148 if (primary_logfile
== -1) {
149 fprintf (stderr
, "error opening logfile (stdout): %s\n", strerror(errno
));
152 close (pipefd_cpid
[0]);
153 close (pipefd_cpid
[1]);
156 secondary_logfile
= open((opts
.stream
==STDOUT_FILENO
?opts
.logerr
:opts
.logout
), O_WRONLY
|O_APPEND
|O_CREAT
, 0600);
157 if (secondary_logfile
== -1) {
158 fprintf (stderr
, "error opening logfile (stderr): %s\n", strerror(errno
));
161 close (pipefd_cpid
[0]);
162 close (pipefd_cpid
[1]);
163 close (primary_logfile
);
167 if ((tmp_cpid
= fork()) == -1) {
168 fprintf (stderr
, "fork error (daemonizer): %s", strerror (errno
));
171 close (pipefd_cpid
[0]);
172 close (pipefd_cpid
[1]);
173 close (primary_logfile
);
174 close (secondary_logfile
);
182 dup2 (pipefd
[1], opts
.stream
);
184 dup2 (secondary_logfile
, opts
.stream
==STDOUT_FILENO
?STDERR_FILENO
:STDOUT_FILENO
);
185 close (primary_logfile
);
186 close (secondary_logfile
);
188 if (setsid () == -1) {
189 fprintf (stderr
, "setsid error (daemonizer): %s", strerror (errno
));
193 if ((cpid_userprog
= fork()) == -1) {
194 fprintf (stderr
, "fork error (userprog): %s", strerror (errno
));
197 if (cpid_userprog
== 0) {
198 close(pipefd_cpid
[0]);
199 close(pipefd_cpid
[1]);
201 execvp (opts
.argv
[0], opts
.argv
);
202 fprintf (stderr
, "exec error (userprog): %s", strerror (errno
));
204 /* only way to get final child's pid to main is through IPC */
205 write(pipefd_cpid
[1], &cpid_userprog
, sizeof(pid_t
));
206 close(pipefd_cpid
[0]);
207 close(pipefd_cpid
[1]);
209 _exit (EX_UNAVAILABLE
);
215 /* read userprog's PID from IPC: */
216 read(pipefd_cpid
[0], &cpid
, sizeof(pid_t
));
217 close(pipefd_cpid
[0]);
218 close(pipefd_cpid
[1]);
221 close (secondary_logfile
);
222 fcntl (pipefd
[0], F_SETFL
, fcntl (pipefd
[0], F_GETFL
, 0) | O_NONBLOCK
);
224 gettimeofday (&begin
, NULL
); /* for timeout */
226 if (pipe(grep_pipefd
) == -1) {
227 fprintf (stderr
, "pipe error (grep)\n");
232 if ((grep_cpid
= fork()) == -1) {
233 fprintf (stderr
, "fork error (grep): %s", strerror (errno
));
235 close (grep_pipefd
[0]);
236 close (grep_pipefd
[1]);
240 if (grep_cpid
== 0) {
241 close (grep_pipefd
[1]);
242 dup2 (grep_pipefd
[0], STDIN_FILENO
);
243 close (grep_pipefd
[0]);
245 close (STDOUT_FILENO
);
247 char* grep_override
= getenv("GREP_OVERRIDE");
248 if (grep_override
!= NULL
) {
249 execlp (grep_override
, basename(grep_override
), opts
.grepopt
, "--", opts
.pattern
, NULL
);
251 execlp ("grep", "grep", opts
.grepopt
, "--", opts
.pattern
, NULL
);
253 fprintf (stderr
, "exec error (grep): %s", strerror (errno
));
256 close (grep_pipefd
[0]);
259 nbytes
= read (pipefd
[0], buf
, BUF_SIZE
);
265 fprintf (stderr
, "read error (userprog): %s", strerror (errno
));
267 close (grep_pipefd
[1]);
268 kill (grep_cpid
, SIGTERM
);
271 } else if (nbytes
== 0) {
272 fprintf (stderr
, "Child program exited prematurely (userprog).\n");
274 close (grep_pipefd
[1]);
275 kill (grep_cpid
, SIGTERM
);
276 if (waitpid (cpid
, &status
, WNOHANG
) > 0 && WIFEXITED (status
)) {
277 return WEXITSTATUS (status
);
279 return EX_UNAVAILABLE
;
281 /* have new userprog-data, send it to grep */
283 write(STDERR_FILENO
, buf
, nbytes
);
286 write(primary_logfile
, buf
, nbytes
);
288 write(grep_pipefd
[1], buf
, nbytes
); /* can cause SIGPIPE if grep exited, therefore signal will be ignored */
291 if (waitpid (grep_cpid
, &grep_status
, WNOHANG
) > 0 && WIFEXITED (grep_status
)) {
292 close (grep_pipefd
[1]);
294 if (WEXITSTATUS(grep_status
) == 0) {
295 /* grep exited with match found */
296 printf ("%d\n", cpid
);
299 /* create a new child to keep pipe alive, empty it and write log files (will exit with exec'd program) */
300 if (!fork()){setsid();if(!fork()) {
301 close(0); close(1); close(2); umask(0); chdir("/");
304 nbytes
= read (pipefd
[0], buf
, BUF_SIZE
);
306 if ((nbytes
== -1 && errno
!= EAGAIN
) || nbytes
== 0) break;
308 write(primary_logfile
, buf
, nbytes
);
311 close (primary_logfile
);
315 close (primary_logfile
);
318 /* grep exited due to an error */
319 fprintf (stderr
, "grep exited due to an error.\n");
321 close (grep_pipefd
[1]);
322 close (primary_logfile
);
327 if (opts
.timeout
> 0) {
328 gettimeofday (&now
, NULL
);
329 timersub (&now
, &begin
, &diff
);
330 if (diff
.tv_sec
>= opts
.timeout
) {
331 fprintf (stderr
, "Timeout reached. \n");
332 printf ("%d\n", cpid
);
335 if (opts
.kill_sig
> 0) kill (cpid
, opts
.kill_sig
);
338 close (grep_pipefd
[1]);
339 close (primary_logfile
);
340 kill (grep_cpid
, SIGTERM
);
341 return EX_UNAVAILABLE
;