1*209e49b2SChris Love /* 2*209e49b2SChris Love * Copyright (c) 1991, 1993 3*209e49b2SChris Love * The Regents of the University of California. All rights reserved. 4*209e49b2SChris Love * 5*209e49b2SChris Love * This code is derived from software contributed to Berkeley by 6*209e49b2SChris Love * Edward Sze-Tyan Wang. 7*209e49b2SChris Love * 8*209e49b2SChris Love * Redistribution and use in source and binary forms, with or without 9*209e49b2SChris Love * modification, are permitted provided that the following conditions 10*209e49b2SChris Love * are met: 11*209e49b2SChris Love * 1. Redistributions of source code must retain the above copyright 12*209e49b2SChris Love * notice, this list of conditions and the following disclaimer. 13*209e49b2SChris Love * 2. Redistributions in binary form must reproduce the above copyright 14*209e49b2SChris Love * notice, this list of conditions and the following disclaimer in the 15*209e49b2SChris Love * documentation and/or other materials provided with the distribution. 16*209e49b2SChris Love * 4. Neither the name of the University nor the names of its contributors 17*209e49b2SChris Love * may be used to endorse or promote products derived from this software 18*209e49b2SChris Love * without specific prior written permission. 19*209e49b2SChris Love * 20*209e49b2SChris Love * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 21*209e49b2SChris Love * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22*209e49b2SChris Love * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23*209e49b2SChris Love * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 24*209e49b2SChris Love * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25*209e49b2SChris Love * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26*209e49b2SChris Love * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27*209e49b2SChris Love * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28*209e49b2SChris Love * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29*209e49b2SChris Love * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30*209e49b2SChris Love * SUCH DAMAGE. 31*209e49b2SChris Love */ 32*209e49b2SChris Love 33*209e49b2SChris Love #include <sys/types.h> 34*209e49b2SChris Love #include <sys/stat.h> 35*209e49b2SChris Love 36*209e49b2SChris Love #include <err.h> 37*209e49b2SChris Love #include <errno.h> 38*209e49b2SChris Love #include <stdio.h> 39*209e49b2SChris Love #include <stdlib.h> 40*209e49b2SChris Love #include <string.h> 41*209e49b2SChris Love #include <unistd.h> 42*209e49b2SChris Love 43*209e49b2SChris Love #include "extern.h" 44*209e49b2SChris Love 45*209e49b2SChris Love int Fflag, fflag, qflag, rflag, rval, no_files; 46*209e49b2SChris Love 47*209e49b2SChris Love file_info_t *files; 48*209e49b2SChris Love 49*209e49b2SChris Love static void obsolete(char **); 50*209e49b2SChris Love static void usage(void); 51*209e49b2SChris Love 52*209e49b2SChris Love int 53*209e49b2SChris Love main(int argc, char *argv[]) 54*209e49b2SChris Love { 55*209e49b2SChris Love struct stat sb; 56*209e49b2SChris Love const char *fn; 57*209e49b2SChris Love FILE *fp; 58*209e49b2SChris Love off_t off; 59*209e49b2SChris Love enum STYLE style; 60*209e49b2SChris Love int i, ch, first; 61*209e49b2SChris Love file_info_t *file; 62*209e49b2SChris Love char *p; 63*209e49b2SChris Love 64*209e49b2SChris Love /* 65*209e49b2SChris Love * Tail's options are weird. First, -n10 is the same as -n-10, not 66*209e49b2SChris Love * -n+10. Second, the number options are 1 based and not offsets, 67*209e49b2SChris Love * so -n+1 is the first line, and -c-1 is the last byte. Third, the 68*209e49b2SChris Love * number options for the -r option specify the number of things that 69*209e49b2SChris Love * get displayed, not the starting point in the file. The one major 70*209e49b2SChris Love * incompatibility in this version as compared to historical versions 71*209e49b2SChris Love * is that the 'r' option couldn't be modified by the -lbc options, 72*209e49b2SChris Love * i.e. it was always done in lines. This version treats -rc as a 73*209e49b2SChris Love * number of characters in reverse order. Finally, the default for 74*209e49b2SChris Love * -r is the entire file, not 10 lines. 75*209e49b2SChris Love */ 76*209e49b2SChris Love #define ARG(units, forward, backward) { \ 77*209e49b2SChris Love if (style) \ 78*209e49b2SChris Love usage(); \ 79*209e49b2SChris Love off = strtoll(optarg, &p, 10) * (units); \ 80*209e49b2SChris Love if (*p) \ 81*209e49b2SChris Love errx(1, "illegal offset -- %s", optarg); \ 82*209e49b2SChris Love switch (optarg[0]) { \ 83*209e49b2SChris Love case '+': \ 84*209e49b2SChris Love if (off) \ 85*209e49b2SChris Love off -= (units); \ 86*209e49b2SChris Love style = (forward); \ 87*209e49b2SChris Love break; \ 88*209e49b2SChris Love case '-': \ 89*209e49b2SChris Love off = -off; \ 90*209e49b2SChris Love /* FALLTHROUGH */ \ 91*209e49b2SChris Love default: \ 92*209e49b2SChris Love style = (backward); \ 93*209e49b2SChris Love break; \ 94*209e49b2SChris Love } \ 95*209e49b2SChris Love } 96*209e49b2SChris Love 97*209e49b2SChris Love obsolete(argv); 98*209e49b2SChris Love style = NOTSET; 99*209e49b2SChris Love off = 0; 100*209e49b2SChris Love while ((ch = getopt(argc, argv, "Fb:c:fn:qr")) != -1) 101*209e49b2SChris Love switch (ch) { 102*209e49b2SChris Love case 'F': /* -F is superset of (and implies) -f */ 103*209e49b2SChris Love Fflag = fflag = 1; 104*209e49b2SChris Love break; 105*209e49b2SChris Love case 'b': 106*209e49b2SChris Love ARG(512, FBYTES, RBYTES); 107*209e49b2SChris Love break; 108*209e49b2SChris Love case 'c': 109*209e49b2SChris Love ARG(1, FBYTES, RBYTES); 110*209e49b2SChris Love break; 111*209e49b2SChris Love case 'f': 112*209e49b2SChris Love fflag = 1; 113*209e49b2SChris Love break; 114*209e49b2SChris Love case 'n': 115*209e49b2SChris Love ARG(1, FLINES, RLINES); 116*209e49b2SChris Love break; 117*209e49b2SChris Love case 'q': 118*209e49b2SChris Love qflag = 1; 119*209e49b2SChris Love break; 120*209e49b2SChris Love case 'r': 121*209e49b2SChris Love rflag = 1; 122*209e49b2SChris Love break; 123*209e49b2SChris Love case '?': 124*209e49b2SChris Love default: 125*209e49b2SChris Love usage(); 126*209e49b2SChris Love } 127*209e49b2SChris Love argc -= optind; 128*209e49b2SChris Love argv += optind; 129*209e49b2SChris Love 130*209e49b2SChris Love no_files = argc ? argc : 1; 131*209e49b2SChris Love 132*209e49b2SChris Love /* 133*209e49b2SChris Love * If displaying in reverse, don't permit follow option, and convert 134*209e49b2SChris Love * style values. 135*209e49b2SChris Love */ 136*209e49b2SChris Love if (rflag) { 137*209e49b2SChris Love if (fflag) 138*209e49b2SChris Love usage(); 139*209e49b2SChris Love if (style == FBYTES) 140*209e49b2SChris Love style = RBYTES; 141*209e49b2SChris Love else if (style == FLINES) 142*209e49b2SChris Love style = RLINES; 143*209e49b2SChris Love } 144*209e49b2SChris Love 145*209e49b2SChris Love /* 146*209e49b2SChris Love * If style not specified, the default is the whole file for -r, and 147*209e49b2SChris Love * the last 10 lines if not -r. 148*209e49b2SChris Love */ 149*209e49b2SChris Love if (style == NOTSET) { 150*209e49b2SChris Love if (rflag) { 151*209e49b2SChris Love off = 0; 152*209e49b2SChris Love style = REVERSE; 153*209e49b2SChris Love } else { 154*209e49b2SChris Love off = 10; 155*209e49b2SChris Love style = RLINES; 156*209e49b2SChris Love } 157*209e49b2SChris Love } 158*209e49b2SChris Love 159*209e49b2SChris Love if (*argv && fflag) { 160*209e49b2SChris Love files = (struct file_info *)malloc(no_files * 161*209e49b2SChris Love sizeof (struct file_info)); 162*209e49b2SChris Love if (!files) 163*209e49b2SChris Love err(1, "Couldn't malloc space for file descriptors."); 164*209e49b2SChris Love 165*209e49b2SChris Love for (file = files; (fn = *argv++); file++) { 166*209e49b2SChris Love file->file_name = strdup(fn); 167*209e49b2SChris Love if (! file->file_name) 168*209e49b2SChris Love errx(1, "Couldn't malloc space for file name."); 169*209e49b2SChris Love if ((file->fp = fopen(file->file_name, "r")) == NULL || 170*209e49b2SChris Love fstat(fileno(file->fp), &file->st)) { 171*209e49b2SChris Love if (file->fp != NULL) { 172*209e49b2SChris Love (void) fclose(file->fp); 173*209e49b2SChris Love file->fp = NULL; 174*209e49b2SChris Love } 175*209e49b2SChris Love if (!Fflag || errno != ENOENT) 176*209e49b2SChris Love ierr(file->file_name); 177*209e49b2SChris Love } 178*209e49b2SChris Love } 179*209e49b2SChris Love follow(files, style, off); 180*209e49b2SChris Love for (i = 0, file = files; i < no_files; i++, file++) { 181*209e49b2SChris Love free(file->file_name); 182*209e49b2SChris Love } 183*209e49b2SChris Love free(files); 184*209e49b2SChris Love } else if (*argv) { 185*209e49b2SChris Love for (first = 1; (fn = *argv++); ) { 186*209e49b2SChris Love if ((fp = fopen(fn, "r")) == NULL || 187*209e49b2SChris Love fstat(fileno(fp), &sb)) { 188*209e49b2SChris Love ierr(fn); 189*209e49b2SChris Love continue; 190*209e49b2SChris Love } 191*209e49b2SChris Love if (argc > 1 && !qflag) { 192*209e49b2SChris Love (void) printf("%s==> %s <==\n", 193*209e49b2SChris Love first ? "" : "\n", fn); 194*209e49b2SChris Love first = 0; 195*209e49b2SChris Love (void) fflush(stdout); 196*209e49b2SChris Love } 197*209e49b2SChris Love 198*209e49b2SChris Love if (rflag) 199*209e49b2SChris Love reverse(fp, fn, style, off, &sb); 200*209e49b2SChris Love else 201*209e49b2SChris Love forward(fp, fn, style, off, &sb); 202*209e49b2SChris Love } 203*209e49b2SChris Love } else { 204*209e49b2SChris Love fn = "stdin"; 205*209e49b2SChris Love 206*209e49b2SChris Love if (fstat(fileno(stdin), &sb)) { 207*209e49b2SChris Love ierr(fn); 208*209e49b2SChris Love exit(1); 209*209e49b2SChris Love } 210*209e49b2SChris Love 211*209e49b2SChris Love /* 212*209e49b2SChris Love * Determine if input is a pipe. 4.4BSD will set the SOCKET 213*209e49b2SChris Love * bit in the st_mode field for pipes. Fix this then. 214*209e49b2SChris Love */ 215*209e49b2SChris Love if (lseek(fileno(stdin), (off_t)0, SEEK_CUR) == -1 && 216*209e49b2SChris Love errno == ESPIPE) { 217*209e49b2SChris Love errno = 0; 218*209e49b2SChris Love fflag = 0; /* POSIX.2 requires this. */ 219*209e49b2SChris Love } 220*209e49b2SChris Love 221*209e49b2SChris Love if (rflag) 222*209e49b2SChris Love reverse(stdin, fn, style, off, &sb); 223*209e49b2SChris Love else 224*209e49b2SChris Love forward(stdin, fn, style, off, &sb); 225*209e49b2SChris Love } 226*209e49b2SChris Love exit(rval); 227*209e49b2SChris Love } 228*209e49b2SChris Love 229*209e49b2SChris Love /* 230*209e49b2SChris Love * Convert the obsolete argument form into something that getopt can handle. 231*209e49b2SChris Love * This means that anything of the form [+-][0-9][0-9]*[lbc][Ffr] that isn't 232*209e49b2SChris Love * the option argument for a -b, -c or -n option gets converted. 233*209e49b2SChris Love */ 234*209e49b2SChris Love static void 235*209e49b2SChris Love obsolete(char *argv[]) 236*209e49b2SChris Love { 237*209e49b2SChris Love char *ap, *p, *t; 238*209e49b2SChris Love size_t len; 239*209e49b2SChris Love char *start; 240*209e49b2SChris Love 241*209e49b2SChris Love while ((ap = *++argv)) { 242*209e49b2SChris Love /* Return if "--" or not an option of any form. */ 243*209e49b2SChris Love if (ap[0] != '-') { 244*209e49b2SChris Love if (ap[0] != '+') 245*209e49b2SChris Love return; 246*209e49b2SChris Love } else if (ap[1] == '-') 247*209e49b2SChris Love return; 248*209e49b2SChris Love 249*209e49b2SChris Love switch (*++ap) { 250*209e49b2SChris Love /* Old-style option. */ 251*209e49b2SChris Love case '0': case '1': case '2': case '3': case '4': 252*209e49b2SChris Love case '5': case '6': case '7': case '8': case '9': 253*209e49b2SChris Love 254*209e49b2SChris Love /* Malloc space for dash, new option and argument. */ 255*209e49b2SChris Love len = strlen(*argv); 256*209e49b2SChris Love if ((start = p = malloc(len + 3)) == NULL) 257*209e49b2SChris Love err(1, "malloc"); 258*209e49b2SChris Love *p++ = '-'; 259*209e49b2SChris Love 260*209e49b2SChris Love /* 261*209e49b2SChris Love * Go to the end of the option argument. Save off any 262*209e49b2SChris Love * trailing options (-3lf) and translate any trailing 263*209e49b2SChris Love * output style characters. 264*209e49b2SChris Love */ 265*209e49b2SChris Love t = *argv + len - 1; 266*209e49b2SChris Love if (*t == 'F' || *t == 'f' || *t == 'r') { 267*209e49b2SChris Love *p++ = *t; 268*209e49b2SChris Love *t-- = '\0'; 269*209e49b2SChris Love } 270*209e49b2SChris Love switch (*t) { 271*209e49b2SChris Love case 'b': 272*209e49b2SChris Love *p++ = 'b'; 273*209e49b2SChris Love *t = '\0'; 274*209e49b2SChris Love break; 275*209e49b2SChris Love case 'c': 276*209e49b2SChris Love *p++ = 'c'; 277*209e49b2SChris Love *t = '\0'; 278*209e49b2SChris Love break; 279*209e49b2SChris Love case 'l': 280*209e49b2SChris Love *t = '\0'; 281*209e49b2SChris Love /* FALLTHROUGH */ 282*209e49b2SChris Love case '0': case '1': case '2': case '3': case '4': 283*209e49b2SChris Love case '5': case '6': case '7': case '8': case '9': 284*209e49b2SChris Love *p++ = 'n'; 285*209e49b2SChris Love break; 286*209e49b2SChris Love default: 287*209e49b2SChris Love errx(1, "illegal option -- %s", *argv); 288*209e49b2SChris Love } 289*209e49b2SChris Love *p++ = *argv[0]; 290*209e49b2SChris Love (void) strcpy(p, ap); 291*209e49b2SChris Love *argv = start; 292*209e49b2SChris Love continue; 293*209e49b2SChris Love 294*209e49b2SChris Love /* 295*209e49b2SChris Love * Legacy Solaris tail supports "+c" "-c", "+l", "-l", 296*209e49b2SChris Love * "+b", and "-b" with an default number of 10. Map 297*209e49b2SChris Love * these arguments to an explicit +/-10 for FreeBSD. 298*209e49b2SChris Love * New argument will be of the form -[bcn][+-]10 299*209e49b2SChris Love */ 300*209e49b2SChris Love case 'b': 301*209e49b2SChris Love case 'c': 302*209e49b2SChris Love case 'l': 303*209e49b2SChris Love if ((start = p = malloc(6)) == NULL) 304*209e49b2SChris Love err(1, "malloc"); 305*209e49b2SChris Love *p++ = '-'; 306*209e49b2SChris Love switch (ap[0]) { 307*209e49b2SChris Love case 'c': 308*209e49b2SChris Love *p++ = ap[0]; 309*209e49b2SChris Love break; 310*209e49b2SChris Love case 'b': 311*209e49b2SChris Love *p++ = ap[0]; 312*209e49b2SChris Love break; 313*209e49b2SChris Love case 'l': 314*209e49b2SChris Love *p++ = 'n'; 315*209e49b2SChris Love break; 316*209e49b2SChris Love } 317*209e49b2SChris Love sprintf(p, "%c10", *argv[0]); 318*209e49b2SChris Love *argv = start; 319*209e49b2SChris Love 320*209e49b2SChris Love continue; 321*209e49b2SChris Love /* 322*209e49b2SChris Love * Options w/ arguments, skip the argument and continue 323*209e49b2SChris Love * with the next option. 324*209e49b2SChris Love */ 325*209e49b2SChris Love case 'n': 326*209e49b2SChris Love if (!ap[1]) 327*209e49b2SChris Love ++argv; 328*209e49b2SChris Love /* FALLTHROUGH */ 329*209e49b2SChris Love /* Options w/o arguments, continue with the next option. */ 330*209e49b2SChris Love case 'F': 331*209e49b2SChris Love case 'f': 332*209e49b2SChris Love case 'r': 333*209e49b2SChris Love continue; 334*209e49b2SChris Love 335*209e49b2SChris Love /* Illegal option, return and let getopt handle it. */ 336*209e49b2SChris Love default: 337*209e49b2SChris Love return; 338*209e49b2SChris Love } 339*209e49b2SChris Love } 340*209e49b2SChris Love } 341*209e49b2SChris Love 342*209e49b2SChris Love static void 343*209e49b2SChris Love usage(void) 344*209e49b2SChris Love { 345*209e49b2SChris Love (void) fprintf(stderr, 346*209e49b2SChris Love "usage: tail [-F | -f | -r] [-q] [-b # | -c # | -n #]" 347*209e49b2SChris Love " [file ...]\n"); 348*209e49b2SChris Love exit(1); 349*209e49b2SChris Love } 350