Unix Tools

Implementations of common Unix command-line utilities written in C.


unix-tools / tail.c

tail.c raw | download
1#define _POSIX_C_SOURCE 200809L
2#include <sys/stat.h>
3#include <fcntl.h>
4#include <unistd.h>
5#include <errno.h>
6#include <stdint.h>
7#include <limits.h>
8#include <ctype.h>
9#include <stdio.h>
10#include <stdlib.h>
11#include <string.h>
12#include <time.h>
13
14static long long count = 10;
15static int anycount, follow, file = STDIN_FILENO;
16static enum { BEG, END } origin = END;
17static enum { CHARS, LINES } units = LINES;
18static enum { FWD, REV } direction = FWD;
19
20static void
21fatal(const char *name)
22{
23 perror(name);
24 exit(1);
25}
26
27static void
28usage(void)
29{
30 fprintf(stderr, "usage: tail [-n N | -c N] [-f | -r] [file]\n");
31 exit(1);
32}
33
34static ssize_t
35tread(char *buf, size_t size)
36{
37 ssize_t n;
38 do { n = read(file, buf, size); } while (n < 0 && errno == EINTR);
39 if (n < 0) fatal("tail: read");
40 return n;
41}
42
43static void
44twrite(const char *buf, size_t size)
45{
46 while (size) {
47 ssize_t n = write(STDOUT_FILENO, buf, size);
48 if (n < 0 && errno == EINTR) continue;
49 if (n <= 0) fatal("tail: stdout");
50 buf += n; size -= (size_t)n;
51 }
52}
53
54static off_t
55tseek(off_t offset, int whence)
56{
57 off_t position = lseek(file, offset, whence);
58 if (position == (off_t)-1) fatal("tail: seek");
59 return position;
60}
61
62static void
63copy(void)
64{
65 char buf[8192];
66 ssize_t n;
67 while ((n = tread(buf, sizeof(buf))) > 0) twrite(buf, (size_t)n);
68}
69
70static void
71skip(void)
72{
73 char buf[8192];
74 while (count > 0) {
75 size_t i = 0, size = sizeof(buf);
76 ssize_t n;
77 if (units == CHARS && count < (long long)size) size = (size_t)count;
78 n = tread(buf, size);
79 if (!n) return;
80 if (units == CHARS) count -= n;
81 else {
82 while (i < (size_t)n && count > 0)
83 if (buf[i++] == '\n') --count;
84 if (count == 0) twrite(buf + i, (size_t)n - i);
85 }
86 }
87 copy();
88}
89
90/* Keep only the requested suffix when the input cannot be sought. */
91static void
92keep(void)
93{
94 char *buf = NULL;
95 size_t len = 0, capacity = 0;
96 for (;;) {
97 ssize_t n;
98 size_t start, lines = 0;
99 if (capacity - len < 8192) {
100 char *newbuf;
101 if (len > SIZE_MAX - 8193) { errno = ENOMEM; fatal("tail"); }
102 capacity = len + 8193;
103 newbuf = realloc(buf, capacity);
104 if (newbuf == NULL) fatal("tail: realloc");
105 buf = newbuf;
106 }
107 n = tread(buf + len, capacity - len - 1);
108 if (!n) break;
109 len += (size_t)n;
110 if (units == CHARS) start = len > (size_t)count ? len - (size_t)count : 0;
111 else {
112 start = len && buf[len - 1] == '\n' ? len - 1 : len;
113 while (start > 0) {
114 if (buf[start - 1] == '\n' && ++lines >= (size_t)count) break;
115 --start;
116 }
117 }
118 memmove(buf, buf + start, len - start);
119 len -= start;
120 }
121 if (direction == REV) {
122 size_t end = len, start;
123 if (len && buf[len - 1] != '\n') buf[end++] = '\n';
124 while (end) {
125 start = end - 1;
126 while (start && buf[start - 1] != '\n') --start;
127 twrite(buf + start, end - start);
128 end = start;
129 }
130 } else twrite(buf, len);
131 free(buf);
132}
133
134/* Scan backward by blocks, as in the supplied implementation. */
135static void
136reverse(void)
137{
138 off_t pos = tseek(0, SEEK_END);
139 char *buf = NULL;
140 size_t len = 0, capacity = 0, split = 0;
141 off_t start = 0;
142 int first = 1;
143 while (pos > 0 && count > 0) {
144 size_t n = pos > 8192 ? 8192 : (size_t)pos, used = 0;
145 char *newbuf;
146 pos -= (off_t)n;
147 if (n > SIZE_MAX - len - 1) { errno = ENOMEM; fatal("tail"); }
148 if (len + n + 1 > capacity) {
149 capacity = len + n + 1;
150 newbuf = realloc(buf, capacity);
151 if (newbuf == NULL) fatal("tail: realloc");
152 buf = newbuf;
153 }
154 if (len) memmove(buf + n, buf, len);
155 len += n;
156 tseek(pos, SEEK_SET);
157 while (used < n) {
158 ssize_t got = tread(buf + used, n - used);
159 if (!got) { fprintf(stderr, "tail: file changed while reading\n"); free(buf); exit(1); }
160 used += (size_t)got;
161 }
162 if (first && buf[len - 1] != '\n') buf[len++] = '\n';
163 first = 0;
164 for (split = len - 1; split > 0 && count > 0; --split) {
165 if (buf[split - 1] == '\n') {
166 --count;
167 if (direction == REV) twrite(buf + split, len - split);
168 len = split;
169 if (!count) { start = pos + (off_t)split; break; }
170 }
171 }
172 }
173 if (direction == FWD) {
174 tseek(start, SEEK_SET);
175 copy();
176 } else if (count > 0) twrite(buf, len);
177 free(buf);
178}
179
180static int
181getnumber(char *s)
182{
183 char *end;
184 int beginning = *s == '+';
185 if (*s == '-' || *s == '+') ++s;
186 if (!isdigit((unsigned char)*s)) return 0;
187 if (anycount++) usage();
188 errno = 0;
189 count = strtoll(s, &end, 10);
190 if (errno || count < 0 || count > INT_MAX || (*end && !strchr("bclfr", *end))) usage();
191 if (beginning) origin = BEG;
192 return 1;
193}
194
195static void
196suffix(char *s)
197{
198 while (*s && strchr("0123456789+-", *s)) ++s;
199 if (*s == 'b') {
200 if (count > INT_MAX / 1024) usage();
201 count *= 1024; units = CHARS; ++s;
202 } else if (*s == 'c') { units = CHARS; ++s; }
203 else if (*s == 'l') ++s;
204 if (*s == 'r') { direction = REV; ++s; }
205 else if (*s == 'f') { follow = 1; ++s; }
206 if (*s) usage();
207}
208
209int
210main(int argc, char **argv)
211{
212 int seekable;
213 off_t previous;
214 for (; argc > 1 && (argv[1][0] == '-' || argv[1][0] == '+'); --argc, ++argv) {
215 char option = argv[1][1];
216 if (strcmp(argv[1], "-") == 0) break;
217 if (strcmp(argv[1], "--") == 0) { --argc; ++argv; break; }
218 if (getnumber(argv[1])) { suffix(argv[1]); continue; }
219 if (option == 'n' || option == 'c') {
220 if (option == 'c') units = CHARS;
221 if (getnumber(argv[1] + 2)) { suffix(argv[1] + 2); continue; }
222 if (argv[1][2]) usage();
223 if (argc <= 2 || !getnumber(argv[2])) usage();
224 suffix(argv[2]); --argc; ++argv;
225 } else if (strcmp(argv[1], "-r") == 0) direction = REV;
226 else if (strcmp(argv[1], "-f") == 0) follow = 1;
227 else usage();
228 }
229 if (argc > 2 || (direction == REV && (units == CHARS || follow || origin == BEG))) usage();
230 if (!anycount) count = direction == REV ? INT_MAX : 10;
231 if (origin == BEG && count > 0) --count;
232 if (argc == 2 && strcmp(argv[1], "-") != 0) {
233 file = open(argv[1], O_RDONLY);
234 if (file < 0) fatal(argv[1]);
235 }
236 seekable = lseek(file, 0, SEEK_CUR) != (off_t)-1;
237 if (count > 0 || origin == BEG) {
238 if (origin == BEG) skip();
239 else if (!seekable) keep();
240 else if (units == CHARS) {
241 off_t end = tseek(0, SEEK_END);
242 tseek(end > count ? end - (off_t)count : 0, SEEK_SET);
243 copy();
244 } else reverse();
245 } else if (seekable) tseek(0, SEEK_END);
246 if (follow && seekable) {
247 previous = tseek(0, SEEK_CUR);
248 for (;;) {
249 struct stat st;
250 struct timespec delay = {1, 0};
251 while (nanosleep(&delay, &delay) < 0 && errno == EINTR) ;
252 if (fstat(file, &st) < 0) fatal("tail: fstat");
253 if (st.st_size < previous) tseek(0, SEEK_SET);
254 copy();
255 previous = tseek(0, SEEK_CUR);
256 }
257 }
258 if (file != STDIN_FILENO && close(file) < 0) fatal("tail: close");
259 return 0;
260}