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>
46 #include <sys/resource.h>
52 #include "pwmd_error.h"
55 void send_to_client(struct client_s
*client
, const gchar
*fmt
, ...)
64 for (p
= client
->outbuf
, n
= 0; *p
; p
++, n
++);
66 if ((client
->outbuf
= g_realloc(client
->outbuf
, (n
+ 2) * sizeof(gchar
*))) == NULL
) {
71 client
->outbuf
[n
++] = g_strdup_vprintf(fmt
, ap
);
72 client
->outbuf
[n
] = NULL
;
76 void send_error(struct client_s
*client
, int pwmd_errno
)
78 send_to_client(client
, "ERR %03i %s\n", pwmd_errno
, pwmd_strerror(pwmd_errno
));
81 void log_write(const gchar
*fmt
, ...)
93 if ((fd
= open(logfile
, O_WRONLY
|O_CREAT
|O_APPEND
, 0600)) == -1) {
99 g_vasprintf(&args
, fmt
, ap
);
102 tm
= localtime(&now
);
103 strftime(tbuf
, sizeof(tbuf
), "%b %d %Y %H:%M:%S ", tm
);
104 tbuf
[sizeof(tbuf
) - 1] = 0;
105 line
= g_strdup_printf("%s %i %s\n", tbuf
, getpid(), args
);
106 write(fd
, line
, strlen(line
));
112 static void catchsig(gint sig
)
116 log_write("caught signal %i (%s)", sig
, strsignal(sig
));
120 waitpid(-1, &status
, 0);
123 log_write("clearing file cache");
124 memset(shm_data
, 0, cache_size
);
128 shutdown(sfd
, SHUT_RDWR
);
134 static void usage(gchar
*pn
)
137 "Usage: %s [-hv] [-f <rcfile>]\n"
138 " -f load the specified rcfile (~/.pwmdrc)\n"
140 " -h this help text\n",
145 gchar
**split_input_line(gchar
*str
, gchar
*delim
, gint n
)
150 return g_strsplit(str
, delim
, n
);
153 static void setup_gcrypt()
155 gcry_check_version(NULL
);
157 if (gcry_cipher_algo_info(GCRY_CIPHER_AES256
, GCRYCTL_TEST_ALGO
, NULL
,
159 errx(EXIT_FAILURE
, "AES cipher not supported");
161 gcry_cipher_algo_info(GCRY_CIPHER_AES256
, GCRYCTL_GET_KEYLEN
, NULL
, &gcrykeysize
);
162 gcry_cipher_algo_info(GCRY_CIPHER_AES256
, GCRYCTL_GET_BLKLEN
, NULL
, &gcryblocksize
);
165 static gint
input_parser(gchar
*str
)
171 str
= g_strchug(str
);
176 while ((p
= strsep(&str
, "\n")) != NULL
) {
177 if (g_ascii_strcasecmp(p
, "QUIT") == 0)
179 else if (g_ascii_strcasecmp(p
, "HELP") == 0)
180 help_command(cl
, NULL
);
181 else if (g_ascii_strncasecmp(p
, "HELP ", 5) == 0) {
186 else if (g_ascii_strcasecmp(p
, "LIST") == 0 ||
187 g_ascii_strncasecmp(p
, "LIST ", 5) == 0) {
188 if (cl
->state
!= STATE_OPEN
)
189 send_error(cl
, EPWMD_NO_FILE
);
193 else if (g_ascii_strncasecmp(p
, "STORE ", 6) == 0) {
197 if (cl
->state
!= STATE_OPEN
)
198 send_error(cl
, EPWMD_NO_FILE
);
200 if ((req
= split_input_line(t
, "\t", 0)) != NULL
) {
201 if (store_command(cl
, req
) == TRUE
)
202 send_to_client(cl
, "OK \n");
205 send_error(cl
, EPWMD_COMMAND_SYNTAX
);
208 else if (g_ascii_strncasecmp(p
, "DELETE ", 7) == 0) {
211 if (cl
->state
!= STATE_OPEN
)
212 send_error(cl
, EPWMD_NO_FILE
);
214 if ((req
= split_input_line(t
, "\t", 0)) != NULL
) {
215 if (delete_command(cl
, req
) == TRUE
)
216 send_to_client(cl
, "OK \n");
219 send_error(cl
, EPWMD_COMMAND_SYNTAX
);
222 else if (g_ascii_strncasecmp(p
, "GET ", 4) == 0) {
226 if (cl
->state
!= STATE_OPEN
)
227 send_error(cl
, EPWMD_NO_FILE
);
229 if ((req
= split_input_line(t
, "\t", 0)) != NULL
)
230 get_command(cl
, &cl
->reader
, req
, 0);
232 send_error(cl
, EPWMD_COMMAND_SYNTAX
);
235 else if (g_ascii_strncasecmp(p
, "ATTR ", 5) == 0) {
239 if (cl
->state
!= STATE_OPEN
)
240 send_error(cl
, EPWMD_NO_FILE
);
242 if ((req
= split_input_line(t
, " ", 4)) != NULL
) {
243 if (attr_command(cl
, req
) == TRUE
)
244 send_to_client(cl
, "OK \n");
247 send_error(cl
, EPWMD_COMMAND_SYNTAX
);
250 else if (g_ascii_strncasecmp(p
, "OPEN ", 5) == 0) {
254 if (cl
->state
== STATE_OPEN
)
255 send_error(cl
, EPWMD_FILE_OPENED
);
257 if ((req
= split_input_line(t
, " ", 2)) != NULL
) {
258 if (open_command(cl
, req
) == TRUE
) {
259 send_to_client(cl
, "OK \n");
260 cl
->state
= STATE_OPEN
;
264 * The document has been parsed and is stored in cl->doc.
267 memset(cl
->xml
, 0, cl
->len
);
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 g_clear_error(&gerror
);
398 memset(cl
->xml
, 0, cl
->len
);
403 gcry_cipher_close(cl
->gh
);
409 xmlFreeTextReader(cl
->reader
);
415 g_free(cl
->filename
);
417 g_main_loop_unref(gloop
);
418 g_main_loop_quit(gloop
);
424 memset(line
, 0, len
);
430 * Called every time a connection is made.
432 static void doit(int fd
)
434 static GSourceFuncs gsrcf
= {
435 source_prepare
, source_check
, source_dispatch
, NULL
, 0, 0
437 GPollFD gfd
= { fd
, G_IO_IN
|G_IO_OUT
|G_IO_HUP
|G_IO_ERR
, 0 };
440 if (use_mlock
&& mlockall(MCL_FUTURE
) == -1)
441 err(EXIT_FAILURE
, "mlockall()");
444 gloop
= g_main_loop_new(NULL
, TRUE
);
445 cl
= g_malloc0(sizeof(struct client_s
));
446 cl
->src
= g_source_new(&gsrcf
, sizeof(GSource
));
448 cl
->state
= STATE_CONNECTED
;
449 cl
->ioc
= g_io_channel_unix_new(fd
);
450 g_source_add_poll(cl
->src
, &cl
->gfd
);
451 g_source_set_callback(cl
->src
, source_cb
, NULL
, NULL
);
452 g_source_attach(cl
->src
, NULL
);
459 if ((gcryerrno
= gcry_cipher_open(&cl
->gh
, GCRY_CIPHER_AES256
, GCRY_CIPHER_MODE_CBC
, 0))) {
460 send_to_client(cl
, "ERR %03i gcrypt: %s\n", EPWMD_ERROR
, gcry_strerror(gcryerrno
));
461 log_write("%s(%i): %s", __FUNCTION__
, __LINE__
, gcry_strerror(gcryerrno
));
465 // FIXME 100% CPU if removed (poll()).
466 send_to_client(cl
, "OK \n");
468 g_main_loop_run(gloop
);
469 g_io_channel_unref(cl
->ioc
);
471 shutdown(fd
, SHUT_RDWR
);
472 log_write("exiting");
476 static void set_rcfile_defaults(GKeyFile
*kf
)
480 snprintf(buf
, sizeof(buf
), "~/.pwmd/socket");
481 g_key_file_set_string(kf
, "default", "socket_path", buf
);
482 snprintf(buf
, sizeof(buf
), "~/.pwmd");
483 g_key_file_set_string(kf
, "default", "data_directory", buf
);
484 snprintf(buf
, sizeof(buf
), "~/.pwmd/.log");
485 g_key_file_set_string(kf
, "default", "log_path", buf
);
486 g_key_file_set_boolean(kf
, "default", "enable_logging", FALSE
);
487 g_key_file_set_integer(kf
, "default", "cache_size", cache_size
);
488 g_key_file_set_boolean(kf
, "default", "disable_mlockall", FALSE
);
491 static GKeyFile
*parse_rcfile(const gchar
*filename
)
493 GKeyFile
*kf
= g_key_file_new();
494 GError
*error
= NULL
;
496 if (g_key_file_load_from_file(kf
, filename
, G_KEY_FILE_NONE
, &error
) == FALSE
) {
497 if (error
->code
== G_FILE_ERROR_NOENT
) {
498 g_clear_error(&error
);
499 set_rcfile_defaults(kf
);
503 warnx("%s: %s", filename
, error
->message
);
504 g_clear_error(&error
);
512 static gboolean
try_xml_decrypt(gint fd
, struct stat st
, guchar
*key
, gchar
**xml
,
519 if ((gcryerrno
= gcry_cipher_open(&gh
, GCRY_CIPHER_AES256
, GCRY_CIPHER_MODE_CBC
, 0))) {
520 warnx("%s(%i): %s", __FUNCTION__
, __LINE__
, gcry_strerror(gcryerrno
));
524 inbuf
= gcry_malloc(st
.st_size
);
525 read(fd
, inbuf
, st
.st_size
);
527 iv
= gcry_malloc(gcryblocksize
);
528 memcpy(iv
, inbuf
, gcryblocksize
);
530 if (decrypt_xml(gh
, key
, gcrykeysize
, iv
, inbuf
+ gcryblocksize
,
531 st
.st_size
- gcryblocksize
, NULL
, 0) == FALSE
) {
532 memset(inbuf
, 0, st
.st_size
);
539 memmove(inbuf
, inbuf
+ gcryblocksize
, st
.st_size
- gcryblocksize
);
541 *len
= st
.st_size
- gcryblocksize
;
543 if (g_strncasecmp(*xml
, "<?xml version=\"1.0\"?>", 21) != 0)
549 static gboolean
get_input(const gchar
*filename
, guchar
*key
)
551 gchar buf
[LINE_MAX
], *p
;
552 struct termios told
, tnew
;
558 gint tty
= isatty(STDIN_FILENO
);
560 if ((fd
= open_file(filename
, &st
)) == -1)
563 if (st
.st_size
== 0) {
564 fprintf(stderr
, "Skipping empty file '%s'.\n", filename
);
570 if (tcgetattr(STDIN_FILENO
, &told
) == -1)
571 err(EXIT_FAILURE
, "tcgetattr()");
573 memcpy(&tnew
, &told
, sizeof(struct termios
));
574 tnew
.c_lflag
&= ~(ECHO
);
575 tnew
.c_lflag
|= ICANON
|ECHONL
;
580 if (tcsetattr(STDIN_FILENO
, TCSANOW
, &tnew
) == -1) {
581 tcsetattr(STDIN_FILENO
, TCSANOW
, &told
);
582 err(EXIT_FAILURE
, "tcsetattr()");
586 printf("Password for '%s': ", filename
);
588 if ((p
= fgets(buf
, sizeof(buf
), stdin
)) == NULL
) {
590 tcsetattr(STDIN_FILENO
, TCSANOW
, &told
);
597 tcsetattr(STDIN_FILENO
, TCSANOW
, &told
);
599 p
[strlen(p
) - 1] = 0;
602 fprintf(stderr
, "Skipping.\n");
607 gcry_md_hash_buffer(GCRY_MD_SHA256
, key
, p
, strlen(p
));
608 memset(buf
, 0, sizeof(buf
));
610 if (try_xml_decrypt(fd
, st
, key
, &xml
, &len
) == FALSE
) {
617 fprintf(stderr
, "Invalid password. Skipping file.\n");
621 fprintf(stderr
, "Invalid password.\n");
631 gint
cache_file_count()
638 for (p
= shm_data
, len
= 0; len
<= cache_size
;) {
639 memcpy(&f
, p
, sizeof(file_cache_t
));
644 p
+= sizeof(file_cache_t
);
645 len
+= sizeof(file_cache_t
);
647 if (len
+ sizeof(file_cache_t
) > cache_size
)
654 gboolean
cache_add_file(const guchar
*md5file
, const guchar
*shakey
)
658 gint nfiles
= cache_file_count();
662 * Make sure there is enough secure memory.
664 if (!md5file
|| (nfiles
+ 1) * sizeof(file_cache_t
) > cache_size
)
668 * Find the first available "slot".
670 for (p
= shm_data
, len
= 0; len
<= cache_size
;) {
671 memcpy(&f
, p
, sizeof(file_cache_t
));
673 if (f
.used
== FALSE
) {
674 memcpy(&f
.filename
, md5file
, sizeof(f
.filename
));
677 memcpy(&f
.key
, shakey
, sizeof(f
.key
));
680 memcpy(p
, &f
, sizeof(file_cache_t
));
684 p
+= sizeof(file_cache_t
);
685 len
+= sizeof(file_cache_t
);
687 if (len
+ sizeof(file_cache_t
) > cache_size
)
694 int main(int argc
, char *argv
[])
697 struct sockaddr_un addr
;
698 struct passwd
*pw
= getpwuid(getuid());
700 gchar
*socketpath
= NULL
, *socketdir
, *socketname
= NULL
;
701 gchar
*socketarg
= NULL
;
702 gchar
*datadir
= NULL
;
707 gchar
**cache_push
= NULL
;
710 #ifdef HAVE_SETRLIMIT
713 rl
.rlim_cur
= rl
.rlim_max
= 0;
715 if (setrlimit(RLIMIT_CORE
, &rl
) != 0)
716 err(EXIT_FAILURE
, "setrlimit()");
720 rcfile
= g_strdup_printf("%s/.pwmdrc", pw
->pw_dir
);
722 if ((page_size
= sysconf(_SC_PAGESIZE
)) == -1)
723 err(EXIT_FAILURE
, "sysconf()");
725 cache_size
= page_size
;
729 * Default to using mlockall().
734 while ((opt
= getopt(argc
, argv
, "hvf:")) != EOF
) {
738 rcfile
= g_strdup(optarg
);
741 printf("%s\n%s\n", PACKAGE_STRING
, PACKAGE_BUGREPORT
);
749 if ((kf
= parse_rcfile(rcfile
)) == NULL
)
752 g_key_file_set_list_separator(kf
, ',');
754 if ((p
= g_key_file_get_string(kf
, "default", "socket_path", NULL
)) == NULL
)
755 errx(EXIT_FAILURE
, "%s: socket_path not defined", rcfile
);
759 snprintf(buf
, sizeof(buf
), "%s%s", g_get_home_dir(), p
--);
761 socketarg
= g_strdup(buf
);
766 if ((p
= g_key_file_get_string(kf
, "default", "data_directory", NULL
)) == NULL
)
767 errx(EXIT_FAILURE
, "%s: data_directory not defined", rcfile
);
771 snprintf(buf
, sizeof(buf
), "%s%s", g_get_home_dir(), p
--);
773 datadir
= g_strdup(buf
);
778 if (g_key_file_has_key(kf
, "default", "cache_size", NULL
) == TRUE
) {
779 cache_size
= g_key_file_get_integer(kf
, "default", "cache_size", NULL
);
781 if (cache_size
< page_size
|| cache_size
% page_size
)
782 errx(EXIT_FAILURE
, "cache size must be in multiples of %li.", page_size
);
786 if (g_key_file_has_key(kf
, "default", "disable_mlockall", NULL
) == TRUE
)
787 use_mlock
= g_key_file_get_integer(kf
, "default", "disable_mlockall", NULL
);
790 if (g_key_file_has_key(kf
, "default", "log_path", NULL
) == TRUE
) {
791 if (g_key_file_has_key(kf
, "default", "enable_logging", NULL
) == TRUE
) {
792 n
= g_key_file_get_boolean(kf
, "default", "enable_logging", NULL
);
795 p
= g_key_file_get_string(kf
, "default", "log_path", NULL
);
799 snprintf(buf
, sizeof(buf
), "%s%s", g_get_home_dir(), p
--);
801 logfile
= g_strdup(buf
);
809 if (g_key_file_has_key(kf
, "default", "cache_push", NULL
) == TRUE
)
810 cache_push
= g_key_file_get_string_list(kf
, "default", "cache_push", NULL
, NULL
);
814 if (strchr(socketarg
, '/') == NULL
) {
815 socketdir
= g_get_current_dir();
816 socketname
= g_strdup(socketarg
);
817 socketpath
= g_strdup_printf("%s/%s", socketdir
, socketname
);
820 socketname
= g_strdup(strrchr(socketarg
, '/'));
822 socketarg
[strlen(socketarg
) - strlen(socketname
) -1] = 0;
823 socketdir
= g_strdup(socketarg
);
824 socketpath
= g_strdup_printf("%s/%s", socketdir
, socketname
);
827 snprintf(buf
, sizeof(buf
), "%s", datadir
);
829 if (mkdir(buf
, 0700) == -1 && errno
!= EEXIST
)
830 err(EXIT_FAILURE
, "%s", buf
);
832 #ifdef MMAP_ANONYMOUS_SHARED
833 if ((shm_data
= mmap(NULL
, cache_size
, PROT_READ
|PROT_WRITE
,
834 MAP_SHARED
|MAP_ANONYMOUS
, -1, 0)) == NULL
) {
835 err(EXIT_FAILURE
, "mmap()");
838 snprintf(buf
, sizeof(buf
), "pwmd.%i", pw
->pw_uid
);
840 if ((fd
= shm_open(buf
, O_CREAT
|O_RDWR
|O_EXCL
, 0600)) == -1)
841 err(EXIT_FAILURE
, "shm_open(): %s", buf
);
844 * Should be enough for the file cache.
846 if (ftruncate(fd
, cache_size
) == -1)
847 err(EXIT_FAILURE
, "ftruncate()");
849 if ((shm_data
= mmap(NULL
, cache_size
, PROT_READ
|PROT_WRITE
, MAP_SHARED
,
852 err(EXIT_FAILURE
, "mmap()");
858 if (mlock(shm_data
, cache_size
) == -1)
862 * bind() doesn't like the full pathname of the socket or any non alphanum
863 * characters so change to the directory where the socket is wanted then
864 * create it then change to datadir.
866 if (chdir(socketdir
))
867 err(EXIT_FAILURE
, "%s", socketdir
);
871 if ((sfd
= socket(PF_UNIX
, SOCK_STREAM
, 0)) == -1)
872 err(EXIT_FAILURE
, "socket()");
874 addr
.sun_family
= AF_UNIX
;
875 snprintf(addr
.sun_path
, sizeof(addr
.sun_path
), "%s", socketname
);
876 g_free(--socketname
);
878 if (bind(sfd
, (struct sockaddr
*)&addr
, sizeof(struct sockaddr
)) == -1)
879 err(EXIT_FAILURE
, "bind()");
881 if (chdir(datadir
)) {
884 err(EXIT_FAILURE
, "%s", datadir
);
891 * Set the cache entry for a file. Prompts for the password.
897 for (opt
= 0; cache_push
[opt
]; opt
++) {
906 gcry_md_hash_buffer(GCRY_MD_MD5
, md5file
, p
, strlen(p
));
908 if (access(p
, R_OK
|W_OK
) != 0) {
909 if (errno
!= ENOENT
) {
911 err(EXIT_FAILURE
, "%s", p
);
918 if (get_input(p
, key
) == FALSE
) {
919 memset(key
, 0, sizeof(key
));
923 if (cache_add_file(md5file
, key
) == FALSE
) {
924 memset(key
, 0, sizeof(key
));
925 errx(EXIT_FAILURE
, "%s: couldn't add file (cache_size?)", p
);
928 fprintf(stderr
, "Added.\n");
929 memset(key
, 0, sizeof(key
));
930 memset(md5file
, 0, sizeof(md5file
));
933 g_strfreev(cache_push
);
934 fprintf(stderr
, "Done! Daemonizing...\n");
939 if (listen(sfd
, 0) == -1)
940 err(EXIT_FAILURE
, "listen()");
942 signal(SIGCHLD
, catchsig
);
943 signal(SIGTERM
, catchsig
);
944 signal(SIGINT
, catchsig
);
945 signal(SIGHUP
, catchsig
);
946 log_write("%s starting: %li slots available", PACKAGE_STRING
, cache_size
/ sizeof(file_cache_t
));
949 socklen_t slen
= sizeof(struct sockaddr_un
);
950 struct sockaddr_un raddr
;
953 if ((fd
= accept(sfd
, (struct sockaddr_un
*)&raddr
, &slen
)) == -1) {
955 log_write("accept(): %s", strerror(errno
));
960 switch ((pid
= fork())) {
962 log_write("fork(): %s", strerror(errno
));
971 log_write("new connection: fd=%i, pid=%i", fd
, pid
);
977 if (munmap(shm_data
, cache_size
) == -1)
978 log_write("munmap(): %s", strerror(errno
));
980 #ifndef MMAP_ANONYMOUS_SHARED
981 if (shm_unlink(buf
) == -1)
982 log_write("shm_unlink(): %s: %s", buf
, strerror(errno
));
985 log_write("pwmd exiting normally");