initial version
authorgirst <girst@users.noreply.github.com>
Tue, 7 Feb 2017 19:42:25 +0000 (20:42 +0100)
committergirst <girst@users.noreply.github.com>
Tue, 7 Feb 2017 19:42:25 +0000 (20:42 +0100)
Makefile [new file with mode: 0644]
README.md [new file with mode: 0644]
fag.1 [new file with mode: 0644]
fag.c [new file with mode: 0644]
fag.h [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..3186a28
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,9 @@
+.PHONY: all clean
+
+all: fag
+
+fag:
+       gcc -std=c99 -Wall -Werror -Wextra fag.c -o fag
+
+clean:
+       rm -f fag
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..afb351a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,70 @@
+# `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)
+```
diff --git a/fag.1 b/fag.1
new file mode 100644 (file)
index 0000000..0111b71
--- /dev/null
+++ b/fag.1
@@ -0,0 +1,97 @@
+.\" 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)
diff --git a/fag.c b/fag.c
new file mode 100644 (file)
index 0000000..e0218c3
--- /dev/null
+++ b/fag.c
@@ -0,0 +1,168 @@
+/* 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;
+                               }
+                       }
+               }
+       }
+}
diff --git a/fag.h b/fag.h
new file mode 100644 (file)
index 0000000..cda06cf
--- /dev/null
+++ b/fag.h
@@ -0,0 +1,25 @@
+#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
Imprint / Impressum