]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * Copyright (c) 2005 Apple Computer, Inc. All rights reserved. | |
3 | * | |
4 | * @APPLE_APACHE_LICENSE_HEADER_START@ | |
5 | * | |
6 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
7 | * you may not use this file except in compliance with the License. | |
8 | * You may obtain a copy of the License at | |
9 | * | |
10 | * http://www.apache.org/licenses/LICENSE-2.0 | |
11 | * | |
12 | * Unless required by applicable law or agreed to in writing, software | |
13 | * distributed under the License is distributed on an "AS IS" BASIS, | |
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
15 | * See the License for the specific language governing permissions and | |
16 | * limitations under the License. | |
17 | * | |
18 | * @APPLE_APACHE_LICENSE_HEADER_END@ | |
19 | */ | |
20 | ||
21 | #include "config.h" | |
22 | #include "launchd.h" | |
23 | ||
24 | #include <sys/types.h> | |
25 | #include <sys/queue.h> | |
26 | #include <sys/event.h> | |
27 | #include <sys/stat.h> | |
28 | #include <sys/ucred.h> | |
29 | #include <sys/fcntl.h> | |
30 | #include <sys/un.h> | |
31 | #include <sys/wait.h> | |
32 | #include <sys/sysctl.h> | |
33 | #include <sys/sockio.h> | |
34 | #include <sys/time.h> | |
35 | #include <sys/resource.h> | |
36 | #include <sys/ioctl.h> | |
37 | #include <sys/mount.h> | |
38 | #include <sys/kern_event.h> | |
39 | #include <sys/reboot.h> | |
40 | #include <sys/socket.h> | |
41 | #include <sys/syscall.h> | |
42 | #include <net/if.h> | |
43 | #include <netinet/in.h> | |
44 | #include <netinet/in_var.h> | |
45 | #include <netinet6/nd6.h> | |
46 | #include <ifaddrs.h> | |
47 | #include <unistd.h> | |
48 | #include <signal.h> | |
49 | #include <errno.h> | |
50 | #include <libgen.h> | |
51 | #include <stdio.h> | |
52 | #include <stdlib.h> | |
53 | #include <stdarg.h> | |
54 | #include <stdbool.h> | |
55 | #include <paths.h> | |
56 | #include <pwd.h> | |
57 | #include <grp.h> | |
58 | #include <ttyent.h> | |
59 | #include <dlfcn.h> | |
60 | #include <dirent.h> | |
61 | #include <string.h> | |
62 | #include <setjmp.h> | |
63 | #include <spawn.h> | |
64 | #include <sched.h> | |
65 | #include <pthread.h> | |
66 | #include <util.h> | |
67 | #include <os/assumes.h> | |
68 | ||
69 | #if HAVE_LIBAUDITD | |
70 | #include <bsm/auditd_lib.h> | |
71 | #include <bsm/audit_session.h> | |
72 | #endif | |
73 | ||
74 | #include "bootstrap.h" | |
75 | #include "vproc.h" | |
76 | #include "vproc_priv.h" | |
77 | #include "vproc_internal.h" | |
78 | #include "launch.h" | |
79 | #include "launch_internal.h" | |
80 | ||
81 | #include "runtime.h" | |
82 | #include "core.h" | |
83 | #include "ipc.h" | |
84 | ||
85 | #define LAUNCHD_CONF ".launchd.conf" | |
86 | ||
87 | extern char **environ; | |
88 | ||
89 | static void pfsystem_callback(void *, struct kevent *); | |
90 | ||
91 | static kq_callback kqpfsystem_callback = pfsystem_callback; | |
92 | ||
93 | static void pid1_magic_init(void); | |
94 | ||
95 | static void testfd_or_openfd(int fd, const char *path, int flags); | |
96 | static bool get_network_state(void); | |
97 | static void monitor_networking_state(void); | |
98 | static void fatal_signal_handler(int sig, siginfo_t *si, void *uap); | |
99 | static void handle_pid1_crashes_separately(void); | |
100 | static void do_pid1_crash_diagnosis_mode(const char *msg); | |
101 | static int basic_fork(void); | |
102 | static bool do_pid1_crash_diagnosis_mode2(const char *msg); | |
103 | ||
104 | static void *update_thread(void *nothing); | |
105 | ||
106 | static void *crash_addr; | |
107 | static pid_t crash_pid; | |
108 | ||
109 | char *_launchd_database_dir; | |
110 | char *_launchd_log_dir; | |
111 | ||
112 | bool launchd_shutting_down; | |
113 | bool network_up; | |
114 | uid_t launchd_uid; | |
115 | FILE *launchd_console = NULL; | |
116 | int32_t launchd_sync_frequency = 30; | |
117 | ||
118 | int | |
119 | main(int argc, char *const *argv) | |
120 | { | |
121 | bool sflag = false; | |
122 | int ch; | |
123 | ||
124 | /* This needs to be cleaned up. Currently, we risk tripping assumes() macros | |
125 | * before we've properly set things like launchd's log database paths, the | |
126 | * global launchd label for syslog messages and the like. Luckily, these are | |
127 | * operations that will probably never fail, like test_of_openfd(), the | |
128 | * stuff in launchd_runtime_init() and the stuff in | |
129 | * handle_pid1_crashes_separately(). | |
130 | */ | |
131 | testfd_or_openfd(STDIN_FILENO, _PATH_DEVNULL, O_RDONLY); | |
132 | testfd_or_openfd(STDOUT_FILENO, _PATH_DEVNULL, O_WRONLY); | |
133 | testfd_or_openfd(STDERR_FILENO, _PATH_DEVNULL, O_WRONLY); | |
134 | ||
135 | if (launchd_use_gmalloc) { | |
136 | if (!getenv("DYLD_INSERT_LIBRARIES")) { | |
137 | setenv("DYLD_INSERT_LIBRARIES", "/usr/lib/libgmalloc.dylib", 1); | |
138 | setenv("MALLOC_STRICT_SIZE", "1", 1); | |
139 | execv(argv[0], argv); | |
140 | } else { | |
141 | unsetenv("DYLD_INSERT_LIBRARIES"); | |
142 | unsetenv("MALLOC_STRICT_SIZE"); | |
143 | } | |
144 | } else if (launchd_malloc_log_stacks) { | |
145 | if (!getenv("MallocStackLogging")) { | |
146 | setenv("MallocStackLogging", "1", 1); | |
147 | execv(argv[0], argv); | |
148 | } else { | |
149 | unsetenv("MallocStackLogging"); | |
150 | } | |
151 | } | |
152 | ||
153 | while ((ch = getopt(argc, argv, "s")) != -1) { | |
154 | switch (ch) { | |
155 | case 's': sflag = true; break; /* single user */ | |
156 | case '?': /* we should do something with the global optopt variable here */ | |
157 | default: | |
158 | fprintf(stderr, "%s: ignoring unknown arguments\n", getprogname()); | |
159 | break; | |
160 | } | |
161 | } | |
162 | ||
163 | if (getpid() != 1 && getppid() != 1) { | |
164 | fprintf(stderr, "%s: This program is not meant to be run directly.\n", getprogname()); | |
165 | exit(EXIT_FAILURE); | |
166 | } | |
167 | ||
168 | launchd_runtime_init(); | |
169 | ||
170 | if (NULL == getenv("PATH")) { | |
171 | setenv("PATH", _PATH_STDPATH, 1); | |
172 | } | |
173 | ||
174 | if (pid1_magic) { | |
175 | pid1_magic_init(); | |
176 | ||
177 | int cfd = -1; | |
178 | if ((cfd = open(_PATH_CONSOLE, O_WRONLY | O_NOCTTY)) != -1) { | |
179 | _fd(cfd); | |
180 | if (!(launchd_console = fdopen(cfd, "w"))) { | |
181 | (void)close(cfd); | |
182 | } | |
183 | } | |
184 | ||
185 | char *extra = ""; | |
186 | if (launchd_osinstaller) { | |
187 | extra = " in the OS Installer"; | |
188 | } else if (sflag) { | |
189 | extra = " in single-user mode"; | |
190 | } | |
191 | ||
192 | launchd_syslog(LOG_NOTICE | LOG_CONSOLE, "*** launchd[1] has started up%s. ***", extra); | |
193 | if (launchd_use_gmalloc) { | |
194 | launchd_syslog(LOG_NOTICE | LOG_CONSOLE, "*** Using libgmalloc. ***"); | |
195 | } | |
196 | ||
197 | if (launchd_verbose_boot) { | |
198 | launchd_syslog(LOG_NOTICE | LOG_CONSOLE, "*** Verbose boot, will log to /dev/console. ***"); | |
199 | } | |
200 | ||
201 | if (launchd_shutdown_debugging) { | |
202 | launchd_syslog(LOG_NOTICE | LOG_CONSOLE, "*** Shutdown debugging is enabled. ***"); | |
203 | } | |
204 | ||
205 | if (launchd_log_shutdown) { | |
206 | launchd_syslog(LOG_NOTICE | LOG_CONSOLE, "*** Shutdown logging is enabled. ***"); | |
207 | } | |
208 | ||
209 | if (launchd_log_perf) { | |
210 | launchd_syslog(LOG_NOTICE | LOG_CONSOLE, "*** Performance logging is enabled. ***"); | |
211 | } | |
212 | ||
213 | if (launchd_log_debug) { | |
214 | launchd_syslog(LOG_NOTICE | LOG_CONSOLE, "*** Debug logging is enabled. ***"); | |
215 | } | |
216 | ||
217 | handle_pid1_crashes_separately(); | |
218 | ||
219 | /* Start the update thread. | |
220 | * | |
221 | * <rdar://problem/5039559&6153301> | |
222 | */ | |
223 | pthread_t t = NULL; | |
224 | (void)os_assumes_zero(pthread_create(&t, NULL, update_thread, NULL)); | |
225 | (void)os_assumes_zero(pthread_detach(t)); | |
226 | ||
227 | /* PID 1 doesn't have a flat namespace. */ | |
228 | launchd_flat_mach_namespace = false; | |
229 | fflush(launchd_console); | |
230 | } else { | |
231 | launchd_uid = getuid(); | |
232 | launchd_var_available = true; | |
233 | if (asprintf(&launchd_label, "com.apple.launchd.peruser.%u", launchd_uid) == 0) { | |
234 | launchd_label = "com.apple.launchd.peruser.unknown"; | |
235 | } | |
236 | ||
237 | struct passwd *pwent = getpwuid(launchd_uid); | |
238 | if (pwent) { | |
239 | launchd_username = strdup(pwent->pw_name); | |
240 | } else { | |
241 | launchd_username = "(unknown)"; | |
242 | } | |
243 | ||
244 | if (asprintf(&_launchd_database_dir, LAUNCHD_DB_PREFIX "/com.apple.launchd.peruser.%u", launchd_uid) == 0) { | |
245 | _launchd_database_dir = ""; | |
246 | } | |
247 | ||
248 | if (asprintf(&_launchd_log_dir, LAUNCHD_LOG_PREFIX "/com.apple.launchd.peruser.%u", launchd_uid) == 0) { | |
249 | _launchd_log_dir = ""; | |
250 | } | |
251 | ||
252 | if (launchd_allow_global_dyld_envvars) { | |
253 | launchd_syslog(LOG_WARNING, "Per-user launchd will allow DYLD_* environment variables in the global environment."); | |
254 | } | |
255 | ||
256 | ipc_server_init(); | |
257 | launchd_log_push(); | |
258 | ||
259 | auditinfo_addr_t auinfo; | |
260 | if (posix_assumes_zero(getaudit_addr(&auinfo, sizeof(auinfo))) != -1) { | |
261 | launchd_audit_session = auinfo.ai_asid; | |
262 | launchd_syslog(LOG_DEBUG, "Our audit session ID is %i", launchd_audit_session); | |
263 | } | |
264 | ||
265 | launchd_audit_port = _audit_session_self(); | |
266 | ||
267 | vproc_transaction_begin(NULL); | |
268 | vproc_transaction_end(NULL, NULL); | |
269 | ||
270 | launchd_syslog(LOG_DEBUG, "Per-user launchd started (UID/username): %u/%s.", launchd_uid, launchd_username); | |
271 | } | |
272 | ||
273 | monitor_networking_state(); | |
274 | jobmgr_init(sflag); | |
275 | ||
276 | launchd_runtime_init2(); | |
277 | launchd_runtime(); | |
278 | } | |
279 | ||
280 | void | |
281 | handle_pid1_crashes_separately(void) | |
282 | { | |
283 | struct sigaction fsa; | |
284 | ||
285 | fsa.sa_sigaction = fatal_signal_handler; | |
286 | fsa.sa_flags = SA_SIGINFO; | |
287 | sigemptyset(&fsa.sa_mask); | |
288 | ||
289 | (void)posix_assumes_zero(sigaction(SIGILL, &fsa, NULL)); | |
290 | (void)posix_assumes_zero(sigaction(SIGFPE, &fsa, NULL)); | |
291 | (void)posix_assumes_zero(sigaction(SIGBUS, &fsa, NULL)); | |
292 | (void)posix_assumes_zero(sigaction(SIGTRAP, &fsa, NULL)); | |
293 | (void)posix_assumes_zero(sigaction(SIGABRT, &fsa, NULL)); | |
294 | (void)posix_assumes_zero(sigaction(SIGSEGV, &fsa, NULL)); | |
295 | } | |
296 | ||
297 | void * | |
298 | update_thread(void *nothing __attribute__((unused))) | |
299 | { | |
300 | (void)posix_assumes_zero(setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_THREAD, IOPOL_THROTTLE)); | |
301 | ||
302 | while (launchd_sync_frequency) { | |
303 | sync(); | |
304 | sleep(launchd_sync_frequency); | |
305 | } | |
306 | ||
307 | launchd_syslog(LOG_DEBUG, "Update thread exiting."); | |
308 | return NULL; | |
309 | } | |
310 | ||
311 | #define PID1_CRASH_LOGFILE "/var/log/launchd-pid1.crash" | |
312 | ||
313 | /* This hack forces the dynamic linker to resolve these symbols ASAP */ | |
314 | static __attribute__((unused)) typeof(sync) *__junk_dyld_trick1 = sync; | |
315 | static __attribute__((unused)) typeof(sleep) *__junk_dyld_trick2 = sleep; | |
316 | static __attribute__((unused)) typeof(reboot) *__junk_dyld_trick3 = reboot; | |
317 | ||
318 | void | |
319 | do_pid1_crash_diagnosis_mode(const char *msg) | |
320 | { | |
321 | if (launchd_wsp) { | |
322 | kill(launchd_wsp, SIGKILL); | |
323 | sleep(3); | |
324 | launchd_wsp = 0; | |
325 | } | |
326 | ||
327 | while (launchd_shutdown_debugging && !do_pid1_crash_diagnosis_mode2(msg)) { | |
328 | sleep(1); | |
329 | } | |
330 | } | |
331 | ||
332 | int | |
333 | basic_fork(void) | |
334 | { | |
335 | int wstatus = 0; | |
336 | pid_t p; | |
337 | ||
338 | switch ((p = fork())) { | |
339 | case -1: | |
340 | launchd_syslog(LOG_ERR | LOG_CONSOLE, "Can't fork PID 1 copy for crash debugging: %m"); | |
341 | return p; | |
342 | case 0: | |
343 | return p; | |
344 | default: | |
345 | do { | |
346 | (void)waitpid(p, &wstatus, 0); | |
347 | } while(!WIFEXITED(wstatus)); | |
348 | ||
349 | fprintf(stdout, "PID 1 copy: exit status: %d\n", WEXITSTATUS(wstatus)); | |
350 | ||
351 | return 1; | |
352 | } | |
353 | ||
354 | return -1; | |
355 | } | |
356 | ||
357 | bool | |
358 | do_pid1_crash_diagnosis_mode2(const char *msg) | |
359 | { | |
360 | if (basic_fork() == 0) { | |
361 | /* Neuter our bootstrap port so that the shell doesn't try talking to us | |
362 | * while we're blocked waiting on it. | |
363 | */ | |
364 | if (launchd_console) { | |
365 | fflush(launchd_console); | |
366 | } | |
367 | ||
368 | task_set_bootstrap_port(mach_task_self(), MACH_PORT_NULL); | |
369 | if (basic_fork() != 0) { | |
370 | if (launchd_console) { | |
371 | fflush(launchd_console); | |
372 | } | |
373 | ||
374 | return true; | |
375 | } | |
376 | } else { | |
377 | return true; | |
378 | } | |
379 | ||
380 | int fd; | |
381 | revoke(_PATH_CONSOLE); | |
382 | if ((fd = open(_PATH_CONSOLE, O_RDWR)) == -1) { | |
383 | _exit(2); | |
384 | } | |
385 | if (login_tty(fd) == -1) { | |
386 | _exit(3); | |
387 | } | |
388 | ||
389 | setenv("TERM", "vt100", 1); | |
390 | fprintf(stdout, "\n"); | |
391 | fprintf(stdout, "Entering launchd PID 1 debugging mode...\n"); | |
392 | fprintf(stdout, "The PID 1 launchd has crashed %s.\n", msg); | |
393 | fprintf(stdout, "It has fork(2)ed itself for debugging.\n"); | |
394 | fprintf(stdout, "To debug the crashing thread of PID 1:\n"); | |
395 | fprintf(stdout, " gdb attach %d\n", getppid()); | |
396 | fprintf(stdout, "To exit this shell and shut down:\n"); | |
397 | fprintf(stdout, " kill -9 1\n"); | |
398 | fprintf(stdout, "A sample of PID 1 has been written to %s\n", PID1_CRASH_LOGFILE); | |
399 | fprintf(stdout, "\n"); | |
400 | fflush(stdout); | |
401 | ||
402 | execl(_PATH_BSHELL, "-sh", NULL); | |
403 | syslog(LOG_ERR, "can't exec %s for PID 1 crash debugging: %m", _PATH_BSHELL); | |
404 | _exit(EXIT_FAILURE); | |
405 | } | |
406 | ||
407 | void | |
408 | fatal_signal_handler(int sig, siginfo_t *si, void *uap __attribute__((unused))) | |
409 | { | |
410 | const char *doom_why = "at instruction"; | |
411 | char msg[128]; | |
412 | #if 0 | |
413 | char *sample_args[] = { "/usr/bin/sample", "1", "1", "-file", PID1_CRASH_LOGFILE, NULL }; | |
414 | pid_t sample_p; | |
415 | int wstatus; | |
416 | #endif | |
417 | ||
418 | crash_addr = si->si_addr; | |
419 | crash_pid = si->si_pid; | |
420 | #if 0 | |
421 | setenv("XPC_SERVICES_UNAVAILABLE", "1", 0); | |
422 | unlink(PID1_CRASH_LOGFILE); | |
423 | ||
424 | switch ((sample_p = vfork())) { | |
425 | case 0: | |
426 | execve(sample_args[0], sample_args, environ); | |
427 | _exit(EXIT_FAILURE); | |
428 | break; | |
429 | default: | |
430 | waitpid(sample_p, &wstatus, 0); | |
431 | break; | |
432 | case -1: | |
433 | break; | |
434 | } | |
435 | #endif | |
436 | switch (sig) { | |
437 | default: | |
438 | case 0: | |
439 | break; | |
440 | case SIGBUS: | |
441 | case SIGSEGV: | |
442 | doom_why = "trying to read/write"; | |
443 | case SIGILL: | |
444 | case SIGFPE: | |
445 | case SIGTRAP: | |
446 | snprintf(msg, sizeof(msg), "%s: %p (%s sent by PID %u)", doom_why, crash_addr, strsignal(sig), crash_pid); | |
447 | sync(); | |
448 | do_pid1_crash_diagnosis_mode(msg); | |
449 | sleep(3); | |
450 | reboot(0); | |
451 | break; | |
452 | } | |
453 | } | |
454 | ||
455 | void | |
456 | pid1_magic_init(void) | |
457 | { | |
458 | launchd_label = "com.apple.launchd"; | |
459 | launchd_username = "system"; | |
460 | ||
461 | _launchd_database_dir = LAUNCHD_DB_PREFIX "/com.apple.launchd"; | |
462 | _launchd_log_dir = LAUNCHD_LOG_PREFIX "/com.apple.launchd"; | |
463 | ||
464 | (void)posix_assumes_zero(setsid()); | |
465 | (void)posix_assumes_zero(chdir("/")); | |
466 | (void)posix_assumes_zero(setlogin("root")); | |
467 | ||
468 | #if !TARGET_OS_EMBEDDED | |
469 | auditinfo_addr_t auinfo = { | |
470 | .ai_termid = { | |
471 | .at_type = AU_IPv4 | |
472 | }, | |
473 | .ai_asid = AU_ASSIGN_ASID, | |
474 | .ai_auid = AU_DEFAUDITID, | |
475 | .ai_flags = AU_SESSION_FLAG_IS_INITIAL, | |
476 | }; | |
477 | ||
478 | if (setaudit_addr(&auinfo, sizeof(auinfo)) == -1) { | |
479 | launchd_syslog(LOG_WARNING | LOG_CONSOLE, "Could not set audit session: %d: %s.", errno, strerror(errno)); | |
480 | _exit(EXIT_FAILURE); | |
481 | } | |
482 | ||
483 | launchd_audit_session = auinfo.ai_asid; | |
484 | launchd_syslog(LOG_DEBUG, "Audit Session ID: %i", launchd_audit_session); | |
485 | ||
486 | launchd_audit_port = _audit_session_self(); | |
487 | #endif // !TARGET_OS_EMBEDDED | |
488 | } | |
489 | ||
490 | char * | |
491 | launchd_copy_persistent_store(int type, const char *file) | |
492 | { | |
493 | char *result = NULL; | |
494 | if (!file) { | |
495 | file = ""; | |
496 | } | |
497 | ||
498 | switch (type) { | |
499 | case LAUNCHD_PERSISTENT_STORE_DB: | |
500 | (void)asprintf(&result, "%s/%s", _launchd_database_dir, file); | |
501 | break; | |
502 | case LAUNCHD_PERSISTENT_STORE_LOGS: | |
503 | (void)asprintf(&result, "%s/%s", _launchd_log_dir, file); | |
504 | break; | |
505 | default: | |
506 | break; | |
507 | } | |
508 | ||
509 | return result; | |
510 | } | |
511 | ||
512 | int | |
513 | _fd(int fd) | |
514 | { | |
515 | if (fd >= 0) { | |
516 | (void)posix_assumes_zero(fcntl(fd, F_SETFD, 1)); | |
517 | } | |
518 | return fd; | |
519 | } | |
520 | ||
521 | void | |
522 | launchd_shutdown(void) | |
523 | { | |
524 | int64_t now; | |
525 | ||
526 | if (launchd_shutting_down) { | |
527 | return; | |
528 | } | |
529 | ||
530 | runtime_ktrace0(RTKT_LAUNCHD_EXITING); | |
531 | ||
532 | launchd_shutting_down = true; | |
533 | launchd_log_push(); | |
534 | ||
535 | now = runtime_get_wall_time(); | |
536 | ||
537 | char *term_who = pid1_magic ? "System shutdown" : "Per-user launchd termination for "; | |
538 | launchd_syslog(LOG_INFO, "%s%s began", term_who, pid1_magic ? "" : launchd_username); | |
539 | ||
540 | os_assert(jobmgr_shutdown(root_jobmgr) != NULL); | |
541 | ||
542 | #if HAVE_LIBAUDITD | |
543 | if (pid1_magic) { | |
544 | (void)os_assumes_zero(audit_quick_stop()); | |
545 | } | |
546 | #endif | |
547 | } | |
548 | ||
549 | void | |
550 | launchd_SessionCreate(void) | |
551 | { | |
552 | #if !TARGET_OS_EMBEDDED | |
553 | auditinfo_addr_t auinfo = { | |
554 | .ai_termid = { .at_type = AU_IPv4 }, | |
555 | .ai_asid = AU_ASSIGN_ASID, | |
556 | .ai_auid = getuid(), | |
557 | .ai_flags = 0, | |
558 | }; | |
559 | if (setaudit_addr(&auinfo, sizeof(auinfo)) == 0) { | |
560 | char session[16]; | |
561 | snprintf(session, sizeof(session), "%x", auinfo.ai_asid); | |
562 | setenv("SECURITYSESSIONID", session, 1); | |
563 | } else { | |
564 | launchd_syslog(LOG_WARNING, "Could not set audit session: %d: %s.", errno, strerror(errno)); | |
565 | } | |
566 | #endif // !TARGET_OS_EMBEDDED | |
567 | } | |
568 | ||
569 | void | |
570 | testfd_or_openfd(int fd, const char *path, int flags) | |
571 | { | |
572 | int tmpfd; | |
573 | ||
574 | if (-1 != (tmpfd = dup(fd))) { | |
575 | (void)posix_assumes_zero(runtime_close(tmpfd)); | |
576 | } else { | |
577 | if (-1 == (tmpfd = open(path, flags | O_NOCTTY, DEFFILEMODE))) { | |
578 | launchd_syslog(LOG_ERR, "open(\"%s\", ...): %m", path); | |
579 | } else if (tmpfd != fd) { | |
580 | (void)posix_assumes_zero(dup2(tmpfd, fd)); | |
581 | (void)posix_assumes_zero(runtime_close(tmpfd)); | |
582 | } | |
583 | } | |
584 | } | |
585 | ||
586 | bool | |
587 | get_network_state(void) | |
588 | { | |
589 | struct ifaddrs *ifa, *ifai; | |
590 | bool up = false; | |
591 | int r; | |
592 | ||
593 | /* Workaround 4978696: getifaddrs() reports false ENOMEM */ | |
594 | while ((r = getifaddrs(&ifa)) == -1 && errno == ENOMEM) { | |
595 | launchd_syslog(LOG_DEBUG, "Worked around bug: 4978696"); | |
596 | (void)posix_assumes_zero(sched_yield()); | |
597 | } | |
598 | ||
599 | if (posix_assumes_zero(r) == -1) { | |
600 | return network_up; | |
601 | } | |
602 | ||
603 | for (ifai = ifa; ifai; ifai = ifai->ifa_next) { | |
604 | if (!(ifai->ifa_flags & IFF_UP)) { | |
605 | continue; | |
606 | } | |
607 | if (ifai->ifa_flags & IFF_LOOPBACK) { | |
608 | continue; | |
609 | } | |
610 | if (ifai->ifa_addr->sa_family != AF_INET && ifai->ifa_addr->sa_family != AF_INET6) { | |
611 | continue; | |
612 | } | |
613 | up = true; | |
614 | break; | |
615 | } | |
616 | ||
617 | freeifaddrs(ifa); | |
618 | ||
619 | return up; | |
620 | } | |
621 | ||
622 | void | |
623 | monitor_networking_state(void) | |
624 | { | |
625 | int pfs = _fd(socket(PF_SYSTEM, SOCK_RAW, SYSPROTO_EVENT)); | |
626 | struct kev_request kev_req; | |
627 | ||
628 | network_up = get_network_state(); | |
629 | ||
630 | if (pfs == -1) { | |
631 | (void)os_assumes_zero(errno); | |
632 | return; | |
633 | } | |
634 | ||
635 | memset(&kev_req, 0, sizeof(kev_req)); | |
636 | kev_req.vendor_code = KEV_VENDOR_APPLE; | |
637 | kev_req.kev_class = KEV_NETWORK_CLASS; | |
638 | ||
639 | if (posix_assumes_zero(ioctl(pfs, SIOCSKEVFILT, &kev_req)) == -1) { | |
640 | runtime_close(pfs); | |
641 | return; | |
642 | } | |
643 | ||
644 | (void)posix_assumes_zero(kevent_mod(pfs, EVFILT_READ, EV_ADD, 0, 0, &kqpfsystem_callback)); | |
645 | } | |
646 | ||
647 | void | |
648 | pfsystem_callback(void *obj __attribute__((unused)), struct kevent *kev) | |
649 | { | |
650 | bool new_networking_state; | |
651 | char buf[1024]; | |
652 | ||
653 | (void)posix_assumes_zero(read((int)kev->ident, &buf, sizeof(buf))); | |
654 | ||
655 | new_networking_state = get_network_state(); | |
656 | ||
657 | if (new_networking_state != network_up) { | |
658 | network_up = new_networking_state; | |
659 | jobmgr_dispatch_all_semaphores(root_jobmgr); | |
660 | } | |
661 | } |