system.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* 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 2005 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"
/*
* system() is a cancellation point.
* Undefine waitpid so we call the real waitpid() rather than _waitpid().
* This ensures that we actually perform the cancellation logic.
*/
#undef waitpid
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdlib.h>
#include <wait.h>
#include <sys/stat.h>
#include <unistd.h>
#include <memory.h>
#include <pthread.h>
#include <errno.h>
#include <synch.h>
#include <spawn.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 {
pthread_cleanup_push(cleanup, &savemask);
do {
w = waitpid(pid, &status, 0);
} while (w == -1 && errno == EINTR);
pthread_cleanup_pop(0);
if (w == -1)
status = -1;
}
cleanup(&savemask);
return (status);
}