/* 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 #include #include #include #include /* 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; }