Commit | Line | Data |
---|---|---|
94bed826 | 1 | /*- |
0da48e03 | 2 | * Copyright (c) 1991 The Regents of the University of California. |
94bed826 KB |
3 | * All rights reserved. |
4 | * | |
0da48e03 KB |
5 | * This code is derived from software contributed to Berkeley by |
6 | * Edward Sze-Tyan Wang. | |
7 | * | |
8 | * %sccs.include.redist.c% | |
22e155fc DF |
9 | */ |
10 | ||
11 | #ifndef lint | |
12 | char copyright[] = | |
0da48e03 | 13 | "@(#) Copyright (c) 1991 The Regents of the University of California.\n\ |
22e155fc | 14 | All rights reserved.\n"; |
94bed826 | 15 | #endif /* not lint */ |
22e155fc | 16 | |
c9e4db79 | 17 | #ifndef lint |
fb9b2fa0 | 18 | static char sccsid[] = "@(#)tail.c 5.10 (Berkeley) %G%"; |
94bed826 | 19 | #endif /* not lint */ |
22e155fc | 20 | |
0da48e03 KB |
21 | #include <sys/types.h> |
22 | #include <sys/stat.h> | |
23 | #include <errno.h> | |
24 | #include <unistd.h> | |
25 | #include <stdio.h> | |
26 | #include <stdlib.h> | |
27 | #include <string.h> | |
28 | #include "extern.h" | |
29 | ||
30 | int fflag, rflag, rval; | |
31 | char *fname; | |
32 | ||
33 | static void obsolete __P((char **)); | |
34 | static void usage __P((void)); | |
35 | ||
8b48703f | 36 | int |
0da48e03 KB |
37 | main(argc, argv) |
38 | int argc; | |
8b48703f | 39 | char *argv[]; |
15dbf57f | 40 | { |
0da48e03 KB |
41 | struct stat sb; |
42 | FILE *fp; | |
43 | long off; | |
44 | enum STYLE style; | |
31919d23 | 45 | int ch, first; |
ea8ca20d | 46 | char *p; |
0da48e03 | 47 | |
ea8ca20d KB |
48 | /* |
49 | * Tail's options are weird. First, -n10 is the same as -n-10, not | |
50 | * -n+10. Second, the number options are 1 based and not offsets, | |
51 | * so -n+1 is the first line, and -c-1 is the last byte. Third, the | |
52 | * number options for the -r option specify the number of things that | |
53 | * get displayed, not the starting point in the file. The one major | |
54 | * incompatibility in this version as compared to historical versions | |
55 | * is that the 'r' option couldn't be modified by the -lbc options, | |
56 | * i.e. it was always done in lines. This version treats -rc as a | |
57 | * number of characters in reverse order. Finally, the default for | |
58 | * -r is the entire file, not 10 lines. | |
59 | */ | |
fb9b2fa0 KB |
60 | #define ARG(units, forward, backward) { \ |
61 | if (style) \ | |
62 | usage(); \ | |
63 | off = strtol(optarg, &p, 10) * (units); \ | |
64 | if (*p) \ | |
65 | err(1, "illegal offset -- %s", optarg); \ | |
66 | switch(optarg[0]) { \ | |
67 | case '+': \ | |
68 | if (off) \ | |
69 | off -= (units); \ | |
70 | style = (forward); \ | |
71 | break; \ | |
72 | case '-': \ | |
73 | off = -off; \ | |
74 | /* FALLTHROUGH */ \ | |
75 | default: \ | |
76 | style = (backward); \ | |
77 | break; \ | |
78 | } \ | |
ea8ca20d | 79 | } |
0da48e03 | 80 | |
ea8ca20d | 81 | obsolete(argv); |
0da48e03 KB |
82 | style = NOTSET; |
83 | while ((ch = getopt(argc, argv, "b:c:fn:r")) != EOF) | |
84 | switch(ch) { | |
85 | case 'b': | |
ea8ca20d | 86 | ARG(512, FBYTES, RBYTES); |
0da48e03 KB |
87 | break; |
88 | case 'c': | |
ea8ca20d | 89 | ARG(1, FBYTES, RBYTES); |
0da48e03 KB |
90 | break; |
91 | case 'f': | |
92 | fflag = 1; | |
93 | break; | |
94 | case 'n': | |
ea8ca20d | 95 | ARG(1, FLINES, RLINES); |
0da48e03 KB |
96 | break; |
97 | case 'r': | |
98 | rflag = 1; | |
99 | break; | |
100 | case '?': | |
101 | default: | |
102 | usage(); | |
15dbf57f | 103 | } |
0da48e03 KB |
104 | argc -= optind; |
105 | argv += optind; | |
106 | ||
8b48703f KB |
107 | if (fflag && argc > 1) |
108 | err(1, "-f option only appropriate for a single file"); | |
109 | ||
0da48e03 | 110 | /* |
ea8ca20d KB |
111 | * If displaying in reverse, don't permit follow option, and convert |
112 | * style values. | |
0da48e03 KB |
113 | */ |
114 | if (rflag) { | |
115 | if (fflag) | |
116 | usage(); | |
0da48e03 KB |
117 | if (style == FBYTES) |
118 | style = RBYTES; | |
8b48703f | 119 | else if (style == FLINES) |
0da48e03 | 120 | style = RLINES; |
15dbf57f | 121 | } |
0da48e03 | 122 | |
ea8ca20d KB |
123 | /* |
124 | * If style not specified, the default is the whole file for -r, and | |
125 | * the last 10 lines if not -r. | |
126 | */ | |
127 | if (style == NOTSET) | |
128 | if (rflag) { | |
129 | off = 0; | |
130 | style = REVERSE; | |
131 | } else { | |
132 | off = 10; | |
133 | style = RLINES; | |
134 | } | |
135 | ||
31919d23 KB |
136 | if (*argv) |
137 | for (first = 1; fname = *argv++;) { | |
8b48703f KB |
138 | if ((fp = fopen(fname, "r")) == NULL || |
139 | fstat(fileno(fp), &sb)) { | |
31919d23 KB |
140 | ierr(); |
141 | continue; | |
142 | } | |
143 | if (argc > 1) { | |
144 | (void)printf("%s==> %s <==\n", | |
145 | first ? "" : "\n", fname); | |
146 | first = 0; | |
fb9b2fa0 | 147 | (void)fflush(stdout); |
31919d23 KB |
148 | } |
149 | ||
150 | if (rflag) | |
151 | reverse(fp, style, off, &sb); | |
152 | else | |
153 | forward(fp, style, off, &sb); | |
8b48703f | 154 | (void)fclose(fp); |
31919d23 KB |
155 | } |
156 | else { | |
0da48e03 | 157 | fname = "stdin"; |
0da48e03 | 158 | |
8b48703f | 159 | if (fstat(fileno(stdin), &sb)) { |
31919d23 KB |
160 | ierr(); |
161 | exit(1); | |
162 | } | |
0da48e03 | 163 | |
31919d23 KB |
164 | /* |
165 | * Determine if input is a pipe. 4.4BSD will set the SOCKET | |
166 | * bit in the st_mode field for pipes. Fix this then. | |
167 | */ | |
8b48703f KB |
168 | if (lseek(fileno(stdin), 0L, SEEK_CUR) == -1 && |
169 | errno == ESPIPE) { | |
31919d23 KB |
170 | errno = 0; |
171 | fflag = 0; /* POSIX.2 requires this. */ | |
172 | } | |
0da48e03 | 173 | |
31919d23 | 174 | if (rflag) |
8b48703f | 175 | reverse(stdin, style, off, &sb); |
31919d23 | 176 | else |
8b48703f | 177 | forward(stdin, style, off, &sb); |
31919d23 | 178 | } |
0da48e03 KB |
179 | exit(rval); |
180 | } | |
181 | ||
182 | /* | |
183 | * Convert the obsolete argument form into something that getopt can handle. | |
184 | * This means that anything of the form [+-][0-9][0-9]*[lbc][fr] that isn't | |
185 | * the option argument for a -b, -c or -n option gets converted. | |
186 | */ | |
187 | static void | |
188 | obsolete(argv) | |
8b48703f | 189 | char *argv[]; |
0da48e03 KB |
190 | { |
191 | register char *ap, *p, *t; | |
192 | int len; | |
193 | char *start; | |
194 | ||
195 | while (ap = *++argv) { | |
196 | /* Return if "--" or not an option of any form. */ | |
197 | if (ap[0] != '-') { | |
198 | if (ap[0] != '+') | |
199 | return; | |
200 | } else if (ap[1] == '-') | |
201 | return; | |
202 | ||
203 | switch(*++ap) { | |
204 | /* Old-style option. */ | |
205 | case '0': case '1': case '2': case '3': case '4': | |
206 | case '5': case '6': case '7': case '8': case '9': | |
207 | ||
208 | /* Malloc space for dash, new option and argument. */ | |
209 | len = strlen(*argv); | |
210 | if ((start = p = malloc(len + 3)) == NULL) | |
31919d23 | 211 | err(1, "%s", strerror(errno)); |
0da48e03 KB |
212 | *p++ = '-'; |
213 | ||
214 | /* | |
215 | * Go to the end of the option argument. Save off any | |
216 | * trailing options (-3lf) and translate any trailing | |
217 | * output style characters. | |
218 | */ | |
219 | t = *argv + len - 1; | |
94dde571 KB |
220 | if (*t == 'f' || *t == 'r') { |
221 | *p++ = *t; | |
222 | *t-- = '\0'; | |
223 | } | |
0da48e03 KB |
224 | switch(*t) { |
225 | case 'b': | |
226 | *p++ = 'b'; | |
227 | *t = '\0'; | |
228 | break; | |
229 | case 'c': | |
230 | *p++ = 'c'; | |
231 | *t = '\0'; | |
232 | break; | |
233 | case 'l': | |
234 | *t = '\0'; | |
235 | /* FALLTHROUGH */ | |
236 | case '0': case '1': case '2': case '3': case '4': | |
237 | case '5': case '6': case '7': case '8': case '9': | |
238 | *p++ = 'n'; | |
239 | break; | |
240 | default: | |
31919d23 | 241 | err(1, "illegal option -- %s", *argv); |
15dbf57f | 242 | } |
0da48e03 KB |
243 | *p++ = *argv[0]; |
244 | (void)strcpy(p, ap); | |
245 | *argv = start; | |
246 | continue; | |
247 | ||
248 | /* | |
249 | * Options w/ arguments, skip the argument and continue | |
250 | * with the next option. | |
251 | */ | |
252 | case 'b': | |
253 | case 'c': | |
254 | case 'n': | |
255 | if (!ap[1]) | |
256 | ++argv; | |
257 | /* FALLTHROUGH */ | |
258 | /* Options w/o arguments, continue with the next option. */ | |
259 | case 'f': | |
260 | case 'r': | |
261 | continue; | |
262 | ||
263 | /* Illegal option, return and let getopt handle it. */ | |
264 | default: | |
265 | return; | |
266 | } | |
15dbf57f | 267 | } |
15dbf57f BJ |
268 | } |
269 | ||
0da48e03 KB |
270 | static void |
271 | usage() | |
272 | { | |
273 | (void)fprintf(stderr, | |
8b48703f | 274 | "usage: tail [-f | -r] [-b # | -c # | -n #] [file ...]\n"); |
0da48e03 | 275 | exit(1); |
15dbf57f | 276 | } |