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