/* * Copyright 2009-2014, Ingo Weinhold, ingo_weinhold@gmx.de. * Distributed under the terms of the MIT License. */ #include "Volume.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "AttributeIndex.h" #include "DebugSupport.h" #include "kernel_interface.h" #include "LastModifiedIndex.h" #include "NameIndex.h" #include "OldUnpackingNodeAttributes.h" #include "PackageFSRoot.h" #include "PackageLinkDirectory.h" #include "PackageLinksDirectory.h" #include "Resolvable.h" #include "SizeIndex.h" #include "UnpackingLeafNode.h" #include "UnpackingDirectory.h" #include "Utils.h" #include "Version.h" // node ID of the root directory static const ino_t kRootDirectoryID = 1; static const uint32 kAllStatFields = 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; // shine-through directories const char* const kShineThroughDirectories[] = { "cache", "non-packaged", "packages", "settings", "var", NULL }; // sanity limit for activation change request const size_t kMaxActivationRequestSize = 10 * 1024 * 1024; // sanity limit for activation file size const size_t kMaxActivationFileSize = 10 * 1024 * 1024; static const char* const kAdministrativeDirectoryName = PACKAGES_DIRECTORY_ADMIN_DIRECTORY; static const char* const kActivationFileName = PACKAGES_DIRECTORY_ACTIVATION_FILE; static const char* const kActivationFilePath = PACKAGES_DIRECTORY_ADMIN_DIRECTORY "/" PACKAGES_DIRECTORY_ACTIVATION_FILE; // #pragma mark - ShineThroughDirectory struct Volume::ShineThroughDirectory : public Directory { ShineThroughDirectory(ino_t id) : Directory(id) { get_real_time(fModifiedTime); } virtual timespec ModifiedTime() const { return fModifiedTime; } private: timespec fModifiedTime; }; // #pragma mark - ActivationChangeRequest struct Volume::ActivationChangeRequest { public: ActivationChangeRequest() : fRequest(NULL), fRequestSize(0) { } ~ActivationChangeRequest() { free(fRequest); } status_t Init(const void* userRequest, size_t requestSize) { // copy request to kernel if (requestSize > kMaxActivationRequestSize) RETURN_ERROR(B_BAD_VALUE); fRequest = (PackageFSActivationChangeRequest*)malloc(requestSize); if (fRequest == NULL) RETURN_ERROR(B_NO_MEMORY); fRequestSize = requestSize; status_t error = user_memcpy(fRequest, userRequest, fRequestSize); if (error != B_OK) RETURN_ERROR(error); uint32 itemCount = fRequest->itemCount; const char* requestEnd = (const char*)fRequest + requestSize; if (&fRequest->items[itemCount] > (void*)requestEnd) RETURN_ERROR(B_BAD_VALUE); // adjust the item name pointers and check their validity addr_t nameDelta = (addr_t)fRequest - (addr_t)userRequest; for (uint32 i = 0; i < itemCount; i++) { PackageFSActivationChangeItem& item = fRequest->items[i]; item.name += nameDelta; if (item.name < (char*)fRequest || item.name >= requestEnd) RETURN_ERROR(B_BAD_VALUE); size_t maxNameSize = requestEnd - item.name; if (strnlen(item.name, maxNameSize) == maxNameSize) RETURN_ERROR(B_BAD_VALUE); } return B_OK; } uint32 CountItems() const { return fRequest->itemCount; } PackageFSActivationChangeItem* ItemAt(uint32 index) const { return index < CountItems() ? &fRequest->items[index] : NULL; } private: PackageFSActivationChangeRequest* fRequest; size_t fRequestSize; }; // #pragma mark - Volume Volume::Volume(fs_volume* fsVolume) : fFSVolume(fsVolume), fRootDirectory(NULL), fPackageFSRoot(NULL), fPackagesDirectory(NULL), fPackagesDirectories(), fPackagesDirectoriesByNodeRef(), fPackageSettings(), fNextNodeID(kRootDirectoryID + 1) { rw_lock_init(&fLock, "packagefs volume"); } Volume::~Volume() { // remove the packages from the node tree { VolumeWriteLocker systemVolumeLocker(_SystemVolumeIfNotSelf()); VolumeWriteLocker volumeLocker(this); for (PackageFileNameHashTable::Iterator it = fPackages.GetIterator(); Package* package = it.Next();) { _RemovePackageContent(package, NULL, false); } } // delete the packages _RemoveAllPackages(); // delete all indices Index* index = fIndices.Clear(true); while (index != NULL) { Index* next = index->IndexHashLink(); delete index; index = next; } // remove all nodes from the ID hash table Node* node = fNodes.Clear(true); while (node != NULL) { Node* next = node->IDHashTableNext(); node->ReleaseReference(); node = next; } if (fPackageFSRoot != NULL) { if (this == fPackageFSRoot->SystemVolume()) _RemovePackageLinksDirectory(); fPackageFSRoot->UnregisterVolume(this); } if (fRootDirectory != NULL) fRootDirectory->ReleaseReference(); while (PackagesDirectory* directory = fPackagesDirectories.RemoveHead()) directory->ReleaseReference(); rw_lock_destroy(&fLock); } status_t Volume::Mount(const char* parameterString) { // init the hash tables status_t error = fPackagesDirectoriesByNodeRef.Init(); if (error != B_OK) RETURN_ERROR(error); error = fNodes.Init(); if (error != B_OK) RETURN_ERROR(error); error = fNodeListeners.Init(); if (error != B_OK) RETURN_ERROR(error); error = fPackages.Init(); if (error != B_OK) RETURN_ERROR(error); error = fIndices.Init(); if (error != B_OK) RETURN_ERROR(error); // create the name index { NameIndex* index = new(std::nothrow) NameIndex; if (index == NULL) RETURN_ERROR(B_NO_MEMORY); error = index->Init(this); if (error != B_OK) { delete index; RETURN_ERROR(error); } fIndices.Insert(index); } // create the size index { SizeIndex* index = new(std::nothrow) SizeIndex; if (index == NULL) RETURN_ERROR(B_NO_MEMORY); error = index->Init(this); if (error != B_OK) { delete index; RETURN_ERROR(error); } fIndices.Insert(index); } // create the last modified index { LastModifiedIndex* index = new(std::nothrow) LastModifiedIndex; if (index == NULL) RETURN_ERROR(B_NO_MEMORY); error = index->Init(this); if (error != B_OK) { delete index; RETURN_ERROR(error); } fIndices.Insert(index); } // create a BEOS:APP_SIG index { AttributeIndex* index = new(std::nothrow) AttributeIndex; if (index == NULL) RETURN_ERROR(B_NO_MEMORY); error = index->Init(this, "BEOS:APP_SIG", B_MIME_STRING_TYPE, 0); if (error != B_OK) { delete index; RETURN_ERROR(error); } fIndices.Insert(index); } // get the mount parameters const char* packages = NULL; const char* volumeName = NULL; const char* mountType = NULL; const char* shineThrough = NULL; const char* packagesState = NULL; DriverSettingsUnloader parameterHandle( parse_driver_settings_string(parameterString)); if (parameterHandle.IsSet()) { packages = get_driver_parameter(parameterHandle.Get(), "packages", NULL, NULL); volumeName = get_driver_parameter(parameterHandle.Get(), "volume-name", NULL, NULL); mountType = get_driver_parameter(parameterHandle.Get(), "type", NULL, NULL); shineThrough = get_driver_parameter(parameterHandle.Get(), "shine-through", NULL, NULL); packagesState = get_driver_parameter(parameterHandle.Get(), "state", NULL, NULL); } if (packages != NULL && packages[0] == '\0') { FATAL("invalid package folder ('packages' parameter)!\n"); RETURN_ERROR(B_BAD_VALUE); } error = _InitMountType(mountType); if (error != B_OK) { FATAL("invalid mount type: \"%s\"\n", mountType); RETURN_ERROR(error); } // get our mount point error = vfs_get_mount_point(fFSVolume->id, &fMountPoint.deviceID, &fMountPoint.nodeID); if (error != B_OK) RETURN_ERROR(error); // load package settings error = fPackageSettings.Load(fMountPoint.deviceID, fMountPoint.nodeID, fMountType); // abort only in case of serious issues (memory shortage) if (error == B_NO_MEMORY) RETURN_ERROR(error); // create package domain fPackagesDirectory = new(std::nothrow) PackagesDirectory; if (fPackagesDirectory == NULL) RETURN_ERROR(B_NO_MEMORY); fPackagesDirectories.Add(fPackagesDirectory); fPackagesDirectoriesByNodeRef.Insert(fPackagesDirectory); struct stat st; error = fPackagesDirectory->Init(packages, fMountPoint.deviceID, fMountPoint.nodeID, st); if (error != B_OK) RETURN_ERROR(error); // If a packages state has been specified, load the needed states. if (packagesState != NULL) { error = _LoadOldPackagesStates(packagesState); if (error != B_OK) RETURN_ERROR(error); } // If no volume name is given, infer it from the mount type. if (volumeName == NULL) { switch (fMountType) { case PACKAGE_FS_MOUNT_TYPE_SYSTEM: volumeName = "system"; break; case PACKAGE_FS_MOUNT_TYPE_HOME: volumeName = "config"; break; case PACKAGE_FS_MOUNT_TYPE_CUSTOM: default: volumeName = "Package FS"; break; } } String volumeNameString; if (!volumeNameString.SetTo(volumeName)) RETURN_ERROR(B_NO_MEMORY); // create the root node fRootDirectory = new(std::nothrow) ::RootDirectory(kRootDirectoryID, st.st_mtim); if (fRootDirectory == NULL) RETURN_ERROR(B_NO_MEMORY); fRootDirectory->Init(NULL, volumeNameString); fNodes.Insert(fRootDirectory); fRootDirectory->AcquireReference(); // one reference for the table // register with packagefs root error = ::PackageFSRoot::RegisterVolume(this); if (error != B_OK) RETURN_ERROR(error); if (this == fPackageFSRoot->SystemVolume()) { error = _AddPackageLinksDirectory(); if (error != B_OK) RETURN_ERROR(error); } // create shine-through directories error = _CreateShineThroughDirectories(shineThrough); if (error != B_OK) RETURN_ERROR(error); // add initial packages error = _AddInitialPackages(); if (error != B_OK) RETURN_ERROR(error); // publish the root node fRootDirectory->AcquireReference(); error = PublishVNode(fRootDirectory); if (error != B_OK) { fRootDirectory->ReleaseReference(); RETURN_ERROR(error); } // bind and publish the shine-through directories error = _PublishShineThroughDirectories(); if (error != B_OK) RETURN_ERROR(error); StringPool::DumpUsageStatistics(); return B_OK; } void Volume::Unmount() { } status_t Volume::IOCtl(Node* node, uint32 operation, void* buffer, size_t size) { switch (operation) { case PACKAGE_FS_OPERATION_GET_VOLUME_INFO: { if (size < sizeof(PackageFSVolumeInfo)) RETURN_ERROR(B_BAD_VALUE); PackageFSVolumeInfo* userVolumeInfo = (PackageFSVolumeInfo*)buffer; VolumeReadLocker volumeReadLocker(this); PackageFSVolumeInfo volumeInfo; volumeInfo.mountType = fMountType; volumeInfo.rootDeviceID = fPackageFSRoot->DeviceID(); volumeInfo.rootDirectoryID = fPackageFSRoot->NodeID(); volumeInfo.packagesDirectoryCount = fPackagesDirectories.Count(); status_t error = user_memcpy(userVolumeInfo, &volumeInfo, sizeof(volumeInfo)); if (error != B_OK) RETURN_ERROR(error); uint32 directoryIndex = 0; for (PackagesDirectoryList::Iterator it = fPackagesDirectories.GetIterator(); PackagesDirectory* directory = it.Next(); directoryIndex++) { PackageFSDirectoryInfo info; info.deviceID = directory->DeviceID(); info.nodeID = directory->NodeID(); PackageFSDirectoryInfo* userInfo = userVolumeInfo->packagesDirectoryInfos + directoryIndex; if (addr_t(userInfo + 1) > (addr_t)buffer + size) break; if (user_memcpy(userInfo, &info, sizeof(info)) != B_OK) return B_BAD_ADDRESS; } return B_OK; } case PACKAGE_FS_OPERATION_GET_PACKAGE_INFOS: { if (size < sizeof(PackageFSGetPackageInfosRequest)) RETURN_ERROR(B_BAD_VALUE); PackageFSGetPackageInfosRequest* request = (PackageFSGetPackageInfosRequest*)buffer; VolumeReadLocker volumeReadLocker(this); addr_t bufferEnd = (addr_t)buffer + size; uint32 packageCount = fPackages.CountElements(); char* nameBuffer = (char*)(request->infos + packageCount); uint32 packageIndex = 0; for (PackageFileNameHashTable::Iterator it = fPackages.GetIterator(); it.HasNext(); packageIndex++) { Package* package = it.Next(); PackageFSPackageInfo info; info.packageDeviceID = package->DeviceID(); info.packageNodeID = package->NodeID(); PackagesDirectory* directory = package->Directory(); info.directoryDeviceID = directory->DeviceID(); info.directoryNodeID = directory->NodeID(); info.name = nameBuffer; PackageFSPackageInfo* userInfo = request->infos + packageIndex; if (addr_t(userInfo + 1) <= bufferEnd) { if (user_memcpy(userInfo, &info, sizeof(info)) != B_OK) return B_BAD_ADDRESS; } const char* name = package->FileName(); size_t nameSize = strlen(name) + 1; char* nameEnd = nameBuffer + nameSize; if ((addr_t)nameEnd <= bufferEnd) { if (user_memcpy(nameBuffer, name, nameSize) != B_OK) return B_BAD_ADDRESS; } nameBuffer = nameEnd; } PackageFSGetPackageInfosRequest header; header.bufferSize = nameBuffer - (char*)request; header.packageCount = packageCount; size_t headerSize = (char*)&request->infos - (char*)request; RETURN_ERROR(user_memcpy(request, &header, headerSize)); } case PACKAGE_FS_OPERATION_CHANGE_ACTIVATION: { ActivationChangeRequest request; status_t error = request.Init(buffer, size); if (error != B_OK) RETURN_ERROR(B_BAD_VALUE); return _ChangeActivation(request); } default: return B_BAD_VALUE; } } void Volume::AddNodeListener(NodeListener* listener, Node* node) { ASSERT(!listener->IsListening()); listener->StartedListening(node); if (NodeListener* list = fNodeListeners.Lookup(node)) list->AddNodeListener(listener); else fNodeListeners.Insert(listener); } void Volume::RemoveNodeListener(NodeListener* listener) { ASSERT(listener->IsListening()); Node* node = listener->ListenedNode(); if (NodeListener* next = listener->RemoveNodeListener()) { // list not empty yet -- if we removed the head, add a new head to the // hash table NodeListener* list = fNodeListeners.Lookup(node); if (list == listener) { fNodeListeners.Remove(listener); fNodeListeners.Insert(next); } } else fNodeListeners.Remove(listener); listener->StoppedListening(); } void Volume::AddQuery(Query* query) { fQueries.Add(query); } void Volume::RemoveQuery(Query* query) { fQueries.Remove(query); } void Volume::UpdateLiveQueries(Node* node, const char* attribute, int32 type, const void* oldKey, size_t oldLength, const void* newKey, size_t newLength) { for (QueryList::Iterator it = fQueries.GetIterator(); Query* query = it.Next();) { query->LiveUpdate(node, attribute, type, oldKey, oldLength, newKey, newLength); } } status_t Volume::GetVNode(ino_t nodeID, Node*& _node) { return get_vnode(fFSVolume, nodeID, (void**)&_node); } status_t Volume::PutVNode(ino_t nodeID) { return put_vnode(fFSVolume, nodeID); } status_t Volume::RemoveVNode(ino_t nodeID) { return remove_vnode(fFSVolume, nodeID); } status_t Volume::PublishVNode(Node* node) { return publish_vnode(fFSVolume, node->ID(), node, &gPackageFSVnodeOps, node->Mode() & S_IFMT, 0); } void Volume::PackageLinkNodeAdded(Node* node) { _AddPackageLinksNode(node); notify_entry_created(ID(), node->Parent()->ID(), node->Name(), node->ID()); _NotifyNodeAdded(node); } void Volume::PackageLinkNodeRemoved(Node* node) { _RemovePackageLinksNode(node); notify_entry_removed(ID(), node->Parent()->ID(), node->Name(), node->ID()); _NotifyNodeRemoved(node); } void Volume::PackageLinkNodeChanged(Node* node, uint32 statFields, const OldNodeAttributes& oldAttributes) { Directory* parent = node->Parent(); notify_stat_changed(ID(), parent != NULL ? parent->ID() : -1, node->ID(), statFields); _NotifyNodeChanged(node, statFields, oldAttributes); } status_t Volume::_LoadOldPackagesStates(const char* packagesState) { // open and stat the admininistrative dir int fd = openat(fPackagesDirectory->DirectoryFD(), kAdministrativeDirectoryName, O_RDONLY); if (fd < 0) { ERROR("Failed to open administrative directory: %s\n", strerror(errno)); RETURN_ERROR(errno); } struct stat adminDirStat; if (fstat(fd, &adminDirStat) < 0) { ERROR("Failed to fstat() administrative directory: %s\n", strerror(errno)); RETURN_ERROR(errno); } // iterate through the "administrative" dir DirCloser dir(fdopendir(fd)); if (!dir.IsSet()) { ERROR("Failed to open administrative directory: %s\n", strerror(errno)); RETURN_ERROR(errno); } while (dirent* entry = readdir(dir.Get())) { if (strncmp(entry->d_name, "state_", 6) != 0 || strcmp(entry->d_name, packagesState) < 0) { continue; } PackagesDirectory* packagesDirectory = new(std::nothrow) PackagesDirectory; status_t error = packagesDirectory->InitOldState(adminDirStat.st_dev, adminDirStat.st_ino, entry->d_name); if (error != B_OK) { delete packagesDirectory; continue; } fPackagesDirectories.Add(packagesDirectory); fPackagesDirectoriesByNodeRef.Insert(packagesDirectory); INFORM("added old packages dir state \"%s\"\n", packagesDirectory->StateName().Data()); } // sort the packages directories by state age fPackagesDirectories.Sort(&PackagesDirectory::IsNewer); return B_OK; } status_t Volume::_AddInitialPackages() { PackagesDirectory* packagesDirectory = fPackagesDirectories.Last(); INFORM("Adding packages from \"%s\"\n", packagesDirectory->Path()); // try reading the activation file of the oldest state status_t error = _AddInitialPackagesFromActivationFile(packagesDirectory); if (error != B_OK && packagesDirectory != fPackagesDirectory) { WARN("Loading packages from old state \"%s\" failed. Loading packages " "from latest state.\n", packagesDirectory->StateName().Data()); // remove all packages already added { VolumeWriteLocker systemVolumeLocker(_SystemVolumeIfNotSelf()); VolumeWriteLocker volumeLocker(this); _RemoveAllPackages(); } // remove the old states while (fPackagesDirectories.Last() != fPackagesDirectory) fPackagesDirectories.RemoveTail()->ReleaseReference(); // try reading the activation file of the latest state packagesDirectory = fPackagesDirectory; error = _AddInitialPackagesFromActivationFile(packagesDirectory); } if (error != B_OK) { INFORM("Loading packages from activation file failed. Loading all " "packages in packages directory.\n"); // remove all packages already added { VolumeWriteLocker systemVolumeLocker(_SystemVolumeIfNotSelf()); VolumeWriteLocker volumeLocker(this); _RemoveAllPackages(); } // read the whole directory error = _AddInitialPackagesFromDirectory(); if (error != B_OK) RETURN_ERROR(error); } // add the packages to the node tree VolumeWriteLocker systemVolumeLocker(_SystemVolumeIfNotSelf()); VolumeWriteLocker volumeLocker(this); for (PackageFileNameHashTable::Iterator it = fPackages.GetIterator(); Package* package = it.Next();) { error = _AddPackageContent(package, false); if (error != B_OK) { for (it.Rewind(); Package* activePackage = it.Next();) { if (activePackage == package) break; _RemovePackageContent(activePackage, NULL, false); } RETURN_ERROR(error); } } return B_OK; } status_t Volume::_AddInitialPackagesFromActivationFile( PackagesDirectory* packagesDirectory) { // try reading the activation file FileDescriptorCloser fd(openat(packagesDirectory->DirectoryFD(), packagesDirectory == fPackagesDirectory ? kActivationFilePath : kActivationFileName, O_RDONLY)); if (!fd.IsSet()) { INFORM("Failed to open packages activation file: %s\n", strerror(errno)); RETURN_ERROR(errno); } // read the whole file into memory to simplify things struct stat st; if (fstat(fd.Get(), &st) != 0) { ERROR("Failed to stat packages activation file: %s\n", strerror(errno)); RETURN_ERROR(errno); } if (st.st_size > (off_t)kMaxActivationFileSize) { ERROR("The packages activation file is too big.\n"); RETURN_ERROR(B_BAD_DATA); } char* fileContent = (char*)malloc(st.st_size + 1); if (fileContent == NULL) RETURN_ERROR(B_NO_MEMORY); MemoryDeleter fileContentDeleter(fileContent); ssize_t bytesRead = read(fd.Get(), fileContent, st.st_size); if (bytesRead < 0) { ERROR("Failed to read packages activation file: %s\n", strerror(errno)); RETURN_ERROR(errno); } if (bytesRead != st.st_size) { ERROR("Failed to read whole packages activation file\n"); RETURN_ERROR(B_ERROR); } // null-terminate to simplify parsing fileContent[st.st_size] = '\0'; // parse the file and add the respective packages const char* packageName = fileContent; char* const fileContentEnd = fileContent + st.st_size; while (packageName < fileContentEnd) { char* packageNameEnd = strchr(packageName, '\n'); if (packageNameEnd == NULL) packageNameEnd = fileContentEnd; // skip empty lines if (packageName == packageNameEnd) { packageName++; continue; } *packageNameEnd = '\0'; if (packageNameEnd - packageName >= B_FILE_NAME_LENGTH) { ERROR("Invalid packages activation file content.\n"); RETURN_ERROR(B_BAD_DATA); } status_t error = _LoadAndAddInitialPackage(packagesDirectory, packageName); if (error != B_OK) RETURN_ERROR(error); packageName = packageNameEnd + 1; } return B_OK; } status_t Volume::_AddInitialPackagesFromDirectory() { // iterate through the dir and create packages int fd = openat(fPackagesDirectory->DirectoryFD(), ".", O_RDONLY); if (fd < 0) { ERROR("Failed to open packages directory: %s\n", strerror(errno)); RETURN_ERROR(errno); } DirCloser dir(fdopendir(fd)); if (!dir.IsSet()) { ERROR("Failed to open packages directory \"%s\": %s\n", fPackagesDirectory->Path(), strerror(errno)); RETURN_ERROR(errno); } while (dirent* entry = readdir(dir.Get())) { // skip "." and ".." if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; // also skip any entry without a ".hpkg" extension size_t nameLength = strlen(entry->d_name); if (nameLength < 5 || memcmp(entry->d_name + nameLength - 5, ".hpkg", 5) != 0) { continue; } _LoadAndAddInitialPackage(fPackagesDirectory, entry->d_name); } return B_OK; } status_t Volume::_LoadAndAddInitialPackage(PackagesDirectory* packagesDirectory, const char* name) { Package* package; status_t error = _LoadPackage(packagesDirectory, name, package); if (error != B_OK) { ERROR("Failed to load package \"%s\": %s\n", name, strerror(error)); RETURN_ERROR(error); } BReference packageReference(package, true); VolumeWriteLocker systemVolumeLocker(_SystemVolumeIfNotSelf()); VolumeWriteLocker volumeLocker(this); _AddPackage(package); return B_OK; } inline void Volume::_AddPackage(Package* package) { fPackages.Insert(package); package->AcquireReference(); } inline void Volume::_RemovePackage(Package* package) { fPackages.Remove(package); package->ReleaseReference(); } void Volume::_RemoveAllPackages() { Package* package = fPackages.Clear(true); while (package != NULL) { Package* next = package->FileNameHashTableNext(); package->ReleaseReference(); package = next; } } inline Package* Volume::_FindPackage(const char* fileName) const { return fPackages.Lookup(fileName); } status_t Volume::_AddPackageContent(Package* package, bool notify) { // Open the package. We don't need the FD here, but this is an optimization. // The attribute indices may want to read the package nodes' attributes and // the package file would be opened and closed for each attribute instance. // Since Package keeps and shares the FD as long as at least one party has // the package open, we prevent that. int fd = package->Open(); if (fd < 0) RETURN_ERROR(fd); PackageCloser packageCloser(package); status_t error = fPackageFSRoot->AddPackage(package); if (error != B_OK) RETURN_ERROR(error); for (PackageNodeList::Iterator it = package->Nodes().GetIterator(); PackageNode* node = it.Next();) { // skip over ".PackageInfo" file, it isn't part of the package content if (strcmp(node->Name(), BPackageKit::BHPKG::B_HPKG_PACKAGE_INFO_FILE_NAME) == 0) { continue; } error = _AddPackageContentRootNode(package, node, notify); if (error != B_OK) { _RemovePackageContent(package, node, notify); RETURN_ERROR(error); } } return B_OK; } void Volume::_RemovePackageContent(Package* package, PackageNode* endNode, bool notify) { PackageNode* node = package->Nodes().Head(); while (node != NULL) { if (node == endNode) break; PackageNode* nextNode = package->Nodes().GetNext(node); // skip over ".PackageInfo" file, it isn't part of the package content if (strcmp(node->Name(), BPackageKit::BHPKG::B_HPKG_PACKAGE_INFO_FILE_NAME) != 0) { _RemovePackageContentRootNode(package, node, NULL, notify); } node = nextNode; } fPackageFSRoot->RemovePackage(package); } /*! This method recursively iterates through the descendents of the given package root node and adds all package nodes to the node tree in pre-order. Due to limited kernel stack space we avoid deep recursive function calls and rather use the package node stack implied by the tree. */ status_t Volume::_AddPackageContentRootNode(Package* package, PackageNode* rootPackageNode, bool notify) { ASSERT_WRITE_LOCKED_RW_LOCK(&fLock); PackageNode* packageNode = rootPackageNode; Directory* directory = fRootDirectory; directory->WriteLock(); do { Node* node; status_t error = _AddPackageNode(directory, packageNode, notify, node); // returns B_OK with a NULL node, when skipping the node if (error != B_OK) { // unlock all directories while (directory != NULL) { directory->WriteUnlock(); directory = directory->Parent(); } // remove the added package nodes _RemovePackageContentRootNode(package, rootPackageNode, packageNode, notify); RETURN_ERROR(error); } // recurse into directory, unless we're supposed to skip the node if (node != NULL) { if (PackageDirectory* packageDirectory = dynamic_cast(packageNode)) { if (packageDirectory->FirstChild() != NULL) { directory = dynamic_cast(node); packageNode = packageDirectory->FirstChild(); directory->WriteLock(); continue; } } } // continue with the next available (ancestors's) sibling do { PackageDirectory* packageDirectory = packageNode->Parent(); PackageNode* sibling = packageDirectory != NULL ? packageDirectory->NextChild(packageNode) : NULL; if (sibling != NULL) { packageNode = sibling; break; } // no more siblings -- go back up the tree packageNode = packageDirectory; directory->WriteUnlock(); directory = directory->Parent(); // the parent is still locked, so this is safe } while (packageNode != NULL); } while (packageNode != NULL); ASSERT(directory == NULL); return B_OK; } /*! Recursively iterates through the descendents of the given package root node and removes all package nodes from the node tree in post-order, until encountering \a endPackageNode (if non-null). Due to limited kernel stack space we avoid deep recursive function calls and rather use the package node stack implied by the tree. */ void Volume::_RemovePackageContentRootNode(Package* package, PackageNode* rootPackageNode, PackageNode* endPackageNode, bool notify) { ASSERT_WRITE_LOCKED_RW_LOCK(&fLock); PackageNode* packageNode = rootPackageNode; Directory* directory = fRootDirectory; directory->WriteLock(); do { if (packageNode == endPackageNode) { // unlock all directories while (directory != NULL) { directory->WriteUnlock(); directory = directory->Parent(); } break; } // recurse into directory if (PackageDirectory* packageDirectory = dynamic_cast(packageNode)) { if (packageDirectory->FirstChild() != NULL) { if (Directory* childDirectory = dynamic_cast( directory->FindChild(packageNode->Name()))) { directory = childDirectory; packageNode = packageDirectory->FirstChild(); directory->WriteLock(); continue; } } } // continue with the next available (ancestors's) sibling do { PackageDirectory* packageDirectory = packageNode->Parent(); PackageNode* sibling = packageDirectory != NULL ? packageDirectory->NextChild(packageNode) : NULL; // we're done with the node -- remove it _RemovePackageNode(directory, packageNode, directory->FindChild(packageNode->Name()), notify); if (sibling != NULL) { packageNode = sibling; break; } // no more siblings -- go back up the tree packageNode = packageDirectory; directory->WriteUnlock(); directory = directory->Parent(); // the parent is still locked, so this is safe } while (packageNode != NULL/* && packageNode != rootPackageNode*/); } while (packageNode != NULL/* && packageNode != rootPackageNode*/); ASSERT(directory == NULL); } status_t Volume::_AddPackageNode(Directory* directory, PackageNode* packageNode, bool notify, Node*& _node) { bool newNode = false; UnpackingNode* unpackingNode; Node* node = directory->FindChild(packageNode->Name()); PackageNode* oldPackageNode = NULL; if (node != NULL) { unpackingNode = dynamic_cast(node); if (unpackingNode == NULL) { _node = NULL; return B_OK; } oldPackageNode = unpackingNode->GetPackageNode(); } else { status_t error = _CreateUnpackingNode(packageNode->Mode(), directory, packageNode->Name(), unpackingNode); if (error != B_OK) RETURN_ERROR(error); node = unpackingNode->GetNode(); newNode = true; } BReference nodeReference(node); NodeWriteLocker nodeWriteLocker(node); BReference newNodeReference; NodeWriteLocker newNodeWriteLocker; Node* oldNode = NULL; if (!newNode && !S_ISDIR(node->Mode()) && oldPackageNode != NULL && unpackingNode->WillBeFirstPackageNode(packageNode)) { // The package node we're going to add will represent the node, // replacing the current head package node. Since the node isn't a // directory, we must make sure that clients having opened or mapped the // node won't be surprised. So we create a new node and remove the // current one. // create a new node and transfer the package nodes to it UnpackingNode* newUnpackingNode; status_t error = unpackingNode->CloneTransferPackageNodes( fNextNodeID++, newUnpackingNode); if (error != B_OK) RETURN_ERROR(error); // remove the old node _NotifyNodeRemoved(node); _RemoveNodeAndVNode(node); oldNode = node; // add the new node unpackingNode = newUnpackingNode; node = unpackingNode->GetNode(); newNodeReference.SetTo(node); newNodeWriteLocker.SetTo(node, false); directory->AddChild(node); fNodes.Insert(node); newNode = true; } status_t error = unpackingNode->AddPackageNode(packageNode, ID()); if (error != B_OK) { // Remove the node, if created before. If the node was created to // replace the previous node, send out notifications instead. if (newNode) { if (oldNode != NULL) { _NotifyNodeAdded(node); if (notify) { notify_entry_removed(ID(), directory->ID(), oldNode->Name(), oldNode->ID()); notify_entry_created(ID(), directory->ID(), node->Name(), node->ID()); } } else _RemoveNode(node); } RETURN_ERROR(error); } if (newNode) { _NotifyNodeAdded(node); } else if (packageNode == unpackingNode->GetPackageNode()) { _NotifyNodeChanged(node, kAllStatFields, OldUnpackingNodeAttributes(oldPackageNode)); } if (notify) { if (newNode) { if (oldNode != NULL) { notify_entry_removed(ID(), directory->ID(), oldNode->Name(), oldNode->ID()); } notify_entry_created(ID(), directory->ID(), node->Name(), node->ID()); } else if (packageNode == unpackingNode->GetPackageNode()) { // The new package node has become the one representing the node. // Send stat changed notification for directories and entry // removed + created notifications for files and symlinks. notify_stat_changed(ID(), directory->ID(), node->ID(), kAllStatFields); // TODO: Actually the attributes might change, too! } } _node = node; return B_OK; } void Volume::_RemovePackageNode(Directory* directory, PackageNode* packageNode, Node* node, bool notify) { UnpackingNode* unpackingNode = dynamic_cast(node); if (unpackingNode == NULL) return; BReference nodeReference(node); NodeWriteLocker nodeWriteLocker(node); PackageNode* headPackageNode = unpackingNode->GetPackageNode(); bool nodeRemoved = false; Node* newNode = NULL; BReference newNodeReference; NodeWriteLocker newNodeWriteLocker; // If this is the last package node of the node, remove it completely. if (unpackingNode->IsOnlyPackageNode(packageNode)) { // Notify before removing the node. Otherwise the indices might not // find the node anymore. _NotifyNodeRemoved(node); unpackingNode->PrepareForRemoval(); _RemoveNodeAndVNode(node); nodeRemoved = true; } else if (packageNode == headPackageNode) { // The node does at least have one more package node, but the one to be // removed is the head. Unless it's a directory, we replace the node // with a completely new one and let the old one die. This is necessary // to avoid surprises for clients that have opened/mapped the node. if (S_ISDIR(packageNode->Mode())) { unpackingNode->RemovePackageNode(packageNode, ID()); _NotifyNodeChanged(node, kAllStatFields, OldUnpackingNodeAttributes(headPackageNode)); } else { // create a new node and transfer the package nodes to it UnpackingNode* newUnpackingNode; status_t error = unpackingNode->CloneTransferPackageNodes( fNextNodeID++, newUnpackingNode); if (error == B_OK) { // remove the package node newUnpackingNode->RemovePackageNode(packageNode, ID()); // remove the old node _NotifyNodeRemoved(node); _RemoveNodeAndVNode(node); // add the new node newNode = newUnpackingNode->GetNode(); newNodeReference.SetTo(newNode); newNodeWriteLocker.SetTo(newNode, false); directory->AddChild(newNode); fNodes.Insert(newNode); _NotifyNodeAdded(newNode); } else { // There's nothing we can do. Remove the node completely. _NotifyNodeRemoved(node); unpackingNode->PrepareForRemoval(); _RemoveNodeAndVNode(node); nodeRemoved = true; } } } else { // The package node to remove is not the head of the node. This change // doesn't have any visible effect. unpackingNode->RemovePackageNode(packageNode, ID()); } if (!notify) return; // send notifications if (nodeRemoved) { notify_entry_removed(ID(), directory->ID(), node->Name(), node->ID()); } else if (packageNode == headPackageNode) { // The removed package node was the one representing the node. // Send stat changed notification for directories and entry // removed + created notifications for files and symlinks. if (S_ISDIR(packageNode->Mode())) { notify_stat_changed(ID(), directory->ID(), node->ID(), kAllStatFields); // TODO: Actually the attributes might change, too! } else { notify_entry_removed(ID(), directory->ID(), node->Name(), node->ID()); notify_entry_created(ID(), directory->ID(), newNode->Name(), newNode->ID()); } } } status_t Volume::_CreateUnpackingNode(mode_t mode, Directory* parent, const String& name, UnpackingNode*& _node) { UnpackingNode* unpackingNode; if (S_ISREG(mode) || S_ISLNK(mode)) unpackingNode = new(std::nothrow) UnpackingLeafNode(fNextNodeID++); else if (S_ISDIR(mode)) unpackingNode = new(std::nothrow) UnpackingDirectory(fNextNodeID++); else RETURN_ERROR(B_UNSUPPORTED); if (unpackingNode == NULL) RETURN_ERROR(B_NO_MEMORY); Node* node = unpackingNode->GetNode(); BReference nodeReference(node, true); status_t error = node->Init(parent, name); if (error != B_OK) RETURN_ERROR(error); parent->AddChild(node); fNodes.Insert(node); nodeReference.Detach(); // we keep the initial node reference for the table _node = unpackingNode; return B_OK; } void Volume::_RemoveNode(Node* node) { // remove from parent Directory* parent = node->Parent(); parent->RemoveChild(node); // remove from node table fNodes.Remove(node); node->ReleaseReference(); } void Volume::_RemoveNodeAndVNode(Node* node) { // If the node is known to the VFS, we get the vnode, remove it, and put it, // so that the VFS will discard it as soon as possible (i.e. now, if no one // else is using it). NodeWriteLocker nodeWriteLocker(node); // Remove the node from its parent and the volume. This makes the node // inaccessible via the get_vnode() and lookup() hooks. _RemoveNode(node); bool getVNode = node->IsKnownToVFS(); nodeWriteLocker.Unlock(); // Get a vnode reference, if the node is already known to the VFS. Node* dummyNode; if (getVNode && GetVNode(node->ID(), dummyNode) == B_OK) { // TODO: There still is a race condition here which we can't avoid // without more help from the VFS. Right after we drop the write // lock a vnode for the node could be discarded by the VFS. At that // point another thread trying to get the vnode by ID would create // a vnode, mark it busy and call our get_vnode() hook. It would // block since we (i.e. the package loader thread executing this // method) still have the volume write lock. Our get_vnode() call // would block, since it finds the vnode marked busy. It times out // eventually, but until then a good deal of FS operations might // block as well due to us holding the volume lock and probably // several node locks as well. A get_vnode*() variant (e.g. // get_vnode_etc() with flags parameter) that wouldn't block and // only get the vnode, if already loaded and non-busy, would be // perfect here. RemoveVNode(node->ID()); PutVNode(node->ID()); } } status_t Volume::_LoadPackage(PackagesDirectory* packagesDirectory, const char* name, Package*& _package) { // Find the package -- check the specified packages directory and iterate // toward the newer states. struct stat st; for (;;) { if (packagesDirectory == NULL) return B_ENTRY_NOT_FOUND; if (fstatat(packagesDirectory->DirectoryFD(), name, &st, 0) == 0) { // check whether the entry is a file if (!S_ISREG(st.st_mode)) return B_BAD_VALUE; break; } packagesDirectory = fPackagesDirectories.GetPrevious(packagesDirectory); } // create a package Package* package = new(std::nothrow) Package(this, packagesDirectory, st.st_dev, st.st_ino); if (package == NULL) RETURN_ERROR(B_NO_MEMORY); BReference packageReference(package, true); status_t error = package->Init(name); if (error != B_OK) return error; error = package->Load(fPackageSettings); if (error != B_OK) return error; _package = packageReference.Detach(); return B_OK; } status_t Volume::_ChangeActivation(ActivationChangeRequest& request) { uint32 itemCount = request.CountItems(); if (itemCount == 0) return B_OK; // first check the request int32 newPackageCount = 0; int32 oldPackageCount = 0; { VolumeReadLocker volumeLocker(this); for (uint32 i = 0; i < itemCount; i++) { PackageFSActivationChangeItem* item = request.ItemAt(i); if (item->parentDeviceID != fPackagesDirectory->DeviceID() || item->parentDirectoryID != fPackagesDirectory->NodeID()) { ERROR("Volume::_ChangeActivation(): mismatching packages " "directory\n"); RETURN_ERROR(B_MISMATCHED_VALUES); } Package* package = _FindPackage(item->name); // TODO: We should better look up the package by node_ref! if (item->type == PACKAGE_FS_ACTIVATE_PACKAGE) { if (package != NULL) { ERROR("Volume::_ChangeActivation(): package to activate " "already activated: \"%s\"\n", item->name); RETURN_ERROR(B_NAME_IN_USE); } newPackageCount++; } else if (item->type == PACKAGE_FS_DEACTIVATE_PACKAGE) { if (package == NULL) { ERROR("Volume::_ChangeActivation(): package to deactivate " "not found: \"%s\"\n", item->name); RETURN_ERROR(B_NAME_NOT_FOUND); } oldPackageCount++; } else if (item->type == PACKAGE_FS_REACTIVATE_PACKAGE) { if (package == NULL) { ERROR("Volume::_ChangeActivation(): package to reactivate " "not found: \"%s\"\n", item->name); RETURN_ERROR(B_NAME_NOT_FOUND); } oldPackageCount++; newPackageCount++; } else RETURN_ERROR(B_BAD_VALUE); } } INFORM("Volume::_ChangeActivation(): %" B_PRId32 " new packages, %" B_PRId32 " old packages\n", newPackageCount, oldPackageCount); // Things look good so far -- allocate reference arrays for the packages to // add and remove. BReference* newPackageReferences = new(std::nothrow) BReference[newPackageCount]; if (newPackageReferences == NULL) RETURN_ERROR(B_NO_MEMORY); ArrayDeleter > newPackageReferencesDeleter( newPackageReferences); BReference* oldPackageReferences = new(std::nothrow) BReference[oldPackageCount]; if (oldPackageReferences == NULL) RETURN_ERROR(B_NO_MEMORY); ArrayDeleter > oldPackageReferencesDeleter( oldPackageReferences); // load all new packages int32 newPackageIndex = 0; for (uint32 i = 0; i < itemCount; i++) { PackageFSActivationChangeItem* item = request.ItemAt(i); if (item->type != PACKAGE_FS_ACTIVATE_PACKAGE && item->type != PACKAGE_FS_REACTIVATE_PACKAGE) { continue; } Package* package; status_t error = _LoadPackage(fPackagesDirectory, item->name, package); if (error != B_OK) { ERROR("Volume::_ChangeActivation(): failed to load package " "\"%s\"\n", item->name); RETURN_ERROR(error); } newPackageReferences[newPackageIndex++].SetTo(package, true); } // apply the changes VolumeWriteLocker systemVolumeLocker(_SystemVolumeIfNotSelf()); VolumeWriteLocker volumeLocker(this); // TODO: Add a change counter to Volume, so we can easily check whether // everything is still the same. // remove the old packages int32 oldPackageIndex = 0; for (uint32 i = 0; i < itemCount; i++) { PackageFSActivationChangeItem* item = request.ItemAt(i); if (item->type != PACKAGE_FS_DEACTIVATE_PACKAGE && item->type != PACKAGE_FS_REACTIVATE_PACKAGE) { continue; } Package* package = _FindPackage(item->name); // TODO: We should better look up the package by node_ref! oldPackageReferences[oldPackageIndex++].SetTo(package); _RemovePackageContent(package, NULL, true); _RemovePackage(package); INFORM("package \"%s\" deactivated\n", package->FileName().Data()); } // TODO: Since package removal cannot fail, consider adding the new packages // first. The reactivation case may make that problematic, since two packages // with the same name would be active after activating the new one. Check! // add the new packages status_t error = B_OK; for (newPackageIndex = 0; newPackageIndex < newPackageCount; newPackageIndex++) { Package* package = newPackageReferences[newPackageIndex]; _AddPackage(package); // add the package to the node tree error = _AddPackageContent(package, true); if (error != B_OK) { _RemovePackage(package); break; } INFORM("package \"%s\" activated\n", package->FileName().Data()); } // Try to roll back the changes, if an error occurred. if (error != B_OK) { for (int32 i = newPackageIndex - 1; i >= 0; i--) { Package* package = newPackageReferences[i]; _RemovePackageContent(package, NULL, true); _RemovePackage(package); } for (int32 i = oldPackageCount - 1; i >= 0; i--) { Package* package = oldPackageReferences[i]; _AddPackage(package); if (_AddPackageContent(package, true) != B_OK) { // nothing we can do here ERROR("Volume::_ChangeActivation(): failed to roll back " "deactivation of package \"%s\" after error\n", package->FileName().Data()); _RemovePackage(package); } } } return error; } status_t Volume::_InitMountType(const char* mountType) { if (mountType == NULL) fMountType = PACKAGE_FS_MOUNT_TYPE_CUSTOM; else if (strcmp(mountType, "system") == 0) fMountType = PACKAGE_FS_MOUNT_TYPE_SYSTEM; else if (strcmp(mountType, "home") == 0) fMountType = PACKAGE_FS_MOUNT_TYPE_HOME; else if (strcmp(mountType, "custom") == 0) fMountType = PACKAGE_FS_MOUNT_TYPE_CUSTOM; else RETURN_ERROR(B_BAD_VALUE); return B_OK; } status_t Volume::_CreateShineThroughDirectory(Directory* parent, const char* name, Directory*& _directory) { ShineThroughDirectory* directory = new(std::nothrow) ShineThroughDirectory( fNextNodeID++); if (directory == NULL) RETURN_ERROR(B_NO_MEMORY); BReference directoryReference(directory, true); String nameString; if (!nameString.SetTo(name)) RETURN_ERROR(B_NO_MEMORY); status_t error = directory->Init(parent, nameString); if (error != B_OK) RETURN_ERROR(error); parent->AddChild(directory); fNodes.Insert(directory); directoryReference.Detach(); // we keep the initial node reference for the table _directory = directory; return B_OK; } status_t Volume::_CreateShineThroughDirectories(const char* shineThroughSetting) { // get the directories to map const char* const* directories = NULL; if (shineThroughSetting == NULL) { // nothing specified -- derive from mount type switch (fMountType) { case PACKAGE_FS_MOUNT_TYPE_SYSTEM: case PACKAGE_FS_MOUNT_TYPE_HOME: directories = kShineThroughDirectories; break; case PACKAGE_FS_MOUNT_TYPE_CUSTOM: return B_OK; case PACKAGE_FS_MOUNT_TYPE_ENUM_COUNT: return B_BAD_VALUE; } } else if (strcmp(shineThroughSetting, "system") == 0) directories = kShineThroughDirectories; else if (strcmp(shineThroughSetting, "home") == 0) directories = kShineThroughDirectories; else if (strcmp(shineThroughSetting, "none") == 0) directories = NULL; else RETURN_ERROR(B_BAD_VALUE); if (directories == NULL) return B_OK; // iterate through the directory list and create the directories while (const char* directoryName = *(directories++)) { // create the directory Directory* directory; status_t error = _CreateShineThroughDirectory(fRootDirectory, directoryName, directory); if (error != B_OK) RETURN_ERROR(error); } return B_OK; } status_t Volume::_PublishShineThroughDirectories() { // Iterate through the root directory children and bind the shine-through // directories to the respective mount point subdirectories. Node* nextNode; for (Node* node = fRootDirectory->FirstChild(); node != NULL; node = nextNode) { nextNode = fRootDirectory->NextChild(node); // skip anything but shine-through directories ShineThroughDirectory* directory = dynamic_cast(node); if (directory == NULL) continue; const char* directoryName = directory->Name(); // look up the mount point subdirectory struct vnode* vnode; status_t error = vfs_entry_ref_to_vnode(fMountPoint.deviceID, fMountPoint.nodeID, directoryName, &vnode); if (error != B_OK) { dprintf("packagefs: Failed to get shine-through directory \"%s\": " "%s\n", directoryName, strerror(error)); _RemoveNode(directory); continue; } VnodePutter vnodePutter(vnode); // stat it struct stat st; error = vfs_stat_vnode(vnode, &st); if (error != B_OK) { dprintf("packagefs: Failed to stat shine-through directory \"%s\": " "%s\n", directoryName, strerror(error)); _RemoveNode(directory); continue; } if (!S_ISDIR(st.st_mode)) { dprintf("packagefs: Shine-through entry \"%s\" is not a " "directory\n", directoryName); _RemoveNode(directory); continue; } // publish the vnode, so the VFS will find it without asking us directory->AcquireReference(); error = PublishVNode(directory); if (error != B_OK) { directory->ReleaseReference(); _RemoveNode(directory); RETURN_ERROR(error); } // bind the directory error = vfs_bind_mount_directory(st.st_dev, st.st_ino, fFSVolume->id, directory->ID()); PutVNode(directory->ID()); // release our reference again -- on success // vfs_bind_mount_directory() got one if (error != B_OK) RETURN_ERROR(error); } return B_OK; } status_t Volume::_AddPackageLinksDirectory() { // called when mounting, so we don't need to lock the volume PackageLinksDirectory* packageLinksDirectory = fPackageFSRoot->GetPackageLinksDirectory(); NodeWriteLocker rootDirectoryWriteLocker(fRootDirectory); NodeWriteLocker packageLinksDirectoryWriteLocker(packageLinksDirectory); packageLinksDirectory->SetParent(fRootDirectory); fRootDirectory->AddChild(packageLinksDirectory); _AddPackageLinksNode(packageLinksDirectory); packageLinksDirectory->SetListener(this); return B_OK; } void Volume::_RemovePackageLinksDirectory() { PackageLinksDirectory* packageLinksDirectory = fPackageFSRoot->GetPackageLinksDirectory(); VolumeWriteLocker volumeLocker(this); NodeWriteLocker rootDirectoryWriteLocker(fRootDirectory); NodeWriteLocker packageLinksDirectoryWriteLocker(packageLinksDirectory); if (packageLinksDirectory->Parent() == fRootDirectory) { packageLinksDirectory->SetListener(NULL); fRootDirectory->RemoveChild(packageLinksDirectory); packageLinksDirectory->SetParent(NULL); } } void Volume::_AddPackageLinksNode(Node* node) { node->SetID(fNextNodeID++); fNodes.Insert(node); node->AcquireReference(); // If this is a directory, recursively add descendants. The directory tree // for the package links isn't deep, so we can do recursion. if (Directory* directory = dynamic_cast(node)) { for (Node* child = directory->FirstChild(); child != NULL; child = directory->NextChild(child)) { NodeWriteLocker childWriteLocker(child); _AddPackageLinksNode(child); } } } void Volume::_RemovePackageLinksNode(Node* node) { // If this is a directory, recursively remove descendants. The directory // tree for the package links isn't deep, so we can do recursion. if (Directory* directory = dynamic_cast(node)) { for (Node* child = directory->FirstChild(); child != NULL; child = directory->NextChild(child)) { NodeWriteLocker childWriteLocker(child); _RemovePackageLinksNode(child); } } fNodes.Remove(node); node->ReleaseReference(); } inline Volume* Volume::_SystemVolumeIfNotSelf() const { if (Volume* systemVolume = fPackageFSRoot->SystemVolume()) return systemVolume == this ? NULL : systemVolume; return NULL; } void Volume::_NotifyNodeAdded(Node* node) { Node* key = node; for (int i = 0; i < 2; i++) { if (NodeListener* listener = fNodeListeners.Lookup(key)) { NodeListener* last = listener->PreviousNodeListener(); while (true) { NodeListener* next = listener->NextNodeListener(); listener->NodeAdded(node); if (listener == last) break; listener = next; } } key = NULL; } } void Volume::_NotifyNodeRemoved(Node* node) { Node* key = node; for (int i = 0; i < 2; i++) { if (NodeListener* listener = fNodeListeners.Lookup(key)) { NodeListener* last = listener->PreviousNodeListener(); while (true) { NodeListener* next = listener->NextNodeListener(); listener->NodeRemoved(node); if (listener == last) break; listener = next; } } key = NULL; } } void Volume::_NotifyNodeChanged(Node* node, uint32 statFields, const OldNodeAttributes& oldAttributes) { Node* key = node; for (int i = 0; i < 2; i++) { if (NodeListener* listener = fNodeListeners.Lookup(key)) { NodeListener* last = listener->PreviousNodeListener(); while (true) { NodeListener* next = listener->NextNodeListener(); listener->NodeChanged(node, statFields, oldAttributes); if (listener == last) break; listener = next; } } key = NULL; } }