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#define FOR_tail
28#include "toys.h"
29
30GLOBALS(
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 struct dev_ino di;
40 } *F;
41)
42
43struct line_list {
44 struct line_list *next, *prev;
45 char *data;
46 int len;
47};
48
49static struct line_list *read_chunk(int fd, int len)
50{
51 struct line_list *line = xmalloc(sizeof(struct line_list)+len);
52
53 memset(line, 0, sizeof(struct line_list));
54 line->data = ((char *)line) + sizeof(struct line_list);
55 line->len = readall(fd, line->data, len);
56
57 if (line->len < 1) {
58 free(line);
59 return 0;
60 }
61
62 return line;
63}
64
65static void write_chunk(void *ptr)
66{
67 struct line_list *list = ptr;
68
69 xwrite(1, list->data, list->len);
70 free(list);
71}
72
73
74
75
76static int try_lseek(int fd, long bytes, long lines)
77{
78 struct line_list *list = 0, *temp;
79 int flag = 0, chunk = sizeof(toybuf);
80 off_t pos = lseek(fd, 0, SEEK_END);
81
82
83 if (pos<0) return 0;
84
85
86 if (bytes) {
87 if (lseek(fd, bytes, SEEK_END)<0) lseek(fd, 0, SEEK_SET);
88 xsendfile(fd, 1);
89 return 1;
90 }
91
92
93
94 bytes = pos;
95 while (lines && pos) {
96 int offset;
97
98
99 if (chunk>pos) chunk = pos;
100 pos -= chunk;
101 if (pos != lseek(fd, pos, SEEK_SET)) {
102 perror_msg("seek failed");
103 break;
104 }
105 if (!(temp = read_chunk(fd, chunk))) break;
106 temp->next = list;
107 list = temp;
108
109
110 offset = list->len;
111 while (offset--) {
112
113 if (!flag) flag++;
114
115
116 else if (list->data[offset] == '\n' && !++lines) {
117 offset++;
118 list->data += offset;
119 list->len -= offset;
120
121 break;
122 }
123 }
124 }
125
126
127 llist_traverse(list, write_chunk);
128
129
130 lseek(fd, bytes, SEEK_SET);
131 return 1;
132}
133
134
135static void tail_continue()
136{
137 long long pos;
138 char *path;
139 struct stat sb;
140 int i = 0, fd, len;
141
142 for (i = 0; ; i++) {
143 if (FLAG(f)) fd = xnotify_wait(TT.not, &path);
144 else {
145 if (i == TT.file_no) {
146 i = 0;
147 msleep(TT.ss);
148 }
149 fd = TT.F[i].fd;
150 path = TT.F[i].path;
151
152 if (stat(TT.F[i].path, &sb)) {
153 if (fd >= 0) {
154 close(fd);
155 TT.F[i].fd = -1;
156 error_msg("file inaccessible: %s\n", TT.F[i].path);
157 }
158 continue;
159 }
160
161 if (fd<0 || !same_dev_ino(&sb, &TT.F[i].di)) {
162 if (fd>=0) close(fd);
163 if (-1 == (TT.F[i].fd = fd = open(path, O_RDONLY))) continue;
164 error_msg("following new file: %s\n", path);
165 TT.F[i].di.dev = sb.st_dev;
166 TT.F[i].di.ino = sb.st_ino;
167 } else if (sb.st_size <= (pos = lseek(fd, 0, SEEK_CUR))) {
168 if (pos == sb.st_size) continue;
169 error_msg("file truncated: %s\n", path);
170 lseek(fd, 0, SEEK_SET);
171 }
172 }
173
174 while ((len = read(fd, toybuf, sizeof(toybuf)))>0) {
175 if (TT.file_no>1 && TT.last_fd != fd) {
176 TT.last_fd = fd;
177 xprintf("\n==> %s <==\n", path);
178 }
179 xwrite(1, toybuf, len);
180 }
181 }
182}
183
184
185static void do_tail(int fd, char *name)
186{
187 long bytes = TT.c, lines = TT.n;
188 int linepop = 1;
189
190 if (FLAG(F)) {
191 if (!fd) perror_exit("no -F with '-'");
192 } else if (fd == -1) return;
193 if (FLAG(f) || FLAG(F)) {
194 char *s = name;
195 struct stat sb;
196
197 if (!fd) sprintf(s = toybuf, "/proc/self/fd/%d", fd);
198
199 if (FLAG(f)) xnotify_add(TT.not, fd, s);
200 if (FLAG(F)) {
201 if (fd != -1) {
202 if (fstat(fd, &sb)) perror_exit("%s", name);
203 TT.F[TT.file_no].di.dev = sb.st_dev;
204 TT.F[TT.file_no].di.ino = sb.st_ino;
205 }
206 TT.F[TT.file_no].fd = fd;
207 TT.F[TT.file_no].path = s;
208 }
209 }
210
211 if (TT.file_no++) xputc('\n');
212 TT.last_fd = fd;
213 if (toys.optc > 1) xprintf("==> %s <==\n", name);
214
215
216
217 if (bytes<0 || lines<0) {
218 struct line_list *list = 0, *new;
219
220
221
222 if (try_lseek(fd, bytes, lines)) return;
223
224
225 for (;;) {
226
227 if (!(new = read_chunk(fd, sizeof(toybuf)))) break;
228 dlist_add_nomalloc((void *)&list, (void *)new);
229
230
231 if (TT.c) {
232 bytes += new->len;
233 if (bytes > 0) {
234 while (list->len <= bytes) {
235 bytes -= list->len;
236 free(dlist_pop(&list));
237 }
238 list->data += bytes;
239 list->len -= bytes;
240 bytes = 0;
241 }
242 } else {
243 int len = new->len, count;
244 char *try = new->data;
245
246
247
248 for (count=0; count<len; count++) {
249 if (linepop) lines++;
250 linepop = try[count] == '\n';
251
252 if (lines > 0) {
253 char c;
254
255 do {
256 c = *list->data;
257 if (!--(list->len)) free(dlist_pop(&list));
258 else list->data++;
259 } while (c != '\n');
260 lines--;
261 }
262 }
263 }
264 }
265
266
267 llist_traverse(list, write_chunk);
268
269
270 } else for (;;) {
271 int len, offset = 0;
272
273
274 len = read(fd, toybuf, sizeof(toybuf));
275 if (len<1) break;
276 while (bytes > 1 || lines > 1) {
277 bytes--;
278 if (toybuf[offset++] == '\n') lines--;
279 if (offset >= len) break;
280 }
281 if (offset<len) xwrite(1, toybuf+offset, len-offset);
282 }
283}
284
285void tail_main(void)
286{
287 char **args = toys.optargs;
288
289 if (!FLAG(n) && !FLAG(c)) {
290 char *arg = *args;
291
292
293 if (arg && *arg == '-' && arg[1]) {
294 TT.n = atolx(*(args++));
295 toys.optc--;
296 } else TT.n = -10;
297 }
298
299 if (FLAG(F)) TT.F = xzalloc(toys.optc*sizeof(*TT.F));
300 else if (FLAG(f)) TT.not = xnotify_init(toys.optc);
301 TT.ss = TT.s ? xparsemillitime(TT.s) : 1000;
302
303 loopfiles_rw(args,
304 O_RDONLY|WARN_ONLY|LOOPFILES_ANYWAY|(O_CLOEXEC*!(FLAG(f) || FLAG(F))),
305 0, do_tail);
306
307
308 if (TT.file_no && (FLAG(F) || FLAG(f))) tail_continue();
309}
310