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