From 4f83e12dd2c7b8a87a67159ce9861b4e6d3ee23d Mon Sep 17 00:00:00 2001 From: girst Date: Tue, 7 Feb 2017 20:42:25 +0100 Subject: [PATCH] initial version --- Makefile | 9 +++ README.md | 70 +++++++++++++++++++++++ fag.1 | 97 +++++++++++++++++++++++++++++++ fag.c | 168 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ fag.h | 25 ++++++++ 5 files changed, 369 insertions(+) create mode 100644 Makefile create mode 100644 README.md create mode 100644 fag.1 create mode 100644 fag.c create mode 100644 fag.h diff --git a/Makefile b/Makefile new file mode 100644 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 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 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 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 +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 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 +#include +#include +#include +#include +#include +#include +#include +#include +#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 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 -- 2.39.3