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