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