1/*	$NetBSD: forward.c,v 1.30 2011/09/03 09:02:20 christos Exp $	*/
2
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#ifndef lint
37#if 0
38static char sccsid[] = "@(#)forward.c	8.1 (Berkeley) 6/6/93";
39#endif
40__RCSID("$NetBSD: forward.c,v 1.30 2011/09/03 09:02:20 christos Exp $");
41#endif /* not lint */
42
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 <limits.h>
50#include <fcntl.h>
51#include <errno.h>
52#include <unistd.h>
53#include <stdio.h>
54#include <stdlib.h>
55#include <string.h>
56#include "extern.h"
57
58static int rlines(FILE *, off_t, struct stat *);
59
60/* defines for inner loop actions */
61#define	USE_SLEEP	0
62#define	USE_KQUEUE	1
63#define	ADD_EVENTS	2
64
65/*
66 * forward -- display the file, from an offset, forward.
67 *
68 * There are eight separate cases for this -- regular and non-regular
69 * files, by bytes or lines and from the beginning or end of the file.
70 *
71 * FBYTES	byte offset from the beginning of the file
72 *	REG	seek
73 *	NOREG	read, counting bytes
74 *
75 * FLINES	line offset from the beginning of the file
76 *	REG	read, counting lines
77 *	NOREG	read, counting lines
78 *
79 * RBYTES	byte offset from the end of the file
80 *	REG	seek
81 *	NOREG	cyclically read characters into a wrap-around buffer
82 *
83 * RLINES
84 *	REG	mmap the file and step back until reach the correct offset.
85 *	NOREG	cyclically read lines into a wrap-around array of buffers
86 */
87void
88forward(FILE *fp, enum STYLE style, off_t off, struct stat *sbp)
89{
90	int ch, n;
91	int kq=-1, action=USE_SLEEP;
92	struct stat statbuf;
93	dev_t lastdev;
94	ino_t lastino;
95	struct kevent ev[2];
96
97	/* Keep track of file's previous incarnation. */
98	lastdev = sbp->st_dev;
99	lastino = sbp->st_ino;
100
101	switch(style) {
102	case FBYTES:
103		if (off == 0)
104			break;
105		if (S_ISREG(sbp->st_mode)) {
106			if (sbp->st_size < off)
107				off = sbp->st_size;
108			if (fseeko(fp, off, SEEK_SET) == -1) {
109				ierr();
110				return;
111			}
112		} else while (off--)
113			if ((ch = getc(fp)) == EOF) {
114				if (ferror(fp)) {
115					ierr();
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();
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)) {
138			if (sbp->st_size >= off &&
139			    fseeko(fp, -off, SEEK_END) == -1) {
140				ierr();
141				return;
142			}
143		} else if (off == 0) {
144			while (getc(fp) != EOF);
145			if (ferror(fp)) {
146				ierr();
147				return;
148			}
149		} else {
150			if (displaybytes(fp, off))
151				return;
152		}
153		break;
154	case RLINES:
155		if (S_ISREG(sbp->st_mode)) {
156			if (!off) {
157				if (fseek(fp, 0L, SEEK_END) == -1) {
158					ierr();
159					return;
160				}
161			} else {
162				if (rlines(fp, off, sbp))
163					return;
164			}
165		} else if (off == 0) {
166			while (getc(fp) != EOF);
167			if (ferror(fp)) {
168				ierr();
169				return;
170			}
171		} else {
172			if (displaylines(fp, off))
173				return;
174		}
175		break;
176	default:
177		break;
178	}
179
180	if (fflag) {
181		kq = kqueue();
182		if (kq < 0)
183			xerr(1, "kqueue");
184		action = ADD_EVENTS;
185	}
186
187	for (;;) {
188		while ((ch = getc(fp)) != EOF)  {
189			if (putchar(ch) == EOF)
190				oerr();
191		}
192		if (ferror(fp)) {
193			ierr();
194			return;
195		}
196		(void)fflush(stdout);
197		if (!fflag)
198			break;
199
200		clearerr(fp);
201
202		switch (action) {
203		case ADD_EVENTS:
204			n = 0;
205
206			memset(ev, 0, sizeof(ev));
207			if (fflag == 2 && fileno(fp) != STDIN_FILENO) {
208				EV_SET(&ev[n], fileno(fp), EVFILT_VNODE,
209				    EV_ADD | EV_ENABLE | EV_CLEAR,
210				    NOTE_DELETE | NOTE_RENAME, 0, 0);
211				n++;
212			}
213			EV_SET(&ev[n], fileno(fp), EVFILT_READ,
214			    EV_ADD | EV_ENABLE, 0, 0, 0);
215			n++;
216
217			if (kevent(kq, ev, n, NULL, 0, NULL) == -1) {
218				close(kq);
219				kq = -1;
220				action = USE_SLEEP;
221			} else {
222				action = USE_KQUEUE;
223			}
224			break;
225
226		case USE_KQUEUE:
227			if (kevent(kq, NULL, 0, ev, 1, NULL) == -1)
228				xerr(1, "kevent");
229
230			if (ev[0].filter == EVFILT_VNODE) {
231				/* file was rotated, wait until it reappears */
232				action = USE_SLEEP;
233			} else if (ev[0].data < 0) {
234				/* file shrank, reposition to end */
235				if (fseek(fp, 0L, SEEK_END) == -1) {
236					ierr();
237					return;
238				}
239			}
240			break;
241
242		case USE_SLEEP:
243			/*
244			 * We pause for one second after displaying any data
245			 * that has accumulated since we read the file.
246			 */
247                	(void) sleep(1);
248
249			if (fflag == 2 && fileno(fp) != STDIN_FILENO &&
250			    stat(fname, &statbuf) != -1) {
251				if (statbuf.st_ino != sbp->st_ino ||
252				    statbuf.st_dev != sbp->st_dev ||
253				    statbuf.st_rdev != sbp->st_rdev ||
254				    statbuf.st_nlink == 0) {
255					fp = freopen(fname, "r", fp);
256					if (fp == NULL) {
257						ierr();
258						goto out;
259					}
260					*sbp = statbuf;
261					if (kq != -1)
262						action = ADD_EVENTS;
263				} else if (kq != -1)
264					action = USE_KQUEUE;
265			}
266			break;
267		}
268	}
269out:
270	if (fflag && kq != -1)
271		close(kq);
272}
273
274/*
275 * rlines -- display the last offset lines of the file.
276 *
277 * Non-zero return means than a (non-fatal) error occurred.
278 */
279static int
280rlines(FILE *fp, off_t off, struct stat *sbp)
281{
282	off_t file_size;
283	off_t file_remaining;
284	char *p = NULL;
285	char *start = NULL;
286	off_t mmap_size;
287	off_t mmap_offset;
288	off_t mmap_remaining = 0;
289
290#define MMAP_MAXSIZE  (10 * 1024 * 1024)
291
292	if (!(file_size = sbp->st_size))
293		return 0;
294	file_remaining = file_size;
295
296	if (file_remaining > MMAP_MAXSIZE) {
297		mmap_size = MMAP_MAXSIZE;
298		mmap_offset = file_remaining - MMAP_MAXSIZE;
299	} else {
300		mmap_size = file_remaining;
301		mmap_offset = 0;
302	}
303
304	while (off) {
305		start = mmap(NULL, (size_t)mmap_size, PROT_READ,
306			     MAP_FILE|MAP_SHARED, fileno(fp), mmap_offset);
307		if (start == MAP_FAILED) {
308			xerr(0, "%s", fname);
309			return 1;
310		}
311
312		mmap_remaining = mmap_size;
313		/* Last char is special, ignore whether newline or not. */
314		for (p = start + mmap_remaining - 1 ; --mmap_remaining ; )
315			if (*--p == '\n' && !--off) {
316				++p;
317				break;
318			}
319
320		file_remaining -= mmap_size - mmap_remaining;
321
322		if (off == 0)
323			break;
324
325		if (file_remaining == 0)
326			break;
327
328		if (munmap(start, mmap_size)) {
329			xerr(0, "%s", fname);
330			return 1;
331		}
332
333		if (mmap_offset >= MMAP_MAXSIZE) {
334			mmap_offset -= MMAP_MAXSIZE;
335		} else {
336			mmap_offset = 0;
337			mmap_size = file_remaining;
338		}
339	}
340
341	/*
342	 * Output the (perhaps partial) data in this mmap'd block.
343	 */
344	WR(p, mmap_size - mmap_remaining);
345	file_remaining += mmap_size - mmap_remaining;
346	if (munmap(start, mmap_size)) {
347		xerr(0, "%s", fname);
348		return 1;
349	}
350
351	/*
352	 * Set the file pointer to reflect the length displayed.
353	 * This will cause the caller to redisplay the data if/when
354	 * needed.
355	 */
356	if (fseeko(fp, file_remaining, SEEK_SET) == -1) {
357		ierr();
358		return 1;
359	}
360	return 0;
361}
362