xref: /freebsd/usr.bin/tail/forward.c (revision 9f23cbd6cae82fd77edfad7173432fa8dccd0a95)
1 /*-
2  * SPDX-License-Identifier: BSD-3-Clause
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. Neither the name of the University nor the names of its contributors
19  *    may be used to endorse or promote products derived from this software
20  *    without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 
35 #include <sys/cdefs.h>
36 
37 __FBSDID("$FreeBSD$");
38 
39 #ifndef lint
40 static const char sccsid[] = "@(#)forward.c	8.1 (Berkeley) 6/6/93";
41 #endif
42 
43 #include <sys/param.h>
44 #include <sys/mount.h>
45 #include <sys/types.h>
46 #include <sys/stat.h>
47 #include <sys/time.h>
48 #include <sys/mman.h>
49 #include <sys/event.h>
50 
51 #include <err.h>
52 #include <errno.h>
53 #include <fcntl.h>
54 #include <limits.h>
55 #include <stdio.h>
56 #include <stdlib.h>
57 #include <string.h>
58 #include <unistd.h>
59 
60 #include <libcasper.h>
61 #include <casper/cap_fileargs.h>
62 
63 #include "extern.h"
64 
65 static void rlines(FILE *, const char *fn, off_t, struct stat *);
66 static int show(file_info_t *);
67 static void set_events(file_info_t *files);
68 
69 /* defines for inner loop actions */
70 #define USE_SLEEP	0
71 #define USE_KQUEUE	1
72 #define ADD_EVENTS	2
73 
74 static struct kevent *ev;
75 static int action = USE_SLEEP;
76 static int kq;
77 
78 static const file_info_t *last;
79 
80 /*
81  * forward -- display the file, from an offset, forward.
82  *
83  * There are eight separate cases for this -- regular and non-regular
84  * files, by bytes or lines and from the beginning or end of the file.
85  *
86  * FBYTES	byte offset from the beginning of the file
87  *	REG	seek
88  *	NOREG	read, counting bytes
89  *
90  * FLINES	line offset from the beginning of the file
91  *	REG	read, counting lines
92  *	NOREG	read, counting lines
93  *
94  * RBYTES	byte offset from the end of the file
95  *	REG	seek
96  *	NOREG	cyclically read characters into a wrap-around buffer
97  *
98  * RLINES
99  *	REG	mmap the file and step back until reach the correct offset.
100  *	NOREG	cyclically read lines into a wrap-around array of buffers
101  */
102 void
103 forward(FILE *fp, const char *fn, enum STYLE style, off_t off, struct stat *sbp)
104 {
105 	int ch;
106 
107 	switch(style) {
108 	case FBYTES:
109 		if (off == 0)
110 			break;
111 		if (S_ISREG(sbp->st_mode)) {
112 			if (sbp->st_size < off)
113 				off = sbp->st_size;
114 			if (fseeko(fp, off, SEEK_SET) == -1) {
115 				ierr(fn);
116 				return;
117 			}
118 		} else while (off--)
119 			if ((ch = getc(fp)) == EOF) {
120 				if (ferror(fp)) {
121 					ierr(fn);
122 					return;
123 				}
124 				break;
125 			}
126 		break;
127 	case FLINES:
128 		if (off == 0)
129 			break;
130 		for (;;) {
131 			if ((ch = getc(fp)) == EOF) {
132 				if (ferror(fp)) {
133 					ierr(fn);
134 					return;
135 				}
136 				break;
137 			}
138 			if (ch == '\n' && !--off)
139 				break;
140 		}
141 		break;
142 	case RBYTES:
143 		if (S_ISREG(sbp->st_mode)) {
144 			if (sbp->st_size >= off &&
145 			    fseeko(fp, -off, SEEK_END) == -1) {
146 				ierr(fn);
147 				return;
148 			}
149 		} else if (off == 0) {
150 			while (getc(fp) != EOF);
151 			if (ferror(fp)) {
152 				ierr(fn);
153 				return;
154 			}
155 		} else
156 			if (bytes(fp, fn, off))
157 				return;
158 		break;
159 	case RLINES:
160 		if (S_ISREG(sbp->st_mode))
161 			if (!off) {
162 				if (fseeko(fp, (off_t)0, SEEK_END) == -1) {
163 					ierr(fn);
164 					return;
165 				}
166 			} else
167 				rlines(fp, fn, off, sbp);
168 		else if (off == 0) {
169 			while (getc(fp) != EOF);
170 			if (ferror(fp)) {
171 				ierr(fn);
172 				return;
173 			}
174 		} else
175 			if (lines(fp, fn, off))
176 				return;
177 		break;
178 	default:
179 		break;
180 	}
181 
182 	while ((ch = getc(fp)) != EOF)
183 		if (putchar(ch) == EOF)
184 			oerr();
185 	if (ferror(fp)) {
186 		ierr(fn);
187 		return;
188 	}
189 	(void)fflush(stdout);
190 }
191 
192 /*
193  * rlines -- display the last offset lines of the file.
194  */
195 static void
196 rlines(FILE *fp, const char *fn, off_t off, struct stat *sbp)
197 {
198 	struct mapinfo map;
199 	off_t curoff, size;
200 	int i;
201 
202 	if (!(size = sbp->st_size))
203 		return;
204 	map.start = NULL;
205 	map.fd = fileno(fp);
206 	map.mapoff = map.maxoff = size;
207 
208 	/*
209 	 * Last char is special, ignore whether newline or not. Note that
210 	 * size == 0 is dealt with above, and size == 1 sets curoff to -1.
211 	 */
212 	curoff = size - 2;
213 	while (curoff >= 0) {
214 		if (curoff < map.mapoff && maparound(&map, curoff) != 0) {
215 			ierr(fn);
216 			return;
217 		}
218 		for (i = curoff - map.mapoff; i >= 0; i--)
219 			if (map.start[i] == '\n' && --off == 0)
220 				break;
221 		/* `i' is either the map offset of a '\n', or -1. */
222 		curoff = map.mapoff + i;
223 		if (i >= 0)
224 			break;
225 	}
226 	curoff++;
227 	if (mapprint(&map, curoff, size - curoff) != 0) {
228 		ierr(fn);
229 		exit(1);
230 	}
231 
232 	/* Set the file pointer to reflect the length displayed. */
233 	if (fseeko(fp, sbp->st_size, SEEK_SET) == -1) {
234 		ierr(fn);
235 		return;
236 	}
237 	if (map.start != NULL && munmap(map.start, map.maplen)) {
238 		ierr(fn);
239 		return;
240 	}
241 }
242 
243 static int
244 show(file_info_t *file)
245 {
246 	int ch;
247 
248 	while ((ch = getc(file->fp)) != EOF) {
249 		if (last != file) {
250 			if (vflag || (qflag == 0 && no_files > 1))
251 				printfn(file->file_name, 1);
252 			last = file;
253 		}
254 		if (putchar(ch) == EOF)
255 			oerr();
256 	}
257 	(void)fflush(stdout);
258 	if (ferror(file->fp)) {
259 		fclose(file->fp);
260 		file->fp = NULL;
261 		ierr(file->file_name);
262 		return 0;
263 	}
264 	clearerr(file->fp);
265 	return 1;
266 }
267 
268 static void
269 set_events(file_info_t *files)
270 {
271 	int i, n = 0;
272 	file_info_t *file;
273 	struct timespec ts;
274 	struct statfs sf;
275 
276 	ts.tv_sec = 0;
277 	ts.tv_nsec = 0;
278 
279 	action = USE_KQUEUE;
280 	for (i = 0, file = files; i < no_files; i++, file++) {
281 		if (! file->fp)
282 			continue;
283 
284 		if (fstatfs(fileno(file->fp), &sf) == 0 &&
285 		    (sf.f_flags & MNT_LOCAL) == 0) {
286 			action = USE_SLEEP;
287 			return;
288 		}
289 
290 		if (Fflag && fileno(file->fp) != STDIN_FILENO) {
291 			EV_SET(&ev[n], fileno(file->fp), EVFILT_VNODE,
292 			    EV_ADD | EV_ENABLE | EV_CLEAR,
293 			    NOTE_DELETE | NOTE_RENAME, 0, 0);
294 			n++;
295 		}
296 		EV_SET(&ev[n], fileno(file->fp), EVFILT_READ,
297 		    EV_ADD | EV_ENABLE | EV_CLEAR, 0, 0, 0);
298 		n++;
299 	}
300 
301 	if (kevent(kq, ev, n, NULL, 0, &ts) < 0) {
302 		action = USE_SLEEP;
303 	}
304 }
305 
306 /*
307  * follow -- display the file, from an offset, forward.
308  *
309  */
310 void
311 follow(file_info_t *files, enum STYLE style, off_t off)
312 {
313 	int active, ev_change, i, n = -1;
314 	struct stat sb2;
315 	file_info_t *file;
316 	FILE *ftmp;
317 	struct timespec ts;
318 
319 	/* Position each of the files */
320 
321 	file = files;
322 	active = 0;
323 	n = 0;
324 	for (i = 0; i < no_files; i++, file++) {
325 		if (file->fp) {
326 			active = 1;
327 			n++;
328 			if (vflag || (qflag == 0 && no_files > 1))
329 				printfn(file->file_name, 1);
330 			forward(file->fp, file->file_name, style, off, &file->st);
331 			if (Fflag && fileno(file->fp) != STDIN_FILENO)
332 				n++;
333 		}
334 	}
335 	if (!Fflag && !active)
336 		return;
337 
338 	last = --file;
339 
340 	kq = kqueue();
341 	if (kq < 0)
342 		err(1, "kqueue");
343 	ev = malloc(n * sizeof(struct kevent));
344 	if (! ev)
345 	    err(1, "Couldn't allocate memory for kevents.");
346 	set_events(files);
347 
348 	for (;;) {
349 		ev_change = 0;
350 		if (Fflag) {
351 			for (i = 0, file = files; i < no_files; i++, file++) {
352 				if (!file->fp) {
353 					file->fp =
354 					    fileargs_fopen(fa, file->file_name,
355 					    "r");
356 					if (file->fp != NULL &&
357 					    fstat(fileno(file->fp), &file->st)
358 					    == -1) {
359 						fclose(file->fp);
360 						file->fp = NULL;
361 					}
362 					if (file->fp != NULL)
363 						ev_change++;
364 					continue;
365 				}
366 				if (fileno(file->fp) == STDIN_FILENO)
367 					continue;
368 				ftmp = fileargs_fopen(fa, file->file_name, "r");
369 				if (ftmp == NULL ||
370 				    fstat(fileno(ftmp), &sb2) == -1) {
371 					if (errno != ENOENT)
372 						ierr(file->file_name);
373 					show(file);
374 					if (file->fp != NULL) {
375 						fclose(file->fp);
376 						file->fp = NULL;
377 					}
378 					if (ftmp != NULL) {
379 						fclose(ftmp);
380 					}
381 					ev_change++;
382 					continue;
383 				}
384 
385 				if (sb2.st_ino != file->st.st_ino ||
386 				    sb2.st_dev != file->st.st_dev ||
387 				    sb2.st_nlink == 0) {
388 					show(file);
389 					fclose(file->fp);
390 					file->fp = ftmp;
391 					memcpy(&file->st, &sb2,
392 					    sizeof(struct stat));
393 					ev_change++;
394 				} else {
395 					fclose(ftmp);
396 				}
397 			}
398 		}
399 
400 		for (i = 0, file = files; i < no_files; i++, file++)
401 			if (file->fp && !show(file))
402 				ev_change++;
403 
404 		if (ev_change)
405 			set_events(files);
406 
407 		switch (action) {
408 		case USE_KQUEUE:
409 			ts.tv_sec = 1;
410 			ts.tv_nsec = 0;
411 			/*
412 			 * In the -F case we set a timeout to ensure that
413 			 * we re-stat the file at least once every second.
414 			 * If we've recieved EINTR, ignore it. Both reasons
415 			 * for its generation are transient.
416 			 */
417 			do {
418 				n = kevent(kq, NULL, 0, ev, 1, Fflag ? &ts : NULL);
419 				if (n < 0 && errno == EINTR)
420 					continue;
421 				if (n < 0)
422 					err(1, "kevent");
423 			} while (n < 0);
424 			if (n == 0) {
425 				/* timeout */
426 				break;
427 			} else if (ev->filter == EVFILT_READ && ev->data < 0) {
428 				/* file shrank, reposition to end */
429 				if (lseek(ev->ident, (off_t)0, SEEK_END) == -1) {
430 					ierr(file->file_name);
431 					continue;
432 				}
433 			}
434 			break;
435 
436 		case USE_SLEEP:
437 			(void) usleep(250000);
438 			break;
439 		}
440 	}
441 }
442