sos-code-article10/sos/fs.c

2264 lines
58 KiB
C

/* Copyright (C) 2005,2006 David Decotigny
Copyright (C) 2000-2005 The KOS Team (Thomas Petazzoni, David
Decotigny, Julien Munier)
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
USA.
*/
#include <sos/assert.h>
#include <sos/list.h>
#include <sos/kmem_slab.h>
#include <sos/kmalloc.h>
#include <sos/syscall.h> /* For the FCNTL commands */
#include "chardev.h"
#include "fs.h"
/** List of available filesystems registered in the system */
static struct sos_fs_manager_type * fs_list = NULL;
/** Last UID delivered for the FS instances */
static sos_ui64_t last_fs_instance_uid;
/* **********************************************************
* Forward declarations
*/
static sos_ret_t fs_fetch_node(struct sos_fs_manager_instance *fs,
sos_ui64_t storage_location,
struct sos_fs_node ** result_fsnode);
static sos_ret_t
fs_allocate_node(struct sos_fs_manager_instance * fs,
sos_fs_node_type_t type,
sos_ui32_t flags,
const struct sos_process * creator,
sos_ui32_t access_rights,
struct sos_fs_node ** result_fsnode);
static sos_ret_t mark_dirty_fsnode(struct sos_fs_node * fsnode,
sos_bool_t force_sync);
static sos_ret_t sos_fs_sync_node(struct sos_fs_node * fsnode);
static sos_ret_t sos_fs_sync_fs(struct sos_fs_manager_instance * fs);
static sos_ret_t
fs_lookup_node(const struct sos_fs_pathname * path,
sos_bool_t follow_symlinks,
const struct sos_fs_nscache_node * root_nsnode,
const struct sos_fs_nscache_node * start_nsnode,
struct sos_fs_nscache_node ** result_nsnode,
struct sos_fs_pathname * result_remaining_path,
int lookup_recursion_level);
static sos_ret_t
fs_resolve_symlink(const struct sos_fs_nscache_node * root_nsnode,
const struct sos_fs_nscache_node * symlink_nsnode,
struct sos_fs_nscache_node ** target_nsnode,
int lookup_recursion_level);
static sos_ret_t
fs_register_child_node(const struct sos_process * creator,
struct sos_fs_nscache_node * parent_nsnode,
const struct sos_fs_pathname * name,
struct sos_fs_node * fsnode,
sos_ui32_t flags,
struct sos_fs_nscache_node ** result_nsnode);
static sos_ret_t
fs_create_child_node(struct sos_fs_nscache_node * parent_nsnode,
const struct sos_fs_pathname * name,
sos_fs_node_type_t type,
sos_ui32_t flags,
const struct sos_process * creator,
sos_ui32_t access_rights,
struct sos_fs_nscache_node ** result_nsnode);
static sos_ret_t
fs_connect_existing_child_node(const struct sos_process * creator,
struct sos_fs_nscache_node * parent_nsnode,
const struct sos_fs_pathname * name,
struct sos_fs_nscache_node * nsnode);
static sos_ret_t
fs_create_node(const struct sos_fs_pathname * _path,
const struct sos_process * creator,
sos_ui32_t access_rights,
sos_fs_node_type_t type,
struct sos_fs_nscache_node ** result_nsnode);
static sos_ret_t
fs_remove_node(const struct sos_process * actor,
struct sos_fs_nscache_node * nsnode);
/* **********************************************************
* Basic low-level memory & co related functions
*/
sos_ret_t
sos_fs_subsystem_setup(const char * root_device,
const char * fsname,
const char * mount_args,
struct sos_fs_manager_instance ** result_rootfs)
{
sos_ret_t retval;
struct sos_fs_manager_type * fs_type;
struct sos_fs_manager_instance * new_fs;
struct sos_fs_node * rdev_fsnode;
int nb_fstypes;
/* root_device is ignored for now */
rdev_fsnode = NULL;
last_fs_instance_uid = 0;
*result_rootfs = NULL;
retval = sos_fs_nscache_subsystem_setup();
if (SOS_OK != retval)
return retval;
/* Look for the FS manager type */
list_foreach(fs_list, fs_type, nb_fstypes)
{
if (! strcmp(fsname, fs_type->name))
break;
}
if (! list_foreach_early_break(fs_list, fs_type, nb_fstypes))
return -SOS_ENODEV;
retval = fs_type->mount(fs_type,
rdev_fsnode,
mount_args, & new_fs);
if (SOS_OK != retval)
{
if (rdev_fsnode)
sos_fs_unref_fsnode(rdev_fsnode);
return retval;
}
/* Update some reserved fields */
sos_fs_nscache_get_fs_node(new_fs->root)->fs = new_fs;
*result_rootfs = new_fs;
return SOS_OK;
}
sos_ret_t sos_fs_ref_fsnode(struct sos_fs_node * fsnode)
{
fsnode->inmem_ref_cnt ++;
return SOS_OK;
}
sos_ret_t _sos_fs_unref_fsnode(struct sos_fs_node * node)
{
SOS_ASSERT_FATAL(node->inmem_ref_cnt > 0);
/* Commit the changes the the FS when the last reference is being
removed */
if ((node->inmem_ref_cnt == 1) && (node->dirty))
{
SOS_ASSERT_FATAL(SOS_OK == sos_fs_sync_node(node));
}
node->inmem_ref_cnt --;
if (node->inmem_ref_cnt == 0)
{
if (SOS_FS_NODE_DEVICE_CHAR == node->type)
sos_chardev_helper_release_fsnode(node);
else if (SOS_FS_NODE_DEVICE_BLOCK == node->type)
sos_blockdev_helper_release_fsnode(node);
sos_hash_remove(node->fs->nodecache, node);
node->destructor(node);
}
return SOS_OK;
}
sos_ret_t sos_fs_ref_opened_file(struct sos_fs_opened_file * of)
{
of->ref_cnt ++;
return SOS_OK;
}
sos_ret_t _sos_fs_unref_opened_file(struct sos_fs_opened_file ** _of)
{
struct sos_fs_opened_file * of = *_of;
*_of = NULL;
SOS_ASSERT_FATAL(of->ref_cnt > 0);
of->ref_cnt --;
if (0 == of->ref_cnt)
{
sos_ret_t retval;
struct sos_fs_nscache_node * nsnode = of->direntry;
struct sos_fs_node * fsnode = sos_fs_nscache_get_fs_node(nsnode);
retval = fsnode->close_opened_file(fsnode, of);
if (SOS_OK != retval)
return retval;
return sos_fs_nscache_unref_node(nsnode);
}
return SOS_OK;
}
/* **********************************************************
* Some helper functions
*/
/** Fetch the given fsnode from hash first, or from disk when not in
hash */
static sos_ret_t fs_fetch_node(struct sos_fs_manager_instance *fs,
sos_ui64_t storage_location,
struct sos_fs_node ** result_fsnode)
{
sos_ret_t retval;
/* If it is already loaded in memory, no need to look further */
*result_fsnode = (struct sos_fs_node*)
sos_hash_lookup(fs->nodecache,
& storage_location);
if (*result_fsnode)
return SOS_OK;
/* Otherwise, call the appropriate method of the FS */
retval = fs->fetch_node_from_disk(fs, storage_location, result_fsnode);
if (SOS_OK != retval)
return retval;
(*result_fsnode)->generation = 0;
/* Special case for device nodes */
if (SOS_FS_NODE_DEVICE_CHAR == (*result_fsnode)->type)
SOS_ASSERT_FATAL(SOS_OK
== sos_chardev_helper_ref_new_fsnode(*result_fsnode));
else if (SOS_FS_NODE_DEVICE_BLOCK == (*result_fsnode)->type)
SOS_ASSERT_FATAL(SOS_OK
== sos_blockdev_helper_ref_new_fsnode(*result_fsnode));
sos_hash_insert(fs->nodecache, *result_fsnode);
return SOS_OK;
}
/**
* Helper function to allocate a new on-disk node
*/
static sos_ret_t
fs_allocate_node(struct sos_fs_manager_instance * fs,
sos_fs_node_type_t type,
sos_ui32_t flags,
const struct sos_process * creator,
sos_ui32_t access_rights,
struct sos_fs_node ** result_fsnode)
{
sos_ret_t retval;
/* Make sure FS is writable ! */
if (fs->flags & SOS_FS_MOUNT_READONLY)
return -SOS_EPERM;
/* Allocate the node on disk */
retval = fs->allocate_new_node(fs, type,
creator, access_rights,
flags,
result_fsnode);
if (SOS_OK != retval)
return retval;
/* Update some resrved fields */
(*result_fsnode)->fs = fs;
/* Special case for device nodes */
if (SOS_FS_NODE_DEVICE_CHAR == (*result_fsnode)->type)
{
SOS_ASSERT_FATAL(SOS_OK
== sos_chardev_helper_ref_new_fsnode(*result_fsnode));
}
else if (SOS_FS_NODE_DEVICE_BLOCK == (*result_fsnode)->type)
{
SOS_ASSERT_FATAL(SOS_OK
== sos_blockdev_helper_ref_new_fsnode(*result_fsnode));
}
/* insert it in the node cache */
retval = sos_hash_insert(fs->nodecache, *result_fsnode);
if (SOS_OK != retval)
{
sos_fs_unref_fsnode(*result_fsnode);
return retval;
}
/* Success: Consider the node as dirty */
mark_dirty_fsnode(*result_fsnode, FALSE);
return retval;
}
/** Helper function to add the given node in the dirty list, or to
write it directly to the disk if the system is mounted in SYNC
mode */
static sos_ret_t mark_dirty_fsnode(struct sos_fs_node * fsnode,
sos_bool_t force_sync)
{
sos_ret_t retval;
sos_bool_t was_dirty = fsnode->dirty;
fsnode->dirty = TRUE;
fsnode->generation ++;
retval = SOS_OK;
/* If the fsnode is newly dirty, add it to the dirty list of the
FS */
if (!was_dirty && fsnode->dirty)
list_add_tail_named(fsnode->fs->dirty_nodes, fsnode,
prev_dirty, next_dirty);
if (force_sync || (fsnode->fs->flags & SOS_FS_MOUNT_SYNC))
return sos_fs_sync_node(fsnode);
return retval;
}
/** Remove the given node from the dirty list of the FS */
static sos_ret_t sos_fs_sync_node(struct sos_fs_node * fsnode)
{
sos_ret_t retval;
if (! fsnode->dirty)
return SOS_OK;
retval = fsnode->ops_file->sync(fsnode);
if (SOS_OK != retval)
return retval;
/* Remove it from the dirty list */
list_delete_named(fsnode->fs->dirty_nodes, fsnode,
prev_dirty, next_dirty);
fsnode->dirty = FALSE;
/* Commit the FS changes to the device */
if (fsnode->fs->device)
{
retval = sos_blockdev_helper_sync_fsnode(fsnode->fs->device);
if (SOS_OK != retval)
{
/* We got a problem ! Mark the node dirty again */
list_add_tail_named(fsnode->fs->dirty_nodes, fsnode,
prev_dirty, next_dirty);
fsnode->dirty = TRUE;
return retval;
}
}
return SOS_OK;
}
/** Collapse the whole dirty list of the FS and commit the changes to
the underlying device */
static sos_ret_t sos_fs_sync_fs(struct sos_fs_manager_instance * fs)
{
struct sos_fs_node * fsnode;
while (NULL != (fsnode = list_get_head_named(fs->dirty_nodes,
prev_dirty, next_dirty)))
{
sos_ret_t retval = sos_fs_sync_node(fsnode);
if (SOS_OK != retval)
return retval;
}
return SOS_OK;
}
/**
* Resolve the given symlink: return the nsnode for the destination
* of the symlink, or error status for dangling symlinks
*
* @note result_nsnode is a NEW reference to the node. It should be
* unreferenced when unused
*/
static sos_ret_t
fs_resolve_symlink(const struct sos_fs_nscache_node * root_nsnode,
const struct sos_fs_nscache_node * symlink_nsnode,
struct sos_fs_nscache_node ** target_nsnode,
int lookup_recursion_level)
{
sos_ret_t retval;
const struct sos_fs_nscache_node * start_nsnode;
struct sos_fs_node * symlink_fsnode;
struct sos_fs_pathname path;
struct sos_fs_pathname remaining;
symlink_fsnode = sos_fs_nscache_get_fs_node(symlink_nsnode);
retval = symlink_fsnode->ops_symlink->expand(symlink_fsnode,
& path.contents,
& path.length);
if (SOS_OK != retval)
return retval;
if (path.length <= 0)
return -SOS_ENOENT;
/* Absolute path for target ? */
if (path.contents[0] == '/')
start_nsnode = root_nsnode;
else
{
retval = sos_fs_nscache_get_parent(symlink_nsnode,
(struct sos_fs_nscache_node**)& start_nsnode);
if (SOS_OK != retval)
return retval;
}
retval = fs_lookup_node(& path, TRUE, root_nsnode, start_nsnode,
target_nsnode,
& remaining, lookup_recursion_level);
if (SOS_OK != retval)
return retval;
/* The target of the symlink could not be completely opened ! */
if (remaining.length != 0)
{
sos_fs_nscache_unref_node(*target_nsnode);
return -SOS_ENOENT;
}
return SOS_OK;
}
#define MAX_LOOKUP_RECURSION_LEVEL 5
/**
* @return OK in any case, except if 1/ a symlink could not be
* resolved, or 2/ a path "a/b" is given where "a" is not a directory,
* or 3/ a fsnode that should exist could not be retrieved from disk.
*
* @param result_remaining_path contains the path that could not be resolved
*
* @param result_nsnode a NEW reference to the farthest node that
* could be resolved. It should be unreferenced when unused
*/
static sos_ret_t
fs_lookup_node(const struct sos_fs_pathname * path,
sos_bool_t follow_symlinks,
const struct sos_fs_nscache_node * root_nsnode,
const struct sos_fs_nscache_node * start_nsnode,
struct sos_fs_nscache_node ** result_nsnode,
struct sos_fs_pathname * result_remaining_path,
int lookup_recursion_level)
{
sos_ret_t retval;
struct sos_fs_nscache_node * current_nsnode;
/* Make sure we did not go too deep while resolving symlinks */
lookup_recursion_level ++;
if (lookup_recursion_level > MAX_LOOKUP_RECURSION_LEVEL)
{
return -SOS_ELOOP;
}
if (path->length <= 0)
return -SOS_ENOENT;
*result_nsnode = NULL;
memcpy(result_remaining_path, path, sizeof(*path));
current_nsnode = (struct sos_fs_nscache_node *)start_nsnode;
sos_fs_nscache_ref_node(current_nsnode);
while (1)
{
struct sos_fs_pathname current_component, remaining;
struct sos_fs_nscache_node * next_nsnode = NULL;
sos_bool_t slashes_after_first_component;
/* dbg_dump_pathname("Before", result_remaining_path); */
/* Extract the next component of the path */
slashes_after_first_component
= sos_fs_pathname_split_path(result_remaining_path,
& current_component, & remaining);
/* dbg_dump_pathname("After", result_remaining_path); */
/* dbg_dump_pathname("Cur", & current_component); */
/* dbg_dump_pathname("Rem", & remaining); */
/* sos_bochs_printf("Slash after=%d\n", slashes_after_first_component); */
/* Could resolve the whole path ? */
if (current_component.length == 0)
{
/* Ok, fine, we got it ! */
memcpy(result_remaining_path, & remaining, sizeof(remaining));
*result_nsnode = current_nsnode;
return SOS_OK;
}
/* Otherwise: make sure we reached a DIR node */
if (sos_fs_nscache_get_fs_node(current_nsnode)->type
!= SOS_FS_NODE_DIRECTORY)
{
sos_fs_nscache_unref_node(current_nsnode);
return -SOS_ENOENT;
}
/* Make sure this directory is "executable" */
if (! (sos_fs_nscache_get_fs_node(current_nsnode)->access_rights
& SOS_FS_EXECUTABLE) )
{
sos_fs_nscache_unref_node(current_nsnode);
return -SOS_EACCES;
}
/* If we can find the entry in the namespace cache, it is really
fine ! */
retval = sos_fs_nscache_lookup(current_nsnode,
& current_component,
root_nsnode,
& next_nsnode);
if (SOS_OK != retval)
{
struct sos_fs_node * current_fsnode, * next_fsnode;
sos_ui64_t storage_location;
/*
* Not found in the namespace cache. Must read from the
* disk...
*/
current_fsnode = sos_fs_nscache_get_fs_node(current_nsnode);
retval = current_fsnode->ops_dir
->lookup(current_fsnode,
current_component.contents,
current_component.length,
& storage_location);
if (SOS_OK != retval)
{
/* Well, we cannot go further, stop here */
*result_nsnode = current_nsnode;
return SOS_OK;
}
/* Now retrieve this node from disk or from the cache into
memory */
retval = fs_fetch_node(current_fsnode->fs,
storage_location, & next_fsnode);
if (SOS_OK != retval)
{
sos_fs_nscache_unref_node(current_nsnode);
return retval;
}
/* Integrate it in the nscache */
retval = sos_fs_nscache_add_new_child_node(current_nsnode,
& current_component,
next_fsnode,
& next_nsnode);
sos_fs_nscache_unref_node(current_nsnode);
if (SOS_OK != retval)
return retval;
}
else
sos_fs_nscache_unref_node(current_nsnode);
/* Reaching a symlink ? */
if (sos_fs_nscache_get_fs_node(next_nsnode)->type
== SOS_FS_NODE_SYMLINK)
{
/* Expand the link only for non-terminal nodes, or for the
terminal node only if follow_symlinks is TRUE */
if ( (remaining.length != 0)
|| follow_symlinks )
{
struct sos_fs_nscache_node * symlink_target;
retval = fs_resolve_symlink(root_nsnode, next_nsnode,
& symlink_target,
lookup_recursion_level);
sos_fs_nscache_unref_node(next_nsnode);
if (SOS_OK != retval)
return retval; /* Dangling symlink */
next_nsnode = symlink_target;
}
}
/* Make sure there was no slash after this component, unless
this component is a directory */
if (slashes_after_first_component
&&
( sos_fs_nscache_get_fs_node(next_nsnode)->type
!= SOS_FS_NODE_DIRECTORY) )
{
sos_fs_nscache_unref_node(next_nsnode);
return -SOS_ENOTDIR;
}
/* Ok, fine, we got it, update the path we still have to explore */
memcpy(result_remaining_path, & remaining, sizeof(remaining));
current_nsnode = next_nsnode;
}
sos_display_fatal_error("Should not get there");
return -SOS_EFATAL;
}
/**
* It is assumed that parent does not already have a child with the
* given name. We make sure that the "path" is a single entity (ie
* not "a/b")
* @return Error if fsnode is not on the same FS as parent_nsnode
*/
static sos_ret_t
fs_register_child_node(const struct sos_process * creator,
struct sos_fs_nscache_node * parent_nsnode,
const struct sos_fs_pathname * name,
struct sos_fs_node * fsnode,
sos_ui32_t flags,
struct sos_fs_nscache_node ** result_nsnode)
{
sos_ret_t retval;
struct sos_fs_node * parent_fsnode;
struct sos_fs_pathname first_component, remaining;
sos_bool_t slashes_after_first_component = FALSE;
*result_nsnode = NULL;
parent_fsnode = sos_fs_nscache_get_fs_node(parent_nsnode);
if (parent_fsnode->type != SOS_FS_NODE_DIRECTORY)
return -SOS_ENOTDIR;
if (name->length <= 0)
return -SOS_EINVAL;
slashes_after_first_component
= sos_fs_pathname_split_path(name, & first_component, & remaining);
if (fsnode->type != SOS_FS_NODE_DIRECTORY)
{
/* Make sure the given name is exactly a single path component
(ie no '/') */
if (slashes_after_first_component)
return -SOS_EINVAL;
}
else
{
/* Make sure there aren't any other component behind the '/'s, if
any */
if (remaining.length > 0)
return -SOS_EINVAL;
}
/* Make sure the parent directory is writeable */
if (! (parent_fsnode->access_rights & SOS_FS_WRITABLE) )
return -SOS_EACCES;
/* Make sure that the entries are located on the same FS */
if (fsnode->fs != parent_fsnode->fs)
return -SOS_EXDEV;
/* Make sure that the nsnode won't be destroyed */
sos_fs_nscache_ref_node(parent_nsnode);
/* Allocate the node in directory */
retval = parent_fsnode->ops_dir->link(parent_fsnode,
creator,
first_component.contents,
first_component.length,
fsnode);
if (SOS_OK != retval)
{
sos_fs_nscache_unref_node(parent_nsnode);
return retval;
}
/* Success: Consider the directory as dirty */
mark_dirty_fsnode(parent_fsnode, FALSE);
/* Allocate the node in nscache cache */
retval = sos_fs_nscache_add_new_child_node(parent_nsnode, & first_component,
fsnode, result_nsnode);
sos_fs_nscache_unref_node(parent_nsnode);
return retval;
}
/** It is assumed that parent does not already have a child with the
given name. We make sure that the "path" is a single entity (ie
not "a/b"). Return a NEW reference to the newly-created NS node */
static sos_ret_t
fs_create_child_node(struct sos_fs_nscache_node * parent_nsnode,
const struct sos_fs_pathname * name,
sos_fs_node_type_t type,
sos_ui32_t flags,
const struct sos_process * creator,
sos_ui32_t access_rights,
struct sos_fs_nscache_node ** result_nsnode)
{
sos_ret_t retval;
struct sos_fs_node * fsnode, * parent_fsnode;
parent_fsnode = sos_fs_nscache_get_fs_node(parent_nsnode);
if (parent_fsnode->type != SOS_FS_NODE_DIRECTORY)
return -SOS_ENOTDIR;
/* Make sure that the nsnode won't be destroyed */
sos_fs_nscache_ref_node(parent_nsnode);
retval = fs_allocate_node(parent_fsnode->fs, type, flags, creator,
access_rights, & fsnode);
if (SOS_OK != retval)
{
sos_fs_nscache_unref_node(parent_nsnode);
return retval;
}
retval = fs_register_child_node(creator,
parent_nsnode, name, fsnode, flags,
result_nsnode);
sos_fs_nscache_unref_node(parent_nsnode);
/* The function does not need it anymore */
sos_fs_unref_fsnode(fsnode);
return retval;
}
/**
* It is assumed that parent does not already have a child with the
* given name, and that the new child does not have a parent yet. We
* make sure that the "path" is a single entity (ie not "a/b") @return
* Error if fsnode is not on the same FS as parent_nsnode
*/
static sos_ret_t
fs_connect_existing_child_node(const struct sos_process * creator,
struct sos_fs_nscache_node * parent_nsnode,
const struct sos_fs_pathname * name,
struct sos_fs_nscache_node * nsnode)
{
sos_ret_t retval;
struct sos_fs_node * parent_fsnode, * fsnode;
struct sos_fs_pathname first_component, remaining;
sos_bool_t slashes_after_first_component = FALSE;
fsnode = sos_fs_nscache_get_fs_node(nsnode);
parent_fsnode = sos_fs_nscache_get_fs_node(parent_nsnode);
if (parent_fsnode->type != SOS_FS_NODE_DIRECTORY)
return -SOS_ENOTDIR;
if (name->length <= 0)
return -SOS_EINVAL;
slashes_after_first_component
= sos_fs_pathname_split_path(name, & first_component, & remaining);
if (fsnode->type != SOS_FS_NODE_DIRECTORY)
{
/* Make sure the given name is exactly a single path component
(ie no '/') */
if (slashes_after_first_component)
return -SOS_EINVAL;
}
else
{
/* Make sure there aren't any other component behind the '/'s, if
any */
if (remaining.length > 0)
return -SOS_EINVAL;
}
/* Make sure the parent directory is writeable */
if (! (parent_fsnode->access_rights & SOS_FS_WRITABLE) )
return -SOS_EACCES;
/* Make sure that the entries are located on the same FS */
if (fsnode->fs != parent_fsnode->fs)
return -SOS_EXDEV;
/* Make sure that the nsnode won't be destroyed */
sos_fs_nscache_ref_node(parent_nsnode);
/* Allocate the node in directory */
retval = parent_fsnode->ops_dir->link(parent_fsnode,
creator,
first_component.contents,
first_component.length,
fsnode);
if (SOS_OK != retval)
{
sos_fs_nscache_unref_node(parent_nsnode);
return retval;
}
/* Success: Consider the directory as dirty */
mark_dirty_fsnode(parent_fsnode, FALSE);
/* Allocate the node in nscache cache */
retval = sos_fs_nscache_add_existing_child_node(parent_nsnode,
& first_component,
nsnode);
sos_fs_nscache_unref_node(parent_nsnode);
return retval;
}
/** Return a new reference to the new node inserted unless
result_nsnode is NULL */
static sos_ret_t
fs_create_node(const struct sos_fs_pathname * _path,
const struct sos_process * creator,
sos_ui32_t access_rights,
sos_fs_node_type_t type,
struct sos_fs_nscache_node ** result_nsnode)
{
sos_ret_t retval;
struct sos_fs_pathname path;
struct sos_fs_nscache_node *nsnode, *new_nsnode;
path.contents = _path->contents;
path.length = _path->length;
if (path.length <= 0)
return -SOS_ENOENT;
if (path.contents[0] == '/')
nsnode = sos_process_get_root(creator)->direntry;
else
nsnode = sos_process_get_cwd(creator)->direntry;
retval = fs_lookup_node(& path,
TRUE,
sos_process_get_root(creator)->direntry,
nsnode,
& nsnode,
& path,
0);
if (SOS_OK != retval)
return retval;
if (path.length <= 0)
{
/* Found the exact match ! */
sos_fs_nscache_unref_node(nsnode);
return -SOS_EEXIST;
}
/* Create a new entry in the file system */
retval = fs_create_child_node(nsnode,
& path,
type,
/* flags */0,
creator, access_rights,
& new_nsnode);
sos_fs_nscache_unref_node(nsnode);
/* node not needed by this function ? */
if (NULL == result_nsnode)
sos_fs_nscache_unref_node(new_nsnode);
else
*result_nsnode = new_nsnode;
return retval;
}
static sos_ret_t
fs_remove_node(const struct sos_process * actor,
struct sos_fs_nscache_node * nsnode)
{
sos_ret_t retval;
struct sos_fs_nscache_node * parent_nsnode;
struct sos_fs_node * parent_fsnode;
struct sos_fs_pathname childname;
/* Refuse to do anything if this is the root of a mounted FS */
if (nsnode == sos_fs_nscache_get_fs_node(nsnode)->fs->root)
return -SOS_EBUSY;
retval = sos_fs_nscache_get_parent(nsnode, & parent_nsnode);
if (SOS_OK != retval)
return retval;
parent_fsnode = sos_fs_nscache_get_fs_node(parent_nsnode);
/* Make sure FS is writable ! */
if (parent_fsnode->fs->flags & SOS_FS_MOUNT_READONLY)
return -SOS_EPERM;
/* Make sure the parent directory is writeable */
if (! (parent_fsnode->access_rights & SOS_FS_WRITABLE) )
return -SOS_EACCES;
sos_fs_nscache_ref_node(parent_nsnode);
sos_fs_nscache_get_name(nsnode, & childname);
retval = parent_fsnode->ops_dir->unlink(parent_fsnode, actor,
childname.contents,
childname.length);
if (SOS_OK == retval)
sos_fs_nscache_disconnect_node(nsnode);
/* Unallocate the node */
if (SOS_OK == retval)
mark_dirty_fsnode(parent_fsnode, FALSE);
sos_fs_nscache_unref_node(parent_nsnode);
return retval;
}
/* **********************************************************
* Exported functions
*/
sos_ret_t sos_fs_new_opened_file(const struct sos_process * owner,
struct sos_fs_nscache_node * nsnode,
sos_ui32_t open_flags,
struct sos_fs_opened_file ** result_of)
{
sos_ret_t retval;
struct sos_fs_node * fsnode = sos_fs_nscache_get_fs_node(nsnode);
retval = fsnode->new_opened_file(fsnode, owner, open_flags, result_of);
if (SOS_OK != retval)
return retval;
(*result_of)->ref_cnt = 1;
(*result_of)->generation = 1;
retval = sos_fs_nscache_register_opened_file(nsnode, *result_of);
if (SOS_OK != retval)
{
fsnode->close_opened_file(fsnode, *result_of);
return retval;
}
(*result_of)->open_flags = open_flags;
return SOS_OK;
}
sos_ret_t
sos_fs_duplicate_opened_file(struct sos_fs_opened_file * src_of,
const struct sos_process * dst_proc,
struct sos_fs_opened_file ** result_of)
{
sos_ret_t retval = src_of->duplicate(src_of, dst_proc, result_of);
if (SOS_OK != retval)
return retval;
SOS_ASSERT_FATAL((*result_of)->owner == dst_proc);
(*result_of)->ref_cnt = 1;
(*result_of)->generation = 1;
retval = sos_fs_nscache_register_opened_file(src_of->direntry, *result_of);
if (SOS_OK != retval)
{
struct sos_fs_node * fsnode = sos_fs_nscache_get_fs_node(src_of->direntry);
fsnode->close_opened_file(fsnode, *result_of);
return retval;
}
return retval;
}
sos_ret_t sos_fs_open(const struct sos_process *owner,
const char *_path,
sos_size_t _pathlen,
sos_ui32_t open_flags,
sos_ui32_t creat_access_rights,
struct sos_fs_opened_file ** of)
{
sos_ret_t retval;
struct sos_fs_nscache_node *nsnode;
struct sos_fs_node * fsnode;
struct sos_fs_pathname path;
/* O_DIR | O_CREAT combination not supported */
if ( (open_flags & SOS_FS_OPEN_DIRECTORY)
&& ( (open_flags & (SOS_FS_OPEN_CREAT
| SOS_FS_OPEN_TRUNC)) ) )
return -SOS_EINVAL;
if (_pathlen <= 0)
return -SOS_ENOENT;
path.contents = _path;
path.length = _pathlen;
if (path.contents[0] == '/')
nsnode = sos_process_get_root(owner)->direntry;
else
nsnode = sos_process_get_cwd(owner)->direntry;
retval = fs_lookup_node(& path,
! (open_flags & SOS_FS_OPEN_NOFOLLOW),
sos_process_get_root(owner)->direntry,
nsnode,
& nsnode,
& path,
0);
if (SOS_OK != retval)
return retval;
if (path.length <= 0)
{
/* Found the exact match ! */
/* Handle O_EXCL flag */
if (open_flags & SOS_FS_OPEN_EXCL)
{
sos_fs_nscache_unref_node(nsnode);
return -SOS_EEXIST;
}
fsnode = sos_fs_nscache_get_fs_node(nsnode);
if ((open_flags & SOS_FS_OPEN_DIRECTORY)
&& (fsnode->type != SOS_FS_NODE_DIRECTORY))
{
sos_fs_nscache_unref_node(nsnode);
return -SOS_ENOTDIR;
}
/* Handle O_TRUNC flag */
if ((open_flags & SOS_FS_OPEN_TRUNC)
&& fsnode->ops_file->truncate)
{
retval = fsnode->ops_file->truncate(fsnode, 0);
if (SOS_OK != retval)
{
sos_fs_nscache_unref_node(nsnode);
return retval;
}
}
}
else
{
struct sos_fs_nscache_node * parent_nsnode = nsnode;
/* Did not find an exact match. Should create the node ! */
if (! (open_flags & SOS_FS_OPEN_CREAT))
{
sos_fs_nscache_unref_node(parent_nsnode);
return -SOS_ENOENT;
}
/* Create a new entry in the file system */
retval = fs_create_child_node(parent_nsnode,
& path,
SOS_FS_NODE_REGULAR_FILE,
open_flags,
owner,
creat_access_rights,
& nsnode);
sos_fs_nscache_unref_node(parent_nsnode);
if (SOS_OK != retval)
{
return retval;
}
fsnode = sos_fs_nscache_get_fs_node(nsnode);
}
/* Recompute access rights */
open_flags &= ~(SOS_FS_OPEN_CREAT
| SOS_FS_OPEN_EXCL
| SOS_FS_OPEN_NOFOLLOW
| SOS_FS_OPEN_DIRECTORY);
if (! (fsnode->access_rights & SOS_FS_WRITABLE))
open_flags &= ~(SOS_FS_OPEN_WRITE);
if (! (fsnode->access_rights & SOS_FS_READABLE))
open_flags &= ~(SOS_FS_OPEN_READ);
if (fsnode->fs->flags & SOS_FS_MOUNT_READONLY)
open_flags &= ~(SOS_FS_OPEN_READ);
/*
* Ok, open it right now !
*/
retval = sos_fs_new_opened_file(owner, nsnode, open_flags, of);
sos_fs_nscache_unref_node(nsnode);
return retval;
}
sos_ret_t sos_fs_close(struct sos_fs_opened_file * of)
{
return sos_fs_unref_opened_file(of);
}
sos_ret_t sos_fs_mark_dirty(struct sos_fs_opened_file * of)
{
struct sos_fs_node * fsnode = sos_fs_nscache_get_fs_node(of->direntry);
/* This function should never get called if the FS is read-only */
SOS_ASSERT_FATAL(! (fsnode->fs->flags & SOS_FS_MOUNT_READONLY));
return mark_dirty_fsnode(fsnode, of->open_flags & SOS_FS_OPEN_SYNC);
}
sos_ret_t sos_fs_read(struct sos_fs_opened_file * of,
sos_uaddr_t dest_buf,
sos_size_t * /* in/ou */len)
{
if (! (of->open_flags & SOS_FS_OPEN_READ))
return -SOS_EPERM;
if (! of->ops_file->read)
return -SOS_ENOSYS;
return of->ops_file->read(of, dest_buf, len);
}
sos_ret_t sos_fs_write(struct sos_fs_opened_file * of,
sos_uaddr_t src_buf,
sos_size_t * /* in/out */len)
{
if (! (of->open_flags & SOS_FS_OPEN_WRITE))
return -SOS_EPERM;
if (! of->ops_file->write)
return -SOS_ENOSYS;
return of->ops_file->write(of, src_buf, len);
}
sos_ret_t sos_fs_seek(struct sos_fs_opened_file *of,
sos_lsoffset_t offset,
sos_seek_whence_t whence,
sos_lsoffset_t * result_position)
{
if (! of->ops_file->seek)
return -SOS_ENOSYS;
return of->ops_file->seek(of, offset, whence, result_position);
}
sos_ret_t sos_fs_ftruncate(struct sos_fs_opened_file *of,
sos_lsoffset_t length)
{
sos_ret_t retval;
struct sos_fs_node * fsnode = sos_fs_nscache_get_fs_node(of->direntry);
if (! (of->open_flags & SOS_FS_OPEN_WRITE))
return -SOS_EPERM;
if (! fsnode->ops_file->truncate)
return -SOS_ENOSYS;
retval = fsnode->ops_file->truncate(fsnode, length);
if (SOS_OK == retval)
mark_dirty_fsnode(fsnode, FALSE);
return retval;
}
sos_ret_t sos_fs_mmap(struct sos_fs_opened_file *of,
sos_uaddr_t *uaddr, sos_size_t size,
sos_ui32_t access_rights,
sos_ui32_t flags,
sos_luoffset_t offset)
{
sos_ui32_t required_access = 0;
struct sos_fs_node * fsnode = sos_fs_nscache_get_fs_node(of->direntry);
if (! of->ops_file->mmap)
return -SOS_ENOSYS;
/* Translate VM requested rights into FS equivalent */
if (access_rights & SOS_VM_MAP_PROT_READ)
required_access |= SOS_FS_OPEN_READ;
if ( (access_rights & SOS_VM_MAP_PROT_WRITE) && (flags & SOS_VR_MAP_SHARED) )
required_access |= SOS_FS_OPEN_WRITE;
if (access_rights & SOS_VM_MAP_PROT_EXEC)
required_access |= SOS_FS_OPEN_READ;
/* Make sure that the opened file allowed this access */
if ((of->open_flags & required_access) != required_access)
return -SOS_EPERM;
if ( (access_rights & SOS_VM_MAP_PROT_EXEC)
&& (fsnode->fs->flags & SOS_FS_MOUNT_NOEXEC) )
return -SOS_EPERM;
return of->ops_file->mmap(of, uaddr, size, access_rights, flags, offset);
}
sos_ret_t sos_fs_fcntl(struct sos_fs_opened_file *of,
int req_id,
sos_ui32_t req_arg /* Usually: sos_uaddr_t */)
{
if (! of->ops_file->fcntl)
return sos_fs_basic_fcntl_helper(of, req_id, req_arg);
return of->ops_file->fcntl(of, req_id, req_arg);
}
sos_ret_t sos_fs_ioctl(struct sos_fs_opened_file *of,
int req_id,
sos_ui32_t req_arg /* Usually: sos_uaddr_t */)
{
struct sos_fs_node * fsnode = sos_fs_nscache_get_fs_node(of->direntry);
if (fsnode->type == SOS_FS_NODE_DEVICE_CHAR)
{
if (! of->ops_chardev->ioctl)
return -SOS_ENOSYS;
return of->ops_chardev->ioctl(of, req_id, req_arg);
}
else if (fsnode->type == SOS_FS_NODE_DEVICE_BLOCK)
{
if (! of->ops_blockdev->ioctl)
return -SOS_ENOSYS;
return of->ops_blockdev->ioctl(of, req_id, req_arg);
}
return -SOS_ENOSYS;
}
sos_ret_t sos_fs_readdir(struct sos_fs_opened_file * of,
struct sos_fs_dirent * result)
{
struct sos_fs_node * fsnode = sos_fs_nscache_get_fs_node(of->direntry);
if (fsnode->type != SOS_FS_NODE_DIRECTORY)
return -SOS_ENOTDIR;
return of->ops_dir->readdir(of, result);
}
sos_ret_t sos_fs_creat(const struct sos_process * creator,
const char * _path,
sos_size_t _pathlen,
sos_ui32_t access_rights)
{
struct sos_fs_pathname path;
path.contents = _path;
path.length = _pathlen;
return fs_create_node(& path, creator, access_rights,
SOS_FS_NODE_REGULAR_FILE, NULL);
}
sos_ret_t sos_fs_link(const struct sos_process * creator,
const char * _old_path,
sos_size_t _old_pathlen,
const char * _new_path,
sos_size_t _new_pathlen)
{
sos_ret_t retval;
struct sos_fs_nscache_node *old_nsnode, *dest_parent_nsnode, *new_nsnode;
struct sos_fs_node * fsnode;
struct sos_fs_pathname old_path, new_path;
if (_old_pathlen <= 0)
return -SOS_ENOENT;
if (_new_pathlen <= 0)
return -SOS_ENOENT;
/* Resolve target FS node using "old_path" */
old_path.contents = _old_path;
old_path.length = _old_pathlen;
if (old_path.contents[0] == '/')
old_nsnode = sos_process_get_root(creator)->direntry;
else
old_nsnode = sos_process_get_cwd(creator)->direntry;
retval = fs_lookup_node(& old_path,
FALSE /* don't follow symlink */,
sos_process_get_root(creator)->direntry,
old_nsnode,
& old_nsnode,
& old_path,
0);
if (SOS_OK != retval)
return retval;
if (old_path.length > 0)
{
/* Could not resolve full path ! */
sos_fs_nscache_unref_node(old_nsnode);
return -SOS_ENOENT;
}
fsnode = sos_fs_nscache_get_fs_node(old_nsnode);
/* Not allowed to link directories ! */
if (fsnode->type == SOS_FS_NODE_DIRECTORY)
{
sos_fs_nscache_unref_node(old_nsnode);
return -SOS_ENOENT;
}
/* Resolve destination path */
new_path.contents = _new_path;
new_path.length = _new_pathlen;
if (new_path.contents[0] == '/')
dest_parent_nsnode = sos_process_get_root(creator)->direntry;
else
dest_parent_nsnode = sos_process_get_cwd(creator)->direntry;
retval = fs_lookup_node(& new_path,
TRUE /* Follow symlink */,
sos_process_get_root(creator)->direntry,
dest_parent_nsnode,
& dest_parent_nsnode,
& new_path,
0);
if (SOS_OK != retval)
{
sos_fs_nscache_unref_node(old_nsnode);
return retval;
}
if (new_path.length == 0)
{
/* Found the exact match ! Not allowed to overwrite it ! */
sos_fs_nscache_unref_node(dest_parent_nsnode);
sos_fs_nscache_unref_node(old_nsnode);
return -SOS_EEXIST;
}
/* Create a new entry in the file system */
retval = fs_register_child_node(creator, dest_parent_nsnode, & new_path,
fsnode, 0,
& new_nsnode);
sos_fs_nscache_unref_node(dest_parent_nsnode);
sos_fs_nscache_unref_node(old_nsnode);
sos_fs_nscache_unref_node(new_nsnode);
return retval;
}
sos_ret_t sos_fs_rename(const struct sos_process * actor,
const char * _old_path,
sos_size_t _old_pathlen,
const char * _new_path,
sos_size_t _new_pathlen)
{
sos_ret_t retval;
struct sos_fs_nscache_node *old_nsnode, *old_parent_nsnode,
*dest_parent_nsnode, *replaced_nsnode;
struct sos_fs_pathname old_name, replaced_name;
struct sos_fs_pathname old_path, new_path;
old_nsnode = old_parent_nsnode = dest_parent_nsnode = replaced_nsnode = NULL;
if (_old_pathlen <= 0)
return -SOS_ENOENT;
if (_new_pathlen <= 0)
return -SOS_ENOENT;
/* Resolve target FS node using "old_path" */
old_path.contents = _old_path;
old_path.length = _old_pathlen;
if (old_path.contents[0] == '/')
old_nsnode = sos_process_get_root(actor)->direntry;
else
old_nsnode = sos_process_get_cwd(actor)->direntry;
retval = fs_lookup_node(& old_path,
FALSE /* don't follow symlink */,
sos_process_get_root(actor)->direntry,
old_nsnode,
& old_nsnode,
& old_path,
0);
if (SOS_OK != retval)
return retval;
if (old_path.length > 0)
{
/* Could not resolve full path ! */
sos_fs_nscache_unref_node(old_nsnode);
return -SOS_ENOENT;
}
/* Not allowed to rename mountpoints ! */
if (sos_fs_nscache_is_mountnode(old_nsnode))
{
sos_fs_nscache_unref_node(old_nsnode);
return -SOS_ENOENT;
}
/* Keep a reference on this node's parent, in case we must undo the
rename */
retval = sos_fs_nscache_get_parent(old_nsnode, & old_parent_nsnode);
if (SOS_OK != retval)
{
sos_fs_nscache_unref_node(old_nsnode);
return retval;
}
sos_fs_nscache_ref_node(old_parent_nsnode);
/* Resolve destination path */
replaced_nsnode = NULL;
new_path.contents = _new_path;
new_path.length = _new_pathlen;
if (new_path.contents[0] == '/')
dest_parent_nsnode = sos_process_get_root(actor)->direntry;
else
dest_parent_nsnode = sos_process_get_cwd(actor)->direntry;
retval = fs_lookup_node(& new_path,
TRUE /* Follow symlink */,
sos_process_get_root(actor)->direntry,
dest_parent_nsnode,
& dest_parent_nsnode,
& new_path,
0);
if (SOS_OK != retval)
{
goto undo_rename;
}
/* Nothing to do ? */
if (old_nsnode == dest_parent_nsnode)
{
sos_fs_nscache_unref_node(old_nsnode);
sos_fs_nscache_unref_node(old_parent_nsnode);
sos_fs_nscache_unref_node(dest_parent_nsnode);
return SOS_OK;
}
/* Remove old nsnode from file ns cache */
sos_fs_nscache_get_name(old_nsnode, & old_name);
retval = fs_remove_node(actor, old_nsnode);
if (SOS_OK != retval)
{
sos_fs_nscache_unref_node(old_nsnode);
sos_fs_nscache_unref_node(old_parent_nsnode);
sos_fs_nscache_unref_node(dest_parent_nsnode);
return -SOS_ENOENT;
}
if (new_path.length == 0)
{
/* Found the exact match ! We disconnect it from the namespace,
but keep it aside */
/* Not allowed to replace directories */
if (sos_fs_nscache_get_fs_node(dest_parent_nsnode)->type
== SOS_FS_NODE_DIRECTORY)
{
retval = -SOS_EBUSY;
goto undo_rename;
}
replaced_nsnode = dest_parent_nsnode;
dest_parent_nsnode = NULL;
/* Retrieve the parent of the node to replace */
retval = sos_fs_nscache_get_parent(replaced_nsnode,
& dest_parent_nsnode);
if (SOS_OK != retval)
{
dest_parent_nsnode = replaced_nsnode;
goto undo_rename;
}
sos_fs_nscache_ref_node(dest_parent_nsnode);
/* Disconnect this node from its parent */
sos_fs_nscache_get_name(replaced_nsnode, & replaced_name);
retval = fs_remove_node(actor, replaced_nsnode);
if (SOS_OK != retval)
goto undo_rename;
}
/* Create a new entry in the file system */
retval = fs_connect_existing_child_node(actor, dest_parent_nsnode,
& new_path,
old_nsnode);
if (SOS_OK != retval)
goto undo_rename;
sos_fs_nscache_unref_node(old_nsnode);
sos_fs_nscache_unref_node(old_parent_nsnode);
sos_fs_nscache_unref_node(dest_parent_nsnode);
if (NULL != replaced_nsnode)
sos_fs_nscache_unref_node(replaced_nsnode);
return retval;
undo_rename:
/* Handle special case: the node replaced something. In case the
previous operation failed, we try to reinsert the replaced
node */
if (NULL != replaced_nsnode)
{
fs_connect_existing_child_node(actor, dest_parent_nsnode,
& replaced_name,
replaced_nsnode);
sos_fs_nscache_unref_node(replaced_nsnode);
}
fs_connect_existing_child_node(actor, old_parent_nsnode,
& old_name,
old_nsnode);
sos_fs_nscache_unref_node(old_nsnode);
sos_fs_nscache_unref_node(old_parent_nsnode);
if (NULL != dest_parent_nsnode)
sos_fs_nscache_unref_node(dest_parent_nsnode);
return retval;
}
sos_ret_t sos_fs_unlink(const struct sos_process * actor,
const char * _path,
sos_size_t _pathlen)
{
sos_ret_t retval;
struct sos_fs_pathname path;
struct sos_fs_nscache_node * nsnode;
path.contents = _path;
path.length = _pathlen;
if (path.length <= 0)
return -SOS_ENOENT;
if (path.contents[0] == '/')
nsnode = sos_process_get_root(actor)->direntry;
else
nsnode = sos_process_get_cwd(actor)->direntry;
retval = fs_lookup_node(& path, FALSE,
sos_process_get_root(actor)->direntry,
nsnode,
& nsnode, & path, 0);
if (SOS_OK != retval)
return retval;
/* Make sure the whole path has been resolved */
if (path.length > 0)
{
sos_fs_nscache_unref_node(nsnode);
return -SOS_ENOENT;
}
if (sos_fs_nscache_get_fs_node(nsnode)->type == SOS_FS_NODE_DIRECTORY)
retval = -SOS_EISDIR;
else
retval = fs_remove_node(actor, nsnode);
sos_fs_nscache_unref_node(nsnode);
return retval;
}
sos_ret_t sos_fs_symlink(const struct sos_process * creator,
const char * _path,
sos_size_t _pathlen,
sos_uaddr_t symlink_target,
sos_size_t symlink_target_len)
{
sos_ret_t retval;
struct sos_fs_pathname path;
struct sos_fs_node * fsnode;
struct sos_fs_nscache_node * symlink;
struct sos_fs_opened_file * tmp_of;
sos_size_t len;
path.contents = _path;
path.length = _pathlen;
retval = fs_create_node(& path, creator, SOS_FS_S_IRWXALL,
SOS_FS_NODE_SYMLINK, & symlink);
if (SOS_OK != retval)
return retval;
/* Artificially open the symlink to change its contents */
fsnode = sos_fs_nscache_get_fs_node(symlink);
retval = fsnode->new_opened_file(fsnode, creator,
SOS_FS_OPEN_WRITE, & tmp_of);
if (SOS_OK != retval)
{
fs_remove_node(creator, symlink);
sos_fs_nscache_unref_node(symlink);
return retval;
}
tmp_of->ref_cnt = 1;
retval = sos_fs_nscache_register_opened_file(symlink, tmp_of);
if (SOS_OK != retval)
{
fsnode->close_opened_file(fsnode, tmp_of);
fs_remove_node(creator, symlink);
sos_fs_nscache_unref_node(symlink);
return retval;
}
len = symlink_target_len;
retval = sos_fs_write(tmp_of, symlink_target, & len);
mark_dirty_fsnode(fsnode, FALSE);
fsnode->close_opened_file(fsnode, tmp_of);
if ((SOS_OK != retval) || (len != symlink_target_len))
{
fs_remove_node(creator, symlink);
if (SOS_OK == retval)
retval = -SOS_ENAMETOOLONG;
}
sos_fs_nscache_unref_node(symlink);
return retval;
}
sos_ret_t sos_fs_mkdir(const struct sos_process * creator,
const char * _path,
sos_size_t _pathlen,
sos_ui32_t access_rights)
{
struct sos_fs_pathname path;
path.contents = _path;
path.length = _pathlen;
return fs_create_node(& path, creator, access_rights,
SOS_FS_NODE_DIRECTORY, NULL);
}
sos_ret_t sos_fs_rmdir(const struct sos_process * actor,
const char * _path,
sos_size_t _pathlen)
{
sos_ret_t retval;
struct sos_fs_pathname path;
struct sos_fs_nscache_node * nsnode;
path.contents = _path;
path.length = _pathlen;
if (path.length <= 0)
return -SOS_ENOENT;
if (path.contents[0] == '/')
nsnode = sos_process_get_root(actor)->direntry;
else
nsnode = sos_process_get_cwd(actor)->direntry;
retval = fs_lookup_node(& path, FALSE,
sos_process_get_root(actor)->direntry,
nsnode,
& nsnode, & path, 0);
if (SOS_OK != retval)
return retval;
/* Make sure the whole path has been resolved */
if (path.length > 0)
{
sos_fs_nscache_unref_node(nsnode);
return -SOS_ENOENT;
}
/* Cannot rmdir non-dir nodes */
if (sos_fs_nscache_get_fs_node(nsnode)->type != SOS_FS_NODE_DIRECTORY)
retval = -SOS_ENOTDIR;
/* Cannot remove directory if it is still used by somebody else */
else if (sos_fs_nscache_get_ref_cnt(nsnode) > 1)
retval = -SOS_EBUSY;
/* Cannot remove directory if it is still has children stored on
disk */
else if (sos_fs_nscache_get_fs_node(nsnode)->ondisk_lnk_cnt > 1)
retval = -SOS_ENOTEMPTY;
/* Otherwise, yes : suppress the node on disk */
else
retval = fs_remove_node(actor, nsnode);
sos_fs_nscache_unref_node(nsnode);
return retval;
}
sos_ret_t sos_fs_mknod(const struct sos_process * creator,
const char * _path,
sos_size_t _pathlen,
sos_fs_node_type_t type /* only block/char allowed */,
sos_ui32_t access_rights,
const struct sos_fs_dev_id_t * devid)
{
sos_ret_t retval;
struct sos_fs_pathname path;
struct sos_fs_nscache_node * nsnode;
struct sos_fs_node * fsnode;
if ((type != SOS_FS_NODE_DEVICE_BLOCK)
&& (type != SOS_FS_NODE_DEVICE_CHAR))
return -SOS_EINVAL;
path.contents = _path;
path.length = _pathlen;
retval = fs_create_node(& path, creator, access_rights,
type, & nsnode);
if (SOS_OK != retval)
return retval;
fsnode = sos_fs_nscache_get_fs_node(nsnode);
fsnode->dev_id.device_class = devid->device_class;
fsnode->dev_id.device_instance = devid->device_instance;
sos_fs_nscache_unref_node(nsnode);
return SOS_OK;
}
sos_ret_t sos_fs_stat(const struct sos_process * actor,
const char * _path,
sos_size_t _pathlen,
int nofollow,
struct sos_fs_stat * result)
{
sos_ret_t retval;
struct sos_fs_pathname path;
struct sos_fs_nscache_node * nsnode;
struct sos_fs_node * fsnode;
path.contents = _path;
path.length = _pathlen;
if (path.length <= 0)
return -SOS_ENOENT;
if (path.contents[0] == '/')
nsnode = sos_process_get_root(actor)->direntry;
else
nsnode = sos_process_get_cwd(actor)->direntry;
retval = fs_lookup_node(& path, (nofollow != 0),
sos_process_get_root(actor)->direntry,
nsnode,
& nsnode, & path, 0);
if (SOS_OK != retval)
return retval;
/* Make sure the whole path has been resolved */
if (path.length > 0)
{
sos_fs_nscache_unref_node(nsnode);
return -SOS_ENOENT;
}
fsnode = sos_fs_nscache_get_fs_node(nsnode);
retval = fsnode->ops_file->stat(fsnode, result);
sos_fs_nscache_unref_node(nsnode);
return retval;
}
sos_ret_t sos_fs_fsync(struct sos_fs_opened_file * of)
{
struct sos_fs_node * fsnode = sos_fs_nscache_get_fs_node(of->direntry);
return sos_fs_sync_node(fsnode);
}
sos_ret_t sos_fs_chmod(const struct sos_process * actor,
const char * _path,
sos_size_t _pathlen,
sos_ui32_t access_rights)
{
sos_ret_t retval;
struct sos_fs_pathname path;
struct sos_fs_nscache_node * nsnode;
struct sos_fs_node * fsnode;
path.contents = _path;
path.length = _pathlen;
if (path.length <= 0)
return -SOS_ENOENT;
if (path.contents[0] == '/')
nsnode = sos_process_get_root(actor)->direntry;
else
nsnode = sos_process_get_cwd(actor)->direntry;
retval = fs_lookup_node(& path, TRUE,
sos_process_get_root(actor)->direntry,
nsnode,
& nsnode, & path, 0);
if (SOS_OK != retval)
return retval;
/* Make sure the whole path has been resolved */
if (path.length > 0)
{
sos_fs_nscache_unref_node(nsnode);
return -SOS_ENOENT;
}
fsnode = sos_fs_nscache_get_fs_node(nsnode);
retval = fsnode->ops_file->chmod(fsnode, access_rights);
if (SOS_OK == retval)
mark_dirty_fsnode(fsnode, FALSE);
sos_fs_nscache_unref_node(nsnode);
return retval;
}
sos_ret_t sos_fs_vfstat(const struct sos_process * actor,
const char * _path,
sos_size_t _pathlen,
struct sos_fs_statfs * result)
{
sos_ret_t retval;
struct sos_fs_pathname path;
struct sos_fs_nscache_node * nsnode;
struct sos_fs_node * fsnode;
path.contents = _path;
path.length = _pathlen;
if (path.length <= 0)
return -SOS_ENOENT;
if (path.contents[0] == '/')
nsnode = sos_process_get_root(actor)->direntry;
else
nsnode = sos_process_get_cwd(actor)->direntry;
retval = fs_lookup_node(& path, FALSE,
sos_process_get_root(actor)->direntry,
nsnode,
& nsnode, & path, 0);
if (SOS_OK != retval)
return retval;
/* Make sure the whole path has been resolved */
if (path.length > 0)
{
sos_fs_nscache_unref_node(nsnode);
return -SOS_ENOENT;
}
fsnode = sos_fs_nscache_get_fs_node(nsnode);
if (fsnode->fs->statfs)
retval = fsnode->fs->statfs(fsnode->fs, result);
else
retval = -SOS_ENOSYS;
sos_fs_nscache_unref_node(nsnode);
return retval;
}
/** This function is resilient against mounting/unmounting of other FS */
sos_ret_t sos_fs_sync_all_fs()
{
int dummy = 0;
sos_ui64_t uid = 0;
while (1)
{
/* Iterate over the FS types */
struct sos_fs_manager_type * fstype;
int ntype;
list_foreach(fs_list, fstype, ntype)
{
/* Iterate over the FS instances */
struct sos_fs_manager_instance * fs;
int ninst;
/* This scan will be exhaustive and resilient to
addition/removal of file systems as long as new FS are
added with list_add_tail (because the scan is "forward",
ie in order head -> tail) */
/* As long as we don't block, we can safely access the
prev/next fields of the FS instance */
list_foreach_forward(fstype->instances, fs, ninst)
{
if (fs->uid <= uid)
continue;
uid = fs->uid;
sos_fs_sync_fs(fs);
/* We must NOT continue the loops because the
prev/next/current fs types/instances might have been
removed or added (sync blocks, by definition) ! */
goto lookup_next_fs;
}
}
/* Reached the end of the list */
break;
lookup_next_fs:
/* Loop over */
dummy ++;
}
/* Now flush all the block devices to disk */
return sos_blockdev_sync_all_devices();
}
/* *************************************************************
* client FS helper functions
*/
sos_ret_t sos_fs_basic_fcntl_helper(struct sos_fs_opened_file * of,
int req_id, sos_ui32_t req_arg)
{
sos_ret_t result = -SOS_ENOSUP;
switch(req_id)
{
case SOS_FCNTL_DUPFD:
{
result = sos_fs_ref_opened_file(of);
if (SOS_OK != result)
break;
result = sos_process_register_opened_file((struct sos_process*)of->owner,
of, req_arg);
}
break;
case SOS_FCNTL_GETFD:
{
result = of->open_flags & SOS_FS_OPEN_CLOSEONEXEC;
}
break;
case SOS_FCNTL_SETFD:
{
of->open_flags &= ~SOS_FS_OPEN_CLOSEONEXEC;
of->open_flags |= (req_arg & SOS_FS_OPEN_CLOSEONEXEC);
result = SOS_OK;
}
break;
case SOS_FCNTL_GETFL:
{
result = of->open_flags;
}
break;
case SOS_FCNTL_SETFL:
{
/* Not supported */
}
break;
default:
break;
}
return result;
}
/* *************************************************************
* mount/umount stuff
*/
sos_ret_t sos_fs_register_fs_instance(struct sos_fs_manager_instance * fs,
struct sos_fs_node * root_fsnode)
{
sos_ret_t retval;
struct sos_fs_nscache_node * nsnode_root;
retval = sos_fs_nscache_create_mounted_root(root_fsnode, & nsnode_root);
if (SOS_OK != retval)
return retval;
fs->uid = ++last_fs_instance_uid;
fs->root = nsnode_root;
sos_hash_insert(fs->nodecache, root_fsnode);
list_add_tail(fs->fs_type->instances, fs);
return SOS_OK;
}
sos_ret_t sos_fs_unregister_fs_instance(struct sos_fs_manager_instance * fs)
{
fs->uid = 0;
list_delete(fs->fs_type->instances, fs);
return SOS_OK;
}
sos_ret_t sos_fs_register_fs_type(struct sos_fs_manager_type * fstype)
{
struct sos_fs_manager_type * iterator;
int nbtypes;
list_foreach_forward(fs_list, iterator, nbtypes)
{
if (! strncmp(fstype->name, iterator->name, SOS_FS_MANAGER_NAME_MAXLEN))
return -SOS_EEXIST;
}
list_add_tail(fs_list, fstype);
return SOS_OK;
}
sos_ret_t sos_fs_unregister_fs_type(struct sos_fs_manager_type * fstype)
{
struct sos_fs_manager_type * iterator;
int nbtypes;
if (! list_is_empty(fstype->instances))
return -SOS_EBUSY;
list_foreach_forward(fs_list, iterator, nbtypes)
{
if (! strncmp(fstype->name, iterator->name, SOS_FS_MANAGER_NAME_MAXLEN))
{
list_delete(fs_list, fstype);
return SOS_OK;
}
}
return -SOS_EINVAL;
}
/**
* _src_path may be empty
*/
sos_ret_t sos_fs_mount(struct sos_process * actor,
const char * _src_path,
sos_size_t _src_pathlen,
const char * _dst_path,
sos_size_t _dst_pathlen,
const char * fsname,
sos_ui32_t mountflags,
const char * args,
struct sos_fs_manager_instance ** result_fs)
{
sos_ret_t retval;
struct sos_fs_pathname src_path, dst_path;
struct sos_fs_nscache_node * src_nsnode, * dst_nsnode;
struct sos_fs_manager_type * fs_type;
struct sos_fs_manager_instance * new_fs;
int nb_fstypes;
if (_dst_pathlen <= 0)
return -SOS_ENOENT;
/* Look for the FS manager type */
list_foreach(fs_list, fs_type, nb_fstypes)
{
if (! strcmp(fsname, fs_type->name))
break;
}
if (! list_foreach_early_break(fs_list, fs_type, nb_fstypes))
return -SOS_ENODEV;
src_path.contents = _src_path;
src_path.length = _src_pathlen;
/* Compute the start_nsnode for the source */
if (src_path.length <= 0)
src_nsnode = NULL;
else if (src_path.contents[0] == '/')
src_nsnode = sos_process_get_root(actor)->direntry;
else
src_nsnode = sos_process_get_cwd(actor)->direntry;
/* Lookup the source node */
if (src_nsnode)
{
retval = fs_lookup_node(& src_path, TRUE,
sos_process_get_root(actor)->direntry,
src_nsnode,
& src_nsnode, & src_path, 0);
if (SOS_OK != retval)
return retval;
/* Make sure the whole path has been resolved */
if (src_path.length > 0)
{
sos_fs_nscache_unref_node(src_nsnode);
return -SOS_ENOENT;
}
}
dst_path.contents = _dst_path;
dst_path.length = _dst_pathlen;
/* Compute the start_nsnode for the destination */
if (dst_path.contents[0] == '/')
dst_nsnode = sos_process_get_root(actor)->direntry;
else
dst_nsnode = sos_process_get_cwd(actor)->direntry;
/* Lookup the destination node */
retval = fs_lookup_node(& dst_path, TRUE,
sos_process_get_root(actor)->direntry,
dst_nsnode,
& dst_nsnode, & dst_path, 0);
if ((SOS_OK != retval) || (dst_path.length > 0))
{
if (src_nsnode)
sos_fs_nscache_unref_node(src_nsnode);
if (dst_path.length > 0)
retval = -SOS_ENOENT;
return retval;
}
/* Actually call the mount callback of the FS */
retval
= fs_type->mount(fs_type,
(src_nsnode)?sos_fs_nscache_get_fs_node(src_nsnode):NULL,
args, & new_fs);
if (SOS_OK != retval)
{
if (src_nsnode)
sos_fs_nscache_unref_node(src_nsnode);
sos_fs_nscache_unref_node(dst_nsnode);
return retval;
}
/* Make sure the nodecache was created */
SOS_ASSERT_FATAL(NULL != new_fs->nodecache);
SOS_ASSERT_FATAL(NULL != new_fs->root);
/* Update some reserved fields */
sos_fs_nscache_get_fs_node(new_fs->root)->fs = new_fs;
/* Register the mountpoint in the nscache */
retval = sos_fs_nscache_mount(dst_nsnode, new_fs->root);
SOS_ASSERT_FATAL(SOS_OK == retval);
/* Un-reference the temporary nsnodes */
if (src_nsnode)
sos_fs_nscache_unref_node(src_nsnode);
sos_fs_nscache_unref_node(dst_nsnode);
if (result_fs)
*result_fs = new_fs;
return SOS_OK;
}
sos_ret_t sos_fs_umount(struct sos_process * actor,
const char * _mounted_root_path,
sos_size_t _mounted_root_pathlen)
{
sos_ret_t retval;
struct sos_fs_pathname mounted_root_path;
struct sos_fs_nscache_node * mounted_root_nsnode;
struct sos_fs_manager_instance * fs;
if (_mounted_root_pathlen <= 0)
return -SOS_ENOENT;
mounted_root_path.contents = _mounted_root_path;
mounted_root_path.length = _mounted_root_pathlen;
/* Compute the start_nsnode for the mounted_root */
if (mounted_root_path.contents[0] == '/')
mounted_root_nsnode = sos_process_get_root(actor)->direntry;
else
mounted_root_nsnode = sos_process_get_cwd(actor)->direntry;
/* Lookup the mounted_root node */
retval = fs_lookup_node(& mounted_root_path, TRUE,
sos_process_get_root(actor)->direntry,
mounted_root_nsnode,
& mounted_root_nsnode, & mounted_root_path, 0);
if (SOS_OK != retval)
return retval;
/* Make sure the whole path has been resolved */
if (mounted_root_path.length > 0)
{
sos_fs_nscache_unref_node(mounted_root_nsnode);
return -SOS_ENOENT;
}
/* Make sure this node is the real root of the FS */
fs = sos_fs_nscache_get_fs_node(mounted_root_nsnode)->fs;
if (fs->root != mounted_root_nsnode)
{
sos_fs_nscache_unref_node(mounted_root_nsnode);
return -SOS_ENOENT;
}
/* Disconnect this FS mounted_root from namespace cache */
retval = sos_fs_nscache_umount(mounted_root_nsnode);
/* Mounted_Root not needed anymore */
sos_fs_nscache_unref_node(mounted_root_nsnode);
if (SOS_OK != retval)
return retval;
fs->root = NULL;
/* Flush any changes to disk */
retval = sos_fs_sync_fs(fs);
if (SOS_OK != retval)
{
return retval;
}
retval = fs->fs_type->umount(fs->fs_type, fs);
return retval;
}