xref: /illumos-gate/usr/src/cmd/tail/forward.c (revision 7c64340fe7f813fbf9b6874c9422f7765bc54eb8)
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  * 4. 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 
33 /*
34  * Copyright (c) 2013, Joyent, Inc. All rights reserved.
35  */
36 
37 #include <sys/param.h>
38 #include <sys/mount.h>
39 #include <sys/types.h>
40 #include <sys/stat.h>
41 #include <sys/statfs.h>
42 #include <sys/statvfs.h>
43 #include <sys/time.h>
44 #include <sys/mman.h>
45 #include <sys/poll.h>
46 #include <port.h>
47 #include <err.h>
48 #include <errno.h>
49 #include <fcntl.h>
50 #include <limits.h>
51 #include <stdio.h>
52 #include <stdlib.h>
53 #include <string.h>
54 #include <strings.h>
55 #include <unistd.h>
56 
57 #include "extern.h"
58 
59 static void rlines(FILE *, const char *fn, off_t, struct stat *);
60 static int show(file_info_t *);
61 static void set_events(file_info_t *files);
62 
63 /* defines for inner loop actions */
64 #define	USE_SLEEP	0
65 #define	USE_PORT	1
66 #define	ADD_EVENTS	2
67 
68 int port;
69 int action = USE_PORT;
70 
71 static const file_info_t *last;
72 
73 /*
74  * forward -- display the file, from an offset, forward.
75  *
76  * There are eight separate cases for this -- regular and non-regular
77  * files, by bytes or lines and from the beginning or end of the file.
78  *
79  * FBYTES	byte offset from the beginning of the file
80  *	REG	seek
81  *	NOREG	read, counting bytes
82  *
83  * FLINES	line offset from the beginning of the file
84  *	REG	read, counting lines
85  *	NOREG	read, counting lines
86  *
87  * RBYTES	byte offset from the end of the file
88  *	REG	seek
89  *	NOREG	cyclically read characters into a wrap-around buffer
90  *
91  * RLINES
92  *	REG	mmap the file and step back until reach the correct offset.
93  *	NOREG	cyclically read lines into a wrap-around array of buffers
94  */
95 void
96 forward(FILE *fp, const char *fn, enum STYLE style, off_t off, struct stat *sbp)
97 {
98 	int ch;
99 
100 	switch (style) {
101 	case FBYTES:
102 		if (off == 0)
103 			break;
104 		if (S_ISREG(sbp->st_mode)) {
105 			if (sbp->st_size < off)
106 				off = sbp->st_size;
107 			if (fseeko(fp, off, SEEK_SET) == -1) {
108 				ierr(fn);
109 				return;
110 			}
111 		} else while (off--)
112 			if ((ch = getc(fp)) == EOF) {
113 				if (ferror(fp)) {
114 					ierr(fn);
115 					return;
116 				}
117 				break;
118 			}
119 		break;
120 	case FLINES:
121 		if (off == 0)
122 			break;
123 		for (;;) {
124 			if ((ch = getc(fp)) == EOF) {
125 				if (ferror(fp)) {
126 					ierr(fn);
127 					return;
128 				}
129 				break;
130 			}
131 			if (ch == '\n' && !--off)
132 				break;
133 		}
134 		break;
135 	case RBYTES:
136 		if (S_ISREG(sbp->st_mode)) {
137 			if (sbp->st_size >= off &&
138 			    fseeko(fp, -off, SEEK_END) == -1) {
139 				ierr(fn);
140 				return;
141 			}
142 		} else if (off == 0) {
143 			while (getc(fp) != EOF)
144 				;
145 			if (ferror(fp)) {
146 				ierr(fn);
147 				return;
148 			}
149 		} else
150 			if (bytes(fp, fn, off))
151 				return;
152 		break;
153 	case RLINES:
154 		if (S_ISREG(sbp->st_mode))
155 			if (!off) {
156 				if (fseeko(fp, (off_t)0, SEEK_END) == -1) {
157 					ierr(fn);
158 					return;
159 				}
160 			} else
161 				rlines(fp, fn, off, sbp);
162 		else if (off == 0) {
163 			while (getc(fp) != EOF)
164 				;
165 			if (ferror(fp)) {
166 				ierr(fn);
167 				return;
168 			}
169 		} else
170 			if (lines(fp, fn, off))
171 				return;
172 		break;
173 	default:
174 		break;
175 	}
176 
177 	while ((ch = getc(fp)) != EOF)
178 		if (putchar(ch) == EOF)
179 			oerr();
180 	if (ferror(fp)) {
181 		ierr(fn);
182 		return;
183 	}
184 	(void) fflush(stdout);
185 }
186 
187 /*
188  * rlines -- display the last offset lines of the file.
189  */
190 static void
191 rlines(FILE *fp, const char *fn, off_t off, struct stat *sbp)
192 {
193 	struct mapinfo map;
194 	off_t curoff, size;
195 	int i;
196 
197 	if ((size = sbp->st_size) == 0)
198 		return;
199 	map.start = NULL;
200 	map.fd = fileno(fp);
201 	map.mapoff = map.maxoff = size;
202 
203 	/*
204 	 * Last char is special, ignore whether newline or not. Note that
205 	 * size == 0 is dealt with above, and size == 1 sets curoff to -1.
206 	 */
207 	curoff = size - 2;
208 	while (curoff >= 0) {
209 		if (curoff < map.mapoff && maparound(&map, curoff) != 0) {
210 			ierr(fn);
211 			return;
212 		}
213 		for (i = curoff - map.mapoff; i >= 0; i--)
214 			if (map.start[i] == '\n' && --off == 0)
215 				break;
216 		/* `i' is either the map offset of a '\n', or -1. */
217 		curoff = map.mapoff + i;
218 		if (i >= 0)
219 			break;
220 	}
221 	curoff++;
222 	if (mapprint(&map, curoff, size - curoff) != 0) {
223 		ierr(fn);
224 		exit(1);
225 	}
226 
227 	/* Set the file pointer to reflect the length displayed. */
228 	if (fseeko(fp, sbp->st_size, SEEK_SET) == -1) {
229 		ierr(fn);
230 		return;
231 	}
232 	if (map.start != NULL && munmap(map.start, map.maplen)) {
233 		ierr(fn);
234 		return;
235 	}
236 }
237 
238 static int
239 show(file_info_t *file)
240 {
241 	int ch;
242 
243 	while ((ch = getc(file->fp)) != EOF) {
244 		if (last != file && no_files > 1) {
245 			if (!qflag)
246 				(void) printf("\n==> %s <==\n",
247 				    file->file_name);
248 			last = file;
249 		}
250 		if (putchar(ch) == EOF)
251 			oerr();
252 	}
253 	(void) fflush(stdout);
254 	if (ferror(file->fp)) {
255 		(void) fclose(file->fp);
256 		file->fp = NULL;
257 		ierr(file->file_name);
258 		return (0);
259 	}
260 	clearerr(file->fp);
261 	return (1);
262 }
263 
264 static void
265 associate(file_info_t *file, boolean_t assoc, port_event_t *ev)
266 {
267 	char buf[64], *name;
268 	int i;
269 
270 	if (action != USE_PORT || file->fp == NULL)
271 		return;
272 
273 	if (!S_ISREG(file->st.st_mode)) {
274 		/*
275 		 * For FIFOs, we use PORT_SOURCE_FD as our port event source.
276 		 */
277 		if (assoc) {
278 			(void) port_associate(port, PORT_SOURCE_FD,
279 			    fileno(file->fp), POLLIN, file);
280 		} else {
281 			(void) port_dissociate(port, PORT_SOURCE_FD,
282 			    fileno(file->fp));
283 		}
284 
285 		return;
286 	}
287 
288 	bzero(&file->fobj, sizeof (file->fobj));
289 
290 	if (!Fflag) {
291 		/*
292 		 * PORT_SOURCE_FILE only allows us to specify a file name, not
293 		 * a file descriptor.  If we are following a specific file (as
294 		 * opposed to a file name) and we were to specify the name of
295 		 * the file to port_associate() and that file were moved
296 		 * aside, we would not be able to reassociate an event because
297 		 * we would not know a name that would resolve to the new file
298 		 * (indeed, there might not be such a name -- the file may
299 		 * have been unlinked).  But there _is_ a name that we know
300 		 * maps to the file and doesn't change: the name of the
301 		 * representation of the open file descriptor in /proc.  We
302 		 * therefore associate with this name (and the underlying
303 		 * file), not the name of the file as specified at the command
304 		 * line.  This also has the (desirable) side-effect of
305 		 * insulating against FILE_RENAME_FROM and FILE_RENAME_TO
306 		 * events that we need to ignore to assure that we don't lose
307 		 * FILE_TRUNC events.
308 		 */
309 		(void) snprintf(buf,
310 		    sizeof (buf), "/proc/self/fd/%d", fileno(file->fp));
311 		name = buf;
312 	} else {
313 		name = file->file_name;
314 	}
315 
316 	/*
317 	 * Note that portfs uses the address of the specified file_obj_t to
318 	 * tag an association; if one creates a different association with a
319 	 * (different) file_obj_t that happens to be at the same address,
320 	 * the first association will be implicitly removed.  To assure that
321 	 * each association has a disjoint file_obj_t, we allocate the memory
322 	 * for each in the file_info, not on the stack.
323 	 */
324 	file->fobj[0].fo_name = name;
325 	file->fobj[1].fo_name = name;
326 
327 	if (assoc) {
328 		/*
329 		 * To assure that we cannot possibly drop a FILE_TRUNC event,
330 		 * we have two different PORT_SOURCE_FILE associations with the
331 		 * port:  one to get only FILE_MODIFIED events and another to
332 		 * get only FILE_TRUNC events.  This assures that we always
333 		 * have an active association for FILE_TRUNC events when the
334 		 * seek offset is non-zero.  Note that the association order
335 		 * _must_ be FILE_TRUNC followed by FILE_MODIFIED:  if a single
336 		 * event induces both a FILE_TRUNC and a FILE_MODIFIED (as
337 		 * a VE_CREATE vnode event does), we must process the
338 		 * FILE_TRUNC before FILE_MODIFIED -- and the order in which
339 		 * these are processed will be the association order.  So
340 		 * if we see a FILE_TRUNC, we dissociate/reassociate the
341 		 * FILE_MODIFIED association.
342 		 */
343 		if (ev == NULL || (ev->portev_events & FILE_TRUNC) ||
344 		    !(ev->portev_events & (FILE_MODIFIED | FILE_TRUNC))) {
345 			(void) port_associate(port, PORT_SOURCE_FILE,
346 			    (uintptr_t)&file->fobj[0], FILE_TRUNC, file);
347 			(void) port_dissociate(port, PORT_SOURCE_FILE,
348 			    (uintptr_t)&file->fobj[1]);
349 			ev = NULL;
350 		}
351 
352 		if (ev == NULL || (ev->portev_events & FILE_MODIFIED) ||
353 		    !(ev->portev_events & (FILE_MODIFIED | FILE_TRUNC))) {
354 			(void) port_associate(port, PORT_SOURCE_FILE,
355 			    (uintptr_t)&file->fobj[1], FILE_MODIFIED, file);
356 		}
357 	} else {
358 		for (i = 0; i <= 1; i++) {
359 			(void) port_dissociate(port, PORT_SOURCE_FILE,
360 			    (uintptr_t)&file->fobj[i]);
361 		}
362 	}
363 }
364 
365 static void
366 set_events(file_info_t *files)
367 {
368 	int i;
369 	file_info_t *file;
370 
371 	for (i = 0, file = files; i < no_files; i++, file++) {
372 		if (! file->fp)
373 			continue;
374 
375 		(void) fstat(fileno(file->fp), &file->st);
376 
377 		associate(file, B_TRUE, NULL);
378 	}
379 }
380 
381 /*
382  * follow -- display the file, from an offset, forward.
383  *
384  */
385 void
386 follow(file_info_t *files, enum STYLE style, off_t off)
387 {
388 	int active, ev_change, i, n = -1;
389 	struct stat sb2;
390 	file_info_t *file;
391 	struct timespec ts;
392 	port_event_t ev;
393 
394 	/* Position each of the files */
395 
396 	file = files;
397 	active = 0;
398 	n = 0;
399 	for (i = 0; i < no_files; i++, file++) {
400 		if (file->fp) {
401 			active = 1;
402 			n++;
403 			if (no_files > 1 && !qflag)
404 				(void) printf("\n==> %s <==\n",
405 				    file->file_name);
406 			forward(file->fp, file->file_name, style, off,
407 			    &file->st);
408 			if (Fflag && fileno(file->fp) != STDIN_FILENO)
409 				n++;
410 		}
411 	}
412 	if (!Fflag && !active)
413 		return;
414 
415 	last = --file;
416 
417 	if (action == USE_PORT &&
418 	    (stat("/proc/self/fd", &sb2) == -1 || !S_ISDIR(sb2.st_mode) ||
419 	    (port = port_create()) == -1))
420 		action = USE_SLEEP;
421 
422 	set_events(files);
423 
424 	for (;;) {
425 		ev_change = 0;
426 		if (Fflag) {
427 			for (i = 0, file = files; i < no_files; i++, file++) {
428 				if (!file->fp) {
429 					file->fp = fopen(file->file_name, "r");
430 					if (file->fp != NULL &&
431 					    fstat(fileno(file->fp), &file->st)
432 					    == -1) {
433 						(void) fclose(file->fp);
434 						file->fp = NULL;
435 					}
436 					if (file->fp != NULL)
437 						ev_change++;
438 					continue;
439 				}
440 				if (fileno(file->fp) == STDIN_FILENO)
441 					continue;
442 				if (stat(file->file_name, &sb2) == -1) {
443 					if (errno != ENOENT)
444 						ierr(file->file_name);
445 					(void) show(file);
446 					(void) fclose(file->fp);
447 					file->fp = NULL;
448 					ev_change++;
449 					continue;
450 				}
451 
452 				if (sb2.st_ino != file->st.st_ino ||
453 				    sb2.st_dev != file->st.st_dev ||
454 				    sb2.st_nlink == 0) {
455 					(void) show(file);
456 					associate(file, B_FALSE, NULL);
457 					file->fp = freopen(file->file_name, "r",
458 					    file->fp);
459 					if (file->fp != NULL) {
460 						(void) memcpy(&file->st, &sb2,
461 						    sizeof (struct stat));
462 					} else if (errno != ENOENT)
463 						ierr(file->file_name);
464 					ev_change++;
465 				}
466 			}
467 		}
468 
469 		for (i = 0, file = files; i < no_files; i++, file++)
470 			if (file->fp && !show(file))
471 				ev_change++;
472 
473 		if (ev_change)
474 			set_events(files);
475 
476 		switch (action) {
477 		case USE_PORT:
478 			ts.tv_sec = 1;
479 			ts.tv_nsec = 0;
480 
481 			/*
482 			 * In the -F case we set a timeout to ensure that
483 			 * we re-stat the file at least once every second.
484 			 */
485 			n = port_get(port, &ev, Fflag ? &ts : NULL);
486 
487 			if (n == 0) {
488 				file = (file_info_t *)ev.portev_user;
489 				associate(file, B_TRUE, &ev);
490 
491 				if (ev.portev_events & FILE_TRUNC)
492 					(void) fseek(file->fp, 0, SEEK_SET);
493 			}
494 
495 			break;
496 
497 		case USE_SLEEP:
498 			(void) usleep(250000);
499 			break;
500 		}
501 	}
502 }
503