2 * tail.c - copy the last part of a file
5 * Build: c89 -o tail tail.c
6 * Source: <http://pdcore.sourceforge.net/>
7 * Spec: <http://www.opengroup.org/onlinepubs/9699919799/utilities/tail.html>
9 * This is free and unencumbered software released into the public domain,
10 * provided "as is", without warranty of any kind, express or implied. See the
11 * file UNLICENSE and the website <http://unlicense.org> for further details.
25 #include <sys/types.h>
28 #define USAGE "usage: tail [-f] [-c number] [-n number] [file]\n"
29 #define NOMALLOC "Unable to allocate memory for read buffer"
30 #define NOFOLLOW "tail: Option [-f] ignored; can only follow regular files and FIFOs\n"
31 #define BADBYTES "Option [-c] requires non-zero byte offset"
32 #define BADLINES "Option [-n] requires integer line offset"
33 #define CNMUTEXCL "Options [-c] and [-n] are mutually exclusive"
34 #define FVANISH "Followed file has vanished"
35 #define FSHRUNK "Followed file has shrunk"
39 static int isdecint(char *s);
40 static void tailfile(int fd, char *fn);
41 static void tailbyteoffset(int fd, char *fn, int isregfile, ssize_t len);
42 static void taillineoffset(int fd, char *fn, int isregfile, ssize_t len);
43 static void copytoeof(int fd, char *fn);
44 static void fatal(int errtype, char *s);
46 static unsigned char* buf;
47 static ssize_t bufsize, offset;
48 static int optf, optc, optn;
51 int main(int argc, char **argv)
53 extern int opterr, optind;
58 setlocale(LC_ALL, "");
62 * try to get a buffer _twice_ the size mandated by the standard as in
63 * later processing we may be paging through the file in _half_ buffer
64 * increments; this way we guarentee POSIX buffer size conformance
66 bufsize = sysconf(_SC_LINE_MAX) * 20; /* POSIX says [{LINE_MAX)*10] */
67 bufsize = bufsize < 4096 ? 4096 : bufsize;
69 if ((buf = malloc(bufsize)) == NULL)
70 fatal(APPERR, NOMALLOC);
72 while ((c = getopt(argc, argv, "fc:n:")) != -1)
75 case 'f': /* follow file */
79 case 'c': /* offset in bytes */
82 if (*optarg && isdecint(optarg))
84 offset = atol(optarg);
85 offset = isdigit(*optarg) ? -offset : offset;
89 fatal(APPERR, BADBYTES);
93 case 'n': /* offset in lines */
95 if (*optarg && isdecint(optarg))
97 offset = atol(optarg);
98 offset = isdigit(*optarg) ? -offset : offset;
100 /* the standard says [-n 0] is OK, but [-n +0] isn't &_& */
101 if (offset != 0 || (offset == 0 && *optarg != '+'))
104 fatal(APPERR, BADLINES);
107 fprintf(stderr, USAGE);
111 if (! optc && ! optn) /* if none, set the default options */
118 fatal(APPERR, CNMUTEXCL);
121 tailfile(STDIN_FILENO, "stdin");
126 if (strcmp(fn, "-") == 0)
127 tailfile(STDIN_FILENO, "stdin");
129 if ((fd = open(fn, O_RDONLY)) == -1)
143 int isdecint(char *s)
148 if (! (isdigit(*c) || *c == '+' || *c == '-'))
159 void tailfile(int fd, char *fn)
165 if (fstat(fd, &sbuf) == -1)
168 if (S_ISREG(sbuf.st_mode))
176 if (offset != 0) /* [-n 0] is permitted; but no point processing that */
179 tailbyteoffset(fd, fn, isregfile, len);
181 taillineoffset(fd, fn, isregfile, len);
186 if (fd == STDIN_FILENO) /* can't follow; warn then exit no error */
188 fprintf(stderr, NOFOLLOW);
192 while(1) /* follow forever */
196 if (isregfile) /* if we can, be polite to the user */
198 if (stat(fn, &sbuf) == -1)
199 fatal(APPERR, FVANISH);
200 else if (sbuf.st_size < len)
201 fatal(APPERR, FSHRUNK);
206 while ((n = read(fd, buf, bufsize)) > 0)
207 if (write(STDOUT_FILENO, buf, n) != n)
208 fatal(SYSERR, "stdout");
217 void tailbyteoffset(int fd, char *fn, int isregfile, ssize_t len)
221 if (isregfile) /* should be seekable, so we'll do so; it's fastest */
224 offset = (offset - 1) > len ? len : offset - 1;
226 offset = (len + offset) > 0 ? len + offset : 0;
228 if (lseek(fd, (off_t)offset, SEEK_SET) == (off_t)-1)
234 else /* possibly non-seekable */
236 if (offset > 0) /* forwards through file */
242 if ((n = read(fd, buf, bufsize)) < 0)
254 if (write(STDOUT_FILENO, buf + offset, n - offset) != n - offset)
255 fatal(SYSERR, "stdout");
260 else /* backwards through file; remember that offset is negative */
262 halfbuf = bufsize / 2;
264 if ((n = read(fd, buf, bufsize)) < 0)
267 if (n < bufsize) /* we've got the whole file */
269 offset = (n + offset) < 0 ? 0 : n + offset;
273 else /* we haven't got the whole file */
275 while(1) /* page through the file, half a buffer at a time */
277 memcpy(buf, buf + halfbuf, halfbuf);
279 if ((n = read(fd, buf + halfbuf, halfbuf)) < 0)
281 else if (n < halfbuf)
285 offset = (halfbuf + n + offset) < 0 ? 0 : halfbuf + n + offset;
286 len = halfbuf + n - offset;
289 if (write(STDOUT_FILENO, buf + offset, len) != len)
290 fatal(SYSERR, "stdout");
296 void taillineoffset(int fd, char *fn, int isregfile, ssize_t len)
298 ssize_t n, i, halfbuf;
300 if (offset > 0) /* forwards through file */
306 if ((n = read(fd, buf, bufsize)) < 0)
315 for (i = 0; i < n && offset > 0; i++)
326 if (write(STDOUT_FILENO, buf + offset, n - offset) != n - offset)
327 fatal(SYSERR, "stdout");
332 else /* backwards through file; remember that offset is negative */
334 if (isregfile && len > 0) /* should be seekable, so we'll do so */
336 n = (len - bufsize) < 0 ? 0 : len - bufsize;
338 if (lseek(fd, (off_t)n, SEEK_SET) == (off_t)-1)
341 if ((n = read(fd, buf, bufsize)) < 0)
344 if (buf[n-1] == '\n')
347 for (i = n - 1; i >= 0 && offset < 0; i--)
358 if (write(STDOUT_FILENO, buf + offset, len) != len)
359 fatal(SYSERR, "stdout");
364 halfbuf = bufsize / 2;
366 if ((n = read(fd, buf, bufsize)) < 0)
369 if (n < bufsize) /* we've got the whole file */
375 if (buf[n-1] == '\n')
378 for (i = n - 1; i >= 0 && offset < 0; i--)
391 else /* we haven't got the whole file */
393 while(1) /* page through the file, half a buffer at a time */
395 memcpy(buf, buf + halfbuf, halfbuf);
397 if ((n = read(fd, buf + halfbuf, halfbuf)) < 0)
399 else if (n < halfbuf)
403 if (buf[halfbuf+n-1] == '\n')
406 for (i = halfbuf + n - 1; i >= 0 && offset < 0; i--)
415 len = halfbuf + n - offset;
418 if (write(STDOUT_FILENO, buf + offset, len) != len)
419 fatal(SYSERR, "stdout");
425 void copytoeof(int fd, char *fn)
429 while ((n = read(fd, buf, bufsize)) > 0)
430 if (write(STDOUT_FILENO, buf, n) != n)
431 fatal(SYSERR, "stdout");
438 void fatal(int errtype, char *s)
440 if (errtype == SYSERR)
441 fprintf(stderr, "tail: %s: %s\n", s, strerror(errno));
443 fprintf(stderr, "tail: %s\n", s);