sos-code-article10/drivers/fs_virtfs.c
2018-07-13 17:13:10 +02:00

1055 lines
28 KiB
C

/* Copyright (C) 2005 David Decotigny
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/kmalloc.h>
#include <sos/klibc.h>
#include <sos/assert.h>
#include <sos/list.h>
#include <sos/ksynch.h>
#include <sos/uaccess.h>
#include <sos/physmem.h>
#include <sos/fs.h>
#include <sos/fs_nscache.h>
#include "fs_virtfs.h"
/**
* @file fs_virtfs.c
*
* All the sos_fs_nodes and their contents are stored in kernel
* memory. Thus, with "virtfs", the "disk" is actually the kernel
* memory. In a sos_fs_node, the storage_location field corresponds to
* the kernel address of the corresponding struct sos_fs_node.
*
* Basic kernel-memory virtual FS. Highly UNDER-optimized filesystem:
* each time we resize a file, a complete reallocation is done. This
* somehow complicates the file mapping, as we cannot do this anyhow
* as long as the file is mapped because the kernel address of the
* file contents must NOT change. For that reason, we forbid to resize
* a file as soon as it is mapped and the file's contents are aligned
* on a page boundary in kernel memory (use of sos_kmem_vmm_alloc
* instead of sos_kmalloc).
*/
/**
* A directory entry, used in virtfs_node of type "directory". This is
* as simple as a sos_fs_node with a name.
*/
struct virtfs_direntry
{
char * name;
struct sos_fs_node * fsnode;
/**
* Used by readdir to be resilient against creat/mkdir/unlink/rmdir
* between 2 successive readdirs. Each time a new child to a node is
* allocated, a new "creation_order" is uniquely attributed. 64bits
* should be large enough.
*/
sos_lcount_t creation_order;
struct virtfs_direntry * sibling_prev, * sibling_next;
};
/**
* Structure of a FS file or dir for virtfs. Virtfs only supports
* regular files, directories or symbolic links:
* - regular files correspond to an area in kernel memory
* - directories correspond to a list of virtfs_direntry
*
* Structural inheritance of sos_fs_node: "super" is the ancestor (in
* "OOP" terms). The "super" field might not be the first field: from
* a sos_fs_node, the corresponding virtfs_node is given by the
* sos_fs_node::custom_data field
*/
struct virtfs_node
{
/** The ancestor */
struct sos_fs_node super;
union
{
/* A file */
struct
{
/* Contents of the file */
sos_size_t size;
void * data;
/* Yes, the file can be mapped ! */
struct sos_umem_vmm_mapped_resource mapres;
sos_count_t num_mappings; /**< Forbids the region to be resized
while it's being mapped, because
this woould cause the underlying
data area to be moved (because of
the implementation we chose, a new
allocation is made each time a file
is resized) */
} file;
/* A directory */
struct
{
/** the children nodes are inserted in FIFO order. This is
important for the readdir function to be resilient against
mkdir/rmdir/creat/unlink */
struct virtfs_direntry * list_entries;
/** Used by readdir to remember the last unique "creation order"
attributed */
sos_lcount_t top_creation_order;
} dir;
}; /* Anonymous union (gcc extension) */
/* The virtfs nodes are chained */
struct virtfs_node * prev, * next;
};
/**
* A virtfs FS instance
*
* Structural inheritance of sos_fs_manager_instance: "super" is the
* ancestor (in "OOP" terms). The "super" field might not be the first
* field: from a sos_fs_manager_instance, the corresponding
* virtfs_instance is given by the
* sos_fs_manager_instance::custom_data field
*/
struct virtfs_instance
{
/** Ancestor */
struct sos_fs_manager_instance super;
/** For the serialization of virtfs "disk" (ie kernel memory)
accesses */
struct sos_kmutex lock;
/** The list of virtfs nodes "on disk" (ie in kernel memory) */
struct virtfs_node * list_fsnodes;
};
/** The description of the "Virtual FS" */
static struct sos_fs_manager_type virtfs_type;
/* ********** Forward declarations */
static sos_ret_t virtfs_mount(struct sos_fs_manager_type * this,
struct sos_fs_node * device,
const char * args,
struct sos_fs_manager_instance ** mounted_fs);
static sos_ret_t virtfs_umount(struct sos_fs_manager_type * this,
struct sos_fs_manager_instance * mounted_fs);
static sos_ret_t virtfs_new_mapping(struct sos_umem_vmm_vr *vr);
sos_ret_t sos_fs_virtfs_subsystem_setup()
{
strzcpy(virtfs_type.name, "virtfs", SOS_FS_MANAGER_NAME_MAXLEN);
virtfs_type.mount = virtfs_mount;
virtfs_type.umount = virtfs_umount;
return sos_fs_register_fs_type(& virtfs_type);
}
/* ********************************************************
* Helper functions
*/
/* Helper function to serialize "disk" accesses */
inline static void virtfs_lock(struct virtfs_node *a_node)
{
struct virtfs_instance * fs = (struct virtfs_instance*)a_node->super.fs->custom_data;
sos_kmutex_lock(& fs->lock, NULL);
}
/* Helper function to serialize "disk" accesses */
inline static void virtfs_unlock(struct virtfs_node *a_node)
{
struct virtfs_instance * fs = (struct virtfs_instance*)a_node->super.fs->custom_data;
sos_kmutex_unlock(& fs->lock);
}
/* Helper function to resize the given virts node (ie kernel memory
reallocation) */
static sos_ret_t virtfs_resize(struct virtfs_node *this,
sos_size_t new_size)
{
void * new_data = NULL;
if (this->file.size == new_size)
return SOS_OK;
/* Don't allow to resize the region when the file is being mapped */
if (this->file.num_mappings > 0)
return -SOS_EBUSY;
if (new_size > 0)
{
/* Use kmem_vmm_alloc instead of kmalloc to make sure the data
WILL be page-aligned (needed by file mapping stuff) */
sos_ui32_t npages = SOS_PAGE_ALIGN_SUP(new_size) / SOS_PAGE_SIZE;
new_data = (void*)sos_kmem_vmm_alloc(npages,
SOS_KMEM_VMM_MAP);
if (! new_data)
return -SOS_OK;
}
/* Copy the data to its new location */
if (this->file.size < new_size)
{
if (this->file.size > 0)
memcpy(new_data, this->file.data, this->file.size);
if (new_size > this->file.size)
memset(new_data + this->file.size, 0x0,
new_size - this->file.size);
}
else if (new_size > 0)
memcpy(new_data, this->file.data, new_size);
if (this->file.data)
sos_kfree((sos_vaddr_t)this->file.data);
this->file.data = new_data;
this->file.size = new_size;
return SOS_OK;
}
/* ********************************************************
* Opened file operations
*/
static sos_ret_t
virtfs_duplicate_opened_file(struct sos_fs_opened_file *this,
const struct sos_process * for_owner,
struct sos_fs_opened_file **result)
{
*result = (struct sos_fs_opened_file*)
sos_kmalloc(sizeof(struct sos_fs_opened_file), 0);
if (! *result)
return -SOS_ENOMEM;
memcpy(*result, this, sizeof(*this));
(*result)->owner = for_owner;
return SOS_OK;
}
static sos_ret_t
virtfs_seek(struct sos_fs_opened_file *this,
sos_lsoffset_t offset,
sos_seek_whence_t whence,
/* out */ sos_lsoffset_t * result_position)
{
sos_lsoffset_t ref_offs;
struct virtfs_node * virtfsnode;
virtfsnode = (struct virtfs_node*)
sos_fs_nscache_get_fs_node(this->direntry)->custom_data;
if ( (virtfsnode->super.type != SOS_FS_NODE_REGULAR_FILE)
&& (virtfsnode->super.type != SOS_FS_NODE_SYMLINK))
return -SOS_ENOSUP;
*result_position = this->position;
switch (whence)
{
case SOS_SEEK_SET:
ref_offs = 0;
break;
case SOS_SEEK_CUR:
ref_offs = this->position;
break;
case SOS_SEEK_END:
ref_offs = virtfsnode->file.size;
break;
default:
return -SOS_EINVAL;
}
if (offset < -ref_offs)
return -SOS_EINVAL;
this->position = ref_offs + offset;
*result_position = this->position;
return SOS_OK;
}
static sos_ret_t virtfs_read(struct sos_fs_opened_file *this,
sos_uaddr_t dest_buf,
sos_size_t * /* in/out */len)
{
sos_ret_t retval;
struct virtfs_node * virtfsnode;
virtfsnode = (struct virtfs_node*)
sos_fs_nscache_get_fs_node(this->direntry)->custom_data;
if ( (virtfsnode->super.type != SOS_FS_NODE_REGULAR_FILE)
&& (virtfsnode->super.type != SOS_FS_NODE_SYMLINK))
return -SOS_ENOSUP;
if (this->position >= virtfsnode->file.size)
{
*len = 0;
return SOS_OK;
}
virtfs_lock(virtfsnode);
if (this->position + *len >= virtfsnode->file.size)
*len = virtfsnode->file.size - this->position;
retval = sos_memcpy_to_user(dest_buf,
((sos_vaddr_t)virtfsnode->file.data)
+ this->position,
*len);
if (retval < 0)
{
virtfs_unlock(virtfsnode);
return retval;
}
this->position += retval;
*len = retval;
virtfs_unlock(virtfsnode);
return SOS_OK;
}
static sos_ret_t virtfs_write(struct sos_fs_opened_file *this,
sos_uaddr_t src_buf,
sos_size_t * /* in/out */len)
{
sos_ret_t retval;
struct virtfs_node * virtfsnode;
virtfsnode = (struct virtfs_node*)
sos_fs_nscache_get_fs_node(this->direntry)->custom_data;
if ( (virtfsnode->super.type != SOS_FS_NODE_REGULAR_FILE)
&& (virtfsnode->super.type != SOS_FS_NODE_SYMLINK))
return -SOS_ENOSUP;
virtfs_lock(virtfsnode);
if (this->position + *len >= virtfsnode->file.size)
{
/* Try to resize if needed */
if (SOS_OK != virtfs_resize(virtfsnode, this->position + *len))
*len = virtfsnode->file.size - this->position;
}
retval = sos_memcpy_from_user(((sos_vaddr_t)virtfsnode->file.data)
+ this->position,
src_buf,
*len);
if (retval < 0)
{
virtfs_unlock(virtfsnode);
return retval;
}
this->position += retval;
*len = retval;
virtfs_unlock(virtfsnode);
return SOS_OK;
}
static sos_ret_t virtfs_mmap(struct sos_fs_opened_file *this,
sos_uaddr_t *uaddr, sos_size_t size,
sos_ui32_t access_rights,
sos_ui32_t flags,
sos_luoffset_t offset)
{
struct virtfs_node * virtfsnode;
virtfsnode = (struct virtfs_node*)
sos_fs_nscache_get_fs_node(this->direntry)->custom_data;
if (virtfsnode->super.type != SOS_FS_NODE_REGULAR_FILE)
return -SOS_ENOSUP;
return sos_umem_vmm_map(sos_process_get_address_space(this->owner),
uaddr, size, access_rights,
flags, & virtfsnode->file.mapres, offset);
}
static sos_ret_t virtfs_readdir(struct sos_fs_opened_file *this,
struct sos_fs_dirent * result)
{
/* For directories, "position" indicates the "creation_order" of the
last readdir operation, and "custom_data" indicates the address
of the last directory entry */
struct virtfs_direntry * direntry, * next_direntry;
struct virtfs_node * virtfsnode;
int nb;
virtfsnode = (struct virtfs_node*)
sos_fs_nscache_get_fs_node(this->direntry)->custom_data;
next_direntry = NULL;
/* If the "generation" of the node did not change, the next direntry
is the next in the list. We can safely do this because the list
of direntries is sorted in increasing creation_order. */
if ((this->generation == virtfsnode->super.generation)
&& (this->custom_data != NULL))
{
direntry = (struct virtfs_direntry*)this->custom_data;
next_direntry = direntry->sibling_next;
/* Did we go past the end of the list ? */
if (next_direntry == list_get_head_named(virtfsnode->dir.list_entries,
sibling_prev, sibling_next))
next_direntry = NULL;
}
else
/* Otherwise we have to lookup the next entry manually */
{
/* Lookup the entry that has next creation_order */
next_direntry = NULL;
list_foreach_forward_named(virtfsnode->dir.list_entries,
direntry, nb,
sibling_prev, sibling_next)
{
if (direntry->creation_order <= this->position)
continue;
if (! next_direntry)
{
next_direntry = direntry;
continue;
}
if (direntry->creation_order < next_direntry->creation_order)
next_direntry = direntry;
}
}
if (! next_direntry)
{
this->custom_data = NULL;
this->position = 0;
return -SOS_ENOENT;
}
/* Update the result */
result->storage_location = ((sos_vaddr_t)next_direntry->fsnode);
result->offset_in_dirfile = next_direntry->creation_order;
result->type = next_direntry->fsnode->type;
result->namelen = strnlen(next_direntry->name,
SOS_FS_DIRENT_NAME_MAXLEN);
strzcpy(result->name, next_direntry->name, SOS_FS_DIRENT_NAME_MAXLEN);
/* Update the custom data */
this->position = next_direntry->creation_order;
this->custom_data = next_direntry;
return SOS_OK;
}
static struct sos_fs_ops_opened_file virtfs_ops_opened_file =
(struct sos_fs_ops_opened_file){
.seek = virtfs_seek,
.read = virtfs_read,
.write = virtfs_write,
.mmap = virtfs_mmap
};
static struct sos_fs_ops_opened_dir virtfs_ops_opened_dir =
(struct sos_fs_ops_opened_dir){
.readdir = virtfs_readdir
};
/* ********************************************************
* FS node operations
*/
static sos_ret_t virtfs_stat_node(struct sos_fs_node * this,
struct sos_fs_stat * result)
{
struct virtfs_node * virtfsnode = (struct virtfs_node*)this->custom_data;
/* Establish the major/minor fields */
result->st_rdev.device_class = 0;
result->st_rdev.device_instance = 0;
if ( (this->type == SOS_FS_NODE_DEVICE_CHAR)
|| (this->type == SOS_FS_NODE_DEVICE_BLOCK) )
{
/* If the current node is a special device: get the major/minor
directly from it */
result->st_rdev.device_class = this->dev_id.device_class;
result->st_rdev.device_instance = this->dev_id.device_instance;
}
else
{
/* Otherwise, retrieve it from the device that it is mounted
on (might not exist...) */
struct sos_fs_node * rootdev = this->fs->device;
if (rootdev)
{
result->st_rdev.device_class = rootdev->dev_id.device_class;
result->st_rdev.device_instance = rootdev->dev_id.device_instance;
}
}
result->st_type = this->type;
result->st_storage_location = this->storage_location;
result->st_access_rights = this->access_rights;
result->st_nlink = this->ondisk_lnk_cnt;
if (this->type == SOS_FS_NODE_REGULAR_FILE)
result->st_size = virtfsnode->file.size;
else
result->st_size = 0;
return SOS_OK;
}
static sos_ret_t virtfs_truncate(struct sos_fs_node *this,
sos_lsoffset_t length)
{
sos_ret_t retval;
struct virtfs_node * virtfsnode;
virtfsnode = (struct virtfs_node*) this->custom_data;
if ( (virtfsnode->super.type != SOS_FS_NODE_REGULAR_FILE)
&& (virtfsnode->super.type != SOS_FS_NODE_SYMLINK))
return -SOS_ENOSUP;
virtfs_lock(virtfsnode);
retval = virtfs_resize(virtfsnode, length);
virtfs_unlock(virtfsnode);
return retval;
}
static sos_ret_t virtfs_sync_node(struct sos_fs_node *this)
{
/* No backing store for virtfs */
return SOS_OK;
}
static sos_ret_t virtfs_chmod_node(struct sos_fs_node * this,
sos_ui32_t access_rights)
{
this->access_rights = access_rights;
return SOS_OK;
}
/** Callback when nothing (in particular no sos_fs_nscache_node) make
reference to the node */
static sos_ret_t virtfs_node_destructor(struct sos_fs_node * this)
{
/* This callback is called only when the fsnode is not needed
anymore by any process or by the kernel. But the node must remain
stored "on disk" as long as the FS is mounted AND the node is
still "on disk" */
if (this->ondisk_lnk_cnt <= 0)
{
struct virtfs_instance * virtfs = (struct virtfs_instance*)this->fs->custom_data;
struct virtfs_node * virtfsnode = (struct virtfs_node*)this->custom_data;
list_delete(virtfs->list_fsnodes, virtfsnode);
sos_kfree((sos_vaddr_t) this->custom_data);
}
return SOS_OK;
}
static struct sos_fs_node_ops_file virtfs_ops_file =
(struct sos_fs_node_ops_file){
.truncate = virtfs_truncate,
.stat = virtfs_stat_node,
.chmod = virtfs_chmod_node,
.sync = virtfs_sync_node
};
static sos_ret_t virtfs_new_opened_file(struct sos_fs_node * this,
const struct sos_process * owner,
sos_ui32_t open_flags,
struct sos_fs_opened_file ** result_of)
{
struct sos_fs_opened_file * of
= (struct sos_fs_opened_file*)sos_kmalloc(sizeof(*of), 0);
if (! of)
return -SOS_ENOMEM;
memset(of, 0x0, sizeof(*of));
of->owner = owner;
of->duplicate = virtfs_duplicate_opened_file;
of->open_flags = open_flags;
of->ops_file = & virtfs_ops_opened_file;
if (this->type == SOS_FS_NODE_DIRECTORY)
of->ops_dir = & virtfs_ops_opened_dir;
*result_of = of;
return SOS_OK;
}
static sos_ret_t virtfs_close_opened_file(struct sos_fs_node * this,
struct sos_fs_opened_file * of)
{
sos_kfree((sos_vaddr_t)of);
return SOS_OK;
}
static sos_ret_t virtfs_symlink_expand(struct sos_fs_node *this,
char const ** target,
sos_size_t * target_len)
{
struct virtfs_node * virtfsnode = (struct virtfs_node*)this->custom_data;
*target = virtfsnode->file.data;
*target_len = virtfsnode->file.size;
return SOS_OK;
}
static struct sos_fs_node_ops_symlink virtfs_ops_symlink
= (struct sos_fs_node_ops_symlink){
.expand = virtfs_symlink_expand
};
static sos_ret_t virtfs_dir_lookup(struct sos_fs_node *this,
const char * name, sos_ui16_t namelen,
sos_ui64_t * result_storage_location)
{
struct virtfs_node * virtfsnode = (struct virtfs_node*)this->custom_data;
struct virtfs_direntry * direntry;
int nbentries;
list_foreach_forward_named(virtfsnode->dir.list_entries,
direntry, nbentries,
sibling_prev, sibling_next)
{
if (!memcmp(name, direntry->name, namelen) && !direntry->name[namelen])
{
*result_storage_location = direntry->fsnode->storage_location;
return SOS_OK;
}
}
return -SOS_ENOENT;
}
static sos_ret_t virtfs_link(struct sos_fs_node *this,
const struct sos_process *actor,
const char * entry_name, sos_ui16_t entry_namelen,
struct sos_fs_node * node)
{
struct virtfs_node * parent = (struct virtfs_node*)this->custom_data;
struct virtfs_direntry * direntry;
direntry = (struct virtfs_direntry*)sos_kmalloc(sizeof(*direntry), 0);
if (! direntry)
return -SOS_ENOMEM;
direntry->name = (char*)sos_kmalloc(entry_namelen + 1, 0);
if (! direntry->name)
{
sos_kfree((sos_vaddr_t)direntry->name);
return -SOS_ENOMEM;
}
memcpy(direntry->name, entry_name, entry_namelen);
direntry->name[entry_namelen] = '\0';
direntry->fsnode = node;
node->ondisk_lnk_cnt ++;
this->ondisk_lnk_cnt ++;
list_add_tail_named(parent->dir.list_entries, direntry,
sibling_prev, sibling_next);
/* Update the index of the new entry in order for the next readdirs
to fetch this new entry */
parent->dir.top_creation_order ++;
direntry->creation_order = parent->dir.top_creation_order;
return SOS_OK;
}
static sos_ret_t
virtfs_unlink(struct sos_fs_node *this,
const struct sos_process *actor,
const char * entry_name, sos_ui16_t entry_namelen)
{
struct virtfs_node * parent = (struct virtfs_node*)this->custom_data;
struct virtfs_direntry * direntry;
int nbentries;
list_foreach_forward_named(parent->dir.list_entries,
direntry, nbentries,
sibling_prev, sibling_next)
{
if (!memcmp(entry_name, direntry->name, entry_namelen)
&& !direntry->name[entry_namelen])
{
list_delete_named(parent->dir.list_entries, direntry,
sibling_prev, sibling_next);
direntry->fsnode->ondisk_lnk_cnt --;
this->ondisk_lnk_cnt --;
sos_kfree((sos_vaddr_t)direntry);
return SOS_OK;
}
}
return -SOS_ENOENT;
}
static struct sos_fs_node_ops_dir virtfs_ops_dir
= (struct sos_fs_node_ops_dir){
.lookup = virtfs_dir_lookup,
.link = virtfs_link,
.unlink = virtfs_unlink
};
/* ********************************************************
* FS instance operations
*/
/** Simulate the access to a node located on disk. In virtfs, the
"disk" is directly the kernel memory, so the
"sos_fs_node::storage_location field corresponds to a kernel
address */
static sos_ret_t
virtfs_fetch_node_from_disk(struct sos_fs_manager_instance * this,
sos_ui64_t storage_location,
struct sos_fs_node ** result)
{
/* The "disk" is simply the ram */
struct virtfs_node * virtfsnode;
virtfsnode = (struct virtfs_node *)((sos_vaddr_t)storage_location);
*result = & virtfsnode->super;
return SOS_OK;
}
static sos_ret_t
virtfs_allocate_new_node(struct sos_fs_manager_instance * this,
sos_fs_node_type_t type,
const struct sos_process * creator,
sos_ui32_t access_rights,
sos_ui32_t flags,
struct sos_fs_node ** result)
{
struct virtfs_node * virtfsnode;
/* Allow only DIRs, FILEs or special device files */
if ((type != SOS_FS_NODE_REGULAR_FILE)
&& (type != SOS_FS_NODE_SYMLINK)
&& (type != SOS_FS_NODE_DIRECTORY)
&& (type != SOS_FS_NODE_DEVICE_CHAR)
&& (type != SOS_FS_NODE_DEVICE_BLOCK))
return -SOS_ENOSUP;
virtfsnode = (struct virtfs_node*) sos_kmalloc(sizeof(*virtfsnode), 0);
if (! virtfsnode)
return -SOS_ENOMEM;
memset(virtfsnode, 0x0, sizeof(*virtfsnode));
*result = & virtfsnode->super;
/* Initialize "file" */
(*result)->inmem_ref_cnt = 1;
(*result)->custom_data = virtfsnode;
(*result)->storage_location = (sos_ui64_t)((sos_vaddr_t)virtfsnode);
(*result)->type = type;
(*result)->access_rights = access_rights;
(*result)->destructor = virtfs_node_destructor;
(*result)->ops_file = & virtfs_ops_file;
/* The "file" functions are defined by the FS code only for
non-special device files */
if ((type != SOS_FS_NODE_DEVICE_CHAR)
&& (type != SOS_FS_NODE_DEVICE_BLOCK))
{
(*result)->new_opened_file = virtfs_new_opened_file;
(*result)->close_opened_file = virtfs_close_opened_file;
}
if (type == SOS_FS_NODE_SYMLINK)
(*result)->ops_symlink = & virtfs_ops_symlink;
else if (type == SOS_FS_NODE_DIRECTORY)
(*result)->ops_dir = & virtfs_ops_dir;
/* Initialize mapping structure */
if (type == SOS_FS_NODE_REGULAR_FILE)
{
virtfsnode->file.mapres.allowed_access_rights
= SOS_VM_MAP_PROT_READ
| SOS_VM_MAP_PROT_WRITE
| SOS_VM_MAP_PROT_EXEC;
virtfsnode->file.mapres.custom_data = virtfsnode;
virtfsnode->file.mapres.mmap = virtfs_new_mapping;
}
list_add_tail(((struct virtfs_instance*)this->custom_data)->list_fsnodes,
virtfsnode);
return SOS_OK;
}
/* ********************************************************
* FS type (mount/umount) operations
*/
static sos_ret_t virtfs_mount(struct sos_fs_manager_type * this,
struct sos_fs_node * device,
const char * args,
struct sos_fs_manager_instance ** mounted_fs)
{
sos_ret_t retval;
struct virtfs_instance * fs;
struct sos_fs_node * fsnode_root;
struct sos_hash_table * hash;
*mounted_fs = (struct sos_fs_manager_instance*)NULL;
/* Create FS node hash table */
hash = sos_hash_create("virtfs H", struct sos_fs_node,
sos_hash_ui64,
sos_hash_key_eq_ui64, 17,
storage_location, hlink_nodecache);
if (! hash)
return -SOS_ENOMEM;
fs = (struct virtfs_instance*)
sos_kmalloc(sizeof(struct virtfs_instance), 0);
if (! fs)
{
sos_hash_dispose(hash);
return -SOS_ENOMEM;
}
memset(fs, 0x0, sizeof(struct virtfs_instance));
retval = sos_kmutex_init(& fs->lock, "virtfs", SOS_KWQ_ORDER_FIFO);
if (SOS_OK != retval)
{
sos_hash_dispose(hash);
sos_kfree((sos_vaddr_t) fs);
return retval;
}
fs->super.custom_data = fs;
fs->super.fs_type = this;
fs->super.allocate_new_node = virtfs_allocate_new_node;
fs->super.fetch_node_from_disk = virtfs_fetch_node_from_disk;
fs->super.nodecache = hash;
retval = virtfs_allocate_new_node(& fs->super, SOS_FS_NODE_DIRECTORY,
NULL,
SOS_FS_READABLE | SOS_FS_WRITABLE
| SOS_FS_EXECUTABLE,
0,
& fsnode_root);
if (SOS_OK != retval)
{
sos_hash_dispose(hash);
sos_kmutex_dispose(& fs->lock);
sos_kfree((sos_vaddr_t) fs);
return retval;
}
retval = sos_fs_register_fs_instance(& fs->super, fsnode_root);
sos_fs_unref_fsnode(fsnode_root);
if (SOS_OK != retval)
{
sos_hash_dispose(hash);
sos_kmutex_dispose(& fs->lock);
sos_kfree((sos_vaddr_t) fs);
return retval;
}
*mounted_fs = & fs->super;
return SOS_OK;
}
static sos_ret_t virtfs_umount(struct sos_fs_manager_type * this,
struct sos_fs_manager_instance * mounted_fs)
{
struct virtfs_instance * virtfs = (struct virtfs_instance*)mounted_fs->custom_data;
sos_hash_dispose(virtfs->super.nodecache);
while (! list_is_empty(virtfs->list_fsnodes))
{
struct virtfs_node * virtfsnode = list_pop_head(virtfs->list_fsnodes);
if (virtfsnode->super.type == SOS_FS_NODE_REGULAR_FILE)
{
if (virtfsnode->file.size > 0)
sos_kfree((sos_vaddr_t) virtfsnode->file.data);
}
else if (virtfsnode->super.type == SOS_FS_NODE_DIRECTORY)
{
while (! list_is_empty_named(virtfsnode->dir.list_entries,
sibling_prev, sibling_next))
{
struct virtfs_direntry * direntry
= list_pop_head_named(virtfsnode->dir.list_entries,
sibling_prev, sibling_next);
sos_kfree((sos_vaddr_t)direntry->name);
sos_kfree((sos_vaddr_t)direntry);
}
}
sos_kfree((sos_vaddr_t)virtfsnode);
}
sos_fs_unregister_fs_instance(& virtfs->super);
sos_kmutex_dispose(& virtfs->lock);
sos_kfree((sos_vaddr_t)virtfs);
return SOS_OK;
}
/* ********************************************************
* File mapping stuff
*/
inline static struct virtfs_node *
get_virtfsnode_of_vr(struct sos_umem_vmm_vr * vr)
{
struct sos_umem_vmm_mapped_resource *mr
= sos_umem_vmm_get_mapped_resource_of_vr(vr);
return (struct virtfs_node *)mr->custom_data;
}
static void virtfs_map_ref(struct sos_umem_vmm_vr * vr)
{
struct virtfs_node * virtfsnode = get_virtfsnode_of_vr(vr);
sos_fs_ref_fsnode(& virtfsnode->super);
virtfsnode->file.num_mappings ++;
}
static void virtfs_map_unref(struct sos_umem_vmm_vr * vr)
{
struct virtfs_node * virtfsnode = get_virtfsnode_of_vr(vr);
SOS_ASSERT_FATAL(virtfsnode->file.num_mappings > 0);
virtfsnode->file.num_mappings --;
_sos_fs_unref_fsnode(& virtfsnode->super);
}
static sos_ret_t virtfs_map_page_in(struct sos_umem_vmm_vr * vr,
sos_uaddr_t uaddr,
sos_bool_t write_access)
{
struct virtfs_node * virtfsnode = get_virtfsnode_of_vr(vr);
sos_luoffset_t offset = uaddr - sos_umem_vmm_get_start_of_vr(vr);
sos_ret_t retval = SOS_OK;
sos_paddr_t ppage_paddr;
/* The region is not allowed to be resized */
if (SOS_PAGE_ALIGN_SUP(offset) > virtfsnode->file.size)
return -SOS_EFAULT;
/* Lookup physical kernel page */
ppage_paddr = sos_paging_get_paddr(SOS_PAGE_ALIGN_INF(virtfsnode->file.data
+ offset));
/* Cannot access unmapped kernel pages */
if (! ppage_paddr)
return -SOS_EFAULT;
/* Remap it in user space */
retval = sos_paging_map(ppage_paddr,
SOS_PAGE_ALIGN_INF(uaddr),
TRUE,
sos_umem_vmm_get_prot_of_vr(vr));
return retval;
}
static struct sos_umem_vmm_vr_ops virtfs_map_ops
= (struct sos_umem_vmm_vr_ops){
.ref = virtfs_map_ref,
.unref = virtfs_map_unref,
.page_in = virtfs_map_page_in
};
static sos_ret_t virtfs_new_mapping(struct sos_umem_vmm_vr *vr)
{
struct virtfs_node * virtfsnode = get_virtfsnode_of_vr(vr);
sos_size_t reqsize;
sos_ret_t retval;
reqsize = sos_umem_vmm_get_offset_in_resource(vr);
reqsize += sos_umem_vmm_get_size_of_vr(vr);
/* Resize the region NOW */
if (reqsize > virtfsnode->file.size)
{
retval = virtfs_resize(virtfsnode,
SOS_PAGE_ALIGN_SUP(reqsize));
if (SOS_OK != retval)
return retval;
}
return sos_umem_vmm_set_ops_of_vr(vr, &virtfs_map_ops);
}