30 |
{ "b", 512 }, |
{ "b", 512 }, |
31 |
{ "k", 1024 }, |
{ "k", 1024 }, |
32 |
{ "m", 1024*1024 }, |
{ "m", 1024*1024 }, |
33 |
{ } |
{ "", 0 } |
34 |
}; |
}; |
35 |
|
|
36 |
struct globals { |
struct globals { |
50 |
off_t current; |
off_t current; |
51 |
struct stat sbuf; |
struct stat sbuf; |
52 |
|
|
|
/* (A good comment is missing here) */ |
|
|
current = lseek(fd, 0, SEEK_CUR); |
|
53 |
/* /proc files report zero st_size, don't lseek them. */ |
/* /proc files report zero st_size, don't lseek them. */ |
54 |
if (fstat(fd, &sbuf) == 0 && sbuf.st_size) |
if (fstat(fd, &sbuf) == 0 && sbuf.st_size > 0) { |
55 |
|
current = lseek(fd, 0, SEEK_CUR); |
56 |
if (sbuf.st_size < current) |
if (sbuf.st_size < current) |
57 |
lseek(fd, 0, SEEK_SET); |
xlseek(fd, 0, SEEK_SET); |
58 |
|
} |
59 |
|
|
60 |
r = full_read(fd, buf, count); |
r = full_read(fd, buf, count); |
61 |
if (r < 0) { |
if (r < 0) { |
85 |
unsigned count = 10; |
unsigned count = 10; |
86 |
unsigned sleep_period = 1; |
unsigned sleep_period = 1; |
87 |
bool from_top; |
bool from_top; |
|
int header_threshhold = 1; |
|
88 |
const char *str_c, *str_n; |
const char *str_c, *str_n; |
89 |
|
|
90 |
char *tailbuf; |
char *tailbuf; |
91 |
size_t tailbufsize; |
size_t tailbufsize; |
92 |
int taillen = 0; |
unsigned header_threshhold = 1; |
93 |
int newlines_seen = 0; |
unsigned nfiles; |
94 |
int nfiles, nread, nwrite, i, opt; |
int i, opt; |
|
unsigned seen; |
|
95 |
|
|
96 |
int *fds; |
int *fds; |
|
char *s, *buf; |
|
97 |
const char *fmt; |
const char *fmt; |
98 |
|
|
99 |
#if ENABLE_INCLUDE_SUSv2 || ENABLE_FEATURE_FANCY_TAIL |
#if ENABLE_INCLUDE_SUSv2 || ENABLE_FEATURE_FANCY_TAIL |
107 |
} |
} |
108 |
#endif |
#endif |
109 |
|
|
110 |
USE_FEATURE_FANCY_TAIL(opt_complementary = "s+";) /* -s N */ |
/* -s NUM, -F imlies -f */ |
111 |
opt = getopt32(argv, "fc:n:" USE_FEATURE_FANCY_TAIL("qs:v"), |
IF_FEATURE_FANCY_TAIL(opt_complementary = "s+:Ff";) |
112 |
&str_c, &str_n USE_FEATURE_FANCY_TAIL(,&sleep_period)); |
opt = getopt32(argv, "fc:n:" IF_FEATURE_FANCY_TAIL("qs:vF"), |
113 |
|
&str_c, &str_n IF_FEATURE_FANCY_TAIL(,&sleep_period)); |
114 |
#define FOLLOW (opt & 0x1) |
#define FOLLOW (opt & 0x1) |
115 |
#define COUNT_BYTES (opt & 0x2) |
#define COUNT_BYTES (opt & 0x2) |
116 |
//if (opt & 0x1) // -f |
//if (opt & 0x1) // -f |
117 |
if (opt & 0x2) count = eat_num(str_c); // -c |
if (opt & 0x2) count = eat_num(str_c); // -c |
118 |
if (opt & 0x4) count = eat_num(str_n); // -n |
if (opt & 0x4) count = eat_num(str_n); // -n |
119 |
#if ENABLE_FEATURE_FANCY_TAIL |
#if ENABLE_FEATURE_FANCY_TAIL |
120 |
if (opt & 0x8) header_threshhold = INT_MAX; // -q |
/* q: make it impossible for nfiles to be > header_threshhold */ |
121 |
|
if (opt & 0x8) header_threshhold = UINT_MAX; // -q |
122 |
|
//if (opt & 0x10) // -s |
123 |
if (opt & 0x20) header_threshhold = 0; // -v |
if (opt & 0x20) header_threshhold = 0; // -v |
124 |
|
# define FOLLOW_RETRY (opt & 0x40) |
125 |
|
#else |
126 |
|
# define FOLLOW_RETRY 0 |
127 |
#endif |
#endif |
128 |
argc -= optind; |
argc -= optind; |
129 |
argv += optind; |
argv += optind; |
131 |
G.status = EXIT_SUCCESS; |
G.status = EXIT_SUCCESS; |
132 |
|
|
133 |
/* open all the files */ |
/* open all the files */ |
134 |
fds = xmalloc(sizeof(int) * (argc + 1)); |
fds = xmalloc(sizeof(fds[0]) * (argc + 1)); |
135 |
if (!argv[0]) { |
if (!argv[0]) { |
136 |
struct stat statbuf; |
struct stat statbuf; |
137 |
|
|
138 |
if (!fstat(STDIN_FILENO, &statbuf) && S_ISFIFO(statbuf.st_mode)) { |
if (fstat(STDIN_FILENO, &statbuf) == 0 |
139 |
|
&& S_ISFIFO(statbuf.st_mode) |
140 |
|
) { |
141 |
opt &= ~1; /* clear FOLLOW */ |
opt &= ~1; /* clear FOLLOW */ |
142 |
} |
} |
143 |
*argv = (char *) bb_msg_standard_input; |
argv[0] = (char *) bb_msg_standard_input; |
144 |
} |
} |
145 |
nfiles = i = 0; |
nfiles = i = 0; |
146 |
do { |
do { |
147 |
int fd = open_or_warn_stdin(argv[i]); |
int fd = open_or_warn_stdin(argv[i]); |
148 |
if (fd < 0) { |
if (fd < 0 && !FOLLOW_RETRY) { |
149 |
G.status = EXIT_FAILURE; |
G.status = EXIT_FAILURE; |
150 |
continue; |
continue; |
151 |
} |
} |
166 |
tailbuf = xmalloc(tailbufsize); |
tailbuf = xmalloc(tailbufsize); |
167 |
|
|
168 |
/* tail the files */ |
/* tail the files */ |
169 |
fmt = header_fmt + 1; /* Skip header leading newline on first output. */ |
fmt = header_fmt + 1; /* skip header leading newline on first output */ |
170 |
i = 0; |
i = 0; |
171 |
do { |
do { |
172 |
|
char *buf; |
173 |
|
int taillen; |
174 |
|
int newlines_seen; |
175 |
|
unsigned seen; |
176 |
|
int nread; |
177 |
|
int fd = fds[i]; |
178 |
|
|
179 |
|
if (ENABLE_FEATURE_FANCY_TAIL && fd < 0) |
180 |
|
continue; /* may happen with -E */ |
181 |
|
|
182 |
if (nfiles > header_threshhold) { |
if (nfiles > header_threshhold) { |
183 |
tail_xprint_header(fmt, argv[i]); |
tail_xprint_header(fmt, argv[i]); |
184 |
fmt = header_fmt; |
fmt = header_fmt; |
185 |
} |
} |
186 |
|
|
187 |
/* Optimizing count-bytes case if the file is seekable. |
if (!from_top) { |
188 |
* Beware of backing up too far. |
off_t current = lseek(fd, 0, SEEK_END); |
|
* Also we exclude files with size 0 (because of /proc/xxx) */ |
|
|
if (COUNT_BYTES && !from_top) { |
|
|
off_t current = lseek(fds[i], 0, SEEK_END); |
|
189 |
if (current > 0) { |
if (current > 0) { |
190 |
if (count == 0) |
unsigned off; |
191 |
continue; /* showing zero lines is easy :) */ |
if (COUNT_BYTES) { |
192 |
current -= count; |
/* Optimizing count-bytes case if the file is seekable. |
193 |
|
* Beware of backing up too far. |
194 |
|
* Also we exclude files with size 0 (because of /proc/xxx) */ |
195 |
|
if (count == 0) |
196 |
|
continue; /* showing zero bytes is easy :) */ |
197 |
|
current -= count; |
198 |
|
if (current < 0) |
199 |
|
current = 0; |
200 |
|
xlseek(fd, current, SEEK_SET); |
201 |
|
bb_copyfd_size(fd, STDOUT_FILENO, count); |
202 |
|
continue; |
203 |
|
} |
204 |
|
#if 1 /* This is technically incorrect for *LONG* strings, but very useful */ |
205 |
|
/* Optimizing count-lines case if the file is seekable. |
206 |
|
* We assume the lines are <64k. |
207 |
|
* (Users complain that tail takes too long |
208 |
|
* on multi-gigabyte files) */ |
209 |
|
off = (count | 0xf); /* for small counts, be more paranoid */ |
210 |
|
if (off > (INT_MAX / (64*1024))) |
211 |
|
off = (INT_MAX / (64*1024)); |
212 |
|
current -= off * (64*1024); |
213 |
if (current < 0) |
if (current < 0) |
214 |
current = 0; |
current = 0; |
215 |
xlseek(fds[i], current, SEEK_SET); |
xlseek(fd, current, SEEK_SET); |
216 |
bb_copyfd_size(fds[i], STDOUT_FILENO, count); |
#endif |
|
continue; |
|
217 |
} |
} |
218 |
} |
} |
219 |
|
|
220 |
buf = tailbuf; |
buf = tailbuf; |
221 |
taillen = 0; |
taillen = 0; |
222 |
|
/* "We saw 1st line/byte". |
223 |
|
* Used only by +N code ("start from Nth", 1-based): */ |
224 |
seen = 1; |
seen = 1; |
225 |
newlines_seen = 0; |
newlines_seen = 0; |
226 |
while ((nread = tail_read(fds[i], buf, tailbufsize-taillen)) > 0) { |
while ((nread = tail_read(fd, buf, tailbufsize-taillen)) > 0) { |
227 |
if (from_top) { |
if (from_top) { |
228 |
nwrite = nread; |
int nwrite = nread; |
229 |
if (seen < count) { |
if (seen < count) { |
230 |
|
/* We need to skip a few more bytes/lines */ |
231 |
if (COUNT_BYTES) { |
if (COUNT_BYTES) { |
232 |
nwrite -= (count - seen); |
nwrite -= (count - seen); |
233 |
seen = count; |
seen = count; |
234 |
} else { |
} else { |
235 |
s = buf; |
char *s = buf; |
236 |
do { |
do { |
237 |
--nwrite; |
--nwrite; |
238 |
if (*s++ == '\n' && ++seen == count) { |
if (*s++ == '\n' && ++seen == count) { |
241 |
} while (nwrite); |
} while (nwrite); |
242 |
} |
} |
243 |
} |
} |
244 |
xwrite(STDOUT_FILENO, buf + nread - nwrite, nwrite); |
if (nwrite > 0) |
245 |
|
xwrite(STDOUT_FILENO, buf + nread - nwrite, nwrite); |
246 |
} else if (count) { |
} else if (count) { |
247 |
if (COUNT_BYTES) { |
if (COUNT_BYTES) { |
248 |
taillen += nread; |
taillen += nread; |
266 |
taillen += nread; |
taillen += nread; |
267 |
} else { |
} else { |
268 |
int extra = (buf[nread-1] != '\n'); |
int extra = (buf[nread-1] != '\n'); |
269 |
|
char *s; |
270 |
|
|
271 |
k = newlines_seen + newlines_in_buf + extra - count; |
k = newlines_seen + newlines_in_buf + extra - count; |
272 |
s = tailbuf; |
s = tailbuf; |
293 |
} |
} |
294 |
} while (++i < nfiles); |
} while (++i < nfiles); |
295 |
|
|
296 |
buf = xrealloc(tailbuf, BUFSIZ); |
tailbuf = xrealloc(tailbuf, BUFSIZ); |
297 |
|
|
298 |
fmt = NULL; |
fmt = NULL; |
299 |
|
|
300 |
if (FOLLOW) while (1) { |
if (FOLLOW) while (1) { |
301 |
sleep(sleep_period); |
sleep(sleep_period); |
302 |
|
|
303 |
i = 0; |
i = 0; |
304 |
do { |
do { |
305 |
|
int nread; |
306 |
|
const char *filename = argv[i]; |
307 |
|
int fd = fds[i]; |
308 |
|
|
309 |
|
if (FOLLOW_RETRY) { |
310 |
|
struct stat sbuf, fsbuf; |
311 |
|
|
312 |
|
if (fd < 0 |
313 |
|
|| fstat(fd, &fsbuf) < 0 |
314 |
|
|| stat(filename, &sbuf) < 0 |
315 |
|
|| fsbuf.st_dev != sbuf.st_dev |
316 |
|
|| fsbuf.st_ino != sbuf.st_ino |
317 |
|
) { |
318 |
|
int new_fd; |
319 |
|
|
320 |
|
if (fd >= 0) |
321 |
|
close(fd); |
322 |
|
new_fd = open(filename, O_RDONLY); |
323 |
|
if (new_fd >= 0) { |
324 |
|
bb_error_msg("%s has %s; following end of new file", |
325 |
|
filename, (fd < 0) ? "appeared" : "been replaced" |
326 |
|
); |
327 |
|
} else if (fd >= 0) { |
328 |
|
bb_perror_msg("%s has become inaccessible", filename); |
329 |
|
} |
330 |
|
fds[i] = fd = new_fd; |
331 |
|
} |
332 |
|
} |
333 |
|
if (ENABLE_FEATURE_FANCY_TAIL && fd < 0) |
334 |
|
continue; |
335 |
if (nfiles > header_threshhold) { |
if (nfiles > header_threshhold) { |
336 |
fmt = header_fmt; |
fmt = header_fmt; |
337 |
} |
} |
338 |
while ((nread = tail_read(fds[i], buf, BUFSIZ)) > 0) { |
while ((nread = tail_read(fd, tailbuf, BUFSIZ)) > 0) { |
339 |
if (fmt) { |
if (fmt) { |
340 |
tail_xprint_header(fmt, argv[i]); |
tail_xprint_header(fmt, filename); |
341 |
fmt = NULL; |
fmt = NULL; |
342 |
} |
} |
343 |
xwrite(STDOUT_FILENO, buf, nread); |
xwrite(STDOUT_FILENO, tailbuf, nread); |
344 |
} |
} |
345 |
} while (++i < nfiles); |
} while (++i < nfiles); |
346 |
} |
} |