X-Git-Url: https://git.saurik.com/apple/launchd.git/blobdiff_plain/e91b9f68c8f72465f3a6a45ce0aa2ad44c776f32..ed34e3c3e5fb80e0702ac7fb92f189862089d820:/launchd/src/launchctl.c diff --git a/launchd/src/launchctl.c b/launchd/src/launchctl.c index f6911d5..a3b78d2 100644 --- a/launchd/src/launchctl.c +++ b/launchd/src/launchctl.c @@ -1,28 +1,33 @@ /* * 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@ */ + +static const char *const __rcs_file_version__ = "$Revision: 1.88 $"; + #include +#include +#include +#include #include +#include #include +#include #include #include #include @@ -30,7 +35,12 @@ #include #include #include +#include +#include +#include #include +#include +#include #include #include #include @@ -41,16 +51,42 @@ #include #include #include +#include #include #include #include +#include +#include +#include +#include "bootstrap_public.h" +#include "bootstrap_private.h" #include "launch.h" #include "launch_priv.h" #define LAUNCH_SECDIR "/tmp/launch-XXXXXX" +#define MACHINIT_JOBKEY_ONDEMAND "OnDemand" +#define MACHINIT_JOBKEY_SERVICENAME "ServiceName" +#define MACHINIT_JOBKEY_COMMAND "Command" +#define MACHINIT_JOBKEY_SERVERPORT "ServerPort" +#define MACHINIT_JOBKEY_SERVICEPORT "ServicePort" + +#define assumes(e) \ + (__builtin_expect(!(e), 0) ? _log_launchctl_bug(__rcs_file_version__, __FILE__, __LINE__, #e), false : true) + + +struct load_unload_state { + launch_data_t pass0; + launch_data_t pass1; + launch_data_t pass2; + char *session_type; + unsigned int editondisk:1, load:1, forceload:1, __pad:29; +}; + +static void myCFDictionaryApplyFunction(const void *key, const void *value, void *context); static bool launch_data_array_append(launch_data_t a, launch_data_t o); +static void distill_jobs(launch_data_t); static void distill_config_file(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); @@ -58,15 +94,49 @@ static launch_data_t CF2launch_data(CFTypeRef); static launch_data_t read_plist_file(const char *file, bool editondisk, bool load); static CFPropertyListRef CreateMyPropertyListFromFile(const char *); static void WriteMyPropertyListToFile(CFPropertyListRef, const char *); -static void readpath(const char *, launch_data_t, launch_data_t, bool editondisk, bool load); +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 launch_data_t do_rendezvous_magic(const struct addrinfo *res, const char *serv); static void submit_job_pass(launch_data_t jobs); - +static void submit_mach_jobs(launch_data_t jobs); +static void let_go_of_mach_jobs(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); +static void print_obj(launch_data_t obj, const char *key, void *context); +static bool is_legacy_mach_job(launch_data_t obj); +static bool delay_to_second_pass(launch_data_t o); +static void delay_to_second_pass2(launch_data_t o, 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 _log_launchctl_bug(const char *rcs_rev, const char *path, unsigned int line, const char *test); +static void loopback_setup_ipv4(void); +static void loopback_setup_ipv6(void); +static pid_t fwexec(const char *const *argv, bool _wait); +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_func_to_dir(const char *thedir, void (*thefunc)(const char *)); +static void apply_sysctls_from_file(const char *thefile); +static void empty_dir(const char *path); +static int touch_file(const char *path, mode_t m); +static void do_sysversion_sysctl(void); +static void workaround4465949(void); + +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_and_stop_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[]); @@ -79,7 +149,10 @@ 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(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 { @@ -90,8 +163,11 @@ static const struct { { "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_and_stop_cmd, "Start specified jobs" }, - { "stop", start_and_stop_cmd, "Stop specified jobs" }, + { "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" }, @@ -101,50 +177,85 @@ static const struct { { "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" }, { "reloadttys", fyi_cmd, "Reload /etc/ttys" }, { "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" }, + { "exit", exit_cmd, "Exit the interactive invocation of launchctl" }, + { "quit", exit_cmd, "Quit the interactive invocation of launchctl" }, { "help", help_cmd, "This help output" }, }; -int main(int argc, char *const argv[]) +static bool istty = false; +static bool verbose = false; + +int +main(int argc, char *const argv[]) { - bool istty = isatty(STDIN_FILENO); char *l; - if (argc > 1) - exit(demux_cmd(argc - 1, argv + 1)); + do_sysversion_sysctl(); + + 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': + verbose = true; + break; + default: + fprintf(stderr, "Unknown argument: '-%c'\n", *flago); + break; + } + } + argc--, argv++; + } if (NULL == readline) { fprintf(stderr, "missing library: readline\n"); exit(EXIT_FAILURE); } - while ((l = readline(istty ? "launchd% " : NULL))) { - char *inputstring = l, *argv2[100], **ap = argv2; - int i = 0; - - while ((*ap = strsep(&inputstring, " \t"))) { - if (**ap != '\0') { - ap++; - i++; + if (argc == 0) { + while ((l = readline(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); + if (i > 0) + demux_cmd(i, argv2); + + free(l); + } - free(l); + if (istty) { + fputc('\n', stdout); + } } - if (istty) - fputc('\n', stdout); + if (argc > 0) { + exit(demux_cmd(argc, argv)); + } exit(EXIT_SUCCESS); } -static int demux_cmd(int argc, char *const argv[]) +int +demux_cmd(int argc, char *const argv[]) { size_t i; @@ -160,7 +271,8 @@ static int demux_cmd(int argc, char *const argv[]) return 1; } -static int unsetenv_cmd(int argc, char *const argv[]) +int +unsetenv_cmd(int argc, char *const argv[]) { launch_data_t resp, tmp, msg; @@ -187,7 +299,8 @@ static int unsetenv_cmd(int argc, char *const argv[]) return 0; } -static int setenv_cmd(int argc, char *const argv[]) +int +setenv_cmd(int argc, char *const argv[]) { launch_data_t resp, tmp, tmpv, msg; @@ -215,21 +328,33 @@ static int setenv_cmd(int argc, char *const argv[]) return 0; } -static int getenv_and_export_cmd(int argc, char *const argv[] __attribute__((unused))) +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) + fprintf(stdout, "setenv %s \"%s\";\n", key, launch_data_get_string(obj)); + else + fprintf(stdout, "%s=\"%s\"; export %s;\n", 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)) + fprintf(stdout, "%s\n", launch_data_get_string(obj)); +} + +int +getenv_and_export_cmd(int argc, char *const argv[] __attribute__((unused))) { launch_data_t resp, msg; bool is_csh = false; - const char *k; - void print_launchd_env(launch_data_t obj, const char *key, void *context __attribute__((unused))) { - if (is_csh) - fprintf(stdout, "setenv %s %s;\n", key, launch_data_get_string(obj)); - else - fprintf(stdout, "%s=%s; export %s;\n", key, launch_data_get_string(obj), key); - } - void print_key_value(launch_data_t obj, const char *key, void *context __attribute__((unused))) { - if (!strcmp(key, k)) - fprintf(stdout, "%s\n", launch_data_get_string(obj)); - } + char *k; if (!strcmp(argv[0], "export")) { char *s = getenv("SHELL"); @@ -248,7 +373,10 @@ static int getenv_and_export_cmd(int argc, char *const argv[] __attribute__((unu launch_data_free(msg); if (resp) { - launch_data_dict_iterate(resp, (!strcmp(argv[0], "export")) ? print_launchd_env : print_key_value, 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); } else { fprintf(stderr, "launch_msg(\"" LAUNCH_KEY_GETUSERENVIRONMENT "\"): %s\n", strerror(errno)); @@ -256,7 +384,8 @@ static int getenv_and_export_cmd(int argc, char *const argv[] __attribute__((unu return 0; } -static void unloadjob(launch_data_t job) +void +unloadjob(launch_data_t job) { launch_data_t resp, tmp, tmps, msg; int e; @@ -285,7 +414,8 @@ static void unloadjob(launch_data_t job) launch_data_free(resp); } -static launch_data_t read_plist_file(const char *file, bool editondisk, bool load) +launch_data_t +read_plist_file(const char *file, bool editondisk, bool load) { CFPropertyListRef plist = CreateMyPropertyListFromFile(file); launch_data_t r = NULL; @@ -310,7 +440,8 @@ static launch_data_t read_plist_file(const char *file, bool editondisk, bool loa return r; } -static void delay_to_second_pass2(launch_data_t o, const char *key, void *context) +void +delay_to_second_pass2(launch_data_t o, const char *key, void *context) { bool *res = context; size_t i; @@ -333,7 +464,8 @@ static void delay_to_second_pass2(launch_data_t o, const char *key, void *contex } } -static bool delay_to_second_pass(launch_data_t o) +bool +delay_to_second_pass(launch_data_t o) { bool res = false; @@ -347,43 +479,158 @@ static bool delay_to_second_pass(launch_data_t o) return res; } -static void readfile(const char *what, launch_data_t pass1, launch_data_t pass2, bool editondisk, bool load) +void +readfile(const char *what, struct load_unload_state *lus) { - launch_data_t tmpd, thejob; + char ourhostname[1024]; + launch_data_t tmpd, tmps, thejob, tmpa; bool job_disabled = false; + size_t i, c; - if (NULL == (thejob = read_plist_file(what, editondisk, load))) { + gethostname(ourhostname, sizeof(ourhostname)); + + if (NULL == (thejob = read_plist_file(what, lus->editondisk, lus->load))) { fprintf(stderr, "%s: no plist was returned for: %s\n", getprogname(), what); return; } + if (is_legacy_mach_job(thejob)) { + fprintf(stderr, "%s: Please convert the following to launchd: %s\n", getprogname(), what); + launch_data_array_append(lus->pass0, thejob); + return; + } + + if (NULL == launch_data_dict_lookup(thejob, LAUNCH_JOBKEY_LABEL)) { + fprintf(stderr, "%s: missing the Label key: %s\n", getprogname(), what); + goto out_bad; + } + + if (NULL != (tmpa = launch_data_dict_lookup(thejob, LAUNCH_JOBKEY_LIMITLOADFROMHOSTS))) { + c = launch_data_array_get_count(tmpa); + + for (i = 0; i < c; i++) { + launch_data_t oai = launch_data_array_get_index(tmpa, i); + if (!strcasecmp(ourhostname, launch_data_get_string(oai))) + goto out_bad; + } + } + + if (NULL != (tmpa = launch_data_dict_lookup(thejob, LAUNCH_JOBKEY_LIMITLOADTOHOSTS))) { + c = launch_data_array_get_count(tmpa); + + for (i = 0; i < c; i++) { + launch_data_t oai = launch_data_array_get_index(tmpa, i); + if (!strcasecmp(ourhostname, launch_data_get_string(oai))) + break; + } + + if (i == c) + goto out_bad; + } + + if ((tmpa = launch_data_dict_lookup(thejob, LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE))) { + const char *allowed_session; + bool skipjob = true; + + if (lus->session_type) switch (launch_data_get_type(tmpa)) { + case LAUNCH_DATA_ARRAY: + c = launch_data_array_get_count(tmpa); + for (i = 0; i < c; i++) { + tmps = launch_data_array_get_index(tmpa, i); + allowed_session = launch_data_get_string(tmps); + if (strcasecmp(lus->session_type, allowed_session) == 0) { + skipjob = false; + break; + } + } + break; + case LAUNCH_DATA_STRING: + allowed_session = launch_data_get_string(tmpa); + if (strcasecmp(lus->session_type, allowed_session) == 0) { + skipjob = false; + } + break; + default: + break; + } + + if (skipjob) { + goto out_bad; + } + } + if ((tmpd = launch_data_dict_lookup(thejob, LAUNCH_JOBKEY_DISABLED))) job_disabled = launch_data_get_bool(tmpd); - if (job_disabled && load) { - launch_data_free(thejob); - return; - } + if (lus->forceload) + job_disabled = false; + + if (job_disabled && lus->load) + goto out_bad; if (delay_to_second_pass(thejob)) - launch_data_array_append(pass2, thejob); + launch_data_array_append(lus->pass2, thejob); else - launch_data_array_append(pass1, thejob); + launch_data_array_append(lus->pass1, thejob); + + if (verbose) + fprintf(stdout, "Will load: %s\n", what); + + return; +out_bad: + if (verbose) + fprintf(stdout, "Ignored: %s\n", what); + launch_data_free(thejob); +} + +bool +path_goodness_check(const char *path, bool forceload) +{ + struct stat sb; + + if (stat(path, &sb) == -1) { + fprintf(stderr, "%s: Couldn't stat(\"%s\"): %s\n", getprogname(), path, strerror(errno)); + return false; + } + + if (forceload) + return true; + + if (sb.st_mode & (S_IWOTH|S_IWGRP)) { + fprintf(stderr, "%s: Dubious permissions on file (skipping): %s\n", getprogname(), path); + return false; + } + + if (sb.st_uid != 0 && sb.st_uid != getuid()) { + fprintf(stderr, "%s: Dubious ownership on file (skipping): %s\n", getprogname(), path); + return false; + } + + if (!(S_ISREG(sb.st_mode) || S_ISDIR(sb.st_mode))) { + fprintf(stderr, "%s: Dubious path. Not a regular file or directory (skipping): %s\n", getprogname(), path); + return false; + } + + return true; } -static void readpath(const char *what, launch_data_t pass1, launch_data_t pass2, bool editondisk, bool load) +void +readpath(const char *what, struct load_unload_state *lus) { char buf[MAXPATHLEN]; struct stat sb; struct dirent *de; DIR *d; + if (!path_goodness_check(what, lus->forceload)) + return; + if (stat(what, &sb) == -1) return; - if (S_ISREG(sb.st_mode) && !(sb.st_mode & S_IWOTH)) { - readfile(what, pass1, pass2, editondisk, load); - } else { + if (S_ISREG(sb.st_mode)) { + readfile(what, lus); + } else if (S_ISDIR(sb.st_mode)) { if ((d = opendir(what)) == NULL) { fprintf(stderr, "%s: opendir() failed to open the directory\n", getprogname()); return; @@ -394,7 +641,10 @@ static void readpath(const char *what, launch_data_t pass1, launch_data_t pass2, continue; snprintf(buf, sizeof(buf), "%s/%s", what, de->d_name); - readfile(buf, pass1, pass2, editondisk, load); + if (!path_goodness_check(buf, lus->forceload)) + continue; + + readfile(buf, lus); } closedir(d); } @@ -405,19 +655,30 @@ struct distill_context { launch_data_t newsockdict; }; -static void distill_config_file(launch_data_t id_plist) +void +distill_jobs(launch_data_t jobs) +{ + size_t i, c = launch_data_array_get_count(jobs); + + for (i = 0; i < c; i++) + distill_config_file(launch_data_array_get_index(jobs, i)); +} + +void +distill_config_file(launch_data_t id_plist) { struct distill_context dc = { id_plist, NULL }; launch_data_t tmp; - if ((tmp = launch_data_dict_lookup(id_plist, LAUNCH_JOBKEY_SOCKETS))) { + if ((tmp = launch_data_dict_lookup(dc.base, LAUNCH_JOBKEY_SOCKETS))) { dc.newsockdict = launch_data_alloc(LAUNCH_DATA_DICTIONARY); launch_data_dict_iterate(tmp, sock_dict_cb, &dc); launch_data_dict_insert(dc.base, dc.newsockdict, LAUNCH_JOBKEY_SOCKETS); } } -static void sock_dict_cb(launch_data_t what, const char *key, void *context) +void +sock_dict_cb(launch_data_t what, const char *key, void *context) { struct distill_context *dc = context; launch_data_t fdarray = launch_data_alloc(LAUNCH_DATA_ARRAY); @@ -437,7 +698,8 @@ 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) +void +sock_dict_edit_entry(launch_data_t tmp, const char *key, launch_data_t fdarray, launch_data_t thejob) { launch_data_t a, val; int sfd, st = SOCK_STREAM; @@ -477,6 +739,9 @@ static void sock_dict_edit_entry(launch_data_t tmp, const char *key, launch_data if ((val = launch_data_dict_lookup(tmp, LAUNCH_JOBSOCKETKEY_PATHNAME))) { struct sockaddr_un sun; + mode_t sun_mode = 0; + mode_t oldmask; + bool setm = false; memset(&sun, 0, sizeof(sun)); @@ -487,15 +752,26 @@ static void sock_dict_edit_entry(launch_data_t tmp, const char *key, launch_data if ((sfd = _fd(socket(AF_UNIX, st, 0))) == -1) return; + if ((val = launch_data_dict_lookup(tmp, LAUNCH_JOBSOCKETKEY_PATHMODE))) { + sun_mode = (mode_t)launch_data_get_integer(val); + setm = true; + } + if (passive) { if (unlink(sun.sun_path) == -1 && errno != ENOENT) { close(sfd); return; } + oldmask = umask(S_IRWXG|S_IRWXO); if (bind(sfd, (struct sockaddr *)&sun, sizeof(sun)) == -1) { close(sfd); + umask(oldmask); return; } + umask(oldmask); + if (setm) { + chmod(sun.sun_path, sun_mode); + } if ((st == SOCK_STREAM || st == SOCK_SEQPACKET) && listen(sfd, SOMAXCONN) == -1) { close(sfd); @@ -510,7 +786,7 @@ static void sock_dict_edit_entry(launch_data_t tmp, const char *key, launch_data launch_data_array_append(fdarray, val); } else { launch_data_t rnames = NULL; - const char *node = NULL, *serv = NULL; + const char *node = NULL, *serv = NULL, *mgroup = NULL; char servnbuf[50]; struct addrinfo hints, *res0, *res; int gerr, sock_opt = 1; @@ -524,6 +800,8 @@ static void sock_dict_edit_entry(launch_data_t tmp, const char *key, launch_data if ((val = launch_data_dict_lookup(tmp, LAUNCH_JOBSOCKETKEY_NODENAME))) node = launch_data_get_string(val); + if ((val = launch_data_dict_lookup(tmp, LAUNCH_JOBSOCKETKEY_MULTICASTGROUP))) + mgroup = launch_data_get_string(val); if ((val = launch_data_dict_lookup(tmp, LAUNCH_JOBSOCKETKEY_SERVICENAME))) { if (LAUNCH_DATA_INTEGER == launch_data_get_type(val)) { sprintf(servnbuf, "%lld", launch_data_get_integer(val)); @@ -567,14 +845,25 @@ static void sock_dict_edit_entry(launch_data_t tmp, const char *key, launch_data fprintf(stderr, "setsockopt(IPV6_V6ONLY): %m"); return; } - if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (void *)&sock_opt, sizeof(sock_opt)) == -1) { - fprintf(stderr, "socket(): %s\n", strerror(errno)); - return; + if (mgroup) { + if (setsockopt(sfd, SOL_SOCKET, SO_REUSEPORT, (void *)&sock_opt, sizeof(sock_opt)) == -1) { + fprintf(stderr, "setsockopt(SO_REUSEPORT): %s\n", strerror(errno)); + return; + } + } else { + if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (void *)&sock_opt, sizeof(sock_opt)) == -1) { + fprintf(stderr, "setsockopt(SO_REUSEADDR): %s\n", strerror(errno)); + return; + } } if (bind(sfd, res->ai_addr, res->ai_addrlen) == -1) { fprintf(stderr, "bind(): %s\n", strerror(errno)); return; } + + if (mgroup) { + do_mgroup_join(sfd, res->ai_family, res->ai_socktype, res->ai_protocol, mgroup); + } if ((res->ai_socktype == SOCK_STREAM || res->ai_socktype == SOCK_SEQPACKET) && listen(sfd, SOMAXCONN) == -1) { fprintf(stderr, "listen(): %s\n", strerror(errno)); @@ -624,7 +913,55 @@ static void sock_dict_edit_entry(launch_data_t tmp, const char *key, launch_data } } -static launch_data_t do_rendezvous_magic(const struct addrinfo *res, const char *serv) +void +do_mgroup_join(int fd, int family, int socktype, int protocol, const char *mgroup) +{ + struct addrinfo hints, *res0, *res; + struct ip_mreq mreq; + struct ipv6_mreq m6req; + int gerr; + + memset(&hints, 0, sizeof(hints)); + + hints.ai_flags |= AI_PASSIVE; + hints.ai_family = family; + hints.ai_socktype = socktype; + hints.ai_protocol = protocol; + + if ((gerr = getaddrinfo(mgroup, NULL, &hints, &res0)) != 0) { + fprintf(stderr, "getaddrinfo(): %s\n", gai_strerror(gerr)); + return; + } + + for (res = res0; res; res = res->ai_next) { + if (AF_INET == family) { + memset(&mreq, 0, sizeof(mreq)); + mreq.imr_multiaddr = ((struct sockaddr_in *)res->ai_addr)->sin_addr; + if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) == -1) { + fprintf(stderr, "setsockopt(IP_ADD_MEMBERSHIP): %s\n", strerror(errno)); + continue; + } + break; + } else if (AF_INET6 == family) { + memset(&m6req, 0, sizeof(m6req)); + m6req.ipv6mr_multiaddr = ((struct sockaddr_in6 *)res->ai_addr)->sin6_addr; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &m6req, sizeof(m6req)) == -1) { + fprintf(stderr, "setsockopt(IPV6_JOIN_GROUP): %s\n", strerror(errno)); + continue; + } + break; + } else { + fprintf(stderr, "unknown family during multicast group bind!\n"); + break; + } + } + + freeaddrinfo(res0); +} + + +launch_data_t +do_rendezvous_magic(const struct addrinfo *res, const char *serv) { struct stat sb; DNSServiceRef service; @@ -655,7 +992,8 @@ static launch_data_t do_rendezvous_magic(const struct addrinfo *res, const char return NULL; } -static CFPropertyListRef CreateMyPropertyListFromFile(const char *posixfile) +CFPropertyListRef +CreateMyPropertyListFromFile(const char *posixfile) { CFPropertyListRef propertyList; CFStringRef errorString; @@ -663,7 +1001,7 @@ static CFPropertyListRef CreateMyPropertyListFromFile(const char *posixfile) SInt32 errorCode; CFURLRef fileURL; - fileURL = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, posixfile, strlen(posixfile), false); + fileURL = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (const UInt8 *)posixfile, strlen(posixfile), false); if (!fileURL) fprintf(stderr, "%s: CFURLCreateFromFileSystemRepresentation(%s) failed\n", getprogname(), posixfile); if (!CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault, fileURL, &resourceData, NULL, NULL, &errorCode)) @@ -675,13 +1013,14 @@ static CFPropertyListRef CreateMyPropertyListFromFile(const char *posixfile) return propertyList; } -static void WriteMyPropertyListToFile(CFPropertyListRef plist, const char *posixfile) +void +WriteMyPropertyListToFile(CFPropertyListRef plist, const char *posixfile) { CFDataRef resourceData; CFURLRef fileURL; SInt32 errorCode; - fileURL = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, posixfile, strlen(posixfile), false); + fileURL = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (const UInt8 *)posixfile, strlen(posixfile), false); if (!fileURL) fprintf(stderr, "%s: CFURLCreateFromFileSystemRepresentation(%s) failed\n", getprogname(), posixfile); resourceData = CFPropertyListCreateXMLData(kCFAllocatorDefault, plist); @@ -691,7 +1030,8 @@ static void WriteMyPropertyListToFile(CFPropertyListRef plist, const char *posix fprintf(stderr, "%s: CFURLWriteDataAndPropertiesToResource(%s) failed: %d\n", getprogname(), posixfile, (int)errorCode); } -void myCFDictionaryApplyFunction(const void *key, const void *value, void *context) +void +myCFDictionaryApplyFunction(const void *key, const void *value, void *context) { launch_data_t ik, iw, where = context; @@ -702,7 +1042,8 @@ void myCFDictionaryApplyFunction(const void *key, const void *value, void *conte launch_data_free(ik); } -static launch_data_t CF2launch_data(CFTypeRef cfr) +launch_data_t +CF2launch_data(CFTypeRef cfr) { launch_data_t r; CFTypeID cft = CFGetTypeID(cfr); @@ -767,7 +1108,8 @@ static launch_data_t CF2launch_data(CFTypeRef cfr) return r; } -static int help_cmd(int argc, char *const argv[]) +int +help_cmd(int argc, char *const argv[]) { FILE *where = stdout; int l, cmdwidth = 0; @@ -777,53 +1119,243 @@ static int help_cmd(int argc, char *const argv[]) where = stderr; fprintf(where, "usage: %s \n", getprogname()); + for (i = 0; i < (sizeof cmds / sizeof cmds[0]); i++) { l = strlen(cmds[i].name); if (l > cmdwidth) cmdwidth = l; } + for (i = 0; i < (sizeof cmds / sizeof cmds[0]); i++) fprintf(where, "\t%-*s\t%s\n", cmdwidth, cmds[i].name, cmds[i].desc); return 0; } -static int _fd(int fd) +int +exit_cmd(int argc __attribute__((unused)), char *const argv[] __attribute__((unused))) +{ + exit(0); +} + +int +_fd(int fd) { if (fd >= 0) fcntl(fd, F_SETFD, 1); return fd; } -static int load_and_unload_cmd(int argc, char *const argv[]) +int +bootstrap_cmd(int argc __attribute__((unused)), char *const argv[] __attribute__((unused))) { - launch_data_t pass1, pass2; - int i, ch; - bool wflag = false; - bool lflag = false; + int memmib[] = { CTL_HW, HW_MEMSIZE }; + int mvnmib[] = { CTL_KERN, KERN_MAXVNODES }; + int hnmib[] = { CTL_KERN, KERN_HOSTNAME }; + uint64_t mem = 0; + uint32_t mvn; + size_t memsz = sizeof(mem); + struct group *tfp_gr; + + if (assumes((tfp_gr = getgrnam("procview")) != NULL)) { + int tfp_r_mib[3] = { CTL_KERN, KERN_TFP, KERN_TFP_READ_GROUP }; + gid_t tfp_r_gid = tfp_gr->gr_gid; + assumes(sysctl(tfp_r_mib, 3, NULL, NULL, &tfp_r_gid, sizeof(tfp_r_gid)) != -1); + } + + if (assumes((tfp_gr = getgrnam("procmod")) != NULL)) { + int tfp_rw_mib[3] = { CTL_KERN, KERN_TFP, KERN_TFP_RW_GROUP }; + gid_t tfp_rw_gid = tfp_gr->gr_gid; + assumes(sysctl(tfp_rw_mib, 3, NULL, NULL, &tfp_rw_gid, sizeof(tfp_rw_gid)) != -1); + } + + if (assumes(sysctl(memmib, 2, &mem, &memsz, NULL, 0) != -1)) { + mvn = mem / (64 * 1024) + 1024; + assumes(sysctl(mvnmib, 2, NULL, NULL, &mvn, sizeof(mvn)) != -1); + } + assumes(sysctl(hnmib, 2, NULL, NULL, "localhost", sizeof("localhost")) != -1); + + loopback_setup_ipv4(); + loopback_setup_ipv6(); + + apply_sysctls_from_file("/etc/sysctl-macosxserver.conf"); + apply_sysctls_from_file("/etc/sysctl.conf"); + + if (path_check("/System/Installation") && path_check("/etc/rc.cdrom")) { + const char *rccdrom_tool[] = { _PATH_BSHELL, "/etc/rc.cdrom", "multiuser", NULL }; + assumes(fwexec(rccdrom_tool, true) != -1); + assumes(reboot(RB_HALT) != -1); + _exit(EXIT_FAILURE); + } else if (is_netboot()) { + const char *rcnetboot_tool[] = { _PATH_BSHELL, "/etc/rc.netboot", "init", NULL }; + if (!assumes(fwexec(rcnetboot_tool, true) != -1)) { + assumes(reboot(RB_HALT) != -1); + _exit(EXIT_FAILURE); + } + } else { + do_potential_fsck(); + } + + if (path_check("/var/account/acct")) { + assumes(acct("/var/account/acct") != -1); + } + + if (path_check("/etc/fstab")) { + const char *mount_tool[] = { "mount", "-vat", "nonfs", NULL }; + assumes(fwexec(mount_tool, true) != -1); + } + + if (path_check("/etc/rc.installer_cleanup")) { + const char *rccleanup_tool[] = { _PATH_BSHELL, "/etc/rc.installer_cleanup", "multiuser", NULL }; + assumes(fwexec(rccleanup_tool, true) != -1); + } + + apply_func_to_dir(_PATH_VARRUN, empty_dir); + apply_func_to_dir(_PATH_TMP, empty_dir); + remove(_PATH_NOLOGIN); + + // XXX --> RMRF_ITEMS="/var/tmp/folders.* + + // 775 www:www /var/run/davlocks 4489695 + // 775 root:daemon /var/run/StartupItems + + assumes(touch_file(_PATH_UTMP, DEFFILEMODE) != -1); + assumes(touch_file(_PATH_UTMPX, DEFFILEMODE) != -1); + assumes(touch_file(_PATH_VARRUN "/.systemStarterRunning", DEFFILEMODE) != -1); + + if (!path_check("/var/db/netinfo/local.nidb.migrated") && !path_check("/var/db/netinfo/local.nidb")) { + const char *create_nidb_tool[] = { "/usr/libexec/create_nidb", NULL }; + + fprintf(stderr, "NetInfo database missing. Creating...\n"); + + mkdir("/var/db/netinfo", ACCESSPERMS); + remove("/var/db/.AppleSetupDone"); + assumes(fwexec(create_nidb_tool, true) != -1); + } + + if (path_check("/etc/security/rc.audit")) { + const char *audit_tool[] = { _PATH_BSHELL, "/etc/security/rc.audit", NULL }; + assumes(fwexec(audit_tool, true) != -1); + } + + if (path_check("/Library/Preferences/com.apple.sharing.firewall.plist")) { + const char *fw_tool[] = { "/usr/libexec/FirewallTool", NULL }; + assumes(fwexec(fw_tool, true) != -1); + } + + const char *bcc_tool[] = { "BootCacheControl", "start", NULL }; + assumes(fwexec(bcc_tool, true) != -1); + + char *load_launchd_items[] = { "load", "-D", "all", "/etc/mach_init.d", NULL }; + if (is_safeboot()) + load_launchd_items[2] = "system"; + assumes(load_and_unload_cmd(4, load_launchd_items) == 0); + + const char *bcc_tag_tool[] = { "BootCacheControl", "tag", NULL }; + assumes(fwexec(bcc_tag_tool, true) != -1); + + const char *SystemStarter_tool[] = { "SystemStarter", NULL }; + assumes(fwexec(SystemStarter_tool, false) != -1); + + workaround4465949(); + + if (path_check("/etc/rc.local")) { + const char *rc_local_tool[] = { _PATH_BSHELL, "/etc/rc.local", NULL }; + assumes(fwexec(rc_local_tool, false) != -1); + } + + return 0; +} + +void +workaround4465949(void) +{ + const char *pbs_tool[] = { "/System/Library/CoreServices/pbs", NULL }; + const char *lca_tool[] = { "/System/Library/CoreServices/Language Chooser.app/Contents/MacOS/Language Chooser", NULL}; + char *const reloadttys_argv[] = { "reloadttys", NULL }; + int wstatus; + pid_t pbs_p; + + if (path_check("/System/Library/LaunchDaemons/com.apple.loginwindow.plist")) { + return; + } + + if (path_check(pbs_tool[0]) && path_check(lca_tool[0]) && + !path_check("/var/db/.AppleSetupDone") && + path_check("/var/db/.RunLanguageChooserToo")) { + if (assumes((pbs_p = fwexec(pbs_tool, false)) != -1)) { + assumes(fwexec(lca_tool, true) != -1); + assumes(kill(pbs_p, SIGTERM) != -1); + assumes(waitpid(pbs_p, &wstatus, 0) != -1); + } + } + + assumes(fyi_cmd(1, reloadttys_argv) == 0); +} + +int +load_and_unload_cmd(int argc, char *const argv[]) +{ + NSSearchPathEnumerationState es = 0; + char nspath[PATH_MAX * 2]; /* safe side, we need to append */ + bool badopts = false; + struct load_unload_state lus; + size_t i; + int ch; + + memset(&lus, 0, sizeof(lus)); if (!strcmp(argv[0], "load")) - lflag = true; + lus.load = true; - while ((ch = getopt(argc, argv, "w")) != -1) { + while ((ch = getopt(argc, argv, "wFS:D:")) != -1) { switch (ch) { case 'w': - wflag = true; + lus.editondisk = true; + break; + case 'F': + lus.forceload = true; break; + case 'S': + lus.session_type = optarg; + break; + case 'D': + if (strcasecmp(optarg, "all") == 0) { + es |= NSAllDomainsMask; + } else if (strcasecmp(optarg, "user") == 0) { + es |= NSUserDomainMask; + } else if (strcasecmp(optarg, "local") == 0) { + es |= NSLocalDomainMask; + } else if (strcasecmp(optarg, "network") == 0) { + es |= NSNetworkDomainMask; + } else if (strcasecmp(optarg, "system") == 0) { + es |= NSSystemDomainMask; + } else { + badopts = true; + } + break; + case '?': default: - fprintf(stderr, "usage: %s load [-w] paths...\n", getprogname()); - return 1; + badopts = true; + break; } } argc -= optind; argv += optind; - if (argc == 0) { - fprintf(stderr, "usage: %s load [-w] paths...\n", getprogname()); + if (lus.session_type == NULL) + es &= ~NSUserDomainMask; + + if (argc == 0 && es == 0) + badopts = true; + + if (badopts) { + fprintf(stderr, "usage: %s load [-wF] [-D ] paths...\n", getprogname()); return 1; } - /* I wish I didn't need to do two passes, but I need to load mDNSResponder and use it too. + /* I wish I didn't need to do three passes, but I need to load mDNSResponder and use it too. + * And loading legacy mach init jobs is extra fun. * * In later versions of launchd, I hope to load everything in the first pass, * then do the Bonjour magic on the jobs that need it, and reload them, but for now, @@ -831,44 +1363,124 @@ static int load_and_unload_cmd(int argc, char *const argv[]) * launchd doesn't have reload support right now. */ - pass1 = launch_data_alloc(LAUNCH_DATA_ARRAY); - pass2 = launch_data_alloc(LAUNCH_DATA_ARRAY); + lus.pass0 = launch_data_alloc(LAUNCH_DATA_ARRAY); + lus.pass1 = launch_data_alloc(LAUNCH_DATA_ARRAY); + lus.pass2 = launch_data_alloc(LAUNCH_DATA_ARRAY); + + es = NSStartSearchPathEnumeration(NSLibraryDirectory, es); + + while ((es = NSGetNextSearchPathEnumeration(es, nspath))) { + glob_t g; - for (i = 0; i < argc; i++) - readpath(argv[i], pass1, pass2, wflag, lflag); + if (lus.session_type) { + strcat(nspath, "/LaunchAgents"); + } else { + strcat(nspath, "/LaunchDaemons"); + } + + if (glob(nspath, GLOB_TILDE|GLOB_NOSORT, NULL, &g) == 0) { + for (i = 0; i < g.gl_pathc; i++) { + readpath(g.gl_pathv[i], &lus); + } + globfree(&g); + } + } + + for (i = 0; i < (size_t)argc; i++) + readpath(argv[i], &lus); - if (0 == launch_data_array_get_count(pass1) && 0 == launch_data_array_get_count(pass2)) { - fprintf(stderr, "nothing found to %s\n", lflag ? "load" : "unload"); - launch_data_free(pass1); - launch_data_free(pass2); + if (launch_data_array_get_count(lus.pass0) == 0 && + launch_data_array_get_count(lus.pass1) == 0 && + launch_data_array_get_count(lus.pass2) == 0) { + fprintf(stderr, "nothing found to %s\n", lus.load ? "load" : "unload"); + launch_data_free(lus.pass0); + launch_data_free(lus.pass1); + launch_data_free(lus.pass2); return 1; } - if (lflag) { - if (0 < launch_data_array_get_count(pass1)) { - submit_job_pass(pass1); - } - if (0 < launch_data_array_get_count(pass2)) { - submit_job_pass(pass2); - } + if (lus.load) { + distill_jobs(lus.pass1); + submit_mach_jobs(lus.pass0); + submit_job_pass(lus.pass1); + let_go_of_mach_jobs(lus.pass0); + distill_jobs(lus.pass2); + submit_job_pass(lus.pass2); } else { - for (i = 0; i < (int)launch_data_array_get_count(pass1); i++) - unloadjob(launch_data_array_get_index(pass1, i)); - for (i = 0; i < (int)launch_data_array_get_count(pass2); i++) - unloadjob(launch_data_array_get_index(pass2, i)); + for (i = 0; i < launch_data_array_get_count(lus.pass1); i++) + unloadjob(launch_data_array_get_index(lus.pass1, i)); + for (i = 0; i < launch_data_array_get_count(lus.pass2); i++) + unloadjob(launch_data_array_get_index(lus.pass2, i)); } return 0; } -static void submit_job_pass(launch_data_t jobs) +void +submit_mach_jobs(launch_data_t jobs) +{ + size_t i, c; + + c = launch_data_array_get_count(jobs); + + for (i = 0; i < c; i++) { + launch_data_t tmp, oai = launch_data_array_get_index(jobs, i); + const char *sn = NULL, *cmd = NULL; + bool d = true; + mach_port_t msr, msv; + kern_return_t kr; + uid_t u = getuid(); + + if ((tmp = launch_data_dict_lookup(oai, MACHINIT_JOBKEY_ONDEMAND))) + d = launch_data_get_bool(tmp); + if ((tmp = launch_data_dict_lookup(oai, MACHINIT_JOBKEY_SERVICENAME))) + sn = launch_data_get_string(tmp); + if ((tmp = launch_data_dict_lookup(oai, MACHINIT_JOBKEY_COMMAND))) + cmd = launch_data_get_string(tmp); + + if ((kr = bootstrap_create_server(bootstrap_port, (char *)cmd, u, d, &msr)) != KERN_SUCCESS) { + fprintf(stderr, "%s: bootstrap_create_server(): %d\n", getprogname(), kr); + continue; + } + if ((kr = bootstrap_create_service(msr, (char*)sn, &msv)) != KERN_SUCCESS) { + fprintf(stderr, "%s: bootstrap_create_service(): %d\n", getprogname(), kr); + mach_port_destroy(mach_task_self(), msr); + continue; + } + launch_data_dict_insert(oai, launch_data_new_machport(msr), MACHINIT_JOBKEY_SERVERPORT); + launch_data_dict_insert(oai, launch_data_new_machport(msv), MACHINIT_JOBKEY_SERVICEPORT); + } +} + +void +let_go_of_mach_jobs(launch_data_t jobs) +{ + size_t i, c = launch_data_array_get_count(jobs); + + for (i = 0; i < c; i++) { + launch_data_t tmp, oai = launch_data_array_get_index(jobs, i); + if ((tmp = launch_data_dict_lookup(oai, MACHINIT_JOBKEY_SERVICEPORT))) { + mach_port_destroy(mach_task_self(), launch_data_get_machport(tmp)); + } else { + fprintf(stderr, "%s: ack! missing service port!\n", getprogname()); + } + if ((tmp = launch_data_dict_lookup(oai, MACHINIT_JOBKEY_SERVERPORT))) { + mach_port_destroy(mach_task_self(), launch_data_get_machport(tmp)); + } else { + fprintf(stderr, "%s: ack! missing server port!\n", getprogname()); + } + } +} + +void +submit_job_pass(launch_data_t jobs) { launch_data_t msg, resp; size_t i; int e; - for (i = 0; i < launch_data_array_get_count(jobs); i++) - distill_config_file(launch_data_array_get_index(jobs, i)); + if (launch_data_array_get_count(jobs) == 0) + return; msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY); @@ -916,15 +1528,19 @@ static void submit_job_pass(launch_data_t jobs) launch_data_free(msg); } -static int start_and_stop_cmd(int argc, char *const argv[]) +int +start_stop_remove_cmd(int argc, char *const argv[]) { launch_data_t resp, msg; const char *lmsgcmd = LAUNCH_KEY_STOPJOB; int e, r = 0; - if (!strcmp(argv[0], "start")) + if (0 == strcmp(argv[0], "start")) lmsgcmd = LAUNCH_KEY_STARTJOB; + if (0 == strcmp(argv[0], "remove")) + lmsgcmd = LAUNCH_KEY_REMOVEJOB; + if (argc != 2) { fprintf(stderr, "usage: %s %s \n", getprogname(), argv[0]); return 1; @@ -953,34 +1569,122 @@ static int start_and_stop_cmd(int argc, char *const argv[]) return r; } -static void print_jobs(launch_data_t j __attribute__((unused)), const char *label, void *context __attribute__((unused))) +void +print_jobs(launch_data_t j) { + static size_t depth = 0; + launch_data_t lo = launch_data_dict_lookup(j, LAUNCH_JOBKEY_LABEL); + launch_data_t pido = launch_data_dict_lookup(j, LAUNCH_JOBKEY_PID); + launch_data_t stato = launch_data_dict_lookup(j, LAUNCH_JOBKEY_LASTEXITSTATUS); + launch_data_t sjobs = launch_data_dict_lookup(j, LAUNCH_JOBKEY_SUBJOBS); + const char *label = launch_data_get_string(lo); + size_t i, c; + + if (pido) { + fprintf(stdout, "%lld\t-\t", launch_data_get_integer(pido)); + } else if (stato) { + int wstatus = (int)launch_data_get_integer(stato); + if (WIFEXITED(wstatus)) { + fprintf(stdout, "-\t%d\t", WEXITSTATUS(wstatus)); + } else if (WIFSIGNALED(wstatus)) { + fprintf(stdout, "-\t-%d\t", WTERMSIG(wstatus)); + } else { + fprintf(stdout, "-\t???\t"); + } + } else { + fprintf(stdout, "-\t-\t"); + } + for (i = 0; i < depth; i++) + fprintf(stdout, "\t"); + fprintf(stdout, "%s\n", label); + + if (sjobs) { + launch_data_t oai; + + c = launch_data_array_get_count(sjobs); + + depth++; + for (i = 0; i < c; i++) { + oai = launch_data_array_get_index(sjobs, i); + print_jobs(oai); + } + depth--; + } } -static int list_cmd(int argc, char *const argv[]) +void +print_obj(launch_data_t obj, const char *key, void *context __attribute__((unused))) { - launch_data_t resp, msg; - int ch, r = 0; - bool vflag = false; + static size_t indent = 0; + size_t i, c; - while ((ch = getopt(argc, argv, "v")) != -1) { - switch (ch) { - case 'v': - vflag = true; - break; - default: - fprintf(stderr, "usage: %s list [-v]\n", getprogname()); - return 1; - } + for (i = 0; i < indent; i++) + fprintf(stdout, "\t"); + + if (key) + fprintf(stdout, "\"%s\" = ", key); + + switch (launch_data_get_type(obj)) { + case LAUNCH_DATA_STRING: + fprintf(stdout, "\"%s\";\n", launch_data_get_string(obj)); + break; + case LAUNCH_DATA_INTEGER: + fprintf(stdout, "%lld;\n", launch_data_get_integer(obj)); + break; + case LAUNCH_DATA_REAL: + fprintf(stdout, "%f;\n", launch_data_get_real(obj)); + break; + case LAUNCH_DATA_BOOL: + fprintf(stdout, "%s;\n", launch_data_get_bool(obj) ? "true" : "false"); + break; + case LAUNCH_DATA_ARRAY: + c = launch_data_array_get_count(obj); + fprintf(stdout, "(\n"); + indent++; + for (i = 0; i < c; i++) + print_obj(launch_data_array_get_index(obj, i), NULL, NULL); + indent--; + for (i = 0; i < indent; i++) + fprintf(stdout, "\t"); + fprintf(stdout, ");\n"); + break; + case LAUNCH_DATA_DICTIONARY: + fprintf(stdout, "{\n"); + indent++; + launch_data_dict_iterate(obj, print_obj, NULL); + indent--; + for (i = 0; i < indent; i++) + fprintf(stdout, "\t"); + fprintf(stdout, "};\n"); + break; + case LAUNCH_DATA_FD: + fprintf(stdout, "file-descriptor-object;\n"); + break; + case LAUNCH_DATA_MACHPORT: + fprintf(stdout, "mach-port-object;\n"); + break; + default: + fprintf(stdout, "???;\n"); + break; } +} + +int +list_cmd(int argc, char *const argv[]) +{ + launch_data_t resp, msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY); + int r = 0; - if (vflag) { - fprintf(stderr, "usage: %s list: \"-v\" flag not implemented yet\n", getprogname()); + if (argc > 2) { + fprintf(stderr, "usage: %s list [label]\n", getprogname()); return 1; + } else if (argc == 2) { + launch_data_dict_insert(msg, launch_data_new_string(argv[1]), LAUNCH_KEY_GETJOB); + } else { + launch_data_dict_insert(msg, launch_data_new_string(""), LAUNCH_KEY_GETJOB); } - msg = launch_data_new_string(LAUNCH_KEY_GETJOBS); resp = launch_msg(msg); launch_data_free(msg); @@ -988,7 +1692,12 @@ static int list_cmd(int argc, char *const argv[]) fprintf(stderr, "launch_msg(): %s\n", strerror(errno)); return 1; } else if (launch_data_get_type(resp) == LAUNCH_DATA_DICTIONARY) { - launch_data_dict_iterate(resp, print_jobs, NULL); + if (argc == 1) { + fprintf(stdout, "PID\tStatus\tLabel\n"); + print_jobs(resp); + } else { + print_obj(resp, NULL, NULL); + } } else { fprintf(stderr, "%s %s returned unknown response\n", getprogname(), argv[0]); r = 1; @@ -999,7 +1708,8 @@ static int list_cmd(int argc, char *const argv[]) return r; } -static int stdio_cmd(int argc, char *const argv[]) +int +stdio_cmd(int argc, char *const argv[]) { launch_data_t resp, msg, tmp; int e, fd = -1, r = 0; @@ -1009,7 +1719,7 @@ static int stdio_cmd(int argc, char *const argv[]) return 1; } - fd = open(argv[1], O_CREAT|O_APPEND|O_WRONLY, DEFFILEMODE); + fd = open(argv[1], O_CREAT|O_APPEND|O_WRONLY|O_NOCTTY, DEFFILEMODE); msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY); @@ -1049,7 +1759,8 @@ static int stdio_cmd(int argc, char *const argv[]) return r; } -static int fyi_cmd(int argc, char *const argv[]) +int +fyi_cmd(int argc, char *const argv[]) { launch_data_t resp, msg; const char *lmsgk = LAUNCH_KEY_RELOADTTYS; @@ -1060,8 +1771,11 @@ static int fyi_cmd(int argc, char *const argv[]) return 1; } - if (!strcmp(argv[0], "shutdown")) + if (!strcmp(argv[0], "shutdown")) { lmsgk = LAUNCH_KEY_SHUTDOWN; + } else if (!strcmp(argv[0], "singleuser")) { + lmsgk = LAUNCH_KEY_SINGLEUSER; + } msg = launch_data_new_string(lmsgk); resp = launch_msg(msg); @@ -1085,7 +1799,8 @@ static int fyi_cmd(int argc, char *const argv[]) return r; } -static int logupdate_cmd(int argc, char *const argv[]) +int +logupdate_cmd(int argc, char *const argv[]) { launch_data_t resp, msg; int e, i, j, r = 0, m = 0; @@ -1193,65 +1908,84 @@ static int logupdate_cmd(int argc, char *const argv[]) return r; } -static int limit_cmd(int argc __attribute__((unused)), char *const argv[]) +static const struct { + const char *name; + int lim; +} limlookup[] = { + { "cpu", RLIMIT_CPU }, + { "filesize", RLIMIT_FSIZE }, + { "data", RLIMIT_DATA }, + { "stack", RLIMIT_STACK }, + { "core", RLIMIT_CORE }, + { "rss", RLIMIT_RSS }, + { "memlock", RLIMIT_MEMLOCK }, + { "maxproc", RLIMIT_NPROC }, + { "maxfiles", RLIMIT_NOFILE } +}; + +static const size_t limlookupcnt = sizeof limlookup / sizeof limlookup[0]; + +ssize_t +name2num(const char *n) +{ + size_t i; + + for (i = 0; i < limlookupcnt; i++) { + if (!strcmp(limlookup[i].name, n)) { + return limlookup[i].lim; + } + } + return -1; +} + +const char * +num2name(int n) +{ + size_t i; + + for (i = 0; i < limlookupcnt; i++) { + if (limlookup[i].lim == n) + return limlookup[i].name; + } + return NULL; +} + +const char * +lim2str(rlim_t val, char *buf) +{ + if (val == RLIM_INFINITY) + strcpy(buf, "unlimited"); + else + sprintf(buf, "%lld", val); + return buf; +} + +bool +str2lim(const char *buf, rlim_t *res) +{ + char *endptr; + *res = strtoll(buf, &endptr, 10); + if (!strcmp(buf, "unlimited")) { + *res = RLIM_INFINITY; + return false; + } else if (*endptr == '\0') { + return false; + } + return true; +} + +int +limit_cmd(int argc __attribute__((unused)), char *const argv[]) { char slimstr[100]; char hlimstr[100]; struct rlimit *lmts = NULL; launch_data_t resp, resp1 = NULL, msg, tmp; int r = 0; - size_t i, lsz = -1, which = 0; + size_t i, lsz = -1; + ssize_t which = 0; rlim_t slim = -1, hlim = -1; bool badargs = false; - static const struct { - const char *name; - int lim; - } limlookup[] = { - { "cpu", RLIMIT_CPU }, - { "filesize", RLIMIT_FSIZE }, - { "data", RLIMIT_DATA }, - { "stack", RLIMIT_STACK }, - { "core", RLIMIT_CORE }, - { "rss", RLIMIT_RSS }, - { "memlock", RLIMIT_MEMLOCK }, - { "maxproc", RLIMIT_NPROC }, - { "maxfiles", RLIMIT_NOFILE } - }; - size_t limlookupcnt = sizeof limlookup / sizeof limlookup[0]; - bool name2num(const char *n) { - for (i = 0; i < limlookupcnt; i++) { - if (!strcmp(limlookup[i].name, n)) { - which = limlookup[i].lim; - return false; - } - } - return true; - }; - const char *num2name(int n) { - for (i = 0; i < limlookupcnt; i++) { - if (limlookup[i].lim == n) - return limlookup[i].name; - } - return NULL; - }; - const char *lim2str(rlim_t val, char *buf) { - if (val == RLIM_INFINITY) - strcpy(buf, "unlimited"); - else - sprintf(buf, "%lld", val); - return buf; - }; - bool str2lim(const char *buf, rlim_t *res) { - char *endptr; - *res = strtoll(buf, &endptr, 10); - if (!strcmp(buf, "unlimited")) { - *res = RLIM_INFINITY; - return false; - } else if (*endptr == '\0') { - return false; - } - return true; - }; if (argc > 4) badargs = true; @@ -1264,7 +1998,7 @@ static int limit_cmd(int argc __attribute__((unused)), char *const argv[]) if (argc == 4 && str2lim(argv[3], &hlim)) badargs = true; - if (argc >= 2 && name2num(argv[1])) + if (argc >= 2 && -1 == (which = name2num(argv[1]))) badargs = true; if (badargs) { @@ -1287,7 +2021,7 @@ static int limit_cmd(int argc __attribute__((unused)), char *const argv[]) lsz = launch_data_get_opaque_size(resp); if (argc <= 2) { for (i = 0; i < (lsz / sizeof(struct rlimit)); i++) { - if (argc == 2 && which != i) + if (argc == 2 && (size_t)which != i) continue; fprintf(stdout, "\t%-12s%-15s%-15s\n", num2name(i), lim2str(lmts[i].rlim_cur, slimstr), @@ -1335,7 +2069,8 @@ static int limit_cmd(int argc __attribute__((unused)), char *const argv[]) return r; } -static int umask_cmd(int argc, char *const argv[]) +int +umask_cmd(int argc, char *const argv[]) { launch_data_t resp, msg; bool badargs = false; @@ -1382,7 +2117,69 @@ static int umask_cmd(int argc, char *const argv[]) return r; } -static int getrusage_cmd(int argc, char *const argv[]) +int +submit_cmd(int argc, char *const argv[]) +{ + launch_data_t msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY); + launch_data_t job = launch_data_alloc(LAUNCH_DATA_DICTIONARY); + launch_data_t resp, largv = launch_data_alloc(LAUNCH_DATA_ARRAY); + int ch, i, r = 0; + + launch_data_dict_insert(job, launch_data_new_bool(false), LAUNCH_JOBKEY_ONDEMAND); + + while ((ch = getopt(argc, argv, "l:p:o:e:")) != -1) { + switch (ch) { + case 'l': + launch_data_dict_insert(job, launch_data_new_string(optarg), LAUNCH_JOBKEY_LABEL); + break; + case 'p': + launch_data_dict_insert(job, launch_data_new_string(optarg), LAUNCH_JOBKEY_PROGRAM); + break; + case 'o': + launch_data_dict_insert(job, launch_data_new_string(optarg), LAUNCH_JOBKEY_STANDARDOUTPATH); + break; + case 'e': + launch_data_dict_insert(job, launch_data_new_string(optarg), LAUNCH_JOBKEY_STANDARDERRORPATH); + break; + default: + fprintf(stderr, "usage: %s submit ...\n", getprogname()); + return 1; + } + } + argc -= optind; + argv += optind; + + for (i = 0; argv[i]; i++) { + launch_data_array_append(largv, launch_data_new_string(argv[i])); + } + + launch_data_dict_insert(job, largv, LAUNCH_JOBKEY_PROGRAMARGUMENTS); + + launch_data_dict_insert(msg, job, LAUNCH_KEY_SUBMITJOB); + + resp = launch_msg(msg); + launch_data_free(msg); + + if (resp == NULL) { + fprintf(stderr, "launch_msg(): %s\n", strerror(errno)); + return 1; + } else if (launch_data_get_type(resp) == LAUNCH_DATA_ERRNO) { + errno = launch_data_get_errno(resp); + if (errno) { + fprintf(stderr, "%s %s error: %s\n", getprogname(), argv[0], strerror(errno)); + r = 1; + } + } else { + fprintf(stderr, "%s %s error: %s\n", getprogname(), argv[0], "unknown response"); + } + + launch_data_free(resp); + + return r; +} + +int +getrusage_cmd(int argc, char *const argv[]) { launch_data_t resp, msg; bool badargs = false; @@ -1443,9 +2240,434 @@ static int getrusage_cmd(int argc, char *const argv[]) return r; } -static bool launch_data_array_append(launch_data_t a, launch_data_t o) +bool +launch_data_array_append(launch_data_t a, launch_data_t o) { size_t offt = launch_data_array_get_count(a); return launch_data_array_set_index(a, o, offt); } + +mach_port_t +str2bsport(const char *s) +{ + bool getrootbs = strcmp(s, "/") == 0; + mach_port_t last_bport, bport = bootstrap_port; + task_t task = mach_task_self(); + kern_return_t result; + + if (strcmp(s, "..") == 0 || getrootbs) { + do { + last_bport = bport; + result = bootstrap_parent(last_bport, &bport); + + if (result == BOOTSTRAP_NOT_PRIVILEGED) { + fprintf(stderr, "Permission denied\n"); + return 1; + } else if (result != BOOTSTRAP_SUCCESS) { + fprintf(stderr, "bootstrap_parent() %d\n", result); + return 1; + } + } while (getrootbs && last_bport != bport); + } else { + int pid = atoi(s); + + result = task_for_pid(mach_task_self(), pid, &task); + + if (result != KERN_SUCCESS) { + fprintf(stderr, "task_for_pid() %s\n", mach_error_string(result)); + return 1; + } + + result = task_get_bootstrap_port(task, &bport); + + if (result != KERN_SUCCESS) { + fprintf(stderr, "Couldn't get bootstrap port: %s\n", mach_error_string(result)); + return 1; + } + } + + return bport; +} + +int +bsexec_cmd(int argc, char *const argv[]) +{ + kern_return_t result; + mach_port_t bport; + + if (argc < 3) { + fprintf(stderr, "usage: %s bsexec prog...\n", getprogname()); + return 1; + } + + bport = str2bsport(argv[1]); + + result = task_set_bootstrap_port(mach_task_self(), bport); + + if (result != KERN_SUCCESS) { + fprintf(stderr, "Couldn't switch to new bootstrap port: %s\n", mach_error_string(result)); + return 1; + } + + setgid(getgid()); + setuid(getuid()); + + execvp(argv[2], argv + 2); + fprintf(stderr, "execvp(): %s\n", strerror(errno)); + return 1; +} + +int +bslist_cmd(int argc, char *const argv[]) +{ + kern_return_t result; + mach_port_t bport = bootstrap_port; + name_array_t service_names; + mach_msg_type_number_t service_cnt, service_active_cnt; + bootstrap_status_array_t service_actives; + unsigned int i; + + if (argc == 2) + bport = str2bsport(argv[1]); + + if (bport == MACH_PORT_NULL) { + fprintf(stderr, "Invalid bootstrap port\n"); + return 1; + } + + result = bootstrap_info(bport, &service_names, &service_cnt, &service_actives, &service_active_cnt); + if (result != BOOTSTRAP_SUCCESS) { + fprintf(stderr, "bootstrap_info(): %d\n", result); + return 1; + } + +#define bport_state(x) (((x) == BOOTSTRAP_STATUS_ACTIVE) ? "A" : ((x) == BOOTSTRAP_STATUS_ON_DEMAND) ? "D" : "I") + + for (i = 0; i < service_cnt ; i++) + fprintf(stdout, "%-3s%s\n", bport_state((service_actives[i])), service_names[i]); + + return 0; +} + +bool +is_legacy_mach_job(launch_data_t obj) +{ + bool has_servicename = launch_data_dict_lookup(obj, MACHINIT_JOBKEY_SERVICENAME); + bool has_command = launch_data_dict_lookup(obj, MACHINIT_JOBKEY_COMMAND); + bool has_label = launch_data_dict_lookup(obj, LAUNCH_JOBKEY_LABEL); + + return has_command && has_servicename && !has_label; +} + +void +_log_launchctl_bug(const char *rcs_rev, const char *path, unsigned int line, const char *test) +{ + int saved_errno = errno; + char buf[100]; + const char *file = strrchr(path, '/'); + char *rcs_rev_tmp = strchr(rcs_rev, ' '); + + if (!file) { + file = path; + } else { + file += 1; + } + + 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'; + } + + fprintf(stderr, "Bug: %s:%u (%s):%u: %s\n", file, line, buf, saved_errno, test); +} + +void +loopback_setup_ipv4(void) +{ + struct ifaliasreq ifra; + struct ifreq ifr; + int s; + + memset(&ifr, 0, sizeof(ifr)); + strcpy(ifr.ifr_name, "lo0"); + + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) + return; + + if (assumes(ioctl(s, SIOCGIFFLAGS, &ifr) != -1)) { + ifr.ifr_flags |= IFF_UP; + assumes(ioctl(s, SIOCSIFFLAGS, &ifr) != -1); + } + + 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); + + assumes(ioctl(s, SIOCAIFADDR, &ifra) != -1); + + assumes(close(s) == 0); +} + +void +loopback_setup_ipv6(void) +{ + struct in6_aliasreq ifra6; + struct ifreq ifr; + int s6; + + memset(&ifr, 0, sizeof(ifr)); + strcpy(ifr.ifr_name, "lo0"); + + if ((s6 = socket(AF_INET6, SOCK_DGRAM, 0)) == -1) + return; + + memset(&ifr, 0, sizeof(ifr)); + strcpy(ifr.ifr_name, "lo0"); + + if (assumes(ioctl(s6, SIOCGIFFLAGS, &ifr) != -1)) { + ifr.ifr_flags |= IFF_UP; + assumes(ioctl(s6, SIOCSIFFLAGS, &ifr) != -1); + } + + 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; + + assumes(ioctl(s6, SIOCAIFADDR_IN6, &ifra6) != -1); + + assumes(close(s6) == 0); +} + +pid_t +fwexec(const char *const *argv, bool _wait) +{ + int wstatus; + pid_t p; + + switch ((p = fork())) { + case -1: + break; + case 0: + setsid(); + execvp(argv[0], (char *const *)argv); + _exit(EXIT_FAILURE); + break; + default: + if (!_wait) + return p; + if (p == waitpid(p, &wstatus, 0)) { + if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) == EXIT_SUCCESS) + return p; + } + break; + } + + return -1; +} + +void +do_potential_fsck(void) +{ + const char *safe_fsck_tool[] = { "fsck", "-fy", NULL }; + const char *fsck_tool[] = { "fsck", "-p", NULL }; + const char *remount_tool[] = { "mount", "-uw", "/", NULL }; + struct statfs sfs; + + if (assumes(statfs("/", &sfs) != -1)) { + if (!(sfs.f_flags & MNT_RDONLY)) { + fprintf(stdout, "Root file system is read-write, skipping fsck.\n"); + return; + } + } + + if (!is_safeboot()) { + if (fwexec(fsck_tool, true) != -1) + goto out; + } + + if (fwexec(safe_fsck_tool, true) != -1) { + goto out; + } + + fprintf(stderr, "fsck failed! Leaving the root file system read-only...\n"); + + return; +out: + assumes(fwexec(remount_tool, true) != -1); +} + +bool +path_check(const char *path) +{ + struct stat sb; + + if (stat(path, &sb) == 0) + return true; + return false; +} + +bool +is_safeboot(void) +{ + int sbmib[] = { CTL_KERN, KERN_SAFEBOOT }; + uint32_t sb = 0; + size_t sbsz = sizeof(sb); + + if (!assumes(sysctl(sbmib, 2, &sb, &sbsz, NULL, 0) == 0)) + return false; + + return (bool)sb; +} + +bool +is_netboot(void) +{ + int nbmib[] = { CTL_KERN, KERN_NETBOOT }; + uint32_t nb = 0; + size_t nbsz = sizeof(nb); + + if (!assumes(sysctl(nbmib, 2, &nb, &nbsz, NULL, 0) == 0)) + return false; + + return (bool)nb; +} + +void +apply_func_to_dir(const char *thedir, void (*thefunc)(const char *)) +{ + struct dirent *de; + DIR *od; + int currend_dir_fd; + + if (!assumes((currend_dir_fd = open(".", 0)) != -1)) + return; + + if (!assumes(chdir(thedir) != -1)) + goto out; + + if (!assumes(od = opendir("."))) + goto out; + + while ((de = readdir(od))) { + struct stat sb; + + if (strcmp(de->d_name, ".") == 0) + continue; + if (strcmp(de->d_name, "..") == 0) + continue; + + if (assumes(stat(de->d_name, &sb) != -1)) { + if (S_ISDIR(sb.st_mode)) + apply_func_to_dir(de->d_name, thefunc); + thefunc(de->d_name); + } + } + + assumes(closedir(od) != -1); + +out: + assumes(fchdir(currend_dir_fd) != -1); + assumes(close(currend_dir_fd) != -1); +} + +void +empty_dir(const char *path) +{ + assumes(chflags(path, 0) != -1); + assumes(remove(path) != -1); +} + +int +touch_file(const char *path, mode_t m) +{ + int fd = open(path, O_CREAT, m); + + if (fd == -1) + return -1; + + return close(fd); +} + +void +apply_sysctls_from_file(const char *thefile) +{ + const char *sysctl_tool[] = { "sysctl", "-w", NULL, NULL }; + size_t ln_len = 0; + char *val, *tmpstr; + FILE *sf; + + if (!(sf = fopen(thefile, "r"))) + return; + + while ((val = fgetln(sf, &ln_len))) { + if (ln_len == 0) + continue; + if (!assumes((tmpstr = malloc(ln_len + 1)) != NULL)) + continue; + memcpy(tmpstr, val, ln_len); + tmpstr[ln_len] = 0; + val = tmpstr; + + while (*val && isspace(*val)) + val++; + if (*val == '\0' || *val == '#') + goto skip_sysctl_tool; + sysctl_tool[2] = val; + assumes(fwexec(sysctl_tool, true) != -1); +skip_sysctl_tool: + free(tmpstr); + } + + assumes(fclose(sf) == 0); +} + +void +do_sysversion_sysctl(void) +{ + int mib[] = { CTL_KERN, KERN_OSVERSION }; + CFDictionaryRef versdict; + CFStringRef buildvers; + char buf[1024]; + size_t bufsz = sizeof(buf); + + /* ER: launchd should set kern.osversion very early in boot */ + + if (getuid() != 0) + return; + + if (sysctl(mib, 2, buf, &bufsz, NULL, 0) == -1) { + fprintf(stderr, "sysctl(): %s\n", strerror(errno)); + return; + } + + if (buf[0] != '\0') + return; + + versdict = _CFCopySystemVersionDictionary(); + buildvers = CFDictionaryGetValue(versdict, _kCFSystemVersionBuildVersionKey); + CFStringGetCString(buildvers, buf, sizeof(buf), kCFStringEncodingUTF8); + + if (sysctl(mib, 2, NULL, 0, buf, strlen(buf) + 1) == -1) { + fprintf(stderr, "sysctl(): %s\n", strerror(errno)); + } + + CFRelease(versdict); +}