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