xref: /illumos-gate/usr/src/cmd/tail/forward.c (revision 83eaeac78ef2f69de16c2fecd3077c0ee9269743)
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  * Solaris porting notes:  the original FreeBSD version made use of the
35  *                         BSD kqueue event notification framework; this
36  *                         was changed to use the Solaris event completion
37  *                         framework: port_create(), port_associate(),
38  *                         and port_get().
39  */
40 
41 #include <sys/param.h>
42 #include <sys/mount.h>
43 #include <sys/types.h>
44 #include <sys/stat.h>
45 #include <sys/statfs.h>
46 #include <sys/statvfs.h>
47 #include <sys/time.h>
48 #include <sys/mman.h>
49 #include <sys/poll.h>
50 #include <port.h>
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 "extern.h"
61 
62 static void rlines(FILE *, const char *fn, off_t, struct stat *);
63 static int show(file_info_t *);
64 static void set_events(file_info_t *files);
65 
66 /* defines for inner loop actions */
67 #define	USE_SLEEP	0
68 #define	USE_PORT	1
69 #define	ADD_EVENTS	2
70 
71 int port;
72 int action = USE_SLEEP;
73 port_event_t ev;
74 
75 static const file_info_t *last;
76 
77 /*
78  * forward -- display the file, from an offset, forward.
79  *
80  * There are eight separate cases for this -- regular and non-regular
81  * files, by bytes or lines and from the beginning or end of the file.
82  *
83  * FBYTES	byte offset from the beginning of the file
84  *	REG	seek
85  *	NOREG	read, counting bytes
86  *
87  * FLINES	line offset from the beginning of the file
88  *	REG	read, counting lines
89  *	NOREG	read, counting lines
90  *
91  * RBYTES	byte offset from the end of the file
92  *	REG	seek
93  *	NOREG	cyclically read characters into a wrap-around buffer
94  *
95  * RLINES
96  *	REG	mmap the file and step back until reach the correct offset.
97  *	NOREG	cyclically read lines into a wrap-around array of buffers
98  */
99 void
100 forward(FILE *fp, const char *fn, enum STYLE style, off_t off, struct stat *sbp)
101 {
102 	int ch;
103 
104 	switch (style) {
105 	case FBYTES:
106 		if (off == 0)
107 			break;
108 		if (S_ISREG(sbp->st_mode)) {
109 			if (sbp->st_size < off)
110 				off = sbp->st_size;
111 			if (fseeko(fp, off, SEEK_SET) == -1) {
112 				ierr(fn);
113 				return;
114 			}
115 		} else while (off--)
116 			if ((ch = getc(fp)) == EOF) {
117 				if (ferror(fp)) {
118 					ierr(fn);
119 					return;
120 				}
121 				break;
122 			}
123 		break;
124 	case FLINES:
125 		if (off == 0)
126 			break;
127 		for (;;) {
128 			if ((ch = getc(fp)) == EOF) {
129 				if (ferror(fp)) {
130 					ierr(fn);
131 					return;
132 				}
133 				break;
134 			}
135 			if (ch == '\n' && !--off)
136 				break;
137 		}
138 		break;
139 	case RBYTES:
140 		if (S_ISREG(sbp->st_mode)) {
141 			if (sbp->st_size >= off &&
142 			    fseeko(fp, -off, SEEK_END) == -1) {
143 				ierr(fn);
144 				return;
145 			}
146 		} else if (off == 0) {
147 			while (getc(fp) != EOF)
148 				;
149 			if (ferror(fp)) {
150 				ierr(fn);
151 				return;
152 			}
153 		} else
154 			if (bytes(fp, fn, off))
155 				return;
156 		break;
157 	case RLINES:
158 		if (S_ISREG(sbp->st_mode))
159 			if (!off) {
160 				if (fseeko(fp, (off_t)0, SEEK_END) == -1) {
161 					ierr(fn);
162 					return;
163 				}
164 			} else
165 				rlines(fp, fn, off, sbp);
166 		else if (off == 0) {
167 			while (getc(fp) != EOF)
168 				;
169 			if (ferror(fp)) {
170 				ierr(fn);
171 				return;
172 			}
173 		} else
174 			if (lines(fp, fn, off))
175 				return;
176 		break;
177 	default:
178 		break;
179 	}
180 
181 	while ((ch = getc(fp)) != EOF)
182 		if (putchar(ch) == EOF)
183 			oerr();
184 	if (ferror(fp)) {
185 		ierr(fn);
186 		return;
187 	}
188 	(void) fflush(stdout);
189 }
190 
191 /*
192  * rlines -- display the last offset lines of the file.
193  */
194 static void
195 rlines(FILE *fp, const char *fn, off_t off, struct stat *sbp)
196 {
197 	struct mapinfo map;
198 	off_t curoff, size;
199 	int i;
200 
201 	if ((size = sbp->st_size) == 0)
202 		return;
203 	map.start = NULL;
204 	map.fd = fileno(fp);
205 	map.mapoff = map.maxoff = size;
206 
207 	/*
208 	 * Last char is special, ignore whether newline or not. Note that
209 	 * size == 0 is dealt with above, and size == 1 sets curoff to -1.
210 	 */
211 	curoff = size - 2;
212 	while (curoff >= 0) {
213 		if (curoff < map.mapoff && maparound(&map, curoff) != 0) {
214 			ierr(fn);
215 			return;
216 		}
217 		for (i = curoff - map.mapoff; i >= 0; i--)
218 			if (map.start[i] == '\n' && --off == 0)
219 				break;
220 		/* `i' is either the map offset of a '\n', or -1. */
221 		curoff = map.mapoff + i;
222 		if (i >= 0)
223 			break;
224 	}
225 	curoff++;
226 	if (mapprint(&map, curoff, size - curoff) != 0) {
227 		ierr(fn);
228 		exit(1);
229 	}
230 
231 	/* Set the file pointer to reflect the length displayed. */
232 	if (fseeko(fp, sbp->st_size, SEEK_SET) == -1) {
233 		ierr(fn);
234 		return;
235 	}
236 	if (map.start != NULL && munmap(map.start, map.maplen)) {
237 		ierr(fn);
238 		return;
239 	}
240 }
241 
242 static int
243 show(file_info_t *file)
244 {
245 	int ch;
246 
247 	while ((ch = getc(file->fp)) != EOF) {
248 		if (last != file && no_files > 1) {
249 			if (!qflag)
250 				(void) printf("\n==> %s <==\n",
251 				    file->file_name);
252 			last = file;
253 		}
254 		if (putchar(ch) == EOF)
255 			oerr();
256 	}
257 	(void) fflush(stdout);
258 	if (ferror(file->fp)) {
259 		(void) 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;
272 	file_info_t *file;
273 
274 	action = USE_PORT;
275 	for (i = 0, file = files; i < no_files; i++, file++) {
276 		if (! file->fp)
277 			continue;
278 
279 		(void) fstat(fileno(file->fp), &file->st);
280 		/* For -f or -F will both use Solaris port interface */
281 		if (fflag && (fileno(file->fp) != STDIN_FILENO)) {
282 			(void) port_associate(port, PORT_SOURCE_FD,
283 			    fileno(file->fp), POLLIN, (void*)file);
284 		}
285 	}
286 }
287 
288 /*
289  * follow -- display the file, from an offset, forward.
290  *
291  */
292 void
293 follow(file_info_t *files, enum STYLE style, off_t off)
294 {
295 	int active, ev_change, i, n = -1;
296 	struct stat sb2;
297 	file_info_t *file;
298 	struct timespec ts;
299 
300 	/* Position each of the files */
301 
302 	file = files;
303 	active = 0;
304 	n = 0;
305 	for (i = 0; i < no_files; i++, file++) {
306 		if (file->fp) {
307 			active = 1;
308 			n++;
309 			if (no_files > 1 && !qflag)
310 				(void) printf("\n==> %s <==\n",
311 				    file->file_name);
312 			forward(file->fp, file->file_name, style, off,
313 			    &file->st);
314 			if (Fflag && fileno(file->fp) != STDIN_FILENO)
315 				n++;
316 		}
317 	}
318 	if (!Fflag && !active)
319 		return;
320 
321 	last = --file;
322 	port = port_create();
323 	set_events(files);
324 
325 	for (;;) {
326 		ev_change = 0;
327 		if (Fflag) {
328 			for (i = 0, file = files; i < no_files; i++, file++) {
329 				if (!file->fp) {
330 					file->fp = fopen(file->file_name, "r");
331 					if (file->fp != NULL &&
332 					    fstat(fileno(file->fp), &file->st)
333 					    == -1) {
334 						(void) fclose(file->fp);
335 						file->fp = NULL;
336 					}
337 					if (file->fp != NULL)
338 						ev_change++;
339 					continue;
340 				}
341 				if (fileno(file->fp) == STDIN_FILENO)
342 					continue;
343 				if (stat(file->file_name, &sb2) == -1) {
344 					if (errno != ENOENT)
345 						ierr(file->file_name);
346 					(void) show(file);
347 					(void) fclose(file->fp);
348 					file->fp = NULL;
349 					ev_change++;
350 					continue;
351 				}
352 
353 				if (sb2.st_ino != file->st.st_ino ||
354 				    sb2.st_dev != file->st.st_dev ||
355 				    sb2.st_nlink == 0) {
356 					(void) show(file);
357 					file->fp = freopen(file->file_name, "r",
358 					    file->fp);
359 					if (file->fp != NULL)
360 						(void) memcpy(&file->st, &sb2,
361 						    sizeof (struct stat));
362 					else if (errno != ENOENT)
363 						ierr(file->file_name);
364 					ev_change++;
365 				}
366 			}
367 		}
368 
369 		for (i = 0, file = files; i < no_files; i++, file++)
370 			if (file->fp && !show(file))
371 				ev_change++;
372 
373 		if (ev_change)
374 			set_events(files);
375 
376 		switch (action) {
377 		case USE_PORT:
378 			ts.tv_sec = 1;
379 			ts.tv_nsec = 0;
380 			/*
381 			 * In the -F case we set a timeout to ensure that
382 			 * we re-stat the file at least once every second.
383 			 */
384 			n = port_get(port, &ev, Fflag? &ts : NULL);
385 			if (n == 0) {
386 				file = (file_info_t *)ev.portev_user;
387 				(void) port_associate(port, PORT_SOURCE_FD,
388 				    fileno(file->fp), POLLIN, (void*)file);
389 			}
390 			break;
391 
392 		case USE_SLEEP:
393 			(void) usleep(250000);
394 			break;
395 		}
396 	}
397 }
398