]>
Commit | Line | Data |
---|---|---|
d7448a72 PR |
1 | /* |
2 | * Copyright (C) 2011 Red Hat, Inc. All rights reserved. | |
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 | ||
13 | #include <dlfcn.h> | |
14 | #include <errno.h> | |
15 | #include <pthread.h> | |
16 | #include <sys/file.h> | |
17 | #include <sys/stat.h> | |
18 | #include <sys/wait.h> | |
19 | #include <sys/time.h> | |
20 | #include <sys/resource.h> | |
dc85d3fb PR |
21 | #include <netinet/in.h> |
22 | #include <sys/un.h> | |
d7448a72 PR |
23 | #include <unistd.h> |
24 | #include <signal.h> | |
25 | ||
26 | #include <syslog.h> | |
dc85d3fb | 27 | #include "daemon-server.h" |
aaca7f11 | 28 | #include "daemon-shared.h" |
dc85d3fb | 29 | #include "libdevmapper.h" |
d7448a72 | 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; | |
46 | ||
47 | static void _exit_handler(int sig __attribute__((unused))) | |
48 | { | |
49 | _shutdown_requested = 1; | |
50 | } | |
51 | ||
52 | #ifdef linux | |
53 | # define OOM_ADJ_FILE "/proc/self/oom_adj" | |
dc85d3fb | 54 | # include <stdio.h> |
d7448a72 PR |
55 | |
56 | /* From linux/oom.h */ | |
57 | # define OOM_DISABLE (-17) | |
58 | # define OOM_ADJUST_MIN (-16) | |
59 | ||
60 | /* | |
61 | * Protection against OOM killer if kernel supports it | |
62 | */ | |
63 | static int _set_oom_adj(int val) | |
64 | { | |
65 | FILE *fp; | |
66 | ||
67 | struct stat st; | |
68 | ||
69 | if (stat(OOM_ADJ_FILE, &st) == -1) { | |
70 | if (errno == ENOENT) | |
71 | perror(OOM_ADJ_FILE " not found"); | |
72 | else | |
73 | perror(OOM_ADJ_FILE ": stat failed"); | |
74 | return 1; | |
75 | } | |
76 | ||
77 | if (!(fp = fopen(OOM_ADJ_FILE, "w"))) { | |
78 | perror(OOM_ADJ_FILE ": fopen failed"); | |
79 | return 0; | |
80 | } | |
81 | ||
82 | fprintf(fp, "%i", val); | |
dc85d3fb | 83 | if (fclose(fp)) |
d7448a72 PR |
84 | perror(OOM_ADJ_FILE ": fclose failed"); |
85 | ||
86 | return 1; | |
87 | } | |
88 | #endif | |
89 | ||
73ffd6e7 PR |
90 | static int _open_socket(daemon_state s) |
91 | { | |
92 | int fd = -1; | |
93 | struct sockaddr_un sockaddr; | |
94 | mode_t old_mask; | |
95 | ||
96 | (void) dm_prepare_selinux_context(s.socket_path, S_IFSOCK); | |
97 | old_mask = umask(0077); | |
98 | ||
99 | /* Open local socket */ | |
100 | fd = socket(PF_UNIX, SOCK_STREAM, 0); | |
101 | if (fd < 0) { | |
dc85d3fb | 102 | perror("Can't create local socket."); |
73ffd6e7 PR |
103 | goto error; |
104 | } | |
105 | ||
106 | /* Set Close-on-exec & non-blocking */ | |
107 | if (fcntl(fd, F_SETFD, 1)) | |
dc85d3fb | 108 | fprintf(stderr, "setting CLOEXEC on socket fd %d failed: %s\n", fd, strerror(errno)); |
73ffd6e7 PR |
109 | fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); |
110 | ||
55e30071 | 111 | fprintf(stderr, "[D] creating %s\n", s.socket_path); |
73ffd6e7 | 112 | memset(&sockaddr, 0, sizeof(sockaddr)); |
92658f56 | 113 | strcpy(sockaddr.sun_path, s.socket_path); |
73ffd6e7 PR |
114 | sockaddr.sun_family = AF_UNIX; |
115 | ||
116 | if (bind(fd, (struct sockaddr *) &sockaddr, sizeof(sockaddr))) { | |
dc85d3fb | 117 | perror("can't bind local socket."); |
73ffd6e7 PR |
118 | goto error; |
119 | } | |
120 | if (listen(fd, 1) != 0) { | |
dc85d3fb | 121 | perror("listen local"); |
73ffd6e7 PR |
122 | goto error; |
123 | } | |
124 | ||
125 | out: | |
126 | umask(old_mask); | |
127 | (void) dm_prepare_selinux_context(NULL, 0); | |
128 | return fd; | |
129 | ||
130 | error: | |
131 | if (fd >= 0) { | |
132 | close(fd); | |
92658f56 | 133 | unlink(s.socket_path); |
73ffd6e7 PR |
134 | fd = -1; |
135 | } | |
136 | goto out; | |
137 | } | |
138 | ||
d7448a72 PR |
139 | static void remove_lockfile(const char *file) |
140 | { | |
141 | if (unlink(file)) | |
dc85d3fb | 142 | perror("unlink failed"); |
d7448a72 PR |
143 | } |
144 | ||
145 | static void _daemonise(void) | |
146 | { | |
147 | int child_status; | |
148 | int fd; | |
149 | pid_t pid; | |
150 | struct rlimit rlim; | |
151 | struct timeval tval; | |
152 | sigset_t my_sigset; | |
153 | ||
154 | sigemptyset(&my_sigset); | |
155 | if (sigprocmask(SIG_SETMASK, &my_sigset, NULL) < 0) { | |
156 | fprintf(stderr, "Unable to restore signals.\n"); | |
157 | exit(EXIT_FAILURE); | |
158 | } | |
159 | signal(SIGTERM, &_exit_handler); | |
160 | ||
161 | switch (pid = fork()) { | |
162 | case -1: | |
163 | perror("fork failed:"); | |
164 | exit(EXIT_FAILURE); | |
165 | ||
166 | case 0: /* Child */ | |
167 | break; | |
168 | ||
169 | default: | |
170 | /* Wait for response from child */ | |
dc85d3fb | 171 | while (!waitpid(pid, &child_status, WNOHANG) && !_shutdown_requested) { |
d7448a72 PR |
172 | tval.tv_sec = 0; |
173 | tval.tv_usec = 250000; /* .25 sec */ | |
174 | select(0, NULL, NULL, NULL, &tval); | |
175 | } | |
176 | ||
177 | if (_shutdown_requested) /* Child has signaled it is ok - we can exit now */ | |
178 | exit(0); | |
179 | ||
180 | /* Problem with child. Determine what it is by exit code */ | |
dc85d3fb | 181 | fprintf(stderr, "Child exited with code %d\n", WEXITSTATUS(child_status)); |
d7448a72 PR |
182 | exit(WEXITSTATUS(child_status)); |
183 | } | |
184 | ||
185 | if (chdir("/")) | |
186 | exit(1); | |
187 | ||
188 | if (getrlimit(RLIMIT_NOFILE, &rlim) < 0) | |
189 | fd = 256; /* just have to guess */ | |
190 | else | |
191 | fd = rlim.rlim_cur; | |
192 | ||
193 | for (--fd; fd >= 0; fd--) | |
194 | close(fd); | |
195 | ||
196 | if ((open("/dev/null", O_RDONLY) < 0) || | |
197 | (open("/dev/null", O_WRONLY) < 0) || | |
198 | (open("/dev/null", O_WRONLY) < 0)) | |
199 | exit(1); | |
200 | ||
201 | setsid(); | |
202 | } | |
203 | ||
aaca7f11 PR |
204 | response daemon_reply_simple(char *id, ...) |
205 | { | |
206 | va_list ap; | |
207 | va_start(ap, id); | |
14e01287 | 208 | response res = { .buffer = format_buffer("response", id, ap), .cft = NULL }; |
aaca7f11 PR |
209 | |
210 | if (!res.buffer) | |
211 | res.error = ENOMEM; | |
212 | ||
213 | return res; | |
214 | } | |
215 | ||
92658f56 PR |
216 | struct thread_baton { |
217 | daemon_state s; | |
218 | client_handle client; | |
219 | }; | |
220 | ||
372e9b3d PR |
221 | int buffer_rewrite(char **buf, const char *format, const char *string) { |
222 | char *old = *buf; | |
223 | dm_asprintf(buf, format, *buf, string); | |
224 | dm_free(old); | |
225 | return 0; | |
226 | } | |
227 | ||
228 | int buffer_line(const char *line, void *baton) { | |
229 | response *r = baton; | |
230 | if (r->buffer) | |
231 | buffer_rewrite(&r->buffer, "%s\n%s", line); | |
232 | else | |
233 | dm_asprintf(&r->buffer, "%s\n", line); | |
234 | return 0; | |
235 | } | |
236 | ||
92658f56 PR |
237 | void *client_thread(void *baton) |
238 | { | |
239 | struct thread_baton *b = baton; | |
240 | request req; | |
241 | while (1) { | |
242 | if (!read_buffer(b->client.socket_fd, &req.buffer)) | |
243 | goto fail; | |
244 | ||
55e30071 | 245 | req.cft = create_config_tree_from_string(req.buffer); |
372e9b3d PR |
246 | if (!req.cft) |
247 | fprintf(stderr, "error parsing request:\n %s\n", req.buffer); | |
92658f56 | 248 | response res = b->s.handler(b->s, b->client, req); |
372e9b3d PR |
249 | if (req.cft) |
250 | destroy_config_tree(req.cft); | |
55e30071 | 251 | dm_free(req.buffer); |
92658f56 PR |
252 | |
253 | if (!res.buffer) { | |
372e9b3d PR |
254 | write_config_node(res.cft->root, buffer_line, &res); |
255 | buffer_rewrite(&res.buffer, "%s\n\n", NULL); | |
a7204204 | 256 | destroy_config_tree(res.cft); |
92658f56 PR |
257 | } |
258 | ||
259 | write_buffer(b->client.socket_fd, res.buffer, strlen(res.buffer)); | |
260 | ||
261 | free(res.buffer); | |
92658f56 PR |
262 | } |
263 | fail: | |
264 | /* TODO what should we really do here? */ | |
a7204204 | 265 | free(baton); |
92658f56 PR |
266 | return NULL; |
267 | } | |
268 | ||
269 | int handle_connect(daemon_state s) | |
270 | { | |
271 | struct sockaddr_un sockaddr; | |
272 | client_handle client; | |
273 | socklen_t sl = sizeof(sockaddr); | |
274 | int client_fd = accept(s.socket_fd, (struct sockaddr *) &sockaddr, &sl); | |
275 | if (client_fd < 0) | |
276 | return 0; | |
277 | ||
278 | struct thread_baton *baton = malloc(sizeof(struct thread_baton)); | |
279 | if (!baton) | |
280 | return 0; | |
281 | ||
282 | client.socket_fd = client_fd; | |
283 | client.read_buf = 0; | |
284 | client.private = 0; | |
285 | baton->s = s; | |
286 | baton->client = client; | |
287 | ||
288 | if (pthread_create(&baton->client.thread_id, NULL, client_thread, baton)) | |
289 | return 0; | |
290 | return 1; | |
291 | } | |
292 | ||
dc85d3fb | 293 | void daemon_start(daemon_state s) |
d7448a72 | 294 | { |
73ffd6e7 | 295 | int failed = 0; |
d7448a72 PR |
296 | /* |
297 | * Switch to C locale to avoid reading large locale-archive file used by | |
298 | * some glibc (on some distributions it takes over 100MB). Some daemons | |
299 | * need to use mlockall(). | |
300 | */ | |
301 | if (setenv("LANG", "C", 1)) | |
302 | perror("Cannot set LANG to C"); | |
303 | ||
304 | if (!s.foreground) | |
305 | _daemonise(); | |
306 | ||
307 | /* TODO logging interface should be somewhat more elaborate */ | |
308 | openlog(s.name, LOG_PID, LOG_DAEMON); | |
309 | ||
310 | (void) dm_prepare_selinux_context(s.pidfile, S_IFREG); | |
311 | ||
312 | /* | |
73ffd6e7 PR |
313 | * NB. Take care to not keep stale locks around. Best not exit(...) |
314 | * after this point. | |
d7448a72 PR |
315 | */ |
316 | if (dm_create_lockfile(s.pidfile) == 0) | |
317 | exit(1); | |
318 | ||
319 | (void) dm_prepare_selinux_context(NULL, 0); | |
320 | ||
321 | /* Set normal exit signals to request shutdown instead of dying. */ | |
322 | signal(SIGINT, &_exit_handler); | |
323 | signal(SIGHUP, &_exit_handler); | |
324 | signal(SIGQUIT, &_exit_handler); | |
92658f56 PR |
325 | signal(SIGTERM, &_exit_handler); |
326 | signal(SIGPIPE, SIG_IGN); | |
d7448a72 PR |
327 | |
328 | #ifdef linux | |
329 | if (s.avoid_oom && !_set_oom_adj(OOM_DISABLE) && !_set_oom_adj(OOM_ADJUST_MIN)) | |
330 | syslog(LOG_ERR, "Failed to set oom_adj to protect against OOM killer"); | |
331 | #endif | |
332 | ||
73ffd6e7 PR |
333 | if (s.socket_path) { |
334 | s.socket_fd = _open_socket(s); | |
335 | if (s.socket_fd < 0) | |
336 | failed = 1; | |
337 | } | |
338 | ||
d7448a72 PR |
339 | /* Signal parent, letting them know we are ready to go. */ |
340 | if (!s.foreground) | |
341 | kill(getppid(), SIGTERM); | |
342 | ||
372e9b3d PR |
343 | if (s.daemon_init) |
344 | s.daemon_init(&s); | |
345 | ||
73ffd6e7 | 346 | while (!_shutdown_requested && !failed) { |
92658f56 PR |
347 | int status; |
348 | fd_set in; | |
349 | FD_ZERO(&in); | |
350 | FD_SET(s.socket_fd, &in); | |
351 | if (select(FD_SETSIZE, &in, NULL, NULL, NULL) < 0 && errno != EINTR) | |
352 | perror("select error"); | |
353 | if (FD_ISSET(s.socket_fd, &in)) | |
354 | if (!handle_connect(s)) | |
355 | syslog(LOG_ERR, "Failed to handle a client connection."); | |
d7448a72 PR |
356 | } |
357 | ||
92658f56 PR |
358 | if (s.socket_fd >= 0) |
359 | unlink(s.socket_path); | |
360 | ||
372e9b3d PR |
361 | if (s.daemon_fini) |
362 | s.daemon_fini(&s); | |
363 | ||
d7448a72 PR |
364 | syslog(LOG_NOTICE, "%s shutting down", s.name); |
365 | closelog(); | |
366 | remove_lockfile(s.pidfile); | |
73ffd6e7 PR |
367 | if (failed) |
368 | exit(1); | |
d7448a72 | 369 | } |