ChangeLog removed.
[bubblegum.git] / src / bubblegum.c
blob1a165baf927f8f518d97c502964df1500f5ad34f
1 /*
2 Copyright (C) 2002 Ben Kibbey <bjk@arbornet.org>
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 #include <stdio.h>
19 #include <stdarg.h>
20 #include <time.h>
21 #include <unistd.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <errno.h>
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <sys/wait.h>
28 #include <signal.h>
29 #include <syslog.h>
30 #include <sys/time.h>
31 #include <sys/resource.h>
32 #include <pwd.h>
33 #include <ctype.h>
35 #ifdef HAVE_CONFIG_H
36 #include <config.h>
37 #endif
39 #ifdef HAVE_ERR_H
40 #include <err.h>
41 #endif
43 #ifdef HAVE_LIMITS_H
44 #include <limits.h>
45 #endif
47 #ifdef HAVE_GETOPT_H
48 #include <getopt.h>
49 #endif
51 #ifdef HAVE_LIBGEN_H
52 #include <libgen.h>
53 #endif
55 #ifdef HAVE_LIBMD
56 #ifdef HAVE_MD5_H
57 #include <md5.h>
58 #endif
59 #endif
61 #include "common.h"
62 #include "bubblegum.h"
64 #ifdef WITH_DMALLOC
65 #include <dmalloc.h>
66 #endif
68 void logit(const char *format, ...)
70 FILE *logfp;
71 va_list ap;
72 char timebuf[MAX_TIME_LEN];
73 #ifdef HAVE_VASPRINTF
74 char *buf;
75 #else
76 char buf[LINE_MAX];
77 #endif
78 time_t now;
79 struct tm *mytm;
81 if (loglevel == 0 && usesyslog == 0)
82 return;
84 va_start(ap, format);
86 if (usesyslog) {
87 openlog(PACKAGE, LOG_PID, SYSLOG_FACILITY);
88 vsyslog(SYSLOG_PRIORITY, format, ap);
89 closelog();
91 va_end(ap);
92 return;
95 if (!loglevel) {
96 va_end(ap);
97 return;
100 if (!(logfp = fopen(logfile, "a"))) {
101 va_end(ap);
102 warn("%s", logfile);
103 return;
106 #ifdef HAVE_VASPRINTF
107 vasprintf(&buf, format, ap);
108 #else
109 vsnprintf(buf, sizeof(buf), format, ap);
110 #endif
111 va_end(ap);
113 time(&now);
114 mytm = localtime(&now);
115 strftime(timebuf, sizeof(timebuf), TIMEFORMAT, mytm);
117 fprintf(logfp, "%s %lu %s\n", timebuf, pid, buf);
119 fclose(logfp);
120 #ifdef HAVE_VASPRINTF
121 free(buf);
122 #endif
124 return;
127 #ifndef HAVE_ERR_H
128 void dumperr(int eval, int verb, const char *fmt, ...)
130 va_list ap;
131 char line[LINE_MAX];
133 va_start(ap, fmt);
134 vsnprintf(line, sizeof(line), fmt, ap);
135 va_end(ap);
137 if (!verb)
138 fprintf(stderr, "%s: %s\n", pn, line);
139 else
140 fprintf(stderr, "%s: %s: %s\n", pn, line, strerror(errno));
142 if (eval >= 0)
143 exit(eval);
145 return;
147 #endif
149 FILES *add_file(FILES *list, char *filename, struct stat st, int old)
151 FILES *new = list;
152 char *cp = filename;
153 char tmp[FILENAME_MAX];
155 #ifdef HAVE_LIBMD
156 if (TEST_BIT(check, MD5)) {
157 if (!(new->md5sum = MD5File(cp, NULL))) {
158 if (old)
159 logit("MD5File(): %s: %s", cp, strerror(errno));
160 else
161 warn("MD5File(): %s", cp);
164 #endif
166 cp = fullpath(wd, cp, tmp, sizeof(tmp));
167 new->filename = strdup(cp);
168 new->laststat = st;
169 new->next = Calloc(1, sizeof(FILES));
171 total++;
172 return new;
175 /* if a file in a filelist or on the command line is a directory and ends with
176 * a '/' then recurse the directory adding all files in it. won't work with
177 * access time checking because readdir() modifies the atime.
179 FILES *recurse_directory(FILES *list, char *filename, int old)
181 DIR *dp;
182 FILES *fptr = list;
183 struct dirent *ent;
184 struct stat st;
186 if (filename[strlen(filename) - 1] == '/')
187 filename[strlen(filename) - 1] = 0;
189 if (!(dp = opendir(filename))) {
190 if (old)
191 logit("%s: %s", filename, strerror(errno));
192 else
193 warn("%s", filename);
195 return fptr;
198 while ((ent = readdir(dp))) {
199 char *cp, tmp[FILENAME_MAX];
201 if ((ent->d_name[0] == '.' && ent->d_name[1] == 0) || (ent->d_name[0]
202 == '.' && ent->d_name[1] == '.' && ent->d_name[2] == 0))
203 continue;
205 cp = fullpath(filename, ent->d_name, tmp, sizeof(tmp));
207 if (STAT(cp, &st) == -1) {
208 if (old)
209 logit("%s: %s", cp, strerror(errno));
210 else
211 warn("%s", cp);
213 continue;
216 if (S_ISDIR(st.st_mode)) {
217 fptr = recurse_directory(fptr, cp, old);
218 stat(cp, &st);
221 fptr = add_file(fptr, cp, st, old);
222 fptr = fptr->next;
225 closedir(dp);
226 return fptr;
229 int isinteger(const char *str)
231 int i;
233 for (i = 0; i < strlen(str); i++) {
234 if (isdigit((unsigned char)str[i]) == 0)
235 return 1;
238 return 0;
241 void *Realloc(void *ptr, size_t size)
243 void *ptr2;
245 if (!(ptr2 = realloc(ptr, size))) {
246 logit("%s(%i) realloc(): %s", __FILE__, __LINE__, strerror(errno));
247 err(EXIT_FAILURE, "realloc()");
250 return ptr2;
253 void *Malloc(size_t size)
255 void *ptr;
257 if (!(ptr = malloc(size))) {
258 logit("%s(%i) malloc(): %s", __FILE__, __LINE__, strerror(errno));
259 err(EXIT_FAILURE, "malloc()");
262 return ptr;
265 void *Calloc(size_t number, size_t size)
267 void *ptr;
269 if (!(ptr = calloc(number, size))) {
270 logit("%s(%i) calloc(): %s", __FILE__, __LINE__, strerror(errno));
271 err(EXIT_FAILURE, "calloc()");
274 return ptr;
277 int pathlength(const char *wd, const char *filename)
279 if (filename[0] == '/')
280 return strlen(filename);
282 return (strlen(wd) + strlen(filename) + 1);
285 char **parseargv(char *str)
287 char **pptr, *s;
288 char arg[255];
289 int index = 0;
290 int quote = 0;
291 int lastchar = 0;
292 int i;
294 if (!str)
295 return NULL;
297 if (!(pptr = malloc(sizeof(char *))))
298 return NULL;
300 for (i = 0, s = str; *s; lastchar = *s++) {
301 if ((*s == '\"' || *s == '\'') && lastchar != '\\') {
302 quote = (quote) ? 0 : 1;
303 continue;
306 if (*s == ' ' && !quote) {
307 arg[i] = 0;
308 pptr = realloc(pptr, (index + 2) * sizeof(char *));
309 pptr[index++] = strdup(arg);
310 arg[0] = i = 0;
311 continue;
314 if ((i + 1) == sizeof(arg))
315 continue;
317 arg[i++] = *s;
320 arg[i] = 0;
322 if (arg[0]) {
323 pptr = realloc(pptr, (index + 2) * sizeof(char *));
324 pptr[index++] = strdup(arg);
327 pptr[index] = NULL;
328 return pptr;
331 char *changetype(unsigned bits)
333 char *str = NULL;
334 static char buf[255];
335 int bit;
337 buf[0] = 0;
339 for (bit = 1; bit < MAXTYPEBITS; bit++) {
340 if (TEST_BIT(bits, bit)) {
341 switch (bit) {
342 case IDEV:
343 str = IDEV_STR;
344 break;
345 case INODE:
346 str = INODE_STR;
347 break;
348 case PERMS:
349 str = PERMS_STR;
350 break;
351 case UID:
352 str = UID_STR;
353 break;
354 case GID:
355 str = GID_STR;
356 break;
357 case SIZE:
358 str = SIZE_STR;
359 break;
360 case HLINKS:
361 str = HLINKS_STR;
362 break;
363 default:
364 str = NULL;
365 break;
368 if (str) {
369 strncat(buf, str, sizeof(buf));
370 strncat(buf, ",", sizeof(buf));
375 if (buf[0] == 0)
376 return NULL;
378 buf[strlen(buf) - 1] = 0;
379 return buf;
382 int is_file(const char *filename)
384 struct stat st;
386 if (stat(filename, &st) == -1)
387 return -1;
389 if (!(st.st_mode & S_IFREG))
390 return 1;
392 return 0;
395 char *difftimestr(time_t compare, time_t basetime)
397 int diff = 0;
398 int day, hour, min, sec, i;
399 static char buf[32];
401 day = hour = min = sec = i = 0;
402 diff = compare - basetime;
404 day = diff / 86400;
405 i = diff % 86400;
407 hour = i / 3600;
408 i = i % 3600;
410 min = i / 60;
411 i = i % 60;
413 sec = i;
415 snprintf(buf, sizeof(buf), "%c:%i:%.2i:%.2i:%.2i", diffwhich, day, hour,
416 min, sec);
417 return buf;
420 char *fullpath(const char *wd, char *filename, char dest[], size_t size)
422 dest[0] = 0;
424 if (filename[0] == '/')
425 strncpy(dest, filename, size);
426 else {
427 strncpy(dest, wd, size);
428 strncat(dest, "/", size);
429 strncat(dest, filename, size);
432 return dest;
435 static char *itoa(int i)
437 static char buf[32];
439 snprintf(buf, sizeof(buf), "%i", i);
440 return strdup(buf);
443 char *changelist(unsigned changes)
445 static char buf[255];
446 int bit;
448 buf[0] = 0;
450 for (bit = 0; bit < MAXCHGBITS; bit++) {
451 if (TEST_BIT(changes, bit)) {
452 switch (bit) {
453 #ifdef HAVE_LIBMD
454 case MD5:
455 strncat(buf, MD5_STR, sizeof(buf));
456 break;
457 #endif
458 case ACCESS:
459 strncat(buf, ATIME_STR, sizeof(buf));
460 break;
461 case MODIFY:
462 strncat(buf, MTIME_STR, sizeof(buf));
463 break;
464 case CHANGE:
465 strncat(buf, CTIME_STR, sizeof(buf));
466 break;
467 case ERROR:
468 strncat(buf, ERROR_STR, sizeof(buf));
469 break;
470 default:
471 break;
474 strncat(buf, ",", sizeof(buf));
478 buf[strlen(buf) - 1] = 0;
479 return buf;
482 char *parsecmd(FILES * flist, char *line, time_t basetime,
483 char *diff, unsigned changes, unsigned runs, unsigned runtotal)
485 static char buf[sizeof(command)];
486 int i = 0;
488 buf[0] = 0;
490 if (line[0] == 0)
491 return NULL;
493 while (*line) {
494 struct tm *mytm;
495 char *s = NULL;
496 char timebuf[MAX_TIME_LEN];
497 int n, malloced = 0, bufsize;
498 time_t blah = 0;
500 if (*line == '%') {
501 switch (*++line) {
502 case 'i':
503 s = diff;
504 break;
505 case 'r':
506 bufsize = 255;
507 s = Calloc(1, bufsize);
508 malloced = 1;
509 snprintf(s, bufsize, "%i/%i", runs, runtotal);
510 break;
511 case 'f':
512 s = (char *) flist->filename;
513 break;
514 case 'F':
515 if (TEST_BIT(changes, ERROR)) {
516 s = NONE_STR;
517 break;
520 if (S_ISDIR(flist->laststat.st_mode))
521 s = DIR_STR;
522 else if (S_ISCHR(flist->laststat.st_mode))
523 s = CHR_STR;
524 else if (S_ISBLK(flist->laststat.st_mode))
525 s = BLK_STR;
526 else if (S_ISREG(flist->laststat.st_mode))
527 s = REG_STR;
528 else if (S_ISFIFO(flist->laststat.st_mode))
529 s = FIFO_STR;
530 else if (S_ISLNK(flist->laststat.st_mode))
531 s = LNK_STR;
532 else if (S_ISFIFO(flist->laststat.st_mode))
533 s = SOCK_STR;
534 else
535 s = "UNKNOWN";
536 break;
537 case 'm':
538 if (TEST_BIT(changes, ERROR)) {
539 s = NONE_STR;
540 break;
543 s = Calloc(1, 5);
544 malloced = 1;
546 snprintf(s, 5, "%.4o", flist->laststat.st_mode & ALLPERMS);
547 break;
548 case 't':
549 /* kinda hokey. use the latest change time. */
550 if (flist->laststat.st_atime > blah)
551 blah = flist->laststat.st_atime;
553 if (flist->laststat.st_mtime > blah)
554 blah = flist->laststat.st_mtime;
556 if (flist->laststat.st_ctime > blah)
557 blah = flist->laststat.st_ctime;
559 mytm = localtime(&blah);
561 strftime(timebuf, sizeof(timebuf), TIMEFORMAT, mytm);
562 timebuf[strlen(timebuf)] = '\0';
563 s = (char *) timebuf;
564 break;
565 case 's':
566 bufsize = LINE_MAX;
567 s = Calloc(1, bufsize);
568 malloced = 1;
569 snprintf(s, bufsize,
570 "niceness: %i, loglvl: %i, int: %lis, "
571 "files: %i", niceness, loglevel, interval, total);
572 break;
573 case 'd':
574 time(&blah);
575 mytm = localtime(&blah);
576 strftime(timebuf, sizeof(timebuf), TIMEFORMAT, mytm);
577 timebuf[strlen(timebuf)] = '\0';
579 s = (char *) timebuf;
580 break;
581 case 'b':
582 if (TEST_BIT(changes, ERROR)) {
583 s = NONE_STR;
584 break;
587 mytm = localtime(&basetime);
588 strftime(timebuf, sizeof(timebuf), TIMEFORMAT, mytm);
589 timebuf[strlen(timebuf)] = '\0';
591 s = (char *) timebuf;
592 break;
593 case 'c':
594 s = changelist(changes);
595 break;
596 case 'y':
597 s = changetype(flist->chgtype);
599 if (!s)
600 s = NONE_STR;
601 break;
602 case 'p':
603 s = itoa(pid);
604 malloced = 1;
605 break;
606 case 'e':
607 s = (flist->staterrno) ? strerror(flist->
608 staterrno) : NONE_STR;
609 break;
610 case '%':
611 s = "%";
612 break;
613 default:
614 break;
618 if (s) {
619 for (n = 0; n < strlen(s); n++) {
620 if (strlen(buf) + 1 == sizeof(buf)) {
621 buf[sizeof(buf)] = 0;
622 return buf;
625 buf[i++] = s[n];
628 if (malloced)
629 free(s);
631 s = NULL;
632 n = *line++;
633 continue;
636 if (i + 1 == sizeof(buf))
637 break;
639 buf[i++] = *line++;
642 buf[i] = 0;
643 return buf;
646 void freelist(FILES * head)
648 FILES *cur, *next;
650 for (cur = head; cur; cur = next) {
651 next = cur->next;
652 free(cur->filename);
654 #ifdef HAVE_LIBMD
655 if (TEST_BIT(check, MD5))
656 free(cur->md5sum);
657 #endif
659 free(cur);
662 return;
665 void ignoresig(int signal)
667 logit("signal %i: ignoring (existing signal action busy)", signal);
668 return;
671 void catchsig(int signal)
673 int status;
675 switch (signal) {
676 case SIGALRM:
677 logit("signal %i: forcing check", signal);
678 break;
679 case SIGTERM:
680 logit("signal %i: terminating", signal);
681 runlevel = QUIT;
682 break;
683 case SIGCHLD:
684 waitpid(-1, &status, WNOHANG);
685 break;
686 case SIGHUP:
687 if (filenamelist) {
688 logit("signal %i: reloading %s ...", signal, filenamelist);
689 runlevel = RELOAD;
691 else
692 logit("signal %i: no file specified (-f)", signal);
694 break;
695 case SIGUSR1:
696 logit("signal %i: resetting changed bits ...", signal);
697 runlevel = RESET;
698 break;
699 case SIGUSR2:
700 logit("signal %i: resetting changed bits and command runs ...",
701 signal);
702 runlevel = RESETALL;
703 break;
704 default:
705 logit("signal %i: ignoring", signal);
706 break;
709 return;
712 /*test to see if any bits are set */
713 int checkbits(unsigned bits)
715 int bit;
717 for (bit = 0; bit < MAXCHGBITS; bit++) {
718 if (TEST_BIT(bits, bit))
719 return 1;
722 return 0;
725 char *changelog(FILES * flist, time_t basetime, unsigned changes)
727 char *line = NULL, *type = NULL, *timediff;
728 int bit, error = 0, bufsize;
729 time_t diff = 0;
730 unsigned origchanged = flist->changed;
732 bufsize = LINE_MAX;
733 line = Calloc(1, bufsize);
735 for (bit = 0; bit < MAXCHGBITS; bit++) {
736 if (TEST_BIT(changes, bit) && TEST_BIT(check, bit)) {
737 switch (bit) {
738 #ifdef HAVE_LIBMD
739 case MD5:
740 strncat(line, MD5_STR ",", bufsize);
741 diff = flist->laststat.st_mtime;
742 break;
743 #endif
744 case ACCESS:
745 strncat(line, ATIME_STR ",", bufsize);
747 if (flist->laststat.st_atime > diff)
748 diff = flist->laststat.st_atime;
749 break;
750 case MODIFY:
751 strncat(line, MTIME_STR ",", bufsize);
753 if (flist->laststat.st_mtime > diff)
754 diff = flist->laststat.st_mtime;
755 break;
756 case CHANGE:
757 strncat(line, CTIME_STR ",", bufsize);
758 type = changetype(flist->chgtype);
760 if (flist->laststat.st_ctime > diff)
761 diff = flist->laststat.st_ctime;
762 break;
763 case ERROR:
764 strncat(line, ERROR_STR ",", bufsize);
765 error = 1;
766 break;
769 SET_BIT(flist->changed, bit);
773 line[strlen(line) - 1] = '\0';
775 if (type) {
776 strncat(line, " ", bufsize);
777 strncat(line, "(", bufsize);
778 strncat(line, type, bufsize);
779 strncat(line, ")", bufsize);
782 timediff = difftimestr(diff, basetime);
784 if (loglevel == 1) {
785 if (checkbits(origchanged))
786 return timediff;
789 logit("%s (%s) %s", line,
790 (error) ? strerror(flist->staterrno) : timediff, flist->filename);
792 free(line);
793 return (TEST_BIT(changes, ERROR)) ? NONE_STR : timediff;
796 void resetbittypes(FILES * flist)
798 RESET_BIT(flist->chgtype, IDEV);
799 RESET_BIT(flist->chgtype, SIZE);
800 RESET_BIT(flist->chgtype, INODE);
801 RESET_BIT(flist->chgtype, HLINKS);
802 RESET_BIT(flist->chgtype, UID);
803 RESET_BIT(flist->chgtype, PERMS);
804 RESET_BIT(flist->chgtype, GID);
806 return;
809 void resetchanged(FILES * flist)
811 #ifdef HAVE_LIBMD
812 RESET_BIT(flist->changed, MD5);
813 #endif
814 RESET_BIT(flist->changed, ACCESS);
815 RESET_BIT(flist->changed, MODIFY);
816 RESET_BIT(flist->changed, CHANGE);
817 RESET_BIT(flist->changed, ERROR);
819 return;
822 unsigned int
823 checkstat(FILES * flist, struct stat st, time_t * basetime,
824 unsigned changes)
826 unsigned change = changes;
828 #ifdef HAVE_LIBMD
829 char *sum;
830 #endif
832 #ifdef HAVE_LIBMD
833 if (TEST_BIT(check, MD5)) {
834 if ((sum = MD5File(flist->filename, NULL))) {
835 flist->md5errno = 0;
837 if (strcmp(sum, flist->md5sum) != 0) {
838 flist->md5sum = Realloc(flist->md5sum, strlen(sum) + 1);
839 strcpy(flist->md5sum, sum);
841 if (*basetime < flist->laststat.st_mtime) {
842 *basetime = flist->laststat.st_mtime;
843 diffwhich = 'M';
846 SET_BIT(change, MD5);
849 free(sum);
852 #endif
854 if (TEST_BIT(check, ACCESS) && st.st_atime != flist->laststat.st_atime) {
855 SET_BIT(change, ACCESS);
857 if (*basetime < flist->laststat.st_atime) {
858 *basetime = flist->laststat.st_atime;
859 diffwhich = 'A';
863 if (TEST_BIT(check, MODIFY) && st.st_mtime != flist->laststat.st_mtime) {
864 SET_BIT(change, MODIFY);
866 if (*basetime < flist->laststat.st_mtime) {
867 *basetime = flist->laststat.st_mtime;
868 diffwhich = 'M';
872 if (TEST_BIT(check, CHANGE) && st.st_ctime != flist->laststat.st_ctime) {
873 SET_BIT(change, CHANGE);
875 if (*basetime < flist->laststat.st_ctime) {
876 *basetime = flist->laststat.st_ctime;
877 diffwhich = 'C';
880 if (st.st_dev != flist->laststat.st_dev)
881 SET_BIT(flist->chgtype, IDEV);
883 if (st.st_ino != flist->laststat.st_ino)
884 SET_BIT(flist->chgtype, INODE);
886 if (st.st_nlink != flist->laststat.st_nlink)
887 SET_BIT(flist->chgtype, HLINKS);
889 if (st.st_uid != flist->laststat.st_uid)
890 SET_BIT(flist->chgtype, UID);
892 if (st.st_gid != flist->laststat.st_gid)
893 SET_BIT(flist->chgtype, GID);
895 if (st.st_size != flist->laststat.st_size)
896 SET_BIT(flist->chgtype, SIZE);
898 if (st.st_mode != flist->laststat.st_mode)
899 SET_BIT(flist->chgtype, PERMS);
902 return change;
905 void daemonstatus(unsigned cmdruns, unsigned runs, unsigned totalruns)
907 char *tr = itoa(totalruns);
908 char *r = itoa(runs);
909 char *cr = itoa(cmdruns);
911 logit("lvl: %i, int: %is, files: %i, fileruns: %s, totalruns: %s/%s",
912 loglevel, interval, total,
913 (command[0] && !totalruns) ? cr : "-",
914 (command[0]) ? r : "-", (command[0] && totalruns) ? tr : "-");
916 free(tr);
917 free(r);
918 free(cr);
920 return;
923 void
924 doit(FILES * fhead, unsigned cmdruns, unsigned totalruns, const char *user)
926 unsigned runs = 0;
928 signal(SIGUSR1, catchsig);
929 signal(SIGUSR2, catchsig);
930 signal(SIGTERM, catchsig);
931 signal(SIGALRM, catchsig);
932 signal(SIGHUP, catchsig);
933 signal(SIGCHLD, catchsig);
935 #ifdef HAVE_LIBMD
936 logit("%s %swatching %s%s%s%s%s", PACKAGE_STRING,
937 #else
938 logit("%s %swatching %s%s%s%s", PACKAGE_STRING,
939 #endif
940 (user) ? user : "",
941 (TEST_BIT(check, ACCESS)) ? ATIME_STR " " : "",
942 (TEST_BIT(check, MODIFY)) ? MTIME_STR " " : "",
943 (TEST_BIT(check, CHANGE)) ? CTIME_STR " " : "",
944 #ifdef HAVE_LIBMD
945 (TEST_BIT(check, MD5)) ? MD5_STR " " : "",
946 #endif
947 (TEST_BIT(check, ERROR)) ? ERROR_STR : "");
949 daemonstatus(cmdruns, runs, totalruns);
951 while (1) {
952 struct timeval tv;
953 FILES *fptr, *fcur;
955 /* check if a signal was received*/
956 if (runlevel != NORMAL) {
957 /* ignore other signals until finished*/
958 signal(SIGUSR1, ignoresig);
959 signal(SIGUSR2, ignoresig);
960 signal(SIGTERM, ignoresig);
961 signal(SIGALRM, ignoresig);
962 signal(SIGHUP, ignoresig);
964 switch (runlevel) {
965 case RELOAD:
966 if ((fcur = loadfile(filenamelist, fhead))) {
967 freelist(fhead);
968 fhead = fcur;
969 daemonstatus(cmdruns, runs, totalruns);
970 break;
973 break;
974 case RESETALL:
975 case RESET:
976 for (fcur = fhead; fcur; fcur = fptr) {
977 fptr = fcur->next;
978 resetchanged(fcur);
980 if (runlevel == RESETALL)
981 fcur->cmdruns = 0;
984 if (runlevel == RESETALL && totalruns)
985 runs = 0;
987 daemonstatus(cmdruns, runs, totalruns);
988 break;
989 case QUIT:
990 freelist(fhead);
992 if (filenamelist)
993 free(filenamelist);
995 if (unlink(pidfile) != 0)
996 logit("%s: %s", pidfile, strerror(errno));
998 free(logfile);
999 free(pidfile);
1000 free(wd);
1002 exit(EXIT_SUCCESS);
1003 break;
1004 default:
1005 break;
1008 runlevel = NORMAL;
1010 /* restore signal handler*/
1011 signal(SIGUSR1, catchsig);
1012 signal(SIGUSR2, catchsig);
1013 signal(SIGTERM, catchsig);
1014 signal(SIGALRM, catchsig);
1015 signal(SIGHUP, catchsig);
1018 fptr = fhead;
1020 while (fptr->next != NULL) {
1021 char **cargv;
1022 char *cmd = NULL, *diff;
1023 unsigned changes = 0;
1024 time_t basetime = 0;
1025 struct stat st;
1027 if (STAT(fptr->filename, &st) == -1) {
1028 /* error when stat()ing. if the last error is the same as the*/
1029 /* current one, skip this file*/
1030 if (fptr->staterrno == errno) {
1031 fptr = fptr->next;
1032 continue;
1035 if (TEST_BIT(check, ERROR)) {
1036 SET_BIT(changes, ERROR);
1037 fptr->staterrno = errno;
1040 else
1041 fptr->staterrno = 0;
1043 /* check the current stat() against the last stat()*/
1044 errno = 0;
1045 changes = checkstat(fptr, st, &basetime, changes);
1047 #ifdef HAVE_LIBMD
1048 /* MD5File() error*/
1049 if (errno && !TEST_BIT(changes, ERROR)) {
1050 if (fptr->md5errno != errno && TEST_BIT(check, ERROR)) {
1051 SET_BIT(changes, ERROR);
1052 SET_BIT(changes, MD5);
1053 fptr->md5errno = fptr->staterrno = errno;
1056 #endif
1058 if (checkbits(changes)) {
1059 fptr->laststat = st;
1060 diff = changelog(fptr, basetime, changes);
1062 /* run a command if specified*/
1063 if (command[0] && ((totalruns && runs < totalruns) ||
1064 (!cmdruns && !totalruns) ||
1065 (fptr->cmdruns < cmdruns && !totalruns))) {
1066 fptr->cmdruns++;
1067 runs++;
1069 cmd = parsecmd(fptr, command, basetime, diff,
1070 changes,
1071 (totalruns) ? runs : fptr->cmdruns,
1072 (totalruns) ? totalruns : cmdruns);
1074 switch (fork()) {
1075 case 0:
1076 signal(SIGUSR1, SIG_DFL);
1077 signal(SIGUSR2, SIG_DFL);
1078 signal(SIGTERM, SIG_DFL);
1079 signal(SIGALRM, SIG_DFL);
1080 signal(SIGHUP, SIG_DFL);
1081 signal(SIGCHLD, SIG_DFL);
1083 switch (fork()) {
1084 case 0:
1085 if (!(cargv = parseargv(cmd)))
1086 exit(EXIT_FAILURE);
1088 errno = EXIT_SUCCESS;
1090 if (execvp(cargv[0], cargv) == -1) {
1091 logit("%s: %s", cargv[0],
1092 strerror(errno));
1093 _exit(errno);
1096 _exit(EXIT_FAILURE);
1097 case -1:
1098 _exit(EXIT_FAILURE);
1099 default:
1100 _exit(EXIT_SUCCESS);
1102 case -1:
1103 logit("fork(): %s", strerror(errno));
1104 _exit(errno);
1105 default:
1106 logit("%i/%i: %s",
1107 (totalruns) ? runs : fptr->cmdruns,
1108 (totalruns) ? totalruns : cmdruns, cmd);
1109 break;
1113 /* get ready for next check*/
1114 resetbittypes(fptr);
1116 if (loglevel == 2)
1117 resetchanged(fptr);
1120 fptr = fptr->next;
1123 tv.tv_sec = interval;
1124 tv.tv_usec = 0;
1126 select(0, 0, 0, 0, &tv);
1129 return;
1132 static void usage(int error, const char *pn)
1134 fprintf(stdout, "Usage: %s [-hvksq] [-l <logfile>] [-#] "
1135 "[[-e <command>] [-r# | -t#]]\n\t[-n#] "
1136 #ifdef HAVE_LIBMD
1137 "[-i#] [-p pidfile] -MAamcE file [...] | -f <filelist>\n",
1138 pn);
1139 #else
1140 "[-i#] [-p pidfile] -AamcE file [...] | -f <filelist>\n",
1141 pn);
1142 #endif
1143 fprintf(stdout, " -a\tCheck the access time of the file.\n");
1144 fprintf(stdout, " -m\tCheck the modification time of the file.\n");
1145 fprintf(stdout, " -c\tCheck the inode change time.\n");
1146 #ifdef HAVE_LIBMD
1147 fprintf(stdout, " -M\tCheck the MD5 checksum of the file (-a not "
1148 "allowed).\n");
1149 #endif
1150 fprintf(stdout, " -E\tCheck for errors when stat()ing a file.\n");
1151 fprintf(stdout, " -A\tShortcut for -amcE.\n");
1152 fprintf(stdout, " -e\tCommand to run when a file changes (see below).\n");
1153 fprintf(stdout, " -r#\tNumber of times to run command per file "
1154 "(0=infinite). The default is %i.\n", CMDRUNS);
1155 fprintf(stdout,
1156 " -t#\tTotal number of times to run command (overrides "
1157 "-r).\n");
1158 fprintf(stdout,
1159 " -n#\tNiceness level (-20 through 20) of the daemon and "
1160 "command.\n");
1161 fprintf(stdout,
1162 " -i#\tInterval in seconds to check files. The default is %i.\n",
1163 INTERVAL);
1164 fprintf(stdout,
1165 " -f\tA file containing a list of filenames, one per line, "
1166 "to watch.\n");
1167 fprintf(stdout, " -k\tDon't follow symbolic links.\n");
1168 fprintf(stdout, " -l\tLog to an alternate file. Default is ~/%s.\n",
1169 LOGFILE);
1170 fprintf(stdout,
1171 " -#\tLog level: 0=none, 1=first change, 2=all changes. "
1172 "Default is %i.\n", LOGLEVEL);
1173 fprintf(stdout, " -p\tAlternate process id file. Default is ~/%s.\n",
1174 PIDFILE);
1175 fprintf(stdout, " -q\tTerminate daemon associated with the process id "
1176 "file.\n");
1177 fprintf(stdout,
1178 " -s\tDisable logging to file and log to " "syslogd(8).\n");
1179 fprintf(stdout, " -h\tThis help text.\n");
1180 fprintf(stdout, " -v\tVersion and copyright information.\n\n");
1181 fprintf(stdout, "The following expansions are available in a command "
1182 "(remember to quote!):\n");
1183 fprintf(stdout, " %%f - filename\t");
1184 fprintf(stdout, "\t%%t - time of (latest) change\t");
1185 fprintf(stdout, "%%b - last change time\n");
1186 fprintf(stdout, " %%e - error info\t");
1187 fprintf(stdout, "%%i - time difference\t\t");
1188 fprintf(stdout, "%%p - daemon PID\n");
1189 fprintf(stdout, " %%d - detection time\t");
1190 fprintf(stdout, "%%s - daemon status\t\t");
1191 fprintf(stdout, "%%r - run number/total\n");
1192 fprintf(stdout, " %%F - file type\t");
1193 fprintf(stdout, "%%m - file permission mode\t");
1194 fprintf(stdout, "%%%% - regular %%\n");
1195 #ifdef HAVE_LIBMD
1196 fprintf(stdout, " %%c - change(s): %s, %s, %s, %s, %s\n", ATIME_STR,
1197 MTIME_STR, CTIME_STR, MD5_STR, ERROR_STR);
1198 #else
1199 fprintf(stdout, " %%c - change(s): %s, %s, %s, %s\n", ATIME_STR,
1200 MTIME_STR, CTIME_STR, ERROR_STR);
1201 #endif
1202 fprintf(stdout, " %%y - change type(s): %s, %s, %s, %s, %s, %s, %s\n",
1203 IDEV_STR, INODE_STR, HLINKS_STR, UID_STR, GID_STR, PERMS_STR,
1204 SIZE_STR);
1206 exit((error) ? EXIT_FAILURE : EXIT_SUCCESS);
1209 int main(int argc, char *argv[])
1211 FILE *fp;
1212 int opt, bufsize = 0, quit = 0;
1213 unsigned cmdruns, totalruns;
1214 char user[16], *s;
1215 struct passwd *passwd;
1216 FILES *fhead, *fptr;
1218 #ifdef HAVE_BASENAME
1219 if (!(pn = basename(argv[0])))
1220 pn = argv[0];
1221 #else
1222 pn = argv[0];
1223 #endif
1225 /* need this for fullpath() because the daemon changes to '/' after fork()*/
1226 /* so filepaths would be wrong if relative (file reloading)*/
1227 if (!(wd = getcwd(NULL, PATH_MAX)))
1228 err(EXIT_FAILURE, "getcwd()");
1230 /* get current user password file info*/
1231 if (!(passwd = getpwuid(getuid())))
1232 errx(EXIT_FAILURE, "getpwuid()");
1234 /* where to log to*/
1235 if ((s = getenv("BUBBLEGUM_LOG"))) {
1236 bufsize = pathlength(wd, s) + 1;
1237 logfile = Malloc(bufsize);
1238 fullpath(wd, s, logfile, bufsize);
1240 else {
1241 bufsize = strlen(passwd->pw_dir) + strlen(LOGFILE) + 2;
1242 logfile = Malloc(bufsize);
1243 snprintf(logfile, bufsize, "%s/%s", passwd->pw_dir, LOGFILE);
1246 /* daemon process id file location*/
1247 if ((s = getenv("BUBBLEGUM_PID"))) {
1248 bufsize = pathlength(wd, s) + 1;
1249 pidfile = Malloc(bufsize);
1250 fullpath(wd, s, pidfile, bufsize);
1252 else {
1253 bufsize = strlen(passwd->pw_dir) + strlen(PIDFILE) + 2;
1254 pidfile = Malloc(bufsize);
1255 snprintf(pidfile, bufsize, "%s/%s", passwd->pw_dir, PIDFILE);
1258 /* set some defaults (from config.h)*/
1259 interval = INTERVAL;
1260 loglevel = LOGLEVEL;
1261 cmdruns = CMDRUNS;
1262 totalruns = 0;
1263 symlinks = 0;
1265 while ((opt = getopt(argc, argv, optstr)) != -1) {
1266 int res;
1267 char tmp[2];
1269 switch (opt) {
1270 case 'q':
1271 quit = 1;
1272 break;
1273 case 'p':
1274 bufsize = pathlength(wd, optarg) + 1;
1275 pidfile = Realloc(pidfile, bufsize);
1276 fullpath(wd, optarg, pidfile, bufsize);
1277 break;
1278 case 'k':
1279 symlinks = 1;
1280 break;
1281 case 'n':
1282 niceness = atoi(optarg);
1284 if (niceness > 20 || niceness < -20)
1285 usage(1, pn);
1286 break;
1287 case 's':
1288 usesyslog = 1;
1289 break;
1290 case 'e':
1291 if (optarg[0] == 0)
1292 usage(1, pn);
1294 strncpy(command, optarg, sizeof(command));
1295 break;
1296 case 'l':
1297 bufsize = pathlength(wd, optarg) + 1;
1298 logfile = Realloc(logfile, bufsize);
1299 fullpath(wd, optarg, logfile, bufsize);
1300 break;
1301 case '0':
1302 case '1':
1303 case '2':
1304 snprintf(tmp, sizeof(tmp), "%c", opt);
1305 loglevel = atoi(tmp);
1306 break;
1307 case 'f':
1308 if ((res = is_file(optarg)) != 0) {
1309 if (res < 0)
1310 err(EXIT_FAILURE, "%s", optarg);
1311 else
1312 errx(EXIT_FAILURE, "%s: Not a regular file",
1313 optarg);
1316 bufsize = pathlength(wd, optarg) + 1;
1317 filenamelist = Malloc(bufsize);
1318 fullpath(wd, optarg, filenamelist, bufsize);
1320 break;
1321 case 't':
1322 if (isinteger(optarg))
1323 usage(1, pn);
1325 totalruns = atoi(optarg);
1326 break;
1327 case 'r':
1328 if (isinteger(optarg))
1329 usage(1, pn);
1331 cmdruns = atoi(optarg);
1332 break;
1333 case 'i':
1334 if (isinteger(optarg))
1335 usage(1, pn);
1337 if ((interval = atol(optarg)) < 1)
1338 usage(1, pn);
1339 break;
1340 case 'h':
1341 usage(0, pn);
1342 break;
1343 case 'v':
1344 fprintf(stdout, "%s\n%s\n", PACKAGE_STRING, COPYRIGHT);
1345 exit(EXIT_SUCCESS);
1346 case 'A':
1347 SET_BIT(check, ACCESS);
1348 SET_BIT(check, CHANGE);
1349 SET_BIT(check, MODIFY);
1350 SET_BIT(check, ERROR);
1351 break;
1352 #ifdef HAVE_LIBMD
1353 case 'M':
1354 SET_BIT(check, MD5);
1355 break;
1356 #endif
1357 case 'a':
1358 SET_BIT(check, ACCESS);
1359 break;
1360 case 'c':
1361 SET_BIT(check, CHANGE);
1362 break;
1363 case 'm':
1364 SET_BIT(check, MODIFY);
1365 break;
1366 case 'E':
1367 SET_BIT(check, ERROR);
1368 break;
1369 default:
1370 usage(1, pn);
1371 break;
1375 if (!command[0] && !usesyslog && !loglevel && !quit)
1376 usage(1, pn);
1378 if ((argc == optind && !filenamelist) || !check) {
1379 if (!quit)
1380 usage(1, pn);
1383 if (argc != optind && filenamelist)
1384 usage(1, pn);
1386 #ifdef HAVE_LIBMD
1387 if (TEST_BIT(check, MD5) && TEST_BIT(check, ACCESS))
1388 errx(EXIT_FAILURE, "Cannot specify both MD5 and ATIME checking. "
1389 "See -h for help.");
1390 #endif
1392 /* see if a daemon is already running and/or quit if specified*/
1393 if ((fp = fopen(pidfile, "r"))) {
1394 if (fscanf(fp, "%lu", &pid) != 1) {
1395 fclose(fp);
1397 if (quit)
1398 errx(EXIT_FAILURE, "%s: Parse error", pidfile);
1400 goto cont;
1403 fclose(fp);
1405 if (kill(pid, 0) == 0) {
1406 if (quit) {
1407 if (kill(pid, SIGTERM) != 0)
1408 warn("kill()");
1410 else
1411 errx(EXIT_FAILURE, "Existing daemon already running (pid %u)",
1412 pid);
1414 else {
1415 if (quit)
1416 err(EXIT_FAILURE, "%u", pid);
1419 if (quit) {
1420 warnx("Terminated (pid %u)", pid);
1421 exit(EXIT_SUCCESS);
1424 else {
1425 if (quit)
1426 err(EXIT_FAILURE, "%s", pidfile);
1429 cont:
1430 pid = getpid();
1431 errno = 0;
1433 /* daemon and child process priority*/
1434 if ((niceness = getpriority(PRIO_PROCESS, pid)) == -1) {
1435 if (errno)
1436 err(EXIT_FAILURE, "getpriority()");
1439 if (setpriority(PRIO_PROCESS, pid, niceness) == -1)
1440 err(EXIT_FAILURE, "setpriority()");
1442 /* make sure we can log to the logging file if specified*/
1443 if (!usesyslog && loglevel) {
1444 if (!(fp = fopen(logfile, "a")))
1445 err(EXIT_FAILURE, "%s", logfile);
1447 fclose(fp);
1450 /* load a file list into the linked list (file.c)*/
1451 if (filenamelist) {
1452 if (!(fhead = loadfile(filenamelist, NULL)))
1453 exit(EXIT_FAILURE);
1455 else {
1456 /* files are from command line*/
1457 fhead = Calloc(1, sizeof(FILES));
1458 fptr = fhead;
1460 while (optind < argc) {
1461 struct stat st;
1462 char *cp = argv[optind++];
1464 if (STAT(cp, &st) == -1) {
1465 warn("%s", cp);
1466 continue;
1469 if (S_ISDIR(st.st_mode) && cp[strlen(cp) - 1] == '/') {
1470 fptr = recurse_directory(fptr, cp, 0);
1471 stat(cp, &st);
1474 fptr = add_file(fptr, cp, st, 0);
1475 fptr = fptr->next;
1478 fptr->next = NULL;
1481 if (!fhead->filename)
1482 errx(EXIT_FAILURE, "No files to watch");
1484 switch (fork()) {
1485 case 0:
1486 pid = getpid();
1488 /* write the daemon pid to the process ID file*/
1489 if (!(fp = fopen(pidfile, "w+")))
1490 warn("%s", pidfile);
1491 else {
1492 fprintf(fp, "%lu\n", pid);
1493 fclose(fp);
1496 if (chdir("/") == -1)
1497 err(EXIT_FAILURE, "/");
1499 if (setsid() == -1)
1500 err(EXIT_FAILURE, "setsid()");
1502 if (usesyslog)
1503 snprintf(user, sizeof(user), "(%s) ", passwd->pw_name);
1505 freopen("/dev/null", "r", stdin);
1506 freopen("/dev/null", "w", stdout);
1507 freopen("/dev/null", "w", stderr);
1509 /* call the main program loop*/
1510 doit(fhead, cmdruns, totalruns, (usesyslog) ? user : NULL);
1511 break;
1512 case -1:
1513 err(EXIT_FAILURE, "fork()");
1514 break;
1515 default:
1516 break;
1519 exit(EXIT_SUCCESS);