xref: /illumos-gate/usr/src/lib/libc/port/stdio/popen.c (revision 24da5b34f49324ed742a340010ed5bd3d4e06625)
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 2006 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 "synonyms.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 	(void) thr_sigsetmask(SIG_SETMASK, &maskset, NULL);
84 	(void) thr_create(NULL, 0, reapchild, arg, THR_DAEMON, NULL);
85 }
86 
87 FILE *
88 popen(const char *cmd, const char *mode)
89 {
90 	int	p[2];
91 	pid_t	pid;
92 	int	myside;
93 	int	yourside;
94 	int	fd;
95 	const char *shpath;
96 	FILE	*iop;
97 	int	stdio;
98 	node_t	*curr;
99 	char	*argvec[4];
100 	node_t	*node;
101 	posix_spawnattr_t attr;
102 	posix_spawn_file_actions_t fact;
103 	int	error;
104 	static const char *sun_path = "/bin/sh";
105 	static const char *xpg4_path = "/usr/xpg4/bin/sh";
106 	static const char *shell = "sh";
107 	static const char *sh_flg = "-c";
108 
109 	if ((node = lmalloc(sizeof (node_t))) == NULL)
110 		return (NULL);
111 	if ((error = posix_spawnattr_init(&attr)) != 0) {
112 		lfree(node, sizeof (node_t));
113 		errno = error;
114 		return (NULL);
115 	}
116 	if ((error = posix_spawn_file_actions_init(&fact)) != 0) {
117 		lfree(node, sizeof (node_t));
118 		(void) posix_spawnattr_destroy(&attr);
119 		errno = error;
120 		return (NULL);
121 	}
122 	if (pipe(p) < 0) {
123 		error = errno;
124 		lfree(node, sizeof (node_t));
125 		(void) posix_spawnattr_destroy(&attr);
126 		(void) posix_spawn_file_actions_destroy(&fact);
127 		errno = error;
128 		return (NULL);
129 	}
130 
131 	shpath = __xpg4? xpg4_path : sun_path;
132 	if (access(shpath, X_OK))	/* XPG4 Requirement: */
133 		shpath = "";		/* force child to fail immediately */
134 
135 	myside = tst(p[WTR], p[RDR]);
136 	yourside = tst(p[RDR], p[WTR]);
137 	/* myside and yourside reverse roles in child */
138 	stdio = tst(0, 1);
139 
140 	/* This will fail more quickly if we run out of fds */
141 	if ((iop = fdopen(myside, mode)) == NULL) {
142 		error = errno;
143 		lfree(node, sizeof (node_t));
144 		(void) posix_spawnattr_destroy(&attr);
145 		(void) posix_spawn_file_actions_destroy(&fact);
146 		(void) close(yourside);
147 		(void) close(myside);
148 		errno = error;
149 		return (NULL);
150 	}
151 
152 	lmutex_lock(&popen_lock);
153 
154 	/* in the child, close all pipes from other popen's */
155 	for (curr = head; curr != NULL && error == 0; curr = curr->next) {
156 		/*
157 		 * These conditions may apply if a previous iob returned
158 		 * by popen() was closed with fclose() rather than pclose(),
159 		 * or if close(fileno(iob)) was called.
160 		 * Accommodate these programming error.
161 		 */
162 		if ((fd = curr->fd) != myside && fd != yourside &&
163 		    fcntl(fd, F_GETFD) >= 0)
164 			error = posix_spawn_file_actions_addclose(&fact, fd);
165 	}
166 	if (error == 0)
167 		error =  posix_spawn_file_actions_addclose(&fact, myside);
168 	if (yourside != stdio) {
169 		if (error == 0)
170 			error = posix_spawn_file_actions_adddup2(&fact,
171 				yourside, stdio);
172 		if (error == 0)
173 			error = posix_spawn_file_actions_addclose(&fact,
174 				yourside);
175 	}
176 	if (error == 0)
177 		error = posix_spawnattr_setflags(&attr,
178 		    POSIX_SPAWN_NOSIGCHLD_NP | POSIX_SPAWN_WAITPID_NP);
179 	if (error) {
180 		lmutex_unlock(&popen_lock);
181 		lfree(node, sizeof (node_t));
182 		(void) posix_spawnattr_destroy(&attr);
183 		(void) posix_spawn_file_actions_destroy(&fact);
184 		(void) fclose(iop);
185 		(void) close(yourside);
186 		errno = error;
187 		return (NULL);
188 	}
189 	argvec[0] = (char *)shell;
190 	argvec[1] = (char *)sh_flg;
191 	argvec[2] = (char *)cmd;
192 	argvec[3] = NULL;
193 	error = posix_spawn(&pid, shpath, &fact, &attr,
194 		(char *const *)argvec, (char *const *)environ);
195 	(void) posix_spawnattr_destroy(&attr);
196 	(void) posix_spawn_file_actions_destroy(&fact);
197 	(void) close(yourside);
198 	if (error) {
199 		lmutex_unlock(&popen_lock);
200 		lfree(node, sizeof (node_t));
201 		(void) fclose(iop);
202 		errno = error;
203 		return (NULL);
204 	}
205 	_insert_nolock(pid, myside, node);
206 
207 	lmutex_unlock(&popen_lock);
208 
209 	_SET_ORIENTATION_BYTE(iop);
210 
211 	return (iop);
212 }
213 
214 int
215 pclose(FILE *ptr)
216 {
217 	pid_t	pid;
218 	int status;
219 
220 	pid = _delete(fileno(ptr));
221 
222 	/* mark this pipe closed */
223 	(void) fclose(ptr);
224 
225 	if (pid <= 0) {
226 		errno = ECHILD;
227 		return (-1);
228 	}
229 
230 	/*
231 	 * pclose() is a cancellation point.
232 	 * Call waitpid_cancel() rather than _waitpid() to make
233 	 * sure that we actually perform the cancellation logic.
234 	 *
235 	 * If we have already been cancelled (pclose() was called from
236 	 * a cancellation cleanup handler), attempt to reap the process
237 	 * w/o waiting, and if that fails just call cleanup(pid).
238 	 */
239 
240 	if (_thrp_cancelled()) {
241 		if (waitpid(pid, &status, WNOHANG) == pid)
242 			return (status);
243 		cleanup((void *)(uintptr_t)pid);
244 		errno = ECHILD;
245 		return (-1);
246 	}
247 
248 	pthread_cleanup_push(cleanup, (void *)(uintptr_t)pid);
249 	while (waitpid_cancel(pid, &status, 0) < 0) {
250 		if (errno != EINTR) {
251 			status = -1;
252 			break;
253 		}
254 	}
255 	pthread_cleanup_pop(0);
256 
257 	return (status);
258 }
259 
260 
261 static void
262 _insert_nolock(pid_t pid, int fd, node_t *new)
263 {
264 	node_t	*prev;
265 	node_t	*curr;
266 
267 	for (prev = curr = head; curr != NULL; curr = curr->next) {
268 		/*
269 		 * curr->fd can equal fd if a previous iob returned by
270 		 * popen() was closed with fclose() rather than pclose(),
271 		 * or if close(fileno(iob)) was called.
272 		 * Accommodate this programming error.
273 		 */
274 		if (curr->fd == fd) {
275 			(void) waitpid(curr->pid, NULL, WNOHANG);
276 			curr->pid = pid;
277 			lfree(new, sizeof (node_t));
278 			return;
279 		}
280 		prev = curr;
281 	}
282 
283 	new->pid = pid;
284 	new->fd = fd;
285 	new->next = NULL;
286 
287 	if (head == NULL)
288 		head = new;
289 	else
290 		prev->next = new;
291 }
292 
293 /*
294  * _insert() and _delete() are used by p2open() in libgen.
295  */
296 int
297 _insert(pid_t pid, int fd)
298 {
299 	node_t *node;
300 
301 	if ((node = lmalloc(sizeof (node_t))) == NULL)
302 		return (-1);
303 
304 	lmutex_lock(&popen_lock);
305 	_insert_nolock(pid, fd, node);
306 	lmutex_unlock(&popen_lock);
307 
308 	return (0);
309 }
310 
311 
312 pid_t
313 _delete(int fd)
314 {
315 	node_t	*prev;
316 	node_t	*curr;
317 	pid_t	pid;
318 
319 	lmutex_lock(&popen_lock);
320 
321 	for (prev = curr = head; curr != NULL; curr = curr->next) {
322 		if (curr->fd == fd) {
323 			if (curr == head)
324 				head = curr->next;
325 			else
326 				prev->next = curr->next;
327 			lmutex_unlock(&popen_lock);
328 			pid = curr->pid;
329 			lfree(curr, sizeof (node_t));
330 			return (pid);
331 		}
332 		prev = curr;
333 	}
334 
335 	lmutex_unlock(&popen_lock);
336 
337 	return (-1);
338 }
339