xref: /netbsd-src/usr.bin/tail/forward.c (revision 37b34d511dea595d3ba03a661cf3b775038ea5f8)
1 /*	$NetBSD: forward.c,v 1.22 2002/09/18 19:29:12 skrll 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.22 2002/09/18 19:29:12 skrll 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 timespec 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.
194 		 */
195 		second.tv_sec = 1;
196 		second.tv_nsec = 0;
197 		if (nanosleep(&second, NULL) == -1)
198 			err(1, "nanosleep: %s", strerror(errno));
199 		clearerr(fp);
200 
201 		if (fflag == 1)
202 			continue;
203 		/*
204 		 * We restat the original filename every five seconds. If
205 		 * the size is ever smaller than the last time we read it,
206 		 * the file has probably been truncated; if the inode or
207 		 * or device number are different, it has been rotated.
208 		 * This causes us to close it, reopen it, and continue
209 		 * the tail -f. If stat returns an error (say, because
210 		 * the file has been removed), just continue with what
211 		 * we've got open now.
212 		 */
213 		if (dostat > 0)  {
214 			dostat -= 1;
215 		} else {
216 			dostat = 5;
217 			if (stat(fname, &statbuf) == 0)  {
218 				if (statbuf.st_dev != lastdev ||
219 				    statbuf.st_ino != lastino ||
220 				    statbuf.st_size < lastsize)  {
221 					lastdev = statbuf.st_dev;
222 					lastino = statbuf.st_ino;
223 					lastsize = 0;
224 					fclose(fp);
225 					if ((fp = fopen(fname, "r")) == NULL)
226 						err(1, "can't reopen %s: %s",
227 						    fname, strerror(errno));
228 				} else {
229 					lastsize = statbuf.st_size;
230 				}
231 			}
232 		}
233 	}
234 }
235 
236 /*
237  * rlines -- display the last offset lines of the file.
238  *
239  * Non-zero return means than a (non-fatal) error occurred.
240  */
241 static int
242 rlines(FILE *fp, long int off, struct stat *sbp)
243 {
244 	off_t file_size;
245 	off_t file_remaining;
246 	char *p;
247 	char *start;
248 	off_t mmap_size;
249 	off_t mmap_offset;
250 	off_t mmap_remaining;
251 
252 #define MMAP_MAXSIZE  (10 * 1024 * 1024)
253 
254 	if (!(file_size = sbp->st_size))
255 		return (0);
256 	file_remaining = file_size;
257 
258 	if (file_remaining > MMAP_MAXSIZE) {
259 		mmap_size = MMAP_MAXSIZE;
260 		mmap_offset = file_remaining - MMAP_MAXSIZE;
261 	} else {
262 		mmap_size = file_remaining;
263 		mmap_offset = 0;
264 	}
265 
266 	while (off) {
267 		start = mmap(NULL, (size_t)mmap_size, PROT_READ,
268 			     MAP_FILE|MAP_SHARED, fileno(fp), mmap_offset);
269 		if (start == MAP_FAILED) {
270 			err(0, "%s: %s", fname, strerror(EFBIG));
271 			return (1);
272 		}
273 
274 		mmap_remaining = mmap_size;
275 		/* Last char is special, ignore whether newline or not. */
276 		for (p = start + mmap_remaining - 1 ; --mmap_remaining ; )
277 			if (*--p == '\n' && !--off) {
278 				++p;
279 				break;
280 			}
281 
282 		file_remaining -= mmap_size - mmap_remaining;
283 
284 		if (off == 0)
285 			break;
286 
287 		if (file_remaining == 0)
288 			break;
289 
290 		if (munmap(start, mmap_size)) {
291 			err(0, "%s: %s", fname, strerror(errno));
292 			return (1);
293 		}
294 
295 		if (mmap_offset >= MMAP_MAXSIZE) {
296 			mmap_offset -= MMAP_MAXSIZE;
297 		} else {
298 			mmap_offset = 0;
299 			mmap_size = file_remaining;
300 		}
301 	}
302 
303 	/*
304 	 * Output the (perhaps partial) data in this mmap'd block.
305 	 */
306 	WR(p, mmap_size - mmap_remaining);
307 	file_remaining += mmap_size - mmap_remaining;
308 	if (munmap(start, mmap_size)) {
309 		err(0, "%s: %s", fname, strerror(errno));
310 		return (1);
311 	}
312 
313 	/*
314 	 * Set the file pointer to reflect the length displayed.
315 	 * This will cause the caller to redisplay the data if/when
316 	 * needed.
317 	 */
318 	if (fseeko(fp, file_remaining, SEEK_SET) == -1) {
319 		ierr();
320 		return (1);
321 	}
322 	return (0);
323 }
324