[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

possible solution to a TODO item...



So, I read this in the TODO.  I hope it hasn't been dealt with already.  :-)

  * There needs to be a centralised logging method. Syslog will do, but
    we currently have a lot of nasty code that appends messages to
    /var/log/messages directly. All of that would be converted to pipe it
    to logger. However, this is a problem, because piping a command to
    logger loses the exit status of the command. Need some way to run a
    command, logging the ouput to syslog, w/o losing exit status.

Anyway, I hacked together a solution for the heck of it; it's a program which
forms a pipe, but returns the exit status of the first command (not the
second).

So instead of 'foo | logger' (giving the logger exit status)
you do 'mispipe foo logger' (giving the foo exit status)

I'm sure it can be improved if added functionality is desired.


-- 
Nathanael Nerode  <neroden at gcc.gnu.org>
US citizens: if you're considering voting for Bush, look at these first:
http://www.misleader.org/  http://www.cbc.ca/news/background/arar/
http://www.house.gov/reform/min/politicsandscience/
/*
 * mispipe: written by Nathanael Nerode.
 *
 * Copyright 2004 Nathanael Nerode.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
 * THE SOFTWARE.
 */

/*
 * Usage: mispipe <command1> <command2>
 * Run <command1> | <command2>, but return with the exit status of <command1>.
 *
 * This is designed for a very specific purpose: logging.
 * "foo | logger -t foo"
 * will return with the exit status of logger, not that of foo.
 * "mispipe foo 'logger -t foo'"
 * will return with the exit status of foo.
 */

/*
 * To do:
 * Make this into a fancy, internationalized, option-handling hellbeast.
 * (But why bother?  It does its job quite well.)
 */

#include <errno.h> /* errno */
#include <sys/types.h>
#include <unistd.h> /* pipe(), fork(),... */
#include <stdlib.h> /* system() */
#include <sys/wait.h> /* waitpid(), etc. */
#include <stdio.h>
#include <stdarg.h> /* va_list, for error() */

static const char* progname; /* Stores argv[0] */
static int filedes[2]; /* Stores pipe file descriptors */

/* Subroutine for 'warning' and 'error' which prefixes progname */
static void warning_prefix(void) {
  fputs(progname, stderr);
  fputs(": ", stderr);
}

#if 0
/* Issue a warning */
__attribute__(( unused, format (printf, 1, 2) ))
static void warning(const char* formatstr, ...) {
  va_list ap;
  va_start(ap, formatstr);
  warning_prefix();
  vfprintf(stderr, formatstr, ap);
  va_end(ap);
}
#endif /* 0 */

/* Issue a warning, then die */
__attribute__(( noreturn, format (printf, 1, 2) ))
static void error(const char* formatstr, ...) {
  va_list ap;
  va_start(ap, formatstr);
  warning_prefix();
  vfprintf(stderr, formatstr, ap);
  va_end(ap);
  exit(1);
}

/* Issue a warning, then die, with errno */
__attribute__(( noreturn ))
static void error_with_errno(const char* message) {
  int saved_errno;
  saved_errno=errno;
  warning_prefix();
  fputs(message, stderr);
  fputs(": error number ", stderr);
  fprintf(stderr, "%i\n", saved_errno);
  exit(1);
}

/* Convert 'wait'/'system'-style exit status to 'exit'-style exit status */
__attribute__(( const ))
static int shorten_status(int status_big) {
    if (WIFEXITED(status_big))
      return WEXITSTATUS(status_big);
    if (WIFSIGNALED(status_big))
      return 128+WTERMSIG(status_big);
    if (WIFSTOPPED(status_big))
      return 128+WSTOPSIG(status_big);
#ifdef WCOREDUMP
    if (WCOREDUMP(status_big))
      error("Ow, somebody dumped core!");
#endif
    error("shorten_status got an invalid status (?!)");
}

/* All the work for command 2. */
__attribute__(( noreturn ))
static void subprocess2(const char* cmd) {
    /* Close the old standard input. */
    if (close(0))
      error_with_errno("Failed (in child) closing standard input");
    /* Make the reading end of the pipe the new standard input. */
    if (dup2(filedes[0], 0) == -1)
      error_with_errno("Failed (in child) redefining standard input");
    /* Close the other end of the pipe. */
    if (close(filedes[1]))
      error_with_errno("Failed (in child) closing filedes[1]");
    /* Do the second command, and throw away the exit status. */
    system(cmd);
    /* Close all the file descriptors. */
    if (close(filedes[0]))
      error_with_errno("Failed (in child) closing filedes[0]"
                       " (while cleaning up)");
    if (close(0))
      error_with_errno("Failed (in child) closing standard output "
                       " (while cleaning up)");
    /* Do the second command, and throw away the exit status. */
    exit(0);
}

/* MAIN PROGRAM */
int main (int argc, const char ** argv)
{
  int status_big; /* Exit status, 'wait' and 'system' style */
  pid_t child2_pid;
  pid_t dead_pid;

  /* Set progname */
  progname = argv[0];

  /* Verify arguments */
  if (argc != 3) 
    error("Wrong number of args, aborting\n");
  /* Open a new pipe */
  if (pipe(filedes))
    error_with_errno("pipe() failed");

  /* Fork to run second command */
  child2_pid = fork();
  if (child2_pid == 0)
    subprocess2(argv[2]);
  if (child2_pid == -1)
    error_with_errno("fork() failed");

  /* Run first command inline (seriously!) */
  /* Close standard output. */
  if (close(1))
    error_with_errno("Failed closing standard output");
  /* Make the writing end of the pipe the new standard output. */
  if (dup2(filedes[1], 1) == -1)
    error_with_errno("Failed redefining standard output");
  /* Close the other end of the pipe. */
  if (close(filedes[0]))
    error_with_errno("Failed closing filedes[0]");
  /* Do the first command, and (crucially) get the status. */
    status_big = system(argv[1]);

  /* Close the pipe "standard output". */
  if (close(filedes[1]))
    error_with_errno("Failed closing filedes[1] (while cleaning up)");
  /* Close standard output. */
  if (close(1))
    error_with_errno("Failed closing standard output (while cleaning up)");

  /* Wait for the other process to exit. */
  /* We don't care about the status. */
  dead_pid = waitpid(child2_pid, NULL, WUNTRACED);
  if (dead_pid == -1) {
    error_with_errno("waitpid() failed");
  } else if (dead_pid != child2_pid) {
    error("waitpid(): Who died? %i\n", dead_pid);
  }

  /* Return the desired exit status. */
  return shorten_status(status_big);
}

Reply to: