send_to_daemon() cleanup.
[pwmd.git] / src / pwmd.c
blob35082c607d3bfd7f34a1a4c72617c090b29d6e80
1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
2 /*
3 Copyright (C) 2006-2007 Ben Kibbey <bjk@luxsci.net>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <err.h>
23 #include <errno.h>
24 #include <ctype.h>
25 #include <string.h>
26 #include <sys/socket.h>
27 #include <sys/un.h>
28 #include <signal.h>
29 #include <stdarg.h>
30 #include <string.h>
31 #include <sys/wait.h>
32 #include <fcntl.h>
33 #include <pwd.h>
34 #include <glib.h>
35 #include <glib/gprintf.h>
36 #include <gcrypt.h>
37 #include <sys/mman.h>
39 #ifdef HAVE_CONFIG_H
40 #include <config.h>
41 #endif
43 #ifdef HAVE_SETRLIMIT
44 #include <sys/time.h>
45 #include <sys/resource.h>
46 #endif
48 #include "xml.h"
49 #include "common.h"
50 #include "commands.h"
51 #include "pwmd_error.h"
52 #include "pwmd.h"
54 void send_to_client(struct client_s *client, const gchar *fmt, ...)
56 va_list ap;
57 gint n = 0;
58 gchar **p;
60 va_start(ap, fmt);
62 if (client->outbuf)
63 for (p = client->outbuf, n = 0; *p; p++, n++);
65 if ((client->outbuf = g_realloc(client->outbuf, (n + 2) * sizeof(gchar *))) == NULL) {
66 warn("g_realloc()");
67 return;
70 client->outbuf[n++] = g_strdup_vprintf(fmt, ap);
71 client->outbuf[n] = NULL;
72 va_end(ap);
75 void send_error(struct client_s *client, int pwmd_errno)
77 send_to_client(client, "ERR %03i %s\n", pwmd_errno, pwmd_strerror(pwmd_errno));
80 void log_write(const gchar *fmt, ...)
82 gchar *args, *line;
83 va_list ap;
84 struct tm *tm;
85 time_t now;
86 gchar tbuf[21];
87 gint fd;
89 if (!logfile || !fmt)
90 return;
92 if ((fd = open(logfile, O_WRONLY|O_CREAT|O_APPEND, 0600)) == -1) {
93 warn("logfile");
94 return;
97 va_start(ap, fmt);
98 g_vasprintf(&args, fmt, ap);
99 va_end(ap);
100 time(&now);
101 tm = localtime(&now);
102 strftime(tbuf, sizeof(tbuf), "%b %d %Y %H:%M:%S ", tm);
103 tbuf[sizeof(tbuf) - 1] = 0;
104 line = g_strdup_printf("%s %i %s\n", tbuf, getpid(), args);
105 write(fd, line, strlen(line));
106 g_free(args);
107 g_free(line);
108 close(fd);
111 static void catchsig(gint sig)
113 gint status;
115 log_write("caught signal %i (%s)", sig, strsignal(sig));
117 switch (sig) {
118 case SIGCHLD:
119 waitpid(-1, &status, 0);
120 break;
121 case SIGHUP:
122 log_write("clearing file cache");
123 memset(shm_data, 0, cache_size);
124 break;
125 default:
126 shutdown(sfd, SHUT_RDWR);
127 close(sfd);
128 quit = 1;
129 break;
133 static void usage(gchar *pn)
135 g_printf(
136 #ifdef HAVE_MLOCKALL
137 "Usage: %s [-hv] [-l <logfile> [-M] [-C <cache_size>] [-d <directory>] [-s <socketpath>]\n"
138 #else
139 "Usage: %s [-hv] [-l <logfile> [-C <cache_size>] [-d <directory>] [-s <socketpath>]\n"
140 #endif
141 " -d directory where to store and retrieve data files\n"
142 " -s socket path\n"
143 #ifdef HAVE_MLOCKALL
144 " -M disable mlockall()\n"
145 #endif
146 " -C cache size (%li)\n"
147 " -l enable logging to the specified file\n"
148 " -v version\n"
149 " -h this help text\n",
150 pn, page_size);
151 exit(EXIT_SUCCESS);
154 gchar **split_input_line(gchar *str, gchar *delim, gint n)
156 if (!str || !*str)
157 return NULL;
159 return g_strsplit(str, delim, n);
162 static void setup_gcrypt()
164 gcry_check_version(NULL);
166 if (gcry_cipher_algo_info(GCRY_CIPHER_AES, GCRYCTL_TEST_ALGO, NULL,
167 NULL) != 0)
168 errx(EXIT_FAILURE, "AES cipher not supported");
170 gcry_cipher_algo_info(GCRY_CIPHER_AES, GCRYCTL_GET_KEYLEN, NULL, &gcrykeysize);
171 gcry_cipher_algo_info(GCRY_CIPHER_AES, GCRYCTL_GET_BLKLEN, NULL, &gcryblocksize);
174 static gint input_parser(gchar *str)
176 gchar *p, *t;
177 gchar **req = NULL;
179 if (str)
180 str = g_strchug(str);
182 if (!*str)
183 return P_OK;
185 while ((p = strsep(&str, "\n")) != NULL) {
186 if (g_ascii_strcasecmp(p, "QUIT") == 0)
187 return P_QUIT;
188 else if (g_ascii_strcasecmp(p, "HELP") == 0)
189 help_command(cl, NULL);
190 else if (g_ascii_strncasecmp(p, "HELP ", 5) == 0) {
191 t = p + 5;
192 t = g_strchug(t);
193 help_command(cl, t);
195 else if (g_ascii_strcasecmp(p, "LIST") == 0 ||
196 g_ascii_strncasecmp(p, "LIST ", 5) == 0) {
197 if (cl->state != STATE_OPEN)
198 send_error(cl, EPWMD_NO_FILE);
199 else
200 list_command(cl, p);
202 else if (g_ascii_strncasecmp(p, "STORE ", 6) == 0) {
203 t = p + 6;
204 t = g_strchug(t);
206 if (cl->state != STATE_OPEN)
207 send_error(cl, EPWMD_NO_FILE);
208 else {
209 if ((req = split_input_line(t, "\t", 0)) != NULL) {
210 if (store_command(cl, req) == TRUE)
211 send_to_client(cl, "OK \n");
213 else
214 send_error(cl, EPWMD_COMMAND_SYNTAX);
217 else if (g_ascii_strncasecmp(p, "DELETE ", 7) == 0) {
218 t = p + 7;
220 if (cl->state != STATE_OPEN)
221 send_error(cl, EPWMD_NO_FILE);
222 else {
223 if ((req = split_input_line(t, "\t", 0)) != NULL) {
224 if (delete_command(cl, req) == TRUE)
225 send_to_client(cl, "OK \n");
227 else
228 send_error(cl, EPWMD_COMMAND_SYNTAX);
231 else if (g_ascii_strncasecmp(p, "GET ", 4) == 0) {
232 t = p + 4;
233 t = g_strchug(t);
235 if (cl->state != STATE_OPEN)
236 send_error(cl, EPWMD_NO_FILE);
237 else {
238 if ((req = split_input_line(t, "\t", 0)) != NULL)
239 get_command(cl, &cl->reader, req, 0);
240 else
241 send_error(cl, EPWMD_COMMAND_SYNTAX);
244 else if (g_ascii_strncasecmp(p, "ATTR ", 5) == 0) {
245 t = p + 5;
246 t = g_strchug(t);
248 if (cl->state != STATE_OPEN)
249 send_error(cl, EPWMD_NO_FILE);
250 else {
251 if ((req = split_input_line(t, " ", 4)) != NULL) {
252 if (attr_command(cl, req) == TRUE)
253 send_to_client(cl, "OK \n");
255 else
256 send_error(cl, EPWMD_COMMAND_SYNTAX);
259 else if (g_ascii_strncasecmp(p, "OPEN ", 5) == 0) {
260 t = p + 5;
261 t = g_strchug(t);
263 if (cl->state == STATE_OPEN)
264 send_error(cl, EPWMD_FILE_OPENED);
265 else {
266 if ((req = split_input_line(t, " ", 2)) != NULL) {
267 if (open_command(cl, req) == TRUE) {
268 send_to_client(cl, "OK \n");
269 cl->state = STATE_OPEN;
272 else
273 send_error(cl, EPWMD_COMMAND_SYNTAX);
276 else if (g_ascii_strncasecmp(p, "SAVE", 4) == 0) {
277 t = p + 4;
278 t = g_strchug(t);
280 if (cl->state != STATE_OPEN)
281 send_error(cl, EPWMD_NO_FILE);
282 else {
283 req = split_input_line(t, " ", 1);
285 if (save_command(cl, cl->filename, (req) ? req[0] : NULL) == TRUE)
286 send_to_client(cl, "OK \n");
289 else if (g_ascii_strncasecmp(p, "CACHE ", 6) == 0) {
290 t = p + 6;
291 t = g_strchug(t);
292 req = split_input_line(t, " ", 2);
294 if (cache_command(cl, req) == TRUE)
295 send_to_client(cl, "OK \n");
297 else if (g_ascii_strncasecmp(p, "DUMP", 4) == 0) {
298 if (cl->state != STATE_OPEN)
299 send_error(cl, EPWMD_NO_FILE);
300 else {
301 if (dump_command(cl) == TRUE)
302 send_to_client(cl, "OK \n");
305 else
306 send_error(cl, EPWMD_COMMAND_SYNTAX);
309 if (req) {
310 g_strfreev(req);
311 req = NULL;
314 return P_OK;
317 static gboolean source_prepare(GSource *src, gint *to)
319 if (cl->gfd.revents & (G_IO_HUP|G_IO_NVAL|G_IO_ERR))
320 return TRUE;
322 return FALSE;
325 static gboolean source_check(GSource *src)
327 if (cl->gfd.revents & (G_IO_IN|G_IO_PRI))
328 return TRUE;
330 if (cl->outbuf && (cl->gfd.revents & (G_IO_OUT)))
331 return TRUE;
333 return FALSE;
336 static gboolean source_dispatch(GSource *src, GSourceFunc cb, gpointer data)
338 return (*cb)(data);
341 static gboolean source_cb(gpointer data)
343 GIOStatus ret;
344 gsize len;
345 gchar *line = NULL;
346 GError *gerror = NULL;
347 gchar **p;
349 if (cl->gfd.revents & (G_IO_HUP|G_IO_NVAL|G_IO_ERR))
350 goto quit;
352 if (cl->outbuf && (cl->gfd.revents & (G_IO_OUT))) {
353 for (p = cl->outbuf; *p; p++) {
354 ret = g_io_channel_write_chars(cl->ioc, *p, -1, &len, &gerror);
356 if (ret == G_IO_STATUS_NORMAL)
357 g_io_channel_flush(cl->ioc, &gerror);
358 else
359 log_write("%s(%i): %s", __FUNCTION__, __LINE__, gerror->message);
361 g_clear_error(&gerror);
363 if (quit)
364 goto quit;
367 g_strfreev(cl->outbuf);
368 cl->outbuf = NULL;
371 if (!cl->gfd.revents & (G_IO_IN))
372 return TRUE;
374 ret = g_io_channel_read_line(cl->ioc, &line, &len, NULL, &gerror);
376 if (ret != G_IO_STATUS_NORMAL) {
377 if (ret == G_IO_STATUS_EOF)
378 goto quit;
380 log_write("%s(%i): %s", __FUNCTION__, __LINE__, gerror->message);
381 g_clear_error(&gerror);
382 return TRUE;
385 line[g_utf8_strlen(line, -1) - 1] = 0;
387 switch (input_parser(line)) {
388 case P_QUIT:
389 quit:
390 if (line) {
391 memset(line, 0, len);
392 g_free(line);
395 ret = g_io_channel_shutdown(cl->ioc, FALSE, &gerror);
397 if (ret != G_IO_STATUS_NORMAL)
398 log_write("%s(%i): %s", __FUNCTION__, __LINE__, gerror->message);
400 g_io_channel_unref(cl->ioc);
401 g_clear_error(&gerror);
403 if (cl->xml) {
404 memset(cl->xml, 0, cl->len);
405 xmlFree(cl->xml);
408 if (cl->gh)
409 gcry_cipher_close(cl->gh);
411 if (cl->doc)
412 xmlFreeDoc(cl->doc);
414 if (cl->reader)
415 xmlFreeTextReader(cl->reader);
417 if (cl->outbuf)
418 g_free(cl->outbuf);
420 if (cl->filename)
421 g_free(cl->filename);
423 g_main_loop_unref(gloop);
424 g_main_loop_quit(gloop);
425 return FALSE;
426 default:
427 break;
430 memset(line, 0, len);
431 g_free(line);
432 return TRUE;
436 * Called every time a connection is made.
438 static void doit(int fd)
440 static GSourceFuncs gsrcf = {
441 source_prepare, source_check, source_dispatch, NULL, 0, 0
443 GPollFD gfd = { fd, G_IO_IN|G_IO_OUT|G_IO_HUP|G_IO_ERR, 0 };
445 #ifdef HAVE_MLOCKALL
446 if (use_mlock && mlockall(MCL_FUTURE) == -1)
447 err(EXIT_FAILURE, "mlockall()");
448 #endif
450 gloop = g_main_loop_new(NULL, TRUE);
451 cl = g_malloc0(sizeof(struct client_s));
452 cl->src = g_source_new(&gsrcf, sizeof(GSource));
453 cl->gfd = gfd;
454 cl->state = STATE_CONNECTED;
455 cl->ioc = g_io_channel_unix_new(fd);
456 g_source_add_poll(cl->src, &cl->gfd);
457 g_source_set_callback(cl->src, source_cb, NULL, NULL);
458 g_source_attach(cl->src, NULL);
459 #ifndef DEBUG
460 close(0);
461 close(1);
462 close(2);
463 #endif
465 if ((gcryerrno = gcry_cipher_open(&cl->gh, GCRY_CIPHER_AES, GCRY_CIPHER_MODE_CBC, 0))) {
466 send_to_client(cl, "ERR %03i gcrypt: %s\n", EPWMD_ERROR, gcry_strerror(gcryerrno));
467 log_write("%s(%i): %s", __FUNCTION__, __LINE__, gcry_strerror(gcryerrno));
468 quit = 1;
470 else
471 // FIXME 100% CPU if removed (poll()).
472 send_to_client(cl, "OK \n");
474 g_main_loop_run(gloop);
475 close(fd);
476 g_free(cl);
477 _exit(EXIT_SUCCESS);
480 int main(int argc, char *argv[])
482 gint opt;
483 struct sockaddr_un addr;
484 struct passwd *pw = getpwuid(getuid());
485 gchar buf[PATH_MAX];
486 gchar *socketpath = NULL, *socketdir, *socketname = NULL;
487 gchar *socketarg = NULL;
488 gchar cwd[PATH_MAX];
489 gchar *datadir = NULL;
490 gint fd;
491 #ifndef DEBUG
492 #ifdef HAVE_SETRLIMIT
493 struct rlimit rl;
495 rl.rlim_cur = rl.rlim_max = 0;
497 if (setrlimit(RLIMIT_CORE, &rl) != 0)
498 err(EXIT_FAILURE, "setrlimit()");
499 #endif
500 #endif
502 if ((page_size = sysconf(_SC_PAGESIZE)) == -1)
503 err(EXIT_FAILURE, "sysconf()");
505 cache_size = page_size;
507 #ifdef HAVE_MLOCKALL
509 * Default to using mlockall().
511 use_mlock = 1;
513 while ((opt = getopt(argc, argv, "C:Mhd:s:vl:")) != EOF) {
514 #else
515 while ((opt = getopt(argc, argv, "C:hd:s:vl:")) != EOF) {
516 #endif
517 switch (opt) {
518 case 'l':
519 if (*optarg != '/') {
520 getcwd(buf, sizeof(buf));
521 strncat(buf, "/", sizeof(buf));
522 strncat(buf, optarg, sizeof(buf));
523 logfile = g_strdup(buf);
525 else
526 logfile = g_strdup(optarg);
528 break;
529 case 'C':
530 cache_size = strtol(optarg, NULL, 10);
532 if (cache_size < page_size || cache_size % page_size ||
533 (cache_size / page_size && cache_size % page_size))
534 errx(EXIT_FAILURE, "cache size must be in multiples of %li.", page_size);
535 break;
536 #ifdef HAVE_MLOCKALL
537 case 'M':
538 use_mlock = 0;
539 break;
540 #endif
541 case 's':
542 socketarg = optarg;
543 break;
544 case 'd':
545 datadir = g_strdup(optarg);
546 break;
547 case 'v':
548 printf("%s\n%s\n", PACKAGE_STRING, PACKAGE_BUGREPORT);
549 exit(EXIT_SUCCESS);
550 case 'h':
551 default:
552 usage(argv[0]);
556 setup_gcrypt();
558 if (getcwd(cwd, sizeof(cwd)) == NULL)
559 err(EXIT_FAILURE, "getcwd()");
561 if (datadir && !socketarg) {
562 socketdir = g_strdup_printf("%s/.pwmd", pw->pw_dir);
563 socketname = g_strdup("socket");
564 socketpath = g_strdup_printf("%s/%s", socketdir, socketname);
567 if (!datadir) {
568 snprintf(buf, sizeof(buf), "%s/.pwmd", pw->pw_dir);
569 datadir = g_strdup(buf);
572 if (!socketarg) {
573 socketdir = g_strdup_printf("%s/.pwmd", pw->pw_dir);
574 socketname = g_strdup("socket");
575 socketpath = g_strdup_printf("%s/%s", socketdir, socketname);
577 else {
578 if (strchr(socketarg, '/') == NULL) {
579 socketdir = g_strdup(cwd);
580 socketname = g_strdup(socketarg);
581 socketpath = g_strdup_printf("%s/%s", socketdir, socketname);
583 else {
584 socketname = g_strdup(strrchr(socketarg, '/'));
585 socketname++;
586 socketarg[strlen(socketarg) - strlen(socketname) -1] = 0;
587 socketdir = g_strdup(socketarg);
588 socketpath = g_strdup_printf("%s/%s", socketdir, socketname);
592 snprintf(buf, sizeof(buf), "%s", datadir);
594 if (mkdir(buf, 0700) == -1 && errno != EEXIST)
595 err(EXIT_FAILURE, "%s", buf);
597 snprintf(buf, sizeof(buf), "pwmd.%i", pw->pw_uid);
599 if ((shm_fd = shm_open(buf, O_CREAT|O_RDWR|O_EXCL, 0600)) == -1)
600 err(EXIT_FAILURE, "shm_open(): %s", buf);
603 * Should be enough for the file cache.
605 if (ftruncate(shm_fd, cache_size) == -1)
606 err(EXIT_FAILURE, "ftruncate()");
608 if ((shm_data = mmap(NULL, cache_size, PROT_READ|PROT_WRITE, MAP_SHARED,
609 shm_fd, 0)) == NULL) {
610 shm_unlink(buf);
611 err(EXIT_FAILURE, "mmap()");
614 if (mlock(shm_data, cache_size) == -1)
615 warn("mlock()");
618 * bind() doesn't like the full pathname of the socket or any non alphanum
619 * characters so change to the directory where the socket is wanted then
620 * create it then change to datadir.
622 if (chdir(socketdir))
623 err(EXIT_FAILURE, "%s", socketdir);
625 g_free(socketdir);
627 if ((sfd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1)
628 err(EXIT_FAILURE, "socket()");
630 addr.sun_family = AF_UNIX;
631 snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", socketname);
633 if (bind(sfd, (struct sockaddr *)&addr, sizeof(struct sockaddr)) == -1)
634 err(EXIT_FAILURE, "bind()");
636 if (chdir(datadir)) {
637 close(sfd);
638 unlink(socketpath);
639 err(EXIT_FAILURE, "%s", datadir);
642 g_free(datadir);
644 if (listen(sfd, 0) == -1)
645 err(EXIT_FAILURE, "listen()");
647 signal(SIGCHLD, catchsig);
648 signal(SIGTERM, catchsig);
649 signal(SIGINT, catchsig);
650 signal(SIGHUP, catchsig);
651 log_write("%s starting: %li slots available", PACKAGE_STRING, cache_size / sizeof(file_cache_t));
653 while (!quit) {
654 socklen_t slen = sizeof(struct sockaddr_un);
655 struct sockaddr_un raddr;
656 pid_t pid;
658 if ((fd = accept(sfd, (struct sockaddr_un *)&raddr, &slen)) == -1) {
659 if (!quit)
660 log_write("accept(): %s", strerror(errno));
662 continue;
665 switch ((pid = fork())) {
666 case -1:
667 log_write("fork(): %s", strerror(errno));
668 break;
669 case 0:
670 doit(fd);
671 break;
672 default:
673 break;
676 log_write("new connection: fd=%i, pid=%i", fd, pid);
679 unlink(socketpath);
680 g_free(socketpath);
682 if (munmap(shm_data, cache_size) == -1)
683 log_write("munmap(): %s", strerror(errno));
685 if (shm_unlink(buf) == -1)
686 log_write("shm_unlink(): %s: %s", buf, strerror(errno));
688 log_write("exiting normally");
689 exit(EXIT_SUCCESS);