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