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
25 #include <sys/types.h>
31 #include <sys/resource.h>
62 #include "bubblegum.h"
68 void logit(const char *format
, ...)
72 char timebuf
[MAX_TIME_LEN
];
81 if (loglevel
== 0 && usesyslog
== 0)
87 openlog(PACKAGE
, LOG_PID
, SYSLOG_FACILITY
);
88 vsyslog(SYSLOG_PRIORITY
, format
, ap
);
100 if (!(logfp
= fopen(logfile
, "a"))) {
106 #ifdef HAVE_VASPRINTF
107 vasprintf(&buf
, format
, ap
);
109 vsnprintf(buf
, sizeof(buf
), format
, ap
);
114 mytm
= localtime(&now
);
115 strftime(timebuf
, sizeof(timebuf
), TIMEFORMAT
, mytm
);
117 fprintf(logfp
, "%s %lu %s\n", timebuf
, pid
, buf
);
120 #ifdef HAVE_VASPRINTF
128 void dumperr(int eval
, int verb
, const char *fmt
, ...)
134 vsnprintf(line
, sizeof(line
), fmt
, ap
);
138 fprintf(stderr
, "%s: %s\n", pn
, line
);
140 fprintf(stderr
, "%s: %s: %s\n", pn
, line
, strerror(errno
));
149 FILES
*add_file(FILES
*list
, char *filename
, struct stat st
, int old
)
153 char tmp
[FILENAME_MAX
];
156 if (TEST_BIT(check
, MD5
)) {
157 if (!(new->md5sum
= MD5File(cp
, NULL
))) {
159 logit("MD5File(): %s: %s", cp
, strerror(errno
));
161 warn("MD5File(): %s", cp
);
166 cp
= fullpath(wd
, cp
, tmp
, sizeof(tmp
));
167 new->filename
= strdup(cp
);
169 new->next
= Calloc(1, sizeof(FILES
));
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
)
186 if (filename
[strlen(filename
) - 1] == '/')
187 filename
[strlen(filename
) - 1] = 0;
189 if (!(dp
= opendir(filename
))) {
191 logit("%s: %s", filename
, strerror(errno
));
193 warn("%s", filename
);
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))
205 cp
= fullpath(filename
, ent
->d_name
, tmp
, sizeof(tmp
));
207 if (STAT(cp
, &st
) == -1) {
209 logit("%s: %s", cp
, strerror(errno
));
216 if (S_ISDIR(st
.st_mode
)) {
217 fptr
= recurse_directory(fptr
, cp
, old
);
221 fptr
= add_file(fptr
, cp
, st
, old
);
229 int isinteger(const char *str
)
233 for (i
= 0; i
< strlen(str
); i
++) {
234 if (isdigit((unsigned char)str
[i
]) == 0)
241 void *Realloc(void *ptr
, size_t size
)
245 if (!(ptr2
= realloc(ptr
, size
))) {
246 logit("%s(%i) realloc(): %s", __FILE__
, __LINE__
, strerror(errno
));
247 err(EXIT_FAILURE
, "realloc()");
253 void *Malloc(size_t size
)
257 if (!(ptr
= malloc(size
))) {
258 logit("%s(%i) malloc(): %s", __FILE__
, __LINE__
, strerror(errno
));
259 err(EXIT_FAILURE
, "malloc()");
265 void *Calloc(size_t number
, size_t size
)
269 if (!(ptr
= calloc(number
, size
))) {
270 logit("%s(%i) calloc(): %s", __FILE__
, __LINE__
, strerror(errno
));
271 err(EXIT_FAILURE
, "calloc()");
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
)
297 if (!(pptr
= malloc(sizeof(char *))))
300 for (i
= 0, s
= str
; *s
; lastchar
= *s
++) {
301 if ((*s
== '\"' || *s
== '\'') && lastchar
!= '\\') {
302 quote
= (quote
) ? 0 : 1;
306 if (*s
== ' ' && !quote
) {
308 pptr
= realloc(pptr
, (index
+ 2) * sizeof(char *));
309 pptr
[index
++] = strdup(arg
);
314 if ((i
+ 1) == sizeof(arg
))
323 pptr
= realloc(pptr
, (index
+ 2) * sizeof(char *));
324 pptr
[index
++] = strdup(arg
);
331 char *changetype(unsigned bits
)
334 static char buf
[255];
339 for (bit
= 1; bit
< MAXTYPEBITS
; bit
++) {
340 if (TEST_BIT(bits
, bit
)) {
369 strncat(buf
, str
, sizeof(buf
));
370 strncat(buf
, ",", sizeof(buf
));
378 buf
[strlen(buf
) - 1] = 0;
382 int is_file(const char *filename
)
386 if (stat(filename
, &st
) == -1)
389 if (!(st
.st_mode
& S_IFREG
))
395 char *difftimestr(time_t compare
, time_t basetime
)
398 int day
, hour
, min
, sec
, i
;
401 day
= hour
= min
= sec
= i
= 0;
402 diff
= compare
- basetime
;
415 snprintf(buf
, sizeof(buf
), "%c:%i:%.2i:%.2i:%.2i", diffwhich
, day
, hour
,
420 char *fullpath(const char *wd
, char *filename
, char dest
[], size_t size
)
424 if (filename
[0] == '/')
425 strncpy(dest
, filename
, size
);
427 strncpy(dest
, wd
, size
);
428 strncat(dest
, "/", size
);
429 strncat(dest
, filename
, size
);
435 static char *itoa(int i
)
439 snprintf(buf
, sizeof(buf
), "%i", i
);
443 char *changelist(unsigned changes
)
445 static char buf
[255];
450 for (bit
= 0; bit
< MAXCHGBITS
; bit
++) {
451 if (TEST_BIT(changes
, bit
)) {
455 strncat(buf
, MD5_STR
, sizeof(buf
));
459 strncat(buf
, ATIME_STR
, sizeof(buf
));
462 strncat(buf
, MTIME_STR
, sizeof(buf
));
465 strncat(buf
, CTIME_STR
, sizeof(buf
));
468 strncat(buf
, ERROR_STR
, sizeof(buf
));
474 strncat(buf
, ",", sizeof(buf
));
478 buf
[strlen(buf
) - 1] = 0;
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
)];
496 char timebuf
[MAX_TIME_LEN
];
497 int n
, malloced
= 0, bufsize
;
507 s
= Calloc(1, bufsize
);
509 snprintf(s
, bufsize
, "%i/%i", runs
, runtotal
);
512 s
= (char *) flist
->filename
;
515 if (TEST_BIT(changes
, ERROR
)) {
520 if (S_ISDIR(flist
->laststat
.st_mode
))
522 else if (S_ISCHR(flist
->laststat
.st_mode
))
524 else if (S_ISBLK(flist
->laststat
.st_mode
))
526 else if (S_ISREG(flist
->laststat
.st_mode
))
528 else if (S_ISFIFO(flist
->laststat
.st_mode
))
530 else if (S_ISLNK(flist
->laststat
.st_mode
))
532 else if (S_ISFIFO(flist
->laststat
.st_mode
))
538 if (TEST_BIT(changes
, ERROR
)) {
546 snprintf(s
, 5, "%.4o", flist
->laststat
.st_mode
& ALLPERMS
);
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
;
567 s
= Calloc(1, bufsize
);
570 "niceness: %i, loglvl: %i, int: %lis, "
571 "files: %i", niceness
, loglevel
, interval
, total
);
575 mytm
= localtime(&blah
);
576 strftime(timebuf
, sizeof(timebuf
), TIMEFORMAT
, mytm
);
577 timebuf
[strlen(timebuf
)] = '\0';
579 s
= (char *) timebuf
;
582 if (TEST_BIT(changes
, ERROR
)) {
587 mytm
= localtime(&basetime
);
588 strftime(timebuf
, sizeof(timebuf
), TIMEFORMAT
, mytm
);
589 timebuf
[strlen(timebuf
)] = '\0';
591 s
= (char *) timebuf
;
594 s
= changelist(changes
);
597 s
= changetype(flist
->chgtype
);
607 s
= (flist
->staterrno
) ? strerror(flist
->
608 staterrno
) : NONE_STR
;
619 for (n
= 0; n
< strlen(s
); n
++) {
620 if (strlen(buf
) + 1 == sizeof(buf
)) {
621 buf
[sizeof(buf
)] = 0;
636 if (i
+ 1 == sizeof(buf
))
646 void freelist(FILES
* head
)
650 for (cur
= head
; cur
; cur
= next
) {
655 if (TEST_BIT(check
, MD5
))
665 void ignoresig(int signal
)
667 logit("signal %i: ignoring (existing signal action busy)", signal
);
671 void catchsig(int signal
)
677 logit("signal %i: forcing check", signal
);
680 logit("signal %i: terminating", signal
);
684 waitpid(-1, &status
, WNOHANG
);
688 logit("signal %i: reloading %s ...", signal
, filenamelist
);
692 logit("signal %i: no file specified (-f)", signal
);
696 logit("signal %i: resetting changed bits ...", signal
);
700 logit("signal %i: resetting changed bits and command runs ...",
705 logit("signal %i: ignoring", signal
);
712 /*test to see if any bits are set */
713 int checkbits(unsigned bits
)
717 for (bit
= 0; bit
< MAXCHGBITS
; bit
++) {
718 if (TEST_BIT(bits
, bit
))
725 char *changelog(FILES
* flist
, time_t basetime
, unsigned changes
)
727 char *line
= NULL
, *type
= NULL
, *timediff
;
728 int bit
, error
= 0, bufsize
;
730 unsigned origchanged
= flist
->changed
;
733 line
= Calloc(1, bufsize
);
735 for (bit
= 0; bit
< MAXCHGBITS
; bit
++) {
736 if (TEST_BIT(changes
, bit
) && TEST_BIT(check
, bit
)) {
740 strncat(line
, MD5_STR
",", bufsize
);
741 diff
= flist
->laststat
.st_mtime
;
745 strncat(line
, ATIME_STR
",", bufsize
);
747 if (flist
->laststat
.st_atime
> diff
)
748 diff
= flist
->laststat
.st_atime
;
751 strncat(line
, MTIME_STR
",", bufsize
);
753 if (flist
->laststat
.st_mtime
> diff
)
754 diff
= flist
->laststat
.st_mtime
;
757 strncat(line
, CTIME_STR
",", bufsize
);
758 type
= changetype(flist
->chgtype
);
760 if (flist
->laststat
.st_ctime
> diff
)
761 diff
= flist
->laststat
.st_ctime
;
764 strncat(line
, ERROR_STR
",", bufsize
);
769 SET_BIT(flist
->changed
, bit
);
773 line
[strlen(line
) - 1] = '\0';
776 strncat(line
, " ", bufsize
);
777 strncat(line
, "(", bufsize
);
778 strncat(line
, type
, bufsize
);
779 strncat(line
, ")", bufsize
);
782 timediff
= difftimestr(diff
, basetime
);
785 if (checkbits(origchanged
))
789 logit("%s (%s) %s", line
,
790 (error
) ? strerror(flist
->staterrno
) : timediff
, flist
->filename
);
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
);
809 void resetchanged(FILES
* flist
)
812 RESET_BIT(flist
->changed
, MD5
);
814 RESET_BIT(flist
->changed
, ACCESS
);
815 RESET_BIT(flist
->changed
, MODIFY
);
816 RESET_BIT(flist
->changed
, CHANGE
);
817 RESET_BIT(flist
->changed
, ERROR
);
823 checkstat(FILES
* flist
, struct stat st
, time_t * basetime
,
826 unsigned change
= changes
;
833 if (TEST_BIT(check
, MD5
)) {
834 if ((sum
= MD5File(flist
->filename
, NULL
))) {
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
;
846 SET_BIT(change
, MD5
);
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
;
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
;
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
;
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
);
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
: "-");
924 doit(FILES
* fhead
, unsigned cmdruns
, unsigned totalruns
, const char *user
)
928 signal(SIGUSR1
, catchsig
);
929 signal(SIGUSR2
, catchsig
);
930 signal(SIGTERM
, catchsig
);
931 signal(SIGALRM
, catchsig
);
932 signal(SIGHUP
, catchsig
);
933 signal(SIGCHLD
, catchsig
);
936 logit("%s %swatching %s%s%s%s%s", PACKAGE_STRING
,
938 logit("%s %swatching %s%s%s%s", PACKAGE_STRING
,
941 (TEST_BIT(check
, ACCESS
)) ? ATIME_STR
" " : "",
942 (TEST_BIT(check
, MODIFY
)) ? MTIME_STR
" " : "",
943 (TEST_BIT(check
, CHANGE
)) ? CTIME_STR
" " : "",
945 (TEST_BIT(check
, MD5
)) ? MD5_STR
" " : "",
947 (TEST_BIT(check
, ERROR
)) ? ERROR_STR
: "");
949 daemonstatus(cmdruns
, runs
, totalruns
);
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
);
966 if ((fcur
= loadfile(filenamelist
, fhead
))) {
969 daemonstatus(cmdruns
, runs
, totalruns
);
976 for (fcur
= fhead
; fcur
; fcur
= fptr
) {
980 if (runlevel
== RESETALL
)
984 if (runlevel
== RESETALL
&& totalruns
)
987 daemonstatus(cmdruns
, runs
, totalruns
);
995 if (unlink(pidfile
) != 0)
996 logit("%s: %s", pidfile
, strerror(errno
));
1010 /* restore signal handler*/
1011 signal(SIGUSR1
, catchsig
);
1012 signal(SIGUSR2
, catchsig
);
1013 signal(SIGTERM
, catchsig
);
1014 signal(SIGALRM
, catchsig
);
1015 signal(SIGHUP
, catchsig
);
1020 while (fptr
->next
!= NULL
) {
1022 char *cmd
= NULL
, *diff
;
1023 unsigned changes
= 0;
1024 time_t basetime
= 0;
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
) {
1035 if (TEST_BIT(check
, ERROR
)) {
1036 SET_BIT(changes
, ERROR
);
1037 fptr
->staterrno
= errno
;
1041 fptr
->staterrno
= 0;
1043 /* check the current stat() against the last stat()*/
1045 changes
= checkstat(fptr
, st
, &basetime
, changes
);
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
;
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
))) {
1069 cmd
= parsecmd(fptr
, command
, basetime
, diff
,
1071 (totalruns
) ? runs
: fptr
->cmdruns
,
1072 (totalruns
) ? totalruns
: cmdruns
);
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
);
1085 if (!(cargv
= parseargv(cmd
)))
1088 errno
= EXIT_SUCCESS
;
1090 if (execvp(cargv
[0], cargv
) == -1) {
1091 logit("%s: %s", cargv
[0],
1096 _exit(EXIT_FAILURE
);
1098 _exit(EXIT_FAILURE
);
1100 _exit(EXIT_SUCCESS
);
1103 logit("fork(): %s", strerror(errno
));
1107 (totalruns
) ? runs
: fptr
->cmdruns
,
1108 (totalruns
) ? totalruns
: cmdruns
, cmd
);
1113 /* get ready for next check*/
1114 resetbittypes(fptr
);
1123 tv
.tv_sec
= interval
;
1126 select(0, 0, 0, 0, &tv
);
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#] "
1137 "[-i#] [-p pidfile] -MAamcE file [...] | -f <filelist>\n",
1140 "[-i#] [-p pidfile] -AamcE file [...] | -f <filelist>\n",
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");
1147 fprintf(stdout
, " -M\tCheck the MD5 checksum of the file (-a not "
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
);
1156 " -t#\tTotal number of times to run command (overrides "
1159 " -n#\tNiceness level (-20 through 20) of the daemon and "
1162 " -i#\tInterval in seconds to check files. The default is %i.\n",
1165 " -f\tA file containing a list of filenames, one per line, "
1167 fprintf(stdout
, " -k\tDon't follow symbolic links.\n");
1168 fprintf(stdout
, " -l\tLog to an alternate file. Default is ~/%s.\n",
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",
1175 fprintf(stdout
, " -q\tTerminate daemon associated with the process id "
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");
1196 fprintf(stdout
, " %%c - change(s): %s, %s, %s, %s, %s\n", ATIME_STR
,
1197 MTIME_STR
, CTIME_STR
, MD5_STR
, ERROR_STR
);
1199 fprintf(stdout
, " %%c - change(s): %s, %s, %s, %s\n", ATIME_STR
,
1200 MTIME_STR
, CTIME_STR
, ERROR_STR
);
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
,
1206 exit((error
) ? EXIT_FAILURE
: EXIT_SUCCESS
);
1209 int main(int argc
, char *argv
[])
1212 int opt
, bufsize
= 0, quit
= 0;
1213 unsigned cmdruns
, totalruns
;
1215 struct passwd
*passwd
;
1216 FILES
*fhead
, *fptr
;
1218 #ifdef HAVE_BASENAME
1219 if (!(pn
= basename(argv
[0])))
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
);
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
);
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
;
1265 while ((opt
= getopt(argc
, argv
, optstr
)) != -1) {
1274 bufsize
= pathlength(wd
, optarg
) + 1;
1275 pidfile
= Realloc(pidfile
, bufsize
);
1276 fullpath(wd
, optarg
, pidfile
, bufsize
);
1282 niceness
= atoi(optarg
);
1284 if (niceness
> 20 || niceness
< -20)
1294 strncpy(command
, optarg
, sizeof(command
));
1297 bufsize
= pathlength(wd
, optarg
) + 1;
1298 logfile
= Realloc(logfile
, bufsize
);
1299 fullpath(wd
, optarg
, logfile
, bufsize
);
1304 snprintf(tmp
, sizeof(tmp
), "%c", opt
);
1305 loglevel
= atoi(tmp
);
1308 if ((res
= is_file(optarg
)) != 0) {
1310 err(EXIT_FAILURE
, "%s", optarg
);
1312 errx(EXIT_FAILURE
, "%s: Not a regular file",
1316 bufsize
= pathlength(wd
, optarg
) + 1;
1317 filenamelist
= Malloc(bufsize
);
1318 fullpath(wd
, optarg
, filenamelist
, bufsize
);
1322 if (isinteger(optarg
))
1325 totalruns
= atoi(optarg
);
1328 if (isinteger(optarg
))
1331 cmdruns
= atoi(optarg
);
1334 if (isinteger(optarg
))
1337 if ((interval
= atol(optarg
)) < 1)
1344 fprintf(stdout
, "%s\n%s\n", PACKAGE_STRING
, COPYRIGHT
);
1347 SET_BIT(check
, ACCESS
);
1348 SET_BIT(check
, CHANGE
);
1349 SET_BIT(check
, MODIFY
);
1350 SET_BIT(check
, ERROR
);
1354 SET_BIT(check
, MD5
);
1358 SET_BIT(check
, ACCESS
);
1361 SET_BIT(check
, CHANGE
);
1364 SET_BIT(check
, MODIFY
);
1367 SET_BIT(check
, ERROR
);
1375 if (!command
[0] && !usesyslog
&& !loglevel
&& !quit
)
1378 if ((argc
== optind
&& !filenamelist
) || !check
) {
1383 if (argc
!= optind
&& filenamelist
)
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.");
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) {
1398 errx(EXIT_FAILURE
, "%s: Parse error", pidfile
);
1405 if (kill(pid
, 0) == 0) {
1407 if (kill(pid
, SIGTERM
) != 0)
1411 errx(EXIT_FAILURE
, "Existing daemon already running (pid %u)",
1416 err(EXIT_FAILURE
, "%u", pid
);
1420 warnx("Terminated (pid %u)", pid
);
1426 err(EXIT_FAILURE
, "%s", pidfile
);
1433 /* daemon and child process priority*/
1434 if ((niceness
= getpriority(PRIO_PROCESS
, pid
)) == -1) {
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
);
1450 /* load a file list into the linked list (file.c)*/
1452 if (!(fhead
= loadfile(filenamelist
, NULL
)))
1456 /* files are from command line*/
1457 fhead
= Calloc(1, sizeof(FILES
));
1460 while (optind
< argc
) {
1462 char *cp
= argv
[optind
++];
1464 if (STAT(cp
, &st
) == -1) {
1469 if (S_ISDIR(st
.st_mode
) && cp
[strlen(cp
) - 1] == '/') {
1470 fptr
= recurse_directory(fptr
, cp
, 0);
1474 fptr
= add_file(fptr
, cp
, st
, 0);
1481 if (!fhead
->filename
)
1482 errx(EXIT_FAILURE
, "No files to watch");
1488 /* write the daemon pid to the process ID file*/
1489 if (!(fp
= fopen(pidfile
, "w+")))
1490 warn("%s", pidfile
);
1492 fprintf(fp
, "%lu\n", pid
);
1496 if (chdir("/") == -1)
1497 err(EXIT_FAILURE
, "/");
1500 err(EXIT_FAILURE
, "setsid()");
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
);
1513 err(EXIT_FAILURE
, "fork()");