xref: /illumos-gate/usr/src/cmd/tail/tail.c (revision dd72704bd9e794056c558153663c739e2012d721)
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