first commit for chrg

This commit is contained in:
wmano
2025-08-16 22:58:22 +08:00
commit 52a3ed5862
2306 changed files with 1021208 additions and 0 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,515 @@
/*
* Copyright (c) 2006-2025 RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2022-10-10 Bernard The first version of rewrite dfs
*/
#include <rtthread.h>
#include "dfs.h"
#include "dfs_file.h"
#include "dfs_private.h"
#include "dfs_dentry.h"
#include "dfs_mnt.h"
#define DBG_TAG "DFS.dentry"
#define DBG_LVL DBG_WARNING
#include <rtdbg.h>
#define DFS_DENTRY_HASH_NR 32
struct dentry_hash_head
{
rt_list_t head[DFS_DENTRY_HASH_NR];
};
static struct dentry_hash_head hash_head;
/**
* @brief Calculate hash value for a dentry based on mount point and path
*
* @param[in] mnt Pointer to the mount point structure
* @param[in] path Path string to be hashed (can be NULL)
*
* @return uint32_t Calculated hash value within range [0, DFS_DENTRY_HASH_NR-1]
*/
static uint32_t _dentry_hash(struct dfs_mnt *mnt, const char *path)
{
uint32_t val = 0;
if (path)
{
while (*path)
{
val = ((val << 5) + val) + *path++;
}
}
return (val ^ (unsigned long) mnt) & (DFS_DENTRY_HASH_NR - 1);
}
/**
* @brief Create a new directory entry (dentry) structure
*
* @param[in] mnt Pointer to the mount point structure
* @param[in] path Path string for the dentry (absolute or relative)
* @param[in] is_rela_path Flag indicating if path is relative (RT_TRUE) or absolute (RT_FALSE)
*
* @return struct dfs_dentry* Pointer to newly created dentry, or NULL if creation failed
*
* @note The created dentry will have its ref_count initialized to 1 and DENTRY_IS_ALLOCED flag set
*/
static struct dfs_dentry *_dentry_create(struct dfs_mnt *mnt, char *path, rt_bool_t is_rela_path)
{
struct dfs_dentry *dentry = RT_NULL;
if (mnt == RT_NULL || path == RT_NULL)
{
return dentry;
}
dentry = (struct dfs_dentry *)rt_calloc(1, sizeof(struct dfs_dentry));
if (dentry)
{
char *dentry_path = path;
if (!is_rela_path)
{
int mntpoint_len = strlen(mnt->fullpath);
if (rt_strncmp(mnt->fullpath, dentry_path, mntpoint_len) == 0)
{
dentry_path += mntpoint_len;
}
}
dentry->pathname = strlen(dentry_path) ? rt_strdup(dentry_path) : rt_strdup(path);
dentry->mnt = dfs_mnt_ref(mnt);
rt_atomic_store(&(dentry->ref_count), 1);
dentry->flags |= DENTRY_IS_ALLOCED;
LOG_I("create a dentry:%p for %s", dentry, mnt->fullpath);
}
return dentry;
}
/**
* @brief Create a new directory entry (dentry) with absolute path
*
* @param[in] mnt Pointer to the mount point structure
* @param[in] fullpath Absolute path string for the dentry
*
* @return struct dfs_dentry* Pointer to newly created dentry, or NULL if creation failed
*
* @note This is a wrapper for _dentry_create() with is_rela_path set to RT_FALSE
* @see _dentry_create()
*/
struct dfs_dentry *dfs_dentry_create(struct dfs_mnt *mnt, char *fullpath)
{
return _dentry_create(mnt, fullpath, RT_FALSE);
}
/**
* @brief Create a new directory entry (dentry) with relative path
*
* @param[in] mnt Pointer to the mount point structure
* @param[in] rela_path Relative path string for the dentry
*
* @return struct dfs_dentry* Pointer to newly created dentry, or NULL if creation failed
*
* @note This is a wrapper for _dentry_create() with is_rela_path set to RT_TRUE
* @see _dentry_create()
*/
struct dfs_dentry *dfs_dentry_create_rela(struct dfs_mnt *mnt, char *rela_path)
{
return _dentry_create(mnt, rela_path, RT_TRUE);;
}
/**
* @brief Increase reference count for a directory entry (dentry)
*
* @param[in,out] dentry Pointer to the directory entry structure to be referenced
*
* @return struct dfs_dentry* The same dentry pointer that was passed in
*
* @note This function will also increase reference count for associated vnode if exists
*/
struct dfs_dentry * dfs_dentry_ref(struct dfs_dentry *dentry)
{
if (dentry)
{
int ret = dfs_file_lock();
if (ret == RT_EOK)
{
rt_atomic_add(&(dentry->ref_count), 1);
if (dentry->vnode)
{
rt_atomic_add(&(dentry->vnode->ref_count), 1);
}
dfs_file_unlock();
}
}
return dentry;
}
/**
* @brief Decrease reference count for a directory entry (dentry) and free if count reaches zero
*
* @param[in,out] dentry Pointer to the directory entry structure to be unreferenced
*
* @return struct dfs_dentry* The same dentry pointer if ref_count > 0, NULL if freed
*/
struct dfs_dentry *dfs_dentry_unref(struct dfs_dentry *dentry)
{
rt_err_t ret = RT_EOK;
if (dentry)
{
ret = dfs_file_lock();
if (ret == RT_EOK)
{
if (dentry->flags & DENTRY_IS_ALLOCED)
{
rt_atomic_sub(&(dentry->ref_count), 1);
}
if (rt_atomic_load(&(dentry->ref_count)) == 0)
{
DLOG(msg, "dentry", "dentry", DLOG_MSG, "free dentry, ref_count=0");
if (dentry->flags & DENTRY_IS_ADDHASH)
{
rt_list_remove(&dentry->hashlist);
}
/* release vnode */
if (dentry->vnode)
{
dfs_vnode_unref(dentry->vnode);
}
/* release mnt */
DLOG(msg, "dentry", "mnt", DLOG_MSG, "dfs_mnt_unref(dentry->mnt)");
if (dentry->mnt)
{
dfs_mnt_unref(dentry->mnt);
}
dfs_file_unlock();
LOG_I("free a dentry: %p", dentry);
rt_free(dentry->pathname);
rt_free(dentry);
dentry = RT_NULL;
}
else
{
if (dentry->vnode)
{
rt_atomic_sub(&(dentry->vnode->ref_count), 1);
}
dfs_file_unlock();
DLOG(note, "dentry", "dentry ref_count=%d", rt_atomic_load(&(dentry->ref_count)));
}
}
}
return dentry;
}
/**
* @brief Look up a directory entry (dentry) in hash table by mount point and path
*
* @param[in] mnt Pointer to the mount point structure to search for
* @param[in] path Path string to search for
*
* @return struct dfs_dentry* Pointer to found dentry (with increased ref_count), or NULL if not found
*/
static struct dfs_dentry *_dentry_hash_lookup(struct dfs_mnt *mnt, const char *path)
{
rt_err_t ret = RT_EOK;
struct dfs_dentry *entry = RT_NULL;
ret = dfs_file_lock();
if (ret == RT_EOK)
{
rt_list_for_each_entry(entry, &hash_head.head[_dentry_hash(mnt, path)], hashlist)
{
if (entry->mnt == mnt && !strcmp(entry->pathname, path))
{
dfs_dentry_ref(entry);
dfs_file_unlock();
return entry;
}
}
dfs_file_unlock();
}
return RT_NULL;
}
/**
* @brief Insert a directory entry (dentry) into the hash table
*
* @param[in,out] dentry Pointer to the directory entry to be inserted
*/
void dfs_dentry_insert(struct dfs_dentry *dentry)
{
dfs_file_lock();
rt_list_insert_after(&hash_head.head[_dentry_hash(dentry->mnt, dentry->pathname)], &dentry->hashlist);
dentry->flags |= DENTRY_IS_ADDHASH;
dfs_file_unlock();
}
/**
* @brief Look up a directory entry (dentry) in the filesystem
*
* @param[in] mnt Pointer to the mount point structure
* @param[in] path Path string to look up
* @param[in] flags Additional lookup flags (currently unused)
*
* @return struct dfs_dentry* Pointer to found/created dentry (with increased ref_count), or NULL if not found
*
* @note This function first searches for dentry in hash table,
* If not found and filesystem supports lookup operation:
* - Creates new dentry
* - Calls filesystem's lookup operation to get vnode
* - If vnode is successfully obtained, adds dentry to hash table
*/
struct dfs_dentry *dfs_dentry_lookup(struct dfs_mnt *mnt, const char *path, uint32_t flags)
{
struct dfs_dentry *dentry;
struct dfs_vnode *vnode = RT_NULL;
int mntpoint_len = strlen(mnt->fullpath);
if (rt_strncmp(mnt->fullpath, path, mntpoint_len) == 0)
{
path += mntpoint_len;
if ((*path) == '\0')
{
/* root */
path = "/";
}
}
dfs_file_lock();
dentry = _dentry_hash_lookup(mnt, path);
if (!dentry)
{
if (mnt->fs_ops->lookup)
{
DLOG(activate, "dentry");
/* not in hash table, create it */
DLOG(msg, "dentry", "dentry", DLOG_MSG, "dfs_dentry_create_rela(mnt=%s, path=%s)", mnt->fullpath, path);
dentry = dfs_dentry_create_rela(mnt, (char*)path);
if (dentry)
{
DLOG(msg, "dentry", mnt->fs_ops->name, DLOG_MSG, "vnode=fs_ops->lookup(dentry)");
if (dfs_is_mounted(mnt) == 0)
{
vnode = mnt->fs_ops->lookup(dentry);
}
if (vnode)
{
DLOG(msg, mnt->fs_ops->name, "dentry", DLOG_MSG_RET, "return vnode");
dentry->vnode = vnode; /* the refcount of created vnode is 1. no need to reference */
dfs_file_lock();
rt_list_insert_after(&hash_head.head[_dentry_hash(mnt, path)], &dentry->hashlist);
dentry->flags |= DENTRY_IS_ADDHASH;
dfs_file_unlock();
if (dentry->flags & (DENTRY_IS_ALLOCED | DENTRY_IS_ADDHASH)
&& !(dentry->flags & DENTRY_IS_OPENED))
{
rt_err_t ret = dfs_file_lock();
if (ret == RT_EOK)
{
dentry->flags |= DENTRY_IS_OPENED;
dfs_file_unlock();
}
}
}
else
{
DLOG(msg, mnt->fs_ops->name, "dentry", DLOG_MSG_RET, "no dentry");
DLOG(msg, "dentry", "dentry", DLOG_MSG, "dfs_dentry_unref(dentry)");
dfs_dentry_unref(dentry);
dentry = RT_NULL;
}
}
DLOG(deactivate, "dentry");
}
}
else
{
DLOG(note, "dentry", "found dentry");
}
dfs_file_unlock();
return dentry;
}
/**
* @brief Get the full path of a directory entry by combining mount point and relative path
*
* @param[in] dentry Pointer to the directory entry structure
*
* @return char* Newly allocated string containing full path, or NULL if allocation failed
*
* @note The caller is responsible for freeing the returned string using rt_free()
* @note Handles path concatenation with or without additional '/' separator
*/
char* dfs_dentry_full_path(struct dfs_dentry* dentry)
{
char *path = NULL;
if (dentry && dentry->mnt)
{
int mnt_len = strlen(dentry->mnt->fullpath);
int path_len = strlen(dentry->pathname);
path = (char *) rt_malloc(mnt_len + path_len + 3);
if (path)
{
if (dentry->pathname[0] == '/' || dentry->mnt->fullpath[mnt_len - 1] == '/')
{
rt_snprintf(path, mnt_len + path_len + 2, "%s%s", dentry->mnt->fullpath,
dentry->pathname);
}
else
{
rt_snprintf(path, mnt_len + path_len + 2, "%s/%s", dentry->mnt->fullpath,
dentry->pathname);
}
}
}
return path;
}
/**
* @brief Get the parent directory path of a dentry by combining mount point and path
*
* @param[in] dentry Pointer to the directory entry structure
*
* @return char* Newly allocated string containing parent path, or NULL if allocation failed
*
* @note The caller is responsible for freeing the returned string using rt_free()
* @note Handles both absolute and relative paths correctly
* @note Returns mount point path if dentry is at root directory
*/
char* dfs_dentry_pathname(struct dfs_dentry* dentry)
{
char *pathname = RT_NULL;
char *index = RT_NULL;
index = strrchr(dentry->pathname, '/');
if (index)
{
int length = index - dentry->pathname;
int path_length = strlen(dentry->mnt->fullpath) + length + 3;
pathname = (char*) rt_malloc(path_length);
if (pathname)
{
if (dentry->pathname[0] == '/')
{
rt_snprintf(pathname, path_length - 1, "%s%.*s", dentry->mnt->fullpath,
length, dentry->pathname);
}
else
{
rt_snprintf(pathname, path_length - 1, "%s/%.*s", dentry->mnt->fullpath,
length, dentry->pathname);
}
}
}
else
{
pathname = rt_strdup(dentry->mnt->fullpath);
}
return pathname;
}
/**
* @brief Calculate CRC32 checksum for the full path of a directory entry
*
* @param[in] dentry Pointer to the directory entry structure
*
* @return uint32_t CRC32 checksum value of the full path
*
* @note Uses standard CRC32 polynomial 0xEDB88320
*/
uint32_t dfs_dentry_full_path_crc32(struct dfs_dentry* dentry)
{
uint32_t crc32 = 0xFFFFFFFF;
char *fullpath = dfs_dentry_full_path(dentry);
if (fullpath)
{
int i = 0;
while(fullpath[i] != '\0')
{
for (uint8_t b = 1; b; b <<= 1)
{
crc32 ^= (fullpath[i] & b) ? 1 : 0;
crc32 = (crc32 & 1) ? crc32 >> 1 ^ 0xEDB88320 : crc32 >> 1;
}
i ++;
}
rt_free(fullpath);
}
return crc32;
}
/**
* @brief Initialize the dentry hash table
*
* @return int Always returns 0 indicating success
*
* @note Initializes all hash buckets in the dentry hash table
*/
int dfs_dentry_init(void)
{
int i = 0;
for(i = 0; i < DFS_DENTRY_HASH_NR; i++)
{
rt_list_init(&hash_head.head[i]);
}
return 0;
}
/**
* @brief Dump all directory entries in the hash table for debugging
*
* @param[in] argc Number of command line arguments (unused)
* @param[in] argv Array of command line arguments (unused)
*
* @return int Always returns 0 indicating success
*
* @note Prints each dentry's full path, memory address and reference count
*/
int dfs_dentry_dump(int argc, char** argv)
{
int index = 0;
struct dfs_dentry *entry = RT_NULL;
dfs_lock();
for (index = 0; index < DFS_DENTRY_HASH_NR; index ++)
{
rt_list_for_each_entry(entry, &hash_head.head[index], hashlist)
{
printf("dentry: %s%s @ %p, ref_count = %zd\n", entry->mnt->fullpath, entry->pathname, entry, (size_t)rt_atomic_load(&entry->ref_count));
}
}
dfs_unlock();
return 0;
}
MSH_CMD_EXPORT_ALIAS(dfs_dentry_dump, dentry_dump, dump dentry in the system);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,693 @@
/*
* Copyright (c) 2006-2025 RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
*/
#include "dfs_file.h"
#include "dfs_dentry.h"
#include "dfs_mnt.h"
#define DBG_TAG "dfs.mmap"
#define DBG_LVL DBG_WARNING
#include <rtdbg.h>
#if defined(RT_USING_SMART) && defined(ARCH_MM_MMU) && defined(RT_USING_PAGECACHE)
#include "dfs_pcache.h"
#include <lwp.h>
#include <sys/mman.h>
#include <lwp_user_mm.h>
#include <mm_aspace.h>
#include <mm_fault.h>
#include <mm_flag.h>
#include <mm_page.h>
#include <mmu.h>
#include <page.h>
#include <tlb.h>
static rt_mem_obj_t dfs_get_mem_obj(struct dfs_file *file);
static void *dfs_mem_obj_get_file(rt_mem_obj_t mem_obj);
/**
* @brief Perform memory mapping operation
*
* @param[in] lwp Pointer to the lightweight process structure
* @param[in] map_vaddr Requested virtual address for mapping (may be NULL)
* @param[in] map_size Size of the memory region to map
* @param[in] attr Memory attributes for the mapping
* @param[in] flags Memory mapping flags
* @param[in] pgoffset Offset in pages from the start of the memory object
* @param[in] data Pointer to the file descriptor to be mapped
* @param[out] code Pointer to store the operation result code
*
* @return void* The mapped virtual address on success, NULL on failure
*
* @note This is a low-level mapping function that interacts directly with the address space manager.
* The actual mapping is performed by rt_aspace_map().
*/
static void *_do_mmap(struct rt_lwp *lwp, void *map_vaddr, size_t map_size, size_t attr,
mm_flag_t flags, off_t pgoffset, void *data, rt_err_t *code)
{
int ret = 0;
void *vaddr = map_vaddr;
rt_mem_obj_t mem_obj = dfs_get_mem_obj(data);
ret = rt_aspace_map(lwp->aspace, &vaddr, map_size,
attr, flags, mem_obj, pgoffset);
if (ret != RT_EOK)
{
vaddr = RT_NULL;
LOG_E("failed to map %lx with size %lx with errno %d", map_vaddr,
map_size, ret);
}
if (code)
{
*code = ret;
}
return vaddr;
}
/**
* @brief Map data to user space address
*
* @param[in,out] mmap2 Pointer to memory mapping arguments structure
* - Input: Contains mapping parameters (addr, length, etc.)
* - Output: Contains the mapped address in ret field if successful
* @param[in] data Pointer to the file descriptor to be mapped
* @param[out] code Pointer to store the error code if mapping fails
*
* @return void* The mapped virtual address on success, NULL on failure
*
* @note This function performs page alignment on the mapping parameters and
* converts user-space flags/attributes to kernel-space before mapping.
*/
static void *_map_data_to_uspace(struct dfs_mmap2_args *mmap2, void *data, rt_err_t *code)
{
size_t offset = 0;
void *map_vaddr = mmap2->addr;
size_t map_size = mmap2->length;
struct rt_lwp *lwp = mmap2->lwp;
rt_size_t k_attr;
rt_size_t k_flags;
if (map_size)
{
offset = (size_t)map_vaddr & ARCH_PAGE_MASK;
map_size += (offset + ARCH_PAGE_SIZE - 1);
map_size &= ~ARCH_PAGE_MASK;
map_vaddr = (void *)((size_t)map_vaddr & ~ARCH_PAGE_MASK);
k_flags = lwp_user_mm_flag_to_kernel(mmap2->flags);
k_flags = MMF_CREATE(k_flags, mmap2->min_align_size);
k_attr = lwp_user_mm_attr_to_kernel(mmap2->prot);
map_vaddr = _do_mmap(lwp, map_vaddr, map_size, k_attr, k_flags, mmap2->pgoffset, data, code);
}
return map_vaddr;
}
static void hint_free(rt_mm_va_hint_t hint)
{
}
/**
* @brief Handle page fault for memory mapped file
*
* @param[in] varea Pointer to the virtual memory area structure
* @param[in,out] msg Pointer to the page fault message structure
* - Input: Contains fault information (fault_vaddr, etc.)
* - Output: Contains response status and mapped page address
*
* @note This function is called when a page fault occurs in a memory mapped file region.
* It attempts to map the faulting page and updates the response accordingly.
*/
static void on_page_fault(struct rt_varea *varea, struct rt_aspace_fault_msg *msg)
{
void *page;
struct dfs_file *file = dfs_mem_obj_get_file(varea->mem_obj);
if (file)
{
LOG_I("%s varea: %p", __func__, varea);
LOG_I("varea start: %p size: 0x%x offset: 0x%x attr: 0x%x flag: 0x%x",
varea->start, varea->size, varea->offset, varea->attr, varea->flag);
LOG_I("fault vaddr: %p", msg->fault_vaddr);
if (file->dentry)
{
LOG_I("file: %s%s", file->dentry->mnt->fullpath, file->dentry->pathname);
}
page = dfs_aspace_mmap(file, varea, msg->fault_vaddr);
if (page)
{
msg->response.status = MM_FAULT_STATUS_OK_MAPPED;
msg->response.size = ARCH_PAGE_SIZE;
msg->response.vaddr = page;
}
else
{
LOG_E("%s varea %p mmap failed at vaddr %p", __func__, varea, msg->fault_vaddr);
}
}
else
{
LOG_E("%s varea %p not a file, vaddr %p", __func__, varea, varea->start);
}
}
/**
* @brief Handle virtual memory area opening event
*
* @param[in] varea Pointer to the virtual memory area structure
*
* @note This function is called when a virtual memory area is opened.
* It increments the reference count of the associated file and
* initializes varea->data to NULL.
*/
static void on_varea_open(struct rt_varea *varea)
{
struct dfs_file *file = dfs_mem_obj_get_file(varea->mem_obj);
varea->data = RT_NULL;
rt_atomic_add(&(file->ref_count), 1);
}
/**
* @brief Handle virtual memory area closing event
*
* @param[in] varea Pointer to the virtual memory area structure
*
* @note This function is called when a virtual memory area is closed.
* It performs cleanup operations including:
* - Unmapping the file from memory
* - Decrementing file reference count
* - Closing and destroying file if reference count reaches zero
*/
static void on_varea_close(struct rt_varea *varea)
{
struct dfs_file *file = dfs_mem_obj_get_file(varea->mem_obj);
if (file)
{
LOG_I("%s varea: %p", __func__, varea);
LOG_I("varea start: %p size: 0x%x offset: 0x%x attr: 0x%x flag: 0x%x",
varea->start, varea->size, varea->offset, varea->attr, varea->flag);
if (file->dentry)
{
LOG_I("file: %s%s", file->dentry->mnt->fullpath, file->dentry->pathname);
}
dfs_aspace_unmap(file, varea);
dfs_file_lock();
if (rt_atomic_load(&(file->ref_count)) == 1)
{
dfs_file_close(file);
dfs_file_destroy(file);
}
else
{
rt_atomic_sub(&(file->ref_count), 1);
}
dfs_file_unlock();
}
else
{
LOG_E("%s varea %p not a file, vaddr %p", __func__, varea, varea->start);
}
}
/**
* @brief Get the name of the memory mapped file
*
* @param[in] varea Pointer to the virtual memory area structure
*
* @return const char* The name of the mapped file if available,
* otherwise returns "file-mapper" as default name
*
* @note This function retrieves the file name from the dentry structure
* associated with the memory mapped file.
*/
static const char *get_name(rt_varea_t varea)
{
struct dfs_file *file = dfs_mem_obj_get_file(varea->mem_obj);
return (file && file->dentry) ? file->dentry->pathname : "file-mapper";
}
/**
* @brief Read data from memory mapped file page
*
* @param[in] varea Pointer to the virtual memory area structure
* @param[in,out] msg Pointer to the I/O message structure
* - Input: Contains read request information
* - Output: Contains response status and read data
*
* @note This function handles page read operations for memory mapped files.
* If the read size is less than page size, it zero-fills the remaining space.
*/
void page_read(struct rt_varea *varea, struct rt_aspace_io_msg *msg)
{
rt_ubase_t ret;
struct dfs_file *file = dfs_mem_obj_get_file(varea->mem_obj);
if (file)
{
LOG_I("%s varea: %p", __func__, varea);
LOG_I("varea start: %p size: 0x%x offset: 0x%x attr: 0x%x flag: 0x%x",
varea->start, varea->size, varea->offset, varea->attr, varea->flag);
ret = dfs_aspace_mmap_read(file, varea, msg);
if (ret >= 0)
{
msg->response.status = MM_FAULT_STATUS_OK;
if (ret < ARCH_PAGE_SIZE)
{
memset((char *)msg->buffer_vaddr + ret, 0, ARCH_PAGE_SIZE - ret);
}
}
}
else
{
LOG_E("%s varea %p not a file, vaddr %p", __func__, varea, varea->start);
}
}
/**
* @brief Write data to memory mapped file page
*
* @param[in] varea Pointer to the virtual memory area structure
* @param[in,out] msg Pointer to the I/O message structure
* - Input: Contains write request information
* - Output: Contains response status and write result
*
* @note This function handles page write operations for memory mapped files.
* If the write size is less than page size, it zero-fills the remaining space.
*/
void page_write(struct rt_varea *varea, struct rt_aspace_io_msg *msg)
{
rt_ubase_t ret;
struct dfs_file *file = dfs_mem_obj_get_file(varea->mem_obj);
if (file)
{
LOG_I("%s varea: %p", __func__, varea);
LOG_I("varea start: %p size: 0x%x offset: 0x%x attr: 0x%x flag: 0x%x",
varea->start, varea->size, varea->offset, varea->attr, varea->flag);
ret = dfs_aspace_mmap_write(file, varea, msg);
if (ret > 0)
{
msg->response.status = MM_FAULT_STATUS_OK;
if (ret < ARCH_PAGE_SIZE)
{
memset((char *)msg->buffer_vaddr + ret, 0, ARCH_PAGE_SIZE - ret);
}
}
}
else
{
LOG_E("%s varea %p not a file, vaddr %p", __func__, varea, varea->start);
}
}
/**
* @brief Unmap pages from virtual memory area
*
* @param[in] varea Pointer to the virtual memory area structure
* @param[in] rm_start Starting address of the range to unmap (must be page aligned)
* @param[in] rm_end Ending address of the range to unmap (must be page aligned)
*
* @return rt_err_t Error code:
* - RT_EOK: Success
* - -RT_ERROR: Failure (varea not associated with a file)
*
* @note This function performs page-by-page unmapping.
* Both rm_start and rm_end must be page-aligned (checked by RT_ASSERT).
*/
static rt_err_t unmap_pages(rt_varea_t varea, void *rm_start, void *rm_end)
{
struct dfs_file *file = dfs_mem_obj_get_file(varea->mem_obj);
if (file)
{
LOG_I("%s varea: %p start: %p end: %p", __func__, varea, rm_start, rm_end);
RT_ASSERT(!((rt_ubase_t)rm_start & ARCH_PAGE_MASK));
RT_ASSERT(!((rt_ubase_t)rm_end & ARCH_PAGE_MASK));
while (rm_start != rm_end)
{
dfs_aspace_page_unmap(file, varea, rm_start);
rm_start += ARCH_PAGE_SIZE;
}
return RT_EOK;
}
else
{
LOG_E("%s varea %p not a file, vaddr %p", __func__, varea, varea->start);
}
return -RT_ERROR;
}
/**
* @brief Handle virtual memory area shrinking operation
*
* @param[in] varea Pointer to the virtual memory area structure
* @param[in] new_vaddr New starting address after shrinking
* @param[in] size New size of the virtual memory area
*
* @return rt_err_t Error code:
* - RT_EOK: Success
* - Other errors from unmap_pages()
*
* @note This function determines the range of pages to unmap based on whether
* the varea is shrinking from the start or end.
*/
rt_err_t on_varea_shrink(struct rt_varea *varea, void *new_vaddr, rt_size_t size)
{
char *varea_start = varea->start;
void *rm_start;
void *rm_end;
LOG_I("%s varea: %p", __func__, varea);
LOG_I("varea start: %p size: 0x%x offset: 0x%x attr: 0x%x flag: 0x%x",
varea->start, varea->size, varea->offset, varea->attr, varea->flag);
LOG_I("new_vaddr: %p size: %p", new_vaddr, size);
if (varea_start == (char *)new_vaddr)
{
rm_start = varea_start + size;
rm_end = varea_start + varea->size;
}
else
{
rm_start = varea_start;
rm_end = new_vaddr;
}
return unmap_pages(varea, rm_start, rm_end);
}
/**
* @brief Handle virtual memory area expansion operation
*
* @param[in] varea Pointer to the virtual memory area structure
* @param[in] new_vaddr New starting address after expansion
* @param[in] size New size of the expanded virtual memory area
*
* @return rt_err_t returns RT_EOK (success).
*
* @note This function is currently not implemented.
*/
rt_err_t on_varea_expand(struct rt_varea *varea, void *new_vaddr, rt_size_t size)
{
LOG_I("%s varea: %p", __func__, varea);
LOG_I("varea start: %p size: 0x%x offset: 0x%x attr: 0x%x flag: 0x%x",
varea->start, varea->size, varea->offset, varea->attr, varea->flag);
LOG_I("new_vaddr: %p size: %p", new_vaddr, size);
return RT_EOK;
}
/**
* @brief Handle virtual memory area splitting operation
*
* @param[in] existed Pointer to the existing virtual memory area to be split
* @param[in] unmap_start Starting address of the range to unmap
* @param[in] unmap_len Length of the range to unmap
* @param[in,out] subset Pointer to the new subset virtual memory area
* - Input: Contains new varea parameters
* - Output: Contains initialized varea after splitting
*
* @return rt_err_t Error code:
* - RT_EOK: Success
* - -RT_ERROR: Failure (varea not associated with a file)
*
* @note This function splits an existing virtual memory area into two parts.
* It unmaps the specified range and initializes the new subset area.
*/
rt_err_t on_varea_split(struct rt_varea *existed, void *unmap_start, rt_size_t unmap_len, struct rt_varea *subset)
{
rt_err_t rc;
struct dfs_file *file = dfs_mem_obj_get_file(existed->mem_obj);
if (file)
{
LOG_I("%s varea: %p", __func__, existed);
LOG_I("varea start: %p size: 0x%x offset: 0x%x attr: 0x%x flag: 0x%x",
existed->start, existed->size, existed->offset, existed->attr, existed->flag);
LOG_I("unmap_start: %p unmap_len: %p", unmap_start, unmap_len);
if (file->dentry)
{
LOG_I("file: %s%s", file->dentry->mnt->fullpath, file->dentry->pathname);
}
rc = unmap_pages(existed, unmap_start, (char *)unmap_start + unmap_len);
if (!rc)
{
rc = unmap_pages(existed, subset->start, (char *)subset->start + subset->size);
if (!rc)
on_varea_open(subset);
}
return rc;
}
else
{
LOG_E("%s varea %p not a file, vaddr %p", __func__, existed, existed->start);
}
return -RT_ERROR;
}
/**
* @brief Handle virtual memory area merging operation
*
* @param[in] merge_to Pointer to the target virtual memory area that will receive the merge
* @param[in] merge_from Pointer to the source virtual memory area to be merged
*
* @return rt_err_t Error code:
* - RT_EOK: Success
* - -RT_ERROR: Failure (varea not associated with a file)
*/
rt_err_t on_varea_merge(struct rt_varea *merge_to, struct rt_varea *merge_from)
{
struct dfs_file *file = dfs_mem_obj_get_file(merge_from->mem_obj);
if (file)
{
LOG_I("%s varea: %p", __func__, merge_from);
LOG_I("varea start: %p size: 0x%x offset: 0x%x attr: 0x%x flag: 0x%x",
merge_from->start, merge_from->size, merge_from->offset, merge_from->attr, merge_from->flag);
if (file->dentry)
{
LOG_I("file: %s%s", file->dentry->mnt->fullpath, file->dentry->pathname);
}
dfs_aspace_unmap(file, merge_from);
on_varea_close(merge_from);
return RT_EOK;
}
else
{
LOG_E("%s varea %p not a file, vaddr %p", __func__, merge_from, merge_from->start);
}
return -RT_ERROR;
}
/**
* @brief Handle virtual memory area remapping operation
*
* @param[in] varea Pointer to the virtual memory area structure
* @param[in] new_size New size of the virtual memory area after remapping
* @param[in] flags Remapping flags (e.g., MREMAP_MAYMOVE)
* @param[in] new_address New starting address after remapping (optional)
*
* @return void* Pointer to the new virtual memory area after remapping
* - Returns RT_NULL if remapping fails
*
* @note This function remaps a virtual memory area to a new address or size.
* It currently supports the MREMAP_MAYMOVE flag.
*/
void *on_varea_mremap(struct rt_varea *varea, rt_size_t new_size, int flags, void *new_address)
{
void *vaddr = RT_NULL;
struct dfs_file *file = dfs_mem_obj_get_file(varea->mem_obj);
#ifndef MREMAP_MAYMOVE
#define MREMAP_MAYMOVE 1
#endif
if (file && flags == MREMAP_MAYMOVE)
{
int ret;
rt_mem_obj_t mem_obj = dfs_get_mem_obj(file);
vaddr = new_address ? new_address : varea->start;
new_size = (new_size + ARCH_PAGE_SIZE - 1);
new_size &= ~ARCH_PAGE_MASK;
ret = rt_aspace_map(varea->aspace, &vaddr, new_size, varea->attr, varea->flag, mem_obj, varea->offset);
if (ret != RT_EOK)
{
LOG_E("failed to map %lx with size %lx with errno %d", vaddr, new_size, ret);
vaddr = RT_NULL;
}
else
{
LOG_I("old: %p size: %p new: %p size: %p", varea->start, varea->size, vaddr, new_size);
}
}
return vaddr;
}
/**
* @brief Memory object operations structure
*
* Defines function pointers for various virtual memory area (varea) operations,
* including memory management, page fault handling, and lifecycle callbacks.
*/
static struct rt_mem_obj _mem_obj =
{
.hint_free = hint_free, /* Free memory hint function */
.on_page_fault = on_page_fault, /* Page fault handler */
.on_varea_open = on_varea_open, /* Varea open callback */
.on_varea_close = on_varea_close, /* Varea close callback */
.get_name = get_name, /* Get mapped file name */
.page_read = page_read, /* Page read operation */
.page_write = page_write, /* Page write operation */
.on_varea_shrink = on_varea_shrink, /* Varea shrink handler */
.on_varea_expand = on_varea_expand, /* Varea expand handler */
.on_varea_split = on_varea_split, /* Varea split handler */
.on_varea_merge = on_varea_merge, /* Varea merge handler */
.on_varea_mremap = on_varea_mremap, /* Varea remap handler */
};
/**
* @brief DFS memory object structure
*
* Contains a standard memory object and an associated file pointer,
* used to maintain the relationship between memory mappings and files.
*/
struct dfs_mem_obj {
struct rt_mem_obj mem_obj; /* Base memory object */
void *file; /* Associated file pointer */
};
/**
* @brief Get or create memory mapping object for a file
*
* @param[in] file Pointer to the file descriptor structure
*
* @return rt_mem_obj_t Memory mapping object associated with the file
* - Returns existing object if already created
* - Creates and initializes new object if not exists
*/
static rt_mem_obj_t dfs_get_mem_obj(struct dfs_file *file)
{
rt_mem_obj_t mobj = file->mmap_context;
if (!mobj)
{
struct dfs_mem_obj *dfs_mobj;
dfs_file_lock();
dfs_mobj = rt_malloc(sizeof(*dfs_mobj));
if (dfs_mobj)
{
dfs_mobj->file = file;
mobj = &dfs_mobj->mem_obj;
memcpy(mobj, &_mem_obj, sizeof(*mobj));
file->mmap_context = mobj;
}
dfs_file_unlock();
}
return mobj;
}
/**
* @brief Get the file descriptor from memory mapping object
*
* @param[in] mem_obj Pointer to the memory mapping object
*
* @return void* Pointer to the associated file descriptor structure
*
* @note This function uses rt_container_of macro to get the containing
* dfs_mem_obj structure from its mem_obj member.
*/
static void *dfs_mem_obj_get_file(rt_mem_obj_t mem_obj)
{
struct dfs_mem_obj *dfs_mobj;
dfs_mobj = rt_container_of(mem_obj, struct dfs_mem_obj, mem_obj);
return dfs_mobj->file;
}
/**
* @brief Map a file into memory
*
* @param[in] file Pointer to the file descriptor structure
* @param[in,out] mmap2 Pointer to memory mapping arguments structure
* - Input: Contains mapping parameters (addr, length, etc.)
* - Output: Contains the mapped address in ret field if successful
*
* @return int Error code:
* - EINVAL: Invalid parameters
* - Other errors from underlying mapping operations
*
* @note This function creates a virtual address area in user space (lwp) for the file mapping.
* The actual mapping is performed by _map_data_to_uspace().
*/
int dfs_file_mmap(struct dfs_file *file, struct dfs_mmap2_args *mmap2)
{
rt_err_t ret = -EINVAL;
void *map_vaddr;
LOG_I("mmap2 args addr: %p length: 0x%x prot: %d flags: 0x%x pgoffset: 0x%x",
mmap2->addr, mmap2->length, mmap2->prot, mmap2->flags, mmap2->pgoffset);
if (file && file->vnode)
{
if (file->vnode->aspace)
{
/* create a va area in user space (lwp) */
map_vaddr = _map_data_to_uspace(mmap2, file, &ret);
if (map_vaddr)
{
mmap2->ret = map_vaddr;
LOG_I("file: %s%s", file->dentry->mnt->fullpath, file->dentry->pathname);
}
}
else
{
LOG_E("File mapping is not supported, file: %s%s", file->dentry->mnt->fullpath, file->dentry->pathname);
}
}
return ret;
}
#else
int dfs_file_mmap(struct dfs_file *file, struct dfs_mmap2_args *mmap2)
{
LOG_E("File mapping support is not enabled, file: %s%s", file->dentry->mnt->fullpath, file->dentry->pathname);
LOG_E("mmap2 args addr: %p length: 0x%x prot: %d flags: 0x%x pgoffset: 0x%x",
mmap2->addr, mmap2->length, mmap2->prot, mmap2->flags, mmap2->pgoffset);
return -EPERM;
}
#endif
@@ -0,0 +1,726 @@
/*
* Copyright (c) 2006-2025 RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2005-02-22 Bernard The first version.
* 2010-06-30 Bernard Optimize for RT-Thread RTOS
* 2011-03-12 Bernard fix the filesystem lookup issue.
* 2017-11-30 Bernard fix the filesystem_operation_table issue.
* 2017-12-05 Bernard fix the fs type search issue in mkfs.
* 2023-05-05 Bernard change to dfs v2.0
*/
#include <dfs_fs.h>
#include <dfs_file.h>
#include <dfs_dentry.h>
#include <dfs_mnt.h>
#include "dfs_private.h"
#ifdef RT_USING_PAGECACHE
#include "dfs_pcache.h"
#endif
#define DBG_TAG "DFS.fs"
#define DBG_LVL DBG_INFO
#include <rtdbg.h>
static struct dfs_filesystem_type *file_systems = NULL;
extern rt_list_t _mnt_list;
/**
* @addtogroup group_fs_api
*/
/*@{*/
/**
* @brief Find a filesystem type by name
*
* This function searches the global filesystem type list for a filesystem
* matching the given name. It returns a pointer to the pointer that holds
* the matching filesystem type (or the end-of-list pointer if not found).
*
* @param[in] name The name of the filesystem type to find
* @return struct dfs_filesystem_type** Pointer to the pointer containing
* the matching filesystem type, or the end-of-list pointer if not found
*/
static struct dfs_filesystem_type **_find_filesystem(const char *name)
{
struct dfs_filesystem_type **type;
for (type = &file_systems; *type; type = &(*type)->next)
{
if (strcmp((*type)->fs_ops->name, name) == 0)
break;
}
return type;
}
/**
* @brief Get the list of registered filesystem types
*
* This function returns a pointer to the head of the global filesystem type list.
*
* @return struct dfs_filesystem_type* Pointer to the head of the filesystem type list
*/
struct dfs_filesystem_type *dfs_filesystems(void)
{
return file_systems;
}
/**
* @brief Register a filesystem type
*
* This function registers a new filesystem type with the global filesystem type list.
*
* @param[in] fs Pointer to the filesystem type to register
* @return int 0 on success, or a negative error code on failure
*/
int dfs_register(struct dfs_filesystem_type *fs)
{
int ret = 0;
struct dfs_filesystem_type **type = _find_filesystem(fs->fs_ops->name);
LOG_D("register %s file system.", fs->fs_ops->name);
if (*type)
{
ret = -EBUSY;
}
else
{
*type = fs;
}
return ret;
}
/**
* @brief Unregister a filesystem type
*
* This function unregisters a filesystem type from the global filesystem type list.
*
* @param[in] fs Pointer to the filesystem type to unregister
* @return int 0 on success, or a negative error code on failure
*/
int dfs_unregister(struct dfs_filesystem_type *fs)
{
int ret = 0;
struct dfs_filesystem_type **type;
if (fs)
{
LOG_D("unregister %s file system.", fs->fs_ops->name);
for (type = &file_systems; *type; type = &(*type)->next)
{
if (strcmp((*type)->fs_ops->name, fs->fs_ops->name) == 0)
{
*type = (*type)->next;
break;
}
}
if (!*type) ret = -EINVAL;
}
return ret;
}
#define REMNT_UNSUPP_FLAGS (~(MS_REMOUNT | MS_RMT_MASK)) /* remount unsupported flags */
/**
* @brief Remount a filesystem
*
* This function remounts a filesystem at the specified path with the given flags.
*
* @param[in] path The path of the filesystem to remount
* @param[in] flags The remount flags (see MS_REMOUNT and MS_RMT_MASK)
* @param[in] data Pointer to additional data required for remounting
* @return int 0 on success, or a negative error code on failure
*/
int dfs_remount(const char *path, rt_ubase_t flags, void *data)
{
int rc = 0;
char *fullpath = RT_NULL;
struct dfs_mnt *mnt = RT_NULL;
if (flags & REMNT_UNSUPP_FLAGS)
{
return -EINVAL;
}
fullpath = dfs_normalize_path(RT_NULL, path);
if (!fullpath)
{
rc = -ENOENT;
}
else
{
DLOG(msg, "dfs", "mnt", DLOG_MSG, "mnt = dfs_mnt_lookup(%s)", fullpath);
mnt = dfs_mnt_lookup(fullpath);
if (mnt)
{
dfs_lock();
dfs_mnt_setflags(mnt, flags);
dfs_unlock();
}
else
{
struct stat buf = {0};
if (dfs_file_stat(fullpath, &buf) == 0 && S_ISBLK(buf.st_mode))
{
/* path was not already mounted on target */
rc = -EINVAL;
}
else
{
/* path is not a directory */
rc = -ENOTDIR;
}
}
}
return rc;
}
/*
* parent(mount path)
* mnt_parent <- - - - - - - +
* | |
* |- mnt_child <- - - - - -+ (1 refcount)
* | |
* |- parent - - + (1 refcount)
*/
/**
* @brief Mount a filesystem at the specified path
*
* This function mounts a filesystem of the specified type at the given path with optional device.
* It handles both root filesystem mounting and regular filesystem mounting scenarios.
*
* @param[in] device_name The name of the device to mount (optional)
* @param[in] path The path of the mount point
* @param[in] filesystemtype The type of the filesystem to mount
* @param[in] rwflag The read/write flags (see MS_RDONLY, MS_RDWR, etc.)
* @param[in] data Pointer to additional data required for mounting
*
* @return int RT_EOK on success, negative error code on failure:
* - EPERM: Path normalization failed or mount operation failed
* - ENODEV: Filesystem type not found or device not available
* - ENOMEM: Memory allocation failure
* - EIO: Filesystem lacks mount method
* - ENOTDIR: Mount point doesn't exist
* - EEXIST: Mount point already mounted
*
* @note Special handling for root filesystem ("/")
* @note Automatic reference counting management for mount points
*/
int dfs_mount(const char *device_name,
const char *path,
const char *filesystemtype,
unsigned long rwflag,
const void *data)
{
int ret = RT_EOK;
char *fullpath = RT_NULL;
rt_device_t dev_id = RT_NULL;
struct dfs_mnt *mnt_parent = RT_NULL, *mnt_child = RT_NULL;
struct dfs_dentry *mntpoint_dentry = RT_NULL;
struct dfs_filesystem_type *type = *_find_filesystem(filesystemtype);
/* normalize the mount path */
if (type)
{
fullpath = dfs_normalize_path(RT_NULL, path);
if (!fullpath)
{
rt_set_errno(EPERM);
ret = -1;
}
}
else
{
rt_set_errno(ENODEV);
ret = -1;
}
/* Main mounting procedure */
if (fullpath)
{
DLOG(note, "mnt", "mount %s(%s) on path: %s", device_name, filesystemtype, fullpath);
/* open specific device */
if (device_name) dev_id = rt_device_find(device_name);
/* Check device requirements */
if (!(type->fs_ops->flags & FS_NEED_DEVICE) ||
((type->fs_ops->flags & FS_NEED_DEVICE) && dev_id))
{
DLOG(msg, "dfs", "mnt", DLOG_MSG, "mnt_parent = dfs_mnt_lookup(%s)", fullpath);
mnt_parent = dfs_mnt_lookup(fullpath); /* Find parent mount point */
/* Handle root filesystem mounting */
if ((!mnt_parent && (strcmp(fullpath, "/") == 0 || strcmp(fullpath, "/dev") == 0))
|| (mnt_parent && strcmp(fullpath, "/") == 0 && strcmp(mnt_parent->fullpath, fullpath) != 0))
{
LOG_D("no mnt found @ mount point %s, should be root.", fullpath);
DLOG(msg, "mnt", "dfs", DLOG_MSG_RET, "no mnt");
/* it's the root file system */
/* the mount point dentry is the same as root dentry. */
/* Create root filesystem mount point */
DLOG(msg, "dfs", "mnt", DLOG_MSG, "mnt_parent = dfs_mnt_create(path)");
mnt_parent = dfs_mnt_create(fullpath); /* mnt->ref_count should be 1. */
if (mnt_parent)
{
DLOG(msg, "mnt", "dfs", DLOG_MSG_RET, "return mnt, ref_count=1");
mnt_parent->fs_ops = type->fs_ops;
mnt_parent->dev_id = dev_id;
if (mnt_parent->fs_ops->mount)
{
DLOG(msg, "dfs", type->fs_ops->name, DLOG_MSG, "fs_ops->mount(mnt_parent, rwflag, data)");
ret = mnt_parent->fs_ops->mount(mnt_parent, rwflag, data);
if (ret == RT_EOK)
{
DLOG(msg, type->fs_ops->name, "dfs", DLOG_MSG_RET, "mount OK, ret root_dentry");
/* Mark as mounted and insert into mount table */
mnt_child = mnt_parent;
mnt_child->flags |= MNT_IS_MOUNTED;
DLOG(note_right, "mnt", "mount sucessfully");
DLOG(msg, "dfs", "mnt", DLOG_MSG, "dfs_mnt_insert(, mnt_child)");
dfs_mnt_insert(RT_NULL, mnt_child);
/* unref it, because the ref_count = 1 when create */
DLOG(msg, "dfs", "mnt", DLOG_MSG, "dfs_mnt_unref(mnt_parent)");
dfs_mnt_unref(mnt_parent);
/*
* About root mnt:
* There are two ref_count:
* 1. the gobal root reference.
* 1. the mnt->parent reference.
*/
}
else
{
LOG_W("mount %s failed with file system type: %s", fullpath, type->fs_ops->name);
DLOG(msg, "dfs", "mnt", DLOG_MSG, "dfs_mnt_destroy(mnt_parent)");
dfs_mnt_destroy(mnt_parent);
mnt_parent = RT_NULL;
rt_set_errno(EPERM);
ret = -1;
}
}
else
{
LOG_W("no mount method on file system type: %s", type->fs_ops->name);
DLOG(msg, "dfs", "mnt", DLOG_MSG, "dfs_mnt_destroy(mnt_parent), no mount method");
dfs_mnt_destroy(mnt_parent);
mnt_parent = RT_NULL;
rt_set_errno(EIO);
ret = -1;
}
}
else
{
LOG_E("create a mnt point failed.");
rt_set_errno(ENOMEM);
ret = -1;
}
}
else if (mnt_parent && (strcmp(mnt_parent->fullpath, fullpath) != 0)) /* Handle regular filesystem mounting */
{
DLOG(msg, "dfs", "dentry", DLOG_MSG, "mntpoint_dentry = dfs_dentry_lookup(mnt_parent, %s, 0)", fullpath);
mntpoint_dentry = dfs_dentry_lookup(mnt_parent, fullpath, 0); /* Find mount point directory entry */
if (mntpoint_dentry)
{
DLOG(msg, "dentry", "dfs", DLOG_MSG_RET, "dentry exist");
DLOG(msg, "dfs", "mnt", DLOG_MSG, "mnt_child = dfs_mnt_create(path)");
mnt_child = dfs_mnt_create(fullpath); /* Create child mount point */
if (mnt_child)
{
LOG_D("create mnt point %p", mnt_child);
mnt_child->fs_ops = type->fs_ops;
mnt_child->dev_id = dev_id;
if (mnt_child->fs_ops->mount)
{
DLOG(msg, "dfs", type->fs_ops->name, DLOG_MSG, "root_dentry = fs_ops->mount(mnt_child, rwflag, data)");
ret = mnt_child->fs_ops->mount(mnt_child, rwflag, data);
if (ret == RT_EOK)
{
mnt_child->flags |= MNT_IS_MOUNTED;
LOG_D("mount %s sucessfully", fullpath);
DLOG(msg, mnt_child->fs_ops->name, "dfs", DLOG_MSG_RET, "mount OK");
DLOG(msg, "dfs", "mnt", DLOG_MSG, "dfs_mnt_insert(mnt_parent, mnt_child)");
dfs_mnt_insert(mnt_parent, mnt_child);
/* unref it, because the ref_count = 1 when create */
DLOG(msg, "dfs", "mnt", DLOG_MSG, "dfs_mnt_unref(mnt_child)");
dfs_mnt_unref(mnt_child);
}
else
{
LOG_W("mount %s failed with file system type: %s", fullpath, type->fs_ops->name);
DLOG(msg, mnt_child->fs_ops->name, "dfs", DLOG_MSG_RET, "mount failed");
dfs_mnt_destroy(mnt_child);
rt_set_errno(EPERM);
ret = -1;
}
}
else
{
LOG_W("no mount method on file system type: %s", type->fs_ops->name);
dfs_mnt_destroy(mnt_child);
rt_set_errno(EIO);
ret = -1;
}
}
else
{
LOG_E("create a mnt point failed.");
rt_set_errno(ENOMEM);
ret = -1;
}
dfs_dentry_unref(mntpoint_dentry);
}
else
{
LOG_W("no mount point (%s) in file system: %s", fullpath, mnt_parent->fullpath);
rt_set_errno(ENOTDIR);
ret = -1;
}
}
else
{
LOG_E("mount point (%s) already mounted!", fullpath);
rt_set_errno(EEXIST);
ret = -1;
}
}
else
{
LOG_E("No device found for this file system.");
rt_set_errno(ENODEV);
ret = -1;
}
rt_free(fullpath);
}
return ret;
}
/**
* @brief Unmount a filesystem from the specified path
*
* This function unmounts a filesystem from the given path. It performs the following operations:
* 1. Normalizes the target path
* 2. Looks up the mount point
* 3. Checks if the filesystem can be safely unmounted
* 4. Performs cleanup operations if unmounting is successful
*
* @param[in] specialfile The path of the filesystem to unmount
* @param[in] flags Unmount flags (MNT_FORCE for forced unmount)
*
* @return int RT_EOK on success, negative error code on failure:
* - EBUSY: Filesystem is busy (in use or has child mounts)
* - EINVAL: Path is not a mount point
* - ENOTDIR: Invalid path format
*
* @note Forced unmount (MNT_FORCE) can unmount even if reference count > 1
* @note Automatically handles page cache cleanup if RT_USING_PAGECACHE is enabled
* @note The function will fail if:
* - The mount point is locked (MNT_IS_LOCKED)
* - There are child mounts present
* - Reference count > 1 and MNT_FORCE not specified
*/
int dfs_umount(const char *specialfile, int flags)
{
int ret = -1;
char *fullpath = RT_NULL;
struct dfs_mnt *mnt = RT_NULL;
fullpath = dfs_normalize_path(NULL, specialfile);
if (fullpath)
{
DLOG(msg, "dfs", "mnt", DLOG_MSG, "mnt = dfs_mnt_lookup(%s)", fullpath);
mnt = dfs_mnt_lookup(fullpath);
if (mnt)
{
if (strcmp(mnt->fullpath, fullpath) == 0)
{
/* is the mount point */
rt_base_t ref_count = rt_atomic_load(&(mnt->ref_count));
if (!(mnt->flags & MNT_IS_LOCKED) && rt_list_isempty(&mnt->child) && (ref_count == 1 || (flags & MNT_FORCE)))
{
#ifdef RT_USING_PAGECACHE
dfs_pcache_unmount(mnt);
#endif
/* destroy this mount point */
DLOG(msg, "dfs", "mnt", DLOG_MSG, "dfs_mnt_destroy(mnt)");
ret = dfs_mnt_destroy(mnt);
}
else
{
LOG_I("the file system is busy!");
ret = -EBUSY;
}
}
else
{
LOG_I("the path:%s is not a mountpoint!", fullpath);
ret = -EINVAL;
}
}
else
{
LOG_I("no filesystem found.");
}
rt_free(fullpath);
}
else
{
rt_set_errno(-ENOTDIR);
}
return ret;
}
/* for compatibility */
int dfs_unmount(const char *specialfile)
{
return dfs_umount(specialfile, 0);
}
/**
* @brief Check if a mount point is mounted
*
* This function checks if the given mount point is mounted. It returns 0 if the mount point is mounted,
* and -1 otherwise.
*
* @param[in] mnt The mount point to check
*
* @return int 0 if mounted, -1 otherwise
*/
int dfs_is_mounted(struct dfs_mnt *mnt)
{
int ret = 0;
if (mnt && !(mnt->flags & MNT_IS_MOUNTED))
{
ret = -1;
}
return ret;
}
/**
* @brief Create a filesystem on the specified device
*
* This function creates a filesystem of the specified type on the given device.
* It performs the following operations:
* 1. Looks up the filesystem type
* 2. Validates device requirements
* 3. Calls the filesystem-specific mkfs operation
* 4. Handles page cache cleanup if successful (when RT_USING_PAGECACHE is enabled)
*
* @param[in] fs_name Name of the filesystem type to create (e.g., "elm", "romfs")
* @param[in] device_name Name of the device to create filesystem on (optional)
*
* @return int RT_EOK on success, negative error code on failure:
* - RT_ERROR: General error
* - ENODEV: Filesystem type not found or device not available
*
* @note For filesystems that don't require a device (FS_NEED_DEVICE not set),
* the device_name parameter can be NULL
* @note Automatically unmounts any existing filesystem on the device
* when RT_USING_PAGECACHE is enabled
* @note The function will fail if:
* - The filesystem type is not found
* - Device is required but not found
* - The filesystem doesn't implement mkfs operation
*/
int dfs_mkfs(const char *fs_name, const char *device_name)
{
rt_device_t dev_id = NULL;
struct dfs_filesystem_type *type;
int ret = -RT_ERROR;
type = *_find_filesystem(fs_name);
if (!type)
{
rt_kprintf("no file system: %s found!\n", fs_name);
return ret;
}
else
{
if (type->fs_ops->flags & FS_NEED_DEVICE)
{
/* check device name, and it should not be NULL */
if (device_name != NULL)
dev_id = rt_device_find(device_name);
if (dev_id == NULL)
{
rt_set_errno(-ENODEV);
rt_kprintf("Device (%s) was not found", device_name);
return ret;
}
}
else
{
dev_id = RT_NULL;
}
}
if (type->fs_ops->mkfs)
{
ret = type->fs_ops->mkfs(dev_id, type->fs_ops->name);
#ifdef RT_USING_PAGECACHE
if (ret == RT_EOK)
{
struct dfs_mnt *mnt = RT_NULL;
mnt = dfs_mnt_dev_lookup(dev_id);
if (mnt)
{
dfs_pcache_unmount(mnt);
}
}
#endif
}
return ret;
}
/**
* @brief Get filesystem statistics for the specified path
*
* This function retrieves filesystem statistics (like total/available space)
* for the filesystem containing the given path. It performs the following operations:
* 1. Normalizes the input path
* 2. Looks up the mount point for the path
* 3. Calls the filesystem-specific statfs operation if available
*
* @param[in] path The path to query filesystem statistics for
* @param[out] buffer Pointer to statfs structure to store the results
*
* @return int RT_EOK on success, negative error code on failure:
* - RT_ERROR: General error (invalid path or filesystem not found)
*
* @note The function will fail if:
* - The path cannot be normalized
* - No mount point is found for the path
* - The filesystem doesn't implement statfs operation
* - The filesystem is not currently mounted
* @note The buffer parameter must point to valid memory allocated by the caller
*/
int dfs_statfs(const char *path, struct statfs *buffer)
{
struct dfs_mnt *mnt;
char *fullpath;
int ret = -RT_ERROR;
fullpath = dfs_normalize_path(NULL, path);
if (!fullpath)
{
return ret;
}
DLOG(msg, "dfs_file", "mnt", DLOG_MSG, "dfs_mnt_lookup(%s)", fullpath);
mnt = dfs_mnt_lookup(fullpath);
if (mnt)
{
if (mnt->fs_ops->statfs)
{
if (dfs_is_mounted(mnt) == 0)
{
ret = mnt->fs_ops->statfs(mnt, buffer);
}
}
}
return ret;
}
/**
* this function will return the mounted path for specified device.
*
* @param[in] device the device object which is mounted.
*
* @return the mounted path or NULL if none device mounted.
*/
const char *dfs_filesystem_get_mounted_path(struct rt_device *device)
{
const char *path = NULL;
return path;
}
/**
* this function will fetch the partition table on specified buffer.
*
* @param[out] part the returned partition structure.
* @param[in] buf the buffer contains partition table.
* @param[in] pindex the index of partition table to fetch.
*
* @return RT_EOK on successful or -RT_ERROR on failed.
*/
int dfs_filesystem_get_partition(struct dfs_partition *part,
uint8_t *buf,
uint32_t pindex)
{
#define DPT_ADDRESS 0x1be /* device partition offset in Boot Sector */
#define DPT_ITEM_SIZE 16 /* partition item size */
uint8_t *dpt;
uint8_t type;
RT_ASSERT(part != NULL);
RT_ASSERT(buf != NULL);
dpt = buf + DPT_ADDRESS + pindex * DPT_ITEM_SIZE;
/* check if it is a valid partition table */
if ((*dpt != 0x80) && (*dpt != 0x00))
return -EIO;
/* get partition type */
type = *(dpt + 4);
if (type == 0)
return -EIO;
/* set partition information
* size is the number of 512-Byte */
part->type = type;
part->offset = *(dpt + 8) | *(dpt + 9) << 8 | *(dpt + 10) << 16 | *(dpt + 11) << 24;
part->size = *(dpt + 12) | *(dpt + 13) << 8 | *(dpt + 14) << 16 | *(dpt + 15) << 24;
rt_kprintf("found part[%d], begin: %ld, size: ",
pindex, part->offset * 512);
if ((part->size >> 11) == 0)
rt_kprintf("%ld%s", part->size >> 1, "KB\n"); /* KB */
else
{
unsigned int part_size;
part_size = part->size >> 11; /* MB */
if ((part_size >> 10) == 0)
rt_kprintf("%d.%ld%s", part_size, (part->size >> 1) & 0x3FF, "MB\n");
else
rt_kprintf("%d.%d%s", part_size >> 10, part_size & 0x3FF, "GB\n");
}
return RT_EOK;
}
/* @} */
@@ -0,0 +1,686 @@
/*
* Copyright (c) 2006-2025 RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2023-05-05 Bernard Implement mnt in dfs v2.0
*/
#include <rtthread.h>
#include "dfs_private.h"
#include <dfs.h>
#include <dfs_dentry.h>
#include <dfs_mnt.h>
#include <dfs_pcache.h>
#define DBG_TAG "DFS.mnt"
#define DBG_LVL DBG_WARNING
#include <rtdbg.h>
static struct dfs_mnt *_root_mnt = RT_NULL;
RT_OBJECT_HOOKLIST_DEFINE(dfs_mnt_umnt);
/*
* mnt tree structure
*
* mnt_root <----------------------------------------+
* | (child) +----------+ |
* v (sibling) v | |
* mnt_child0 -> mnt_child1 | |
* | (child) | |
* v / (parent) | (root)
* mnt_child10 ---/
*
*/
/**
* @brief Create a new dfs_mnt structure instance.
*
* This function allocates memory to create a new dfs_mnt structure instance and initializes it.
* If the memory allocation is successful, it copies the input path string into the instance and initializes related lists and flags.
*
* @param[in] path The path string to be mounted. This path information will be copied to the newly created dfs_mnt instance.
*
* @return If the memory allocation is successful, returns a pointer to the newly created dfs_mnt structure;
* if the memory allocation fails, returns RT_NULL.
*/
struct dfs_mnt *dfs_mnt_create(const char *path)
{
struct dfs_mnt *mnt = rt_calloc(1, sizeof(struct dfs_mnt));
if (mnt)
{
LOG_I("create mnt at %s", path);
mnt->fullpath = rt_strdup(path);
rt_list_init(&mnt->sibling);
rt_list_init(&mnt->child);
mnt->flags |= MNT_IS_ALLOCED;
rt_atomic_store(&(mnt->ref_count), 1);
}
else
{
rt_set_errno(-ENOMEM);
}
return mnt;
}
/**
* @brief Insert a child mount point into the mount tree.
*
* This function inserts a child mount point into the specified parent mount point's child list.
* If the parent mount point is not provided, it will try to find the appropriate mount point based on the child's path.
* If the child mount point is the root, it will update the global root mount point accordingly.
*
* @param[in,out] mnt Pointer to the parent dfs_mnt structure. If NULL, it will be updated to the appropriate mount point.
* @param[in] child Pointer to the child dfs_mnt structure to be inserted.
*
* @return Always returns 0 to indicate success.
*/
int dfs_mnt_insert(struct dfs_mnt* mnt, struct dfs_mnt* child)
{
if (child)
{
if (mnt == RT_NULL)
{
/* insert into root */
mnt = dfs_mnt_lookup(child->fullpath);
if (mnt == RT_NULL || (strcmp(child->fullpath, "/") == 0))
{
/* it's root mnt */
mnt = child;
mnt->flags |= MNT_IS_LOCKED;
/* ref to gobal root */
if (_root_mnt)
{
child = _root_mnt;
rt_atomic_sub(&(_root_mnt->parent->ref_count), 1);
rt_atomic_sub(&(_root_mnt->ref_count), 1);
_root_mnt->flags &= ~MNT_IS_LOCKED;
_root_mnt = dfs_mnt_ref(mnt);
mnt->parent = dfs_mnt_ref(mnt);
mnt->flags |= MNT_IS_ADDLIST;
mkdir("/dev", 0777);
}
else
{
_root_mnt = dfs_mnt_ref(mnt);
}
}
}
if (mnt)
{
child->flags |= MNT_IS_ADDLIST;
if (child != mnt)
{
/* not the root, insert into the child list */
rt_list_insert_before(&mnt->child, &child->sibling);
/* child ref self */
dfs_mnt_ref(child);
}
/* parent ref parent */
child->parent = dfs_mnt_ref(mnt);
}
}
return 0;
}
/**
* @brief Remove a mount point from the mount tree.
*
* This function attempts to remove a specified mount point from the mount tree.
* It can only remove a mount point if it has no child mount points. If the mount point
* has children, it logs a warning message instead of performing the removal.
*
* @param[in] mnt Pointer to the dfs_mnt structure representing the mount point to be removed.
*
* @return Returns RT_EOK if the mount point is successfully removed.
* Returns -RT_ERROR if the mount point has child mount points and cannot be removed.
*/
int dfs_mnt_remove(struct dfs_mnt* mnt)
{
int ret = -RT_ERROR;
if (rt_list_isempty(&mnt->child))
{
rt_list_remove(&mnt->sibling);
if (mnt->parent)
{
/* parent unref parent */
rt_atomic_sub(&(mnt->parent->ref_count), 1);
}
ret = RT_EOK;
}
else
{
LOG_W("remove a mnt point:%s with child.", mnt->fullpath);
}
return ret;
}
/**
* @brief Recursively search for a mount point associated with a specific device ID in the mount tree.
*
* This function traverses the mount tree starting from the given mount point `mnt` to find
* a mount point that is associated with the specified device ID `dev_id`. It uses a depth-first
* search algorithm to iterate through the child mount points.
*
* @param[in] mnt Pointer to the root dfs_mnt structure from which the search will start.
* @param[in] dev_id Pointer to the device ID to search for.
*
* @return If a mount point associated with the given device ID is found, returns a pointer to the corresponding dfs_mnt structure.
* Otherwise, returns RT_NULL.
*/
static struct dfs_mnt *_dfs_mnt_dev_lookup(struct dfs_mnt *mnt, rt_device_t dev_id)
{
struct dfs_mnt *ret = RT_NULL, *iter = RT_NULL;
rt_list_for_each_entry(iter, &mnt->child, sibling)
{
if (iter->dev_id == dev_id)
{
ret = iter;
break;
}
else
{
ret = _dfs_mnt_dev_lookup(iter, dev_id);
if (ret)
{
break;
}
}
}
return ret;
}
/**
* @brief Search for a mount point associated with a specific device ID in the mount tree.
*
* This function initiates a search for a mount point that is associated with the specified
* device ID `dev_id` starting from the root mount point. It first checks the root mount point
* directly, and if not found, it recursively searches the entire mount tree using the
* internal helper function `_dfs_mnt_dev_lookup`.
*
* @param[in] dev_id Pointer to the device ID to search for.
*
* @return If a mount point associated with the given device ID is found, returns a pointer to the corresponding dfs_mnt structure.
* Otherwise, returns RT_NULL.
*/
struct dfs_mnt *dfs_mnt_dev_lookup(rt_device_t dev_id)
{
struct dfs_mnt *mnt = _root_mnt;
struct dfs_mnt *ret = RT_NULL;
if (mnt)
{
dfs_lock();
if (mnt->dev_id == dev_id)
{
dfs_unlock();
return mnt;
}
ret = _dfs_mnt_dev_lookup(mnt, dev_id);
dfs_unlock();
}
return ret;
}
/**
* @brief Look up the mount point associated with a given full path.
*
* This function searches the mount tree starting from the root mount point to find
* the most specific mount point that matches the given full path. It traverses down
* the mount tree to identify the deepest mount point that is a prefix of the given path.
*
* @param[in] fullpath The full path string for which to find the associated mount point.
*
* @return If a matching mount point is found, returns a pointer to the corresponding dfs_mnt structure.
* Otherwise, returns RT_NULL.
*/
struct dfs_mnt *dfs_mnt_lookup(const char *fullpath)
{
struct dfs_mnt *mnt = _root_mnt;
struct dfs_mnt *iter = RT_NULL;
if (mnt)
{
int mnt_len = rt_strlen(mnt->fullpath);
dfs_lock();
if ((strncmp(mnt->fullpath, fullpath, mnt_len) == 0) &&
(mnt_len == 1 || (fullpath[mnt_len] == '\0') || (fullpath[mnt_len] == '/')))
{
while (!rt_list_isempty(&mnt->child))
{
rt_list_for_each_entry(iter, &mnt->child, sibling)
{
mnt_len = rt_strlen(iter->fullpath);
if ((strncmp(iter->fullpath, fullpath, mnt_len) == 0) &&
((fullpath[mnt_len] == '\0') || (fullpath[mnt_len] == '/')))
{
mnt = iter;
break;
}
}
if (mnt != iter) break;
}
}
else
{
mnt = RT_NULL;
}
dfs_unlock();
if (mnt)
{
LOG_D("mnt_lookup: %s path @ mount point %p", fullpath, mnt);
DLOG(note, "mnt", "found mnt(%s)", mnt->fs_ops->name);
}
}
return mnt;
}
/**
* @brief Increase the reference count of a dfs_mnt structure instance.
*
* This function increments the reference count of the specified dfs_mnt structure.
* The reference count is used to track how many parts of the system are currently
* using this mount point.
*
* @param[in,out] mnt Pointer to the dfs_mnt structure whose reference count is to be increased.
* If the pointer is valid, the reference count within the structure will be modified.
* @return Returns the same pointer to the dfs_mnt structure that was passed in.
* If the input pointer is NULL, it simply returns NULL.
*/
struct dfs_mnt* dfs_mnt_ref(struct dfs_mnt* mnt)
{
if (mnt)
{
rt_atomic_add(&(mnt->ref_count), 1);
DLOG(note, "mnt", "mnt(%s),ref_count=%d", mnt->fs_ops->name, rt_atomic_load(&(mnt->ref_count)));
}
return mnt;
}
/**
* @brief Decrease the reference count of a dfs_mnt structure instance and free it if necessary.
*
* This function decrements the reference count of the specified dfs_mnt structure.
* If the reference count reaches zero after the decrement, it will perform the unmount operation,
* trigger the unmount hook, free the allocated path memory, and finally free the dfs_mnt structure itself.
*
* @param[in,out] mnt Pointer to the dfs_mnt structure whose reference count is to be decreased.
* If the reference count reaches zero, the structure will be freed.
*
* @return returns RT_EOK to indicate success.
*/
int dfs_mnt_unref(struct dfs_mnt *mnt)
{
rt_err_t ret = RT_EOK;
rt_base_t ref_count;
if (mnt)
{
ref_count = rt_atomic_sub(&(mnt->ref_count), 1) - 1;
if (ref_count == 0)
{
dfs_lock();
if (mnt->flags & MNT_IS_UMOUNT)
{
mnt->fs_ops->umount(mnt);
RT_OBJECT_HOOKLIST_CALL(dfs_mnt_umnt, (mnt));
}
/* free full path */
rt_free(mnt->fullpath);
mnt->fullpath = RT_NULL;
/* destroy self and the ref_count should be 0 */
DLOG(msg, "mnt", "mnt", DLOG_MSG, "free mnt(%s)", mnt->fs_ops->name);
rt_free(mnt);
dfs_unlock();
}
else
{
DLOG(note, "mnt", "mnt(%s),ref_count=%d", mnt->fs_ops->name, rt_atomic_load(&(mnt->ref_count)));
}
}
return ret;
}
/**
* @brief Set specific flags for a dfs_mnt structure instance.
*
* This function sets specific flags for the given dfs_mnt structure.
* If the MS_RDONLY flag is included in the input flags, it sets the MNT_RDONLY flag
* for the mount point and cleans the page cache if the page cache feature is enabled.
*
* @param[in,out] mnt Pointer to the dfs_mnt structure for which flags are to be set.
* The structure's `flags` member will be modified if necessary.
* @param[in] flags The flags to be set for the mount point. This includes the MS_RDONLY flag.
*
* @return returns 0 to indicate success.
*/
int dfs_mnt_setflags(struct dfs_mnt *mnt, int flags)
{
int error = 0;
if (flags & MS_RDONLY)
{
mnt->flags |= MNT_RDONLY;
#ifdef RT_USING_PAGECACHE
dfs_pcache_clean(mnt);
#endif
}
return error;
}
/**
* @brief Destroy a dfs_mnt structure instance and unmount it if necessary.
*
* This function attempts to destroy the specified dfs_mnt structure instance.
* If the mount point is currently mounted, it marks the mount point as unmounted,
* sets the unmount flag, and removes it from the mount list if it was added.
* Finally, it decreases the reference count of the mount point and frees the
* structure if the reference count reaches zero.
*
* @param[in,out] mnt Pointer to the dfs_mnt structure to be destroyed.
*
* @return Returns RT_EOK to indicate success.
*/
int dfs_mnt_destroy(struct dfs_mnt* mnt)
{
rt_err_t ret = RT_EOK;
if (mnt)
{
if (mnt->flags & MNT_IS_MOUNTED)
{
mnt->flags &= ~MNT_IS_MOUNTED;
mnt->flags |= MNT_IS_UMOUNT;
/* remote it from mnt list */
if (mnt->flags & MNT_IS_ADDLIST)
{
dfs_mnt_remove(mnt);
}
}
dfs_mnt_unref(mnt);
}
return ret;
}
/**
* @brief Recursively traverse the mount point tree and apply a callback function.
*
* This function performs a depth-first traversal of the mount point tree starting from the given mount point.
* It applies the specified callback function to each mount point in the tree. If the callback function returns
* a non-NULL pointer, the traversal stops and the result is returned immediately.
*
* @param[in] mnt Pointer to the root dfs_mnt structure from which the traversal will start.
* If NULL, the function will return RT_NULL without performing any traversal.
* @param[in] func Pointer to the callback function to be applied to each mount point.
* The callback function takes a pointer to a dfs_mnt structure and a generic parameter,
* and returns a pointer to a dfs_mnt structure or RT_NULL.
* @param[in] parameter Generic pointer to a parameter that will be passed to the callback function.
*
* @return If the callback function returns a non-NULL pointer during the traversal, returns that pointer.
* Otherwise, returns RT_NULL.
*/
static struct dfs_mnt* _dfs_mnt_foreach(struct dfs_mnt *mnt, struct dfs_mnt* (*func)(struct dfs_mnt *mnt, void *parameter), void *parameter)
{
struct dfs_mnt *iter, *ret = NULL;
if (mnt)
{
ret = func(mnt, parameter);
if (ret == RT_NULL)
{
if (!rt_list_isempty(&mnt->child))
{
/* for each in mount point list */
rt_list_for_each_entry(iter, &mnt->child, sibling)
{
ret = _dfs_mnt_foreach(iter, func, parameter);
if (ret != RT_NULL)
{
break;
}
}
}
}
}
else
{
ret = RT_NULL;
}
return ret;
}
/**
* @brief Compare a mount point's device ID with a given device object.
*
* This function checks if the device ID associated with a specified mount point
* matches the given device object. If a match is found, it returns a pointer to
* the corresponding dfs_mnt structure; otherwise, it returns RT_NULL.
*
* @param[in] mnt Pointer to the dfs_mnt structure representing the mount point to be checked.
* @param[in] device Pointer to the device object to compare against the mount point's device ID.
*
* @return If the device ID of the mount point matches the given device object, returns a pointer to the dfs_mnt structure.
* Otherwise, returns RT_NULL.
*/
static struct dfs_mnt* _mnt_cmp_devid(struct dfs_mnt *mnt, void *device)
{
struct dfs_mnt *ret = RT_NULL;
struct rt_device *dev = (struct rt_device*)device;
if (dev && mnt)
{
if (mnt->dev_id == dev)
{
ret = mnt;
}
}
return ret;
}
/**
* this function will return the mounted path for specified device.
*
* @param[in] device the device object which is mounted.
*
* @return the mounted path or NULL if none device mounted.
*/
const char *dfs_mnt_get_mounted_path(struct rt_device *device)
{
const char* path = RT_NULL;
if (_root_mnt)
{
struct dfs_mnt* mnt;
dfs_lock();
mnt = _dfs_mnt_foreach(_root_mnt, _mnt_cmp_devid, device);
dfs_unlock();
if (mnt) path = mnt->fullpath;
}
return path;
}
/**
* @brief Print information about a mount point to the console.
*
* This function is designed to be used as a callback in the mount point tree traversal.
* It prints the file system name, device name (or `(NULL)` if no device is associated),
* mount path, and reference count of the specified mount point to the console using `rt_kprintf`.
*
* @param[in] mnt Pointer to the dfs_mnt structure representing the mount point to be printed.
* If NULL, the function does nothing.
* @param[in] parameter A generic pointer to a parameter. This parameter is not used in this function.
*
* @return Always returns RT_NULL as it is a callback function mainly used for side - effects (printing).
*/
static struct dfs_mnt* _mnt_dump(struct dfs_mnt *mnt, void *parameter)
{
if (mnt)
{
if (mnt->dev_id)
{
rt_kprintf("%-10s %-6s %-10s %d\n",
mnt->fs_ops->name, mnt->dev_id->parent.name, mnt->fullpath, rt_atomic_load(&(mnt->ref_count)));
}
else
{
rt_kprintf("%-10s (NULL) %-10s %d\n",
mnt->fs_ops->name, mnt->fullpath, rt_atomic_load(&(mnt->ref_count)));
}
}
return RT_NULL;
}
/**
* @brief Compare a mount point's full path with a given path.
*
* This function is designed to be used as a callback in the mount point tree traversal.
* It compares the full path of the specified mount point with the given path.
* If the mount point's full path starts with the given path, it returns a pointer to the dfs_mnt structure;
* otherwise, it returns RT_NULL.
*
* @param[in] mnt Pointer to the dfs_mnt structure representing the mount point to be checked.
* If NULL, the function will not perform the comparison and return RT_NULL.
* @param[in] parameter A generic pointer to a parameter, which should be cast to a `const char*`
* representing the path to compare against the mount point's full path.
*
* @return If the mount point's full path starts with the given path, returns a pointer to the dfs_mnt structure.
* Otherwise, returns RT_NULL.
*/
static struct dfs_mnt* _mnt_cmp_path(struct dfs_mnt* mnt, void *parameter)
{
const char* fullpath = (const char*)parameter;
struct dfs_mnt *ret = RT_NULL;
if (strncmp(mnt->fullpath, fullpath, rt_strlen(fullpath)) == 0)
{
ret = mnt;
}
return ret;
}
/**
* @brief Check if a mount point has a child mount point matching the given path.
*
* This function checks whether the specified mount point has a child mount point
* whose full path starts with the given path. It uses a depth-first traversal of
* the mount point tree starting from the provided mount point and applies the
* `_mnt_cmp_path` callback function to each mount point.
*
* @param[in] mnt Pointer to the root dfs_mnt structure from which the search will start.
* If NULL, the function will return RT_FALSE without performing any search.
* @param[in] fullpath The full path string to compare against the child mount points' paths.
* If NULL, the function will return RT_FALSE without performing any search.
*
* @return Returns RT_TRUE if a child mount point with a matching path is found.
* Returns RT_FALSE if no matching child mount point is found, or if either input parameter is NULL.
*/
rt_bool_t dfs_mnt_has_child_mnt(struct dfs_mnt *mnt, const char* fullpath)
{
int ret = RT_FALSE;
if (mnt && fullpath)
{
struct dfs_mnt *m = RT_NULL;
dfs_lock();
m = _dfs_mnt_foreach(mnt, _mnt_cmp_path, (void*)fullpath);
dfs_unlock();
if (m)
{
ret = RT_TRUE;
}
}
return ret;
}
/**
* @brief List all mount points starting from a specified mount point.
*
* This function lists information about all mount points in the mount point tree,
* starting from the specified mount point. If the input mount point is NULL,
* it starts from the root mount point. It uses the `_dfs_mnt_foreach` function
* with the `_mnt_dump` callback to print mount point information.
*
* @param[in] mnt Pointer to the dfs_mnt structure from which to start listing mount points.
* If NULL, the function will start from the root mount point.
*
* @return Always returns 0 to indicate success.
*/
int dfs_mnt_list(struct dfs_mnt *mnt)
{
if (!mnt) mnt = _root_mnt;
/* lock file system */
dfs_lock();
_dfs_mnt_foreach(mnt, _mnt_dump, RT_NULL);
/* unlock file system */
dfs_unlock();
return 0;
}
/**
* @brief Traverse all mount points in the mount tree and apply a callback function.
*
* @param[in] func Pointer to the callback function to be applied to each mount point.
* The callback function takes a pointer to a `dfs_mnt` structure and a generic parameter,
* and returns a pointer to a `dfs_mnt` structure or `RT_NULL`.
* @param[in] parameter Generic pointer to a parameter that will be passed to the callback function.
*
* @return Always returns 0.
*/
int dfs_mnt_foreach(struct dfs_mnt* (*func)(struct dfs_mnt *mnt, void *parameter), void *parameter)
{
/* lock file system */
dfs_lock();
_dfs_mnt_foreach(_root_mnt, func, parameter);
/* unlock file system */
dfs_unlock();
return 0;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,19 @@
/*
* Copyright (c) 2006-2023, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
*/
#ifndef DFS_PRIVATE_H__
#define DFS_PRIVATE_H__
#include <dfs.h>
#define NO_WORKING_DIR "system does not support working directory\n"
extern char working_directory[];
#endif
@@ -0,0 +1,516 @@
/*
* Copyright (c) 2006-2025 RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
*/
#include <dfs_seq_file.h>
#include <dfs_dentry.h>
#define DBG_TAG "DFS.seq"
#define DBG_LVL DBG_WARNING
#include <rtdbg.h>
#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif
/**
* @brief Handle buffer overflow condition in sequence file
*
* @param[in,out] seq Pointer to sequence file structure
*
* @details Sets the count to size to indicate buffer is full
*/
static void dfs_seq_overflow(struct dfs_seq_file *seq)
{
seq->count = seq->size;
}
/**
* @brief Allocate memory for sequence file operations
*
* @param[in] size Size of memory to allocate in bytes
*
* @return void* Pointer to allocated memory, or NULL if allocation fails
*/
static void *dfs_seq_alloc(unsigned long size)
{
return rt_calloc(1, size);
}
/**
* @brief Initialize and open a sequence file
*
* @param[in] file Pointer to the file structure to be initialized
* @param[in] ops Pointer to sequence operations structure containing callback functions
*
* @return int 0 on success, negative error code on failure:
* -EINVAL if ops is NULL
* -ENOMEM if memory allocation fails
*/
int dfs_seq_open(struct dfs_file *file, const struct dfs_seq_ops *ops)
{
struct dfs_seq_file *seq;
if (!ops)
{
LOG_E("dfs_seq_open: ops = null, pathname: %s\n", file->dentry->pathname);
return -EINVAL;
}
if (file->data)
{
LOG_W("dfs_seq_open: file->data != null\n");
}
seq = rt_calloc(1, sizeof(struct dfs_seq_file));
if (!seq)
return -ENOMEM;
file->data = seq;
rt_mutex_init(&seq->lock, "dfs_seq", RT_IPC_FLAG_PRIO);
seq->ops = ops;
seq->file = file;
return 0;
}
/**
* @brief Traverse sequence file data with specified offset
*
* This function traverses the sequence file data starting from the specified offset.
* It handles buffer overflow conditions by dynamically resizing the buffer when needed.
*
* @param[in,out] seq Pointer to sequence file structure
* @param[in] offset Position to start traversing from
*
* @return int 0 on success, negative error code on failure:
* -ENOMEM if memory allocation fails
* -EAGAIN if buffer needs to be resized
*
* @note Data output loop: start() -> show() -> next() -> show() -> ... -> next() -> stop()
*/
static int dfs_seq_traverse(struct dfs_seq_file *seq, off_t offset)
{
off_t pos = 0;
int error = 0;
void *p;
seq->index = 0;
seq->count = seq->from = 0;
if (!offset)
return 0;
if (!seq->buf)
{
seq->buf = dfs_seq_alloc(seq->size = PAGE_SIZE);
if (!seq->buf)
return -ENOMEM;
}
p = seq->ops->start(seq, &seq->index);
while (p)
{
error = seq->ops->show(seq, p);
if (error < 0)
break;
if (error)
{
error = 0;
seq->count = 0;
}
if (dfs_seq_is_full(seq))
goto Eoverflow;
p = seq->ops->next(seq, p, &seq->index);
if (pos + seq->count > offset)
{
seq->from = offset - pos;
seq->count -= seq->from;
break;
}
pos += seq->count;
seq->count = 0;
if (pos == offset)
break;
}
seq->ops->stop(seq, p);
return error;
Eoverflow:
seq->ops->stop(seq, p);
rt_free(seq->buf);
seq->count = 0;
seq->buf = dfs_seq_alloc(seq->size <<= 1);
return !seq->buf ? -ENOMEM : -EAGAIN;
}
/**
* @brief Read data from sequence file
*
* @param[in] file Pointer to the file structure
* @param[out] buf Buffer to store the read data
* @param[in] size Size of the buffer in bytes
* @param[in,out] pos Current file position (updated after read)
*
* @return ssize_t Number of bytes read on success, negative error code on failure:
* -EFAULT if buffer error occurs
* -ENOMEM if memory allocation fails
* 0 if size is 0
*
* @details This function implements the core sequence file reading logic with following steps:
* 1. Reset iterator if reading from start
* 2. Synchronize position if needed
* 3. Allocate buffer if not exists
* 4. Copy remaining data from previous read
* 5. Start iteration and fill buffer with new data
* 6. Handle buffer overflow by doubling size
* 7. Copy data to user buffer and update positions
*/
ssize_t dfs_seq_read(struct dfs_file *file, void *buf, size_t size, off_t *pos)
{
struct dfs_seq_file *seq = file->data;
size_t copied = 0;
size_t n;
void *p;
int err = 0;
if (!size)
return 0;
rt_mutex_take(&seq->lock, RT_WAITING_FOREVER);
/*
* if request is to read from zero offset, reset iterator to first
* record as it might have been already advanced by previous requests
*/
if (*pos == 0)
{
seq->index = 0;
seq->count = 0;
}
/* Don't assume ki_pos is where we left it */
if (*pos != seq->read_pos)
{
while ((err = dfs_seq_traverse(seq, *pos)) == -EAGAIN)
;
if (err)
{
/* With prejudice... */
seq->read_pos = 0;
seq->index = 0;
seq->count = 0;
goto Done;
}
else
{
seq->read_pos = *pos;
}
}
/* grab buffer if we didn't have one */
if (!seq->buf)
{
seq->buf = dfs_seq_alloc(seq->size = PAGE_SIZE);
if (!seq->buf)
goto Enomem;
}
/* something left in the buffer - copy it out first */
if (seq->count)
{
n = seq->count > size ? size : seq->count;
rt_memcpy((char *)buf + copied, seq->buf + seq->from, n);
size -= n;
seq->count -= n;
seq->from += n;
copied += n;
if (seq->count) /* hadn't managed to copy everything */
goto Done;
}
/* get a non-empty record in the buffer */
seq->from = 0;
p = seq->ops->start(seq, &seq->index);
while (p)
{
err = seq->ops->show(seq, p);
if (err < 0) /* hard error */
break;
if (err) /* ->show() says "skip it" */
seq->count = 0;
if (!seq->count)
{ /* empty record */
p = seq->ops->next(seq, p, &seq->index);
continue;
}
if (!dfs_seq_is_full(seq)) /* got it */
goto Fill;
/* need a bigger buffer */
seq->ops->stop(seq, p);
rt_free(seq->buf);
seq->count = 0;
seq->buf = dfs_seq_alloc(seq->size <<= 1);
if (!seq->buf)
goto Enomem;
p = seq->ops->start(seq, &seq->index);
}
/* EOF or an error */
seq->ops->stop(seq, p);
seq->count = 0;
goto Done;
Fill:
/* one non-empty record is in the buffer; if they want more, */
/* try to fit more in, but in any case we need to advance */
/* the iterator once for every record shown. */
while (1)
{
size_t offs = seq->count;
off_t pos = seq->index;
p = seq->ops->next(seq, p, &seq->index);
if (pos == seq->index)
{
LOG_W(".next function %p did not update position index\n", seq->ops->next);
seq->index++;
}
if (!p) /* no next record for us */
break;
if (seq->count >= size)
break;
err = seq->ops->show(seq, p);
if (err > 0)
{ /* ->show() says "skip it" */
seq->count = offs;
}
else if (err || dfs_seq_is_full(seq))
{
seq->count = offs;
break;
}
}
seq->ops->stop(seq, p);
n = seq->count > size ? size : seq->count;
rt_memcpy((char *)buf + copied, seq->buf, n);
size -= n;
copied += n;
seq->count -= n;
seq->from = n;
Done:
if (!copied)
{
copied = seq->count ? -EFAULT : err;
}
else
{
*pos += copied;
seq->read_pos += copied;
}
rt_mutex_release(&seq->lock);
return copied;
Enomem:
err = -ENOMEM;
goto Done;
}
/**
* @brief Reposition the file offset for sequence file
*
* @param[in] file Pointer to the file structure
* @param[in] offset Offset value according to whence
* @param[in] whence Reference position for offset:
* - SEEK_SET: from file beginning
* - SEEK_CUR: from current position
* @return off_t New file offset on success, negative error code on failure:
* -EINVAL for invalid parameters
*/
off_t dfs_seq_lseek(struct dfs_file *file, off_t offset, int whence)
{
struct dfs_seq_file *seq = file->data;
off_t retval = -EINVAL;
rt_mutex_take(&seq->lock, RT_WAITING_FOREVER);
switch (whence)
{
case SEEK_CUR:
offset += file->fpos;
case SEEK_SET:
if (offset < 0)
break;
retval = offset;
if (offset != seq->read_pos)
{
while ((retval = dfs_seq_traverse(seq, offset)) == -EAGAIN);
if (retval)
{
/* with extreme prejudice... */
retval = 0;
seq->read_pos = 0;
seq->index = 0;
seq->count = 0;
}
else
{
seq->read_pos = offset;
retval = offset;
}
}
}
rt_mutex_release(&seq->lock);
return retval;
}
/**
* @brief Release resources associated with a sequence file
*
* @param[in] file Pointer to the file structure to be released
*
* @return int Always returns 0 indicating success
*/
int dfs_seq_release(struct dfs_file *file)
{
struct dfs_seq_file *seq = file->data;
if (seq)
{
rt_mutex_detach(&seq->lock);
if (seq->buf)
{
rt_free(seq->buf);
}
rt_free(seq);
}
return 0;
}
/**
* @brief Format and write data to sequence file buffer using variable arguments
*
* @param[in,out] seq Pointer to sequence file structure
* @param[in] f Format string (printf-style)
* @param[in] args Variable arguments list
*
* @details This function:
* - Formats data using vsnprintf
* - Triggers overflow if buffer is full
*/
void dfs_seq_vprintf(struct dfs_seq_file *seq, const char *f, va_list args)
{
int len;
if (seq->count < seq->size)
{
len = vsnprintf(seq->buf + seq->count, seq->size - seq->count, f, args);
if (seq->count + len < seq->size)
{
seq->count += len;
return;
}
}
dfs_seq_overflow(seq);
}
/**
* @brief Format and print data to sequence file buffer (printf-style)
*
* @param[in,out] seq Pointer to sequence file structure
* @param[in] f Format string (printf-style)
* @param[in] ... Variable arguments matching format string
*/
void dfs_seq_printf(struct dfs_seq_file *seq, const char *f, ...)
{
va_list args;
va_start(args, f);
dfs_seq_vprintf(seq, f, args);
va_end(args);
}
/**
* @brief Write a single character to sequence file buffer
*
* @param[in,out] seq Pointer to sequence file structure
* @param[in] c Character to be written
*/
void dfs_seq_putc(struct dfs_seq_file *seq, char c)
{
if (seq->count < seq->size)
{
seq->buf[seq->count++] = c;
}
}
/**
* @brief Write a string to sequence file buffer
*
* @param[in,out] seq Pointer to sequence file structure
* @param[in] s Null-terminated string to be written
*/
void dfs_seq_puts(struct dfs_seq_file *seq, const char *s)
{
int len = strlen(s);
if (seq->count + len >= seq->size)
{
dfs_seq_overflow(seq);
return;
}
rt_memcpy(seq->buf + seq->count, s, len);
seq->count += len;
}
/**
* @brief Write arbitrary binary data to sequence file buffer
*
* @param[in,out] seq Pointer to sequence file structure
* @param[in] data Pointer to data to be written
* @param[in] len Length of data in bytes
*
* @return int 0 on success, -1 if buffer overflow occurs
*/
int dfs_seq_write(struct dfs_seq_file *seq, const void *data, size_t len)
{
if (seq->count + len < seq->size)
{
rt_memcpy(seq->buf + seq->count, data, len);
seq->count += len;
return 0;
}
dfs_seq_overflow(seq);
return -1;
}
/**
* @brief Pad the sequence file buffer with spaces and optionally append a character
*
* @param[in,out] seq Pointer to sequence file structure
* @param[in] c Optional character to append after padding (if not '\0')
*/
void dfs_seq_pad(struct dfs_seq_file *seq, char c)
{
int size = seq->pad_until - seq->count;
if (size > 0)
{
if (size + seq->count > seq->size)
{
dfs_seq_overflow(seq);
return;
}
rt_memset(seq->buf + seq->count, ' ', size);
seq->count += size;
}
if (c)
{
dfs_seq_putc(seq, c);
}
}
@@ -0,0 +1,180 @@
/*
* Copyright (c) 2006-2025 RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2023-05-05 Bernard Implement vnode in dfs v2.0
*/
#include <dfs_file.h>
#include <dfs_mnt.h>
#ifdef RT_USING_PAGECACHE
#include "dfs_pcache.h"
#endif
#define DBG_TAG "DFS.vnode"
#define DBG_LVL DBG_WARNING
#include <rtdbg.h>
/**
* @brief Initialize a virtual node (vnode) structure
*
* @param[in,out] vnode Pointer to the vnode to be initialized
* @param[in] type Type of the vnode
* @param[in] fops Pointer to file operations structure
*
* @return int Always returns 0 indicating success
*/
int dfs_vnode_init(struct dfs_vnode *vnode, int type, const struct dfs_file_ops *fops)
{
if (vnode)
{
rt_memset(vnode, 0, sizeof(struct dfs_vnode));
vnode->type = type;
rt_atomic_store(&(vnode->ref_count), 1);
vnode->mnt = RT_NULL;
vnode->fops = fops;
}
return 0;
}
/**
* @brief Create and initialize a new virtual node (vnode)
*
* @return struct dfs_vnode* Pointer to the newly created vnode, or NULL if creation failed
*/
struct dfs_vnode *dfs_vnode_create(void)
{
struct dfs_vnode *vnode = rt_calloc(1, sizeof(struct dfs_vnode));
if (!vnode)
{
LOG_E("create a vnode failed.");
return RT_NULL;
}
rt_atomic_store(&(vnode->ref_count), 1);
LOG_I("create a vnode: %p", vnode);
return vnode;
}
/**
* @brief Destroy a virtual node (vnode) and free its resources
*
* @param[in] vnode Pointer to the vnode to be destroyed
*
* @return int Always returns 0. Note that this does not guarantee success, as errors may occur internally.
*/
int dfs_vnode_destroy(struct dfs_vnode* vnode)
{
rt_err_t ret = RT_EOK;
if (vnode)
{
ret = dfs_file_lock();
if (ret == RT_EOK)
{
if (rt_atomic_load(&(vnode->ref_count)) == 1)
{
LOG_I("free a vnode: %p", vnode);
#ifdef RT_USING_PAGECACHE
if (vnode->aspace)
{
dfs_aspace_destroy(vnode->aspace);
}
#endif
if (vnode->mnt)
{
DLOG(msg, "vnode", vnode->mnt->fs_ops->name, DLOG_MSG, "fs_ops->free_vnode");
vnode->mnt->fs_ops->free_vnode(vnode);
}
else
{
DLOG(msg, "vnode", "vnode", DLOG_MSG, "destroy vnode(mnt=NULL)");
}
dfs_file_unlock();
rt_free(vnode);
}
else
{
dfs_file_unlock();
}
}
}
return 0;
}
/**
* @brief Increase reference count of a virtual node (vnode)
*
* @param[in,out] vnode Pointer to the vnode to be referenced
*
* @return struct dfs_vnode* The same vnode pointer that was passed in
*/
struct dfs_vnode *dfs_vnode_ref(struct dfs_vnode *vnode)
{
if (vnode)
{
rt_atomic_add(&(vnode->ref_count), 1);
DLOG(note, "vnode", "vnode ref_count=%d", rt_atomic_load(&(vnode->ref_count)));
}
return vnode;
}
/**
* @brief Decrease reference count of a virtual node (vnode) and potentially free it
*
* @param[in,out] vnode Pointer to the vnode to be unreferenced
*/
void dfs_vnode_unref(struct dfs_vnode *vnode)
{
rt_err_t ret = RT_EOK;
if (vnode)
{
ret = dfs_file_lock();
if (ret == RT_EOK)
{
rt_atomic_sub(&(vnode->ref_count), 1);
DLOG(note, "vnode", "vnode ref_count=%d", rt_atomic_load(&(vnode->ref_count)));
#ifdef RT_USING_PAGECACHE
if (vnode->aspace)
{
dfs_aspace_destroy(vnode->aspace);
}
#endif
if (rt_atomic_load(&(vnode->ref_count)) == 0)
{
LOG_I("free a vnode: %p", vnode);
DLOG(msg, "vnode", "vnode", DLOG_MSG, "free vnode, ref_count=0");
if (vnode->mnt)
{
DLOG(msg, "vnode", vnode->mnt->fs_ops->name, DLOG_MSG, "fs_ops->free_vnode");
vnode->mnt->fs_ops->free_vnode(vnode);
}
dfs_file_unlock();
rt_free(vnode);
}
else
{
dfs_file_unlock();
DLOG(note, "vnode", "vnode ref_count=%d", rt_atomic_load(&(vnode->ref_count)));
}
}
}
return;
}