1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
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
26 #include <sys/socket.h>
35 #include <glib/gprintf.h>
45 #include <sys/resource.h>
51 #include "pwmd_error.h"
54 void send_to_client(struct client_s
*client
, const gchar
*fmt
, ...)
63 for (p
= client
->outbuf
, n
= 0; *p
; p
++, n
++);
65 if ((client
->outbuf
= g_realloc(client
->outbuf
, (n
+ 2) * sizeof(gchar
*))) == NULL
) {
70 client
->outbuf
[n
++] = g_strdup_vprintf(fmt
, ap
);
71 client
->outbuf
[n
] = NULL
;
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
, ...)
92 if ((fd
= open(logfile
, O_WRONLY
|O_CREAT
|O_APPEND
, 0600)) == -1) {
98 g_vasprintf(&args
, fmt
, ap
);
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
));
111 static void catchsig(gint sig
)
115 log_write("caught signal %i (%s)", sig
, strsignal(sig
));
119 waitpid(-1, &status
, 0);
122 log_write("clearing file cache");
123 memset(shm_data
, 0, cache_size
);
126 shutdown(sfd
, SHUT_RDWR
);
133 static void usage(gchar
*pn
)
137 "Usage: %s [-hv] [-l <logfile> [-M] [-C <cache_size>] [-d <directory>] [-s <socketpath>]\n"
139 "Usage: %s [-hv] [-l <logfile> [-C <cache_size>] [-d <directory>] [-s <socketpath>]\n"
141 " -d directory where to store and retrieve data files\n"
144 " -M disable mlockall()\n"
146 " -C cache size (%li)\n"
147 " -l enable logging to the specified file\n"
149 " -h this help text\n",
154 gchar
**split_input_line(gchar
*str
, gchar
*delim
, gint n
)
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
,
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
)
180 str
= g_strchug(str
);
185 while ((p
= strsep(&str
, "\n")) != NULL
) {
186 if (g_ascii_strcasecmp(p
, "QUIT") == 0)
188 else if (g_ascii_strcasecmp(p
, "HELP") == 0)
189 help_command(cl
, NULL
);
190 else if (g_ascii_strncasecmp(p
, "HELP ", 5) == 0) {
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
);
202 else if (g_ascii_strncasecmp(p
, "STORE ", 6) == 0) {
206 if (cl
->state
!= STATE_OPEN
)
207 send_error(cl
, EPWMD_NO_FILE
);
209 if ((req
= split_input_line(t
, "\t", 0)) != NULL
) {
210 if (store_command(cl
, req
) == TRUE
)
211 send_to_client(cl
, "OK \n");
214 send_error(cl
, EPWMD_COMMAND_SYNTAX
);
217 else if (g_ascii_strncasecmp(p
, "DELETE ", 7) == 0) {
220 if (cl
->state
!= STATE_OPEN
)
221 send_error(cl
, EPWMD_NO_FILE
);
223 if ((req
= split_input_line(t
, "\t", 0)) != NULL
) {
224 if (delete_command(cl
, req
) == TRUE
)
225 send_to_client(cl
, "OK \n");
228 send_error(cl
, EPWMD_COMMAND_SYNTAX
);
231 else if (g_ascii_strncasecmp(p
, "GET ", 4) == 0) {
235 if (cl
->state
!= STATE_OPEN
)
236 send_error(cl
, EPWMD_NO_FILE
);
238 if ((req
= split_input_line(t
, "\t", 0)) != NULL
)
239 get_command(cl
, &cl
->reader
, req
, 0);
241 send_error(cl
, EPWMD_COMMAND_SYNTAX
);
244 else if (g_ascii_strncasecmp(p
, "ATTR ", 5) == 0) {
248 if (cl
->state
!= STATE_OPEN
)
249 send_error(cl
, EPWMD_NO_FILE
);
251 if ((req
= split_input_line(t
, " ", 4)) != NULL
) {
252 if (attr_command(cl
, req
) == TRUE
)
253 send_to_client(cl
, "OK \n");
256 send_error(cl
, EPWMD_COMMAND_SYNTAX
);
259 else if (g_ascii_strncasecmp(p
, "OPEN ", 5) == 0) {
263 if (cl
->state
== STATE_OPEN
)
264 send_error(cl
, EPWMD_FILE_OPENED
);
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
;
273 send_error(cl
, EPWMD_COMMAND_SYNTAX
);
276 else if (g_ascii_strncasecmp(p
, "SAVE", 4) == 0) {
280 if (cl
->state
!= STATE_OPEN
)
281 send_error(cl
, EPWMD_NO_FILE
);
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) {
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
);
301 if (dump_command(cl
) == TRUE
)
302 send_to_client(cl
, "OK \n");
306 send_error(cl
, EPWMD_COMMAND_SYNTAX
);
317 static gboolean
source_prepare(GSource
*src
, gint
*to
)
319 if (cl
->gfd
.revents
& (G_IO_HUP
|G_IO_NVAL
|G_IO_ERR
))
325 static gboolean
source_check(GSource
*src
)
327 if (cl
->gfd
.revents
& (G_IO_IN
|G_IO_PRI
))
330 if (cl
->outbuf
&& (cl
->gfd
.revents
& (G_IO_OUT
)))
336 static gboolean
source_dispatch(GSource
*src
, GSourceFunc cb
, gpointer data
)
341 static gboolean
source_cb(gpointer data
)
346 GError
*gerror
= NULL
;
349 if (cl
->gfd
.revents
& (G_IO_HUP
|G_IO_NVAL
|G_IO_ERR
))
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
);
359 log_write("%s(%i): %s", __FUNCTION__
, __LINE__
, gerror
->message
);
361 g_clear_error(&gerror
);
367 g_strfreev(cl
->outbuf
);
371 if (!cl
->gfd
.revents
& (G_IO_IN
))
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
)
380 log_write("%s(%i): %s", __FUNCTION__
, __LINE__
, gerror
->message
);
381 g_clear_error(&gerror
);
385 line
[g_utf8_strlen(line
, -1) - 1] = 0;
387 switch (input_parser(line
)) {
391 memset(line
, 0, len
);
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
);
404 memset(cl
->xml
, 0, cl
->len
);
409 gcry_cipher_close(cl
->gh
);
415 xmlFreeTextReader(cl
->reader
);
421 g_free(cl
->filename
);
423 g_main_loop_unref(gloop
);
424 g_main_loop_quit(gloop
);
430 memset(line
, 0, len
);
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 };
446 if (use_mlock
&& mlockall(MCL_FUTURE
) == -1)
447 err(EXIT_FAILURE
, "mlockall()");
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
));
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
);
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
));
471 // FIXME 100% CPU if removed (poll()).
472 send_to_client(cl
, "OK \n");
474 g_main_loop_run(gloop
);
480 int main(int argc
, char *argv
[])
483 struct sockaddr_un addr
;
484 struct passwd
*pw
= getpwuid(getuid());
486 gchar
*socketpath
= NULL
, *socketdir
, *socketname
= NULL
;
487 gchar
*socketarg
= NULL
;
489 gchar
*datadir
= NULL
;
492 #ifdef HAVE_SETRLIMIT
495 rl
.rlim_cur
= rl
.rlim_max
= 0;
497 if (setrlimit(RLIMIT_CORE
, &rl
) != 0)
498 err(EXIT_FAILURE
, "setrlimit()");
502 if ((page_size
= sysconf(_SC_PAGESIZE
)) == -1)
503 err(EXIT_FAILURE
, "sysconf()");
505 cache_size
= page_size
;
509 * Default to using mlockall().
513 while ((opt
= getopt(argc
, argv
, "C:Mhd:s:vl:")) != EOF
) {
515 while ((opt
= getopt(argc
, argv
, "C:hd:s:vl:")) != EOF
) {
519 if (*optarg
!= '/') {
520 getcwd(buf
, sizeof(buf
));
521 strncat(buf
, "/", sizeof(buf
));
522 strncat(buf
, optarg
, sizeof(buf
));
523 logfile
= g_strdup(buf
);
526 logfile
= g_strdup(optarg
);
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
);
545 datadir
= g_strdup(optarg
);
548 printf("%s\n%s\n", PACKAGE_STRING
, PACKAGE_BUGREPORT
);
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
);
568 snprintf(buf
, sizeof(buf
), "%s/.pwmd", pw
->pw_dir
);
569 datadir
= g_strdup(buf
);
573 socketdir
= g_strdup_printf("%s/.pwmd", pw
->pw_dir
);
574 socketname
= g_strdup("socket");
575 socketpath
= g_strdup_printf("%s/%s", socketdir
, socketname
);
578 if (strchr(socketarg
, '/') == NULL
) {
579 socketdir
= g_strdup(cwd
);
580 socketname
= g_strdup(socketarg
);
581 socketpath
= g_strdup_printf("%s/%s", socketdir
, socketname
);
584 socketname
= g_strdup(strrchr(socketarg
, '/'));
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
) {
611 err(EXIT_FAILURE
, "mmap()");
614 if (mlock(shm_data
, cache_size
) == -1)
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
);
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
)) {
639 err(EXIT_FAILURE
, "%s", 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
));
654 socklen_t slen
= sizeof(struct sockaddr_un
);
655 struct sockaddr_un raddr
;
658 if ((fd
= accept(sfd
, (struct sockaddr_un
*)&raddr
, &slen
)) == -1) {
660 log_write("accept(): %s", strerror(errno
));
665 switch ((pid
= fork())) {
667 log_write("fork(): %s", strerror(errno
));
676 log_write("new connection: fd=%i, pid=%i", fd
, pid
);
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");