1/* 2 * Copyright (c) 1988, 1993 3 * The Regents of the University of California. All rights reserved. 4 * 5 * This code is derived from software written by Ken Arnold and 6 * published in UNIX Review, Vol. 6, No. 8. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 4. Neither the name of the University nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 * SUCH DAMAGE. 31 */ 32 33#ifdef VARIANT_DARWINEXTSN 34#define _DARWIN_UNLIMITED_STREAMS 35#endif /* VARIANT_DARWINEXTSN */ 36 37#if defined(LIBC_SCCS) && !defined(lint) 38static char sccsid[] = "@(#)popen.c 8.3 (Berkeley) 5/3/95"; 39#endif /* LIBC_SCCS and not lint */ 40#include <sys/cdefs.h> 41__FBSDID("$FreeBSD: src/lib/libc/gen/popen.c,v 1.21 2009/05/27 19:28:04 ed Exp $"); 42 43#include "namespace.h" 44#include <sys/param.h> 45#include <sys/queue.h> 46#include <sys/wait.h> 47#include <sys/socket.h> 48#include <wchar.h> /* fwide() */ 49#include <signal.h> 50#include <errno.h> 51#include <unistd.h> 52#include <stdio.h> 53#include <stdlib.h> 54#include <string.h> 55#include <paths.h> 56#include <pthread.h> 57#include <spawn.h> 58#include "un-namespace.h" 59#include "libc_private.h" 60 61#include <crt_externs.h> 62#define environ (*_NSGetEnviron()) 63 64/* Our queue.h doesn't have SLIST_REMOVE_AFTER in it yet 65 * <rdar://problem/7431558> API: Add SLIST_REMOVE_AFTER to sys/queue.h (from FreeBSD) 66 */ 67#ifndef SLIST_REMOVE_AFTER 68#define SLIST_REMOVE_AFTER(elm, field) do { \ 69 SLIST_NEXT(elm, field) = \ 70 SLIST_NEXT(SLIST_NEXT(elm, field), field); \ 71} while (0) 72#endif 73 74/* 3516149 - store file descriptor and use that to close to prevent blocking */ 75struct pid { 76 SLIST_ENTRY(pid) next; 77 FILE *fp; 78 int fd; 79 pid_t pid; 80}; 81#define pidlist __popen_pidlist 82#define pidlist_mutex __popen_pidlist_mutex 83#ifndef BUILDING_VARIANT 84__private_extern__ SLIST_HEAD(, pid) pidlist = SLIST_HEAD_INITIALIZER(pidlist); 85__private_extern__ pthread_mutex_t pidlist_mutex = PTHREAD_MUTEX_INITIALIZER; 86#else /* BUILDING_VARIANT */ 87extern SLIST_HEAD(, pid) pidlist; 88extern pthread_mutex_t pidlist_mutex; 89#endif /* !BUILDING_VARIANT */ 90 91#define THREAD_LOCK() if (__isthreaded) _pthread_mutex_lock(&pidlist_mutex) 92#define THREAD_UNLOCK() if (__isthreaded) _pthread_mutex_unlock(&pidlist_mutex) 93 94FILE * 95popen(command, type) 96 const char *command, *type; 97{ 98 struct pid *cur; 99 FILE *iop; 100 int pdes[2], pid, twoway, other; 101 char *argv[4]; 102 struct pid *p; 103 posix_spawn_file_actions_t file_actions; 104 int err; 105 106 if (type == NULL) { 107 errno = EINVAL; 108 return (NULL); 109 } 110 if (strcmp(type, "r+") == 0) { 111 twoway = 1; 112 type = "r+"; 113 if (socketpair(AF_UNIX, SOCK_STREAM, 0, pdes) < 0) 114 return (NULL); 115 } else { 116 twoway = 0; 117 if ((*type != 'r' && *type != 'w') || type[1]) { 118 errno = EINVAL; 119 return (NULL); 120 } 121 if (pipe(pdes) < 0) 122 return (NULL); 123 } 124 125 /* fdopen can now fail */ 126 if (*type == 'r') { 127 iop = fdopen(pdes[0], type); 128 other = pdes[1]; 129 } else { 130 iop = fdopen(pdes[1], type); 131 other = pdes[0]; 132 } 133 if (iop == NULL) { 134 (void)_close(pdes[0]); 135 (void)_close(pdes[1]); 136 return (NULL); 137 } 138 139 if ((cur = malloc(sizeof(struct pid))) == NULL) { 140 (void)fclose(iop); 141 (void)_close(other); 142 return (NULL); 143 } 144 145 if ((err = posix_spawn_file_actions_init(&file_actions)) != 0) { 146 (void)fclose(iop); 147 (void)_close(other); 148 free(cur); 149 errno = err; 150 return (NULL); 151 } 152 if (*type == 'r') { 153 /* 154 * The dup2() to STDIN_FILENO is repeated to avoid 155 * writing to pdes[1], which might corrupt the 156 * parent's copy. This isn't good enough in 157 * general, since the _exit() is no return, so 158 * the compiler is free to corrupt all the local 159 * variables. 160 */ 161 (void)posix_spawn_file_actions_addclose(&file_actions, pdes[0]); 162 if (pdes[1] != STDOUT_FILENO) { 163 (void)posix_spawn_file_actions_adddup2(&file_actions, pdes[1], STDOUT_FILENO); 164 (void)posix_spawn_file_actions_addclose(&file_actions, pdes[1]); 165 if (twoway) 166 (void)posix_spawn_file_actions_adddup2(&file_actions, STDOUT_FILENO, STDIN_FILENO); 167 } else if (twoway && (pdes[1] != STDIN_FILENO)) 168 (void)posix_spawn_file_actions_adddup2(&file_actions, pdes[1], STDIN_FILENO); 169 } else { 170 if (pdes[0] != STDIN_FILENO) { 171 (void)posix_spawn_file_actions_adddup2(&file_actions, pdes[0], STDIN_FILENO); 172 (void)posix_spawn_file_actions_addclose(&file_actions, pdes[0]); 173 } 174 (void)posix_spawn_file_actions_addclose(&file_actions, pdes[1]); 175 } 176 SLIST_FOREACH(p, &pidlist, next) 177 (void)posix_spawn_file_actions_addclose(&file_actions, p->fd); 178 179 argv[0] = "sh"; 180 argv[1] = "-c"; 181 argv[2] = (char *)command; 182 argv[3] = NULL; 183 184 err = posix_spawn(&pid, _PATH_BSHELL, &file_actions, NULL, argv, environ); 185 posix_spawn_file_actions_destroy(&file_actions); 186 187 if (err == ENOMEM || err == EAGAIN) { /* as if fork failed */ 188 (void)fclose(iop); 189 (void)_close(other); 190 free(cur); 191 errno = err; 192 return (NULL); 193 } else if (err != 0) { /* couldn't exec the shell */ 194 pid = -1; 195 } 196 197 if (*type == 'r') { 198 cur->fd = pdes[0]; 199 (void)_close(pdes[1]); 200 } else { 201 cur->fd = pdes[1]; 202 (void)_close(pdes[0]); 203 } 204 205 /* Link into list of file descriptors. */ 206 cur->fp = iop; 207 cur->pid = pid; 208 THREAD_LOCK(); 209 SLIST_INSERT_HEAD(&pidlist, cur, next); 210 THREAD_UNLOCK(); 211 fwide(iop, -1); /* byte stream */ 212 return (iop); 213} 214 215#ifndef BUILDING_VARIANT 216/* 217 * pclose -- 218 * Pclose returns -1 if stream is not associated with a `popened' command, 219 * if already `pclosed', or waitpid returns an error. 220 */ 221int 222pclose(iop) 223 FILE *iop; 224{ 225 struct pid *cur, *last = NULL; 226 int pstat; 227 pid_t pid; 228 229 /* 230 * Find the appropriate file pointer and remove it from the list. 231 */ 232 THREAD_LOCK(); 233 SLIST_FOREACH(cur, &pidlist, next) { 234 if (cur->fp == iop) 235 break; 236 last = cur; 237 } 238 if (cur == NULL) { 239 THREAD_UNLOCK(); 240 return (-1); 241 } 242 if (last == NULL) 243 SLIST_REMOVE_HEAD(&pidlist, next); 244 else 245 SLIST_REMOVE_AFTER(last, next); 246 THREAD_UNLOCK(); 247 248 (void)fclose(iop); 249 250 if (cur->pid < 0) { 251 free(cur); 252 return W_EXITCODE(127, 0); 253 } 254 do { 255 pid = _wait4(cur->pid, &pstat, 0, (struct rusage *)0); 256 } while (pid == -1 && errno == EINTR); 257 258 free(cur); 259 260 return (pid == -1 ? -1 : pstat); 261} 262#endif /* !BUILDING_VARIANT */ 263