xref: /illumos-gate/usr/src/lib/libc/port/stdio/popen.c (revision 462453d2d0c563559a4caf186db76954e563bd1a)
17c478bd9Sstevel@tonic-gate /*
27c478bd9Sstevel@tonic-gate  * CDDL HEADER START
37c478bd9Sstevel@tonic-gate  *
47c478bd9Sstevel@tonic-gate  * The contents of this file are subject to the terms of the
5a5f69788Scraigm  * Common Development and Distribution License (the "License").
6a5f69788Scraigm  * You may not use this file except in compliance with the License.
77c478bd9Sstevel@tonic-gate  *
87c478bd9Sstevel@tonic-gate  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
97c478bd9Sstevel@tonic-gate  * or http://www.opensolaris.org/os/licensing.
107c478bd9Sstevel@tonic-gate  * See the License for the specific language governing permissions
117c478bd9Sstevel@tonic-gate  * and limitations under the License.
127c478bd9Sstevel@tonic-gate  *
137c478bd9Sstevel@tonic-gate  * When distributing Covered Code, include this CDDL HEADER in each
147c478bd9Sstevel@tonic-gate  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
157c478bd9Sstevel@tonic-gate  * If applicable, add the following below this CDDL HEADER, with the
167c478bd9Sstevel@tonic-gate  * fields enclosed by brackets "[]" replaced with your own identifying
177c478bd9Sstevel@tonic-gate  * information: Portions Copyright [yyyy] [name of copyright owner]
187c478bd9Sstevel@tonic-gate  *
197c478bd9Sstevel@tonic-gate  * CDDL HEADER END
207c478bd9Sstevel@tonic-gate  */
21a5f69788Scraigm 
227c478bd9Sstevel@tonic-gate /*
23a574db85Sraf  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
247c478bd9Sstevel@tonic-gate  * Use is subject to license terms.
257c478bd9Sstevel@tonic-gate  */
267c478bd9Sstevel@tonic-gate 
27*462453d2SMatthew Ahrens /*
28*462453d2SMatthew Ahrens  * Copyright (c) 2011 by Delphix. All rights reserved.
29*462453d2SMatthew Ahrens  */
30*462453d2SMatthew Ahrens 
317c478bd9Sstevel@tonic-gate /*	Copyright (c) 1988 AT&T	*/
327c478bd9Sstevel@tonic-gate /*	  All Rights Reserved  	*/
337c478bd9Sstevel@tonic-gate 
347257d1b4Sraf #pragma weak _pclose = pclose
357257d1b4Sraf #pragma weak _popen = popen
367c478bd9Sstevel@tonic-gate 
377257d1b4Sraf #include "lint.h"
387c478bd9Sstevel@tonic-gate #include "mtlib.h"
397c478bd9Sstevel@tonic-gate #include "file64.h"
407c478bd9Sstevel@tonic-gate #include <sys/types.h>
417c478bd9Sstevel@tonic-gate #include <stdio.h>
427c478bd9Sstevel@tonic-gate #include <stdlib.h>
437c478bd9Sstevel@tonic-gate #include <wait.h>
447c478bd9Sstevel@tonic-gate #include <signal.h>
457c478bd9Sstevel@tonic-gate #include <fcntl.h>
467c478bd9Sstevel@tonic-gate #include <unistd.h>
477c478bd9Sstevel@tonic-gate #include <errno.h>
487c478bd9Sstevel@tonic-gate #include <thread.h>
49657b1f3dSraf #include <pthread.h>
507c478bd9Sstevel@tonic-gate #include <synch.h>
517c478bd9Sstevel@tonic-gate #include <spawn.h>
526a5408e6SRichard Lowe #include <paths.h>
537c478bd9Sstevel@tonic-gate #include "stdiom.h"
547c478bd9Sstevel@tonic-gate #include "mse.h"
557c478bd9Sstevel@tonic-gate #include "libc.h"
567c478bd9Sstevel@tonic-gate 
577c478bd9Sstevel@tonic-gate static mutex_t popen_lock = DEFAULTMUTEX;
587c478bd9Sstevel@tonic-gate 
597c478bd9Sstevel@tonic-gate typedef struct node {
607c478bd9Sstevel@tonic-gate 	pid_t	pid;
617c478bd9Sstevel@tonic-gate 	int	fd;
627c478bd9Sstevel@tonic-gate 	struct	node	*next;
637c478bd9Sstevel@tonic-gate } node_t;
647c478bd9Sstevel@tonic-gate 
657c478bd9Sstevel@tonic-gate static	node_t  *head = NULL;
66657b1f3dSraf static	void	_insert_nolock(pid_t, int, node_t *);
677c478bd9Sstevel@tonic-gate 
68657b1f3dSraf /*
69657b1f3dSraf  * Cancellation cleanup handler.
70657b1f3dSraf  * If we were cancelled in waitpid(), create a daemon thread to
71657b1f3dSraf  * reap our abandoned child.  No other thread can do this for us.
72657b1f3dSraf  */
73657b1f3dSraf static void
cleanup(void * arg)74657b1f3dSraf cleanup(void *arg)
75657b1f3dSraf {
76657b1f3dSraf 	extern const sigset_t maskset;
77657b1f3dSraf 	extern void *reapchild(void *);		/* see port/stdio/system.c */
78657b1f3dSraf 
79a574db85Sraf 	/*
80a574db85Sraf 	 * We have been cancelled.  There is no need to restore
81a574db85Sraf 	 * the original sigmask after blocking all signals because
82a574db85Sraf 	 * pthread_exit() will block all signals while we exit.
83a574db85Sraf 	 */
84657b1f3dSraf 	(void) thr_sigsetmask(SIG_SETMASK, &maskset, NULL);
85657b1f3dSraf 	(void) thr_create(NULL, 0, reapchild, arg, THR_DAEMON, NULL);
86657b1f3dSraf }
877c478bd9Sstevel@tonic-gate 
887c478bd9Sstevel@tonic-gate FILE *
popen(const char * cmd,const char * mode)897c478bd9Sstevel@tonic-gate popen(const char *cmd, const char *mode)
907c478bd9Sstevel@tonic-gate {
917c478bd9Sstevel@tonic-gate 	pid_t	pid;
92*462453d2SMatthew Ahrens 	int	myfd, fd;
936a5408e6SRichard Lowe 	const char *shpath = _PATH_BSHELL;
947c478bd9Sstevel@tonic-gate 	FILE	*iop;
957c478bd9Sstevel@tonic-gate 	node_t	*curr;
96657b1f3dSraf 	node_t	*node;
977c478bd9Sstevel@tonic-gate 	posix_spawn_file_actions_t fact;
98*462453d2SMatthew Ahrens 	posix_spawnattr_t attr;
997c478bd9Sstevel@tonic-gate 	int	error;
1007c478bd9Sstevel@tonic-gate 
101657b1f3dSraf 	if ((node = lmalloc(sizeof (node_t))) == NULL)
1027c478bd9Sstevel@tonic-gate 		return (NULL);
103657b1f3dSraf 	if ((error = posix_spawnattr_init(&attr)) != 0) {
104657b1f3dSraf 		lfree(node, sizeof (node_t));
105657b1f3dSraf 		errno = error;
106657b1f3dSraf 		return (NULL);
107657b1f3dSraf 	}
108657b1f3dSraf 	if ((error = posix_spawn_file_actions_init(&fact)) != 0) {
109657b1f3dSraf 		lfree(node, sizeof (node_t));
110657b1f3dSraf 		(void) posix_spawnattr_destroy(&attr);
111657b1f3dSraf 		errno = error;
112657b1f3dSraf 		return (NULL);
113657b1f3dSraf 	}
114*462453d2SMatthew Ahrens 
115*462453d2SMatthew Ahrens 	if (access(shpath, X_OK))	/* XPG4 Requirement: */
116*462453d2SMatthew Ahrens 		shpath = "";		/* force child to fail immediately */
117*462453d2SMatthew Ahrens 
118*462453d2SMatthew Ahrens 
119*462453d2SMatthew Ahrens 	/*
120*462453d2SMatthew Ahrens 	 * fdopen() can fail (if the fd is too high or we are out of memory),
121*462453d2SMatthew Ahrens 	 * but we don't want to have any way to fail after creating the child
122*462453d2SMatthew Ahrens 	 * process.  So we fdopen() a dummy fd (myfd), and once we get the real
123*462453d2SMatthew Ahrens 	 * fd from posix_spawn_pipe_np(), we dup2() the real fd onto the dummy.
124*462453d2SMatthew Ahrens 	 */
125*462453d2SMatthew Ahrens 	myfd = open("/dev/null", O_RDWR);
126*462453d2SMatthew Ahrens 	if (myfd == -1) {
127657b1f3dSraf 		error = errno;
128657b1f3dSraf 		lfree(node, sizeof (node_t));
129657b1f3dSraf 		(void) posix_spawnattr_destroy(&attr);
130657b1f3dSraf 		(void) posix_spawn_file_actions_destroy(&fact);
131657b1f3dSraf 		errno = error;
132657b1f3dSraf 		return (NULL);
133657b1f3dSraf 	}
134*462453d2SMatthew Ahrens 	iop = fdopen(myfd, mode);
135*462453d2SMatthew Ahrens 	if (iop == NULL) {
136657b1f3dSraf 		error = errno;
137657b1f3dSraf 		lfree(node, sizeof (node_t));
138657b1f3dSraf 		(void) posix_spawnattr_destroy(&attr);
139657b1f3dSraf 		(void) posix_spawn_file_actions_destroy(&fact);
140*462453d2SMatthew Ahrens 		(void) close(myfd);
141657b1f3dSraf 		errno = error;
142a5f69788Scraigm 		return (NULL);
143a5f69788Scraigm 	}
144a5f69788Scraigm 
1457c478bd9Sstevel@tonic-gate 	lmutex_lock(&popen_lock);
1467c478bd9Sstevel@tonic-gate 
1477c478bd9Sstevel@tonic-gate 	/* in the child, close all pipes from other popen's */
148657b1f3dSraf 	for (curr = head; curr != NULL && error == 0; curr = curr->next) {
149657b1f3dSraf 		/*
150*462453d2SMatthew Ahrens 		 * The fd may no longer be open if an iob previously returned
151657b1f3dSraf 		 * by popen() was closed with fclose() rather than pclose(),
152*462453d2SMatthew Ahrens 		 * or if close(fileno(iob)) was called.  Use fcntl() to check
153*462453d2SMatthew Ahrens 		 * if the fd is still open, so that these programming errors
154*462453d2SMatthew Ahrens 		 * won't cause us to malfunction here.
155657b1f3dSraf 		 */
156*462453d2SMatthew Ahrens 		if (fcntl(curr->fd, F_GETFD) >= 0) {
1577c478bd9Sstevel@tonic-gate 			error = posix_spawn_file_actions_addclose(&fact,
158*462453d2SMatthew Ahrens 			    curr->fd);
159*462453d2SMatthew Ahrens 		}
1607c478bd9Sstevel@tonic-gate 	}
161f9f6ed06SRoger A. Faulkner 	/*
162f9f6ed06SRoger A. Faulkner 	 * See the comments in port/stdio/system.c for why these
163f9f6ed06SRoger A. Faulkner 	 * non-portable posix_spawn() attributes are being used.
164f9f6ed06SRoger A. Faulkner 	 */
165*462453d2SMatthew Ahrens 	if (error == 0) {
166657b1f3dSraf 		error = posix_spawnattr_setflags(&attr,
167f9f6ed06SRoger A. Faulkner 		    POSIX_SPAWN_NOSIGCHLD_NP |
168f9f6ed06SRoger A. Faulkner 		    POSIX_SPAWN_WAITPID_NP |
169f9f6ed06SRoger A. Faulkner 		    POSIX_SPAWN_NOEXECERR_NP);
170*462453d2SMatthew Ahrens 	}
171*462453d2SMatthew Ahrens 	if (error != 0) {
1727c478bd9Sstevel@tonic-gate 		lmutex_unlock(&popen_lock);
173657b1f3dSraf 		lfree(node, sizeof (node_t));
174657b1f3dSraf 		(void) posix_spawnattr_destroy(&attr);
1757c478bd9Sstevel@tonic-gate 		(void) posix_spawn_file_actions_destroy(&fact);
176a5f69788Scraigm 		(void) fclose(iop);
1777c478bd9Sstevel@tonic-gate 		errno = error;
1787c478bd9Sstevel@tonic-gate 		return (NULL);
1797c478bd9Sstevel@tonic-gate 	}
180*462453d2SMatthew Ahrens 	error = posix_spawn_pipe_np(&pid, &fd, cmd, *mode != 'r', &fact, &attr);
181657b1f3dSraf 	(void) posix_spawnattr_destroy(&attr);
1827c478bd9Sstevel@tonic-gate 	(void) posix_spawn_file_actions_destroy(&fact);
183*462453d2SMatthew Ahrens 	if (error != 0) {
1847c478bd9Sstevel@tonic-gate 		lmutex_unlock(&popen_lock);
185657b1f3dSraf 		lfree(node, sizeof (node_t));
186a5f69788Scraigm 		(void) fclose(iop);
187657b1f3dSraf 		errno = error;
1887c478bd9Sstevel@tonic-gate 		return (NULL);
1897c478bd9Sstevel@tonic-gate 	}
190*462453d2SMatthew Ahrens 	_insert_nolock(pid, myfd, node);
1917c478bd9Sstevel@tonic-gate 
1927c478bd9Sstevel@tonic-gate 	lmutex_unlock(&popen_lock);
1937c478bd9Sstevel@tonic-gate 
194*462453d2SMatthew Ahrens 	/*
195*462453d2SMatthew Ahrens 	 * myfd is the one that we fdopen()'ed; make it refer to the
196*462453d2SMatthew Ahrens 	 * pipe to the child.
197*462453d2SMatthew Ahrens 	 */
198*462453d2SMatthew Ahrens 	(void) dup2(fd, myfd);
199*462453d2SMatthew Ahrens 	(void) close(fd);
200*462453d2SMatthew Ahrens 
2017c478bd9Sstevel@tonic-gate 	_SET_ORIENTATION_BYTE(iop);
2027c478bd9Sstevel@tonic-gate 
2037c478bd9Sstevel@tonic-gate 	return (iop);
2047c478bd9Sstevel@tonic-gate }
2057c478bd9Sstevel@tonic-gate 
206a574db85Sraf /*
207a574db85Sraf  * pclose() is a cancellation point.
208a574db85Sraf  */
2097c478bd9Sstevel@tonic-gate int
pclose(FILE * ptr)2107c478bd9Sstevel@tonic-gate pclose(FILE *ptr)
2117c478bd9Sstevel@tonic-gate {
2127c478bd9Sstevel@tonic-gate 	pid_t	pid;
2137c478bd9Sstevel@tonic-gate 	int status;
2147c478bd9Sstevel@tonic-gate 
2157c478bd9Sstevel@tonic-gate 	pid = _delete(fileno(ptr));
2167c478bd9Sstevel@tonic-gate 
2177c478bd9Sstevel@tonic-gate 	/* mark this pipe closed */
2187c478bd9Sstevel@tonic-gate 	(void) fclose(ptr);
2197c478bd9Sstevel@tonic-gate 
220657b1f3dSraf 	if (pid <= 0) {
221657b1f3dSraf 		errno = ECHILD;
2227c478bd9Sstevel@tonic-gate 		return (-1);
223657b1f3dSraf 	}
2247c478bd9Sstevel@tonic-gate 
225657b1f3dSraf 	/*
226a574db85Sraf 	 * waitpid() is a cancellation point.
227a574db85Sraf 	 * This causes pclose() to be a cancellation point.
228657b1f3dSraf 	 *
229657b1f3dSraf 	 * If we have already been cancelled (pclose() was called from
230657b1f3dSraf 	 * a cancellation cleanup handler), attempt to reap the process
231657b1f3dSraf 	 * w/o waiting, and if that fails just call cleanup(pid).
232657b1f3dSraf 	 */
233657b1f3dSraf 
234657b1f3dSraf 	if (_thrp_cancelled()) {
235a574db85Sraf 		/* waitpid(..., WNOHANG) is not a cancellation point */
236657b1f3dSraf 		if (waitpid(pid, &status, WNOHANG) == pid)
237657b1f3dSraf 			return (status);
238657b1f3dSraf 		cleanup((void *)(uintptr_t)pid);
239657b1f3dSraf 		errno = ECHILD;
240657b1f3dSraf 		return (-1);
241657b1f3dSraf 	}
242657b1f3dSraf 
243657b1f3dSraf 	pthread_cleanup_push(cleanup, (void *)(uintptr_t)pid);
244a574db85Sraf 	while (waitpid(pid, &status, 0) < 0) {
2457c478bd9Sstevel@tonic-gate 		if (errno != EINTR) {
2467c478bd9Sstevel@tonic-gate 			status = -1;
2477c478bd9Sstevel@tonic-gate 			break;
2487c478bd9Sstevel@tonic-gate 		}
2497c478bd9Sstevel@tonic-gate 	}
250657b1f3dSraf 	pthread_cleanup_pop(0);
2517c478bd9Sstevel@tonic-gate 
2527c478bd9Sstevel@tonic-gate 	return (status);
2537c478bd9Sstevel@tonic-gate }
2547c478bd9Sstevel@tonic-gate 
2557c478bd9Sstevel@tonic-gate 
256657b1f3dSraf static void
_insert_nolock(pid_t pid,int fd,node_t * new)257657b1f3dSraf _insert_nolock(pid_t pid, int fd, node_t *new)
2587c478bd9Sstevel@tonic-gate {
2597c478bd9Sstevel@tonic-gate 	node_t	*prev;
2607c478bd9Sstevel@tonic-gate 	node_t	*curr;
2617c478bd9Sstevel@tonic-gate 
262657b1f3dSraf 	for (prev = curr = head; curr != NULL; curr = curr->next) {
263657b1f3dSraf 		/*
264657b1f3dSraf 		 * curr->fd can equal fd if a previous iob returned by
265657b1f3dSraf 		 * popen() was closed with fclose() rather than pclose(),
266a574db85Sraf 		 * or if close(fileno(iob)) was called.  Don't let these
267a574db85Sraf 		 * programming errors cause us to malfunction here.
268657b1f3dSraf 		 */
269657b1f3dSraf 		if (curr->fd == fd) {
270a574db85Sraf 			/* make a lame attempt to reap the forgotten child */
271657b1f3dSraf 			(void) waitpid(curr->pid, NULL, WNOHANG);
272657b1f3dSraf 			curr->pid = pid;
273657b1f3dSraf 			lfree(new, sizeof (node_t));
274657b1f3dSraf 			return;
275657b1f3dSraf 		}
2767c478bd9Sstevel@tonic-gate 		prev = curr;
277657b1f3dSraf 	}
2787c478bd9Sstevel@tonic-gate 
2797c478bd9Sstevel@tonic-gate 	new->pid = pid;
2807c478bd9Sstevel@tonic-gate 	new->fd = fd;
2817c478bd9Sstevel@tonic-gate 	new->next = NULL;
2827c478bd9Sstevel@tonic-gate 
2837c478bd9Sstevel@tonic-gate 	if (head == NULL)
2847c478bd9Sstevel@tonic-gate 		head = new;
2857c478bd9Sstevel@tonic-gate 	else
2867c478bd9Sstevel@tonic-gate 		prev->next = new;
2877c478bd9Sstevel@tonic-gate }
2887c478bd9Sstevel@tonic-gate 
2897c478bd9Sstevel@tonic-gate /*
2907c478bd9Sstevel@tonic-gate  * _insert() and _delete() are used by p2open() in libgen.
2917c478bd9Sstevel@tonic-gate  */
2927c478bd9Sstevel@tonic-gate int
_insert(pid_t pid,int fd)2937c478bd9Sstevel@tonic-gate _insert(pid_t pid, int fd)
2947c478bd9Sstevel@tonic-gate {
295657b1f3dSraf 	node_t *node;
296657b1f3dSraf 
297657b1f3dSraf 	if ((node = lmalloc(sizeof (node_t))) == NULL)
298657b1f3dSraf 		return (-1);
2997c478bd9Sstevel@tonic-gate 
3007c478bd9Sstevel@tonic-gate 	lmutex_lock(&popen_lock);
301657b1f3dSraf 	_insert_nolock(pid, fd, node);
3027c478bd9Sstevel@tonic-gate 	lmutex_unlock(&popen_lock);
3037c478bd9Sstevel@tonic-gate 
304657b1f3dSraf 	return (0);
3057c478bd9Sstevel@tonic-gate }
3067c478bd9Sstevel@tonic-gate 
3077c478bd9Sstevel@tonic-gate 
3087c478bd9Sstevel@tonic-gate pid_t
_delete(int fd)3097c478bd9Sstevel@tonic-gate _delete(int fd)
3107c478bd9Sstevel@tonic-gate {
3117c478bd9Sstevel@tonic-gate 	node_t	*prev;
3127c478bd9Sstevel@tonic-gate 	node_t	*curr;
3137c478bd9Sstevel@tonic-gate 	pid_t	pid;
3147c478bd9Sstevel@tonic-gate 
3157c478bd9Sstevel@tonic-gate 	lmutex_lock(&popen_lock);
3167c478bd9Sstevel@tonic-gate 
3177c478bd9Sstevel@tonic-gate 	for (prev = curr = head; curr != NULL; curr = curr->next) {
3187c478bd9Sstevel@tonic-gate 		if (curr->fd == fd) {
3197c478bd9Sstevel@tonic-gate 			if (curr == head)
3207c478bd9Sstevel@tonic-gate 				head = curr->next;
3217c478bd9Sstevel@tonic-gate 			else
3227c478bd9Sstevel@tonic-gate 				prev->next = curr->next;
323657b1f3dSraf 			lmutex_unlock(&popen_lock);
3247c478bd9Sstevel@tonic-gate 			pid = curr->pid;
3257c478bd9Sstevel@tonic-gate 			lfree(curr, sizeof (node_t));
3267c478bd9Sstevel@tonic-gate 			return (pid);
3277c478bd9Sstevel@tonic-gate 		}
3287c478bd9Sstevel@tonic-gate 		prev = curr;
3297c478bd9Sstevel@tonic-gate 	}
3307c478bd9Sstevel@tonic-gate 
3317c478bd9Sstevel@tonic-gate 	lmutex_unlock(&popen_lock);
3327c478bd9Sstevel@tonic-gate 
3337c478bd9Sstevel@tonic-gate 	return (-1);
3347c478bd9Sstevel@tonic-gate }
335