| Index: src/trusted/platform/posix/nacl_process.c
|
| diff --git a/src/trusted/platform/posix/nacl_process.c b/src/trusted/platform/posix/nacl_process.c
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..654d88d0f586bead89227ba2a2406a5190f773f2
|
| --- /dev/null
|
| +++ b/src/trusted/platform/posix/nacl_process.c
|
| @@ -0,0 +1,415 @@
|
| +/*
|
| + * Copyright (c) 2012 The Native Client Authors. All rights reserved.
|
| + * Use of this source code is governed by a BSD-style license that can be
|
| + * found in the LICENSE file.
|
| + */
|
| +
|
| +#include "native_client/src/trusted/platform/nacl_process.h"
|
| +
|
| +#include <errno.h>
|
| +#include <fcntl.h>
|
| +#include <limits.h>
|
| +#include <signal.h>
|
| +#include <stdlib.h>
|
| +#include <sys/resource.h>
|
| +#include <sys/time.h>
|
| +#include <sys/types.h>
|
| +#include <sys/wait.h>
|
| +#include <unistd.h>
|
| +
|
| +#if NACL_LINUX
|
| +#include <dirent.h>
|
| +#include <sys/stat.h>
|
| +#include <sys/syscall.h>
|
| +#elif NACL_OSX
|
| +#include <mach/mach.h>
|
| +#endif
|
| +
|
| +#include "native_client/src/include/nacl_macros.h"
|
| +#include "native_client/src/include/portability.h"
|
| +#include "native_client/src/include/portability_string.h"
|
| +
|
| +#include "native_client/src/shared/platform/nacl_check.h"
|
| +#include "native_client/src/shared/platform/nacl_log.h"
|
| +#include "native_client/src/shared/platform/nacl_sync.h"
|
| +#include "native_client/src/shared/platform/nacl_sync_checked.h"
|
| +
|
| +#include "native_client/src/trusted/service_runtime/nacl_signal.h"
|
| +
|
| +#if NACL_LINUX
|
| +struct linux_dirent {
|
| + long d_ino;
|
| + off_t d_off;
|
| + unsigned short d_reclen;
|
| + char d_name[];
|
| +};
|
| +#endif
|
| +
|
| +static const int kSignals[] = {
|
| +#if NACL_LINUX
|
| + SIGSTKFLT,
|
| + NACL_THREAD_SUSPEND_SIGNAL,
|
| +#endif
|
| + SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGBUS, SIGFPE, SIGSEGV
|
| +};
|
| +
|
| +#if NACL_LINUX
|
| +static const rlim_t kSystemDefaultMaxFds = 8192;
|
| +static const char *kFDDir = "/proc/self/fd";
|
| +#else
|
| +static const rlim_t kSystemDefaultMaxFds = 256;
|
| +static const char *kFDDir = "/dev/fd";
|
| +#endif
|
| +
|
| +static void NaClResetSignalHandlers() {
|
| + struct sigaction dfl;
|
| + size_t i;
|
| +#if NACL_OSX
|
| + int rv = task_set_exception_ports(mach_task_self(), NACL_MACH_EXCEPTION_MASK,
|
| + MACH_PORT_NULL, EXCEPTION_DEFAULT,
|
| + THREAD_STATE_NONE);
|
| + if (rv != KERN_SUCCESS) {
|
| + NaClLog(LOG_FATAL, "Failed to unregister default exception handler.\n");
|
| + }
|
| +#endif
|
| +
|
| + memset(&dfl, 0, sizeof dfl);
|
| + dfl.sa_handler = SIG_DFL;
|
| + CHECK(sigemptyset(&dfl.sa_mask) == 0);
|
| +
|
| + for (i = 0; i < NACL_ARRAY_SIZE(kSignals); i++) {
|
| + if (sigaction(kSignals[i], &dfl, NULL) != 0) {
|
| + NaClLog(LOG_FATAL,
|
| + "Failed to unregister handler for %d with error %d\n",
|
| + kSignals[i], errno);
|
| + }
|
| + }
|
| +}
|
| +
|
| +static void NaClCloseAllFds() {
|
| + struct rlimit nofile;
|
| + rlim_t max_fds;
|
| + int fd;
|
| +#if NACL_LINUX
|
| + unsigned char buf[512];
|
| + size_t offset = 0;
|
| + size_t size = 0;
|
| + int dir_fd;
|
| +#endif
|
| +
|
| + /* Get the maximum number of FDs possible. */
|
| + if (getrlimit(RLIMIT_NOFILE, &nofile)) {
|
| + NaClLog(LOG_ERROR, "NaClProcessFork: getrlimit failed.\n");
|
| + max_fds = kSystemDefaultMaxFds;
|
| + } else {
|
| + max_fds = nofile.rlim_cur;
|
| + }
|
| +
|
| + if (max_fds > INT_MAX) {
|
| + max_fds = INT_MAX;
|
| + }
|
| +
|
| +#if NACL_LINUX
|
| + dir_fd = open(kFDDir, O_RDONLY | O_DIRECTORY);
|
| + if (-1 == dir_fd) {
|
| + NaClLog(LOG_FATAL,
|
| + "NaClProcessFork: failed to open %s, error %d.\n",
|
| + kFDDir, errno);
|
| + }
|
| +
|
| + for (;;) {
|
| + struct linux_dirent *dirent;
|
| + char *endptr;
|
| + int rv;
|
| +
|
| + if (size != 0) {
|
| + dirent = (struct linux_dirent *)&buf[offset];
|
| + offset += dirent->d_reclen;
|
| + }
|
| + if (offset == size) {
|
| + rv = syscall(__NR_getdents64, dir_fd, buf, sizeof buf);
|
| + if (rv == 0) {
|
| + /* We are done, there are no more entries */
|
| + break;
|
| + } else if (rv == -1) {
|
| + NaClLog(LOG_ERROR,
|
| + "NaClCloseAllFds: getdents64 failed error %d\n", errno);
|
| + break;
|
| + }
|
| + size = rv;
|
| + offset = 0;
|
| + }
|
| + dirent = (struct linux_dirent *)&buf[offset];
|
| +
|
| + /* Skip . and .. entries. */
|
| + if (dirent->d_name[0] == '.') {
|
| + continue;
|
| + }
|
| +
|
| + fd = strtol(dirent->d_name, &endptr, 10);
|
| + if (endptr != NULL || fd < 0) {
|
| + continue;
|
| + }
|
| +
|
| + if (fd == STDIN_FILENO ||
|
| + fd == STDOUT_FILENO ||
|
| + fd == STDERR_FILENO ||
|
| + fd == dir_fd) {
|
| + continue;
|
| + }
|
| +
|
| + /* Valgrind opens FDs >= |max_fds|, handle them here. */
|
| + if (fd < (int) max_fds) {
|
| + DCHECK(close(fd) == 0);
|
| + }
|
| + }
|
| +
|
| + DCHECK(close(dir_fd) == 0);
|
| +#else
|
| + for (fd = 0; fd < (int) max_fds; ++fd) {
|
| + if (fd == STDIN_FILENO ||
|
| + fd == STDOUT_FILENO ||
|
| + fd == STDERR_FILENO) {
|
| + continue;
|
| + }
|
| +
|
| + /*
|
| + * We deliberately ignore any errors because we do
|
| + * not know whether the filedescriptor is valid.
|
| + */
|
| + close(fd);
|
| + }
|
| +#endif
|
| +}
|
| +
|
| +int NaClProcessLaunch(struct NaClProcess *npp,
|
| + char *const *argv,
|
| + char *const *envp,
|
| + int flags) {
|
| + pid_t pid;
|
| +
|
| + NaClLog(2,
|
| + "NaClProcessLaunch(0x%08"NACL_PRIxPTR")\n",
|
| + (uintptr_t) npp);
|
| +
|
| + CHECK(npp != NULL);
|
| +
|
| + pid = fork();
|
| + if (pid < 0) {
|
| + NaClLog(LOG_ERROR, "NaClProcessFork: fork failed\n");
|
| + return 0;
|
| + } else if (pid == 0) {
|
| + /* Child process */
|
| + int null_fd;
|
| + int new_fd;
|
| +
|
| + /* We do not want parent and child to share standard input. */
|
| + null_fd = open("/dev/null", O_RDONLY);
|
| + if (null_fd < 0) {
|
| + NaClLog(LOG_ERROR,
|
| + "NaClProcessFork: failed to open /dev/null\n");
|
| + _exit(127);
|
| + }
|
| +
|
| + new_fd = dup2(null_fd, STDIN_FILENO);
|
| + if (new_fd != STDIN_FILENO) {
|
| + NaClLog(LOG_ERROR,
|
| + "NaClProcessFork: failed to dup /dev/null for stdin\n");
|
| + _exit(127);
|
| + }
|
| + DCHECK(close(null_fd) != 0);
|
| +
|
| + if (0 != (flags & NACL_PROCESS_LAUNCH_NEW_GROUP)) {
|
| + /* Setup new process group. */
|
| + if (setpgid(0, 0) < 0) {
|
| + NaClLog(LOG_ERROR,
|
| + "NaClProcessFork: setpgid failed error %d\n", errno);
|
| + _exit(127);
|
| + }
|
| + }
|
| +
|
| + /*
|
| + * The previous signal handlers are likely to be meaningless in
|
| + * the child's context so we reset them to the defaults.
|
| + */
|
| + NaClResetSignalHandlers();
|
| +
|
| + if (0 != (flags & NACL_PROCESS_LAUNCH_CLOSE_FDS)) {
|
| + NaClCloseAllFds();
|
| + }
|
| +
|
| + if (NULL != envp) {
|
| + execvpe(argv[0], argv, envp);
|
| + } else {
|
| + execvp(argv[0], argv);
|
| + }
|
| +
|
| + /*
|
| + * When successful, exec* does not return, so if we reached
|
| + * here, there must have been an error; report it.
|
| + */
|
| + NaClLog(LOG_FATAL,
|
| + "NaclProcessSpawn: failed to execvpe, error %d\n",
|
| + errno);
|
| + }
|
| +
|
| + /* Parent process */
|
| + NaClLog(4, "NaClProcessFork: forked child process %d\n", pid);
|
| +
|
| + if (npp != NULL) {
|
| + npp->pid = pid;
|
| + }
|
| +
|
| + return 1;
|
| +}
|
| +
|
| +/*
|
| + * Attempts to kill the process identified by the given process handle.
|
| + * The exit_code is ignored since POSIX can't enforce that.
|
| + */
|
| +int NaClProcessKill(struct NaClProcess *npp, int exit_code, int wait) {
|
| + static unsigned int kMaxSleepMs = 1000;
|
| + int retval;
|
| + UNREFERENCED_PARAMETER(exit_code);
|
| +
|
| + NaClLog(2,
|
| + "NaClProcessKill(0x%08"NACL_PRIxPTR", %d, %d)\n",
|
| + (uintptr_t) npp, exit_code, wait);
|
| +
|
| + CHECK(npp != NULL);
|
| + CHECK(npp->pid > 1);
|
| +
|
| + retval = kill(npp->pid, SIGTERM);
|
| + if (-1 == retval) {
|
| + NaClLog(LOG_ERROR,
|
| + "NaClProcessKill: unable to terminate process %d\n", errno);
|
| + goto done;
|
| + }
|
| +
|
| + if (wait != 0) {
|
| + unsigned int sleep_ms = 4;
|
| + int retries = 60;
|
| + int exited = 0;
|
| +
|
| + /* The process may not end immediately due to pending I/O */
|
| + while (retries-- > 0) {
|
| + pid_t pid = waitpid(npp->pid, NULL, WNOHANG);
|
| + if (pid == npp->pid) {
|
| + exited = 1;
|
| + break;
|
| + } else if (pid == -1) {
|
| + if (ECHILD == errno) {
|
| + /*
|
| + * The wait may fail with ECHILD if another process also waited for
|
| + * the same pid, causing the process state to get cleaned up.
|
| + */
|
| + exited = 1;
|
| + break;
|
| + }
|
| + NaClLog(LOG_ERROR,
|
| + "NaClProcessKill: waitpid(%d) returned error %d\n",
|
| + npp->pid, errno);
|
| + }
|
| +
|
| + usleep(sleep_ms * 1000);
|
| + if (sleep_ms < kMaxSleepMs) {
|
| + sleep_ms *= 2;
|
| + }
|
| + }
|
| +
|
| + /*
|
| + * If we're waiting and the child hasn't died by now, force it
|
| + * with a SIGKILL.
|
| + */
|
| + if (!exited) {
|
| + if (-1 == (retval = kill(npp->pid, SIGKILL))) {
|
| + NaClLog(LOG_ERROR,
|
| + "NaClProcessKill: failed to kill process %d\n", errno);
|
| + }
|
| + }
|
| + }
|
| +
|
| + done:
|
| + return retval;
|
| +}
|
| +
|
| +int NaClProcessGetStatus(struct NaClProcess *npp,
|
| + int *status) {
|
| + int tmp_status = 0;
|
| + pid_t pid;
|
| +
|
| + NaClLog(2,
|
| + ("NaClProcessGetStatus(0x%08"NACL_PRIxPTR
|
| + ", 0x%08"NACL_PRIxPTR")\n"),
|
| + (uintptr_t) npp, (uintptr_t) status);
|
| +
|
| + CHECK(npp != NULL);
|
| +
|
| + NaClLog(4,
|
| + "NaClProcessGetStatus: checking status of process %d\n",
|
| + (int) npp->pid);
|
| +
|
| + pid = waitpid(npp->pid, &tmp_status, WNOHANG);
|
| + if (pid == -1) {
|
| + NaClLog(LOG_ERROR,
|
| + "NaClProcessGetStatus: waitpid(%d) returned error %d\n",
|
| + npp->pid, errno);
|
| + return 0;
|
| + } else if (pid == 0) {
|
| + *status = NACL_PROCESS_STATUS_STILL_RUNNING;
|
| + goto done;
|
| + }
|
| +
|
| + if (WIFSIGNALED(tmp_status)) {
|
| + switch (WTERMSIG(tmp_status)) {
|
| + case SIGABRT:
|
| + case SIGBUS:
|
| + case SIGFPE:
|
| + case SIGILL:
|
| + case SIGSEGV:
|
| + *status = NACL_PROCESS_STATUS_CRASHED;
|
| + goto done;
|
| + case SIGINT:
|
| + case SIGKILL:
|
| + case SIGTERM:
|
| + *status = NACL_PROCESS_STATUS_KILLED;
|
| + goto done;
|
| + default:
|
| + break;
|
| + }
|
| + }
|
| +
|
| + if (WIFEXITED(tmp_status) != 0 && WEXITSTATUS(tmp_status) != 0) {
|
| + *status = NACL_PROCESS_STATUS_ABNORMAL_EXIT;
|
| + goto done;
|
| + }
|
| + *status = NACL_PROCESS_STATUS_NORMAL_EXIT;
|
| +
|
| + done:
|
| + return 1;
|
| +}
|
| +
|
| +int NaClProcessWaitForExitCode(struct NaClProcess *npp,
|
| + int *exit_code) {
|
| + int status;
|
| +
|
| + NaClLog(2,
|
| + ("NaClProcessWaitForExitCode(0x%08"NACL_PRIxPTR
|
| + ", 0x%08"NACL_PRIxPTR")\n"),
|
| + (uintptr_t) npp, (uintptr_t) exit_code);
|
| +
|
| + CHECK(npp != NULL);
|
| +
|
| + if (-1 == waitpid(npp->pid, &status, 0)) {
|
| + return 0;
|
| + }
|
| +
|
| + if (WIFEXITED(status) != 0) {
|
| + *exit_code = WEXITSTATUS(status);
|
| + return 1;
|
| + }
|
| +
|
| + /* Check whether the process signaled */
|
| + CHECK(WIFSIGNALED(status) != 0);
|
| + return 0;
|
| +}
|
|
|