xref: /openbsd/usr.bin/tail/forward.c (revision d415bd75)
1 /*	$OpenBSD: forward.c,v 1.33 2019/06/28 13:35:04 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 <errno.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <unistd.h>
46 
47 #include "extern.h"
48 
49 static int rlines(struct tailfile *, off_t);
50 static inline void tfprint(FILE *fp);
51 static int tfqueue(struct tailfile *tf);
52 static const struct timespec *tfreopen(struct tailfile *tf);
53 
54 static int kq = -1;
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(struct tailfile *tf, int nfiles, enum STYLE style, off_t origoff)
80 {
81 	int ch;
82 	struct tailfile *ctf, *ltf;
83 	struct kevent ke;
84 	const struct timespec *ts = NULL;
85 	int i;
86 	int nevents;
87 
88 	if (nfiles < 1)
89 		return;
90 
91 	if (fflag && (kq = kqueue()) == -1)
92 		warn("kqueue");
93 
94 	for (i = 0; i < nfiles; i++) {
95 		off_t off = origoff;
96 		if (nfiles > 1)
97 			printfname(tf[i].fname);
98 
99 		switch(style) {
100 		case FBYTES:
101 			if (off == 0)
102 				break;
103 			if (S_ISREG(tf[i].sb.st_mode)) {
104 				if (tf[i].sb.st_size < off)
105 					off = tf[i].sb.st_size;
106 				if (fseeko(tf[i].fp, off, SEEK_SET) == -1) {
107 					ierr(tf[i].fname);
108 					return;
109 				}
110 			} else while (off--)
111 				if ((ch = getc(tf[i].fp)) == EOF) {
112 					if (ferror(tf[i].fp)) {
113 						ierr(tf[i].fname);
114 						return;
115 					}
116 					break;
117 				}
118 			break;
119 		case FLINES:
120 			if (off == 0)
121 				break;
122 			for (;;) {
123 				if ((ch = getc(tf[i].fp)) == EOF) {
124 					if (ferror(tf[i].fp)) {
125 						ierr(tf[i].fname);
126 						return;
127 					}
128 					break;
129 				}
130 				if (ch == '\n' && !--off)
131 					break;
132 			}
133 			break;
134 		case RBYTES:
135 			if (S_ISREG(tf[i].sb.st_mode)) {
136 				if (tf[i].sb.st_size >= off &&
137 				    fseeko(tf[i].fp, -off, SEEK_END) == -1) {
138 					ierr(tf[i].fname);
139 					return;
140 				}
141 			} else if (off == 0) {
142 				while (getc(tf[i].fp) != EOF)
143 					;
144 				if (ferror(tf[i].fp)) {
145 					ierr(tf[i].fname);
146 					return;
147 				}
148 			} else {
149 				if (bytes(&(tf[i]), off))
150 					return;
151 			}
152 			break;
153 		case RLINES:
154 			if (S_ISREG(tf[i].sb.st_mode)) {
155 				if (!off) {
156 					if (fseeko(tf[i].fp, (off_t)0,
157 					    SEEK_END) == -1) {
158 						ierr(tf[i].fname);
159 						return;
160 					}
161 				} else if (rlines(&(tf[i]), off) != 0)
162 					lines(&(tf[i]), off);
163 			} else if (off == 0) {
164 				while (getc(tf[i].fp) != EOF)
165 					;
166 				if (ferror(tf[i].fp)) {
167 					ierr(tf[i].fname);
168 					return;
169 				}
170 			} else {
171 				if (lines(&(tf[i]), off))
172 					return;
173 			}
174 			break;
175 		default:
176 			err(1, "Unsupported style");
177 		}
178 
179 		tfprint(tf[i].fp);
180 		if (fflag && tfqueue(&(tf[i])) == -1)
181 			warn("Unable to follow %s", tf[i].fname);
182 
183 	}
184 	ltf = &(tf[i-1]);
185 
186 	(void)fflush(stdout);
187 	if (!fflag || kq == -1)
188 		return;
189 
190 	while (1) {
191 		if ((nevents = kevent(kq, NULL, 0, &ke, 1, ts)) <= 0) {
192 			if (errno == EINTR) {
193 				close(kq);
194 				return;
195 			}
196 		}
197 
198 		ctf = ke.udata;
199 		if (nevents > 0) {
200 			if (ke.filter == EVFILT_READ) {
201 				if (ctf != ltf) {
202 					printfname(ctf->fname);
203 					ltf = ctf;
204 				}
205 				clearerr(ctf->fp);
206 				tfprint(ctf->fp);
207 				if (ferror(ctf->fp)) {
208 					ierr(ctf->fname);
209 					fclose(ctf->fp);
210 					warn("Lost file %s", ctf->fname);
211 					continue;
212 				}
213 				(void)fflush(stdout);
214 				clearerr(ctf->fp);
215 			} else if (ke.filter == EVFILT_VNODE) {
216 				if (ke.fflags & (NOTE_DELETE | NOTE_RENAME)) {
217 					/*
218 					 * File was deleted or renamed.
219 					 *
220 					 * Continue to look at it until
221 					 * a new file reappears with
222 					 * the same name.
223 					 */
224 					(void) tfreopen(ctf);
225 				} else if (ke.fflags & NOTE_TRUNCATE) {
226 					warnx("%s has been truncated, "
227 					    "resetting.", ctf->fname);
228 					fpurge(ctf->fp);
229 					rewind(ctf->fp);
230 				}
231 			}
232 		}
233 		ts = tfreopen(NULL);
234 	}
235 }
236 
237 /*
238  * rlines -- display the last offset lines of the file.
239  */
240 static int
241 rlines(struct tailfile *tf, off_t off)
242 {
243 	off_t pos;
244 	int ch;
245 
246 	pos = tf->sb.st_size;
247 	if (pos == 0)
248 		return (0);
249 
250 	/*
251 	 * Position before char.
252 	 * Last char is special, ignore it whether newline or not.
253 	 */
254 	pos -= 2;
255 	ch = EOF;
256 	for (; off > 0 && pos >= 0; pos--) {
257 		/* A seek per char isn't a problem with a smart stdio */
258 		if (fseeko(tf[0].fp, pos, SEEK_SET) == -1) {
259 			ierr(tf->fname);
260 			return (1);
261 		}
262 		if ((ch = getc(tf[0].fp)) == '\n')
263 			off--;
264 		else if (ch == EOF) {
265 			if (ferror(tf[0].fp)) {
266 				ierr(tf->fname);
267 				return (1);
268 			}
269 			break;
270 		}
271 	}
272 	/* If we read until start of file, put back last read char */
273 	if (pos < 0 && off > 0 && ch != EOF && ungetc(ch, tf[0].fp) == EOF) {
274 		ierr(tf->fname);
275 		return (1);
276 	}
277 
278 	while (!feof(tf[0].fp) && (ch = getc(tf[0].fp)) != EOF)
279 		if (putchar(ch) == EOF)
280 			oerr();
281 	if (ferror(tf[0].fp)) {
282 		ierr(tf->fname);
283 		return (1);
284 	}
285 
286 	return (0);
287 }
288 
289 static inline void
290 tfprint(FILE *fp)
291 {
292 	int ch;
293 
294 	while (!feof(fp) && (ch = getc(fp)) != EOF)
295 		if (putchar(ch) == EOF)
296 			oerr();
297 }
298 
299 static int
300 tfqueue(struct tailfile *tf)
301 {
302 	struct kevent ke[2];
303 	int i = 1;
304 
305 	if (kq < 0) {
306 		errno = EBADF;
307 		return -1;
308 	}
309 
310 	EV_SET(&(ke[0]), fileno(tf->fp), EVFILT_READ,
311 	    EV_ENABLE | EV_ADD | EV_CLEAR, 0, 0, tf);
312 
313 	if (S_ISREG(tf->sb.st_mode)) {
314 		i = 2;
315 		EV_SET(&(ke[1]), fileno(tf->fp), EVFILT_VNODE,
316 		    EV_ENABLE | EV_ADD | EV_CLEAR,
317 		    NOTE_DELETE | NOTE_RENAME | NOTE_TRUNCATE,
318 		    0, tf);
319 	}
320 	if (kevent(kq, ke, i, NULL, 0, NULL) == -1) {
321 		ierr(tf->fname);
322 		return -1;
323 	}
324 	return 0;
325 }
326 
327 #define AFILESINCR 8
328 static const struct timespec *
329 tfreopen(struct tailfile *tf) {
330 	static struct tailfile		**reopen = NULL;
331 	static int			  nfiles = 0, afiles = 0;
332 	static const struct timespec	  ts = {1, 0};
333 
334 	struct stat			  sb;
335 	struct tailfile			**treopen, *ttf;
336 	int				  i;
337 
338 	if (tf && !(tf->fp == stdin) &&
339 	    ((stat(tf->fname, &sb) != 0) || sb.st_ino != tf->sb.st_ino)) {
340 		if (afiles < ++nfiles) {
341 			afiles += AFILESINCR;
342 			treopen = reallocarray(reopen, afiles, sizeof(*reopen));
343 			if (treopen)
344 				reopen = treopen;
345 			else
346 				afiles -= AFILESINCR;
347 		}
348 		if (nfiles <= afiles) {
349 			for (i = 0; i < nfiles - 1; i++)
350 				if (strcmp(reopen[i]->fname, tf->fname) == 0)
351 					break;
352 			if (i < nfiles - 1)
353 				nfiles--;
354 			else
355 				reopen[nfiles-1] = tf;
356 		} else {
357 			warnx("Lost track of %s", tf->fname);
358 			nfiles--;
359 		}
360 	}
361 
362 	for (i = 0; i < nfiles; i++) {
363 		ttf = reopen[i];
364 		if (stat(ttf->fname, &sb) == -1)
365 			continue;
366 		if (sb.st_ino != ttf->sb.st_ino) {
367 			(void) memcpy(&(ttf->sb), &sb, sizeof(ttf->sb));
368 			ttf->fp = freopen(ttf->fname, "r", ttf->fp);
369 			if (ttf->fp == NULL)
370 				ierr(ttf->fname);
371 			else {
372 				warnx("%s has been replaced, reopening.",
373 				    ttf->fname);
374 				tfqueue(ttf);
375 			}
376 		}
377 		reopen[i] = reopen[--nfiles];
378 	}
379 
380 	return nfiles ? &ts : NULL;
381 }
382