vfs_notify_kqueue.c [plain text]
#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;
struct kevent kq_event;
int kq_fd;
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)
{
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 ,
EVFILT_VNODE, EV_DELETE,
0 , 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;
}
EV_SET(&kq_watch->kq_event, fd ,
EVFILT_VNODE,
EV_ADD | EV_CLEAR | EV_ENABLE,
NOTE_WRITE , 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 );
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) {
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));
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);
}