/*
 * tail.c - replaces Unix version which uses a fixed size buffer and
 * therefore can only deliver a limited number of lines.
 * The -r option (print backwards) is unimplemented, because I
 * haven't figured out the right thing to do.  Printing the entire
 * file, as the manual says, could run out of memory and probably
 * isn't what you want anyway.
 *	--phr, 25-27 June 84.
 */

#include <stdio.h>
#include <sys/file.h>
#include <errno.h>

#define DEFAULTLINES 20		/* number of lines/chars/blocks to read */
#define BLOCKSIZE 512
#define BUFSIZE (BLOCKSIZE*8)

/* modes */
#define BACKWARD 02
#define CHARS 010
#define FOREVER 020
#define HARDWAY 040

extern int errno;
char *index ();			/* why the f* do I need this? */
char *malloc (), *xmalloc ();

char *progname; /* "tail" */

main (argc, argv)
     int argc;
     char **argv;
{
  int fd;
  int mode = 0;
  char c, bgetc ();
  int number = -DEFAULTLINES;

  progname = argv[0];
	
  if (argv[1][0] == '+' || argv[1][0] == '-')
    {
      number = atoi (argv[1]);
      --argc;
      argv++;
      if (number == 0)
	number = -DEFAULTLINES;
      /* now skip over the number */
      while (index ("+-0123456789 ", argv[0][0]) != NULL)
	if (*++argv[0] == '\0')
	  break;
      while (**argv)
	{
	  switch (*argv[0]++)
	    {
	    case 'l':
	      mode &= ~CHARS;	/* do-nothing */
	      break;
	    case 'b':
	      number *= BLOCKSIZE;
	      /* fall through */
	    case 'c':
	      mode |= CHARS;
	      break;
	    case 'r':
	      mode |= BACKWARD;
	      break;
	    case 'f':
	      mode |= FOREVER;
	      break;
	    case 'H':
	      mode |= HARDWAY;
	      break;
	    }
	}
      if ((mode & (FOREVER|BACKWARD)) == (FOREVER|BACKWARD))
	{
	  fprintf (2, "%s: CAN'T print backwards forever!\n", progname);
	  exit (1);
	}
    }
  if (argc > 1 && (fd = open (argv[1], O_RDONLY)) < 0)
    {
      perror (argv[1]);
      exit (1);
    }
  tail (fd, mode, number);
  exit (0);
}

tail (f, mode, number)
     int f;
     int mode, number;
{
  char buf[BUFSIZE];
  int pos;

  if (mode & CHARS)
    {
      if ((mode & HARDWAY) ||
	  lseek (f, number, number>0 ? L_SET : L_XTND) < 0)
	{
	  hardchars (f, mode, number);
	  return 0;
	}
      if (!(mode & BACKWARD))
	{
	  dump_remainder (f, mode);
	  return 0;
	}
      /* do backward chars here (?) */
    }
  else
    {
      if ((mode & HARDWAY) ||
	  number > 0 ||
	  (pos = lseek (f, 0, L_XTND)) < 0)
	{
	  hardlines (f, mode, number);
	  return 0;
	}
      if (!(mode & BACKWARD))
	{
	  register int n;
	  /*
	   * set n to the no. of chars to be read,
	   * 0 < n <= BUFSIZE;
	   * new pos will be a multiple of BUFSIZE
	   */
	  n = (pos - 1) % BUFSIZE + 1;
	  pos -= n;
	  lseek (f, pos, L_SET);
	  n = read (f, buf, n);
	  if (n >= 0)		/* Can be 0 doing tail-f of zero-length file */
	    do
	      {
		register int j;
		for (j = n-1; j >= 0; j--)
		  {
		    if (buf[j] == '\n' && number++ >= 0)
		      {
			/* flush partial block */
			if (j != n-1)
			  write (1, &buf[j+1], n-j-1);
			dump_remainder (f, mode);
			return 0;
		      }
		  }
		/* didn't find enough newlines in that bufferload */
		if (pos == 0)
		  {
		    /* not enough lines, print entire file */
		    lseek (f, 0, L_SET);
		    dump_remainder (f, mode);
		    return 0;
		  }
		pos -= BUFSIZE;
		lseek (f, pos, L_SET);
	      } while ((n = read (f, buf, BUFSIZE)) > 0);
	}
    }
}

dump_remainder (f, mode)
     int f, mode;
{
  int n;
  char buf[BUFSIZE];

 output:
  while ((n = read (f, buf, BUFSIZE)) > 0)
    write (1, buf, n);
  if (mode & FOREVER)
    {
      sleep (1);
      goto output;
    }
}

hardlines (f, mode, number)
     int f, mode, number;
{
  struct buf { struct buf *next;
	       int nchars, linect;
	       char buf[BUFSIZE];
	     };
  typedef struct buf BUF;
  BUF *first;
  register BUF *last, *spare;
  register int i;
  int nlines;

  if (number > 0)
    return (head (f, mode, number));

  /*
   * Input is always read into a fresh buffer.
   * If there is enough room in the last buffer read, then the
   * contents of the new one are just moved into it.  This is because
   * when reading from pipes, nread can often be very small.
   *
   * If there's not enough room, link the new buffer onto the end
   * of the list, then either free up the oldest buffer for the
   * next read if that would leave enough lines, or else malloc
   * a new one.
   * Some compaction mechanism is possible but probably not worthwhile.
   */

  first = last = (BUF *) xmalloc (sizeof (BUF));
  first->nchars = first->linect = 0;
  last->nchars = last->linect = 0;
  spare = (BUF *) xmalloc (sizeof (BUF));
  nlines = 0;

  while ((spare->nchars = read (f, spare->buf, BUFSIZE)) > 0)
    {
      spare->linect = 0;
      spare->next = NULL;

      for (i = 0; i < spare->nchars; i++)
	if (spare->buf[i] == '\n')
	  ++spare->linect;
      nlines += spare->linect;
      if (spare->nchars + last->nchars < BUFSIZE)
	{
	  strncpy (&last->buf[last->nchars], spare->buf, spare->nchars);
	  last->nchars += spare->nchars;
	  last->linect += spare->linect;
	}
      else
	{
	  last = last->next = spare;
	  if (nlines - first->linect > -number)
	    {
	      spare = first;
	      nlines -= first->linect;
	      first = first->next;
	    }
	  else
	    spare = (BUF *) xmalloc (sizeof (BUF));
	}
    }

  /* now run through the list, printing lines. */
  /* first, skip over unneeded buffers */
  for (spare = first; spare != NULL; spare = spare->next)
    {
      if (nlines - spare->linect <= -number)
	break;
      nlines -= spare->linect;
    }

  if (spare == NULL)
    {
      fprintf (2, "%s: Impossible error in hardlines!  Please report bug\n", progname);
      exit (3);
    }
  /* find correct beginning, then print the rest of the file. */
  if (nlines > -number)
    {
      register char *p = spare->buf;
      /* remember: number < 0; nlines+number < spare->linect */
      for (i = 0; i < nlines + number; i++)
	/* Can't use index() -- that loses on lines containing \0 */
	while (*p++ != '\n')
	  ;
      i = p - spare->buf;
    }
  else
    i = 0;
  write (1, &spare->buf[i], spare->nchars-i);

  for (spare = spare->next; spare != NULL; spare = spare->next)
    write (1, spare->buf, spare->nchars);
}


hardchars (f, mode, number)		/* stripped down version of hardlines */
     int f, mode, number;
{
  struct buf { struct buf *next;
	       int nchars;
	       char buf[BUFSIZE];
	     };
  typedef struct buf BUF;
  BUF *first;
  register BUF *last, *spare;
  register int i;
  int nchars;

  if (number > 0)
    return (head (f, mode, number));

  /*
   * Same comment as last time, more or less.
   */

  first = last = (BUF *) xmalloc (sizeof (BUF));
  first->nchars = last->nchars = 0;
  spare = (BUF *) xmalloc (sizeof (BUF));
  nchars = 0;

  while ((spare->nchars = read (f, spare->buf, BUFSIZE)) > 0)
    {
      spare->next = NULL;

      nchars += spare->nchars;
      if (spare->nchars + last->nchars < BUFSIZE)
	{
	  strncpy (&last->buf[last->nchars], spare->buf, spare->nchars);
	  last->nchars += spare->nchars;
	}
      else
	{
	  last = last->next = spare;
	  if (nchars - first->nchars > -number)
	    {
	      spare = first;
	      nchars -= first->nchars;
	      first = first->next;
	    }
	  else
	    {
	      spare = (BUF *) xmalloc (sizeof (BUF));
	    }
	}
    }

  /* now run through the list, printing lines. */
  /* first, skip over unneeded buffers */
  for (spare = first; spare != NULL; spare = spare->next)
    {
      if (nchars - spare->nchars <= -number)
	break;
      nchars -= spare->nchars;
    }

  if (spare == NULL)
    {
      fprintf (2, "%s: Impossible error in hardchars!  Please report bug\n",
	       progname);
      exit (3);
    }
  /* find correct beginning, then print the rest of the file. */
  if (nchars > -number)
    i = nchars + number;
  else
    i = 0;
  write (1, &spare->buf[i], spare->nchars-i);

  for (spare = spare->next; spare != NULL; spare = spare->next)
    write (1, spare->buf, spare->nchars);
}

head (f, mode, number)
     int f, mode, number;
{
  int n, nread;
  register int i;
  char buf[BUFSIZE];

  if (number <= 0)
    return 0;

  while (nread < number)
    {
      n = read (f, buf, BUFSIZE);
      if (n <= 0)
	break;
      if (mode & CHARS)
	nread += n;
      else
	for (i = 0; i < n; i++)
	  if (buf[i] == '\n')
	    if (++nread >= number-1)
	      goto out;
    }
 out:
  if (mode & CHARS)
    {
      if (nread > number)
	write (1, &buf[n-(nread-number)], nread-number);
    }
  else
    {
      if (++i < n)	/* skip over newline */
	write (1, &buf[i], n-i);
    }
  dump_remainder (f, mode);
}

char *
xmalloc (size)
     int size;
{
  register char *val;
  val = malloc (size);
  if (val) return (val);
  fprintf (2, "%s: memory exhausted.\n", progname);
  exit (3);
}
