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