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 * 4. 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 33 /* 34 * Copyright 2017, Joyent, Inc. 35 */ 36 37 #include <sys/types.h> 38 #include <sys/stat.h> 39 40 #include <ctype.h> 41 #include <err.h> 42 #include <errno.h> 43 #include <stdio.h> 44 #include <stdlib.h> 45 #include <string.h> 46 #include <unistd.h> 47 48 #include "extern.h" 49 50 int Fflag, fflag, qflag, rflag, rval, no_files; 51 52 file_info_t *files; 53 54 static void obsolete(char **); 55 static void usage(void); 56 57 int 58 main(int argc, char *argv[]) 59 { 60 struct stat sb; 61 const char *fn; 62 FILE *fp; 63 off_t off; 64 enum STYLE style; 65 int i, ch, first; 66 file_info_t *file; 67 char *p; 68 69 /* 70 * Tail's options are weird. First, -n10 is the same as -n-10, not 71 * -n+10. Second, the number options are 1 based and not offsets, 72 * so -n+1 is the first line, and -c-1 is the last byte. Third, the 73 * number options for the -r option specify the number of things that 74 * get displayed, not the starting point in the file. The one major 75 * incompatibility in this version as compared to historical versions 76 * is that the 'r' option couldn't be modified by the -lbc options, 77 * i.e. it was always done in lines. This version treats -rc as a 78 * number of characters in reverse order. Finally, the default for 79 * -r is the entire file, not 10 lines. 80 */ 81 #define ARG(units, forward, backward) { \ 82 if (style) \ 83 usage(); \ 84 off = strtoll(optarg, &p, 10) * (units); \ 85 if (*p) \ 86 errx(1, "illegal offset -- %s", optarg); \ 87 switch (optarg[0]) { \ 88 case '+': \ 89 if (off) \ 90 off -= (units); \ 91 style = (forward); \ 92 break; \ 93 case '-': \ 94 off = -off; \ 95 /* FALLTHROUGH */ \ 96 default: \ 97 style = (backward); \ 98 break; \ 99 } \ 100 } 101 102 obsolete(argv); 103 style = NOTSET; 104 off = 0; 105 while ((ch = getopt(argc, argv, "Fb:c:fn:qr")) != -1) 106 switch (ch) { 107 case 'F': /* -F is superset of (and implies) -f */ 108 Fflag = fflag = 1; 109 break; 110 case 'b': 111 ARG(512, FBYTES, RBYTES); 112 break; 113 case 'c': 114 ARG(1, FBYTES, RBYTES); 115 break; 116 case 'f': 117 fflag = 1; 118 break; 119 case 'n': 120 ARG(1, FLINES, RLINES); 121 break; 122 case 'q': 123 qflag = 1; 124 break; 125 case 'r': 126 rflag = 1; 127 break; 128 case '?': 129 default: 130 usage(); 131 } 132 argc -= optind; 133 argv += optind; 134 135 no_files = argc ? argc : 1; 136 137 /* 138 * If displaying in reverse, don't permit follow option, and convert 139 * style values. 140 */ 141 if (rflag) { 142 if (fflag) 143 usage(); 144 if (style == FBYTES) 145 style = RBYTES; 146 else if (style == FLINES) 147 style = RLINES; 148 } 149 150 /* 151 * If style not specified, the default is the whole file for -r, and 152 * the last 10 lines if not -r. 153 */ 154 if (style == NOTSET) { 155 if (rflag) { 156 off = 0; 157 style = REVERSE; 158 } else { 159 off = 10; 160 style = RLINES; 161 } 162 } 163 164 if (*argv && fflag) { 165 files = (struct file_info *)malloc(no_files * 166 sizeof (struct file_info)); 167 if (!files) 168 err(1, "Couldn't malloc space for file descriptors."); 169 170 for (file = files; (fn = *argv++); file++) { 171 file->file_name = strdup(fn); 172 if (! file->file_name) 173 errx(1, "Couldn't malloc space for file name."); 174 if ((file->fp = fopen(file->file_name, "r")) == NULL || 175 fstat(fileno(file->fp), &file->st)) { 176 if (file->fp != NULL) { 177 (void) fclose(file->fp); 178 file->fp = NULL; 179 } 180 if (!Fflag || errno != ENOENT) 181 ierr(file->file_name); 182 } 183 } 184 follow(files, style, off); 185 for (i = 0, file = files; i < no_files; i++, file++) { 186 free(file->file_name); 187 } 188 free(files); 189 } else if (*argv) { 190 for (first = 1; (fn = *argv++); ) { 191 if ((fp = fopen(fn, "r")) == NULL || 192 fstat(fileno(fp), &sb)) { 193 ierr(fn); 194 continue; 195 } 196 if (argc > 1 && !qflag) { 197 (void) printf("%s==> %s <==\n", 198 first ? "" : "\n", fn); 199 first = 0; 200 (void) fflush(stdout); 201 } 202 203 if (rflag) 204 reverse(fp, fn, style, off, &sb); 205 else 206 forward(fp, fn, style, off, &sb); 207 } 208 } else { 209 fn = "stdin"; 210 211 if (fstat(fileno(stdin), &sb)) { 212 ierr(fn); 213 exit(1); 214 } 215 216 /* 217 * Determine if input is a pipe. 4.4BSD will set the SOCKET 218 * bit in the st_mode field for pipes. Fix this then. 219 */ 220 if (lseek(fileno(stdin), (off_t)0, SEEK_CUR) == -1 && 221 errno == ESPIPE) { 222 errno = 0; 223 fflag = 0; /* POSIX.2 requires this. */ 224 } 225 226 if (rflag) 227 reverse(stdin, fn, style, off, &sb); 228 else 229 forward(stdin, fn, style, off, &sb); 230 } 231 exit(rval); 232 } 233 234 static boolean_t 235 iscount(const char *ap) 236 { 237 char c; 238 239 if (ap == NULL) { 240 return (B_FALSE); 241 } 242 243 c = ap[0]; 244 245 if (c == '+' || c == '-') { 246 c = ap[1]; 247 } 248 249 return (isdigit(c) ? B_TRUE : B_FALSE); 250 } 251 252 /* 253 * Convert the obsolete argument form into something that getopt can handle. 254 * This means that anything of the form [+-][0-9][0-9]*[lbc][Ffr] that isn't 255 * the option argument for a -b, -c or -n option gets converted. 256 */ 257 static void 258 obsolete(char *argv[]) 259 { 260 char *ap, *p, *t; 261 size_t len; 262 char *start; 263 264 while ((ap = *++argv)) { 265 /* Return if "--" or not an option of any form. */ 266 if (ap[0] != '-') { 267 if (ap[0] != '+') 268 return; 269 } else if (ap[1] == '-') 270 return; 271 272 switch (*++ap) { 273 /* Old-style option. */ 274 case '0': case '1': case '2': case '3': case '4': 275 case '5': case '6': case '7': case '8': case '9': 276 277 /* Malloc space for dash, new option and argument. */ 278 len = strlen(*argv); 279 if ((start = p = malloc(len + 3)) == NULL) 280 err(1, "malloc"); 281 *p++ = '-'; 282 283 /* 284 * Go to the end of the option argument. Save off any 285 * trailing options (-3lf) and translate any trailing 286 * output style characters. 287 */ 288 t = *argv + len - 1; 289 if (*t == 'F' || *t == 'f' || *t == 'r') { 290 *p++ = *t; 291 *t-- = '\0'; 292 } 293 switch (*t) { 294 case 'b': 295 *p++ = 'b'; 296 *t = '\0'; 297 break; 298 case 'c': 299 *p++ = 'c'; 300 *t = '\0'; 301 break; 302 case 'l': 303 *t = '\0'; 304 /* FALLTHROUGH */ 305 case '0': case '1': case '2': case '3': case '4': 306 case '5': case '6': case '7': case '8': case '9': 307 *p++ = 'n'; 308 break; 309 default: 310 errx(1, "illegal option -- %s", *argv); 311 } 312 *p++ = *argv[0]; 313 (void) strcpy(p, ap); 314 *argv = start; 315 continue; 316 317 /* 318 * Legacy Solaris tail supports "+c" "-c", "+l", "-l", 319 * "+b", and "-b" with a default value of 10. We need 320 * to determine here whether or not a count has been 321 * provided after the flag, and create a new, explicit 322 * argument as appropriate. [+-]l isn't allowed to have 323 * any numbers after it, but [+-][bc] can, potentially 324 * in the next command-line argument. We therefore 325 * handle them in two separate cases below. 326 */ 327 case 'l': 328 len = strlen(ap); 329 start = NULL; 330 331 if (len > 2) { 332 errx(1, "illegal option -- %s", *argv); 333 } 334 335 /* The only characters following should be flags */ 336 if (len == 2 && !isalpha(ap[1])) { 337 errx(1, "illegal option -- %s", *argv); 338 } 339 340 if (asprintf(&start, "-%sn%c10", 341 ap + 1, *argv[0]) == -1) { 342 err(1, "asprintf"); 343 } 344 345 *argv = start; 346 347 continue; 348 case 'b': 349 case 'c': 350 len = strlen(ap); 351 start = NULL; 352 353 if (len == 1) { 354 /* 355 * The option is just the flag name. Check if 356 * the next argument is a count, so we know 357 * whether we need to default to 10. 358 */ 359 if (iscount(argv[1])) { 360 ++argv; 361 continue; 362 } else { 363 if (asprintf(&start, 364 "-%c%c10", ap[0], *argv[0]) == -1) { 365 err(1, "asprintf"); 366 } 367 } 368 } else { 369 /* 370 * The option has characters following the c/b. 371 * If the characters following the option are a 372 * count, then we use those. This invocation is 373 * only allowed when '-' is used. 374 * 375 * Otherwise, we need to honor the following 376 * flags, and default to 10. 377 */ 378 if (iscount(ap + 1)) { 379 if (*argv[0] != '-') { 380 errx(1, "illegal option -- %s", 381 *argv); 382 } 383 384 if (asprintf(&start, "-%c%s", 385 ap[0], ap + 1) == -1) { 386 err(1, "asprintf"); 387 } 388 } else { 389 if (asprintf(&start, "-%s%c%c10", 390 ap + 1, ap[0], *argv[0]) == -1) { 391 err(1, "asprintf"); 392 } 393 } 394 } 395 396 *argv = start; 397 398 continue; 399 /* 400 * Options w/ arguments, skip the argument and continue 401 * with the next option. 402 */ 403 case 'n': 404 if (!ap[1]) 405 ++argv; 406 /* FALLTHROUGH */ 407 /* Options w/o arguments, continue with the next option. */ 408 case 'F': 409 case 'f': 410 case 'r': 411 continue; 412 413 /* Illegal option, return and let getopt handle it. */ 414 default: 415 return; 416 } 417 } 418 } 419 420 static void 421 usage(void) 422 { 423 (void) fprintf(stderr, 424 "usage: tail [-F | -f | -r] [-q] [-b # | -c # | -n #]" 425 " [file ...]\n"); 426 exit(1); 427 } 428