diff --git a/inc/dmod_sal.h b/inc/dmod_sal.h index 387fcb6..03e140a 100644 --- a/inc/dmod_sal.h +++ b/inc/dmod_sal.h @@ -105,9 +105,10 @@ DMOD_BUILTIN_API(Dmod, 1.0, size_t , _FileSize, ( void* File ) ); DMOD_BUILTIN_API(Dmod, 1.0, void , _FileClose, ( void* File ) ); DMOD_BUILTIN_API(Dmod, 1.0, const char* , _GetRepoDir, ( void ) ); DMOD_BUILTIN_API(Dmod, 1.0, bool , _FileAvailable, ( const char* Path ) ); -DMOD_BUILTIN_API(Dmod, 1.0, void* , _OpenDir, ( const char* Path ) ); -DMOD_BUILTIN_API(Dmod, 1.0, const char* , _ReadDir, ( void* Dir ) ); -DMOD_BUILTIN_API(Dmod, 1.0, void , _CloseDir, ( void* Dir ) ); +DMOD_BUILTIN_API(Dmod, 1.0, void* , _OpenDir, ( const char* Path ) ); +DMOD_BUILTIN_API(Dmod, 1.0, const char* , _ReadDir, ( void* Dir ) ); +DMOD_BUILTIN_API(Dmod, 1.0, const Dmod_DirEntry_t* , _ReadDirEx, ( void* Dir ) ); +DMOD_BUILTIN_API(Dmod, 1.0, void , _CloseDir, ( void* Dir ) ); DMOD_BUILTIN_API(Dmod, 1.0, int , _MakeDir, ( const char* Path, int Mode ) ); DMOD_BUILTIN_API(Dmod, 1.0, int , _Access, ( const char* Path, int Mode ) ); DMOD_BUILTIN_API(Dmod, 1.0, char* , _FileReadLine, ( char* Buffer, int Size, void* File ) ); diff --git a/inc/dmod_types.h b/inc/dmod_types.h index 5f8e71c..b63544b 100644 --- a/inc/dmod_types.h +++ b/inc/dmod_types.h @@ -289,6 +289,29 @@ typedef struct void* _Data; //!< Internal data used for iteration } Dmod_ModuleNode_t; +/** + * @brief Directory entry type + */ +typedef enum +{ + Dmod_DirEntryType_Unknown = 0, //!< Unknown type + Dmod_DirEntryType_File, //!< Regular file + Dmod_DirEntryType_Dir, //!< Directory + Dmod_DirEntryType_Link, //!< Symbolic link + Dmod_DirEntryType_Other //!< Other type (socket, FIFO, etc.) +} Dmod_DirEntryType_t; + +/** + * @brief Directory entry structure + * + * Structure returned by Dmod_ReadDirEx containing information about a directory entry. + */ +typedef struct +{ + const char* name; //!< Name of the entry + Dmod_DirEntryType_t type; //!< Type of the entry +} Dmod_DirEntry_t; + #ifdef __cplusplus } #endif diff --git a/src/system/if/dmod_if_file.c b/src/system/if/dmod_if_file.c index 907c692..36f314e 100644 --- a/src/system/if/dmod_if_file.c +++ b/src/system/if/dmod_if_file.c @@ -244,6 +244,63 @@ DMOD_INPUT_WEAK_API_DECLARATION(Dmod, 1.0, const char*, _ReadDir, ( void* Dir )) #endif } +/** + * @brief Read directory entry with extended information + * + * @param Dir Pointer to directory handle + * + * @return Pointer to Dmod_DirEntry_t structure with entry information, NULL when no more entries + * + * @note The returned pointer points to static storage that may be overwritten by subsequent calls. + * This behavior is consistent with the POSIX readdir() function and the original Dmod_ReadDir(). + * Not thread-safe: use separate directory handles per thread. + */ +DMOD_INPUT_WEAK_API_DECLARATION(Dmod, 1.0, const Dmod_DirEntry_t*, _ReadDirEx, ( void* Dir )) +{ + #if DMOD_USE_DIRENT + static Dmod_DirEntry_t dirEntry; + struct dirent* entry = readdir((DIR*)Dir); + + if (entry == NULL) + { + return NULL; + } + + dirEntry.name = entry->d_name; + dirEntry.type = Dmod_DirEntryType_Unknown; + + // Determine entry type based on d_type if available + #if defined(_DIRENT_HAVE_D_TYPE) + // Use constants from dirent.h: DT_REG=8, DT_DIR=4, DT_LNK=10 + // Direct comparison is used for compatibility with systems where these + // constants might not be properly available in preprocessor context + switch (entry->d_type) + { + case 8: // DT_REG - Regular file + dirEntry.type = Dmod_DirEntryType_File; + break; + case 4: // DT_DIR - Directory + dirEntry.type = Dmod_DirEntryType_Dir; + break; + case 10: // DT_LNK - Symbolic link + dirEntry.type = Dmod_DirEntryType_Link; + break; + case 0: // DT_UNKNOWN + dirEntry.type = Dmod_DirEntryType_Unknown; + break; + default: // Other types (socket, FIFO, etc.) + dirEntry.type = Dmod_DirEntryType_Other; + break; + } + #endif + + return &dirEntry; + #else + DMOD_LOG_ERROR("Dmod_ReadDirEx interface not implemented\n"); + return NULL; + #endif +} + /** * @brief Close directory * diff --git a/tests/system/if/tests_dmod_dir.cpp b/tests/system/if/tests_dmod_dir.cpp index fa41a0b..bbda019 100644 --- a/tests/system/if/tests_dmod_dir.cpp +++ b/tests/system/if/tests_dmod_dir.cpp @@ -207,3 +207,138 @@ TEST_F(DmodDirTest, RemoveDirNonExistent) int result = Dmod_RemoveDir("/non/existent/path/xyz123456"); ASSERT_EQ(result, -1); } + +/** + * @brief Test for Dmod_ReadDirEx + * + * The test checks if the function can read directory entries with extended information. + */ +TEST_F(DmodDirTest, ReadDirEx) +{ + // Use current directory which should have some entries + void* dir = Dmod_OpenDir("."); + + ASSERT_NE(dir, nullptr); + + // Read at least one entry + const Dmod_DirEntry_t* entry = Dmod_ReadDirEx(dir); + ASSERT_NE(entry, nullptr); + + // Entry name should not be empty + ASSERT_NE(entry->name, nullptr); + ASSERT_GT(strlen(entry->name), 0); + + // Entry type should be valid (even if unknown) + ASSERT_GE(entry->type, Dmod_DirEntryType_Unknown); + ASSERT_LE(entry->type, Dmod_DirEntryType_Other); + + Dmod_CloseDir(dir); +} + +/** + * @brief Test for Dmod_ReadDirEx reaching end + * + * The test checks if the function returns NULL when all entries are read. + */ +TEST_F(DmodDirTest, ReadDirExEnd) +{ + // Create empty test directory + Dmod_MakeDir(testDirPath, 0755); + + void* dir = Dmod_OpenDir(testDirPath); + ASSERT_NE(dir, nullptr); + + // Read all entries (should be . and .. at minimum) + int count = 0; + const Dmod_DirEntry_t* entry; + while ((entry = Dmod_ReadDirEx(dir)) != NULL && count < 100) + { + count++; + } + + // Should have read at least . and .. + ASSERT_GE(count, 2); + + // Reading past the end should return NULL + ASSERT_EQ(Dmod_ReadDirEx(dir), nullptr); + + Dmod_CloseDir(dir); +} + +/** + * @brief Test for Dmod_ReadDirEx with directory type detection + * + * The test checks if the function correctly identifies directory types. + */ +TEST_F(DmodDirTest, ReadDirExTypeDetection) +{ + // Use current directory which should have some entries + void* dir = Dmod_OpenDir("."); + + ASSERT_NE(dir, nullptr); + + bool foundDir = false; + bool foundAnyType = false; + const Dmod_DirEntry_t* entry; + + // Read entries and look for directories (. and .. should be present) + while ((entry = Dmod_ReadDirEx(dir)) != NULL) + { + if (entry->type == Dmod_DirEntryType_Dir) + { + foundDir = true; + foundAnyType = true; + break; + } + if (entry->type != Dmod_DirEntryType_Unknown) + { + foundAnyType = true; + } + } + + // Should find at least one directory (. or ..) OR have type detection available + // Note: On some filesystems, d_type might return DT_UNKNOWN for all entries + // In this case, we just verify that the function works without crashing + if (foundAnyType) + { + ASSERT_TRUE(foundDir); + } + + Dmod_CloseDir(dir); +} + +/** + * @brief Test backward compatibility between Dmod_ReadDir and Dmod_ReadDirEx + * + * The test ensures both functions work on the same directory and return the same entries. + */ +TEST_F(DmodDirTest, BackwardCompatibility) +{ + // Create test directory with a known file + Dmod_MakeDir(testDirPath, 0755); + + // Count entries using old API + void* dir1 = Dmod_OpenDir(testDirPath); + ASSERT_NE(dir1, nullptr); + + int countOld = 0; + while (Dmod_ReadDir(dir1) != NULL && countOld < 100) + { + countOld++; + } + Dmod_CloseDir(dir1); + + // Count entries using new API + void* dir2 = Dmod_OpenDir(testDirPath); + ASSERT_NE(dir2, nullptr); + + int countNew = 0; + while (Dmod_ReadDirEx(dir2) != NULL && countNew < 100) + { + countNew++; + } + Dmod_CloseDir(dir2); + + // Both should return the same count + ASSERT_EQ(countOld, countNew); +}