summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBryan Cantrill <bryan@joyent.com>2012-08-03 19:23:29 +0000
committerRobert Mustacchi <rm@joyent.com>2013-11-06 17:20:21 -0800
commit72102e7461c97dc268d21d9dd8f02da45f174acd (patch)
treebb9ad3093bfda709f66f4c8758f02edeec24e8c1
parentb3d32f0ceb59362ba287dcfd6de471e98bfc7fa9 (diff)
downloadillumos-joyent-72102e7461c97dc268d21d9dd8f02da45f174acd.tar.gz
3928 `tail -f ...` doesn't notice file truncation
3929 `man tail` doesn't mentioned "-F" option 3930 'tail -F ...' not resetting the offset of file rotation properly 3968 want FILE_TRUNC event for PORT_SOURCE_FILE Reviewed by: Gordon Ross <gordon.ross@nexenta.com> Approved by: Dan McDonald <danmcd@nexenta.com>
-rw-r--r--usr/src/cmd/tail/extern.h9
-rw-r--r--usr/src/cmd/tail/forward.c145
-rw-r--r--usr/src/cmd/tail/tests/tailtests.sh451
-rw-r--r--usr/src/man/man1/tail.127
-rw-r--r--usr/src/man/man3c/port_associate.3c30
-rw-r--r--usr/src/uts/common/fs/nfs/nfs3_vnops.c25
-rw-r--r--usr/src/uts/common/fs/nfs/nfs4_vnops.c27
-rw-r--r--usr/src/uts/common/fs/nfs/nfs_vnops.c26
-rw-r--r--usr/src/uts/common/fs/pcfs/pc_vnops.c8
-rw-r--r--usr/src/uts/common/fs/portfs/port_fop.c45
-rw-r--r--usr/src/uts/common/fs/tmpfs/tmp_vnops.c18
-rw-r--r--usr/src/uts/common/fs/udfs/udf_vnops.c10
-rw-r--r--usr/src/uts/common/fs/ufs/ufs_vnops.c7
-rw-r--r--usr/src/uts/common/fs/vnode.c10
-rw-r--r--usr/src/uts/common/fs/zfs/zfs_vnops.c6
-rw-r--r--usr/src/uts/common/sys/port.h7
-rw-r--r--usr/src/uts/common/sys/port_impl.h12
-rw-r--r--usr/src/uts/common/sys/vnode.h5
18 files changed, 752 insertions, 116 deletions
diff --git a/usr/src/cmd/tail/extern.h b/usr/src/cmd/tail/extern.h
index 147a887272..7f121b2d9d 100644
--- a/usr/src/cmd/tail/extern.h
+++ b/usr/src/cmd/tail/extern.h
@@ -28,6 +28,12 @@
*
*/
+#ifndef _TAIL_EXTERN_H
+#define _TAIL_EXTERN_H
+
+#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 +54,7 @@ struct file_info {
FILE *fp;
char *file_name;
struct stat st;
+ file_obj_t fobj[2];
};
typedef struct file_info file_info_t;
@@ -67,3 +74,5 @@ int mapprint(struct mapinfo *, off_t, off_t);
int maparound(struct mapinfo *, off_t);
extern int Fflag, fflag, qflag, rflag, rval, no_files;
+
+#endif /* _TAIL_EXTERN_H */
diff --git a/usr/src/cmd/tail/forward.c b/usr/src/cmd/tail/forward.c
index e4f22582f3..8b87dfff06 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) 2013, 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,107 @@ show(file_info_t *file)
}
static void
+associate(file_info_t *file, boolean_t assoc, port_event_t *ev)
+{
+ char buf[64], *name;
+ int i;
+
+ 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));
+
+ if (!Fflag) {
+ /*
+ * PORT_SOURCE_FILE only allows us to specify a file name, not
+ * a file descriptor. If we are following a specific file (as
+ * opposed to a file name) and 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. This also has the (desirable) side-effect of
+ * insulating against FILE_RENAME_FROM and FILE_RENAME_TO
+ * events that we need to ignore to assure that we don't lose
+ * FILE_TRUNC events.
+ */
+ (void) snprintf(buf,
+ sizeof (buf), "/proc/self/fd/%d", fileno(file->fp));
+ name = buf;
+ } else {
+ name = file->file_name;
+ }
+
+ /*
+ * 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_obj_t that happens to be at the same address,
+ * the first association will be implicitly removed. To assure that
+ * each association has a disjoint file_obj_t, we allocate the memory
+ * for each in the file_info, not on the stack.
+ */
+ file->fobj[0].fo_name = name;
+ file->fobj[1].fo_name = name;
+
+ if (assoc) {
+ /*
+ * To assure that we cannot possibly drop a FILE_TRUNC event,
+ * we have two different PORT_SOURCE_FILE associations with the
+ * port: one to get only FILE_MODIFIED events and another to
+ * get only FILE_TRUNC events. This assures that we always
+ * have an active association for FILE_TRUNC events when the
+ * seek offset is non-zero. Note that the association order
+ * _must_ be FILE_TRUNC followed by FILE_MODIFIED: if a single
+ * event induces both a FILE_TRUNC and a FILE_MODIFIED (as
+ * a VE_CREATE vnode event does), we must process the
+ * FILE_TRUNC before FILE_MODIFIED -- and the order in which
+ * these are processed will be the association order. So
+ * if we see a FILE_TRUNC, we dissociate/reassociate the
+ * FILE_MODIFIED association.
+ */
+ if (ev == NULL || (ev->portev_events & FILE_TRUNC) ||
+ !(ev->portev_events & (FILE_MODIFIED | FILE_TRUNC))) {
+ (void) port_associate(port, PORT_SOURCE_FILE,
+ (uintptr_t)&file->fobj[0], FILE_TRUNC, file);
+ (void) port_dissociate(port, PORT_SOURCE_FILE,
+ (uintptr_t)&file->fobj[1]);
+ ev = NULL;
+ }
+
+ if (ev == NULL || (ev->portev_events & FILE_MODIFIED) ||
+ !(ev->portev_events & (FILE_MODIFIED | FILE_TRUNC))) {
+ (void) port_associate(port, PORT_SOURCE_FILE,
+ (uintptr_t)&file->fobj[1], FILE_MODIFIED, file);
+ }
+ } else {
+ for (i = 0; i <= 1; i++) {
+ (void) port_dissociate(port, PORT_SOURCE_FILE,
+ (uintptr_t)&file->fobj[i]);
+ }
+ }
+}
+
+static void
set_events(file_info_t *files)
{
int i;
@@ -271,6 +373,8 @@ set_events(file_info_t *files)
continue;
(void) fstat(fileno(file->fp), &file->st);
+
+ associate(file, B_TRUE, NULL);
}
}
@@ -284,6 +388,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 +413,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 +453,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, NULL);
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 +474,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, &ev);
+
+ 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..d3a8bb1cf2 100644
--- a/usr/src/cmd/tail/tests/tailtests.sh
+++ b/usr/src/cmd/tail/tests/tailtests.sh
@@ -13,129 +13,468 @@
#
# Copyright 2010 Chris Love. All rights reserved.
+# Copyright (c) 2013, 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$output"
+ echo -e "$CMD: test $test: actual output:\n$actual"
+ 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"
+checktest "$a" "$o" 17
-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"
+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
+ export TMPDIR=/var/tmp
+ 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
+
+#
+# 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
+
+#
+# At this point, tail is polling on stat'ing the missing file; we need to
+# be sure to sleep long enough after recreating it to know that it will pick
+# it up.
+#
+echo -e "d\ne\nf" > $follow
+sleep 5
+kill $child
+sleep 1
+
+o=`echo -e "a\nb\nc\nd\ne\nf\n"`
+a=`cat $out`
+checktest "$a" "$o" 26
+rm $moved
+
+#
+# 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
+
+if [[ `uname -s` == "SunOS" ]]; then
+ #
+ # Use DTrace to truncate the file between the return from port_get()
+ # and the reassociation of the file object with the port, exposing
+ # any race conditions whereby FILE_TRUNC events are lost.
+ #
+ cat /dev/null > $follow
+ dtrace -c "$PROG -f $follow" -s /dev/stdin > $out <<EOF
+ #pragma D option destructive
+ #pragma D option quiet
+
+ pid\$target::port_get:return
+ /++i == 5/
+ {
+ stop();
+ system("cat /dev/null > $follow");
+ system("prun %d", pid);
+ }
+
+ tick-1sec
+ {
+ system("echo %d >> $follow", j++);
+ }
+
+ tick-1sec
+ /j == 10/
+ {
+ exit(0);
+ }
+EOF
+
+ o=`echo -e "0\n1\n2\n3\n5\n6\n7\n8\n9\n"`
+ a=`cat $out`
+ checktest "$a" "$o" 27a
+ rm $follow
+
+ cat /dev/null > $follow
+ dtrace -c "$PROG -f $follow" -s /dev/stdin > $out <<EOF
+ #pragma D option destructive
+ #pragma D option quiet
+
+ pid\$target::port_get:return
+ /++i == 5/
+ {
+ stop();
+ system("mv $follow $moved");
+ system("cat /dev/null > $moved");
+ system("prun %d", pid);
+ }
+
+ tick-1sec
+ {
+ system("echo %d >> %s", j++,
+ i < 5 ? "$follow" : "$moved");
+ }
+
+ tick-1sec
+ /j == 10/
+ {
+ exit(0);
+ }
+EOF
+
+ o=`echo -e "0\n1\n2\n3\n5\n6\n7\n8\n9\n"`
+ a=`cat $out`
+ checktest "$a" "$o" 27b
+ rm $moved
+
+ #
+ # Verify that -F will deal properly with the file being truncated
+ # not by truncation, but rather via an ftruncate() from logadm.
+ #
+ cat /dev/null > $follow
+ ( $PROG -F $follow > $out ) &
+ child=$!
+ echo -e "a\nb\nc\nd\ne\nf" >> $follow
+ logadm -c $follow
+ sleep 2
+ echo -e "g\nh\ni" >> $follow
+ sleep 2
+ kill $child
+ sleep 1
+
+ o=`echo -e "a\nb\nc\nd\ne\nf\ng\nh\ni\n"`
+ a=`cat $out`
+ checktest "$a" "$o" 27c
+fi
+
+#
+# We're now going to test that while we may miss output due to truncations
+# occurring faster than tail can read, we don't ever repeat output.
+#
+cat /dev/null > $follow
+( $PROG -f $follow > $out ) &
+tchild=$!
+( let i=0 ; while true; do echo $i > $follow ; sleep 0.1; let i=i+1 ; done ) &
+child=$!
+sleep 10
+kill $tchild
+kill $child
+
+a=`sort $out | uniq -c | sort -n | tail -1 | awk '{ print $1 }'`
+o=1
+checktest "$a" "$o" 28
-echo "Completed"
+echo "$CMD: completed"
-exit 0
+exit $errs
-# Template for additional test cases
-#o=`echo -e ""`
-#a=`echo -e "" | $PROG `
-#[[ "$a" != "$o" ]] && echo "Fail test - $a"
diff --git a/usr/src/man/man1/tail.1 b/usr/src/man/man1/tail.1
index aca819269e..b96e6400dd 100644
--- a/usr/src/man/man1/tail.1
+++ b/usr/src/man/man1/tail.1
@@ -23,12 +23,12 @@ tail \- deliver the last part of a file
.LP
.nf
-\fB/usr/bin/tail\fR [\(+- \fInumber\fR [lbcf]] [\fIfile\fR]
+\fB/usr/bin/tail\fR [\(+- \fInumber\fR [lbcfF]] [\fIfile\fR]
.fi
.LP
.nf
-\fB/usr/bin/tail\fR [\fB-lbcf\fR] [\fIfile\fR]
+\fB/usr/bin/tail\fR [\fB-lbcfF\fR] [\fIfile\fR]
.fi
.LP
@@ -92,11 +92,24 @@ Units of bytes.
\fB\fB-f\fR \fR
.ad
.RS 7n
-Follow. If the input-file is not a pipe, the program does not terminate after
-the line of the input-file has been copied, but enters an endless loop, wherein
-it sleeps for a second and then attempts to read and copy further records from
-the input-file. Thus it can be used to monitor the growth of a file that is
-being written by some other process.
+Follow. If the input-file is not a pipe, \fBtail\fR does not terminate after
+the last line of the input-file has been copied, but enters an endless loop,
+wherein it watches the file for modifications and attempts to read and copy
+further records from the input-file. Thus it can be used to monitor the growth
+of a file that is being written by some other process. If the watched file is
+truncated \fBtail\fR will begin reading records from the start of the file.
+.RE
+
+.sp
+.ne 2
+.na
+\fB\fB-F\fR \fR
+.ad
+.RS 7n
+Follow named file. Operates as with \fB-f\fR, except that if the file is moved
+(e.g. if a watched log file is rotated) \fBtail\fR will close the original file
+and begin reading records from the start of the file with the specified name
+if and when that file is recreated.
.RE
.sp
diff --git a/usr/src/man/man3c/port_associate.3c b/usr/src/man/man3c/port_associate.3c
index d876b5c9d7..f6e66b0d65 100644
--- a/usr/src/man/man3c/port_associate.3c
+++ b/usr/src/man/man3c/port_associate.3c
@@ -73,21 +73,21 @@ the port.
.LP
Objects of type \fBPORT_SOURCE_FILE\fR are pointer to the structure
\fBfile_obj\fR defined in \fB<sys/port.h>\fR. This event source provides event
-notification when the specified file/directory is accessed or modified or when
-its status changes. The path name of the file/directory to be watched is passed
-in the \fBstruct file_obj\fR along with the \fBaccess\fR, \fBmodification\fR,
-and \fBchange\fR time stamps acquired from a \fBstat\fR(2) call. If the file
-name is a symbolic links, it is followed by default. The \fBFILE_NOFOLLOW\fR
-needs to be passed in along with the specified events if the symbolic link
-itself needs to be watched and \fBlstat()\fR needs to be used to get the file
-status of the symbolic link file.
+notification when the specified file/directory is accessed, modified,
+truncated or when its status changes. The path name of the file/directory to
+be watched is passed in the \fBstruct file_obj\fR along with the \fBaccess\fR,
+\fBmodification\fR, and \fBchange\fR time stamps acquired from a \fBstat\fR(2)
+call. If the file name is a symbolic link, it is followed by default. The
+\fBFILE_NOFOLLOW\fR needs to be passed in along with the specified events if
+the symbolic link itself needs to be watched and \fBlstat()\fR needs to be
+used to get the file status of the symbolic link file.
.sp
.LP
The \fBstruct file_obj\fR contains the following elements:
.sp
.in +2
.nf
-timestruc_t fo_atime; /* Access time got from stat() */
+timestruc_t fo_atime; /* Access time from stat() */
timestruc_t fo_mtime; /* Modification time from stat() */
timestruc_t fo_ctime; /* Change time from stat() */
char *fo_name; /* Pointer to a null terminated path name */
@@ -104,11 +104,13 @@ occurs.
.sp
.LP
The event types that can be specified at \fBport_associate()\fR time for
-\fBPORT_SOURCE_FILE\fR are \fBFILE_ACCESS\fR, \fBFILE_MODIFIED\fR, and
-\fBFILE_ATTRIB\fR, corresponding to the three time stamps. An \fBfo_atime\fR
-change results in the \fBFILE_ACCESS\fR event, an \fBfo_mtime\fR change results
-in the \fBFILE_MODIFIED\fR event, and an \fBfo_time\fR change results in the
-\fBFILE_ATTRIB\fR event.
+\fBPORT_SOURCE_FILE\fR are \fBFILE_ACCESS\fR, \fBFILE_MODIFIED\fR,
+\fBFILE_ATTRIB\fR, and \fbFILE_TRUNC\fR. The first three of these correspond
+to the three time stamps: an \fBfo_atime\fR change results in the
+\fBFILE_ACCESS\fR event, an \fBfo_mtime\fR change results in the
+\fBFILE_MODIFIED\fR event, and an \fBfo_ctime\fR change results in the
+\fBFILE_ATTRIB\fR event. If the operation that induced the time stamp update
+also truncated the file, \fBFILE_TRUNC\fR will be set in the resulting event.
.sp
.LP
The following exception events are delivered when they occur. These event types
diff --git a/usr/src/uts/common/fs/nfs/nfs3_vnops.c b/usr/src/uts/common/fs/nfs/nfs3_vnops.c
index d31b53d2e9..fd1595b9f5 100644
--- a/usr/src/uts/common/fs/nfs/nfs3_vnops.c
+++ b/usr/src/uts/common/fs/nfs/nfs3_vnops.c
@@ -28,6 +28,10 @@
* All rights reserved.
*/
+/*
+ * Copyright (c) 2013, Joyent, Inc. All rights reserved.
+ */
+
#include <sys/param.h>
#include <sys/types.h>
#include <sys/systm.h>
@@ -1327,7 +1331,12 @@ nfs3_setattr(vnode_t *vp, struct vattr *vap, int flags, cred_t *cr,
if (error)
return (error);
- return (nfs3setattr(vp, vap, flags, cr));
+ error = nfs3setattr(vp, vap, flags, cr);
+
+ if (error == 0 && (vap->va_mask & AT_SIZE) && vap->va_size == 0)
+ vnevent_truncate(vp, ct);
+
+ return (error);
}
static int
@@ -2298,6 +2307,12 @@ top:
vattr.va_mask = AT_SIZE;
error = nfs3setattr(vp,
&vattr, 0, cr);
+
+ /*
+ * Existing file was truncated;
+ * emit a create event.
+ */
+ vnevent_create(vp, ct);
}
}
}
@@ -2306,12 +2321,9 @@ top:
if (error) {
VN_RELE(vp);
} else {
- /*
- * existing file got truncated, notify.
- */
- vnevent_create(vp, ct);
*vpp = vp;
}
+
return (error);
}
@@ -5514,6 +5526,9 @@ nfs3_space(vnode_t *vp, int cmd, struct flock64 *bfp, int flag,
va.va_mask = AT_SIZE;
va.va_size = bfp->l_start;
error = nfs3setattr(vp, &va, 0, cr);
+
+ if (error == 0 && bfp->l_start == 0)
+ vnevent_truncate(vp, ct);
} else
error = EINVAL;
}
diff --git a/usr/src/uts/common/fs/nfs/nfs4_vnops.c b/usr/src/uts/common/fs/nfs/nfs4_vnops.c
index 5ae2c28d53..0c97fcc176 100644
--- a/usr/src/uts/common/fs/nfs/nfs4_vnops.c
+++ b/usr/src/uts/common/fs/nfs/nfs4_vnops.c
@@ -31,6 +31,10 @@
* All Rights Reserved
*/
+/*
+ * Copyright (c) 2013, Joyent, Inc. All rights reserved.
+ */
+
#include <sys/param.h>
#include <sys/types.h>
#include <sys/systm.h>
@@ -3712,6 +3716,8 @@ static int
nfs4_setattr(vnode_t *vp, struct vattr *vap, int flags, cred_t *cr,
caller_context_t *ct)
{
+ int error;
+
if (vap->va_mask & AT_NOSET)
return (EINVAL);
@@ -3727,8 +3733,12 @@ nfs4_setattr(vnode_t *vp, struct vattr *vap, int flags, cred_t *cr,
* to setattr (e.g. basic without chmod) then we will
* need to add a check here before calling the server.
*/
+ error = nfs4setattr(vp, vap, flags, cr, NULL);
- return (nfs4setattr(vp, vap, flags, cr, NULL));
+ if (error == 0 && (vap->va_mask & AT_SIZE) && vap->va_size == 0)
+ vnevent_truncate(vp, ct);
+
+ return (error);
}
/*
@@ -6653,16 +6663,20 @@ top:
} else {
vnode_t *tvp;
rnode4_t *trp;
- /*
- * existing file got truncated, notify.
- */
tvp = vp;
if (vp->v_type == VREG) {
trp = VTOR4(vp);
if (IS_SHADOW(vp, trp))
tvp = RTOV4(trp);
}
- vnevent_create(tvp, ct);
+
+ if (must_trunc) {
+ /*
+ * existing file got truncated, notify.
+ */
+ vnevent_create(tvp, ct);
+ }
+
*vpp = vp;
}
return (error);
@@ -10991,6 +11005,9 @@ nfs4_space(vnode_t *vp, int cmd, struct flock64 *bfp, int flag,
va.va_mask = AT_SIZE;
va.va_size = bfp->l_start;
error = nfs4setattr(vp, &va, 0, cr, NULL);
+
+ if (error == 0 && bfp->l_start == 0)
+ vnevent_truncate(vp, ct);
} else
error = EINVAL;
}
diff --git a/usr/src/uts/common/fs/nfs/nfs_vnops.c b/usr/src/uts/common/fs/nfs/nfs_vnops.c
index a3f43a4e95..df128f4bc6 100644
--- a/usr/src/uts/common/fs/nfs/nfs_vnops.c
+++ b/usr/src/uts/common/fs/nfs/nfs_vnops.c
@@ -25,6 +25,10 @@
* All rights reserved.
*/
+/*
+ * Copyright (c) 2013, Joyent, Inc. All rights reserved.
+ */
+
#include <sys/param.h>
#include <sys/types.h>
#include <sys/systm.h>
@@ -1167,7 +1171,12 @@ nfs_setattr(vnode_t *vp, struct vattr *vap, int flags, cred_t *cr,
if (error)
return (error);
- return (nfssetattr(vp, vap, flags, cr));
+ error = nfssetattr(vp, vap, flags, cr);
+
+ if (error == 0 && (mask & AT_SIZE) && vap->va_size == 0)
+ vnevent_truncate(vp, ct);
+
+ return (error);
}
static int
@@ -2030,6 +2039,14 @@ nfs_create(vnode_t *dvp, char *nm, struct vattr *va, enum vcexcl exclusive,
vp->v_type == VREG) {
vattr.va_mask = AT_SIZE;
error = nfssetattr(vp, &vattr, 0, cr);
+
+ if (!error) {
+ /*
+ * Existing file was truncated;
+ * emit a create event.
+ */
+ vnevent_create(vp, ct);
+ }
}
}
}
@@ -2037,10 +2054,6 @@ nfs_create(vnode_t *dvp, char *nm, struct vattr *va, enum vcexcl exclusive,
if (error) {
VN_RELE(vp);
} else {
- /*
- * existing file got truncated, notify.
- */
- vnevent_create(vp, ct);
*vpp = vp;
}
return (error);
@@ -4610,6 +4623,9 @@ nfs_space(vnode_t *vp, int cmd, struct flock64 *bfp, int flag,
va.va_mask = AT_SIZE;
va.va_size = bfp->l_start;
error = nfssetattr(vp, &va, 0, cr);
+
+ if (error == 0 && bfp->l_start == 0)
+ vnevent_truncate(vp, ct);
} else
error = EINVAL;
}
diff --git a/usr/src/uts/common/fs/pcfs/pc_vnops.c b/usr/src/uts/common/fs/pcfs/pc_vnops.c
index ce9711140b..e2c6f3a371 100644
--- a/usr/src/uts/common/fs/pcfs/pc_vnops.c
+++ b/usr/src/uts/common/fs/pcfs/pc_vnops.c
@@ -24,6 +24,10 @@
* Use is subject to license terms.
*/
+/*
+ * Copyright (c) 2013, Joyent, Inc. All rights reserved.
+ */
+
#include <sys/param.h>
#include <sys/t_lock.h>
#include <sys/systm.h>
@@ -772,8 +776,12 @@ pcfs_setattr(
goto out;
}
error = pc_truncate(pcp, (uint_t)vap->va_size);
+
if (error)
goto out;
+
+ if (vap->va_size == 0)
+ vnevent_truncate(vp, ct);
}
/*
* Change file modified times.
diff --git a/usr/src/uts/common/fs/portfs/port_fop.c b/usr/src/uts/common/fs/portfs/port_fop.c
index 2852a98f52..03a03dd2a6 100644
--- a/usr/src/uts/common/fs/portfs/port_fop.c
+++ b/usr/src/uts/common/fs/portfs/port_fop.c
@@ -23,6 +23,9 @@
* Use is subject to license terms.
*/
+/*
+ * Copyright (c) 2013, Joyent, Inc. All rights reserved.
+ */
/*
* File Events Notification
@@ -1256,7 +1259,7 @@ port_associate_fop(port_t *pp, int source, uintptr_t object, int events,
void *user)
{
portfop_cache_t *pfcp;
- vnode_t *vp, *dvp, *oldvp = NULL, *olddvp = NULL;
+ vnode_t *vp, *dvp, *oldvp = NULL, *olddvp = NULL, *orig;
portfop_t *pfp;
int error = 0;
file_obj_t fobj;
@@ -1313,8 +1316,7 @@ port_associate_fop(port_t *pp, int source, uintptr_t object, int events,
goto errout;
}
- vp = port_resolve_vp(vp);
-
+ vp = port_resolve_vp(orig = vp);
if (vp != NULL && vnevent_support(vp, NULL)) {
error = ENOTSUP;
@@ -1322,10 +1324,14 @@ port_associate_fop(port_t *pp, int source, uintptr_t object, int events,
}
/*
- * If dvp belongs to a different filesystem just ignore it.
- * Hardlinks cannot exist across filesystems.
+ * If dvp belongs to a different filesystem just ignore it, as hard
+ * links cannot exist across filesystems. We make an exception for
+ * procfs, however, the magic of which we treat semantically as a hard
+ * link, allowing one to use /proc/[pid]/fd/[fd] for PORT_SOURCE_FILE
+ * and avoid spurious FILE_RENAME_FROM/FILE_RENAME_TO events.
*/
- if (dvp != NULL && dvp->v_vfsp != vp->v_vfsp) {
+ if (dvp != NULL && dvp->v_vfsp != vp->v_vfsp &&
+ !(orig->v_type == VPROC && vp != NULL && vp->v_type != VPROC)) {
VN_RELE(dvp);
dvp = NULL;
}
@@ -1822,12 +1828,19 @@ port_fop_sendevent(vnode_t *vp, int events, vnode_t *dvp, char *cname)
if (!removeall) {
/*
* All the active ones are in the beginning of the list.
+ * Note that we process this list in reverse order to assure
+ * that events are delivered in the order that they were
+ * associated.
*/
- for (pfp = (portfop_t *)list_head(&pvp->pvp_pfoplist);
- pfp && pfp->pfop_flags & PORT_FOP_ACTIVE; pfp = npfp) {
+ for (pfp = (portfop_t *)list_tail(&pvp->pvp_pfoplist);
+ pfp && !(pfp->pfop_flags & PORT_FOP_ACTIVE); pfp = npfp) {
+ npfp = list_prev(&pvp->pvp_pfoplist, pfp);
+ }
+
+ for (; pfp != NULL; pfp = npfp) {
int levents = events;
- npfp = list_next(&pvp->pvp_pfoplist, pfp);
+ npfp = list_prev(&pvp->pvp_pfoplist, pfp);
/*
* Hard links case - If the file is being
* removed/renamed, and the name matches
@@ -1965,7 +1978,9 @@ port_fop(vnode_t *vp, int op, int retval)
if (op & FOP_ATTRIB_MASK) {
event |= FILE_ATTRIB;
}
-
+ if (op & FOP_TRUNC_MASK) {
+ event |= FILE_TRUNC;
+ }
if (event) {
port_fop_sendevent(vp, event, NULL, NULL);
}
@@ -2147,6 +2162,9 @@ port_fop_setattr(femarg_t *vf, vattr_t *vap, int flags, cred_t *cr,
int events = 0;
retval = vnext_setattr(vf, vap, flags, cr, ct);
+ if (vap->va_mask & AT_SIZE) {
+ events |= FOP_FILE_TRUNC;
+ }
if (vap->va_mask & (AT_SIZE|AT_MTIME)) {
events |= FOP_FILE_SETATTR_MTIME;
}
@@ -2322,8 +2340,8 @@ port_fop_vnevent(femarg_t *vf, vnevent_t vnevent, vnode_t *dvp, char *name,
port_fop_sendevent(vp, FILE_DELETE, dvp, name);
break;
case VE_CREATE:
- port_fop_sendevent(vp, FILE_MODIFIED|FILE_ATTRIB,
- NULL, NULL);
+ port_fop_sendevent(vp,
+ FILE_MODIFIED|FILE_ATTRIB|FILE_TRUNC, NULL, NULL);
break;
case VE_LINK:
port_fop_sendevent(vp, FILE_ATTRIB, NULL, NULL);
@@ -2337,6 +2355,9 @@ port_fop_vnevent(femarg_t *vf, vnevent_t vnevent, vnode_t *dvp, char *name,
case VE_MOUNTEDOVER:
port_fop_sendevent(vp, MOUNTEDOVER, NULL, NULL);
break;
+ case VE_TRUNCATE:
+ port_fop_sendevent(vp, FILE_TRUNC, NULL, NULL);
+ break;
default:
break;
}
diff --git a/usr/src/uts/common/fs/tmpfs/tmp_vnops.c b/usr/src/uts/common/fs/tmpfs/tmp_vnops.c
index 61d72a4015..e44bba1252 100644
--- a/usr/src/uts/common/fs/tmpfs/tmp_vnops.c
+++ b/usr/src/uts/common/fs/tmpfs/tmp_vnops.c
@@ -24,6 +24,10 @@
* Use is subject to license terms.
*/
+/*
+ * Copyright (c) 2012, Joyent, Inc. All rights reserved.
+ */
+
#include <sys/types.h>
#include <sys/param.h>
#include <sys/t_lock.h>
@@ -780,6 +784,10 @@ tmp_setattr(
error = tmpnode_trunc(tm, tp, (ulong_t)vap->va_size);
rw_exit(&tp->tn_contents);
rw_exit(&tp->tn_rwlock);
+
+ if (error == 0 && vap->va_size == 0)
+ vnevent_truncate(vp, ct);
+
goto out1;
}
out:
@@ -978,6 +986,8 @@ again:
}
if (error == 0) { /* name found */
+ boolean_t trunc = B_FALSE;
+
ASSERT(oldtp);
rw_enter(&oldtp->tn_rwlock, RW_WRITER);
@@ -1005,6 +1015,7 @@ again:
rw_enter(&oldtp->tn_contents, RW_WRITER);
(void) tmpnode_trunc(tm, oldtp, 0);
rw_exit(&oldtp->tn_contents);
+ trunc = B_TRUE;
}
rw_exit(&oldtp->tn_rwlock);
if (IS_DEVVP(*vpp)) {
@@ -1019,9 +1030,9 @@ again:
*vpp = newvp;
}
- if (error == 0) {
+ if (trunc)
vnevent_create(*vpp, ct);
- }
+
return (0);
}
@@ -2318,6 +2329,9 @@ tmp_space(
if ((bfp->l_start > MAXOFF_T) || (bfp->l_len > MAXOFF_T))
return (EFBIG);
error = tmp_freesp(vp, bfp, flag);
+
+ if (error == 0 && bfp->l_start == 0)
+ vnevent_truncate(vp, ct);
}
return (error);
}
diff --git a/usr/src/uts/common/fs/udfs/udf_vnops.c b/usr/src/uts/common/fs/udfs/udf_vnops.c
index 90ad1b8d48..307d3987ed 100644
--- a/usr/src/uts/common/fs/udfs/udf_vnops.c
+++ b/usr/src/uts/common/fs/udfs/udf_vnops.c
@@ -23,6 +23,10 @@
* Copyright (c) 1998, 2010, Oracle and/or its affiliates. All rights reserved.
*/
+/*
+ * Copyright (c) 2013, Joyent, Inc. All rights reserved.
+ */
+
#include <sys/types.h>
#include <sys/t_lock.h>
#include <sys/param.h>
@@ -564,6 +568,9 @@ udf_setattr(
if (error = ud_itrunc(ip, vap->va_size, 0, cr)) {
goto update_inode;
}
+
+ if (vap->va_size == 0)
+ vnevent_truncate(vp, ct);
}
/*
* Change file access or modified times.
@@ -1621,6 +1628,9 @@ udf_space(
error = EINVAL;
} else if ((error = convoff(vp, bfp, 0, offset)) == 0) {
error = ud_freesp(vp, bfp, flag, cr);
+
+ if (error == 0 && bfp->l_start == 0)
+ vnevent_truncate(vp, ct);
}
return (error);
diff --git a/usr/src/uts/common/fs/ufs/ufs_vnops.c b/usr/src/uts/common/fs/ufs/ufs_vnops.c
index e210339dfb..92bdb0d0a7 100644
--- a/usr/src/uts/common/fs/ufs/ufs_vnops.c
+++ b/usr/src/uts/common/fs/ufs/ufs_vnops.c
@@ -21,6 +21,7 @@
/*
* Copyright (c) 1984, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, Joyent, Inc. All rights reserved.
*/
/* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */
@@ -2191,6 +2192,9 @@ again:
rw_enter(&ip->i_contents, RW_WRITER);
goto update_inode;
}
+
+ if (error == 0 && vap->va_size)
+ vnevent_truncate(vp, ct);
}
if (ulp) {
@@ -4460,6 +4464,9 @@ ufs_space(struct vnode *vp, int cmd, struct flock64 *bfp, int flag,
if (error)
return (error);
error = ufs_freesp(vp, bfp, flag, cr);
+
+ if (error == 0 && bfp->l_start == 0)
+ vnevent_truncate(vp, ct);
} else if (cmd == F_ALLOCSP) {
error = ufs_lockfs_begin(ufsvfsp, &ulp,
ULOCKFS_FALLOCATE_MASK);
diff --git a/usr/src/uts/common/fs/vnode.c b/usr/src/uts/common/fs/vnode.c
index 382369c7fc..d67d164286 100644
--- a/usr/src/uts/common/fs/vnode.c
+++ b/usr/src/uts/common/fs/vnode.c
@@ -21,6 +21,7 @@
/*
* Copyright (c) 1988, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, Joyent, Inc. All rights reserved.
*/
/* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */
@@ -2582,6 +2583,15 @@ vnevent_mountedover(vnode_t *vp, caller_context_t *ct)
(void) VOP_VNEVENT(vp, VE_MOUNTEDOVER, NULL, NULL, ct);
}
+void
+vnevent_truncate(vnode_t *vp, caller_context_t *ct)
+{
+ if (vp == NULL || vp->v_femhead == NULL) {
+ return;
+ }
+ (void) VOP_VNEVENT(vp, VE_TRUNCATE, NULL, NULL, ct);
+}
+
/*
* Vnode accessors.
*/
diff --git a/usr/src/uts/common/fs/zfs/zfs_vnops.c b/usr/src/uts/common/fs/zfs/zfs_vnops.c
index 6fa1d83700..78e2a8cce5 100644
--- a/usr/src/uts/common/fs/zfs/zfs_vnops.c
+++ b/usr/src/uts/common/fs/zfs/zfs_vnops.c
@@ -2756,6 +2756,9 @@ top:
ZFS_EXIT(zfsvfs);
return (err);
}
+
+ if (vap->va_size == 0)
+ vnevent_truncate(ZTOV(zp), ct);
}
if (mask & (AT_ATIME|AT_MTIME) ||
@@ -4770,6 +4773,9 @@ zfs_space(vnode_t *vp, int cmd, flock64_t *bfp, int flag,
error = zfs_freesp(zp, off, len, flag, TRUE);
+ if (error == 0 && off == 0 && len == 0)
+ vnevent_truncate(ZTOV(zp), ct);
+
ZFS_EXIT(zfsvfs);
return (error);
}
diff --git a/usr/src/uts/common/sys/port.h b/usr/src/uts/common/sys/port.h
index ccb0308255..d4d74d55ea 100644
--- a/usr/src/uts/common/sys/port.h
+++ b/usr/src/uts/common/sys/port.h
@@ -24,11 +24,13 @@
* Use is subject to license terms.
*/
+/*
+ * Copyright (c) 2012, Joyent, Inc. All rights reserved.
+ */
+
#ifndef _SYS_PORT_H
#define _SYS_PORT_H
-#pragma ident "%Z%%M% %I% %E% SMI"
-
#ifdef __cplusplus
extern "C" {
#endif
@@ -106,6 +108,7 @@ typedef struct port_notify32 {
#define FILE_ACCESS 0x00000001
#define FILE_MODIFIED 0x00000002
#define FILE_ATTRIB 0x00000004
+#define FILE_TRUNC 0x00100000
#define FILE_NOFOLLOW 0x10000000
/*
diff --git a/usr/src/uts/common/sys/port_impl.h b/usr/src/uts/common/sys/port_impl.h
index 9f3f291874..504fb9ece1 100644
--- a/usr/src/uts/common/sys/port_impl.h
+++ b/usr/src/uts/common/sys/port_impl.h
@@ -24,6 +24,10 @@
* Use is subject to license terms.
*/
+/*
+ * Copyright (c) 2012, Joyent, Inc. All rights reserved.
+ */
+
#ifndef _SYS_PORT_IMPL_H
#define _SYS_PORT_IMPL_H
@@ -311,6 +315,7 @@ typedef struct portfop_vp {
#define FOP_FILE_SETATTR_MTIME 0x00080000
#define FOP_FILE_SETATTR_CTIME 0x00100000
#define FOP_FILE_LINK_SRC 0x00200000
+#define FOP_FILE_TRUNC 0x00400000
/*
* File modification event.
@@ -339,10 +344,15 @@ typedef struct portfop_vp {
/*
+ * File trunc event
+ */
+#define FOP_TRUNC_MASK (FOP_FILE_TRUNC|FOP_FILE_CREATE)
+
+/*
* valid watchable events
*/
#define FILE_EVENTS_MASK (FILE_ACCESS|FILE_MODIFIED|FILE_ATTRIB \
- |FILE_NOFOLLOW)
+ |FILE_NOFOLLOW|FILE_TRUNC)
/* --- End file events --- */
/*
diff --git a/usr/src/uts/common/sys/vnode.h b/usr/src/uts/common/sys/vnode.h
index d29152346e..af9516fe52 100644
--- a/usr/src/uts/common/sys/vnode.h
+++ b/usr/src/uts/common/sys/vnode.h
@@ -21,6 +21,7 @@
/*
* Copyright (c) 1988, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, Joyent, Inc. All rights reserved.
*/
/* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */
@@ -733,7 +734,8 @@ typedef enum vnevent {
VE_CREATE = 5, /* Create with vnode's name which exists */
VE_LINK = 6, /* Link with vnode's name as source */
VE_RENAME_DEST_DIR = 7, /* Rename with vnode as target dir */
- VE_MOUNTEDOVER = 8 /* File or Filesystem got mounted over vnode */
+ VE_MOUNTEDOVER = 8, /* File or Filesystem got mounted over vnode */
+ VE_TRUNCATE = 9 /* Truncate */
} vnevent_t;
/*
@@ -1290,6 +1292,7 @@ void vnevent_create(vnode_t *, caller_context_t *);
void vnevent_link(vnode_t *, caller_context_t *);
void vnevent_rename_dest_dir(vnode_t *, caller_context_t *ct);
void vnevent_mountedover(vnode_t *, caller_context_t *);
+void vnevent_truncate(vnode_t *, caller_context_t *);
int vnevent_support(vnode_t *, caller_context_t *);
/* Vnode specific data */