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