c0bcb1708498a48c2a2cd9333cd23e15b8c844bc
[mmondor.git] / mmsoftware / mmstatd / src / mmstatd.c
1 /* $Id: mmstatd.c,v 1.25 2003/10/10 04:38:53 mmondor Exp $ */
2
3 /*
4 * Copyright (C) 2002-2003, Matthew Mondor
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. All advertising materials mentioning features or use of this software
16 * must display the following acknowledgement:
17 * This product includes software written by Matthew Mondor.
18 * 4. The name of Matthew Mondor may not be used to endorse or promote
19 * products derived from this software without specific prior written
20 * permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY MATTHEW MONDOR ``AS IS'' AND ANY EXPRESS OR
23 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
24 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
25 * IN NO EVENT SHALL MATTHEW MONDOR BE LIABLE FOR ANY DIRECT, INDIRECT,
26 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
28 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
31 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 */
33
34 /* TODO:
35 * - Finish client packet uid credential checking, credentials have to be
36 * passed through the unix domain socket
37 * - Have endian-independant logfile and database formats (Note: linux has no
38 * standard 64-bit conversion function it seems, although NetBSD does)
39 */
40
41
42
43
44 /* HEADERS */
45
46 #include <sys/types.h>
47 #include <sys/stat.h>
48 #include <sys/wait.h>
49 #include <sys/socket.h>
50 #include <sys/un.h>
51 #include <sys/uio.h>
52 #include <sys/file.h>
53 #include <netinet/in.h>
54 #include <arpa/inet.h>
55 #include <arpa/nameser.h>
56 #include <resolv.h>
57 #include <poll.h>
58 #include <syslog.h>
59 #include <signal.h>
60 #include <stddef.h>
61 #include <unistd.h>
62 #include <fcntl.h>
63 #include <stdlib.h>
64 #include <stdio.h>
65 #include <time.h>
66 #include <dirent.h>
67 #include <pwd.h>
68 #include <grp.h>
69 #include <errno.h>
70
71 #include <mmtypes.h>
72 #include <mmpool.h>
73 #include <mmhash.h>
74 #include <mmstring.h>
75 #include <mmstat.h>
76 #include <mmstatd.h>
77 #include <mmreadcfg.h>
78 #include <mmlog.h>
79
80
81
82
83 MMCOPYRIGHT("@(#) Copyright (c) 2002-2003\n\
84 \tMatthew Mondor. All rights reserved.\n");
85 MMRCSID("$Id: mmstatd.c,v 1.25 2003/10/10 04:38:53 mmondor Exp $");
86
87
88
89
90 /* GLOBALS */
91
92 static struct mmstat_config CONF;
93 static pid_t librarian_pid = -1, logger_pid = -1;
94 static pool_t key_pool;
95 static hashtable_t key_table;
96 static int pipefds[2], syncbytes = 0;
97 static bool pipesend = TRUE;
98
99
100
101
102 /* FUNCTIONS */
103
104 static pid_t
105 process_spawn(int (*function)(void *), void *params, bool leader)
106 {
107 pid_t pid = -1;
108 int fd;
109
110 /* Create new process */
111 if (!(pid = fork())) {
112 struct sigaction act;
113
114 /* Child */
115 if (leader) setsid();
116 chdir("/");
117 umask(0);
118
119 /* Make sure that stdin, stdout and stderr are safe */
120 if ((fd = open("/dev/null", O_RDWR)) != -1) {
121 dup2(fd, 0);
122 dup2(fd, 1);
123 dup2(fd, 2);
124 if (fd > 2)
125 close(fd);
126 }
127
128 /* Setup our break handler */
129 act.sa_handler = sighandler;
130 act.sa_flags = SA_NOCLDWAIT;
131 sigemptyset(&act.sa_mask);
132 sigaction(SIGTERM, &act, NULL);
133 sigaction(SIGSEGV, &act, NULL);
134 sigaction(SIGPIPE, &act, NULL);
135
136 /* Signals we want to ignore */
137 signal(SIGTTOU, SIG_IGN);
138 signal(SIGTTIN, SIG_IGN);
139 signal(SIGTSTP, SIG_IGN);
140
141 /* Simply call the wanted child function */
142 exit(function(params));
143
144 }
145
146 /* Parent */
147 return (pid);
148 }
149
150
151 static void
152 sighandler(int sig)
153 {
154 switch (sig) {
155 case SIGTERM:
156 syslog(LOG_NOTICE, "Received SIGTERM, cleaning up");
157 signal(SIGTERM, SIG_IGN);
158 kill(0, SIGTERM);
159 exit(0);
160 break;
161 case SIGSEGV:
162 syslog(LOG_NOTICE, "Received SIGSEGV! Cleaning up");
163 kill(0, SIGTERM);
164 exit(0);
165 break;
166 case SIGPIPE:
167 pipesend = FALSE;
168 break;
169 default:
170 syslog(LOG_NOTICE, "Signal handler catched unexpected signal");
171 break;
172 }
173 }
174
175
176 /* This uses an fd which remains open in child processes, if they close it the
177 * lock seems to be released automatically.
178 */
179 static bool
180 lock_check(const char *file)
181 {
182 int fd;
183
184 if ((fd = open(file, O_CREAT | O_TRUNC | O_WRONLY, 0600)) != -1) {
185 if (!(flock(fd, LOCK_EX | LOCK_NB))) return (TRUE);
186 close(fd);
187 }
188
189 return (FALSE);
190 }
191
192
193 static int
194 unix_init(const char *name, gid_t group, mode_t mode, int backlog, bool stream,
195 bool del)
196 {
197 int sock;
198 struct sockaddr_un sau;
199
200 if (del) unlink(name);
201
202 /* Open public UNIX domain socket */
203 if (stream) {
204 if ((sock = socket(AF_LOCAL, SOCK_STREAM, 0)) != -1) {
205 mm_strncpy(sau.sun_path, name, 100);
206 sau.sun_family = AF_UNIX;
207 if (bind(sock, (struct sockaddr *)&sau, sizeof(struct sockaddr_un))
208 != -1) {
209 if (!chmod(name, mode)) {
210 chown(name, -1, group);
211 if (!(listen(sock, backlog)))
212 return (sock);
213 else
214 DPRINTF("unix_init", "listen(%d)", sock);
215 } else
216 DPRINTF("unix_init", "chmod(%s, %d)", name, mode);
217 } else
218 DPRINTF("unix_init", "bind(%d)", sock);
219 close(sock);
220 } else
221 DPRINTF("unix_init", "socket()");
222 } else {
223 if ((sock = socket(AF_LOCAL, SOCK_DGRAM, 0)) != -1) {
224 int opt;
225
226 /* Set output buffer size */
227 opt = (int)BALIGN_CEIL(sizeof(struct log_entry) * MAX_TRANSACT,
228 1024);
229 if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(int))
230 == -1)
231 DPRINTF("unix_init", "setsockopt(%d, SO_RCVBUF)", sock);
232
233 mm_strncpy(sau.sun_path, name, 100);
234 sau.sun_family = AF_UNIX;
235 if (bind(sock, (struct sockaddr *)&sau, sizeof(struct sockaddr_un))
236 != -1) {
237 if (!chmod(name, mode)) {
238 chown(name, -1, group);
239 return (sock);
240 } else
241 DPRINTF("unix_init", "chmod(%s, %d)", name, mode);
242 } else
243 DPRINTF("unix_init", "bind(%d)", sock);
244 close(sock);
245 } else
246 DPRINTF("unix_init", "socket()");
247 }
248
249 return (-1);
250 }
251
252
253 static bool
254 log_match(const char *str, const char *pat)
255 {
256 for (; *pat != '*'; pat++, str++) {
257 if (*str == '\0') {
258 if (*pat != '\0')
259 return FALSE;
260 else
261 return TRUE;
262 }
263 if(*str != *pat && *pat != '?')
264 return FALSE;
265 }
266 while (pat[1] == '*')
267 pat++;
268 do
269 if (log_match(str, pat + 1))
270 return TRUE;
271 while (*str++ != '\0');
272
273 return FALSE;
274 }
275
276
277 /* Writes requested log entries to specified fd, and if required automatically
278 * perform logfile rotation, of course putting a mark at the end of the logfile
279 * pointing to the next one continueing it. Files are rotated when reaching
280 * approximately one megabyte in length. Returns TRUE on success or FALSE on
281 * fatal error (eg: disk full).
282 */
283 static bool
284 logentries_write(int pfd, struct log_entry *entries, int len, int *fd,
285 long *lognum, off_t *logpos)
286 {
287 ssize_t l;
288 bool ok;
289 char filename[256], dat[MAX_TRANSACT];
290
291 ok = TRUE;
292 l = len * sizeof(struct log_entry);
293
294 /* First perform logfile rotation if required */
295 if (*logpos + l > CONF.max_logsize) {
296 struct log_entry newentry;
297 int newfd;
298
299 (*lognum)++;
300 if (*lognum > 99999999) *lognum = 0;
301 *logpos = 0;
302 snprintf(filename, 255, "%s/%08ld.log", CONF.ENV_DIR, *lognum);
303 if ((newfd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0600))
304 != -1) {
305 fsync(newfd);
306 mm_memclr(&newentry, sizeof(struct log_entry));
307 newentry.type = STAT_NEWFILE;
308 newentry.un.newfile.lognum = *lognum;
309 if ((write(*fd, &newentry, sizeof(struct log_entry))) !=
310 sizeof(struct log_entry))
311 DPRINTF("logentries_write", "write(STAT_NEWFILE)");
312 fsync(*fd);
313 close(*fd);
314 *fd = newfd;
315 write(pfd, dat, 1);
316 } else {
317 DPRINTF("logentries_write", "open(%s)", filename);
318 ok = FALSE;
319 }
320 }
321
322 /* Write our new log entry */
323 if (ok) {
324 time_t tim;
325 int i;
326
327 /* Mark entry time */
328 tim = time(NULL);
329 for (i = 0; i < len; i++)
330 entries[i].time = tim;
331 /* Write to logfile */
332 if (write(*fd, entries, l) == l) {
333 *logpos += l;
334 /* A trick to keep resonable physical disk sync rate */
335 syncbytes += l;
336 if (syncbytes >= CONF.SYNC_BYTES) {
337 syncbytes = 0;
338 fdatasync(*fd);
339 }
340 write(pfd, dat, len);
341 } else {
342 DPRINTF("logentries_write", "write(STAT_*)");
343 ok = FALSE;
344 }
345 }
346
347 return (ok);
348 }
349
350
351 /* This function attempts to read a log entry from specified fd, transparently
352 * rotating to the new logfile when required, and updates fd and lognum via
353 * provided pointers. If pfd is -1, we attempt to read an entry, and return
354 * FALSE if none could be read. Otherwise we attempt to read for an entry
355 * monitoring pfd for new data notification, until at least a full entry
356 * could be read. We return TRUE if an entry could be read.
357 */
358 static bool
359 logentry_read(int pfd, struct log_entry *entry, int *fd, long *lognum,
360 off_t *logpos)
361 {
362 char filename[256], c;
363 int newfd;
364 bool ok, redo;
365 struct pollfd fds[] = {
366 {pfd, POLLIN, 0}
367 };
368
369 redo = TRUE;
370 while (redo) {
371 redo = FALSE;
372
373 ok = FALSE;
374 if (pfd == -1) {
375 if ((read(*fd, entry, sizeof(struct log_entry))) ==
376 sizeof(struct log_entry))
377 ok = TRUE;
378 } else {
379 for (;;) {
380 if ((poll(fds, 1, 5000)) > 0) {
381 if (fds[0].revents & POLLIN) {
382 if ((read(pfd, &c, 1)) == 1) {
383 if ((read(*fd, entry, sizeof(struct log_entry)))
384 == sizeof(struct log_entry))
385 ok = TRUE;
386 else
387 DPRINTF("logentry_read",
388 "read(%d, %d) partial read",
389 *fd, (int)sizeof(struct log_entry));
390 break;
391 }
392 }
393 }
394 }
395 }
396
397 if (ok) {
398 /* logentry_debug('|', entry); */
399 *logpos += sizeof(struct log_entry);
400 /* If required switch to next logfile */
401 if (entry->type == STAT_NEWFILE) {
402 snprintf(filename, 255, "%s/%08ld.log", CONF.ENV_DIR,
403 entry->un.newfile.lognum);
404 if ((newfd = open(filename, O_RDONLY)) != -1) {
405 close(*fd);
406 *fd = newfd;
407 *logpos = 0;
408 *lognum = entry->un.newfile.lognum;
409 redo = TRUE;
410 } else {
411 DPRINTF("logentry_read", "open(%s)", filename);
412 ok = FALSE;
413 }
414 }
415 }
416
417 }
418
419 return (ok);
420 }
421
422
423 /* This function attempts to read at least one entry, but all entries of a
424 * transaction if any is detected. If pfd is -1, we return FALSE if we
425 * cannot read an entry or the whole transaction, otherwise we wait for
426 * more data to be available using fdb with poll(). We only return TRUE
427 * if a single entry or whole transaction could be read.
428 * If STAT_NEWFILE entry is encoutered, auto rotation to the new logfile
429 * is performed and fd,lognum,logpos are updated via the provided pointers,
430 * transparently.
431 * The STAT_TRANSACT header and footer entries are never put in the buffer,
432 * this way their persistant flag is ignored.
433 * IMPORTANT: Because of the way this function works to prevent additionnal
434 * memory copy operations, the supplied buffer size should at least have one
435 * additionnal entry than specified maximum size.
436 */
437 static int
438 logentries_read(int pfd, struct log_entry *entries, int max, int *fd,
439 long *lognum, off_t *logpos)
440 {
441 struct log_entry *ptr;
442 int len;
443 bool transact;
444
445 transact = FALSE;
446 ptr = entries;
447 len = 0;
448
449 if (logentry_read(pfd, entries, fd, lognum, logpos)) {
450 if (entries->type == STAT_TRANSACT) {
451 if (entries->un.transact.begin) transact = TRUE;
452 else {
453 /* Mismatch */
454 len = 0;
455 DPRINTF("logentries_read", "Mismatched transaction start");
456 }
457 } else
458 len++;
459 }
460
461 /* If we fill the buffer of max entries or if we reach EOF before end of
462 * transaction marker can be found, we simply drop the whole transaction
463 * and return 0. That is where we need a read-ahead buffer, and require
464 * an additionnal entry.
465 */
466 while (transact) {
467 if (len > max) {
468 len = 0;
469 syslog(LOG_NOTICE, "logentries_read() - MAX_TRANSACT exceeded");
470 break;
471 }
472 if (!logentry_read(pfd, ptr, fd, lognum, logpos)) {
473 len = 1;
474 break;
475 } else {
476 if (ptr->type == STAT_TRANSACT) {
477 if (ptr->un.transact.begin) {
478 /* Mismatch */
479 len = 0;
480 DPRINTF("logentries_read", "Mismatched transaction end");
481 }
482 break;
483 } else {
484 ptr++;
485 len++;
486 }
487 }
488 }
489
490 return (len);
491 }
492
493
494 /* Apply requested log entry to the live db.
495 * STAT_NEWFILE or STAT_TRANSACT entries are never processed through this
496 * function.
497 */
498 static bool
499 logentry_process(struct log_entry *entry, bool tmp)
500 {
501 enum stat_type type;
502
503 /* logentry_debug(':', entry); */
504
505 type = entry->type;
506
507 if (tmp || entry->persistant) {
508 register size_t len;
509 register char *ptr;
510
511 /* Verify if we should perform wildcard matching and perform an atomic
512 * operation on all matching keys
513 */
514 for (ptr = entry->key; *ptr != '\0'; ptr++)
515 if (*ptr == '?' || *ptr == '*')
516 break;
517 len = mm_strlen(entry->key);
518
519 if (*ptr == '\0') { /* Operation on single key */
520 struct key_node *knod;
521
522 /* Locate corresponding key in table */
523 if ((knod = (struct key_node *)hashtable_lookup(&key_table,
524 entry->key, len + 1)) != NULL) {
525 /* Key exists, make sure that UID matches entry creator's or
526 * that operator was UID 0
527 */
528 if (knod->entry.uid != entry->uid && entry->uid != 0) {
529 syslog(LOG_NOTICE, "Unauthorized UID!");
530 return FALSE;
531 }
532 if (type == STAT_UPDATE)
533 knod->entry.value += entry->un.update.modifier;
534 else if(type == STAT_RESET)
535 knod->entry.value = entry->un.reset.value;
536 if (type == STAT_DELETE || (knod->entry.value == 0 &&
537 entry->autoflush)) {
538 /* Delete entry */
539 hashtable_unlink(&key_table, (hashnode_t *)knod);
540 pool_free((pnode_t *)knod);
541 } else
542 knod->entry.modified = entry->time;
543 } else {
544 /* Key does not exist */
545 if (type == STAT_DELETE)
546 return TRUE;
547 if (type == STAT_UPDATE || type == STAT_RESET) {
548 if (!(entry->autoflush && (type == STAT_UPDATE ?
549 entry->un.update.modifier :
550 entry->un.reset.value) == 0)) {
551 /* Create new entry */
552 if ((knod = (struct key_node *)pool_alloc(&key_pool,
553 FALSE)) != NULL) {
554 mm_memcpy(knod->entry.key, entry->key, len + 1);
555 knod->entry.value = (type == STAT_UPDATE ?
556 entry->un.update.modifier :
557 entry->un.reset.value);
558 knod->entry.uid = entry->uid;
559 knod->entry.created = knod->entry.modified =
560 entry->time;
561 knod->entry.persistant = entry->persistant;
562 knod->processed = FALSE;
563 hashtable_link(&key_table, (hashnode_t *)knod,
564 knod->entry.key, len + 1);
565 } else
566 return FALSE;
567 }
568 }
569 }
570
571 } else /* Operation on all keys matching pattern */
572 hashtable_iterate(&key_table, logentry_process_iterator, entry);
573
574 }
575
576 return TRUE;
577 }
578
579
580 static bool
581 logentry_process_iterator(hashnode_t *hnod, void *udata)
582 {
583 struct key_node *knod = (struct key_node *)hnod;
584 struct log_entry *entry = udata;
585
586 if ((knod->entry.uid == entry->uid || entry->uid == 0) &&
587 log_match(knod->entry.key, entry->key)) {
588 if (entry->type == STAT_RESET)
589 knod->entry.value = entry->un.reset.value;
590 else if (entry->type == STAT_UPDATE)
591 knod->entry.value += entry->un.update.modifier;
592 if (entry->type == STAT_DELETE || (knod->entry.value == 0 &&
593 entry->autoflush)) {
594 hashtable_unlink(&key_table, (hashnode_t *)knod);
595 pool_free((pnode_t *)knod);
596 } else
597 knod->entry.modified = entry->time;
598 }
599
600 return TRUE;
601 }
602
603
604 static bool
605 logentries_process(struct log_entry *entries, int len, bool tmp)
606 {
607 int i;
608 bool ok = TRUE;
609
610 for (i = 0; i < len; i++) {
611 if (!logentry_process(&entries[i], tmp)) {
612 ok = FALSE;
613 break;
614 }
615 }
616
617 return (ok);
618 }
619
620
621 /* Only used for debugging when testing/developing */
622 /*
623 static void
624 logentry_debug(char c, struct log_entry *entry)
625 {
626 switch (entry->type) {
627 case STAT_TRANSACT:
628 syslog(LOG_NOTICE, "%c STAT_TRANSACT u=%d p=%d a=%d beg=%d k=%s",
629 c, entry->uid, entry->persistant, entry->autoflush,
630 entry->un.transact.begin, entry->key);
631 break;
632 case STAT_NEWFILE:
633 syslog(LOG_NOTICE, "%c STAT_NEWFILE u=%d p=%d a=%d num=%ld k=%s",
634 c, entry->uid, entry->persistant, entry->autoflush,
635 entry->un.newfile.lognum, entry->key);
636 break;
637 case STAT_UPDATE:
638 syslog(LOG_NOTICE, "%c STAT_UPDATE u=%d p=%d a=%d mod=%lld k=%s",
639 c, entry->uid, entry->persistant, entry->autoflush,
640 entry->un.update.modifier, entry->key);
641 break;
642 case STAT_RESET:
643 syslog(LOG_NOTICE, "%c STAT_RESET u=%d p=%d a=%d val=%lld k=%s",
644 c, entry->uid, entry->persistant, entry->autoflush,
645 entry->un.reset.value, entry->key);
646 break;
647 case STAT_DELETE:
648 syslog(LOG_NOTICE, "%c STAT_DELETE u=%d p=%d a=%d k=%s",
649 c, entry->uid, entry->persistant, entry->autoflush,
650 entry->key);
651 break;
652 default:
653 syslog(LOG_NOTICE, "%c Unknown entry type!", c);
654 }
655 }
656 */
657
658
659 /* This function prepares the db lists, and attempts to load previously saved
660 * database and sync state. If part of the database cannot be loaded, we
661 * log an error via syslog but will either provide an empty or incomplete
662 * db in memory.
663 */
664 static void
665 db_load(long *lognum, off_t *logpos)
666 {
667 char filename[256];
668 FILE *fh;
669 bool ok;
670 u_int32_t version, ver;
671
672 ok = TRUE;
673 *lognum = 0;
674 *logpos = 0;
675
676 snprintf(filename, 255, "%s/mmstatd.db", CONF.ENV_DIR);
677 if (pool_init(&key_pool, malloc, free, sizeof(struct key_node),
678 32768 / sizeof(struct key_node), 0, 0)) {
679 if (hashtable_init(&key_table, HT_DEFAULT_CAPACITY, HT_DEFAULT_FACTOR,
680 malloc, free, mm_memcmp, hashtable_hash, TRUE)) {
681 if ((fh = fopen(filename, "rb"))) {
682 /* We now remember how to load old mmstatd database files,
683 * since at times the format changes accross versions.
684 * so first determine which version, and call the appropriate
685 * function.
686 */
687 if (fread(&ver, sizeof(u_int32_t), 1, fh) == 1) {
688 version = 0xFFFFFFFF - ver;
689 switch (version) {
690 case 2:
691 /* mmstatd 0.0.2, db v2 */
692 ok = db_load_v2(fh, lognum, logpos);
693 break;
694 case 3:
695 /* mmstatd 0.0.3 db v3 */
696 ok = db_load_v3(fh, lognum, logpos);
697 break;
698 case 4:
699 /* mmstatd 0.0.7 db v4 */
700 ok = db_load_v4(fh, lognum, logpos);
701 break;
702 default:
703 {
704 /* First version */
705 long o_lognum, o_logpos;
706
707 /* Seek back to start since there was no
708 * version info back then
709 */
710 fseek(fh, 0, SEEK_SET);
711 ok = db_load_v1(fh, &o_lognum, &o_logpos);
712 *lognum = o_lognum;
713 *logpos = (off_t)o_logpos;
714 }
715 break;
716 }
717 } else ok = FALSE;
718 fclose(fh);
719 if (!ok)
720 syslog(LOG_NOTICE,
721 "db_load() - Error loading database (corrupt)");
722 } else
723 syslog(LOG_NOTICE, "db_load() - New database");
724 } else
725 DPRINTF("db_load", "hashtable_init()");
726 } else
727 DPRINTF("db_load", "pool_init()");
728 }
729
730
731 static bool
732 db_load_v1(FILE *fh, long *lognum, long *logpos)
733 {
734 struct key_node *knod = NULL;
735 u_int64_t hash;
736 unsigned char len;
737 bool ok = TRUE;
738
739 if (fread(lognum, sizeof(long), 1, fh) != 1 ||
740 fread(logpos, sizeof(long), 1, fh) != 1)
741 ok = FALSE;
742 while (ok) {
743 if (fread(&hash, sizeof(u_int64_t), 1, fh) == 1) {
744 if ((knod = (struct key_node *)pool_alloc(&key_pool, FALSE))) {
745 knod->entry.persistant = TRUE;
746 knod->processed = FALSE;
747 if (fread(&knod->entry.value, sizeof(int64_t), 1, fh) != 1
748 || fread(&knod->entry.created, sizeof(time_t), 1,
749 fh) != 1
750 || fread(&knod->entry.modified, sizeof(time_t), 1,
751 fh) != 1
752 || fread(&knod->entry.uid, sizeof(uid_t), 1,
753 fh) != 1
754 || fread(&len, sizeof(unsigned char), 1, fh) != 1
755 || fread(&knod->entry.key, len + 1, 1, fh) != 1)
756 ok = FALSE;
757 else {
758 if (!hashtable_link(&key_table, (hashnode_t *)knod,
759 knod->entry.key, len + 1)) {
760 DPRINTF("db_load_v1", "Duplicate key '%s'",
761 knod->entry.key);
762 knod = (struct key_node *)pool_free((pnode_t *)knod);
763 }
764 }
765 } else
766 ok = FALSE;
767 if (!ok)
768 if (knod) pool_free((pnode_t *)knod);
769 } else
770 break;
771 }
772
773 return (ok);
774 }
775
776
777 static bool
778 db_load_v2(FILE *fh, long *lognum, off_t *logpos)
779 {
780 struct key_node *knod = NULL;
781 u_int64_t hash;
782 unsigned char len;
783 bool ok = TRUE;
784
785 if (fread(lognum, sizeof(long), 1, fh) != 1 ||
786 fread(logpos, sizeof(off_t), 1, fh) != 1)
787 ok = FALSE;
788 while (ok) {
789 if (fread(&hash, sizeof(u_int64_t), 1, fh) == 1) {
790 if ((knod = (struct key_node *)pool_alloc(&key_pool, FALSE))) {
791 knod->entry.persistant = TRUE;
792 knod->processed = FALSE;
793 if (fread(&knod->entry.value, sizeof(int64_t), 1, fh) != 1
794 || fread(&knod->entry.created, sizeof(time_t), 1,
795 fh) != 1
796 || fread(&knod->entry.modified, sizeof(time_t), 1,
797 fh) != 1
798 || fread(&knod->entry.uid, sizeof(uid_t), 1,
799 fh) != 1
800 || fread(&len, sizeof(unsigned char), 1, fh) != 1
801 || fread(&knod->entry.key, len + 1, 1, fh) != 1)
802 ok = FALSE;
803 else {
804 if (!hashtable_link(&key_table, (hashnode_t *)knod,
805 knod->entry.key, len + 1)) {
806 DPRINTF("db_load_v2", "Duplicate key '%s'",
807 knod->entry.key);
808 knod = (struct key_node *)pool_free((pnode_t *)knod);
809 }
810 }
811 } else
812 ok = FALSE;
813 if (!ok)
814 if (knod) pool_free((pnode_t *)knod);
815 } else
816 break;
817 }
818
819 return (ok);
820 }
821
822
823 static bool
824 db_load_v3(FILE *fh, long *lognum, off_t *logpos)
825 {
826 struct key_node *knod = NULL;
827 u_int64_t hash;
828 size_t len;
829 bool ok = TRUE;
830
831 if (fread(lognum, sizeof(long), 1, fh) != 1 ||
832 fread(logpos, sizeof(off_t), 1, fh) != 1)
833 ok = FALSE;
834 while (ok) {
835 if (fread(&hash, sizeof(u_int64_t), 1, fh) == 1) {
836 if ((knod = (struct key_node *)pool_alloc(&key_pool, FALSE))) {
837 knod->entry.persistant = TRUE;
838 knod->processed = FALSE;
839 if (fread(&knod->entry.value, sizeof(int64_t), 1, fh) != 1
840 || fread(&knod->entry.created, sizeof(time_t), 1,
841 fh) != 1
842 || fread(&knod->entry.modified, sizeof(time_t), 1,
843 fh) != 1
844 || fread(&knod->entry.uid, sizeof(uid_t), 1,
845 fh) != 1
846 || fread(&len, sizeof(size_t), 1, fh) != 1
847 || fread(&knod->entry.key, len + 1, 1, fh) != 1)
848 ok = FALSE;
849 else {
850 if (!hashtable_link(&key_table, (hashnode_t *)knod,
851 knod->entry.key, len + 1)) {
852 DPRINTF("db_load_v3", "Duplicate key '%s'",
853 knod->entry.key);
854 knod = (struct key_node *)pool_free((pnode_t *)knod);
855 }
856 }
857 } else
858 ok = FALSE;
859 if (!ok)
860 if (knod) pool_free((pnode_t *)knod);
861 } else
862 break;
863 }
864
865 return (ok);
866 }
867
868
869 static bool
870 db_load_v4(FILE *fh, long *lognum, off_t *logpos)
871 {
872 struct key_node *knod = NULL;
873 int64_t val;
874 size_t len;
875 bool ok = TRUE;
876
877 if (fread(lognum, sizeof(long), 1, fh) != 1 ||
878 fread(logpos, sizeof(off_t), 1, fh) != 1)
879 ok = FALSE;
880 while (ok) {
881 if (fread(&val, sizeof(int64_t), 1, fh) == 1) {
882 if ((knod = (struct key_node *)pool_alloc(&key_pool, FALSE))) {
883 knod->entry.persistant = TRUE;
884 knod->entry.value = val;
885 knod->processed = FALSE;
886 if (fread(&knod->entry.created, sizeof(time_t), 1, fh) != 1
887 || fread(&knod->entry.modified, sizeof(time_t), 1,
888 fh) != 1
889 || fread(&knod->entry.uid, sizeof(uid_t), 1,
890 fh) != 1
891 || fread(&len, sizeof(size_t), 1, fh) != 1
892 || fread(&knod->entry.key, len + 1, 1, fh) != 1)
893 ok = FALSE;
894 else {
895 if (!hashtable_link(&key_table, (hashnode_t *)knod,
896 knod->entry.key, len + 1)) {
897 DPRINTF("db_load_v4", "Duplicate key '%s'",
898 knod->entry.key);
899 knod = (struct key_node *)pool_free((pnode_t *)knod);
900 }
901 }
902 } else
903 ok = FALSE;
904 if (!ok)
905 if (knod) pool_free((pnode_t *)knod);
906 } else
907 break;
908 }
909
910 return (ok);
911 }
912
913
914 /* This function syncs the memory db to disk with current sync info.
915 * If save was successful, obsolete recovery logs are deleted.
916 * We warn through syslog if the sync could not be performed.
917 * If all is TRUE, all logs are deleted after sync.
918 */
919 /* XXX Make file format portable among various endian architectures */
920 static void
921 db_sync(long lognum, off_t logpos, bool all)
922 {
923 char old_db[256], new_db[256];
924 FILE *fh;
925 DIR *dir;
926 u_int32_t version = 0xFFFFFFFF - 4;
927 bool ok;
928 struct db_sync_iterator_udata data;
929
930 /* We use a technique which permits to retrieve the previous state of
931 * the db in case we could not save properly, using rename().
932 */
933 ok = TRUE;
934
935 snprintf(old_db, 255, "%s/mmstatd.db", CONF.ENV_DIR);
936 snprintf(new_db, 255, "%s/mmstatd.tdb", CONF.ENV_DIR);
937
938 /* Use stdio buffering since we are about to perform many small writes */
939 if ((fh = fopen(new_db, "wb"))) {
940 fchmod(fileno(fh), 0600);
941 /* Write db version and current sync state */
942 if (fwrite(&version, sizeof(u_int32_t), 1, fh) == 1 &&
943 fwrite(&lognum, sizeof(long), 1, fh) == 1 &&
944 fwrite(&logpos, sizeof(off_t), 1, fh) == 1) {
945 /* DB contents */
946 data.fh = fh;
947 data.ok = TRUE;
948 hashtable_iterate(&key_table, db_sync_iterator, &data);
949 ok = data.ok;
950 } else
951 ok = FALSE;
952
953 /* We always verified that fwrite(3) succeed, but we also must make
954 * sure that fsync(2) and close(2) do (the latter being called by
955 * fclose(3)).
956 */
957 if (ok) {
958 if (fflush(fh) != 0 || fsync(fileno(fh)) != 0)
959 ok = FALSE;
960 }
961 while (fclose(fh) != 0) {
962 if (errno != EINTR) {
963 ok = FALSE;
964 break;
965 }
966 }
967 } else
968 ok = FALSE;
969
970 if (ok) {
971 /* We are certain that the new file was properly written, we can now
972 * atomically replace the old database by the new one.
973 */
974 if (rename(new_db, old_db) == -1)
975 ok = FALSE;
976 }
977
978 if (!ok) {
979 unlink(new_db);
980 syslog(LOG_NOTICE, "db_sync() - Error writing database!");
981 } else {
982 /* Scan for recovery logs and unlink obsolete ones */
983 if ((dir = opendir(CONF.ENV_DIR))) {
984 char *name, filename[256];
985 struct dirent *dire;
986 long i;
987
988 while ((dire = readdir(dir))) {
989 name = dire->d_name;
990 if (log_match(name, "????????.log")) {
991 i = atol(name);
992 if (all || i < lognum) {
993 snprintf(filename, 255, "%s/%s", CONF.ENV_DIR, name);
994 unlink(filename);
995 }
996 }
997 }
998 closedir(dir);
999 }
1000 }
1001 }
1002
1003
1004 static bool
1005 db_sync_iterator(hashnode_t *hnod, void *udata)
1006 {
1007 struct key_node *knod = (struct key_node *)hnod;
1008 struct db_sync_iterator_udata *data = udata;
1009 FILE *fh = data->fh;
1010
1011 if (knod->entry.persistant) {
1012 size_t len;
1013
1014 len = mm_strlen(knod->entry.key);
1015 if (fwrite(&knod->entry.value, sizeof(int64_t), 1, fh) != 1 ||
1016 fwrite(&knod->entry.created, sizeof(time_t), 1, fh) != 1 ||
1017 fwrite(&knod->entry.modified, sizeof(time_t), 1, fh) != 1 ||
1018 fwrite(&knod->entry.uid, sizeof(uid_t), 1, fh) != 1 ||
1019 fwrite(&len, sizeof(size_t), 1, fh) != 1 ||
1020 fwrite(knod->entry.key, len + 1, 1, fh) != 1) {
1021 data->ok = FALSE;
1022 return FALSE;
1023 }
1024 }
1025
1026 return TRUE;
1027 }
1028
1029
1030 static void
1031 db_free(void)
1032 {
1033 if (HASHTABLE_VALID(&key_table))
1034 hashtable_destroy(&key_table, FALSE);
1035 if (POOL_VALID(&key_pool))
1036 pool_destroy(&key_pool);
1037 }
1038
1039
1040 /* This function loads the db, attempts to perform recovery processing the
1041 * logs, resyncs the db and unloads everything. This function is intended to
1042 * be executed when the logging daemon process is not running.
1043 */
1044 static void
1045 db_recover(void)
1046 {
1047 int fd;
1048 long lognum, len;
1049 off_t logpos;
1050 char filename[256];
1051 struct log_entry lentry[MAX_TRANSACT + 1];
1052 bool ok;
1053
1054 syslog(LOG_NOTICE, "Recovering last db modifications from logs");
1055
1056 /* First obtain our last position in the last logfile so that we do not
1057 * process logs which have already been applied to the db before last sync.
1058 */
1059 ok = FALSE;
1060 snprintf(filename, 255, "%s/%s", CONF.ENV_DIR, "mmstatd.db");
1061 db_load(&lognum, &logpos);
1062
1063 /* If any, start processing logs, but make sure to only start after
1064 * lognum/logpos, if there are any remaining, since we are only
1065 * performing recovery of unsynced pending logs.
1066 */
1067 snprintf(filename, 255, "%s/%08ld.log", CONF.ENV_DIR, lognum);
1068 if ((fd = open(filename, O_RDONLY)) != -1) {
1069 if (logpos > 0) lseek(fd, logpos, SEEK_SET);
1070 while ((len = logentries_read(-1, lentry, MAX_TRANSACT, &fd, &lognum,
1071 &logpos)))
1072 if (!logentries_process(lentry, len, FALSE)) {
1073 DPRINTF("db_recover", "logentries_process()");
1074 break;
1075 }
1076 close(fd);
1077 }
1078
1079 /* Sync back db to disk and delete obsolete recovery logs */
1080 lognum = 0;
1081 logpos = 0;
1082 db_sync(lognum, logpos, TRUE);
1083 db_free();
1084 }
1085
1086
1087 /* key char array should be KEY_SIZE bytes in size */
1088 static void
1089 stats_write(int fd, const char *key)
1090 {
1091 struct key_node *knod;
1092
1093 /* Sanity check */
1094 if (key[KEY_SIZE - 1] == '\0') {
1095 /* Verify if this consists of: '*' and/or '?' pattern, absolute
1096 * key name or full report request
1097 */
1098 if (*key == '\0') {
1099 /* Full statistics report request */
1100 hashtable_iterate(&key_table, stats_write_iterator_full, &fd);
1101 } else {
1102 const char *ptr;
1103
1104 for (ptr = key; *ptr != '\0'; ptr++)
1105 if (*ptr == '*' || *ptr == '?')
1106 break;
1107 if (*ptr != '\0') {
1108 struct stats_write_iterator_pattern_udata data;
1109
1110 /* Key pattern matching report request */
1111 data.fd = fd;
1112 data.pattern = key;
1113 hashtable_iterate(&key_table, stats_write_iterator_pattern,
1114 &data);
1115 } else {
1116 /* Absolute key report request */
1117 if ((knod = (struct key_node *)hashtable_lookup(&key_table,
1118 key, mm_strlen(key) + 1)) != NULL) {
1119 if (pipesend)
1120 write(fd, &knod->entry, sizeof(mmstatent_t));
1121 }
1122 }
1123 }
1124 }
1125 }
1126
1127
1128 static bool
1129 stats_write_iterator_full(hashnode_t *hnod, void *udata)
1130 {
1131 struct key_node *knod = (struct key_node *)hnod;
1132 int fd = *(int *)udata;
1133
1134 if (pipesend)
1135 write(fd, &knod->entry, sizeof(mmstatent_t));
1136
1137 return pipesend;
1138 }
1139
1140
1141 static bool
1142 stats_write_iterator_pattern(hashnode_t *hnod, void *udata)
1143 {
1144 struct key_node *knod = (struct key_node *)hnod;
1145 struct stats_write_iterator_pattern_udata *data = udata;
1146
1147 if (pipesend) {
1148 if (log_match(knod->entry.key, data->pattern))
1149 write(data->fd, &knod->entry, sizeof(mmstatent_t));
1150 }
1151
1152 return pipesend;
1153 }
1154
1155
1156 static void
1157 stats_rotate(const char *pattern, const char *prefix)
1158 {
1159 char okey[KEY_SIZE + 1], nkey[KEY_SIZE + 1];
1160
1161 /* Sanity check */
1162 if (pattern[KEY_SIZE - 1] == '\0' && prefix[KEY_SIZE - 1] == '\0') {
1163 struct stats_rotate_iterator_process_udata data;
1164
1165 /* Because modifying a key requires to unlink and relink it to the
1166 * table, and that the ordering of the iteration is undefined, we
1167 * have to make sure not to attempt to process the same entry more
1168 * than once, which could still be matching with the pattern.
1169 * We therefore use a boolean flag (processed) to prevent this.
1170 * Our iterator functions use it.
1171 */
1172 data.pattern = pattern;
1173 data.prefix = prefix;
1174 data.okey = okey;
1175 data.nkey = nkey;
1176 hashtable_iterate(&key_table, stats_rotate_iterator_process, &data);
1177 hashtable_iterate(&key_table, stats_rotate_iterator_clear, NULL);
1178 }
1179 }
1180
1181
1182 static bool
1183 stats_rotate_iterator_process(hashnode_t *hnod, void *udata)
1184 {
1185 struct key_node *knod = (struct key_node *)hnod;
1186 struct stats_rotate_iterator_process_udata *data = udata;
1187
1188 /* We make sure to not process already processed keys, and to restore the
1189 * old key if the renaming process causes a duplicate.
1190 */
1191 if (knod->processed == FALSE && knod->entry.persistant &&
1192 log_match(knod->entry.key, data->pattern)) {
1193 knod->processed = TRUE;
1194 mm_memcpy(data->okey, knod->entry.key, KEY_SIZE);
1195 hashtable_unlink(&key_table, (hashnode_t *)knod);
1196 snprintf(data->nkey, KEY_SIZE - 1, "%s%s", data->prefix,
1197 knod->entry.key);
1198 mm_memcpy(knod->entry.key, data->nkey, KEY_SIZE);
1199 if (!hashtable_link(&key_table, (hashnode_t *)knod, knod->entry.key,
1200 mm_strlen(data->nkey) + 1)) {
1201 /* Would cause a duplicate, restore entry */
1202 syslog(LOG_NOTICE, "stats_rotate() - Rename of key '%s' to '%s' \
1203 would cause a duplicate", data->okey, data->nkey);
1204 mm_memcpy(knod->entry.key, data->okey, KEY_SIZE);
1205 hashtable_link(&key_table, (hashnode_t *)knod, knod->entry.key,
1206 mm_strlen(data->okey));
1207 }
1208 }
1209
1210 return TRUE;
1211 }
1212
1213
1214 /* ARGSUSED */
1215 static bool
1216 stats_rotate_iterator_clear(hashnode_t *hnod, void *udata)
1217 {
1218 struct key_node *knod = (struct key_node *)hnod;
1219
1220 knod->processed = FALSE;
1221
1222 return TRUE;
1223 }
1224
1225
1226 int
1227 main(int argc, char **argv)
1228 {
1229 char *conf_file = "/etc/mmstatd.conf", *tmp;
1230 int ret = 0, ngids;
1231 uid_t uid;
1232 gid_t *gids;
1233 LONG facility;
1234 cres_t cres;
1235 carg_t *cargp;
1236 carg_t cargs[] = {
1237 {CAT_STR, CAF_NONE, 1, 31, "USER", CONF.USER},
1238 {CAT_STR, CAF_NONE, 1, 255, "GROUPS", CONF.GROUPS},
1239 {CAT_STR, CAF_NONE, 1, 31, "LOG_FACILITY", CONF.LOG_FACILITY},
1240 {CAT_STR, CAF_NONE, 1, 255, "PID_FILE", CONF.PID_FILE},
1241 {CAT_STR, CAF_NONE, 1, 255, "LOCK_FILE", CONF.LOCK_FILE},
1242 {CAT_STR, CAF_NONE, 1, 255, "LOG_SOCKET", CONF.LOG_SOCKET},
1243 {CAT_STR, CAF_NONE, 1, 255, "STAT_SOCKET", CONF.STAT_SOCKET},
1244 {CAT_STR, CAF_NONE, 1, 127, "ENV_DIR", CONF.ENV_DIR},
1245 {CAT_STR, CAF_NONE, 1, 31, "LOG_GROUP", CONF.LOG_GROUP},
1246 {CAT_STR, CAF_NONE, 1, 31, "STAT_GROUP", CONF.STAT_GROUP},
1247 {CAT_VAL, CAF_NONE, 1, 99999999, "SYNC_INTERVAL",
1248 &CONF.SYNC_INTERVAL},
1249 {CAT_VAL, CAF_NONE, 0, 99999999, "SYNC_BYTES", &CONF.SYNC_BYTES},
1250 {CAT_VAL, CAF_NONE, 1, 99999999, "MAX_LOGSIZE", &CONF.MAX_LOGSIZE},
1251 {CAT_VAL, CAF_NONE, 0, 9999, "STATS_RATE", &CONF.STATS_RATE},
1252 {CAT_VAL, CAF_NONE, 1, 9999, "STATS_TIME", &CONF.STATS_TIME},
1253 {CAT_END, CAF_NONE, 0, 0, NULL, NULL}
1254 };
1255 cmap_t cmap[] = {
1256 {"LOG_AUTH", LOG_AUTH},
1257 {"LOG_AUTHPRIV", LOG_AUTHPRIV},
1258 {"LOG_CRON", LOG_CRON},
1259 {"LOG_DAEMON", LOG_DAEMON},
1260 {"LOG_FTP", LOG_FTP},
1261 {"LOG_LPR", LOG_LPR},
1262 {"LOG_MAIL", LOG_MAIL},
1263 {"LOG_NEWS", LOG_NEWS},
1264 {"LOG_SYSLOG", LOG_SYSLOG},
1265 {"LOG_USER", LOG_USER},
1266 {"LOG_UUCP", LOG_UUCP},
1267 {NULL, 0}
1268 };
1269
1270 /* Set defaults */
1271 mm_strcpy(CONF.USER, "mmstatd");
1272 mm_strcpy(CONF.GROUPS, "mmstat,staff");
1273 mm_strcpy(CONF.LOG_FACILITY, "LOG_AUTHPRIV");
1274 mm_strcpy(CONF.PID_FILE, "/var/mmstatd/mmstatd.pid");
1275 mm_strcpy(CONF.LOCK_FILE, "/var/mmstatd/mmstatd.lock");
1276 mm_strcpy(CONF.LOG_SOCKET, "/var/mmstatd/mmstatd_log.sock");
1277 mm_strcpy(CONF.STAT_SOCKET, "/var/mmstatd/mmstatd_stat.sock");
1278 mm_strcpy(CONF.ENV_DIR, "/var/mmstatd");
1279 mm_strcpy(CONF.LOG_GROUP, "mmstat");
1280 mm_strcpy(CONF.STAT_GROUP, "staff");
1281 CONF.SYNC_INTERVAL = 1800;
1282 CONF.SYNC_BYTES = 4096;
1283 CONF.MAX_LOGSIZE = 1048576;
1284 CONF.STATS_RATE = 5;
1285 CONF.STATS_TIME = 10;
1286
1287 /* Read config file */
1288 if ((tmp = getenv("MMSTATCONF")))
1289 conf_file = tmp;
1290 if (argc == 2)
1291 conf_file = argv[1];
1292 if (!mmreadcfg(&cres, cargs, conf_file)) {
1293 /* Error parsing configuration file, report which */
1294 printf("\nError parsing '%s'\n", conf_file);
1295 printf("Error : %s\n", mmreadcfg_strerr(cres.CR_Err));
1296 if (*(cres.CR_Data)) printf("Data : %s\n", cres.CR_Data);
1297 if ((cargp = cres.CR_Keyword) != NULL) {
1298 printf("Keyword: %s\n", cargp->CA_Keyword);
1299 printf("Minimum: %ld\n", cargp->CA_Min);
1300 printf("Maximum: %ld\n", cargp->CA_Max);
1301 }
1302 if (cres.CR_Line != -1)
1303 printf("Line : %d\n", cres.CR_Line);
1304 printf("\n");
1305 exit(-1);
1306 }
1307
1308 if (!mmmapstring(cmap, CONF.LOG_FACILITY, &facility)) {
1309 printf("\nUnknown syslog facility %s\n\n", CONF.LOG_FACILITY);
1310 exit(-1);
1311 }
1312 openlog(DAEMON_NAME, LOG_PID | LOG_NDELAY, facility);
1313
1314 #ifndef NODROPPRIVS
1315 if ((getuid())) {
1316 printf("\nOnly the super user may start this daemon\n\n");
1317 syslog(LOG_NOTICE, "* Only superuser can start me");
1318 exit(-1);
1319 }
1320 #else /* NODROPPRIVS */
1321 if ((getuid()) == 0) {
1322 printf("\nCompiled with NODROPPRIVS, refusing to run as uid 0\n\n");
1323 syslog(LOG_NOTICE, "* NODROPPRIVS, refusing to run as uid 0");
1324 exit(-1);
1325 }
1326 #endif /* !NODROPPRIVS */
1327
1328 if (!lock_check(CONF.LOCK_FILE)) {
1329 printf("\nmmstatd already running\n\n");
1330 exit(-1);
1331 }
1332 /* Post parsing */
1333 if ((uid = mmgetuid(CONF.USER)) == -1) {
1334 printf("\nUnknown USER '%s'\n\n", CONF.USER);
1335 exit(-1);
1336 }
1337 if (!(gids = mmgetgidarray(&ngids, CONF.GROUPS))) {
1338 printf("\nOne or more of following GROUPS unknown: '%s'\n\n",
1339 CONF.GROUPS);
1340 exit(-1);
1341 }
1342 if ((CONF.log_group = mmgetgid(CONF.LOG_GROUP)) == -1) {
1343 printf("\nUnknown LOG_GROUP '%s'\n\n", CONF.LOG_GROUP);
1344 exit(-1);
1345 }
1346 if ((CONF.stat_group = mmgetgid(CONF.STAT_GROUP)) == -1) {
1347 printf("\nUnknown STAT_GROUP '%s'\n\n", CONF.STAT_GROUP);
1348 exit(-1);
1349 }
1350 CONF.max_logsize = (off_t)CONF.MAX_LOGSIZE;
1351
1352 /* Initialization */
1353 librarian_pid = logger_pid = -1;
1354 pipefds[0] = pipefds[1] = -1;
1355
1356 printf("\r\n+++ %s (%s)\r\n\r\n", DAEMON_NAME, DAEMON_VERSION);
1357
1358 /* Drop root privileges */
1359 if (mmdropprivs(uid, gids, ngids)) {
1360 mmfreegidarray(gids);
1361 /* Launch the librarian process */
1362 if ((librarian_pid = process_spawn(librarian_init, NULL, TRUE))
1363 == -1) {
1364 DPRINTF("main", "process_spawn(librarian)");
1365 ret = -1;
1366 }
1367 } else {
1368 ret = -1;
1369 DPRINTF("main", "mmdropprivs()");
1370 }
1371
1372 closelog();
1373
1374 return (ret);
1375 }
1376
1377
1378 static int
1379 librarian_init(void *args)
1380 {
1381 int fd;
1382
1383 syslog(LOG_NOTICE, "Librarian process started");
1384
1385 /* Write PID file */
1386 if ((fd = open(CONF.PID_FILE, O_CREAT | O_TRUNC | O_WRONLY, 0600)) != -1) {
1387 char str[16];
1388 snprintf(str, 15, "%d", getpid());
1389 write(fd, str, mm_strlen(str));
1390 close(fd);
1391 } else
1392 DPRINTF("librarian_init", "Cannot write pid file");
1393
1394 /* Perform recovery */
1395 db_recover();
1396
1397 /* Prepare our notification pipe */
1398 if (!(pipe(pipefds))) {
1399
1400 /* Start the logger process */
1401 if ((logger_pid = process_spawn(logger_init, NULL, FALSE)) != -1) {
1402 char filename[256];
1403 long lognum;
1404 off_t logpos;
1405 int ufd, lfd, max;
1406
1407 close(pipefds[1]);
1408 pipefds[1] = -1;
1409
1410 if ((ufd = unix_init(CONF.STAT_SOCKET, CONF.stat_group, 0660, 16,
1411 TRUE, TRUE)) != -1) {
1412 syslog(LOG_NOTICE, "Loading database");
1413 /* Because we recovered, lognum and logpos will be 0 */
1414 db_load(&lognum, &logpos);
1415 /* Make sure that we open for reading the file that the logger
1416 * opened for writing
1417 */
1418 snprintf(filename, 255, "%s/00000000.log", CONF.ENV_DIR);
1419 max = 10;
1420 while ((lfd = open(filename, O_RDONLY)) == -1 && max) {
1421 sleep(1);
1422 max--;
1423 }
1424 if (lfd != -1) {
1425 /* Finally start main loop */
1426 librarian_main(pipefds[0], ufd, lfd, &lognum, &logpos);
1427 close(lfd);
1428 } else
1429 DPRINTF("librarian_init", "open(%s)", filename);
1430 db_sync(lognum, logpos, FALSE);
1431 db_free();
1432 close(ufd);
1433 unlink(CONF.STAT_SOCKET);
1434 } else
1435 DPRINTF("librarian_init", "unix_init(%s)", CONF.STAT_SOCKET);
1436 } else
1437 DPRINTF("librarian_init", "process_spawn(logger)");
1438
1439 close(pipefds[0]);
1440 pipefds[0] = -1;
1441 } else
1442 DPRINTF("librarian_init", "pipe()");
1443
1444 unlink(CONF.PID_FILE);
1445
1446 /* Kill logger daemon */
1447 if (logger_pid != -1) {
1448 int status;
1449 if (!kill(logger_pid, SIGTERM)) waitpid(logger_pid, &status, 0);
1450 }
1451
1452 syslog(LOG_NOTICE, "Exiting librarian");
1453 return (0);
1454 }
1455
1456
1457 /* Here consists of the main librarian server process. It's function consists
1458 * in following the logs created by the logger process in an ASYNC manner,
1459 * and process them, managing the database. It also performs total SYNC of the
1460 * database to disk at fixed intervals, cleaning up obsolete recovery logs.
1461 */
1462 static void
1463 librarian_main(int pfd, int ufd, int lfd, long *lognum, off_t *logpos)
1464 {
1465 time_t otim, ttim;
1466 long secs, cnt;
1467 int len;
1468 struct log_entry entries[MAX_TRANSACT + 1];
1469 struct pollfd fds[] = {
1470 {pfd, POLLIN, 0},
1471 {ufd, POLLIN, 0},
1472 };
1473
1474 #ifndef __GLIBC__
1475 setproctitle("Librarian process");
1476 #endif
1477
1478 ttim = time(NULL);
1479 secs = cnt = 0;
1480 for (;;) {
1481 otim = time(NULL);
1482 if (poll(fds, 2, 60000) > 0) {
1483 /* Process more log entries if any */
1484 if (fds[0].revents & POLLIN) {
1485 if ((len = logentries_read(pfd, entries, MAX_TRANSACT, &lfd,
1486 lognum, logpos)))
1487 logentries_process(entries, len, TRUE);
1488 }
1489 /* Verify if we obtain a report request connection */
1490 if (fds[1].revents & POLLIN) {
1491 socklen_t addrl;
1492 struct sockaddr addr;
1493 int sfd;
1494 time_t t;
1495 char key[KEY_SIZE + 1], key2[KEY_SIZE + 1], c;
1496
1497 /* Accept connection and send status report */
1498 addrl = sizeof(struct sockaddr);
1499 pipesend = TRUE;
1500 if ((sfd = accept(ufd, &addr, &addrl)) != -1) {
1501 struct pollfd fds2[] = {
1502 {sfd, POLLIN, 0}
1503 };
1504
1505 /* Make sure to drop connection immediately if rate
1506 * was exceeded
1507 */
1508 if (CONF.STATS_RATE != 0) {
1509 if ((t = time(NULL)) > ttim + CONF.STATS_TIME) {
1510 ttim = t;
1511 cnt = 0;
1512 }
1513 }
1514 if (CONF.STATS_RATE == 0 || cnt < CONF.STATS_RATE) {
1515 cnt++;
1516 if (pipesend) write(sfd, "+", 1);
1517 if (poll(fds2, 1, 250) == 1) {
1518 if (fds2[0].revents & POLLIN) {
1519 if (read(sfd, &c, 1) == 1) {
1520 if (c == 's') {
1521 if (read(sfd, key, KEY_SIZE) ==
1522 KEY_SIZE) {
1523 shutdown(sfd, SHUT_RD);
1524 if (pipesend) stats_write(sfd, key);
1525 }
1526 } else if (c == 'r') {
1527 if (read(sfd, key, KEY_SIZE) ==
1528 KEY_SIZE &&
1529 read(sfd, key2, KEY_SIZE) ==
1530 KEY_SIZE) {
1531 stats_rotate(key, key2);
1532 /* Force immediate db sync */
1533 secs += CONF.SYNC_INTERVAL;
1534 }
1535 } else
1536 syslog(LOG_NOTICE,
1537 "librarian_main() - invalid req");
1538 } else
1539 DPRINTF("librarian_main", "read(%d)", sfd);
1540 }
1541 }
1542 } else if (pipesend) write(sfd, "-", 1);
1543 close(sfd);
1544 }
1545 }
1546 }
1547 /* Verify if it's time for a sync */
1548 secs += (time(NULL) - otim);
1549 if (secs > CONF.SYNC_INTERVAL) {
1550 secs = 0;
1551 db_sync(*lognum, *logpos, FALSE);
1552 }
1553 }
1554 }
1555
1556
1557 static int
1558 logger_init(void *args)
1559 {
1560 char filename[256];
1561 long lognum;
1562 off_t logpos;
1563 int ufd, lfd;
1564
1565 syslog(LOG_NOTICE, "Logger process started");
1566
1567 close(pipefds[0]);
1568 pipefds[0] = -1;
1569 lognum = 0;
1570 logpos = 0;
1571
1572 if ((ufd = unix_init(CONF.LOG_SOCKET, CONF.log_group, 0220, 0, FALSE,
1573 TRUE)) != -1) {
1574 snprintf(filename, 255, "%s/00000000.log", CONF.ENV_DIR);
1575 if ((lfd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0600)) != -1) {
1576 fsync(lfd);
1577 logger_main(pipefds[1], ufd, lfd, &lognum, &logpos);
1578 close(lfd);
1579 } else
1580 DPRINTF("logger_init", "open(%s)", filename);
1581 close(ufd);
1582 unlink(CONF.LOG_SOCKET);
1583 } else
1584 DPRINTF("logger_init", "unix_init(%s)", CONF.LOG_SOCKET);
1585 close(pipefds[1]);
1586 pipefds[1] = -1;
1587
1588 syslog(LOG_NOTICE, "Exiting logger");
1589 return (0);
1590 }
1591
1592
1593 /* Here consists of the logger process server, using unix domain sockets.
1594 * It's function is to obtain single as well as transaction locked requests
1595 * from the various processes, and to generate recovery logs and sync them
1596 * to disk as soon as possible. It is of major importance that this process
1597 * perform all required sanity checking on the input datagrams, thus preventing
1598 * packet attacks resulting in undefined behavior. We do not allow the client
1599 * to generate STAT_NEWFILE or STAT_TRANSACT control packets, and we ensure
1600 * that they only send valid packet lengths, particularly valid key string
1601 * as well. Morover, we make sure to set the euid of the datagram originator.
1602 * XXX BUG: There seems to be a pretty serious problem, I seem to misunderstand
1603 * the man pages about recvmsg(), cmsg, etc to obtain user credentials...
1604 * even the data seems to not be right, but the size is.
1605 */
1606 static void
1607 logger_main(int pfd, int ufd, int lfd, long *lognum, off_t *logpos)
1608 {
1609 uid_t uid, euid;
1610 int len, l, i, i2;
1611 enum stat_type t;
1612 char *tmp;
1613 struct log_entry aentries[MAX_TRANSACT + 2], *entries;
1614 struct pollfd fds[] = {
1615 {ufd, POLLIN, 0}
1616 };
1617
1618 #ifndef __GLIBC__
1619 setproctitle("Logger process");
1620 #endif
1621
1622 /* Prepare transaction header and footer entries, the footer will need
1623 * to be copied earlier in the buffer when the transaction packet is
1624 * smaller than MAX_TRANSACT, to allow using one write() only.
1625 */
1626 entries = &(aentries[1]);
1627 mm_memclr(aentries, sizeof(struct log_entry));
1628 aentries->type = STAT_TRANSACT;
1629 mm_memcpy(&(aentries[MAX_TRANSACT + 1]), aentries,
1630 sizeof(struct log_entry));
1631 aentries->un.transact.begin = TRUE;
1632
1633 for (;;) {
1634 if (poll(fds, 1, -1) > 0) {
1635 if (fds[0].revents & POLLIN) {
1636 /* New packet to log, may consist of a single operation or
1637 * of transaction-protected atomic operations array.
1638 */
1639 if ((len = recvfrom(ufd, entries,
1640 sizeof(struct log_entry) * MAX_TRANSACT,
1641 MSG_WAITALL, NULL, NULL)) > 0) {
1642 /* XXX Eventually obtain packet sender credentials */
1643 uid = euid = 0;
1644 /* Perform some sanity checking, first verify packet size */
1645 l = 0;
1646 i2 = 1;
1647 if ((len >= sizeof(struct log_entry)) &&
1648 (len <= sizeof(struct log_entry) * MAX_TRANSACT) &&
1649 ((len % sizeof(struct log_entry)) == 0)) {
1650 /* Now verify packet type and key C string validity
1651 * XXX Should eventually use cleaner code here
1652 */
1653 l = len / sizeof(struct log_entry);
1654 for (i2 = 0; i2 < l; i2++) {
1655 t = entries[i2].type;
1656 if (t != STAT_UPDATE && t != STAT_RESET &&
1657 t != STAT_DELETE)
1658 break;
1659 tmp = entries[i2].key;
1660 for (i = 0; i < KEY_SIZE; i++)
1661 if (tmp[i] == '\0' || tmp[i] < 33 ||
1662 tmp[i] == '%')
1663 break;
1664 if (i < 1 || i > KEY_SIZE - 1 ||
1665 (tmp[i] != '\0' &&
1666 (tmp[i] < 33 || tmp[i] == '%')))
1667 break;
1668 /* Make sure that packet originator uid cannot be
1669 * spoofed
1670 */
1671 entries[i2].uid = euid;
1672 }
1673 }
1674 if (i2 == l) {
1675 /* This packet at least won't crash us, it will
1676 * simply be ignored if invalid to this point.
1677 * Perform some magic before writing the entry if
1678 * it consists of a transaction.
1679 */
1680 if (len == sizeof(struct log_entry)) {
1681 if (!logentries_write(pfd, entries, 1, &lfd,
1682 lognum, logpos))
1683 syslog(LOG_NOTICE,
1684 "logger_main() - Error writing logs!");
1685 } else {
1686 t = len / sizeof(struct log_entry);
1687 if (t < MAX_TRANSACT)
1688 mm_memcpy(&entries[t],
1689 &aentries[MAX_TRANSACT + 1],
1690 sizeof(struct log_entry));
1691 t += 2;
1692 if (!logentries_write(pfd, aentries, t, &lfd,
1693 lognum, logpos))
1694 syslog(LOG_NOTICE,
1695 "logger_main() - Error writing logs!");
1696 }
1697 } else
1698 syslog(LOG_NOTICE,
1699 "Illegal packet from uid %d, euid %d, %d bytes",
1700 uid, euid, len);
1701 } else
1702 DPRINTF("logger_main", "recvfrom(%d)", ufd);
1703 }
1704 }
1705 }
1706 }