1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51#include "libbb.h"
52
53struct globals {
54 bool from_top;
55 bool exitcode;
56} FIX_ALIASING;
57#define G (*(struct globals*)&bb_common_bufsiz1)
58#define INIT_G() do { } while (0)
59
60static void tail_xprint_header(const char *fmt, const char *filename)
61{
62 if (fdprintf(STDOUT_FILENO, fmt, filename) < 0)
63 bb_perror_nomsg_and_die();
64}
65
66static ssize_t tail_read(int fd, char *buf, size_t count)
67{
68 ssize_t r;
69
70 r = full_read(fd, buf, count);
71 if (r < 0) {
72 bb_perror_msg(bb_msg_read_error);
73 G.exitcode = EXIT_FAILURE;
74 }
75
76 return r;
77}
78
79#define header_fmt_str "\n==> %s <==\n"
80
81static unsigned eat_num(const char *p)
82{
83 if (*p == '-')
84 p++;
85 else if (*p == '+') {
86 p++;
87 G.from_top = 1;
88 }
89 return xatou_sfx(p, bkm_suffixes);
90}
91
92int tail_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
93int tail_main(int argc, char **argv)
94{
95 unsigned count = 10;
96 unsigned sleep_period = 1;
97 const char *str_c, *str_n;
98
99 char *tailbuf;
100 size_t tailbufsize;
101 unsigned header_threshhold = 1;
102 unsigned nfiles;
103 int i, opt;
104
105 int *fds;
106 const char *fmt;
107 int prev_fd;
108
109 INIT_G();
110
111#if ENABLE_INCLUDE_SUSv2 || ENABLE_FEATURE_FANCY_TAIL
112
113 if (argv[1] && (argv[1][0] == '+' || argv[1][0] == '-')
114 && isdigit(argv[1][1])
115 ) {
116 count = eat_num(argv[1]);
117 argv++;
118 argc--;
119 }
120#endif
121
122
123 IF_FEATURE_FANCY_TAIL(opt_complementary = "s+:Ff";)
124 opt = getopt32(argv, "fc:n:" IF_FEATURE_FANCY_TAIL("qs:vF"),
125 &str_c, &str_n IF_FEATURE_FANCY_TAIL(,&sleep_period));
126#define FOLLOW (opt & 0x1)
127#define COUNT_BYTES (opt & 0x2)
128
129 if (opt & 0x2) count = eat_num(str_c);
130 if (opt & 0x4) count = eat_num(str_n);
131#if ENABLE_FEATURE_FANCY_TAIL
132
133 if (opt & 0x8) header_threshhold = UINT_MAX;
134
135 if (opt & 0x20) header_threshhold = 0;
136# define FOLLOW_RETRY (opt & 0x40)
137#else
138# define FOLLOW_RETRY 0
139#endif
140 argc -= optind;
141 argv += optind;
142
143
144 fds = xmalloc(sizeof(fds[0]) * (argc + 1));
145 if (!argv[0]) {
146 struct stat statbuf;
147
148 if (fstat(STDIN_FILENO, &statbuf) == 0
149 && S_ISFIFO(statbuf.st_mode)
150 ) {
151 opt &= ~1;
152 }
153 argv[0] = (char *) bb_msg_standard_input;
154 }
155 nfiles = i = 0;
156 do {
157 int fd = open_or_warn_stdin(argv[i]);
158 if (fd < 0 && !FOLLOW_RETRY) {
159 G.exitcode = EXIT_FAILURE;
160 continue;
161 }
162 fds[nfiles] = fd;
163 argv[nfiles++] = argv[i];
164 } while (++i < argc);
165
166 if (!nfiles)
167 bb_error_msg_and_die("no files");
168
169
170 tailbufsize = BUFSIZ;
171 if (!G.from_top && COUNT_BYTES) {
172 if (tailbufsize < count + BUFSIZ) {
173 tailbufsize = count + BUFSIZ;
174 }
175 }
176
177
178
179 tailbuf = NULL;
180
181
182
183 fmt = header_fmt_str + 1;
184 i = 0;
185 do {
186 char *buf;
187 int taillen;
188 int newlines_seen;
189 unsigned seen;
190 int nread;
191 int fd = fds[i];
192
193 if (ENABLE_FEATURE_FANCY_TAIL && fd < 0)
194 continue;
195
196 if (nfiles > header_threshhold) {
197 tail_xprint_header(fmt, argv[i]);
198 fmt = header_fmt_str;
199 }
200
201 if (!G.from_top) {
202 off_t current = lseek(fd, 0, SEEK_END);
203 if (current > 0) {
204 unsigned off;
205 if (COUNT_BYTES) {
206
207
208
209 if (count == 0)
210 continue;
211 current -= count;
212 if (current < 0)
213 current = 0;
214 xlseek(fd, current, SEEK_SET);
215 bb_copyfd_size(fd, STDOUT_FILENO, count);
216 continue;
217 }
218#if 1
219
220
221
222
223 off = (count | 0xf);
224 if (off > (INT_MAX / (64*1024)))
225 off = (INT_MAX / (64*1024));
226 current -= off * (64*1024);
227 if (current < 0)
228 current = 0;
229 xlseek(fd, current, SEEK_SET);
230#endif
231 }
232 }
233
234 if (!tailbuf)
235 tailbuf = xmalloc(tailbufsize);
236
237 buf = tailbuf;
238 taillen = 0;
239
240
241 seen = 1;
242 newlines_seen = 0;
243 while ((nread = tail_read(fd, buf, tailbufsize - taillen)) > 0) {
244 if (G.from_top) {
245 int nwrite = nread;
246 if (seen < count) {
247
248 if (COUNT_BYTES) {
249 nwrite -= (count - seen);
250 seen += nread;
251 } else {
252 char *s = buf;
253 do {
254 --nwrite;
255 if (*s++ == '\n' && ++seen == count) {
256 break;
257 }
258 } while (nwrite);
259 }
260 }
261 if (nwrite > 0)
262 xwrite(STDOUT_FILENO, buf + nread - nwrite, nwrite);
263 } else if (count) {
264 if (COUNT_BYTES) {
265 taillen += nread;
266 if (taillen > (int)count) {
267 memmove(tailbuf, tailbuf + taillen - count, count);
268 taillen = count;
269 }
270 } else {
271 int k = nread;
272 int newlines_in_buf = 0;
273
274 do {
275 k--;
276 if (buf[k] == '\n') {
277 newlines_in_buf++;
278 }
279 } while (k);
280
281 if (newlines_seen + newlines_in_buf < (int)count) {
282 newlines_seen += newlines_in_buf;
283 taillen += nread;
284 } else {
285 int extra = (buf[nread-1] != '\n');
286 char *s;
287
288 k = newlines_seen + newlines_in_buf + extra - count;
289 s = tailbuf;
290 while (k) {
291 if (*s == '\n') {
292 k--;
293 }
294 s++;
295 }
296 taillen += nread - (s - tailbuf);
297 memmove(tailbuf, s, taillen);
298 newlines_seen = count - extra;
299 }
300 if (tailbufsize < (size_t)taillen + BUFSIZ) {
301 tailbufsize = taillen + BUFSIZ;
302 tailbuf = xrealloc(tailbuf, tailbufsize);
303 }
304 }
305 buf = tailbuf + taillen;
306 }
307 }
308 if (!G.from_top) {
309 xwrite(STDOUT_FILENO, tailbuf, taillen);
310 }
311 } while (++i < nfiles);
312 prev_fd = fds[i-1];
313
314 tailbuf = xrealloc(tailbuf, BUFSIZ);
315
316 fmt = NULL;
317
318 if (FOLLOW) while (1) {
319 sleep(sleep_period);
320
321 i = 0;
322 do {
323 int nread;
324 const char *filename = argv[i];
325 int fd = fds[i];
326
327 if (FOLLOW_RETRY) {
328 struct stat sbuf, fsbuf;
329
330 if (fd < 0
331 || fstat(fd, &fsbuf) < 0
332 || stat(filename, &sbuf) < 0
333 || fsbuf.st_dev != sbuf.st_dev
334 || fsbuf.st_ino != sbuf.st_ino
335 ) {
336 int new_fd;
337
338 if (fd >= 0)
339 close(fd);
340 new_fd = open(filename, O_RDONLY);
341 if (new_fd >= 0) {
342 bb_error_msg("%s has %s; following end of new file",
343 filename, (fd < 0) ? "appeared" : "been replaced"
344 );
345 } else if (fd >= 0) {
346 bb_perror_msg("%s has become inaccessible", filename);
347 }
348 fds[i] = fd = new_fd;
349 }
350 }
351 if (ENABLE_FEATURE_FANCY_TAIL && fd < 0)
352 continue;
353 if (nfiles > header_threshhold) {
354 fmt = header_fmt_str;
355 }
356 for (;;) {
357
358 struct stat sbuf;
359
360 if (fstat(fd, &sbuf) == 0 && sbuf.st_size > 0) {
361 off_t current = lseek(fd, 0, SEEK_CUR);
362 if (sbuf.st_size < current)
363 xlseek(fd, 0, SEEK_SET);
364 }
365
366 nread = tail_read(fd, tailbuf, BUFSIZ);
367 if (nread <= 0)
368 break;
369 if (fmt && (fd != prev_fd)) {
370 tail_xprint_header(fmt, filename);
371 fmt = NULL;
372 prev_fd = fd;
373 }
374 xwrite(STDOUT_FILENO, tailbuf, nread);
375 }
376 } while (++i < nfiles);
377 }
378
379 if (ENABLE_FEATURE_CLEAN_UP) {
380 free(fds);
381 free(tailbuf);
382 }
383 return G.exitcode;
384}
385