#include <time.h>
#include <sys/time.h>
#include <signal.h>
+#include <fcntl.h>
+#include <sys/stat.h>
#ifdef HAVE_CONFIG_H
#include <config.h>
static int ch;
static char *outname = NULL;
static FILE *out;
-static int flush = 1;
+static int flush = 1, locklog = 1;
static char *format;
-static time_t now;
+static struct timeval now;
static volatile int reopen = 0;
static void qputs(char *s, FILE *o)
{
char *h, *p;
char buf[1024];
- struct timeval tv;
switch(o) {
case '%':
case 't':
if(!*d)
d = "%a, %d %b %Y %H:%M:%S %z";
- strftime(buf, sizeof(buf), d, localtime(&now));
+ strftime(buf, sizeof(buf), d, localtime(&now.tv_sec));
qputs(buf, out);
break;
case 'T':
if(!*d)
d = "%a, %d %b %Y %H:%M:%S %z";
- strftime(buf, sizeof(buf), d, gmtime(&now));
+ strftime(buf, sizeof(buf), d, gmtime(&now.tv_sec));
qputs(buf, out);
break;
case 's':
- gettimeofday(&tv, NULL);
- fprintf(out, "%06i", (int)tv.tv_usec);
+ fprintf(out, "%06i", (int)now.tv_usec);
break;
case 'A':
logitem(req, 'h', "X-Ash-Address");
static void serve(struct hthead *req, int fd)
{
- now = time(NULL);
+ gettimeofday(&now, NULL);
if(sendreq(ch, req, fd)) {
flog(LOG_ERR, "accesslog: could not pass request to child: %s", strerror(errno));
exit(1);
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)
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);
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;
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();
if(pfd[1].revents & POLLHUP)
break;
}
+ if(pidfile != NULL)
+ unlink(pidfile);
return(0);
}