/*
 * title:	checkmail.c
 *
 * author:	Craig E. Ward
 *
 * envionment:	UNIX + ANSI C (gcc 3.1)
 *
 * usage:	checkmail {path_to_mail_directory}
 *
 *		gcc -ansi -o checkmail checkmail.c
 *
 * description:
 *		CMSI 587 Operating Systems, Spring 2003
 *              Assignment 3 -- Checkmail
 *              Instructor: Tri Nguyen
 *
 *              This is an exercise in using some of the system calls related
 *              to reading files and processing signals.
 *
 */
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <fcntl.h>
#include <libgen.h>
#include <signal.h>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <unistd.h>

#define WAIT_SECONDS 5

typedef enum {FALSE, TRUE, NO=0, YES} bool_t;

/* function prototypes */
static void log(char *, ...);
static bool_t changed_file(void);
static void show_new_senders(void);
static void alarm_handler(int);

/* external data */
extern int errno;

/* initialized data */
static off_t lastoffset = 0;	/* where we stopped before */
static off_t lastsize = 0;	/* size of file last time we checked */
static char *maildir = "/var/mail"; /* system dependent */

/* uninitialized data */
static char *progname;
static char *username;

int main (int argc, char *argv[])
{
  progname = basename((const char*)argv[0]);

  username = getlogin();
  if (username == NULL) {
    log("%s: failed to get login username: %s ", progname, strerror(errno));
    exit(1);
  }
  if (argc == 2) {
    maildir = argv[1];		/* take as new path */
    log("%s: maildir changed to %s", progname, maildir);
  }

  if (chdir(maildir) == -1) {
    log("%s: can't chdir to %s: %s", progname, maildir, strerror(errno));
    exit(2);
  }

  signal(SIGALRM, alarm_handler); /*  set handler for SIGALRM */

  /* 
   * primary working loop -- goes forever until some signal other than SIGALRM
   * is sent.
   */
  for (;;) {
    if (changed_file()) {
      show_new_senders();
    }
    alarm(WAIT_SECONDS);
    pause();
  }
  exit(0);
} /* end main */

static bool_t changed_file()
{
  bool_t is_changed = NO;
  struct stat buf;

  memset(&buf, '\0', sizeof (struct stat));
  if (stat(username, &buf) == -1) {/* no mailfile */
    log("%s: stat failed: %s", progname, strerror(errno));
  }
  if (buf.st_size != lastsize) {
    is_changed = YES;
    if (lastsize > buf.st_size) lastoffset = 0;	/* new file */
    lastsize = buf.st_size;	/* update to new size */
  }
  return is_changed;
} /* changed_file */

static void show_new_senders()
{
  bool_t from_found = NO;
  bool_t subj_found = NO;
  int mfd;			/* mailfile file descriptor */
  int i;			/* simple index */
  int count;			/* count of characters processed */
  char ch;			/* one character */
  char inbuf[2048];		/* raw input buffer */
  char line[181];		/* one line buffer */
  char from[20];		/* buffer for holding a From string */
  char subject[40];		/* buffer for holding a Subject string */
  ssize_t bytes = 0;		/* return value of read */

  alarm(0);			/* cancel SIGALRMs */

  if ((mfd = open(username, (O_RDONLY|O_SHLOCK), 0)) == -1) {
    log("%s: open failed on %s/%s: %s",
	progname, maildir, username, strerror(errno));
  } else {
    if (lastoffset < lastsize && lastoffset != 0) {
      if ((lseek(mfd, lastoffset, SEEK_SET)) == -1) {
	log("%s: lseek i/o error on %s/%s: %s", 
	    progname, maildir, username, strerror(errno));
	exit(2);
      }
    }
    lastoffset = lastsize;

    memset(from, '\0', sizeof from);
    memset(subject, '\0', sizeof subject);
    while ((bytes = read(mfd, inbuf, sizeof inbuf)) > 0) {
      count = 0;
      while (count < bytes) {
	i = 0;
	memset(line, '\0', sizeof line);
	while ((ch = inbuf[count]) != '\n') {
	  line[i] = ch;		/* copy */
	  i++;
	  count++;
	  if (count >= bytes) break;
	}
	count++;
	if (line[0] == '\0') continue;
	if (!from_found && strncmp(line, "From:", 5) == 0) {
	  strncpy(from, line+5, (strcspn(line+5, "<@\n")) - 1);
	  /* strncpy(from, line+5, (sizeof from) - 1); */
	  from_found = YES;
	  log("show_new_senders: found from: %s", from);
	}
	if (!subj_found && strncmp(line, "Subject:", 7) == 0) {
	  strncpy(subject, line, (sizeof subject) - 1);
	  subj_found = YES;
	  log("show_new_senders: found subject: %s", subject);
	}
	if (from_found && subj_found) {
	  fprintf(stderr, "You have new mail from: %s\t%s\n", from, subject);
	  from_found = subj_found = NO;
	  memset(from, '\0', sizeof from);
	  memset(subject, '\0', sizeof subject);
	  continue;
	}
      }
    }
    if (bytes == -1) {
      log("show_new_senders: read i/o error on %s/%s: %s",
	  maildir, username, strerror(errno));
    }
    close(mfd); /* close the file */
  }
  return;
}

/*
 * When system sends us the SIGALRM signal, trap it here. The pause in main
 * will then return and the loop will begin again.
 */
static void alarm_handler(int sig)
{
  /*  log("alarm_handler traps signal %d (SIGALRM)", sig);*/
  return;
}
/*
 * Simple logging function
 */
static void log(char *format, ...)
{
  va_list ap;			/* variable argument list pointer */
  time_t now = 0;		/* current time */
  char timestr[41];		/* formatting of time */
  
  /* put a timestamp on log text */
  memset(timestr, '\0', sizeof timestr);
  time(&now);
  (void)strftime(timestr, ((sizeof timestr) - 1), 
		 "%m%d%y %H:%M:%S", localtime(&now));
  printf("[%s] ", timestr);

  /* process the argument string */
  va_start(ap, format);
  vprintf(format,ap);
  va_end(ap);

  putchar('\n');
  (void)fflush(stdout);

  return;
} /* end debug */

