From a7d654ad0a46bd0d203043a490686710ee344671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20D=C3=B6rfler?= Date: Sat, 18 Jan 2003 14:11:02 +0000 Subject: [PATCH] Added almost complete node monitor implementation - support for watching mounts/ unmounts is not yet implemented. Added an empty implementation for send_notification() - the function that is (or will be) able to send out BMessages from within the kernel; currently just prints out what should be done. git-svn-id: file:///srv/svn/repos/haiku/trunk/current@2481 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- src/kernel/core/fs/message.c | 39 +++ src/kernel/core/fs/node_monitor.c | 485 ++++++++++++++++++++++++++++++ 2 files changed, 524 insertions(+) create mode 100644 src/kernel/core/fs/message.c create mode 100644 src/kernel/core/fs/node_monitor.c diff --git a/src/kernel/core/fs/message.c b/src/kernel/core/fs/message.c new file mode 100644 index 0000000000..b2fe09fb21 --- /dev/null +++ b/src/kernel/core/fs/message.c @@ -0,0 +1,39 @@ +/* +** Copyright 2003, Axel Dörfler, axeld@pinc-software.de. All rights reserved. +** Distributed under the terms of the OpenBeOS License. +*/ + + +#include +#include +#include + + +#define MESSAGE_HEADER 'FOB1' + +/* +static void +init_message(uint8 **_buffer, uint32 what, uint32 token) +{ + uint32 *header = *_buffer; + *header++ = MESSAGE_HEADER; + + *_buffer = (uint8 *)header; +} +*/ + +status_t +send_notification(port_id port, long token, ulong what, long op, mount_id device, + mount_id toDevice, vnode_id parentNode, vnode_id toParentNode, + vnode_id node, const char *name) +{ + // this is currently the BeOS compatible send_notification() function + // and will probably stay that way, not yet implemented, though + + dprintf("send_notification(port = %ld, token = %ld, op = %ld, device = %ld, " + "node = %Ld, parentNode = %Ld, toParentNode = %Ld, name = \"%s\"\n", + port, token, op, device, node, parentNode, toParentNode, name); + return 0; +} + + diff --git a/src/kernel/core/fs/node_monitor.c b/src/kernel/core/fs/node_monitor.c new file mode 100644 index 0000000000..1682e7123a --- /dev/null +++ b/src/kernel/core/fs/node_monitor.c @@ -0,0 +1,485 @@ +/* +** Copyright 2003, Axel Dörfler, axeld@pinc-software.de. All rights reserved. +** Distributed under the terms of the OpenBeOS License. +*/ + + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + + +#define TRACE_MONITOR 0 +#if TRACE_MONITOR +# define TRACE(x) dprintf x +#else +# define TRACE(x) ; +#endif + + +// ToDo: add more fine grained locking - maybe using a ref_count in the +// node_monitor structure? +// ToDo: implement watching mounts/unmounts +// ToDo: return another error code than B_NO_MEMORY if the team's maximum is hit + + +typedef struct monitor_listener monitor_listener; +typedef struct node_monitor node_monitor; + +struct monitor_listener { + monitor_listener *next; + monitor_listener *prev; + list_link monitor_link; + port_id port; + uint32 token; + uint32 flags; + node_monitor *monitor; +}; + +struct node_monitor { + node_monitor *next; + mount_id device; + vnode_id node; + struct list listeners; +}; + +struct monitor_hash_key { + mount_id device; + vnode_id node; +}; + +#define MONITORS_HASH_TABLE_SIZE 16 + +static hash_table *gMonitorHash; +static mutex gMonitorMutex; + + +static int +monitor_compare(void *_monitor, const void *_key) +{ + node_monitor *monitor = _monitor; + const struct monitor_hash_key *key = _key; + + if (monitor->device == key->device && monitor->node == key->node) + return 0; + + return -1; +} + + +static uint32 +monitor_hash(void *_monitor, const void *_key, uint32 range) +{ + node_monitor *monitor = _monitor; + const struct monitor_hash_key *key = _key; + +#define MHASH(device, node) (((uint32)((node) >> 32) + (uint32)(node)) ^ (uint32)(device)) + + if (monitor != NULL) + return (MHASH(monitor->device, monitor->node) % range); + + return (MHASH(key->device, key->node) % range); +#undef MHASH +} + + +/** Returns the monitor that matches the specified device/node pair. + * Must be called with monitors lock hold. + */ + +static node_monitor * +get_monitor_for(mount_id device, vnode_id node) +{ + struct monitor_hash_key key; + key.device = device; + key.node = node; + + return (node_monitor *)hash_lookup(gMonitorHash, &key); +} + + +/** Returns the listener that matches the specified port/token pair. + * Must be called with monitors lock hold. + */ + +static monitor_listener * +get_listener_for(node_monitor *monitor, port_id port, uint32 token) +{ + monitor_listener *listener = NULL; + + while ((listener = list_get_next_item(&monitor->listeners, listener)) != NULL) { + // does this listener match? + if (listener->port == port && listener->token == token) + return listener; + } + + return NULL; +} + + +/** Removes the specified node_monitor from the hashtable + * and free it. + * Must be called with monitors lock hold. + */ + +static void +remove_monitor(node_monitor *monitor) +{ + hash_remove(gMonitorHash, monitor); + free(monitor); +} + + +/** Removes the specified monitor_listener from all lists + * and free it. + * Must be called with monitors lock hold. + */ + +static void +remove_listener(monitor_listener *listener) +{ + node_monitor *monitor = listener->monitor; + + // remove it from the listener and I/O context lists + list_remove_link(&listener->monitor_link); + list_remove_link(listener); + + free(listener); + + if (list_is_empty(&monitor->listeners)) + remove_monitor(monitor); +} + + +static status_t +add_node_monitor(io_context *context, mount_id device, vnode_id node, + uint32 flags, port_id port, uint32 token) +{ + monitor_listener *listener; + node_monitor *monitor; + status_t status = B_OK; + + TRACE(("add_node_monitor(dev = %ld, node = %Ld, flags = %ld, port = %ld, token = %ld\n", + device, node, flags, port, token)); + + mutex_lock(&gMonitorMutex); + + monitor = get_monitor_for(device, node); + if (monitor == NULL) { + // check if this team is allowed to have more listeners + if (context->num_monitors >= context->max_monitors) { + // the BeBook says to return B_NO_MEMORY in this case, but + // we should have another one. + status = B_NO_MEMORY; + goto out; + } + + // create new monitor + monitor = (node_monitor *)malloc(sizeof(node_monitor)); + if (monitor == NULL) { + status = B_NO_MEMORY; + goto out; + } + + // initialize monitor + monitor->device = device; + monitor->node = node; + list_init_etc(&monitor->listeners, offsetof(monitor_listener, monitor_link)); + + hash_insert(gMonitorHash, monitor); + } else { + // check if the listener is already listening, and + // if so, just add the new flags + + listener = get_listener_for(monitor, port, token); + if (listener != NULL) { + listener->flags |= flags; + goto out; + } + + // check if this team is allowed to have more listeners + if (context->num_monitors >= context->max_monitors) { + // the BeBook says to return B_NO_MEMORY in this case, but + // we should have another one. + status = B_NO_MEMORY; + goto out; + } + } + + // add listener + + listener = (monitor_listener *)malloc(sizeof(monitor_listener)); + if (listener == NULL) { + // no memory for the listener, so remove the monitor as well if needed + if (list_is_empty(&monitor->listeners)) + remove_monitor(monitor); + + status = B_NO_MEMORY; + goto out; + } + + // initialize listener, and add it to the lists + listener->port = port; + listener->token = token; + listener->flags = flags; + listener->monitor = monitor; + + list_add_link_to_head(&monitor->listeners, &listener->monitor_link); + list_add_link_to_head(&context->node_monitors, listener); + + context->num_monitors++; + +out: + mutex_unlock(&gMonitorMutex); + return status; +} + + +static status_t +remove_node_monitor(struct io_context *context, mount_id device, vnode_id node, + uint32 flags, port_id port, uint32 token) +{ + monitor_listener *listener; + node_monitor *monitor; + status_t status = B_OK; + + TRACE(("remove_node_monitor(dev = %ld, node = %Ld, flags = %ld, port = %ld, token = %ld\n", + device, node, flags, port, token)); + + mutex_lock(&gMonitorMutex); + + // get the monitor for this device/node pair + monitor = get_monitor_for(device, node); + if (monitor == NULL) { + status = B_ENTRY_NOT_FOUND; + goto out; + } + + // see if it has the listener we are looking for + listener = get_listener_for(monitor, port, token); + if (listener == NULL) { + status = B_ENTRY_NOT_FOUND; + goto out; + } + + // no flags means remove all flags + if (flags == B_STOP_WATCHING) + flags = ~0; + + listener->flags &= ~flags; + + // if there aren't anymore flags, remove this listener + if (listener->flags == B_STOP_WATCHING) { + remove_listener(listener); + context->num_monitors--; + } + +out: + mutex_unlock(&gMonitorMutex); + return status; +} + + +static status_t +remove_node_monitors_by_target(struct io_context *context, port_id port, uint32 token) +{ + monitor_listener *listener = NULL; + int32 count = 0; + + while ((listener = list_get_next_item(&context->node_monitors, listener)) != NULL) { + monitor_listener *removeListener; + + if (listener->port != port || listener->token != token) + continue; + + listener = list_get_prev_item(&context->node_monitors, removeListener = listener); + // this line sets the listener one item back, allowing us + // to remove its successor (which is saved in "removeListener") + + remove_listener(removeListener); + count++; + } + + return count > 0 ? B_OK : B_ENTRY_NOT_FOUND; +} + + +// #pragma mark - +// Exported private kernel API + + +status_t +remove_node_monitors(struct io_context *context) +{ + mutex_lock(&gMonitorMutex); + + while (!list_is_empty(&context->node_monitors)) { + // the remove_listener() function will also free the node_monitor + // if it doesn't have any listeners attached anymore + remove_listener(list_get_first_item(&context->node_monitors)); + } + + mutex_unlock(&gMonitorMutex); + return B_OK; +} + + +status_t +node_monitor_init(void) +{ + gMonitorHash = hash_init(MONITORS_HASH_TABLE_SIZE, offsetof(node_monitor, next), + &monitor_compare, &monitor_hash); + if (gMonitorHash == NULL) + panic("node_monitor_init: error creating mounts hash table\n"); + + return mutex_init(&gMonitorMutex, "node monitor"); +} + + +// #pragma mark - +// Exported public kernel API + + +status_t +start_watching(dev_t device, ino_t node, uint32 flags, port_id port, uint32 token) +{ + io_context *context = get_current_io_context(true); + + return add_node_monitor(context, device, node, flags, port, token); +} + + +status_t +stop_watching(dev_t device, ino_t node, uint32 flags, port_id port, uint32 token) +{ + io_context *context = get_current_io_context(true); + + return remove_node_monitor(context, device, node, flags, port, token); +} + + +status_t +stop_notifying(port_id port, uint32 token) +{ + io_context *context = get_current_io_context(true); + + return remove_node_monitors_by_target(context, port, token); +} + + +status_t +notify_listener(int op, mount_id device, vnode_id parentNode, vnode_id toParentNode, + vnode_id node, const char *name) +{ + monitor_listener *listener; + node_monitor *monitor; + status_t status = B_OK; + + TRACE(("notify_listener(op = %d, device = %ld, node = %Ld, parent = %Ld, toParent = %Ld" + ", name = \"%s\"\n", op, device, node, parentNode, toParentNode, name)); + + mutex_lock(&gMonitorMutex); + + // check the main "node" + + if ((op == B_ENTRY_MOVED + || op == B_ENTRY_REMOVED + || op == B_STAT_CHANGED + || op == B_ATTR_CHANGED) + && (monitor = get_monitor_for(device, node)) != NULL) { + // iterate over all listeners for this monitor, and see + // if we have to send anything + listener = NULL; + while ((listener = list_get_next_item(&monitor->listeners, listener)) != NULL) { + // do we have a reason to notify this listener? + if (((listener->flags & B_WATCH_NAME) != 0 + && (op == B_ENTRY_MOVED || op == B_ENTRY_REMOVED)) + || ((listener->flags & B_WATCH_STAT) != 0 + && op == B_STAT_CHANGED) + || ((listener->flags & B_WATCH_ATTR) != 0 + && op == B_ATTR_CHANGED)) { + // then do it! + send_notification(listener->port, listener->token, B_NODE_MONITOR, + op, device, 0, parentNode, toParentNode, node, name); + } + } + } + + // check its parent directory + + if ((op == B_ENTRY_MOVED + || op == B_ENTRY_REMOVED + || op == B_ENTRY_CREATED) + && (monitor = get_monitor_for(device, parentNode)) != NULL) { + // iterate over all listeners for this monitor, and see + // if we have to send anything + listener = NULL; + while ((listener = list_get_next_item(&monitor->listeners, listener)) != NULL) { + // do we have a reason to notify this listener? + if ((listener->flags & B_WATCH_DIRECTORY) != 0) { + send_notification(listener->port, listener->token, B_NODE_MONITOR, + op, device, 0, parentNode, toParentNode, node, name); + } + } + } + + // check its new target parent directory + + if (op == B_ENTRY_MOVED + && (monitor = get_monitor_for(device, toParentNode)) != NULL) { + // iterate over all listeners for this monitor, and see + // if we have to send anything + listener = NULL; + while ((listener = list_get_next_item(&monitor->listeners, listener)) != NULL) { + // do we have a reason to notify this listener? + if ((listener->flags & B_WATCH_DIRECTORY) != 0) { + send_notification(listener->port, listener->token, B_NODE_MONITOR, + B_ENTRY_MOVED, device, 0, parentNode, toParentNode, node, name); + } + } + } + + mutex_unlock(&gMonitorMutex); + return B_OK; +} + + +// #pragma mark - +// Userland syscalls + + +status_t +user_stop_notifying(port_id port, uint32 token) +{ + io_context *context = get_current_io_context(false); + + return remove_node_monitors_by_target(context, port, token); +} + + +status_t +user_start_watching(dev_t device, ino_t node, uint32 flags, port_id port, uint32 token) +{ + io_context *context = get_current_io_context(false); + + return add_node_monitor(context, device, node, flags, port, token); +} + + +status_t +user_stop_watching(dev_t device, ino_t node, uint32 flags, port_id port, uint32 token) +{ + io_context *context = get_current_io_context(false); + + return remove_node_monitor(context, device, node, flags, port, token); +} +