/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" /* Copyright (c) 1988 AT&T */ /* All Rights Reserved */ #include "synonyms.h" #include "mtlib.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "libc.h" extern const char **environ; extern int __xpg4; /* defined in _xpg4.c; 0 if not xpg4-compiled program */ static mutex_t sys_lock = DEFAULTMUTEX; /* protects the following */ static uint_t sys_count = 0; /* number of threads in system() */ static struct sigaction sys_ibuf; /* SIGINT */ static struct sigaction sys_qbuf; /* SIGQUIT */ static struct sigaction sys_cbuf; /* SIGCHLD */ /* * Cancellation cleanup handler. */ static void cleanup(void *arg) { sigset_t *savemaskp = arg; lmutex_lock(&sys_lock); if (--sys_count == 0) { /* leaving system() */ /* * There are no remaining threads in system(), * so restore the several signal actions. */ (void) sigaction(SIGINT, &sys_ibuf, NULL); (void) sigaction(SIGQUIT, &sys_qbuf, NULL); if (sys_cbuf.sa_handler == SIG_IGN || (sys_cbuf.sa_flags & SA_NOCLDWAIT)) (void) sigaction(SIGCHLD, &sys_cbuf, NULL); } lmutex_unlock(&sys_lock); (void) sigprocmask(SIG_SETMASK, savemaskp, NULL); } int system(const char *cmd) { pid_t pid; pid_t w; int status; int error; struct sigaction action; sigset_t mask; sigset_t savemask; struct stat64 buf; const char *shpath; char *argvec[4]; posix_spawnattr_t attr; static const char *sun_path = "/bin/sh"; static const char *xpg4_path = "/usr/xpg4/bin/sh"; static const char *shell = "sh"; shpath = __xpg4? xpg4_path : sun_path; if (cmd == NULL) { if (stat64(shpath, &buf) != 0) { return (0); } else if (getuid() == buf.st_uid) { /* exec for user */ if ((buf.st_mode & 0100) == 0) return (0); } else if (getgid() == buf.st_gid) { /* exec for group */ if ((buf.st_mode & 0010) == 0) return (0); } else if ((buf.st_mode & 0001) == 0) { /* exec for others */ return (0); } return (1); } /* * Initialize the posix_spawn() attributes structure. */ if ((error = posix_spawnattr_init(&attr)) != 0) { errno = error; return (-1); } error = posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF); /* * We are required to block SIGCHLD so that we don't cause * the process's signal handler, if any, to be called. * This doesn't really work for a multithreaded process * because some other thread may receive the SIGCHLD. */ (void) sigemptyset(&mask); (void) sigaddset(&mask, SIGCHLD); (void) sigprocmask(SIG_BLOCK, &mask, &savemask); /* * Tell posix_spawn() to restore the signal mask in the child. */ if (error == 0) error = posix_spawnattr_setsigmask(&attr, &savemask); /* * We are required to set the disposition of SIGINT and SIGQUIT * to be ignored for the duration of the system() operation. * * We allow more than one thread to call system() concurrently by * keeping a count of such threads. The signal actions are set * to SIG_IGN when the first thread calls system(). They are * restored in cleanup() when the last thread exits system(). * * However, system() is still MT-unsafe because sigaction() has * a process-wide effect and some other thread may also be * setting the signal actions for SIGINT or SIGQUIT. */ lmutex_lock(&sys_lock); if (sys_count++ == 0) { (void) memset(&action, 0, sizeof (action)); action.sa_handler = SIG_IGN; (void) sigaction(SIGINT, &action, &sys_ibuf); (void) sigaction(SIGQUIT, &action, &sys_qbuf); /* * If the action for SIGCHLD is SIG_IGN, then set it to SIG_DFL * so we can retrieve the status of the spawned-off shell. * The execve() performed in posix_spawn() will set the action * for SIGCHLD in the child process to SIG_DFL regardless, * so this has no negative consequencies for the child. * * Note that this is not required by the SUSv3 standard. * The standard permits this error: * ECHILD The status of the child process created * by system() is no longer available. * So we could leave the action for SIGCHLD alone and * still be standards-conforming, but this is the way * the SunOS system() has always behaved (in fact it * used to set the action to SIG_DFL unconditinally), * so we retain this behavior here. */ (void) sigaction(SIGCHLD, NULL, &sys_cbuf); if (sys_cbuf.sa_handler == SIG_IGN || (sys_cbuf.sa_flags & SA_NOCLDWAIT)) { action.sa_handler = SIG_DFL; (void) sigaction(SIGCHLD, &action, NULL); } } lmutex_unlock(&sys_lock); /* * If SIGINT and SIGQUIT were not already SIG_IGN, tell * posix_spawn() to make them SIG_DFL in the child, * else leave them as SIG_IGN in the child. */ (void) sigemptyset(&mask); if (sys_ibuf.sa_handler != SIG_IGN) (void) sigaddset(&mask, SIGINT); if (sys_qbuf.sa_handler != SIG_IGN) (void) sigaddset(&mask, SIGQUIT); if (error == 0) error = posix_spawnattr_setsigdefault(&attr, &mask); argvec[0] = (char *)shell; argvec[1] = "-c"; argvec[2] = (char *)cmd; argvec[3] = NULL; if (error == 0) error = posix_spawn(&pid, shpath, NULL, &attr, (char *const *)argvec, (char *const *)environ); (void) posix_spawnattr_destroy(&attr); if (error) { errno = error; status = -1; } else { /* * system() is a cancellation point. * Call waitpid_cancel() rather than _waitpid() to make * sure that we actually perform the cancellation logic. */ pthread_cleanup_push(cleanup, &savemask); do { w = waitpid_cancel(pid, &status, 0); } while (w == -1 && errno == EINTR); pthread_cleanup_pop(0); if (w == -1) status = -1; } cleanup(&savemask); return (status); }