mirror of
https://github.com/CloverHackyColor/CloverBootloader.git
synced 2024-11-23 11:35:19 +01:00
7c0aa811ec
Signed-off-by: Sergey Isakov <isakov-sl@bk.ru>
1140 lines
35 KiB
C
1140 lines
35 KiB
C
/* $Id: fsw_core.c 29125 2010-05-06 09:43:05Z vboxsync $ */
|
|
/** @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
|
|
* documentation and/or other materials provided with 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.
|
|
*/
|
|
// nms42 - caching support
|
|
|
|
#include "fsw_core.h"
|
|
|
|
//#include "Version.h"
|
|
//CONST CHAR8* CloverRevision = REVISION_STR;
|
|
|
|
#define DEBUG_CR 0
|
|
|
|
#if DEBUG_CR==2
|
|
#define DBG(...) AsciiPrint(__VA_ARGS__)
|
|
#elif DEBUG_CR==1
|
|
#define DBG(...) BootLog(__VA_ARGS__)
|
|
#else
|
|
#define DBG(...)
|
|
#endif
|
|
|
|
const char* fsw_errors[] = {
|
|
"SUCCESS", "OUT_OF_MEMORY", "IO_ERROR", "UNSUPPORTED", "NOT_FOUND",
|
|
"VOLUME_CORRUPTED", "UNKNOWN_ERROR"
|
|
};
|
|
|
|
|
|
// functions
|
|
|
|
static void fsw_blockcache_free(struct fsw_volume *vol);
|
|
|
|
#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.
|
|
*/
|
|
|
|
fsw_status_t fsw_mount(void *host_data,
|
|
struct fsw_host_table *host_table,
|
|
struct fsw_fstype_table *fstype_table,
|
|
struct fsw_volume **vol_out)
|
|
{
|
|
fsw_status_t status;
|
|
struct fsw_volume *vol;
|
|
|
|
// allocate memory for the structure
|
|
status = fsw_alloc_zero(fstype_table->volume_struct_size, (void **)&vol);
|
|
if (status)
|
|
return status;
|
|
|
|
// initialize fields
|
|
vol->phys_blocksize = 512;
|
|
vol->log_blocksize = 512;
|
|
vol->label.type = FSW_STRING_TYPE_EMPTY;
|
|
vol->host_data = host_data;
|
|
vol->host_table = host_table;
|
|
vol->fstype_table = fstype_table;
|
|
vol->host_string_type = host_table->native_string_type;
|
|
|
|
// let the fs driver mount the file system
|
|
status = vol->fstype_table->volume_mount(vol);
|
|
if (status)
|
|
goto errorexit;
|
|
|
|
// TODO: anything else?
|
|
|
|
*vol_out = vol;
|
|
return FSW_SUCCESS;
|
|
|
|
errorexit:
|
|
fsw_unmount(vol);
|
|
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.
|
|
*/
|
|
|
|
void fsw_unmount(struct fsw_volume *vol)
|
|
{
|
|
if (!vol) {
|
|
return;
|
|
}
|
|
if (vol->root)
|
|
fsw_dnode_release(vol->root);
|
|
// TODO: check that no other dnodes are still around
|
|
|
|
vol->fstype_table->volume_free(vol);
|
|
|
|
fsw_blockcache_free(vol);
|
|
fsw_strfree(&vol->label);
|
|
fsw_free(vol);
|
|
}
|
|
|
|
/**
|
|
* Get in-depth information on the volume. This function can be called by the host
|
|
* driver to get additional information on the volume.
|
|
*/
|
|
|
|
fsw_status_t fsw_volume_stat(struct fsw_volume *vol, struct fsw_volume_stat *sb)
|
|
{
|
|
if (!vol) {
|
|
return FSW_UNSUPPORTED;
|
|
}
|
|
return vol->fstype_table->volume_stat(vol, sb);
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
|
|
void fsw_set_blocksize_(struct fsw_volume *vol, fsw_u32 phys_blocksize, fsw_u32 log_blocksize)
|
|
{
|
|
// 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
|
|
fsw_blockcache_free(vol);
|
|
|
|
// signal host driver to drop caches etc.
|
|
vol->host_table->change_blocksize(vol,
|
|
vol->phys_blocksize, vol->log_blocksize,
|
|
phys_blocksize, log_blocksize);
|
|
|
|
vol->phys_blocksize = phys_blocksize;
|
|
vol->log_blocksize = log_blocksize;
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
fsw_status_t status;
|
|
fsw_u32 i, discard_level, new_bcache_size;
|
|
struct fsw_blockcache *new_bcache = NULL;
|
|
|
|
// 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)
|
|
cache_level = MAX_CACHE_LEVEL;
|
|
|
|
// check block cache
|
|
for (i = 0; i < vol->bcache_size; i++) {
|
|
if (vol->bcache[i].phys_bno == phys_bno) {
|
|
// cache hit!
|
|
if (vol->bcache[i].cache_level < cache_level)
|
|
vol->bcache[i].cache_level = cache_level; // promote the entry
|
|
vol->bcache[i].refcount++;
|
|
*buffer_out = vol->bcache[i].data;
|
|
return FSW_SUCCESS;
|
|
}
|
|
}
|
|
|
|
// find a free entry in the cache table
|
|
for (i = 0; i < vol->bcache_size; i++) {
|
|
if (vol->bcache[i].phys_bno == (fsw_u32)FSW_INVALID_BNO)
|
|
break;
|
|
}
|
|
if (i >= vol->bcache_size) {
|
|
for (discard_level = 0; discard_level <= MAX_CACHE_LEVEL; discard_level++) {
|
|
for (i = 0; i < vol->bcache_size; i++) {
|
|
if (vol->bcache[i].refcount == 0 && vol->bcache[i].cache_level <= discard_level)
|
|
break;
|
|
}
|
|
if (i < vol->bcache_size)
|
|
break;
|
|
}
|
|
}
|
|
if (i >= vol->bcache_size) {
|
|
// enlarge / create the cache
|
|
if (vol->bcache_size < 16)
|
|
new_bcache_size = 16;
|
|
else
|
|
new_bcache_size = vol->bcache_size << 1;
|
|
status = fsw_alloc(new_bcache_size * sizeof(struct fsw_blockcache), &new_bcache);
|
|
if (status != FSW_SUCCESS) {
|
|
if (new_bcache) {
|
|
fsw_free(new_bcache);
|
|
}
|
|
return status;
|
|
}
|
|
if (vol->bcache_size > 0) {
|
|
fsw_memcpy(new_bcache, vol->bcache, vol->bcache_size * sizeof(struct fsw_blockcache));
|
|
}
|
|
for (i = vol->bcache_size; i < new_bcache_size; i++) {
|
|
new_bcache[i].refcount = 0;
|
|
new_bcache[i].cache_level = 0;
|
|
new_bcache[i].phys_bno = (fsw_u32)FSW_INVALID_BNO;
|
|
new_bcache[i].data = NULL;
|
|
}
|
|
i = vol->bcache_size;
|
|
|
|
// switch caches
|
|
if (vol->bcache != NULL)
|
|
fsw_free(vol->bcache);
|
|
vol->bcache = new_bcache;
|
|
vol->bcache_size = new_bcache_size;
|
|
}
|
|
vol->bcache[i].phys_bno = (fsw_u32)FSW_INVALID_BNO;
|
|
|
|
// read the data
|
|
if (vol->bcache[i].data == NULL) {
|
|
status = fsw_alloc(vol->phys_blocksize, &vol->bcache[i].data);
|
|
if (status)
|
|
return status;
|
|
}
|
|
status = vol->host_table->read_block(vol, phys_bno, vol->bcache[i].data);
|
|
if (status)
|
|
return status;
|
|
|
|
vol->bcache[i].phys_bno = phys_bno;
|
|
vol->bcache[i].cache_level = cache_level;
|
|
vol->bcache[i].refcount = 1;
|
|
*buffer_out = vol->bcache[i].data;
|
|
return FSW_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Releases a disk block. This function must be called to release disk blocks returned
|
|
* from fsw_block_get.
|
|
*/
|
|
|
|
void fsw_block_release_(struct VOLSTRUCTNAME *vol, fsw_u32 phys_bno, void *buffer)
|
|
{
|
|
fsw_u32 i;
|
|
if (!vol) {
|
|
return;
|
|
}
|
|
|
|
// 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++) {
|
|
if (vol->bcache[i].phys_bno == phys_bno && vol->bcache[i].refcount > 0)
|
|
vol->bcache[i].refcount--;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
|
|
static void fsw_blockcache_free(struct fsw_volume *vol)
|
|
{
|
|
fsw_u32 i;
|
|
if (!vol) {
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < vol->bcache_size; i++) {
|
|
if (vol->bcache[i].data != NULL)
|
|
fsw_free(vol->bcache[i].data);
|
|
}
|
|
if (vol->bcache != NULL) {
|
|
fsw_free(vol->bcache);
|
|
vol->bcache = NULL;
|
|
}
|
|
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.
|
|
*/
|
|
|
|
static void fsw_dnode_register(struct fsw_volume *vol, struct fsw_dnode *dno)
|
|
{
|
|
if (!vol || !dno) {
|
|
return;
|
|
}
|
|
|
|
dno->next = vol->dnode_head;
|
|
if (vol->dnode_head != NULL)
|
|
vol->dnode_head->prev = dno;
|
|
dno->prev = NULL;
|
|
vol->dnode_head = dno;
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
fsw_status_t status;
|
|
struct fsw_dnode *dno;
|
|
|
|
// allocate memory for the structure
|
|
status = fsw_alloc_zero(vol->fstype_table->dnode_struct_size, (void **)&dno);
|
|
if (status)
|
|
return status;
|
|
|
|
// fill the structure
|
|
dno->vol = vol;
|
|
dno->parent = NULL;
|
|
dno->dnode_id = dnode_id;
|
|
dno->type = FSW_DNODE_TYPE_DIR;
|
|
dno->refcount = 1;
|
|
dno->name.type = FSW_STRING_TYPE_EMPTY;
|
|
// TODO: instead, call a function to create an empty string in the native string type
|
|
|
|
fsw_dnode_register(vol, dno);
|
|
|
|
*dno_out = dno;
|
|
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.
|
|
*/
|
|
|
|
fsw_status_t fsw_dnode_create_(struct fsw_dnode *parent_dno, fsw_u32 dnode_id, int type,
|
|
struct fsw_string *name, struct fsw_dnode **dno_out)
|
|
{
|
|
fsw_status_t status;
|
|
struct fsw_volume *vol;
|
|
struct fsw_dnode *dno;
|
|
if (!parent_dno) {
|
|
return FSW_NOT_FOUND;
|
|
}
|
|
vol = parent_dno->vol;
|
|
|
|
// check if we already have a dnode with the same id
|
|
for (dno = vol->dnode_head; dno; dno = dno->next) {
|
|
if (dno->dnode_id == dnode_id) {
|
|
fsw_dnode_retain(dno);
|
|
*dno_out = dno;
|
|
return FSW_SUCCESS;
|
|
}
|
|
}
|
|
|
|
// allocate memory for the structure
|
|
status = fsw_alloc_zero(vol->fstype_table->dnode_struct_size, (void **)&dno);
|
|
if (status)
|
|
return status;
|
|
|
|
// fill the structure
|
|
dno->vol = vol;
|
|
dno->parent = parent_dno;
|
|
fsw_dnode_retain(dno->parent);
|
|
dno->dnode_id = dnode_id;
|
|
dno->type = type;
|
|
dno->refcount = 1;
|
|
status = fsw_strdup_coerce(&dno->name, vol->host_table->native_string_type, name);
|
|
if (status) {
|
|
fsw_free(dno);
|
|
return status;
|
|
}
|
|
|
|
fsw_dnode_register(vol, dno);
|
|
|
|
*dno_out = dno;
|
|
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.
|
|
*/
|
|
|
|
void fsw_dnode_retain(struct fsw_dnode *dno)
|
|
{
|
|
dno->refcount++;
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
|
|
void fsw_dnode_release(struct fsw_dnode *dno)
|
|
{
|
|
struct fsw_volume *vol;
|
|
struct fsw_dnode *parent_dno;
|
|
if (!dno) {
|
|
return;
|
|
}
|
|
vol = dno->vol;
|
|
|
|
|
|
dno->refcount--;
|
|
#if defined(FSW_DNODE_CACHE_SIZE) && FSW_DNODE_CACHE_SIZE > 0 // numcslots always zero for non dir dnodes
|
|
if (dno->refcount != dno->numcslots)
|
|
return;
|
|
#else
|
|
if (dno->refcount > 0)
|
|
return;
|
|
#endif
|
|
|
|
parent_dno = dno->parent;
|
|
|
|
// de-register from volume's list
|
|
if (dno->next)
|
|
dno->next->prev = dno->prev;
|
|
if (dno->prev)
|
|
dno->prev->next = dno->next;
|
|
if (vol->dnode_head == dno)
|
|
vol->dnode_head = dno->next;
|
|
|
|
#if defined(FSW_DNODE_CACHE_SIZE) && FSW_DNODE_CACHE_SIZE > 0
|
|
if (dno->type == FSW_DNODE_TYPE_DIR) {
|
|
int i;
|
|
|
|
for (i = 0; i < FSW_DNODE_CACHE_SIZE; i++) {
|
|
struct fsw_dnode *cache_entry = dno->cache[i];
|
|
|
|
if (cache_entry == NULL)
|
|
continue;
|
|
// numcslots not decremented on purpose
|
|
fsw_dnode_release(cache_entry);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// run fstype-specific cleanup
|
|
vol->fstype_table->dnode_free(vol, dno);
|
|
|
|
fsw_strfree(&dno->name);
|
|
fsw_free(dno);
|
|
|
|
// release our pointer to the parent, possibly deallocating it, too
|
|
if (parent_dno)
|
|
fsw_dnode_release(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
|
|
*/
|
|
|
|
fsw_status_t fsw_dnode_fill(struct fsw_dnode *dno)
|
|
{
|
|
// TODO: check a flag right here, call fstype's dnode_fill only once per dnode
|
|
|
|
return dno->vol->fstype_table->dnode_fill(dno->vol, dno);
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
|
|
fsw_status_t fsw_dnode_stat(struct fsw_dnode *dno, struct fsw_dnode_stat_str *sb)
|
|
{
|
|
fsw_status_t status;
|
|
|
|
status = fsw_dnode_fill(dno);
|
|
if (status)
|
|
return status;
|
|
|
|
sb->used_bytes = 0;
|
|
status = dno->vol->fstype_table->dnode_stat(dno->vol, dno, sb);
|
|
if (!status && !sb->used_bytes)
|
|
sb->used_bytes = FSW_U64_DIV(dno->size + dno->vol->log_blocksize - 1, dno->vol->log_blocksize);
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Lookup a directory entry by name in directory cache.
|
|
* Given a directory dnode and a file name, it looks up the named entry in the
|
|
* directory cache. If miss, the function calls fstype lookup and cache positive results.
|
|
*
|
|
* If the dnode is not a directory, the call will fail.
|
|
*
|
|
* If the function returns FSW_SUCCESS, *child_dno_out points to the requested directory
|
|
* entry. The caller must call fsw_dnode_release on it.
|
|
*/
|
|
|
|
fsw_status_t fsw_dnode_lookup_cache(struct fsw_dnode *dno,
|
|
struct fsw_string *lookup_name, struct fsw_dnode **child_dno_out)
|
|
{
|
|
fsw_status_t status;
|
|
struct fsw_volume *vol = dno->vol;
|
|
struct fsw_dnode *cache_dno = NULL;
|
|
#if defined(FSW_DNODE_CACHE_SIZE) && FSW_DNODE_CACHE_SIZE > 0
|
|
int i;
|
|
#endif
|
|
|
|
fsw_dnode_retain(dno);
|
|
|
|
// ensure we have full information
|
|
status = fsw_dnode_fill(dno);
|
|
if (status)
|
|
goto errorexit;
|
|
|
|
// make sure we operate on a directory
|
|
if (dno->type != FSW_DNODE_TYPE_DIR) {
|
|
status = FSW_UNSUPPORTED;
|
|
goto errorexit;
|
|
}
|
|
|
|
#if defined(FSW_DNODE_CACHE_SIZE) && FSW_DNODE_CACHE_SIZE > 0
|
|
for (i = 0; i < FSW_DNODE_CACHE_SIZE; i++) {
|
|
struct fsw_dnode *cache_entry = dno->cache[i];
|
|
|
|
if (cache_entry == NULL)
|
|
continue;
|
|
if (fsw_streq(&cache_entry->name, lookup_name)) {
|
|
cache_dno = cache_entry;
|
|
break;
|
|
}
|
|
}
|
|
if (cache_dno != NULL) {
|
|
// move found entry to first slot
|
|
while (i > 0) {
|
|
dno->cache[i] = dno->cache[i - 1];
|
|
i--;
|
|
}
|
|
dno->cache[0] = cache_dno;
|
|
fsw_dnode_retain(cache_dno);
|
|
goto goodexit;
|
|
}
|
|
#endif
|
|
|
|
// Cache miss (or no cache at all). Do real lookup
|
|
status = vol->fstype_table->dir_lookup(vol, dno, lookup_name, &cache_dno);
|
|
if (status)
|
|
goto errorexit;
|
|
|
|
#if defined(FSW_DNODE_CACHE_SIZE) && FSW_DNODE_CACHE_SIZE > 0
|
|
// release dnode pushed out of cache
|
|
i = FSW_DNODE_CACHE_SIZE - 1;
|
|
if (dno->cache[i] != NULL) {
|
|
dno->numcslots--;
|
|
fsw_dnode_release(dno->cache[i]);
|
|
}
|
|
// cache found entry at first slot
|
|
while (i > 0) {
|
|
dno->cache[i] = dno->cache[i - 1];
|
|
i--;
|
|
}
|
|
dno->cache[0] = cache_dno;
|
|
dno->numcslots++;
|
|
fsw_dnode_retain(cache_dno);
|
|
|
|
goodexit:
|
|
#endif
|
|
|
|
fsw_dnode_release(dno);
|
|
*child_dno_out = cache_dno;
|
|
return FSW_SUCCESS;
|
|
|
|
errorexit:
|
|
fsw_dnode_release(dno);
|
|
if (cache_dno != NULL)
|
|
fsw_dnode_release(cache_dno);
|
|
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 symlink it resolved.
|
|
*
|
|
* If the dnode is not a directory, the call will fail.
|
|
*
|
|
* If the function returns FSW_SUCCESS, *child_dno_out points to the requested directory
|
|
* entry. The caller must call fsw_dnode_release on it.
|
|
*/
|
|
|
|
fsw_status_t fsw_dnode_lookup(struct fsw_dnode *dno,
|
|
struct fsw_string *lookup_name,
|
|
struct fsw_dnode **child_dno_out)
|
|
{
|
|
fsw_status_t status;
|
|
struct fsw_dnode *child_dno = NULL;
|
|
|
|
fsw_dnode_retain(dno);
|
|
DBG(" lookup name at dnode type=%d\n", dno->type);
|
|
// ensure we have full information
|
|
status = fsw_dnode_fill(dno);
|
|
if (status)
|
|
goto errorexit;
|
|
|
|
// resolve symlink if necessary
|
|
if (dno->type == FSW_DNODE_TYPE_SYMLINK) {
|
|
DBG("resolve link at core\n");
|
|
status = fsw_dnode_resolve(dno, &child_dno);
|
|
if (status)
|
|
goto errorexit;
|
|
|
|
// symlink target becomes the new dno
|
|
fsw_dnode_release(dno);
|
|
dno = child_dno; // is already retained
|
|
child_dno = NULL;
|
|
|
|
// ensure we have full information
|
|
status = fsw_dnode_fill(dno);
|
|
if (status)
|
|
goto errorexit;
|
|
}
|
|
|
|
// make sure we operate on a directory
|
|
if (dno->type != FSW_DNODE_TYPE_DIR) {
|
|
status = FSW_UNSUPPORTED;
|
|
goto errorexit;
|
|
}
|
|
|
|
// check special paths
|
|
if (fsw_streq_cstr(lookup_name, ".")) { // self directory
|
|
child_dno = dno;
|
|
fsw_dnode_retain(child_dno);
|
|
|
|
} else if (fsw_streq_cstr(lookup_name, "..")) { // parent directory
|
|
if (dno->parent == NULL) {
|
|
// We cannot go up from the root directory. Caution: Certain apps like the EFI shell
|
|
// rely on this behaviour!
|
|
status = FSW_NOT_FOUND;
|
|
DBG("go up root\n");
|
|
goto errorexit;
|
|
}
|
|
child_dno = dno->parent;
|
|
fsw_dnode_retain(child_dno);
|
|
|
|
} else {
|
|
// do an cached actual lookup
|
|
DBG(" dnode_lookup_cache\n");
|
|
status = fsw_dnode_lookup_cache(dno, lookup_name, &child_dno);
|
|
if (status)
|
|
goto errorexit;
|
|
}
|
|
|
|
fsw_dnode_release(dno);
|
|
*child_dno_out = child_dno;
|
|
return FSW_SUCCESS;
|
|
|
|
errorexit:
|
|
DBG(" lookup failed status=%a\n", fsw_errors[status]);
|
|
fsw_dnode_release(dno);
|
|
if (child_dno != NULL)
|
|
fsw_dnode_release(child_dno);
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
|
|
fsw_status_t fsw_dnode_lookup_path(struct fsw_dnode *dno,
|
|
struct fsw_string *lookup_path,
|
|
char separator,
|
|
struct fsw_dnode **child_dno_out)
|
|
{
|
|
fsw_status_t status;
|
|
struct fsw_volume *vol;
|
|
struct fsw_dnode *child_dno = NULL;
|
|
struct fsw_string lookup_name;
|
|
struct fsw_string remaining_path;
|
|
int root_if_empty;
|
|
|
|
if (!dno) {
|
|
return FSW_UNSUPPORTED;
|
|
}
|
|
vol = dno->vol;
|
|
#if DEBUG_CR
|
|
{
|
|
int i;
|
|
fsw_u16 ch;
|
|
|
|
if (lookup_path->type == FSW_STRING_TYPE_ISO88591) {
|
|
DBG("dnode %a lookup ASCII\n", lookup_path->data);
|
|
} else if (lookup_path->type == FSW_STRING_TYPE_UTF16) {
|
|
DBG("dnode lookup UNI:");
|
|
for (i = 0; i < lookup_path->len; i++) {
|
|
ch = ((fsw_u16 *) lookup_path->data)[i];
|
|
DBG("%c", ch?ch:L'@');
|
|
}
|
|
DBG("\n");
|
|
}
|
|
}
|
|
#endif
|
|
// remaining_path = *lookup_path;
|
|
fsw_memcpy(&remaining_path, lookup_path, sizeof(struct fsw_string));
|
|
fsw_dnode_retain(dno);
|
|
DBG("len of remaining_path=%d\n", fsw_strlen(&remaining_path));
|
|
// loop over the path
|
|
for (root_if_empty = 1; fsw_strlen(&remaining_path) > 0; root_if_empty = 0) {
|
|
// parse next path component
|
|
fsw_strsplit(&lookup_name, &remaining_path, separator);
|
|
DBG(" len of lookup_name=%d\n", fsw_strlen(&lookup_name));
|
|
if (fsw_strlen(&lookup_name) == 0) { // empty path component
|
|
if (root_if_empty) {
|
|
child_dno = vol->root;
|
|
DBG("set lookup root\n");
|
|
} else {
|
|
child_dno = dno;
|
|
DBG("set lookup current\n");
|
|
}
|
|
fsw_dnode_retain(child_dno);
|
|
} else {
|
|
// do an actual directory lookup
|
|
status = fsw_dnode_lookup(dno, &lookup_name, &child_dno);
|
|
if (status) {
|
|
DBG("dnode lookup failed %a\n", fsw_errors[status]);
|
|
goto errorexit;
|
|
}
|
|
}
|
|
|
|
// child_dno becomes the new dno
|
|
fsw_dnode_release(dno);
|
|
dno = child_dno; // is already retained
|
|
child_dno = NULL;
|
|
}
|
|
|
|
*child_dno_out = dno;
|
|
return FSW_SUCCESS;
|
|
|
|
errorexit:
|
|
fsw_dnode_release(dno);
|
|
if (child_dno != NULL)
|
|
fsw_dnode_release(child_dno);
|
|
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.
|
|
*/
|
|
|
|
fsw_status_t fsw_dnode_dir_read(struct fsw_shandle *shand, struct fsw_dnode **child_dno_out)
|
|
{
|
|
fsw_status_t status;
|
|
struct fsw_dnode *dno;
|
|
fsw_u64 saved_pos;
|
|
if (!shand) {
|
|
return FSW_UNSUPPORTED;
|
|
}
|
|
dno = shand->dnode;
|
|
if (!dno || dno->type != FSW_DNODE_TYPE_DIR)
|
|
return FSW_UNSUPPORTED;
|
|
|
|
saved_pos = shand->pos;
|
|
DBG("core dir_read at id=%d\n", dno->dnode_id);
|
|
status = dno->vol->fstype_table->dir_read(dno->vol, dno, shand, child_dno_out);
|
|
if (status)
|
|
shand->pos = saved_pos;
|
|
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.
|
|
*/
|
|
|
|
fsw_status_t fsw_dnode_readlink(struct fsw_dnode *dno, struct fsw_string *target_name)
|
|
{
|
|
fsw_status_t status;
|
|
|
|
status = fsw_dnode_fill(dno);
|
|
if (status)
|
|
return status;
|
|
if (dno->type != FSW_DNODE_TYPE_SYMLINK)
|
|
return FSW_UNSUPPORTED;
|
|
DBG("core readlink at id=%d\n", dno->dnode_id);
|
|
return dno->vol->fstype_table->readlink(dno->vol, dno, target_name);
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
|
|
fsw_status_t fsw_dnode_readlink_data_(struct fsw_dnode *dno, struct fsw_string *link_target)
|
|
{
|
|
fsw_status_t status;
|
|
struct fsw_shandle shand;
|
|
fsw_u32 buffer_size;
|
|
char buffer[FSW_PATH_MAX];
|
|
|
|
struct fsw_string s;
|
|
|
|
if (dno == NULL || dno->size > FSW_PATH_MAX)
|
|
return FSW_VOLUME_CORRUPTED;
|
|
|
|
s.type = FSW_STRING_TYPE_ISO88591;
|
|
s.size = s.len = (int)dno->size;
|
|
s.data = buffer;
|
|
|
|
// open shandle and read the data
|
|
status = fsw_shandle_open(dno, &shand);
|
|
if (status)
|
|
return status;
|
|
buffer_size = (fsw_u32)s.size;
|
|
status = fsw_shandle_read(&shand, &buffer_size, buffer);
|
|
fsw_shandle_close(&shand);
|
|
if (status)
|
|
return status;
|
|
if ((int)buffer_size < s.size)
|
|
return FSW_VOLUME_CORRUPTED;
|
|
|
|
if (dno->vol == NULL)
|
|
return FSW_VOLUME_CORRUPTED;
|
|
status = fsw_strdup_coerce(link_target, dno->vol->host_string_type, &s);
|
|
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.
|
|
*/
|
|
|
|
fsw_status_t fsw_dnode_resolve(struct fsw_dnode *dno, struct fsw_dnode **target_dno_out)
|
|
{
|
|
fsw_status_t status;
|
|
struct fsw_string target_name;
|
|
struct fsw_dnode *target_dno;
|
|
|
|
fsw_dnode_retain(dno);
|
|
|
|
while (1) {
|
|
// get full information
|
|
status = fsw_dnode_fill(dno);
|
|
if (status)
|
|
goto errorexit;
|
|
if (dno->type != FSW_DNODE_TYPE_SYMLINK) {
|
|
// found a non-symlink target, return it
|
|
*target_dno_out = dno;
|
|
return FSW_SUCCESS;
|
|
}
|
|
if (dno->parent == NULL) { // safety measure, cannot happen in theory
|
|
status = FSW_NOT_FOUND;
|
|
goto errorexit;
|
|
}
|
|
|
|
// read the link's target
|
|
status = fsw_dnode_readlink(dno, &target_name);
|
|
if (status)
|
|
goto errorexit;
|
|
|
|
// resolve it
|
|
status = fsw_dnode_lookup_path(dno->parent, &target_name, '/', &target_dno);
|
|
// status = fsw_dnode_lookup_path(dno->parent, &target_name, '\\', &target_dno);
|
|
fsw_strfree(&target_name);
|
|
if (status)
|
|
goto errorexit;
|
|
|
|
// target_dno becomes the new dno
|
|
fsw_dnode_release(dno);
|
|
dno = target_dno; // is already retained
|
|
}
|
|
|
|
errorexit:
|
|
fsw_dnode_release(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.
|
|
*/
|
|
|
|
fsw_status_t fsw_shandle_open_(struct fsw_dnode *dno, struct fsw_shandle *shand)
|
|
{
|
|
fsw_status_t status;
|
|
struct fsw_volume *vol;
|
|
if (!dno || !dno->vol) {
|
|
return FSW_VOLUME_CORRUPTED;
|
|
}
|
|
vol = dno->vol;
|
|
|
|
// read full dnode information into memory
|
|
status = vol->fstype_table->dnode_fill(vol, dno);
|
|
if (status)
|
|
return status;
|
|
|
|
// setup shandle
|
|
fsw_dnode_retain(dno);
|
|
|
|
shand->dnode = dno;
|
|
shand->pos = 0;
|
|
shand->extent.type = FSW_EXTENT_TYPE_INVALID;
|
|
|
|
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.
|
|
*/
|
|
|
|
void fsw_shandle_close(struct fsw_shandle *shand)
|
|
{
|
|
if (shand->extent.type == FSW_EXTENT_TYPE_BUFFER) {
|
|
fsw_free(shand->extent.buffer);
|
|
}
|
|
fsw_dnode_release(shand->dnode);
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
fsw_status_t status;
|
|
struct fsw_dnode *dno = shand->dnode;
|
|
struct fsw_volume *vol = dno->vol;
|
|
fsw_u8 *buffer, *block_buffer;
|
|
fsw_u32 buflen, copylen, pos;
|
|
fsw_u32 log_bno, pos_in_extent, phys_bno, pos_in_physblock;
|
|
fsw_u32 cache_level;
|
|
|
|
if (shand->pos >= dno->size) { // already at EOF
|
|
*buffer_size_inout = 0;
|
|
return FSW_SUCCESS;
|
|
}
|
|
|
|
// initialize vars
|
|
buffer = buffer_in;
|
|
buflen = *buffer_size_inout;
|
|
pos = (fsw_u32)shand->pos;
|
|
cache_level = (dno->type != FSW_DNODE_TYPE_FILE) ? 1 : 0;
|
|
// restrict read to file size
|
|
if (buflen > dno->size - pos)
|
|
buflen = (fsw_u32)(dno->size - pos);
|
|
|
|
while (buflen > 0) {
|
|
// get extent for the current logical block
|
|
log_bno = pos / vol->log_blocksize;
|
|
if (shand->extent.type == FSW_EXTENT_TYPE_INVALID ||
|
|
log_bno < shand->extent.log_start ||
|
|
log_bno >= shand->extent.log_start + shand->extent.log_count) {
|
|
|
|
if (shand->extent.type == FSW_EXTENT_TYPE_BUFFER)
|
|
fsw_free(shand->extent.buffer);
|
|
|
|
// ask the file system for the proper extent
|
|
shand->extent.log_start = log_bno;
|
|
status = vol->fstype_table->get_extent(vol, dno, &shand->extent);
|
|
if (status) {
|
|
shand->extent.type = FSW_EXTENT_TYPE_INVALID;
|
|
return status;
|
|
}
|
|
}
|
|
|
|
pos_in_extent = pos - shand->extent.log_start * vol->log_blocksize;
|
|
|
|
// dispatch by extent type
|
|
if (shand->extent.type == FSW_EXTENT_TYPE_PHYSBLOCK) {
|
|
// convert to physical block number and offset
|
|
phys_bno = shand->extent.phys_start + pos_in_extent / vol->phys_blocksize;
|
|
pos_in_physblock = pos_in_extent & (vol->phys_blocksize - 1);
|
|
copylen = vol->phys_blocksize - pos_in_physblock;
|
|
if (copylen > buflen)
|
|
copylen = buflen;
|
|
|
|
// get one physical block
|
|
status = fsw_block_get(vol, phys_bno, cache_level, (void **)&block_buffer);
|
|
if (status)
|
|
return status;
|
|
|
|
// copy data from it
|
|
fsw_memcpy(buffer, block_buffer + pos_in_physblock, copylen);
|
|
fsw_block_release(vol, phys_bno, block_buffer);
|
|
|
|
} else if (shand->extent.type == FSW_EXTENT_TYPE_BUFFER) {
|
|
copylen = shand->extent.log_count * vol->log_blocksize - pos_in_extent;
|
|
if (copylen > buflen)
|
|
copylen = buflen;
|
|
fsw_memcpy(buffer, (fsw_u8 *)shand->extent.buffer + pos_in_extent, copylen);
|
|
|
|
} else { // _SPARSE or _INVALID
|
|
copylen = shand->extent.log_count * vol->log_blocksize - pos_in_extent;
|
|
if (copylen > buflen)
|
|
copylen = buflen;
|
|
fsw_memzero(buffer, copylen);
|
|
|
|
}
|
|
|
|
buffer += copylen;
|
|
buflen -= copylen;
|
|
pos += copylen;
|
|
}
|
|
|
|
*buffer_size_inout = (fsw_u32)(pos - shand->pos);
|
|
shand->pos = pos;
|
|
|
|
return FSW_SUCCESS;
|
|
}
|
|
|
|
// EOF
|