/*
*
*	Copyright (c) 2001 Fredrik Sjoholm <fredrik@sjoholm.com>
*	All rights reserved.
*	License: GPL - The GNU General Public License
*
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <time.h>
#include <string.h>



struct {
  char* data;
  int used;
  int size;
} buf;

struct FileInfo {
  char *name;
  int fd;
  struct stat stat;	// remember info about file so we can know if it's been rotated
} out;

struct Conf {
  char timestamp;	// { 0 | 1 }
  char pidfile;	// { 0 | 1 }
  char * time_format;
  char * pidfile_name;
  int max_len;
  int pid;
} conf;

#define DEFAULT_TIME_FORMAT "%Y%m%d;%T: "

volatile int gotHUP;

void growbuf(int size);
int openfile (struct FileInfo* file);
int filechanged (struct FileInfo* file);
void unlinkpidfile (struct Conf* conf);
void catchHUP (int sig);
void handleHUP ();
void writetime (int fd);

int main(int argc, char** argv)
{
  char *eol, *pos;
  int done = 0;
  int opt = 0;
  int totalwn = 0;
  FILE *pid_fd;


  // look for switches
  conf.time_format = NULL;
  while (++opt < argc) {
    if (!strcmp(argv[opt], "-t")) {
      conf.timestamp = 1;
      conf.time_format = DEFAULT_TIME_FORMAT;
    } else if (!strcmp(argv[opt], "-T")) {
      opt++;
      if (opt < argc) {
        conf.timestamp = 1;
        conf.time_format = argv[opt];
      }
    } else if (!strcmp(argv[opt], "-p")) {
      opt++;
      if (opt < argc) {
        conf.pidfile = 1;
        conf.pid = getpid();
        conf.pidfile_name = argv[opt];
      }
    } else if (!strcmp(argv[opt], "-l")) {
      opt++;
      if (opt < argc) {
	conf.max_len = atoi (argv[opt]);
      }
    } else {
	break;
    }
  }

  if (opt >= argc) {
    fprintf (stderr, 
	"Usage: pipeline| %s [options] {logfile|-}  # SIGHUP will reopen logfile\n"
	"	-t	     prepend each line with \"YYYYMMDD;HH:MM:SS: \"\n"
	"	-T <format>  prepend each line with specified strftime(3) format\n\n"
	"	-l <number>  log file length limit (force truncation)\n"
	"	-p <pidfile> pid file\n",
	argv[0]);
    exit(1);
  }

  if (conf.pidfile) {
	  pid_fd = fopen(conf.pidfile_name, "w");
	  if ((pid_fd = fopen(conf.pidfile_name, "w")) == NULL) {
		fprintf (stderr, "Could not open pidile: %s", conf.pidfile_name);
		exit(1);
	  }else if (pid_fd) {
		fprintf(pid_fd, "%lu\n", (unsigned long)getpid());
		fclose(pid_fd);
	  }
  }

  out.name = argv[opt];
  openfile (&out);

  signal (SIGHUP, catchHUP);

  growbuf (4096);

  while (!done) {
    int size;

    if (buf.used == buf.size) growbuf (buf.size*2);

    size = read (0, buf.data+buf.used, buf.size-buf.used);
    if (size > 0) {
      buf.used += size;
    }
    else if (size == 0) {
      done = 1;
    }
    else { }

    for (;;) {

      if (gotHUP) {
	handleHUP();
	signal (SIGHUP, catchHUP);
	gotHUP = 0;
      }

      pos = buf.data;
      eol = (char*) memchr(buf.data, '\n', buf.used);
      if (eol == 0) break;
      eol ++;

      if (conf.timestamp)
	writetime (out.fd);

      while (pos < eol) {
	int wn = write(out.fd, pos, eol-pos);
	if (wn > 0) {
	  pos += wn;
	  totalwn += wn;
	}
	else if (errno == ENOSPC) {
	  char str[256];
	  if (ftruncate(out.fd,0)) {
	    fprintf(stderr, "truncating %s: %d %s\n", out.name, errno, strerror(errno));
 	    unlinkpidfile(&conf); 
	    exit(1);
	  }
	  write (out.fd, str, snprintf(str, sizeof(str), "Device full: truncating %s\n\n", out.name));
	}
	else {
	  fprintf(stderr, "write %s: %d %s\n", out.name, errno, strerror(errno));
  	  unlinkpidfile(&conf); 
	  exit(1);
	}
	if (conf.max_len && (totalwn > conf.max_len)) {
	  char str[256];
	  if (ftruncate(out.fd,0)) {
	    fprintf(stderr, "truncating %s: %d %s\n", out.name, errno, strerror(errno));
  	    unlinkpidfile(&conf); 
	    exit(1);
	  }
	  write (out.fd, str, snprintf(str, sizeof(str), "File size limit: truncating %s\n\n", out.name));
	  totalwn = 0;
	}
      }
    
      buf.used = buf.data + buf.used - eol;
      memmove (buf.data, eol, buf.used);
    }

  }



  unlinkpidfile(&conf); 
  exit(0);
}



void growbuf (int size)
{
  if (size > buf.size) {
    buf.data = (char*) realloc(buf.data, size);
    buf.size = size;
  }
}




int openfile (struct FileInfo* file)
{
  int fd;
  if (strcmp(file->name,"-") != 0) {
#ifdef O_LARGEFILE
    fd = open (file->name, O_CREAT|O_APPEND|O_WRONLY|O_LARGEFILE, 0666);
#else
    fd = open (file->name, O_CREAT|O_APPEND|O_WRONLY, 0666);
#endif
  } else {
    fd = 2;
  }
  if (fd < 0) {
    fprintf (stderr, "open write %s: %s\n", file->name, strerror(errno));
    exit(1);
  }
  file->fd = fd;
  stat (file->name, &file->stat);	// update stat buffer
  return fd;
}


int reopenfile (struct FileInfo* file)
{
  if (strcmp(file->name,"-") != 0) {
    close (file->fd);
    return openfile (file);
  } else {
    return file->fd;
  }
}





int filechanged (struct FileInfo* file)
{
  struct stat stat2;

  if (strcmp(file->name,"-") == 0) { return 0; }

  if (stat (file->name, &stat2) < 0) {
    return 1;	// file removed or something
  }

  if (stat2.st_ino != file->stat.st_ino || stat2.st_dev != file->stat.st_dev) {
    // file changed or was moved to a different device
    return 1;
  }
  
  return 0;
}



void catchHUP (int sig)
{
  gotHUP = 1;
}



void handleHUP ()
{
  char str[256];
  if (filechanged(&out)) {
    // only reopen file and print msg if the file on disk was changed or removed
    write (out.fd, str, snprintf (str, sizeof(str), "Caught SIGHUP: Reopening %s (closed)\n\n", out.name));
    reopenfile (&out);
    write (out.fd, str, snprintf (str, sizeof(str), "Caught SIGHUP: Reopening %s (opened)\n\n", out.name));
  }
}



// format: "YYYYMMDD;HH.MM.SS: "
void writetime (int fd)
{
  time_t now;
  struct tm *t;
  char buf[32];
  int len;

  time (&now);
  t = localtime (&now);
  
  len = strftime(buf, sizeof(buf), conf.time_format, t);
  write (fd, &buf, len);
}




void unlinkpidfile (struct Conf* conf)
{
  if (conf->pidfile) {
	  unlink(conf->pidfile_name);
  }
}
