--- /dev/null
+.PHONY: all clean
+
+all: fag
+
+fag:
+ gcc -std=c99 -Wall -Werror -Wextra fag.c -o fag
+
+clean:
+ rm -f fag
--- /dev/null
+# `fag` (Fork After Grep)
+
+```
+fag(1) User Commands fag(1)
+
+NAME
+ fag - daemonize program after a string was found (ForkAfterGrep)
+
+SYNOPSIS
+ fag [OPTIONS] PATTERN PROGRAM [ARGUMENTS...]
+
+DESCRIPTION
+ fag searches the PROGRAM for the string PATTERN. This is useful if a
+ program takes a while to initialize and prints a message to stdout or
+ stderr when ready. When placed in a script, fag blocks execution until
+ the pattern was found, then daemonizes the child process, returns the
+ PID on stdout and exits.
+
+OPTIONS
+ Behaviour Changing Options
+ -t SECONDS
+ Set a timeout of SECONDS seconds.
+
+ -k [SIGNAL]
+ If given, send a signal to PROGRAM. SIGNAL defaults to SIGTERM
+ (15). Right now, only decimal notation is implemented.
+
+ -e Search PATTERN on stderr instead of stdout.
+
+ Generic Program Information
+ -h Output a short usage message and exit.
+
+ -v Display version and copyright information and exit.
+
+EXIT STATUS
+ If PATTERN was found, 0 is returned. Otherwise, the exit status follows
+ the BSD guideline outlined in #include <sysexits.h> if the error
+ occured from within fag or in case the chid process exits prematurely,
+ its exit code is inherited. Notably, 69 is returned when the timeout is
+ reached.
+
+BUGS
+ Known Bugs
+ Only a simple string search is performed on PATTERN in this version.
+
+ If a PROGRAM like cat opens stdout/stderr, but never writes to it, the
+ timeout isn't triggered.
+
+ SIGNAL needs to be given as an integer; mnemonic should be supported in
+ the future.
+
+ Sometimes, stdin behaves strange after the program terminates.
+
+ Reporting Bugs
+ Please report bugs and patches to the issue tracker at
+ https://github.com/girst/forkaftergrep/.
+
+NOTES
+ Some might find the name of this program offensive. Feel free to create
+ a symlink or alias on your system.
+
+COPYRIGHT
+ Copyright 2017 Tobias Girstmair. This is free software; see
+ https://www.gnu.org/licenses/gpl-3.0.html for conditions.
+
+AUTHOR
+ Tobias Girstmair (http://isticktoit.net)
+
+1.0 07 February 2017 fag(1)
+```
--- /dev/null
+.\" Manpage for nuseradd.
+.\" Contact vivek@nixcraft.net.in to correct errors or typos.
+.TH fag 1 "07 February 2017" "1.0" "User Commands"
+.hy 0
+.SH NAME
+fag \- daemonize program after a string was found (ForkAfterGrep)
+.
+.SH SYNOPSIS
+.B fag
+.RI [ OPTIONS ]
+.I PATTERN
+.I PROGRAM
+.RI [ ARGUMENTS .\|.\|.]
+.br
+.SH DESCRIPTION
+.B fag
+searches the
+.IR PROGRAM
+for the string
+.IR PATTERN .
+This is useful if a program takes a while to initialize and prints a message to stdout or stderr when ready. When placed in a script,
+.B fag
+blocks execution until the pattern was found, then daemonizes the child process, returns the PID on stdout and exits.
+.SH OPTIONS
+.SS "Behaviour Changing Options"
+.TP
+.BI \-t " SECONDS" "\fR
+Set a timeout of
+.I SECONDS
+seconds.
+.TP
+.BR \-k " [" \fISIGNAL\fP "]
+If given, send a signal to
+.IR PROGRAM .
+.I SIGNAL
+defaults to
+.BR SIGTERM " (15)."
+Right now, only decimal notation is implemented.
+.TP
+.BR \-e
+Search
+.IR PATTERN
+on
+.BR stderr
+instead of
+.BR stdout .
+.SS "Generic Program Information"
+.TP
+.B \-h
+Output a short usage message and exit.
+.TP
+.BR \-v
+Display version and copyright information and exit.
+.SH EXIT STATUS
+If
+.IR PATTERN
+was found, 0 is returned. Otherwise, the exit status follows the BSD guideline outlined in
+.B #include <sysexits.h>
+if the error occured from within
+.B fag
+or in case the chid process exits prematurely, its exit code is inherited. Notably, \fI69\fP is returned when the timeout is reached.
+.\".RS
+.\".IP *
+.\"EX_OK (0): successful termination
+.\".IP *
+.\"EX_UNAVAILABLE (69): PROGRAM timed out
+.\".IP *
+.\"EX_USAGE (64): command was used incorrectly (argument error)
+.\".IP *
+.\"EX_OSERR (71): system error (can't fork, pipe, etc.)
+.\".IP *
+.\"EX_IOERR (74): input/output error (e.g. can't read)
+.\".RE
+.SH BUGS
+.SS Known Bugs
+Only a simple string search is performed on
+.IR PATTERN
+in this version.
+.PP
+If a
+.IR PROGRAM
+like
+.BR cat
+opens stdout/stderr, but never writes to it, the timeout isn't triggered.
+.PP
+.IR SIGNAL
+needs to be given as an integer; mnemonic should be supported in the future.
+.PP
+Sometimes, stdin behaves strange after the program terminates.
+.SS Reporting Bugs
+Please report bugs and patches to the issue tracker at https://github.com/girst/forkaftergrep/.
+.SH NOTES
+Some might find the name of this program offensive. Feel free to create a symlink or alias on your system.
+.SH COPYRIGHT
+Copyright 2017 Tobias Girstmair. This is free software; see https://www.gnu.org/licenses/gpl-3.0.html for conditions.
+.SH AUTHOR
+Tobias Girstmair (http://isticktoit.net)
--- /dev/null
+/* forkaftergrep (C) 2017 Tobias Girstmair, GPLv3 */
+
+#define _XOPEN_SOURCE 500
+#define _BSD_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include "fag.h"
+
+struct opt opts = {0, 0, NULL, NULL, STDOUT_FILENO};
+
+int main (int argc, char** argv) {
+ int opt;
+ opterr = 0;
+
+ /* the `+' forces getopt to stop at the first non-option */
+ while ((opt = getopt (argc, argv, "+t:k::ehv")) != -1) {
+ switch (opt) {
+ case 't':
+ opts.timeout = atoi (optarg);
+ break;
+ case 'k':
+ opts.kill_sig = optarg ? atoi (optarg) : SIGTERM;
+ break;
+ case 'e':
+ opts.stream = STDERR_FILENO;
+ break;
+ case 'h':
+ fprintf (stderr, VERTEXT USAGE
+ "Options:\n"
+ "\t-t N\ttimeout after N seconds\n"
+ "\t-k [M]\tsend signal M to child after timeout (default: 15/SIGTERM)\n"
+ "\t-e\tgrep on stderr instead of stdout\n", argv[0]);
+ return EX_OK;
+ case 'v':
+ fprintf (stderr, VERTEXT);
+ return EX_OK;
+ default:
+ fprintf (stderr, "Unrecognized option: %c\n" USAGE, optopt, argv[0]);
+ return EX_USAGE;
+ }
+ }
+
+ /* the first non-option argument is the search string */
+ if (optind < argc) {
+ opts.pattern = argv[optind++];
+ } else {
+ fprintf (stderr, USAGE "(Missing PATTERN)\n", argv[0]);
+ return EX_USAGE;
+ }
+
+ /* the remaining are the program to be run */
+ if (optind < argc) {
+ opts.argv = &(argv[optind]);
+ } else {
+ fprintf (stderr, USAGE "(Missing PROGRAM)\n", argv[0]);
+ return EX_USAGE;
+ }
+
+ int retval = fork_after_grep (opts);
+
+ return retval;
+}
+
+int fork_after_grep (struct opt opts) {
+ printf ("timeout:\t%d\n" "kill_sig:\t%d\n" "pattern: \t%s\n" "stream: \t%d\n" "program: \t"
+ , opts.timeout, opts.kill_sig, opts.pattern, opts.stream);
+ for (char** p = opts.argv; *p;) printf ("%s ", *p++); putchar ('\n');
+
+ int pipefd[2];
+ pid_t cpid;
+ int status;
+
+ char buf[BUF_SIZE];
+ int nbytes;
+
+ struct timeval begin, now, diff;
+
+ if (pipe(pipefd) == -1) {
+ fprintf (stderr, "pipe error\n");
+ return EX_OSERR;
+ }
+
+ if ((cpid = fork()) == -1) {
+ fprintf (stderr, "fork error: %s", strerror (errno));
+ close (pipefd[0]);
+ close (pipefd[1]);
+ return EX_OSERR;
+ }
+
+ if (cpid == 0) {
+ close (pipefd[0]);
+ dup2 (pipefd[1], opts.stream);
+ close (pipefd[1]);
+ close (opts.stream==STDOUT_FILENO?STDERR_FILENO:STDOUT_FILENO);
+
+ if (setsid () == -1) {
+ fprintf (stderr, "setsid error: %s", strerror (errno));
+ _exit (EX_OSERR);
+ }
+
+ execvp (opts.argv[0], opts.argv);
+ fprintf (stderr, "exec error: %s", strerror (errno));
+ _exit (EX_UNAVAILABLE);
+ } else {
+ close (pipefd[1]);
+ fcntl (pipefd[0], F_SETFL, fcntl (pipefd[0], F_GETFL, 0) | O_NONBLOCK);
+
+ gettimeofday (&begin, NULL);
+
+ for (;;) {
+ usleep (20000);
+ nbytes = read (pipefd[0], buf, BUF_SIZE);
+ if (nbytes == -1) {
+ switch (errno) {
+ case EAGAIN:
+ continue;
+ default:
+ fprintf (stderr, "read error: %s", strerror (errno));
+ close (pipefd[0]);
+ close (pipefd[1]);
+ return EX_IOERR;
+ }
+ } else if (nbytes == 0) {
+ fprintf (stderr, "Child program exited prematurely.\n");
+ close (pipefd[0]);
+ close (pipefd[1]);
+ if (waitpid (cpid, &status, WNOHANG) > 0 && WIFEXITED (status)) {
+ return WEXITSTATUS (status);
+ }
+ return EX_UNAVAILABLE;
+ }
+ if (strstr (buf, opts.pattern) != NULL) {
+ printf ("%d\n", cpid);
+ /* create a new child to keep pipe alive (will exit with exec'd program) */
+ if (!fork ()) {
+ while (kill(cpid, 0) != -1 && errno != ESRCH ) sleep (1);
+ close (pipefd[0]);
+ close (pipefd[1]);
+//close(0);close(1);close(2);
+ _exit(0);
+ }
+ close (pipefd[0]);
+ close (pipefd[1]);
+//close(0);close(1);close(2);
+ return EX_OK;
+ }
+
+ if (opts.timeout > 0) {
+ gettimeofday (&now, NULL);
+ timersub (&now, &begin, &diff);
+ if (diff.tv_sec >= opts.timeout) {
+ fprintf (stderr, "Timeout reached. \n");
+ if (opts.kill_sig > 0) kill (cpid, opts.kill_sig);
+ close (pipefd[0]);
+ close (pipefd[1]);
+ return EX_UNAVAILABLE;
+ }
+ }
+ }
+ }
+}
--- /dev/null
+#ifndef __FAG_H__
+#define __FAG_H__
+
+#define PROGRNAME "fag"
+#define VERSION "1.0"
+#define AUTHOR "Tobias Girstmair"
+#define YEAR "2017"
+#define LICENSE "GNU GPLv3"
+
+#define BUF_SIZE 4096
+
+#define USAGE "Usage: %s [OPTION]... PATTERN PROGRAM [ARG]...\n"
+#define VERTEXT ""PROGRNAME" "VERSION"\t(C) "YEAR" "AUTHOR", "LICENSE"\n"
+
+struct opt {
+ int timeout;
+ int kill_sig;
+ char* pattern;
+ char** argv;
+ int stream;
+};
+
+int fork_after_grep (struct opt opts);
+
+#endif