haiku/src/kernel/core/fs/vfs.c

4201 lines
90 KiB
C
Raw Normal View History

/* Virtual File System and
** File System Interface Layer
*/
/*
** Copyright 2002, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
** Distributed under the terms of the OpenBeOS License.
*/
/*
** Copyright 2001-2002, Travis Geiselbrecht. All rights reserved.
** Distributed under the terms of the NewOS License.
*/
#include <kernel.h>
#include <stage2.h>
#include <vfs.h>
#include <vm.h>
#include <vm_cache.h>
#include <debug.h>
#include <khash.h>
#include <lock.h>
#include <thread.h>
#include <memheap.h>
#include <arch/cpu.h>
#include <elf.h>
#include <Errors.h>
#include <kerrors.h>
#include <atomic.h>
#include <fd.h>
#include <OS.h>
#include <devfs.h>
#include "rootfs.h"
#include "bootfs.h"
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <resource.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <limits.h>
#include <StorageDefs.h>
#include <fs_info.h>
#ifndef TRACE_VFS
# define TRACE_VFS 1
#endif
#if 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
// Passed in buffers from user-space shouldn't be in the kernel
#define CHECK_USER_ADDRESS(x) \
((addr)(x) < KERNEL_BASE || (addr)(x) > KERNEL_TOP)
struct vnode {
struct vnode *next;
struct vnode *mount_prev;
struct vnode *mount_next;
struct vm_cache *cache;
mount_id mount_id;
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 mount_id;
vnode_id vnode_id;
};
typedef struct file_system {
struct file_system *next;
struct fs_ops *ops;
const char *name;
image_id image;
int32 ref_count;
} file_system;
#define FS_CALL(vnode, op) (vnode->mount->fs->ops->op)
#define FS_MOUNT_CALL(mount, op) (mount->fs->ops->op)
struct fs_mount {
struct fs_mount *next;
file_system *fs;
mount_id id;
void *cookie;
char *mount_point;
recursive_lock rlock;
struct vnode *root_vnode;
struct vnode *covers_vnode;
struct vnode *vnodes_head;
struct vnode *vnodes_tail;
bool unmounting;
};
static file_system *gFileSystems;
static mutex gFileSystemsMutex;
static mutex gMountMutex;
static mutex gMountOpMutex;
static mutex gVnodeMutex;
#define VNODE_HASH_TABLE_SIZE 1024
static void *gVnodeTable;
static struct vnode *gRoot;
#define MOUNTS_HASH_TABLE_SIZE 16
static void *gMountsTable;
static mount_id gNextMountID = 0;
/* function declarations */
static int vfs_mount(char *path, const char *device, const char *fs_name, void *args, bool kernel);
static int vfs_unmount(char *path, bool kernel);
static ssize_t vfs_read(struct file_descriptor *, void *, off_t, size_t *);
static ssize_t vfs_write(struct file_descriptor *, const void *, off_t, size_t *);
static int vfs_ioctl(struct file_descriptor *, ulong, void *buf, size_t len);
static status_t vfs_read_dir(struct file_descriptor *,struct dirent *buffer,size_t bufferSize,uint32 *_count);
static status_t vfs_rewind_dir(struct file_descriptor *);
static int vfs_read_stat(struct file_descriptor *, struct stat *);
static int vfs_close(struct file_descriptor *, int, struct io_context *);
// file descriptor operation prototypes
static ssize_t file_read(struct file_descriptor *, off_t pos, void *buffer, size_t *);
static ssize_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_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 ssize_t attr_read(struct file_descriptor *, off_t pos, void *buffer, size_t *);
static ssize_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 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 int vfs_open(char *path, int omode, bool kernel);
static int vfs_open_dir(char *path, bool kernel);
static int vfs_create(char *path, int omode, int perms, bool kernel);
static int vfs_create_dir(char *path, int perms, bool kernel);
static status_t dir_vnode_to_path(struct vnode *vnode, char *buffer, size_t bufferSize);
struct fd_ops gFileOps = {
file_read,
file_write,
file_seek,
common_ioctl,
NULL,
NULL,
common_read_stat,
file_close,
file_free_fd
};
struct fd_ops gDirectoryOps = {
NULL,
NULL,
NULL,
common_ioctl,
dir_read,
dir_rewind,
common_read_stat,
dir_close,
dir_free_fd
};
struct fd_ops gAttributeDirectoryOps = {
NULL,
NULL,
NULL,
common_ioctl,
attr_dir_read,
attr_dir_rewind,
common_read_stat,
attr_dir_close,
attr_dir_free_fd
};
struct fd_ops gAttributeOps = {
attr_read,
attr_write,
attr_seek,
common_ioctl,
NULL,
NULL,
attr_read_stat,
attr_close,
attr_free_fd
};
struct fd_ops gIndexDirectoryOps = {
NULL,
NULL,
NULL,
NULL,
index_dir_read,
index_dir_rewind,
NULL,
index_dir_close,
index_dir_free_fd
};
static int
mount_compare(void *_m, const void *_key)
{
struct fs_mount *mount = _m;
const mount_id *id = _key;
if (mount->id == *id)
return 0;
return -1;
}
static unsigned int
mount_hash(void *_m, const void *_key, unsigned int range)
{
struct fs_mount *mount = _m;
const mount_id *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)
{
struct fs_mount *mount;
ASSERT_LOCKED_MUTEX(&gMountMutex);
mount = hash_lookup(gMountsTable, &id);
return mount;
}
/** Creates a new file_system structure.
* The gFileSystems lock must be hold when you call this function.
*/
static file_system *
new_file_system(const char *name, struct fs_ops *ops)
{
file_system *fs;
ASSERT_LOCKED_MUTEX(&gFileSystemsMutex);
fs = (struct file_system *)kmalloc(sizeof(struct file_system));
if (fs == NULL)
return NULL;
fs->name = name;
fs->ops = ops;
fs->image = -1;
fs->ref_count = 0;
// add it to the queue
fs->next = gFileSystems;
gFileSystems = fs;
return B_OK;
}
static status_t
unload_file_system(file_system *fs)
{
void (*uninit)();
// The image_id is invalid if it's an internal file system
if (fs->image < B_OK)
return B_OK;
uninit = (void *)elf_lookup_symbol(fs->image, "uninit_file_system");
if (uninit != NULL)
uninit();
// ToDo: unloading is not yet supported - we need a unload image_id first...
kfree(fs);
return B_OK;
}
static file_system *
load_file_system(const char *name)
{
char path[SYS_MAX_PATH_LEN];
file_system *fs;
struct fs_ops **ops;
int32 *version;
void (*init)();
image_id image;
// search in the user directory
sprintf(path, "/boot/home/config/add-ons/kernel/file_systems/%s", name);
image = elf_load_kspace(path, "");
if (image == B_ENTRY_NOT_FOUND) {
// search in the system directory
sprintf(path, "/boot/addons/fs/%s", name);
//sprintf(path, "/boot/beos/system/add-ons/kernel/file_systems/%s", name);
image = elf_load_kspace(path, "");
}
if (image < B_OK)
return NULL;
init = (void *)elf_lookup_symbol(image, "init_file_system");
version = (int32 *)elf_lookup_symbol(image, "api_version");
ops = (fs_ops **)elf_lookup_symbol(image, "fs_entry");
if (init == NULL || version == NULL || ops == NULL) {
dprintf("vfs: add-on \"%s\" doesn't export all necessary symbols.\n", name);
goto err;
}
if (*version != B_CURRENT_FS_API_VERSION) {
dprintf("vfs: add-on \"%s\" supports unknown API version %ld\n", name, *version);
goto err;
}
fs = new_file_system(name, *ops);
if (fs == NULL)
goto err;
fs->image = image;
// initialize file system add-on
init();
return fs;
err:
elf_unload_kspace(path);
return NULL;
}
static status_t
put_file_system(file_system *fs)
{
status_t status;
mutex_lock(&gFileSystemsMutex);
if (--fs->ref_count == 0)
status = unload_file_system(fs);
else
status = B_OK;
mutex_unlock(&gFileSystemsMutex);
return status;
}
static file_system *
get_file_system(const char *name)
{
file_system *fs;
mutex_lock(&gFileSystemsMutex);
for (fs = gFileSystems; fs != NULL; fs = fs->next) {
if (!strcmp(name, fs->name))
break;
}
if (fs == NULL)
fs = load_file_system(name);
// if we find a suitable file system, increment its reference counter
if (fs)
fs->ref_count++;
mutex_unlock(&gFileSystemsMutex);
return fs;
}
static int
vnode_compare(void *_v, const void *_key)
{
struct vnode *v = _v;
const struct vnode_hash_key *key = _key;
if (v->mount_id == key->mount_id && v->id == key->vnode_id)
return 0;
return -1;
}
static unsigned int
vnode_hash(void *_v, const void *_key, unsigned int range)
{
struct vnode *vnode = _v;
const struct vnode_hash_key *key = _key;
#define VHASH(mountid, vnodeid) (((uint32)((vnodeid) >> 32) + (uint32)(vnodeid)) ^ (uint32)(mountid))
if (vnode != NULL)
return (VHASH(vnode->mount_id, vnode->id) % range);
else
return (VHASH(key->mount_id, key->vnode_id) % range);
#undef VHASH
}
static void
add_vnode_to_mount_list(struct vnode *vnode, struct fs_mount *mount)
{
recursive_lock_lock(&mount->rlock);
vnode->mount_next = mount->vnodes_head;
vnode->mount_prev = NULL;
if (vnode->mount_next)
vnode->mount_next->mount_prev = vnode;
mount->vnodes_head = vnode;
if (mount->vnodes_tail == NULL)
mount->vnodes_tail = vnode;
recursive_lock_unlock(&mount->rlock);
}
static void
remove_vnode_from_mount_list(struct vnode *vnode, struct fs_mount *mount)
{
recursive_lock_lock(&mount->rlock);
if (vnode->mount_next)
vnode->mount_next->mount_prev = vnode->mount_prev;
else
mount->vnodes_tail = vnode->mount_prev;
if (vnode->mount_prev)
vnode->mount_prev->mount_next = vnode->mount_next;
else
mount->vnodes_head = vnode->mount_next;
vnode->mount_prev = vnode->mount_next = NULL;
recursive_lock_unlock(&mount->rlock);
}
static struct vnode *
create_new_vnode(void)
{
struct vnode *vnode;
vnode = (struct vnode *)kmalloc(sizeof(struct vnode));
if (vnode == NULL)
return NULL;
memset(vnode, 0, sizeof(struct vnode));
return vnode;
}
static int
dec_vnode_ref_count(struct vnode *vnode, bool reenter)
{
int err;
int old_ref;
mutex_lock(&gVnodeMutex);
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(&gVnodeMutex);
/* 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(&gVnodeMutex);
hash_remove(gVnodeTable, vnode);
mutex_unlock(&gVnodeMutex);
kfree(vnode);
err = 1;
} else {
mutex_unlock(&gVnodeMutex);
err = 0;
}
return err;
}
static int
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));
return 0;
}
static struct vnode *
lookup_vnode(mount_id mountID, vnode_id vnodeID)
{
struct vnode_hash_key key;
key.mount_id = mountID;
key.vnode_id = vnodeID;
return hash_lookup(gVnodeTable, &key);
}
static int
get_vnode(mount_id mountID, vnode_id vnodeID, struct vnode **_vnode, int reenter)
{
struct vnode *vnode;
int err;
FUNCTION(("get_vnode: mountid %ld vnid 0x%Lx %p\n", mountID, vnodeID, _vnode));
mutex_lock(&gVnodeMutex);
while (true) {
vnode = lookup_vnode(mountID, vnodeID);
if (vnode) {
if (vnode->busy) {
mutex_unlock(&gVnodeMutex);
snooze(10000); // 10 ms
mutex_lock(&gVnodeMutex);
continue;
}
}
break;
}
PRINT(("get_vnode: tried to lookup vnode, got 0x%p\n", vnode));
if (vnode) {
inc_vnode_ref_count(vnode);
} else {
// we need to create a new vnode and read it in
vnode = create_new_vnode();
if (!vnode) {
err = B_NO_MEMORY;
goto err;
}
vnode->mount_id = mountID;
vnode->id = vnodeID;
mutex_lock(&gMountMutex);
vnode->mount = find_mount(mountID);
if (!vnode->mount) {
mutex_unlock(&gMountMutex);
err = ERR_INVALID_HANDLE;
goto err;
}
vnode->busy = true;
hash_insert(gVnodeTable, vnode);
mutex_unlock(&gVnodeMutex);
add_vnode_to_mount_list(vnode, vnode->mount);
mutex_unlock(&gMountMutex);
err = FS_CALL(vnode, get_vnode)(vnode->mount->cookie, vnodeID, &vnode->private_node, reenter);
if (err < 0 && vnode->private_node == NULL) {
remove_vnode_from_mount_list(vnode, vnode->mount);
if (vnode->private_node == NULL)
err = EINVAL;
}
mutex_lock(&gVnodeMutex);
if (err < 0)
goto err1;
vnode->busy = false;
vnode->ref_count = 1;
}
mutex_unlock(&gVnodeMutex);
PRINT(("get_vnode: returning 0x%p\n", vnode));
*_vnode = vnode;
return B_OK;
err1:
hash_remove(gVnodeTable, vnode);
err:
mutex_unlock(&gVnodeMutex);
if (vnode)
kfree(vnode);
return err;
}
static inline void
put_vnode(struct vnode *vnode)
{
dec_vnode_ref_count(vnode, false);
}
static status_t
entry_ref_to_vnode(mount_id mountID, vnode_id directoryID, const char *name, struct vnode **_vnode)
{
struct vnode *directory, *vnode;
vnode_id id;
int status;
int type;
status = get_vnode(mountID, directoryID, &directory, false);
if (status < 0)
return status;
status = FS_CALL(directory, lookup)(directory->mount->cookie,
directory->private_node, name, &id, &type);
put_vnode(directory);
if (status < 0)
return status;
mutex_lock(&gVnodeMutex);
vnode = lookup_vnode(mountID, id);
mutex_unlock(&gVnodeMutex);
if (vnode == NULL) {
// fs_lookup() should have left the vnode referenced, so chances
// are good that this will never happen
panic("entry_ref_to_vnode: could not lookup vnode (mountid 0x%lx vnid 0x%Lx)\n", mountID, id);
return B_ENTRY_NOT_FOUND;
}
*_vnode = vnode;
return B_OK;
}
static status_t
vnode_path_to_vnode(struct vnode *vnode, char *path, bool traverseLeafLink, struct vnode **_vnode, int count)
{
status_t status = 0;
FUNCTION(("vnode_path_to_vnode(path = %s)\n", path));
if (!path)
return EINVAL;
while (true) {
struct vnode *nextVnode;
vnode_id vnodeID;
char *nextPath;
int type;
PRINT(("path_to_vnode: top of loop. p = %p, *p = %c, p = '%s'\n", path, *path, path));
// done?
if (*path == '\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
if (strcmp("..", path) == 0 && vnode->mount->root_vnode == vnode) {
// move to the covered vnode so we pass the '..' parse to the underlying filesystem
if (vnode->mount->covers_vnode) {
nextVnode = vnode->mount->covers_vnode;
inc_vnode_ref_count(nextVnode);
dec_vnode_ref_count(vnode, false);
vnode = nextVnode;
}
}
// tell the filesystem to get the vnode of this path component
status = FS_CALL(vnode, lookup)(vnode->mount->cookie, vnode->private_node, path, &vnodeID, &type);
if (status < 0) {
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(&gVnodeMutex);
nextVnode = lookup_vnode(vnode->mount_id, vnodeID);
mutex_unlock(&gVnodeMutex);
if (!nextVnode) {
// pretty screwed up here
panic("path_to_vnode: could not lookup vnode (mountid 0x%lx vnid 0x%Lx)\n", vnode->mount_id, vnodeID);
put_vnode(vnode);
return ERR_VFS_PATH_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;
// 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 = kmalloc(SYS_MAX_PATH_LEN);
if (buffer == NULL) {
status = B_NO_MEMORY;
goto resolve_link_error;
}
status = FS_CALL(nextVnode, read_link)(nextVnode->mount->cookie, nextVnode->private_node, buffer, SYS_MAX_PATH_LEN);
if (status < B_OK) {
kfree(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 = gRoot;
inc_vnode_ref_count(vnode);
}
status = vnode_path_to_vnode(vnode, path, traverseLeafLink, &nextVnode, count + 1);
kfree(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
if (vnode->covered_by) {
nextVnode = vnode->covered_by;
inc_vnode_ref_count(nextVnode);
put_vnode(vnode);
vnode = nextVnode;
}
}
*_vnode = vnode;
return B_OK;
}
static int
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 EINVAL;
// figure out if we need to start at root or at cwd
if (*path == '/') {
while (*++path == '/')
;
start = gRoot;
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, _vnode, 0);
}
/** 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 int
path_to_dir_vnode(char *path, struct vnode **_vnode, char *filename, bool kernel)
{
char *p = strrchr(path, '/');
FUNCTION(("path_to_dir_vnode(path = %s)\n", path));
if (!p) {
// this path is single segment with no '/' in it
// ex. "foo"
strcpy(filename, path);
strcpy(path, ".");
} else {
// replace the filename portion of the path with a '.'
strcpy(filename, ++p);
if (p[0] != '\0'){
p[0] = '.';
p[1] = '\0';
}
}
return path_to_vnode(path, true, _vnode, kernel);
}
/** 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)
{
/* this implementation is currently bound to SYS_MAX_PATH_LEN */
char path[SYS_MAX_PATH_LEN];
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);
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(&gVnodeMutex);
parentVnode = lookup_vnode(vnode->mount_id, parentID);
mutex_unlock(&gVnodeMutex);
if (parentVnode == NULL) {
panic("dir_vnode_to_path: could not lookup vnode (mountid 0x%lx vnid 0x%Lx)\n", vnode->mount_id, parentID);
status = B_ENTRY_NOT_FOUND;
goto out;
}
// 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 (parentID == id) {
// we have reached the root level directory of this file system
// which means we have constructed the full path
break;
}
if (!FS_CALL(vnode, get_vnode_name)) {
// If we don'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 = FS_CALL(vnode, read_dir)(vnode->mount->cookie, vnode->private_node,
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] = '/';
}
// add the mountpoint
length = strlen(vnode->mount->mount_point);
if (bufferSize - (sizeof(path) - insert) < length + 1) {
status = ENOBUFS;
goto out;
}
memcpy(buffer, vnode->mount->mount_point, length);
if (insert != sizeof(path))
memcpy(buffer + length, path + insert, sizeof(path) - insert);
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 > SYS_MAX_PATH_LEN - 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;
}
*_vnode = descriptor->u.vnode;
return descriptor;
}
static struct vnode *
get_vnode_from_fd(struct io_context *ioContext, int fd)
{
struct file_descriptor *descriptor;
struct vnode *vnode;
descriptor = get_fd(ioContext, 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)
{
struct vnode *vnode;
if (fd != -1) {
struct file_descriptor *descriptor = get_fd_and_vnode(fd, &vnode, kernel);
if (descriptor == NULL)
return B_FILE_ERROR;
inc_vnode_ref_count(vnode);
put_fd(descriptor);
*_vnode = vnode;
return B_OK;
}
return path_to_vnode(path, traverseLeafLink, _vnode, kernel);
}
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 = &gFileOps;
break;
case FDTYPE_DIR:
descriptor->ops = &gDirectoryOps;
break;
case FDTYPE_ATTR:
descriptor->ops = &gAttributeOps;
break;
case FDTYPE_ATTR_DIR:
descriptor->ops = &gAttributeDirectoryOps;
break;
case FDTYPE_INDEX_DIR:
descriptor->ops = &gIndexDirectoryOps;
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) {
kfree(descriptor);
return B_NO_MORE_FDS;
}
return fd;
}
// #pragma mark -
// Functions the VFS exports for other parts of the kernel
int
vfs_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;
}
int
vfs_put_vnode(mount_id mountID, vnode_id vnodeID)
{
struct vnode *vnode;
mutex_lock(&gVnodeMutex);
vnode = lookup_vnode(mountID, vnodeID);
mutex_unlock(&gVnodeMutex);
if (vnode)
dec_vnode_ref_count(vnode, true);
return B_OK;
}
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);
}
int
vfs_remove_vnode(mount_id mountID, vnode_id vnodeID)
{
struct vnode *vnode;
mutex_lock(&gVnodeMutex);
vnode = lookup_vnode(mountID, vnodeID);
if (vnode)
vnode->delete_me = true;
mutex_unlock(&gVnodeMutex);
return 0;
}
void *
vfs_get_cache_ptr(void *vnode)
{
return ((struct vnode *)vnode)->cache;
}
int
vfs_set_cache_ptr(void *vnode, void *cache)
{
if (test_and_set((int32 *)&(((struct vnode *)vnode)->cache), (int32)cache, 0) == 0)
return 0;
return -1;
}
int
vfs_get_vnode_from_fd(int fd, bool kernel, void **vnode)
{
struct io_context *ioctx;
ioctx = get_current_io_context(kernel);
*vnode = get_vnode_from_fd(ioctx, fd);
if (*vnode == NULL)
return ERR_INVALID_HANDLE;
return B_NO_ERROR;
}
int
vfs_get_vnode_from_path(const char *path, bool kernel, void **_vnode)
{
struct vnode *vnode;
int err;
char buf[SYS_MAX_PATH_LEN+1];
PRINT(("vfs_get_vnode_from_path: entry. path = '%s', kernel %d\n", path, kernel));
strncpy(buf, path, SYS_MAX_PATH_LEN);
buf[SYS_MAX_PATH_LEN] = 0;
err = path_to_vnode(buf, true, &vnode, kernel);
if (err >= 0)
*_vnode = vnode;
return err;
}
int
vfs_put_vnode_ptr(void *vnode)
{
struct vnode *v = vnode;
put_vnode(v);
return 0;
}
ssize_t
vfs_can_page(void *_v)
{
struct vnode *vnode = _v;
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);
return 0;
}
ssize_t
vfs_read_page(void *_v, iovecs *vecs, off_t pos)
{
struct vnode *vnode = _v;
FUNCTION(("vfs_readpage: vnode %p, vecs %p, pos %Ld\n", vnode, vecs, pos));
return FS_CALL(vnode, read_pages)(vnode->mount->cookie, vnode->private_node, vecs, pos);
}
ssize_t
vfs_write_page(void *_v, iovecs *vecs, off_t pos)
{
struct vnode *vnode = _v;
FUNCTION(("vfs_writepage: vnode %p, vecs %p, pos %Ld\n", vnode, vecs, pos));
return FS_CALL(vnode, write_pages)(vnode->mount->cookie, vnode->private_node, vecs, pos);
}
/** 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 table_size;
struct io_context *context;
struct io_context *parentContext;
context = kmalloc(sizeof(struct io_context));
if (context == NULL)
return NULL;
memset(context, 0, sizeof(struct io_context));
parentContext = (struct io_context *)_parentContext;
if (parentContext)
table_size = parentContext->table_size;
else
table_size = DEFAULT_FD_TABLE_SIZE;
context->fds = kmalloc(sizeof(struct file_descriptor *) * table_size);
if (context->fds == NULL) {
kfree(context);
return NULL;
}
memset(context->fds, 0, sizeof(struct file_descriptor *) * table_size);
if (mutex_init(&context->io_mutex, "I/O context") < 0) {
kfree(context->fds);
kfree(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 < table_size; 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 = gRoot;
if (context->cwd)
inc_vnode_ref_count(context->cwd);
}
context->table_size = table_size;
return context;
}
int
vfs_free_io_context(void *_ioContext)
{
struct io_context *context = (struct io_context *)_ioContext;
int 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);
kfree(context->fds);
kfree(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 (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 = kmalloc(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 = kmalloc(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));
}
kfree(context->fds);
context->fds = 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;
}
}
#ifdef DEBUG
int
vfs_test(void)
{
int fd;
int err;
dprintf("vfs_test() entry\n");
fd = sys_open_dir("/");
dprintf("fd = %d\n", fd);
sys_close(fd);
fd = sys_open_dir("/");
dprintf("fd = %d\n", fd);
sys_create_dir("/foo", 0755);
sys_create_dir("/foo/bar", 0755);
sys_create_dir("/foo/bar/gar", 0755);
sys_create_dir("/foo/bar/tar", 0755);
#if 1
fd = sys_open_dir("/foo/bar");
if (fd < 0)
panic("unable to open /foo/bar\n");
{
char buf[64];
ssize_t len;
sys_seek(fd, 0, SEEK_SET);
for (;;) {
len = sys_read(fd, buf, -1, sizeof(buf));
if (len > 0)
dprintf("readdir returned name = '%s'\n", buf);
else {
dprintf("readdir returned %s\n", strerror(len));
break;
}
}
}
// do some unlink tests
err = sys_unlink("/foo/bar");
if (err == B_NO_ERROR)
panic("unlink of full directory should not have passed\n");
sys_unlink("/foo/bar/gar");
sys_unlink("/foo/bar/tar");
err = sys_unlink("/foo/bar");
if (err != B_NO_ERROR)
panic("unlink of empty directory should have worked\n");
sys_create_dir("/test", 0755);
sys_create_dir("/test", 0755);
err = sys_mount("/test", NULL, "rootfs", NULL);
if (err < 0)
panic("failed mount test\n");
#endif
#if 1
fd = sys_open_dir("/boot");
sys_close(fd);
fd = sys_open_dir("/boot");
if (fd < 0)
panic("unable to open dir /boot\n");
{
char buf[256];
struct dirent *dirent = (struct dirent *)buf;
ssize_t len;
sys_rewind_dir(fd);
for (;;) {
len = sys_read_dir(fd, dirent, sizeof(buf), 1);
// if(len < 0)
// panic("readdir returned %Ld\n", (long long)len);
if (len > 0)
dprintf("sys_read returned name = '%s'\n", dirent->d_name);
else {
dprintf("sys_read returned %s\n", strerror(len));
break;
}
}
}
sys_close(fd);
fd = sys_open("/boot/kernel", O_RDONLY);
if (fd < 0)
panic("unable to open kernel file '/boot/kernel'\n");
{
char buf[64];
ssize_t len;
len = sys_read(fd, buf, 0, sizeof(buf));
if (len < 0)
panic("failed on read\n");
dprintf("read returned %Ld\n", (long long)len);
}
sys_close(fd);
{
struct stat stat;
err = sys_read_stat("/boot/kernel", &stat);
if (err < 0)
panic("err stating '/boot/kernel'\n");
dprintf("stat results:\n");
dprintf("\tvnid 0x%Lx\n\ttype %d\n\tsize 0x%Lx\n", stat.st_ino, stat.st_mode, stat.st_size);
}
#endif
dprintf("vfs_test() done\n");
return 0;
}
#endif
int
vfs_bootstrap_all_filesystems(void)
{
int err;
int fd;
// bootstrap the root filesystem
bootstrap_rootfs();
err = sys_mount("/", NULL, "rootfs", NULL);
if (err < 0)
panic("error mounting rootfs!\n");
sys_setcwd(-1, "/");
// bootstrap the bootfs
bootstrap_bootfs();
sys_create_dir("/boot",0755);
err = sys_mount("/boot", NULL, "bootfs", NULL);
if (err < 0)
panic("error mounting bootfs\n");
// bootstrap the devfs
bootstrap_devfs();
sys_create_dir("/dev",0755);
err = sys_mount("/dev", NULL, "devfs", NULL);
if (err < 0)
panic("error mounting devfs\n");
return B_NO_ERROR;
}
int
vfs_register_filesystem(const char *name, struct fs_ops *ops)
{
status_t status = B_OK;
file_system *fs;
if (name == NULL || *name == '\0' || ops == NULL)
return B_BAD_VALUE;
mutex_lock(&gFileSystemsMutex);
fs = new_file_system(name, ops);
if (fs == NULL)
status = B_NO_MEMORY;
mutex_unlock(&gFileSystemsMutex);
return status;
}
int
vfs_init(kernel_args *ka)
{
{
struct vnode *v;
gVnodeTable = hash_init(VNODE_HASH_TABLE_SIZE, (addr)&v->next - (addr)v,
&vnode_compare, &vnode_hash);
if (gVnodeTable == NULL)
panic("vfs_init: error creating vnode hash table\n");
}
{
struct fs_mount *mount;
gMountsTable = hash_init(MOUNTS_HASH_TABLE_SIZE, (addr)&mount->next - (addr)mount,
&mount_compare, &mount_hash);
if (gMountsTable == NULL)
panic("vfs_init: error creating mounts hash table\n");
}
gFileSystems = NULL;
gRoot = NULL;
if (mutex_init(&gFileSystemsMutex, "vfs_lock") < 0)
panic("vfs_init: error allocating file systems lock\n");
if (mutex_init(&gMountOpMutex, "vfs_mount_op_lock") < 0)
panic("vfs_init: error allocating mount op lock\n");
if (mutex_init(&gMountMutex, "vfs_mount_lock") < 0)
panic("vfs_init: error allocating mount lock\n");
if (mutex_init(&gVnodeMutex, "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(&gVnodeMutex);
vnode = lookup_vnode(directory->mount_id, newID);
mutex_unlock(&gVnodeMutex);
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;
err:
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)
{
struct file_descriptor *descriptor;
fs_cookie cookie;
int status;
status = FS_CALL(vnode, open_dir)(vnode->mount->cookie, vnode->private_node, &cookie);
if (status < 0)
return status;
// file is opened, create a fd
status = get_new_fd(FDTYPE_DIR, vnode, cookie, 0, kernel);
if (status >= 0)
return status;
err:
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)
{
struct file_descriptor *descriptor;
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;
err:
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,*vnode;
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, *vnode;
fs_cookie cookie;
vnode_id newID;
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(char *path, int omode, bool kernel)
{
struct file_descriptor *descriptor;
struct vnode *vnode = NULL;
int status;
int fd;
FUNCTION(("file_open: entry. path = '%s', omode %d, kernel %d\n", path, omode, kernel));
// get the vnode matching the path
status = path_to_vnode(path, (omode & O_NOTRAVERSE) == 0, &vnode, kernel);
if (status < B_OK)
return status;
status = open_vnode(vnode, omode, kernel);
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 ssize_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 ssize_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;
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 int
dir_create_entry_ref(mount_id mountID, vnode_id parentID, const char *name, int perms, bool kernel)
{
struct vnode *vnode;
vnode_id newID;
int 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 int
dir_create(char *path, int perms, bool kernel)
{
char filename[SYS_MAX_NAME_LEN];
struct vnode *vnode;
vnode_id newID;
int status;
FUNCTION(("dir_create: path '%s', perms %d, kernel %d\n", path, perms, kernel));
status = path_to_dir_vnode(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_node_ref(mount_id mountID, vnode_id directoryID, bool kernel)
{
struct vnode *vnode;
int status;
FUNCTION(("dir_open_entry_ref()\n"));
// get the vnode matching the node_ref
status = get_vnode(mountID, directoryID, &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_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 == NULL || *name == '\0')
return B_BAD_VALUE;
// get the vnode matching the entry_ref
status = entry_ref_to_vnode(mountID, parentID, name, &vnode);
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(char *path, bool kernel)
{
struct vnode *vnode;
int status;
FUNCTION(("dir_open: path = '%s', kernel %d\n", path, kernel));
status = path_to_vnode(path, true, &vnode, kernel);
if (status < 0)
return status;
status = open_dir_vnode(vnode,kernel);
if (status < 0)
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)
{
struct vnode *vnode = descriptor->u.vnode;
if (FS_CALL(vnode, read_dir))
return FS_CALL(vnode, read_dir)(vnode->mount->cookie,vnode->private_node,descriptor->cookie,buffer,bufferSize,_count);
return EOPNOTSUPP;
}
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_sync(int fd, bool kernel)
{
struct file_descriptor *descriptor;
struct vnode *vnode;
int status;
FUNCTION(("vfs_fsync: entry. fd %d kernel %d\n", fd, kernel));
descriptor = get_fd_and_vnode(fd, &vnode, kernel);
if (descriptor == NULL)
return ERR_INVALID_HANDLE;
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 int
common_read_link(char *path, char *buffer, size_t bufferSize, 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, 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(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(path = %s, toPath = %s, mode = %d, kernel = %d)\n", path, toPath, mode, kernel));
status = path_to_dir_vnode(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(char *path, bool kernel)
{
char filename[SYS_MAX_NAME_LEN];
struct vnode *vnode;
int status;
FUNCTION(("common_unlink: path '%s', kernel %d\n", path, kernel));
status = path_to_dir_vnode(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(char *path, 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(path = %s, newPath = %s, kernel = %d)\n", path, newPath, kernel));
status = path_to_dir_vnode(path, &fromVnode, fromName, kernel);
if (status < 0)
return status;
status = path_to_dir_vnode(newPath, &toVnode, toName, kernel);
if (status < 0)
goto err;
if (fromVnode->mount_id != toVnode->mount_id) {
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 0x%p\n", stat));
return FS_CALL(vnode, read_stat)(vnode->mount->cookie, vnode->private_node, stat);
}
static status_t
common_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: path '%s', stat %p, stat_mask %d, kernel %d\n", 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(get_current_io_context(kernel), fd);
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;
err1:
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(get_current_io_context(kernel), fd);
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;
err1:
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 ssize_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 ssize_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(int fd, const struct stat *stat, int statMask, bool kernel)
{
struct file_descriptor *descriptor;
struct vnode *vnode;
int status;
FUNCTION(("attr_write_stat: fd = %d, stat = %p, statMask %d, kernel %d\n", fd, stat, statMask, kernel));
descriptor = get_fd_and_vnode(fd, &vnode, kernel);
if (descriptor == NULL)
return B_FILE_ERROR;
if (FS_CALL(vnode, write_attr_stat))
status = FS_CALL(vnode, write_attr_stat)(vnode->mount->cookie, vnode->private_node, descriptor->cookie, stat, statMask);
else
status = EROFS;
put_vnode(vnode);
return status;
}
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_vnode(vnode);
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->mount_id != toVnode->mount_id) {
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_vnode(toVnode);
err:
put_vnode(fromVnode);
return status;
}
static status_t
index_dir_open(mount_id mountID, bool kernel)
{
struct fs_mount *mount;
fs_cookie cookie;
int status;
FUNCTION(("index_dir_open(mountID = %ld, kernel = %d)\n", mountID, kernel));
mutex_lock(&gMountMutex);
mount = find_mount(mountID);
if (mount == NULL)
goto err;
// ToDo: lock the mount in some way - i.e. increment the root node's ref counter
mutex_unlock(&gMountMutex);
if (FS_MOUNT_CALL(mount, open_index_dir) == NULL)
return EOPNOTSUPP;
status = FS_MOUNT_CALL(mount, open_index_dir)(mount->cookie, &cookie);
if (status < B_OK)
return status;
// get fd for the index directory
status = get_new_fd(FDTYPE_INDEX_DIR, (void *)mount, cookie, 0, kernel);
if (status >= 0)
return status;
err1:
FS_MOUNT_CALL(mount, close_index_dir)(mount->cookie, cookie);
FS_MOUNT_CALL(mount, free_index_dir_cookie)(mount->cookie, cookie);
return status;
err:
mutex_unlock(&gMountMutex);
return B_ENTRY_NOT_FOUND;
}
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;
}
// #pragma mark -
// General File System functions
static status_t
fs_mount(char *path, const char *device, const char *fsName, void *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 device and args,
// though.
if (fsName == NULL || fsName[0] == '\0')
return B_BAD_VALUE;
mutex_lock(&gMountOpMutex);
mount = (struct fs_mount *)kmalloc(sizeof(struct fs_mount));
if (mount == NULL) {
err = B_NO_MEMORY;
goto err;
}
mount->vnodes_head = mount->vnodes_tail = NULL;
mount->mount_point = kstrdup(path);
if (mount->mount_point == NULL) {
err = B_NO_MEMORY;
goto err1;
}
mount->fs = get_file_system(fsName);
if (mount->fs == NULL) {
err = ERR_VFS_INVALID_FS;
goto err2;
}
recursive_lock_create(&mount->rlock);
mount->id = gNextMountID++;
mount->unmounting = false;
if (!gRoot) {
// we haven't mounted anything yet
if (strcmp(path, "/") != 0) {
err = ERR_VFS_GENERAL;
goto err3;
}
err = FS_MOUNT_CALL(mount, mount)(mount->id, device, NULL, &mount->cookie, &root_id);
if (err < 0) {
err = ERR_VFS_GENERAL;
goto err3;
}
mount->covers_vnode = NULL; // this is the root mount
} else {
err = path_to_vnode(path, true, &covered_vnode, kernel);
if (err < 0)
goto err2;
if (!covered_vnode) {
err = ERR_VFS_GENERAL;
goto err2;
}
// XXX insert check to make sure covered_vnode is a DIR, or maybe it's okay for it not to be
if (covered_vnode != gRoot
&& covered_vnode->mount->root_vnode == covered_vnode) {
err = ERR_VFS_ALREADY_MOUNTPOINT;
goto err2;
}
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 err4;
}
mutex_lock(&gMountMutex);
// insert mount struct into list
hash_insert(gMountsTable, mount);
mutex_unlock(&gMountMutex);
err = get_vnode(mount->id, root_id, &mount->root_vnode, 0);
if (err < 0)
goto err5;
// XXX may be a race here
if (mount->covers_vnode)
mount->covers_vnode->covered_by = mount->root_vnode;
if (!gRoot)
gRoot = mount->root_vnode;
mutex_unlock(&gMountOpMutex);
return 0;
err5:
FS_MOUNT_CALL(mount, unmount)(mount->cookie);
err4:
if (mount->covers_vnode)
put_vnode(mount->covers_vnode);
err3:
recursive_lock_destroy(&mount->rlock);
put_file_system(mount->fs);
err2:
kfree(mount->mount_point);
err1:
kfree(mount);
err:
mutex_unlock(&gMountOpMutex);
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;
mutex_lock(&gMountOpMutex);
mount = find_mount(vnode->mount_id);
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);
err = ERR_VFS_NOT_MOUNTPOINT;
goto err;
}
/* grab the vnode master mutex to keep someone from creating a vnode
while we're figuring out if we can continue */
mutex_lock(&gVnodeMutex);
/* 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
for (vnode = mount->vnodes_head; vnode != NULL; vnode = vnode->mount_next) {
if (vnode->busy || vnode->ref_count != 0) {
mount->root_vnode->ref_count += 2;
mutex_unlock(&gVnodeMutex);
put_vnode(mount->root_vnode);
err = EBUSY;
goto err;
}
}
/* we can safely continue, mark all of the vnodes busy and this mount
structure in unmounting state */
for (vnode = mount->vnodes_head; vnode; vnode = vnode->mount_next) {
if (vnode != mount->root_vnode)
vnode->busy = true;
}
mount->unmounting = true;
mutex_unlock(&gVnodeMutex);
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(&gMountMutex);
hash_remove(gMountsTable, mount);
mutex_unlock(&gMountMutex);
mutex_unlock(&gMountOpMutex);
FS_MOUNT_CALL(mount, unmount)(mount->cookie);
// release the file system
put_file_system(mount->fs);
kfree(mount->mount_point);
kfree(mount);
return 0;
err:
mutex_unlock(&gMountOpMutex);
return err;
}
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 */
mutex_lock(&gMountOpMutex);
mutex_lock(&gMountMutex);
hash_open(gMountsTable, &iter);
while ((mount = hash_next(gMountsTable, &iter))) {
if (FS_MOUNT_CALL(mount, sync))
FS_MOUNT_CALL(mount, sync)(mount->cookie);
}
hash_close(gMountsTable, &iter, false);
mutex_unlock(&gMountMutex);
mutex_unlock(&gMountOpMutex);
return 0;
}
static status_t
fs_read_info(dev_t device, struct fs_info *info)
{
struct fs_mount *mount;
int status;
mutex_lock(&gMountMutex);
mount = find_mount(device);
if (mount == NULL) {
status = EINVAL;
goto error;
}
if (FS_MOUNT_CALL(mount, read_fs_info))
status = FS_MOUNT_CALL(mount, read_fs_info)(mount->cookie, info);
else
status = EOPNOTSUPP;
// fill in other info the file system doesn't (have to) know about
info->dev = mount->id;
info->root = mount->root_vnode->id;
error:
mutex_unlock(&gMountMutex);
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(&gMountMutex);
mount = find_mount(device);
if (mount == NULL) {
status = EINVAL;
goto error;
}
if (FS_MOUNT_CALL(mount, write_fs_info))
status = FS_MOUNT_CALL(mount, write_fs_info)(mount->cookie, info, mask);
else
status = EROFS;
error:
mutex_unlock(&gMountMutex);
return status;
}
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 = ERR_VFS_WRONG_STREAM_TYPE;
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
int
sys_mount(const char *path, const char *device, const char *fs_name, void *args)
{
char pathBuffer[SYS_MAX_PATH_LEN + 1];
strlcpy(pathBuffer, path, SYS_MAX_PATH_LEN - 1);
return fs_mount(pathBuffer, device, fs_name, args, true);
}
int
sys_unmount(const char *path)
{
char pathBuffer[SYS_MAX_PATH_LEN + 1];
strlcpy(pathBuffer, path, SYS_MAX_PATH_LEN - 1);
return fs_unmount(pathBuffer, true);
}
int
sys_sync(void)
{
return fs_sync();
}
int
sys_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) - 1);
return file_open_entry_ref(device, inode, nameCopy, omode, true);
}
int
sys_open(const char *path, int omode)
{
char pathBuffer[SYS_MAX_PATH_LEN + 1];
strlcpy(pathBuffer, path, SYS_MAX_PATH_LEN - 1);
return file_open(pathBuffer, omode, true);
}
int
sys_open_dir_node_ref(dev_t device, ino_t inode)
{
return dir_open_node_ref(device, inode, true);
}
int
sys_open_dir_entry_ref(dev_t device, ino_t inode, const char *name)
{
return dir_open_entry_ref(device, inode, name, true);
}
int
sys_open_dir(const char *path)
{
char pathBuffer[SYS_MAX_PATH_LEN + 1];
strlcpy(pathBuffer, path, SYS_MAX_PATH_LEN - 1);
return dir_open(pathBuffer, true);
}
int
sys_fsync(int fd)
{
return common_sync(fd, true);
}
int
sys_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
sys_create(const char *path, int omode, int perms)
{
char buffer[SYS_MAX_PATH_LEN + 1];
strlcpy(buffer, path, SYS_MAX_PATH_LEN - 1);
return file_create(buffer, omode, perms, true);
}
int
sys_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);
}
int
sys_create_dir(const char *path, int perms)
{
char pathBuffer[SYS_MAX_PATH_LEN + 1];
strlcpy(pathBuffer, path, SYS_MAX_PATH_LEN - 1);
return dir_create(pathBuffer, perms, true);
}
int
sys_remove_dir(const char *path)
{
char pathBuffer[SYS_MAX_PATH_LEN + 1];
strlcpy(pathBuffer, path, SYS_MAX_PATH_LEN - 1);
return dir_remove(pathBuffer, true);
}
int
sys_read_link(const char *path, char *buffer, size_t bufferSize)
{
char pathBuffer[SYS_MAX_PATH_LEN + 1];
strlcpy(pathBuffer, path, SYS_MAX_PATH_LEN - 1);
return common_read_link(pathBuffer, buffer, bufferSize, true);
}
int
sys_write_link(const char *path, const char *toPath)
{
char pathBuffer[SYS_MAX_PATH_LEN + 1];
char toPathBuffer[SYS_MAX_PATH_LEN + 1];
int status;
strlcpy(pathBuffer, path, SYS_MAX_PATH_LEN - 1);
strlcpy(toPathBuffer, toPath, SYS_MAX_PATH_LEN - 1);
status = check_path(toPathBuffer);
if (status < B_OK)
return status;
return common_write_link(pathBuffer, toPathBuffer, true);
}
int
sys_create_symlink(const char *path, const char *toPath, int mode)
{
char pathBuffer[SYS_MAX_PATH_LEN + 1];
char toPathBuffer[SYS_MAX_PATH_LEN + 1];
int status;
strlcpy(pathBuffer, path, SYS_MAX_PATH_LEN - 1);
strlcpy(toPathBuffer, toPath, SYS_MAX_PATH_LEN - 1);
status = check_path(toPathBuffer);
if (status < B_OK)
return status;
return common_create_symlink(pathBuffer, toPathBuffer, mode, true);
}
int
sys_create_link(const char *path, const char *toPath)
{
char pathBuffer[SYS_MAX_PATH_LEN + 1];
char toPathBuffer[SYS_MAX_PATH_LEN + 1];
strlcpy(pathBuffer, path, SYS_MAX_PATH_LEN - 1);
strlcpy(toPathBuffer, toPath, SYS_MAX_PATH_LEN - 1);
return common_create_link(pathBuffer, toPathBuffer, true);
}
int
sys_unlink(const char *path)
{
char pathBuffer[SYS_MAX_PATH_LEN + 1];
strlcpy(pathBuffer, path, SYS_MAX_PATH_LEN - 1);
return common_unlink(pathBuffer, true);
}
int
sys_rename(const char *oldPath, const char *newPath)
{
char oldPathBuffer[SYS_MAX_PATH_LEN + 1];
char newPathBuffer[SYS_MAX_PATH_LEN + 1];
strlcpy(oldPathBuffer, oldPath, SYS_MAX_PATH_LEN - 1);
strlcpy(newPathBuffer, newPath, SYS_MAX_PATH_LEN - 1);
return common_rename(oldPathBuffer, newPathBuffer, true);
}
int
sys_access(const char *path, int mode)
{
char pathBuffer[SYS_MAX_PATH_LEN + 1];
strlcpy(pathBuffer, path, SYS_MAX_PATH_LEN - 1);
return common_access(pathBuffer, mode, true);
}
int
sys_read_stat(const char *path, bool traverseLeafLink, struct stat *stat)
{
char pathBuffer[SYS_MAX_PATH_LEN + 1];
struct vnode *vnode;
int status;
strlcpy(pathBuffer, path, SYS_MAX_PATH_LEN - 1);
FUNCTION(("sys_read_stat: path '%s', stat %p,\n", path, stat));
status = path_to_vnode(pathBuffer, traverseLeafLink, &vnode, true);
if (status < 0)
return status;
status = FS_CALL(vnode, read_stat)(vnode->mount->cookie, vnode->private_node, stat);
put_vnode(vnode);
return status;
}
int
sys_write_stat(int fd, const char *path, bool traverseLeafLink, struct stat *stat, int statMask)
{
char pathBuffer[SYS_MAX_PATH_LEN + 1];
if (fd == -1)
strlcpy(pathBuffer, path, SYS_MAX_PATH_LEN - 1);
return common_write_stat(fd, pathBuffer, traverseLeafLink, stat, statMask, true);
}
int
sys_open_attr_dir(int fd, const char *path)
{
char pathBuffer[SYS_MAX_PATH_LEN + 1];
if (fd == -1)
strlcpy(pathBuffer, path, SYS_MAX_PATH_LEN - 1);
return attr_dir_open(fd, pathBuffer, true);
}
int
sys_create_attr(int fd, const char *name, uint32 type, int openMode)
{
return attr_create(fd, name, type, openMode, true);
}
int
sys_open_attr(int fd, const char *name, int openMode)
{
return attr_open(fd, name, openMode, true);
}
int
sys_write_attr_stat(int fd, const struct stat *stat, int statMask)
{
return attr_write_stat(fd, stat, statMask, true);
}
int
sys_remove_attr(int fd, const char *name)
{
return attr_remove(fd, name, true);
}
int
sys_rename_attr(int fromFile, const char *fromName, int toFile, const char *toName)
{
return attr_rename(fromFile, fromName, toFile, toName, true);
}
int
sys_getcwd(char *buffer, size_t size)
{
char path[SYS_MAX_PATH_LEN];
int status;
PRINT(("sys_getcwd: buf %p, %ld\n", buffer, size));
// Call vfs to get current working directory
status = get_cwd(path, SYS_MAX_PATH_LEN - 1, true);
if (status < 0)
return status;
path[SYS_MAX_PATH_LEN - 1] = '\0';
strlcpy(buffer, path, size);
return status;
}
int
sys_setcwd(int fd, const char *path)
{
char pathBuffer[SYS_MAX_PATH_LEN + 1];
if (fd == -1)
strlcpy(pathBuffer, path, SYS_MAX_PATH_LEN - 1);
return set_cwd(fd, pathBuffer, true);
}
// #pragma mark -
// Calls from userland (with extra address checks)
int
user_mount(const char *upath, const char *udevice, const char *ufs_name, void *args)
{
char path[SYS_MAX_PATH_LEN + 1];
char fs_name[SYS_MAX_OS_NAME_LEN + 1];
char device[SYS_MAX_PATH_LEN + 1];
int rc;
if (!CHECK_USER_ADDRESS(upath)
|| !CHECK_USER_ADDRESS(ufs_name)
|| !CHECK_USER_ADDRESS(udevice))
return B_BAD_ADDRESS;
rc = user_strlcpy(path, upath, SYS_MAX_PATH_LEN);
if (rc < 0)
return rc;
rc = user_strlcpy(fs_name, ufs_name, SYS_MAX_OS_NAME_LEN);
if (rc < 0)
return rc;
if (udevice) {
rc = user_strlcpy(device, udevice, SYS_MAX_PATH_LEN);
if (rc < 0)
return rc;
} else
device[0] = '\0';
return fs_mount(path, device, fs_name, args, false);
}
int
user_unmount(const char *userPath)
{
char path[SYS_MAX_PATH_LEN + 1];
int status;
status = user_strlcpy(path, userPath, SYS_MAX_PATH_LEN);
if (status < 0)
return status;
return fs_unmount(path, false);
}
int
user_sync(void)
{
return fs_sync();
}
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 (!CHECK_USER_ADDRESS(userName))
return ERR_VM_BAD_USER_MEMORY;
status = user_strlcpy(name, userName, sizeof(name) - 1);
if (status < B_OK)
return status;
return file_open_entry_ref(device, inode, name, omode, false);
}
int
user_open(const char *userPath, int omode)
{
char path[SYS_MAX_PATH_LEN + 1];
int status;
if (!CHECK_USER_ADDRESS(userPath))
return ERR_VM_BAD_USER_MEMORY;
status = user_strlcpy(path, userPath, SYS_MAX_PATH_LEN);
if (status < 0)
return status;
return file_open(path, omode, false);
}
int
user_open_dir_node_ref(dev_t device, ino_t inode)
{
return dir_open_node_ref(device, inode, false);
}
int
user_open_dir_entry_ref(dev_t device, ino_t inode, const char *uname)
{
char name[B_FILE_NAME_LENGTH];
int status;
if (!CHECK_USER_ADDRESS(uname))
return B_BAD_ADDRESS;
status = user_strlcpy(name, uname, sizeof(name));
if (status < B_OK)
return status;
return dir_open_entry_ref(device, inode, name, false);
}
int
user_open_dir(const char *userPath)
{
char path[SYS_MAX_PATH_LEN + 1];
int status;
if (!CHECK_USER_ADDRESS(userPath))
return B_BAD_ADDRESS;
status = user_strlcpy(path, userPath, SYS_MAX_PATH_LEN);
if (status < 0)
return status;
return dir_open(path, false);
}
int
user_fsync(int fd)
{
return common_sync(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 (!CHECK_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[SYS_MAX_PATH_LEN + 1];
int status;
if (!CHECK_USER_ADDRESS(userPath))
return B_BAD_ADDRESS;
status = user_strlcpy(path, userPath, SYS_MAX_PATH_LEN);
if (status < 0)
return status;
return file_create(path, openMode, perms, false);
}
int
user_create_dir_entry_ref(dev_t device, ino_t inode, const char *userName, int perms)
{
char name[B_FILE_NAME_LENGTH];
int status;
if (!CHECK_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);
}
int
user_create_dir(const char *userPath, int perms)
{
char path[SYS_MAX_PATH_LEN + 1];
int status;
if (!CHECK_USER_ADDRESS(userPath))
return B_BAD_ADDRESS;
status = user_strlcpy(path, userPath, SYS_MAX_PATH_LEN);
if (status < 0)
return status;
return dir_create(path, perms, false);
}
int
user_remove_dir(const char *userPath)
{
char path[SYS_MAX_PATH_LEN + 1];
int status;
if (!CHECK_USER_ADDRESS(userPath))
return B_BAD_ADDRESS;
status = user_strlcpy(path, userPath, SYS_MAX_PATH_LEN);
if (status < 0)
return status;
return dir_remove(path, false);
}
int
user_read_link(const char *userPath, char *userBuffer, size_t bufferSize)
{
char path[SYS_MAX_PATH_LEN + 1];
char buffer[SYS_MAX_PATH_LEN + 1];
int status;
if (!CHECK_USER_ADDRESS(userPath)
|| !CHECK_USER_ADDRESS(userBuffer))
return B_BAD_ADDRESS;
status = user_strlcpy(path, userPath, SYS_MAX_PATH_LEN);
if (status < 0)
return status;
if (bufferSize > SYS_MAX_PATH_LEN)
bufferSize = SYS_MAX_PATH_LEN;
status = common_read_link(path, buffer, bufferSize, false);
if (status < B_OK)
return status;
// ToDo: think about buffer length and the return value at read_link()
return user_strlcpy(userBuffer, buffer, bufferSize);
}
int
user_write_link(const char *userPath, const char *userToPath)
{
char path[SYS_MAX_PATH_LEN + 1];
char toPath[SYS_MAX_PATH_LEN + 1];
int status;
if (!CHECK_USER_ADDRESS(userPath)
|| !CHECK_USER_ADDRESS(userToPath))
return B_BAD_ADDRESS;
status = user_strlcpy(path, userPath, SYS_MAX_PATH_LEN);
if (status < 0)
return status;
status = user_strlcpy(toPath, userToPath, SYS_MAX_PATH_LEN);
if (status < 0)
return status;
status = check_path(toPath);
if (status < B_OK)
return status;
return common_write_link(path, toPath, false);
}
int
user_create_symlink(const char *userPath, const char *userToPath, int mode)
{
char path[SYS_MAX_PATH_LEN + 1];
char toPath[SYS_MAX_PATH_LEN + 1];
int status;
if (!CHECK_USER_ADDRESS(userPath)
|| !CHECK_USER_ADDRESS(userToPath))
return B_BAD_ADDRESS;
status = user_strlcpy(path, userPath, SYS_MAX_PATH_LEN);
if (status < 0)
return status;
status = user_strlcpy(toPath, userToPath, SYS_MAX_PATH_LEN);
if (status < 0)
return status;
status = check_path(toPath);
if (status < B_OK)
return status;
return common_create_symlink(path, toPath, mode, false);
}
int
user_create_link(const char *userPath, const char *userToPath)
{
char path[SYS_MAX_PATH_LEN + 1];
char toPath[SYS_MAX_PATH_LEN + 1];
int status;
if (!CHECK_USER_ADDRESS(userPath)
|| !CHECK_USER_ADDRESS(userToPath))
return B_BAD_ADDRESS;
status = user_strlcpy(path, userPath, SYS_MAX_PATH_LEN);
if (status < 0)
return status;
status = user_strlcpy(toPath, userToPath, SYS_MAX_PATH_LEN);
if (status < 0)
return status;
status = check_path(toPath);
if (status < B_OK)
return status;
return common_create_link(path, toPath, false);
}
int
user_unlink(const char *userPath)
{
char path[SYS_MAX_PATH_LEN + 1];
int status;
if (!CHECK_USER_ADDRESS(userPath))
return B_BAD_ADDRESS;
status = user_strlcpy(path, userPath, SYS_MAX_PATH_LEN);
if (status < 0)
return status;
return common_unlink(path, false);
}
int
user_rename(const char *userOldPath, const char *userNewPath)
{
char oldPath[SYS_MAX_PATH_LEN + 1];
char newPath[SYS_MAX_PATH_LEN + 1];
int status;
if (!CHECK_USER_ADDRESS(userOldPath) || !CHECK_USER_ADDRESS(userNewPath))
return B_BAD_ADDRESS;
status = user_strlcpy(oldPath, userOldPath, SYS_MAX_PATH_LEN);
if (status < 0)
return status;
status = user_strlcpy(newPath, userNewPath, SYS_MAX_PATH_LEN);
if (status < 0)
return status;
return common_rename(oldPath, newPath, false);
}
int
user_access(const char *userPath, int mode)
{
char path[SYS_MAX_PATH_LEN + 1];
int status;
if (!CHECK_USER_ADDRESS(userPath))
return B_BAD_ADDRESS;
status = user_strlcpy(path, userPath, SYS_MAX_PATH_LEN);
if (status < 0)
return status;
return common_access(path, mode, false);
}
int
user_read_stat(const char *userPath, bool traverseLink, struct stat *userStat)
{
char path[SYS_MAX_PATH_LEN + 1];
struct vnode *vnode = NULL;
struct stat stat;
int rc;
if (!CHECK_USER_ADDRESS(userPath)
|| !CHECK_USER_ADDRESS(userStat))
return B_BAD_ADDRESS;
rc = user_strlcpy(path, userPath, SYS_MAX_PATH_LEN);
if (rc < 0)
return rc;
FUNCTION(("user_read_stat(path = %s, traverseLeafLink = %d)\n", path, traverseLink));
rc = path_to_vnode(path, traverseLink, &vnode, false);
if (rc < 0)
return rc;
if (FS_CALL(vnode, read_stat))
rc = FS_CALL(vnode, read_stat)(vnode->mount->cookie, vnode->private_node, &stat);
put_vnode(vnode);
if (rc < 0)
return rc;
return user_memcpy(userStat, &stat, sizeof(struct stat));
}
int
user_write_stat(int fd, const char *userPath, bool traverseLeafLink, struct stat *userStat, int statMask)
{
char path[SYS_MAX_PATH_LEN + 1];
struct stat stat;
if (!CHECK_USER_ADDRESS(userStat))
return B_BAD_ADDRESS;
if (fd == -1) {
if (!CHECK_USER_ADDRESS(userPath)
|| user_strlcpy(path, userPath, SYS_MAX_PATH_LEN) < B_OK)
return B_BAD_ADDRESS;
}
if (user_memcpy(&stat, userStat, sizeof(struct stat)) < B_OK)
return B_BAD_ADDRESS;
return common_write_stat(fd, path, traverseLeafLink, &stat, statMask, false);
}
int
user_open_attr_dir(int fd, const char *userPath)
{
char pathBuffer[SYS_MAX_PATH_LEN + 1];
if (fd == -1) {
if (!CHECK_USER_ADDRESS(userPath)
|| user_strlcpy(pathBuffer, userPath, SYS_MAX_PATH_LEN) < 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];
status_t status;
if (!CHECK_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 (!CHECK_USER_ADDRESS(userName)
|| user_strlcpy(name, userName, B_FILE_NAME_LENGTH) < B_OK)
return B_BAD_ADDRESS;
return attr_open(fd, name, openMode, false);
}
int
user_write_attr_stat(int fd, const struct stat *userStat, int statMask)
{
struct stat stat;
if (!CHECK_USER_ADDRESS(userStat)
|| user_memcpy(&stat, userStat, sizeof(struct stat)) < B_OK)
return B_BAD_ADDRESS;
return attr_write_stat(fd, &stat, statMask, false);
}
int
user_remove_attr(int fd, const char *userName)
{
char name[B_FILE_NAME_LENGTH];
if (!CHECK_USER_ADDRESS(userName)
|| user_strlcpy(name, userName, B_FILE_NAME_LENGTH) < B_OK)
return B_BAD_ADDRESS;
return attr_remove(fd, name, false);
}
int
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 (!CHECK_USER_ADDRESS(userFromName)
|| !CHECK_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_getcwd(char *userBuffer, size_t size)
{
char buffer[SYS_MAX_PATH_LEN];
int status;
PRINT(("user_getcwd: buf %p, %ld\n", userBuffer, size));
if (!CHECK_USER_ADDRESS(userBuffer))
return B_BAD_ADDRESS;
if (size > SYS_MAX_PATH_LEN)
size = SYS_MAX_PATH_LEN;
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;
}
int
user_setcwd(int fd, const char *userPath)
{
char path[SYS_MAX_PATH_LEN];
PRINT(("user_setcwd: path = %p\n", userPath));
if (fd == -1) {
if (!CHECK_USER_ADDRESS(userPath)
|| user_strlcpy(path, userPath, SYS_MAX_PATH_LEN) < B_OK)
return B_BAD_ADDRESS;
}
return set_cwd(fd, path, false);
}