xref: /dragonfly/usr.bin/tail/forward.c (revision cfd1aba3)
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 (continuing)
264 		printf("\n");
265 	printf("==> %s <==\n", filename);
266 	continuing = 1;
267 	last_index = at_index;
268 }
269 
270 static void
271 set_events(file_info_t *files)
272 {
273 	int i, n;
274 	file_info_t *file;
275 	struct timespec ts;
276 
277 	ts.tv_sec = 0;
278 	ts.tv_nsec = 0;
279 
280 	n = 0;
281 	action = USE_KQUEUE;
282 	for (i = 0, file = files; i < no_files; i++, file++) {
283 		if (file->fp == NULL)
284 			continue;
285 		if (Fflag && fileno(file->fp) != STDIN_FILENO) {
286 			EV_SET(&ev[n], fileno(file->fp), EVFILT_VNODE,
287 			       EV_ADD | EV_ENABLE | EV_CLEAR,
288 			       NOTE_DELETE | NOTE_RENAME, 0, 0);
289 			n++;
290 		}
291 		EV_SET(&ev[n], fileno(file->fp), EVFILT_READ,
292 		       EV_ADD | EV_ENABLE | EV_CLEAR, 0, 0, 0);
293 		n++;
294 	}
295 
296 	if (kevent(kq, ev, n, NULL, 0, &ts) < 0)
297 		action = USE_SLEEP;
298 }
299 
300 void
301 follow(file_info_t *files, enum STYLE style, off_t off)
302 {
303 	int active, i, n;
304 	file_info_t *file;
305 	struct stat sb2;
306 	struct timespec ts;
307 
308 	/* Position each of the files */
309 	file = files;
310 	active = 0;
311 	n = 0;
312 	for (i = 0; i < no_files; i++, file++) {
313 		if (file->fp) {
314 			active = 1;
315 			n++;
316 			if (no_files > 1)
317 				showfilename(i, file->file_name);
318 			forward(file->fp, style, off, &file->st);
319 			if (Fflag && fileno(file->fp) != STDIN_FILENO)
320 				n++;
321 		}
322 	}
323 
324 	if (!active)
325 		return;
326 
327 	kq = kqueue();
328 	if (kq == -1)
329 		err(1, "kqueue");
330 	ev = malloc(n * sizeof(struct kevent));
331 	if (ev == NULL)
332 		err(1, "Couldn't allocate memory for kevents.");
333 	set_events(files);
334 
335 	for (;;) {
336 		for (i = 0, file = files; i < no_files; i++, file++) {
337 			if (file->fp == NULL)
338 				continue;
339 			if (Fflag && fileno(file->fp) != STDIN_FILENO) {
340 				if (stat(file->file_name, &sb2) == -1) {
341 					/*
342 					 * file was rotated, skip it until it
343 					 * reappears.
344 					 */
345 					continue;
346 				}
347 				if (sb2.st_ino != file->st.st_ino ||
348 				    sb2.st_dev != file->st.st_dev ||
349 				    sb2.st_nlink == 0) {
350 					file->fp = freopen(file->file_name, "r",
351 							   file->fp);
352 					if (file->fp == NULL) {
353 						ierr();
354 						continue;
355 					} else {
356 						memcpy(&file->st, &sb2,
357 						       sizeof(struct stat));
358 						set_events(files);
359 					}
360 				}
361 			}
362 			show(file, i);
363 		}
364 
365 		switch (action) {
366 		case USE_KQUEUE:
367 			ts.tv_sec = 1;
368 			ts.tv_nsec = 0;
369 			/*
370 			 * In the -F case, we set a timeout to ensure that
371 			 * we re-stat the file at least once every second.
372 			 */
373 			n = kevent(kq, NULL, 0, ev, 1, Fflag ? &ts : NULL);
374 			if (n == -1)
375 				err(1, "kevent");
376 			if (n == 0) {
377 				/* timeout */
378 				break;
379 			} else if (ev->filter == EVFILT_READ && ev->data < 0) {
380 				/* file shrank, reposition to end */
381 				if (lseek(ev->ident, 0, SEEK_END) == -1) {
382 					ierr();
383 					continue;
384 				}
385 			}
386 			break;
387 
388 		case USE_SLEEP:
389 			usleep(250000);
390 			break;
391 		}
392 	}
393 }
394