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 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 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 { 292 int ch; 293 294 while (!feof(fp) && (ch = getc(fp)) != EOF) 295 if (putchar(ch) == EOF) 296 oerr(); 297 } 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) == -1) { 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 && !(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