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; |
+} |