1209e49b2SChris Love /* 2209e49b2SChris Love * Copyright (c) 1991, 1993 3209e49b2SChris Love * The Regents of the University of California. All rights reserved. 4209e49b2SChris Love * 5209e49b2SChris Love * This code is derived from software contributed to Berkeley by 6209e49b2SChris Love * Edward Sze-Tyan Wang. 7209e49b2SChris Love * 8209e49b2SChris Love * Redistribution and use in source and binary forms, with or without 9209e49b2SChris Love * modification, are permitted provided that the following conditions 10209e49b2SChris Love * are met: 11209e49b2SChris Love * 1. Redistributions of source code must retain the above copyright 12209e49b2SChris Love * notice, this list of conditions and the following disclaimer. 13209e49b2SChris Love * 2. Redistributions in binary form must reproduce the above copyright 14209e49b2SChris Love * notice, this list of conditions and the following disclaimer in the 15209e49b2SChris Love * documentation and/or other materials provided with the distribution. 16209e49b2SChris Love * 4. Neither the name of the University nor the names of its contributors 17209e49b2SChris Love * may be used to endorse or promote products derived from this software 18209e49b2SChris Love * without specific prior written permission. 19209e49b2SChris Love * 20209e49b2SChris Love * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 21209e49b2SChris Love * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22209e49b2SChris Love * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23209e49b2SChris Love * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 24209e49b2SChris Love * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25209e49b2SChris Love * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26209e49b2SChris Love * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27209e49b2SChris Love * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28209e49b2SChris Love * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29209e49b2SChris Love * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30209e49b2SChris Love * SUCH DAMAGE. 31209e49b2SChris Love */ 32209e49b2SChris Love 33209e49b2SChris Love #include <sys/types.h> 34209e49b2SChris Love #include <sys/stat.h> 35209e49b2SChris Love 36209e49b2SChris Love #include <err.h> 37209e49b2SChris Love #include <errno.h> 38209e49b2SChris Love #include <stdio.h> 39209e49b2SChris Love #include <stdlib.h> 40209e49b2SChris Love #include <string.h> 41209e49b2SChris Love #include <unistd.h> 42209e49b2SChris Love 43209e49b2SChris Love #include "extern.h" 44209e49b2SChris Love 45209e49b2SChris Love int Fflag, fflag, qflag, rflag, rval, no_files; 46209e49b2SChris Love 47209e49b2SChris Love file_info_t *files; 48209e49b2SChris Love 49209e49b2SChris Love static void obsolete(char **); 50209e49b2SChris Love static void usage(void); 51209e49b2SChris Love 52209e49b2SChris Love int 53209e49b2SChris Love main(int argc, char *argv[]) 54209e49b2SChris Love { 55209e49b2SChris Love struct stat sb; 56209e49b2SChris Love const char *fn; 57209e49b2SChris Love FILE *fp; 58209e49b2SChris Love off_t off; 59209e49b2SChris Love enum STYLE style; 60209e49b2SChris Love int i, ch, first; 61209e49b2SChris Love file_info_t *file; 62209e49b2SChris Love char *p; 63209e49b2SChris Love 64209e49b2SChris Love /* 65209e49b2SChris Love * Tail's options are weird. First, -n10 is the same as -n-10, not 66209e49b2SChris Love * -n+10. Second, the number options are 1 based and not offsets, 67209e49b2SChris Love * so -n+1 is the first line, and -c-1 is the last byte. Third, the 68209e49b2SChris Love * number options for the -r option specify the number of things that 69209e49b2SChris Love * get displayed, not the starting point in the file. The one major 70209e49b2SChris Love * incompatibility in this version as compared to historical versions 71209e49b2SChris Love * is that the 'r' option couldn't be modified by the -lbc options, 72209e49b2SChris Love * i.e. it was always done in lines. This version treats -rc as a 73209e49b2SChris Love * number of characters in reverse order. Finally, the default for 74209e49b2SChris Love * -r is the entire file, not 10 lines. 75209e49b2SChris Love */ 76209e49b2SChris Love #define ARG(units, forward, backward) { \ 77209e49b2SChris Love if (style) \ 78209e49b2SChris Love usage(); \ 79209e49b2SChris Love off = strtoll(optarg, &p, 10) * (units); \ 80209e49b2SChris Love if (*p) \ 81209e49b2SChris Love errx(1, "illegal offset -- %s", optarg); \ 82209e49b2SChris Love switch (optarg[0]) { \ 83209e49b2SChris Love case '+': \ 84209e49b2SChris Love if (off) \ 85209e49b2SChris Love off -= (units); \ 86209e49b2SChris Love style = (forward); \ 87209e49b2SChris Love break; \ 88209e49b2SChris Love case '-': \ 89209e49b2SChris Love off = -off; \ 90209e49b2SChris Love /* FALLTHROUGH */ \ 91209e49b2SChris Love default: \ 92209e49b2SChris Love style = (backward); \ 93209e49b2SChris Love break; \ 94209e49b2SChris Love } \ 95209e49b2SChris Love } 96209e49b2SChris Love 97209e49b2SChris Love obsolete(argv); 98209e49b2SChris Love style = NOTSET; 99209e49b2SChris Love off = 0; 100209e49b2SChris Love while ((ch = getopt(argc, argv, "Fb:c:fn:qr")) != -1) 101209e49b2SChris Love switch (ch) { 102209e49b2SChris Love case 'F': /* -F is superset of (and implies) -f */ 103209e49b2SChris Love Fflag = fflag = 1; 104209e49b2SChris Love break; 105209e49b2SChris Love case 'b': 106209e49b2SChris Love ARG(512, FBYTES, RBYTES); 107209e49b2SChris Love break; 108209e49b2SChris Love case 'c': 109209e49b2SChris Love ARG(1, FBYTES, RBYTES); 110209e49b2SChris Love break; 111209e49b2SChris Love case 'f': 112209e49b2SChris Love fflag = 1; 113209e49b2SChris Love break; 114209e49b2SChris Love case 'n': 115209e49b2SChris Love ARG(1, FLINES, RLINES); 116209e49b2SChris Love break; 117209e49b2SChris Love case 'q': 118209e49b2SChris Love qflag = 1; 119209e49b2SChris Love break; 120209e49b2SChris Love case 'r': 121209e49b2SChris Love rflag = 1; 122209e49b2SChris Love break; 123209e49b2SChris Love case '?': 124209e49b2SChris Love default: 125209e49b2SChris Love usage(); 126209e49b2SChris Love } 127209e49b2SChris Love argc -= optind; 128209e49b2SChris Love argv += optind; 129209e49b2SChris Love 130209e49b2SChris Love no_files = argc ? argc : 1; 131209e49b2SChris Love 132209e49b2SChris Love /* 133209e49b2SChris Love * If displaying in reverse, don't permit follow option, and convert 134209e49b2SChris Love * style values. 135209e49b2SChris Love */ 136209e49b2SChris Love if (rflag) { 137209e49b2SChris Love if (fflag) 138209e49b2SChris Love usage(); 139209e49b2SChris Love if (style == FBYTES) 140209e49b2SChris Love style = RBYTES; 141209e49b2SChris Love else if (style == FLINES) 142209e49b2SChris Love style = RLINES; 143209e49b2SChris Love } 144209e49b2SChris Love 145209e49b2SChris Love /* 146209e49b2SChris Love * If style not specified, the default is the whole file for -r, and 147209e49b2SChris Love * the last 10 lines if not -r. 148209e49b2SChris Love */ 149209e49b2SChris Love if (style == NOTSET) { 150209e49b2SChris Love if (rflag) { 151209e49b2SChris Love off = 0; 152209e49b2SChris Love style = REVERSE; 153209e49b2SChris Love } else { 154209e49b2SChris Love off = 10; 155209e49b2SChris Love style = RLINES; 156209e49b2SChris Love } 157209e49b2SChris Love } 158209e49b2SChris Love 159209e49b2SChris Love if (*argv && fflag) { 160209e49b2SChris Love files = (struct file_info *)malloc(no_files * 161209e49b2SChris Love sizeof (struct file_info)); 162209e49b2SChris Love if (!files) 163209e49b2SChris Love err(1, "Couldn't malloc space for file descriptors."); 164209e49b2SChris Love 165209e49b2SChris Love for (file = files; (fn = *argv++); file++) { 166209e49b2SChris Love file->file_name = strdup(fn); 167209e49b2SChris Love if (! file->file_name) 168209e49b2SChris Love errx(1, "Couldn't malloc space for file name."); 169209e49b2SChris Love if ((file->fp = fopen(file->file_name, "r")) == NULL || 170209e49b2SChris Love fstat(fileno(file->fp), &file->st)) { 171209e49b2SChris Love if (file->fp != NULL) { 172209e49b2SChris Love (void) fclose(file->fp); 173209e49b2SChris Love file->fp = NULL; 174209e49b2SChris Love } 175209e49b2SChris Love if (!Fflag || errno != ENOENT) 176209e49b2SChris Love ierr(file->file_name); 177209e49b2SChris Love } 178209e49b2SChris Love } 179209e49b2SChris Love follow(files, style, off); 180209e49b2SChris Love for (i = 0, file = files; i < no_files; i++, file++) { 181209e49b2SChris Love free(file->file_name); 182209e49b2SChris Love } 183209e49b2SChris Love free(files); 184209e49b2SChris Love } else if (*argv) { 185209e49b2SChris Love for (first = 1; (fn = *argv++); ) { 186209e49b2SChris Love if ((fp = fopen(fn, "r")) == NULL || 187209e49b2SChris Love fstat(fileno(fp), &sb)) { 188209e49b2SChris Love ierr(fn); 189209e49b2SChris Love continue; 190209e49b2SChris Love } 191209e49b2SChris Love if (argc > 1 && !qflag) { 192209e49b2SChris Love (void) printf("%s==> %s <==\n", 193209e49b2SChris Love first ? "" : "\n", fn); 194209e49b2SChris Love first = 0; 195209e49b2SChris Love (void) fflush(stdout); 196209e49b2SChris Love } 197209e49b2SChris Love 198209e49b2SChris Love if (rflag) 199209e49b2SChris Love reverse(fp, fn, style, off, &sb); 200209e49b2SChris Love else 201209e49b2SChris Love forward(fp, fn, style, off, &sb); 202209e49b2SChris Love } 203209e49b2SChris Love } else { 204209e49b2SChris Love fn = "stdin"; 205209e49b2SChris Love 206209e49b2SChris Love if (fstat(fileno(stdin), &sb)) { 207209e49b2SChris Love ierr(fn); 208209e49b2SChris Love exit(1); 209209e49b2SChris Love } 210209e49b2SChris Love 211209e49b2SChris Love /* 212209e49b2SChris Love * Determine if input is a pipe. 4.4BSD will set the SOCKET 213209e49b2SChris Love * bit in the st_mode field for pipes. Fix this then. 214209e49b2SChris Love */ 215209e49b2SChris Love if (lseek(fileno(stdin), (off_t)0, SEEK_CUR) == -1 && 216209e49b2SChris Love errno == ESPIPE) { 217209e49b2SChris Love errno = 0; 218209e49b2SChris Love fflag = 0; /* POSIX.2 requires this. */ 219209e49b2SChris Love } 220209e49b2SChris Love 221209e49b2SChris Love if (rflag) 222209e49b2SChris Love reverse(stdin, fn, style, off, &sb); 223209e49b2SChris Love else 224209e49b2SChris Love forward(stdin, fn, style, off, &sb); 225209e49b2SChris Love } 226209e49b2SChris Love exit(rval); 227209e49b2SChris Love } 228209e49b2SChris Love 229209e49b2SChris Love /* 230209e49b2SChris Love * Convert the obsolete argument form into something that getopt can handle. 231209e49b2SChris Love * This means that anything of the form [+-][0-9][0-9]*[lbc][Ffr] that isn't 232209e49b2SChris Love * the option argument for a -b, -c or -n option gets converted. 233209e49b2SChris Love */ 234209e49b2SChris Love static void 235209e49b2SChris Love obsolete(char *argv[]) 236209e49b2SChris Love { 237209e49b2SChris Love char *ap, *p, *t; 238209e49b2SChris Love size_t len; 239209e49b2SChris Love char *start; 240209e49b2SChris Love 241209e49b2SChris Love while ((ap = *++argv)) { 242209e49b2SChris Love /* Return if "--" or not an option of any form. */ 243209e49b2SChris Love if (ap[0] != '-') { 244209e49b2SChris Love if (ap[0] != '+') 245209e49b2SChris Love return; 246209e49b2SChris Love } else if (ap[1] == '-') 247209e49b2SChris Love return; 248209e49b2SChris Love 249209e49b2SChris Love switch (*++ap) { 250209e49b2SChris Love /* Old-style option. */ 251209e49b2SChris Love case '0': case '1': case '2': case '3': case '4': 252209e49b2SChris Love case '5': case '6': case '7': case '8': case '9': 253209e49b2SChris Love 254209e49b2SChris Love /* Malloc space for dash, new option and argument. */ 255209e49b2SChris Love len = strlen(*argv); 256209e49b2SChris Love if ((start = p = malloc(len + 3)) == NULL) 257209e49b2SChris Love err(1, "malloc"); 258209e49b2SChris Love *p++ = '-'; 259209e49b2SChris Love 260209e49b2SChris Love /* 261209e49b2SChris Love * Go to the end of the option argument. Save off any 262209e49b2SChris Love * trailing options (-3lf) and translate any trailing 263209e49b2SChris Love * output style characters. 264209e49b2SChris Love */ 265209e49b2SChris Love t = *argv + len - 1; 266209e49b2SChris Love if (*t == 'F' || *t == 'f' || *t == 'r') { 267209e49b2SChris Love *p++ = *t; 268209e49b2SChris Love *t-- = '\0'; 269209e49b2SChris Love } 270209e49b2SChris Love switch (*t) { 271209e49b2SChris Love case 'b': 272209e49b2SChris Love *p++ = 'b'; 273209e49b2SChris Love *t = '\0'; 274209e49b2SChris Love break; 275209e49b2SChris Love case 'c': 276209e49b2SChris Love *p++ = 'c'; 277209e49b2SChris Love *t = '\0'; 278209e49b2SChris Love break; 279209e49b2SChris Love case 'l': 280209e49b2SChris Love *t = '\0'; 281209e49b2SChris Love /* FALLTHROUGH */ 282209e49b2SChris Love case '0': case '1': case '2': case '3': case '4': 283209e49b2SChris Love case '5': case '6': case '7': case '8': case '9': 284209e49b2SChris Love *p++ = 'n'; 285209e49b2SChris Love break; 286209e49b2SChris Love default: 287209e49b2SChris Love errx(1, "illegal option -- %s", *argv); 288209e49b2SChris Love } 289209e49b2SChris Love *p++ = *argv[0]; 290209e49b2SChris Love (void) strcpy(p, ap); 291209e49b2SChris Love *argv = start; 292209e49b2SChris Love continue; 293209e49b2SChris Love 294209e49b2SChris Love /* 295209e49b2SChris Love * Legacy Solaris tail supports "+c" "-c", "+l", "-l", 296209e49b2SChris Love * "+b", and "-b" with an default number of 10. Map 297209e49b2SChris Love * these arguments to an explicit +/-10 for FreeBSD. 298209e49b2SChris Love * New argument will be of the form -[bcn][+-]10 299209e49b2SChris Love */ 300209e49b2SChris Love case 'b': 301209e49b2SChris Love case 'c': 302209e49b2SChris Love case 'l': 303209e49b2SChris Love if ((start = p = malloc(6)) == NULL) 304209e49b2SChris Love err(1, "malloc"); 305209e49b2SChris Love *p++ = '-'; 306209e49b2SChris Love switch (ap[0]) { 307209e49b2SChris Love case 'c': 308209e49b2SChris Love *p++ = ap[0]; 309209e49b2SChris Love break; 310209e49b2SChris Love case 'b': 311209e49b2SChris Love *p++ = ap[0]; 312209e49b2SChris Love break; 313209e49b2SChris Love case 'l': 314209e49b2SChris Love *p++ = 'n'; 315209e49b2SChris Love break; 316209e49b2SChris Love } 317*065da06dSChris Love (void) snprintf(p, 6, "%c10", *argv[0]); 318209e49b2SChris Love *argv = start; 319209e49b2SChris Love 320209e49b2SChris Love continue; 321209e49b2SChris Love /* 322209e49b2SChris Love * Options w/ arguments, skip the argument and continue 323209e49b2SChris Love * with the next option. 324209e49b2SChris Love */ 325209e49b2SChris Love case 'n': 326209e49b2SChris Love if (!ap[1]) 327209e49b2SChris Love ++argv; 328209e49b2SChris Love /* FALLTHROUGH */ 329209e49b2SChris Love /* Options w/o arguments, continue with the next option. */ 330209e49b2SChris Love case 'F': 331209e49b2SChris Love case 'f': 332209e49b2SChris Love case 'r': 333209e49b2SChris Love continue; 334209e49b2SChris Love 335209e49b2SChris Love /* Illegal option, return and let getopt handle it. */ 336209e49b2SChris Love default: 337209e49b2SChris Love return; 338209e49b2SChris Love } 339209e49b2SChris Love } 340209e49b2SChris Love } 341209e49b2SChris Love 342209e49b2SChris Love static void 343209e49b2SChris Love usage(void) 344209e49b2SChris Love { 345209e49b2SChris Love (void) fprintf(stderr, 346209e49b2SChris Love "usage: tail [-F | -f | -r] [-q] [-b # | -c # | -n #]" 347209e49b2SChris Love " [file ...]\n"); 348209e49b2SChris Love exit(1); 349209e49b2SChris Love } 350