fsw_core.c revision 4fd606d1f5abe38e1f42c38de1d2e895166bd0f4
/* $Id$ */
/** @file
* fsw_core.c - Core file system wrapper abstraction layer code.
*/
/*-
* This code is based on:
*
* Copyright (c) 2006 Christoph Pfisterer
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* distribution.
*
* * Neither the name of Christoph Pfisterer nor the names of the
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "fsw_core.h"
// functions
#define MAX_CACHE_LEVEL (5)
/**
* Mount a volume with a given file system driver. This function is called by the
* host driver to make a volume accessible. The file system driver to use is specified
* by a pointer to its dispatch table. The file system driver will look at the
* data on the volume to determine if it can read the format. If the volume is found
* unsuitable, FSW_UNSUPPORTED is returned.
*
* If this function returns FSW_SUCCESS, *vol_out points at a valid volume data
* structure. The caller must release it later by calling fsw_unmount.
*
* If this function returns an error status, the caller only needs to clean up its
* own buffers that may have been allocated through the read_block interface.
*/
struct fsw_host_table *host_table,
struct fsw_fstype_table *fstype_table,
struct fsw_volume **vol_out)
{
struct fsw_volume *vol;
// allocate memory for the structure
if (status)
return status;
// initialize fields
// let the fs driver mount the file system
if (status)
goto errorexit;
// TODO: anything else?
return FSW_SUCCESS;
return status;
}
/**
* Unmount a volume by releasing all memory associated with it. This function is
* called by the host driver when a volume is no longer needed. It is also called
* by the core after a failed mount to clean up any allocated memory.
*
* Note that all dnodes must have been released before calling this function.
*/
{
// TODO: check that no other dnodes are still around
}
/**
* Get in-depth information on the volume. This function can be called by the host
* driver to get additional information on the volume.
*/
{
}
/**
* Set the physical and logical block sizes of the volume. This functions is called by
* the file system driver to announce the block sizes it wants to use for accessing
* the disk (physical) and for addressing file contents (logical).
* Usually both sizes will be the same but there may be file systems that need to access
* metadata at a smaller block size than the allocation unit for files.
*
* Calling this function causes the block cache to be dropped. All pointers returned
* from fsw_block_get become invalid. This function should only be called while
* mounting the file system, not as a part of file access operations.
*
* Both sizes are measured in bytes, must be powers of 2, and must not be smaller
* than 512 bytes. The logical block size cannot be smaller than the physical block size.
*/
{
// TODO: Check the sizes. Both must be powers of 2. log_blocksize must not be smaller than
// phys_blocksize.
// drop core block cache if present
// signal host driver to drop caches etc.
}
/**
* Get a block of data from the disk. This function is called by the file system driver
* or by core functions. It calls through to the host driver's device access routine.
* Given a physical block number, it reads the block into memory (or fetches it from the
* block cache) and returns the address of the memory buffer. The caller should provide
* an indication of how important the block is in the cache_level parameter. Blocks with
* a low level are purged first. Some suggestions for cache levels:
*
* - 0: File data
* - 1: Directory data, symlink data
* - 2: File system metadata
* - 3..5: File system metadata with a high rate of access
*
* If this function returns successfully, the returned data pointer is valid until the
* caller calls fsw_block_release.
*/
fsw_status_t fsw_block_get(struct VOLSTRUCTNAME *vol, fsw_u32 phys_bno, fsw_u32 cache_level, void **buffer_out)
{
struct fsw_blockcache *new_bcache;
// TODO: allow the host driver to do its own caching; just call through if
// the appropriate function pointers are set
if (cache_level > MAX_CACHE_LEVEL)
// check block cache
for (i = 0; i < vol->bcache_size; i++) {
// cache hit!
return FSW_SUCCESS;
}
}
// find a free entry in the cache table
for (i = 0; i < vol->bcache_size; i++) {
break;
}
if (i >= vol->bcache_size) {
for (i = 0; i < vol->bcache_size; i++) {
break;
}
if (i < vol->bcache_size)
break;
}
}
if (i >= vol->bcache_size) {
// enlarge / create the cache
new_bcache_size = 16;
else
if (status)
return status;
if (vol->bcache_size > 0)
new_bcache[i].refcount = 0;
new_bcache[i].cache_level = 0;
}
i = vol->bcache_size;
// switch caches
}
// read the data
if (status)
return status;
}
if (status)
return status;
return FSW_SUCCESS;
}
/**
* Releases a disk block. This function must be called to release disk blocks returned
* from fsw_block_get.
*/
{
fsw_u32 i;
// TODO: allow the host driver to do its own caching; just call through if
// the appropriate function pointers are set
// update block cache
for (i = 0; i < vol->bcache_size; i++) {
}
}
/**
* Release the block cache. Called internally when changing block sizes and when
* unmounting the volume. It frees all data occupied by the generic block cache.
*/
{
fsw_u32 i;
for (i = 0; i < vol->bcache_size; i++) {
}
}
vol->bcache_size = 0;
}
/**
* Add a new dnode to the list of known dnodes. This internal function is used when a
* dnode is created to add it to the dnode list that is used to search for existing
* dnodes by id.
*/
{
}
/**
* Create a dnode representing the root directory. This function is called by the file system
* driver while mounting the file system. The root directory is special because it has no parent
* dnode, its name is defined to be empty, and its type is also fixed. Otherwise, this functions
* behaves in the same way as fsw_dnode_create.
*/
fsw_status_t fsw_dnode_create_root(struct fsw_volume *vol, fsw_u32 dnode_id, struct fsw_dnode **dno_out)
{
// allocate memory for the structure
if (status)
return status;
// fill the structure
// TODO: instead, call a function to create an empty string in the native string type
return FSW_SUCCESS;
}
/**
* Create a new dnode representing a file system object. This function is called by
* the file system driver in response to directory lookup or read requests. Note that
* if there already is a dnode with the given dnode_id on record, then no new object
* is created. Instead, the existing dnode is returned and its reference count
* increased. All other parameters are ignored in this case.
*
* The type passed into this function may be FSW_DNODE_TYPE_UNKNOWN. It is sufficient
* to fill the type field during the dnode_fill call.
*
* The name parameter must describe a string with the object's name. A copy will be
* stored in the dnode structure for future reference. The name will not be used to
* shortcut directory lookups, but may be used to reconstruct paths.
*
* If the function returns successfully, *dno_out contains a pointer to the dnode
* that must be released by the caller with fsw_dnode_release.
*/
{
// check if we already have a dnode with the same id
return FSW_SUCCESS;
}
}
// allocate memory for the structure
if (status)
return status;
// fill the structure
if (status) {
return status;
}
return FSW_SUCCESS;
}
/**
* Increases the reference count of a dnode. This must be balanced with
* fsw_dnode_release calls. Note that some dnode functions return a retained
* dnode pointer to their caller.
*/
{
}
/**
* Release a dnode pointer, deallocating it if this was the last reference.
* This function decrements the reference counter of the dnode. If the counter
* reaches zero, the dnode is freed. Since the parent dnode is released
* during that process, this function may cause it to be freed, too.
*/
{
struct fsw_dnode *parent_dno;
// de-register from volume's list
// run fstype-specific cleanup
// release our pointer to the parent, possibly deallocating it, too
if (parent_dno)
}
}
/**
* Get full information about a dnode from disk. This function is called by the host
* driver as well as by the core functions. Some file systems defer reading full
* information on a dnode until it is actually needed (i.e. separation between
* directory and inode information). This function makes sure that all information
* is available in the dnode structure. The following fields may not have a correct
* value until fsw_dnode_fill has been called:
*
* type, size
*/
{
// TODO: check a flag right here, call fstype's dnode_fill only once per dnode
}
/**
* Get extended information about a dnode. This function can be called by the host
* driver to get a full compliment of information about a dnode in addition to the
* fields of the fsw_dnode structure itself.
*
* Some data requires host-specific conversion to be useful (i.e. timestamps) and
* will be passed to callback functions instead of being written into the structure.
* These callbacks must be filled in by the caller.
*/
{
if (status)
return status;
sb->used_bytes = 0;
return status;
}
/**
* Lookup a directory entry by name. This function is called by the host driver.
* Given a directory dnode and a file name, it looks up the named entry in the
* directory.
*
* If the dnode is not a directory, the call will fail. The caller is responsible for
* resolving symbolic links before calling this function.
*
* If the function returns FSW_SUCCESS, *child_dno_out points to the requested directory
* entry. The caller must call fsw_dnode_release on it.
*/
{
if (status)
return status;
return FSW_UNSUPPORTED;
}
/**
* Find a file system object by path. This function is called by the host driver.
* Given a directory dnode and a relative or absolute path, it walks the directory
* tree until it finds the target dnode. If an intermediate node turns out to be
* a symlink, it is resolved automatically. If the target node is a symlink, it
* is not resolved.
*
* If the function returns FSW_SUCCESS, *child_dno_out points to the requested directory
* entry. The caller must call fsw_dnode_release on it.
*/
struct fsw_dnode **child_dno_out)
{
struct fsw_string lookup_name;
struct fsw_string remaining_path;
int root_if_empty;
// loop over the path
// parse next path component
if (root_if_empty)
else
} else {
// do an actual directory lookup
// ensure we have full information
if (status)
goto errorexit;
// resolve symlink if necessary
if (status)
goto errorexit;
// symlink target becomes the new dno
// ensure we have full information
if (status)
goto errorexit;
}
// make sure we operate on a directory
return FSW_UNSUPPORTED;
goto errorexit;
}
// check special paths
// We cannot go up from the root directory. Caution: Certain apps like the EFI shell
// rely on this behaviour!
goto errorexit;
}
} else {
// do an actual lookup
if (status)
goto errorexit;
}
}
// child_dno becomes the new dno
}
*child_dno_out = dno;
return FSW_SUCCESS;
return status;
}
/**
* Get the next directory item in sequential order. This function is called by the
* host driver to read the complete contents of a directory in sequential (file system
* defined) order. Calling this function returns the next entry. Iteration state is
* kept by a shandle on the directory's dnode. The caller must set up the shandle
* when starting the iteration.
*
* When the end of the directory is reached, this function returns FSW_NOT_FOUND.
* If the function returns FSW_SUCCESS, *child_dno_out points to the next directory
* entry. The caller must call fsw_dnode_release on it.
*/
{
return FSW_UNSUPPORTED;
if (status)
return status;
}
/**
* Read the target path of a symbolic link. This function is called by the host driver
* to read the "content" of a symbolic link, that is the relative or absolute path
* it points to.
*
* If the function returns FSW_SUCCESS, the string handle provided by the caller is
* filled with a string in the host's preferred encoding. The caller is responsible
* for calling fsw_strfree on the string.
*/
{
if (status)
return status;
return FSW_UNSUPPORTED;
}
/**
* Read the target path of a symbolic link by accessing file data. This function can
* be called by the file system driver if the file system stores the target path
* as normal file data. This function will open an shandle, read the whole content
* of the file into a buffer, and build a string from that. Currently the encoding
* for the string is fixed as FSW_STRING_TYPE_ISO88591.
*
* If the function returns FSW_SUCCESS, the string handle provided by the caller is
* filled with a string in the host's preferred encoding. The caller is responsible
* for calling fsw_strfree on the string.
*/
{
struct fsw_shandle shand;
char buffer[FSW_PATH_MAX];
struct fsw_string s;
return FSW_VOLUME_CORRUPTED;
// open shandle and read the data
if (status)
return status;
if (status)
return status;
if ((int)buffer_size < s.size)
return FSW_VOLUME_CORRUPTED;
return status;
}
/**
* Resolve a symbolic link. This function can be called by the host driver to make
* sure the a dnode is fully resolved instead of pointing at a symlink. If the dnode
* passed in is not a symlink, it is returned unmodified.
*
* Note that absolute paths will be resolved relative to the root directory of the
* volume. If the host is an operating system with its own VFS layer, it should
* resolve symlinks on its own.
*
* If the function returns FSW_SUCCESS, *target_dno_out points at a dnode that is
* not a symlink. The caller is responsible for calling fsw_dnode_release on it.
*/
{
struct fsw_string target_name;
struct fsw_dnode *target_dno;
while (1) {
// get full information
if (status)
goto errorexit;
// found a non-symlink target, return it
*target_dno_out = dno;
return FSW_SUCCESS;
}
goto errorexit;
}
// read the link's target
if (status)
goto errorexit;
// resolve it
if (status)
goto errorexit;
// target_dno becomes the new dno
}
return status;
}
/**
* Set up a shandle (storage handle) to access a file's data. This function is called
* by the host driver and by the core when they need to access a file's data. It is also
* used in accessing the raw data of directories and symlinks if the file system uses
* the same mechanisms for storing the data of those items.
*
* The storage for the fsw_shandle structure is provided by the caller. The dnode and pos
* fields may be accessed, pos may also be written to to set the file pointer. The file's
* data size is available as shand->dnode->size.
*
* If this function returns FSW_SUCCESS, the caller must call fsw_shandle_close to release
* the dnode reference held by the shandle.
*/
{
// read full dnode information into memory
if (status)
return status;
// setup shandle
return FSW_SUCCESS;
}
/**
* Close a shandle after accessing the dnode's data. This function is called by the host
* driver or core functions when they are finished with accessing a file's data. It
* releases the dnode reference and frees any buffers associated with the shandle itself.
* The dnode is only released if this was the last reference using it.
*/
{
}
/**
* Read data from a shandle (storage handle for a dnode). This function is called by the
* host driver or internally when data is read from a file. TODO: more
*/
fsw_status_t fsw_shandle_read(struct fsw_shandle *shand, fsw_u32 *buffer_size_inout, void *buffer_in)
{
*buffer_size_inout = 0;
return FSW_SUCCESS;
}
// initialize vars
// restrict read to file size
while (buflen > 0) {
// get extent for the current logical block
// ask the file system for the proper extent
if (status) {
return status;
}
}
// dispatch by extent type
// convert to physical block number and offset
// get one physical block
if (status)
return status;
// copy data from it
} else { // _SPARSE or _INVALID
}
}
return FSW_SUCCESS;
}
// EOF