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