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