1 /* $NetBSD: popenve.c,v 1.3 2026/02/07 14:26:07 christos 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 #ifdef HAVE_CONFIG_H 36 #include "config.h" 37 #endif 38 39 #ifdef HAVE_SYS_CDEFS_H 40 #include <sys/cdefs.h> 41 #endif 42 #if defined(LIBC_SCCS) && !defined(lint) 43 #if 0 44 static char sccsid[] = "@(#)popen.c 8.3 (Berkeley) 5/3/95"; 45 #else 46 __RCSID("$NetBSD: popenve.c,v 1.3 2026/02/07 14:26:07 christos Exp $"); 47 #endif 48 #endif /* LIBC_SCCS and not lint */ 49 50 #include <sys/types.h> 51 #include <sys/socket.h> 52 #include <sys/wait.h> 53 54 #include <assert.h> 55 #include <errno.h> 56 #include <fcntl.h> 57 #include <paths.h> 58 #include <signal.h> 59 #include <stdio.h> 60 #include <stdlib.h> 61 #include <string.h> 62 #include <unistd.h> 63 64 #ifdef __weak_alias 65 __weak_alias(popen,_popen) 66 __weak_alias(pclose,_pclose) 67 #endif 68 69 static struct pid { 70 struct pid *next; 71 FILE *fp; 72 #ifdef _REENTRANT 73 int fd; 74 #endif 75 pid_t pid; 76 } *pidlist; 77 78 #ifdef _REENTRANT 79 static rwlock_t pidlist_lock = RWLOCK_INITIALIZER; 80 #endif 81 82 static struct pid * 83 pdes_get(int *pdes, const char **type) 84 { 85 struct pid *cur; 86 int flags = strchr(*type, 'e') ? O_CLOEXEC : 0; 87 int serrno; 88 89 if (strchr(*type, '+')) { 90 #ifndef SOCK_CLOEXEC 91 #define SOCK_CLOEXEC 0 92 #endif 93 int stype = flags ? (SOCK_STREAM | SOCK_CLOEXEC) : SOCK_STREAM; 94 *type = "r+"; 95 if (socketpair(AF_LOCAL, stype, 0, pdes) < 0) 96 return NULL; 97 #if SOCK_CLOEXEC == 0 98 fcntl(pdes[0], F_SETFD, FD_CLOEXEC); 99 fcntl(pdes[1], F_SETFD, FD_CLOEXEC); 100 #endif 101 } else { 102 *type = strrchr(*type, 'r') ? "r" : "w"; 103 #if SOCK_CLOEXEC != 0 104 if (pipe2(pdes, flags) == -1) 105 return NULL; 106 #else 107 if (pipe(pdes) == -1) 108 return NULL; 109 fcntl(pdes[0], F_SETFL, fcntl(pdes[0], F_GETFL) | flags); 110 fcntl(pdes[1], F_SETFL, fcntl(pdes[1], F_GETFL) | flags); 111 #endif 112 } 113 114 if ((cur = malloc(sizeof(*cur))) != NULL) { 115 if (**type == 'r') { 116 cur->fp = fdopen(pdes[0], *type); 117 #ifdef _REENTRANT 118 cur->fd = pdes[0]; 119 #endif 120 } else { 121 cur->fp = fdopen(pdes[1], *type); 122 #ifdef _REENTRANT 123 cur->fd = pdes[1]; 124 #endif 125 } 126 if (cur->fp != NULL) 127 return cur; 128 } 129 serrno = errno; 130 (void)close(pdes[0]); 131 (void)close(pdes[1]); 132 free(cur); 133 errno = serrno; 134 return NULL; 135 } 136 137 static void 138 pdes_child(int *pdes, const char *type) 139 { 140 struct pid *old; 141 142 if (type[0] == 'r') { 143 (void)close(pdes[0]); 144 if (pdes[1] != STDOUT_FILENO) { 145 (void)dup2(pdes[1], STDOUT_FILENO); 146 (void)close(pdes[1]); 147 } 148 if (type[1] == '+') 149 (void)dup2(STDOUT_FILENO, STDIN_FILENO); 150 } else { 151 (void)close(pdes[1]); 152 if (pdes[0] != STDIN_FILENO) { 153 (void)dup2(pdes[0], STDIN_FILENO); 154 (void)close(pdes[0]); 155 } 156 } 157 158 /* POSIX.2 B.3.2.2 "popen() shall ensure that any streams 159 from previous popen() calls that remain open in the 160 parent process are closed in the new child process. */ 161 for (old = pidlist; old; old = old->next) { 162 #ifdef _REENTRANT 163 (void)close(old->fd); /* don't allow a flush */ 164 #else 165 (void)close(fileno(old->fp)); /* don't allow a flush */ 166 #endif 167 } 168 } 169 170 static void 171 pdes_parent(int *pdes, struct pid *cur, pid_t pid, const char *type) 172 { 173 /* Parent */ 174 if (*type == 'r') 175 (void)close(pdes[1]); 176 else 177 (void)close(pdes[0]); 178 179 /* Link into list of file descriptors. */ 180 cur->pid = pid; 181 cur->next = pidlist; 182 pidlist = cur; 183 } 184 185 static void 186 pdes_error(int *pdes, struct pid *cur) 187 { 188 free(cur); 189 (void)close(pdes[0]); 190 (void)close(pdes[1]); 191 } 192 193 FILE * 194 popenve(const char *cmd, char *const *argv, char *const *envp, const char *type) 195 { 196 struct pid *cur; 197 int pdes[2], serrno; 198 pid_t pid; 199 200 if ((cur = pdes_get(pdes, &type)) == NULL) 201 return NULL; 202 203 #ifdef _REENTRANT 204 (void)rwlock_rdlock(&pidlist_lock); 205 #endif 206 switch (pid = fork()) { 207 case -1: /* Error. */ 208 serrno = errno; 209 #ifdef _REENTRANT 210 (void)rwlock_unlock(&pidlist_lock); 211 #endif 212 pdes_error(pdes, cur); 213 errno = serrno; 214 return NULL; 215 /* NOTREACHED */ 216 case 0: /* Child. */ 217 pdes_child(pdes, type); 218 execve(cmd, argv, envp); 219 _exit(127); 220 /* NOTREACHED */ 221 } 222 223 pdes_parent(pdes, cur, pid, type); 224 225 #ifdef _REENTRANT 226 (void)rwlock_unlock(&pidlist_lock); 227 #endif 228 229 return cur->fp; 230 } 231 232 /* 233 * pclose -- 234 * Pclose returns -1 if stream is not associated with a `popened' command, 235 * if already `pclosed', or waitpid returns an error. 236 */ 237 int 238 pcloseve(FILE *iop) 239 { 240 struct pid *cur, *last; 241 int pstat; 242 pid_t pid; 243 244 #ifdef _REENTRANT 245 rwlock_wrlock(&pidlist_lock); 246 #endif 247 248 /* Find the appropriate file pointer. */ 249 for (last = NULL, cur = pidlist; cur; last = cur, cur = cur->next) 250 if (cur->fp == iop) 251 break; 252 if (cur == NULL) { 253 #ifdef _REENTRANT 254 (void)rwlock_unlock(&pidlist_lock); 255 #endif 256 errno = ESRCH; 257 return -1; 258 } 259 260 (void)fclose(iop); 261 262 /* Remove the entry from the linked list. */ 263 if (last == NULL) 264 pidlist = cur->next; 265 else 266 last->next = cur->next; 267 268 #ifdef _REENTRANT 269 (void)rwlock_unlock(&pidlist_lock); 270 #endif 271 272 do { 273 pid = waitpid(cur->pid, &pstat, 0); 274 } while (pid == -1 && errno == EINTR); 275 276 free(cur); 277 278 return pid == -1 ? -1 : pstat; 279 } 280