xref: /openbsd-src/usr.bin/tail/forward.c (revision db3296cf5c1dd9058ceecc3a29fe4aaa0bd26000)
1 /*	$OpenBSD: forward.c,v 1.17 2003/07/14 08:06:06 otto Exp $	*/
2 /*	$NetBSD: forward.c,v 1.7 1996/02/13 16:49:10 ghudson Exp $	*/
3 
4 /*-
5  * Copyright (c) 1991, 1993
6  *	The Regents of the University of California.  All rights reserved.
7  *
8  * This code is derived from software contributed to Berkeley by
9  * Edward Sze-Tyan Wang.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  * 3. Neither the name of the University nor the names of its contributors
20  *    may be used to endorse or promote products derived from this software
21  *    without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  */
35 
36 #ifndef lint
37 #if 0
38 static char sccsid[] = "@(#)forward.c	8.1 (Berkeley) 6/6/93";
39 #endif
40 static char rcsid[] = "$OpenBSD: forward.c,v 1.17 2003/07/14 08:06:06 otto Exp $";
41 #endif /* not lint */
42 
43 #include <sys/types.h>
44 #include <sys/stat.h>
45 #include <sys/mman.h>
46 #include <sys/event.h>
47 
48 #include <err.h>
49 #include <errno.h>
50 #include <fcntl.h>
51 #include <limits.h>
52 #include <stdio.h>
53 #include <stdlib.h>
54 #include <string.h>
55 #include <unistd.h>
56 
57 #include "extern.h"
58 
59 static int rlines(FILE *, long, struct stat *);
60 
61 /*
62  * forward -- display the file, from an offset, forward.
63  *
64  * There are eight separate cases for this -- regular and non-regular
65  * files, by bytes or lines and from the beginning or end of the file.
66  *
67  * FBYTES	byte offset from the beginning of the file
68  *	REG	seek
69  *	NOREG	read, counting bytes
70  *
71  * FLINES	line offset from the beginning of the file
72  *	REG	read, counting lines
73  *	NOREG	read, counting lines
74  *
75  * RBYTES	byte offset from the end of the file
76  *	REG	seek
77  *	NOREG	cyclically read characters into a wrap-around buffer
78  *
79  * RLINES
80  *	REG	step back until the correct offset is reached.
81  *	NOREG	cyclically read lines into a wrap-around array of buffers
82  */
83 void
84 forward(fp, style, off, sbp)
85 	FILE *fp;
86 	enum STYLE style;
87 	long off;
88 	struct stat *sbp;
89 {
90 	int ch;
91 	struct stat nsb;
92 	int kq;
93 	struct kevent ke;
94 
95 	switch(style) {
96 	case FBYTES:
97 		if (off == 0)
98 			break;
99 		if (S_ISREG(sbp->st_mode)) {
100 			if (sbp->st_size < off)
101 				off = sbp->st_size;
102 			if (fseek(fp, off, SEEK_SET) == -1) {
103 				ierr();
104 				return;
105 			}
106 		} else while (off--)
107 			if ((ch = getc(fp)) == EOF) {
108 				if (ferror(fp)) {
109 					ierr();
110 					return;
111 				}
112 				break;
113 			}
114 		break;
115 	case FLINES:
116 		if (off == 0)
117 			break;
118 		for (;;) {
119 			if ((ch = getc(fp)) == EOF) {
120 				if (ferror(fp)) {
121 					ierr();
122 					return;
123 				}
124 				break;
125 			}
126 			if (ch == '\n' && !--off)
127 				break;
128 		}
129 		break;
130 	case RBYTES:
131 		if (S_ISREG(sbp->st_mode)) {
132 			if (sbp->st_size >= off &&
133 			    fseek(fp, -off, SEEK_END) == -1) {
134 				ierr();
135 				return;
136 			}
137 		} else if (off == 0) {
138 			while (getc(fp) != EOF)
139 				;
140 			if (ferror(fp)) {
141 				ierr();
142 				return;
143 			}
144 		} else {
145 			if (bytes(fp, off))
146 				return;
147 		}
148 		break;
149 	case RLINES:
150 		if (S_ISREG(sbp->st_mode)) {
151 			if (!off) {
152 				if (fseek(fp, 0L, SEEK_END) == -1) {
153 					ierr();
154 					return;
155 				}
156 			} else if (rlines(fp, off, sbp) != 0)
157 				lines(fp, off);
158 		} else if (off == 0) {
159 			while (getc(fp) != EOF)
160 				;
161 			if (ferror(fp)) {
162 				ierr();
163 				return;
164 			}
165 		} else {
166 			if (lines(fp, off))
167 				return;
168 		}
169 		break;
170 	}
171 
172 	kq = -1;
173 kq_retry:
174 	if (fflag && ((kq = kqueue()) >= 0)) {
175 		ke.ident = fileno(fp);
176 		ke.flags = EV_ENABLE|EV_ADD|EV_CLEAR;
177 		ke.filter = EVFILT_READ;
178 		ke.fflags = ke.data = 0;
179 		ke.udata = NULL;
180 		if (kevent(kq, &ke, 1, NULL, 0, NULL) < 0) {
181 			close(kq);
182 			kq = -1;
183 		} else if (S_ISREG(sbp->st_mode)) {
184 			ke.ident = fileno(fp);
185 			ke.flags = EV_ENABLE|EV_ADD|EV_CLEAR;
186 			ke.filter = EVFILT_VNODE;
187 			ke.fflags = NOTE_DELETE | NOTE_RENAME;
188 			if (kevent(kq, &ke, 1, NULL, 0, NULL) < 0) {
189 				close(kq);
190 				kq = -1;
191 			}
192 		}
193 	}
194 
195 	for (;;) {
196 		while (!feof(fp) && (ch = getc(fp)) != EOF)
197 			if (putchar(ch) == EOF)
198 				oerr();
199 		if (ferror(fp)) {
200 			ierr();
201 			if (kq != -1)
202 				close(kq);
203 			return;
204 		}
205 		(void)fflush(stdout);
206 		if (!fflag)
207 			break;
208 		clearerr(fp);
209 		if (kq < 0 || kevent(kq, NULL, 0, &ke, 1, NULL) <= 0) {
210 			sleep(1);
211 		} else if (ke.filter == EVFILT_READ) {
212 			continue;
213 		} else {
214 			/*
215 			 * File was renamed or deleted.
216 			 *
217 			 * Continue to look at it until a new file reappears
218 			 * with the same name.
219 			 * Fall back to the old algorithm for that.
220 			 */
221 			close(kq);
222 			kq = -1;
223 		}
224 
225 		if (is_stdin || stat(fname, &nsb) != 0)
226 			continue;
227 		/* Reopen file if the inode changes or file was truncated */
228 		if (nsb.st_ino != sbp->st_ino) {
229 			warnx("%s has been replaced, reopening.", fname);
230 			if ((fp = freopen(fname, "r", fp)) == NULL) {
231 				ierr();
232 				if (kq >= 0)
233 					close(kq);
234 				return;
235 			}
236 			(void)memcpy(sbp, &nsb, sizeof(nsb));
237 			goto kq_retry;
238 		} else if (nsb.st_size < sbp->st_size) {
239 			warnx("%s has been truncated, resetting.", fname);
240 			rewind(fp);
241 			(void)memcpy(sbp, &nsb, sizeof(nsb));
242 		}
243 	}
244 	if (kq >= 0)
245 		close(kq);
246 }
247 
248 /*
249  * rlines -- display the last offset lines of the file.
250  */
251 static int
252 rlines(FILE *fp, long off, struct stat *sbp)
253 {
254 	off_t pos;
255 	int ch;
256 
257 	pos = sbp->st_size;
258 	if (pos == 0)
259 		return (0);
260 
261 	/*
262 	 * Position before char.
263 	 * Last char is special, ignore it whether newline or not.
264 	 */
265 	pos -= 2;
266 	ch = EOF;
267 	for (; off > 0 && pos >= 0; pos--) {
268 		/* A seek per char isn't a problem with a smart stdio */
269 		if (fseeko(fp, pos, SEEK_SET) == -1) {
270 			ierr();
271 			return (1);
272 		}
273 		if ((ch = getc(fp)) == '\n')
274 			off--;
275 		else if (ch == EOF) {
276 			if (ferror(fp)) {
277 				ierr();
278 				return (1);
279 			}
280 			break;
281 		}
282 	}
283 	/* If we read until start of file, put back last read char */
284 	if (pos < 0 && off > 0 && ch != EOF && ungetc(ch, fp) == EOF) {
285 		ierr();
286 		return (1);
287 	}
288 
289 	while (!feof(fp) && (ch = getc(fp)) != EOF)
290 		if (putchar(ch) == EOF)
291 			oerr();
292 	if (ferror(fp)) {
293 		ierr();
294 		return (1);
295 	}
296 
297 	return (0);
298 }
299