]> git.saurik.com Git - apple/launchd.git/blob - launchd/src/launchd.c
0a714e15c52aad358710ccba7dbeeb36631df7ab
[apple/launchd.git] / launchd / src / launchd.c
1 /*
2 * Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23 #include <Security/Authorization.h>
24 #include <Security/AuthorizationTags.h>
25 #include <Security/AuthSession.h>
26 #include <sys/types.h>
27 #include <sys/queue.h>
28 #include <sys/event.h>
29 #include <sys/stat.h>
30 #include <sys/ucred.h>
31 #include <sys/fcntl.h>
32 #include <sys/un.h>
33 #include <sys/wait.h>
34 #include <sys/sysctl.h>
35 #include <sys/sockio.h>
36 #include <sys/time.h>
37 #include <sys/resource.h>
38 #include <sys/ioctl.h>
39 #include <sys/mount.h>
40 #include <net/if.h>
41 #include <netinet/in.h>
42 #include <netinet/in_var.h>
43 #include <netinet6/nd6.h>
44 #include <unistd.h>
45 #include <signal.h>
46 #include <errno.h>
47 #include <syslog.h>
48 #include <libgen.h>
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <stdarg.h>
52 #include <stdbool.h>
53 #include <pthread.h>
54 #include <paths.h>
55 #include <pwd.h>
56 #include <grp.h>
57 #include <dlfcn.h>
58 #include <dirent.h>
59
60 #include "launch.h"
61 #include "launch_priv.h"
62 #include "launchd.h"
63
64 #include "bootstrap_internal.h"
65
66 #define LAUNCHD_MIN_JOB_RUN_TIME 10
67 #define LAUNCHD_REWARD_JOB_RUN_TIME 60
68 #define LAUNCHD_FAILED_EXITS_THRESHOLD 10
69 #define PID1LAUNCHD_CONF "/etc/launchd.conf"
70 #define LAUNCHD_CONF ".launchd.conf"
71 #define LAUNCHCTL_PATH "/bin/launchctl"
72 #define SECURITY_LIB "/System/Library/Frameworks/Security.framework/Versions/A/Security"
73 #define VOLFSDIR "/.vol"
74
75 extern char **environ;
76
77 struct jobcb {
78 kq_callback kqjob_callback;
79 TAILQ_ENTRY(jobcb) tqe;
80 launch_data_t ldj;
81 pid_t p;
82 int execfd;
83 time_t start_time;
84 size_t failed_exits;
85 int *vnodes;
86 size_t vnodes_cnt;
87 int *qdirs;
88 size_t qdirs_cnt;
89 unsigned int start_interval;
90 struct tm *start_cal_interval;
91 unsigned int checkedin:1, firstborn:1, debug:1, throttle:1, futureflags:28;
92 char label[0];
93 };
94
95 struct conncb {
96 kq_callback kqconn_callback;
97 TAILQ_ENTRY(conncb) tqe;
98 launch_t conn;
99 struct jobcb *j;
100 int disabled_batch:1, futureflags:31;
101 };
102
103 static TAILQ_HEAD(jobcbhead, jobcb) jobs = TAILQ_HEAD_INITIALIZER(jobs);
104 static TAILQ_HEAD(conncbhead, conncb) connections = TAILQ_HEAD_INITIALIZER(connections);
105 static int mainkq = 0;
106 static int asynckq = 0;
107 static int batch_disabler_count = 0;
108
109 static launch_data_t load_job(launch_data_t pload);
110 static launch_data_t get_jobs(const char *which);
111 static launch_data_t setstdio(int d, launch_data_t o);
112 static launch_data_t adjust_rlimits(launch_data_t in);
113 static void batch_job_enable(bool e, struct conncb *c);
114 static void do_shutdown(void);
115
116 static void listen_callback(void *, struct kevent *);
117 static void async_callback(void);
118 static void signal_callback(void *, struct kevent *);
119 static void fs_callback(void);
120 static void simple_zombie_reaper(void *, struct kevent *);
121 static void readcfg_callback(void *, struct kevent *);
122
123 static kq_callback kqlisten_callback = listen_callback;
124 static kq_callback kqasync_callback = (kq_callback)async_callback;
125 static kq_callback kqsignal_callback = signal_callback;
126 static kq_callback kqfs_callback = (kq_callback)fs_callback;
127 static kq_callback kqreadcfg_callback = readcfg_callback;
128 kq_callback kqsimple_zombie_reaper = simple_zombie_reaper;
129
130 static void job_watch(struct jobcb *j);
131 static void job_ignore(struct jobcb *j);
132 static void job_start(struct jobcb *j);
133 static void job_start_child(struct jobcb *j, int execfd);
134 static void job_setup_attributes(struct jobcb *j);
135 static void job_stop(struct jobcb *j);
136 static void job_reap(struct jobcb *j);
137 static void job_remove(struct jobcb *j);
138 static void job_set_alarm(struct jobcb *j);
139 static void job_callback(void *obj, struct kevent *kev);
140 static void job_log(struct jobcb *j, int pri, const char *msg, ...) __attribute__((format(printf, 3, 4)));
141 static void job_log_error(struct jobcb *j, int pri, const char *msg, ...) __attribute__((format(printf, 3, 4)));
142 static void job_prep_log_msg(struct jobcb *j, char *buf, const char *msg, int err);
143
144 static void ipc_open(int fd, struct jobcb *j);
145 static void ipc_close(struct conncb *c);
146 static void ipc_callback(void *, struct kevent *);
147 static void ipc_readmsg(launch_data_t msg, void *context);
148 static void ipc_readmsg2(launch_data_t data, const char *cmd, void *context);
149
150 #ifdef PID1_REAP_ADOPTED_CHILDREN
151 static void pid1waitpid(void);
152 static bool launchd_check_pid(pid_t p);
153 #endif
154 static void pid1_magic_init(bool sflag, bool vflag, bool xflag);
155 static void launchd_server_init(bool create_session);
156 static void conceive_firstborn(char *argv[]);
157
158 static void usage(FILE *where);
159 static int _fd(int fd);
160
161 static void loopback_setup(void);
162 static void workaround3048875(int argc, char *argv[]);
163 static void reload_launchd_config(void);
164 static int dir_has_files(const char *path);
165 static void testfd_or_openfd(int fd, const char *path, int flags);
166 static void setup_job_env(launch_data_t obj, const char *key, void *context);
167 static void unsetup_job_env(launch_data_t obj, const char *key, void *context);
168
169 static time_t cronemu(int mon, int mday, int hour, int min);
170 static time_t cronemu_wday(int wday, int hour, int min);
171 static bool cronemu_mon(struct tm *wtm, int mon, int mday, int hour, int min);
172 static bool cronemu_mday(struct tm *wtm, int mday, int hour, int min);
173 static bool cronemu_hour(struct tm *wtm, int hour, int min);
174 static bool cronemu_min(struct tm *wtm, int min);
175
176 static size_t total_children = 0;
177 static pid_t readcfg_pid = 0;
178 static pid_t launchd_proper_pid = 0;
179 static bool launchd_inited = false;
180 static bool shutdown_in_progress = false;
181 static pthread_t mach_server_loop_thread;
182 mach_port_t launchd_bootstrap_port = MACH_PORT_NULL;
183 sigset_t blocked_signals = 0;
184 pthread_mutex_t giant_lock = PTHREAD_MUTEX_INITIALIZER;
185 static char *pending_stdout = NULL;
186 static char *pending_stderr = NULL;
187
188 int main(int argc, char *argv[])
189 {
190 static const int sigigns[] = { SIGHUP, SIGINT, SIGPIPE, SIGALRM,
191 SIGTERM, SIGURG, SIGTSTP, SIGTSTP, SIGCONT, /*SIGCHLD,*/
192 SIGTTIN, SIGTTOU, SIGIO, SIGXCPU, SIGXFSZ, SIGVTALRM, SIGPROF,
193 SIGWINCH, SIGINFO, SIGUSR1, SIGUSR2 };
194 struct kevent kev;
195 size_t i;
196 bool sflag = false, xflag = false, vflag = false, dflag = false;
197 int ch;
198
199 __log_liblaunch_bug = _log_launchd_bug;
200
201 if (getpid() == 1)
202 workaround3048875(argc, argv);
203
204 setegid(getgid());
205 seteuid(getuid());
206
207 testfd_or_openfd(STDIN_FILENO, _PATH_DEVNULL, O_RDONLY);
208 testfd_or_openfd(STDOUT_FILENO, _PATH_DEVNULL, O_WRONLY);
209 testfd_or_openfd(STDERR_FILENO, _PATH_DEVNULL, O_WRONLY);
210
211 openlog(getprogname(), LOG_CONS|(getpid() != 1 ? LOG_PID|LOG_PERROR : 0), LOG_LAUNCHD);
212 setlogmask(LOG_UPTO(LOG_NOTICE));
213
214 while ((ch = getopt(argc, argv, "dhsvx")) != -1) {
215 switch (ch) {
216 case 'd': dflag = true; break;
217 case 's': sflag = true; break;
218 case 'x': xflag = true; break;
219 case 'v': vflag = true; break;
220 case 'h': usage(stdout); break;
221 default:
222 syslog(LOG_WARNING, "ignoring unknown arguments");
223 usage(stderr);
224 break;
225 }
226 }
227 argc -= optind;
228 argv += optind;
229
230 if (dflag) {
231 assumes(daemon(0, 0) != -1);
232 }
233
234 if (!assumes((mainkq = kqueue()) != -1)) {
235 abort();
236 }
237
238 if (!assumes((asynckq = kqueue()) != -1)) {
239 abort();
240 }
241
242 if (!assumes(kevent_mod(asynckq, EVFILT_READ, EV_ADD, 0, 0, &kqasync_callback) != -1)) {
243 abort();
244 }
245
246 sigemptyset(&blocked_signals);
247
248 for (i = 0; i < (sizeof(sigigns) / sizeof(int)); i++) {
249 assumes(kevent_mod(sigigns[i], EVFILT_SIGNAL, EV_ADD, 0, 0, &kqsignal_callback) != -1);
250 sigaddset(&blocked_signals, sigigns[i]);
251 signal(sigigns[i], SIG_IGN);
252 }
253
254 /* sigh... ignoring SIGCHLD has side effects: we can't call wait*() */
255 assumes(kevent_mod(SIGCHLD, EVFILT_SIGNAL, EV_ADD, 0, 0, &kqsignal_callback) != -1);
256
257 assumes(pthread_mutex_lock(&giant_lock) == 0);
258
259 if (getpid() == 1) {
260 pid1_magic_init(sflag, vflag, xflag);
261 } else {
262 launchd_bootstrap_port = bootstrap_port;
263 launchd_server_init(argv[0] ? true : false);
264 }
265
266 /* do this after pid1_magic_init() to not catch ourselves mounting stuff */
267 assumes(kevent_mod(0, EVFILT_FS, EV_ADD, 0, 0, &kqfs_callback) != -1);
268
269
270 if (argv[0])
271 conceive_firstborn(argv);
272
273 reload_launchd_config();
274
275 if (argv[0])
276 job_start(TAILQ_FIRST(&jobs));
277
278 for (;;) {
279 static struct timespec timeout = { 30, 0 };
280 struct timespec *timeoutp = NULL;
281 int kev_r;
282
283 if (getpid() == 1) {
284 if (readcfg_pid == 0)
285 init_pre_kevent();
286 } else {
287 if (TAILQ_EMPTY(&jobs)) {
288 /* launched on demand */
289 timeoutp = &timeout;
290 } else if (shutdown_in_progress && total_children == 0) {
291 exit(EXIT_SUCCESS);
292 }
293 }
294
295 assumes(pthread_mutex_unlock(&giant_lock) == 0);
296 kev_r = kevent(mainkq, NULL, 0, &kev, 1, timeoutp);
297 assumes(pthread_mutex_lock(&giant_lock) == 0);
298
299 switch (kev_r) {
300 case -1:
301 syslog(LOG_DEBUG, "kevent(): %m");
302 break;
303 case 1:
304 (*((kq_callback *)kev.udata))(kev.udata, &kev);
305 break;
306 case 0:
307 if (timeoutp)
308 exit(EXIT_SUCCESS);
309 else
310 syslog(LOG_DEBUG, "kevent(): spurious return with infinite timeout");
311 break;
312 default:
313 syslog(LOG_DEBUG, "unexpected: kevent() returned something != 0, -1 or 1");
314 break;
315 }
316 }
317 }
318
319 static void pid1_magic_init(bool sflag, bool vflag, bool xflag)
320 {
321 pthread_attr_t attr;
322 int memmib[2] = { CTL_HW, HW_MEMSIZE };
323 int mvnmib[2] = { CTL_KERN, KERN_MAXVNODES };
324 int hnmib[2] = { CTL_KERN, KERN_HOSTNAME };
325 #ifdef KERN_TFP
326 int tfp_r_mib[3] = { CTL_KERN, KERN_TFP, KERN_TFP_READ_GROUP };
327 int tfp_rw_mib[3] = { CTL_KERN, KERN_TFP, KERN_TFP_RW_GROUP };
328 gid_t tfp_r_gid = 0;
329 gid_t tfp_rw_gid = 0;
330 struct group *tfp_gr;
331 #endif
332 uint64_t mem = 0;
333 uint32_t mvn;
334 size_t memsz = sizeof(mem);
335
336 #ifdef KERN_TFP
337 if ((tfp_gr = getgrnam("procview"))) {
338 tfp_r_gid = tfp_gr->gr_gid;
339 assumes(sysctl(tfp_r_mib, 3, NULL, NULL, &tfp_r_gid, sizeof(tfp_r_gid)) != -1);
340 }
341
342 if ((tfp_gr = getgrnam("procmod"))) {
343 tfp_rw_gid = tfp_gr->gr_gid;
344 assumes(sysctl(tfp_rw_mib, 3, NULL, NULL, &tfp_rw_gid, sizeof(tfp_rw_gid)) != -1);
345 }
346 #endif
347
348 setpriority(PRIO_PROCESS, 0, -1);
349
350 assumes(setsid() != -1);
351
352 assumes(chdir("/") != -1);
353
354 if (assumes(sysctl(memmib, 2, &mem, &memsz, NULL, 0) != -1)) {
355 /* The following assignment of mem to itself if the size
356 * of data returned is 32 bits instead of 64 is a clever
357 * C trick to move the 32 bits on big endian systems to
358 * the least significant bytes of the 64 mem variable.
359 *
360 * On little endian systems, this is effectively a no-op.
361 */
362 if (memsz == 4)
363 mem = *(uint32_t *)&mem;
364 mvn = mem / (64 * 1024) + 1024;
365 assumes(sysctl(mvnmib, 2, NULL, NULL, &mvn, sizeof(mvn)) != -1);
366 }
367 assumes(sysctl(hnmib, 2, NULL, NULL, "localhost", sizeof("localhost")) != -1);
368
369 assumes(setlogin("root") != -1);
370
371 loopback_setup();
372
373 assumes(mount("fdesc", "/dev", MNT_UNION, NULL) != -1);
374
375 setenv("PATH", _PATH_STDPATH, 1);
376
377 launchd_bootstrap_port = mach_init_init();
378 assumes(task_set_bootstrap_port(mach_task_self(), launchd_bootstrap_port) == KERN_SUCCESS);
379 bootstrap_port = MACH_PORT_NULL;
380
381 assumes(pthread_attr_init(&attr) == 0);
382 assumes(pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) == 0);
383
384 if (!assumes(pthread_create(&mach_server_loop_thread, &attr, mach_server_loop, NULL) == 0)) {
385 abort();
386 }
387
388 pthread_attr_destroy(&attr);
389
390 init_boot(sflag, vflag, xflag);
391 }
392
393
394 #ifdef PID1_REAP_ADOPTED_CHILDREN
395 static bool launchd_check_pid(pid_t p)
396 {
397 struct kevent kev;
398 struct jobcb *j;
399
400 TAILQ_FOREACH(j, &jobs, tqe) {
401 if (j->p == p) {
402 EV_SET(&kev, p, EVFILT_PROC, 0, 0, 0, j);
403 j->kqjob_callback(j, &kev);
404 return true;
405 }
406 }
407
408 if (p == readcfg_pid) {
409 readcfg_callback(NULL, NULL);
410 return true;
411 }
412
413 return false;
414 }
415 #endif
416
417 static char *sockdir = NULL;
418 static char *sockpath = NULL;
419
420 static void launchd_clean_up(void)
421 {
422 if (launchd_proper_pid != getpid())
423 return;
424
425 seteuid(0);
426 setegid(0);
427
428 if (assumes(unlink(sockpath) != -1))
429 assumes(rmdir(sockdir) != -1);
430
431 setegid(getgid());
432 seteuid(getuid());
433 }
434
435 static void launchd_server_init(bool create_session)
436 {
437 struct sockaddr_un sun;
438 mode_t oldmask;
439 int r, fd = -1, ourdirfd = -1;
440 char ourdir[1024];
441
442 memset(&sun, 0, sizeof(sun));
443 sun.sun_family = AF_UNIX;
444
445 if (create_session) {
446 snprintf(ourdir, sizeof(ourdir), "%s/%u.%u", LAUNCHD_SOCK_PREFIX, getuid(), getpid());
447 snprintf(sun.sun_path, sizeof(sun.sun_path), "%s/%u.%u/sock", LAUNCHD_SOCK_PREFIX, getuid(), getpid());
448 setenv(LAUNCHD_SOCKET_ENV, sun.sun_path, 1);
449 } else {
450 snprintf(ourdir, sizeof(ourdir), "%s/%u", LAUNCHD_SOCK_PREFIX, getuid());
451 snprintf(sun.sun_path, sizeof(sun.sun_path), "%s/%u/sock", LAUNCHD_SOCK_PREFIX, getuid());
452 }
453
454 seteuid(0);
455 setegid(0);
456
457 if (mkdir(LAUNCHD_SOCK_PREFIX, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) == -1) {
458 if (errno == EROFS) {
459 goto out_bad;
460 } else if (errno == EEXIST) {
461 struct stat sb;
462 stat(LAUNCHD_SOCK_PREFIX, &sb);
463 if (!S_ISDIR(sb.st_mode)) {
464 errno = EEXIST;
465 syslog(LOG_ERR, "mkdir(\"%s\"): %m", LAUNCHD_SOCK_PREFIX);
466 goto out_bad;
467 }
468 } else {
469 syslog(LOG_ERR, "mkdir(\"%s\"): %m", LAUNCHD_SOCK_PREFIX);
470 goto out_bad;
471 }
472 }
473
474 unlink(ourdir);
475 if (mkdir(ourdir, S_IRWXU) == -1) {
476 if (errno == EROFS) {
477 goto out_bad;
478 } else if (errno == EEXIST) {
479 struct stat sb;
480 stat(ourdir, &sb);
481 if (!S_ISDIR(sb.st_mode)) {
482 errno = EEXIST;
483 syslog(LOG_ERR, "mkdir(\"%s\"): %m", LAUNCHD_SOCK_PREFIX);
484 goto out_bad;
485 }
486 } else {
487 syslog(LOG_ERR, "mkdir(\"%s\"): %m", ourdir);
488 goto out_bad;
489 }
490 }
491
492 assumes(chown(ourdir, getuid(), getgid()) != -1);
493
494 setegid(getgid());
495 seteuid(getuid());
496
497 if (!assumes((ourdirfd = _fd(open(ourdir, O_RDONLY))) != -1)) {
498 goto out_bad;
499 }
500
501 if (flock(ourdirfd, LOCK_EX|LOCK_NB) == -1) {
502 if (errno == EWOULDBLOCK) {
503 exit(EXIT_SUCCESS);
504 } else {
505 syslog(LOG_ERR, "flock(\"%s\"): %m", ourdir);
506 goto out_bad;
507 }
508 }
509
510 if (unlink(sun.sun_path) == -1 && errno != ENOENT) {
511 if (errno != EROFS)
512 syslog(LOG_ERR, "unlink(\"thesocket\"): %m");
513 goto out_bad;
514 }
515 if (!assumes((fd = _fd(socket(AF_UNIX, SOCK_STREAM, 0))) != -1)) {
516 goto out_bad;
517 }
518 oldmask = umask(077);
519 r = bind(fd, (struct sockaddr *)&sun, sizeof(sun));
520 umask(oldmask);
521 if (r == -1) {
522 if (errno != EROFS)
523 syslog(LOG_ERR, "bind(\"thesocket\"): %m");
524 goto out_bad;
525 }
526
527 if (!assumes(listen(fd, SOMAXCONN) != -1)) {
528 goto out_bad;
529 }
530
531 if (!assumes(kevent_mod(fd, EVFILT_READ, EV_ADD, 0, 0, &kqlisten_callback) != -1)) {
532 goto out_bad;
533 }
534
535 launchd_inited = true;
536
537 sockdir = strdup(ourdir);
538 sockpath = strdup(sun.sun_path);
539
540 launchd_proper_pid = getpid();
541 atexit(launchd_clean_up);
542
543 out_bad:
544 setegid(getgid());
545 seteuid(getuid());
546
547 if (!launchd_inited) {
548 if (fd != -1)
549 assumes(close(fd) != -1);
550 if (ourdirfd != -1)
551 assumes(close(ourdirfd) != -1);
552 }
553 }
554
555 static long long job_get_integer(launch_data_t j, const char *key)
556 {
557 launch_data_t t;
558
559 if (!assumes(j != NULL))
560 return -1;
561
562 if ((t = launch_data_dict_lookup(j, key))) {
563 return launch_data_get_integer(t);
564 } else {
565 return 0;
566 }
567 }
568
569 static const char *job_get_string(launch_data_t j, const char *key)
570 {
571 launch_data_t t;
572
573 if (!assumes(j != NULL))
574 return NULL;
575
576 if ((t = launch_data_dict_lookup(j, key))) {
577 return launch_data_get_string(t);
578 } else {
579 return NULL;
580 }
581 }
582
583 static const char *job_get_file2exec(launch_data_t j)
584 {
585 launch_data_t tmpi, tmp;
586
587 if (!assumes(j != NULL))
588 return NULL;
589
590 if ((tmp = launch_data_dict_lookup(j, LAUNCH_JOBKEY_PROGRAM))) {
591 return launch_data_get_string(tmp);
592 } else {
593 tmp = launch_data_dict_lookup(j, LAUNCH_JOBKEY_PROGRAMARGUMENTS);
594 if (assumes(tmp != NULL)) {
595 tmpi = launch_data_array_get_index(tmp, 0);
596 if (assumes(tmpi != NULL))
597 return launch_data_get_string(tmpi);
598 }
599 return NULL;
600 }
601 }
602
603 static bool job_get_bool(launch_data_t j, const char *key)
604 {
605 launch_data_t t;
606
607 if (!assumes(j != NULL))
608 return false;
609
610 if ((t = launch_data_dict_lookup(j, key))) {
611 return launch_data_get_bool(t);
612 } else {
613 return false;
614 }
615 }
616
617 static void ipc_open(int fd, struct jobcb *j)
618 {
619 struct conncb *c = calloc(1, sizeof(struct conncb));
620
621 if (!assumes(c != NULL))
622 return;
623
624 assumes(fcntl(fd, F_SETFL, O_NONBLOCK) != -1);
625
626 c->kqconn_callback = ipc_callback;
627 c->conn = launchd_fdopen(fd);
628 c->j = j;
629 TAILQ_INSERT_TAIL(&connections, c, tqe);
630 assumes(kevent_mod(fd, EVFILT_READ, EV_ADD, 0, 0, &c->kqconn_callback) != -1);
631 }
632
633 static void simple_zombie_reaper(void *obj __attribute__((unused)), struct kevent *kev)
634 {
635 assumes(waitpid(kev->ident, NULL, 0) != -1);
636 }
637
638 static void listen_callback(void *obj __attribute__((unused)), struct kevent *kev)
639 {
640 struct sockaddr_un sun;
641 socklen_t sl = sizeof(sun);
642 int cfd;
643
644 if (assumes((cfd = _fd(accept(kev->ident, (struct sockaddr *)&sun, &sl))) != -1)) {
645 ipc_open(cfd, NULL);
646 }
647 }
648
649 static void ipc_callback(void *obj, struct kevent *kev)
650 {
651 struct conncb *c = obj;
652 int r;
653
654 if (kev->filter == EVFILT_READ) {
655 if (launchd_msg_recv(c->conn, ipc_readmsg, c) == -1 && errno != EAGAIN) {
656 if (errno != ECONNRESET)
657 syslog(LOG_DEBUG, "%s(): recv: %m", __func__);
658 ipc_close(c);
659 }
660 } else if (kev->filter == EVFILT_WRITE) {
661 r = launchd_msg_send(c->conn, NULL);
662 if (r == -1) {
663 if (errno != EAGAIN) {
664 syslog(LOG_DEBUG, "%s(): send: %m", __func__);
665 ipc_close(c);
666 }
667 } else if (r == 0) {
668 assumes(kevent_mod(launchd_getfd(c->conn), EVFILT_WRITE, EV_DELETE, 0, 0, NULL) != -1);
669 }
670 } else {
671 syslog(LOG_DEBUG, "%s(): unknown filter type!", __func__);
672 ipc_close(c);
673 }
674 }
675
676 static void set_user_env(launch_data_t obj, const char *key, void *context __attribute__((unused)))
677 {
678 setenv(key, launch_data_get_string(obj), 1);
679 }
680
681 static void launch_data_close_fds(launch_data_t o)
682 {
683 size_t i;
684
685 switch (launch_data_get_type(o)) {
686 case LAUNCH_DATA_DICTIONARY:
687 launch_data_dict_iterate(o, (void (*)(launch_data_t, const char *, void *))launch_data_close_fds, NULL);
688 break;
689 case LAUNCH_DATA_ARRAY:
690 for (i = 0; i < launch_data_array_get_count(o); i++)
691 launch_data_close_fds(launch_data_array_get_index(o, i));
692 break;
693 case LAUNCH_DATA_FD:
694 if (launch_data_get_fd(o) != -1)
695 assumes(close(launch_data_get_fd(o)) != -1);
696 break;
697 default:
698 break;
699 }
700 }
701
702 static void launch_data_revoke_fds(launch_data_t o)
703 {
704 size_t i;
705
706 switch (launch_data_get_type(o)) {
707 case LAUNCH_DATA_DICTIONARY:
708 launch_data_dict_iterate(o, (void (*)(launch_data_t, const char *, void *))launch_data_revoke_fds, NULL);
709 break;
710 case LAUNCH_DATA_ARRAY:
711 for (i = 0; i < launch_data_array_get_count(o); i++)
712 launch_data_revoke_fds(launch_data_array_get_index(o, i));
713 break;
714 case LAUNCH_DATA_FD:
715 launch_data_set_fd(o, -1);
716 break;
717 default:
718 break;
719 }
720 }
721
722 static void job_ignore_fds(launch_data_t o, const char *key __attribute__((unused)), void *cookie)
723 {
724 struct jobcb *j = cookie;
725 size_t i;
726 int fd;
727
728 switch (launch_data_get_type(o)) {
729 case LAUNCH_DATA_DICTIONARY:
730 launch_data_dict_iterate(o, job_ignore_fds, cookie);
731 break;
732 case LAUNCH_DATA_ARRAY:
733 for (i = 0; i < launch_data_array_get_count(o); i++)
734 job_ignore_fds(launch_data_array_get_index(o, i), NULL, cookie);
735 break;
736 case LAUNCH_DATA_FD:
737 fd = launch_data_get_fd(o);
738 if (-1 != fd) {
739 job_log(j, LOG_DEBUG, "Ignoring FD: %d", fd);
740 assumes(kevent_mod(fd, EVFILT_READ, EV_DELETE, 0, 0, NULL) != -1);
741 }
742 break;
743 default:
744 break;
745 }
746 }
747
748 static void job_ignore(struct jobcb *j)
749 {
750 launch_data_t j_sockets = launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_SOCKETS);
751 size_t i;
752
753 if (j_sockets)
754 job_ignore_fds(j_sockets, NULL, j);
755
756 for (i = 0; i < j->vnodes_cnt; i++) {
757 assumes(kevent_mod(j->vnodes[i], EVFILT_VNODE, EV_DELETE, 0, 0, NULL) != -1);
758 }
759 for (i = 0; i < j->qdirs_cnt; i++) {
760 assumes(kevent_mod(j->qdirs[i], EVFILT_VNODE, EV_DELETE, 0, 0, NULL) != -1);
761 }
762 }
763
764 static void job_watch_fds(launch_data_t o, const char *key __attribute__((unused)), void *cookie)
765 {
766 struct jobcb *j = cookie;
767 size_t i;
768 int fd;
769
770 switch (launch_data_get_type(o)) {
771 case LAUNCH_DATA_DICTIONARY:
772 launch_data_dict_iterate(o, job_watch_fds, cookie);
773 break;
774 case LAUNCH_DATA_ARRAY:
775 for (i = 0; i < launch_data_array_get_count(o); i++)
776 job_watch_fds(launch_data_array_get_index(o, i), NULL, cookie);
777 break;
778 case LAUNCH_DATA_FD:
779 fd = launch_data_get_fd(o);
780 if (-1 != fd) {
781 job_log(j, LOG_DEBUG, "Watching FD: %d", fd);
782 assumes(kevent_mod(fd, EVFILT_READ, EV_ADD, 0, 0, cookie) != -1);
783 }
784 break;
785 default:
786 break;
787 }
788 }
789
790 static void job_watch(struct jobcb *j)
791 {
792 launch_data_t ld_qdirs = launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_QUEUEDIRECTORIES);
793 launch_data_t ld_vnodes = launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_WATCHPATHS);
794 launch_data_t j_sockets = launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_SOCKETS);
795 size_t i;
796
797 if (j_sockets)
798 job_watch_fds(j_sockets, NULL, &j->kqjob_callback);
799
800 for (i = 0; i < j->vnodes_cnt; i++) {
801 if (-1 == j->vnodes[i]) {
802 launch_data_t ld_idx = launch_data_array_get_index(ld_vnodes, i);
803 const char *thepath = launch_data_get_string(ld_idx);
804
805 if (-1 == (j->vnodes[i] = _fd(open(thepath, O_EVTONLY))))
806 job_log_error(j, LOG_ERR, "open(\"%s\", O_EVTONLY)", thepath);
807 }
808 assumes(kevent_mod(j->vnodes[i], EVFILT_VNODE, EV_ADD|EV_CLEAR,
809 NOTE_WRITE|NOTE_EXTEND|NOTE_DELETE|NOTE_RENAME|NOTE_REVOKE|NOTE_ATTRIB|NOTE_LINK,
810 0, &j->kqjob_callback) != -1);
811 }
812
813 for (i = 0; i < j->qdirs_cnt; i++) {
814 assumes(kevent_mod(j->qdirs[i], EVFILT_VNODE, EV_ADD|EV_CLEAR,
815 NOTE_WRITE|NOTE_EXTEND|NOTE_ATTRIB|NOTE_LINK, 0, &j->kqjob_callback) != -1);
816 }
817
818 for (i = 0; i < j->qdirs_cnt; i++) {
819 launch_data_t ld_idx = launch_data_array_get_index(ld_qdirs, i);
820 const char *thepath = launch_data_get_string(ld_idx);
821 int dcc_r;
822
823 if (-1 == (dcc_r = dir_has_files(thepath))) {
824 job_log_error(j, LOG_ERR, "dir_has_files(\"%s\", ...)", thepath);
825 } else if (dcc_r > 0 && !shutdown_in_progress) {
826 job_start(j);
827 break;
828 }
829 }
830 }
831
832 static void job_stop(struct jobcb *j)
833 {
834 if (j->p)
835 assumes(kill(j->p, SIGTERM) != -1);
836 }
837
838 static void job_remove(struct jobcb *j)
839 {
840 launch_data_t tmp;
841 size_t i;
842
843 job_log(j, LOG_DEBUG, "Removed");
844
845 TAILQ_REMOVE(&jobs, j, tqe);
846 if (j->p) {
847 if (kevent_mod(j->p, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, &kqsimple_zombie_reaper) == -1) {
848 job_reap(j);
849 } else {
850 job_stop(j);
851 }
852 }
853 if ((tmp = launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_USERENVIRONMENTVARIABLES)))
854 launch_data_dict_iterate(tmp, unsetup_job_env, NULL);
855 launch_data_close_fds(j->ldj);
856 launch_data_free(j->ldj);
857 if (j->execfd)
858 close(j->execfd);
859 for (i = 0; i < j->vnodes_cnt; i++)
860 if (-1 != j->vnodes[i])
861 close(j->vnodes[i]);
862 if (j->vnodes)
863 free(j->vnodes);
864 for (i = 0; i < j->qdirs_cnt; i++)
865 if (-1 != j->qdirs[i])
866 close(j->qdirs[i]);
867 if (j->qdirs)
868 free(j->qdirs);
869 if (j->start_interval)
870 assumes(kevent_mod((uintptr_t)&j->start_interval, EVFILT_TIMER, EV_DELETE, 0, 0, NULL) != -1);
871 if (j->start_cal_interval) {
872 assumes(kevent_mod((uintptr_t)j->start_cal_interval, EVFILT_TIMER, EV_DELETE, 0, 0, NULL) != -1);
873 free(j->start_cal_interval);
874 }
875 kevent_mod((uintptr_t)j, EVFILT_TIMER, EV_DELETE, 0, 0, NULL);
876 free(j);
877 }
878
879 struct readmsg_context {
880 struct conncb *c;
881 launch_data_t resp;
882 };
883
884 static void ipc_readmsg(launch_data_t msg, void *context)
885 {
886 struct readmsg_context rmc = { context, NULL };
887
888 if (LAUNCH_DATA_DICTIONARY == launch_data_get_type(msg)) {
889 launch_data_dict_iterate(msg, ipc_readmsg2, &rmc);
890 } else if (LAUNCH_DATA_STRING == launch_data_get_type(msg)) {
891 ipc_readmsg2(NULL, launch_data_get_string(msg), &rmc);
892 } else {
893 rmc.resp = launch_data_new_errno(EINVAL);
894 }
895
896 if (NULL == rmc.resp)
897 rmc.resp = launch_data_new_errno(ENOSYS);
898
899 launch_data_close_fds(msg);
900
901 if (launchd_msg_send(rmc.c->conn, rmc.resp) == -1) {
902 if (errno == EAGAIN) {
903 assumes(kevent_mod(launchd_getfd(rmc.c->conn), EVFILT_WRITE, EV_ADD, 0, 0, &rmc.c->kqconn_callback) != -1);
904 } else {
905 syslog(LOG_DEBUG, "launchd_msg_send() == -1: %m");
906 ipc_close(rmc.c);
907 }
908 }
909 launch_data_free(rmc.resp);
910 }
911
912 static void
913 attach_bonjourfds_to_job(launch_data_t o, const char *key, void *context __attribute__((unused)))
914 {
915 struct jobcb *j = NULL;
916
917 TAILQ_FOREACH(j, &jobs, tqe) {
918 if (strcmp(j->label, key) == 0)
919 break;
920 }
921
922 if (j == NULL)
923 return;
924
925 launch_data_dict_insert(j->ldj, launch_data_copy(o), LAUNCH_JOBKEY_BONJOURFDS);
926 launch_data_revoke_fds(o);
927 }
928
929 static void ipc_readmsg2(launch_data_t data, const char *cmd, void *context)
930 {
931 struct readmsg_context *rmc = context;
932 launch_data_t resp = NULL;
933 struct jobcb *j;
934
935 if (rmc->resp)
936 return;
937
938 if (!strcmp(cmd, LAUNCH_KEY_STARTJOB)) {
939 TAILQ_FOREACH(j, &jobs, tqe) {
940 if (!strcmp(j->label, launch_data_get_string(data))) {
941 job_start(j);
942 resp = launch_data_new_errno(0);
943 }
944 }
945 if (NULL == resp)
946 resp = launch_data_new_errno(ESRCH);
947 } else if (!strcmp(cmd, LAUNCH_KEY_STOPJOB)) {
948 TAILQ_FOREACH(j, &jobs, tqe) {
949 if (!strcmp(j->label, launch_data_get_string(data))) {
950 job_stop(j);
951 resp = launch_data_new_errno(0);
952 }
953 }
954 if (NULL == resp)
955 resp = launch_data_new_errno(ESRCH);
956 } else if (!strcmp(cmd, LAUNCH_KEY_REMOVEJOB)) {
957 TAILQ_FOREACH(j, &jobs, tqe) {
958 if (!strcmp(j->label, launch_data_get_string(data))) {
959 job_remove(j);
960 resp = launch_data_new_errno(0);
961 }
962 }
963 if (NULL == resp)
964 resp = launch_data_new_errno(ESRCH);
965 } else if (!strcmp(cmd, LAUNCH_KEY_SUBMITJOB)) {
966 if (launch_data_get_type(data) == LAUNCH_DATA_ARRAY) {
967 launch_data_t tmp;
968 size_t i;
969
970 resp = launch_data_alloc(LAUNCH_DATA_ARRAY);
971 for (i = 0; i < launch_data_array_get_count(data); i++) {
972 tmp = load_job(launch_data_array_get_index(data, i));
973 launch_data_array_set_index(resp, tmp, i);
974 }
975 } else {
976 resp = load_job(data);
977 }
978 } else if (!strcmp(cmd, LAUNCH_KEY_WORKAROUNDBONJOUR)) {
979 launch_data_dict_iterate(data, attach_bonjourfds_to_job, NULL);
980 resp = launch_data_new_errno(0);
981 } else if (!strcmp(cmd, LAUNCH_KEY_UNSETUSERENVIRONMENT)) {
982 unsetenv(launch_data_get_string(data));
983 resp = launch_data_new_errno(0);
984 } else if (!strcmp(cmd, LAUNCH_KEY_GETUSERENVIRONMENT)) {
985 char **tmpenviron = environ;
986 resp = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
987 for (; *tmpenviron; tmpenviron++) {
988 char envkey[1024];
989 launch_data_t s = launch_data_alloc(LAUNCH_DATA_STRING);
990 launch_data_set_string(s, strchr(*tmpenviron, '=') + 1);
991 strncpy(envkey, *tmpenviron, sizeof(envkey));
992 *(strchr(envkey, '=')) = '\0';
993 launch_data_dict_insert(resp, s, envkey);
994 }
995 } else if (!strcmp(cmd, LAUNCH_KEY_SETUSERENVIRONMENT)) {
996 launch_data_dict_iterate(data, set_user_env, NULL);
997 resp = launch_data_new_errno(0);
998 } else if (!strcmp(cmd, LAUNCH_KEY_CHECKIN)) {
999 if (rmc->c->j) {
1000 if (assumes((resp = launch_data_copy(rmc->c->j->ldj)) != NULL)) {
1001 if (NULL == launch_data_dict_lookup(resp, LAUNCH_JOBKEY_TIMEOUT)) {
1002 launch_data_t to = launch_data_new_integer(LAUNCHD_MIN_JOB_RUN_TIME);
1003 launch_data_dict_insert(resp, to, LAUNCH_JOBKEY_TIMEOUT);
1004 }
1005 }
1006 rmc->c->j->checkedin = true;
1007 } else {
1008 resp = launch_data_new_errno(EACCES);
1009 }
1010 } else if (!strcmp(cmd, LAUNCH_KEY_RELOADTTYS)) {
1011 update_ttys();
1012 resp = launch_data_new_errno(0);
1013 } else if (!strcmp(cmd, LAUNCH_KEY_SHUTDOWN)) {
1014 do_shutdown();
1015 resp = launch_data_new_errno(0);
1016 } else if (!strcmp(cmd, LAUNCH_KEY_GETJOBS)) {
1017 resp = get_jobs(NULL);
1018 launch_data_revoke_fds(resp);
1019 } else if (!strcmp(cmd, LAUNCH_KEY_GETRESOURCELIMITS)) {
1020 resp = adjust_rlimits(NULL);
1021 } else if (!strcmp(cmd, LAUNCH_KEY_SETRESOURCELIMITS)) {
1022 resp = adjust_rlimits(data);
1023 } else if (!strcmp(cmd, LAUNCH_KEY_GETJOB)) {
1024 resp = get_jobs(launch_data_get_string(data));
1025 launch_data_revoke_fds(resp);
1026 } else if (!strcmp(cmd, LAUNCH_KEY_GETJOBWITHHANDLES)) {
1027 resp = get_jobs(launch_data_get_string(data));
1028 } else if (!strcmp(cmd, LAUNCH_KEY_SETLOGMASK)) {
1029 resp = launch_data_new_integer(setlogmask(launch_data_get_integer(data)));
1030 } else if (!strcmp(cmd, LAUNCH_KEY_GETLOGMASK)) {
1031 int oldmask = setlogmask(LOG_UPTO(LOG_DEBUG));
1032 resp = launch_data_new_integer(oldmask);
1033 setlogmask(oldmask);
1034 } else if (!strcmp(cmd, LAUNCH_KEY_SETUMASK)) {
1035 resp = launch_data_new_integer(umask(launch_data_get_integer(data)));
1036 } else if (!strcmp(cmd, LAUNCH_KEY_GETUMASK)) {
1037 mode_t oldmask = umask(0);
1038 resp = launch_data_new_integer(oldmask);
1039 umask(oldmask);
1040 } else if (!strcmp(cmd, LAUNCH_KEY_GETRUSAGESELF)) {
1041 struct rusage rusage;
1042 assumes(getrusage(RUSAGE_SELF, &rusage) != -1);
1043 resp = launch_data_new_opaque(&rusage, sizeof(rusage));
1044 } else if (!strcmp(cmd, LAUNCH_KEY_GETRUSAGECHILDREN)) {
1045 struct rusage rusage;
1046 assumes(getrusage(RUSAGE_CHILDREN, &rusage) != -1);
1047 resp = launch_data_new_opaque(&rusage, sizeof(rusage));
1048 } else if (!strcmp(cmd, LAUNCH_KEY_SETSTDOUT)) {
1049 resp = setstdio(STDOUT_FILENO, data);
1050 } else if (!strcmp(cmd, LAUNCH_KEY_SETSTDERR)) {
1051 resp = setstdio(STDERR_FILENO, data);
1052 } else if (!strcmp(cmd, LAUNCH_KEY_BATCHCONTROL)) {
1053 batch_job_enable(launch_data_get_bool(data), rmc->c);
1054 resp = launch_data_new_errno(0);
1055 } else if (!strcmp(cmd, LAUNCH_KEY_BATCHQUERY)) {
1056 resp = launch_data_alloc(LAUNCH_DATA_BOOL);
1057 launch_data_set_bool(resp, batch_disabler_count == 0);
1058 }
1059
1060 rmc->resp = resp;
1061 }
1062
1063 static launch_data_t setstdio(int d, launch_data_t o)
1064 {
1065 launch_data_t resp = launch_data_new_errno(0);
1066
1067 if (launch_data_get_type(o) == LAUNCH_DATA_STRING) {
1068 char **where = &pending_stderr;
1069 if (d == STDOUT_FILENO)
1070 where = &pending_stdout;
1071 if (*where)
1072 free(*where);
1073 *where = strdup(launch_data_get_string(o));
1074 } else if (launch_data_get_type(o) == LAUNCH_DATA_FD) {
1075 assumes(dup2(launch_data_get_fd(o), d) != -1);
1076 } else {
1077 launch_data_set_errno(resp, EINVAL);
1078 }
1079
1080 return resp;
1081 }
1082
1083 static void batch_job_enable(bool e, struct conncb *c)
1084 {
1085 if (e && c->disabled_batch) {
1086 batch_disabler_count--;
1087 c->disabled_batch = 0;
1088 if (batch_disabler_count == 0)
1089 assumes(kevent_mod(asynckq, EVFILT_READ, EV_ENABLE, 0, 0, &kqasync_callback) != -1);
1090 } else if (!e && !c->disabled_batch) {
1091 if (batch_disabler_count == 0)
1092 assumes(kevent_mod(asynckq, EVFILT_READ, EV_DISABLE, 0, 0, &kqasync_callback) != -1);
1093 batch_disabler_count++;
1094 c->disabled_batch = 1;
1095 }
1096 }
1097
1098 static launch_data_t load_job(launch_data_t pload)
1099 {
1100 launch_data_t tmp, resp;
1101 const char *label;
1102 struct jobcb *j;
1103 bool startnow, hasprog = false, hasprogargs = false;
1104
1105 if ((label = job_get_string(pload, LAUNCH_JOBKEY_LABEL))) {
1106 TAILQ_FOREACH(j, &jobs, tqe) {
1107 if (!strcmp(j->label, label)) {
1108 resp = launch_data_new_errno(EEXIST);
1109 goto out;
1110 }
1111 }
1112 } else {
1113 resp = launch_data_new_errno(EINVAL);
1114 goto out;
1115 }
1116
1117 if (launch_data_dict_lookup(pload, LAUNCH_JOBKEY_PROGRAM))
1118 hasprog = true;
1119 if (launch_data_dict_lookup(pload, LAUNCH_JOBKEY_PROGRAMARGUMENTS))
1120 hasprogargs = true;
1121
1122 if (!hasprog && !hasprogargs) {
1123 resp = launch_data_new_errno(EINVAL);
1124 goto out;
1125 }
1126
1127 j = calloc(1, sizeof(struct jobcb) + strlen(label) + 1);
1128 strcpy(j->label, label);
1129 j->ldj = launch_data_copy(pload);
1130 launch_data_revoke_fds(pload);
1131 j->kqjob_callback = job_callback;
1132
1133
1134 if (launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_ONDEMAND) == NULL) {
1135 tmp = launch_data_alloc(LAUNCH_DATA_BOOL);
1136 launch_data_set_bool(tmp, true);
1137 launch_data_dict_insert(j->ldj, tmp, LAUNCH_JOBKEY_ONDEMAND);
1138 }
1139
1140 TAILQ_INSERT_TAIL(&jobs, j, tqe);
1141
1142 j->debug = job_get_bool(j->ldj, LAUNCH_JOBKEY_DEBUG);
1143
1144 startnow = !job_get_bool(j->ldj, LAUNCH_JOBKEY_ONDEMAND);
1145
1146 if (job_get_bool(j->ldj, LAUNCH_JOBKEY_RUNATLOAD))
1147 startnow = true;
1148
1149 if ((tmp = launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_QUEUEDIRECTORIES))) {
1150 size_t i;
1151
1152 j->qdirs_cnt = launch_data_array_get_count(tmp);
1153 j->qdirs = malloc(sizeof(int) * j->qdirs_cnt);
1154
1155 for (i = 0; i < j->qdirs_cnt; i++) {
1156 const char *thepath = launch_data_get_string(launch_data_array_get_index(tmp, i));
1157
1158 if (-1 == (j->qdirs[i] = _fd(open(thepath, O_EVTONLY))))
1159 job_log_error(j, LOG_ERR, "open(\"%s\", O_EVTONLY)", thepath);
1160 }
1161
1162 }
1163
1164 if ((tmp = launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_STARTINTERVAL))) {
1165 j->start_interval = launch_data_get_integer(tmp);
1166
1167 if (j->start_interval == 0) {
1168 job_log(j, LOG_WARNING, "StartInterval is zero, ignoring");
1169 } else {
1170 assumes(kevent_mod((uintptr_t)&j->start_interval, EVFILT_TIMER, EV_ADD, NOTE_SECONDS, j->start_interval, j) != -1);
1171 }
1172 }
1173
1174 if ((tmp = launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_STARTCALENDARINTERVAL))) {
1175 launch_data_t tmp_k;
1176
1177 j->start_cal_interval = calloc(1, sizeof(struct tm));
1178 j->start_cal_interval->tm_min = -1;
1179 j->start_cal_interval->tm_hour = -1;
1180 j->start_cal_interval->tm_mday = -1;
1181 j->start_cal_interval->tm_wday = -1;
1182 j->start_cal_interval->tm_mon = -1;
1183
1184 if (LAUNCH_DATA_DICTIONARY == launch_data_get_type(tmp)) {
1185 if ((tmp_k = launch_data_dict_lookup(tmp, LAUNCH_JOBKEY_CAL_MINUTE)))
1186 j->start_cal_interval->tm_min = launch_data_get_integer(tmp_k);
1187 if ((tmp_k = launch_data_dict_lookup(tmp, LAUNCH_JOBKEY_CAL_HOUR)))
1188 j->start_cal_interval->tm_hour = launch_data_get_integer(tmp_k);
1189 if ((tmp_k = launch_data_dict_lookup(tmp, LAUNCH_JOBKEY_CAL_DAY)))
1190 j->start_cal_interval->tm_mday = launch_data_get_integer(tmp_k);
1191 if ((tmp_k = launch_data_dict_lookup(tmp, LAUNCH_JOBKEY_CAL_WEEKDAY)))
1192 j->start_cal_interval->tm_wday = launch_data_get_integer(tmp_k);
1193 if ((tmp_k = launch_data_dict_lookup(tmp, LAUNCH_JOBKEY_CAL_MONTH)))
1194 j->start_cal_interval->tm_mon = launch_data_get_integer(tmp_k);
1195 }
1196
1197 job_set_alarm(j);
1198 }
1199
1200 if ((tmp = launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_WATCHPATHS))) {
1201 size_t i;
1202
1203 j->vnodes_cnt = launch_data_array_get_count(tmp);
1204 j->vnodes = malloc(sizeof(int) * j->vnodes_cnt);
1205
1206 for (i = 0; i < j->vnodes_cnt; i++) {
1207 const char *thepath = launch_data_get_string(launch_data_array_get_index(tmp, i));
1208
1209 assumes((j->vnodes[i] = _fd(open(thepath, O_EVTONLY))) != -1);
1210 }
1211
1212 }
1213
1214 if ((tmp = launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_USERENVIRONMENTVARIABLES)))
1215 launch_data_dict_iterate(tmp, setup_job_env, NULL);
1216
1217 if (job_get_bool(j->ldj, LAUNCH_JOBKEY_ONDEMAND))
1218 job_watch(j);
1219
1220 if (startnow)
1221 job_start(j);
1222
1223 resp = launch_data_new_errno(0);
1224 out:
1225 return resp;
1226 }
1227
1228 static launch_data_t get_jobs(const char *which)
1229 {
1230 struct jobcb *j;
1231 launch_data_t tmp, resp = NULL;
1232
1233 if (which) {
1234 TAILQ_FOREACH(j, &jobs, tqe) {
1235 if (!strcmp(which, j->label))
1236 resp = launch_data_copy(j->ldj);
1237 }
1238 if (resp == NULL)
1239 resp = launch_data_new_errno(ESRCH);
1240 } else {
1241 resp = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
1242
1243 TAILQ_FOREACH(j, &jobs, tqe) {
1244 tmp = launch_data_copy(j->ldj);
1245 launch_data_dict_insert(resp, tmp, j->label);
1246 }
1247 }
1248
1249 return resp;
1250 }
1251
1252 static void usage(FILE *where)
1253 {
1254 fprintf(where, "%s: [-d] [-- command [args ...]]\n", getprogname());
1255 fprintf(where, "\t-d\tdaemonize\n");
1256 fprintf(where, "\t-h\tthis usage statement\n");
1257
1258 if (where == stdout)
1259 exit(EXIT_SUCCESS);
1260 }
1261
1262 int kevent_mod(uintptr_t ident, short filter, u_short flags, u_int fflags, intptr_t data, void *udata)
1263 {
1264 struct kevent kev;
1265 int q = mainkq;
1266
1267 if (EVFILT_TIMER == filter || EVFILT_VNODE == filter)
1268 q = asynckq;
1269
1270 if (flags & EV_ADD && !assumes(udata != NULL)) {
1271 errno = EINVAL;
1272 return -1;
1273 }
1274
1275 #ifdef PID1_REAP_ADOPTED_CHILDREN
1276 if (filter == EVFILT_PROC && getpid() == 1)
1277 return 0;
1278 #endif
1279 EV_SET(&kev, ident, filter, flags, fflags, data, udata);
1280 return kevent(q, &kev, 1, NULL, 0, NULL);
1281 }
1282
1283 static int _fd(int fd)
1284 {
1285 if (fd >= 0)
1286 assumes(fcntl(fd, F_SETFD, 1) != -1);
1287 return fd;
1288 }
1289
1290 static void ipc_close(struct conncb *c)
1291 {
1292 batch_job_enable(true, c);
1293
1294 TAILQ_REMOVE(&connections, c, tqe);
1295 launchd_close(c->conn);
1296 free(c);
1297 }
1298
1299 static void setup_job_env(launch_data_t obj, const char *key, void *context __attribute__((unused)))
1300 {
1301 if (LAUNCH_DATA_STRING == launch_data_get_type(obj))
1302 setenv(key, launch_data_get_string(obj), 1);
1303 }
1304
1305 static void unsetup_job_env(launch_data_t obj, const char *key, void *context __attribute__((unused)))
1306 {
1307 if (LAUNCH_DATA_STRING == launch_data_get_type(obj))
1308 unsetenv(key);
1309 }
1310
1311 static void job_reap(struct jobcb *j)
1312 {
1313 bool od = job_get_bool(j->ldj, LAUNCH_JOBKEY_ONDEMAND);
1314 time_t td = time(NULL) - j->start_time;
1315 bool bad_exit = false;
1316 int status;
1317
1318 job_log(j, LOG_DEBUG, "Reaping");
1319
1320 if (j->execfd) {
1321 assumes(close(j->execfd) != -1);
1322 j->execfd = 0;
1323 }
1324
1325 #ifdef PID1_REAP_ADOPTED_CHILDREN
1326 if (getpid() == 1)
1327 status = pid1_child_exit_status;
1328 else
1329 #endif
1330 if (!assumes(waitpid(j->p, &status, 0) != -1)) {
1331 return;
1332 }
1333
1334 if (WIFEXITED(status) && WEXITSTATUS(status) != 0) {
1335 job_log(j, LOG_WARNING, "exited with exit code: %d", WEXITSTATUS(status));
1336 bad_exit = true;
1337 }
1338
1339 if (WIFSIGNALED(status)) {
1340 int s = WTERMSIG(status);
1341 if (SIGKILL == s || SIGTERM == s) {
1342 job_log(j, LOG_NOTICE, "exited: %s", strsignal(s));
1343 } else {
1344 job_log(j, LOG_WARNING, "exited abnormally: %s", strsignal(s));
1345 bad_exit = true;
1346 }
1347 }
1348
1349 if (!od) {
1350 if (td < LAUNCHD_MIN_JOB_RUN_TIME) {
1351 job_log(j, LOG_WARNING, "respawning too quickly! throttling");
1352 bad_exit = true;
1353 j->throttle = true;
1354 } else if (td >= LAUNCHD_REWARD_JOB_RUN_TIME) {
1355 job_log(j, LOG_INFO, "lived long enough, forgiving past exit failures");
1356 j->failed_exits = 0;
1357 }
1358 }
1359
1360 if (bad_exit)
1361 j->failed_exits++;
1362
1363 if (j->failed_exits > 0) {
1364 int failures_left = LAUNCHD_FAILED_EXITS_THRESHOLD - j->failed_exits;
1365 if (failures_left)
1366 job_log(j, LOG_WARNING, "%d more failure%s without living at least %d seconds will cause job removal",
1367 failures_left, failures_left > 1 ? "s" : "", LAUNCHD_REWARD_JOB_RUN_TIME);
1368 }
1369
1370 total_children--;
1371 j->p = 0;
1372 }
1373
1374 static bool job_restart_fitness_test(struct jobcb *j)
1375 {
1376 bool od = job_get_bool(j->ldj, LAUNCH_JOBKEY_ONDEMAND);
1377
1378 if (j->firstborn) {
1379 job_log(j, LOG_DEBUG, "first born died, begin shutdown");
1380 do_shutdown();
1381 return false;
1382 } else if (job_get_bool(j->ldj, LAUNCH_JOBKEY_SERVICEIPC) && !j->checkedin) {
1383 job_log(j, LOG_WARNING, "failed to checkin");
1384 job_remove(j);
1385 return false;
1386 } else if (j->failed_exits >= LAUNCHD_FAILED_EXITS_THRESHOLD) {
1387 job_log(j, LOG_WARNING, "too many failures in succession");
1388 job_remove(j);
1389 return false;
1390 } else if (od || shutdown_in_progress) {
1391 if (!od && shutdown_in_progress)
1392 job_log(j, LOG_NOTICE, "exited while shutdown is in progress, will not restart unless demand requires it");
1393 job_watch(j);
1394 return false;
1395 }
1396
1397 return true;
1398 }
1399
1400 static void job_callback(void *obj, struct kevent *kev)
1401 {
1402 struct jobcb *j = obj;
1403 bool d = j->debug;
1404 bool startnow = true;
1405 int oldmask = 0;
1406
1407 if (d) {
1408 oldmask = setlogmask(LOG_UPTO(LOG_DEBUG));
1409 job_log(j, LOG_DEBUG, "log level debug temporarily enabled while processing job");
1410 }
1411
1412 if (kev->filter == EVFILT_PROC) {
1413 job_reap(j);
1414
1415 startnow = job_restart_fitness_test(j);
1416
1417 if (startnow && j->throttle) {
1418 j->throttle = false;
1419 job_log(j, LOG_WARNING, "will restart in %d seconds", LAUNCHD_MIN_JOB_RUN_TIME);
1420 if (assumes(kevent_mod((uintptr_t)j, EVFILT_TIMER, EV_ADD|EV_ONESHOT,
1421 NOTE_SECONDS, LAUNCHD_MIN_JOB_RUN_TIME, &j->kqjob_callback) != -1)) {
1422 startnow = false;
1423 }
1424 }
1425 } else if (kev->filter == EVFILT_TIMER && (void *)kev->ident == j->start_cal_interval) {
1426 job_set_alarm(j);
1427 } else if (kev->filter == EVFILT_VNODE) {
1428 size_t i;
1429 const char *thepath = NULL;
1430
1431 for (i = 0; i < j->vnodes_cnt; i++) {
1432 if (j->vnodes[i] == (int)kev->ident) {
1433 launch_data_t ld_vnodes = launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_WATCHPATHS);
1434
1435 thepath = launch_data_get_string(launch_data_array_get_index(ld_vnodes, i));
1436
1437 job_log(j, LOG_DEBUG, "watch path modified: %s", thepath);
1438
1439 if ((NOTE_DELETE|NOTE_RENAME|NOTE_REVOKE) & kev->fflags) {
1440 job_log(j, LOG_DEBUG, "watch path invalidated: %s", thepath);
1441 assumes(close(j->vnodes[i]) != -1);
1442 j->vnodes[i] = -1; /* this will get fixed in job_watch() */
1443 }
1444 }
1445 }
1446
1447 for (i = 0; i < j->qdirs_cnt; i++) {
1448 if (j->qdirs[i] == (int)kev->ident) {
1449 launch_data_t ld_qdirs = launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_QUEUEDIRECTORIES);
1450 int dcc_r;
1451
1452 thepath = launch_data_get_string(launch_data_array_get_index(ld_qdirs, i));
1453
1454 job_log(j, LOG_DEBUG, "queue directory modified: %s", thepath);
1455
1456 if (-1 == (dcc_r = dir_has_files(thepath))) {
1457 job_log_error(j, LOG_ERR, "dir_has_files(\"%s\", ...)", thepath);
1458 } else if (0 == dcc_r) {
1459 job_log(j, LOG_DEBUG, "spurious wake up, directory empty: %s", thepath);
1460 startnow = false;
1461 }
1462 }
1463 }
1464 /* if we get here, then the vnodes either wasn't a qdir, or if it was, it has entries in it */
1465 } else if (kev->filter == EVFILT_READ && (int)kev->ident == j->execfd) {
1466 if (kev->data > 0) {
1467 int e;
1468
1469 assumes(read(j->execfd, &e, sizeof(e)) != -1);
1470 errno = e;
1471 job_log_error(j, LOG_ERR, "execve()");
1472 job_remove(j);
1473 j = NULL;
1474 startnow = false;
1475 } else {
1476 assumes(close(j->execfd) != -1);
1477 j->execfd = 0;
1478 }
1479 startnow = false;
1480 }
1481
1482 if (startnow)
1483 job_start(j);
1484
1485 if (d) {
1486 /* the job might have been removed, must not call job_log() */
1487 syslog(LOG_DEBUG, "restoring original log mask");
1488 setlogmask(oldmask);
1489 }
1490 }
1491
1492 static void job_start(struct jobcb *j)
1493 {
1494 int spair[2];
1495 int execspair[2];
1496 bool sipc;
1497 char nbuf[64];
1498 pid_t c;
1499
1500 job_log(j, LOG_DEBUG, "Starting");
1501
1502 if (j->p) {
1503 job_log(j, LOG_DEBUG, "already running");
1504 return;
1505 }
1506
1507 j->checkedin = false;
1508
1509 sipc = job_get_bool(j->ldj, LAUNCH_JOBKEY_SERVICEIPC);
1510
1511 if (launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_INETDCOMPATIBILITY))
1512 sipc = true;
1513
1514 if (sipc)
1515 assumes(socketpair(AF_UNIX, SOCK_STREAM, 0, spair) != -1);
1516
1517 assumes(socketpair(AF_UNIX, SOCK_STREAM, 0, execspair) != -1);
1518
1519 time(&j->start_time);
1520
1521 switch (c = fork_with_bootstrap_port(launchd_bootstrap_port)) {
1522 case -1:
1523 job_log_error(j, LOG_ERR, "fork() failed, will try again in one second");
1524 assumes(close(execspair[0]) != -1);
1525 assumes(close(execspair[1]) != -1);;
1526 if (sipc) {
1527 assumes(close(spair[0]) != -1);
1528 assumes(close(spair[1]) != -1);
1529 }
1530 if (job_get_bool(j->ldj, LAUNCH_JOBKEY_ONDEMAND))
1531 job_ignore(j);
1532 break;
1533 case 0:
1534 assumes(close(execspair[0]) != -1);
1535 /* wait for our parent to say they've attached a kevent to us */
1536 assumes(read(_fd(execspair[1]), &c, sizeof(c)) != -1);
1537 if (j->firstborn) {
1538 setpgid(getpid(), getpid());
1539 if (isatty(STDIN_FILENO)) {
1540 if (tcsetpgrp(STDIN_FILENO, getpid()) == -1)
1541 job_log_error(j, LOG_WARNING, "tcsetpgrp()");
1542 }
1543 }
1544
1545 if (sipc) {
1546 assumes(close(spair[0]) != -1);
1547 sprintf(nbuf, "%d", spair[1]);
1548 setenv(LAUNCHD_TRUSTED_FD_ENV, nbuf, 1);
1549 }
1550 job_start_child(j, execspair[1]);
1551 break;
1552 default:
1553 assumes(close(execspair[1]) != -1);
1554 j->execfd = _fd(execspair[0]);
1555 if (sipc) {
1556 assumes(close(spair[1]) != -1);
1557 ipc_open(_fd(spair[0]), j);
1558 }
1559 assumes(kevent_mod(j->execfd, EVFILT_READ, EV_ADD, 0, 0, j) != -1);
1560
1561 if (assumes(kevent_mod(c, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, j) != -1)) {
1562 j->p = c;
1563 total_children++;
1564 if (job_get_bool(j->ldj, LAUNCH_JOBKEY_ONDEMAND))
1565 job_ignore(j);
1566 } else {
1567 job_reap(j);
1568 }
1569 /* this unblocks the child and avoids a race
1570 * between the above fork() and the kevent_mod() */
1571 assumes(write(j->execfd, &c, sizeof(c)) != -1);
1572 break;
1573 }
1574 }
1575
1576 static void job_start_child(struct jobcb *j, int execfd)
1577 {
1578 launch_data_t ldpa = launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_PROGRAMARGUMENTS);
1579 bool inetcompat = launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_INETDCOMPATIBILITY) ? true : false;
1580 size_t i, argv_cnt;
1581 const char **argv, *file2exec = "/usr/libexec/launchproxy";
1582 int r;
1583 bool hasprog = false;
1584
1585 job_setup_attributes(j);
1586
1587 if (ldpa) {
1588 argv_cnt = launch_data_array_get_count(ldpa);
1589 argv = alloca((argv_cnt + 2) * sizeof(char *));
1590 for (i = 0; i < argv_cnt; i++)
1591 argv[i + 1] = launch_data_get_string(launch_data_array_get_index(ldpa, i));
1592 argv[argv_cnt + 1] = NULL;
1593 } else {
1594 argv = alloca(3 * sizeof(char *));
1595 argv[1] = job_get_string(j->ldj, LAUNCH_JOBKEY_PROGRAM);
1596 argv[2] = NULL;
1597 }
1598
1599 if (job_get_string(j->ldj, LAUNCH_JOBKEY_PROGRAM))
1600 hasprog = true;
1601
1602 if (inetcompat) {
1603 argv[0] = file2exec;
1604 } else {
1605 argv++;
1606 file2exec = job_get_file2exec(j->ldj);
1607 }
1608
1609 if (hasprog) {
1610 r = execv(file2exec, (char *const*)argv);
1611 } else {
1612 r = execvp(file2exec, (char *const*)argv);
1613 }
1614
1615 if (-1 == r) {
1616 assumes(write(execfd, &errno, sizeof(errno)) != -1);
1617 job_log_error(j, LOG_ERR, "execv%s(\"%s\", ...)", hasprog ? "" : "p", file2exec);
1618 }
1619 _exit(EXIT_FAILURE);
1620 }
1621
1622 static void job_setup_attributes(struct jobcb *j)
1623 {
1624 launch_data_t srl = launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_SOFTRESOURCELIMITS);
1625 launch_data_t hrl = launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_HARDRESOURCELIMITS);
1626 bool inetcompat = launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_INETDCOMPATIBILITY) ? true : false;
1627 launch_data_t tmp;
1628 size_t i;
1629 const char *tmpstr;
1630 struct group *gre = NULL;
1631 gid_t gre_g = 0;
1632 static const struct {
1633 const char *key;
1634 int val;
1635 } limits[] = {
1636 { LAUNCH_JOBKEY_RESOURCELIMIT_CORE, RLIMIT_CORE },
1637 { LAUNCH_JOBKEY_RESOURCELIMIT_CPU, RLIMIT_CPU },
1638 { LAUNCH_JOBKEY_RESOURCELIMIT_DATA, RLIMIT_DATA },
1639 { LAUNCH_JOBKEY_RESOURCELIMIT_FSIZE, RLIMIT_FSIZE },
1640 { LAUNCH_JOBKEY_RESOURCELIMIT_MEMLOCK, RLIMIT_MEMLOCK },
1641 { LAUNCH_JOBKEY_RESOURCELIMIT_NOFILE, RLIMIT_NOFILE },
1642 { LAUNCH_JOBKEY_RESOURCELIMIT_NPROC, RLIMIT_NPROC },
1643 { LAUNCH_JOBKEY_RESOURCELIMIT_RSS, RLIMIT_RSS },
1644 { LAUNCH_JOBKEY_RESOURCELIMIT_STACK, RLIMIT_STACK },
1645 };
1646
1647 setpriority(PRIO_PROCESS, 0, job_get_integer(j->ldj, LAUNCH_JOBKEY_NICE));
1648
1649 if (srl || hrl) {
1650 for (i = 0; i < (sizeof(limits) / sizeof(limits[0])); i++) {
1651 struct rlimit rl;
1652
1653 if (!assumes(getrlimit(limits[i].val, &rl) != -1)) {
1654 continue;
1655 }
1656
1657 if (hrl)
1658 rl.rlim_max = job_get_integer(hrl, limits[i].key);
1659 if (srl)
1660 rl.rlim_cur = job_get_integer(srl, limits[i].key);
1661
1662 assumes(setrlimit(limits[i].val, &rl) != -1);
1663 }
1664 }
1665
1666 if (!inetcompat && job_get_bool(j->ldj, LAUNCH_JOBKEY_SESSIONCREATE))
1667 launchd_SessionCreate(job_get_file2exec(j->ldj));
1668
1669 if (job_get_bool(j->ldj, LAUNCH_JOBKEY_LOWPRIORITYIO)) {
1670 int lowprimib[] = { CTL_KERN, KERN_PROC_LOW_PRI_IO };
1671 int val = 1;
1672
1673 assumes(sysctl(lowprimib, sizeof(lowprimib) / sizeof(lowprimib[0]), NULL, NULL, &val, sizeof(val)) != -1);
1674 }
1675 if ((tmpstr = job_get_string(j->ldj, LAUNCH_JOBKEY_ROOTDIRECTORY))) {
1676 assumes(chroot(tmpstr) != -1);
1677 assumes(chdir(".") != -1);
1678 }
1679 if ((tmpstr = job_get_string(j->ldj, LAUNCH_JOBKEY_GROUPNAME))) {
1680 gre = getgrnam(tmpstr);
1681 if (gre) {
1682 gre_g = gre->gr_gid;
1683 if (-1 == setgid(gre_g)) {
1684 job_log_error(j, LOG_ERR, "setgid(%d)", gre_g);
1685 exit(EXIT_FAILURE);
1686 }
1687 } else {
1688 job_log(j, LOG_ERR, "getgrnam(\"%s\") failed", tmpstr);
1689 exit(EXIT_FAILURE);
1690 }
1691 }
1692 if ((tmpstr = job_get_string(j->ldj, LAUNCH_JOBKEY_USERNAME))) {
1693 struct passwd *pwe = getpwnam(tmpstr);
1694 if (pwe) {
1695 uid_t pwe_u = pwe->pw_uid;
1696 uid_t pwe_g = pwe->pw_gid;
1697
1698 if (pwe->pw_expire && time(NULL) >= pwe->pw_expire) {
1699 job_log(j, LOG_ERR, "expired account: %s", tmpstr);
1700 exit(EXIT_FAILURE);
1701 }
1702 if (job_get_bool(j->ldj, LAUNCH_JOBKEY_INITGROUPS)) {
1703 if (-1 == initgroups(tmpstr, gre ? gre_g : pwe_g)) {
1704 job_log_error(j, LOG_ERR, "initgroups()");
1705 exit(EXIT_FAILURE);
1706 }
1707 }
1708 if (!gre) {
1709 if (-1 == setgid(pwe_g)) {
1710 job_log_error(j, LOG_ERR, "setgid(%d)", pwe_g);
1711 exit(EXIT_FAILURE);
1712 }
1713 }
1714 if (-1 == setuid(pwe_u)) {
1715 job_log_error(j, LOG_ERR, "setuid(%d)", pwe_u);
1716 exit(EXIT_FAILURE);
1717 }
1718 } else {
1719 job_log(j, LOG_WARNING, "getpwnam(\"%s\") failed", tmpstr);
1720 exit(EXIT_FAILURE);
1721 }
1722 }
1723 if ((tmpstr = job_get_string(j->ldj, LAUNCH_JOBKEY_WORKINGDIRECTORY)))
1724 assumes(chdir(tmpstr) != -1);
1725 if (launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_UMASK))
1726 umask(job_get_integer(j->ldj, LAUNCH_JOBKEY_UMASK));
1727 if ((tmpstr = job_get_string(j->ldj, LAUNCH_JOBKEY_STANDARDOUTPATH))) {
1728 int sofd = open(tmpstr, O_WRONLY|O_APPEND|O_CREAT, DEFFILEMODE);
1729 if (assumes(sofd != -1)) {
1730 assumes(dup2(sofd, STDOUT_FILENO) != -1);
1731 assumes(close(sofd) != -1);
1732 }
1733 }
1734 if ((tmpstr = job_get_string(j->ldj, LAUNCH_JOBKEY_STANDARDERRORPATH))) {
1735 int sefd = open(tmpstr, O_WRONLY|O_APPEND|O_CREAT, DEFFILEMODE);
1736 if (assumes(sefd != -1)) {
1737 assumes(dup2(sefd, STDERR_FILENO) != -1);
1738 assumes(close(sefd) != -1);
1739 }
1740 }
1741 if ((tmp = launch_data_dict_lookup(j->ldj, LAUNCH_JOBKEY_ENVIRONMENTVARIABLES)))
1742 launch_data_dict_iterate(tmp, setup_job_env, NULL);
1743
1744 assumes(setsid() != -1);
1745 }
1746
1747 #ifdef PID1_REAP_ADOPTED_CHILDREN
1748 __private_extern__ int pid1_child_exit_status = 0;
1749 static void pid1waitpid(void)
1750 {
1751 pid_t p;
1752
1753 while ((p = waitpid(-1, &pid1_child_exit_status, WNOHANG)) > 0) {
1754 if (!launchd_check_pid(p))
1755 init_check_pid(p);
1756 }
1757 }
1758 #endif
1759
1760 static void do_shutdown(void)
1761 {
1762 struct jobcb *j;
1763
1764 shutdown_in_progress = true;
1765
1766 assumes(kevent_mod(asynckq, EVFILT_READ, EV_DISABLE, 0, 0, &kqasync_callback) != -1);
1767
1768 TAILQ_FOREACH(j, &jobs, tqe)
1769 job_stop(j);
1770
1771 if (getpid() == 1) {
1772 catatonia();
1773 mach_start_shutdown(SIGTERM);
1774 }
1775 }
1776
1777 static void signal_callback(void *obj __attribute__((unused)), struct kevent *kev)
1778 {
1779 switch (kev->ident) {
1780 case SIGHUP:
1781 update_ttys();
1782 reload_launchd_config();
1783 break;
1784 case SIGTERM:
1785 do_shutdown();
1786 break;
1787 #ifdef PID1_REAP_ADOPTED_CHILDREN
1788 case SIGCHLD:
1789 /* <rdar://problem/3632556> Please automatically reap processes reparented to PID 1 */
1790 if (getpid() == 1)
1791 pid1waitpid();
1792 break;
1793 #endif
1794 default:
1795 break;
1796 }
1797 }
1798
1799 static void fs_callback(void)
1800 {
1801 static bool mounted_volfs = false;
1802
1803 if (1 != getpid())
1804 mounted_volfs = true;
1805
1806 if (pending_stdout) {
1807 int fd = open(pending_stdout, O_CREAT|O_APPEND|O_WRONLY, DEFFILEMODE);
1808 if (fd != -1) {
1809 assumes(dup2(fd, STDOUT_FILENO) != -1);
1810 assumes(close(fd) != -1);
1811 free(pending_stdout);
1812 pending_stdout = NULL;
1813 }
1814 }
1815 if (pending_stderr) {
1816 int fd = open(pending_stderr, O_CREAT|O_APPEND|O_WRONLY, DEFFILEMODE);
1817 if (fd != -1) {
1818 assumes(dup2(fd, STDERR_FILENO) != -1);
1819 assumes(close(fd) != -1);
1820 free(pending_stderr);
1821 pending_stderr = NULL;
1822 }
1823 }
1824
1825 if (!mounted_volfs) {
1826 int r = mount("volfs", VOLFSDIR, MNT_RDONLY, NULL);
1827
1828 if (-1 == r && errno == ENOENT) {
1829 assumes(mkdir(VOLFSDIR, ACCESSPERMS & ~(S_IWUSR|S_IWGRP|S_IWOTH)) != -1);
1830 r = mount("volfs", VOLFSDIR, MNT_RDONLY, NULL);
1831 }
1832
1833 if (-1 == r) {
1834 syslog(LOG_WARNING, "mount(\"%s\", \"%s\", ...): %m", "volfs", VOLFSDIR);
1835 } else {
1836 mounted_volfs = true;
1837 }
1838 }
1839
1840 if (!launchd_inited)
1841 launchd_server_init(false);
1842 }
1843
1844 static void readcfg_callback(void *obj __attribute__((unused)), struct kevent *kev __attribute__((unused)))
1845 {
1846 int status;
1847
1848 #ifdef PID1_REAP_ADOPTED_CHILDREN
1849 if (getpid() == 1)
1850 status = pid1_child_exit_status;
1851 else
1852 #endif
1853 if (-1 == waitpid(readcfg_pid, &status, 0)) {
1854 syslog(LOG_WARNING, "waitpid(readcfg_pid, ...): %m");
1855 return;
1856 }
1857
1858 readcfg_pid = 0;
1859
1860 if (WIFEXITED(status)) {
1861 if (WEXITSTATUS(status))
1862 syslog(LOG_WARNING, "Unable to read launchd.conf: launchctl exited with status: %d", WEXITSTATUS(status));
1863 } else if (WIFSIGNALED(status)) {
1864 syslog(LOG_WARNING, "Unable to read launchd.conf: launchctl exited abnormally: %s", strsignal(WTERMSIG(status)));
1865 } else {
1866 syslog(LOG_WARNING, "Unable to read launchd.conf: launchctl exited abnormally");
1867 }
1868 }
1869
1870 static void reload_launchd_config(void)
1871 {
1872 struct stat sb;
1873 static char *ldconf = PID1LAUNCHD_CONF;
1874 const char *h = getenv("HOME");
1875
1876 if (h && ldconf == PID1LAUNCHD_CONF)
1877 asprintf(&ldconf, "%s/%s", h, LAUNCHD_CONF);
1878
1879 if (!ldconf)
1880 return;
1881
1882 if (lstat(ldconf, &sb) == 0) {
1883 int spair[2];
1884 assumes(socketpair(AF_UNIX, SOCK_STREAM, 0, spair) != -1);
1885 readcfg_pid = fork_with_bootstrap_port(launchd_bootstrap_port);
1886 if (readcfg_pid == 0) {
1887 char nbuf[100];
1888 assumes(close(spair[0]) != -1);
1889 sprintf(nbuf, "%d", spair[1]);
1890 setenv(LAUNCHD_TRUSTED_FD_ENV, nbuf, 1);
1891 int fd = open(ldconf, O_RDONLY);
1892 if (fd == -1) {
1893 syslog(LOG_ERR, "open(\"%s\"): %m", ldconf);
1894 exit(EXIT_FAILURE);
1895 }
1896 assumes(dup2(fd, STDIN_FILENO) != -1);
1897 assumes(close(fd) != -1);
1898 assumes(execl(LAUNCHCTL_PATH, LAUNCHCTL_PATH, NULL) != -1);
1899 _exit(EXIT_FAILURE);
1900 } else if (readcfg_pid == -1) {
1901 assumes(close(spair[0]) != -1);
1902 assumes(close(spair[1]) != -1);
1903 syslog(LOG_ERR, "fork(): %m");
1904 readcfg_pid = 0;
1905 } else {
1906 assumes(close(spair[1]) != -1);
1907 ipc_open(_fd(spair[0]), NULL);
1908 assumes(kevent_mod(readcfg_pid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, &kqreadcfg_callback) != -1);
1909 }
1910 }
1911 }
1912
1913 static void conceive_firstborn(char *argv[])
1914 {
1915 launch_data_t r, d = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
1916 launch_data_t args = launch_data_alloc(LAUNCH_DATA_ARRAY);
1917 launch_data_t l = launch_data_new_string("com.apple.launchd.firstborn");
1918 size_t i;
1919
1920 for (i = 0; *argv; argv++, i++)
1921 launch_data_array_set_index(args, launch_data_new_string(*argv), i);
1922
1923 launch_data_dict_insert(d, args, LAUNCH_JOBKEY_PROGRAMARGUMENTS);
1924 launch_data_dict_insert(d, l, LAUNCH_JOBKEY_LABEL);
1925
1926 r = load_job(d);
1927
1928 launch_data_free(r);
1929 launch_data_free(d);
1930
1931 TAILQ_FIRST(&jobs)->firstborn = true;
1932 }
1933
1934 static void loopback_setup(void)
1935 {
1936 struct ifaliasreq ifra;
1937 struct in6_aliasreq ifra6;
1938 struct ifreq ifr;
1939 int s, s6;
1940
1941 memset(&ifr, 0, sizeof(ifr));
1942 strcpy(ifr.ifr_name, "lo0");
1943
1944 assumes((s = socket(AF_INET, SOCK_DGRAM, 0)) != -1);
1945 assumes((s6 = socket(AF_INET6, SOCK_DGRAM, 0)) != -1);
1946
1947 if (ioctl(s, SIOCGIFFLAGS, &ifr) == -1) {
1948 syslog(LOG_ERR, "ioctl(SIOCGIFFLAGS): %m");
1949 } else {
1950 ifr.ifr_flags |= IFF_UP;
1951
1952 assumes(ioctl(s, SIOCSIFFLAGS, &ifr) != -1);
1953 }
1954
1955 memset(&ifr, 0, sizeof(ifr));
1956 strcpy(ifr.ifr_name, "lo0");
1957
1958 if (ioctl(s6, SIOCGIFFLAGS, &ifr) == -1) {
1959 syslog(LOG_ERR, "ioctl(SIOCGIFFLAGS): %m");
1960 } else {
1961 ifr.ifr_flags |= IFF_UP;
1962
1963 assumes(ioctl(s6, SIOCSIFFLAGS, &ifr) != -1);
1964 }
1965
1966 memset(&ifra, 0, sizeof(ifra));
1967 strcpy(ifra.ifra_name, "lo0");
1968
1969 ((struct sockaddr_in *)&ifra.ifra_addr)->sin_family = AF_INET;
1970 ((struct sockaddr_in *)&ifra.ifra_addr)->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
1971 ((struct sockaddr_in *)&ifra.ifra_addr)->sin_len = sizeof(struct sockaddr_in);
1972 ((struct sockaddr_in *)&ifra.ifra_mask)->sin_family = AF_INET;
1973 ((struct sockaddr_in *)&ifra.ifra_mask)->sin_addr.s_addr = htonl(IN_CLASSA_NET);
1974 ((struct sockaddr_in *)&ifra.ifra_mask)->sin_len = sizeof(struct sockaddr_in);
1975
1976 assumes(ioctl(s, SIOCAIFADDR, &ifra) != -1);
1977
1978 memset(&ifra6, 0, sizeof(ifra6));
1979 strcpy(ifra6.ifra_name, "lo0");
1980
1981 ifra6.ifra_addr.sin6_family = AF_INET6;
1982 ifra6.ifra_addr.sin6_addr = in6addr_loopback;
1983 ifra6.ifra_addr.sin6_len = sizeof(struct sockaddr_in6);
1984 ifra6.ifra_prefixmask.sin6_family = AF_INET6;
1985 memset(&ifra6.ifra_prefixmask.sin6_addr, 0xff, sizeof(struct in6_addr));
1986 ifra6.ifra_prefixmask.sin6_len = sizeof(struct sockaddr_in6);
1987 ifra6.ifra_lifetime.ia6t_vltime = ND6_INFINITE_LIFETIME;
1988 ifra6.ifra_lifetime.ia6t_pltime = ND6_INFINITE_LIFETIME;
1989
1990 assumes(ioctl(s6, SIOCAIFADDR_IN6, &ifra6) != -1);
1991
1992 assumes(close(s) != -1);
1993 assumes(close(s6) != -1);
1994 }
1995
1996 static void workaround3048875(int argc, char *argv[])
1997 {
1998 int i;
1999 char **ap, *newargv[100], *p = argv[1];
2000
2001 if (argc == 1 || argc > 2)
2002 return;
2003
2004 newargv[0] = argv[0];
2005 for (ap = newargv + 1, i = 1; ap < &newargv[100]; ap++, i++) {
2006 if ((*ap = strsep(&p, " \t")) == NULL)
2007 break;
2008 if (**ap == '\0') {
2009 *ap = NULL;
2010 break;
2011 }
2012 }
2013
2014 if (argc == i)
2015 return;
2016
2017 execv(newargv[0], newargv);
2018 }
2019
2020 static launch_data_t adjust_rlimits(launch_data_t in)
2021 {
2022 static struct rlimit *l = NULL;
2023 static size_t lsz = sizeof(struct rlimit) * RLIM_NLIMITS;
2024 struct rlimit *ltmp;
2025 size_t i,ltmpsz;
2026
2027 if (l == NULL) {
2028 l = malloc(lsz);
2029 for (i = 0; i < RLIM_NLIMITS; i++) {
2030 assumes(getrlimit(i, l + i) != -1);
2031 }
2032 }
2033
2034 if (in) {
2035 ltmp = launch_data_get_opaque(in);
2036 ltmpsz = launch_data_get_opaque_size(in);
2037
2038 if (ltmpsz > lsz) {
2039 syslog(LOG_WARNING, "Too much rlimit data sent!");
2040 ltmpsz = lsz;
2041 }
2042
2043 for (i = 0; i < (ltmpsz / sizeof(struct rlimit)); i++) {
2044 if (ltmp[i].rlim_cur == l[i].rlim_cur && ltmp[i].rlim_max == l[i].rlim_max)
2045 continue;
2046
2047 if (readcfg_pid && getpid() == 1) {
2048 int gmib[] = { CTL_KERN, KERN_MAXPROC };
2049 int pmib[] = { CTL_KERN, KERN_MAXPROCPERUID };
2050 const char *gstr = "kern.maxproc";
2051 const char *pstr = "kern.maxprocperuid";
2052 int gval = ltmp[i].rlim_max;
2053 int pval = ltmp[i].rlim_cur;
2054 switch (i) {
2055 case RLIMIT_NOFILE:
2056 gmib[1] = KERN_MAXFILES;
2057 pmib[1] = KERN_MAXFILESPERPROC;
2058 gstr = "kern.maxfiles";
2059 pstr = "kern.maxfilesperproc";
2060 break;
2061 case RLIMIT_NPROC:
2062 /* kernel will not clamp to this value, we must */
2063 if (gval > (2048 + 20))
2064 gval = 2048 + 20;
2065 break;
2066 default:
2067 break;
2068 }
2069 assumes(sysctl(gmib, 2, NULL, NULL, &gval, sizeof(gval)) != -1);
2070 assumes(sysctl(pmib, 2, NULL, NULL, &pval, sizeof(pval)) != -1);
2071 }
2072 assumes(setrlimit(i, ltmp + i) != -1);
2073 /* the kernel may have clamped the values we gave it */
2074 assumes(getrlimit(i, l + i) != -1);
2075 }
2076 }
2077
2078 return launch_data_new_opaque(l, sizeof(struct rlimit) * RLIM_NLIMITS);
2079 }
2080
2081 __private_extern__ void launchd_SessionCreate(const char *who)
2082 {
2083 void *seclib = dlopen(SECURITY_LIB, RTLD_LAZY);
2084 OSStatus (*sescr)(SessionCreationFlags flags, SessionAttributeBits attributes);
2085
2086 if (seclib) {
2087 sescr = dlsym(seclib, "SessionCreate");
2088
2089 if (sescr) {
2090 OSStatus scr = sescr(0, 0);
2091 if (scr != noErr)
2092 syslog(LOG_WARNING, "%s: SessionCreate() failed: %d", who, scr);
2093 } else {
2094 syslog(LOG_WARNING, "%s: couldn't find SessionCreate() in %s", who, SECURITY_LIB);
2095 }
2096
2097 dlclose(seclib);
2098 } else {
2099 syslog(LOG_WARNING, "%s: dlopen(\"%s\",...): %s", who, SECURITY_LIB, dlerror());
2100 }
2101 }
2102
2103 static int dir_has_files(const char *path)
2104 {
2105 DIR *dd = opendir(path);
2106 struct dirent *de;
2107 bool r = 0;
2108
2109 if (!dd)
2110 return -1;
2111
2112 while ((de = readdir(dd))) {
2113 if (strcmp(de->d_name, ".") && strcmp(de->d_name, "..")) {
2114 r = 1;
2115 break;
2116 }
2117 }
2118
2119 closedir(dd);
2120 return r;
2121 }
2122
2123 static void job_set_alarm(struct jobcb *j)
2124 {
2125 time_t later;
2126
2127 later = cronemu(j->start_cal_interval->tm_mon, j->start_cal_interval->tm_mday,
2128 j->start_cal_interval->tm_hour, j->start_cal_interval->tm_min);
2129
2130 if (j->start_cal_interval->tm_wday != -1) {
2131 time_t otherlater = cronemu_wday(j->start_cal_interval->tm_wday,
2132 j->start_cal_interval->tm_hour, j->start_cal_interval->tm_min);
2133
2134 if (-1 != j->start_cal_interval->tm_mday) {
2135 later = later < otherlater ? later : otherlater;
2136 } else {
2137 later = otherlater;
2138 }
2139 }
2140
2141 if (-1 == kevent_mod((uintptr_t)j->start_cal_interval, EVFILT_TIMER, EV_ADD, NOTE_ABSOLUTE|NOTE_SECONDS, later, j)) {
2142 job_log_error(j, LOG_ERR, "adding kevent alarm");
2143 } else {
2144 job_log(j, LOG_INFO, "scheduled to run again at: %s", ctime(&later));
2145 }
2146 }
2147
2148 void
2149 job_prep_log_msg(struct jobcb *j, char *buf, const char *msg, int err)
2150 {
2151 size_t lsz = strlen(j->label);
2152 size_t i, o;
2153
2154 for (i = 0, o = 0; i < lsz; i++, o++) {
2155 if (j->label[i] == '%') {
2156 buf[o] = '%';
2157 o++;
2158 buf[o] = '%';
2159 } else {
2160 buf[o] = j->label[i];
2161 }
2162 }
2163
2164 buf[o++] = ':';
2165 buf[o++] = ' ';
2166
2167 if (err) {
2168 sprintf(buf + o, "%s: %s", msg, strerror(err));
2169 } else {
2170 strcpy(buf + o, msg);
2171 }
2172 }
2173
2174 void
2175 job_log_error(struct jobcb *j, int pri, const char *msg, ...)
2176 {
2177 char newmsg[10000];
2178 va_list ap;
2179
2180 job_prep_log_msg(j, newmsg, msg, errno);
2181
2182 va_start(ap, msg);
2183
2184 vsyslog(pri, newmsg, ap);
2185
2186 va_end(ap);
2187 }
2188
2189 void
2190 job_log(struct jobcb *j, int pri, const char *msg, ...)
2191 {
2192 char newmsg[10000];
2193 va_list ap;
2194
2195 job_prep_log_msg(j, newmsg, msg, 0);
2196
2197 va_start(ap, msg);
2198
2199 vsyslog(pri, newmsg, ap);
2200
2201 va_end(ap);
2202 }
2203
2204 static void async_callback(void)
2205 {
2206 struct timespec timeout = { 0, 0 };
2207 struct kevent kev;
2208
2209 switch (kevent(asynckq, NULL, 0, &kev, 1, &timeout)) {
2210 case -1:
2211 syslog(LOG_DEBUG, "kevent(): %m");
2212 break;
2213 case 1:
2214 (*((kq_callback *)kev.udata))(kev.udata, &kev);
2215 case 0:
2216 break;
2217 default:
2218 syslog(LOG_DEBUG, "unexpected: kevent() returned something != 0, -1 or 1");
2219 }
2220 }
2221
2222 static void testfd_or_openfd(int fd, const char *path, int flags)
2223 {
2224 int tmpfd;
2225
2226 if (-1 != (tmpfd = dup(fd))) {
2227 assumes(close(tmpfd) != -1);
2228 } else {
2229 if (-1 == (tmpfd = open(path, flags))) {
2230 syslog(LOG_ERR, "open(\"%s\", ...): %m", path);
2231 } else if (tmpfd != fd) {
2232 assumes(dup2(tmpfd, fd) != -1);
2233 assumes(close(tmpfd) != -1);
2234 }
2235 }
2236 }
2237
2238 time_t
2239 cronemu(int mon, int mday, int hour, int min)
2240 {
2241 struct tm workingtm;
2242 time_t now;
2243
2244 now = time(NULL);
2245 workingtm = *localtime(&now);
2246
2247 workingtm.tm_isdst = -1;
2248 workingtm.tm_sec = 0;
2249 workingtm.tm_min++;
2250
2251 while (!cronemu_mon(&workingtm, mon, mday, hour, min)) {
2252 workingtm.tm_year++;
2253 workingtm.tm_mon = 0;
2254 workingtm.tm_mday = 1;
2255 workingtm.tm_hour = 0;
2256 workingtm.tm_min = 0;
2257 mktime(&workingtm);
2258 }
2259
2260 return mktime(&workingtm);
2261 }
2262
2263 time_t
2264 cronemu_wday(int wday, int hour, int min)
2265 {
2266 struct tm workingtm;
2267 time_t now;
2268
2269 now = time(NULL);
2270 workingtm = *localtime(&now);
2271
2272 workingtm.tm_isdst = -1;
2273 workingtm.tm_sec = 0;
2274 workingtm.tm_min++;
2275
2276 if (wday == 7)
2277 wday = 0;
2278
2279 while (workingtm.tm_wday != wday || !cronemu_hour(&workingtm, hour, min)) {
2280 workingtm.tm_mday++;
2281 workingtm.tm_hour = 0;
2282 workingtm.tm_min = 0;
2283 cronemu_hour(&workingtm, hour, min);
2284 mktime(&workingtm);
2285 }
2286
2287 return mktime(&workingtm);
2288 }
2289
2290 bool
2291 cronemu_mon(struct tm *wtm, int mon, int mday, int hour, int min)
2292 {
2293 if (mon == -1) {
2294 struct tm workingtm = *wtm;
2295 int carrytest;
2296
2297 while (!cronemu_mday(&workingtm, mday, hour, min)) {
2298 workingtm.tm_mon++;
2299 workingtm.tm_mday = 1;
2300 workingtm.tm_hour = 0;
2301 workingtm.tm_min = 0;
2302 carrytest = workingtm.tm_mon;
2303 mktime(&workingtm);
2304 if (carrytest != workingtm.tm_mon)
2305 return false;
2306 }
2307 *wtm = workingtm;
2308 return true;
2309 }
2310
2311 if (mon < wtm->tm_mon)
2312 return false;
2313
2314 if (mon > wtm->tm_mon) {
2315 wtm->tm_mon = mon;
2316 wtm->tm_mday = 1;
2317 wtm->tm_hour = 0;
2318 wtm->tm_min = 0;
2319 }
2320
2321 return cronemu_mday(wtm, mday, hour, min);
2322 }
2323
2324 bool
2325 cronemu_mday(struct tm *wtm, int mday, int hour, int min)
2326 {
2327 if (mday == -1) {
2328 struct tm workingtm = *wtm;
2329 int carrytest;
2330
2331 while (!cronemu_hour(&workingtm, hour, min)) {
2332 workingtm.tm_mday++;
2333 workingtm.tm_hour = 0;
2334 workingtm.tm_min = 0;
2335 carrytest = workingtm.tm_mday;
2336 mktime(&workingtm);
2337 if (carrytest != workingtm.tm_mday)
2338 return false;
2339 }
2340 *wtm = workingtm;
2341 return true;
2342 }
2343
2344 if (mday < wtm->tm_mday)
2345 return false;
2346
2347 if (mday > wtm->tm_mday) {
2348 wtm->tm_mday = mday;
2349 wtm->tm_hour = 0;
2350 wtm->tm_min = 0;
2351 }
2352
2353 return cronemu_hour(wtm, hour, min);
2354 }
2355
2356 bool
2357 cronemu_hour(struct tm *wtm, int hour, int min)
2358 {
2359 if (hour == -1) {
2360 struct tm workingtm = *wtm;
2361 int carrytest;
2362
2363 while (!cronemu_min(&workingtm, min)) {
2364 workingtm.tm_hour++;
2365 workingtm.tm_min = 0;
2366 carrytest = workingtm.tm_hour;
2367 mktime(&workingtm);
2368 if (carrytest != workingtm.tm_hour)
2369 return false;
2370 }
2371 *wtm = workingtm;
2372 return true;
2373 }
2374
2375 if (hour < wtm->tm_hour)
2376 return false;
2377
2378 if (hour > wtm->tm_hour) {
2379 wtm->tm_hour = hour;
2380 wtm->tm_min = 0;
2381 }
2382
2383 return cronemu_min(wtm, min);
2384 }
2385
2386 bool
2387 cronemu_min(struct tm *wtm, int min)
2388 {
2389 if (min == -1)
2390 return true;
2391
2392 if (min < wtm->tm_min)
2393 return false;
2394
2395 if (min > wtm->tm_min) {
2396 wtm->tm_min = min;
2397 }
2398
2399 return true;
2400 }
2401
2402 void
2403 _log_launchd_bug(const char *path, unsigned int line, const char *test)
2404 {
2405 int saved_errno = errno;
2406 const char *file = strrchr(path, '/');
2407
2408 if (!file) {
2409 file = path;
2410 } else {
2411 file += 1;
2412 }
2413
2414 syslog(LOG_NOTICE, "Bug: %s:%u:%u: %s", file, line, saved_errno, test);
2415 }