]>
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 | static const char *const __rcs_file_version__ = "$Revision: 23408 $"; | |
22 | ||
23 | #include "config.h" | |
24 | #include "launchd.h" | |
25 | ||
26 | #include <Security/Authorization.h> | |
27 | #include <Security/AuthorizationTags.h> | |
28 | #include <Security/AuthSession.h> | |
29 | #include <sys/types.h> | |
30 | #include <sys/queue.h> | |
31 | #include <sys/event.h> | |
32 | #include <sys/stat.h> | |
33 | #include <sys/ucred.h> | |
34 | #include <sys/fcntl.h> | |
35 | #include <sys/un.h> | |
36 | #include <sys/wait.h> | |
37 | #include <sys/sysctl.h> | |
38 | #include <sys/sockio.h> | |
39 | #include <sys/time.h> | |
40 | #include <sys/resource.h> | |
41 | #include <sys/ioctl.h> | |
42 | #include <sys/mount.h> | |
43 | #include <sys/kern_event.h> | |
44 | #include <sys/reboot.h> | |
45 | #include <net/if.h> | |
46 | #include <netinet/in.h> | |
47 | #include <netinet/in_var.h> | |
48 | #include <netinet6/nd6.h> | |
49 | #include <ifaddrs.h> | |
50 | #include <unistd.h> | |
51 | #include <signal.h> | |
52 | #include <errno.h> | |
53 | #include <libgen.h> | |
54 | #include <stdio.h> | |
55 | #include <stdlib.h> | |
56 | #include <stdarg.h> | |
57 | #include <stdbool.h> | |
58 | #include <paths.h> | |
59 | #include <pwd.h> | |
60 | #include <grp.h> | |
61 | #include <ttyent.h> | |
62 | #include <dlfcn.h> | |
63 | #include <dirent.h> | |
64 | #include <string.h> | |
65 | #include <setjmp.h> | |
66 | #include <spawn.h> | |
67 | #include <sched.h> | |
68 | ||
69 | #include "libbootstrap_public.h" | |
70 | #include "libvproc_public.h" | |
71 | #include "libvproc_internal.h" | |
72 | #include "liblaunch_public.h" | |
73 | ||
74 | #include "launchd_runtime.h" | |
75 | #include "launchd_core_logic.h" | |
76 | #include "launchd_unix_ipc.h" | |
77 | ||
78 | #define LAUNCHD_CONF ".launchd.conf" | |
79 | #define SECURITY_LIB "/System/Library/Frameworks/Security.framework/Versions/A/Security" | |
80 | #define SHUTDOWN_LOG_DIR "/var/log/shutdown" | |
81 | ||
82 | ||
83 | extern char **environ; | |
84 | ||
85 | static void pfsystem_callback(void *, struct kevent *); | |
86 | ||
87 | static kq_callback kqpfsystem_callback = pfsystem_callback; | |
88 | ||
89 | static void pid1_magic_init(void); | |
90 | ||
91 | static void testfd_or_openfd(int fd, const char *path, int flags); | |
92 | static bool get_network_state(void); | |
93 | static void monitor_networking_state(void); | |
94 | static void fatal_signal_handler(int sig, siginfo_t *si, void *uap); | |
95 | static void handle_pid1_crashes_separately(void); | |
96 | static void prep_shutdown_log_dir(void); | |
97 | ||
98 | static bool re_exec_in_single_user_mode = false; | |
99 | static void *crash_addr; | |
100 | static pid_t crash_pid; | |
101 | ||
102 | static bool shutdown_in_progress = false; | |
103 | bool debug_shutdown_hangs = false; | |
104 | bool network_up = false; | |
105 | ||
106 | int | |
107 | main(int argc, char *const *argv) | |
108 | { | |
109 | bool sflag = false; | |
110 | int ch; | |
111 | ||
112 | testfd_or_openfd(STDIN_FILENO, _PATH_DEVNULL, O_RDONLY); | |
113 | testfd_or_openfd(STDOUT_FILENO, _PATH_DEVNULL, O_WRONLY); | |
114 | testfd_or_openfd(STDERR_FILENO, _PATH_DEVNULL, O_WRONLY); | |
115 | ||
116 | while ((ch = getopt(argc, argv, "s")) != -1) { | |
117 | switch (ch) { | |
118 | case 's': sflag = true; break; /* single user */ | |
119 | case '?': /* we should do something with the global optopt variable here */ | |
120 | default: | |
121 | fprintf(stderr, "%s: ignoring unknown arguments\n", getprogname()); | |
122 | break; | |
123 | } | |
124 | } | |
125 | ||
126 | if (getpid() != 1 && getppid() != 1) { | |
127 | fprintf(stderr, "%s: This program is not meant to be run directly.\n", getprogname()); | |
128 | exit(EXIT_FAILURE); | |
129 | } | |
130 | ||
131 | launchd_runtime_init(); | |
132 | ||
133 | if (NULL == getenv("PATH")) { | |
134 | setenv("PATH", _PATH_STDPATH, 1); | |
135 | } | |
136 | ||
137 | if (getpid() == 1) { | |
138 | pid1_magic_init(); | |
139 | } else { | |
140 | ipc_server_init(); | |
141 | } | |
142 | ||
143 | monitor_networking_state(); | |
144 | ||
145 | if (getpid() == 1) { | |
146 | handle_pid1_crashes_separately(); | |
147 | } | |
148 | ||
149 | jobmgr_init(sflag); | |
150 | ||
151 | launchd_runtime_init2(); | |
152 | ||
153 | launchd_runtime(); | |
154 | } | |
155 | ||
156 | void | |
157 | handle_pid1_crashes_separately(void) | |
158 | { | |
159 | struct sigaction fsa; | |
160 | ||
161 | fsa.sa_sigaction = fatal_signal_handler; | |
162 | fsa.sa_flags = SA_SIGINFO; | |
163 | sigemptyset(&fsa.sa_mask); | |
164 | ||
165 | launchd_assumes(sigaction(SIGILL, &fsa, NULL) != -1); | |
166 | launchd_assumes(sigaction(SIGFPE, &fsa, NULL) != -1); | |
167 | launchd_assumes(sigaction(SIGBUS, &fsa, NULL) != -1); | |
168 | launchd_assumes(sigaction(SIGSEGV, &fsa, NULL) != -1); | |
169 | } | |
170 | ||
171 | #define PID1_CRASH_LOGFILE "/var/log/launchd-pid1.crash" | |
172 | ||
173 | /* This hack forces the dynamic linker to resolve these symbols ASAP */ | |
174 | static __attribute__((unused)) typeof(sync) *__junk_dyld_trick1 = sync; | |
175 | static __attribute__((unused)) typeof(sleep) *__junk_dyld_trick2 = sleep; | |
176 | static __attribute__((unused)) typeof(reboot) *__junk_dyld_trick3 = reboot; | |
177 | ||
178 | void | |
179 | fatal_signal_handler(int sig, siginfo_t *si, void *uap) | |
180 | { | |
181 | const char *doom_why = "at instruction"; | |
182 | char *sample_args[] = { "/usr/bin/sample", "1", "1", "-file", PID1_CRASH_LOGFILE, NULL }; | |
183 | pid_t sample_p; | |
184 | int wstatus; | |
185 | ||
186 | crash_addr = si->si_addr; | |
187 | crash_pid = si->si_pid; | |
188 | ||
189 | unlink(PID1_CRASH_LOGFILE); | |
190 | ||
191 | switch ((sample_p = vfork())) { | |
192 | case 0: | |
193 | execve(sample_args[0], sample_args, environ); | |
194 | _exit(EXIT_FAILURE); | |
195 | break; | |
196 | default: | |
197 | waitpid(sample_p, &wstatus, 0); | |
198 | break; | |
199 | case -1: | |
200 | break; | |
201 | } | |
202 | ||
203 | switch (sig) { | |
204 | default: | |
205 | case 0: | |
206 | break; | |
207 | case SIGBUS: | |
208 | case SIGSEGV: | |
209 | doom_why = "trying to read/write"; | |
210 | case SIGILL: | |
211 | case SIGFPE: | |
212 | runtime_syslog(LOG_EMERG, "We crashed %s: %p (sent by PID %u)", doom_why, crash_addr, crash_pid); | |
213 | sync(); | |
214 | sleep(3); | |
215 | reboot(0); | |
216 | break; | |
217 | } | |
218 | } | |
219 | ||
220 | void | |
221 | pid1_magic_init(void) | |
222 | { | |
223 | launchd_assumes(setsid() != -1); | |
224 | launchd_assumes(chdir("/") != -1); | |
225 | launchd_assumes(setlogin("root") != -1); | |
226 | launchd_assumes(mount("fdesc", "/dev", MNT_UNION, NULL) != -1); | |
227 | } | |
228 | ||
229 | ||
230 | int | |
231 | _fd(int fd) | |
232 | { | |
233 | if (fd >= 0) { | |
234 | launchd_assumes(fcntl(fd, F_SETFD, 1) != -1); | |
235 | } | |
236 | return fd; | |
237 | } | |
238 | ||
239 | void | |
240 | prep_shutdown_log_dir(void) | |
241 | { | |
242 | launchd_assumes(mkdir(SHUTDOWN_LOG_DIR, S_IRWXU) != -1 || errno == EEXIST); | |
243 | } | |
244 | ||
245 | void | |
246 | launchd_shutdown(void) | |
247 | { | |
248 | struct stat sb; | |
249 | ||
250 | if (shutdown_in_progress) { | |
251 | return; | |
252 | } | |
253 | ||
254 | shutdown_in_progress = true; | |
255 | ||
256 | if (getpid() == 1 && stat("/var/db/debugShutdownHangs", &sb) != -1) { | |
257 | /* | |
258 | * When this changes to a more sustainable API, update this: | |
259 | * http://howto.apple.com/db.cgi?Debugging_Apps_Non-Responsive_At_Shutdown | |
260 | */ | |
261 | runtime_setlogmask(LOG_UPTO(LOG_DEBUG)); | |
262 | prep_shutdown_log_dir(); | |
263 | debug_shutdown_hangs = true; | |
264 | } | |
265 | ||
266 | launchd_assert(jobmgr_shutdown(root_jobmgr) != NULL); | |
267 | } | |
268 | ||
269 | void | |
270 | launchd_single_user(void) | |
271 | { | |
272 | runtime_syslog(LOG_NOTICE, "Going to single-user mode"); | |
273 | ||
274 | re_exec_in_single_user_mode = true; | |
275 | ||
276 | launchd_shutdown(); | |
277 | ||
278 | sleep(3); | |
279 | ||
280 | runtime_kill(-1, SIGKILL); | |
281 | } | |
282 | ||
283 | void | |
284 | launchd_SessionCreate(void) | |
285 | { | |
286 | OSStatus (*sescr)(SessionCreationFlags flags, SessionAttributeBits attributes); | |
287 | void *seclib; | |
288 | ||
289 | if (launchd_assumes((seclib = dlopen(SECURITY_LIB, RTLD_LAZY)) != NULL)) { | |
290 | if (launchd_assumes((sescr = dlsym(seclib, "SessionCreate")) != NULL)) { | |
291 | launchd_assumes(sescr(0, 0) == noErr); | |
292 | } | |
293 | launchd_assumes(dlclose(seclib) != -1); | |
294 | } | |
295 | } | |
296 | ||
297 | void | |
298 | testfd_or_openfd(int fd, const char *path, int flags) | |
299 | { | |
300 | int tmpfd; | |
301 | ||
302 | if (-1 != (tmpfd = dup(fd))) { | |
303 | launchd_assumes(runtime_close(tmpfd) == 0); | |
304 | } else { | |
305 | if (-1 == (tmpfd = open(path, flags | O_NOCTTY, DEFFILEMODE))) { | |
306 | runtime_syslog(LOG_ERR, "open(\"%s\", ...): %m", path); | |
307 | } else if (tmpfd != fd) { | |
308 | launchd_assumes(dup2(tmpfd, fd) != -1); | |
309 | launchd_assumes(runtime_close(tmpfd) == 0); | |
310 | } | |
311 | } | |
312 | } | |
313 | ||
314 | bool | |
315 | get_network_state(void) | |
316 | { | |
317 | struct ifaddrs *ifa, *ifai; | |
318 | bool up = false; | |
319 | int r; | |
320 | ||
321 | /* Workaround 4978696: getifaddrs() reports false ENOMEM */ | |
322 | while ((r = getifaddrs(&ifa)) == -1 && errno == ENOMEM) { | |
323 | runtime_syslog(LOG_DEBUG, "Worked around bug: 4978696"); | |
324 | launchd_assumes(sched_yield() != -1); | |
325 | } | |
326 | ||
327 | if (!launchd_assumes(r != -1)) { | |
328 | return network_up; | |
329 | } | |
330 | ||
331 | for (ifai = ifa; ifai; ifai = ifai->ifa_next) { | |
332 | if (!(ifai->ifa_flags & IFF_UP)) { | |
333 | continue; | |
334 | } | |
335 | if (ifai->ifa_flags & IFF_LOOPBACK) { | |
336 | continue; | |
337 | } | |
338 | if (ifai->ifa_addr->sa_family != AF_INET && ifai->ifa_addr->sa_family != AF_INET6) { | |
339 | continue; | |
340 | } | |
341 | up = true; | |
342 | break; | |
343 | } | |
344 | ||
345 | freeifaddrs(ifa); | |
346 | ||
347 | return up; | |
348 | } | |
349 | ||
350 | void | |
351 | monitor_networking_state(void) | |
352 | { | |
353 | int pfs = _fd(socket(PF_SYSTEM, SOCK_RAW, SYSPROTO_EVENT)); | |
354 | struct kev_request kev_req; | |
355 | ||
356 | network_up = get_network_state(); | |
357 | ||
358 | if (!launchd_assumes(pfs != -1)) { | |
359 | return; | |
360 | } | |
361 | ||
362 | memset(&kev_req, 0, sizeof(kev_req)); | |
363 | kev_req.vendor_code = KEV_VENDOR_APPLE; | |
364 | kev_req.kev_class = KEV_NETWORK_CLASS; | |
365 | ||
366 | if (!launchd_assumes(ioctl(pfs, SIOCSKEVFILT, &kev_req) != -1)) { | |
367 | runtime_close(pfs); | |
368 | return; | |
369 | } | |
370 | ||
371 | launchd_assumes(kevent_mod(pfs, EVFILT_READ, EV_ADD, 0, 0, &kqpfsystem_callback) != -1); | |
372 | } | |
373 | ||
374 | void | |
375 | pfsystem_callback(void *obj, struct kevent *kev) | |
376 | { | |
377 | bool new_networking_state; | |
378 | char buf[1024]; | |
379 | ||
380 | launchd_assumes(read(kev->ident, &buf, sizeof(buf)) != -1); | |
381 | ||
382 | new_networking_state = get_network_state(); | |
383 | ||
384 | if (new_networking_state != network_up) { | |
385 | network_up = new_networking_state; | |
386 | jobmgr_dispatch_all_semaphores(root_jobmgr); | |
387 | } | |
388 | } | |
389 | ||
390 | void | |
391 | _log_launchd_bug(const char *rcs_rev, const char *path, unsigned int line, const char *test) | |
392 | { | |
393 | int saved_errno = errno; | |
394 | char buf[100]; | |
395 | const char *file = strrchr(path, '/'); | |
396 | char *rcs_rev_tmp = strchr(rcs_rev, ' '); | |
397 | ||
398 | if (!file) { | |
399 | file = path; | |
400 | } else { | |
401 | file += 1; | |
402 | } | |
403 | ||
404 | if (!rcs_rev_tmp) { | |
405 | strlcpy(buf, rcs_rev, sizeof(buf)); | |
406 | } else { | |
407 | strlcpy(buf, rcs_rev_tmp + 1, sizeof(buf)); | |
408 | rcs_rev_tmp = strchr(buf, ' '); | |
409 | if (rcs_rev_tmp) { | |
410 | *rcs_rev_tmp = '\0'; | |
411 | } | |
412 | } | |
413 | ||
414 | runtime_syslog(LOG_NOTICE, "Bug: %s:%u (%s):%u: %s", file, line, buf, saved_errno, test); | |
415 | } |