sos-code-article10/sos/fs_nscache.c

528 lines
13 KiB
C

/* 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 <sos/assert.h>
#include <sos/list.h>
#include <sos/kmem_slab.h>
#include <sos/kmalloc.h>
#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) );
}