hoewweken
2023-07-12 4082f985456507cb05e4ea79b8fe44b34259d3d6
rebuild libebml with gcc-10 (#12787

1 files added
1 files modified
3047 ■■■■■ changed files
components/library/libebml/Makefile 1 ●●●● patch | view | raw | blame | history
components/openindiana/illumos-gate/patches/0004-OS-3294-add-support-for-inotify.patch 3046 ●●●●● patch | view | raw | blame | history
components/library/libebml/Makefile
@@ -20,6 +20,7 @@
COMPONENT_NAME=         libebml
COMPONENT_VERSION=      1.4.4
COMPONENT_REVISION=     1
COMPONENT_SUMMARY=      Extensible Binary Markup Language
COMPONENT_PROJECT_URL=  https://matroska-org.github.io/libebml/
COMPONENT_SRC=          $(COMPONENT_NAME)-$(COMPONENT_VERSION)
components/openindiana/illumos-gate/patches/0004-OS-3294-add-support-for-inotify.patch
New file
@@ -0,0 +1,3046 @@
From a9a246c0c49e192616e7499eaa2362b21fde8f5e Mon Sep 17 00:00:00 2001
From: Bryan Cantrill <bryan@joyent.com>
Date: Thu, 18 Sep 2014 06:22:00 +0000
Subject: [PATCH] OS-3294 add support for inotify Reviewed by: Jerry Jelinek
 <jerry.jelinek@joyent.com> Reviewed by: Robert Mustacchi <rm@joyent.com>
---
 usr/src/cmd/devfsadm/misc_link.c        |    5 +-
 usr/src/lib/libc/amd64/Makefile         |    1 +
 usr/src/lib/libc/i386/Makefile.com      |    1 +
 usr/src/lib/libc/port/mapfile-vers      |    4 +
 usr/src/lib/libc/port/sys/inotify.c     |  142 +++
 usr/src/lib/libc/sparc/Makefile.com     |    1 +
 usr/src/man/man3c/Makefile              |    3 +
 usr/src/man/man3c/inotify_add_watch.3c  |  120 ++
 usr/src/man/man3c/inotify_init.3c       |  107 ++
 usr/src/man/man3c/inotify_rm_watch.3c   |   81 ++
 usr/src/man/man5/Makefile               |    1 +
 usr/src/man/man5/inotify.5              |  305 +++++
 usr/src/uts/common/Makefile.files       |    2 +
 usr/src/uts/common/fs/nfs/nfs3_vnops.c  |    5 +-
 usr/src/uts/common/fs/nfs/nfs4_vnops.c  |   30 +-
 usr/src/uts/common/fs/nfs/nfs_vnops.c   |    6 +-
 usr/src/uts/common/fs/pcfs/pc_dir.c     |   12 +-
 usr/src/uts/common/fs/tmpfs/tmp_vnops.c |    6 +-
 usr/src/uts/common/fs/udfs/udf_dir.c    |    6 +-
 usr/src/uts/common/fs/ufs/ufs_vnops.c   |    7 +-
 usr/src/uts/common/fs/vnode.c           |    8 +-
 usr/src/uts/common/fs/zfs/zfs_vnops.c   |    6 +-
 usr/src/uts/common/io/inotify.c         | 1480 +++++++++++++++++++++++
 usr/src/uts/common/io/inotify.conf      |   16 +
 usr/src/uts/common/sys/Makefile         |    1 +
 usr/src/uts/common/sys/inotify.h        |  153 +++
 usr/src/uts/common/sys/vnode.h          |    8 +-
 usr/src/uts/intel/Makefile.intel        |    1 +
 usr/src/uts/intel/inotify/Makefile      |   70 ++
 usr/src/uts/sparc/Makefile.sparc        |    2 +
 usr/src/uts/sparc/inotify/Makefile      |   70 ++
 32 files changed, 2617 insertions(+), 51 deletions(-)
 create mode 100644 usr/src/lib/libc/port/sys/inotify.c
 create mode 100644 usr/src/man/man3c/inotify_add_watch.3c
 create mode 100644 usr/src/man/man3c/inotify_init.3c
 create mode 100644 usr/src/man/man3c/inotify_rm_watch.3c
 create mode 100644 usr/src/man/man5/inotify.5
 create mode 100644 usr/src/uts/common/io/inotify.c
 create mode 100644 usr/src/uts/common/io/inotify.conf
 create mode 100644 usr/src/uts/common/sys/inotify.h
 create mode 100644 usr/src/uts/intel/inotify/Makefile
 create mode 100644 usr/src/uts/sparc/inotify/Makefile
diff --git a/usr/src/cmd/devfsadm/misc_link.c b/usr/src/cmd/devfsadm/misc_link.c
index b7aef8b00d..70599d6039 100644
--- a/usr/src/cmd/devfsadm/misc_link.c
+++ a/usr/src/cmd/devfsadm/misc_link.c
@@ -108,6 +108,9 @@ static devfsadm_create_t misc_cbt[] = {
         "(^nca$)|(^rds$)|(^sdp$)|(^ipnet$)|(^dlpistub$)|(^bpf$)",
         TYPE_EXACT | DRV_RE, ILEVEL_1, minor_name
     },
+    { "pseudo", "ddi_pseudo", "inotify",
+        TYPE_EXACT | DRV_EXACT, ILEVEL_0, minor_name
+    },
     { "pseudo", "ddi_pseudo", "ipd",
         TYPE_EXACT | DRV_EXACT, ILEVEL_0, minor_name
     },
diff --git a/usr/src/lib/libc/amd64/Makefile b/usr/src/lib/libc/amd64/Makefile
index 4afe40c01b..a7cfc36ddb 100644
--- a/usr/src/lib/libc/amd64/Makefile
+++ b/usr/src/lib/libc/amd64/Makefile
@@ -860,6 +860,7 @@ PORTSYS=            \
     fcntl.o            \
     getpagesizes.o        \
     getpeerucred.o        \
+    inotify.o        \
     inst_sync.o        \
     issetugid.o        \
     label.o            \
diff --git a/usr/src/lib/libc/i386/Makefile.com b/usr/src/lib/libc/i386/Makefile.com
index f229dec61d..e8b72336f3 100644
--- a/usr/src/lib/libc/i386/Makefile.com
+++ b/usr/src/lib/libc/i386/Makefile.com
@@ -898,6 +898,7 @@ PORTSYS=            \
     fcntl.o            \
     getpagesizes.o        \
     getpeerucred.o        \
+    inotify.o        \
     inst_sync.o        \
     issetugid.o        \
     label.o            \
diff --git a/usr/src/lib/libc/port/mapfile-vers b/usr/src/lib/libc/port/mapfile-vers
index ecbb16fe85..70d37d04be 100644
--- a/usr/src/lib/libc/port/mapfile-vers
+++ b/usr/src/lib/libc/port/mapfile-vers
@@ -2858,6 +2858,10 @@ $endif
     __idmap_unreg;
     __init_daemon_priv;
     __init_suid_priv;
+    inotify_init;
+    inotify_init1;
+    inotify_add_watch;
+    inotify_rm_watch;
     _insert;
     inst_sync;
     _iswctype;
diff --git a/usr/src/lib/libc/port/sys/inotify.c b/usr/src/lib/libc/port/sys/inotify.c
new file mode 100644
index 0000000000..6bfe988735
--- /dev/null
+++ b/usr/src/lib/libc/port/sys/inotify.c
@@ -0,0 +1,142 @@
+/*
+ * 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 of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright (c) 2014, Joyent, Inc.  All rights reserved.
+ */
+
+#include <sys/inotify.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <strings.h>
+#include <dirent.h>
+
+int
+inotify_init()
+{
+    return (open("/dev/inotify", O_RDWR));
+}
+
+int
+inotify_init1(int flags)
+{
+    int oflags = O_RDWR;
+
+    if (flags & IN_NONBLOCK)
+        oflags |= O_NONBLOCK;
+
+    if (flags & IN_CLOEXEC)
+        oflags |= O_CLOEXEC;
+
+    return (open("/dev/inotify", oflags));
+}
+
+int
+inotify_add_watch(int fd, const char *pathname, uint32_t mask)
+{
+    inotify_addwatch_t ioc;
+    inotify_addchild_t cioc;
+    struct stat buf;
+    int dirfd, wd;
+    DIR *dir;
+    struct dirent *dp;
+    int oflags = O_RDONLY;
+
+    if (mask & IN_DONT_FOLLOW)
+        oflags |= O_NOFOLLOW;
+
+    if ((dirfd = open(pathname, oflags)) < 0)
+        return (-1);
+
+    if (fstat(dirfd, &buf) != 0) {
+        (void) close(dirfd);
+        return (-1);
+    }
+
+    if ((mask & IN_ONLYDIR) && !(buf.st_mode & S_IFDIR)) {
+        (void) close(dirfd);
+        errno = ENOTDIR;
+        return (-1);
+    }
+
+    bzero(&ioc, sizeof (ioc));
+    ioc.inaw_fd = dirfd;
+    ioc.inaw_mask = mask;
+
+    if ((wd = ioctl(fd, INOTIFYIOC_ADD_WATCH, &ioc)) < 0) {
+        (void) close(dirfd);
+        return (-1);
+    }
+
+    if (!(buf.st_mode & S_IFDIR) || !(mask & IN_CHILD_EVENTS)) {
+        (void) ioctl(fd, INOTIFYIOC_ACTIVATE, wd);
+        (void) close(dirfd);
+        return (wd);
+    }
+
+    /*
+     * If we have a directory and we have a mask that denotes child events,
+     * we need to manually add a child watch to every directory entry.
+     * (Because our watch is in place, it will automatically be added to
+     * files that are newly created after this point.)
+     */
+    if ((dir = fdopendir(dirfd)) == NULL) {
+        (void) inotify_rm_watch(fd, wd);
+        (void) close(dirfd);
+        return (-1);
+    }
+
+    bzero(&cioc, sizeof (cioc));
+    cioc.inac_fd = dirfd;
+
+    while ((dp = readdir(dir)) != NULL) {
+        if (strcmp(dp->d_name, ".") == 0)
+            continue;
+
+        if (strcmp(dp->d_name, "..") == 0)
+            continue;
+
+        cioc.inac_name = dp->d_name;
+
+        if (ioctl(fd, INOTIFYIOC_ADD_CHILD, &cioc) != 0) {
+            /*
+             * If we get an error that indicates clear internal
+             * malfunctioning, we propagate the error.  Otherwise
+             * we eat it:  this could be a file that no longer
+             * exists or a symlink or something else that we
+             * can't lookup.
+             */
+            switch (errno) {
+            case ENXIO:
+            case EFAULT:
+            case EBADF:
+                (void) closedir(dir);
+                inotify_rm_watch(fd, wd);
+                return (-1);
+            default:
+                break;
+            }
+        }
+    }
+
+    (void) closedir(dir);
+    (void) ioctl(fd, INOTIFYIOC_ACTIVATE, wd);
+
+    return (wd);
+}
+
+int
+inotify_rm_watch(int fd, int wd)
+{
+    return (ioctl(fd, INOTIFYIOC_RM_WATCH, wd));
+}
diff --git a/usr/src/lib/libc/sparc/Makefile.com b/usr/src/lib/libc/sparc/Makefile.com
index b6f50d3263..4c5bbcfc96 100644
--- a/usr/src/lib/libc/sparc/Makefile.com
+++ b/usr/src/lib/libc/sparc/Makefile.com
@@ -933,6 +933,7 @@ PORTSYS=            \
     fcntl.o            \
     getpagesizes.o        \
     getpeerucred.o        \
+    inotify.o        \
     inst_sync.o        \
     issetugid.o        \
     label.o            \
diff --git a/usr/src/man/man3c/Makefile b/usr/src/man/man3c/Makefile
index 8c9186bc0a..a2e6e99cbc 100644
--- a/usr/src/man/man3c/Makefile
+++ b/usr/src/man/man3c/Makefile
@@ -221,6 +221,9 @@ MANFILES=     __fbufsize.3c                    \
        index.3c                    \
        inet.3c                        \
        initgroups.3c                    \
+         inotify_init.3c                    \
+         inotify_add_watch.3c                \
+         inotify_rm_watch.3c                \
        insque.3c                    \
        is_system_labeled.3c                \
        isaexec.3c                    \
diff --git a/usr/src/man/man3c/inotify_add_watch.3c b/usr/src/man/man3c/inotify_add_watch.3c
new file mode 100644
index 0000000000..4f79e03c82
--- /dev/null
+++ b/usr/src/man/man3c/inotify_add_watch.3c
@@ -0,0 +1,120 @@
+'\" te
+.\"  Copyright (c) 2014, Joyent, Inc. All Rights Reserved.
+.\"  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 of the CDDL is also available via the Internet at
+.\"  http://www.illumos.org/license/CDDL.
+.TH INOTIFY_ADD_WATCH 3C "Sep 17, 2014"
+.SH NAME
+inotify_add_watch \- add a watch to an inotify instance
+.SH SYNOPSIS
+
+.LP
+.nf
+#include <sys/inotify.h>
+
+\fBint\fR \fBinotify_add_watch\fR(\fBint\fR \fIfd\fR, \fBconst char *\fR\fIpathname\fR, \fBuint32_t\fR \fImask\fR);
+.fi
+
+.SH DESCRIPTION
+.sp
+.LP
+The \fBinotify_add_watch()\fR function adds a watch for the file or
+directory specified by \fIpathname\fR to the inotify instance
+specified by \fIfd\fR for the events specified by \fImask\fR.  See
+\fBinotify\fR(5) for details on the meaning of \fImask\fR, how
+it affects the interpretation of \fIpathname\fR, and how
+events for the watched file or directory are subsequently
+retrieved via \fBread\fR(2).
+
+.SH RETURN VALUES
+.sp
+.LP
+Upon succesful completion, \fBinotify_add_watch()\fR returns the
+watch descriptor associated with the new watch.
+If an error occurs, -1 is returned and errno is set to indicate
+the error.
+
+.SH ERRORS
+.sp
+.LP
+\fBinotify_add_watch()\fR will fail if:
+.sp
+.ne 2
+.na
+\fB\fBEACCES\fR\fR
+.ad
+.RS 10n
+\fIpathname\fR could not be opened for reading.
+.RE
+
+.sp
+.ne 2
+.na
+\fB\fBEBADF\fR\fR
+.ad
+.RS 10n
+The \fIfd\fR argument is not a valid open file descriptor.
+.RE
+
+.sp
+.ne 2
+.na
+\fB\fBEFAULT\fR\fR
+.ad
+.RS 10n
+The memory associated with \fIpathname\fR was not mapped.
+.RE
+
+.sp
+.ne 2
+.na
+\fB\fBEINVAL\fR\fR
+.ad
+.RS 10n
+The \fIfd\fR argument does not correspond to an
+\fBinotify\fR(5) instance as initialized with
+\fBinotify_init\fR(3C) or \fBinotify_init1\fR(3C).
+.RE
+
+.sp
+.ne 2
+.na
+\fB\fBENOSPC\fR\fR
+.ad
+.RS 10n
+The number of watches on the specified instance would exceed the
+maximum number of watches per \fBinotify\fR(5) instance.
+.RE
+
+.sp
+.ne 2
+.na
+\fB\fBENOTDIR\fR\fR
+.ad
+.RS 10n
+\fIpathname\fR does not correspond to a directory and
+\fBIN_ONLYDIR\fR was specified in \fImask\fR.
+.RE
+
+.sp
+.SH NOTES
+.sp
+.LP
+
+While the \fBinotify\fR(5) facility is implemented for purposes of
+offering compatibility for Linux-borne applications, native
+applications may opt to use it instead of (or in addition to) the
+\fBPORT_SOURCE_FILE\fR capability of event ports.  See
+\fBinotify\fR(5) for details and restrictions.
+
+.SH SEE ALSO
+.sp
+.LP
+\fBinotify_init\fR(3C), \fBinotify_init1\fR(3C),
+\fBport_create\fR(3C), \fBport_associate\fR(3C), \fBport_get\fR(3C),
+\fBinotify\fR(5)
diff --git a/usr/src/man/man3c/inotify_init.3c b/usr/src/man/man3c/inotify_init.3c
new file mode 100644
index 0000000000..a091df2c26
--- /dev/null
+++ b/usr/src/man/man3c/inotify_init.3c
@@ -0,0 +1,107 @@
+'\" te
+.\"  Copyright (c) 2014, Joyent, Inc. All Rights Reserved.
+.\"  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 of the CDDL is also available via the Internet at
+.\"  http://www.illumos.org/license/CDDL.
+.TH INOTIFY_INIT 3C "Sep 17, 2014"
+.SH NAME
+inotify_init, inotify_init1 \- initialize an inotify instance
+.SH SYNOPSIS
+
+.LP
+.nf
+#include <sys/inotify.h>
+
+\fBint\fR \fBinotify_init\fR(\fBvoid\fR);
+.fi
+
+.LP
+.nf
+\fBint\fR \fBinotify_init1\fR(\fBint\fR \fIflags\fR);
+.fi
+
+.SH DESCRIPTION
+.sp
+.LP
+The \fBinotify_init()\fR and \fBinotify_init1()\fR functions both create an
+\fBinotify\fR(5) instance that can be operated upon via
+\fBinotify_add_watch\fR(3C), \fBinotify_rm_watch\fR(3C) and \fBread\fR(2).
+\fBinotify\fR instances are
+represented as file descriptors, and should be closed via \fBclose\fR(2).
+
+The only difference between the two functions is their signature;
+\fBinotify_init()\fR takes no arguments,
+while \fBinotify_init1()\fR takes a \fIflags\fR argument that can have
+any of the following values:
+
+.sp
+.ne 2
+.na
+\fBIN_CLOEXEC\fR
+.ad
+.RS 12n
+Instance should be closed upon an
+\fBexec\fR(2); see \fBopen\fR(2)'s description of \fBO_CLOEXEC\fR.
+.RE
+
+.sp
+.ne 2
+.na
+\fBIN_NONBLOCK\fR
+.ad
+.RS 12n
+Instance be set to be non-blocking.  A \fBread\fR(2) on an
+\fBinotify\fR instance that has been initialized with
+\fBIN_NONBLOCK\fR will return \fBEAGAIN\fR if there are
+no events enqueued in lieu of blocking.
+.RE
+
+.SH RETURN VALUES
+.sp
+.LP
+Upon succesful completion, 0 is returned. Otherwise, -1 is returned and errno
+is set to indicate the error.
+.SH ERRORS
+.sp
+.LP
+The \fBinotify_init()\fR and \fBinotify_init1()\fR functions will fail if:
+.sp
+.ne 2
+.na
+\fB\fBEINVAL\fR\fR
+.ad
+.RS 10n
+The \fIflags\fR are invalid (\fBinotify_init1()\fR).
+.RE
+
+.sp
+.ne 2
+.na
+\fB\fBEMFILE\fR\fR
+.ad
+.RS 10n
+There are currently {\fBOPEN_MAX\fR} file descriptors open in the calling
+process, or the maximum number of \fBinotify\fR instances for the user
+would be exceeded.
+.RE
+
+.sp
+.SH NOTES
+.sp
+.LP
+
+While the \fBinotify\fR(5) facility is implemented for purposes of
+offering compatibility for Linux-borne applications, native
+applications may opt to use it instead of (or in addition to) the
+\fBPORT_SOURCE_FILE\fR capability of event ports.  See
+\fBinotify\fR(5) for details and restrictions.
+
+.SH SEE ALSO
+.sp
+.LP
+\fBinotiy_add_watch\fR(3C), \fBinotify_rm_watch\fR(3C), \fBinotify\fR(5)
diff --git a/usr/src/man/man3c/inotify_rm_watch.3c b/usr/src/man/man3c/inotify_rm_watch.3c
new file mode 100644
index 0000000000..de568f8e24
--- /dev/null
+++ b/usr/src/man/man3c/inotify_rm_watch.3c
@@ -0,0 +1,81 @@
+'\" te
+.\"  Copyright (c) 2014, Joyent, Inc. All Rights Reserved.
+.\"  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 of the CDDL is also available via the Internet at
+.\"  http://www.illumos.org/license/CDDL.
+.TH INOTIFY_RM_WATCH 3C "Sep 17, 2014"
+.SH NAME
+inotify_rm_watch \- remove a watch from an inotify instance
+.SH SYNOPSIS
+
+.LP
+.nf
+#include <sys/inotify.h>
+
+\fBint\fR \fBinotify_rm_watch\fR(\fBint\fR \fIfd\fR, \fBint\fR \fIwd\fR);
+.fi
+
+.SH DESCRIPTION
+.sp
+.LP
+The \fBinotify_rm_watch()\fR function removes the watch specified
+by \fIwd\fR from the inotify instance associated with \fIfd\fR.
+Removing a watch will induce an \fBIN_IGNORED\fR event; see
+\fBinotify\fR(5) for details.
+
+.SH RETURN VALUES
+.sp
+.LP
+Upon succesful completion, \fBinotify_add_watch()\fR returns the
+watch descriptor associated with the new watch.
+If an error occurs, -1 is returned and errno is set to indicate
+the error.
+
+.SH ERRORS
+.sp
+.LP
+\fBinotify_rm_watch()\fR will fail if:
+.sp
+.ne 2
+.na
+\fB\fBEBADF\fR\fR
+.ad
+.RS 10n
+The \fIfd\fR argument is not a valid open file descriptor.
+.RE
+
+.sp
+.ne 2
+.na
+\fB\fBEINVAL\fR\fR
+.ad
+.RS 10n
+The \fIfd\fR argument does not correspond to an
+\fBinotify\fR(5) instance as initialized with
+\fBinotify_init\fR(3C) or \fBinotify_init1\fR(3C), or
+\fIwd\fR is not a valid watch for the specified inotify
+instance.
+.RE
+
+.sp
+.SH NOTES
+.sp
+.LP
+
+While the \fBinotify\fR(5) facility is implemented for purposes of
+offering compatibility for Linux-borne applications, native
+applications may opt to use it instead of (or in addition to) the
+\fBPORT_SOURCE_FILE\fR capability of event ports.  See
+\fBinotify\fR(5) for details and restrictions.
+
+.SH SEE ALSO
+.sp
+.LP
+\fBinotify_init\fR(3C), \fBinotify_init1\fR(3C),
+\fBport_create\fR(3C), \fBport_associate\fR(3C), \fBport_get\fR(3C),
+\fBinotify\fR(5)
diff --git a/usr/src/man/man5/Makefile b/usr/src/man/man5/Makefile
index 624e0aeb48..a8015a3dd3 100644
--- a/usr/src/man/man5/Makefile
+++ b/usr/src/man/man5/Makefile
@@ -89,6 +89,7 @@ MANFILES=    Intro.5            \
        ib.5                \
        ike.config.5            \
        ike.preshared.5            \
+        inotify.5        \
        ipf.5                \
        ipmon.5                \
        ipnat.5                \
diff --git a/usr/src/man/man5/inotify.5 b/usr/src/man/man5/inotify.5
new file mode 100644
index 0000000000..810e889d74
--- /dev/null
+++ b/usr/src/man/man5/inotify.5
@@ -0,0 +1,305 @@
+'\" te
+.\"  Copyright (c) 2014, Joyent, Inc. All Rights Reserved.
+.\"  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 of the CDDL is also available via the Internet at
+.\"  http://www.illumos.org/license/CDDL.
+.TH INOTIFY 5 "Sep 17, 2014"
+.SH NAME
+inotify \- Linux-compatible file event notification facility
+.SH SYNOPSIS
+
+.LP
+.nf
+#include <sys/inotify.h>
+.fi
+
+.SH DESCRIPTION
+.sp
+.LP
+
+\fBinotify\fR is a facility for receiving file system events on specified
+files or directories.  When monitoring a directory, \fBinotify\fR can be
+used to retrieve events not only on the directory, but also on any files
+that the directory contains.  \fBinotify\fR originated with Linux, and
+this facility is designed to be binary-compatible with the Linux facility,
+including the following interfaces:
+
+.RS +4
+.TP
+.ie t \(bu
+.el o
+\fBinotify_init\fR(3C) creates an \fBinotify\fR instance, returning a file
+descriptor associated with the in-kernel event queue.
+.RE
+.RS +4
+.TP
+.ie t \(bu
+.el o
+\fBinotify_init1\fR(3C) also creates an \fBinotify\fR instance, but allows
+for a flags argument that controls some attributes of the returned file
+descriptor.
+.RE
+.RS +4
+.TP
+.ie t \(bu
+.el o
+\fBinotify_add_watch\fR(3C) allows a watch of a particular file or directory
+to be added to a watch list associated with the specified \fBinotify\fR
+instance. \fBinotify_add_watch\fR(3C) returns a watch descriptor that will
+be reflected in the \fIwd\fR member of the \fIinotify_event\fR structure
+returned via a \fBread\fR(2) of the instance.
+.RE
+.RS +4
+.TP
+.ie t \(bu
+.el o
+\fBinotify_rm_watch\fR(3C) removes the watch that corresponds to the specified
+watch descriptor.
+.RE
+
+When all file descriptors referring to a particular \fBinotify\fR instance
+are closed, the instance and all watches associated with that instance are
+freed.
+
+To consume events on an \fBinotify\fR instance, an application should
+issue a \fBread\fR(2) to the instance.  If no events are available
+(and the \fBinotify\fR instance has not been explicitly made non-blocking
+via \fBinotify_init1\fR(3C)) the \fBread\fR(2) will block until a
+watched event occurs. If and when events are available, \fBread\fR(2) will
+return an array of the following structures:
+
+.sp
+.in +2
+.nf
+struct inotify_event {
+        int      wd;       /* watch descriptor */
+        uint32_t mask;     /* mask of event */
+        uint32_t cookie;   /* cookie for associating renames */
+        uint32_t len;      /* size of name field */
+        char     name[];   /* optional name */
+};
+.fi
+.in -2
+
+\fIwd\fR contains the watch descriptor that corresponds to the event,
+as returned by \fBinotify_add_watch\fR(3C).
+
+\fImask\fR is a bitwise \fBOR\fR of event masks (see below) that
+describes the event.
+
+\fIcookie\fR is an opaque value that can be used to associate different
+events into a single logical event. In particular, it allows consumers to
+associate \fBIN_MOVED_FROM\fR events with subsequent \fBIN_MOVED_TO\fR
+events.
+
+\fIlen\fR denotes the length of the \fIname\fR field, including any padding
+required for trailing null bytes and alignment. The size of the entire
+event is therefore the size of the \fIinotify_event\fR structure plus the
+value of \fIlen\fR.
+
+\fIname\fR contains the name of the file associated with the event, if any.
+This field is only present when the watched entity is a directory and
+the event corresponds to a file that was contained by the watched directory
+(though see \fBNOTES\fR and \fBWARNINGS\fR for details and limitations).
+When present, \fIname\fR is null terminated, and may contain additional
+zero bytes
+to pad for alignment. (The length of this field -- including any bytes
+for alignment -- is denoted by the \fIlen\fR field.)
+
+.SS "Events"
+
+The events that can be generated on a watched entity are as follows:
+
+.sp
+.in +2
+.TS
+c c
+l l .
+\fIEvent\fR    \fIDescription\fR
+\fBIN_ACCESS\fR    File/directory was accessed
+\fBIN_ATTRIB\fR    File/directory attributes were changed
+\fBIN_CLOSE_WRITE\fR    File/directory opened for writing was closed
+\fBIN_CLOSE_NOWRITE\fR    File/directory not opened for writing was closed
+\fBIN_CREATE\fR    File/directory created in watched directory
+\fBIN_DELETE\fR    File/directory deleted from watched directory
+\fBIN_DELETE_SELF\fR    Watched file/directory was deleted
+\fBIN_MODIFY\fR    File/directory was modified
+\fBIN_MODIFY_SELF\fR    Watched file/directory was modified
+\fBIN_MOVED_FROM\fR    File was renamed from entity in watched directory
+\fBIN_MOVED_TO\fR    File was renamed to entity in watched directory
+\fBIN_OPEN\fR    File/directory was opened
+.TE
+.in -2
+
+Of these, all events except \fBIN_MOVE_SELF\fR and \fBIN_DELETE_SELF\fR
+can refer to either the watched entity or (if the watched entity
+is a directory) a file or directory contained by the watched directory.
+(See \fBNOTES\fR and \fBWARNINGS\fR, below for details on this
+mechanism and its limitations.)
+If the event corresponds to a contained entity,
+\fIname\fR will be set to the name of the affected
+entity.
+
+In addition to speciyfing events of interest, watched events may
+be modified by potentially setting any of the following when adding a
+watch via \fBinotify_add_watch\fR(3C):
+
+.sp
+.ne 2
+.na
+\fBIN_DONT_FOLLOW\fR
+.ad
+.RS 12n
+Don't follow the specified pathname if it is a symbolic link.
+.RE
+
+.sp
+.ne 2
+.na
+\fBIN_EXCL_UNLINK\fR
+.ad
+.RS 12n
+If watching a directory and a contained entity becomes unlinked, cease
+generating events for that entity. (By default, contained entities will
+continue to generate events on their former parent directory.)
+.RE
+
+.sp
+.ne 2
+.na
+\fBIN_MASK_ADD\fR
+.ad
+.RS 12n
+If the specified pathname is already being watched, the specified events
+will be added to the watched events instead of the default behavior of
+replacing them. (If one
+may forgive the editorializing, this particular interface gewgaw
+seems entirely superfluous, and a canonical example of
+feasibility trumping wisdom.)
+.RE
+
+.sp
+.ne 2
+.na
+\fBIN_ONESHOT\fR
+.ad
+.RS 12n
+Once an event has been generated for the watched entity, remove the
+watch from the watch list as if \fBinotify_rm_watch\fR(3C) had been called
+on it (thereby inducing an \fBIN_IGNORED\fR event).
+.RE
+
+.sp
+.ne 2
+.na
+\fBIN_ONLYDIR\fR
+.ad
+.RS 12n
+Only watch the specified pathname if it is a directory.
+.RE
+
+In addition to the specified events, the following bits may be specified
+in the \fImask\fR field as returned from \fBread\fR(2):
+
+.sp
+.ne 2
+.na
+\fBIN_IGNORED\fR
+.ad
+.RS 12n
+A watch was removed explicitly (i.e, via \fBinotify_rm_watch\fR(3C)) or
+implicitly (e.g., because \fBIN_ONESHOT\fR was set or because the watched
+entity was deleted).
+.RE
+
+.sp
+.ne 2
+.na
+\fBIN_ISDIR\fR
+.ad
+.RS 12n
+The entity inducing the event is a directory.
+.RE
+
+.sp
+.ne 2
+.na
+\fBIN_Q_OVERFLOW\fR
+.ad
+.RS 12n
+The event queue exceeded the maximum event queue length per instance.
+(By default, this is 16384, but it can be tuned by setting
+\fBinotify_maxevents\fR via \fB/etc/system\fR.)
+.RE
+
+.sp
+.ne 2
+.na
+\fBIN_UNMOUNT\fR
+.ad
+.RS 12n
+The filesystem containing the watched entity was unmounted.
+.RE
+
+.sp
+.SH NOTES
+.sp
+.LP
+
+\fBinotify\fR instances can be monitored via \fBpoll\fR(2),
+\fBport_get\fR(3C), \fBepoll\fR(5), etc.
+
+The event queue associated with an \fBinotify\fR instance is serialized
+and ordered: events will be placed on the tail of the queue in the order
+that they occur.
+
+If at the time an event occurs the tail of the event queue is identical
+to the newly received event, the newly received event will be dropped,
+effectively coalescing the two events.
+
+When watching a directory and receieving events on contained elements
+(i.e., a contained file or subdirectory), note that the information
+received in the \fIname\fR field may be stale:  the file may have been
+renamed between the event and its processing.  If a file has been unlinked
+(and if \fBIN_EXCL_UNLINK\fR has not been set),
+the \fIname\fR will reflect the last name that resolved to the file.
+If a new file is created in the same directory with the old name, events
+on the new file and the old (unlinked) file will become undistinguishable.
+
+The number of bytes that are available to be read on an \fBinotify\fR
+instance can be determined via a \fBFIONREAD\fR \fBioctl\fR(2).
+
+.sp
+.SH WARNINGS
+.sp
+.LP
+
+While a best effort has been made to mimic the Linux semantics, there
+remains a fundamental difference with respect to hard links:  on Linux,
+if a file has multiple hard links to it, a notification on watched
+directory or file will be received if and only if that event was received
+via a watched directory or file.  For events that are induced by open files
+(such as \fBIN_MODIFY\fR), these semantics seem peculiar:  the watched
+file is in fact changing, but because it is not changing via the watched
+path, no notification is received.  By contrast, the implementation here
+will always yield an event in this case -- even if the event was induced
+by an \fBopen\fR(2) via an unwatched path.  If an event occurs within a
+watched directory on a file for which there exist multiple hard links within
+the same (watched) directory, the event's \fIname\fR will correspond to one
+of the links to the file.  If multiple hard links exist to the
+same file in the same watched directory and one of the links is removed,
+notifications may not necessarily continue to be received for the file,
+despite the (remaining) link in the watched directory; users of
+\fBinotify\fR should exercise extreme caution when watching directories
+that contain files with multiple hard links in the same directory.
+
+.SH SEE ALSO
+.sp
+.LP
+\fBinotify_init\fR(3C), \fBinotify_init1\fR(3C), \fBinotify_add_watch\fR(3C),
+\fBinotify_rm_watch\fR(3C), \fBport_get\fR(3C), \fBepoll\fR(5)
diff --git a/usr/src/uts/common/Makefile.files b/usr/src/uts/common/Makefile.files
index ad37a2d6b5..b4ea210cac 100644
--- a/usr/src/uts/common/Makefile.files
+++ b/usr/src/uts/common/Makefile.files
@@ -1007,6 +1007,8 @@ DEVPOOL_OBJS += devpool.o
 I8042_OBJS +=    i8042.o
+INOTIFY_OBJS +=    inotify.o
+
 KB8042_OBJS +=    \
         at_keyprocess.o    \
         kb8042.o    \
diff --git a/usr/src/uts/common/fs/nfs/nfs3_vnops.c b/usr/src/uts/common/fs/nfs/nfs3_vnops.c
index 291e5cd337..450cc22683 100644
--- a/usr/src/uts/common/fs/nfs/nfs3_vnops.c
+++ b/usr/src/uts/common/fs/nfs/nfs3_vnops.c
@@ -3352,10 +3352,9 @@ nfs3rename(vnode_t *odvp, char *onm, vnode_t *ndvp, char *nnm, cred_t *cr,
         if (nvp)
             vnevent_rename_dest(nvp, ndvp, nnm, ct);
-        if (odvp != ndvp)
-            vnevent_rename_dest_dir(ndvp, ct);
         ASSERT(ovp != NULL);
         vnevent_rename_src(ovp, odvp, onm, ct);
+        vnevent_rename_dest_dir(ndvp, ovp, nnm, ct);
     }
     if (nvp) {
diff --git a/usr/src/uts/common/fs/nfs/nfs4_vnops.c b/usr/src/uts/common/fs/nfs/nfs4_vnops.c
index b9ba9a6ead..31d922e4be 100644
--- a/usr/src/uts/common/fs/nfs/nfs4_vnops.c
+++ b/usr/src/uts/common/fs/nfs/nfs4_vnops.c
@@ -8059,8 +8059,9 @@ link_call:
      * vnode if it already existed.
      */
     if (error == 0) {
-        vnode_t *tvp;
+        vnode_t *tvp, *tovp;
         rnode4_t *trp;
+
         /*
          * Notify the vnode. Each links is represented by
          * a different vnode, in nfsv4.
@@ -8073,23 +8074,20 @@ link_call:
             vnevent_rename_dest(tvp, ndvp, nnm, ct);
         }
-        /*
-         * if the source and destination directory are not the
-         * same notify the destination directory.
-         */
-        if (VTOR4(odvp) != VTOR4(ndvp)) {
-            trp = VTOR4(ndvp);
-            tvp = ndvp;
-            if (IS_SHADOW(ndvp, trp))
-                tvp = RTOV4(trp);
-            vnevent_rename_dest_dir(tvp, ct);
-        }
-
         trp = VTOR4(ovp);
-        tvp = ovp;
+        tovp = ovp;
         if (IS_SHADOW(ovp, trp))
-            tvp = RTOV4(trp);
+            tovp = RTOV4(trp);
+
         vnevent_rename_src(tvp, odvp, onm, ct);
+
+        trp = VTOR4(ndvp);
+        tvp = ndvp;
+
+        if (IS_SHADOW(ndvp, trp))
+            tvp = RTOV4(trp);
+
+        vnevent_rename_dest_dir(tvp, tovp, nnm, ct);
     }
     if (nvp) {
diff --git a/usr/src/uts/common/fs/nfs/nfs_vnops.c b/usr/src/uts/common/fs/nfs/nfs_vnops.c
index 4ac6450381..77d3541208 100644
--- a/usr/src/uts/common/fs/nfs/nfs_vnops.c
+++ b/usr/src/uts/common/fs/nfs/nfs_vnops.c
@@ -2687,11 +2687,9 @@ nfsrename(vnode_t *odvp, char *onm, vnode_t *ndvp, char *nnm, cred_t *cr,
         if (nvp)
             vnevent_rename_dest(nvp, ndvp, nnm, ct);
-        if (odvp != ndvp)
-            vnevent_rename_dest_dir(ndvp, ct);
-
         ASSERT(ovp != NULL);
         vnevent_rename_src(ovp, odvp, onm, ct);
+        vnevent_rename_dest_dir(ndvp, ovp, nnm, ct);
     }
     if (nvp) {
diff --git a/usr/src/uts/common/fs/pcfs/pc_dir.c b/usr/src/uts/common/fs/pcfs/pc_dir.c
index a9ee604b7c..21a0b1a4bd 100644
--- a/usr/src/uts/common/fs/pcfs/pc_dir.c
+++ b/usr/src/uts/common/fs/pcfs/pc_dir.c
@@ -24,6 +24,10 @@
  * Use is subject to license terms.
  */
+/*
+ * Copyright (c) 2014, Joyent, Inc. All rights reserved.
+ */
+
 #include <sys/param.h>
 #include <sys/errno.h>
 #include <sys/systm.h>
diff --git a/usr/src/uts/common/fs/tmpfs/tmp_vnops.c b/usr/src/uts/common/fs/tmpfs/tmp_vnops.c
index 624ea30b7f..c404433edd 100644
--- a/usr/src/uts/common/fs/tmpfs/tmp_vnops.c
+++ b/usr/src/uts/common/fs/tmpfs/tmp_vnops.c
@@ -1307,6 +1307,5 @@ tmp_rename(
         * vnevent_rename_dest is called in tdirenter().
         * Notify the target dir if not same as source dir.
         */
-        if (ndvp != odvp)
-            vnevent_rename_dest_dir(ndvp, ct);
+        vnevent_rename_dest_dir(ndvp, TNTOV(fromtp), nnm, ct);
    }
    done:
diff --git a/usr/src/uts/common/fs/udfs/udf_dir.c b/usr/src/uts/common/fs/udfs/udf_dir.c
index c1e2c74a87..def046a0bf 100644
--- a/usr/src/uts/common/fs/udfs/udf_dir.c
+++ b/usr/src/uts/common/fs/udfs/udf_dir.c
@@ -562,9 +563,8 @@ out:
                     namep, ctp);
             }
-            if (sdp != tdp) {
-                vnevent_rename_dest_dir(ITOV(tdp), ctp);
-            }
+            vnevent_rename_dest_dir(ITOV(tdp), ITOV(tip),
+                namep, ctp);
         }
         /*
diff --git a/usr/src/uts/common/fs/ufs/ufs_vnops.c b/usr/src/uts/common/fs/ufs/ufs_vnops.c
index 3fcfda1ab6..c77872b11d 100644
--- a/usr/src/uts/common/fs/ufs/ufs_vnops.c
+++ b/usr/src/uts/common/fs/ufs/ufs_vnops.c
@@ -3705,12 +3705,7 @@ retry_firstlock:
        error = 0;
    if (error == 0) {
        vnevent_rename_src(ITOV(sip), sdvp, snm, ct);
        /*
         * Notify the target directory of the rename event
         * if source and target directories are not the same.
         */
-    if (sdvp != tdvp)
-        vnevent_rename_dest_dir(tdvp, ct);
+    vnevent_rename_dest_dir(tdvp, ITOV(sip), tnm, ct);
 errout:
     if (slot.fbp)
diff --git a/usr/src/uts/common/fs/vnode.c b/usr/src/uts/common/fs/vnode.c
index 846c343a4f..561fb1bd02 100644
--- a/usr/src/uts/common/fs/vnode.c
+++ b/usr/src/uts/common/fs/vnode.c
@@ -21,7 +21,7 @@
 /*
  * Copyright (c) 1988, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2013, Joyent, Inc. All rights reserved.
+ * Copyright (c) 2014, Joyent, Inc. All rights reserved.
  */
 /*    Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T    */
@@ -2522,6 +2522,7 @@ vnevent_rename_src(vnode_t *vp, vnode_t *dvp, char *name, caller_context_t *ct)
     if (vp == NULL || vp->v_femhead == NULL) {
         return;
     }
+    (void) VOP_VNEVENT(dvp, VE_RENAME_SRC_DIR, vp, name, ct);
     (void) VOP_VNEVENT(vp, VE_RENAME_SRC, dvp, name, ct);
 }
@@ -2536,12 +2537,13 @@ vnevent_rename_dest(vnode_t *vp, vnode_t *dvp, char *name,
 }
 void
-vnevent_rename_dest_dir(vnode_t *vp, caller_context_t *ct)
+vnevent_rename_dest_dir(vnode_t *vp, vnode_t *nvp, char *name,
+    caller_context_t *ct)
 {
     if (vp == NULL || vp->v_femhead == NULL) {
         return;
     }
-    (void) VOP_VNEVENT(vp, VE_RENAME_DEST_DIR, NULL, NULL, ct);
+    (void) VOP_VNEVENT(vp, VE_RENAME_DEST_DIR, nvp, name, ct);
 }
 void
diff --git a/usr/src/uts/common/fs/zfs/zfs_vnops.c b/usr/src/uts/common/fs/zfs/zfs_vnops.c
index f74bd50bbc..6f661d6cf8 100644
--- a/usr/src/uts/common/fs/zfs/zfs_vnops.c
+++ b/usr/src/uts/common/fs/zfs/zfs_vnops.c
@@ -22,7 +22,7 @@
  * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2012, 2014 by Delphix. All rights reserved.
  * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
- * Copyright 2013 Joyent, Inc.  All rights reserved.
+ * Copyright 2014 Joyent, Inc.  All rights reserved.
  */
 /* Portions Copyright 2007 Jeremy Teo */
@@ -3698,9 +3698,7 @@ top:
     if (error == 0) {
         vnevent_rename_src(ZTOV(szp), sdvp, snm, ct);
-        /* notify the target dir if it is not the same as source dir */
-        if (tdvp != sdvp)
-            vnevent_rename_dest_dir(tdvp, ct);
+        vnevent_rename_dest_dir(tdvp, ZTOV(szp), tnm, ct);
     }
 out:
     if (zl != NULL)
diff --git a/usr/src/uts/common/io/inotify.c b/usr/src/uts/common/io/inotify.c
new file mode 100644
index 0000000000..b8dfa1223b
--- /dev/null
+++ b/usr/src/uts/common/io/inotify.c
@@ -0,0 +1,1480 @@
+/*
+ * 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 of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright (c) 2014 Joyent, Inc.  All rights reserved.
+ */
+
+/*
+ * Support for the inotify facility, a Linux-borne facility for asynchronous
+ * notification of certain events on specified files or directories.  Our
+ * implementation broadly leverages the file event monitoring facility, and
+ * would actually be quite straightforward were it not for a very serious
+ * blunder in the inotify interface:  in addition to allowing for one to be
+ * notified on events on a particular file or directory, inotify also allows
+ * for one to be notified on certain events on files _within_ a watched
+ * directory -- even though those events have absolutely nothing to do with
+ * the directory itself.  This leads to all sorts of madness because file
+ * operations are (of course) not undertaken on paths but rather on open
+ * files -- and the relationships between open files and the paths that resolve
+ * to those files are neither static nor isomorphic.  We implement this
+ * concept by having _child watches_ when directories are watched with events
+ * in IN_CHILD_EVENTS.  We add child watches when a watch on a directory is
+ * first added, and we modify those child watches dynamically as files are
+ * created, deleted, moved into or moved out of the specified directory.  This
+ * mechanism works well, absent hard links.  Hard links, unfortunately, break
+ * this rather badly, and the user is warned that watches on directories that
+ * have multiple directory entries referring to the same file may behave
+ * unexpectedly.
+ */
+
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/inotify.h>
+#include <sys/fem.h>
+#include <sys/conf.h>
+#include <sys/stat.h>
+#include <sys/vfs_opreg.h>
+#include <sys/vmem.h>
+#include <sys/avl.h>
+#include <sys/sysmacros.h>
+#include <sys/cyclic.h>
+#include <sys/filio.h>
+
+struct inotify_state;
+struct inotify_kevent;
+
+typedef struct inotify_watch inotify_watch_t;
+typedef struct inotify_state inotify_state_t;
+typedef struct inotify_kevent inotify_kevent_t;
+
+struct inotify_watch {
+    kmutex_t inw_lock;            /* lock protecting ref count */
+    int inw_refcnt;                /* reference count */
+    uint8_t inw_zombie:1;            /* boolean: is zombie */
+    uint8_t inw_fired:1;            /* boolean: fired one-shot */
+    uint8_t inw_active:1;            /* boolean: watch is active */
+    uint8_t inw_orphaned:1;            /* boolean: orphaned */
+    kcondvar_t inw_cv;            /* condvar for zombifier */
+    uint32_t inw_mask;            /* mask of watch */
+    int32_t inw_wd;                /* watch descriptor */
+    vnode_t *inw_vp;            /* underlying vnode */
+    inotify_watch_t *inw_parent;        /* parent, if a child */
+    avl_node_t inw_byvp;            /* watches by vnode */
+    avl_node_t inw_bywd;            /* watches by descriptor */
+    avl_tree_t inw_children;        /* children, if a parent */
+    char *inw_name;                /* name, if a child */
+    list_node_t inw_orphan;            /* orphan list */
+    inotify_state_t *inw_state;        /* corresponding state */
+};
+
+struct inotify_kevent {
+    inotify_kevent_t *ine_next;        /* next event in queue */
+    struct inotify_event ine_event;        /* event (variable size) */
+};
+
+#define    INOTIFY_EVENT_LENGTH(ev) \
+    (sizeof (inotify_kevent_t) + (ev)->ine_event.len)
+
+struct inotify_state {
+    kmutex_t ins_lock;            /* lock protecting state */
+    avl_tree_t ins_byvp;            /* watches by vnode */
+    avl_tree_t ins_bywd;            /* watches by descriptor */
+    vmem_t *ins_wds;            /* watch identifier arena */
+    int ins_maxwatches;            /* maximum number of watches */
+    int ins_maxevents;            /* maximum number of events */
+    int ins_nevents;            /* current # of events */
+    int32_t ins_size;            /* total size of events */
+    inotify_kevent_t *ins_head;        /* head of event queue */
+    inotify_kevent_t *ins_tail;        /* tail of event queue */
+    pollhead_t ins_pollhd;            /* poll head */
+    kcondvar_t ins_cv;            /* condvar for reading */
+    list_t ins_orphans;            /* orphan list */
+    cyclic_id_t ins_cleaner;        /* cyclic for cleaning */
+    inotify_watch_t *ins_zombies;        /* zombie watch list */
+    cred_t *ins_cred;            /* creator's credentials */
+    inotify_state_t *ins_next;        /* next state on global list */
+};
+
+/*
+ * Tunables (exported read-only in lx-branded zones via /proc).
+ */
+int    inotify_maxwatches = 8192;        /* max watches per instance */
+int    inotify_maxevents = 16384;        /* max events */
+int    inotify_maxinstances = 128;        /* max instances per user */
+
+/*
+ * Internal global variables.
+ */
+static kmutex_t        inotify_lock;        /* lock protecting state */
+static dev_info_t    *inotify_devi;        /* device info */
+static fem_t        *inotify_femp;        /* FEM pointer */
+static vmem_t        *inotify_minor;        /* minor number arena */
+static void        *inotify_softstate;    /* softstate pointer */
+static inotify_state_t    *inotify_state;        /* global list if state */
+
+static void inotify_watch_event(inotify_watch_t *, uint64_t, char *);
+static void inotify_watch_insert(inotify_watch_t *, vnode_t *, char *);
+static void inotify_watch_delete(inotify_watch_t *, uint32_t);
+static void inotify_watch_remove(inotify_state_t *state,
+    inotify_watch_t *watch);
+
+static int
+inotify_fop_close(femarg_t *vf, int flag, int count, offset_t offset,
+    cred_t *cr, caller_context_t *ct)
+{
+    inotify_watch_t *watch = vf->fa_fnode->fn_available;
+    int rval;
+
+    if ((rval = vnext_close(vf, flag, count, offset, cr, ct)) == 0) {
+        inotify_watch_event(watch, flag & FWRITE ?
+            IN_CLOSE_WRITE : IN_CLOSE_NOWRITE, NULL);
+    }
+
+    return (rval);
+}
+
+static int
+inotify_fop_create(femarg_t *vf, char *name, vattr_t *vap, vcexcl_t excl,
+    int mode, vnode_t **vpp, cred_t *cr, int flag, caller_context_t *ct,
+    vsecattr_t *vsecp)
+{
+    inotify_watch_t *watch = vf->fa_fnode->fn_available;
+    int rval;
+
+    if ((rval = vnext_create(vf, name, vap, excl, mode,
+        vpp, cr, flag, ct, vsecp)) == 0) {
+        inotify_watch_insert(watch, *vpp, name);
+        inotify_watch_event(watch, IN_CREATE, name);
+    }
+
+    return (rval);
+}
+
+static int
+inotify_fop_link(femarg_t *vf, vnode_t *svp, char *tnm, cred_t *cr,
+    caller_context_t *ct, int flags)
+{
+    inotify_watch_t *watch = vf->fa_fnode->fn_available;
+    int rval;
+
+    if ((rval = vnext_link(vf, svp, tnm, cr, ct, flags)) == 0) {
+        inotify_watch_insert(watch, svp, tnm);
+        inotify_watch_event(watch, IN_CREATE, tnm);
+    }
+
+    return (rval);
+}
+
+static int
+inotify_fop_mkdir(femarg_t *vf, char *name, vattr_t *vap, vnode_t **vpp,
+    cred_t *cr, caller_context_t *ct, int flags, vsecattr_t *vsecp)
+{
+    inotify_watch_t *watch = vf->fa_fnode->fn_available;
+    int rval;
+
+    if ((rval = vnext_mkdir(vf, name, vap, vpp, cr,
+        ct, flags, vsecp)) == 0) {
+        inotify_watch_insert(watch, *vpp, name);
+        inotify_watch_event(watch, IN_CREATE | IN_ISDIR, name);
+    }
+
+    return (rval);
+}
+
+static int
+inotify_fop_open(femarg_t *vf, int mode, cred_t *cr, caller_context_t *ct)
+{
+    inotify_watch_t *watch = vf->fa_fnode->fn_available;
+    int rval;
+
+    if ((rval = vnext_open(vf, mode, cr, ct)) == 0)
+        inotify_watch_event(watch, IN_OPEN, NULL);
+
+    return (rval);
+}
+
+static int
+inotify_fop_read(femarg_t *vf, struct uio *uiop, int ioflag, struct cred *cr,
+    caller_context_t *ct)
+{
+    inotify_watch_t *watch = vf->fa_fnode->fn_available;
+    int rval = vnext_read(vf, uiop, ioflag, cr, ct);
+    inotify_watch_event(watch, IN_ACCESS, NULL);
+
+    return (rval);
+}
+
+static int
+inotify_fop_readdir(femarg_t *vf, uio_t *uiop, cred_t *cr, int *eofp,
+    caller_context_t *ct, int flags)
+{
+    inotify_watch_t *watch = vf->fa_fnode->fn_available;
+    int rval = vnext_readdir(vf, uiop, cr, eofp, ct, flags);
+    inotify_watch_event(watch, IN_ACCESS | IN_ISDIR, NULL);
+
+    return (rval);
+}
+
+int
+inotify_fop_remove(femarg_t *vf, char *nm, cred_t *cr, caller_context_t *ct,
+    int flags)
+{
+    inotify_watch_t *watch = vf->fa_fnode->fn_available;
+    int rval;
+
+    if ((rval = vnext_remove(vf, nm, cr, ct, flags)) == 0)
+        inotify_watch_event(watch, IN_DELETE, nm);
+
+    return (rval);
+}
+
+int
+inotify_fop_rmdir(femarg_t *vf, char *nm, vnode_t *cdir, cred_t *cr,
+    caller_context_t *ct, int flags)
+{
+    inotify_watch_t *watch = vf->fa_fnode->fn_available;
+    int rval;
+
+    if ((rval = vnext_rmdir(vf, nm, cdir, cr, ct, flags)) == 0)
+        inotify_watch_event(watch, IN_DELETE | IN_ISDIR, nm);
+
+    return (rval);
+}
+
+static int
+inotify_fop_setattr(femarg_t *vf, vattr_t *vap, int flags, cred_t *cr,
+    caller_context_t *ct)
+{
+    inotify_watch_t *watch = vf->fa_fnode->fn_available;
+    int rval;
+
+    if ((rval = vnext_setattr(vf, vap, flags, cr, ct)) == 0)
+        inotify_watch_event(watch, IN_ATTRIB, NULL);
+
+    return (rval);
+}
+
+static int
+inotify_fop_write(femarg_t *vf, struct uio *uiop, int ioflag, struct cred *cr,
+    caller_context_t *ct)
+{
+    inotify_watch_t *watch = vf->fa_fnode->fn_available;
+    int rval = vnext_write(vf, uiop, ioflag, cr, ct);
+    inotify_watch_event(watch, IN_MODIFY, NULL);
+
+    return (rval);
+}
+
+static int
+inotify_fop_vnevent(femarg_t *vf, vnevent_t vnevent, vnode_t *dvp, char *name,
+    caller_context_t *ct)
+{
+    inotify_watch_t *watch = vf->fa_fnode->fn_available;
+
+    switch (vnevent) {
+    case VE_RENAME_SRC:
+        inotify_watch_event(watch, IN_MOVE_SELF, NULL);
+        inotify_watch_delete(watch, IN_MOVE_SELF);
+        break;
+    case VE_REMOVE:
+        /*
+         * Linux will apparently fire an IN_ATTRIB event when the link
+         * count changes (including when it drops to 0 on a remove).
+         * This is merely somewhat odd; what is amazing is that this
+         * IN_ATTRIB event is not visible on an inotify watch on the
+         * parent directory.  (IN_ATTRIB events are normally sent to
+         * watches on the parent directory).  While it's hard to
+         * believe that this constitutes desired semantics, ltp
+         * unfortunately tests this case (if implicitly); in the name
+         * of bug-for-bug compatibility, we fire IN_ATTRIB iff we are
+         * explicitly watching the file that has been removed.
+         */
+        if (watch->inw_parent == NULL)
+            inotify_watch_event(watch, IN_ATTRIB, NULL);
+
+        /*FALLTHROUGH*/
+    case VE_RENAME_DEST:
+        inotify_watch_event(watch, IN_DELETE_SELF, NULL);
+        inotify_watch_delete(watch, IN_DELETE_SELF);
+        break;
+    case VE_RMDIR:
+        /*
+         * It seems that IN_ISDIR should really be OR'd in here, but
+         * Linux doesn't seem to do that in this case; for the sake of
+         * bug-for-bug compatibility, we don't do it either.
+         */
+        inotify_watch_event(watch, IN_DELETE_SELF, NULL);
+        inotify_watch_delete(watch, IN_DELETE_SELF);
+        break;
+    case VE_CREATE:
+        inotify_watch_event(watch, IN_MODIFY | IN_ATTRIB, NULL);
+        break;
+    case VE_LINK:
+        inotify_watch_event(watch, IN_ATTRIB, NULL);
+        break;
+    case VE_RENAME_SRC_DIR:
+        inotify_watch_event(watch, IN_MOVED_FROM, name);
+        break;
+    case VE_RENAME_DEST_DIR:
+        if (name == NULL)
+            name = dvp->v_path;
+
+        inotify_watch_insert(watch, dvp, name);
+        inotify_watch_event(watch, IN_MOVED_TO, name);
+        break;
+    case VE_SUPPORT:
+    case VE_MOUNTEDOVER:
+    case VE_TRUNCATE:
+        break;
+    }
+
+    return (vnext_vnevent(vf, vnevent, dvp, name, ct));
+}
+
+const fs_operation_def_t inotify_vnodesrc_template[] = {
+    VOPNAME_CLOSE,        { .femop_close = inotify_fop_close },
+    VOPNAME_CREATE,        { .femop_create = inotify_fop_create },
+    VOPNAME_LINK,        { .femop_link = inotify_fop_link },
+    VOPNAME_MKDIR,        { .femop_mkdir = inotify_fop_mkdir },
+    VOPNAME_OPEN,        { .femop_open = inotify_fop_open },
+    VOPNAME_READ,        { .femop_read = inotify_fop_read },
+    VOPNAME_READDIR,    { .femop_readdir = inotify_fop_readdir },
+    VOPNAME_REMOVE,        { .femop_remove = inotify_fop_remove },
+    VOPNAME_RMDIR,        { .femop_rmdir = inotify_fop_rmdir },
+    VOPNAME_SETATTR,    { .femop_setattr = inotify_fop_setattr },
+    VOPNAME_WRITE,        { .femop_write = inotify_fop_write },
+    VOPNAME_VNEVENT,    { .femop_vnevent = inotify_fop_vnevent },
+    NULL, NULL
+};
+
+static int
+inotify_watch_cmpwd(inotify_watch_t *lhs, inotify_watch_t *rhs)
+{
+    if (lhs->inw_wd < rhs->inw_wd)
+        return (-1);
+
+    if (lhs->inw_wd > rhs->inw_wd)
+        return (1);
+
+    return (0);
+}
+
+static int
+inotify_watch_cmpvp(inotify_watch_t *lhs, inotify_watch_t *rhs)
+{
+    uintptr_t lvp = (uintptr_t)lhs->inw_vp, rvp = (uintptr_t)rhs->inw_vp;
+
+    if (lvp < rvp)
+        return (-1);
+
+    if (lvp > rvp)
+        return (1);
+
+    return (0);
+}
+
+static void
+inotify_watch_hold(inotify_watch_t *watch)
+{
+    mutex_enter(&watch->inw_lock);
+    VERIFY(watch->inw_refcnt > 0);
+    watch->inw_refcnt++;
+    mutex_exit(&watch->inw_lock);
+}
+
+static void
+inotify_watch_release(inotify_watch_t *watch)
+{
+    mutex_enter(&watch->inw_lock);
+    VERIFY(watch->inw_refcnt > 1);
+
+    if (--watch->inw_refcnt == 1 && watch->inw_zombie) {
+        /*
+         * We're down to our last reference; kick anyone that might be
+         * waiting.
+         */
+        cv_signal(&watch->inw_cv);
+    }
+
+    mutex_exit(&watch->inw_lock);
+}
+
+static void
+inotify_watch_event(inotify_watch_t *watch, uint64_t mask, char *name)
+{
+    inotify_kevent_t *event, *tail;
+    inotify_state_t *state = watch->inw_state;
+    uint32_t wd = watch->inw_wd, cookie = 0, len;
+    int align = sizeof (uintptr_t) - 1;
+    boolean_t removal = mask & IN_REMOVAL ? B_TRUE : B_FALSE;
+    inotify_watch_t *source = watch;
+
+    if (!(mask &= watch->inw_mask) || mask == IN_ISDIR)
+        return;
+
+    if (watch->inw_parent != NULL) {
+        /*
+         * This is an event on the child; if this isn't a valid child
+         * event, return.  Otherwise, we move our watch to be our
+         * parent (which we know is around because we have a hold on
+         * it) and continue.
+         */
+        if (!(mask & IN_CHILD_EVENTS))
+            return;
+
+        name = watch->inw_name;
+        watch = watch->inw_parent;
+    }
+
+    if (!removal) {
+        mutex_enter(&state->ins_lock);
+
+        if (watch->inw_zombie ||
+            watch->inw_fired || !watch->inw_active) {
+            mutex_exit(&state->ins_lock);
+            return;
+        }
+    } else {
+        if (!watch->inw_active)
+            return;
+
+        VERIFY(MUTEX_HELD(&state->ins_lock));
+    }
+
+    /*
+     * If this is an operation on a directory and it's a child event
+     * (event if it's not on a child), we specify IN_ISDIR.
+     */
+    if (source->inw_vp->v_type == VDIR && (mask & IN_CHILD_EVENTS))
+        mask |= IN_ISDIR;
+
+    if (mask & (IN_MOVED_FROM | IN_MOVED_TO))
+        cookie = (uint32_t)curthread->t_did;
+
+    if (state->ins_nevents >= state->ins_maxevents) {
+        /*
+         * We're at our maximum number of events -- turn our event
+         * into an IN_Q_OVERFLOW event, which will be coalesced if
+         * it's already the tail event.
+         */
+        mask = IN_Q_OVERFLOW;
+        wd = (uint32_t)-1;
+        cookie = 0;
+        len = 0;
+    }
+
+    if ((tail = state->ins_tail) != NULL && tail->ine_event.wd == wd &&
+        tail->ine_event.mask == mask && tail->ine_event.cookie == cookie &&
+        ((tail->ine_event.len == 0 && len == 0) ||
+        (name != NULL && tail->ine_event.len != 0 &&
+        strcmp(tail->ine_event.name, name) == 0))) {
+        /*
+         * This is an implicitly coalesced event; we're done.
+         */
+        if (!removal)
+            mutex_exit(&state->ins_lock);
+        return;
+    }
+
+    if (name != NULL) {
+        if ((len = strlen(name) + 1) & align)
+            len += (align + 1) - (len & align);
+    } else {
+        len = 0;
+    }
+
+    event = kmem_zalloc(sizeof (inotify_kevent_t) + len, KM_SLEEP);
+    event->ine_event.wd = wd;
+    event->ine_event.mask = (uint32_t)mask;
+    event->ine_event.cookie = cookie;
+    event->ine_event.len = len;
+
+    if (name != NULL)
+        strcpy(event->ine_event.name, name);
+
+    if (tail != NULL) {
+        tail->ine_next = event;
+    } else {
+        VERIFY(state->ins_head == NULL);
+        state->ins_head = event;
+        cv_broadcast(&state->ins_cv);
+    }
+
+    state->ins_tail = event;
+    state->ins_nevents++;
+    state->ins_size += sizeof (inotify_kevent_t) + len;
+
+    if ((watch->inw_mask & IN_ONESHOT) && !watch->inw_fired) {
+        /*
+         * If this is a one-shot, we need to remove the watch.  (Note
+         * that this will recurse back into inotify_watch_event() to
+         * fire the IN_IGNORED event -- but with "removal" set.)
+         */
+        watch->inw_fired = 1;
+        inotify_watch_remove(state, watch);
+    }
+
+    if (removal)
+        return;
+
+    mutex_exit(&state->ins_lock);
+    pollwakeup(&state->ins_pollhd, POLLRDNORM | POLLIN);
+}
+
+/*
+ * Destroy a watch.  By the time we're in here, the watch must have exactly
+ * one reference.
+ */
+static void
+inotify_watch_destroy(inotify_watch_t *watch)
+{
+    VERIFY(MUTEX_HELD(&watch->inw_lock));
+
+    if (watch->inw_name != NULL)
+        kmem_free(watch->inw_name, strlen(watch->inw_name) + 1);
+
+    kmem_free(watch, sizeof (inotify_watch_t));
+}
+
+/*
+ * Zombify a watch.  By the time we come in here, it must be true that the
+ * watch has already been fem_uninstall()'d -- the only reference should be
+ * in the state's data structure.  If we can get away with freeing it, we'll
+ * do that -- but if the reference count is greater than one due to an active
+ * vnode operation, we'll put this watch on the zombie list on the state
+ * structure.
+ */
+static void
+inotify_watch_zombify(inotify_watch_t *watch)
+{
+    inotify_state_t *state = watch->inw_state;
+
+    VERIFY(MUTEX_HELD(&state->ins_lock));
+    VERIFY(!watch->inw_zombie);
+
+    watch->inw_zombie = 1;
+
+    if (watch->inw_parent != NULL) {
+        inotify_watch_release(watch->inw_parent);
+    } else {
+        avl_remove(&state->ins_byvp, watch);
+        avl_remove(&state->ins_bywd, watch);
+        vmem_free(state->ins_wds, (void *)(uintptr_t)watch->inw_wd, 1);
+        watch->inw_wd = -1;
+    }
+
+    mutex_enter(&watch->inw_lock);
+
+    if (watch->inw_refcnt == 1) {
+        /*
+         * There are no operations in flight and there is no way
+         * for anyone to discover this watch -- we can destroy it.
+         */
+        inotify_watch_destroy(watch);
+    } else {
+        /*
+         * There are operations in flight; we will need to enqueue
+         * this for later destruction.
+         */
+        watch->inw_parent = state->ins_zombies;
+        state->ins_zombies = watch;
+        mutex_exit(&watch->inw_lock);
+    }
+}
+
+static inotify_watch_t *
+inotify_watch_add(inotify_state_t *state, inotify_watch_t *parent,
+    const char *name, vnode_t *vp, uint32_t mask)
+{
+    inotify_watch_t *watch;
+    int err;
+
+    VERIFY(MUTEX_HELD(&state->ins_lock));
+
+    watch = kmem_zalloc(sizeof (inotify_watch_t), KM_SLEEP);
+
+    watch->inw_vp = vp;
+    watch->inw_mask = mask;
+    watch->inw_state = state;
+    watch->inw_refcnt = 1;
+
+    if (parent == NULL) {
+        watch->inw_wd = (int)(uintptr_t)vmem_alloc(state->ins_wds,
+            1, VM_BESTFIT | VM_SLEEP);
+        avl_add(&state->ins_byvp, watch);
+        avl_add(&state->ins_bywd, watch);
+
+        avl_create(&watch->inw_children,
+            (int(*)(const void *, const void *))inotify_watch_cmpvp,
+            sizeof (inotify_watch_t),
+            offsetof(inotify_watch_t, inw_byvp));
+    } else {
+        VERIFY(name != NULL);
+        inotify_watch_hold(parent);
+        watch->inw_mask &= IN_CHILD_EVENTS;
+        watch->inw_parent = parent;
+        watch->inw_name = kmem_alloc(strlen(name) + 1, KM_SLEEP);
+        strcpy(watch->inw_name, name);
+
+        avl_add(&parent->inw_children, watch);
+    }
+
+    /*
+     * Add our monitor to the vnode.  We must not have the watch lock held
+     * when we do this, as it will immediately hold our watch.
+     */
+    err = fem_install(vp, inotify_femp, watch, OPARGUNIQ,
+        (void (*)(void *))inotify_watch_hold,
+        (void (*)(void *))inotify_watch_release);
+
+    VERIFY(err == 0);
+
+    return (watch);
+}
+
+/*
+ * Remove a (non-child) watch.  This is called from either synchronous context
+ * via inotify_rm_watch() or monitor context via either a vnevent or a
+ * one-shot.
+ */
+static void
+inotify_watch_remove(inotify_state_t *state, inotify_watch_t *watch)
+{
+    inotify_watch_t *child;
+    int err;
+
+    VERIFY(MUTEX_HELD(&state->ins_lock));
+    VERIFY(watch->inw_parent == NULL);
+
+    err = fem_uninstall(watch->inw_vp, inotify_femp, watch);
+    VERIFY(err == 0);
+
+    /*
+     * If we have children, we're going to remove them all and set them
+     * all to be zombies.
+     */
+    while ((child = avl_first(&watch->inw_children)) != NULL) {
+        VERIFY(child->inw_parent == watch);
+        avl_remove(&watch->inw_children, child);
+
+        err = fem_uninstall(child->inw_vp, inotify_femp, child);
+        VERIFY(err == 0);
+
+        /*
+         * If this child watch has been orphaned, remove it from the
+         * state's list of orphans.
+         */
+        if (watch->inw_orphaned)
+            list_remove(&state->ins_orphans, watch);
+
+        VN_RELE(child->inw_vp);
+
+        /*
+         * We're down (or should be down) to a single reference to
+         * this child watch; it's safe to zombify it.
+         */
+        inotify_watch_zombify(child);
+    }
+
+    inotify_watch_event(watch, IN_IGNORED | IN_REMOVAL, NULL);
+    VN_RELE(watch->inw_vp);
+
+    /*
+     * It's now safe to zombify the watch -- we know that the only reference
+     * can come from operations in flight.
+     */
+    inotify_watch_zombify(watch);
+}
+
+/*
+ * Delete a watch.  Should only be called from VOP context.
+ */
+static void
+inotify_watch_delete(inotify_watch_t *watch, uint32_t event)
+{
+    inotify_state_t *state = watch->inw_state;
+    inotify_watch_t cmp = { .inw_vp = watch->inw_vp }, *parent;
+    int err;
+
+    if (event != IN_DELETE_SELF && !(watch->inw_mask & IN_CHILD_EVENTS))
+        return;
+
+    mutex_enter(&state->ins_lock);
+
+    if (watch->inw_zombie) {
+        mutex_exit(&state->ins_lock);
+        return;
+    }
+
+    if ((parent = watch->inw_parent) == NULL) {
+        if (event == IN_DELETE_SELF) {
+            /*
+             * If we're here because we're being deleted and we
+             * are not a child watch, we need to delete the entire
+             * watch, children and all.
+             */
+            inotify_watch_remove(state, watch);
+        }
+
+        mutex_exit(&state->ins_lock);
+        return;
+    } else {
+        if (event == IN_DELETE_SELF &&
+            !(parent->inw_mask & IN_EXCL_UNLINK)) {
+            /*
+             * This is a child watch for a file that is being
+             * removed and IN_EXCL_UNLINK has not been specified;
+             * indicate that it is orphaned and add it to the list
+             * of orphans.  (This list will be checked by the
+             * cleaning cyclic to determine when the watch has
+             * become the only hold on the vnode, at which point
+             * the watch can be zombified.)  Note that we check
+             * if the watch is orphaned before we orphan it:  hard
+             * links make it possible for VE_REMOVE to be called
+             * multiple times on the same vnode. (!)
+             */
+            if (!watch->inw_orphaned) {
+                watch->inw_orphaned = 1;
+                list_insert_head(&state->ins_orphans, watch);
+            }
+
+            mutex_exit(&state->ins_lock);
+            return;
+        }
+
+        if (watch->inw_orphaned) {
+            /*
+             * If we're here, a file was orphaned and then later
+             * moved -- which almost certainly means that hard
+             * links are on the scene.  We choose the orphan over
+             * the move because we don't want to spuriously
+             * drop events if we can avoid it.
+             */
+            list_remove(&state->ins_orphans, watch);
+        }
+    }
+
+    if (avl_find(&parent->inw_children, &cmp, NULL) == NULL) {
+        /*
+         * This watch has already been deleted from the parent.
+         */
+        mutex_exit(&state->ins_lock);
+        return;
+    }
+
+    avl_remove(&parent->inw_children, watch);
+    err = fem_uninstall(watch->inw_vp, inotify_femp, watch);
+    VERIFY(err == 0);
+
+    VN_RELE(watch->inw_vp);
+
+    /*
+     * It's now safe to zombify the watch -- which won't actually delete
+     * it as we know that the reference count is greater than 1.
+     */
+    inotify_watch_zombify(watch);
+    mutex_exit(&state->ins_lock);
+}
+
+/*
+ * Insert a new child watch.  Should only be called from VOP context when
+ * a child is created in a watched directory.
+ */
+static void
+inotify_watch_insert(inotify_watch_t *watch, vnode_t *vp, char *name)
+{
+    inotify_state_t *state = watch->inw_state;
+    inotify_watch_t cmp = { .inw_vp = vp };
+
+    if (!(watch->inw_mask & IN_CHILD_EVENTS))
+        return;
+
+    mutex_enter(&state->ins_lock);
+
+    if (watch->inw_zombie || watch->inw_parent != NULL || vp == NULL) {
+        mutex_exit(&state->ins_lock);
+        return;
+    }
+
+    if (avl_find(&watch->inw_children, &cmp, NULL) != NULL) {
+        mutex_exit(&state->ins_lock);
+        return;
+    }
+
+    VN_HOLD(vp);
+    watch = inotify_watch_add(state, watch, name, vp, watch->inw_mask);
+    VERIFY(watch != NULL);
+
+    mutex_exit(&state->ins_lock);
+}
+
+
+static int
+inotify_add_watch(inotify_state_t *state, vnode_t *vp, uint32_t mask,
+    int32_t *wdp)
+{
+    inotify_watch_t *watch, cmp = { .inw_vp = vp };
+    uint32_t set;
+
+    set = (mask & (IN_ALL_EVENTS | IN_MODIFIERS)) | IN_UNMASKABLE;
+
+    /*
+     * Lookup our vnode to determine if we already have a watch on it.
+     */
+    mutex_enter(&state->ins_lock);
+
+    if ((watch = avl_find(&state->ins_byvp, &cmp, NULL)) == NULL) {
+        /*
+         * We don't have this watch; allocate a new one, provided that
+         * we have fewer than our limit.
+         */
+        if (avl_numnodes(&state->ins_bywd) >= state->ins_maxwatches) {
+            mutex_exit(&state->ins_lock);
+            return (ENOSPC);
+        }
+
+        VN_HOLD(vp);
+        watch = inotify_watch_add(state, NULL, NULL, vp, set);
+        *wdp = watch->inw_wd;
+        mutex_exit(&state->ins_lock);
+
+        return (0);
+    }
+
+    VERIFY(!watch->inw_zombie);
+
+    if (!(mask & IN_MASK_ADD)) {
+        /*
+         * Note that if we're resetting our event mask and we're
+         * transitioning from an event mask that includes child events
+         * to one that doesn't, there will be potentially some stale
+         * child watches.  This is basically fine:  they won't fire,
+         * and they will correctly be removed when the watch is
+         * removed.
+         */
+        watch->inw_mask = 0;
+    }
+
+    watch->inw_mask |= set;
+
+    *wdp = watch->inw_wd;
+
+    mutex_exit(&state->ins_lock);
+
+    return (0);
+}
+
+static int
+inotify_add_child(inotify_state_t *state, vnode_t *vp, char *name)
+{
+    inotify_watch_t *watch, cmp = { .inw_vp = vp };
+    vnode_t *cvp;
+    int err;
+
+    /*
+     * Verify that the specified child doesn't have a directory component
+     * within it.
+     */
+    if (strchr(name, '/') != NULL)
+        return (EINVAL);
+
+    /*
+     * Lookup the underlying file.  Note that this will succeed even if
+     * we don't have permissions to actually read the file.
+     */
+    if ((err = lookupnameat(name,
+        UIO_SYSSPACE, NO_FOLLOW, NULL, &cvp, vp)) != 0) {
+        return (err);
+    }
+
+    /*
+     * Use our vnode to find our watch, and then add our child watch to it.
+     */
+    mutex_enter(&state->ins_lock);
+
+    if ((watch = avl_find(&state->ins_byvp, &cmp, NULL)) == NULL) {
+        /*
+         * This is unexpected -- it means that we don't have the
+         * watch that we thought we had.
+         */
+        mutex_exit(&state->ins_lock);
+        VN_RELE(cvp);
+        return (ENXIO);
+    }
+
+    /*
+     * Now lookup the child vnode in the watch; we'll only add it if it
+     * isn't already there.
+     */
+    cmp.inw_vp = cvp;
+
+    if (avl_find(&watch->inw_children, &cmp, NULL) != NULL) {
+        mutex_exit(&state->ins_lock);
+        VN_RELE(cvp);
+        return (0);
+    }
+
+    watch = inotify_watch_add(state, watch, name, cvp, watch->inw_mask);
+    VERIFY(watch != NULL);
+    mutex_exit(&state->ins_lock);
+
+    return (0);
+}
+
+static int
+inotify_rm_watch(inotify_state_t *state, int32_t wd)
+{
+    inotify_watch_t *watch, cmp = { .inw_wd = wd };
+
+    mutex_enter(&state->ins_lock);
+
+    if ((watch = avl_find(&state->ins_bywd, &cmp, NULL)) == NULL) {
+        mutex_exit(&state->ins_lock);
+        return (EINVAL);
+    }
+
+    inotify_watch_remove(state, watch);
+    mutex_exit(&state->ins_lock);
+
+    return (0);
+}
+
+static int
+inotify_activate(inotify_state_t *state, int32_t wd)
+{
+    inotify_watch_t *watch, cmp = { .inw_wd = wd };
+
+    mutex_enter(&state->ins_lock);
+
+    if ((watch = avl_find(&state->ins_bywd, &cmp, NULL)) == NULL) {
+        mutex_exit(&state->ins_lock);
+        return (EINVAL);
+    }
+
+    watch->inw_active = 1;
+
+    mutex_exit(&state->ins_lock);
+
+    return (0);
+}
+
+/*
+ * Called periodically as a cyclic to process the orphans and zombies.
+ */
+static void
+inotify_clean(void *arg)
+{
+    inotify_state_t *state = arg;
+    inotify_watch_t *watch, *parent, *next, **prev;
+    int err;
+
+    mutex_enter(&state->ins_lock);
+
+    for (watch = list_head(&state->ins_orphans);
+        watch != NULL; watch = next) {
+        next = list_next(&state->ins_orphans, watch);
+
+        VERIFY(!watch->inw_zombie);
+        VERIFY((parent = watch->inw_parent) != NULL);
+
+        if (watch->inw_vp->v_count > 1)
+            continue;
+
+        avl_remove(&parent->inw_children, watch);
+        err = fem_uninstall(watch->inw_vp, inotify_femp, watch);
+        VERIFY(err == 0);
+
+        list_remove(&state->ins_orphans, watch);
+
+        VN_RELE(watch->inw_vp);
+        inotify_watch_zombify(watch);
+    }
+
+    prev = &state->ins_zombies;
+
+    while ((watch = *prev) != NULL) {
+        mutex_enter(&watch->inw_lock);
+
+        if (watch->inw_refcnt == 1) {
+            *prev = watch->inw_parent;
+            inotify_watch_destroy(watch);
+            continue;
+        }
+
+        prev = &watch->inw_parent;
+        mutex_exit(&watch->inw_lock);
+    }
+
+    mutex_exit(&state->ins_lock);
+}
+
+/*ARGSUSED*/
+static int
+inotify_open(dev_t *devp, int flag, int otyp, cred_t *cred_p)
+{
+    inotify_state_t *state;
+    major_t major = getemajor(*devp);
+    minor_t minor = getminor(*devp);
+    int instances = 0;
+    cyc_handler_t hdlr;
+    cyc_time_t when;
+    char c[64];
+
+    if (minor != INOTIFYMNRN_INOTIFY)
+        return (ENXIO);
+
+    mutex_enter(&inotify_lock);
+
+    for (state = inotify_state; state != NULL; state = state->ins_next) {
+        if (state->ins_cred == cred_p)
+            instances++;
+    }
+
+    if (instances >= inotify_maxinstances) {
+        mutex_exit(&inotify_lock);
+        return (EMFILE);
+    }
+
+    minor = (minor_t)(uintptr_t)vmem_alloc(inotify_minor, 1,
+        VM_BESTFIT | VM_SLEEP);
+
+    if (ddi_soft_state_zalloc(inotify_softstate, minor) != DDI_SUCCESS) {
+        vmem_free(inotify_minor, (void *)(uintptr_t)minor, 1);
+        mutex_exit(&inotify_lock);
+        return (NULL);
+    }
+
+    state = ddi_get_soft_state(inotify_softstate, minor);
+    *devp = makedevice(major, minor);
+
+    crhold(cred_p);
+    state->ins_cred = cred_p;
+    state->ins_next = inotify_state;
+    inotify_state = state;
+
+    (void) snprintf(c, sizeof (c), "inotify_watchid_%d", minor);
+    state->ins_wds = vmem_create(c, (void *)1, UINT32_MAX, 1,
+        NULL, NULL, NULL, 0, VM_SLEEP | VMC_IDENTIFIER);
+
+    avl_create(&state->ins_bywd,
+        (int(*)(const void *, const void *))inotify_watch_cmpwd,
+        sizeof (inotify_watch_t),
+        offsetof(inotify_watch_t, inw_bywd));
+
+    avl_create(&state->ins_byvp,
+        (int(*)(const void *, const void *))inotify_watch_cmpvp,
+        sizeof (inotify_watch_t),
+        offsetof(inotify_watch_t, inw_byvp));
+
+    list_create(&state->ins_orphans, sizeof (inotify_watch_t),
+        offsetof(inotify_watch_t, inw_orphan));
+
+    state->ins_maxwatches = inotify_maxwatches;
+    state->ins_maxevents = inotify_maxevents;
+
+    mutex_exit(&inotify_lock);
+
+    mutex_enter(&cpu_lock);
+
+    hdlr.cyh_func = inotify_clean;
+    hdlr.cyh_level = CY_LOW_LEVEL;
+    hdlr.cyh_arg = state;
+
+    when.cyt_when = 0;
+    when.cyt_interval = NANOSEC;
+
+    state->ins_cleaner = cyclic_add(&hdlr, &when);
+    mutex_exit(&cpu_lock);
+
+    return (0);
+}
+
+/*ARGSUSED*/
+static int
+inotify_read(dev_t dev, uio_t *uio, cred_t *cr)
+{
+    inotify_state_t *state;
+    inotify_kevent_t *event;
+    minor_t minor = getminor(dev);
+    int err = 0, nevents = 0;
+    size_t len;
+
+    state = ddi_get_soft_state(inotify_softstate, minor);
+
+    mutex_enter(&state->ins_lock);
+
+    while (state->ins_head == NULL) {
+        if (uio->uio_fmode & (FNDELAY|FNONBLOCK)) {
+            mutex_exit(&state->ins_lock);
+            return (EAGAIN);
+        }
+
+        if (!cv_wait_sig_swap(&state->ins_cv, &state->ins_lock)) {
+            mutex_exit(&state->ins_lock);
+            return (EINTR);
+        }
+    }
+
+    /*
+     * We have events and we have our lock; return as many as we can.
+     */
+    while ((event = state->ins_head) != NULL) {
+        len = sizeof (event->ine_event) + event->ine_event.len;
+
+        if (uio->uio_resid < len) {
+            if (nevents == 0)
+                err = EINVAL;
+            break;
+        }
+
+        nevents++;
+
+        if ((err = uiomove(&event->ine_event, len, UIO_READ, uio)) != 0)
+            break;
+
+        VERIFY(state->ins_nevents > 0);
+        state->ins_nevents--;
+
+        VERIFY(state->ins_size > 0);
+        state->ins_size -= INOTIFY_EVENT_LENGTH(event);
+
+        if ((state->ins_head = event->ine_next) == NULL) {
+            VERIFY(event == state->ins_tail);
+            VERIFY(state->ins_nevents == 0);
+            state->ins_tail = NULL;
+        }
+
+        kmem_free(event, INOTIFY_EVENT_LENGTH(event));
+    }
+
+    mutex_exit(&state->ins_lock);
+
+    return (err);
+}
+
+/*ARGSUSED*/
+static int
+inotify_poll(dev_t dev, short events, int anyyet, short *reventsp,
+    struct pollhead **phpp)
+{
+    inotify_state_t *state;
+    minor_t minor = getminor(dev);
+
+    state = ddi_get_soft_state(inotify_softstate, minor);
+
+    mutex_enter(&state->ins_lock);
+
+    if (state->ins_head != NULL) {
+        *reventsp = POLLRDNORM | POLLIN;
+    } else {
+        *reventsp = 0;
+
+        if (!anyyet)
+            *phpp = &state->ins_pollhd;
+    }
+
+    mutex_exit(&state->ins_lock);
+
+    return (0);
+}
+
+/*ARGSUSED*/
+static int
+inotify_ioctl(dev_t dev, int cmd, intptr_t arg, int md, cred_t *cr, int *rv)
+{
+    inotify_state_t *state;
+    minor_t minor = getminor(dev);
+    file_t *fp;
+    int rval;
+
+    state = ddi_get_soft_state(inotify_softstate, minor);
+
+    switch (cmd) {
+    case INOTIFYIOC_ADD_WATCH: {
+        inotify_addwatch_t addwatch;
+        file_t *fp;
+
+        if (copyin((void *)arg, &addwatch, sizeof (addwatch)) != 0)
+            return (EFAULT);
+
+        if ((fp = getf(addwatch.inaw_fd)) == NULL)
+            return (EBADF);
+
+        rval = inotify_add_watch(state, fp->f_vnode,
+            addwatch.inaw_mask, rv);
+
+        releasef(addwatch.inaw_fd);
+        return (rval);
+    }
+
+    case INOTIFYIOC_ADD_CHILD: {
+        inotify_addchild_t addchild;
+        char name[MAXPATHLEN];
+
+        if (copyin((void *)arg, &addchild, sizeof (addchild)) != 0)
+            return (EFAULT);
+
+        if (copyinstr(addchild.inac_name, name, MAXPATHLEN, NULL) != 0)
+            return (EFAULT);
+
+        if ((fp = getf(addchild.inac_fd)) == NULL)
+            return (EBADF);
+
+        rval = inotify_add_child(state, fp->f_vnode, name);
+
+        releasef(addchild.inac_fd);
+        return (rval);
+    }
+
+    case INOTIFYIOC_RM_WATCH:
+        return (inotify_rm_watch(state, arg));
+
+    case INOTIFYIOC_ACTIVATE:
+        return (inotify_activate(state, arg));
+
+    case FIONREAD:
+        mutex_enter(&state->ins_lock);
+        *rv = state->ins_size;
+        mutex_exit(&state->ins_lock);
+
+        return (0);
+
+    default:
+        break;
+    }
+
+    return (ENOTTY);
+}
+
+/*ARGSUSED*/
+static int
+inotify_close(dev_t dev, int flag, int otyp, cred_t *cred_p)
+{
+    inotify_state_t *state, **sp;
+    inotify_watch_t *watch, *zombies;
+    inotify_kevent_t *event;
+    minor_t minor = getminor(dev);
+
+    state = ddi_get_soft_state(inotify_softstate, minor);
+
+    mutex_enter(&state->ins_lock);
+
+    /*
+     * First, destroy all of our watches.
+     */
+    while ((watch = avl_first(&state->ins_bywd)) != NULL)
+        inotify_watch_remove(state, watch);
+
+    /*
+     * And now destroy our event queue.
+     */
+    while ((event = state->ins_head) != NULL) {
+        state->ins_head = event->ine_next;
+        kmem_free(event, INOTIFY_EVENT_LENGTH(event));
+    }
+
+    zombies = state->ins_zombies;
+    state->ins_zombies = NULL;
+    mutex_exit(&state->ins_lock);
+
+    /*
+     * Now that our state lock is dropped, we can synchronously wait on
+     * any zombies.
+     */
+    while ((watch = zombies) != NULL) {
+        zombies = zombies->inw_parent;
+
+        mutex_enter(&watch->inw_lock);
+
+        while (watch->inw_refcnt > 1)
+            cv_wait(&watch->inw_cv, &watch->inw_lock);
+
+        inotify_watch_destroy(watch);
+    }
+
+    mutex_enter(&cpu_lock);
+    cyclic_remove(state->ins_cleaner);
+    mutex_exit(&cpu_lock);
+
+    mutex_enter(&inotify_lock);
+
+    /*
+     * Remove our state from our global list, and release our hold on
+     * the cred.
+     */
+    for (sp = &inotify_state; *sp != state; sp = &((*sp)->ins_next))
+        VERIFY(*sp != NULL);
+
+    *sp = (*sp)->ins_next;
+    crfree(state->ins_cred);
+
+    ddi_soft_state_free(inotify_softstate, minor);
+    vmem_free(inotify_minor, (void *)(uintptr_t)minor, 1);
+
+    mutex_exit(&inotify_lock);
+
+    return (0);
+}
+
+/*ARGSUSED*/
+static int
+inotify_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
+{
+    mutex_enter(&inotify_lock);
+
+    if (ddi_soft_state_init(&inotify_softstate,
+        sizeof (inotify_state_t), 0) != 0) {
+        cmn_err(CE_NOTE, "/dev/inotify failed to create soft state");
+        mutex_exit(&inotify_lock);
+        return (DDI_FAILURE);
+    }
+
+    if (ddi_create_minor_node(devi, "inotify", S_IFCHR,
+        INOTIFYMNRN_INOTIFY, DDI_PSEUDO, NULL) == DDI_FAILURE) {
+        cmn_err(CE_NOTE, "/dev/inotify couldn't create minor node");
+        ddi_soft_state_fini(&inotify_softstate);
+        mutex_exit(&inotify_lock);
+        return (DDI_FAILURE);
+    }
+
+    if (fem_create("inotify_fem",
+        inotify_vnodesrc_template, &inotify_femp) != 0) {
+        cmn_err(CE_NOTE, "/dev/inotify couldn't create FEM state");
+        ddi_remove_minor_node(devi, NULL);
+        ddi_soft_state_fini(&inotify_softstate);
+        mutex_exit(&inotify_lock);
+        return (DDI_FAILURE);
+    }
+
+    ddi_report_dev(devi);
+    inotify_devi = devi;
+
+    inotify_minor = vmem_create("inotify_minor", (void *)INOTIFYMNRN_CLONE,
+        UINT32_MAX - INOTIFYMNRN_CLONE, 1, NULL, NULL, NULL, 0,
+        VM_SLEEP | VMC_IDENTIFIER);
+
+    mutex_exit(&inotify_lock);
+
+    return (DDI_SUCCESS);
+}
+
+/*ARGSUSED*/
+static int
+inotify_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
+{
+    switch (cmd) {
+    case DDI_DETACH:
+        break;
+
+    case DDI_SUSPEND:
+        return (DDI_SUCCESS);
+
+    default:
+        return (DDI_FAILURE);
+    }
+
+    mutex_enter(&inotify_lock);
+    fem_free(inotify_femp);
+    vmem_destroy(inotify_minor);
+
+    ddi_remove_minor_node(inotify_devi, NULL);
+    inotify_devi = NULL;
+
+    ddi_soft_state_fini(&inotify_softstate);
+    mutex_exit(&inotify_lock);
+
+    return (DDI_SUCCESS);
+}
+
+/*ARGSUSED*/
+static int
+inotify_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
+{
+    int error;
+
+    switch (infocmd) {
+    case DDI_INFO_DEVT2DEVINFO:
+        *result = (void *)inotify_devi;
+        error = DDI_SUCCESS;
+        break;
+    case DDI_INFO_DEVT2INSTANCE:
+        *result = (void *)0;
+        error = DDI_SUCCESS;
+        break;
+    default:
+        error = DDI_FAILURE;
+    }
+    return (error);
+}
+
+static struct cb_ops inotify_cb_ops = {
+    inotify_open,        /* open */
+    inotify_close,        /* close */
+    nulldev,        /* strategy */
+    nulldev,        /* print */
+    nodev,            /* dump */
+    inotify_read,        /* read */
+    nodev,            /* write */
+    inotify_ioctl,        /* ioctl */
+    nodev,            /* devmap */
+    nodev,            /* mmap */
+    nodev,            /* segmap */
+    inotify_poll,        /* poll */
+    ddi_prop_op,        /* cb_prop_op */
+    0,            /* streamtab  */
+    D_NEW | D_MP        /* Driver compatibility flag */
+};
+
+static struct dev_ops inotify_ops = {
+    DEVO_REV,        /* devo_rev */
+    0,            /* refcnt */
+    inotify_info,        /* get_dev_info */
+    nulldev,        /* identify */
+    nulldev,        /* probe */
+    inotify_attach,        /* attach */
+    inotify_detach,        /* detach */
+    nodev,            /* reset */
+    &inotify_cb_ops,    /* driver operations */
+    NULL,            /* bus operations */
+    nodev,            /* dev power */
+    ddi_quiesce_not_needed,    /* quiesce */
+};
+
+static struct modldrv modldrv = {
+    &mod_driverops,        /* module type (this is a pseudo driver) */
+    "inotify support",    /* name of module */
+    &inotify_ops,        /* driver ops */
+};
+
+static struct modlinkage modlinkage = {
+    MODREV_1,
+    (void *)&modldrv,
+    NULL
+};
+
+int
+_init(void)
+{
+    return (mod_install(&modlinkage));
+}
+
+int
+_info(struct modinfo *modinfop)
+{
+    return (mod_info(&modlinkage, modinfop));
+}
+
+int
+_fini(void)
+{
+    return (mod_remove(&modlinkage));
+}
diff --git a/usr/src/uts/common/io/inotify.conf b/usr/src/uts/common/io/inotify.conf
new file mode 100644
index 0000000000..ce9da6180f
--- /dev/null
+++ b/usr/src/uts/common/io/inotify.conf
@@ -0,0 +1,16 @@
+#
+# 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 of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright (c) 2014 Joyent, Inc.  All rights reserved.
+#
+
+name="inotify" parent="pseudo" instance=0;
diff --git a/usr/src/uts/common/sys/Makefile b/usr/src/uts/common/sys/Makefile
index 08b2488b97..ce449efd20 100644
--- a/usr/src/uts/common/sys/Makefile
+++ b/usr/src/uts/common/sys/Makefile
@@ -271,6 +271,7 @@ CHKHDRS=            \
     idmap.h         \
     ieeefp.h        \
     id_space.h        \
+    inotify.h        \
     instance.h        \
     int_const.h        \
     int_fmtio.h        \
diff --git a/usr/src/uts/common/sys/inotify.h b/usr/src/uts/common/sys/inotify.h
new file mode 100644
index 0000000000..8acc1a7280
--- /dev/null
+++ b/usr/src/uts/common/sys/inotify.h
@@ -0,0 +1,153 @@
+/*
+ * 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 of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright (c) 2014 Joyent, Inc.  All rights reserved.
+ */
+
+/*
+ * Header file to support for the inotify facility.  Note that this facility
+ * is designed to be binary compatible with the Linux inotify facility; values
+ * for constants here should therefore exactly match those found in Linux, and
+ * this facility shouldn't be extended independently of Linux.
+ */
+
+#ifndef _SYS_INOTIFY_H
+#define    _SYS_INOTIFY_H
+
+#include <sys/types.h>
+
+#ifdef    __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Events that can be explicitly requested on any inotify watch.
+ */
+#define    IN_ACCESS        0x00000001
+#define    IN_MODIFY        0x00000002
+#define    IN_ATTRIB        0x00000004
+#define    IN_CLOSE_WRITE        0x00000008
+#define    IN_CLOSE_NOWRITE    0x00000010
+#define    IN_OPEN            0x00000020
+#define    IN_MOVED_FROM        0x00000040
+#define    IN_MOVED_TO        0x00000080
+#define    IN_CREATE        0x00000100
+#define    IN_DELETE        0x00000200
+#define    IN_DELETE_SELF        0x00000400
+#define    IN_MOVE_SELF        0x00000800
+
+/*
+ * Events that can be sent to an inotify watch -- requested or not.
+ */
+#define    IN_UNMOUNT        0x00002000
+#define    IN_Q_OVERFLOW        0x00004000
+#define    IN_IGNORED        0x00008000
+
+/*
+ * Flags that can modify an inotify event.
+ */
+#define    IN_ONLYDIR        0x01000000
+#define    IN_DONT_FOLLOW        0x02000000
+#define    IN_EXCL_UNLINK        0x04000000
+#define    IN_MASK_ADD        0x20000000
+#define    IN_ISDIR        0x40000000
+#define    IN_ONESHOT        0x80000000
+
+/*
+ * Helpful constants.
+ */
+#define    IN_CLOSE        (IN_CLOSE_WRITE | IN_CLOSE_NOWRITE)
+#define    IN_MOVE            (IN_MOVED_FROM | IN_MOVED_TO)
+#define    IN_ALL_EVENTS        \
+    (IN_ACCESS | IN_MODIFY | IN_ATTRIB | IN_CLOSE_WRITE | \
+    IN_CLOSE_NOWRITE | IN_OPEN | IN_MOVED_FROM | IN_MOVED_TO | \
+    IN_DELETE | IN_CREATE | IN_DELETE_SELF | IN_MOVE_SELF)
+
+#define    IN_CHILD_EVENTS        \
+    (IN_ACCESS | IN_MODIFY | IN_ATTRIB | IN_CLOSE_WRITE | \
+    IN_CLOSE_NOWRITE | IN_MODIFY | IN_OPEN)
+
+/*
+ * To assure binary compatibility with Linux, these values are fixed at their
+ * Linux equivalents, not their native ones.
+ */
+#define    IN_CLOEXEC        02000000        /* LX_O_CLOEXEC */
+#define    IN_NONBLOCK        04000            /* LX_O_NONBLOCK */
+
+struct inotify_event {
+    int32_t        wd;        /* watch descriptor */
+    uint32_t    mask;        /* mask of events */
+    uint32_t    cookie;        /* event association cookie, if any */
+    uint32_t    len;        /* size of name field */
+    char        name[];        /* optional NUL-terminated name */
+};
+
+/*
+ * These ioctl values are specific to the native implementation; applications
+ * shouldn't be using them directly, and they should therefore be safe to
+ * change without breaking apps.
+ */
+#define    INOTIFYIOC        (('i' << 24) | ('n' << 16) | ('y' << 8))
+#define    INOTIFYIOC_ADD_WATCH    (INOTIFYIOC | 1)    /* add watch */
+#define    INOTIFYIOC_RM_WATCH    (INOTIFYIOC | 2)    /* remove watch */
+#define    INOTIFYIOC_ADD_CHILD    (INOTIFYIOC | 3)    /* add child watch */
+#define    INOTIFYIOC_ACTIVATE    (INOTIFYIOC | 4)    /* activate watch */
+
+#ifndef _LP64
+#ifndef _LITTLE_ENDIAN
+#define    INOTIFY_PTR(type, name)    uint32_t name##pad; type *name
+#else
+#define    INOTIFY_PTR(type, name)    type *name; uint32_t name##pad
+#endif
+#else
+#define    INOTIFY_PTR(type, name)    type *name
+#endif
+
+typedef struct inotify_addwatch {
+    int inaw_fd;            /* open fd for object */
+    uint32_t inaw_mask;        /* desired mask */
+} inotify_addwatch_t;
+
+typedef struct inotify_addchild {
+    INOTIFY_PTR(char, inac_name);    /* pointer to name */
+    int inac_fd;            /* open fd for parent */
+} inotify_addchild_t;
+
+#ifndef _KERNEL
+
+extern int inotify_init(void);
+extern int inotify_init1(int);
+extern int inotify_add_watch(int, const char *, uint32_t);
+extern int inotify_rm_watch(int, int);
+
+#else
+
+#define    IN_UNMASKABLE \
+    (IN_UNMOUNT | IN_Q_OVERFLOW | IN_IGNORED | IN_ISDIR)
+
+#define    IN_MODIFIERS \
+    (IN_EXCL_UNLINK | IN_ONESHOT)
+
+#define    IN_FLAGS \
+    (IN_ONLYDIR | IN_DONT_FOLLOW | IN_MASK_ADD)
+
+#define    IN_REMOVAL        (1ULL << 32)
+#define    INOTIFYMNRN_INOTIFY    0
+#define    INOTIFYMNRN_CLONE    1
+
+#endif /* _KERNEL */
+
+#ifdef    __cplusplus
+}
+#endif
+
+#endif    /* _SYS_INOTIFY_H */
diff --git a/usr/src/uts/common/sys/vnode.h b/usr/src/uts/common/sys/vnode.h
index af9516fe52..c1c12a084e 100644
--- a/usr/src/uts/common/sys/vnode.h
+++ b/usr/src/uts/common/sys/vnode.h
@@ -21,7 +21,7 @@
 /*
  * Copyright (c) 1988, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2013, Joyent, Inc. All rights reserved.
+ * Copyright (c) 2014, Joyent, Inc. All rights reserved.
  */
 /*    Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T    */
@@ -735,7 +735,8 @@ typedef enum vnevent    {
     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_TRUNCATE = 9        /* Truncate */
+    VE_TRUNCATE = 9,    /* Truncate */
+    VE_RENAME_SRC_DIR = 10    /* Rename with vnode as source dir */
 } vnevent_t;
 /*
@@ -1290,7 +1291,8 @@ void    vnevent_remove(vnode_t *, vnode_t *, char *, caller_context_t *);
 void    vnevent_rmdir(vnode_t *, vnode_t *, char *, caller_context_t *);
 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_rename_dest_dir(vnode_t *, vnode_t *, char *,
+    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 *);
diff --git a/usr/src/uts/intel/Makefile.intel b/usr/src/uts/intel/Makefile.intel
index 0abcdcb8c4..e534120477 100644
--- a/usr/src/uts/intel/Makefile.intel
+++ b/usr/src/uts/intel/Makefile.intel
@@ -251,6 +251,7 @@ DRV_KMODS    += i8042
 DRV_KMODS    += i915
 DRV_KMODS    += icmp
 DRV_KMODS    += icmp6
+DRV_KMODS    += inotify
 DRV_KMODS    += intel_nb5000
 DRV_KMODS    += intel_nhm
 DRV_KMODS    += ip
diff --git a/usr/src/uts/intel/inotify/Makefile b/usr/src/uts/intel/inotify/Makefile
new file mode 100644
index 0000000000..80e7a80404
--- /dev/null
+++ b/usr/src/uts/intel/inotify/Makefile
@@ -0,0 +1,70 @@
+#
+# 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 of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright (c) 2014 Joyent, Inc.  All rights reserved.
+#
+
+#
+#    Path to the base of the uts directory tree (usually /usr/src/uts).
+#
+UTSBASE    = ../..
+
+#
+#    Define the module and object file sets.
+#
+MODULE        = inotify
+OBJECTS        = $(INOTIFY_OBJS:%=$(OBJS_DIR)/%)
+LINTS        = $(INOTIFY_OBJS:%.o=$(LINTS_DIR)/%.ln)
+ROOTMODULE    = $(USR_DRV_DIR)/$(MODULE)
+CONF_SRCDIR    = $(UTSBASE)/common/io
+
+#
+#    Include common rules.
+#
+include $(UTSBASE)/intel/Makefile.intel
+
+LINTTAGS    += -erroff=E_STRUCT_DERIVED_FROM_FLEX_MBR
+CERRWARN    += -_gcc=-Wno-parentheses
+LDFLAGS        += -dy -Nfs/specfs
+
+#
+#    Define targets
+#
+ALL_TARGET    = $(BINARY) $(SRC_CONFILE)
+LINT_TARGET    = $(MODULE).lint
+INSTALL_TARGET    = $(BINARY) $(ROOTMODULE) $(ROOT_CONFFILE)
+
+#
+#    Default build targets.
+#
+.KEEP_STATE:
+
+def:        $(DEF_DEPS)
+
+all:        $(ALL_DEPS)
+
+clean:        $(CLEAN_DEPS)
+
+clobber:    $(CLOBBER_DEPS)
+
+lint:        $(LINT_DEPS)
+
+modlintlib:    $(MODLINTLIB_DEPS)
+
+clean.lint:    $(CLEAN_LINT_DEPS)
+
+install:    $(INSTALL_DEPS)
+
+#
+#    Include common targets.
+#
+include $(UTSBASE)/intel/Makefile.targ
diff --git a/usr/src/uts/sparc/Makefile.sparc b/usr/src/uts/sparc/Makefile.sparc
index 5a1639a692..b989364998 100644
--- a/usr/src/uts/sparc/Makefile.sparc
+++ b/usr/src/uts/sparc/Makefile.sparc
@@ -21,6 +21,7 @@
 # Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
 # Copyright (c) 2013 Andrew Stormont.  All rights reserved.
+# Copyright (c) 2014, Joyent, Inc. All rights reserved.
 #
@@ -236,6 +237,7 @@ DRV_KMODS    += nulldriver
 DRV_KMODS    += bridge trill
 DRV_KMODS    += bpf
 DRV_KMODS    += dca
+DRV_KMODS    += inotify
 #
 #       Hardware Drivers in common space
diff --git a/usr/src/uts/sparc/inotify/Makefile b/usr/src/uts/sparc/inotify/Makefile
new file mode 100644
index 0000000000..ce2b956955
--- /dev/null
+++ b/usr/src/uts/sparc/inotify/Makefile
@@ -0,0 +1,70 @@
+#
+# 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 of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright (c) 2014 Joyent, Inc.  All rights reserved.
+#
+
+#
+#    Path to the base of the uts directory tree (usually /usr/src/uts).
+#
+UTSBASE    = ../..
+
+#
+#    Define the module and object file sets.
+#
+MODULE        = inotify
+OBJECTS        = $(INOTIFY_OBJS:%=$(OBJS_DIR)/%)
+LINTS        = $(INOTIFY_OBJS:%.o=$(LINTS_DIR)/%.ln)
+ROOTMODULE    = $(USR_DRV_DIR)/$(MODULE)
+CONF_SRCDIR    = $(UTSBASE)/common/io
+
+#
+#    Include common rules.
+#
+include $(UTSBASE)/sparc/Makefile.sparc
+
+LINTTAGS    += -erroff=E_STRUCT_DERIVED_FROM_FLEX_MBR
+CERRWARN    += -_gcc=-Wno-parentheses
+LDFLAGS        += -dy -Nfs/specfs
+
+#
+#    Define targets
+#
+ALL_TARGET    = $(BINARY) $(SRC_CONFILE)
+LINT_TARGET    = $(MODULE).lint
+INSTALL_TARGET    = $(BINARY) $(ROOTMODULE) $(ROOT_CONFFILE)
+
+#
+#    Default build targets.
+#
+.KEEP_STATE:
+
+def:        $(DEF_DEPS)
+
+all:        $(ALL_DEPS)
+
+clean:        $(CLEAN_DEPS)
+
+clobber:    $(CLOBBER_DEPS)
+
+lint:        $(LINT_DEPS)
+
+modlintlib:    $(MODLINTLIB_DEPS)
+
+clean.lint:    $(CLEAN_LINT_DEPS)
+
+install:    $(INSTALL_DEPS)
+
+#
+#    Include common targets.
+#
+include $(UTSBASE)/sparc/Makefile.targ
--
2.40.1