haiku/src/kernel/core/fs/vfs.cpp
Axel Dörfler 7adec93fe8 Added some minor debug output, and a ToDo comment for file_seek().
git-svn-id: file:///srv/svn/repos/haiku/trunk/current@9714 a95241bf-73f2-0310-859d-f6bbb57e9c96
2004-11-01 19:48:40 +00:00

5837 lines
137 KiB
C++
Executable File

/*
** Copyright 2002-2004, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
** Distributed under the terms of the Haiku 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 <OS.h>
#include <StorageDefs.h>
#include <fs_info.h>
#include <fs_interface.h>
#include <disk_device_manager/KDiskDevice.h>
#include <disk_device_manager/KDiskDeviceManager.h>
#include <disk_device_manager/KDiskDeviceUtils.h>
#include <disk_device_manager/KDiskSystem.h>
#include <KPath.h>
#include <syscalls.h>
#include <boot/kernel_args.h>
#include <vfs.h>
#include <vm.h>
#include <vm_cache.h>
#include <khash.h>
#include <lock.h>
#include <kerrors.h>
#include <fd.h>
#include <fs/node_monitor.h>
#include <util/kernel_cpp.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/resource.h>
#include <fcntl.h>
#include <limits.h>
#include <stddef.h>
//#define TRACE_VFS
#ifdef TRACE_VFS
# define PRINT(x) dprintf x
# define FUNCTION(x) dprintf x
#else
# define PRINT(x) ;
# define FUNCTION(x) ;
#endif
#define MAX_SYM_LINKS SYMLINKS_MAX
static struct {
const char *path;
const char *target;
} sPredefinedLinks[] = {
{"/system", "/boot/beos/system"},
{"/bin", "/boot/beos/bin"},
{"/etc", "/boot/beos/etc"},
{"/var", "/boot/var"},
{"/tmp", "/boot/tmp"},
{NULL}
};
struct vnode {
struct vnode *next;
struct vm_cache *cache;
mount_id device;
list_link mount_link;
vnode_id id;
fs_vnode private_node;
struct fs_mount *mount;
struct vnode *covered_by;
int32 ref_count;
bool delete_me;
bool busy;
};
struct vnode_hash_key {
mount_id device;
vnode_id vnode;
};
#define FS_CALL(vnode, op) (vnode->mount->fs->op)
#define FS_MOUNT_CALL(mount, op) (mount->fs->op)
struct fs_mount {
struct fs_mount *next;
file_system_info *fs;
mount_id id;
void *cookie;
char *device_name;
char *fs_name;
recursive_lock rlock; // guards the vnodes list
struct vnode *root_vnode;
struct vnode *covers_vnode;
KPartition *partition;
struct list vnodes;
bool unmounting;
bool owns_file_device;
};
// RecursiveLockLocking
class RecursiveLockLocking {
public:
inline bool Lock(recursive_lock *lockable)
{
recursive_lock_lock(lockable);
return true;
}
inline void Unlock(recursive_lock *lockable)
{
recursive_lock_unlock(lockable);
}
};
// RecursiveLocker
typedef AutoLocker<recursive_lock, RecursiveLockLocking> RecursiveLocker;
static 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 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 recursive_lock sMountOpLock;
/** \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.
The thread trying to lock the mutex must not hold sMountMutex.
*/
static mutex sVnodeMutex;
#define VNODE_HASH_TABLE_SIZE 1024
static hash_table *sVnodeTable;
static struct vnode *sRoot;
#define MOUNTS_HASH_TABLE_SIZE 16
static hash_table *sMountsTable;
static mount_id sNextMountID = 1;
// This can be used by other code to see if there is a boot file system already
dev_t gBootDevice = -1;
/* function declarations */
// file descriptor operation prototypes
static status_t file_read(struct file_descriptor *, off_t pos, void *buffer, size_t *);
static status_t file_write(struct file_descriptor *, off_t pos, const void *buffer, size_t *);
static off_t file_seek(struct file_descriptor *, off_t pos, int seek_type);
static void file_free_fd(struct file_descriptor *);
static status_t file_close(struct file_descriptor *);
static status_t dir_read(struct file_descriptor *, struct dirent *buffer, size_t bufferSize, uint32 *_count);
static status_t dir_read(struct vnode *vnode, fs_cookie cookie, struct dirent *buffer, size_t bufferSize, uint32 *_count);
static status_t dir_rewind(struct file_descriptor *);
static void dir_free_fd(struct file_descriptor *);
static status_t dir_close(struct file_descriptor *);
static status_t attr_dir_read(struct file_descriptor *, struct dirent *buffer, size_t bufferSize, uint32 *_count);
static status_t attr_dir_rewind(struct file_descriptor *);
static void attr_dir_free_fd(struct file_descriptor *);
static status_t attr_dir_close(struct file_descriptor *);
static status_t attr_read(struct file_descriptor *, off_t pos, void *buffer, size_t *);
static status_t attr_write(struct file_descriptor *, off_t pos, const void *buffer, size_t *);
static off_t attr_seek(struct file_descriptor *, off_t pos, int seek_type);
static void attr_free_fd(struct file_descriptor *);
static status_t attr_close(struct file_descriptor *);
static status_t attr_read_stat(struct file_descriptor *, struct stat *);
static status_t attr_write_stat(struct file_descriptor *, const struct stat *, int statMask);
static status_t index_dir_read(struct file_descriptor *, struct dirent *buffer, size_t bufferSize, uint32 *_count);
static status_t index_dir_rewind(struct file_descriptor *);
static void index_dir_free_fd(struct file_descriptor *);
static status_t index_dir_close(struct file_descriptor *);
static status_t common_ioctl(struct file_descriptor *, ulong, void *buf, size_t len);
static status_t common_read_stat(struct file_descriptor *, struct stat *);
static status_t common_write_stat(struct file_descriptor *, const struct stat *, int statMask);
static status_t vnode_path_to_vnode(struct vnode *vnode, char *path,
bool traverseLeafLink, int count, struct vnode **_vnode, int *_type);
static status_t dir_vnode_to_path(struct vnode *vnode, char *buffer, size_t bufferSize);
static status_t fd_and_path_to_vnode(int fd, char *path, bool traverseLeafLink,
struct vnode **_vnode, bool kernel);
static void inc_vnode_ref_count(struct vnode *vnode);
static 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, // select()
NULL, // deselect()
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
// 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;
};
class FDCloser {
public:
FDCloser() : fFD(-1), fKernel(true) {}
FDCloser(int fd, bool kernel) : fFD(fd), fKernel(kernel) {}
~FDCloser()
{
Close();
}
void SetTo(int fd, bool kernel)
{
Close();
fFD = fd;
fKernel = kernel;
}
void Close()
{
if (fFD >= 0) {
if (fKernel)
_kern_close(fFD);
else
_user_close(fFD);
fFD = -1;
}
}
int Detach()
{
int fd = fFD;
fFD = -1;
return fd;
}
private:
int fFD;
bool fKernel;
};
static int
mount_compare(void *_m, const void *_key)
{
struct fs_mount *mount = (fs_mount *)_m;
const mount_id *id = (mount_id *)_key;
if (mount->id == *id)
return 0;
return -1;
}
static uint32
mount_hash(void *_m, const void *_key, uint32 range)
{
struct fs_mount *mount = (fs_mount *)_m;
const mount_id *id = (mount_id *)_key;
if (mount)
return mount->id % range;
return *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(mount_id id)
{
ASSERT_LOCKED_MUTEX(&sMountMutex);
return (fs_mount *)hash_lookup(sMountsTable, (void *)&id);
}
static struct fs_mount *
get_mount(mount_id id)
{
struct fs_mount *mount;
mutex_lock(&sMountMutex);
mount = find_mount(id);
if (mount) {
// ToDo: the volume is locked (against removal) by locking
// its root node - investigate if that's a good idea
if (mount->root_vnode)
inc_vnode_ref_count(mount->root_vnode);
else
mount = NULL;
}
mutex_unlock(&sMountMutex);
return mount;
}
static void
put_mount(struct fs_mount *mount)
{
if (mount)
put_vnode(mount->root_vnode);
}
static status_t
put_file_system(file_system_info *fs)
{
return put_module(fs->module_info.name);
}
static file_system_info *
get_file_system(const char *fsName)
{
// construct module name (we currently support only one API)
char name[B_FILE_NAME_LENGTH];
snprintf(name, sizeof(name), "file_systems/%s/v1", fsName);
file_system_info *info;
if (get_module(name, (module_info **)&info) != B_OK)
return NULL;
return info;
}
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
vnode_hash(void *_vnode, const void *_key, uint32 range)
{
struct vnode *vnode = (struct vnode *)_vnode;
const struct vnode_hash_key *key = (vnode_hash_key *)_key;
#define VHASH(mountid, vnodeid) (((uint32)((vnodeid) >> 32) + (uint32)(vnodeid)) ^ (uint32)(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)
{
recursive_lock_lock(&mount->rlock);
list_add_link_to_head(&mount->vnodes, &vnode->mount_link);
recursive_lock_unlock(&mount->rlock);
}
static void
remove_vnode_from_mount_list(struct vnode *vnode, struct fs_mount *mount)
{
recursive_lock_lock(&mount->rlock);
list_remove_link(&vnode->mount_link);
vnode->mount_link.next = vnode->mount_link.prev = NULL;
recursive_lock_unlock(&mount->rlock);
}
static status_t
create_new_vnode(struct vnode **_vnode, mount_id mountID, vnode_id vnodeID)
{
FUNCTION(("create_new_vnode()\n"));
struct vnode *vnode = (struct vnode *)malloc(sizeof(struct vnode));
if (vnode == NULL)
return B_NO_MEMORY;
// initialize basic values
memset(vnode, 0, sizeof(struct vnode));
vnode->device = mountID;
vnode->id = vnodeID;
// add the vnode to the mount structure
mutex_lock(&sMountMutex);
vnode->mount = find_mount(mountID);
if (!vnode->mount || vnode->mount->unmounting) {
mutex_unlock(&sMountMutex);
free(vnode);
return B_ENTRY_NOT_FOUND;
}
hash_insert(sVnodeTable, vnode);
add_vnode_to_mount_list(vnode, vnode->mount);
mutex_unlock(&sMountMutex);
vnode->ref_count = 1;
*_vnode = vnode;
return B_OK;
}
/** \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 B_OK, if everything went fine, an error code otherwise.
*/
static status_t
dec_vnode_ref_count(struct vnode *vnode, bool reenter)
{
int err;
int old_ref;
mutex_lock(&sVnodeMutex);
if (vnode->busy == true)
panic("dec_vnode_ref_count called on vnode that was busy! vnode %p\n", vnode);
old_ref = atomic_add(&vnode->ref_count, -1);
PRINT(("dec_vnode_ref_count: vnode %p, ref now %ld\n", vnode, vnode->ref_count));
if (old_ref == 1) {
vnode->busy = true;
mutex_unlock(&sVnodeMutex);
/* if we have a vm_cache attached, remove it */
if (vnode->cache)
vm_cache_release_ref((vm_cache_ref *)vnode->cache);
vnode->cache = NULL;
if (vnode->delete_me)
FS_CALL(vnode, remove_vnode)(vnode->mount->cookie, vnode->private_node, reenter);
else
FS_CALL(vnode, put_vnode)(vnode->mount->cookie, vnode->private_node, reenter);
remove_vnode_from_mount_list(vnode, vnode->mount);
mutex_lock(&sVnodeMutex);
hash_remove(sVnodeTable, vnode);
mutex_unlock(&sVnodeMutex);
free(vnode);
err = 1;
} else {
mutex_unlock(&sVnodeMutex);
err = 0;
}
return err;
}
/** \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)
{
atomic_add(&vnode->ref_count, 1);
PRINT(("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(mount_id mountID, 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 B_OK, if everything when fine, an error code otherwise.
*/
static status_t
get_vnode(mount_id mountID, vnode_id vnodeID, struct vnode **_vnode, int reenter)
{
FUNCTION(("get_vnode: mountid %ld vnid 0x%Lx %p\n", mountID, vnodeID, _vnode));
mutex_lock(&sVnodeMutex);
restart:
struct vnode *vnode = lookup_vnode(mountID, vnodeID);
if (vnode && vnode->busy) {
// ToDo: this is an endless loop if the vnode is not
// becoming unbusy anymore (for whatever reason)
mutex_unlock(&sVnodeMutex);
snooze(10000); // 10 ms
mutex_lock(&sVnodeMutex);
goto restart;
}
PRINT(("get_vnode: tried to lookup vnode, got %p\n", vnode));
status_t status;
if (vnode) {
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 < B_OK)
goto err;
vnode->busy = true;
mutex_unlock(&sVnodeMutex);
status = FS_CALL(vnode, get_vnode)(vnode->mount->cookie, vnodeID, &vnode->private_node, reenter);
if (status < B_OK || vnode->private_node == NULL) {
remove_vnode_from_mount_list(vnode, vnode->mount);
if (status == B_NO_ERROR)
status = B_BAD_VALUE;
}
mutex_lock(&sVnodeMutex);
if (status < B_OK)
goto err1;
vnode->busy = false;
}
mutex_unlock(&sVnodeMutex);
PRINT(("get_vnode: returning %p\n", vnode));
*_vnode = vnode;
return B_OK;
err1:
hash_remove(sVnodeTable, vnode);
remove_vnode_from_mount_list(vnode, vnode->mount);
err:
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);
}
/** \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;
recursive_lock_lock(&sMountOpLock);
if (vnode->covered_by) {
volumeRoot = vnode->covered_by;
inc_vnode_ref_count(volumeRoot);
}
recursive_lock_unlock(&sMountOpLock);
return volumeRoot;
}
/** \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.
\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;
recursive_lock_lock(&sMountOpLock);
struct fs_mount *mount = vnode->mount;
if (vnode == mount->root_vnode && mount->covers_vnode) {
mountPoint = mount->covers_vnode;
inc_vnode_ref_count(mountPoint);
}
recursive_lock_unlock(&sMountOpLock);
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 B_FILE_NAME_LENGTH at least.
* \return \c B_OK, if everything went fine, \c B_NAME_TOO_LONG, if the leaf
* name is longer than \c B_FILE_NAME_LENGTH.
*/
static status_t
get_dir_path_and_leaf(char *path, char *filename)
{
char *p = 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 (strlcpy(filename, path, B_FILE_NAME_LENGTH) >= B_FILE_NAME_LENGTH)
return B_NAME_TOO_LONG;
strcpy(path, ".");
} else {
p++;
if (*p == '\0') {
// special case: the path ends in '/'
strcpy(filename, ".");
} else {
// normal leaf: replace the leaf portion of the path with a '.'
if (strlcpy(filename, p, B_FILE_NAME_LENGTH)
>= B_FILE_NAME_LENGTH) {
return B_NAME_TOO_LONG;
}
}
p[0] = '.';
p[1] = '\0';
}
return B_OK;
}
static status_t
entry_ref_to_vnode(mount_id mountID, vnode_id directoryID, const char *name, struct vnode **_vnode)
{
char clonedName[B_FILE_NAME_LENGTH + 1];
if (strlcpy(clonedName, name, B_FILE_NAME_LENGTH) >= B_FILE_NAME_LENGTH)
return B_NAME_TOO_LONG;
// get the directory vnode and let vnode_path_to_vnode() do the rest
struct vnode *directory;
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 status_t
vnode_path_to_vnode(struct vnode *vnode, char *path, bool traverseLeafLink,
int count, struct vnode **_vnode, int *_type)
{
status_t status = 0;
int type = 0;
FUNCTION(("vnode_path_to_vnode(vnode = %p, path = %s)\n", vnode, path));
if (!path)
return B_BAD_VALUE;
while (true) {
struct vnode *nextVnode;
vnode_id vnodeID;
char *nextPath;
PRINT(("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 (!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 (FS_CALL(vnode, access))
status = FS_CALL(vnode, access)(vnode->mount->cookie, vnode->private_node, X_OK);
// Tell the filesystem to get the vnode of this path component (if we got the
// permission from the call above)
if (status >= B_OK)
status = FS_CALL(vnode, lookup)(vnode->mount->cookie, vnode->private_node, path, &vnodeID, &type);
if (status < B_OK) {
put_vnode(vnode);
return status;
}
// Lookup the vnode, the call to fs_lookup should have caused a get_vnode to be called
// from inside the filesystem, thus the vnode would have to be in the list and it's
// ref count incremented at this point
mutex_lock(&sVnodeMutex);
nextVnode = lookup_vnode(vnode->device, vnodeID);
mutex_unlock(&sVnodeMutex);
if (!nextVnode) {
// pretty screwed up here - the file system found the vnode, but the hash
// lookup failed, so our internal structures are messed up
panic("path_to_vnode: could not lookup vnode (mountid 0x%lx vnid 0x%Lx)\n", vnode->device, vnodeID);
put_vnode(vnode);
return B_ENTRY_NOT_FOUND;
}
// If the new node is a symbolic link, resolve it (if we've been told to do it)
if (S_ISLNK(type) && !(!traverseLeafLink && nextPath[0] == '\0')) {
char *buffer;
PRINT(("traverse link\n"));
// it's not exactly nice style using goto in this way, but hey, it works :-/
if (count + 1 > MAX_SYM_LINKS) {
status = B_LINK_LIMIT;
goto resolve_link_error;
}
buffer = (char *)malloc(B_PATH_NAME_LENGTH);
if (buffer == NULL) {
status = B_NO_MEMORY;
goto resolve_link_error;
}
status = FS_CALL(nextVnode, read_link)(nextVnode->mount->cookie, nextVnode->private_node, buffer, B_PATH_NAME_LENGTH);
if (status < 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, _type);
free(buffer);
if (status < B_OK) {
put_vnode(vnode);
return status;
}
}
// 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 (_type)
*_type = type;
return B_OK;
}
static status_t
path_to_vnode(char *path, bool traverseLink, struct vnode **_vnode, bool kernel)
{
struct vnode *start;
FUNCTION(("path_to_vnode(path = \"%s\")\n", path));
if (!path)
return B_BAD_VALUE;
// figure out if we need to start at root or at cwd
if (*path == '/') {
while (*++path == '/')
;
start = sRoot;
inc_vnode_ref_count(start);
} else {
struct io_context *context = get_current_io_context(kernel);
mutex_lock(&context->io_mutex);
start = context->cwd;
inc_vnode_ref_count(start);
mutex_unlock(&context->io_mutex);
}
return vnode_path_to_vnode(start, path, traverseLink, 0, _vnode, NULL);
}
/** 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 status_t
path_to_dir_vnode(char *path, struct vnode **_vnode, char *filename, bool kernel)
{
status_t status = get_dir_path_and_leaf(path, filename);
if (status != B_OK)
return status;
return path_to_vnode(path, true, _vnode, 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 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 B_OK, if everything went fine, another error code otherwise.
*/
static status_t
fd_and_path_to_dir_vnode(int fd, char *path, struct vnode **_vnode,
char *filename, bool kernel)
{
if (!path)
return B_BAD_VALUE;
if (fd < 0)
return path_to_dir_vnode(path, _vnode, filename, kernel);
status_t status = get_dir_path_and_leaf(path, filename);
if (status != B_OK)
return status;
return fd_and_path_to_vnode(fd, path, true, _vnode, kernel);
}
static status_t
get_vnode_name(struct vnode *vnode, struct vnode *parent,
char *name, size_t nameSize)
{
VNodePutter vnodePutter;
// See if vnode is the root of a mount and move to the covered
// vnode so we get the underlying file system
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 (FS_CALL(vnode, get_vnode_name)) {
// The FS supports getting the name of a vnode.
return FS_CALL(vnode, get_vnode_name)(vnode->mount->cookie,
vnode->private_node, name, nameSize);
}
// The FS doesn't support getting the name of a vnode. So we search the
// parent directory for the vnode.
fs_cookie cookie;
status_t status = FS_CALL(parent, open_dir)(parent->mount->cookie,
parent->private_node, &cookie);
if (status >= B_OK) {
char buffer[sizeof(struct dirent) + B_FILE_NAME_LENGTH];
struct dirent *dirent = (struct dirent *)buffer;
while (true) {
uint32 num = 1;
status = dir_read(parent, cookie, dirent, sizeof(buffer), &num);
if (status < B_OK)
break;
if (vnode->id == dirent->d_ino)
// found correct entry!
if (strlcpy(name, dirent->d_name, nameSize) >= nameSize)
status = B_BUFFER_OVERFLOW;
break;
}
FS_CALL(vnode, close_dir)(vnode->mount->cookie, vnode->private_node, cookie);
}
return status;
}
/** 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 status_t
dir_vnode_to_path(struct vnode *vnode, char *buffer, size_t bufferSize)
{
FUNCTION(("dir_vnode_to_path(%p, %p, %lu)\n", vnode, buffer, bufferSize));
/* this implementation is currently bound to B_PATH_NAME_LENGTH */
char path[B_PATH_NAME_LENGTH];
int32 insert = sizeof(path);
int32 maxLevel = 256;
int32 length;
status_t status;
if (vnode == NULL || buffer == NULL)
return EINVAL;
// 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 dirent) + B_FILE_NAME_LENGTH];
char *name = &((struct dirent *)nameBuffer)->d_name[0];
struct vnode *parentVnode;
vnode_id parentID, id;
int type;
// lookup the parent vnode
status = FS_CALL(vnode, lookup)(vnode->mount->cookie, vnode->private_node, "..", &parentID, &type);
if (status < B_OK)
goto out;
mutex_lock(&sVnodeMutex);
parentVnode = lookup_vnode(vnode->device, parentID);
mutex_unlock(&sVnodeMutex);
if (parentVnode == NULL) {
panic("dir_vnode_to_path: could not lookup vnode (mountid 0x%lx vnid 0x%Lx)\n", vnode->device, parentID);
status = B_ENTRY_NOT_FOUND;
goto out;
}
// 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);
// Does the file system support getting the name of a vnode?
// If so, get it here...
if (status == B_OK && FS_CALL(vnode, get_vnode_name))
status = FS_CALL(vnode, get_vnode_name)(vnode->mount->cookie, vnode->private_node, name, B_FILE_NAME_LENGTH);
// ... if not, find it out later (by iterating through
// the parent directory, searching for the id)
id = vnode->id;
// release the current vnode, we only need its parent from now on
put_vnode(vnode);
vnode = parentVnode;
if (status < B_OK)
goto out;
// 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 = ELOOP;
goto out;
}
if (hitRoot) {
// we have reached "/", which means we have constructed the full
// path
break;
}
if (!FS_CALL(vnode, get_vnode_name)) {
// If we haven't got the vnode's name yet, we have to search for it
// in the parent directory now
fs_cookie cookie;
status = FS_CALL(vnode, open_dir)(vnode->mount->cookie, vnode->private_node, &cookie);
if (status >= B_OK) {
struct dirent *dirent = (struct dirent *)nameBuffer;
while (true) {
uint32 num = 1;
status = dir_read(vnode, cookie, dirent, sizeof(nameBuffer),
&num);
if (status < B_OK)
break;
if (id == dirent->d_ino)
// found correct entry!
break;
}
FS_CALL(vnode, close_dir)(vnode->mount->cookie, vnode->private_node, cookie);
}
if (status < B_OK)
goto out;
}
// add the name infront of the current path
name[B_FILE_NAME_LENGTH - 1] = '\0';
length = strlen(name);
insert -= length;
if (insert <= 0) {
status = ENOBUFS;
goto out;
}
memcpy(path + insert, name, length);
path[--insert] = '/';
}
// the root dir will result in an empty path: fix it
if (path[insert] == '\0')
path[--insert] = '/';
PRINT((" path is: %s\n", path + insert));
// copy the path to the output buffer
length = sizeof(path) - insert;
if (length <= (int)bufferSize)
memcpy(buffer, path + insert, length);
else
status = 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 status_t
check_path(char *to)
{
int32 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 > B_FILE_NAME_LENGTH)
return B_NAME_TOO_LONG;
}
if (length == 0)
return B_ENTRY_NOT_FOUND;
// complete path if there is a slash at the end
if (*(to - 1) == '/') {
if (length > B_PATH_NAME_LENGTH - 2)
return B_NAME_TOO_LONG;
to[0] = '.';
to[1] = '\0';
}
return 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 (descriptor->u.vnode == 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 = descriptor->u.vnode;
if (vnode != NULL)
inc_vnode_ref_count(vnode);
put_fd(descriptor);
return vnode;
}
static status_t
fd_and_path_to_vnode(int fd, char *path, bool traverseLeafLink, struct vnode **_vnode, bool kernel)
{
if (fd < 0 && !path)
return B_BAD_VALUE;
status_t status;
struct vnode *vnode = NULL;
if (fd < 0 || path[0] == '/') {
// no FD or absolute path
status = path_to_vnode(path, traverseLeafLink, &vnode, kernel);
} else {
// FD only, or FD + relative path
vnode = get_vnode_from_fd(fd, kernel);
if (!vnode)
return B_FILE_ERROR;
status = vnode_path_to_vnode(vnode, path, traverseLeafLink, 0, &vnode,
NULL);
}
if (status == B_OK)
*_vnode = vnode;
return status;
}
static int
get_new_fd(int type, struct vnode *vnode, fs_cookie cookie, int openMode, bool kernel)
{
struct file_descriptor *descriptor;
int fd;
descriptor = alloc_fd();
if (!descriptor)
return B_NO_MEMORY;
descriptor->u.vnode = vnode;
descriptor->cookie = cookie;
switch (type) {
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;
case FDTYPE_INDEX_DIR:
descriptor->ops = &sIndexDirectoryOps;
break;
default:
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 B_NO_MORE_FDS;
}
return fd;
}
// #pragma mark -
// Public VFS API
extern "C" status_t
new_vnode(mount_id mountID, vnode_id vnodeID, fs_vnode privateNode)
{
FUNCTION(("new_vnode()\n"));
if (privateNode == NULL)
return B_BAD_VALUE;
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(mountID, vnodeID);
if (vnode != NULL)
panic("vnode %ld:%Ld already exists (node = %p, vnode->node = %p)!", mountID, vnodeID, privateNode, vnode->private_node);
status_t status = create_new_vnode(&vnode, mountID, vnodeID);
if (status == B_OK)
vnode->private_node = privateNode;
PRINT(("returns: %s\n", strerror(status)));
mutex_unlock(&sVnodeMutex);
return status;
}
extern "C" status_t
get_vnode(mount_id mountID, vnode_id vnodeID, fs_vnode *_fsNode)
{
struct vnode *vnode;
int status = get_vnode(mountID, vnodeID, &vnode, true);
if (status < 0)
return status;
*_fsNode = vnode->private_node;
return B_OK;
}
extern "C" status_t
put_vnode(mount_id mountID, vnode_id vnodeID)
{
struct vnode *vnode;
mutex_lock(&sVnodeMutex);
vnode = lookup_vnode(mountID, vnodeID);
mutex_unlock(&sVnodeMutex);
if (vnode)
dec_vnode_ref_count(vnode, true);
return B_OK;
}
extern "C" status_t
remove_vnode(mount_id mountID, vnode_id vnodeID)
{
struct vnode *vnode;
mutex_lock(&sVnodeMutex);
vnode = lookup_vnode(mountID, vnodeID);
if (vnode)
vnode->delete_me = true;
mutex_unlock(&sVnodeMutex);
return B_OK;
}
extern "C" status_t
unremove_vnode(mount_id mountID, vnode_id vnodeID)
{
struct vnode *vnode;
mutex_lock(&sVnodeMutex);
vnode = lookup_vnode(mountID, vnodeID);
if (vnode)
vnode->delete_me = false;
mutex_unlock(&sVnodeMutex);
return B_OK;
}
// #pragma mark -
// Functions the VFS exports for other parts of the kernel
void
vfs_vnode_acquire_ref(void *vnode)
{
FUNCTION(("vfs_vnode_acquire_ref: vnode 0x%p\n", vnode));
inc_vnode_ref_count((struct vnode *)vnode);
}
void
vfs_vnode_release_ref(void *vnode)
{
FUNCTION(("vfs_vnode_release_ref: vnode 0x%p\n", vnode));
dec_vnode_ref_count((struct vnode *)vnode, false);
}
/** 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.
*/
extern "C" 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 B_FILE_ERROR;
*_cookie = descriptor->cookie;
return B_OK;
}
extern "C" int
vfs_get_vnode_from_fd(int fd, bool kernel, void **vnode)
{
*vnode = get_vnode_from_fd(fd, kernel);
if (*vnode == NULL)
return B_FILE_ERROR;
return B_NO_ERROR;
}
extern "C" status_t
vfs_get_vnode_from_path(const char *path, bool kernel, void **_vnode)
{
struct vnode *vnode;
status_t status;
char buffer[B_PATH_NAME_LENGTH + 1];
PRINT(("vfs_get_vnode_from_path: entry. path = '%s', kernel %d\n", path, kernel));
strlcpy(buffer, path, sizeof(buffer));
status = path_to_vnode(buffer, true, &vnode, kernel);
if (status < B_OK)
return status;
*_vnode = vnode;
return B_OK;
}
extern "C" status_t
vfs_get_vnode(mount_id mountID, vnode_id vnodeID, void **_vnode)
{
// ToDo: this currently doesn't use the sVnodeMutex lock - that's
// because it's only called from file_cache_create() with that
// lock held anyway (as it should be called from fs_read_vnode()).
// Find a better solution!
struct vnode *vnode = lookup_vnode(mountID, vnodeID);
if (vnode == NULL)
return B_ERROR;
*_vnode = vnode;
return B_OK;
//return get_vnode(mountID, vnodeID, (struct vnode **)_vnode, true);
}
extern "C" status_t
vfs_get_fs_node_from_path(mount_id mountID, const char *path, bool kernel, void **_node)
{
char buffer[B_PATH_NAME_LENGTH + 1];
struct vnode *vnode;
status_t status;
PRINT(("vfs_get_fs_node_from_path(mountID = %ld, path = \"%s\", kernel %d)\n", mountID, path, kernel));
strlcpy(buffer, path, sizeof(buffer));
status = path_to_vnode(buffer, true, &vnode, kernel);
if (status < B_OK)
return status;
if (vnode->device != mountID) {
// wrong mount ID - must not gain access on foreign file system nodes
put_vnode(vnode);
return B_BAD_VALUE;
}
*_node = vnode->private_node;
return B_OK;
}
status_t
vfs_get_module_path(const char *basePath, const char *moduleName, char *pathBuffer,
size_t bufferSize)
{
struct vnode *dir, *file;
status_t status;
size_t length;
char *path;
if (bufferSize == 0 || strlcpy(pathBuffer, basePath, bufferSize) >= bufferSize)
return B_BUFFER_OVERFLOW;
status = path_to_vnode(pathBuffer, true, &dir, true);
if (status < B_OK)
return status;
length = strlen(pathBuffer);
if (pathBuffer[length - 1] != '/') {
pathBuffer[length] = '/';
length++;
}
path = pathBuffer + length;
bufferSize -= length;
while (moduleName) {
int type;
char *nextPath = strchr(moduleName, '/');
if (nextPath == NULL)
length = strlen(moduleName);
else
length = nextPath - moduleName;
if (length + 1 >= bufferSize) {
status = B_BUFFER_OVERFLOW;
goto err;
}
memcpy(path, moduleName, length);
path[length] = '\0';
moduleName = nextPath;
status = vnode_path_to_vnode(dir, path, true, 0, &file, &type);
if (status < B_OK)
goto err;
put_vnode(dir);
if (S_ISDIR(type)) {
// goto the next directory
path[length] = '/';
path[length + 1] = '\0';
path += length + 1;
dir = file;
} else if (S_ISREG(type)) {
// it's a file so it should be what we've searched for
put_vnode(file);
return B_OK;
} else {
PRINT(("vfs_get_module_path(): something is strange here: %d...\n", type));
status = B_ERROR;
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 = 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 B_OK if everything went fine, another error code otherwise.
*/
status_t
vfs_normalize_path(const char *path, char *buffer, size_t bufferSize,
bool kernel)
{
if (!path || !buffer || bufferSize < 1)
return B_BAD_VALUE;
PRINT(("vfs_normalize_path(`%s')\n", path));
// copy the supplied path to the stack, so it can be modified
char mutablePath[B_PATH_NAME_LENGTH + 1];
if (strlcpy(mutablePath, path, B_PATH_NAME_LENGTH) >= B_PATH_NAME_LENGTH)
return B_NAME_TOO_LONG;
// get the dir vnode and the leaf name
struct vnode *dirNode;
char leaf[B_FILE_NAME_LENGTH];
status_t error = path_to_dir_vnode(mutablePath, &dirNode, leaf, kernel);
if (error != B_OK)
{
PRINT(("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 = (strcmp(leaf, ".") == 0 || strcmp(leaf, "..") == 0);
if (isDir)
error = vnode_path_to_vnode(dirNode, leaf, false, 0, &dirNode, NULL);
if (error != B_OK)
{
PRINT(("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 < B_OK)
{
PRINT(("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 ((strcmp(buffer, "/") != 0
&& strlcat(buffer, "/", bufferSize) >= bufferSize)
|| strlcat(buffer, leaf, bufferSize) >= bufferSize) {
return B_NAME_TOO_LONG;
}
}
PRINT(("vfs_normalize_path() -> `%s'\n", buffer));
return B_OK;
}
int
vfs_put_vnode_ptr(void *_vnode)
{
struct vnode *vnode = (struct vnode *)_vnode;
put_vnode(vnode);
return 0;
}
extern "C" bool
vfs_can_page(void *_vnode, void *cookie)
{
struct vnode *vnode = (struct vnode *)_vnode;
FUNCTION(("vfs_canpage: vnode 0x%p\n", vnode));
if (FS_CALL(vnode, can_page))
return FS_CALL(vnode, can_page)(vnode->mount->cookie, vnode->private_node, cookie);
return false;
}
extern "C" status_t
vfs_read_pages(void *_vnode, void *cookie, off_t pos, const iovec *vecs, size_t count, size_t *_numBytes)
{
struct vnode *vnode = (struct vnode *)_vnode;
FUNCTION(("vfs_read_pages: vnode %p, vecs %p, pos %Ld\n", vnode, vecs, pos));
return FS_CALL(vnode, read_pages)(vnode->mount->cookie, vnode->private_node, cookie, pos, vecs, count, _numBytes);
}
extern "C" status_t
vfs_write_pages(void *_vnode, void *cookie, off_t pos, const iovec *vecs, size_t count, size_t *_numBytes)
{
struct vnode *vnode = (struct vnode *)_vnode;
FUNCTION(("vfs_write_pages: vnode %p, vecs %p, pos %Ld\n", vnode, vecs, pos));
return FS_CALL(vnode, write_pages)(vnode->mount->cookie, vnode->private_node, cookie, pos, vecs, count, _numBytes);
}
extern "C" status_t
vfs_get_vnode_cache(void *_vnode, void **_cache)
{
struct vnode *vnode = (struct vnode *)_vnode;
if (vnode->cache != NULL) {
*_cache = vnode->cache;
return B_OK;
}
mutex_lock(&sVnodeMutex);
status_t status = B_OK;
// The cache could have been created in the meantime
if (vnode->cache == NULL)
status = vm_create_vnode_cache(vnode, (void **)&vnode->cache);
if (status == B_OK)
*_cache = vnode->cache;
mutex_unlock(&sVnodeMutex);
return status;
}
status_t
vfs_get_file_map(void *_vnode, off_t offset, size_t size, file_io_vec *vecs, size_t *_count)
{
struct vnode *vnode = (struct vnode *)_vnode;
FUNCTION(("vfs_get_file_map: vnode %p, vecs %p, offset %Ld, size = %lu\n", vnode, vecs, offset, size));
return FS_CALL(vnode, get_file_map)(vnode->mount->cookie, vnode->private_node, offset, size, vecs, _count);
}
/** Closes all file descriptors of the specified I/O context that
* don't have the O_CLOEXEC flag set.
*/
void
vfs_exec_io_context(void *_context)
{
struct io_context *context = (struct io_context *)_context;
uint32 i;
for (i = 0; i < context->table_size; i++) {
mutex_lock(&context->io_mutex);
struct file_descriptor *descriptor = context->fds[i];
bool remove = false;
if (descriptor != NULL && (descriptor->open_mode & O_CLOEXEC) != 0) {
context->fds[i] = NULL;
context->num_used_fds--;
remove = true;
}
mutex_unlock(&context->io_mutex);
if (remove)
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)
{
size_t tableSize;
struct io_context *context;
struct io_context *parentContext;
context = (io_context *)malloc(sizeof(struct io_context));
if (context == NULL)
return NULL;
memset(context, 0, sizeof(struct io_context));
parentContext = (struct io_context *)_parentContext;
if (parentContext)
tableSize = parentContext->table_size;
else
tableSize = DEFAULT_FD_TABLE_SIZE;
context->fds = (file_descriptor **)malloc(sizeof(struct file_descriptor *) * tableSize);
if (context->fds == NULL) {
free(context);
return NULL;
}
memset(context->fds, 0, sizeof(struct file_descriptor *) * tableSize);
if (mutex_init(&context->io_mutex, "I/O context") < 0) {
free(context->fds);
free(context);
return NULL;
}
// Copy all parent files which don't have the O_CLOEXEC flag set
if (parentContext) {
size_t i;
mutex_lock(&parentContext->io_mutex);
context->cwd = parentContext->cwd;
if (context->cwd)
inc_vnode_ref_count(context->cwd);
for (i = 0; i < tableSize; i++) {
if (parentContext->fds[i] && (parentContext->fds[i]->open_mode & O_CLOEXEC) == 0) {
context->fds[i] = parentContext->fds[i];
atomic_add(&context->fds[i]->ref_count, 1);
}
}
mutex_unlock(&parentContext->io_mutex);
} else {
context->cwd = sRoot;
if (context->cwd)
inc_vnode_ref_count(context->cwd);
}
context->table_size = tableSize;
list_init(&context->node_monitors);
context->max_monitors = MAX_NODE_MONITORS;
return context;
}
int
vfs_free_io_context(void *_ioContext)
{
struct io_context *context = (struct io_context *)_ioContext;
uint32 i;
if (context->cwd)
dec_vnode_ref_count(context->cwd, false);
mutex_lock(&context->io_mutex);
for (i = 0; i < context->table_size; i++) {
if (context->fds[i])
put_fd(context->fds[i]);
}
mutex_unlock(&context->io_mutex);
mutex_destroy(&context->io_mutex);
remove_node_monitors(context);
free(context->fds);
free(context);
return 0;
}
static int
vfs_resize_fd_table(struct io_context *context, const int newSize)
{
void *fds;
int status = B_OK;
if (newSize <= 0 || newSize > MAX_FD_TABLE_SIZE)
return EINVAL;
mutex_lock(&context->io_mutex);
if ((size_t)newSize < context->table_size) {
// shrink the fd table
int i;
// Make sure none of the fds being dropped are in use
for(i = context->table_size; i-- > newSize;) {
if (context->fds[i]) {
status = EBUSY;
goto out;
}
}
fds = malloc(sizeof(struct file_descriptor *) * newSize);
if (fds == NULL) {
status = ENOMEM;
goto out;
}
memcpy(fds, context->fds, sizeof(struct file_descriptor *) * newSize);
} else {
// enlarge the fd table
fds = malloc(sizeof(struct file_descriptor *) * newSize);
if (fds == NULL) {
status = ENOMEM;
goto out;
}
// copy the fd array, and zero the additional slots
memcpy(fds, context->fds, sizeof(void *) * context->table_size);
memset((char *)fds + (sizeof(void *) * context->table_size), 0,
sizeof(void *) * (newSize - context->table_size));
}
free(context->fds);
context->fds = (file_descriptor **)fds;
context->table_size = newSize;
out:
mutex_unlock(&context->io_mutex);
return status;
}
int
vfs_getrlimit(int resource, struct rlimit * rlp)
{
if (!rlp)
return -1;
switch (resource) {
case RLIMIT_NOFILE:
{
struct io_context *ioctx = get_current_io_context(false);
mutex_lock(&ioctx->io_mutex);
rlp->rlim_cur = ioctx->table_size;
rlp->rlim_max = MAX_FD_TABLE_SIZE;
mutex_unlock(&ioctx->io_mutex);
return 0;
}
default:
return -1;
}
}
int
vfs_setrlimit(int resource, const struct rlimit * rlp)
{
if (!rlp)
return -1;
switch (resource) {
case RLIMIT_NOFILE:
return vfs_resize_fd_table(get_current_io_context(false), rlp->rlim_cur);
default:
return -1;
}
}
status_t
vfs_bootstrap_file_systems(void)
{
status_t status;
// bootstrap the root filesystem
status = _kern_mount("/", NULL, "rootfs", 0, NULL);
if (status < B_OK)
panic("error mounting rootfs!\n");
_kern_setcwd(-1, "/");
// bootstrap the devfs
_kern_create_dir(-1, "/dev", 0755);
status = _kern_mount("/dev", NULL, "devfs", 0, NULL);
if (status < B_OK)
panic("error mounting devfs\n");
// bootstrap the pipefs
_kern_create_dir(-1, "/pipe", 0755);
status = _kern_mount("/pipe", NULL, "pipefs", 0, NULL);
if (status < B_OK)
panic("error mounting pipefs\n");
// bootstrap the bootfs (if possible)
_kern_create_dir(-1, "/boot", 0755);
status = _kern_mount("/boot", NULL, "bootfs", 0, NULL);
if (status < B_OK) {
// this is no fatal exception at this point, as we may mount
// a real on disk file system later
dprintf("error mounting bootfs\n");
}
// create some standard links on the rootfs
for (int32 i = 0; sPredefinedLinks[i].path != NULL; i++) {
_kern_create_symlink(-1, sPredefinedLinks[i].path,
sPredefinedLinks[i].target, 0);
// we don't care if it will succeed or not
}
return B_OK;
}
status_t
vfs_mount_boot_file_system()
{
// make the boot partition (and probably others) available
KDiskDeviceManager::CreateDefault();
KDiskDeviceManager *manager = KDiskDeviceManager::Default();
status_t status = manager->InitialDeviceScan();
if (status == B_OK) {
// ToDo: do this for real... (no hacks allowed :))
for (;;) {
snooze(500000);
if (manager->CountJobs() == 0)
break;
}
} else
dprintf("KDiskDeviceManager::InitialDeviceScan() failed: %s\n", strerror(status));
file_system_info *bootfs;
if ((bootfs = get_file_system("bootfs")) == NULL) {
// no bootfs there, yet
// ToDo: do this for real!
status_t status = _kern_mount("/boot", "/dev/disk/scsi/0/0/0/raw",
"bfs", 0, NULL);
if (status < B_OK)
panic("could not get boot device: %s!\n", strerror(status));
} else
put_file_system(bootfs);
gBootDevice = sNextMountID - 1;
// create link for the name of the boot device
fs_info info;
if (_kern_read_fs_info(gBootDevice, &info) == B_OK) {
char path[B_FILE_NAME_LENGTH + 1];
snprintf(path, sizeof(path), "/%s", info.volume_name);
_kern_create_symlink(-1, path, "/boot", 0);
}
return B_OK;
}
status_t
vfs_init(kernel_args *args)
{
{
struct vnode *v;
sVnodeTable = hash_init(VNODE_HASH_TABLE_SIZE, (addr_t)&v->next - (addr_t)v,
&vnode_compare, &vnode_hash);
if (sVnodeTable == NULL)
panic("vfs_init: error creating vnode hash table\n");
}
{
struct fs_mount *mount;
sMountsTable = hash_init(MOUNTS_HASH_TABLE_SIZE, (addr_t)&mount->next - (addr_t)mount,
&mount_compare, &mount_hash);
if (sMountsTable == NULL)
panic("vfs_init: error creating mounts hash table\n");
}
node_monitor_init();
sRoot = NULL;
if (mutex_init(&sFileSystemsMutex, "vfs_lock") < 0)
panic("vfs_init: error allocating file systems lock\n");
if (recursive_lock_init(&sMountOpLock, "vfs_mount_op_lock") < 0)
panic("vfs_init: error allocating mount op lock\n");
if (mutex_init(&sMountMutex, "vfs_mount_lock") < 0)
panic("vfs_init: error allocating mount lock\n");
if (mutex_init(&sVnodeMutex, "vfs_vnode_lock") < 0)
panic("vfs_init: error allocating vnode lock\n");
return 0;
}
// #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;
fs_cookie cookie;
vnode_id newID;
int status;
if (FS_CALL(directory, create) == NULL)
return EROFS;
status = FS_CALL(directory, create)(directory->mount->cookie, directory->private_node, name, openMode, perms, &cookie, &newID);
if (status < B_OK)
return status;
mutex_lock(&sVnodeMutex);
vnode = lookup_vnode(directory->device, newID);
mutex_unlock(&sVnodeMutex);
if (vnode == NULL) {
dprintf("vfs: fs_create() returned success but there is no vnode!");
return EINVAL;
}
if ((status = get_new_fd(FDTYPE_FILE, vnode, cookie, openMode, kernel)) >= 0)
return status;
// something went wrong, clean up
FS_CALL(vnode, close)(vnode->mount->cookie, vnode->private_node, cookie);
FS_CALL(vnode, free_cookie)(vnode->mount->cookie, vnode->private_node, cookie);
put_vnode(vnode);
FS_CALL(directory, unlink)(directory->mount->cookie, directory->private_node, 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 omode, bool kernel)
{
fs_cookie cookie;
int status;
status = FS_CALL(vnode, open)(vnode->mount->cookie, vnode->private_node, omode, &cookie);
if (status < 0)
return status;
status = get_new_fd(FDTYPE_FILE, vnode, cookie, omode, kernel);
if (status < 0) {
FS_CALL(vnode, close)(vnode->mount->cookie, vnode->private_node, cookie);
FS_CALL(vnode, free_cookie)(vnode->mount->cookie, vnode->private_node, 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)
{
fs_cookie cookie;
int status;
status = FS_CALL(vnode, open_dir)(vnode->mount->cookie, vnode->private_node, &cookie);
if (status < B_OK)
return status;
// file is opened, create a fd
status = get_new_fd(FDTYPE_DIR, vnode, cookie, 0, kernel);
if (status >= 0)
return status;
FS_CALL(vnode, close_dir)(vnode->mount->cookie, vnode->private_node, cookie);
FS_CALL(vnode, free_dir_cookie)(vnode->mount->cookie, vnode->private_node, 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)
{
fs_cookie cookie;
int status;
status = FS_CALL(vnode, open_attr_dir)(vnode->mount->cookie, vnode->private_node, &cookie);
if (status < 0)
return status;
// file is opened, create a fd
status = get_new_fd(FDTYPE_ATTR_DIR, vnode, cookie, 0, kernel);
if (status >= 0)
return status;
FS_CALL(vnode, close_attr_dir)(vnode->mount->cookie, vnode->private_node, cookie);
FS_CALL(vnode, free_attr_dir_cookie)(vnode->mount->cookie, vnode->private_node, cookie);
return status;
}
static int
file_create_entry_ref(mount_id mountID, 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 < B_OK)
return status;
status = create_vnode(directory, name, openMode, perms, kernel);
put_vnode(directory);
return status;
}
static int
file_create(char *path, int openMode, int perms, bool kernel)
{
char name[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 = path_to_dir_vnode(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(mount_id mountID, vnode_id directoryID, const char *name, int openMode, bool kernel)
{
struct vnode *vnode;
int status;
if (name == NULL || *name == '\0')
return B_BAD_VALUE;
FUNCTION(("file_open_entry_ref()\n"));
// get the vnode matching the entry_ref
status = entry_ref_to_vnode(mountID, directoryID, name, &vnode);
if (status < B_OK)
return status;
status = open_vnode(vnode, openMode, kernel);
if (status < B_OK)
put_vnode(vnode);
return status;
}
static int
file_open(int fd, char *path, int omode, bool kernel)
{
int status = B_OK;
bool traverse = ((omode & O_NOTRAVERSE) == 0);
FUNCTION(("file_open: fd: %d, entry path = '%s', omode %d, kernel %d\n", fd, path, omode, kernel));
// get the vnode matching the vnode + path combination
struct vnode *vnode = NULL;
status = fd_and_path_to_vnode(fd, path, traverse, &vnode, kernel);
if (status != B_OK)
return status;
// open the vnode
status = open_vnode(vnode, omode, kernel);
// put only on error -- otherwise our reference was transferred to the FD
if (status < B_OK)
put_vnode(vnode);
return status;
}
static status_t
file_close(struct file_descriptor *descriptor)
{
struct vnode *vnode = descriptor->u.vnode;
FUNCTION(("file_close(descriptor = %p)\n", descriptor));
if (FS_CALL(vnode, close))
return FS_CALL(vnode, close)(vnode->mount->cookie, vnode->private_node, descriptor->cookie);
return B_OK;
}
static void
file_free_fd(struct file_descriptor *descriptor)
{
struct vnode *vnode = descriptor->u.vnode;
if (vnode != NULL) {
FS_CALL(vnode, free_cookie)(vnode->mount->cookie, vnode->private_node, descriptor->cookie);
put_vnode(vnode);
}
}
static status_t
file_read(struct file_descriptor *descriptor, off_t pos, void *buffer, 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)(vnode->mount->cookie, vnode->private_node, descriptor->cookie, pos, buffer, length);
}
static status_t
file_write(struct file_descriptor *descriptor, off_t pos, const void *buffer, 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)(vnode->mount->cookie, vnode->private_node, descriptor->cookie, pos, buffer, length);
}
static off_t
file_seek(struct file_descriptor *descriptor, off_t pos, int seekType)
{
off_t offset;
FUNCTION(("file_seek(pos = %Ld, seekType = %d)\n", pos, seekType));
// ToDo: seek should fail for pipes and FIFOs...
switch (seekType) {
case SEEK_SET:
offset = 0;
break;
case SEEK_CUR:
offset = descriptor->pos;
break;
case SEEK_END:
{
struct vnode *vnode = descriptor->u.vnode;
struct stat stat;
status_t status;
if (FS_CALL(vnode, read_stat) == NULL)
return EOPNOTSUPP;
status = FS_CALL(vnode, read_stat)(vnode->mount->cookie, vnode->private_node, &stat);
if (status < B_OK)
return status;
offset = stat.st_size;
break;
}
default:
return B_BAD_VALUE;
}
// assumes off_t is 64 bits wide
if (offset > 0 && LONGLONG_MAX - offset < pos)
return EOVERFLOW;
pos += offset;
if (pos < 0)
return B_BAD_VALUE;
return descriptor->pos = pos;
}
static status_t
dir_create_entry_ref(mount_id mountID, vnode_id parentID, const char *name, int perms, bool kernel)
{
struct vnode *vnode;
vnode_id newID;
status_t status;
if (name == NULL || *name == '\0')
return 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 < B_OK)
return status;
if (FS_CALL(vnode, create_dir))
status = FS_CALL(vnode, create_dir)(vnode->mount->cookie, vnode->private_node, name, perms, &newID);
else
status = EROFS;
put_vnode(vnode);
return status;
}
static status_t
dir_create(int fd, char *path, int perms, bool kernel)
{
char filename[SYS_MAX_NAME_LEN];
struct vnode *vnode;
vnode_id newID;
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 (FS_CALL(vnode, create_dir))
status = FS_CALL(vnode, create_dir)(vnode->mount->cookie, vnode->private_node, filename, perms, &newID);
else
status = EROFS;
put_vnode(vnode);
return status;
}
static int
dir_open_entry_ref(mount_id mountID, vnode_id parentID, const char *name, bool kernel)
{
struct vnode *vnode;
int status;
FUNCTION(("dir_open_entry_ref()\n"));
if (name && *name == '\0')
return 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 < B_OK)
return status;
status = open_dir_vnode(vnode, kernel);
if (status < B_OK)
put_vnode(vnode);
return status;
}
static int
dir_open(int fd, char *path, bool kernel)
{
int status = 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;
status = fd_and_path_to_vnode(fd, path, true, &vnode, kernel);
if (status != B_OK)
return status;
// open the dir
status = open_dir_vnode(vnode, kernel);
if (status < B_OK)
put_vnode(vnode);
return status;
}
static status_t
dir_close(struct file_descriptor *descriptor)
{
struct vnode *vnode = descriptor->u.vnode;
FUNCTION(("dir_close(descriptor = %p)\n", descriptor));
if (FS_CALL(vnode, close_dir))
return FS_CALL(vnode, close_dir)(vnode->mount->cookie, vnode->private_node, descriptor->cookie);
return 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)(vnode->mount->cookie, vnode->private_node, descriptor->cookie);
put_vnode(vnode);
}
}
static status_t
dir_read(struct file_descriptor *descriptor, struct dirent *buffer, size_t bufferSize, uint32 *_count)
{
return dir_read(descriptor->u.vnode, descriptor->cookie, buffer, bufferSize, _count);
}
static void
fix_dirent(struct vnode *parent, struct 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 (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
struct vnode *vnode;
status_t status = vnode_path_to_vnode(parent, "..", false, 0, &vnode,
NULL);
if (status == B_OK) {
entry->d_dev = vnode->device;
entry->d_ino = vnode->id;
}
} else {
// resolve mount points
struct vnode *vnode = NULL;
status_t status = get_vnode(entry->d_dev, entry->d_ino, &vnode, false);
if (status != B_OK)
return;
recursive_lock_lock(&sMountOpLock);
if (vnode->covered_by) {
entry->d_dev = vnode->covered_by->device;
entry->d_ino = vnode->covered_by->id;
}
recursive_lock_unlock(&sMountOpLock);
put_vnode(vnode);
}
}
static status_t
dir_read(struct vnode *vnode, fs_cookie cookie, struct dirent *buffer, size_t bufferSize, uint32 *_count)
{
if (!FS_CALL(vnode, read_dir))
return EOPNOTSUPP;
status_t error = FS_CALL(vnode, read_dir)(vnode->mount->cookie,vnode->private_node,cookie,buffer,bufferSize,_count);
if (error != 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 status_t
dir_rewind(struct file_descriptor *descriptor)
{
struct vnode *vnode = descriptor->u.vnode;
if (FS_CALL(vnode, rewind_dir))
return FS_CALL(vnode, rewind_dir)(vnode->mount->cookie,vnode->private_node,descriptor->cookie);
return EOPNOTSUPP;
}
static status_t
dir_remove(char *path, bool kernel)
{
char name[B_FILE_NAME_LENGTH];
struct vnode *directory;
status_t status;
status = path_to_dir_vnode(path, &directory, name, kernel);
if (status < B_OK)
return status;
if (FS_CALL(directory, remove_dir))
status = FS_CALL(directory, remove_dir)(directory->mount->cookie, directory->private_node, name);
else
status = EROFS;
put_vnode(directory);
return status;
}
static status_t
common_ioctl(struct file_descriptor *descriptor, ulong op, void *buffer, size_t length)
{
struct vnode *vnode = descriptor->u.vnode;
if (FS_CALL(vnode, ioctl)) {
return FS_CALL(vnode, ioctl)(vnode->mount->cookie, vnode->private_node,
descriptor->cookie, op, buffer, length);
}
return EOPNOTSUPP;
}
static status_t
common_fcntl(int fd, int op, uint32 argument, bool kernel)
{
struct file_descriptor *descriptor;
struct vnode *vnode;
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 B_FILE_ERROR;
switch (op) {
case F_SETFD:
// Set file descriptor flags
// O_CLOEXEC is the only flag available at this time
if (argument == FD_CLOEXEC)
descriptor->open_mode |= O_CLOEXEC;
else
descriptor->open_mode &= O_CLOEXEC;
status = B_OK;
break;
case F_GETFD:
// Get file descriptor flags
status = (descriptor->open_mode & O_CLOEXEC) ? FD_CLOEXEC : 0;
break;
case F_SETFL:
// Set file descriptor open mode
if (FS_CALL(vnode, set_flags)) {
// we only accept changes to O_APPEND and O_NONBLOCK
argument &= O_APPEND | O_NONBLOCK;
status = FS_CALL(vnode, set_flags)(vnode->mount->cookie, vnode->private_node, descriptor->cookie, (int)argument);
if (status == B_OK) {
// update this descriptor's open_mode field
descriptor->open_mode = (descriptor->open_mode & ~(O_APPEND | O_NONBLOCK)) | argument;
}
} else
status = EOPNOTSUPP;
break;
case F_GETFL:
// Get file descriptor open mode
status = descriptor->open_mode;
break;
// ToDo: add support for more ops
default:
status = B_BAD_VALUE;
}
put_fd(descriptor);
return status;
}
static status_t
common_sync(int fd, bool kernel)
{
struct file_descriptor *descriptor;
struct vnode *vnode;
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 B_FILE_ERROR;
if (FS_CALL(vnode, fsync) != NULL)
status = FS_CALL(vnode, fsync)(vnode->mount->cookie, vnode->private_node);
else
status = EOPNOTSUPP;
put_fd(descriptor);
return status;
}
static status_t
common_lock_node(int fd, bool kernel)
{
// TODO: Implement!
return EOPNOTSUPP;
}
static status_t
common_unlock_node(int fd, bool kernel)
{
// TODO: Implement!
return EOPNOTSUPP;
}
static status_t
common_read_link(int fd, char *path, char *buffer, size_t bufferSize,
bool kernel)
{
struct vnode *vnode;
int status;
status = fd_and_path_to_vnode(fd, path, false, &vnode, kernel);
if (status < B_OK)
return status;
if (FS_CALL(vnode, read_link) != NULL)
status = FS_CALL(vnode, read_link)(vnode->mount->cookie, vnode->private_node, buffer, bufferSize);
else
status = B_BAD_VALUE;
put_vnode(vnode);
return status;
}
static status_t
common_write_link(char *path, char *toPath, bool kernel)
{
struct vnode *vnode;
int status;
status = path_to_vnode(path, false, &vnode, kernel);
if (status < B_OK)
return status;
if (FS_CALL(vnode, write_link) != NULL)
status = FS_CALL(vnode, write_link)(vnode->mount->cookie, vnode->private_node, toPath);
else
status = EOPNOTSUPP;
put_vnode(vnode);
return status;
}
static 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[B_FILE_NAME_LENGTH];
struct vnode *vnode;
int 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 < B_OK)
return status;
if (FS_CALL(vnode, create_symlink) != NULL)
status = FS_CALL(vnode, create_symlink)(vnode->mount->cookie, vnode->private_node, name, toPath, mode);
else
status = EROFS;
put_vnode(vnode);
return status;
}
static status_t
common_create_link(char *path, char *toPath, bool kernel)
{
// path validity checks have to be in the calling function!
char name[B_FILE_NAME_LENGTH];
struct vnode *directory, *vnode;
int 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 < B_OK)
return status;
status = path_to_vnode(toPath, true, &vnode, kernel);
if (status < B_OK)
goto err;
if (directory->mount != vnode->mount) {
status = B_CROSS_DEVICE_LINK;
goto err1;
}
if (FS_CALL(vnode, link) != NULL)
status = FS_CALL(vnode, link)(directory->mount->cookie, directory->private_node, name, vnode->private_node);
else
status = EROFS;
err1:
put_vnode(vnode);
err:
put_vnode(directory);
return status;
}
static status_t
common_unlink(int fd, char *path, bool kernel)
{
char filename[SYS_MAX_NAME_LEN];
struct vnode *vnode;
int 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 (FS_CALL(vnode, unlink) != NULL)
status = FS_CALL(vnode, unlink)(vnode->mount->cookie, vnode->private_node, filename);
else
status = EROFS;
put_vnode(vnode);
return status;
}
static status_t
common_access(char *path, int mode, bool kernel)
{
struct vnode *vnode;
int status;
status = path_to_vnode(path, true, &vnode, kernel);
if (status < B_OK)
return status;
if (FS_CALL(vnode, access) != NULL)
status = FS_CALL(vnode, access)(vnode->mount->cookie, vnode->private_node, mode);
else
status = EOPNOTSUPP;
put_vnode(vnode);
return status;
}
static status_t
common_rename(int fd, char *path, int newFD, char *newPath, bool kernel)
{
struct vnode *fromVnode, *toVnode;
char fromName[SYS_MAX_NAME_LEN];
char toName[SYS_MAX_NAME_LEN];
int 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 = B_CROSS_DEVICE_LINK;
goto err1;
}
if (FS_CALL(fromVnode, rename) != NULL)
status = FS_CALL(fromVnode, rename)(fromVnode->mount->cookie, fromVnode->private_node, fromName, toVnode->private_node, toName);
else
status = EROFS;
err1:
put_vnode(toVnode);
err:
put_vnode(fromVnode);
return status;
}
static status_t
common_read_stat(struct file_descriptor *descriptor, struct stat *stat)
{
struct vnode *vnode = descriptor->u.vnode;
FUNCTION(("common_read_stat: stat %p\n", stat));
status_t status = FS_CALL(vnode, read_stat)(vnode->mount->cookie,
vnode->private_node, stat);
// fill in the st_dev and st_ino fields
if (status == B_OK) {
stat->st_dev = vnode->device;
stat->st_ino = vnode->id;
}
return status;
}
static status_t
common_write_stat(struct file_descriptor *descriptor, const struct 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 (!FS_CALL(vnode, write_stat))
return EROFS;
return FS_CALL(vnode, write_stat)(vnode->mount->cookie, vnode->private_node, stat, statMask);
}
static status_t
common_path_read_stat(int fd, char *path, bool traverseLeafLink,
struct stat *stat, bool kernel)
{
struct vnode *vnode;
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, kernel);
if (status < 0)
return status;
status = FS_CALL(vnode, read_stat)(vnode->mount->cookie, vnode->private_node, stat);
// fill in the st_dev and st_ino fields
if (status == B_OK) {
stat->st_dev = vnode->device;
stat->st_ino = vnode->id;
}
put_vnode(vnode);
return status;
}
static status_t
common_path_write_stat(int fd, char *path, bool traverseLeafLink,
const struct stat *stat, int statMask, bool kernel)
{
struct vnode *vnode;
int 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, kernel);
if (status < 0)
return status;
if (FS_CALL(vnode, write_stat))
status = FS_CALL(vnode, write_stat)(vnode->mount->cookie, vnode->private_node, stat, statMask);
else
status = EROFS;
put_vnode(vnode);
return status;
}
static status_t
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, kernel);
if (status < B_OK)
return status;
status = open_attr_dir_vnode(vnode, kernel);
if (status < 0)
put_vnode(vnode);
return status;
}
static status_t
attr_dir_close(struct file_descriptor *descriptor)
{
struct vnode *vnode = descriptor->u.vnode;
FUNCTION(("dir_close(descriptor = %p)\n", descriptor));
if (FS_CALL(vnode, close_attr_dir))
return FS_CALL(vnode, close_attr_dir)(vnode->mount->cookie, vnode->private_node, descriptor->cookie);
return 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)(vnode->mount->cookie, vnode->private_node, descriptor->cookie);
put_vnode(vnode);
}
}
static status_t
attr_dir_read(struct file_descriptor *descriptor, struct dirent *buffer, size_t bufferSize, uint32 *_count)
{
struct vnode *vnode = descriptor->u.vnode;
if (FS_CALL(vnode, read_attr_dir))
return FS_CALL(vnode, read_attr_dir)(vnode->mount->cookie, vnode->private_node, descriptor->cookie, buffer, bufferSize, _count);
return EOPNOTSUPP;
}
static status_t
attr_dir_rewind(struct file_descriptor *descriptor)
{
struct vnode *vnode = descriptor->u.vnode;
if (FS_CALL(vnode, rewind_attr_dir))
return FS_CALL(vnode, rewind_attr_dir)(vnode->mount->cookie, vnode->private_node, descriptor->cookie);
return EOPNOTSUPP;
}
static int
attr_create(int fd, const char *name, uint32 type, int openMode, bool kernel)
{
struct vnode *vnode;
fs_cookie cookie;
int status;
if (name == NULL || *name == '\0')
return B_BAD_VALUE;
vnode = get_vnode_from_fd(fd, kernel);
if (vnode == NULL)
return B_FILE_ERROR;
if (FS_CALL(vnode, create_attr) == NULL) {
status = EROFS;
goto err;
}
status = FS_CALL(vnode, create_attr)(vnode->mount->cookie, vnode->private_node, name, type, openMode, &cookie);
if (status < B_OK)
goto err;
if ((status = get_new_fd(FDTYPE_ATTR, vnode, cookie, openMode, kernel)) >= 0)
return status;
FS_CALL(vnode, close_attr)(vnode->mount->cookie, vnode->private_node, cookie);
FS_CALL(vnode, free_attr_cookie)(vnode->mount->cookie, vnode->private_node, cookie);
FS_CALL(vnode, remove_attr)(vnode->mount->cookie, vnode->private_node, name);
err:
put_vnode(vnode);
return status;
}
static int
attr_open(int fd, const char *name, int openMode, bool kernel)
{
struct vnode *vnode;
fs_cookie cookie;
int status;
if (name == NULL || *name == '\0')
return B_BAD_VALUE;
vnode = get_vnode_from_fd(fd, kernel);
if (vnode == NULL)
return B_FILE_ERROR;
if (FS_CALL(vnode, open_attr) == NULL) {
status = EOPNOTSUPP;
goto err;
}
status = FS_CALL(vnode, open_attr)(vnode->mount->cookie, vnode->private_node, name, openMode, &cookie);
if (status < B_OK)
goto err;
if ((status = get_new_fd(FDTYPE_ATTR, vnode, cookie, openMode, kernel)) >= 0)
return status;
FS_CALL(vnode, close_attr)(vnode->mount->cookie, vnode->private_node, cookie);
FS_CALL(vnode, free_attr_cookie)(vnode->mount->cookie, vnode->private_node, cookie);
err:
put_vnode(vnode);
return status;
}
static status_t
attr_close(struct file_descriptor *descriptor)
{
struct vnode *vnode = descriptor->u.vnode;
FUNCTION(("attr_close(descriptor = %p)\n", descriptor));
if (FS_CALL(vnode, close_attr))
return FS_CALL(vnode, close_attr)(vnode->mount->cookie, vnode->private_node, descriptor->cookie);
return 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)(vnode->mount->cookie, vnode->private_node, descriptor->cookie);
put_vnode(vnode);
}
}
static status_t
attr_read(struct file_descriptor *descriptor, off_t pos, void *buffer, 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 (!FS_CALL(vnode, read_attr))
return EOPNOTSUPP;
return FS_CALL(vnode, read_attr)(vnode->mount->cookie, vnode->private_node, descriptor->cookie, pos, buffer, length);
}
static status_t
attr_write(struct file_descriptor *descriptor, off_t pos, const void *buffer, size_t *length)
{
struct vnode *vnode = descriptor->u.vnode;
FUNCTION(("attr_write: buf %p, pos %Ld, len %p\n", buffer, pos, length));
if (!FS_CALL(vnode, write_attr))
return EOPNOTSUPP;
return FS_CALL(vnode, write_attr)(vnode->mount->cookie, vnode->private_node, descriptor->cookie, pos, buffer, length);
}
static off_t
attr_seek(struct file_descriptor *descriptor, off_t pos, int seekType)
{
off_t offset;
switch (seekType) {
case SEEK_SET:
offset = 0;
break;
case SEEK_CUR:
offset = descriptor->pos;
break;
case SEEK_END:
{
struct vnode *vnode = descriptor->u.vnode;
struct stat stat;
status_t status;
if (FS_CALL(vnode, read_stat) == NULL)
return EOPNOTSUPP;
status = FS_CALL(vnode, read_attr_stat)(vnode->mount->cookie, vnode->private_node, descriptor->cookie, &stat);
if (status < B_OK)
return status;
offset = stat.st_size;
break;
}
default:
return B_BAD_VALUE;
}
// assumes off_t is 64 bits wide
if (offset > 0 && LONGLONG_MAX - offset < pos)
return EOVERFLOW;
pos += offset;
if (pos < 0)
return B_BAD_VALUE;
return descriptor->pos = pos;
}
static status_t
attr_read_stat(struct file_descriptor *descriptor, struct stat *stat)
{
struct vnode *vnode = descriptor->u.vnode;
FUNCTION(("attr_read_stat: stat 0x%p\n", stat));
if (!FS_CALL(vnode, read_attr_stat))
return EOPNOTSUPP;
return FS_CALL(vnode, read_attr_stat)(vnode->mount->cookie, vnode->private_node, descriptor->cookie, stat);
}
static status_t
attr_write_stat(struct file_descriptor *descriptor, const struct stat *stat, int statMask)
{
struct vnode *vnode = descriptor->u.vnode;
FUNCTION(("attr_write_stat: stat = %p, statMask %d\n", stat, statMask));
if (!FS_CALL(vnode, write_attr_stat))
return EROFS;
return FS_CALL(vnode, write_attr_stat)(vnode->mount->cookie, vnode->private_node, descriptor->cookie, stat, statMask);
}
static status_t
attr_remove(int fd, const char *name, bool kernel)
{
struct file_descriptor *descriptor;
struct vnode *vnode;
int status;
if (name == NULL || *name == '\0')
return 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 B_FILE_ERROR;
if (FS_CALL(vnode, remove_attr))
status = FS_CALL(vnode, remove_attr)(vnode->mount->cookie, vnode->private_node, name);
else
status = EROFS;
put_fd(descriptor);
return status;
}
static 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;
int status;
if (fromName == NULL || *fromName == '\0' || toName == NULL || *toName == '\0')
return 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 B_FILE_ERROR;
toDescriptor = get_fd_and_vnode(tofd, &toVnode, kernel);
if (toDescriptor == NULL) {
status = B_FILE_ERROR;
goto err;
}
// are the files on the same volume?
if (fromVnode->device != toVnode->device) {
status = B_CROSS_DEVICE_LINK;
goto err1;
}
if (FS_CALL(fromVnode, rename_attr))
status = FS_CALL(fromVnode, rename_attr)(fromVnode->mount->cookie, fromVnode->private_node, fromName, toVnode->private_node, toName);
else
status = EROFS;
err1:
put_fd(toDescriptor);
err:
put_fd(fromDescriptor);
return status;
}
static status_t
index_dir_open(mount_id mountID, bool kernel)
{
struct fs_mount *mount;
fs_cookie cookie;
status_t status;
FUNCTION(("index_dir_open(mountID = %ld, kernel = %d)\n", mountID, kernel));
mount = get_mount(mountID);
if (mount == NULL)
return B_BAD_VALUE;
if (FS_MOUNT_CALL(mount, open_index_dir) == NULL) {
status = EOPNOTSUPP;
goto out;
}
status = FS_MOUNT_CALL(mount, open_index_dir)(mount->cookie, &cookie);
if (status < B_OK)
goto out;
// get fd for the index directory
status = get_new_fd(FDTYPE_INDEX_DIR, (struct vnode *)mount, cookie, 0, kernel);
if (status >= 0)
goto out;
// something went wrong
FS_MOUNT_CALL(mount, close_index_dir)(mount->cookie, cookie);
FS_MOUNT_CALL(mount, free_index_dir_cookie)(mount->cookie, cookie);
out:
put_mount(mount);
return status;
}
static status_t
index_dir_close(struct file_descriptor *descriptor)
{
struct fs_mount *mount = descriptor->u.mount;
FUNCTION(("dir_close(descriptor = %p)\n", descriptor));
if (FS_MOUNT_CALL(mount, close_index_dir))
return FS_MOUNT_CALL(mount, close_index_dir)(mount->cookie, descriptor->cookie);
return 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)(mount->cookie, descriptor->cookie);
// ToDo: find a replacement ref_count object - perhaps the root dir?
//put_vnode(vnode);
}
}
static status_t
index_dir_read(struct file_descriptor *descriptor, struct dirent *buffer, size_t bufferSize, uint32 *_count)
{
struct fs_mount *mount = descriptor->u.mount;
if (FS_MOUNT_CALL(mount, read_index_dir))
return FS_MOUNT_CALL(mount, read_index_dir)(mount->cookie, descriptor->cookie, buffer, bufferSize, _count);
return EOPNOTSUPP;
}
static status_t
index_dir_rewind(struct file_descriptor *descriptor)
{
struct fs_mount *mount = descriptor->u.mount;
if (FS_MOUNT_CALL(mount, rewind_index_dir))
return FS_MOUNT_CALL(mount, rewind_index_dir)(mount->cookie, descriptor->cookie);
return EOPNOTSUPP;
}
static status_t
index_create(mount_id mountID, const char *name, uint32 type, uint32 flags, bool kernel)
{
struct fs_mount *mount;
status_t status;
FUNCTION(("index_create(mountID = %ld, name = %s, kernel = %d)\n", mountID, name, kernel));
mount = get_mount(mountID);
if (mount == NULL)
return B_BAD_VALUE;
if (FS_MOUNT_CALL(mount, create_index) == NULL) {
status = EROFS;
goto out;
}
status = FS_MOUNT_CALL(mount, create_index)(mount->cookie, name, type, flags);
out:
put_mount(mount);
return status;
}
#if 0
static status_t
index_read_stat(struct file_descriptor *descriptor, struct stat *stat)
{
struct vnode *vnode = descriptor->u.vnode;
// ToDo: currently unused!
FUNCTION(("index_read_stat: stat 0x%p\n", stat));
if (!FS_CALL(vnode, read_index_stat))
return EOPNOTSUPP;
return EOPNOTSUPP;
//return FS_CALL(vnode, read_index_stat)(vnode->mount->cookie, vnode->private_node, descriptor->cookie, stat);
}
static void
index_free_fd(struct file_descriptor *descriptor)
{
struct vnode *vnode = descriptor->u.vnode;
if (vnode != NULL) {
FS_CALL(vnode, free_index_cookie)(vnode->mount->cookie, vnode->private_node, descriptor->cookie);
put_vnode(vnode);
}
}
#endif
static status_t
index_name_read_stat(mount_id mountID, const char *name, struct stat *stat, bool kernel)
{
struct fs_mount *mount;
status_t status;
FUNCTION(("index_remove(mountID = %ld, name = %s, kernel = %d)\n", mountID, name, kernel));
mount = get_mount(mountID);
if (mount == NULL)
return B_BAD_VALUE;
if (FS_MOUNT_CALL(mount, read_index_stat) == NULL) {
status = EOPNOTSUPP;
goto out;
}
status = FS_MOUNT_CALL(mount, read_index_stat)(mount->cookie, name, stat);
out:
put_mount(mount);
return status;
}
static status_t
index_remove(mount_id mountID, const char *name, bool kernel)
{
struct fs_mount *mount;
status_t status;
FUNCTION(("index_remove(mountID = %ld, name = %s, kernel = %d)\n", mountID, name, kernel));
mount = get_mount(mountID);
if (mount == NULL)
return B_BAD_VALUE;
if (FS_MOUNT_CALL(mount, remove_index) == NULL) {
status = EROFS;
goto out;
}
status = FS_MOUNT_CALL(mount, remove_index)(mount->cookie, name);
out:
put_mount(mount);
return status;
}
// #pragma mark -
// General File System functions
static status_t
fs_mount(char *path, const char *device, const char *fsName, uint32 flags,
const char *args, bool kernel)
{
struct fs_mount *mount;
struct vnode *covered_vnode = NULL;
vnode_id root_id;
int err = 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 & B_MOUNT_VIRTUAL_DEVICE)
return B_BAD_VALUE;
} else if (fsName[0] == '\0')
return B_BAD_VALUE;
RecursiveLocker mountOpLocker(sMountOpLock);
// Helper to delete a newly created file device on failure.
// Not exactly beautiful, but helps to keep the code below cleaner.
struct FileDeviceDeleter {
FileDeviceDeleter() : id(-1) {}
~FileDeviceDeleter()
{
KDiskDeviceManager::Default()->DeleteFileDevice(id);
}
partition_id id;
} fileDeviceDeleter;
// 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
KDiskDeviceManager *ddm = KDiskDeviceManager::Default();
KPartition *partition = NULL;
bool newlyCreatedFileDevice = false;
if (!(flags & B_MOUNT_VIRTUAL_DEVICE) && device) {
// normalize the device path
KPath normalizedDevice;
err = normalizedDevice.SetTo(device, true);
if (err != B_OK)
return err;
// get a corresponding partition from the DDM
partition = ddm->RegisterPartition(normalizedDevice.Path(), true);
if (!partition) {
// Partition not found: This either means, the user supplied
// an invalid path, or the path refers to an image file. We try
// to let the DDM create a file device for the path.
partition_id deviceID = ddm->CreateFileDevice(
normalizedDevice.Path(), &newlyCreatedFileDevice);
if (deviceID >= 0) {
partition = ddm->RegisterPartition(deviceID, true);
if (newlyCreatedFileDevice)
fileDeviceDeleter.id = deviceID;
// TODO: We must wait here, until the partition scan job is done.
}
}
if (!partition) {
PRINT(("fs_mount(): Partition `%s' not found.\n",
normalizedDevice.Path()));
return B_ENTRY_NOT_FOUND;
}
}
PartitionRegistrar partitionRegistrar(partition, true);
// Write lock the partition's device. For the time being, we keep the lock
// until we're done mounting -- not nice, but ensure, that no-one is
// interfering.
// TODO: Find a better solution.
KDiskDevice *diskDevice = NULL;
if (partition) {
diskDevice = ddm->WriteLockDevice(partition->Device()->ID());
if (!diskDevice) {
PRINT(("fs_mount(): Failed to lock disk device!\n"));
return B_ERROR;
}
}
DeviceWriteLocker writeLocker(diskDevice, true);
if (partition) {
// make sure, that the partition is not busy
if (partition->IsBusy() || partition->IsDescendantBusy()) {
PRINT(("fs_mount(): Partition is busy.\n"));
return B_BUSY;
}
// if no FS name had been supplied, we get it from the partition
if (!fsName) {
KDiskSystem *diskSystem = partition->DiskSystem();
if (!diskSystem) {
PRINT(("fs_mount(): No FS name was given, and the DDM didn't "
"recognize it.\n"));
return B_BAD_VALUE;
}
if (!diskSystem->IsFileSystem()) {
PRINT(("fs_mount(): No FS name was given, and the DDM found a "
"partitioning system.\n"));
return B_BAD_VALUE;
}
// The disk system name will not change, and the KDiskSystem
// object will not go away while the disk device is locked (and
// the partition has a reference to it), so this is safe.
fsName = diskSystem->Name();
}
}
mount = (struct fs_mount *)malloc(sizeof(struct fs_mount));
if (mount == NULL)
return B_NO_MEMORY;
list_init_etc(&mount->vnodes, offsetof(struct vnode, mount_link));
mount->fs_name = strdup(fsName);
if (mount->fs_name == NULL) {
err = B_NO_MEMORY;
goto err1;
}
mount->device_name = strdup(device);
// "device" can be NULL
mount->fs = get_file_system(fsName);
if (mount->fs == NULL) {
err = ENODEV;
goto err3;
}
err = recursive_lock_init(&mount->rlock, "mount rlock");
if (err < B_OK)
goto err4;
mount->id = sNextMountID++;
mount->partition = NULL;
mount->unmounting = false;
mount->owns_file_device = false;
mutex_lock(&sMountMutex);
// insert mount struct into list before we call fs mount()
hash_insert(sMountsTable, mount);
mutex_unlock(&sMountMutex);
if (!sRoot) {
// we haven't mounted anything yet
if (strcmp(path, "/") != 0) {
err = B_ERROR;
goto err5;
}
err = FS_MOUNT_CALL(mount, mount)(mount->id, device, NULL, &mount->cookie, &root_id);
if (err < 0) {
// ToDo: why should we hide the error code from the file system here?
//err = ERR_VFS_GENERAL;
goto err5;
}
mount->covers_vnode = NULL; // this is the root mount
} else {
err = path_to_vnode(path, true, &covered_vnode, kernel);
if (err < 0)
goto err5;
if (!covered_vnode) {
err = B_ERROR;
goto err5;
}
// make sure covered_vnode is a DIR
struct stat coveredNodeStat;
err = FS_CALL(covered_vnode, read_stat)(covered_vnode->mount->cookie,
covered_vnode->private_node, &coveredNodeStat);
if (err < 0)
goto err5;
if (!S_ISDIR(coveredNodeStat.st_mode)) {
err = B_NOT_A_DIRECTORY;
goto err5;
}
if (covered_vnode->mount->root_vnode == covered_vnode) {
err = ERR_VFS_ALREADY_MOUNTPOINT;
goto err5;
}
mount->covers_vnode = covered_vnode;
// mount it
err = FS_MOUNT_CALL(mount, mount)(mount->id, device, NULL, &mount->cookie, &root_id);
if (err < 0)
goto err6;
}
err = get_vnode(mount->id, root_id, &mount->root_vnode, 0);
if (err < 0)
goto err7;
// No race here, since fs_mount() is the only function changing
// covers_vnode (and holds sMountOpLock at that time).
if (mount->covers_vnode)
mount->covers_vnode->covered_by = mount->root_vnode;
if (!sRoot)
sRoot = mount->root_vnode;
// supply the partition (if any) with the mount cookie and mark it mounted
if (partition) {
partition->SetMountCookie(mount->cookie);
partition->AddFlags(B_PARTITION_MOUNTED);
// keep a partition reference as long as the partition is mounted
partitionRegistrar.Detach();
mount->partition = partition;
mount->owns_file_device = newlyCreatedFileDevice;
fileDeviceDeleter.id = -1;
}
return B_OK;
err7:
FS_MOUNT_CALL(mount, unmount)(mount->cookie);
err6:
if (mount->covers_vnode)
put_vnode(mount->covers_vnode);
err5:
mutex_lock(&sMountMutex);
hash_remove(sMountsTable, mount);
mutex_unlock(&sMountMutex);
recursive_lock_destroy(&mount->rlock);
err4:
put_file_system(mount->fs);
free(mount->device_name);
err3:
free(mount->fs_name);
err1:
free(mount);
return err;
}
static status_t
fs_unmount(char *path, bool kernel)
{
struct fs_mount *mount;
struct vnode *vnode;
int err;
FUNCTION(("vfs_unmount: entry. path = '%s', kernel %d\n", path, kernel));
err = path_to_vnode(path, true, &vnode, kernel);
if (err < 0)
return ERR_VFS_PATH_NOT_FOUND;
RecursiveLocker mountOpLocker(sMountOpLock);
mount = find_mount(vnode->device);
if (!mount)
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 ERR_VFS_NOT_MOUNTPOINT;
}
// if the volume is associated with a partition, lock the device of the
// partition as long as we are unmounting
KDiskDeviceManager* ddm = KDiskDeviceManager::Default();
KPartition *partition = mount->partition;
KDiskDevice *diskDevice = NULL;
if (partition) {
diskDevice = ddm->WriteLockDevice(partition->Device()->ID());
if (!diskDevice) {
PRINT(("fs_unmount(): Failed to lock disk device!\n"));
return B_ERROR;
}
}
DeviceWriteLocker writeLocker(diskDevice, true);
// make sure, that the partition is not busy
if (partition) {
if (partition->IsBusy() || partition->IsDescendantBusy()) {
PRINT(("fs_unmount(): Partition is busy.\n"));
return B_BUSY;
}
}
/* grab the vnode master mutex to keep someone from creating
* a vnode while we're figuring out if we can continue
*/
mutex_lock(&sVnodeMutex);
/* simulate the root vnode having it's refcount decremented */
mount->root_vnode->ref_count -= 2;
// 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) {
if (vnode->busy || vnode->ref_count != 0) {
mount->root_vnode->ref_count += 2;
mutex_unlock(&sVnodeMutex);
put_vnode(mount->root_vnode);
return EBUSY;
}
}
/* we can safely continue, mark all of the vnodes busy and this mount
* structure in unmounting state
*/
while ((vnode = (struct vnode *)list_get_next_item(&mount->vnodes, vnode)) != NULL) {
if (vnode != mount->root_vnode)
vnode->busy = true;
}
mount->unmounting = true;
/* add 2 back to the root vnode's ref */
mount->root_vnode->ref_count += 2;
mutex_unlock(&sVnodeMutex);
mount->covers_vnode->covered_by = NULL;
put_vnode(mount->covers_vnode);
/* release the ref on the root vnode twice */
put_vnode(mount->root_vnode);
put_vnode(mount->root_vnode);
// ToDo: when full vnode cache in place, will need to force
// a putvnode/removevnode here
/* remove the mount structure from the hash table */
mutex_lock(&sMountMutex);
hash_remove(sMountsTable, mount);
mutex_unlock(&sMountMutex);
mountOpLocker.Unlock();
FS_MOUNT_CALL(mount, unmount)(mount->cookie);
// release the file system
put_file_system(mount->fs);
// dereference the partition
if (partition) {
if (mount->owns_file_device)
KDiskDeviceManager::Default()->DeleteFileDevice(partition->ID());
partition->Unregister();
}
free(mount->device_name);
free(mount->fs_name);
free(mount);
return 0;
}
static status_t
fs_sync(void)
{
struct hash_iterator iter;
struct fs_mount *mount;
FUNCTION(("vfs_sync: entry.\n"));
/* cycle through and call sync on each mounted fs */
recursive_lock_lock(&sMountOpLock);
mutex_lock(&sMountMutex);
hash_open(sMountsTable, &iter);
while ((mount = (struct fs_mount *)hash_next(sMountsTable, &iter))) {
if (FS_MOUNT_CALL(mount, sync))
FS_MOUNT_CALL(mount, sync)(mount->cookie);
}
hash_close(sMountsTable, &iter, false);
mutex_unlock(&sMountMutex);
recursive_lock_unlock(&sMountOpLock);
return 0;
}
static status_t
fs_read_info(dev_t device, struct fs_info *info)
{
struct fs_mount *mount;
status_t status = B_OK;
mutex_lock(&sMountMutex);
mount = find_mount(device);
if (mount == NULL) {
status = B_BAD_VALUE;
goto out;
}
// fill in info the file system doesn't (have to) know about
memset(info, 0, sizeof(struct fs_info));
info->dev = mount->id;
info->root = mount->root_vnode->id;
strlcpy(info->fsh_name, mount->fs_name, sizeof(info->fsh_name));
if (mount->device_name != NULL)
strlcpy(info->device_name, mount->device_name, sizeof(info->device_name));
if (FS_MOUNT_CALL(mount, read_fs_info))
status = FS_MOUNT_CALL(mount, read_fs_info)(mount->cookie, info);
// if the call is not supported by the file system, there are still
// the parts that we filled out ourselves
out:
mutex_unlock(&sMountMutex);
return status;
}
static status_t
fs_write_info(dev_t device, const struct fs_info *info, int mask)
{
struct fs_mount *mount;
int status;
mutex_lock(&sMountMutex);
mount = find_mount(device);
if (mount == NULL) {
status = EINVAL;
goto out;
}
if (FS_MOUNT_CALL(mount, write_fs_info))
status = FS_MOUNT_CALL(mount, write_fs_info)(mount->cookie, info, mask);
else
status = EROFS;
out:
mutex_unlock(&sMountMutex);
return status;
}
static dev_t
fs_next_device(int32 *_cookie)
{
struct fs_mount *mount = NULL;
dev_t device = *_cookie;
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)
break;
}
*_cookie = device;
if (mount != NULL)
device = mount->id;
else
device = B_BAD_VALUE;
mutex_unlock(&sMountMutex);
return device;
}
static status_t
get_cwd(char *buffer, size_t size, bool kernel)
{
// Get current working directory from io context
struct io_context *context = get_current_io_context(kernel);
int status;
FUNCTION(("vfs_get_cwd: buf %p, size %ld\n", buffer, size));
mutex_lock(&context->io_mutex);
if (context->cwd)
status = dir_vnode_to_path(context->cwd, buffer, size);
else
status = B_ERROR;
mutex_unlock(&context->io_mutex);
return status;
}
static status_t
set_cwd(int fd, char *path, bool kernel)
{
struct io_context *context;
struct vnode *vnode = NULL;
struct vnode *oldDirectory;
struct stat stat;
int rc;
FUNCTION(("set_cwd: path = \'%s\'\n", path));
// Get vnode for passed path, and bail if it failed
rc = fd_and_path_to_vnode(fd, path, true, &vnode, kernel);
if (rc < 0)
return rc;
rc = FS_CALL(vnode, read_stat)(vnode->mount->cookie, vnode->private_node, &stat);
if (rc < 0)
goto err;
if (!S_ISDIR(stat.st_mode)) {
// nope, can't cwd to here
rc = B_NOT_A_DIRECTORY;
goto err;
}
// Get current io context and lock
context = get_current_io_context(kernel);
mutex_lock(&context->io_mutex);
// save the old current working directory first
oldDirectory = context->cwd;
context->cwd = vnode;
mutex_unlock(&context->io_mutex);
if (oldDirectory)
put_vnode(oldDirectory);
return B_NO_ERROR;
err:
put_vnode(vnode);
return rc;
}
// #pragma mark -
// Calls from within the kernel
status_t
_kern_mount(const char *path, const char *device, const char *fs_name,
uint32 flags, const char *args)
{
char pathBuffer[B_PATH_NAME_LENGTH + 1];
strlcpy(pathBuffer, path, B_PATH_NAME_LENGTH);
return fs_mount(pathBuffer, device, fs_name, flags, args, true);
}
status_t
_kern_unmount(const char *path)
{
char pathBuffer[B_PATH_NAME_LENGTH + 1];
strlcpy(pathBuffer, path, B_PATH_NAME_LENGTH);
return fs_unmount(pathBuffer, true);
}
status_t
_kern_read_fs_info(dev_t device, struct fs_info *info)
{
if (info == NULL)
return B_BAD_VALUE;
return fs_read_info(device, info);
}
status_t
_kern_write_fs_info(dev_t device, const struct fs_info *info, int mask)
{
if (info == NULL)
return B_BAD_VALUE;
return fs_write_info(device, info, mask);
}
status_t
_kern_sync(void)
{
return fs_sync();
}
dev_t
_kern_next_device(int32 *_cookie)
{
return fs_next_device(_cookie);
}
int
_kern_open_entry_ref(dev_t device, ino_t inode, const char *name, int omode)
{
char nameCopy[B_FILE_NAME_LENGTH];
strlcpy(nameCopy, name, sizeof(nameCopy));
return file_open_entry_ref(device, inode, nameCopy, omode, 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 omode 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 omode)
{
char pathBuffer[B_PATH_NAME_LENGTH + 1];
strlcpy(pathBuffer, path, B_PATH_NAME_LENGTH);
return file_open(fd, pathBuffer, omode, 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(dev_t device, 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)
{
char pathBuffer[B_PATH_NAME_LENGTH + 1];
strlcpy(pathBuffer, path, B_PATH_NAME_LENGTH);
return dir_open(fd, pathBuffer, true);
}
status_t
_kern_fcntl(int fd, int op, uint32 argument)
{
return common_fcntl(fd, op, argument, true);
}
status_t
_kern_fsync(int fd)
{
return common_sync(fd, true);
}
status_t
_kern_lock_node(int fd)
{
return common_lock_node(fd, true);
}
status_t
_kern_unlock_node(int fd)
{
return common_unlock_node(fd, true);
}
int
_kern_create_entry_ref(dev_t device, ino_t inode, const char *name, int omode, int perms)
{
return file_create_entry_ref(device, inode, name, omode, perms, true);
}
int
_kern_create(const char *path, int omode, int perms)
{
char buffer[B_PATH_NAME_LENGTH + 1];
strlcpy(buffer, path, B_PATH_NAME_LENGTH);
return file_create(buffer, omode, perms, true);
}
status_t
_kern_create_dir_entry_ref(dev_t device, 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 B_OK, if the directory has been created successfully, another
error code otherwise.
*/
status_t
_kern_create_dir(int fd, const char *path, int perms)
{
char pathBuffer[B_PATH_NAME_LENGTH + 1];
strlcpy(pathBuffer, path, B_PATH_NAME_LENGTH);
return dir_create(fd, pathBuffer, perms, true);
}
status_t
_kern_remove_dir(const char *path)
{
char pathBuffer[B_PATH_NAME_LENGTH + 1];
strlcpy(pathBuffer, path, B_PATH_NAME_LENGTH);
return dir_remove(pathBuffer, 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.
*
* \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 The size of the supplied buffer.
* \return The length of the link on success or an appropriate error code
*/
ssize_t
_kern_read_link(int fd, const char *path, char *buffer, size_t bufferSize)
{
status_t status;
if (path) {
char pathBuffer[B_PATH_NAME_LENGTH + 1];
strlcpy(pathBuffer, path, B_PATH_NAME_LENGTH);
status = common_read_link(fd, pathBuffer, buffer, bufferSize, true);
} else
status = common_read_link(fd, NULL, buffer, bufferSize, true);
if (status < B_OK)
return status;
// Unlike what POSIX wants, our file systems must always null terminate links
return strlen(buffer);
}
status_t
_kern_write_link(const char *path, const char *toPath)
{
char pathBuffer[B_PATH_NAME_LENGTH + 1];
char toPathBuffer[B_PATH_NAME_LENGTH + 1];
int status;
strlcpy(pathBuffer, path, B_PATH_NAME_LENGTH);
strlcpy(toPathBuffer, toPath, B_PATH_NAME_LENGTH);
status = check_path(toPathBuffer);
if (status < B_OK)
return status;
return common_write_link(pathBuffer, toPathBuffer, 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 path The absolute or relative path. Must not be \c NULL.
\param mode The access permissions the new symlink shall have.
\return \c B_OK, if the symlink has been created successfully, another
error code otherwise.
*/
status_t
_kern_create_symlink(int fd, const char *path, const char *toPath, int mode)
{
char pathBuffer[B_PATH_NAME_LENGTH + 1];
char toPathBuffer[B_PATH_NAME_LENGTH + 1];
status_t status;
strlcpy(pathBuffer, path, B_PATH_NAME_LENGTH);
strlcpy(toPathBuffer, toPath, B_PATH_NAME_LENGTH);
status = check_path(toPathBuffer);
if (status < B_OK)
return status;
return common_create_symlink(fd, pathBuffer, toPathBuffer, mode, true);
}
status_t
_kern_create_link(const char *path, const char *toPath)
{
char pathBuffer[B_PATH_NAME_LENGTH + 1];
char toPathBuffer[B_PATH_NAME_LENGTH + 1];
strlcpy(pathBuffer, path, B_PATH_NAME_LENGTH);
strlcpy(toPathBuffer, toPath, B_PATH_NAME_LENGTH);
return common_create_link(pathBuffer, toPathBuffer, 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 B_OK, if the entry has been removed successfully, another
error code otherwise.
*/
status_t
_kern_unlink(int fd, const char *path)
{
char pathBuffer[B_PATH_NAME_LENGTH + 1];
strlcpy(pathBuffer, path, B_PATH_NAME_LENGTH);
return common_unlink(fd, pathBuffer, 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 B_OK, if the entry has been moved successfully, another
error code otherwise.
*/
status_t
_kern_rename(int oldFD, const char *oldPath, int newFD, const char *newPath)
{
char oldPathBuffer[B_PATH_NAME_LENGTH + 1];
char newPathBuffer[B_PATH_NAME_LENGTH + 1];
strlcpy(oldPathBuffer, oldPath, B_PATH_NAME_LENGTH);
strlcpy(newPathBuffer, newPath, B_PATH_NAME_LENGTH);
return common_rename(oldFD, oldPathBuffer, newFD, newPathBuffer, true);
}
status_t
_kern_access(const char *path, int mode)
{
char pathBuffer[B_PATH_NAME_LENGTH + 1];
strlcpy(pathBuffer, path, B_PATH_NAME_LENGTH);
return common_access(pathBuffer, 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 B_OK, if the the stat data have been read successfully, another
error code otherwise.
*/
status_t
_kern_read_stat(int fd, const char *path, bool traverseLeafLink,
struct stat *stat, size_t statSize)
{
struct stat completeStat;
struct stat *originalStat = NULL;
status_t status;
if (statSize > sizeof(struct stat))
return B_BAD_VALUE;
// this supports different stat extensions
if (statSize < sizeof(struct stat)) {
originalStat = stat;
stat = &completeStat;
}
if (path) {
// path given: get the stat of the node referred to by (fd, path)
char pathBuffer[B_PATH_NAME_LENGTH + 1];
strlcpy(pathBuffer, path, B_PATH_NAME_LENGTH);
status = common_path_read_stat(fd, pathBuffer, 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 B_FILE_ERROR;
if (descriptor->ops->fd_read_stat)
status = descriptor->ops->fd_read_stat(descriptor, stat);
else
status = EOPNOTSUPP;
put_fd(descriptor);
}
if (status == B_OK && originalStat != NULL)
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 B_OK, if the the stat data have been written successfully,
another error code otherwise.
*/
status_t
_kern_write_stat(int fd, const char *path, bool traverseLeafLink,
const struct stat *stat, size_t statSize, int statMask)
{
struct stat completeStat;
if (statSize > sizeof(struct stat))
return B_BAD_VALUE;
// this supports different stat extensions
if (statSize < sizeof(struct stat)) {
memset((uint8 *)&completeStat + statSize, 0, sizeof(struct stat) - statSize);
memcpy(&completeStat, stat, statSize);
stat = &completeStat;
}
int status;
if (path) {
// path given: write the stat of the node referred to by (fd, path)
char pathBuffer[B_PATH_NAME_LENGTH + 1];
strlcpy(pathBuffer, path, B_PATH_NAME_LENGTH);
status = common_path_write_stat(fd, pathBuffer, 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 B_FILE_ERROR;
if (descriptor->ops->fd_write_stat)
status = descriptor->ops->fd_write_stat(descriptor, stat, statMask);
else
status = EOPNOTSUPP;
put_fd(descriptor);
}
return status;
}
int
_kern_open_attr_dir(int fd, const char *path)
{
char pathBuffer[B_PATH_NAME_LENGTH + 1];
if (fd == -1)
strlcpy(pathBuffer, path, B_PATH_NAME_LENGTH);
return attr_dir_open(fd, pathBuffer, true);
}
int
_kern_create_attr(int fd, const char *name, uint32 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);
}
status_t
_kern_remove_attr(int fd, const char *name)
{
return attr_remove(fd, name, true);
}
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(dev_t device)
{
return index_dir_open(device, true);
}
status_t
_kern_create_index(dev_t device, const char *name, uint32 type, uint32 flags)
{
return index_create(device, name, type, flags, true);
}
status_t
_kern_read_index_stat(dev_t device, const char *name, struct stat *stat)
{
return index_name_read_stat(device, name, stat, true);
}
status_t
_kern_remove_index(dev_t device, const char *name)
{
return index_remove(device, name, true);
}
status_t
_kern_getcwd(char *buffer, size_t size)
{
char path[B_PATH_NAME_LENGTH];
int status;
PRINT(("sys_getcwd: buf %p, %ld\n", buffer, size));
// Call vfs to get current working directory
status = get_cwd(path, B_PATH_NAME_LENGTH - 1, true);
if (status < 0)
return status;
path[B_PATH_NAME_LENGTH - 1] = '\0';
strlcpy(buffer, path, size);
return status;
}
status_t
_kern_setcwd(int fd, const char *path)
{
char pathBuffer[B_PATH_NAME_LENGTH + 1];
if (fd == -1)
strlcpy(pathBuffer, path, B_PATH_NAME_LENGTH);
return set_cwd(fd, pathBuffer, true);
}
// #pragma mark -
// Calls from userland (with extra address checks)
status_t
_user_mount(const char *userPath, const char *userDevice, const char *userFileSystem,
uint32 flags, const char *userArgs)
{
char fileSystem[B_OS_NAME_LENGTH];
KPath path, device;
char *args = NULL;
status_t status;
if (!IS_USER_ADDRESS(userPath)
|| !IS_USER_ADDRESS(userFileSystem)
|| !IS_USER_ADDRESS(userDevice))
return B_BAD_ADDRESS;
if (path.InitCheck() != B_OK || device.InitCheck() != B_OK)
return B_NO_MEMORY;
if (user_strlcpy(path.LockBuffer(), userPath, B_PATH_NAME_LENGTH) < B_OK)
return B_BAD_ADDRESS;
if (userFileSystem != NULL
&& user_strlcpy(fileSystem, userFileSystem, sizeof(fileSystem)) < B_OK)
return B_BAD_ADDRESS;
if (userDevice != NULL
&& user_strlcpy(device.LockBuffer(), userDevice, B_PATH_NAME_LENGTH) < B_OK)
return B_BAD_ADDRESS;
if (userArgs != NULL) {
// We have no real length restriction, so we need to create
// a buffer large enough to hold the argument string
// ToDo: we could think about determinung the length of the string
// in userland :)
ssize_t length = user_strlcpy(args, userArgs, 0);
if (length < B_OK)
return B_BAD_ADDRESS;
// this is a safety restriction
if (length > 32 * 1024)
return B_NAME_TOO_LONG;
if (length > 0) {
args = (char *)malloc(length + 1);
if (args == NULL)
return B_NO_MEMORY;
if (user_strlcpy(args, userArgs, length + 1) < B_OK) {
free(args);
return B_BAD_ADDRESS;
}
}
}
path.UnlockBuffer();
device.UnlockBuffer();
status = fs_mount(path.LockBuffer(), userDevice != NULL ? device.Path() : NULL,
userFileSystem ? fileSystem : NULL, flags, args, false);
free(args);
return status;
}
status_t
_user_unmount(const char *userPath)
{
char path[B_PATH_NAME_LENGTH + 1];
int status;
status = user_strlcpy(path, userPath, B_PATH_NAME_LENGTH);
if (status < 0)
return status;
return fs_unmount(path, false);
}
status_t
_user_read_fs_info(dev_t device, struct fs_info *userInfo)
{
struct fs_info info;
status_t status;
if (userInfo == NULL)
return B_BAD_VALUE;
if (!IS_USER_ADDRESS(userInfo))
return B_BAD_ADDRESS;
status = fs_read_info(device, &info);
if (status != B_OK)
return status;
if (user_memcpy(userInfo, &info, sizeof(struct fs_info)) < B_OK)
return B_BAD_ADDRESS;
return B_OK;
}
status_t
_user_write_fs_info(dev_t device, const struct fs_info *userInfo, int mask)
{
struct fs_info info;
if (userInfo == NULL)
return B_BAD_VALUE;
if (!IS_USER_ADDRESS(userInfo)
|| user_memcpy(&info, userInfo, sizeof(struct fs_info)) < B_OK)
return B_BAD_ADDRESS;
return fs_write_info(device, &info, mask);
}
dev_t
_user_next_device(int32 *_userCookie)
{
int32 cookie;
dev_t device;
if (!IS_USER_ADDRESS(_userCookie)
|| user_memcpy(&cookie, _userCookie, sizeof(int32)) < B_OK)
return B_BAD_ADDRESS;
device = fs_next_device(&cookie);
if (device >= B_OK) {
// update user cookie
if (user_memcpy(_userCookie, &cookie, sizeof(int32)) < B_OK)
return B_BAD_ADDRESS;
}
return device;
}
status_t
_user_sync(void)
{
return fs_sync();
}
status_t
_user_entry_ref_to_path(dev_t device, ino_t inode, const char *leaf,
char *userPath, size_t pathLength)
{
char path[B_PATH_NAME_LENGTH + 1];
struct vnode *vnode;
int status;
if (!IS_USER_ADDRESS(userPath))
return B_BAD_ADDRESS;
// copy the leaf name onto the stack
char stackLeaf[B_FILE_NAME_LENGTH];
if (leaf) {
if (!IS_USER_ADDRESS(leaf))
return B_BAD_ADDRESS;
int len = user_strlcpy(stackLeaf, leaf, B_FILE_NAME_LENGTH);
if (len < 0)
return len;
if (len >= B_FILE_NAME_LENGTH)
return B_NAME_TOO_LONG;
leaf = stackLeaf;
// filter invalid leaf names
if (leaf[0] == '\0' || strchr(leaf, '/'))
return B_BAD_VALUE;
}
// get the vnode matching the dir's node_ref
if (leaf && (strcmp(leaf, ".") == 0 || 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 < B_OK)
return status;
// get the directory path
status = dir_vnode_to_path(vnode, path, sizeof(path));
put_vnode(vnode);
// we don't need the vnode anymore
if (status < B_OK)
return status;
// append the leaf name
if (leaf) {
// insert a directory separator if this is not the file system root
if ((strcmp(path, "/") && strlcat(path, "/", sizeof(path)) >= sizeof(path))
|| strlcat(path, leaf, sizeof(path)) >= sizeof(path)) {
return B_NAME_TOO_LONG;
}
}
int len = user_strlcpy(userPath, path, pathLength);
if (len < 0)
return len;
if (len >= (int)pathLength)
return B_BUFFER_OVERFLOW;
return B_OK;
}
int
_user_open_entry_ref(dev_t device, ino_t inode, const char *userName, int omode)
{
char name[B_FILE_NAME_LENGTH];
int status;
if (!IS_USER_ADDRESS(userName))
return B_BAD_ADDRESS;
status = user_strlcpy(name, userName, sizeof(name));
if (status < B_OK)
return status;
return file_open_entry_ref(device, inode, name, omode, false);
}
int
_user_open(int fd, const char *userPath, int omode)
{
char path[B_PATH_NAME_LENGTH + 1];
int status;
if (!IS_USER_ADDRESS(userPath))
return B_BAD_ADDRESS;
status = user_strlcpy(path, userPath, B_PATH_NAME_LENGTH);
if (status < 0)
return status;
return file_open(-1, path, omode, false);
}
int
_user_open_dir_entry_ref(dev_t device, ino_t inode, const char *uname)
{
if (uname) {
char name[B_FILE_NAME_LENGTH];
if (!IS_USER_ADDRESS(uname))
return B_BAD_ADDRESS;
int status = user_strlcpy(name, uname, sizeof(name));
if (status < B_OK)
return status;
return dir_open_entry_ref(device, inode, name, false);
}
return dir_open_entry_ref(device, inode, NULL, false);
}
int
_user_open_dir(int fd, const char *userPath)
{
char path[B_PATH_NAME_LENGTH + 1];
int status;
if (!IS_USER_ADDRESS(userPath))
return B_BAD_ADDRESS;
status = user_strlcpy(path, userPath, B_PATH_NAME_LENGTH);
if (status < 0)
return status;
return dir_open(fd, path, false);
}
/** \brief Opens a directory's parent directory and returns the entry name
of the former.
Aside from that is returns the directory's entry name, this method is
equivalent to \code _user_open_dir(fd, "..") \endcode. It really is
equivalent, if \a userName is \c NULL.
If a name buffer is supplied and the name does not fit the buffer, the
function fails. A buffer of size \c B_FILE_NAME_LENGTH should be safe.
\param fd A FD referring to a directory.
\param userName Buffer the directory's entry name shall be written into.
May be \c NULL.
\param nameLength Size of the name buffer.
\return The file descriptor of the opened parent directory, if everything
went fine, an error code otherwise.
*/
int
_user_open_parent_dir(int fd, char *userName, size_t nameLength)
{
bool kernel = false;
if (userName && !IS_USER_ADDRESS(userName))
return B_BAD_ADDRESS;
// open the parent dir
int parentFD = dir_open(fd, "..", kernel);
if (parentFD < 0)
return parentFD;
FDCloser fdCloser(parentFD, kernel);
if (userName) {
// get the vnodes
struct vnode *parentVNode = get_vnode_from_fd(parentFD, kernel);
struct vnode *dirVNode = get_vnode_from_fd(fd, kernel);
VNodePutter parentVNodePutter(parentVNode);
VNodePutter dirVNodePutter(dirVNode);
if (!parentVNode || !dirVNode)
return B_FILE_ERROR;
// get the vnode name
char name[B_FILE_NAME_LENGTH];
status_t status = get_vnode_name(dirVNode, parentVNode,
name, sizeof(name));
if (status != B_OK)
return status;
// copy the name to the userland buffer
int len = user_strlcpy(userName, name, nameLength);
if (len < 0)
return len;
if (len >= (int)nameLength)
return B_BUFFER_OVERFLOW;
}
return fdCloser.Detach();
}
status_t
_user_fcntl(int fd, int op, uint32 argument)
{
return common_fcntl(fd, op, argument, false);
}
status_t
_user_fsync(int fd)
{
return common_sync(fd, false);
}
status_t
_user_lock_node(int fd)
{
return common_lock_node(fd, false);
}
status_t
_user_unlock_node(int fd)
{
return common_unlock_node(fd, false);
}
int
_user_create_entry_ref(dev_t device, ino_t inode, const char *userName, int openMode, int perms)
{
char name[B_FILE_NAME_LENGTH];
int status;
if (!IS_USER_ADDRESS(userName))
return B_BAD_ADDRESS;
status = user_strlcpy(name, userName, sizeof(name));
if (status < 0)
return status;
return file_create_entry_ref(device, inode, name, openMode, perms, false);
}
int
_user_create(const char *userPath, int openMode, int perms)
{
char path[B_PATH_NAME_LENGTH + 1];
int status;
if (!IS_USER_ADDRESS(userPath))
return B_BAD_ADDRESS;
status = user_strlcpy(path, userPath, B_PATH_NAME_LENGTH);
if (status < 0)
return status;
return file_create(path, openMode, perms, false);
}
status_t
_user_create_dir_entry_ref(dev_t device, ino_t inode, const char *userName, int perms)
{
char name[B_FILE_NAME_LENGTH];
status_t status;
if (!IS_USER_ADDRESS(userName))
return B_BAD_ADDRESS;
status = user_strlcpy(name, userName, sizeof(name));
if (status < 0)
return status;
return dir_create_entry_ref(device, inode, name, perms, false);
}
status_t
_user_create_dir(int fd, const char *userPath, int perms)
{
char path[B_PATH_NAME_LENGTH + 1];
status_t status;
if (!IS_USER_ADDRESS(userPath))
return B_BAD_ADDRESS;
status = user_strlcpy(path, userPath, B_PATH_NAME_LENGTH);
if (status < 0)
return status;
return dir_create(fd, path, perms, false);
}
status_t
_user_remove_dir(const char *userPath)
{
char path[B_PATH_NAME_LENGTH + 1];
int status;
if (!IS_USER_ADDRESS(userPath))
return B_BAD_ADDRESS;
status = user_strlcpy(path, userPath, B_PATH_NAME_LENGTH);
if (status < 0)
return status;
return dir_remove(path, false);
}
ssize_t
_user_read_link(int fd, const char *userPath, char *userBuffer, size_t bufferSize)
{
char path[B_PATH_NAME_LENGTH + 1];
char buffer[B_PATH_NAME_LENGTH];
int status;
if (!IS_USER_ADDRESS(userBuffer))
return B_BAD_ADDRESS;
if (userPath) {
if (!IS_USER_ADDRESS(userPath))
return B_BAD_ADDRESS;
status = user_strlcpy(path, userPath, B_PATH_NAME_LENGTH);
if (status < 0)
return status;
if (bufferSize > B_PATH_NAME_LENGTH)
bufferSize = B_PATH_NAME_LENGTH;
status = common_read_link(fd, path, buffer, bufferSize, false);
} else
status = common_read_link(fd, NULL, buffer, bufferSize, false);
if (status < B_OK)
return status;
status = user_strlcpy(userBuffer, buffer, bufferSize);
if (status < 0)
return status;
return (status >= (int)bufferSize ? bufferSize : status + 1);
}
status_t
_user_write_link(const char *userPath, const char *userToPath)
{
char path[B_PATH_NAME_LENGTH + 1];
char toPath[B_PATH_NAME_LENGTH + 1];
int status;
if (!IS_USER_ADDRESS(userPath)
|| !IS_USER_ADDRESS(userToPath))
return B_BAD_ADDRESS;
status = user_strlcpy(path, userPath, B_PATH_NAME_LENGTH);
if (status < 0)
return status;
status = user_strlcpy(toPath, userToPath, B_PATH_NAME_LENGTH);
if (status < 0)
return status;
status = check_path(toPath);
if (status < B_OK)
return status;
return common_write_link(path, toPath, false);
}
status_t
_user_create_symlink(int fd, const char *userPath, const char *userToPath,
int mode)
{
char path[B_PATH_NAME_LENGTH + 1];
char toPath[B_PATH_NAME_LENGTH + 1];
status_t status;
if (!IS_USER_ADDRESS(userPath)
|| !IS_USER_ADDRESS(userToPath))
return B_BAD_ADDRESS;
status = user_strlcpy(path, userPath, B_PATH_NAME_LENGTH);
if (status < 0)
return status;
status = user_strlcpy(toPath, userToPath, B_PATH_NAME_LENGTH);
if (status < 0)
return status;
status = check_path(toPath);
if (status < B_OK)
return status;
return common_create_symlink(fd, path, toPath, mode, false);
}
status_t
_user_create_link(const char *userPath, const char *userToPath)
{
char path[B_PATH_NAME_LENGTH + 1];
char toPath[B_PATH_NAME_LENGTH + 1];
status_t status;
if (!IS_USER_ADDRESS(userPath)
|| !IS_USER_ADDRESS(userToPath))
return B_BAD_ADDRESS;
status = user_strlcpy(path, userPath, B_PATH_NAME_LENGTH);
if (status < 0)
return status;
status = user_strlcpy(toPath, userToPath, B_PATH_NAME_LENGTH);
if (status < 0)
return status;
status = check_path(toPath);
if (status < B_OK)
return status;
return common_create_link(path, toPath, false);
}
status_t
_user_unlink(int fd, const char *userPath)
{
char path[B_PATH_NAME_LENGTH + 1];
int status;
if (!IS_USER_ADDRESS(userPath))
return B_BAD_ADDRESS;
status = user_strlcpy(path, userPath, B_PATH_NAME_LENGTH);
if (status < 0)
return status;
return common_unlink(fd, path, false);
}
status_t
_user_rename(int oldFD, const char *userOldPath, int newFD,
const char *userNewPath)
{
char oldPath[B_PATH_NAME_LENGTH + 1];
char newPath[B_PATH_NAME_LENGTH + 1];
int status;
if (!IS_USER_ADDRESS(userOldPath) || !IS_USER_ADDRESS(userNewPath))
return B_BAD_ADDRESS;
status = user_strlcpy(oldPath, userOldPath, B_PATH_NAME_LENGTH);
if (status < 0)
return status;
status = user_strlcpy(newPath, userNewPath, B_PATH_NAME_LENGTH);
if (status < 0)
return status;
return common_rename(oldFD, oldPath, newFD, newPath, false);
}
status_t
_user_access(const char *userPath, int mode)
{
char path[B_PATH_NAME_LENGTH + 1];
int status;
if (!IS_USER_ADDRESS(userPath))
return B_BAD_ADDRESS;
status = user_strlcpy(path, userPath, B_PATH_NAME_LENGTH);
if (status < 0)
return status;
return common_access(path, mode, false);
}
status_t
_user_read_stat(int fd, const char *userPath, bool traverseLink,
struct stat *userStat, size_t statSize)
{
struct stat stat;
int status;
if (statSize > sizeof(struct stat))
return B_BAD_VALUE;
if (!IS_USER_ADDRESS(userStat))
return B_BAD_ADDRESS;
if (userPath) {
// path given: get the stat of the node referred to by (fd, path)
char path[B_PATH_NAME_LENGTH + 1];
if (!IS_USER_ADDRESS(userPath))
return B_BAD_ADDRESS;
int len = user_strlcpy(path, userPath, B_PATH_NAME_LENGTH);
if (len < 0)
return len;
if (len >= B_PATH_NAME_LENGTH)
return B_NAME_TOO_LONG;
status = common_path_read_stat(fd, path, traverseLink, &stat, false);
} else {
// no path given: get the FD and use the FD operation
struct file_descriptor *descriptor
= get_fd(get_current_io_context(false), fd);
if (descriptor == NULL)
return B_FILE_ERROR;
if (descriptor->ops->fd_read_stat)
status = descriptor->ops->fd_read_stat(descriptor, &stat);
else
status = EOPNOTSUPP;
put_fd(descriptor);
}
if (status < B_OK)
return status;
return user_memcpy(userStat, &stat, statSize);
}
status_t
_user_write_stat(int fd, const char *userPath, bool traverseLeafLink,
const struct stat *userStat, size_t statSize, int statMask)
{
char path[B_PATH_NAME_LENGTH + 1];
struct stat stat;
if (statSize > sizeof(struct stat))
return B_BAD_VALUE;
if (!IS_USER_ADDRESS(userStat)
|| user_memcpy(&stat, userStat, statSize) < B_OK)
return B_BAD_ADDRESS;
// clear additional stat fields
if (statSize < sizeof(struct stat))
memset((uint8 *)&stat + statSize, 0, sizeof(struct stat) - statSize);
status_t status;
if (userPath) {
// path given: write the stat of the node referred to by (fd, path)
if (!IS_USER_ADDRESS(userPath))
return B_BAD_ADDRESS;
int len = user_strlcpy(path, userPath, B_PATH_NAME_LENGTH);
if (len < 0)
return len;
if (len >= B_PATH_NAME_LENGTH)
return B_NAME_TOO_LONG;
status = common_path_write_stat(fd, path, traverseLeafLink, &stat,
statMask, false);
} else {
// no path given: get the FD and use the FD operation
struct file_descriptor *descriptor
= get_fd(get_current_io_context(false), fd);
if (descriptor == NULL)
return B_FILE_ERROR;
if (descriptor->ops->fd_write_stat)
status = descriptor->ops->fd_write_stat(descriptor, &stat, statMask);
else
status = EOPNOTSUPP;
put_fd(descriptor);
}
return status;
}
int
_user_open_attr_dir(int fd, const char *userPath)
{
char pathBuffer[B_PATH_NAME_LENGTH + 1];
if (fd == -1) {
if (!IS_USER_ADDRESS(userPath)
|| user_strlcpy(pathBuffer, userPath, B_PATH_NAME_LENGTH) < B_OK)
return B_BAD_ADDRESS;
}
return attr_dir_open(fd, pathBuffer, false);
}
int
_user_create_attr(int fd, const char *userName, uint32 type, int openMode)
{
char name[B_FILE_NAME_LENGTH];
if (!IS_USER_ADDRESS(userName)
|| user_strlcpy(name, userName, B_FILE_NAME_LENGTH) < B_OK)
return B_BAD_ADDRESS;
return attr_create(fd, name, type, openMode, false);
}
int
_user_open_attr(int fd, const char *userName, int openMode)
{
char name[B_FILE_NAME_LENGTH];
if (!IS_USER_ADDRESS(userName)
|| user_strlcpy(name, userName, B_FILE_NAME_LENGTH) < B_OK)
return B_BAD_ADDRESS;
return attr_open(fd, name, openMode, false);
}
status_t
_user_remove_attr(int fd, const char *userName)
{
char name[B_FILE_NAME_LENGTH];
if (!IS_USER_ADDRESS(userName)
|| user_strlcpy(name, userName, B_FILE_NAME_LENGTH) < B_OK)
return B_BAD_ADDRESS;
return attr_remove(fd, name, false);
}
status_t
_user_rename_attr(int fromFile, const char *userFromName, int toFile, const char *userToName)
{
char fromName[B_FILE_NAME_LENGTH];
char toName[B_FILE_NAME_LENGTH];
if (!IS_USER_ADDRESS(userFromName)
|| !IS_USER_ADDRESS(userToName))
return B_BAD_ADDRESS;
if (user_strlcpy(fromName, userFromName, B_FILE_NAME_LENGTH) < B_OK
|| user_strlcpy(toName, userToName, B_FILE_NAME_LENGTH) < B_OK)
return B_BAD_ADDRESS;
return attr_rename(fromFile, fromName, toFile, toName, false);
}
int
_user_open_index_dir(dev_t device)
{
return index_dir_open(device, false);
}
status_t
_user_create_index(dev_t device, const char *userName, uint32 type, uint32 flags)
{
char name[B_FILE_NAME_LENGTH];
if (!IS_USER_ADDRESS(userName)
|| user_strlcpy(name, userName, B_FILE_NAME_LENGTH) < B_OK)
return B_BAD_ADDRESS;
return index_create(device, name, type, flags, false);
}
status_t
_user_read_index_stat(dev_t device, const char *userName, struct stat *userStat)
{
char name[B_FILE_NAME_LENGTH];
struct stat stat;
status_t status;
if (!IS_USER_ADDRESS(userName)
|| !IS_USER_ADDRESS(userStat)
|| user_strlcpy(name, userName, B_FILE_NAME_LENGTH) < B_OK)
return B_BAD_ADDRESS;
status = index_name_read_stat(device, name, &stat, false);
if (status == B_OK) {
if (user_memcpy(userStat, &stat, sizeof(stat)) < B_OK)
return B_BAD_ADDRESS;
}
return status;
}
status_t
_user_remove_index(dev_t device, const char *userName)
{
char name[B_FILE_NAME_LENGTH];
if (!IS_USER_ADDRESS(userName)
|| user_strlcpy(name, userName, B_FILE_NAME_LENGTH) < B_OK)
return B_BAD_ADDRESS;
return index_remove(device, name, false);
}
status_t
_user_getcwd(char *userBuffer, size_t size)
{
char buffer[B_PATH_NAME_LENGTH];
int status;
PRINT(("user_getcwd: buf %p, %ld\n", userBuffer, size));
if (!IS_USER_ADDRESS(userBuffer))
return B_BAD_ADDRESS;
if (size > B_PATH_NAME_LENGTH)
size = B_PATH_NAME_LENGTH;
status = get_cwd(buffer, size, false);
if (status < 0)
return status;
// Copy back the result
if (user_strlcpy(userBuffer, buffer, size) < B_OK)
return B_BAD_ADDRESS;
return status;
}
status_t
_user_setcwd(int fd, const char *userPath)
{
char path[B_PATH_NAME_LENGTH];
PRINT(("user_setcwd: path = %p\n", userPath));
if (fd == -1) {
if (!IS_USER_ADDRESS(userPath)
|| user_strlcpy(path, userPath, B_PATH_NAME_LENGTH) < B_OK)
return B_BAD_ADDRESS;
}
return set_cwd(fd, path, false);
}
int
_user_open_query(dev_t device, const char *query, uint32 flags, port_id port,
int32 token)
{
// TODO: Implement!
return B_ERROR;
}