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