Chromium Code Reviews| 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..ca970aecf7636f44fe6ea840e3c781c7bbd7316c |
| --- /dev/null |
| +++ b/src/trusted/platform/posix/nacl_process.c |
| @@ -0,0 +1,434 @@ |
| +/* |
| + * 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_OSX |
| +#include <crt_externs.h> |
| +#include <sys/event.h> |
| +#else |
| +extern char **environ; |
| +#endif |
| + |
| +#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[] = { |
|
Mark Seaborn
2012/08/24 00:22:42
This is duplicating code from nacl_signal.c.
|
| +#if NACL_LINUX |
| + SIGSTKFLT, |
| + NACL_THREAD_SUSPEND_SIGNAL, |
| +#endif |
| + SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGBUS, SIGFPE, SIGSEGV |
| +}; |
| + |
| +#if NACL_OSX |
| +#define NACL_MACH_EXCEPTION_MASK EXC_MASK_BAD_ACCESS |
|
Mark Seaborn
2012/08/24 00:22:42
This is duplicating code from osx/mach_exception_h
|
| +#endif |
| + |
| +#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); |
|
Mark Seaborn
2012/08/24 00:22:42
I am sceptical you really want to close all FDs.
|
| + 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); |
|
Mark Seaborn
2012/08/24 00:22:42
Why are you using the getdents syscall directly?
|
| + 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 |
|
Mark Seaborn
2012/08/24 00:22:42
It would be better to check for EBADF and fail if
|
| + * 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); |
|
Mark Seaborn
2012/08/24 00:22:42
This doesn't work inside an outer sandbox...
|
| + 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) { |
| +#if NACL_OSX |
| + *_NSGetEnviron() = (char **) envp; |
| +#else |
| + environ = (char **) envp; |
| +#endif |
| + } |
| + |
| + NaClLog(4, |
| + "NaClProcessLaunch: exec application '%s'\n", |
| + argv[0]); |
| + |
| + 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 execvp, error %d\n", |
| + errno); |
| + } |
| + |
| + /* Parent process */ |
| + NaClLog(4, "NaClProcessFork: forked child process %d\n", pid); |
| + |
| + if (npp != NULL) { |
|
Mark Seaborn
2012/08/24 00:22:42
You already did CHECK(npp != NULL) earlier
|
| + 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) { |
|
Mark Seaborn
2012/08/24 00:22:42
I don't see any tests that cover this function...
|
| + 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); |
|
Mark Seaborn
2012/08/24 00:22:42
Why are you trying SIGTERM and then trying SIGKILL
|
| + 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; |
| +} |