haiku/src/kits/tracker/VirtualDirectoryManager.cpp
John Scipione b8bcb08800 Tracker: Fix integer overflow
CID1108329
2014-07-18 17:58:12 -04:00

827 lines
19 KiB
C++

/*
* 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 <errno.h>
#include <new>
#include <Directory.h>
#include <File.h>
#include <StringList.h>
#include <AutoDeleter.h>
#include <AutoLocker.h>
#include <DriverSettings.h>
#include <NotOwningEntryRef.h>
#include <Uuid.h>
#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<Info> 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<char> 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<BString, VirtualDirectoryManager::Info*> 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<RootInfo> 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