xref: /illumos-gate/usr/src/lib/libc/port/stdio/popen.c (revision 6e375c8351497b82ffa4f33cbf61d712999b4605)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*	Copyright (c) 1988 AT&T	*/
28 /*	  All Rights Reserved  	*/
29 
30 #pragma weak _pclose = pclose
31 #pragma weak _popen = popen
32 
33 #include "lint.h"
34 #include "mtlib.h"
35 #include "file64.h"
36 #include <sys/types.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <wait.h>
40 #include <signal.h>
41 #include <fcntl.h>
42 #include <unistd.h>
43 #include <errno.h>
44 #include <thread.h>
45 #include <pthread.h>
46 #include <synch.h>
47 #include <spawn.h>
48 #include "stdiom.h"
49 #include "mse.h"
50 #include "libc.h"
51 
52 #define	tst(a, b) (*mode == 'r'? (b) : (a))
53 #define	RDR	0
54 #define	WTR	1
55 
56 extern	int __xpg4;	/* defined in _xpg4.c; 0 if not xpg4-compiled program */
57 extern const char **_environ;
58 
59 static mutex_t popen_lock = DEFAULTMUTEX;
60 
61 typedef struct node {
62 	pid_t	pid;
63 	int	fd;
64 	struct	node	*next;
65 } node_t;
66 
67 static	node_t  *head = NULL;
68 static	void	_insert_nolock(pid_t, int, node_t *);
69 
70 /*
71  * Cancellation cleanup handler.
72  * If we were cancelled in waitpid(), create a daemon thread to
73  * reap our abandoned child.  No other thread can do this for us.
74  */
75 static void
76 cleanup(void *arg)
77 {
78 	extern const sigset_t maskset;
79 	extern void *reapchild(void *);		/* see port/stdio/system.c */
80 
81 	/*
82 	 * We have been cancelled.  There is no need to restore
83 	 * the original sigmask after blocking all signals because
84 	 * pthread_exit() will block all signals while we exit.
85 	 */
86 	(void) thr_sigsetmask(SIG_SETMASK, &maskset, NULL);
87 	(void) thr_create(NULL, 0, reapchild, arg, THR_DAEMON, NULL);
88 }
89 
90 FILE *
91 popen(const char *cmd, const char *mode)
92 {
93 	int	p[2];
94 	pid_t	pid;
95 	int	myside;
96 	int	yourside;
97 	int	fd;
98 	const char *shpath;
99 	FILE	*iop;
100 	int	stdio;
101 	node_t	*curr;
102 	char	*argvec[4];
103 	node_t	*node;
104 	posix_spawnattr_t attr;
105 	posix_spawn_file_actions_t fact;
106 	int	error;
107 	static const char *sun_path = "/bin/sh";
108 	static const char *xpg4_path = "/usr/xpg4/bin/sh";
109 	static const char *shell = "sh";
110 	static const char *sh_flg = "-c";
111 
112 	if ((node = lmalloc(sizeof (node_t))) == NULL)
113 		return (NULL);
114 	if ((error = posix_spawnattr_init(&attr)) != 0) {
115 		lfree(node, sizeof (node_t));
116 		errno = error;
117 		return (NULL);
118 	}
119 	if ((error = posix_spawn_file_actions_init(&fact)) != 0) {
120 		lfree(node, sizeof (node_t));
121 		(void) posix_spawnattr_destroy(&attr);
122 		errno = error;
123 		return (NULL);
124 	}
125 	if (pipe(p) < 0) {
126 		error = errno;
127 		lfree(node, sizeof (node_t));
128 		(void) posix_spawnattr_destroy(&attr);
129 		(void) posix_spawn_file_actions_destroy(&fact);
130 		errno = error;
131 		return (NULL);
132 	}
133 
134 	shpath = __xpg4? xpg4_path : sun_path;
135 	if (access(shpath, X_OK))	/* XPG4 Requirement: */
136 		shpath = "";		/* force child to fail immediately */
137 
138 	myside = tst(p[WTR], p[RDR]);
139 	yourside = tst(p[RDR], p[WTR]);
140 	/* myside and yourside reverse roles in child */
141 	stdio = tst(0, 1);
142 
143 	/* This will fail more quickly if we run out of fds */
144 	if ((iop = fdopen(myside, mode)) == NULL) {
145 		error = errno;
146 		lfree(node, sizeof (node_t));
147 		(void) posix_spawnattr_destroy(&attr);
148 		(void) posix_spawn_file_actions_destroy(&fact);
149 		(void) close(yourside);
150 		(void) close(myside);
151 		errno = error;
152 		return (NULL);
153 	}
154 
155 	lmutex_lock(&popen_lock);
156 
157 	/* in the child, close all pipes from other popen's */
158 	for (curr = head; curr != NULL && error == 0; curr = curr->next) {
159 		/*
160 		 * These conditions may apply if a previous iob returned
161 		 * by popen() was closed with fclose() rather than pclose(),
162 		 * or if close(fileno(iob)) was called.  Don't let these
163 		 * programming errors cause us to malfunction here.
164 		 */
165 		if ((fd = curr->fd) != myside && fd != yourside &&
166 		    fcntl(fd, F_GETFD) >= 0)
167 			error = posix_spawn_file_actions_addclose(&fact, fd);
168 	}
169 	if (error == 0)
170 		error =  posix_spawn_file_actions_addclose(&fact, myside);
171 	if (yourside != stdio) {
172 		if (error == 0)
173 			error = posix_spawn_file_actions_adddup2(&fact,
174 			    yourside, stdio);
175 		if (error == 0)
176 			error = posix_spawn_file_actions_addclose(&fact,
177 			    yourside);
178 	}
179 	/*
180 	 * See the comments in port/stdio/system.c for why these
181 	 * non-portable posix_spawn() attributes are being used.
182 	 */
183 	if (error == 0)
184 		error = posix_spawnattr_setflags(&attr,
185 		    POSIX_SPAWN_NOSIGCHLD_NP |
186 		    POSIX_SPAWN_WAITPID_NP |
187 		    POSIX_SPAWN_NOEXECERR_NP);
188 	if (error) {
189 		lmutex_unlock(&popen_lock);
190 		lfree(node, sizeof (node_t));
191 		(void) posix_spawnattr_destroy(&attr);
192 		(void) posix_spawn_file_actions_destroy(&fact);
193 		(void) fclose(iop);
194 		(void) close(yourside);
195 		errno = error;
196 		return (NULL);
197 	}
198 	argvec[0] = (char *)shell;
199 	argvec[1] = (char *)sh_flg;
200 	argvec[2] = (char *)cmd;
201 	argvec[3] = NULL;
202 	error = posix_spawn(&pid, shpath, &fact, &attr,
203 	    (char *const *)argvec, (char *const *)_environ);
204 	(void) posix_spawnattr_destroy(&attr);
205 	(void) posix_spawn_file_actions_destroy(&fact);
206 	(void) close(yourside);
207 	if (error) {
208 		lmutex_unlock(&popen_lock);
209 		lfree(node, sizeof (node_t));
210 		(void) fclose(iop);
211 		errno = error;
212 		return (NULL);
213 	}
214 	_insert_nolock(pid, myside, node);
215 
216 	lmutex_unlock(&popen_lock);
217 
218 	_SET_ORIENTATION_BYTE(iop);
219 
220 	return (iop);
221 }
222 
223 /*
224  * pclose() is a cancellation point.
225  */
226 int
227 pclose(FILE *ptr)
228 {
229 	pid_t	pid;
230 	int status;
231 
232 	pid = _delete(fileno(ptr));
233 
234 	/* mark this pipe closed */
235 	(void) fclose(ptr);
236 
237 	if (pid <= 0) {
238 		errno = ECHILD;
239 		return (-1);
240 	}
241 
242 	/*
243 	 * waitpid() is a cancellation point.
244 	 * This causes pclose() to be a cancellation point.
245 	 *
246 	 * If we have already been cancelled (pclose() was called from
247 	 * a cancellation cleanup handler), attempt to reap the process
248 	 * w/o waiting, and if that fails just call cleanup(pid).
249 	 */
250 
251 	if (_thrp_cancelled()) {
252 		/* waitpid(..., WNOHANG) is not a cancellation point */
253 		if (waitpid(pid, &status, WNOHANG) == pid)
254 			return (status);
255 		cleanup((void *)(uintptr_t)pid);
256 		errno = ECHILD;
257 		return (-1);
258 	}
259 
260 	pthread_cleanup_push(cleanup, (void *)(uintptr_t)pid);
261 	while (waitpid(pid, &status, 0) < 0) {
262 		if (errno != EINTR) {
263 			status = -1;
264 			break;
265 		}
266 	}
267 	pthread_cleanup_pop(0);
268 
269 	return (status);
270 }
271 
272 
273 static void
274 _insert_nolock(pid_t pid, int fd, node_t *new)
275 {
276 	node_t	*prev;
277 	node_t	*curr;
278 
279 	for (prev = curr = head; curr != NULL; curr = curr->next) {
280 		/*
281 		 * curr->fd can equal fd if a previous iob returned by
282 		 * popen() was closed with fclose() rather than pclose(),
283 		 * or if close(fileno(iob)) was called.  Don't let these
284 		 * programming errors cause us to malfunction here.
285 		 */
286 		if (curr->fd == fd) {
287 			/* make a lame attempt to reap the forgotten child */
288 			(void) waitpid(curr->pid, NULL, WNOHANG);
289 			curr->pid = pid;
290 			lfree(new, sizeof (node_t));
291 			return;
292 		}
293 		prev = curr;
294 	}
295 
296 	new->pid = pid;
297 	new->fd = fd;
298 	new->next = NULL;
299 
300 	if (head == NULL)
301 		head = new;
302 	else
303 		prev->next = new;
304 }
305 
306 /*
307  * _insert() and _delete() are used by p2open() in libgen.
308  */
309 int
310 _insert(pid_t pid, int fd)
311 {
312 	node_t *node;
313 
314 	if ((node = lmalloc(sizeof (node_t))) == NULL)
315 		return (-1);
316 
317 	lmutex_lock(&popen_lock);
318 	_insert_nolock(pid, fd, node);
319 	lmutex_unlock(&popen_lock);
320 
321 	return (0);
322 }
323 
324 
325 pid_t
326 _delete(int fd)
327 {
328 	node_t	*prev;
329 	node_t	*curr;
330 	pid_t	pid;
331 
332 	lmutex_lock(&popen_lock);
333 
334 	for (prev = curr = head; curr != NULL; curr = curr->next) {
335 		if (curr->fd == fd) {
336 			if (curr == head)
337 				head = curr->next;
338 			else
339 				prev->next = curr->next;
340 			lmutex_unlock(&popen_lock);
341 			pid = curr->pid;
342 			lfree(curr, sizeof (node_t));
343 			return (pid);
344 		}
345 		prev = curr;
346 	}
347 
348 	lmutex_unlock(&popen_lock);
349 
350 	return (-1);
351 }
352