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