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