tail.c - sbase - suckless unix tools
(HTM) git clone git://git.suckless.org/sbase
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
(DIR) LICENSE
---
tail.c (4355B)
---
1 /* See LICENSE file for copyright and license details. */
2 #include <sys/stat.h>
3
4 #include <fcntl.h>
5 #include <unistd.h>
6 #include <stdint.h>
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <unistd.h>
11
12 #include "utf.h"
13 #include "util.h"
14
15 static char mode = 'n';
16
17 static int
18 dropinit(int fd, const char *fname, size_t count)
19 {
20 Rune r;
21 char buf[BUFSIZ], *p;
22 ssize_t n;
23 int nr;
24
25 if (count < 2)
26 goto copy;
27 count--; /* numbering starts at 1 */
28 while (count && (n = read(fd, buf, sizeof(buf))) > 0) {
29 switch (mode) {
30 case 'n': /* lines */
31 for (p = buf; count && n > 0; p++, n--) {
32 if (*p == '\n')
33 count--;
34 }
35 break;
36 case 'c': /* bytes */
37 if (count > n) {
38 count -= n;
39 } else {
40 p = buf + count;
41 n -= count;
42 count = 0;
43 }
44 break;
45 case 'm': /* runes */
46 for (p = buf; count && n > 0; p += nr, n -= nr, count--) {
47 nr = charntorune(&r, p, n);
48 if (!nr) {
49 /* we don't have a full rune, move
50 * remaining data to beginning and read
51 * again */
52 memmove(buf, p, n);
53 break;
54 }
55 }
56 break;
57 }
58 }
59 if (count) {
60 if (n < 0)
61 weprintf("read %s:", fname);
62 if (n <= 0)
63 return n;
64 }
65
66 /* write the rest of the buffer */
67 if (writeall(1, p, n) < 0)
68 eprintf("write:");
69 copy:
70 switch (concat(fd, fname, 1, "<stdout>")) {
71 case -1: /* read error */
72 return -1;
73 case -2: /* write error */
74 exit(1);
75 default:
76 return 0;
77 }
78 }
79
80 static int
81 taketail(int fd, const char *fname, size_t count)
82 {
83 static char *buf = NULL;
84 static size_t size = 0;
85 char *p;
86 size_t len = 0, left;
87 ssize_t n;
88
89 if (!count)
90 return 0;
91 for (;;) {
92 if (len + BUFSIZ > size) {
93 /* make sure we have at least BUFSIZ to read */
94 size += 2 * BUFSIZ;
95 buf = erealloc(buf, size);
96 }
97 n = read(fd, buf + len, size - len);
98 if (n < 0) {
99 weprintf("read %s:", fname);
100 return -1;
101 }
102 if (n == 0)
103 break;
104 len += n;
105 switch (mode) {
106 case 'n': /* lines */
107 /* ignore the last character; if it is a newline, it
108 * ends the last line */
109 for (p = buf + len - 2, left = count; p >= buf; p--) {
110 if (*p != '\n')
111 continue;
112 left--;
113 if (!left) {
114 p++;
115 break;
116 }
117 }
118 break;
119 case 'c': /* bytes */
120 p = count < len ? buf + len - count : buf;
121 break;
122 case 'm': /* runes */
123 for (p = buf + len - 1, left = count; p >= buf; p--) {
124 /* skip utf-8 continuation bytes */
125 if (UTF8_POINT(*p))
126 continue;
127 left--;
128 if (!left)
129 break;
130 }
131 break;
132 }
133 if (p > buf) {
134 len -= p - buf;
135 memmove(buf, p, len);
136 }
137 }
138 if (writeall(1, buf, len) < 0)
139 eprintf("write:");
140 return 0;
141 }
142
143 static void
144 usage(void)
145 {
146 eprintf("usage: %s [-f] [-c num | -m num | -n num | -num] [file ...]\n", argv0);
147 }
148
149 int
150 main(int argc, char *argv[])
151 {
152 struct stat st1, st2;
153 int fd;
154 size_t n = 10;
155 int fflag = 0, ret = 0, newline = 0, many = 0;
156 char *numstr;
157 int (*tail)(int, const char *, size_t) = taketail;
158
159 ARGBEGIN {
160 case 'f':
161 fflag = 1;
162 break;
163 case 'c':
164 case 'm':
165 case 'n':
166 mode = ARGC();
167 numstr = EARGF(usage());
168 n = MIN(llabs(estrtonum(numstr, LLONG_MIN + 1,
169 MIN(LLONG_MAX, SIZE_MAX))), SIZE_MAX);
170 if (strchr(numstr, '+'))
171 tail = dropinit;
172 break;
173 ARGNUM:
174 n = ARGNUMF();
175 break;
176 default:
177 usage();
178 } ARGEND
179
180 if (!argc) {
181 if (tail(0, "<stdin>", n) < 0)
182 ret = 1;
183 } else {
184 if ((many = argc > 1) && fflag)
185 usage();
186 for (newline = 0; *argv; argc--, argv++) {
187 if (!strcmp(*argv, "-")) {
188 *argv = "<stdin>";
189 fd = 0;
190 } else if ((fd = open(*argv, O_RDONLY)) < 0) {
191 weprintf("open %s:", *argv);
192 ret = 1;
193 continue;
194 }
195 if (many)
196 printf("%s==> %s <==\n", newline ? "\n" : "", *argv);
197 if (fstat(fd, &st1) < 0)
198 eprintf("fstat %s:", *argv);
199 if (!(S_ISFIFO(st1.st_mode) || S_ISREG(st1.st_mode)))
200 fflag = 0;
201 newline = 1;
202 if (tail(fd, *argv, n) < 0) {
203 ret = 1;
204 fflag = 0;
205 }
206
207 if (!fflag) {
208 if (fd != 0)
209 close(fd);
210 continue;
211 }
212 for (;;) {
213 if (concat(fd, *argv, 1, "<stdout>") < 0)
214 exit(1);
215 if (fstat(fd, &st2) < 0)
216 eprintf("fstat %s:", *argv);
217 if (st2.st_size < st1.st_size) {
218 fprintf(stderr, "%s: file truncated\n", *argv);
219 if (lseek(fd, SEEK_SET, 0) < 0)
220 eprintf("lseek:");
221 }
222 st1 = st2;
223 sleep(1);
224 }
225 }
226 }
227
228 return ret;
229 }