master xplshn/aruu / cmd / posix / tail.c
  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}