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
33 #include <sys/cdefs.h>
34 #ifndef lint
35 __COPYRIGHT("@(#) Copyright (c) 1991, 1993\
36 The Regents of the University of California. All rights reserved.");
37 #endif /* not lint */
38
39 #ifndef lint
40 #if 0
41 static char sccsid[] = "@(#)tail.c 8.1 (Berkeley) 6/6/93";
42 #endif
43 __RCSID("$NetBSD: tail.c,v 1.21 2023/08/07 19:11:25 tnn Exp $");
44 #endif /* not lint */
45
46 #include <sys/types.h>
47 #include <sys/stat.h>
48 #include <errno.h>
49 #include <unistd.h>
50 #include <stdio.h>
51 #include <stdlib.h>
52 #include <string.h>
53 #include "extern.h"
54
55 int fflag, rflag, rval;
56 const char *fname;
57
58 static void obsolete(char **);
59 static void usage(void) __dead;
60
61 int
main(int argc,char * argv[])62 main(int argc, char *argv[])
63 {
64 struct stat sb;
65 FILE *fp;
66 off_t off;
67 enum STYLE style;
68 int ch, first;
69 char *p;
70 int qflag = 0;
71 int vflag = 0;
72
73 setprogname(argv[0]);
74 off = 0;
75 /*
76 * Tail's options are weird. First, -n10 is the same as -n-10, not
77 * -n+10. Second, the number options are 1 based and not offsets,
78 * so -n+1 is the first line, and -c-1 is the last byte. Third, the
79 * number options for the -r option specify the number of things that
80 * get displayed, not the starting point in the file. The one major
81 * incompatibility in this version as compared to historical versions
82 * is that the 'r' option couldn't be modified by the -lbc options,
83 * i.e., it was always done in lines. This version treats -rc as a
84 * number of characters in reverse order. Finally, the default for
85 * -r is the entire file, not 10 lines.
86 */
87 #define ARG(units, forward, backward) { \
88 if (style) \
89 usage(); \
90 off = strtoll(optarg, &p, 10) * (units); \
91 if (*p) \
92 xerrx(1, "illegal offset -- %s", optarg); \
93 switch(optarg[0]) { \
94 case '+': \
95 if (off) \
96 off -= (units); \
97 style = (forward); \
98 break; \
99 case '-': \
100 off = -off; \
101 /* FALLTHROUGH */ \
102 default: \
103 style = (backward); \
104 break; \
105 } \
106 }
107
108 obsolete(argv);
109 style = NOTSET;
110 if (strcmp(getprogname(), "tac") == 0) {
111 qflag = 1;
112 vflag = 0;
113 rflag = 1;
114 argc -= 1;
115 argv += 1;
116 } else { /* tail */
117 while ((ch = getopt(argc, argv, "Fb:c:fn:rqv")) != -1)
118 switch(ch) {
119 case 'F':
120 fflag = 2;
121 break;
122 case 'b':
123 ARG(512, FBYTES, RBYTES);
124 break;
125 case 'c':
126 ARG(1, FBYTES, RBYTES);
127 break;
128 case 'f':
129 fflag = 1;
130 break;
131 case 'n':
132 ARG(1, FLINES, RLINES);
133 break;
134 case 'r':
135 rflag = 1;
136 break;
137 case 'q':
138 qflag = 1;
139 vflag = 0;
140 break;
141 case 'v':
142 qflag = 0;
143 vflag = 1;
144 break;
145 case '?':
146 default:
147 usage();
148 }
149 argc -= optind;
150 argv += optind;
151 }
152
153 if (fflag && argc > 1)
154 xerrx(1,
155 "-f and -F options only appropriate for a single file");
156
157 /*
158 * If displaying in reverse, don't permit follow option, and convert
159 * style values.
160 */
161 if (rflag) {
162 if (fflag)
163 usage();
164 if (style == FBYTES)
165 style = RBYTES;
166 else if (style == FLINES)
167 style = RLINES;
168 }
169
170 /*
171 * If style not specified, the default is the whole file for -r, and
172 * the last 10 lines if not -r.
173 */
174 if (style == NOTSET) {
175 if (rflag) {
176 off = 0;
177 style = REVERSE;
178 } else {
179 off = 10;
180 style = RLINES;
181 }
182 }
183 if (*argv)
184 for (first = 1; (fname = *argv++) != NULL;) {
185 if ((fp = fopen(fname, "r")) == NULL ||
186 fstat(fileno(fp), &sb)) {
187 ierr();
188 continue;
189 }
190 if (vflag || (qflag == 0 && argc > 1)) {
191 (void)printf("%s==> %s <==\n",
192 first ? "" : "\n", fname);
193 first = 0;
194 (void)fflush(stdout);
195 }
196
197 if (rflag)
198 reverse(fp, style, off, &sb);
199 else
200 forward(fp, style, off, &sb);
201 (void)fclose(fp);
202 }
203 else {
204 fname = "stdin";
205
206 if (fstat(fileno(stdin), &sb)) {
207 ierr();
208 exit(1);
209 }
210
211 /*
212 * Determine if input is a pipe. 4.4BSD will set the SOCKET
213 * bit in the st_mode field for pipes. Fix this then.
214 */
215 if (lseek(fileno(stdin), (off_t)0, SEEK_CUR) == -1 &&
216 errno == ESPIPE) {
217 errno = 0;
218 fflag = 0; /* POSIX.2 requires this. */
219 }
220
221 if (rflag)
222 reverse(stdin, style, off, &sb);
223 else
224 forward(stdin, style, off, &sb);
225 }
226 exit(rval);
227 }
228
229 /*
230 * Convert the obsolete argument form into something that getopt can handle.
231 * This means that anything of the form [+-][0-9][0-9]*[lbc][fr] that isn't
232 * the option argument for a -b, -c or -n option gets converted.
233 */
234 static void
obsolete(char * argv[])235 obsolete(char *argv[])
236 {
237 char *ap, *p, *t;
238 size_t len;
239 char *start;
240
241 while ((ap = *++argv) != NULL) {
242 /* Return if "--" or not an option of any form. */
243 if (ap[0] != '-') {
244 if (ap[0] != '+')
245 return;
246 } else if (ap[1] == '-')
247 return;
248
249 switch (*++ap) {
250 /* Old-style option. */
251 case '0': case '1': case '2': case '3': case '4':
252 case '5': case '6': case '7': case '8': case '9':
253
254 /* Malloc space for dash, new option and argument. */
255 len = strlen(*argv);
256 if ((start = p = malloc(len + 3)) == NULL)
257 xerr(1, "malloc");
258 *p++ = '-';
259
260 /*
261 * Go to the end of the option argument. Save off any
262 * trailing options (-3lf) and translate any trailing
263 * output style characters.
264 */
265 t = *argv + len - 1;
266 if (*t == 'f' || *t == 'r') {
267 *p++ = *t;
268 *t-- = '\0';
269 }
270 switch(*t) {
271 case 'b':
272 *p++ = 'b';
273 *t = '\0';
274 break;
275 case 'c':
276 *p++ = 'c';
277 *t = '\0';
278 break;
279 case 'l':
280 *t = '\0';
281 /* FALLTHROUGH */
282 case '0': case '1': case '2': case '3': case '4':
283 case '5': case '6': case '7': case '8': case '9':
284 *p++ = 'n';
285 break;
286 default:
287 xerrx(1, "illegal option -- %s", *argv);
288 }
289 *p++ = *argv[0];
290 (void)strcpy(p, ap);
291 *argv = start;
292 continue;
293
294 /*
295 * Options w/ arguments, skip the argument and continue
296 * with the next option.
297 */
298 case 'b':
299 case 'c':
300 case 'n':
301 if (!ap[1])
302 ++argv;
303 /* FALLTHROUGH */
304 /* Options w/o arguments, continue with the next option. */
305 case 'f':
306 case 'r':
307 continue;
308
309 /* Illegal option, return and let getopt handle it. */
310 default:
311 return;
312 }
313 }
314 }
315
316 static void
usage(void)317 usage(void)
318 {
319 (void)fprintf(stderr,
320 "Usage: %s [-qv] [-f | -F | -r] [-b # | -c # | -n #] [file ...]\n",
321 getprogname());
322 exit(1);
323 }
324