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