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