528 lines
13 KiB
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) );
|
||
|
}
|