// 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;
}
