python: Added more useful logging to wsgidir.
[ashd.git] / src / accesslog.c
index 06bdd98..0c292c8 100644 (file)
@@ -25,6 +25,8 @@
 #include <time.h>
 #include <sys/time.h>
 #include <signal.h>
+#include <fcntl.h>
+#include <sys/stat.h>
 
 #ifdef HAVE_CONFIG_H
 #include <config.h>
@@ -39,7 +41,7 @@
 static int ch;
 static char *outname = NULL;
 static FILE *out;
-static int flush = 1;
+static int flush = 1, locklog = 1;
 static char *format;
 static struct timeval now;
 static volatile int reopen = 0;
@@ -56,7 +58,7 @@ static void qputs(char *s, FILE *o)
        } else if(*s == '\t') {
            fputs("\\t", o);
        } else if((*s < 32) || (*s >= 128)) {
-           fprintf(o, "\\x%02x", *s);
+           fprintf(o, "\\x%02x", (int)(unsigned char)*s);
        } else {
            fputc(*s, o);
        }
@@ -176,25 +178,103 @@ static void sighandler(int sig)
        reopen = 1;
 }
 
+static int lockfile(FILE *file)
+{
+    struct flock ld;
+    
+    memset(&ld, 0, sizeof(ld));
+    ld.l_type = F_WRLCK;
+    ld.l_whence = SEEK_SET;
+    ld.l_start = 0;
+    ld.l_len = 0;
+    return(fcntl(fileno(file), F_SETLK, &ld));
+}
+
+static void fetchpid(char *filename)
+{
+    int fd, ret;
+    struct flock ld;
+    
+    if((fd = open(filename, O_WRONLY)) < 0) {
+       fprintf(stderr, "accesslog: %s: %s\n", filename, strerror(errno));
+       exit(1);
+    }
+    memset(&ld, 0, sizeof(ld));
+    ld.l_type = F_WRLCK;
+    ld.l_whence = SEEK_SET;
+    ld.l_start = 0;
+    ld.l_len = 0;
+    ret = fcntl(fd, F_GETLK, &ld);
+    close(fd);
+    if(ret) {
+       fprintf(stderr, "accesslog: %s: %s\n", filename, strerror(errno));
+       exit(1);
+    }
+    if(ld.l_type == F_UNLCK) {
+       fprintf(stderr, "accesslog: %s: not locked\n", filename);
+       exit(1);
+    }
+    printf("%i\n", (int)ld.l_pid);
+}
+
 static void reopenlog(void)
 {
     FILE *new;
+    struct stat olds, news;
     
     if(outname == NULL) {
        flog(LOG_WARNING, "accesslog: received SIGHUP but logging to stdout, so ignoring");
        return;
     }
+    if(locklog) {
+       if(fstat(fileno(out), &olds)) {
+           flog(LOG_ERR, "accesslog: could not stat current logfile(?!): %s", strerror(errno));
+           return;
+       }
+       if(!stat(outname, &news)) {
+           if((olds.st_dev == news.st_dev) && (olds.st_ino == news.st_ino)) {
+               /*
+                * This needs to be ignored, because if the same logfile
+                * is opened and then closed, the lock is lost. To quote
+                * the Linux fcntl(2) manpage: "This is bad." No kidding.
+                *
+                * Technically, there is a race condition here when the
+                * file has been stat'ed but not yet opened, where the old
+                * log file, having been previously renamed, changes name
+                * back to the name accesslog knows and is thus reopened
+                * regardlessly, but I think that might fit under the
+                * idiom "pathological case". It should, at least, not be
+                * a security problem.
+                */
+               flog(LOG_INFO, "accesslog: received SIGHUP, but logfile has not changed, so ignoring");
+               return;
+           }
+       }
+    }
     if((new = fopen(outname, "a")) == NULL) {
        flog(LOG_WARNING, "accesslog: could not reopen log file `%s' on SIGHUP: %s", outname, strerror(errno));
        return;
     }
+    fcntl(fileno(new), F_SETFD, FD_CLOEXEC);
+    if(locklog) {
+       if(lockfile(new)) {
+           if((errno == EAGAIN) || (errno == EACCES)) {
+               flog(LOG_ERR, "accesslog: logfile is already locked; reverting to current log", strerror(errno));
+               fclose(new);
+               return;
+           } else {
+               flog(LOG_WARNING, "accesslog: could not lock logfile, so no lock will be held: %s", strerror(errno));
+           }
+       }
+    }
     fclose(out);
     out = new;
 }
 
 static void usage(FILE *out)
 {
-    fprintf(out, "usage: accesslog [-hFa] [-f FORMAT] OUTFILE CHILD [ARGS...]\n");
+    fprintf(out, "usage: accesslog [-hFaL] [-f FORMAT] [-p PIDFILE] OUTFILE CHILD [ARGS...]\n");
+    fprintf(out, "       accesslog -P LOGFILE\n");
 }
 
 int main(int argc, char **argv)
@@ -203,9 +283,11 @@ int main(int argc, char **argv)
     struct hthead *req;
     int fd;
     struct pollfd pfd[2];
+    char *pidfile;
+    FILE *pidout;
     
-    optarg = NULL;
-    while((c = getopt(argc, argv, "+hFaf:")) >= 0) {
+    pidfile = NULL;
+    while((c = getopt(argc, argv, "+hFaLf:p:P:")) >= 0) {
        switch(c) {
        case 'h':
            usage(stdout);
@@ -213,9 +295,18 @@ int main(int argc, char **argv)
        case 'F':
            flush = 0;
            break;
+       case 'L':
+           locklog = 0;
+           break;
        case 'f':
            format = optarg;
            break;
+       case 'P':
+           fetchpid(optarg);
+           exit(0);
+       case 'p':
+           pidfile = optarg;
+           break;
        case 'a':
            format = "%A - - [%{%d/%b/%Y:%H:%M:%S %z}t] \"%m %u %v\" - - \"%R\" \"%G\"";
            break;
@@ -236,17 +327,44 @@ int main(int argc, char **argv)
        outname = argv[optind];
     if(outname == NULL) {
        out = stdout;
+       locklog = 0;
     } else {
        if((out = fopen(argv[optind], "a")) == NULL) {
            flog(LOG_ERR, "accesslog: could not open %s for logging: %s", argv[optind], strerror(errno));
            exit(1);
        }
+       fcntl(fileno(out), F_SETFD, FD_CLOEXEC);
+    }
+    if(locklog) {
+       if(lockfile(out)) {
+           if((errno == EAGAIN) || (errno == EACCES)) {
+               flog(LOG_ERR, "accesslog: logfile is already locked", strerror(errno));
+               exit(1);
+           } else {
+               flog(LOG_WARNING, "accesslog: could not lock logfile: %s", strerror(errno));
+           }
+       }
     }
     if((ch = stdmkchild(argv + optind + 1, NULL, NULL)) < 0) {
-       flog(LOG_ERR, "accesslog: could fork child: %s", strerror(errno));
+       flog(LOG_ERR, "accesslog: could not fork child: %s", strerror(errno));
        exit(1);
     }
     signal(SIGHUP, sighandler);
+    if(pidfile) {
+       if(!strcmp(pidfile, "-")) {
+           if(!outname) {
+               flog(LOG_ERR, "accesslog: cannot derive PID file name without an output file");
+               exit(1);
+           }
+           pidfile = sprintf2("%s.pid", outname);
+       }
+       if((pidout = fopen(pidfile, "w")) == NULL) {
+           flog(LOG_ERR, "accesslog: could not open PID file %s for writing: %s", pidfile);
+           exit(1);
+       }
+       fprintf(pidout, "%i\n", (int)getpid());
+       fclose(pidout);
+    }
     while(1) {
        if(reopen) {
            reopenlog();
@@ -277,5 +395,7 @@ int main(int argc, char **argv)
        if(pfd[1].revents & POLLHUP)
            break;
     }
+    if(pidfile != NULL)
+       unlink(pidfile);
     return(0);
 }