aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDuncaen <mail@duncano.de>2018-03-06 23:44:33 +0100
committerDuncaen <mail@duncano.de>2018-03-06 23:44:33 +0100
commit1cc00733691a45fda2731f8efc2471d763caf786 (patch)
tree31d926e8f9ecd80cc67b667fc2c3a7c7afe17f8b
parent5dcb7d6e947d5e24baad9b61fc4e5473e65777bf (diff)
downloadlobase-1cc00733691a45fda2731f8efc2471d763caf786.tar.gz
usr.bin/tail: import from OPENBSD_6_2 and add tail -f implementaion using inotify for linux
-rw-r--r--README9
-rwxr-xr-xREADME.sh3
-rw-r--r--usr.bin/Makefile7
-rw-r--r--usr.bin/tail/Makefile9
-rw-r--r--usr.bin/tail/PORT_NOTES3
-rw-r--r--usr.bin/tail/extern.h63
-rw-r--r--usr.bin/tail/forward.c380
-rw-r--r--usr.bin/tail/forward_linux.c411
-rw-r--r--usr.bin/tail/misc.c62
-rw-r--r--usr.bin/tail/read.c226
-rw-r--r--usr.bin/tail/reverse.c279
-rw-r--r--usr.bin/tail/tail.1187
-rw-r--r--usr.bin/tail/tail.c299
13 files changed, 1929 insertions, 9 deletions
diff --git a/README b/README
index e60fa70..27ba3f6 100644
--- a/README
+++ b/README
@@ -71,9 +71,6 @@ TODO
- install headers
- build shared libraries?
-- usr.bin/tail
- - import, rewrite -F or add a shim for kqueue?
-
- libtool?
lib
@@ -104,6 +101,7 @@ md5
mkdir
mv
pax
+ps
pwd
rm
rmdir
@@ -198,10 +196,14 @@ sort
spell
split
stat Does not support file flags, generations and birthtime on linux
+tail There are small differences in how -f handles file truncations,
+ kqueue provides events for file truncations, inotify just notifies
+ about a file modification.
tee
telnet
tftp
time
+top
touch
tr
true
@@ -218,6 +220,7 @@ uudecode
uuencode
vacation
vis
+w
wc
what
which
diff --git a/README.sh b/README.sh
index fd31ba8..38427f7 100755
--- a/README.sh
+++ b/README.sh
@@ -72,9 +72,6 @@ TODO
- install headers
- build shared libraries?
-- usr.bin/tail
- - import, rewrite -F or add a shim for kqueue?
-
- libtool?
lib
diff --git a/usr.bin/Makefile b/usr.bin/Makefile
index c1a16c9..3feffb8 100644
--- a/usr.bin/Makefile
+++ b/usr.bin/Makefile
@@ -4,8 +4,9 @@ SUBDIR= apply awk basename bc biff cal calendar cmp colrm col column comm \
from ftp getconf getent getopt grep head hexdump id indent join jot lam leave \
lndir logger logname look lorder mail mkdep mktemp nice nl nohup paste patch \
pkg-config pr printenv printf readlink renice rev rs sdiff sed shar signify \
- sort spell split stat tee telnet tftp time touch tr true tsort tty ul units uname unexpand \
- uniq unvis uudecode uuencode vacation vis wc what which xinstall htpasswd cu newsyslog \
- banner cap_mkdb unifdef whois xargs csplit compress ctags deroff yes
+ sort spell split stat tail tee telnet tftp time touch tr true tsort tty \
+ ul units uname unexpand uniq unvis uudecode uuencode vacation vis wc what \
+ which xinstall htpasswd cu newsyslog banner cap_mkdb unifdef whois xargs \
+ csplit compress ctags deroff yes
SKIPDIR=file ftp cu
include ${.TOPDIR}/mk/bsd.subdir.mk
diff --git a/usr.bin/tail/Makefile b/usr.bin/tail/Makefile
new file mode 100644
index 0000000..517bf6f
--- /dev/null
+++ b/usr.bin/tail/Makefile
@@ -0,0 +1,9 @@
+# $OpenBSD: Makefile,v 1.5 1999/02/03 02:09:30 millert Exp $
+
+.TOPDIR?= ../..
+
+PROG= tail
+SRCS= forward_linux.c misc.c read.c reverse.c tail.c
+COPTS+= -Wall
+
+include ${.TOPDIR}/mk/bsd.prog.mk
diff --git a/usr.bin/tail/PORT_NOTES b/usr.bin/tail/PORT_NOTES
new file mode 100644
index 0000000..1a6e1c6
--- /dev/null
+++ b/usr.bin/tail/PORT_NOTES
@@ -0,0 +1,3 @@
+There are small differences in how -f handles file truncations,
+kqueue provides events for file truncations, inotify just notifies
+about a file modification.
diff --git a/usr.bin/tail/extern.h b/usr.bin/tail/extern.h
new file mode 100644
index 0000000..7cd29e8
--- /dev/null
+++ b/usr.bin/tail/extern.h
@@ -0,0 +1,63 @@
+/* $OpenBSD: extern.h,v 1.12 2015/11/19 17:50:04 tedu Exp $ */
+/* $NetBSD: extern.h,v 1.3 1994/11/23 07:42:00 jtc Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)extern.h 8.1 (Berkeley) 6/6/93
+ */
+
+#define WR(p, size) \
+ if (write(STDOUT_FILENO, p, size) != size) \
+ oerr();
+
+struct tailfile {
+ char *fname;
+ FILE *fp;
+ struct stat sb;
+#ifdef __linux__
+ int fwd; /* inotify file watch descriptor */
+ int dwd; /* inotify dir watch descriptor */
+ long pos; /* used to keep track of truncations */
+#endif
+};
+
+enum STYLE { NOTSET = 0, FBYTES, FLINES, RBYTES, RLINES, REVERSE };
+
+void forward(struct tailfile *, int, enum STYLE, off_t);
+void reverse(struct tailfile *, int, enum STYLE, off_t);
+
+int bytes(struct tailfile *, off_t);
+int lines(struct tailfile *, off_t);
+
+void ierr(const char *);
+void oerr(void);
+void printfname(const char *);
+
+extern int fflag, rflag, rval;
+extern int is_stdin;
diff --git a/usr.bin/tail/forward.c b/usr.bin/tail/forward.c
new file mode 100644
index 0000000..ca8ed65
--- /dev/null
+++ b/usr.bin/tail/forward.c
@@ -0,0 +1,380 @@
+/* $OpenBSD: forward.c,v 1.31 2016/07/05 05:06:27 jsg Exp $ */
+/* $NetBSD: forward.c,v 1.7 1996/02/13 16:49:10 ghudson Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Edward Sze-Tyan Wang.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/event.h>
+
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "extern.h"
+
+static int rlines(struct tailfile *, off_t);
+static inline void tfprint(FILE *fp);
+static int tfqueue(struct tailfile *tf);
+static const struct timespec *tfreopen(struct tailfile *tf);
+
+static int kq = -1;
+
+/*
+ * forward -- display the file, from an offset, forward.
+ *
+ * There are eight separate cases for this -- regular and non-regular
+ * files, by bytes or lines and from the beginning or end of the file.
+ *
+ * FBYTES byte offset from the beginning of the file
+ * REG seek
+ * NOREG read, counting bytes
+ *
+ * FLINES line offset from the beginning of the file
+ * REG read, counting lines
+ * NOREG read, counting lines
+ *
+ * RBYTES byte offset from the end of the file
+ * REG seek
+ * NOREG cyclically read characters into a wrap-around buffer
+ *
+ * RLINES
+ * REG step back until the correct offset is reached.
+ * NOREG cyclically read lines into a wrap-around array of buffers
+ */
+void
+forward(struct tailfile *tf, int nfiles, enum STYLE style, off_t origoff)
+{
+ int ch;
+ struct tailfile *ctf, *ltf;
+ struct kevent ke;
+ const struct timespec *ts = NULL;
+ int i;
+ int nevents;
+
+ if (nfiles < 1)
+ return;
+
+ if (fflag && (kq = kqueue()) < 0)
+ warn("kqueue");
+
+ for (i = 0; i < nfiles; i++) {
+ off_t off = origoff;
+ if (nfiles > 1)
+ printfname(tf[i].fname);
+
+ switch(style) {
+ case FBYTES:
+ if (off == 0)
+ break;
+ if (S_ISREG(tf[i].sb.st_mode)) {
+ if (tf[i].sb.st_size < off)
+ off = tf[i].sb.st_size;
+ if (fseeko(tf[i].fp, off, SEEK_SET) == -1) {
+ ierr(tf[i].fname);
+ return;
+ }
+ } else while (off--)
+ if ((ch = getc(tf[i].fp)) == EOF) {
+ if (ferror(tf[i].fp)) {
+ ierr(tf[i].fname);
+ return;
+ }
+ break;
+ }
+ break;
+ case FLINES:
+ if (off == 0)
+ break;
+ for (;;) {
+ if ((ch = getc(tf[i].fp)) == EOF) {
+ if (ferror(tf[i].fp)) {
+ ierr(tf[i].fname);
+ return;
+ }
+ break;
+ }
+ if (ch == '\n' && !--off)
+ break;
+ }
+ break;
+ case RBYTES:
+ if (S_ISREG(tf[i].sb.st_mode)) {
+ if (tf[i].sb.st_size >= off &&
+ fseeko(tf[i].fp, -off, SEEK_END) == -1) {
+ ierr(tf[i].fname);
+ return;
+ }
+ } else if (off == 0) {
+ while (getc(tf[i].fp) != EOF)
+ ;
+ if (ferror(tf[i].fp)) {
+ ierr(tf[i].fname);
+ return;
+ }
+ } else {
+ if (bytes(&(tf[i]), off))
+ return;
+ }
+ break;
+ case RLINES:
+ if (S_ISREG(tf[i].sb.st_mode)) {
+ if (!off) {
+ if (fseeko(tf[i].fp, (off_t)0,
+ SEEK_END) == -1) {
+ ierr(tf[i].fname);
+ return;
+ }
+ } else if (rlines(&(tf[i]), off) != 0)
+ lines(&(tf[i]), off);
+ } else if (off == 0) {
+ while (getc(tf[i].fp) != EOF)
+ ;
+ if (ferror(tf[i].fp)) {
+ ierr(tf[i].fname);
+ return;
+ }
+ } else {
+ if (lines(&(tf[i]), off))
+ return;
+ }
+ break;
+ default:
+ err(1, "Unsupported style");
+ }
+
+ tfprint(tf[i].fp);
+ if (fflag && tfqueue(&(tf[i])) == -1)
+ warn("Unable to follow %s", tf[i].fname);
+
+ }
+ ltf = &(tf[i-1]);
+
+ (void)fflush(stdout);
+ if (!fflag || kq < 0)
+ return;
+
+ while (1) {
+ if ((nevents = kevent(kq, NULL, 0, &ke, 1, ts)) <= 0) {
+ if (errno == EINTR) {
+ close(kq);
+ return;
+ }
+ }
+
+ ctf = ke.udata;
+ if (nevents > 0) {
+ if (ke.filter == EVFILT_READ) {
+ if (ctf != ltf) {
+ printfname(ctf->fname);
+ ltf = ctf;
+ }
+ clearerr(ctf->fp);
+ tfprint(ctf->fp);
+ if (ferror(ctf->fp)) {
+ ierr(ctf->fname);
+ fclose(ctf->fp);
+ warn("Lost file %s", ctf->fname);
+ continue;
+ }
+ (void)fflush(stdout);
+ clearerr(ctf->fp);
+ } else if (ke.filter == EVFILT_VNODE) {
+ if (ke.fflags & (NOTE_DELETE | NOTE_RENAME)) {
+ /*
+ * File was deleted or renamed.
+ *
+ * Continue to look at it until
+ * a new file reappears with
+ * the same name.
+ */
+ (void) tfreopen(ctf);
+ } else if (ke.fflags & NOTE_TRUNCATE) {
+ warnx("%s has been truncated, "
+ "resetting.", ctf->fname);
+ fpurge(ctf->fp);
+ rewind(ctf->fp);
+ }
+ }
+ }
+ ts = tfreopen(NULL);
+ }
+}
+
+/*
+ * rlines -- display the last offset lines of the file.
+ */
+static int
+rlines(struct tailfile *tf, off_t off)
+{
+ off_t pos;
+ int ch;
+
+ pos = tf->sb.st_size;
+ if (pos == 0)
+ return (0);
+
+ /*
+ * Position before char.
+ * Last char is special, ignore it whether newline or not.
+ */
+ pos -= 2;
+ ch = EOF;
+ for (; off > 0 && pos >= 0; pos--) {
+ /* A seek per char isn't a problem with a smart stdio */
+ if (fseeko(tf[0].fp, pos, SEEK_SET) == -1) {
+ ierr(tf->fname);
+ return (1);
+ }
+ if ((ch = getc(tf[0].fp)) == '\n')
+ off--;
+ else if (ch == EOF) {
+ if (ferror(tf[0].fp)) {
+ ierr(tf->fname);
+ return (1);
+ }
+ break;
+ }
+ }
+ /* If we read until start of file, put back last read char */
+ if (pos < 0 && off > 0 && ch != EOF && ungetc(ch, tf[0].fp) == EOF) {
+ ierr(tf->fname);
+ return (1);
+ }
+
+ while (!feof(tf[0].fp) && (ch = getc(tf[0].fp)) != EOF)
+ if (putchar(ch) == EOF)
+ oerr();
+ if (ferror(tf[0].fp)) {
+ ierr(tf->fname);
+ return (1);
+ }
+
+ return (0);
+}
+
+static inline void
+tfprint(FILE *fp)
+{
+ int ch;
+
+ while (!feof(fp) && (ch = getc(fp)) != EOF)
+ if (putchar(ch) == EOF)
+ oerr();
+}
+
+static int
+tfqueue(struct tailfile *tf)
+{
+ struct kevent ke[2];
+ int i = 1;
+
+ if (kq < 0) {
+ errno = EBADF;
+ return -1;
+ }
+
+ EV_SET(&(ke[0]), fileno(tf->fp), EVFILT_READ,
+ EV_ENABLE | EV_ADD | EV_CLEAR, 0, 0, tf);
+
+ if (S_ISREG(tf->sb.st_mode)) {
+ i = 2;
+ EV_SET(&(ke[1]), fileno(tf->fp), EVFILT_VNODE,
+ EV_ENABLE | EV_ADD | EV_CLEAR,
+ NOTE_DELETE | NOTE_RENAME | NOTE_TRUNCATE,
+ 0, tf);
+ }
+ if (kevent(kq, ke, i, NULL, 0, NULL) < 0) {
+ ierr(tf->fname);
+ return -1;
+ }
+ return 0;
+}
+
+#define AFILESINCR 8
+static const struct timespec *
+tfreopen(struct tailfile *tf) {
+ static struct tailfile **reopen = NULL;
+ static int nfiles = 0, afiles = 0;
+ static const struct timespec ts = {1, 0};
+
+ struct stat sb;
+ struct tailfile **treopen, *ttf;
+ int i;
+
+ if (tf && ((stat(tf->fname, &sb) != 0) || sb.st_ino != tf->sb.st_ino)) {
+ if (afiles < ++nfiles) {
+ afiles += AFILESINCR;
+ treopen = reallocarray(reopen, afiles, sizeof(*reopen));
+ if (treopen)
+ reopen = treopen;
+ else
+ afiles -= AFILESINCR;
+ }
+ if (nfiles <= afiles) {
+ for (i = 0; i < nfiles - 1; i++)
+ if (strcmp(reopen[i]->fname, tf->fname) == 0)
+ break;
+ if (i < nfiles - 1)
+ nfiles--;
+ else
+ reopen[nfiles-1] = tf;
+ } else {
+ warnx("Lost track of %s", tf->fname);
+ nfiles--;
+ }
+ }
+
+ for (i = 0; i < nfiles; i++) {
+ ttf = reopen[i];
+ if (stat(ttf->fname, &sb) == -1)
+ continue;
+ if (sb.st_ino != ttf->sb.st_ino) {
+ (void) memcpy(&(ttf->sb), &sb, sizeof(ttf->sb));
+ ttf->fp = freopen(ttf->fname, "r", ttf->fp);
+ if (ttf->fp == NULL)
+ ierr(ttf->fname);
+ else {
+ warnx("%s has been replaced, reopening.",
+ ttf->fname);
+ tfqueue(ttf);
+ }
+ }
+ reopen[i] = reopen[--nfiles];
+ }
+
+ return nfiles ? &ts : NULL;
+}
diff --git a/usr.bin/tail/forward_linux.c b/usr.bin/tail/forward_linux.c
new file mode 100644
index 0000000..3c0d83a
--- /dev/null
+++ b/usr.bin/tail/forward_linux.c
@@ -0,0 +1,411 @@
+/* $OpenBSD: forward.c,v 1.31 2016/07/05 05:06:27 jsg Exp $ */
+/* $NetBSD: forward.c,v 1.7 1996/02/13 16:49:10 ghudson Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Edward Sze-Tyan Wang.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/inotify.h>
+
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <libgen.h>
+
+#include "extern.h"
+
+
+static int rlines(struct tailfile *, off_t);
+static inline void tfprint(FILE *fp);
+static int tfqueue(struct tailfile *tf);
+static void tfreopen(struct tailfile *tf);
+
+static int kq = -1;
+static char ibuf[8192];
+
+/*
+ * forward -- display the file, from an offset, forward.
+ *
+ * There are eight separate cases for this -- regular and non-regular
+ * files, by bytes or lines and from the beginning or end of the file.
+ *
+ * FBYTES byte offset from the beginning of the file
+ * REG seek
+ * NOREG read, counting bytes
+ *
+ * FLINES line offset from the beginning of the file
+ * REG read, counting lines
+ * NOREG read, counting lines
+ *
+ * RBYTES byte offset from the end of the file
+ * REG seek
+ * NOREG cyclically read characters into a wrap-around buffer
+ *
+ * RLINES
+ * REG step back until the correct offset is reached.
+ * NOREG cyclically read lines into a wrap-around array of buffers
+ */
+void
+forward(struct tailfile *tf, int nfiles, enum STYLE style, off_t origoff)
+{
+ struct stat sb;
+ int ch;
+ struct inotify_event *ev;
+ struct tailfile *ctf, *ltf;
+ int i;
+ ssize_t rd;
+
+ if (nfiles < 1)
+ return;
+
+ if (fflag && (kq = inotify_init1(0)) < 0)
+ warn("inotify_init1");
+
+ for (i = 0; i < nfiles; i++) {
+ off_t off = origoff;
+ if (nfiles > 1)
+ printfname(tf[i].fname);
+
+ switch(style) {
+ case FBYTES:
+ if (off == 0)
+ break;
+ if (S_ISREG(tf[i].sb.st_mode)) {
+ if (tf[i].sb.st_size < off)
+ off = tf[i].sb.st_size;
+ if (fseeko(tf[i].fp, off, SEEK_SET) == -1) {
+ ierr(tf[i].fname);
+ return;
+ }
+ } else while (off--)
+ if ((ch = getc(tf[i].fp)) == EOF) {
+ if (ferror(tf[i].fp)) {
+ ierr(tf[i].fname);
+ return;
+ }
+ break;
+ }
+ break;
+ case FLINES:
+ if (off == 0)
+ break;
+ for (;;) {
+ if ((ch = getc(tf[i].fp)) == EOF) {
+ if (ferror(tf[i].fp)) {
+ ierr(tf[i].fname);
+ return;
+ }
+ break;
+ }
+ if (ch == '\n' && !--off)
+ break;
+ }
+ break;
+ case RBYTES:
+ if (S_ISREG(tf[i].sb.st_mode)) {
+ if (tf[i].sb.st_size >= off &&
+ fseeko(tf[i].fp, -off, SEEK_END) == -1) {
+ ierr(tf[i].fname);
+ return;
+ }
+ } else if (off == 0) {
+ while (getc(tf[i].fp) != EOF)
+ ;
+ if (ferror(tf[i].fp)) {
+ ierr(tf[i].fname);
+ return;
+ }
+ } else {
+ if (bytes(&(tf[i]), off))
+ return;
+ }
+ break;
+ case RLINES:
+ if (S_ISREG(tf[i].sb.st_mode)) {
+ if (!off) {
+ if (fseeko(tf[i].fp, (off_t)0,
+ SEEK_END) == -1) {
+ ierr(tf[i].fname);
+ return;
+ }
+ } else if (rlines(&(tf[i]), off) != 0)
+ lines(&(tf[i]), off);
+ } else if (off == 0) {
+ while (getc(tf[i].fp) != EOF)
+ ;
+ if (ferror(tf[i].fp)) {
+ ierr(tf[i].fname);
+ return;
+ }
+ } else {
+ if (lines(&(tf[i]), off))
+ return;
+ }
+ break;
+ default:
+ err(1, "Unsupported style");
+ }
+
+ tfprint(tf[i].fp);
+ if (fflag && tfqueue(&(tf[i])) == -1)
+ warn("Unable to follow %s", tf[i].fname);
+
+ }
+ ltf = &(tf[i-1]);
+
+ (void)fflush(stdout);
+ if (!fflag || kq < 0)
+ return;
+
+ while (1) {
+ if ((rd = read(kq, ibuf, sizeof ibuf)) <= 0) {
+ if (errno == EINTR) {
+ close(kq);
+ return;
+ }
+ }
+
+ for (ssize_t j = 0; j < rd; j += sizeof (*ev) + ev->len) {
+ ev = (struct inotify_event *)(ibuf + j);
+ if (ev->mask & IN_IGNORED)
+ continue;
+
+ if (ev->mask & (IN_CREATE|IN_MOVED_TO)) {
+ ctf = NULL;
+ for (i = 0; i < nfiles; i++)
+ if (tf[i].dwd == ev->wd && strcmp(ev->name, tf[i].fname) == 0) {
+ ctf = &tf[i];
+ break;
+ }
+ if (ctf == NULL)
+ continue;
+ tfreopen(ctf);
+ goto dofile;
+ }
+ if (ev->mask & IN_MODIFY) {
+ ctf = NULL;
+ for (i = 0; i < nfiles; i++)
+ if (tf[i].fwd == ev->wd) {
+ ctf = &tf[i];
+ break;
+ }
+ if (ctf == NULL)
+ continue;
+dofile:
+ if (ctf != ltf) {
+ printfname(ctf->fname);
+ ltf = ctf;
+ }
+
+ /*
+ * XXX: Stupid hack to try to detect truncations.
+ *
+ * If the filsesize is smaller than the stored
+ * location, it was most likely truncated, in this
+ * case we rewind back to the beginning.
+ *
+ * This however does not work if the file is truncated
+ * to a larger size, there is no way to easily tell
+ * if the size changed because data was appended
+ * or the file was truncated.
+ */
+ if (stat(ctf->fname, &sb) != -1 && sb.st_size < ctf->pos) {
+ warnx("%s has been truncated, "
+ "resetting.", ctf->fname);
+ rewind(ctf->fp);
+ ctf->pos = 0;
+ }
+
+ clearerr(ctf->fp);
+ tfprint(ctf->fp);
+ if (ferror(ctf->fp)) {
+ ierr(ctf->fname);
+ fclose(ctf->fp);
+ warn("Lost file %s", ctf->fname);
+ continue;
+ }
+ (void)fflush(stdout);
+ clearerr(ctf->fp);
+ ctf->pos = ftell(ctf->fp);
+ }
+ }
+
+ tfreopen(NULL);
+ }
+}
+
+/*
+ * rlines -- display the last offset lines of the file.
+ */
+static int
+rlines(struct tailfile *tf, off_t off)
+{
+ off_t pos;
+ int ch;
+
+ pos = tf->sb.st_size;
+ if (pos == 0)
+ return (0);
+
+ /*
+ * Position before char.
+ * Last char is special, ignore it whether newline or not.
+ */
+ pos -= 2;
+ ch = EOF;
+ for (; off > 0 && pos >= 0; pos--) {
+ /* A seek per char isn't a problem with a smart stdio */
+ if (fseeko(tf[0].fp, pos, SEEK_SET) == -1) {
+ ierr(tf->fname);
+ return (1);
+ }
+ if ((ch = getc(tf[0].fp)) == '\n')
+ off--;
+ else if (ch == EOF) {
+ if (ferror(tf[0].fp)) {
+ ierr(tf->fname);
+ return (1);
+ }
+ break;
+ }
+ }
+ /* If we read until start of file, put back last read char */
+ if (pos < 0 && off > 0 && ch != EOF && ungetc(ch, tf[0].fp) == EOF) {
+ ierr(tf->fname);
+ return (1);
+ }
+
+ while (!feof(tf[0].fp) && (ch = getc(tf[0].fp)) != EOF)
+ if (putchar(ch) == EOF)
+ oerr();
+ if (ferror(tf[0].fp)) {
+ ierr(tf->fname);
+ return (1);
+ }
+
+ return (0);
+}
+
+static inline void
+tfprint(FILE *fp)
+{
+ int ch;
+
+ while (!feof(fp) && (ch = getc(fp)) != EOF)
+ if (putchar(ch) == EOF)
+ oerr();
+}
+
+static int
+tfqueue(struct tailfile *tf)
+{
+ char *dir;
+
+ if (kq < 0) {
+ errno = EBADF;
+ return -1;
+ }
+
+ if ((dir = dirname(tf->fname)) == NULL) {
+ ierr(tf->fname);
+ return -1;
+ }
+
+ if (tf->dwd == -1 && (tf->dwd = inotify_add_watch(kq, dir, IN_CREATE|IN_MOVED_TO)) == -1) {
+ ierr(tf->fname);
+ return -1;
+ }
+ if (tf->fwd != -1)
+ (void) inotify_rm_watch(kq, tf->fwd);
+ if ((tf->fwd = inotify_add_watch(kq, tf->fname, IN_MODIFY)) == -1) {
+ ierr(tf->fname);
+ return -1;
+ }
+ return 0;
+}
+
+#define AFILESINCR 8
+static void
+tfreopen(struct tailfile *tf) {
+ static struct tailfile **reopen = NULL;
+ static int nfiles = 0, afiles = 0;
+
+ struct stat sb;
+ struct tailfile **treopen, *ttf;
+ int i;
+
+ if (tf && ((stat(tf->fname, &sb) != 0) || sb.st_ino != tf->sb.st_ino)) {
+ if (afiles < ++nfiles) {
+ afiles += AFILESINCR;
+ treopen = reallocarray(reopen, afiles, sizeof(*reopen));
+ if (treopen)
+ reopen = treopen;
+ else
+ afiles -= AFILESINCR;
+ }
+ if (nfiles <= afiles) {
+ for (i = 0; i < nfiles - 1; i++)
+ if (strcmp(reopen[i]->fname, tf->fname) == 0)
+ break;
+ if (i < nfiles - 1)
+ nfiles--;
+ else
+ reopen[nfiles-1] = tf;
+ } else {
+ warnx("Lost track of %s", tf->fname);
+ nfiles--;
+ }
+ }
+
+ for (i = 0; i < nfiles; i++) {
+ ttf = reopen[i];
+ if (stat(ttf->fname, &sb) == -1)
+ continue;
+ if (sb.st_ino != ttf->sb.st_ino) {
+ (void) memcpy(&(ttf->sb), &sb, sizeof(ttf->sb));
+ ttf->pos = 0;
+ ttf->fp = freopen(ttf->fname, "r", ttf->fp);
+ if (ttf->fp == NULL)
+ ierr(ttf->fname);
+ else {
+ warnx("%s has been replaced, reopening.",
+ ttf->fname);
+ tfqueue(ttf);
+ }
+ }
+ reopen[i] = reopen[--nfiles];
+ }
+}
diff --git a/usr.bin/tail/misc.c b/usr.bin/tail/misc.c
new file mode 100644
index 0000000..80187d4
--- /dev/null
+++ b/usr.bin/tail/misc.c
@@ -0,0 +1,62 @@
+/* $OpenBSD: misc.c,v 1.9 2015/11/19 17:50:04 tedu Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Edward Sze-Tyan Wang.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <stdio.h>
+
+#include "extern.h"
+
+void
+ierr(const char *fname)
+{
+ warn("%s", fname);
+ rval = 1;
+}
+
+void
+oerr(void)
+{
+ err(1, "stdout");
+}
+
+void printfname(const char *fname)
+{
+ static int first = 1;
+ (void)printf("%s==> %s <==\n", first ? "" : "\n", fname);
+ first = 0;
+ (void)fflush(stdout);
+}
diff --git a/usr.bin/tail/read.c b/usr.bin/tail/read.c
new file mode 100644
index 0000000..e87836d
--- /dev/null
+++ b/usr.bin/tail/read.c
@@ -0,0 +1,226 @@
+/* $OpenBSD: read.c,v 1.20 2017/03/26 19:55:07 martijn Exp $ */
+/* $NetBSD: read.c,v 1.4 1994/11/23 07:42:07 jtc Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Edward Sze-Tyan Wang.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "extern.h"
+
+/*
+ * bytes -- read bytes to an offset from the end and display.
+ *
+ * This is the function that reads to a byte offset from the end of the input,
+ * storing the data in a wrap-around buffer which is then displayed. If the
+ * rflag is set, the data is displayed in lines in reverse order, and this
+ * routine has the usual nastiness of trying to find the newlines. Otherwise,
+ * it is displayed from the character closest to the beginning of the input to
+ * the end.
+ *
+ * A non-zero return means an (non-fatal) error occurred.
+ *
+ */
+int
+bytes(struct tailfile *tf, off_t off)
+{
+ int ch;
+ size_t len, tlen;
+ char *ep, *p, *t;
+ int wrap;
+ char *sp;
+
+ if (off > SIZE_MAX)
+ errx(1, "offset too large");
+
+ if ((sp = p = malloc(off)) == NULL)
+ err(1, NULL);
+
+ for (wrap = 0, ep = p + off; (ch = getc(tf->fp)) != EOF;) {
+ *p = ch;
+ if (++p == ep) {
+ wrap = 1;
+ p = sp;
+ }
+ }
+ if (ferror(tf->fp)) {
+ ierr(tf->fname);
+ free(sp);
+ return(1);
+ }
+
+ if (rflag) {
+ for (t = p - 1, len = 0; t >= sp; --t, ++len)
+ if (*t == '\n' && len) {
+ WR(t + 1, len);
+ len = 0;
+ }
+ if (wrap) {
+ tlen = len;
+ for (t = ep - 1, len = 0; t >= p; --t, ++len)
+ if (*t == '\n') {
+ if (len) {
+ WR(t + 1, len);
+ len = 0;
+ }
+ if (tlen) {
+ WR(sp, tlen);
+ tlen = 0;
+ }
+ }
+ if (len)
+ WR(t + 1, len);
+ if (tlen)
+ WR(sp, tlen);
+ }
+ } else {
+ if (wrap && (len = ep - p))
+ WR(p, len);
+ if ((len = p - sp))
+ WR(sp, len);
+ }
+
+ free(sp);
+ return(0);
+}
+
+/*
+ * lines -- read lines to an offset from the end and display.
+ *
+ * This is the function that reads to a line offset from the end of the input,
+ * storing the data in an array of buffers which is then displayed. If the
+ * rflag is set, the data is displayed in lines in reverse order, and this
+ * routine has the usual nastiness of trying to find the newlines. Otherwise,
+ * it is displayed from the line closest to the beginning of the input to
+ * the end.
+ *
+ * A non-zero return means an (non-fatal) error occurred.
+ *
+ */
+int
+lines(struct tailfile *tf, off_t off)
+{
+ struct {
+ size_t blen;
+ size_t len;
+ char *l;
+ } *lines = NULL;
+ int ch, rc = 0;
+ char *p = NULL;
+ int wrap;
+ size_t cnt, lineno, nlineno, recno, blen, newsize;
+ char *sp = NULL, *newp = NULL;
+
+ if (off > SIZE_MAX)
+ errx(1, "offset too large");
+
+ lineno = blen = cnt = recno = wrap = 0;
+
+ while ((ch = getc(tf->fp)) != EOF) {
+ if (++cnt > blen) {
+ newsize = blen + 1024;
+ if ((newp = realloc(sp, newsize)) == NULL)
+ err(1, NULL);
+ sp = newp;
+ blen = newsize;
+ p = sp + cnt - 1;
+ }
+ *p++ = ch;
+ if (recno >= lineno) {
+ nlineno = (lineno + 1024) > off ?
+ (size_t) off : lineno + 1024;
+ if ((lines = recallocarray(lines, lineno, nlineno,
+ sizeof(*lines))) == NULL)
+ err(1, NULL);
+ lineno = nlineno;
+ }
+ if (ch == '\n') {
+ if (lines[recno].blen < cnt) {
+ newsize = cnt + 256;
+ if ((newp = realloc(lines[recno].l,
+ newsize)) == NULL)
+ err(1, NULL);
+ lines[recno].l = newp;
+ lines[recno].blen = newsize;
+ }
+ memcpy(lines[recno].l, sp, (lines[recno].len = cnt));
+ cnt = 0;
+ p = sp;
+ if (++recno == off) {
+ wrap = 1;
+ recno = 0;
+ }
+ }
+ }
+ if (ferror(tf->fp)) {
+ ierr(tf->fname);
+ rc = 1;
+ goto done;
+ }
+ if (cnt) {
+ lines[recno].l = sp;
+ lines[recno].len = cnt;
+ sp = NULL;
+ if (++recno == off) {
+ wrap = 1;
+ recno = 0;
+ }
+ }
+
+ if (rflag) {
+ for (cnt = recno; cnt > 0; --cnt)
+ WR(lines[cnt - 1].l, lines[cnt - 1].len);
+ if (wrap)
+ for (cnt = off; cnt > recno; --cnt)
+ WR(lines[cnt - 1].l, lines[cnt - 1].len);
+ } else {
+ if (wrap)
+ for (cnt = recno; cnt < off; ++cnt)
+ WR(lines[cnt].l, lines[cnt].len);
+ for (cnt = 0; cnt < recno; ++cnt)
+ WR(lines[cnt].l, lines[cnt].len);
+ }
+done:
+ for (cnt = 0; cnt < lineno; cnt++)
+ free(lines[cnt].l);
+ free(sp);
+ free(lines);
+ return(rc);
+}
diff --git a/usr.bin/tail/reverse.c b/usr.bin/tail/reverse.c
new file mode 100644
index 0000000..144c481
--- /dev/null
+++ b/usr.bin/tail/reverse.c
@@ -0,0 +1,279 @@
+/* $OpenBSD: reverse.c,v 1.21 2015/11/19 17:50:04 tedu Exp $ */
+/* $NetBSD: reverse.c,v 1.6 1994/11/23 07:42:10 jtc Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Edward Sze-Tyan Wang.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/stat.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "extern.h"
+
+static void r_buf(FILE *);
+static int r_reg(struct tailfile *, enum STYLE, off_t);
+
+#define COPYCHAR(tf, ch) \
+ do { \
+ if ((ch = getc(tf->fp)) == EOF) { \
+ ierr(tf->fname); \
+ return (0); \
+ } \
+ if (putchar(ch) == EOF) { \
+ oerr(); \
+ return (0); \
+ } \
+ } while (0)
+
+/*
+ * reverse -- display input in reverse order by line.
+ *
+ * There are six separate cases for this -- regular and non-regular
+ * files by bytes, lines or the whole file.
+ *
+ * BYTES display N bytes
+ * REG reverse scan and display the lines
+ * NOREG cyclically read characters into a wrap-around buffer
+ *
+ * LINES display N lines
+ * REG reverse scan and display the lines
+ * NOREG cyclically read lines into a wrap-around array of buffers
+ *
+ * FILE display the entire file
+ * REG reverse scan and display the lines
+ * NOREG cyclically read input into a linked list of buffers
+ */
+void
+reverse(struct tailfile *tf, int nfiles, enum STYLE style, off_t off)
+{
+ int i;
+
+ if (style != REVERSE && off == 0)
+ return;
+
+ for (i = 0; i < nfiles; i++) {
+ if (nfiles > 1)
+ printfname(tf[i].fname);
+ if (!S_ISREG(tf[i].sb.st_mode) ||
+ r_reg(&(tf[i]), style, off) != 0) {
+ switch(style) {
+ case FBYTES:
+ case RBYTES:
+ (void)bytes(&(tf[i]), off);
+ break;
+ case FLINES:
+ case RLINES:
+ (void)lines(&(tf[i]), off);
+ break;
+ case REVERSE:
+ r_buf(tf[i].fp);
+ break;
+ default:
+ err(1, "Unsupported style");
+ }
+ }
+ }
+}
+
+/*
+ * r_reg -- display a regular file in reverse order by line.
+ */
+static int
+r_reg(struct tailfile *tf, enum STYLE style, off_t off)
+{
+ off_t start, pos, end;
+ int ch;
+
+ end = tf->sb.st_size;
+ if (end == 0)
+ return (0);
+
+ /* Position before char, ignore last char whether newline or not */
+ pos = end-2;
+ ch = EOF;
+ start = 0;
+
+ if (style == RBYTES && off < end)
+ start = end - off;
+
+ for (; pos >= start; pos--) {
+ /* A seek per char isn't a problem with a smart stdio */
+ if (fseeko(tf->fp, pos, SEEK_SET) != 0) {
+ ierr(tf->fname);
+ return (0);
+ }
+ if ((ch = getc(tf->fp)) == '\n') {
+ while (--end > pos)
+ COPYCHAR(tf, ch);
+ end++;
+ if (style == RLINES && --off == 0)
+ break;
+ }
+ else if (ch == EOF) {
+ ierr(tf->fname);
+ return (0);
+ }
+ }
+ if (pos < start) {
+ if (ch != EOF && ungetc(ch, tf->fp) == EOF) {
+ ierr(tf->fname);
+ return (0);
+ }
+ while (--end >= start)
+ COPYCHAR(tf, ch);
+ }
+ return (0);
+}
+
+#define BSZ (128 * 1024)
+struct bf {
+ struct bf *next;
+ struct bf *prev;
+ size_t len;
+ char l[BSZ];
+};
+
+/*
+ * r_buf -- display a non-regular file in reverse order by line.
+ *
+ * This is the function that saves the entire input, storing the data in a
+ * doubly linked list of buffers and then displays them in reverse order.
+ * It has the usual nastiness of trying to find the newlines, as there's no
+ * guarantee that a newline occurs anywhere in the file, let alone in any
+ * particular buffer. If we run out of memory, input is discarded (and the
+ * user warned).
+ */
+static void
+r_buf(FILE *fp)
+{
+ struct bf *mark, *tr, *tl = NULL;
+ int ch;
+ size_t len, llen;
+ char *p;
+ off_t enomem;
+
+ for (mark = NULL, enomem = 0;;) {
+ /*
+ * Allocate a new block and link it into place in a doubly
+ * linked list. If out of memory, toss the LRU block and
+ * keep going.
+ */
+ if (enomem || (tl = malloc(sizeof(*tl))) == NULL) {
+ if (!mark)
+ err(1, NULL);
+ tl = enomem ? tl->next : mark;
+ enomem += tl->len;
+ } else if (mark) {
+ tl->next = mark;
+ tl->prev = mark->prev;
+ mark->prev->next = tl;
+ mark->prev = tl;
+ } else {
+ mark = tl;
+ mark->next = mark->prev = mark;
+ }
+
+ if (!enomem)
+ tl->len = 0;
+
+ /* Fill the block with input data. */
+ for (p = tl->l, len = 0;
+ len < BSZ && (ch = getc(fp)) != EOF; ++len)
+ *p++ = ch;
+
+ /*
+ * If no input data for this block and we tossed some data,
+ * recover it.
+ */
+ if (!len) {
+ if (enomem)
+ enomem -= tl->len;
+ tl = tl->prev;
+ break;
+ }
+
+ tl->len = len;
+ if (ch == EOF)
+ break;
+ }
+
+ if (enomem) {
+ (void)fprintf(stderr,
+ "tail: warning: %lld bytes discarded\n", (long long)enomem);
+ rval = 1;
+ }
+
+ /*
+ * Step through the blocks in the reverse order read. The last char
+ * is special, ignore whether newline or not.
+ */
+ for (mark = tl;;) {
+ for (p = tl->l + (len = tl->len) - 1, llen = 0; len--;
+ --p, ++llen)
+ if (*p == '\n') {
+ if (llen) {
+ WR(p + 1, llen);
+ llen = 0;
+ }
+ if (tl == mark)
+ continue;
+ for (tr = tl->next; tr->len; tr = tr->next) {
+ WR(tr->l, tr->len);
+ tr->len = 0;
+ if (tr == mark)
+ break;
+ }
+ }
+ tl->len = llen;
+ if ((tl = tl->prev) == mark)
+ break;
+ }
+ tl = tl->next;
+ if (tl->len) {
+ WR(tl->l, tl->len);
+ tl->len = 0;
+ }
+ while ((tl = tl->next)->len) {
+ WR(tl->l, tl->len);
+ tl->len = 0;
+ }
+
+ tl->prev->next = NULL;
+ while (tl != NULL) {
+ tr = tl->next;
+ free(tl);
+ tl = tr;
+ }
+}
diff --git a/usr.bin/tail/tail.1 b/usr.bin/tail/tail.1
new file mode 100644
index 0000000..9d5744f
--- /dev/null
+++ b/usr.bin/tail/tail.1
@@ -0,0 +1,187 @@
+.\" $OpenBSD: tail.1,v 1.23 2015/10/25 21:50:32 zhuk Exp $
+.\" $NetBSD: tail.1,v 1.4 1994/11/23 07:42:13 jtc Exp $
+.\"
+.\" Copyright (c) 1980, 1990, 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)tail.1 8.1 (Berkeley) 6/6/93
+.\"
+.Dd $Mdocdate: October 25 2015 $
+.Dt TAIL 1
+.Os
+.Sh NAME
+.Nm tail
+.Nd display the last part of a file
+.Sh SYNOPSIS
+.Nm tail
+.Op Fl f | r
+.Oo
+.Fl b Ar number |
+.Fl c Ar number |
+.Fl n Ar number |
+.Fl Ar number
+.Oc
+.Op Ar
+.Sh DESCRIPTION
+The
+.Nm
+utility displays the contents of
+.Ar file
+or, by default, its standard input, to the standard output.
+.Pp
+The display begins at a byte, line, or 512-byte block location in the
+input.
+Numbers having a leading plus
+.Pq Ql +
+sign are relative to the beginning of the input, for example,
+.Ic -c +2
+starts the display at the second
+byte of the input.
+Numbers having a leading minus
+.Pq Ql -
+sign or no explicit sign are
+relative to the end of the input, for example,
+.Ic -n 2
+displays the last two lines of the input.
+The default starting location is
+.Ic -n 10 ,
+or the last 10 lines of the input.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl b Ar number
+The location is
+.Ar number
+512-byte blocks.
+.It Fl c Ar number
+The location is
+.Ar number
+bytes.
+.It Fl f
+Do not stop when end-of-file is reached; instead, wait for additional
+data to be appended to the input.
+If the file is replaced (i.e., the inode number changes),
+.Nm
+will reopen the file and continue.
+If the file is truncated,
+.Nm
+will reset its position to the beginning.
+This makes
+.Nm
+more useful for watching log files that may get rotated.
+The
+.Fl f
+option is ignored if there are no
+.Fa file
+arguments and the standard input is a pipe or a FIFO.
+.It Fl n Ar number | Fl Ar number
+The location is
+.Ar number
+lines.
+.It Fl r
+The
+.Fl r
+option causes the input to be displayed in reverse order, by line.
+Additionally, this option changes the meaning of the
+.Fl b ,
+.Fl c ,
+and
+.Fl n
+options.
+When the
+.Fl r
+option is specified, these options specify the number of bytes, lines
+or 512-byte blocks to display, instead of the bytes, lines, or blocks
+from the beginning or end of the input from which to begin the display.
+The default for the
+.Fl r
+option is to display all of the input.
+.El
+.Pp
+If more than one file is specified,
+.Nm
+precedes the output of each file with the following, in order
+to distinguish files:
+.Pp
+.Dl ==> Ar file No <==
+.Sh EXIT STATUS
+.Ex -std tail
+.Sh EXAMPLES
+To display the last 500 lines of the file
+.Ar foo :
+.Pp
+.Dl $ tail -500 foo
+.Pp
+Keep
+.Pa /var/log/messages
+open, displaying to the standard output anything appended to the file:
+.Pp
+.Dl $ tail -f /var/log/messages
+.Sh SEE ALSO
+.Xr cat 1 ,
+.Xr head 1 ,
+.Xr sed 1
+.Sh STANDARDS
+The
+.Nm
+utility is compliant with the
+.St -p1003.1-2008
+specification.
+.Pp
+The flags
+.Op Fl br
+are extensions to that specification.
+.Pp
+The historic command line syntax of
+.Nm
+is supported by this implementation.
+The only difference between this implementation and historic versions
+of
+.Nm tail ,
+once the command line syntax translation has been done, is that the
+.Fl b ,
+.Fl c
+and
+.Fl n
+options modify the
+.Fl r
+option, i.e.,
+.Ic -r -c 4
+displays the last 4 characters of the last line
+of the input, while the historic tail (using the historic syntax
+.Ic -4cr )
+would ignore the
+.Fl c
+option and display the last 4 lines of the input.
+.Sh HISTORY
+A
+.Nm
+command appeared in
+.At v7 .
diff --git a/usr.bin/tail/tail.c b/usr.bin/tail/tail.c
new file mode 100644
index 0000000..a5a54e7
--- /dev/null
+++ b/usr.bin/tail/tail.c
@@ -0,0 +1,299 @@
+/* $OpenBSD: tail.c,v 1.21 2016/02/03 12:23:57 halex Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Edward Sze-Tyan Wang.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "extern.h"
+
+int fflag, rflag, rval;
+int is_stdin;
+
+static void obsolete(char **);
+static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ struct tailfile *tf;
+ off_t off = 0;
+ enum STYLE style;
+ int ch;
+ int i;
+ char *p;
+
+ if (pledge("stdio rpath", NULL) == -1)
+ err(1, "pledge");
+
+ /*
+ * Tail's options are weird. First, -n10 is the same as -n-10, not
+ * -n+10. Second, the number options are 1 based and not offsets,
+ * so -n+1 is the first line, and -c-1 is the last byte. Third, the
+ * number options for the -r option specify the number of things that
+ * get displayed, not the starting point in the file. The one major
+ * incompatibility in this version as compared to historical versions
+ * is that the 'r' option couldn't be modified by the -lbc options,
+ * i.e. it was always done in lines. This version treats -rc as a
+ * number of characters in reverse order. Finally, the default for
+ * -r is the entire file, not 10 lines.
+ */
+#define ARG(units, forward, backward) { \
+ if (style) \
+ usage(); \
+ off = strtoll(optarg, &p, 10) * (units); \
+ if (*p) \
+ errx(1, "illegal offset -- %s", optarg); \
+ switch(optarg[0]) { \
+ case '+': \
+ if (off) \
+ off -= (units); \
+ style = (forward); \
+ break; \
+ case '-': \
+ off = -off; \
+ /* FALLTHROUGH */ \
+ default: \
+ style = (backward); \
+ break; \
+ } \
+}
+
+ obsolete(argv);
+ style = NOTSET;
+ while ((ch = getopt(argc, argv, "b:c:fn:r")) != -1)
+ switch(ch) {
+ case 'b':
+ ARG(512, FBYTES, RBYTES);
+ break;
+ case 'c':
+ ARG(1, FBYTES, RBYTES);
+ break;
+ case 'f':
+ fflag = 1;
+ break;
+ case 'n':
+ ARG(1, FLINES, RLINES);
+ break;
+ case 'r':
+ rflag = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ /*
+ * If displaying in reverse, don't permit follow option, and convert
+ * style values.
+ */
+ if (rflag) {
+ if (fflag)
+ usage();
+ if (style == FBYTES)
+ style = RBYTES;
+ else if (style == FLINES)
+ style = RLINES;
+ }
+
+ /*
+ * If style not specified, the default is the whole file for -r, and
+ * the last 10 lines if not -r.
+ */
+ if (style == NOTSET) {
+ if (rflag) {
+ off = 0;
+ style = REVERSE;
+ } else {
+ off = 10;
+ style = RLINES;
+ }
+ }
+
+ if ((tf = reallocarray(NULL, argc ? argc : 1, sizeof(*tf))) == NULL)
+ err(1, "reallocarray");
+
+ if (argc) {
+ for (i = 0; *argv; i++) {
+ tf[i].fname = *argv++;
+#ifdef __linux__
+ tf[i].dwd = -1;
+ tf[i].fwd = -1;
+#endif
+ if ((tf[i].fp = fopen(tf[i].fname, "r")) == NULL ||
+ fstat(fileno(tf[i].fp), &(tf[i].sb))) {
+ ierr(tf[i].fname);
+ i--;
+ continue;
+ }
+ }
+ if (rflag)
+ reverse(tf, i, style, off);
+ else
+ forward(tf, i, style, off);
+ }
+ else {
+ if (pledge("stdio", NULL) == -1)
+ err(1, "pledge");
+
+ tf[0].fname = "stdin";
+ tf[0].fp = stdin;
+ is_stdin = 1;
+
+ if (fstat(fileno(stdin), &(tf[0].sb))) {
+ ierr(tf[0].fname);
+ exit(1);
+ }
+
+ /*
+ * Determine if input is a pipe. 4.4BSD will set the SOCKET
+ * bit in the st_mode field for pipes. Fix this then.
+ */
+ if (lseek(fileno(tf[0].fp), (off_t)0, SEEK_CUR) == -1 &&
+ errno == ESPIPE) {
+ errno = 0;
+ fflag = 0; /* POSIX.2 requires this. */
+ }
+
+ if (rflag)
+ reverse(tf, 1, style, off);
+ else
+ forward(tf, 1, style, off);
+ }
+ exit(rval);
+}
+
+/*
+ * Convert the obsolete argument form into something that getopt can handle.
+ * This means that anything of the form [+-][0-9][0-9]*[lbc][fr] that isn't
+ * the option argument for a -b, -c or -n option gets converted.
+ */
+static void
+obsolete(char *argv[])
+{
+ char *ap, *p, *t;
+ size_t len;
+ char *start;
+
+ while ((ap = *++argv)) {
+ /* Return if "--" or not an option of any form. */
+ if (ap[0] != '-') {
+ if (ap[0] != '+')
+ return;
+ } else if (ap[1] == '-')
+ return;
+
+ switch(*++ap) {
+ /* Old-style option. */
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+
+ /* Malloc space for dash, new option and argument. */
+ len = strlen(*argv);
+ if ((start = p = malloc(len + 4)) == NULL)
+ err(1, NULL);
+ *p++ = '-';
+
+ /*
+ * Go to the end of the option argument. Save off any
+ * trailing options (-3lf) and translate any trailing
+ * output style characters.
+ */
+ t = *argv + len - 1;
+ if (*t == 'f' || *t == 'r') {
+ *p++ = *t;
+ *t-- = '\0';
+ }
+ switch(*t) {
+ case 'b':
+ *p++ = 'b';
+ *t = '\0';
+ break;
+ case 'c':
+ *p++ = 'c';
+ *t = '\0';
+ break;
+ case 'l':
+ *t = '\0';
+ /* FALLTHROUGH */
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ *p++ = 'n';
+ break;
+ default:
+ errx(1, "illegal option -- %s", *argv);
+ }
+ *p++ = *argv[0];
+ (void)strlcpy(p, ap, start + len + 4 - p);
+ *argv = start;
+ continue;
+
+ /*
+ * Options w/ arguments, skip the argument and continue
+ * with the next option.
+ */
+ case 'b':
+ case 'c':
+ case 'n':
+ if (!ap[1])
+ ++argv;
+ /* FALLTHROUGH */
+ /* Options w/o arguments, continue with the next option. */
+ case 'f':
+ case 'r':
+ continue;
+
+ /* Illegal option, return and let getopt handle it. */
+ default:
+ return;
+ }
+ }
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+ "usage: tail [-f | -r] "
+ "[-b number | -c number | -n number | -number] [file ...]\n");
+ exit(1);
+}