/*
* tail.c - copy the last part of a file
*
* Version: 2008-1.01
* Build: c89 -o tail tail.c
* Source:
* Spec:
*
* This is free and unencumbered software released into the public domain,
* provided "as is", without warranty of any kind, express or implied. See the
* file UNLICENSE and the website for further details.
*/
#define _POSIX_SOURCE
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define USAGE "usage: tail [-f] [-c number] [-n number] [file]\n"
#define NOMALLOC "Unable to allocate memory for read buffer"
#define NOFOLLOW "tail: Option [-f] ignored; can only follow regular files and FIFOs\n"
#define BADBYTES "Option [-c] requires non-zero byte offset"
#define BADLINES "Option [-n] requires integer line offset"
#define CNMUTEXCL "Options [-c] and [-n] are mutually exclusive"
#define FVANISH "Followed file has vanished"
#define FSHRUNK "Followed file has shrunk"
#define SYSERR 1
#define APPERR 0
static int isdecint(char *s);
static void tailfile(int fd, char *fn);
static void tailbyteoffset(int fd, char *fn, int isregfile, ssize_t len);
static void taillineoffset(int fd, char *fn, int isregfile, ssize_t len);
static void copytoeof(int fd, char *fn);
static void fatal(int errtype, char *s);
static unsigned char* buf;
static ssize_t bufsize, offset;
static int optf, optc, optn;
int main(int argc, char **argv)
{
extern int opterr, optind;
extern char *optarg;
int c, fd;
char *fn;
setlocale(LC_ALL, "");
opterr = 0;
/*
* try to get a buffer _twice_ the size mandated by the standard as in
* later processing we may be paging through the file in _half_ buffer
* increments; this way we guarentee POSIX buffer size conformance
*/
bufsize = sysconf(_SC_LINE_MAX) * 20; /* POSIX says [{LINE_MAX)*10] */
bufsize = bufsize < 4096 ? 4096 : bufsize;
if ((buf = malloc(bufsize)) == NULL)
fatal(APPERR, NOMALLOC);
while ((c = getopt(argc, argv, "fc:n:")) != -1)
switch (c)
{
case 'f': /* follow file */
optf = 1;
break;
case 'c': /* offset in bytes */
optc = 1;
offset = 0;
if (*optarg && isdecint(optarg))
{
offset = atol(optarg);
offset = isdigit(*optarg) ? -offset : offset;
}
if (offset == 0)
fatal(APPERR, BADBYTES);
else
break;
case 'n': /* offset in lines */
optn = 1;
if (*optarg && isdecint(optarg))
{
offset = atol(optarg);
offset = isdigit(*optarg) ? -offset : offset;
/* the standard says [-n 0] is OK, but [-n +0] isn't &_& */
if (offset != 0 || (offset == 0 && *optarg != '+'))
break;
}
fatal(APPERR, BADLINES);
default:
fprintf(stderr, USAGE);
exit(1);
}
if (! optc && ! optn) /* if none, set the default options */
{
optn = 1;
offset = -10;
}
if (optc && optn)
fatal(APPERR, CNMUTEXCL);
if (optind >= argc)
tailfile(STDIN_FILENO, "stdin");
else
{
fn = argv[optind++];
if (strcmp(fn, "-") == 0)
tailfile(STDIN_FILENO, "stdin");
else
if ((fd = open(fn, O_RDONLY)) == -1)
fatal(SYSERR, fn);
else
{
tailfile(fd, fn);
if (close(fd) == -1)
fatal(SYSERR, fn);
}
}
return(0);
}
int isdecint(char *s)
{
int badch = 0;
char *c = s;
if (! (isdigit(*c) || *c == '+' || *c == '-'))
badch = 1;
else
for (c++; *c; c++)
if (! isdigit(*c))
badch = 1;
return (! badch);
}
void tailfile(int fd, char *fn)
{
struct stat sbuf;
int isregfile;
ssize_t n, len;
if (fstat(fd, &sbuf) == -1)
fatal(SYSERR, fn);
if (S_ISREG(sbuf.st_mode))
{
isregfile = 1;
len = sbuf.st_size;
}
else
isregfile = len = 0;
if (offset != 0) /* [-n 0] is permitted; but no point processing that */
{
if (optc)
tailbyteoffset(fd, fn, isregfile, len);
else
taillineoffset(fd, fn, isregfile, len);
}
if (optf)
{
if (fd == STDIN_FILENO) /* can't follow; warn then exit no error */
{
fprintf(stderr, NOFOLLOW);
exit(0);
}
while(1) /* follow forever */
{
sleep(1);
if (isregfile) /* if we can, be polite to the user */
{
if (stat(fn, &sbuf) == -1)
fatal(APPERR, FVANISH);
else if (sbuf.st_size < len)
fatal(APPERR, FSHRUNK);
len = sbuf.st_size;
}
while ((n = read(fd, buf, bufsize)) > 0)
if (write(STDOUT_FILENO, buf, n) != n)
fatal(SYSERR, "stdout");
if (n < 0)
fatal(SYSERR, fn);
}
}
}
void tailbyteoffset(int fd, char *fn, int isregfile, ssize_t len)
{
ssize_t n, halfbuf;
if (isregfile) /* should be seekable, so we'll do so; it's fastest */
{
if (offset > 0)
offset = (offset - 1) > len ? len : offset - 1;
else
offset = (len + offset) > 0 ? len + offset : 0;
if (lseek(fd, (off_t)offset, SEEK_SET) == (off_t)-1)
fatal(SYSERR, fn);
copytoeof(fd, fn);
}
else /* possibly non-seekable */
{
if (offset > 0) /* forwards through file */
{
offset--;
while(1)
{
if ((n = read(fd, buf, bufsize)) < 0)
fatal(SYSERR, fn);
if (n == 0)
offset = 0;
if (offset <= n)
break;
else
offset -= n;
}
if (write(STDOUT_FILENO, buf + offset, n - offset) != n - offset)
fatal(SYSERR, "stdout");
copytoeof(fd, fn);
}
else /* backwards through file; remember that offset is negative */
{
halfbuf = bufsize / 2;
if ((n = read(fd, buf, bufsize)) < 0)
fatal(SYSERR, fn);
if (n < bufsize) /* we've got the whole file */
{
offset = (n + offset) < 0 ? 0 : n + offset;
len = n - offset;
}
else /* we haven't got the whole file */
{
while(1) /* page through the file, half a buffer at a time */
{
memcpy(buf, buf + halfbuf, halfbuf);
if ((n = read(fd, buf + halfbuf, halfbuf)) < 0)
fatal(SYSERR, fn);
else if (n < halfbuf)
break;
}
offset = (halfbuf + n + offset) < 0 ? 0 : halfbuf + n + offset;
len = halfbuf + n - offset;
}
if (write(STDOUT_FILENO, buf + offset, len) != len)
fatal(SYSERR, "stdout");
}
}
}
void taillineoffset(int fd, char *fn, int isregfile, ssize_t len)
{
ssize_t n, i, halfbuf;
if (offset > 0) /* forwards through file */
{
offset--;
while(1)
{
if ((n = read(fd, buf, bufsize)) < 0)
fatal(SYSERR, fn);
if (n == 0)
{
offset = 0;
break;
}
for (i = 0; i < n && offset > 0; i++)
if (buf[i] == '\n')
offset--;
if (offset == 0)
{
offset = i;
break;
}
}
if (write(STDOUT_FILENO, buf + offset, n - offset) != n - offset)
fatal(SYSERR, "stdout");
copytoeof(fd, fn);
}
else /* backwards through file; remember that offset is negative */
{
if (isregfile && len > 0) /* should be seekable, so we'll do so */
{
n = (len - bufsize) < 0 ? 0 : len - bufsize;
if (lseek(fd, (off_t)n, SEEK_SET) == (off_t)-1)
fatal(SYSERR, fn);
if ((n = read(fd, buf, bufsize)) < 0)
fatal(SYSERR, fn);
if (buf[n-1] == '\n')
offset--;
for (i = n - 1; i >= 0 && offset < 0; i--)
if (buf[i] == '\n')
offset++;
if (offset == 0)
offset = i + 2;
else
offset = 0;
len = n - offset;
if (write(STDOUT_FILENO, buf + offset, len) != len)
fatal(SYSERR, "stdout");
}
else
{
halfbuf = bufsize / 2;
if ((n = read(fd, buf, bufsize)) < 0)
fatal(SYSERR, fn);
if (n < bufsize) /* we've got the whole file */
{
if (n == 0)
offset = 0;
else
{
if (buf[n-1] == '\n')
offset--;
for (i = n - 1; i >= 0 && offset < 0; i--)
if (buf[i] == '\n')
offset++;
if (offset == 0)
offset = i + 2;
else
offset = 0;
}
len = n - offset;
}
else /* we haven't got the whole file */
{
while(1) /* page through the file, half a buffer at a time */
{
memcpy(buf, buf + halfbuf, halfbuf);
if ((n = read(fd, buf + halfbuf, halfbuf)) < 0)
fatal(SYSERR, fn);
else if (n < halfbuf)
break;
}
if (buf[halfbuf+n-1] == '\n')
offset--;
for (i = halfbuf + n - 1; i >= 0 && offset < 0; i--)
if (buf[i] == '\n')
offset++;
if (offset == 0)
offset = i + 2;
else
offset = 0;
len = halfbuf + n - offset;
}
if (write(STDOUT_FILENO, buf + offset, len) != len)
fatal(SYSERR, "stdout");
}
}
}
void copytoeof(int fd, char *fn)
{
ssize_t n;
while ((n = read(fd, buf, bufsize)) > 0)
if (write(STDOUT_FILENO, buf, n) != n)
fatal(SYSERR, "stdout");
if (n < 0)
fatal(SYSERR, fn);
}
void fatal(int errtype, char *s)
{
if (errtype == SYSERR)
fprintf(stderr, "tail: %s: %s\n", s, strerror(errno));
else
fprintf(stderr, "tail: %s\n", s);
exit(1);
}