David Learns Programming

logtailer.c

/*
   logtailer: a tool to watch log files.
   Copyright 2001, 2002, 2008 Adam Sampson <ats@offog.org>
 
   Please report bugs to <ats@offog.org>.
 
   logtailer is free software; you can redistribute and/or modify it
   under the terms of that license as published by the Free Software
   Foundation; either version 2 of the License, or (at your option)
   any later version.
 
   logtailer 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 logtailer; see the file COPYING. If not, write to the
   Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
   MA 02111-1307 USA, or see <a href="http://www.gnu.org/.<br />
*/</a></p>
<p>/*" title="http://www.gnu.org/.<br />
*/</p>
<p>/*">http://www.gnu.org/.<br />
*/</p>
<p>/* Interval to poll the logs, in seconds. */
#define TIMEOUT 1
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>
#include <fcntl.h>
 
void die(char *fmt, char *arg) {
  fprintf(stderr, fmt, arg);
  exit(20);
}
 
struct log {
  char *name;
  char *label;
  ino_t inode;
  off_t size;
  int fd;
  struct log *next;
};
typedef struct log log;
 
log *first = NULL, *prev = NULL, *this;
 
/* Print all the text that's available from a log. */
void printlog(log *this) {
#define BUFSIZE 1024
  char buf[BUFSIZE];
  int len;
  static log *lastlog = NULL;
 
  /* If the log is not the last one we printed from,
     then print a label. */
  if (this != lastlog) {
    fprintf(stderr, "<%s>\n", this->label);
    lastlog = this;
  }
 
  /* Write everything we can to stderr. */
  while ((len = read(this->fd, buf, BUFSIZE)) > 0) {
    fwrite(buf, sizeof(char), len, stderr);
  }
}
 
/* Open up a log file. */
void openlog(log *this) {
  this->fd = open(this->name, O_RDONLY);
  if ((this->fd) < 0) die("Unable to open '%s'\n", this->name);
}
 
/* Add a logfile to the list. */
void addlog(char *label, char *name) {
  struct stat st;
 
  if (stat(name, &st) < 0) die("Unable to stat file '%s'\n", name);
  if (!S_ISREG(st.st_mode)) die("'%s' is not a file\n", name);
 
  this = (log *)malloc(sizeof(log));
  this->label = label;
  this->name = name;
  this->inode = st.st_ino;
  this->size  = st.st_size;
 
  openlog(this);
 
  /* Start at the end of the log. */
  lseek(this->fd, 0, SEEK_END);
 
  if (!first) first = this;
  if (prev) prev->next = this;
  prev = this;
}
 
int main(int argc, char **argv) {
  int i;
 
  for (i=1; i<argc;) {
    if (!strcmp(argv[i++], "{")) {
      int labelpos = i++;
      while (strcmp(argv[i], "}")) {
	if (i + 1 >= argc) die("Invalid arguments%s\n", "");
	addlog(argv[labelpos], argv[i++]);
      }
      i++;
    } else {
      if (i >= argc) die("Invalid arguments%s\n", "");
      addlog(argv[i - 1], argv[i]);
      i++;
    }
  }
  if (i<argc) die("Too many arguments%s\n", "");
  if (!first) die("No logs specified%s\n","");
 
  while (1) {
    int found = 0;
 
    /* Scan though all the fds. */
    for (this = first; this; this = this->next) {
      struct stat st;
 
      if (stat(this->name, &st) < 0) {
	die("Unable to stat file '%s'\n", this->name);
      }
 
      /* Has new content been written? */
      if (st.st_size > this->size) {
	printlog(this);
	this->size = st.st_size;
	found = 1;
      }
 
      /* Has the file been rotated? */
      if (st.st_ino != this->inode || st.st_size < this->size) {
	/* Read any remaining contents, then reopen it. */
	printlog(this);
	close(this->fd);
	openlog(this);
	this->inode = st.st_ino;
	this->size = st.st_size;
	found = 1;
      }
    }
 
    /* If we didn't find anything, wait for a while. */
    if (!found) sleep(TIMEOUT);
  }
 
  /* Close all the fds. */
  for (this = first; this; this = this->next) {
    close(this->fd);
  }
 
  return 0;
}
</code>