/* * Copyright (c) 2005-2011 Apple Inc. All rights reserved. * * @APPLE_APACHE_LICENSE_HEADER_START@ * * 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 * * 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_APACHE_LICENSE_HEADER_END@ */ #include "config.h" #include "launch.h" #include "launch_priv.h" #include "bootstrap.h" #include "vproc.h" #include "vproc_priv.h" #include "vproc_internal.h" #include "bootstrap_priv.h" #include "launch_internal.h" #include #include #include #include #ifndef DARLING #include #endif // DARLING #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 #include #include #include #include #include #include #include #include #include #include #include #if HAVE_SYSTEMSTATS #include #endif #if HAVE_LIBAUDITD #include #ifndef AUDITD_PLIST_FILE #define AUDITD_PLIST_FILE "/System/Library/LaunchDaemons/com.apple.auditd.plist" #endif #endif extern char **environ; #define LAUNCH_SECDIR _PATH_TMP "launch-XXXXXX" #define LAUNCH_ENV_KEEPCONTEXT "LaunchKeepContext" #define LAUNCH_ENV_BOOTSTRAPPINGSYSTEM "LaunchBootstrappingSystem" #define CFTypeCheck(cf, type) (CFGetTypeID(cf) == type ## GetTypeID()) #define CFReleaseIfNotNULL(cf) if (cf) CFRelease(cf); #if TARGET_OS_EMBEDDED #include #define XPC_PLIST_CACHE "/System/Library/Caches/com.apple.xpcd/xpcd_cache.dylib" #define XPC_PLIST_CACHE_KEY "LaunchDaemons" #if JETSAM_PRIORITY_REVISION #define READ_JETSAM_DEFAULTS 1 #define JETSAM_PROP_DIR "/System/Library/LaunchDaemons" #define JETSAM_PROP_DIR_LENGTH (sizeof(JETSAM_PROP_DIR) - 1) #define JETSAM_PROP_PREFIX "com.apple.jetsamproperties." #define JETSAM_PROP_PREFIX_LENGTH (sizeof(JETSAM_PROP_PREFIX) - 1) #define JETSAM_PROP_SUFFIX ".plist" #define JETSAM_PROP_SUFFIX_LENGTH (sizeof(JETSAM_PROP_SUFFIX) - 1) #endif #endif struct load_unload_state { launch_data_t pass1; char *session_type; bool editondisk:1, load:1, forceload:1; }; static void launchctl_log(int level, const char *fmt, ...); static void launchctl_log_CFString(int level, CFStringRef string); static void myCFDictionaryApplyFunction(const void *key, const void *value, void *context); static CFTypeRef CFTypeCreateFromLaunchData(launch_data_t obj); static CFArrayRef CFArrayCreateFromLaunchArray(launch_data_t arr); static CFDictionaryRef CFDictionaryCreateFromLaunchDictionary(launch_data_t dict); static bool launch_data_array_append(launch_data_t a, launch_data_t o); static void insert_event(launch_data_t, const char *, const char *, launch_data_t); static void distill_jobs(launch_data_t); static void distill_config_file(launch_data_t); static void distill_fsevents(launch_data_t); static void sock_dict_cb(launch_data_t what, const char *key, void *context); static void sock_dict_edit_entry(launch_data_t tmp, const char *key, launch_data_t fdarray, launch_data_t thejob); static launch_data_t CF2launch_data(CFTypeRef); static launch_data_t read_plist_file(const char *file, bool editondisk, bool load); #if TARGET_OS_EMBEDDED static CFPropertyListRef GetPropertyListFromCache(void); static CFPropertyListRef CreateMyPropertyListFromCachedFile(const char *posixfile); static bool require_jobs_from_cache(void); #endif static CFPropertyListRef CreateMyPropertyListFromFile(const char *); static CFPropertyListRef CFPropertyListCreateFromFile(CFURLRef plistURL); static void WriteMyPropertyListToFile(CFPropertyListRef, const char *); static bool path_goodness_check(const char *path, bool forceload); static void readpath(const char *, struct load_unload_state *); static void readfile(const char *, struct load_unload_state *); static int _fd(int); static int demux_cmd(int argc, char *const argv[]); static void submit_job_pass(launch_data_t jobs); static void do_mgroup_join(int fd, int family, int socktype, int protocol, const char *mgroup); static mach_port_t str2bsport(const char *s); static void print_jobs(launch_data_t j, const char *key, void *context); static void print_obj(launch_data_t obj, const char *key, void *context); static bool str2lim(const char *buf, rlim_t *res); static const char *lim2str(rlim_t val, char *buf); static const char *num2name(int n); static ssize_t name2num(const char *n); static void unloadjob(launch_data_t job); static void print_key_value(launch_data_t obj, const char *key, void *context); static void print_launchd_env(launch_data_t obj, const char *key, void *context); static void loopback_setup_ipv4(void); static void loopback_setup_ipv6(void); static pid_t fwexec(const char *const *argv, int *wstatus); static void do_potential_fsck(void); static bool path_check(const char *path); static bool is_safeboot(void); static bool is_netboot(void); static void apply_sysctls_from_file(const char *thefile); static void empty_dir(const char *thedir, struct stat *psb); static int touch_file(const char *path, mode_t m); static void do_sysversion_sysctl(void); static void do_application_firewall_magic(int sfd, launch_data_t thejob); static void preheat_page_cache_hack(void); static void do_bootroot_magic(void); static void do_single_user_mode(bool); static bool do_single_user_mode2(void); static void do_crash_debug_mode(void); static bool do_crash_debug_mode2(void); static void read_launchd_conf(void); static bool job_disabled_logic(launch_data_t obj); static void fix_bogus_file_metadata(void); static void do_file_init(void) __attribute__((constructor)); static void setup_system_context(void); static void handle_system_bootstrapper_crashes_separately(void); static void fatal_signal_handler(int sig, siginfo_t *si, void *uap); typedef enum { BOOTCACHE_START = 1, BOOTCACHE_TAG, BOOTCACHE_STOP, } BootCache_action_t; static void do_BootCache_magic(BootCache_action_t what); static int bootstrap_cmd(int argc, char *const argv[]); static int load_and_unload_cmd(int argc, char *const argv[]); //static int reload_cmd(int argc, char *const argv[]); static int start_stop_remove_cmd(int argc, char *const argv[]); static int submit_cmd(int argc, char *const argv[]); static int list_cmd(int argc, char *const argv[]); static int setenv_cmd(int argc, char *const argv[]); static int unsetenv_cmd(int argc, char *const argv[]); static int getenv_and_export_cmd(int argc, char *const argv[]); static int wait4debugger_cmd(int argc, char *const argv[]); static int limit_cmd(int argc, char *const argv[]); static int stdio_cmd(int argc, char *const argv[]); static int fyi_cmd(int argc, char *const argv[]); static int logupdate_cmd(int argc, char *const argv[]); static int umask_cmd(int argc, char *const argv[]); static int getrusage_cmd(int argc, char *const argv[]); static int bsexec_cmd(int argc, char *const argv[]); static int _bslist_cmd(mach_port_t bport, unsigned int depth, bool show_job, bool local_only); static int bslist_cmd(int argc, char *const argv[]); static int _bstree_cmd(mach_port_t bsport, unsigned int depth, bool show_jobs); static int bstree_cmd(int argc __attribute__((unused)), char * const argv[] __attribute__((unused))); static int managerpid_cmd(int argc __attribute__((unused)), char * const argv[] __attribute__((unused))); static int manageruid_cmd(int argc __attribute__((unused)), char * const argv[] __attribute__((unused))); static int managername_cmd(int argc __attribute__((unused)), char * const argv[] __attribute__((unused))); static int asuser_cmd(int argc, char * const argv[]); static int exit_cmd(int argc, char *const argv[]) __attribute__((noreturn)); static int help_cmd(int argc, char *const argv[]); static const struct { const char *name; int (*func)(int argc, char *const argv[]); const char *desc; } cmds[] = { { "load", load_and_unload_cmd, "Load configuration files and/or directories" }, { "unload", load_and_unload_cmd, "Unload configuration files and/or directories" }, // { "reload", reload_cmd, "Reload configuration files and/or directories" }, { "start", start_stop_remove_cmd, "Start specified job" }, { "stop", start_stop_remove_cmd, "Stop specified job" }, { "submit", submit_cmd, "Submit a job from the command line" }, { "remove", start_stop_remove_cmd, "Remove specified job" }, { "bootstrap", bootstrap_cmd, "Bootstrap launchd" }, { "list", list_cmd, "List jobs and information about jobs" }, { "setenv", setenv_cmd, "Set an environmental variable in launchd" }, { "unsetenv", unsetenv_cmd, "Unset an environmental variable in launchd" }, { "getenv", getenv_and_export_cmd, "Get an environmental variable from launchd" }, { "export", getenv_and_export_cmd, "Export shell settings from launchd" }, { "debug", wait4debugger_cmd, "Set the WaitForDebugger flag for the target job to true." }, { "limit", limit_cmd, "View and adjust launchd resource limits" }, { "stdout", stdio_cmd, "Redirect launchd's standard out to the given path" }, { "stderr", stdio_cmd, "Redirect launchd's standard error to the given path" }, { "shutdown", fyi_cmd, "Prepare for system shutdown" }, { "singleuser", fyi_cmd, "Switch to single-user mode" }, { "getrusage", getrusage_cmd, "Get resource usage statistics from launchd" }, { "log", logupdate_cmd, "Adjust the logging level or mask of launchd" }, { "umask", umask_cmd, "Change launchd's umask" }, { "bsexec", bsexec_cmd, "Execute a process within a different Mach bootstrap subset" }, { "bslist", bslist_cmd, "List Mach bootstrap services and optional servers" }, { "bstree", bstree_cmd, "Show the entire Mach bootstrap tree. Requires root privileges." }, { "managerpid", managerpid_cmd, "Print the PID of the launchd managing this Mach bootstrap." }, { "manageruid", manageruid_cmd, "Print the UID of the launchd managing this Mach bootstrap." }, { "managername", managername_cmd, "Print the name of this Mach bootstrap." }, { "asuser", asuser_cmd, "Execute a subcommand in the given user's context." }, { "exit", exit_cmd, "Exit the interactive invocation of launchctl" }, { "quit", exit_cmd, "Quit the interactive invocation of launchctl" }, { "help", help_cmd, "This help output" }, }; static bool _launchctl_istty; static bool _launchctl_verbose; static bool _launchctl_is_managed; static bool _launchctl_apple_internal; static bool _launchctl_system_context; static bool _launchctl_uid0_context; static bool _launchctl_system_bootstrap; static bool _launchctl_peruser_bootstrap; static bool _launchctl_verbose_boot = false; static bool _launchctl_startup_debugging = false; static bool _launchctl_overrides_db_changed = false; static CFMutableDictionaryRef _launchctl_overrides_db = NULL; static char *_launchctl_job_overrides_db_path; static char *_launchctl_managername = NULL; #if READ_JETSAM_DEFAULTS static CFDictionaryRef _launchctl_jetsam_defaults = NULL; static CFDictionaryRef _launchctl_jetsam_defaults_cached = NULL; #endif int main(int argc, char *const argv[]) { char *l; if (getenv(LAUNCH_ENV_BOOTSTRAPPINGSYSTEM)) { /* We're bootstrapping the install environment, so we can't talk to * mDNSResponder or opendirectoryd. * * See . */ si_search_module_set_flags("mdns", 1); si_search_module_set_flags("ds", 1); } int64_t is_managed = 0; (void)vproc_swap_integer(NULL, VPROC_GSK_IS_MANAGED, NULL, &is_managed); _launchctl_is_managed = is_managed; _launchctl_istty = isatty(STDIN_FILENO); argc--, argv++; if (argc > 0 && argv[0][0] == '-') { char *flago; for (flago = argv[0] + 1; *flago; flago++) { switch (*flago) { case 'v': _launchctl_verbose = true; break; case 'u': if (argc > 1) { if (strncmp(argv[1], "root", sizeof("root")) == 0) { _launchctl_uid0_context = true; } else { launchctl_log(LOG_ERR, "Unknown user: %s", argv[1]); exit(EXIT_FAILURE); } argc--, argv++; } else { launchctl_log(LOG_ERR, "-u option requires an argument."); } break; case '1': _launchctl_system_context = true; break; default: launchctl_log(LOG_ERR, "Unknown argument: '-%c'", *flago); break; } } argc--, argv++; } /* Running in the context of the root user's per-user launchd is only from * within that session. */ if (_launchctl_uid0_context) { int64_t manager_uid = -1, manager_pid = -1; (void)vproc_swap_integer(NULL, VPROC_GSK_MGR_UID, NULL, &manager_uid); (void)vproc_swap_integer(NULL, VPROC_GSK_MGR_PID, NULL, &manager_pid); if (manager_uid || manager_pid == 1) { launchctl_log(LOG_ERR, "Running in the root user's per-user context is not supported outside of the root user's bootstrap."); exit(EXIT_FAILURE); } } else if (!(_launchctl_system_context || _launchctl_uid0_context)) { /* Running in the system context is implied when we're running as root * and not running as a bootstrapper. */ _launchctl_system_context = (!_launchctl_is_managed && getuid() == 0); } if (_launchctl_system_context) { if (getuid() == 0) { setup_system_context(); } else { launchctl_log(LOG_ERR, "You must be root to run in the system context."); exit(EXIT_FAILURE); } } else if (_launchctl_uid0_context) { if (getuid() != 0) { launchctl_log(LOG_ERR, "You must be root to run in the root user context."); exit(EXIT_FAILURE); } } if (!readline) { launchctl_log(LOG_ERR, "missing library: readline"); exit(EXIT_FAILURE); } if (argc == 0) { while ((l = readline(_launchctl_istty ? "launchd% " : NULL))) { char *inputstring = l, *argv2[100], **ap = argv2; int i = 0; while ((*ap = strsep(&inputstring, " \t"))) { if (**ap != '\0') { ap++; i++; } } if (i > 0) { demux_cmd(i, argv2); } free(l); } if (_launchctl_istty) { fputc('\n', stdout); } } if (argc > 0) { exit(demux_cmd(argc, argv)); } exit(EXIT_SUCCESS); } int demux_cmd(int argc, char *const argv[]) { size_t i; optind = 1; optreset = 1; for (i = 0; i < (sizeof cmds / sizeof cmds[0]); i++) { if (!strcmp(cmds[i].name, argv[0])) { return cmds[i].func(argc, argv); } } launchctl_log(LOG_ERR, "%s: unknown subcommand \"%s\"", getprogname(), argv[0]); return 1; } void launchctl_log(int level, const char *fmt, ...) { va_list ap; va_start(ap, fmt); if (_launchctl_is_managed) { vsyslog(level, fmt, ap); } else { char *buff = NULL; (void)vasprintf(&buff, fmt, ap); FILE *where = stdout; if (level < LOG_NOTICE) { where = stderr; } fprintf(where, "%s\n", buff); free(buff); } va_end(ap); } void launchctl_log_CFString(int level, CFStringRef string) { // Big enough. Don't feel like jumping through CF's hoops. char *buff = malloc(4096); (void)CFStringGetCString(string, buff, 4096, kCFStringEncodingUTF8); launchctl_log(level, "%s", buff); free(buff); } void read_launchd_conf(void) { #if !TARGET_OS_EMBEDDED char s[1000], *c, *av[100]; const char *file; size_t len; int i; FILE *f; if (getppid() == 1) { file = "/etc/launchd.conf"; } else { file = "/etc/launchd-user.conf"; } if (!(f = fopen(file, "r"))) { return; } while ((c = fgets(s, (int) sizeof s, f))) { len = strlen(c); if (len && c[len - 1] == '\n') { c[len - 1] = '\0'; } i = 0; while ((av[i] = strsep(&c, " \t"))) { if (*(av[i]) != '\0') { i++; } } if (i > 0) { demux_cmd(i, av); } } fclose(f); #endif // !TARGET_OS_EMBEDDED } static CFPropertyListRef CFPropertyListCreateFromFile(CFURLRef plistURL) { CFReadStreamRef plistReadStream = CFReadStreamCreateWithFile(NULL, plistURL); CFErrorRef streamErr = NULL; if (!CFReadStreamOpen(plistReadStream)) { streamErr = CFReadStreamCopyError(plistReadStream); CFStringRef errString = CFErrorCopyDescription(streamErr); launchctl_log_CFString(LOG_ERR, errString); CFRelease(errString); CFRelease(streamErr); } CFPropertyListRef plist = NULL; if (plistReadStream) { CFStringRef errString = NULL; CFPropertyListFormat plistFormat = 0; plist = CFPropertyListCreateFromStream(NULL, plistReadStream, 0, kCFPropertyListImmutable, &plistFormat, &errString); if (!plist) { launchctl_log_CFString(LOG_ERR, errString); CFRelease(errString); } } CFReadStreamClose(plistReadStream); CFRelease(plistReadStream); return plist; } int unsetenv_cmd(int argc, char *const argv[]) { launch_data_t resp, tmp, msg; if (argc != 2) { launchctl_log(LOG_ERR, "%s usage: unsetenv ", getprogname()); return 1; } msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY); tmp = launch_data_new_string(argv[1]); launch_data_dict_insert(msg, tmp, LAUNCH_KEY_UNSETUSERENVIRONMENT); resp = launch_msg(msg); launch_data_free(msg); if (resp) { launch_data_free(resp); } else { launchctl_log(LOG_ERR, "launch_msg(\"%s\"): %s", LAUNCH_KEY_UNSETUSERENVIRONMENT, strerror(errno)); } return 0; } int setenv_cmd(int argc, char *const argv[]) { launch_data_t resp, tmp, tmpv, msg; if (argc != 3) { launchctl_log(LOG_ERR, "%s usage: setenv ", getprogname()); return 1; } msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY); tmp = launch_data_alloc(LAUNCH_DATA_DICTIONARY); tmpv = launch_data_new_string(argv[2]); launch_data_dict_insert(tmp, tmpv, argv[1]); launch_data_dict_insert(msg, tmp, LAUNCH_KEY_SETUSERENVIRONMENT); resp = launch_msg(msg); launch_data_free(msg); if (resp) { launch_data_free(resp); } else { launchctl_log(LOG_ERR, "launch_msg(\"%s\"): %s", LAUNCH_KEY_SETUSERENVIRONMENT, strerror(errno)); } return 0; } void print_launchd_env(launch_data_t obj, const char *key, void *context) { bool *is_csh = context; /* XXX escape the double quotes */ if (*is_csh) { launchctl_log(LOG_NOTICE, "setenv %s \"%s\";", key, launch_data_get_string(obj)); } else { launchctl_log(LOG_NOTICE, "%s=\"%s\"; export %s;", key, launch_data_get_string(obj), key); } } void print_key_value(launch_data_t obj, const char *key, void *context) { const char *k = context; if (!strcmp(key, k)) { launchctl_log(LOG_NOTICE, "%s", launch_data_get_string(obj)); } } int getenv_and_export_cmd(int argc, char *const argv[]) { launch_data_t resp; bool is_csh = false; char *k; if (!strcmp(argv[0], "export")) { char *s = getenv("SHELL"); if (s) { is_csh = strstr(s, "csh") ? true : false; } } else if (argc != 2) { launchctl_log(LOG_ERR, "%s usage: getenv ", getprogname()); return 1; } k = argv[1]; if (vproc_swap_complex(NULL, VPROC_GSK_ENVIRONMENT, NULL, &resp) == NULL) { if (!strcmp(argv[0], "export")) { launch_data_dict_iterate(resp, print_launchd_env, &is_csh); } else { launch_data_dict_iterate(resp, print_key_value, k); } launch_data_free(resp); return 0; } else { return 1; } return 0; } int wait4debugger_cmd(int argc, char * const argv[]) { if (argc != 3) { launchctl_log(LOG_ERR, "%s usage: debug