]>
git.gir.st - forkaftergrep.git/blob - fag.c
1 /* forkaftergrep (C) 2017 Tobias Girstmair, GPLv3 */
2 //TODO: if grep exits with an error, fag thinks a match was found (e.g. grep -BEP, killall grep)
3 //TODO: allow redirect of both streams to files (have to simulate `tee(1)' in the pipe-passer and the dummy-proc; instead of closing nontracked fd redirect to logfile/devnull)
4 //TODO: grep is missing `-e' and `-f' options; fag's `-e' collides with grep's
6 #define _XOPEN_SOURCE 500
7 #define _DEFAULT_SOURCE
23 void term_child ( int s
) {
24 if ( cpid
!= 0 ) kill ( cpid
, s
);
28 int main ( int argc
, char ** argv
) {
29 struct opt opts
= { 0 , 0 , 0 , NULL
, NULL
, "/dev/null" , "/dev/null" , STDOUT_FILENO
, "-q" };
30 /* `-q': don't print anything; exit with 0 on match; with 1 on error. used to interface with `grep' */
34 /* generate grep options string */
35 char * p
= opts
. grepopt
+ 2 ; /* move cursor behind `q' */
37 signal ( SIGPIPE
, SIG_IGN
); /* ignore broken pipe between fag and grep */
38 struct sigaction sa
= { 0 }; /* terminate child if fag gets interrupted/terminated */
39 sa
. sa_handler
= & term_child
; /* NOTE: will also be inherited by keep-pipe-alive-child */
40 sigaction ( SIGINT
, & sa
, NULL
);
41 sigaction ( SIGTERM
, & sa
, NULL
);
44 /* the `+' forces getopt to stop at the first non-option */
45 while (( opt
= getopt ( argc
, argv
, "+t:k::el:L:VhvEFGPiwxyU" )) != - 1 ) {
48 opts
. timeout
= atoi ( optarg
);
51 opts
. kill_sig
= optarg
? atoi ( optarg
) : SIGTERM
;
54 opts
. stream
= STDERR_FILENO
;
66 fprintf ( stderr
, VERTEXT USAGE
68 " \t -t N \t timeout after N seconds \n "
69 " \t -k[N] \t send signal N to child after timeout (default: 15/SIGTERM) \n "
70 " \t -e \t grep on stderr instead of stdout \n "
71 " \t -l FILE \t log PROGRAM's standard output to FILE \n "
72 " \t -L FILE \t log PROGRAM's standard error to FILE \n "
73 " \t -V \t be verbose; print PROGRAM's stdout/stderr to stderr \n "
74 " \t -[EFP] \t grep: matcher selection \n "
75 " \t -[iwxU] \t grep: matching control \n " , argv
[ 0 ]);
78 fprintf ( stderr
, VERTEXT
);
80 /* `grep' options (Note: missing `-e:', `-f:') */
81 case 'E' : case 'F' : case 'G' : case 'P' :
82 case 'i' : case 'y' : case 'w' : case 'x' :
83 case 'U' : *( p
++)= opt
; break ;
86 fprintf ( stderr
, "Unrecognized option: %c \n " USAGE
, optopt
, argv
[ 0 ]);
90 * p
= '\0' ; /* terminate grep_options string */
92 /* the first non-option argument is the search string */
94 opts
. pattern
= argv
[ optind
++];
96 fprintf ( stderr
, USAGE
"(Missing PATTERN) \n " , argv
[ 0 ]);
100 /* the remaining are the program to be run */
102 opts
. argv
= &( argv
[ optind
]);
104 fprintf ( stderr
, USAGE
"(Missing PROGRAM) \n " , argv
[ 0 ]);
108 int retval
= fork_after_grep ( opts
);
113 int fork_after_grep ( struct opt opts
) {
115 int pipefd_cpid
[ 2 ]; /* IPC to extract PID from daemonized userprog */
122 struct timeval begin
, now
, diff
;
124 if ( pipe ( pipefd
) == - 1 ) {
125 fprintf ( stderr
, "pipe error (userprog) \n " );
129 if ( pipe ( pipefd_cpid
) == - 1 ) {
130 fprintf ( stderr
, "pipe error (cpid-ipc) \n " );
136 if (( tmp_cpid
= fork ()) == - 1 ) {
137 fprintf ( stderr
, "fork error (daemonizer): %s" , strerror ( errno
));
140 close ( pipefd_cpid
[ 0 ]);
141 close ( pipefd_cpid
[ 1 ]);
149 dup2 ( pipefd
[ 1 ], opts
. stream
);
151 //dup2 (open("/dev/null", O_WRONLY), opts.stream==STDOUT_FILENO?STDERR_FILENO:STDOUT_FILENO);
152 close ( opts
. stream
== STDOUT_FILENO
? STDERR_FILENO
: STDOUT_FILENO
);
154 if ( setsid () == - 1 ) {
155 fprintf ( stderr
, "setsid error (daemonizer): %s" , strerror ( errno
));
159 if (( cpid_userprog
= fork ()) == - 1 ) {
160 fprintf ( stderr
, "fork error (userprog): %s" , strerror ( errno
));
163 if ( cpid_userprog
== 0 ) {
164 close ( pipefd_cpid
[ 0 ]);
165 close ( pipefd_cpid
[ 1 ]);
167 execvp ( opts
. argv
[ 0 ], opts
. argv
);
168 fprintf ( stderr
, "exec error (userprog): %s" , strerror ( errno
));
170 /* only way to get final child's pid to main is through IPC */
171 write ( pipefd_cpid
[ 1 ], & cpid_userprog
, sizeof ( pid_t
));
172 close ( pipefd_cpid
[ 0 ]);
173 close ( pipefd_cpid
[ 1 ]);
175 _exit ( EX_UNAVAILABLE
);
181 /* read userprog's PID from IPC: */
182 read ( pipefd_cpid
[ 0 ], & cpid
, sizeof ( pid_t
));
183 close ( pipefd_cpid
[ 0 ]);
184 close ( pipefd_cpid
[ 1 ]);
187 fcntl ( pipefd
[ 0 ], F_SETFL
, fcntl ( pipefd
[ 0 ], F_GETFL
, 0 ) | O_NONBLOCK
);
189 gettimeofday (& begin
, NULL
); /* for timeout */
191 if ( pipe ( grep_pipefd
) == - 1 ) {
192 fprintf ( stderr
, "pipe error (grep) \n " );
197 if (( grep_cpid
= fork ()) == - 1 ) {
198 fprintf ( stderr
, "fork error (grep): %s" , strerror ( errno
));
200 close ( grep_pipefd
[ 0 ]);
201 close ( grep_pipefd
[ 1 ]);
205 if ( grep_cpid
== 0 ) {
206 close ( grep_pipefd
[ 1 ]);
207 dup2 ( grep_pipefd
[ 0 ], STDIN_FILENO
);
208 close ( grep_pipefd
[ 0 ]);
210 close ( STDOUT_FILENO
);
212 execlp ( "grep" , "grep" , opts
. grepopt
, "--" , opts
. pattern
, NULL
);
213 fprintf ( stderr
, "exec error (grep): %s" , strerror ( errno
));
216 close ( grep_pipefd
[ 0 ]);
219 nbytes
= read ( pipefd
[ 0 ], buf
, BUF_SIZE
);
225 fprintf ( stderr
, "read error (userprog): %s" , strerror ( errno
));
227 close ( grep_pipefd
[ 1 ]);
228 kill ( grep_cpid
, SIGTERM
);
231 } else if ( nbytes
== 0 ) {
232 fprintf ( stderr
, "Child program exited prematurely (userprog). \n " );
234 close ( grep_pipefd
[ 1 ]);
235 kill ( grep_cpid
, SIGTERM
);
236 if ( waitpid ( cpid
, & status
, WNOHANG
) > 0 && WIFEXITED ( status
)) {
237 return WEXITSTATUS ( status
);
239 return EX_UNAVAILABLE
;
241 /* have new userprog-data, send it to grep */
243 write ( STDERR_FILENO
, buf
, nbytes
);
246 write ( grep_pipefd
[ 1 ], buf
, nbytes
); /* can cause SIGPIPE if grep exited, therefore signal will be ignored */
249 if ( waitpid ( grep_cpid
, & grep_status
, WNOHANG
) > 0 && WIFEXITED ( grep_status
)) {
250 close ( grep_pipefd
[ 1 ]);
252 if ( WEXITSTATUS ( grep_status
) == 0 ) {
253 /* grep exited with match found */
254 printf ( "%d \n " , cpid
);
257 /* create a new child to keep pipe alive, empty it and write log files (will exit with exec'd program) */
258 if (! fork ()){ setsid (); if (! fork ()) {
259 close ( 0 ); close ( 1 ); close ( 2 ); umask ( 0 ); chdir ( "/" );
260 int devnull
= open ( "/dev/null" , O_WRONLY
);
261 dup2 ( devnull
, pipefd
[ 0 ]);
263 while ( kill ( cpid
, 0 ) != - 1 && errno
!= ESRCH
) sleep ( 1 );
270 /* grep exited due to an error */
271 fprintf ( stderr
, "grep exited due to an error. \n " );
273 close ( grep_pipefd
[ 1 ]);
278 if ( opts
. timeout
> 0 ) {
279 gettimeofday (& now
, NULL
);
280 timersub (& now
, & begin
, & diff
);
281 if ( diff
. tv_sec
>= opts
. timeout
) {
282 fprintf ( stderr
, "Timeout reached. \n " );
283 if ( opts
. kill_sig
> 0 ) kill ( cpid
, opts
. kill_sig
);
285 close ( grep_pipefd
[ 1 ]);
286 kill ( grep_cpid
, SIGTERM
);
287 return EX_UNAVAILABLE
;