1 | /* tail - copy the end of a file Author: Norbert Schlenker */
|
---|
2 |
|
---|
3 | /* Syntax: tail [-f] [-c number | -n number] [file]
|
---|
4 | * tail -[number][c|l][f] [file] (obsolescent)
|
---|
5 | * tail +[number][c|l][f] [file] (obsolescent)
|
---|
6 | * Flags:
|
---|
7 | * -c number Measure starting point in bytes. If number begins
|
---|
8 | * with '+', the starting point is relative to the
|
---|
9 | * the file's beginning. If number begins with '-'
|
---|
10 | * or has no sign, the starting point is relative to
|
---|
11 | * the end of the file.
|
---|
12 | * -f Keep trying to read after EOF on files and FIFOs.
|
---|
13 | * -n number Measure starting point in lines. The number
|
---|
14 | * following the flag has significance similar to
|
---|
15 | * that described for the -c flag.
|
---|
16 | *
|
---|
17 | * If neither -c nor -n are specified, the default is tail -n 10.
|
---|
18 | *
|
---|
19 | * In the obsolescent syntax, an argument with a 'c' following the
|
---|
20 | * (optional) number is equivalent to "-c number" in the standard
|
---|
21 | * syntax, with number including the leading sign ('+' or '-') of the
|
---|
22 | * argument. An argument with 'l' following the number is equivalent
|
---|
23 | * to "-n number" in the standard syntax. If the number is not
|
---|
24 | * specified, 10 is used as the default. If neither 'c' nor 'l' are
|
---|
25 | * specified, 'l' is assumed. The character 'f' may be suffixed to
|
---|
26 | * the argument and is equivalent to specifying "-f" in the standard
|
---|
27 | * syntax. Look for lines marked "OBSOLESCENT".
|
---|
28 | *
|
---|
29 | * If no file is specified, standard input is assumed.
|
---|
30 | *
|
---|
31 | * P1003.2 does not specify tail's behavior when a count of 0 is given.
|
---|
32 | * It also does not specify clearly whether the first byte (line) of a
|
---|
33 | * file should be numbered 0 or 1. Historical behavior is that the
|
---|
34 | * first byte is actually number 1 (contrary to all Unix standards).
|
---|
35 | * Historically, a count of 0 (or -0) results in no output whatsoever,
|
---|
36 | * while a count of +0 results in the entire file being copied (just like
|
---|
37 | * +1). The implementor does not agree with these behaviors, but has
|
---|
38 | * copied them slavishly. Look for lines marked "HISTORICAL".
|
---|
39 | *
|
---|
40 | * Author: Norbert Schlenker
|
---|
41 | * Copyright: None. Released to the public domain.
|
---|
42 | * Reference: P1003.2 section 4.59 (draft 10)
|
---|
43 | * Notes: Under Minix, this program requires chmem =30000.
|
---|
44 | * Bugs: No internationalization support; all messages are in English.
|
---|
45 | */
|
---|
46 |
|
---|
47 | /* Force visible Posix names */
|
---|
48 | #ifndef _POSIX_SOURCE
|
---|
49 | #define _POSIX_SOURCE 1
|
---|
50 | #endif
|
---|
51 |
|
---|
52 | /* External interfaces */
|
---|
53 | #include <sys/types.h>
|
---|
54 | #include <sys/stat.h>
|
---|
55 | #include <unistd.h>
|
---|
56 | #include <ctype.h>
|
---|
57 | #include <stdlib.h>
|
---|
58 | #include <stdio.h>
|
---|
59 |
|
---|
60 | /* External interfaces that should have been standardized into <getopt.h> */
|
---|
61 | extern char *optarg;
|
---|
62 | extern int optind;
|
---|
63 |
|
---|
64 | /* We expect this constant to be defined in <limits.h> in a Posix program,
|
---|
65 | * but we'll specify it here just in case it's been left out.
|
---|
66 | */
|
---|
67 | #ifndef LINE_MAX
|
---|
68 | #define LINE_MAX 2048 /* minimum acceptable lower bound */
|
---|
69 | #endif
|
---|
70 |
|
---|
71 | /* Magic numbers suggested or required by Posix specification */
|
---|
72 | #define SUCCESS 0 /* exit code in case of success */
|
---|
73 | #define FAILURE 1 /* or failure */
|
---|
74 | #define DEFAULT_COUNT 10 /* default number of lines or bytes */
|
---|
75 | #define MIN_BUFSIZE (LINE_MAX * DEFAULT_COUNT)
|
---|
76 | #define SLEEP_INTERVAL 1 /* sleep for one second intervals with -f */
|
---|
77 |
|
---|
78 | #define FALSE 0
|
---|
79 | #define TRUE 1
|
---|
80 |
|
---|
81 | /* Internal functions - prototyped under Minix */
|
---|
82 | _PROTOTYPE(int main, (int argc, char **argv));
|
---|
83 | _PROTOTYPE(int tail, (int count, int bytes, int read_until_killed));
|
---|
84 | _PROTOTYPE(int keep_reading, (void));
|
---|
85 | _PROTOTYPE(void usage, (void));
|
---|
86 |
|
---|
87 | int main(argc, argv)
|
---|
88 | int argc;
|
---|
89 | char *argv[];
|
---|
90 | {
|
---|
91 | int cflag = FALSE;
|
---|
92 | int nflag = FALSE;
|
---|
93 | int fflag = FALSE;
|
---|
94 | int number = -DEFAULT_COUNT;
|
---|
95 | char *suffix;
|
---|
96 | int opt;
|
---|
97 | struct stat stat_buf;
|
---|
98 |
|
---|
99 | /* Determining whether this invocation is via the standard syntax or
|
---|
100 | * via an obsolescent one is a nasty kludge. Here it is, but there is
|
---|
101 | * no pretense at elegance.
|
---|
102 | */
|
---|
103 | if (argc == 1) { /* simple: default read of a pipe */
|
---|
104 | exit(tail(-DEFAULT_COUNT, 0, fflag));
|
---|
105 | }
|
---|
106 | if ((argv[1][0] == '+') || /* OBSOLESCENT */
|
---|
107 | (argv[1][0] == '-' && ((isdigit(argv[1][1])) ||
|
---|
108 | (argv[1][1] == 'l') ||
|
---|
109 | (argv[1][1] == 'c' && argv[1][2] == 'f')))) {
|
---|
110 | --argc; ++argv;
|
---|
111 | if (isdigit(argv[0][1])) {
|
---|
112 | number = (int)strtol(argv[0], &suffix, 10);
|
---|
113 | if (number == 0) { /* HISTORICAL */
|
---|
114 | if (argv[0][0] == '+')
|
---|
115 | number = 1;
|
---|
116 | else
|
---|
117 | exit(SUCCESS);
|
---|
118 | }
|
---|
119 | } else {
|
---|
120 | number = (argv[0][0] == '+') ? DEFAULT_COUNT : -DEFAULT_COUNT;
|
---|
121 | suffix = &(argv[0][1]);
|
---|
122 | }
|
---|
123 | if (*suffix != '\0') {
|
---|
124 | if (*suffix == 'c') {
|
---|
125 | cflag = TRUE;
|
---|
126 | ++suffix;
|
---|
127 | }
|
---|
128 | else
|
---|
129 | if (*suffix == 'l') {
|
---|
130 | nflag = TRUE;
|
---|
131 | ++suffix;
|
---|
132 | }
|
---|
133 | }
|
---|
134 | if (*suffix != '\0') {
|
---|
135 | if (*suffix == 'f') {
|
---|
136 | fflag = TRUE;
|
---|
137 | ++suffix;
|
---|
138 | }
|
---|
139 | }
|
---|
140 | if (*suffix != '\0') { /* bad form: assume to be a file name */
|
---|
141 | number = -DEFAULT_COUNT;
|
---|
142 | cflag = nflag = FALSE;
|
---|
143 | fflag = FALSE;
|
---|
144 | } else {
|
---|
145 | --argc; ++argv;
|
---|
146 | }
|
---|
147 | } else { /* new standard syntax */
|
---|
148 | while ((opt = getopt(argc, argv, "c:fn:")) != EOF) {
|
---|
149 | switch (opt) {
|
---|
150 | case 'c':
|
---|
151 | cflag = TRUE;
|
---|
152 | if (*optarg == '+' || *optarg == '-')
|
---|
153 | number = atoi(optarg);
|
---|
154 | else
|
---|
155 | if (isdigit(*optarg))
|
---|
156 | number = -atoi(optarg);
|
---|
157 | else
|
---|
158 | usage();
|
---|
159 | if (number == 0) { /* HISTORICAL */
|
---|
160 | if (*optarg == '+')
|
---|
161 | number = 1;
|
---|
162 | else
|
---|
163 | exit(SUCCESS);
|
---|
164 | }
|
---|
165 | break;
|
---|
166 | case 'f':
|
---|
167 | fflag = TRUE;
|
---|
168 | break;
|
---|
169 | case 'n':
|
---|
170 | nflag = TRUE;
|
---|
171 | if (*optarg == '+' || *optarg == '-')
|
---|
172 | number = atoi(optarg);
|
---|
173 | else
|
---|
174 | if (isdigit(*optarg))
|
---|
175 | number = -atoi(optarg);
|
---|
176 | else
|
---|
177 | usage();
|
---|
178 | if (number == 0) { /* HISTORICAL */
|
---|
179 | if (*optarg == '+')
|
---|
180 | number = 1;
|
---|
181 | else
|
---|
182 | exit(SUCCESS);
|
---|
183 | }
|
---|
184 | break;
|
---|
185 | default:
|
---|
186 | usage();
|
---|
187 | /* NOTREACHED */
|
---|
188 | }
|
---|
189 | }
|
---|
190 | argc -= optind;
|
---|
191 | argv += optind;
|
---|
192 | }
|
---|
193 |
|
---|
194 | if (argc > 1 || /* too many arguments */
|
---|
195 | (cflag && nflag)) { /* both bytes and lines specified */
|
---|
196 | usage();
|
---|
197 | }
|
---|
198 |
|
---|
199 | if (argc > 0) { /* an actual file */
|
---|
200 | if (freopen(argv[0], "r", stdin) != stdin) {
|
---|
201 | fputs("tail: could not open ", stderr);
|
---|
202 | fputs(argv[0], stderr);
|
---|
203 | fputs("\n", stderr);
|
---|
204 | exit(FAILURE);
|
---|
205 | }
|
---|
206 | /* There is an optimization possibility here. If a file is being
|
---|
207 | * read, we need not look at the front of it. If we seek backwards
|
---|
208 | * from the end, we can (potentially) avoid looking at most of the
|
---|
209 | * file. Some systems fail when asked to seek backwards to a point
|
---|
210 | * before the start of the file, so we avoid that possibility.
|
---|
211 | */
|
---|
212 | if (number < 0 && fstat(fileno(stdin), &stat_buf) == 0) {
|
---|
213 | long offset = cflag ? (long)number : (long)number * LINE_MAX;
|
---|
214 |
|
---|
215 | if (-offset < stat_buf.st_size)
|
---|
216 | fseek(stdin, offset, SEEK_END);
|
---|
217 | }
|
---|
218 | } else {
|
---|
219 | fflag = FALSE; /* force -f off when reading a pipe */
|
---|
220 | }
|
---|
221 | exit(tail(number, cflag, fflag));
|
---|
222 | /* NOTREACHED */
|
---|
223 | }
|
---|
224 |
|
---|
225 | int tail(count, bytes, read_until_killed)
|
---|
226 | int count; /* lines or bytes desired */
|
---|
227 | int bytes; /* TRUE if we want bytes */
|
---|
228 | int read_until_killed; /* keep reading at EOF */
|
---|
229 | {
|
---|
230 | int c;
|
---|
231 | char *buf; /* pointer to input buffer */
|
---|
232 | char *buf_end; /* and one past its end */
|
---|
233 | char *start; /* pointer to first desired character in buf */
|
---|
234 | char *finish; /* pointer past last desired character */
|
---|
235 | int wrapped_once = FALSE; /* TRUE after buf has been filled once */
|
---|
236 |
|
---|
237 | /* This is magic. If count is positive, it means start at the count'th
|
---|
238 | * line or byte, with the first line or byte considered number 1. Thus,
|
---|
239 | * we want to SKIP one less line or byte than the number specified. In
|
---|
240 | * the negative case, we look backward from the end of the file for the
|
---|
241 | * (count + 1)'th newline or byte, so we really want the count to be one
|
---|
242 | * LARGER than was specified (in absolute value). In either case, the
|
---|
243 | * right thing to do is:
|
---|
244 | */
|
---|
245 | --count;
|
---|
246 |
|
---|
247 | /* Count is positive: skip the desired lines or bytes and then copy. */
|
---|
248 | if (count >= 0) {
|
---|
249 | while (count > 0 && (c = getchar()) != EOF) {
|
---|
250 | if (bytes || c == '\n')
|
---|
251 | --count;
|
---|
252 | }
|
---|
253 | while ((c = getchar()) != EOF) {
|
---|
254 | if (putchar(c) == EOF)
|
---|
255 | return FAILURE;
|
---|
256 | }
|
---|
257 | if (read_until_killed)
|
---|
258 | return keep_reading();
|
---|
259 | return ferror(stdin) ? FAILURE : SUCCESS;
|
---|
260 | }
|
---|
261 |
|
---|
262 | /* Count is negative: allocate a reasonably large buffer. */
|
---|
263 | if ((buf = (char *)malloc(MIN_BUFSIZE + 1)) == (char *)NULL) {
|
---|
264 | fputs("tail: out of memory\n", stderr);
|
---|
265 | return FAILURE;
|
---|
266 | }
|
---|
267 | buf_end = buf + (MIN_BUFSIZE + 1);
|
---|
268 |
|
---|
269 | /* Read the entire file into the buffer. */
|
---|
270 | finish = buf;
|
---|
271 | while ((c = getchar()) != EOF) {
|
---|
272 | *finish++ = c;
|
---|
273 | if (finish == buf_end) {
|
---|
274 | finish = buf;
|
---|
275 | wrapped_once = TRUE;
|
---|
276 | }
|
---|
277 | }
|
---|
278 | if (ferror(stdin))
|
---|
279 | return FAILURE;
|
---|
280 |
|
---|
281 | /* Back up inside the buffer. The count has already been adjusted to
|
---|
282 | * back up exactly one character too far, so we will bump the buffer
|
---|
283 | * pointer once after we're done.
|
---|
284 | *
|
---|
285 | * BUG: For large line counts, the buffer may not be large enough to
|
---|
286 | * hold all the lines. The specification allows the program to
|
---|
287 | * fail in such a case - this program will simply dump the entire
|
---|
288 | * buffer's contents as its best attempt at the desired behavior.
|
---|
289 | */
|
---|
290 | if (finish != buf || wrapped_once) { /* file was not empty */
|
---|
291 | start = (finish == buf) ? buf_end - 1 : finish - 1;
|
---|
292 | while (start != finish) {
|
---|
293 | if ((bytes || *start == '\n') && ++count == 0)
|
---|
294 | break;
|
---|
295 | if (start == buf) {
|
---|
296 | start = buf_end - 1;
|
---|
297 | if (!wrapped_once) /* never wrapped: stop now */
|
---|
298 | break;
|
---|
299 | } else {
|
---|
300 | --start;
|
---|
301 | }
|
---|
302 | }
|
---|
303 | if (++start == buf_end) { /* bump after going too far */
|
---|
304 | start = buf;
|
---|
305 | }
|
---|
306 | if (finish > start) {
|
---|
307 | fwrite(start, 1, finish - start, stdout);
|
---|
308 | } else {
|
---|
309 | fwrite(start, 1, buf_end - start, stdout);
|
---|
310 | fwrite(buf, 1, finish - buf, stdout);
|
---|
311 | }
|
---|
312 | }
|
---|
313 | if (read_until_killed)
|
---|
314 | return keep_reading();
|
---|
315 | return ferror(stdout) ? FAILURE : SUCCESS;
|
---|
316 | }
|
---|
317 |
|
---|
318 | /* Wake at intervals to reread standard input. Copy anything read to
|
---|
319 | * standard output and then go to sleep again.
|
---|
320 | */
|
---|
321 | int keep_reading()
|
---|
322 | {
|
---|
323 | char buf[1024];
|
---|
324 | int n;
|
---|
325 | int i;
|
---|
326 | off_t pos;
|
---|
327 | struct stat st;
|
---|
328 |
|
---|
329 | pos = lseek(0, (off_t) 0, SEEK_CUR);
|
---|
330 | for (;;) {
|
---|
331 | for (i = 0; i < 60; i++) {
|
---|
332 | while ((n = read(0, buf, sizeof(buf))) > 0) {
|
---|
333 | if (write(1, buf, n) < 0) return FAILURE;
|
---|
334 | }
|
---|
335 | if (n < 0) return FAILURE;
|
---|
336 |
|
---|
337 | sleep(SLEEP_INTERVAL);
|
---|
338 | }
|
---|
339 |
|
---|
340 | /* Rewind if suddenly truncated. */
|
---|
341 | if (pos != -1) {
|
---|
342 | if (fstat(0, &st) == -1) {
|
---|
343 | pos = -1;
|
---|
344 | } else
|
---|
345 | if (st.st_size < pos) {
|
---|
346 | pos = lseek(0, (off_t) 0, SEEK_SET);
|
---|
347 | } else {
|
---|
348 | pos = st.st_size;
|
---|
349 | }
|
---|
350 | }
|
---|
351 | }
|
---|
352 | }
|
---|
353 |
|
---|
354 | /* Tell the user the standard syntax. */
|
---|
355 | void usage()
|
---|
356 | {
|
---|
357 | fputs("Usage: tail [-f] [-c number | -n number] [file]\n", stderr);
|
---|
358 | exit(FAILURE);
|
---|
359 | }
|
---|