// VolumeManager.cpp #include "VolumeManager.h" #include #include #include #include #include #include #include #include #include #include "ClientVolume.h" #include "DebugSupport.h" #include "Directory.h" #include "Entry.h" #include "FDManager.h" #include "NodeHandle.h" #include "Path.h" #include "QueryDomain.h" #include "Volume.h" // TODO: We should filter recent events at some point. Otherwise we'll end up // with one event of each kind for each entry/node. const bigtime_t kRecentEventLifeTime = 100000; // 0.1 s // QueryHandler class VolumeManager::QueryHandler : public BHandler, public QueryListener, public BReferenceable { public: QueryHandler(NodeMonitorListener* listener, QueryDomain* queryDomain, QueryHandle* handle) : BHandler(), QueryListener(), BReferenceable(), fListener(listener), fQueryDomain(queryDomain), fHandle(handle) { } QueryDomain* GetQueryDomain() const { return fQueryDomain; } QueryHandle* GetQueryHandle() const { return fHandle; } virtual void MessageReceived(BMessage* message) { switch (message->what) { case B_QUERY_UPDATE: { NodeMonitoringEvent* event = NULL; int32 opcode; if (message->FindInt32("opcode", &opcode) == B_OK) { switch (opcode) { case B_ENTRY_CREATED: event = new(std::nothrow) EntryCreatedEvent; break; case B_ENTRY_REMOVED: event = new(std::nothrow) EntryRemovedEvent; break; case B_ENTRY_MOVED: event = new(std::nothrow) EntryMovedEvent; break; } } if (event) { event->queryHandler = this; AcquireReference(); if (event->Init(message) == B_OK) fListener->ProcessNodeMonitoringEvent(event); else delete event; } break; } default: BHandler::MessageReceived(message); } } virtual void QueryHandleClosed(QueryHandle* handle) { BLooper* looper = Looper(); if (looper && looper->Lock()) { looper->RemoveHandler(this); looper->Unlock(); } handle->SetQueryListener(NULL); ReleaseReference(); } private: NodeMonitorListener* fListener; QueryDomain* fQueryDomain; QueryHandle* fHandle; }; // VolumeMap struct VolumeManager::VolumeMap : HashMap, Volume*> { }; // ClientVolumeMap struct VolumeManager::ClientVolumeMap : HashMap, ClientVolume*> { }; // private BeOS syscalls to set the FD and node monitor slot limits extern "C" int _kset_fd_limit_(int num); extern "C" int _kset_mon_limit_(int num); // EntryCreatedEventMap struct VolumeManager::EntryCreatedEventMap : HashMap { }; // EntryRemovedEventMap struct VolumeManager::EntryRemovedEventMap : HashMap { }; // EntryMovedEventKey struct EntryMovedEventKey : public EntryRef { EntryMovedEventKey() { } EntryMovedEventKey(dev_t volumeID, ino_t fromDirectory, const char* fromName, ino_t toDirectory, const char* toName) : EntryRef(volumeID, fromDirectory, fromName), toDirectory(toDirectory), toName(toName) { } uint32 GetHashCode() const { uint32 hash = EntryRef::GetHashCode(); hash = 17 * hash + (uint32)(toDirectory >> 32); hash = 17 * hash + (uint32)toDirectory; hash = 17 * hash + string_hash(toName.GetString()); return hash; } bool operator==(const EntryMovedEventKey& other) const { return (*(const EntryRef*)this) == other && toDirectory == other.toDirectory && toName == other.toName; } bool operator!=(const EntryMovedEventKey& other) const { return !(*this == other); } ino_t toDirectory; HashString toName; }; // EntryMovedEventMap struct VolumeManager::EntryMovedEventMap : HashMap { }; // NodeStatChangedEventMap struct VolumeManager::NodeStatChangedEventMap : HashMap { }; typedef EntryRef AttributeRef; // NodeAttributeChangedEventMap struct VolumeManager::NodeAttributeChangedEventMap : HashMap { }; // #pragma mark - // constructor VolumeManager::VolumeManager() : fLock("volume manager"), fVolumes(NULL), fRootVolume(NULL), fClientVolumes(NULL), fNodeMonitor(NULL), fNodeMonitoringProcessor(-1), fNodeMonitoringEvents(), fRecentNodeMonitoringEvents(), fEntryCreatedEvents(NULL), fEntryRemovedEvents(NULL), fEntryMovedEvents(NULL), fNodeStatChangedEvents(NULL), fNodeAttributeChangedEvents(NULL), fRevision(0), fTerminating(false) { } // destructor VolumeManager::~VolumeManager() { // terminate the node monitor and the node monitoring processor fTerminating = true; if (fNodeMonitor && fNodeMonitor->Lock()) fNodeMonitor->Quit(); fNodeMonitoringEvents.Close(true); if (fNodeMonitoringProcessor >= 0) { int32 result; wait_for_thread(fNodeMonitoringProcessor, &result); } // delete all events // entry created events for (EntryCreatedEventMap::Iterator it = fEntryCreatedEvents->GetIterator(); it.HasNext();) { it.Next().value->ReleaseReference(); } delete fEntryCreatedEvents; // entry removed events for (EntryRemovedEventMap::Iterator it = fEntryRemovedEvents->GetIterator(); it.HasNext();) { it.Next().value->ReleaseReference(); } delete fEntryRemovedEvents; // entry moved events for (EntryMovedEventMap::Iterator it = fEntryMovedEvents->GetIterator(); it.HasNext();) { it.Next().value->ReleaseReference(); } delete fEntryMovedEvents; // stat changed events for (NodeStatChangedEventMap::Iterator it = fNodeStatChangedEvents->GetIterator(); it.HasNext();) { it.Next().value->ReleaseReference(); } delete fNodeStatChangedEvents; // attribute changed events for (NodeAttributeChangedEventMap::Iterator it = fNodeAttributeChangedEvents->GetIterator(); it.HasNext();) { it.Next().value->ReleaseReference(); } delete fNodeAttributeChangedEvents; delete fClientVolumes; // delete the volumes for (VolumeMap::Iterator it = fVolumes->GetIterator(); it.HasNext();) { Volume* volume = it.Next().value; delete volume; } delete fVolumes; } // Init status_t VolumeManager::Init() { // check node monitoring message queue status_t error = fNodeMonitoringEvents.InitCheck(); if (error != B_OK) return error; // entry created event map fEntryCreatedEvents = new(std::nothrow) EntryCreatedEventMap; if (!fEntryCreatedEvents) return B_NO_MEMORY; // entry removed event map fEntryRemovedEvents = new(std::nothrow) EntryRemovedEventMap; if (!fEntryRemovedEvents) return B_NO_MEMORY; // entry moved event map fEntryMovedEvents = new(std::nothrow) EntryMovedEventMap; if (!fEntryMovedEvents) return B_NO_MEMORY; // node stat changed event map fNodeStatChangedEvents = new(std::nothrow) NodeStatChangedEventMap; if (!fNodeStatChangedEvents) return B_NO_MEMORY; // node attribute changed event map fNodeAttributeChangedEvents = new(std::nothrow) NodeAttributeChangedEventMap; if (!fNodeAttributeChangedEvents) return B_NO_MEMORY; // create the node monitor fNodeMonitor = new(std::nothrow) NodeMonitor(this); if (!fNodeMonitor) return B_NO_MEMORY; // create the volume map fVolumes = new(std::nothrow) VolumeMap; if (!fVolumes) return B_NO_MEMORY; if (fVolumes->InitCheck() != B_OK) return fVolumes->InitCheck(); // create the client volume map fClientVolumes = new(std::nothrow) ClientVolumeMap; if (!fClientVolumes) return B_NO_MEMORY; if (fClientVolumes->InitCheck() != B_OK) return fClientVolumes->InitCheck(); // start the node monitor thread_id monitorThread = fNodeMonitor->Run(); if (monitorThread < 0) { delete fNodeMonitor; fNodeMonitor = NULL; return monitorThread; } // create all volumes int32 cookie = 0; dev_t volumeID; while ((volumeID = next_dev(&cookie)) >= 0) _AddVolume(volumeID); // get the root volume volumeID = dev_for_path("/"); if (volumeID < 0) return volumeID; fRootVolume = GetVolume(volumeID, true); if (!fRootVolume) return B_ERROR; // spawn the node monitoring message processor fNodeMonitoringProcessor = spawn_thread(&_NodeMonitoringProcessorEntry, "node monitoring processor", B_NORMAL_PRIORITY, this); if (fNodeMonitoringProcessor < 0) return fNodeMonitoringProcessor; resume_thread(fNodeMonitoringProcessor); return B_OK; } // GetRootVolume Volume* VolumeManager::GetRootVolume() const { return fRootVolume; } // AddClientVolume status_t VolumeManager::AddClientVolume(ClientVolume* clientVolume) { if (!clientVolume) return B_BAD_VALUE; return fClientVolumes->Put(clientVolume->GetID(), clientVolume); } // RemoveClientVolume void VolumeManager::RemoveClientVolume(ClientVolume* clientVolume) { if (!clientVolume) return; fClientVolumes->Remove(clientVolume->GetID()); } // CreateDefault status_t VolumeManager::CreateDefault() { if (sManager) return B_OK; VolumeManager* manager = new(std::nothrow) VolumeManager; if (!manager) return B_NO_MEMORY; status_t error = manager->Init(); if (error != B_OK) { delete manager; return error; } sManager = manager; return B_OK; } // DeleteDefault void VolumeManager::DeleteDefault() { if (sManager) { delete sManager; sManager = NULL; } } // GetDefault VolumeManager* VolumeManager::GetDefault() { return sManager; } // Lock bool VolumeManager::Lock() { bool alreadyLocked = fLock.IsLocked(); bool success = fLock.Lock(); // If locking was successful and we didn't have a lock before, we increment // the revision. if (success && !alreadyLocked) fRevision++; return success; } // Unlock void VolumeManager::Unlock() { return fLock.Unlock(); } // GetRevision int64 VolumeManager::GetRevision() const { return fRevision; } // GetVolume Volume* VolumeManager::GetVolume(dev_t volumeID, bool add) { Volume* volume = fVolumes->Get(volumeID); if (!volume && add) _AddVolume(volumeID, &volume); return volume; } // #pragma mark - // AddNode status_t VolumeManager::AddNode(Node* node) { if (!node || !node->GetVolume()) return B_BAD_VALUE; status_t error = node->GetVolume()->AddNode(node); // start watching the node if (error == B_OK) fNodeMonitor->StartWatching(node->GetNodeRef()); return error; } // RemoveNode void VolumeManager::RemoveNode(Node* node) { if (!node) return; // if the node is a directory, we remove all its entries first if (Directory* directory = dynamic_cast(node)) { while (Entry* entry = directory->GetFirstEntry()) { RemoveEntry(entry); delete entry; } } // remove all referring entries while (Entry* entry = node->GetFirstReferringEntry()) RemoveEntry(entry); // remove the node from the volume if (node->GetVolume()) node->GetVolume()->RemoveNode(node); // stop watching the node fNodeMonitor->StopWatching(node->GetNodeRef()); } // GetNode Node* VolumeManager::GetNode(dev_t volumeID, ino_t nodeID) { if (Volume* volume = GetVolume(volumeID)) return volume->GetNode(nodeID); return NULL; } // LoadNode status_t VolumeManager::LoadNode(const struct stat& st, Node** _node) { Node* node = GetNode(st.st_dev, st.st_ino); if (!node) { // node not known yet: create it // get the volume Volume* volume = GetVolume(st.st_dev, true); if (!volume) return B_BAD_VALUE; // create the node if (S_ISDIR(st.st_mode)) node = new(std::nothrow) Directory(volume, st); else node = new(std::nothrow) Node(volume, st); if (!node) return B_NO_MEMORY; // add it status_t error = AddNode(node); if (error != B_OK) { delete node; return error; } } if (_node) *_node = node; return B_OK; } // #pragma mark - // GetDirectory Directory* VolumeManager::GetDirectory(dev_t volumeID, ino_t nodeID) { return dynamic_cast(GetNode(volumeID, nodeID)); } // GetRootDirectory Directory* VolumeManager::GetRootDirectory() const { return (fRootVolume ? fRootVolume->GetRootDirectory() : NULL); } // GetParentDirectory Directory* VolumeManager::GetParentDirectory(Directory* directory) { if (!directory) return NULL; // get ".." entry Entry* parentEntry; if (LoadEntry(directory->GetVolumeID(), directory->GetID(), "..", true, &parentEntry) != B_OK) { return NULL; } return dynamic_cast(parentEntry->GetNode()); } // LoadDirectory status_t VolumeManager::LoadDirectory(dev_t volumeID, ino_t directoryID, Directory** _directory) { // try to get the node Node* node = GetNode(volumeID, directoryID); bool newNode = false; if (!node) { // directory not yet loaded: stat it NoAllocEntryRef entryRef(volumeID, directoryID, "."); struct stat st; BEntry bEntry; status_t error = FDManager::SetEntry(&bEntry, &entryRef); if (error == B_OK) error = bEntry.GetStat(&st); if (error != B_OK) return error; // load the node error = LoadNode(st, &node); if (error != B_OK) return error; newNode = true; } // check, if the node is a directory Directory* directory = dynamic_cast(node); if (!directory) return B_NOT_A_DIRECTORY; if (newNode) CompletePathToRoot(directory); if (_directory) *_directory = directory; return B_OK; } // #pragma mark - // AddEntry status_t VolumeManager::AddEntry(Entry* entry) { if (!entry || !entry->GetVolume() || !entry->GetDirectory() || ! entry->GetNode()) { return B_BAD_VALUE; } // add the entry to the volume status_t error = entry->GetVolume()->AddEntry(entry); if (error != B_OK) return error; // add the entry to its directory and node entry->GetDirectory()->AddEntry(entry); entry->GetNode()->AddReferringEntry(entry); //PRINT(("VolumeManager::AddEntry(): %ld, %lld, `%s', dir: %p, " //"entry count: %ld\n", entry->GetVolumeID(), entry->GetDirectoryID(), //entry->GetName(), entry->GetDirectory(), //entry->GetDirectory()->CountEntries())); return B_OK; } // RemoveEntry void VolumeManager::RemoveEntry(Entry* entry) { if (entry) { // remove the entry from the volume if (entry->GetVolume()) entry->GetVolume()->RemoveEntry(entry); // remove the entry from the directory and its node entry->GetDirectory()->RemoveEntry(entry); entry->GetNode()->RemoveReferringEntry(entry); //PRINT(("VolumeManager::RemoveEntry(): %ld, %lld, `%s', dir: %p, " //"entry count: %ld\n", entry->GetVolumeID(), entry->GetDirectoryID(), //entry->GetName(), entry->GetDirectory(), //entry->GetDirectory()->CountEntries())); } } // DeleteEntry void VolumeManager::DeleteEntry(Entry* entry, bool keepNode) { if (!entry) return; Node* node = entry->GetNode(); // remove the entry RemoveEntry(entry); delete entry; // remove the node, if it doesn't have any more actual referring entries if (!keepNode && !node->GetActualReferringEntry()) { RemoveNode(node); if (node != node->GetVolume()->GetRootDirectory()) delete node; } } // GetEntry Entry* VolumeManager::GetEntry(dev_t volumeID, ino_t directoryID, const char* name) { if (Volume* volume = GetVolume(volumeID)) return volume->GetEntry(directoryID, name); return NULL; } // GetEntry Entry* VolumeManager::GetEntry(const entry_ref& ref) { return GetEntry(ref.device, ref.directory, ref.name); } // LoadEntry status_t VolumeManager::LoadEntry(dev_t volumeID, ino_t directoryID, const char* name, bool loadDir, Entry** _entry) { Entry* entry = GetEntry(volumeID, directoryID, name); if (!entry) { // entry not known yet: create it PRINT("VolumeManager::LoadEntry(%" B_PRIdDEV ", " "%" B_PRIdINO ", `%s')\n", volumeID, directoryID, name); // get the volume Volume* volume = GetVolume(volumeID, true); if (!volume) return B_BAD_VALUE; // get the directory status_t error = B_OK; Directory* directory = GetDirectory(volumeID, directoryID); if (!directory) { if (!loadDir) return B_ENTRY_NOT_FOUND; //PRINT((" loading directory...\n")); // load the directory error = LoadDirectory(volumeID, directoryID, &directory); if (error != B_OK) return error; } //PRINT((" opening BNode...\n")); // stat the entry NoAllocEntryRef entryRef(volumeID, directoryID, name); struct stat st; BNode bNode; error = bNode.SetTo(&entryRef); //PRINT((" stat()ing BNode...\n")); if (error == B_OK) error = bNode.GetStat(&st); if (error != B_OK) return error; //PRINT((" loading node...\n")); // load the node Node* node; error = LoadNode(st, &node); if (error != B_OK) return error; //PRINT((" creating and adding entry...\n")); // create the entry entry = new(std::nothrow) Entry(volume, directory, name, node); if (!entry) return B_NO_MEMORY; // add it error = AddEntry(entry); if (error != B_OK) { delete entry; return error; } //PRINT((" adding entry done\n")); } if (_entry) *_entry = entry; return B_OK; } // #pragma mark - // OpenQuery status_t VolumeManager::OpenQuery(QueryDomain* queryDomain, const char* queryString, uint32 flags, port_id remotePort, int32 remoteToken, QueryHandle** handle) { if (!queryDomain || !queryString || !handle) return B_BAD_VALUE; bool liveQuery = (flags & B_LIVE_QUERY); PRINT("VolumeManager::OpenQuery(%p, \"%s\", 0x%" B_PRIx32 ", " "%" B_PRId32 ", %" B_PRId32 ")\n", queryDomain, queryString, flags, remotePort, remoteToken); // allocate the handle QueryHandle* queryHandle = new(std::nothrow) QueryHandle(remotePort, remoteToken); if (!queryHandle) return B_NO_MEMORY; ObjectDeleter handleDeleter(queryHandle); // allocate a query handler, if this is a live query QueryHandler* queryHandler = NULL; if (liveQuery) { queryHandler = new(std::nothrow) QueryHandler(this, queryDomain, queryHandle); if (!queryHandler) return B_NO_MEMORY; fNodeMonitor->Lock(); fNodeMonitor->AddHandler(queryHandler); fNodeMonitor->Unlock(); queryHandle->SetQueryListener(queryHandler); } // iterate through the volumes and create a query for each one // supporting queries for (VolumeMap::Iterator it = fVolumes->GetIterator(); it.HasNext();) { Volume* volume = it.Next().value; if (!volume->KnowsQuery()) continue; // The volume should either be contained by the client volume or // the other way around. Otherwise they are located in different // branches of the FS tree and don't have common nodes. if (!queryDomain->QueryDomainIntersectsWith(volume)) continue; PRINT("VolumeManager::OpenQuery(): adding Query for volume " "%" B_PRIdDEV "\n", volume->GetID()); // create the query for this volume BVolume bVolume(volume->GetID()); Query* query = new(std::nothrow) Query; if (!query) return B_NO_MEMORY; // init the query ObjectDeleter queryDeleter(query); status_t error = query->SetVolume(&bVolume); if (error != B_OK) return error; error = query->SetPredicate(queryString); if (error != B_OK) return error; if (liveQuery) { error = query->SetTarget(queryHandler); if (error != B_OK) return error; } // fetch error = query->Fetch(); if (error != B_OK) return error; queryHandle->AddQuery(query); queryDeleter.Detach(); } *handle = queryHandle; handleDeleter.Detach(); return B_OK; } // CompletePathToRoot status_t VolumeManager::CompletePathToRoot(Directory* directory) { if (!directory) return B_BAD_VALUE; while (directory != GetRootDirectory()) { // if the dir has a valid entry referring to it, we've nothing to do if (directory->GetActualReferringEntry()) return B_OK; // get a proper entry_ref BEntry bEntry; entry_ref entryRef(directory->GetVolumeID(), directory->GetID(), "."); status_t error = FDManager::SetEntry(&bEntry, &entryRef); if (error == B_OK) error = bEntry.GetRef(&entryRef); if (error != B_OK) return error; // if the entry is already loaded, we're done if (GetEntry(entryRef)) return B_OK; // the entry is not yet known -- load it Entry* entry; error = LoadEntry(entryRef.device, entryRef.directory, entryRef.name, true, &entry); if (error != B_OK) return error; // get the entry's parent dir and enter the next round directory = entry->GetDirectory(); } return B_OK; } // GetPath status_t VolumeManager::GetPath(Entry* entry, Path* path) { // get directory path status_t error = GetPath(entry->GetDirectory(), path); if (error != B_OK) return error; // append the entry name return path->Append(entry->GetName()); } // GetPath status_t VolumeManager::GetPath(Node* node, Path* path) { if (node == GetRootDirectory()) return path->SetTo("/"); // get an entry referring to the node Entry* entry = node->GetActualReferringEntry(); if (!entry) { // if the node is a directory, we complete the path to the root and // try again if (Directory* directory = dynamic_cast(node)) { CompletePathToRoot(directory); entry = node->GetActualReferringEntry(); } if (!entry) return B_ERROR; } return GetPath(entry, path); } // DirectoryContains bool VolumeManager::DirectoryContains(Directory* directory, Entry* entry) { if (!directory || !entry) return false; return DirectoryContains(directory, entry->GetDirectory(), true); } // DirectoryContains bool VolumeManager::DirectoryContains(Directory* directory, Directory* descendant, bool reflexive) { if (!directory || !descendant) return false; // a directory contains itself, just as defined by the caller if (directory == descendant) return reflexive; // if the directory is the root directory, it contains everything Directory* rootDir = GetRootDirectory(); if (directory == rootDir) return true; // recursively get the descendant's parent dir until reaching the root dir // or the given dir while (descendant != rootDir) { descendant = GetParentDirectory(descendant); if (!descendant) return false; if (descendant == directory) return true; } return false; } // DirectoryContains bool VolumeManager::DirectoryContains(Directory* directory, Node* descendant, bool reflexive) { if (!directory || !descendant) return false; // if the node is a directory, let the other version do the job if (Directory* dir = dynamic_cast(descendant)) return DirectoryContains(directory, dir, reflexive); // iterate through the referring entries and check, if the directory // contains any of them for (Entry* entry = descendant->GetFirstReferringEntry(); entry; entry = descendant->GetNextReferringEntry(entry)) { if (DirectoryContains(directory, entry)) return true; } return false; } // #pragma mark - // ProcessNodeMonitoringEvent void VolumeManager::ProcessNodeMonitoringEvent(NodeMonitoringEvent* event) { if (fNodeMonitoringEvents.Push(event) != B_OK) delete event; } // _AddVolume status_t VolumeManager::_AddVolume(dev_t volumeID, Volume** _volume) { if (GetVolume(volumeID)) return B_OK; // create the volume Volume* volume = new(std::nothrow) Volume(volumeID); if (!volume) RETURN_ERROR(B_NO_MEMORY); ObjectDeleter volumeDeleter(volume); status_t error = volume->Init(); if (error != B_OK) RETURN_ERROR(error); // add it error = fVolumes->Put(volumeID, volume); if (error != B_OK) RETURN_ERROR(error); // add the root node error = AddNode(volume->GetRootDirectory()); if (error != B_OK) { fVolumes->Remove(volumeID); RETURN_ERROR(error); } // complete the root dir path CompletePathToRoot(volume->GetRootDirectory()); volumeDeleter.Detach(); if (_volume) *_volume = volume; return B_OK; } // _EntryCreated void VolumeManager::_EntryCreated(EntryCreatedEvent* event) { // get the directory Directory* directory = GetDirectory(event->volumeID, event->directoryID); if (!directory) return; // check, if there is an earlier similar event bool notify = true; NoAllocEntryRef ref(event->volumeID, event->directoryID, event->name.GetString()); EntryCreatedEvent* oldEvent = fEntryCreatedEvents->Get(ref); // remove the old event if (oldEvent) { fEntryCreatedEvents->Remove(ref); fRecentNodeMonitoringEvents.Remove(oldEvent); notify = !_IsRecentEvent(oldEvent); oldEvent->ReleaseReference(); } // add the new event if (fEntryCreatedEvents->Put(ref, event) == B_OK) { fRecentNodeMonitoringEvents.Insert(event); event->AcquireReference(); } // if the directory is complete or at least has iterators attached to it, // we load the entry if (directory->IsComplete() || directory->HasDirIterators()) { Entry* entry; LoadEntry(ref.device, ref.directory, ref.name, false, &entry); } // send notifications if (notify) { for (ClientVolumeMap::Iterator it = fClientVolumes->GetIterator(); it.HasNext();) { ClientVolume* clientVolume = it.Next().value; if (DirectoryContains(clientVolume->GetRootDirectory(), directory, true)) { clientVolume->ProcessNodeMonitoringEvent(event); } } } } // _EntryRemoved void VolumeManager::_EntryRemoved(EntryRemovedEvent* event, bool keepNode) { // get node and directory Node* node = GetNode(event->nodeVolumeID, event->nodeID); Directory* directory = GetDirectory(event->volumeID, event->directoryID); if (!directory) return; // find the entry Entry* entry = NULL; if (node) { if (event->name.GetLength() == 0) { for (entry = node->GetFirstReferringEntry(); entry; entry = node->GetNextReferringEntry(entry)) { if (!entry->Exists()) { event->name.SetTo(entry->GetName()); break; } } } else { entry = GetEntry(directory->GetVolumeID(), directory->GetID(), event->name.GetString()); } } // check, if there is an earlier similar event bool notify = true; NoAllocEntryRef ref(event->volumeID, event->directoryID, event->name.GetString()); EntryRemovedEvent* oldEvent = fEntryRemovedEvents->Get(ref); // TODO: Under BeOS R5 the entry name is not encoded in the // "entry removed" node monitoring message. If we have seen the entry // before, we can get the entry nevertheless (see above). Usually we // get 2 "entry removed" events: One for watching the directory and one // for watching the node. After the first one has been processed, we've // forgotten everything about the entry and we won't be able to find out // the entry's name for the second one. Hence we will never find the // previous event in the fEntryRemovedEvents map. We should probably // fall back to using a NodeRef as key under BeOS R5. // remove the old event if (oldEvent) { fEntryRemovedEvents->Remove(ref); fRecentNodeMonitoringEvents.Remove(oldEvent); notify = !_IsRecentEvent(oldEvent); oldEvent->ReleaseReference(); } // add the new event if (fEntryRemovedEvents->Put(ref, event) == B_OK) { fRecentNodeMonitoringEvents.Insert(event); event->AcquireReference(); } // remove the entry if (entry) { RemoveEntry(entry); delete entry; } // remove the node, if it doesn't have any more actual referring entries if (node && !keepNode && !node->GetActualReferringEntry()) { RemoveNode(node); if (node != node->GetVolume()->GetRootDirectory()) delete node; } // send notifications if (notify) { NodeRef nodeRef(event->nodeVolumeID, event->nodeID); for (ClientVolumeMap::Iterator it = fClientVolumes->GetIterator(); it.HasNext();) { // We send a notification, if the client volume contains the entry, // but also, if the removed entry refers to the client volume's // root. The client connection has a special handling for this // case. ClientVolume* clientVolume = it.Next().value; Directory* rootDir = clientVolume->GetRootDirectory(); if (DirectoryContains(rootDir, directory, true) || clientVolume->GetRootNodeRef() == nodeRef) { clientVolume->ProcessNodeMonitoringEvent(event); } } } } // _EntryMoved void VolumeManager::_EntryMoved(EntryMovedEvent* event) { _CheckVolumeRootMoved(event); Directory* fromDirectory = GetDirectory(event->volumeID, event->fromDirectoryID); Directory* toDirectory = GetDirectory(event->volumeID, event->toDirectoryID); Node* node = GetNode(event->nodeVolumeID, event->nodeID); // we should at least have one of the directories if (!fromDirectory && !toDirectory) return; // find the old entry Entry* oldEntry = NULL; if (node) { if (event->fromName.GetLength() == 0) { for (oldEntry = node->GetFirstReferringEntry(); oldEntry; oldEntry = node->GetNextReferringEntry(oldEntry)) { if (!oldEntry->Exists()) { event->fromName.SetTo(oldEntry->GetName()); break; } } } else { oldEntry = GetEntry(event->volumeID, event->fromDirectoryID, event->fromName.GetString()); } } // check, if there is an earlier similar event bool notify = true; if (event->fromName.GetLength() > 0) { EntryMovedEventKey key(event->volumeID, event->fromDirectoryID, event->fromName.GetString(), event->toDirectoryID, event->toName.GetString()); EntryMovedEvent* oldEvent = fEntryMovedEvents->Get(key); // remove the old event if (oldEvent) { fEntryMovedEvents->Remove(key); fRecentNodeMonitoringEvents.Remove(oldEvent); notify = !_IsRecentEvent(oldEvent); oldEvent->ReleaseReference(); } // add the new event if (fEntryMovedEvents->Put(key, event) == B_OK) { fRecentNodeMonitoringEvents.Insert(event); event->AcquireReference(); } } // remove the old entry if (oldEntry) { RemoveEntry(oldEntry); delete oldEntry; } // If the to directory is complete or at least has iterators attached to it, // we load the new entry. We also load it, if the node is the root of a // volume. if (toDirectory && (toDirectory->IsComplete() || toDirectory->HasDirIterators() || (node && node == node->GetVolume()->GetRootDirectory()))) { Entry* newEntry; LoadEntry(event->volumeID, event->toDirectoryID, event->toName.GetString(), false, &newEntry); } // remove the node, if it doesn't have any more actual referring entries if (node && !node->GetActualReferringEntry()) { RemoveNode(node); if (node != node->GetVolume()->GetRootDirectory()) delete node; } // send notifications if (notify) { for (ClientVolumeMap::Iterator it = fClientVolumes->GetIterator(); it.HasNext();) { ClientVolume* clientVolume = it.Next().value; // check, if it contains the from/to directories Directory* rootDir = clientVolume->GetRootDirectory(); bool containsFrom = DirectoryContains(rootDir, fromDirectory, true); bool containsTo = DirectoryContains(rootDir, toDirectory, true); if (containsFrom) { if (containsTo) { // contains source and target dir clientVolume->ProcessNodeMonitoringEvent(event); } else { // contains only the source dir: generate an "entry removed" // event EntryRemovedEvent *removedEvent = new(std::nothrow) EntryRemovedEvent; if (!removedEvent) continue; removedEvent->opcode = B_ENTRY_REMOVED; removedEvent->time = event->time; removedEvent->volumeID = event->volumeID; removedEvent->directoryID = event->fromDirectoryID; removedEvent->nodeVolumeID = event->nodeVolumeID; removedEvent->nodeID = event->nodeID; if (event->fromName.GetLength() > 0) removedEvent->name = event->fromName; clientVolume->ProcessNodeMonitoringEvent(removedEvent); removedEvent->ReleaseReference(); } } else if (containsTo) { // contains only the target directory: generate an // "entry created" event EntryCreatedEvent *createdEvent = new(std::nothrow) EntryCreatedEvent; if (!createdEvent) continue; createdEvent->opcode = B_ENTRY_CREATED; createdEvent->time = event->time; createdEvent->volumeID = event->volumeID; createdEvent->directoryID = event->toDirectoryID; createdEvent->name = event->toName; clientVolume->ProcessNodeMonitoringEvent(createdEvent); createdEvent->ReleaseReference(); } } } } // _NodeStatChanged void VolumeManager::_NodeStatChanged(StatChangedEvent* event) { // get the node Node* node = GetNode(event->volumeID, event->nodeID); if (!node) return; // check, if there is an earlier similar event bool notify = true; NodeRef ref(event->volumeID, event->nodeID); StatChangedEvent* oldEvent = fNodeStatChangedEvents->Get(ref); // remove the old event if (oldEvent) { fNodeStatChangedEvents->Remove(ref); fRecentNodeMonitoringEvents.Remove(oldEvent); notify = !_IsRecentEvent(oldEvent); oldEvent->ReleaseReference(); } // add the new event if (fNodeStatChangedEvents->Put(ref, event) == B_OK) { fRecentNodeMonitoringEvents.Insert(event); event->AcquireReference(); } if (notify) { // update the cached node stat node->UpdateStat(); // send notifications for (ClientVolumeMap::Iterator it = fClientVolumes->GetIterator(); it.HasNext();) { ClientVolume* clientVolume = it.Next().value; if (DirectoryContains(clientVolume->GetRootDirectory(), node, true)) clientVolume->ProcessNodeMonitoringEvent(event); } } } // _NodeAttributeChanged void VolumeManager::_NodeAttributeChanged(AttributeChangedEvent* event) { // get the node Node* node = GetNode(event->volumeID, event->nodeID); if (!node) return; // check, if there is an earlier similar event bool notify = true; AttributeRef ref(event->volumeID, event->nodeID, event->attribute.GetString()); AttributeChangedEvent* oldEvent = fNodeAttributeChangedEvents->Get(ref); // remove the old event if (oldEvent) { fNodeAttributeChangedEvents->Remove(ref); fRecentNodeMonitoringEvents.Remove(oldEvent); notify = !_IsRecentEvent(oldEvent); oldEvent->ReleaseReference(); } // add the new event if (fNodeAttributeChangedEvents->Put(ref, event) == B_OK) { fRecentNodeMonitoringEvents.Insert(event); event->AcquireReference(); } // send notifications if (notify) { for (ClientVolumeMap::Iterator it = fClientVolumes->GetIterator(); it.HasNext();) { ClientVolume* clientVolume = it.Next().value; if (DirectoryContains(clientVolume->GetRootDirectory(), node, true)) clientVolume->ProcessNodeMonitoringEvent(event); } } } // _VolumeMounted void VolumeManager::_VolumeMounted(VolumeMountedEvent* event) { entry_ref rootRef; bool rootRefInitialized = false; // remove the entry referring to the covered directory Directory* coveredDirectory = GetDirectory(event->volumeID, event->directoryID); if (coveredDirectory) { if (Entry* entry = coveredDirectory->GetActualReferringEntry()) { // get an entry for later rootRef = entry->GetEntryRef(); rootRefInitialized = true; // send the "entry removed" event EntryRemovedEvent* event; if (_GenerateEntryRemovedEvent(entry, system_time(), &event) == B_OK) { _EntryRemoved(event, true); event->ReleaseReference(); } else { RemoveEntry(entry); delete entry; } } } // add the volume _AddVolume(event->newVolumeID); // generate an "entry created" event for the root dir entry if (rootRefInitialized) _GenerateEntryCreatedEvent(rootRef, event->time); } // _VolumeUnmounted void VolumeManager::_VolumeUnmounted(VolumeUnmountedEvent* event) { // get the volume Volume* volume = GetVolume(event->volumeID); if (!volume) return; entry_ref rootRef; bool rootRefInitialized = false; // remove all actual entries referring to the root directory (should only // be one) if (Directory* rootDir = volume->GetRootDirectory()) { // get an entry ref for the root dir if (Entry* entry = rootDir->GetActualReferringEntry()) { rootRef = entry->GetEntryRef(); rootRefInitialized = true; } Entry* entry = rootDir->GetFirstReferringEntry(); while (entry) { Entry* nextEntry = rootDir->GetNextReferringEntry(entry); if (entry->IsActualEntry()) { EntryRemovedEvent* removedEvent; if (_GenerateEntryRemovedEvent(entry, event->time, &removedEvent) == B_OK) { _EntryRemoved(removedEvent, true); removedEvent->ReleaseReference(); } else { RemoveEntry(entry); delete entry; } } entry = nextEntry; } } // remove all entries of the volume while (Entry* entry = volume->GetFirstEntry()) { bool remove = true; if (entry->IsActualEntry()) { if (_GenerateEntryRemovedEvent(entry, event->time) != B_OK) remove = false; } if (remove) { RemoveEntry(entry); delete entry; } } // remove all nodes while (Node* node = volume->GetFirstNode()) { RemoveNode(node); if (node != volume->GetRootDirectory()) delete node; } // remove the volume fVolumes->Remove(volume->GetID()); delete volume; // generate an "entry created" event for the covered node if (rootRefInitialized) _GenerateEntryCreatedEvent(rootRef, event->time); } // _QueryEntryCreated void VolumeManager::_QueryEntryCreated(EntryCreatedEvent* event) { // get the query handler QueryHandler* queryHandler = dynamic_cast(event->queryHandler); if (!queryHandler) return; // load the entry (just to make sure that it really exists) Entry* entry = NULL; status_t error = LoadEntry(event->volumeID, event->directoryID, event->name.GetString(), true, &entry); if (error != B_OK) return; // get remote port and token if (!queryHandler->LockLooper()) return; QueryHandle* queryHandle = queryHandler->GetQueryHandle(); event->remotePort = queryHandle->GetRemotePort(); event->remoteToken = queryHandle->GetRemoteToken(); queryHandler->UnlockLooper(); // send a notification to the client volume queryHandler->GetQueryDomain()->ProcessQueryEvent(event); } // _QueryEntryRemoved void VolumeManager::_QueryEntryRemoved(EntryRemovedEvent* event) { // get the query handler QueryHandler* queryHandler = dynamic_cast(event->queryHandler); if (!queryHandler) return; // load the directory (just to make sure that it really exists) Directory* directory = NULL; status_t error = LoadDirectory(event->volumeID, event->directoryID, &directory); if (error != B_OK) return; // get remote port and token if (!queryHandler->LockLooper()) return; QueryHandle* queryHandle = queryHandler->GetQueryHandle(); event->remotePort = queryHandle->GetRemotePort(); event->remoteToken = queryHandle->GetRemoteToken(); queryHandler->UnlockLooper(); // send a notification to the client volume queryHandler->GetQueryDomain()->ProcessQueryEvent(event); } // _QueryEntryMoved void VolumeManager::_QueryEntryMoved(EntryMovedEvent* event) { // we simply split the event into a `removed' and a `created' event // allocate the events EntryRemovedEvent* removedEvent = new(std::nothrow) EntryRemovedEvent; EntryCreatedEvent* createdEvent = new(std::nothrow) EntryCreatedEvent; if (!removedEvent || !createdEvent) { delete removedEvent; delete createdEvent; return; } // init the removed event removedEvent->opcode = B_ENTRY_REMOVED; removedEvent->time = event->time; removedEvent->queryHandler = event->queryHandler; removedEvent->queryHandler->AcquireReference(); removedEvent->volumeID = event->volumeID; removedEvent->directoryID = event->fromDirectoryID; removedEvent->nodeVolumeID = event->volumeID; removedEvent->nodeID = event->nodeID; removedEvent->name = event->fromName; // init the created event createdEvent->opcode = B_ENTRY_CREATED; createdEvent->time = event->time; createdEvent->queryHandler = event->queryHandler; createdEvent->queryHandler->AcquireReference(); createdEvent->volumeID = event->volumeID; createdEvent->directoryID = event->toDirectoryID; createdEvent->nodeID = event->nodeID; createdEvent->name = event->toName; // send them _QueryEntryRemoved(removedEvent); removedEvent->ReleaseReference(); _QueryEntryCreated(createdEvent); createdEvent->ReleaseReference(); } // _IsRecentEvent bool VolumeManager::_IsRecentEvent(NodeMonitoringEvent* event) const { return (event && system_time() < event->time + kRecentEventLifeTime); } // _GenerateEntryCreatedEvent status_t VolumeManager::_GenerateEntryCreatedEvent(const entry_ref& ref, bigtime_t time, EntryCreatedEvent** _event) { // load the entry Entry* entry; status_t error = LoadEntry(ref.device, ref.directory, ref.name, true, &entry); if (error != B_OK) return error; // create the event EntryCreatedEvent* event = new(std::nothrow) EntryCreatedEvent; if (!event) return B_NO_MEMORY; // fill in the fields event->opcode = B_ENTRY_CREATED; event->time = time; event->volumeID = entry->GetVolumeID(); event->directoryID = entry->GetDirectoryID(); event->nodeID = entry->GetNode()->GetID(); event->name.SetTo(entry->GetName()); if (_event) { *_event = event; } else { _EntryCreated(event); event->ReleaseReference(); } return B_OK; } // _GenerateEntryRemovedEvent status_t VolumeManager::_GenerateEntryRemovedEvent(Entry* entry, bigtime_t time, EntryRemovedEvent** _event) { if (!entry) return B_BAD_VALUE; // create the event EntryRemovedEvent* event = new(std::nothrow) EntryRemovedEvent; if (!event) return B_NO_MEMORY; // fill in the fields event->opcode = B_ENTRY_REMOVED; event->time = time; event->volumeID = entry->GetVolumeID(); event->directoryID = entry->GetDirectoryID(); event->nodeVolumeID = entry->GetNode()->GetVolumeID(); event->nodeID = entry->GetNode()->GetID(); event->name.SetTo(entry->GetName()); if (_event) { *_event = event; } else { _EntryRemoved(event, false); event->ReleaseReference(); } return B_OK; } // _CheckVolumeRootMoved void VolumeManager::_CheckVolumeRootMoved(EntryMovedEvent* event) { // If a volume root is moved, the sent node monitoring message does // unforunately contain the node_ref of the covered node, not that of the // volume root -- a BeOS R5 VFS bug. Since we have the entry_ref of the // new entry, we can stat the node. // check whether the node is the root of a volume NoAllocEntryRef ref(event->volumeID, event->toDirectoryID, event->toName.GetString()); BEntry entry; struct stat st; if (FDManager::SetEntry(&entry, &ref) == B_OK && entry.GetStat(&st) == B_OK) { event->nodeVolumeID = st.st_dev; event->nodeID = st.st_ino; if (Volume* volume = GetVolume(st.st_dev)) { if (volume->GetRootID() == st.st_ino) { PRINT("Mount point for volume %" B_PRIdDEV " renamed\n", volume->GetID()); } } } } // _NodeMonitoringProcessorEntry int32 VolumeManager::_NodeMonitoringProcessorEntry(void* data) { return ((VolumeManager*)data)->_NodeMonitoringProcessor(); } // _NodeMonitoryProcessor int32 VolumeManager::_NodeMonitoringProcessor() { do { NodeMonitoringEvent* event = NULL; status_t error = fNodeMonitoringEvents.Pop(&event); VolumeManagerLocker managerLocker; while (error == B_OK) { if (event->queryHandler) { switch (event->opcode) { case B_ENTRY_CREATED: _QueryEntryCreated( dynamic_cast(event)); break; case B_ENTRY_REMOVED: _QueryEntryRemoved( dynamic_cast(event)); break; case B_ENTRY_MOVED: _QueryEntryMoved(dynamic_cast(event)); break; } } else { switch (event->opcode) { case B_ENTRY_CREATED: _EntryCreated(dynamic_cast(event)); break; case B_ENTRY_REMOVED: _EntryRemoved(dynamic_cast(event), false); break; case B_ENTRY_MOVED: _EntryMoved(dynamic_cast(event)); break; case B_STAT_CHANGED: _NodeStatChanged( dynamic_cast(event)); break; case B_ATTR_CHANGED: _NodeAttributeChanged( dynamic_cast(event)); break; case B_DEVICE_MOUNTED: _VolumeMounted(dynamic_cast(event)); break; case B_DEVICE_UNMOUNTED: _VolumeUnmounted( dynamic_cast(event)); break; } } event->ReleaseReference(); // If there is another event available, get it as long as we // have the VolumeManager lock. error = fNodeMonitoringEvents.Pop(&event, 0); } } while (!fTerminating); return 0; } // sManager VolumeManager* VolumeManager::sManager = NULL;