diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d299e60..c11c56b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Build dmffs project + - name: Build dmramfs project run: | mkdir -p build cd build @@ -31,24 +31,6 @@ jobs: cmake ../build/_deps/dmod-src -DDMOD_MODE=DMOD_SYSTEM cmake --build . --target dmod_loader - - name: Prepare test filesystem - run: | - mkdir -p /tmp/flashfs/subdir - echo "Hello from DMFFS!" > /tmp/flashfs/hello.txt - echo "Test file 1" > /tmp/flashfs/test1.txt - echo "File in subdirectory" > /tmp/flashfs/subdir/nested.txt - echo "Config data" > /tmp/flashfs/config.cfg - echo "Test filesystem created:" - find /tmp/flashfs -type f -exec echo " {}" \; - - - name: Create filesystem image with make_dmffs - run: | - ./build_dmod/examples/system/dmod_loader/dmod_loader \ - ./build/dmf/make_dmffs.dmf \ - --args /tmp/flashfs /tmp/flash-fs.ffs - echo "Filesystem image created:" - ls -lh /tmp/flash-fs.ffs - - name: Clone and build dmvfs run: | cd /tmp @@ -56,18 +38,13 @@ jobs: cd dmvfs mkdir build cd build - cmake .. -DDMOD_MODE=DMOD_SYSTEM -DDMOD_MEMORY="08080000:/tmp/flash-fs.ffs" -DDMVFS_BUILD_TESTS=ON + cmake .. -DDMOD_MODE=DMOD_SYSTEM -DDMVFS_BUILD_TESTS=ON cmake --build . echo "dmvfs built successfully, fs_tester available at:" ls -lh ./tests/fs_tester - name: Run filesystem tests run: | - export FLASH_FS_ADDR="0x08080000" - export FLASH_FS_SIZE="0x1000" - echo "Running filesystem tests in read-only mode..." - /tmp/dmvfs/build/tests/fs_tester --read-only-fs \ - --test-file /mnt/hello.txt \ - --test-dir /mnt \ - ./build/dmf/dmffs.dmf + echo "Running filesystem tests for dmramfs..." + /tmp/dmvfs/build/tests/fs_tester ./build/dmf/dmramfs.dmf echo "Filesystem test completed successfully" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f900931..018d051 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -64,7 +64,7 @@ jobs: echo "version=$VERSION" >> $GITHUB_OUTPUT echo "Extracted version: $VERSION" - - name: Build dmffs for ${{ matrix.arch_name }} + - name: Build dmramfs for ${{ matrix.arch_name }} run: | set -e ARCH_DIR_NAME=$(echo "${{ matrix.arch_name }}" | sed 's|/|-|') @@ -91,20 +91,12 @@ jobs: DMF_DIR="$BUILD_DIR/dmf" DMFC_DIR="$BUILD_DIR/dmfc" - cp $DMF_DIR/dmffs.dmf release_package/ - cp $DMF_DIR/dmffs_version.txt release_package/ - cp $DMFC_DIR/dmffs.dmfc release_package/ + cp $DMF_DIR/dmramfs.dmf release_package/ + cp $DMF_DIR/dmramfs_version.txt release_package/ + cp $DMFC_DIR/dmramfs.dmfc release_package/ # Copy .dmd file if it exists - if [ -f $DMF_DIR/dmffs.dmd ]; then - cp $DMF_DIR/dmffs.dmd release_package/ - fi - - cp $DMF_DIR/make_dmffs.dmf release_package/ - cp $DMF_DIR/make_dmffs_version.txt release_package/ - cp $DMFC_DIR/make_dmffs.dmfc release_package/ - # Copy .dmd file if it exists - if [ -f $DMF_DIR/make_dmffs.dmd ]; then - cp $DMF_DIR/make_dmffs.dmd release_package/ + if [ -f $DMF_DIR/dmramfs.dmd ]; then + cp $DMF_DIR/dmramfs.dmd release_package/ fi # Copy documentation and license @@ -120,16 +112,16 @@ jobs: - name: Create release archive run: | cd release_package - zip -r ../dmffs-${{ github.event.release.tag_name }}-$ARCH_DIR_NAME.zip . + zip -r ../dmramfs-${{ github.event.release.tag_name }}-$ARCH_DIR_NAME.zip . cd .. echo "Created archive:" - ls -lh dmffs-*.zip + ls -lh dmramfs-*.zip - name: Upload artifact uses: actions/upload-artifact@v4 with: name: ${{ env.ARTIFACT_NAME }} - path: dmffs-${{ github.event.release.tag_name }}-*.zip + path: dmramfs-${{ github.event.release.tag_name }}-*.zip retention-days: 1 generate-versions-manifest: @@ -148,7 +140,7 @@ jobs: - name: Generate versions.dmm run: | set -e - echo "# List of available versions for dmffs modules" > versions.dmm + echo "# List of available versions for dmramfs modules" > versions.dmm echo "# Generated automatically by CI" >> versions.dmm echo "" >> versions.dmm @@ -166,9 +158,8 @@ jobs: echo "Found versions: $VERSIONS" - # Add $version-available directives for both modules - echo "\$version-available dmffs $VERSIONS" >> versions.dmm - echo "\$version-available make_dmffs $VERSIONS" >> versions.dmm + # Add $version-available directives for the module + echo "\$version-available dmramfs $VERSIONS" >> versions.dmm echo "Generated versions.dmm:" cat versions.dmm diff --git a/.gitignore b/.gitignore index 8508d38..b268805 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,10 @@ build-*/ build_*/ cmake-build/* +# CodeQL analysis artifacts +_codeql_build_dir/ +_codeql_detected_source_root + # CLion # JetBrains specific template is maintained in a separate JetBrains.gitignore that can # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore diff --git a/README.md b/README.md index 8c9d53c..b959e7b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,106 @@ # dmramfs -DMOD Ram FS + +DMOD RAM File System - an in-memory file system module for the DMOD framework. + +## Overview + +`dmramfs` is a RAM-based file system implementation that provides a fully featured, transient file system stored entirely in memory. It implements the DMOD File System Interface (DMFSI) and can be used with the DMOD Virtual File System (DMVFS). + +## Features + +- **In-Memory Storage**: All files and directories are stored in RAM for fast access +- **Full File Operations**: Support for read, write, seek, truncate, and append operations +- **Directory Support**: Create, list, and navigate directories +- **File Management**: Rename, delete, and get file statistics +- **DMFSI Compliant**: Implements the standard DMOD file system interface + +## Dependencies + +This module requires the following system libraries: +- **dmlist** - DMOD linked list library (fetched automatically via CMake FetchContent) + +## Building + +```bash +mkdir build +cd build +cmake .. -DDMOD_MODE=DMOD_MODULE +cmake --build . +``` + +## Usage + +The module can be loaded and mounted using DMVFS: + +```c +#include "dmvfs.h" + +// Initialize DMVFS +dmvfs_init(16, 32); + +// Mount the RAM filesystem at /mnt +dmvfs_mount_fs("dmramfs", "/mnt", NULL); + +// Use standard file operations +void* fp; +dmvfs_fopen(&fp, "/mnt/test.txt", DMFSI_O_CREAT | DMFSI_O_RDWR, 0, 0); +dmvfs_fwrite(fp, "Hello, World!", 13, NULL); +dmvfs_fclose(fp); + +// Unmount when done +dmvfs_unmount_fs("/mnt"); +dmvfs_deinit(); +``` + +## API + +The module implements the full DMFSI interface: + +### File Operations +- `_fopen` - Open a file +- `_fclose` - Close a file +- `_fread` - Read from a file +- `_fwrite` - Write to a file +- `_lseek` - Seek to a position +- `_tell` - Get current position +- `_eof` - Check for end of file +- `_size` - Get file size +- `_getc` - Read a single character +- `_putc` - Write a single character +- `_sync` - Sync file (no-op for RAM FS) +- `_fflush` - Flush buffers (no-op for RAM FS) + +### Directory Operations +- `_mkdir` - Create a directory +- `_opendir` - Open a directory for reading +- `_readdir` - Read directory entries +- `_closedir` - Close a directory +- `_direxists` - Check if directory exists + +### File Management +- `_stat` - Get file/directory statistics +- `_unlink` - Delete a file +- `_rename` - Rename a file + +## Testing + +Tests are run using the `fs_tester` tool from the dmvfs repository: + +```bash +# Build dmvfs with tests +cd /path/to/dmvfs +mkdir build && cd build +cmake .. -DDMOD_MODE=DMOD_SYSTEM -DDMVFS_BUILD_TESTS=ON +cmake --build . + +# Run tests against dmramfs +./tests/fs_tester /path/to/dmramfs.dmf +``` + +## Known Issues + +When running `fs_tester`, you may see `[ERROR] Failed to close file` messages even though tests pass. This is due to a return value convention mismatch in the dmvfs layer (which expects boolean success/failure) versus the DMFSI interface convention (which uses 0 for success). The actual file operations work correctly despite these messages. + +## License + +See [LICENSE](LICENSE) file for details. diff --git a/src/dmramfs.c b/src/dmramfs.c index be8663d..42ede62 100644 --- a/src/dmramfs.c +++ b/src/dmramfs.c @@ -4,6 +4,7 @@ #include "dmramfs.h" #include "dmfsi.h" #include "dmlist.h" +#include /** * @brief Magic number for RAMFS context validation @@ -29,6 +30,7 @@ typedef struct file_t* file; int mode; int attribute; + size_t position; // Current read/write position } file_handle_t; /** @@ -41,6 +43,16 @@ typedef struct dir dmlist_context_t* dirs; } dir_t; +/** + * @brief Directory handle structure for reading directory entries + */ +typedef struct +{ + dir_t* dir; + size_t file_index; // Current index in files list + size_t dir_index; // Current index in dirs list +} dir_handle_t; + /** * @brief File system context structure */ @@ -54,12 +66,18 @@ struct dmfsi_context // ============================================================================ // Local Prototypes // ============================================================================ -static int compare_file_name (const void* a, const void* b); -static int compare_dir_name (const void* a, const void* b); -static file_t* find_file (dir_t* dir, dmfsi_path_t* path); -static dir_t* find_dir (dir_t* dir, dmfsi_path_t* path); -static file_t* create_file (dir_t* dir, dmfsi_path_t* path); -static file_handle_t* create_file_handle (file_t* file, int mode, int attribute); +static int compare_file_name (const void* a, const void* b); +static int compare_dir_name (const void* a, const void* b); +static int compare_handle_ptr (const void* a, const void* b); +static int compare_file_ptr (const void* a, const void* b); +static file_t* find_file (dir_t* dir, dmfsi_path_t* path); +static dir_t* find_dir (dir_t* dir, dmfsi_path_t* path); +static file_t* create_file (dir_t* dir, dmfsi_path_t* path); +static file_handle_t* create_file_handle (file_t* file, int mode, int attribute); +static dir_t* create_dir (dir_t* parent, dmfsi_path_t* path); +static dir_t* create_root_dir (void); +static void free_file (file_t* file); +static void free_dir (dir_t* dir); // ============================================================================ @@ -107,6 +125,13 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, dmfsi_context_t, _init, (const cha return NULL; } ctx->magic = DMRAMFS_CONTEXT_MAGIC; + ctx->root_dir = create_root_dir(); + if (ctx->root_dir == NULL) + { + DMOD_LOG_ERROR("dmramfs: Failed to create root directory\n"); + Dmod_Free(ctx); + return NULL; + } return ctx; } @@ -117,6 +142,10 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, int, _deinit, (dmfsi_context_t ctx { if (ctx) { + if (ctx->root_dir) + { + free_dir(ctx->root_dir); + } Dmod_Free(ctx); } return DMFSI_OK; @@ -178,8 +207,29 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, int, _fopen, (dmfsi_context_t ctx, */ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, int, _fclose, (dmfsi_context_t ctx, void* fp) ) { - // TODO: Implement file close - return DMFSI_ERR_GENERAL; + if(dmfsi_dmramfs_context_is_valid(ctx) == 0) + { + DMOD_LOG_ERROR("dmramfs: Invalid context in fclose\n"); + return DMFSI_ERR_INVALID; + } + + if (fp == NULL) + { + DMOD_LOG_ERROR("dmramfs: NULL file pointer in fclose\n"); + return DMFSI_ERR_INVALID; + } + + file_handle_t* handle = (file_handle_t*)fp; + file_t* file = handle->file; + + // Remove handle from file's handle list + if (file && file->handles) + { + dmlist_remove(file->handles, handle, compare_handle_ptr); + } + + Dmod_Free(handle); + return DMFSI_OK; } /** @@ -187,9 +237,39 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, int, _fclose, (dmfsi_context_t ctx */ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, int, _fread, (dmfsi_context_t ctx, void* fp, void* buffer, size_t size, size_t* read) ) { - // TODO: Implement file read - if (read) *read = 0; - return DMFSI_ERR_GENERAL; + if(dmfsi_dmramfs_context_is_valid(ctx) == 0) + { + DMOD_LOG_ERROR("dmramfs: Invalid context in fread\n"); + return DMFSI_ERR_INVALID; + } + + if (fp == NULL || buffer == NULL) + { + if (read) *read = 0; + return DMFSI_ERR_INVALID; + } + + file_handle_t* handle = (file_handle_t*)fp; + file_t* file = handle->file; + + if (file == NULL || file->data == NULL) + { + if (read) *read = 0; + return DMFSI_OK; // Empty file, nothing to read + } + + // Calculate how much we can read + size_t available = (handle->position < file->size) ? (file->size - handle->position) : 0; + size_t to_read = (size < available) ? size : available; + + if (to_read > 0) + { + memcpy(buffer, (char*)file->data + handle->position, to_read); + handle->position += to_read; + } + + if (read) *read = to_read; + return DMFSI_OK; } /** @@ -197,9 +277,69 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, int, _fread, (dmfsi_context_t ctx, */ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, int, _fwrite, (dmfsi_context_t ctx, void* fp, const void* buffer, size_t size, size_t* written) ) { - // TODO: Implement file write - if (written) *written = 0; - return DMFSI_ERR_GENERAL; + if(dmfsi_dmramfs_context_is_valid(ctx) == 0) + { + DMOD_LOG_ERROR("dmramfs: Invalid context in fwrite\n"); + return DMFSI_ERR_INVALID; + } + + if (fp == NULL || buffer == NULL) + { + if (written) *written = 0; + return DMFSI_ERR_INVALID; + } + + file_handle_t* handle = (file_handle_t*)fp; + file_t* file = handle->file; + + if (file == NULL) + { + if (written) *written = 0; + return DMFSI_ERR_INVALID; + } + + // Calculate new size needed + size_t end_position = handle->position + size; + + // Resize the file data buffer if needed + if (end_position > file->size) + { + void* new_data = Dmod_Malloc(end_position); + if (new_data == NULL) + { + DMOD_LOG_ERROR("dmramfs: Failed to allocate memory for file data\n"); + if (written) *written = 0; + return DMFSI_ERR_GENERAL; + } + + // Copy existing data if any + if (file->data && file->size > 0) + { + memcpy(new_data, file->data, file->size); + } + + // Zero-fill gap between old size and current position + if (handle->position > file->size) + { + memset((char*)new_data + file->size, 0, handle->position - file->size); + } + + // Free old data + if (file->data) + { + Dmod_Free(file->data); + } + + file->data = new_data; + file->size = end_position; + } + + // Write the data + memcpy((char*)file->data + handle->position, buffer, size); + handle->position += size; + + if (written) *written = size; + return DMFSI_OK; } /** @@ -207,8 +347,43 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, int, _fwrite, (dmfsi_context_t ctx */ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, long, _lseek, (dmfsi_context_t ctx, void* fp, long offset, int whence) ) { - // TODO: Implement file seek - return -1; + if(dmfsi_dmramfs_context_is_valid(ctx) == 0) + { + DMOD_LOG_ERROR("dmramfs: Invalid context in lseek\n"); + return -1; + } + + if (fp == NULL) + { + return -1; + } + + file_handle_t* handle = (file_handle_t*)fp; + file_t* file = handle->file; + long new_position; + + switch (whence) + { + case DMFSI_SEEK_SET: + new_position = offset; + break; + case DMFSI_SEEK_CUR: + new_position = (long)handle->position + offset; + break; + case DMFSI_SEEK_END: + new_position = (file ? (long)file->size : 0) + offset; + break; + default: + return -1; + } + + if (new_position < 0) + { + return -1; + } + + handle->position = (size_t)new_position; + return new_position; } /** @@ -234,8 +409,27 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, int, _sync, (dmfsi_context_t ctx, */ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, int, _getc, (dmfsi_context_t ctx, void* fp) ) { - // TODO: Implement getc - return -1; + if(dmfsi_dmramfs_context_is_valid(ctx) == 0) + { + return -1; + } + + if (fp == NULL) + { + return -1; + } + + file_handle_t* handle = (file_handle_t*)fp; + file_t* file = handle->file; + + if (file == NULL || file->data == NULL || handle->position >= file->size) + { + return -1; // EOF + } + + unsigned char c = ((unsigned char*)file->data)[handle->position]; + handle->position++; + return (int)c; } /** @@ -243,8 +437,26 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, int, _getc, (dmfsi_context_t ctx, */ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, int, _putc, (dmfsi_context_t ctx, void* fp, int c) ) { - // TODO: Implement putc - return -1; + if(dmfsi_dmramfs_context_is_valid(ctx) == 0) + { + return -1; + } + + if (fp == NULL) + { + return -1; + } + + unsigned char ch = (unsigned char)c; + size_t written = 0; + + int ret = dmfsi_dmramfs_fwrite(ctx, fp, &ch, 1, &written); + if (ret != DMFSI_OK || written != 1) + { + return -1; + } + + return c; } /** @@ -252,8 +464,18 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, int, _putc, (dmfsi_context_t ctx, */ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, long, _tell, (dmfsi_context_t ctx, void* fp) ) { - // TODO: Implement tell - return -1; + if(dmfsi_dmramfs_context_is_valid(ctx) == 0) + { + return -1; + } + + if (fp == NULL) + { + return -1; + } + + file_handle_t* handle = (file_handle_t*)fp; + return (long)handle->position; } /** @@ -261,8 +483,25 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, long, _tell, (dmfsi_context_t ctx, */ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, int, _eof, (dmfsi_context_t ctx, void* fp) ) { - // TODO: Implement EOF check - return -1; + if(dmfsi_dmramfs_context_is_valid(ctx) == 0) + { + return -1; + } + + if (fp == NULL) + { + return -1; + } + + file_handle_t* handle = (file_handle_t*)fp; + file_t* file = handle->file; + + if (file == NULL) + { + return 1; // Empty file is at EOF + } + + return (handle->position >= file->size) ? 1 : 0; } /** @@ -270,8 +509,25 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, int, _eof, (dmfsi_context_t ctx, v */ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, long, _size, (dmfsi_context_t ctx, void* fp) ) { - // TODO: Implement size - return -1; + if(dmfsi_dmramfs_context_is_valid(ctx) == 0) + { + return -1; + } + + if (fp == NULL) + { + return -1; + } + + file_handle_t* handle = (file_handle_t*)fp; + file_t* file = handle->file; + + if (file == NULL) + { + return 0; + } + + return (long)file->size; } /** @@ -288,8 +544,8 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, int, _fflush, (dmfsi_context_t ctx */ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, int, _error, (dmfsi_context_t ctx, void* fp) ) { - // TODO: Implement error reporting - return DMFSI_ERR_GENERAL; + // RAM filesystem doesn't track error state per handle + return DMFSI_OK; } /** @@ -297,8 +553,79 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, int, _error, (dmfsi_context_t ctx, */ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, int, _opendir, (dmfsi_context_t ctx, void** dp, const char* path) ) { - // TODO: Implement directory open - return DMFSI_ERR_GENERAL; + if(dmfsi_dmramfs_context_is_valid(ctx) == 0) + { + DMOD_LOG_ERROR("dmramfs: Invalid context in opendir\n"); + return DMFSI_ERR_INVALID; + } + + if (dp == NULL) + { + return DMFSI_ERR_INVALID; + } + + dir_t* dir = NULL; + + // Handle root directory case + if (path == NULL || strcmp(path, "/") == 0 || strcmp(path, "") == 0) + { + dir = ctx->root_dir; + } + else + { + // Remove leading slash if present + const char* search_path = path; + if (search_path[0] == '/') + { + search_path++; + } + + // Remove trailing slash if present + size_t len = strlen(search_path); + char* clean_path = dmfsi_strndup(search_path, len); + if (clean_path == NULL) + { + return DMFSI_ERR_GENERAL; + } + + if (len > 0 && clean_path[len - 1] == '/') + { + clean_path[len - 1] = '\0'; + } + + if (strlen(clean_path) == 0) + { + dir = ctx->root_dir; + } + else + { + dmfsi_path_t* p = dmfsi_path_create(clean_path); + if (p != NULL) + { + dir = find_dir(ctx->root_dir, p); + dmfsi_path_free(p); + } + } + Dmod_Free(clean_path); + } + + if (dir == NULL) + { + return DMFSI_ERR_NOT_FOUND; + } + + dir_handle_t* handle = Dmod_Malloc(sizeof(dir_handle_t)); + if (handle == NULL) + { + return DMFSI_ERR_GENERAL; + } + + handle->dir = dir; + handle->file_index = 0; + handle->dir_index = 0; + + *dp = handle; + return DMFSI_OK; } /** @@ -306,8 +633,19 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, int, _opendir, (dmfsi_context_t ct */ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, int, _closedir, (dmfsi_context_t ctx, void* dp) ) { - // TODO: Implement directory close - return DMFSI_ERR_GENERAL; + if(dmfsi_dmramfs_context_is_valid(ctx) == 0) + { + return DMFSI_ERR_INVALID; + } + + if (dp == NULL) + { + return DMFSI_ERR_INVALID; + } + + dir_handle_t* handle = (dir_handle_t*)dp; + Dmod_Free(handle); + return DMFSI_OK; } /** @@ -315,7 +653,59 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, int, _closedir, (dmfsi_context_t c */ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, int, _readdir, (dmfsi_context_t ctx, void* dp, dmfsi_dir_entry_t* entry) ) { - // TODO: Implement directory read + if(dmfsi_dmramfs_context_is_valid(ctx) == 0) + { + return DMFSI_ERR_INVALID; + } + + if (dp == NULL || entry == NULL) + { + return DMFSI_ERR_INVALID; + } + + dir_handle_t* handle = (dir_handle_t*)dp; + dir_t* dir = handle->dir; + + if (dir == NULL) + { + return DMFSI_ERR_NOT_FOUND; + } + + // First iterate through files + size_t file_count = dmlist_size(dir->files); + if (handle->file_index < file_count) + { + file_t* file = (file_t*)dmlist_get(dir->files, handle->file_index); + if (file != NULL) + { + strncpy(entry->name, file->file_name, sizeof(entry->name) - 1); + entry->name[sizeof(entry->name) - 1] = '\0'; + entry->size = (uint32_t)file->size; + entry->attr = 0; // Regular file + entry->time = 0; + handle->file_index++; + return DMFSI_OK; + } + } + + // Then iterate through subdirectories + size_t dir_count = dmlist_size(dir->dirs); + if (handle->dir_index < dir_count) + { + dir_t* subdir = (dir_t*)dmlist_get(dir->dirs, handle->dir_index); + if (subdir != NULL) + { + strncpy(entry->name, subdir->dir_name, sizeof(entry->name) - 1); + entry->name[sizeof(entry->name) - 1] = '\0'; + entry->size = 0; + entry->attr = 0x10; // Directory attribute + entry->time = 0; + handle->dir_index++; + return DMFSI_OK; + } + } + + // No more entries return DMFSI_ERR_NOT_FOUND; } @@ -324,8 +714,69 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, int, _readdir, (dmfsi_context_t ct */ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, int, _stat, (dmfsi_context_t ctx, const char* path, dmfsi_stat_t* stat) ) { - // TODO: Implement stat - return DMFSI_ERR_GENERAL; + if(dmfsi_dmramfs_context_is_valid(ctx) == 0) + { + DMOD_LOG_ERROR("dmramfs: Invalid context in stat\n"); + return DMFSI_ERR_INVALID; + } + + if (path == NULL || stat == NULL) + { + return DMFSI_ERR_INVALID; + } + + // Remove leading slash if present + const char* search_path = path; + if (search_path[0] == '/') + { + search_path++; + } + + if (strlen(search_path) == 0) + { + // Root directory stat + stat->size = 0; + stat->attr = 0x10; // Directory + stat->ctime = 0; + stat->mtime = 0; + stat->atime = 0; + return DMFSI_OK; + } + + dmfsi_path_t* p = dmfsi_path_create(search_path); + if (p == NULL) + { + return DMFSI_ERR_INVALID; + } + + // Try to find as file first + file_t* file = find_file(ctx->root_dir, p); + if (file != NULL) + { + stat->size = (uint32_t)file->size; + stat->attr = 0; // Regular file + stat->ctime = 0; + stat->mtime = 0; + stat->atime = 0; + dmfsi_path_free(p); + return DMFSI_OK; + } + + // Try to find as directory + dir_t* dir = find_dir(ctx->root_dir, p); + if (dir != NULL) + { + stat->size = 0; + stat->attr = 0x10; // Directory + stat->ctime = 0; + stat->mtime = 0; + stat->atime = 0; + dmfsi_path_free(p); + return DMFSI_OK; + } + + dmfsi_path_free(p); + return DMFSI_ERR_NOT_FOUND; } /** @@ -333,26 +784,198 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, int, _stat, (dmfsi_context_t ctx, */ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, int, _unlink, (dmfsi_context_t ctx, const char* path) ) { - // TODO: Implement file deletion - return DMFSI_ERR_GENERAL; -} - -/** - * @brief Rename a file or directory - */ -dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, int, _rename, (dmfsi_context_t ctx, const char* oldpath, const char* newpath) ) -{ - // TODO: Implement rename - return DMFSI_ERR_GENERAL; -} - + if(dmfsi_dmramfs_context_is_valid(ctx) == 0) + { + DMOD_LOG_ERROR("dmramfs: Invalid context in unlink\n"); + return DMFSI_ERR_INVALID; + } + + if (path == NULL) + { + return DMFSI_ERR_INVALID; + } + + // Remove leading slash if present + const char* search_path = path; + if (search_path[0] == '/') + { + search_path++; + } + + dmfsi_path_t* p = dmfsi_path_create(search_path); + if (p == NULL) + { + return DMFSI_ERR_INVALID; + } + + // Find the parent directory and file + dir_t* parent_dir = ctx->root_dir; + dmfsi_path_t* current = p; + + // Navigate to parent directory + while (current->directory != NULL && current->next != NULL) + { + dir_t* subdir = dmlist_find(parent_dir->dirs, current->directory, compare_dir_name); + if (subdir == NULL) + { + dmfsi_path_free(p); + return DMFSI_ERR_NOT_FOUND; + } + parent_dir = subdir; + current = current->next; + } + + // Find and remove the file + const char* filename = (current->filename != NULL) ? current->filename : current->directory; + if (filename == NULL) + { + dmfsi_path_free(p); + return DMFSI_ERR_NOT_FOUND; + } + + file_t* file = dmlist_find(parent_dir->files, filename, compare_file_name); + if (file == NULL) + { + dmfsi_path_free(p); + return DMFSI_ERR_NOT_FOUND; + } + + // Check if file has open handles + if (file->handles && dmlist_size(file->handles) > 0) + { + dmfsi_path_free(p); + return DMFSI_ERR_INVALID; // File is in use + } + + // Remove from list and free + dmlist_remove(parent_dir->files, file, compare_file_ptr); + free_file(file); + + dmfsi_path_free(p); + return DMFSI_OK; +} + +/** + * @brief Rename a file or directory + */ +dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, int, _rename, (dmfsi_context_t ctx, const char* oldpath, const char* newpath) ) +{ + if(dmfsi_dmramfs_context_is_valid(ctx) == 0) + { + DMOD_LOG_ERROR("dmramfs: Invalid context in rename\n"); + return DMFSI_ERR_INVALID; + } + + if (oldpath == NULL || newpath == NULL) + { + return DMFSI_ERR_INVALID; + } + + // Remove leading slashes + const char* old_search = (oldpath[0] == '/') ? oldpath + 1 : oldpath; + const char* new_search = (newpath[0] == '/') ? newpath + 1 : newpath; + + dmfsi_path_t* old_p = dmfsi_path_create(old_search); + if (old_p == NULL) + { + return DMFSI_ERR_INVALID; + } + + // Find the file + file_t* file = find_file(ctx->root_dir, old_p); + if (file == NULL) + { + dmfsi_path_free(old_p); + return DMFSI_ERR_NOT_FOUND; + } + + // Extract just the filename from the new path + dmfsi_path_t* new_p = dmfsi_path_create(new_search); + if (new_p == NULL) + { + dmfsi_path_free(old_p); + return DMFSI_ERR_INVALID; + } + + // Find the new filename (traverse to the end) + dmfsi_path_t* current = new_p; + while (current->next != NULL) + { + current = current->next; + } + + const char* new_name = (current->filename != NULL) ? current->filename : current->directory; + if (new_name == NULL) + { + dmfsi_path_free(old_p); + dmfsi_path_free(new_p); + return DMFSI_ERR_INVALID; + } + + // Update the filename + char* old_name = file->file_name; + file->file_name = dmfsi_strndup(new_name, strlen(new_name)); + if (file->file_name == NULL) + { + file->file_name = old_name; // Restore on failure + dmfsi_path_free(old_p); + dmfsi_path_free(new_p); + return DMFSI_ERR_GENERAL; + } + + Dmod_Free(old_name); + dmfsi_path_free(old_p); + dmfsi_path_free(new_p); + return DMFSI_OK; +} + /** * @brief Change file mode/permissions */ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, int, _chmod, (dmfsi_context_t ctx, const char* path, int mode) ) { - // TODO: Implement chmod - return DMFSI_ERR_GENERAL; + // RAM filesystem doesn't support permissions + // But we should verify the file exists + if(dmfsi_dmramfs_context_is_valid(ctx) == 0) + { + return DMFSI_ERR_INVALID; + } + + if (path == NULL) + { + return DMFSI_ERR_INVALID; + } + + // Remove leading slash if present + const char* search_path = path; + if (search_path[0] == '/') + { + search_path++; + } + + if (strlen(search_path) == 0) + { + return DMFSI_OK; // Root directory + } + + dmfsi_path_t* p = dmfsi_path_create(search_path); + if (p == NULL) + { + return DMFSI_ERR_INVALID; + } + + // Check if file or directory exists + file_t* file = find_file(ctx->root_dir, p); + dir_t* dir = (file == NULL) ? find_dir(ctx->root_dir, p) : NULL; + + dmfsi_path_free(p); + + if (file == NULL && dir == NULL) + { + return DMFSI_ERR_NOT_FOUND; + } + + return DMFSI_OK; } /** @@ -360,8 +983,48 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, int, _chmod, (dmfsi_context_t ctx, */ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, int, _utime, (dmfsi_context_t ctx, const char* path, uint32_t atime, uint32_t mtime) ) { - // TODO: Implement utime - return DMFSI_ERR_GENERAL; + // RAM filesystem doesn't track timestamps + // But we should verify the file exists + if(dmfsi_dmramfs_context_is_valid(ctx) == 0) + { + return DMFSI_ERR_INVALID; + } + + if (path == NULL) + { + return DMFSI_ERR_INVALID; + } + + // Remove leading slash if present + const char* search_path = path; + if (search_path[0] == '/') + { + search_path++; + } + + if (strlen(search_path) == 0) + { + return DMFSI_OK; // Root directory + } + + dmfsi_path_t* p = dmfsi_path_create(search_path); + if (p == NULL) + { + return DMFSI_ERR_INVALID; + } + + // Check if file or directory exists + file_t* file = find_file(ctx->root_dir, p); + dir_t* dir = (file == NULL) ? find_dir(ctx->root_dir, p) : NULL; + + dmfsi_path_free(p); + + if (file == NULL && dir == NULL) + { + return DMFSI_ERR_NOT_FOUND; + } + + return DMFSI_OK; } /** @@ -369,8 +1032,53 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, int, _utime, (dmfsi_context_t ctx, */ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, int, _mkdir, (dmfsi_context_t ctx, const char* path, int mode) ) { - // TODO: Implement directory creation - return DMFSI_ERR_GENERAL; + if(dmfsi_dmramfs_context_is_valid(ctx) == 0) + { + DMOD_LOG_ERROR("dmramfs: Invalid context in mkdir\n"); + return DMFSI_ERR_INVALID; + } + + if (path == NULL) + { + return DMFSI_ERR_INVALID; + } + + // Remove leading slash if present + const char* search_path = path; + if (search_path[0] == '/') + { + search_path++; + } + + if (strlen(search_path) == 0) + { + return DMFSI_ERR_INVALID; // Can't create root + } + + dmfsi_path_t* p = dmfsi_path_create(search_path); + if (p == NULL) + { + return DMFSI_ERR_INVALID; + } + + // Check if already exists + dir_t* existing = find_dir(ctx->root_dir, p); + if (existing != NULL) + { + dmfsi_path_free(p); + return DMFSI_OK; // Already exists + } + + // Create the directory + dir_t* new_dir = create_dir(ctx->root_dir, p); + dmfsi_path_free(p); + + if (new_dir == NULL) + { + return DMFSI_ERR_GENERAL; + } + + return DMFSI_OK; } /** @@ -378,8 +1086,60 @@ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, int, _mkdir, (dmfsi_context_t ctx, */ dmod_dmfsi_dif_api_declaration( 1.0, dmramfs, int, _direxists, (dmfsi_context_t ctx, const char* path) ) { - // TODO: Implement directory existence check - return 0; + if(dmfsi_dmramfs_context_is_valid(ctx) == 0) + { + return 0; + } + + if (path == NULL) + { + return 0; + } + + // Handle root directory + if (strcmp(path, "/") == 0 || strcmp(path, "") == 0) + { + return 1; + } + + // Remove leading slash if present + const char* search_path = path; + if (search_path[0] == '/') + { + search_path++; + } + + // Remove trailing slash if present + size_t len = strlen(search_path); + char* clean_path = dmfsi_strndup(search_path, len); + if (clean_path == NULL) + { + return 0; + } + + if (len > 0 && clean_path[len - 1] == '/') + { + clean_path[len - 1] = '\0'; + } + + if (strlen(clean_path) == 0) + { + Dmod_Free(clean_path); + return 1; // Root directory + } + + dmfsi_path_t* p = dmfsi_path_create(clean_path); + Dmod_Free(clean_path); + + if (p == NULL) + { + return 0; + } + + dir_t* dir = find_dir(ctx->root_dir, p); + dmfsi_path_free(p); + + return (dir != NULL) ? 1 : 0; } // ============================================================================ @@ -407,38 +1167,50 @@ static int compare_dir_name(const void* a, const void* b) return strcmp(dir_a->dir_name, name_b); } +/** + * @brief Compare file handle pointers + */ +static int compare_handle_ptr(const void* a, const void* b) +{ + return (a == b) ? 0 : 1; +} + +/** + * @brief Compare file pointers + */ +static int compare_file_ptr(const void* a, const void* b) +{ + return (a == b) ? 0 : 1; +} + /** * @brief Find a file by its path */ static file_t* find_file(dir_t* dir, dmfsi_path_t* path) { + if (dir == NULL || path == NULL) + { + return NULL; + } + if(path->filename != NULL) { file_t* file = dmlist_find(dir->files, path->filename, compare_file_name); - if(file == NULL) - { - dir_t* subdir = dmlist_find(dir->dirs, path->filename, compare_dir_name); - if(subdir != NULL) - { - DMOD_LOG_ERROR("dmramfs: Path '%s' is a directory, not a file\n", path->filename); - } - else - { - DMOD_LOG_ERROR("dmramfs: File '%s' not found in path\n", path->filename); - } - } + return file; // Return the found file (or NULL if not found) } else if(path->directory != NULL) { + // Handle empty directory (root path like /file.txt) + if (strlen(path->directory) == 0 && path->next != NULL) + { + return find_file(dir, path->next); + } + dir_t* subdir = dmlist_find(dir->dirs, path->directory, compare_dir_name); if(subdir != NULL && path->next != NULL) { return find_file(subdir, path->next); } - else - { - DMOD_LOG_ERROR("dmramfs: Directory '%s' not found in path\n", path->directory); - } } return NULL; } @@ -448,25 +1220,37 @@ static file_t* find_file(dir_t* dir, dmfsi_path_t* path) */ static dir_t* find_dir(dir_t* dir, dmfsi_path_t* path) { - if(path->directory != NULL) + if (dir == NULL || path == NULL) { - dir_t* subdir = dmlist_find(dir->dirs, path->directory, compare_dir_name); - if(subdir != NULL) + return NULL; + } + + // Handle case where path ends with a filename (treat as dir name for direxists) + const char* name = (path->directory != NULL) ? path->directory : path->filename; + if (name == NULL) + { + return NULL; + } + + // Handle empty directory (root path) + if (strlen(name) == 0 && path->next != NULL) + { + return find_dir(dir, path->next); + } + + dir_t* subdir = dmlist_find(dir->dirs, name, compare_dir_name); + if (subdir != NULL) + { + if (path->next != NULL) { - if(path->next != NULL) - { - return find_dir(subdir, path->next); - } - else - { - return subdir; - } + return find_dir(subdir, path->next); } - else + else { - DMOD_LOG_ERROR("dmramfs: Directory '%s' not found in path\n", path->directory); + return subdir; } } + return NULL; } @@ -492,10 +1276,11 @@ static file_t* create_file(dir_t* dir, dmfsi_path_t* path) file->data = NULL; file->size = 0; file->handles = dmlist_create(DMOD_MODULE_NAME); - if(dmlist_insert(dir->files, 0, file) != 0) + if(!dmlist_insert(dir->files, 0, file)) { DMOD_LOG_ERROR("dmramfs: Failed to insert new file '%s' into directory\n", path->filename); - Dmod_Free(file->file_name); + if (file->handles) dmlist_destroy(file->handles); + if (file->file_name) Dmod_Free(file->file_name); Dmod_Free(file); return NULL; } @@ -503,6 +1288,12 @@ static file_t* create_file(dir_t* dir, dmfsi_path_t* path) } else if(path->directory != NULL) { + // Handle empty directory (root path like /file.txt) + if (strlen(path->directory) == 0 && path->next != NULL) + { + return create_file(dir, path->next); + } + dir_t* subdir = dmlist_find(dir->dirs, path->directory, compare_dir_name); if(subdir == NULL) { @@ -538,6 +1329,24 @@ static file_handle_t* create_file_handle(file_t* file, int mode, int attribute) handle->file = file; handle->mode = mode; handle->attribute = attribute; + handle->position = 0; + + // Handle truncate mode + if ((mode & DMFSI_O_TRUNC) && file != NULL) + { + if (file->data) + { + Dmod_Free(file->data); + file->data = NULL; + } + file->size = 0; + } + + // Handle append mode - start at end of file + if ((mode & DMFSI_O_APPEND) && file != NULL) + { + handle->position = file->size; + } if(!dmlist_push_back(file->handles, handle)) { @@ -547,4 +1356,219 @@ static file_handle_t* create_file_handle(file_t* file, int mode, int attribute) } return handle; +} + +/** + * @brief Create the root directory + * + * @return dir_t* Pointer to the created root directory, or NULL on failure + */ +static dir_t* create_root_dir(void) +{ + dir_t* root = Dmod_Malloc(sizeof(dir_t)); + if (root == NULL) + { + DMOD_LOG_ERROR("dmramfs: Failed to allocate memory for root directory\n"); + return NULL; + } + + root->dir_name = dmfsi_strndup("/", 1); + root->files = dmlist_create(DMOD_MODULE_NAME); + root->dirs = dmlist_create(DMOD_MODULE_NAME); + + if (root->dir_name == NULL || root->files == NULL || root->dirs == NULL) + { + DMOD_LOG_ERROR("dmramfs: Failed to initialize root directory\n"); + if (root->dir_name) Dmod_Free(root->dir_name); + if (root->files) dmlist_destroy(root->files); + if (root->dirs) dmlist_destroy(root->dirs); + Dmod_Free(root); + return NULL; + } + + return root; +} + +/** + * @brief Create a directory at the specified path + * + * @param parent The parent directory + * @param path The path to create the directory at + * + * @return dir_t* Pointer to the created directory, or NULL on failure + */ +static dir_t* create_dir(dir_t* parent, dmfsi_path_t* path) +{ + if (path == NULL || parent == NULL) + { + return NULL; + } + + // If this is a filename entry, treat it as a directory name (for mkdir with no trailing slash) + const char* name = (path->filename != NULL) ? path->filename : path->directory; + if (name == NULL) + { + return NULL; + } + + // Handle empty directory (root path) + if (strlen(name) == 0 && path->next != NULL) + { + return create_dir(parent, path->next); + } + + // Check if it's the final component + if (path->filename != NULL || path->next == NULL) + { + // Check if directory already exists + dir_t* existing = dmlist_find(parent->dirs, name, compare_dir_name); + if (existing != NULL) + { + return existing; + } + + // Create new directory + dir_t* new_dir = Dmod_Malloc(sizeof(dir_t)); + if (new_dir == NULL) + { + DMOD_LOG_ERROR("dmramfs: Failed to allocate memory for directory '%s'\n", name); + return NULL; + } + + new_dir->dir_name = dmfsi_strndup(name, strlen(name)); + new_dir->files = dmlist_create(DMOD_MODULE_NAME); + new_dir->dirs = dmlist_create(DMOD_MODULE_NAME); + + if (new_dir->dir_name == NULL || new_dir->files == NULL || new_dir->dirs == NULL) + { + DMOD_LOG_ERROR("dmramfs: Failed to initialize directory '%s'\n", name); + if (new_dir->dir_name) Dmod_Free(new_dir->dir_name); + if (new_dir->files) dmlist_destroy(new_dir->files); + if (new_dir->dirs) dmlist_destroy(new_dir->dirs); + Dmod_Free(new_dir); + return NULL; + } + + if (!dmlist_insert(parent->dirs, 0, new_dir)) + { + DMOD_LOG_ERROR("dmramfs: Failed to insert directory '%s' into parent\n", name); + if (new_dir->dir_name) Dmod_Free(new_dir->dir_name); + if (new_dir->files) dmlist_destroy(new_dir->files); + if (new_dir->dirs) dmlist_destroy(new_dir->dirs); + Dmod_Free(new_dir); + return NULL; + } + + return new_dir; + } + else + { + // Navigate to or create intermediate directory + dir_t* subdir = dmlist_find(parent->dirs, name, compare_dir_name); + if (subdir == NULL) + { + // Create intermediate directory + subdir = Dmod_Malloc(sizeof(dir_t)); + if (subdir == NULL) + { + return NULL; + } + + subdir->dir_name = dmfsi_strndup(name, strlen(name)); + subdir->files = dmlist_create(DMOD_MODULE_NAME); + subdir->dirs = dmlist_create(DMOD_MODULE_NAME); + + if (!dmlist_insert(parent->dirs, 0, subdir)) + { + if (subdir->dir_name) Dmod_Free(subdir->dir_name); + if (subdir->files) dmlist_destroy(subdir->files); + if (subdir->dirs) dmlist_destroy(subdir->dirs); + Dmod_Free(subdir); + return NULL; + } + } + + return create_dir(subdir, path->next); + } +} + +/** + * @brief Free a file and all its resources + * + * @param file The file to free + */ +static void free_file(file_t* file) +{ + if (file == NULL) + { + return; + } + + if (file->file_name) + { + Dmod_Free(file->file_name); + } + + if (file->data) + { + Dmod_Free(file->data); + } + + if (file->handles) + { + // Free all handles + while (dmlist_size(file->handles) > 0) + { + file_handle_t* handle = (file_handle_t*)dmlist_front(file->handles); + dmlist_pop_front(file->handles); + if (handle) Dmod_Free(handle); + } + dmlist_destroy(file->handles); + } + + Dmod_Free(file); +} + +/** + * @brief Free a directory and all its contents recursively + * + * @param dir The directory to free + */ +static void free_dir(dir_t* dir) +{ + if (dir == NULL) + { + return; + } + + // Free all files + if (dir->files) + { + while (dmlist_size(dir->files) > 0) + { + file_t* file = (file_t*)dmlist_front(dir->files); + dmlist_pop_front(dir->files); + free_file(file); + } + dmlist_destroy(dir->files); + } + + // Free all subdirectories recursively + if (dir->dirs) + { + while (dmlist_size(dir->dirs) > 0) + { + dir_t* subdir = (dir_t*)dmlist_front(dir->dirs); + dmlist_pop_front(dir->dirs); + free_dir(subdir); + } + dmlist_destroy(dir->dirs); + } + + if (dir->dir_name) + { + Dmod_Free(dir->dir_name); + } + + Dmod_Free(dir); } \ No newline at end of file