1/*-
2 * SPDX-License-Identifier: BSD-3-Clause
3 *
4 * Copyright (c) 1991, 1993
5 *	The Regents of the University of California.  All rights reserved.
6 *
7 * This code is derived from software contributed to Berkeley by
8 * Edward Sze-Tyan Wang.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 * 3. Neither the name of the University nor the names of its contributors
19 *    may be used to endorse or promote products derived from this software
20 *    without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 * SUCH DAMAGE.
33 */
34
35#include <sys/cdefs.h>
36
37__FBSDID("$FreeBSD$");
38
39#ifndef lint
40static const char sccsid[] = "@(#)forward.c	8.1 (Berkeley) 6/6/93";
41#endif
42
43#include <sys/param.h>
44#include <sys/mount.h>
45#include <sys/types.h>
46#include <sys/stat.h>
47#include <sys/time.h>
48#include <sys/mman.h>
49#include <sys/event.h>
50
51#include <err.h>
52#include <errno.h>
53#include <fcntl.h>
54#include <limits.h>
55#include <stdio.h>
56#include <stdlib.h>
57#include <string.h>
58#include <unistd.h>
59
60#include <libcasper.h>
61#include <casper/cap_fileargs.h>
62
63#include "extern.h"
64
65static void rlines(FILE *, const char *fn, off_t, struct stat *);
66static int show(file_info_t *);
67static void set_events(file_info_t *files);
68
69/* defines for inner loop actions */
70#define USE_SLEEP	0
71#define USE_KQUEUE	1
72#define ADD_EVENTS	2
73
74static struct kevent *ev;
75static int action = USE_SLEEP;
76static int kq;
77
78static const file_info_t *last;
79
80/*
81 * forward -- display the file, from an offset, forward.
82 *
83 * There are eight separate cases for this -- regular and non-regular
84 * files, by bytes or lines and from the beginning or end of the file.
85 *
86 * FBYTES	byte offset from the beginning of the file
87 *	REG	seek
88 *	NOREG	read, counting bytes
89 *
90 * FLINES	line offset from the beginning of the file
91 *	REG	read, counting lines
92 *	NOREG	read, counting lines
93 *
94 * RBYTES	byte offset from the end of the file
95 *	REG	seek
96 *	NOREG	cyclically read characters into a wrap-around buffer
97 *
98 * RLINES
99 *	REG	mmap the file and step back until reach the correct offset.
100 *	NOREG	cyclically read lines into a wrap-around array of buffers
101 */
102void
103forward(FILE *fp, const char *fn, enum STYLE style, off_t off, struct stat *sbp)
104{
105	int ch;
106
107	switch(style) {
108	case FBYTES:
109		if (off == 0)
110			break;
111		if (S_ISREG(sbp->st_mode)) {
112			if (sbp->st_size < off)
113				off = sbp->st_size;
114			if (fseeko(fp, off, SEEK_SET) == -1) {
115				ierr(fn);
116				return;
117			}
118		} else while (off--)
119			if ((ch = getc(fp)) == EOF) {
120				if (ferror(fp)) {
121					ierr(fn);
122					return;
123				}
124				break;
125			}
126		break;
127	case FLINES:
128		if (off == 0)
129			break;
130		for (;;) {
131			if ((ch = getc(fp)) == EOF) {
132				if (ferror(fp)) {
133					ierr(fn);
134					return;
135				}
136				break;
137			}
138			if (ch == '\n' && !--off)
139				break;
140		}
141		break;
142	case RBYTES:
143		if (S_ISREG(sbp->st_mode)) {
144			if (sbp->st_size >= off &&
145			    fseeko(fp, -off, SEEK_END) == -1) {
146				ierr(fn);
147				return;
148			}
149		} else if (off == 0) {
150			while (getc(fp) != EOF);
151			if (ferror(fp)) {
152				ierr(fn);
153				return;
154			}
155		} else
156			if (bytes(fp, fn, off))
157				return;
158		break;
159	case RLINES:
160		if (S_ISREG(sbp->st_mode))
161			if (!off) {
162				if (fseeko(fp, (off_t)0, SEEK_END) == -1) {
163					ierr(fn);
164					return;
165				}
166			} else
167				rlines(fp, fn, off, sbp);
168		else if (off == 0) {
169			while (getc(fp) != EOF);
170			if (ferror(fp)) {
171				ierr(fn);
172				return;
173			}
174		} else
175			if (lines(fp, fn, off))
176				return;
177		break;
178	default:
179		break;
180	}
181
182	while ((ch = getc(fp)) != EOF)
183		if (putchar(ch) == EOF)
184			oerr();
185	if (ferror(fp)) {
186		ierr(fn);
187		return;
188	}
189	(void)fflush(stdout);
190}
191
192/*
193 * rlines -- display the last offset lines of the file.
194 */
195static void
196rlines(FILE *fp, const char *fn, off_t off, struct stat *sbp)
197{
198	struct mapinfo map;
199	off_t curoff, size;
200	int i;
201
202	if (!(size = sbp->st_size))
203		return;
204	map.start = NULL;
205	map.fd = fileno(fp);
206	map.mapoff = map.maxoff = size;
207
208	/*
209	 * Last char is special, ignore whether newline or not. Note that
210	 * size == 0 is dealt with above, and size == 1 sets curoff to -1.
211	 */
212	curoff = size - 2;
213	while (curoff >= 0) {
214		if (curoff < map.mapoff && maparound(&map, curoff) != 0) {
215			ierr(fn);
216			return;
217		}
218		for (i = curoff - map.mapoff; i >= 0; i--)
219			if (map.start[i] == '\n' && --off == 0)
220				break;
221		/* `i' is either the map offset of a '\n', or -1. */
222		curoff = map.mapoff + i;
223		if (i >= 0)
224			break;
225	}
226	curoff++;
227	if (mapprint(&map, curoff, size - curoff) != 0) {
228		ierr(fn);
229		exit(1);
230	}
231
232	/* Set the file pointer to reflect the length displayed. */
233	if (fseeko(fp, sbp->st_size, SEEK_SET) == -1) {
234		ierr(fn);
235		return;
236	}
237	if (map.start != NULL && munmap(map.start, map.maplen)) {
238		ierr(fn);
239		return;
240	}
241}
242
243static int
244show(file_info_t *file)
245{
246	int ch;
247
248	while ((ch = getc(file->fp)) != EOF) {
249		if (last != file && no_files > 1) {
250			if (!qflag)
251				printfn(file->file_name, 1);
252			last = file;
253		}
254		if (putchar(ch) == EOF)
255			oerr();
256	}
257	(void)fflush(stdout);
258	if (ferror(file->fp)) {
259		fclose(file->fp);
260		file->fp = NULL;
261		ierr(file->file_name);
262		return 0;
263	}
264	clearerr(file->fp);
265	return 1;
266}
267
268static void
269set_events(file_info_t *files)
270{
271	int i, n = 0;
272	file_info_t *file;
273	struct timespec ts;
274	struct statfs sf;
275
276	ts.tv_sec = 0;
277	ts.tv_nsec = 0;
278
279	action = USE_KQUEUE;
280	for (i = 0, file = files; i < no_files; i++, file++) {
281		if (! file->fp)
282			continue;
283
284		if (fstatfs(fileno(file->fp), &sf) == 0 &&
285		    (sf.f_flags & MNT_LOCAL) == 0) {
286			action = USE_SLEEP;
287			return;
288		}
289
290		if (Fflag && fileno(file->fp) != STDIN_FILENO) {
291			EV_SET(&ev[n], fileno(file->fp), EVFILT_VNODE,
292			    EV_ADD | EV_ENABLE | EV_CLEAR,
293			    NOTE_DELETE | NOTE_RENAME, 0, 0);
294			n++;
295		}
296		EV_SET(&ev[n], fileno(file->fp), EVFILT_READ,
297		    EV_ADD | EV_ENABLE | EV_CLEAR, 0, 0, 0);
298		n++;
299	}
300
301	if (kevent(kq, ev, n, NULL, 0, &ts) < 0) {
302		action = USE_SLEEP;
303	}
304}
305
306/*
307 * follow -- display the file, from an offset, forward.
308 *
309 */
310void
311follow(file_info_t *files, enum STYLE style, off_t off)
312{
313	int active, ev_change, i, n = -1;
314	struct stat sb2;
315	file_info_t *file;
316	FILE *ftmp;
317	struct timespec ts;
318
319	/* Position each of the files */
320
321	file = files;
322	active = 0;
323	n = 0;
324	for (i = 0; i < no_files; i++, file++) {
325		if (file->fp) {
326			active = 1;
327			n++;
328			if (no_files > 1 && !qflag)
329				printfn(file->file_name, 1);
330			forward(file->fp, file->file_name, style, off, &file->st);
331			if (Fflag && fileno(file->fp) != STDIN_FILENO)
332				n++;
333		}
334	}
335	if (!Fflag && !active)
336		return;
337
338	last = --file;
339
340	kq = kqueue();
341	if (kq < 0)
342		err(1, "kqueue");
343	ev = malloc(n * sizeof(struct kevent));
344	if (! ev)
345	    err(1, "Couldn't allocate memory for kevents.");
346	set_events(files);
347
348	for (;;) {
349		ev_change = 0;
350		if (Fflag) {
351			for (i = 0, file = files; i < no_files; i++, file++) {
352				if (!file->fp) {
353					file->fp =
354					    fileargs_fopen(fa, file->file_name,
355					    "r");
356					if (file->fp != NULL &&
357					    fstat(fileno(file->fp), &file->st)
358					    == -1) {
359						fclose(file->fp);
360						file->fp = NULL;
361					}
362					if (file->fp != NULL)
363						ev_change++;
364					continue;
365				}
366				if (fileno(file->fp) == STDIN_FILENO)
367					continue;
368				ftmp = fileargs_fopen(fa, file->file_name, "r");
369				if (ftmp == NULL ||
370				    fstat(fileno(ftmp), &sb2) == -1) {
371					if (errno != ENOENT)
372						ierr(file->file_name);
373					show(file);
374					if (file->fp != NULL) {
375						fclose(file->fp);
376						file->fp = NULL;
377					}
378					if (ftmp != NULL) {
379						fclose(ftmp);
380					}
381					ev_change++;
382					continue;
383				}
384
385				if (sb2.st_ino != file->st.st_ino ||
386				    sb2.st_dev != file->st.st_dev ||
387				    sb2.st_nlink == 0) {
388					show(file);
389					fclose(file->fp);
390					file->fp = ftmp;
391					memcpy(&file->st, &sb2,
392					    sizeof(struct stat));
393					ev_change++;
394				} else {
395					fclose(ftmp);
396				}
397			}
398		}
399
400		for (i = 0, file = files; i < no_files; i++, file++)
401			if (file->fp && !show(file))
402				ev_change++;
403
404		if (ev_change)
405			set_events(files);
406
407		switch (action) {
408		case USE_KQUEUE:
409			ts.tv_sec = 1;
410			ts.tv_nsec = 0;
411			/*
412			 * In the -F case we set a timeout to ensure that
413			 * we re-stat the file at least once every second.
414			 */
415			n = kevent(kq, NULL, 0, ev, 1, Fflag ? &ts : NULL);
416			if (n < 0)
417				err(1, "kevent");
418			if (n == 0) {
419				/* timeout */
420				break;
421			} else if (ev->filter == EVFILT_READ && ev->data < 0) {
422				/* file shrank, reposition to end */
423				if (lseek(ev->ident, (off_t)0, SEEK_END) == -1) {
424					ierr(file->file_name);
425					continue;
426				}
427			}
428			break;
429
430		case USE_SLEEP:
431			(void) usleep(250000);
432			break;
433		}
434	}
435}
436