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