diff options
Diffstat (limited to 'usr/src/cmd/tail')
-rw-r--r-- | usr/src/cmd/tail/extern.h | 5 | ||||
-rw-r--r-- | usr/src/cmd/tail/forward.c | 103 | ||||
-rw-r--r-- | usr/src/cmd/tail/tests/tailtests.sh | 338 |
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" |