fixed sigpipe
[forkaftergrep.git] / fag.c
1 /* forkaftergrep (C) 2017 Tobias Girstmair, GPLv3 */
2 //TODO: if grep exits with an error, fag thinks a match was found
3
4 #define _XOPEN_SOURCE 500
5 #define _DEFAULT_SOURCE
6 #include <errno.h>
7 #include <fcntl.h>
8 #include <signal.h>
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
18 int main (int argc, char** argv) {
19 struct opt opts = {0, 0, 0, NULL, NULL, STDOUT_FILENO, "-q"};
20 int opt;
21 opterr = 0;
22
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
28 /* the `+' forces getopt to stop at the first non-option */
29 while ((opt = getopt (argc, argv, "+t:k::eVhvEFGPiwxyU")) != -1) {
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;
40 case 'V':
41 opts.verbose = 1;
42 break;
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"
48 "\t-e\tgrep on stderr instead of stdout\n"
49 "\t-V\tbe verbose; print PROGRAM's stdout/stderr to stderr\n"
50 "\t-[EFGPiwxyU]\t grep options\n", argv[0]);
51 return EX_OK;
52 case 'v':
53 fprintf (stderr, VERTEXT);
54 return EX_OK;
55 /* `grep' options (Note: missing `-e:', `-f:') */
56 case 'E': case 'F': case 'G': case 'P':
57 case 'i': case 'y': case 'w': case 'x':
58 case 'U': *(p++)=opt; break;
59
60 default:
61 fprintf (stderr, "Unrecognized option: %c\n" USAGE, optopt, argv[0]);
62 return EX_USAGE;
63 }
64 }
65 *p = '\0'; /* finish grep_options string */
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
83 signal (SIGPIPE, SIG_IGN); /* ignore broken pipe between fag and grep */
84
85 int retval = fork_after_grep (opts);
86
87 return retval;
88 }
89
90 int fork_after_grep (struct opt opts) {
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) {
101 fprintf (stderr, "pipe error (userprog)\n");
102 return EX_OSERR;
103 }
104
105 if ((cpid = fork()) == -1) {
106 fprintf (stderr, "fork error (userprog): %s", strerror (errno));
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) {
119 fprintf (stderr, "setsid error (userprog): %s", strerror (errno));
120 _exit (EX_OSERR);
121 }
122
123 execvp (opts.argv[0], opts.argv);
124 fprintf (stderr, "exec error (userprog): %s", strerror (errno));
125 _exit (EX_UNAVAILABLE);
126 } else {
127 pid_t grep_cpid;
128 int grep_pipefd[2];
129 int grep_status;
130
131 close (pipefd[1]);
132 fcntl (pipefd[0], F_SETFL, fcntl (pipefd[0], F_GETFL, 0) | O_NONBLOCK);
133
134 gettimeofday (&begin, NULL); /* for timeout */
135
136 if (pipe(grep_pipefd) == -1) {
137 fprintf (stderr, "pipe error (grep)\n");
138 close (pipefd[0]);
139 return EX_OSERR;
140 }
141
142 if ((grep_cpid = fork()) == -1) {
143 fprintf (stderr, "fork error (grep): %s", strerror (errno));
144 close (pipefd[0]);
145 close (grep_pipefd[0]);
146 close (grep_pipefd[1]);
147 return EX_OSERR;
148 }
149
150 if (grep_cpid == 0) {
151 close (grep_pipefd[1]);
152 dup2 (grep_pipefd[0], STDIN_FILENO);
153 close (grep_pipefd[0]);
154
155 close (STDERR_FILENO);
156 close (STDOUT_FILENO);
157
158 execlp ("grep", "grep", opts.grepopt, opts.pattern, NULL);
159 fprintf (stderr, "exec error (grep): %s", strerror (errno));
160 _exit (EX_SOFTWARE);
161 } else {
162 close (grep_pipefd[0]);
163 for (;;) {
164 usleep (20000);
165 nbytes = read (pipefd[0], buf, BUF_SIZE);
166 if (nbytes == -1) {
167 switch (errno) {
168 case EAGAIN:
169 break;
170 default:
171 fprintf (stderr, "read error (userprog): %s", strerror (errno));
172 close (pipefd[0]);
173 close (grep_pipefd[1]);
174 //TODO: kill grep?
175 return EX_IOERR;
176 }
177 } else if (nbytes == 0) {
178 fprintf (stderr, "Child program exited prematurely (userprog).\n");
179 close (pipefd[0]);
180 close (grep_pipefd[1]);
181 //TODO: kill grep?
182 if (waitpid (cpid, &status, WNOHANG) > 0 && WIFEXITED (status)) {
183 return WEXITSTATUS (status);
184 }
185 return EX_UNAVAILABLE;
186 } else {
187 /* have new userprog-data, send it to grep */
188 if (opts.verbose) {
189 write(STDERR_FILENO, buf, nbytes);
190 }
191
192 write(grep_pipefd[1], buf, nbytes); /* can cause SIGPIPE if grep exited, therefore signal will be ignored */
193 }
194
195 // TODO: exits with `0' even if `grep' exits with code > 0 !
196 if (waitpid (grep_cpid, &grep_status, WNOHANG) > 0 && WIFEXITED (grep_status)) {
197 close (grep_pipefd[1]);
198
199 if (WEXITSTATUS(grep_status) == 0) {
200 /* grep exited with match found */
201 printf ("%d\n", cpid);
202
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]);
207 _exit(0);
208 }
209 close (pipefd[0]);
210 return EX_OK;
211 } else {
212 /* grep exited due to an error */
213 fprintf (stderr, "grep exited due to an error.\n");
214 close (pipefd[0]);
215 close (grep_pipefd[1]);
216 return EX_IOERR;
217 }
218 }
219
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]);
227 close (grep_pipefd[1]);
228 //TODO: kill grep?
229 return EX_UNAVAILABLE;
230 }
231 }
232 }
233 }
234 }
235 }
Imprint / Impressum