xref: /openbsd/usr.bin/tail/forward.c (revision 404b540a)
1 /*	$OpenBSD: forward.c,v 1.25 2008/11/13 18:33:03 landry 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.25 2008/11/13 18:33:03 landry Exp $";
41 #endif /* not lint */
42 
43 #include <sys/types.h>
44 #include <sys/stat.h>
45 #include <sys/event.h>
46 
47 #include <err.h>
48 #include <stdio.h>
49 #include <string.h>
50 #include <unistd.h>
51 
52 #include "extern.h"
53 
54 static int rlines(FILE *, off_t, struct stat *);
55 
56 /*
57  * forward -- display the file, from an offset, forward.
58  *
59  * There are eight separate cases for this -- regular and non-regular
60  * files, by bytes or lines and from the beginning or end of the file.
61  *
62  * FBYTES	byte offset from the beginning of the file
63  *	REG	seek
64  *	NOREG	read, counting bytes
65  *
66  * FLINES	line offset from the beginning of the file
67  *	REG	read, counting lines
68  *	NOREG	read, counting lines
69  *
70  * RBYTES	byte offset from the end of the file
71  *	REG	seek
72  *	NOREG	cyclically read characters into a wrap-around buffer
73  *
74  * RLINES
75  *	REG	step back until the correct offset is reached.
76  *	NOREG	cyclically read lines into a wrap-around array of buffers
77  */
78 void
79 forward(FILE *fp, enum STYLE style, off_t off, struct stat *sbp)
80 {
81 	int ch;
82 	struct stat nsb;
83 	int kq, queue;
84 	struct kevent ke;
85 
86 	switch(style) {
87 	case FBYTES:
88 		if (off == 0)
89 			break;
90 		if (S_ISREG(sbp->st_mode)) {
91 			if (sbp->st_size < off)
92 				off = sbp->st_size;
93 			if (fseeko(fp, off, SEEK_SET) == -1) {
94 				ierr();
95 				return;
96 			}
97 		} else while (off--)
98 			if ((ch = getc(fp)) == EOF) {
99 				if (ferror(fp)) {
100 					ierr();
101 					return;
102 				}
103 				break;
104 			}
105 		break;
106 	case FLINES:
107 		if (off == 0)
108 			break;
109 		for (;;) {
110 			if ((ch = getc(fp)) == EOF) {
111 				if (ferror(fp)) {
112 					ierr();
113 					return;
114 				}
115 				break;
116 			}
117 			if (ch == '\n' && !--off)
118 				break;
119 		}
120 		break;
121 	case RBYTES:
122 		if (S_ISREG(sbp->st_mode)) {
123 			if (sbp->st_size >= off &&
124 			    fseeko(fp, -off, SEEK_END) == -1) {
125 				ierr();
126 				return;
127 			}
128 		} else if (off == 0) {
129 			while (getc(fp) != EOF)
130 				;
131 			if (ferror(fp)) {
132 				ierr();
133 				return;
134 			}
135 		} else {
136 			if (bytes(fp, off))
137 				return;
138 		}
139 		break;
140 	case RLINES:
141 		if (S_ISREG(sbp->st_mode)) {
142 			if (!off) {
143 				if (fseeko(fp, (off_t)0, SEEK_END) == -1) {
144 					ierr();
145 					return;
146 				}
147 			} else if (rlines(fp, off, sbp) != 0)
148 				lines(fp, off);
149 		} else if (off == 0) {
150 			while (getc(fp) != EOF)
151 				;
152 			if (ferror(fp)) {
153 				ierr();
154 				return;
155 			}
156 		} else {
157 			if (lines(fp, off))
158 				return;
159 		}
160 		break;
161 	}
162 
163 	kq = -1;
164 kq_retry:
165 	if (fflag && ((kq = kqueue()) >= 0)) {
166 		EV_SET(&ke, fileno(fp), EVFILT_READ,
167 		    EV_ENABLE | EV_ADD | EV_CLEAR,
168 		    0,
169 		    0, NULL);
170 		if (kevent(kq, &ke, 1, NULL, 0, NULL) < 0) {
171 			close(kq);
172 			kq = -1;
173 		} else if (S_ISREG(sbp->st_mode)) {
174 			EV_SET(&ke, fileno(fp), EVFILT_VNODE,
175 			    EV_ENABLE | EV_ADD | EV_CLEAR,
176 			    NOTE_DELETE | NOTE_RENAME | NOTE_TRUNCATE,
177 			    0, NULL);
178 			if (kevent(kq, &ke, 1, NULL, 0, NULL) < 0) {
179 				close(kq);
180 				kq = -1;
181 			}
182 		}
183 	}
184 
185 	for (;;) {
186 		while (!feof(fp) && (ch = getc(fp)) != EOF)
187 			if (putchar(ch) == EOF)
188 				oerr();
189 		if (ferror(fp)) {
190 			ierr();
191 			if (kq != -1)
192 				close(kq);
193 			return;
194 		}
195 		(void)fflush(stdout);
196 		if (!fflag)
197 			break;
198 		clearerr(fp);
199 		queue = 1;
200 		if (kq < 0 || kevent(kq, NULL, 0, &ke, 1, NULL) <= 0) {
201 			queue = 0;
202 			sleep(1);
203 		} else if (ke.filter == EVFILT_READ) {
204 			continue;
205 		} else if ((ke.fflags & NOTE_TRUNCATE) == 0) {
206 			/*
207 			 * File was renamed or deleted.
208 			 *
209 			 * Continue to look at it until a new file reappears
210 			 * with the same name.
211 			 * Fall back to the old algorithm for that.
212 			 */
213 			close(kq);
214 			kq = -1;
215 		}
216 
217 		if (is_stdin || stat(fname, &nsb) != 0)
218 			continue;
219 		/* Reopen file if the inode changes or file was truncated */
220 		if (nsb.st_ino != sbp->st_ino) {
221 			warnx("%s has been replaced, reopening.", fname);
222 			if ((fp = freopen(fname, "r", fp)) == NULL) {
223 				ierr();
224 				if (kq >= 0)
225 					close(kq);
226 				return;
227 			}
228 			(void)memcpy(sbp, &nsb, sizeof(nsb));
229 			goto kq_retry;
230 		} else if ((queue && (ke.fflags & NOTE_TRUNCATE)) ||
231 		    (!queue && nsb.st_size < sbp->st_size)) {
232 			warnx("%s has been truncated, resetting.", fname);
233 			fpurge(fp);
234 			rewind(fp);
235 		}
236 		(void)memcpy(sbp, &nsb, sizeof(nsb));
237 	}
238 	if (kq >= 0)
239 		close(kq);
240 }
241 
242 /*
243  * rlines -- display the last offset lines of the file.
244  */
245 static int
246 rlines(FILE *fp, off_t off, struct stat *sbp)
247 {
248 	off_t pos;
249 	int ch;
250 
251 	pos = sbp->st_size;
252 	if (pos == 0)
253 		return (0);
254 
255 	/*
256 	 * Position before char.
257 	 * Last char is special, ignore it whether newline or not.
258 	 */
259 	pos -= 2;
260 	ch = EOF;
261 	for (; off > 0 && pos >= 0; pos--) {
262 		/* A seek per char isn't a problem with a smart stdio */
263 		if (fseeko(fp, pos, SEEK_SET) == -1) {
264 			ierr();
265 			return (1);
266 		}
267 		if ((ch = getc(fp)) == '\n')
268 			off--;
269 		else if (ch == EOF) {
270 			if (ferror(fp)) {
271 				ierr();
272 				return (1);
273 			}
274 			break;
275 		}
276 	}
277 	/* If we read until start of file, put back last read char */
278 	if (pos < 0 && off > 0 && ch != EOF && ungetc(ch, fp) == EOF) {
279 		ierr();
280 		return (1);
281 	}
282 
283 	while (!feof(fp) && (ch = getc(fp)) != EOF)
284 		if (putchar(ch) == EOF)
285 			oerr();
286 	if (ferror(fp)) {
287 		ierr();
288 		return (1);
289 	}
290 
291 	return (0);
292 }
293