]>
Commit | Line | Data |
---|---|---|
ed34e3c3 A |
1 | /* |
2 | * Copyright (c) 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 | ||
5b0a4722 A |
21 | static const char *const __rcs_file_version__ = "$Revision: 23274 $"; |
22 | ||
23 | #include "config.h" | |
24 | #include "launchd_unix_ipc.h" | |
ed34e3c3 A |
25 | |
26 | #include <sys/types.h> | |
27 | #include <sys/queue.h> | |
28 | #include <sys/event.h> | |
29 | #include <sys/stat.h> | |
30 | #include <sys/ucred.h> | |
31 | #include <sys/fcntl.h> | |
32 | #include <sys/un.h> | |
33 | #include <sys/wait.h> | |
34 | #include <sys/sysctl.h> | |
35 | #include <sys/sockio.h> | |
36 | #include <sys/time.h> | |
37 | #include <sys/resource.h> | |
38 | #include <sys/ioctl.h> | |
39 | #include <unistd.h> | |
40 | #include <signal.h> | |
41 | #include <errno.h> | |
ed34e3c3 A |
42 | #include <libgen.h> |
43 | #include <stdio.h> | |
44 | #include <stdlib.h> | |
45 | #include <stdarg.h> | |
46 | #include <stdbool.h> | |
47 | #include <paths.h> | |
48 | #include <string.h> | |
49 | ||
5b0a4722 A |
50 | #include "liblaunch_public.h" |
51 | #include "liblaunch_private.h" | |
ed34e3c3 | 52 | #include "launchd.h" |
5b0a4722 | 53 | #include "launchd_runtime.h" |
ed34e3c3 | 54 | #include "launchd_core_logic.h" |
ed34e3c3 A |
55 | |
56 | extern char **environ; | |
57 | ||
5b0a4722 | 58 | static LIST_HEAD(, conncb) connections; |
ed34e3c3 A |
59 | |
60 | static launch_data_t adjust_rlimits(launch_data_t in); | |
61 | ||
62 | static void ipc_readmsg2(launch_data_t data, const char *cmd, void *context); | |
63 | ||
64 | static void ipc_listen_callback(void *obj __attribute__((unused)), struct kevent *kev); | |
65 | ||
66 | static kq_callback kqipc_listen_callback = ipc_listen_callback; | |
67 | ||
68 | static pid_t ipc_self = 0; | |
69 | ||
70 | char *sockpath = NULL; | |
71 | static char *sockdir = NULL; | |
72 | ||
73 | static bool ipc_inited = false; | |
74 | ||
75 | void | |
76 | ipc_clean_up(void) | |
77 | { | |
5b0a4722 | 78 | if (ipc_self != getpid()) { |
ed34e3c3 | 79 | return; |
5b0a4722 | 80 | } |
ed34e3c3 | 81 | |
5b0a4722 A |
82 | if (-1 == unlink(sockpath)) { |
83 | runtime_syslog(LOG_WARNING, "unlink(\"%s\"): %m", sockpath); | |
84 | } else if (-1 == rmdir(sockdir)) { | |
85 | runtime_syslog(LOG_WARNING, "rmdir(\"%s\"): %m", sockdir); | |
86 | } | |
ed34e3c3 A |
87 | } |
88 | ||
89 | void | |
5b0a4722 | 90 | ipc_server_init(void) |
ed34e3c3 A |
91 | { |
92 | struct sockaddr_un sun; | |
93 | mode_t oldmask; | |
94 | int r, fd = -1; | |
95 | char ourdir[1024]; | |
ed34e3c3 | 96 | |
5b0a4722 | 97 | if (ipc_inited) { |
ed34e3c3 | 98 | return; |
5b0a4722 | 99 | } |
ed34e3c3 A |
100 | |
101 | memset(&sun, 0, sizeof(sun)); | |
102 | sun.sun_family = AF_UNIX; | |
103 | ||
104 | if (getpid() == 1) { | |
105 | strcpy(ourdir, LAUNCHD_SOCK_PREFIX); | |
106 | strncpy(sun.sun_path, LAUNCHD_SOCK_PREFIX "/sock", sizeof(sun.sun_path)); | |
107 | ||
108 | unlink(ourdir); | |
109 | if (mkdir(ourdir, S_IRWXU) == -1) { | |
110 | if (errno == EROFS) { | |
111 | goto out_bad; | |
112 | } else if (errno == EEXIST) { | |
113 | struct stat sb; | |
114 | stat(ourdir, &sb); | |
115 | if (!S_ISDIR(sb.st_mode)) { | |
116 | errno = EEXIST; | |
5b0a4722 | 117 | runtime_syslog(LOG_ERR, "mkdir(\"%s\"): %m", LAUNCHD_SOCK_PREFIX); |
ed34e3c3 A |
118 | goto out_bad; |
119 | } | |
120 | } else { | |
5b0a4722 | 121 | runtime_syslog(LOG_ERR, "mkdir(\"%s\"): %m", ourdir); |
ed34e3c3 A |
122 | goto out_bad; |
123 | } | |
124 | } | |
125 | } else { | |
126 | snprintf(ourdir, sizeof(ourdir), "/tmp/launchd-%u.XXXXXX", getpid()); | |
5b0a4722 | 127 | if (!launchd_assumes(mkdtemp(ourdir) != NULL)) { |
ed34e3c3 | 128 | goto out_bad; |
5b0a4722 | 129 | } |
ed34e3c3 | 130 | snprintf(sun.sun_path, sizeof(sun.sun_path), "%s/sock", ourdir); |
ed34e3c3 A |
131 | } |
132 | ||
133 | if (unlink(sun.sun_path) == -1 && errno != ENOENT) { | |
5b0a4722 A |
134 | if (errno != EROFS) { |
135 | runtime_syslog(LOG_ERR, "unlink(\"thesocket\"): %m"); | |
136 | } | |
ed34e3c3 A |
137 | goto out_bad; |
138 | } | |
139 | ||
5b0a4722 | 140 | if (!launchd_assumes((fd = _fd(socket(AF_UNIX, SOCK_STREAM, 0))) != -1)) { |
ed34e3c3 | 141 | goto out_bad; |
5b0a4722 | 142 | } |
ed34e3c3 A |
143 | |
144 | oldmask = umask(S_IRWXG|S_IRWXO); | |
145 | r = bind(fd, (struct sockaddr *)&sun, sizeof(sun)); | |
146 | umask(oldmask); | |
147 | ||
148 | if (r == -1) { | |
5b0a4722 A |
149 | if (errno != EROFS) { |
150 | runtime_syslog(LOG_ERR, "bind(\"thesocket\"): %m"); | |
151 | } | |
ed34e3c3 A |
152 | goto out_bad; |
153 | } | |
154 | ||
155 | if (listen(fd, SOMAXCONN) == -1) { | |
5b0a4722 | 156 | runtime_syslog(LOG_ERR, "listen(\"thesocket\"): %m"); |
ed34e3c3 A |
157 | goto out_bad; |
158 | } | |
159 | ||
5b0a4722 A |
160 | if (kevent_mod(fd, EVFILT_READ, EV_ADD, 0, 0, &kqipc_listen_callback) == -1) { |
161 | runtime_syslog(LOG_ERR, "kevent_mod(\"thesocket\", EVFILT_READ): %m"); | |
ed34e3c3 A |
162 | goto out_bad; |
163 | } | |
164 | ||
165 | ipc_inited = true; | |
166 | ||
5b0a4722 A |
167 | sockdir = strdup(ourdir); |
168 | sockpath = strdup(sun.sun_path); | |
169 | ipc_self = getpid(); | |
170 | atexit(ipc_clean_up); | |
ed34e3c3 A |
171 | |
172 | out_bad: | |
5b0a4722 A |
173 | if (!ipc_inited && fd != -1) { |
174 | launchd_assumes(runtime_close(fd) == 0); | |
175 | } | |
ed34e3c3 A |
176 | } |
177 | ||
178 | void | |
5b0a4722 | 179 | ipc_open(int fd, job_t j) |
ed34e3c3 A |
180 | { |
181 | struct conncb *c = calloc(1, sizeof(struct conncb)); | |
182 | ||
183 | fcntl(fd, F_SETFL, O_NONBLOCK); | |
184 | ||
185 | c->kqconn_callback = ipc_callback; | |
186 | c->conn = launchd_fdopen(fd); | |
187 | c->j = j; | |
5b0a4722 | 188 | LIST_INSERT_HEAD(&connections, c, sle); |
ed34e3c3 A |
189 | kevent_mod(fd, EVFILT_READ, EV_ADD, 0, 0, &c->kqconn_callback); |
190 | } | |
191 | ||
192 | void | |
193 | ipc_listen_callback(void *obj __attribute__((unused)), struct kevent *kev) | |
194 | { | |
195 | struct sockaddr_un sun; | |
196 | socklen_t sl = sizeof(sun); | |
197 | int cfd; | |
198 | ||
199 | if ((cfd = _fd(accept(kev->ident, (struct sockaddr *)&sun, &sl))) == -1) { | |
200 | return; | |
201 | } | |
202 | ||
203 | ipc_open(cfd, NULL); | |
204 | } | |
205 | ||
206 | void | |
207 | ipc_callback(void *obj, struct kevent *kev) | |
208 | { | |
209 | struct conncb *c = obj; | |
210 | int r; | |
211 | ||
212 | if (kev->filter == EVFILT_READ) { | |
213 | if (launchd_msg_recv(c->conn, ipc_readmsg, c) == -1 && errno != EAGAIN) { | |
5b0a4722 A |
214 | if (errno != ECONNRESET) { |
215 | runtime_syslog(LOG_DEBUG, "%s(): recv: %m", __func__); | |
216 | } | |
ed34e3c3 A |
217 | ipc_close(c); |
218 | } | |
219 | } else if (kev->filter == EVFILT_WRITE) { | |
220 | r = launchd_msg_send(c->conn, NULL); | |
221 | if (r == -1) { | |
222 | if (errno != EAGAIN) { | |
5b0a4722 | 223 | runtime_syslog(LOG_DEBUG, "%s(): send: %m", __func__); |
ed34e3c3 A |
224 | ipc_close(c); |
225 | } | |
226 | } else if (r == 0) { | |
227 | kevent_mod(launchd_getfd(c->conn), EVFILT_WRITE, EV_DELETE, 0, 0, NULL); | |
228 | } | |
229 | } else { | |
5b0a4722 | 230 | runtime_syslog(LOG_DEBUG, "%s(): unknown filter type!", __func__); |
ed34e3c3 A |
231 | ipc_close(c); |
232 | } | |
233 | } | |
234 | ||
235 | static void set_user_env(launch_data_t obj, const char *key, void *context __attribute__((unused))) | |
236 | { | |
237 | setenv(key, launch_data_get_string(obj), 1); | |
238 | } | |
239 | ||
5b0a4722 A |
240 | void |
241 | ipc_close_all_with_job(job_t j) | |
242 | { | |
243 | struct conncb *ci, *cin; | |
244 | ||
245 | LIST_FOREACH_SAFE(ci, &connections, sle, cin) { | |
246 | if (ci->j == j) { | |
247 | ipc_close(ci); | |
248 | } | |
249 | } | |
250 | } | |
251 | ||
ed34e3c3 A |
252 | void |
253 | ipc_close_fds(launch_data_t o) | |
254 | { | |
255 | size_t i; | |
256 | ||
257 | switch (launch_data_get_type(o)) { | |
258 | case LAUNCH_DATA_DICTIONARY: | |
259 | launch_data_dict_iterate(o, (void (*)(launch_data_t, const char *, void *))ipc_close_fds, NULL); | |
260 | break; | |
261 | case LAUNCH_DATA_ARRAY: | |
262 | for (i = 0; i < launch_data_array_get_count(o); i++) | |
263 | ipc_close_fds(launch_data_array_get_index(o, i)); | |
264 | break; | |
265 | case LAUNCH_DATA_FD: | |
5b0a4722 A |
266 | if (launch_data_get_fd(o) != -1) { |
267 | launchd_assumes(runtime_close(launch_data_get_fd(o)) == 0); | |
268 | } | |
ed34e3c3 A |
269 | break; |
270 | default: | |
271 | break; | |
272 | } | |
273 | } | |
274 | ||
275 | void | |
276 | ipc_revoke_fds(launch_data_t o) | |
277 | { | |
278 | size_t i; | |
279 | ||
280 | switch (launch_data_get_type(o)) { | |
281 | case LAUNCH_DATA_DICTIONARY: | |
282 | launch_data_dict_iterate(o, (void (*)(launch_data_t, const char *, void *))ipc_revoke_fds, NULL); | |
283 | break; | |
284 | case LAUNCH_DATA_ARRAY: | |
285 | for (i = 0; i < launch_data_array_get_count(o); i++) | |
286 | ipc_revoke_fds(launch_data_array_get_index(o, i)); | |
287 | break; | |
288 | case LAUNCH_DATA_FD: | |
289 | launch_data_set_fd(o, -1); | |
290 | break; | |
291 | default: | |
292 | break; | |
293 | } | |
294 | } | |
295 | ||
296 | struct readmsg_context { | |
297 | struct conncb *c; | |
298 | launch_data_t resp; | |
299 | }; | |
300 | ||
301 | void | |
302 | ipc_readmsg(launch_data_t msg, void *context) | |
303 | { | |
304 | struct readmsg_context rmc = { context, NULL }; | |
305 | ||
306 | if (LAUNCH_DATA_DICTIONARY == launch_data_get_type(msg)) { | |
307 | launch_data_dict_iterate(msg, ipc_readmsg2, &rmc); | |
308 | } else if (LAUNCH_DATA_STRING == launch_data_get_type(msg)) { | |
309 | ipc_readmsg2(NULL, launch_data_get_string(msg), &rmc); | |
310 | } else { | |
311 | rmc.resp = launch_data_new_errno(EINVAL); | |
312 | } | |
313 | ||
5b0a4722 | 314 | if (NULL == rmc.resp) { |
ed34e3c3 | 315 | rmc.resp = launch_data_new_errno(ENOSYS); |
5b0a4722 | 316 | } |
ed34e3c3 A |
317 | |
318 | ipc_close_fds(msg); | |
319 | ||
320 | if (launchd_msg_send(rmc.c->conn, rmc.resp) == -1) { | |
321 | if (errno == EAGAIN) { | |
322 | kevent_mod(launchd_getfd(rmc.c->conn), EVFILT_WRITE, EV_ADD, 0, 0, &rmc.c->kqconn_callback); | |
323 | } else { | |
5b0a4722 | 324 | runtime_syslog(LOG_DEBUG, "launchd_msg_send() == -1: %m"); |
ed34e3c3 A |
325 | ipc_close(rmc.c); |
326 | } | |
327 | } | |
328 | launch_data_free(rmc.resp); | |
329 | } | |
330 | ||
331 | ||
332 | void | |
333 | ipc_readmsg2(launch_data_t data, const char *cmd, void *context) | |
334 | { | |
335 | struct readmsg_context *rmc = context; | |
336 | launch_data_t resp = NULL; | |
5b0a4722 | 337 | job_t j; |
ed34e3c3 | 338 | |
5b0a4722 | 339 | if (rmc->resp) { |
ed34e3c3 | 340 | return; |
5b0a4722 A |
341 | } |
342 | ||
343 | //job_log(rmc->c->j, LOG_DEBUG, "Unix IPC request: %s", cmd); | |
ed34e3c3 A |
344 | |
345 | if (data == NULL) { | |
346 | if (!strcmp(cmd, LAUNCH_KEY_CHECKIN)) { | |
347 | if (rmc->c->j) { | |
348 | resp = job_export(rmc->c->j); | |
349 | job_checkin(rmc->c->j); | |
350 | } else { | |
351 | resp = launch_data_new_errno(EACCES); | |
352 | } | |
ed34e3c3 A |
353 | } else if (!strcmp(cmd, LAUNCH_KEY_SHUTDOWN)) { |
354 | launchd_shutdown(); | |
355 | resp = launch_data_new_errno(0); | |
356 | } else if (!strcmp(cmd, LAUNCH_KEY_SINGLEUSER)) { | |
357 | launchd_single_user(); | |
358 | resp = launch_data_new_errno(0); | |
359 | } else if (!strcmp(cmd, LAUNCH_KEY_GETJOBS)) { | |
360 | resp = job_export_all(); | |
361 | ipc_revoke_fds(resp); | |
362 | } else if (!strcmp(cmd, LAUNCH_KEY_GETRESOURCELIMITS)) { | |
363 | resp = adjust_rlimits(NULL); | |
ed34e3c3 A |
364 | } else if (!strcmp(cmd, LAUNCH_KEY_GETRUSAGESELF)) { |
365 | struct rusage rusage; | |
366 | getrusage(RUSAGE_SELF, &rusage); | |
367 | resp = launch_data_new_opaque(&rusage, sizeof(rusage)); | |
368 | } else if (!strcmp(cmd, LAUNCH_KEY_GETRUSAGECHILDREN)) { | |
369 | struct rusage rusage; | |
370 | getrusage(RUSAGE_CHILDREN, &rusage); | |
371 | resp = launch_data_new_opaque(&rusage, sizeof(rusage)); | |
ed34e3c3 A |
372 | } |
373 | } else if (!strcmp(cmd, LAUNCH_KEY_STARTJOB)) { | |
5b0a4722 A |
374 | if ((j = job_find(launch_data_get_string(data))) != NULL) { |
375 | job_dispatch(j, true); | |
ed34e3c3 A |
376 | errno = 0; |
377 | } | |
378 | resp = launch_data_new_errno(errno); | |
379 | } else if (!strcmp(cmd, LAUNCH_KEY_STOPJOB)) { | |
5b0a4722 | 380 | if ((j = job_find(launch_data_get_string(data))) != NULL) { |
ed34e3c3 A |
381 | job_stop(j); |
382 | errno = 0; | |
383 | } | |
384 | resp = launch_data_new_errno(errno); | |
385 | } else if (!strcmp(cmd, LAUNCH_KEY_REMOVEJOB)) { | |
5b0a4722 | 386 | if ((j = job_find(launch_data_get_string(data))) != NULL) { |
ed34e3c3 A |
387 | job_remove(j); |
388 | errno = 0; | |
389 | } | |
390 | resp = launch_data_new_errno(errno); | |
391 | } else if (!strcmp(cmd, LAUNCH_KEY_SUBMITJOB)) { | |
392 | if (launch_data_get_type(data) == LAUNCH_DATA_ARRAY) { | |
393 | resp = job_import_bulk(data); | |
394 | } else { | |
5b0a4722 | 395 | if (job_import(data)) { |
ed34e3c3 | 396 | errno = 0; |
5b0a4722 | 397 | } |
ed34e3c3 A |
398 | resp = launch_data_new_errno(errno); |
399 | } | |
400 | } else if (!strcmp(cmd, LAUNCH_KEY_UNSETUSERENVIRONMENT)) { | |
401 | unsetenv(launch_data_get_string(data)); | |
402 | resp = launch_data_new_errno(0); | |
403 | } else if (!strcmp(cmd, LAUNCH_KEY_SETUSERENVIRONMENT)) { | |
404 | launch_data_dict_iterate(data, set_user_env, NULL); | |
405 | resp = launch_data_new_errno(0); | |
406 | } else if (!strcmp(cmd, LAUNCH_KEY_SETRESOURCELIMITS)) { | |
407 | resp = adjust_rlimits(data); | |
408 | } else if (!strcmp(cmd, LAUNCH_KEY_GETJOB)) { | |
5b0a4722 | 409 | if ((j = job_find(launch_data_get_string(data))) == NULL) { |
ed34e3c3 A |
410 | resp = launch_data_new_errno(errno); |
411 | } else { | |
412 | resp = job_export(j); | |
413 | ipc_revoke_fds(resp); | |
414 | } | |
ed34e3c3 A |
415 | } |
416 | ||
417 | rmc->resp = resp; | |
418 | } | |
419 | ||
420 | void | |
421 | ipc_close(struct conncb *c) | |
422 | { | |
5b0a4722 A |
423 | LIST_REMOVE(c, sle); |
424 | launchd_close(c->conn, runtime_close); | |
ed34e3c3 A |
425 | free(c); |
426 | } | |
427 | ||
428 | launch_data_t | |
429 | adjust_rlimits(launch_data_t in) | |
430 | { | |
431 | struct rlimit l[RLIM_NLIMITS]; | |
432 | struct rlimit *ltmp; | |
433 | size_t i,ltmpsz; | |
434 | ||
435 | for (i = 0; i < RLIM_NLIMITS; i++) { | |
436 | launchd_assumes(getrlimit(i, l + i) != -1); | |
437 | } | |
438 | ||
439 | if (in) { | |
440 | ltmp = launch_data_get_opaque(in); | |
441 | ltmpsz = launch_data_get_opaque_size(in); | |
442 | ||
443 | if (ltmpsz > sizeof(l)) { | |
5b0a4722 | 444 | runtime_syslog(LOG_WARNING, "Too much rlimit data sent!"); |
ed34e3c3 A |
445 | ltmpsz = sizeof(l); |
446 | } | |
447 | ||
448 | for (i = 0; i < (ltmpsz / sizeof(struct rlimit)); i++) { | |
5b0a4722 | 449 | if (ltmp[i].rlim_cur == l[i].rlim_cur && ltmp[i].rlim_max == l[i].rlim_max) { |
ed34e3c3 | 450 | continue; |
5b0a4722 | 451 | } |
ed34e3c3 | 452 | |
5b0a4722 | 453 | if (/* XXX readcfg_pid && */ getpid() == 1 && (i == RLIMIT_NOFILE || i == RLIMIT_NPROC)) { |
ed34e3c3 A |
454 | int gmib[] = { CTL_KERN, KERN_MAXPROC }; |
455 | int pmib[] = { CTL_KERN, KERN_MAXPROCPERUID }; | |
456 | const char *gstr = "kern.maxproc"; | |
457 | const char *pstr = "kern.maxprocperuid"; | |
458 | int gval = ltmp[i].rlim_max; | |
459 | int pval = ltmp[i].rlim_cur; | |
460 | switch (i) { | |
461 | case RLIMIT_NOFILE: | |
462 | gmib[1] = KERN_MAXFILES; | |
463 | pmib[1] = KERN_MAXFILESPERPROC; | |
464 | gstr = "kern.maxfiles"; | |
465 | pstr = "kern.maxfilesperproc"; | |
466 | break; | |
467 | case RLIMIT_NPROC: | |
468 | /* kernel will not clamp to this value, we must */ | |
5b0a4722 | 469 | if (gval > (2048 + 20)) { |
ed34e3c3 | 470 | gval = 2048 + 20; |
5b0a4722 | 471 | } |
ed34e3c3 A |
472 | break; |
473 | default: | |
474 | break; | |
475 | } | |
476 | ||
477 | if (gval > 0) { | |
478 | launchd_assumes(sysctl(gmib, 2, NULL, NULL, &gval, sizeof(gval)) != -1); | |
479 | } else { | |
5b0a4722 | 480 | runtime_syslog(LOG_WARNING, "sysctl(\"%s\"): can't be zero", gstr); |
ed34e3c3 A |
481 | } |
482 | if (pval > 0) { | |
483 | launchd_assumes(sysctl(pmib, 2, NULL, NULL, &pval, sizeof(pval)) != -1); | |
484 | } else { | |
5b0a4722 | 485 | runtime_syslog(LOG_WARNING, "sysctl(\"%s\"): can't be zero", pstr); |
ed34e3c3 A |
486 | } |
487 | } | |
488 | launchd_assumes(setrlimit(i, ltmp + i) != -1); | |
489 | /* the kernel may have clamped the values we gave it */ | |
490 | launchd_assumes(getrlimit(i, l + i) != -1); | |
491 | } | |
492 | } | |
493 | ||
494 | return launch_data_new_opaque(l, sizeof(struct rlimit) * RLIM_NLIMITS); | |
495 | } |