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