/* ** Copyright 2003-2004, 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 #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; }; struct interested_monitor_listener_list { struct list *listeners; monitor_listener *first_listener; uint32 flags; }; #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 = (node_monitor*)_monitor; const struct monitor_hash_key *key = (const struct monitor_hash_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 = (node_monitor*)_monitor; const struct monitor_hash_key *key = (const struct monitor_hash_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 = (monitor_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 = (monitor_listener*)list_get_next_item( &context->node_monitors, listener)) != NULL) { monitor_listener *removeListener; if (listener->port != port || listener->token != token) continue; listener = (monitor_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( (monitor_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 /** \brief Subscribes a target to node and/or mount watching. * * Depending on \a flags, different actions are performed. If flags is \c 0, * mount watching is requested. \a device and \a node must be \c -1 in this * case. Otherwise node watching is requested. \a device and \a node must * refer to a valid node, and \a flags must note contain the flag * \c B_WATCH_MOUNT, but at least one of the other valid flags. * * \param device The device the node resides on (node_ref::device). \c -1, if * only mount watching is requested. * \param node The node ID of the node (node_ref::device). \c -1, if * only mount watching is requested. * \param flags A bit mask composed of the values specified in * . * \param port The port of the target (a looper port). * \param handlerToken The token of the target handler. \c -2, if the * preferred handler of the looper is the target. * \return \c B_OK, if everything went fine, another error code otherwise. */ 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); } /** \brief Unsubscribes a target from watching a node. * \param device The device the node resides on (node_ref::device). * \param node The node ID of the node (node_ref::device). * \param flags Which monitors should be removed (B_STOP_WATCHING for all) * \param port The port of the target (a looper port). * \param handlerToken The token of the target handler. \c -2, if the * preferred handler of the looper is the target. * \return \c B_OK, if everything went fine, another error code otherwise. */ 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); } /** \brief Unsubscribes a target from node and mount monitoring. * \param port The port of the target (a looper port). * \param handlerToken The token of the target handler. \c -2, if the * preferred handler of the looper is the target. * \return \c B_OK, if everything went fine, another error code otherwise. */ 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; 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 = (monitor_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 = (monitor_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 = (monitor_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; } /** \brief Given device and node ID and a node monitoring event mask, the function checks whether there are listeners interested in any of the events for that node and, if so, adds the respective listener list to a supplied array of listener lists. Note, that in general not all of the listeners in an appended list will be interested in the events, but it is guaranteed that interested_monitor_listener_list::first_listener is indeed the first listener in the list, that is interested. \param device The ID of the mounted FS, the node lives in. \param node The ID of the node. \param flags The mask specifying the events occurred for the given node (a combination of \c B_WATCH_* constants). \param interestedListeners An array of listener lists. If there are interested listeners for the node, the list will be appended to this array. \param interestedListenerCount The number of elements in the \a interestedListeners array. Will be incremented, if a list is appended. */ static void get_interested_monitor_listeners(mount_id device, vnode_id node, uint32 flags, interested_monitor_listener_list *interestedListeners, int32 &interestedListenerCount) { // get the monitor for the node node_monitor *monitor = get_monitor_for(device, node); if (!monitor) return; // iterate through the listeners until we find one with matching flags monitor_listener *listener = NULL; while ((listener = (monitor_listener*)list_get_next_item( &monitor->listeners, listener)) != NULL) { if (listener->flags & flags) { interested_monitor_listener_list &list = interestedListeners[interestedListenerCount++]; list.listeners = &monitor->listeners; list.first_listener = listener; list.flags = flags; return; } } } /** \brief Sends a notifcation message to the given listeners. \param message The message to be sent. \param interestedListeners An array of listener lists. \param interestedListenerCount The number of elements in the \a interestedListeners array. \return - \c B_OK, if everything went fine, - another error code otherwise. */ static status_t send_notification_message(KMessage &message, interested_monitor_listener_list *interestedListeners, int32 interestedListenerCount) { // Since the messaging service supports broadcasting and that is more // efficient than sending the messages individually, we collect the // listener targets in an array and send the message to them at once. const int32 maxTargetCount = 16; messaging_target targets[maxTargetCount]; int32 targetCount = 0; // iterate through the lists interested_monitor_listener_list *list = interestedListeners; for (int32 i = 0; i < interestedListenerCount; i++, list++) { // iterate through the listeners monitor_listener *listener = list->first_listener; do { if (listener->flags & list->flags) { // the listener's flags match: add it to the targets messaging_target &target = targets[targetCount++]; target.port = listener->port; target.token = listener->token; // if the target array is full, send the message if (targetCount == maxTargetCount) { status_t error = send_message(&message, targets, targetCount); if (error != B_OK) return error; targetCount = 0; } } } while ((listener = (monitor_listener*)list_get_next_item( list->listeners, listener)) != NULL); } // if any targets are left (the usual case, unless the target array got // full early), send the message if (targetCount > 0) return send_message(&message, targets, targetCount); return B_OK; } /** \brief Notifies all interested listeners that an entry has been created or removed. \param opcode \c B_ENTRY_CREATED or \c B_ENTRY_REMOVED. \param device The ID of the mounted FS, the entry lives/lived in. \param directory The entry's parent directory ID. \param name The entry's name. \param node The ID of the node the entry refers/referred to. \return - \c B_OK, if everything went fine, - another error code otherwise. */ static status_t notify_entry_created_or_removed(int32 opcode, mount_id device, vnode_id directory, const char *name, vnode_id node) { if (!name) return B_BAD_VALUE; MutexLocker locker(gMonitorMutex); // get the lists of all interested listeners interested_monitor_listener_list interestedListeners[3]; int32 interestedListenerCount = 0; // ... for the node if (opcode != B_ENTRY_CREATED) { get_interested_monitor_listeners(device, node, B_WATCH_NAME, interestedListeners, interestedListenerCount); } // ... for the directory get_interested_monitor_listeners(device, directory, B_WATCH_DIRECTORY, interestedListeners, interestedListenerCount); if (interestedListenerCount == 0) return B_OK; // there are interested listeners: construct the message and send it char messageBuffer[1024]; KMessage message; message.SetTo(messageBuffer, sizeof(messageBuffer), B_NODE_MONITOR); message.AddInt32("opcode", opcode); message.AddInt32("device", device); message.AddInt64("directory", directory); message.AddInt64("node", node); message.AddString("name", name); // for "removed" Haiku only return send_notification_message(message, interestedListeners, interestedListenerCount); } /** \brief Notifies all interested listeners that an entry has been created. \param device The ID of the mounted FS, the entry lives in. \param directory The entry's parent directory ID. \param name The entry's name. \param node The ID of the node the entry refers to. \return - \c B_OK, if everything went fine, - another error code otherwise. */ status_t notify_entry_created(mount_id device, vnode_id directory, const char *name, vnode_id node) { return notify_entry_created_or_removed(B_ENTRY_CREATED, device, directory, name, node); } /** \brief Notifies all interested listeners that an entry has been removed. \param device The ID of the mounted FS, the entry lived in. \param directory The entry's former parent directory ID. \param name The entry's name. \param node The ID of the node the entry referred to. \return - \c B_OK, if everything went fine, - another error code otherwise. */ status_t notify_entry_removed(mount_id device, vnode_id directory, const char *name, vnode_id node) { return notify_entry_created_or_removed(B_ENTRY_REMOVED, device, directory, name, node); } /** \brief Notifies all interested listeners that an entry has been moved. \param device The ID of the mounted FS, the entry lives in. \param fromDirectory The entry's previous parent directory ID. \param fromName The entry's previous name. \param toDirectory The entry's new parent directory ID. \param toName The entry's new name. \param node The ID of the node the entry refers to. \return - \c B_OK, if everything went fine, - another error code otherwise. */ status_t notify_entry_moved(mount_id device, vnode_id fromDirectory, const char *fromName, vnode_id toDirectory, const char *toName, vnode_id node) { if (!fromName || !toName) return B_BAD_VALUE; // If node is a mount point, we need to resolve it to the mounted // volume's root node. mount_id nodeDevice = device; resolve_mount_point_to_volume_root(device, node, &nodeDevice, &node); MutexLocker locker(gMonitorMutex); // get the lists of all interested listeners interested_monitor_listener_list interestedListeners[3]; int32 interestedListenerCount = 0; // ... for the node get_interested_monitor_listeners(nodeDevice, node, B_WATCH_NAME, interestedListeners, interestedListenerCount); // ... for the source directory get_interested_monitor_listeners(device, fromDirectory, B_WATCH_DIRECTORY, interestedListeners, interestedListenerCount); // ... for the target directory get_interested_monitor_listeners(device, toDirectory, B_WATCH_DIRECTORY, interestedListeners, interestedListenerCount); if (interestedListenerCount == 0) return B_OK; // there are interested listeners: construct the message and send it char messageBuffer[1024]; KMessage message; message.SetTo(messageBuffer, sizeof(messageBuffer), B_NODE_MONITOR); message.AddInt32("opcode", B_ENTRY_MOVED); message.AddInt32("device", device); message.AddInt64("from directory", fromDirectory); message.AddInt64("to directory", toDirectory); message.AddInt32("node device", nodeDevice); // Haiku only message.AddInt64("node", node); message.AddString("from name", fromName); // Haiku only message.AddString("name", toName); return send_notification_message(message, interestedListeners, interestedListenerCount); } /** \brief Notifies all interested listeners that a node's stat data have changed. \param device The ID of the mounted FS, the node lives in. \param node The ID of the node. \param statFields A bitwise combination of one or more of the \c B_STAT_* constants defined in , indicating what fields of the stat data have changed. \return - \c B_OK, if everything went fine, - another error code otherwise. */ status_t notify_stat_changed(mount_id device, vnode_id node, uint32 statFields) { MutexLocker locker(gMonitorMutex); // get the lists of all interested listeners interested_monitor_listener_list interestedListeners[3]; int32 interestedListenerCount = 0; // ... for the node get_interested_monitor_listeners(device, node, B_WATCH_STAT, interestedListeners, interestedListenerCount); if (interestedListenerCount == 0) return B_OK; // there are interested listeners: construct the message and send it char messageBuffer[1024]; KMessage message; message.SetTo(messageBuffer, sizeof(messageBuffer), B_NODE_MONITOR); message.AddInt32("opcode", B_STAT_CHANGED); message.AddInt32("device", device); message.AddInt64("node", node); message.AddInt32("fields", statFields); // Haiku only return send_notification_message(message, interestedListeners, interestedListenerCount); } /** \brief Notifies all interested listeners that a node attribute has changed. \param device The ID of the mounted FS, the node lives in. \param node The ID of the node. \param attribute The attribute's name. \param cause Either of \c B_ATTR_CREATED, \c B_ATTR_REMOVED, or \c B_ATTR_CHANGED, indicating what exactly happened to the attribute. \return - \c B_OK, if everything went fine, - another error code otherwise. */ status_t notify_attribute_changed(mount_id device, vnode_id node, const char *attribute, int32 cause) { if (!attribute) return B_BAD_VALUE; MutexLocker locker(gMonitorMutex); // get the lists of all interested listeners interested_monitor_listener_list interestedListeners[3]; int32 interestedListenerCount = 0; // ... for the node get_interested_monitor_listeners(device, node, B_WATCH_ATTR, interestedListeners, interestedListenerCount); if (interestedListenerCount == 0) return B_OK; // there are interested listeners: construct the message and send it char messageBuffer[1024]; KMessage message; message.SetTo(messageBuffer, sizeof(messageBuffer), B_NODE_MONITOR); message.AddInt32("opcode", B_ATTR_CHANGED); message.AddInt32("device", device); message.AddInt64("node", node); message.AddString("attr", attribute); message.AddInt32("cause", cause); // Haiku only return send_notification_message(message, interestedListeners, interestedListenerCount); } /** \brief Notifies the listener of a live query that an entry has been added to or removed from the query (for whatever reason). \param opcode \c B_ENTRY_CREATED or \c B_ENTRY_REMOVED. \param port The target port of the listener. \param token The BHandler token of the listener. \param device The ID of the mounted FS, the entry lives in. \param directory The entry's parent directory ID. \param name The entry's name. \param node The ID of the node the entry refers to. \return - \c B_OK, if everything went fine, - another error code otherwise. */ static status_t notify_query_entry_created_or_removed(int32 opcode, port_id port, int32 token, mount_id device, vnode_id directory, const char *name, vnode_id node) { if (!name) return B_BAD_VALUE; // construct the message char messageBuffer[1024]; KMessage message; message.SetTo(messageBuffer, sizeof(messageBuffer), B_QUERY_UPDATE); message.AddInt32("opcode", opcode); message.AddInt32("device", device); message.AddInt64("directory", directory); message.AddInt64("node", node); message.AddString("name", name); // send the message messaging_target target; target.port = port; target.token = token; return send_message(&message, &target, 1); } /** \brief Notifies the listener of a live query that an entry has been added to the query (for whatever reason). \param port The target port of the listener. \param token The BHandler token of the listener. \param device The ID of the mounted FS, the entry lives in. \param directory The entry's parent directory ID. \param name The entry's name. \param node The ID of the node the entry refers to. \return - \c B_OK, if everything went fine, - another error code otherwise. */ status_t notify_query_entry_created(port_id port, int32 token, mount_id device, vnode_id directory, const char *name, vnode_id node) { return notify_query_entry_created_or_removed(B_ENTRY_CREATED, port, token, device, directory, name, node); } /** \brief Notifies the listener of a live query that an entry has been removed from the query (for whatever reason). \param port The target port of the listener. \param token The BHandler token of the listener. \param device The ID of the mounted FS, the entry lives in. \param directory The entry's parent directory ID. \param name The entry's name. \param node The ID of the node the entry refers to. \return - \c B_OK, if everything went fine, - another error code otherwise. */ status_t notify_query_entry_removed(port_id port, int32 token, mount_id device, vnode_id directory, const char *name, vnode_id node) { return notify_query_entry_created_or_removed(B_ENTRY_REMOVED, port, token, device, directory, name, node); } // #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); }