OLD | NEW |
(Empty) | |
| 1 /* |
| 2 * Copyright (c) 2012 The Native Client Authors. All rights reserved. |
| 3 * Use of this source code is governed by a BSD-style license that can be |
| 4 * found in the LICENSE file. |
| 5 */ |
| 6 |
| 7 #include "native_client/src/trusted/platform/nacl_process.h" |
| 8 |
| 9 #include <errno.h> |
| 10 #include <fcntl.h> |
| 11 #include <limits.h> |
| 12 #include <signal.h> |
| 13 #include <stdlib.h> |
| 14 #include <sys/resource.h> |
| 15 #include <sys/time.h> |
| 16 #include <sys/types.h> |
| 17 #include <sys/wait.h> |
| 18 #include <unistd.h> |
| 19 |
| 20 #if NACL_LINUX |
| 21 #include <dirent.h> |
| 22 #include <sys/stat.h> |
| 23 #include <sys/syscall.h> |
| 24 #elif NACL_OSX |
| 25 #include <mach/mach.h> |
| 26 #endif |
| 27 |
| 28 #include "native_client/src/include/nacl_macros.h" |
| 29 #include "native_client/src/include/portability.h" |
| 30 #include "native_client/src/include/portability_string.h" |
| 31 |
| 32 #include "native_client/src/shared/platform/nacl_check.h" |
| 33 #include "native_client/src/shared/platform/nacl_log.h" |
| 34 #include "native_client/src/shared/platform/nacl_sync.h" |
| 35 #include "native_client/src/shared/platform/nacl_sync_checked.h" |
| 36 |
| 37 #include "native_client/src/trusted/service_runtime/nacl_signal.h" |
| 38 |
| 39 #if NACL_LINUX |
| 40 struct linux_dirent { |
| 41 long d_ino; |
| 42 off_t d_off; |
| 43 unsigned short d_reclen; |
| 44 char d_name[]; |
| 45 }; |
| 46 #endif |
| 47 |
| 48 static const int kSignals[] = { |
| 49 #if NACL_LINUX |
| 50 SIGSTKFLT, |
| 51 NACL_THREAD_SUSPEND_SIGNAL, |
| 52 #endif |
| 53 SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGBUS, SIGFPE, SIGSEGV |
| 54 }; |
| 55 |
| 56 #if NACL_LINUX |
| 57 static const rlim_t kSystemDefaultMaxFds = 8192; |
| 58 static const char *kFDDir = "/proc/self/fd"; |
| 59 #else |
| 60 static const rlim_t kSystemDefaultMaxFds = 256; |
| 61 static const char *kFDDir = "/dev/fd"; |
| 62 #endif |
| 63 |
| 64 static void NaClResetSignalHandlers() { |
| 65 struct sigaction dfl; |
| 66 size_t i; |
| 67 #if NACL_OSX |
| 68 int rv = task_set_exception_ports(mach_task_self(), NACL_MACH_EXCEPTION_MASK, |
| 69 MACH_PORT_NULL, EXCEPTION_DEFAULT, |
| 70 THREAD_STATE_NONE); |
| 71 if (rv != KERN_SUCCESS) { |
| 72 NaClLog(LOG_FATAL, "Failed to unregister default exception handler.\n"); |
| 73 } |
| 74 #endif |
| 75 |
| 76 memset(&dfl, 0, sizeof dfl); |
| 77 dfl.sa_handler = SIG_DFL; |
| 78 CHECK(sigemptyset(&dfl.sa_mask) == 0); |
| 79 |
| 80 for (i = 0; i < NACL_ARRAY_SIZE(kSignals); i++) { |
| 81 if (sigaction(kSignals[i], &dfl, NULL) != 0) { |
| 82 NaClLog(LOG_FATAL, |
| 83 "Failed to unregister handler for %d with error %d\n", |
| 84 kSignals[i], errno); |
| 85 } |
| 86 } |
| 87 } |
| 88 |
| 89 static void NaClCloseAllFds() { |
| 90 struct rlimit nofile; |
| 91 rlim_t max_fds; |
| 92 int fd; |
| 93 #if NACL_LINUX |
| 94 unsigned char buf[512]; |
| 95 size_t offset = 0; |
| 96 size_t size = 0; |
| 97 int dir_fd; |
| 98 #endif |
| 99 |
| 100 /* Get the maximum number of FDs possible. */ |
| 101 if (getrlimit(RLIMIT_NOFILE, &nofile)) { |
| 102 NaClLog(LOG_ERROR, "NaClProcessFork: getrlimit failed.\n"); |
| 103 max_fds = kSystemDefaultMaxFds; |
| 104 } else { |
| 105 max_fds = nofile.rlim_cur; |
| 106 } |
| 107 |
| 108 if (max_fds > INT_MAX) { |
| 109 max_fds = INT_MAX; |
| 110 } |
| 111 |
| 112 #if NACL_LINUX |
| 113 dir_fd = open(kFDDir, O_RDONLY | O_DIRECTORY); |
| 114 if (-1 == dir_fd) { |
| 115 NaClLog(LOG_FATAL, |
| 116 "NaClProcessFork: failed to open %s, error %d.\n", |
| 117 kFDDir, errno); |
| 118 } |
| 119 |
| 120 for (;;) { |
| 121 struct linux_dirent *dirent; |
| 122 char *endptr; |
| 123 int rv; |
| 124 |
| 125 if (size != 0) { |
| 126 dirent = (struct linux_dirent *)&buf[offset]; |
| 127 offset += dirent->d_reclen; |
| 128 } |
| 129 if (offset == size) { |
| 130 rv = syscall(__NR_getdents64, dir_fd, buf, sizeof buf); |
| 131 if (rv == 0) { |
| 132 /* We are done, there are no more entries */ |
| 133 break; |
| 134 } else if (rv == -1) { |
| 135 NaClLog(LOG_ERROR, |
| 136 "NaClCloseAllFds: getdents64 failed error %d\n", errno); |
| 137 break; |
| 138 } |
| 139 size = rv; |
| 140 offset = 0; |
| 141 } |
| 142 dirent = (struct linux_dirent *)&buf[offset]; |
| 143 |
| 144 /* Skip . and .. entries. */ |
| 145 if (dirent->d_name[0] == '.') { |
| 146 continue; |
| 147 } |
| 148 |
| 149 fd = strtol(dirent->d_name, &endptr, 10); |
| 150 if (endptr != NULL || fd < 0) { |
| 151 continue; |
| 152 } |
| 153 |
| 154 if (fd == STDIN_FILENO || |
| 155 fd == STDOUT_FILENO || |
| 156 fd == STDERR_FILENO || |
| 157 fd == dir_fd) { |
| 158 continue; |
| 159 } |
| 160 |
| 161 /* Valgrind opens FDs >= |max_fds|, handle them here. */ |
| 162 if (fd < (int) max_fds) { |
| 163 DCHECK(close(fd) == 0); |
| 164 } |
| 165 } |
| 166 |
| 167 DCHECK(close(dir_fd) == 0); |
| 168 #else |
| 169 for (fd = 0; fd < (int) max_fds; ++fd) { |
| 170 if (fd == STDIN_FILENO || |
| 171 fd == STDOUT_FILENO || |
| 172 fd == STDERR_FILENO) { |
| 173 continue; |
| 174 } |
| 175 |
| 176 /* |
| 177 * We deliberately ignore any errors because we do |
| 178 * not know whether the filedescriptor is valid. |
| 179 */ |
| 180 close(fd); |
| 181 } |
| 182 #endif |
| 183 } |
| 184 |
| 185 int NaClProcessLaunch(struct NaClProcess *npp, |
| 186 char *const *argv, |
| 187 char *const *envp, |
| 188 int flags) { |
| 189 pid_t pid; |
| 190 |
| 191 NaClLog(2, |
| 192 "NaClProcessLaunch(0x%08"NACL_PRIxPTR")\n", |
| 193 (uintptr_t) npp); |
| 194 |
| 195 CHECK(npp != NULL); |
| 196 |
| 197 pid = fork(); |
| 198 if (pid < 0) { |
| 199 NaClLog(LOG_ERROR, "NaClProcessFork: fork failed\n"); |
| 200 return 0; |
| 201 } else if (pid == 0) { |
| 202 /* Child process */ |
| 203 int null_fd; |
| 204 int new_fd; |
| 205 |
| 206 /* We do not want parent and child to share standard input. */ |
| 207 null_fd = open("/dev/null", O_RDONLY); |
| 208 if (null_fd < 0) { |
| 209 NaClLog(LOG_ERROR, |
| 210 "NaClProcessFork: failed to open /dev/null\n"); |
| 211 _exit(127); |
| 212 } |
| 213 |
| 214 new_fd = dup2(null_fd, STDIN_FILENO); |
| 215 if (new_fd != STDIN_FILENO) { |
| 216 NaClLog(LOG_ERROR, |
| 217 "NaClProcessFork: failed to dup /dev/null for stdin\n"); |
| 218 _exit(127); |
| 219 } |
| 220 DCHECK(close(null_fd) != 0); |
| 221 |
| 222 if (0 != (flags & NACL_PROCESS_LAUNCH_NEW_GROUP)) { |
| 223 /* Setup new process group. */ |
| 224 if (setpgid(0, 0) < 0) { |
| 225 NaClLog(LOG_ERROR, |
| 226 "NaClProcessFork: setpgid failed error %d\n", errno); |
| 227 _exit(127); |
| 228 } |
| 229 } |
| 230 |
| 231 /* |
| 232 * The previous signal handlers are likely to be meaningless in |
| 233 * the child's context so we reset them to the defaults. |
| 234 */ |
| 235 NaClResetSignalHandlers(); |
| 236 |
| 237 if (0 != (flags & NACL_PROCESS_LAUNCH_CLOSE_FDS)) { |
| 238 NaClCloseAllFds(); |
| 239 } |
| 240 |
| 241 if (NULL != envp) { |
| 242 execvpe(argv[0], argv, envp); |
| 243 } else { |
| 244 execvp(argv[0], argv); |
| 245 } |
| 246 |
| 247 /* |
| 248 * When successful, exec* does not return, so if we reached |
| 249 * here, there must have been an error; report it. |
| 250 */ |
| 251 NaClLog(LOG_FATAL, |
| 252 "NaclProcessSpawn: failed to execvpe, error %d\n", |
| 253 errno); |
| 254 } |
| 255 |
| 256 /* Parent process */ |
| 257 NaClLog(4, "NaClProcessFork: forked child process %d\n", pid); |
| 258 |
| 259 if (npp != NULL) { |
| 260 npp->pid = pid; |
| 261 } |
| 262 |
| 263 return 1; |
| 264 } |
| 265 |
| 266 /* |
| 267 * Attempts to kill the process identified by the given process handle. |
| 268 * The exit_code is ignored since POSIX can't enforce that. |
| 269 */ |
| 270 int NaClProcessKill(struct NaClProcess *npp, int exit_code, int wait) { |
| 271 static unsigned int kMaxSleepMs = 1000; |
| 272 int retval; |
| 273 UNREFERENCED_PARAMETER(exit_code); |
| 274 |
| 275 NaClLog(2, |
| 276 "NaClProcessKill(0x%08"NACL_PRIxPTR", %d, %d)\n", |
| 277 (uintptr_t) npp, exit_code, wait); |
| 278 |
| 279 CHECK(npp != NULL); |
| 280 CHECK(npp->pid > 1); |
| 281 |
| 282 retval = kill(npp->pid, SIGTERM); |
| 283 if (-1 == retval) { |
| 284 NaClLog(LOG_ERROR, |
| 285 "NaClProcessKill: unable to terminate process %d\n", errno); |
| 286 goto done; |
| 287 } |
| 288 |
| 289 if (wait != 0) { |
| 290 unsigned int sleep_ms = 4; |
| 291 int retries = 60; |
| 292 int exited = 0; |
| 293 |
| 294 /* The process may not end immediately due to pending I/O */ |
| 295 while (retries-- > 0) { |
| 296 pid_t pid = waitpid(npp->pid, NULL, WNOHANG); |
| 297 if (pid == npp->pid) { |
| 298 exited = 1; |
| 299 break; |
| 300 } else if (pid == -1) { |
| 301 if (ECHILD == errno) { |
| 302 /* |
| 303 * The wait may fail with ECHILD if another process also waited for |
| 304 * the same pid, causing the process state to get cleaned up. |
| 305 */ |
| 306 exited = 1; |
| 307 break; |
| 308 } |
| 309 NaClLog(LOG_ERROR, |
| 310 "NaClProcessKill: waitpid(%d) returned error %d\n", |
| 311 npp->pid, errno); |
| 312 } |
| 313 |
| 314 usleep(sleep_ms * 1000); |
| 315 if (sleep_ms < kMaxSleepMs) { |
| 316 sleep_ms *= 2; |
| 317 } |
| 318 } |
| 319 |
| 320 /* |
| 321 * If we're waiting and the child hasn't died by now, force it |
| 322 * with a SIGKILL. |
| 323 */ |
| 324 if (!exited) { |
| 325 if (-1 == (retval = kill(npp->pid, SIGKILL))) { |
| 326 NaClLog(LOG_ERROR, |
| 327 "NaClProcessKill: failed to kill process %d\n", errno); |
| 328 } |
| 329 } |
| 330 } |
| 331 |
| 332 done: |
| 333 return retval; |
| 334 } |
| 335 |
| 336 int NaClProcessGetStatus(struct NaClProcess *npp, |
| 337 int *status) { |
| 338 int tmp_status = 0; |
| 339 pid_t pid; |
| 340 |
| 341 NaClLog(2, |
| 342 ("NaClProcessGetStatus(0x%08"NACL_PRIxPTR |
| 343 ", 0x%08"NACL_PRIxPTR")\n"), |
| 344 (uintptr_t) npp, (uintptr_t) status); |
| 345 |
| 346 CHECK(npp != NULL); |
| 347 |
| 348 NaClLog(4, |
| 349 "NaClProcessGetStatus: checking status of process %d\n", |
| 350 (int) npp->pid); |
| 351 |
| 352 pid = waitpid(npp->pid, &tmp_status, WNOHANG); |
| 353 if (pid == -1) { |
| 354 NaClLog(LOG_ERROR, |
| 355 "NaClProcessGetStatus: waitpid(%d) returned error %d\n", |
| 356 npp->pid, errno); |
| 357 return 0; |
| 358 } else if (pid == 0) { |
| 359 *status = NACL_PROCESS_STATUS_STILL_RUNNING; |
| 360 goto done; |
| 361 } |
| 362 |
| 363 if (WIFSIGNALED(tmp_status)) { |
| 364 switch (WTERMSIG(tmp_status)) { |
| 365 case SIGABRT: |
| 366 case SIGBUS: |
| 367 case SIGFPE: |
| 368 case SIGILL: |
| 369 case SIGSEGV: |
| 370 *status = NACL_PROCESS_STATUS_CRASHED; |
| 371 goto done; |
| 372 case SIGINT: |
| 373 case SIGKILL: |
| 374 case SIGTERM: |
| 375 *status = NACL_PROCESS_STATUS_KILLED; |
| 376 goto done; |
| 377 default: |
| 378 break; |
| 379 } |
| 380 } |
| 381 |
| 382 if (WIFEXITED(tmp_status) != 0 && WEXITSTATUS(tmp_status) != 0) { |
| 383 *status = NACL_PROCESS_STATUS_ABNORMAL_EXIT; |
| 384 goto done; |
| 385 } |
| 386 *status = NACL_PROCESS_STATUS_NORMAL_EXIT; |
| 387 |
| 388 done: |
| 389 return 1; |
| 390 } |
| 391 |
| 392 int NaClProcessWaitForExitCode(struct NaClProcess *npp, |
| 393 int *exit_code) { |
| 394 int status; |
| 395 |
| 396 NaClLog(2, |
| 397 ("NaClProcessWaitForExitCode(0x%08"NACL_PRIxPTR |
| 398 ", 0x%08"NACL_PRIxPTR")\n"), |
| 399 (uintptr_t) npp, (uintptr_t) exit_code); |
| 400 |
| 401 CHECK(npp != NULL); |
| 402 |
| 403 if (-1 == waitpid(npp->pid, &status, 0)) { |
| 404 return 0; |
| 405 } |
| 406 |
| 407 if (WIFEXITED(status) != 0) { |
| 408 *exit_code = WEXITSTATUS(status); |
| 409 return 1; |
| 410 } |
| 411 |
| 412 /* Check whether the process signaled */ |
| 413 CHECK(WIFSIGNALED(status) != 0); |
| 414 return 0; |
| 415 } |
OLD | NEW |