1 /* $OpenBSD: forward.c,v 1.33 2019/06/28 13:35:04 deraadt Exp $ */
2 /* $NetBSD: forward.c,v 1.7 1996/02/13 16:49:10 ghudson Exp $ */
3
4 /*-
5 * Copyright (c) 1991, 1993
6 * The Regents of the University of California. All rights reserved.
7 *
8 * This code is derived from software contributed to Berkeley by
9 * Edward Sze-Tyan Wang.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 */
35
36 #include <sys/types.h>
37 #include <sys/stat.h>
38 #include <sys/event.h>
39
40 #include <err.h>
41 #include <errno.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <unistd.h>
46
47 #include "extern.h"
48
49 static int rlines(struct tailfile *, off_t);
50 static inline void tfprint(FILE *fp);
51 static int tfqueue(struct tailfile *tf);
52 static const struct timespec *tfreopen(struct tailfile *tf);
53
54 static int kq = -1;
55
56 /*
57 * forward -- display the file, from an offset, forward.
58 *
59 * There are eight separate cases for this -- regular and non-regular
60 * files, by bytes or lines and from the beginning or end of the file.
61 *
62 * FBYTES byte offset from the beginning of the file
63 * REG seek
64 * NOREG read, counting bytes
65 *
66 * FLINES line offset from the beginning of the file
67 * REG read, counting lines
68 * NOREG read, counting lines
69 *
70 * RBYTES byte offset from the end of the file
71 * REG seek
72 * NOREG cyclically read characters into a wrap-around buffer
73 *
74 * RLINES
75 * REG step back until the correct offset is reached.
76 * NOREG cyclically read lines into a wrap-around array of buffers
77 */
78 void
forward(struct tailfile * tf,int nfiles,enum STYLE style,off_t origoff)79 forward(struct tailfile *tf, int nfiles, enum STYLE style, off_t origoff)
80 {
81 int ch;
82 struct tailfile *ctf, *ltf;
83 struct kevent ke;
84 const struct timespec *ts = NULL;
85 int i;
86 int nevents;
87
88 if (nfiles < 1)
89 return;
90
91 if (fflag && (kq = kqueue()) == -1)
92 warn("kqueue");
93
94 for (i = 0; i < nfiles; i++) {
95 off_t off = origoff;
96 if (nfiles > 1)
97 printfname(tf[i].fname);
98
99 switch(style) {
100 case FBYTES:
101 if (off == 0)
102 break;
103 if (S_ISREG(tf[i].sb.st_mode)) {
104 if (tf[i].sb.st_size < off)
105 off = tf[i].sb.st_size;
106 if (fseeko(tf[i].fp, off, SEEK_SET) == -1) {
107 ierr(tf[i].fname);
108 return;
109 }
110 } else while (off--)
111 if ((ch = getc(tf[i].fp)) == EOF) {
112 if (ferror(tf[i].fp)) {
113 ierr(tf[i].fname);
114 return;
115 }
116 break;
117 }
118 break;
119 case FLINES:
120 if (off == 0)
121 break;
122 for (;;) {
123 if ((ch = getc(tf[i].fp)) == EOF) {
124 if (ferror(tf[i].fp)) {
125 ierr(tf[i].fname);
126 return;
127 }
128 break;
129 }
130 if (ch == '\n' && !--off)
131 break;
132 }
133 break;
134 case RBYTES:
135 if (S_ISREG(tf[i].sb.st_mode)) {
136 if (tf[i].sb.st_size >= off &&
137 fseeko(tf[i].fp, -off, SEEK_END) == -1) {
138 ierr(tf[i].fname);
139 return;
140 }
141 } else if (off == 0) {
142 while (getc(tf[i].fp) != EOF)
143 ;
144 if (ferror(tf[i].fp)) {
145 ierr(tf[i].fname);
146 return;
147 }
148 } else {
149 if (bytes(&(tf[i]), off))
150 return;
151 }
152 break;
153 case RLINES:
154 if (S_ISREG(tf[i].sb.st_mode)) {
155 if (!off) {
156 if (fseeko(tf[i].fp, (off_t)0,
157 SEEK_END) == -1) {
158 ierr(tf[i].fname);
159 return;
160 }
161 } else if (rlines(&(tf[i]), off) != 0)
162 lines(&(tf[i]), off);
163 } else if (off == 0) {
164 while (getc(tf[i].fp) != EOF)
165 ;
166 if (ferror(tf[i].fp)) {
167 ierr(tf[i].fname);
168 return;
169 }
170 } else {
171 if (lines(&(tf[i]), off))
172 return;
173 }
174 break;
175 default:
176 err(1, "Unsupported style");
177 }
178
179 tfprint(tf[i].fp);
180 if (fflag && tfqueue(&(tf[i])) == -1)
181 warn("Unable to follow %s", tf[i].fname);
182
183 }
184 ltf = &(tf[i-1]);
185
186 (void)fflush(stdout);
187 if (!fflag || kq == -1)
188 return;
189
190 while (1) {
191 if ((nevents = kevent(kq, NULL, 0, &ke, 1, ts)) <= 0) {
192 if (errno == EINTR) {
193 close(kq);
194 return;
195 }
196 }
197
198 ctf = ke.udata;
199 if (nevents > 0) {
200 if (ke.filter == EVFILT_READ) {
201 if (ctf != ltf) {
202 printfname(ctf->fname);
203 ltf = ctf;
204 }
205 clearerr(ctf->fp);
206 tfprint(ctf->fp);
207 if (ferror(ctf->fp)) {
208 ierr(ctf->fname);
209 fclose(ctf->fp);
210 warn("Lost file %s", ctf->fname);
211 continue;
212 }
213 (void)fflush(stdout);
214 clearerr(ctf->fp);
215 } else if (ke.filter == EVFILT_VNODE) {
216 if (ke.fflags & (NOTE_DELETE | NOTE_RENAME)) {
217 /*
218 * File was deleted or renamed.
219 *
220 * Continue to look at it until
221 * a new file reappears with
222 * the same name.
223 */
224 (void) tfreopen(ctf);
225 } else if (ke.fflags & NOTE_TRUNCATE) {
226 warnx("%s has been truncated, "
227 "resetting.", ctf->fname);
228 fpurge(ctf->fp);
229 rewind(ctf->fp);
230 }
231 }
232 }
233 ts = tfreopen(NULL);
234 }
235 }
236
237 /*
238 * rlines -- display the last offset lines of the file.
239 */
240 static int
rlines(struct tailfile * tf,off_t off)241 rlines(struct tailfile *tf, off_t off)
242 {
243 off_t pos;
244 int ch;
245
246 pos = tf->sb.st_size;
247 if (pos == 0)
248 return (0);
249
250 /*
251 * Position before char.
252 * Last char is special, ignore it whether newline or not.
253 */
254 pos -= 2;
255 ch = EOF;
256 for (; off > 0 && pos >= 0; pos--) {
257 /* A seek per char isn't a problem with a smart stdio */
258 if (fseeko(tf[0].fp, pos, SEEK_SET) == -1) {
259 ierr(tf->fname);
260 return (1);
261 }
262 if ((ch = getc(tf[0].fp)) == '\n')
263 off--;
264 else if (ch == EOF) {
265 if (ferror(tf[0].fp)) {
266 ierr(tf->fname);
267 return (1);
268 }
269 break;
270 }
271 }
272 /* If we read until start of file, put back last read char */
273 if (pos < 0 && off > 0 && ch != EOF && ungetc(ch, tf[0].fp) == EOF) {
274 ierr(tf->fname);
275 return (1);
276 }
277
278 while (!feof(tf[0].fp) && (ch = getc(tf[0].fp)) != EOF)
279 if (putchar(ch) == EOF)
280 oerr();
281 if (ferror(tf[0].fp)) {
282 ierr(tf->fname);
283 return (1);
284 }
285
286 return (0);
287 }
288
289 static inline void
tfprint(FILE * fp)290 tfprint(FILE *fp)
291 {
292 int ch;
293
294 while (!feof(fp) && (ch = getc(fp)) != EOF)
295 if (putchar(ch) == EOF)
296 oerr();
297 }
298
299 static int
tfqueue(struct tailfile * tf)300 tfqueue(struct tailfile *tf)
301 {
302 struct kevent ke[2];
303 int i = 1;
304
305 if (kq < 0) {
306 errno = EBADF;
307 return -1;
308 }
309
310 EV_SET(&(ke[0]), fileno(tf->fp), EVFILT_READ,
311 EV_ENABLE | EV_ADD | EV_CLEAR, 0, 0, tf);
312
313 if (S_ISREG(tf->sb.st_mode)) {
314 i = 2;
315 EV_SET(&(ke[1]), fileno(tf->fp), EVFILT_VNODE,
316 EV_ENABLE | EV_ADD | EV_CLEAR,
317 NOTE_DELETE | NOTE_RENAME | NOTE_TRUNCATE,
318 0, tf);
319 }
320 if (kevent(kq, ke, i, NULL, 0, NULL) == -1) {
321 ierr(tf->fname);
322 return -1;
323 }
324 return 0;
325 }
326
327 #define AFILESINCR 8
328 static const struct timespec *
tfreopen(struct tailfile * tf)329 tfreopen(struct tailfile *tf) {
330 static struct tailfile **reopen = NULL;
331 static int nfiles = 0, afiles = 0;
332 static const struct timespec ts = {1, 0};
333
334 struct stat sb;
335 struct tailfile **treopen, *ttf;
336 int i;
337
338 if (tf && !(tf->fp == stdin) &&
339 ((stat(tf->fname, &sb) != 0) || sb.st_ino != tf->sb.st_ino)) {
340 if (afiles < ++nfiles) {
341 afiles += AFILESINCR;
342 treopen = reallocarray(reopen, afiles, sizeof(*reopen));
343 if (treopen)
344 reopen = treopen;
345 else
346 afiles -= AFILESINCR;
347 }
348 if (nfiles <= afiles) {
349 for (i = 0; i < nfiles - 1; i++)
350 if (strcmp(reopen[i]->fname, tf->fname) == 0)
351 break;
352 if (i < nfiles - 1)
353 nfiles--;
354 else
355 reopen[nfiles-1] = tf;
356 } else {
357 warnx("Lost track of %s", tf->fname);
358 nfiles--;
359 }
360 }
361
362 for (i = 0; i < nfiles; i++) {
363 ttf = reopen[i];
364 if (stat(ttf->fname, &sb) == -1)
365 continue;
366 if (sb.st_ino != ttf->sb.st_ino) {
367 (void) memcpy(&(ttf->sb), &sb, sizeof(ttf->sb));
368 ttf->fp = freopen(ttf->fname, "r", ttf->fp);
369 if (ttf->fp == NULL)
370 ierr(ttf->fname);
371 else {
372 warnx("%s has been replaced, reopening.",
373 ttf->fname);
374 tfqueue(ttf);
375 }
376 }
377 reopen[i] = reopen[--nfiles];
378 }
379
380 return nfiles ? &ts : NULL;
381 }
382