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