[9] | 1 | /* tail - copy the end of a file Author: Norbert Schlenker */
|
---|
| 2 |
|
---|
| 3 | /* Syntax: tail [-f] [-c number | -n number] [file]
|
---|
| 4 | * tail -[number][c|l][f] [file] (obsolescent)
|
---|
| 5 | * tail +[number][c|l][f] [file] (obsolescent)
|
---|
| 6 | * Flags:
|
---|
| 7 | * -c number Measure starting point in bytes. If number begins
|
---|
| 8 | * with '+', the starting point is relative to the
|
---|
| 9 | * the file's beginning. If number begins with '-'
|
---|
| 10 | * or has no sign, the starting point is relative to
|
---|
| 11 | * the end of the file.
|
---|
| 12 | * -f Keep trying to read after EOF on files and FIFOs.
|
---|
| 13 | * -n number Measure starting point in lines. The number
|
---|
| 14 | * following the flag has significance similar to
|
---|
| 15 | * that described for the -c flag.
|
---|
| 16 | *
|
---|
| 17 | * If neither -c nor -n are specified, the default is tail -n 10.
|
---|
| 18 | *
|
---|
| 19 | * In the obsolescent syntax, an argument with a 'c' following the
|
---|
| 20 | * (optional) number is equivalent to "-c number" in the standard
|
---|
| 21 | * syntax, with number including the leading sign ('+' or '-') of the
|
---|
| 22 | * argument. An argument with 'l' following the number is equivalent
|
---|
| 23 | * to "-n number" in the standard syntax. If the number is not
|
---|
| 24 | * specified, 10 is used as the default. If neither 'c' nor 'l' are
|
---|
| 25 | * specified, 'l' is assumed. The character 'f' may be suffixed to
|
---|
| 26 | * the argument and is equivalent to specifying "-f" in the standard
|
---|
| 27 | * syntax. Look for lines marked "OBSOLESCENT".
|
---|
| 28 | *
|
---|
| 29 | * If no file is specified, standard input is assumed.
|
---|
| 30 | *
|
---|
| 31 | * P1003.2 does not specify tail's behavior when a count of 0 is given.
|
---|
| 32 | * It also does not specify clearly whether the first byte (line) of a
|
---|
| 33 | * file should be numbered 0 or 1. Historical behavior is that the
|
---|
| 34 | * first byte is actually number 1 (contrary to all Unix standards).
|
---|
| 35 | * Historically, a count of 0 (or -0) results in no output whatsoever,
|
---|
| 36 | * while a count of +0 results in the entire file being copied (just like
|
---|
| 37 | * +1). The implementor does not agree with these behaviors, but has
|
---|
| 38 | * copied them slavishly. Look for lines marked "HISTORICAL".
|
---|
| 39 | *
|
---|
| 40 | * Author: Norbert Schlenker
|
---|
| 41 | * Copyright: None. Released to the public domain.
|
---|
| 42 | * Reference: P1003.2 section 4.59 (draft 10)
|
---|
| 43 | * Notes: Under Minix, this program requires chmem =30000.
|
---|
| 44 | * Bugs: No internationalization support; all messages are in English.
|
---|
| 45 | */
|
---|
| 46 |
|
---|
| 47 | /* Force visible Posix names */
|
---|
| 48 | #ifndef _POSIX_SOURCE
|
---|
| 49 | #define _POSIX_SOURCE 1
|
---|
| 50 | #endif
|
---|
| 51 |
|
---|
| 52 | /* External interfaces */
|
---|
| 53 | #include <sys/types.h>
|
---|
| 54 | #include <sys/stat.h>
|
---|
| 55 | #include <unistd.h>
|
---|
| 56 | #include <ctype.h>
|
---|
| 57 | #include <stdlib.h>
|
---|
| 58 | #include <stdio.h>
|
---|
| 59 |
|
---|
| 60 | /* External interfaces that should have been standardized into <getopt.h> */
|
---|
| 61 | extern char *optarg;
|
---|
| 62 | extern int optind;
|
---|
| 63 |
|
---|
| 64 | /* We expect this constant to be defined in <limits.h> in a Posix program,
|
---|
| 65 | * but we'll specify it here just in case it's been left out.
|
---|
| 66 | */
|
---|
| 67 | #ifndef LINE_MAX
|
---|
| 68 | #define LINE_MAX 2048 /* minimum acceptable lower bound */
|
---|
| 69 | #endif
|
---|
| 70 |
|
---|
| 71 | /* Magic numbers suggested or required by Posix specification */
|
---|
| 72 | #define SUCCESS 0 /* exit code in case of success */
|
---|
| 73 | #define FAILURE 1 /* or failure */
|
---|
| 74 | #define DEFAULT_COUNT 10 /* default number of lines or bytes */
|
---|
| 75 | #define MIN_BUFSIZE (LINE_MAX * DEFAULT_COUNT)
|
---|
| 76 | #define SLEEP_INTERVAL 1 /* sleep for one second intervals with -f */
|
---|
| 77 |
|
---|
| 78 | #define FALSE 0
|
---|
| 79 | #define TRUE 1
|
---|
| 80 |
|
---|
| 81 | /* Internal functions - prototyped under Minix */
|
---|
| 82 | _PROTOTYPE(int main, (int argc, char **argv));
|
---|
| 83 | _PROTOTYPE(int tail, (int count, int bytes, int read_until_killed));
|
---|
| 84 | _PROTOTYPE(int keep_reading, (void));
|
---|
| 85 | _PROTOTYPE(void usage, (void));
|
---|
| 86 |
|
---|
| 87 | int main(argc, argv)
|
---|
| 88 | int argc;
|
---|
| 89 | char *argv[];
|
---|
| 90 | {
|
---|
| 91 | int cflag = FALSE;
|
---|
| 92 | int nflag = FALSE;
|
---|
| 93 | int fflag = FALSE;
|
---|
| 94 | int number = -DEFAULT_COUNT;
|
---|
| 95 | char *suffix;
|
---|
| 96 | int opt;
|
---|
| 97 | struct stat stat_buf;
|
---|
| 98 |
|
---|
| 99 | /* Determining whether this invocation is via the standard syntax or
|
---|
| 100 | * via an obsolescent one is a nasty kludge. Here it is, but there is
|
---|
| 101 | * no pretense at elegance.
|
---|
| 102 | */
|
---|
| 103 | if (argc == 1) { /* simple: default read of a pipe */
|
---|
| 104 | exit(tail(-DEFAULT_COUNT, 0, fflag));
|
---|
| 105 | }
|
---|
| 106 | if ((argv[1][0] == '+') || /* OBSOLESCENT */
|
---|
| 107 | (argv[1][0] == '-' && ((isdigit(argv[1][1])) ||
|
---|
| 108 | (argv[1][1] == 'l') ||
|
---|
| 109 | (argv[1][1] == 'c' && argv[1][2] == 'f')))) {
|
---|
| 110 | --argc; ++argv;
|
---|
| 111 | if (isdigit(argv[0][1])) {
|
---|
| 112 | number = (int)strtol(argv[0], &suffix, 10);
|
---|
| 113 | if (number == 0) { /* HISTORICAL */
|
---|
| 114 | if (argv[0][0] == '+')
|
---|
| 115 | number = 1;
|
---|
| 116 | else
|
---|
| 117 | exit(SUCCESS);
|
---|
| 118 | }
|
---|
| 119 | } else {
|
---|
| 120 | number = (argv[0][0] == '+') ? DEFAULT_COUNT : -DEFAULT_COUNT;
|
---|
| 121 | suffix = &(argv[0][1]);
|
---|
| 122 | }
|
---|
| 123 | if (*suffix != '\0') {
|
---|
| 124 | if (*suffix == 'c') {
|
---|
| 125 | cflag = TRUE;
|
---|
| 126 | ++suffix;
|
---|
| 127 | }
|
---|
| 128 | else
|
---|
| 129 | if (*suffix == 'l') {
|
---|
| 130 | nflag = TRUE;
|
---|
| 131 | ++suffix;
|
---|
| 132 | }
|
---|
| 133 | }
|
---|
| 134 | if (*suffix != '\0') {
|
---|
| 135 | if (*suffix == 'f') {
|
---|
| 136 | fflag = TRUE;
|
---|
| 137 | ++suffix;
|
---|
| 138 | }
|
---|
| 139 | }
|
---|
| 140 | if (*suffix != '\0') { /* bad form: assume to be a file name */
|
---|
| 141 | number = -DEFAULT_COUNT;
|
---|
| 142 | cflag = nflag = FALSE;
|
---|
| 143 | fflag = FALSE;
|
---|
| 144 | } else {
|
---|
| 145 | --argc; ++argv;
|
---|
| 146 | }
|
---|
| 147 | } else { /* new standard syntax */
|
---|
| 148 | while ((opt = getopt(argc, argv, "c:fn:")) != EOF) {
|
---|
| 149 | switch (opt) {
|
---|
| 150 | case 'c':
|
---|
| 151 | cflag = TRUE;
|
---|
| 152 | if (*optarg == '+' || *optarg == '-')
|
---|
| 153 | number = atoi(optarg);
|
---|
| 154 | else
|
---|
| 155 | if (isdigit(*optarg))
|
---|
| 156 | number = -atoi(optarg);
|
---|
| 157 | else
|
---|
| 158 | usage();
|
---|
| 159 | if (number == 0) { /* HISTORICAL */
|
---|
| 160 | if (*optarg == '+')
|
---|
| 161 | number = 1;
|
---|
| 162 | else
|
---|
| 163 | exit(SUCCESS);
|
---|
| 164 | }
|
---|
| 165 | break;
|
---|
| 166 | case 'f':
|
---|
| 167 | fflag = TRUE;
|
---|
| 168 | break;
|
---|
| 169 | case 'n':
|
---|
| 170 | nflag = TRUE;
|
---|
| 171 | if (*optarg == '+' || *optarg == '-')
|
---|
| 172 | number = atoi(optarg);
|
---|
| 173 | else
|
---|
| 174 | if (isdigit(*optarg))
|
---|
| 175 | number = -atoi(optarg);
|
---|
| 176 | else
|
---|
| 177 | usage();
|
---|
| 178 | if (number == 0) { /* HISTORICAL */
|
---|
| 179 | if (*optarg == '+')
|
---|
| 180 | number = 1;
|
---|
| 181 | else
|
---|
| 182 | exit(SUCCESS);
|
---|
| 183 | }
|
---|
| 184 | break;
|
---|
| 185 | default:
|
---|
| 186 | usage();
|
---|
| 187 | /* NOTREACHED */
|
---|
| 188 | }
|
---|
| 189 | }
|
---|
| 190 | argc -= optind;
|
---|
| 191 | argv += optind;
|
---|
| 192 | }
|
---|
| 193 |
|
---|
| 194 | if (argc > 1 || /* too many arguments */
|
---|
| 195 | (cflag && nflag)) { /* both bytes and lines specified */
|
---|
| 196 | usage();
|
---|
| 197 | }
|
---|
| 198 |
|
---|
| 199 | if (argc > 0) { /* an actual file */
|
---|
| 200 | if (freopen(argv[0], "r", stdin) != stdin) {
|
---|
| 201 | fputs("tail: could not open ", stderr);
|
---|
| 202 | fputs(argv[0], stderr);
|
---|
| 203 | fputs("\n", stderr);
|
---|
| 204 | exit(FAILURE);
|
---|
| 205 | }
|
---|
| 206 | /* There is an optimization possibility here. If a file is being
|
---|
| 207 | * read, we need not look at the front of it. If we seek backwards
|
---|
| 208 | * from the end, we can (potentially) avoid looking at most of the
|
---|
| 209 | * file. Some systems fail when asked to seek backwards to a point
|
---|
| 210 | * before the start of the file, so we avoid that possibility.
|
---|
| 211 | */
|
---|
| 212 | if (number < 0 && fstat(fileno(stdin), &stat_buf) == 0) {
|
---|
| 213 | long offset = cflag ? (long)number : (long)number * LINE_MAX;
|
---|
| 214 |
|
---|
| 215 | if (-offset < stat_buf.st_size)
|
---|
| 216 | fseek(stdin, offset, SEEK_END);
|
---|
| 217 | }
|
---|
| 218 | } else {
|
---|
| 219 | fflag = FALSE; /* force -f off when reading a pipe */
|
---|
| 220 | }
|
---|
| 221 | exit(tail(number, cflag, fflag));
|
---|
| 222 | /* NOTREACHED */
|
---|
| 223 | }
|
---|
| 224 |
|
---|
| 225 | int tail(count, bytes, read_until_killed)
|
---|
| 226 | int count; /* lines or bytes desired */
|
---|
| 227 | int bytes; /* TRUE if we want bytes */
|
---|
| 228 | int read_until_killed; /* keep reading at EOF */
|
---|
| 229 | {
|
---|
| 230 | int c;
|
---|
| 231 | char *buf; /* pointer to input buffer */
|
---|
| 232 | char *buf_end; /* and one past its end */
|
---|
| 233 | char *start; /* pointer to first desired character in buf */
|
---|
| 234 | char *finish; /* pointer past last desired character */
|
---|
| 235 | int wrapped_once = FALSE; /* TRUE after buf has been filled once */
|
---|
| 236 |
|
---|
| 237 | /* This is magic. If count is positive, it means start at the count'th
|
---|
| 238 | * line or byte, with the first line or byte considered number 1. Thus,
|
---|
| 239 | * we want to SKIP one less line or byte than the number specified. In
|
---|
| 240 | * the negative case, we look backward from the end of the file for the
|
---|
| 241 | * (count + 1)'th newline or byte, so we really want the count to be one
|
---|
| 242 | * LARGER than was specified (in absolute value). In either case, the
|
---|
| 243 | * right thing to do is:
|
---|
| 244 | */
|
---|
| 245 | --count;
|
---|
| 246 |
|
---|
| 247 | /* Count is positive: skip the desired lines or bytes and then copy. */
|
---|
| 248 | if (count >= 0) {
|
---|
| 249 | while (count > 0 && (c = getchar()) != EOF) {
|
---|
| 250 | if (bytes || c == '\n')
|
---|
| 251 | --count;
|
---|
| 252 | }
|
---|
| 253 | while ((c = getchar()) != EOF) {
|
---|
| 254 | if (putchar(c) == EOF)
|
---|
| 255 | return FAILURE;
|
---|
| 256 | }
|
---|
| 257 | if (read_until_killed)
|
---|
| 258 | return keep_reading();
|
---|
| 259 | return ferror(stdin) ? FAILURE : SUCCESS;
|
---|
| 260 | }
|
---|
| 261 |
|
---|
| 262 | /* Count is negative: allocate a reasonably large buffer. */
|
---|
| 263 | if ((buf = (char *)malloc(MIN_BUFSIZE + 1)) == (char *)NULL) {
|
---|
| 264 | fputs("tail: out of memory\n", stderr);
|
---|
| 265 | return FAILURE;
|
---|
| 266 | }
|
---|
| 267 | buf_end = buf + (MIN_BUFSIZE + 1);
|
---|
| 268 |
|
---|
| 269 | /* Read the entire file into the buffer. */
|
---|
| 270 | finish = buf;
|
---|
| 271 | while ((c = getchar()) != EOF) {
|
---|
| 272 | *finish++ = c;
|
---|
| 273 | if (finish == buf_end) {
|
---|
| 274 | finish = buf;
|
---|
| 275 | wrapped_once = TRUE;
|
---|
| 276 | }
|
---|
| 277 | }
|
---|
| 278 | if (ferror(stdin))
|
---|
| 279 | return FAILURE;
|
---|
| 280 |
|
---|
| 281 | /* Back up inside the buffer. The count has already been adjusted to
|
---|
| 282 | * back up exactly one character too far, so we will bump the buffer
|
---|
| 283 | * pointer once after we're done.
|
---|
| 284 | *
|
---|
| 285 | * BUG: For large line counts, the buffer may not be large enough to
|
---|
| 286 | * hold all the lines. The specification allows the program to
|
---|
| 287 | * fail in such a case - this program will simply dump the entire
|
---|
| 288 | * buffer's contents as its best attempt at the desired behavior.
|
---|
| 289 | */
|
---|
| 290 | if (finish != buf || wrapped_once) { /* file was not empty */
|
---|
| 291 | start = (finish == buf) ? buf_end - 1 : finish - 1;
|
---|
| 292 | while (start != finish) {
|
---|
| 293 | if ((bytes || *start == '\n') && ++count == 0)
|
---|
| 294 | break;
|
---|
| 295 | if (start == buf) {
|
---|
| 296 | start = buf_end - 1;
|
---|
| 297 | if (!wrapped_once) /* never wrapped: stop now */
|
---|
| 298 | break;
|
---|
| 299 | } else {
|
---|
| 300 | --start;
|
---|
| 301 | }
|
---|
| 302 | }
|
---|
| 303 | if (++start == buf_end) { /* bump after going too far */
|
---|
| 304 | start = buf;
|
---|
| 305 | }
|
---|
| 306 | if (finish > start) {
|
---|
| 307 | fwrite(start, 1, finish - start, stdout);
|
---|
| 308 | } else {
|
---|
| 309 | fwrite(start, 1, buf_end - start, stdout);
|
---|
| 310 | fwrite(buf, 1, finish - buf, stdout);
|
---|
| 311 | }
|
---|
| 312 | }
|
---|
| 313 | if (read_until_killed)
|
---|
| 314 | return keep_reading();
|
---|
| 315 | return ferror(stdout) ? FAILURE : SUCCESS;
|
---|
| 316 | }
|
---|
| 317 |
|
---|
| 318 | /* Wake at intervals to reread standard input. Copy anything read to
|
---|
| 319 | * standard output and then go to sleep again.
|
---|
| 320 | */
|
---|
| 321 | int keep_reading()
|
---|
| 322 | {
|
---|
| 323 | char buf[1024];
|
---|
| 324 | int n;
|
---|
| 325 | int i;
|
---|
| 326 | off_t pos;
|
---|
| 327 | struct stat st;
|
---|
| 328 |
|
---|
| 329 | pos = lseek(0, (off_t) 0, SEEK_CUR);
|
---|
| 330 | for (;;) {
|
---|
| 331 | for (i = 0; i < 60; i++) {
|
---|
| 332 | while ((n = read(0, buf, sizeof(buf))) > 0) {
|
---|
| 333 | if (write(1, buf, n) < 0) return FAILURE;
|
---|
| 334 | }
|
---|
| 335 | if (n < 0) return FAILURE;
|
---|
| 336 |
|
---|
| 337 | sleep(SLEEP_INTERVAL);
|
---|
| 338 | }
|
---|
| 339 |
|
---|
| 340 | /* Rewind if suddenly truncated. */
|
---|
| 341 | if (pos != -1) {
|
---|
| 342 | if (fstat(0, &st) == -1) {
|
---|
| 343 | pos = -1;
|
---|
| 344 | } else
|
---|
| 345 | if (st.st_size < pos) {
|
---|
| 346 | pos = lseek(0, (off_t) 0, SEEK_SET);
|
---|
| 347 | } else {
|
---|
| 348 | pos = st.st_size;
|
---|
| 349 | }
|
---|
| 350 | }
|
---|
| 351 | }
|
---|
| 352 | }
|
---|
| 353 |
|
---|
| 354 | /* Tell the user the standard syntax. */
|
---|
| 355 | void usage()
|
---|
| 356 | {
|
---|
| 357 | fputs("Usage: tail [-f] [-c number | -n number] [file]\n", stderr);
|
---|
| 358 | exit(FAILURE);
|
---|
| 359 | }
|
---|