1/*	$OpenBSD: forward.c,v 1.33 2019/06/28 13:35:04 deraadt Exp $	*/
2/*	$NetBSD: forward.c,v 1.7 1996/02/13 16:49:10 ghudson Exp $	*/
3
4/*-
5 * Copyright (c) 1991, 1993
6 *	The Regents of the University of California.  All rights reserved.
7 *
8 * This code is derived from software contributed to Berkeley by
9 * Edward Sze-Tyan Wang.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 *    notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 *    notice, this list of conditions and the following disclaimer in the
18 *    documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the University nor the names of its contributors
20 *    may be used to endorse or promote products derived from this software
21 *    without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 */
35
36#include <sys/types.h>
37#include <sys/stat.h>
38#include <sys/event.h>
39
40#include <err.h>
41#include <errno.h>
42#include <stdio.h>
43#include <stdlib.h>
44#include <string.h>
45#include <unistd.h>
46
47#include "extern.h"
48
49static int rlines(struct tailfile *, off_t);
50static inline void tfprint(FILE *fp);
51static int tfqueue(struct tailfile *tf);
52static const struct timespec *tfreopen(struct tailfile *tf);
53
54static int kq = -1;
55
56/*
57 * forward -- display the file, from an offset, forward.
58 *
59 * There are eight separate cases for this -- regular and non-regular
60 * files, by bytes or lines and from the beginning or end of the file.
61 *
62 * FBYTES	byte offset from the beginning of the file
63 *	REG	seek
64 *	NOREG	read, counting bytes
65 *
66 * FLINES	line offset from the beginning of the file
67 *	REG	read, counting lines
68 *	NOREG	read, counting lines
69 *
70 * RBYTES	byte offset from the end of the file
71 *	REG	seek
72 *	NOREG	cyclically read characters into a wrap-around buffer
73 *
74 * RLINES
75 *	REG	step back until the correct offset is reached.
76 *	NOREG	cyclically read lines into a wrap-around array of buffers
77 */
78void
79forward(struct tailfile *tf, int nfiles, enum STYLE style, off_t origoff)
80{
81	int ch;
82	struct tailfile *ctf, *ltf;
83	struct kevent ke;
84	const struct timespec *ts = NULL;
85	int i;
86	int nevents;
87
88	if (nfiles < 1)
89		return;
90
91	if (fflag && (kq = kqueue()) == -1)
92		warn("kqueue");
93
94	for (i = 0; i < nfiles; i++) {
95		off_t off = origoff;
96		if (nfiles > 1)
97			printfname(tf[i].fname);
98
99		switch(style) {
100		case FBYTES:
101			if (off == 0)
102				break;
103			if (S_ISREG(tf[i].sb.st_mode)) {
104				if (tf[i].sb.st_size < off)
105					off = tf[i].sb.st_size;
106				if (fseeko(tf[i].fp, off, SEEK_SET) == -1) {
107					ierr(tf[i].fname);
108					return;
109				}
110			} else while (off--)
111				if ((ch = getc(tf[i].fp)) == EOF) {
112					if (ferror(tf[i].fp)) {
113						ierr(tf[i].fname);
114						return;
115					}
116					break;
117				}
118			break;
119		case FLINES:
120			if (off == 0)
121				break;
122			for (;;) {
123				if ((ch = getc(tf[i].fp)) == EOF) {
124					if (ferror(tf[i].fp)) {
125						ierr(tf[i].fname);
126						return;
127					}
128					break;
129				}
130				if (ch == '\n' && !--off)
131					break;
132			}
133			break;
134		case RBYTES:
135			if (S_ISREG(tf[i].sb.st_mode)) {
136				if (tf[i].sb.st_size >= off &&
137				    fseeko(tf[i].fp, -off, SEEK_END) == -1) {
138					ierr(tf[i].fname);
139					return;
140				}
141			} else if (off == 0) {
142				while (getc(tf[i].fp) != EOF)
143					;
144				if (ferror(tf[i].fp)) {
145					ierr(tf[i].fname);
146					return;
147				}
148			} else {
149				if (bytes(&(tf[i]), off))
150					return;
151			}
152			break;
153		case RLINES:
154			if (S_ISREG(tf[i].sb.st_mode)) {
155				if (!off) {
156					if (fseeko(tf[i].fp, (off_t)0,
157					    SEEK_END) == -1) {
158						ierr(tf[i].fname);
159						return;
160					}
161				} else if (rlines(&(tf[i]), off) != 0)
162					lines(&(tf[i]), off);
163			} else if (off == 0) {
164				while (getc(tf[i].fp) != EOF)
165					;
166				if (ferror(tf[i].fp)) {
167					ierr(tf[i].fname);
168					return;
169				}
170			} else {
171				if (lines(&(tf[i]), off))
172					return;
173			}
174			break;
175		default:
176			err(1, "Unsupported style");
177		}
178
179		tfprint(tf[i].fp);
180		if (fflag && tfqueue(&(tf[i])) == -1)
181			warn("Unable to follow %s", tf[i].fname);
182
183	}
184	ltf = &(tf[i-1]);
185
186	(void)fflush(stdout);
187	if (!fflag || kq == -1)
188		return;
189
190	while (1) {
191		if ((nevents = kevent(kq, NULL, 0, &ke, 1, ts)) <= 0) {
192			if (errno == EINTR) {
193				close(kq);
194				return;
195			}
196		}
197
198		ctf = ke.udata;
199		if (nevents > 0) {
200			if (ke.filter == EVFILT_READ) {
201				if (ctf != ltf) {
202					printfname(ctf->fname);
203					ltf = ctf;
204				}
205				clearerr(ctf->fp);
206				tfprint(ctf->fp);
207				if (ferror(ctf->fp)) {
208					ierr(ctf->fname);
209					fclose(ctf->fp);
210					warn("Lost file %s", ctf->fname);
211					continue;
212				}
213				(void)fflush(stdout);
214				clearerr(ctf->fp);
215			} else if (ke.filter == EVFILT_VNODE) {
216				if (ke.fflags & (NOTE_DELETE | NOTE_RENAME)) {
217					/*
218					 * File was deleted or renamed.
219					 *
220					 * Continue to look at it until
221					 * a new file reappears with
222					 * the same name.
223					 */
224					(void) tfreopen(ctf);
225				} else if (ke.fflags & NOTE_TRUNCATE) {
226					warnx("%s has been truncated, "
227					    "resetting.", ctf->fname);
228					fpurge(ctf->fp);
229					rewind(ctf->fp);
230				}
231			}
232		}
233		ts = tfreopen(NULL);
234	}
235}
236
237/*
238 * rlines -- display the last offset lines of the file.
239 */
240static int
241rlines(struct tailfile *tf, off_t off)
242{
243	off_t pos;
244	int ch;
245
246	pos = tf->sb.st_size;
247	if (pos == 0)
248		return (0);
249
250	/*
251	 * Position before char.
252	 * Last char is special, ignore it whether newline or not.
253	 */
254	pos -= 2;
255	ch = EOF;
256	for (; off > 0 && pos >= 0; pos--) {
257		/* A seek per char isn't a problem with a smart stdio */
258		if (fseeko(tf[0].fp, pos, SEEK_SET) == -1) {
259			ierr(tf->fname);
260			return (1);
261		}
262		if ((ch = getc(tf[0].fp)) == '\n')
263			off--;
264		else if (ch == EOF) {
265			if (ferror(tf[0].fp)) {
266				ierr(tf->fname);
267				return (1);
268			}
269			break;
270		}
271	}
272	/* If we read until start of file, put back last read char */
273	if (pos < 0 && off > 0 && ch != EOF && ungetc(ch, tf[0].fp) == EOF) {
274		ierr(tf->fname);
275		return (1);
276	}
277
278	while (!feof(tf[0].fp) && (ch = getc(tf[0].fp)) != EOF)
279		if (putchar(ch) == EOF)
280			oerr();
281	if (ferror(tf[0].fp)) {
282		ierr(tf->fname);
283		return (1);
284	}
285
286	return (0);
287}
288
289static inline void
290tfprint(FILE *fp)
291{
292	int ch;
293
294	while (!feof(fp) && (ch = getc(fp)) != EOF)
295		if (putchar(ch) == EOF)
296			oerr();
297}
298
299static int
300tfqueue(struct tailfile *tf)
301{
302	struct kevent ke[2];
303	int i = 1;
304
305	if (kq < 0) {
306		errno = EBADF;
307		return -1;
308	}
309
310	EV_SET(&(ke[0]), fileno(tf->fp), EVFILT_READ,
311	    EV_ENABLE | EV_ADD | EV_CLEAR, 0, 0, tf);
312
313	if (S_ISREG(tf->sb.st_mode)) {
314		i = 2;
315		EV_SET(&(ke[1]), fileno(tf->fp), EVFILT_VNODE,
316		    EV_ENABLE | EV_ADD | EV_CLEAR,
317		    NOTE_DELETE | NOTE_RENAME | NOTE_TRUNCATE,
318		    0, tf);
319	}
320	if (kevent(kq, ke, i, NULL, 0, NULL) == -1) {
321		ierr(tf->fname);
322		return -1;
323	}
324	return 0;
325}
326
327#define AFILESINCR 8
328static const struct timespec *
329tfreopen(struct tailfile *tf) {
330	static struct tailfile		**reopen = NULL;
331	static int			  nfiles = 0, afiles = 0;
332	static const struct timespec	  ts = {1, 0};
333
334	struct stat			  sb;
335	struct tailfile			**treopen, *ttf;
336	int				  i;
337
338	if (tf && !(tf->fp == stdin) &&
339	    ((stat(tf->fname, &sb) != 0) || sb.st_ino != tf->sb.st_ino)) {
340		if (afiles < ++nfiles) {
341			afiles += AFILESINCR;
342			treopen = reallocarray(reopen, afiles, sizeof(*reopen));
343			if (treopen)
344				reopen = treopen;
345			else
346				afiles -= AFILESINCR;
347		}
348		if (nfiles <= afiles) {
349			for (i = 0; i < nfiles - 1; i++)
350				if (strcmp(reopen[i]->fname, tf->fname) == 0)
351					break;
352			if (i < nfiles - 1)
353				nfiles--;
354			else
355				reopen[nfiles-1] = tf;
356		} else {
357			warnx("Lost track of %s", tf->fname);
358			nfiles--;
359		}
360	}
361
362	for (i = 0; i < nfiles; i++) {
363		ttf = reopen[i];
364		if (stat(ttf->fname, &sb) == -1)
365			continue;
366		if (sb.st_ino != ttf->sb.st_ino) {
367			(void) memcpy(&(ttf->sb), &sb, sizeof(ttf->sb));
368			ttf->fp = freopen(ttf->fname, "r", ttf->fp);
369			if (ttf->fp == NULL)
370				ierr(ttf->fname);
371			else {
372				warnx("%s has been replaced, reopening.",
373				    ttf->fname);
374				tfqueue(ttf);
375			}
376		}
377		reopen[i] = reopen[--nfiles];
378	}
379
380	return nfiles ? &ts : NULL;
381}
382