3130aa793d
allows event listeners to add new listeners inside a node monitoring event (i.e. if one wants to add a listener for the entry that has just been created and triggered the current notification). git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@24760 a95241bf-73f2-0310-859d-f6bbb57e9c96
1180 lines
34 KiB
C++
1180 lines
34 KiB
C++
/*
|
|
* Copyright 2003-2008, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
|
|
* Copyright 2005-2008, Ingo Weinhold, bonefish@users.sf.net.
|
|
*
|
|
* Distributed under the terms of the MIT License.
|
|
*/
|
|
|
|
|
|
#include <fs/node_monitor.h>
|
|
|
|
#include <stddef.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <AppDefs.h>
|
|
#include <NodeMonitor.h>
|
|
|
|
#include <fd.h>
|
|
#include <khash.h>
|
|
#include <lock.h>
|
|
#include <messaging.h>
|
|
#include <Notifications.h>
|
|
#include <vfs.h>
|
|
#include <util/AutoLock.h>
|
|
#include <util/DoublyLinkedList.h>
|
|
#include <util/KMessage.h>
|
|
#include <util/list.h>
|
|
|
|
|
|
//#define TRACE_MONITOR
|
|
#ifdef 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: 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 {
|
|
list_link context_link;
|
|
DoublyLinkedListLink<monitor_listener> monitor_link;
|
|
NotificationListener *listener;
|
|
uint32 flags;
|
|
node_monitor *monitor;
|
|
};
|
|
|
|
typedef DoublyLinkedList<monitor_listener, DoublyLinkedListMemberGetLink<
|
|
monitor_listener, &monitor_listener::monitor_link> > MonitorListenerList;
|
|
|
|
struct node_monitor {
|
|
HashTableLink<node_monitor> link;
|
|
dev_t device;
|
|
ino_t node;
|
|
MonitorListenerList listeners;
|
|
};
|
|
|
|
struct interested_monitor_listener_list {
|
|
MonitorListenerList::Iterator iterator;
|
|
uint32 flags;
|
|
};
|
|
|
|
static UserMessagingMessageSender sNodeMonitorSender;
|
|
|
|
class UserNodeListener : public UserMessagingListener {
|
|
public:
|
|
UserNodeListener(port_id port, int32 token)
|
|
: UserMessagingListener(sNodeMonitorSender, port, token)
|
|
{
|
|
}
|
|
|
|
bool operator==(const NotificationListener& _other) const
|
|
{
|
|
const UserNodeListener* other
|
|
= dynamic_cast<const UserNodeListener*>(&_other);
|
|
return other != NULL && other->Port() == Port()
|
|
&& other->Token() == Token();
|
|
}
|
|
};
|
|
|
|
class NodeMonitorService : public NotificationService {
|
|
public:
|
|
NodeMonitorService();
|
|
virtual ~NodeMonitorService();
|
|
|
|
status_t InitCheck();
|
|
|
|
status_t NotifyEntryCreatedOrRemoved(int32 opcode, dev_t device,
|
|
ino_t directory, const char *name, ino_t node);
|
|
status_t NotifyEntryMoved(dev_t device, ino_t fromDirectory,
|
|
const char *fromName, ino_t toDirectory, const char *toName,
|
|
ino_t node);
|
|
status_t NotifyStatChanged(dev_t device, ino_t node, uint32 statFields);
|
|
status_t NotifyAttributeChanged(dev_t device, ino_t node,
|
|
const char *attribute, int32 cause);
|
|
status_t NotifyUnmount(dev_t device);
|
|
status_t NotifyMount(dev_t device, dev_t parentDevice,
|
|
ino_t parentDirectory);
|
|
|
|
status_t RemoveListeners(io_context *context);
|
|
|
|
status_t AddListener(const KMessage *eventSpecifier,
|
|
NotificationListener &listener);
|
|
status_t UpdateListener(const KMessage *eventSpecifier,
|
|
NotificationListener &listener);
|
|
status_t RemoveListener(const KMessage *eventSpecifier,
|
|
NotificationListener &listener);
|
|
|
|
status_t AddListener(io_context *context, dev_t device, ino_t node,
|
|
uint32 flags, NotificationListener ¬ificationListener);
|
|
status_t RemoveListener(io_context *context, dev_t device, ino_t node,
|
|
NotificationListener ¬ificationListener);
|
|
|
|
status_t RemoveUserListeners(struct io_context *context,
|
|
port_id port, uint32 token);
|
|
status_t UpdateUserListener(io_context *context, dev_t device,
|
|
ino_t node, uint32 flags, UserNodeListener &userListener);
|
|
|
|
virtual const char* Name() { return "node monitor"; }
|
|
|
|
private:
|
|
void _RemoveMonitor(node_monitor *monitor);
|
|
void _RemoveListener(monitor_listener *listener);
|
|
node_monitor *_MonitorFor(dev_t device, ino_t node);
|
|
status_t _GetMonitor(io_context *context, dev_t device, ino_t node,
|
|
bool addIfNecessary, node_monitor **_monitor);
|
|
monitor_listener *_MonitorListenerFor(node_monitor* monitor,
|
|
NotificationListener& notificationListener);
|
|
status_t _AddMonitorListener(io_context *context,
|
|
node_monitor* monitor, uint32 flags,
|
|
NotificationListener& notificationListener);
|
|
status_t _UpdateListener(io_context *context, dev_t device, ino_t node,
|
|
uint32 flags, bool addFlags,
|
|
NotificationListener ¬ificationListener);
|
|
void _GetInterestedMonitorListeners(dev_t device, ino_t node,
|
|
uint32 flags, interested_monitor_listener_list *interestedListeners,
|
|
int32 &interestedListenerCount);
|
|
status_t _SendNotificationMessage(KMessage &message,
|
|
interested_monitor_listener_list *interestedListeners,
|
|
int32 interestedListenerCount);
|
|
|
|
struct monitor_hash_key {
|
|
dev_t device;
|
|
ino_t node;
|
|
};
|
|
|
|
struct HashDefinition {
|
|
typedef monitor_hash_key* KeyType;
|
|
typedef node_monitor ValueType;
|
|
|
|
size_t HashKey(monitor_hash_key* key) const
|
|
{ return _Hash(key->device, key->node); }
|
|
size_t Hash(node_monitor *monitor) const
|
|
{ return _Hash(monitor->device, monitor->node); }
|
|
|
|
bool Compare(monitor_hash_key* key, node_monitor *monitor) const
|
|
{
|
|
return key->device == monitor->device
|
|
&& key->node == monitor->node;
|
|
}
|
|
|
|
HashTableLink<node_monitor>* GetLink(
|
|
node_monitor* monitor) const
|
|
{ return &monitor->link; }
|
|
|
|
uint32 _Hash(dev_t device, ino_t node) const
|
|
{
|
|
return ((uint32)(node >> 32) + (uint32)node) ^ (uint32)device;
|
|
}
|
|
};
|
|
|
|
typedef OpenHashTable<HashDefinition> MonitorHash;
|
|
MonitorHash fMonitors;
|
|
recursive_lock fRecursiveLock;
|
|
};
|
|
|
|
static NodeMonitorService sNodeMonitorService;
|
|
|
|
|
|
/*! \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,
|
|
dev_t device, ino_t directory, const char *name, ino_t 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);
|
|
}
|
|
|
|
|
|
// #pragma mark - NodeMonitorService
|
|
|
|
|
|
NodeMonitorService::NodeMonitorService()
|
|
{
|
|
recursive_lock_init(&fRecursiveLock, "node monitor");
|
|
}
|
|
|
|
|
|
NodeMonitorService::~NodeMonitorService()
|
|
{
|
|
recursive_lock_destroy(&fRecursiveLock);
|
|
}
|
|
|
|
|
|
status_t
|
|
NodeMonitorService::InitCheck()
|
|
{
|
|
return fRecursiveLock.sem >= B_OK ? B_OK : fRecursiveLock.sem;
|
|
}
|
|
|
|
|
|
/*! Removes the specified node_monitor from the hashtable
|
|
and free it.
|
|
Must be called with monitors lock hold.
|
|
*/
|
|
void
|
|
NodeMonitorService::_RemoveMonitor(node_monitor *monitor)
|
|
{
|
|
fMonitors.Remove(monitor);
|
|
delete monitor;
|
|
}
|
|
|
|
|
|
/*! Removes the specified monitor_listener from all lists
|
|
and free it.
|
|
Must be called with monitors lock hold.
|
|
*/
|
|
void
|
|
NodeMonitorService::_RemoveListener(monitor_listener *listener)
|
|
{
|
|
node_monitor *monitor = listener->monitor;
|
|
|
|
// remove it from the listener and I/O context lists
|
|
monitor->listeners.Remove(listener);
|
|
list_remove_link(listener);
|
|
|
|
delete listener;
|
|
|
|
if (monitor->listeners.IsEmpty())
|
|
_RemoveMonitor(monitor);
|
|
}
|
|
|
|
|
|
/*! Returns the monitor that matches the specified device/node pair.
|
|
Must be called with monitors lock hold.
|
|
*/
|
|
node_monitor *
|
|
NodeMonitorService::_MonitorFor(dev_t device, ino_t node)
|
|
{
|
|
struct monitor_hash_key key;
|
|
key.device = device;
|
|
key.node = node;
|
|
|
|
return fMonitors.Lookup(&key);
|
|
}
|
|
|
|
|
|
/*! Returns the monitor that matches the specified device/node pair.
|
|
If the monitor does not exist yet, it will be created.
|
|
Must be called with monitors lock hold.
|
|
*/
|
|
status_t
|
|
NodeMonitorService::_GetMonitor(io_context *context, dev_t device, ino_t node,
|
|
bool addIfNecessary, node_monitor** _monitor)
|
|
{
|
|
node_monitor* monitor = _MonitorFor(device, node);
|
|
if (monitor != NULL) {
|
|
*_monitor = monitor;
|
|
return B_OK;
|
|
}
|
|
if (!addIfNecessary)
|
|
return B_BAD_VALUE;
|
|
|
|
// 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.
|
|
return B_NO_MEMORY;
|
|
}
|
|
|
|
// create new monitor
|
|
monitor = new(std::nothrow) node_monitor;
|
|
if (monitor == NULL)
|
|
return B_NO_MEMORY;
|
|
|
|
// initialize monitor
|
|
monitor->device = device;
|
|
monitor->node = node;
|
|
|
|
if (fMonitors.Insert(monitor) < B_OK) {
|
|
delete monitor;
|
|
return B_NO_MEMORY;
|
|
}
|
|
|
|
*_monitor = monitor;
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
/*! Returns the listener that matches the specified port/token pair.
|
|
Must be called with monitors lock hold.
|
|
*/
|
|
monitor_listener*
|
|
NodeMonitorService::_MonitorListenerFor(node_monitor* monitor,
|
|
NotificationListener& notificationListener)
|
|
{
|
|
MonitorListenerList::Iterator iterator
|
|
= monitor->listeners.GetIterator();
|
|
|
|
while (monitor_listener* listener = iterator.Next()) {
|
|
// does this listener match?
|
|
if (*listener->listener == notificationListener)
|
|
return listener;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
status_t
|
|
NodeMonitorService::_AddMonitorListener(io_context *context,
|
|
node_monitor* monitor, uint32 flags,
|
|
NotificationListener& notificationListener)
|
|
{
|
|
monitor_listener *listener = new(std::nothrow) monitor_listener;
|
|
if (listener == NULL) {
|
|
// no memory for the listener, so remove the monitor as well if needed
|
|
if (monitor->listeners.IsEmpty())
|
|
_RemoveMonitor(monitor);
|
|
|
|
return B_NO_MEMORY;
|
|
}
|
|
|
|
// initialize listener, and add it to the lists
|
|
listener->listener = ¬ificationListener;
|
|
listener->flags = flags;
|
|
listener->monitor = monitor;
|
|
|
|
monitor->listeners.Add(listener);
|
|
list_add_link_to_head(&context->node_monitors, listener);
|
|
|
|
context->num_monitors++;
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
status_t
|
|
NodeMonitorService::AddListener(io_context *context, dev_t device, ino_t node,
|
|
uint32 flags, NotificationListener& notificationListener)
|
|
{
|
|
TRACE(("%s(dev = %ld, node = %Ld, flags = %ld, listener = %p\n",
|
|
__PRETTY_FUNCTION__, device, node, flags, ¬ificationListener));
|
|
|
|
RecursiveLocker _(fRecursiveLock);
|
|
|
|
node_monitor *monitor;
|
|
status_t status = _GetMonitor(context, device, node, true, &monitor);
|
|
if (status < B_OK)
|
|
return status;
|
|
|
|
// add listener
|
|
|
|
return _AddMonitorListener(context, monitor, flags, notificationListener);
|
|
}
|
|
|
|
|
|
status_t
|
|
NodeMonitorService::_UpdateListener(io_context *context, dev_t device,
|
|
ino_t node, uint32 flags, bool addFlags,
|
|
NotificationListener& notificationListener)
|
|
{
|
|
TRACE(("%s(dev = %ld, node = %Ld, flags = %ld, listener = %p\n",
|
|
__PRETTY_FUNCTION__, device, node, flags, ¬ificationListener));
|
|
|
|
RecursiveLocker _(fRecursiveLock);
|
|
|
|
node_monitor *monitor;
|
|
status_t status = _GetMonitor(context, device, node, false, &monitor);
|
|
if (status < B_OK)
|
|
return status;
|
|
|
|
MonitorListenerList::Iterator iterator = monitor->listeners.GetIterator();
|
|
while (monitor_listener* listener = iterator.Next()) {
|
|
if (*listener->listener == notificationListener) {
|
|
if (addFlags)
|
|
listener->flags |= flags;
|
|
else
|
|
listener->flags = flags;
|
|
return B_OK;
|
|
}
|
|
}
|
|
|
|
return B_BAD_VALUE;
|
|
}
|
|
|
|
|
|
/*! \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.
|
|
*/
|
|
void
|
|
NodeMonitorService::_GetInterestedMonitorListeners(dev_t device, ino_t node,
|
|
uint32 flags, interested_monitor_listener_list *interestedListeners,
|
|
int32 &interestedListenerCount)
|
|
{
|
|
// get the monitor for the node
|
|
node_monitor *monitor = _MonitorFor(device, node);
|
|
if (monitor == NULL)
|
|
return;
|
|
|
|
// iterate through the listeners until we find one with matching flags
|
|
MonitorListenerList::Iterator iterator = monitor->listeners.GetIterator();
|
|
while (monitor_listener *listener = iterator.Next()) {
|
|
if (listener->flags & flags) {
|
|
interested_monitor_listener_list &list
|
|
= interestedListeners[interestedListenerCount++];
|
|
list.iterator = iterator;
|
|
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.
|
|
*/
|
|
status_t
|
|
NodeMonitorService::_SendNotificationMessage(KMessage &message,
|
|
interested_monitor_listener_list *interestedListeners,
|
|
int32 interestedListenerCount)
|
|
{
|
|
// iterate through the lists
|
|
interested_monitor_listener_list *list = interestedListeners;
|
|
for (int32 i = 0; i < interestedListenerCount; i++, list++) {
|
|
// iterate through the listeners
|
|
MonitorListenerList::Iterator iterator = list->iterator;
|
|
do {
|
|
monitor_listener *listener = iterator.Current();
|
|
if (listener->flags & list->flags)
|
|
listener->listener->EventOccured(*this, &message);
|
|
} while (iterator.Next() != NULL);
|
|
}
|
|
|
|
list = interestedListeners;
|
|
for (int32 i = 0; i < interestedListenerCount; i++, list++) {
|
|
// iterate through the listeners
|
|
do {
|
|
monitor_listener *listener = list->iterator.Current();
|
|
if (listener->flags & list->flags)
|
|
listener->listener->AllListenersNotified(*this);
|
|
} while (list->iterator.Next() != NULL);
|
|
}
|
|
|
|
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.
|
|
*/
|
|
status_t
|
|
NodeMonitorService::NotifyEntryCreatedOrRemoved(int32 opcode, dev_t device,
|
|
ino_t directory, const char *name, ino_t node)
|
|
{
|
|
if (!name)
|
|
return B_BAD_VALUE;
|
|
|
|
RecursiveLocker locker(fRecursiveLock);
|
|
|
|
// get the lists of all interested listeners
|
|
interested_monitor_listener_list interestedListeners[3];
|
|
int32 interestedListenerCount = 0;
|
|
// ... for the node
|
|
if (opcode != B_ENTRY_CREATED) {
|
|
_GetInterestedMonitorListeners(device, node, B_WATCH_NAME,
|
|
interestedListeners, interestedListenerCount);
|
|
}
|
|
// ... for the directory
|
|
_GetInterestedMonitorListeners(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 _SendNotificationMessage(message, interestedListeners,
|
|
interestedListenerCount);
|
|
}
|
|
|
|
|
|
inline status_t
|
|
NodeMonitorService::NotifyEntryMoved(dev_t device, ino_t fromDirectory,
|
|
const char *fromName, ino_t toDirectory, const char *toName,
|
|
ino_t 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.
|
|
dev_t nodeDevice = device;
|
|
resolve_mount_point_to_volume_root(device, node, &nodeDevice, &node);
|
|
|
|
RecursiveLocker locker(fRecursiveLock);
|
|
|
|
// get the lists of all interested listeners
|
|
interested_monitor_listener_list interestedListeners[3];
|
|
int32 interestedListenerCount = 0;
|
|
// ... for the node
|
|
_GetInterestedMonitorListeners(nodeDevice, node, B_WATCH_NAME,
|
|
interestedListeners, interestedListenerCount);
|
|
// ... for the source directory
|
|
_GetInterestedMonitorListeners(device, fromDirectory, B_WATCH_DIRECTORY,
|
|
interestedListeners, interestedListenerCount);
|
|
// ... for the target directory
|
|
if (toDirectory != fromDirectory) {
|
|
_GetInterestedMonitorListeners(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 _SendNotificationMessage(message, interestedListeners,
|
|
interestedListenerCount);
|
|
}
|
|
|
|
|
|
inline status_t
|
|
NodeMonitorService::NotifyStatChanged(dev_t device, ino_t node,
|
|
uint32 statFields)
|
|
{
|
|
RecursiveLocker locker(fRecursiveLock);
|
|
|
|
// get the lists of all interested listeners
|
|
interested_monitor_listener_list interestedListeners[3];
|
|
int32 interestedListenerCount = 0;
|
|
// ... for the node, depending on wether its an interim update or not
|
|
_GetInterestedMonitorListeners(device, node,
|
|
(statFields & B_STAT_INTERIM_UPDATE) != 0
|
|
? B_WATCH_INTERIM_STAT : 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 _SendNotificationMessage(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
|
|
NodeMonitorService::NotifyAttributeChanged(dev_t device, ino_t node,
|
|
const char *attribute, int32 cause)
|
|
{
|
|
if (!attribute)
|
|
return B_BAD_VALUE;
|
|
|
|
RecursiveLocker locker(fRecursiveLock);
|
|
|
|
// get the lists of all interested listeners
|
|
interested_monitor_listener_list interestedListeners[3];
|
|
int32 interestedListenerCount = 0;
|
|
// ... for the node
|
|
_GetInterestedMonitorListeners(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 _SendNotificationMessage(message, interestedListeners,
|
|
interestedListenerCount);
|
|
}
|
|
|
|
|
|
inline status_t
|
|
NodeMonitorService::NotifyUnmount(dev_t device)
|
|
{
|
|
TRACE(("unmounted device: %ld\n", device));
|
|
|
|
RecursiveLocker locker(fRecursiveLock);
|
|
|
|
// get the lists of all interested listeners
|
|
interested_monitor_listener_list interestedListeners[3];
|
|
int32 interestedListenerCount = 0;
|
|
_GetInterestedMonitorListeners(-1, -1, B_WATCH_MOUNT,
|
|
interestedListeners, interestedListenerCount);
|
|
|
|
if (interestedListenerCount == 0)
|
|
return B_OK;
|
|
|
|
// there are interested listeners: construct the message and send it
|
|
char messageBuffer[96];
|
|
KMessage message;
|
|
message.SetTo(messageBuffer, sizeof(messageBuffer), B_NODE_MONITOR);
|
|
message.AddInt32("opcode", B_DEVICE_UNMOUNTED);
|
|
message.AddInt32("device", device);
|
|
|
|
return _SendNotificationMessage(message, interestedListeners,
|
|
interestedListenerCount);
|
|
}
|
|
|
|
|
|
inline status_t
|
|
NodeMonitorService::NotifyMount(dev_t device, dev_t parentDevice,
|
|
ino_t parentDirectory)
|
|
{
|
|
TRACE(("mounted device: %ld, parent %ld:%Ld\n", device, parentDevice,
|
|
parentDirectory));
|
|
|
|
RecursiveLocker locker(fRecursiveLock);
|
|
|
|
// get the lists of all interested listeners
|
|
interested_monitor_listener_list interestedListeners[3];
|
|
int32 interestedListenerCount = 0;
|
|
_GetInterestedMonitorListeners(-1, -1, B_WATCH_MOUNT,
|
|
interestedListeners, interestedListenerCount);
|
|
|
|
if (interestedListenerCount == 0)
|
|
return B_OK;
|
|
|
|
// there are interested listeners: construct the message and send it
|
|
char messageBuffer[128];
|
|
KMessage message;
|
|
message.SetTo(messageBuffer, sizeof(messageBuffer), B_NODE_MONITOR);
|
|
message.AddInt32("opcode", B_DEVICE_MOUNTED);
|
|
message.AddInt32("new device", device);
|
|
message.AddInt32("device", parentDevice);
|
|
message.AddInt64("directory", parentDirectory);
|
|
|
|
return _SendNotificationMessage(message, interestedListeners,
|
|
interestedListenerCount);
|
|
}
|
|
|
|
|
|
inline status_t
|
|
NodeMonitorService::RemoveListeners(io_context *context)
|
|
{
|
|
RecursiveLocker locker(fRecursiveLock);
|
|
|
|
while (!list_is_empty(&context->node_monitors)) {
|
|
// the _RemoveListener() method will also free the node_monitor
|
|
// if it doesn't have any listeners attached anymore
|
|
_RemoveListener(
|
|
(monitor_listener*)list_get_first_item(&context->node_monitors));
|
|
}
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
status_t
|
|
NodeMonitorService::AddListener(const KMessage* eventSpecifier,
|
|
NotificationListener& listener)
|
|
{
|
|
if (eventSpecifier == NULL)
|
|
return B_BAD_VALUE;
|
|
|
|
io_context *context = get_current_io_context(
|
|
eventSpecifier->GetBool("kernel", true));
|
|
|
|
dev_t device = eventSpecifier->GetInt32("device", -1);
|
|
ino_t node = eventSpecifier->GetInt64("node", -1);
|
|
uint32 flags = eventSpecifier->GetInt32("flags", 0);
|
|
|
|
return AddListener(context, device, node, flags, listener);
|
|
}
|
|
|
|
|
|
status_t
|
|
NodeMonitorService::UpdateListener(const KMessage* eventSpecifier,
|
|
NotificationListener& listener)
|
|
{
|
|
if (eventSpecifier == NULL)
|
|
return B_BAD_VALUE;
|
|
|
|
io_context *context = get_current_io_context(
|
|
eventSpecifier->GetBool("kernel", true));
|
|
|
|
dev_t device = eventSpecifier->GetInt32("device", -1);
|
|
ino_t node = eventSpecifier->GetInt64("node", -1);
|
|
uint32 flags = eventSpecifier->GetInt32("flags", 0);
|
|
bool addFlags = eventSpecifier->GetBool("add flags", false);
|
|
|
|
return _UpdateListener(context, device, node, flags, addFlags, listener);
|
|
}
|
|
|
|
|
|
status_t
|
|
NodeMonitorService::RemoveListener(const KMessage* eventSpecifier,
|
|
NotificationListener& listener)
|
|
{
|
|
if (eventSpecifier == NULL)
|
|
return B_BAD_VALUE;
|
|
|
|
io_context *context = get_current_io_context(
|
|
eventSpecifier->GetBool("kernel", true));
|
|
|
|
dev_t device = eventSpecifier->GetInt32("device", -1);
|
|
ino_t node = eventSpecifier->GetInt64("node", -1);
|
|
|
|
return RemoveListener(context, device, node, listener);
|
|
}
|
|
|
|
|
|
status_t
|
|
NodeMonitorService::RemoveListener(io_context *context, dev_t device,
|
|
ino_t node, NotificationListener& notificationListener)
|
|
{
|
|
TRACE(("%s(dev = %ld, node = %Ld, listener = %p\n",
|
|
__PRETTY_FUNCTION__, device, node, ¬ificationListener));
|
|
|
|
RecursiveLocker _(fRecursiveLock);
|
|
|
|
// get the monitor for this device/node pair
|
|
node_monitor *monitor = _MonitorFor(device, node);
|
|
if (monitor == NULL)
|
|
return B_BAD_VALUE;
|
|
|
|
// see if it has the listener we are looking for
|
|
monitor_listener* listener = _MonitorListenerFor(monitor,
|
|
notificationListener);
|
|
if (listener == NULL)
|
|
return B_BAD_VALUE;
|
|
|
|
_RemoveListener(listener);
|
|
context->num_monitors--;
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
inline status_t
|
|
NodeMonitorService::RemoveUserListeners(struct io_context *context,
|
|
port_id port, uint32 token)
|
|
{
|
|
UserNodeListener userListener(port, token);
|
|
monitor_listener *listener = NULL;
|
|
int32 count = 0;
|
|
|
|
RecursiveLocker _(fRecursiveLock);
|
|
|
|
while ((listener = (monitor_listener*)list_get_next_item(
|
|
&context->node_monitors, listener)) != NULL) {
|
|
monitor_listener *removeListener;
|
|
|
|
if (*listener->listener != userListener)
|
|
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")
|
|
|
|
_RemoveListener(removeListener);
|
|
count++;
|
|
}
|
|
|
|
return count > 0 ? B_OK : B_ENTRY_NOT_FOUND;
|
|
}
|
|
|
|
|
|
status_t
|
|
NodeMonitorService::UpdateUserListener(io_context *context, dev_t device,
|
|
ino_t node, uint32 flags, UserNodeListener& userListener)
|
|
{
|
|
TRACE(("%s(dev = %ld, node = %Ld, flags = %ld, listener = %p\n",
|
|
__PRETTY_FUNCTION__, device, node, flags, &userListener));
|
|
|
|
RecursiveLocker _(fRecursiveLock);
|
|
|
|
node_monitor *monitor;
|
|
status_t status = _GetMonitor(context, device, node, true, &monitor);
|
|
if (status < B_OK)
|
|
return status;
|
|
|
|
MonitorListenerList::Iterator iterator = monitor->listeners.GetIterator();
|
|
while (monitor_listener* listener = iterator.Next()) {
|
|
if (*listener->listener == userListener) {
|
|
listener->flags |= flags;
|
|
return B_OK;
|
|
}
|
|
}
|
|
|
|
UserNodeListener* copiedListener = new(std::nothrow) UserNodeListener(
|
|
userListener);
|
|
if (copiedListener == NULL) {
|
|
if (monitor->listeners.IsEmpty())
|
|
_RemoveMonitor(monitor);
|
|
return B_NO_MEMORY;
|
|
}
|
|
|
|
return _AddMonitorListener(context, monitor, flags, *copiedListener);
|
|
}
|
|
|
|
|
|
// #pragma mark - private kernel API
|
|
|
|
|
|
status_t
|
|
remove_node_monitors(struct io_context *context)
|
|
{
|
|
return sNodeMonitorService.RemoveListeners(context);
|
|
}
|
|
|
|
|
|
status_t
|
|
node_monitor_init(void)
|
|
{
|
|
new(&sNodeMonitorSender) UserMessagingMessageSender();
|
|
new(&sNodeMonitorService) NodeMonitorService();
|
|
|
|
if (sNodeMonitorService.InitCheck() < B_OK)
|
|
panic("initializing node monitor failed\n");
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
status_t
|
|
notify_unmount(dev_t device)
|
|
{
|
|
return sNodeMonitorService.NotifyUnmount(device);
|
|
}
|
|
|
|
|
|
status_t
|
|
notify_mount(dev_t device, dev_t parentDevice, ino_t parentDirectory)
|
|
{
|
|
return sNodeMonitorService.NotifyMount(device, parentDevice,
|
|
parentDirectory);
|
|
}
|
|
|
|
|
|
status_t
|
|
remove_node_listener(dev_t device, dev_t node, NotificationListener& listener)
|
|
{
|
|
return sNodeMonitorService.RemoveListener(get_current_io_context(true),
|
|
device, node, listener);
|
|
}
|
|
|
|
|
|
status_t
|
|
add_node_listener(dev_t device, dev_t node, uint32 flags,
|
|
NotificationListener& listener)
|
|
{
|
|
return sNodeMonitorService.AddListener(get_current_io_context(true),
|
|
device, node, flags, listener);
|
|
}
|
|
|
|
|
|
// #pragma mark - public kernel API
|
|
|
|
|
|
status_t
|
|
notify_listener(int op, dev_t device, ino_t parentNode, ino_t toParentNode,
|
|
ino_t node, const char *name)
|
|
{
|
|
TRACE(("notify_listener(op = %d, device = %ld, node = %Ld, parent = %Ld, toParent = %Ld"
|
|
", name = \"%s\"\n", op, device, node, parentNode, toParentNode, name));
|
|
|
|
switch (op) {
|
|
case B_ENTRY_CREATED:
|
|
return notify_entry_created(device, parentNode, name, node);
|
|
|
|
case B_ENTRY_REMOVED:
|
|
return notify_entry_removed(device, parentNode, name, node);
|
|
|
|
case B_ENTRY_MOVED:
|
|
// no fromName -- use an empty string
|
|
return notify_entry_moved(device, parentNode, "", toParentNode,
|
|
name, node);
|
|
|
|
case B_STAT_CHANGED:
|
|
{
|
|
// no statFields -- consider all stat fields changed
|
|
uint32 statFields = B_STAT_MODE | B_STAT_UID | B_STAT_GID
|
|
| B_STAT_SIZE | B_STAT_ACCESS_TIME | B_STAT_MODIFICATION_TIME
|
|
| B_STAT_CREATION_TIME | B_STAT_CHANGE_TIME;
|
|
return notify_stat_changed(device, node, statFields);
|
|
}
|
|
|
|
case B_ATTR_CHANGED:
|
|
// no cause -- use B_ATTR_CHANGED
|
|
return notify_attribute_changed(device, node, name, B_ATTR_CHANGED);
|
|
|
|
default:
|
|
return B_BAD_VALUE;
|
|
}
|
|
}
|
|
|
|
|
|
/*! \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(dev_t device, ino_t directory, const char *name,
|
|
ino_t node)
|
|
{
|
|
return sNodeMonitorService.NotifyEntryCreatedOrRemoved(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(dev_t device, ino_t directory, const char *name,
|
|
ino_t node)
|
|
{
|
|
return sNodeMonitorService.NotifyEntryCreatedOrRemoved(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(dev_t device, ino_t fromDirectory,
|
|
const char *fromName, ino_t toDirectory, const char *toName,
|
|
ino_t node)
|
|
{
|
|
return sNodeMonitorService.NotifyEntryMoved(device, fromDirectory,
|
|
fromName, toDirectory, toName, node);
|
|
}
|
|
|
|
|
|
/*! \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 <NodeMonitor.h>, 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(dev_t device, ino_t node, uint32 statFields)
|
|
{
|
|
return sNodeMonitorService.NotifyStatChanged(device, node, statFields);
|
|
}
|
|
|
|
|
|
/*! \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(dev_t device, ino_t node, const char *attribute,
|
|
int32 cause)
|
|
{
|
|
return sNodeMonitorService.NotifyAttributeChanged(device, node, attribute,
|
|
cause);
|
|
}
|
|
|
|
|
|
/*! \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, dev_t device,
|
|
ino_t directory, const char *name, ino_t 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, dev_t device,
|
|
ino_t directory, const char *name, ino_t node)
|
|
{
|
|
return notify_query_entry_created_or_removed(B_ENTRY_REMOVED, port, token,
|
|
device, directory, name, node);
|
|
}
|
|
|
|
|
|
// #pragma mark - User syscalls
|
|
|
|
|
|
status_t
|
|
_user_stop_notifying(port_id port, uint32 token)
|
|
{
|
|
io_context *context = get_current_io_context(false);
|
|
|
|
return sNodeMonitorService.RemoveUserListeners(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);
|
|
|
|
UserNodeListener listener(port, token);
|
|
return sNodeMonitorService.UpdateUserListener(context, device, node, flags,
|
|
listener);
|
|
}
|
|
|
|
|
|
status_t
|
|
_user_stop_watching(dev_t device, ino_t node, port_id port, uint32 token)
|
|
{
|
|
io_context *context = get_current_io_context(false);
|
|
|
|
UserNodeListener listener(port, token);
|
|
return sNodeMonitorService.RemoveListener(context, device, node,
|
|
listener);
|
|
}
|
|
|