xref: /netbsd/usr.bin/tail/forward.c (revision c4a72b64)
1 /*	$NetBSD: forward.c,v 1.23 2002/10/30 21:48:50 jdolecek 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. All advertising materials mentioning features or use of this software
19  *    must display the following acknowledgement:
20  *	This product includes software developed by the University of
21  *	California, Berkeley and its contributors.
22  * 4. Neither the name of the University nor the names of its contributors
23  *    may be used to endorse or promote products derived from this software
24  *    without specific prior written permission.
25  *
26  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
27  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
30  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
35  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36  * SUCH DAMAGE.
37  */
38 
39 #include <sys/cdefs.h>
40 #ifndef lint
41 #if 0
42 static char sccsid[] = "@(#)forward.c	8.1 (Berkeley) 6/6/93";
43 #endif
44 __RCSID("$NetBSD: forward.c,v 1.23 2002/10/30 21:48:50 jdolecek Exp $");
45 #endif /* not lint */
46 
47 #include <sys/types.h>
48 #include <sys/stat.h>
49 #include <sys/time.h>
50 #include <sys/mman.h>
51 #include <sys/event.h>
52 
53 #include <limits.h>
54 #include <fcntl.h>
55 #include <errno.h>
56 #include <unistd.h>
57 #include <stdio.h>
58 #include <stdlib.h>
59 #include <string.h>
60 #include "extern.h"
61 
62 static int rlines(FILE *, long, struct stat *);
63 
64 /* defines for inner loop actions */
65 #define	USE_SLEEP	0
66 #define	USE_KQUEUE	1
67 #define	ADD_EVENTS	2
68 
69 /*
70  * forward -- display the file, from an offset, forward.
71  *
72  * There are eight separate cases for this -- regular and non-regular
73  * files, by bytes or lines and from the beginning or end of the file.
74  *
75  * FBYTES	byte offset from the beginning of the file
76  *	REG	seek
77  *	NOREG	read, counting bytes
78  *
79  * FLINES	line offset from the beginning of the file
80  *	REG	read, counting lines
81  *	NOREG	read, counting lines
82  *
83  * RBYTES	byte offset from the end of the file
84  *	REG	seek
85  *	NOREG	cyclically read characters into a wrap-around buffer
86  *
87  * RLINES
88  *	REG	mmap the file and step back until reach the correct offset.
89  *	NOREG	cyclically read lines into a wrap-around array of buffers
90  */
91 void
92 forward(FILE *fp, enum STYLE style, long int off, struct stat *sbp)
93 {
94 	int ch, n;
95 	int kq=-1, action=USE_SLEEP;
96 	struct stat statbuf;
97 	dev_t lastdev;
98 	ino_t lastino;
99 	struct kevent ev[2];
100 
101 	/* Keep track of file's previous incarnation. */
102 	lastdev = sbp->st_dev;
103 	lastino = sbp->st_ino;
104 
105 	switch(style) {
106 	case FBYTES:
107 		if (off == 0)
108 			break;
109 		if (S_ISREG(sbp->st_mode)) {
110 			if (sbp->st_size < off)
111 				off = sbp->st_size;
112 			if (fseek(fp, off, SEEK_SET) == -1) {
113 				ierr();
114 				return;
115 			}
116 		} else while (off--)
117 			if ((ch = getc(fp)) == EOF) {
118 				if (ferror(fp)) {
119 					ierr();
120 					return;
121 				}
122 				break;
123 			}
124 		break;
125 	case FLINES:
126 		if (off == 0)
127 			break;
128 		for (;;) {
129 			if ((ch = getc(fp)) == EOF) {
130 				if (ferror(fp)) {
131 					ierr();
132 					return;
133 				}
134 				break;
135 			}
136 			if (ch == '\n' && !--off)
137 				break;
138 		}
139 		break;
140 	case RBYTES:
141 		if (S_ISREG(sbp->st_mode)) {
142 			if (sbp->st_size >= off &&
143 			    fseek(fp, -off, SEEK_END) == -1) {
144 				ierr();
145 				return;
146 			}
147 		} else if (off == 0) {
148 			while (getc(fp) != EOF);
149 			if (ferror(fp)) {
150 				ierr();
151 				return;
152 			}
153 		} else {
154 			if (bytes(fp, off))
155 				return;
156 		}
157 		break;
158 	case RLINES:
159 		if (S_ISREG(sbp->st_mode)) {
160 			if (!off) {
161 				if (fseek(fp, 0L, SEEK_END) == -1) {
162 					ierr();
163 					return;
164 				}
165 			} else {
166 				if (rlines(fp, off, sbp))
167 					return;
168 			}
169 		} else if (off == 0) {
170 			while (getc(fp) != EOF);
171 			if (ferror(fp)) {
172 				ierr();
173 				return;
174 			}
175 		} else {
176 			if (lines(fp, off))
177 				return;
178 		}
179 		break;
180 	default:
181 		break;
182 	}
183 
184 	if (fflag) {
185 		kq = kqueue();
186 		if (kq < 0)
187 			err(1, "kqueue");
188 		action = ADD_EVENTS;
189 	}
190 
191 	for (;;) {
192 		while ((ch = getc(fp)) != EOF)  {
193 			if (putchar(ch) == EOF)
194 				oerr();
195 		}
196 		if (ferror(fp)) {
197 			ierr();
198 			return;
199 		}
200 		(void)fflush(stdout);
201 		if (!fflag)
202 			break;
203 
204 		clearerr(fp);
205 
206 		switch (action) {
207 		case ADD_EVENTS:
208 			n = 0;
209 
210 			memset(ev, 0, sizeof(ev));
211 			if (fflag == 2 && fileno(fp) != STDIN_FILENO) {
212 				EV_SET(&ev[n], fileno(fp), EVFILT_VNODE,
213 					EV_ADD | EV_ENABLE | EV_CLEAR,
214 					NOTE_DELETE | NOTE_RENAME, 0, 0);
215 				n++;
216 			}
217 			EV_SET(&ev[n], fileno(fp), EVFILT_READ,
218 				EV_ADD | EV_ENABLE, 0, 0, 0);
219 			n++;
220 
221 			if (kevent(kq, ev, n, NULL, 0, NULL) < 0) {
222 				close(kq);
223 				kq = -1;
224 				action = USE_SLEEP;
225 			} else {
226 				action = USE_KQUEUE;
227 			}
228 			break;
229 
230 		case USE_KQUEUE:
231 			if (kevent(kq, NULL, 0, ev, 1, NULL) < 0)
232 				err(1, "kevent");
233 
234 			if (ev[0].filter == EVFILT_VNODE) {
235 				/* file was rotated, wait until it reappears */
236 				action = USE_SLEEP;
237 			} else if (ev[0].data < 0) {
238 				/* file shrank, reposition to end */
239 				if (fseek(fp, 0L, SEEK_END) == -1) {
240 					ierr();
241 					return;
242 				}
243 			}
244 			break;
245 
246 		case USE_SLEEP:
247 			/*
248 			 * We pause for one second after displaying any data
249 			 * that has accumulated since we read the file.
250 			 */
251                 	(void) usleep(1000000);
252 
253 			if (fflag == 2 && fileno(fp) != STDIN_FILENO &&
254 			    stat(fname, &statbuf) != -1) {
255 				if (statbuf.st_ino != sbp->st_ino ||
256 				    statbuf.st_dev != sbp->st_dev ||
257 				    statbuf.st_rdev != sbp->st_rdev ||
258 				    statbuf.st_nlink == 0) {
259 					fp = freopen(fname, "r", fp);
260 					if (fp == NULL) {
261 						ierr();
262 						break;
263 					}
264 					*sbp = statbuf;
265 					if (kq != -1)
266 						action = ADD_EVENTS;
267 				} else if (kq != -1)
268 					action = USE_KQUEUE;
269 			}
270 			break;
271 		}
272 	}
273 
274 	if (fflag && kq != -1)
275 		close(kq);
276 }
277 
278 /*
279  * rlines -- display the last offset lines of the file.
280  *
281  * Non-zero return means than a (non-fatal) error occurred.
282  */
283 static int
284 rlines(FILE *fp, long int off, struct stat *sbp)
285 {
286 	off_t file_size;
287 	off_t file_remaining;
288 	char *p;
289 	char *start;
290 	off_t mmap_size;
291 	off_t mmap_offset;
292 	off_t mmap_remaining;
293 
294 #define MMAP_MAXSIZE  (10 * 1024 * 1024)
295 
296 	if (!(file_size = sbp->st_size))
297 		return (0);
298 	file_remaining = file_size;
299 
300 	if (file_remaining > MMAP_MAXSIZE) {
301 		mmap_size = MMAP_MAXSIZE;
302 		mmap_offset = file_remaining - MMAP_MAXSIZE;
303 	} else {
304 		mmap_size = file_remaining;
305 		mmap_offset = 0;
306 	}
307 
308 	while (off) {
309 		start = mmap(NULL, (size_t)mmap_size, PROT_READ,
310 			     MAP_FILE|MAP_SHARED, fileno(fp), mmap_offset);
311 		if (start == MAP_FAILED) {
312 			err(0, "%s: %s", fname, strerror(EFBIG));
313 			return (1);
314 		}
315 
316 		mmap_remaining = mmap_size;
317 		/* Last char is special, ignore whether newline or not. */
318 		for (p = start + mmap_remaining - 1 ; --mmap_remaining ; )
319 			if (*--p == '\n' && !--off) {
320 				++p;
321 				break;
322 			}
323 
324 		file_remaining -= mmap_size - mmap_remaining;
325 
326 		if (off == 0)
327 			break;
328 
329 		if (file_remaining == 0)
330 			break;
331 
332 		if (munmap(start, mmap_size)) {
333 			err(0, "%s: %s", fname, strerror(errno));
334 			return (1);
335 		}
336 
337 		if (mmap_offset >= MMAP_MAXSIZE) {
338 			mmap_offset -= MMAP_MAXSIZE;
339 		} else {
340 			mmap_offset = 0;
341 			mmap_size = file_remaining;
342 		}
343 	}
344 
345 	/*
346 	 * Output the (perhaps partial) data in this mmap'd block.
347 	 */
348 	WR(p, mmap_size - mmap_remaining);
349 	file_remaining += mmap_size - mmap_remaining;
350 	if (munmap(start, mmap_size)) {
351 		err(0, "%s: %s", fname, strerror(errno));
352 		return (1);
353 	}
354 
355 	/*
356 	 * Set the file pointer to reflect the length displayed.
357 	 * This will cause the caller to redisplay the data if/when
358 	 * needed.
359 	 */
360 	if (fseeko(fp, file_remaining, SEEK_SET) == -1) {
361 		ierr();
362 		return (1);
363 	}
364 	return (0);
365 }
366