/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright (c) 2011 by Delphix. All rights reserved. */ /* Copyright (c) 1988 AT&T */ /* All Rights Reserved */ #pragma weak _pclose = pclose #pragma weak _popen = popen #include "lint.h" #include "mtlib.h" #include "file64.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "stdiom.h" #include "mse.h" #include "libc.h" static mutex_t popen_lock = DEFAULTMUTEX; typedef struct node { pid_t pid; int fd; struct node *next; } node_t; static node_t *head = NULL; static void _insert_nolock(pid_t, int, node_t *); /* * Cancellation cleanup handler. * If we were cancelled in waitpid(), create a daemon thread to * reap our abandoned child. No other thread can do this for us. */ static void cleanup(void *arg) { extern const sigset_t maskset; extern void *reapchild(void *); /* see port/stdio/system.c */ /* * We have been cancelled. There is no need to restore * the original sigmask after blocking all signals because * pthread_exit() will block all signals while we exit. */ (void) thr_sigsetmask(SIG_SETMASK, &maskset, NULL); (void) thr_create(NULL, 0, reapchild, arg, THR_DAEMON, NULL); } FILE * popen(const char *cmd, const char *mode) { pid_t pid; int myfd, fd; const char *shpath = _PATH_BSHELL; FILE *iop; node_t *curr; node_t *node; posix_spawn_file_actions_t fact; posix_spawnattr_t attr; int error; if ((node = lmalloc(sizeof (node_t))) == NULL) return (NULL); if ((error = posix_spawnattr_init(&attr)) != 0) { lfree(node, sizeof (node_t)); errno = error; return (NULL); } if ((error = posix_spawn_file_actions_init(&fact)) != 0) { lfree(node, sizeof (node_t)); (void) posix_spawnattr_destroy(&attr); errno = error; return (NULL); } if (access(shpath, X_OK)) /* XPG4 Requirement: */ shpath = ""; /* force child to fail immediately */ /* * fdopen() can fail (if the fd is too high or we are out of memory), * but we don't want to have any way to fail after creating the child * process. So we fdopen() a dummy fd (myfd), and once we get the real * fd from posix_spawn_pipe_np(), we dup2() the real fd onto the dummy. */ myfd = open("/dev/null", O_RDWR); if (myfd == -1) { error = errno; lfree(node, sizeof (node_t)); (void) posix_spawnattr_destroy(&attr); (void) posix_spawn_file_actions_destroy(&fact); errno = error; return (NULL); } iop = fdopen(myfd, mode); if (iop == NULL) { error = errno; lfree(node, sizeof (node_t)); (void) posix_spawnattr_destroy(&attr); (void) posix_spawn_file_actions_destroy(&fact); (void) close(myfd); errno = error; return (NULL); } lmutex_lock(&popen_lock); /* in the child, close all pipes from other popen's */ for (curr = head; curr != NULL && error == 0; curr = curr->next) { /* * The fd may no longer be open if an iob previously returned * by popen() was closed with fclose() rather than pclose(), * or if close(fileno(iob)) was called. Use fcntl() to check * if the fd is still open, so that these programming errors * won't cause us to malfunction here. */ if (fcntl(curr->fd, F_GETFD) >= 0) { error = posix_spawn_file_actions_addclose(&fact, curr->fd); } } /* * See the comments in port/stdio/system.c for why these * non-portable posix_spawn() attributes are being used. */ if (error == 0) { error = posix_spawnattr_setflags(&attr, POSIX_SPAWN_NOSIGCHLD_NP | POSIX_SPAWN_WAITPID_NP | POSIX_SPAWN_NOEXECERR_NP); } if (error != 0) { lmutex_unlock(&popen_lock); lfree(node, sizeof (node_t)); (void) posix_spawnattr_destroy(&attr); (void) posix_spawn_file_actions_destroy(&fact); (void) fclose(iop); errno = error; return (NULL); } error = posix_spawn_pipe_np(&pid, &fd, cmd, *mode != 'r', &fact, &attr); (void) posix_spawnattr_destroy(&attr); (void) posix_spawn_file_actions_destroy(&fact); if (error != 0) { lmutex_unlock(&popen_lock); lfree(node, sizeof (node_t)); (void) fclose(iop); errno = error; return (NULL); } _insert_nolock(pid, myfd, node); lmutex_unlock(&popen_lock); /* * myfd is the one that we fdopen()'ed; make it refer to the * pipe to the child. */ (void) dup2(fd, myfd); (void) close(fd); _SET_ORIENTATION_BYTE(iop); return (iop); } /* * pclose() is a cancellation point. */ int pclose(FILE *ptr) { pid_t pid; int status; pid = _delete(fileno(ptr)); /* mark this pipe closed */ (void) fclose(ptr); if (pid <= 0) { errno = ECHILD; return (-1); } /* * waitpid() is a cancellation point. * This causes pclose() to be a cancellation point. * * If we have already been cancelled (pclose() was called from * a cancellation cleanup handler), attempt to reap the process * w/o waiting, and if that fails just call cleanup(pid). */ if (_thrp_cancelled()) { /* waitpid(..., WNOHANG) is not a cancellation point */ if (waitpid(pid, &status, WNOHANG) == pid) return (status); cleanup((void *)(uintptr_t)pid); errno = ECHILD; return (-1); } pthread_cleanup_push(cleanup, (void *)(uintptr_t)pid); while (waitpid(pid, &status, 0) < 0) { if (errno != EINTR) { status = -1; break; } } pthread_cleanup_pop(0); return (status); } static void _insert_nolock(pid_t pid, int fd, node_t *new) { node_t *prev; node_t *curr; for (prev = curr = head; curr != NULL; curr = curr->next) { /* * curr->fd can equal fd if a previous iob returned by * popen() was closed with fclose() rather than pclose(), * or if close(fileno(iob)) was called. Don't let these * programming errors cause us to malfunction here. */ if (curr->fd == fd) { /* make a lame attempt to reap the forgotten child */ (void) waitpid(curr->pid, NULL, WNOHANG); curr->pid = pid; lfree(new, sizeof (node_t)); return; } prev = curr; } new->pid = pid; new->fd = fd; new->next = NULL; if (head == NULL) head = new; else prev->next = new; } /* * _insert() and _delete() are used by p2open() in libgen. */ int _insert(pid_t pid, int fd) { node_t *node; if ((node = lmalloc(sizeof (node_t))) == NULL) return (-1); lmutex_lock(&popen_lock); _insert_nolock(pid, fd, node); lmutex_unlock(&popen_lock); return (0); } pid_t _delete(int fd) { node_t *prev; node_t *curr; pid_t pid; lmutex_lock(&popen_lock); for (prev = curr = head; curr != NULL; curr = curr->next) { if (curr->fd == fd) { if (curr == head) head = curr->next; else prev->next = curr->next; lmutex_unlock(&popen_lock); pid = curr->pid; lfree(curr, sizeof (node_t)); return (pid); } prev = curr; } lmutex_unlock(&popen_lock); return (-1); }