GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/tail/forward.c Lines: 21 163 12.9 %
Date: 2016-12-06 Branches: 25 223 11.2 %

Line Branch Exec Source
1
/*	$OpenBSD: forward.c,v 1.31 2016/07/05 05:06:27 jsg 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
79
forward(struct tailfile *tf, int nfiles, enum STYLE style, off_t origoff)
80
54
{
81
	int ch;
82
	struct tailfile *ctf, *ltf;
83
	struct kevent ke;
84
54
	const struct timespec *ts = NULL;
85
	int i;
86
	int nevents;
87
88
54
	if (nfiles < 1)
89
		return;
90
91

54
	if (fflag && (kq = kqueue()) < 0)
92
		warn("kqueue");
93
94
108
	for (i = 0; i < nfiles; i++) {
95
54
		off_t off = origoff;
96
54
		if (nfiles > 1)
97
			printfname(tf[i].fname);
98
99

54
		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
54
			if (off == 0)
121
				break;
122
			for (;;) {
123

1267
				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

1267
				if (ch == '\n' && !--off)
131
54
					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
54
		tfprint(tf[i].fp);
180

54
		if (fflag && tfqueue(&(tf[i])) == -1)
181
			warn("Unable to follow %s", tf[i].fname);
182
183
	}
184
54
	ltf = &(tf[i-1]);
185
186
54
	(void)fflush(stdout);
187

54
	if (!fflag || kq < 0)
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
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
290
tfprint(FILE *fp)
291
54
{
292
	int ch;
293
294



1589
	while (!feof(fp) && (ch = getc(fp)) != EOF)
295

1481
		if (putchar(ch) == EOF)
296
			oerr();
297
54
}
298
299
static int
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) < 0) {
321
		ierr(tf->fname);
322
		return -1;
323
	}
324
	return 0;
325
}
326
327
#define AFILESINCR 8
328
static const struct timespec *
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 && ((stat(tf->fname, &sb) != 0) || sb.st_ino != tf->sb.st_ino)) {
339
		if (afiles < ++nfiles) {
340
			afiles += AFILESINCR;
341
			treopen = reallocarray(reopen, afiles, sizeof(*reopen));
342
			if (treopen)
343
				reopen = treopen;
344
			else
345
				afiles -= AFILESINCR;
346
		}
347
		if (nfiles <= afiles) {
348
			for (i = 0; i < nfiles - 1; i++)
349
				if (strcmp(reopen[i]->fname, tf->fname) == 0)
350
					break;
351
			if (i < nfiles - 1)
352
				nfiles--;
353
			else
354
				reopen[nfiles-1] = tf;
355
		} else {
356
			warnx("Lost track of %s", tf->fname);
357
			nfiles--;
358
		}
359
	}
360
361
	for (i = 0; i < nfiles; i++) {
362
		ttf = reopen[i];
363
		if (stat(ttf->fname, &sb) == -1)
364
			continue;
365
		if (sb.st_ino != ttf->sb.st_ino) {
366
			(void) memcpy(&(ttf->sb), &sb, sizeof(ttf->sb));
367
			ttf->fp = freopen(ttf->fname, "r", ttf->fp);
368
			if (ttf->fp == NULL)
369
				ierr(ttf->fname);
370
			else {
371
				warnx("%s has been replaced, reopening.",
372
				    ttf->fname);
373
				tfqueue(ttf);
374
			}
375
		}
376
		reopen[i] = reopen[--nfiles];
377
	}
378
379
	return nfiles ? &ts : NULL;
380
}