aboutsummaryrefslogtreecommitdiffstats
path: root/text_cmds/tail
diff options
context:
space:
mode:
authorCameron Katri <me@cameronkatri.com>2021-05-09 14:20:58 -0400
committerCameron Katri <me@cameronkatri.com>2021-05-09 14:20:58 -0400
commit5fd83771641d15c418f747bd343ba6738d3875f7 (patch)
tree5abf0f78f680d9837dbd93d4d4c3933bb7509599 /text_cmds/tail
downloadapple_cmds-5fd83771641d15c418f747bd343ba6738d3875f7.tar.gz
apple_cmds-5fd83771641d15c418f747bd343ba6738d3875f7.tar.zst
apple_cmds-5fd83771641d15c418f747bd343ba6738d3875f7.zip
Import macOS userland
adv_cmds-176 basic_cmds-55 bootstrap_cmds-116.100.1 developer_cmds-66 diskdev_cmds-667.40.1 doc_cmds-53.60.1 file_cmds-321.40.3 mail_cmds-35 misc_cmds-34 network_cmds-606.40.1 patch_cmds-17 remote_cmds-63 shell_cmds-216.60.1 system_cmds-880.60.2 text_cmds-106
Diffstat (limited to 'text_cmds/tail')
-rw-r--r--text_cmds/tail/extern.h76
-rw-r--r--text_cmds/tail/forward.c520
-rw-r--r--text_cmds/tail/misc.c119
-rw-r--r--text_cmds/tail/read.c214
-rw-r--r--text_cmds/tail/reverse.c287
-rw-r--r--text_cmds/tail/tail.1188
-rw-r--r--text_cmds/tail/tail.c349
7 files changed, 1753 insertions, 0 deletions
diff --git a/text_cmds/tail/extern.h b/text_cmds/tail/extern.h
new file mode 100644
index 0000000..d956680
--- /dev/null
+++ b/text_cmds/tail/extern.h
@@ -0,0 +1,76 @@
+/*-
+ * 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. 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
+ *
+ * $FreeBSD$
+ */
+
+#define WR(p, size) do { \
+ if (write(STDOUT_FILENO, p, size) != (ssize_t)size) \
+ oerr(); \
+ } while(0)
+
+#define TAILMAPLEN (4<<20)
+
+struct mapinfo {
+ off_t mapoff;
+ off_t maxoff;
+ size_t maplen;
+ char *start;
+ int fd;
+};
+
+struct file_info {
+ FILE *fp;
+ char *file_name;
+ struct stat st;
+};
+
+typedef struct file_info file_info_t;
+
+enum STYLE { NOTSET = 0, FBYTES, FLINES, RBYTES, RLINES, REVERSE };
+
+void follow(file_info_t *, enum STYLE, off_t);
+void forward(FILE *, enum STYLE, off_t, struct stat *);
+void reverse(FILE *, enum STYLE, off_t, struct stat *);
+
+int bytes(FILE *, off_t);
+int lines(FILE *, off_t);
+
+void ierr(void);
+void oerr(void);
+int mapprint(struct mapinfo *, off_t, off_t);
+int maparound(struct mapinfo *, off_t);
+
+extern int Fflag, fflag, qflag, rflag, rval, no_files;
+extern const char *fname;
diff --git a/text_cmds/tail/forward.c b/text_cmds/tail/forward.c
new file mode 100644
index 0000000..172898a
--- /dev/null
+++ b/text_cmds/tail/forward.c
@@ -0,0 +1,520 @@
+/*-
+ * 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. 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/cdefs.h>
+
+__FBSDID("$FreeBSD$");
+
+#ifndef lint
+static const char sccsid[] = "@(#)forward.c 8.1 (Berkeley) 6/6/93";
+#endif
+
+#include <sys/param.h>
+#include <sys/mount.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/mman.h>
+#include <sys/event.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "extern.h"
+
+static void rlines(FILE *, off_t, struct stat *);
+static void show(file_info_t *);
+static void set_events(file_info_t *files);
+
+/* defines for inner loop actions */
+#define USE_SLEEP 0
+#define USE_KQUEUE 1
+#define ADD_EVENTS 2
+
+struct kevent *ev;
+int action = USE_SLEEP;
+int kq;
+
+static const file_info_t *last;
+
+/*
+ * 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 mmap the file and step back until reach the correct offset.
+ * NOREG cyclically read lines into a wrap-around array of buffers
+ */
+void
+forward(FILE *fp, enum STYLE style, off_t off, struct stat *sbp)
+{
+ int ch;
+
+ switch(style) {
+ case FBYTES:
+ if (off == 0)
+ break;
+ if (S_ISREG(sbp->st_mode)) {
+ if (sbp->st_size < off)
+ off = sbp->st_size;
+ if (fseeko(fp, off, SEEK_SET) == -1) {
+ ierr();
+ return;
+ }
+ } else while (off--)
+ if ((ch = getc(fp)) == EOF) {
+ if (ferror(fp)) {
+ ierr();
+ return;
+ }
+ break;
+ }
+ break;
+ case FLINES:
+ if (off == 0)
+ break;
+ for (;;) {
+ if ((ch = getc(fp)) == EOF) {
+ if (ferror(fp)) {
+ ierr();
+ return;
+ }
+ break;
+ }
+ if (ch == '\n' && !--off)
+ break;
+ }
+ break;
+ case RBYTES:
+ if (S_ISREG(sbp->st_mode)) {
+ if (sbp->st_size >= off &&
+ fseeko(fp, -off, SEEK_END) == -1) {
+ ierr();
+ return;
+ }
+ } else if (off == 0) {
+ while (getc(fp) != EOF);
+ if (ferror(fp)) {
+ ierr();
+ return;
+ }
+ } else
+ if (bytes(fp, off))
+ return;
+ break;
+ case RLINES:
+ if (S_ISREG(sbp->st_mode))
+ if (!off) {
+ if (fseeko(fp, (off_t)0, SEEK_END) == -1) {
+ ierr();
+ return;
+ }
+ } else
+ rlines(fp, off, sbp);
+ else if (off == 0) {
+ while (getc(fp) != EOF);
+ if (ferror(fp)) {
+ ierr();
+ return;
+ }
+ } else
+ if (lines(fp, off))
+ return;
+ break;
+ default:
+ break;
+ }
+
+ while ((ch = getc(fp)) != EOF)
+ if (putchar(ch) == EOF)
+ oerr();
+ if (ferror(fp)) {
+ ierr();
+ return;
+ }
+ (void)fflush(stdout);
+}
+
+/*
+ * rlines -- display the last offset lines of the file.
+ */
+static void
+rlines(fp, off, sbp)
+ FILE *fp;
+ off_t off;
+ struct stat *sbp;
+{
+#ifdef __APPLE__
+ /* Using mmap on network filesystems can frequently lead
+ to distress, and even on local file systems other processes
+ truncating the file can also lead to upset. */
+
+ /* Seek to sbp->st_blksize before the end of the file, find
+ all the newlines. If there are enough, print the last off
+ lines. Otherwise go back another sbp->st_blksize bytes,
+ and count newlines. Avoid re-reading blocks when possible. */
+
+ // +1 because we capture line ends and we want off line _starts_,
+ // +1 because the first line might be partial when try_at != 0
+ off_t search_for = off +2;
+ off_t try_at = sbp->st_size;
+ off_t last_try = sbp->st_size;
+ off_t found_this_pass = 0;
+ off_t found_total = 0;
+ off_t *found_at = calloc(search_for, sizeof(off_t));
+
+ flockfile(fp);
+
+ if (found_at == NULL) {
+ ierr();
+ goto done;
+ }
+
+ if (off == 0 || sbp->st_size == 0) {
+ goto done;
+ }
+
+ /* The last character is special. Check to make sure that it is a \n,
+ * and if not, subtract one from the number of \n we need to search for.
+ */
+ if (0 != fseeko(fp, sbp->st_size - 1, SEEK_SET)) {
+ ierr();
+ goto done;
+ }
+ if ('\n' != getc_unlocked(fp)) {
+ search_for--;
+ }
+
+ while(try_at != 0) {
+ found_this_pass = 0;
+
+ if (try_at < sbp->st_blksize) {
+ found_at[found_this_pass++] = 0;
+ try_at = 0;
+ } else {
+ last_try = try_at;
+ try_at -= sbp->st_blksize;
+ }
+
+ if (0 != fseeko(fp, try_at, SEEK_SET)) {
+ ierr();
+ goto done;
+ }
+
+ char ch;
+ while(EOF != (ch = getc_unlocked(fp))) {
+ if (ch == '\n') {
+ found_at[found_this_pass++ % search_for] = ftello(fp);
+ found_total++;
+ }
+ if (ftello(fp) == last_try && found_total < search_for) {
+ // We just reached the last block we scanned,
+ // and we know there arn't enough lines found
+ // so far to be happy, so we don't have to
+ // read it again.
+ break;
+ }
+ }
+
+ if (found_this_pass >= search_for || try_at == 0) {
+ off_t min = found_at[0];
+ int min_i = 0;
+ int i;
+ int lim = (found_this_pass < search_for) ? found_this_pass : search_for;
+ for(i = 1; i < lim; i++) {
+ if (found_at[i] < min) {
+ min = found_at[i];
+ min_i = i;
+ }
+ }
+
+ off_t target = min;
+
+ if (found_this_pass >= search_for) {
+ // min_i might be a partial line (unless
+ // try_at is 0). If we found search_for
+ // lines, min_i+1 is the first known full line
+ // _and_ because we look for an extra line we
+ // don't need to show it.
+ target = found_at[(min_i + 1) % search_for];
+ }
+
+ if (0 != fseeko(fp, target, SEEK_SET)) {
+ ierr();
+ goto done;
+ }
+
+ flockfile(stdout);
+ while(EOF != (ch = getc_unlocked(fp))) {
+ if (EOF == putchar_unlocked(ch)) {
+ funlockfile(stdout);
+ oerr();
+ goto done;
+ }
+ }
+ funlockfile(stdout);
+ goto done;
+ }
+ }
+
+done:
+ funlockfile(fp);
+ free(found_at);
+ return;
+#else
+ struct mapinfo map;
+ off_t curoff, size;
+ int i;
+
+ if (!(size = sbp->st_size))
+ return;
+ map.start = NULL;
+ map.fd = fileno(fp);
+ map.mapoff = map.maxoff = size;
+
+ /*
+ * Last char is special, ignore whether newline or not. Note that
+ * size == 0 is dealt with above, and size == 1 sets curoff to -1.
+ */
+ curoff = size - 2;
+ while (curoff >= 0) {
+ if (curoff < map.mapoff && maparound(&map, curoff) != 0) {
+ ierr();
+ return;
+ }
+ for (i = curoff - map.mapoff; i >= 0; i--)
+ if (map.start[i] == '\n' && --off == 0)
+ break;
+ /* `i' is either the map offset of a '\n', or -1. */
+ curoff = map.mapoff + i;
+ if (i >= 0)
+ break;
+ }
+ curoff++;
+ if (mapprint(&map, curoff, size - curoff) != 0) {
+ ierr();
+ exit(1);
+ }
+
+ /* Set the file pointer to reflect the length displayed. */
+ if (fseeko(fp, sbp->st_size, SEEK_SET) == -1) {
+ ierr();
+ return;
+ }
+ if (map.start != NULL && munmap(map.start, map.maplen)) {
+ ierr();
+ return;
+ }
+#endif
+}
+
+static void
+show(file_info_t *file)
+{
+ int ch;
+
+ while ((ch = getc(file->fp)) != EOF) {
+ if (last != file && no_files > 1) {
+ if (!qflag)
+ (void)printf("\n==> %s <==\n", file->file_name);
+ last = file;
+ }
+ if (putchar(ch) == EOF)
+ oerr();
+ }
+ (void)fflush(stdout);
+ if (ferror(file->fp)) {
+ file->fp = NULL;
+ fname = file->file_name;
+ ierr();
+ fname = NULL;
+ } else
+ clearerr(file->fp);
+}
+
+static void
+set_events(file_info_t *files)
+{
+ int i, n = 0;
+ file_info_t *file;
+ struct timespec ts;
+ struct statfs sf;
+
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
+
+ action = USE_KQUEUE;
+ for (i = 0, file = files; i < no_files; i++, file++) {
+ if (! file->fp)
+ continue;
+
+ if (fstatfs(fileno(file->fp), &sf) == 0 &&
+ (sf.f_flags & MNT_LOCAL) == 0) {
+ action = USE_SLEEP;
+ return;
+ }
+
+ if (Fflag && fileno(file->fp) != STDIN_FILENO) {
+ EV_SET(&ev[n], fileno(file->fp), EVFILT_VNODE,
+ EV_ADD | EV_ENABLE | EV_CLEAR,
+ NOTE_DELETE | NOTE_RENAME, 0, 0);
+ n++;
+ }
+ EV_SET(&ev[n], fileno(file->fp), EVFILT_READ,
+ EV_ADD | EV_ENABLE | EV_CLEAR, 0, 0, 0);
+ n++;
+ }
+
+ if (kevent(kq, ev, n, NULL, 0, &ts) < 0) {
+ action = USE_SLEEP;
+ }
+}
+
+/*
+ * follow -- display the file, from an offset, forward.
+ *
+ */
+void
+follow(file_info_t *files, enum STYLE style, off_t off)
+{
+ int active, i, n = -1;
+ struct stat sb2;
+ file_info_t *file;
+ struct timespec ts;
+
+ /* Position each of the files */
+
+ file = files;
+ active = 0;
+ n = 0;
+ for (i = 0; i < no_files; i++, file++) {
+ if (file->fp) {
+ active = 1;
+ n++;
+ if (no_files > 1 && !qflag)
+ (void)printf("\n==> %s <==\n", file->file_name);
+ fname = file->file_name;
+ forward(file->fp, style, off, &file->st);
+ fname = NULL;
+ if (Fflag && fileno(file->fp) != STDIN_FILENO)
+ n++;
+ }
+ }
+ if (! active)
+ return;
+
+ last = --file;
+
+ kq = kqueue();
+ if (kq < 0)
+ err(1, "kqueue");
+ ev = malloc(n * sizeof(struct kevent));
+ if (! ev)
+ err(1, "Couldn't allocate memory for kevents.");
+ set_events(files);
+
+ for (;;) {
+ for (i = 0, file = files; i < no_files; i++, file++) {
+ if (! file->fp)
+ continue;
+ if (Fflag && file->fp && fileno(file->fp) != STDIN_FILENO) {
+ if (stat(file->file_name, &sb2) == 0 &&
+ (sb2.st_ino != file->st.st_ino ||
+ sb2.st_dev != file->st.st_dev ||
+ sb2.st_nlink == 0)) {
+ show(file);
+ file->fp = freopen(file->file_name, "r", file->fp);
+ if (file->fp == NULL) {
+ ierr();
+ continue;
+ } else {
+ memcpy(&file->st, &sb2, sizeof(struct stat));
+ set_events(files);
+ }
+ }
+ }
+ show(file);
+ }
+
+ switch (action) {
+ case USE_KQUEUE:
+ ts.tv_sec = 1;
+ ts.tv_nsec = 0;
+ /*
+ * In the -F case we set a timeout to ensure that
+ * we re-stat the file at least once every second.
+ */
+ n = kevent(kq, NULL, 0, ev, 1, Fflag ? &ts : NULL);
+ if (n < 0)
+ err(1, "kevent");
+ if (n == 0) {
+ /* timeout */
+ break;
+ } else if (ev->filter == EVFILT_READ && ev->data < 0) {
+ /* file shrank, reposition to end */
+ if (lseek(ev->ident, (off_t)0, SEEK_END) == -1) {
+ ierr();
+ continue;
+ }
+ }
+ break;
+
+ case USE_SLEEP:
+ (void) usleep(250000);
+ break;
+ }
+ }
+}
diff --git a/text_cmds/tail/misc.c b/text_cmds/tail/misc.c
new file mode 100644
index 0000000..584e3d1
--- /dev/null
+++ b/text_cmds/tail/misc.c
@@ -0,0 +1,119 @@
+/*-
+ * 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. 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/cdefs.h>
+
+__FBSDID("$FreeBSD$");
+
+#ifndef lint
+static const char sccsid[] = "@(#)misc.c 8.1 (Berkeley) 6/6/93";
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "extern.h"
+
+void
+ierr()
+{
+ warn("%s", fname);
+ rval = 1;
+}
+
+void
+oerr()
+{
+ err(1, "stdout");
+}
+
+/*
+ * Print `len' bytes from the file associated with `mip', starting at
+ * absolute file offset `startoff'. May move map window.
+ */
+int
+mapprint(struct mapinfo *mip, off_t startoff, off_t len)
+{
+ int n;
+
+ while (len > 0) {
+ if (startoff < mip->mapoff || startoff >= mip->mapoff +
+ (off_t)mip->maplen) {
+ if (maparound(mip, startoff) != 0)
+ return (1);
+ }
+ n = (mip->mapoff + mip->maplen) - startoff;
+ if (n > len)
+ n = len;
+ WR(mip->start + (startoff - mip->mapoff), n);
+ startoff += n;
+ len -= n;
+ }
+ return (0);
+}
+
+/*
+ * Move the map window so that it contains the byte at absolute file
+ * offset `offset'. The start of the map window will be TAILMAPLEN
+ * aligned.
+ */
+int
+maparound(struct mapinfo *mip, off_t offset)
+{
+
+ if (mip->start != NULL && munmap(mip->start, mip->maplen) != 0)
+ return (1);
+
+ mip->mapoff = offset & ~((off_t)TAILMAPLEN - 1);
+ mip->maplen = TAILMAPLEN;
+ if ((off_t)mip->maplen > mip->maxoff - mip->mapoff)
+ mip->maplen = mip->maxoff - mip->mapoff;
+ if (mip->maplen <= 0)
+ abort();
+ if ((mip->start = mmap(NULL, mip->maplen, PROT_READ, MAP_SHARED,
+ mip->fd, mip->mapoff)) == MAP_FAILED)
+ return (1);
+
+ return (0);
+}
diff --git a/text_cmds/tail/read.c b/text_cmds/tail/read.c
new file mode 100644
index 0000000..85d4d6e
--- /dev/null
+++ b/text_cmds/tail/read.c
@@ -0,0 +1,214 @@
+/*-
+ * 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. 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/cdefs.h>
+
+__FBSDID("$FreeBSD$");
+
+#ifndef lint
+static const char sccsid[] = "@(#)read.c 8.1 (Berkeley) 6/6/93";
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.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.
+ */
+int
+bytes(FILE *fp, off_t off)
+{
+ int ch, len, tlen;
+ char *ep, *p, *t;
+ int wrap;
+ char *sp;
+
+ if ((sp = p = malloc(off)) == NULL)
+ err(1, "malloc");
+
+ for (wrap = 0, ep = p + off; (ch = getc(fp)) != EOF;) {
+ *p = ch;
+ if (++p == ep) {
+ wrap = 1;
+ p = sp;
+ }
+ }
+ if (ferror(fp)) {
+ ierr();
+ 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);
+ len = p - sp;
+ if (len)
+ 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.
+ */
+int
+lines(FILE *fp, off_t off)
+{
+ struct {
+ int blen;
+ u_int len;
+ char *l;
+ } *llines;
+ int ch, rc;
+ char *p, *sp;
+ int blen, cnt, recno, wrap;
+
+ if ((llines = malloc(off * sizeof(*llines))) == NULL)
+ err(1, "malloc");
+ bzero(llines, off * sizeof(*llines));
+ sp = NULL;
+ blen = cnt = recno = wrap = 0;
+ rc = 0;
+
+ while ((ch = getc(fp)) != EOF) {
+ if (++cnt > blen) {
+ if ((sp = realloc(sp, blen += 1024)) == NULL)
+ err(1, "realloc");
+ p = sp + cnt - 1;
+ }
+ *p++ = ch;
+ if (ch == '\n') {
+ if ((int)llines[recno].blen < cnt) {
+ llines[recno].blen = cnt + 256;
+ if ((llines[recno].l = realloc(llines[recno].l,
+ llines[recno].blen)) == NULL)
+ err(1, "realloc");
+ }
+ bcopy(sp, llines[recno].l, llines[recno].len = cnt);
+ cnt = 0;
+ p = sp;
+ if (++recno == off) {
+ wrap = 1;
+ recno = 0;
+ }
+ }
+ }
+ if (ferror(fp)) {
+ ierr();
+ rc = 1;
+ goto done;
+ }
+ if (cnt) {
+ llines[recno].l = sp;
+ sp = NULL;
+ llines[recno].len = cnt;
+ if (++recno == off) {
+ wrap = 1;
+ recno = 0;
+ }
+ }
+
+ if (rflag) {
+ for (cnt = recno - 1; cnt >= 0; --cnt)
+ WR(llines[cnt].l, llines[cnt].len);
+ if (wrap)
+ for (cnt = off - 1; cnt >= recno; --cnt)
+ WR(llines[cnt].l, llines[cnt].len);
+ } else {
+ if (wrap)
+ for (cnt = recno; cnt < off; ++cnt)
+ WR(llines[cnt].l, llines[cnt].len);
+ for (cnt = 0; cnt < recno; ++cnt)
+ WR(llines[cnt].l, llines[cnt].len);
+ }
+done:
+ for (cnt = 0; cnt < off; cnt++)
+ free(llines[cnt].l);
+ free(sp);
+ free(llines);
+ return (rc);
+}
diff --git a/text_cmds/tail/reverse.c b/text_cmds/tail/reverse.c
new file mode 100644
index 0000000..f0a52aa
--- /dev/null
+++ b/text_cmds/tail/reverse.c
@@ -0,0 +1,287 @@
+/*-
+ * 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)reverse.c 8.1 (Berkeley) 6/6/93";
+#endif /* not lint */
+#endif
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "extern.h"
+
+static void r_buf(FILE *);
+static void r_reg(FILE *, enum STYLE, off_t, struct stat *);
+
+/*
+ * 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 mmap the file and display the lines
+ * NOREG cyclically read characters into a wrap-around buffer
+ *
+ * LINES display N lines
+ * REG mmap the file and display the lines
+ * NOREG cyclically read lines into a wrap-around array of buffers
+ *
+ * FILE display the entire file
+ * REG mmap the file and display the lines
+ * NOREG cyclically read input into a linked list of buffers
+ */
+void
+reverse(FILE *fp, enum STYLE style, off_t off, struct stat *sbp)
+{
+ if (style != REVERSE && off == 0)
+ return;
+
+ if (S_ISREG(sbp->st_mode))
+ r_reg(fp, style, off, sbp);
+ else
+ switch(style) {
+ case FBYTES:
+ case RBYTES:
+ bytes(fp, off);
+ break;
+ case FLINES:
+ case RLINES:
+ lines(fp, off);
+ break;
+ case REVERSE:
+ r_buf(fp);
+ break;
+ default:
+ break;
+ }
+}
+
+/*
+ * r_reg -- display a regular file in reverse order by line.
+ */
+static void
+r_reg(FILE *fp, enum STYLE style, off_t off, struct stat *sbp)
+{
+ struct mapinfo map;
+ off_t curoff, size, lineend;
+ int i;
+
+ if (!(size = sbp->st_size))
+ return;
+
+ map.start = NULL;
+ map.mapoff = map.maxoff = size;
+ map.fd = fileno(fp);
+
+ /*
+ * Last char is special, ignore whether newline or not. Note that
+ * size == 0 is dealt with above, and size == 1 sets curoff to -1.
+ */
+ curoff = size - 2;
+ lineend = size;
+ while (curoff >= 0) {
+ if (curoff < map.mapoff ||
+ curoff >= map.mapoff + (off_t)map.maplen) {
+ if (maparound(&map, curoff) != 0) {
+ ierr();
+ return;
+ }
+ }
+ for (i = curoff - map.mapoff; i >= 0; i--) {
+ if (style == RBYTES && --off == 0)
+ break;
+ if (map.start[i] == '\n')
+ break;
+ }
+ /* `i' is either the map offset of a '\n', or -1. */
+ curoff = map.mapoff + i;
+ if (i < 0)
+ continue;
+
+ /* Print the line and update offsets. */
+ if (mapprint(&map, curoff + 1, lineend - curoff - 1) != 0) {
+ ierr();
+ return;
+ }
+ lineend = curoff + 1;
+ curoff--;
+
+ if (style == RLINES)
+ off--;
+
+ if (off == 0 && style != REVERSE) {
+ /* Avoid printing anything below. */
+ curoff = 0;
+ break;
+ }
+ }
+ if (curoff < 0 && mapprint(&map, 0, lineend) != 0) {
+ ierr();
+ return;
+ }
+ if (map.start != NULL && munmap(map.start, map.maplen))
+ ierr();
+}
+
+typedef struct bf {
+ struct bf *next;
+ struct bf *prev;
+ int len;
+ char *l;
+} BF;
+
+/*
+ * 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)
+{
+ BF *mark, *tl, *tr;
+ int ch, len, llen;
+ char *p;
+ off_t enomem;
+
+#define BSZ (128 * 1024)
+ 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(BF))) == NULL ||
+ (tl->l = malloc(BSZ)) == NULL) {
+ if (!mark)
+ err(1, "malloc");
+ 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;
+ }
+
+ /* Fill the block with input data. */
+ for (p = tl->l, len = 0;
+ len < BSZ && (ch = getc(fp)) != EOF; ++len)
+ *p++ = ch;
+
+ if (ferror(fp)) {
+ ierr();
+ return;
+ }
+
+ /*
+ * If no input data for this block and we tossed some data,
+ * recover it.
+ */
+ if (!len && enomem) {
+ enomem -= tl->len;
+ tl = tl->prev;
+ break;
+ }
+
+ tl->len = len;
+ if (ch == EOF)
+ break;
+ }
+
+ if (enomem) {
+ warnx("warning: %jd bytes discarded", (intmax_t)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;
+ }
+}
diff --git a/text_cmds/tail/tail.1 b/text_cmds/tail/tail.1
new file mode 100644
index 0000000..251511a
--- /dev/null
+++ b/text_cmds/tail/tail.1
@@ -0,0 +1,188 @@
+.\" 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. All advertising materials mentioning features or use of this software
+.\" must display the following acknowledgement:
+.\" This product includes software developed by the University of
+.\" California, Berkeley and its contributors.
+.\" 4. 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
+.\" $FreeBSD$
+.\"
+.Dd June 29, 2006
+.Dt TAIL 1
+.Os
+.Sh NAME
+.Nm tail
+.Nd display the last part of a file
+.Sh SYNOPSIS
+.Nm
+.Op Fl F | f | r
+.Op Fl q
+.Oo
+.Fl b Ar number | Fl c Ar number | Fl n 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,
+.Dq Li "-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,
+.Dq Li "-n 2"
+displays the last two lines of the input.
+The default starting location is
+.Dq Li "-n 10" ,
+or the last 10 lines of the input.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.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
+The
+.Fl f
+option causes
+.Nm
+to not stop when end of file is reached, but rather to wait for additional
+data to be appended to the input.
+The
+.Fl f
+option is ignored if the standard input is a pipe, but not if it is a FIFO.
+.It Fl F
+The
+.Fl F
+option implies the
+.Fl f
+option, but
+.Nm
+will also check to see if the file being followed has been renamed or rotated.
+The file is closed and reopened when
+.Nm
+detects that the filename being read from has a new inode number.
+The
+.Fl F
+option is ignored if reading from standard input rather than a file.
+.It Fl n Ar number
+The location is
+.Ar number
+lines.
+.It Fl q
+Suppresses printing of headers when multiple files are being examined.
+.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 , 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 a single file is specified, each file is preceded by a
+header consisting of the string
+.Dq Li "==> " Ns Ar XXX Ns Li " <=="
+where
+.Ar XXX
+is the name of the file unless
+.Fl q
+flag is specified.
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr cat 1 ,
+.Xr head 1 ,
+.Xr sed 1
+.Sh STANDARDS
+The
+.Nm
+utility is expected to be a superset of the
+.St -p1003.2-92
+specification.
+In particular, the
+.Fl F ,
+.Fl b
+and
+.Fl r
+options are extensions to that standard.
+.Pp
+The historic command line syntax of
+.Nm
+is supported by this implementation.
+The only difference between this implementation and historic versions
+of
+.Nm ,
+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.,
+.Dq Li "-r -c 4"
+displays the last 4 characters of the last line
+of the input, while the historic tail (using the historic syntax
+.Dq Li -4cr )
+would ignore the
+.Fl c
+option and display the last 4 lines of the input.
+.Sh HISTORY
+A
+.Nm
+command appeared in PWB UNIX.
diff --git a/text_cmds/tail/tail.c b/text_cmds/tail/tail.c
new file mode 100644
index 0000000..57129ef
--- /dev/null
+++ b/text_cmds/tail/tail.c
@@ -0,0 +1,349 @@
+/*-
+ * 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. 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/cdefs.h>
+
+__FBSDID("$FreeBSD$");
+
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 1991, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif
+
+#ifndef lint
+static const char sccsid[] = "@(#)tail.c 8.1 (Berkeley) 6/6/93";
+#endif
+
+#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, fflag, qflag, rflag, rval, no_files;
+const char *fname;
+
+file_info_t *files;
+
+static void obsolete(char **);
+static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ struct stat sb;
+ FILE *fp;
+ off_t off;
+ enum STYLE style;
+ int i, ch, first;
+ file_info_t *file;
+ char *p;
+
+ /*
+ * 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, "Fb:c:fn:qr")) != -1)
+ switch(ch) {
+ case 'F': /* -F is superset of (and implies) -f */
+ Fflag = fflag = 1;
+ break;
+ 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 'q':
+ qflag = 1;
+ break;
+ case 'r':
+ rflag = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ no_files = argc ? argc : 1;
+
+ /*
+ * 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 (fflag && !argc) {
+ /*
+ * 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(stdin), (off_t)0, SEEK_CUR) == -1 &&
+ errno == ESPIPE) {
+ errno = 0;
+ fflag = 0; /* POSIX.2 requires this. */
+ }
+ }
+
+ if (fflag) {
+ files = (struct file_info *) malloc(no_files * sizeof(struct file_info));
+ if (! files)
+ err(1, "Couldn't malloc space for file descriptors.");
+
+ for (file = files; (fname = argc ? *argv++ : "stdin"); file++) {
+ file->file_name = malloc(strlen(fname)+1);
+ if (! file->file_name)
+ errx(1, "Couldn't malloc space for file name.");
+ strncpy(file->file_name, fname, strlen(fname)+1);
+ file->fp = argc ? fopen(file->file_name, "r") : stdin;
+ if (file->fp == NULL ||
+ fstat(fileno(file->fp), &file->st)) {
+ file->fp = NULL;
+ ierr();
+ continue;
+ }
+ if (!argc)
+ break;
+ }
+ follow(files, style, off);
+ for (i = 0, file = files; i < no_files; i++, file++) {
+ free(file->file_name);
+ }
+ free(files);
+ } else if (*argv) {
+ for (first = 1; (fname = *argv++);) {
+ if ((fp = fopen(fname, "r")) == NULL ||
+ fstat(fileno(fp), &sb)) {
+ ierr();
+ continue;
+ }
+ if (argc > 1 && !qflag) {
+ (void)printf("%s==> %s <==\n",
+ first ? "" : "\n", fname);
+ first = 0;
+ (void)fflush(stdout);
+ }
+
+#ifdef __APPLE__
+ /* 3849683: don't read a directory */
+ if (S_IFDIR == (sb.st_mode & S_IFMT))
+ continue;
+#endif
+
+ if (rflag)
+ reverse(fp, style, off, &sb);
+ else
+ forward(fp, style, off, &sb);
+ }
+ } else {
+ fname = "stdin";
+
+ if (fstat(fileno(stdin), &sb)) {
+ ierr();
+ exit(1);
+ }
+
+ if (rflag)
+ reverse(stdin, style, off, &sb);
+ else
+ forward(stdin, style, off, &sb);
+ }
+ 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][Ffr] 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 + 3)) == NULL)
+ err(1, "malloc");
+ *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 == '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)strcpy(p, ap);
+ *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 'f':
+ case 'r':
+ continue;
+
+ /* Illegal option, return and let getopt handle it. */
+ default:
+ return;
+ }
+ }
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr,
+ "usage: tail [-F | -f | -r] [-q] [-b # | -c # | -n #]"
+ " [file ...]\n");
+ exit(1);
+}