1 /*
2  * mispipe: written by Nathanael Nerode.
3  *
4  * Copyright 2004 Nathanael Nerode.
5  *
6  * Licensed under the GPL version 2 or above, and dual-licensed under the
7  * following license:
8  *
9  * Permission is hereby granted, free of charge, to any person obtaining a
10  * copy of this software and associated documentation files (the "Software"),
11  * to deal in the Software without restriction, including without limitation
12  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
13  * and/or sell copies of the Software, and to permit persons to whom the
14  * Software is furnished to do so, subject to the following conditions:
15  *
16  * The above copyright notice and this permission notice shall be included in
17  * all copies or substantial portions of the Software.
18  *
19  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
22  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25  * THE SOFTWARE.
26  */
27 
28 /*
29  * Usage: mispipe <command1> <command2>
30  * Run <command1> | <command2>, but return with the exit status of <command1>.
31  *
32  * This is designed for a very specific purpose: logging.
33  * "foo | logger -t foo"
34  * will return with the exit status of logger, not that of foo.
35  * "mispipe foo 'logger -t foo'"
36  * will return with the exit status of foo.
37  */
38 
39 /*
40  * To do:
41  * Make this into a fancy, internationalized, option-handling hellbeast.
42  * (But why bother?  It does its job quite well.)
43  */
44 
45 #include <errno.h> /* errno */
46 #include <sys/types.h>
47 #include <unistd.h> /* pipe(), fork(),... */
48 #include <stdlib.h> /* system() */
49 #include <sys/wait.h> /* waitpid(), etc. */
50 #include <stdio.h>
51 #include <stdarg.h> /* va_list, for error() */
52 
53 static const char* progname; /* Stores argv[0] */
54 static int filedes[2]; /* Stores pipe file descriptors */
55 
56 /* Subroutine for 'warning' and 'error' which prefixes progname */
warning_prefix(void)57 static void warning_prefix(void) {
58 	fputs(progname, stderr);
59 	fputs(": ", stderr);
60 }
61 
62 /* Issue a warning, then die */
63 __attribute__(( noreturn, format (printf, 1, 2) ))
error(const char * formatstr,...)64 static void error(const char* formatstr, ...) {
65 	va_list ap;
66 	va_start(ap, formatstr);
67 	warning_prefix();
68 	vfprintf(stderr, formatstr, ap);
69 	va_end(ap);
70 	exit(1);
71 }
72 
73 /* Issue a warning, then die, with errno */
74 __attribute__(( noreturn ))
error_with_errno(const char * message)75 static void error_with_errno(const char* message) {
76 	int saved_errno;
77 	saved_errno=errno;
78 	warning_prefix();
79 	fputs(message, stderr);
80 	fputs(": error number ", stderr);
81 	fprintf(stderr, "%i\n", saved_errno);
82 	exit(1);
83 }
84 
85 /* Convert 'wait'/'system'-style exit status to 'exit'-style exit status */
86 __attribute__(( const ))
shorten_status(int status_big)87 static int shorten_status(int status_big) {
88 	if (WIFEXITED(status_big))
89 		return WEXITSTATUS(status_big);
90 	if (WIFSIGNALED(status_big))
91 		return 128+WTERMSIG(status_big);
92 	if (WIFSTOPPED(status_big))
93 		return 128+WSTOPSIG(status_big);
94 #ifdef WCOREDUMP
95 	if (WCOREDUMP(status_big))
96 		 error("Ow, somebody dumped core!");
97 #endif
98 	error("shorten_status got an invalid status (?!)");
99 }
100 
101 /* All the work for command 2. */
102 __attribute__(( noreturn ))
subprocess2(const char * cmd)103 static void subprocess2(const char* cmd) {
104 	/* Close the old standard input. */
105 	if (close(0))
106 		error_with_errno("Failed (in child) closing standard input");
107 	/* Make the reading end of the pipe the new standard input. */
108 	if (dup2(filedes[0], 0) == -1)
109 		error_with_errno("Failed (in child) redefining standard input");
110 	/* Close the original file descriptor for it */
111 	if (close(filedes[0]))
112 		error_with_errno("Failed (in child) closing filedes[0]");
113 	/* Close the other end of the pipe. */
114 	if (close(filedes[1]))
115 		error_with_errno("Failed (in child) closing filedes[1]");
116 	/* Do the second command, and throw away the exit status. */
117 	if (system(cmd)) {}
118 	/* Close the standard input. */
119 	if (close(0))
120 		error_with_errno("Failed (in child) closing standard output "
121 			" (while cleaning up)");
122 	exit(0);
123 }
124 
main(int argc,const char ** argv)125 int main (int argc, const char ** argv) {
126 	int status_big; /* Exit status, 'wait' and 'system' style */
127 	pid_t child2_pid;
128 	pid_t dead_pid;
129 
130 	/* Set progname */
131 	progname = argv[0];
132 
133 	/* Verify arguments */
134 	if (argc != 3)
135 		error("Wrong number of args, aborting\n");
136 	/* Open a new pipe */
137 	if (pipe(filedes))
138 		error_with_errno("pipe() failed");
139 
140 	/* Fork to run second command */
141 	child2_pid = fork();
142 	if (child2_pid == 0)
143 		subprocess2(argv[2]);
144 	if (child2_pid == -1)
145 		error_with_errno("fork() failed");
146 
147 	/* Run first command inline (seriously!) */
148 	/* Close standard output. */
149 	if (close(1))
150 		error_with_errno("Failed closing standard output");
151 	/* Make the writing end of the pipe the new standard output. */
152 	if (dup2(filedes[1], 1) == -1)
153 		error_with_errno("Failed redefining standard output");
154 	/* Close the original file descriptor for it. */
155 	if (close(filedes[1]))
156 		error_with_errno("Failed closing filedes[1]");
157 	/* Close the other end of the pipe. */
158 	if (close(filedes[0]))
159 		error_with_errno("Failed closing filedes[0]");
160 	/* Do the first command, and (crucially) get the status. */
161 	status_big = system(argv[1]);
162 
163 	/* Close standard output. */
164 	if (close(1))
165 		error_with_errno("Failed closing standard output (while cleaning up)");
166 
167 	/* Wait for the other process to exit. */
168 	/* We don't care about the status. */
169 	dead_pid = waitpid(child2_pid, NULL, WUNTRACED);
170 	if (dead_pid == -1) {
171 		error_with_errno("waitpid() failed");
172 	}
173 	else if (dead_pid != child2_pid) {
174 		error("waitpid(): Who died? %i\n", dead_pid);
175 	}
176 
177 	/* Return the desired exit status. */
178 	return shorten_status(status_big);
179 }
180