1055 lines
28 KiB
C
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);
|
|
}
|