]> git.gir.st - forkaftergrep.git/blob - fag.c
now uses grep for matches; todo: grep-params
[forkaftergrep.git] / fag.c
1 /* forkaftergrep (C) 2017 Tobias Girstmair, GPLv3 */
2 //TODO: close all descriptors (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};//"-E", //TODO: supply params (-E, -P, etc.) to grep from command line
118
119 close (pipefd[1]);
120 fcntl (pipefd[0], F_SETFL, fcntl (pipefd[0], F_GETFL, 0) | O_NONBLOCK);
121
122 gettimeofday (&begin, NULL);
123
124 if (pipe(pipefd_togrep) == -1) {
125 fprintf (stderr, "pipe error (grep)\n");
126 return EX_OSERR;
127 }
128
129 if ((grep_cpid = fork()) == -1) {
130 fprintf (stderr, "fork error (grep): %s", strerror (errno));
131 close (pipefd[0]);
132 close (pipefd[1]);
133 return EX_OSERR;
134 }
135
136 if (grep_cpid == 0) {
137 close (pipefd_togrep[1]);
138 dup2 (pipefd_togrep[0], STDIN_FILENO);
139 close (pipefd_togrep[0]);
140
141 close (STDERR_FILENO);
142 close (STDOUT_FILENO);
143
144 execvp (grepargv[0], grepargv);
145 fprintf (stderr, "exec error (grep): %s", strerror (errno));
146 _exit (EX_UNAVAILABLE);
147 } else {
148 for (;;) {
149 usleep (20000);
150 memset (buf, 0, BUF_SIZE);
151 nbytes = read (pipefd[0], buf, BUF_SIZE);
152 if (nbytes == -1) {
153 switch (errno) {
154 case EAGAIN:
155 break;
156 default:
157 fprintf (stderr, "read error (userprog): %s", strerror (errno));
158 close (pipefd[0]);
159 close (pipefd[1]);
160 return EX_IOERR;
161 }
162 } else if (nbytes == 0) {
163 fprintf (stderr, "Child program exited prematurely (userprog).\n");
164 close (pipefd[0]);
165 close (pipefd[1]);
166 if (waitpid (cpid, &status, WNOHANG) > 0 && WIFEXITED (status)) {
167 return WEXITSTATUS (status);
168 }
169 return EX_UNAVAILABLE;
170 } else {
171 /* have new userprog-data, send it to grep */
172 if (opts.verbose) {
173 fputs (buf, stderr);
174 }
175
176 write(pipefd_togrep[1], buf, strlen(buf));
177 }
178
179 if (waitpid (grep_cpid, &grep_status, WNOHANG) > 0 && WIFEXITED (grep_status)) {
180 if (WEXITSTATUS(grep_status) == 0) {
181 /* grep exited with match found */
182 printf ("%d\n", cpid);
183 /* create a new child to keep pipe alive (will exit with exec'd program) */
184 if (!fork ()) {
185 while (kill(cpid, 0) != -1 && errno != ESRCH ) sleep (1);
186 close (pipefd[0]);
187 close (pipefd[1]);
188 _exit(0);
189 }
190 close (pipefd[0]);
191 close (pipefd[1]);
192 return EX_OK;
193 } else {
194 /* grep exited due to an error */
195 fprintf (stderr, "grep exited due to an error.");
196 close (pipefd[0]);
197 close (pipefd[1]);
198 close (pipefd_togrep[1]);
199 return EX_IOERR;
200 }
201 }
202
203 if (opts.timeout > 0) {
204 gettimeofday (&now, NULL);
205 timersub (&now, &begin, &diff);
206 if (diff.tv_sec >= opts.timeout) {
207 fprintf (stderr, "Timeout reached. \n");
208 if (opts.kill_sig > 0) kill (cpid, opts.kill_sig);
209 close (pipefd[0]);
210 close (pipefd[1]);
211 return EX_UNAVAILABLE;
212 }
213 }
214 }
215 }
216 }
217 }
Imprint / Impressum