/* logtailer 0.3
 * Watch multiple files, checking for rotation.
 * Usage: logtailer term1 term2 term3 ...
 * where termN = label file
 * or    termN = { label file1 file2 file3 ... }
 * Copyright (c) 2000, Adam Sampson <azz@gnu.org> 
 *
 * Changelog:
 * 0.2	first public release
 * 0.3	changed erroneous &buf to buf
 */

/* Interval to poll the logs, in seconds. */
#define TIMEOUT 1

#include <stdio.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)) {
    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;
}
