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