changeset 13197:1b557c5c9727

244 Need replacement for closed /usr/bin/tail and /usr/xpg4/bin/tail Reviewed by: garrett@nexenta.com Reviewed by: richlowe@richlowe.net Reviewed by: gordon.w.ross@gmail.com Approved by: garrett@nexenta.com
author Chris Love <cjlove@san.rr.com>
date Fri, 01 Oct 2010 20:02:51 -0700
parents 5c9ec1505f4b
children f4ee39d1ce08
files exception_lists/closed-bins usr/src/Makefile.lint usr/src/cmd/Makefile usr/src/cmd/tail/Makefile usr/src/cmd/tail/THIRDPARTYLICENSE usr/src/cmd/tail/THIRDPARTYLICENSE.descrip usr/src/cmd/tail/extern.h usr/src/cmd/tail/forward.c usr/src/cmd/tail/misc.c usr/src/cmd/tail/read.c usr/src/cmd/tail/reverse.c usr/src/cmd/tail/tail.c usr/src/cmd/tail/tests/sun_solaris_tail.sh usr/src/cmd/tail/tests/tailtests.sh usr/src/pkg/manifests/SUNWcs.mf usr/src/pkg/manifests/system-xopen-xcu4.mf
diffstat 16 files changed, 2190 insertions(+), 6 deletions(-) [+]
line wrap: on
line diff
--- a/exception_lists/closed-bins	Sat Sep 25 11:51:56 2010 +0800
+++ b/exception_lists/closed-bins	Fri Oct 01 20:02:51 2010 -0700
@@ -38,5 +38,8 @@
 ./usr/lib/localedef/src/iso_8859_1/localedef.src
 ./usr/bin/localedef
 ./usr/bin/tr
+./usr/bin/tail 
+./usr/xpg4/bin/tail 
+
 ./usr/xpg4/bin/tr
 ./usr/xpg6/bin/tr
--- a/usr/src/Makefile.lint	Sat Sep 25 11:51:56 2010 +0800
+++ b/usr/src/Makefile.lint	Fri Oct 01 20:02:51 2010 -0700
@@ -291,6 +291,7 @@
 	cmd/syseventd \
 	cmd/syslogd \
 	cmd/tabs \
+	cmd/tail \
 	cmd/th_tools \
 	cmd/tip \
 	cmd/touch \
@@ -476,7 +477,6 @@
 	$(CLOSED)/cmd/cmd-inet/usr.lib/in.iked \
 	$(CLOSED)/cmd/pax \
 	$(CLOSED)/cmd/sed_xpg4 \
-	$(CLOSED)/cmd/tail \
 	$(CLOSED)/lib/libc_i18n
 
 i386_SUBDIRS= \
--- a/usr/src/cmd/Makefile	Sat Sep 25 11:51:56 2010 +0800
+++ b/usr/src/cmd/Makefile	Fri Oct 01 20:02:51 2010 -0700
@@ -393,6 +393,7 @@
 	syseventadm	\
 	syslogd		\
 	tabs		\
+	tail		\
 	tar		\
 	tbl		\
 	tcopy		\
@@ -473,8 +474,7 @@
 	$(CLOSED)/cmd/pax	\
 	$(CLOSED)/cmd/printf	\
 	$(CLOSED)/cmd/sed	\
-	$(CLOSED)/cmd/sed_xpg4	\
-	$(CLOSED)/cmd/tail
+	$(CLOSED)/cmd/sed_xpg4
 
 i386_SUBDIRS=		\
 	acpihpd		\
@@ -757,8 +757,7 @@
 	$(CLOSED)/cmd/pax	\
 	$(CLOSED)/cmd/printf	\
 	$(CLOSED)/cmd/sed	\
-	$(CLOSED)/cmd/sed_xpg4	\
-	$(CLOSED)/cmd/tail
+	$(CLOSED)/cmd/sed_xpg4
 
 sparc_MSGSUBDIRS=	\
 	fruadm		\
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/tail/Makefile	Fri Oct 01 20:02:51 2010 -0700
@@ -0,0 +1,57 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source.  A copy is of the CDDL is also available via the Internet
+# at http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2010 Chris Love.  All rights reserved.
+#
+
+
+PROG=		tail
+XPG4PROG=	$(PROG)
+
+OBJS=		forward.o misc.o read.o reverse.o tail.o
+SRCS=	 	$(OBJS:%.o=%.c)
+
+include ../Makefile.cmd
+
+CLOBBERFILES=	$(PROG)
+
+
+C99MODE=	-xc99=%all
+C99LMODE=	-Xc99=%all
+LINTFLAGS +=	-I. -erroff=E_CONSTANT_CONDITION
+
+# install rules
+$(ROOTINC)/% : %
+	$(INS.file)
+
+.KEEP_STATE:
+
+.PARALLEL: $(OBJS)
+
+all:		$(PROG) 
+
+$(PROG):	$(OBJS)
+	$(LINK.c) $(OBJS) -o $@ $(LDLIBS)
+	$(POST_PROCESS)
+
+install: all .WAIT $(ROOTPROG) $(ROOTXPG4PROG)
+
+$(ROOTXPG4PROG):
+	-$(RM) $@
+	-$(LN) -s ../../bin/$(PROG) $@
+
+lint: 	lint_SRCS
+
+clean:
+	$(RM) $(OBJS)
+
+include ../Makefile.targ
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/tail/THIRDPARTYLICENSE	Fri Oct 01 20:02:51 2010 -0700
@@ -0,0 +1,76 @@
+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.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+
+
+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.
+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.
+
+
+Copyright (c) 1988, 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.
+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.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/tail/THIRDPARTYLICENSE.descrip	Fri Oct 01 20:02:51 2010 -0700
@@ -0,0 +1,1 @@
+TAIL UTILITY
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/tail/extern.h	Fri Oct 01 20:02:51 2010 -0700
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ * 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.
+ *
+ */
+
+
+#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 *, const char *, enum STYLE, off_t, struct stat *);
+void reverse(FILE *, const char *, enum STYLE, off_t, struct stat *);
+
+int bytes(FILE *, const char *, off_t);
+int lines(FILE *, const char *, off_t);
+
+void ierr(const char *);
+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;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/tail/forward.c	Fri Oct 01 20:02:51 2010 -0700
@@ -0,0 +1,397 @@
+/*
+ * 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.
+ * 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.
+ */
+
+/*
+ * Solaris porting notes:  the original FreeBSD version made use of the
+ *                         BSD kqueue event notification framework; this
+ *                         was changed to use the Solaris event completion
+ *                         framework: port_create(), port_associate(),
+ *                         and port_get().
+ */
+
+#include <sys/param.h>
+#include <sys/mount.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/statfs.h>
+#include <sys/statvfs.h>
+#include <sys/time.h>
+#include <sys/mman.h>
+#include <sys/poll.h>
+#include <port.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 *, const char *fn, off_t, struct stat *);
+static int show(file_info_t *);
+static void set_events(file_info_t *files);
+
+/* defines for inner loop actions */
+#define	USE_SLEEP	0
+#define	USE_PORT	1
+#define	ADD_EVENTS	2
+
+int port;
+int action = USE_SLEEP;
+port_event_t ev;
+
+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, const char *fn, 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(fn);
+				return;
+			}
+		} else while (off--)
+			if ((ch = getc(fp)) == EOF) {
+				if (ferror(fp)) {
+					ierr(fn);
+					return;
+				}
+				break;
+			}
+		break;
+	case FLINES:
+		if (off == 0)
+			break;
+		for (;;) {
+			if ((ch = getc(fp)) == EOF) {
+				if (ferror(fp)) {
+					ierr(fn);
+					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(fn);
+				return;
+			}
+		} else if (off == 0) {
+			while (getc(fp) != EOF)
+				;
+			if (ferror(fp)) {
+				ierr(fn);
+				return;
+			}
+		} else
+			if (bytes(fp, fn, off))
+				return;
+		break;
+	case RLINES:
+		if (S_ISREG(sbp->st_mode))
+			if (!off) {
+				if (fseeko(fp, (off_t)0, SEEK_END) == -1) {
+					ierr(fn);
+					return;
+				}
+			} else
+				rlines(fp, fn, off, sbp);
+		else if (off == 0) {
+			while (getc(fp) != EOF)
+				;
+			if (ferror(fp)) {
+				ierr(fn);
+				return;
+			}
+		} else
+			if (lines(fp, fn, off))
+				return;
+		break;
+	default:
+		break;
+	}
+
+	while ((ch = getc(fp)) != EOF)
+		if (putchar(ch) == EOF)
+			oerr();
+	if (ferror(fp)) {
+		ierr(fn);
+		return;
+	}
+	(void) fflush(stdout);
+}
+
+/*
+ * rlines -- display the last offset lines of the file.
+ */
+static void
+rlines(FILE *fp, const char *fn, off_t off, struct stat *sbp)
+{
+	struct mapinfo map;
+	off_t curoff, size;
+	int i;
+
+	if ((size = sbp->st_size) == 0)
+		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(fn);
+			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(fn);
+		exit(1);
+	}
+
+	/* Set the file pointer to reflect the length displayed. */
+	if (fseeko(fp, sbp->st_size, SEEK_SET) == -1) {
+		ierr(fn);
+		return;
+	}
+	if (map.start != NULL && munmap(map.start, map.maplen)) {
+		ierr(fn);
+		return;
+	}
+}
+
+static int
+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)) {
+		(void) fclose(file->fp);
+		file->fp = NULL;
+		ierr(file->file_name);
+		return (0);
+	}
+	clearerr(file->fp);
+	return (1);
+}
+
+static void
+set_events(file_info_t *files)
+{
+	int i;
+	file_info_t *file;
+
+	action = USE_PORT;
+	for (i = 0, file = files; i < no_files; i++, file++) {
+		if (! file->fp)
+			continue;
+
+		(void) fstat(fileno(file->fp), &file->st);
+		/* For -f or -F will both use Solaris port interface */
+		if (fflag && (fileno(file->fp) != STDIN_FILENO)) {
+			(void) port_associate(port, PORT_SOURCE_FD,
+			    fileno(file->fp), POLLIN, (void*)file);
+		}
+	}
+}
+
+/*
+ * follow -- display the file, from an offset, forward.
+ *
+ */
+void
+follow(file_info_t *files, enum STYLE style, off_t off)
+{
+	int active, ev_change, 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);
+			forward(file->fp, file->file_name, style, off,
+			    &file->st);
+			if (Fflag && fileno(file->fp) != STDIN_FILENO)
+				n++;
+		}
+	}
+	if (!Fflag && !active)
+		return;
+
+	last = --file;
+	port = port_create();
+	set_events(files);
+
+	for (;;) {
+		ev_change = 0;
+		if (Fflag) {
+			for (i = 0, file = files; i < no_files; i++, file++) {
+				if (!file->fp) {
+					file->fp = fopen(file->file_name, "r");
+					if (file->fp != NULL &&
+					    fstat(fileno(file->fp), &file->st)
+					    == -1) {
+						(void) fclose(file->fp);
+						file->fp = NULL;
+					}
+					if (file->fp != NULL)
+						ev_change++;
+					continue;
+				}
+				if (fileno(file->fp) == STDIN_FILENO)
+					continue;
+				if (stat(file->file_name, &sb2) == -1) {
+					if (errno != ENOENT)
+						ierr(file->file_name);
+					(void) show(file);
+					(void) fclose(file->fp);
+					file->fp = NULL;
+					ev_change++;
+					continue;
+				}
+
+				if (sb2.st_ino != file->st.st_ino ||
+				    sb2.st_dev != file->st.st_dev ||
+				    sb2.st_nlink == 0) {
+					(void) show(file);
+					file->fp = freopen(file->file_name, "r",
+					    file->fp);
+					if (file->fp != NULL)
+						(void) memcpy(&file->st, &sb2,
+						    sizeof (struct stat));
+					else if (errno != ENOENT)
+						ierr(file->file_name);
+					ev_change++;
+				}
+			}
+		}
+
+		for (i = 0, file = files; i < no_files; i++, file++)
+			if (file->fp && !show(file))
+				ev_change++;
+
+		if (ev_change)
+			set_events(files);
+
+		switch (action) {
+		case USE_PORT:
+			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 = port_get(port, &ev, Fflag? &ts : NULL);
+			if (n == 0) {
+				file = (file_info_t *)ev.portev_user;
+				(void) port_associate(port, PORT_SOURCE_FD,
+				    fileno(file->fp), POLLIN, (void*)file);
+			}
+			break;
+
+		case USE_SLEEP:
+			(void) usleep(250000);
+			break;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/tail/misc.c	Fri Oct 01 20:02:51 2010 -0700
@@ -0,0 +1,107 @@
+/*
+ * 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.
+ * 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/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(const char *fname)
+{
+	warn("%s", fname);
+	rval = 1;
+}
+
+void
+oerr(void)
+{
+	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);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/tail/read.c	Fri Oct 01 20:02:51 2010 -0700
@@ -0,0 +1,203 @@
+/*
+ * 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.
+ * 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/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 <strings.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, const char *fn, 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(fn);
+		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, const char *fn, off_t off)
+{
+	struct {
+		int blen;
+		uint_t 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));
+	p = 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(fn);
+		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);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/tail/reverse.c	Fri Oct 01 20:02:51 2010 -0700
@@ -0,0 +1,275 @@
+/*
+ * 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.
+ * 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/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 *, const char *);
+static void r_reg(FILE *, const char *, 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, const char *fn, enum STYLE style, off_t off, struct stat *sbp)
+{
+	if (style != REVERSE && off == 0)
+		return;
+
+	if (S_ISREG(sbp->st_mode))
+		r_reg(fp, fn, style, off, sbp);
+	else
+		switch (style) {
+		case FBYTES:
+		case RBYTES:
+			(void) bytes(fp, fn, off);
+			break;
+		case FLINES:
+		case RLINES:
+			(void) lines(fp, fn, off);
+			break;
+		case REVERSE:
+			r_buf(fp, fn);
+			break;
+		default:
+			break;
+		}
+}
+
+/*
+ * r_reg -- display a regular file in reverse order by line.
+ */
+static void
+r_reg(FILE *fp, const char *fn, enum STYLE style, off_t off, struct stat *sbp)
+{
+	struct mapinfo map;
+	off_t curoff, size, lineend;
+	int i;
+
+	if ((size = sbp->st_size) == 0)
+		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(fn);
+				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(fn);
+			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(fn);
+		return;
+	}
+	if (map.start != NULL && munmap(map.start, map.maplen))
+		ierr(fn);
+}
+
+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, const char *fn)
+{
+	BF *mark, *tl, *tr;
+	int ch, len, llen;
+	char *p;
+	off_t enomem;
+
+	tl = NULL;
+#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(fn);
+			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;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/tail/tail.c	Fri Oct 01 20:02:51 2010 -0700
@@ -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.
+ * 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/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;
+
+file_info_t *files;
+
+static void obsolete(char **);
+static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+	struct stat sb;
+	const char *fn;
+	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;
+	off = 0;
+	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 (*argv && 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; (fn = *argv++); file++) {
+			file->file_name = strdup(fn);
+			if (! file->file_name)
+				errx(1, "Couldn't malloc space for file name.");
+			if ((file->fp = fopen(file->file_name, "r")) == NULL ||
+			    fstat(fileno(file->fp), &file->st)) {
+				if (file->fp != NULL) {
+					(void) fclose(file->fp);
+					file->fp = NULL;
+				}
+				if (!Fflag || errno != ENOENT)
+					ierr(file->file_name);
+			}
+		}
+		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; (fn = *argv++); ) {
+			if ((fp = fopen(fn, "r")) == NULL ||
+			    fstat(fileno(fp), &sb)) {
+				ierr(fn);
+				continue;
+			}
+			if (argc > 1 && !qflag) {
+				(void) printf("%s==> %s <==\n",
+				    first ? "" : "\n", fn);
+				first = 0;
+				(void) fflush(stdout);
+			}
+
+			if (rflag)
+				reverse(fp, fn, style, off, &sb);
+			else
+				forward(fp, fn, style, off, &sb);
+		}
+	} else {
+		fn = "stdin";
+
+		if (fstat(fileno(stdin), &sb)) {
+			ierr(fn);
+			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(stdin), (off_t)0, SEEK_CUR) == -1 &&
+		    errno == ESPIPE) {
+			errno = 0;
+			fflag = 0;		/* POSIX.2 requires this. */
+		}
+
+		if (rflag)
+			reverse(stdin, fn, style, off, &sb);
+		else
+			forward(stdin, fn, 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;
+
+		/*
+		 * Legacy Solaris tail supports "+c" "-c", "+l", "-l",
+		 * "+b", and "-b" with an default number of 10.  Map
+		 * these arguments to an explicit +/-10 for FreeBSD.
+		 * New argument will be of the form -[bcn][+-]10
+		 */
+		case 'b':
+		case 'c':
+		case 'l':
+			if ((start = p = malloc(6)) == NULL)
+				err(1, "malloc");
+			*p++ = '-';
+			switch (ap[0]) {
+			case 'c':
+				*p++ = ap[0];
+				break;
+			case 'b':
+				*p++ = ap[0];
+				break;
+			case 'l':
+				*p++ = 'n';
+				break;
+			}
+			sprintf(p, "%c10", *argv[0]);
+			*argv = start;
+
+			continue;
+		/*
+		 * Options w/ arguments, skip the argument and continue
+		 * with the next option.
+		 */
+		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);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/tail/tests/sun_solaris_tail.sh	Fri Oct 01 20:02:51 2010 -0700
@@ -0,0 +1,505 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
+#
+
+#
+# Additional tests borrowed from ksh93 builtin tail test script
+# (usr/src/lib/libshell/common/tests/sun_solaris_builtin_tail).  Modified
+# to use /usr/bin/tail rather than the ksh93 builtin.
+#
+TAIL=/usr/bin/tail
+
+# test setup
+function err_exit
+{
+	print -u2 -n "\t"
+	print -u2 -r ${Command}[$1]: "${@:2}"
+	(( Errors < 127 && Errors++ ))
+}
+alias err_exit='err_exit $LINENO'
+
+set -o nounset
+Command=${0##*/}
+integer Errors=0
+
+# common functions
+function isvalidpid
+{
+        kill -0 ${1} 2>/dev/null && return 0
+        return 1
+}
+
+function waitpidtimeout
+{
+	integer pid=$1
+	float timeout=$2
+	float i
+	float -r STEP=0.5 # const
+
+	(( timeout=timeout/STEP ))
+	
+	for (( i=0 ; i < timeout ; i+=STEP )) ; do
+		isvalidpid ${pid} || break
+		sleep ${STEP}
+	done
+	
+	return 0
+}
+
+function myintseq
+{
+        integer i
+	float arg1=$1
+	float arg2=$2
+	float arg3=$3
+
+        case $# in
+                1)
+                        for (( i=1 ; i <= arg1 ; i++ )) ; do
+                                printf "%d\n" i
+                        done
+                        ;;
+                2)
+                        for (( i=arg1 ; i <= arg2 ; i++ )) ; do
+                                printf "%d\n" i
+                        done
+                        ;;
+                3)
+                        for (( i=arg1 ; i <= arg3 ; i+=arg2 )) ; do
+                                printf "%d\n" i
+                        done
+                        ;;
+                *)
+                        print -u2 -f "%s: Illegal number of arguments %d\n" "$0" $#
+			return 1
+                        ;;
+        esac
+        
+        return 0
+}
+
+# quote input string but use single-backslash that "err_exit" prints
+# the strings correctly
+function singlebackslashquote
+{
+	typeset s
+	s="$(printf "%q\n" "$1")"
+	print -r "$s"
+	return 0
+}
+
+# quote input string but use double-backslash that "err_exit" prints
+# the strings correctly
+function doublebackslashquote
+{
+	typeset s
+	s="$(printf "%q\n" "$1")"
+	s="${s//\\/\\\\}"
+	print -r "$s"
+	return 0
+}
+
+
+# main
+builtin mktemp || err_exit "mktemp builtin not found"
+builtin rm || err_exit "rm builtin not found"
+# builtin tail || err_exit "tail builtin not found"
+
+typeset ocwd
+typeset tmpdir
+
+# create temporary test directory
+ocwd="$PWD"
+tmpdir="$(mktemp -t -d "test_sun_solaris_builtin_tail.XXXXXXXX")" || err_exit "Cannot create temporary directory"
+
+cd "${tmpdir}" || { err_exit "cd ${tmpdir} failed." ; exit $((Errors)) ; }
+
+
+# run tests:
+
+# test1: basic tests
+compound -a testcases=(
+	(
+		name="reverse_n"
+		input=$'hello\nworld'
+		compound -A tail_args=(
+			[legacy]=(   argv=( "-r"  ) )
+		)
+		expected_output=$'world\nhello'
+	)
+	(
+		name="revlist0n"
+		input=$'1\n2\n3\n4'
+		compound -A tail_args=(
+			[legacy]=(   argv=( "-0"	 ) )
+#			[std_like]=( argv=( "-n" "0" ) ) 
+		)
+		expected_output=$''
+	)
+	(
+		name="revlist0nr"
+		input=$'1\n2\n3\n4'
+		compound -A tail_args=(
+			[legacy]=(       argv=( "-0r"	      ) )
+#			[std_like]=(     argv=( "-n" "0" "-r" ) )
+#			[long_options]=( argv=( "--lines" "0" "--reverse" ) )
+		)
+		expected_output=$'' )
+	(
+		name="revlist1n"
+		input=$'1\n2\n3\n4'
+		compound -A tail_args=(
+			[legacy]=(       argv=( "-1"     ) )
+#			[std_like]=(     argv=( "-n" "1" ) )
+#			[long_options]=( argv=( "--lines" "1" ) )
+		)
+		expected_output=$'4' )
+	(
+		name="revlist1nr"
+		input=$'1\n2\n3\n4'
+		compound -A tail_args=(
+			[legacy]=(       argv=( "-1r" ) )
+#			[std_like]=(     argv=( "-n" "1" "-r" ) )
+#			[long_options]=( argv=( "--lines" "1" "--reverse" ) )
+		)
+		expected_output=$'4'
+	)
+	(
+		name="revlist2n"
+		input=$'1\n2\n3\n4'
+		compound -A tail_args=(
+			[legacy]=(   argv=( "-2"  ) )
+#			[std_like]=( argv=( "-n" "2" ) )
+		)
+		expected_output=$'3\n4'
+	)
+	(
+		name="revlist2nr"
+		input=$'1\n2\n3\n4'
+		compound -A tail_args=(
+			[legacy]=(   argv=( "-2r" ) )
+#			[std_like]=( argv=( "-n" "2" "-r" ) )
+			)
+		expected_output=$'4\n3'
+	)
+	(
+		name="revlist3nr"
+		input=$'1\n2\n3\n4'
+		compound -A tail_args=(
+			[legacy]=(   argv=( "-3r" ) )
+#			[std_like]=( argv=( "-n" "3" "-r" ) )
+		)
+		expected_output=$'4\n3\n2'
+	)
+	(
+		name="revlist2p"
+		input=$'1\n2\n3\n4'
+		compound -A tail_args=(
+			[legacy]=(   argv=( "+2"  ) )
+#			[std_like]=( argv=( "-n" "+2" ) )
+			)
+		expected_output=$'2\n3\n4'
+	)
+#	Note:  following test case trips up legacy Solaris 'tail' as well
+#	(
+#		name="revlist2pr"
+#		input=$'1\n2\n3\n4'
+#		compound -A tail_args=(
+#			[legacy]=(   argv=( "+2r" ) )
+#			[std_like]=( argv=( "-n" "+2" "-r" ) )
+#		)
+#		expected_output=$'4\n3\n2'
+#	)
+	(
+		name="revlist3p"
+		input=$'1\n2\n3\n4'
+		compound -A tail_args=(
+			[legacy]=(   argv=( "+3"  ) )
+			[std_like]=( argv=( "-n" "+3"  ) )
+		)
+		expected_output=$'3\n4'
+	)
+#	Note: following test case trips up legacy Solaris 'tail' as well
+#	(
+#		name="revlist3pr"
+#		input=$'1\n2\n3\n4'
+#		compound -A tail_args=(
+#			[legacy]=(   argv=( "+3r" ) )
+#			[std_like]=( argv=( "-n" "+3" "-r" ) )
+#		)
+#		expected_output=$'4\n3'
+#	)
+	(
+		name="revlist4p"
+		input=$'1\n2\n3\n4'
+		compound -A tail_args=(
+			[legacy]=(   argv=( "+4"  ) )
+#			[std_like]=( argv=( "-n" "+4"  ) )
+		)
+		expected_output=$'4'
+	)
+#	Note: following test case trips up legacy Solaris 'tail' as well
+#	(
+#		name="revlist4pr"
+#		input=$'1\n2\n3\n4'
+#		compound -A tail_args=(
+#			[legacy]=(   argv=( "+4r" ) )
+#			[std_like]=( argv=( "-n" "+4" "-r" ) )
+#		)
+#		expected_output=$'4'
+#	)
+	(
+		name="revlist5p"
+		input=$'1\n2\n3\n4'
+		compound -A tail_args=(
+			[legacy]=(   argv=( "+5"  ) )
+#			[std_like]=( argv=( "-n" "+5"  ) )
+		)
+		expected_output=$''
+	)
+#	Note: following test case trips up legacy Solaris 'tail' as well
+#	(
+#		name="revlist5pr"
+#		input=$'1\n2\n3\n4'
+#		compound -A tail_args=(
+#			[legacy]=(   argv=( "+5r" ) )
+#			[std_like]=( argv=( "-n" "+5" "-r" ) )
+#		)
+#		expected_output=$''
+#	)
+)
+
+for testid in "${!testcases[@]}" ; do
+	nameref tc=testcases[${testid}]
+
+	for argv_variants in "${!tc.tail_args[@]}" ; do
+		nameref argv=tc.tail_args[${argv_variants}].argv
+		output=$(
+				set -o pipefail
+	          		(trap "" PIPE ; print -r -- "${tc.input}") | $TAIL "${argv[@]}"
+			) || err_exit "test ${tc.name}/${argv_variants}: command failed with exit code $?"
+	
+		[[ "${output}" == "${tc.expected_output}" ]] || err_exit "test ${tc.name}/${argv_variants}: Expected $(doublebackslashquote "${tc.expected_output}"), got $(doublebackslashquote "${output}")"
+	done
+done
+
+
+# test2: test "tail -r </etc/profile | rev -l" vs. "cat </etc/profile"
+[[ "$($TAIL -r </etc/profile | rev -l)" == "$( cat /etc/profile )" ]] || err_exit "'tail -r </etc/profile | rev -l' output does not match 'cat /etc/profile'" 
+
+# Test case not applicable to FreeBSD 'tail'
+# test 3: ast-ksh.2009-05-05 "tail" builtin may crash if we pass unsupported long options
+#$SHELL -o errexit -c 'builtin tail ; print "hello" | tail --attack_of_chicken_monsters' >/dev/null 2>&1
+#(( $? == 2 )) || err_exit "expected exit code 2 for unsupported long option, got $?" 
+
+
+# test 4: FIFO tests
+
+# FIFO test functions
+# (we use functions here to do propper garbage collection)
+function test_tail_fifo_1
+{
+	typeset tail_cmd="$1"
+	integer i
+	integer tail_pid=-1
+	
+	# cleanup trap
+	trap "rm -f tailtestfifo tailout" EXIT
+
+	# create test FIFO
+	mkfifo tailtestfifo
+
+	${tail_cmd} -f <tailtestfifo >tailout &
+	tail_pid=$!
+
+	myintseq 20 >tailtestfifo
+
+	waitpidtimeout ${tail_pid} 5
+
+	if isvalidpid ${tail_pid} ; then
+		err_exit "test_tail_fifo_1: # tail hung (not expected)"
+		kill -KILL ${tail_pid}
+	fi
+
+	wait || err_exit "tail child returned non-zero exit code=$?"
+	
+	[[ "$(cat tailout)" == $'11\n12\n13\n14\n15\n16\n17\n18\n19\n20' ]] || err_exit "test_tail_fifo_1: Expected $(doublebackslashquote '11\n12\n13\n14\n15\n16\n17\n18\n19\n20'), got $(doublebackslashquote "$(cat tailout)")"
+
+	return 0
+}
+
+function test_tail_fifo_2
+{
+	typeset tail_cmd="$1"
+	integer i
+	integer tail_pid=-1
+	
+	# cleanup trap
+	trap "rm -f tailtestfifo tailout" EXIT
+
+	# create test FIFO
+	mkfifo tailtestfifo
+
+	${tail_cmd} -f tailtestfifo >tailout &
+	tail_pid=$!
+
+	myintseq 14 >tailtestfifo
+
+	waitpidtimeout ${tail_pid} 5
+
+	if isvalidpid ${tail_pid} ; then
+		[[ "$(cat tailout)" == $'5\n6\n7\n8\n9\n10\n11\n12\n13\n14' ]] || err_exit "test_tail_fifo_2: Expected $(doublebackslashquote $'5\n6\n7\n8\n9\n10\n11\n12\n13\n14'), got $(doublebackslashquote "$(cat tailout)")"
+
+		myintseq 15 >>tailtestfifo
+
+		waitpidtimeout ${tail_pid} 5
+
+		if isvalidpid ${tail_pid} ; then
+			kill -KILL ${tail_pid}
+		else
+			err_exit "test_tail_fifo_2: # tail exit with return code $? (not expected)"
+		fi
+	fi
+
+	wait || err_exit "tail child returned non-zero exit code=$?"
+	
+	[[ "$(cat tailout)" == $'5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15' ]] || err_exit "test_tail_fifo_2: Expected $(doublebackslashquote $'5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15'), got $(doublebackslashquote "$(cat tailout)")"
+
+	return 0
+}
+
+# fixme: This should test /usr/bin/tail and /usr/xpg4/bin/tail in Solaris
+test_tail_fifo_1 "$TAIL"
+test_tail_fifo_2 "$TAIL"
+
+
+# test 5: "tail -f" tests
+function followtest1
+{
+	typeset -r FOLLOWFILE="followfile.txt"
+	typeset -r OUTFILE="outfile.txt"
+
+	typeset title="$1"
+	typeset testcmd="$2"
+	typeset usenewline=$3
+	typeset followstr=""
+	typeset newline=""
+	integer i
+	integer tailchild=-1
+
+	if ${usenewline} ; then
+		newline=$'\n'
+	fi
+	
+	rm -f "${FOLLOWFILE}" "${OUTFILE}"
+	print -n "${newline}" > "${FOLLOWFILE}"
+
+	${testcmd} -f "${FOLLOWFILE}" >"${OUTFILE}" &
+	(( tailchild=$! ))
+
+	for (( i=0 ; i < 10 ; i++)) ; do
+		followstr+="${newline}${i}"
+		print -n "${i}${newline}" >>"${FOLLOWFILE}"
+		sleep 2
+
+		[[ "$( < "${OUTFILE}")" == "${followstr}" ]] || err_exit "${title}: Expected $(doublebackslashquote "${followstr}"), got "$(doublebackslashquote "$( < "${OUTFILE}")")""
+	done
+
+	kill -KILL ${tailchild} 2>/dev/null
+	#kill -TERM ${tailchild} 2>/dev/null
+	waitpidtimeout ${tailchild} 5
+	
+	if isvalidpid ${tailchild} ; then
+		err_exit "${title}: tail pid=${tailchild} hung."
+		kill -KILL ${tailchild} 2>/dev/null
+	fi
+	
+	wait ${tailchild} 2>/dev/null
+	
+	rm -f "${FOLLOWFILE}" "${OUTFILE}"
+
+	return 0
+}
+
+followtest1 "test5a" "$TAIL" true
+# fixme: later we should test this, too:
+#followtest1 "test5b" "tail" false
+#followtest1 "test5c" "/usr/xpg4/bin/tail" true
+#followtest1 "test5d" "/usr/xpg4/bin/tail" false
+#followtest1 "test5e" "/usr/bin/tail" true
+#followtest1 "test5f" "/usr/bin/tail" false
+
+
+# test 6: "tail -f" tests
+function followtest2
+{
+	typeset -r FOLLOWFILE="followfile.txt"
+	typeset -r OUTFILE="outfile.txt"
+
+	typeset title="$1"
+	typeset testcmd="$2"
+	integer tailchild=-1
+
+	rm -f "${FOLLOWFILE}" "${OUTFILE}"
+
+	myintseq 50000 >"${FOLLOWFILE}"
+
+	${testcmd} -n 60000 -f "${FOLLOWFILE}" >"${OUTFILE}" &
+	(( tailchild=$! ))
+	
+	sleep 10
+
+	kill -KILL ${tailchild} 2>/dev/null
+	#kill -TERM ${tailchild} 2>/dev/null
+	waitpidtimeout ${tailchild} 5
+	
+	if isvalidpid ${tailchild} ; then
+		err_exit "${title}: tail pid=${tailchild} hung."
+		kill -KILL ${tailchild} 2>/dev/null
+	fi
+	
+	wait ${tailchild} 2>/dev/null
+		
+	# this tail should be an external process
+	outstr=$(/usr/bin/tail "${OUTFILE}") || err_exit "tail returned non-zero exit code $?"
+        [[ "${outstr}" == 49991*50000 ]] || err_exit "${title}: Expected match for 49991*50000, got "$(singlebackslashquote "${outstr}")""	
+	
+	rm -f "${FOLLOWFILE}" "${OUTFILE}"
+
+	return 0
+}
+
+followtest2 "test6a" "$TAIL"
+followtest2 "test6b" "$TAIL"
+# fixme: later we should test this, too:
+#followtest2 "test6c" "/usr/bin/tail"
+
+
+# cleanup
+cd "${ocwd}"
+rmdir "${tmpdir}" || err_exit "Cannot remove temporary directory ${tmpdir}".
+
+
+# tests done
+exit $((Errors))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/tail/tests/tailtests.sh	Fri Oct 01 20:02:51 2010 -0700
@@ -0,0 +1,141 @@
+#!/bin/bash
+#
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source.  A copy is of the CDDL is also available via the Internet
+# at http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2010 Chris Love.  All rights reserved.
+#
+
+
+# 
+# Test cases for 'tail', some based on CoreUtils test cases (validated
+# with legacy Solaris 'tail' and/or xpg4 'tail')
+#
+PROG=/usr/bin/tail
+
+case $1 in 
+    -x)
+    	PROG=/usr/xpg4/bin/tail
+	;;
+    -o)
+    	PROG=$2
+	;;
+    -?)
+    	echo "Usage: tailtests.sh [-x][-o <override tail executable>]"
+	exit 1
+	;;
+esac
+
+echo "Using $PROG"
+
+o=`echo -e "bcd"`
+a=`echo -e "abcd" | $PROG +2c`
+[[ "$a" != "$o" ]] && echo "Fail test 1 - $a"
+
+o=`echo -e ""`
+a=`echo "abcd" | $PROG +8c`
+[[ "$a" != "$o" ]] && echo "Fail test 2 - $a"
+
+o=`echo -e "abcd"`
+a=`echo "abcd" | $PROG -9c`
+[[ "$a" != "$o" ]] && echo "Fail test 3 - $a"
+
+o=`echo -e "x"`
+a=`echo -e "x" | $PROG -1l`
+[[ "$a" != "x" ]] && echo "Fail test 4 - $a"
+
+o=`echo -e "\n"`
+a=`echo -e "x\ny\n" | $PROG -1l`
+[[ "$a" != "$o" ]] && echo "Fail test 5 - $a"
+
+o=`echo -e "y\n"`
+a=`echo -e "x\ny\n" | $PROG -2l`
+[[ "$a" != "$o" ]] && echo "Fail test 6 - $a"
+
+o=`echo -e "y"`
+a=`echo -e "x\ny" | $PROG -1l`
+[[ "$a" != "$o" ]] && echo "Fail test 7 - $a"
+
+o=`echo -e "x\ny\n"`
+a=`echo -e "x\ny\n" | $PROG +1l`
+[[ "$a" != "$o" ]] && echo "Fail test 8 - $a"
+
+o=`echo -e "y\n"`
+a=`echo -e "x\ny\n" | $PROG +2l`
+[[ "$a" != "$o" ]] && echo "Fail test 9 - $a"
+
+o=`echo -e "x"`
+a=`echo -e "x" | $PROG -1`
+[[ "$a" != "$o" ]] && echo "Fail test 10 - $a"
+
+o=`echo -e "\n"`
+a=`echo -e "x\ny\n" | $PROG -1`
+[[ "$a" != "$o" ]] && echo "Fail test 11 - $a"
+
+o=`echo -e "y\n"`
+a=`echo -e "x\ny\n" | $PROG -2`
+[[ "$a" != "$o" ]] && echo "Fail test 12 - $a"
+
+o=`echo -e "y"`
+a=`echo -e "x\ny" | $PROG -1`
+[[ "$a" != "$o" ]] && echo "Fail test 13 - $a"
+
+o=`echo -e "x\ny\n"`
+a=`echo -e "x\ny\n" | $PROG +1`
+[[ "$a" != "$o" ]] && echo "Fail test 14 - $a"
+
+o=`echo -e "y\n"`
+a=`echo -e "x\ny\n" | $PROG +2`
+[[ "$a" != "$o" ]] && echo "Fail test 15 - $a"
+
+# For compatibility with Legacy Solaris tail this should also work as '+c'
+o=`echo -e "yyz"`
+a=`echo -e "xyyyyyyyyyyz" | $PROG +10c`
+[[ "$a" != "$o" ]] && echo "Fail test 16 - $a"
+
+o=`echo -e "yyz"`
+a=`echo -e "xyyyyyyyyyyz" | $PROG +c`
+[[ "$a" != "$o" ]] && echo "Fail test 16a - $a"
+
+
+# For compatibility with Legacy Solaris tail this should also work as '+l'
+o=`echo -e "y\ny\nz"`
+a=`echo -e "x\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\nz" | $PROG +10l`
+[[ "$a" != "$o" ]] && echo "Fail test 17 - $a"
+
+o=`echo -e "y\ny\nz"`
+a=`echo -e "x\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\nz" | $PROG +l`
+[[ "$a" != "$o" ]] && echo "Fail test 17a - $a"
+
+
+# For compatibility with Legacy Solaris tail this should also work as '-l'
+o=`echo -e "y\ny\ny\ny\ny\ny\ny\ny\ny\nz"`
+a=`echo -e "x\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\nz" | $PROG -10l`
+[[ "$a" != "$o" ]] && echo "Fail test 18 - $a"
+
+o=`echo -e "y\ny\ny\ny\ny\ny\ny\ny\ny\nz"`
+a=`echo -e "x\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\nz" | $PROG -l`
+[[ "$a" != "$o" ]] && echo "Fail test 18a - $a"
+
+o=`echo -e "c\nb\na"`
+a=`echo -e "a\nb\nc" | $PROG -r`
+[[ "$a" != "$o" ]] && echo "Fail test 19 - $a"
+
+
+echo "Completed"
+
+exit 0
+
+# Template for additional test cases
+#o=`echo -e ""`
+#a=`echo -e "" | $PROG `
+#[[ "$a" != "$o" ]] && echo "Fail test  - $a"
--- a/usr/src/pkg/manifests/SUNWcs.mf	Sat Sep 25 11:51:56 2010 +0800
+++ b/usr/src/pkg/manifests/SUNWcs.mf	Fri Oct 01 20:02:51 2010 -0700
@@ -2570,6 +2570,8 @@
     license=usr/src/cmd/script/THIRDPARTYLICENSE
 license usr/src/cmd/stat/vmstat/THIRDPARTYLICENSE \
     license=usr/src/cmd/stat/vmstat/THIRDPARTYLICENSE
+license usr/src/cmd/tail/THIRDPARTYLICENSE \
+    license=usr/src/cmd/tail/THIRDPARTYLICENSE
 license usr/src/cmd/tip/THIRDPARTYLICENSE \
     license=usr/src/cmd/tip/THIRDPARTYLICENSE
 license usr/src/cmd/tr/THIRDPARTYLICENSE \
--- a/usr/src/pkg/manifests/system-xopen-xcu4.mf	Sat Sep 25 11:51:56 2010 +0800
+++ b/usr/src/pkg/manifests/system-xopen-xcu4.mf	Fri Oct 01 20:02:51 2010 -0700
@@ -70,7 +70,6 @@
 file path=usr/xpg4/bin/sed mode=0555
 file path=usr/xpg4/bin/sort mode=0555
 file path=usr/xpg4/bin/stty mode=0555
-file path=usr/xpg4/bin/tail mode=0555
 file path=usr/xpg4/bin/who mode=0555
 hardlink path=usr/xpg4/bin/bg target=../../../usr/xpg4/bin/alias
 hardlink path=usr/xpg4/bin/cd target=../../../usr/xpg4/bin/alias
@@ -105,4 +104,5 @@
 license lic_OSBL license=lic_OSBL
 license lic_OSBL_preamble license=lic_OSBL_preamble
 link path=usr/xpg4/bin/ipcs target=../../bin/ipcs
+link path=usr/xpg4/bin/tail target=../../bin/tail
 link path=usr/xpg4/bin/tr target=../../bin/tr