/*
 * Copyright 2002-2009, Axel Dörfler, axeld@pinc-software.de.
 * Distributed under the terms of the MIT License.
 *
 * Copyright 2001-2002, Travis Geiselbrecht. All rights reserved.
 * Distributed under the terms of the NewOS License.
 */
 
/*! Virtual File System and File System Interface Layer */
 
#include "vfs.h"
 
#include <new>
#include <stdlib.h>
#include <string.h>
 
#include "fd.h"
#include "fssh_atomic.h"
#include "fssh_defs.h"
#include "fssh_dirent.h"
#include "fssh_errno.h"
#include "fssh_fcntl.h"
#include "fssh_fs_info.h"
#include "fssh_fs_volume.h"
#include "fssh_kernel_export.h"
#include "fssh_module.h"
#include "fssh_stat.h"
#include "fssh_stdio.h"
#include "fssh_string.h"
#include "fssh_uio.h"
#include "fssh_unistd.h"
#include "hash.h"
#include "KPath.h"
#include "posix_compatibility.h"
#include "syscalls.h"
 
//#define TRACE_VFS
#ifdef TRACE_VFS
#	define TRACE(x) fssh_dprintf x
#	define FUNCTION(x) fssh_dprintf x
#else
#	define TRACE(x) ;
#	define FUNCTION(x) ;
#endif
 
#define ADD_DEBUGGER_COMMANDS
 
#define ASSERT_LOCKED_MUTEX(x)
#define ASSERT(x)
 
namespace FSShell {
 
 
#define HAS_FS_CALL(vnode, op)			(vnode->ops->op != NULL)
#define HAS_FS_MOUNT_CALL(mount, op)	(mount->volume->ops->op != NULL)
 
#define FS_CALL(vnode, op, params...) \
			vnode->ops->op(vnode->mount->volume, vnode, params)
#define FS_CALL_NO_PARAMS(vnode, op) \
			vnode->ops->op(vnode->mount->volume, vnode)
#define FS_MOUNT_CALL(mount, op, params...) \
			mount->volume->ops->op(mount->volume, params)
#define FS_MOUNT_CALL_NO_PARAMS(mount, op) \
			mount->volume->ops->op(mount->volume)
 
 
const static uint32_t kMaxUnusedVnodes = 16;
	// This is the maximum number of unused vnodes that the system
	// will keep around (weak limit, if there is enough memory left,
	// they won't get flushed even when hitting that limit).
	// It may be chosen with respect to the available memory or enhanced
	// by some timestamp/frequency heurism.
 
struct vnode : fssh_fs_vnode {
	struct vnode	*next;
	vm_cache_ref	*cache;
	fssh_mount_id	device;
	list_link		mount_link;
	list_link		unused_link;
	fssh_vnode_id	id;
	struct fs_mount	*mount;
	struct vnode	*covered_by;
	int32_t			ref_count;
	uint32_t		type : 29;
						// TODO: S_INDEX_DIR actually needs another bit.
						// Better combine this field with the following ones.
	uint32_t		remove : 1;
	uint32_t		busy : 1;
	uint32_t		unpublished : 1;
	struct file_descriptor *mandatory_locked_by;
};
 
struct vnode_hash_key {
	fssh_mount_id	device;
	fssh_vnode_id	vnode;
};
 
/**	\brief Structure to manage a mounted file system
 
	Note: The root_vnode and covers_vnode fields (what others?) are
	initialized in fs_mount() and not changed afterwards. That is as soon
	as the mount is mounted and it is made sure it won't be unmounted
	(e.g. by holding a reference to a vnode of that mount) (read) access
	to those fields is always safe, even without additional locking. Morever
	while mounted the mount holds a reference to the covers_vnode, and thus
	making the access path vnode->mount->covers_vnode->mount->... safe if a
	reference to vnode is held (note that for the root mount covers_vnode
	is NULL, though).
 */
struct fs_mount {
	struct fs_mount	*next;
	fssh_file_system_module_info *fs;
	fssh_mount_id		id;
	fssh_fs_volume		*volume;
	char			*device_name;
	char			*fs_name;
	fssh_recursive_lock	rlock;	// guards the vnodes list
	struct vnode	*root_vnode;
	struct vnode	*covers_vnode;
	struct list		vnodes;
	bool			unmounting;
	bool			owns_file_device;
};
 
static fssh_mutex sFileSystemsMutex;
 
/**	\brief Guards sMountsTable.
 *
 *	The holder is allowed to read/write access the sMountsTable.
 *	Manipulation of the fs_mount structures themselves
 *	(and their destruction) requires different locks though.
 */
static fssh_mutex sMountMutex;
 
/**	\brief Guards mount/unmount operations.
 *
 *	The fs_mount() and fs_unmount() hold the lock during their whole operation.
 *	That is locking the lock ensures that no FS is mounted/unmounted. In
 *	particular this means that
 *	- sMountsTable will not be modified,
 *	- the fields immutable after initialization of the fs_mount structures in
 *	  sMountsTable will not be modified,
 *	- vnode::covered_by of any vnode in sVnodeTable will not be modified.
 *
 *	The thread trying to lock the lock must not hold sVnodeMutex or
 *	sMountMutex.
 */
static fssh_recursive_lock sMountOpLock;
 
/**	\brief Guards the vnode::covered_by field of any vnode
 *
 *	The holder is allowed to read access the vnode::covered_by field of any
 *	vnode. Additionally holding sMountOpLock allows for write access.
 *
 *	The thread trying to lock the must not hold sVnodeMutex.
 */
static fssh_mutex sVnodeCoveredByMutex;
 
/**	\brief Guards sVnodeTable.
 *
 *	The holder is allowed to read/write access sVnodeTable and to
 *	to any unbusy vnode in that table, save
 *	to the immutable fields (device, id, private_node, mount) to which
 *	only read-only access is allowed, and to the field covered_by, which is
 *	guarded by sMountOpLock and sVnodeCoveredByMutex.
 *
 *	The thread trying to lock the mutex must not hold sMountMutex.
 *	You must not have this mutex held when calling create_sem(), as this
 *	might call vfs_free_unused_vnodes().
 */
static fssh_mutex sVnodeMutex;
 
#define VNODE_HASH_TABLE_SIZE 1024
static hash_table *sVnodeTable;
static list sUnusedVnodeList;
static uint32_t sUnusedVnodes = 0;
static struct vnode *sRoot;
 
#define MOUNTS_HASH_TABLE_SIZE 16
static hash_table *sMountsTable;
static fssh_mount_id sNextMountID = 1;
 
#define MAX_TEMP_IO_VECS 8
 
fssh_mode_t __fssh_gUmask = 022;
 
/* function declarations */
 
// file descriptor operation prototypes
static fssh_status_t file_read(struct file_descriptor *, fssh_off_t pos,
			void *buffer, fssh_size_t *);
static fssh_status_t file_write(struct file_descriptor *, fssh_off_t pos,
			const void *buffer, fssh_size_t *);
static fssh_off_t file_seek(struct file_descriptor *, fssh_off_t pos,
			int seek_type);
static void file_free_fd(struct file_descriptor *);
static fssh_status_t file_close(struct file_descriptor *);
static fssh_status_t dir_read(struct file_descriptor *,
			struct fssh_dirent *buffer, fssh_size_t bufferSize,
			uint32_t *_count);
static fssh_status_t dir_read(struct vnode *vnode, void *cookie,
			struct fssh_dirent *buffer, fssh_size_t bufferSize,
			uint32_t *_count);
static fssh_status_t dir_rewind(struct file_descriptor *);
static void dir_free_fd(struct file_descriptor *);
static fssh_status_t dir_close(struct file_descriptor *);
static fssh_status_t attr_dir_read(struct file_descriptor *,
			struct fssh_dirent *buffer, fssh_size_t bufferSize,
			uint32_t *_count);
static fssh_status_t attr_dir_rewind(struct file_descriptor *);
static void attr_dir_free_fd(struct file_descriptor *);
static fssh_status_t attr_dir_close(struct file_descriptor *);
static fssh_status_t attr_read(struct file_descriptor *, fssh_off_t pos,
			void *buffer, fssh_size_t *);
static fssh_status_t attr_write(struct file_descriptor *, fssh_off_t pos,
			const void *buffer, fssh_size_t *);
static fssh_off_t attr_seek(struct file_descriptor *, fssh_off_t pos,
			int seek_type);
static void attr_free_fd(struct file_descriptor *);
static fssh_status_t attr_close(struct file_descriptor *);
static fssh_status_t attr_read_stat(struct file_descriptor *,
			struct fssh_stat *);
static fssh_status_t attr_write_stat(struct file_descriptor *,
			const struct fssh_stat *, int statMask);
static fssh_status_t index_dir_read(struct file_descriptor *,
			struct fssh_dirent *buffer, fssh_size_t bufferSize,
			uint32_t *_count);
static fssh_status_t index_dir_rewind(struct file_descriptor *);
static void index_dir_free_fd(struct file_descriptor *);
static fssh_status_t index_dir_close(struct file_descriptor *);
static fssh_status_t query_read(struct file_descriptor *,
			struct fssh_dirent *buffer, fssh_size_t bufferSize,
			uint32_t *_count);
static fssh_status_t query_rewind(struct file_descriptor *);
static void query_free_fd(struct file_descriptor *);
static fssh_status_t query_close(struct file_descriptor *);
 
static fssh_status_t common_ioctl(struct file_descriptor *, uint32_t, void *buf,
			fssh_size_t len);
static fssh_status_t common_read_stat(struct file_descriptor *,
			struct fssh_stat *);
static fssh_status_t common_write_stat(struct file_descriptor *,
			const struct fssh_stat *, int statMask);
 
static fssh_status_t vnode_path_to_vnode(struct vnode *vnode, char *path,
			bool traverseLeafLink, int count, struct vnode **_vnode,
			fssh_vnode_id *_parentID);
static fssh_status_t dir_vnode_to_path(struct vnode *vnode, char *buffer,
			fssh_size_t bufferSize);
static fssh_status_t fd_and_path_to_vnode(int fd, char *path,
			bool traverseLeafLink, struct vnode **_vnode,
			fssh_vnode_id *_parentID, bool kernel);
static void inc_vnode_ref_count(struct vnode *vnode);
static fssh_status_t dec_vnode_ref_count(struct vnode *vnode, bool reenter);
static inline void put_vnode(struct vnode *vnode);
 
static struct fd_ops sFileOps = {
	file_read,
	file_write,
	file_seek,
	common_ioctl,
	NULL,
	NULL,
	NULL,		// read_dir()
	NULL,		// rewind_dir()
	common_read_stat,
	common_write_stat,
	file_close,
	file_free_fd
};
 
static struct fd_ops sDirectoryOps = {
	NULL,		// read()
	NULL,		// write()
	NULL,		// seek()
	common_ioctl,
	NULL,		// select()
	NULL,		// deselect()
	dir_read,
	dir_rewind,
	common_read_stat,
	common_write_stat,
	dir_close,
	dir_free_fd
};
 
static struct fd_ops sAttributeDirectoryOps = {
	NULL,		// read()
	NULL,		// write()
	NULL,		// seek()
	common_ioctl,
	NULL,		// select()
	NULL,		// deselect()
	attr_dir_read,
	attr_dir_rewind,
	common_read_stat,
	common_write_stat,
	attr_dir_close,
	attr_dir_free_fd
};
 
static struct fd_ops sAttributeOps = {
	attr_read,
	attr_write,
	attr_seek,
	common_ioctl,
	NULL,		// select()
	NULL,		// deselect()
	NULL,		// read_dir()
	NULL,		// rewind_dir()
	attr_read_stat,
	attr_write_stat,
	attr_close,
	attr_free_fd
};
 
static struct fd_ops sIndexDirectoryOps = {
	NULL,		// read()
	NULL,		// write()
	NULL,		// seek()
	NULL,		// ioctl()
	NULL,		// select()
	NULL,		// deselect()
	index_dir_read,
	index_dir_rewind,
	NULL,		// read_stat()
	NULL,		// write_stat()
	index_dir_close,
	index_dir_free_fd
};
 
#if 0
static struct fd_ops sIndexOps = {
	NULL,		// read()
	NULL,		// write()
	NULL,		// seek()
	NULL,		// ioctl()
	NULL,		// select()
	NULL,		// deselect()
	NULL,		// dir_read()
	NULL,		// dir_rewind()
	index_read_stat,	// read_stat()
	NULL,		// write_stat()
	NULL,		// dir_close()
	NULL		// free_fd()
};
#endif
 
static struct fd_ops sQueryOps = {
	NULL,		// read()
	NULL,		// write()
	NULL,		// seek()
	NULL,		// ioctl()
	NULL,		// select()
	NULL,		// deselect()
	query_read,
	query_rewind,
	NULL,		// read_stat()
	NULL,		// write_stat()
	query_close,
	query_free_fd
};
 
 
// VNodePutter
class VNodePutter {
public:
	VNodePutter(struct vnode *vnode = NULL) : fVNode(vnode) {}
 
	~VNodePutter()
	{
		Put();
	}
 
	void SetTo(struct vnode *vnode)
	{
		Put();
		fVNode = vnode;
	}
 
	void Put()
	{
		if (fVNode) {
			put_vnode(fVNode);
			fVNode = NULL;
		}
	}
 
	struct vnode *Detach()
	{
		struct vnode *vnode = fVNode;
		fVNode = NULL;
		return vnode;
	}
 
private:
	struct vnode *fVNode;
};
 
 
static int
mount_compare(void *_m, const void *_key)
{
	struct fs_mount *mount = (fs_mount *)_m;
	const fssh_mount_id *id = (fssh_mount_id *)_key;
 
	if (mount->id == *id)
		return 0;
 
	return -1;
}
 
 
static uint32_t
mount_hash(void *_m, const void *_key, uint32_t range)
{
	struct fs_mount *mount = (fs_mount *)_m;
	const fssh_mount_id *id = (fssh_mount_id *)_key;
 
	if (mount)
		return mount->id % range;
 
	return (uint32_t)*id % range;
}
 
 
/** Finds the mounted device (the fs_mount structure) with the given ID.
 *	Note, you must hold the gMountMutex lock when you call this function.
 */
 
static struct fs_mount *
find_mount(fssh_mount_id id)
{
	ASSERT_LOCKED_MUTEX(&sMountMutex);
 
	return (fs_mount *)hash_lookup(sMountsTable, (void *)&id);
}
 
 
static fssh_status_t
get_mount(fssh_mount_id id, struct fs_mount **_mount)
{
	MutexLocker locker(&sMountMutex);
 
	struct fs_mount *mount = find_mount(id);
	if (mount == NULL)
		return FSSH_B_BAD_VALUE;
 
	if (mount->root_vnode == NULL) {
		// might have been called during a mount operation in which
		// case the root node may still be NULL
		return FSSH_B_BUSY;
	}
 
	inc_vnode_ref_count(mount->root_vnode);
	*_mount = mount;
 
	return FSSH_B_OK;
}
 
 
static void
put_mount(struct fs_mount *mount)
{
	if (mount)
		put_vnode(mount->root_vnode);
}
 
 
static fssh_status_t
put_file_system(fssh_file_system_module_info *fs)
{
	return fssh_put_module(fs->info.name);
}
 
 
/**	Tries to open the specified file system module.
 *	Accepts a file system name of the form "bfs" or "file_systems/bfs/v1".
 *	Returns a pointer to file system module interface, or NULL if it
 *	could not open the module.
 */
 
static fssh_file_system_module_info *
get_file_system(const char *fsName)
{
	char name[FSSH_B_FILE_NAME_LENGTH];
	if (fssh_strncmp(fsName, "file_systems/", fssh_strlen("file_systems/"))) {
		// construct module name if we didn't get one
		// (we currently support only one API)
		fssh_snprintf(name, sizeof(name), "file_systems/%s/v1", fsName);
		fsName = NULL;
	}
 
	fssh_file_system_module_info *info;
	if (fssh_get_module(fsName ? fsName : name, (fssh_module_info **)&info) != FSSH_B_OK)
		return NULL;
 
	return info;
}
 
 
/**	Accepts a file system name of the form "bfs" or "file_systems/bfs/v1"
 *	and returns a compatible fs_info.fsh_name name ("bfs" in both cases).
 *	The name is allocated for you, and you have to free() it when you're
 *	done with it.
 *	Returns NULL if the required memory is no available.
 */
 
static char *
get_file_system_name(const char *fsName)
{
	const fssh_size_t length = fssh_strlen("file_systems/");
 
	if (fssh_strncmp(fsName, "file_systems/", length)) {
		// the name already seems to be the module's file name
		return fssh_strdup(fsName);
	}
 
	fsName += length;
	const char *end = fssh_strchr(fsName, '/');
	if (end == NULL) {
		// this doesn't seem to be a valid name, but well...
		return fssh_strdup(fsName);
	}
 
	// cut off the trailing /v1
 
	char *name = (char *)malloc(end + 1 - fsName);
	if (name == NULL)
		return NULL;
 
	fssh_strlcpy(name, fsName, end + 1 - fsName);
	return name;
}
 
 
static int
vnode_compare(void *_vnode, const void *_key)
{
	struct vnode *vnode = (struct vnode *)_vnode;
	const struct vnode_hash_key *key = (vnode_hash_key *)_key;
 
	if (vnode->device == key->device && vnode->id == key->vnode)
		return 0;
 
	return -1;
}
 
 
static uint32_t
vnode_hash(void *_vnode, const void *_key, uint32_t range)
{
	struct vnode *vnode = (struct vnode *)_vnode;
	const struct vnode_hash_key *key = (vnode_hash_key *)_key;
 
#define VHASH(mountid, vnodeid) (((uint32_t)((vnodeid) >> 32) + (uint32_t)(vnodeid)) ^ (uint32_t)(mountid))
 
	if (vnode != NULL)
		return VHASH(vnode->device, vnode->id) % range;
 
	return VHASH(key->device, key->vnode) % range;
 
#undef VHASH
}
 
 
static void
add_vnode_to_mount_list(struct vnode *vnode, struct fs_mount *mount)
{
	fssh_recursive_lock_lock(&mount->rlock);
 
	list_add_link_to_head(&mount->vnodes, &vnode->mount_link);
 
	fssh_recursive_lock_unlock(&mount->rlock);
}
 
 
static void
remove_vnode_from_mount_list(struct vnode *vnode, struct fs_mount *mount)
{
	fssh_recursive_lock_lock(&mount->rlock);
 
	list_remove_link(&vnode->mount_link);
	vnode->mount_link.next = vnode->mount_link.prev = NULL;
 
	fssh_recursive_lock_unlock(&mount->rlock);
}
 
 
static fssh_status_t
create_new_vnode(struct vnode **_vnode, fssh_mount_id mountID, fssh_vnode_id vnodeID)
{
	FUNCTION(("create_new_vnode()\n"));
 
	struct vnode *vnode = (struct vnode *)malloc(sizeof(struct vnode));
	if (vnode == NULL)
		return FSSH_B_NO_MEMORY;
 
	// initialize basic values
	fssh_memset(vnode, 0, sizeof(struct vnode));
	vnode->device = mountID;
	vnode->id = vnodeID;
 
	// add the vnode to the mount structure
	fssh_mutex_lock(&sMountMutex);
	vnode->mount = find_mount(mountID);
	if (!vnode->mount || vnode->mount->unmounting) {
		fssh_mutex_unlock(&sMountMutex);
		free(vnode);
		return FSSH_B_ENTRY_NOT_FOUND;
	}
 
	hash_insert(sVnodeTable, vnode);
	add_vnode_to_mount_list(vnode, vnode->mount);
 
	fssh_mutex_unlock(&sMountMutex);
 
	vnode->ref_count = 1;
	*_vnode = vnode;
 
	return FSSH_B_OK;
}
 
 
/**	Frees the vnode and all resources it has acquired, and removes
 *	it from the vnode hash as well as from its mount structure.
 *	Will also make sure that any cache modifications are written back.
 */
 
static void
free_vnode(struct vnode *vnode, bool reenter)
{
	ASSERT(vnode->ref_count == 0 && vnode->busy);
 
	// write back any changes in this vnode's cache -- but only
	// if the vnode won't be deleted, in which case the changes
	// will be discarded
 
	if (!vnode->remove && HAS_FS_CALL(vnode, fsync))
		FS_CALL_NO_PARAMS(vnode, fsync);
 
	if (!vnode->unpublished) {
		if (vnode->remove)
			FS_CALL(vnode, remove_vnode, reenter);
		else
			FS_CALL(vnode, put_vnode, reenter);
	}
 
	// The file system has removed the resources of the vnode now, so we can
	// make it available again (and remove the busy vnode from the hash)
	fssh_mutex_lock(&sVnodeMutex);
	hash_remove(sVnodeTable, vnode);
	fssh_mutex_unlock(&sVnodeMutex);
 
	remove_vnode_from_mount_list(vnode, vnode->mount);
 
	free(vnode);
}
 
 
/**	\brief Decrements the reference counter of the given vnode and deletes it,
 *	if the counter dropped to 0.
 *
 *	The caller must, of course, own a reference to the vnode to call this
 *	function.
 *	The caller must not hold the sVnodeMutex or the sMountMutex.
 *
 *	\param vnode the vnode.
 *	\param reenter \c true, if this function is called (indirectly) from within
 *		   a file system.
 *	\return \c FSSH_B_OK, if everything went fine, an error code otherwise.
 */
 
static fssh_status_t
dec_vnode_ref_count(struct vnode *vnode, bool reenter)
{
	fssh_mutex_lock(&sVnodeMutex);
 
	int32_t oldRefCount = fssh_atomic_add(&vnode->ref_count, -1);
 
	TRACE(("dec_vnode_ref_count: vnode %p, ref now %ld\n", vnode, vnode->ref_count));
 
	if (oldRefCount == 1) {
		if (vnode->busy)
			fssh_panic("dec_vnode_ref_count: called on busy vnode %p\n", vnode);
 
		bool freeNode = false;
 
		// Just insert the vnode into an unused list if we don't need
		// to delete it
		if (vnode->remove) {
			vnode->busy = true;
			freeNode = true;
		} else {
			list_add_item(&sUnusedVnodeList, vnode);
			if (++sUnusedVnodes > kMaxUnusedVnodes) {
				// there are too many unused vnodes so we free the oldest one
				// ToDo: evaluate this mechanism
				vnode = (struct vnode *)list_remove_head_item(&sUnusedVnodeList);
				vnode->busy = true;
				freeNode = true;
				sUnusedVnodes--;
			}
		}
 
		fssh_mutex_unlock(&sVnodeMutex);
 
		if (freeNode)
			free_vnode(vnode, reenter);
	} else
		fssh_mutex_unlock(&sVnodeMutex);
 
	return FSSH_B_OK;
}
 
 
/**	\brief Increments the reference counter of the given vnode.
 *
 *	The caller must either already have a reference to the vnode or hold
 *	the sVnodeMutex.
 *
 *	\param vnode the vnode.
 */
 
static void
inc_vnode_ref_count(struct vnode *vnode)
{
	fssh_atomic_add(&vnode->ref_count, 1);
	TRACE(("inc_vnode_ref_count: vnode %p, ref now %ld\n", vnode, vnode->ref_count));
}
 
 
/**	\brief Looks up a vnode by mount and node ID in the sVnodeTable.
 *
 *	The caller must hold the sVnodeMutex.
 *
 *	\param mountID the mount ID.
 *	\param vnodeID the node ID.
 *
 *	\return The vnode structure, if it was found in the hash table, \c NULL
 *			otherwise.
 */
 
static struct vnode *
lookup_vnode(fssh_mount_id mountID, fssh_vnode_id vnodeID)
{
	struct vnode_hash_key key;
 
	key.device = mountID;
	key.vnode = vnodeID;
 
	return (vnode *)hash_lookup(sVnodeTable, &key);
}
 
 
/**	\brief Retrieves a vnode for a given mount ID, node ID pair.
 *
 *	If the node is not yet in memory, it will be loaded.
 *
 *	The caller must not hold the sVnodeMutex or the sMountMutex.
 *
 *	\param mountID the mount ID.
 *	\param vnodeID the node ID.
 *	\param _vnode Pointer to a vnode* variable into which the pointer to the
 *		   retrieved vnode structure shall be written.
 *	\param reenter \c true, if this function is called (indirectly) from within
 *		   a file system.
 *	\return \c FSSH_B_OK, if everything when fine, an error code otherwise.
 */
 
static fssh_status_t
get_vnode(fssh_mount_id mountID, fssh_vnode_id vnodeID, struct vnode **_vnode, int reenter)
{
	FUNCTION(("get_vnode: mountid %ld vnid 0x%Lx %p\n", mountID, vnodeID, _vnode));
 
	fssh_mutex_lock(&sVnodeMutex);
 
	int32_t tries = 300;
		// try for 3 secs
restart:
	struct vnode *vnode = lookup_vnode(mountID, vnodeID);
	if (vnode && vnode->busy) {
		fssh_mutex_unlock(&sVnodeMutex);
		if (--tries < 0) {
			// vnode doesn't seem to become unbusy
			fssh_panic("vnode %d:%" FSSH_B_PRIdINO " is not becoming unbusy!\n",
				(int)mountID, vnodeID);
			return FSSH_B_BUSY;
		}
		fssh_snooze(10000); // 10 ms
		fssh_mutex_lock(&sVnodeMutex);
		goto restart;
	}
 
	TRACE(("get_vnode: tried to lookup vnode, got %p\n", vnode));
 
	fssh_status_t status;
 
	if (vnode) {
		if (vnode->ref_count == 0) {
			// this vnode has been unused before
			list_remove_item(&sUnusedVnodeList, vnode);
			sUnusedVnodes--;
		}
		inc_vnode_ref_count(vnode);
	} else {
		// we need to create a new vnode and read it in
		status = create_new_vnode(&vnode, mountID, vnodeID);
		if (status < FSSH_B_OK)
			goto err;
 
		vnode->busy = true;
		fssh_mutex_unlock(&sVnodeMutex);
 
		int type;
		uint32_t flags;
		status = FS_MOUNT_CALL(vnode->mount, get_vnode, vnodeID, vnode, &type,
			&flags, reenter);
		if (status == FSSH_B_OK && vnode->private_node == NULL)
			status = FSSH_B_BAD_VALUE;
 
		fssh_mutex_lock(&sVnodeMutex);
 
		if (status < FSSH_B_OK)
			goto err1;
 
		vnode->type = type;
		vnode->busy = false;
	}
 
	fssh_mutex_unlock(&sVnodeMutex);
 
	TRACE(("get_vnode: returning %p\n", vnode));
 
	*_vnode = vnode;
	return FSSH_B_OK;
 
err1:
	hash_remove(sVnodeTable, vnode);
	remove_vnode_from_mount_list(vnode, vnode->mount);
err:
	fssh_mutex_unlock(&sVnodeMutex);
	if (vnode)
		free(vnode);
 
	return status;
}
 
 
/**	\brief Decrements the reference counter of the given vnode and deletes it,
 *	if the counter dropped to 0.
 *
 *	The caller must, of course, own a reference to the vnode to call this
 *	function.
 *	The caller must not hold the sVnodeMutex or the sMountMutex.
 *
 *	\param vnode the vnode.
 */
 
static inline void
put_vnode(struct vnode *vnode)
{
	dec_vnode_ref_count(vnode, false);
}
 
 
/**	Disconnects all file descriptors that are associated with the
 *	\a vnodeToDisconnect, or if this is NULL, all vnodes of the specified
 *	\a mount object.
 *
 *	Note, after you've called this function, there might still be ongoing
 *	accesses - they won't be interrupted if they already happened before.
 *	However, any subsequent access will fail.
 *
 *	This is not a cheap function and should be used with care and rarely.
 *	TODO: there is currently no means to stop a blocking read/write!
 */
 
void
disconnect_mount_or_vnode_fds(struct fs_mount *mount,
	struct vnode *vnodeToDisconnect)
{
}
 
 
/**	\brief Resolves a mount point vnode to the volume root vnode it is covered
 *		   by.
 *
 *	Given an arbitrary vnode, the function checks, whether the node is covered
 *	by the root of a volume. If it is the function obtains a reference to the
 *	volume root node and returns it.
 *
 *	\param vnode The vnode in question.
 *	\return The volume root vnode the vnode cover is covered by, if it is
 *			indeed a mount point, or \c NULL otherwise.
 */
 
static struct vnode *
resolve_mount_point_to_volume_root(struct vnode *vnode)
{
	if (!vnode)
		return NULL;
 
	struct vnode *volumeRoot = NULL;
 
	fssh_mutex_lock(&sVnodeCoveredByMutex);
	if (vnode->covered_by) {
		volumeRoot = vnode->covered_by;
		inc_vnode_ref_count(volumeRoot);
	}
	fssh_mutex_unlock(&sVnodeCoveredByMutex);
 
	return volumeRoot;
}
 
 
/**	\brief Resolves a mount point vnode to the volume root vnode it is covered
 *		   by.
 *
 *	Given an arbitrary vnode (identified by mount and node ID), the function
 *	checks, whether the node is covered by the root of a volume. If it is the
 *	function returns the mount and node ID of the volume root node. Otherwise
 *	it simply returns the supplied mount and node ID.
 *
 *	In case of error (e.g. the supplied node could not be found) the variables
 *	for storing the resolved mount and node ID remain untouched and an error
 *	code is returned.
 *
 *	\param mountID The mount ID of the vnode in question.
 *	\param nodeID The node ID of the vnode in question.
 *	\param resolvedMountID Pointer to storage for the resolved mount ID.
 *	\param resolvedNodeID Pointer to storage for the resolved node ID.
 *	\return
 *	- \c FSSH_B_OK, if everything went fine,
 *	- another error code, if something went wrong.
 */
 
fssh_status_t
resolve_mount_point_to_volume_root(fssh_mount_id mountID, fssh_vnode_id nodeID,
	fssh_mount_id *resolvedMountID, fssh_vnode_id *resolvedNodeID)
{
	// get the node
	struct vnode *node;
	fssh_status_t error = get_vnode(mountID, nodeID, &node, false);
	if (error != FSSH_B_OK)
		return error;
 
	// resolve the node
	struct vnode *resolvedNode = resolve_mount_point_to_volume_root(node);
	if (resolvedNode) {
		put_vnode(node);
		node = resolvedNode;
	}
 
	// set the return values
	*resolvedMountID = node->device;
	*resolvedNodeID = node->id;
 
	put_vnode(node);
 
	return FSSH_B_OK;
}
 
 
/**	\brief Resolves a volume root vnode to the underlying mount point vnode.
 *
 *	Given an arbitrary vnode, the function checks, whether the node is the
 *	root of a volume. If it is (and if it is not "/"), the function obtains
 *	a reference to the underlying mount point node and returns it.
 *
 *	\param vnode The vnode in question (caller must have a reference).
 *	\return The mount point vnode the vnode covers, if it is indeed a volume
 *			root and not "/", or \c NULL otherwise.
 */
 
static struct vnode *
resolve_volume_root_to_mount_point(struct vnode *vnode)
{
	if (!vnode)
		return NULL;
 
	struct vnode *mountPoint = NULL;
 
	struct fs_mount *mount = vnode->mount;
	if (vnode == mount->root_vnode && mount->covers_vnode) {
		mountPoint = mount->covers_vnode;
		inc_vnode_ref_count(mountPoint);
	}
 
	return mountPoint;
}
 
 
/**	\brief Gets the directory path and leaf name for a given path.
 *
 *	The supplied \a path is transformed to refer to the directory part of
 *	the entry identified by the original path, and into the buffer \a filename
 *	the leaf name of the original entry is written.
 *	Neither the returned path nor the leaf name can be expected to be
 *	canonical.
 *
 *	\param path The path to be analyzed. Must be able to store at least one
 *		   additional character.
 *	\param filename The buffer into which the leaf name will be written.
 *		   Must be of size FSSH_B_FILE_NAME_LENGTH at least.
 *	\return \c FSSH_B_OK, if everything went fine, \c FSSH_B_NAME_TOO_LONG, if the leaf
 *		   name is longer than \c FSSH_B_FILE_NAME_LENGTH.
 */
 
static fssh_status_t
get_dir_path_and_leaf(char *path, char *filename)
{
	char *p = fssh_strrchr(path, '/');
		// '/' are not allowed in file names!
 
	FUNCTION(("get_dir_path_and_leaf(path = %s)\n", path));
 
	if (!p) {
		// this path is single segment with no '/' in it
		// ex. "foo"
		if (fssh_strlcpy(filename, path, FSSH_B_FILE_NAME_LENGTH) >= FSSH_B_FILE_NAME_LENGTH)
			return FSSH_B_NAME_TOO_LONG;
		fssh_strcpy(path, ".");
	} else {
		p++;
		if (*p == '\0') {
			// special case: the path ends in '/'
			fssh_strcpy(filename, ".");
		} else {
			// normal leaf: replace the leaf portion of the path with a '.'
			if (fssh_strlcpy(filename, p, FSSH_B_FILE_NAME_LENGTH)
				>= FSSH_B_FILE_NAME_LENGTH) {
				return FSSH_B_NAME_TOO_LONG;
			}
		}
		p[0] = '.';
		p[1] = '\0';
	}
	return FSSH_B_OK;
}
 
 
static fssh_status_t
entry_ref_to_vnode(fssh_mount_id mountID, fssh_vnode_id directoryID, const char *name, struct vnode **_vnode)
{
	char clonedName[FSSH_B_FILE_NAME_LENGTH + 1];
	if (fssh_strlcpy(clonedName, name, FSSH_B_FILE_NAME_LENGTH) >= FSSH_B_FILE_NAME_LENGTH)
		return FSSH_B_NAME_TOO_LONG;
 
	// get the directory vnode and let vnode_path_to_vnode() do the rest
	struct vnode *directory;
 
	fssh_status_t status = get_vnode(mountID, directoryID, &directory, false);
	if (status < 0)
		return status;
 
	return vnode_path_to_vnode(directory, clonedName, false, 0, _vnode, NULL);
}
 
 
static fssh_status_t
lookup_dir_entry(struct vnode* dir, const char* name, struct vnode** _vnode)
{
	fssh_ino_t id;
	fssh_status_t status = FS_CALL(dir, lookup, name, &id);
	if (status < FSSH_B_OK)
		return status;
 
	fssh_mutex_lock(&sVnodeMutex);
	*_vnode = lookup_vnode(dir->device, id);
	fssh_mutex_unlock(&sVnodeMutex);
 
	if (*_vnode == NULL) {
		fssh_panic("lookup_dir_entry(): could not lookup vnode (mountid %d "
			"vnid %" FSSH_B_PRIdINO ")\n", (int)dir->device, id);
		return FSSH_B_ENTRY_NOT_FOUND;
	}
 
	return FSSH_B_OK;
}
 
 
/*!	Returns the vnode for the relative path starting at the specified \a vnode.
	\a path must not be NULL.
	If it returns successfully, \a path contains the name of the last path
	component. This function clobbers the buffer pointed to by \a path only
	if it does contain more than one component.
	Note, this reduces the ref_count of the starting \a vnode, no matter if
	it is successful or not!
*/
static fssh_status_t
vnode_path_to_vnode(struct vnode *vnode, char *path, bool traverseLeafLink,
	int count, struct vnode **_vnode, fssh_vnode_id *_parentID)
{
	fssh_status_t status = 0;
	fssh_vnode_id lastParentID = vnode->id;
 
	FUNCTION(("vnode_path_to_vnode(vnode = %p, path = %s)\n", vnode, path));
 
	if (path == NULL) {
		put_vnode(vnode);
		return FSSH_B_BAD_VALUE;
	}
 
	while (true) {
		struct vnode *nextVnode;
		char *nextPath;
 
		TRACE(("vnode_path_to_vnode: top of loop. p = %p, p = '%s'\n", path, path));
 
		// done?
		if (path[0] == '\0')
			break;
 
		// walk to find the next path component ("path" will point to a single
		// path component), and filter out multiple slashes
		for (nextPath = path + 1; *nextPath != '\0' && *nextPath != '/'; nextPath++);
 
		if (*nextPath == '/') {
			*nextPath = '\0';
			do
				nextPath++;
			while (*nextPath == '/');
		}
 
		// See if the '..' is at the root of a mount and move to the covered
		// vnode so we pass the '..' path to the underlying filesystem
		if (!fssh_strcmp("..", path)
			&& vnode->mount->root_vnode == vnode
			&& vnode->mount->covers_vnode) {
			nextVnode = vnode->mount->covers_vnode;
			inc_vnode_ref_count(nextVnode);
			put_vnode(vnode);
			vnode = nextVnode;
		}
 
		// Check if we have the right to search the current directory vnode.
		// If a file system doesn't have the access() function, we assume that
		// searching a directory is always allowed
		if (HAS_FS_CALL(vnode, access))
			status = FS_CALL(vnode, access, FSSH_X_OK);
 
		// Tell the filesystem to get the vnode of this path component (if we got the
		// permission from the call above)
		if (status >= FSSH_B_OK)
			status = lookup_dir_entry(vnode, path, &nextVnode);
 
		if (status < FSSH_B_OK) {
			put_vnode(vnode);
			return status;
		}
 
		// If the new node is a symbolic link, resolve it (if we've been told to do it)
		if (FSSH_S_ISLNK(vnode->type)
			&& !(!traverseLeafLink && nextPath[0] == '\0')) {
			fssh_size_t bufferSize;
			char *buffer;
 
			TRACE(("traverse link\n"));
 
			// it's not exactly nice style using goto in this way, but hey, it works :-/
			if (count + 1 > FSSH_B_MAX_SYMLINKS) {
				status = FSSH_B_LINK_LIMIT;
				goto resolve_link_error;
			}
 
			buffer = (char *)malloc(bufferSize = FSSH_B_PATH_NAME_LENGTH);
			if (buffer == NULL) {
				status = FSSH_B_NO_MEMORY;
				goto resolve_link_error;
			}
 
			if (HAS_FS_CALL(nextVnode, read_symlink)) {
				status = FS_CALL(nextVnode, read_symlink, buffer,
					&bufferSize);
			} else
				status = FSSH_B_BAD_VALUE;
 
			if (status < FSSH_B_OK) {
				free(buffer);
 
		resolve_link_error:
				put_vnode(vnode);
				put_vnode(nextVnode);
 
				return status;
			}
			put_vnode(nextVnode);
 
			// Check if we start from the root directory or the current
			// directory ("vnode" still points to that one).
			// Cut off all leading slashes if it's the root directory
			path = buffer;
			if (path[0] == '/') {
				// we don't need the old directory anymore
				put_vnode(vnode);
 
				while (*++path == '/')
					;
				vnode = sRoot;
				inc_vnode_ref_count(vnode);
			}
			inc_vnode_ref_count(vnode);
				// balance the next recursion - we will decrement the ref_count
				// of the vnode, no matter if we succeeded or not
 
			status = vnode_path_to_vnode(vnode, path, traverseLeafLink, count + 1,
				&nextVnode, &lastParentID);
 
			free(buffer);
 
			if (status < FSSH_B_OK) {
				put_vnode(vnode);
				return status;
			}
		} else
			lastParentID = vnode->id;
 
		// decrease the ref count on the old dir we just looked up into
		put_vnode(vnode);
 
		path = nextPath;
		vnode = nextVnode;
 
		// see if we hit a mount point
		struct vnode *mountPoint = resolve_mount_point_to_volume_root(vnode);
		if (mountPoint) {
			put_vnode(vnode);
			vnode = mountPoint;
		}
	}
 
	*_vnode = vnode;
	if (_parentID)
		*_parentID = lastParentID;
 
	return FSSH_B_OK;
}
 
 
static fssh_status_t
path_to_vnode(char *path, bool traverseLink, struct vnode **_vnode,
	fssh_vnode_id *_parentID, bool kernel)
{
	struct vnode *start = NULL;
 
	FUNCTION(("path_to_vnode(path = \"%s\")\n", path));
 
	if (!path)
		return FSSH_B_BAD_VALUE;
 
	// figure out if we need to start at root or at cwd
	if (*path == '/') {
		if (sRoot == NULL) {
			// we're a bit early, aren't we?
			return FSSH_B_ERROR;
		}
 
		while (*++path == '/')
			;
		start = sRoot;
		inc_vnode_ref_count(start);
	} else {
		struct io_context *context = get_current_io_context(kernel);
 
		fssh_mutex_lock(&context->io_mutex);
		start = context->cwd;
		if (start != NULL)
			inc_vnode_ref_count(start);
		fssh_mutex_unlock(&context->io_mutex);
 
		if (start == NULL)
			return FSSH_B_ERROR;
	}
 
	return vnode_path_to_vnode(start, path, traverseLink, 0, _vnode, _parentID);
}
 
 
/** Returns the vnode in the next to last segment of the path, and returns
 *	the last portion in filename.
 *	The path buffer must be able to store at least one additional character.
 */
 
static fssh_status_t
path_to_dir_vnode(char *path, struct vnode **_vnode, char *filename, bool kernel)
{
	fssh_status_t status = get_dir_path_and_leaf(path, filename);
	if (status != FSSH_B_OK)
		return status;
 
	return path_to_vnode(path, true, _vnode, NULL, kernel);
}
 
 
/**	\brief Retrieves the directory vnode and the leaf name of an entry referred
 *		   to by a FD + path pair.
 *
 *	\a path must be given in either case. \a fd might be omitted, in which
 *	case \a path is either an absolute path or one relative to the current
 *	directory. If both a supplied and \a path is relative it is reckoned off
 *	of the directory referred to by \a fd. If \a path is absolute \a fd is
 *	ignored.
 *
 *	The caller has the responsibility to call put_vnode() on the returned
 *	directory vnode.
 *
 *	\param fd The FD. May be < 0.
 *	\param path The absolute or relative path. Must not be \c NULL. The buffer
 *	       is modified by this function. It must have at least room for a
 *	       string one character longer than the path it contains.
 *	\param _vnode A pointer to a variable the directory vnode shall be written
 *		   into.
 *	\param filename A buffer of size FSSH_B_FILE_NAME_LENGTH or larger into which
 *		   the leaf name of the specified entry will be written.
 *	\param kernel \c true, if invoked from inside the kernel, \c false if
 *		   invoked from userland.
 *	\return \c FSSH_B_OK, if everything went fine, another error code otherwise.
 */
 
static fssh_status_t
fd_and_path_to_dir_vnode(int fd, char *path, struct vnode **_vnode,
	char *filename, bool kernel)
{
	if (!path)
		return FSSH_B_BAD_VALUE;
	if (fd < 0)
		return path_to_dir_vnode(path, _vnode, filename, kernel);
 
	fssh_status_t status = get_dir_path_and_leaf(path, filename);
	if (status != FSSH_B_OK)
		return status;
 
	return fd_and_path_to_vnode(fd, path, true, _vnode, NULL, kernel);
}
 
 
/** Returns a vnode's name in the d_name field of a supplied dirent buffer.
 */
 
static fssh_status_t
get_vnode_name(struct vnode *vnode, struct vnode *parent,
	struct fssh_dirent *buffer, fssh_size_t bufferSize)
{
	if (bufferSize < sizeof(struct fssh_dirent))
		return FSSH_B_BAD_VALUE;
 
	// See if vnode is the root of a mount and move to the covered
	// vnode so we get the underlying file system
	VNodePutter vnodePutter;
	if (vnode->mount->root_vnode == vnode && vnode->mount->covers_vnode != NULL) {
		vnode = vnode->mount->covers_vnode;
		inc_vnode_ref_count(vnode);
		vnodePutter.SetTo(vnode);
	}
 
	if (HAS_FS_CALL(vnode, get_vnode_name)) {
		// The FS supports getting the name of a vnode.
		return FS_CALL(vnode, get_vnode_name, buffer->d_name,
			(char*)buffer + bufferSize - buffer->d_name);
	}
 
	// The FS doesn't support getting the name of a vnode. So we search the
	// parent directory for the vnode, if the caller let us.
 
	if (parent == NULL)
		return FSSH_EOPNOTSUPP;
 
	void *cookie;
 
	fssh_status_t status = FS_CALL(parent, open_dir, &cookie);
	if (status >= FSSH_B_OK) {
		while (true) {
			uint32_t num = 1;
			status = dir_read(parent, cookie, buffer, bufferSize, &num);
			if (status < FSSH_B_OK)
				break;
			if (num == 0) {
				status = FSSH_B_ENTRY_NOT_FOUND;
				break;
			}
 
			if (vnode->id == buffer->d_ino) {
				// found correct entry!
				break;
			}
		}
 
		FS_CALL(vnode, close_dir, cookie);
		FS_CALL(vnode, free_dir_cookie, cookie);
	}
	return status;
}
 
 
static fssh_status_t
get_vnode_name(struct vnode *vnode, struct vnode *parent, char *name,
	fssh_size_t nameSize)
{
	char buffer[sizeof(struct fssh_dirent) + FSSH_B_FILE_NAME_LENGTH];
	struct fssh_dirent *dirent = (struct fssh_dirent *)buffer;
 
	fssh_status_t status = get_vnode_name(vnode, parent, buffer, sizeof(buffer));
	if (status != FSSH_B_OK)
		return status;
 
	if (fssh_strlcpy(name, dirent->d_name, nameSize) >= nameSize)
		return FSSH_B_BUFFER_OVERFLOW;
 
	return FSSH_B_OK;
}
 
 
/**	Gets the full path to a given directory vnode.
 *	It uses the fs_get_vnode_name() call to get the name of a vnode; if a
 *	file system doesn't support this call, it will fall back to iterating
 *	through the parent directory to get the name of the child.
 *
 *	To protect against circular loops, it supports a maximum tree depth
 *	of 256 levels.
 *
 *	Note that the path may not be correct the time this function returns!
 *	It doesn't use any locking to prevent returning the correct path, as
 *	paths aren't safe anyway: the path to a file can change at any time.
 *
 *	It might be a good idea, though, to check if the returned path exists
 *	in the calling function (it's not done here because of efficiency)
 */
 
static fssh_status_t
dir_vnode_to_path(struct vnode *vnode, char *buffer, fssh_size_t bufferSize)
{
	FUNCTION(("dir_vnode_to_path(%p, %p, %lu)\n", vnode, buffer, bufferSize));
 
	if (vnode == NULL || buffer == NULL)
		return FSSH_B_BAD_VALUE;
 
	/* this implementation is currently bound to FSSH_B_PATH_NAME_LENGTH */
	KPath pathBuffer;
	if (pathBuffer.InitCheck() != FSSH_B_OK)
		return FSSH_B_NO_MEMORY;
 
	char *path = pathBuffer.LockBuffer();
	int32_t insert = pathBuffer.BufferSize();
	int32_t maxLevel = 256;
	int32_t length;
	fssh_status_t status;
 
	// we don't use get_vnode() here because this call is more
	// efficient and does all we need from get_vnode()
	inc_vnode_ref_count(vnode);
 
	// resolve a volume root to its mount point
	struct vnode *mountPoint = resolve_volume_root_to_mount_point(vnode);
	if (mountPoint) {
		put_vnode(vnode);
		vnode = mountPoint;
	}
 
	path[--insert] = '\0';
 
	while (true) {
		// the name buffer is also used for fs_read_dir()
		char nameBuffer[sizeof(struct fssh_dirent) + FSSH_B_FILE_NAME_LENGTH];
		char *name = &((struct fssh_dirent *)nameBuffer)->d_name[0];
		struct vnode *parentVnode;
		fssh_vnode_id parentID;
 
		// lookup the parent vnode
		status = lookup_dir_entry(vnode, "..", &parentVnode);
		if (status < FSSH_B_OK)
			goto out;
 
		// get the node's name
		status = get_vnode_name(vnode, parentVnode,
			(struct fssh_dirent*)nameBuffer, sizeof(nameBuffer));
 
		// resolve a volume root to its mount point
		mountPoint = resolve_volume_root_to_mount_point(parentVnode);
		if (mountPoint) {
			put_vnode(parentVnode);
			parentVnode = mountPoint;
			parentID = parentVnode->id;
		}
 
		bool hitRoot = (parentVnode == vnode);
 
		// release the current vnode, we only need its parent from now on
		put_vnode(vnode);
		vnode = parentVnode;
 
		if (status < FSSH_B_OK)
			goto out;
 
		if (hitRoot) {
			// we have reached "/", which means we have constructed the full
			// path
			break;
		}
 
		// ToDo: add an explicit check for loops in about 10 levels to do
		// real loop detection
 
		// don't go deeper as 'maxLevel' to prevent circular loops
		if (maxLevel-- < 0) {
			status = FSSH_ELOOP;
			goto out;
		}
 
		// add the name in front of the current path
		name[FSSH_B_FILE_NAME_LENGTH - 1] = '\0';
		length = fssh_strlen(name);
		insert -= length;
		if (insert <= 0) {
			status = FSSH_ENOBUFS;
			goto out;
		}
		fssh_memcpy(path + insert, name, length);
		path[--insert] = '/';
	}
 
	// the root dir will result in an empty path: fix it
	if (path[insert] == '\0')
		path[--insert] = '/';
 
	TRACE(("  path is: %s\n", path + insert));
 
	// copy the path to the output buffer
	length = pathBuffer.BufferSize() - insert;
	if (length <= (int)bufferSize)
		fssh_memcpy(buffer, path + insert, length);
	else
		status = FSSH_ENOBUFS;
 
out:
	put_vnode(vnode);
	return status;
}
 
 
/**	Checks the length of every path component, and adds a '.'
 *	if the path ends in a slash.
 *	The given path buffer must be able to store at least one
 *	additional character.
 */
 
static fssh_status_t
check_path(char *to)
{
	int32_t length = 0;
 
	// check length of every path component
 
	while (*to) {
		char *begin;
		if (*to == '/')
			to++, length++;
 
		begin = to;
		while (*to != '/' && *to)
			to++, length++;
 
		if (to - begin > FSSH_B_FILE_NAME_LENGTH)
			return FSSH_B_NAME_TOO_LONG;
	}
 
	if (length == 0)
		return FSSH_B_ENTRY_NOT_FOUND;
 
	// complete path if there is a slash at the end
 
	if (*(to - 1) == '/') {
		if (length > FSSH_B_PATH_NAME_LENGTH - 2)
			return FSSH_B_NAME_TOO_LONG;
 
		to[0] = '.';
		to[1] = '\0';
	}
 
	return FSSH_B_OK;
}
 
 
static struct file_descriptor *
get_fd_and_vnode(int fd, struct vnode **_vnode, bool kernel)
{
	struct file_descriptor *descriptor = get_fd(get_current_io_context(kernel), fd);
	if (descriptor == NULL)
		return NULL;
 
	if (fd_vnode(descriptor) == NULL) {
		put_fd(descriptor);
		return NULL;
	}
 
	// ToDo: when we can close a file descriptor at any point, investigate
	//	if this is still valid to do (accessing the vnode without ref_count
	//	or locking)
	*_vnode = descriptor->u.vnode;
	return descriptor;
}
 
 
static struct vnode *
get_vnode_from_fd(int fd, bool kernel)
{
	struct file_descriptor *descriptor;
	struct vnode *vnode;
 
	descriptor = get_fd(get_current_io_context(kernel), fd);
	if (descriptor == NULL)
		return NULL;
 
	vnode = fd_vnode(descriptor);
	if (vnode != NULL)
		inc_vnode_ref_count(vnode);
 
	put_fd(descriptor);
	return vnode;
}
 
 
/**	Gets the vnode from an FD + path combination. If \a fd is lower than zero,
 *	only the path will be considered. In this case, the \a path must not be
 *	NULL.
 *	If \a fd is a valid file descriptor, \a path may be NULL for directories,
 *	and should be NULL for files.
 */
 
static fssh_status_t
fd_and_path_to_vnode(int fd, char *path, bool traverseLeafLink,
	struct vnode **_vnode, fssh_vnode_id *_parentID, bool kernel)
{
	if (fd < 0 && !path)
		return FSSH_B_BAD_VALUE;
 
	if (fd < 0 || (path != NULL && path[0] == '/')) {
		// no FD or absolute path
		return path_to_vnode(path, traverseLeafLink, _vnode, _parentID, kernel);
	}
 
	// FD only, or FD + relative path
	struct vnode *vnode = get_vnode_from_fd(fd, kernel);
	if (!vnode)
		return FSSH_B_FILE_ERROR;
 
	if (path != NULL) {
		return vnode_path_to_vnode(vnode, path, traverseLeafLink, 0,
			_vnode, _parentID);
	}
 
	// there is no relative path to take into account
 
	*_vnode = vnode;
	if (_parentID)
		*_parentID = -1;
 
	return FSSH_B_OK;
}
 
 
static int
get_new_fd(int type, struct fs_mount *mount, struct vnode *vnode,
	void *cookie, int openMode, bool kernel)
{
	struct file_descriptor *descriptor;
	int fd;
 
	// if the vnode is locked, we don't allow creating a new file descriptor for it
	if (vnode && vnode->mandatory_locked_by != NULL)
		return FSSH_B_BUSY;
 
	descriptor = alloc_fd();
	if (!descriptor)
		return FSSH_B_NO_MEMORY;
 
	if (vnode)
		descriptor->u.vnode = vnode;
	else
		descriptor->u.mount = mount;
	descriptor->cookie = cookie;
 
	switch (type) {
		// vnode types
		case FDTYPE_FILE:
			descriptor->ops = &sFileOps;
			break;
		case FDTYPE_DIR:
			descriptor->ops = &sDirectoryOps;
			break;
		case FDTYPE_ATTR:
			descriptor->ops = &sAttributeOps;
			break;
		case FDTYPE_ATTR_DIR:
			descriptor->ops = &sAttributeDirectoryOps;
			break;
 
		// mount types
		case FDTYPE_INDEX_DIR:
			descriptor->ops = &sIndexDirectoryOps;
			break;
		case FDTYPE_QUERY:
			descriptor->ops = &sQueryOps;
			break;
 
		default:
			fssh_panic("get_new_fd() called with unknown type %d\n", type);
			break;
	}
	descriptor->type = type;
	descriptor->open_mode = openMode;
 
	fd = new_fd(get_current_io_context(kernel), descriptor);
	if (fd < 0) {
		free(descriptor);
		return FSSH_B_NO_MORE_FDS;
	}
 
	return fd;
}
 
 
/*!	Does the dirty work of combining the file_io_vecs with the iovecs
	and calls the file system hooks to read/write the request to disk.
*/
static fssh_status_t
common_file_io_vec_pages(int fd, const fssh_file_io_vec *fileVecs,
	fssh_size_t fileVecCount, const fssh_iovec *vecs, fssh_size_t vecCount,
	uint32_t *_vecIndex, fssh_size_t *_vecOffset, fssh_size_t *_numBytes,
	bool doWrite)
{
	if (fileVecCount == 0) {
		// There are no file vecs at this offset, so we're obviously trying
		// to access the file outside of its bounds
		return FSSH_B_BAD_VALUE;
	}
 
	fssh_size_t numBytes = *_numBytes;
	uint32_t fileVecIndex;
	fssh_size_t vecOffset = *_vecOffset;
	uint32_t vecIndex = *_vecIndex;
	fssh_status_t status;
	fssh_size_t size;
 
	if (!doWrite && vecOffset == 0) {
		// now directly read the data from the device
		// the first file_io_vec can be read directly
 
		size = fileVecs[0].length;
		if (size > numBytes)
			size = numBytes;
 
		status = fssh_read_pages(fd, fileVecs[0].offset, &vecs[vecIndex],
			vecCount - vecIndex, &size);
		if (status < FSSH_B_OK)
			return status;
 
		// TODO: this is a work-around for buggy device drivers!
		//	When our own drivers honour the length, we can:
		//	a) also use this direct I/O for writes (otherwise, it would
		//	   overwrite precious data)
		//	b) panic if the term below is true (at least for writes)
		if ((uint64_t)size > (uint64_t)fileVecs[0].length) {
			//dprintf("warning: device driver %p doesn't respect total length in read_pages() call!\n", ref->device);
			size = fileVecs[0].length;
		}
 
		ASSERT(size <= fileVecs[0].length);
 
		// If the file portion was contiguous, we're already done now
		if (size == numBytes)
			return FSSH_B_OK;
 
		// if we reached the end of the file, we can return as well
		if ((uint64_t)size != (uint64_t)fileVecs[0].length) {
			*_numBytes = size;
			return FSSH_B_OK;
		}
 
		fileVecIndex = 1;
 
		// first, find out where we have to continue in our iovecs
		for (; vecIndex < vecCount; vecIndex++) {
			if (size < vecs[vecIndex].iov_len)
				break;
 
			size -= vecs[vecIndex].iov_len;
		}
 
		vecOffset = size;
	} else {
		fileVecIndex = 0;
		size = 0;
	}
 
	// Too bad, let's process the rest of the file_io_vecs
 
	fssh_size_t totalSize = size;
	fssh_size_t bytesLeft = numBytes - size;
 
	for (; fileVecIndex < fileVecCount; fileVecIndex++) {
		const fssh_file_io_vec &fileVec = fileVecs[fileVecIndex];
		fssh_off_t fileOffset = fileVec.offset;
		fssh_off_t fileLeft = fssh_min_c((uint64_t)fileVec.length, (uint64_t)bytesLeft);
 
		TRACE(("FILE VEC [%lu] length %Ld\n", fileVecIndex, fileLeft));
 
		// process the complete fileVec
		while (fileLeft > 0) {
			fssh_iovec tempVecs[MAX_TEMP_IO_VECS];
			uint32_t tempCount = 0;
 
			// size tracks how much of what is left of the current fileVec
			// (fileLeft) has been assigned to tempVecs
			size = 0;
 
			// assign what is left of the current fileVec to the tempVecs
			for (size = 0; (uint64_t)size < (uint64_t)fileLeft && vecIndex < vecCount
					&& tempCount < MAX_TEMP_IO_VECS;) {
				// try to satisfy one iovec per iteration (or as much as
				// possible)
 
				// bytes left of the current iovec
				fssh_size_t vecLeft = vecs[vecIndex].iov_len - vecOffset;
				if (vecLeft == 0) {
					vecOffset = 0;
					vecIndex++;
					continue;
				}
 
				TRACE(("fill vec %ld, offset = %lu, size = %lu\n",
					vecIndex, vecOffset, size));
 
				// actually available bytes
				fssh_size_t tempVecSize = fssh_min_c(vecLeft, fileLeft - size);
 
				tempVecs[tempCount].iov_base
					= (void *)((fssh_addr_t)vecs[vecIndex].iov_base + vecOffset);
				tempVecs[tempCount].iov_len = tempVecSize;
				tempCount++;
 
				size += tempVecSize;
				vecOffset += tempVecSize;
			}
 
			fssh_size_t bytes = size;
			if (doWrite) {
				status = fssh_write_pages(fd, fileOffset, tempVecs,
					tempCount, &bytes);
			} else {
				status = fssh_read_pages(fd, fileOffset, tempVecs,
					tempCount, &bytes);
			}
			if (status < FSSH_B_OK)
				return status;
 
			totalSize += bytes;
			bytesLeft -= size;
			fileOffset += size;
			fileLeft -= size;
 
			if (size != bytes || vecIndex >= vecCount) {
				// there are no more bytes or iovecs, let's bail out
				*_numBytes = totalSize;
				return FSSH_B_OK;
			}
		}
	}
 
	*_vecIndex = vecIndex;
	*_vecOffset = vecOffset;
	*_numBytes = totalSize;
	return FSSH_B_OK;
}
 
 
//	#pragma mark - public VFS API
 
 
extern "C" fssh_status_t
fssh_new_vnode(fssh_fs_volume *volume, fssh_vnode_id vnodeID,
	void *privateNode, fssh_fs_vnode_ops *ops)
{
	FUNCTION(("new_vnode(volume = %p (%ld), vnodeID = %Ld, node = %p)\n",
		volume, volume->id, vnodeID, privateNode));
 
	if (privateNode == NULL)
		return FSSH_B_BAD_VALUE;
 
	fssh_mutex_lock(&sVnodeMutex);
 
	// file system integrity check:
	// test if the vnode already exists and bail out if this is the case!
 
	// ToDo: the R5 implementation obviously checks for a different cookie
	//	and doesn't panic if they are equal
 
	struct vnode *vnode = lookup_vnode(volume->id, vnodeID);
	if (vnode != NULL) {
		fssh_panic("vnode %d:%" FSSH_B_PRIdINO " already exists (node = %p, "
			"vnode->node = %p)!", (int)volume->id, vnodeID, privateNode,
			vnode->private_node);
	}
 
	fssh_status_t status = create_new_vnode(&vnode, volume->id, vnodeID);
	if (status == FSSH_B_OK) {
		vnode->private_node = privateNode;
		vnode->ops = ops;
		vnode->busy = true;
		vnode->unpublished = true;
	}
 
	TRACE(("returns: %s\n", strerror(status)));
 
	fssh_mutex_unlock(&sVnodeMutex);
	return status;
}
 
 
extern "C" fssh_status_t
fssh_publish_vnode(fssh_fs_volume *volume, fssh_vnode_id vnodeID,
	void *privateNode, fssh_fs_vnode_ops *ops, int type, uint32_t flags)
{
	FUNCTION(("publish_vnode()\n"));
 
	MutexLocker locker(sVnodeMutex);
 
	struct vnode *vnode = lookup_vnode(volume->id, vnodeID);
	fssh_status_t status = FSSH_B_OK;
 
	if (vnode != NULL && vnode->busy && vnode->unpublished
		&& vnode->private_node == privateNode) {
		// already known, but not published
	} else if (vnode == NULL && privateNode != NULL) {
		status = create_new_vnode(&vnode, volume->id, vnodeID);
		if (status == FSSH_B_OK) {
			vnode->private_node = privateNode;
			vnode->ops = ops;
			vnode->busy = true;
			vnode->unpublished = true;
		}
	} else
		status = FSSH_B_BAD_VALUE;
 
	// create sub vnodes, if necessary
	if (status == FSSH_B_OK && volume->sub_volume != NULL) {
		locker.Unlock();
 
		fssh_fs_volume *subVolume = volume;
		while (status == FSSH_B_OK && subVolume->sub_volume != NULL) {
			subVolume = subVolume->sub_volume;
			status = subVolume->ops->create_sub_vnode(subVolume, vnodeID,
				vnode);
		}
 
		if (status != FSSH_B_OK) {
			// error -- clean up the created sub vnodes
			while (subVolume->super_volume != volume) {
				subVolume = subVolume->super_volume;
				subVolume->ops->delete_sub_vnode(subVolume, vnode);
			}
		}
 
		locker.Lock();
	}
 
	if (status == FSSH_B_OK) {
		vnode->type = type;
		vnode->busy = false;
		vnode->unpublished = false;
	}
 
	TRACE(("returns: %s\n", strerror(status)));
 
	return status;
}
 
 
extern "C" fssh_status_t
fssh_get_vnode(fssh_fs_volume *volume, fssh_vnode_id vnodeID,
	void **privateNode)
{
	struct vnode *vnode;
 
	if (volume == NULL)
		return FSSH_B_BAD_VALUE;
 
	fssh_status_t status = get_vnode(volume->id, vnodeID, &vnode, true);
	if (status < FSSH_B_OK)
		return status;
 
	// If this is a layered FS, we need to get the node cookie for the requested
	// layer.
	if (HAS_FS_CALL(vnode, get_super_vnode)) {
		fssh_fs_vnode resolvedNode;
		fssh_status_t status = FS_CALL(vnode, get_super_vnode, volume,
			&resolvedNode);
		if (status != FSSH_B_OK) {
			fssh_panic("get_vnode(): Failed to get super node for vnode %p, "
				"volume: %p", vnode, volume);
			put_vnode(vnode);
			return status;
		}
 
		if (privateNode != NULL)
			*privateNode = resolvedNode.private_node;
	} else if (privateNode != NULL)
		*privateNode = vnode->private_node;
 
	return FSSH_B_OK;
}
 
 
extern "C" fssh_status_t
fssh_acquire_vnode(fssh_fs_volume *volume, fssh_vnode_id vnodeID)
{
	struct vnode *vnode;
 
	fssh_mutex_lock(&sVnodeMutex);
	vnode = lookup_vnode(volume->id, vnodeID);
	fssh_mutex_unlock(&sVnodeMutex);
 
	if (vnode == NULL)
		return FSSH_B_BAD_VALUE;
 
	inc_vnode_ref_count(vnode);
	return FSSH_B_OK;
}
 
 
extern "C" fssh_status_t
fssh_put_vnode(fssh_fs_volume *volume, fssh_vnode_id vnodeID)
{
	struct vnode *vnode;
 
	fssh_mutex_lock(&sVnodeMutex);
	vnode = lookup_vnode(volume->id, vnodeID);
	fssh_mutex_unlock(&sVnodeMutex);
 
	if (vnode == NULL)
		return FSSH_B_BAD_VALUE;
 
	dec_vnode_ref_count(vnode, true);
	return FSSH_B_OK;
}
 
 
extern "C" fssh_status_t
fssh_remove_vnode(fssh_fs_volume *volume, fssh_vnode_id vnodeID)
{
	struct vnode *vnode;
	bool remove = false;
 
	MutexLocker locker(sVnodeMutex);
 
	vnode = lookup_vnode(volume->id, vnodeID);
	if (vnode == NULL)
		return FSSH_B_ENTRY_NOT_FOUND;
 
	if (vnode->covered_by != NULL) {
		// this vnode is in use
		fssh_mutex_unlock(&sVnodeMutex);
		return FSSH_B_BUSY;
	}
 
	vnode->remove = true;
	if (vnode->unpublished) {
		// prepare the vnode for deletion
		vnode->busy = true;
		remove = true;
	}
 
	locker.Unlock();
 
	if (remove) {
		// if the vnode hasn't been published yet, we delete it here
		fssh_atomic_add(&vnode->ref_count, -1);
		free_vnode(vnode, true);
	}
 
	return FSSH_B_OK;
}
 
 
extern "C" fssh_status_t
fssh_unremove_vnode(fssh_fs_volume *volume, fssh_vnode_id vnodeID)
{
	struct vnode *vnode;
 
	fssh_mutex_lock(&sVnodeMutex);
 
	vnode = lookup_vnode(volume->id, vnodeID);
	if (vnode)
		vnode->remove = false;
 
	fssh_mutex_unlock(&sVnodeMutex);
	return FSSH_B_OK;
}
 
 
extern "C" fssh_status_t
fssh_get_vnode_removed(fssh_fs_volume *volume, fssh_vnode_id vnodeID, bool* removed)
{
	fssh_mutex_lock(&sVnodeMutex);
 
	fssh_status_t result;
 
	if (struct vnode* vnode = lookup_vnode(volume->id, vnodeID)) {
		if (removed)
			*removed = vnode->remove;
		result = FSSH_B_OK;
	} else
		result = FSSH_B_BAD_VALUE;
 
	fssh_mutex_unlock(&sVnodeMutex);
	return result;
}
 
 
extern "C" fssh_status_t
fssh_mark_vnode_busy(fssh_fs_volume* volume, fssh_vnode_id vnodeID, bool busy)
{
	fssh_mutex_lock(&sVnodeMutex);
 
	struct vnode* vnode = lookup_vnode(volume->id, vnodeID);
	if (vnode == NULL) {
		fssh_mutex_unlock(&sVnodeMutex);
		return FSSH_B_ENTRY_NOT_FOUND;
	}
 
	// are we trying to mark an already busy node busy again?
	if (busy && vnode->busy) {
		fssh_mutex_unlock(&sVnodeMutex);
		return FSSH_B_BUSY;
	}
 
	vnode->busy = busy;
 
	fssh_mutex_unlock(&sVnodeMutex);
	return FSSH_B_OK;
}
 
 
extern "C" fssh_status_t
fssh_change_vnode_id(fssh_fs_volume* volume, fssh_vnode_id vnodeID,
	fssh_vnode_id newID)
{
	fssh_mutex_lock(&sVnodeMutex);
 
	struct vnode* vnode = lookup_vnode(volume->id, vnodeID);
	if (vnode == NULL) {
		fssh_mutex_unlock(&sVnodeMutex);
		return FSSH_B_ENTRY_NOT_FOUND;
	}
 
	hash_remove(sVnodeTable, vnode);
	vnode->id = newID;
	hash_insert(sVnodeTable, vnode);
 
	fssh_mutex_unlock(&sVnodeMutex);
	return FSSH_B_OK;
}
 
 
extern "C" fssh_fs_volume*
fssh_volume_for_vnode(fssh_fs_vnode *_vnode)
{
	if (_vnode == NULL)
		return NULL;
 
	struct vnode* vnode = static_cast<struct vnode*>(_vnode);
	return vnode->mount->volume;
}
 
 
extern "C" fssh_status_t
fssh_check_access_permissions(int accessMode, fssh_mode_t mode,
	fssh_gid_t nodeGroupID, fssh_uid_t nodeUserID)
{
	// get node permissions
	int userPermissions = (mode & FSSH_S_IRWXU) >> 6;
	int groupPermissions = (mode & FSSH_S_IRWXG) >> 3;
	int otherPermissions = mode & FSSH_S_IRWXO;
 
	// get the node permissions for this uid/gid
	int permissions = 0;
	fssh_uid_t uid = fssh_geteuid();
 
	if (uid == 0) {
		// user is root
		// root has always read/write permission, but at least one of the
		// X bits must be set for execute permission
		permissions = userPermissions | groupPermissions | otherPermissions
			| FSSH_S_IROTH | FSSH_S_IWOTH;
		if (FSSH_S_ISDIR(mode))
			permissions |= FSSH_S_IXOTH;
	} else if (uid == nodeUserID) {
		// user is node owner
		permissions = userPermissions;
	} else if (fssh_getegid() == nodeGroupID) {
		// user is in owning group
		permissions = groupPermissions;
	} else {
		// user is one of the others
		permissions = otherPermissions;
	}
 
	return (accessMode & ~permissions) == 0 ? FSSH_B_OK : FSSH_B_NOT_ALLOWED;
}
 
 
//! Works directly on the host's file system
extern "C" fssh_status_t
fssh_read_pages(int fd, fssh_off_t pos, const fssh_iovec *vecs,
	fssh_size_t count, fssh_size_t *_numBytes)
{
	// check how much the iovecs allow us to read
	fssh_size_t toRead = 0;
	for (fssh_size_t i = 0; i < count; i++)
		toRead += vecs[i].iov_len;
 
	fssh_iovec* newVecs = NULL;
	if (*_numBytes < toRead) {
		// We're supposed to read less than specified by the vecs. Since
		// readv_pos() doesn't support this, we need to clone the vecs.
		newVecs = new(std::nothrow) fssh_iovec[count];
		if (!newVecs)
			return FSSH_B_NO_MEMORY;
 
		fssh_size_t newCount = 0;
		for (fssh_size_t i = 0; i < count && toRead > 0; i++) {
			fssh_size_t vecLen = fssh_min_c(vecs[i].iov_len, toRead);
			newVecs[i].iov_base = vecs[i].iov_base;
			newVecs[i].iov_len = vecLen;
			toRead -= vecLen;
			newCount++;
		}
 
		vecs = newVecs;
		count = newCount;
	}
 
	fssh_ssize_t bytesRead = fssh_readv_pos(fd, pos, vecs, count);
	delete[] newVecs;
	if (bytesRead < 0)
		return fssh_get_errno();
 
	*_numBytes = bytesRead;
	return FSSH_B_OK;
}
 
 
//! Works directly on the host's file system
extern "C" fssh_status_t
fssh_write_pages(int fd, fssh_off_t pos, const fssh_iovec *vecs,
	fssh_size_t count, fssh_size_t *_numBytes)
{
	// check how much the iovecs allow us to write
	fssh_size_t toWrite = 0;
	for (fssh_size_t i = 0; i < count; i++)
		toWrite += vecs[i].iov_len;
 
	fssh_iovec* newVecs = NULL;
	if (*_numBytes < toWrite) {
		// We're supposed to write less than specified by the vecs. Since
		// writev_pos() doesn't support this, we need to clone the vecs.
		newVecs = new(std::nothrow) fssh_iovec[count];
		if (!newVecs)
			return FSSH_B_NO_MEMORY;
 
		fssh_size_t newCount = 0;
		for (fssh_size_t i = 0; i < count && toWrite > 0; i++) {
			fssh_size_t vecLen = fssh_min_c(vecs[i].iov_len, toWrite);
			newVecs[i].iov_base = vecs[i].iov_base;
			newVecs[i].iov_len = vecLen;
			toWrite -= vecLen;
			newCount++;
		}
 
		vecs = newVecs;
		count = newCount;
	}
 
	fssh_ssize_t bytesWritten = fssh_writev_pos(fd, pos, vecs, count);
	delete[] newVecs;
	if (bytesWritten < 0)
		return fssh_get_errno();
 
	*_numBytes = bytesWritten;
	return FSSH_B_OK;
}
 
 
//! Works directly on the host's file system
extern "C" fssh_status_t
fssh_read_file_io_vec_pages(int fd, const fssh_file_io_vec *fileVecs,
	fssh_size_t fileVecCount, const fssh_iovec *vecs, fssh_size_t vecCount,
	uint32_t *_vecIndex, fssh_size_t *_vecOffset, fssh_size_t *_bytes)
{
	return common_file_io_vec_pages(fd, fileVecs, fileVecCount,
		vecs, vecCount, _vecIndex, _vecOffset, _bytes, false);
}
 
 
//! Works directly on the host's file system
extern "C" fssh_status_t
fssh_write_file_io_vec_pages(int fd, const fssh_file_io_vec *fileVecs,
	fssh_size_t fileVecCount, const fssh_iovec *vecs, fssh_size_t vecCount,
	uint32_t *_vecIndex, fssh_size_t *_vecOffset, fssh_size_t *_bytes)
{
	return common_file_io_vec_pages(fd, fileVecs, fileVecCount,
		vecs, vecCount, _vecIndex, _vecOffset, _bytes, true);
}
 
 
extern "C" fssh_status_t
fssh_entry_cache_add(fssh_dev_t mountID, fssh_ino_t dirID, const char* name,
	fssh_ino_t nodeID)
{
	// We don't implement an entry cache in the FS shell.
	return FSSH_B_OK;
}
 
 
extern "C" fssh_status_t
fssh_entry_cache_add_missing(fssh_dev_t mountID, fssh_ino_t dirID,
	const char* name)
{
	// We don't implement an entry cache in the FS shell.
	return FSSH_B_OK;
}
 
 
extern "C" fssh_status_t
fssh_entry_cache_remove(fssh_dev_t mountID, fssh_ino_t dirID, const char* name)
{
	// We don't implement an entry cache in the FS shell.
	return FSSH_B_ENTRY_NOT_FOUND;
}
 
 
//	#pragma mark - private VFS API
//	Functions the VFS exports for other parts of the kernel
 
 
/** Acquires another reference to the vnode that has to be released
 *	by calling vfs_put_vnode().
 */
 
void
vfs_acquire_vnode(void *_vnode)
{
	inc_vnode_ref_count((struct vnode *)_vnode);
}
 
 
/** This is currently called from file_cache_create() only.
 *	It's probably a temporary solution as long as devfs requires that
 *	fs_read_pages()/fs_write_pages() are called with the standard
 *	open cookie and not with a device cookie.
 *	If that's done differently, remove this call; it has no other
 *	purpose.
 */
 
fssh_status_t
vfs_get_cookie_from_fd(int fd, void **_cookie)
{
	struct file_descriptor *descriptor;
 
	descriptor = get_fd(get_current_io_context(true), fd);
	if (descriptor == NULL)
		return FSSH_B_FILE_ERROR;
 
	*_cookie = descriptor->cookie;
	return FSSH_B_OK;
}
 
 
int
vfs_get_vnode_from_fd(int fd, bool kernel, void **vnode)
{
	*vnode = get_vnode_from_fd(fd, kernel);
 
	if (*vnode == NULL)
		return FSSH_B_FILE_ERROR;
 
	return FSSH_B_NO_ERROR;
}
 
 
fssh_status_t
vfs_get_vnode_from_path(const char *path, bool kernel, void **_vnode)
{
	TRACE(("vfs_get_vnode_from_path: entry. path = '%s', kernel %d\n", path, kernel));
 
	KPath pathBuffer(FSSH_B_PATH_NAME_LENGTH + 1);
	if (pathBuffer.InitCheck() != FSSH_B_OK)
		return FSSH_B_NO_MEMORY;
 
	char *buffer = pathBuffer.LockBuffer();
	fssh_strlcpy(buffer, path, pathBuffer.BufferSize());
 
	struct vnode *vnode;
	fssh_status_t status = path_to_vnode(buffer, true, &vnode, NULL, kernel);
	if (status < FSSH_B_OK)
		return status;
 
	*_vnode = vnode;
	return FSSH_B_OK;
}
 
 
fssh_status_t
vfs_get_vnode(fssh_mount_id mountID, fssh_vnode_id vnodeID, void **_vnode)
{
	struct vnode *vnode;
 
	fssh_status_t status = get_vnode(mountID, vnodeID, &vnode, false);
	if (status < FSSH_B_OK)
		return status;
 
	*_vnode = vnode;
	return FSSH_B_OK;
}
 
 
fssh_status_t
vfs_read_pages(void *_vnode, void *cookie, fssh_off_t pos,
	const fssh_iovec *vecs, fssh_size_t count, fssh_size_t *_numBytes)
{
	struct vnode *vnode = (struct vnode *)_vnode;
 
	return FS_CALL(vnode, read_pages,
		cookie, pos, vecs, count, _numBytes);
}
 
 
fssh_status_t
vfs_write_pages(void *_vnode, void *cookie, fssh_off_t pos,
	const fssh_iovec *vecs, fssh_size_t count, fssh_size_t *_numBytes)
{
	struct vnode *vnode = (struct vnode *)_vnode;
 
	return FS_CALL(vnode, write_pages,
		cookie, pos, vecs, count, _numBytes);
}
 
 
fssh_status_t
vfs_entry_ref_to_vnode(fssh_mount_id mountID, fssh_vnode_id directoryID,
	const char *name, void **_vnode)
{
	return entry_ref_to_vnode(mountID, directoryID, name,
		(struct vnode **)_vnode);
}
 
 
void
vfs_fs_vnode_to_node_ref(void *_vnode, fssh_mount_id *_mountID,
	fssh_vnode_id *_vnodeID)
{
	struct vnode *vnode = (struct vnode *)_vnode;
 
	*_mountID = vnode->device;
	*_vnodeID = vnode->id;
}
 
 
/**	Looks up a vnode with the given mount and vnode ID.
 *	Must only be used with "in-use" vnodes as it doesn't grab a reference
 *	to the node.
 *	It's currently only be used by file_cache_create().
 */
 
fssh_status_t
vfs_lookup_vnode(fssh_mount_id mountID, fssh_vnode_id vnodeID,
	struct vnode **_vnode)
{
	fssh_mutex_lock(&sVnodeMutex);
	struct vnode *vnode = lookup_vnode(mountID, vnodeID);
	fssh_mutex_unlock(&sVnodeMutex);
 
	if (vnode == NULL)
		return FSSH_B_ERROR;
 
	*_vnode = vnode;
	return FSSH_B_OK;
}
 
 
fssh_status_t
vfs_get_fs_node_from_path(fssh_fs_volume *volume, const char *path,
	bool kernel, void **_node)
{
	TRACE(("vfs_get_fs_node_from_path(volume = %p (%ld), path = \"%s\", "
		"kernel %d)\n", volume, volume->id, path, kernel));
 
	KPath pathBuffer(FSSH_B_PATH_NAME_LENGTH + 1);
	if (pathBuffer.InitCheck() != FSSH_B_OK)
		return FSSH_B_NO_MEMORY;
 
	fs_mount *mount;
	fssh_status_t status = get_mount(volume->id, &mount);
	if (status < FSSH_B_OK)
		return status;
 
	char *buffer = pathBuffer.LockBuffer();
	fssh_strlcpy(buffer, path, pathBuffer.BufferSize());
 
	struct vnode *vnode = mount->root_vnode;
 
	if (buffer[0] == '/')
		status = path_to_vnode(buffer, true, &vnode, NULL, true);
	else {
		inc_vnode_ref_count(vnode);
			// vnode_path_to_vnode() releases a reference to the starting vnode
		status = vnode_path_to_vnode(vnode, buffer, true, 0, &vnode, NULL);
	}
 
	put_mount(mount);
 
	if (status < FSSH_B_OK)
		return status;
 
	if (vnode->device != volume->id) {
		// wrong mount ID - must not gain access on foreign file system nodes
		put_vnode(vnode);
		return FSSH_B_BAD_VALUE;
	}
 
	// Use get_vnode() to resolve the cookie for the right layer.
	status = ::fssh_get_vnode(volume, vnode->id, _node);
	put_vnode(vnode);
 
	return FSSH_B_OK;
}
 
 
/**	Finds the full path to the file that contains the module \a moduleName,
 *	puts it into \a pathBuffer, and returns FSSH_B_OK for success.
 *	If \a pathBuffer was too small, it returns \c FSSH_B_BUFFER_OVERFLOW,
 *	\c FSSH_B_ENTRY_NOT_FOUNT if no file could be found.
 *	\a pathBuffer is clobbered in any case and must not be relied on if this
 *	functions returns unsuccessfully.
 */
 
fssh_status_t
vfs_get_module_path(const char *basePath, const char *moduleName, char *pathBuffer,
	fssh_size_t bufferSize)
{
	struct vnode *dir, *file;
	fssh_status_t status;
	fssh_size_t length;
	char *path;
 
	if (bufferSize == 0 || fssh_strlcpy(pathBuffer, basePath, bufferSize) >= bufferSize)
		return FSSH_B_BUFFER_OVERFLOW;
 
	status = path_to_vnode(pathBuffer, true, &dir, NULL, true);
	if (status < FSSH_B_OK)
		return status;
 
	// the path buffer had been clobbered by the above call
	length = fssh_strlcpy(pathBuffer, basePath, bufferSize);
	if (pathBuffer[length - 1] != '/')
		pathBuffer[length++] = '/';
 
	path = pathBuffer + length;
	bufferSize -= length;
 
	while (moduleName) {
		char *nextPath = fssh_strchr(moduleName, '/');
		if (nextPath == NULL)
			length = fssh_strlen(moduleName);
		else {
			length = nextPath - moduleName;
			nextPath++;
		}
 
		if (length + 1 >= bufferSize) {
			status = FSSH_B_BUFFER_OVERFLOW;
			goto err;
		}
 
		fssh_memcpy(path, moduleName, length);
		path[length] = '\0';
		moduleName = nextPath;
 
		status = vnode_path_to_vnode(dir, path, true, 0, &file, NULL);
		if (status < FSSH_B_OK) {
			// vnode_path_to_vnode() has already released the reference to dir
			return status;
		}
 
		if (FSSH_S_ISDIR(file->type)) {
			// goto the next directory
			path[length] = '/';
			path[length + 1] = '\0';
			path += length + 1;
			bufferSize -= length + 1;
 
			dir = file;
		} else if (FSSH_S_ISREG(file->type)) {
			// it's a file so it should be what we've searched for
			put_vnode(file);
 
			return FSSH_B_OK;
		} else {
			TRACE(("vfs_get_module_path(): something is strange here: %d...\n", file->type));
			status = FSSH_B_ERROR;
			dir = file;
			goto err;
		}
	}
 
	// if we got here, the moduleName just pointed to a directory, not to
	// a real module - what should we do in this case?
	status = FSSH_B_ENTRY_NOT_FOUND;
 
err:
	put_vnode(dir);
	return status;
}
 
 
/**	\brief Normalizes a given path.
 *
 *	The path must refer to an existing or non-existing entry in an existing
 *	directory, that is chopping off the leaf component the remaining path must
 *	refer to an existing directory.
 *
 *	The returned will be canonical in that it will be absolute, will not
 *	contain any "." or ".." components or duplicate occurrences of '/'s,
 *	and none of the directory components will by symbolic links.
 *
 *	Any two paths referring to the same entry, will result in the same
 *	normalized path (well, that is pretty much the definition of `normalized',
 *	isn't it :-).
 *
 *	\param path The path to be normalized.
 *	\param buffer The buffer into which the normalized path will be written.
 *	\param bufferSize The size of \a buffer.
 *	\param kernel \c true, if the IO context of the kernel shall be used,
 *		   otherwise that of the team this thread belongs to. Only relevant,
 *		   if the path is relative (to get the CWD).
 *	\return \c FSSH_B_OK if everything went fine, another error code otherwise.
 */
 
fssh_status_t
vfs_normalize_path(const char *path, char *buffer, fssh_size_t bufferSize,
	bool kernel)
{
	if (!path || !buffer || bufferSize < 1)
		return FSSH_B_BAD_VALUE;
 
	TRACE(("vfs_normalize_path(`%s')\n", path));
 
	// copy the supplied path to the stack, so it can be modified
	KPath mutablePathBuffer(FSSH_B_PATH_NAME_LENGTH + 1);
	if (mutablePathBuffer.InitCheck() != FSSH_B_OK)
		return FSSH_B_NO_MEMORY;
 
	char *mutablePath = mutablePathBuffer.LockBuffer();
	if (fssh_strlcpy(mutablePath, path, FSSH_B_PATH_NAME_LENGTH) >= FSSH_B_PATH_NAME_LENGTH)
		return FSSH_B_NAME_TOO_LONG;
 
	// get the dir vnode and the leaf name
	struct vnode *dirNode;
	char leaf[FSSH_B_FILE_NAME_LENGTH];
	fssh_status_t error = path_to_dir_vnode(mutablePath, &dirNode, leaf, kernel);
	if (error != FSSH_B_OK) {
		TRACE(("vfs_normalize_path(): failed to get dir vnode: %s\n", strerror(error)));
		return error;
	}
 
	// if the leaf is "." or "..", we directly get the correct directory
	// vnode and ignore the leaf later
	bool isDir = (fssh_strcmp(leaf, ".") == 0 || fssh_strcmp(leaf, "..") == 0);
	if (isDir)
		error = vnode_path_to_vnode(dirNode, leaf, false, 0, &dirNode, NULL);
	if (error != FSSH_B_OK) {
		TRACE(("vfs_normalize_path(): failed to get dir vnode for \".\" or \"..\": %s\n",
			strerror(error)));
		return error;
	}
 
	// get the directory path
	error = dir_vnode_to_path(dirNode, buffer, bufferSize);
	put_vnode(dirNode);
	if (error < FSSH_B_OK) {
		TRACE(("vfs_normalize_path(): failed to get dir path: %s\n", strerror(error)));
		return error;
	}
 
	// append the leaf name
	if (!isDir) {
		// insert a directory separator only if this is not the file system root
		if ((fssh_strcmp(buffer, "/") != 0
			 && fssh_strlcat(buffer, "/", bufferSize) >= bufferSize)
			|| fssh_strlcat(buffer, leaf, bufferSize) >= bufferSize) {
			return FSSH_B_NAME_TOO_LONG;
		}
	}
 
	TRACE(("vfs_normalize_path() -> `%s'\n", buffer));
	return FSSH_B_OK;
}
 
 
void
vfs_put_vnode(void *_vnode)
{
	put_vnode((struct vnode *)_vnode);
}
 
 
fssh_status_t
vfs_get_cwd(fssh_mount_id *_mountID, fssh_vnode_id *_vnodeID)
{
	// Get current working directory from io context
	struct io_context *context = get_current_io_context(false);
	fssh_status_t status = FSSH_B_OK;
 
	fssh_mutex_lock(&context->io_mutex);
 
	if (context->cwd != NULL) {
		*_mountID = context->cwd->device;
		*_vnodeID = context->cwd->id;
	} else
		status = FSSH_B_ERROR;
 
	fssh_mutex_unlock(&context->io_mutex);
	return status;
}
 
 
fssh_status_t
vfs_get_file_map(void *_vnode, fssh_off_t offset, fssh_size_t size,
	fssh_file_io_vec *vecs, fssh_size_t *_count)
{
	struct vnode *vnode = (struct vnode *)_vnode;
 
	FUNCTION(("vfs_get_file_map: vnode %p, vecs %p, offset %lld, size = %u\n", vnode, vecs, offset, (unsigned)size));
 
	return FS_CALL(vnode, get_file_map, offset, size, vecs, _count);
}
 
 
fssh_status_t
vfs_stat_vnode(void *_vnode, struct fssh_stat *stat)
{
	struct vnode *vnode = (struct vnode *)_vnode;
 
	fssh_status_t status = FS_CALL(vnode, read_stat, stat);
 
	// fill in the st_dev and st_ino fields
	if (status == FSSH_B_OK) {
		stat->fssh_st_dev = vnode->device;
		stat->fssh_st_ino = vnode->id;
	}
 
	return status;
}
 
 
fssh_status_t
vfs_get_vnode_name(void *_vnode, char *name, fssh_size_t nameSize)
{
	return get_vnode_name((struct vnode *)_vnode, NULL, name, nameSize);
}
 
 
fssh_status_t
vfs_entry_ref_to_path(fssh_dev_t device, fssh_ino_t inode, const char *leaf,
	bool kernel, char *path, fssh_size_t pathLength)
{
	struct vnode *vnode;
	fssh_status_t status;
 
	// filter invalid leaf names
	if (leaf != NULL && (leaf[0] == '\0' || fssh_strchr(leaf, '/')))
		return FSSH_B_BAD_VALUE;
 
	// get the vnode matching the dir's node_ref
	if (leaf && (fssh_strcmp(leaf, ".") == 0 || fssh_strcmp(leaf, "..") == 0)) {
		// special cases "." and "..": we can directly get the vnode of the
		// referenced directory
		status = entry_ref_to_vnode(device, inode, leaf, &vnode);
		leaf = NULL;
	} else
		status = get_vnode(device, inode, &vnode, false);
	if (status < FSSH_B_OK)
		return status;
 
	// get the directory path
	status = dir_vnode_to_path(vnode, path, pathLength);
	put_vnode(vnode);
		// we don't need the vnode anymore
	if (status < FSSH_B_OK)
		return status;
 
	// append the leaf name
	if (leaf) {
		// insert a directory separator if this is not the file system root
		if ((fssh_strcmp(path, "/") && fssh_strlcat(path, "/", pathLength)
				>= pathLength)
			|| fssh_strlcat(path, leaf, pathLength) >= pathLength) {
			return FSSH_B_NAME_TOO_LONG;
		}
	}
 
	return FSSH_B_OK;
}
 
 
/**	If the given descriptor locked its vnode, that lock will be released.
 */
 
void
vfs_unlock_vnode_if_locked(struct file_descriptor *descriptor)
{
	struct vnode *vnode = fd_vnode(descriptor);
 
	if (vnode != NULL && vnode->mandatory_locked_by == descriptor)
		vnode->mandatory_locked_by = NULL;
}
 
 
/**	Closes all file descriptors of the specified I/O context that
 *	don't have the FSSH_O_CLOEXEC flag set.
 */
 
void
vfs_exec_io_context(void *_context)
{
	struct io_context *context = (struct io_context *)_context;
	uint32_t i;
 
	for (i = 0; i < context->table_size; i++) {
		fssh_mutex_lock(&context->io_mutex);
 
		struct file_descriptor *descriptor = context->fds[i];
		bool remove = false;
 
		if (descriptor != NULL && fd_close_on_exec(context, i)) {
			context->fds[i] = NULL;
			context->num_used_fds--;
 
			remove = true;
		}
 
		fssh_mutex_unlock(&context->io_mutex);
 
		if (remove) {
			close_fd(descriptor);
			put_fd(descriptor);
		}
	}
}
 
 
/** Sets up a new io_control structure, and inherits the properties
 *	of the parent io_control if it is given.
 */
 
void *
vfs_new_io_context(void *_parentContext)
{
	fssh_size_t tableSize;
	struct io_context *context;
	struct io_context *parentContext;
 
	context = (io_context *)malloc(sizeof(struct io_context));
	if (context == NULL)
		return NULL;
 
	fssh_memset(context, 0, sizeof(struct io_context));
 
	parentContext = (struct io_context *)_parentContext;
	if (parentContext)
		tableSize = parentContext->table_size;
	else
		tableSize = DEFAULT_FD_TABLE_SIZE;
 
	// allocate space for FDs and their close-on-exec flag
	context->fds = (file_descriptor **)malloc(sizeof(struct file_descriptor *) * tableSize
		+ (tableSize + 7) / 8);
	if (context->fds == NULL) {
		free(context);
		return NULL;
	}
 
	fssh_memset(context->fds, 0, sizeof(struct file_descriptor *) * tableSize
		+ (tableSize + 7) / 8);
	context->fds_close_on_exec = (uint8_t *)(context->fds + tableSize);
 
	fssh_mutex_init(&context->io_mutex, "I/O context");
 
	// Copy all parent files which don't have the FSSH_O_CLOEXEC flag set
 
	if (parentContext) {
		fssh_size_t i;
 
		fssh_mutex_lock(&parentContext->io_mutex);
 
		context->cwd = parentContext->cwd;
		if (context->cwd)
			inc_vnode_ref_count(context->cwd);
 
		for (i = 0; i < tableSize; i++) {
			struct file_descriptor *descriptor = parentContext->fds[i];
 
			if (descriptor != NULL && !fd_close_on_exec(parentContext, i)) {
				context->fds[i] = descriptor;
				context->num_used_fds++;
				fssh_atomic_add(&descriptor->ref_count, 1);
				fssh_atomic_add(&descriptor->open_count, 1);
			}
		}
 
		fssh_mutex_unlock(&parentContext->io_mutex);
	} else {
		context->cwd = sRoot;
 
		if (context->cwd)
			inc_vnode_ref_count(context->cwd);
	}
 
	context->table_size = tableSize;
 
	return context;
}
 
 
fssh_status_t
vfs_free_io_context(void *_ioContext)
{
	struct io_context *context = (struct io_context *)_ioContext;
	uint32_t i;
 
	if (context->cwd)
		dec_vnode_ref_count(context->cwd, false);
 
	fssh_mutex_lock(&context->io_mutex);
 
	for (i = 0; i < context->table_size; i++) {
		if (struct file_descriptor *descriptor = context->fds[i]) {
			close_fd(descriptor);
			put_fd(descriptor);
		}
	}
 
	fssh_mutex_destroy(&context->io_mutex);
 
	free(context->fds);
	free(context);
 
	return FSSH_B_OK;
}
 
 
fssh_status_t
vfs_init(kernel_args *args)
{
	sVnodeTable = hash_init(VNODE_HASH_TABLE_SIZE, fssh_offsetof(struct vnode, next),
		&vnode_compare, &vnode_hash);
	if (sVnodeTable == NULL)
		fssh_panic("vfs_init: error creating vnode hash table\n");
 
	list_init_etc(&sUnusedVnodeList, fssh_offsetof(struct vnode, unused_link));
 
	sMountsTable = hash_init(MOUNTS_HASH_TABLE_SIZE, fssh_offsetof(struct fs_mount, next),
		&mount_compare, &mount_hash);
	if (sMountsTable == NULL)
		fssh_panic("vfs_init: error creating mounts hash table\n");
 
	sRoot = NULL;
 
	fssh_mutex_init(&sFileSystemsMutex, "vfs_lock");
	fssh_recursive_lock_init(&sMountOpLock, "vfs_mount_op_lock");
	fssh_mutex_init(&sMountMutex, "vfs_mount_lock");
	fssh_mutex_init(&sVnodeCoveredByMutex, "vfs_vnode_covered_by_lock");
	fssh_mutex_init(&sVnodeMutex, "vfs_vnode_lock");
 
	if (block_cache_init() != FSSH_B_OK)
		return FSSH_B_ERROR;
 
	return file_cache_init();
}
 
 
//	#pragma mark -
//	The filetype-dependent implementations (fd_ops + open/create/rename/remove, ...)
 
 
/** Calls fs_open() on the given vnode and returns a new
 *	file descriptor for it
 */
 
static int
create_vnode(struct vnode *directory, const char *name, int openMode, int perms, bool kernel)
{
	struct vnode *vnode;
	void *cookie;
	fssh_vnode_id newID;
	int status;
 
	if (!HAS_FS_CALL(directory, create))
		return FSSH_EROFS;
 
	status = FS_CALL(directory, create, name, openMode, perms, &cookie, &newID);
	if (status < FSSH_B_OK)
		return status;
 
	fssh_mutex_lock(&sVnodeMutex);
	vnode = lookup_vnode(directory->device, newID);
	fssh_mutex_unlock(&sVnodeMutex);
 
	if (vnode == NULL) {
		fssh_dprintf("vfs: fs_create() returned success but there is no vnode!");
		return FSSH_EINVAL;
	}
 
	if ((status = get_new_fd(FDTYPE_FILE, NULL, vnode, cookie, openMode, kernel)) >= 0)
		return status;
 
	// something went wrong, clean up
 
	FS_CALL(vnode, close, cookie);
	FS_CALL(vnode, free_cookie, cookie);
	put_vnode(vnode);
 
	FS_CALL(directory, unlink, name);
 
	return status;
}
 
 
/** Calls fs_open() on the given vnode and returns a new
 *	file descriptor for it
 */
 
static int
open_vnode(struct vnode *vnode, int openMode, bool kernel)
{
	void *cookie;
	int status;
 
	status = FS_CALL(vnode, open, openMode, &cookie);
	if (status < 0)
		return status;
 
	status = get_new_fd(FDTYPE_FILE, NULL, vnode, cookie, openMode, kernel);
	if (status < 0) {
		FS_CALL(vnode, close, cookie);
		FS_CALL(vnode, free_cookie, cookie);
	}
	return status;
}
 
 
/** Calls fs open_dir() on the given vnode and returns a new
 *	file descriptor for it
 */
 
static int
open_dir_vnode(struct vnode *vnode, bool kernel)
{
	void *cookie;
	int status;
 
	status = FS_CALL(vnode, open_dir, &cookie);
	if (status < FSSH_B_OK)
		return status;
 
	// file is opened, create a fd
	status = get_new_fd(FDTYPE_DIR, NULL, vnode, cookie, 0, kernel);
	if (status >= 0)
		return status;
 
	FS_CALL(vnode, close_dir, cookie);
	FS_CALL(vnode, free_dir_cookie, cookie);
 
	return status;
}
 
 
/** Calls fs open_attr_dir() on the given vnode and returns a new
 *	file descriptor for it.
 *	Used by attr_dir_open(), and attr_dir_open_fd().
 */
 
static int
open_attr_dir_vnode(struct vnode *vnode, bool kernel)
{
	void *cookie;
	int status;
 
	if (!HAS_FS_CALL(vnode, open_attr_dir))
		return FSSH_EOPNOTSUPP;
 
	status = FS_CALL(vnode, open_attr_dir, &cookie);
	if (status < 0)
		return status;
 
	// file is opened, create a fd
	status = get_new_fd(FDTYPE_ATTR_DIR, NULL, vnode, cookie, 0, kernel);
	if (status >= 0)
		return status;
 
	FS_CALL(vnode, close_attr_dir, cookie);
	FS_CALL(vnode, free_attr_dir_cookie, cookie);
 
	return status;
}
 
 
static int
file_create_entry_ref(fssh_mount_id mountID, fssh_vnode_id directoryID, const char *name, int openMode, int perms, bool kernel)
{
	struct vnode *directory;
	int status;
 
	FUNCTION(("file_create_entry_ref: name = '%s', omode %x, perms %d, kernel %d\n", name, openMode, perms, kernel));
 
	// get directory to put the new file in
	status = get_vnode(mountID, directoryID, &directory, false);
	if (status < FSSH_B_OK)
		return status;
 
	status = create_vnode(directory, name, openMode, perms, kernel);
	put_vnode(directory);
 
	return status;
}
 
 
static int
file_create(int fd, char *path, int openMode, int perms, bool kernel)
{
	char name[FSSH_B_FILE_NAME_LENGTH];
	struct vnode *directory;
	int status;
 
	FUNCTION(("file_create: path '%s', omode %x, perms %d, kernel %d\n", path, openMode, perms, kernel));
 
	// get directory to put the new file in
	status = fd_and_path_to_dir_vnode(fd, path, &directory, name, kernel);
	if (status < 0)
		return status;
 
	status = create_vnode(directory, name, openMode, perms, kernel);
 
	put_vnode(directory);
	return status;
}
 
 
static int
file_open_entry_ref(fssh_mount_id mountID, fssh_vnode_id directoryID, const char *name, int openMode, bool kernel)
{
	struct vnode *vnode;
	int status;
 
	if (name == NULL || *name == '\0')
		return FSSH_B_BAD_VALUE;
 
	FUNCTION(("file_open_entry_ref(ref = (%ld, %Ld, %s), openMode = %d)\n",
		mountID, directoryID, name, openMode));
 
	// get the vnode matching the entry_ref
	status = entry_ref_to_vnode(mountID, directoryID, name, &vnode);
	if (status < FSSH_B_OK)
		return status;
 
	status = open_vnode(vnode, openMode, kernel);
	if (status < FSSH_B_OK)
		put_vnode(vnode);
 
	return status;
}
 
 
static int
file_open(int fd, char *path, int openMode, bool kernel)
{
	int status = FSSH_B_OK;
	bool traverse = ((openMode & FSSH_O_NOTRAVERSE) == 0);
 
	FUNCTION(("file_open: fd: %d, entry path = '%s', omode %d, kernel %d\n",
		fd, path, openMode, kernel));
 
	// get the vnode matching the vnode + path combination
	struct vnode *vnode = NULL;
	fssh_vnode_id parentID;
	status = fd_and_path_to_vnode(fd, path, traverse, &vnode, &parentID, kernel);
	if (status != FSSH_B_OK)
		return status;
 
	// open the vnode
	status = open_vnode(vnode, openMode, kernel);
	// put only on error -- otherwise our reference was transferred to the FD
	if (status < FSSH_B_OK)
		put_vnode(vnode);
 
	return status;
}
 
 
static fssh_status_t
file_close(struct file_descriptor *descriptor)
{
	struct vnode *vnode = descriptor->u.vnode;
	fssh_status_t status = FSSH_B_OK;
 
	FUNCTION(("file_close(descriptor = %p)\n", descriptor));
 
	if (HAS_FS_CALL(vnode, close))
		status = FS_CALL(vnode, close, descriptor->cookie);
 
	return status;
}
 
 
static void
file_free_fd(struct file_descriptor *descriptor)
{
	struct vnode *vnode = descriptor->u.vnode;
 
	if (vnode != NULL) {
		FS_CALL(vnode, free_cookie, descriptor->cookie);
		put_vnode(vnode);
	}
}
 
 
static fssh_status_t
file_read(struct file_descriptor *descriptor, fssh_off_t pos, void *buffer, fssh_size_t *length)
{
	struct vnode *vnode = descriptor->u.vnode;
 
	FUNCTION(("file_read: buf %p, pos %Ld, len %p = %ld\n", buffer, pos, length, *length));
	return FS_CALL(vnode, read, descriptor->cookie, pos, buffer, length);
}
 
 
static fssh_status_t
file_write(struct file_descriptor *descriptor, fssh_off_t pos, const void *buffer, fssh_size_t *length)
{
	struct vnode *vnode = descriptor->u.vnode;
 
	FUNCTION(("file_write: buf %p, pos %Ld, len %p\n", buffer, pos, length));
	return FS_CALL(vnode, write, descriptor->cookie, pos, buffer, length);
}
 
 
static fssh_off_t
file_seek(struct file_descriptor *descriptor, fssh_off_t pos, int seekType)
{
	fssh_off_t offset;
 
	FUNCTION(("file_seek(pos = %Ld, seekType = %d)\n", pos, seekType));
	// ToDo: seek should fail for pipes and FIFOs...
 
	switch (seekType) {
		case FSSH_SEEK_SET:
			offset = 0;
			break;
		case FSSH_SEEK_CUR:
			offset = descriptor->pos;
			break;
		case FSSH_SEEK_END:
		{
			struct vnode *vnode = descriptor->u.vnode;
			struct fssh_stat stat;
			fssh_status_t status;
 
			if (!HAS_FS_CALL(vnode, read_stat))
				return FSSH_EOPNOTSUPP;
 
			status = FS_CALL(vnode, read_stat, &stat);
			if (status < FSSH_B_OK)
				return status;
 
			offset = stat.fssh_st_size;
			break;
		}
		default:
			return FSSH_B_BAD_VALUE;
	}
 
	// assumes fssh_off_t is 64 bits wide
	if (offset > 0 && LLONG_MAX - offset < pos)
		return FSSH_EOVERFLOW;
 
	pos += offset;
	if (pos < 0)
		return FSSH_B_BAD_VALUE;
 
	return descriptor->pos = pos;
}
 
 
static fssh_status_t
dir_create_entry_ref(fssh_mount_id mountID, fssh_vnode_id parentID, const char *name, int perms, bool kernel)
{
	struct vnode *vnode;
	fssh_status_t status;
 
	if (name == NULL || *name == '\0')
		return FSSH_B_BAD_VALUE;
 
	FUNCTION(("dir_create_entry_ref(dev = %ld, ino = %Ld, name = '%s', perms = %d)\n", mountID, parentID, name, perms));
 
	status = get_vnode(mountID, parentID, &vnode, kernel);
	if (status < FSSH_B_OK)
		return status;
 
	if (HAS_FS_CALL(vnode, create_dir))
		status = FS_CALL(vnode, create_dir, name, perms);
	else
		status = FSSH_EROFS;
 
	put_vnode(vnode);
	return status;
}
 
 
static fssh_status_t
dir_create(int fd, char *path, int perms, bool kernel)
{
	char filename[FSSH_B_FILE_NAME_LENGTH];
	struct vnode *vnode;
	fssh_status_t status;
 
	FUNCTION(("dir_create: path '%s', perms %d, kernel %d\n", path, perms, kernel));
 
	status = fd_and_path_to_dir_vnode(fd, path, &vnode, filename, kernel);
	if (status < 0)
		return status;
 
	if (HAS_FS_CALL(vnode, create_dir))
		status = FS_CALL(vnode, create_dir, filename, perms);
	else
		status = FSSH_EROFS;
 
	put_vnode(vnode);
	return status;
}
 
 
static int
dir_open_entry_ref(fssh_mount_id mountID, fssh_vnode_id parentID, const char *name, bool kernel)
{
	struct vnode *vnode;
	int status;
 
	FUNCTION(("dir_open_entry_ref()\n"));
 
	if (name && *name == '\0')
		return FSSH_B_BAD_VALUE;
 
	// get the vnode matching the entry_ref/node_ref
	if (name)
		status = entry_ref_to_vnode(mountID, parentID, name, &vnode);
	else
		status = get_vnode(mountID, parentID, &vnode, false);
	if (status < FSSH_B_OK)
		return status;
 
	status = open_dir_vnode(vnode, kernel);
	if (status < FSSH_B_OK)
		put_vnode(vnode);
 
	return status;
}
 
 
static int
dir_open(int fd, char *path, bool kernel)
{
	int status = FSSH_B_OK;
 
	FUNCTION(("dir_open: fd: %d, entry path = '%s', kernel %d\n", fd, path, kernel));
 
	// get the vnode matching the vnode + path combination
	struct vnode *vnode = NULL;
	fssh_vnode_id parentID;
	status = fd_and_path_to_vnode(fd, path, true, &vnode, &parentID, kernel);
	if (status != FSSH_B_OK)
		return status;
 
	// open the dir
	status = open_dir_vnode(vnode, kernel);
	if (status < FSSH_B_OK)
		put_vnode(vnode);
 
	return status;
}
 
 
static fssh_status_t
dir_close(struct file_descriptor *descriptor)
{
	struct vnode *vnode = descriptor->u.vnode;
 
	FUNCTION(("dir_close(descriptor = %p)\n", descriptor));
 
	if (HAS_FS_CALL(vnode, close_dir))
		return FS_CALL(vnode, close_dir, descriptor->cookie);
 
	return FSSH_B_OK;
}
 
 
static void
dir_free_fd(struct file_descriptor *descriptor)
{
	struct vnode *vnode = descriptor->u.vnode;
 
	if (vnode != NULL) {
		FS_CALL(vnode, free_dir_cookie, descriptor->cookie);
		put_vnode(vnode);
	}
}
 
 
static fssh_status_t
dir_read(struct file_descriptor *descriptor, struct fssh_dirent *buffer,
	fssh_size_t bufferSize, uint32_t *_count)
{
	return dir_read(descriptor->u.vnode, descriptor->cookie, buffer, bufferSize, _count);
}
 
 
static void
fix_dirent(struct vnode *parent, struct fssh_dirent *entry)
{
	// set d_pdev and d_pino
	entry->d_pdev = parent->device;
	entry->d_pino = parent->id;
 
	// If this is the ".." entry and the directory is the root of a FS,
	// we need to replace d_dev and d_ino with the actual values.
	if (fssh_strcmp(entry->d_name, "..") == 0
		&& parent->mount->root_vnode == parent
		&& parent->mount->covers_vnode) {
		inc_vnode_ref_count(parent);
			// vnode_path_to_vnode() puts the node
 
		// ".." is guaranteed to to be clobbered by this call
		struct vnode *vnode;
		fssh_status_t status = vnode_path_to_vnode(parent, (char*)"..", false,
			0, &vnode, NULL);
 
		if (status == FSSH_B_OK) {
			entry->d_dev = vnode->device;
			entry->d_ino = vnode->id;
		}
	} else {
		// resolve mount points
		struct vnode *vnode = NULL;
		fssh_status_t status = get_vnode(entry->d_dev, entry->d_ino, &vnode, false);
		if (status != FSSH_B_OK)
			return;
 
		fssh_mutex_lock(&sVnodeCoveredByMutex);
		if (vnode->covered_by) {
			entry->d_dev = vnode->covered_by->device;
			entry->d_ino = vnode->covered_by->id;
		}
		fssh_mutex_unlock(&sVnodeCoveredByMutex);
 
		put_vnode(vnode);
	}
}
 
 
static fssh_status_t
dir_read(struct vnode *vnode, void *cookie, struct fssh_dirent *buffer,
	fssh_size_t bufferSize, uint32_t *_count)
{
	if (!HAS_FS_CALL(vnode, read_dir))
		return FSSH_EOPNOTSUPP;
 
	fssh_status_t error = FS_CALL(vnode, read_dir,cookie,buffer,bufferSize,_count);
	if (error != FSSH_B_OK)
		return error;
 
	// we need to adjust the read dirents
	if (*_count > 0) {
		// XXX: Currently reading only one dirent is supported. Make this a loop!
		fix_dirent(vnode, buffer);
	}
 
	return error;
}
 
 
static fssh_status_t
dir_rewind(struct file_descriptor *descriptor)
{
	struct vnode *vnode = descriptor->u.vnode;
 
	if (HAS_FS_CALL(vnode, rewind_dir))
		return FS_CALL(vnode, rewind_dir,descriptor->cookie);
 
	return FSSH_EOPNOTSUPP;
}
 
 
static fssh_status_t
dir_remove(int fd, char *path, bool kernel)
{
	char name[FSSH_B_FILE_NAME_LENGTH];
	struct vnode *directory;
	fssh_status_t status;
 
	if (path != NULL) {
		// we need to make sure our path name doesn't stop with "/", ".", or ".."
		char *lastSlash = fssh_strrchr(path, '/');
		if (lastSlash != NULL) {
			char *leaf = lastSlash + 1;
			if (!fssh_strcmp(leaf, ".."))
				return FSSH_B_NOT_ALLOWED;
 
			// omit multiple slashes
			while (lastSlash > path && lastSlash[-1] == '/') {
				lastSlash--;
			}
 
			if (!leaf[0]
				|| !fssh_strcmp(leaf, ".")) {
				// "name/" -> "name", or "name/." -> "name"
				lastSlash[0] = '\0';
			}
		} else if (!fssh_strcmp(path, ".."))
			return FSSH_B_NOT_ALLOWED;
	}
 
	status = fd_and_path_to_dir_vnode(fd, path, &directory, name, kernel);
	if (status < FSSH_B_OK)
		return status;
 
	if (HAS_FS_CALL(directory, remove_dir)) {
		status = FS_CALL(directory, remove_dir, name);
	} else
		status = FSSH_EROFS;
 
	put_vnode(directory);
	return status;
}
 
 
static fssh_status_t
common_ioctl(struct file_descriptor *descriptor, uint32_t op, void *buffer,
	fssh_size_t length)
{
	struct vnode *vnode = descriptor->u.vnode;
 
	if (HAS_FS_CALL(vnode, ioctl)) {
		return FS_CALL(vnode, ioctl,
			descriptor->cookie, op, buffer, length);
	}
 
	return FSSH_EOPNOTSUPP;
}
 
 
static fssh_status_t
common_fcntl(int fd, int op, uint32_t argument, bool kernel)
{
	struct file_descriptor *descriptor;
	struct vnode *vnode;
	fssh_status_t status;
 
	FUNCTION(("common_fcntl(fd = %d, op = %d, argument = %lx, %s)\n",
		fd, op, argument, kernel ? "kernel" : "user"));
 
	descriptor = get_fd_and_vnode(fd, &vnode, kernel);
	if (descriptor == NULL)
		return FSSH_B_FILE_ERROR;
 
	switch (op) {
		case FSSH_F_SETFD:
		{
			struct io_context *context = get_current_io_context(kernel);
			// Set file descriptor flags
 
			// FSSH_O_CLOEXEC is the only flag available at this time
			fssh_mutex_lock(&context->io_mutex);
			fd_set_close_on_exec(context, fd, argument == FSSH_FD_CLOEXEC);
			fssh_mutex_unlock(&context->io_mutex);
 
			status = FSSH_B_OK;
			break;
		}
 
		case FSSH_F_GETFD:
		{
			struct io_context *context = get_current_io_context(kernel);
 
			// Get file descriptor flags
			fssh_mutex_lock(&context->io_mutex);
			status = fd_close_on_exec(context, fd) ? FSSH_FD_CLOEXEC : 0;
			fssh_mutex_unlock(&context->io_mutex);
			break;
		}
 
		case FSSH_F_SETFL:
			// Set file descriptor open mode
			if (HAS_FS_CALL(vnode, set_flags)) {
				// we only accept changes to FSSH_O_APPEND and FSSH_O_NONBLOCK
				argument &= FSSH_O_APPEND | FSSH_O_NONBLOCK;
 
				status = FS_CALL(vnode, set_flags, descriptor->cookie, (int)argument);
				if (status == FSSH_B_OK) {
					// update this descriptor's open_mode field
					descriptor->open_mode = (descriptor->open_mode & ~(FSSH_O_APPEND | FSSH_O_NONBLOCK))
						| argument;
				}
			} else
				status = FSSH_EOPNOTSUPP;
			break;
 
		case FSSH_F_GETFL:
			// Get file descriptor open mode
			status = descriptor->open_mode;
			break;
 
		case FSSH_F_DUPFD:
		{
			struct io_context *context = get_current_io_context(kernel);
 
			status = new_fd_etc(context, descriptor, (int)argument);
			if (status >= 0) {
				fssh_mutex_lock(&context->io_mutex);
				fd_set_close_on_exec(context, fd, false);
				fssh_mutex_unlock(&context->io_mutex);
 
				fssh_atomic_add(&descriptor->ref_count, 1);
			}
			break;
		}
 
		case FSSH_F_GETLK:
		case FSSH_F_SETLK:
		case FSSH_F_SETLKW:
			status = FSSH_B_BAD_VALUE;
			break;
 
		// ToDo: add support for more ops?
 
		default:
			status = FSSH_B_BAD_VALUE;
	}
 
	put_fd(descriptor);
	return status;
}
 
 
static fssh_status_t
common_sync(int fd, bool kernel)
{
	struct file_descriptor *descriptor;
	struct vnode *vnode;
	fssh_status_t status;
 
	FUNCTION(("common_fsync: entry. fd %d kernel %d\n", fd, kernel));
 
	descriptor = get_fd_and_vnode(fd, &vnode, kernel);
	if (descriptor == NULL)
		return FSSH_B_FILE_ERROR;
 
	if (HAS_FS_CALL(vnode, fsync))
		status = FS_CALL_NO_PARAMS(vnode, fsync);
	else
		status = FSSH_EOPNOTSUPP;
 
	put_fd(descriptor);
	return status;
}
 
 
static fssh_status_t
common_lock_node(int fd, bool kernel)
{
	struct file_descriptor *descriptor;
	struct vnode *vnode;
 
	descriptor = get_fd_and_vnode(fd, &vnode, kernel);
	if (descriptor == NULL)
		return FSSH_B_FILE_ERROR;
 
	fssh_status_t status = FSSH_B_OK;
 
	// We need to set the locking atomically - someone
	// else might set one at the same time
#ifdef __x86_64__
	if (fssh_atomic_test_and_set64((int64_t *)&vnode->mandatory_locked_by,
			(fssh_addr_t)descriptor, 0) != 0)
#else
	if (fssh_atomic_test_and_set((int32_t *)&vnode->mandatory_locked_by,
			(fssh_addr_t)descriptor, 0) != 0)
#endif
		status = FSSH_B_BUSY;
 
	put_fd(descriptor);
	return status;
}
 
 
static fssh_status_t
common_unlock_node(int fd, bool kernel)
{
	struct file_descriptor *descriptor;
	struct vnode *vnode;
 
	descriptor = get_fd_and_vnode(fd, &vnode, kernel);
	if (descriptor == NULL)
		return FSSH_B_FILE_ERROR;
 
	fssh_status_t status = FSSH_B_OK;
 
	// We need to set the locking atomically - someone
	// else might set one at the same time
#ifdef __x86_64__
	if (fssh_atomic_test_and_set64((int64_t *)&vnode->mandatory_locked_by,
			0, (fssh_addr_t)descriptor) != (int64_t)descriptor)
#else
	if (fssh_atomic_test_and_set((int32_t *)&vnode->mandatory_locked_by,
			0, (fssh_addr_t)descriptor) != (int32_t)descriptor)
#endif
		status = FSSH_B_BAD_VALUE;
 
	put_fd(descriptor);
	return status;
}
 
 
static fssh_status_t
common_read_link(int fd, char *path, char *buffer, fssh_size_t *_bufferSize,
	bool kernel)
{
	struct vnode *vnode;
	fssh_status_t status;
 
	status = fd_and_path_to_vnode(fd, path, false, &vnode, NULL, kernel);
	if (status < FSSH_B_OK)
		return status;
 
	if (HAS_FS_CALL(vnode, read_symlink)) {
		status = FS_CALL(vnode, read_symlink, buffer, _bufferSize);
	} else
		status = FSSH_B_BAD_VALUE;
 
	put_vnode(vnode);
	return status;
}
 
 
static fssh_status_t
common_create_symlink(int fd, char *path, const char *toPath, int mode,
	bool kernel)
{
	// path validity checks have to be in the calling function!
	char name[FSSH_B_FILE_NAME_LENGTH];
	struct vnode *vnode;
	fssh_status_t status;
 
	FUNCTION(("common_create_symlink(fd = %d, path = %s, toPath = %s, mode = %d, kernel = %d)\n", fd, path, toPath, mode, kernel));
 
	status = fd_and_path_to_dir_vnode(fd, path, &vnode, name, kernel);
	if (status < FSSH_B_OK)
		return status;
 
	if (HAS_FS_CALL(vnode, create_symlink))
		status = FS_CALL(vnode, create_symlink, name, toPath, mode);
	else
		status = FSSH_EROFS;
 
	put_vnode(vnode);
 
	return status;
}
 
 
static fssh_status_t
common_create_link(char *path, char *toPath, bool kernel)
{
	// path validity checks have to be in the calling function!
	char name[FSSH_B_FILE_NAME_LENGTH];
	struct vnode *directory, *vnode;
	fssh_status_t status;
 
	FUNCTION(("common_create_link(path = %s, toPath = %s, kernel = %d)\n", path, toPath, kernel));
 
	status = path_to_dir_vnode(path, &directory, name, kernel);
	if (status < FSSH_B_OK)
		return status;
 
	status = path_to_vnode(toPath, true, &vnode, NULL, kernel);
	if (status < FSSH_B_OK)
		goto err;
 
	if (directory->mount != vnode->mount) {
		status = FSSH_B_CROSS_DEVICE_LINK;
		goto err1;
	}
 
	if (HAS_FS_CALL(directory, link))
		status = FS_CALL(directory, link, name, vnode);
	else
		status = FSSH_EROFS;
 
err1:
	put_vnode(vnode);
err:
	put_vnode(directory);
 
	return status;
}
 
 
static fssh_status_t
common_unlink(int fd, char *path, bool kernel)
{
	char filename[FSSH_B_FILE_NAME_LENGTH];
	struct vnode *vnode;
	fssh_status_t status;
 
	FUNCTION(("common_unlink: fd: %d, path '%s', kernel %d\n", fd, path, kernel));
 
	status = fd_and_path_to_dir_vnode(fd, path, &vnode, filename, kernel);
	if (status < 0)
		return status;
 
	if (HAS_FS_CALL(vnode, unlink))
		status = FS_CALL(vnode, unlink, filename);
	else
		status = FSSH_EROFS;
 
	put_vnode(vnode);
 
	return status;
}
 
 
static fssh_status_t
common_access(char *path, int mode, bool kernel)
{
	struct vnode *vnode;
	fssh_status_t status;
 
	status = path_to_vnode(path, true, &vnode, NULL, kernel);
	if (status < FSSH_B_OK)
		return status;
 
	if (HAS_FS_CALL(vnode, access))
		status = FS_CALL(vnode, access, mode);
	else
		status = FSSH_B_OK;
 
	put_vnode(vnode);
 
	return status;
}
 
 
static fssh_status_t
common_rename(int fd, char *path, int newFD, char *newPath, bool kernel)
{
	struct vnode *fromVnode, *toVnode;
	char fromName[FSSH_B_FILE_NAME_LENGTH];
	char toName[FSSH_B_FILE_NAME_LENGTH];
	fssh_status_t status;
 
	FUNCTION(("common_rename(fd = %d, path = %s, newFD = %d, newPath = %s, kernel = %d)\n", fd, path, newFD, newPath, kernel));
 
	status = fd_and_path_to_dir_vnode(fd, path, &fromVnode, fromName, kernel);
	if (status < 0)
		return status;
 
	status = fd_and_path_to_dir_vnode(newFD, newPath, &toVnode, toName, kernel);
	if (status < 0)
		goto err;
 
	if (fromVnode->device != toVnode->device) {
		status = FSSH_B_CROSS_DEVICE_LINK;
		goto err1;
	}
 
	if (HAS_FS_CALL(fromVnode, rename))
		status = FS_CALL(fromVnode, rename, fromName, toVnode, toName);
	else
		status = FSSH_EROFS;
 
err1:
	put_vnode(toVnode);
err:
	put_vnode(fromVnode);
 
	return status;
}
 
 
static fssh_status_t
common_read_stat(struct file_descriptor *descriptor, struct fssh_stat *stat)
{
	struct vnode *vnode = descriptor->u.vnode;
 
	FUNCTION(("common_read_stat: stat %p\n", stat));
 
	stat->fssh_st_atim.tv_nsec = 0;
	stat->fssh_st_mtim.tv_nsec = 0;
	stat->fssh_st_ctim.tv_nsec = 0;
	stat->fssh_st_crtim.tv_nsec = 0;
 
	fssh_status_t status = FS_CALL(vnode, read_stat, stat);
 
	// fill in the st_dev and st_ino fields
	if (status == FSSH_B_OK) {
		stat->fssh_st_dev = vnode->device;
		stat->fssh_st_ino = vnode->id;
	}
 
	return status;
}
 
 
static fssh_status_t
common_write_stat(struct file_descriptor *descriptor,
	const struct fssh_stat *stat, int statMask)
{
	struct vnode *vnode = descriptor->u.vnode;
 
	FUNCTION(("common_write_stat(vnode = %p, stat = %p, statMask = %d)\n", vnode, stat, statMask));
	if (!HAS_FS_CALL(vnode, write_stat))
		return FSSH_EROFS;
 
	return FS_CALL(vnode, write_stat, stat, statMask);
}
 
 
static fssh_status_t
common_path_read_stat(int fd, char *path, bool traverseLeafLink,
	struct fssh_stat *stat, bool kernel)
{
	struct vnode *vnode;
	fssh_status_t status;
 
	FUNCTION(("common_path_read_stat: fd: %d, path '%s', stat %p,\n", fd, path, stat));
 
	status = fd_and_path_to_vnode(fd, path, traverseLeafLink, &vnode, NULL, kernel);
	if (status < 0)
		return status;
 
	status = FS_CALL(vnode, read_stat, stat);
 
	// fill in the st_dev and st_ino fields
	if (status == FSSH_B_OK) {
		stat->fssh_st_dev = vnode->device;
		stat->fssh_st_ino = vnode->id;
	}
 
	put_vnode(vnode);
	return status;
}
 
 
static fssh_status_t
common_path_write_stat(int fd, char *path, bool traverseLeafLink,
	const struct fssh_stat *stat, int statMask, bool kernel)
{
	struct vnode *vnode;
	fssh_status_t status;
 
	FUNCTION(("common_write_stat: fd: %d, path '%s', stat %p, stat_mask %d, kernel %d\n", fd, path, stat, statMask, kernel));
 
	status = fd_and_path_to_vnode(fd, path, traverseLeafLink, &vnode, NULL, kernel);
	if (status < 0)
		return status;
 
	if (HAS_FS_CALL(vnode, write_stat))
		status = FS_CALL(vnode, write_stat, stat, statMask);
	else
		status = FSSH_EROFS;
 
	put_vnode(vnode);
 
	return status;
}
 
 
static int
attr_dir_open(int fd, char *path, bool kernel)
{
	struct vnode *vnode;
	int status;
 
	FUNCTION(("attr_dir_open(fd = %d, path = '%s', kernel = %d)\n", fd, path, kernel));
 
	status = fd_and_path_to_vnode(fd, path, true, &vnode, NULL, kernel);
	if (status < FSSH_B_OK)
		return status;
 
	status = open_attr_dir_vnode(vnode, kernel);
	if (status < 0)
		put_vnode(vnode);
 
	return status;
}
 
 
static fssh_status_t
attr_dir_close(struct file_descriptor *descriptor)
{
	struct vnode *vnode = descriptor->u.vnode;
 
	FUNCTION(("attr_dir_close(descriptor = %p)\n", descriptor));
 
	if (HAS_FS_CALL(vnode, close_attr_dir))
		return FS_CALL(vnode, close_attr_dir, descriptor->cookie);
 
	return FSSH_B_OK;
}
 
 
static void
attr_dir_free_fd(struct file_descriptor *descriptor)
{
	struct vnode *vnode = descriptor->u.vnode;
 
	if (vnode != NULL) {
		FS_CALL(vnode, free_attr_dir_cookie, descriptor->cookie);
		put_vnode(vnode);
	}
}
 
 
static fssh_status_t
attr_dir_read(struct file_descriptor *descriptor, struct fssh_dirent *buffer,
	fssh_size_t bufferSize, uint32_t *_count)
{
	struct vnode *vnode = descriptor->u.vnode;
 
	FUNCTION(("attr_dir_read(descriptor = %p)\n", descriptor));
 
	if (HAS_FS_CALL(vnode, read_attr_dir))
		return FS_CALL(vnode, read_attr_dir, descriptor->cookie, buffer, bufferSize, _count);
 
	return FSSH_EOPNOTSUPP;
}
 
 
static fssh_status_t
attr_dir_rewind(struct file_descriptor *descriptor)
{
	struct vnode *vnode = descriptor->u.vnode;
 
	FUNCTION(("attr_dir_rewind(descriptor = %p)\n", descriptor));
 
	if (HAS_FS_CALL(vnode, rewind_attr_dir))
		return FS_CALL(vnode, rewind_attr_dir, descriptor->cookie);
 
	return FSSH_EOPNOTSUPP;
}
 
 
static int
attr_create(int fd, const char *name, uint32_t type, int openMode, bool kernel)
{
	struct vnode *vnode;
	void *cookie;
	int status;
 
	if (name == NULL || *name == '\0')
		return FSSH_B_BAD_VALUE;
 
	vnode = get_vnode_from_fd(fd, kernel);
	if (vnode == NULL)
		return FSSH_B_FILE_ERROR;
 
	if (!HAS_FS_CALL(vnode, create_attr)) {
		status = FSSH_EROFS;
		goto err;
	}
 
	status = FS_CALL(vnode, create_attr, name, type, openMode, &cookie);
	if (status < FSSH_B_OK)
		goto err;
 
	if ((status = get_new_fd(FDTYPE_ATTR, NULL, vnode, cookie, openMode, kernel)) >= 0)
		return status;
 
	FS_CALL(vnode, close_attr, cookie);
	FS_CALL(vnode, free_attr_cookie, cookie);
 
	FS_CALL(vnode, remove_attr, name);
 
err:
	put_vnode(vnode);
 
	return status;
}
 
 
static int
attr_open(int fd, const char *name, int openMode, bool kernel)
{
	struct vnode *vnode;
	void *cookie;
	int status;
 
	if (name == NULL || *name == '\0')
		return FSSH_B_BAD_VALUE;
 
	vnode = get_vnode_from_fd(fd, kernel);
	if (vnode == NULL)
		return FSSH_B_FILE_ERROR;
 
	if (!HAS_FS_CALL(vnode, open_attr)) {
		status = FSSH_EOPNOTSUPP;
		goto err;
	}
 
	status = FS_CALL(vnode, open_attr, name, openMode, &cookie);
	if (status < FSSH_B_OK)
		goto err;
 
	// now we only need a file descriptor for this attribute and we're done
	if ((status = get_new_fd(FDTYPE_ATTR, NULL, vnode, cookie, openMode, kernel)) >= 0)
		return status;
 
	FS_CALL(vnode, close_attr, cookie);
	FS_CALL(vnode, free_attr_cookie, cookie);
 
err:
	put_vnode(vnode);
 
	return status;
}
 
 
static fssh_status_t
attr_close(struct file_descriptor *descriptor)
{
	struct vnode *vnode = descriptor->u.vnode;
 
	FUNCTION(("attr_close(descriptor = %p)\n", descriptor));
 
	if (HAS_FS_CALL(vnode, close_attr))
		return FS_CALL(vnode, close_attr, descriptor->cookie);
 
	return FSSH_B_OK;
}
 
 
static void
attr_free_fd(struct file_descriptor *descriptor)
{
	struct vnode *vnode = descriptor->u.vnode;
 
	if (vnode != NULL) {
		FS_CALL(vnode, free_attr_cookie, descriptor->cookie);
		put_vnode(vnode);
	}
}
 
 
static fssh_status_t
attr_read(struct file_descriptor *descriptor, fssh_off_t pos, void *buffer, fssh_size_t *length)
{
	struct vnode *vnode = descriptor->u.vnode;
 
	FUNCTION(("attr_read: buf %p, pos %Ld, len %p = %ld\n", buffer, pos, length, *length));
	if (!HAS_FS_CALL(vnode, read_attr))
		return FSSH_EOPNOTSUPP;
 
	return FS_CALL(vnode, read_attr, descriptor->cookie, pos, buffer, length);
}
 
 
static fssh_status_t
attr_write(struct file_descriptor *descriptor, fssh_off_t pos, const void *buffer, fssh_size_t *length)
{
	struct vnode *vnode = descriptor->u.vnode;
 
	FUNCTION(("attr_write: buf %p, pos %Ld, len %p\n", buffer, pos, length));
	if (!HAS_FS_CALL(vnode, write_attr))
		return FSSH_EOPNOTSUPP;
 
	return FS_CALL(vnode, write_attr, descriptor->cookie, pos, buffer, length);
}
 
 
static fssh_off_t
attr_seek(struct file_descriptor *descriptor, fssh_off_t pos, int seekType)
{
	fssh_off_t offset;
 
	switch (seekType) {
		case FSSH_SEEK_SET:
			offset = 0;
			break;
		case FSSH_SEEK_CUR:
			offset = descriptor->pos;
			break;
		case FSSH_SEEK_END:
		{
			struct vnode *vnode = descriptor->u.vnode;
			struct fssh_stat stat;
			fssh_status_t status;
 
			if (!HAS_FS_CALL(vnode, read_stat))
				return FSSH_EOPNOTSUPP;
 
			status = FS_CALL(vnode, read_attr_stat, descriptor->cookie, &stat);
			if (status < FSSH_B_OK)
				return status;
 
			offset = stat.fssh_st_size;
			break;
		}
		default:
			return FSSH_B_BAD_VALUE;
	}
 
	// assumes fssh_off_t is 64 bits wide
	if (offset > 0 && LLONG_MAX - offset < pos)
		return FSSH_EOVERFLOW;
 
	pos += offset;
	if (pos < 0)
		return FSSH_B_BAD_VALUE;
 
	return descriptor->pos = pos;
}
 
 
static fssh_status_t
attr_read_stat(struct file_descriptor *descriptor, struct fssh_stat *stat)
{
	struct vnode *vnode = descriptor->u.vnode;
 
	FUNCTION(("attr_read_stat: stat 0x%p\n", stat));
 
	if (!HAS_FS_CALL(vnode, read_attr_stat))
		return FSSH_EOPNOTSUPP;
 
	return FS_CALL(vnode, read_attr_stat, descriptor->cookie, stat);
}
 
 
static fssh_status_t
attr_write_stat(struct file_descriptor *descriptor,
	const struct fssh_stat *stat, int statMask)
{
	struct vnode *vnode = descriptor->u.vnode;
 
	FUNCTION(("attr_write_stat: stat = %p, statMask %d\n", stat, statMask));
 
	if (!HAS_FS_CALL(vnode, write_attr_stat))
		return FSSH_EROFS;
 
	return FS_CALL(vnode, write_attr_stat, descriptor->cookie, stat, statMask);
}
 
 
static fssh_status_t
attr_remove(int fd, const char *name, bool kernel)
{
	struct file_descriptor *descriptor;
	struct vnode *vnode;
	fssh_status_t status;
 
	if (name == NULL || *name == '\0')
		return FSSH_B_BAD_VALUE;
 
	FUNCTION(("attr_remove: fd = %d, name = \"%s\", kernel %d\n", fd, name, kernel));
 
	descriptor = get_fd_and_vnode(fd, &vnode, kernel);
	if (descriptor == NULL)
		return FSSH_B_FILE_ERROR;
 
	if (HAS_FS_CALL(vnode, remove_attr))
		status = FS_CALL(vnode, remove_attr, name);
	else
		status = FSSH_EROFS;
 
	put_fd(descriptor);
 
	return status;
}
 
 
static fssh_status_t
attr_rename(int fromfd, const char *fromName, int tofd, const char *toName, bool kernel)
{
	struct file_descriptor *fromDescriptor, *toDescriptor;
	struct vnode *fromVnode, *toVnode;
	fssh_status_t status;
 
	if (fromName == NULL || *fromName == '\0' || toName == NULL || *toName == '\0')
		return FSSH_B_BAD_VALUE;
 
	FUNCTION(("attr_rename: from fd = %d, from name = \"%s\", to fd = %d, to name = \"%s\", kernel %d\n", fromfd, fromName, tofd, toName, kernel));
 
	fromDescriptor = get_fd_and_vnode(fromfd, &fromVnode, kernel);
	if (fromDescriptor == NULL)
		return FSSH_B_FILE_ERROR;
 
	toDescriptor = get_fd_and_vnode(tofd, &toVnode, kernel);
	if (toDescriptor == NULL) {
		status = FSSH_B_FILE_ERROR;
		goto err;
	}
 
	// are the files on the same volume?
	if (fromVnode->device != toVnode->device) {
		status = FSSH_B_CROSS_DEVICE_LINK;
		goto err1;
	}
 
	if (HAS_FS_CALL(fromVnode, rename_attr))
		status = FS_CALL(fromVnode, rename_attr, fromName, toVnode, toName);
	else
		status = FSSH_EROFS;
 
err1:
	put_fd(toDescriptor);
err:
	put_fd(fromDescriptor);
 
	return status;
}
 
 
static fssh_status_t
index_dir_open(fssh_mount_id mountID, bool kernel)
{
	struct fs_mount *mount;
	void *cookie;
 
	FUNCTION(("index_dir_open(mountID = %ld, kernel = %d)\n", mountID, kernel));
 
	fssh_status_t status = get_mount(mountID, &mount);
	if (status < FSSH_B_OK)
		return status;
 
	if (!HAS_FS_MOUNT_CALL(mount, open_index_dir)) {
		status = FSSH_EOPNOTSUPP;
		goto out;
	}
 
	status = FS_MOUNT_CALL(mount, open_index_dir, &cookie);
	if (status < FSSH_B_OK)
		goto out;
 
	// get fd for the index directory
	status = get_new_fd(FDTYPE_INDEX_DIR, mount, NULL, cookie, 0, kernel);
	if (status >= 0)
		goto out;
 
	// something went wrong
	FS_MOUNT_CALL(mount, close_index_dir, cookie);
	FS_MOUNT_CALL(mount, free_index_dir_cookie, cookie);
 
out:
	put_mount(mount);
	return status;
}
 
 
static fssh_status_t
index_dir_close(struct file_descriptor *descriptor)
{
	struct fs_mount *mount = descriptor->u.mount;
 
	FUNCTION(("index_dir_close(descriptor = %p)\n", descriptor));
 
	if (HAS_FS_MOUNT_CALL(mount, close_index_dir))
		return FS_MOUNT_CALL(mount, close_index_dir, descriptor->cookie);
 
	return FSSH_B_OK;
}
 
 
static void
index_dir_free_fd(struct file_descriptor *descriptor)
{
	struct fs_mount *mount = descriptor->u.mount;
 
	if (mount != NULL) {
		FS_MOUNT_CALL(mount, free_index_dir_cookie, descriptor->cookie);
		// ToDo: find a replacement ref_count object - perhaps the root dir?
		//put_vnode(vnode);
	}
}
 
 
static fssh_status_t
index_dir_read(struct file_descriptor *descriptor, struct fssh_dirent *buffer,
	fssh_size_t bufferSize, uint32_t *_count)
{
	struct fs_mount *mount = descriptor->u.mount;
 
	if (HAS_FS_MOUNT_CALL(mount, read_index_dir))
		return FS_MOUNT_CALL(mount, read_index_dir, descriptor->cookie, buffer, bufferSize, _count);
 
	return FSSH_EOPNOTSUPP;
}
 
 
static fssh_status_t
index_dir_rewind(struct file_descriptor *descriptor)
{
	struct fs_mount *mount = descriptor->u.mount;
 
	if (HAS_FS_MOUNT_CALL(mount, rewind_index_dir))
		return FS_MOUNT_CALL(mount, rewind_index_dir, descriptor->cookie);
 
	return FSSH_EOPNOTSUPP;
}
 
 
static fssh_status_t
index_create(fssh_mount_id mountID, const char *name, uint32_t type, uint32_t flags, bool kernel)
{
	FUNCTION(("index_create(mountID = %ld, name = %s, kernel = %d)\n", mountID, name, kernel));
 
	struct fs_mount *mount;
	fssh_status_t status = get_mount(mountID, &mount);
	if (status < FSSH_B_OK)
		return status;
 
	if (!HAS_FS_MOUNT_CALL(mount, create_index)) {
		status = FSSH_EROFS;
		goto out;
	}
 
	status = FS_MOUNT_CALL(mount, create_index, name, type, flags);
 
out:
	put_mount(mount);
	return status;
}
 
 
static fssh_status_t
index_name_read_stat(fssh_mount_id mountID, const char *name,
	struct fssh_stat *stat, bool kernel)
{
	FUNCTION(("index_remove(mountID = %ld, name = %s, kernel = %d)\n", mountID, name, kernel));
 
	struct fs_mount *mount;
	fssh_status_t status = get_mount(mountID, &mount);
	if (status < FSSH_B_OK)
		return status;
 
	if (!HAS_FS_MOUNT_CALL(mount, read_index_stat)) {
		status = FSSH_EOPNOTSUPP;
		goto out;
	}
 
	status = FS_MOUNT_CALL(mount, read_index_stat, name, stat);
 
out:
	put_mount(mount);
	return status;
}
 
 
static fssh_status_t
index_remove(fssh_mount_id mountID, const char *name, bool kernel)
{
	FUNCTION(("index_remove(mountID = %ld, name = %s, kernel = %d)\n", mountID, name, kernel));
 
	struct fs_mount *mount;
	fssh_status_t status = get_mount(mountID, &mount);
	if (status < FSSH_B_OK)
		return status;
 
	if (!HAS_FS_MOUNT_CALL(mount, remove_index)) {
		status = FSSH_EROFS;
		goto out;
	}
 
	status = FS_MOUNT_CALL(mount, remove_index, name);
 
out:
	put_mount(mount);
	return status;
}
 
 
/*!	ToDo: the query FS API is still the pretty much the same as in R5.
		It would be nice if the FS would find some more kernel support
		for them.
		For example, query parsing should be moved into the kernel.
*/
static int
query_open(fssh_dev_t device, const char *query, uint32_t flags,
	fssh_port_id port, int32_t token, bool kernel)
{
	struct fs_mount *mount;
	void *cookie;
 
	FUNCTION(("query_open(device = %ld, query = \"%s\", kernel = %d)\n", device, query, kernel));
 
	fssh_status_t status = get_mount(device, &mount);
	if (status < FSSH_B_OK)
		return status;
 
	if (!HAS_FS_MOUNT_CALL(mount, open_query)) {
		status = FSSH_EOPNOTSUPP;
		goto out;
	}
 
	status = FS_MOUNT_CALL(mount, open_query, query, flags, port, token, &cookie);
	if (status < FSSH_B_OK)
		goto out;
 
	// get fd for the index directory
	status = get_new_fd(FDTYPE_QUERY, mount, NULL, cookie, 0, kernel);
	if (status >= 0)
		goto out;
 
	// something went wrong
	FS_MOUNT_CALL(mount, close_query, cookie);
	FS_MOUNT_CALL(mount, free_query_cookie, cookie);
 
out:
	put_mount(mount);
	return status;
}
 
 
static fssh_status_t
query_close(struct file_descriptor *descriptor)
{
	struct fs_mount *mount = descriptor->u.mount;
 
	FUNCTION(("query_close(descriptor = %p)\n", descriptor));
 
	if (HAS_FS_MOUNT_CALL(mount, close_query))
		return FS_MOUNT_CALL(mount, close_query, descriptor->cookie);
 
	return FSSH_B_OK;
}
 
 
static void
query_free_fd(struct file_descriptor *descriptor)
{
	struct fs_mount *mount = descriptor->u.mount;
 
	if (mount != NULL) {
		FS_MOUNT_CALL(mount, free_query_cookie, descriptor->cookie);
		// ToDo: find a replacement ref_count object - perhaps the root dir?
		//put_vnode(vnode);
	}
}
 
 
static fssh_status_t
query_read(struct file_descriptor *descriptor, struct fssh_dirent *buffer,
	fssh_size_t bufferSize, uint32_t *_count)
{
	struct fs_mount *mount = descriptor->u.mount;
 
	if (HAS_FS_MOUNT_CALL(mount, read_query))
		return FS_MOUNT_CALL(mount, read_query, descriptor->cookie, buffer, bufferSize, _count);
 
	return FSSH_EOPNOTSUPP;
}
 
 
static fssh_status_t
query_rewind(struct file_descriptor *descriptor)
{
	struct fs_mount *mount = descriptor->u.mount;
 
	if (HAS_FS_MOUNT_CALL(mount, rewind_query))
		return FS_MOUNT_CALL(mount, rewind_query, descriptor->cookie);
 
	return FSSH_EOPNOTSUPP;
}
 
 
//	#pragma mark -
//	General File System functions
 
 
static fssh_dev_t
fs_mount(char *path, const char *device, const char *fsName, uint32_t flags,
	const char *args, bool kernel)
{
	struct fs_mount *mount;
	fssh_status_t status = 0;
 
	FUNCTION(("fs_mount: entry. path = '%s', fs_name = '%s'\n", path, fsName));
 
	// The path is always safe, we just have to make sure that fsName is
	// almost valid - we can't make any assumptions about args, though.
	// A NULL fsName is OK, if a device was given and the FS is not virtual.
	// We'll get it from the DDM later.
	if (fsName == NULL) {
		if (!device || flags & FSSH_B_MOUNT_VIRTUAL_DEVICE)
			return FSSH_B_BAD_VALUE;
	} else if (fsName[0] == '\0')
		return FSSH_B_BAD_VALUE;
 
	RecursiveLocker mountOpLocker(sMountOpLock);
 
	// If the file system is not a "virtual" one, the device argument should
	// point to a real file/device (if given at all).
	// get the partition
	KPath normalizedDevice;
 
	if (!(flags & FSSH_B_MOUNT_VIRTUAL_DEVICE) && device) {
		// normalize the device path
//		status = normalizedDevice.SetTo(device, true);
// NOTE: normalizing works only in our namespace.
		status = normalizedDevice.SetTo(device, false);
		if (status != FSSH_B_OK)
			return status;
 
		device = normalizedDevice.Path();
			// correct path to file device
	}
 
	mount = (struct fs_mount *)malloc(sizeof(struct fs_mount));
	if (mount == NULL)
		return FSSH_B_NO_MEMORY;
 
	mount->volume = (fssh_fs_volume*)malloc(sizeof(fssh_fs_volume));
	if (mount->volume == NULL) {
		free(mount);
		return FSSH_B_NO_MEMORY;
	}
 
	list_init_etc(&mount->vnodes, fssh_offsetof(struct vnode, mount_link));
 
	mount->fs_name = get_file_system_name(fsName);
	if (mount->fs_name == NULL) {
		status = FSSH_B_NO_MEMORY;
		goto err1;
	}
 
	mount->device_name = fssh_strdup(device);
		// "device" can be NULL
 
	mount->fs = get_file_system(fsName);
	if (mount->fs == NULL) {
		status = FSSH_ENODEV;
		goto err3;
	}
 
	fssh_recursive_lock_init(&mount->rlock, "mount rlock");
 
	// initialize structure
	mount->id = sNextMountID++;
	mount->root_vnode = NULL;
	mount->covers_vnode = NULL;
	mount->unmounting = false;
	mount->owns_file_device = false;
 
	mount->volume->id = mount->id;
	mount->volume->layer = 0;
	mount->volume->private_volume = NULL;
	mount->volume->ops = NULL;
	mount->volume->sub_volume = NULL;
	mount->volume->super_volume = NULL;
 
	// insert mount struct into list before we call FS's mount() function
	// so that vnodes can be created for this mount
	fssh_mutex_lock(&sMountMutex);
	hash_insert(sMountsTable, mount);
	fssh_mutex_unlock(&sMountMutex);
 
	fssh_vnode_id rootID;
 
	if (!sRoot) {
		// we haven't mounted anything yet
		if (fssh_strcmp(path, "/") != 0) {
			status = FSSH_B_ERROR;
			goto err4;
		}
 
		status = mount->fs->mount(mount->volume, device, flags, args, &rootID);
		if (status < 0) {
			// ToDo: why should we hide the error code from the file system here?
			//status = ERR_VFS_GENERAL;
			goto err4;
		}
	} else {
		struct vnode *coveredVnode;
		status = path_to_vnode(path, true, &coveredVnode, NULL, kernel);
		if (status < FSSH_B_OK)
			goto err4;
 
		// make sure covered_vnode is a DIR
		struct fssh_stat coveredNodeStat;
		status = FS_CALL(coveredVnode, read_stat, &coveredNodeStat);
		if (status < FSSH_B_OK)
			goto err4;
 
		if (!FSSH_S_ISDIR(coveredNodeStat.fssh_st_mode)) {
			status = FSSH_B_NOT_A_DIRECTORY;
			goto err4;
		}
 
		if (coveredVnode->mount->root_vnode == coveredVnode) {
			// this is already a mount point
			status = FSSH_B_BUSY;
			goto err4;
		}
 
		mount->covers_vnode = coveredVnode;
 
		// mount it
		status = mount->fs->mount(mount->volume, device, flags, args, &rootID);
		if (status < FSSH_B_OK)
			goto err5;
	}
 
	// the root node is supposed to be owned by the file system - it must
	// exist at this point
	mount->root_vnode = lookup_vnode(mount->id, rootID);
	if (mount->root_vnode == NULL || mount->root_vnode->ref_count != 1) {
		fssh_panic("fs_mount: file system does not own its root node!\n");
		status = FSSH_B_ERROR;
		goto err6;
	}
 
	// No race here, since fs_mount() is the only function changing
	// covers_vnode (and holds sMountOpLock at that time).
	fssh_mutex_lock(&sVnodeCoveredByMutex);
	if (mount->covers_vnode)
		mount->covers_vnode->covered_by = mount->root_vnode;
	fssh_mutex_unlock(&sVnodeCoveredByMutex);
 
	if (!sRoot)
		sRoot = mount->root_vnode;
 
	return mount->id;
 
err6:
	FS_MOUNT_CALL_NO_PARAMS(mount, unmount);
err5:
	if (mount->covers_vnode)
		put_vnode(mount->covers_vnode);
 
err4:
	fssh_mutex_lock(&sMountMutex);
	hash_remove(sMountsTable, mount);
	fssh_mutex_unlock(&sMountMutex);
 
	fssh_recursive_lock_destroy(&mount->rlock);
 
	put_file_system(mount->fs);
	free(mount->device_name);
err3:
	free(mount->fs_name);
err1:
	free(mount->volume);
	free(mount);
 
	return status;
}
 
 
static fssh_status_t
fs_unmount(char *path, uint32_t flags, bool kernel)
{
	struct fs_mount *mount;
	struct vnode *vnode;
	fssh_status_t err;
 
	FUNCTION(("vfs_unmount: entry. path = '%s', kernel %d\n", path, kernel));
 
	err = path_to_vnode(path, true, &vnode, NULL, kernel);
	if (err < 0)
		return FSSH_B_ENTRY_NOT_FOUND;
 
	RecursiveLocker mountOpLocker(sMountOpLock);
 
	mount = find_mount(vnode->device);
	if (!mount)
		fssh_panic("vfs_unmount: find_mount() failed on root vnode @%p of mount\n", vnode);
 
	if (mount->root_vnode != vnode) {
		// not mountpoint
		put_vnode(vnode);
		return FSSH_B_BAD_VALUE;
	}
 
	// grab the vnode master mutex to keep someone from creating
	// a vnode while we're figuring out if we can continue
	fssh_mutex_lock(&sVnodeMutex);
 
	bool disconnectedDescriptors = false;
 
	while (true) {
		bool busy = false;
 
		// cycle through the list of vnodes associated with this mount and
		// make sure all of them are not busy or have refs on them
		vnode = NULL;
		while ((vnode = (struct vnode *)list_get_next_item(&mount->vnodes, vnode)) != NULL) {
			// The root vnode ref_count needs to be 2 here: one for the file
			// system, one from the path_to_vnode() call above
			if (vnode->busy
				|| ((vnode->ref_count != 0 && mount->root_vnode != vnode)
					|| (vnode->ref_count != 2 && mount->root_vnode == vnode))) {
				// there are still vnodes in use on this mount, so we cannot
				// unmount yet
				busy = true;
				break;
			}
		}
 
		if (!busy)
			break;
 
		if ((flags & FSSH_B_FORCE_UNMOUNT) == 0) {
			fssh_mutex_unlock(&sVnodeMutex);
			put_vnode(mount->root_vnode);
 
			return FSSH_B_BUSY;
		}
 
		if (disconnectedDescriptors) {
			// wait a bit until the last access is finished, and then try again
			fssh_mutex_unlock(&sVnodeMutex);
			fssh_snooze(100000);
			// TODO: if there is some kind of bug that prevents the ref counts
			//	from getting back to zero, this will fall into an endless loop...
			fssh_mutex_lock(&sVnodeMutex);
			continue;
		}
 
		// the file system is still busy - but we're forced to unmount it,
		// so let's disconnect all open file descriptors
 
		mount->unmounting = true;
			// prevent new vnodes from being created
 
		fssh_mutex_unlock(&sVnodeMutex);
 
		disconnect_mount_or_vnode_fds(mount, NULL);
		disconnectedDescriptors = true;
 
		fssh_mutex_lock(&sVnodeMutex);
	}
 
	// we can safely continue, mark all of the vnodes busy and this mount
	// structure in unmounting state
	mount->unmounting = true;
 
	while ((vnode = (struct vnode *)list_get_next_item(&mount->vnodes, vnode)) != NULL) {
		vnode->busy = true;
 
		if (vnode->ref_count == 0) {
			// this vnode has been unused before
			list_remove_item(&sUnusedVnodeList, vnode);
			sUnusedVnodes--;
		}
	}
 
	// The ref_count of the root node is 2 at this point, see above why this is
	mount->root_vnode->ref_count -= 2;
 
	fssh_mutex_unlock(&sVnodeMutex);
 
	fssh_mutex_lock(&sVnodeCoveredByMutex);
	mount->covers_vnode->covered_by = NULL;
	fssh_mutex_unlock(&sVnodeCoveredByMutex);
	put_vnode(mount->covers_vnode);
 
	// Free all vnodes associated with this mount.
	// They will be removed from the mount list by free_vnode(), so
	// we don't have to do this.
	while ((vnode = (struct vnode *)list_get_first_item(&mount->vnodes)) != NULL) {
		free_vnode(vnode, false);
	}
 
	// remove the mount structure from the hash table
	fssh_mutex_lock(&sMountMutex);
	hash_remove(sMountsTable, mount);
	fssh_mutex_unlock(&sMountMutex);
 
	mountOpLocker.Unlock();
 
	FS_MOUNT_CALL_NO_PARAMS(mount, unmount);
 
	// release the file system
	put_file_system(mount->fs);
 
	free(mount->device_name);
	free(mount->fs_name);
	free(mount);
 
	return FSSH_B_OK;
}
 
 
static fssh_status_t
fs_sync(fssh_dev_t device)
{
	struct fs_mount *mount;
	fssh_status_t status = get_mount(device, &mount);
	if (status < FSSH_B_OK)
		return status;
 
	fssh_mutex_lock(&sMountMutex);
 
	if (HAS_FS_MOUNT_CALL(mount, sync))
		status = FS_MOUNT_CALL_NO_PARAMS(mount, sync);
 
	fssh_mutex_unlock(&sMountMutex);
 
	struct vnode *previousVnode = NULL;
	while (true) {
		// synchronize access to vnode list
		fssh_recursive_lock_lock(&mount->rlock);
 
		struct vnode *vnode = (struct vnode *)list_get_next_item(&mount->vnodes,
			previousVnode);
 
		fssh_vnode_id id = -1;
		if (vnode != NULL)
			id = vnode->id;
 
		fssh_recursive_lock_unlock(&mount->rlock);
 
		if (vnode == NULL)
			break;
 
		// acquire a reference to the vnode
 
		if (get_vnode(mount->id, id, &vnode, true) == FSSH_B_OK) {
			if (previousVnode != NULL)
				put_vnode(previousVnode);
 
			if (HAS_FS_CALL(vnode, fsync))
				FS_CALL_NO_PARAMS(vnode, fsync);
 
			// the next vnode might change until we lock the vnode list again,
			// but this vnode won't go away since we keep a reference to it.
			previousVnode = vnode;
		} else {
			fssh_dprintf("syncing of mount %d stopped due to vnode %"
				FSSH_B_PRIdINO ".\n", (int)mount->id, id);
			break;
		}
	}
 
	if (previousVnode != NULL)
		put_vnode(previousVnode);
 
	put_mount(mount);
	return status;
}
 
 
static fssh_status_t
fs_read_info(fssh_dev_t device, struct fssh_fs_info *info)
{
	struct fs_mount *mount;
	fssh_status_t status = get_mount(device, &mount);
	if (status < FSSH_B_OK)
		return status;
 
	fssh_memset(info, 0, sizeof(struct fssh_fs_info));
 
	if (HAS_FS_MOUNT_CALL(mount, read_fs_info))
		status = FS_MOUNT_CALL(mount, read_fs_info, info);
 
	// fill in info the file system doesn't (have to) know about
	if (status == FSSH_B_OK) {
		info->dev = mount->id;
		info->root = mount->root_vnode->id;
		fssh_strlcpy(info->fsh_name, mount->fs_name, sizeof(info->fsh_name));
		if (mount->device_name != NULL) {
			fssh_strlcpy(info->device_name, mount->device_name,
				sizeof(info->device_name));
		}
	}
 
	// if the call is not supported by the file system, there are still
	// the parts that we filled out ourselves
 
	put_mount(mount);
	return status;
}
 
 
static fssh_status_t
fs_write_info(fssh_dev_t device, const struct fssh_fs_info *info, int mask)
{
	struct fs_mount *mount;
	fssh_status_t status = get_mount(device, &mount);
	if (status < FSSH_B_OK)
		return status;
 
	if (HAS_FS_MOUNT_CALL(mount, write_fs_info))
		status = FS_MOUNT_CALL(mount, write_fs_info, info, mask);
	else
		status = FSSH_EROFS;
 
	put_mount(mount);
	return status;
}
 
 
static fssh_dev_t
fs_next_device(int32_t *_cookie)
{
	struct fs_mount *mount = NULL;
	fssh_dev_t device = *_cookie;
 
	fssh_mutex_lock(&sMountMutex);
 
	// Since device IDs are assigned sequentially, this algorithm
	// does work good enough. It makes sure that the device list
	// returned is sorted, and that no device is skipped when an
	// already visited device got unmounted.
 
	while (device < sNextMountID) {
		mount = find_mount(device++);
		if (mount != NULL && mount->volume->private_volume != NULL)
			break;
	}
 
	*_cookie = device;
 
	if (mount != NULL)
		device = mount->id;
	else
		device = FSSH_B_BAD_VALUE;
 
	fssh_mutex_unlock(&sMountMutex);
 
	return device;
}
 
 
static fssh_status_t
get_cwd(char *buffer, fssh_size_t size, bool kernel)
{
	// Get current working directory from io context
	struct io_context *context = get_current_io_context(kernel);
	fssh_status_t status;
 
	FUNCTION(("vfs_get_cwd: buf %p, size %ld\n", buffer, size));
 
	fssh_mutex_lock(&context->io_mutex);
 
	if (context->cwd)
		status = dir_vnode_to_path(context->cwd, buffer, size);
	else
		status = FSSH_B_ERROR;
 
	fssh_mutex_unlock(&context->io_mutex);
	return status;
}
 
 
static fssh_status_t
set_cwd(int fd, char *path, bool kernel)
{
	struct io_context *context;
	struct vnode *vnode = NULL;
	struct vnode *oldDirectory;
	struct fssh_stat stat;
	fssh_status_t status;
 
	FUNCTION(("set_cwd: path = \'%s\'\n", path));
 
	// Get vnode for passed path, and bail if it failed
	status = fd_and_path_to_vnode(fd, path, true, &vnode, NULL, kernel);
	if (status < 0)
		return status;
 
	status = FS_CALL(vnode, read_stat, &stat);
	if (status < 0)
		goto err;
 
	if (!FSSH_S_ISDIR(stat.fssh_st_mode)) {
		// nope, can't cwd to here
		status = FSSH_B_NOT_A_DIRECTORY;
		goto err;
	}
 
	// Get current io context and lock
	context = get_current_io_context(kernel);
	fssh_mutex_lock(&context->io_mutex);
 
	// save the old current working directory first
	oldDirectory = context->cwd;
	context->cwd = vnode;
 
	fssh_mutex_unlock(&context->io_mutex);
 
	if (oldDirectory)
		put_vnode(oldDirectory);
 
	return FSSH_B_NO_ERROR;
 
err:
	put_vnode(vnode);
	return status;
}
 
 
//	#pragma mark -
//	Calls from within the kernel
 
 
fssh_dev_t
_kern_mount(const char *path, const char *device, const char *fsName,
	uint32_t flags, const char *args, fssh_size_t argsLength)
{
	KPath pathBuffer(path, false, FSSH_B_PATH_NAME_LENGTH + 1);
	if (pathBuffer.InitCheck() != FSSH_B_OK)
		return FSSH_B_NO_MEMORY;
 
	return fs_mount(pathBuffer.LockBuffer(), device, fsName, flags, args, true);
}
 
 
fssh_status_t
_kern_unmount(const char *path, uint32_t flags)
{
	KPath pathBuffer(path, false, FSSH_B_PATH_NAME_LENGTH + 1);
	if (pathBuffer.InitCheck() != FSSH_B_OK)
		return FSSH_B_NO_MEMORY;
 
	return fs_unmount(pathBuffer.LockBuffer(), flags, true);
}
 
 
fssh_status_t
_kern_read_fs_info(fssh_dev_t device, struct fssh_fs_info *info)
{
	if (info == NULL)
		return FSSH_B_BAD_VALUE;
 
	return fs_read_info(device, info);
}
 
 
fssh_status_t
_kern_write_fs_info(fssh_dev_t device, const struct fssh_fs_info *info, int mask)
{
	if (info == NULL)
		return FSSH_B_BAD_VALUE;
 
	return fs_write_info(device, info, mask);
}
 
 
fssh_status_t
_kern_sync(void)
{
	// Note: _kern_sync() is also called from _user_sync()
	int32_t cookie = 0;
	fssh_dev_t device;
	while ((device = fs_next_device(&cookie)) >= 0) {
		fssh_status_t status = fs_sync(device);
		if (status != FSSH_B_OK && status != FSSH_B_BAD_VALUE)
			fssh_dprintf("sync: device %d couldn't sync: %s\n", (int)device, fssh_strerror(status));
	}
 
	return FSSH_B_OK;
}
 
 
fssh_dev_t
_kern_next_device(int32_t *_cookie)
{
	return fs_next_device(_cookie);
}
 
 
int
_kern_open_entry_ref(fssh_dev_t device, fssh_ino_t inode, const char *name, int openMode, int perms)
{
	if (openMode & FSSH_O_CREAT)
		return file_create_entry_ref(device, inode, name, openMode, perms, true);
 
	return file_open_entry_ref(device, inode, name, openMode, true);
}
 
 
/**	\brief Opens a node specified by a FD + path pair.
 *
 *	At least one of \a fd and \a path must be specified.
 *	If only \a fd is given, the function opens the node identified by this
 *	FD. If only a path is given, this path is opened. If both are given and
 *	the path is absolute, \a fd is ignored; a relative path is reckoned off
 *	of the directory (!) identified by \a fd.
 *
 *	\param fd The FD. May be < 0.
 *	\param path The absolute or relative path. May be \c NULL.
 *	\param openMode The open mode.
 *	\return A FD referring to the newly opened node, or an error code,
 *			if an error occurs.
 */
 
int
_kern_open(int fd, const char *path, int openMode, int perms)
{
	KPath pathBuffer(path, false, FSSH_B_PATH_NAME_LENGTH + 1);
	if (pathBuffer.InitCheck() != FSSH_B_OK)
		return FSSH_B_NO_MEMORY;
 
	if (openMode & FSSH_O_CREAT)
		return file_create(fd, pathBuffer.LockBuffer(), openMode, perms, true);
 
	return file_open(fd, pathBuffer.LockBuffer(), openMode, true);
}
 
 
/**	\brief Opens a directory specified by entry_ref or node_ref.
 *
 *	The supplied name may be \c NULL, in which case directory identified
 *	by \a device and \a inode will be opened. Otherwise \a device and
 *	\a inode identify the parent directory of the directory to be opened
 *	and \a name its entry name.
 *
 *	\param device If \a name is specified the ID of the device the parent
 *		   directory of the directory to be opened resides on, otherwise
 *		   the device of the directory itself.
 *	\param inode If \a name is specified the node ID of the parent
 *		   directory of the directory to be opened, otherwise node ID of the
 *		   directory itself.
 *	\param name The entry name of the directory to be opened. If \c NULL,
 *		   the \a device + \a inode pair identify the node to be opened.
 *	\return The FD of the newly opened directory or an error code, if
 *			something went wrong.
 */
 
int
_kern_open_dir_entry_ref(fssh_dev_t device, fssh_ino_t inode, const char *name)
{
	return dir_open_entry_ref(device, inode, name, true);
}
 
 
/**	\brief Opens a directory specified by a FD + path pair.
 *
 *	At least one of \a fd and \a path must be specified.
 *	If only \a fd is given, the function opens the directory identified by this
 *	FD. If only a path is given, this path is opened. If both are given and
 *	the path is absolute, \a fd is ignored; a relative path is reckoned off
 *	of the directory (!) identified by \a fd.
 *
 *	\param fd The FD. May be < 0.
 *	\param path The absolute or relative path. May be \c NULL.
 *	\return A FD referring to the newly opened directory, or an error code,
 *			if an error occurs.
 */
 
int
_kern_open_dir(int fd, const char *path)
{
	KPath pathBuffer(path, false, FSSH_B_PATH_NAME_LENGTH + 1);
	if (pathBuffer.InitCheck() != FSSH_B_OK)
		return FSSH_B_NO_MEMORY;
 
	return dir_open(fd, pathBuffer.LockBuffer(), true);
}
 
 
fssh_status_t
_kern_fcntl(int fd, int op, uint32_t argument)
{
	return common_fcntl(fd, op, argument, true);
}
 
 
fssh_status_t
_kern_fsync(int fd)
{
	return common_sync(fd, true);
}
 
 
fssh_status_t
_kern_lock_node(int fd)
{
	return common_lock_node(fd, true);
}
 
 
fssh_status_t
_kern_unlock_node(int fd)
{
	return common_unlock_node(fd, true);
}
 
 
fssh_status_t
_kern_create_dir_entry_ref(fssh_dev_t device, fssh_ino_t inode, const char *name, int perms)
{
	return dir_create_entry_ref(device, inode, name, perms, true);
}
 
 
/**	\brief Creates a directory specified by a FD + path pair.
 *
 *	\a path must always be specified (it contains the name of the new directory
 *	at least). If only a path is given, this path identifies the location at
 *	which the directory shall be created. If both \a fd and \a path are given and
 *	the path is absolute, \a fd is ignored; a relative path is reckoned off
 *	of the directory (!) identified by \a fd.
 *
 *	\param fd The FD. May be < 0.
 *	\param path The absolute or relative path. Must not be \c NULL.
 *	\param perms The access permissions the new directory shall have.
 *	\return \c FSSH_B_OK, if the directory has been created successfully, another
 *			error code otherwise.
 */
 
fssh_status_t
_kern_create_dir(int fd, const char *path, int perms)
{
	KPath pathBuffer(path, false, FSSH_B_PATH_NAME_LENGTH + 1);
	if (pathBuffer.InitCheck() != FSSH_B_OK)
		return FSSH_B_NO_MEMORY;
 
	return dir_create(fd, pathBuffer.LockBuffer(), perms, true);
}
 
 
fssh_status_t
_kern_remove_dir(int fd, const char *path)
{
	if (path) {
		KPath pathBuffer(path, false, FSSH_B_PATH_NAME_LENGTH + 1);
		if (pathBuffer.InitCheck() != FSSH_B_OK)
			return FSSH_B_NO_MEMORY;
 
		return dir_remove(fd, pathBuffer.LockBuffer(), true);
	}
 
	return dir_remove(fd, NULL, true);
}
 
 
/**	\brief Reads the contents of a symlink referred to by a FD + path pair.
 *
 *	At least one of \a fd and \a path must be specified.
 *	If only \a fd is given, the function the symlink to be read is the node
 *	identified by this FD. If only a path is given, this path identifies the
 *	symlink to be read. If both are given and the path is absolute, \a fd is
 *	ignored; a relative path is reckoned off of the directory (!) identified
 *	by \a fd.
 *	If this function fails with FSSH_B_BUFFER_OVERFLOW, the \a _bufferSize pointer
 *	will still be updated to reflect the required buffer size.
 *
 *	\param fd The FD. May be < 0.
 *	\param path The absolute or relative path. May be \c NULL.
 *	\param buffer The buffer into which the contents of the symlink shall be
 *		   written.
 *	\param _bufferSize A pointer to the size of the supplied buffer.
 *	\return The length of the link on success or an appropriate error code
 */
 
fssh_status_t
_kern_read_link(int fd, const char *path, char *buffer, fssh_size_t *_bufferSize)
{
	if (path) {
		KPath pathBuffer(path, false, FSSH_B_PATH_NAME_LENGTH + 1);
		if (pathBuffer.InitCheck() != FSSH_B_OK)
			return FSSH_B_NO_MEMORY;
 
		return common_read_link(fd, pathBuffer.LockBuffer(),
			buffer, _bufferSize, true);
	}
 
	return common_read_link(fd, NULL, buffer, _bufferSize, true);
}
 
 
/**	\brief Creates a symlink specified by a FD + path pair.
 *
 *	\a path must always be specified (it contains the name of the new symlink
 *	at least). If only a path is given, this path identifies the location at
 *	which the symlink shall be created. If both \a fd and \a path are given and
 *	the path is absolute, \a fd is ignored; a relative path is reckoned off
 *	of the directory (!) identified by \a fd.
 *
 *	\param fd The FD. May be < 0.
 *	\param toPath The absolute or relative path. Must not be \c NULL.
 *	\param mode The access permissions the new symlink shall have.
 *	\return \c FSSH_B_OK, if the symlink has been created successfully, another
 *			error code otherwise.
 */
 
fssh_status_t
_kern_create_symlink(int fd, const char *path, const char *toPath, int mode)
{
	KPath pathBuffer(path, false, FSSH_B_PATH_NAME_LENGTH + 1);
	KPath toPathBuffer(toPath, false, FSSH_B_PATH_NAME_LENGTH + 1);
	if (pathBuffer.InitCheck() != FSSH_B_OK || toPathBuffer.InitCheck() != FSSH_B_OK)
		return FSSH_B_NO_MEMORY;
 
	char *toBuffer = toPathBuffer.LockBuffer();
 
	fssh_status_t status = check_path(toBuffer);
	if (status < FSSH_B_OK)
		return status;
 
	return common_create_symlink(fd, pathBuffer.LockBuffer(),
		toBuffer, mode, true);
}
 
 
fssh_status_t
_kern_create_link(const char *path, const char *toPath)
{
	KPath pathBuffer(path, false, FSSH_B_PATH_NAME_LENGTH + 1);
	KPath toPathBuffer(toPath, false, FSSH_B_PATH_NAME_LENGTH + 1);
	if (pathBuffer.InitCheck() != FSSH_B_OK || toPathBuffer.InitCheck() != FSSH_B_OK)
		return FSSH_B_NO_MEMORY;
 
	return common_create_link(pathBuffer.LockBuffer(),
		toPathBuffer.LockBuffer(), true);
}
 
 
/**	\brief Removes an entry specified by a FD + path pair from its directory.
 *
 *	\a path must always be specified (it contains at least the name of the entry
 *	to be deleted). If only a path is given, this path identifies the entry
 *	directly. If both \a fd and \a path are given and the path is absolute,
 *	\a fd is ignored; a relative path is reckoned off of the directory (!)
 *	identified by \a fd.
 *
 *	\param fd The FD. May be < 0.
 *	\param path The absolute or relative path. Must not be \c NULL.
 *	\return \c FSSH_B_OK, if the entry has been removed successfully, another
 *			error code otherwise.
 */
 
fssh_status_t
_kern_unlink(int fd, const char *path)
{
	KPath pathBuffer(path, false, FSSH_B_PATH_NAME_LENGTH + 1);
	if (pathBuffer.InitCheck() != FSSH_B_OK)
		return FSSH_B_NO_MEMORY;
 
	return common_unlink(fd, pathBuffer.LockBuffer(), true);
}
 
 
/**	\brief Moves an entry specified by a FD + path pair to a an entry specified
 *		   by another FD + path pair.
 *
 *	\a oldPath and \a newPath must always be specified (they contain at least
 *	the name of the entry). If only a path is given, this path identifies the
 *	entry directly. If both a FD and a path are given and the path is absolute,
 *	the FD is ignored; a relative path is reckoned off of the directory (!)
 *	identified by the respective FD.
 *
 *	\param oldFD The FD of the old location. May be < 0.
 *	\param oldPath The absolute or relative path of the old location. Must not
 *		   be \c NULL.
 *	\param newFD The FD of the new location. May be < 0.
 *	\param newPath The absolute or relative path of the new location. Must not
 *		   be \c NULL.
 *	\return \c FSSH_B_OK, if the entry has been moved successfully, another
 *			error code otherwise.
 */
 
fssh_status_t
_kern_rename(int oldFD, const char *oldPath, int newFD, const char *newPath)
{
	KPath oldPathBuffer(oldPath, false, FSSH_B_PATH_NAME_LENGTH + 1);
	KPath newPathBuffer(newPath, false, FSSH_B_PATH_NAME_LENGTH + 1);
	if (oldPathBuffer.InitCheck() != FSSH_B_OK || newPathBuffer.InitCheck() != FSSH_B_OK)
		return FSSH_B_NO_MEMORY;
 
	return common_rename(oldFD, oldPathBuffer.LockBuffer(),
		newFD, newPathBuffer.LockBuffer(), true);
}
 
 
fssh_status_t
_kern_access(const char *path, int mode)
{
	KPath pathBuffer(path, false, FSSH_B_PATH_NAME_LENGTH + 1);
	if (pathBuffer.InitCheck() != FSSH_B_OK)
		return FSSH_B_NO_MEMORY;
 
	return common_access(pathBuffer.LockBuffer(), mode, true);
}
 
 
/**	\brief Reads stat data of an entity specified by a FD + path pair.
 *
 *	If only \a fd is given, the stat operation associated with the type
 *	of the FD (node, attr, attr dir etc.) is performed. If only \a path is
 *	given, this path identifies the entry for whose node to retrieve the
 *	stat data. If both \a fd and \a path are given and the path is absolute,
 *	\a fd is ignored; a relative path is reckoned off of the directory (!)
 *	identified by \a fd and specifies the entry whose stat data shall be
 *	retrieved.
 *
 *	\param fd The FD. May be < 0.
 *	\param path The absolute or relative path. Must not be \c NULL.
 *	\param traverseLeafLink If \a path is given, \c true specifies that the
 *		   function shall not stick to symlinks, but traverse them.
 *	\param stat The buffer the stat data shall be written into.
 *	\param statSize The size of the supplied stat buffer.
 *	\return \c FSSH_B_OK, if the the stat data have been read successfully, another
 *			error code otherwise.
 */
 
fssh_status_t
_kern_read_stat(int fd, const char *path, bool traverseLeafLink,
	fssh_struct_stat *stat, fssh_size_t statSize)
{
	fssh_struct_stat completeStat;
	fssh_struct_stat *originalStat = NULL;
	fssh_status_t status;
 
	if (statSize > sizeof(fssh_struct_stat))
		return FSSH_B_BAD_VALUE;
 
	// this supports different stat extensions
	if (statSize < sizeof(fssh_struct_stat)) {
		originalStat = stat;
		stat = &completeStat;
	}
 
	if (path) {
		// path given: get the stat of the node referred to by (fd, path)
		KPath pathBuffer(path, false, FSSH_B_PATH_NAME_LENGTH + 1);
		if (pathBuffer.InitCheck() != FSSH_B_OK)
			return FSSH_B_NO_MEMORY;
 
		status = common_path_read_stat(fd, pathBuffer.LockBuffer(),
			traverseLeafLink, stat, true);
	} else {
		// no path given: get the FD and use the FD operation
		struct file_descriptor *descriptor
			= get_fd(get_current_io_context(true), fd);
		if (descriptor == NULL)
			return FSSH_B_FILE_ERROR;
 
		if (descriptor->ops->fd_read_stat)
			status = descriptor->ops->fd_read_stat(descriptor, stat);
		else
			status = FSSH_EOPNOTSUPP;
 
		put_fd(descriptor);
	}
 
	if (status == FSSH_B_OK && originalStat != NULL)
		fssh_memcpy(originalStat, stat, statSize);
 
	return status;
}
 
 
/**	\brief Writes stat data of an entity specified by a FD + path pair.
 *
 *	If only \a fd is given, the stat operation associated with the type
 *	of the FD (node, attr, attr dir etc.) is performed. If only \a path is
 *	given, this path identifies the entry for whose node to write the
 *	stat data. If both \a fd and \a path are given and the path is absolute,
 *	\a fd is ignored; a relative path is reckoned off of the directory (!)
 *	identified by \a fd and specifies the entry whose stat data shall be
 *	written.
 *
 *	\param fd The FD. May be < 0.
 *	\param path The absolute or relative path. Must not be \c NULL.
 *	\param traverseLeafLink If \a path is given, \c true specifies that the
 *		   function shall not stick to symlinks, but traverse them.
 *	\param stat The buffer containing the stat data to be written.
 *	\param statSize The size of the supplied stat buffer.
 *	\param statMask A mask specifying which parts of the stat data shall be
 *		   written.
 *	\return \c FSSH_B_OK, if the the stat data have been written successfully,
 *			another error code otherwise.
 */
 
fssh_status_t
_kern_write_stat(int fd, const char *path, bool traverseLeafLink,
	const fssh_struct_stat *stat, fssh_size_t statSize, int statMask)
{
	fssh_struct_stat completeStat;
 
	if (statSize > sizeof(fssh_struct_stat))
		return FSSH_B_BAD_VALUE;
 
	// this supports different stat extensions
	if (statSize < sizeof(fssh_struct_stat)) {
		fssh_memset((uint8_t *)&completeStat + statSize, 0, sizeof(fssh_struct_stat) - statSize);
		fssh_memcpy(&completeStat, stat, statSize);
		stat = &completeStat;
	}
 
	fssh_status_t status;
 
	if (path) {
		// path given: write the stat of the node referred to by (fd, path)
		KPath pathBuffer(path, false, FSSH_B_PATH_NAME_LENGTH + 1);
		if (pathBuffer.InitCheck() != FSSH_B_OK)
			return FSSH_B_NO_MEMORY;
 
		status = common_path_write_stat(fd, pathBuffer.LockBuffer(),
			traverseLeafLink, stat, statMask, true);
	} else {
		// no path given: get the FD and use the FD operation
		struct file_descriptor *descriptor
			= get_fd(get_current_io_context(true), fd);
		if (descriptor == NULL)
			return FSSH_B_FILE_ERROR;
 
		if (descriptor->ops->fd_write_stat)
			status = descriptor->ops->fd_write_stat(descriptor, stat, statMask);
		else
			status = FSSH_EOPNOTSUPP;
 
		put_fd(descriptor);
	}
 
	return status;
}
 
 
int
_kern_open_attr_dir(int fd, const char *path)
{
	KPath pathBuffer(FSSH_B_PATH_NAME_LENGTH + 1);
	if (pathBuffer.InitCheck() != FSSH_B_OK)
		return FSSH_B_NO_MEMORY;
 
	if (path != NULL)
		pathBuffer.SetTo(path);
 
	return attr_dir_open(fd, path ? pathBuffer.LockBuffer() : NULL, true);
}
 
 
int
_kern_create_attr(int fd, const char *name, uint32_t type, int openMode)
{
	return attr_create(fd, name, type, openMode, true);
}
 
 
int
_kern_open_attr(int fd, const char *name, int openMode)
{
	return attr_open(fd, name, openMode, true);
}
 
 
fssh_status_t
_kern_remove_attr(int fd, const char *name)
{
	return attr_remove(fd, name, true);
}
 
 
fssh_status_t
_kern_rename_attr(int fromFile, const char *fromName, int toFile, const char *toName)
{
	return attr_rename(fromFile, fromName, toFile, toName, true);
}
 
 
int
_kern_open_index_dir(fssh_dev_t device)
{
	return index_dir_open(device, true);
}
 
 
fssh_status_t
_kern_create_index(fssh_dev_t device, const char *name, uint32_t type, uint32_t flags)
{
	return index_create(device, name, type, flags, true);
}
 
 
fssh_status_t
_kern_read_index_stat(fssh_dev_t device, const char *name, fssh_struct_stat *stat)
{
	return index_name_read_stat(device, name, stat, true);
}
 
 
fssh_status_t
_kern_remove_index(fssh_dev_t device, const char *name)
{
	return index_remove(device, name, true);
}
 
 
fssh_status_t
_kern_getcwd(char *buffer, fssh_size_t size)
{
	TRACE(("_kern_getcwd: buf %p, %ld\n", buffer, size));
 
	// Call vfs to get current working directory
	return get_cwd(buffer, size, true);
}
 
 
fssh_status_t
_kern_setcwd(int fd, const char *path)
{
	KPath pathBuffer(FSSH_B_PATH_NAME_LENGTH + 1);
	if (pathBuffer.InitCheck() != FSSH_B_OK)
		return FSSH_B_NO_MEMORY;
 
	if (path != NULL)
		pathBuffer.SetTo(path);
 
	return set_cwd(fd, path != NULL ? pathBuffer.LockBuffer() : NULL, true);
}
 
 
fssh_status_t
_kern_initialize_volume(const char* fsName, const char *partition,
	const char *name, const char *parameters)
{
	if (!fsName || ! partition)
		return FSSH_B_BAD_VALUE;
 
	// The partition argument should point to a real file/device.
 
	// open partition
	int fd = fssh_open(partition, FSSH_O_RDWR);
	if (fd < 0)
		return fssh_errno;
 
	// get the file system module
	fssh_file_system_module_info* fsModule = get_file_system(fsName);
	if (fsModule == NULL) {
		fssh_close(fd);
		return FSSH_ENODEV;
	}
 
	// initialize
	fssh_status_t status;
	if (fsModule->initialize) {
		status = (*fsModule->initialize)(fd, -1, name, parameters, 0, -1);
			// We've got no partition or job IDs -- the FS will hopefully
			// ignore that.
			// TODO: Get the actual size!
	} else
		status = FSSH_B_NOT_SUPPORTED;
 
	// put the file system module, close partition
	put_file_system(fsModule);
	fssh_close(fd);
 
	return status;
}
 
 
fssh_status_t
_kern_entry_ref_to_path(fssh_dev_t device, fssh_ino_t inode, const char *leaf,
	char* path, fssh_size_t pathLength)
{
	return vfs_entry_ref_to_path(device, inode, leaf, true, path, pathLength);
}
 
 
int
_kern_open_query(fssh_dev_t device, const char *query, fssh_size_t queryLength,
	uint32_t flags, fssh_port_id port, int32_t token)
{
	return query_open(device, query, flags, port, token, false);
}
 
 
}	// namespace FSShell
 
 
#include "vfs_request_io.cpp"

V576 Incorrect format. Consider checking the third actual argument of the 'fssh_dprintf' function. The memsize type argument is expected.

V576 Incorrect format. Consider checking the third actual argument of the 'fssh_panic' function. The memsize type argument is expected.

V576 Incorrect format. Consider checking the third actual argument of the 'fssh_panic' function. The memsize type argument is expected.

V576 Incorrect format. Consider checking the third actual argument of the 'fssh_panic' function. The memsize type argument is expected.