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
36
37#include <sys/param.h>
38#include <sys/mount.h>
39#include <sys/types.h>
40#include <sys/stat.h>
41#include <sys/time.h>
42#include <sys/mman.h>
43#include <sys/event.h>
44
45#include <err.h>
46#include <errno.h>
47#include <fcntl.h>
48#include <limits.h>
49#include <stdio.h>
50#include <stdlib.h>
51#include <string.h>
52#include <unistd.h>
53
54#include <libcasper.h>
55#include <casper/cap_fileargs.h>
56
57#include "extern.h"
58
59static void rlines(FILE *, const char *fn, off_t, struct stat *);
60static int show(file_info_t *);
61static void set_events(file_info_t *files);
62
63/* defines for inner loop actions */
64#define USE_SLEEP	0
65#define USE_KQUEUE	1
66#define ADD_EVENTS	2
67
68static struct kevent *ev;
69static int action = USE_SLEEP;
70static int kq;
71
72static const file_info_t *last;
73
74/*
75 * forward -- display the file, from an offset, forward.
76 *
77 * There are eight separate cases for this -- regular and non-regular
78 * files, by bytes or lines and from the beginning or end of the file.
79 *
80 * FBYTES	byte offset from the beginning of the file
81 *	REG	seek
82 *	NOREG	read, counting bytes
83 *
84 * FLINES	line offset from the beginning of the file
85 *	REG	read, counting lines
86 *	NOREG	read, counting lines
87 *
88 * RBYTES	byte offset from the end of the file
89 *	REG	seek
90 *	NOREG	cyclically read characters into a wrap-around buffer
91 *
92 * RLINES
93 *	REG	mmap the file and step back until reach the correct offset.
94 *	NOREG	cyclically read lines into a wrap-around array of buffers
95 */
96void
97forward(FILE *fp, const char *fn, enum STYLE style, off_t off, struct stat *sbp)
98{
99	int ch;
100
101	switch(style) {
102	case FBYTES:
103		if (off == 0)
104			break;
105		if (S_ISREG(sbp->st_mode) && sbp->st_size > 0) {
106			if (sbp->st_size < off)
107				off = sbp->st_size;
108			if (fseeko(fp, off, SEEK_SET) == -1) {
109				ierr(fn);
110				return;
111			}
112		} else while (off--)
113			if ((ch = getc(fp)) == EOF) {
114				if (ferror(fp)) {
115					ierr(fn);
116					return;
117				}
118				break;
119			}
120		break;
121	case FLINES:
122		if (off == 0)
123			break;
124		for (;;) {
125			if ((ch = getc(fp)) == EOF) {
126				if (ferror(fp)) {
127					ierr(fn);
128					return;
129				}
130				break;
131			}
132			if (ch == '\n' && !--off)
133				break;
134		}
135		break;
136	case RBYTES:
137		if (S_ISREG(sbp->st_mode) && sbp->st_size > 0) {
138			if (sbp->st_size >= off &&
139			    fseeko(fp, -off, SEEK_END) == -1) {
140				ierr(fn);
141				return;
142			}
143		} else if (off == 0) {
144			while (getc(fp) != EOF);
145			if (ferror(fp)) {
146				ierr(fn);
147				return;
148			}
149		} else
150			if (bytes(fp, fn, off))
151				return;
152		break;
153	case RLINES:
154		if (S_ISREG(sbp->st_mode) && sbp->st_size > 0)
155			if (!off) {
156				if (fseeko(fp, (off_t)0, SEEK_END) == -1) {
157					ierr(fn);
158					return;
159				}
160			} else
161				rlines(fp, fn, off, sbp);
162		else if (off == 0) {
163			while (getc(fp) != EOF);
164			if (ferror(fp)) {
165				ierr(fn);
166				return;
167			}
168		} else
169			if (lines(fp, fn, off))
170				return;
171		break;
172	default:
173		break;
174	}
175
176	while ((ch = getc(fp)) != EOF)
177		if (putchar(ch) == EOF)
178			oerr();
179	if (ferror(fp)) {
180		ierr(fn);
181		return;
182	}
183	(void)fflush(stdout);
184}
185
186/*
187 * rlines -- display the last offset lines of the file.
188 */
189static void
190rlines(FILE *fp, const char *fn, off_t off, struct stat *sbp)
191{
192	struct mapinfo map;
193	off_t curoff, size;
194	int i;
195
196	if (!(size = sbp->st_size))
197		return;
198	map.start = NULL;
199	map.fd = fileno(fp);
200	map.mapoff = map.maxoff = size;
201
202	/*
203	 * Last char is special, ignore whether newline or not. Note that
204	 * size == 0 is dealt with above, and size == 1 sets curoff to -1.
205	 */
206	curoff = size - 2;
207	while (curoff >= 0) {
208		if (curoff < map.mapoff && maparound(&map, curoff) != 0) {
209			ierr(fn);
210			return;
211		}
212		for (i = curoff - map.mapoff; i >= 0; i--)
213			if (map.start[i] == '\n' && --off == 0)
214				break;
215		/* `i' is either the map offset of a '\n', or -1. */
216		curoff = map.mapoff + i;
217		if (i >= 0)
218			break;
219	}
220	curoff++;
221	if (mapprint(&map, curoff, size - curoff) != 0) {
222		ierr(fn);
223		exit(1);
224	}
225
226	/* Set the file pointer to reflect the length displayed. */
227	if (fseeko(fp, sbp->st_size, SEEK_SET) == -1) {
228		ierr(fn);
229		return;
230	}
231	if (map.start != NULL && munmap(map.start, map.maplen)) {
232		ierr(fn);
233		return;
234	}
235}
236
237static int
238show(file_info_t *file)
239{
240	int ch;
241
242	while ((ch = getc(file->fp)) != EOF) {
243		if (last != file) {
244			if (vflag || (qflag == 0 && no_files > 1))
245				printfn(file->file_name, 1);
246			last = file;
247		}
248		if (putchar(ch) == EOF)
249			oerr();
250	}
251	(void)fflush(stdout);
252	if (ferror(file->fp)) {
253		fclose(file->fp);
254		file->fp = NULL;
255		ierr(file->file_name);
256		return 0;
257	}
258	clearerr(file->fp);
259	return 1;
260}
261
262static void
263set_events(file_info_t *files)
264{
265	int i, n = 0;
266	file_info_t *file;
267	struct timespec ts;
268	struct statfs sf;
269
270	ts.tv_sec = 0;
271	ts.tv_nsec = 0;
272
273	action = USE_KQUEUE;
274	for (i = 0, file = files; i < no_files; i++, file++) {
275		if (!file->fp)
276			continue;
277
278		if (fstatfs(fileno(file->fp), &sf) == 0 &&
279		    (sf.f_flags & MNT_LOCAL) == 0) {
280			action = USE_SLEEP;
281			return;
282		}
283
284		if (Fflag && fileno(file->fp) != STDIN_FILENO) {
285			EV_SET(&ev[n], fileno(file->fp), EVFILT_VNODE,
286			    EV_ADD | EV_ENABLE | EV_CLEAR,
287			    NOTE_DELETE | NOTE_RENAME, 0, 0);
288			n++;
289		}
290		EV_SET(&ev[n], fileno(file->fp), EVFILT_READ,
291		    EV_ADD | EV_ENABLE | EV_CLEAR, 0, 0, 0);
292		n++;
293	}
294
295	if (kevent(kq, ev, n, NULL, 0, &ts) < 0) {
296		action = USE_SLEEP;
297	}
298}
299
300/*
301 * follow -- display the file, from an offset, forward.
302 *
303 */
304void
305follow(file_info_t *files, enum STYLE style, off_t off)
306{
307	int active, ev_change, i, n;
308	struct stat sb2;
309	file_info_t *file;
310	FILE *ftmp;
311	struct timespec ts;
312
313	/* Position each of the files */
314	active = 0;
315	for (i = 0, file = files; i < no_files; i++, file++) {
316		if (!file->fp)
317			continue;
318		active = 1;
319		if (vflag || (qflag == 0 && no_files > 1))
320			printfn(file->file_name, 1);
321		forward(file->fp, file->file_name, style, off, &file->st);
322	}
323	if (!Fflag && !active)
324		return;
325
326	last = --file;
327
328	kq = kqueue();
329	if (kq < 0)
330		err(1, "kqueue");
331	/*
332	 * The number of kqueue events we track may vary over time and may
333	 * even grow past its initial value in the -F case, but it will
334	 * never exceed two per file, so just preallocate that.
335	 */
336	ev = malloc(no_files * 2 * sizeof(struct kevent));
337	if (ev == NULL)
338		err(1, "failed to allocate memory for kevents");
339	set_events(files);
340
341	for (;;) {
342		ev_change = 0;
343		if (Fflag) {
344			for (i = 0, file = files; i < no_files; i++, file++) {
345				if (!file->fp) {
346					file->fp =
347					    fileargs_fopen(fa, file->file_name,
348					    "r");
349					if (file->fp != NULL &&
350					    fstat(fileno(file->fp), &file->st)
351					    == -1) {
352						fclose(file->fp);
353						file->fp = NULL;
354					}
355					if (file->fp != NULL)
356						ev_change++;
357					continue;
358				}
359				if (fileno(file->fp) == STDIN_FILENO)
360					continue;
361				ftmp = fileargs_fopen(fa, file->file_name, "r");
362				if (ftmp == NULL ||
363				    fstat(fileno(ftmp), &sb2) == -1) {
364					if (errno != ENOENT)
365						ierr(file->file_name);
366					show(file);
367					if (file->fp != NULL) {
368						fclose(file->fp);
369						file->fp = NULL;
370					}
371					if (ftmp != NULL) {
372						fclose(ftmp);
373					}
374					ev_change++;
375					continue;
376				}
377
378				if (sb2.st_ino != file->st.st_ino ||
379				    sb2.st_dev != file->st.st_dev ||
380				    sb2.st_nlink == 0) {
381					show(file);
382					fclose(file->fp);
383					file->fp = ftmp;
384					memcpy(&file->st, &sb2,
385					    sizeof(struct stat));
386					ev_change++;
387				} else {
388					fclose(ftmp);
389				}
390			}
391		}
392
393		for (i = 0, file = files; i < no_files; i++, file++)
394			if (file->fp && !show(file))
395				ev_change++;
396
397		if (ev_change)
398			set_events(files);
399
400		switch (action) {
401		case USE_KQUEUE:
402			ts.tv_sec = 1;
403			ts.tv_nsec = 0;
404			/*
405			 * In the -F case we set a timeout to ensure that
406			 * we re-stat the file at least once every second.
407			 * If we've received EINTR, ignore it. Both reasons
408			 * for its generation are transient.
409			 */
410			do {
411				n = kevent(kq, NULL, 0, ev, 1, Fflag ? &ts : NULL);
412				if (n < 0 && errno != EINTR)
413					err(1, "kevent");
414			} while (n < 0);
415			if (n == 0) {
416				/* timeout */
417				break;
418			} else if (ev->filter == EVFILT_READ && ev->data < 0) {
419				/* file shrank, reposition to end */
420				if (lseek(ev->ident, (off_t)0, SEEK_END) == -1) {
421					ierr(file->file_name);
422					continue;
423				}
424			}
425			break;
426
427		case USE_SLEEP:
428			(void) usleep(250000);
429			break;
430		}
431	}
432}
433