1 /* $NetBSD: forward.c,v 1.34 2024/01/14 17:37:32 christos Exp $ */
2
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 #ifndef lint
37 #if 0
38 static char sccsid[] = "@(#)forward.c 8.1 (Berkeley) 6/6/93";
39 #endif
40 __RCSID("$NetBSD: forward.c,v 1.34 2024/01/14 17:37:32 christos Exp $");
41 #endif /* not lint */
42
43 #include <sys/types.h>
44 #include <sys/stat.h>
45 #include <sys/time.h>
46 #include <sys/mman.h>
47 #include <sys/event.h>
48
49 #include <limits.h>
50 #include <fcntl.h>
51 #include <errno.h>
52 #include <unistd.h>
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <string.h>
56 #include "extern.h"
57
58 static int rlines(FILE *, off_t, struct stat *);
59
60 /* defines for inner loop actions */
61 #define USE_SLEEP 0
62 #define USE_KQUEUE 1
63 #define ADD_EVENTS 2
64
65 /*
66 * forward -- display the file, from an offset, forward.
67 *
68 * There are eight separate cases for this -- regular and non-regular
69 * files, by bytes or lines and from the beginning or end of the file.
70 *
71 * FBYTES byte offset from the beginning of the file
72 * REG seek
73 * NOREG read, counting bytes
74 *
75 * FLINES line offset from the beginning of the file
76 * REG read, counting lines
77 * NOREG read, counting lines
78 *
79 * RBYTES byte offset from the end of the file
80 * REG seek
81 * NOREG cyclically read characters into a wrap-around buffer
82 *
83 * RLINES
84 * REG mmap the file and step back until reach the correct offset.
85 * NOREG cyclically read lines into a wrap-around array of buffers
86 */
87 void
forward(FILE * fp,enum STYLE style,off_t off,struct stat * sbp)88 forward(FILE *fp, enum STYLE style, off_t off, struct stat *sbp)
89 {
90 int ch, n;
91 int kq=-1, action=USE_SLEEP;
92 struct stat statbuf;
93 struct kevent ev[2];
94
95 switch(style) {
96 case FBYTES:
97 if (off == 0)
98 break;
99 if (S_ISREG(sbp->st_mode) && sbp->st_size > 0) {
100 if (sbp->st_size < off)
101 off = sbp->st_size;
102 if (fseeko(fp, off, SEEK_SET) == -1) {
103 ierr();
104 return;
105 }
106 } else while (off--)
107 if ((ch = getc(fp)) == EOF) {
108 if (ferror(fp)) {
109 ierr();
110 return;
111 }
112 break;
113 }
114 break;
115 case FLINES:
116 if (off == 0)
117 break;
118 for (;;) {
119 if ((ch = getc(fp)) == EOF) {
120 if (ferror(fp)) {
121 ierr();
122 return;
123 }
124 break;
125 }
126 if (ch == '\n' && !--off)
127 break;
128 }
129 break;
130 case RBYTES:
131 if (S_ISREG(sbp->st_mode) && sbp->st_size > 0) {
132 if (sbp->st_size >= off &&
133 fseeko(fp, -off, SEEK_END) == -1) {
134 ierr();
135 return;
136 }
137 } else if (off == 0) {
138 while (getc(fp) != EOF);
139 if (ferror(fp)) {
140 ierr();
141 return;
142 }
143 } else {
144 if (displaybytes(fp, off))
145 return;
146 }
147 break;
148 case RLINES:
149 if (S_ISREG(sbp->st_mode) && sbp->st_size > 0) {
150 if (!off) {
151 if (fseek(fp, 0L, SEEK_END) == -1) {
152 ierr();
153 return;
154 }
155 } else {
156 if (rlines(fp, off, sbp))
157 return;
158 }
159 } else if (off == 0) {
160 while (getc(fp) != EOF);
161 if (ferror(fp)) {
162 ierr();
163 return;
164 }
165 } else {
166 if (displaylines(fp, off))
167 return;
168 }
169 break;
170 default:
171 break;
172 }
173
174 if (fflag) {
175 kq = kqueue();
176 if (kq < 0)
177 xerr(1, "kqueue");
178 action = ADD_EVENTS;
179 }
180
181 for (;;) {
182 while ((ch = getc(fp)) != EOF) {
183 if (putchar(ch) == EOF)
184 oerr();
185 }
186 if (ferror(fp)) {
187 ierr();
188 return;
189 }
190 (void)fflush(stdout);
191 if (!fflag)
192 break;
193
194 clearerr(fp);
195
196 switch (action) {
197 case ADD_EVENTS:
198 n = 0;
199
200 memset(ev, 0, sizeof(ev));
201 if (fflag == 2 && fp != stdin) {
202 EV_SET(&ev[n], fileno(fp), EVFILT_VNODE,
203 EV_ADD | EV_ENABLE | EV_CLEAR,
204 NOTE_DELETE | NOTE_RENAME, 0, 0);
205 n++;
206 }
207 EV_SET(&ev[n], fileno(fp), EVFILT_READ,
208 EV_ADD | EV_ENABLE, 0, 0, 0);
209 n++;
210
211 if (kevent(kq, ev, n, NULL, 0, NULL) == -1) {
212 close(kq);
213 kq = -1;
214 action = USE_SLEEP;
215 } else {
216 action = USE_KQUEUE;
217 }
218 break;
219
220 case USE_KQUEUE:
221 if (kevent(kq, NULL, 0, ev, 1, NULL) == -1)
222 xerr(1, "kevent");
223
224 if (ev[0].filter == EVFILT_VNODE) {
225 /* file was rotated, wait until it reappears */
226 action = USE_SLEEP;
227 } else if (ev[0].data < 0) {
228 /* file shrank, reposition to end */
229 if (fseek(fp, 0L, SEEK_END) == -1) {
230 ierr();
231 return;
232 }
233 }
234 break;
235
236 case USE_SLEEP:
237 /*
238 * We pause for one second after displaying any data
239 * that has accumulated since we read the file.
240 */
241 (void) sleep(1);
242
243 if (fflag == 2 && fp != stdin &&
244 stat(fname, &statbuf) != -1) {
245 if (statbuf.st_ino != sbp->st_ino ||
246 statbuf.st_dev != sbp->st_dev ||
247 statbuf.st_rdev != sbp->st_rdev ||
248 statbuf.st_nlink == 0) {
249 fp = freopen(fname, "r", fp);
250 if (fp == NULL) {
251 ierr();
252 goto out;
253 }
254 *sbp = statbuf;
255 if (kq != -1)
256 action = ADD_EVENTS;
257 } else if (kq != -1)
258 action = USE_KQUEUE;
259 }
260 break;
261 }
262 }
263 out:
264 if (fflag && kq != -1)
265 close(kq);
266 }
267
268 /*
269 * rlines -- display the last offset lines of the file.
270 *
271 * Non-zero return means than a (non-fatal) error occurred.
272 */
273 static int
rlines(FILE * fp,off_t off,struct stat * sbp)274 rlines(FILE *fp, off_t off, struct stat *sbp)
275 {
276 off_t file_size;
277 off_t file_remaining;
278 char *p = NULL;
279 char *start = NULL;
280 off_t mmap_size;
281 off_t mmap_offset;
282 off_t mmap_remaining = 0;
283
284 #define MMAP_MAXSIZE (10 * 1024 * 1024)
285
286 if (!(file_size = sbp->st_size))
287 return 0;
288 file_remaining = file_size;
289
290 if (file_remaining > MMAP_MAXSIZE) {
291 mmap_size = MMAP_MAXSIZE;
292 mmap_offset = file_remaining - MMAP_MAXSIZE;
293 } else {
294 mmap_size = file_remaining;
295 mmap_offset = 0;
296 }
297
298 while (off) {
299 start = mmap(NULL, (size_t)mmap_size, PROT_READ,
300 MAP_FILE|MAP_SHARED, fileno(fp), mmap_offset);
301 if (start == MAP_FAILED) {
302 xerr(0, "%s", fname);
303 return 1;
304 }
305
306 mmap_remaining = mmap_size;
307 /* Last char is special, ignore whether newline or not. */
308 for (p = start + mmap_remaining - 1 ; --mmap_remaining ; )
309 if (*--p == '\n' && !--off) {
310 ++p;
311 break;
312 }
313
314 file_remaining -= mmap_size - mmap_remaining;
315
316 if (off == 0)
317 break;
318
319 if (file_remaining == 0)
320 break;
321
322 if (munmap(start, mmap_size)) {
323 xerr(0, "%s", fname);
324 return 1;
325 }
326
327 if (mmap_offset >= MMAP_MAXSIZE) {
328 mmap_offset -= MMAP_MAXSIZE;
329 } else {
330 mmap_offset = 0;
331 mmap_size = file_remaining;
332 }
333 }
334
335 /*
336 * Output the (perhaps partial) data in this mmap'd block.
337 */
338 WR(p, mmap_size - mmap_remaining);
339 file_remaining += mmap_size - mmap_remaining;
340 if (munmap(start, mmap_size)) {
341 xerr(0, "%s", fname);
342 return 1;
343 }
344
345 /*
346 * Set the file pointer to reflect the length displayed.
347 * This will cause the caller to redisplay the data if/when
348 * needed.
349 */
350 if (fseeko(fp, file_remaining, SEEK_SET) == -1) {
351 ierr();
352 return 1;
353 }
354 return 0;
355 }
356