forward.c revision 1.19
1/*	$OpenBSD: forward.c,v 1.19 2004/03/01 16:35:05 otto 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#ifndef lint
37#if 0
38static char sccsid[] = "@(#)forward.c	8.1 (Berkeley) 6/6/93";
39#endif
40static char rcsid[] = "$OpenBSD: forward.c,v 1.19 2004/03/01 16:35:05 otto Exp $";
41#endif /* not lint */
42
43#include <sys/types.h>
44#include <sys/stat.h>
45#include <sys/mman.h>
46#include <sys/event.h>
47
48#include <err.h>
49#include <errno.h>
50#include <fcntl.h>
51#include <limits.h>
52#include <stdio.h>
53#include <stdlib.h>
54#include <string.h>
55#include <unistd.h>
56
57#include "extern.h"
58
59static int rlines(FILE *, off_t, struct stat *);
60
61/*
62 * forward -- display the file, from an offset, forward.
63 *
64 * There are eight separate cases for this -- regular and non-regular
65 * files, by bytes or lines and from the beginning or end of the file.
66 *
67 * FBYTES	byte offset from the beginning of the file
68 *	REG	seek
69 *	NOREG	read, counting bytes
70 *
71 * FLINES	line offset from the beginning of the file
72 *	REG	read, counting lines
73 *	NOREG	read, counting lines
74 *
75 * RBYTES	byte offset from the end of the file
76 *	REG	seek
77 *	NOREG	cyclically read characters into a wrap-around buffer
78 *
79 * RLINES
80 *	REG	step back until the correct offset is reached.
81 *	NOREG	cyclically read lines into a wrap-around array of buffers
82 */
83void
84forward(fp, style, off, sbp)
85	FILE *fp;
86	enum STYLE style;
87	off_t off;
88	struct stat *sbp;
89{
90	int ch;
91	struct stat nsb;
92	int kq;
93	struct kevent ke;
94
95	switch(style) {
96	case FBYTES:
97		if (off == 0)
98			break;
99		if (S_ISREG(sbp->st_mode)) {
100			if (sbp->st_size < off)
101				off = sbp->st_size;
102			if (fseeko(fp, off, SEEK_SET) == -1) {
103				ierr();
104				return;
105			}
106		} else while (off--)
107			if ((ch = getc(fp)) == EOF) {
108				if (ferror(fp)) {
109					ierr();
110					return;
111				}
112				break;
113			}
114		break;
115	case FLINES:
116		if (off == 0)
117			break;
118		for (;;) {
119			if ((ch = getc(fp)) == EOF) {
120				if (ferror(fp)) {
121					ierr();
122					return;
123				}
124				break;
125			}
126			if (ch == '\n' && !--off)
127				break;
128		}
129		break;
130	case RBYTES:
131		if (S_ISREG(sbp->st_mode)) {
132			if (sbp->st_size >= off &&
133			    fseeko(fp, -off, SEEK_END) == -1) {
134				ierr();
135				return;
136			}
137		} else if (off == 0) {
138			while (getc(fp) != EOF)
139				;
140			if (ferror(fp)) {
141				ierr();
142				return;
143			}
144		} else {
145			if (bytes(fp, off))
146				return;
147		}
148		break;
149	case RLINES:
150		if (S_ISREG(sbp->st_mode)) {
151			if (!off) {
152				if (fseeko(fp, (off_t)0, SEEK_END) == -1) {
153					ierr();
154					return;
155				}
156			} else if (rlines(fp, off, sbp) != 0)
157				lines(fp, off);
158		} else if (off == 0) {
159			while (getc(fp) != EOF)
160				;
161			if (ferror(fp)) {
162				ierr();
163				return;
164			}
165		} else {
166			if (lines(fp, off))
167				return;
168		}
169		break;
170	}
171
172	kq = -1;
173kq_retry:
174	if (fflag && ((kq = kqueue()) >= 0)) {
175		ke.ident = fileno(fp);
176		ke.flags = EV_ENABLE|EV_ADD|EV_CLEAR;
177		ke.filter = EVFILT_READ;
178		ke.fflags = ke.data = 0;
179		ke.udata = NULL;
180		if (kevent(kq, &ke, 1, NULL, 0, NULL) < 0) {
181			close(kq);
182			kq = -1;
183		} else if (S_ISREG(sbp->st_mode)) {
184			ke.ident = fileno(fp);
185			ke.flags = EV_ENABLE|EV_ADD|EV_CLEAR;
186			ke.filter = EVFILT_VNODE;
187			ke.fflags = NOTE_DELETE | NOTE_RENAME | NOTE_TRUNCATE;
188			if (kevent(kq, &ke, 1, NULL, 0, NULL) < 0) {
189				close(kq);
190				kq = -1;
191			}
192		}
193	}
194
195	for (;;) {
196		while (!feof(fp) && (ch = getc(fp)) != EOF)
197			if (putchar(ch) == EOF)
198				oerr();
199		if (ferror(fp)) {
200			ierr();
201			if (kq != -1)
202				close(kq);
203			return;
204		}
205		(void)fflush(stdout);
206		if (!fflag)
207			break;
208		clearerr(fp);
209		if (kq < 0 || kevent(kq, NULL, 0, &ke, 1, NULL) <= 0) {
210			sleep(1);
211		} else if (ke.filter == EVFILT_READ) {
212			continue;
213		} else if ((ke.fflags & NOTE_TRUNCATE) == 0) {
214			/*
215			 * File was renamed or deleted.
216			 *
217			 * Continue to look at it until a new file reappears
218			 * with the same name.
219			 * Fall back to the old algorithm for that.
220			 */
221			close(kq);
222			kq = -1;
223		}
224
225		if (is_stdin || stat(fname, &nsb) != 0)
226			continue;
227		/* Reopen file if the inode changes or file was truncated */
228		if (nsb.st_ino != sbp->st_ino) {
229			warnx("%s has been replaced, reopening.", fname);
230			if ((fp = freopen(fname, "r", fp)) == NULL) {
231				ierr();
232				if (kq >= 0)
233					close(kq);
234				return;
235			}
236			(void)memcpy(sbp, &nsb, sizeof(nsb));
237			goto kq_retry;
238		} else if (ke.fflags & NOTE_TRUNCATE) {
239			warnx("%s has been truncated, resetting.", fname);
240			fpurge(fp);
241			rewind(fp);
242		}
243		(void)memcpy(sbp, &nsb, sizeof(nsb));
244	}
245	if (kq >= 0)
246		close(kq);
247}
248
249/*
250 * rlines -- display the last offset lines of the file.
251 */
252static int
253rlines(FILE *fp, off_t off, struct stat *sbp)
254{
255	off_t pos;
256	int ch;
257
258	pos = sbp->st_size;
259	if (pos == 0)
260		return (0);
261
262	/*
263	 * Position before char.
264	 * Last char is special, ignore it whether newline or not.
265	 */
266	pos -= 2;
267	ch = EOF;
268	for (; off > 0 && pos >= 0; pos--) {
269		/* A seek per char isn't a problem with a smart stdio */
270		if (fseeko(fp, pos, SEEK_SET) == -1) {
271			ierr();
272			return (1);
273		}
274		if ((ch = getc(fp)) == '\n')
275			off--;
276		else if (ch == EOF) {
277			if (ferror(fp)) {
278				ierr();
279				return (1);
280			}
281			break;
282		}
283	}
284	/* If we read until start of file, put back last read char */
285	if (pos < 0 && off > 0 && ch != EOF && ungetc(ch, fp) == EOF) {
286		ierr();
287		return (1);
288	}
289
290	while (!feof(fp) && (ch = getc(fp)) != EOF)
291		if (putchar(ch) == EOF)
292			oerr();
293	if (ferror(fp)) {
294		ierr();
295		return (1);
296	}
297
298	return (0);
299}
300