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