summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--source3/Makefile.in5
-rw-r--r--source3/modules/onefs.h9
-rw-r--r--source3/modules/onefs_notify.c680
-rw-r--r--source3/modules/vfs_onefs.c2
4 files changed, 694 insertions, 2 deletions
diff --git a/source3/Makefile.in b/source3/Makefile.in
index 6ff90fc815..2df995fb20 100644
--- a/source3/Makefile.in
+++ b/source3/Makefile.in
@@ -621,7 +621,8 @@ PROFILES_OBJ = utils/profiles.o \
$(LIB_OBJ) $(LIB_DUMMY_OBJ) \
$(POPT_LIB_OBJ)
-OPLOCK_OBJ = smbd/oplock.o smbd/oplock_irix.o smbd/oplock_linux.o smbd/oplock_onefs.o
+OPLOCK_OBJ = smbd/oplock.o smbd/oplock_irix.o smbd/oplock_linux.o \
+ smbd/oplock_onefs.o
NOTIFY_OBJ = smbd/notify.o smbd/notify_inotify.o smbd/notify_internal.o
@@ -666,7 +667,7 @@ VFS_ACL_TDB_OBJ = modules/vfs_acl_tdb.o
VFS_SMB_TRAFFIC_ANALYZER_OBJ = modules/vfs_smb_traffic_analyzer.o
VFS_ONEFS_OBJ = modules/vfs_onefs.o modules/onefs_acl.o modules/onefs_system.o \
modules/onefs_open.o modules/onefs_streams.o modules/onefs_dir.c \
- modules/onefs_cbrl.o
+ modules/onefs_cbrl.o modules/onefs_notify.o
VFS_ONEFS_SHADOW_COPY_OBJ = modules/vfs_onefs_shadow_copy.o modules/onefs_shadow_copy.o
PERFCOUNT_ONEFS_OBJ = modules/perfcount_onefs.o
PERFCOUNT_TEST_OBJ = modules/perfcount_test.o
diff --git a/source3/modules/onefs.h b/source3/modules/onefs.h
index c002739f1f..ea452a454d 100644
--- a/source3/modules/onefs.h
+++ b/source3/modules/onefs.h
@@ -204,6 +204,15 @@ bool onefs_brl_cancel_windows(vfs_handle_struct *handle,
struct lock_struct *plock,
struct blocking_lock_record *blr);
+NTSTATUS onefs_notify_watch(vfs_handle_struct *vfs_handle,
+ struct sys_notify_context *ctx,
+ struct notify_entry *e,
+ void (*callback)(struct sys_notify_context *ctx,
+ void *private_data,
+ struct notify_event *ev),
+ void *private_data,
+ void *handle_p);
+
NTSTATUS onefs_fget_nt_acl(vfs_handle_struct *handle, files_struct *fsp,
uint32 security_info, SEC_DESC **ppdesc);
diff --git a/source3/modules/onefs_notify.c b/source3/modules/onefs_notify.c
new file mode 100644
index 0000000000..40f690876d
--- /dev/null
+++ b/source3/modules/onefs_notify.c
@@ -0,0 +1,680 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Support for change notify using OneFS's file event notification system
+ *
+ * Copyright (C) Andrew Tridgell, 2006
+ * Copyright (C) Steven Danneman, 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Implement handling of change notify requests on files and directories using
+ * Isilon OneFS's "ifs event" file notification system.
+ *
+ * The structure of this file is based off the implementation of change notify
+ * using the inotify system calls in smbd/notify_inotify.c */
+
+/* TODO: We could reduce the number of file descriptors used by merging
+ * multiple watch requests on the same directory into the same
+ * onefs_notify_watch_context. To do this we'd need mux/demux routines that
+ * when receiving an event on that watch context would check it against the
+ * CompletionFilter and WatchTree of open SMB requests, and return notify
+ * events back to the proper SMB requests */
+
+#include "onefs.h"
+
+#include <ifs/ifs_types.h>
+#include <ifs/ifs_syscalls.h>
+#include <isi_util/syscalls.h>
+
+#include <sys/event.h>
+
+#define ONEFS_IFS_EVENT_MAX_NUM 8
+#define ONEFS_IFS_EVENT_MAX_BYTES (ONEFS_IFS_EVENT_MAX_NUM * \
+ sizeof(struct ifs_event))
+
+struct onefs_notify_watch_context {
+ struct sys_notify_context *ctx;
+ int watch_fd;
+ ino_t watch_lin;
+ const char *path;
+ int ifs_event_fd;
+ uint32_t ifs_filter; /* the ifs event mask */
+ uint32_t smb_filter; /* the windows completion filter */
+ void (*callback)(struct sys_notify_context *ctx,
+ void *private_data,
+ struct notify_event *ev);
+ void *private_data;
+};
+
+/**
+ * Conversion map from a SMB completion filter to an IFS event mask.
+ */
+static const struct {
+ uint32_t smb_filter;
+ uint32_t ifs_filter;
+} onefs_notify_conv[] = {
+ {FILE_NOTIFY_CHANGE_FILE_NAME,
+ NOTE_CREATE | NOTE_DELETE | NOTE_RENAME_FROM | NOTE_RENAME_TO},
+ {FILE_NOTIFY_CHANGE_DIR_NAME,
+ NOTE_CREATE | NOTE_DELETE | NOTE_RENAME_FROM | NOTE_RENAME_TO},
+ {FILE_NOTIFY_CHANGE_ATTRIBUTES,
+ NOTE_CREATE | NOTE_DELETE | NOTE_RENAME_FROM | NOTE_RENAME_TO |
+ NOTE_ATTRIB},
+ {FILE_NOTIFY_CHANGE_SIZE,
+ NOTE_SIZE | NOTE_EXTEND},
+ {FILE_NOTIFY_CHANGE_LAST_WRITE,
+ NOTE_WRITE | NOTE_ATTRIB},
+ /* OneFS doesn't set atime by default, but we can somewhat fake it by
+ * notifying for other events that imply ACCESS */
+ {FILE_NOTIFY_CHANGE_LAST_ACCESS,
+ NOTE_WRITE | NOTE_ATTRIB},
+ /* We don't have an ifs_event for the setting of create time, but we
+ * can fake by notifying when a "new" file is created via rename */
+ {FILE_NOTIFY_CHANGE_CREATION,
+ NOTE_RENAME_TO},
+ {FILE_NOTIFY_CHANGE_SECURITY,
+ NOTE_SECURITY},
+ /* Unsupported bits
+ FILE_NOTIFY_CHANGE_EA (no EAs in OneFS)
+ FILE_NOTIFY_CHANGE_STREAM_NAME (no ifs_event equivalent)
+ FILE_NOTIFY_CHANGE_STREAM_SIZE (no ifs_event equivalent)
+ FILE_NOTIFY_CHANGE_STREAM_WRITE (no ifs_event equivalent) */
+};
+
+#define ONEFS_NOTIFY_UNSUPPORTED (FILE_NOTIFY_CHANGE_LAST_ACCESS | \
+ FILE_NOTIFY_CHANGE_CREATION | \
+ FILE_NOTIFY_CHANGE_EA | \
+ FILE_NOTIFY_CHANGE_STREAM_NAME | \
+ FILE_NOTIFY_CHANGE_STREAM_SIZE | \
+ FILE_NOTIFY_CHANGE_STREAM_WRITE)
+
+/**
+ * Convert Windows/SMB filter/flags to IFS event filter.
+ *
+ * @param[in] smb_filter Windows Completion Filter sent in the SMB
+ *
+ * @return ifs event filter mask
+ */
+static uint32_t
+onefs_notify_smb_filter_to_ifs_filter(uint32_t smb_filter)
+{
+ int i;
+ uint32_t ifs_filter = 0x0;
+
+ for (i=0;i<ARRAY_SIZE(onefs_notify_conv);i++) {
+ if (onefs_notify_conv[i].smb_filter & smb_filter) {
+ ifs_filter |= onefs_notify_conv[i].ifs_filter;
+ }
+ }
+
+ return ifs_filter;
+}
+
+/**
+ * Convert IFS filter/flags to a Windows notify action.
+ *
+ * Returns Win notification actions, types (1-5).
+ *
+ * @param[in] smb_filter Windows Completion Filter sent in the SMB
+ * @param[in] ifs_filter Returned from the kernel in the ifs_event
+ *
+ * @return 0 if there are no more relevant flags.
+ */
+static int
+onefs_notify_ifs_filter_to_smb_action(uint32_t smb_filter, uint32_t ifs_filter)
+{
+ /* Handle Windows special cases, before modifying events bitmask */
+
+ /* Special case 1: win32api->MoveFile needs to send a modified
+ * notification on the new file, if smb_filter == ATTRIBUTES.
+ * Here we handle the case where two separate ATTR & NAME notifications
+ * have been registered. We handle the case where both bits are set in
+ * the same registration in onefs_notify_dispatch() */
+ if ((smb_filter & FILE_NOTIFY_CHANGE_ATTRIBUTES) &&
+ !(smb_filter & FILE_NOTIFY_CHANGE_FILE_NAME) &&
+ (ifs_filter & NOTE_FILE) && (ifs_filter & NOTE_RENAME_TO))
+ {
+ return NOTIFY_ACTION_MODIFIED;
+ }
+
+ /* Special case 2: Writes need to send a modified
+ * notification on the file, if smb_filter = ATTRIBUTES. */
+ if ((smb_filter & FILE_NOTIFY_CHANGE_ATTRIBUTES) &&
+ (ifs_filter & NOTE_FILE) && (ifs_filter & NOTE_WRITE))
+ {
+ return NOTIFY_ACTION_MODIFIED;
+ }
+
+ /* Loop because some events may be filtered out. Eventually all
+ * relevant events will be taken care of and returned. */
+ while (1) {
+ if (ifs_filter & NOTE_CREATE) {
+ ifs_filter &= ~NOTE_CREATE;
+ if ((smb_filter & FILE_NOTIFY_CHANGE_FILE_NAME) &&
+ (ifs_filter & NOTE_FILE))
+ return NOTIFY_ACTION_ADDED;
+ if ((smb_filter & FILE_NOTIFY_CHANGE_DIR_NAME) &&
+ (ifs_filter & NOTE_DIRECTORY))
+ return NOTIFY_ACTION_ADDED;
+ }
+ else if (ifs_filter & NOTE_DELETE) {
+ ifs_filter &= ~NOTE_DELETE;
+ if ((smb_filter & FILE_NOTIFY_CHANGE_FILE_NAME) &&
+ (ifs_filter & NOTE_FILE))
+ return NOTIFY_ACTION_REMOVED;
+ if ((smb_filter & FILE_NOTIFY_CHANGE_DIR_NAME) &&
+ (ifs_filter & NOTE_DIRECTORY))
+ return NOTIFY_ACTION_REMOVED;
+ }
+ else if (ifs_filter & NOTE_WRITE) {
+ ifs_filter &= ~NOTE_WRITE;
+ if ((smb_filter & FILE_NOTIFY_CHANGE_LAST_WRITE) ||
+ (smb_filter & FILE_NOTIFY_CHANGE_LAST_ACCESS))
+ return NOTIFY_ACTION_MODIFIED;
+ }
+ else if ((ifs_filter & NOTE_SIZE) || (ifs_filter & NOTE_EXTEND)) {
+ ifs_filter &= ~NOTE_SIZE;
+ ifs_filter &= ~NOTE_EXTEND;
+
+ /* TODO: Do something on NOTE_DIR? */
+ if ((smb_filter & FILE_NOTIFY_CHANGE_SIZE) &&
+ (ifs_filter & NOTE_FILE))
+ return NOTIFY_ACTION_MODIFIED;
+ }
+ else if (ifs_filter & NOTE_ATTRIB) {
+ ifs_filter &= ~NOTE_ATTRIB;
+ /* NOTE_ATTRIB needs to be converted to a
+ * LAST_WRITE as well, because we need to send
+ * LAST_WRITE when the mtime changes. Looking into
+ * better alternatives as this causes extra LAST_WRITE
+ * notifications. We also return LAST_ACCESS as a
+ * modification to attribs implies this. */
+ if ((smb_filter & FILE_NOTIFY_CHANGE_ATTRIBUTES) ||
+ (smb_filter & FILE_NOTIFY_CHANGE_LAST_WRITE) ||
+ (smb_filter & FILE_NOTIFY_CHANGE_LAST_ACCESS))
+ return NOTIFY_ACTION_MODIFIED;
+ }
+ else if (ifs_filter & NOTE_LINK) {
+ ifs_filter &= ~NOTE_LINK;
+ /* NOTE_LINK will send out NO notifications */
+ }
+ else if (ifs_filter & NOTE_REVOKE) {
+ ifs_filter &= ~NOTE_REVOKE;
+ /* NOTE_REVOKE will send out NO notifications */
+ }
+ else if (ifs_filter & NOTE_RENAME_FROM) {
+ ifs_filter &= ~NOTE_RENAME_FROM;
+
+ if (((smb_filter & FILE_NOTIFY_CHANGE_FILE_NAME) &&
+ (ifs_filter & NOTE_FILE)) ||
+ ((smb_filter & FILE_NOTIFY_CHANGE_DIR_NAME) &&
+ (ifs_filter & NOTE_DIRECTORY))) {
+ /* Check if this is a RENAME, not a MOVE */
+ if (ifs_filter & NOTE_RENAME_SAMEDIR) {
+ /* Remove the NOTE_RENAME_SAMEDIR, IFF
+ * RENAME_TO is not in this event */
+ if (!(ifs_filter & NOTE_RENAME_TO))
+ ifs_filter &=
+ ~NOTE_RENAME_SAMEDIR;
+ return NOTIFY_ACTION_OLD_NAME;
+ }
+ return NOTIFY_ACTION_REMOVED;
+ }
+ }
+ else if (ifs_filter & NOTE_RENAME_TO) {
+ ifs_filter &= ~NOTE_RENAME_TO;
+
+ if (((smb_filter & FILE_NOTIFY_CHANGE_FILE_NAME) &&
+ (ifs_filter & NOTE_FILE)) ||
+ ((smb_filter & FILE_NOTIFY_CHANGE_DIR_NAME) &&
+ (ifs_filter & NOTE_DIRECTORY))) {
+ /* Check if this is a RENAME, not a MOVE */
+ if (ifs_filter & NOTE_RENAME_SAMEDIR) {
+ /* Remove the NOTE_RENAME_SAMEDIR, IFF
+ * RENAME_FROM is not in this event */
+ if (!(ifs_filter & NOTE_RENAME_FROM))
+ ifs_filter &=
+ ~NOTE_RENAME_SAMEDIR;
+ return NOTIFY_ACTION_NEW_NAME;
+ }
+ return NOTIFY_ACTION_ADDED;
+ }
+ /* RAW-NOTIFY shows us that a rename triggers a
+ * creation time change */
+ if ((smb_filter & FILE_NOTIFY_CHANGE_CREATION) &&
+ (ifs_filter & NOTE_FILE))
+ return NOTIFY_ACTION_MODIFIED;
+ }
+ else if (ifs_filter & NOTE_SECURITY) {
+ ifs_filter &= ~NOTE_SECURITY;
+
+ if (smb_filter & FILE_NOTIFY_CHANGE_SECURITY)
+ return NOTIFY_ACTION_MODIFIED;
+ } else {
+ /* No relevant flags found */
+ return 0;
+ }
+ }
+}
+
+/**
+ * Retrieve a directory path of a changed file, relative to the watched dir
+ *
+ * @param[in] wc watch context for the returned event
+ * @param[in] e ifs_event notification returned from the kernel
+ * @param[out] path name relative to the watched dir. This is talloced
+ * off of wc and needs to be freed by the caller.
+ *
+ * @return true on success
+ *
+ * TODO: This function currently doesn't handle path names with multiple
+ * encodings. enc_get_lin_path() should be used in the future to convert
+ * each path segment's encoding to UTF-8
+ */
+static bool
+get_ifs_event_path(struct onefs_notify_watch_context *wc, struct ifs_event *e,
+ char **path)
+{
+ char *path_buf = NULL;
+ size_t pathlen = 0;
+ int error = 0;
+
+ SMB_ASSERT(path);
+
+ /* Lookup the path from watch_dir through our parent dir */
+ if (e->namelen > 0) {
+ error = lin_get_path(wc->watch_lin,
+ e->parent_lin,
+ HEAD_SNAPID,
+ e->parent_parent_lin,
+ e->parent_name_hash,
+ &pathlen, &path_buf);
+ if (!error) {
+ /* Only add slash if a path exists in path_buf from
+ * lin_get_path call. Windows does not expect a
+ * leading '/' */
+ if (pathlen > 0)
+ *path = talloc_asprintf(wc, "%s/%s",
+ path_buf, e->name);
+ else
+ *path = talloc_asprintf(wc, "%s", e->name);
+ SAFE_FREE(path_buf);
+ }
+ }
+
+ /* If ifs_event didn't return a name, or we errored out of our intial
+ * path lookup, try again using the lin of the changed file */
+ if (!(*path)) {
+ error = lin_get_path(wc->watch_lin,
+ e->lin,
+ HEAD_SNAPID,
+ e->parent_lin,
+ e->name_hash,
+ &pathlen, &path_buf);
+ if (error) {
+ /* It's possible that both the lin and the parent lin
+ * are invalid (or not given) -- we will skip these
+ * events. */
+ DEBUG(3,("Path lookup failed. LINS are invalid: "
+ "e->lin: 0x%llu, e->parent_lin: 0x%llu, "
+ "e->parent_parent_lin: 0x%llu\n",
+ e->lin, e->parent_lin, e->parent_parent_lin));
+ SAFE_FREE(path_buf);
+ return false;
+ } else {
+ *path = talloc_asprintf(wc, "%s", path_buf);
+ DEBUG(5, ("Using path from event LIN = %s\n",
+ path_buf));
+ SAFE_FREE(path_buf);
+ }
+ }
+
+ /* Replacement of UNIX slashes with WIN slashes is handled at a
+ * higher layer. */
+
+ return true;
+}
+
+/**
+ * Dispatch one OneFS notify event to the general Samba code
+ *
+ * @param[in] wc watch context for the returned event
+ * @param[in] e event returned from the kernel
+ *
+ * @return nothing
+ */
+static void
+onefs_notify_dispatch(struct onefs_notify_watch_context *wc,
+ struct ifs_event *e)
+{
+ char *path = NULL;
+ struct notify_event ne;
+
+ DEBUG(5, ("Retrieved ifs event from kernel: lin=%#llx, ifs_events=%#x, "
+ "parent_lin=%#llx, namelen=%d, name=\"%s\"\n",
+ e->lin, e->events, e->parent_lin, e->namelen, e->name));
+
+ /* Check validity of event returned from kernel */
+ if (e->lin == 0) {
+ /* The lin == 0 specifies 1 of 2 cases:
+ * 1) We are out of events. The kernel has a limited
+ * amount (somewhere near 90000)
+ * 2) Split nodes have merged back and had data written
+ * to them -- thus we've missed some of those events. */
+ DEBUG(3, ("We've missed some kernel ifs events!\n"));
+
+ /* Alert higher level to the problem so it returns catch-all
+ * response to the client */
+ ne.path = NULL;
+ ne.action = 0;
+ wc->callback(wc->ctx, wc->private_data, &ne);
+ }
+
+ if (e->lin == wc->watch_lin) {
+ /* Windows doesn't report notifications on root
+ * watched directory */
+ /* TODO: This should be abstracted out to the general layer
+ * instead of being handled in every notify provider */
+ DEBUG(5, ("Skipping notification on root of the watched "
+ "path.\n"));
+ return;
+ }
+
+ /* Retrieve the full path for the ifs event name */
+ if(!get_ifs_event_path(wc, e, &path)) {
+ DEBUG(3, ("Failed to convert the ifs_event lins to a path. "
+ "Skipping this event\n"));
+ return;
+ }
+
+ if (!strncmp(path, ".ifsvar", 7)) {
+ /* Skip notifications on file if its in ifs configuration
+ * directory */
+ goto clean;
+ }
+
+ ne.path = path;
+
+ /* Convert ifs event mask to an smb action mask */
+ ne.action = onefs_notify_ifs_filter_to_smb_action(wc->smb_filter,
+ e->events);
+
+ DEBUG(5, ("Converted smb_filter=%#x, ifs_events=%#x, to "
+ "ne.action = %d, for ne.path = %s\n",
+ wc->smb_filter, e->events, ne.action, ne.path));
+
+ if (!ne.action)
+ goto clean;
+
+ /* Return notify_event to higher level */
+ wc->callback(wc->ctx, wc->private_data, &ne);
+
+ /* SMB expects a file rename/move to generate three actions, a
+ * rename_from/delete on the from file, a rename_to/create on the to
+ * file, and a modify for the rename_to file. If we have two separate
+ * notifications registered for ATTRIBUTES and FILENAME, this case will
+ * be handled by separate ifs_events in
+ * onefs_notify_ifs_filter_to_smb_action(). If both bits are registered
+ * in the same notification, we must send an extra MODIFIED action
+ * here. */
+ if ((wc->smb_filter & FILE_NOTIFY_CHANGE_ATTRIBUTES) &&
+ (wc->smb_filter & FILE_NOTIFY_CHANGE_FILE_NAME) &&
+ (e->events & NOTE_FILE) && (e->events & NOTE_RENAME_TO))
+ {
+ ne.action = NOTIFY_ACTION_MODIFIED;
+ wc->callback(wc->ctx, wc->private_data, &ne);
+ }
+
+ /* FALLTHROUGH */
+clean:
+ talloc_free(path);
+ return;
+}
+
+/**
+ * Callback when the kernel has some events for us
+ *
+ * Read events off ifs event fd and pass them to our dispatch function
+ *
+ * @param ev context of all tevents registered in the smbd
+ * @param fde tevent struct specific to one ifs event channel
+ * @param flags tevent flags passed when we added our ifs event channel fd to
+ * the main loop
+ * @param private_data onefs_notify_watch_context specific to this ifs event
+ * channel
+ *
+ * @return nothing
+ */
+static void
+onefs_notify_handler(struct event_context *ev,
+ struct fd_event *fde,
+ uint16_t flags,
+ void *private_data)
+{
+ struct onefs_notify_watch_context *wc = NULL;
+ struct ifs_event ifs_events[ONEFS_IFS_EVENT_MAX_NUM];
+ ssize_t nread = 0;
+ int count = 0;
+ int i = 0;
+
+ wc = talloc_get_type(private_data, struct onefs_notify_watch_context);
+
+ /* Read as many ifs events from the notify channel as we can */
+ nread = sys_read(wc->ifs_event_fd, &ifs_events,
+ ONEFS_IFS_EVENT_MAX_BYTES);
+ if (nread == 0) {
+ DEBUG(0,("No data found while reading ifs event fd?!\n"));
+ return;
+ }
+ if (nread < 0) {
+ DEBUG(0,("Failed to read ifs event data: %s\n",
+ strerror(errno)));
+ return;
+ }
+
+ count = nread / sizeof(struct ifs_event);
+
+ DEBUG(5, ("Got %d notification events in %d bytes.\n", count, nread));
+
+ /* Dispatch ifs_events one-at-a-time to higher level */
+ for (i=0; i < count; i++) {
+ onefs_notify_dispatch(wc, &ifs_events[i]);
+ }
+}
+
+/**
+ * Destroy the ifs event channel
+ *
+ * This is called from talloc_free() when the generic Samba notify layer frees
+ * the onefs_notify_watch_context.
+ *
+ * @param[in] wc pointer to watch context which is being destroyed
+ *
+ * return 0 on success
+*/
+static int
+onefs_watch_destructor(struct onefs_notify_watch_context *wc)
+{
+ /* The ifs_event_fd will re de-registered from the event loop by
+ * another destructor triggered from the freeing of this wc */
+ close(wc->ifs_event_fd);
+ return 0;
+}
+
+/**
+ * Register a single change notify watch request.
+ *
+ * Open an event listener on a directory to watch for modifications. This
+ * channel is closed by a destructor when the caller calls talloc_free()
+ * on *handle.
+ *
+ * @param[in] vfs_handle handle given to most VFS operations
+ * @param[in] ctx context (conn and tevent) for this connection
+ * @param[in] e filter and path of client's notify request
+ * @param[in] callback function to call when file notification event is received
+ * from the kernel, passing that event up to Samba's
+ * generalized change notify layer
+ * @param[in] private_data opaque data given to us by the general change notify
+ * layer which must be returned in the callback function
+ * @param[out] handle_p handle returned to generalized change notify layer used
+ * to close the event channel when notification is
+ * cancelled
+ *
+ * @return NT_STATUS_OK unless error
+ */
+NTSTATUS
+onefs_notify_watch(vfs_handle_struct *vfs_handle,
+ struct sys_notify_context *ctx,
+ struct notify_entry *e,
+ void (*callback)(struct sys_notify_context *ctx,
+ void *private_data,
+ struct notify_event *ev),
+ void *private_data,
+ void *handle_p)
+{
+ int ifs_event_fd = -1;
+ uint32_t ifs_filter = 0;
+ uint32_t smb_filter = e->filter;
+ bool watch_tree = !!e->subdir_filter;
+ struct onefs_notify_watch_context *wc = NULL;
+ void **handle = (void **)handle_p;
+ SMB_STRUCT_STAT sbuf;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+
+ /* Fallback to default Samba implementation if kernel CN is disabled */
+ if (!lp_kernel_change_notify(vfs_handle->conn->params)) {
+ (*handle) = NULL;
+ return NT_STATUS_OK;
+ }
+
+ /* The OneFS open path should always give us a valid fd on a directory*/
+ SMB_ASSERT(e->dir_fd >= 0);
+
+ /* Always set e->filter to 0 so we don't fallback on the default change
+ * notify backend. It's not cluster coherent or cross-protocol so we
+ * can't guarantee correctness using it. */
+ e->filter = 0;
+ e->subdir_filter = 0;
+
+ /* Snapshots do not currently allow event listeners. See Isilon
+ * bug 33476 for an example of .snapshot debug spew that can occur. */
+ if (e->dir_id.extid != HEAD_SNAPID)
+ return NT_STATUS_INVALID_PARAMETER;
+
+ /* Convert Completion Filter mask to IFS Event mask */
+ ifs_filter = onefs_notify_smb_filter_to_ifs_filter(smb_filter);
+
+ if (smb_filter & ONEFS_NOTIFY_UNSUPPORTED) {
+ /* One or more of the filter bits could not be fully handled by
+ * the ifs_event system. To be correct, if we cannot service a
+ * bit in the completion filter we should return
+ * NT_STATUS_NOT_IMPLEMENTED to let the client know that they
+ * won't be receiving all the notify events that they asked for.
+ * Unfortunately, WinXP clients cache this error message, stop
+ * trying to send any notify requests at all, and instead return
+ * NOT_IMPLEMENTED to all requesting apps without ever sending a
+ * message to us. Thus we lie, and say we can service all bits,
+ * but simply don't return actions for the filter bits we can't
+ * detect or fully implement. */
+ DEBUG(3,("One or more of the Windows completion filter bits "
+ "for \"%s\" could not be fully handled by the "
+ "ifs_event system. The failed bits are "
+ "smb_filter=%#x\n",
+ e->path, smb_filter & ONEFS_NOTIFY_UNSUPPORTED));
+ }
+
+ if (ifs_filter == 0) {
+ /* None of the filter bits given are supported by the ifs_event
+ * system. Don't create a kernel notify channel, but mock
+ * up a fake handle for the caller. */
+ DEBUG(3,("No bits in the Windows completion filter could be "
+ "translated to ifs_event mask for \"%s\", "
+ "smb_filter=%#x\n", e->path, smb_filter));
+ (*handle) = NULL;
+ return NT_STATUS_OK;
+ }
+
+ /* Register an ifs event channel for this watch request */
+ ifs_event_fd = ifs_create_listener(watch_tree ?
+ EVENT_RECURSIVE :
+ EVENT_CHILDREN,
+ ifs_filter,
+ e->dir_fd);
+ if (ifs_event_fd < 0) {
+ DEBUG(0,("Failed to create listener for %s with \"%s\". "
+ "smb_filter=0x%x, ifs_filter=0x%x, watch_tree=%u\n",
+ strerror(errno), e->path, smb_filter, ifs_filter,
+ watch_tree));
+ return map_nt_error_from_unix(errno);
+ }
+
+ /* Create a watch context to track this change notify request */
+ wc = talloc(ctx, struct onefs_notify_watch_context);
+ if (wc == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto err;
+ }
+
+ /* Get LIN for directory */
+ if (sys_fstat(e->dir_fd, &sbuf)) {
+ DEBUG(0, ("stat on directory fd failed: %s\n",
+ strerror(errno)));
+ status = map_nt_error_from_unix(errno);
+ goto err;
+ }
+
+ if (sbuf.st_ino == 0) {
+ DEBUG(0, ("0 LIN found!\n"));
+ goto err;
+ }
+
+ wc->ctx = ctx;
+ wc->watch_fd = e->dir_fd;
+ wc->watch_lin = sbuf.st_ino;
+ wc->ifs_event_fd = ifs_event_fd;
+ wc->ifs_filter = ifs_filter;
+ wc->smb_filter = smb_filter;
+ wc->callback = callback;
+ wc->private_data = private_data;
+ wc->path = talloc_strdup(wc, e->path);
+ if (wc->path == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto err;
+ }
+
+ (*handle) = wc;
+
+ /* The caller frees the handle to stop watching */
+ talloc_set_destructor(wc, onefs_watch_destructor);
+
+ /* Add a tevent waiting for the ifs event fd to be readable */
+ event_add_fd(ctx->ev, wc, wc->ifs_event_fd, EVENT_FD_READ,
+ onefs_notify_handler, wc);
+
+ DEBUG(10, ("Watching for changes on \"%s\" smb_filter=0x%x, "
+ "ifs_filter=0x%x, watch_tree=%d, ifs_event_fd=%d, "
+ "dir_fd=%d, dir_lin=0x%llx\n",
+ e->path, smb_filter, ifs_filter, watch_tree,
+ ifs_event_fd, e->dir_fd, sbuf.st_ino));
+
+ return NT_STATUS_OK;
+
+err:
+ talloc_free(wc);
+ SMB_ASSERT(ifs_event_fd >= 0);
+ close(ifs_event_fd);
+ return status;
+}
diff --git a/source3/modules/vfs_onefs.c b/source3/modules/vfs_onefs.c
index 522b94399d..f0c6a9d8bb 100644
--- a/source3/modules/vfs_onefs.c
+++ b/source3/modules/vfs_onefs.c
@@ -368,6 +368,8 @@ static vfs_op_tuple onefs_ops[] = {
SMB_VFS_LAYER_OPAQUE},
{SMB_VFS_OP(onefs_brl_cancel_windows), SMB_VFS_OP_BRL_CANCEL_WINDOWS,
SMB_VFS_LAYER_OPAQUE},
+ {SMB_VFS_OP(onefs_notify_watch), SMB_VFS_OP_NOTIFY_WATCH,
+ SMB_VFS_LAYER_OPAQUE},
{SMB_VFS_OP(onefs_fget_nt_acl), SMB_VFS_OP_FGET_NT_ACL,
SMB_VFS_LAYER_OPAQUE},
{SMB_VFS_OP(onefs_get_nt_acl), SMB_VFS_OP_GET_NT_ACL,