Mercurial > hg > toybox
comparison toys/tail.c @ 540:c2f39708a4c4
Redo tail to use optargs and optionally support lseek. Add support to optargs and llist.c, plus add a test suite entry. Still no -f support though.
author | Rob Landley <rob@landley.net> |
---|---|
date | Mon, 12 Mar 2012 00:25:40 -0500 |
parents | a497beb97eee |
children | 47edfc1a4983 |
comparison
equal
deleted
inserted
replaced
539:09135436042b | 540:c2f39708a4c4 |
---|---|
4 * | 4 * |
5 * Copyright 2012 Timothy Elliott <tle@holymonkey.com> | 5 * Copyright 2012 Timothy Elliott <tle@holymonkey.com> |
6 * | 6 * |
7 * See http://www.opengroup.org/onlinepubs/009695399/utilities/tail.html | 7 * See http://www.opengroup.org/onlinepubs/009695399/utilities/tail.html |
8 | 8 |
9 USE_TAIL(NEWTOY(tail, "c-|fn-|", TOYFLAG_BIN)) | 9 USE_TAIL(NEWTOY(tail, "fc-n-", TOYFLAG_BIN)) |
10 | 10 |
11 config TAIL | 11 config TAIL |
12 bool "tail" | 12 bool "tail" |
13 default n | 13 default y |
14 help | 14 help |
15 usage: tail [-n|c number] [-f] [file...] | 15 usage: tail [-n|c number] [-f] [file...] |
16 | 16 |
17 Copy last lines from files to stdout. If no files listed, copy from | 17 Copy last lines from files to stdout. If no files listed, copy from |
18 stdin. Filename "-" is a synonym for stdin. | 18 stdin. Filename "-" is a synonym for stdin. |
19 | 19 |
20 -n output the last X lines (default 10), +X counts from start. | 20 -n output the last X lines (default 10), +X counts from start. |
21 -c output the last X bytes, +X counts from start | 21 -c output the last X bytes, +X counts from start |
22 -f follow file, waiting for more data to be appended | 22 -f follow file, waiting for more data to be appended |
23 | |
24 config TAIL_SEEK | |
25 bool "tail seek support" | |
26 default y | |
27 depends on TAIL | |
28 help | |
29 This version uses lseek, which is faster on large files. | |
23 */ | 30 */ |
24 | 31 |
25 #include "toys.h" | 32 #include "toys.h" |
26 | 33 |
27 DEFINE_GLOBALS( | 34 DEFINE_GLOBALS( |
32 ) | 39 ) |
33 | 40 |
34 #define TT this.tail | 41 #define TT this.tail |
35 | 42 |
36 #define FLAG_n 1 | 43 #define FLAG_n 1 |
37 #define FLAG_f 2 | 44 #define FLAG_c 2 |
38 #define FLAG_c 4 | 45 #define FLAG_f 4 |
39 | 46 |
40 struct line_list { | 47 struct line_list { |
41 struct line_list *next; | 48 struct line_list *next, *prev; |
42 char *data; | 49 char *data; |
43 ssize_t len; | 50 int len; |
44 long lines; | |
45 }; | 51 }; |
46 | 52 |
47 static void print_after_offset(int fd, long bytes, long lines) | 53 static struct line_list *get_chunk(int fd, int len) |
48 { | 54 { |
49 ssize_t read_len; | 55 struct line_list *line = xmalloc(sizeof(struct line_list)+len); |
50 long size=sizeof(toybuf); | 56 |
51 char c; | 57 line->data = ((char *)line) + sizeof(struct line_list); |
52 | 58 line->len = readall(fd, line->data, len); |
53 while (bytes > 0 || lines > 0) { | 59 |
54 if (1>read(fd, &c, 1)) break; | 60 if (line->len < 1) { |
55 bytes--; | 61 free(line); |
56 if (c == '\n') lines--; | 62 return 0; |
57 } | 63 } |
58 | 64 |
59 for (;;) { | 65 return line; |
60 read_len = xread(fd, toybuf, size); | 66 } |
61 if (read_len<1) break; | 67 |
62 xwrite(1, toybuf, read_len); | 68 static void dump_chunk(void *ptr) |
63 } | 69 { |
64 } | 70 struct line_list *list = ptr; |
65 | 71 xwrite(1, list->data, list->len); |
66 static void print_last_bytes(int fd, long bytes) | |
67 { | |
68 char *buf1, *buf2, *temp; | |
69 ssize_t read_len; | |
70 | |
71 buf1 = xmalloc(bytes); | |
72 buf2 = xmalloc(bytes); | |
73 | |
74 for(;;) { | |
75 // swap buf1 and buf2 | |
76 temp = buf1; | |
77 buf1 = buf2; | |
78 buf2 = temp; | |
79 | |
80 read_len = readall(fd, buf2, bytes); | |
81 if (read_len<bytes) break; | |
82 } | |
83 | |
84 // output part of buf1 and all of buf2 | |
85 xwrite(1, buf1 + read_len, bytes - read_len); | |
86 xwrite(1, buf2, read_len); | |
87 | |
88 if (CFG_TOYBOX_FREE) { | |
89 free(buf1); | |
90 free(buf2); | |
91 } | |
92 } | |
93 | |
94 static void free_line(void *data) | |
95 { | |
96 struct line_list *list = (struct line_list *)data; | |
97 free(list->data); | |
98 free(list); | 72 free(list); |
99 } | 73 } |
100 | 74 |
101 static void llist_add(struct line_list **head, struct line_list *new) | 75 // Reading through very large files is slow. Using lseek can speed things |
102 { | 76 // up a lot, but isn't applicable to all input (cat | tail). |
103 struct line_list *cur = *head; | 77 // Note: bytes and lines are negative here. |
104 | 78 static int try_lseek(int fd, long bytes, long lines) |
105 new->next = NULL; | 79 { |
106 if (cur) { | 80 struct line_list *list = 0, *temp; |
107 while (cur->next) | 81 int flag = 0, chunk = sizeof(toybuf); |
108 cur = cur->next; | 82 ssize_t pos = lseek(fd, 0, SEEK_END); |
109 cur->next = new; | 83 |
110 } else *head = new; | 84 // If lseek() doesn't work on this stream, return now. |
111 } | 85 if (pos<0) return 0; |
112 | 86 |
113 static void print_last_lines(int fd, long lines) | 87 // Seek to the right spot, output data from there. |
114 { | 88 if (bytes) { |
115 ssize_t i, total=0; | 89 if (lseek(fd, bytes, SEEK_END)<0) lseek(fd, 0, SEEK_SET); |
116 long size=sizeof(toybuf); | 90 xsendfile(fd, 1); |
117 struct line_list *buf=NULL, *cur; | 91 return 1; |
118 | 92 } |
119 for (;;) { | 93 |
120 // read from input and append to buffer list | 94 // Read from end to find enough lines, then output them. |
121 cur = xzalloc(sizeof(struct line_list)); | 95 |
122 | 96 bytes = pos; |
123 cur->data = xmalloc(size); | 97 while (lines && pos) { |
124 cur->len = readall(fd, cur->data, size); | 98 int offset; |
125 llist_add(&buf, cur); | 99 |
126 | 100 // Read in next chunk from end of file |
127 // count newlines in latest input | 101 if (chunk>pos) chunk = pos; |
128 for (i=0; i<cur->len; i++) | 102 pos -= chunk; |
129 if (cur->data[i] == '\n') cur->lines++; | 103 if (pos != lseek(fd, pos, SEEK_SET)) { |
130 total += cur->lines; | 104 perror_msg("seek failed"); |
131 | 105 break; |
132 // release first buffers if it leaves us enough newlines | 106 } |
133 while (total - buf->lines > lines) { | 107 if (!(temp = get_chunk(fd, chunk))) break; |
134 total -= buf->lines; | 108 if (list) list->next = temp; |
135 free_line(llist_pop(&buf)); | 109 list = temp; |
136 } | 110 |
137 if (cur->len < size) break; | 111 // Count newlines in this chunk. |
138 } | 112 offset = list->len; |
139 | 113 while (offset--) { |
140 // if last buffer doesn't end in a newline, pretend like it did | 114 // If the last line ends with a newline, that one doesn't count. |
141 if (cur->data[cur->len - 1]!='\n') total++; | 115 if (!flag) { |
142 | 116 flag++; |
143 // print out part of the first buffer | 117 |
144 i = 0; | 118 continue; |
145 while (total>lines) | 119 } |
146 if (buf->data[i++]=='\n') total--; | 120 |
147 xwrite(1, buf->data + i, buf->len - i); | 121 // Start outputting data right after newline |
148 | 122 if (list->data[offset] == '\n' && !++lines) { |
149 // print remaining buffers in their entirety | 123 offset++; |
150 for (cur=buf->next; cur; cur=cur->next) | 124 list->data += offset; |
151 xwrite(1, cur->data, cur->len); | 125 list->len -= offset; |
152 | 126 |
153 if (CFG_TOYBOX_FREE) llist_free(buf, free_line); | 127 break; |
154 } | 128 } |
155 | 129 } |
130 } | |
131 | |
132 // Output stored data | |
133 llist_free(list, dump_chunk); | |
134 | |
135 // In case of -f | |
136 lseek(fd, bytes, SEEK_SET); | |
137 return 1; | |
138 } | |
139 | |
140 // Called for each file listed on command line, and/or stdin | |
156 static void do_tail(int fd, char *name) | 141 static void do_tail(int fd, char *name) |
157 { | 142 { |
158 long lines=TT.lines, bytes=TT.bytes; | 143 long bytes = TT.bytes, lines = TT.lines; |
159 | 144 |
160 if (toys.optc > 1) { | 145 if (toys.optc > 1) { |
161 // print an extra newline for all but the first file | |
162 if (TT.file_no++) xputc('\n'); | 146 if (TT.file_no++) xputc('\n'); |
163 xprintf("==> %s <==\n", name); | 147 xprintf("==> %s <==\n", name); |
164 } | 148 } |
165 | 149 |
166 if (lines > 0 || bytes > 0) print_after_offset(fd, bytes, lines); | 150 // Are we measuring from the end of the file? |
167 else if (bytes < 0) print_last_bytes(fd, bytes * -1); | 151 |
168 else print_last_lines(fd, lines * -1); | 152 if (bytes<0 || lines<0) { |
153 struct line_list *list = 0, *new; | |
154 | |
155 // The slow codepath is always needed, and can handle all input, | |
156 // so make lseek support optional. | |
157 if (CFG_TAIL_SEEK && try_lseek(fd, bytes, lines)); | |
158 | |
159 // Read data until we run out, keep a trailing buffer | |
160 else for (;;) { | |
161 int len, count; | |
162 char *try; | |
163 | |
164 if (!(new = get_chunk(fd, sizeof(toybuf)))) break; | |
165 // append in order | |
166 dlist_add_nomalloc((struct double_list **)&list, | |
167 (struct double_list *)new); | |
168 | |
169 // Measure new chunk, discarding extra data from buffer | |
170 len = new->len; | |
171 try = new->data; | |
172 for (count=0; count<len; count++) { | |
173 if ((toys.optflags & FLAG_c) && bytes) { | |
174 bytes++; | |
175 continue; | |
176 } | |
177 | |
178 if (lines) { | |
179 if(try[count] != '\n') continue; | |
180 if (lines<0) { | |
181 if (!++lines) ++lines; | |
182 continue; | |
183 } | |
184 } | |
185 | |
186 // Time to discard data; given that bytes and lines were | |
187 // nonzero coming in, we can't discard too much if we're | |
188 // measuring right. | |
189 do { | |
190 char c = *(list->data++); | |
191 if (!(--list->len)) { | |
192 struct line_list *next = list->next; | |
193 list->prev->next = next; | |
194 list->next->prev = list->prev; | |
195 free(list); | |
196 list = next; | |
197 } | |
198 if (c == '\n') break; | |
199 } while (lines); | |
200 } | |
201 } | |
202 | |
203 // Output/free the buffer. | |
204 llist_free(list, dump_chunk); | |
205 | |
206 // Measuring from the beginning of the file. | |
207 } else for (;;) { | |
208 int len, offset = 0; | |
209 | |
210 // Error while reading does not exit. Error writing does. | |
211 len = read(fd, toybuf, sizeof(toybuf)); | |
212 if (len<1) break; | |
213 while (bytes > 1 || lines > 1) { | |
214 bytes--; | |
215 if (toybuf[offset++] == '\n') lines--; | |
216 if (offset >= len) break; | |
217 } | |
218 if (offset<len) xwrite(1, toybuf+offset, len-offset); | |
219 } | |
220 | |
221 // -f support: cache name/descriptor | |
169 } | 222 } |
170 | 223 |
171 void tail_main(void) | 224 void tail_main(void) |
172 { | 225 { |
173 // if nothing specified, default -n to -10 | 226 // if nothing specified, default -n to -10 |
174 if (!(toys.optflags&(FLAG_n|FLAG_c))) TT.lines = -10; | 227 if (!(toys.optflags&(FLAG_n|FLAG_c))) TT.lines = -10; |
175 | 228 |
176 loopfiles(toys.optargs, do_tail); | 229 loopfiles(toys.optargs, do_tail); |
177 } | 230 |
231 // do -f stuff | |
232 } |