4f83e12d |
1 | /* forkaftergrep (C) 2017 Tobias Girstmair, GPLv3 */ |
2 | |
3 | #define _XOPEN_SOURCE 500 |
34f76df0 |
4 | #define _DEFAULT_SOURCE |
4f83e12d |
5 | #include <errno.h> |
6 | #include <fcntl.h> |
cebe28dd |
7 | #include <signal.h> |
4f83e12d |
8 | #include <stdio.h> |
9 | #include <stdlib.h> |
10 | #include <string.h> |
11 | #include <sysexits.h> |
12 | #include <sys/time.h> |
13 | #include <sys/wait.h> |
14 | #include <unistd.h> |
15 | #include "fag.h" |
16 | |
4f83e12d |
17 | int main (int argc, char** argv) { |
fdb3674a |
18 | struct opt opts = {0, 0, 0, NULL, NULL, STDOUT_FILENO, "-q"}; |
ac6eee9c |
19 | /* `-q': don't print anything; exit with 0 on match; with 1 on error. used to interface with `grep' */ |
4f83e12d |
20 | int opt; |
21 | opterr = 0; |
22 | |
fdb3674a |
23 | /* generate grep options string */ |
fdb3674a |
24 | char* p = opts.grepopt+2; /* move cursor behind `q' */ |
25 | |
ac6eee9c |
26 | signal (SIGPIPE, SIG_IGN); /* ignore broken pipe between fag and grep */ |
27 | |
fdb3674a |
28 | |
4f83e12d |
29 | /* the `+' forces getopt to stop at the first non-option */ |
1e23aa5b |
30 | while ((opt = getopt (argc, argv, "+t:k::eVhvEFGPiwxyU")) != -1) { |
4f83e12d |
31 | switch (opt) { |
32 | case 't': |
33 | opts.timeout = atoi (optarg); |
34 | break; |
35 | case 'k': |
36 | opts.kill_sig = optarg ? atoi (optarg) : SIGTERM; |
37 | break; |
38 | case 'e': |
39 | opts.stream = STDERR_FILENO; |
40 | break; |
ff6565ef |
41 | case 'V': |
42 | opts.verbose = 1; |
43 | break; |
4f83e12d |
44 | case 'h': |
45 | fprintf (stderr, VERTEXT USAGE |
46 | "Options:\n" |
47 | "\t-t N\ttimeout after N seconds\n" |
ac6eee9c |
48 | "\t-k[M]\tsend signal M to child after timeout (default: 15/SIGTERM)\n" |
329d9605 |
49 | "\t-e\tgrep on stderr instead of stdout\n" |
05257d9b |
50 | "\t-V\tbe verbose; print PROGRAM's stdout/stderr to stderr\n" |
ac6eee9c |
51 | "\t-[EFP]\tgrep: matcher selection\n" |
52 | "\t-[iwxU]\tgrep: matching control\n", argv[0]); |
4f83e12d |
53 | return EX_OK; |
54 | case 'v': |
55 | fprintf (stderr, VERTEXT); |
56 | return EX_OK; |
1e23aa5b |
57 | /* `grep' options (Note: missing `-e:', `-f:') */ |
fdb3674a |
58 | case 'E': case 'F': case 'G': case 'P': |
59 | case 'i': case 'y': case 'w': case 'x': |
60 | case 'U': *(p++)=opt; break; |
1e23aa5b |
61 | |
4f83e12d |
62 | default: |
63 | fprintf (stderr, "Unrecognized option: %c\n" USAGE, optopt, argv[0]); |
64 | return EX_USAGE; |
65 | } |
66 | } |
ac6eee9c |
67 | *p = '\0'; /* terminate grep_options string */ |
4f83e12d |
68 | |
69 | /* the first non-option argument is the search string */ |
70 | if (optind < argc) { |
71 | opts.pattern = argv[optind++]; |
72 | } else { |
73 | fprintf (stderr, USAGE "(Missing PATTERN)\n", argv[0]); |
74 | return EX_USAGE; |
75 | } |
76 | |
77 | /* the remaining are the program to be run */ |
78 | if (optind < argc) { |
79 | opts.argv = &(argv[optind]); |
80 | } else { |
81 | fprintf (stderr, USAGE "(Missing PROGRAM)\n", argv[0]); |
82 | return EX_USAGE; |
83 | } |
84 | |
fdb3674a |
85 | int retval = fork_after_grep (opts); |
4f83e12d |
86 | |
87 | return retval; |
88 | } |
89 | |
fdb3674a |
90 | int 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]); |
ac6eee9c |
174 | kill (grep_cpid, SIGTERM); |
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]); |
ac6eee9c |
181 | kill (grep_cpid, SIGTERM); |
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 | |
195 | if (waitpid (grep_cpid, &grep_status, WNOHANG) > 0 && WIFEXITED (grep_status)) { |
1e23aa5b |
196 | close (grep_pipefd[1]); |
d8642d91 |
197 | |
f27db2a6 |
198 | if (WEXITSTATUS(grep_status) == 0) { |
199 | /* grep exited with match found */ |
200 | printf ("%d\n", cpid); |
d8642d91 |
201 | |
f27db2a6 |
202 | /* create a new child to keep pipe alive (will exit with exec'd program) */ |
203 | if (!fork ()) { |
204 | while (kill(cpid, 0) != -1 && errno != ESRCH ) sleep (1); |
205 | close (pipefd[0]); |
f27db2a6 |
206 | _exit(0); |
207 | } |
208 | close (pipefd[0]); |
f27db2a6 |
209 | return EX_OK; |
210 | } else { |
211 | /* grep exited due to an error */ |
fdb3674a |
212 | fprintf (stderr, "grep exited due to an error.\n"); |
52503080 |
213 | close (pipefd[0]); |
1e23aa5b |
214 | close (grep_pipefd[1]); |
f27db2a6 |
215 | return EX_IOERR; |
52503080 |
216 | } |
4f83e12d |
217 | } |
4f83e12d |
218 | |
52503080 |
219 | if (opts.timeout > 0) { |
220 | gettimeofday (&now, NULL); |
221 | timersub (&now, &begin, &diff); |
222 | if (diff.tv_sec >= opts.timeout) { |
223 | fprintf (stderr, "Timeout reached. \n"); |
224 | if (opts.kill_sig > 0) kill (cpid, opts.kill_sig); |
225 | close (pipefd[0]); |
1e23aa5b |
226 | close (grep_pipefd[1]); |
ac6eee9c |
227 | kill (grep_cpid, SIGTERM); |
52503080 |
228 | return EX_UNAVAILABLE; |
229 | } |
4f83e12d |
230 | } |
231 | } |
232 | } |
233 | } |
234 | } |