1 1.1 christos /* $NetBSD: popen.c,v 1.1 2018/01/09 03:31:15 christos Exp $ */ 2 1.1 christos 3 1.1 christos /* 4 1.1 christos * Copyright (c) 1988, 1993 5 1.1 christos * The Regents of the University of California. All rights reserved. 6 1.1 christos * Copyright (c) 2017 The NetBSD Foundation, Inc. 7 1.1 christos * Copyright (c) 2016 The DragonFly Project 8 1.1 christos * Copyright (c) 2014 The FreeBSD Foundation 9 1.1 christos * All rights reserved. 10 1.1 christos * 11 1.1 christos * This code is derived from software contributed to The NetBSD Foundation 12 1.1 christos * by Tomohiro Kusumi <kusumi.tomohiro (at) gmail.com>. 13 1.1 christos * 14 1.1 christos * This code is derived from software written by Ken Arnold and 15 1.1 christos * published in UNIX Review, Vol. 6, No. 8. 16 1.1 christos * 17 1.1 christos * Portions of this software were developed by Edward Tomasz Napierala 18 1.1 christos * under sponsorship from the FreeBSD Foundation. 19 1.1 christos * 20 1.1 christos * Redistribution and use in source and binary forms, with or without 21 1.1 christos * modification, are permitted provided that the following conditions 22 1.1 christos * are met: 23 1.1 christos * 1. Redistributions of source code must retain the above copyright 24 1.1 christos * notice, this list of conditions and the following disclaimer. 25 1.1 christos * 2. Redistributions in binary form must reproduce the above copyright 26 1.1 christos * notice, this list of conditions and the following disclaimer in the 27 1.1 christos * documentation and/or other materials provided with the distribution. 28 1.1 christos * 3. Neither the name of the University nor the names of its contributors 29 1.1 christos * may be used to endorse or promote products derived from this software 30 1.1 christos * without specific prior written permission. 31 1.1 christos * 32 1.1 christos * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 33 1.1 christos * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 34 1.1 christos * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 35 1.1 christos * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 36 1.1 christos * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 37 1.1 christos * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 38 1.1 christos * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 39 1.1 christos * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 40 1.1 christos * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 41 1.1 christos * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 42 1.1 christos * SUCH DAMAGE. 43 1.1 christos * 44 1.1 christos */ 45 1.1 christos #include <sys/cdefs.h> 46 1.1 christos __RCSID("$NetBSD: popen.c,v 1.1 2018/01/09 03:31:15 christos Exp $"); 47 1.1 christos 48 1.1 christos #include <sys/types.h> 49 1.1 christos #include <sys/queue.h> 50 1.1 christos #include <sys/wait.h> 51 1.1 christos #include <errno.h> 52 1.1 christos #include <fcntl.h> 53 1.1 christos #include <paths.h> 54 1.1 christos #include <stdarg.h> 55 1.1 christos #include <stdio.h> 56 1.1 christos #include <stdlib.h> 57 1.1 christos #include <string.h> 58 1.1 christos #include <unistd.h> 59 1.1 christos 60 1.1 christos #include "common.h" 61 1.1 christos 62 1.1 christos extern char **environ; 63 1.1 christos 64 1.1 christos struct pid { 65 1.1 christos SLIST_ENTRY(pid) next; 66 1.1 christos FILE *outfp; 67 1.1 christos pid_t pid; 68 1.1 christos char *command; 69 1.1 christos }; 70 1.1 christos static SLIST_HEAD(, pid) pidlist = SLIST_HEAD_INITIALIZER(pidlist); 71 1.1 christos 72 1.1 christos #define ARGV_LEN 42 73 1.1 christos 74 1.1 christos /* 75 1.1 christos * Replacement for popen(3), without stdin (which we do not use), but with 76 1.1 christos * stderr, proper logging, and improved command line arguments passing. 77 1.1 christos * Error handling is built in - if it returns, then it succeeded. 78 1.1 christos */ 79 1.1 christos FILE * 80 1.1 christos auto_popen(const char *argv0, ...) 81 1.1 christos { 82 1.1 christos va_list ap; 83 1.1 christos struct pid *cur, *p; 84 1.1 christos pid_t pid; 85 1.1 christos int error, i, nullfd, outfds[2]; 86 1.1 christos char *arg, *argv[ARGV_LEN], *command; 87 1.1 christos 88 1.1 christos nullfd = open(_PATH_DEVNULL, O_RDWR, 0); 89 1.1 christos if (nullfd < 0) 90 1.1 christos log_err(1, "cannot open %s", _PATH_DEVNULL); 91 1.1 christos 92 1.1 christos error = pipe(outfds); 93 1.1 christos if (error != 0) 94 1.1 christos log_err(1, "pipe"); 95 1.1 christos 96 1.1 christos cur = malloc(sizeof(struct pid)); 97 1.1 christos if (cur == NULL) 98 1.1 christos log_err(1, "malloc"); 99 1.1 christos 100 1.1 christos argv[0] = checked_strdup(argv0); 101 1.1 christos command = argv[0]; 102 1.1 christos 103 1.1 christos va_start(ap, argv0); 104 1.1 christos for (i = 1;; i++) { 105 1.1 christos if (i >= ARGV_LEN) 106 1.1 christos log_errx(1, "too many arguments to auto_popen"); 107 1.1 christos arg = va_arg(ap, char *); 108 1.1 christos argv[i] = arg; 109 1.1 christos if (arg == NULL) 110 1.1 christos break; 111 1.1 christos 112 1.1 christos command = concat(command, ' ', arg); 113 1.1 christos } 114 1.1 christos va_end(ap); 115 1.1 christos 116 1.1 christos cur->command = checked_strdup(command); 117 1.1 christos 118 1.1 christos switch (pid = fork()) { 119 1.1 christos case -1: /* Error. */ 120 1.1 christos log_err(1, "fork"); 121 1.1 christos /* NOTREACHED */ 122 1.1 christos case 0: /* Child. */ 123 1.1 christos dup2(nullfd, STDIN_FILENO); 124 1.1 christos dup2(outfds[1], STDOUT_FILENO); 125 1.1 christos 126 1.1 christos close(nullfd); 127 1.1 christos close(outfds[0]); 128 1.1 christos close(outfds[1]); 129 1.1 christos 130 1.1 christos SLIST_FOREACH(p, &pidlist, next) 131 1.1 christos close(fileno(p->outfp)); 132 1.1 christos execvp(argv[0], argv); 133 1.1 christos log_err(1, "failed to execute %s", argv[0]); 134 1.1 christos /* NOTREACHED */ 135 1.1 christos } 136 1.1 christos 137 1.1 christos log_debugx("executing \"%s\" as pid %d", command, pid); 138 1.1 christos 139 1.1 christos /* Parent; assume fdopen cannot fail. */ 140 1.1 christos cur->outfp = fdopen(outfds[0], "r"); 141 1.1 christos close(nullfd); 142 1.1 christos close(outfds[1]); 143 1.1 christos 144 1.1 christos /* Link into list of file descriptors. */ 145 1.1 christos cur->pid = pid; 146 1.1 christos SLIST_INSERT_HEAD(&pidlist, cur, next); 147 1.1 christos 148 1.1 christos return cur->outfp; 149 1.1 christos } 150 1.1 christos 151 1.1 christos int 152 1.1 christos auto_pclose(FILE *iop) 153 1.1 christos { 154 1.1 christos struct pid *cur, *last = NULL; 155 1.1 christos int status; 156 1.1 christos pid_t pid; 157 1.1 christos 158 1.1 christos /* 159 1.1 christos * Find the appropriate file pointer and remove it from the list. 160 1.1 christos */ 161 1.1 christos SLIST_FOREACH(cur, &pidlist, next) { 162 1.1 christos if (cur->outfp == iop) 163 1.1 christos break; 164 1.1 christos last = cur; 165 1.1 christos } 166 1.1 christos if (cur == NULL) { 167 1.1 christos return -1; 168 1.1 christos } 169 1.1 christos if (last == NULL) 170 1.1 christos SLIST_REMOVE_HEAD(&pidlist, next); 171 1.1 christos else 172 1.1 christos SLIST_REMOVE_AFTER(last, next); 173 1.1 christos 174 1.1 christos fclose(cur->outfp); 175 1.1 christos 176 1.1 christos do { 177 1.1 christos pid = wait4(cur->pid, &status, 0, NULL); 178 1.1 christos } while (pid == -1 && errno == EINTR); 179 1.1 christos 180 1.1 christos if (WIFSIGNALED(status)) { 181 1.1 christos log_warnx("\"%s\", pid %d, terminated with signal %d", 182 1.1 christos cur->command, pid, WTERMSIG(status)); 183 1.1 christos return status; 184 1.1 christos } 185 1.1 christos 186 1.1 christos if (WEXITSTATUS(status) != 0) { 187 1.1 christos log_warnx("\"%s\", pid %d, terminated with exit status %d", 188 1.1 christos cur->command, pid, WEXITSTATUS(status)); 189 1.1 christos return status; 190 1.1 christos } 191 1.1 christos 192 1.1 christos log_debugx("\"%s\", pid %d, terminated gracefully", cur->command, pid); 193 1.1 christos 194 1.1 christos free(cur->command); 195 1.1 christos free(cur); 196 1.1 christos 197 1.1 christos return pid == -1 ? -1 : status; 198 1.1 christos } 199