8671 tail(1) ignores arguments after -c/b/l
[unleashed.git] / usr / src / cmd / tail / tail.c
blob8e83ae40796e3a6ceaba2f9d7c8e9f2726c1597e
1 /*
2 * Copyright (c) 1991, 1993
3 * The Regents of the University of California. All rights reserved.
5 * This code is derived from software contributed to Berkeley by
6 * Edward Sze-Tyan Wang.
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.
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.
34 * Copyright 2017, Joyent, Inc.
37 #include <sys/types.h>
38 #include <sys/stat.h>
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>
48 #include "extern.h"
50 int Fflag, fflag, qflag, rflag, rval, no_files;
52 file_info_t *files;
54 static void obsolete(char **);
55 static void usage(void);
57 int
58 main(int argc, char *argv[])
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;
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.
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 } \
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();
132 argc -= optind;
133 argv += optind;
135 no_files = argc ? argc : 1;
138 * If displaying in reverse, don't permit follow option, and convert
139 * style values.
141 if (rflag) {
142 if (fflag)
143 usage();
144 if (style == FBYTES)
145 style = RBYTES;
146 else if (style == FLINES)
147 style = RLINES;
151 * If style not specified, the default is the whole file for -r, and
152 * the last 10 lines if not -r.
154 if (style == NOTSET) {
155 if (rflag) {
156 off = 0;
157 style = REVERSE;
158 } else {
159 off = 10;
160 style = RLINES;
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.");
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;
180 if (!Fflag || errno != ENOENT)
181 ierr(file->file_name);
184 follow(files, style, off);
185 for (i = 0, file = files; i < no_files; i++, file++) {
186 free(file->file_name);
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;
196 if (argc > 1 && !qflag) {
197 (void) printf("%s==> %s <==\n",
198 first ? "" : "\n", fn);
199 first = 0;
200 (void) fflush(stdout);
203 if (rflag)
204 reverse(fp, fn, style, off, &sb);
205 else
206 forward(fp, fn, style, off, &sb);
208 } else {
209 fn = "stdin";
211 if (fstat(fileno(stdin), &sb)) {
212 ierr(fn);
213 exit(1);
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.
220 if (lseek(fileno(stdin), (off_t)0, SEEK_CUR) == -1 &&
221 errno == ESPIPE) {
222 errno = 0;
223 fflag = 0; /* POSIX.2 requires this. */
226 if (rflag)
227 reverse(stdin, fn, style, off, &sb);
228 else
229 forward(stdin, fn, style, off, &sb);
231 exit(rval);
234 static boolean_t
235 iscount(const char *ap)
237 char c;
239 if (ap == NULL) {
240 return (B_FALSE);
243 c = ap[0];
245 if (c == '+' || c == '-') {
246 c = ap[1];
249 return (isdigit(c) ? B_TRUE : B_FALSE);
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.
257 static void
258 obsolete(char *argv[])
260 char *ap, *p, *t;
261 size_t len;
262 char *start;
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;
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':
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++ = '-';
284 * Go to the end of the option argument. Save off any
285 * trailing options (-3lf) and translate any trailing
286 * output style characters.
288 t = *argv + len - 1;
289 if (*t == 'F' || *t == 'f' || *t == 'r') {
290 *p++ = *t;
291 *t-- = '\0';
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);
312 *p++ = *argv[0];
313 (void) strcpy(p, ap);
314 *argv = start;
315 continue;
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.
327 case 'l':
328 len = strlen(ap);
329 start = NULL;
331 if (len > 2) {
332 errx(1, "illegal option -- %s", *argv);
335 /* The only characters following should be flags */
336 if (len == 2 && !isalpha(ap[1])) {
337 errx(1, "illegal option -- %s", *argv);
340 if (asprintf(&start, "-%sn%c10",
341 ap + 1, *argv[0]) == -1) {
342 err(1, "asprintf");
345 *argv = start;
347 continue;
348 case 'b':
349 case 'c':
350 len = strlen(ap);
351 start = NULL;
353 if (len == 1) {
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.
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");
368 } else {
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.
375 * Otherwise, we need to honor the following
376 * flags, and default to 10.
378 if (iscount(ap + 1)) {
379 if (*argv[0] != '-') {
380 errx(1, "illegal option -- %s",
381 *argv);
384 if (asprintf(&start, "-%c%s",
385 ap[0], ap + 1) == -1) {
386 err(1, "asprintf");
388 } else {
389 if (asprintf(&start, "-%s%c%c10",
390 ap + 1, ap[0], *argv[0]) == -1) {
391 err(1, "asprintf");
396 *argv = start;
398 continue;
400 * Options w/ arguments, skip the argument and continue
401 * with the next option.
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;
413 /* Illegal option, return and let getopt handle it. */
414 default:
415 return;
420 static void
421 usage(void)
423 (void) fprintf(stderr,
424 "usage: tail [-F | -f | -r] [-q] [-b # | -c # | -n #]"
425 " [file ...]\n");
426 exit(1);