1 /*-
2 * SPDX-License-Identifier: BSD-3-Clause
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
36
37 #include <sys/param.h>
38 #include <sys/mount.h>
39 #include <sys/types.h>
40 #include <sys/stat.h>
41 #include <sys/time.h>
42 #include <sys/mman.h>
43 #include <sys/event.h>
44
45 #include <err.h>
46 #include <errno.h>
47 #include <fcntl.h>
48 #include <limits.h>
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <unistd.h>
53
54 #include <libcasper.h>
55 #include <casper/cap_fileargs.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_KQUEUE 1
66 #define ADD_EVENTS 2
67
68 static struct kevent *ev;
69 static int action = USE_SLEEP;
70 static int kq;
71
72 static const file_info_t *last;
73
74 /*
75 * forward -- display the file, from an offset, forward.
76 *
77 * There are eight separate cases for this -- regular and non-regular
78 * files, by bytes or lines and from the beginning or end of the file.
79 *
80 * FBYTES byte offset from the beginning of the file
81 * REG seek
82 * NOREG read, counting bytes
83 *
84 * FLINES line offset from the beginning of the file
85 * REG read, counting lines
86 * NOREG read, counting lines
87 *
88 * RBYTES byte offset from the end of the file
89 * REG seek
90 * NOREG cyclically read characters into a wrap-around buffer
91 *
92 * RLINES
93 * REG mmap the file and step back until reach the correct offset.
94 * NOREG cyclically read lines into a wrap-around array of buffers
95 */
96 void
forward(FILE * fp,const char * fn,enum STYLE style,off_t off,struct stat * sbp)97 forward(FILE *fp, const char *fn, enum STYLE style, off_t off, struct stat *sbp)
98 {
99 int ch;
100
101 switch(style) {
102 case FBYTES:
103 if (off == 0)
104 break;
105 if (S_ISREG(sbp->st_mode) && sbp->st_size > 0) {
106 if (sbp->st_size < off)
107 off = sbp->st_size;
108 if (fseeko(fp, off, SEEK_SET) == -1) {
109 ierr(fn);
110 return;
111 }
112 } else while (off--)
113 if ((ch = getc(fp)) == EOF) {
114 if (ferror(fp)) {
115 ierr(fn);
116 return;
117 }
118 break;
119 }
120 break;
121 case FLINES:
122 if (off == 0)
123 break;
124 for (;;) {
125 if ((ch = getc(fp)) == EOF) {
126 if (ferror(fp)) {
127 ierr(fn);
128 return;
129 }
130 break;
131 }
132 if (ch == '\n' && !--off)
133 break;
134 }
135 break;
136 case RBYTES:
137 if (S_ISREG(sbp->st_mode) && sbp->st_size > 0) {
138 if (sbp->st_size >= off &&
139 fseeko(fp, -off, SEEK_END) == -1) {
140 ierr(fn);
141 return;
142 }
143 } else if (off == 0) {
144 while (getc(fp) != EOF);
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) && sbp->st_size > 0)
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 if (ferror(fp)) {
165 ierr(fn);
166 return;
167 }
168 } else
169 if (lines(fp, fn, off))
170 return;
171 break;
172 default:
173 break;
174 }
175
176 while ((ch = getc(fp)) != EOF)
177 if (putchar(ch) == EOF)
178 oerr();
179 if (ferror(fp)) {
180 ierr(fn);
181 return;
182 }
183 (void)fflush(stdout);
184 }
185
186 /*
187 * rlines -- display the last offset lines of the file.
188 */
189 static void
rlines(FILE * fp,const char * fn,off_t off,struct stat * sbp)190 rlines(FILE *fp, const char *fn, off_t off, struct stat *sbp)
191 {
192 struct mapinfo map;
193 off_t curoff, size;
194 int i;
195
196 if (!(size = sbp->st_size))
197 return;
198 map.start = NULL;
199 map.fd = fileno(fp);
200 map.mapoff = map.maxoff = size;
201
202 /*
203 * Last char is special, ignore whether newline or not. Note that
204 * size == 0 is dealt with above, and size == 1 sets curoff to -1.
205 */
206 curoff = size - 2;
207 while (curoff >= 0) {
208 if (curoff < map.mapoff && maparound(&map, curoff) != 0) {
209 ierr(fn);
210 return;
211 }
212 for (i = curoff - map.mapoff; i >= 0; i--)
213 if (map.start[i] == '\n' && --off == 0)
214 break;
215 /* `i' is either the map offset of a '\n', or -1. */
216 curoff = map.mapoff + i;
217 if (i >= 0)
218 break;
219 }
220 curoff++;
221 if (mapprint(&map, curoff, size - curoff) != 0) {
222 ierr(fn);
223 exit(1);
224 }
225
226 /* Set the file pointer to reflect the length displayed. */
227 if (fseeko(fp, sbp->st_size, SEEK_SET) == -1) {
228 ierr(fn);
229 return;
230 }
231 if (map.start != NULL && munmap(map.start, map.maplen)) {
232 ierr(fn);
233 return;
234 }
235 }
236
237 static int
show(file_info_t * file)238 show(file_info_t *file)
239 {
240 int ch;
241
242 while ((ch = getc(file->fp)) != EOF) {
243 if (last != file) {
244 if (vflag || (qflag == 0 && no_files > 1))
245 printfn(file->file_name, 1);
246 last = file;
247 }
248 if (putchar(ch) == EOF)
249 oerr();
250 }
251 (void)fflush(stdout);
252 if (ferror(file->fp)) {
253 fclose(file->fp);
254 file->fp = NULL;
255 ierr(file->file_name);
256 return 0;
257 }
258 clearerr(file->fp);
259 return 1;
260 }
261
262 static void
set_events(file_info_t * files)263 set_events(file_info_t *files)
264 {
265 int i, n = 0;
266 file_info_t *file;
267 struct timespec ts;
268 struct statfs sf;
269
270 ts.tv_sec = 0;
271 ts.tv_nsec = 0;
272
273 action = USE_KQUEUE;
274 for (i = 0, file = files; i < no_files; i++, file++) {
275 if (!file->fp)
276 continue;
277
278 if (fstatfs(fileno(file->fp), &sf) == 0 &&
279 (sf.f_flags & MNT_LOCAL) == 0) {
280 action = USE_SLEEP;
281 return;
282 }
283
284 if (Fflag && fileno(file->fp) != STDIN_FILENO) {
285 EV_SET(&ev[n], fileno(file->fp), EVFILT_VNODE,
286 EV_ADD | EV_ENABLE | EV_CLEAR,
287 NOTE_DELETE | NOTE_RENAME, 0, 0);
288 n++;
289 }
290 EV_SET(&ev[n], fileno(file->fp), EVFILT_READ,
291 EV_ADD | EV_ENABLE | EV_CLEAR, 0, 0, 0);
292 n++;
293 }
294
295 if (kevent(kq, ev, n, NULL, 0, &ts) < 0) {
296 action = USE_SLEEP;
297 }
298 }
299
300 /*
301 * follow -- display the file, from an offset, forward.
302 *
303 */
304 void
follow(file_info_t * files,enum STYLE style,off_t off)305 follow(file_info_t *files, enum STYLE style, off_t off)
306 {
307 int active, ev_change, i, n;
308 struct stat sb2;
309 file_info_t *file;
310 FILE *ftmp;
311 struct timespec ts;
312
313 /* Position each of the files */
314 active = 0;
315 for (i = 0, file = files; i < no_files; i++, file++) {
316 if (!file->fp)
317 continue;
318 active = 1;
319 if (vflag || (qflag == 0 && no_files > 1))
320 printfn(file->file_name, 1);
321 forward(file->fp, file->file_name, style, off, &file->st);
322 }
323 if (!Fflag && !active)
324 return;
325
326 last = --file;
327
328 kq = kqueue();
329 if (kq < 0)
330 err(1, "kqueue");
331 /*
332 * The number of kqueue events we track may vary over time and may
333 * even grow past its initial value in the -F case, but it will
334 * never exceed two per file, so just preallocate that.
335 */
336 ev = malloc(no_files * 2 * sizeof(struct kevent));
337 if (ev == NULL)
338 err(1, "failed to allocate memory for kevents");
339 set_events(files);
340
341 for (;;) {
342 ev_change = 0;
343 if (Fflag) {
344 for (i = 0, file = files; i < no_files; i++, file++) {
345 if (!file->fp) {
346 file->fp =
347 fileargs_fopen(fa, file->file_name,
348 "r");
349 if (file->fp != NULL &&
350 fstat(fileno(file->fp), &file->st)
351 == -1) {
352 fclose(file->fp);
353 file->fp = NULL;
354 }
355 if (file->fp != NULL)
356 ev_change++;
357 continue;
358 }
359 if (fileno(file->fp) == STDIN_FILENO)
360 continue;
361 ftmp = fileargs_fopen(fa, file->file_name, "r");
362 if (ftmp == NULL ||
363 fstat(fileno(ftmp), &sb2) == -1) {
364 if (errno != ENOENT)
365 ierr(file->file_name);
366 show(file);
367 if (file->fp != NULL) {
368 fclose(file->fp);
369 file->fp = NULL;
370 }
371 if (ftmp != NULL) {
372 fclose(ftmp);
373 }
374 ev_change++;
375 continue;
376 }
377
378 if (sb2.st_ino != file->st.st_ino ||
379 sb2.st_dev != file->st.st_dev ||
380 sb2.st_nlink == 0) {
381 show(file);
382 if (file->fp != NULL)
383 fclose(file->fp);
384 file->fp = ftmp;
385 memcpy(&file->st, &sb2,
386 sizeof(struct stat));
387 ev_change++;
388 } else {
389 fclose(ftmp);
390 }
391 }
392 }
393
394 for (i = 0, file = files; i < no_files; i++, file++)
395 if (file->fp && !show(file))
396 ev_change++;
397
398 if (ev_change)
399 set_events(files);
400
401 switch (action) {
402 case USE_KQUEUE:
403 ts.tv_sec = 1;
404 ts.tv_nsec = 0;
405 /*
406 * In the -F case we set a timeout to ensure that
407 * we re-stat the file at least once every second.
408 * If we've received EINTR, ignore it. Both reasons
409 * for its generation are transient.
410 */
411 do {
412 n = kevent(kq, NULL, 0, ev, 1, Fflag ? &ts : NULL);
413 if (n < 0 && errno != EINTR)
414 err(1, "kevent");
415 } while (n < 0);
416 if (n == 0) {
417 /* timeout */
418 break;
419 } else if (ev->filter == EVFILT_READ && ev->data < 0) {
420 /* file shrank, reposition to end */
421 if (lseek(ev->ident, (off_t)0, SEEK_END) == -1) {
422 ierr(file->file_name);
423 continue;
424 }
425 }
426 break;
427
428 case USE_SLEEP:
429 (void) usleep(250000);
430 break;
431 }
432 }
433 }
434