xref: /netbsd-src/usr.bin/tail/forward.c (revision a536ee5124e62c9a0051a252f7833dc8f50f44c9)
1 /*	$NetBSD: forward.c,v 1.31 2011/09/03 10:59:10 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
38 static char sccsid[] = "@(#)forward.c	8.1 (Berkeley) 6/6/93";
39 #endif
40 __RCSID("$NetBSD: forward.c,v 1.31 2011/09/03 10:59:10 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 
58 static 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  */
87 void
88 forward(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 	}
269 out:
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  */
279 static int
280 rlines(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