--- /dev/null
+/*
+ * mispipe: written by Nathanael Nerode.
+ *
+ * Copyright 2004 Nathanael Nerode.
+ *
+ * Licensed under the GPL version 2 or above, and dual-licensed under the
+ * following license:
+ *
+ * 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);
+}
+
+/* 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)");
+ exit(0);
+}
+
+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);
+}
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+
+Copyright 2006 Joey Hess <joey@kitenet.net>
+
+This program is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+-->
+
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook V4.4//EN"
+"file:///usr/share/xml/docbook/schema/dtd/4.4/docbookx.dtd"
+[]>
+
+<refentry>
+ <refentryinfo>
+ <address>
+ <email>neroden@fastmail.fm</email>
+ </address>
+ <author>
+ <firstname>Nathanael</firstname>
+ <surname>Nerode</surname>
+ </author>
+ <date>2006-09-07</date>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>mispipe</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>mispipe</refname>
+ <refpurpose>pipe two commands, returning the exit status of
+ the first</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>mispope</command>
+ <arg><replaceable>"command1"</replaceable></arg>
+ <arg><replaceable>"command2"</replaceable></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>DESCRIPTION</title>
+
+ <para><command>mispipe</command> pipes two commands
+ together like the shell does, but unlike piping in the
+ shell, the exit status of the first command is returned.
+ </para>
+
+ <para>
+ Note that some shells, notably <command>bash</command>,
+ do offer a pipefail option, however, that option does not
+ behave the same since it makes a failure of any command in
+ the pipeline be returned, not just the exit status of the
+ first.
+ </para>
+
+ </refsect1>
+
+ <refsect1>
+ <title>EXIT STATUS</title>
+
+ <para>The exit status of the first command. If the process
+ terminated abnormally (due to a signal), 128 will be added
+ to its exit status.</para>
+
+ </refsect1>
+</refentry>