X-Git-Url: https://git.saurik.com/apple/launchd.git/blobdiff_plain/ed34e3c3e5fb80e0702ac7fb92f189862089d820..dcace88fe6cde929a524daa9e1f1495bf1e24cfe:/launchd/src/launchd.c diff --git a/launchd/src/launchd.c b/launchd/src/launchd.c index e7acf68..78e1738 100644 --- a/launchd/src/launchd.c +++ b/launchd/src/launchd.c @@ -18,11 +18,11 @@ * @APPLE_APACHE_LICENSE_HEADER_END@ */ -static const char *const __rcs_file_version__ = "$Revision: 1.217 $"; +static const char *const __rcs_file_version__ = "$Revision: 24863 $"; + +#include "config.h" +#include "launchd.h" -#include -#include -#include #include #include #include @@ -38,6 +38,9 @@ static const char *const __rcs_file_version__ = "$Revision: 1.217 $"; #include #include #include +#include +#include +#include #include #include #include @@ -46,7 +49,6 @@ static const char *const __rcs_file_version__ = "$Revision: 1.217 $"; #include #include #include -#include #include #include #include @@ -59,501 +61,501 @@ static const char *const __rcs_file_version__ = "$Revision: 1.217 $"; #include #include #include +#include +#include +#include #include +#include -#include "bootstrap_public.h" -#include "bootstrap_private.h" +#if HAVE_LIBAUDITD +#include +#include +#endif + +#include "bootstrap.h" +#include "vproc.h" +#include "vproc_priv.h" +#include "vproc_internal.h" #include "launch.h" -#include "launch_priv.h" -#include "launchd.h" +#include "launch_internal.h" + +#include "launchd_runtime.h" #include "launchd_core_logic.h" #include "launchd_unix_ipc.h" -#include "launchd_internalServer.h" -#include "launchd_internal.h" -#include "notifyServer.h" -#include "bootstrapServer.h" - -union MaxRequestSize { - union __RequestUnion__do_notify_subsystem req; - union __ReplyUnion__do_notify_subsystem rep; - union __RequestUnion__x_launchd_internal_subsystem req2; - union __ReplyUnion__x_launchd_internal_subsystem rep2; - union __RequestUnion__x_bootstrap_subsystem req3; - union __ReplyUnion__x_bootstrap_subsystem rep3; -}; - -static boolean_t launchd_internal_demux(mach_msg_header_t *Request, mach_msg_header_t *Reply); - -#define PID1LAUNCHD_CONF "/etc/launchd.conf" #define LAUNCHD_CONF ".launchd.conf" -#define LAUNCHCTL_PATH "/bin/launchctl" -#define SECURITY_LIB "/System/Library/Frameworks/Security.framework/Versions/A/Security" extern char **environ; -static void async_callback(void); -static void signal_callback(void *, struct kevent *); -static void fs_callback(void); -static void ppidexit_callback(void); static void pfsystem_callback(void *, struct kevent *); -static kq_callback kqasync_callback = (kq_callback)async_callback; -static kq_callback kqsignal_callback = signal_callback; -static kq_callback kqfs_callback = (kq_callback)fs_callback; -static kq_callback kqppidexit_callback = (kq_callback)ppidexit_callback; static kq_callback kqpfsystem_callback = pfsystem_callback; -static void pid1_magic_init(bool sflag); - -static void usage(FILE *where); +static void pid1_magic_init(void); static void testfd_or_openfd(int fd, const char *path, int flags); static bool get_network_state(void); static void monitor_networking_state(void); -static void *kqueue_demand_loop(void *arg); - -static pthread_t kqueue_demand_thread; -static int mainkq = 0; -static int asynckq = 0; -static bool re_exec_in_single_user_mode = false; -static char *pending_stdout = NULL; -static char *pending_stderr = NULL; -static struct jobcb *rlcj = NULL; - -sigset_t blocked_signals = 0; -bool shutdown_in_progress = false; -bool network_up = false; -int batch_disabler_count = 0; -mach_port_t launchd_internal_port = MACH_PORT_NULL; -mach_port_t ipc_port_set = MACH_PORT_NULL; +static void fatal_signal_handler(int sig, siginfo_t *si, void *uap); +static void handle_pid1_crashes_separately(void); +static void do_pid1_crash_diagnosis_mode(const char *msg); +static int basic_fork(void); +static bool do_pid1_crash_diagnosis_mode2(const char *msg); + +static void *update_thread(void *nothing); + +static bool re_exec_in_single_user_mode; +static void *crash_addr; +static pid_t crash_pid; + +bool shutdown_in_progress; +bool fake_shutdown_in_progress; +bool network_up; +char g_username[128] = "__Uninitialized__"; +char g_my_label[128] = "__Uninitialized__"; +char g_launchd_database_dir[PATH_MAX]; +FILE *g_console = NULL; +int32_t g_sync_frequency = 30; int main(int argc, char *const *argv) { - static const int sigigns[] = { SIGHUP, SIGINT, SIGPIPE, SIGALRM, - SIGTERM, SIGURG, SIGTSTP, SIGTSTP, SIGCONT, /*SIGCHLD,*/ - SIGTTIN, SIGTTOU, SIGIO, SIGXCPU, SIGXFSZ, SIGVTALRM, SIGPROF, - SIGWINCH, SIGINFO, SIGUSR1, SIGUSR2 - }; - bool sflag = false, dflag = false, Dflag = false; - mach_msg_type_number_t l2l_name_cnt = 0, l2l_port_cnt = 0; - name_array_t l2l_names = NULL; - mach_port_array_t l2l_ports = NULL; - char ldconf[PATH_MAX] = PID1LAUNCHD_CONF; - const char *h = getenv("HOME"); - const char *session_type = NULL; - const char *optargs = NULL; - launch_data_t ldresp, ldmsg = launch_data_new_string(LAUNCH_KEY_CHECKIN); - struct jobcb *fbj = NULL; - struct stat sb; - size_t i, checkin_fdcnt = 0; - int *checkin_fds = NULL; - mach_port_t req_mport = MACH_PORT_NULL; - mach_port_t checkin_mport = MACH_PORT_NULL; - int ch, ker, logopts; - - /* main() phase one: sanitize the process */ - - if (getpid() != 1 && (ldresp = launch_msg(ldmsg)) && launch_data_get_type(ldresp) == LAUNCH_DATA_DICTIONARY) { - const char *ldlabel = launch_data_get_string(launch_data_dict_lookup(ldresp, LAUNCH_JOBKEY_LABEL)); - launch_data_t tmp; - - if ((tmp = launch_data_dict_lookup(ldresp, LAUNCH_JOBKEY_SOCKETS))) { - if ((tmp = launch_data_dict_lookup(tmp, "LaunchIPC"))) { - checkin_fdcnt = launch_data_array_get_count(tmp); - checkin_fds = alloca(sizeof(int) * checkin_fdcnt); - for (i = 0; i < checkin_fdcnt; i++) { - checkin_fds[i] = _fd(launch_data_get_fd(launch_data_array_get_index(tmp, i))); - } - } + bool sflag = false; + int ch; + + testfd_or_openfd(STDIN_FILENO, _PATH_DEVNULL, O_RDONLY); + testfd_or_openfd(STDOUT_FILENO, _PATH_DEVNULL, O_WRONLY); + testfd_or_openfd(STDERR_FILENO, _PATH_DEVNULL, O_WRONLY); + + if (g_use_gmalloc) { + if (!getenv("DYLD_INSERT_LIBRARIES")) { + setenv("DYLD_INSERT_LIBRARIES", "/usr/lib/libgmalloc.dylib", 1); + setenv("MALLOC_STRICT_SIZE", "1", 1); + execv(argv[0], argv); + } else { + unsetenv("DYLD_INSERT_LIBRARIES"); + unsetenv("MALLOC_STRICT_SIZE"); } - if ((tmp = launch_data_dict_lookup(ldresp, LAUNCH_JOBKEY_MACHSERVICES))) { - if ((tmp = launch_data_dict_lookup(tmp, ldlabel))) { - checkin_mport = launch_data_get_machport(tmp); - } + } else if (g_malloc_log_stacks) { + if (!getenv("MallocStackLogging")) { + setenv("MallocStackLogging", "1", 1); + execv(argv[0], argv); + } else { + unsetenv("MallocStackLogging"); } - launch_data_free(ldresp); - } else { - int sigi, fdi, dts = getdtablesize(); - sigset_t emptyset; - - for (fdi = STDERR_FILENO + 1; fdi < dts; fdi++) - close(fdi); - for (sigi = 1; sigi < NSIG; sigi++) - launchd_assumes(signal(sigi, SIG_DFL) != SIG_ERR); - sigemptyset(&emptyset); - launchd_assumes(sigprocmask(SIG_SETMASK, &emptyset, NULL) == 0); } - launch_data_free(ldmsg); - - testfd_or_openfd(STDIN_FILENO, _PATH_DEVNULL, O_RDONLY|O_NOCTTY); - testfd_or_openfd(STDOUT_FILENO, _PATH_DEVNULL, O_WRONLY|O_NOCTTY); - testfd_or_openfd(STDERR_FILENO, _PATH_DEVNULL, O_WRONLY|O_NOCTTY); - - /* main phase two: parse arguments */ - - if (getpid() == 1) { - optargs = "s"; - } else { - optargs = "DS:dh"; - } - - while ((ch = getopt(argc, argv, optargs)) != -1) { + while ((ch = getopt(argc, argv, "s")) != -1) { switch (ch) { - case 'S': session_type = optarg; break; /* what type of session we're creating */ - case 'D': Dflag = true; break; /* debug */ - case 'd': dflag = true; break; /* daemonize */ - case 's': sflag = true; break; /* single user */ - case 'h': usage(stdout); break; /* help */ + case 's': sflag = true; break; /* single user */ case '?': /* we should do something with the global optopt variable here */ default: - fprintf(stderr, "ignoring unknown arguments\n"); - usage(stderr); + fprintf(stderr, "%s: ignoring unknown arguments\n", getprogname()); break; } } - argc -= optind; - argv += optind; - - /* main phase three: get the party started */ - - if (dflag) - launchd_assumes(daemon(0, 0) == 0); - - launchd_assert((errno = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_PORT_SET, &ipc_port_set)) == KERN_SUCCESS); - launchd_assert(launchd_mport_create_recv(&launchd_internal_port) == KERN_SUCCESS); - launchd_assert(launchd_mport_make_send(launchd_internal_port) == KERN_SUCCESS); - launchd_assert((errno = mach_port_move_member(mach_task_self(), launchd_internal_port, ipc_port_set)) == KERN_SUCCESS); - - logopts = LOG_PID|LOG_CONS; - if (Dflag) - logopts |= LOG_PERROR; - openlog(getprogname(), logopts, LOG_LAUNCHD); - setlogmask(LOG_UPTO(Dflag ? LOG_DEBUG : LOG_NOTICE)); - - launchd_assert((mainkq = kqueue()) != -1); - - launchd_assert((asynckq = kqueue()) != -1); - - launchd_assert(kevent_mod(asynckq, EVFILT_READ, EV_ADD, 0, 0, &kqasync_callback) != -1); - - sigemptyset(&blocked_signals); - - for (i = 0; i < (sizeof(sigigns) / sizeof(int)); i++) { - launchd_assumes(kevent_mod(sigigns[i], EVFILT_SIGNAL, EV_ADD, 0, 0, &kqsignal_callback) != -1); - sigaddset(&blocked_signals, sigigns[i]); - launchd_assumes(signal(sigigns[i], SIG_IGN) != SIG_ERR); + if (getpid() != 1 && getppid() != 1) { + fprintf(stderr, "%s: This program is not meant to be run directly.\n", getprogname()); + exit(EXIT_FAILURE); } - /* sigh... ignoring SIGCHLD has side effects: we can't call wait*() */ - launchd_assert(kevent_mod(SIGCHLD, EVFILT_SIGNAL, EV_ADD, 0, 0, &kqsignal_callback) != -1); - - if (session_type && strcmp(session_type, "Aqua") == 0) { - mach_port_t newparent; - - launchd_assert(bootstrap_parent(bootstrap_port, &newparent) == BOOTSTRAP_SUCCESS); + launchd_runtime_init(); - launchd_assert(_launchd_to_launchd(bootstrap_port, &req_mport, &checkin_mport, - &l2l_names, &l2l_name_cnt, &l2l_ports, &l2l_port_cnt) == BOOTSTRAP_SUCCESS); - - launchd_assert(l2l_name_cnt == l2l_port_cnt); + if (pid1_magic) { + int cfd = -1; + if (launchd_assumes((cfd = open(_PATH_CONSOLE, O_WRONLY | O_NOCTTY)) != -1)) { + _fd(cfd); + if (!launchd_assumes((g_console = fdopen(cfd, "w")) != NULL)) { + close(cfd); + } + } + } - task_set_bootstrap_port(mach_task_self(), newparent); - launchd_assumes(mach_port_deallocate(mach_task_self(), bootstrap_port) == KERN_SUCCESS); - bootstrap_port = newparent; + if (NULL == getenv("PATH")) { + setenv("PATH", _PATH_STDPATH, 1); } - mach_init_init(req_mport, checkin_mport, l2l_names, l2l_ports, l2l_name_cnt); + if (pid1_magic) { + pid1_magic_init(); + } else { + ipc_server_init(); + + runtime_log_push(); + + struct passwd *pwent = getpwuid(getuid()); + if (pwent) { + strlcpy(g_username, pwent->pw_name, sizeof(g_username) - 1); + } - if (h) - sprintf(ldconf, "%s/%s", h, LAUNCHD_CONF); + snprintf(g_my_label, sizeof(g_my_label), "com.apple.launchd.peruser.%u", getuid()); + + auditinfo_addr_t auinfo; + if (launchd_assumes(getaudit_addr(&auinfo, sizeof(auinfo)) != -1)) { + g_audit_session = auinfo.ai_asid; + runtime_syslog(LOG_DEBUG, "Our audit session ID is %i", g_audit_session); + } + + g_audit_session_port = _audit_session_self(); + snprintf(g_launchd_database_dir, sizeof(g_launchd_database_dir), LAUNCHD_DB_PREFIX "/com.apple.launchd.peruser.%u", getuid()); + runtime_syslog(LOG_DEBUG, "Per-user launchd for UID %u (%s) has begun.", getuid(), g_username); + } - rlcj = job_new(root_job, READCONF_LABEL, LAUNCHCTL_PATH, NULL, ldconf, MACH_PORT_NULL); - launchd_assert(rlcj != NULL); + if (pid1_magic) { + runtime_syslog(LOG_NOTICE | LOG_CONSOLE, "*** launchd[1] has started up. ***"); + if (g_use_gmalloc) { + runtime_syslog(LOG_NOTICE | LOG_CONSOLE, "*** Using libgmalloc. ***"); + } + if (g_malloc_log_stacks) { + runtime_syslog(LOG_NOTICE | LOG_CONSOLE, "*** Logging stacks of malloc(3) allocations. ***"); + } - if (argv[0]) - fbj = job_new(root_job, FIRSTBORN_LABEL, NULL, (const char *const *)argv, NULL, MACH_PORT_NULL); + if (g_verbose_boot) { + runtime_syslog(LOG_NOTICE | LOG_CONSOLE, "*** Verbose boot, will log to /dev/console. ***"); + } - if (NULL == getenv("PATH")) - setenv("PATH", _PATH_STDPATH, 1); + if (g_shutdown_debugging) { + runtime_syslog(LOG_NOTICE | LOG_CONSOLE, "*** Shutdown debugging is enabled. ***"); + } - if (getpid() == 1) { - pid1_magic_init(sflag); + /* PID 1 doesn't have a flat namespace. */ + g_flat_mach_namespace = false; } else { - ipc_server_init(checkin_fds, checkin_fdcnt); + if (g_use_gmalloc) { + runtime_syslog(LOG_NOTICE, "*** Per-user launchd using libgmalloc. ***"); + } } monitor_networking_state(); - /* do this after pid1_magic_init() to not catch ourselves mounting stuff */ - launchd_assumes(kevent_mod(0, EVFILT_FS, EV_ADD, 0, 0, &kqfs_callback) != -1); + if (pid1_magic) { + handle_pid1_crashes_separately(); + } else { + #if !TARGET_OS_EMBEDDED + /* prime shared memory before the 'bootstrap_port' global is set to zero */ + _vproc_transaction_begin(); + _vproc_transaction_end(); + #endif + } - if (session_type) { - pid_t pp = getppid(); + if (pid1_magic) { + /* Start the update thread -- rdar://problem/5039559&6153301 */ + pthread_t t = NULL; + int err = pthread_create(&t, NULL, update_thread, NULL); + (void)launchd_assumes(err == 0); + (void)launchd_assumes(pthread_detach(t) == 0); + } - /* As a per session launchd, we need to exit if our parent dies. - * - * Normally, in Unix, SIGHUP would cause us to exit, but we're a - * daemon, and daemons use SIGHUP to signal the need to reread - * configuration files. "Weee." - */ + jobmgr_init(sflag); + + launchd_runtime_init2(); - if (pp == 1) - exit(EXIT_SUCCESS); + launchd_runtime(); +} - ker = kevent_mod(pp, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, &kqppidexit_callback); +void +handle_pid1_crashes_separately(void) +{ + struct sigaction fsa; + + fsa.sa_sigaction = fatal_signal_handler; + fsa.sa_flags = SA_SIGINFO; + sigemptyset(&fsa.sa_mask); + + (void)launchd_assumes(sigaction(SIGILL, &fsa, NULL) != -1); + (void)launchd_assumes(sigaction(SIGFPE, &fsa, NULL) != -1); + (void)launchd_assumes(sigaction(SIGBUS, &fsa, NULL) != -1); + (void)launchd_assumes(sigaction(SIGSEGV, &fsa, NULL) != -1); + (void)launchd_assumes(sigaction(SIGABRT, &fsa, NULL) != -1); + (void)launchd_assumes(sigaction(SIGTRAP, &fsa, NULL) != -1); +} - if (ker == -1) - exit(launchd_assumes(errno == ESRCH) ? EXIT_SUCCESS : EXIT_FAILURE); +void *update_thread(void *nothing __attribute__((unused))) +{ + /* use IOPOL_PASSIVE for sync thread */ + (void)launchd_assumes(setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_THREAD, IOPOL_PASSIVE) != -1); + + while( g_sync_frequency ) { + sync(); + sleep(g_sync_frequency); } + + runtime_syslog(LOG_DEBUG, "Update thread exiting."); + return NULL; +} - if (stat(ldconf, &sb) == 0) - job_start(rlcj); - - if (fbj) - job_start(fbj); - - pthread_attr_t attr; - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); - pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN); - launchd_assert(pthread_create(&kqueue_demand_thread, &attr, kqueue_demand_loop, NULL) == 0); - pthread_attr_destroy(&attr); +#define PID1_CRASH_LOGFILE "/var/log/launchd-pid1.crash" - mach_msg_return_t msgr; - mach_msg_size_t mxmsgsz = sizeof(union MaxRequestSize) + MAX_TRAILER_SIZE; +/* This hack forces the dynamic linker to resolve these symbols ASAP */ +static __attribute__((unused)) typeof(sync) *__junk_dyld_trick1 = sync; +static __attribute__((unused)) typeof(sleep) *__junk_dyld_trick2 = sleep; +static __attribute__((unused)) typeof(reboot) *__junk_dyld_trick3 = reboot; - if (getpid() == 1 && !job_active(rlcj)) - init_pre_kevent(); +void +do_pid1_crash_diagnosis_mode(const char *msg) +{ + if (g_wsp) { + kill(g_wsp, SIGKILL); + sleep(3); + g_wsp = 0; + } - for (;;) { - msgr = mach_msg_server(launchd_internal_demux, mxmsgsz, ipc_port_set, - MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT) | - MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)); - launchd_assumes(msgr == MACH_MSG_SUCCESS); + while (g_shutdown_debugging && !do_pid1_crash_diagnosis_mode2(msg)) { + sleep(1); } } -void * -kqueue_demand_loop(void *arg __attribute__((unused))) +int +basic_fork(void) { - fd_set rfds; + int wstatus = 0; + pid_t p; + + switch ((p = fork())) { + case -1: + runtime_syslog(LOG_ERR | LOG_CONSOLE, "Can't fork PID 1 copy for crash debugging: %m"); + return p; + case 0: + return p; + default: + do { + (void)waitpid(p, &wstatus, 0); + } while(!WIFEXITED(wstatus)); - for (;;) { - FD_ZERO(&rfds); - FD_SET(mainkq, &rfds); - if (launchd_assumes(select(mainkq + 1, &rfds, NULL, NULL, NULL) == 1)) - launchd_assumes(handle_kqueue(launchd_internal_port, mainkq) == 0); - } + fprintf(stdout, "PID 1 copy: exit status: %d\n", WEXITSTATUS(wstatus)); - return NULL; + return 1; + } + + return -1; } -kern_return_t -x_handle_kqueue(mach_port_t junk __attribute__((unused)), integer_t fd) +bool +do_pid1_crash_diagnosis_mode2(const char *msg) { - struct timespec ts = { 0, 0 }; - struct kevent kev; - int kevr; - - launchd_assumes((kevr = kevent(fd, NULL, 0, &kev, 1, &ts)) != -1); - - if (kevr == 1) - (*((kq_callback *)kev.udata))(kev.udata, &kev); - - if (shutdown_in_progress && total_children == 0) { - mach_init_reap(); - - shutdown_in_progress = false; - - if (getpid() != 1) { - exit(EXIT_SUCCESS); - } else if (re_exec_in_single_user_mode) { - re_exec_in_single_user_mode = false; - launchd_assumes(execl("/sbin/launchd", "/sbin/launchd", "-s", NULL) != -1); + if (basic_fork() == 0) { + /* Neuter our bootstrap port so that the shell doesn't try talking to us while + * we're blocked waiting on it. + */ + if (g_console) { + fflush(g_console); } + task_set_bootstrap_port(mach_task_self(), MACH_PORT_NULL); + if (basic_fork() != 0) { + if (g_console) { + fflush(g_console); + } + return true; + } + } else { + return true; } - - if (getpid() == 1) { - if (rlcj && job_active(rlcj)) - goto out; - init_pre_kevent(); + + int fd; + revoke(_PATH_CONSOLE); + if ((fd = open(_PATH_CONSOLE, O_RDWR)) == -1) { + _exit(2); } - -out: - return 0; + if (login_tty(fd) == -1) { + _exit(3); + } + setenv("TERM", "vt100", 1); + fprintf(stdout, "\n"); + fprintf(stdout, "Entering launchd PID 1 debugging mode...\n"); + fprintf(stdout, "The PID 1 launchd has crashed %s.\n", msg); + fprintf(stdout, "It has fork(2)ed itself for debugging.\n"); + fprintf(stdout, "To debug the crashing thread of PID 1:\n"); + fprintf(stdout, " gdb attach %d\n", getppid()); + fprintf(stdout, "To exit this shell and shut down:\n"); + fprintf(stdout, " kill -9 1\n"); + fprintf(stdout, "A sample of PID 1 has been written to %s\n", PID1_CRASH_LOGFILE); + fprintf(stdout, "\n"); + fflush(stdout); + + execl(_PATH_BSHELL, "-sh", NULL); + syslog(LOG_ERR, "can't exec %s for PID 1 crash debugging: %m", _PATH_BSHELL); + _exit(EXIT_FAILURE); } void -pid1_magic_init(bool sflag) +fatal_signal_handler(int sig, siginfo_t *si, void *uap __attribute__((unused))) { - launchd_assumes(setsid() != -1); - launchd_assumes(chdir("/") != -1); - launchd_assumes(setlogin("root") != -1); - launchd_assumes(mount("fdesc", "/dev", MNT_UNION, NULL) != -1); + const char *doom_why = "at instruction"; + char msg[128]; + char *sample_args[] = { "/usr/bin/sample", "1", "1", "-file", PID1_CRASH_LOGFILE, NULL }; + pid_t sample_p; + int wstatus; + + crash_addr = si->si_addr; + crash_pid = si->si_pid; + + unlink(PID1_CRASH_LOGFILE); - init_boot(sflag); -} + switch ((sample_p = vfork())) { + case 0: + execve(sample_args[0], sample_args, environ); + _exit(EXIT_FAILURE); + break; + default: + waitpid(sample_p, &wstatus, 0); + break; + case -1: + break; + } + switch (sig) { + default: + case 0: + break; + case SIGBUS: + case SIGSEGV: + doom_why = "trying to read/write"; + case SIGILL: + case SIGFPE: + snprintf(msg, sizeof(msg), "%s: %p (%s sent by PID %u)", doom_why, crash_addr, strsignal(sig), crash_pid); + sync(); + do_pid1_crash_diagnosis_mode(msg); + sleep(3); + reboot(0); + break; + } +} void -usage(FILE *where) +pid1_magic_init(void) { - const char *opts = "[-d]"; - - if (getuid() == 0) - opts = "[-d] [-S -U ]"; - - fprintf(where, "%s: %s [-- command [args ...]]\n", getprogname(), opts); - - fprintf(where, "\t-d Daemonize.\n"); - fprintf(where, "\t-h This usage statement.\n"); - - if (getuid() == 0) { - fprintf(where, "\t-S What type of session to create (Aqua, tty or X11).\n"); - fprintf(where, "\t-U Which user to create the session as.\n"); + (void)launchd_assumes(setsid() != -1); + (void)launchd_assumes(chdir("/") != -1); + (void)launchd_assumes(setlogin("root") != -1); + + strcpy(g_my_label, "com.apple.launchd"); + +#if !TARGET_OS_EMBEDDED + auditinfo_addr_t auinfo = { + .ai_termid = { .at_type = AU_IPv4 }, + .ai_asid = AU_ASSIGN_ASID, + .ai_auid = AU_DEFAUDITID, + .ai_flags = AU_SESSION_FLAG_IS_INITIAL, + }; + + if (!launchd_assumes(setaudit_addr(&auinfo, sizeof(auinfo)) != -1)) { + runtime_syslog(LOG_WARNING | LOG_CONSOLE, "Could not set audit session: %s.", strerror(errno)); + _exit(EXIT_FAILURE); } - if (where == stdout) - exit(EXIT_SUCCESS); + g_audit_session = auinfo.ai_asid; + runtime_syslog(LOG_DEBUG, "Audit Session ID: %i", g_audit_session); + + g_audit_session_port = _audit_session_self(); +#endif + + strcpy(g_launchd_database_dir, LAUNCHD_DB_PREFIX "/com.apple.launchd"); } -int -kevent_mod(uintptr_t ident, short filter, u_short flags, u_int fflags, intptr_t data, void *udata) +char * +launchd_data_base_path(int db_type) { - struct kevent kev; - int q = mainkq; - - if (EVFILT_TIMER == filter || EVFILT_VNODE == filter) - q = asynckq; - - if (flags & EV_ADD && !launchd_assumes(udata != NULL)) { - errno = EINVAL; - return -1; + static char result[PATH_MAX]; + static int last_db_type = -1; + + if (db_type == last_db_type) { + return result; } - - EV_SET(&kev, ident, filter, flags, fflags, data, udata); - return kevent(q, &kev, 1, NULL, 0, NULL); + + switch (db_type) { + case LAUNCHD_DB_TYPE_OVERRIDES : + snprintf(result, sizeof(result), "%s/%s", g_launchd_database_dir, "overrides.plist"); + last_db_type = db_type; + break; + case LAUNCHD_DB_TYPE_JOBCACHE : + snprintf(result, sizeof(result), "%s/%s", g_launchd_database_dir, "jobcache.launchdata"); + last_db_type = db_type; + break; + default : + break; + } + + return result; } int _fd(int fd) { - if (fd >= 0) - launchd_assumes(fcntl(fd, F_SETFD, 1) != -1); + if (fd >= 0) { + (void)launchd_assumes(fcntl(fd, F_SETFD, 1) != -1); + } return fd; } void -ppidexit_callback(void) +launchd_shutdown(void) { - launchd_shutdown(); + int64_t now; - /* Let's just bail for now. We should really try to wait for jobs to exit first. */ - exit(EXIT_SUCCESS); -} + if (shutdown_in_progress) { + return; + } + + runtime_ktrace0(RTKT_LAUNCHD_EXITING); -void -launchd_shutdown(void) -{ shutdown_in_progress = true; - launchd_assumes(close(asynckq) != -1); - - rlcj = NULL; + if (pid1_magic || g_log_per_user_shutdown) { + /* + * When this changes to a more sustainable API, update this: + * http://howto.apple.com/db.cgi?Debugging_Apps_Non-Responsive_At_Shutdown + */ + runtime_setlogmask(LOG_UPTO(LOG_DEBUG)); + } - job_remove_all_inactive(root_job); + runtime_log_push(); - if (getpid() == 1) - catatonia(); -} + now = runtime_get_wall_time(); -void -launchd_single_user(void) -{ - int tries; + char *term_who = pid1_magic ? "System shutdown" : "Per-user launchd termination for "; + runtime_syslog(LOG_INFO, "%s%s began", term_who, pid1_magic ? "" : g_username); - launchd_shutdown(); - - kill(-1, SIGTERM); + launchd_assert(jobmgr_shutdown(root_jobmgr) != NULL); - for (tries = 0; tries < 10; tries++) { - sleep(1); - if (kill(-1, 0) == -1 && errno == ESRCH) - goto out; +#if HAVE_LIBAUDITD + if (pid1_magic) { + (void)launchd_assumes(audit_quick_stop() == 0); } +#endif +} - syslog(LOG_WARNING, "Gave up waiting for processes to exit while going to single user mode, sending SIGKILL"); - kill(-1, SIGKILL); +void +launchd_single_user(void) +{ + runtime_syslog(LOG_NOTICE, "Going to single-user mode"); -out: re_exec_in_single_user_mode = true; -} -static void signal_callback(void *obj __attribute__((unused)), struct kevent *kev) -{ - switch (kev->ident) { - case SIGHUP: - if (rlcj) - job_start(rlcj); - break; - case SIGTERM: - launchd_shutdown(); - break; - default: - break; - } -} + launchd_shutdown(); -void -fs_callback(void) -{ - if (pending_stdout) { - int fd = open(pending_stdout, O_CREAT|O_APPEND|O_WRONLY|O_NOCTTY, DEFFILEMODE); - if (fd != -1) { - launchd_assumes(dup2(fd, STDOUT_FILENO) != -1); - launchd_assumes(close(fd) == 0); - free(pending_stdout); - pending_stdout = NULL; - } - } - if (pending_stderr) { - int fd = open(pending_stderr, O_CREAT|O_APPEND|O_WRONLY|O_NOCTTY, DEFFILEMODE); - if (fd != -1) { - launchd_assumes(dup2(fd, STDERR_FILENO) != -1); - launchd_assumes(close(fd) == 0); - free(pending_stderr); - pending_stderr = NULL; - } - } + sleep(3); - ipc_server_init(NULL, 0); + runtime_kill(-1, SIGKILL); } void launchd_SessionCreate(void) { - OSStatus (*sescr)(SessionCreationFlags flags, SessionAttributeBits attributes); - void *seclib; - - if (launchd_assumes((seclib = dlopen(SECURITY_LIB, RTLD_LAZY)) != NULL)) { - if (launchd_assumes((sescr = dlsym(seclib, "SessionCreate")) != NULL)) - launchd_assumes(sescr(0, 0) == noErr); - launchd_assumes(dlclose(seclib) != -1); +#if !TARGET_OS_EMBEDDED + auditinfo_addr_t auinfo = { + .ai_termid = { .at_type = AU_IPv4 }, + .ai_asid = AU_ASSIGN_ASID, + .ai_auid = getuid(), + .ai_flags = 0, + }; + if (launchd_assumes(setaudit_addr(&auinfo, sizeof(auinfo)) == 0)) { + char session[16]; + snprintf(session, sizeof(session), "%x", auinfo.ai_asid); + setenv("SECURITYSESSIONID", session, 1); + } else { + runtime_syslog(LOG_WARNING, "Could not set audit session: %s.", strerror(errno)); } -} - -void -async_callback(void) -{ - struct timespec timeout = { 0, 0 }; - struct kevent kev; - - if (launchd_assumes(kevent(asynckq, NULL, 0, &kev, 1, &timeout) == 1)) - (*((kq_callback *)kev.udata))(kev.udata, &kev); +#endif } void @@ -562,71 +564,44 @@ testfd_or_openfd(int fd, const char *path, int flags) int tmpfd; if (-1 != (tmpfd = dup(fd))) { - launchd_assumes(close(tmpfd) == 0); + (void)launchd_assumes(runtime_close(tmpfd) == 0); } else { - if (-1 == (tmpfd = open(path, flags))) { - syslog(LOG_ERR, "open(\"%s\", ...): %m", path); + if (-1 == (tmpfd = open(path, flags | O_NOCTTY, DEFFILEMODE))) { + runtime_syslog(LOG_ERR, "open(\"%s\", ...): %m", path); } else if (tmpfd != fd) { - launchd_assumes(dup2(tmpfd, fd) != -1); - launchd_assumes(close(tmpfd) == 0); + (void)launchd_assumes(dup2(tmpfd, fd) != -1); + (void)launchd_assumes(runtime_close(tmpfd) == 0); } } } -launch_data_t -launchd_setstdio(int d, launch_data_t o) -{ - launch_data_t resp = launch_data_new_errno(0); - - if (launch_data_get_type(o) == LAUNCH_DATA_STRING) { - char **where = &pending_stderr; - - if (d == STDOUT_FILENO) - where = &pending_stdout; - if (*where) - free(*where); - *where = strdup(launch_data_get_string(o)); - } else if (launch_data_get_type(o) == LAUNCH_DATA_FD) { - launchd_assumes(dup2(launch_data_get_fd(o), d) != -1); - } else { - launch_data_set_errno(resp, EINVAL); - } - - return resp; -} - -void -batch_job_enable(bool e, struct conncb *c) -{ - if (e && c->disabled_batch) { - batch_disabler_count--; - c->disabled_batch = 0; - if (batch_disabler_count == 0) - kevent_mod(asynckq, EVFILT_READ, EV_ENABLE, 0, 0, &kqasync_callback); - } else if (!e && !c->disabled_batch) { - if (batch_disabler_count == 0) - kevent_mod(asynckq, EVFILT_READ, EV_DISABLE, 0, 0, &kqasync_callback); - batch_disabler_count++; - c->disabled_batch = 1; - } -} - bool get_network_state(void) { struct ifaddrs *ifa, *ifai; bool up = false; + int r; + + /* Workaround 4978696: getifaddrs() reports false ENOMEM */ + while ((r = getifaddrs(&ifa)) == -1 && errno == ENOMEM) { + runtime_syslog(LOG_DEBUG, "Worked around bug: 4978696"); + (void)launchd_assumes(sched_yield() != -1); + } - if (!launchd_assumes(getifaddrs(&ifa) != -1)) + if (!launchd_assumes(r != -1)) { return network_up; + } for (ifai = ifa; ifai; ifai = ifai->ifa_next) { - if (!(ifai->ifa_flags & IFF_UP)) + if (!(ifai->ifa_flags & IFF_UP)) { continue; - if (ifai->ifa_flags & IFF_LOOPBACK) + } + if (ifai->ifa_flags & IFF_LOOPBACK) { continue; - if (ifai->ifa_addr->sa_family != AF_INET && ifai->ifa_addr->sa_family != AF_INET6) + } + if (ifai->ifa_addr->sa_family != AF_INET && ifai->ifa_addr->sa_family != AF_INET6) { continue; + } up = true; break; } @@ -644,34 +619,35 @@ monitor_networking_state(void) network_up = get_network_state(); - if (!launchd_assumes(pfs != -1)) + if (!launchd_assumes(pfs != -1)) { return; + } memset(&kev_req, 0, sizeof(kev_req)); kev_req.vendor_code = KEV_VENDOR_APPLE; kev_req.kev_class = KEV_NETWORK_CLASS; if (!launchd_assumes(ioctl(pfs, SIOCSKEVFILT, &kev_req) != -1)) { - close(pfs); + runtime_close(pfs); return; } - launchd_assumes(kevent_mod(pfs, EVFILT_READ, EV_ADD, 0, 0, &kqpfsystem_callback) != -1); + (void)launchd_assumes(kevent_mod(pfs, EVFILT_READ, EV_ADD, 0, 0, &kqpfsystem_callback) != -1); } void -pfsystem_callback(void *obj, struct kevent *kev) +pfsystem_callback(void *obj __attribute__((unused)), struct kevent *kev) { bool new_networking_state; char buf[1024]; - launchd_assumes(read(kev->ident, &buf, sizeof(buf)) != -1); + (void)launchd_assumes(read((int)kev->ident, &buf, sizeof(buf)) != -1); new_networking_state = get_network_state(); if (new_networking_state != network_up) { network_up = new_networking_state; - job_dispatch_all_other_semaphores(root_job, NULL); + jobmgr_dispatch_all_semaphores(root_jobmgr); } } @@ -683,6 +659,8 @@ _log_launchd_bug(const char *rcs_rev, const char *path, unsigned int line, const const char *file = strrchr(path, '/'); char *rcs_rev_tmp = strchr(rcs_rev, ' '); + runtime_ktrace1(RTKT_LAUNCHD_BUG); + if (!file) { file = path; } else { @@ -694,53 +672,10 @@ _log_launchd_bug(const char *rcs_rev, const char *path, unsigned int line, const } else { strlcpy(buf, rcs_rev_tmp + 1, sizeof(buf)); rcs_rev_tmp = strchr(buf, ' '); - if (rcs_rev_tmp) + if (rcs_rev_tmp) { *rcs_rev_tmp = '\0'; - } - - syslog(LOG_NOTICE, "Bug: %s:%u (%s):%u: %s", file, line, buf, saved_errno, test); -} - -bool -progeny_check(pid_t p) -{ - pid_t selfpid = getpid(); - - while (p != selfpid && p != 1) { - int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, p }; - size_t miblen = sizeof(mib) / sizeof(mib[0]); - struct kinfo_proc kp; - size_t kplen = sizeof(kp); - - if (launchd_assumes(sysctl(mib, miblen, &kp, &kplen, NULL, 0) != -1) - && launchd_assumes(kplen == sizeof(kp))) { - p = kp.kp_eproc.e_ppid; - } else { - return false; } } - if (p == selfpid) - return true; - - return false; -} - -boolean_t -launchd_internal_demux(mach_msg_header_t *Request, mach_msg_header_t *Reply) -{ - if (gc_this_job) { - job_remove(gc_this_job); - gc_this_job = NULL; - } - - if (Request->msgh_local_port == launchd_internal_port) { - if (launchd_internal_server_routine(Request)) - return launchd_internal_server(Request, Reply); - } else { - if (bootstrap_server_routine(Request)) - return bootstrap_server(Request, Reply); - } - - return notify_server(Request, Reply); + runtime_syslog(LOG_NOTICE, "Bug: %s:%u (%s):%u: %s", file, line, buf, saved_errno, test); }