summaryrefslogtreecommitdiff
path: root/usr/src/cmd/tail
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/cmd/tail')
-rw-r--r--usr/src/cmd/tail/extern.h5
-rw-r--r--usr/src/cmd/tail/forward.c103
-rw-r--r--usr/src/cmd/tail/tests/tailtests.sh338
3 files changed, 384 insertions, 62 deletions
diff --git a/usr/src/cmd/tail/extern.h b/usr/src/cmd/tail/extern.h
index 147a887272..c308fa88d8 100644
--- a/usr/src/cmd/tail/extern.h
+++ b/usr/src/cmd/tail/extern.h
@@ -28,6 +28,10 @@
*
*/
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <port.h>
+
#define WR(p, size) do { \
if (write(STDOUT_FILENO, p, size) != (ssize_t)size) \
@@ -48,6 +52,7 @@ struct file_info {
FILE *fp;
char *file_name;
struct stat st;
+ file_obj_t fobj;
};
typedef struct file_info file_info_t;
diff --git a/usr/src/cmd/tail/forward.c b/usr/src/cmd/tail/forward.c
index e4f22582f3..d6ca780de1 100644
--- a/usr/src/cmd/tail/forward.c
+++ b/usr/src/cmd/tail/forward.c
@@ -31,9 +31,7 @@
*/
/*
- * Solaris porting notes: the original FreeBSD version made use of the
- * BSD kqueue event notification framework; this
- * was changed to use usleep()
+ * Copyright (c) 2012, Joyent, Inc. All rights reserved.
*/
#include <sys/param.h>
@@ -53,6 +51,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <strings.h>
#include <unistd.h>
#include "extern.h"
@@ -63,9 +62,11 @@ 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 action = USE_SLEEP;
+int port;
+int action = USE_PORT;
static const file_info_t *last;
@@ -261,6 +262,65 @@ show(file_info_t *file)
}
static void
+associate(file_info_t *file, boolean_t assoc)
+{
+ char buf[64];
+
+ if (action != USE_PORT || file->fp == NULL)
+ return;
+
+ if (!S_ISREG(file->st.st_mode)) {
+ /*
+ * For FIFOs, we use PORT_SOURCE_FD as our port event source.
+ */
+ if (assoc) {
+ (void) port_associate(port, PORT_SOURCE_FD,
+ fileno(file->fp), POLLIN, file);
+ } else {
+ (void) port_dissociate(port, PORT_SOURCE_FD,
+ fileno(file->fp));
+ }
+
+ return;
+ }
+
+ bzero(&file->fobj, sizeof (file->fobj));
+
+ /*
+ * We pull a bit of a stunt here. PORT_SOURCE_FILE only allows us to
+ * specify a file name -- not a file descriptor. If we were to specify
+ * the name of the file to port_associate() and that file were moved
+ * aside, we would not be able to reassociate an event because we would
+ * not know a name that would resolve to the new file (indeed, there
+ * might not be such a name -- the file may have been unlinked). But
+ * there _is_ a name that we know maps to the file and doesn't change:
+ * the name of the representation of the open file descriptor in /proc.
+ * We therefore associate with this name (and the underlying file),
+ * not the name of the file as specified at the command line.
+ */
+ (void) snprintf(buf,
+ sizeof (buf), "/proc/self/fd/%d", fileno(file->fp));
+
+ /*
+ * Note that portfs uses the address of the specified file_obj_t to
+ * tag an association; if one creates a different association with a
+ * (different) file_ob_t that happens to be at the same address,
+ * the first association will be implicitly removed. To assure that
+ * each file has a disjoint file_obj_t, we allocate the memory for it
+ * in the file_info, not on the stack.
+ */
+ file->fobj.fo_name = buf;
+
+ if (assoc) {
+ (void) port_associate(port, PORT_SOURCE_FILE,
+ (uintptr_t)&file->fobj, FILE_MODIFIED | FILE_TRUNC, file);
+ } else {
+ (void) port_dissociate(port, PORT_SOURCE_FILE,
+ (uintptr_t)&file->fobj);
+ }
+}
+
+static void
set_events(file_info_t *files)
{
int i;
@@ -271,6 +331,8 @@ set_events(file_info_t *files)
continue;
(void) fstat(fileno(file->fp), &file->st);
+
+ associate(file, B_TRUE);
}
}
@@ -284,6 +346,8 @@ 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;
+ port_event_t ev;
/* Position each of the files */
@@ -307,6 +371,12 @@ follow(file_info_t *files, enum STYLE style, off_t off)
return;
last = --file;
+
+ if (action == USE_PORT &&
+ (stat("/proc/self/fd", &sb2) == -1 || !S_ISDIR(sb2.st_mode) ||
+ (port = port_create()) == -1))
+ action = USE_SLEEP;
+
set_events(files);
for (;;) {
@@ -341,12 +411,13 @@ follow(file_info_t *files, enum STYLE style, off_t off)
sb2.st_dev != file->st.st_dev ||
sb2.st_nlink == 0) {
(void) show(file);
+ associate(file, B_FALSE);
file->fp = freopen(file->file_name, "r",
file->fp);
- if (file->fp != NULL)
+ if (file->fp != NULL) {
(void) memcpy(&file->st, &sb2,
sizeof (struct stat));
- else if (errno != ENOENT)
+ } else if (errno != ENOENT)
ierr(file->file_name);
ev_change++;
}
@@ -361,6 +432,26 @@ follow(file_info_t *files, enum STYLE style, off_t off)
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;
+ associate(file, B_TRUE);
+
+ if (ev.portev_events & FILE_TRUNC)
+ (void) fseek(file->fp, 0, SEEK_SET);
+ }
+
+ break;
+
case USE_SLEEP:
(void) usleep(250000);
break;
diff --git a/usr/src/cmd/tail/tests/tailtests.sh b/usr/src/cmd/tail/tests/tailtests.sh
index 57ec1c46cc..1ce5f5df02 100644
--- a/usr/src/cmd/tail/tests/tailtests.sh
+++ b/usr/src/cmd/tail/tests/tailtests.sh
@@ -13,129 +13,355 @@
#
# Copyright 2010 Chris Love. All rights reserved.
+# Copyright (c) 2012, Joyent, Inc. All rights reserved.
#
+checktest()
+{
+ local actual=$1
+ local output=$2
+ local test=$3
+
+ if [[ "$actual" != "$output" ]]; then
+ echo "$CMD: test $test: FAIL"
+ echo -e "$CMD: test $test: expected output:\n$o"
+ echo -e "$CMD: test $test: actual output:\n$a"
+ else
+ echo "$CMD: test $test: pass"
+ fi
+}
#
# Test cases for 'tail', some based on CoreUtils test cases (validated
-# with legacy Solaris 'tail' and/or xpg4 'tail')
+# with legacy Solaris 'tail' and/or xpg4 'tail'). Note that this is designed
+# to be able to run on BSD systems as well to check our behavior against
+# theirs (some behavior that is known to be idiosyncratic to illumos is
+# skipped on non-illumos systems).
#
PROG=/usr/bin/tail
+CMD=`basename $0`
+DIR=""
+
+while [[ $# -gt 0 ]]; do
+ case $1 in
+ -x)
+ PROG=/usr/xpg4/bin/tail
+ shift
+ ;;
+ -o)
+ PROG=$2
+ shift 2
+ ;;
+ -d)
+ DIR=$2
+ shift 2
+ ;;
+ *)
+ echo "Usage: tailtests.sh" \
+ "[-x][-o <override tail executable>]" \
+ "[-d <override output directory>]"
+ exit 1
+ ;;
+ esac
+done
-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"
+#
+# Shut bash up upon receiving a term so we can drop it on our children
+# without disrupting the output.
+#
+trap "exit 0" TERM
+
+echo "$CMD: program is $PROG"
+
+if [[ $DIR != "" ]]; then
+ echo "$CMD: directory is $DIR"
+fi
o=`echo -e "bcd"`
a=`echo -e "abcd" | $PROG +2c`
-[[ "$a" != "$o" ]] && echo "Fail test 1 - $a"
+checktest "$a" "$o" 1
o=`echo -e ""`
a=`echo "abcd" | $PROG +8c`
-[[ "$a" != "$o" ]] && echo "Fail test 2 - $a"
+checktest "$a" "$o" 2
o=`echo -e "abcd"`
a=`echo "abcd" | $PROG -9c`
-[[ "$a" != "$o" ]] && echo "Fail test 3 - $a"
+checktest "$a" "$o" 3
o=`echo -e "x"`
a=`echo -e "x" | $PROG -1l`
-[[ "$a" != "x" ]] && echo "Fail test 4 - $a"
+checktest "$a" "$o" 4
o=`echo -e "\n"`
a=`echo -e "x\ny\n" | $PROG -1l`
-[[ "$a" != "$o" ]] && echo "Fail test 5 - $a"
+checktest "$a" "$o" 5
o=`echo -e "y\n"`
a=`echo -e "x\ny\n" | $PROG -2l`
-[[ "$a" != "$o" ]] && echo "Fail test 6 - $a"
+checktest "$a" "$o" 6
o=`echo -e "y"`
a=`echo -e "x\ny" | $PROG -1l`
-[[ "$a" != "$o" ]] && echo "Fail test 7 - $a"
+checktest "$a" "$o" 7
o=`echo -e "x\ny\n"`
a=`echo -e "x\ny\n" | $PROG +1l`
-[[ "$a" != "$o" ]] && echo "Fail test 8 - $a"
+checktest "$a" "$o" 8
o=`echo -e "y\n"`
a=`echo -e "x\ny\n" | $PROG +2l`
-[[ "$a" != "$o" ]] && echo "Fail test 9 - $a"
+checktest "$a" "$o" 9
o=`echo -e "x"`
a=`echo -e "x" | $PROG -1`
-[[ "$a" != "$o" ]] && echo "Fail test 10 - $a"
+checktest "$a" "$o" 10
o=`echo -e "\n"`
a=`echo -e "x\ny\n" | $PROG -1`
-[[ "$a" != "$o" ]] && echo "Fail test 11 - $a"
+checktest "$a" "$o" 11
o=`echo -e "y\n"`
a=`echo -e "x\ny\n" | $PROG -2`
-[[ "$a" != "$o" ]] && echo "Fail test 12 - $a"
+checktest "$a" "$o" 12
o=`echo -e "y"`
a=`echo -e "x\ny" | $PROG -1`
-[[ "$a" != "$o" ]] && echo "Fail test 13 - $a"
+checktest "$a" "$o" 13
o=`echo -e "x\ny\n"`
a=`echo -e "x\ny\n" | $PROG +1`
-[[ "$a" != "$o" ]] && echo "Fail test 14 - $a"
+checktest "$a" "$o" 14
o=`echo -e "y\n"`
a=`echo -e "x\ny\n" | $PROG +2`
-[[ "$a" != "$o" ]] && echo "Fail test 15 - $a"
+checktest "$a" "$o" 15
-# 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"
-
+checktest "$a" "$o" 16
-# 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"
-
+checktest "$a" "$o" 17
-# 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"
+checktest "$a" "$o" 18
-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"
+#
+# For reasons that are presumably as accidental as they are ancient, legacy
+# (and closed) Solaris tail(1) allows +c, +l and -l to be aliases for +10c,
+# +10l and -10l, respectively. If we are on SunOS, verify that this silly
+# behavior is functional.
+#
+if [[ `uname -s` == "SunOS" ]]; then
+ o=`echo -e "yyz"`
+ a=`echo -e "xyyyyyyyyyyz" | $PROG +c`
+ checktest "$a" "$o" 16a
+
+ o=`echo -e "y\ny\nz"`
+ a=`echo -e "x\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\nz" | $PROG +l`
+ checktest "$a" "$o" 17a
+
+ 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`
+ checktest "$a" "$o" 18a
+fi
o=`echo -e "c\nb\na"`
a=`echo -e "a\nb\nc" | $PROG -r`
-[[ "$a" != "$o" ]] && echo "Fail test 19 - $a"
+checktest "$a" "$o" 19
+
+#
+# Now we want to do a series of follow tests.
+#
+if [[ $DIR == "" ]]; then
+ tdir=$(mktemp -d -t tailtest.XXXXXXXX || exit 1)
+else
+ tdir=$(mktemp -d $DIR/tailtest.XXXXXXXX || exit 1)
+fi
+
+follow=$tdir/follow
+moved=$tdir/follow.moved
+out=$tdir/out
+
+#
+# First, verify that following works in its most basic sense.
+#
+echo -e "a\nb\nc" > $follow
+$PROG -f $follow > $out 2> /dev/null &
+child=$!
+sleep 2
+echo -e "d\ne\nf" >> $follow
+sleep 1
+kill $child
+sleep 1
+
+o=`echo -e "a\nb\nc\nd\ne\nf\n"`
+a=`cat $out`
+checktest "$a" "$o" 20
+rm $follow
+
+#
+# Now verify that following correctly follows the file being moved.
+#
+echo -e "a\nb\nc" > $follow
+$PROG -f $follow > $out 2> /dev/null &
+child=$!
+sleep 2
+mv $follow $moved
+
+echo -e "d\ne\nf" >> $moved
+sleep 1
+kill $child
+sleep 1
+
+o=`echo -e "a\nb\nc\nd\ne\nf\n"`
+a=`cat $out`
+checktest "$a" "$o" 21
+rm $moved
+
+#
+# And now truncation with the new offset being less than the old offset.
+#
+echo -e "a\nb\nc" > $follow
+$PROG -f $follow > $out 2> /dev/null &
+child=$!
+sleep 2
+echo -e "d\ne\nf" >> $follow
+sleep 1
+echo -e "g\nh\ni" > $follow
+sleep 1
+kill $child
+sleep 1
+
+o=`echo -e "a\nb\nc\nd\ne\nf\ng\nh\ni\n"`
+a=`cat $out`
+checktest "$a" "$o" 22
+rm $follow
+
+#
+# And truncation with the new offset being greater than the old offset.
+#
+echo -e "a\nb\nc" > $follow
+sleep 1
+$PROG -f $follow > $out 2> /dev/null &
+child=$!
+sleep 2
+echo -e "d\ne\nf" >> $follow
+sleep 1
+echo -e "g\nh\ni\nj\nk\nl\nm\no\np\nq" > $follow
+sleep 1
+kill $child
+sleep 1
+
+o=`echo -e "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\no\np\nq"`
+a=`cat $out`
+checktest "$a" "$o" 23
+rm $follow
+#
+# Verify that we can follow the moved file and correctly see a truncation.
+#
+echo -e "a\nb\nc" > $follow
+$PROG -f $follow > $out 2> /dev/null &
+child=$!
+sleep 2
+mv $follow $moved
+
+echo -e "d\ne\nf" >> $moved
+sleep 1
+echo -e "g\nh\ni\nj\nk\nl\nm\no\np\nq" > $moved
+sleep 1
+kill $child
+sleep 1
+
+o=`echo -e "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\no\np\nq"`
+a=`cat $out`
+checktest "$a" "$o" 24
+rm $moved
+
+#
+# Verify that capital-F follow properly deals with truncation
+#
+echo -e "a\nb\nc" > $follow
+$PROG -F $follow > $out 2> /dev/null &
+child=$!
+sleep 2
+echo -e "d\ne\nf" >> $follow
+sleep 1
+echo -e "g\nh\ni" > $follow
+sleep 1
+kill $child
+sleep 1
+
+o=`echo -e "a\nb\nc\nd\ne\nf\ng\nh\ni\n"`
+a=`cat $out`
+checktest "$a" "$o" 25
+rm $follow
-echo "Completed"
+#
+# Verify that capital-F follow _won't_ follow the moved file and will
+# correctly deal with recreation of the original file.
+#
+echo -e "a\nb\nc" > $follow
+$PROG -F $follow > $out 2> /dev/null &
+child=$!
+sleep 2
+mv $follow $moved
+
+echo -e "x\ny\nz" >> $moved
+echo -e "d\ne\nf" > $follow
+sleep 1
+kill $child
+sleep 1
+
+o=`echo -e "a\nb\nc\nd\ne\nf\n"`
+a=`cat $out`
+checktest "$a" "$o" 26
+rm $moved
-exit 0
+#
+# Verify that following two files works.
+#
+echo -e "one" > $follow
+echo -e "two" > $moved
+$PROG -f $follow $moved > $out 2> /dev/null &
+child=$!
+sleep 2
+echo -e "three" >> $follow
+sleep 1
+echo -e "four" >> $moved
+sleep 1
+echo -e "five" >> $follow
+sleep 1
+kill $child
+sleep 1
+
+# There is a bug where the content comes before the header lines,
+# where rlines/mapprint happens before the header. A pain to fix.
+# In this test, just make sure we see both files change.
+o="one
+
+==> $follow <==
+two
+
+==> $moved <==
+
+==> $follow <==
+three
+
+==> $moved <==
+four
+
+==> $follow <==
+five"
+a=`cat $out`
+checktest "$a" "$o" 27
+rm $follow $moved
+
+echo "$CMD: completed"
+
+exit $errs
-# Template for additional test cases
-#o=`echo -e ""`
-#a=`echo -e "" | $PROG `
-#[[ "$a" != "$o" ]] && echo "Fail test - $a"