New file |
| | |
| | | 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 |
| | | |