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