mirror of
https://review.haiku-os.org/buildtools
synced 2024-11-23 07:18:49 +01:00
f4194f2ee8
* A Node can no longer have a referring "." or ".." Entry (except the root directory), not even temporarily. This rules out cycles when resolving paths. * Made the code more robust against missed node monitoring messages. If an entry is encountered that shouldn't be there, it is removed. As implemented before, a Node could end up with a NULL referring Entry, leading to a crash when an Entry that references the Node was moved. Missing node monitoring messages is actually virtually impossible, since a dedicated looper does nothing else but pushing those into a separate queue, but nevertheless Stippi seems to have managed the trick. :-) git-svn-id: file:///srv/svn/repos/haiku/buildtools/trunk@21307 a95241bf-73f2-0310-859d-f6bbb57e9c96
1464 lines
29 KiB
C++
1464 lines
29 KiB
C++
// StatCacheServer.cpp
|
|
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
#include <Application.h>
|
|
#include <Autolock.h>
|
|
#include <Message.h>
|
|
#include <NodeMonitor.h>
|
|
#include <Path.h>
|
|
|
|
#include "StatCacheServer.h"
|
|
#include "StatCacheServerImpl.h"
|
|
|
|
//#define DBG(x) { x; }
|
|
#define DBG(x)
|
|
#define OUT(format...) {printf(format); fflush(stdout);}
|
|
|
|
static const int32 kMaxSymlinks = 32;
|
|
|
|
// node monitor constants
|
|
static const int32 kDefaultNodeMonitorLimit = 4096;
|
|
static const int32 kNodeMonitorLimitIncrement = 512;
|
|
|
|
// private BeOS syscall to set the node monitor slot limits
|
|
extern "C" int _kset_mon_limit_(int num);
|
|
|
|
static inline bool
|
|
is_dot_or_dotdot(const char* name)
|
|
{
|
|
if (name && name[0] == '.')
|
|
return (name[1] == '\0' || name[1] == '.' && name[2] == '\0');
|
|
return false;
|
|
}
|
|
|
|
// get_dirent_size
|
|
static inline
|
|
int32
|
|
get_dirent_size(const char *name)
|
|
{
|
|
dirent *dummy = NULL;
|
|
int32 entrySize = (dummy->d_name + strlen(name) + 1) - (char*)dummy;
|
|
return (entrySize + 3) & ~0x3;
|
|
}
|
|
|
|
// node_ref_hash
|
|
static inline
|
|
uint32
|
|
node_ref_hash(dev_t device, ino_t node)
|
|
{
|
|
uint32 hash = device;
|
|
hash = hash * 17 + (uint32)node;
|
|
hash = hash * 17 + (uint32)(node >> 32);
|
|
return hash;
|
|
}
|
|
|
|
// string_hash
|
|
//
|
|
// from the Dragon Book: a slightly modified hashpjw()
|
|
static inline
|
|
uint32
|
|
string_hash(const char *name)
|
|
{
|
|
uint32 h = 0;
|
|
if (name) {
|
|
for (; *name; name++) {
|
|
uint32 g = h & 0xf0000000;
|
|
if (g)
|
|
h ^= g >> 24;
|
|
h = (h << 4) + *name;
|
|
}
|
|
}
|
|
return h;
|
|
}
|
|
|
|
// NodeRefHash
|
|
size_t
|
|
NodeRefHash::operator()(const node_ref &nodeRef) const
|
|
{
|
|
return node_ref_hash(nodeRef.device, nodeRef.node);
|
|
}
|
|
|
|
// EntryRefHash
|
|
size_t
|
|
EntryRefHash::operator()(const entry_ref &entryRef) const
|
|
{
|
|
uint32 hash = node_ref_hash(entryRef.device, entryRef.directory);
|
|
hash = hash * 17 + string_hash(entryRef.name);
|
|
return hash;
|
|
}
|
|
|
|
|
|
// #pragma mark -
|
|
|
|
// constructor
|
|
Entry::Entry()
|
|
: Referencable(),
|
|
fParent(NULL),
|
|
fName(),
|
|
fNode(NULL),
|
|
fPrevious(NULL),
|
|
fNext(NULL)
|
|
{
|
|
}
|
|
|
|
// destructor
|
|
Entry::~Entry()
|
|
{
|
|
SetNode(NULL);
|
|
}
|
|
|
|
// SetTo
|
|
status_t
|
|
Entry::SetTo(Directory *parent, const char *name)
|
|
{
|
|
fParent = parent;
|
|
fName = name;
|
|
return B_OK;
|
|
}
|
|
|
|
// GetParent
|
|
Directory *
|
|
Entry::GetParent() const
|
|
{
|
|
return fParent;
|
|
}
|
|
|
|
// GetName
|
|
const char *
|
|
Entry::GetName() const
|
|
{
|
|
return fName.c_str();
|
|
}
|
|
|
|
// SetNode
|
|
void
|
|
Entry::SetNode(Node *node)
|
|
{
|
|
if (fNode != node) {
|
|
if (fNode)
|
|
fNode->RemoveReference();
|
|
fNode = node;
|
|
if (fNode) {
|
|
fNode->AddReference();
|
|
if (!fNode->GetEntry() && !is_dot_or_dotdot(fName.c_str()))
|
|
fNode->SetEntry(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
// GetNode
|
|
Node *
|
|
Entry::GetNode() const
|
|
{
|
|
return fNode;
|
|
}
|
|
|
|
// SetPrevious
|
|
void
|
|
Entry::SetPrevious(Entry *entry)
|
|
{
|
|
fPrevious = entry;
|
|
}
|
|
|
|
// GetPrevious
|
|
Entry *
|
|
Entry::GetPrevious() const
|
|
{
|
|
return fPrevious;
|
|
}
|
|
|
|
// SetNext
|
|
void
|
|
Entry::SetNext(Entry *entry)
|
|
{
|
|
fNext = entry;
|
|
}
|
|
|
|
// GetNext
|
|
Entry *
|
|
Entry::GetNext() const
|
|
{
|
|
return fNext;
|
|
}
|
|
|
|
// GetEntryRef
|
|
entry_ref
|
|
Entry::GetEntryRef() const
|
|
{
|
|
node_ref dirRef(fParent->GetNodeRef());
|
|
return entry_ref(dirRef.device, dirRef.node, fName.c_str());
|
|
}
|
|
|
|
// GetPath
|
|
status_t
|
|
Entry::GetPath(string& path)
|
|
{
|
|
if (!fParent)
|
|
return B_ERROR;
|
|
|
|
// get directory path
|
|
status_t error = fParent->GetPath(path);
|
|
if (error != B_OK)
|
|
return error;
|
|
|
|
// append the entry name
|
|
if (path[path.length() - 1] != '/')
|
|
path += '/';
|
|
path += fName;
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
// Unreferenced
|
|
void
|
|
Entry::Unreferenced()
|
|
{
|
|
NodeManager::GetDefault()->EntryUnreferenced(this);
|
|
}
|
|
|
|
|
|
// #pragma mark -
|
|
|
|
// constructor
|
|
Node::Node(const struct stat &st)
|
|
: Referencable(),
|
|
fEntry(NULL),
|
|
fStat(st),
|
|
fStatValid(false)
|
|
{
|
|
}
|
|
|
|
// destructor
|
|
Node::~Node()
|
|
{
|
|
// stop watching the node
|
|
NodeManager::GetDefault()->StopWatching(this);
|
|
|
|
SetEntry(NULL);
|
|
}
|
|
|
|
// SetTo
|
|
status_t
|
|
Node::SetTo(Entry *entry)
|
|
{
|
|
// start watching the node
|
|
status_t error = NodeManager::GetDefault()->StartWatching(this);
|
|
if (error != B_OK)
|
|
return error;
|
|
|
|
SetEntry(entry);
|
|
|
|
// update the stat
|
|
return UpdateStat();
|
|
}
|
|
|
|
// GetPath
|
|
status_t
|
|
Node::GetPath(string& path)
|
|
{
|
|
if (this == NodeManager::GetDefault()->GetRootDirectory()) {
|
|
path = "/";
|
|
return B_OK;
|
|
}
|
|
|
|
if (!fEntry)
|
|
return B_ERROR;
|
|
return fEntry->GetPath(path);
|
|
}
|
|
|
|
// GetStat
|
|
const struct stat &
|
|
Node::GetStat() const
|
|
{
|
|
return fStat;
|
|
}
|
|
|
|
// GetStat
|
|
status_t
|
|
Node::GetStat(struct stat *st)
|
|
{
|
|
if (!fStatValid) {
|
|
status_t error = UpdateStat();
|
|
if (error != B_OK)
|
|
return error;
|
|
}
|
|
*st = fStat;
|
|
return B_OK;
|
|
}
|
|
|
|
// UpdateStat
|
|
status_t
|
|
Node::UpdateStat()
|
|
{
|
|
// get path
|
|
string path;
|
|
status_t error = GetPath(path);
|
|
if (error != B_OK)
|
|
return error;
|
|
|
|
DBG(OUT("disk access: lstat(): %s\n", path.c_str()));
|
|
|
|
// read stat
|
|
if (lstat(path.c_str(), &fStat) < 0)
|
|
return errno;
|
|
fStatValid = true;
|
|
return B_OK;
|
|
}
|
|
|
|
// MarkStatInvalid
|
|
void
|
|
Node::MarkStatInvalid()
|
|
{
|
|
fStatValid = false;
|
|
}
|
|
|
|
// SetEntry
|
|
void
|
|
Node::SetEntry(Entry *entry)
|
|
{
|
|
if (entry != fEntry) {
|
|
if (fEntry)
|
|
fEntry->RemoveReference();
|
|
fEntry = entry;
|
|
if (fEntry)
|
|
fEntry->AddReference();
|
|
}
|
|
}
|
|
|
|
// GetEntry
|
|
Entry *
|
|
Node::GetEntry() const
|
|
{
|
|
return fEntry;
|
|
}
|
|
|
|
// GetNodeRef
|
|
node_ref
|
|
Node::GetNodeRef() const
|
|
{
|
|
node_ref nodeRef;
|
|
nodeRef.device = fStat.st_dev;
|
|
nodeRef.node = fStat.st_ino;
|
|
return nodeRef;
|
|
}
|
|
|
|
// Unreferenced
|
|
void
|
|
Node::Unreferenced()
|
|
{
|
|
NodeManager::GetDefault()->NodeUnreferenced(this);
|
|
}
|
|
|
|
|
|
// #pragma mark -
|
|
|
|
// constructor
|
|
Directory::Directory(const struct stat &st)
|
|
: Node(st),
|
|
fFirstEntry(NULL),
|
|
fLastEntry(NULL),
|
|
fIsComplete(false)
|
|
{
|
|
}
|
|
|
|
// destructor
|
|
Directory::~Directory()
|
|
{
|
|
while (Entry *entry = GetFirstEntry())
|
|
RemoveEntry(entry);
|
|
}
|
|
|
|
// SetTo
|
|
status_t
|
|
Directory::SetTo(Entry *entry)
|
|
{
|
|
return Node::SetTo(entry);
|
|
}
|
|
|
|
// FindEntry
|
|
status_t
|
|
Directory::FindEntry(const char *name, Entry **entry)
|
|
{
|
|
entry_ref ref(fStat.st_dev, fStat.st_ino, name);
|
|
if (!fIsComplete)
|
|
return NodeManager::GetDefault()->CreateEntry(ref, NULL, entry);
|
|
*entry = NodeManager::GetDefault()->GetEntry(ref);
|
|
return (*entry ? B_OK : B_ENTRY_NOT_FOUND);
|
|
}
|
|
|
|
// GetFirstEntry
|
|
Entry *
|
|
Directory::GetFirstEntry() const
|
|
{
|
|
return fFirstEntry;
|
|
}
|
|
|
|
// GetNextEntry
|
|
Entry *
|
|
Directory::GetNextEntry(Entry *entry) const
|
|
{
|
|
return (entry ? entry->GetNext() : NULL);
|
|
}
|
|
|
|
// ReadAllEntries
|
|
status_t
|
|
Directory::ReadAllEntries()
|
|
{
|
|
if (fIsComplete)
|
|
return B_OK;
|
|
|
|
// get the path
|
|
string path;
|
|
status_t error = GetPath(path);
|
|
if (error != B_OK)
|
|
return error;
|
|
|
|
DBG(OUT("disk access: opendir(): %s\n", path.c_str()));
|
|
|
|
// open the directory
|
|
DIR *dir = opendir(path.c_str());
|
|
if (!dir)
|
|
return errno;
|
|
|
|
// read the directory
|
|
while (dirent *entry = readdir(dir)) {
|
|
Entry *dummy;
|
|
FindEntry(entry->d_name, &dummy);
|
|
}
|
|
closedir(dir);
|
|
|
|
fIsComplete = true;
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
// IsComplete
|
|
bool
|
|
Directory::IsComplete() const
|
|
{
|
|
return fIsComplete;
|
|
}
|
|
|
|
// AddEntry
|
|
void
|
|
Directory::AddEntry(Entry *entry)
|
|
{
|
|
if (fLastEntry) {
|
|
entry->SetPrevious(fLastEntry);
|
|
entry->SetNext(NULL);
|
|
fLastEntry->SetNext(entry);
|
|
fLastEntry = entry;
|
|
} else {
|
|
entry->SetPrevious(NULL);
|
|
entry->SetNext(NULL);
|
|
fFirstEntry = fLastEntry = entry;
|
|
}
|
|
entry->AddReference();
|
|
|
|
// the reference the "." entry has, shall be ignored
|
|
if (strcmp(entry->GetName(), ".") == 0)
|
|
fReferenceBaseCount++;
|
|
}
|
|
|
|
// RemoveEntry
|
|
void
|
|
Directory::RemoveEntry(Entry *entry)
|
|
{
|
|
if (entry->GetParent() != this)
|
|
return;
|
|
|
|
// the reference the "." entry has, shall be ignored
|
|
if (strcmp(entry->GetName(), ".") == 0)
|
|
fReferenceBaseCount--;
|
|
|
|
if (entry->GetPrevious())
|
|
entry->GetPrevious()->SetNext(entry->GetNext());
|
|
else
|
|
fFirstEntry = entry->GetNext();
|
|
if (entry->GetNext())
|
|
entry->GetNext()->SetPrevious(entry->GetPrevious());
|
|
else
|
|
fLastEntry = entry->GetPrevious();
|
|
entry->SetPrevious(NULL);
|
|
entry->SetNext(NULL);
|
|
entry->RemoveReference();
|
|
}
|
|
|
|
|
|
// #pragma mark -
|
|
|
|
// constructor
|
|
SymLink::SymLink(const struct stat &st)
|
|
: Node(st),
|
|
fTarget()
|
|
{
|
|
}
|
|
|
|
// destructor
|
|
SymLink::~SymLink()
|
|
{
|
|
}
|
|
|
|
// SetTo
|
|
status_t
|
|
SymLink::SetTo(Entry *entry)
|
|
{
|
|
// node initialization
|
|
status_t error = Node::SetTo(entry);
|
|
if (error != B_OK)
|
|
return error;
|
|
|
|
// get the entry path
|
|
string path;
|
|
error = entry->GetPath(path);
|
|
if (error != B_OK)
|
|
return error;
|
|
|
|
// read the link
|
|
char target[B_PATH_NAME_LENGTH + 1];
|
|
ssize_t bytesRead = readlink(path.c_str(), target, B_PATH_NAME_LENGTH);
|
|
if (bytesRead < 0)
|
|
return errno;
|
|
target[bytesRead] = '\0';
|
|
fTarget = target;
|
|
return B_OK;
|
|
}
|
|
|
|
// GetTarget
|
|
const char *
|
|
SymLink::GetTarget() const
|
|
{
|
|
return fTarget.c_str();
|
|
}
|
|
|
|
|
|
// #pragma mark -
|
|
|
|
// destructor
|
|
NodeMonitor::NodeMonitor()
|
|
// higher priority and larger queue, since we must not miss update events
|
|
: BLooper("node monitor", B_DISPLAY_PRIORITY, 1000),
|
|
fCurrentNodeMonitorLimit(kDefaultNodeMonitorLimit),
|
|
fMessageCountSem(-1)
|
|
{
|
|
}
|
|
|
|
// destructor
|
|
NodeMonitor::~NodeMonitor()
|
|
{
|
|
delete_sem(fMessageCountSem);
|
|
}
|
|
|
|
// Init
|
|
status_t
|
|
NodeMonitor::Init()
|
|
{
|
|
fMessageCountSem = create_sem(0, "nm message count");
|
|
if (fMessageCountSem < 0)
|
|
return fMessageCountSem;
|
|
return B_OK;
|
|
}
|
|
|
|
// MessageReceived
|
|
void
|
|
NodeMonitor::MessageReceived(BMessage *message)
|
|
{
|
|
switch (message->what) {
|
|
case B_NODE_MONITOR:
|
|
DetachCurrentMessage();
|
|
fMessageQueue.AddMessage(message);
|
|
release_sem(fMessageCountSem);
|
|
break;
|
|
default:
|
|
BLooper::MessageReceived(message);
|
|
}
|
|
}
|
|
|
|
// StartWatching
|
|
status_t
|
|
NodeMonitor::StartWatching(Node *node)
|
|
{
|
|
if (!node)
|
|
return B_BAD_VALUE;
|
|
uint32 flags = B_WATCH_STAT;
|
|
if (S_ISDIR(node->GetStat().st_mode))
|
|
flags |= B_WATCH_DIRECTORY;
|
|
node_ref ref = node->GetNodeRef();
|
|
status_t error = watch_node(&ref, flags, this);
|
|
// If starting to watch the node fail, we allocate more node
|
|
// monitoring slots and try again.
|
|
if (error != B_OK) {
|
|
fCurrentNodeMonitorLimit += kNodeMonitorLimitIncrement;
|
|
error = _kset_mon_limit_(fCurrentNodeMonitorLimit);
|
|
if (error == B_OK)
|
|
error = watch_node(&ref, flags, this);
|
|
}
|
|
return error;
|
|
}
|
|
|
|
// StopWatching
|
|
status_t
|
|
NodeMonitor::StopWatching(Node *node)
|
|
{
|
|
if (!node)
|
|
return B_BAD_VALUE;
|
|
node_ref ref = node->GetNodeRef();
|
|
return watch_node(&ref, B_STOP_WATCHING, this);
|
|
}
|
|
|
|
// GetNextMonitoringMessage
|
|
status_t
|
|
NodeMonitor::GetNextMonitoringMessage(BMessage **_message)
|
|
{
|
|
// acquire the semaphore
|
|
status_t error = B_OK;
|
|
do {
|
|
error = acquire_sem(fMessageCountSem);
|
|
} while (error == B_INTERRUPTED);
|
|
if (error != B_OK)
|
|
return error;
|
|
|
|
// get the message
|
|
BMessage *message = fMessageQueue.NextMessage();
|
|
if (!message)
|
|
return B_ERROR;
|
|
*_message = message;
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
// #pragma mark -
|
|
|
|
// constructor
|
|
PathResolver::PathResolver()
|
|
: fSymLinkCounter(0)
|
|
{
|
|
}
|
|
|
|
// FindEntry
|
|
status_t
|
|
PathResolver::FindEntry(const char *path, bool traverse, Entry **_entry)
|
|
{
|
|
return FindEntry(NULL, path, traverse, _entry);
|
|
}
|
|
|
|
// FindEntry
|
|
status_t
|
|
PathResolver::FindEntry(Entry *entry, const char *path, bool traverse,
|
|
Entry **_entry)
|
|
{
|
|
// we accept only absolute paths, if no entry was given
|
|
if (!path || (!entry && *path != '/'))
|
|
return B_BAD_VALUE;
|
|
|
|
// get the root directory for absolute paths
|
|
if (*path == '/') {
|
|
entry = NodeManager::GetDefault()->GetRootDirectory()->GetEntry();
|
|
// skip '/'
|
|
while (*path == '/')
|
|
path++;
|
|
}
|
|
|
|
while (*path != '\0') {
|
|
// get path component
|
|
int componentLen;
|
|
if (char *nextSlash = strchr(path, '/'))
|
|
componentLen = nextSlash - path;
|
|
else
|
|
componentLen = strlen(path);
|
|
string component(path, componentLen);
|
|
path += componentLen;
|
|
|
|
// resolve symlink
|
|
Node *node = entry->GetNode();
|
|
if (SymLink *symlink = dynamic_cast<SymLink*>(node)) {
|
|
status_t error = ResolveSymlink(symlink, &node);
|
|
if (error != B_OK)
|
|
return error;
|
|
}
|
|
|
|
// find the entry
|
|
if (Directory *dir = dynamic_cast<Directory*>(node)) {
|
|
status_t error = dir->FindEntry(component.c_str(), &entry);
|
|
if (error != B_OK)
|
|
return error;
|
|
} else
|
|
return B_ENTRY_NOT_FOUND;
|
|
|
|
// skip '/'
|
|
while (*path == '/')
|
|
path++;
|
|
}
|
|
|
|
// traverse leaf symlink, if requested
|
|
if (traverse) {
|
|
status_t error = ResolveSymlink(entry, &entry);
|
|
if (error != B_OK)
|
|
return error;
|
|
}
|
|
|
|
*_entry = entry;
|
|
return B_OK;
|
|
}
|
|
|
|
// FindNode
|
|
status_t
|
|
PathResolver::FindNode(const char *path, bool traverse, Node **node)
|
|
{
|
|
Entry *entry;
|
|
status_t error = FindEntry(path, traverse, &entry);
|
|
if (error != B_OK)
|
|
return error;
|
|
if (!entry->GetNode())
|
|
return B_ENTRY_NOT_FOUND;
|
|
*node = entry->GetNode();
|
|
return B_OK;
|
|
}
|
|
|
|
// ResolveSymlink
|
|
status_t
|
|
PathResolver::ResolveSymlink(Node *node, Node **_node)
|
|
{
|
|
Entry *entry;
|
|
status_t error = ResolveSymlink(node, &entry);
|
|
if (error != B_OK)
|
|
return error;
|
|
if (!entry->GetNode())
|
|
return B_ENTRY_NOT_FOUND;
|
|
*_node = entry->GetNode();
|
|
return B_OK;
|
|
}
|
|
|
|
// ResolveSymlink
|
|
status_t
|
|
PathResolver::ResolveSymlink(Node *node, Entry **entry)
|
|
{
|
|
return ResolveSymlink(node->GetEntry(), entry);
|
|
}
|
|
|
|
// ResolveSymlink
|
|
status_t
|
|
PathResolver::ResolveSymlink(Entry *entry, Entry **_entry)
|
|
{
|
|
if (!entry->GetNode())
|
|
return B_ENTRY_NOT_FOUND;
|
|
|
|
SymLink *symlink = dynamic_cast<SymLink*>(entry->GetNode());
|
|
if (!symlink) {
|
|
*_entry = entry;
|
|
return B_OK;
|
|
}
|
|
|
|
if (fSymLinkCounter > kMaxSymlinks)
|
|
return B_LINK_LIMIT;
|
|
|
|
const char *target = symlink->GetTarget();
|
|
if (!target || !symlink->GetEntry() || !symlink->GetEntry()->GetParent()
|
|
|| !symlink->GetEntry()->GetParent()->GetEntry())
|
|
return B_ENTRY_NOT_FOUND;
|
|
|
|
fSymLinkCounter++;
|
|
status_t error = FindEntry(symlink->GetEntry()->GetParent()->GetEntry(),
|
|
target, true, _entry);
|
|
fSymLinkCounter--;
|
|
return error;
|
|
}
|
|
|
|
|
|
// #pragma mark -
|
|
|
|
// constructor
|
|
NodeManager::NodeManager()
|
|
: BLocker("node manager"),
|
|
fRootDirectory(NULL),
|
|
fNodeMonitor(NULL),
|
|
fNodeMonitoringProcessor(-1)
|
|
{
|
|
}
|
|
|
|
// destructor
|
|
NodeManager::~NodeManager()
|
|
{
|
|
if (fNodeMonitor) {
|
|
fNodeMonitor->Lock();
|
|
fNodeMonitor->Quit();
|
|
}
|
|
|
|
if (fNodeMonitoringProcessor >= 0) {
|
|
status_t status;
|
|
wait_for_thread(fNodeMonitoringProcessor, &status);
|
|
}
|
|
}
|
|
|
|
// GetDefault
|
|
NodeManager *
|
|
NodeManager::GetDefault()
|
|
{
|
|
return &sManager;
|
|
}
|
|
|
|
// Init
|
|
status_t
|
|
NodeManager::Init()
|
|
{
|
|
// create the node monitor
|
|
fNodeMonitor = new NodeMonitor;
|
|
status_t error = fNodeMonitor->Init();
|
|
if (error != B_OK)
|
|
return error;
|
|
fNodeMonitor->Run();
|
|
|
|
// spawn the node monitoring processor
|
|
fNodeMonitoringProcessor = spawn_thread(&_NodeMonitoringProcessorEntry,
|
|
"node monitoring processor", B_NORMAL_PRIORITY, this);
|
|
if (fNodeMonitoringProcessor < 0)
|
|
return fNodeMonitoringProcessor;
|
|
resume_thread(fNodeMonitoringProcessor);
|
|
|
|
// get root dir stat
|
|
struct stat st;
|
|
if (lstat("/", &st) < 0)
|
|
return errno;
|
|
|
|
// create the root node
|
|
node_ref nodeRef;
|
|
nodeRef.device = st.st_dev;
|
|
nodeRef.node = st.st_ino;
|
|
fRootDirectory = new Directory(st);
|
|
fNodes[nodeRef] = fRootDirectory;
|
|
|
|
// create an entry pointing to the root node
|
|
entry_ref entryRef(st.st_dev, st.st_ino, ".");
|
|
Entry *entry = new Entry;
|
|
error = entry->SetTo(fRootDirectory, ".");
|
|
if (error != B_OK)
|
|
return error;
|
|
entry->SetNode(fRootDirectory);
|
|
fEntries[entryRef] = entry;
|
|
|
|
// now we can initialize the root directory
|
|
error = fRootDirectory->SetTo(entry);
|
|
|
|
return error;
|
|
}
|
|
|
|
// GetRootDirectory
|
|
Directory *
|
|
NodeManager::GetRootDirectory() const
|
|
{
|
|
return fRootDirectory;
|
|
}
|
|
|
|
// GetNode
|
|
Node *
|
|
NodeManager::GetNode(const node_ref &nodeRef)
|
|
{
|
|
NodeMap::iterator it = fNodes.find(nodeRef);
|
|
if (it == fNodes.end())
|
|
return NULL;
|
|
return it->second;
|
|
}
|
|
|
|
// GetEntry
|
|
Entry *
|
|
NodeManager::GetEntry(const entry_ref &entryRef)
|
|
{
|
|
EntryMap::iterator it = fEntries.find(entryRef);
|
|
if (it == fEntries.end())
|
|
return NULL;
|
|
return it->second;
|
|
}
|
|
|
|
// CreateEntry
|
|
status_t
|
|
NodeManager::CreateEntry(const entry_ref &entryRef, const node_ref *nodeRef,
|
|
Entry **_entry)
|
|
{
|
|
Entry *entry = GetEntry(entryRef);
|
|
|
|
// If the entry is known, but its node is not the one it should be, we
|
|
// remove the entry.
|
|
if (nodeRef && entry && entry->GetNode()
|
|
&& entry->GetNode()->GetNodeRef() != *nodeRef) {
|
|
RemoveEntry(entry);
|
|
entry = NULL;
|
|
}
|
|
|
|
if (!entry) {
|
|
// entry does not yet exist -- create it
|
|
|
|
// get the parent directory
|
|
node_ref parentDirRef;
|
|
parentDirRef.device = entryRef.device;
|
|
parentDirRef.node = entryRef.directory;
|
|
Directory *dir;
|
|
status_t error = CreateDirectory(parentDirRef, &dir);
|
|
if (error != B_OK)
|
|
return error;
|
|
|
|
// if the directory hasn't created it, we need to do that now
|
|
entry = GetEntry(entryRef);
|
|
if (!entry) {
|
|
entry = new Entry;
|
|
error = entry->SetTo(dir, entryRef.name);
|
|
if (error != B_OK) {
|
|
delete entry;
|
|
return error;
|
|
}
|
|
|
|
// get the entry's node
|
|
Node *node;
|
|
error = NodeManager::GetDefault()->_CreateNode(entry, &node);
|
|
if (error != B_OK) {
|
|
delete entry;
|
|
return error;
|
|
}
|
|
|
|
// If the node already existed, but points to another entry, we
|
|
// remove that entry now. We probably missed the respective
|
|
// B_ENTRY_REMOVED notification.
|
|
if (node->GetEntry() && node->GetEntry() != entry
|
|
&& !is_dot_or_dotdot(entry->GetName())) {
|
|
RemoveEntry(node->GetEntry());
|
|
|
|
// reinit node watching
|
|
StopWatching(node);
|
|
StartWatching(node);
|
|
}
|
|
|
|
entry->SetNode(node);
|
|
node->RemoveReference();
|
|
// reference acquired by _CreateNode()
|
|
|
|
// initialization successful: add the entry to the dir and to the
|
|
// entry map
|
|
dir->AddEntry(entry);
|
|
fEntries[entryRef] = entry;
|
|
entry->RemoveReference();
|
|
DBG(
|
|
string path;
|
|
entry->GetPath(path);
|
|
OUT("entry created: `%s'\n", path.c_str());
|
|
)
|
|
}
|
|
}
|
|
|
|
*_entry = entry;
|
|
return B_OK;
|
|
}
|
|
|
|
// CreateDirectory
|
|
status_t
|
|
NodeManager::CreateDirectory(const node_ref &nodeRef, Directory **_dir)
|
|
{
|
|
Node *node = GetNode(nodeRef);
|
|
if (!node) {
|
|
// node not yet known -- load the directory
|
|
// get the full path
|
|
entry_ref entryRef(nodeRef.device, nodeRef.node, ".");
|
|
BPath path;
|
|
status_t error = path.SetTo(&entryRef);
|
|
if (error != B_OK)
|
|
return error;
|
|
|
|
// find the node
|
|
error = PathResolver().FindNode(path.Path(), false, &node);
|
|
if (error != B_OK)
|
|
return error;
|
|
}
|
|
|
|
// node found -- check, if it is a directory
|
|
Directory *dir = dynamic_cast<Directory*>(node);
|
|
if (!dir)
|
|
return B_NOT_A_DIRECTORY;
|
|
|
|
*_dir = dir;
|
|
return B_OK;
|
|
}
|
|
|
|
// RemoveEntry
|
|
void
|
|
NodeManager::RemoveEntry(Entry *entry)
|
|
{
|
|
if (!entry)
|
|
return;
|
|
|
|
DBG(
|
|
string path;
|
|
entry->GetPath(path);
|
|
OUT("entry removed: `%s'\n", path.c_str());
|
|
)
|
|
|
|
// get a temporary reference, so that the entry will not be deleted when
|
|
// we unset the node
|
|
entry->AddReference();
|
|
|
|
// remove from directory and node
|
|
if (entry->GetParent())
|
|
entry->GetParent()->RemoveEntry(entry);
|
|
|
|
// detach from node
|
|
Node *node = entry->GetNode();
|
|
if (node) {
|
|
if (node->GetEntry() == entry)
|
|
node->SetEntry(NULL);
|
|
entry->SetNode(NULL);
|
|
}
|
|
|
|
// surrender our temporary reference: now the entry should be unreference
|
|
entry->RemoveReference();
|
|
}
|
|
|
|
// MoveEntry
|
|
void
|
|
NodeManager::MoveEntry(Entry *entry, const entry_ref &newRef,
|
|
const node_ref &nodeRef)
|
|
{
|
|
// get the target directory
|
|
node_ref newDirRef;
|
|
newDirRef.device = newRef.device;
|
|
newDirRef.node = newRef.directory;
|
|
Directory *newDir = dynamic_cast<Directory*>(GetNode(newDirRef));
|
|
if (!newDir) {
|
|
// target directory unknown -- simply remove the entry
|
|
RemoveEntry(entry);
|
|
return;
|
|
}
|
|
|
|
// If the directory, the name, or the node (missed B_ENTRY_REMOVED and
|
|
// B_ENTRY_CREATED) changed, we remove the old entry and create a new one.
|
|
Node *node = entry->GetNode();
|
|
if (newDir != entry->GetParent()
|
|
|| strcmp(newRef.name, entry->GetName()) != 0
|
|
|| (node && node->GetNodeRef() != nodeRef)) {
|
|
// get a temporary reference to the node, so it won't be unnecessarily
|
|
// deleted
|
|
if (node)
|
|
node->AddReference();
|
|
|
|
RemoveEntry(entry);
|
|
CreateEntry(newRef, &nodeRef, &entry);
|
|
|
|
if (node)
|
|
node->RemoveReference();
|
|
}
|
|
}
|
|
|
|
// EntryUnreferenced
|
|
void
|
|
NodeManager::EntryUnreferenced(Entry *entry)
|
|
{
|
|
DBG(OUT("NodeManager::EntryUnreferenced(%p): (%p, `%s')\n", entry, entry->GetParent(), entry->GetName()));
|
|
// remove entry from the map and delete it
|
|
if (fEntries.erase(entry->GetEntryRef()) > 0)
|
|
delete entry;
|
|
}
|
|
|
|
// NodeUnreferenced
|
|
void
|
|
NodeManager::NodeUnreferenced(Node *node)
|
|
{
|
|
DBG(OUT("NodeManager::NodeUnreferenced(%p): entry: %p\n", node, node->GetEntry()));
|
|
// remove node from the map and delete it
|
|
if (fNodes.erase(node->GetNodeRef()) > 0)
|
|
delete node;
|
|
}
|
|
|
|
// StartWatching
|
|
status_t
|
|
NodeManager::StartWatching(Node *node)
|
|
{
|
|
return fNodeMonitor->StartWatching(node);
|
|
}
|
|
|
|
// StopWatching
|
|
status_t
|
|
NodeManager::StopWatching(Node *node)
|
|
{
|
|
return fNodeMonitor->StopWatching(node);
|
|
}
|
|
|
|
// _NodeMonitoringProcessorEntry
|
|
int32
|
|
NodeManager::_NodeMonitoringProcessorEntry(void *data)
|
|
{
|
|
return ((NodeManager*)data)->_NodeMonitoringProcessor();
|
|
}
|
|
|
|
// _NodeMonitoringProcessor
|
|
int32
|
|
NodeManager::_NodeMonitoringProcessor()
|
|
{
|
|
BMessage *message;
|
|
while (fNodeMonitor->GetNextMonitoringMessage(&message) == B_OK) {
|
|
int32 opcode;
|
|
if (message->FindInt32("opcode", &opcode) == B_OK) {
|
|
BAutolock _(this);
|
|
switch (opcode) {
|
|
case B_ENTRY_CREATED:
|
|
_EntryCreated(message);
|
|
break;
|
|
case B_ENTRY_REMOVED:
|
|
_EntryRemoved(message);
|
|
break;
|
|
case B_ENTRY_MOVED:
|
|
_EntryMoved(message);
|
|
break;
|
|
case B_STAT_CHANGED:
|
|
_StatChanged(message);
|
|
break;
|
|
}
|
|
}
|
|
delete message;
|
|
}
|
|
}
|
|
|
|
// _CreateNode
|
|
//
|
|
// On success the caller gets a reference to the node, they are required to
|
|
// surrender, if done with the node.
|
|
status_t
|
|
NodeManager::_CreateNode(Entry *entry, Node **_node)
|
|
{
|
|
// get the path
|
|
string path;
|
|
status_t error = entry->GetPath(path);
|
|
if (error != B_OK)
|
|
return error;
|
|
|
|
DBG(OUT("disk access: lstat(): %s\n", path.c_str()));
|
|
|
|
// read the stat
|
|
struct stat st;
|
|
if (lstat(path.c_str(), &st) < 0)
|
|
return errno;
|
|
|
|
// check, if the node does already exist
|
|
node_ref nodeRef;
|
|
nodeRef.device = st.st_dev;
|
|
nodeRef.node = st.st_ino;
|
|
Node *node = GetNode(nodeRef);
|
|
|
|
if (node) {
|
|
node->AddReference();
|
|
} else {
|
|
// node does not yet exist -- create it
|
|
if (S_ISLNK(st.st_mode))
|
|
node = new SymLink(st);
|
|
else if (S_ISDIR(st.st_mode))
|
|
node = new Directory(st);
|
|
else
|
|
node = new Node(st);
|
|
|
|
error = node->SetTo(entry);
|
|
if (error != B_OK) {
|
|
delete node;
|
|
return error;
|
|
}
|
|
|
|
fNodes[nodeRef] = node;
|
|
}
|
|
|
|
*_node = node;
|
|
return B_OK;
|
|
}
|
|
|
|
// _EntryCreated
|
|
void
|
|
NodeManager::_EntryCreated(BMessage *message)
|
|
{
|
|
// get the info
|
|
node_ref dirNodeRef;
|
|
node_ref nodeRef;
|
|
const char* name;
|
|
if (message->FindInt32("device", &dirNodeRef.device) != B_OK
|
|
|| message->FindInt64("directory", &dirNodeRef.node) != B_OK
|
|
|| message->FindInt64("node", &nodeRef.node) != B_OK
|
|
|| message->FindString("name", &name) != B_OK) {
|
|
return;
|
|
}
|
|
nodeRef.device = dirNodeRef.device;
|
|
|
|
// get the directory
|
|
Node *node = NodeManager::GetDefault()->GetNode(dirNodeRef);
|
|
Directory *dir = dynamic_cast<Directory*>(node);
|
|
if (!dir)
|
|
return;
|
|
|
|
// add the entry, if the directory is complete
|
|
if (dir->IsComplete()) {
|
|
Entry *entry;
|
|
if (dir->FindEntry(name, &entry) != B_OK) {
|
|
entry_ref ref(dirNodeRef.device, dirNodeRef.node, name);
|
|
NodeManager::GetDefault()->CreateEntry(ref, &nodeRef, &entry);
|
|
}
|
|
}
|
|
}
|
|
|
|
// _EntryRemoved
|
|
void
|
|
NodeManager::_EntryRemoved(BMessage *message)
|
|
{
|
|
// get the info
|
|
node_ref nodeRef;
|
|
const char* name;
|
|
if (message->FindInt32("device", &nodeRef.device) != B_OK
|
|
// || message->FindInt64("directory", &nodeRef.node) != B_OK
|
|
|| message->FindInt64("node", &nodeRef.node) != B_OK) {
|
|
return;
|
|
}
|
|
|
|
// get the node
|
|
Node *node = NodeManager::GetDefault()->GetNode(nodeRef);
|
|
if (!node)
|
|
return;
|
|
|
|
// remove it
|
|
NodeManager::GetDefault()->RemoveEntry(node->GetEntry());
|
|
}
|
|
|
|
// _EntryMoved
|
|
void
|
|
NodeManager::_EntryMoved(BMessage *message)
|
|
{
|
|
// get the info
|
|
node_ref nodeRef;
|
|
ino_t newDirID;
|
|
const char* name;
|
|
if (message->FindInt32("device", &nodeRef.device) != B_OK
|
|
// || message->FindInt64("from directory", &fromDirectoryID) != B_OK
|
|
|| message->FindInt64("to directory", &newDirID) != B_OK
|
|
|| message->FindInt64("node", &nodeRef.node) != B_OK
|
|
|| message->FindString("name", &name) != B_OK) {
|
|
return;
|
|
}
|
|
|
|
// get the node
|
|
Node *node = NodeManager::GetDefault()->GetNode(nodeRef);
|
|
if (!node) {
|
|
// create it if not present
|
|
Entry *entry;
|
|
entry_ref newRef(nodeRef.device, newDirID, name);
|
|
NodeManager::GetDefault()->CreateEntry(newRef, &nodeRef, &entry);
|
|
return;
|
|
}
|
|
|
|
// move it
|
|
entry_ref newRef(nodeRef.device, newDirID, name);
|
|
NodeManager::GetDefault()->MoveEntry(node->GetEntry(), newRef, nodeRef);
|
|
}
|
|
|
|
// _StatChanged
|
|
void
|
|
NodeManager::_StatChanged(BMessage *message)
|
|
{
|
|
// get the node ref
|
|
node_ref nodeRef;
|
|
if (message->FindInt32("device", &nodeRef.device) != B_OK
|
|
|| message->FindInt64("node", &nodeRef.node)) {
|
|
return;
|
|
}
|
|
|
|
// get the node
|
|
Node *node = GetNode(nodeRef);
|
|
if (!node)
|
|
return;
|
|
|
|
node->MarkStatInvalid();
|
|
}
|
|
|
|
// sManager
|
|
NodeManager NodeManager::sManager;
|
|
|
|
|
|
// #pragma mark -
|
|
|
|
// read_request
|
|
static
|
|
status_t
|
|
read_request(port_id port, stat_cache_request &request)
|
|
{
|
|
status_t error = B_OK;
|
|
bool done = false;
|
|
do {
|
|
int32 code;
|
|
ssize_t bytesRead = read_port(port, &code, &request,
|
|
sizeof(stat_cache_request));
|
|
if (bytesRead < 0) {
|
|
error = bytesRead;
|
|
done = (error != B_INTERRUPTED);
|
|
} else if (bytesRead
|
|
< ((stat_cache_request*)NULL)->path + 2 - (char*)NULL) {
|
|
DBG(OUT("request too short: %ld\n", bytesRead));
|
|
error = B_ERROR;
|
|
} else {
|
|
done = true;
|
|
error = B_OK;
|
|
}
|
|
} while (!done);
|
|
return error;
|
|
}
|
|
|
|
// handle_stat_request
|
|
static
|
|
status_t
|
|
handle_stat_request(stat_cache_request &request)
|
|
{
|
|
DBG(OUT("handle_stat_request(): `%s'\n", request.path));
|
|
stat_cache_stat_reply reply;
|
|
|
|
// get the node
|
|
PathResolver resolver;
|
|
Node *node;
|
|
reply.error = resolver.FindNode(request.path, true, &node);
|
|
|
|
// get the stat
|
|
if (reply.error == B_OK)
|
|
reply.error = node->GetStat(&reply.st);
|
|
DBG(OUT(" -> `%s'\n", strerror(reply.error)));
|
|
|
|
// send the reply
|
|
return write_port(request.replyPort, 0, &reply, sizeof(reply));
|
|
}
|
|
|
|
// handle_read_dir_request
|
|
static
|
|
status_t
|
|
handle_read_dir_request(stat_cache_request &request)
|
|
{
|
|
DBG(OUT("handle_read_dir_request(): `%s'\n", request.path));
|
|
// get the directory
|
|
PathResolver resolver;
|
|
Node *node = NULL;
|
|
status_t error = resolver.FindNode(request.path, true, &node);
|
|
Directory *dir = dynamic_cast<Directory*>(node);
|
|
if (error == B_OK && !dir)
|
|
error = B_NOT_A_DIRECTORY;
|
|
|
|
// read all entries
|
|
if (error == B_OK)
|
|
error = dir->ReadAllEntries();
|
|
|
|
// compute the reply size
|
|
int32 replySize = sizeof(stat_cache_readdir_reply);
|
|
int32 entryCount = 0;
|
|
if (error == B_OK) {
|
|
for (Entry *entry = dir->GetFirstEntry();
|
|
entry;
|
|
entry = dir->GetNextEntry(entry)) {
|
|
replySize += get_dirent_size(entry->GetName());
|
|
entryCount++;
|
|
}
|
|
}
|
|
|
|
// allocate a reply
|
|
stat_cache_readdir_reply *reply
|
|
= (stat_cache_readdir_reply*)new uint8[replySize];
|
|
reply->error = error;
|
|
reply->entryCount = entryCount;
|
|
|
|
// copy the entries into the reply
|
|
if (error == B_OK) {
|
|
uint8 *buffer = reply->buffer;
|
|
for (Entry *entry = dir->GetFirstEntry();
|
|
entry;
|
|
entry = dir->GetNextEntry(entry)) {
|
|
// get the required info
|
|
int32 entrySize = get_dirent_size(entry->GetName());
|
|
node_ref parentNodeRef(entry->GetParent()->GetNodeRef());
|
|
node_ref nodeRef(entry->GetNode()->GetNodeRef());
|
|
|
|
// fill in the dirent
|
|
dirent *ent = (dirent*)buffer;
|
|
ent->d_pdev = parentNodeRef.device;
|
|
ent->d_pino = parentNodeRef.node;
|
|
ent->d_dev = nodeRef.device;
|
|
ent->d_ino = nodeRef.node;
|
|
ent->d_reclen = entrySize;
|
|
strcpy(ent->d_name, entry->GetName());
|
|
|
|
buffer += entrySize;
|
|
}
|
|
}
|
|
|
|
DBG(OUT(" -> entryCount: %ld, error: `%s'\n", reply->entryCount, strerror(reply->error)));
|
|
// send the reply
|
|
error = write_port(request.replyPort, 0, reply, replySize);
|
|
delete[] (uint8*)reply;
|
|
return error;
|
|
}
|
|
|
|
// request_loop
|
|
int32
|
|
request_loop(void *data)
|
|
{
|
|
port_id requestPort = *(port_id*)data;
|
|
|
|
// init node manager
|
|
status_t error = NodeManager::GetDefault()->Init();
|
|
if (error != B_OK) {
|
|
fprintf(stderr, "Failed to init node manager: %s\n", strerror(error));
|
|
return 1;
|
|
}
|
|
|
|
// handle requests until our port goes away
|
|
stat_cache_request request;
|
|
while (read_request(requestPort, request) == B_OK) {
|
|
BAutolock _(NodeManager::GetDefault());
|
|
switch (request.command) {
|
|
case STAT_CACHE_COMMAND_STAT:
|
|
handle_stat_request(request);
|
|
break;
|
|
case STAT_CACHE_COMMAND_READDIR:
|
|
handle_read_dir_request(request);
|
|
break;
|
|
default:
|
|
fprintf(stderr, "Unknown command: %ld\n", request.command);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// delete the request port
|
|
if (delete_port(requestPort) == B_OK)
|
|
be_app->PostMessage(B_QUIT_REQUESTED);
|
|
return 0;
|
|
}
|
|
|
|
// main
|
|
int
|
|
main()
|
|
{
|
|
// create the request port
|
|
port_id requestPort = create_port(1, STAT_CACHE_SERVER_PORT_NAME);
|
|
if (requestPort < 0) {
|
|
fprintf(stderr, "Failed to create request port: %s\n",
|
|
strerror(requestPort));
|
|
return 1;
|
|
}
|
|
|
|
// start the request handling loop
|
|
thread_id requestLoop = spawn_thread(request_loop, "request loop",
|
|
B_NORMAL_PRIORITY, &requestPort);
|
|
if (resume_thread(requestLoop) < B_OK)
|
|
return -1;
|
|
|
|
// the BApplication is only needed for a smooth server shutdown
|
|
BApplication app("application/x-vnd.haiku.jam-cacheserver");
|
|
app.Run();
|
|
|
|
// delete the request port
|
|
delete_port(requestPort);
|
|
|
|
// wait for the request handling loop to quit
|
|
status_t result;
|
|
wait_for_thread(requestLoop, &result);
|
|
|
|
return 0;
|
|
}
|