/* Copyright (C) 2002 Ben Kibbey This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_CONFIG_H #include #endif #ifdef HAVE_ERR_H #include #endif #ifdef HAVE_LIMITS_H #include #endif #ifdef HAVE_GETOPT_H #include #endif #ifdef HAVE_LIBGEN_H #include #endif #ifdef HAVE_LIBMD #ifdef HAVE_MD5_H #include #endif #endif #include "common.h" #include "bubblegum.h" #ifdef WITH_DMALLOC #include #endif void logit(const char *format, ...) { FILE *logfp; va_list ap; char timebuf[MAX_TIME_LEN]; #ifdef HAVE_VASPRINTF char *buf; #else char buf[LINE_MAX]; #endif time_t now; struct tm *mytm; if (loglevel == 0 && usesyslog == 0) return; va_start(ap, format); if (usesyslog) { openlog(PACKAGE, LOG_PID, SYSLOG_FACILITY); vsyslog(SYSLOG_PRIORITY, format, ap); closelog(); va_end(ap); return; } if (!loglevel) { va_end(ap); return; } if (!(logfp = fopen(logfile, "a"))) { va_end(ap); warn("%s", logfile); return; } #ifdef HAVE_VASPRINTF vasprintf(&buf, format, ap); #else vsnprintf(buf, sizeof(buf), format, ap); #endif va_end(ap); time(&now); mytm = localtime(&now); strftime(timebuf, sizeof(timebuf), TIMEFORMAT, mytm); fprintf(logfp, "%s %lu %s\n", timebuf, pid, buf); fclose(logfp); #ifdef HAVE_VASPRINTF free(buf); #endif return; } #ifndef HAVE_ERR_H void dumperr(int eval, int verb, const char *fmt, ...) { va_list ap; char line[LINE_MAX]; va_start(ap, fmt); vsnprintf(line, sizeof(line), fmt, ap); va_end(ap); if (!verb) fprintf(stderr, "%s: %s\n", pn, line); else fprintf(stderr, "%s: %s: %s\n", pn, line, strerror(errno)); if (eval >= 0) exit(eval); return; } #endif FILES *add_file(FILES *list, char *filename, struct stat st, int old) { FILES *new = list; char *cp = filename; char tmp[FILENAME_MAX]; #ifdef HAVE_LIBMD if (TEST_BIT(check, MD5)) { if (!(new->md5sum = MD5File(cp, NULL))) { if (old) logit("MD5File(): %s: %s", cp, strerror(errno)); else warn("MD5File(): %s", cp); } } #endif cp = fullpath(wd, cp, tmp, sizeof(tmp)); new->filename = strdup(cp); new->laststat = st; new->next = Calloc(1, sizeof(FILES)); total++; return new; } /* if a file in a filelist or on the command line is a directory and ends with * a '/' then recurse the directory adding all files in it. won't work with * access time checking because readdir() modifies the atime. */ FILES *recurse_directory(FILES *list, char *filename, int old) { DIR *dp; FILES *fptr = list; struct dirent *ent; struct stat st; if (filename[strlen(filename) - 1] == '/') filename[strlen(filename) - 1] = 0; if (!(dp = opendir(filename))) { if (old) logit("%s: %s", filename, strerror(errno)); else warn("%s", filename); return fptr; } while ((ent = readdir(dp))) { char *cp, tmp[FILENAME_MAX]; if ((ent->d_name[0] == '.' && ent->d_name[1] == 0) || (ent->d_name[0] == '.' && ent->d_name[1] == '.' && ent->d_name[2] == 0)) continue; cp = fullpath(filename, ent->d_name, tmp, sizeof(tmp)); if (STAT(cp, &st) == -1) { if (old) logit("%s: %s", cp, strerror(errno)); else warn("%s", cp); continue; } if (S_ISDIR(st.st_mode)) { fptr = recurse_directory(fptr, cp, old); stat(cp, &st); } fptr = add_file(fptr, cp, st, old); fptr = fptr->next; } closedir(dp); return fptr; } int isinteger(const char *str) { int i; for (i = 0; i < strlen(str); i++) { if (isdigit((unsigned char)str[i]) == 0) return 1; } return 0; } void *Realloc(void *ptr, size_t size) { void *ptr2; if (!(ptr2 = realloc(ptr, size))) { logit("%s(%i) realloc(): %s", __FILE__, __LINE__, strerror(errno)); err(EXIT_FAILURE, "realloc()"); } return ptr2; } void *Malloc(size_t size) { void *ptr; if (!(ptr = malloc(size))) { logit("%s(%i) malloc(): %s", __FILE__, __LINE__, strerror(errno)); err(EXIT_FAILURE, "malloc()"); } return ptr; } void *Calloc(size_t number, size_t size) { void *ptr; if (!(ptr = calloc(number, size))) { logit("%s(%i) calloc(): %s", __FILE__, __LINE__, strerror(errno)); err(EXIT_FAILURE, "calloc()"); } return ptr; } int pathlength(const char *wd, const char *filename) { if (filename[0] == '/') return strlen(filename); return (strlen(wd) + strlen(filename) + 1); } char **parseargv(char *str) { char **pptr, *s; char arg[255]; int index = 0; int quote = 0; int lastchar = 0; int i; if (!str) return NULL; if (!(pptr = malloc(sizeof(char *)))) return NULL; for (i = 0, s = str; *s; lastchar = *s++) { if ((*s == '\"' || *s == '\'') && lastchar != '\\') { quote = (quote) ? 0 : 1; continue; } if (*s == ' ' && !quote) { arg[i] = 0; pptr = realloc(pptr, (index + 2) * sizeof(char *)); pptr[index++] = strdup(arg); arg[0] = i = 0; continue; } if ((i + 1) == sizeof(arg)) continue; arg[i++] = *s; } arg[i] = 0; if (arg[0]) { pptr = realloc(pptr, (index + 2) * sizeof(char *)); pptr[index++] = strdup(arg); } pptr[index] = NULL; return pptr; } char *changetype(unsigned bits) { char *str = NULL; static char buf[255]; int bit; buf[0] = 0; for (bit = 1; bit < MAXTYPEBITS; bit++) { if (TEST_BIT(bits, bit)) { switch (bit) { case IDEV: str = IDEV_STR; break; case INODE: str = INODE_STR; break; case PERMS: str = PERMS_STR; break; case UID: str = UID_STR; break; case GID: str = GID_STR; break; case SIZE: str = SIZE_STR; break; case HLINKS: str = HLINKS_STR; break; default: str = NULL; break; } if (str) { strncat(buf, str, sizeof(buf)); strncat(buf, ",", sizeof(buf)); } } } if (buf[0] == 0) return NULL; buf[strlen(buf) - 1] = 0; return buf; } int is_file(const char *filename) { struct stat st; if (stat(filename, &st) == -1) return -1; if (!(st.st_mode & S_IFREG)) return 1; return 0; } char *difftimestr(time_t compare, time_t basetime) { int diff = 0; int day, hour, min, sec, i; static char buf[32]; day = hour = min = sec = i = 0; diff = compare - basetime; day = diff / 86400; i = diff % 86400; hour = i / 3600; i = i % 3600; min = i / 60; i = i % 60; sec = i; snprintf(buf, sizeof(buf), "%c:%i:%.2i:%.2i:%.2i", diffwhich, day, hour, min, sec); return buf; } char *fullpath(const char *wd, char *filename, char dest[], size_t size) { dest[0] = 0; if (filename[0] == '/') strncpy(dest, filename, size); else { strncpy(dest, wd, size); strncat(dest, "/", size); strncat(dest, filename, size); } return dest; } static char *itoa(int i) { static char buf[32]; snprintf(buf, sizeof(buf), "%i", i); return strdup(buf); } char *changelist(unsigned changes) { static char buf[255]; int bit; buf[0] = 0; for (bit = 0; bit < MAXCHGBITS; bit++) { if (TEST_BIT(changes, bit)) { switch (bit) { #ifdef HAVE_LIBMD case MD5: strncat(buf, MD5_STR, sizeof(buf)); break; #endif case ACCESS: strncat(buf, ATIME_STR, sizeof(buf)); break; case MODIFY: strncat(buf, MTIME_STR, sizeof(buf)); break; case CHANGE: strncat(buf, CTIME_STR, sizeof(buf)); break; case ERROR: strncat(buf, ERROR_STR, sizeof(buf)); break; default: break; } strncat(buf, ",", sizeof(buf)); } } buf[strlen(buf) - 1] = 0; return buf; } char *parsecmd(FILES * flist, char *line, time_t basetime, char *diff, unsigned changes, unsigned runs, unsigned runtotal) { static char buf[sizeof(command)]; int i = 0; buf[0] = 0; if (line[0] == 0) return NULL; while (*line) { struct tm *mytm; char *s = NULL; char timebuf[MAX_TIME_LEN]; int n, malloced = 0, bufsize; time_t blah = 0; if (*line == '%') { switch (*++line) { case 'i': s = diff; break; case 'r': bufsize = 255; s = Calloc(1, bufsize); malloced = 1; snprintf(s, bufsize, "%i/%i", runs, runtotal); break; case 'f': s = (char *) flist->filename; break; case 'F': if (TEST_BIT(changes, ERROR)) { s = NONE_STR; break; } if (S_ISDIR(flist->laststat.st_mode)) s = DIR_STR; else if (S_ISCHR(flist->laststat.st_mode)) s = CHR_STR; else if (S_ISBLK(flist->laststat.st_mode)) s = BLK_STR; else if (S_ISREG(flist->laststat.st_mode)) s = REG_STR; else if (S_ISFIFO(flist->laststat.st_mode)) s = FIFO_STR; else if (S_ISLNK(flist->laststat.st_mode)) s = LNK_STR; else if (S_ISFIFO(flist->laststat.st_mode)) s = SOCK_STR; else s = "UNKNOWN"; break; case 'm': if (TEST_BIT(changes, ERROR)) { s = NONE_STR; break; } s = Calloc(1, 5); malloced = 1; snprintf(s, 5, "%.4o", flist->laststat.st_mode & ALLPERMS); break; case 't': /* kinda hokey. use the latest change time. */ if (flist->laststat.st_atime > blah) blah = flist->laststat.st_atime; if (flist->laststat.st_mtime > blah) blah = flist->laststat.st_mtime; if (flist->laststat.st_ctime > blah) blah = flist->laststat.st_ctime; mytm = localtime(&blah); strftime(timebuf, sizeof(timebuf), TIMEFORMAT, mytm); timebuf[strlen(timebuf)] = '\0'; s = (char *) timebuf; break; case 's': bufsize = LINE_MAX; s = Calloc(1, bufsize); malloced = 1; snprintf(s, bufsize, "niceness: %i, loglvl: %i, int: %lis, " "files: %i", niceness, loglevel, interval, total); break; case 'd': time(&blah); mytm = localtime(&blah); strftime(timebuf, sizeof(timebuf), TIMEFORMAT, mytm); timebuf[strlen(timebuf)] = '\0'; s = (char *) timebuf; break; case 'b': if (TEST_BIT(changes, ERROR)) { s = NONE_STR; break; } mytm = localtime(&basetime); strftime(timebuf, sizeof(timebuf), TIMEFORMAT, mytm); timebuf[strlen(timebuf)] = '\0'; s = (char *) timebuf; break; case 'c': s = changelist(changes); break; case 'y': s = changetype(flist->chgtype); if (!s) s = NONE_STR; break; case 'p': s = itoa(pid); malloced = 1; break; case 'e': s = (flist->staterrno) ? strerror(flist-> staterrno) : NONE_STR; break; case '%': s = "%"; break; default: break; } } if (s) { for (n = 0; n < strlen(s); n++) { if (strlen(buf) + 1 == sizeof(buf)) { buf[sizeof(buf)] = 0; return buf; } buf[i++] = s[n]; } if (malloced) free(s); s = NULL; n = *line++; continue; } if (i + 1 == sizeof(buf)) break; buf[i++] = *line++; } buf[i] = 0; return buf; } void freelist(FILES * head) { FILES *cur, *next; for (cur = head; cur; cur = next) { next = cur->next; free(cur->filename); #ifdef HAVE_LIBMD if (TEST_BIT(check, MD5)) free(cur->md5sum); #endif free(cur); } return; } void ignoresig(int signal) { logit("signal %i: ignoring (existing signal action busy)", signal); return; } void catchsig(int signal) { int status; switch (signal) { case SIGALRM: logit("signal %i: forcing check", signal); break; case SIGTERM: logit("signal %i: terminating", signal); runlevel = QUIT; break; case SIGCHLD: waitpid(-1, &status, WNOHANG); break; case SIGHUP: if (filenamelist) { logit("signal %i: reloading %s ...", signal, filenamelist); runlevel = RELOAD; } else logit("signal %i: no file specified (-f)", signal); break; case SIGUSR1: logit("signal %i: resetting changed bits ...", signal); runlevel = RESET; break; case SIGUSR2: logit("signal %i: resetting changed bits and command runs ...", signal); runlevel = RESETALL; break; default: logit("signal %i: ignoring", signal); break; } return; } /*test to see if any bits are set */ int checkbits(unsigned bits) { int bit; for (bit = 0; bit < MAXCHGBITS; bit++) { if (TEST_BIT(bits, bit)) return 1; } return 0; } char *changelog(FILES * flist, time_t basetime, unsigned changes) { char *line = NULL, *type = NULL, *timediff; int bit, error = 0, bufsize; time_t diff = 0; unsigned origchanged = flist->changed; bufsize = LINE_MAX; line = Calloc(1, bufsize); for (bit = 0; bit < MAXCHGBITS; bit++) { if (TEST_BIT(changes, bit) && TEST_BIT(check, bit)) { switch (bit) { #ifdef HAVE_LIBMD case MD5: strncat(line, MD5_STR ",", bufsize); diff = flist->laststat.st_mtime; break; #endif case ACCESS: strncat(line, ATIME_STR ",", bufsize); if (flist->laststat.st_atime > diff) diff = flist->laststat.st_atime; break; case MODIFY: strncat(line, MTIME_STR ",", bufsize); if (flist->laststat.st_mtime > diff) diff = flist->laststat.st_mtime; break; case CHANGE: strncat(line, CTIME_STR ",", bufsize); type = changetype(flist->chgtype); if (flist->laststat.st_ctime > diff) diff = flist->laststat.st_ctime; break; case ERROR: strncat(line, ERROR_STR ",", bufsize); error = 1; break; } SET_BIT(flist->changed, bit); } } line[strlen(line) - 1] = '\0'; if (type) { strncat(line, " ", bufsize); strncat(line, "(", bufsize); strncat(line, type, bufsize); strncat(line, ")", bufsize); } timediff = difftimestr(diff, basetime); if (loglevel == 1) { if (checkbits(origchanged)) return timediff; } logit("%s (%s) %s", line, (error) ? strerror(flist->staterrno) : timediff, flist->filename); free(line); return (TEST_BIT(changes, ERROR)) ? NONE_STR : timediff; } void resetbittypes(FILES * flist) { RESET_BIT(flist->chgtype, IDEV); RESET_BIT(flist->chgtype, SIZE); RESET_BIT(flist->chgtype, INODE); RESET_BIT(flist->chgtype, HLINKS); RESET_BIT(flist->chgtype, UID); RESET_BIT(flist->chgtype, PERMS); RESET_BIT(flist->chgtype, GID); return; } void resetchanged(FILES * flist) { #ifdef HAVE_LIBMD RESET_BIT(flist->changed, MD5); #endif RESET_BIT(flist->changed, ACCESS); RESET_BIT(flist->changed, MODIFY); RESET_BIT(flist->changed, CHANGE); RESET_BIT(flist->changed, ERROR); return; } unsigned int checkstat(FILES * flist, struct stat st, time_t * basetime, unsigned changes) { unsigned change = changes; #ifdef HAVE_LIBMD char *sum; #endif #ifdef HAVE_LIBMD if (TEST_BIT(check, MD5)) { if ((sum = MD5File(flist->filename, NULL))) { flist->md5errno = 0; if (strcmp(sum, flist->md5sum) != 0) { flist->md5sum = Realloc(flist->md5sum, strlen(sum) + 1); strcpy(flist->md5sum, sum); if (*basetime < flist->laststat.st_mtime) { *basetime = flist->laststat.st_mtime; diffwhich = 'M'; } SET_BIT(change, MD5); } free(sum); } } #endif if (TEST_BIT(check, ACCESS) && st.st_atime != flist->laststat.st_atime) { SET_BIT(change, ACCESS); if (*basetime < flist->laststat.st_atime) { *basetime = flist->laststat.st_atime; diffwhich = 'A'; } } if (TEST_BIT(check, MODIFY) && st.st_mtime != flist->laststat.st_mtime) { SET_BIT(change, MODIFY); if (*basetime < flist->laststat.st_mtime) { *basetime = flist->laststat.st_mtime; diffwhich = 'M'; } } if (TEST_BIT(check, CHANGE) && st.st_ctime != flist->laststat.st_ctime) { SET_BIT(change, CHANGE); if (*basetime < flist->laststat.st_ctime) { *basetime = flist->laststat.st_ctime; diffwhich = 'C'; } if (st.st_dev != flist->laststat.st_dev) SET_BIT(flist->chgtype, IDEV); if (st.st_ino != flist->laststat.st_ino) SET_BIT(flist->chgtype, INODE); if (st.st_nlink != flist->laststat.st_nlink) SET_BIT(flist->chgtype, HLINKS); if (st.st_uid != flist->laststat.st_uid) SET_BIT(flist->chgtype, UID); if (st.st_gid != flist->laststat.st_gid) SET_BIT(flist->chgtype, GID); if (st.st_size != flist->laststat.st_size) SET_BIT(flist->chgtype, SIZE); if (st.st_mode != flist->laststat.st_mode) SET_BIT(flist->chgtype, PERMS); } return change; } void daemonstatus(unsigned cmdruns, unsigned runs, unsigned totalruns) { char *tr = itoa(totalruns); char *r = itoa(runs); char *cr = itoa(cmdruns); logit("lvl: %i, int: %is, files: %i, fileruns: %s, totalruns: %s/%s", loglevel, interval, total, (command[0] && !totalruns) ? cr : "-", (command[0]) ? r : "-", (command[0] && totalruns) ? tr : "-"); free(tr); free(r); free(cr); return; } void doit(FILES * fhead, unsigned cmdruns, unsigned totalruns, const char *user) { unsigned runs = 0; signal(SIGUSR1, catchsig); signal(SIGUSR2, catchsig); signal(SIGTERM, catchsig); signal(SIGALRM, catchsig); signal(SIGHUP, catchsig); signal(SIGCHLD, catchsig); #ifdef HAVE_LIBMD logit("%s %swatching %s%s%s%s%s", PACKAGE_STRING, #else logit("%s %swatching %s%s%s%s", PACKAGE_STRING, #endif (user) ? user : "", (TEST_BIT(check, ACCESS)) ? ATIME_STR " " : "", (TEST_BIT(check, MODIFY)) ? MTIME_STR " " : "", (TEST_BIT(check, CHANGE)) ? CTIME_STR " " : "", #ifdef HAVE_LIBMD (TEST_BIT(check, MD5)) ? MD5_STR " " : "", #endif (TEST_BIT(check, ERROR)) ? ERROR_STR : ""); daemonstatus(cmdruns, runs, totalruns); while (1) { struct timeval tv; FILES *fptr, *fcur; /* check if a signal was received*/ if (runlevel != NORMAL) { /* ignore other signals until finished*/ signal(SIGUSR1, ignoresig); signal(SIGUSR2, ignoresig); signal(SIGTERM, ignoresig); signal(SIGALRM, ignoresig); signal(SIGHUP, ignoresig); switch (runlevel) { case RELOAD: if ((fcur = loadfile(filenamelist, fhead))) { freelist(fhead); fhead = fcur; daemonstatus(cmdruns, runs, totalruns); break; } break; case RESETALL: case RESET: for (fcur = fhead; fcur; fcur = fptr) { fptr = fcur->next; resetchanged(fcur); if (runlevel == RESETALL) fcur->cmdruns = 0; } if (runlevel == RESETALL && totalruns) runs = 0; daemonstatus(cmdruns, runs, totalruns); break; case QUIT: freelist(fhead); if (filenamelist) free(filenamelist); if (unlink(pidfile) != 0) logit("%s: %s", pidfile, strerror(errno)); free(logfile); free(pidfile); free(wd); exit(EXIT_SUCCESS); break; default: break; } runlevel = NORMAL; /* restore signal handler*/ signal(SIGUSR1, catchsig); signal(SIGUSR2, catchsig); signal(SIGTERM, catchsig); signal(SIGALRM, catchsig); signal(SIGHUP, catchsig); } fptr = fhead; while (fptr->next != NULL) { char **cargv; char *cmd = NULL, *diff; unsigned changes = 0; time_t basetime = 0; struct stat st; if (STAT(fptr->filename, &st) == -1) { /* error when stat()ing. if the last error is the same as the*/ /* current one, skip this file*/ if (fptr->staterrno == errno) { fptr = fptr->next; continue; } if (TEST_BIT(check, ERROR)) { SET_BIT(changes, ERROR); fptr->staterrno = errno; } } else fptr->staterrno = 0; /* check the current stat() against the last stat()*/ errno = 0; changes = checkstat(fptr, st, &basetime, changes); #ifdef HAVE_LIBMD /* MD5File() error*/ if (errno && !TEST_BIT(changes, ERROR)) { if (fptr->md5errno != errno && TEST_BIT(check, ERROR)) { SET_BIT(changes, ERROR); SET_BIT(changes, MD5); fptr->md5errno = fptr->staterrno = errno; } } #endif if (checkbits(changes)) { fptr->laststat = st; diff = changelog(fptr, basetime, changes); /* run a command if specified*/ if (command[0] && ((totalruns && runs < totalruns) || (!cmdruns && !totalruns) || (fptr->cmdruns < cmdruns && !totalruns))) { fptr->cmdruns++; runs++; cmd = parsecmd(fptr, command, basetime, diff, changes, (totalruns) ? runs : fptr->cmdruns, (totalruns) ? totalruns : cmdruns); switch (fork()) { case 0: signal(SIGUSR1, SIG_DFL); signal(SIGUSR2, SIG_DFL); signal(SIGTERM, SIG_DFL); signal(SIGALRM, SIG_DFL); signal(SIGHUP, SIG_DFL); signal(SIGCHLD, SIG_DFL); switch (fork()) { case 0: if (!(cargv = parseargv(cmd))) exit(EXIT_FAILURE); errno = EXIT_SUCCESS; if (execvp(cargv[0], cargv) == -1) { logit("%s: %s", cargv[0], strerror(errno)); _exit(errno); } _exit(EXIT_FAILURE); case -1: _exit(EXIT_FAILURE); default: _exit(EXIT_SUCCESS); } case -1: logit("fork(): %s", strerror(errno)); _exit(errno); default: logit("%i/%i: %s", (totalruns) ? runs : fptr->cmdruns, (totalruns) ? totalruns : cmdruns, cmd); break; } } /* get ready for next check*/ resetbittypes(fptr); if (loglevel == 2) resetchanged(fptr); } fptr = fptr->next; } tv.tv_sec = interval; tv.tv_usec = 0; select(0, 0, 0, 0, &tv); } return; } static void usage(int error, const char *pn) { fprintf(stdout, "Usage: %s [-hvksq] [-l ] [-#] " "[[-e ] [-r# | -t#]]\n\t[-n#] " #ifdef HAVE_LIBMD "[-i#] [-p pidfile] -MAamcE file [...] | -f \n", pn); #else "[-i#] [-p pidfile] -AamcE file [...] | -f \n", pn); #endif fprintf(stdout, " -a\tCheck the access time of the file.\n"); fprintf(stdout, " -m\tCheck the modification time of the file.\n"); fprintf(stdout, " -c\tCheck the inode change time.\n"); #ifdef HAVE_LIBMD fprintf(stdout, " -M\tCheck the MD5 checksum of the file (-a not " "allowed).\n"); #endif fprintf(stdout, " -E\tCheck for errors when stat()ing a file.\n"); fprintf(stdout, " -A\tShortcut for -amcE.\n"); fprintf(stdout, " -e\tCommand to run when a file changes (see below).\n"); fprintf(stdout, " -r#\tNumber of times to run command per file " "(0=infinite). The default is %i.\n", CMDRUNS); fprintf(stdout, " -t#\tTotal number of times to run command (overrides " "-r).\n"); fprintf(stdout, " -n#\tNiceness level (-20 through 20) of the daemon and " "command.\n"); fprintf(stdout, " -i#\tInterval in seconds to check files. The default is %i.\n", INTERVAL); fprintf(stdout, " -f\tA file containing a list of filenames, one per line, " "to watch.\n"); fprintf(stdout, " -k\tDon't follow symbolic links.\n"); fprintf(stdout, " -l\tLog to an alternate file. Default is ~/%s.\n", LOGFILE); fprintf(stdout, " -#\tLog level: 0=none, 1=first change, 2=all changes. " "Default is %i.\n", LOGLEVEL); fprintf(stdout, " -p\tAlternate process id file. Default is ~/%s.\n", PIDFILE); fprintf(stdout, " -q\tTerminate daemon associated with the process id " "file.\n"); fprintf(stdout, " -s\tDisable logging to file and log to " "syslogd(8).\n"); fprintf(stdout, " -h\tThis help text.\n"); fprintf(stdout, " -v\tVersion and copyright information.\n\n"); fprintf(stdout, "The following expansions are available in a command " "(remember to quote!):\n"); fprintf(stdout, " %%f - filename\t"); fprintf(stdout, "\t%%t - time of (latest) change\t"); fprintf(stdout, "%%b - last change time\n"); fprintf(stdout, " %%e - error info\t"); fprintf(stdout, "%%i - time difference\t\t"); fprintf(stdout, "%%p - daemon PID\n"); fprintf(stdout, " %%d - detection time\t"); fprintf(stdout, "%%s - daemon status\t\t"); fprintf(stdout, "%%r - run number/total\n"); fprintf(stdout, " %%F - file type\t"); fprintf(stdout, "%%m - file permission mode\t"); fprintf(stdout, "%%%% - regular %%\n"); #ifdef HAVE_LIBMD fprintf(stdout, " %%c - change(s): %s, %s, %s, %s, %s\n", ATIME_STR, MTIME_STR, CTIME_STR, MD5_STR, ERROR_STR); #else fprintf(stdout, " %%c - change(s): %s, %s, %s, %s\n", ATIME_STR, MTIME_STR, CTIME_STR, ERROR_STR); #endif fprintf(stdout, " %%y - change type(s): %s, %s, %s, %s, %s, %s, %s\n", IDEV_STR, INODE_STR, HLINKS_STR, UID_STR, GID_STR, PERMS_STR, SIZE_STR); exit((error) ? EXIT_FAILURE : EXIT_SUCCESS); } int main(int argc, char *argv[]) { FILE *fp; int opt, bufsize = 0, quit = 0; unsigned cmdruns, totalruns; char user[16], *s; struct passwd *passwd; FILES *fhead, *fptr; #ifdef HAVE_BASENAME if (!(pn = basename(argv[0]))) pn = argv[0]; #else pn = argv[0]; #endif /* need this for fullpath() because the daemon changes to '/' after fork()*/ /* so filepaths would be wrong if relative (file reloading)*/ if (!(wd = getcwd(NULL, PATH_MAX))) err(EXIT_FAILURE, "getcwd()"); /* get current user password file info*/ if (!(passwd = getpwuid(getuid()))) errx(EXIT_FAILURE, "getpwuid()"); /* where to log to*/ if ((s = getenv("BUBBLEGUM_LOG"))) { bufsize = pathlength(wd, s) + 1; logfile = Malloc(bufsize); fullpath(wd, s, logfile, bufsize); } else { bufsize = strlen(passwd->pw_dir) + strlen(LOGFILE) + 2; logfile = Malloc(bufsize); snprintf(logfile, bufsize, "%s/%s", passwd->pw_dir, LOGFILE); } /* daemon process id file location*/ if ((s = getenv("BUBBLEGUM_PID"))) { bufsize = pathlength(wd, s) + 1; pidfile = Malloc(bufsize); fullpath(wd, s, pidfile, bufsize); } else { bufsize = strlen(passwd->pw_dir) + strlen(PIDFILE) + 2; pidfile = Malloc(bufsize); snprintf(pidfile, bufsize, "%s/%s", passwd->pw_dir, PIDFILE); } /* set some defaults (from config.h)*/ interval = INTERVAL; loglevel = LOGLEVEL; cmdruns = CMDRUNS; totalruns = 0; symlinks = 0; while ((opt = getopt(argc, argv, optstr)) != -1) { int res; char tmp[2]; switch (opt) { case 'q': quit = 1; break; case 'p': bufsize = pathlength(wd, optarg) + 1; pidfile = Realloc(pidfile, bufsize); fullpath(wd, optarg, pidfile, bufsize); break; case 'k': symlinks = 1; break; case 'n': niceness = atoi(optarg); if (niceness > 20 || niceness < -20) usage(1, pn); break; case 's': usesyslog = 1; break; case 'e': if (optarg[0] == 0) usage(1, pn); strncpy(command, optarg, sizeof(command)); break; case 'l': bufsize = pathlength(wd, optarg) + 1; logfile = Realloc(logfile, bufsize); fullpath(wd, optarg, logfile, bufsize); break; case '0': case '1': case '2': snprintf(tmp, sizeof(tmp), "%c", opt); loglevel = atoi(tmp); break; case 'f': if ((res = is_file(optarg)) != 0) { if (res < 0) err(EXIT_FAILURE, "%s", optarg); else errx(EXIT_FAILURE, "%s: Not a regular file", optarg); } bufsize = pathlength(wd, optarg) + 1; filenamelist = Malloc(bufsize); fullpath(wd, optarg, filenamelist, bufsize); break; case 't': if (isinteger(optarg)) usage(1, pn); totalruns = atoi(optarg); break; case 'r': if (isinteger(optarg)) usage(1, pn); cmdruns = atoi(optarg); break; case 'i': if (isinteger(optarg)) usage(1, pn); if ((interval = atol(optarg)) < 1) usage(1, pn); break; case 'h': usage(0, pn); break; case 'v': fprintf(stdout, "%s\n%s\n", PACKAGE_STRING, COPYRIGHT); exit(EXIT_SUCCESS); case 'A': SET_BIT(check, ACCESS); SET_BIT(check, CHANGE); SET_BIT(check, MODIFY); SET_BIT(check, ERROR); break; #ifdef HAVE_LIBMD case 'M': SET_BIT(check, MD5); break; #endif case 'a': SET_BIT(check, ACCESS); break; case 'c': SET_BIT(check, CHANGE); break; case 'm': SET_BIT(check, MODIFY); break; case 'E': SET_BIT(check, ERROR); break; default: usage(1, pn); break; } } if (!command[0] && !usesyslog && !loglevel && !quit) usage(1, pn); if ((argc == optind && !filenamelist) || !check) { if (!quit) usage(1, pn); } if (argc != optind && filenamelist) usage(1, pn); #ifdef HAVE_LIBMD if (TEST_BIT(check, MD5) && TEST_BIT(check, ACCESS)) errx(EXIT_FAILURE, "Cannot specify both MD5 and ATIME checking. " "See -h for help."); #endif /* see if a daemon is already running and/or quit if specified*/ if ((fp = fopen(pidfile, "r"))) { if (fscanf(fp, "%lu", &pid) != 1) { fclose(fp); if (quit) errx(EXIT_FAILURE, "%s: Parse error", pidfile); goto cont; } fclose(fp); if (kill(pid, 0) == 0) { if (quit) { if (kill(pid, SIGTERM) != 0) warn("kill()"); } else errx(EXIT_FAILURE, "Existing daemon already running (pid %u)", pid); } else { if (quit) err(EXIT_FAILURE, "%u", pid); } if (quit) { warnx("Terminated (pid %u)", pid); exit(EXIT_SUCCESS); } } else { if (quit) err(EXIT_FAILURE, "%s", pidfile); } cont: pid = getpid(); errno = 0; /* daemon and child process priority*/ if ((niceness = getpriority(PRIO_PROCESS, pid)) == -1) { if (errno) err(EXIT_FAILURE, "getpriority()"); } if (setpriority(PRIO_PROCESS, pid, niceness) == -1) err(EXIT_FAILURE, "setpriority()"); /* make sure we can log to the logging file if specified*/ if (!usesyslog && loglevel) { if (!(fp = fopen(logfile, "a"))) err(EXIT_FAILURE, "%s", logfile); fclose(fp); } /* load a file list into the linked list (file.c)*/ if (filenamelist) { if (!(fhead = loadfile(filenamelist, NULL))) exit(EXIT_FAILURE); } else { /* files are from command line*/ fhead = Calloc(1, sizeof(FILES)); fptr = fhead; while (optind < argc) { struct stat st; char *cp = argv[optind++]; if (STAT(cp, &st) == -1) { warn("%s", cp); continue; } if (S_ISDIR(st.st_mode) && cp[strlen(cp) - 1] == '/') { fptr = recurse_directory(fptr, cp, 0); stat(cp, &st); } fptr = add_file(fptr, cp, st, 0); fptr = fptr->next; } fptr->next = NULL; } if (!fhead->filename) errx(EXIT_FAILURE, "No files to watch"); switch (fork()) { case 0: pid = getpid(); /* write the daemon pid to the process ID file*/ if (!(fp = fopen(pidfile, "w+"))) warn("%s", pidfile); else { fprintf(fp, "%lu\n", pid); fclose(fp); } if (chdir("/") == -1) err(EXIT_FAILURE, "/"); if (setsid() == -1) err(EXIT_FAILURE, "setsid()"); if (usesyslog) snprintf(user, sizeof(user), "(%s) ", passwd->pw_name); freopen("/dev/null", "r", stdin); freopen("/dev/null", "w", stdout); freopen("/dev/null", "w", stderr); /* call the main program loop*/ doit(fhead, cmdruns, totalruns, (usesyslog) ? user : NULL); break; case -1: err(EXIT_FAILURE, "fork()"); break; default: break; } exit(EXIT_SUCCESS); }