]>
Commit | Line | Data |
---|---|---|
d7448a72 | 1 | /* |
7126d8c2 | 2 | * Copyright (C) 2011-2012 Red Hat, Inc. |
d7448a72 PR |
3 | * |
4 | * This copyrighted material is made available to anyone wishing to use, | |
5 | * modify, copy, or redistribute it subject to the terms and conditions | |
6 | * of the GNU Lesser General Public License v.2.1. | |
7 | * | |
8 | * You should have received a copy of the GNU Lesser General Public License | |
9 | * along with this program; if not, write to the Free Software Foundation, | |
10 | * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
11 | */ | |
12 | ||
7126d8c2 AK |
13 | #include "daemon-shared.h" |
14 | #include "daemon-server.h" | |
15 | ||
d7448a72 PR |
16 | #include <dlfcn.h> |
17 | #include <errno.h> | |
18 | #include <pthread.h> | |
19 | #include <sys/file.h> | |
20 | #include <sys/stat.h> | |
21 | #include <sys/wait.h> | |
22 | #include <sys/time.h> | |
23 | #include <sys/resource.h> | |
dc85d3fb PR |
24 | #include <netinet/in.h> |
25 | #include <sys/un.h> | |
d7448a72 PR |
26 | #include <unistd.h> |
27 | #include <signal.h> | |
28 | ||
29 | #include <syslog.h> | |
30 | ||
dc85d3fb | 31 | #if 0 |
d7448a72 PR |
32 | /* Create a device monitoring thread. */ |
33 | static int _pthread_create(pthread_t *t, void *(*fun)(void *), void *arg, int stacksize) | |
34 | { | |
35 | pthread_attr_t attr; | |
36 | pthread_attr_init(&attr); | |
37 | /* | |
38 | * We use a smaller stack since it gets preallocated in its entirety | |
39 | */ | |
40 | pthread_attr_setstacksize(&attr, stacksize); | |
41 | return pthread_create(t, &attr, fun, arg); | |
42 | } | |
dc85d3fb | 43 | #endif |
d7448a72 PR |
44 | |
45 | static volatile sig_atomic_t _shutdown_requested = 0; | |
870353f9 | 46 | static int _systemd_activation = 0; |
d7448a72 PR |
47 | |
48 | static void _exit_handler(int sig __attribute__((unused))) | |
49 | { | |
50 | _shutdown_requested = 1; | |
51 | } | |
52 | ||
53 | #ifdef linux | |
870353f9 PR |
54 | |
55 | #include <stddef.h> | |
56 | ||
57 | /* | |
58 | * Kernel version 2.6.36 and higher has | |
59 | * new OOM killer adjustment interface. | |
60 | */ | |
61 | # define OOM_ADJ_FILE_OLD "/proc/self/oom_adj" | |
62 | # define OOM_ADJ_FILE "/proc/self/oom_score_adj" | |
d7448a72 PR |
63 | |
64 | /* From linux/oom.h */ | |
870353f9 | 65 | /* Old interface */ |
d7448a72 PR |
66 | # define OOM_DISABLE (-17) |
67 | # define OOM_ADJUST_MIN (-16) | |
870353f9 PR |
68 | /* New interface */ |
69 | # define OOM_SCORE_ADJ_MIN (-1000) | |
70 | ||
71 | /* Systemd on-demand activation support */ | |
72 | # define SD_LISTEN_PID_ENV_VAR_NAME "LISTEN_PID" | |
73 | # define SD_LISTEN_FDS_ENV_VAR_NAME "LISTEN_FDS" | |
74 | # define SD_LISTEN_FDS_START 3 | |
75 | # define SD_FD_SOCKET_SERVER SD_LISTEN_FDS_START | |
76 | ||
77 | # include <stdio.h> | |
78 | ||
79 | static int _set_oom_adj(const char *oom_adj_path, int val) | |
80 | { | |
81 | FILE *fp; | |
82 | ||
83 | if (!(fp = fopen(oom_adj_path, "w"))) { | |
84 | perror("oom_adj: fopen failed"); | |
85 | return 0; | |
86 | } | |
87 | ||
88 | fprintf(fp, "%i", val); | |
89 | ||
90 | if (dm_fclose(fp)) | |
91 | perror("oom_adj: fclose failed"); | |
92 | ||
93 | return 1; | |
94 | } | |
d7448a72 PR |
95 | |
96 | /* | |
97 | * Protection against OOM killer if kernel supports it | |
98 | */ | |
870353f9 | 99 | static int _protect_against_oom_killer(void) |
d7448a72 | 100 | { |
d7448a72 PR |
101 | struct stat st; |
102 | ||
103 | if (stat(OOM_ADJ_FILE, &st) == -1) { | |
870353f9 | 104 | if (errno != ENOENT) |
d7448a72 | 105 | perror(OOM_ADJ_FILE ": stat failed"); |
870353f9 PR |
106 | |
107 | /* Try old oom_adj interface as a fallback */ | |
108 | if (stat(OOM_ADJ_FILE_OLD, &st) == -1) { | |
109 | if (errno == ENOENT) | |
110 | perror(OOM_ADJ_FILE_OLD " not found"); | |
111 | else | |
112 | perror(OOM_ADJ_FILE_OLD ": stat failed"); | |
113 | return 1; | |
114 | } | |
115 | ||
116 | return _set_oom_adj(OOM_ADJ_FILE_OLD, OOM_DISABLE) || | |
117 | _set_oom_adj(OOM_ADJ_FILE_OLD, OOM_ADJUST_MIN); | |
d7448a72 PR |
118 | } |
119 | ||
870353f9 PR |
120 | return _set_oom_adj(OOM_ADJ_FILE, OOM_SCORE_ADJ_MIN); |
121 | } | |
122 | ||
123 | union sockaddr_union { | |
124 | struct sockaddr sa; | |
125 | struct sockaddr_un un; | |
126 | }; | |
127 | ||
128 | static int _handle_preloaded_socket(int fd, const char *path) | |
129 | { | |
130 | struct stat st_fd; | |
131 | union sockaddr_union sockaddr; | |
132 | int type = 0; | |
133 | socklen_t len = sizeof(type); | |
134 | size_t path_len = strlen(path); | |
135 | ||
136 | if (fd < 0) | |
d7448a72 | 137 | return 0; |
d7448a72 | 138 | |
870353f9 PR |
139 | if (fstat(fd, &st_fd) < 0 || !S_ISSOCK(st_fd.st_mode)) |
140 | return 0; | |
141 | ||
142 | if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &type, &len) < 0 || | |
143 | len != sizeof(type) || type != SOCK_STREAM) | |
144 | return 0; | |
145 | ||
146 | memset(&sockaddr, 0, sizeof(sockaddr)); | |
147 | len = sizeof(sockaddr); | |
148 | if (getsockname(fd, &sockaddr.sa, &len) < 0 || | |
149 | len < sizeof(sa_family_t) || | |
150 | sockaddr.sa.sa_family != PF_UNIX) | |
151 | return 0; | |
152 | ||
153 | if (!(len >= offsetof(struct sockaddr_un, sun_path) + path_len + 1 && | |
154 | memcmp(path, sockaddr.un.sun_path, path_len) == 0)) | |
155 | return 0; | |
d7448a72 PR |
156 | |
157 | return 1; | |
158 | } | |
870353f9 PR |
159 | |
160 | static int _systemd_handover(struct daemon_state *ds) | |
161 | { | |
162 | const char *e; | |
163 | char *p; | |
164 | unsigned long env_pid, env_listen_fds; | |
165 | int r = 0; | |
166 | ||
167 | /* LISTEN_PID must be equal to our PID! */ | |
168 | if (!(e = getenv(SD_LISTEN_PID_ENV_VAR_NAME))) | |
169 | goto out; | |
170 | ||
171 | errno = 0; | |
172 | env_pid = strtoul(e, &p, 10); | |
173 | if (errno || !p || *p || env_pid <= 0 || | |
174 | getpid() != (pid_t) env_pid) | |
175 | ; | |
176 | ||
177 | /* LISTEN_FDS must be 1 and the fd must be a socket! */ | |
178 | if (!(e = getenv(SD_LISTEN_FDS_ENV_VAR_NAME))) | |
179 | goto out; | |
180 | ||
181 | errno = 0; | |
182 | env_listen_fds = strtoul(e, &p, 10); | |
183 | if (errno || !p || *p || env_listen_fds != 1) | |
184 | goto out; | |
185 | ||
186 | /* Check and handle the socket passed in */ | |
187 | if ((r = _handle_preloaded_socket(SD_FD_SOCKET_SERVER, ds->socket_path))) | |
188 | ds->socket_fd = SD_FD_SOCKET_SERVER; | |
189 | ||
190 | out: | |
191 | unsetenv(SD_LISTEN_PID_ENV_VAR_NAME); | |
192 | unsetenv(SD_LISTEN_FDS_ENV_VAR_NAME); | |
193 | return r; | |
194 | } | |
195 | ||
d7448a72 PR |
196 | #endif |
197 | ||
73ffd6e7 PR |
198 | static int _open_socket(daemon_state s) |
199 | { | |
200 | int fd = -1; | |
201 | struct sockaddr_un sockaddr; | |
202 | mode_t old_mask; | |
203 | ||
204 | (void) dm_prepare_selinux_context(s.socket_path, S_IFSOCK); | |
205 | old_mask = umask(0077); | |
206 | ||
207 | /* Open local socket */ | |
208 | fd = socket(PF_UNIX, SOCK_STREAM, 0); | |
209 | if (fd < 0) { | |
dc85d3fb | 210 | perror("Can't create local socket."); |
73ffd6e7 PR |
211 | goto error; |
212 | } | |
213 | ||
214 | /* Set Close-on-exec & non-blocking */ | |
215 | if (fcntl(fd, F_SETFD, 1)) | |
dc85d3fb | 216 | fprintf(stderr, "setting CLOEXEC on socket fd %d failed: %s\n", fd, strerror(errno)); |
73ffd6e7 PR |
217 | fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); |
218 | ||
55e30071 | 219 | fprintf(stderr, "[D] creating %s\n", s.socket_path); |
73ffd6e7 | 220 | memset(&sockaddr, 0, sizeof(sockaddr)); |
92658f56 | 221 | strcpy(sockaddr.sun_path, s.socket_path); |
73ffd6e7 PR |
222 | sockaddr.sun_family = AF_UNIX; |
223 | ||
224 | if (bind(fd, (struct sockaddr *) &sockaddr, sizeof(sockaddr))) { | |
dc85d3fb | 225 | perror("can't bind local socket."); |
73ffd6e7 PR |
226 | goto error; |
227 | } | |
228 | if (listen(fd, 1) != 0) { | |
dc85d3fb | 229 | perror("listen local"); |
73ffd6e7 PR |
230 | goto error; |
231 | } | |
232 | ||
233 | out: | |
234 | umask(old_mask); | |
235 | (void) dm_prepare_selinux_context(NULL, 0); | |
236 | return fd; | |
237 | ||
238 | error: | |
239 | if (fd >= 0) { | |
6e2761e9 ZK |
240 | if (close(fd)) |
241 | perror("close failed"); | |
242 | if (unlink(s.socket_path)) | |
243 | perror("unlink failed"); | |
73ffd6e7 PR |
244 | fd = -1; |
245 | } | |
246 | goto out; | |
247 | } | |
248 | ||
d7448a72 PR |
249 | static void remove_lockfile(const char *file) |
250 | { | |
251 | if (unlink(file)) | |
dc85d3fb | 252 | perror("unlink failed"); |
d7448a72 PR |
253 | } |
254 | ||
255 | static void _daemonise(void) | |
256 | { | |
257 | int child_status; | |
258 | int fd; | |
259 | pid_t pid; | |
260 | struct rlimit rlim; | |
261 | struct timeval tval; | |
262 | sigset_t my_sigset; | |
263 | ||
264 | sigemptyset(&my_sigset); | |
265 | if (sigprocmask(SIG_SETMASK, &my_sigset, NULL) < 0) { | |
266 | fprintf(stderr, "Unable to restore signals.\n"); | |
267 | exit(EXIT_FAILURE); | |
268 | } | |
269 | signal(SIGTERM, &_exit_handler); | |
270 | ||
271 | switch (pid = fork()) { | |
272 | case -1: | |
273 | perror("fork failed:"); | |
274 | exit(EXIT_FAILURE); | |
275 | ||
276 | case 0: /* Child */ | |
277 | break; | |
278 | ||
279 | default: | |
280 | /* Wait for response from child */ | |
dc85d3fb | 281 | while (!waitpid(pid, &child_status, WNOHANG) && !_shutdown_requested) { |
d7448a72 PR |
282 | tval.tv_sec = 0; |
283 | tval.tv_usec = 250000; /* .25 sec */ | |
284 | select(0, NULL, NULL, NULL, &tval); | |
285 | } | |
286 | ||
287 | if (_shutdown_requested) /* Child has signaled it is ok - we can exit now */ | |
288 | exit(0); | |
289 | ||
290 | /* Problem with child. Determine what it is by exit code */ | |
dc85d3fb | 291 | fprintf(stderr, "Child exited with code %d\n", WEXITSTATUS(child_status)); |
d7448a72 PR |
292 | exit(WEXITSTATUS(child_status)); |
293 | } | |
294 | ||
295 | if (chdir("/")) | |
296 | exit(1); | |
297 | ||
298 | if (getrlimit(RLIMIT_NOFILE, &rlim) < 0) | |
299 | fd = 256; /* just have to guess */ | |
300 | else | |
301 | fd = rlim.rlim_cur; | |
302 | ||
870353f9 PR |
303 | for (--fd; fd >= 0; fd--) { |
304 | #ifdef linux | |
305 | /* Do not close fds preloaded by systemd! */ | |
306 | if (_systemd_activation && fd == SD_FD_SOCKET_SERVER) | |
307 | continue; | |
308 | #endif | |
d7448a72 | 309 | close(fd); |
870353f9 | 310 | } |
d7448a72 PR |
311 | |
312 | if ((open("/dev/null", O_RDONLY) < 0) || | |
313 | (open("/dev/null", O_WRONLY) < 0) || | |
314 | (open("/dev/null", O_WRONLY) < 0)) | |
315 | exit(1); | |
316 | ||
317 | setsid(); | |
318 | } | |
319 | ||
e68a6fbf | 320 | response daemon_reply_simple(const char *id, ...) |
aaca7f11 PR |
321 | { |
322 | va_list ap; | |
6d404585 ZK |
323 | response res = { .cft = NULL }; |
324 | ||
aaca7f11 | 325 | va_start(ap, id); |
aaca7f11 | 326 | |
6d404585 | 327 | if (!(res.buffer = format_buffer("response", id, ap))) |
aaca7f11 PR |
328 | res.error = ENOMEM; |
329 | ||
6d404585 ZK |
330 | va_end(ap); |
331 | ||
aaca7f11 PR |
332 | return res; |
333 | } | |
334 | ||
92658f56 PR |
335 | struct thread_baton { |
336 | daemon_state s; | |
337 | client_handle client; | |
338 | }; | |
339 | ||
6e4e3082 | 340 | static int buffer_rewrite(char **buf, const char *format, const char *string) { |
372e9b3d | 341 | char *old = *buf; |
c682e837 ZK |
342 | int r = dm_asprintf(buf, format, *buf, string); |
343 | ||
372e9b3d | 344 | dm_free(old); |
c682e837 ZK |
345 | |
346 | return (r < 0) ? 0 : 1; | |
372e9b3d PR |
347 | } |
348 | ||
6e4e3082 | 349 | static int buffer_line(const char *line, void *baton) { |
372e9b3d | 350 | response *r = baton; |
c682e837 ZK |
351 | |
352 | if (r->buffer) { | |
353 | if (!buffer_rewrite(&r->buffer, "%s\n%s", line)) | |
354 | return 0; | |
355 | } else if (dm_asprintf(&r->buffer, "%s\n", line) < 0) | |
356 | return 0; | |
357 | ||
358 | return 1; | |
372e9b3d PR |
359 | } |
360 | ||
3f694b12 PR |
361 | static response builtin_handler(daemon_state s, client_handle h, request r) |
362 | { | |
363 | const char *rq = daemon_request_str(r, "request", "NONE"); | |
364 | ||
365 | if (!strcmp(rq, "hello")) { | |
366 | return daemon_reply_simple("OK", "protocol = %s", s.protocol ?: "default", | |
367 | "version = %d", s.protocol_version, NULL); | |
368 | } | |
369 | ||
370 | response res = { .buffer = NULL, .error = EPROTO }; | |
371 | return res; | |
372 | } | |
373 | ||
6e4e3082 | 374 | static void *client_thread(void *baton) |
92658f56 PR |
375 | { |
376 | struct thread_baton *b = baton; | |
377 | request req; | |
6d404585 ZK |
378 | response res; |
379 | ||
92658f56 PR |
380 | while (1) { |
381 | if (!read_buffer(b->client.socket_fd, &req.buffer)) | |
382 | goto fail; | |
383 | ||
c033ea01 | 384 | req.cft = dm_config_from_string(req.buffer); |
372e9b3d PR |
385 | if (!req.cft) |
386 | fprintf(stderr, "error parsing request:\n %s\n", req.buffer); | |
3f694b12 PR |
387 | |
388 | res = builtin_handler(b->s, b->client, req); | |
389 | ||
390 | if (res.error == EPROTO) /* Not a builtin, delegate to the custom handler. */ | |
391 | res = b->s.handler(b->s, b->client, req); | |
92658f56 PR |
392 | |
393 | if (!res.buffer) { | |
c033ea01 | 394 | dm_config_write_node(res.cft->root, buffer_line, &res); |
c682e837 ZK |
395 | if (!buffer_rewrite(&res.buffer, "%s\n\n", NULL)) |
396 | goto fail; | |
615534d3 | 397 | dm_config_destroy(res.cft); |
92658f56 PR |
398 | } |
399 | ||
d5406851 PR |
400 | if (req.cft) |
401 | dm_config_destroy(req.cft); | |
402 | dm_free(req.buffer); | |
403 | ||
92658f56 PR |
404 | write_buffer(b->client.socket_fd, res.buffer, strlen(res.buffer)); |
405 | ||
406 | free(res.buffer); | |
92658f56 PR |
407 | } |
408 | fail: | |
409 | /* TODO what should we really do here? */ | |
d5406851 | 410 | close(b->client.socket_fd); |
a7204204 | 411 | free(baton); |
92658f56 PR |
412 | return NULL; |
413 | } | |
414 | ||
6e4e3082 | 415 | static int handle_connect(daemon_state s) |
92658f56 | 416 | { |
6d404585 | 417 | struct thread_baton *baton; |
92658f56 | 418 | struct sockaddr_un sockaddr; |
6d404585 | 419 | client_handle client = { .thread_id = 0 }; |
92658f56 | 420 | socklen_t sl = sizeof(sockaddr); |
6d404585 ZK |
421 | |
422 | client.socket_fd = accept(s.socket_fd, (struct sockaddr *) &sockaddr, &sl); | |
423 | if (client.socket_fd < 0) | |
92658f56 PR |
424 | return 0; |
425 | ||
6d404585 | 426 | if (!(baton = malloc(sizeof(struct thread_baton)))) |
92658f56 PR |
427 | return 0; |
428 | ||
92658f56 PR |
429 | baton->s = s; |
430 | baton->client = client; | |
431 | ||
432 | if (pthread_create(&baton->client.thread_id, NULL, client_thread, baton)) | |
433 | return 0; | |
6d404585 | 434 | |
d5406851 PR |
435 | pthread_detach(baton->client.thread_id); |
436 | ||
92658f56 PR |
437 | return 1; |
438 | } | |
439 | ||
dc85d3fb | 440 | void daemon_start(daemon_state s) |
d7448a72 | 441 | { |
73ffd6e7 | 442 | int failed = 0; |
d7448a72 PR |
443 | /* |
444 | * Switch to C locale to avoid reading large locale-archive file used by | |
445 | * some glibc (on some distributions it takes over 100MB). Some daemons | |
446 | * need to use mlockall(). | |
447 | */ | |
448 | if (setenv("LANG", "C", 1)) | |
449 | perror("Cannot set LANG to C"); | |
450 | ||
870353f9 PR |
451 | #ifdef linux |
452 | _systemd_activation = _systemd_handover(&s); | |
453 | #endif | |
454 | ||
d7448a72 PR |
455 | if (!s.foreground) |
456 | _daemonise(); | |
457 | ||
458 | /* TODO logging interface should be somewhat more elaborate */ | |
459 | openlog(s.name, LOG_PID, LOG_DAEMON); | |
460 | ||
461 | (void) dm_prepare_selinux_context(s.pidfile, S_IFREG); | |
462 | ||
463 | /* | |
73ffd6e7 PR |
464 | * NB. Take care to not keep stale locks around. Best not exit(...) |
465 | * after this point. | |
d7448a72 PR |
466 | */ |
467 | if (dm_create_lockfile(s.pidfile) == 0) | |
468 | exit(1); | |
469 | ||
470 | (void) dm_prepare_selinux_context(NULL, 0); | |
471 | ||
472 | /* Set normal exit signals to request shutdown instead of dying. */ | |
473 | signal(SIGINT, &_exit_handler); | |
474 | signal(SIGHUP, &_exit_handler); | |
475 | signal(SIGQUIT, &_exit_handler); | |
92658f56 | 476 | signal(SIGTERM, &_exit_handler); |
c033ea01 | 477 | signal(SIGALRM, &_exit_handler); |
92658f56 | 478 | signal(SIGPIPE, SIG_IGN); |
d7448a72 PR |
479 | |
480 | #ifdef linux | |
870353f9 PR |
481 | /* Systemd has adjusted oom killer for us already */ |
482 | if (s.avoid_oom && !_systemd_activation && !_protect_against_oom_killer()) | |
483 | syslog(LOG_ERR, "Failed to protect against OOM killer"); | |
d7448a72 PR |
484 | #endif |
485 | ||
870353f9 | 486 | if (!_systemd_activation && s.socket_path) { |
73ffd6e7 PR |
487 | s.socket_fd = _open_socket(s); |
488 | if (s.socket_fd < 0) | |
489 | failed = 1; | |
490 | } | |
491 | ||
d7448a72 PR |
492 | /* Signal parent, letting them know we are ready to go. */ |
493 | if (!s.foreground) | |
494 | kill(getppid(), SIGTERM); | |
495 | ||
372e9b3d PR |
496 | if (s.daemon_init) |
497 | s.daemon_init(&s); | |
498 | ||
73ffd6e7 | 499 | while (!_shutdown_requested && !failed) { |
92658f56 PR |
500 | fd_set in; |
501 | FD_ZERO(&in); | |
502 | FD_SET(s.socket_fd, &in); | |
503 | if (select(FD_SETSIZE, &in, NULL, NULL, NULL) < 0 && errno != EINTR) | |
504 | perror("select error"); | |
505 | if (FD_ISSET(s.socket_fd, &in)) | |
506 | if (!handle_connect(s)) | |
507 | syslog(LOG_ERR, "Failed to handle a client connection."); | |
d7448a72 PR |
508 | } |
509 | ||
92658f56 | 510 | if (s.socket_fd >= 0) |
6e2761e9 ZK |
511 | if (unlink(s.socket_path)) |
512 | perror("unlink error"); | |
92658f56 | 513 | |
372e9b3d PR |
514 | if (s.daemon_fini) |
515 | s.daemon_fini(&s); | |
516 | ||
d7448a72 PR |
517 | syslog(LOG_NOTICE, "%s shutting down", s.name); |
518 | closelog(); | |
519 | remove_lockfile(s.pidfile); | |
73ffd6e7 PR |
520 | if (failed) |
521 | exit(1); | |
d7448a72 | 522 | } |