/* tail - copy the end of a file Author: Norbert Schlenker */ /* Syntax: tail [-f] [-c number | -n number] [file] * tail -[number][c|l][f] [file] (obsolescent) * tail +[number][c|l][f] [file] (obsolescent) * Flags: * -c number Measure starting point in bytes. If number begins * with '+', the starting point is relative to the * the file's beginning. If number begins with '-' * or has no sign, the starting point is relative to * the end of the file. * -f Keep trying to read after EOF on files and FIFOs. * -n number Measure starting point in lines. The number * following the flag has significance similar to * that described for the -c flag. * * If neither -c nor -n are specified, the default is tail -n 10. * * In the obsolescent syntax, an argument with a 'c' following the * (optional) number is equivalent to "-c number" in the standard * syntax, with number including the leading sign ('+' or '-') of the * argument. An argument with 'l' following the number is equivalent * to "-n number" in the standard syntax. If the number is not * specified, 10 is used as the default. If neither 'c' nor 'l' are * specified, 'l' is assumed. The character 'f' may be suffixed to * the argument and is equivalent to specifying "-f" in the standard * syntax. Look for lines marked "OBSOLESCENT". * * If no file is specified, standard input is assumed. * * P1003.2 does not specify tail's behavior when a count of 0 is given. * It also does not specify clearly whether the first byte (line) of a * file should be numbered 0 or 1. Historical behavior is that the * first byte is actually number 1 (contrary to all Unix standards). * Historically, a count of 0 (or -0) results in no output whatsoever, * while a count of +0 results in the entire file being copied (just like * +1). The implementor does not agree with these behaviors, but has * copied them slavishly. Look for lines marked "HISTORICAL". * * Author: Norbert Schlenker * Copyright: None. Released to the public domain. * Reference: P1003.2 section 4.59 (draft 10) * Notes: Under Minix, this program requires chmem =30000. * Bugs: No internationalization support; all messages are in English. */ /* Force visible Posix names */ #ifndef _POSIX_SOURCE #define _POSIX_SOURCE 1 #endif /* External interfaces */ #include #include #include #include #include #include /* External interfaces that should have been standardized into */ extern char *optarg; extern int optind; /* We expect this constant to be defined in in a Posix program, * but we'll specify it here just in case it's been left out. */ #ifndef LINE_MAX #define LINE_MAX 2048 /* minimum acceptable lower bound */ #endif /* Magic numbers suggested or required by Posix specification */ #define SUCCESS 0 /* exit code in case of success */ #define FAILURE 1 /* or failure */ #define DEFAULT_COUNT 10 /* default number of lines or bytes */ #define MIN_BUFSIZE (LINE_MAX * DEFAULT_COUNT) #define SLEEP_INTERVAL 1 /* sleep for one second intervals with -f */ #define FALSE 0 #define TRUE 1 /* Internal functions - prototyped under Minix */ _PROTOTYPE(int main, (int argc, char **argv)); _PROTOTYPE(int tail, (int count, int bytes, int read_until_killed)); _PROTOTYPE(int keep_reading, (void)); _PROTOTYPE(void usage, (void)); int main(argc, argv) int argc; char *argv[]; { int cflag = FALSE; int nflag = FALSE; int fflag = FALSE; int number = -DEFAULT_COUNT; char *suffix; int opt; struct stat stat_buf; /* Determining whether this invocation is via the standard syntax or * via an obsolescent one is a nasty kludge. Here it is, but there is * no pretense at elegance. */ if (argc == 1) { /* simple: default read of a pipe */ exit(tail(-DEFAULT_COUNT, 0, fflag)); } if ((argv[1][0] == '+') || /* OBSOLESCENT */ (argv[1][0] == '-' && ((isdigit(argv[1][1])) || (argv[1][1] == 'l') || (argv[1][1] == 'c' && argv[1][2] == 'f')))) { --argc; ++argv; if (isdigit(argv[0][1])) { number = (int)strtol(argv[0], &suffix, 10); if (number == 0) { /* HISTORICAL */ if (argv[0][0] == '+') number = 1; else exit(SUCCESS); } } else { number = (argv[0][0] == '+') ? DEFAULT_COUNT : -DEFAULT_COUNT; suffix = &(argv[0][1]); } if (*suffix != '\0') { if (*suffix == 'c') { cflag = TRUE; ++suffix; } else if (*suffix == 'l') { nflag = TRUE; ++suffix; } } if (*suffix != '\0') { if (*suffix == 'f') { fflag = TRUE; ++suffix; } } if (*suffix != '\0') { /* bad form: assume to be a file name */ number = -DEFAULT_COUNT; cflag = nflag = FALSE; fflag = FALSE; } else { --argc; ++argv; } } else { /* new standard syntax */ while ((opt = getopt(argc, argv, "c:fn:")) != EOF) { switch (opt) { case 'c': cflag = TRUE; if (*optarg == '+' || *optarg == '-') number = atoi(optarg); else if (isdigit(*optarg)) number = -atoi(optarg); else usage(); if (number == 0) { /* HISTORICAL */ if (*optarg == '+') number = 1; else exit(SUCCESS); } break; case 'f': fflag = TRUE; break; case 'n': nflag = TRUE; if (*optarg == '+' || *optarg == '-') number = atoi(optarg); else if (isdigit(*optarg)) number = -atoi(optarg); else usage(); if (number == 0) { /* HISTORICAL */ if (*optarg == '+') number = 1; else exit(SUCCESS); } break; default: usage(); /* NOTREACHED */ } } argc -= optind; argv += optind; } if (argc > 1 || /* too many arguments */ (cflag && nflag)) { /* both bytes and lines specified */ usage(); } if (argc > 0) { /* an actual file */ if (freopen(argv[0], "r", stdin) != stdin) { fputs("tail: could not open ", stderr); fputs(argv[0], stderr); fputs("\n", stderr); exit(FAILURE); } /* There is an optimization possibility here. If a file is being * read, we need not look at the front of it. If we seek backwards * from the end, we can (potentially) avoid looking at most of the * file. Some systems fail when asked to seek backwards to a point * before the start of the file, so we avoid that possibility. */ if (number < 0 && fstat(fileno(stdin), &stat_buf) == 0) { long offset = cflag ? (long)number : (long)number * LINE_MAX; if (-offset < stat_buf.st_size) fseek(stdin, offset, SEEK_END); } } else { fflag = FALSE; /* force -f off when reading a pipe */ } exit(tail(number, cflag, fflag)); /* NOTREACHED */ } int tail(count, bytes, read_until_killed) int count; /* lines or bytes desired */ int bytes; /* TRUE if we want bytes */ int read_until_killed; /* keep reading at EOF */ { int c; char *buf; /* pointer to input buffer */ char *buf_end; /* and one past its end */ char *start; /* pointer to first desired character in buf */ char *finish; /* pointer past last desired character */ int wrapped_once = FALSE; /* TRUE after buf has been filled once */ /* This is magic. If count is positive, it means start at the count'th * line or byte, with the first line or byte considered number 1. Thus, * we want to SKIP one less line or byte than the number specified. In * the negative case, we look backward from the end of the file for the * (count + 1)'th newline or byte, so we really want the count to be one * LARGER than was specified (in absolute value). In either case, the * right thing to do is: */ --count; /* Count is positive: skip the desired lines or bytes and then copy. */ if (count >= 0) { while (count > 0 && (c = getchar()) != EOF) { if (bytes || c == '\n') --count; } while ((c = getchar()) != EOF) { if (putchar(c) == EOF) return FAILURE; } if (read_until_killed) return keep_reading(); return ferror(stdin) ? FAILURE : SUCCESS; } /* Count is negative: allocate a reasonably large buffer. */ if ((buf = (char *)malloc(MIN_BUFSIZE + 1)) == (char *)NULL) { fputs("tail: out of memory\n", stderr); return FAILURE; } buf_end = buf + (MIN_BUFSIZE + 1); /* Read the entire file into the buffer. */ finish = buf; while ((c = getchar()) != EOF) { *finish++ = c; if (finish == buf_end) { finish = buf; wrapped_once = TRUE; } } if (ferror(stdin)) return FAILURE; /* Back up inside the buffer. The count has already been adjusted to * back up exactly one character too far, so we will bump the buffer * pointer once after we're done. * * BUG: For large line counts, the buffer may not be large enough to * hold all the lines. The specification allows the program to * fail in such a case - this program will simply dump the entire * buffer's contents as its best attempt at the desired behavior. */ if (finish != buf || wrapped_once) { /* file was not empty */ start = (finish == buf) ? buf_end - 1 : finish - 1; while (start != finish) { if ((bytes || *start == '\n') && ++count == 0) break; if (start == buf) { start = buf_end - 1; if (!wrapped_once) /* never wrapped: stop now */ break; } else { --start; } } if (++start == buf_end) { /* bump after going too far */ start = buf; } if (finish > start) { fwrite(start, 1, finish - start, stdout); } else { fwrite(start, 1, buf_end - start, stdout); fwrite(buf, 1, finish - buf, stdout); } } if (read_until_killed) return keep_reading(); return ferror(stdout) ? FAILURE : SUCCESS; } /* Wake at intervals to reread standard input. Copy anything read to * standard output and then go to sleep again. */ int keep_reading() { char buf[1024]; int n; int i; off_t pos; struct stat st; pos = lseek(0, (off_t) 0, SEEK_CUR); for (;;) { for (i = 0; i < 60; i++) { while ((n = read(0, buf, sizeof(buf))) > 0) { if (write(1, buf, n) < 0) return FAILURE; } if (n < 0) return FAILURE; sleep(SLEEP_INTERVAL); } /* Rewind if suddenly truncated. */ if (pos != -1) { if (fstat(0, &st) == -1) { pos = -1; } else if (st.st_size < pos) { pos = lseek(0, (off_t) 0, SEEK_SET); } else { pos = st.st_size; } } } } /* Tell the user the standard syntax. */ void usage() { fputs("Usage: tail [-f] [-c number | -n number] [file]\n", stderr); exit(FAILURE); }