xref: /netbsd-src/usr.bin/tail/forward.c (revision 481fca6e59249d8ffcf24fef7cfbe7b131bfb080)
1 /*	$NetBSD: forward.c,v 1.16 1999/07/21 06:38:49 cgd 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.16 1999/07/21 06:38:49 cgd 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 size;
253 	char *p;
254 	char *start;
255 
256 	if (!(size = sbp->st_size))
257 		return (0);
258 
259 	if (size > SIZE_T_MAX) {
260 		err(0, "%s: %s", fname, strerror(EFBIG));
261 		return (1);
262 	}
263 
264 	if ((start = mmap(NULL, (size_t)size, PROT_READ,
265 	    MAP_FILE|MAP_SHARED, fileno(fp), (off_t)0)) == (caddr_t)-1) {
266 		err(0, "%s: %s", fname, strerror(EFBIG));
267 		return (1);
268 	}
269 
270 	/* Last char is special, ignore whether newline or not. */
271 	for (p = start + size - 1; --size;)
272 		if (*--p == '\n' && !--off) {
273 			++p;
274 			break;
275 		}
276 
277 	/* Set the file pointer to reflect the length displayed. */
278 	size = sbp->st_size - size;
279 	WR(p, size);
280 	if (fseek(fp, (long)sbp->st_size, SEEK_SET) == -1) {
281 		ierr();
282 		return (1);
283 	}
284 	if (munmap(start, (size_t)sbp->st_size)) {
285 		err(0, "%s: %s", fname, strerror(errno));
286 		return (1);
287 	}
288 	return (0);
289 }
290