/* * Copyright 2013 Haiku, Inc. All rights reserved. * Distributed under the terms of the MIT License. * * Authors: * Ingo Weinhold, ingo_weinhold@gmx.de */ #include "VirtualDirectoryManager.h" #include #include #include #include #include #include #include #include #include #include #include "MimeTypes.h" #include "Model.h" namespace BPrivate { static const size_t kMaxVirtualDirectoryFileSize = 10 * 1024; static const char* const kTemporaryDefinitionFileBaseDirectoryPath = "/tmp/tracker/virtual-directories"; // #pragma mark - VirtualDirectoryManager::Info class VirtualDirectoryManager::Info { private: typedef BObjectList InfoList; public: Info(RootInfo* root, Info* parent, const BString& path, const node_ref& definitionFileNodeRef, const entry_ref& definitionFileEntryRef) : fRoot(root), fParent(parent), fPath(path), fDefinitionFileNodeRef(definitionFileNodeRef), fDefinitionFileEntryRef(definitionFileEntryRef), fId(), fChildDefinitionsDirectoryRef(-1, -1), fChildren(10, true) { } ~Info() { } RootInfo* Root() const { return fRoot; } Info* Parent() const { return fParent; } const char* Name() const { return fDefinitionFileEntryRef.name; } const BString& Path() const { return fPath; } const node_ref& DefinitionFileNodeRef() const { return fDefinitionFileNodeRef; } const entry_ref& DefinitionFileEntryRef() const { return fDefinitionFileEntryRef; } const InfoList& Children() const { return fChildren; } const BString& Id() const { return fId; } void SetId(const BString& id) { fId = id; } const node_ref& ChildDefinitionsDirectoryRef() const { return fChildDefinitionsDirectoryRef; } void SetChildDefinitionsDirectoryRef(const node_ref& ref) { fChildDefinitionsDirectoryRef = ref; } Info* GetChild(const char* name) const { for (int32 i = 0; Info* child = fChildren.ItemAt(i); i++) { if (strcmp(name, child->Name()) == 0) return child; } return NULL; } Info* CreateChild(const node_ref& definitionFileNodeRef, const entry_ref& definitionFileEntryRef) { BString path; if (fPath.IsEmpty()) { path = definitionFileEntryRef.name; } else { path.SetToFormat("%s/%s", fPath.String(), definitionFileEntryRef.name); } if (path.IsEmpty()) return NULL; Info* info = new(std::nothrow) Info(fRoot, this, path, definitionFileNodeRef, definitionFileEntryRef); if (info == NULL || !fChildren.AddItem(info)) { delete info; return NULL; } return info; } bool DeleteChild(Info* info) { return fChildren.RemoveItem(info, true); } void DeleteChildAt(int32 index) { delete fChildren.RemoveItemAt(index); } private: RootInfo* fRoot; Info* fParent; BString fPath; node_ref fDefinitionFileNodeRef; entry_ref fDefinitionFileEntryRef; BString fId; node_ref fChildDefinitionsDirectoryRef; InfoList fChildren; }; // #pragma mark - VirtualDirectoryManager::RootInfo class VirtualDirectoryManager::RootInfo { public: RootInfo(const node_ref& definitionFileNodeRef, const entry_ref& definitionFileEntryRef) : fDirectoryPaths(), fInfo(new(std::nothrow) VirtualDirectoryManager::Info(this, NULL, BString(), definitionFileNodeRef, definitionFileEntryRef)), fFileTime(-1), fLastChangeTime(-1) { } ~RootInfo() { delete fInfo; } status_t InitCheck() const { return fInfo != NULL ? B_OK : B_NO_MEMORY; } bigtime_t FileTime() const { return fFileTime; } bigtime_t LastChangeTime() const { return fLastChangeTime; } const BStringList& DirectoryPaths() const { return fDirectoryPaths; } status_t ReadDefinition(bool* _changed = NULL) { // open the definition file BFile file; status_t error = file.SetTo(&fInfo->DefinitionFileEntryRef(), B_READ_ONLY); if (error != B_OK) return error; struct stat st; error = file.GetStat(&st); if (error != B_OK) return error; bigtime_t fileTime = st.st_mtim.tv_sec; fileTime *= 1000000; fileTime += st.st_mtim.tv_nsec / 1000; if (fileTime == fFileTime) { if (_changed != NULL) *_changed = false; return B_OK; } if (node_ref(st.st_dev, st.st_ino) != fInfo->DefinitionFileNodeRef()) return B_ENTRY_NOT_FOUND; // read the contents off_t fileSize = st.st_size; if (fileSize > (off_t)kMaxVirtualDirectoryFileSize) return B_BAD_VALUE; char* buffer = new(std::nothrow) char[fileSize + 1]; if (buffer == NULL) return B_NO_MEMORY; ArrayDeleter bufferDeleter(buffer); ssize_t bytesRead = file.ReadAt(0, buffer, fileSize); if (bytesRead < 0) return bytesRead; buffer[bytesRead] = '\0'; // parse it BStringList oldDirectoryPaths(fDirectoryPaths); fDirectoryPaths.MakeEmpty(); BDriverSettings driverSettings; error = driverSettings.SetToString(buffer); if (error != B_OK) return error; BDriverParameterIterator it = driverSettings.ParameterIterator("directory"); while (it.HasNext()) { BDriverParameter parameter = it.Next(); for (int32 i = 0; i < parameter.CountValues(); i++) fDirectoryPaths.Add(parameter.ValueAt(i)); } // update file time and check whether something has changed fFileTime = fileTime; bool changed = fDirectoryPaths != oldDirectoryPaths; if (changed || fLastChangeTime < 0) fLastChangeTime = fFileTime; if (_changed != NULL) *_changed = changed; return B_OK; } VirtualDirectoryManager::Info* Info() const { return fInfo; } private: typedef std::map InfoMap; private: BStringList fDirectoryPaths; VirtualDirectoryManager::Info* fInfo; bigtime_t fFileTime; // actual file modified time bigtime_t fLastChangeTime; // last time something actually changed }; // #pragma mark - VirtualDirectoryManager VirtualDirectoryManager::VirtualDirectoryManager() : fLock("virtual directory manager") { } /*static*/ VirtualDirectoryManager* VirtualDirectoryManager::Instance() { static VirtualDirectoryManager* manager = new(std::nothrow) VirtualDirectoryManager; return manager; } status_t VirtualDirectoryManager::ResolveDirectoryPaths( const node_ref& definitionFileNodeRef, const entry_ref& definitionFileEntryRef, BStringList& _directoryPaths, node_ref* _definitionFileNodeRef, entry_ref* _definitionFileEntryRef) { Info* info = _InfoForNodeRef(definitionFileNodeRef); if (info == NULL) { status_t error = _ResolveUnknownDefinitionFile(definitionFileNodeRef, definitionFileEntryRef, info); if (error != B_OK) return error; } const BString& subDirectory = info->Path(); const BStringList& rootDirectoryPaths = info->Root()->DirectoryPaths(); if (subDirectory.IsEmpty()) { _directoryPaths = rootDirectoryPaths; } else { _directoryPaths.MakeEmpty(); int32 count = rootDirectoryPaths.CountStrings(); for (int32 i = 0; i < count; i++) { BString path = rootDirectoryPaths.StringAt(i); _directoryPaths.Add(path << '/' << subDirectory); } } if (_definitionFileEntryRef != NULL) { *_definitionFileEntryRef = info->DefinitionFileEntryRef(); if (_definitionFileEntryRef->name == NULL) return B_NO_MEMORY; } if (_definitionFileNodeRef != NULL) *_definitionFileNodeRef = info->DefinitionFileNodeRef(); return B_OK; } bool VirtualDirectoryManager::GetDefinitionFileChangeTime( const node_ref& definitionFileRef, bigtime_t& _time) const { Info* info = _InfoForNodeRef(definitionFileRef); if (info == NULL) return false; _time = info->Root()->LastChangeTime(); return true; } bool VirtualDirectoryManager::GetRootDefinitionFile( const node_ref& definitionFileRef, node_ref& _rootDefinitionFileRef) { Info* info = _InfoForNodeRef(definitionFileRef); if (info == NULL) return false; _rootDefinitionFileRef = info->Root()->Info()->DefinitionFileNodeRef(); return true; } bool VirtualDirectoryManager::GetSubDirectoryDefinitionFile( const node_ref& baseDefinitionRef, const char* subDirName, entry_ref& _entryRef, node_ref& _nodeRef) { Info* parentInfo = _InfoForNodeRef(baseDefinitionRef); if (parentInfo == NULL) return false; Info* info = parentInfo->GetChild(subDirName); if (info == NULL) return false; _entryRef = info->DefinitionFileEntryRef(); _nodeRef = info->DefinitionFileNodeRef(); return _entryRef.name != NULL; } bool VirtualDirectoryManager::GetParentDirectoryDefinitionFile( const node_ref& subDirDefinitionRef, entry_ref& _entryRef, node_ref& _nodeRef) { Info* info = _InfoForNodeRef(subDirDefinitionRef); if (info == NULL) return false; Info* parentInfo = info->Parent(); if (parentInfo == NULL) return false; _entryRef = parentInfo->DefinitionFileEntryRef(); _nodeRef = parentInfo->DefinitionFileNodeRef(); return _entryRef.name != NULL; } status_t VirtualDirectoryManager::TranslateDirectoryEntry( const node_ref& definitionFileRef, dirent* buffer) { NotOwningEntryRef entryRef(buffer->d_pdev, buffer->d_pino, buffer->d_name); node_ref nodeRef(buffer->d_dev, buffer->d_ino); status_t result = TranslateDirectoryEntry(definitionFileRef, entryRef, nodeRef); if (result != B_OK) return result; buffer->d_pdev = entryRef.device; buffer->d_pino = entryRef.directory; buffer->d_dev = nodeRef.device; buffer->d_ino = nodeRef.node; return B_OK; } status_t VirtualDirectoryManager::TranslateDirectoryEntry( const node_ref& definitionFileRef, entry_ref& _entryRef, node_ref& _nodeRef) { Info* parentInfo = _InfoForNodeRef(definitionFileRef); if (parentInfo == NULL) return B_BAD_VALUE; // get the info for the entry Info* info = parentInfo->GetChild(_entryRef.name); if (info == NULL) { // If not done yet, create a directory for the parent, where we can // place the new definition file. if (parentInfo->Id().IsEmpty()) { BString id = BUuid().SetToRandom().ToString(); if (id.IsEmpty()) return B_NO_MEMORY; BPath path(kTemporaryDefinitionFileBaseDirectoryPath, id); status_t error = path.InitCheck(); if (error != B_OK) return error; error = create_directory(path.Path(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); if (error != B_OK) return error; struct stat st; if (lstat(path.Path(), &st) != 0) return errno; parentInfo->SetId(id); parentInfo->SetChildDefinitionsDirectoryRef( node_ref(st.st_dev, st.st_ino)); } // create the definition file const node_ref& directoryRef = parentInfo->ChildDefinitionsDirectoryRef(); NotOwningEntryRef entryRef(directoryRef, _entryRef.name); BFile definitionFile; status_t error = definitionFile.SetTo(&entryRef, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); if (error != B_OK) return error; node_ref nodeRef; error = definitionFile.GetNodeRef(&nodeRef); if (error != B_OK) return error; BNodeInfo nodeInfo(&definitionFile); error = nodeInfo.SetType(kVirtualDirectoryMimeType); if (error != B_OK) return error; // create the info info = parentInfo->CreateChild(nodeRef, entryRef); if (info == NULL || !_AddInfo(info)) return B_NO_MEMORY; // Write some info into the definition file that helps us to find the // root definition file. This is only necessary when definition file // entry refs are transferred between applications. Then the receiving // application may need to find the root definition file and resolve // the subdirectories. const entry_ref& rootEntryRef = parentInfo->Root()->Info()->DefinitionFileEntryRef(); BString definitionFileContent; definitionFileContent.SetToFormat( "root {\n" " device %" B_PRIdDEV "\n" " directory %" B_PRIdINO "\n" " name \"%s\"\n" "}\n" "subdir \"%s\"\n", rootEntryRef.device, rootEntryRef.directory, rootEntryRef.name, info->Path().String()); // failure is not nice, but not mission critical for this application if (!definitionFileContent.IsEmpty()) { definitionFile.WriteAt(0, definitionFileContent.String(), definitionFileContent.Length()); } } const entry_ref& entryRef = info->DefinitionFileEntryRef(); _nodeRef = info->DefinitionFileNodeRef(); _entryRef.device = entryRef.device; _entryRef.directory = entryRef.directory; return B_OK; } bool VirtualDirectoryManager::DefinitionFileChanged( const node_ref& definitionFileRef) { Info* info = _InfoForNodeRef(definitionFileRef); if (info == NULL) return false; _UpdateTree(info->Root()); return _InfoForNodeRef(definitionFileRef) != NULL; } status_t VirtualDirectoryManager::DirectoryRemoved(const node_ref& definitionFileRef) { Info* info = _InfoForNodeRef(definitionFileRef); if (info == NULL) return B_ENTRY_NOT_FOUND; _RemoveDirectory(info); // delete the info if (info->Parent() == NULL) delete info->Root(); else info->Parent()->DeleteChild(info); return B_OK; } /*static*/ bool VirtualDirectoryManager::GetEntry(const BStringList& directoryPaths, const char* name, entry_ref* _ref, struct stat* _st) { int32 count = directoryPaths.CountStrings(); for (int32 i = 0; i < count; i++) { BPath path; if (path.SetTo(directoryPaths.StringAt(i), name) != B_OK) continue; struct stat st; if (lstat(path.Path(), &st) == 0) { if (_ref != NULL) { if (get_ref_for_path(path.Path(), _ref) != B_OK) return false; } if (_st != NULL) *_st = st; return true; } } return false; } VirtualDirectoryManager::Info* VirtualDirectoryManager::_InfoForNodeRef(const node_ref& nodeRef) const { NodeRefInfoMap::const_iterator it = fInfos.find(nodeRef); return it != fInfos.end() ? it->second : NULL; } bool VirtualDirectoryManager::_AddInfo(Info* info) { try { fInfos[info->DefinitionFileNodeRef()] = info; return true; } catch (...) { return false; } } void VirtualDirectoryManager::_RemoveInfo(Info* info) { NodeRefInfoMap::iterator it = fInfos.find(info->DefinitionFileNodeRef()); if (it != fInfos.end()) fInfos.erase(it); } void VirtualDirectoryManager::_UpdateTree(RootInfo* root) { bool changed = false; status_t result = root->ReadDefinition(&changed); if (result != B_OK) { DirectoryRemoved(root->Info()->DefinitionFileNodeRef()); return; } if (!changed) return; _UpdateTree(root->Info()); } void VirtualDirectoryManager::_UpdateTree(Info* info) { const BStringList& directoryPaths = info->Root()->DirectoryPaths(); int32 childCount = info->Children().CountItems(); for (int32 i = childCount -1; i >= 0; i--) { Info* childInfo = info->Children().ItemAt(i); struct stat st; if (GetEntry(directoryPaths, childInfo->Path(), NULL, &st) && S_ISDIR(st.st_mode)) { _UpdateTree(childInfo); } else { _RemoveDirectory(childInfo); info->DeleteChildAt(i); } } } void VirtualDirectoryManager::_RemoveDirectory(Info* info) { // recursively remove the subdirectories for (int32 i = 0; Info* child = info->Children().ItemAt(i); i++) _RemoveDirectory(child); // remove the directory for the child definition file if (!info->Id().IsEmpty()) { BPath path(kTemporaryDefinitionFileBaseDirectoryPath, info->Id()); if (path.InitCheck() == B_OK) rmdir(path.Path()); } // unless this is the root directory, remove the definition file if (info != info->Root()->Info()) BEntry(&info->DefinitionFileEntryRef()).Remove(); _RemoveInfo(info); } status_t VirtualDirectoryManager::_ResolveUnknownDefinitionFile( const node_ref& definitionFileNodeRef, const entry_ref& definitionFileEntryRef, Info*& _info) { // This is either a root definition file or a subdir definition file // created by another application. We'll just try to read the info from the // file that a subdir definition file would contain. If that fails, we // assume a root definition file. entry_ref entryRef; BString subDirPath; if (_ReadSubDirectoryDefinitionFileInfo(definitionFileEntryRef, entryRef, subDirPath) != B_OK) { return _CreateRootInfo(definitionFileNodeRef, definitionFileEntryRef, _info); } if (subDirPath.IsEmpty()) return B_BAD_VALUE; // get the root definition file node ref node_ref nodeRef; status_t error = BEntry(&entryRef).GetNodeRef(&nodeRef); if (error != B_OK) return error; // resolve/create the root info Info* info = _InfoForNodeRef(nodeRef); if (info == NULL) { error = _CreateRootInfo(nodeRef, entryRef, info); if (error != B_OK) return error; } else if (info->Root()->Info() != info) return B_BAD_VALUE; const BStringList& rootDirectoryPaths = info->Root()->DirectoryPaths(); // now we can traverse the subdir path and resolve all infos along the way int32 nextComponentOffset = 0; while (nextComponentOffset < subDirPath.Length()) { int32 componentEnd = subDirPath.FindFirst('/', nextComponentOffset); if (componentEnd >= 0) { // skip duplicate '/'s if (componentEnd == nextComponentOffset + 1) { nextComponentOffset = componentEnd; continue; } nextComponentOffset = componentEnd + 1; } else { componentEnd = subDirPath.Length(); nextComponentOffset = componentEnd; } BString entryPath(subDirPath, componentEnd); if (entryPath.IsEmpty()) return B_NO_MEMORY; struct stat st; if (!GetEntry(rootDirectoryPaths, entryPath, &entryRef, &st)) return B_ENTRY_NOT_FOUND; if (!S_ISDIR(st.st_mode)) return B_BAD_VALUE; error = TranslateDirectoryEntry(info->DefinitionFileNodeRef(), entryRef, nodeRef); if (error != B_OK) return error; info = _InfoForNodeRef(nodeRef); } _info = info; return B_OK; } status_t VirtualDirectoryManager::_CreateRootInfo(const node_ref& definitionFileNodeRef, const entry_ref& definitionFileEntryRef, Info*& _info) { RootInfo* root = new(std::nothrow) RootInfo(definitionFileNodeRef, definitionFileEntryRef); if (root == NULL || root->InitCheck() != B_OK) { delete root; return B_NO_MEMORY; } ObjectDeleter rootDeleter(root); status_t error = root->ReadDefinition(); if (error != B_OK) return error; if (!_AddInfo(root->Info())) return B_NO_MEMORY; rootDeleter.Detach(); _info = root->Info(); return B_OK; } status_t VirtualDirectoryManager::_ReadSubDirectoryDefinitionFileInfo( const entry_ref& entryRef, entry_ref& _rootDefinitionFileEntryRef, BString& _subDirPath) { BDriverSettings driverSettings; status_t error = driverSettings.Load(entryRef); if (error != B_OK) return error; const char* subDirPath = driverSettings.GetParameterValue("subdir"); if (subDirPath == NULL || subDirPath[0] == '\0') return B_BAD_DATA; BDriverParameter rootParameter; if (!driverSettings.FindParameter("root", &rootParameter)) return B_BAD_DATA; const char* name = rootParameter.GetParameterValue("name"); dev_t device = rootParameter.GetInt32ParameterValue("device", -1, -1); ino_t directory = rootParameter.GetInt64ParameterValue("directory"); if (name == NULL || name[0] == '\0' || device < 0) return B_BAD_DATA; _rootDefinitionFileEntryRef = entry_ref(device, directory, name); _subDirPath = subDirPath; return !_subDirPath.IsEmpty() && _rootDefinitionFileEntryRef.name != NULL ? B_OK : B_NO_MEMORY; } } // namespace BPrivate