1 /* $NetBSD: popen.c,v 1.37 2021/10/29 19:27:06 kre Exp $ */ 2 3 /* 4 * Copyright (c) 1988, 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * This code is derived from software written by Ken Arnold and 8 * published in UNIX Review, Vol. 6, No. 8. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 3. Neither the name of the University nor the names of its contributors 19 * may be used to endorse or promote products derived from this software 20 * without specific prior written permission. 21 * 22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32 * SUCH DAMAGE. 33 */ 34 35 #include <sys/cdefs.h> 36 #if defined(LIBC_SCCS) && !defined(lint) 37 #if 0 38 static char sccsid[] = "@(#)popen.c 8.3 (Berkeley) 5/3/95"; 39 #else 40 __RCSID("$NetBSD: popen.c,v 1.37 2021/10/29 19:27:06 kre Exp $"); 41 #endif 42 #endif /* LIBC_SCCS and not lint */ 43 44 #include "namespace.h" 45 #include <sys/param.h> 46 #include <sys/wait.h> 47 #include <sys/socket.h> 48 49 #include <assert.h> 50 #include <errno.h> 51 #include <paths.h> 52 #include <signal.h> 53 #include <stdio.h> 54 #include <stdlib.h> 55 #include <string.h> 56 #include <unistd.h> 57 #include <fcntl.h> 58 59 #include "env.h" 60 61 #ifdef __weak_alias 62 __weak_alias(popen,_popen) 63 __weak_alias(pclose,_pclose) 64 #endif 65 66 static struct pid { 67 struct pid *next; 68 FILE *fp; 69 #ifdef _REENTRANT 70 int fd; 71 #endif 72 pid_t pid; 73 } *pidlist; 74 75 #ifdef _REENTRANT 76 static mutex_t pidlist_mutex = MUTEX_INITIALIZER; 77 # define MUTEX_LOCK() \ 78 do { \ 79 if (__isthreaded) \ 80 mutex_lock(&pidlist_mutex); \ 81 } while (/*CONSTCOND*/0) 82 # define MUTEX_UNLOCK() \ 83 do { \ 84 if (__isthreaded) \ 85 mutex_unlock(&pidlist_mutex); \ 86 } while (/*CONSTCOND*/0) 87 #else 88 # define MUTEX_LOCK() __nothing 89 # define MUTEX_UNLOCK() __nothing 90 #endif 91 92 static struct pid * 93 pdes_get(int *pdes, const char **type) 94 { 95 struct pid *cur; 96 int flags = strchr(*type, 'e') ? O_CLOEXEC : 0; 97 int serrno; 98 99 if (strchr(*type, '+')) { 100 int stype = flags ? (SOCK_STREAM | SOCK_CLOEXEC) : SOCK_STREAM; 101 *type = "r+"; 102 if (socketpair(AF_LOCAL, stype, 0, pdes) < 0) 103 return NULL; 104 } else { 105 *type = strrchr(*type, 'r') ? "r" : "w"; 106 if (pipe2(pdes, flags) == -1) 107 return NULL; 108 } 109 110 if ((cur = malloc(sizeof(*cur))) != NULL) 111 return cur; 112 serrno = errno; 113 (void)close(pdes[0]); 114 (void)close(pdes[1]); 115 errno = serrno; 116 return NULL; 117 } 118 119 static void 120 pdes_child(int *pdes, const char *type) 121 { 122 struct pid *old; 123 124 /* POSIX.2 B.3.2.2 "popen() shall ensure that any streams 125 from previous popen() calls that remain open in the 126 parent process are closed in the new child process. */ 127 for (old = pidlist; old; old = old->next) 128 #ifdef _REENTRANT 129 (void)close(old->fd); /* don't allow a flush */ 130 #else 131 (void)close(fileno(old->fp)); /* don't allow a flush */ 132 #endif 133 134 if (type[0] == 'r') { 135 (void)close(pdes[0]); 136 if (pdes[1] != STDOUT_FILENO) { 137 (void)dup2(pdes[1], STDOUT_FILENO); 138 (void)close(pdes[1]); 139 } 140 if (type[1] == '+') 141 (void)dup2(STDOUT_FILENO, STDIN_FILENO); 142 } else { 143 (void)close(pdes[1]); 144 if (pdes[0] != STDIN_FILENO) { 145 (void)dup2(pdes[0], STDIN_FILENO); 146 (void)close(pdes[0]); 147 } 148 } 149 } 150 151 static void 152 pdes_parent(int *pdes, struct pid *cur, pid_t pid, const char *type) 153 { 154 FILE *iop; 155 156 /* Parent; assume fdopen can't fail. */ 157 if (*type == 'r') { 158 iop = fdopen(pdes[0], type); 159 #ifdef _REENTRANT 160 cur->fd = pdes[0]; 161 #endif 162 (void)close(pdes[1]); 163 } else { 164 iop = fdopen(pdes[1], type); 165 #ifdef _REENTRANT 166 cur->fd = pdes[1]; 167 #endif 168 (void)close(pdes[0]); 169 } 170 171 /* Link into list of file descriptors. */ 172 cur->fp = iop; 173 cur->pid = pid; 174 cur->next = pidlist; 175 pidlist = cur; 176 } 177 178 static void 179 pdes_error(int *pdes, struct pid *cur) 180 { 181 free(cur); 182 (void)close(pdes[0]); 183 (void)close(pdes[1]); 184 } 185 186 FILE * 187 popen(const char *cmd, const char *type) 188 { 189 struct pid *cur; 190 int pdes[2], serrno; 191 pid_t pid; 192 193 _DIAGASSERT(cmd != NULL); 194 _DIAGASSERT(type != NULL); 195 196 if ((cur = pdes_get(pdes, &type)) == NULL) 197 return NULL; 198 199 MUTEX_LOCK(); 200 (void)__readlockenv(); 201 switch (pid = vfork()) { 202 case -1: /* Error. */ 203 serrno = errno; 204 (void)__unlockenv(); 205 MUTEX_UNLOCK(); 206 pdes_error(pdes, cur); 207 errno = serrno; 208 return NULL; 209 /* NOTREACHED */ 210 case 0: /* Child. */ 211 pdes_child(pdes, type); 212 execl(_PATH_BSHELL, "sh", "-c", "--", cmd, NULL); 213 _exit(127); 214 /* NOTREACHED */ 215 } 216 (void)__unlockenv(); 217 218 pdes_parent(pdes, cur, pid, type); 219 220 MUTEX_UNLOCK(); 221 222 return cur->fp; 223 } 224 225 FILE * 226 popenve(const char *cmd, char *const *argv, char *const *envp, const char *type) 227 { 228 struct pid *cur; 229 int pdes[2], serrno; 230 pid_t pid; 231 232 _DIAGASSERT(cmd != NULL); 233 _DIAGASSERT(type != NULL); 234 235 if ((cur = pdes_get(pdes, &type)) == NULL) 236 return NULL; 237 238 MUTEX_LOCK(); 239 switch (pid = vfork()) { 240 case -1: /* Error. */ 241 serrno = errno; 242 MUTEX_UNLOCK(); 243 pdes_error(pdes, cur); 244 errno = serrno; 245 return NULL; 246 /* NOTREACHED */ 247 case 0: /* Child. */ 248 pdes_child(pdes, type); 249 execve(cmd, argv, envp); 250 _exit(127); 251 /* NOTREACHED */ 252 } 253 254 pdes_parent(pdes, cur, pid, type); 255 256 MUTEX_UNLOCK(); 257 258 return cur->fp; 259 } 260 261 /* 262 * pclose -- 263 * Pclose returns -1 if stream is not associated with a `popened' command, 264 * if already `pclosed', or waitpid returns an error. 265 */ 266 int 267 pclose(FILE *iop) 268 { 269 struct pid *cur, *last; 270 int pstat; 271 pid_t pid; 272 273 _DIAGASSERT(iop != NULL); 274 275 MUTEX_LOCK(); 276 277 /* Find the appropriate file pointer. */ 278 for (last = NULL, cur = pidlist; cur; last = cur, cur = cur->next) 279 if (cur->fp == iop) 280 break; 281 if (cur == NULL) { 282 MUTEX_UNLOCK(); 283 errno = ESRCH; 284 return -1; 285 } 286 287 (void)fclose(iop); 288 289 /* Remove the entry from the linked list. */ 290 if (last == NULL) 291 pidlist = cur->next; 292 else 293 last->next = cur->next; 294 295 MUTEX_UNLOCK(); 296 297 do { 298 pid = waitpid(cur->pid, &pstat, 0); 299 } while (pid == -1 && errno == EINTR); 300 301 free(cur); 302 303 return pid == -1 ? -1 : pstat; 304 } 305