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 dev_t dev;
40 ino_t ino;
41 } *F;
42)
43
44struct line_list {
45 struct line_list *next, *prev;
46 char *data;
47 int len;
48};
49
50static 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
66static 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
75
76
77static 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
84 if (pos<0) return 0;
85
86
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
94
95 bytes = pos;
96 while (lines && pos) {
97 int offset;
98
99
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
111 offset = list->len;
112 while (offset--) {
113
114 if (!flag) flag++;
115
116
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
128 llist_traverse(list, write_chunk);
129
130
131 lseek(fd, bytes, SEEK_SET);
132 return 1;
133}
134
135
136static 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
186static 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
217
218 if (bytes<0 || lines<0) {
219 struct line_list *list = 0, *new;
220
221
222
223 if (try_lseek(fd, bytes, lines)) return;
224
225
226 for (;;) {
227
228 if (!(new = read_chunk(fd, sizeof(toybuf)))) break;
229 dlist_add_nomalloc((void *)&list, (void *)new);
230
231
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
248
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
268 llist_traverse(list, write_chunk);
269
270
271 } else for (;;) {
272 int len, offset = 0;
273
274
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
286void tail_main(void)
287{
288 char **args = toys.optargs;
289
290 if (!FLAG(n) && !FLAG(c)) {
291 char *arg = *args;
292
293
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
309 if (TT.file_no && (FLAG(F) || FLAG(f))) tail_continue();
310}
311