1209e49b2SChris Love /*
2209e49b2SChris Love * Copyright (c) 1991, 1993
3209e49b2SChris Love * The Regents of the University of California. All rights reserved.
4209e49b2SChris Love *
5209e49b2SChris Love * This code is derived from software contributed to Berkeley by
6209e49b2SChris Love * Edward Sze-Tyan Wang.
7209e49b2SChris Love *
8209e49b2SChris Love * Redistribution and use in source and binary forms, with or without
9209e49b2SChris Love * modification, are permitted provided that the following conditions
10209e49b2SChris Love * are met:
11209e49b2SChris Love * 1. Redistributions of source code must retain the above copyright
12209e49b2SChris Love * notice, this list of conditions and the following disclaimer.
13209e49b2SChris Love * 2. Redistributions in binary form must reproduce the above copyright
14209e49b2SChris Love * notice, this list of conditions and the following disclaimer in the
15209e49b2SChris Love * documentation and/or other materials provided with the distribution.
16209e49b2SChris Love * 4. Neither the name of the University nor the names of its contributors
17209e49b2SChris Love * may be used to endorse or promote products derived from this software
18209e49b2SChris Love * without specific prior written permission.
19209e49b2SChris Love *
20209e49b2SChris Love * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21209e49b2SChris Love * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22209e49b2SChris Love * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23209e49b2SChris Love * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24209e49b2SChris Love * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25209e49b2SChris Love * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26209e49b2SChris Love * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27209e49b2SChris Love * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28209e49b2SChris Love * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29209e49b2SChris Love * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30209e49b2SChris Love * SUCH DAMAGE.
31209e49b2SChris Love */
32209e49b2SChris Love
33209e49b2SChris Love #include <sys/types.h>
34209e49b2SChris Love #include <sys/stat.h>
35209e49b2SChris Love
36209e49b2SChris Love #include <err.h>
37209e49b2SChris Love #include <errno.h>
38209e49b2SChris Love #include <stdio.h>
39209e49b2SChris Love #include <stdlib.h>
40209e49b2SChris Love #include <string.h>
41209e49b2SChris Love #include <unistd.h>
42209e49b2SChris Love
43209e49b2SChris Love #include "extern.h"
44209e49b2SChris Love
45209e49b2SChris Love int Fflag, fflag, qflag, rflag, rval, no_files;
46209e49b2SChris Love
47209e49b2SChris Love file_info_t *files;
48209e49b2SChris Love
49209e49b2SChris Love static void obsolete(char **);
50209e49b2SChris Love static void usage(void);
51209e49b2SChris Love
52209e49b2SChris Love int
main(int argc,char * argv[])53209e49b2SChris Love main(int argc, char *argv[])
54209e49b2SChris Love {
55209e49b2SChris Love struct stat sb;
56209e49b2SChris Love const char *fn;
57209e49b2SChris Love FILE *fp;
58209e49b2SChris Love off_t off;
59209e49b2SChris Love enum STYLE style;
60209e49b2SChris Love int i, ch, first;
61209e49b2SChris Love file_info_t *file;
62209e49b2SChris Love char *p;
63209e49b2SChris Love
64209e49b2SChris Love /*
65209e49b2SChris Love * Tail's options are weird. First, -n10 is the same as -n-10, not
66209e49b2SChris Love * -n+10. Second, the number options are 1 based and not offsets,
67209e49b2SChris Love * so -n+1 is the first line, and -c-1 is the last byte. Third, the
68209e49b2SChris Love * number options for the -r option specify the number of things that
69209e49b2SChris Love * get displayed, not the starting point in the file. The one major
70209e49b2SChris Love * incompatibility in this version as compared to historical versions
71209e49b2SChris Love * is that the 'r' option couldn't be modified by the -lbc options,
72209e49b2SChris Love * i.e. it was always done in lines. This version treats -rc as a
73209e49b2SChris Love * number of characters in reverse order. Finally, the default for
74209e49b2SChris Love * -r is the entire file, not 10 lines.
75209e49b2SChris Love */
76209e49b2SChris Love #define ARG(units, forward, backward) { \
77209e49b2SChris Love if (style) \
78209e49b2SChris Love usage(); \
79209e49b2SChris Love off = strtoll(optarg, &p, 10) * (units); \
80209e49b2SChris Love if (*p) \
81209e49b2SChris Love errx(1, "illegal offset -- %s", optarg); \
82209e49b2SChris Love switch (optarg[0]) { \
83209e49b2SChris Love case '+': \
84209e49b2SChris Love if (off) \
85209e49b2SChris Love off -= (units); \
86209e49b2SChris Love style = (forward); \
87209e49b2SChris Love break; \
88209e49b2SChris Love case '-': \
89209e49b2SChris Love off = -off; \
90209e49b2SChris Love /* FALLTHROUGH */ \
91209e49b2SChris Love default: \
92209e49b2SChris Love style = (backward); \
93209e49b2SChris Love break; \
94209e49b2SChris Love } \
95209e49b2SChris Love }
96209e49b2SChris Love
97209e49b2SChris Love obsolete(argv);
98209e49b2SChris Love style = NOTSET;
99209e49b2SChris Love off = 0;
100209e49b2SChris Love while ((ch = getopt(argc, argv, "Fb:c:fn:qr")) != -1)
101209e49b2SChris Love switch (ch) {
102209e49b2SChris Love case 'F': /* -F is superset of (and implies) -f */
103209e49b2SChris Love Fflag = fflag = 1;
104209e49b2SChris Love break;
105209e49b2SChris Love case 'b':
106209e49b2SChris Love ARG(512, FBYTES, RBYTES);
107209e49b2SChris Love break;
108209e49b2SChris Love case 'c':
109209e49b2SChris Love ARG(1, FBYTES, RBYTES);
110209e49b2SChris Love break;
111209e49b2SChris Love case 'f':
112209e49b2SChris Love fflag = 1;
113209e49b2SChris Love break;
114209e49b2SChris Love case 'n':
115209e49b2SChris Love ARG(1, FLINES, RLINES);
116209e49b2SChris Love break;
117209e49b2SChris Love case 'q':
118209e49b2SChris Love qflag = 1;
119209e49b2SChris Love break;
120209e49b2SChris Love case 'r':
121209e49b2SChris Love rflag = 1;
122209e49b2SChris Love break;
123209e49b2SChris Love case '?':
124209e49b2SChris Love default:
125209e49b2SChris Love usage();
126209e49b2SChris Love }
127209e49b2SChris Love argc -= optind;
128209e49b2SChris Love argv += optind;
129209e49b2SChris Love
130209e49b2SChris Love no_files = argc ? argc : 1;
131209e49b2SChris Love
132209e49b2SChris Love /*
133209e49b2SChris Love * If displaying in reverse, don't permit follow option, and convert
134209e49b2SChris Love * style values.
135209e49b2SChris Love */
136209e49b2SChris Love if (rflag) {
137209e49b2SChris Love if (fflag)
138209e49b2SChris Love usage();
139209e49b2SChris Love if (style == FBYTES)
140209e49b2SChris Love style = RBYTES;
141209e49b2SChris Love else if (style == FLINES)
142209e49b2SChris Love style = RLINES;
143209e49b2SChris Love }
144209e49b2SChris Love
145209e49b2SChris Love /*
146209e49b2SChris Love * If style not specified, the default is the whole file for -r, and
147209e49b2SChris Love * the last 10 lines if not -r.
148209e49b2SChris Love */
149209e49b2SChris Love if (style == NOTSET) {
150209e49b2SChris Love if (rflag) {
151209e49b2SChris Love off = 0;
152209e49b2SChris Love style = REVERSE;
153209e49b2SChris Love } else {
154209e49b2SChris Love off = 10;
155209e49b2SChris Love style = RLINES;
156209e49b2SChris Love }
157209e49b2SChris Love }
158209e49b2SChris Love
159209e49b2SChris Love if (*argv && fflag) {
160209e49b2SChris Love files = (struct file_info *)malloc(no_files *
161209e49b2SChris Love sizeof (struct file_info));
162209e49b2SChris Love if (!files)
163209e49b2SChris Love err(1, "Couldn't malloc space for file descriptors.");
164209e49b2SChris Love
165209e49b2SChris Love for (file = files; (fn = *argv++); file++) {
166209e49b2SChris Love file->file_name = strdup(fn);
167209e49b2SChris Love if (! file->file_name)
168209e49b2SChris Love errx(1, "Couldn't malloc space for file name.");
169209e49b2SChris Love if ((file->fp = fopen(file->file_name, "r")) == NULL ||
170209e49b2SChris Love fstat(fileno(file->fp), &file->st)) {
171209e49b2SChris Love if (file->fp != NULL) {
172209e49b2SChris Love (void) fclose(file->fp);
173209e49b2SChris Love file->fp = NULL;
174209e49b2SChris Love }
175209e49b2SChris Love if (!Fflag || errno != ENOENT)
176209e49b2SChris Love ierr(file->file_name);
177209e49b2SChris Love }
178209e49b2SChris Love }
179209e49b2SChris Love follow(files, style, off);
180209e49b2SChris Love for (i = 0, file = files; i < no_files; i++, file++) {
181209e49b2SChris Love free(file->file_name);
182209e49b2SChris Love }
183209e49b2SChris Love free(files);
184209e49b2SChris Love } else if (*argv) {
185209e49b2SChris Love for (first = 1; (fn = *argv++); ) {
186209e49b2SChris Love if ((fp = fopen(fn, "r")) == NULL ||
187209e49b2SChris Love fstat(fileno(fp), &sb)) {
188209e49b2SChris Love ierr(fn);
189209e49b2SChris Love continue;
190209e49b2SChris Love }
191209e49b2SChris Love if (argc > 1 && !qflag) {
192209e49b2SChris Love (void) printf("%s==> %s <==\n",
193209e49b2SChris Love first ? "" : "\n", fn);
194209e49b2SChris Love first = 0;
195209e49b2SChris Love (void) fflush(stdout);
196209e49b2SChris Love }
197209e49b2SChris Love
198209e49b2SChris Love if (rflag)
199209e49b2SChris Love reverse(fp, fn, style, off, &sb);
200209e49b2SChris Love else
201209e49b2SChris Love forward(fp, fn, style, off, &sb);
202209e49b2SChris Love }
203209e49b2SChris Love } else {
204209e49b2SChris Love fn = "stdin";
205209e49b2SChris Love
206209e49b2SChris Love if (fstat(fileno(stdin), &sb)) {
207209e49b2SChris Love ierr(fn);
208209e49b2SChris Love exit(1);
209209e49b2SChris Love }
210209e49b2SChris Love
211209e49b2SChris Love /*
212209e49b2SChris Love * Determine if input is a pipe. 4.4BSD will set the SOCKET
213209e49b2SChris Love * bit in the st_mode field for pipes. Fix this then.
214209e49b2SChris Love */
215209e49b2SChris Love if (lseek(fileno(stdin), (off_t)0, SEEK_CUR) == -1 &&
216209e49b2SChris Love errno == ESPIPE) {
217209e49b2SChris Love errno = 0;
218209e49b2SChris Love fflag = 0; /* POSIX.2 requires this. */
219209e49b2SChris Love }
220209e49b2SChris Love
221209e49b2SChris Love if (rflag)
222209e49b2SChris Love reverse(stdin, fn, style, off, &sb);
223209e49b2SChris Love else
224209e49b2SChris Love forward(stdin, fn, style, off, &sb);
225209e49b2SChris Love }
226209e49b2SChris Love exit(rval);
227209e49b2SChris Love }
228209e49b2SChris Love
229209e49b2SChris Love /*
230209e49b2SChris Love * Convert the obsolete argument form into something that getopt can handle.
231209e49b2SChris Love * This means that anything of the form [+-][0-9][0-9]*[lbc][Ffr] that isn't
232209e49b2SChris Love * the option argument for a -b, -c or -n option gets converted.
233209e49b2SChris Love */
234209e49b2SChris Love static void
obsolete(char * argv[])235209e49b2SChris Love obsolete(char *argv[])
236209e49b2SChris Love {
237209e49b2SChris Love char *ap, *p, *t;
238209e49b2SChris Love size_t len;
239209e49b2SChris Love char *start;
240209e49b2SChris Love
241209e49b2SChris Love while ((ap = *++argv)) {
242209e49b2SChris Love /* Return if "--" or not an option of any form. */
243209e49b2SChris Love if (ap[0] != '-') {
244209e49b2SChris Love if (ap[0] != '+')
245209e49b2SChris Love return;
246209e49b2SChris Love } else if (ap[1] == '-')
247209e49b2SChris Love return;
248209e49b2SChris Love
249209e49b2SChris Love switch (*++ap) {
250209e49b2SChris Love /* Old-style option. */
251209e49b2SChris Love case '0': case '1': case '2': case '3': case '4':
252209e49b2SChris Love case '5': case '6': case '7': case '8': case '9':
253209e49b2SChris Love
254209e49b2SChris Love /* Malloc space for dash, new option and argument. */
255209e49b2SChris Love len = strlen(*argv);
256209e49b2SChris Love if ((start = p = malloc(len + 3)) == NULL)
257209e49b2SChris Love err(1, "malloc");
258209e49b2SChris Love *p++ = '-';
259209e49b2SChris Love
260209e49b2SChris Love /*
261209e49b2SChris Love * Go to the end of the option argument. Save off any
262209e49b2SChris Love * trailing options (-3lf) and translate any trailing
263209e49b2SChris Love * output style characters.
264209e49b2SChris Love */
265209e49b2SChris Love t = *argv + len - 1;
266209e49b2SChris Love if (*t == 'F' || *t == 'f' || *t == 'r') {
267209e49b2SChris Love *p++ = *t;
268209e49b2SChris Love *t-- = '\0';
269209e49b2SChris Love }
270209e49b2SChris Love switch (*t) {
271209e49b2SChris Love case 'b':
272209e49b2SChris Love *p++ = 'b';
273209e49b2SChris Love *t = '\0';
274209e49b2SChris Love break;
275209e49b2SChris Love case 'c':
276209e49b2SChris Love *p++ = 'c';
277209e49b2SChris Love *t = '\0';
278209e49b2SChris Love break;
279209e49b2SChris Love case 'l':
280209e49b2SChris Love *t = '\0';
281209e49b2SChris Love /* FALLTHROUGH */
282209e49b2SChris Love case '0': case '1': case '2': case '3': case '4':
283209e49b2SChris Love case '5': case '6': case '7': case '8': case '9':
284209e49b2SChris Love *p++ = 'n';
285209e49b2SChris Love break;
286209e49b2SChris Love default:
287209e49b2SChris Love errx(1, "illegal option -- %s", *argv);
288209e49b2SChris Love }
289209e49b2SChris Love *p++ = *argv[0];
290209e49b2SChris Love (void) strcpy(p, ap);
291209e49b2SChris Love *argv = start;
292209e49b2SChris Love continue;
293209e49b2SChris Love
294209e49b2SChris Love /*
295209e49b2SChris Love * Legacy Solaris tail supports "+c" "-c", "+l", "-l",
296209e49b2SChris Love * "+b", and "-b" with an default number of 10. Map
297209e49b2SChris Love * these arguments to an explicit +/-10 for FreeBSD.
298209e49b2SChris Love * New argument will be of the form -[bcn][+-]10
299209e49b2SChris Love */
300209e49b2SChris Love case 'b':
301209e49b2SChris Love case 'c':
302209e49b2SChris Love case 'l':
303209e49b2SChris Love if ((start = p = malloc(6)) == NULL)
304209e49b2SChris Love err(1, "malloc");
305209e49b2SChris Love *p++ = '-';
306209e49b2SChris Love switch (ap[0]) {
307209e49b2SChris Love case 'c':
308209e49b2SChris Love *p++ = ap[0];
309209e49b2SChris Love break;
310209e49b2SChris Love case 'b':
311209e49b2SChris Love *p++ = ap[0];
312209e49b2SChris Love break;
313209e49b2SChris Love case 'l':
314209e49b2SChris Love *p++ = 'n';
315209e49b2SChris Love break;
316209e49b2SChris Love }
317*065da06dSChris Love (void) snprintf(p, 6, "%c10", *argv[0]);
318209e49b2SChris Love *argv = start;
319209e49b2SChris Love
320209e49b2SChris Love continue;
321209e49b2SChris Love /*
322209e49b2SChris Love * Options w/ arguments, skip the argument and continue
323209e49b2SChris Love * with the next option.
324209e49b2SChris Love */
325209e49b2SChris Love case 'n':
326209e49b2SChris Love if (!ap[1])
327209e49b2SChris Love ++argv;
328209e49b2SChris Love /* FALLTHROUGH */
329209e49b2SChris Love /* Options w/o arguments, continue with the next option. */
330209e49b2SChris Love case 'F':
331209e49b2SChris Love case 'f':
332209e49b2SChris Love case 'r':
333209e49b2SChris Love continue;
334209e49b2SChris Love
335209e49b2SChris Love /* Illegal option, return and let getopt handle it. */
336209e49b2SChris Love default:
337209e49b2SChris Love return;
338209e49b2SChris Love }
339209e49b2SChris Love }
340209e49b2SChris Love }
341209e49b2SChris Love
342209e49b2SChris Love static void
usage(void)343209e49b2SChris Love usage(void)
344209e49b2SChris Love {
345209e49b2SChris Love (void) fprintf(stderr,
346209e49b2SChris Love "usage: tail [-F | -f | -r] [-q] [-b # | -c # | -n #]"
347209e49b2SChris Love " [file ...]\n");
348209e49b2SChris Love exit(1);
349209e49b2SChris Love }
350