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