/* Copyright (C) 2005 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 "fs_nscache.h" /** * A so-called "dentry" / "nsnode" structure. Used to make the * "in-memory" representation of the file system files/dir/devices * that have been used up to now */ struct sos_fs_nscache_node { /** The reference to the associated sos_fs_node */ struct sos_fs_node *fs_node; struct sos_fs_pathname name; /** Number of references to that node, reference from parent (if any) is EXCLUDED */ sos_count_t ref_cnt; /* * Ued to chain the mounted filesystem */ /** If this node is a mountpoint (a file system is mounted on it): reference to the filesystem mounted on it */ struct sos_fs_nscache_node *mounted_root; /** If this node is the root of a mounted filesystem: reference to the mountpoint where it is mounted on */ struct sos_fs_nscache_node *mountpoint; /** ".." */ struct sos_fs_nscache_node *parent; /** List of the already known children */ struct sos_fs_nscache_node *children; /** Other children for the same parent */ struct sos_fs_nscache_node *siblings_prev, *siblings_next; }; /** The cache of nscache_node objects */ static struct sos_kslab_cache * cache_of_nscache_nodes; sos_ret_t sos_fs_nscache_subsystem_setup() { cache_of_nscache_nodes = sos_kmem_cache_create("fs_nscache", sizeof(struct sos_fs_nscache_node), 3, 0, SOS_KSLAB_CREATE_MAP | SOS_KSLAB_CREATE_ZERO); if (! cache_of_nscache_nodes) return -SOS_ENOMEM; return SOS_OK; }; sos_bool_t sos_fs_pathname_eat_slashes(const struct sos_fs_pathname * path, struct sos_fs_pathname * result) { sos_bool_t retval = FALSE; result->contents = path->contents; result->length = path->length; while (result->length > 0) { if (*result->contents != '/') break; result->contents ++; result->length --; retval = TRUE; } if(result->length <= 0) result->contents = NULL; return retval; } static sos_bool_t sos_fs_pathname_eat_non_slashes(const struct sos_fs_pathname * path, struct sos_fs_pathname * result) { sos_bool_t retval = FALSE; result->contents = path->contents; result->length = path->length; while (result->length > 0) { if (*result->contents == '/') break; result->contents ++; result->length --; retval = TRUE; } if(result->length <= 0) result->contents = NULL; return retval; } sos_bool_t sos_fs_pathname_split_path(const struct sos_fs_pathname * path, struct sos_fs_pathname * result_first_component, struct sos_fs_pathname * result_remaining_path) { result_first_component->contents = path->contents; result_first_component->length = path->length; /* Skip any leading slash */ sos_fs_pathname_eat_slashes(result_first_component, result_first_component); /* Extract the first component */ sos_fs_pathname_eat_non_slashes(result_first_component, result_remaining_path); SOS_ASSERT_FATAL(result_remaining_path->length >= 0); result_first_component->length -= result_remaining_path->length; /* Return true if there is something left (at least one slash) */ return (result_remaining_path->length > 0); } sos_bool_t fs_pathname_iseq(const struct sos_fs_pathname * p1, const struct sos_fs_pathname * p2) { if (!p1->contents) SOS_ASSERT_FATAL(p1->length == 0); if (!p2->contents) SOS_ASSERT_FATAL(p2->length == 0); if (p1->length != p2->length) return FALSE; if (p1->length == 0) return TRUE; return (0 == memcmp(p1->contents, p2->contents, p1->length)); } #define fs_pathname_isstr(str,path) \ ({ struct sos_fs_pathname _s; _s.contents = str; _s.length = sizeof(str)-1; \ fs_pathname_iseq(&_s, (path)); }) struct sos_fs_node * sos_fs_nscache_get_fs_node(const struct sos_fs_nscache_node * nsnode) { return nsnode->fs_node; } sos_ret_t sos_fs_nscache_get_parent(const struct sos_fs_nscache_node * nsnode, struct sos_fs_nscache_node ** result_parent) { *result_parent = nsnode->parent; if (*result_parent) return SOS_OK; return -SOS_ENOENT; } sos_ret_t sos_fs_nscache_get_name(const struct sos_fs_nscache_node * nsnode, struct sos_fs_pathname * result_pathname) { result_pathname->contents = nsnode->name.contents; result_pathname->length = nsnode->name.length; return SOS_OK; } sos_ret_t sos_fs_nscache_get_ref_cnt(const struct sos_fs_nscache_node * nsnode) { return nsnode->ref_cnt; } sos_ret_t sos_fs_nscache_lookup(struct sos_fs_nscache_node * cur_nsnode, const struct sos_fs_pathname * node_name, const struct sos_fs_nscache_node * root_nsnode, struct sos_fs_nscache_node ** result_nsnode) { if (fs_pathname_isstr(".", node_name)) { *result_nsnode = cur_nsnode; } else if (fs_pathname_isstr("..", node_name)) { /* Effectively go up only if we did not reach a root node */ if (cur_nsnode == root_nsnode) /* did reach chroot */ { /* Simply stay here */ *result_nsnode = cur_nsnode; } else { /* If current node is a mounted FS, rewind the mountpoint chain */ for ( ; cur_nsnode->mountpoint ; cur_nsnode = cur_nsnode->mountpoint) /* nop */ ; /* Now go up to parent */ SOS_ASSERT_FATAL(NULL != cur_nsnode->parent); *result_nsnode = cur_nsnode->parent; } /* Update the nscache_node result */ sos_fs_nscache_ref_node(*result_nsnode); return SOS_OK; } else { /* Normal lookup: we iterate over the list of children nscache nodes */ int nb_children; struct sos_fs_nscache_node * child; /* Lookup the child node with the correct name, if any */ list_foreach_named(cur_nsnode->children, child, nb_children, siblings_prev, siblings_next) { struct sos_fs_node * fs_node = cur_nsnode->fs_node; if (fs_node->fs->nsnode_same_name) { if (fs_node->fs-> nsnode_same_name(child->name.contents, child->name.length, node_name->contents, node_name->length)) break; } else if (fs_pathname_iseq(& child->name, node_name)) break; } /* Did not find it ! */ if (! list_foreach_early_break(cur_nsnode->children, child, nb_children)) return -SOS_ENOENT; /* Yes, found it ! */ *result_nsnode = child; } /* Found it. Now, Follow the mountpoint chain, if any */ for ( ; (*result_nsnode)->mounted_root ; *result_nsnode = (*result_nsnode)->mounted_root) /* nop */ ; /* Update the nscache_node result */ sos_fs_nscache_ref_node(*result_nsnode); return SOS_OK; } sos_ret_t sos_fs_nscache_ref_node(struct sos_fs_nscache_node * nsnode) { SOS_ASSERT_FATAL(nsnode->ref_cnt > 0); nsnode->ref_cnt ++; return SOS_OK; } /* Eventually collapses a whole list of nsnodes (non recursive) */ sos_ret_t _sos_fs_nscache_unref_node(struct sos_fs_nscache_node ** nsnode) { struct sos_fs_nscache_node * to_delete = NULL, *node; node = *nsnode; *nsnode = NULL; while (node) { /* Unreference this node */ SOS_ASSERT_FATAL(node->ref_cnt > 0); node->ref_cnt --; /* Is it a good candidate for deletion ? */ if (node->ref_cnt > 0) break; /* No */ if (node->parent) { struct sos_fs_nscache_node * parent = node->parent; SOS_ASSERT_FATAL(node->parent->ref_cnt >= 1); list_delete_named(parent->children, node, siblings_prev, siblings_next); /* The parent lost one child: next iteration will decrement ths parent's ref cnt */ } /* Add to the list of elements to suppress */ list_add_tail_named(to_delete, node, siblings_prev, siblings_next); /* Now look if parent (if any) can be destroyed */ node = node->parent; } /* Now destroy all the elements gathered */ while (! list_is_empty_named(to_delete, siblings_prev, siblings_next)) { node = list_pop_head_named(to_delete, siblings_prev, siblings_next); sos_fs_unref_fsnode(node->fs_node); sos_kfree((sos_vaddr_t)node); } return SOS_OK; } sos_ret_t sos_fs_nscache_add_new_child_node(struct sos_fs_nscache_node * parent, const struct sos_fs_pathname * node_name, struct sos_fs_node * fsnode, struct sos_fs_nscache_node ** result_nsnode) { struct sos_fs_nscache_node * nsnode; /* Allocate a new nscache node from slab */ nsnode = (struct sos_fs_nscache_node*) sos_kmem_cache_alloc(cache_of_nscache_nodes, SOS_KSLAB_ALLOC_ATOMIC); if (! nsnode) return -SOS_ENOMEM; /* Allocate a new memory chunk to hold the node's name */ if (node_name && (node_name->length > 0)) { char * contents = (char*) sos_kmalloc(node_name->length, SOS_KMALLOC_ATOMIC); if (! contents) { sos_kfree((sos_vaddr_t)nsnode); return -SOS_ENOMEM; } memcpy(contents, node_name->contents, node_name->length); nsnode->name.contents = contents; nsnode->name.length = node_name->length; } /* Now initialize the new node's fields */ nsnode->ref_cnt = 1; sos_fs_ref_fsnode(fsnode); nsnode->fs_node = fsnode; /* Register this node as a child of its parent, if any */ nsnode->parent = parent; if (parent) { sos_fs_nscache_ref_node(parent); list_add_head_named(parent->children, nsnode, siblings_prev, siblings_next); } *result_nsnode = nsnode; return SOS_OK; } sos_ret_t sos_fs_nscache_add_existing_child_node(struct sos_fs_nscache_node * parent, const struct sos_fs_pathname * node_name, struct sos_fs_nscache_node * nsnode) { SOS_ASSERT_FATAL(nsnode->parent == NULL); /* If the node already had a name, suppress it */ if (NULL != nsnode->name.contents) { sos_kfree((sos_vaddr_t)nsnode->name.contents); } memset(& nsnode->name, 0x0, sizeof(struct sos_fs_pathname)); /* Allocate a new memory chunk to hold the node's name */ if (node_name && (node_name->length > 0)) { char * contents = (char*) sos_kmalloc(node_name->length, SOS_KMALLOC_ATOMIC); if (! contents) { sos_kfree((sos_vaddr_t)nsnode); return -SOS_ENOMEM; } memcpy(contents, node_name->contents, node_name->length); nsnode->name.contents = contents; nsnode->name.length = node_name->length; } /* Register this node as a child of its parent, if any */ nsnode->parent = parent; if (parent) { sos_fs_nscache_ref_node(parent); list_add_head_named(parent->children, nsnode, siblings_prev, siblings_next); } return SOS_OK; } sos_ret_t sos_fs_nscache_disconnect_node(struct sos_fs_nscache_node * nsnode) { if (! nsnode->parent) return SOS_OK; list_delete_named(nsnode->parent->children, nsnode, siblings_prev, siblings_next); sos_fs_nscache_unref_node(nsnode->parent); nsnode->parent = NULL; return SOS_OK; } sos_ret_t sos_fs_nscache_register_opened_file(struct sos_fs_nscache_node * nsnode, struct sos_fs_opened_file * of) { of->direntry = nsnode; sos_fs_nscache_ref_node(nsnode); return SOS_OK; } sos_ret_t sos_fs_nscache_mount(struct sos_fs_nscache_node * mountpoint, struct sos_fs_nscache_node * mounted_root) { SOS_ASSERT_FATAL(NULL == mountpoint->mounted_root); SOS_ASSERT_FATAL(NULL == mounted_root->mountpoint); mountpoint->mounted_root = mounted_root; mounted_root->mountpoint = mountpoint; sos_fs_nscache_ref_node(mountpoint); sos_fs_nscache_ref_node(mounted_root); return SOS_OK; } sos_ret_t sos_fs_nscache_umount(struct sos_fs_nscache_node * mounted_root) { struct sos_fs_manager_instance *fs; SOS_ASSERT_FATAL(NULL != mounted_root->mountpoint); SOS_ASSERT_FATAL(mounted_root->mountpoint->mounted_root == mounted_root); /* No FS should be mounted on the mounted root to umount */ SOS_ASSERT_FATAL(NULL == mounted_root->mounted_root); /* The mounted root should have its own reference, plus a reference from the mountpoint and from the fs instance */ SOS_ASSERT_FATAL(mounted_root->ref_cnt >= 3); if (mounted_root->ref_cnt != 3) return -SOS_EBUSY; fs = mounted_root->fs_node->fs; SOS_ASSERT_FATAL(NULL != fs); SOS_ASSERT_FATAL(fs->root == mounted_root); /* Undo the mountpoint <-> mounted_root mutual reference */ mounted_root->mountpoint->mounted_root = NULL; sos_fs_nscache_unref_node(mounted_root->mountpoint); mounted_root->mountpoint = NULL; sos_fs_nscache_unref_node(mounted_root); /* Undo the fs->root -> mounted_root reference */ sos_fs_nscache_unref_node(fs->root); return SOS_OK; } sos_bool_t sos_fs_nscache_is_mountnode(const struct sos_fs_nscache_node * nsnode) { return ( (NULL != nsnode->mounted_root) || (NULL != nsnode->mountpoint) ); }