1 /* $NetBSD: popenve.c,v 1.2 2025/02/11 17:48:30 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.2 2025/02/11 17:48:30 christos Exp $"); 47 #endif 48 #endif /* LIBC_SCCS and not lint */ 49 50 #include <sys/param.h> 51 #include <sys/wait.h> 52 #include <sys/socket.h> 53 54 #include <assert.h> 55 #include <errno.h> 56 #include <paths.h> 57 #include <signal.h> 58 #include <stdio.h> 59 #include <stdlib.h> 60 #include <string.h> 61 #include <unistd.h> 62 #include <fcntl.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 return cur; 116 serrno = errno; 117 (void)close(pdes[0]); 118 (void)close(pdes[1]); 119 errno = serrno; 120 return NULL; 121 } 122 123 static void 124 pdes_child(int *pdes, const char *type) 125 { 126 struct pid *old; 127 128 /* POSIX.2 B.3.2.2 "popen() shall ensure that any streams 129 from previous popen() calls that remain open in the 130 parent process are closed in the new child process. */ 131 for (old = pidlist; old; old = old->next) 132 #ifdef _REENTRANT 133 (void)close(old->fd); /* don't allow a flush */ 134 #else 135 (void)close(fileno(old->fp)); /* don't allow a flush */ 136 #endif 137 138 if (type[0] == 'r') { 139 (void)close(pdes[0]); 140 if (pdes[1] != STDOUT_FILENO) { 141 (void)dup2(pdes[1], STDOUT_FILENO); 142 (void)close(pdes[1]); 143 } 144 if (type[1] == '+') 145 (void)dup2(STDOUT_FILENO, STDIN_FILENO); 146 } else { 147 (void)close(pdes[1]); 148 if (pdes[0] != STDIN_FILENO) { 149 (void)dup2(pdes[0], STDIN_FILENO); 150 (void)close(pdes[0]); 151 } 152 } 153 } 154 155 static void 156 pdes_parent(int *pdes, struct pid *cur, pid_t pid, const char *type) 157 { 158 FILE *iop; 159 160 /* Parent; assume fdopen can't fail. */ 161 if (*type == 'r') { 162 iop = fdopen(pdes[0], type); 163 #ifdef _REENTRANT 164 cur->fd = pdes[0]; 165 #endif 166 (void)close(pdes[1]); 167 } else { 168 iop = fdopen(pdes[1], type); 169 #ifdef _REENTRANT 170 cur->fd = pdes[1]; 171 #endif 172 (void)close(pdes[0]); 173 } 174 175 /* Link into list of file descriptors. */ 176 cur->fp = iop; 177 cur->pid = pid; 178 cur->next = pidlist; 179 pidlist = cur; 180 } 181 182 static void 183 pdes_error(int *pdes, struct pid *cur) 184 { 185 free(cur); 186 (void)close(pdes[0]); 187 (void)close(pdes[1]); 188 } 189 190 FILE * 191 popenve(const char *cmd, char *const *argv, char *const *envp, const char *type) 192 { 193 struct pid *cur; 194 int pdes[2], serrno; 195 pid_t pid; 196 197 if ((cur = pdes_get(pdes, &type)) == NULL) 198 return NULL; 199 200 #ifdef _REENTRANT 201 (void)rwlock_rdlock(&pidlist_lock); 202 #endif 203 switch (pid = vfork()) { 204 case -1: /* Error. */ 205 serrno = errno; 206 #ifdef _REENTRANT 207 (void)rwlock_unlock(&pidlist_lock); 208 #endif 209 pdes_error(pdes, cur); 210 errno = serrno; 211 return NULL; 212 /* NOTREACHED */ 213 case 0: /* Child. */ 214 pdes_child(pdes, type); 215 execve(cmd, argv, envp); 216 _exit(127); 217 /* NOTREACHED */ 218 } 219 220 pdes_parent(pdes, cur, pid, type); 221 222 #ifdef _REENTRANT 223 (void)rwlock_unlock(&pidlist_lock); 224 #endif 225 226 return cur->fp; 227 } 228 229 /* 230 * pclose -- 231 * Pclose returns -1 if stream is not associated with a `popened' command, 232 * if already `pclosed', or waitpid returns an error. 233 */ 234 int 235 pcloseve(FILE *iop) 236 { 237 struct pid *cur, *last; 238 int pstat; 239 pid_t pid; 240 241 #ifdef _REENTRANT 242 rwlock_wrlock(&pidlist_lock); 243 #endif 244 245 /* Find the appropriate file pointer. */ 246 for (last = NULL, cur = pidlist; cur; last = cur, cur = cur->next) 247 if (cur->fp == iop) 248 break; 249 if (cur == NULL) { 250 #ifdef _REENTRANT 251 (void)rwlock_unlock(&pidlist_lock); 252 #endif 253 errno = ESRCH; 254 return -1; 255 } 256 257 (void)fclose(iop); 258 259 /* Remove the entry from the linked list. */ 260 if (last == NULL) 261 pidlist = cur->next; 262 else 263 last->next = cur->next; 264 265 #ifdef _REENTRANT 266 (void)rwlock_unlock(&pidlist_lock); 267 #endif 268 269 do { 270 pid = waitpid(cur->pid, &pstat, 0); 271 } while (pid == -1 && errno == EINTR); 272 273 free(cur); 274 275 return pid == -1 ? -1 : pstat; 276 } 277