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