148313Sbostic /*- 250453Sbostic * Copyright (c) 1991 The Regents of the University of California. 348313Sbostic * All rights reserved. 448313Sbostic * 550453Sbostic * This code is derived from software contributed to Berkeley by 650453Sbostic * Edward Sze-Tyan Wang. 750453Sbostic * 850453Sbostic * %sccs.include.redist.c% 921579Sdist */ 1021579Sdist 1117708Sbloom #ifndef lint 1221579Sdist char copyright[] = 1350453Sbostic "@(#) Copyright (c) 1991 The Regents of the University of California.\n\ 1421579Sdist All rights reserved.\n"; 1548313Sbostic #endif /* not lint */ 1621579Sdist 1721579Sdist #ifndef lint 18*53506Sbostic static char sccsid[] = "@(#)tail.c 5.10 (Berkeley) 05/14/92"; 1948313Sbostic #endif /* not lint */ 2021579Sdist 2150453Sbostic #include <sys/types.h> 2250453Sbostic #include <sys/stat.h> 2350453Sbostic #include <errno.h> 2450453Sbostic #include <unistd.h> 2550453Sbostic #include <stdio.h> 2650453Sbostic #include <stdlib.h> 2750453Sbostic #include <string.h> 2850453Sbostic #include "extern.h" 291214Sroot 3050453Sbostic int fflag, rflag, rval; 3150453Sbostic char *fname; 321214Sroot 3350453Sbostic static void obsolete __P((char **)); 3450453Sbostic static void usage __P((void)); 351214Sroot 3652836Sbostic int 3750453Sbostic main(argc, argv) 3850453Sbostic int argc; 3952836Sbostic char *argv[]; 401214Sroot { 4150453Sbostic struct stat sb; 4250453Sbostic FILE *fp; 4350453Sbostic long off; 4450453Sbostic enum STYLE style; 4552827Sbostic int ch, first; 4652472Sbostic char *p; 471214Sroot 4852472Sbostic /* 4952472Sbostic * Tail's options are weird. First, -n10 is the same as -n-10, not 5052472Sbostic * -n+10. Second, the number options are 1 based and not offsets, 5152472Sbostic * so -n+1 is the first line, and -c-1 is the last byte. Third, the 5252472Sbostic * number options for the -r option specify the number of things that 5352472Sbostic * get displayed, not the starting point in the file. The one major 5452472Sbostic * incompatibility in this version as compared to historical versions 5552472Sbostic * is that the 'r' option couldn't be modified by the -lbc options, 5652472Sbostic * i.e. it was always done in lines. This version treats -rc as a 5752472Sbostic * number of characters in reverse order. Finally, the default for 5852472Sbostic * -r is the entire file, not 10 lines. 5952472Sbostic */ 60*53506Sbostic #define ARG(units, forward, backward) { \ 61*53506Sbostic if (style) \ 62*53506Sbostic usage(); \ 63*53506Sbostic off = strtol(optarg, &p, 10) * (units); \ 64*53506Sbostic if (*p) \ 65*53506Sbostic err(1, "illegal offset -- %s", optarg); \ 66*53506Sbostic switch(optarg[0]) { \ 67*53506Sbostic case '+': \ 68*53506Sbostic if (off) \ 69*53506Sbostic off -= (units); \ 70*53506Sbostic style = (forward); \ 71*53506Sbostic break; \ 72*53506Sbostic case '-': \ 73*53506Sbostic off = -off; \ 74*53506Sbostic /* FALLTHROUGH */ \ 75*53506Sbostic default: \ 76*53506Sbostic style = (backward); \ 77*53506Sbostic break; \ 78*53506Sbostic } \ 7952472Sbostic } 8052472Sbostic 8150453Sbostic obsolete(argv); 8250453Sbostic style = NOTSET; 8350453Sbostic while ((ch = getopt(argc, argv, "b:c:fn:r")) != EOF) 8450453Sbostic switch(ch) { 8550453Sbostic case 'b': 8652472Sbostic ARG(512, FBYTES, RBYTES); 8750453Sbostic break; 8850453Sbostic case 'c': 8952472Sbostic ARG(1, FBYTES, RBYTES); 9050453Sbostic break; 9150453Sbostic case 'f': 9250453Sbostic fflag = 1; 9350453Sbostic break; 9450453Sbostic case 'n': 9552472Sbostic ARG(1, FLINES, RLINES); 9650453Sbostic break; 9750453Sbostic case 'r': 9850453Sbostic rflag = 1; 9950453Sbostic break; 10050453Sbostic case '?': 10150453Sbostic default: 10250453Sbostic usage(); 1031214Sroot } 10450453Sbostic argc -= optind; 10550453Sbostic argv += optind; 10650453Sbostic 10752836Sbostic if (fflag && argc > 1) 10852836Sbostic err(1, "-f option only appropriate for a single file"); 10952836Sbostic 11050453Sbostic /* 11152472Sbostic * If displaying in reverse, don't permit follow option, and convert 11252472Sbostic * style values. 11350453Sbostic */ 11450453Sbostic if (rflag) { 11550453Sbostic if (fflag) 11650453Sbostic usage(); 11750453Sbostic if (style == FBYTES) 11850453Sbostic style = RBYTES; 11952836Sbostic else if (style == FLINES) 12050453Sbostic style = RLINES; 1211214Sroot } 1221214Sroot 12352472Sbostic /* 12452472Sbostic * If style not specified, the default is the whole file for -r, and 12552472Sbostic * the last 10 lines if not -r. 12652472Sbostic */ 12752472Sbostic if (style == NOTSET) 12852472Sbostic if (rflag) { 12952472Sbostic off = 0; 13052472Sbostic style = REVERSE; 13152472Sbostic } else { 13252472Sbostic off = 10; 13352472Sbostic style = RLINES; 13452472Sbostic } 13552472Sbostic 13652827Sbostic if (*argv) 13752827Sbostic for (first = 1; fname = *argv++;) { 13852836Sbostic if ((fp = fopen(fname, "r")) == NULL || 13952836Sbostic fstat(fileno(fp), &sb)) { 14052827Sbostic ierr(); 14152827Sbostic continue; 14252827Sbostic } 14352827Sbostic if (argc > 1) { 14452827Sbostic (void)printf("%s==> %s <==\n", 14552827Sbostic first ? "" : "\n", fname); 14652827Sbostic first = 0; 147*53506Sbostic (void)fflush(stdout); 14852827Sbostic } 14952827Sbostic 15052827Sbostic if (rflag) 15152827Sbostic reverse(fp, style, off, &sb); 15252827Sbostic else 15352827Sbostic forward(fp, style, off, &sb); 15452836Sbostic (void)fclose(fp); 15552827Sbostic } 15652827Sbostic else { 15750453Sbostic fname = "stdin"; 1581214Sroot 15952836Sbostic if (fstat(fileno(stdin), &sb)) { 16052827Sbostic ierr(); 16152827Sbostic exit(1); 16252827Sbostic } 1631214Sroot 16452827Sbostic /* 16552827Sbostic * Determine if input is a pipe. 4.4BSD will set the SOCKET 16652827Sbostic * bit in the st_mode field for pipes. Fix this then. 16752827Sbostic */ 16852836Sbostic if (lseek(fileno(stdin), 0L, SEEK_CUR) == -1 && 16952836Sbostic errno == ESPIPE) { 17052827Sbostic errno = 0; 17152827Sbostic fflag = 0; /* POSIX.2 requires this. */ 17252827Sbostic } 17352827Sbostic 17452827Sbostic if (rflag) 17552836Sbostic reverse(stdin, style, off, &sb); 17652827Sbostic else 17752836Sbostic forward(stdin, style, off, &sb); 17850453Sbostic } 17950453Sbostic exit(rval); 18050453Sbostic } 18150453Sbostic 18250453Sbostic /* 18350453Sbostic * Convert the obsolete argument form into something that getopt can handle. 18450453Sbostic * This means that anything of the form [+-][0-9][0-9]*[lbc][fr] that isn't 18550453Sbostic * the option argument for a -b, -c or -n option gets converted. 18650453Sbostic */ 18750453Sbostic static void 18850453Sbostic obsolete(argv) 18952836Sbostic char *argv[]; 19050453Sbostic { 19150453Sbostic register char *ap, *p, *t; 19250453Sbostic int len; 19350453Sbostic char *start; 19450453Sbostic 19550453Sbostic while (ap = *++argv) { 19650453Sbostic /* Return if "--" or not an option of any form. */ 19750453Sbostic if (ap[0] != '-') { 19850453Sbostic if (ap[0] != '+') 19950453Sbostic return; 20050453Sbostic } else if (ap[1] == '-') 20150453Sbostic return; 20250453Sbostic 20350453Sbostic switch(*++ap) { 20450453Sbostic /* Old-style option. */ 20550453Sbostic case '0': case '1': case '2': case '3': case '4': 20650453Sbostic case '5': case '6': case '7': case '8': case '9': 20750453Sbostic 20850453Sbostic /* Malloc space for dash, new option and argument. */ 20950453Sbostic len = strlen(*argv); 21050453Sbostic if ((start = p = malloc(len + 3)) == NULL) 21152827Sbostic err(1, "%s", strerror(errno)); 21250453Sbostic *p++ = '-'; 21350453Sbostic 21450453Sbostic /* 21550453Sbostic * Go to the end of the option argument. Save off any 21650453Sbostic * trailing options (-3lf) and translate any trailing 21750453Sbostic * output style characters. 21850453Sbostic */ 21950453Sbostic t = *argv + len - 1; 22050896Sbostic if (*t == 'f' || *t == 'r') { 22150896Sbostic *p++ = *t; 22250896Sbostic *t-- = '\0'; 22350896Sbostic } 22450453Sbostic switch(*t) { 22550453Sbostic case 'b': 22650453Sbostic *p++ = 'b'; 22750453Sbostic *t = '\0'; 22850453Sbostic break; 22950453Sbostic case 'c': 23050453Sbostic *p++ = 'c'; 23150453Sbostic *t = '\0'; 23250453Sbostic break; 23350453Sbostic case 'l': 23450453Sbostic *t = '\0'; 23550453Sbostic /* FALLTHROUGH */ 23650453Sbostic case '0': case '1': case '2': case '3': case '4': 23750453Sbostic case '5': case '6': case '7': case '8': case '9': 23850453Sbostic *p++ = 'n'; 23950453Sbostic break; 24050453Sbostic default: 24152827Sbostic err(1, "illegal option -- %s", *argv); 2421214Sroot } 24350453Sbostic *p++ = *argv[0]; 24450453Sbostic (void)strcpy(p, ap); 24550453Sbostic *argv = start; 24650453Sbostic continue; 2471214Sroot 24850453Sbostic /* 24950453Sbostic * Options w/ arguments, skip the argument and continue 25050453Sbostic * with the next option. 25150453Sbostic */ 25250453Sbostic case 'b': 25350453Sbostic case 'c': 25450453Sbostic case 'n': 25550453Sbostic if (!ap[1]) 25650453Sbostic ++argv; 25750453Sbostic /* FALLTHROUGH */ 25850453Sbostic /* Options w/o arguments, continue with the next option. */ 25950453Sbostic case 'f': 26050453Sbostic case 'r': 26150453Sbostic continue; 2621214Sroot 26350453Sbostic /* Illegal option, return and let getopt handle it. */ 26450453Sbostic default: 26550453Sbostic return; 2661214Sroot } 2671214Sroot } 2681214Sroot } 2691214Sroot 27050453Sbostic static void 27150453Sbostic usage() 27250453Sbostic { 27350453Sbostic (void)fprintf(stderr, 27452836Sbostic "usage: tail [-f | -r] [-b # | -c # | -n #] [file ...]\n"); 27550453Sbostic exit(1); 2761214Sroot } 277