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