]>
Commit | Line | Data |
---|---|---|
ed34e3c3 | 1 | /* |
ed34e3c3 A |
2 | * @APPLE_APACHE_LICENSE_HEADER_START@ |
3 | * | |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | * you may not use this file except in compliance with the License. | |
6 | * You may obtain a copy of the License at | |
7 | * | |
8 | * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | * | |
10 | * Unless required by applicable law or agreed to in writing, software | |
11 | * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | * See the License for the specific language governing permissions and | |
14 | * limitations under the License. | |
15 | * | |
16 | * @APPLE_APACHE_LICENSE_HEADER_END@ | |
17 | */ | |
18 | ||
26b346da | 19 | static const char *const __rcs_file_version__ = "$Revision: 23923 $"; |
5b0a4722 A |
20 | |
21 | #include "config.h" | |
22 | #include "launchd_core_logic.h" | |
ed34e3c3 | 23 | |
f36da725 | 24 | #include <TargetConditionals.h> |
ed34e3c3 A |
25 | #include <mach/mach.h> |
26 | #include <mach/mach_error.h> | |
5b0a4722 | 27 | #include <mach/mach_time.h> |
ed34e3c3 A |
28 | #include <mach/boolean.h> |
29 | #include <mach/message.h> | |
30 | #include <mach/notify.h> | |
31 | #include <mach/mig_errors.h> | |
32 | #include <mach/mach_traps.h> | |
33 | #include <mach/mach_interface.h> | |
34 | #include <mach/host_info.h> | |
35 | #include <mach/mach_host.h> | |
36 | #include <mach/exception.h> | |
5b0a4722 | 37 | #include <mach/host_reboot.h> |
ed34e3c3 A |
38 | #include <sys/types.h> |
39 | #include <sys/queue.h> | |
40 | #include <sys/event.h> | |
ed34e3c3 A |
41 | #include <sys/stat.h> |
42 | #include <sys/ucred.h> | |
43 | #include <sys/fcntl.h> | |
44 | #include <sys/un.h> | |
5b0a4722 | 45 | #include <sys/reboot.h> |
ed34e3c3 A |
46 | #include <sys/wait.h> |
47 | #include <sys/sysctl.h> | |
48 | #include <sys/sockio.h> | |
49 | #include <sys/time.h> | |
50 | #include <sys/resource.h> | |
51 | #include <sys/ioctl.h> | |
52 | #include <sys/mount.h> | |
5b0a4722 | 53 | #include <sys/pipe.h> |
ed34e3c3 A |
54 | #include <net/if.h> |
55 | #include <netinet/in.h> | |
56 | #include <netinet/in_var.h> | |
57 | #include <netinet6/nd6.h> | |
5b0a4722 | 58 | #include <bsm/libbsm.h> |
ed34e3c3 A |
59 | #include <unistd.h> |
60 | #include <signal.h> | |
61 | #include <errno.h> | |
ed34e3c3 A |
62 | #include <libgen.h> |
63 | #include <stdio.h> | |
64 | #include <stdlib.h> | |
65 | #include <stdarg.h> | |
66 | #include <stdbool.h> | |
67 | #include <paths.h> | |
68 | #include <pwd.h> | |
69 | #include <grp.h> | |
70 | #include <ttyent.h> | |
71 | #include <dlfcn.h> | |
72 | #include <dirent.h> | |
73 | #include <string.h> | |
74 | #include <ctype.h> | |
75 | #include <glob.h> | |
5b0a4722 | 76 | #include <spawn.h> |
f36da725 | 77 | #if HAVE_SANDBOX |
5b0a4722 | 78 | #include <sandbox.h> |
f36da725 A |
79 | #endif |
80 | #if HAVE_QUARANTINE | |
81 | #include <quarantine.h> | |
82 | #endif | |
5b0a4722 | 83 | |
ef398931 A |
84 | #include "launch.h" |
85 | #include "launch_priv.h" | |
86 | #include "launch_internal.h" | |
87 | #include "bootstrap.h" | |
88 | #include "bootstrap_priv.h" | |
89 | #include "vproc.h" | |
90 | #include "vproc_internal.h" | |
5b0a4722 A |
91 | |
92 | #include "reboot2.h" | |
ed34e3c3 | 93 | |
ed34e3c3 | 94 | #include "launchd.h" |
5b0a4722 | 95 | #include "launchd_runtime.h" |
ed34e3c3 | 96 | #include "launchd_unix_ipc.h" |
5b0a4722 A |
97 | #include "protocol_vproc.h" |
98 | #include "protocol_vprocServer.h" | |
ef398931 | 99 | #include "protocol_job_reply.h" |
5b0a4722 A |
100 | |
101 | #define LAUNCHD_MIN_JOB_RUN_TIME 10 | |
102 | #define LAUNCHD_DEFAULT_EXIT_TIMEOUT 20 | |
103 | #define LAUNCHD_SIGKILL_TIMER 5 | |
104 | ||
105 | ||
106 | #define TAKE_SUBSET_NAME "TakeSubsetName" | |
107 | #define TAKE_SUBSET_PID "TakeSubsetPID" | |
108 | #define TAKE_SUBSET_PERPID "TakeSubsetPerPID" | |
109 | ||
110 | #define IS_POWER_OF_TWO(v) (!(v & (v - 1)) && v) | |
111 | ||
112 | extern char **environ; | |
113 | ||
114 | struct waiting_for_removal { | |
115 | SLIST_ENTRY(waiting_for_removal) sle; | |
116 | mach_port_t reply_port; | |
117 | }; | |
118 | ||
119 | static bool waiting4removal_new(job_t j, mach_port_t rp); | |
120 | static void waiting4removal_delete(job_t j, struct waiting_for_removal *w4r); | |
121 | ||
122 | struct mspolicy { | |
123 | SLIST_ENTRY(mspolicy) sle; | |
124 | unsigned int allow:1, per_pid:1; | |
125 | const char name[0]; | |
126 | }; | |
127 | ||
128 | static bool mspolicy_new(job_t j, const char *name, bool allow, bool pid_local, bool skip_check); | |
129 | static bool mspolicy_copy(job_t j_to, job_t j_from); | |
130 | static void mspolicy_setup(launch_data_t obj, const char *key, void *context); | |
131 | static bool mspolicy_check(job_t j, const char *name, bool pid_local); | |
132 | static void mspolicy_delete(job_t j, struct mspolicy *msp); | |
ed34e3c3 A |
133 | |
134 | struct machservice { | |
135 | SLIST_ENTRY(machservice) sle; | |
5b0a4722 A |
136 | SLIST_ENTRY(machservice) special_port_sle; |
137 | LIST_ENTRY(machservice) name_hash_sle; | |
138 | LIST_ENTRY(machservice) port_hash_sle; | |
139 | job_t job; | |
140 | uint64_t bad_perf_cnt; | |
141 | unsigned int gen_num; | |
ed34e3c3 | 142 | mach_port_name_t port; |
5b0a4722 A |
143 | unsigned int isActive:1, reset:1, recv:1, hide:1, kUNCServer:1, per_user_hack:1, debug_on_close:1, per_pid:1, special_port_num:10; |
144 | const char name[0]; | |
ed34e3c3 A |
145 | }; |
146 | ||
5b0a4722 A |
147 | static SLIST_HEAD(, machservice) special_ports; /* hack, this should be per jobmgr_t */ |
148 | ||
149 | #define PORT_HASH_SIZE 32 | |
150 | #define HASH_PORT(x) (IS_POWER_OF_TWO(PORT_HASH_SIZE) ? (MACH_PORT_INDEX(x) & (PORT_HASH_SIZE - 1)) : (MACH_PORT_INDEX(x) % PORT_HASH_SIZE)) | |
151 | ||
152 | static LIST_HEAD(, machservice) port_hash[PORT_HASH_SIZE]; | |
153 | ||
ed34e3c3 A |
154 | static void machservice_setup(launch_data_t obj, const char *key, void *context); |
155 | static void machservice_setup_options(launch_data_t obj, const char *key, void *context); | |
5b0a4722 A |
156 | static void machservice_resetport(job_t j, struct machservice *ms); |
157 | static struct machservice *machservice_new(job_t j, const char *name, mach_port_t *serviceport, bool pid_local); | |
158 | static void machservice_ignore(job_t j, struct machservice *ms); | |
159 | static void machservice_watch(job_t j, struct machservice *ms); | |
160 | static void machservice_delete(job_t j, struct machservice *, bool port_died); | |
161 | static void machservice_request_notifications(struct machservice *); | |
162 | static mach_port_t machservice_port(struct machservice *); | |
163 | static job_t machservice_job(struct machservice *); | |
164 | static bool machservice_hidden(struct machservice *); | |
165 | static bool machservice_active(struct machservice *); | |
166 | static const char *machservice_name(struct machservice *); | |
167 | static bootstrap_status_t machservice_status(struct machservice *); | |
ed34e3c3 A |
168 | |
169 | struct socketgroup { | |
170 | SLIST_ENTRY(socketgroup) sle; | |
171 | int *fds; | |
172 | unsigned int junkfds:1, fd_cnt:31; | |
f36da725 A |
173 | union { |
174 | const char name[0]; | |
175 | char name_init[0]; | |
176 | }; | |
ed34e3c3 A |
177 | }; |
178 | ||
5b0a4722 A |
179 | static bool socketgroup_new(job_t j, const char *name, int *fds, unsigned int fd_cnt, bool junkfds); |
180 | static void socketgroup_delete(job_t j, struct socketgroup *sg); | |
181 | static void socketgroup_watch(job_t j, struct socketgroup *sg); | |
182 | static void socketgroup_ignore(job_t j, struct socketgroup *sg); | |
183 | static void socketgroup_callback(job_t j); | |
ed34e3c3 | 184 | static void socketgroup_setup(launch_data_t obj, const char *key, void *context); |
5b0a4722 | 185 | static void socketgroup_kevent_mod(job_t j, struct socketgroup *sg, bool do_add); |
ed34e3c3 A |
186 | |
187 | struct calendarinterval { | |
5b0a4722 | 188 | LIST_ENTRY(calendarinterval) global_sle; |
ed34e3c3 | 189 | SLIST_ENTRY(calendarinterval) sle; |
5b0a4722 | 190 | job_t job; |
ed34e3c3 | 191 | struct tm when; |
5b0a4722 | 192 | time_t when_next; |
ed34e3c3 A |
193 | }; |
194 | ||
5b0a4722 A |
195 | static LIST_HEAD(, calendarinterval) sorted_calendar_events; |
196 | ||
197 | static bool calendarinterval_new(job_t j, struct tm *w); | |
198 | static bool calendarinterval_new_from_obj(job_t j, launch_data_t obj); | |
199 | static void calendarinterval_new_from_obj_dict_walk(launch_data_t obj, const char *key, void *context); | |
200 | static void calendarinterval_delete(job_t j, struct calendarinterval *ci); | |
201 | static void calendarinterval_setalarm(job_t j, struct calendarinterval *ci); | |
202 | static void calendarinterval_callback(void); | |
203 | static void calendarinterval_sanity_check(void); | |
ed34e3c3 A |
204 | |
205 | struct envitem { | |
206 | SLIST_ENTRY(envitem) sle; | |
207 | char *value; | |
f36da725 A |
208 | union { |
209 | const char key[0]; | |
210 | char key_init[0]; | |
211 | }; | |
ed34e3c3 A |
212 | }; |
213 | ||
5b0a4722 A |
214 | static bool envitem_new(job_t j, const char *k, const char *v, bool global); |
215 | static void envitem_delete(job_t j, struct envitem *ei, bool global); | |
ed34e3c3 A |
216 | static void envitem_setup(launch_data_t obj, const char *key, void *context); |
217 | ||
218 | struct limititem { | |
219 | SLIST_ENTRY(limititem) sle; | |
220 | struct rlimit lim; | |
221 | unsigned int setsoft:1, sethard:1, which:30; | |
222 | }; | |
223 | ||
5b0a4722 A |
224 | static bool limititem_update(job_t j, int w, rlim_t r); |
225 | static void limititem_delete(job_t j, struct limititem *li); | |
ed34e3c3 | 226 | static void limititem_setup(launch_data_t obj, const char *key, void *context); |
f36da725 | 227 | #if HAVE_SANDBOX |
5b0a4722 | 228 | static void seatbelt_setup_flags(launch_data_t obj, const char *key, void *context); |
f36da725 | 229 | #endif |
ed34e3c3 A |
230 | |
231 | typedef enum { | |
232 | NETWORK_UP = 1, | |
233 | NETWORK_DOWN, | |
234 | SUCCESSFUL_EXIT, | |
235 | FAILED_EXIT, | |
236 | PATH_EXISTS, | |
237 | PATH_MISSING, | |
5b0a4722 A |
238 | OTHER_JOB_ENABLED, |
239 | OTHER_JOB_DISABLED, | |
240 | OTHER_JOB_ACTIVE, | |
241 | OTHER_JOB_INACTIVE, | |
242 | PATH_CHANGES, | |
243 | DIR_NOT_EMPTY, | |
ed34e3c3 A |
244 | // FILESYSTEMTYPE_IS_MOUNTED, /* for nfsiod, but maybe others */ |
245 | } semaphore_reason_t; | |
246 | ||
247 | struct semaphoreitem { | |
248 | SLIST_ENTRY(semaphoreitem) sle; | |
249 | semaphore_reason_t why; | |
5b0a4722 | 250 | int fd; |
f36da725 A |
251 | union { |
252 | const char what[0]; | |
253 | char what_init[0]; | |
254 | }; | |
ed34e3c3 A |
255 | }; |
256 | ||
5b0a4722 A |
257 | struct semaphoreitem_dict_iter_context { |
258 | job_t j; | |
259 | semaphore_reason_t why_true; | |
260 | semaphore_reason_t why_false; | |
261 | }; | |
ed34e3c3 | 262 | |
5b0a4722 A |
263 | static bool semaphoreitem_new(job_t j, semaphore_reason_t why, const char *what); |
264 | static void semaphoreitem_delete(job_t j, struct semaphoreitem *si); | |
265 | static void semaphoreitem_setup(launch_data_t obj, const char *key, void *context); | |
266 | static void semaphoreitem_setup_dict_iter(launch_data_t obj, const char *key, void *context); | |
267 | static void semaphoreitem_callback(job_t j, struct kevent *kev); | |
268 | static void semaphoreitem_watch(job_t j, struct semaphoreitem *si); | |
269 | static void semaphoreitem_ignore(job_t j, struct semaphoreitem *si); | |
270 | static void semaphoreitem_runtime_mod_ref(struct semaphoreitem *si, bool add); | |
271 | ||
272 | #define ACTIVE_JOB_HASH_SIZE 32 | |
273 | #define ACTIVE_JOB_HASH(x) (IS_POWER_OF_TWO(ACTIVE_JOB_HASH_SIZE) ? (x & (ACTIVE_JOB_HASH_SIZE - 1)) : (x % ACTIVE_JOB_HASH_SIZE)) | |
274 | #define MACHSERVICE_HASH_SIZE 37 | |
275 | ||
276 | struct jobmgr_s { | |
277 | kq_callback kqjobmgr_callback; | |
278 | SLIST_ENTRY(jobmgr_s) sle; | |
279 | SLIST_HEAD(, jobmgr_s) submgrs; | |
280 | LIST_HEAD(, job_s) jobs; | |
281 | LIST_HEAD(, job_s) active_jobs[ACTIVE_JOB_HASH_SIZE]; | |
282 | LIST_HEAD(, machservice) ms_hash[MACHSERVICE_HASH_SIZE]; | |
283 | mach_port_t jm_port; | |
284 | mach_port_t req_port; | |
285 | jobmgr_t parentmgr; | |
286 | int reboot_flags; | |
287 | unsigned int global_on_demand_cnt; | |
288 | unsigned int hopefully_first_cnt; | |
289 | unsigned int normal_active_cnt; | |
290 | unsigned int sent_stop_to_normal_jobs:1, sent_stop_to_hopefully_last_jobs:1, shutting_down:1, session_initialized:1; | |
f36da725 A |
291 | union { |
292 | const char name[0]; | |
293 | char name_init[0]; | |
294 | }; | |
5b0a4722 | 295 | }; |
ed34e3c3 | 296 | |
5b0a4722 A |
297 | #define jobmgr_assumes(jm, e) \ |
298 | (__builtin_expect(!(e), 0) ? jobmgr_log_bug(jm, __rcs_file_version__, __FILE__, __LINE__, #e), false : true) | |
299 | ||
300 | static jobmgr_t jobmgr_new(jobmgr_t jm, mach_port_t requestorport, mach_port_t transfer_port, bool sflag, const char *name); | |
301 | static job_t jobmgr_import2(jobmgr_t jm, launch_data_t pload); | |
302 | static jobmgr_t jobmgr_parent(jobmgr_t jm); | |
303 | static jobmgr_t jobmgr_do_garbage_collection(jobmgr_t jm); | |
f36da725 | 304 | static bool jobmgr_label_test(jobmgr_t jm, const char *str); |
5b0a4722 A |
305 | static void jobmgr_reap_bulk(jobmgr_t jm, struct kevent *kev); |
306 | static void jobmgr_log_stray_children(jobmgr_t jm); | |
307 | static void jobmgr_remove(jobmgr_t jm); | |
308 | static void jobmgr_dispatch_all(jobmgr_t jm, bool newmounthack); | |
309 | static job_t jobmgr_init_session(jobmgr_t jm, const char *session_type, bool sflag); | |
310 | static job_t jobmgr_find_by_pid(jobmgr_t jm, pid_t p, bool create_anon); | |
311 | static job_t job_mig_intran2(jobmgr_t jm, mach_port_t mport, pid_t upid); | |
312 | static void job_export_all2(jobmgr_t jm, launch_data_t where); | |
313 | static void jobmgr_callback(void *obj, struct kevent *kev); | |
314 | static void jobmgr_setup_env_from_other_jobs(jobmgr_t jm); | |
315 | static void jobmgr_export_env_from_other_jobs(jobmgr_t jm, launch_data_t dict); | |
316 | static struct machservice *jobmgr_lookup_service(jobmgr_t jm, const char *name, bool check_parent, pid_t target_pid); | |
317 | static void jobmgr_logv(jobmgr_t jm, int pri, int err, const char *msg, va_list ap) __attribute__((format(printf, 4, 0))); | |
318 | static void jobmgr_log(jobmgr_t jm, int pri, const char *msg, ...) __attribute__((format(printf, 3, 4))); | |
319 | /* static void jobmgr_log_error(jobmgr_t jm, int pri, const char *msg, ...) __attribute__((format(printf, 3, 4))); */ | |
320 | static void jobmgr_log_bug(jobmgr_t jm, const char *rcs_rev, const char *path, unsigned int line, const char *test); | |
321 | ||
322 | #define DO_RUSAGE_SUMMATION 0 | |
323 | ||
324 | #define AUTO_PICK_LEGACY_LABEL (const char *)(~0) | |
325 | ||
326 | struct job_s { | |
ed34e3c3 | 327 | kq_callback kqjob_callback; |
5b0a4722 A |
328 | LIST_ENTRY(job_s) sle; |
329 | LIST_ENTRY(job_s) pid_hash_sle; | |
330 | LIST_ENTRY(job_s) label_hash_sle; | |
ed34e3c3 | 331 | SLIST_HEAD(, socketgroup) sockets; |
ed34e3c3 A |
332 | SLIST_HEAD(, calendarinterval) cal_intervals; |
333 | SLIST_HEAD(, envitem) global_env; | |
334 | SLIST_HEAD(, envitem) env; | |
335 | SLIST_HEAD(, limititem) limits; | |
5b0a4722 | 336 | SLIST_HEAD(, mspolicy) mspolicies; |
ed34e3c3 A |
337 | SLIST_HEAD(, machservice) machservices; |
338 | SLIST_HEAD(, semaphoreitem) semaphores; | |
5b0a4722 A |
339 | SLIST_HEAD(, waiting_for_removal) removal_watchers; |
340 | #if DO_RUSAGE_SUMMATION | |
ed34e3c3 | 341 | struct rusage ru; |
5b0a4722 A |
342 | #endif |
343 | cpu_type_t *j_binpref; | |
344 | size_t j_binpref_cnt; | |
345 | mach_port_t j_port; | |
f36da725 | 346 | mach_port_t wait_reply_port; /* we probably should switch to a list of waiters */ |
ed34e3c3 | 347 | uid_t mach_uid; |
5b0a4722 | 348 | jobmgr_t mgr; |
ed34e3c3 A |
349 | char **argv; |
350 | char *prog; | |
351 | char *rootdir; | |
352 | char *workingdir; | |
353 | char *username; | |
354 | char *groupname; | |
ed34e3c3 A |
355 | char *stdoutpath; |
356 | char *stderrpath; | |
fe044cc9 | 357 | char *alt_exc_handler; |
5b0a4722 A |
358 | struct machservice *lastlookup; |
359 | unsigned int lastlookup_gennum; | |
f36da725 | 360 | #if HAVE_SANDBOX |
5b0a4722 A |
361 | char *seatbelt_profile; |
362 | uint64_t seatbelt_flags; | |
f36da725 A |
363 | #endif |
364 | #if HAVE_QUARANTINE | |
5b0a4722 A |
365 | void *quarantine_data; |
366 | size_t quarantine_data_sz; | |
f36da725 | 367 | #endif |
ed34e3c3 A |
368 | pid_t p; |
369 | int argc; | |
370 | int last_exit_status; | |
5b0a4722 A |
371 | int forkfd; |
372 | int log_redirect_fd; | |
ed34e3c3 | 373 | int nice; |
5b0a4722 A |
374 | unsigned int timeout; |
375 | unsigned int exit_timeout; | |
376 | int stdout_err_fd; | |
377 | uint64_t sent_sigterm_time; | |
378 | uint64_t start_time; | |
379 | uint32_t min_run_time; | |
380 | uint32_t start_interval; | |
381 | unsigned int checkedin:1, anonymous:1, debug:1, inetcompat:1, inetcompat_wait:1, | |
382 | ondemand:1, session_create:1, low_pri_io:1, no_init_groups:1, priv_port_has_senders:1, | |
383 | importing_global_env:1, importing_hard_limits:1, setmask:1, legacy_mach_job:1, start_pending:1; | |
ed34e3c3 | 384 | mode_t mask; |
5b0a4722 A |
385 | unsigned int globargv:1, wait4debugger:1, unload_at_exit:1, stall_before_exec:1, only_once:1, |
386 | currently_ignored:1, forced_peers_to_demand_mode:1, setnice:1, hopefully_exits_last:1, removal_pending:1, | |
387 | wait4pipe_eof:1, sent_sigkill:1, debug_before_kill:1, weird_bootstrap:1, start_on_mount:1, | |
388 | per_user:1, hopefully_exits_first:1, deny_unknown_mslookups:1, unload_at_mig_return:1, abandon_pg:1, | |
f36da725 | 389 | poll_for_vfs_changes:1, internal_exc_handler:1, deny_job_creation:1; |
5b0a4722 | 390 | const char label[0]; |
ed34e3c3 A |
391 | }; |
392 | ||
5b0a4722 A |
393 | #define LABEL_HASH_SIZE 53 |
394 | ||
395 | static LIST_HEAD(, job_s) label_hash[LABEL_HASH_SIZE]; | |
396 | static size_t hash_label(const char *label) __attribute__((pure)); | |
397 | static size_t hash_ms(const char *msstr) __attribute__((pure)); | |
398 | ||
399 | ||
400 | #define job_assumes(j, e) \ | |
401 | (__builtin_expect(!(e), 0) ? job_log_bug(j, __rcs_file_version__, __FILE__, __LINE__, #e), false : true) | |
402 | ||
ed34e3c3 | 403 | static void job_import_keys(launch_data_t obj, const char *key, void *context); |
5b0a4722 A |
404 | static void job_import_bool(job_t j, const char *key, bool value); |
405 | static void job_import_string(job_t j, const char *key, const char *value); | |
406 | static void job_import_integer(job_t j, const char *key, long long value); | |
407 | static void job_import_dictionary(job_t j, const char *key, launch_data_t value); | |
408 | static void job_import_array(job_t j, const char *key, launch_data_t value); | |
409 | static void job_import_opaque(job_t j, const char *key, launch_data_t value); | |
410 | static bool job_set_global_on_demand(job_t j, bool val); | |
411 | static const char *job_active(job_t j); | |
412 | static void job_watch(job_t j); | |
413 | static void job_ignore(job_t j); | |
414 | static void job_reap(job_t j); | |
415 | static bool job_useless(job_t j); | |
416 | static bool job_keepalive(job_t j); | |
417 | static void job_start(job_t j); | |
418 | static void job_start_child(job_t j) __attribute__((noreturn)); | |
419 | static void job_setup_attributes(job_t j); | |
420 | static bool job_setup_machport(job_t j); | |
421 | static void job_setup_fd(job_t j, int target_fd, const char *path, int flags); | |
422 | static void job_postfork_become_user(job_t j); | |
ef398931 | 423 | #if !TARGET_OS_EMBEDDED |
cf0bacfd | 424 | static void job_enable_audit_for_user(job_t j, uid_t u, char *name); |
ef398931 | 425 | #endif |
5b0a4722 A |
426 | static void job_find_and_blame_pids_with_weird_uids(job_t j); |
427 | static void job_force_sampletool(job_t j); | |
428 | static void job_setup_exception_port(job_t j, task_t target_task); | |
429 | static void job_reparent_hack(job_t j, const char *where); | |
ed34e3c3 | 430 | static void job_callback(void *obj, struct kevent *kev); |
5b0a4722 A |
431 | static void job_callback_proc(job_t j, int flags, int fflags); |
432 | static void job_callback_timer(job_t j, void *ident); | |
433 | static void job_callback_read(job_t j, int ident); | |
434 | static void job_log_stray_pg(job_t j); | |
435 | static job_t job_new_anonymous(jobmgr_t jm, pid_t anonpid); | |
436 | static job_t job_new(jobmgr_t jm, const char *label, const char *prog, const char *const *argv); | |
437 | static job_t job_new_via_mach_init(job_t j, const char *cmd, uid_t uid, bool ond); | |
438 | static const char *job_prog(job_t j); | |
439 | static jobmgr_t job_get_bs(job_t j); | |
440 | static void job_kill(job_t j); | |
441 | static void job_uncork_fork(job_t j); | |
442 | static void job_log_stdouterr(job_t j); | |
443 | static void job_logv(job_t j, int pri, int err, const char *msg, va_list ap) __attribute__((format(printf, 4, 0))); | |
444 | static void job_log_error(job_t j, int pri, const char *msg, ...) __attribute__((format(printf, 3, 4))); | |
445 | static void job_log_bug(job_t j, const char *rcs_rev, const char *path, unsigned int line, const char *test); | |
446 | static void job_log_stdouterr2(job_t j, const char *msg, ...); | |
447 | static void job_set_exeception_port(job_t j, mach_port_t port); | |
448 | static kern_return_t job_handle_mpm_wait(job_t j, mach_port_t srp, int *waitstatus); | |
449 | ||
ed34e3c3 A |
450 | |
451 | ||
452 | static const struct { | |
453 | const char *key; | |
454 | int val; | |
455 | } launchd_keys2limits[] = { | |
5b0a4722 A |
456 | { LAUNCH_JOBKEY_RESOURCELIMIT_CORE, RLIMIT_CORE }, |
457 | { LAUNCH_JOBKEY_RESOURCELIMIT_CPU, RLIMIT_CPU }, | |
458 | { LAUNCH_JOBKEY_RESOURCELIMIT_DATA, RLIMIT_DATA }, | |
459 | { LAUNCH_JOBKEY_RESOURCELIMIT_FSIZE, RLIMIT_FSIZE }, | |
460 | { LAUNCH_JOBKEY_RESOURCELIMIT_MEMLOCK, RLIMIT_MEMLOCK }, | |
461 | { LAUNCH_JOBKEY_RESOURCELIMIT_NOFILE, RLIMIT_NOFILE }, | |
462 | { LAUNCH_JOBKEY_RESOURCELIMIT_NPROC, RLIMIT_NPROC }, | |
463 | { LAUNCH_JOBKEY_RESOURCELIMIT_RSS, RLIMIT_RSS }, | |
464 | { LAUNCH_JOBKEY_RESOURCELIMIT_STACK, RLIMIT_STACK }, | |
ed34e3c3 A |
465 | }; |
466 | ||
467 | static time_t cronemu(int mon, int mday, int hour, int min); | |
468 | static time_t cronemu_wday(int wday, int hour, int min); | |
469 | static bool cronemu_mon(struct tm *wtm, int mon, int mday, int hour, int min); | |
470 | static bool cronemu_mday(struct tm *wtm, int mday, int hour, int min); | |
471 | static bool cronemu_hour(struct tm *wtm, int hour, int min); | |
472 | static bool cronemu_min(struct tm *wtm, int min); | |
473 | ||
5b0a4722 A |
474 | /* miscellaneous file local functions */ |
475 | static void ensure_root_bkgd_setup(void); | |
476 | static int dir_has_files(job_t j, const char *path); | |
ed34e3c3 | 477 | static char **mach_cmd2argv(const char *string); |
5b0a4722 A |
478 | static size_t our_strhash(const char *s) __attribute__((pure)); |
479 | static void extract_rcsid_substr(const char *i, char *o, size_t osz); | |
480 | static void do_first_per_user_launchd_hack(void); | |
f36da725 | 481 | static size_t get_kern_max_proc(void); |
5b0a4722 A |
482 | static void do_file_init(void) __attribute__((constructor)); |
483 | ||
484 | /* file local globals */ | |
f36da725 | 485 | static bool do_apple_internal_magic; |
5b0a4722 A |
486 | static size_t total_children; |
487 | static size_t total_anon_children; | |
488 | static mach_port_t the_exception_server; | |
489 | static bool did_first_per_user_launchd_BootCache_hack; | |
490 | #define JOB_BOOTCACHE_HACK_CHECK(j) (j->per_user && !did_first_per_user_launchd_BootCache_hack && (j->mach_uid >= 500) && (j->mach_uid != (uid_t)-2)) | |
491 | static jobmgr_t background_jobmgr; | |
492 | static job_t workaround_5477111; | |
493 | static mach_timebase_info_data_t tbi; | |
494 | ||
495 | /* process wide globals */ | |
496 | mach_port_t inherited_bootstrap_port; | |
497 | jobmgr_t root_jobmgr; | |
ed34e3c3 | 498 | |
ed34e3c3 A |
499 | |
500 | void | |
5b0a4722 | 501 | job_ignore(job_t j) |
ed34e3c3 | 502 | { |
5b0a4722 | 503 | struct semaphoreitem *si; |
ed34e3c3 A |
504 | struct socketgroup *sg; |
505 | struct machservice *ms; | |
ed34e3c3 | 506 | |
5b0a4722 A |
507 | if (j->currently_ignored) { |
508 | return; | |
509 | } | |
510 | ||
511 | job_log(j, LOG_DEBUG, "Ignoring..."); | |
512 | ||
513 | j->currently_ignored = true; | |
514 | ||
515 | if (j->poll_for_vfs_changes) { | |
516 | j->poll_for_vfs_changes = false; | |
517 | job_assumes(j, kevent_mod((uintptr_t)&j->semaphores, EVFILT_TIMER, EV_DELETE, 0, 0, j) != -1); | |
518 | } | |
519 | ||
520 | SLIST_FOREACH(sg, &j->sockets, sle) { | |
ed34e3c3 | 521 | socketgroup_ignore(j, sg); |
5b0a4722 | 522 | } |
ed34e3c3 | 523 | |
5b0a4722 A |
524 | SLIST_FOREACH(ms, &j->machservices, sle) { |
525 | machservice_ignore(j, ms); | |
526 | } | |
ed34e3c3 | 527 | |
5b0a4722 A |
528 | SLIST_FOREACH(si, &j->semaphores, sle) { |
529 | semaphoreitem_ignore(j, si); | |
530 | } | |
ed34e3c3 A |
531 | } |
532 | ||
533 | void | |
5b0a4722 | 534 | job_watch(job_t j) |
ed34e3c3 | 535 | { |
5b0a4722 | 536 | struct semaphoreitem *si; |
ed34e3c3 A |
537 | struct socketgroup *sg; |
538 | struct machservice *ms; | |
ed34e3c3 | 539 | |
5b0a4722 A |
540 | if (!j->currently_ignored) { |
541 | return; | |
542 | } | |
543 | ||
544 | job_log(j, LOG_DEBUG, "Watching..."); | |
545 | ||
546 | j->currently_ignored = false; | |
547 | ||
548 | SLIST_FOREACH(sg, &j->sockets, sle) { | |
ed34e3c3 | 549 | socketgroup_watch(j, sg); |
5b0a4722 | 550 | } |
ed34e3c3 | 551 | |
5b0a4722 A |
552 | SLIST_FOREACH(ms, &j->machservices, sle) { |
553 | machservice_watch(j, ms); | |
554 | } | |
ed34e3c3 | 555 | |
5b0a4722 A |
556 | SLIST_FOREACH(si, &j->semaphores, sle) { |
557 | semaphoreitem_watch(j, si); | |
558 | } | |
ed34e3c3 A |
559 | } |
560 | ||
561 | void | |
5b0a4722 | 562 | job_stop(job_t j) |
ed34e3c3 | 563 | { |
5b0a4722 A |
564 | if (!j->p || j->anonymous) { |
565 | return; | |
566 | } | |
ed34e3c3 | 567 | |
5b0a4722 A |
568 | job_assumes(j, runtime_kill(j->p, SIGTERM) != -1); |
569 | j->sent_sigterm_time = mach_absolute_time(); | |
570 | ||
571 | if (j->exit_timeout) { | |
572 | job_assumes(j, kevent_mod((uintptr_t)&j->exit_timeout, EVFILT_TIMER, | |
573 | EV_ADD|EV_ONESHOT, NOTE_SECONDS, j->exit_timeout, j) != -1); | |
574 | } | |
575 | ||
576 | job_log(j, LOG_DEBUG, "Sent SIGTERM signal"); | |
ed34e3c3 A |
577 | } |
578 | ||
579 | launch_data_t | |
5b0a4722 | 580 | job_export(job_t j) |
ed34e3c3 A |
581 | { |
582 | launch_data_t tmp, tmp2, tmp3, r = launch_data_alloc(LAUNCH_DATA_DICTIONARY); | |
583 | ||
5b0a4722 | 584 | if (r == NULL) { |
ed34e3c3 | 585 | return NULL; |
5b0a4722 | 586 | } |
ed34e3c3 | 587 | |
5b0a4722 | 588 | if ((tmp = launch_data_new_string(j->label))) { |
ed34e3c3 | 589 | launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_LABEL); |
5b0a4722 A |
590 | } |
591 | if ((tmp = launch_data_new_string(j->mgr->name))) { | |
592 | launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE); | |
593 | } | |
594 | if ((tmp = launch_data_new_bool(j->ondemand))) { | |
ed34e3c3 | 595 | launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_ONDEMAND); |
5b0a4722 A |
596 | } |
597 | if ((tmp = launch_data_new_integer(j->last_exit_status))) { | |
ed34e3c3 | 598 | launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_LASTEXITSTATUS); |
5b0a4722 A |
599 | } |
600 | if (j->p && (tmp = launch_data_new_integer(j->p))) { | |
ed34e3c3 | 601 | launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_PID); |
5b0a4722 A |
602 | } |
603 | if ((tmp = launch_data_new_integer(j->timeout))) { | |
ed34e3c3 | 604 | launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_TIMEOUT); |
5b0a4722 A |
605 | } |
606 | if (j->prog && (tmp = launch_data_new_string(j->prog))) { | |
ed34e3c3 | 607 | launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_PROGRAM); |
5b0a4722 A |
608 | } |
609 | if (j->stdoutpath && (tmp = launch_data_new_string(j->stdoutpath))) { | |
ed34e3c3 | 610 | launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_STANDARDOUTPATH); |
5b0a4722 A |
611 | } |
612 | if (j->stderrpath && (tmp = launch_data_new_string(j->stderrpath))) { | |
ed34e3c3 | 613 | launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_STANDARDERRORPATH); |
5b0a4722 | 614 | } |
ed34e3c3 A |
615 | if (j->argv && (tmp = launch_data_alloc(LAUNCH_DATA_ARRAY))) { |
616 | int i; | |
617 | ||
618 | for (i = 0; i < j->argc; i++) { | |
5b0a4722 | 619 | if ((tmp2 = launch_data_new_string(j->argv[i]))) { |
ed34e3c3 | 620 | launch_data_array_set_index(tmp, tmp2, i); |
5b0a4722 | 621 | } |
ed34e3c3 A |
622 | } |
623 | ||
624 | launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_PROGRAMARGUMENTS); | |
625 | } | |
626 | ||
5b0a4722 A |
627 | if (j->session_create && (tmp = launch_data_new_bool(true))) { |
628 | launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_SESSIONCREATE); | |
629 | } | |
630 | ||
ed34e3c3 | 631 | if (j->inetcompat && (tmp = launch_data_alloc(LAUNCH_DATA_DICTIONARY))) { |
5b0a4722 | 632 | if ((tmp2 = launch_data_new_bool(j->inetcompat_wait))) { |
ed34e3c3 | 633 | launch_data_dict_insert(tmp, tmp2, LAUNCH_JOBINETDCOMPATIBILITY_WAIT); |
5b0a4722 | 634 | } |
ed34e3c3 A |
635 | launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_INETDCOMPATIBILITY); |
636 | } | |
637 | ||
638 | if (!SLIST_EMPTY(&j->sockets) && (tmp = launch_data_alloc(LAUNCH_DATA_DICTIONARY))) { | |
639 | struct socketgroup *sg; | |
640 | int i; | |
641 | ||
642 | SLIST_FOREACH(sg, &j->sockets, sle) { | |
5b0a4722 A |
643 | if (sg->junkfds) { |
644 | continue; | |
645 | } | |
ed34e3c3 A |
646 | if ((tmp2 = launch_data_alloc(LAUNCH_DATA_ARRAY))) { |
647 | for (i = 0; i < sg->fd_cnt; i++) { | |
5b0a4722 | 648 | if ((tmp3 = launch_data_new_fd(sg->fds[i]))) { |
ed34e3c3 | 649 | launch_data_array_set_index(tmp2, tmp3, i); |
5b0a4722 | 650 | } |
ed34e3c3 A |
651 | } |
652 | launch_data_dict_insert(tmp, tmp2, sg->name); | |
653 | } | |
654 | } | |
655 | ||
656 | launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_SOCKETS); | |
657 | } | |
658 | ||
659 | if (!SLIST_EMPTY(&j->machservices) && (tmp = launch_data_alloc(LAUNCH_DATA_DICTIONARY))) { | |
660 | struct machservice *ms; | |
5b0a4722 A |
661 | |
662 | tmp3 = NULL; | |
ed34e3c3 A |
663 | |
664 | SLIST_FOREACH(ms, &j->machservices, sle) { | |
5b0a4722 A |
665 | if (ms->per_pid) { |
666 | if (tmp3 == NULL) { | |
667 | tmp3 = launch_data_alloc(LAUNCH_DATA_DICTIONARY); | |
668 | } | |
669 | if (tmp3) { | |
670 | tmp2 = launch_data_new_machport(MACH_PORT_NULL); | |
671 | launch_data_dict_insert(tmp3, tmp2, ms->name); | |
672 | } | |
673 | } else { | |
674 | tmp2 = launch_data_new_machport(MACH_PORT_NULL); | |
675 | launch_data_dict_insert(tmp, tmp2, ms->name); | |
676 | } | |
ed34e3c3 A |
677 | } |
678 | ||
679 | launch_data_dict_insert(r, tmp, LAUNCH_JOBKEY_MACHSERVICES); | |
5b0a4722 A |
680 | |
681 | if (tmp3) { | |
682 | launch_data_dict_insert(r, tmp3, LAUNCH_JOBKEY_PERJOBMACHSERVICES); | |
683 | } | |
684 | } | |
685 | ||
686 | return r; | |
687 | } | |
688 | ||
689 | static void | |
690 | jobmgr_log_active_jobs(jobmgr_t jm) | |
691 | { | |
692 | const char *why_active; | |
693 | jobmgr_t jmi; | |
694 | job_t ji; | |
695 | ||
696 | SLIST_FOREACH(jmi, &jm->submgrs, sle) { | |
697 | jobmgr_log_active_jobs(jmi); | |
698 | } | |
699 | ||
700 | LIST_FOREACH(ji, &jm->jobs, sle) { | |
701 | why_active = job_active(ji); | |
702 | ||
703 | job_log(ji, LOG_DEBUG, "%s", why_active ? why_active : "Inactive"); | |
ed34e3c3 A |
704 | } |
705 | ||
5b0a4722 A |
706 | } |
707 | ||
708 | static void | |
709 | still_alive_with_check(void) | |
710 | { | |
711 | jobmgr_log(root_jobmgr, LOG_NOTICE, "Still alive with %lu/%lu children", total_children, total_anon_children); | |
712 | ||
713 | jobmgr_log_active_jobs(root_jobmgr); | |
714 | ||
715 | runtime_closelog(); /* hack to flush logs */ | |
716 | } | |
717 | ||
718 | jobmgr_t | |
719 | jobmgr_shutdown(jobmgr_t jm) | |
720 | { | |
721 | jobmgr_t jmi, jmn; | |
722 | job_t ji; | |
723 | ||
724 | jobmgr_log(jm, LOG_DEBUG, "Beginning job manager shutdown with flags: %s", reboot_flags_to_C_names(jm->reboot_flags)); | |
725 | ||
726 | jm->shutting_down = true; | |
727 | ||
728 | SLIST_FOREACH_SAFE(jmi, &jm->submgrs, sle, jmn) { | |
729 | jobmgr_shutdown(jmi); | |
730 | } | |
ed34e3c3 | 731 | |
5b0a4722 A |
732 | if (jm->hopefully_first_cnt) { |
733 | LIST_FOREACH(ji, &jm->jobs, sle) { | |
734 | if (ji->p && ji->hopefully_exits_first) { | |
735 | job_stop(ji); | |
736 | } | |
ed34e3c3 | 737 | } |
5b0a4722 | 738 | } |
ed34e3c3 | 739 | |
5b0a4722 A |
740 | if (debug_shutdown_hangs && jm->parentmgr == NULL && getpid() == 1) { |
741 | runtime_set_timeout(still_alive_with_check, 5); | |
ed34e3c3 A |
742 | } |
743 | ||
5b0a4722 | 744 | return jobmgr_do_garbage_collection(jm); |
ed34e3c3 A |
745 | } |
746 | ||
747 | void | |
5b0a4722 | 748 | jobmgr_remove(jobmgr_t jm) |
ed34e3c3 | 749 | { |
5b0a4722 A |
750 | jobmgr_t jmi; |
751 | job_t ji; | |
ed34e3c3 | 752 | |
5b0a4722 | 753 | jobmgr_log(jm, LOG_DEBUG, "Removed job manager"); |
ed34e3c3 | 754 | |
5b0a4722 A |
755 | if (!jobmgr_assumes(jm, SLIST_EMPTY(&jm->submgrs))) { |
756 | while ((jmi = SLIST_FIRST(&jm->submgrs))) { | |
757 | jobmgr_remove(jmi); | |
758 | } | |
759 | } | |
760 | ||
761 | while ((ji = LIST_FIRST(&jm->jobs))) { | |
762 | /* We should only have anonymous jobs left */ | |
763 | job_assumes(ji, ji->anonymous); | |
764 | job_remove(ji); | |
765 | } | |
766 | ||
767 | if (jm->req_port) { | |
768 | jobmgr_assumes(jm, launchd_mport_deallocate(jm->req_port) == KERN_SUCCESS); | |
769 | } | |
770 | ||
771 | if (jm->jm_port) { | |
772 | jobmgr_assumes(jm, launchd_mport_close_recv(jm->jm_port) == KERN_SUCCESS); | |
773 | } | |
774 | ||
775 | if (jm == background_jobmgr) { | |
776 | background_jobmgr = NULL; | |
ed34e3c3 | 777 | } |
5b0a4722 A |
778 | |
779 | if (jm->parentmgr) { | |
780 | runtime_del_ref(); | |
781 | SLIST_REMOVE(&jm->parentmgr->submgrs, jm, jobmgr_s, sle); | |
782 | } else if (getpid() == 1) { | |
783 | jobmgr_log(jm, LOG_DEBUG, "About to call: reboot(%s)", reboot_flags_to_C_names(jm->reboot_flags)); | |
784 | runtime_closelog(); | |
785 | jobmgr_assumes(jm, reboot(jm->reboot_flags) != -1); | |
786 | runtime_closelog(); | |
787 | } else { | |
788 | runtime_closelog(); | |
789 | jobmgr_log(jm, LOG_DEBUG, "About to exit"); | |
790 | exit(EXIT_SUCCESS); | |
791 | } | |
792 | ||
793 | free(jm); | |
ed34e3c3 A |
794 | } |
795 | ||
796 | void | |
5b0a4722 | 797 | job_remove(job_t j) |
ed34e3c3 | 798 | { |
5b0a4722 | 799 | struct waiting_for_removal *w4r; |
ed34e3c3 | 800 | struct calendarinterval *ci; |
5b0a4722 | 801 | struct semaphoreitem *si; |
ed34e3c3 | 802 | struct socketgroup *sg; |
5b0a4722 | 803 | struct machservice *ms; |
ed34e3c3 | 804 | struct limititem *li; |
5b0a4722 | 805 | struct mspolicy *msp; |
ed34e3c3 | 806 | struct envitem *ei; |
ed34e3c3 | 807 | |
5b0a4722 A |
808 | if (j->p && j->anonymous) { |
809 | job_reap(j); | |
810 | } else if (j->p) { | |
811 | job_log(j, LOG_DEBUG, "Removal pended until the job exits"); | |
ed34e3c3 | 812 | |
5b0a4722 A |
813 | if (!j->removal_pending) { |
814 | j->removal_pending = true; | |
ed34e3c3 A |
815 | job_stop(j); |
816 | } | |
5b0a4722 A |
817 | |
818 | return; | |
ed34e3c3 A |
819 | } |
820 | ||
5b0a4722 | 821 | ipc_close_all_with_job(j); |
ed34e3c3 | 822 | |
5b0a4722 A |
823 | if (j->forced_peers_to_demand_mode) { |
824 | job_set_global_on_demand(j, false); | |
825 | } | |
ed34e3c3 | 826 | |
5b0a4722 A |
827 | if (!job_assumes(j, j->forkfd == 0)) { |
828 | job_assumes(j, runtime_close(j->forkfd) != -1); | |
ed34e3c3 A |
829 | } |
830 | ||
5b0a4722 A |
831 | if (!job_assumes(j, j->log_redirect_fd == 0)) { |
832 | job_assumes(j, runtime_close(j->log_redirect_fd) != -1); | |
833 | } | |
ed34e3c3 | 834 | |
5b0a4722 A |
835 | if (j->j_port) { |
836 | job_assumes(j, launchd_mport_close_recv(j->j_port) == KERN_SUCCESS); | |
ed34e3c3 | 837 | } |
ed34e3c3 | 838 | |
5b0a4722 A |
839 | if (!job_assumes(j, j->wait_reply_port == MACH_PORT_NULL)) { |
840 | job_assumes(j, launchd_mport_deallocate(j->wait_reply_port) == KERN_SUCCESS); | |
841 | } | |
ed34e3c3 | 842 | |
5b0a4722 A |
843 | while ((msp = SLIST_FIRST(&j->mspolicies))) { |
844 | mspolicy_delete(j, msp); | |
845 | } | |
846 | while ((sg = SLIST_FIRST(&j->sockets))) { | |
ed34e3c3 | 847 | socketgroup_delete(j, sg); |
5b0a4722 A |
848 | } |
849 | while ((ci = SLIST_FIRST(&j->cal_intervals))) { | |
ed34e3c3 | 850 | calendarinterval_delete(j, ci); |
5b0a4722 A |
851 | } |
852 | while ((ei = SLIST_FIRST(&j->env))) { | |
ed34e3c3 | 853 | envitem_delete(j, ei, false); |
5b0a4722 A |
854 | } |
855 | while ((ei = SLIST_FIRST(&j->global_env))) { | |
ed34e3c3 | 856 | envitem_delete(j, ei, true); |
5b0a4722 A |
857 | } |
858 | while ((li = SLIST_FIRST(&j->limits))) { | |
ed34e3c3 | 859 | limititem_delete(j, li); |
5b0a4722 A |
860 | } |
861 | while ((ms = SLIST_FIRST(&j->machservices))) { | |
862 | machservice_delete(j, ms, false); | |
863 | } | |
864 | while ((si = SLIST_FIRST(&j->semaphores))) { | |
ed34e3c3 | 865 | semaphoreitem_delete(j, si); |
5b0a4722 A |
866 | } |
867 | while ((w4r = SLIST_FIRST(&j->removal_watchers))) { | |
868 | waiting4removal_delete(j, w4r); | |
869 | } | |
ed34e3c3 | 870 | |
5b0a4722 | 871 | if (j->prog) { |
ed34e3c3 | 872 | free(j->prog); |
5b0a4722 A |
873 | } |
874 | if (j->argv) { | |
ed34e3c3 | 875 | free(j->argv); |
5b0a4722 A |
876 | } |
877 | if (j->rootdir) { | |
ed34e3c3 | 878 | free(j->rootdir); |
5b0a4722 A |
879 | } |
880 | if (j->workingdir) { | |
ed34e3c3 | 881 | free(j->workingdir); |
5b0a4722 A |
882 | } |
883 | if (j->username) { | |
ed34e3c3 | 884 | free(j->username); |
5b0a4722 A |
885 | } |
886 | if (j->groupname) { | |
ed34e3c3 | 887 | free(j->groupname); |
5b0a4722 A |
888 | } |
889 | if (j->stdoutpath) { | |
ed34e3c3 | 890 | free(j->stdoutpath); |
5b0a4722 A |
891 | } |
892 | if (j->stderrpath) { | |
ed34e3c3 | 893 | free(j->stderrpath); |
5b0a4722 | 894 | } |
fe044cc9 A |
895 | if (j->alt_exc_handler) { |
896 | free(j->alt_exc_handler); | |
897 | } | |
f36da725 | 898 | #if HAVE_SANDBOX |
5b0a4722 A |
899 | if (j->seatbelt_profile) { |
900 | free(j->seatbelt_profile); | |
901 | } | |
f36da725 A |
902 | #endif |
903 | #if HAVE_QUARANTINE | |
5b0a4722 A |
904 | if (j->quarantine_data) { |
905 | free(j->quarantine_data); | |
906 | } | |
f36da725 | 907 | #endif |
5b0a4722 A |
908 | if (j->j_binpref) { |
909 | free(j->j_binpref); | |
910 | } | |
911 | if (j->start_interval) { | |
912 | runtime_del_ref(); | |
913 | job_assumes(j, kevent_mod((uintptr_t)&j->start_interval, EVFILT_TIMER, EV_DELETE, 0, 0, NULL) != -1); | |
914 | } | |
915 | if (j->poll_for_vfs_changes) { | |
916 | job_assumes(j, kevent_mod((uintptr_t)&j->semaphores, EVFILT_TIMER, EV_DELETE, 0, 0, j) != -1); | |
917 | } | |
ed34e3c3 A |
918 | |
919 | kevent_mod((uintptr_t)j, EVFILT_TIMER, EV_DELETE, 0, 0, NULL); | |
5b0a4722 A |
920 | |
921 | LIST_REMOVE(j, sle); | |
922 | LIST_REMOVE(j, label_hash_sle); | |
923 | ||
924 | job_log(j, LOG_DEBUG, "Removed"); | |
925 | ||
ed34e3c3 A |
926 | free(j); |
927 | } | |
928 | ||
929 | void | |
930 | socketgroup_setup(launch_data_t obj, const char *key, void *context) | |
931 | { | |
932 | launch_data_t tmp_oai; | |
5b0a4722 | 933 | job_t j = context; |
ed34e3c3 A |
934 | unsigned int i, fd_cnt = 1; |
935 | int *fds; | |
936 | ||
5b0a4722 | 937 | if (launch_data_get_type(obj) == LAUNCH_DATA_ARRAY) { |
ed34e3c3 | 938 | fd_cnt = launch_data_array_get_count(obj); |
5b0a4722 | 939 | } |
ed34e3c3 A |
940 | |
941 | fds = alloca(fd_cnt * sizeof(int)); | |
942 | ||
943 | for (i = 0; i < fd_cnt; i++) { | |
5b0a4722 | 944 | if (launch_data_get_type(obj) == LAUNCH_DATA_ARRAY) { |
ed34e3c3 | 945 | tmp_oai = launch_data_array_get_index(obj, i); |
5b0a4722 | 946 | } else { |
ed34e3c3 | 947 | tmp_oai = obj; |
5b0a4722 | 948 | } |
ed34e3c3 A |
949 | |
950 | fds[i] = launch_data_get_fd(tmp_oai); | |
951 | } | |
952 | ||
953 | socketgroup_new(j, key, fds, fd_cnt, strcmp(key, LAUNCH_JOBKEY_BONJOURFDS) == 0); | |
954 | ||
955 | ipc_revoke_fds(obj); | |
956 | } | |
957 | ||
958 | bool | |
5b0a4722 A |
959 | job_set_global_on_demand(job_t j, bool val) |
960 | { | |
961 | if (j->forced_peers_to_demand_mode && val) { | |
962 | return false; | |
963 | } else if (!j->forced_peers_to_demand_mode && !val) { | |
964 | return false; | |
965 | } | |
966 | ||
967 | if ((j->forced_peers_to_demand_mode = val)) { | |
968 | j->mgr->global_on_demand_cnt++; | |
969 | } else { | |
970 | j->mgr->global_on_demand_cnt--; | |
971 | } | |
972 | ||
973 | if (j->mgr->global_on_demand_cnt == 0) { | |
974 | jobmgr_dispatch_all(j->mgr, false); | |
975 | } | |
976 | ||
977 | return true; | |
978 | } | |
979 | ||
980 | bool | |
981 | job_setup_machport(job_t j) | |
ed34e3c3 | 982 | { |
5b0a4722 A |
983 | mach_msg_size_t mxmsgsz; |
984 | ||
985 | if (!job_assumes(j, launchd_mport_create_recv(&j->j_port) == KERN_SUCCESS)) { | |
ed34e3c3 | 986 | goto out_bad; |
5b0a4722 A |
987 | } |
988 | ||
989 | /* Sigh... at the moment, MIG has maxsize == sizeof(reply union) */ | |
990 | mxmsgsz = sizeof(union __RequestUnion__job_mig_protocol_vproc_subsystem); | |
991 | if (job_mig_protocol_vproc_subsystem.maxsize > mxmsgsz) { | |
992 | mxmsgsz = job_mig_protocol_vproc_subsystem.maxsize; | |
993 | } | |
ed34e3c3 | 994 | |
5b0a4722 | 995 | if (!job_assumes(j, runtime_add_mport(j->j_port, protocol_vproc_server, mxmsgsz) == KERN_SUCCESS)) { |
ed34e3c3 | 996 | goto out_bad2; |
5b0a4722 A |
997 | } |
998 | ||
999 | if (!job_assumes(j, launchd_mport_notify_req(j->j_port, MACH_NOTIFY_NO_SENDERS) == KERN_SUCCESS)) { | |
1000 | job_assumes(j, launchd_mport_close_recv(j->j_port) == KERN_SUCCESS); | |
1001 | goto out_bad; | |
1002 | } | |
ed34e3c3 A |
1003 | |
1004 | return true; | |
1005 | out_bad2: | |
5b0a4722 | 1006 | job_assumes(j, launchd_mport_close_recv(j->j_port) == KERN_SUCCESS); |
ed34e3c3 A |
1007 | out_bad: |
1008 | return false; | |
1009 | } | |
1010 | ||
5b0a4722 A |
1011 | job_t |
1012 | job_new_via_mach_init(job_t j, const char *cmd, uid_t uid, bool ond) | |
ed34e3c3 A |
1013 | { |
1014 | const char **argv = (const char **)mach_cmd2argv(cmd); | |
5b0a4722 | 1015 | job_t jr = NULL; |
ed34e3c3 | 1016 | |
5b0a4722 | 1017 | if (!job_assumes(j, argv != NULL)) { |
ed34e3c3 | 1018 | goto out_bad; |
5b0a4722 | 1019 | } |
ed34e3c3 | 1020 | |
5b0a4722 | 1021 | jr = job_new(j->mgr, AUTO_PICK_LEGACY_LABEL, NULL, argv); |
ed34e3c3 A |
1022 | |
1023 | free(argv); | |
1024 | ||
5b0a4722 A |
1025 | /* jobs can easily be denied creation during shutdown */ |
1026 | if (!jr) { | |
ed34e3c3 | 1027 | goto out_bad; |
5b0a4722 | 1028 | } |
ed34e3c3 | 1029 | |
5b0a4722 A |
1030 | jr->mach_uid = uid; |
1031 | jr->ondemand = ond; | |
1032 | jr->legacy_mach_job = true; | |
1033 | jr->abandon_pg = true; | |
1034 | jr->priv_port_has_senders = true; /* the IPC that called us will make-send on this port */ | |
ed34e3c3 | 1035 | |
5b0a4722 | 1036 | if (!job_setup_machport(jr)) { |
ed34e3c3 A |
1037 | goto out_bad; |
1038 | } | |
1039 | ||
5b0a4722 | 1040 | job_log(jr, LOG_INFO, "Legacy%s server created", ond ? " on-demand" : ""); |
ed34e3c3 | 1041 | |
5b0a4722 | 1042 | return jr; |
ed34e3c3 A |
1043 | |
1044 | out_bad: | |
5b0a4722 A |
1045 | if (jr) { |
1046 | job_remove(jr); | |
1047 | } | |
ed34e3c3 A |
1048 | return NULL; |
1049 | } | |
1050 | ||
1051 | kern_return_t | |
5b0a4722 | 1052 | job_handle_mpm_wait(job_t j, mach_port_t srp, int *waitstatus) |
ed34e3c3 A |
1053 | { |
1054 | if (j->p) { | |
1055 | j->wait_reply_port = srp; | |
1056 | return MIG_NO_REPLY; | |
1057 | } | |
1058 | ||
1059 | *waitstatus = j->last_exit_status; | |
1060 | ||
1061 | return 0; | |
1062 | } | |
1063 | ||
5b0a4722 A |
1064 | job_t |
1065 | job_new_anonymous(jobmgr_t jm, pid_t anonpid) | |
ed34e3c3 | 1066 | { |
5b0a4722 A |
1067 | int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, anonpid }; |
1068 | struct kinfo_proc kp; | |
1069 | size_t len = sizeof(kp); | |
1070 | const char *zombie = NULL; | |
1071 | bool shutdown_state; | |
1072 | job_t jp = NULL, jr = NULL; | |
1073 | ||
1074 | if (!jobmgr_assumes(jm, anonpid != 0)) { | |
ed34e3c3 A |
1075 | return NULL; |
1076 | } | |
5b0a4722 A |
1077 | |
1078 | if (!jobmgr_assumes(jm, anonpid < 100000)) { | |
1079 | /* The kernel current defines PID_MAX to be 99999, but that define isn't exported */ | |
ed34e3c3 | 1080 | return NULL; |
5b0a4722 | 1081 | } |
ed34e3c3 | 1082 | |
5b0a4722 A |
1083 | if (!jobmgr_assumes(jm, sysctl(mib, 4, &kp, &len, NULL, 0) != -1)) { |
1084 | return NULL; | |
1085 | } | |
ed34e3c3 | 1086 | |
5b0a4722 A |
1087 | if (len != sizeof(kp)) { |
1088 | jobmgr_log(jm, LOG_DEBUG, "Tried to create an anonymous job for nonexistent PID: %u", anonpid); | |
ed34e3c3 A |
1089 | return NULL; |
1090 | } | |
1091 | ||
5b0a4722 A |
1092 | if (!jobmgr_assumes(jm, kp.kp_proc.p_comm[0] != '\0')) { |
1093 | return NULL; | |
1094 | } | |
ed34e3c3 | 1095 | |
5b0a4722 A |
1096 | if (kp.kp_proc.p_stat == SZOMB) { |
1097 | jobmgr_log(jm, LOG_DEBUG, "Tried to create an anonymous job for zombie PID: %u", anonpid); | |
1098 | zombie = "zombie"; | |
ed34e3c3 A |
1099 | } |
1100 | ||
5b0a4722 A |
1101 | switch (kp.kp_eproc.e_ppid) { |
1102 | case 0: | |
1103 | /* the kernel */ | |
1104 | break; | |
1105 | case 1: | |
1106 | if (getpid() != 1) { | |
1107 | /* we cannot possibly find a parent job_t that is useful in this function */ | |
1108 | break; | |
1109 | } | |
1110 | /* fall through */ | |
1111 | default: | |
1112 | jp = jobmgr_find_by_pid(jm, kp.kp_eproc.e_ppid, true); | |
1113 | jobmgr_assumes(jm, jp != NULL); | |
1114 | break; | |
1115 | } | |
1116 | ||
1117 | /* A total hack: Normally, job_new() returns an error during shutdown, but anonymous jobs are special. */ | |
1118 | if ((shutdown_state = jm->shutting_down)) { | |
1119 | jm->shutting_down = false; | |
1120 | } | |
1121 | ||
1122 | if (jobmgr_assumes(jm, (jr = job_new(jm, AUTO_PICK_LEGACY_LABEL, zombie ? zombie : kp.kp_proc.p_comm, NULL)) != NULL)) { | |
1123 | u_int proc_fflags = NOTE_EXEC|NOTE_EXIT /* |NOTE_REAP */; | |
1124 | ||
1125 | total_anon_children++; | |
1126 | jr->anonymous = true; | |
1127 | jr->p = anonpid; | |
1128 | ||
1129 | /* anonymous process reaping is messy */ | |
1130 | LIST_INSERT_HEAD(&jm->active_jobs[ACTIVE_JOB_HASH(jr->p)], jr, pid_hash_sle); | |
1131 | ||
1132 | if (kevent_mod(jr->p, EVFILT_PROC, EV_ADD, proc_fflags, 0, root_jobmgr) == -1 && job_assumes(jr, errno == ESRCH)) { | |
1133 | /* zombies are weird */ | |
1134 | job_log(jr, LOG_ERR, "Failed to add kevent for PID %u. Will unload at MIG return", jr->p); | |
1135 | jr->unload_at_mig_return = true; | |
1136 | } | |
1137 | ||
1138 | if (jp) { | |
1139 | job_assumes(jr, mspolicy_copy(jr, jp)); | |
1140 | } | |
1141 | ||
1142 | if (shutdown_state && jm->hopefully_first_cnt == 0) { | |
1143 | job_log(jr, LOG_APPLEONLY, "This process showed up to the party while all the guests were leaving. Odds are that it will have a miserable time"); | |
ed34e3c3 | 1144 | } |
5b0a4722 A |
1145 | |
1146 | job_log(jr, LOG_DEBUG, "Created PID %u anonymously by PPID %u%s%s", anonpid, kp.kp_eproc.e_ppid, jp ? ": " : "", jp ? jp->label : ""); | |
ed34e3c3 A |
1147 | } |
1148 | ||
5b0a4722 A |
1149 | if (shutdown_state) { |
1150 | jm->shutting_down = true; | |
1151 | } | |
ed34e3c3 A |
1152 | |
1153 | return jr; | |
1154 | } | |
1155 | ||
5b0a4722 A |
1156 | job_t |
1157 | job_new(jobmgr_t jm, const char *label, const char *prog, const char *const *argv) | |
ed34e3c3 A |
1158 | { |
1159 | const char *const *argv_tmp = argv; | |
5b0a4722 A |
1160 | char auto_label[1000]; |
1161 | const char *bn = NULL; | |
ed34e3c3 | 1162 | char *co; |
5b0a4722 | 1163 | size_t minlabel_len; |
ed34e3c3 | 1164 | int i, cc = 0; |
5b0a4722 A |
1165 | job_t j; |
1166 | ||
1167 | launchd_assert(offsetof(struct job_s, kqjob_callback) == 0); | |
1168 | ||
1169 | if (jm->shutting_down) { | |
1170 | errno = EINVAL; | |
1171 | return NULL; | |
1172 | } | |
ed34e3c3 | 1173 | |
5b0a4722 | 1174 | if (prog == NULL && argv == NULL) { |
ed34e3c3 A |
1175 | errno = EINVAL; |
1176 | return NULL; | |
1177 | } | |
1178 | ||
5b0a4722 A |
1179 | if (label == AUTO_PICK_LEGACY_LABEL) { |
1180 | bn = prog ? prog : basename((char *)argv[0]); /* prog for auto labels is kp.kp_kproc.p_comm */ | |
1181 | snprintf(auto_label, sizeof(auto_label), "%s.%s", sizeof(void *) == 8 ? "0xdeadbeeffeedface" : "0xbabecafe", bn); | |
1182 | label = auto_label; | |
1183 | /* This is so we can do gross things later. See NOTE_EXEC for anonymous jobs */ | |
1184 | minlabel_len = strlen(label) + MAXCOMLEN; | |
1185 | } else { | |
1186 | minlabel_len = strlen(label); | |
1187 | } | |
1188 | ||
1189 | j = calloc(1, sizeof(struct job_s) + minlabel_len + 1); | |
ed34e3c3 | 1190 | |
5b0a4722 A |
1191 | if (!jobmgr_assumes(jm, j != NULL)) { |
1192 | return NULL; | |
1193 | } | |
ed34e3c3 | 1194 | |
5b0a4722 A |
1195 | if (label == auto_label) { |
1196 | snprintf((char *)j->label, strlen(label) + 1, "%p.%s", j, bn); | |
1197 | } else { | |
1198 | strcpy((char *)j->label, label); | |
1199 | } | |
ed34e3c3 | 1200 | j->kqjob_callback = job_callback; |
5b0a4722 A |
1201 | j->mgr = jm; |
1202 | j->min_run_time = LAUNCHD_MIN_JOB_RUN_TIME; | |
1203 | j->timeout = RUNTIME_ADVISABLE_IDLE_TIMEOUT; | |
1204 | j->exit_timeout = LAUNCHD_DEFAULT_EXIT_TIMEOUT; | |
1205 | j->currently_ignored = true; | |
ed34e3c3 A |
1206 | j->ondemand = true; |
1207 | j->checkedin = true; | |
ed34e3c3 A |
1208 | |
1209 | if (prog) { | |
1210 | j->prog = strdup(prog); | |
5b0a4722 | 1211 | if (!job_assumes(j, j->prog != NULL)) { |
ed34e3c3 | 1212 | goto out_bad; |
5b0a4722 | 1213 | } |
ed34e3c3 A |
1214 | } |
1215 | ||
1216 | if (argv) { | |
1217 | while (*argv_tmp++) | |
1218 | j->argc++; | |
1219 | ||
5b0a4722 | 1220 | for (i = 0; i < j->argc; i++) { |
ed34e3c3 | 1221 | cc += strlen(argv[i]) + 1; |
5b0a4722 | 1222 | } |
ed34e3c3 A |
1223 | |
1224 | j->argv = malloc((j->argc + 1) * sizeof(char *) + cc); | |
1225 | ||
5b0a4722 | 1226 | if (!job_assumes(j, j->argv != NULL)) { |
ed34e3c3 | 1227 | goto out_bad; |
5b0a4722 | 1228 | } |
ed34e3c3 A |
1229 | |
1230 | co = ((char *)j->argv) + ((j->argc + 1) * sizeof(char *)); | |
1231 | ||
1232 | for (i = 0; i < j->argc; i++) { | |
1233 | j->argv[i] = co; | |
1234 | strcpy(co, argv[i]); | |
1235 | co += strlen(argv[i]) + 1; | |
1236 | } | |
1237 | j->argv[i] = NULL; | |
1238 | } | |
1239 | ||
5b0a4722 A |
1240 | LIST_INSERT_HEAD(&jm->jobs, j, sle); |
1241 | LIST_INSERT_HEAD(&label_hash[hash_label(j->label)], j, label_hash_sle); | |
1242 | ||
1243 | job_log(j, LOG_DEBUG, "Conceived"); | |
ed34e3c3 A |
1244 | |
1245 | return j; | |
1246 | ||
1247 | out_bad: | |
5b0a4722 A |
1248 | if (j->prog) { |
1249 | free(j->prog); | |
ed34e3c3 | 1250 | } |
5b0a4722 A |
1251 | free(j); |
1252 | ||
ed34e3c3 A |
1253 | return NULL; |
1254 | } | |
1255 | ||
5b0a4722 | 1256 | job_t |
ed34e3c3 A |
1257 | job_import(launch_data_t pload) |
1258 | { | |
5b0a4722 | 1259 | job_t j = jobmgr_import2(root_jobmgr, pload); |
ed34e3c3 | 1260 | |
5b0a4722 | 1261 | if (j == NULL) { |
ed34e3c3 | 1262 | return NULL; |
5b0a4722 | 1263 | } |
ed34e3c3 | 1264 | |
5b0a4722 | 1265 | return job_dispatch(j, false); |
ed34e3c3 A |
1266 | } |
1267 | ||
1268 | launch_data_t | |
1269 | job_import_bulk(launch_data_t pload) | |
1270 | { | |
1271 | launch_data_t resp = launch_data_alloc(LAUNCH_DATA_ARRAY); | |
5b0a4722 | 1272 | job_t *ja; |
ed34e3c3 A |
1273 | size_t i, c = launch_data_array_get_count(pload); |
1274 | ||
5b0a4722 | 1275 | ja = alloca(c * sizeof(job_t )); |
ed34e3c3 A |
1276 | |
1277 | for (i = 0; i < c; i++) { | |
5b0a4722 | 1278 | if ((ja[i] = jobmgr_import2(root_jobmgr, launch_data_array_get_index(pload, i)))) { |
ed34e3c3 | 1279 | errno = 0; |
5b0a4722 | 1280 | } |
ed34e3c3 A |
1281 | launch_data_array_set_index(resp, launch_data_new_errno(errno), i); |
1282 | } | |
1283 | ||
1284 | for (i = 0; i < c; i++) { | |
5b0a4722 | 1285 | if (ja[i] == NULL) { |
ed34e3c3 | 1286 | continue; |
5b0a4722 A |
1287 | } |
1288 | job_dispatch(ja[i], false); | |
ed34e3c3 A |
1289 | } |
1290 | ||
1291 | return resp; | |
1292 | } | |
1293 | ||
1294 | void | |
5b0a4722 | 1295 | job_import_bool(job_t j, const char *key, bool value) |
ed34e3c3 | 1296 | { |
5b0a4722 A |
1297 | bool found_key = false; |
1298 | ||
ed34e3c3 | 1299 | switch (key[0]) { |
5b0a4722 A |
1300 | case 'a': |
1301 | case 'A': | |
1302 | if (strcasecmp(key, LAUNCH_JOBKEY_ABANDONPROCESSGROUP) == 0) { | |
1303 | j->abandon_pg = value; | |
1304 | found_key = true; | |
1305 | } | |
ed34e3c3 A |
1306 | break; |
1307 | case 'k': | |
1308 | case 'K': | |
5b0a4722 | 1309 | if (strcasecmp(key, LAUNCH_JOBKEY_KEEPALIVE) == 0) { |
ed34e3c3 | 1310 | j->ondemand = !value; |
5b0a4722 A |
1311 | found_key = true; |
1312 | } | |
ed34e3c3 A |
1313 | break; |
1314 | case 'o': | |
1315 | case 'O': | |
5b0a4722 | 1316 | if (strcasecmp(key, LAUNCH_JOBKEY_ONDEMAND) == 0) { |
ed34e3c3 | 1317 | j->ondemand = value; |
5b0a4722 A |
1318 | found_key = true; |
1319 | } | |
ed34e3c3 A |
1320 | break; |
1321 | case 'd': | |
1322 | case 'D': | |
5b0a4722 | 1323 | if (strcasecmp(key, LAUNCH_JOBKEY_DEBUG) == 0) { |
ed34e3c3 | 1324 | j->debug = value; |
5b0a4722 A |
1325 | found_key = true; |
1326 | } else if (strcasecmp(key, LAUNCH_JOBKEY_DISABLED) == 0) { | |
1327 | job_assumes(j, !value); | |
1328 | found_key = true; | |
1329 | } | |
1330 | break; | |
1331 | case 'h': | |
1332 | case 'H': | |
1333 | if (strcasecmp(key, LAUNCH_JOBKEY_HOPEFULLYEXITSLAST) == 0) { | |
1334 | j->hopefully_exits_last = value; | |
1335 | found_key = true; | |
1336 | } else if (strcasecmp(key, LAUNCH_JOBKEY_HOPEFULLYEXITSFIRST) == 0) { | |
1337 | j->hopefully_exits_first = value; | |
1338 | found_key = true; | |
1339 | } | |
ed34e3c3 A |
1340 | break; |
1341 | case 's': | |
1342 | case 'S': | |
5b0a4722 | 1343 | if (strcasecmp(key, LAUNCH_JOBKEY_SESSIONCREATE) == 0) { |
ed34e3c3 | 1344 | j->session_create = value; |
5b0a4722 A |
1345 | found_key = true; |
1346 | } else if (strcasecmp(key, LAUNCH_JOBKEY_STARTONMOUNT) == 0) { | |
1347 | j->start_on_mount = value; | |
1348 | found_key = true; | |
1349 | } else if (strcasecmp(key, LAUNCH_JOBKEY_SERVICEIPC) == 0) { | |
1350 | /* this only does something on Mac OS X 10.4 "Tiger" */ | |
1351 | found_key = true; | |
1352 | } | |
ed34e3c3 A |
1353 | break; |
1354 | case 'l': | |
1355 | case 'L': | |
5b0a4722 | 1356 | if (strcasecmp(key, LAUNCH_JOBKEY_LOWPRIORITYIO) == 0) { |
ed34e3c3 | 1357 | j->low_pri_io = value; |
5b0a4722 A |
1358 | found_key = true; |
1359 | } else if (strcasecmp(key, LAUNCH_JOBKEY_LAUNCHONLYONCE) == 0) { | |
1360 | j->only_once = value; | |
1361 | found_key = true; | |
1362 | } | |
ed34e3c3 | 1363 | break; |
fe044cc9 A |
1364 | case 'm': |
1365 | case 'M': | |
1366 | if (strcasecmp(key, LAUNCH_JOBKEY_MACHEXCEPTIONHANDLER) == 0) { | |
1367 | j->internal_exc_handler = value; | |
1368 | found_key = true; | |
1369 | } | |
1370 | break; | |
ed34e3c3 A |
1371 | case 'i': |
1372 | case 'I': | |
5b0a4722 A |
1373 | if (strcasecmp(key, LAUNCH_JOBKEY_INITGROUPS) == 0) { |
1374 | if (getuid() != 0) { | |
1375 | job_log(j, LOG_WARNING, "Ignored this key: %s", key); | |
1376 | return; | |
1377 | } | |
1378 | j->no_init_groups = !value; | |
1379 | found_key = true; | |
1380 | } | |
ed34e3c3 A |
1381 | break; |
1382 | case 'r': | |
1383 | case 'R': | |
5b0a4722 A |
1384 | if (strcasecmp(key, LAUNCH_JOBKEY_RUNATLOAD) == 0) { |
1385 | if (value) { | |
1386 | /* We don't want value == false to change j->start_pending */ | |
1387 | j->start_pending = true; | |
1388 | } | |
1389 | found_key = true; | |
1390 | } | |
ed34e3c3 A |
1391 | break; |
1392 | case 'e': | |
1393 | case 'E': | |
5b0a4722 | 1394 | if (strcasecmp(key, LAUNCH_JOBKEY_ENABLEGLOBBING) == 0) { |
ed34e3c3 | 1395 | j->globargv = value; |
5b0a4722 A |
1396 | found_key = true; |
1397 | } else if (strcasecmp(key, LAUNCH_JOBKEY_ENTERKERNELDEBUGGERBEFOREKILL) == 0) { | |
1398 | j->debug_before_kill = value; | |
1399 | found_key = true; | |
1400 | } | |
ed34e3c3 A |
1401 | break; |
1402 | case 'w': | |
1403 | case 'W': | |
5b0a4722 | 1404 | if (strcasecmp(key, LAUNCH_JOBKEY_WAITFORDEBUGGER) == 0) { |
ed34e3c3 | 1405 | j->wait4debugger = value; |
5b0a4722 A |
1406 | found_key = true; |
1407 | } | |
ed34e3c3 A |
1408 | break; |
1409 | default: | |
1410 | break; | |
1411 | } | |
5b0a4722 A |
1412 | |
1413 | if (!found_key) { | |
1414 | job_log(j, LOG_WARNING, "Unknown key for boolean: %s", key); | |
1415 | } | |
ed34e3c3 A |
1416 | } |
1417 | ||
1418 | void | |
5b0a4722 | 1419 | job_import_string(job_t j, const char *key, const char *value) |
ed34e3c3 A |
1420 | { |
1421 | char **where2put = NULL; | |
ed34e3c3 A |
1422 | |
1423 | switch (key[0]) { | |
fe044cc9 A |
1424 | case 'm': |
1425 | case 'M': | |
1426 | if (strcasecmp(key, LAUNCH_JOBKEY_MACHEXCEPTIONHANDLER) == 0) { | |
1427 | where2put = &j->alt_exc_handler; | |
1428 | } | |
1429 | break; | |
ed34e3c3 A |
1430 | case 'p': |
1431 | case 'P': | |
5b0a4722 A |
1432 | if (strcasecmp(key, LAUNCH_JOBKEY_PROGRAM) == 0) { |
1433 | return; | |
1434 | } | |
ed34e3c3 A |
1435 | break; |
1436 | case 'l': | |
1437 | case 'L': | |
5b0a4722 A |
1438 | if (strcasecmp(key, LAUNCH_JOBKEY_LABEL) == 0) { |
1439 | return; | |
1440 | } else if (strcasecmp(key, LAUNCH_JOBKEY_LIMITLOADTOHOSTS) == 0) { | |
1441 | return; | |
1442 | } else if (strcasecmp(key, LAUNCH_JOBKEY_LIMITLOADFROMHOSTS) == 0) { | |
1443 | return; | |
1444 | } else if (strcasecmp(key, LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE) == 0) { | |
1445 | job_reparent_hack(j, value); | |
1446 | return; | |
1447 | } | |
ed34e3c3 A |
1448 | break; |
1449 | case 'r': | |
1450 | case 'R': | |
5b0a4722 A |
1451 | if (strcasecmp(key, LAUNCH_JOBKEY_ROOTDIRECTORY) == 0) { |
1452 | if (getuid() != 0) { | |
1453 | job_log(j, LOG_WARNING, "Ignored this key: %s", key); | |
1454 | return; | |
1455 | } | |
ed34e3c3 | 1456 | where2put = &j->rootdir; |
5b0a4722 | 1457 | } |
ed34e3c3 A |
1458 | break; |
1459 | case 'w': | |
1460 | case 'W': | |
5b0a4722 | 1461 | if (strcasecmp(key, LAUNCH_JOBKEY_WORKINGDIRECTORY) == 0) { |
ed34e3c3 | 1462 | where2put = &j->workingdir; |
5b0a4722 | 1463 | } |
ed34e3c3 A |
1464 | break; |
1465 | case 'u': | |
1466 | case 'U': | |
5b0a4722 A |
1467 | if (strcasecmp(key, LAUNCH_JOBKEY_USERNAME) == 0) { |
1468 | if (getuid() != 0) { | |
1469 | job_log(j, LOG_WARNING, "Ignored this key: %s", key); | |
1470 | return; | |
1471 | } else if (strcmp(value, "root") == 0) { | |
1472 | return; | |
1473 | } | |
ed34e3c3 | 1474 | where2put = &j->username; |
5b0a4722 | 1475 | } |
ed34e3c3 A |
1476 | break; |
1477 | case 'g': | |
1478 | case 'G': | |
5b0a4722 A |
1479 | if (strcasecmp(key, LAUNCH_JOBKEY_GROUPNAME) == 0) { |
1480 | if (getuid() != 0) { | |
1481 | job_log(j, LOG_WARNING, "Ignored this key: %s", key); | |
1482 | return; | |
1483 | } else if (strcmp(value, "wheel") == 0) { | |
1484 | return; | |
1485 | } | |
ed34e3c3 | 1486 | where2put = &j->groupname; |
5b0a4722 | 1487 | } |
ed34e3c3 A |
1488 | break; |
1489 | case 's': | |
1490 | case 'S': | |
1491 | if (strcasecmp(key, LAUNCH_JOBKEY_STANDARDOUTPATH) == 0) { | |
1492 | where2put = &j->stdoutpath; | |
1493 | } else if (strcasecmp(key, LAUNCH_JOBKEY_STANDARDERRORPATH) == 0) { | |
1494 | where2put = &j->stderrpath; | |
f36da725 | 1495 | #if HAVE_SANDBOX |
5b0a4722 A |
1496 | } else if (strcasecmp(key, LAUNCH_JOBKEY_SANDBOXPROFILE) == 0) { |
1497 | where2put = &j->seatbelt_profile; | |
f36da725 | 1498 | #endif |
ed34e3c3 A |
1499 | } |
1500 | break; | |
1501 | default: | |
5b0a4722 | 1502 | job_log(j, LOG_WARNING, "Unknown key for string: %s", key); |
ed34e3c3 A |
1503 | break; |
1504 | } | |
1505 | ||
1506 | if (where2put) { | |
5b0a4722 | 1507 | job_assumes(j, (*where2put = strdup(value)) != NULL); |
ed34e3c3 | 1508 | } else { |
5b0a4722 | 1509 | job_log(j, LOG_WARNING, "Unknown key: %s", key); |
ed34e3c3 A |
1510 | } |
1511 | } | |
1512 | ||
1513 | void | |
5b0a4722 | 1514 | job_import_integer(job_t j, const char *key, long long value) |
ed34e3c3 A |
1515 | { |
1516 | switch (key[0]) { | |
5b0a4722 A |
1517 | case 'e': |
1518 | case 'E': | |
1519 | if (strcasecmp(key, LAUNCH_JOBKEY_EXITTIMEOUT) == 0) { | |
1520 | if (value < 0) { | |
1521 | job_log(j, LOG_WARNING, "%s less than zero. Ignoring.", LAUNCH_JOBKEY_EXITTIMEOUT); | |
1522 | } else if (value > UINT32_MAX) { | |
1523 | job_log(j, LOG_WARNING, "%s is too large. Ignoring.", LAUNCH_JOBKEY_EXITTIMEOUT); | |
1524 | } else { | |
1525 | j->exit_timeout = value; | |
1526 | } | |
1527 | } | |
1528 | break; | |
ed34e3c3 A |
1529 | case 'n': |
1530 | case 'N': | |
5b0a4722 | 1531 | if (strcasecmp(key, LAUNCH_JOBKEY_NICE) == 0) { |
ed34e3c3 | 1532 | j->nice = value; |
5b0a4722 A |
1533 | j->setnice = true; |
1534 | } | |
ed34e3c3 A |
1535 | break; |
1536 | case 't': | |
1537 | case 'T': | |
1538 | if (strcasecmp(key, LAUNCH_JOBKEY_TIMEOUT) == 0) { | |
5b0a4722 A |
1539 | if (value < 0) { |
1540 | job_log(j, LOG_WARNING, "%s less than zero. Ignoring.", LAUNCH_JOBKEY_TIMEOUT); | |
1541 | } else if (value > UINT32_MAX) { | |
1542 | job_log(j, LOG_WARNING, "%s is too large. Ignoring.", LAUNCH_JOBKEY_TIMEOUT); | |
1543 | } else { | |
ed34e3c3 | 1544 | j->timeout = value; |
5b0a4722 A |
1545 | } |
1546 | } else if (strcasecmp(key, LAUNCH_JOBKEY_THROTTLEINTERVAL) == 0) { | |
1547 | if (value < 0) { | |
1548 | job_log(j, LOG_WARNING, "%s less than zero. Ignoring.", LAUNCH_JOBKEY_THROTTLEINTERVAL); | |
1549 | } else if (value > UINT32_MAX) { | |
1550 | job_log(j, LOG_WARNING, "%s is too large. Ignoring.", LAUNCH_JOBKEY_THROTTLEINTERVAL); | |
1551 | } else { | |
1552 | j->min_run_time = value; | |
1553 | } | |
ed34e3c3 A |
1554 | } |
1555 | break; | |
1556 | case 'u': | |
1557 | case 'U': | |
1558 | if (strcasecmp(key, LAUNCH_JOBKEY_UMASK) == 0) { | |
1559 | j->mask = value; | |
1560 | j->setmask = true; | |
1561 | } | |
1562 | break; | |
1563 | case 's': | |
1564 | case 'S': | |
1565 | if (strcasecmp(key, LAUNCH_JOBKEY_STARTINTERVAL) == 0) { | |
5b0a4722 A |
1566 | if (value <= 0) { |
1567 | job_log(j, LOG_WARNING, "%s is not greater than zero. Ignoring.", LAUNCH_JOBKEY_STARTINTERVAL); | |
1568 | } else if (value > UINT32_MAX) { | |
1569 | job_log(j, LOG_WARNING, "%s is too large. Ignoring.", LAUNCH_JOBKEY_STARTINTERVAL); | |
1570 | } else { | |
1571 | runtime_add_ref(); | |
ed34e3c3 | 1572 | j->start_interval = value; |
5b0a4722 A |
1573 | |
1574 | job_assumes(j, kevent_mod((uintptr_t)&j->start_interval, EVFILT_TIMER, EV_ADD, NOTE_SECONDS, value, j) != -1); | |
1575 | } | |
f36da725 | 1576 | #if HAVE_SANDBOX |
5b0a4722 A |
1577 | } else if (strcasecmp(key, LAUNCH_JOBKEY_SANDBOXFLAGS) == 0) { |
1578 | j->seatbelt_flags = value; | |
f36da725 | 1579 | #endif |
5b0a4722 A |
1580 | } |
1581 | ||
1582 | break; | |
1583 | default: | |
1584 | job_log(j, LOG_WARNING, "Unknown key for integer: %s", key); | |
1585 | break; | |
1586 | } | |
1587 | } | |
1588 | ||
1589 | void | |
ef398931 A |
1590 | job_import_opaque(job_t j __attribute__((unused)), |
1591 | const char *key, launch_data_t value __attribute__((unused))) | |
5b0a4722 A |
1592 | { |
1593 | switch (key[0]) { | |
1594 | case 'q': | |
1595 | case 'Q': | |
f36da725 | 1596 | #if HAVE_QUARANTINE |
5b0a4722 A |
1597 | if (strcasecmp(key, LAUNCH_JOBKEY_QUARANTINEDATA) == 0) { |
1598 | size_t tmpsz = launch_data_get_opaque_size(value); | |
1599 | ||
1600 | if (job_assumes(j, j->quarantine_data = malloc(tmpsz))) { | |
1601 | memcpy(j->quarantine_data, launch_data_get_opaque(value), tmpsz); | |
1602 | j->quarantine_data_sz = tmpsz; | |
1603 | } | |
ed34e3c3 | 1604 | } |
f36da725 | 1605 | #endif |
ed34e3c3 A |
1606 | break; |
1607 | default: | |
1608 | break; | |
1609 | } | |
1610 | } | |
1611 | ||
f36da725 A |
1612 | static void |
1613 | policy_setup(launch_data_t obj, const char *key, void *context) | |
1614 | { | |
1615 | job_t j = context; | |
1616 | bool found_key = false; | |
1617 | ||
1618 | switch (key[0]) { | |
1619 | case 'd': | |
1620 | case 'D': | |
1621 | if (strcasecmp(key, LAUNCH_JOBPOLICY_DENYCREATINGOTHERJOBS) == 0) { | |
1622 | j->deny_job_creation = launch_data_get_bool(obj); | |
1623 | found_key = true; | |
1624 | } | |
1625 | break; | |
1626 | default: | |
1627 | break; | |
1628 | } | |
1629 | ||
1630 | if (unlikely(!found_key)) { | |
1631 | job_log(j, LOG_WARNING, "Unknown policy: %s", key); | |
1632 | } | |
1633 | } | |
1634 | ||
ed34e3c3 | 1635 | void |
5b0a4722 | 1636 | job_import_dictionary(job_t j, const char *key, launch_data_t value) |
ed34e3c3 A |
1637 | { |
1638 | launch_data_t tmp; | |
1639 | ||
1640 | switch (key[0]) { | |
f36da725 A |
1641 | case 'p': |
1642 | case 'P': | |
1643 | if (strcasecmp(key, LAUNCH_JOBKEY_POLICIES) == 0) { | |
1644 | launch_data_dict_iterate(value, policy_setup, j); | |
1645 | } | |
1646 | break; | |
ed34e3c3 A |
1647 | case 'k': |
1648 | case 'K': | |
5b0a4722 | 1649 | if (strcasecmp(key, LAUNCH_JOBKEY_KEEPALIVE) == 0) { |
ed34e3c3 | 1650 | launch_data_dict_iterate(value, semaphoreitem_setup, j); |
5b0a4722 | 1651 | } |
ed34e3c3 A |
1652 | break; |
1653 | case 'i': | |
1654 | case 'I': | |
1655 | if (strcasecmp(key, LAUNCH_JOBKEY_INETDCOMPATIBILITY) == 0) { | |
1656 | j->inetcompat = true; | |
5b0a4722 A |
1657 | j->abandon_pg = true; |
1658 | if ((tmp = launch_data_dict_lookup(value, LAUNCH_JOBINETDCOMPATIBILITY_WAIT))) { | |
ed34e3c3 | 1659 | j->inetcompat_wait = launch_data_get_bool(tmp); |
5b0a4722 | 1660 | } |
ed34e3c3 A |
1661 | } |
1662 | break; | |
1663 | case 'e': | |
1664 | case 'E': | |
5b0a4722 | 1665 | if (strcasecmp(key, LAUNCH_JOBKEY_ENVIRONMENTVARIABLES) == 0) { |
ed34e3c3 | 1666 | launch_data_dict_iterate(value, envitem_setup, j); |
5b0a4722 | 1667 | } |
ed34e3c3 A |
1668 | break; |
1669 | case 'u': | |
1670 | case 'U': | |
1671 | if (strcasecmp(key, LAUNCH_JOBKEY_USERENVIRONMENTVARIABLES) == 0) { | |
1672 | j->importing_global_env = true; | |
1673 | launch_data_dict_iterate(value, envitem_setup, j); | |
1674 | j->importing_global_env = false; | |
1675 | } | |
1676 | break; | |
1677 | case 's': | |
1678 | case 'S': | |
1679 | if (strcasecmp(key, LAUNCH_JOBKEY_SOCKETS) == 0) { | |
1680 | launch_data_dict_iterate(value, socketgroup_setup, j); | |
1681 | } else if (strcasecmp(key, LAUNCH_JOBKEY_STARTCALENDARINTERVAL) == 0) { | |
1682 | calendarinterval_new_from_obj(j, value); | |
1683 | } else if (strcasecmp(key, LAUNCH_JOBKEY_SOFTRESOURCELIMITS) == 0) { | |
1684 | launch_data_dict_iterate(value, limititem_setup, j); | |
f36da725 | 1685 | #if HAVE_SANDBOX |
5b0a4722 A |
1686 | } else if (strcasecmp(key, LAUNCH_JOBKEY_SANDBOXFLAGS) == 0) { |
1687 | launch_data_dict_iterate(value, seatbelt_setup_flags, j); | |
f36da725 | 1688 | #endif |
ed34e3c3 A |
1689 | } |
1690 | break; | |
1691 | case 'h': | |
1692 | case 'H': | |
1693 | if (strcasecmp(key, LAUNCH_JOBKEY_HARDRESOURCELIMITS) == 0) { | |
1694 | j->importing_hard_limits = true; | |
1695 | launch_data_dict_iterate(value, limititem_setup, j); | |
1696 | j->importing_hard_limits = false; | |
1697 | } | |
1698 | break; | |
1699 | case 'm': | |
1700 | case 'M': | |
1701 | if (strcasecmp(key, LAUNCH_JOBKEY_MACHSERVICES) == 0) { | |
1702 | launch_data_dict_iterate(value, machservice_setup, j); | |
5b0a4722 A |
1703 | } else if (strcasecmp(key, LAUNCH_JOBKEY_MACHSERVICELOOKUPPOLICIES) == 0) { |
1704 | launch_data_dict_iterate(value, mspolicy_setup, j); | |
ed34e3c3 A |
1705 | } |
1706 | break; | |
1707 | default: | |
5b0a4722 | 1708 | job_log(j, LOG_WARNING, "Unknown key for dictionary: %s", key); |
ed34e3c3 A |
1709 | break; |
1710 | } | |
1711 | } | |
1712 | ||
1713 | void | |
5b0a4722 | 1714 | job_import_array(job_t j, const char *key, launch_data_t value) |
ed34e3c3 | 1715 | { |
5b0a4722 A |
1716 | size_t i, value_cnt = launch_data_array_get_count(value); |
1717 | const char *str; | |
ed34e3c3 A |
1718 | |
1719 | switch (key[0]) { | |
5b0a4722 A |
1720 | case 'p': |
1721 | case 'P': | |
1722 | if (strcasecmp(key, LAUNCH_JOBKEY_PROGRAMARGUMENTS) == 0) { | |
1723 | return; | |
1724 | } | |
1725 | break; | |
1726 | case 'l': | |
1727 | case 'L': | |
1728 | if (strcasecmp(key, LAUNCH_JOBKEY_LIMITLOADTOHOSTS) == 0) { | |
1729 | return; | |
1730 | } else if (strcasecmp(key, LAUNCH_JOBKEY_LIMITLOADFROMHOSTS) == 0) { | |
1731 | return; | |
1732 | } else if (strcasecmp(key, LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE) == 0) { | |
1733 | job_log(j, LOG_NOTICE, "launchctl should have transformed the \"%s\" array to a string", LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE); | |
1734 | return; | |
1735 | } | |
1736 | break; | |
ed34e3c3 A |
1737 | case 'q': |
1738 | case 'Q': | |
1739 | if (strcasecmp(key, LAUNCH_JOBKEY_QUEUEDIRECTORIES) == 0) { | |
5b0a4722 A |
1740 | for (i = 0; i < value_cnt; i++) { |
1741 | str = launch_data_get_string(launch_data_array_get_index(value, i)); | |
1742 | if (job_assumes(j, str != NULL)) { | |
1743 | semaphoreitem_new(j, DIR_NOT_EMPTY, str); | |
1744 | } | |
1745 | } | |
1746 | ||
ed34e3c3 A |
1747 | } |
1748 | break; | |
1749 | case 'w': | |
1750 | case 'W': | |
5b0a4722 A |
1751 | if (strcasecmp(key, LAUNCH_JOBKEY_WATCHPATHS) == 0) { |
1752 | for (i = 0; i < value_cnt; i++) { | |
1753 | str = launch_data_get_string(launch_data_array_get_index(value, i)); | |
1754 | if (job_assumes(j, str != NULL)) { | |
1755 | semaphoreitem_new(j, PATH_CHANGES, str); | |
1756 | } | |
1757 | } | |
1758 | } | |
ed34e3c3 A |
1759 | break; |
1760 | case 'b': | |
1761 | case 'B': | |
5b0a4722 | 1762 | if (strcasecmp(key, LAUNCH_JOBKEY_BONJOURFDS) == 0) { |
ed34e3c3 | 1763 | socketgroup_setup(value, LAUNCH_JOBKEY_BONJOURFDS, j); |
5b0a4722 A |
1764 | } else if (strcasecmp(key, LAUNCH_JOBKEY_BINARYORDERPREFERENCE) == 0) { |
1765 | if (job_assumes(j, j->j_binpref = malloc(value_cnt * sizeof(*j->j_binpref)))) { | |
1766 | j->j_binpref_cnt = value_cnt; | |
1767 | for (i = 0; i < value_cnt; i++) { | |
1768 | j->j_binpref[i] = launch_data_get_integer(launch_data_array_get_index(value, i)); | |
1769 | } | |
1770 | } | |
1771 | } | |
ed34e3c3 A |
1772 | break; |
1773 | case 's': | |
1774 | case 'S': | |
1775 | if (strcasecmp(key, LAUNCH_JOBKEY_STARTCALENDARINTERVAL) == 0) { | |
5b0a4722 | 1776 | for (i = 0; i < value_cnt; i++) { |
ed34e3c3 | 1777 | calendarinterval_new_from_obj(j, launch_data_array_get_index(value, i)); |
5b0a4722 | 1778 | } |
ed34e3c3 A |
1779 | } |
1780 | break; | |
1781 | default: | |
5b0a4722 | 1782 | job_log(j, LOG_WARNING, "Unknown key for array: %s", key); |
ed34e3c3 A |
1783 | break; |
1784 | } | |
ed34e3c3 A |
1785 | } |
1786 | ||
1787 | void | |
1788 | job_import_keys(launch_data_t obj, const char *key, void *context) | |
1789 | { | |
5b0a4722 | 1790 | job_t j = context; |
ed34e3c3 A |
1791 | launch_data_type_t kind; |
1792 | ||
5b0a4722 | 1793 | if (obj == NULL) { |
ed34e3c3 | 1794 | return; |
5b0a4722 | 1795 | } |
ed34e3c3 A |
1796 | |
1797 | kind = launch_data_get_type(obj); | |
1798 | ||
1799 | switch (kind) { | |
1800 | case LAUNCH_DATA_BOOL: | |
1801 | job_import_bool(j, key, launch_data_get_bool(obj)); | |
1802 | break; | |
1803 | case LAUNCH_DATA_STRING: | |
1804 | job_import_string(j, key, launch_data_get_string(obj)); | |
1805 | break; | |
1806 | case LAUNCH_DATA_INTEGER: | |
1807 | job_import_integer(j, key, launch_data_get_integer(obj)); | |
1808 | break; | |
1809 | case LAUNCH_DATA_DICTIONARY: | |
1810 | job_import_dictionary(j, key, obj); | |
1811 | break; | |
1812 | case LAUNCH_DATA_ARRAY: | |
1813 | job_import_array(j, key, obj); | |
1814 | break; | |
5b0a4722 A |
1815 | case LAUNCH_DATA_OPAQUE: |
1816 | job_import_opaque(j, key, obj); | |
1817 | break; | |
ed34e3c3 A |
1818 | default: |
1819 | job_log(j, LOG_WARNING, "Unknown value type '%d' for key: %s", kind, key); | |
1820 | break; | |
1821 | } | |
1822 | } | |
1823 | ||
5b0a4722 A |
1824 | job_t |
1825 | jobmgr_import2(jobmgr_t jm, launch_data_t pload) | |
ed34e3c3 A |
1826 | { |
1827 | launch_data_t tmp, ldpa; | |
1828 | const char *label = NULL, *prog = NULL; | |
1829 | const char **argv = NULL; | |
5b0a4722 A |
1830 | job_t j; |
1831 | ||
1832 | if (pload == NULL) { | |
1833 | errno = EINVAL; | |
1834 | return NULL; | |
1835 | } | |
ed34e3c3 | 1836 | |
5b0a4722 A |
1837 | if (launch_data_get_type(pload) != LAUNCH_DATA_DICTIONARY) { |
1838 | errno = EINVAL; | |
ed34e3c3 | 1839 | return NULL; |
5b0a4722 | 1840 | } |
ed34e3c3 | 1841 | |
5b0a4722 A |
1842 | if (!(tmp = launch_data_dict_lookup(pload, LAUNCH_JOBKEY_LABEL))) { |
1843 | errno = EINVAL; | |
ed34e3c3 | 1844 | return NULL; |
5b0a4722 | 1845 | } |
ed34e3c3 | 1846 | |
5b0a4722 A |
1847 | if (launch_data_get_type(tmp) != LAUNCH_DATA_STRING) { |
1848 | errno = EINVAL; | |
1849 | return NULL; | |
ed34e3c3 | 1850 | } |
5b0a4722 A |
1851 | |
1852 | if (!(label = launch_data_get_string(tmp))) { | |
1853 | errno = EINVAL; | |
1854 | return NULL; | |
1855 | } | |
1856 | ||
ed34e3c3 A |
1857 | if ((tmp = launch_data_dict_lookup(pload, LAUNCH_JOBKEY_PROGRAM)) && |
1858 | (launch_data_get_type(tmp) == LAUNCH_DATA_STRING)) { | |
1859 | prog = launch_data_get_string(tmp); | |
1860 | } | |
ed34e3c3 | 1861 | |
5b0a4722 A |
1862 | if ((ldpa = launch_data_dict_lookup(pload, LAUNCH_JOBKEY_PROGRAMARGUMENTS))) { |
1863 | size_t i, c; | |
1864 | ||
1865 | if (launch_data_get_type(ldpa) != LAUNCH_DATA_ARRAY) { | |
1866 | errno = EINVAL; | |
1867 | return NULL; | |
1868 | } | |
1869 | ||
1870 | c = launch_data_array_get_count(ldpa); | |
1871 | ||
1872 | argv = alloca((c + 1) * sizeof(char *)); | |
1873 | ||
1874 | for (i = 0; i < c; i++) { | |
1875 | tmp = launch_data_array_get_index(ldpa, i); | |
1876 | ||
1877 | if (launch_data_get_type(tmp) != LAUNCH_DATA_STRING) { | |
1878 | errno = EINVAL; | |
1879 | return NULL; | |
1880 | } | |
1881 | ||
1882 | argv[i] = launch_data_get_string(tmp); | |
1883 | } | |
1884 | ||
1885 | argv[i] = NULL; | |
1886 | } | |
1887 | ||
1888 | if ((j = job_find(label)) != NULL) { | |
ed34e3c3 A |
1889 | errno = EEXIST; |
1890 | return NULL; | |
f36da725 | 1891 | } else if (!jobmgr_label_test(jm, label)) { |
ed34e3c3 A |
1892 | errno = EINVAL; |
1893 | return NULL; | |
1894 | } | |
1895 | ||
5b0a4722 A |
1896 | if ((j = job_new(jm, label, prog, argv))) { |
1897 | launch_data_dict_iterate(pload, job_import_keys, j); | |
1898 | } | |
ed34e3c3 | 1899 | |
5b0a4722 A |
1900 | return j; |
1901 | } | |
ed34e3c3 | 1902 | |
f36da725 A |
1903 | bool |
1904 | jobmgr_label_test(jobmgr_t jm, const char *str) | |
1905 | { | |
1906 | char *endstr = NULL; | |
1907 | const char *ptr; | |
1908 | ||
1909 | if (str[0] == '\0') { | |
1910 | jobmgr_log(jm, LOG_ERR, "Empty job labels are not allowed"); | |
1911 | return false; | |
1912 | } | |
1913 | ||
1914 | for (ptr = str; *ptr; ptr++) { | |
1915 | if (iscntrl(*ptr)) { | |
1916 | jobmgr_log(jm, LOG_ERR, "ASCII control characters are not allowed in job labels. Index: %td Value: 0x%hhx", ptr - str, *ptr); | |
1917 | return false; | |
1918 | } | |
1919 | } | |
1920 | ||
1921 | strtoll(str, &endstr, 0); | |
1922 | ||
1923 | if (str != endstr) { | |
1924 | jobmgr_log(jm, LOG_ERR, "Job labels are not allowed to begin with numbers: %s", str); | |
1925 | return false; | |
1926 | } | |
1927 | ||
1928 | if ((strncasecmp(str, "com.apple.launchd", strlen("com.apple.launchd")) == 0) || | |
1929 | (strncasecmp(str, "com.apple.launchctl", strlen("com.apple.launchctl")) == 0)) { | |
1930 | jobmgr_log(jm, LOG_ERR, "Job labels are not allowed to use a reserved prefix: %s", str); | |
1931 | return false; | |
1932 | } | |
1933 | ||
1934 | return true; | |
1935 | } | |
1936 | ||
5b0a4722 A |
1937 | job_t |
1938 | job_find(const char *label) | |
1939 | { | |
1940 | job_t ji; | |
1941 | ||
1942 | LIST_FOREACH(ji, &label_hash[hash_label(label)], label_hash_sle) { | |
1943 | if (ji->removal_pending) { | |
1944 | continue; /* 5351245 */ | |
1945 | } else if (ji->mgr->shutting_down) { | |
1946 | continue; /* 5488633 */ | |
1947 | } | |
1948 | ||
1949 | if (strcmp(ji->label, label) == 0) { | |
1950 | return ji; | |
1951 | } | |
ed34e3c3 A |
1952 | } |
1953 | ||
5b0a4722 A |
1954 | errno = ESRCH; |
1955 | return NULL; | |
1956 | } | |
ed34e3c3 | 1957 | |
5b0a4722 A |
1958 | job_t |
1959 | jobmgr_find_by_pid(jobmgr_t jm, pid_t p, bool create_anon) | |
1960 | { | |
1961 | job_t ji = NULL; | |
1962 | ||
1963 | LIST_FOREACH(ji, &jm->active_jobs[ACTIVE_JOB_HASH(p)], pid_hash_sle) { | |
1964 | if (ji->p == p) { | |
1965 | break; | |
1966 | } | |
1967 | } | |
1968 | ||
1969 | if (ji) { | |
1970 | return ji; | |
1971 | } else if (create_anon) { | |
1972 | return job_new_anonymous(jm, p); | |
1973 | } else { | |
1974 | return NULL; | |
1975 | } | |
ed34e3c3 A |
1976 | } |
1977 | ||
5b0a4722 A |
1978 | job_t |
1979 | job_mig_intran2(jobmgr_t jm, mach_port_t mport, pid_t upid) | |
ed34e3c3 | 1980 | { |
5b0a4722 A |
1981 | jobmgr_t jmi; |
1982 | job_t ji; | |
ed34e3c3 | 1983 | |
5b0a4722 A |
1984 | if (jm->jm_port == mport) { |
1985 | jobmgr_assumes(jm, (ji = jobmgr_find_by_pid(jm, upid, true)) != NULL); | |
1986 | return ji; | |
1987 | } | |
ed34e3c3 | 1988 | |
5b0a4722 A |
1989 | SLIST_FOREACH(jmi, &jm->submgrs, sle) { |
1990 | job_t jr; | |
ed34e3c3 | 1991 | |
5b0a4722 | 1992 | if ((jr = job_mig_intran2(jmi, mport, upid))) { |
ed34e3c3 | 1993 | return jr; |
5b0a4722 A |
1994 | } |
1995 | } | |
1996 | ||
1997 | LIST_FOREACH(ji, &jm->jobs, sle) { | |
1998 | if (ji->j_port == mport) { | |
1999 | return ji; | |
2000 | } | |
ed34e3c3 A |
2001 | } |
2002 | ||
ed34e3c3 A |
2003 | return NULL; |
2004 | } | |
2005 | ||
5b0a4722 A |
2006 | job_t |
2007 | job_mig_intran(mach_port_t p) | |
ed34e3c3 | 2008 | { |
5b0a4722 A |
2009 | struct ldcred ldc; |
2010 | job_t jr; | |
ed34e3c3 | 2011 | |
5b0a4722 | 2012 | runtime_get_caller_creds(&ldc); |
ed34e3c3 | 2013 | |
5b0a4722 A |
2014 | jr = job_mig_intran2(root_jobmgr, p, ldc.pid); |
2015 | ||
2016 | if (!jobmgr_assumes(root_jobmgr, jr != NULL)) { | |
2017 | int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, 0 }; | |
2018 | struct kinfo_proc kp; | |
2019 | size_t len = sizeof(kp); | |
2020 | ||
2021 | mib[3] = ldc.pid; | |
2022 | ||
2023 | if (jobmgr_assumes(root_jobmgr, sysctl(mib, 4, &kp, &len, NULL, 0) != -1) && jobmgr_assumes(root_jobmgr, len == sizeof(kp))) { | |
2024 | jobmgr_log(root_jobmgr, LOG_ERR, "%s() was confused by PID %u UID %u EUID %u Mach Port 0x%x: %s", __func__, ldc.pid, ldc.uid, ldc.euid, p, kp.kp_proc.p_comm); | |
2025 | } | |
2026 | } | |
2027 | ||
2028 | return jr; | |
2029 | } | |
2030 | ||
2031 | job_t | |
2032 | job_find_by_service_port(mach_port_t p) | |
2033 | { | |
2034 | struct machservice *ms; | |
2035 | ||
2036 | LIST_FOREACH(ms, &port_hash[HASH_PORT(p)], port_hash_sle) { | |
2037 | if (ms->recv && (ms->port == p)) { | |
2038 | return ms->job; | |
2039 | } | |
ed34e3c3 A |
2040 | } |
2041 | ||
ed34e3c3 A |
2042 | return NULL; |
2043 | } | |
2044 | ||
2045 | void | |
5b0a4722 | 2046 | job_mig_destructor(job_t j) |
ed34e3c3 | 2047 | { |
5b0a4722 A |
2048 | /* |
2049 | * 5477111 | |
2050 | * | |
2051 | * 'j' can be invalid at this point. We should fix this up after Leopard ships. | |
2052 | */ | |
2053 | ||
2054 | if (j && j != workaround_5477111 && j->unload_at_mig_return) { | |
2055 | job_log(j, LOG_NOTICE, "Unloading PID %u at MIG return.", j->p); | |
2056 | job_remove(j); | |
2057 | } | |
ed34e3c3 | 2058 | |
5b0a4722 | 2059 | workaround_5477111 = NULL; |
ed34e3c3 | 2060 | |
5b0a4722 A |
2061 | calendarinterval_sanity_check(); |
2062 | } | |
2063 | ||
2064 | void | |
2065 | job_export_all2(jobmgr_t jm, launch_data_t where) | |
2066 | { | |
2067 | jobmgr_t jmi; | |
2068 | job_t ji; | |
2069 | ||
2070 | SLIST_FOREACH(jmi, &jm->submgrs, sle) { | |
2071 | job_export_all2(jmi, where); | |
2072 | } | |
2073 | ||
2074 | LIST_FOREACH(ji, &jm->jobs, sle) { | |
2075 | launch_data_t tmp; | |
2076 | ||
2077 | if (jobmgr_assumes(jm, (tmp = job_export(ji)) != NULL)) { | |
2078 | launch_data_dict_insert(where, tmp, ji->label); | |
2079 | } | |
2080 | } | |
ed34e3c3 A |
2081 | } |
2082 | ||
2083 | launch_data_t | |
2084 | job_export_all(void) | |
2085 | { | |
2086 | launch_data_t resp = launch_data_alloc(LAUNCH_DATA_DICTIONARY); | |
2087 | ||
5b0a4722 A |
2088 | if (launchd_assumes(resp != NULL)) { |
2089 | job_export_all2(root_jobmgr, resp); | |
2090 | } | |
ed34e3c3 A |
2091 | |
2092 | return resp; | |
2093 | } | |
2094 | ||
2095 | void | |
5b0a4722 | 2096 | job_log_stray_pg(job_t j) |
ed34e3c3 | 2097 | { |
5b0a4722 | 2098 | int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PGRP, j->p }; |
f36da725 | 2099 | size_t i, kp_cnt, len = sizeof(struct kinfo_proc) * get_kern_max_proc(); |
5b0a4722 | 2100 | struct kinfo_proc *kp; |
ed34e3c3 | 2101 | |
f36da725 A |
2102 | #if TARGET_OS_EMBEDDED |
2103 | if (!do_apple_internal_magic) { | |
2104 | return; | |
2105 | } | |
2106 | #endif | |
2107 | ||
5b0a4722 A |
2108 | if (!job_assumes(j, (kp = malloc(len)) != NULL)) { |
2109 | return; | |
2110 | } | |
2111 | if (!job_assumes(j, sysctl(mib, 4, kp, &len, NULL, 0) != -1)) { | |
2112 | goto out; | |
ed34e3c3 A |
2113 | } |
2114 | ||
5b0a4722 A |
2115 | kp_cnt = len / sizeof(struct kinfo_proc); |
2116 | ||
2117 | for (i = 0; i < kp_cnt; i++) { | |
2118 | pid_t p_i = kp[i].kp_proc.p_pid; | |
2119 | pid_t pp_i = kp[i].kp_eproc.e_ppid; | |
2120 | const char *z = (kp[i].kp_proc.p_stat == SZOMB) ? "zombie " : ""; | |
2121 | const char *n = kp[i].kp_proc.p_comm; | |
2122 | ||
2123 | if (p_i == j->p) { | |
2124 | continue; | |
2125 | } else if (!job_assumes(j, p_i != 0 && p_i != 1)) { | |
2126 | continue; | |
2127 | } | |
2128 | ||
2129 | job_log(j, LOG_WARNING, "Stray %sprocess with PGID equal to this dead job: PID %u PPID %u %s", z, p_i, pp_i, n); | |
2130 | } | |
2131 | ||
2132 | out: | |
2133 | free(kp); | |
2134 | } | |
2135 | ||
2136 | void | |
2137 | job_reap(job_t j) | |
2138 | { | |
2139 | struct rusage ru; | |
2140 | int status; | |
2141 | ||
2142 | job_log(j, LOG_DEBUG, "Reaping"); | |
2143 | ||
2144 | if (j->weird_bootstrap) { | |
2145 | mach_msg_size_t mxmsgsz = sizeof(union __RequestUnion__job_mig_protocol_vproc_subsystem); | |
2146 | ||
2147 | if (job_mig_protocol_vproc_subsystem.maxsize > mxmsgsz) { | |
2148 | mxmsgsz = job_mig_protocol_vproc_subsystem.maxsize; | |
2149 | } | |
2150 | ||
2151 | job_assumes(j, runtime_add_mport(j->mgr->jm_port, protocol_vproc_server, mxmsgsz) == KERN_SUCCESS); | |
2152 | j->weird_bootstrap = false; | |
2153 | } | |
2154 | ||
2155 | if (j->log_redirect_fd && !j->wait4pipe_eof) { | |
2156 | job_assumes(j, runtime_close(j->log_redirect_fd) != -1); | |
2157 | j->log_redirect_fd = 0; | |
2158 | } | |
2159 | ||
2160 | if (j->forkfd) { | |
2161 | job_assumes(j, runtime_close(j->forkfd) != -1); | |
2162 | j->forkfd = 0; | |
2163 | } | |
2164 | ||
2165 | if (j->anonymous) { | |
2166 | status = 0; | |
2167 | memset(&ru, 0, sizeof(ru)); | |
2168 | } else { | |
2169 | /* | |
2170 | * The job is dead. While the PID/PGID is still known to be | |
2171 | * valid, try to kill abandoned descendant processes. | |
2172 | */ | |
2173 | job_log_stray_pg(j); | |
2174 | if (!j->abandon_pg) { | |
2175 | job_assumes(j, runtime_killpg(j->p, SIGTERM) != -1 || errno == ESRCH); | |
2176 | } | |
2177 | ||
2178 | /* | |
2179 | * 5020256 | |
2180 | * | |
2181 | * The current implementation of ptrace() causes the traced process to | |
2182 | * be abducted away from the true parent and adopted by the tracer. | |
2183 | * | |
2184 | * Once the tracing process relinquishes control, the kernel then | |
2185 | * restores the true parent/child relationship. | |
2186 | * | |
2187 | * Unfortunately, the wait*() family of APIs is unaware of the temporarily | |
2188 | * data structures changes, and they return an error if reality hasn't | |
2189 | * been restored by the time they are called. | |
2190 | */ | |
2191 | if (!job_assumes(j, wait4(j->p, &status, 0, &ru) != -1)) { | |
2192 | job_log(j, LOG_NOTICE, "Working around 5020256. Assuming the job crashed."); | |
2193 | ||
2194 | status = W_EXITCODE(0, SIGSEGV); | |
2195 | memset(&ru, 0, sizeof(ru)); | |
2196 | } | |
ed34e3c3 A |
2197 | } |
2198 | ||
5b0a4722 A |
2199 | if (j->exit_timeout) { |
2200 | kevent_mod((uintptr_t)&j->exit_timeout, EVFILT_TIMER, EV_DELETE, 0, 0, NULL); | |
2201 | } | |
2202 | ||
2203 | if (j->anonymous) { | |
2204 | total_anon_children--; | |
2205 | } else { | |
2206 | runtime_del_ref(); | |
2207 | total_children--; | |
2208 | } | |
2209 | LIST_REMOVE(j, pid_hash_sle); | |
2210 | ||
ed34e3c3 A |
2211 | if (j->wait_reply_port) { |
2212 | job_log(j, LOG_DEBUG, "MPM wait reply being sent"); | |
5b0a4722 | 2213 | job_assumes(j, job_mig_wait_reply(j->wait_reply_port, 0, status) == 0); |
ed34e3c3 A |
2214 | j->wait_reply_port = MACH_PORT_NULL; |
2215 | } | |
2216 | ||
5b0a4722 A |
2217 | if (j->sent_sigterm_time) { |
2218 | uint64_t td_sec, td_usec, td = (mach_absolute_time() - j->sent_sigterm_time) * tbi.numer / tbi.denom; | |
2219 | ||
2220 | td_sec = td / NSEC_PER_SEC; | |
2221 | td_usec = (td % NSEC_PER_SEC) / NSEC_PER_USEC; | |
2222 | ||
2223 | job_log(j, LOG_INFO, "Exited %lld.%06lld seconds after %s was sent", | |
2224 | td_sec, td_usec, signal_to_C_name(j->sent_sigkill ? SIGKILL : SIGTERM)); | |
2225 | } | |
2226 | ||
2227 | #if DO_RUSAGE_SUMMATION | |
ed34e3c3 A |
2228 | timeradd(&ru.ru_utime, &j->ru.ru_utime, &j->ru.ru_utime); |
2229 | timeradd(&ru.ru_stime, &j->ru.ru_stime, &j->ru.ru_stime); | |
2230 | j->ru.ru_maxrss += ru.ru_maxrss; | |
2231 | j->ru.ru_ixrss += ru.ru_ixrss; | |
2232 | j->ru.ru_idrss += ru.ru_idrss; | |
2233 | j->ru.ru_isrss += ru.ru_isrss; | |
2234 | j->ru.ru_minflt += ru.ru_minflt; | |
2235 | j->ru.ru_majflt += ru.ru_majflt; | |
2236 | j->ru.ru_nswap += ru.ru_nswap; | |
2237 | j->ru.ru_inblock += ru.ru_inblock; | |
2238 | j->ru.ru_oublock += ru.ru_oublock; | |
2239 | j->ru.ru_msgsnd += ru.ru_msgsnd; | |
2240 | j->ru.ru_msgrcv += ru.ru_msgrcv; | |
2241 | j->ru.ru_nsignals += ru.ru_nsignals; | |
2242 | j->ru.ru_nvcsw += ru.ru_nvcsw; | |
2243 | j->ru.ru_nivcsw += ru.ru_nivcsw; | |
5b0a4722 | 2244 | #endif |
ed34e3c3 A |
2245 | |
2246 | if (WIFEXITED(status) && WEXITSTATUS(status) != 0) { | |
5b0a4722 | 2247 | job_log(j, LOG_WARNING, "Exited with exit code: %d", WEXITSTATUS(status)); |
ed34e3c3 A |
2248 | } |
2249 | ||
2250 | if (WIFSIGNALED(status)) { | |
2251 | int s = WTERMSIG(status); | |
2252 | if (SIGKILL == s || SIGTERM == s) { | |
2253 | job_log(j, LOG_NOTICE, "Exited: %s", strsignal(s)); | |
2254 | } else { | |
2255 | job_log(j, LOG_WARNING, "Exited abnormally: %s", strsignal(s)); | |
ed34e3c3 A |
2256 | } |
2257 | } | |
2258 | ||
5b0a4722 A |
2259 | if (j->hopefully_exits_first) { |
2260 | j->mgr->hopefully_first_cnt--; | |
2261 | } else if (!j->anonymous && !j->hopefully_exits_last) { | |
2262 | j->mgr->normal_active_cnt--; | |
2263 | } | |
2264 | j->last_exit_status = status; | |
2265 | j->sent_sigkill = false; | |
cf0bacfd A |
2266 | j->lastlookup = NULL; |
2267 | j->lastlookup_gennum = 0; | |
5b0a4722 A |
2268 | j->p = 0; |
2269 | ||
2270 | /* | |
2271 | * We need to someday evaluate other jobs and find those who wish to track the | |
2272 | * active/inactive state of this job. The current job_dispatch() logic makes | |
2273 | * this messy, given that jobs can be deleted at dispatch. | |
2274 | */ | |
2275 | } | |
2276 | ||
2277 | void | |
2278 | jobmgr_dispatch_all(jobmgr_t jm, bool newmounthack) | |
2279 | { | |
2280 | jobmgr_t jmi, jmn; | |
2281 | job_t ji, jn; | |
2282 | ||
2283 | if (jm->shutting_down) { | |
2284 | return; | |
2285 | } | |
2286 | ||
2287 | SLIST_FOREACH_SAFE(jmi, &jm->submgrs, sle, jmn) { | |
2288 | jobmgr_dispatch_all(jmi, newmounthack); | |
ed34e3c3 A |
2289 | } |
2290 | ||
5b0a4722 A |
2291 | LIST_FOREACH_SAFE(ji, &jm->jobs, sle, jn) { |
2292 | if (newmounthack && ji->start_on_mount) { | |
2293 | ji->start_pending = true; | |
2294 | } | |
ed34e3c3 | 2295 | |
5b0a4722 | 2296 | job_dispatch(ji, false); |
ed34e3c3 | 2297 | } |
5b0a4722 | 2298 | } |
ed34e3c3 | 2299 | |
5b0a4722 A |
2300 | job_t |
2301 | job_dispatch(job_t j, bool kickstart) | |
2302 | { | |
2303 | /* | |
2304 | * The whole job removal logic needs to be consolidated. The fact that | |
2305 | * a job can be removed from just about anywhere makes it easy to have | |
2306 | * stale pointers left behind somewhere on the stack that might get | |
2307 | * used after the deallocation. In particular, during job iteration. | |
2308 | * | |
2309 | * This is a classic example. The act of dispatching a job may delete it. | |
2310 | */ | |
2311 | if (!job_active(j)) { | |
2312 | if (job_useless(j)) { | |
2313 | job_remove(j); | |
2314 | return NULL; | |
2315 | } else if (kickstart || job_keepalive(j)) { | |
2316 | job_start(j); | |
2317 | } else { | |
2318 | job_watch(j); | |
2319 | ||
2320 | /* | |
2321 | * 5455720 | |
2322 | * | |
2323 | * Path checking and monitoring is really racy right now. | |
2324 | * We should clean this up post Leopard. | |
2325 | */ | |
2326 | if (job_keepalive(j)) { | |
2327 | job_start(j); | |
2328 | } | |
2329 | } | |
2330 | } else { | |
2331 | job_log(j, LOG_DEBUG, "Tried to dispatch an already active job."); | |
2332 | } | |
2333 | ||
2334 | return j; | |
ed34e3c3 A |
2335 | } |
2336 | ||
2337 | void | |
5b0a4722 | 2338 | job_log_stdouterr2(job_t j, const char *msg, ...) |
ed34e3c3 | 2339 | { |
5b0a4722 A |
2340 | struct runtime_syslog_attr attr = { j->label, j->label, j->mgr->name, LOG_NOTICE, getuid(), j->p, j->p }; |
2341 | va_list ap; | |
2342 | ||
2343 | va_start(ap, msg); | |
2344 | runtime_vsyslog(&attr, msg, ap); | |
2345 | va_end(ap); | |
2346 | } | |
2347 | ||
2348 | void | |
2349 | job_log_stdouterr(job_t j) | |
2350 | { | |
2351 | char *msg, *bufindex, *buf = malloc(BIG_PIPE_SIZE + 1); | |
2352 | bool close_log_redir = false; | |
2353 | ssize_t rsz; | |
2354 | ||
2355 | if (!job_assumes(j, buf != NULL)) { | |
ed34e3c3 | 2356 | return; |
5b0a4722 A |
2357 | } |
2358 | ||
2359 | bufindex = buf; | |
2360 | ||
2361 | rsz = read(j->log_redirect_fd, buf, BIG_PIPE_SIZE); | |
2362 | ||
2363 | if (rsz == 0) { | |
2364 | job_log(j, LOG_DEBUG, "Standard out/error pipe closed"); | |
2365 | close_log_redir = true; | |
2366 | } else if (!job_assumes(j, rsz != -1)) { | |
2367 | close_log_redir = true; | |
ed34e3c3 | 2368 | } else { |
5b0a4722 A |
2369 | buf[rsz] = '\0'; |
2370 | ||
2371 | while ((msg = strsep(&bufindex, "\n\r"))) { | |
2372 | if (msg[0]) { | |
2373 | job_log_stdouterr2(j, "%s", msg); | |
2374 | } | |
2375 | } | |
2376 | } | |
2377 | ||
2378 | free(buf); | |
2379 | ||
2380 | if (close_log_redir) { | |
2381 | job_assumes(j, runtime_close(j->log_redirect_fd) != -1); | |
2382 | j->log_redirect_fd = 0; | |
2383 | job_dispatch(j, false); | |
ed34e3c3 A |
2384 | } |
2385 | } | |
2386 | ||
2387 | void | |
5b0a4722 | 2388 | job_kill(job_t j) |
ed34e3c3 | 2389 | { |
5b0a4722 A |
2390 | if (!j->p || j->anonymous) { |
2391 | return; | |
2392 | } | |
ed34e3c3 | 2393 | |
5b0a4722 A |
2394 | job_assumes(j, runtime_kill(j->p, SIGKILL) != -1); |
2395 | ||
2396 | j->sent_sigkill = true; | |
2397 | ||
2398 | job_assumes(j, kevent_mod((uintptr_t)&j->exit_timeout, EVFILT_TIMER, | |
2399 | EV_ADD, NOTE_SECONDS, LAUNCHD_SIGKILL_TIMER, j) != -1); | |
2400 | ||
2401 | job_log(j, LOG_DEBUG, "Sent SIGKILL signal."); | |
2402 | } | |
2403 | ||
2404 | void | |
ef398931 | 2405 | job_callback_proc(job_t j, int flags __attribute__((unused)), int fflags) |
5b0a4722 A |
2406 | { |
2407 | if ((fflags & NOTE_EXEC) && j->anonymous) { | |
2408 | int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, j->p }; | |
2409 | struct kinfo_proc kp; | |
2410 | size_t len = sizeof(kp); | |
2411 | ||
2412 | if (job_assumes(j, sysctl(mib, 4, &kp, &len, NULL, 0) != -1)) { | |
2413 | char newlabel[1000]; | |
2414 | ||
2415 | snprintf(newlabel, sizeof(newlabel), "%p.%s", j, kp.kp_proc.p_comm); | |
2416 | ||
2417 | job_log(j, LOG_DEBUG, "Program changed. Updating the label to: %s", newlabel); | |
2418 | ||
2419 | LIST_REMOVE(j, label_hash_sle); | |
2420 | strcpy((char *)j->label, newlabel); | |
2421 | LIST_INSERT_HEAD(&label_hash[hash_label(j->label)], j, label_hash_sle); | |
2422 | } | |
ed34e3c3 A |
2423 | } |
2424 | ||
5b0a4722 A |
2425 | if (fflags & NOTE_FORK) { |
2426 | job_log(j, LOG_DEBUG, "Called fork()"); | |
2427 | } | |
2428 | ||
2429 | if (fflags & NOTE_EXIT) { | |
ed34e3c3 A |
2430 | job_reap(j); |
2431 | ||
5b0a4722 A |
2432 | if (j->anonymous) { |
2433 | job_remove(j); | |
2434 | j = NULL; | |
ed34e3c3 | 2435 | } else { |
5b0a4722 | 2436 | j = job_dispatch(j, false); |
ed34e3c3 | 2437 | } |
5b0a4722 A |
2438 | } |
2439 | ||
2440 | /* NOTE_REAP sanity checking is disabled for now while we try and diagnose 5289559 */ | |
2441 | #if 0 | |
2442 | if (j && (fflags & NOTE_REAP)) { | |
2443 | job_assumes(j, flags & EV_ONESHOT); | |
2444 | job_assumes(j, flags & EV_EOF); | |
2445 | ||
2446 | job_assumes(j, j->p == 0); | |
2447 | } | |
2448 | #endif | |
2449 | } | |
2450 | ||
2451 | void | |
2452 | job_callback_timer(job_t j, void *ident) | |
2453 | { | |
2454 | if (j == ident) { | |
2455 | job_dispatch(j, true); | |
2456 | } else if (&j->semaphores == ident) { | |
2457 | job_dispatch(j, false); | |
2458 | } else if (&j->start_interval == ident) { | |
2459 | j->start_pending = true; | |
2460 | job_dispatch(j, false); | |
2461 | } else if (&j->exit_timeout == ident) { | |
2462 | if (j->sent_sigkill) { | |
2463 | uint64_t td = (mach_absolute_time() - j->sent_sigterm_time) * tbi.numer / tbi.denom; | |
2464 | ||
2465 | td /= NSEC_PER_SEC; | |
2466 | td -= j->exit_timeout; | |
2467 | ||
2468 | job_log(j, LOG_ERR, "Did not die after sending SIGKILL %llu seconds ago...", td); | |
ed34e3c3 | 2469 | } else { |
5b0a4722 A |
2470 | job_force_sampletool(j); |
2471 | if (j->debug_before_kill) { | |
2472 | job_log(j, LOG_NOTICE, "Exit timeout elapsed. Entering the kernel debugger."); | |
2473 | job_assumes(j, host_reboot(mach_host_self(), HOST_REBOOT_DEBUGGER) == KERN_SUCCESS); | |
2474 | } | |
2475 | job_log(j, LOG_WARNING, "Exit timeout elapsed (%u seconds). Killing.", j->exit_timeout); | |
2476 | job_kill(j); | |
ed34e3c3 | 2477 | } |
5b0a4722 A |
2478 | } else { |
2479 | job_assumes(j, false); | |
2480 | } | |
2481 | } | |
2482 | ||
2483 | void | |
2484 | job_callback_read(job_t j, int ident) | |
2485 | { | |
2486 | if (ident == j->log_redirect_fd) { | |
2487 | job_log_stdouterr(j); | |
2488 | } else { | |
2489 | socketgroup_callback(j); | |
2490 | } | |
2491 | } | |
2492 | ||
2493 | void | |
2494 | jobmgr_reap_bulk(jobmgr_t jm, struct kevent *kev) | |
2495 | { | |
2496 | jobmgr_t jmi; | |
2497 | job_t j; | |
2498 | ||
2499 | SLIST_FOREACH(jmi, &jm->submgrs, sle) { | |
2500 | jobmgr_reap_bulk(jmi, kev); | |
2501 | } | |
2502 | ||
2503 | if ((j = jobmgr_find_by_pid(jm, kev->ident, false))) { | |
2504 | kev->udata = j; | |
2505 | job_callback(j, kev); | |
2506 | } | |
2507 | } | |
2508 | ||
2509 | void | |
2510 | jobmgr_callback(void *obj, struct kevent *kev) | |
2511 | { | |
2512 | jobmgr_t jm = obj; | |
2513 | ||
2514 | switch (kev->filter) { | |
2515 | case EVFILT_PROC: | |
2516 | jobmgr_reap_bulk(jm, kev); | |
2517 | if (launchd_assumes(root_jobmgr != NULL)) { | |
2518 | root_jobmgr = jobmgr_do_garbage_collection(root_jobmgr); | |
ed34e3c3 | 2519 | } |
5b0a4722 A |
2520 | break; |
2521 | case EVFILT_SIGNAL: | |
2522 | switch (kev->ident) { | |
2523 | case SIGTERM: | |
2524 | return launchd_shutdown(); | |
2525 | case SIGUSR1: | |
2526 | return calendarinterval_callback(); | |
2527 | default: | |
2528 | return (void)jobmgr_assumes(jm, false); | |
ed34e3c3 | 2529 | } |
5b0a4722 A |
2530 | break; |
2531 | case EVFILT_FS: | |
2532 | if (kev->fflags & VQ_MOUNT) { | |
2533 | jobmgr_dispatch_all(jm, true); | |
ed34e3c3 | 2534 | } |
5b0a4722 | 2535 | jobmgr_dispatch_all_semaphores(jm); |
ed34e3c3 | 2536 | break; |
5b0a4722 A |
2537 | case EVFILT_TIMER: |
2538 | if (jobmgr_assumes(jm, kev->ident == (uintptr_t)&sorted_calendar_events)) { | |
2539 | calendarinterval_callback(); | |
2540 | } | |
ed34e3c3 A |
2541 | break; |
2542 | default: | |
5b0a4722 | 2543 | return (void)jobmgr_assumes(jm, false); |
ed34e3c3 | 2544 | } |
5b0a4722 | 2545 | } |
ed34e3c3 | 2546 | |
5b0a4722 A |
2547 | void |
2548 | job_callback(void *obj, struct kevent *kev) | |
2549 | { | |
2550 | job_t j = obj; | |
2551 | ||
2552 | job_log(j, LOG_DEBUG, "Dispatching kevent callback."); | |
2553 | ||
2554 | switch (kev->filter) { | |
2555 | case EVFILT_PROC: | |
2556 | return job_callback_proc(j, kev->flags, kev->fflags); | |
2557 | case EVFILT_TIMER: | |
2558 | return job_callback_timer(j, (void *)kev->ident); | |
2559 | case EVFILT_VNODE: | |
2560 | return semaphoreitem_callback(j, kev); | |
2561 | case EVFILT_READ: | |
2562 | return job_callback_read(j, kev->ident); | |
2563 | case EVFILT_MACHPORT: | |
2564 | return (void)job_dispatch(j, true); | |
2565 | default: | |
2566 | return (void)job_assumes(j, false); | |
ed34e3c3 A |
2567 | } |
2568 | } | |
2569 | ||
2570 | void | |
5b0a4722 | 2571 | job_start(job_t j) |
ed34e3c3 | 2572 | { |
5b0a4722 | 2573 | uint64_t td, tnow = mach_absolute_time(); |
ed34e3c3 A |
2574 | int spair[2]; |
2575 | int execspair[2]; | |
5b0a4722 | 2576 | int oepair[2]; |
ed34e3c3 A |
2577 | char nbuf[64]; |
2578 | pid_t c; | |
2579 | bool sipc = false; | |
5b0a4722 | 2580 | u_int proc_fflags = /* NOTE_EXEC|NOTE_FORK| */ NOTE_EXIT /* |NOTE_REAP */; |
ed34e3c3 | 2581 | |
5b0a4722 | 2582 | if (!job_assumes(j, j->mgr != NULL)) { |
ed34e3c3 | 2583 | return; |
5b0a4722 | 2584 | } |
ed34e3c3 A |
2585 | |
2586 | if (job_active(j)) { | |
2587 | job_log(j, LOG_DEBUG, "Already started"); | |
2588 | return; | |
5b0a4722 A |
2589 | } |
2590 | ||
2591 | job_assumes(j, tnow > j->start_time); | |
2592 | ||
2593 | /* | |
2594 | * Some users adjust the wall-clock and then expect software to not notice. | |
2595 | * Therefore, launchd must use an absolute clock instead of gettimeofday() | |
2596 | * or time() wherever possible. | |
2597 | */ | |
2598 | td = (tnow - j->start_time) * tbi.numer / tbi.denom; | |
2599 | td /= NSEC_PER_SEC; | |
2600 | ||
2601 | if (j->start_time && (td < j->min_run_time) && !j->legacy_mach_job && !j->inetcompat) { | |
2602 | time_t respawn_delta = j->min_run_time - (uint32_t)td; | |
2603 | ||
2604 | /* | |
2605 | * We technically should ref-count throttled jobs to prevent idle exit, | |
2606 | * but we're not directly tracking the 'throttled' state at the moment. | |
2607 | */ | |
2608 | ||
2609 | job_log(j, LOG_WARNING, "Throttling respawn: Will start in %ld seconds", respawn_delta); | |
2610 | job_assumes(j, kevent_mod((uintptr_t)j, EVFILT_TIMER, EV_ADD|EV_ONESHOT, NOTE_SECONDS, respawn_delta, j) != -1); | |
2611 | job_ignore(j); | |
ed34e3c3 A |
2612 | return; |
2613 | } | |
2614 | ||
5b0a4722 | 2615 | j->sent_sigterm_time = 0; |
ed34e3c3 | 2616 | |
5b0a4722 | 2617 | if (!j->legacy_mach_job) { |
ed34e3c3 | 2618 | sipc = (!SLIST_EMPTY(&j->sockets) || !SLIST_EMPTY(&j->machservices)); |
cf0bacfd A |
2619 | #if TARGET_OS_EMBEDDED |
2620 | if (j->username && strcmp(j->username, "mobile") == 0 && strncmp(j->label, "com.apple.", strlen("com.apple.")) != 0) { | |
2621 | sipc = false; | |
2622 | } | |
2623 | #endif | |
5b0a4722 | 2624 | } |
ed34e3c3 A |
2625 | |
2626 | j->checkedin = false; | |
2627 | ||
5b0a4722 A |
2628 | if (sipc) { |
2629 | job_assumes(j, socketpair(AF_UNIX, SOCK_STREAM, 0, spair) != -1); | |
2630 | } | |
ed34e3c3 | 2631 | |
5b0a4722 | 2632 | job_assumes(j, socketpair(AF_UNIX, SOCK_STREAM, 0, execspair) != -1); |
ed34e3c3 | 2633 | |
5b0a4722 A |
2634 | if (!j->legacy_mach_job && job_assumes(j, pipe(oepair) != -1)) { |
2635 | j->log_redirect_fd = _fd(oepair[0]); | |
2636 | job_assumes(j, fcntl(j->log_redirect_fd, F_SETFL, O_NONBLOCK) != -1); | |
2637 | job_assumes(j, kevent_mod(j->log_redirect_fd, EVFILT_READ, EV_ADD, 0, 0, j) != -1); | |
ed34e3c3 A |
2638 | } |
2639 | ||
5b0a4722 A |
2640 | j->start_time = tnow; |
2641 | ||
2642 | switch (c = runtime_fork(j->weird_bootstrap ? j->j_port : j->mgr->jm_port)) { | |
ed34e3c3 A |
2643 | case -1: |
2644 | job_log_error(j, LOG_ERR, "fork() failed, will try again in one second"); | |
5b0a4722 A |
2645 | job_assumes(j, runtime_close(execspair[0]) == 0); |
2646 | job_assumes(j, runtime_close(execspair[1]) == 0); | |
ed34e3c3 | 2647 | if (sipc) { |
5b0a4722 A |
2648 | job_assumes(j, runtime_close(spair[0]) == 0); |
2649 | job_assumes(j, runtime_close(spair[1]) == 0); | |
ed34e3c3 | 2650 | } |
fe044cc9 A |
2651 | if (!j->legacy_mach_job) { |
2652 | job_assumes(j, runtime_close(oepair[0]) != -1); | |
2653 | job_assumes(j, runtime_close(oepair[1]) != -1); | |
2654 | j->log_redirect_fd = 0; | |
2655 | } | |
26b346da A |
2656 | job_assumes(j, kevent_mod((uintptr_t)j, EVFILT_TIMER, EV_ADD|EV_ONESHOT, NOTE_SECONDS, 1, j) != -1); |
2657 | job_ignore(j); | |
ed34e3c3 A |
2658 | break; |
2659 | case 0: | |
5b0a4722 A |
2660 | if (_vproc_post_fork_ping()) { |
2661 | _exit(EXIT_FAILURE); | |
2662 | } | |
2663 | if (!j->legacy_mach_job) { | |
2664 | job_assumes(j, dup2(oepair[1], STDOUT_FILENO) != -1); | |
2665 | job_assumes(j, dup2(oepair[1], STDERR_FILENO) != -1); | |
2666 | job_assumes(j, runtime_close(oepair[1]) != -1); | |
2667 | } | |
2668 | job_assumes(j, runtime_close(execspair[0]) == 0); | |
ed34e3c3 A |
2669 | /* wait for our parent to say they've attached a kevent to us */ |
2670 | read(_fd(execspair[1]), &c, sizeof(c)); | |
ed34e3c3 A |
2671 | |
2672 | if (sipc) { | |
5b0a4722 A |
2673 | job_assumes(j, runtime_close(spair[0]) == 0); |
2674 | snprintf(nbuf, sizeof(nbuf), "%d", spair[1]); | |
ed34e3c3 A |
2675 | setenv(LAUNCHD_TRUSTED_FD_ENV, nbuf, 1); |
2676 | } | |
5b0a4722 | 2677 | job_start_child(j); |
ed34e3c3 A |
2678 | break; |
2679 | default: | |
5b0a4722 A |
2680 | job_log(j, LOG_DEBUG, "Started as PID: %u", c); |
2681 | ||
2682 | j->start_pending = false; | |
2683 | ||
2684 | runtime_add_ref(); | |
ed34e3c3 | 2685 | total_children++; |
5b0a4722 A |
2686 | LIST_INSERT_HEAD(&j->mgr->active_jobs[ACTIVE_JOB_HASH(c)], j, pid_hash_sle); |
2687 | ||
2688 | if (JOB_BOOTCACHE_HACK_CHECK(j)) { | |
2689 | did_first_per_user_launchd_BootCache_hack = true; | |
2690 | } | |
2691 | ||
2692 | if (!j->legacy_mach_job) { | |
2693 | job_assumes(j, runtime_close(oepair[1]) != -1); | |
2694 | } | |
2695 | j->p = c; | |
2696 | if (j->hopefully_exits_first) { | |
2697 | j->mgr->hopefully_first_cnt++; | |
2698 | } else if (!j->hopefully_exits_last) { | |
2699 | j->mgr->normal_active_cnt++; | |
2700 | } | |
2701 | j->forkfd = _fd(execspair[0]); | |
2702 | job_assumes(j, runtime_close(execspair[1]) == 0); | |
ed34e3c3 | 2703 | if (sipc) { |
5b0a4722 | 2704 | job_assumes(j, runtime_close(spair[1]) == 0); |
ed34e3c3 A |
2705 | ipc_open(_fd(spair[0]), j); |
2706 | } | |
5b0a4722 A |
2707 | if (job_assumes(j, kevent_mod(c, EVFILT_PROC, EV_ADD, proc_fflags, 0, root_jobmgr ? root_jobmgr : j->mgr) != -1)) { |
2708 | job_ignore(j); | |
ed34e3c3 | 2709 | } else { |
5b0a4722 | 2710 | job_reap(j); |
ed34e3c3 A |
2711 | } |
2712 | ||
2713 | if (!j->stall_before_exec) { | |
5b0a4722 | 2714 | job_uncork_fork(j); |
ed34e3c3 A |
2715 | } |
2716 | break; | |
2717 | } | |
2718 | } | |
2719 | ||
2720 | void | |
5b0a4722 A |
2721 | do_first_per_user_launchd_hack(void) |
2722 | { | |
2723 | char *bcct_tool[] = { "/usr/sbin/BootCacheControl", "tag", NULL }; | |
2724 | int dummystatus; | |
2725 | pid_t bcp; | |
2726 | ||
2727 | if (launchd_assumes((bcp = vfork()) != -1)) { | |
2728 | if (bcp == 0) { | |
2729 | execve(bcct_tool[0], bcct_tool, environ); | |
2730 | _exit(EXIT_FAILURE); | |
2731 | } else { | |
2732 | launchd_assumes(waitpid(bcp, &dummystatus, 0) != -1); | |
2733 | } | |
2734 | } | |
2735 | } | |
2736 | ||
2737 | void | |
2738 | job_start_child(job_t j) | |
ed34e3c3 A |
2739 | { |
2740 | const char *file2exec = "/usr/libexec/launchproxy"; | |
2741 | const char **argv; | |
5b0a4722 | 2742 | posix_spawnattr_t spattr; |
ed34e3c3 | 2743 | int gflags = GLOB_NOSORT|GLOB_NOCHECK|GLOB_TILDE|GLOB_DOOFFS; |
5b0a4722 | 2744 | pid_t junk_pid; |
ed34e3c3 | 2745 | glob_t g; |
5b0a4722 A |
2746 | short spflags = POSIX_SPAWN_SETEXEC; |
2747 | size_t binpref_out_cnt = 0; | |
ed34e3c3 A |
2748 | int i; |
2749 | ||
5b0a4722 A |
2750 | if (JOB_BOOTCACHE_HACK_CHECK(j)) { |
2751 | do_first_per_user_launchd_hack(); | |
2752 | } | |
2753 | ||
2754 | job_assumes(j, posix_spawnattr_init(&spattr) == 0); | |
2755 | ||
ed34e3c3 A |
2756 | job_setup_attributes(j); |
2757 | ||
2758 | if (j->argv && j->globargv) { | |
2759 | g.gl_offs = 1; | |
2760 | for (i = 0; i < j->argc; i++) { | |
5b0a4722 | 2761 | if (i > 0) { |
ed34e3c3 | 2762 | gflags |= GLOB_APPEND; |
5b0a4722 | 2763 | } |
ed34e3c3 A |
2764 | if (glob(j->argv[i], gflags, NULL, &g) != 0) { |
2765 | job_log_error(j, LOG_ERR, "glob(\"%s\")", j->argv[i]); | |
2766 | exit(EXIT_FAILURE); | |
2767 | } | |
2768 | } | |
2769 | g.gl_pathv[0] = (char *)file2exec; | |
2770 | argv = (const char **)g.gl_pathv; | |
2771 | } else if (j->argv) { | |
2772 | argv = alloca((j->argc + 2) * sizeof(char *)); | |
2773 | argv[0] = file2exec; | |
5b0a4722 | 2774 | for (i = 0; i < j->argc; i++) { |
ed34e3c3 | 2775 | argv[i + 1] = j->argv[i]; |
5b0a4722 | 2776 | } |
ed34e3c3 A |
2777 | argv[i + 1] = NULL; |
2778 | } else { | |
2779 | argv = alloca(3 * sizeof(char *)); | |
2780 | argv[0] = file2exec; | |
2781 | argv[1] = j->prog; | |
2782 | argv[2] = NULL; | |
2783 | } | |
2784 | ||
5b0a4722 | 2785 | if (!j->inetcompat) { |
ed34e3c3 | 2786 | argv++; |
5b0a4722 A |
2787 | } |
2788 | ||
2789 | if (j->wait4debugger) { | |
2790 | job_log(j, LOG_WARNING, "Spawned and waiting for the debugger to attach before continuing..."); | |
2791 | spflags |= POSIX_SPAWN_START_SUSPENDED; | |
2792 | } | |
2793 | ||
2794 | job_assumes(j, posix_spawnattr_setflags(&spattr, spflags) == 0); | |
2795 | ||
2796 | if (j->j_binpref_cnt) { | |
2797 | job_assumes(j, posix_spawnattr_setbinpref_np(&spattr, j->j_binpref_cnt, j->j_binpref, &binpref_out_cnt) == 0); | |
2798 | job_assumes(j, binpref_out_cnt == j->j_binpref_cnt); | |
2799 | } | |
2800 | ||
f36da725 | 2801 | #if HAVE_QUARANTINE |
5b0a4722 A |
2802 | if (j->quarantine_data) { |
2803 | qtn_proc_t qp; | |
ed34e3c3 | 2804 | |
5b0a4722 A |
2805 | if (job_assumes(j, qp = qtn_proc_alloc())) { |
2806 | if (job_assumes(j, qtn_proc_init_with_data(qp, j->quarantine_data, j->quarantine_data_sz) == 0)) { | |
2807 | job_assumes(j, qtn_proc_apply_to_self(qp) == 0); | |
2808 | } | |
2809 | } | |
2810 | } | |
f36da725 | 2811 | #endif |
ed34e3c3 | 2812 | |
f36da725 | 2813 | #if HAVE_SANDBOX |
5b0a4722 A |
2814 | if (j->seatbelt_profile) { |
2815 | char *seatbelt_err_buf = NULL; | |
ed34e3c3 | 2816 | |
5b0a4722 A |
2817 | if (!job_assumes(j, sandbox_init(j->seatbelt_profile, j->seatbelt_flags, &seatbelt_err_buf) != -1)) { |
2818 | if (seatbelt_err_buf) { | |
2819 | job_log(j, LOG_ERR, "Sandbox failed to init: %s", seatbelt_err_buf); | |
2820 | } | |
2821 | goto out_bad; | |
2822 | } | |
ed34e3c3 | 2823 | } |
f36da725 | 2824 | #endif |
ed34e3c3 A |
2825 | |
2826 | if (j->prog) { | |
5b0a4722 A |
2827 | errno = posix_spawn(&junk_pid, j->inetcompat ? file2exec : j->prog, NULL, &spattr, (char *const*)argv, environ); |
2828 | job_log_error(j, LOG_ERR, "posix_spawn(\"%s\", ...)", j->prog); | |
ed34e3c3 | 2829 | } else { |
5b0a4722 A |
2830 | errno = posix_spawnp(&junk_pid, j->inetcompat ? file2exec : argv[0], NULL, &spattr, (char *const*)argv, environ); |
2831 | job_log_error(j, LOG_ERR, "posix_spawnp(\"%s\", ...)", argv[0]); | |
ed34e3c3 A |
2832 | } |
2833 | ||
ef398931 | 2834 | #if HAVE_SANDBOX |
5b0a4722 | 2835 | out_bad: |
ef398931 | 2836 | #endif |
5b0a4722 | 2837 | _exit(EXIT_FAILURE); |
ed34e3c3 A |
2838 | } |
2839 | ||
5b0a4722 A |
2840 | void |
2841 | jobmgr_export_env_from_other_jobs(jobmgr_t jm, launch_data_t dict) | |
ed34e3c3 | 2842 | { |
5b0a4722 | 2843 | launch_data_t tmp; |
ed34e3c3 | 2844 | struct envitem *ei; |
5b0a4722 | 2845 | job_t ji; |
ed34e3c3 | 2846 | |
5b0a4722 A |
2847 | if (jm->parentmgr) { |
2848 | jobmgr_export_env_from_other_jobs(jm->parentmgr, dict); | |
2849 | } else { | |
2850 | char **tmpenviron = environ; | |
2851 | for (; *tmpenviron; tmpenviron++) { | |
2852 | char envkey[1024]; | |
2853 | launch_data_t s = launch_data_alloc(LAUNCH_DATA_STRING); | |
2854 | launch_data_set_string(s, strchr(*tmpenviron, '=') + 1); | |
2855 | strncpy(envkey, *tmpenviron, sizeof(envkey)); | |
2856 | *(strchr(envkey, '=')) = '\0'; | |
2857 | launch_data_dict_insert(dict, s, envkey); | |
2858 | } | |
2859 | } | |
ed34e3c3 | 2860 | |
5b0a4722 A |
2861 | LIST_FOREACH(ji, &jm->jobs, sle) { |
2862 | SLIST_FOREACH(ei, &ji->global_env, sle) { | |
2863 | if ((tmp = launch_data_new_string(ei->value))) { | |
2864 | launch_data_dict_insert(dict, tmp, ei->key); | |
2865 | } | |
2866 | } | |
2867 | } | |
ed34e3c3 A |
2868 | } |
2869 | ||
2870 | void | |
5b0a4722 | 2871 | jobmgr_setup_env_from_other_jobs(jobmgr_t jm) |
ed34e3c3 | 2872 | { |
ed34e3c3 | 2873 | struct envitem *ei; |
5b0a4722 | 2874 | job_t ji; |
ed34e3c3 | 2875 | |
5b0a4722 A |
2876 | if (jm->parentmgr) { |
2877 | jobmgr_setup_env_from_other_jobs(jm->parentmgr); | |
2878 | } | |
ed34e3c3 | 2879 | |
5b0a4722 A |
2880 | LIST_FOREACH(ji, &jm->jobs, sle) { |
2881 | SLIST_FOREACH(ei, &ji->global_env, sle) { | |
2882 | setenv(ei->key, ei->value, 1); | |
ed34e3c3 | 2883 | } |
5b0a4722 A |
2884 | } |
2885 | } | |
ed34e3c3 | 2886 | |
5b0a4722 A |
2887 | void |
2888 | job_find_and_blame_pids_with_weird_uids(job_t j) | |
2889 | { | |
2890 | int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL }; | |
f36da725 A |
2891 | size_t i, kp_cnt, len = sizeof(struct kinfo_proc) * get_kern_max_proc(); |
2892 | struct kinfo_proc *kp; | |
5b0a4722 | 2893 | uid_t u = j->mach_uid; |
ed34e3c3 | 2894 | |
f36da725 A |
2895 | #if TARGET_OS_EMBEDDED |
2896 | if (!do_apple_internal_magic) { | |
2897 | return; | |
2898 | } | |
2899 | #endif | |
2900 | kp = malloc(len); | |
2901 | ||
5b0a4722 A |
2902 | if (!job_assumes(j, kp != NULL)) { |
2903 | return; | |
2904 | } | |
2905 | if (!job_assumes(j, sysctl(mib, 3, kp, &len, NULL, 0) != -1)) { | |
2906 | goto out; | |
ed34e3c3 A |
2907 | } |
2908 | ||
5b0a4722 | 2909 | kp_cnt = len / sizeof(struct kinfo_proc); |
ed34e3c3 | 2910 | |
5b0a4722 A |
2911 | for (i = 0; i < kp_cnt; i++) { |
2912 | uid_t i_euid = kp[i].kp_eproc.e_ucred.cr_uid; | |
2913 | uid_t i_uid = kp[i].kp_eproc.e_pcred.p_ruid; | |
2914 | uid_t i_svuid = kp[i].kp_eproc.e_pcred.p_svuid; | |
2915 | pid_t i_pid = kp[i].kp_proc.p_pid; | |
2916 | ||
2917 | if (i_euid != u && i_uid != u && i_svuid != u) { | |
2918 | continue; | |
2919 | } | |
ed34e3c3 | 2920 | |
5b0a4722 A |
2921 | job_log(j, LOG_ERR, "PID %u \"%s\" has no account to back it! Real/effective/saved UIDs: %u/%u/%u", |
2922 | i_pid, kp[i].kp_proc.p_comm, i_uid, i_euid, i_svuid); | |
2923 | ||
2924 | /* Temporarily disabled due to 5423935 and 4946119. */ | |
2925 | #if 0 | |
2926 | /* Ask the accountless process to exit. */ | |
2927 | job_assumes(j, runtime_kill(i_pid, SIGTERM) != -1); | |
2928 | #endif | |
ed34e3c3 | 2929 | } |
5b0a4722 A |
2930 | |
2931 | out: | |
2932 | free(kp); | |
2933 | } | |
2934 | ||
ef398931 | 2935 | #if !TARGET_OS_EMBEDDED |
cf0bacfd A |
2936 | void |
2937 | job_enable_audit_for_user(job_t j, uid_t u, char *name) | |
2938 | { | |
2939 | auditinfo_t auinfo = { | |
2940 | .ai_auid = u, | |
2941 | .ai_asid = j->p, | |
2942 | }; | |
2943 | long au_cond; | |
2944 | ||
2945 | if (!job_assumes(j, auditon(A_GETCOND, &au_cond, sizeof(long)) == 0)) { | |
2946 | _exit(EXIT_FAILURE); | |
2947 | } | |
2948 | ||
2949 | if (au_cond != AUC_NOAUDIT) { | |
2950 | if (!job_assumes(j, au_user_mask(name, &auinfo.ai_mask) == 0)) { | |
2951 | _exit(EXIT_FAILURE); | |
2952 | } else if (!job_assumes(j, setaudit(&auinfo) == 0)) { | |
2953 | _exit(EXIT_FAILURE); | |
2954 | } | |
2955 | } | |
2956 | } | |
ef398931 | 2957 | #endif |
cf0bacfd | 2958 | |
5b0a4722 A |
2959 | void |
2960 | job_postfork_become_user(job_t j) | |
2961 | { | |
2962 | char loginname[2000]; | |
2963 | char tmpdirpath[PATH_MAX]; | |
2964 | char shellpath[PATH_MAX]; | |
2965 | char homedir[PATH_MAX]; | |
2966 | struct passwd *pwe; | |
2967 | size_t r; | |
2968 | gid_t desired_gid = -1; | |
2969 | uid_t desired_uid = -1; | |
2970 | ||
2971 | if (getuid() != 0) { | |
2972 | return; | |
2973 | } | |
2974 | ||
2975 | /* | |
2976 | * I contend that having UID == 0 and GID != 0 is of dubious value. | |
2977 | * Nevertheless, this used to work in Tiger. See: 5425348 | |
2978 | */ | |
2979 | if (j->groupname && !j->username) { | |
2980 | j->username = "root"; | |
2981 | } | |
2982 | ||
2983 | if (j->username) { | |
2984 | if ((pwe = getpwnam(j->username)) == NULL) { | |
2985 | job_log(j, LOG_ERR, "getpwnam(\"%s\") failed", j->username); | |
2986 | _exit(EXIT_FAILURE); | |
2987 | } | |
2988 | } else if (j->mach_uid) { | |
2989 | if ((pwe = getpwuid(j->mach_uid)) == NULL) { | |
2990 | job_log(j, LOG_ERR, "getpwuid(\"%u\") failed", j->mach_uid); | |
2991 | job_find_and_blame_pids_with_weird_uids(j); | |
2992 | _exit(EXIT_FAILURE); | |
2993 | } | |
2994 | } else { | |
2995 | return; | |
2996 | } | |
2997 | ||
2998 | /* | |
2999 | * We must copy the results of getpw*(). | |
3000 | * | |
3001 | * Why? Because subsequent API calls may call getpw*() as a part of | |
3002 | * their implementation. Since getpw*() returns a [now thread scoped] | |
3003 | * global, we must therefore cache the results before continuing. | |
3004 | */ | |
3005 | ||
3006 | desired_uid = pwe->pw_uid; | |
3007 | desired_gid = pwe->pw_gid; | |
3008 | ||
3009 | strlcpy(shellpath, pwe->pw_shell, sizeof(shellpath)); | |
3010 | strlcpy(loginname, pwe->pw_name, sizeof(loginname)); | |
3011 | strlcpy(homedir, pwe->pw_dir, sizeof(homedir)); | |
3012 | ||
3013 | if (pwe->pw_expire && time(NULL) >= pwe->pw_expire) { | |
3014 | job_log(j, LOG_ERR, "Expired account"); | |
3015 | _exit(EXIT_FAILURE); | |
3016 | } | |
3017 | ||
3018 | ||
3019 | if (j->username && strcmp(j->username, loginname) != 0) { | |
3020 | job_log(j, LOG_WARNING, "Suspicious setup: User \"%s\" maps to user: %s", j->username, loginname); | |
3021 | } else if (j->mach_uid && (j->mach_uid != desired_uid)) { | |
3022 | job_log(j, LOG_WARNING, "Suspicious setup: UID %u maps to UID %u", j->mach_uid, desired_uid); | |
ed34e3c3 | 3023 | } |
5b0a4722 | 3024 | |
ed34e3c3 | 3025 | if (j->groupname) { |
5b0a4722 A |
3026 | struct group *gre; |
3027 | ||
3028 | if ((gre = getgrnam(j->groupname)) == NULL) { | |
ed34e3c3 | 3029 | job_log(j, LOG_ERR, "getgrnam(\"%s\") failed", j->groupname); |
5b0a4722 | 3030 | _exit(EXIT_FAILURE); |
ed34e3c3 | 3031 | } |
5b0a4722 A |
3032 | |
3033 | desired_gid = gre->gr_gid; | |
3034 | } | |
3035 | ||
ef398931 | 3036 | #if !TARGET_OS_EMBEDDED |
cf0bacfd | 3037 | job_enable_audit_for_user(j, desired_uid, loginname); |
ef398931 | 3038 | #endif |
cf0bacfd | 3039 | |
5b0a4722 A |
3040 | if (!job_assumes(j, setlogin(loginname) != -1)) { |
3041 | _exit(EXIT_FAILURE); | |
ed34e3c3 | 3042 | } |
ed34e3c3 | 3043 | |
5b0a4722 A |
3044 | if (!job_assumes(j, setgid(desired_gid) != -1)) { |
3045 | _exit(EXIT_FAILURE); | |
3046 | } | |
ed34e3c3 | 3047 | |
5b0a4722 A |
3048 | /* |
3049 | * The kernel team and the DirectoryServices team want initgroups() | |
3050 | * called after setgid(). See 4616864 for more information. | |
3051 | */ | |
ed34e3c3 | 3052 | |
5b0a4722 A |
3053 | if (!j->no_init_groups) { |
3054 | if (!job_assumes(j, initgroups(loginname, desired_gid) != -1)) { | |
3055 | _exit(EXIT_FAILURE); | |
ed34e3c3 A |
3056 | } |
3057 | } | |
5b0a4722 A |
3058 | |
3059 | if (!job_assumes(j, setuid(desired_uid) != -1)) { | |
3060 | _exit(EXIT_FAILURE); | |
ed34e3c3 | 3061 | } |
5b0a4722 A |
3062 | |
3063 | r = confstr(_CS_DARWIN_USER_TEMP_DIR, tmpdirpath, sizeof(tmpdirpath)); | |
3064 | ||
3065 | if (r > 0 && r < sizeof(tmpdirpath)) { | |
3066 | setenv("TMPDIR", tmpdirpath, 0); | |
ed34e3c3 | 3067 | } |
5b0a4722 A |
3068 | |
3069 | setenv("SHELL", shellpath, 0); | |
3070 | setenv("HOME", homedir, 0); | |
3071 | setenv("USER", loginname, 0); | |
3072 | setenv("LOGNAME", loginname, 0); | |
3073 | } | |
3074 | ||
3075 | void | |
3076 | job_setup_attributes(job_t j) | |
3077 | { | |
3078 | struct limititem *li; | |
3079 | struct envitem *ei; | |
3080 | ||
3081 | if (j->setnice) { | |
3082 | job_assumes(j, setpriority(PRIO_PROCESS, 0, j->nice) != -1); | |
3083 | } | |
3084 | ||
3085 | SLIST_FOREACH(li, &j->limits, sle) { | |
3086 | struct rlimit rl; | |
3087 | ||
3088 | if (!job_assumes(j, getrlimit(li->which, &rl) != -1)) { | |
3089 | continue; | |
3090 | } | |
3091 | ||
3092 | if (li->sethard) { | |
3093 | rl.rlim_max = li->lim.rlim_max; | |
3094 | } | |
3095 | if (li->setsoft) { | |
3096 | rl.rlim_cur = li->lim.rlim_cur; | |
3097 | } | |
3098 | ||
3099 | if (setrlimit(li->which, &rl) == -1) { | |
3100 | job_log_error(j, LOG_WARNING, "setrlimit()"); | |
ed34e3c3 A |
3101 | } |
3102 | } | |
3103 | ||
5b0a4722 A |
3104 | if (!j->inetcompat && j->session_create) { |
3105 | launchd_SessionCreate(); | |
3106 | } | |
3107 | ||
3108 | if (j->low_pri_io) { | |
3109 | job_assumes(j, setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_PROCESS, IOPOL_THROTTLE) != -1); | |
3110 | } | |
3111 | if (j->rootdir) { | |
3112 | job_assumes(j, chroot(j->rootdir) != -1); | |
3113 | job_assumes(j, chdir(".") != -1); | |
3114 | } | |
3115 | ||
3116 | job_postfork_become_user(j); | |
3117 | ||
3118 | if (j->workingdir) { | |
3119 | job_assumes(j, chdir(j->workingdir) != -1); | |
3120 | } | |
3121 | ||
3122 | if (j->setmask) { | |
3123 | umask(j->mask); | |
3124 | } | |
3125 | ||
3126 | job_setup_fd(j, STDOUT_FILENO, j->stdoutpath, O_WRONLY|O_APPEND|O_CREAT); | |
3127 | job_setup_fd(j, STDERR_FILENO, j->stderrpath, O_WRONLY|O_APPEND|O_CREAT); | |
ed34e3c3 | 3128 | |
5b0a4722 A |
3129 | jobmgr_setup_env_from_other_jobs(j->mgr); |
3130 | ||
3131 | SLIST_FOREACH(ei, &j->env, sle) { | |
ed34e3c3 | 3132 | setenv(ei->key, ei->value, 1); |
5b0a4722 A |
3133 | } |
3134 | ||
3135 | /* | |
3136 | * We'd like to call setsid() unconditionally, but we have reason to | |
3137 | * believe that prevents launchd from being able to send signals to | |
3138 | * setuid children. We'll settle for process-groups. | |
3139 | */ | |
3140 | if (getppid() != 1) { | |
3141 | job_assumes(j, setpgid(0, 0) != -1); | |
3142 | } else { | |
3143 | job_assumes(j, setsid() != -1); | |
3144 | } | |
3145 | } | |
3146 | ||
3147 | void | |
3148 | job_setup_fd(job_t j, int target_fd, const char *path, int flags) | |
3149 | { | |
3150 | int fd; | |
3151 | ||
3152 | if (!path) { | |
3153 | return; | |
3154 | } | |
3155 | ||
3156 | if ((fd = open(path, flags|O_NOCTTY, DEFFILEMODE)) == -1) { | |
3157 | job_log_error(j, LOG_WARNING, "open(\"%s\", ...)", path); | |
3158 | return; | |
3159 | } | |
ed34e3c3 | 3160 | |
5b0a4722 A |
3161 | job_assumes(j, dup2(fd, target_fd) != -1); |
3162 | job_assumes(j, runtime_close(fd) == 0); | |
ed34e3c3 A |
3163 | } |
3164 | ||
3165 | int | |
5b0a4722 | 3166 | dir_has_files(job_t j, const char *path) |
ed34e3c3 A |
3167 | { |
3168 | DIR *dd = opendir(path); | |
3169 | struct dirent *de; | |
3170 | bool r = 0; | |
3171 | ||
5b0a4722 | 3172 | if (!dd) { |
ed34e3c3 | 3173 | return -1; |
5b0a4722 | 3174 | } |
ed34e3c3 A |
3175 | |
3176 | while ((de = readdir(dd))) { | |
3177 | if (strcmp(de->d_name, ".") && strcmp(de->d_name, "..")) { | |
3178 | r = 1; | |
3179 | break; | |
3180 | } | |
3181 | } | |
3182 | ||
5b0a4722 | 3183 | job_assumes(j, closedir(dd) == 0); |
ed34e3c3 A |
3184 | return r; |
3185 | } | |
3186 | ||
3187 | void | |
5b0a4722 | 3188 | calendarinterval_setalarm(job_t j, struct calendarinterval *ci) |
ed34e3c3 | 3189 | { |
5b0a4722 A |
3190 | struct calendarinterval *ci_iter, *ci_prev = NULL; |
3191 | time_t later, head_later; | |
ed34e3c3 A |
3192 | |
3193 | later = cronemu(ci->when.tm_mon, ci->when.tm_mday, ci->when.tm_hour, ci->when.tm_min); | |
3194 | ||
3195 | if (ci->when.tm_wday != -1) { | |
3196 | time_t otherlater = cronemu_wday(ci->when.tm_wday, ci->when.tm_hour, ci->when.tm_min); | |
3197 | ||
3198 | if (ci->when.tm_mday == -1) { | |
3199 | later = otherlater; | |
3200 | } else { | |
3201 | later = later < otherlater ? later : otherlater; | |
3202 | } | |
3203 | } | |
3204 | ||
5b0a4722 A |
3205 | ci->when_next = later; |
3206 | ||
3207 | LIST_FOREACH(ci_iter, &sorted_calendar_events, global_sle) { | |
3208 | if (ci->when_next < ci_iter->when_next) { | |
3209 | LIST_INSERT_BEFORE(ci_iter, ci, global_sle); | |
3210 | break; | |
3211 | } | |
3212 | ||
3213 | ci_prev = ci_iter; | |
ed34e3c3 | 3214 | } |
ed34e3c3 | 3215 | |
5b0a4722 A |
3216 | if (ci_iter == NULL) { |
3217 | /* ci must want to fire after every other timer, or there are no timers */ | |
ed34e3c3 | 3218 | |
5b0a4722 A |
3219 | if (LIST_EMPTY(&sorted_calendar_events)) { |
3220 | LIST_INSERT_HEAD(&sorted_calendar_events, ci, global_sle); | |
ed34e3c3 | 3221 | } else { |
5b0a4722 | 3222 | LIST_INSERT_AFTER(ci_prev, ci, global_sle); |
ed34e3c3 A |
3223 | } |
3224 | } | |
ed34e3c3 | 3225 | |
5b0a4722 | 3226 | head_later = LIST_FIRST(&sorted_calendar_events)->when_next; |
ed34e3c3 | 3227 | |
5b0a4722 A |
3228 | /* Workaround 5225889 */ |
3229 | kevent_mod((uintptr_t)&sorted_calendar_events, EVFILT_TIMER, EV_DELETE, 0, 0, root_jobmgr); | |
ed34e3c3 | 3230 | |
5b0a4722 A |
3231 | if (job_assumes(j, kevent_mod((uintptr_t)&sorted_calendar_events, EVFILT_TIMER, EV_ADD, NOTE_ABSOLUTE|NOTE_SECONDS, head_later, root_jobmgr) != -1)) { |
3232 | char time_string[100]; | |
3233 | size_t time_string_len; | |
ed34e3c3 | 3234 | |
5b0a4722 A |
3235 | ctime_r(&later, time_string); |
3236 | time_string_len = strlen(time_string); | |
ed34e3c3 | 3237 | |
5b0a4722 A |
3238 | if (time_string_len && time_string[time_string_len - 1] == '\n') { |
3239 | time_string[time_string_len - 1] = '\0'; | |
3240 | } | |
ed34e3c3 | 3241 | |
5b0a4722 A |
3242 | job_log(j, LOG_INFO, "Scheduled to run again at %s", time_string); |
3243 | } | |
ed34e3c3 A |
3244 | } |
3245 | ||
3246 | void | |
5b0a4722 | 3247 | extract_rcsid_substr(const char *i, char *o, size_t osz) |
ed34e3c3 | 3248 | { |
5b0a4722 | 3249 | char *rcs_rev_tmp = strchr(i, ' '); |
ed34e3c3 | 3250 | |
5b0a4722 A |
3251 | if (!rcs_rev_tmp) { |
3252 | strlcpy(o, i, osz); | |
3253 | } else { | |
3254 | strlcpy(o, rcs_rev_tmp + 1, osz); | |
3255 | rcs_rev_tmp = strchr(o, ' '); | |
3256 | if (rcs_rev_tmp) { | |
3257 | *rcs_rev_tmp = '\0'; | |
3258 | } | |
3259 | } | |
ed34e3c3 A |
3260 | } |
3261 | ||
5b0a4722 A |
3262 | void |
3263 | jobmgr_log_bug(jobmgr_t jm, const char *rcs_rev, const char *path, unsigned int line, const char *test) | |
ed34e3c3 | 3264 | { |
5b0a4722 A |
3265 | int saved_errno = errno; |
3266 | const char *file = strrchr(path, '/'); | |
3267 | char buf[100]; | |
ed34e3c3 | 3268 | |
5b0a4722 | 3269 | extract_rcsid_substr(rcs_rev, buf, sizeof(buf)); |
ed34e3c3 | 3270 | |
5b0a4722 A |
3271 | if (!file) { |
3272 | file = path; | |
3273 | } else { | |
3274 | file += 1; | |
3275 | } | |
ed34e3c3 | 3276 | |
5b0a4722 A |
3277 | jobmgr_log(jm, LOG_NOTICE, "Bug: %s:%u (%s):%u: %s", file, line, buf, saved_errno, test); |
3278 | } | |
ed34e3c3 A |
3279 | |
3280 | void | |
5b0a4722 | 3281 | job_log_bug(job_t j, const char *rcs_rev, const char *path, unsigned int line, const char *test) |
ed34e3c3 | 3282 | { |
5b0a4722 A |
3283 | int saved_errno = errno; |
3284 | const char *file = strrchr(path, '/'); | |
3285 | char buf[100]; | |
ed34e3c3 | 3286 | |
5b0a4722 | 3287 | extract_rcsid_substr(rcs_rev, buf, sizeof(buf)); |
ed34e3c3 | 3288 | |
5b0a4722 A |
3289 | if (!file) { |
3290 | file = path; | |
3291 | } else { | |
3292 | file += 1; | |
ed34e3c3 | 3293 | } |
5b0a4722 A |
3294 | |
3295 | job_log(j, LOG_NOTICE, "Bug: %s:%u (%s):%u: %s", file, line, buf, saved_errno, test); | |
ed34e3c3 A |
3296 | } |
3297 | ||
3298 | void | |
5b0a4722 | 3299 | job_logv(job_t j, int pri, int err, const char *msg, va_list ap) |
ed34e3c3 | 3300 | { |
5b0a4722 A |
3301 | struct runtime_syslog_attr attr = { "com.apple.launchd", j->label, j->mgr->name, pri, getuid(), getpid(), j->p }; |
3302 | char *newmsg; | |
3303 | int oldmask = 0; | |
3304 | size_t newmsgsz; | |
ed34e3c3 | 3305 | |
5b0a4722 A |
3306 | /* |
3307 | * Hack: If bootstrap_port is set, we must be on the child side of a | |
3308 | * fork(), but before the exec*(). Let's route the log message back to | |
3309 | * launchd proper. | |
3310 | */ | |
3311 | if (bootstrap_port) { | |
3312 | return _vproc_logv(pri, err, msg, ap); | |
3313 | } | |
ed34e3c3 | 3314 | |
5b0a4722 A |
3315 | newmsgsz = strlen(msg) + 200; |
3316 | newmsg = alloca(newmsgsz); | |
ed34e3c3 | 3317 | |
5b0a4722 A |
3318 | if (err) { |
3319 | snprintf(newmsg, newmsgsz, "%s: %s", msg, strerror(err)); | |
3320 | } else { | |
3321 | snprintf(newmsg, newmsgsz, "%s", msg); | |
3322 | } | |
ed34e3c3 | 3323 | |
5b0a4722 A |
3324 | if (j->debug) { |
3325 | oldmask = setlogmask(LOG_UPTO(LOG_DEBUG)); | |
3326 | } | |
ed34e3c3 | 3327 | |
5b0a4722 | 3328 | runtime_vsyslog(&attr, newmsg, ap); |
ed34e3c3 | 3329 | |
5b0a4722 A |
3330 | if (j->debug) { |
3331 | setlogmask(oldmask); | |
ed34e3c3 A |
3332 | } |
3333 | } | |
3334 | ||
3335 | void | |
5b0a4722 | 3336 | job_log_error(job_t j, int pri, const char *msg, ...) |
ed34e3c3 | 3337 | { |
5b0a4722 | 3338 | va_list ap; |
ed34e3c3 | 3339 | |
5b0a4722 A |
3340 | va_start(ap, msg); |
3341 | job_logv(j, pri, errno, msg, ap); | |
3342 | va_end(ap); | |
ed34e3c3 A |
3343 | } |
3344 | ||
5b0a4722 A |
3345 | void |
3346 | job_log(job_t j, int pri, const char *msg, ...) | |
ed34e3c3 | 3347 | { |
5b0a4722 | 3348 | va_list ap; |
ed34e3c3 | 3349 | |
5b0a4722 A |
3350 | va_start(ap, msg); |
3351 | job_logv(j, pri, 0, msg, ap); | |
3352 | va_end(ap); | |
3353 | } | |
ed34e3c3 | 3354 | |
5b0a4722 A |
3355 | #if 0 |
3356 | void | |
3357 | jobmgr_log_error(jobmgr_t jm, int pri, const char *msg, ...) | |
3358 | { | |
3359 | va_list ap; | |
ed34e3c3 | 3360 | |
5b0a4722 A |
3361 | va_start(ap, msg); |
3362 | jobmgr_logv(jm, pri, errno, msg, ap); | |
3363 | va_end(ap); | |
3364 | } | |
3365 | #endif | |
ed34e3c3 | 3366 | |
5b0a4722 A |
3367 | void |
3368 | jobmgr_log(jobmgr_t jm, int pri, const char *msg, ...) | |
3369 | { | |
3370 | va_list ap; | |
ed34e3c3 | 3371 | |
5b0a4722 A |
3372 | va_start(ap, msg); |
3373 | jobmgr_logv(jm, pri, 0, msg, ap); | |
3374 | va_end(ap); | |
ed34e3c3 A |
3375 | } |
3376 | ||
5b0a4722 A |
3377 | void |
3378 | jobmgr_logv(jobmgr_t jm, int pri, int err, const char *msg, va_list ap) | |
ed34e3c3 | 3379 | { |
5b0a4722 A |
3380 | char *newmsg; |
3381 | char *newname; | |
3382 | size_t i, o, jmname_len = strlen(jm->name), newmsgsz; | |
ed34e3c3 | 3383 | |
5b0a4722 A |
3384 | newname = alloca((jmname_len + 1) * 2); |
3385 | newmsgsz = (jmname_len + 1) * 2 + strlen(msg) + 100; | |
3386 | newmsg = alloca(newmsgsz); | |
ed34e3c3 | 3387 | |
5b0a4722 A |
3388 | for (i = 0, o = 0; i < jmname_len; i++, o++) { |
3389 | if (jm->name[i] == '%') { | |
3390 | newname[o] = '%'; | |
3391 | o++; | |
3392 | } | |
3393 | newname[o] = jm->name[i]; | |
3394 | } | |
3395 | newname[o] = '\0'; | |
ed34e3c3 | 3396 | |
5b0a4722 A |
3397 | if (err) { |
3398 | snprintf(newmsg, newmsgsz, "%s: %s: %s", newname, msg, strerror(err)); | |
3399 | } else { | |
3400 | snprintf(newmsg, newmsgsz, "%s: %s", newname, msg); | |
3401 | } | |
ed34e3c3 | 3402 | |
5b0a4722 A |
3403 | if (jm->parentmgr) { |
3404 | jobmgr_logv(jm->parentmgr, pri, 0, newmsg, ap); | |
3405 | } else { | |
3406 | struct runtime_syslog_attr attr = { "com.apple.launchd", "com.apple.launchd", jm->name, pri, getuid(), getpid(), getpid() }; | |
ed34e3c3 | 3407 | |
5b0a4722 A |
3408 | runtime_vsyslog(&attr, newmsg, ap); |
3409 | } | |
ed34e3c3 A |
3410 | } |
3411 | ||
3412 | void | |
5b0a4722 | 3413 | semaphoreitem_ignore(job_t j, struct semaphoreitem *si) |
ed34e3c3 | 3414 | { |
5b0a4722 A |
3415 | if (si->fd != -1) { |
3416 | job_log(j, LOG_DEBUG, "Ignoring Vnode: %d", si->fd); | |
3417 | job_assumes(j, kevent_mod(si->fd, EVFILT_VNODE, EV_DELETE, 0, 0, NULL) != -1); | |
3418 | } | |
ed34e3c3 A |
3419 | } |
3420 | ||
3421 | void | |
5b0a4722 | 3422 | semaphoreitem_watch(job_t j, struct semaphoreitem *si) |
ed34e3c3 | 3423 | { |
f36da725 A |
3424 | char *parentdir, tmp_path[PATH_MAX]; |
3425 | const char *which_path = si->what; | |
5b0a4722 A |
3426 | int saved_errno = 0; |
3427 | int fflags = 0; | |
ed34e3c3 | 3428 | |
5b0a4722 A |
3429 | switch (si->why) { |
3430 | case PATH_EXISTS: | |
3431 | fflags = NOTE_DELETE|NOTE_RENAME|NOTE_REVOKE|NOTE_EXTEND|NOTE_WRITE; | |
3432 | break; | |
3433 | case PATH_MISSING: | |
3434 | fflags = NOTE_DELETE|NOTE_RENAME; | |
3435 | break; | |
3436 | case DIR_NOT_EMPTY: | |
3437 | case PATH_CHANGES: | |
3438 | fflags = NOTE_DELETE|NOTE_RENAME|NOTE_REVOKE|NOTE_EXTEND|NOTE_WRITE|NOTE_ATTRIB|NOTE_LINK; | |
3439 | break; | |
3440 | default: | |
3441 | return; | |
ed34e3c3 A |
3442 | } |
3443 | ||
f36da725 A |
3444 | /* dirname() may modify tmp_path */ |
3445 | strlcpy(tmp_path, si->what, sizeof(tmp_path)); | |
3446 | ||
3447 | if (!job_assumes(j, (parentdir = dirname(tmp_path)))) { | |
3448 | return; | |
3449 | } | |
3450 | ||
5b0a4722 A |
3451 | /* See 5321044 for why we do the do-while loop and 5415523 for why ENOENT is checked */ |
3452 | do { | |
3453 | if (si->fd == -1) { | |
3454 | if ((si->fd = _fd(open(which_path, O_EVTONLY|O_NOCTTY))) == -1) { | |
f36da725 | 3455 | which_path = parentdir; |
5b0a4722 A |
3456 | si->fd = _fd(open(which_path, O_EVTONLY|O_NOCTTY)); |
3457 | } | |
3458 | } | |
3459 | ||
3460 | if (si->fd == -1) { | |
3461 | return job_log_error(j, LOG_ERR, "Path monitoring failed on \"%s\"", which_path); | |
3462 | } | |
3463 | ||
3464 | job_log(j, LOG_DEBUG, "Watching Vnode: %d", si->fd); | |
3465 | ||
3466 | if (kevent_mod(si->fd, EVFILT_VNODE, EV_ADD, fflags, 0, j) == -1) { | |
3467 | saved_errno = errno; | |
3468 | /* | |
3469 | * The FD can be revoked between the open() and kevent(). | |
3470 | * This is similar to the inability for kevents to be | |
3471 | * attached to short lived zombie processes after fork() | |
3472 | * but before kevent(). | |
3473 | */ | |
3474 | job_assumes(j, runtime_close(si->fd) == 0); | |
3475 | si->fd = -1; | |
3476 | } | |
3477 | } while ((si->fd == -1) && (saved_errno == ENOENT)); | |
3478 | ||
3479 | if (saved_errno == ENOTSUP) { | |
3480 | /* | |
3481 | * 3524219 NFS needs kqueue support | |
3482 | * 4124079 VFS needs generic kqueue support | |
3483 | * 5226811 EVFILT: Launchd EVFILT_VNODE doesn't work on /dev | |
3484 | */ | |
3485 | job_log(j, LOG_DEBUG, "Falling back to polling for path: %s", si->what); | |
3486 | ||
3487 | if (!j->poll_for_vfs_changes) { | |
3488 | j->poll_for_vfs_changes = true; | |
3489 | job_assumes(j, kevent_mod((uintptr_t)&j->semaphores, EVFILT_TIMER, EV_ADD, NOTE_SECONDS, 3, j) != -1); | |
3490 | } | |
ed34e3c3 A |
3491 | } |
3492 | } | |
3493 | ||
5b0a4722 A |
3494 | void |
3495 | semaphoreitem_callback(job_t j, struct kevent *kev) | |
ed34e3c3 | 3496 | { |
5b0a4722 A |
3497 | char invalidation_reason[100] = ""; |
3498 | struct semaphoreitem *si; | |
3499 | ||
3500 | SLIST_FOREACH(si, &j->semaphores, sle) { | |
3501 | switch (si->why) { | |
3502 | case PATH_CHANGES: | |
3503 | case PATH_EXISTS: | |
3504 | case PATH_MISSING: | |
3505 | case DIR_NOT_EMPTY: | |
3506 | break; | |
3507 | default: | |
3508 | continue; | |
3509 | } | |
3510 | ||
3511 | if (si->fd == (int)kev->ident) { | |
3512 | break; | |
3513 | } | |
3514 | } | |
3515 | ||
3516 | if (!job_assumes(j, si != NULL)) { | |
3517 | return; | |
3518 | } | |
3519 | ||
3520 | if (NOTE_DELETE & kev->fflags) { | |
3521 | strcat(invalidation_reason, "deleted"); | |
3522 | } | |
3523 | ||
3524 | if (NOTE_RENAME & kev->fflags) { | |
3525 | if (invalidation_reason[0]) { | |
3526 | strcat(invalidation_reason, "/renamed"); | |
3527 | } else { | |
3528 | strcat(invalidation_reason, "renamed"); | |
3529 | } | |
3530 | } | |
3531 | ||
3532 | if (NOTE_REVOKE & kev->fflags) { | |
3533 | if (invalidation_reason[0]) { | |
3534 | strcat(invalidation_reason, "/revoked"); | |
3535 | } else { | |
3536 | strcat(invalidation_reason, "revoked"); | |
3537 | } | |
3538 | } | |
3539 | ||
3540 | if (invalidation_reason[0]) { | |
3541 | job_log(j, LOG_DEBUG, "Path %s: %s", invalidation_reason, si->what); | |
3542 | job_assumes(j, runtime_close(si->fd) == 0); | |
3543 | si->fd = -1; /* this will get fixed in semaphoreitem_watch() */ | |
3544 | } | |
3545 | ||
3546 | job_log(j, LOG_DEBUG, "Watch path modified: %s", si->what); | |
3547 | ||
3548 | if (si->why == PATH_CHANGES) { | |
3549 | j->start_pending = true; | |
3550 | } | |
3551 | ||
3552 | job_dispatch(j, false); | |
3553 | } | |
3554 | ||
3555 | void | |
3556 | calendarinterval_new_from_obj_dict_walk(launch_data_t obj, const char *key, void *context) | |
3557 | { | |
3558 | struct tm *tmptm = context; | |
3559 | int64_t val; | |
3560 | ||
3561 | if (LAUNCH_DATA_INTEGER != launch_data_get_type(obj)) { | |
3562 | /* hack to let caller know something went wrong */ | |
3563 | tmptm->tm_sec = -1; | |
3564 | return; | |
3565 | } | |
3566 | ||
3567 | val = launch_data_get_integer(obj); | |
3568 | ||
3569 | if (strcasecmp(key, LAUNCH_JOBKEY_CAL_MINUTE) == 0) { | |
3570 | tmptm->tm_min = val; | |
3571 | } else if (strcasecmp(key, LAUNCH_JOBKEY_CAL_HOUR) == 0) { | |
3572 | tmptm->tm_hour = val; | |
3573 | } else if (strcasecmp(key, LAUNCH_JOBKEY_CAL_DAY) == 0) { | |
3574 | tmptm->tm_mday = val; | |
3575 | } else if (strcasecmp(key, LAUNCH_JOBKEY_CAL_WEEKDAY) == 0) { | |
3576 | tmptm->tm_wday = val; | |
3577 | } else if (strcasecmp(key, LAUNCH_JOBKEY_CAL_MONTH) == 0) { | |
3578 | tmptm->tm_mon = val; | |
3579 | tmptm->tm_mon -= 1; /* 4798263 cron compatibility */ | |
3580 | } | |
3581 | } | |
3582 | ||
3583 | bool | |
3584 | calendarinterval_new_from_obj(job_t j, launch_data_t obj) | |
3585 | { | |
3586 | struct tm tmptm; | |
3587 | ||
3588 | memset(&tmptm, 0, sizeof(0)); | |
3589 | ||
3590 | tmptm.tm_min = -1; | |
3591 | tmptm.tm_hour = -1; | |
3592 | tmptm.tm_mday = -1; | |
3593 | tmptm.tm_wday = -1; | |
3594 | tmptm.tm_mon = -1; | |
3595 | ||
3596 | if (!job_assumes(j, obj != NULL)) { | |
3597 | return false; | |
3598 | } | |
3599 | ||
3600 | if (LAUNCH_DATA_DICTIONARY != launch_data_get_type(obj)) { | |
3601 | return false; | |
3602 | } | |
3603 | ||
3604 | launch_data_dict_iterate(obj, calendarinterval_new_from_obj_dict_walk, &tmptm); | |
3605 | ||
3606 | if (tmptm.tm_sec == -1) { | |
3607 | return false; | |
3608 | } | |
3609 | ||
3610 | return calendarinterval_new(j, &tmptm); | |
3611 | } | |
3612 | ||
3613 | bool | |
3614 | calendarinterval_new(job_t j, struct tm *w) | |
3615 | { | |
3616 | struct calendarinterval *ci = calloc(1, sizeof(struct calendarinterval)); | |
3617 | ||
3618 | if (!job_assumes(j, ci != NULL)) { | |
3619 | return false; | |
3620 | } | |
3621 | ||
3622 | ci->when = *w; | |
3623 | ci->job = j; | |
3624 | ||
3625 | SLIST_INSERT_HEAD(&j->cal_intervals, ci, sle); | |
3626 | ||
3627 | calendarinterval_setalarm(j, ci); | |
3628 | ||
3629 | runtime_add_ref(); | |
3630 | ||
3631 | return true; | |
3632 | } | |
3633 | ||
3634 | void | |
3635 | calendarinterval_delete(job_t j, struct calendarinterval *ci) | |
3636 | { | |
3637 | SLIST_REMOVE(&j->cal_intervals, ci, calendarinterval, sle); | |
3638 | LIST_REMOVE(ci, global_sle); | |
3639 | ||
3640 | free(ci); | |
3641 | ||
3642 | runtime_del_ref(); | |
3643 | } | |
3644 | ||
3645 | void | |
3646 | calendarinterval_sanity_check(void) | |
3647 | { | |
3648 | struct calendarinterval *ci = LIST_FIRST(&sorted_calendar_events); | |
3649 | time_t now = time(NULL); | |
3650 | ||
3651 | if (ci && (ci->when_next < now)) { | |
3652 | jobmgr_assumes(root_jobmgr, raise(SIGUSR1) != -1); | |
3653 | } | |
3654 | } | |
3655 | ||
3656 | void | |
3657 | calendarinterval_callback(void) | |
3658 | { | |
3659 | struct calendarinterval *ci, *ci_next; | |
3660 | time_t now = time(NULL); | |
3661 | ||
3662 | LIST_FOREACH_SAFE(ci, &sorted_calendar_events, global_sle, ci_next) { | |
3663 | job_t j = ci->job; | |
3664 | ||
3665 | if (ci->when_next > now) { | |
3666 | break; | |
3667 | } | |
3668 | ||
3669 | LIST_REMOVE(ci, global_sle); | |
3670 | calendarinterval_setalarm(j, ci); | |
3671 | ||
3672 | j->start_pending = true; | |
3673 | job_dispatch(j, false); | |
3674 | } | |
3675 | } | |
3676 | ||
3677 | bool | |
3678 | socketgroup_new(job_t j, const char *name, int *fds, unsigned int fd_cnt, bool junkfds) | |
3679 | { | |
3680 | struct socketgroup *sg = calloc(1, sizeof(struct socketgroup) + strlen(name) + 1); | |
3681 | ||
3682 | if (!job_assumes(j, sg != NULL)) { | |
3683 | return false; | |
3684 | } | |
ed34e3c3 A |
3685 | |
3686 | sg->fds = calloc(1, fd_cnt * sizeof(int)); | |
3687 | sg->fd_cnt = fd_cnt; | |
3688 | sg->junkfds = junkfds; | |
3689 | ||
5b0a4722 | 3690 | if (!job_assumes(j, sg->fds != NULL)) { |
ed34e3c3 A |
3691 | free(sg); |
3692 | return false; | |
3693 | } | |
3694 | ||
3695 | memcpy(sg->fds, fds, fd_cnt * sizeof(int)); | |
f36da725 | 3696 | strcpy(sg->name_init, name); |
ed34e3c3 A |
3697 | |
3698 | SLIST_INSERT_HEAD(&j->sockets, sg, sle); | |
3699 | ||
5b0a4722 A |
3700 | runtime_add_ref(); |
3701 | ||
ed34e3c3 A |
3702 | return true; |
3703 | } | |
3704 | ||
3705 | void | |
5b0a4722 | 3706 | socketgroup_delete(job_t j, struct socketgroup *sg) |
ed34e3c3 A |
3707 | { |
3708 | unsigned int i; | |
3709 | ||
5b0a4722 A |
3710 | for (i = 0; i < sg->fd_cnt; i++) { |
3711 | #if 0 | |
3712 | struct sockaddr_storage ss; | |
3713 | struct sockaddr_un *sun = (struct sockaddr_un *)&ss; | |
3714 | socklen_t ss_len = sizeof(ss); | |
3715 | ||
3716 | /* 5480306 */ | |
3717 | if (job_assumes(j, getsockname(sg->fds[i], (struct sockaddr *)&ss, &ss_len) != -1) | |
3718 | && job_assumes(j, ss_len > 0) && (ss.ss_family == AF_UNIX)) { | |
3719 | job_assumes(j, unlink(sun->sun_path) != -1); | |
3720 | /* We might conditionally need to delete a directory here */ | |
3721 | } | |
3722 | #endif | |
3723 | job_assumes(j, runtime_close(sg->fds[i]) != -1); | |
3724 | } | |
ed34e3c3 A |
3725 | |
3726 | SLIST_REMOVE(&j->sockets, sg, socketgroup, sle); | |
3727 | ||
3728 | free(sg->fds); | |
3729 | free(sg); | |
5b0a4722 A |
3730 | |
3731 | runtime_del_ref(); | |
ed34e3c3 A |
3732 | } |
3733 | ||
3734 | void | |
5b0a4722 | 3735 | socketgroup_kevent_mod(job_t j, struct socketgroup *sg, bool do_add) |
ed34e3c3 | 3736 | { |
5b0a4722 | 3737 | struct kevent kev[sg->fd_cnt]; |
ed34e3c3 A |
3738 | char buf[10000]; |
3739 | unsigned int i, buf_off = 0; | |
3740 | ||
5b0a4722 | 3741 | if (sg->junkfds) { |
ed34e3c3 | 3742 | return; |
5b0a4722 A |
3743 | } |
3744 | ||
3745 | for (i = 0; i < sg->fd_cnt; i++) { | |
3746 | EV_SET(&kev[i], sg->fds[i], EVFILT_READ, do_add ? EV_ADD : EV_DELETE, 0, 0, j); | |
3747 | buf_off += snprintf(buf + buf_off, sizeof(buf) - buf_off, " %d", sg->fds[i]); | |
3748 | } | |
ed34e3c3 | 3749 | |
5b0a4722 | 3750 | job_log(j, LOG_DEBUG, "%s Sockets:%s", do_add ? "Watching" : "Ignoring", buf); |
ed34e3c3 | 3751 | |
5b0a4722 | 3752 | job_assumes(j, kevent_bulk_mod(kev, sg->fd_cnt) != -1); |
ed34e3c3 | 3753 | |
5b0a4722 A |
3754 | for (i = 0; i < sg->fd_cnt; i++) { |
3755 | job_assumes(j, kev[i].flags & EV_ERROR); | |
3756 | errno = kev[i].data; | |
3757 | job_assumes(j, kev[i].data == 0); | |
3758 | } | |
ed34e3c3 A |
3759 | } |
3760 | ||
3761 | void | |
5b0a4722 | 3762 | socketgroup_ignore(job_t j, struct socketgroup *sg) |
ed34e3c3 | 3763 | { |
5b0a4722 A |
3764 | socketgroup_kevent_mod(j, sg, false); |
3765 | } | |
ed34e3c3 | 3766 | |
5b0a4722 A |
3767 | void |
3768 | socketgroup_watch(job_t j, struct socketgroup *sg) | |
3769 | { | |
3770 | socketgroup_kevent_mod(j, sg, true); | |
ed34e3c3 A |
3771 | } |
3772 | ||
3773 | void | |
5b0a4722 | 3774 | socketgroup_callback(job_t j) |
ed34e3c3 | 3775 | { |
5b0a4722 | 3776 | job_dispatch(j, true); |
ed34e3c3 A |
3777 | } |
3778 | ||
3779 | bool | |
5b0a4722 | 3780 | envitem_new(job_t j, const char *k, const char *v, bool global) |
ed34e3c3 A |
3781 | { |
3782 | struct envitem *ei = calloc(1, sizeof(struct envitem) + strlen(k) + 1 + strlen(v) + 1); | |
3783 | ||
5b0a4722 | 3784 | if (!job_assumes(j, ei != NULL)) { |
ed34e3c3 | 3785 | return false; |
5b0a4722 | 3786 | } |
ed34e3c3 | 3787 | |
f36da725 A |
3788 | strcpy(ei->key_init, k); |
3789 | ei->value = ei->key_init + strlen(k) + 1; | |
ed34e3c3 A |
3790 | strcpy(ei->value, v); |
3791 | ||
3792 | if (global) { | |
3793 | SLIST_INSERT_HEAD(&j->global_env, ei, sle); | |
3794 | } else { | |
3795 | SLIST_INSERT_HEAD(&j->env, ei, sle); | |
3796 | } | |
3797 | ||
5b0a4722 A |
3798 | job_log(j, LOG_DEBUG, "Added environmental variable: %s=%s", k, v); |
3799 | ||
ed34e3c3 A |
3800 | return true; |
3801 | } | |
3802 | ||
3803 | void | |
5b0a4722 | 3804 | envitem_delete(job_t j, struct envitem *ei, bool global) |
ed34e3c3 A |
3805 | { |
3806 | if (global) { | |
3807 | SLIST_REMOVE(&j->global_env, ei, envitem, sle); | |
3808 | } else { | |
3809 | SLIST_REMOVE(&j->env, ei, envitem, sle); | |
3810 | } | |
3811 | ||
3812 | free(ei); | |
3813 | } | |
3814 | ||
3815 | void | |
3816 | envitem_setup(launch_data_t obj, const char *key, void *context) | |
3817 | { | |
5b0a4722 | 3818 | job_t j = context; |
ed34e3c3 | 3819 | |
5b0a4722 | 3820 | if (launch_data_get_type(obj) != LAUNCH_DATA_STRING) { |
ed34e3c3 | 3821 | return; |
5b0a4722 | 3822 | } |
ed34e3c3 A |
3823 | |
3824 | envitem_new(j, key, launch_data_get_string(obj), j->importing_global_env); | |
3825 | } | |
3826 | ||
3827 | bool | |
5b0a4722 | 3828 | limititem_update(job_t j, int w, rlim_t r) |
ed34e3c3 A |
3829 | { |
3830 | struct limititem *li; | |
3831 | ||
3832 | SLIST_FOREACH(li, &j->limits, sle) { | |
5b0a4722 | 3833 | if (li->which == w) { |
ed34e3c3 | 3834 | break; |
5b0a4722 | 3835 | } |
ed34e3c3 A |
3836 | } |
3837 | ||
3838 | if (li == NULL) { | |
3839 | li = calloc(1, sizeof(struct limititem)); | |
3840 | ||
5b0a4722 | 3841 | if (!job_assumes(j, li != NULL)) { |
ed34e3c3 | 3842 | return false; |
5b0a4722 A |
3843 | } |
3844 | ||
3845 | SLIST_INSERT_HEAD(&j->limits, li, sle); | |
ed34e3c3 A |
3846 | |
3847 | li->which = w; | |
3848 | } | |
3849 | ||
3850 | if (j->importing_hard_limits) { | |
3851 | li->lim.rlim_max = r; | |
3852 | li->sethard = true; | |
3853 | } else { | |
3854 | li->lim.rlim_cur = r; | |
3855 | li->setsoft = true; | |
3856 | } | |
3857 | ||
3858 | return true; | |
3859 | } | |
3860 | ||
3861 | void | |
5b0a4722 | 3862 | limititem_delete(job_t j, struct limititem *li) |
ed34e3c3 A |
3863 | { |
3864 | SLIST_REMOVE(&j->limits, li, limititem, sle); | |
3865 | ||
3866 | free(li); | |
3867 | } | |
3868 | ||
f36da725 | 3869 | #if HAVE_SANDBOX |
5b0a4722 A |
3870 | void |
3871 | seatbelt_setup_flags(launch_data_t obj, const char *key, void *context) | |
3872 | { | |
3873 | job_t j = context; | |
3874 | ||
3875 | if (launch_data_get_type(obj) != LAUNCH_DATA_BOOL) { | |
3876 | job_log(j, LOG_WARNING, "Sandbox flag value must be boolean: %s", key); | |
3877 | return; | |
3878 | } | |
3879 | ||
3880 | if (launch_data_get_bool(obj) == false) { | |
3881 | return; | |
3882 | } | |
3883 | ||
3884 | if (strcasecmp(key, LAUNCH_JOBKEY_SANDBOX_NAMED) == 0) { | |
3885 | j->seatbelt_flags |= SANDBOX_NAMED; | |
3886 | } | |
3887 | } | |
f36da725 | 3888 | #endif |
5b0a4722 | 3889 | |
ed34e3c3 A |
3890 | void |
3891 | limititem_setup(launch_data_t obj, const char *key, void *context) | |
3892 | { | |
5b0a4722 | 3893 | job_t j = context; |
ed34e3c3 A |
3894 | int i, limits_cnt = (sizeof(launchd_keys2limits) / sizeof(launchd_keys2limits[0])); |
3895 | rlim_t rl; | |
3896 | ||
5b0a4722 | 3897 | if (launch_data_get_type(obj) != LAUNCH_DATA_INTEGER) { |
ed34e3c3 | 3898 | return; |
5b0a4722 | 3899 | } |
ed34e3c3 A |
3900 | |
3901 | rl = launch_data_get_integer(obj); | |
3902 | ||
3903 | for (i = 0; i < limits_cnt; i++) { | |
5b0a4722 | 3904 | if (strcasecmp(launchd_keys2limits[i].key, key) == 0) { |
ed34e3c3 | 3905 | break; |
5b0a4722 | 3906 | } |
ed34e3c3 A |
3907 | } |
3908 | ||
5b0a4722 | 3909 | if (i == limits_cnt) { |
ed34e3c3 | 3910 | return; |
5b0a4722 | 3911 | } |
ed34e3c3 A |
3912 | |
3913 | limititem_update(j, launchd_keys2limits[i].val, rl); | |
3914 | } | |
3915 | ||
3916 | bool | |
5b0a4722 | 3917 | job_useless(job_t j) |
ed34e3c3 | 3918 | { |
5b0a4722 A |
3919 | /* Yes, j->unload_at_exit and j->only_once seem the same, but they'll differ someday... */ |
3920 | ||
3921 | if ((j->unload_at_exit || j->only_once) && j->start_time != 0) { | |
3922 | if (j->unload_at_exit && j->j_port) { | |
3923 | return false; | |
3924 | } | |
ed34e3c3 A |
3925 | job_log(j, LOG_INFO, "Exited. Was only configured to run once."); |
3926 | return true; | |
5b0a4722 A |
3927 | } else if (j->removal_pending) { |
3928 | job_log(j, LOG_DEBUG, "Exited while removal was pending."); | |
ed34e3c3 | 3929 | return true; |
f36da725 | 3930 | } else if (j->mgr->shutting_down && (j->hopefully_exits_first || j->mgr->hopefully_first_cnt == 0)) { |
5b0a4722 | 3931 | job_log(j, LOG_DEBUG, "Exited while shutdown in progress. Processes remaining: %lu/%lu", total_children, total_anon_children); |
ed34e3c3 | 3932 | return true; |
5b0a4722 A |
3933 | } else if (j->legacy_mach_job) { |
3934 | if (SLIST_EMPTY(&j->machservices)) { | |
3935 | job_log(j, LOG_INFO, "Garbage collecting"); | |
3936 | return true; | |
3937 | } else if (!j->checkedin) { | |
3938 | job_log(j, LOG_WARNING, "Failed to check-in!"); | |
3939 | return true; | |
3940 | } | |
ed34e3c3 A |
3941 | } |
3942 | ||
3943 | return false; | |
3944 | } | |
3945 | ||
3946 | bool | |
5b0a4722 | 3947 | job_keepalive(job_t j) |
ed34e3c3 A |
3948 | { |
3949 | mach_msg_type_number_t statusCnt; | |
3950 | mach_port_status_t status; | |
3951 | struct semaphoreitem *si; | |
3952 | struct machservice *ms; | |
3953 | struct stat sb; | |
3954 | bool good_exit = (WIFEXITED(j->last_exit_status) && WEXITSTATUS(j->last_exit_status) == 0); | |
ed34e3c3 | 3955 | |
f36da725 A |
3956 | if (j->mgr->shutting_down) { |
3957 | return false; | |
3958 | } | |
3959 | ||
5b0a4722 A |
3960 | /* |
3961 | * 5066316 | |
3962 | * | |
3963 | * We definitely need to revisit this after Leopard ships. Please see | |
3964 | * launchctl.c for the other half of this hack. | |
3965 | */ | |
3966 | if (j->mgr->global_on_demand_cnt > 0 && strcmp(j->label, "com.apple.kextd") != 0) { | |
3967 | return false; | |
3968 | } | |
3969 | ||
3970 | if (j->start_pending) { | |
3971 | job_log(j, LOG_DEBUG, "KeepAlive check: Pent-up non-IPC launch criteria."); | |
ed34e3c3 A |
3972 | return true; |
3973 | } | |
3974 | ||
3975 | if (!j->ondemand) { | |
3976 | job_log(j, LOG_DEBUG, "KeepAlive check: job configured to run continuously."); | |
3977 | return true; | |
3978 | } | |
3979 | ||
3980 | SLIST_FOREACH(ms, &j->machservices, sle) { | |
3981 | statusCnt = MACH_PORT_RECEIVE_STATUS_COUNT; | |
3982 | if (mach_port_get_attributes(mach_task_self(), ms->port, MACH_PORT_RECEIVE_STATUS, | |
5b0a4722 | 3983 | (mach_port_info_t)&status, &statusCnt) != KERN_SUCCESS) { |
ed34e3c3 | 3984 | continue; |
5b0a4722 | 3985 | } |
ed34e3c3 A |
3986 | if (status.mps_msgcount) { |
3987 | job_log(j, LOG_DEBUG, "KeepAlive check: job restarted due to %d queued Mach messages on service: %s", | |
3988 | status.mps_msgcount, ms->name); | |
3989 | return true; | |
3990 | } | |
3991 | } | |
3992 | ||
3993 | ||
3994 | SLIST_FOREACH(si, &j->semaphores, sle) { | |
3995 | bool wanted_state = false; | |
5b0a4722 A |
3996 | int qdir_file_cnt; |
3997 | job_t other_j; | |
3998 | ||
ed34e3c3 A |
3999 | switch (si->why) { |
4000 | case NETWORK_UP: | |
4001 | wanted_state = true; | |
4002 | case NETWORK_DOWN: | |
4003 | if (network_up == wanted_state) { | |
5b0a4722 | 4004 | job_log(j, LOG_DEBUG, "KeepAlive: The network is %s.", wanted_state ? "up" : "down"); |
ed34e3c3 A |
4005 | return true; |
4006 | } | |
4007 | break; | |
4008 | case SUCCESSFUL_EXIT: | |
4009 | wanted_state = true; | |
4010 | case FAILED_EXIT: | |
4011 | if (good_exit == wanted_state) { | |
5b0a4722 A |
4012 | job_log(j, LOG_DEBUG, "KeepAlive: The exit state was %s.", wanted_state ? "successful" : "failure"); |
4013 | return true; | |
4014 | } | |
4015 | break; | |
4016 | case OTHER_JOB_ENABLED: | |
4017 | wanted_state = true; | |
4018 | case OTHER_JOB_DISABLED: | |
4019 | if ((bool)job_find(si->what) == wanted_state) { | |
4020 | job_log(j, LOG_DEBUG, "KeepAlive: The following job is %s: %s", wanted_state ? "enabled" : "disabled", si->what); | |
ed34e3c3 A |
4021 | return true; |
4022 | } | |
4023 | break; | |
5b0a4722 A |
4024 | case OTHER_JOB_ACTIVE: |
4025 | wanted_state = true; | |
4026 | case OTHER_JOB_INACTIVE: | |
4027 | if ((other_j = job_find(si->what))) { | |
4028 | if ((bool)other_j->p == wanted_state) { | |
4029 | job_log(j, LOG_DEBUG, "KeepAlive: The following job is %s: %s", wanted_state ? "active" : "inactive", si->what); | |
4030 | return true; | |
4031 | } | |
4032 | } | |
4033 | break; | |
ed34e3c3 A |
4034 | case PATH_EXISTS: |
4035 | wanted_state = true; | |
4036 | case PATH_MISSING: | |
4037 | if ((bool)(stat(si->what, &sb) == 0) == wanted_state) { | |
5b0a4722 A |
4038 | if (si->fd != -1) { |
4039 | job_assumes(j, runtime_close(si->fd) == 0); | |
4040 | si->fd = -1; | |
4041 | } | |
4042 | job_log(j, LOG_DEBUG, "KeepAlive: The following path %s: %s", wanted_state ? "exists" : "is missing", si->what); | |
4043 | return true; | |
4044 | } | |
4045 | break; | |
4046 | case PATH_CHANGES: | |
4047 | break; | |
4048 | case DIR_NOT_EMPTY: | |
4049 | if (-1 == (qdir_file_cnt = dir_has_files(j, si->what))) { | |
4050 | job_log_error(j, LOG_ERR, "Failed to count the number of files in \"%s\"", si->what); | |
4051 | } else if (qdir_file_cnt > 0) { | |
4052 | job_log(j, LOG_DEBUG, "KeepAlive: Directory is not empty: %s", si->what); | |
ed34e3c3 A |
4053 | return true; |
4054 | } | |
ed34e3c3 A |
4055 | break; |
4056 | } | |
4057 | } | |
4058 | ||
ed34e3c3 A |
4059 | return false; |
4060 | } | |
4061 | ||
4062 | const char * | |
5b0a4722 | 4063 | job_prog(job_t j) |
ed34e3c3 A |
4064 | { |
4065 | if (j->prog) { | |
4066 | return j->prog; | |
4067 | } else if (j->argv) { | |
4068 | return j->argv[0]; | |
4069 | } else { | |
4070 | return ""; | |
4071 | } | |
4072 | } | |
4073 | ||
5b0a4722 A |
4074 | const char * |
4075 | job_active(job_t j) | |
ed34e3c3 A |
4076 | { |
4077 | struct machservice *ms; | |
4078 | ||
5b0a4722 A |
4079 | if (j->p) { |
4080 | return "PID is still valid"; | |
4081 | } | |
ed34e3c3 | 4082 | |
5b0a4722 A |
4083 | if (j->mgr->shutting_down && j->log_redirect_fd) { |
4084 | job_assumes(j, runtime_close(j->log_redirect_fd) != -1); | |
4085 | j->log_redirect_fd = 0; | |
4086 | } | |
ed34e3c3 | 4087 | |
5b0a4722 A |
4088 | if (j->log_redirect_fd) { |
4089 | if (job_assumes(j, j->wait4pipe_eof)) { | |
4090 | return "Standard out/error is still valid"; | |
4091 | } else { | |
4092 | job_assumes(j, runtime_close(j->log_redirect_fd) != -1); | |
4093 | j->log_redirect_fd = 0; | |
ed34e3c3 | 4094 | } |
5b0a4722 A |
4095 | } |
4096 | ||
4097 | if (j->priv_port_has_senders) { | |
4098 | return "Privileged Port still has outstanding senders"; | |
ed34e3c3 A |
4099 | } |
4100 | ||
4101 | SLIST_FOREACH(ms, &j->machservices, sle) { | |
5b0a4722 A |
4102 | if (ms->recv && ms->isActive) { |
4103 | return "Mach service is still active"; | |
4104 | } | |
ed34e3c3 A |
4105 | } |
4106 | ||
5b0a4722 | 4107 | return NULL; |
ed34e3c3 A |
4108 | } |
4109 | ||
5b0a4722 A |
4110 | void |
4111 | machservice_watch(job_t j, struct machservice *ms) | |
ed34e3c3 | 4112 | { |
5b0a4722 A |
4113 | if (ms->recv) { |
4114 | job_assumes(j, runtime_add_mport(ms->port, NULL, 0) == KERN_SUCCESS); | |
4115 | } | |
ed34e3c3 A |
4116 | } |
4117 | ||
5b0a4722 A |
4118 | void |
4119 | machservice_ignore(job_t j, struct machservice *ms) | |
ed34e3c3 | 4120 | { |
5b0a4722 | 4121 | job_assumes(j, runtime_remove_mport(ms->port) == KERN_SUCCESS); |
ed34e3c3 A |
4122 | } |
4123 | ||
4124 | void | |
5b0a4722 | 4125 | machservice_resetport(job_t j, struct machservice *ms) |
ed34e3c3 | 4126 | { |
5b0a4722 A |
4127 | LIST_REMOVE(ms, port_hash_sle); |
4128 | job_assumes(j, launchd_mport_close_recv(ms->port) == KERN_SUCCESS); | |
4129 | job_assumes(j, launchd_mport_deallocate(ms->port) == KERN_SUCCESS); | |
4130 | ms->gen_num++; | |
4131 | job_assumes(j, launchd_mport_create_recv(&ms->port) == KERN_SUCCESS); | |
4132 | job_assumes(j, launchd_mport_make_send(ms->port) == KERN_SUCCESS); | |
4133 | LIST_INSERT_HEAD(&port_hash[HASH_PORT(ms->port)], ms, port_hash_sle); | |
ed34e3c3 A |
4134 | } |
4135 | ||
4136 | struct machservice * | |
5b0a4722 | 4137 | machservice_new(job_t j, const char *name, mach_port_t *serviceport, bool pid_local) |
ed34e3c3 A |
4138 | { |
4139 | struct machservice *ms; | |
4140 | ||
5b0a4722 | 4141 | if ((ms = calloc(1, sizeof(struct machservice) + strlen(name) + 1)) == NULL) { |
ed34e3c3 | 4142 | return NULL; |
5b0a4722 | 4143 | } |
ed34e3c3 | 4144 | |
5b0a4722 | 4145 | strcpy((char *)ms->name, name); |
ed34e3c3 | 4146 | ms->job = j; |
5b0a4722 | 4147 | ms->per_pid = pid_local; |
ed34e3c3 A |
4148 | |
4149 | if (*serviceport == MACH_PORT_NULL) { | |
5b0a4722 | 4150 | if (!job_assumes(j, launchd_mport_create_recv(&ms->port) == KERN_SUCCESS)) { |
ed34e3c3 | 4151 | goto out_bad; |
5b0a4722 | 4152 | } |
ed34e3c3 | 4153 | |
5b0a4722 | 4154 | if (!job_assumes(j, launchd_mport_make_send(ms->port) == KERN_SUCCESS)) { |
ed34e3c3 | 4155 | goto out_bad2; |
5b0a4722 | 4156 | } |
ed34e3c3 | 4157 | *serviceport = ms->port; |
ed34e3c3 A |
4158 | ms->recv = true; |
4159 | } else { | |
4160 | ms->port = *serviceport; | |
4161 | ms->isActive = true; | |
4162 | } | |
4163 | ||
4164 | SLIST_INSERT_HEAD(&j->machservices, ms, sle); | |
5b0a4722 A |
4165 | LIST_INSERT_HEAD(&j->mgr->ms_hash[hash_ms(ms->name)], ms, name_hash_sle); |
4166 | LIST_INSERT_HEAD(&port_hash[HASH_PORT(ms->port)], ms, port_hash_sle); | |
ed34e3c3 A |
4167 | |
4168 | job_log(j, LOG_INFO, "Mach service added: %s", name); | |
4169 | ||
4170 | return ms; | |
4171 | out_bad2: | |
5b0a4722 | 4172 | job_assumes(j, launchd_mport_close_recv(ms->port) == KERN_SUCCESS); |
ed34e3c3 A |
4173 | out_bad: |
4174 | free(ms); | |
4175 | return NULL; | |
4176 | } | |
4177 | ||
4178 | bootstrap_status_t | |
4179 | machservice_status(struct machservice *ms) | |
4180 | { | |
4181 | if (ms->isActive) { | |
4182 | return BOOTSTRAP_STATUS_ACTIVE; | |
4183 | } else if (ms->job->ondemand) { | |
4184 | return BOOTSTRAP_STATUS_ON_DEMAND; | |
4185 | } else { | |
4186 | return BOOTSTRAP_STATUS_INACTIVE; | |
4187 | } | |
4188 | } | |
4189 | ||
4190 | void | |
5b0a4722 | 4191 | job_setup_exception_port(job_t j, task_t target_task) |
ed34e3c3 | 4192 | { |
fe044cc9 | 4193 | struct machservice *ms; |
ed34e3c3 | 4194 | thread_state_flavor_t f = 0; |
fe044cc9 | 4195 | mach_port_t exc_port = the_exception_server; |
5b0a4722 | 4196 | |
fe044cc9 A |
4197 | if (j->alt_exc_handler) { |
4198 | ms = jobmgr_lookup_service(j->mgr, j->alt_exc_handler, true, 0); | |
4199 | if (ms) { | |
4200 | exc_port = machservice_port(ms); | |
4201 | } else { | |
4202 | job_log(j, LOG_WARNING, "Falling back to default Mach exception handler. Could not find: %s", j->alt_exc_handler); | |
4203 | } | |
4204 | } else if (j->internal_exc_handler) { | |
4205 | exc_port = runtime_get_kernel_port(); | |
4206 | } else if (!exc_port) { | |
5b0a4722 A |
4207 | return; |
4208 | } | |
ed34e3c3 | 4209 | |
ef398931 | 4210 | #if defined (__ppc__) || defined (__ppc64__) |
ed34e3c3 | 4211 | f = PPC_THREAD_STATE64; |
ef398931 | 4212 | #elif defined(__i386__) || defined(__x86_64__) |
ed34e3c3 | 4213 | f = x86_THREAD_STATE; |
f36da725 A |
4214 | #elif defined(__arm__) |
4215 | f = ARM_THREAD_STATE; | |
4216 | #else | |
4217 | #error "unknown architecture" | |
ed34e3c3 A |
4218 | #endif |
4219 | ||
5b0a4722 | 4220 | if (target_task) { |
fe044cc9 | 4221 | job_assumes(j, task_set_exception_ports(target_task, EXC_MASK_CRASH, exc_port, |
5b0a4722 | 4222 | EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES, f) == KERN_SUCCESS); |
fe044cc9 | 4223 | } else if (getpid() == 1 && the_exception_server) { |
5b0a4722 A |
4224 | mach_port_t mhp = mach_host_self(); |
4225 | job_assumes(j, host_set_exception_ports(mhp, EXC_MASK_CRASH, the_exception_server, | |
4226 | EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES, f) == KERN_SUCCESS); | |
4227 | job_assumes(j, launchd_mport_deallocate(mhp) == KERN_SUCCESS); | |
4228 | } | |
4229 | ||
4230 | } | |
4231 | ||
4232 | void | |
4233 | job_set_exeception_port(job_t j, mach_port_t port) | |
4234 | { | |
4235 | if (!the_exception_server) { | |
4236 | the_exception_server = port; | |
4237 | job_setup_exception_port(j, 0); | |
4238 | } else { | |
4239 | job_log(j, LOG_WARNING, "The exception server is already claimed!"); | |
4240 | } | |
4241 | } | |
4242 | ||
4243 | void | |
4244 | machservice_setup_options(launch_data_t obj, const char *key, void *context) | |
4245 | { | |
4246 | struct machservice *ms = context; | |
4247 | mach_port_t mhp = mach_host_self(); | |
4248 | int which_port; | |
4249 | bool b; | |
4250 | ||
4251 | if (!job_assumes(ms->job, mhp != MACH_PORT_NULL)) { | |
ed34e3c3 A |
4252 | return; |
4253 | } | |
4254 | ||
4255 | switch (launch_data_get_type(obj)) { | |
4256 | case LAUNCH_DATA_INTEGER: | |
4257 | which_port = launch_data_get_integer(obj); | |
4258 | if (strcasecmp(key, LAUNCH_JOBKEY_MACH_TASKSPECIALPORT) == 0) { | |
5b0a4722 A |
4259 | switch (which_port) { |
4260 | case TASK_KERNEL_PORT: | |
4261 | case TASK_HOST_PORT: | |
4262 | case TASK_NAME_PORT: | |
4263 | case TASK_BOOTSTRAP_PORT: | |
4264 | /* I find it a little odd that zero isn't reserved in the header */ | |
4265 | case 0: | |
4266 | job_log(ms->job, LOG_WARNING, "Tried to set a reserved task special port: %d", which_port); | |
4267 | break; | |
4268 | default: | |
4269 | ms->special_port_num = which_port; | |
4270 | SLIST_INSERT_HEAD(&special_ports, ms, special_port_sle); | |
4271 | break; | |
4272 | } | |
ed34e3c3 | 4273 | } else if (strcasecmp(key, LAUNCH_JOBKEY_MACH_HOSTSPECIALPORT) == 0 && getpid() == 1) { |
5b0a4722 A |
4274 | if (which_port > HOST_MAX_SPECIAL_KERNEL_PORT) { |
4275 | job_assumes(ms->job, (errno = host_set_special_port(mhp, which_port, ms->port)) == KERN_SUCCESS); | |
4276 | } else { | |
4277 | job_log(ms->job, LOG_WARNING, "Tried to set a reserved host special port: %d", which_port); | |
4278 | } | |
ed34e3c3 A |
4279 | } |
4280 | case LAUNCH_DATA_BOOL: | |
4281 | b = launch_data_get_bool(obj); | |
5b0a4722 A |
4282 | if (strcasecmp(key, LAUNCH_JOBKEY_MACH_ENTERKERNELDEBUGGERONCLOSE) == 0) { |
4283 | ms->debug_on_close = b; | |
4284 | } else if (strcasecmp(key, LAUNCH_JOBKEY_MACH_RESETATCLOSE) == 0) { | |
ed34e3c3 A |
4285 | ms->reset = b; |
4286 | } else if (strcasecmp(key, LAUNCH_JOBKEY_MACH_HIDEUNTILCHECKIN) == 0) { | |
4287 | ms->hide = b; | |
4288 | } else if (strcasecmp(key, LAUNCH_JOBKEY_MACH_EXCEPTIONSERVER) == 0) { | |
5b0a4722 | 4289 | job_set_exeception_port(ms->job, ms->port); |
ed34e3c3 A |
4290 | } else if (strcasecmp(key, LAUNCH_JOBKEY_MACH_KUNCSERVER) == 0) { |
4291 | ms->kUNCServer = b; | |
5b0a4722 | 4292 | job_assumes(ms->job, host_set_UNDServer(mhp, ms->port) == KERN_SUCCESS); |
ed34e3c3 A |
4293 | } |
4294 | break; | |
5b0a4722 A |
4295 | case LAUNCH_DATA_DICTIONARY: |
4296 | job_set_exeception_port(ms->job, ms->port); | |
4297 | break; | |
ed34e3c3 A |
4298 | default: |
4299 | break; | |
4300 | } | |
4301 | ||
5b0a4722 | 4302 | job_assumes(ms->job, launchd_mport_deallocate(mhp) == KERN_SUCCESS); |
ed34e3c3 A |
4303 | } |
4304 | ||
4305 | void | |
4306 | machservice_setup(launch_data_t obj, const char *key, void *context) | |
4307 | { | |
5b0a4722 | 4308 | job_t j = context; |
ed34e3c3 A |
4309 | struct machservice *ms; |
4310 | mach_port_t p = MACH_PORT_NULL; | |
4311 | ||
5b0a4722 | 4312 | if ((ms = jobmgr_lookup_service(j->mgr, key, false, 0))) { |
ed34e3c3 A |
4313 | job_log(j, LOG_WARNING, "Conflict with job: %s over Mach service: %s", ms->job->label, key); |
4314 | return; | |
4315 | } | |
4316 | ||
5b0a4722 | 4317 | if ((ms = machservice_new(j, key, &p, false)) == NULL) { |
ed34e3c3 A |
4318 | job_log_error(j, LOG_WARNING, "Cannot add service: %s", key); |
4319 | return; | |
4320 | } | |
4321 | ||
4322 | ms->isActive = false; | |
4323 | ||
4324 | if (launch_data_get_type(obj) == LAUNCH_DATA_DICTIONARY) { | |
4325 | launch_data_dict_iterate(obj, machservice_setup_options, ms); | |
4326 | } | |
4327 | } | |
4328 | ||
5b0a4722 A |
4329 | jobmgr_t |
4330 | jobmgr_do_garbage_collection(jobmgr_t jm) | |
ed34e3c3 | 4331 | { |
5b0a4722 A |
4332 | jobmgr_t jmi, jmn; |
4333 | job_t ji, jn; | |
ed34e3c3 | 4334 | |
5b0a4722 A |
4335 | SLIST_FOREACH_SAFE(jmi, &jm->submgrs, sle, jmn) { |
4336 | jobmgr_do_garbage_collection(jmi); | |
4337 | } | |
ed34e3c3 | 4338 | |
5b0a4722 A |
4339 | if (!jm->shutting_down) { |
4340 | return jm; | |
4341 | } | |
4342 | ||
4343 | jobmgr_log(jm, LOG_DEBUG, "Garbage collecting."); | |
4344 | ||
4345 | /* | |
4346 | * Normally, we wait for all resources of a job (Unix PIDs/FDs and Mach ports) | |
4347 | * to reset before we conider the job truly dead and ready to be spawned again. | |
4348 | * | |
4349 | * In order to work around 5487724 and 3456090, we're going to call reboot() | |
4350 | * when the last PID dies and not wait for the associated resources to reset. | |
4351 | */ | |
4352 | if (getpid() == 1 && jm->parentmgr == NULL && total_children == 0) { | |
4353 | jobmgr_log(jm, LOG_DEBUG, "About to force a call to: reboot(%s)", reboot_flags_to_C_names(jm->reboot_flags)); | |
4354 | runtime_closelog(); | |
4355 | jobmgr_assumes(jm, reboot(jm->reboot_flags) != -1); | |
4356 | } | |
4357 | ||
4358 | if (jm->hopefully_first_cnt) { | |
4359 | return jm; | |
4360 | } | |
4361 | ||
4362 | if (jm->parentmgr && jm->parentmgr->shutting_down && jm->parentmgr->hopefully_first_cnt) { | |
4363 | return jm; | |
4364 | } | |
4365 | ||
4366 | if (!jm->sent_stop_to_normal_jobs) { | |
4367 | jobmgr_log(jm, LOG_DEBUG, "Asking \"normal\" jobs to exit."); | |
4368 | ||
4369 | LIST_FOREACH_SAFE(ji, &jm->jobs, sle, jn) { | |
4370 | if (!job_active(ji)) { | |
4371 | job_remove(ji); | |
4372 | } else if (!ji->hopefully_exits_last) { | |
4373 | job_stop(ji); | |
4374 | } | |
4375 | } | |
4376 | ||
4377 | jm->sent_stop_to_normal_jobs = true; | |
ed34e3c3 | 4378 | } |
5b0a4722 A |
4379 | |
4380 | if (jm->normal_active_cnt) { | |
4381 | return jm; | |
4382 | } | |
4383 | ||
4384 | if (!jm->sent_stop_to_hopefully_last_jobs) { | |
4385 | jobmgr_log(jm, LOG_DEBUG, "Asking \"hopefully last\" jobs to exit."); | |
4386 | ||
4387 | LIST_FOREACH(ji, &jm->jobs, sle) { | |
4388 | if (ji->p && ji->anonymous) { | |
4389 | continue; | |
4390 | } else if (ji->p && job_assumes(ji, ji->hopefully_exits_last)) { | |
4391 | job_stop(ji); | |
4392 | } | |
4393 | } | |
4394 | ||
4395 | jm->sent_stop_to_hopefully_last_jobs = true; | |
4396 | } | |
4397 | ||
4398 | if (!SLIST_EMPTY(&jm->submgrs)) { | |
4399 | return jm; | |
4400 | } | |
4401 | ||
4402 | LIST_FOREACH(ji, &jm->jobs, sle) { | |
4403 | if (!ji->anonymous) { | |
4404 | return jm; | |
4405 | } | |
4406 | } | |
4407 | ||
4408 | jobmgr_log_stray_children(jm); | |
4409 | jobmgr_remove(jm); | |
4410 | return NULL; | |
ed34e3c3 A |
4411 | } |
4412 | ||
4413 | void | |
5b0a4722 | 4414 | jobmgr_log_stray_children(jobmgr_t jm) |
ed34e3c3 | 4415 | { |
5b0a4722 | 4416 | int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL }; |
f36da725 | 4417 | size_t i, kp_cnt, len = sizeof(struct kinfo_proc) * get_kern_max_proc(); |
5b0a4722 | 4418 | struct kinfo_proc *kp; |
ed34e3c3 | 4419 | |
f36da725 A |
4420 | #if TARGET_OS_EMBEDDED |
4421 | if (!do_apple_internal_magic) { | |
4422 | return; | |
4423 | } | |
4424 | #endif | |
5b0a4722 A |
4425 | if (jm->parentmgr || getpid() != 1) { |
4426 | return; | |
4427 | } | |
ed34e3c3 | 4428 | |
5b0a4722 A |
4429 | if (!jobmgr_assumes(jm, (kp = malloc(len)) != NULL)) { |
4430 | return; | |
4431 | } | |
4432 | if (!jobmgr_assumes(jm, sysctl(mib, 3, kp, &len, NULL, 0) != -1)) { | |
4433 | goto out; | |
4434 | } | |
4435 | ||
4436 | kp_cnt = len / sizeof(struct kinfo_proc); | |
ed34e3c3 | 4437 | |
5b0a4722 A |
4438 | for (i = 0; i < kp_cnt; i++) { |
4439 | pid_t p_i = kp[i].kp_proc.p_pid; | |
4440 | pid_t pp_i = kp[i].kp_eproc.e_ppid; | |
4441 | pid_t pg_i = kp[i].kp_eproc.e_pgid; | |
4442 | const char *z = (kp[i].kp_proc.p_stat == SZOMB) ? "zombie " : ""; | |
4443 | const char *n = kp[i].kp_proc.p_comm; | |
4444 | ||
4445 | if (p_i == 0 || p_i == 1) { | |
4446 | continue; | |
ed34e3c3 | 4447 | } |
5b0a4722 A |
4448 | |
4449 | jobmgr_log(jm, LOG_WARNING, "Stray %sprocess at shutdown: PID %u PPID %u PGID %u %s", z, p_i, pp_i, pg_i, n); | |
4450 | ||
4451 | /* | |
4452 | * The kernel team requested that launchd not do this for Leopard. | |
4453 | * jobmgr_assumes(jm, runtime_kill(p_i, SIGKILL) != -1); | |
4454 | */ | |
ed34e3c3 A |
4455 | } |
4456 | ||
5b0a4722 A |
4457 | out: |
4458 | free(kp); | |
ed34e3c3 A |
4459 | } |
4460 | ||
5b0a4722 A |
4461 | jobmgr_t |
4462 | jobmgr_parent(jobmgr_t jm) | |
ed34e3c3 | 4463 | { |
5b0a4722 A |
4464 | return jm->parentmgr; |
4465 | } | |
ed34e3c3 | 4466 | |
5b0a4722 A |
4467 | void |
4468 | job_uncork_fork(job_t j) | |
4469 | { | |
4470 | pid_t c = j->p; | |
4471 | ||
4472 | job_log(j, LOG_DEBUG, "Uncorking the fork()."); | |
4473 | /* this unblocks the child and avoids a race | |
4474 | * between the above fork() and the kevent_mod() */ | |
4475 | job_assumes(j, write(j->forkfd, &c, sizeof(c)) == sizeof(c)); | |
4476 | job_assumes(j, runtime_close(j->forkfd) != -1); | |
4477 | j->forkfd = 0; | |
4478 | } | |
4479 | ||
4480 | jobmgr_t | |
4481 | jobmgr_new(jobmgr_t jm, mach_port_t requestorport, mach_port_t transfer_port, bool sflag, const char *name) | |
4482 | { | |
4483 | mach_msg_size_t mxmsgsz; | |
4484 | job_t bootstrapper = NULL; | |
4485 | jobmgr_t jmr; | |
4486 | ||
4487 | launchd_assert(offsetof(struct jobmgr_s, kqjobmgr_callback) == 0); | |
4488 | ||
4489 | if (jm && requestorport == MACH_PORT_NULL) { | |
4490 | jobmgr_log(jm, LOG_ERR, "Mach sub-bootstrap create request requires a requester port"); | |
ed34e3c3 A |
4491 | return NULL; |
4492 | } | |
4493 | ||
5b0a4722 | 4494 | jmr = calloc(1, sizeof(struct jobmgr_s) + (name ? (strlen(name) + 1) : 128)); |
ed34e3c3 | 4495 | |
5b0a4722 | 4496 | if (jmr == NULL) { |
ed34e3c3 | 4497 | return NULL; |
5b0a4722 | 4498 | } |
ed34e3c3 | 4499 | |
5b0a4722 | 4500 | jmr->kqjobmgr_callback = jobmgr_callback; |
f36da725 | 4501 | strcpy(jmr->name_init, name ? name : "Under construction"); |
5b0a4722 A |
4502 | |
4503 | jmr->req_port = requestorport; | |
4504 | ||
4505 | if ((jmr->parentmgr = jm)) { | |
4506 | SLIST_INSERT_HEAD(&jm->submgrs, jmr, sle); | |
4507 | } | |
4508 | ||
4509 | if (jm && !jobmgr_assumes(jmr, launchd_mport_notify_req(jmr->req_port, MACH_NOTIFY_DEAD_NAME) == KERN_SUCCESS)) { | |
ed34e3c3 A |
4510 | goto out_bad; |
4511 | } | |
4512 | ||
5b0a4722 A |
4513 | if (transfer_port != MACH_PORT_NULL) { |
4514 | jobmgr_assumes(jmr, jm != NULL); | |
4515 | jmr->jm_port = transfer_port; | |
4516 | } else if (!jm && getpid() != 1) { | |
4517 | char *trusted_fd = getenv(LAUNCHD_TRUSTED_FD_ENV); | |
4518 | name_t service_buf; | |
4519 | ||
4520 | snprintf(service_buf, sizeof(service_buf), "com.apple.launchd.peruser.%u", getuid()); | |
4521 | ||
4522 | if (!jobmgr_assumes(jmr, bootstrap_check_in(bootstrap_port, service_buf, &jmr->jm_port) == 0)) { | |
4523 | goto out_bad; | |
4524 | } | |
4525 | ||
4526 | if (trusted_fd) { | |
4527 | int dfd, lfd = strtol(trusted_fd, NULL, 10); | |
4528 | ||
4529 | if ((dfd = dup(lfd)) >= 0) { | |
4530 | jobmgr_assumes(jmr, runtime_close(dfd) != -1); | |
4531 | jobmgr_assumes(jmr, runtime_close(lfd) != -1); | |
4532 | } | |
4533 | ||
4534 | unsetenv(LAUNCHD_TRUSTED_FD_ENV); | |
4535 | } | |
4536 | ||
4537 | /* cut off the Libc cache, we don't want to deadlock against ourself */ | |
4538 | inherited_bootstrap_port = bootstrap_port; | |
4539 | bootstrap_port = MACH_PORT_NULL; | |
4540 | launchd_assert(launchd_mport_notify_req(inherited_bootstrap_port, MACH_NOTIFY_DEAD_NAME) == KERN_SUCCESS); | |
ed34e3c3 | 4541 | |
5b0a4722 A |
4542 | /* We set this explicitly as we start each child */ |
4543 | launchd_assert(launchd_set_bport(MACH_PORT_NULL) == KERN_SUCCESS); | |
4544 | } else if (!jobmgr_assumes(jmr, launchd_mport_create_recv(&jmr->jm_port) == KERN_SUCCESS)) { | |
ed34e3c3 | 4545 | goto out_bad; |
5b0a4722 | 4546 | } |
ed34e3c3 | 4547 | |
5b0a4722 | 4548 | if (!name) { |
f36da725 | 4549 | sprintf(jmr->name_init, "%u", MACH_PORT_INDEX(jmr->jm_port)); |
ed34e3c3 A |
4550 | } |
4551 | ||
5b0a4722 A |
4552 | /* Sigh... at the moment, MIG has maxsize == sizeof(reply union) */ |
4553 | mxmsgsz = sizeof(union __RequestUnion__job_mig_protocol_vproc_subsystem); | |
4554 | if (job_mig_protocol_vproc_subsystem.maxsize > mxmsgsz) { | |
4555 | mxmsgsz = job_mig_protocol_vproc_subsystem.maxsize; | |
4556 | } | |
4557 | ||
4558 | if (!jm) { | |
4559 | jobmgr_assumes(jmr, kevent_mod(SIGTERM, EVFILT_SIGNAL, EV_ADD, 0, 0, jmr) != -1); | |
4560 | jobmgr_assumes(jmr, kevent_mod(SIGUSR1, EVFILT_SIGNAL, EV_ADD, 0, 0, jmr) != -1); | |
4561 | jobmgr_assumes(jmr, kevent_mod(0, EVFILT_FS, EV_ADD, VQ_MOUNT|VQ_UNMOUNT|VQ_UPDATE, 0, jmr) != -1); | |
4562 | } | |
4563 | ||
4564 | if (name) { | |
4565 | bootstrapper = jobmgr_init_session(jmr, name, sflag); | |
4566 | } | |
4567 | ||
4568 | if (!bootstrapper || !bootstrapper->weird_bootstrap) { | |
4569 | if (!jobmgr_assumes(jmr, runtime_add_mport(jmr->jm_port, protocol_vproc_server, mxmsgsz) == KERN_SUCCESS)) { | |
4570 | goto out_bad; | |
4571 | } | |
4572 | } | |
4573 | ||
4574 | jobmgr_log(jmr, LOG_DEBUG, "Created job manager%s%s", jm ? " with parent: " : ".", jm ? jm->name : ""); | |
4575 | ||
4576 | if (bootstrapper) { | |
4577 | jobmgr_assumes(jmr, job_dispatch(bootstrapper, true) != NULL); | |
4578 | } | |
4579 | ||
4580 | if (jmr->parentmgr) { | |
4581 | runtime_add_ref(); | |
4582 | } | |
4583 | ||
4584 | return jmr; | |
ed34e3c3 A |
4585 | |
4586 | out_bad: | |
5b0a4722 A |
4587 | if (jmr) { |
4588 | jobmgr_remove(jmr); | |
4589 | } | |
ed34e3c3 A |
4590 | return NULL; |
4591 | } | |
4592 | ||
5b0a4722 A |
4593 | job_t |
4594 | jobmgr_init_session(jobmgr_t jm, const char *session_type, bool sflag) | |
4595 | { | |
4596 | const char *bootstrap_tool[] = { "/bin/launchctl", "bootstrap", "-S", session_type, sflag ? "-s" : NULL, NULL }; | |
4597 | char thelabel[1000]; | |
4598 | job_t bootstrapper; | |
4599 | ||
4600 | snprintf(thelabel, sizeof(thelabel), "com.apple.launchctl.%s", session_type); | |
4601 | bootstrapper = job_new(jm, thelabel, NULL, bootstrap_tool); | |
4602 | if (jobmgr_assumes(jm, bootstrapper != NULL) && (jm->parentmgr || getuid())) { | |
4603 | char buf[100]; | |
4604 | ||
4605 | /* <rdar://problem/5042202> launchd-201: can't ssh in with AFP OD account (hangs) */ | |
4606 | snprintf(buf, sizeof(buf), "0x%X:0:0", getuid()); | |
4607 | envitem_new(bootstrapper, "__CF_USER_TEXT_ENCODING", buf, false); | |
4608 | bootstrapper->weird_bootstrap = true; | |
4609 | jobmgr_assumes(jm, job_setup_machport(bootstrapper)); | |
4610 | } | |
4611 | ||
4612 | jm->session_initialized = true; | |
4613 | ||
4614 | return bootstrapper; | |
4615 | } | |
4616 | ||
4617 | jobmgr_t | |
4618 | jobmgr_delete_anything_with_port(jobmgr_t jm, mach_port_t port) | |
ed34e3c3 A |
4619 | { |
4620 | struct machservice *ms, *next_ms; | |
5b0a4722 | 4621 | jobmgr_t jmi, jmn; |
ed34e3c3 A |
4622 | |
4623 | /* Mach ports, unlike Unix descriptors, are reference counted. In other | |
4624 | * words, when some program hands us a second or subsequent send right | |
4625 | * to a port we already have open, the Mach kernel gives us the same | |
4626 | * port number back and increments an reference count associated with | |
4627 | * the port. This forces us, when discovering that a receive right at | |
4628 | * the other end has been deleted, to wander all of our objects to see | |
4629 | * what weird places clients might have handed us the same send right | |
4630 | * to use. | |
4631 | */ | |
4632 | ||
5b0a4722 A |
4633 | if (jm == root_jobmgr) { |
4634 | if (port == inherited_bootstrap_port) { | |
4635 | launchd_assumes(launchd_mport_deallocate(port) == KERN_SUCCESS); | |
4636 | inherited_bootstrap_port = MACH_PORT_NULL; | |
ed34e3c3 | 4637 | |
5b0a4722 A |
4638 | return jobmgr_shutdown(jm); |
4639 | } | |
4640 | ||
4641 | LIST_FOREACH_SAFE(ms, &port_hash[HASH_PORT(port)], port_hash_sle, next_ms) { | |
4642 | if (ms->port == port) { | |
4643 | machservice_delete(ms->job, ms, true); | |
4644 | } | |
4645 | } | |
4646 | } | |
4647 | ||
4648 | SLIST_FOREACH_SAFE(jmi, &jm->submgrs, sle, jmn) { | |
4649 | jobmgr_delete_anything_with_port(jmi, port); | |
4650 | } | |
ed34e3c3 | 4651 | |
5b0a4722 A |
4652 | if (jm->req_port == port) { |
4653 | jobmgr_log(jm, LOG_DEBUG, "Request port died: 0x%x", port); | |
4654 | return jobmgr_shutdown(jm); | |
ed34e3c3 | 4655 | } |
5b0a4722 A |
4656 | |
4657 | return jm; | |
ed34e3c3 A |
4658 | } |
4659 | ||
4660 | struct machservice * | |
5b0a4722 | 4661 | jobmgr_lookup_service(jobmgr_t jm, const char *name, bool check_parent, pid_t target_pid) |
ed34e3c3 A |
4662 | { |
4663 | struct machservice *ms; | |
ed34e3c3 | 4664 | |
5b0a4722 A |
4665 | if (target_pid) { |
4666 | jobmgr_assumes(jm, !check_parent); | |
4667 | } | |
ed34e3c3 | 4668 | |
5b0a4722 A |
4669 | LIST_FOREACH(ms, &jm->ms_hash[hash_ms(name)], name_hash_sle) { |
4670 | if ((target_pid && ms->per_pid && ms->job->p == target_pid) || (!target_pid && !ms->per_pid)) { | |
4671 | if (strcmp(name, ms->name) == 0) { | |
ed34e3c3 | 4672 | return ms; |
5b0a4722 | 4673 | } |
ed34e3c3 A |
4674 | } |
4675 | } | |
4676 | ||
5b0a4722 | 4677 | if (jm->parentmgr == NULL) { |
ed34e3c3 | 4678 | return NULL; |
5b0a4722 | 4679 | } |
ed34e3c3 | 4680 | |
5b0a4722 | 4681 | if (!check_parent) { |
ed34e3c3 | 4682 | return NULL; |
5b0a4722 | 4683 | } |
ed34e3c3 | 4684 | |
5b0a4722 | 4685 | return jobmgr_lookup_service(jm->parentmgr, name, true, 0); |
ed34e3c3 A |
4686 | } |
4687 | ||
4688 | mach_port_t | |
4689 | machservice_port(struct machservice *ms) | |
4690 | { | |
4691 | return ms->port; | |
4692 | } | |
4693 | ||
5b0a4722 | 4694 | job_t |
ed34e3c3 A |
4695 | machservice_job(struct machservice *ms) |
4696 | { | |
4697 | return ms->job; | |
4698 | } | |
4699 | ||
4700 | bool | |
4701 | machservice_hidden(struct machservice *ms) | |
4702 | { | |
4703 | return ms->hide; | |
4704 | } | |
4705 | ||
4706 | bool | |
4707 | machservice_active(struct machservice *ms) | |
4708 | { | |
4709 | return ms->isActive; | |
4710 | } | |
4711 | ||
4712 | const char * | |
4713 | machservice_name(struct machservice *ms) | |
4714 | { | |
4715 | return ms->name; | |
4716 | } | |
4717 | ||
4718 | void | |
5b0a4722 | 4719 | machservice_delete(job_t j, struct machservice *ms, bool port_died) |
ed34e3c3 | 4720 | { |
5b0a4722 A |
4721 | if (ms->debug_on_close) { |
4722 | job_log(j, LOG_NOTICE, "About to enter kernel debugger because of Mach port: 0x%x", ms->port); | |
4723 | job_assumes(j, host_reboot(mach_host_self(), HOST_REBOOT_DEBUGGER) == KERN_SUCCESS); | |
4724 | } | |
4725 | ||
4726 | if (ms->recv && job_assumes(j, !ms->isActive)) { | |
4727 | job_assumes(j, launchd_mport_close_recv(ms->port) == KERN_SUCCESS); | |
4728 | } | |
4729 | ||
4730 | job_assumes(j, launchd_mport_deallocate(ms->port) == KERN_SUCCESS); | |
4731 | ||
4732 | if (ms->port == the_exception_server) { | |
4733 | the_exception_server = 0; | |
ed34e3c3 A |
4734 | } |
4735 | ||
5b0a4722 | 4736 | job_log(j, LOG_INFO, "Mach service deleted%s: %s", port_died ? " (port died)" : "", ms->name); |
ed34e3c3 | 4737 | |
5b0a4722 A |
4738 | if (ms->special_port_num) { |
4739 | SLIST_REMOVE(&special_ports, ms, machservice, special_port_sle); | |
4740 | } | |
ed34e3c3 | 4741 | |
5b0a4722 A |
4742 | SLIST_REMOVE(&j->machservices, ms, machservice, sle); |
4743 | LIST_REMOVE(ms, name_hash_sle); | |
4744 | LIST_REMOVE(ms, port_hash_sle); | |
ed34e3c3 A |
4745 | |
4746 | free(ms); | |
4747 | } | |
4748 | ||
4749 | void | |
5b0a4722 | 4750 | machservice_request_notifications(struct machservice *ms) |
ed34e3c3 A |
4751 | { |
4752 | mach_msg_id_t which = MACH_NOTIFY_DEAD_NAME; | |
4753 | ||
4754 | ms->isActive = true; | |
4755 | ||
5b0a4722 | 4756 | if (ms->recv) { |
ed34e3c3 A |
4757 | which = MACH_NOTIFY_PORT_DESTROYED; |
4758 | job_checkin(ms->job); | |
4759 | } | |
4760 | ||
5b0a4722 | 4761 | job_assumes(ms->job, launchd_mport_notify_req(ms->port, which) == KERN_SUCCESS); |
ed34e3c3 A |
4762 | } |
4763 | ||
5b0a4722 A |
4764 | #define NELEM(x) (sizeof(x)/sizeof(x[0])) |
4765 | #define END_OF(x) (&(x)[NELEM(x)]) | |
ed34e3c3 A |
4766 | |
4767 | char ** | |
4768 | mach_cmd2argv(const char *string) | |
4769 | { | |
4770 | char *argv[100], args[1000]; | |
4771 | const char *cp; | |
4772 | char *argp = args, term, **argv_ret, *co; | |
4773 | unsigned int nargs = 0, i; | |
4774 | ||
4775 | for (cp = string; *cp;) { | |
4776 | while (isspace(*cp)) | |
4777 | cp++; | |
4778 | term = (*cp == '"') ? *cp++ : '\0'; | |
5b0a4722 | 4779 | if (nargs < NELEM(argv)) { |
ed34e3c3 | 4780 | argv[nargs++] = argp; |
5b0a4722 | 4781 | } |
ed34e3c3 | 4782 | while (*cp && (term ? *cp != term : !isspace(*cp)) && argp < END_OF(args)) { |
5b0a4722 | 4783 | if (*cp == '\\') { |
ed34e3c3 | 4784 | cp++; |
5b0a4722 | 4785 | } |
ed34e3c3 | 4786 | *argp++ = *cp; |
5b0a4722 | 4787 | if (*cp) { |
ed34e3c3 | 4788 | cp++; |
5b0a4722 | 4789 | } |
ed34e3c3 A |
4790 | } |
4791 | *argp++ = '\0'; | |
4792 | } | |
4793 | argv[nargs] = NULL; | |
4794 | ||
5b0a4722 | 4795 | if (nargs == 0) { |
ed34e3c3 | 4796 | return NULL; |
5b0a4722 | 4797 | } |
ed34e3c3 A |
4798 | |
4799 | argv_ret = malloc((nargs + 1) * sizeof(char *) + strlen(string) + 1); | |
4800 | ||
5b0a4722 | 4801 | if (!launchd_assumes(argv_ret != NULL)) { |
ed34e3c3 | 4802 | return NULL; |
5b0a4722 | 4803 | } |
ed34e3c3 A |
4804 | |
4805 | co = (char *)argv_ret + (nargs + 1) * sizeof(char *); | |
4806 | ||
4807 | for (i = 0; i < nargs; i++) { | |
4808 | strcpy(co, argv[i]); | |
4809 | argv_ret[i] = co; | |
4810 | co += strlen(argv[i]) + 1; | |
4811 | } | |
4812 | argv_ret[i] = NULL; | |
4813 | ||
4814 | return argv_ret; | |
4815 | } | |
4816 | ||
4817 | void | |
5b0a4722 | 4818 | job_checkin(job_t j) |
ed34e3c3 A |
4819 | { |
4820 | j->checkedin = true; | |
4821 | } | |
4822 | ||
4823 | bool | |
5b0a4722 | 4824 | job_ack_port_destruction(mach_port_t p) |
ed34e3c3 | 4825 | { |
ed34e3c3 A |
4826 | struct machservice *ms; |
4827 | ||
5b0a4722 A |
4828 | LIST_FOREACH(ms, &port_hash[HASH_PORT(p)], port_hash_sle) { |
4829 | if (ms->recv && (ms->port == p)) { | |
ed34e3c3 | 4830 | break; |
5b0a4722 | 4831 | } |
ed34e3c3 A |
4832 | } |
4833 | ||
5b0a4722 | 4834 | if (!ms) { |
ed34e3c3 | 4835 | return false; |
5b0a4722 | 4836 | } |
ed34e3c3 A |
4837 | |
4838 | ms->isActive = false; | |
4839 | ||
5b0a4722 A |
4840 | if (ms->reset) { |
4841 | machservice_resetport(ms->job, ms); | |
4842 | } | |
ed34e3c3 | 4843 | |
5b0a4722 A |
4844 | job_log(ms->job, LOG_DEBUG, "Receive right returned to us: %s", ms->name); |
4845 | job_dispatch(ms->job, false); | |
ed34e3c3 | 4846 | |
5b0a4722 | 4847 | root_jobmgr = jobmgr_do_garbage_collection(root_jobmgr); |
ed34e3c3 A |
4848 | |
4849 | return true; | |
4850 | } | |
4851 | ||
4852 | void | |
5b0a4722 | 4853 | job_ack_no_senders(job_t j) |
ed34e3c3 A |
4854 | { |
4855 | j->priv_port_has_senders = false; | |
4856 | ||
5b0a4722 A |
4857 | job_assumes(j, launchd_mport_close_recv(j->j_port) == KERN_SUCCESS); |
4858 | j->j_port = 0; | |
4859 | ||
ed34e3c3 A |
4860 | job_log(j, LOG_DEBUG, "No more senders on privileged Mach bootstrap port"); |
4861 | ||
5b0a4722 | 4862 | job_dispatch(j, false); |
ed34e3c3 A |
4863 | } |
4864 | ||
5b0a4722 A |
4865 | jobmgr_t |
4866 | job_get_bs(job_t j) | |
ed34e3c3 | 4867 | { |
5b0a4722 A |
4868 | if (job_assumes(j, j->mgr != NULL)) { |
4869 | return j->mgr; | |
4870 | } | |
ed34e3c3 | 4871 | |
5b0a4722 | 4872 | return NULL; |
ed34e3c3 A |
4873 | } |
4874 | ||
5b0a4722 A |
4875 | bool |
4876 | job_is_anonymous(job_t j) | |
ed34e3c3 | 4877 | { |
5b0a4722 | 4878 | return j->anonymous; |
ed34e3c3 A |
4879 | } |
4880 | ||
5b0a4722 A |
4881 | void |
4882 | job_force_sampletool(job_t j) | |
ed34e3c3 | 4883 | { |
5b0a4722 A |
4884 | struct stat sb; |
4885 | char logfile[PATH_MAX]; | |
4886 | char pidstr[100]; | |
4887 | char *sample_args[] = { "sample", pidstr, "1", "-mayDie", "-file", logfile, NULL }; | |
4888 | char *contents = NULL; | |
4889 | int logfile_fd = -1; | |
4890 | int console_fd = -1; | |
4891 | int wstatus; | |
4892 | pid_t sp; | |
4893 | ||
4894 | if (!debug_shutdown_hangs) { | |
4895 | return; | |
4896 | } | |
4897 | ||
4898 | snprintf(pidstr, sizeof(pidstr), "%u", j->p); | |
4899 | snprintf(logfile, sizeof(logfile), SHUTDOWN_LOG_DIR "/%s-%u.sample.txt", j->label, j->p); | |
ed34e3c3 | 4900 | |
5b0a4722 A |
4901 | if (!job_assumes(j, unlink(logfile) != -1 || errno == ENOENT)) { |
4902 | goto out; | |
4903 | } | |
ed34e3c3 | 4904 | |
5b0a4722 A |
4905 | /* |
4906 | * This will stall launchd for as long as the 'sample' tool runs. | |
4907 | * | |
4908 | * We didn't give the 'sample' tool a bootstrap port, so it therefore | |
4909 | * can't deadlock against launchd. | |
4910 | */ | |
4911 | if (!job_assumes(j, (errno = posix_spawnp(&sp, sample_args[0], NULL, NULL, sample_args, environ)) == 0)) { | |
4912 | goto out; | |
4913 | } | |
ed34e3c3 | 4914 | |
5b0a4722 A |
4915 | job_log(j, LOG_DEBUG, "Waiting for 'sample' to finish."); |
4916 | ||
4917 | if (!job_assumes(j, waitpid(sp, &wstatus, 0) != -1)) { | |
4918 | goto out; | |
4919 | } | |
4920 | ||
4921 | /* | |
4922 | * This won't work if the VFS or filesystems are sick: | |
4923 | * sync(); | |
4924 | */ | |
4925 | ||
4926 | if (!job_assumes(j, WIFEXITED(wstatus) && WEXITSTATUS(wstatus) == 0)) { | |
4927 | goto out; | |
4928 | } | |
4929 | ||
4930 | if (!job_assumes(j, (logfile_fd = open(logfile, O_RDONLY|O_NOCTTY)) != -1)) { | |
4931 | goto out; | |
4932 | } | |
4933 | ||
4934 | if (!job_assumes(j, (console_fd = open(_PATH_CONSOLE, O_WRONLY|O_APPEND|O_NOCTTY)) != -1)) { | |
4935 | goto out; | |
4936 | } | |
4937 | ||
4938 | if (!job_assumes(j, fstat(logfile_fd, &sb) != -1)) { | |
4939 | goto out; | |
4940 | } | |
4941 | ||
4942 | contents = malloc(sb.st_size); | |
4943 | ||
4944 | if (!job_assumes(j, contents != NULL)) { | |
4945 | goto out; | |
4946 | } | |
4947 | ||
4948 | if (!job_assumes(j, read(logfile_fd, contents, sb.st_size) == sb.st_size)) { | |
4949 | goto out; | |
4950 | } | |
4951 | ||
4952 | job_assumes(j, write(console_fd, contents, sb.st_size) == sb.st_size); | |
4953 | ||
4954 | out: | |
4955 | if (contents) { | |
4956 | free(contents); | |
4957 | } | |
4958 | ||
4959 | if (logfile_fd != -1) { | |
4960 | job_assumes(j, runtime_fsync(logfile_fd) != -1); | |
4961 | job_assumes(j, runtime_close(logfile_fd) != -1); | |
4962 | } | |
4963 | ||
4964 | if (console_fd != -1) { | |
4965 | job_assumes(j, runtime_close(console_fd) != -1); | |
4966 | } | |
4967 | ||
4968 | job_log(j, LOG_DEBUG, "Finished sampling."); | |
ed34e3c3 A |
4969 | } |
4970 | ||
4971 | bool | |
5b0a4722 | 4972 | semaphoreitem_new(job_t j, semaphore_reason_t why, const char *what) |
ed34e3c3 A |
4973 | { |
4974 | struct semaphoreitem *si; | |
4975 | size_t alloc_sz = sizeof(struct semaphoreitem); | |
4976 | ||
5b0a4722 | 4977 | if (what) { |
ed34e3c3 | 4978 | alloc_sz += strlen(what) + 1; |
5b0a4722 | 4979 | } |
ed34e3c3 | 4980 | |
5b0a4722 | 4981 | if (!job_assumes(j, si = calloc(1, alloc_sz))) { |
ed34e3c3 | 4982 | return false; |
5b0a4722 | 4983 | } |
ed34e3c3 | 4984 | |
5b0a4722 | 4985 | si->fd = -1; |
ed34e3c3 A |
4986 | si->why = why; |
4987 | ||
5b0a4722 | 4988 | if (what) { |
f36da725 | 4989 | strcpy(si->what_init, what); |
5b0a4722 | 4990 | } |
ed34e3c3 A |
4991 | |
4992 | SLIST_INSERT_HEAD(&j->semaphores, si, sle); | |
4993 | ||
5b0a4722 A |
4994 | semaphoreitem_runtime_mod_ref(si, true); |
4995 | ||
ed34e3c3 A |
4996 | return true; |
4997 | } | |
4998 | ||
4999 | void | |
5b0a4722 A |
5000 | semaphoreitem_runtime_mod_ref(struct semaphoreitem *si, bool add) |
5001 | { | |
5002 | /* | |
5003 | * External events need to be tracked. | |
5004 | * Internal events do NOT need to be tracked. | |
5005 | */ | |
5006 | ||
5007 | switch (si->why) { | |
5008 | case SUCCESSFUL_EXIT: | |
5009 | case FAILED_EXIT: | |
5010 | case OTHER_JOB_ENABLED: | |
5011 | case OTHER_JOB_DISABLED: | |
5012 | case OTHER_JOB_ACTIVE: | |
5013 | case OTHER_JOB_INACTIVE: | |
5014 | return; | |
5015 | default: | |
5016 | break; | |
5017 | } | |
5018 | ||
5019 | if (add) { | |
5020 | runtime_add_ref(); | |
5021 | } else { | |
5022 | runtime_del_ref(); | |
5023 | } | |
5024 | } | |
5025 | ||
5026 | void | |
5027 | semaphoreitem_delete(job_t j, struct semaphoreitem *si) | |
ed34e3c3 | 5028 | { |
5b0a4722 A |
5029 | semaphoreitem_runtime_mod_ref(si, false); |
5030 | ||
5031 | SLIST_REMOVE(&j->semaphores, si, semaphoreitem, sle); | |
ed34e3c3 | 5032 | |
5b0a4722 A |
5033 | if (si->fd != -1) { |
5034 | job_assumes(j, runtime_close(si->fd) != -1); | |
5035 | } | |
5036 | ||
5037 | free(si); | |
ed34e3c3 A |
5038 | } |
5039 | ||
5040 | void | |
5b0a4722 | 5041 | semaphoreitem_setup_dict_iter(launch_data_t obj, const char *key, void *context) |
ed34e3c3 | 5042 | { |
5b0a4722 | 5043 | struct semaphoreitem_dict_iter_context *sdic = context; |
ed34e3c3 A |
5044 | semaphore_reason_t why; |
5045 | ||
5b0a4722 | 5046 | why = launch_data_get_bool(obj) ? sdic->why_true : sdic->why_false; |
ed34e3c3 | 5047 | |
5b0a4722 | 5048 | semaphoreitem_new(sdic->j, why, key); |
ed34e3c3 A |
5049 | } |
5050 | ||
5051 | void | |
5052 | semaphoreitem_setup(launch_data_t obj, const char *key, void *context) | |
5053 | { | |
5b0a4722 A |
5054 | struct semaphoreitem_dict_iter_context sdic = { context, 0, 0 }; |
5055 | job_t j = context; | |
ed34e3c3 A |
5056 | semaphore_reason_t why; |
5057 | ||
5b0a4722 A |
5058 | switch (launch_data_get_type(obj)) { |
5059 | case LAUNCH_DATA_BOOL: | |
5060 | if (strcasecmp(key, LAUNCH_JOBKEY_KEEPALIVE_NETWORKSTATE) == 0) { | |
5061 | why = launch_data_get_bool(obj) ? NETWORK_UP : NETWORK_DOWN; | |
5062 | semaphoreitem_new(j, why, NULL); | |
5063 | } else if (strcasecmp(key, LAUNCH_JOBKEY_KEEPALIVE_SUCCESSFULEXIT) == 0) { | |
5064 | why = launch_data_get_bool(obj) ? SUCCESSFUL_EXIT : FAILED_EXIT; | |
5065 | semaphoreitem_new(j, why, NULL); | |
5066 | j->start_pending = true; | |
5067 | } else { | |
5068 | job_assumes(j, false); | |
5069 | } | |
5070 | break; | |
5071 | case LAUNCH_DATA_DICTIONARY: | |
5072 | if (strcasecmp(key, LAUNCH_JOBKEY_KEEPALIVE_PATHSTATE) == 0) { | |
5073 | sdic.why_true = PATH_EXISTS; | |
5074 | sdic.why_false = PATH_MISSING; | |
5075 | } else if (strcasecmp(key, LAUNCH_JOBKEY_KEEPALIVE_OTHERJOBACTIVE) == 0) { | |
5076 | sdic.why_true = OTHER_JOB_ACTIVE; | |
5077 | sdic.why_false = OTHER_JOB_INACTIVE; | |
5078 | } else if (strcasecmp(key, LAUNCH_JOBKEY_KEEPALIVE_OTHERJOBENABLED) == 0) { | |
5079 | sdic.why_true = OTHER_JOB_ENABLED; | |
5080 | sdic.why_false = OTHER_JOB_DISABLED; | |
5081 | } else { | |
5082 | job_assumes(j, false); | |
5083 | break; | |
5084 | } | |
5085 | ||
5086 | launch_data_dict_iterate(obj, semaphoreitem_setup_dict_iter, &sdic); | |
5087 | break; | |
5088 | default: | |
5089 | job_assumes(j, false); | |
5090 | break; | |
ed34e3c3 A |
5091 | } |
5092 | } | |
5093 | ||
5094 | void | |
5b0a4722 | 5095 | jobmgr_dispatch_all_semaphores(jobmgr_t jm) |
ed34e3c3 | 5096 | { |
5b0a4722 A |
5097 | jobmgr_t jmi, jmn; |
5098 | job_t ji, jn; | |
ed34e3c3 | 5099 | |
ed34e3c3 | 5100 | |
5b0a4722 A |
5101 | SLIST_FOREACH_SAFE(jmi, &jm->submgrs, sle, jmn) { |
5102 | jobmgr_dispatch_all_semaphores(jmi); | |
5103 | } | |
ed34e3c3 | 5104 | |
5b0a4722 A |
5105 | LIST_FOREACH_SAFE(ji, &jm->jobs, sle, jn) { |
5106 | if (!SLIST_EMPTY(&ji->semaphores)) { | |
5107 | job_dispatch(ji, false); | |
5108 | } | |
5109 | } | |
ed34e3c3 A |
5110 | } |
5111 | ||
5112 | time_t | |
5113 | cronemu(int mon, int mday, int hour, int min) | |
5114 | { | |
5115 | struct tm workingtm; | |
5116 | time_t now; | |
5117 | ||
5118 | now = time(NULL); | |
5119 | workingtm = *localtime(&now); | |
5120 | ||
5121 | workingtm.tm_isdst = -1; | |
5122 | workingtm.tm_sec = 0; | |
5123 | workingtm.tm_min++; | |
5124 | ||
5125 | while (!cronemu_mon(&workingtm, mon, mday, hour, min)) { | |
5126 | workingtm.tm_year++; | |
5127 | workingtm.tm_mon = 0; | |
5128 | workingtm.tm_mday = 1; | |
5129 | workingtm.tm_hour = 0; | |
5130 | workingtm.tm_min = 0; | |
5131 | mktime(&workingtm); | |
5132 | } | |
5133 | ||
5134 | return mktime(&workingtm); | |
5135 | } | |
5136 | ||
5137 | time_t | |
5138 | cronemu_wday(int wday, int hour, int min) | |
5139 | { | |
5140 | struct tm workingtm; | |
5141 | time_t now; | |
5142 | ||
5143 | now = time(NULL); | |
5144 | workingtm = *localtime(&now); | |
5145 | ||
5146 | workingtm.tm_isdst = -1; | |
5147 | workingtm.tm_sec = 0; | |
5148 | workingtm.tm_min++; | |
5149 | ||
5b0a4722 | 5150 | if (wday == 7) { |
ed34e3c3 | 5151 | wday = 0; |
5b0a4722 | 5152 | } |
ed34e3c3 A |
5153 | |
5154 | while (!(workingtm.tm_wday == wday && cronemu_hour(&workingtm, hour, min))) { | |
5155 | workingtm.tm_mday++; | |
5156 | workingtm.tm_hour = 0; | |
5157 | workingtm.tm_min = 0; | |
5158 | mktime(&workingtm); | |
5159 | } | |
5160 | ||
5161 | return mktime(&workingtm); | |
5162 | } | |
5163 | ||
5164 | bool | |
5165 | cronemu_mon(struct tm *wtm, int mon, int mday, int hour, int min) | |
5166 | { | |
5167 | if (mon == -1) { | |
5168 | struct tm workingtm = *wtm; | |
5169 | int carrytest; | |
5170 | ||
5171 | while (!cronemu_mday(&workingtm, mday, hour, min)) { | |
5172 | workingtm.tm_mon++; | |
5173 | workingtm.tm_mday = 1; | |
5174 | workingtm.tm_hour = 0; | |
5175 | workingtm.tm_min = 0; | |
5176 | carrytest = workingtm.tm_mon; | |
5177 | mktime(&workingtm); | |
5b0a4722 | 5178 | if (carrytest != workingtm.tm_mon) { |
ed34e3c3 | 5179 | return false; |
5b0a4722 | 5180 | } |
ed34e3c3 A |
5181 | } |
5182 | *wtm = workingtm; | |
5183 | return true; | |
5184 | } | |
5185 | ||
5b0a4722 | 5186 | if (mon < wtm->tm_mon) { |
ed34e3c3 | 5187 | return false; |
5b0a4722 | 5188 | } |
ed34e3c3 | 5189 | |
5b0a4722 | 5190 | if (mon > wtm->tm_mon) { |
ed34e3c3 A |
5191 | wtm->tm_mon = mon; |
5192 | wtm->tm_mday = 1; | |
5193 | wtm->tm_hour = 0; | |
5194 | wtm->tm_min = 0; | |
5195 | } | |
5196 | ||
5197 | return cronemu_mday(wtm, mday, hour, min); | |
5198 | } | |
5199 | ||
5200 | bool | |
5201 | cronemu_mday(struct tm *wtm, int mday, int hour, int min) | |
5202 | { | |
5203 | if (mday == -1) { | |
5204 | struct tm workingtm = *wtm; | |
5205 | int carrytest; | |
5206 | ||
5207 | while (!cronemu_hour(&workingtm, hour, min)) { | |
5208 | workingtm.tm_mday++; | |
5209 | workingtm.tm_hour = 0; | |
5210 | workingtm.tm_min = 0; | |
5211 | carrytest = workingtm.tm_mday; | |
5212 | mktime(&workingtm); | |
5b0a4722 | 5213 | if (carrytest != workingtm.tm_mday) { |
ed34e3c3 | 5214 | return false; |
5b0a4722 | 5215 | } |
ed34e3c3 A |
5216 | } |
5217 | *wtm = workingtm; | |
5218 | return true; | |
5219 | } | |
5220 | ||
5b0a4722 | 5221 | if (mday < wtm->tm_mday) { |
ed34e3c3 | 5222 | return false; |
5b0a4722 | 5223 | } |
ed34e3c3 | 5224 | |
5b0a4722 | 5225 | if (mday > wtm->tm_mday) { |
ed34e3c3 A |
5226 | wtm->tm_mday = mday; |
5227 | wtm->tm_hour = 0; | |
5228 | wtm->tm_min = 0; | |
5229 | } | |
5230 | ||
5231 | return cronemu_hour(wtm, hour, min); | |
5232 | } | |
5233 | ||
5234 | bool | |
5235 | cronemu_hour(struct tm *wtm, int hour, int min) | |
5236 | { | |
5237 | if (hour == -1) { | |
5238 | struct tm workingtm = *wtm; | |
5239 | int carrytest; | |
5240 | ||
5241 | while (!cronemu_min(&workingtm, min)) { | |
5242 | workingtm.tm_hour++; | |
5243 | workingtm.tm_min = 0; | |
5244 | carrytest = workingtm.tm_hour; | |
5245 | mktime(&workingtm); | |
5b0a4722 | 5246 | if (carrytest != workingtm.tm_hour) { |
ed34e3c3 | 5247 | return false; |
5b0a4722 | 5248 | } |
ed34e3c3 A |
5249 | } |
5250 | *wtm = workingtm; | |
5251 | return true; | |
5252 | } | |
5253 | ||
5b0a4722 | 5254 | if (hour < wtm->tm_hour) { |
ed34e3c3 | 5255 | return false; |
5b0a4722 | 5256 | } |
ed34e3c3 A |
5257 | |
5258 | if (hour > wtm->tm_hour) { | |
5259 | wtm->tm_hour = hour; | |
5260 | wtm->tm_min = 0; | |
5261 | } | |
5262 | ||
5263 | return cronemu_min(wtm, min); | |
5264 | } | |
5265 | ||
5266 | bool | |
5267 | cronemu_min(struct tm *wtm, int min) | |
5268 | { | |
5b0a4722 | 5269 | if (min == -1) { |
ed34e3c3 | 5270 | return true; |
5b0a4722 | 5271 | } |
ed34e3c3 | 5272 | |
5b0a4722 | 5273 | if (min < wtm->tm_min) { |
ed34e3c3 | 5274 | return false; |
5b0a4722 | 5275 | } |
ed34e3c3 A |
5276 | |
5277 | if (min > wtm->tm_min) { | |
5278 | wtm->tm_min = min; | |
5279 | } | |
5280 | ||
5281 | return true; | |
5282 | } | |
5b0a4722 A |
5283 | |
5284 | kern_return_t | |
5285 | job_mig_create_server(job_t j, cmd_t server_cmd, uid_t server_uid, boolean_t on_demand, mach_port_t *server_portp) | |
5286 | { | |
5287 | struct ldcred ldc; | |
5288 | job_t js; | |
5289 | ||
cf0bacfd A |
5290 | #if TARGET_OS_EMBEDDED |
5291 | return BOOTSTRAP_NOT_PRIVILEGED; | |
5292 | #endif | |
5293 | ||
5b0a4722 A |
5294 | if (!launchd_assumes(j != NULL)) { |
5295 | return BOOTSTRAP_NO_MEMORY; | |
5296 | } | |
5297 | ||
f36da725 A |
5298 | if (unlikely(j->deny_job_creation)) { |
5299 | return BOOTSTRAP_NOT_PRIVILEGED; | |
5300 | } | |
5301 | ||
5b0a4722 A |
5302 | runtime_get_caller_creds(&ldc); |
5303 | ||
5304 | job_log(j, LOG_DEBUG, "Server create attempt: %s", server_cmd); | |
5305 | ||
5306 | #define LET_MERE_MORTALS_ADD_SERVERS_TO_PID1 | |
5307 | /* XXX - This code should go away once the per session launchd is integrated with the rest of the system */ | |
5308 | #ifdef LET_MERE_MORTALS_ADD_SERVERS_TO_PID1 | |
5309 | if (getpid() == 1) { | |
5310 | if (ldc.euid && server_uid && (ldc.euid != server_uid)) { | |
5311 | job_log(j, LOG_WARNING, "Server create: \"%s\": Will run as UID %d, not UID %d as they told us to", | |
5312 | server_cmd, ldc.euid, server_uid); | |
5313 | server_uid = ldc.euid; | |
5314 | } | |
5315 | } else | |
5316 | #endif | |
5317 | if (getuid()) { | |
5318 | if (server_uid != getuid()) { | |
5319 | job_log(j, LOG_WARNING, "Server create: \"%s\": As UID %d, we will not be able to switch to UID %d", | |
5320 | server_cmd, getuid(), server_uid); | |
5321 | } | |
5322 | server_uid = 0; /* zero means "do nothing" */ | |
5323 | } | |
5324 | ||
5325 | js = job_new_via_mach_init(j, server_cmd, server_uid, on_demand); | |
5326 | ||
5327 | if (js == NULL) { | |
5328 | return BOOTSTRAP_NO_MEMORY; | |
5329 | } | |
5330 | ||
5331 | *server_portp = js->j_port; | |
5332 | return BOOTSTRAP_SUCCESS; | |
5333 | } | |
5334 | ||
5335 | kern_return_t | |
5336 | job_mig_send_signal(job_t j, mach_port_t srp, name_t targetlabel, int sig) | |
5337 | { | |
5338 | struct ldcred ldc; | |
5339 | job_t otherj; | |
5340 | ||
5341 | if (!launchd_assumes(j != NULL)) { | |
5342 | return BOOTSTRAP_NO_MEMORY; | |
5343 | } | |
5344 | ||
5345 | runtime_get_caller_creds(&ldc); | |
5346 | ||
5347 | if (ldc.euid != 0 && ldc.euid != getuid()) { | |
5348 | return BOOTSTRAP_NOT_PRIVILEGED; | |
5349 | } | |
5350 | ||
5351 | if (!(otherj = job_find(targetlabel))) { | |
5352 | return BOOTSTRAP_UNKNOWN_SERVICE; | |
5353 | } | |
5354 | ||
5355 | if (sig == VPROC_MAGIC_UNLOAD_SIGNAL) { | |
5356 | bool do_block = otherj->p; | |
5357 | ||
5358 | if (otherj->anonymous) { | |
5359 | return BOOTSTRAP_NOT_PRIVILEGED; | |
5360 | } | |
5361 | ||
5362 | job_remove(otherj); | |
5363 | ||
5364 | if (do_block) { | |
5365 | job_log(j, LOG_DEBUG, "Blocking MIG return of job_remove(): %s", otherj->label); | |
5366 | /* this is messy. We shouldn't access 'otherj' after job_remove(), but we check otherj->p first... */ | |
5367 | job_assumes(otherj, waiting4removal_new(otherj, srp)); | |
5368 | return MIG_NO_REPLY; | |
5369 | } else { | |
5370 | return 0; | |
5371 | } | |
5372 | } else if (otherj->p) { | |
5373 | job_assumes(j, runtime_kill(otherj->p, sig) != -1); | |
5374 | } | |
5375 | ||
5376 | return 0; | |
5377 | } | |
5378 | ||
5379 | kern_return_t | |
5380 | job_mig_log_forward(job_t j, vm_offset_t inval, mach_msg_type_number_t invalCnt) | |
5381 | { | |
5382 | struct ldcred ldc; | |
5383 | ||
5384 | if (!launchd_assumes(j != NULL)) { | |
5385 | return BOOTSTRAP_NO_MEMORY; | |
5386 | } | |
5387 | ||
5388 | if (!job_assumes(j, j->per_user)) { | |
5389 | return BOOTSTRAP_NOT_PRIVILEGED; | |
5390 | } | |
5391 | ||
5392 | runtime_get_caller_creds(&ldc); | |
5393 | ||
5394 | return runtime_log_forward(ldc.euid, ldc.egid, inval, invalCnt); | |
5395 | } | |
5396 | ||
5397 | kern_return_t | |
5398 | job_mig_log_drain(job_t j, mach_port_t srp, vm_offset_t *outval, mach_msg_type_number_t *outvalCnt) | |
5399 | { | |
5400 | struct ldcred ldc; | |
5401 | ||
5402 | if (!launchd_assumes(j != NULL)) { | |
5403 | return BOOTSTRAP_NO_MEMORY; | |
5404 | } | |
5405 | ||
5406 | runtime_get_caller_creds(&ldc); | |
5407 | ||
5408 | if (ldc.euid) { | |
5409 | return BOOTSTRAP_NOT_PRIVILEGED; | |
5410 | } | |
5411 | ||
5412 | return runtime_log_drain(srp, outval, outvalCnt); | |
5413 | } | |
5414 | ||
5415 | kern_return_t | |
5416 | job_mig_swap_complex(job_t j, vproc_gsk_t inkey, vproc_gsk_t outkey, | |
5417 | vm_offset_t inval, mach_msg_type_number_t invalCnt, | |
5418 | vm_offset_t *outval, mach_msg_type_number_t *outvalCnt) | |
5419 | { | |
5420 | const char *action; | |
5421 | launch_data_t input_obj, output_obj; | |
5422 | size_t data_offset = 0; | |
5423 | size_t packed_size; | |
5424 | struct ldcred ldc; | |
5425 | ||
5426 | runtime_get_caller_creds(&ldc); | |
5427 | ||
5428 | if (!launchd_assumes(j != NULL)) { | |
5429 | return BOOTSTRAP_NO_MEMORY; | |
5430 | } | |
5431 | ||
5432 | if (inkey && ldc.euid && ldc.euid != getuid()) { | |
5433 | return BOOTSTRAP_NOT_PRIVILEGED; | |
5434 | } | |
5435 | ||
5436 | if (inkey && outkey && !job_assumes(j, inkey == outkey)) { | |
5437 | return 1; | |
5438 | } | |
5439 | ||
5440 | if (inkey && outkey) { | |
5441 | action = "Swapping"; | |
5442 | } else if (inkey) { | |
5443 | action = "Setting"; | |
5444 | } else { | |
5445 | action = "Getting"; | |
5446 | } | |
5447 | ||
5448 | job_log(j, LOG_DEBUG, "%s key: %u", action, inkey ? inkey : outkey); | |
5449 | ||
5450 | *outvalCnt = 20 * 1024 * 1024; | |
5451 | mig_allocate(outval, *outvalCnt); | |
5452 | if (!job_assumes(j, *outval != 0)) { | |
5453 | return 1; | |
5454 | } | |
5455 | ||
5456 | if (invalCnt && !job_assumes(j, (input_obj = launch_data_unpack((void *)inval, invalCnt, NULL, 0, &data_offset, NULL)) != NULL)) { | |
5457 | goto out_bad; | |
5458 | } | |
5459 | ||
5460 | switch (outkey) { | |
5461 | case VPROC_GSK_ENVIRONMENT: | |
5462 | if (!job_assumes(j, (output_obj = launch_data_alloc(LAUNCH_DATA_DICTIONARY)))) { | |
5463 | goto out_bad; | |
5464 | } | |
5465 | jobmgr_export_env_from_other_jobs(j->mgr, output_obj); | |
5466 | if (!job_assumes(j, launch_data_pack(output_obj, (void *)*outval, *outvalCnt, NULL, NULL) != 0)) { | |
5467 | goto out_bad; | |
5468 | } | |
5469 | launch_data_free(output_obj); | |
5470 | break; | |
5471 | case VPROC_GSK_ALLJOBS: | |
5472 | if (!job_assumes(j, (output_obj = job_export_all()) != NULL)) { | |
5473 | goto out_bad; | |
5474 | } | |
5475 | ipc_revoke_fds(output_obj); | |
5476 | packed_size = launch_data_pack(output_obj, (void *)*outval, *outvalCnt, NULL, NULL); | |
5477 | if (!job_assumes(j, packed_size != 0)) { | |
5478 | goto out_bad; | |
5479 | } | |
5480 | launch_data_free(output_obj); | |
5481 | break; | |
5482 | case 0: | |
5483 | mig_deallocate(*outval, *outvalCnt); | |
5484 | *outval = 0; | |
5485 | *outvalCnt = 0; | |
5486 | break; | |
5487 | default: | |
5488 | goto out_bad; | |
5489 | } | |
5490 | ||
5491 | if (invalCnt) switch (inkey) { | |
5492 | case VPROC_GSK_ENVIRONMENT: | |
5493 | job_assumes(j, false); | |
5494 | break; | |
5495 | case 0: | |
5496 | break; | |
5497 | default: | |
5498 | goto out_bad; | |
5499 | } | |
5500 | ||
5501 | mig_deallocate(inval, invalCnt); | |
5502 | ||
5503 | return 0; | |
5504 | ||
5505 | out_bad: | |
5506 | if (*outval) { | |
5507 | mig_deallocate(*outval, *outvalCnt); | |
5508 | } | |
5509 | return 1; | |
5510 | } | |
5511 | ||
5512 | kern_return_t | |
5513 | job_mig_swap_integer(job_t j, vproc_gsk_t inkey, vproc_gsk_t outkey, int64_t inval, int64_t *outval) | |
5514 | { | |
5515 | const char *action; | |
5516 | kern_return_t kr = 0; | |
5517 | struct ldcred ldc; | |
5518 | int oldmask; | |
5519 | ||
5520 | runtime_get_caller_creds(&ldc); | |
5521 | ||
5522 | if (!launchd_assumes(j != NULL)) { | |
5523 | return BOOTSTRAP_NO_MEMORY; | |
5524 | } | |
5525 | ||
5526 | if (inkey && ldc.euid && ldc.euid != getuid()) { | |
5527 | return BOOTSTRAP_NOT_PRIVILEGED; | |
5528 | } | |
5529 | ||
5530 | if (inkey && outkey && !job_assumes(j, inkey == outkey)) { | |
5531 | return 1; | |
5532 | } | |
5533 | ||
5534 | if (inkey && outkey) { | |
5535 | action = "Swapping"; | |
5536 | } else if (inkey) { | |
5537 | action = "Setting"; | |
5538 | } else { | |
5539 | action = "Getting"; | |
5540 | } | |
5541 | ||
5542 | job_log(j, LOG_DEBUG, "%s key: %u", action, inkey ? inkey : outkey); | |
5543 | ||
5544 | switch (outkey) { | |
5545 | case VPROC_GSK_LAST_EXIT_STATUS: | |
5546 | *outval = j->last_exit_status; | |
5547 | break; | |
5548 | case VPROC_GSK_MGR_UID: | |
5549 | *outval = getuid(); | |
5550 | break; | |
5551 | case VPROC_GSK_MGR_PID: | |
5552 | *outval = getpid(); | |
5553 | break; | |
5554 | case VPROC_GSK_IS_MANAGED: | |
5555 | *outval = j->anonymous ? 0 : 1; | |
5556 | break; | |
5557 | case VPROC_GSK_BASIC_KEEPALIVE: | |
5558 | *outval = !j->ondemand; | |
5559 | break; | |
5560 | case VPROC_GSK_START_INTERVAL: | |
5561 | *outval = j->start_interval; | |
5562 | break; | |
5563 | case VPROC_GSK_IDLE_TIMEOUT: | |
5564 | *outval = j->timeout; | |
5565 | break; | |
5566 | case VPROC_GSK_EXIT_TIMEOUT: | |
5567 | *outval = j->exit_timeout; | |
5568 | break; | |
5569 | case VPROC_GSK_GLOBAL_LOG_MASK: | |
5570 | oldmask = runtime_setlogmask(LOG_UPTO(LOG_DEBUG)); | |
5571 | *outval = oldmask; | |
5572 | runtime_setlogmask(oldmask); | |
5573 | break; | |
5574 | case VPROC_GSK_GLOBAL_UMASK: | |
5575 | oldmask = umask(0); | |
5576 | *outval = oldmask; | |
5577 | umask(oldmask); | |
5578 | break; | |
5579 | case 0: | |
5580 | *outval = 0; | |
5581 | break; | |
5582 | default: | |
5583 | kr = 1; | |
5584 | break; | |
5585 | } | |
5586 | ||
5587 | switch (inkey) { | |
5588 | case VPROC_GSK_GLOBAL_ON_DEMAND: | |
5589 | kr = job_set_global_on_demand(j, (bool)inval) ? 0 : 1; | |
5590 | break; | |
5591 | case VPROC_GSK_BASIC_KEEPALIVE: | |
5592 | j->ondemand = !inval; | |
5593 | break; | |
5594 | case VPROC_GSK_START_INTERVAL: | |
5595 | if ((uint64_t)inval > UINT32_MAX) { | |
5596 | kr = 1; | |
5597 | } else if (inval) { | |
5598 | if (j->start_interval == 0) { | |
5599 | runtime_add_ref(); | |
5600 | } else { | |
5601 | /* Workaround 5225889 */ | |
5602 | job_assumes(j, kevent_mod((uintptr_t)&j->start_interval, EVFILT_TIMER, EV_DELETE, 0, 0, j) != -1); | |
5603 | } | |
5604 | j->start_interval = inval; | |
5605 | job_assumes(j, kevent_mod((uintptr_t)&j->start_interval, EVFILT_TIMER, EV_ADD, NOTE_SECONDS, j->start_interval, j) != -1); | |
5606 | } else if (j->start_interval) { | |
5607 | job_assumes(j, kevent_mod((uintptr_t)&j->start_interval, EVFILT_TIMER, EV_DELETE, 0, 0, NULL) != -1); | |
5608 | if (j->start_interval != 0) { | |
5609 | runtime_del_ref(); | |
5610 | } | |
5611 | j->start_interval = 0; | |
5612 | } | |
5613 | break; | |
5614 | case VPROC_GSK_IDLE_TIMEOUT: | |
5615 | if ((unsigned int)inval > 0) { | |
5616 | j->timeout = inval; | |
5617 | } | |
5618 | break; | |
5619 | case VPROC_GSK_EXIT_TIMEOUT: | |
5620 | if ((unsigned int)inval > 0) { | |
5621 | j->exit_timeout = inval; | |
5622 | } | |
5623 | break; | |
5624 | case VPROC_GSK_GLOBAL_LOG_MASK: | |
5625 | runtime_setlogmask(inval); | |
5626 | break; | |
5627 | case VPROC_GSK_GLOBAL_UMASK: | |
5628 | umask(inval); | |
5629 | break; | |
5630 | case 0: | |
5631 | break; | |
5632 | default: | |
5633 | kr = 1; | |
5634 | break; | |
5635 | } | |
5636 | ||
5637 | return kr; | |
5638 | } | |
5639 | ||
5640 | kern_return_t | |
5641 | job_mig_post_fork_ping(job_t j, task_t child_task) | |
5642 | { | |
5643 | struct machservice *ms; | |
5644 | ||
5645 | if (!launchd_assumes(j != NULL)) { | |
5646 | return BOOTSTRAP_NO_MEMORY; | |
5647 | } | |
5648 | ||
5649 | job_log(j, LOG_DEBUG, "Post fork ping."); | |
5650 | ||
5651 | job_setup_exception_port(j, child_task); | |
5652 | ||
5653 | SLIST_FOREACH(ms, &special_ports, special_port_sle) { | |
5654 | if (j->per_user && (ms->special_port_num != TASK_ACCESS_PORT)) { | |
5655 | /* The TASK_ACCESS_PORT funny business is to workaround 5325399. */ | |
5656 | continue; | |
5657 | } | |
5658 | ||
5659 | errno = task_set_special_port(child_task, ms->special_port_num, ms->port); | |
5660 | ||
5661 | if (errno) { | |
5662 | int desired_log_level = LOG_ERR; | |
5663 | ||
5664 | if (j->anonymous) { | |
5665 | /* 5338127 */ | |
5666 | ||
5667 | desired_log_level = LOG_WARNING; | |
5668 | ||
5669 | if (ms->special_port_num == TASK_SEATBELT_PORT) { | |
5670 | desired_log_level = LOG_DEBUG; | |
5671 | } | |
5672 | } | |
5673 | ||
5674 | job_log(j, desired_log_level, "Could not setup Mach task special port %u: %s", ms->special_port_num, mach_error_string(errno)); | |
5675 | } | |
5676 | } | |
5677 | ||
5678 | job_assumes(j, launchd_mport_deallocate(child_task) == KERN_SUCCESS); | |
5679 | ||
5680 | return 0; | |
5681 | } | |
5682 | ||
5683 | kern_return_t | |
5684 | job_mig_reboot2(job_t j, uint64_t flags) | |
5685 | { | |
5686 | char who_started_the_reboot[2048] = ""; | |
5687 | struct kinfo_proc kp; | |
5688 | struct ldcred ldc; | |
5689 | pid_t pid_to_log; | |
5690 | ||
5691 | if (!launchd_assumes(j != NULL)) { | |
5692 | return BOOTSTRAP_NO_MEMORY; | |
5693 | } | |
5694 | ||
5695 | if (getpid() != 1) { | |
5696 | return BOOTSTRAP_NOT_PRIVILEGED; | |
5697 | } | |
5698 | ||
5699 | runtime_get_caller_creds(&ldc); | |
5700 | ||
5701 | if (ldc.euid) { | |
5702 | return BOOTSTRAP_NOT_PRIVILEGED; | |
5703 | } | |
5704 | ||
5705 | for (pid_to_log = ldc.pid; pid_to_log; pid_to_log = kp.kp_eproc.e_ppid) { | |
5706 | int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid_to_log }; | |
5707 | size_t who_offset, len = sizeof(kp); | |
5708 | ||
5709 | if (!job_assumes(j, sysctl(mib, 4, &kp, &len, NULL, 0) != -1)) { | |
5710 | return 1; | |
5711 | } | |
5712 | ||
5713 | who_offset = strlen(who_started_the_reboot); | |
5714 | snprintf(who_started_the_reboot + who_offset, sizeof(who_started_the_reboot) - who_offset, | |
5715 | " %s[%u]%s", kp.kp_proc.p_comm, pid_to_log, kp.kp_eproc.e_ppid ? " ->" : ""); | |
5716 | } | |
5717 | ||
5718 | root_jobmgr->reboot_flags = (int)flags; | |
5719 | ||
5720 | launchd_shutdown(); | |
5721 | ||
5722 | job_log(j, LOG_DEBUG, "reboot2() initiated by:%s", who_started_the_reboot); | |
5723 | ||
5724 | return 0; | |
5725 | } | |
5726 | ||
5727 | kern_return_t | |
5728 | job_mig_getsocket(job_t j, name_t spr) | |
5729 | { | |
5730 | if (!launchd_assumes(j != NULL)) { | |
5731 | return BOOTSTRAP_NO_MEMORY; | |
5732 | } | |
5733 | ||
5734 | ipc_server_init(); | |
5735 | ||
5736 | if (!sockpath) { | |
5737 | return BOOTSTRAP_NO_MEMORY; | |
5738 | } | |
5739 | ||
5740 | strncpy(spr, sockpath, sizeof(name_t)); | |
5741 | ||
5742 | return BOOTSTRAP_SUCCESS; | |
5743 | } | |
5744 | ||
5745 | kern_return_t | |
5746 | job_mig_log(job_t j, int pri, int err, logmsg_t msg) | |
5747 | { | |
5748 | if (!launchd_assumes(j != NULL)) { | |
5749 | return BOOTSTRAP_NO_MEMORY; | |
5750 | } | |
5751 | ||
5752 | if ((errno = err)) { | |
5753 | job_log_error(j, pri, "%s", msg); | |
5754 | } else { | |
5755 | job_log(j, pri, "%s", msg); | |
5756 | } | |
5757 | ||
5758 | return 0; | |
5759 | } | |
5760 | ||
5761 | void | |
5762 | ensure_root_bkgd_setup(void) | |
5763 | { | |
5764 | if (background_jobmgr || getpid() != 1) { | |
5765 | return; | |
5766 | } | |
5767 | ||
5768 | if (!jobmgr_assumes(root_jobmgr, (background_jobmgr = jobmgr_new(root_jobmgr, mach_task_self(), MACH_PORT_NULL, false, VPROCMGR_SESSION_BACKGROUND)) != NULL)) { | |
5769 | return; | |
5770 | } | |
5771 | ||
5772 | background_jobmgr->req_port = 0; | |
5773 | jobmgr_assumes(root_jobmgr, launchd_mport_make_send(background_jobmgr->jm_port) == KERN_SUCCESS); | |
5774 | } | |
5775 | ||
5776 | kern_return_t | |
5777 | job_mig_lookup_per_user_context(job_t j, uid_t which_user, mach_port_t *up_cont) | |
5778 | { | |
5779 | struct ldcred ldc; | |
5780 | job_t ji; | |
5781 | ||
cf0bacfd A |
5782 | #if TARGET_OS_EMBEDDED |
5783 | return BOOTSTRAP_NOT_PRIVILEGED; | |
5784 | #endif | |
5785 | ||
5b0a4722 A |
5786 | if (!launchd_assumes(j != NULL)) { |
5787 | return BOOTSTRAP_NO_MEMORY; | |
5788 | } | |
5789 | ||
5790 | job_log(j, LOG_DEBUG, "Looking up per user launchd for UID: %u", which_user); | |
5791 | ||
5792 | runtime_get_caller_creds(&ldc); | |
5793 | ||
5794 | if (getpid() != 1) { | |
5795 | job_log(j, LOG_ERR, "Only PID 1 supports per user launchd lookups."); | |
5796 | return BOOTSTRAP_NOT_PRIVILEGED; | |
5797 | } | |
5798 | ||
5799 | if (ldc.euid || ldc.uid) { | |
5800 | which_user = ldc.euid ? ldc.euid : ldc.uid; | |
5801 | } | |
5802 | ||
5803 | *up_cont = MACH_PORT_NULL; | |
5804 | ||
5805 | if (which_user == 0) { | |
5806 | ensure_root_bkgd_setup(); | |
5807 | ||
5808 | *up_cont = background_jobmgr->jm_port; | |
5809 | ||
5810 | return 0; | |
5811 | } | |
5812 | ||
5813 | LIST_FOREACH(ji, &root_jobmgr->jobs, sle) { | |
5814 | if (!ji->per_user) { | |
5815 | continue; | |
5816 | } | |
5817 | if (ji->mach_uid != which_user) { | |
5818 | continue; | |
5819 | } | |
5820 | if (SLIST_EMPTY(&ji->machservices)) { | |
5821 | continue; | |
5822 | } | |
5823 | if (!SLIST_FIRST(&ji->machservices)->per_user_hack) { | |
5824 | continue; | |
5825 | } | |
5826 | break; | |
5827 | } | |
5828 | ||
5829 | if (ji == NULL) { | |
5830 | struct machservice *ms; | |
5831 | char lbuf[1024]; | |
5832 | ||
5833 | job_log(j, LOG_DEBUG, "Creating per user launchd job for UID: %u", which_user); | |
5834 | ||
5835 | sprintf(lbuf, "com.apple.launchd.peruser.%u", which_user); | |
5836 | ||
5837 | ji = job_new(root_jobmgr, lbuf, "/sbin/launchd", NULL); | |
5838 | ||
5839 | if (ji == NULL) { | |
5840 | return BOOTSTRAP_NO_MEMORY; | |
5841 | } | |
5842 | ||
5843 | ji->mach_uid = which_user; | |
5844 | ji->per_user = true; | |
5845 | ||
5846 | if ((ms = machservice_new(ji, lbuf, up_cont, false)) == NULL) { | |
5847 | job_remove(ji); | |
5848 | return BOOTSTRAP_NO_MEMORY; | |
5849 | } | |
5850 | ||
5851 | ms->per_user_hack = true; | |
5852 | ms->hide = true; | |
5853 | ||
5854 | ji = job_dispatch(ji, false); | |
5855 | } else { | |
5856 | job_log(j, LOG_DEBUG, "Per user launchd job found for UID: %u", which_user); | |
5857 | } | |
5858 | ||
5859 | if (job_assumes(j, ji != NULL)) { | |
5860 | *up_cont = machservice_port(SLIST_FIRST(&ji->machservices)); | |
5861 | } | |
5862 | ||
5863 | return 0; | |
5864 | } | |
5865 | ||
5866 | kern_return_t | |
5867 | job_mig_check_in(job_t j, name_t servicename, mach_port_t *serviceportp) | |
5868 | { | |
5869 | static pid_t last_warned_pid = 0; | |
5870 | struct machservice *ms; | |
5871 | struct ldcred ldc; | |
5872 | ||
5873 | if (!launchd_assumes(j != NULL)) { | |
5874 | return BOOTSTRAP_NO_MEMORY; | |
5875 | } | |
5876 | ||
5877 | runtime_get_caller_creds(&ldc); | |
5878 | ||
5879 | ms = jobmgr_lookup_service(j->mgr, servicename, true, 0); | |
5880 | ||
5881 | if (ms == NULL) { | |
5882 | job_log(j, LOG_DEBUG, "Check-in of Mach service failed. Unknown: %s", servicename); | |
5883 | return BOOTSTRAP_UNKNOWN_SERVICE; | |
5884 | } | |
5885 | if (machservice_job(ms) != j) { | |
5886 | if (last_warned_pid != ldc.pid) { | |
5887 | job_log(j, LOG_NOTICE, "Check-in of Mach service failed. PID %d is not privileged: %s", | |
5888 | ldc.pid, servicename); | |
5889 | last_warned_pid = ldc.pid; | |
5890 | } | |
5891 | return BOOTSTRAP_NOT_PRIVILEGED; | |
5892 | } | |
5893 | if (machservice_active(ms)) { | |
5894 | job_log(j, LOG_WARNING, "Check-in of Mach service failed. Already active: %s", servicename); | |
5895 | return BOOTSTRAP_SERVICE_ACTIVE; | |
5896 | } | |
5897 | ||
5898 | machservice_request_notifications(ms); | |
5899 | ||
5900 | job_log(j, LOG_INFO, "Check-in of service: %s", servicename); | |
5901 | ||
5902 | *serviceportp = machservice_port(ms); | |
5903 | return BOOTSTRAP_SUCCESS; | |
5904 | } | |
5905 | ||
5906 | kern_return_t | |
5907 | job_mig_register2(job_t j, name_t servicename, mach_port_t serviceport, uint64_t flags) | |
5908 | { | |
5909 | struct machservice *ms; | |
5910 | struct ldcred ldc; | |
5911 | ||
5912 | if (!launchd_assumes(j != NULL)) { | |
5913 | return BOOTSTRAP_NO_MEMORY; | |
5914 | } | |
5915 | ||
5916 | runtime_get_caller_creds(&ldc); | |
5917 | ||
5918 | #if 0 | |
5919 | job_log(j, LOG_APPLEONLY, "bootstrap_register() is deprecated. Service: %s", servicename); | |
5920 | #endif | |
5921 | ||
5922 | job_log(j, LOG_DEBUG, "%sMach service registration attempt: %s", flags & BOOTSTRAP_PER_PID_SERVICE ? "Per PID " : "", servicename); | |
5923 | ||
f36da725 A |
5924 | /* 5641783 for the embedded hack */ |
5925 | #if !TARGET_OS_EMBEDDED | |
5b0a4722 A |
5926 | /* |
5927 | * From a per-user/session launchd's perspective, SecurityAgent (UID | |
5928 | * 92) is a rogue application (not our UID, not root and not a child of | |
5929 | * us). We'll have to reconcile this design friction at a later date. | |
5930 | */ | |
5931 | if (j->anonymous && job_get_bs(j)->parentmgr == NULL && ldc.uid != 0 && ldc.uid != getuid() && ldc.uid != 92) { | |
5932 | if (getpid() == 1) { | |
5933 | return VPROC_ERR_TRY_PER_USER; | |
5934 | } else { | |
5935 | return BOOTSTRAP_NOT_PRIVILEGED; | |
5936 | } | |
5937 | } | |
f36da725 | 5938 | #endif |
5b0a4722 A |
5939 | |
5940 | ms = jobmgr_lookup_service(j->mgr, servicename, false, flags & BOOTSTRAP_PER_PID_SERVICE ? ldc.pid : 0); | |
5941 | ||
5942 | if (ms) { | |
5943 | if (machservice_job(ms) != j) { | |
5944 | return BOOTSTRAP_NOT_PRIVILEGED; | |
5945 | } | |
5946 | if (machservice_active(ms)) { | |
5947 | job_log(j, LOG_DEBUG, "Mach service registration failed. Already active: %s", servicename); | |
5948 | return BOOTSTRAP_SERVICE_ACTIVE; | |
5949 | } | |
5950 | job_checkin(j); | |
5951 | machservice_delete(j, ms, false); | |
5952 | } | |
5953 | ||
5954 | if (serviceport != MACH_PORT_NULL) { | |
5955 | if ((ms = machservice_new(j, servicename, &serviceport, flags & BOOTSTRAP_PER_PID_SERVICE ? true : false))) { | |
5956 | machservice_request_notifications(ms); | |
5957 | } else { | |
5958 | return BOOTSTRAP_NO_MEMORY; | |
5959 | } | |
5960 | } | |
5961 | ||
5962 | return BOOTSTRAP_SUCCESS; | |
5963 | } | |
5964 | ||
5965 | kern_return_t | |
5966 | job_mig_look_up2(job_t j, name_t servicename, mach_port_t *serviceportp, mach_msg_type_name_t *ptype, pid_t target_pid, uint64_t flags) | |
5967 | { | |
5968 | struct machservice *ms; | |
5969 | struct ldcred ldc; | |
5970 | kern_return_t kr; | |
5971 | ||
5972 | if (!launchd_assumes(j != NULL)) { | |
5973 | return BOOTSTRAP_NO_MEMORY; | |
5974 | } | |
5975 | ||
5976 | runtime_get_caller_creds(&ldc); | |
5977 | ||
f36da725 A |
5978 | /* 5641783 for the embedded hack */ |
5979 | #if !TARGET_OS_EMBEDDED | |
5b0a4722 A |
5980 | if (getpid() == 1 && j->anonymous && job_get_bs(j)->parentmgr == NULL && ldc.uid != 0 && ldc.euid != 0) { |
5981 | return VPROC_ERR_TRY_PER_USER; | |
5982 | } | |
f36da725 | 5983 | #endif |
5b0a4722 A |
5984 | |
5985 | if (!mspolicy_check(j, servicename, flags & BOOTSTRAP_PER_PID_SERVICE)) { | |
5986 | job_log(j, LOG_NOTICE, "Policy denied Mach service lookup: %s", servicename); | |
5987 | return BOOTSTRAP_NOT_PRIVILEGED; | |
5988 | } | |
5989 | ||
5990 | if (flags & BOOTSTRAP_PER_PID_SERVICE) { | |
5991 | ms = jobmgr_lookup_service(j->mgr, servicename, false, target_pid); | |
5992 | } else { | |
5993 | ms = jobmgr_lookup_service(j->mgr, servicename, true, 0); | |
5994 | } | |
5995 | ||
cf0bacfd | 5996 | if (ms && machservice_hidden(ms) && !machservice_active(ms)) { |
5b0a4722 A |
5997 | ms = NULL; |
5998 | } else if (ms && ms->per_user_hack) { | |
5999 | ms = NULL; | |
6000 | } | |
6001 | ||
6002 | if (ms) { | |
6003 | launchd_assumes(machservice_port(ms) != MACH_PORT_NULL); | |
6004 | job_log(j, LOG_DEBUG, "%sMach service lookup: %s", flags & BOOTSTRAP_PER_PID_SERVICE ? "Per PID " : "", servicename); | |
6005 | #if 0 | |
6006 | /* After Leopard ships, we should enable this */ | |
6007 | if (j->lastlookup == ms && j->lastlookup_gennum == ms->gen_num && !j->per_user) { | |
6008 | ms->bad_perf_cnt++; | |
6009 | job_log(j, LOG_APPLEONLY, "Performance opportunity: Number of bootstrap_lookup(... \"%s\" ...) calls that should have been cached: %llu", | |
6010 | servicename, ms->bad_perf_cnt); | |
6011 | } | |
6012 | j->lastlookup = ms; | |
6013 | j->lastlookup_gennum = ms->gen_num; | |
6014 | #endif | |
6015 | *serviceportp = machservice_port(ms); | |
6016 | *ptype = MACH_MSG_TYPE_COPY_SEND; | |
6017 | kr = BOOTSTRAP_SUCCESS; | |
6018 | } else if (!(flags & BOOTSTRAP_PER_PID_SERVICE) && (inherited_bootstrap_port != MACH_PORT_NULL)) { | |
6019 | job_log(j, LOG_DEBUG, "Mach service lookup forwarded: %s", servicename); | |
6020 | *ptype = MACH_MSG_TYPE_MOVE_SEND; | |
6021 | kr = bootstrap_look_up(inherited_bootstrap_port, servicename, serviceportp); | |
6022 | } else if (getpid() == 1 && j->anonymous && ldc.euid >= 500 && strcasecmp(job_get_bs(j)->name, VPROCMGR_SESSION_LOGINWINDOW) == 0) { | |
6023 | /* | |
6024 | * 5240036 Should start background session when a lookup of CCacheServer occurs | |
6025 | * | |
6026 | * This is a total hack. We sniff out loginwindow session, and attempt to guess what it is up to. | |
6027 | * If we find a EUID that isn't root, we force it over to the per-user context. | |
6028 | */ | |
6029 | return VPROC_ERR_TRY_PER_USER; | |
6030 | } else { | |
6031 | job_log(j, LOG_DEBUG, "%sMach service lookup failed: %s", flags & BOOTSTRAP_PER_PID_SERVICE ? "Per PID " : "", servicename); | |
6032 | kr = BOOTSTRAP_UNKNOWN_SERVICE; | |
6033 | } | |
6034 | ||
6035 | return kr; | |
6036 | } | |
6037 | ||
6038 | kern_return_t | |
6039 | job_mig_parent(job_t j, mach_port_t *parentport, mach_msg_type_name_t *pptype) | |
6040 | { | |
6041 | if (!launchd_assumes(j != NULL)) { | |
6042 | return BOOTSTRAP_NO_MEMORY; | |
6043 | } | |
6044 | ||
6045 | job_log(j, LOG_DEBUG, "Requested parent bootstrap port"); | |
6046 | jobmgr_t jm = j->mgr; | |
6047 | ||
6048 | *pptype = MACH_MSG_TYPE_MAKE_SEND; | |
6049 | ||
6050 | if (jobmgr_parent(jm)) { | |
6051 | *parentport = jobmgr_parent(jm)->jm_port; | |
6052 | } else if (MACH_PORT_NULL == inherited_bootstrap_port) { | |
6053 | *parentport = jm->jm_port; | |
6054 | } else { | |
6055 | *pptype = MACH_MSG_TYPE_COPY_SEND; | |
6056 | *parentport = inherited_bootstrap_port; | |
6057 | } | |
6058 | return BOOTSTRAP_SUCCESS; | |
6059 | } | |
6060 | ||
6061 | kern_return_t | |
6062 | job_mig_info(job_t j, name_array_t *servicenamesp, unsigned int *servicenames_cnt, | |
6063 | bootstrap_status_array_t *serviceactivesp, unsigned int *serviceactives_cnt) | |
6064 | { | |
6065 | name_array_t service_names = NULL; | |
6066 | bootstrap_status_array_t service_actives = NULL; | |
6067 | unsigned int cnt = 0, cnt2 = 0; | |
6068 | struct machservice *ms; | |
6069 | jobmgr_t jm; | |
6070 | job_t ji; | |
6071 | ||
cf0bacfd A |
6072 | #if TARGET_OS_EMBEDDED |
6073 | return BOOTSTRAP_NOT_PRIVILEGED; | |
6074 | #endif | |
6075 | ||
5b0a4722 A |
6076 | if (!launchd_assumes(j != NULL)) { |
6077 | return BOOTSTRAP_NO_MEMORY; | |
6078 | } | |
6079 | ||
6080 | jm = j->mgr; | |
6081 | ||
6082 | LIST_FOREACH(ji, &jm->jobs, sle) { | |
6083 | SLIST_FOREACH(ms, &ji->machservices, sle) { | |
6084 | if (!ms->per_pid) { | |
6085 | cnt++; | |
6086 | } | |
6087 | } | |
6088 | } | |
6089 | ||
6090 | if (cnt == 0) { | |
6091 | goto out; | |
6092 | } | |
6093 | ||
6094 | mig_allocate((vm_address_t *)&service_names, cnt * sizeof(service_names[0])); | |
6095 | if (!launchd_assumes(service_names != NULL)) { | |
6096 | goto out_bad; | |
6097 | } | |
6098 | ||
6099 | mig_allocate((vm_address_t *)&service_actives, cnt * sizeof(service_actives[0])); | |
6100 | if (!launchd_assumes(service_actives != NULL)) { | |
6101 | goto out_bad; | |
6102 | } | |
6103 | ||
6104 | LIST_FOREACH(ji, &jm->jobs, sle) { | |
6105 | SLIST_FOREACH(ms, &ji->machservices, sle) { | |
6106 | if (!ms->per_pid) { | |
6107 | strlcpy(service_names[cnt2], machservice_name(ms), sizeof(service_names[0])); | |
6108 | service_actives[cnt2] = machservice_status(ms); | |
6109 | cnt2++; | |
6110 | } | |
6111 | } | |
6112 | } | |
6113 | ||
6114 | launchd_assumes(cnt == cnt2); | |
6115 | ||
6116 | out: | |
6117 | *servicenamesp = service_names; | |
6118 | *serviceactivesp = service_actives; | |
6119 | *servicenames_cnt = *serviceactives_cnt = cnt; | |
6120 | ||
6121 | return BOOTSTRAP_SUCCESS; | |
6122 | ||
6123 | out_bad: | |
6124 | if (service_names) { | |
6125 | mig_deallocate((vm_address_t)service_names, cnt * sizeof(service_names[0])); | |
6126 | } | |
6127 | if (service_actives) { | |
6128 | mig_deallocate((vm_address_t)service_actives, cnt * sizeof(service_actives[0])); | |
6129 | } | |
6130 | ||
6131 | return BOOTSTRAP_NO_MEMORY; | |
6132 | } | |
6133 | ||
6134 | void | |
6135 | job_reparent_hack(job_t j, const char *where) | |
6136 | { | |
6137 | jobmgr_t jmi, jmi2; | |
6138 | ||
6139 | ensure_root_bkgd_setup(); | |
6140 | ||
6141 | /* NULL is only passed for our custom API for LaunchServices. If that is the case, we do magic. */ | |
6142 | if (where == NULL) { | |
6143 | if (strcasecmp(j->mgr->name, VPROCMGR_SESSION_LOGINWINDOW) == 0) { | |
6144 | where = VPROCMGR_SESSION_LOGINWINDOW; | |
6145 | } else { | |
6146 | where = VPROCMGR_SESSION_AQUA; | |
6147 | } | |
6148 | } | |
6149 | ||
6150 | if (strcasecmp(j->mgr->name, where) == 0) { | |
6151 | return; | |
6152 | } | |
6153 | ||
6154 | SLIST_FOREACH(jmi, &root_jobmgr->submgrs, sle) { | |
6155 | if (jmi->shutting_down) { | |
6156 | continue; | |
6157 | } else if (strcasecmp(jmi->name, where) == 0) { | |
6158 | goto jm_found; | |
6159 | } else if (strcasecmp(jmi->name, VPROCMGR_SESSION_BACKGROUND) == 0 && getpid() == 1) { | |
6160 | SLIST_FOREACH(jmi2, &jmi->submgrs, sle) { | |
6161 | if (strcasecmp(jmi2->name, where) == 0) { | |
6162 | jmi = jmi2; | |
6163 | goto jm_found; | |
6164 | } | |
6165 | } | |
6166 | } | |
6167 | } | |
6168 | ||
6169 | jm_found: | |
6170 | if (job_assumes(j, jmi != NULL)) { | |
6171 | struct machservice *msi; | |
6172 | ||
6173 | SLIST_FOREACH(msi, &j->machservices, sle) { | |
6174 | LIST_REMOVE(msi, name_hash_sle); | |
6175 | } | |
6176 | ||
6177 | LIST_REMOVE(j, sle); | |
6178 | LIST_INSERT_HEAD(&jmi->jobs, j, sle); | |
6179 | j->mgr = jmi; | |
6180 | ||
6181 | SLIST_FOREACH(msi, &j->machservices, sle) { | |
6182 | LIST_INSERT_HEAD(&j->mgr->ms_hash[hash_ms(msi->name)], msi, name_hash_sle); | |
6183 | } | |
6184 | } | |
6185 | } | |
6186 | ||
6187 | kern_return_t | |
6188 | job_mig_move_subset(job_t j, mach_port_t target_subset, name_t session_type) | |
6189 | { | |
6190 | mach_msg_type_number_t l2l_i, l2l_port_cnt = 0; | |
6191 | mach_port_array_t l2l_ports = NULL; | |
6192 | mach_port_t reqport, rcvright; | |
6193 | kern_return_t kr = 1; | |
6194 | launch_data_t out_obj_array = NULL; | |
6195 | struct ldcred ldc; | |
6196 | jobmgr_t jmr = NULL; | |
6197 | ||
cf0bacfd A |
6198 | #if TARGET_OS_EMBEDDED |
6199 | return BOOTSTRAP_NOT_PRIVILEGED; | |
6200 | #endif | |
6201 | ||
5b0a4722 A |
6202 | if (!launchd_assumes(j != NULL)) { |
6203 | return BOOTSTRAP_NO_MEMORY; | |
6204 | } | |
6205 | ||
6206 | runtime_get_caller_creds(&ldc); | |
6207 | ||
6208 | if (target_subset == MACH_PORT_NULL) { | |
6209 | job_t j2; | |
6210 | ||
6211 | if (j->mgr->session_initialized) { | |
6212 | if (ldc.uid == 0 && getpid() == 1) { | |
6213 | if (strcmp(j->mgr->name, VPROCMGR_SESSION_LOGINWINDOW) == 0) { | |
6214 | job_t ji, jn; | |
6215 | ||
6216 | LIST_FOREACH_SAFE(ji, &j->mgr->jobs, sle, jn) { | |
6217 | if (!ji->anonymous) { | |
6218 | job_remove(ji); | |
6219 | } | |
6220 | } | |
6221 | ||
6222 | ensure_root_bkgd_setup(); | |
6223 | ||
6224 | SLIST_REMOVE(&j->mgr->parentmgr->submgrs, j->mgr, jobmgr_s, sle); | |
6225 | j->mgr->parentmgr = background_jobmgr; | |
6226 | SLIST_INSERT_HEAD(&j->mgr->parentmgr->submgrs, j->mgr, sle); | |
6227 | ||
6228 | /* | |
6229 | * We really should wait for all the jobs to die before proceeding. See 5351245 for more info. | |
6230 | * | |
6231 | * We have hacked around this in job_find() by ignoring jobs that are pending removal. | |
6232 | */ | |
6233 | ||
6234 | } else if (strcmp(j->mgr->name, VPROCMGR_SESSION_AQUA) == 0) { | |
6235 | job_log(j, LOG_DEBUG, "Tried to move the Aqua session."); | |
6236 | return 0; | |
6237 | } else if (strcmp(j->mgr->name, VPROCMGR_SESSION_BACKGROUND) == 0) { | |
6238 | job_log(j, LOG_DEBUG, "Tried to move the background session."); | |
6239 | return 0; | |
6240 | } else { | |
6241 | job_log(j, LOG_ERR, "Tried to initialize an already setup session!"); | |
6242 | kr = BOOTSTRAP_NOT_PRIVILEGED; | |
6243 | goto out; | |
6244 | } | |
6245 | } else { | |
6246 | job_log(j, LOG_ERR, "Tried to initialize an already setup session!"); | |
6247 | kr = BOOTSTRAP_NOT_PRIVILEGED; | |
6248 | goto out; | |
6249 | } | |
6250 | } else if (ldc.uid == 0 && getpid() == 1 && strcmp(session_type, VPROCMGR_SESSION_STANDARDIO) == 0) { | |
6251 | ensure_root_bkgd_setup(); | |
6252 | ||
6253 | SLIST_REMOVE(&j->mgr->parentmgr->submgrs, j->mgr, jobmgr_s, sle); | |
6254 | j->mgr->parentmgr = background_jobmgr; | |
6255 | SLIST_INSERT_HEAD(&j->mgr->parentmgr->submgrs, j->mgr, sle); | |
6256 | } else if (strcmp(session_type, VPROCMGR_SESSION_LOGINWINDOW) == 0) { | |
6257 | jobmgr_t jmi; | |
6258 | ||
6259 | /* | |
6260 | * 5330262 | |
6261 | * | |
6262 | * We're working around LoginWindow and the WindowServer. | |
6263 | * | |
6264 | * In practice, there is only one LoginWindow session. Unfortunately, for certain | |
6265 | * scenarios, the WindowServer spawns loginwindow, and in those cases, it frequently | |
6266 | * spawns a replacement loginwindow session before cleaning up the previous one. | |
6267 | * | |
6268 | * We're going to use the creation of a new LoginWindow context as a clue that the | |
6269 | * previous LoginWindow context is on the way out and therefore we should just | |
6270 | * kick-start the shutdown of it. | |
6271 | */ | |
6272 | ||
6273 | SLIST_FOREACH(jmi, &root_jobmgr->submgrs, sle) { | |
6274 | if (jmi->shutting_down) { | |
6275 | continue; | |
6276 | } else if (strcasecmp(jmi->name, session_type) == 0) { | |
6277 | jobmgr_shutdown(jmi); | |
6278 | break; | |
6279 | } | |
6280 | } | |
6281 | } | |
6282 | ||
6283 | jobmgr_log(j->mgr, LOG_DEBUG, "Renaming to: %s", session_type); | |
f36da725 | 6284 | strcpy(j->mgr->name_init, session_type); |
5b0a4722 A |
6285 | |
6286 | if (job_assumes(j, (j2 = jobmgr_init_session(j->mgr, session_type, false)))) { | |
6287 | job_assumes(j, job_dispatch(j2, true)); | |
6288 | } | |
6289 | ||
6290 | kr = 0; | |
6291 | goto out; | |
6292 | } else if (job_mig_intran2(root_jobmgr, target_subset, ldc.pid)) { | |
6293 | job_log(j, LOG_ERR, "Moving a session to ourself is bogus."); | |
6294 | ||
6295 | kr = BOOTSTRAP_NOT_PRIVILEGED; | |
6296 | goto out; | |
6297 | } | |
6298 | ||
6299 | job_log(j, LOG_DEBUG, "Move subset attempt: 0x%x", target_subset); | |
6300 | ||
f36da725 | 6301 | errno = kr = _vproc_grab_subset(target_subset, &reqport, &rcvright, &out_obj_array, &l2l_ports, &l2l_port_cnt); |
5b0a4722 A |
6302 | |
6303 | if (!job_assumes(j, kr == 0)) { | |
6304 | goto out; | |
6305 | } | |
6306 | ||
6307 | launchd_assert(launch_data_array_get_count(out_obj_array) == l2l_port_cnt); | |
6308 | ||
6309 | if (!job_assumes(j, (jmr = jobmgr_new(j->mgr, reqport, rcvright, false, session_type)) != NULL)) { | |
6310 | kr = BOOTSTRAP_NO_MEMORY; | |
6311 | goto out; | |
6312 | } | |
6313 | ||
6314 | for (l2l_i = 0; l2l_i < l2l_port_cnt; l2l_i++) { | |
6315 | launch_data_t tmp, obj_at_idx; | |
6316 | struct machservice *ms; | |
6317 | job_t j_for_service; | |
6318 | const char *serv_name; | |
6319 | pid_t target_pid; | |
6320 | bool serv_perpid; | |
6321 | ||
6322 | job_assumes(j, obj_at_idx = launch_data_array_get_index(out_obj_array, l2l_i)); | |
6323 | job_assumes(j, tmp = launch_data_dict_lookup(obj_at_idx, TAKE_SUBSET_PID)); | |
6324 | target_pid = (pid_t)launch_data_get_integer(tmp); | |
6325 | job_assumes(j, tmp = launch_data_dict_lookup(obj_at_idx, TAKE_SUBSET_PERPID)); | |
6326 | serv_perpid = launch_data_get_bool(tmp); | |
6327 | job_assumes(j, tmp = launch_data_dict_lookup(obj_at_idx, TAKE_SUBSET_NAME)); | |
6328 | serv_name = launch_data_get_string(tmp); | |
6329 | ||
6330 | j_for_service = jobmgr_find_by_pid(jmr, target_pid, true); | |
6331 | ||
6332 | if (!j_for_service) { | |
6333 | /* The PID probably exited */ | |
6334 | job_assumes(j, launchd_mport_deallocate(l2l_ports[l2l_i]) == KERN_SUCCESS); | |
6335 | continue; | |
6336 | } | |
6337 | ||
6338 | if ((ms = machservice_new(j_for_service, serv_name, &l2l_ports[l2l_i], serv_perpid))) { | |
6339 | machservice_request_notifications(ms); | |
6340 | } | |
6341 | } | |
6342 | ||
6343 | kr = 0; | |
6344 | ||
6345 | out: | |
6346 | if (out_obj_array) { | |
6347 | launch_data_free(out_obj_array); | |
6348 | } | |
6349 | ||
6350 | if (l2l_ports) { | |
6351 | mig_deallocate((vm_address_t)l2l_ports, l2l_port_cnt * sizeof(l2l_ports[0])); | |
6352 | } | |
6353 | ||
6354 | if (kr == 0) { | |
6355 | if (target_subset) { | |
6356 | job_assumes(j, launchd_mport_deallocate(target_subset) == KERN_SUCCESS); | |
6357 | } | |
6358 | } else if (jmr) { | |
6359 | jobmgr_shutdown(jmr); | |
6360 | } | |
6361 | ||
6362 | return kr; | |
6363 | } | |
6364 | ||
6365 | kern_return_t | |
6366 | job_mig_take_subset(job_t j, mach_port_t *reqport, mach_port_t *rcvright, | |
6367 | vm_offset_t *outdata, mach_msg_type_number_t *outdataCnt, | |
6368 | mach_port_array_t *portsp, unsigned int *ports_cnt) | |
6369 | { | |
6370 | launch_data_t tmp_obj, tmp_dict, outdata_obj_array = NULL; | |
6371 | mach_port_array_t ports = NULL; | |
6372 | unsigned int cnt = 0, cnt2 = 0; | |
6373 | size_t packed_size; | |
6374 | struct machservice *ms; | |
6375 | jobmgr_t jm; | |
6376 | job_t ji; | |
6377 | ||
cf0bacfd A |
6378 | #if TARGET_OS_EMBEDDED |
6379 | return BOOTSTRAP_NOT_PRIVILEGED; | |
6380 | #endif | |
6381 | ||
5b0a4722 A |
6382 | if (!launchd_assumes(j != NULL)) { |
6383 | return BOOTSTRAP_NO_MEMORY; | |
6384 | } | |
6385 | ||
6386 | jm = j->mgr; | |
6387 | ||
6388 | if (getpid() != 1) { | |
6389 | job_log(j, LOG_ERR, "Only the system launchd will transfer Mach sub-bootstraps."); | |
6390 | return BOOTSTRAP_NOT_PRIVILEGED; | |
6391 | } else if (jobmgr_parent(jm) == NULL) { | |
6392 | job_log(j, LOG_ERR, "Root Mach bootstrap cannot be transferred."); | |
6393 | return BOOTSTRAP_NOT_PRIVILEGED; | |
6394 | } else if (strcasecmp(jm->name, VPROCMGR_SESSION_AQUA) == 0) { | |
6395 | job_log(j, LOG_ERR, "Cannot transfer a setup GUI session."); | |
6396 | return BOOTSTRAP_NOT_PRIVILEGED; | |
6397 | } else if (!j->anonymous) { | |
6398 | job_log(j, LOG_ERR, "Only the anonymous job can transfer Mach sub-bootstraps."); | |
6399 | return BOOTSTRAP_NOT_PRIVILEGED; | |
6400 | } | |
6401 | ||
6402 | job_log(j, LOG_DEBUG, "Transferring sub-bootstrap to the per session launchd."); | |
6403 | ||
6404 | outdata_obj_array = launch_data_alloc(LAUNCH_DATA_ARRAY); | |
6405 | if (!job_assumes(j, outdata_obj_array)) { | |
6406 | goto out_bad; | |
6407 | } | |
6408 | ||
6409 | *outdataCnt = 20 * 1024 * 1024; | |
6410 | mig_allocate(outdata, *outdataCnt); | |
6411 | if (!job_assumes(j, *outdata != 0)) { | |
6412 | return 1; | |
6413 | } | |
6414 | ||
6415 | LIST_FOREACH(ji, &j->mgr->jobs, sle) { | |
6416 | if (!ji->anonymous) { | |
6417 | continue; | |
6418 | } | |
6419 | SLIST_FOREACH(ms, &ji->machservices, sle) { | |
6420 | cnt++; | |
6421 | } | |
6422 | } | |
6423 | ||
6424 | mig_allocate((vm_address_t *)&ports, cnt * sizeof(ports[0])); | |
6425 | if (!launchd_assumes(ports != NULL)) { | |
6426 | goto out_bad; | |
6427 | } | |
6428 | ||
6429 | LIST_FOREACH(ji, &j->mgr->jobs, sle) { | |
6430 | if (!ji->anonymous) { | |
6431 | continue; | |
6432 | } | |
6433 | ||
6434 | SLIST_FOREACH(ms, &ji->machservices, sle) { | |
6435 | if (job_assumes(j, (tmp_dict = launch_data_alloc(LAUNCH_DATA_DICTIONARY)))) { | |
6436 | job_assumes(j, launch_data_array_set_index(outdata_obj_array, tmp_dict, cnt2)); | |
6437 | } else { | |
6438 | goto out_bad; | |
6439 | } | |
6440 | ||
6441 | if (job_assumes(j, (tmp_obj = launch_data_new_string(machservice_name(ms))))) { | |
6442 | job_assumes(j, launch_data_dict_insert(tmp_dict, tmp_obj, TAKE_SUBSET_NAME)); | |
6443 | } else { | |
6444 | goto out_bad; | |
6445 | } | |
6446 | ||
6447 | if (job_assumes(j, (tmp_obj = launch_data_new_integer((ms->job->p))))) { | |
6448 | job_assumes(j, launch_data_dict_insert(tmp_dict, tmp_obj, TAKE_SUBSET_PID)); | |
6449 | } else { | |
6450 | goto out_bad; | |
6451 | } | |
6452 | ||
6453 | if (job_assumes(j, (tmp_obj = launch_data_new_bool((ms->per_pid))))) { | |
6454 | job_assumes(j, launch_data_dict_insert(tmp_dict, tmp_obj, TAKE_SUBSET_PERPID)); | |
6455 | } else { | |
6456 | goto out_bad; | |
6457 | } | |
6458 | ||
6459 | ports[cnt2] = machservice_port(ms); | |
6460 | ||
6461 | /* Increment the send right by one so we can shutdown the jobmgr cleanly */ | |
6462 | jobmgr_assumes(jm, (errno = mach_port_mod_refs(mach_task_self(), ports[cnt2], MACH_PORT_RIGHT_SEND, 1)) == 0); | |
6463 | cnt2++; | |
6464 | } | |
6465 | } | |
6466 | ||
6467 | launchd_assumes(cnt == cnt2); | |
6468 | ||
6469 | packed_size = launch_data_pack(outdata_obj_array, (void *)*outdata, *outdataCnt, NULL, NULL); | |
6470 | if (!job_assumes(j, packed_size != 0)) { | |
6471 | goto out_bad; | |
6472 | } | |
6473 | ||
6474 | launch_data_free(outdata_obj_array); | |
6475 | ||
6476 | *portsp = ports; | |
6477 | *ports_cnt = cnt; | |
6478 | ||
6479 | *reqport = jm->req_port; | |
6480 | *rcvright = jm->jm_port; | |
6481 | ||
6482 | jm->req_port = 0; | |
6483 | jm->jm_port = 0; | |
6484 | ||
6485 | workaround_5477111 = j; | |
6486 | ||
6487 | jobmgr_shutdown(jm); | |
6488 | ||
6489 | return BOOTSTRAP_SUCCESS; | |
6490 | ||
6491 | out_bad: | |
6492 | if (outdata_obj_array) { | |
6493 | launch_data_free(outdata_obj_array); | |
6494 | } | |
6495 | if (*outdata) { | |
6496 | mig_deallocate(*outdata, *outdataCnt); | |
6497 | } | |
6498 | if (ports) { | |
6499 | mig_deallocate((vm_address_t)ports, cnt * sizeof(ports[0])); | |
6500 | } | |
6501 | ||
6502 | return BOOTSTRAP_NO_MEMORY; | |
6503 | } | |
6504 | ||
6505 | kern_return_t | |
6506 | job_mig_subset(job_t j, mach_port_t requestorport, mach_port_t *subsetportp) | |
6507 | { | |
6508 | int bsdepth = 0; | |
6509 | jobmgr_t jmr; | |
6510 | ||
6511 | if (!launchd_assumes(j != NULL)) { | |
6512 | return BOOTSTRAP_NO_MEMORY; | |
6513 | } | |
6514 | ||
6515 | jmr = j->mgr; | |
6516 | ||
6517 | while ((jmr = jobmgr_parent(jmr)) != NULL) { | |
6518 | bsdepth++; | |
6519 | } | |
6520 | ||
6521 | /* Since we use recursion, we need an artificial depth for subsets */ | |
6522 | if (bsdepth > 100) { | |
6523 | job_log(j, LOG_ERR, "Mach sub-bootstrap create request failed. Depth greater than: %d", bsdepth); | |
6524 | return BOOTSTRAP_NO_MEMORY; | |
6525 | } | |
6526 | ||
6527 | if ((jmr = jobmgr_new(j->mgr, requestorport, MACH_PORT_NULL, false, NULL)) == NULL) { | |
6528 | if (requestorport == MACH_PORT_NULL) { | |
6529 | return BOOTSTRAP_NOT_PRIVILEGED; | |
6530 | } | |
6531 | return BOOTSTRAP_NO_MEMORY; | |
6532 | } | |
6533 | ||
6534 | *subsetportp = jmr->jm_port; | |
6535 | return BOOTSTRAP_SUCCESS; | |
6536 | } | |
6537 | ||
6538 | kern_return_t | |
6539 | job_mig_create_service(job_t j, name_t servicename, mach_port_t *serviceportp) | |
6540 | { | |
6541 | struct machservice *ms; | |
6542 | ||
6543 | if (!launchd_assumes(j != NULL)) { | |
6544 | return BOOTSTRAP_NO_MEMORY; | |
6545 | } | |
6546 | ||
6547 | if (job_prog(j)[0] == '\0') { | |
6548 | job_log(j, LOG_ERR, "Mach service creation requires a target server: %s", servicename); | |
6549 | return BOOTSTRAP_NOT_PRIVILEGED; | |
6550 | } | |
6551 | ||
6552 | if (!j->legacy_mach_job) { | |
6553 | job_log(j, LOG_ERR, "bootstrap_create_service() is only allowed against legacy Mach jobs: %s", servicename); | |
6554 | return BOOTSTRAP_NOT_PRIVILEGED; | |
6555 | } | |
6556 | ||
6557 | ms = jobmgr_lookup_service(j->mgr, servicename, false, 0); | |
6558 | if (ms) { | |
6559 | job_log(j, LOG_DEBUG, "Mach service creation attempt for failed. Already exists: %s", servicename); | |
6560 | return BOOTSTRAP_NAME_IN_USE; | |
6561 | } | |
6562 | ||
6563 | job_checkin(j); | |
6564 | ||
6565 | *serviceportp = MACH_PORT_NULL; | |
6566 | ms = machservice_new(j, servicename, serviceportp, false); | |
6567 | ||
6568 | if (!launchd_assumes(ms != NULL)) { | |
6569 | goto out_bad; | |
6570 | } | |
6571 | ||
6572 | return BOOTSTRAP_SUCCESS; | |
6573 | ||
6574 | out_bad: | |
6575 | launchd_assumes(launchd_mport_close_recv(*serviceportp) == KERN_SUCCESS); | |
6576 | return BOOTSTRAP_NO_MEMORY; | |
6577 | } | |
6578 | ||
f36da725 A |
6579 | kern_return_t |
6580 | job_mig_embedded_wait(job_t j, name_t targetlabel, integer_t *waitstatus) | |
6581 | { | |
6582 | job_t otherj; | |
6583 | ||
6584 | if (!launchd_assumes(j != NULL)) { | |
6585 | return BOOTSTRAP_NO_MEMORY; | |
6586 | } | |
6587 | ||
6588 | if (unlikely(!(otherj = job_find(targetlabel)))) { | |
6589 | return BOOTSTRAP_UNKNOWN_SERVICE; | |
6590 | } | |
6591 | ||
6592 | *waitstatus = j->last_exit_status; | |
6593 | ||
6594 | return 0; | |
6595 | } | |
6596 | ||
6597 | kern_return_t | |
6598 | job_mig_embedded_kickstart(job_t j, name_t targetlabel, pid_t *out_pid, mach_port_t *out_name_port) | |
6599 | { | |
6600 | struct ldcred ldc; | |
6601 | kern_return_t kr; | |
6602 | job_t otherj; | |
6603 | ||
6604 | if (!launchd_assumes(j != NULL)) { | |
6605 | return BOOTSTRAP_NO_MEMORY; | |
6606 | } | |
6607 | ||
6608 | if (unlikely(!(otherj = job_find(targetlabel)))) { | |
6609 | return BOOTSTRAP_UNKNOWN_SERVICE; | |
6610 | } | |
6611 | ||
6612 | runtime_get_caller_creds(&ldc); | |
6613 | ||
6614 | if (ldc.euid != 0 && ldc.euid != geteuid() | |
6615 | #if TARGET_OS_EMBEDDED | |
6616 | && j->username && otherj->username | |
6617 | && strcmp(j->username, otherj->username) != 0 | |
6618 | #endif | |
6619 | ) { | |
6620 | return BOOTSTRAP_NOT_PRIVILEGED; | |
6621 | } | |
6622 | ||
6623 | otherj = job_dispatch(otherj, true); | |
6624 | ||
6625 | if (!job_assumes(j, otherj && otherj->p)) { | |
6626 | return BOOTSTRAP_NO_MEMORY; | |
6627 | } | |
6628 | ||
6629 | kr = task_name_for_pid(mach_task_self(), otherj->p, out_name_port); | |
6630 | if (!job_assumes(j, kr == 0)) { | |
6631 | return kr; | |
6632 | } | |
6633 | ||
6634 | *out_pid = otherj->p; | |
6635 | ||
6636 | return 0; | |
6637 | } | |
6638 | ||
5b0a4722 A |
6639 | kern_return_t |
6640 | job_mig_wait(job_t j, mach_port_t srp, integer_t *waitstatus) | |
6641 | { | |
6642 | if (!launchd_assumes(j != NULL)) { | |
6643 | return BOOTSTRAP_NO_MEMORY; | |
6644 | } | |
6645 | #if 0 | |
6646 | struct ldcred ldc; | |
6647 | runtime_get_caller_creds(&ldc); | |
6648 | #endif | |
6649 | return job_handle_mpm_wait(j, srp, waitstatus); | |
6650 | } | |
6651 | ||
6652 | kern_return_t | |
6653 | job_mig_uncork_fork(job_t j) | |
6654 | { | |
6655 | if (!launchd_assumes(j != NULL)) { | |
6656 | return BOOTSTRAP_NO_MEMORY; | |
6657 | } | |
6658 | ||
6659 | if (!j->stall_before_exec) { | |
6660 | job_log(j, LOG_WARNING, "Attempt to uncork a job that isn't in the middle of a fork()."); | |
6661 | return 1; | |
6662 | } | |
6663 | ||
6664 | job_uncork_fork(j); | |
6665 | j->stall_before_exec = false; | |
6666 | return 0; | |
6667 | } | |
6668 | ||
6669 | kern_return_t | |
6670 | job_mig_set_service_policy(job_t j, pid_t target_pid, uint64_t flags, name_t target_service) | |
6671 | { | |
cf0bacfd | 6672 | struct ldcred ldc; |
5b0a4722 A |
6673 | job_t target_j; |
6674 | ||
6675 | if (!launchd_assumes(j != NULL)) { | |
6676 | return BOOTSTRAP_NO_MEMORY; | |
6677 | } | |
6678 | ||
cf0bacfd A |
6679 | runtime_get_caller_creds(&ldc); |
6680 | ||
6681 | #if TARGET_OS_EMBEDDED | |
ededfeb7 | 6682 | if( ldc.euid ) { |
cf0bacfd A |
6683 | return BOOTSTRAP_NOT_PRIVILEGED; |
6684 | } | |
ededfeb7 A |
6685 | #else |
6686 | if( ldc.euid && (ldc.euid != getuid()) ) { | |
6687 | int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, target_pid }; | |
6688 | struct kinfo_proc kp; | |
6689 | size_t len = sizeof(kp); | |
6690 | ||
6691 | job_assumes(j, sysctl(mib, 4, &kp, &len, NULL, 0) != -1); | |
6692 | job_assumes(j, len == sizeof(kp)); | |
6693 | ||
6694 | uid_t kp_euid = kp.kp_eproc.e_ucred.cr_uid; | |
6695 | uid_t kp_uid = kp.kp_eproc.e_pcred.p_ruid; | |
6696 | ||
6697 | if( ldc.euid == kp_euid ) { | |
6698 | job_log(j, LOG_DEBUG, "Working around rdar://problem/5982485 and allowing job to set policy for PID %u.", target_pid); | |
6699 | } else { | |
6700 | job_log(j, LOG_ERR, "Denied Mach service policy update requested by UID/EUID %u/%u against PID %u with UID/EUID %u/%u due to mismatched credentials.", ldc.uid, ldc.euid, target_pid, kp_uid, kp_euid); | |
6701 | ||
6702 | return BOOTSTRAP_NOT_PRIVILEGED; | |
6703 | } | |
6704 | } | |
6705 | #endif | |
cf0bacfd | 6706 | |
5b0a4722 A |
6707 | if (!job_assumes(j, (target_j = jobmgr_find_by_pid(j->mgr, target_pid, true)) != NULL)) { |
6708 | return BOOTSTRAP_NO_MEMORY; | |
6709 | } | |
6710 | ||
6711 | if (SLIST_EMPTY(&j->mspolicies)) { | |
6712 | job_log(j, LOG_DEBUG, "Setting policy on job \"%s\" for Mach service: %s", target_j->label, target_service); | |
6713 | if (target_service[0]) { | |
6714 | job_assumes(j, mspolicy_new(target_j, target_service, flags & BOOTSTRAP_ALLOW_LOOKUP, flags & BOOTSTRAP_PER_PID_SERVICE, false)); | |
6715 | } else { | |
6716 | target_j->deny_unknown_mslookups = !(flags & BOOTSTRAP_ALLOW_LOOKUP); | |
f36da725 | 6717 | target_j->deny_job_creation = (bool)(flags & BOOTSTRAP_DENY_JOB_CREATION); |
5b0a4722 A |
6718 | } |
6719 | } else { | |
6720 | job_log(j, LOG_WARNING, "Jobs that have policies assigned to them may not set policies."); | |
6721 | return BOOTSTRAP_NOT_PRIVILEGED; | |
6722 | } | |
6723 | ||
6724 | return 0; | |
6725 | } | |
6726 | ||
6727 | kern_return_t | |
6728 | job_mig_spawn(job_t j, vm_offset_t indata, mach_msg_type_number_t indataCnt, pid_t *child_pid, mach_port_t *obsvr_port) | |
6729 | { | |
6730 | launch_data_t input_obj = NULL; | |
6731 | size_t data_offset = 0; | |
6732 | struct ldcred ldc; | |
6733 | job_t jr; | |
6734 | ||
cf0bacfd A |
6735 | #if TARGET_OS_EMBEDDED |
6736 | return BOOTSTRAP_NOT_PRIVILEGED; | |
6737 | #endif | |
6738 | ||
5b0a4722 A |
6739 | runtime_get_caller_creds(&ldc); |
6740 | ||
6741 | if (!launchd_assumes(j != NULL)) { | |
6742 | return BOOTSTRAP_NO_MEMORY; | |
6743 | } | |
6744 | ||
f36da725 A |
6745 | if (unlikely(j->deny_job_creation)) { |
6746 | return BOOTSTRAP_NOT_PRIVILEGED; | |
6747 | } | |
6748 | ||
5b0a4722 A |
6749 | if (getpid() == 1 && ldc.euid && ldc.uid) { |
6750 | job_log(j, LOG_DEBUG, "Punting spawn to per-user-context"); | |
6751 | return VPROC_ERR_TRY_PER_USER; | |
6752 | } | |
6753 | ||
6754 | if (!job_assumes(j, indataCnt != 0)) { | |
6755 | return 1; | |
6756 | } | |
6757 | ||
6758 | if (!job_assumes(j, (input_obj = launch_data_unpack((void *)indata, indataCnt, NULL, 0, &data_offset, NULL)) != NULL)) { | |
6759 | return 1; | |
6760 | } | |
6761 | ||
6762 | jr = jobmgr_import2(j->mgr, input_obj); | |
6763 | ||
6764 | if (!job_assumes(j, jr != NULL)) { | |
6765 | switch (errno) { | |
6766 | case EEXIST: | |
6767 | return BOOTSTRAP_NAME_IN_USE; | |
6768 | default: | |
6769 | return BOOTSTRAP_NO_MEMORY; | |
6770 | } | |
6771 | } | |
6772 | ||
6773 | job_reparent_hack(jr, NULL); | |
6774 | ||
6775 | if (getpid() == 1) { | |
6776 | jr->mach_uid = ldc.uid; | |
6777 | } | |
6778 | ||
6779 | jr->unload_at_exit = true; | |
6780 | jr->wait4pipe_eof = true; | |
6781 | jr->abandon_pg = true; | |
6782 | jr->stall_before_exec = jr->wait4debugger; | |
6783 | jr->wait4debugger = false; | |
6784 | ||
6785 | jr = job_dispatch(jr, true); | |
6786 | ||
6787 | if (!job_assumes(j, jr != NULL)) { | |
6788 | return BOOTSTRAP_NO_MEMORY; | |
6789 | } | |
6790 | ||
fe044cc9 A |
6791 | if (!job_assumes(jr, jr->p)) { |
6792 | job_remove(jr); | |
6793 | return BOOTSTRAP_NO_MEMORY; | |
6794 | } | |
5b0a4722 A |
6795 | |
6796 | if (!job_setup_machport(jr)) { | |
6797 | job_remove(jr); | |
6798 | return BOOTSTRAP_NO_MEMORY; | |
6799 | } | |
6800 | ||
fe044cc9 | 6801 | job_log(jr, LOG_DEBUG, "Spawned by PID %u: %s", j->p, j->label); |
5b0a4722 A |
6802 | |
6803 | *child_pid = jr->p; | |
6804 | *obsvr_port = jr->j_port; | |
6805 | ||
6806 | mig_deallocate(indata, indataCnt); | |
6807 | ||
6808 | return BOOTSTRAP_SUCCESS; | |
6809 | } | |
6810 | ||
6811 | void | |
6812 | jobmgr_init(bool sflag) | |
6813 | { | |
6814 | const char *root_session_type = getpid() == 1 ? VPROCMGR_SESSION_SYSTEM : VPROCMGR_SESSION_BACKGROUND; | |
6815 | ||
6816 | launchd_assert((root_jobmgr = jobmgr_new(NULL, MACH_PORT_NULL, MACH_PORT_NULL, sflag, root_session_type)) != NULL); | |
6817 | } | |
6818 | ||
6819 | size_t | |
6820 | our_strhash(const char *s) | |
6821 | { | |
6822 | size_t c, r = 5381; | |
6823 | ||
6824 | /* djb2 | |
6825 | * This algorithm was first reported by Dan Bernstein many years ago in comp.lang.c | |
6826 | */ | |
6827 | ||
6828 | while ((c = *s++)) { | |
6829 | r = ((r << 5) + r) + c; /* hash*33 + c */ | |
6830 | } | |
6831 | ||
6832 | return r; | |
6833 | } | |
6834 | ||
6835 | size_t | |
6836 | hash_label(const char *label) | |
6837 | { | |
6838 | return our_strhash(label) % LABEL_HASH_SIZE; | |
6839 | } | |
6840 | ||
6841 | size_t | |
6842 | hash_ms(const char *msstr) | |
6843 | { | |
6844 | return our_strhash(msstr) % MACHSERVICE_HASH_SIZE; | |
6845 | } | |
6846 | ||
6847 | bool | |
6848 | mspolicy_copy(job_t j_to, job_t j_from) | |
6849 | { | |
6850 | struct mspolicy *msp; | |
6851 | ||
6852 | SLIST_FOREACH(msp, &j_from->mspolicies, sle) { | |
6853 | if (!mspolicy_new(j_to, msp->name, msp->allow, msp->per_pid, true)) { | |
6854 | return false; | |
6855 | } | |
6856 | } | |
6857 | ||
6858 | return true; | |
6859 | } | |
6860 | ||
6861 | bool | |
6862 | mspolicy_new(job_t j, const char *name, bool allow, bool pid_local, bool skip_check) | |
6863 | { | |
6864 | struct mspolicy *msp; | |
6865 | ||
6866 | if (!skip_check) SLIST_FOREACH(msp, &j->mspolicies, sle) { | |
6867 | if (msp->per_pid != pid_local) { | |
6868 | continue; | |
6869 | } else if (strcmp(msp->name, name) == 0) { | |
6870 | return false; | |
6871 | } | |
6872 | } | |
6873 | ||
6874 | if ((msp = calloc(1, sizeof(struct mspolicy) + strlen(name) + 1)) == NULL) { | |
6875 | return false; | |
6876 | } | |
6877 | ||
6878 | strcpy((char *)msp->name, name); | |
6879 | msp->per_pid = pid_local; | |
6880 | msp->allow = allow; | |
6881 | ||
6882 | SLIST_INSERT_HEAD(&j->mspolicies, msp, sle); | |
6883 | ||
6884 | return true; | |
6885 | } | |
6886 | ||
6887 | void | |
6888 | mspolicy_setup(launch_data_t obj, const char *key, void *context) | |
6889 | { | |
6890 | job_t j = context; | |
6891 | ||
6892 | if (launch_data_get_type(obj) != LAUNCH_DATA_BOOL) { | |
6893 | job_log(j, LOG_WARNING, "Invalid object type for Mach service policy key: %s", key); | |
6894 | return; | |
6895 | } | |
6896 | ||
6897 | job_assumes(j, mspolicy_new(j, key, launch_data_get_bool(obj), false, false)); | |
6898 | } | |
6899 | ||
6900 | bool | |
6901 | mspolicy_check(job_t j, const char *name, bool pid_local) | |
6902 | { | |
6903 | struct mspolicy *mspi; | |
6904 | ||
6905 | SLIST_FOREACH(mspi, &j->mspolicies, sle) { | |
6906 | if (mspi->per_pid != pid_local) { | |
6907 | continue; | |
6908 | } else if (strcmp(mspi->name, name) != 0) { | |
6909 | continue; | |
6910 | } | |
6911 | return mspi->allow; | |
6912 | } | |
6913 | ||
6914 | return !j->deny_unknown_mslookups; | |
6915 | } | |
6916 | ||
6917 | void | |
6918 | mspolicy_delete(job_t j, struct mspolicy *msp) | |
6919 | { | |
6920 | SLIST_REMOVE(&j->mspolicies, msp, mspolicy, sle); | |
6921 | ||
6922 | free(msp); | |
6923 | } | |
6924 | ||
6925 | bool | |
6926 | waiting4removal_new(job_t j, mach_port_t rp) | |
6927 | { | |
6928 | struct waiting_for_removal *w4r; | |
6929 | ||
6930 | if (!job_assumes(j, (w4r = malloc(sizeof(struct waiting_for_removal))) != NULL)) { | |
6931 | return false; | |
6932 | } | |
6933 | ||
6934 | w4r->reply_port = rp; | |
6935 | ||
6936 | SLIST_INSERT_HEAD(&j->removal_watchers, w4r, sle); | |
6937 | ||
6938 | return true; | |
6939 | } | |
6940 | ||
6941 | void | |
6942 | waiting4removal_delete(job_t j, struct waiting_for_removal *w4r) | |
6943 | { | |
6944 | job_assumes(j, job_mig_send_signal_reply(w4r->reply_port, 0) == 0); | |
6945 | ||
6946 | SLIST_REMOVE(&j->removal_watchers, w4r, waiting_for_removal, sle); | |
6947 | ||
6948 | free(w4r); | |
6949 | } | |
6950 | ||
f36da725 A |
6951 | size_t |
6952 | get_kern_max_proc(void) | |
6953 | { | |
6954 | int mib[] = { CTL_KERN, KERN_MAXPROC }; | |
6955 | int max = 100; | |
6956 | size_t max_sz = sizeof(max); | |
6957 | ||
6958 | launchd_assumes(sysctl(mib, 2, &max, &max_sz, NULL, 0) != -1); | |
6959 | ||
6960 | return max; | |
6961 | } | |
6962 | ||
5b0a4722 A |
6963 | void |
6964 | do_file_init(void) | |
6965 | { | |
f36da725 A |
6966 | struct stat sb; |
6967 | ||
5b0a4722 A |
6968 | launchd_assert(mach_timebase_info(&tbi) == 0); |
6969 | ||
f36da725 A |
6970 | if (stat("/AppleInternal", &sb) == 0) { |
6971 | do_apple_internal_magic = true; | |
6972 | } | |
5b0a4722 | 6973 | } |