]>
Commit | Line | Data |
---|---|---|
5b0a4722 A |
1 | /* |
2 | * Copyright (c) 1999-2005 Apple Computer, Inc. All rights reserved. | |
3 | * | |
4 | * @APPLE_APACHE_LICENSE_HEADER_START@ | |
5 | * | |
6 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
7 | * you may not use this file except in compliance with the License. | |
8 | * You may obtain a copy of the License at | |
9 | * | |
10 | * http://www.apache.org/licenses/LICENSE-2.0 | |
11 | * | |
12 | * Unless required by applicable law or agreed to in writing, software | |
13 | * distributed under the License is distributed on an "AS IS" BASIS, | |
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
15 | * See the License for the specific language governing permissions and | |
16 | * limitations under the License. | |
17 | * | |
18 | * @APPLE_APACHE_LICENSE_HEADER_END@ | |
19 | */ | |
20 | ||
21 | #include "config.h" | |
ef398931 A |
22 | #include "vproc.h" |
23 | #include "vproc_priv.h" | |
24 | #include "vproc_internal.h" | |
5b0a4722 | 25 | |
eabd1701 A |
26 | #include <dispatch/dispatch.h> |
27 | #include <libproc.h> | |
5b0a4722 A |
28 | #include <mach/mach.h> |
29 | #include <mach/vm_map.h> | |
30 | #include <sys/param.h> | |
31 | #include <stdlib.h> | |
32 | #include <stdio.h> | |
33 | #include <errno.h> | |
34 | #include <unistd.h> | |
35 | #include <syslog.h> | |
36 | #include <pthread.h> | |
ddbbfbc1 A |
37 | #include <signal.h> |
38 | #include <assert.h> | |
39 | #include <libkern/OSAtomic.h> | |
40 | #include <sys/syscall.h> | |
dcace88f | 41 | #include <sys/event.h> |
eabd1701 A |
42 | #include <System/sys/fileport.h> |
43 | #include <assumes.h> | |
ddbbfbc1 | 44 | |
f36da725 A |
45 | #if HAVE_QUARANTINE |
46 | #include <quarantine.h> | |
47 | #endif | |
5b0a4722 | 48 | |
ef398931 A |
49 | #include "launch.h" |
50 | #include "launch_priv.h" | |
51 | #include "launch_internal.h" | |
eabd1701 | 52 | #include "ktrace.h" |
5b0a4722 | 53 | |
eabd1701 | 54 | #include "job.h" |
5b0a4722 | 55 | |
eabd1701 A |
56 | #include "helper.h" |
57 | #include "helperServer.h" | |
dcace88f | 58 | |
5b0a4722 A |
59 | #include "reboot2.h" |
60 | ||
eabd1701 A |
61 | #define likely(x) __builtin_expect((bool)(x), true) |
62 | #define unlikely(x) __builtin_expect((bool)(x), false) | |
ddbbfbc1 | 63 | |
eabd1701 | 64 | #define _vproc_set_crash_log_message(x) |
5b0a4722 | 65 | |
eabd1701 A |
66 | void _vproc_transactions_enable_internal(void *arg); |
67 | void _vproc_transaction_begin_internal(void *arg __unused); | |
68 | void _vproc_transaction_end_internal(void *arg __unused); | |
ddbbfbc1 | 69 | |
eabd1701 A |
70 | static dispatch_once_t _vproc_transaction_once = 0; |
71 | static uint64_t _vproc_transaction_enabled = 0; | |
72 | static dispatch_queue_t _vproc_transaction_queue = NULL; | |
73 | static int64_t _vproc_transaction_cnt = 0; | |
dcace88f | 74 | |
eabd1701 | 75 | #pragma mark vproc Object |
ddbbfbc1 A |
76 | struct vproc_s { |
77 | int32_t refcount; | |
78 | mach_port_t j_port; | |
79 | }; | |
80 | ||
eabd1701 A |
81 | vproc_t |
82 | vprocmgr_lookup_vproc(const char *label) | |
ddbbfbc1 A |
83 | { |
84 | struct vproc_s *vp = NULL; | |
eabd1701 | 85 | |
ddbbfbc1 A |
86 | mach_port_t mp = MACH_PORT_NULL; |
87 | kern_return_t kr = vproc_mig_port_for_label(bootstrap_port, (char *)label, &mp); | |
dcace88f | 88 | if (kr == BOOTSTRAP_SUCCESS) { |
ddbbfbc1 | 89 | vp = (struct vproc_s *)calloc(1, sizeof(struct vproc_s)); |
dcace88f | 90 | if (vp) { |
ddbbfbc1 A |
91 | vp->refcount = 1; |
92 | mach_port_mod_refs(mach_task_self(), mp, MACH_PORT_RIGHT_SEND, 1); | |
93 | vp->j_port = mp; | |
94 | } | |
dcace88f | 95 | (void)mach_port_deallocate(mach_task_self(), mp); |
ddbbfbc1 | 96 | } |
eabd1701 | 97 | |
ddbbfbc1 A |
98 | return vp; |
99 | } | |
100 | ||
eabd1701 A |
101 | vproc_t |
102 | vproc_retain(vproc_t vp) | |
ddbbfbc1 A |
103 | { |
104 | int32_t orig = OSAtomicAdd32(1, &vp->refcount) - 1; | |
dcace88f | 105 | if (orig <= 0) { |
dcace88f | 106 | _vproc_set_crash_log_message("Under-retain / over-release of vproc_t."); |
ddbbfbc1 A |
107 | abort(); |
108 | } | |
eabd1701 | 109 | |
ddbbfbc1 A |
110 | return vp; |
111 | } | |
112 | ||
eabd1701 A |
113 | void |
114 | vproc_release(vproc_t vp) | |
ddbbfbc1 A |
115 | { |
116 | int32_t newval = OSAtomicAdd32(-1, &vp->refcount); | |
dcace88f | 117 | if (newval < 0) { |
dcace88f | 118 | _vproc_set_crash_log_message("Over-release of vproc_t."); |
ddbbfbc1 | 119 | abort(); |
dcace88f | 120 | } else if (newval == 0) { |
ddbbfbc1 A |
121 | mach_port_deallocate(mach_task_self(), vp->j_port); |
122 | free(vp); | |
123 | } | |
124 | } | |
125 | ||
eabd1701 | 126 | #pragma mark Transactions |
ddbbfbc1 | 127 | static void |
eabd1701 | 128 | _vproc_transaction_init_once(void *arg __unused) |
ddbbfbc1 | 129 | { |
eabd1701 A |
130 | int64_t enable_transactions = 0; |
131 | (void)vproc_swap_integer(NULL, VPROC_GSK_TRANSACTIONS_ENABLED, 0, &enable_transactions); | |
132 | if (enable_transactions != 0) { | |
133 | (void)osx_assumes_zero(proc_track_dirty(getpid(), PROC_DIRTY_TRACK)); | |
134 | _vproc_transaction_enabled = 1; | |
135 | } | |
136 | _vproc_transaction_queue = dispatch_queue_create("com.apple.idle-exit-queue", NULL); | |
137 | } | |
ddbbfbc1 | 138 | |
eabd1701 A |
139 | void |
140 | _vproc_transactions_enable_internal(void *arg __unused) | |
141 | { | |
142 | (void)osx_assumes_zero(proc_track_dirty(getpid(), PROC_DIRTY_TRACK)); | |
143 | _vproc_transaction_enabled = 1; | |
ddbbfbc1 | 144 | |
eabd1701 A |
145 | if (_vproc_transaction_cnt > 0) { |
146 | (void)osx_assumes_zero(proc_set_dirty(getpid(), true)); | |
ddbbfbc1 | 147 | } |
eabd1701 | 148 | } |
ddbbfbc1 | 149 | |
eabd1701 A |
150 | void |
151 | _vproc_transactions_enable(void) | |
152 | { | |
153 | dispatch_once_f(&_vproc_transaction_once, NULL, _vproc_transaction_init_once); | |
154 | dispatch_sync_f(_vproc_transaction_queue, NULL, _vproc_transactions_enable_internal); | |
ddbbfbc1 A |
155 | } |
156 | ||
eabd1701 A |
157 | void |
158 | _vproc_transaction_begin_internal(void *ctx __unused) | |
ddbbfbc1 | 159 | { |
eabd1701 A |
160 | int64_t new = ++_vproc_transaction_cnt; |
161 | if (new == 1 && _vproc_transaction_enabled) { | |
162 | (void)osx_assumes_zero(proc_set_dirty(getpid(), true)); | |
ddbbfbc1 | 163 | } |
eabd1701 A |
164 | } |
165 | ||
166 | void | |
167 | _vproc_transaction_begin(void) | |
168 | { | |
169 | dispatch_once_f(&_vproc_transaction_once, NULL, _vproc_transaction_init_once); | |
170 | dispatch_sync_f(_vproc_transaction_queue, NULL, _vproc_transaction_begin_internal); | |
ddbbfbc1 A |
171 | } |
172 | ||
173 | vproc_transaction_t | |
eabd1701 | 174 | vproc_transaction_begin(vproc_t vp __unused) |
ddbbfbc1 | 175 | { |
ddbbfbc1 | 176 | _vproc_transaction_begin(); |
ddbbfbc1 | 177 | |
eabd1701 A |
178 | /* Return non-NULL on success. Originally, there were dreams of returning |
179 | * an object or something, but those never panned out. | |
180 | */ | |
181 | return (vproc_transaction_t)vproc_transaction_begin;; | |
ddbbfbc1 A |
182 | } |
183 | ||
184 | void | |
eabd1701 | 185 | _vproc_transaction_end_internal(void *arg __unused) |
ddbbfbc1 | 186 | { |
eabd1701 A |
187 | int64_t new = --_vproc_transaction_cnt; |
188 | if (new == 0 && _vproc_transaction_enabled) { | |
189 | (void)osx_assumes_zero(proc_set_dirty(getpid(), false)); | |
190 | } else if (new < 0) { | |
191 | _vproc_set_crash_log_message("Underflow of transaction count."); | |
ddbbfbc1 | 192 | } |
eabd1701 | 193 | } |
ddbbfbc1 | 194 | |
eabd1701 A |
195 | void |
196 | _vproc_transaction_end(void) | |
197 | { | |
198 | dispatch_once_f(&_vproc_transaction_once, NULL, _vproc_transaction_init_once); | |
199 | dispatch_sync_f(_vproc_transaction_queue, NULL, _vproc_transaction_end_internal); | |
200 | } | |
201 | ||
202 | void | |
203 | vproc_transaction_end(vproc_t vp __unused, vproc_transaction_t vpt __unused) | |
204 | { | |
205 | _vproc_transaction_end(); | |
ddbbfbc1 A |
206 | } |
207 | ||
208 | size_t | |
209 | _vproc_transaction_count(void) | |
210 | { | |
eabd1701 | 211 | return _vproc_transaction_cnt; |
ddbbfbc1 A |
212 | } |
213 | ||
214 | size_t | |
215 | _vproc_standby_count(void) | |
216 | { | |
ddbbfbc1 | 217 | return 0; |
eabd1701 | 218 | } |
ddbbfbc1 A |
219 | |
220 | size_t | |
221 | _vproc_standby_timeout(void) | |
222 | { | |
eabd1701 | 223 | return 0; |
ddbbfbc1 A |
224 | } |
225 | ||
226 | bool | |
227 | _vproc_pid_is_managed(pid_t p) | |
228 | { | |
229 | boolean_t result = false; | |
230 | vproc_mig_pid_is_managed(bootstrap_port, p, &result); | |
eabd1701 | 231 | |
ddbbfbc1 A |
232 | return result; |
233 | } | |
234 | ||
235 | kern_return_t | |
236 | _vproc_transaction_count_for_pid(pid_t p, int32_t *count, bool *condemned) | |
237 | { | |
eabd1701 A |
238 | /* Activity Monitor relies on us returning this error code when the process |
239 | * is not opted into Instant Off. | |
240 | */ | |
241 | kern_return_t error = BOOTSTRAP_NO_MEMORY; | |
ddbbfbc1 | 242 | |
eabd1701 A |
243 | if (condemned) { |
244 | *condemned = false; | |
ddbbfbc1 A |
245 | } |
246 | ||
eabd1701 A |
247 | if (count) { |
248 | uint32_t flags; | |
249 | int ret = proc_get_dirty(p, &flags); | |
250 | if (ret == 0) { | |
251 | if (flags & PROC_DIRTY_TRACKED) { | |
252 | *count = (flags & PROC_DIRTY_IS_DIRTY) ? 1 : 0; | |
253 | error = BOOTSTRAP_SUCCESS; | |
254 | } else { | |
255 | error = BOOTSTRAP_NO_MEMORY; | |
256 | } | |
257 | } else if (ret == ENOTSUP) { | |
258 | error = BOOTSTRAP_NO_MEMORY; | |
259 | } else if (ret == ESRCH) { | |
260 | error = BOOTSTRAP_UNKNOWN_SERVICE; | |
261 | } else if (ret == EPERM) { | |
262 | error = BOOTSTRAP_NOT_PRIVILEGED; | |
263 | } else { | |
264 | error = ret; | |
265 | } | |
ddbbfbc1 | 266 | } |
ddbbfbc1 | 267 | |
eabd1701 | 268 | return error; |
dcace88f | 269 | } |
ddbbfbc1 | 270 | void |
eabd1701 | 271 | _vproc_transaction_try_exit(int status) |
ddbbfbc1 | 272 | { |
eabd1701 A |
273 | #if !TARGET_OS_EMBEDDED |
274 | if (_vproc_transaction_cnt == 0) { | |
275 | _exit(status); | |
ddbbfbc1 | 276 | } |
ddbbfbc1 | 277 | #else |
eabd1701 | 278 | _exit(status); |
ddbbfbc1 A |
279 | #endif |
280 | } | |
281 | ||
282 | void | |
283 | _vproc_standby_begin(void) | |
284 | { | |
ddbbfbc1 | 285 | |
ddbbfbc1 A |
286 | } |
287 | ||
eabd1701 A |
288 | vproc_standby_t |
289 | vproc_standby_begin(vproc_t vp __unused) | |
ddbbfbc1 | 290 | { |
eabd1701 | 291 | return (vproc_standby_t)vproc_standby_begin; |
ddbbfbc1 A |
292 | } |
293 | ||
294 | void | |
295 | _vproc_standby_end(void) | |
296 | { | |
ddbbfbc1 | 297 | |
ddbbfbc1 | 298 | } |
5b0a4722 | 299 | |
eabd1701 A |
300 | /* TODO: obsoleted - remove post-build submission */ |
301 | ||
dcace88f A |
302 | int32_t * |
303 | _vproc_transaction_ptr(void) | |
304 | { | |
eabd1701 A |
305 | static int32_t dummy = 1; |
306 | return &dummy; | |
dcace88f A |
307 | } |
308 | ||
309 | void | |
eabd1701 | 310 | _vproc_transaction_set_callouts(_vproc_transaction_callout gone2zero __unused, _vproc_transaction_callout gonenonzero __unused) |
dcace88f | 311 | { |
eabd1701 | 312 | } |
dcace88f | 313 | |
eabd1701 | 314 | /* */ |
dcace88f | 315 | |
eabd1701 A |
316 | void |
317 | vproc_standby_end(vproc_t vp __unused, vproc_standby_t vpt __unused) | |
318 | { | |
dcace88f | 319 | |
dcace88f A |
320 | } |
321 | ||
eabd1701 | 322 | #pragma mark Miscellaneous SPI |
5b0a4722 | 323 | kern_return_t |
eabd1701 A |
324 | _vproc_grab_subset(mach_port_t bp, mach_port_t *reqport, mach_port_t *rcvright, |
325 | launch_data_t *outval, mach_port_array_t *ports, | |
326 | mach_msg_type_number_t *portCnt) | |
5b0a4722 A |
327 | { |
328 | mach_msg_type_number_t outdata_cnt; | |
329 | vm_offset_t outdata = 0; | |
330 | size_t data_offset = 0; | |
331 | launch_data_t out_obj; | |
332 | kern_return_t kr; | |
333 | ||
334 | if ((kr = vproc_mig_take_subset(bp, reqport, rcvright, &outdata, &outdata_cnt, ports, portCnt))) { | |
335 | goto out; | |
336 | } | |
337 | ||
338 | if ((out_obj = launch_data_unpack((void *)outdata, outdata_cnt, NULL, 0, &data_offset, NULL))) { | |
339 | *outval = launch_data_copy(out_obj); | |
340 | } else { | |
341 | kr = 1; | |
342 | } | |
343 | ||
344 | out: | |
345 | if (outdata) { | |
346 | mig_deallocate(outdata, outdata_cnt); | |
347 | } | |
348 | ||
349 | return kr; | |
350 | } | |
351 | ||
5b0a4722 | 352 | vproc_err_t |
ddbbfbc1 | 353 | _vprocmgr_move_subset_to_user(uid_t target_user, const char *session_type, uint64_t flags) |
5b0a4722 | 354 | { |
5b0a4722 A |
355 | kern_return_t kr = 0; |
356 | bool is_bkgd = (strcmp(session_type, VPROCMGR_SESSION_BACKGROUND) == 0); | |
357 | int64_t ldpid, lduid; | |
358 | ||
359 | if (vproc_swap_integer(NULL, VPROC_GSK_MGR_PID, 0, &ldpid) != 0) { | |
360 | return (vproc_err_t)_vprocmgr_move_subset_to_user; | |
361 | } | |
362 | ||
363 | if (vproc_swap_integer(NULL, VPROC_GSK_MGR_UID, 0, &lduid) != 0) { | |
364 | return (vproc_err_t)_vprocmgr_move_subset_to_user; | |
365 | } | |
366 | ||
367 | if (!is_bkgd && ldpid != 1) { | |
368 | if (lduid == getuid()) { | |
369 | return NULL; | |
370 | } | |
371 | /* | |
372 | * Not all sessions can be moved. | |
373 | * We should clean up this mess someday. | |
374 | */ | |
375 | return (vproc_err_t)_vprocmgr_move_subset_to_user; | |
376 | } | |
377 | ||
eabd1701 A |
378 | mach_port_t puc = 0; |
379 | mach_port_t rootbs = MACH_PORT_NULL; | |
380 | (void)bootstrap_get_root(bootstrap_port, &rootbs); | |
381 | ||
ddbbfbc1 A |
382 | if (vproc_mig_lookup_per_user_context(rootbs, target_user, &puc) != 0) { |
383 | return (vproc_err_t)_vprocmgr_move_subset_to_user; | |
384 | } | |
eabd1701 | 385 | |
dcace88f | 386 | if (is_bkgd) { |
ddbbfbc1 A |
387 | task_set_bootstrap_port(mach_task_self(), puc); |
388 | mach_port_deallocate(mach_task_self(), bootstrap_port); | |
389 | bootstrap_port = puc; | |
5b0a4722 | 390 | } else { |
ddbbfbc1 A |
391 | kr = vproc_mig_move_subset(puc, bootstrap_port, (char *)session_type, _audit_session_self(), flags); |
392 | mach_port_deallocate(mach_task_self(), puc); | |
5b0a4722 | 393 | } |
5b0a4722 A |
394 | |
395 | if (kr) { | |
396 | return (vproc_err_t)_vprocmgr_move_subset_to_user; | |
397 | } | |
398 | ||
5b0a4722 A |
399 | return _vproc_post_fork_ping(); |
400 | } | |
401 | ||
ddbbfbc1 A |
402 | vproc_err_t |
403 | _vprocmgr_switch_to_session(const char *target_session, vproc_flags_t flags __attribute__((unused))) | |
404 | { | |
405 | mach_port_t new_bsport = MACH_PORT_NULL; | |
406 | kern_return_t kr = KERN_FAILURE; | |
407 | ||
408 | mach_port_t tnp = MACH_PORT_NULL; | |
409 | task_name_for_pid(mach_task_self(), getpid(), &tnp); | |
dcace88f | 410 | if ((kr = vproc_mig_switch_to_session(bootstrap_port, tnp, (char *)target_session, _audit_session_self(), &new_bsport)) != KERN_SUCCESS) { |
ddbbfbc1 A |
411 | _vproc_log(LOG_NOTICE, "_vprocmgr_switch_to_session(): kr = 0x%x", kr); |
412 | return (vproc_err_t)_vprocmgr_switch_to_session; | |
413 | } | |
eabd1701 | 414 | |
ddbbfbc1 A |
415 | task_set_bootstrap_port(mach_task_self(), new_bsport); |
416 | mach_port_deallocate(mach_task_self(), bootstrap_port); | |
417 | bootstrap_port = new_bsport; | |
eabd1701 | 418 | |
ddbbfbc1 A |
419 | return !issetugid() ? _vproc_post_fork_ping() : NULL; |
420 | } | |
421 | ||
422 | vproc_err_t | |
423 | _vprocmgr_detach_from_console(vproc_flags_t flags __attribute__((unused))) | |
424 | { | |
425 | return _vprocmgr_switch_to_session(VPROCMGR_SESSION_BACKGROUND, 0); | |
426 | } | |
5b0a4722 | 427 | |
eabd1701 A |
428 | vproc_err_t |
429 | _vproc_post_fork_ping(void) | |
430 | { | |
431 | #if !TARGET_OS_EMBEDDED | |
432 | au_asid_t s = AU_DEFAUDITSID; | |
433 | do { | |
434 | mach_port_t session = MACH_PORT_NULL; | |
435 | kern_return_t kr = vproc_mig_post_fork_ping(bootstrap_port, mach_task_self(), &session); | |
436 | if (kr != KERN_SUCCESS) { | |
437 | /* If this happens, our bootstrap port probably got hosed. */ | |
438 | _vproc_log(LOG_ERR, "Post-fork ping failed!"); | |
439 | break; | |
440 | } | |
441 | ||
442 | /* If we get back MACH_PORT_NULL, that means we just stick with the session | |
443 | * we inherited across fork(2). | |
444 | */ | |
445 | if (session == MACH_PORT_NULL) { | |
446 | s = ~AU_DEFAUDITSID; | |
447 | break; | |
448 | } | |
449 | ||
450 | s = _audit_session_join(session); | |
451 | if (s == 0) { | |
452 | _vproc_log_error(LOG_ERR, "Could not join security session!"); | |
453 | s = AU_DEFAUDITSID; | |
454 | } else { | |
455 | _vproc_log(LOG_DEBUG, "Joined session %d.", s); | |
456 | } | |
457 | } while (0); | |
458 | ||
459 | return s != AU_DEFAUDITSID ? NULL : _vproc_post_fork_ping; | |
460 | #else | |
461 | mach_port_t session = MACH_PORT_NULL; | |
462 | return vproc_mig_post_fork_ping(bootstrap_port, mach_task_self(), &session) ? _vproc_post_fork_ping : NULL; | |
463 | #endif | |
464 | } | |
465 | ||
466 | vproc_err_t | |
467 | _vprocmgr_init(const char *session_type) | |
468 | { | |
469 | if (vproc_mig_init_session(bootstrap_port, (char *)session_type, _audit_session_self()) == 0) { | |
470 | return NULL; | |
471 | } | |
472 | ||
473 | return (vproc_err_t)_vprocmgr_init; | |
474 | } | |
475 | ||
5b0a4722 A |
476 | pid_t |
477 | _spawn_via_launchd(const char *label, const char *const *argv, const struct spawn_via_launchd_attr *spawn_attrs, int struct_version) | |
478 | { | |
479 | size_t i, good_enough_size = 10*1024*1024; | |
480 | mach_msg_type_number_t indata_cnt = 0; | |
481 | vm_offset_t indata = 0; | |
482 | mach_port_t obsvr_port = MACH_PORT_NULL; | |
483 | launch_data_t tmp, tmp_array, in_obj; | |
484 | const char *const *tmpp; | |
485 | kern_return_t kr = 1; | |
486 | void *buf = NULL; | |
487 | pid_t p = -1; | |
488 | ||
489 | if ((in_obj = launch_data_alloc(LAUNCH_DATA_DICTIONARY)) == NULL) { | |
490 | goto out; | |
491 | } | |
492 | ||
493 | if ((tmp = launch_data_new_string(label)) == NULL) { | |
494 | goto out; | |
495 | } | |
496 | ||
497 | launch_data_dict_insert(in_obj, tmp, LAUNCH_JOBKEY_LABEL); | |
498 | ||
499 | if ((tmp_array = launch_data_alloc(LAUNCH_DATA_ARRAY)) == NULL) { | |
500 | goto out; | |
501 | } | |
502 | ||
503 | for (i = 0; *argv; i++, argv++) { | |
504 | tmp = launch_data_new_string(*argv); | |
505 | if (tmp == NULL) { | |
506 | goto out; | |
507 | } | |
508 | ||
509 | launch_data_array_set_index(tmp_array, tmp, i); | |
510 | } | |
511 | ||
512 | launch_data_dict_insert(in_obj, tmp_array, LAUNCH_JOBKEY_PROGRAMARGUMENTS); | |
513 | ||
514 | if (spawn_attrs) switch (struct_version) { | |
dcace88f | 515 | case 3: |
5b0a4722 | 516 | case 2: |
f36da725 | 517 | #if HAVE_QUARANTINE |
5b0a4722 A |
518 | if (spawn_attrs->spawn_quarantine) { |
519 | char qbuf[QTN_SERIALIZED_DATA_MAX]; | |
520 | size_t qbuf_sz = QTN_SERIALIZED_DATA_MAX; | |
521 | ||
522 | if (qtn_proc_to_data(spawn_attrs->spawn_quarantine, qbuf, &qbuf_sz) == 0) { | |
523 | tmp = launch_data_new_opaque(qbuf, qbuf_sz); | |
524 | launch_data_dict_insert(in_obj, tmp, LAUNCH_JOBKEY_QUARANTINEDATA); | |
525 | } | |
526 | } | |
f36da725 | 527 | #endif |
5b0a4722 A |
528 | |
529 | if (spawn_attrs->spawn_seatbelt_profile) { | |
530 | tmp = launch_data_new_string(spawn_attrs->spawn_seatbelt_profile); | |
531 | launch_data_dict_insert(in_obj, tmp, LAUNCH_JOBKEY_SANDBOXPROFILE); | |
532 | } | |
533 | ||
534 | if (spawn_attrs->spawn_seatbelt_flags) { | |
535 | tmp = launch_data_new_integer(*spawn_attrs->spawn_seatbelt_flags); | |
536 | launch_data_dict_insert(in_obj, tmp, LAUNCH_JOBKEY_SANDBOXFLAGS); | |
537 | } | |
538 | ||
539 | /* fall through */ | |
540 | case 1: | |
541 | if (spawn_attrs->spawn_binpref) { | |
542 | tmp_array = launch_data_alloc(LAUNCH_DATA_ARRAY); | |
543 | for (i = 0; i < spawn_attrs->spawn_binpref_cnt; i++) { | |
544 | tmp = launch_data_new_integer(spawn_attrs->spawn_binpref[i]); | |
545 | launch_data_array_set_index(tmp_array, tmp, i); | |
546 | } | |
547 | launch_data_dict_insert(in_obj, tmp_array, LAUNCH_JOBKEY_BINARYORDERPREFERENCE); | |
548 | } | |
549 | /* fall through */ | |
550 | case 0: | |
551 | if (spawn_attrs->spawn_flags & SPAWN_VIA_LAUNCHD_STOPPED) { | |
552 | tmp = launch_data_new_bool(true); | |
553 | launch_data_dict_insert(in_obj, tmp, LAUNCH_JOBKEY_WAITFORDEBUGGER); | |
554 | } | |
dcace88f A |
555 | if (spawn_attrs->spawn_flags & SPAWN_VIA_LAUNCHD_TALAPP) { |
556 | tmp = launch_data_new_string(LAUNCH_KEY_POSIXSPAWNTYPE_TALAPP); | |
557 | launch_data_dict_insert(in_obj, tmp, LAUNCH_JOBKEY_POSIXSPAWNTYPE); | |
558 | } | |
559 | if (spawn_attrs->spawn_flags & SPAWN_VIA_LAUNCHD_WIDGET) { | |
560 | tmp = launch_data_new_string(LAUNCH_KEY_POSIXSPAWNTYPE_WIDGET); | |
561 | launch_data_dict_insert(in_obj, tmp, LAUNCH_JOBKEY_POSIXSPAWNTYPE); | |
562 | } | |
563 | if (spawn_attrs->spawn_flags & SPAWN_VIA_LAUNCHD_DISABLE_ASLR) { | |
564 | tmp = launch_data_new_bool(true); | |
565 | launch_data_dict_insert(in_obj, tmp, LAUNCH_JOBKEY_DISABLEASLR); | |
566 | } | |
5b0a4722 A |
567 | |
568 | if (spawn_attrs->spawn_env) { | |
569 | launch_data_t tmp_dict = launch_data_alloc(LAUNCH_DATA_DICTIONARY); | |
570 | ||
571 | for (tmpp = spawn_attrs->spawn_env; *tmpp; tmpp++) { | |
572 | char *eqoff, tmpstr[strlen(*tmpp) + 1]; | |
573 | ||
574 | strcpy(tmpstr, *tmpp); | |
575 | ||
576 | eqoff = strchr(tmpstr, '='); | |
577 | ||
578 | if (!eqoff) { | |
579 | goto out; | |
580 | } | |
eabd1701 | 581 | |
5b0a4722 | 582 | *eqoff = '\0'; |
eabd1701 | 583 | |
5b0a4722 A |
584 | launch_data_dict_insert(tmp_dict, launch_data_new_string(eqoff + 1), tmpstr); |
585 | } | |
586 | ||
587 | launch_data_dict_insert(in_obj, tmp_dict, LAUNCH_JOBKEY_ENVIRONMENTVARIABLES); | |
588 | } | |
589 | ||
590 | if (spawn_attrs->spawn_path) { | |
591 | tmp = launch_data_new_string(spawn_attrs->spawn_path); | |
592 | launch_data_dict_insert(in_obj, tmp, LAUNCH_JOBKEY_PROGRAM); | |
593 | } | |
594 | ||
595 | if (spawn_attrs->spawn_chdir) { | |
596 | tmp = launch_data_new_string(spawn_attrs->spawn_chdir); | |
597 | launch_data_dict_insert(in_obj, tmp, LAUNCH_JOBKEY_WORKINGDIRECTORY); | |
598 | } | |
599 | ||
600 | if (spawn_attrs->spawn_umask) { | |
601 | tmp = launch_data_new_integer(*spawn_attrs->spawn_umask); | |
602 | launch_data_dict_insert(in_obj, tmp, LAUNCH_JOBKEY_UMASK); | |
603 | } | |
604 | ||
605 | break; | |
606 | default: | |
607 | break; | |
608 | } | |
609 | ||
610 | if (!(buf = malloc(good_enough_size))) { | |
611 | goto out; | |
612 | } | |
613 | ||
614 | if ((indata_cnt = launch_data_pack(in_obj, buf, good_enough_size, NULL, NULL)) == 0) { | |
615 | goto out; | |
616 | } | |
617 | ||
618 | indata = (vm_offset_t)buf; | |
619 | ||
dcace88f A |
620 | if (struct_version == 3) { |
621 | kr = vproc_mig_spawn2(bootstrap_port, indata, indata_cnt, _audit_session_self(), &p, &obsvr_port); | |
622 | } else { | |
623 | _vproc_set_crash_log_message("Bogus version passed to _spawn_via_launchd(). For this release, the only valid version is 3."); | |
624 | } | |
5b0a4722 A |
625 | |
626 | if (kr == VPROC_ERR_TRY_PER_USER) { | |
627 | mach_port_t puc; | |
628 | ||
629 | if (vproc_mig_lookup_per_user_context(bootstrap_port, 0, &puc) == 0) { | |
dcace88f A |
630 | if (struct_version == 3) { |
631 | kr = vproc_mig_spawn2(puc, indata, indata_cnt, _audit_session_self(), &p, &obsvr_port); | |
632 | } | |
5b0a4722 A |
633 | mach_port_deallocate(mach_task_self(), puc); |
634 | } | |
635 | } | |
636 | ||
637 | out: | |
638 | if (in_obj) { | |
639 | launch_data_free(in_obj); | |
640 | } | |
641 | ||
642 | if (buf) { | |
643 | free(buf); | |
644 | } | |
645 | ||
646 | switch (kr) { | |
647 | case BOOTSTRAP_SUCCESS: | |
648 | if (spawn_attrs && spawn_attrs->spawn_observer_port) { | |
649 | *spawn_attrs->spawn_observer_port = obsvr_port; | |
650 | } else { | |
dcace88f A |
651 | if (struct_version == 3) { |
652 | mach_port_mod_refs(mach_task_self(), obsvr_port, MACH_PORT_RIGHT_RECEIVE, -1); | |
653 | } else { | |
654 | mach_port_deallocate(mach_task_self(), obsvr_port); | |
655 | } | |
5b0a4722 A |
656 | } |
657 | return p; | |
658 | case BOOTSTRAP_NOT_PRIVILEGED: | |
659 | errno = EPERM; break; | |
660 | case BOOTSTRAP_NO_MEMORY: | |
661 | errno = ENOMEM; break; | |
662 | case BOOTSTRAP_NAME_IN_USE: | |
663 | errno = EEXIST; break; | |
664 | case 1: | |
665 | errno = EIO; break; | |
666 | default: | |
667 | errno = EINVAL; break; | |
668 | } | |
669 | ||
670 | return -1; | |
671 | } | |
672 | ||
673 | kern_return_t | |
dcace88f | 674 | mpm_wait(mach_port_t ajob __attribute__((unused)), int *wstatus) |
5b0a4722 | 675 | { |
dcace88f A |
676 | *wstatus = 0; |
677 | return 0; | |
5b0a4722 A |
678 | } |
679 | ||
680 | kern_return_t | |
dcace88f | 681 | mpm_uncork_fork(mach_port_t ajob __attribute__((unused))) |
5b0a4722 | 682 | { |
dcace88f | 683 | return KERN_FAILURE; |
5b0a4722 A |
684 | } |
685 | ||
686 | kern_return_t | |
687 | _vprocmgr_getsocket(name_t sockpath) | |
688 | { | |
689 | return vproc_mig_getsocket(bootstrap_port, sockpath); | |
690 | } | |
691 | ||
692 | vproc_err_t | |
693 | _vproc_get_last_exit_status(int *wstatus) | |
694 | { | |
695 | int64_t val; | |
696 | ||
697 | if (vproc_swap_integer(NULL, VPROC_GSK_LAST_EXIT_STATUS, 0, &val) == 0) { | |
698 | *wstatus = (int)val; | |
699 | return NULL; | |
700 | } | |
701 | ||
702 | return (vproc_err_t)_vproc_get_last_exit_status; | |
703 | } | |
704 | ||
705 | vproc_err_t | |
706 | _vproc_send_signal_by_label(const char *label, int sig) | |
707 | { | |
708 | if (vproc_mig_send_signal(bootstrap_port, (char *)label, sig) == 0) { | |
709 | return NULL; | |
710 | } | |
711 | ||
712 | return _vproc_send_signal_by_label; | |
713 | } | |
714 | ||
715 | vproc_err_t | |
716 | _vprocmgr_log_forward(mach_port_t mp, void *data, size_t len) | |
717 | { | |
718 | if (vproc_mig_log_forward(mp, (vm_offset_t)data, len) == 0) { | |
719 | return NULL; | |
720 | } | |
721 | ||
722 | return _vprocmgr_log_forward; | |
723 | } | |
724 | ||
725 | vproc_err_t | |
726 | _vprocmgr_log_drain(vproc_t vp __attribute__((unused)), pthread_mutex_t *mutex, _vprocmgr_log_drain_callback_t func) | |
727 | { | |
728 | mach_msg_type_number_t outdata_cnt, tmp_cnt; | |
729 | vm_offset_t outdata = 0; | |
ddbbfbc1 | 730 | struct timeval tv; |
5b0a4722 A |
731 | struct logmsg_s *lm; |
732 | ||
733 | if (!func) { | |
734 | return _vprocmgr_log_drain; | |
735 | } | |
736 | ||
737 | if (vproc_mig_log_drain(bootstrap_port, &outdata, &outdata_cnt) != 0) { | |
738 | return _vprocmgr_log_drain; | |
739 | } | |
740 | ||
741 | tmp_cnt = outdata_cnt; | |
742 | ||
743 | if (mutex) { | |
744 | pthread_mutex_lock(mutex); | |
745 | } | |
746 | ||
747 | for (lm = (struct logmsg_s *)outdata; tmp_cnt > 0; lm = ((void *)lm + lm->obj_sz)) { | |
ddbbfbc1 A |
748 | lm->from_name = (char *)lm + lm->from_name_offset; |
749 | lm->about_name = (char *)lm + lm->about_name_offset; | |
750 | lm->msg = (char *)lm + lm->msg_offset; | |
751 | lm->session_name = (char *)lm + lm->session_name_offset; | |
752 | ||
753 | tv.tv_sec = lm->when / USEC_PER_SEC; | |
754 | tv.tv_usec = lm->when % USEC_PER_SEC; | |
5b0a4722 | 755 | |
ddbbfbc1 | 756 | func(&tv, lm->from_pid, lm->about_pid, lm->sender_uid, lm->sender_gid, lm->pri, |
5b0a4722 A |
757 | lm->from_name, lm->about_name, lm->session_name, lm->msg); |
758 | ||
759 | tmp_cnt -= lm->obj_sz; | |
760 | } | |
761 | ||
762 | if (mutex) { | |
763 | pthread_mutex_unlock(mutex); | |
764 | } | |
765 | ||
766 | if (outdata) { | |
767 | mig_deallocate(outdata, outdata_cnt); | |
768 | } | |
769 | ||
770 | return NULL; | |
771 | } | |
772 | ||
773 | vproc_err_t | |
ddbbfbc1 | 774 | vproc_swap_integer(vproc_t vp, vproc_gsk_t key, int64_t *inval, int64_t *outval) |
5b0a4722 | 775 | { |
ddbbfbc1 | 776 | kern_return_t kr = KERN_FAILURE; |
eabd1701 | 777 | int64_t dummyval = 0; |
ddbbfbc1 A |
778 | mach_port_t mp = vp ? vp->j_port : bootstrap_port; |
779 | if ((kr = vproc_mig_swap_integer(mp, inval ? key : 0, outval ? key : 0, inval ? *inval : 0, outval ? outval : &dummyval)) == 0) { | |
5b0a4722 | 780 | switch (key) { |
dcace88f A |
781 | case VPROC_GSK_PERUSER_SUSPEND: |
782 | if (dummyval) { | |
783 | /* Wait for the per-user launchd to exit before returning. */ | |
784 | int kq = kqueue(); | |
785 | struct kevent kev; | |
786 | EV_SET(&kev, dummyval, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, 0); | |
787 | int r = kevent(kq, &kev, 1, &kev, 1, NULL); | |
788 | (void)close(kq); | |
789 | if (r != 1) { | |
790 | return NULL; | |
791 | } | |
792 | break; | |
ddbbfbc1 | 793 | } |
5b0a4722 A |
794 | default: |
795 | break; | |
796 | } | |
797 | return NULL; | |
798 | } | |
799 | ||
800 | return (vproc_err_t)vproc_swap_integer; | |
801 | } | |
802 | ||
5b0a4722 | 803 | vproc_err_t |
ddbbfbc1 | 804 | vproc_swap_complex(vproc_t vp, vproc_gsk_t key, launch_data_t inval, launch_data_t *outval) |
5b0a4722 A |
805 | { |
806 | size_t data_offset = 0, good_enough_size = 10*1024*1024; | |
807 | mach_msg_type_number_t indata_cnt = 0, outdata_cnt; | |
808 | vm_offset_t indata = 0, outdata = 0; | |
809 | launch_data_t out_obj; | |
810 | void *rval = vproc_swap_complex; | |
811 | void *buf = NULL; | |
812 | ||
813 | if (inval) { | |
814 | if (!(buf = malloc(good_enough_size))) { | |
815 | goto out; | |
816 | } | |
817 | ||
818 | if ((indata_cnt = launch_data_pack(inval, buf, good_enough_size, NULL, NULL)) == 0) { | |
819 | goto out; | |
820 | } | |
821 | ||
822 | indata = (vm_offset_t)buf; | |
823 | } | |
824 | ||
ddbbfbc1 A |
825 | mach_port_t mp = vp ? vp->j_port : bootstrap_port; |
826 | if (vproc_mig_swap_complex(mp, inval ? key : 0, outval ? key : 0, indata, indata_cnt, &outdata, &outdata_cnt) != 0) { | |
5b0a4722 A |
827 | goto out; |
828 | } | |
829 | ||
830 | if (outval) { | |
831 | if (!(out_obj = launch_data_unpack((void *)outdata, outdata_cnt, NULL, 0, &data_offset, NULL))) { | |
832 | goto out; | |
833 | } | |
834 | ||
835 | if (!(*outval = launch_data_copy(out_obj))) { | |
836 | goto out; | |
837 | } | |
838 | } | |
839 | ||
840 | rval = NULL; | |
841 | out: | |
842 | if (buf) { | |
843 | free(buf); | |
844 | } | |
845 | ||
846 | if (outdata) { | |
847 | mig_deallocate(outdata, outdata_cnt); | |
848 | } | |
849 | ||
850 | return rval; | |
851 | } | |
852 | ||
ddbbfbc1 A |
853 | vproc_err_t |
854 | vproc_swap_string(vproc_t vp, vproc_gsk_t key, const char *instr, char **outstr) | |
855 | { | |
856 | launch_data_t instr_data = instr ? launch_data_new_string(instr) : NULL; | |
857 | launch_data_t outstr_data = NULL; | |
eabd1701 | 858 | |
ddbbfbc1 | 859 | vproc_err_t verr = vproc_swap_complex(vp, key, instr_data, &outstr_data); |
dcace88f A |
860 | if (!verr && outstr) { |
861 | if (launch_data_get_type(outstr_data) == LAUNCH_DATA_STRING) { | |
ddbbfbc1 A |
862 | *outstr = strdup(launch_data_get_string(outstr_data)); |
863 | } else { | |
864 | verr = (vproc_err_t)vproc_swap_string; | |
865 | } | |
866 | launch_data_free(outstr_data); | |
867 | } | |
dcace88f | 868 | if (instr_data) { |
ddbbfbc1 A |
869 | launch_data_free(instr_data); |
870 | } | |
eabd1701 | 871 | |
ddbbfbc1 A |
872 | return verr; |
873 | } | |
874 | ||
5b0a4722 A |
875 | void * |
876 | reboot2(uint64_t flags) | |
877 | { | |
eabd1701 A |
878 | mach_port_t rootbs = MACH_PORT_NULL; |
879 | (void)bootstrap_get_root(bootstrap_port, &rootbs); | |
880 | if (vproc_mig_reboot2(rootbs, flags) == 0) { | |
881 | (void)mach_port_deallocate(mach_task_self(), rootbs); | |
5b0a4722 A |
882 | return NULL; |
883 | } | |
884 | ||
885 | return reboot2; | |
886 | } | |
887 | ||
f36da725 | 888 | vproc_err_t |
eabd1701 A |
889 | _vproc_kickstart_by_label(const char *label, pid_t *out_pid, |
890 | mach_port_t *out_port_name __unused, mach_port_t *out_obsrvr_port __unused, | |
891 | vproc_flags_t flags) | |
f36da725 | 892 | { |
dcace88f A |
893 | /* Ignore the two port parameters. This SPI isn't long for this world, and |
894 | * all the current clients just leak them anyway. | |
895 | */ | |
896 | kern_return_t kr = vproc_mig_kickstart(bootstrap_port, (char *)label, out_pid, flags); | |
897 | if (kr == KERN_SUCCESS) { | |
f36da725 A |
898 | return NULL; |
899 | } | |
900 | ||
901 | return (vproc_err_t)_vproc_kickstart_by_label; | |
902 | } | |
903 | ||
5b0a4722 A |
904 | vproc_err_t |
905 | _vproc_set_global_on_demand(bool state) | |
906 | { | |
907 | int64_t val = state ? ~0 : 0; | |
908 | ||
909 | if (vproc_swap_integer(NULL, VPROC_GSK_GLOBAL_ON_DEMAND, &val, NULL) == 0) { | |
910 | return NULL; | |
911 | } | |
912 | ||
913 | return (vproc_err_t)_vproc_set_global_on_demand; | |
914 | } | |
915 | ||
916 | void | |
917 | _vproc_logv(int pri, int err, const char *msg, va_list ap) | |
918 | { | |
919 | char flat_msg[3000]; | |
920 | ||
921 | vsnprintf(flat_msg, sizeof(flat_msg), msg, ap); | |
922 | ||
923 | vproc_mig_log(bootstrap_port, pri, err, flat_msg); | |
924 | } | |
925 | ||
926 | void | |
927 | _vproc_log(int pri, const char *msg, ...) | |
928 | { | |
929 | va_list ap; | |
930 | ||
931 | va_start(ap, msg); | |
932 | _vproc_logv(pri, 0, msg, ap); | |
933 | va_end(ap); | |
934 | } | |
935 | ||
936 | void | |
937 | _vproc_log_error(int pri, const char *msg, ...) | |
938 | { | |
939 | int saved_errno = errno; | |
940 | va_list ap; | |
941 | ||
942 | va_start(ap, msg); | |
943 | _vproc_logv(pri, saved_errno, msg, ap); | |
944 | va_end(ap); | |
945 | } | |
dcace88f | 946 | |
dcace88f A |
947 | /* The type naming convention is as follows: |
948 | * For requests... | |
949 | * union __RequestUnion__<userprefix><subsystem>_subsystem | |
950 | * For replies... | |
951 | * union __ReplyUnion__<userprefix><subsystem>_subsystem | |
952 | */ | |
953 | union maxmsgsz { | |
954 | union __RequestUnion__helper_downcall_launchd_helper_subsystem req; | |
955 | union __ReplyUnion__helper_downcall_launchd_helper_subsystem rep; | |
956 | }; | |
957 | ||
958 | size_t vprocmgr_helper_maxmsgsz = sizeof(union maxmsgsz); | |
959 | ||
dcace88f A |
960 | kern_return_t |
961 | helper_recv_wait(mach_port_t p, int status) | |
962 | { | |
eabd1701 A |
963 | #if __LAUNCH_MACH_PORT_CONTEXT_T_DEFINED__ |
964 | mach_port_context_t ctx = status; | |
965 | #else | |
966 | mach_vm_address_t ctx = status; | |
967 | #endif | |
968 | ||
969 | return (errno = mach_port_set_context(mach_task_self(), p, ctx)); | |
dcace88f A |
970 | } |
971 | ||
972 | int | |
973 | launch_wait(mach_port_t port) | |
974 | { | |
975 | int status = -1; | |
976 | errno = mach_msg_server_once(launchd_helper_server, vprocmgr_helper_maxmsgsz, port, 0); | |
977 | if (errno == MACH_MSG_SUCCESS) { | |
eabd1701 A |
978 | #if __LAUNCH_MACH_PORT_CONTEXT_T_DEFINED__ |
979 | mach_port_context_t ctx = 0; | |
980 | #else | |
dcace88f | 981 | mach_vm_address_t ctx = 0; |
eabd1701 | 982 | #endif |
dcace88f A |
983 | if ((errno = mach_port_get_context(mach_task_self(), port, &ctx)) == KERN_SUCCESS) { |
984 | status = ctx; | |
985 | } | |
986 | } | |
987 | ||
988 | return status; | |
989 | } | |
eabd1701 A |
990 | |
991 | launch_data_t | |
992 | launch_socket_service_check_in(void) | |
993 | { | |
994 | launch_data_t reply = NULL; | |
995 | ||
996 | size_t big_enough = 10 * 1024; | |
997 | void *buff = malloc(big_enough); | |
998 | if (buff) { | |
999 | launch_data_t req = launch_data_new_string(LAUNCH_KEY_CHECKIN); | |
1000 | if (req) { | |
1001 | size_t sz = launch_data_pack(req, buff, big_enough, NULL, NULL); | |
1002 | if (sz) { | |
1003 | vm_address_t sreply = 0; | |
1004 | mach_msg_size_t sreplyCnt = 0; | |
1005 | mach_port_array_t fdps = NULL; | |
1006 | mach_msg_size_t fdpsCnt = 0; | |
1007 | kern_return_t kr = vproc_mig_legacy_ipc_request(bootstrap_port, (vm_address_t)buff, sz, NULL, 0, &sreply, &sreplyCnt, &fdps, &fdpsCnt, _audit_session_self()); | |
1008 | if (kr == BOOTSTRAP_SUCCESS) { | |
1009 | int fds[128]; | |
1010 | ||
1011 | size_t i = 0; | |
1012 | size_t nfds = fdpsCnt / sizeof(fdps[0]); | |
1013 | for (i = 0; i < nfds; i++) { | |
1014 | fds[i] = fileport_makefd(fdps[i]); | |
1015 | (void)mach_port_deallocate(mach_task_self(), fdps[i]); | |
1016 | } | |
1017 | ||
1018 | size_t dataoff = 0; | |
1019 | size_t fdoff = 0; | |
1020 | reply = launch_data_unpack((void *)sreply, sreplyCnt, fds, nfds, &dataoff, &fdoff); | |
1021 | reply = launch_data_copy(reply); | |
1022 | ||
1023 | mig_deallocate(sreply, sreplyCnt); | |
1024 | mig_deallocate((vm_address_t)fdps, fdpsCnt); | |
1025 | } | |
1026 | } | |
1027 | ||
1028 | launch_data_free(req); | |
1029 | } | |
1030 | ||
1031 | free(buff); | |
1032 | } | |
1033 | ||
1034 | return reply; | |
1035 | } |