xref: /netbsd-src/usr.bin/tail/forward.c (revision 08c81a9c2dc8c7300e893321eb65c0925d60871c)
1 /*	$NetBSD: forward.c,v 1.19 2002/06/14 00:47:41 wiz 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.19 2002/06/14 00:47:41 wiz 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 
52 #include <limits.h>
53 #include <fcntl.h>
54 #include <errno.h>
55 #include <unistd.h>
56 #include <stdio.h>
57 #include <stdlib.h>
58 #include <string.h>
59 #include "extern.h"
60 
61 static int rlines(FILE *, long, struct stat *);
62 
63 /*
64  * forward -- display the file, from an offset, forward.
65  *
66  * There are eight separate cases for this -- regular and non-regular
67  * files, by bytes or lines and from the beginning or end of the file.
68  *
69  * FBYTES	byte offset from the beginning of the file
70  *	REG	seek
71  *	NOREG	read, counting bytes
72  *
73  * FLINES	line offset from the beginning of the file
74  *	REG	read, counting lines
75  *	NOREG	read, counting lines
76  *
77  * RBYTES	byte offset from the end of the file
78  *	REG	seek
79  *	NOREG	cyclically read characters into a wrap-around buffer
80  *
81  * RLINES
82  *	REG	mmap the file and step back until reach the correct offset.
83  *	NOREG	cyclically read lines into a wrap-around array of buffers
84  */
85 void
86 forward(FILE *fp, enum STYLE style, long int off, struct stat *sbp)
87 {
88 	int ch;
89 	struct timeval second;
90 	int dostat = 0;
91 	struct stat statbuf;
92 	off_t lastsize = 0;
93 	dev_t lastdev;
94 	ino_t lastino;
95 
96 	/* Keep track of file's previous incarnation. */
97 	lastdev = sbp->st_dev;
98 	lastino = sbp->st_ino;
99 
100 	switch(style) {
101 	case FBYTES:
102 		if (off == 0)
103 			break;
104 		if (S_ISREG(sbp->st_mode)) {
105 			if (sbp->st_size < off)
106 				off = sbp->st_size;
107 			if (fseek(fp, off, SEEK_SET) == -1) {
108 				ierr();
109 				return;
110 			}
111 		} else while (off--)
112 			if ((ch = getc(fp)) == EOF) {
113 				if (ferror(fp)) {
114 					ierr();
115 					return;
116 				}
117 				break;
118 			}
119 		break;
120 	case FLINES:
121 		if (off == 0)
122 			break;
123 		for (;;) {
124 			if ((ch = getc(fp)) == EOF) {
125 				if (ferror(fp)) {
126 					ierr();
127 					return;
128 				}
129 				break;
130 			}
131 			if (ch == '\n' && !--off)
132 				break;
133 		}
134 		break;
135 	case RBYTES:
136 		if (S_ISREG(sbp->st_mode)) {
137 			if (sbp->st_size >= off &&
138 			    fseek(fp, -off, SEEK_END) == -1) {
139 				ierr();
140 				return;
141 			}
142 		} else if (off == 0) {
143 			while (getc(fp) != EOF);
144 			if (ferror(fp)) {
145 				ierr();
146 				return;
147 			}
148 		} else {
149 			if (bytes(fp, off))
150 				return;
151 		}
152 		break;
153 	case RLINES:
154 		if (S_ISREG(sbp->st_mode)) {
155 			if (!off) {
156 				if (fseek(fp, 0L, SEEK_END) == -1) {
157 					ierr();
158 					return;
159 				}
160 			} else {
161 				if (rlines(fp, off, sbp))
162 					return;
163 			}
164 		} else if (off == 0) {
165 			while (getc(fp) != EOF);
166 			if (ferror(fp)) {
167 				ierr();
168 				return;
169 			}
170 		} else {
171 			if (lines(fp, off))
172 				return;
173 		}
174 		break;
175 	default:
176 		break;
177 	}
178 
179 	for (;;) {
180 		while ((ch = getc(fp)) != EOF)  {
181 			if (putchar(ch) == EOF)
182 				oerr();
183 		}
184 		if (ferror(fp)) {
185 			ierr();
186 			return;
187 		}
188 		(void)fflush(stdout);
189 		if (!fflag)
190 			break;
191 		/*
192 		 * We pause for one second after displaying any data that has
193 		 * accumulated since we read the file.  Since sleep(3) takes
194 		 * eight system calls, use select() instead.
195 		 */
196 		second.tv_sec = 1;
197 		second.tv_usec = 0;
198 		if (select(0, NULL, NULL, NULL, &second) == -1)
199 			err(1, "select: %s", strerror(errno));
200 		clearerr(fp);
201 
202 		if (fflag == 1)
203 			continue;
204 		/*
205 		 * We restat the original filename every five seconds. If
206 		 * the size is ever smaller than the last time we read it,
207 		 * the file has probably been truncated; if the inode or
208 		 * or device number are different, it has been rotated.
209 		 * This causes us to close it, reopen it, and continue
210 		 * the tail -f. If stat returns an error (say, because
211 		 * the file has been removed), just continue with what
212 		 * we've got open now.
213 		 */
214 		if (dostat > 0)  {
215 			dostat -= 1;
216 		} else {
217 			dostat = 5;
218 			if (stat(fname, &statbuf) == 0)  {
219 				if (statbuf.st_dev != lastdev ||
220 				    statbuf.st_ino != lastino ||
221 				    statbuf.st_size < lastsize)  {
222 					lastdev = statbuf.st_dev;
223 					lastino = statbuf.st_ino;
224 					lastsize = 0;
225 					fclose(fp);
226 					if ((fp = fopen(fname, "r")) == NULL)
227 						err(1, "can't reopen %s: %s",
228 						    fname, strerror(errno));
229 				} else {
230 					lastsize = statbuf.st_size;
231 				}
232 			}
233 		}
234 	}
235 }
236 
237 /*
238  * rlines -- display the last offset lines of the file.
239  *
240  * Non-zero return means than a (non-fatal) error occurred.
241  */
242 static int
243 rlines(FILE *fp, long int off, struct stat *sbp)
244 {
245 	off_t file_size;
246 	off_t file_remaining;
247 	char *p;
248 	char *start;
249 	off_t mmap_size;
250 	off_t mmap_offset;
251 	off_t mmap_remaining;
252 
253 #define MMAP_MAXSIZE  (10 * 1024 * 1024)
254 
255 	if (!(file_size = sbp->st_size))
256 		return (0);
257 	file_remaining = file_size;
258 
259 	if (file_remaining > MMAP_MAXSIZE) {
260 		mmap_size = MMAP_MAXSIZE;
261 		mmap_offset = file_remaining - MMAP_MAXSIZE;
262 	} else {
263 		mmap_size = file_remaining;
264 		mmap_offset = 0;
265 	}
266 
267 	while (off) {
268 		start = mmap(NULL, (size_t)mmap_size, PROT_READ,
269 			     MAP_FILE|MAP_SHARED, fileno(fp), mmap_offset);
270 		if (start == MAP_FAILED) {
271 			err(0, "%s: %s", fname, strerror(EFBIG));
272 			return (1);
273 		}
274 
275 		mmap_remaining = mmap_size;
276 		/* Last char is special, ignore whether newline or not. */
277 		for (p = start + mmap_remaining - 1 ; --mmap_remaining ; )
278 			if (*--p == '\n' && !--off) {
279 				++p;
280 				break;
281 			}
282 
283 		file_remaining -= mmap_size - mmap_remaining;
284 
285 		if (off == 0)
286 			break;
287 
288 		if (file_remaining == 0)
289 			break;
290 
291 		if (munmap(start, mmap_size)) {
292 			err(0, "%s: %s", fname, strerror(errno));
293 			return (1);
294 		}
295 
296 		if (mmap_offset >= MMAP_MAXSIZE) {
297 			mmap_offset -= MMAP_MAXSIZE;
298 		} else {
299 			mmap_offset = 0;
300 			mmap_size = file_remaining;
301 		}
302 	}
303 
304 	/*
305 	 * Output the (perhaps partial) data in this mmap'd block.
306 	 */
307 	WR(p, mmap_size - mmap_remaining);
308 	file_remaining += mmap_size - mmap_remaining;
309 	if (munmap(start, mmap_size)) {
310 		err(0, "%s: %s", fname, strerror(errno));
311 		return (1);
312 	}
313 
314 	/*
315 	 * Set the file pointer to reflect the length displayed.
316 	 * This will cause the caller to redisplay the data if/when
317 	 * needed.
318 	 */
319 	if (fseeko(fp, file_remaining, SEEK_SET) == -1) {
320 		ierr();
321 		return (1);
322 	}
323 	return (0);
324 }
325