vfs-module-kqueue-notify   [plain text]


Kqueue file notification module.

This module implementd file change notifications on top of the
kqueue mechanism. Unfortunately the kqueue mechanismm is quite a
poor match for SMB semantics, but it is sufficient for us to give
a notification that a name changed in a directory. This is enough
to trigger Finder and Explorer to go and refresh the directory
listing and make the filename show up or go away.

Index: samba/source/modules/vfs_notify_kqueue.c
===================================================================
--- /dev/null
+++ samba/source/modules/vfs_notify_kqueue.c
@@ -0,0 +1,320 @@
+/*
+ * Kqueue file notification support.
+ *
+ * Copyright (c) 2009 Apple Inc. All rights reserved.
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include "includes.h"
+
+#include <sys/event.h>
+
+#ifndef FD_CLOEXEC
+#define FD_CLOEXEC 1
+#endif
+
+#define KQTRACE 10
+
+typedef void (*notify_callback)(struct sys_notify_context *,
+			       void *, struct notify_event *);
+
+#define KQ_NOTIFY_MASK ( \
+	    FILE_NOTIFY_CHANGE_FILE_NAME | \
+	    FILE_NOTIFY_CHANGE_DIR_NAME \
+	)
+
+struct kqueue_watch_context
+{
+	const char *	kq_path;	/* path we are watching */
+	struct kevent	kq_event;	/* kevent */
+	int		kq_fd;		/* kevent file descriptor */
+
+	struct {
+		notify_callback			callback;
+		struct sys_notify_context *	notify;
+		void *				data;
+	} kq_observer;
+};
+
+static int global_kq = -1;
+
+static int
+kqueue_watch_dealloc(
+		struct kqueue_watch_context * kq_watch)
+{
+	/* Just cancel the kqueue watch. */
+	DEBUG(KQTRACE, ("cancelling watch for fd=%d, path=%s\n",
+			kq_watch->kq_fd, kq_watch->kq_path));
+
+	if (kq_watch->kq_fd != -1) {
+		EV_SET(&kq_watch->kq_event,
+			kq_watch->kq_fd /* ident */,
+			EVFILT_VNODE, EV_DELETE,
+			0 /* fflags */, 0, NULL);
+
+		kevent(global_kq, &kq_watch->kq_event, 1, NULL, 0, NULL);
+		close(kq_watch->kq_fd);
+		kq_watch->kq_fd = -1;
+	}
+
+	return 0;
+}
+
+static struct kqueue_watch_context *
+kqueue_watch_alloc(
+		void * mem_ctx,
+		const struct notify_entry * entry)
+{
+	struct kqueue_watch_context * kq_watch;
+
+	kq_watch = TALLOC_P(mem_ctx, struct kqueue_watch_context);
+	if (kq_watch == NULL) {
+		return NULL;
+	}
+
+	kq_watch->kq_path = talloc_strdup(kq_watch, entry->path);
+	if (kq_watch->kq_path == NULL) {
+		TALLOC_FREE(kq_watch);
+		return NULL;
+	}
+
+	kq_watch->kq_fd = -1;
+	return kq_watch;
+}
+
+static NTSTATUS
+kqueue_watch_start(
+		const struct connection_struct * conn,
+		struct kqueue_watch_context * kq_watch)
+{
+	int fd;
+	int err;
+	struct stat sbuf;
+
+	DEBUG(KQTRACE, ("adding watch for path=%s\n", kq_watch->kq_path));
+
+	if (lp_widelinks(SNUM(conn))) {
+		fd = open(kq_watch->kq_path, O_EVTONLY);
+	} else {
+		fd = open(kq_watch->kq_path, O_EVTONLY|O_NOFOLLOW);
+	}
+
+	if (fd == -1) {
+		int errsav = errno;
+		DEBUG(2, ("open(%s) failed: errno=%d, %s\n",
+				kq_watch->kq_path, errsav, strerror(errsav)));
+		return map_nt_error_from_unix(errsav);
+	}
+
+	fcntl(fd, F_SETFD, FD_CLOEXEC);
+
+	fstat(fd, &sbuf);
+	if (!S_ISDIR(sbuf.st_mode)) {
+		DEBUG(KQTRACE, ("%s is not a directory\n", kq_watch->kq_path));
+		close(fd);
+		return NT_STATUS_NOT_A_DIRECTORY;
+	}
+
+	/* We can tell when a directory changes, but that's about it. We have
+	 * no idea when attributes of the directory entries change and we have
+	 * no idea whether a file or a directory changed.
+	 */
+
+	EV_SET(&kq_watch->kq_event, fd /* ident */,
+		EVFILT_VNODE,
+		EV_ADD | EV_CLEAR | EV_ENABLE,
+		NOTE_WRITE /* fflags */, 0, kq_watch);
+
+	err = kevent(global_kq, &kq_watch->kq_event, 1, NULL, 0, NULL);
+	if (err == -1) {
+		int errsav = errno;
+		close(kq_watch->kq_fd);
+		DEBUG(2, ("kevent failed: errno=%d, %s\n",
+			errsav, strerror(errsav)));
+		return map_nt_error_from_unix(errsav);
+	}
+
+	kq_watch->kq_fd = fd;
+
+	DEBUG(KQTRACE, ("added watch for fd=%d path=%s\n",
+			kq_watch->kq_fd, kq_watch->kq_path));
+
+	return NT_STATUS_OK;
+}
+
+static void
+kqueue_event_handler(
+		struct event_context * context,
+		struct fd_event * fd,
+		uint16_t flags,
+		void *data)
+{
+	struct kevent ev_list[8];
+	struct timespec ev_timeout = { .tv_sec = 0, .tv_nsec = 0 };
+
+	DEBUG(KQTRACE, ("polling kqueue events\n"));
+
+	for (;;) {
+		int i;
+		int err;
+
+		ZERO_ARRAY(ev_list);
+
+		err = kevent(global_kq, NULL, 0,
+			ev_list, ARRAY_SIZE(ev_list),
+			&ev_timeout /* timeout */);
+
+		if (err == -1) {
+			DEBUG(2, ("kevent failed, errno=%d, %s\n",
+						errno, strerror(errno)));
+			return;
+		}
+
+		if (err == 0) {
+			DEBUG(KQTRACE, ("done polling\n"));
+			return;
+		}
+
+		DEBUG(KQTRACE, ("handling %d kqueue events\n", err));
+
+		for (i = 0; i < MIN(err, ARRAY_SIZE(ev_list)); ++i) {
+			struct kqueue_watch_context * kq_watch;
+			struct kevent * kev = &ev_list[i];
+			struct notify_event nev;
+
+			kq_watch = (struct kqueue_watch_context *)kev->udata;
+			if (!kq_watch) {
+				/* We got an event tht doesn't have a context
+				 * set. This should never happen, but we are
+				 * defensive and dutifully remove the event and
+				 * the file descriptor.
+				 */
+				DEBUG(2, ("missing kqueue watch context for fd=%d\n",
+							(int)kev->ident));
+				kev->flags = EV_DELETE;
+				kevent(global_kq, kev, 1, NULL, 0, NULL);
+				close(kev->ident);
+				continue;
+			}
+
+			DEBUG(KQTRACE, ("signalling event for fd=%d path=%s\n",
+					kq_watch->kq_fd,
+					kq_watch->kq_path));
+
+			/* We have no real idea what happend. Let's just say
+			 * that a file waas added. We don't know the name, but
+			 * we can get away with the empty string.
+			 */
+			nev.action = NOTIFY_ACTION_ADDED;
+			nev.path = "";
+			nev.private_data = NULL;
+
+			kq_watch->kq_observer.callback(
+					kq_watch->kq_observer.notify,
+					kq_watch->kq_observer.data,
+					&nev);
+
+		}
+	}
+}
+
+static NTSTATUS
+kqueue_init_kqueue(
+		struct event_context * event)
+{
+	if (global_kq != -1) {
+		return NT_STATUS_OK;
+	}
+
+	DEBUG(KQTRACE, ("initializing global kqueue\n"));
+
+	global_kq = kqueue();
+	if (global_kq == -1) {
+		DEBUG(0, ("failed create a kqueue, %s\n", strerror(errno)));
+		return NT_STATUS_INSUFFICIENT_RESOURCES;
+	}
+
+	fcntl(global_kq, F_SETFD, FD_CLOEXEC);
+
+	if (event_add_fd(event, event, global_kq, EVENT_FD_READ,
+		    kqueue_event_handler, NULL) == NULL) {
+		DEBUG(0, ("failed to add kqueue monitor event\n"));
+		close(global_kq);
+		global_kq = -1;
+		return NT_STATUS_IO_DEVICE_ERROR;
+	}
+
+	return NT_STATUS_OK;
+}
+
+static NTSTATUS kqueue_notify_watch(
+		vfs_handle_struct * vfs_handle,
+		struct sys_notify_context * context,
+		struct notify_entry * entry,
+		notify_callback callback,
+		void * callback_data,
+		void * watch_handle)
+{
+	NTSTATUS status;
+
+	struct kqueue_watch_context * kq_watch;
+
+	if (entry->subdir_filter != 0) {
+		DEBUG(10, ("ignoring subdir_filter=%#x\n", entry->subdir_filter));
+	}
+
+	if ((entry->filter & KQ_NOTIFY_MASK) == 0) {
+		DEBUG(10, ("ignoring unsupported kqueue filter=%#x\n", entry->filter));
+		return NT_STATUS_OK;
+	}
+
+	status = kqueue_init_kqueue(context->ev);
+	if (!NT_STATUS_IS_OK(status)) {
+		return status;
+	}
+
+	kq_watch = kqueue_watch_alloc(context, entry);
+	if (kq_watch == NULL) {
+		return NT_STATUS_NO_MEMORY;
+	}
+
+	kq_watch->kq_observer.callback = callback;
+	kq_watch->kq_observer.notify = context;
+	kq_watch->kq_observer.data = callback_data;
+
+	status = kqueue_watch_start(context->conn, kq_watch);
+	if (!NT_STATUS_IS_OK(status)) {
+		TALLOC_FREE(kq_watch);
+		return status;
+	}
+
+	*(void **)watch_handle = kq_watch;
+	talloc_set_destructor(kq_watch, kqueue_watch_dealloc);
+	return NT_STATUS_OK;
+}
+
+static vfs_op_tuple notify_kqueue_ops[] =
+{
+
+	{SMB_VFS_OP(kqueue_notify_watch), SMB_VFS_OP_NOTIFY_WATCH, SMB_VFS_LAYER_OPAQUE},
+	{SMB_VFS_OP(NULL), SMB_VFS_OP_NOOP, SMB_VFS_LAYER_NOOP}
+};
+
+NTSTATUS vfs_notify_kqueue_init(void)
+{
+	return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "notify_kqueue",
+				notify_kqueue_ops);
+}
Index: samba/source/configure.in
===================================================================
--- samba/source/configure.in.orig
+++ samba/source/configure.in
@@ -6446,6 +6446,7 @@ SMB_MODULE(vfs_gpfs, \$(VFS_GPFS_OBJ), "
 SMB_MODULE(vfs_readahead, \$(VFS_READAHEAD_OBJ), "bin/readahead.$SHLIBEXT", VFS)
 SMB_MODULE(vfs_notify_fam, \$(VFS_NOTIFY_FAM_OBJ), "bin/notify_fam.$SHLIBEXT", VFS)
 SMB_MODULE(vfs_darwin_streams, \$(VFS_DARWIN_STREAMS_OBJ), "bin/darwin_streams.$SHLIBEXT", VFS)
+SMB_MODULE(vfs_notify_kqueue, \$(VFS_NOTIFY_KQUEUE_OBJ), "bin/notify_kqueue.$SHLIBEXT", VFS)
 
 SMB_SUBSYSTEM(VFS,smbd/vfs.o)
 
Index: samba/source/Makefile.in
===================================================================
--- samba/source/Makefile.in.orig
+++ samba/source/Makefile.in
@@ -436,6 +436,7 @@ VFS_NOTIFY_FAM_OBJ = modules/vfs_notify_
 VFS_READAHEAD_OBJ = modules/vfs_readahead.o
 VFS_DARWIN_STREAMS_OBJ = modules/vfs_darwin_streams.o
 VFS_DARWINACL_OBJ = modules/vfs_darwin_acls.o
+VFS_NOTIFY_KQUEUE_OBJ = modules/vfs_notify_kqueue.o
 
 PLAINTEXT_AUTH_OBJ = auth/pampass.o auth/pass_check.o
 
@@ -1402,6 +1403,10 @@ bin/darwin_streams.@SHLIBEXT@: $(VFS_DAR
 		$(VFS_DARWIN_STREAMS_OBJ) \
 		-framework ByteRangeLocking
 
+bin/notify_kqueue.@SHLIBEXT@: $(VFS_NOTIFY_KQUEUE_OBJ)
+	@echo "Building plugin $@"
+	@$(SHLD_MODULE) $(VFS_NOTIFY_KQUEUE_OBJ)
+
 bin/darwinacl.@SHLIBEXT@: $(VFS_DARWINACL_OBJ)
 	@echo "Building plugin $@"
 	@$(SHLD_MODULE) $(VFS_DARWINACL_OBJ)