1 /* tail.c - copy last lines from input to stdout.
2 *
3 * Copyright 2012 Timothy Elliott <tle@holymonkey.com>
4 *
5 * See http://opengroup.org/onlinepubs/9699919799/utilities/tail.html
6 *
7 * Deviations from posix: -f waits for pipe/fifo on stdin (nonblock?).
8
9 USE_TAIL(NEWTOY(tail, "?fFs:c(bytes)-n(lines)-[-cn][-fF]", TOYFLAG_USR|TOYFLAG_BIN))
10
11 config TAIL
12 bool "tail"
13 default y
14 help
15 usage: tail [-n|c NUMBER] [-f|F] [-s SECONDS] [FILE...]
16
17 Copy last lines from files to stdout. If no files listed, copy from
18 stdin. Filename "-" is a synonym for stdin.
19
20 -n Output the last NUMBER lines (default 10), +X counts from start
21 -c Output the last NUMBER bytes, +NUMBER counts from start
22 -f Follow FILE(s) by descriptor, waiting for more data to be appended
23 -F Follow FILE(s) by filename, waiting for more data, and retrying
24 -s Used with -F, sleep SECONDS between retries (default 1)
25 */
26
27 #define FOR_tail
28 #include "toys.h"
29
30 GLOBALS(
31 long n, c;
32 char *s;
33
34 int file_no, last_fd, ss;
35 struct xnotify *not;
36 struct {
37 char *path;
38 int fd;
39 dev_t dev;
40 ino_t ino;
41 } *F;
42 )
43
44 struct line_list {
45 struct line_list *next, *prev;
46 char *data;
47 int len;
48 };
49
read_chunk(int fd,int len)50 static struct line_list *read_chunk(int fd, int len)
51 {
52 struct line_list *line = xmalloc(sizeof(struct line_list)+len);
53
54 memset(line, 0, sizeof(struct line_list));
55 line->data = ((char *)line) + sizeof(struct line_list);
56 line->len = readall(fd, line->data, len);
57
58 if (line->len < 1) {
59 free(line);
60 return 0;
61 }
62
63 return line;
64 }
65
write_chunk(void * ptr)66 static void write_chunk(void *ptr)
67 {
68 struct line_list *list = ptr;
69
70 xwrite(1, list->data, list->len);
71 free(list);
72 }
73
74 // Reading through very large files is slow. Using lseek can speed things
75 // up a lot, but isn't applicable to all input (cat | tail).
76 // Note: bytes and lines are negative here.
try_lseek(int fd,long bytes,long lines)77 static int try_lseek(int fd, long bytes, long lines)
78 {
79 struct line_list *list = 0, *temp;
80 int flag = 0, chunk = sizeof(toybuf);
81 off_t pos = lseek(fd, 0, SEEK_END);
82
83 // If lseek() doesn't work on this stream, return now.
84 if (pos<0) return 0;
85
86 // Seek to the right spot, output data from there.
87 if (bytes) {
88 if (lseek(fd, bytes, SEEK_END)<0) lseek(fd, 0, SEEK_SET);
89 xsendfile(fd, 1);
90 return 1;
91 }
92
93 // Read from end to find enough lines, then output them.
94
95 bytes = pos;
96 while (lines && pos) {
97 int offset;
98
99 // Read in next chunk from end of file
100 if (chunk>pos) chunk = pos;
101 pos -= chunk;
102 if (pos != lseek(fd, pos, SEEK_SET)) {
103 perror_msg("seek failed");
104 break;
105 }
106 if (!(temp = read_chunk(fd, chunk))) break;
107 temp->next = list;
108 list = temp;
109
110 // Count newlines in this chunk.
111 offset = list->len;
112 while (offset--) {
113 // If the last line ends with a newline, that one doesn't count.
114 if (!flag) flag++;
115
116 // Start outputting data right after newline
117 else if (list->data[offset] == '\n' && !++lines) {
118 offset++;
119 list->data += offset;
120 list->len -= offset;
121
122 break;
123 }
124 }
125 }
126
127 // Output stored data
128 llist_traverse(list, write_chunk);
129
130 // In case of -f
131 lseek(fd, bytes, SEEK_SET);
132 return 1;
133 }
134
135 // For -f and -F
tail_continue()136 static void tail_continue()
137 {
138 long long pos;
139 char *path;
140 struct stat sb;
141 int i = 0, fd, len;
142
143 for (i = 0; ; i++) {
144 if (FLAG(f)) fd = xnotify_wait(TT.not, &path);
145 else {
146 if (i == TT.file_no) {
147 i = 0;
148 msleep(TT.ss);
149 }
150 fd = TT.F[i].fd;
151 path = TT.F[i].path;
152
153 if (stat(TT.F[i].path, &sb)) {
154 if (fd >= 0) {
155 close(fd);
156 TT.F[i].fd = -1;
157 error_msg("file inaccessible: %s\n", TT.F[i].path);
158 }
159 continue;
160 }
161
162 if (fd<0 || sb.st_dev!=TT.F[i].dev || sb.st_ino!=TT.F[i].ino) {
163 if (fd>=0) close(fd);
164 if (-1 == (TT.F[i].fd = fd = open(path, O_RDONLY))) continue;
165 error_msg("following new file: %s\n", path);
166 TT.F[i].dev = sb.st_dev;
167 TT.F[i].ino = sb.st_ino;
168 } else if (sb.st_size <= (pos = lseek(fd, 0, SEEK_CUR))) {
169 if (pos == sb.st_size) continue;
170 error_msg("file truncated: %s\n", path);
171 lseek(fd, 0, SEEK_SET);
172 }
173 }
174
175 while ((len = read(fd, toybuf, sizeof(toybuf)))>0) {
176 if (TT.file_no>1 && TT.last_fd != fd) {
177 TT.last_fd = fd;
178 xprintf("\n==> %s <==\n", path);
179 }
180 xwrite(1, toybuf, len);
181 }
182 }
183 }
184
185 // Called for each file listed on command line, and/or stdin
do_tail(int fd,char * name)186 static void do_tail(int fd, char *name)
187 {
188 long bytes = TT.c, lines = TT.n;
189 int linepop = 1;
190
191 if (FLAG(F)) {
192 if (!fd) perror_exit("no -F with '-'");
193 } else if (fd == -1) return;
194 if (FLAG(f) || FLAG(F)) {
195 char *s = name;
196 struct stat sb;
197
198 if (!fd) sprintf(s = toybuf, "/proc/self/fd/%d", fd);
199
200 if (FLAG(f)) xnotify_add(TT.not, fd, s);
201 if (FLAG(F)) {
202 if (fd != -1) {
203 if (fstat(fd, &sb)) perror_exit("%s", name);
204 TT.F[TT.file_no].dev = sb.st_dev;
205 TT.F[TT.file_no].ino = sb.st_ino;
206 }
207 TT.F[TT.file_no].fd = fd;
208 TT.F[TT.file_no].path = s;
209 }
210 }
211
212 if (TT.file_no++) xputc('\n');
213 TT.last_fd = fd;
214 if (toys.optc > 1) xprintf("==> %s <==\n", name);
215
216 // Are we measuring from the end of the file?
217
218 if (bytes<0 || lines<0) {
219 struct line_list *list = 0, *new;
220
221 // The slow codepath is always needed, and can handle all input,
222 // so make lseek support optional.
223 if (try_lseek(fd, bytes, lines)) return;
224
225 // Read data until we run out, keep a trailing buffer
226 for (;;) {
227 // Read next page of data, appending to linked list in order
228 if (!(new = read_chunk(fd, sizeof(toybuf)))) break;
229 dlist_add_nomalloc((void *)&list, (void *)new);
230
231 // If tracing bytes, add until we have enough, discarding overflow.
232 if (TT.c) {
233 bytes += new->len;
234 if (bytes > 0) {
235 while (list->len <= bytes) {
236 bytes -= list->len;
237 free(dlist_pop(&list));
238 }
239 list->data += bytes;
240 list->len -= bytes;
241 bytes = 0;
242 }
243 } else {
244 int len = new->len, count;
245 char *try = new->data;
246
247 // First character _after_ a newline starts a new line, which
248 // works even if file doesn't end with a newline
249 for (count=0; count<len; count++) {
250 if (linepop) lines++;
251 linepop = try[count] == '\n';
252
253 if (lines > 0) {
254 char c;
255
256 do {
257 c = *list->data;
258 if (!--(list->len)) free(dlist_pop(&list));
259 else list->data++;
260 } while (c != '\n');
261 lines--;
262 }
263 }
264 }
265 }
266
267 // Output/free the buffer.
268 llist_traverse(list, write_chunk);
269
270 // Measuring from the beginning of the file.
271 } else for (;;) {
272 int len, offset = 0;
273
274 // Error while reading does not exit. Error writing does.
275 len = read(fd, toybuf, sizeof(toybuf));
276 if (len<1) break;
277 while (bytes > 1 || lines > 1) {
278 bytes--;
279 if (toybuf[offset++] == '\n') lines--;
280 if (offset >= len) break;
281 }
282 if (offset<len) xwrite(1, toybuf+offset, len-offset);
283 }
284 }
285
tail_main(void)286 void tail_main(void)
287 {
288 char **args = toys.optargs;
289
290 if (!FLAG(n) && !FLAG(c)) {
291 char *arg = *args;
292
293 // handle old "-42" style arguments, else default to last 10 lines
294 if (arg && *arg == '-' && arg[1]) {
295 TT.n = atolx(*(args++));
296 toys.optc--;
297 } else TT.n = -10;
298 }
299
300 if (FLAG(F)) TT.F = xzalloc(toys.optc*sizeof(*TT.F));
301 else if (FLAG(f)) TT.not = xnotify_init(toys.optc);
302 TT.ss = TT.s ? xparsemillitime(TT.s) : 1000;
303
304 loopfiles_rw(args,
305 O_RDONLY|WARN_ONLY|LOOPFILES_ANYWAY|(O_CLOEXEC*!(FLAG(f) || FLAG(F))),
306 0, do_tail);
307
308 // Wait for more data when following files
309 if (TT.file_no && (FLAG(F) || FLAG(f))) tail_continue();
310 }
311