X-Git-Url: https://git.saurik.com/apple/launchd.git/blobdiff_plain/aa59983ad5a41ca0272b5584ad9dabbde5b0c49b..dcace88fe6cde929a524daa9e1f1495bf1e24cfe:/launchd/src/launchd.c diff --git a/launchd/src/launchd.c b/launchd/src/launchd.c index d6c1057..78e1738 100644 --- a/launchd/src/launchd.c +++ b/launchd/src/launchd.c @@ -1,32 +1,28 @@ /* * Copyright (c) 2005 Apple Computer, Inc. All rights reserved. * - * @APPLE_LICENSE_HEADER_START@ + * @APPLE_APACHE_LICENSE_HEADER_START@ * - * This file contains Original Code and/or Modifications of Original Code - * as defined in and that are subject to the Apple Public Source License - * Version 2.0 (the 'License'). You may not use this file except in - * compliance with the License. Please obtain a copy of the License at - * http://www.opensource.apple.com/apsl/ and read it before using this - * file. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * The Original Code and all software distributed under the License are - * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER - * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, - * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. - * Please see the License for the specific language governing rights and + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and * limitations under the License. * - * @APPLE_LICENSE_HEADER_END@ + * @APPLE_APACHE_LICENSE_HEADER_END@ */ -#include -#include -#include -#ifdef EVFILT_MACH_IMPLEMENTED -#include -#include -#endif + +static const char *const __rcs_file_version__ = "$Revision: 24863 $"; + +#include "config.h" +#include "launchd.h" + #include #include #include @@ -41,2380 +37,645 @@ #include #include #include +#include +#include +#include +#include #include #include #include #include +#include #include #include #include -#include #include #include #include #include #include -#include #include #include #include +#include #include #include +#include +#include +#include +#include +#include +#include +#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 "bootstrap_internal.h" +#include "launchd_runtime.h" +#include "launchd_core_logic.h" +#include "launchd_unix_ipc.h" -#define LAUNCHD_MIN_JOB_RUN_TIME 10 -#define LAUNCHD_REWARD_JOB_RUN_TIME 60 -#define LAUNCHD_FAILED_EXITS_THRESHOLD 10 -#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" -#define VOLFSDIR "/.vol" extern char **environ; -struct jobcb { - kq_callback kqjob_callback; - TAILQ_ENTRY(jobcb) tqe; - launch_data_t ldj; - pid_t p; - int execfd; - time_t start_time; - size_t failed_exits; - int *vnodes; - size_t vnodes_cnt; - int *qdirs; - size_t qdirs_cnt; - unsigned int start_interval; - struct tm *start_cal_interval; - unsigned int checkedin:1, firstborn:1, debug:1, throttle:1, futureflags:28; - char label[0]; -}; - -struct conncb { - kq_callback kqconn_callback; - TAILQ_ENTRY(conncb) tqe; - launch_t conn; - struct jobcb *j; - int disabled_batch:1, futureflags:31; -}; - -static TAILQ_HEAD(jobcbhead, jobcb) jobs = TAILQ_HEAD_INITIALIZER(jobs); -static TAILQ_HEAD(conncbhead, conncb) connections = TAILQ_HEAD_INITIALIZER(connections); -static int mainkq = 0; -static int asynckq = 0; -static int batch_disabler_count = 0; - -static launch_data_t load_job(launch_data_t pload); -static launch_data_t get_jobs(const char *which); -static launch_data_t setstdio(int d, launch_data_t o); -static launch_data_t adjust_rlimits(launch_data_t in); -static void batch_job_enable(bool e, struct conncb *c); -static void do_shutdown(void); - -static void listen_callback(void *, struct kevent *); -static void async_callback(void); -static void signal_callback(void *, struct kevent *); -static void fs_callback(void); -static void simple_zombie_reaper(void *, struct kevent *); -static void readcfg_callback(void *, struct kevent *); - -static kq_callback kqlisten_callback = listen_callback; -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 kqreadcfg_callback = readcfg_callback; -kq_callback kqsimple_zombie_reaper = simple_zombie_reaper; - -static void job_watch(struct jobcb *j); -static void job_ignore(struct jobcb *j); -static void job_start(struct jobcb *j); -static void job_start_child(struct jobcb *j, int execfd); -static void job_setup_attributes(struct jobcb *j); -static void job_stop(struct jobcb *j); -static void job_reap(struct jobcb *j); -static void job_remove(struct jobcb *j); -static void job_set_alarm(struct jobcb *j); -static void job_callback(void *obj, struct kevent *kev); -static void job_log(struct jobcb *j, int pri, const char *msg, ...) __attribute__((format(printf, 3, 4))); -static void job_log_error(struct jobcb *j, int pri, const char *msg, ...) __attribute__((format(printf, 3, 4))); - -static void ipc_open(int fd, struct jobcb *j); -static void ipc_close(struct conncb *c); -static void ipc_callback(void *, struct kevent *); -static void ipc_readmsg(launch_data_t msg, void *context); -static void ipc_readmsg2(launch_data_t data, const char *cmd, void *context); - -#ifdef PID1_REAP_ADOPTED_CHILDREN -static void pid1waitpid(void); -static bool launchd_check_pid(pid_t p); -#endif -static void pid1_magic_init(bool sflag, bool vflag, bool xflag); -static void launchd_server_init(bool create_session); -static void conceive_firstborn(char *argv[]); - -#ifdef EVFILT_MACH_IMPLEMENTED -static void *mach_demand_loop(void *); -static void mach_callback(void *, struct kevent *); -static kq_callback kqmach_callback = mach_callback; -#endif +static void pfsystem_callback(void *, struct kevent *); -static void usage(FILE *where); -static int _fd(int fd); +static kq_callback kqpfsystem_callback = pfsystem_callback; + +static void pid1_magic_init(void); -static void loopback_setup(void); -static void workaround3048875(int argc, char *argv[]); -static void reload_launchd_config(void); -static int dir_has_files(const char *path); static void testfd_or_openfd(int fd, const char *path, int flags); -static void setup_job_env(launch_data_t obj, const char *key, void *context); -static void unsetup_job_env(launch_data_t obj, const char *key, void *context); - - -static size_t total_children = 0; -static pid_t readcfg_pid = 0; -static pid_t launchd_proper_pid = 0; -static bool launchd_inited = false; -static bool shutdown_in_progress = false; -static pthread_t mach_server_loop_thread; -mach_port_t launchd_bootstrap_port = MACH_PORT_NULL; -sigset_t blocked_signals = 0; -static char *pending_stdout = NULL; -static char *pending_stderr = NULL; - -int main(int argc, char *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 }; - struct kevent kev; - size_t i; - bool sflag = false, xflag = false, vflag = false, dflag = false; +static bool get_network_state(void); +static void monitor_networking_state(void); +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) +{ + bool sflag = false; int ch; - if (getpid() == 1) - workaround3048875(argc, argv); - - setegid(getgid()); - seteuid(getuid()); - 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); - openlog(getprogname(), LOG_CONS|(getpid() != 1 ? LOG_PID|LOG_PERROR : 0), LOG_LAUNCHD); - setlogmask(LOG_UPTO(LOG_NOTICE)); - - while ((ch = getopt(argc, argv, "dhsvx")) != -1) { + 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"); + } + } else if (g_malloc_log_stacks) { + if (!getenv("MallocStackLogging")) { + setenv("MallocStackLogging", "1", 1); + execv(argv[0], argv); + } else { + unsetenv("MallocStackLogging"); + } + } + + while ((ch = getopt(argc, argv, "s")) != -1) { switch (ch) { - case 'd': dflag = true; break; - case 's': sflag = true; break; - case 'x': xflag = true; break; - case 'v': vflag = true; break; - case 'h': usage(stdout); break; + case 's': sflag = true; break; /* single user */ + case '?': /* we should do something with the global optopt variable here */ default: - syslog(LOG_WARNING, "ignoring unknown arguments"); - usage(stderr); + fprintf(stderr, "%s: ignoring unknown arguments\n", getprogname()); break; } } - argc -= optind; - argv += optind; - - if (dflag && daemon(0, 0) == -1) - syslog(LOG_WARNING, "couldn't daemonize: %m"); - - if ((mainkq = kqueue()) == -1) { - syslog(LOG_EMERG, "kqueue(): %m"); - abort(); - } - if ((asynckq = kqueue()) == -1) { - syslog(LOG_ERR, "kqueue(): %m"); - abort(); - } - - if (kevent_mod(asynckq, EVFILT_READ, EV_ADD, 0, 0, &kqasync_callback) == -1) { - syslog(LOG_ERR, "kevent_mod(asynckq, EVFILT_READ): %m"); - abort(); + if (getpid() != 1 && getppid() != 1) { + fprintf(stderr, "%s: This program is not meant to be run directly.\n", getprogname()); + exit(EXIT_FAILURE); } - sigemptyset(&blocked_signals); + launchd_runtime_init(); - for (i = 0; i < (sizeof(sigigns) / sizeof(int)); i++) { - if (kevent_mod(sigigns[i], EVFILT_SIGNAL, EV_ADD, 0, 0, &kqsignal_callback) == -1) - syslog(LOG_ERR, "failed to add kevent for signal: %d: %m", sigigns[i]); - sigaddset(&blocked_signals, sigigns[i]); - signal(sigigns[i], SIG_IGN); + 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); + } + } } - /* sigh... ignoring SIGCHLD has side effects: we can't call wait*() */ - if (kevent_mod(SIGCHLD, EVFILT_SIGNAL, EV_ADD, 0, 0, &kqsignal_callback) == -1) - syslog(LOG_ERR, "failed to add kevent for signal: %d: %m", SIGCHLD); - - if (getpid() == 1) { - pid1_magic_init(sflag, vflag, xflag); - } else { - launchd_bootstrap_port = bootstrap_port; - launchd_server_init(argv[0] ? true : false); + if (NULL == getenv("PATH")) { + setenv("PATH", _PATH_STDPATH, 1); } - /* do this after pid1_magic_init() to not catch ourselves mounting stuff */ - if (kevent_mod(0, EVFILT_FS, EV_ADD, 0, 0, &kqfs_callback) == -1) - syslog(LOG_ERR, "kevent_mod(EVFILT_FS, &kqfs_callback): %m"); - - - if (argv[0]) - conceive_firstborn(argv); - - reload_launchd_config(); - - if (argv[0]) - job_start(TAILQ_FIRST(&jobs)); - - for (;;) { - static struct timespec timeout = { 30, 0 }; - struct timespec *timeoutp = NULL; - - if (getpid() == 1) { - if (readcfg_pid == 0) - init_pre_kevent(); - } else { - if (TAILQ_EMPTY(&jobs)) { - /* launched on demand */ - timeoutp = &timeout; - } else if (shutdown_in_progress && total_children == 0) { - exit(EXIT_SUCCESS); - } + 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); } - switch (kevent(mainkq, NULL, 0, &kev, 1, timeoutp)) { - case -1: - syslog(LOG_DEBUG, "kevent(): %m"); - break; - case 1: - (*((kq_callback *)kev.udata))(kev.udata, &kev); - break; - case 0: - if (timeoutp) - exit(EXIT_SUCCESS); - else - syslog(LOG_DEBUG, "kevent(): spurious return with infinite timeout"); - break; - default: - syslog(LOG_DEBUG, "unexpected: kevent() returned something != 0, -1 or 1"); - break; + 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); } -} -static void pid1_magic_init(bool sflag, bool vflag, bool xflag) -{ - pthread_attr_t attr; - int memmib[2] = { CTL_HW, HW_PHYSMEM }; - int mvnmib[2] = { CTL_KERN, KERN_MAXVNODES }; - int hnmib[2] = { CTL_KERN, KERN_HOSTNAME }; - int tfp_r_mib[3] = { CTL_KERN, KERN_TFP, KERN_TFP_READ_GROUP }; - int tfp_rw_mib[3] = { CTL_KERN, KERN_TFP, KERN_TFP_RW_GROUP }; - gid_t tfp_r_gid = 0; - gid_t tfp_rw_gid = 0; - struct group *tfp_gr; - uint64_t mem = 0; - uint32_t mvn; - size_t memsz = sizeof(mem); - int pthr_r; - - if ((tfp_gr = getgrnam("procview"))) { - tfp_r_gid = tfp_gr->gr_gid; - sysctl(tfp_r_mib, 3, NULL, NULL, &tfp_r_gid, sizeof(tfp_r_gid)); - } + 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 ((tfp_gr = getgrnam("procmod"))) { - tfp_rw_gid = tfp_gr->gr_gid; - sysctl(tfp_rw_mib, 3, NULL, NULL, &tfp_rw_gid, sizeof(tfp_rw_gid)); - } + if (g_verbose_boot) { + runtime_syslog(LOG_NOTICE | LOG_CONSOLE, "*** Verbose boot, will log to /dev/console. ***"); + } - setpriority(PRIO_PROCESS, 0, -1); + if (g_shutdown_debugging) { + runtime_syslog(LOG_NOTICE | LOG_CONSOLE, "*** Shutdown debugging is enabled. ***"); + } - if (setsid() == -1) - syslog(LOG_ERR, "setsid(): %m"); + /* PID 1 doesn't have a flat namespace. */ + g_flat_mach_namespace = false; + } else { + if (g_use_gmalloc) { + runtime_syslog(LOG_NOTICE, "*** Per-user launchd using libgmalloc. ***"); + } + } - if (chdir("/") == -1) - syslog(LOG_ERR, "chdir(\"/\"): %m"); + monitor_networking_state(); - if (sysctl(memmib, 2, &mem, &memsz, NULL, 0) == -1) { - syslog(LOG_WARNING, "sysctl(\"%s\"): %m", "hw.physmem"); + if (pid1_magic) { + handle_pid1_crashes_separately(); } else { - /* The following assignment of mem to itself if the size - * of data returned is 32 bits instead of 64 is a clever - * C trick to move the 32 bits on big endian systems to - * the least significant bytes of the 64 mem variable. - * - * On little endian systems, this is effectively a no-op. - */ - if (memsz == 4) - mem = *(uint32_t *)&mem; - mvn = mem / (64 * 1024) + 1024; - if (sysctl(mvnmib, 2, NULL, NULL, &mvn, sizeof(mvn)) == -1) - syslog(LOG_WARNING, "sysctl(\"%s\"): %m", "kern.maxvnodes"); + #if !TARGET_OS_EMBEDDED + /* prime shared memory before the 'bootstrap_port' global is set to zero */ + _vproc_transaction_begin(); + _vproc_transaction_end(); + #endif } - if (sysctl(hnmib, 2, NULL, NULL, "localhost", sizeof("localhost")) == -1) - syslog(LOG_WARNING, "sysctl(\"%s\"): %m", "kern.hostname"); - if (setlogin("root") == -1) - syslog(LOG_ERR, "setlogin(\"root\"): %m"); + 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); + } - loopback_setup(); + jobmgr_init(sflag); + + launchd_runtime_init2(); - if (mount("fdesc", "/dev", MNT_UNION, NULL) == -1) - syslog(LOG_ERR, "mount(\"%s\", \"%s\", ...): %m", "fdesc", "/dev/"); + launchd_runtime(); +} - setenv("PATH", _PATH_STDPATH, 1); +void +handle_pid1_crashes_separately(void) +{ + struct sigaction fsa; - launchd_bootstrap_port = mach_init_init(); - task_set_bootstrap_port(mach_task_self(), launchd_bootstrap_port); - bootstrap_port = MACH_PORT_NULL; + fsa.sa_sigaction = fatal_signal_handler; + fsa.sa_flags = SA_SIGINFO; + sigemptyset(&fsa.sa_mask); - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + (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); +} - pthr_r = pthread_create(&mach_server_loop_thread, &attr, mach_server_loop, NULL); - if (pthr_r != 0) { - syslog(LOG_ERR, "pthread_create(mach_server_loop): %s", strerror(pthr_r)); - exit(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); } - - pthread_attr_destroy(&attr); - - init_boot(sflag, vflag, xflag); + + runtime_syslog(LOG_DEBUG, "Update thread exiting."); + return NULL; } +#define PID1_CRASH_LOGFILE "/var/log/launchd-pid1.crash" -#ifdef PID1_REAP_ADOPTED_CHILDREN -static bool launchd_check_pid(pid_t p) -{ - struct kevent kev; - struct jobcb *j; +/* 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; - TAILQ_FOREACH(j, &jobs, tqe) { - if (j->p == p) { - EV_SET(&kev, p, EVFILT_PROC, 0, 0, 0, j); - j->kqjob_callback(j, &kev); - return true; - } +void +do_pid1_crash_diagnosis_mode(const char *msg) +{ + if (g_wsp) { + kill(g_wsp, SIGKILL); + sleep(3); + g_wsp = 0; } - if (p == readcfg_pid) { - readcfg_callback(NULL, NULL); - return true; + while (g_shutdown_debugging && !do_pid1_crash_diagnosis_mode2(msg)) { + sleep(1); } - - return false; } -#endif -static char *sockdir = NULL; -static char *sockpath = NULL; - -static void launchd_clean_up(void) +int +basic_fork(void) { - if (launchd_proper_pid != getpid()) - return; - - seteuid(0); - setegid(0); + 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)); - if (-1 == unlink(sockpath)) - syslog(LOG_WARNING, "unlink(\"%s\"): %m", sockpath); - else if (-1 == rmdir(sockdir)) - syslog(LOG_WARNING, "rmdir(\"%s\"): %m", sockdir); + fprintf(stdout, "PID 1 copy: exit status: %d\n", WEXITSTATUS(wstatus)); - setegid(getgid()); - seteuid(getuid()); + return 1; + } + + return -1; } -static void launchd_server_init(bool create_session) +bool +do_pid1_crash_diagnosis_mode2(const char *msg) { - struct sockaddr_un sun; - mode_t oldmask; - int r, fd = -1, ourdirfd = -1; - char ourdir[1024]; - - memset(&sun, 0, sizeof(sun)); - sun.sun_family = AF_UNIX; - - if (create_session) { - snprintf(ourdir, sizeof(ourdir), "%s/%u.%u", LAUNCHD_SOCK_PREFIX, getuid(), getpid()); - snprintf(sun.sun_path, sizeof(sun.sun_path), "%s/%u.%u/sock", LAUNCHD_SOCK_PREFIX, getuid(), getpid()); - setenv(LAUNCHD_SOCKET_ENV, sun.sun_path, 1); - } else { - snprintf(ourdir, sizeof(ourdir), "%s/%u", LAUNCHD_SOCK_PREFIX, getuid()); - snprintf(sun.sun_path, sizeof(sun.sun_path), "%s/%u/sock", LAUNCHD_SOCK_PREFIX, getuid()); - } - - seteuid(0); - setegid(0); - - if (mkdir(LAUNCHD_SOCK_PREFIX, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) == -1) { - if (errno == EROFS) { - goto out_bad; - } else if (errno == EEXIST) { - struct stat sb; - stat(LAUNCHD_SOCK_PREFIX, &sb); - if (!S_ISDIR(sb.st_mode)) { - errno = EEXIST; - syslog(LOG_ERR, "mkdir(\"%s\"): %m", LAUNCHD_SOCK_PREFIX); - goto out_bad; - } - } else { - syslog(LOG_ERR, "mkdir(\"%s\"): %m", LAUNCHD_SOCK_PREFIX); - goto out_bad; + 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); } - } - - unlink(ourdir); - if (mkdir(ourdir, S_IRWXU) == -1) { - if (errno == EROFS) { - goto out_bad; - } else if (errno == EEXIST) { - struct stat sb; - stat(ourdir, &sb); - if (!S_ISDIR(sb.st_mode)) { - errno = EEXIST; - syslog(LOG_ERR, "mkdir(\"%s\"): %m", LAUNCHD_SOCK_PREFIX); - goto out_bad; + task_set_bootstrap_port(mach_task_self(), MACH_PORT_NULL); + if (basic_fork() != 0) { + if (g_console) { + fflush(g_console); } - } else { - syslog(LOG_ERR, "mkdir(\"%s\"): %m", ourdir); - goto out_bad; + return true; } + } else { + return true; } + + int fd; + revoke(_PATH_CONSOLE); + if ((fd = open(_PATH_CONSOLE, O_RDWR)) == -1) { + _exit(2); + } + 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); +} - if (chown(ourdir, getuid(), getgid()) == -1) - syslog(LOG_WARNING, "chown(\"%s\"): %m", ourdir); - - setegid(getgid()); - seteuid(getuid()); - - ourdirfd = _fd(open(ourdir, O_RDONLY)); - if (ourdirfd == -1) { - syslog(LOG_ERR, "open(\"%s\"): %m", ourdir); - goto out_bad; - } +void +fatal_signal_handler(int sig, siginfo_t *si, void *uap __attribute__((unused))) +{ + 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; - if (flock(ourdirfd, LOCK_EX|LOCK_NB) == -1) { - if (errno == EWOULDBLOCK) { - exit(EXIT_SUCCESS); - } else { - syslog(LOG_ERR, "flock(\"%s\"): %m", ourdir); - goto out_bad; - } - } + crash_addr = si->si_addr; + crash_pid = si->si_pid; + + unlink(PID1_CRASH_LOGFILE); - if (unlink(sun.sun_path) == -1 && errno != ENOENT) { - if (errno != EROFS) - syslog(LOG_ERR, "unlink(\"thesocket\"): %m"); - goto out_bad; - } - if ((fd = _fd(socket(AF_UNIX, SOCK_STREAM, 0))) == -1) { - syslog(LOG_ERR, "socket(\"thesocket\"): %m"); - goto out_bad; - } - oldmask = umask(077); - r = bind(fd, (struct sockaddr *)&sun, sizeof(sun)); - umask(oldmask); - if (r == -1) { - if (errno != EROFS) - syslog(LOG_ERR, "bind(\"thesocket\"): %m"); - goto out_bad; + 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; } - if (listen(fd, SOMAXCONN) == -1) { - syslog(LOG_ERR, "listen(\"thesocket\"): %m"); - goto out_bad; + 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; } +} - if (kevent_mod(fd, EVFILT_READ, EV_ADD, 0, 0, &kqlisten_callback) == -1) { - syslog(LOG_ERR, "kevent_mod(\"thesocket\", EVFILT_READ): %m"); - goto out_bad; +void +pid1_magic_init(void) +{ + (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); } - launchd_inited = true; - - sockdir = strdup(ourdir); - sockpath = strdup(sun.sun_path); + g_audit_session = auinfo.ai_asid; + runtime_syslog(LOG_DEBUG, "Audit Session ID: %i", g_audit_session); - launchd_proper_pid = getpid(); - atexit(launchd_clean_up); - -out_bad: - setegid(getgid()); - seteuid(getuid()); - - if (!launchd_inited) { - if (fd != -1) - close(fd); - if (ourdirfd != -1) - close(ourdirfd); - } + g_audit_session_port = _audit_session_self(); +#endif + + strcpy(g_launchd_database_dir, LAUNCHD_DB_PREFIX "/com.apple.launchd"); } -static long long job_get_integer(launch_data_t j, const char *key) +char * +launchd_data_base_path(int db_type) { - launch_data_t t = launch_data_dict_lookup(j, key); - if (t) - return launch_data_get_integer(t); - else - return 0; + static char result[PATH_MAX]; + static int last_db_type = -1; + + if (db_type == last_db_type) { + return result; + } + + 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; } -static const char *job_get_string(launch_data_t j, const char *key) +int +_fd(int fd) { - launch_data_t t = launch_data_dict_lookup(j, key); - if (t) - return launch_data_get_string(t); - else - return NULL; + if (fd >= 0) { + (void)launchd_assumes(fcntl(fd, F_SETFD, 1) != -1); + } + return fd; } -static const char *job_get_file2exec(launch_data_t j) +void +launchd_shutdown(void) { - launch_data_t tmpi, tmp = launch_data_dict_lookup(j, LAUNCH_JOBKEY_PROGRAM); + int64_t now; - if (tmp) { - return launch_data_get_string(tmp); - } else { - tmp = launch_data_dict_lookup(j, LAUNCH_JOBKEY_PROGRAMARGUMENTS); - if (tmp) { - tmpi = launch_data_array_get_index(tmp, 0); - if (tmpi) - return launch_data_get_string(tmpi); - } - return NULL; + if (shutdown_in_progress) { + return; } -} - -static bool job_get_bool(launch_data_t j, const char *key) -{ - launch_data_t t = launch_data_dict_lookup(j, key); - if (t) - return launch_data_get_bool(t); - else - return false; -} -static void ipc_open(int fd, struct jobcb *j) -{ - struct conncb *c = calloc(1, sizeof(struct conncb)); + runtime_ktrace0(RTKT_LAUNCHD_EXITING); - fcntl(fd, F_SETFL, O_NONBLOCK); + shutdown_in_progress = true; - c->kqconn_callback = ipc_callback; - c->conn = launchd_fdopen(fd); - c->j = j; - TAILQ_INSERT_TAIL(&connections, c, tqe); - kevent_mod(fd, EVFILT_READ, EV_ADD, 0, 0, &c->kqconn_callback); -} + 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)); + } -static void simple_zombie_reaper(void *obj __attribute__((unused)), struct kevent *kev) -{ - waitpid(kev->ident, NULL, 0); -} + runtime_log_push(); -static void listen_callback(void *obj __attribute__((unused)), struct kevent *kev) -{ - struct sockaddr_un sun; - socklen_t sl = sizeof(sun); - int cfd; + now = runtime_get_wall_time(); - if ((cfd = _fd(accept(kev->ident, (struct sockaddr *)&sun, &sl))) == -1) { - return; - } + 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); - ipc_open(cfd, NULL); -} + launchd_assert(jobmgr_shutdown(root_jobmgr) != NULL); -static void ipc_callback(void *obj, struct kevent *kev) -{ - struct conncb *c = obj; - int r; - - if (kev->filter == EVFILT_READ) { - if (launchd_msg_recv(c->conn, ipc_readmsg, c) == -1 && errno != EAGAIN) { - if (errno != ECONNRESET) - syslog(LOG_DEBUG, "%s(): recv: %m", __func__); - ipc_close(c); - } - } else if (kev->filter == EVFILT_WRITE) { - r = launchd_msg_send(c->conn, NULL); - if (r == -1) { - if (errno != EAGAIN) { - syslog(LOG_DEBUG, "%s(): send: %m", __func__); - ipc_close(c); - } - } else if (r == 0) { - kevent_mod(launchd_getfd(c->conn), EVFILT_WRITE, EV_DELETE, 0, 0, NULL); - } - } else { - syslog(LOG_DEBUG, "%s(): unknown filter type!", __func__); - ipc_close(c); +#if HAVE_LIBAUDITD + if (pid1_magic) { + (void)launchd_assumes(audit_quick_stop() == 0); } +#endif } -static void set_user_env(launch_data_t obj, const char *key, void *context __attribute__((unused))) +void +launchd_single_user(void) { - setenv(key, launch_data_get_string(obj), 1); -} + runtime_syslog(LOG_NOTICE, "Going to single-user mode"); -static void launch_data_close_fds(launch_data_t o) -{ - size_t i; + re_exec_in_single_user_mode = true; - switch (launch_data_get_type(o)) { - case LAUNCH_DATA_DICTIONARY: - launch_data_dict_iterate(o, (void (*)(launch_data_t, const char *, void *))launch_data_close_fds, NULL); - break; - case LAUNCH_DATA_ARRAY: - for (i = 0; i < launch_data_array_get_count(o); i++) - launch_data_close_fds(launch_data_array_get_index(o, i)); - break; - case LAUNCH_DATA_FD: - if (launch_data_get_fd(o) != -1) - close(launch_data_get_fd(o)); - break; - default: - break; - } + launchd_shutdown(); + + sleep(3); + + runtime_kill(-1, SIGKILL); } -static void launch_data_revoke_fds(launch_data_t o) +void +launchd_SessionCreate(void) { - size_t i; - - switch (launch_data_get_type(o)) { - case LAUNCH_DATA_DICTIONARY: - launch_data_dict_iterate(o, (void (*)(launch_data_t, const char *, void *))launch_data_revoke_fds, NULL); - break; - case LAUNCH_DATA_ARRAY: - for (i = 0; i < launch_data_array_get_count(o); i++) - launch_data_revoke_fds(launch_data_array_get_index(o, i)); - break; - case LAUNCH_DATA_FD: - launch_data_set_fd(o, -1); - break; - default: - break; +#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)); } +#endif } -static void job_ignore_fds(launch_data_t o, const char *key __attribute__((unused)), void *cookie) +void +testfd_or_openfd(int fd, const char *path, int flags) { - struct jobcb *j = cookie; - size_t i; - int fd; + int tmpfd; - switch (launch_data_get_type(o)) { - case LAUNCH_DATA_DICTIONARY: - launch_data_dict_iterate(o, job_ignore_fds, cookie); - break; - case LAUNCH_DATA_ARRAY: - for (i = 0; i < launch_data_array_get_count(o); i++) - job_ignore_fds(launch_data_array_get_index(o, i), NULL, cookie); - break; - case LAUNCH_DATA_FD: - fd = launch_data_get_fd(o); - if (-1 != fd) { - job_log(j, LOG_DEBUG, "Ignoring FD: %d", fd); - kevent_mod(fd, EVFILT_READ, EV_DELETE, 0, 0, NULL); + if (-1 != (tmpfd = dup(fd))) { + (void)launchd_assumes(runtime_close(tmpfd) == 0); + } else { + if (-1 == (tmpfd = open(path, flags | O_NOCTTY, DEFFILEMODE))) { + runtime_syslog(LOG_ERR, "open(\"%s\", ...): %m", path); + } else if (tmpfd != fd) { + (void)launchd_assumes(dup2(tmpfd, fd) != -1); + (void)launchd_assumes(runtime_close(tmpfd) == 0); } - break; - default: - break; } } -static void job_ignore(struct jobcb *j) +bool +get_network_state(void) { - launch_data_t j_sockets = launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_SOCKETS); - size_t i; - - if (j_sockets) - job_ignore_fds(j_sockets, NULL, j); + struct ifaddrs *ifa, *ifai; + bool up = false; + int r; - for (i = 0; i < j->vnodes_cnt; i++) { - kevent_mod(j->vnodes[i], EVFILT_VNODE, EV_DELETE, 0, 0, NULL); - } - for (i = 0; i < j->qdirs_cnt; i++) { - kevent_mod(j->qdirs[i], EVFILT_VNODE, EV_DELETE, 0, 0, NULL); + /* 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); } -} -static void job_watch_fds(launch_data_t o, const char *key __attribute__((unused)), void *cookie) -{ - struct jobcb *j = cookie; - size_t i; - int fd; + if (!launchd_assumes(r != -1)) { + return network_up; + } - switch (launch_data_get_type(o)) { - case LAUNCH_DATA_DICTIONARY: - launch_data_dict_iterate(o, job_watch_fds, cookie); - break; - case LAUNCH_DATA_ARRAY: - for (i = 0; i < launch_data_array_get_count(o); i++) - job_watch_fds(launch_data_array_get_index(o, i), NULL, cookie); - break; - case LAUNCH_DATA_FD: - fd = launch_data_get_fd(o); - if (-1 != fd) { - job_log(j, LOG_DEBUG, "Watching FD: %d", fd); - kevent_mod(fd, EVFILT_READ, EV_ADD, 0, 0, cookie); + for (ifai = ifa; ifai; ifai = ifai->ifa_next) { + if (!(ifai->ifa_flags & IFF_UP)) { + continue; } - break; - default: + if (ifai->ifa_flags & IFF_LOOPBACK) { + continue; + } + if (ifai->ifa_addr->sa_family != AF_INET && ifai->ifa_addr->sa_family != AF_INET6) { + continue; + } + up = true; break; } -} -static void job_watch(struct jobcb *j) -{ - launch_data_t ld_qdirs = launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_QUEUEDIRECTORIES); - launch_data_t ld_vnodes = launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_WATCHPATHS); - launch_data_t j_sockets = launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_SOCKETS); - size_t i; + freeifaddrs(ifa); - if (j_sockets) - job_watch_fds(j_sockets, NULL, &j->kqjob_callback); + return up; +} - for (i = 0; i < j->vnodes_cnt; i++) { - if (-1 == j->vnodes[i]) { - launch_data_t ld_idx = launch_data_array_get_index(ld_vnodes, i); - const char *thepath = launch_data_get_string(ld_idx); +void +monitor_networking_state(void) +{ + int pfs = _fd(socket(PF_SYSTEM, SOCK_RAW, SYSPROTO_EVENT)); + struct kev_request kev_req; - if (-1 == (j->vnodes[i] = _fd(open(thepath, O_EVTONLY)))) - job_log_error(j, LOG_ERR, "open(\"%s\", O_EVTONLY)", thepath); - } - kevent_mod(j->vnodes[i], EVFILT_VNODE, EV_ADD|EV_CLEAR, - NOTE_WRITE|NOTE_EXTEND|NOTE_DELETE|NOTE_RENAME|NOTE_REVOKE|NOTE_ATTRIB|NOTE_LINK, - 0, &j->kqjob_callback); - } + network_up = get_network_state(); - for (i = 0; i < j->qdirs_cnt; i++) { - kevent_mod(j->qdirs[i], EVFILT_VNODE, EV_ADD|EV_CLEAR, - NOTE_WRITE|NOTE_EXTEND|NOTE_ATTRIB|NOTE_LINK, 0, &j->kqjob_callback); + if (!launchd_assumes(pfs != -1)) { + return; } - for (i = 0; i < j->qdirs_cnt; i++) { - launch_data_t ld_idx = launch_data_array_get_index(ld_qdirs, i); - const char *thepath = launch_data_get_string(ld_idx); - int dcc_r; + memset(&kev_req, 0, sizeof(kev_req)); + kev_req.vendor_code = KEV_VENDOR_APPLE; + kev_req.kev_class = KEV_NETWORK_CLASS; - if (-1 == (dcc_r = dir_has_files(thepath))) { - job_log_error(j, LOG_ERR, "dir_has_files(\"%s\", ...)", thepath); - } else if (dcc_r > 0 && !shutdown_in_progress) { - job_start(j); - break; - } + if (!launchd_assumes(ioctl(pfs, SIOCSKEVFILT, &kev_req) != -1)) { + runtime_close(pfs); + return; } -} -static void job_stop(struct jobcb *j) -{ - if (j->p) - kill(j->p, SIGTERM); + (void)launchd_assumes(kevent_mod(pfs, EVFILT_READ, EV_ADD, 0, 0, &kqpfsystem_callback) != -1); } -static void job_remove(struct jobcb *j) +void +pfsystem_callback(void *obj __attribute__((unused)), struct kevent *kev) { - launch_data_t tmp; - size_t i; + bool new_networking_state; + char buf[1024]; - job_log(j, LOG_DEBUG, "Removed"); + (void)launchd_assumes(read((int)kev->ident, &buf, sizeof(buf)) != -1); - TAILQ_REMOVE(&jobs, j, tqe); - if (j->p) { - if (kevent_mod(j->p, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, &kqsimple_zombie_reaper) == -1) { - job_reap(j); - } else { - job_stop(j); - } - } - if ((tmp = launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_USERENVIRONMENTVARIABLES))) - launch_data_dict_iterate(tmp, unsetup_job_env, NULL); - launch_data_close_fds(j->ldj); - launch_data_free(j->ldj); - if (j->execfd) - close(j->execfd); - for (i = 0; i < j->vnodes_cnt; i++) - if (-1 != j->vnodes[i]) - close(j->vnodes[i]); - if (j->vnodes) - free(j->vnodes); - for (i = 0; i < j->qdirs_cnt; i++) - if (-1 != j->qdirs[i]) - close(j->qdirs[i]); - if (j->qdirs) - free(j->qdirs); - if (j->start_interval) - kevent_mod((uintptr_t)&j->start_interval, EVFILT_TIMER, EV_DELETE, 0, 0, NULL); - if (j->start_cal_interval) { - kevent_mod((uintptr_t)j->start_cal_interval, EVFILT_TIMER, EV_DELETE, 0, 0, NULL); - free(j->start_cal_interval); + new_networking_state = get_network_state(); + + if (new_networking_state != network_up) { + network_up = new_networking_state; + jobmgr_dispatch_all_semaphores(root_jobmgr); } - kevent_mod((uintptr_t)j, EVFILT_TIMER, EV_DELETE, 0, 0, NULL); - free(j); } -struct readmsg_context { - struct conncb *c; - launch_data_t resp; -}; - -static void ipc_readmsg(launch_data_t msg, void *context) +void +_log_launchd_bug(const char *rcs_rev, const char *path, unsigned int line, const char *test) { - struct readmsg_context rmc = { context, NULL }; + int saved_errno = errno; + char buf[100]; + const char *file = strrchr(path, '/'); + char *rcs_rev_tmp = strchr(rcs_rev, ' '); - if (LAUNCH_DATA_DICTIONARY == launch_data_get_type(msg)) { - launch_data_dict_iterate(msg, ipc_readmsg2, &rmc); - } else if (LAUNCH_DATA_STRING == launch_data_get_type(msg)) { - ipc_readmsg2(NULL, launch_data_get_string(msg), &rmc); + runtime_ktrace1(RTKT_LAUNCHD_BUG); + + if (!file) { + file = path; } else { - rmc.resp = launch_data_new_errno(EINVAL); + file += 1; } - if (NULL == rmc.resp) - rmc.resp = launch_data_new_errno(ENOSYS); - - launch_data_close_fds(msg); - - if (launchd_msg_send(rmc.c->conn, rmc.resp) == -1) { - if (errno == EAGAIN) { - kevent_mod(launchd_getfd(rmc.c->conn), EVFILT_WRITE, EV_ADD, 0, 0, &rmc.c->kqconn_callback); - } else { - syslog(LOG_DEBUG, "launchd_msg_send() == -1: %m"); - ipc_close(rmc.c); + if (!rcs_rev_tmp) { + strlcpy(buf, rcs_rev, sizeof(buf)); + } else { + strlcpy(buf, rcs_rev_tmp + 1, sizeof(buf)); + rcs_rev_tmp = strchr(buf, ' '); + if (rcs_rev_tmp) { + *rcs_rev_tmp = '\0'; } } - launch_data_free(rmc.resp); -} - - -static void ipc_readmsg2(launch_data_t data, const char *cmd, void *context) -{ - struct readmsg_context *rmc = context; - launch_data_t resp = NULL; - struct jobcb *j; - - if (rmc->resp) - return; - if (!strcmp(cmd, LAUNCH_KEY_STARTJOB)) { - TAILQ_FOREACH(j, &jobs, tqe) { - if (!strcmp(j->label, launch_data_get_string(data))) { - job_start(j); - resp = launch_data_new_errno(0); - } - } - if (NULL == resp) - resp = launch_data_new_errno(ESRCH); - } else if (!strcmp(cmd, LAUNCH_KEY_STOPJOB)) { - TAILQ_FOREACH(j, &jobs, tqe) { - if (!strcmp(j->label, launch_data_get_string(data))) { - job_stop(j); - resp = launch_data_new_errno(0); - } - } - if (NULL == resp) - resp = launch_data_new_errno(ESRCH); - } else if (!strcmp(cmd, LAUNCH_KEY_REMOVEJOB)) { - TAILQ_FOREACH(j, &jobs, tqe) { - if (!strcmp(j->label, launch_data_get_string(data))) { - job_remove(j); - resp = launch_data_new_errno(0); - } - } - if (NULL == resp) - resp = launch_data_new_errno(ESRCH); - } else if (!strcmp(cmd, LAUNCH_KEY_SUBMITJOB)) { - if (launch_data_get_type(data) == LAUNCH_DATA_ARRAY) { - launch_data_t tmp; - size_t i; - - resp = launch_data_alloc(LAUNCH_DATA_ARRAY); - for (i = 0; i < launch_data_array_get_count(data); i++) { - tmp = load_job(launch_data_array_get_index(data, i)); - launch_data_array_set_index(resp, tmp, i); - } - } else { - resp = load_job(data); - } - } else if (!strcmp(cmd, LAUNCH_KEY_UNSETUSERENVIRONMENT)) { - unsetenv(launch_data_get_string(data)); - resp = launch_data_new_errno(0); - } else if (!strcmp(cmd, LAUNCH_KEY_GETUSERENVIRONMENT)) { - char **tmpenviron = environ; - resp = launch_data_alloc(LAUNCH_DATA_DICTIONARY); - for (; *tmpenviron; tmpenviron++) { - char envkey[1024]; - launch_data_t s = launch_data_alloc(LAUNCH_DATA_STRING); - launch_data_set_string(s, strchr(*tmpenviron, '=') + 1); - strncpy(envkey, *tmpenviron, sizeof(envkey)); - *(strchr(envkey, '=')) = '\0'; - launch_data_dict_insert(resp, s, envkey); - } - } else if (!strcmp(cmd, LAUNCH_KEY_SETUSERENVIRONMENT)) { - launch_data_dict_iterate(data, set_user_env, NULL); - resp = launch_data_new_errno(0); - } else if (!strcmp(cmd, LAUNCH_KEY_CHECKIN)) { - if (rmc->c->j) { - resp = launch_data_copy(rmc->c->j->ldj); - if (NULL == launch_data_dict_lookup(resp, LAUNCH_JOBKEY_TIMEOUT)) { - launch_data_t to = launch_data_new_integer(LAUNCHD_MIN_JOB_RUN_TIME); - launch_data_dict_insert(resp, to, LAUNCH_JOBKEY_TIMEOUT); - } - rmc->c->j->checkedin = true; - } else { - resp = launch_data_new_errno(EACCES); - } - } else if (!strcmp(cmd, LAUNCH_KEY_RELOADTTYS)) { - update_ttys(); - resp = launch_data_new_errno(0); - } else if (!strcmp(cmd, LAUNCH_KEY_SHUTDOWN)) { - do_shutdown(); - resp = launch_data_new_errno(0); - } else if (!strcmp(cmd, LAUNCH_KEY_GETJOBS)) { - resp = get_jobs(NULL); - launch_data_revoke_fds(resp); - } else if (!strcmp(cmd, LAUNCH_KEY_GETRESOURCELIMITS)) { - resp = adjust_rlimits(NULL); - } else if (!strcmp(cmd, LAUNCH_KEY_SETRESOURCELIMITS)) { - resp = adjust_rlimits(data); - } else if (!strcmp(cmd, LAUNCH_KEY_GETJOB)) { - resp = get_jobs(launch_data_get_string(data)); - launch_data_revoke_fds(resp); - } else if (!strcmp(cmd, LAUNCH_KEY_GETJOBWITHHANDLES)) { - resp = get_jobs(launch_data_get_string(data)); - } else if (!strcmp(cmd, LAUNCH_KEY_SETLOGMASK)) { - resp = launch_data_new_integer(setlogmask(launch_data_get_integer(data))); - } else if (!strcmp(cmd, LAUNCH_KEY_GETLOGMASK)) { - int oldmask = setlogmask(LOG_UPTO(LOG_DEBUG)); - resp = launch_data_new_integer(oldmask); - setlogmask(oldmask); - } else if (!strcmp(cmd, LAUNCH_KEY_SETUMASK)) { - resp = launch_data_new_integer(umask(launch_data_get_integer(data))); - } else if (!strcmp(cmd, LAUNCH_KEY_GETUMASK)) { - mode_t oldmask = umask(0); - resp = launch_data_new_integer(oldmask); - umask(oldmask); - } else if (!strcmp(cmd, LAUNCH_KEY_GETRUSAGESELF)) { - struct rusage rusage; - getrusage(RUSAGE_SELF, &rusage); - resp = launch_data_new_opaque(&rusage, sizeof(rusage)); - } else if (!strcmp(cmd, LAUNCH_KEY_GETRUSAGECHILDREN)) { - struct rusage rusage; - getrusage(RUSAGE_CHILDREN, &rusage); - resp = launch_data_new_opaque(&rusage, sizeof(rusage)); - } else if (!strcmp(cmd, LAUNCH_KEY_SETSTDOUT)) { - resp = setstdio(STDOUT_FILENO, data); - } else if (!strcmp(cmd, LAUNCH_KEY_SETSTDERR)) { - resp = setstdio(STDERR_FILENO, data); - } else if (!strcmp(cmd, LAUNCH_KEY_BATCHCONTROL)) { - batch_job_enable(launch_data_get_bool(data), rmc->c); - resp = launch_data_new_errno(0); - } else if (!strcmp(cmd, LAUNCH_KEY_BATCHQUERY)) { - resp = launch_data_alloc(LAUNCH_DATA_BOOL); - launch_data_set_bool(resp, batch_disabler_count == 0); - } - - rmc->resp = resp; -} - -static launch_data_t 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) { - dup2(launch_data_get_fd(o), d); - } else { - launch_data_set_errno(resp, EINVAL); - } - - return resp; -} - -static 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; - } -} - -static launch_data_t load_job(launch_data_t pload) -{ - launch_data_t tmp, resp; - const char *label; - struct jobcb *j; - bool startnow, hasprog = false, hasprogargs = false; - - if ((label = job_get_string(pload, LAUNCH_JOBKEY_LABEL))) { - TAILQ_FOREACH(j, &jobs, tqe) { - if (!strcmp(j->label, label)) { - resp = launch_data_new_errno(EEXIST); - goto out; - } - } - } else { - resp = launch_data_new_errno(EINVAL); - goto out; - } - - if (launch_data_dict_lookup(pload, LAUNCH_JOBKEY_PROGRAM)) - hasprog = true; - if (launch_data_dict_lookup(pload, LAUNCH_JOBKEY_PROGRAMARGUMENTS)) - hasprogargs = true; - - if (!hasprog && !hasprogargs) { - resp = launch_data_new_errno(EINVAL); - goto out; - } - - j = calloc(1, sizeof(struct jobcb) + strlen(label) + 1); - strcpy(j->label, label); - j->ldj = launch_data_copy(pload); - launch_data_revoke_fds(pload); - j->kqjob_callback = job_callback; - - - if (launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_ONDEMAND) == NULL) { - tmp = launch_data_alloc(LAUNCH_DATA_BOOL); - launch_data_set_bool(tmp, true); - launch_data_dict_insert(j->ldj, tmp, LAUNCH_JOBKEY_ONDEMAND); - } - - TAILQ_INSERT_TAIL(&jobs, j, tqe); - - j->debug = job_get_bool(j->ldj, LAUNCH_JOBKEY_DEBUG); - - startnow = !job_get_bool(j->ldj, LAUNCH_JOBKEY_ONDEMAND); - - if (job_get_bool(j->ldj, LAUNCH_JOBKEY_RUNATLOAD)) - startnow = true; - - if ((tmp = launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_QUEUEDIRECTORIES))) { - size_t i; - - j->qdirs_cnt = launch_data_array_get_count(tmp); - j->qdirs = malloc(sizeof(int) * j->qdirs_cnt); - - for (i = 0; i < j->qdirs_cnt; i++) { - const char *thepath = launch_data_get_string(launch_data_array_get_index(tmp, i)); - - if (-1 == (j->qdirs[i] = _fd(open(thepath, O_EVTONLY)))) - job_log_error(j, LOG_ERR, "open(\"%s\", O_EVTONLY)", thepath); - } - - } - - if ((tmp = launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_STARTINTERVAL))) { - j->start_interval = launch_data_get_integer(tmp); - - if (j->start_interval == 0) - job_log(j, LOG_WARNING, "StartInterval is zero, ignoring"); - else if (-1 == kevent_mod((uintptr_t)&j->start_interval, EVFILT_TIMER, EV_ADD, NOTE_SECONDS, j->start_interval, &j->kqjob_callback)) - job_log_error(j, LOG_ERR, "adding kevent timer"); - } - - if ((tmp = launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_STARTCALENDARINTERVAL))) { - launch_data_t tmp_k; - - j->start_cal_interval = calloc(1, sizeof(struct tm)); - j->start_cal_interval->tm_min = -1; - j->start_cal_interval->tm_hour = -1; - j->start_cal_interval->tm_mday = -1; - j->start_cal_interval->tm_wday = -1; - j->start_cal_interval->tm_mon = -1; - - if (LAUNCH_DATA_DICTIONARY == launch_data_get_type(tmp)) { - if ((tmp_k = launch_data_dict_lookup(tmp, LAUNCH_JOBKEY_CAL_MINUTE))) - j->start_cal_interval->tm_min = launch_data_get_integer(tmp_k); - if ((tmp_k = launch_data_dict_lookup(tmp, LAUNCH_JOBKEY_CAL_HOUR))) - j->start_cal_interval->tm_hour = launch_data_get_integer(tmp_k); - if ((tmp_k = launch_data_dict_lookup(tmp, LAUNCH_JOBKEY_CAL_DAY))) - j->start_cal_interval->tm_mday = launch_data_get_integer(tmp_k); - if ((tmp_k = launch_data_dict_lookup(tmp, LAUNCH_JOBKEY_CAL_WEEKDAY))) - j->start_cal_interval->tm_wday = launch_data_get_integer(tmp_k); - if ((tmp_k = launch_data_dict_lookup(tmp, LAUNCH_JOBKEY_CAL_MONTH))) - j->start_cal_interval->tm_mon = launch_data_get_integer(tmp_k); - } - - job_set_alarm(j); - } - - if ((tmp = launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_WATCHPATHS))) { - size_t i; - - j->vnodes_cnt = launch_data_array_get_count(tmp); - j->vnodes = malloc(sizeof(int) * j->vnodes_cnt); - - for (i = 0; i < j->vnodes_cnt; i++) { - const char *thepath = launch_data_get_string(launch_data_array_get_index(tmp, i)); - - if (-1 == (j->vnodes[i] = _fd(open(thepath, O_EVTONLY)))) - job_log_error(j, LOG_ERR, "open(\"%s\", O_EVTONLY)", thepath); - } - - } - - if ((tmp = launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_USERENVIRONMENTVARIABLES))) - launch_data_dict_iterate(tmp, setup_job_env, NULL); - - if (job_get_bool(j->ldj, LAUNCH_JOBKEY_ONDEMAND)) - job_watch(j); - - if (startnow) - job_start(j); - - resp = launch_data_new_errno(0); -out: - return resp; -} - -static launch_data_t get_jobs(const char *which) -{ - struct jobcb *j; - launch_data_t tmp, resp = NULL; - - if (which) { - TAILQ_FOREACH(j, &jobs, tqe) { - if (!strcmp(which, j->label)) - resp = launch_data_copy(j->ldj); - } - if (resp == NULL) - resp = launch_data_new_errno(ESRCH); - } else { - resp = launch_data_alloc(LAUNCH_DATA_DICTIONARY); - - TAILQ_FOREACH(j, &jobs, tqe) { - tmp = launch_data_copy(j->ldj); - launch_data_dict_insert(resp, tmp, j->label); - } - } - - return resp; -} - -static void usage(FILE *where) -{ - fprintf(where, "%s: [-d] [-- command [args ...]]\n", getprogname()); - fprintf(where, "\t-d\tdaemonize\n"); - fprintf(where, "\t-h\tthis usage statement\n"); - - if (where == stdout) - exit(EXIT_SUCCESS); -} - -#ifdef EVFILT_MACH_IMPLEMENTED -static void **machcbtable = NULL; -static size_t machcbtable_cnt = 0; -static int machcbreadfd = -1; -static int machcbwritefd = -1; -static mach_port_t mach_demand_port_set = MACH_PORT_NULL; -static pthread_t mach_demand_thread; - -static void mach_callback(void *obj __attribute__((unused)), struct kevent *kev __attribute__((unused))) -{ - struct kevent mkev; - mach_port_t mp; - - read(machcbreadfd, &mp, sizeof(mp)); - - EV_SET(&mkev, mp, EVFILT_MACHPORT, 0, 0, 0, machcbtable[MACH_PORT_INDEX(mp)]); - - (*((kq_callback *)mkev.udata))(mkev.udata, &mkev); -} -#endif - -int kevent_mod(uintptr_t ident, short filter, u_short flags, u_int fflags, intptr_t data, void *udata) -{ - struct kevent kev; - int q = mainkq; -#ifdef EVFILT_MACH_IMPLEMENTED - kern_return_t kr; - pthread_attr_t attr; - int pthr_r, pfds[2]; -#endif - - if (EVFILT_TIMER == filter || EVFILT_VNODE == filter) - q = asynckq; - - if (flags & EV_ADD && NULL == udata) { - syslog(LOG_ERR, "%s(): kev.udata == NULL!!!", __func__); - syslog(LOG_ERR, "kev: ident %d filter %d flags 0x%x fflags 0x%x", - ident, filter, flags, fflags); - errno = EINVAL; - return -1; - } - -#ifdef EVFILT_MACH_IMPLEMENTED - if (filter != EVFILT_MACHPORT) { -#endif -#ifdef PID1_REAP_ADOPTED_CHILDREN - if (filter == EVFILT_PROC && getpid() == 1) - return 0; -#endif - EV_SET(&kev, ident, filter, flags, fflags, data, udata); - return kevent(q, &kev, 1, NULL, 0, NULL); -#ifdef EVFILT_MACH_IMPLEMENTED - } - - if (machcbtable == NULL) { - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - - pthr_r = pthread_create(&mach_demand_thread, &attr, mach_demand_loop, NULL); - if (pthr_r != 0) { - syslog(LOG_ERR, "pthread_create(mach_demand_loop): %s", strerror(pthr_r)); - exit(EXIT_FAILURE); - } - - pthread_attr_destroy(&attr); - - machcbtable = malloc(0); - pipe(pfds); - machcbwritefd = _fd(pfds[1]); - machcbreadfd = _fd(pfds[0]); - kevent_mod(machcbreadfd, EVFILT_READ, EV_ADD, 0, 0, &kqmach_callback); - kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_PORT_SET, &mach_demand_port_set); - if (kr != KERN_SUCCESS) { - syslog(LOG_ERR, "mach_port_allocate(demand_port_set): %s", mach_error_string(kr)); - exit(EXIT_FAILURE); - } - } - - if (flags & EV_ADD) { - kr = mach_port_move_member(mach_task_self(), ident, mach_demand_port_set); - if (kr != KERN_SUCCESS) { - syslog(LOG_ERR, "mach_port_move_member(): %s", mach_error_string(kr)); - exit(EXIT_FAILURE); - } - - if (MACH_PORT_INDEX(ident) > machcbtable_cnt) - machcbtable = realloc(machcbtable, MACH_PORT_INDEX(ident) * sizeof(void *)); - - machcbtable[MACH_PORT_INDEX(ident)] = udata; - } else if (flags & EV_DELETE) { - kr = mach_port_move_member(mach_task_self(), ident, MACH_PORT_NULL); - if (kr != KERN_SUCCESS) { - syslog(LOG_ERR, "mach_port_move_member(): %s", mach_error_string(kr)); - exit(EXIT_FAILURE); - } - } else { - syslog(LOG_DEBUG, "kevent_mod(EVFILT_MACHPORT) with flags: %d", flags); - errno = EINVAL; - return -1; - } - - return 0; -#endif -} - -static int _fd(int fd) -{ - if (fd >= 0) - fcntl(fd, F_SETFD, 1); - return fd; -} - -static void ipc_close(struct conncb *c) -{ - batch_job_enable(true, c); - - TAILQ_REMOVE(&connections, c, tqe); - launchd_close(c->conn); - free(c); -} - -static void setup_job_env(launch_data_t obj, const char *key, void *context __attribute__((unused))) -{ - if (LAUNCH_DATA_STRING == launch_data_get_type(obj)) - setenv(key, launch_data_get_string(obj), 1); -} - -static void unsetup_job_env(launch_data_t obj, const char *key, void *context __attribute__((unused))) -{ - if (LAUNCH_DATA_STRING == launch_data_get_type(obj)) - unsetenv(key); -} - -static void job_reap(struct jobcb *j) -{ - bool od = job_get_bool(j->ldj, LAUNCH_JOBKEY_ONDEMAND); - time_t td = time(NULL) - j->start_time; - bool bad_exit = false; - int status; - - job_log(j, LOG_DEBUG, "Reaping"); - - if (j->execfd) { - close(j->execfd); - j->execfd = 0; - } - -#ifdef PID1_REAP_ADOPTED_CHILDREN - if (getpid() == 1) - status = pid1_child_exit_status; - else -#endif - if (-1 == waitpid(j->p, &status, 0)) { - job_log_error(j, LOG_ERR, "waitpid(%d, ...)", j->p); - return; - } - - if (WIFEXITED(status) && WEXITSTATUS(status) != 0) { - job_log(j, LOG_WARNING, "exited with exit code: %d", WEXITSTATUS(status)); - bad_exit = true; - } - - if (WIFSIGNALED(status)) { - int s = WTERMSIG(status); - if (SIGKILL == s || SIGTERM == s) { - job_log(j, LOG_NOTICE, "exited: %s", strsignal(s)); - } else { - job_log(j, LOG_WARNING, "exited abnormally: %s", strsignal(s)); - bad_exit = true; - } - } - - if (!od) { - if (td < LAUNCHD_MIN_JOB_RUN_TIME) { - job_log(j, LOG_WARNING, "respawning too quickly! throttling"); - bad_exit = true; - j->throttle = true; - } else if (td >= LAUNCHD_REWARD_JOB_RUN_TIME) { - job_log(j, LOG_INFO, "lived long enough, forgiving past exit failures"); - j->failed_exits = 0; - } - } - - if (bad_exit) - j->failed_exits++; - - if (j->failed_exits > 0) { - int failures_left = LAUNCHD_FAILED_EXITS_THRESHOLD - j->failed_exits; - if (failures_left) - job_log(j, LOG_WARNING, "%d more failure%s without living at least %d seconds will cause job removal", - failures_left, failures_left > 1 ? "s" : "", LAUNCHD_REWARD_JOB_RUN_TIME); - } - - total_children--; - j->p = 0; -} - -static bool job_restart_fitness_test(struct jobcb *j) -{ - bool od = job_get_bool(j->ldj, LAUNCH_JOBKEY_ONDEMAND); - - if (j->firstborn) { - job_log(j, LOG_DEBUG, "first born died, begin shutdown"); - do_shutdown(); - return false; - } else if (job_get_bool(j->ldj, LAUNCH_JOBKEY_SERVICEIPC) && !j->checkedin) { - job_log(j, LOG_WARNING, "failed to checkin"); - job_remove(j); - return false; - } else if (j->failed_exits >= LAUNCHD_FAILED_EXITS_THRESHOLD) { - job_log(j, LOG_WARNING, "too many failures in succession"); - job_remove(j); - return false; - } else if (od || shutdown_in_progress) { - if (!od && shutdown_in_progress) - job_log(j, LOG_NOTICE, "exited while shutdown is in progress, will not restart unless demand requires it"); - job_watch(j); - return false; - } - - return true; -} - -static void job_callback(void *obj, struct kevent *kev) -{ - struct jobcb *j = obj; - bool d = j->debug; - bool startnow = true; - int oldmask = 0; - - if (d) { - oldmask = setlogmask(LOG_UPTO(LOG_DEBUG)); - job_log(j, LOG_DEBUG, "log level debug temporarily enabled while processing job"); - } - - if (kev->filter == EVFILT_PROC) { - job_reap(j); - - startnow = job_restart_fitness_test(j); - - if (startnow && j->throttle) { - j->throttle = false; - job_log(j, LOG_WARNING, "will restart in %d seconds", LAUNCHD_MIN_JOB_RUN_TIME); - if (-1 == kevent_mod((uintptr_t)j, EVFILT_TIMER, EV_ADD|EV_ONESHOT, - NOTE_SECONDS, LAUNCHD_MIN_JOB_RUN_TIME, &j->kqjob_callback)) { - job_log_error(j, LOG_WARNING, "failed to setup timer callback!, starting now!"); - } else { - startnow = false; - } - } - } else if (kev->filter == EVFILT_TIMER && (void *)kev->ident == j->start_cal_interval) { - job_set_alarm(j); - } else if (kev->filter == EVFILT_VNODE) { - size_t i; - const char *thepath = NULL; - - for (i = 0; i < j->vnodes_cnt; i++) { - if (j->vnodes[i] == (int)kev->ident) { - launch_data_t ld_vnodes = launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_WATCHPATHS); - - thepath = launch_data_get_string(launch_data_array_get_index(ld_vnodes, i)); - - job_log(j, LOG_DEBUG, "watch path modified: %s", thepath); - - if ((NOTE_DELETE|NOTE_RENAME|NOTE_REVOKE) & kev->fflags) { - job_log(j, LOG_DEBUG, "watch path invalidated: %s", thepath); - close(j->vnodes[i]); - j->vnodes[i] = -1; /* this will get fixed in job_watch() */ - } - } - } - - for (i = 0; i < j->qdirs_cnt; i++) { - if (j->qdirs[i] == (int)kev->ident) { - launch_data_t ld_qdirs = launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_QUEUEDIRECTORIES); - int dcc_r; - - thepath = launch_data_get_string(launch_data_array_get_index(ld_qdirs, i)); - - job_log(j, LOG_DEBUG, "queue directory modified: %s", thepath); - - if (-1 == (dcc_r = dir_has_files(thepath))) { - job_log_error(j, LOG_ERR, "dir_has_files(\"%s\", ...)", thepath); - } else if (0 == dcc_r) { - job_log(j, LOG_DEBUG, "spurious wake up, directory empty: %s", thepath); - startnow = false; - } - } - } - /* if we get here, then the vnodes either wasn't a qdir, or if it was, it has entries in it */ - } else if (kev->filter == EVFILT_READ && (int)kev->ident == j->execfd) { - if (kev->data > 0) { - int e; - - read(j->execfd, &e, sizeof(e)); - errno = e; - job_log_error(j, LOG_ERR, "execve()"); - job_remove(j); - j = NULL; - startnow = false; - } else { - close(j->execfd); - j->execfd = 0; - } - startnow = false; - } - - if (startnow) - job_start(j); - - if (d) { - /* the job might have been removed, must not call job_log() */ - syslog(LOG_DEBUG, "restoring original log mask"); - setlogmask(oldmask); - } -} - -static void job_start(struct jobcb *j) -{ - int spair[2]; - int execspair[2]; - bool sipc; - char nbuf[64]; - pid_t c; - - job_log(j, LOG_DEBUG, "Starting"); - - if (j->p) { - job_log(j, LOG_DEBUG, "already running"); - return; - } - - j->checkedin = false; - - sipc = job_get_bool(j->ldj, LAUNCH_JOBKEY_SERVICEIPC); - - if (job_get_bool(j->ldj, LAUNCH_JOBKEY_INETDCOMPATIBILITY)) - sipc = true; - - if (sipc) - socketpair(AF_UNIX, SOCK_STREAM, 0, spair); - - socketpair(AF_UNIX, SOCK_STREAM, 0, execspair); - - time(&j->start_time); - - switch (c = fork_with_bootstrap_port(launchd_bootstrap_port)) { - case -1: - job_log_error(j, LOG_ERR, "fork() failed, will try again in one second"); - close(execspair[0]); - close(execspair[1]); - if (sipc) { - close(spair[0]); - close(spair[1]); - } - if (job_get_bool(j->ldj, LAUNCH_JOBKEY_ONDEMAND)) - job_ignore(j); - break; - case 0: - close(execspair[0]); - /* wait for our parent to say they've attached a kevent to us */ - read(_fd(execspair[1]), &c, sizeof(c)); - if (j->firstborn) { - setpgid(getpid(), getpid()); - if (isatty(STDIN_FILENO)) { - if (tcsetpgrp(STDIN_FILENO, getpid()) == -1) - job_log_error(j, LOG_WARNING, "tcsetpgrp()"); - } - } - - if (sipc) { - close(spair[0]); - sprintf(nbuf, "%d", spair[1]); - setenv(LAUNCHD_TRUSTED_FD_ENV, nbuf, 1); - } - job_start_child(j, execspair[1]); - break; - default: - close(execspair[1]); - j->execfd = _fd(execspair[0]); - if (sipc) { - close(spair[1]); - ipc_open(_fd(spair[0]), j); - } - if (kevent_mod(j->execfd, EVFILT_READ, EV_ADD, 0, 0, &j->kqjob_callback) == -1) - job_log_error(j, LOG_ERR, "kevent_mod(j->execfd): %m"); - if (kevent_mod(c, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, &j->kqjob_callback) == -1) { - job_log_error(j, LOG_ERR, "kevent()"); - job_reap(j); - } else { - j->p = c; - total_children++; - if (job_get_bool(j->ldj, LAUNCH_JOBKEY_ONDEMAND)) - job_ignore(j); - } - /* this unblocks the child and avoids a race - * between the above fork() and the kevent_mod() */ - write(j->execfd, &c, sizeof(c)); - break; - } -} - -static void job_start_child(struct jobcb *j, int execfd) -{ - launch_data_t ldpa = launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_PROGRAMARGUMENTS); - bool inetcompat = job_get_bool(j->ldj, LAUNCH_JOBKEY_INETDCOMPATIBILITY); - size_t i, argv_cnt; - const char **argv, *file2exec = "/usr/libexec/launchproxy"; - int r; - bool hasprog = false; - - job_setup_attributes(j); - - if (ldpa) { - argv_cnt = launch_data_array_get_count(ldpa); - argv = alloca((argv_cnt + 2) * sizeof(char *)); - for (i = 0; i < argv_cnt; i++) - argv[i + 1] = launch_data_get_string(launch_data_array_get_index(ldpa, i)); - argv[argv_cnt + 1] = NULL; - } else { - argv = alloca(3 * sizeof(char *)); - argv[1] = job_get_string(j->ldj, LAUNCH_JOBKEY_PROGRAM); - argv[2] = NULL; - } - - if (job_get_string(j->ldj, LAUNCH_JOBKEY_PROGRAM)) - hasprog = true; - - if (inetcompat) { - argv[0] = file2exec; - } else { - argv++; - file2exec = job_get_file2exec(j->ldj); - } - - if (hasprog) { - r = execv(file2exec, (char *const*)argv); - } else { - r = execvp(file2exec, (char *const*)argv); - } - - if (-1 == r) { - write(execfd, &errno, sizeof(errno)); - job_log_error(j, LOG_ERR, "execv%s(\"%s\", ...)", hasprog ? "" : "p", file2exec); - } - exit(EXIT_FAILURE); -} - -static void job_setup_attributes(struct jobcb *j) -{ - launch_data_t srl = launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_SOFTRESOURCELIMITS); - launch_data_t hrl = launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_HARDRESOURCELIMITS); - bool inetcompat = job_get_bool(j->ldj, LAUNCH_JOBKEY_INETDCOMPATIBILITY); - launch_data_t tmp; - size_t i; - const char *tmpstr; - struct group *gre = NULL; - gid_t gre_g = 0; - static const struct { - const char *key; - int val; - } limits[] = { - { LAUNCH_JOBKEY_RESOURCELIMIT_CORE, RLIMIT_CORE }, - { LAUNCH_JOBKEY_RESOURCELIMIT_CPU, RLIMIT_CPU }, - { LAUNCH_JOBKEY_RESOURCELIMIT_DATA, RLIMIT_DATA }, - { LAUNCH_JOBKEY_RESOURCELIMIT_FSIZE, RLIMIT_FSIZE }, - { LAUNCH_JOBKEY_RESOURCELIMIT_MEMLOCK, RLIMIT_MEMLOCK }, - { LAUNCH_JOBKEY_RESOURCELIMIT_NOFILE, RLIMIT_NOFILE }, - { LAUNCH_JOBKEY_RESOURCELIMIT_NPROC, RLIMIT_NPROC }, - { LAUNCH_JOBKEY_RESOURCELIMIT_RSS, RLIMIT_RSS }, - { LAUNCH_JOBKEY_RESOURCELIMIT_STACK, RLIMIT_STACK }, - }; - - setpriority(PRIO_PROCESS, 0, job_get_integer(j->ldj, LAUNCH_JOBKEY_NICE)); - - if (srl || hrl) { - for (i = 0; i < (sizeof(limits) / sizeof(limits[0])); i++) { - struct rlimit rl; - - if (getrlimit(limits[i].val, &rl) == -1) { - job_log_error(j, LOG_WARNING, "getrlimit()"); - continue; - } - - if (hrl) - rl.rlim_max = job_get_integer(hrl, limits[i].key); - if (srl) - rl.rlim_cur = job_get_integer(srl, limits[i].key); - - if (setrlimit(limits[i].val, &rl) == -1) - job_log_error(j, LOG_WARNING, "setrlimit()"); - } - } - - if (!inetcompat && job_get_bool(j->ldj, LAUNCH_JOBKEY_SESSIONCREATE)) - launchd_SessionCreate(job_get_file2exec(j->ldj)); - - if (job_get_bool(j->ldj, LAUNCH_JOBKEY_LOWPRIORITYIO)) { - int lowprimib[] = { CTL_KERN, KERN_PROC_LOW_PRI_IO }; - int val = 1; - - if (sysctl(lowprimib, sizeof(lowprimib) / sizeof(lowprimib[0]), NULL, NULL, &val, sizeof(val)) == -1) - job_log_error(j, LOG_WARNING, "sysctl(\"%s\")", "kern.proc_low_pri_io"); - } - if ((tmpstr = job_get_string(j->ldj, LAUNCH_JOBKEY_ROOTDIRECTORY))) { - chroot(tmpstr); - chdir("."); - } - if ((tmpstr = job_get_string(j->ldj, LAUNCH_JOBKEY_GROUPNAME))) { - gre = getgrnam(tmpstr); - if (gre) { - gre_g = gre->gr_gid; - if (-1 == setgid(gre_g)) { - job_log_error(j, LOG_ERR, "setgid(%d)", gre_g); - exit(EXIT_FAILURE); - } - } else { - job_log(j, LOG_ERR, "getgrnam(\"%s\") failed", tmpstr); - exit(EXIT_FAILURE); - } - } - if ((tmpstr = job_get_string(j->ldj, LAUNCH_JOBKEY_USERNAME))) { - struct passwd *pwe = getpwnam(tmpstr); - if (pwe) { - uid_t pwe_u = pwe->pw_uid; - uid_t pwe_g = pwe->pw_gid; - - if (pwe->pw_expire && time(NULL) >= pwe->pw_expire) { - job_log(j, LOG_ERR, "expired account: %s", tmpstr); - exit(EXIT_FAILURE); - } - if (job_get_bool(j->ldj, LAUNCH_JOBKEY_INITGROUPS)) { - if (-1 == initgroups(tmpstr, gre ? gre_g : pwe_g)) { - job_log_error(j, LOG_ERR, "initgroups()"); - exit(EXIT_FAILURE); - } - } - if (!gre) { - if (-1 == setgid(pwe_g)) { - job_log_error(j, LOG_ERR, "setgid(%d)", pwe_g); - exit(EXIT_FAILURE); - } - } - if (-1 == setuid(pwe_u)) { - job_log_error(j, LOG_ERR, "setuid(%d)", pwe_u); - exit(EXIT_FAILURE); - } - } else { - job_log(j, LOG_WARNING, "getpwnam(\"%s\") failed", tmpstr); - exit(EXIT_FAILURE); - } - } - if ((tmpstr = job_get_string(j->ldj, LAUNCH_JOBKEY_WORKINGDIRECTORY))) - chdir(tmpstr); - if (launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_UMASK)) - umask(job_get_integer(j->ldj, LAUNCH_JOBKEY_UMASK)); - if ((tmpstr = job_get_string(j->ldj, LAUNCH_JOBKEY_STANDARDOUTPATH))) { - int sofd = open(tmpstr, O_WRONLY|O_APPEND|O_CREAT, DEFFILEMODE); - if (sofd == -1) { - job_log_error(j, LOG_WARNING, "open(\"%s\", ...)", tmpstr); - } else { - dup2(sofd, STDOUT_FILENO); - close(sofd); - } - } - if ((tmpstr = job_get_string(j->ldj, LAUNCH_JOBKEY_STANDARDERRORPATH))) { - int sefd = open(tmpstr, O_WRONLY|O_APPEND|O_CREAT, DEFFILEMODE); - if (sefd == -1) { - job_log_error(j, LOG_WARNING, "open(\"%s\", ...)", tmpstr); - } else { - dup2(sefd, STDERR_FILENO); - close(sefd); - } - } - if ((tmp = launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_ENVIRONMENTVARIABLES))) - launch_data_dict_iterate(tmp, setup_job_env, NULL); - - setsid(); -} - -#ifdef PID1_REAP_ADOPTED_CHILDREN -__private_extern__ int pid1_child_exit_status = 0; -static void pid1waitpid(void) -{ - pid_t p; - - while ((p = waitpid(-1, &pid1_child_exit_status, WNOHANG)) > 0) { - if (!launchd_check_pid(p)) - init_check_pid(p); - } -} -#endif - -static void do_shutdown(void) -{ - struct jobcb *j; - - shutdown_in_progress = true; - - kevent_mod(asynckq, EVFILT_READ, EV_DISABLE, 0, 0, &kqasync_callback); - - TAILQ_FOREACH(j, &jobs, tqe) - job_stop(j); - - if (getpid() == 1) { - catatonia(); - mach_start_shutdown(SIGTERM); - } -} - -static void signal_callback(void *obj __attribute__((unused)), struct kevent *kev) -{ - switch (kev->ident) { - case SIGHUP: - update_ttys(); - reload_launchd_config(); - break; - case SIGTERM: - do_shutdown(); - break; -#ifdef PID1_REAP_ADOPTED_CHILDREN - case SIGCHLD: - /* Please automatically reap processes reparented to PID 1 */ - if (getpid() == 1) - pid1waitpid(); - break; -#endif - default: - break; - } -} - -static void fs_callback(void) -{ - static bool mounted_volfs = false; - - if (1 != getpid()) - mounted_volfs = true; - - if (pending_stdout) { - int fd = open(pending_stdout, O_CREAT|O_APPEND|O_WRONLY, DEFFILEMODE); - if (fd != -1) { - dup2(fd, STDOUT_FILENO); - close(fd); - free(pending_stdout); - pending_stdout = NULL; - } - } - if (pending_stderr) { - int fd = open(pending_stderr, O_CREAT|O_APPEND|O_WRONLY, DEFFILEMODE); - if (fd != -1) { - dup2(fd, STDERR_FILENO); - close(fd); - free(pending_stderr); - pending_stderr = NULL; - } - } - - if (!mounted_volfs) { - int r = mount("volfs", VOLFSDIR, MNT_RDONLY, NULL); - - if (-1 == r && errno == ENOENT) { - mkdir(VOLFSDIR, ACCESSPERMS & ~(S_IWUSR|S_IWGRP|S_IWOTH)); - r = mount("volfs", VOLFSDIR, MNT_RDONLY, NULL); - } - - if (-1 == r) { - syslog(LOG_WARNING, "mount(\"%s\", \"%s\", ...): %m", "volfs", VOLFSDIR); - } else { - mounted_volfs = true; - } - } - - if (!launchd_inited) - launchd_server_init(false); -} - -static void readcfg_callback(void *obj __attribute__((unused)), struct kevent *kev __attribute__((unused))) -{ - int status; - -#ifdef PID1_REAP_ADOPTED_CHILDREN - if (getpid() == 1) - status = pid1_child_exit_status; - else -#endif - if (-1 == waitpid(readcfg_pid, &status, 0)) { - syslog(LOG_WARNING, "waitpid(readcfg_pid, ...): %m"); - return; - } - - readcfg_pid = 0; - - if (WIFEXITED(status)) { - if (WEXITSTATUS(status)) - syslog(LOG_WARNING, "Unable to read launchd.conf: launchctl exited with status: %d", WEXITSTATUS(status)); - } else if (WIFSIGNALED(status)) { - syslog(LOG_WARNING, "Unable to read launchd.conf: launchctl exited abnormally: %s", strsignal(WTERMSIG(status))); - } else { - syslog(LOG_WARNING, "Unable to read launchd.conf: launchctl exited abnormally"); - } -} - -#ifdef EVFILT_MACH_IMPLEMENTED -static void *mach_demand_loop(void *arg __attribute__((unused))) -{ - mach_msg_empty_rcv_t dummy; - kern_return_t kr; - mach_port_name_array_t members; - mach_msg_type_number_t membersCnt; - mach_port_status_t status; - mach_msg_type_number_t statusCnt; - unsigned int i; - - for (;;) { - - /* - * Receive indication of message on demand service - * ports without actually receiving the message (we'll - * let the actual server do that. - */ - kr = mach_msg(&dummy.header, MACH_RCV_MSG|MACH_RCV_LARGE, - 0, 0, mach_demand_port_set, 0, MACH_PORT_NULL); - if (kr != MACH_RCV_TOO_LARGE) { - syslog(LOG_WARNING, "%s(): mach_msg(): %s", __func__, mach_error_string(kr)); - continue; - } - - /* - * Some port(s) now have messages on them, find out - * which ones (there is no indication of which port - * triggered in the MACH_RCV_TOO_LARGE indication). - */ - kr = mach_port_get_set_status(mach_task_self(), - mach_demand_port_set, &members, &membersCnt); - if (kr != KERN_SUCCESS) { - syslog(LOG_WARNING, "%s(): mach_port_get_set_status(): %s", __func__, mach_error_string(kr)); - continue; - } - - for (i = 0; i < membersCnt; i++) { - statusCnt = MACH_PORT_RECEIVE_STATUS_COUNT; - kr = mach_port_get_attributes(mach_task_self(), members[i], - MACH_PORT_RECEIVE_STATUS, (mach_port_info_t)&status, &statusCnt); - if (kr != KERN_SUCCESS) { - syslog(LOG_WARNING, "%s(): mach_port_get_attributes(): %s", __func__, mach_error_string(kr)); - continue; - } - - /* - * For each port with messages, take it out of the - * demand service portset, and inform the main thread - * that it might have to start the server responsible - * for it. - */ - if (status.mps_msgcount) { - kr = mach_port_move_member(mach_task_self(), members[i], MACH_PORT_NULL); - if (kr != KERN_SUCCESS) { - syslog(LOG_WARNING, "%s(): mach_port_move_member(): %s", __func__, mach_error_string(kr)); - continue; - } - write(machcbwritefd, &(members[i]), sizeof(members[i])); - } - } - - kr = vm_deallocate(mach_task_self(), (vm_address_t) members, - (vm_size_t) membersCnt * sizeof(mach_port_name_t)); - if (kr != KERN_SUCCESS) { - syslog(LOG_WARNING, "%s(): vm_deallocate(): %s", __func__, mach_error_string(kr)); - continue; - } - } - - return NULL; -} -#endif - -static void reload_launchd_config(void) -{ - struct stat sb; - static char *ldconf = PID1LAUNCHD_CONF; - const char *h = getenv("HOME"); - - if (h && ldconf == PID1LAUNCHD_CONF) - asprintf(&ldconf, "%s/%s", h, LAUNCHD_CONF); - - if (!ldconf) - return; - - if (lstat(ldconf, &sb) == 0) { - int spair[2]; - socketpair(AF_UNIX, SOCK_STREAM, 0, spair); - readcfg_pid = fork_with_bootstrap_port(launchd_bootstrap_port); - if (readcfg_pid == 0) { - char nbuf[100]; - close(spair[0]); - sprintf(nbuf, "%d", spair[1]); - setenv(LAUNCHD_TRUSTED_FD_ENV, nbuf, 1); - int fd = open(ldconf, O_RDONLY); - if (fd == -1) { - syslog(LOG_ERR, "open(\"%s\"): %m", ldconf); - exit(EXIT_FAILURE); - } - dup2(fd, STDIN_FILENO); - close(fd); - execl(LAUNCHCTL_PATH, LAUNCHCTL_PATH, NULL); - syslog(LOG_ERR, "execl(\"%s\", ...): %m", LAUNCHCTL_PATH); - exit(EXIT_FAILURE); - } else if (readcfg_pid == -1) { - close(spair[0]); - close(spair[1]); - syslog(LOG_ERR, "fork(): %m"); - readcfg_pid = 0; - } else { - close(spair[1]); - ipc_open(_fd(spair[0]), NULL); - if (kevent_mod(readcfg_pid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, &kqreadcfg_callback) == -1) - syslog(LOG_ERR, "kevent_mod(EVFILT_PROC, &kqreadcfg_callback): %m"); - } - } -} - -static void conceive_firstborn(char *argv[]) -{ - launch_data_t r, d = launch_data_alloc(LAUNCH_DATA_DICTIONARY); - launch_data_t args = launch_data_alloc(LAUNCH_DATA_ARRAY); - launch_data_t l = launch_data_new_string("com.apple.launchd.firstborn"); - size_t i; - - for (i = 0; *argv; argv++, i++) - launch_data_array_set_index(args, launch_data_new_string(*argv), i); - - launch_data_dict_insert(d, args, LAUNCH_JOBKEY_PROGRAMARGUMENTS); - launch_data_dict_insert(d, l, LAUNCH_JOBKEY_LABEL); - - r = load_job(d); - - launch_data_free(r); - launch_data_free(d); - - TAILQ_FIRST(&jobs)->firstborn = true; -} - -static void loopback_setup(void) -{ - struct ifaliasreq ifra; - struct in6_aliasreq ifra6; - struct ifreq ifr; - int s, s6; - - memset(&ifr, 0, sizeof(ifr)); - strcpy(ifr.ifr_name, "lo0"); - - if (-1 == (s = socket(AF_INET, SOCK_DGRAM, 0))) - syslog(LOG_ERR, "%s: socket(%s, ...): %m", __PRETTY_FUNCTION__, "AF_INET"); - if (-1 == (s6 = socket(AF_INET6, SOCK_DGRAM, 0))) - syslog(LOG_ERR, "%s: socket(%s, ...): %m", __PRETTY_FUNCTION__, "AF_INET6"); - - if (ioctl(s, SIOCGIFFLAGS, &ifr) == -1) { - syslog(LOG_ERR, "ioctl(SIOCGIFFLAGS): %m"); - } else { - ifr.ifr_flags |= IFF_UP; - - if (ioctl(s, SIOCSIFFLAGS, &ifr) == -1) - syslog(LOG_ERR, "ioctl(SIOCSIFFLAGS): %m"); - } - - memset(&ifr, 0, sizeof(ifr)); - strcpy(ifr.ifr_name, "lo0"); - - if (ioctl(s6, SIOCGIFFLAGS, &ifr) == -1) { - syslog(LOG_ERR, "ioctl(SIOCGIFFLAGS): %m"); - } else { - ifr.ifr_flags |= IFF_UP; - - if (ioctl(s6, SIOCSIFFLAGS, &ifr) == -1) - syslog(LOG_ERR, "ioctl(SIOCSIFFLAGS): %m"); - } - - memset(&ifra, 0, sizeof(ifra)); - strcpy(ifra.ifra_name, "lo0"); - - ((struct sockaddr_in *)&ifra.ifra_addr)->sin_family = AF_INET; - ((struct sockaddr_in *)&ifra.ifra_addr)->sin_addr.s_addr = htonl(INADDR_LOOPBACK); - ((struct sockaddr_in *)&ifra.ifra_addr)->sin_len = sizeof(struct sockaddr_in); - ((struct sockaddr_in *)&ifra.ifra_mask)->sin_family = AF_INET; - ((struct sockaddr_in *)&ifra.ifra_mask)->sin_addr.s_addr = htonl(IN_CLASSA_NET); - ((struct sockaddr_in *)&ifra.ifra_mask)->sin_len = sizeof(struct sockaddr_in); - - if (ioctl(s, SIOCAIFADDR, &ifra) == -1) - syslog(LOG_ERR, "ioctl(SIOCAIFADDR ipv4): %m"); - - memset(&ifra6, 0, sizeof(ifra6)); - strcpy(ifra6.ifra_name, "lo0"); - - ifra6.ifra_addr.sin6_family = AF_INET6; - ifra6.ifra_addr.sin6_addr = in6addr_loopback; - ifra6.ifra_addr.sin6_len = sizeof(struct sockaddr_in6); - ifra6.ifra_prefixmask.sin6_family = AF_INET6; - memset(&ifra6.ifra_prefixmask.sin6_addr, 0xff, sizeof(struct in6_addr)); - ifra6.ifra_prefixmask.sin6_len = sizeof(struct sockaddr_in6); - ifra6.ifra_lifetime.ia6t_vltime = ND6_INFINITE_LIFETIME; - ifra6.ifra_lifetime.ia6t_pltime = ND6_INFINITE_LIFETIME; - - if (ioctl(s6, SIOCAIFADDR_IN6, &ifra6) == -1) - syslog(LOG_ERR, "ioctl(SIOCAIFADDR ipv6): %m"); - - close(s); - close(s6); -} - -static void workaround3048875(int argc, char *argv[]) -{ - int i; - char **ap, *newargv[100], *p = argv[1]; - - if (argc == 1 || argc > 2) - return; - - newargv[0] = argv[0]; - for (ap = newargv + 1, i = 1; ap < &newargv[100]; ap++, i++) { - if ((*ap = strsep(&p, " \t")) == NULL) - break; - if (**ap == '\0') { - *ap = NULL; - break; - } - } - - if (argc == i) - return; - - execv(newargv[0], newargv); -} - -static launch_data_t adjust_rlimits(launch_data_t in) -{ - static struct rlimit *l = NULL; - static size_t lsz = sizeof(struct rlimit) * RLIM_NLIMITS; - struct rlimit *ltmp; - size_t i,ltmpsz; - - if (l == NULL) { - l = malloc(lsz); - for (i = 0; i < RLIM_NLIMITS; i++) { - if (getrlimit(i, l + i) == -1) - syslog(LOG_WARNING, "getrlimit(): %m"); - } - } - - if (in) { - ltmp = launch_data_get_opaque(in); - ltmpsz = launch_data_get_opaque_size(in); - - if (ltmpsz > lsz) { - syslog(LOG_WARNING, "Too much rlimit data sent!"); - ltmpsz = lsz; - } - - for (i = 0; i < (ltmpsz / sizeof(struct rlimit)); i++) { - if (ltmp[i].rlim_cur == l[i].rlim_cur && ltmp[i].rlim_max == l[i].rlim_max) - continue; - - if (readcfg_pid && getpid() == 1) { - int gmib[] = { CTL_KERN, KERN_MAXPROC }; - int pmib[] = { CTL_KERN, KERN_MAXPROCPERUID }; - const char *gstr = "kern.maxproc"; - const char *pstr = "kern.maxprocperuid"; - int gval = ltmp[i].rlim_max; - int pval = ltmp[i].rlim_cur; - switch (i) { - case RLIMIT_NOFILE: - gmib[1] = KERN_MAXFILES; - pmib[1] = KERN_MAXFILESPERPROC; - gstr = "kern.maxfiles"; - pstr = "kern.maxfilesperproc"; - break; - case RLIMIT_NPROC: - /* kernel will not clamp to this value, we must */ - if (gval > (2048 + 20)) - gval = 2048 + 20; - break; - default: - break; - } - if (sysctl(gmib, 2, NULL, NULL, &gval, sizeof(gval)) == -1) - syslog(LOG_WARNING, "sysctl(\"%s\"): %m", gstr); - if (sysctl(pmib, 2, NULL, NULL, &pval, sizeof(pval)) == -1) - syslog(LOG_WARNING, "sysctl(\"%s\"): %m", pstr); - } - if (setrlimit(i, ltmp + i) == -1) - syslog(LOG_WARNING, "setrlimit(): %m"); - /* the kernel may have clamped the values we gave it */ - if (getrlimit(i, l + i) == -1) - syslog(LOG_WARNING, "getrlimit(): %m"); - } - } - - return launch_data_new_opaque(l, sizeof(struct rlimit) * RLIM_NLIMITS); -} - -__private_extern__ void launchd_SessionCreate(const char *who) -{ - void *seclib = dlopen(SECURITY_LIB, RTLD_LAZY); - OSStatus (*sescr)(SessionCreationFlags flags, SessionAttributeBits attributes); - - if (seclib) { - sescr = dlsym(seclib, "SessionCreate"); - - if (sescr) { - OSStatus scr = sescr(0, 0); - if (scr != noErr) - syslog(LOG_WARNING, "%s: SessionCreate() failed: %d", who, scr); - } else { - syslog(LOG_WARNING, "%s: couldn't find SessionCreate() in %s", who, SECURITY_LIB); - } - - dlclose(seclib); - } else { - syslog(LOG_WARNING, "%s: dlopen(\"%s\",...): %s", who, SECURITY_LIB, dlerror()); - } -} - -static int dir_has_files(const char *path) -{ - DIR *dd = opendir(path); - struct dirent *de; - bool r = 0; - - if (!dd) - return -1; - - while ((de = readdir(dd))) { - if (strcmp(de->d_name, ".") && strcmp(de->d_name, "..")) { - r = 1; - break; - } - } - - closedir(dd); - return r; -} - -static void job_set_alarm(struct jobcb *j) -{ - struct tm otherlatertm, latertm, *nowtm; - time_t later, otherlater = 0, now = time(NULL); - - nowtm = localtime(&now); - - latertm = *nowtm; - - latertm.tm_sec = 0; - latertm.tm_isdst = -1; - - - if (-1 != j->start_cal_interval->tm_min) - latertm.tm_min = j->start_cal_interval->tm_min; - if (-1 != j->start_cal_interval->tm_hour) - latertm.tm_hour = j->start_cal_interval->tm_hour; - - otherlatertm = latertm; - - if (-1 != j->start_cal_interval->tm_mday) - latertm.tm_mday = j->start_cal_interval->tm_mday; - if (-1 != j->start_cal_interval->tm_mon) - latertm.tm_mon = j->start_cal_interval->tm_mon; - - /* cron semantics are fun */ - if (-1 != j->start_cal_interval->tm_wday) { - int delta, realwday = j->start_cal_interval->tm_wday; - - if (realwday == 7) - realwday = 0; - - delta = realwday - nowtm->tm_wday; - - /* Now Later Delta Desired - * 0 6 6 6 - * 6 0 -6 7 + -6 - * 1 5 4 4 - * 5 1 -4 7 + -4 - */ - if (delta > 0) - otherlatertm.tm_mday += delta; - else if (delta < 0) - otherlatertm.tm_mday += 7 + delta; - else if (-1 != j->start_cal_interval->tm_hour && otherlatertm.tm_hour <= nowtm->tm_hour) - otherlatertm.tm_mday += 7; - else if (-1 != j->start_cal_interval->tm_min && otherlatertm.tm_min <= nowtm->tm_min) - otherlatertm.tm_hour++; - else - otherlatertm.tm_min++; - - otherlater = mktime(&otherlatertm); - } - - if (-1 != j->start_cal_interval->tm_mon && latertm.tm_mon <= nowtm->tm_mon) { - latertm.tm_year++; - } else if (-1 != j->start_cal_interval->tm_mday && latertm.tm_mday <= nowtm->tm_mday) { - latertm.tm_mon++; - } else if (-1 != j->start_cal_interval->tm_hour && latertm.tm_hour <= nowtm->tm_hour) { - latertm.tm_mday++; - } else if (-1 != j->start_cal_interval->tm_min && latertm.tm_min <= nowtm->tm_min) { - latertm.tm_hour++; - } else { - latertm.tm_min++; - } - - later = mktime(&latertm); - - if (otherlater) { - if (-1 != j->start_cal_interval->tm_mday) - later = later < otherlater ? later : otherlater; - else - later = otherlater; - } - - if (-1 == kevent_mod((uintptr_t)j->start_cal_interval, EVFILT_TIMER, EV_ADD, NOTE_ABSOLUTE|NOTE_SECONDS, later, &j->kqjob_callback)) - job_log_error(j, LOG_ERR, "adding kevent alarm"); -} - -static void job_log_error(struct jobcb *j, int pri, const char *msg, ...) -{ - size_t newmsg_sz = strlen(msg) + strlen(j->label) + 200; - char *newmsg = alloca(newmsg_sz); - va_list ap; - - sprintf(newmsg, "%s: %s: %s", j->label, msg, strerror(errno)); - - va_start(ap, msg); - - vsyslog(pri, newmsg, ap); - - va_end(ap); -} - -static void job_log(struct jobcb *j, int pri, const char *msg, ...) -{ - size_t newmsg_sz = strlen(msg) + sizeof(": ") + strlen(j->label); - char *newmsg = alloca(newmsg_sz); - va_list ap; - - sprintf(newmsg, "%s: %s", j->label, msg); - - va_start(ap, msg); - - vsyslog(pri, newmsg, ap); - - va_end(ap); -} - -static void async_callback(void) -{ - struct timespec timeout = { 0, 0 }; - struct kevent kev; - - switch (kevent(asynckq, NULL, 0, &kev, 1, &timeout)) { - case -1: - syslog(LOG_DEBUG, "kevent(): %m"); - break; - case 1: - (*((kq_callback *)kev.udata))(kev.udata, &kev); - case 0: - break; - default: - syslog(LOG_DEBUG, "unexpected: kevent() returned something != 0, -1 or 1"); - } -} - -static void testfd_or_openfd(int fd, const char *path, int flags) -{ - int tmpfd; - - if (-1 != (tmpfd = dup(fd))) { - close(tmpfd); - } else { - if (-1 == (tmpfd = open(path, flags))) { - syslog(LOG_ERR, "open(\"%s\", ...): %m", path); - } else if (tmpfd != fd) { - dup2(tmpfd, fd); - close(tmpfd); - } - } + runtime_syslog(LOG_NOTICE, "Bug: %s:%u (%s):%u: %s", file, line, buf, saved_errno, test); }