From 047de93a15d2a4874992fcf94ce517b5a6d8538e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 19 Nov 2025 19:14:48 +0000 Subject: [PATCH 1/5] Initial plan From 02f0acc44a426d0bb9d4d68070d92f7122e7814b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 19 Nov 2025 19:22:05 +0000 Subject: [PATCH 2/5] Add Dmod_ReadModules API to list loaded and available modules Co-authored-by: JohnAmadis <17320783+JohnAmadis@users.noreply.github.com> --- inc/dmod.h | 2 + inc/dmod_types.h | 17 ++++ src/system/dmod_system.c | 191 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 210 insertions(+) diff --git a/inc/dmod.h b/inc/dmod.h index 3c3732d..bab8057 100644 --- a/inc/dmod.h +++ b/inc/dmod.h @@ -126,6 +126,8 @@ DMOD_BUILTIN_API( Dmod, 1.0, size_t , _GetNumberOfPackages, ( void ) ); DMOD_BUILTIN_API( Dmod, 1.0, bool , _GetPackageInfo, ( uint32_t PackageIndex, char* outName, size_t NameMaxLength, size_t* outSize ) ); DMOD_BUILTIN_API( Dmod, 1.0, uint32_t , _GetMainIndexFromPackage, ( const char* PackageName ) ); +DMOD_BUILTIN_API( Dmod, 1.0, size_t , _ReadModules, ( Dmod_ModuleInfo_t* outModules, size_t Max ) ); + //! @} #ifdef __cplusplus diff --git a/inc/dmod_types.h b/inc/dmod_types.h index d72587f..e9ee7bc 100644 --- a/inc/dmod_types.h +++ b/inc/dmod_types.h @@ -27,6 +27,23 @@ typedef enum Dmod_ModuleType_Count, } Dmod_ModuleType_t; +typedef enum +{ + Dmod_ModuleState_Available, //!< Module is available but not loaded + Dmod_ModuleState_Loaded, //!< Module is loaded but not enabled + Dmod_ModuleState_Enabled, //!< Module is enabled (library modules) + Dmod_ModuleState_Running, //!< Module is running (application modules) + + Dmod_ModuleState_Count, +} Dmod_ModuleState_t; + +typedef struct +{ + char ModuleName[DMOD_MAX_MODULE_NAME_LENGTH]; + char Version[DMOD_MAX_VERSION_LENGTH]; + Dmod_ModuleState_t State; +} Dmod_ModuleInfo_t; + typedef struct { uint32_t Size; diff --git a/src/system/dmod_system.c b/src/system/dmod_system.c index ae36342..f1a3701 100644 --- a/src/system/dmod_system.c +++ b/src/system/dmod_system.c @@ -1634,3 +1634,194 @@ static bool CheckModuleArchitecture( const char* FilePath, const char* ExpectedA return true; } +/** + * @brief Helper function to check if module is already in the list + * + * @param outModules Array of module info structures + * @param Count Current count of modules in the array + * @param ModuleName Name of the module to check + * + * @return True if module is already in the list, false otherwise + */ +static bool IsModuleInList( const Dmod_ModuleInfo_t* outModules, size_t Count, const char* ModuleName ) +{ + for( size_t i = 0; i < Count; i++ ) + { + if( strcmp( outModules[i].ModuleName, ModuleName ) == 0 ) + { + return true; + } + } + return false; +} + +/** + * @brief Helper function to add module info to the list + * + * @param outModules Array of module info structures + * @param Count Current count of modules in the array + * @param Max Maximum number of modules that can be stored + * @param ModuleName Name of the module + * @param Version Version of the module + * @param State State of the module + * + * @return True if module was added successfully, false if array is full or module already exists + */ +static bool AddModuleToList( Dmod_ModuleInfo_t* outModules, size_t* Count, size_t Max, + const char* ModuleName, const char* Version, Dmod_ModuleState_t State ) +{ + if( *Count >= Max ) + { + return false; + } + + if( IsModuleInList( outModules, *Count, ModuleName ) ) + { + return true; // Module already in list, skip + } + + strncpy( outModules[*Count].ModuleName, ModuleName, DMOD_MAX_MODULE_NAME_LENGTH - 1 ); + outModules[*Count].ModuleName[DMOD_MAX_MODULE_NAME_LENGTH - 1] = '\0'; + + strncpy( outModules[*Count].Version, Version, DMOD_MAX_VERSION_LENGTH - 1 ); + outModules[*Count].Version[DMOD_MAX_VERSION_LENGTH - 1] = '\0'; + + outModules[*Count].State = State; + (*Count)++; + + return true; +} + +/** + * @brief Read modules (loaded and available) + * + * This function lists all loaded modules and available modules in known search paths and packages. + * + * @param outModules Array of module info structures to fill + * @param Max Maximum number of modules that can be stored in the array + * + * @return Number of modules found (both loaded and available) + */ +size_t Dmod_ReadModules( Dmod_ModuleInfo_t* outModules, size_t Max ) +{ + if( outModules == NULL || Max == 0 ) + { + DMOD_LOG_ERROR("Cannot read modules - invalid parameters\n"); + return 0; + } + + size_t count = 0; + + // 1. Add loaded modules from Dmod_Contexts array + for( size_t i = 0; i < DMOD_MAX_MODULES; i++ ) + { + if( Dmod_Contexts[i] != NULL && Dmod_Contexts[i]->Header != NULL ) + { + Dmod_ModuleState_t state = Dmod_ModuleState_Loaded; + + if( Dmod_Contexts[i]->Running ) + { + state = Dmod_ModuleState_Running; + } + else if( Dmod_Contexts[i]->Enabled ) + { + state = Dmod_ModuleState_Enabled; + } + + if( !AddModuleToList( outModules, &count, Max, + Dmod_Contexts[i]->Header->Name, + Dmod_Contexts[i]->Header->Version, + state ) ) + { + return count; // Array is full + } + } + } + + // 2. Scan available modules in search paths + Dmod_SearchNode_t* searchNode = Dmod_Hlp_PrepareModulesSearchNodes(); + Dmod_SearchNode_t* currentNode = searchNode; + + while( currentNode != NULL && count < Max ) + { + const char* repoDir = currentNode->Path; + void* dir = Dmod_OpenDir( repoDir ); + + if( dir != NULL ) + { + const char* fileName; + while( (fileName = Dmod_ReadDir( dir )) != NULL && count < Max ) + { + // Check if it's a .dmf or .dmfc file + size_t len = strlen( fileName ); + bool isDmf = (len > 4 && strcmp( &fileName[len - 4], ".dmf" ) == 0); + bool isDmfc = (len > 5 && strcmp( &fileName[len - 5], ".dmfc" ) == 0); + + if( isDmf || isDmfc ) + { + // Extract module name (remove extension) + char moduleName[DMOD_MAX_MODULE_NAME_LENGTH]; + size_t nameLen = isDmf ? len - 4 : len - 5; + if( nameLen >= DMOD_MAX_MODULE_NAME_LENGTH ) + { + nameLen = DMOD_MAX_MODULE_NAME_LENGTH - 1; + } + strncpy( moduleName, fileName, nameLen ); + moduleName[nameLen] = '\0'; + + // Check if module is already loaded or in list + if( !IsModuleInList( outModules, count, moduleName ) ) + { + // Read module header to get version + char filePath[DMOD_MAX_FILE_PATH_LENGTH]; + Dmod_SnPrintf( filePath, sizeof(filePath), "%s/%s", repoDir, fileName ); + + Dmod_ModuleHeader_t header; + if( Dmod_ReadModuleHeader( filePath, &header ) ) + { + // Check architecture match + if( strcmp( header.Arch, DMOD_ARCH ) == 0 ) + { + AddModuleToList( outModules, &count, Max, + header.Name, header.Version, + Dmod_ModuleState_Available ); + } + } + } + } + } + Dmod_CloseDir( dir ); + } + + currentNode = currentNode->Prev; + } + + Dmod_Hlp_FreeSearchPathList( searchNode ); + + // 3. Add available modules from packages + for( size_t i = 0; i < DMOD_MAX_NUMBER_OF_PACKAGES && count < Max; i++ ) + { + if( Dmod_Pck_IsValidSlot( &Dmod_Packages[i] ) ) + { + Dmod_PackageSlot_t* slot = &Dmod_Packages[i]; + + for( size_t j = 0; j < slot->DmpHeader->ModuleCount && count < Max; j++ ) + { + const char* moduleName = slot->ModuleEntries[j].ModuleName; + + if( !IsModuleInList( outModules, count, moduleName ) ) + { + // For package modules, we need to read the header to get version + // This requires reading from the package, which is complex + // For now, we'll add them with empty version + AddModuleToList( outModules, &count, Max, + moduleName, "", + Dmod_ModuleState_Available ); + } + } + } + } + + return count; +} + From 9b52ff06a09b01a997f5c35efdf9d49107e8c9b2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 19 Nov 2025 19:25:16 +0000 Subject: [PATCH 3/5] Add list_modules example and fix package slot validation Co-authored-by: JohnAmadis <17320783+JohnAmadis@users.noreply.github.com> --- examples/system/CMakeLists.txt | 3 +- examples/system/list_modules/CMakeLists.txt | 26 ++++++ examples/system/list_modules/main.c | 89 +++++++++++++++++++++ src/system/dmod_system.c | 2 +- 4 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 examples/system/list_modules/CMakeLists.txt create mode 100644 examples/system/list_modules/main.c diff --git a/examples/system/CMakeLists.txt b/examples/system/CMakeLists.txt index d73141f..ab70d52 100644 --- a/examples/system/CMakeLists.txt +++ b/examples/system/CMakeLists.txt @@ -1,2 +1,3 @@ add_subdirectory(dif_test) -add_subdirectory(dmod_loader) \ No newline at end of file +add_subdirectory(dmod_loader) +add_subdirectory(list_modules) \ No newline at end of file diff --git a/examples/system/list_modules/CMakeLists.txt b/examples/system/list_modules/CMakeLists.txt new file mode 100644 index 0000000..fae9fe8 --- /dev/null +++ b/examples/system/list_modules/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.10) + +# Set the project name +project(list_modules VERSION ${dmod_VERSION_MAJOR}.${dmod_VERSION_MINOR}) + +# Specify the C++ standard +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +# Add the executable +add_executable(${PROJECT_NAME} main.c) + +# Include directories +include_directories(${PROJECT_SOURCE_DIR}) + +# Link libraries +target_link_libraries(${PROJECT_NAME} dmod) + +target_link_options(${PROJECT_NAME} PRIVATE -L ${DMOD_DIR}/scripts) +target_link_options(${PROJECT_NAME} PRIVATE -T ${DMOD_DIR}/examples/system/dmod_loader/main.ld) + +# Install the tool to the system bin directory +install(TARGETS ${PROJECT_NAME} + RUNTIME DESTINATION bin + COMPONENT tools +) diff --git a/examples/system/list_modules/main.c b/examples/system/list_modules/main.c new file mode 100644 index 0000000..3ce374f --- /dev/null +++ b/examples/system/list_modules/main.c @@ -0,0 +1,89 @@ +#include +#include +#include "dmod.h" + +/** + * @brief Simple test application to list all modules + * + * This application demonstrates the usage of Dmod_ReadModules API + */ +int main( int argc, char *argv[] ) +{ + // Initialize Dmod system + if (!Dmod_Initialize()) + { + printf("Error: Failed to initialize Dmod system\n"); + return -1; + } + + printf("=== DMOD Module Lister ===\n\n"); + + // If a module name is provided, try to load it first + if( argc > 1 ) + { + for( int i = 1; i < argc; i++ ) + { + printf("Loading module: %s\n", argv[i]); + if( Dmod_LoadModuleByName( argv[i] ) ) + { + printf(" Successfully loaded module: %s\n", argv[i]); + } + else + { + printf(" Failed to load module: %s\n", argv[i]); + } + } + printf("\n"); + } + + // Allocate array for module info + Dmod_ModuleInfo_t modules[100]; + size_t count = Dmod_ReadModules( modules, 100 ); + + printf("Found %zu modules:\n\n", count); + + if( count > 0 ) + { + // Print header + printf("%-30s %-15s %-15s\n", "Module Name", "Version", "State"); + printf("%-30s %-15s %-15s\n", "--------------------------------", "---------------", "---------------"); + + // Print each module + for( size_t i = 0; i < count; i++ ) + { + const char* stateStr = "Unknown"; + switch( modules[i].State ) + { + case Dmod_ModuleState_Available: + stateStr = "Available"; + break; + case Dmod_ModuleState_Loaded: + stateStr = "Loaded"; + break; + case Dmod_ModuleState_Enabled: + stateStr = "Enabled"; + break; + case Dmod_ModuleState_Running: + stateStr = "Running"; + break; + default: + stateStr = "Unknown"; + break; + } + + printf("%-30s %-15s %-15s\n", + modules[i].ModuleName, + modules[i].Version[0] != '\0' ? modules[i].Version : "N/A", + stateStr); + } + } + else + { + printf("No modules found. Try:\n"); + printf(" - Setting DMOD_REPO_PATHS environment variable to a directory with .dmf/.dmfc files\n"); + printf(" - Running with module name arguments: %s \n", argv[0]); + } + + printf("\n"); + return 0; +} diff --git a/src/system/dmod_system.c b/src/system/dmod_system.c index f1a3701..1bf9861 100644 --- a/src/system/dmod_system.c +++ b/src/system/dmod_system.c @@ -1801,7 +1801,7 @@ size_t Dmod_ReadModules( Dmod_ModuleInfo_t* outModules, size_t Max ) // 3. Add available modules from packages for( size_t i = 0; i < DMOD_MAX_NUMBER_OF_PACKAGES && count < Max; i++ ) { - if( Dmod_Pck_IsValidSlot( &Dmod_Packages[i] ) ) + if( Dmod_Pck_IsSlotUsed( &Dmod_Packages[i] ) && Dmod_Pck_IsValidSlot( &Dmod_Packages[i] ) ) { Dmod_PackageSlot_t* slot = &Dmod_Packages[i]; From a71af900485bdc1f0d7f433b30d68b1a9ca2a6e7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 19 Nov 2025 19:29:23 +0000 Subject: [PATCH 4/5] Add unit tests and fix null pointer crash in Dmod_Hlp_AddSearchNode Co-authored-by: JohnAmadis <17320783+JohnAmadis@users.noreply.github.com> --- src/system/private/dmod_hlp.c | 6 + tests/system/public/CMakeLists.txt | 1 + .../system/public/tests_dmod_read_modules.cpp | 116 ++++++++++++++++++ 3 files changed, 123 insertions(+) create mode 100644 tests/system/public/tests_dmod_read_modules.cpp diff --git a/src/system/private/dmod_hlp.c b/src/system/private/dmod_hlp.c index c2dc296..cbd4ecb 100644 --- a/src/system/private/dmod_hlp.c +++ b/src/system/private/dmod_hlp.c @@ -1,4 +1,5 @@ #define DMOD_PRIVATE +#include #include "dmod.h" #include "private/dmod_hlp.h" @@ -45,6 +46,11 @@ bool Dmod_Hlp_InitPointer( Dmod_Context_t* Context, void** PointerRef, const cha */ Dmod_SearchNode_t* Dmod_Hlp_AddSearchNode( Dmod_SearchNode_t* Tail, const char* Path ) { + if( Path == NULL ) + { + return Tail; + } + Dmod_SearchNode_t* newNode = (Dmod_SearchNode_t*)Dmod_Malloc( sizeof(Dmod_SearchNode_t) ); if( newNode == NULL ) { diff --git a/tests/system/public/CMakeLists.txt b/tests/system/public/CMakeLists.txt index f8724ec..25cbe92 100644 --- a/tests/system/public/CMakeLists.txt +++ b/tests/system/public/CMakeLists.txt @@ -3,5 +3,6 @@ set(TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/tests_dmod_dmfc_api.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tests_dmod_getname.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tests_dmod_arch.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tests_dmod_read_modules.cpp PARENT_SCOPE ) diff --git a/tests/system/public/tests_dmod_read_modules.cpp b/tests/system/public/tests_dmod_read_modules.cpp new file mode 100644 index 0000000..1d33d40 --- /dev/null +++ b/tests/system/public/tests_dmod_read_modules.cpp @@ -0,0 +1,116 @@ +#define DMOD_PRIVATE +#include +#include +#include +#include +#include "dmod.h" +#include "dmod_system.h" +#include "private/dmod_ctx.h" +#include "private/dmod_vars.h" + +// =============================================================== +// Test fixture +// =============================================================== + +class DmodReadModulesTest : public ::testing::Test { +protected: + void SetUp() override { + // Initialize Dmod system + Dmod_Initialize(); + } + + void TearDown() override { + // Clean up any loaded modules + for( size_t i = 0; i < DMOD_MAX_MODULES; i++ ) + { + if( Dmod_Contexts[i] != NULL ) + { + Dmod_Context_Delete( Dmod_Contexts[i] ); + Dmod_Contexts[i] = NULL; + } + } + } +}; + +// =============================================================== +// Tests +// =============================================================== + +/** + * @brief Test Dmod_ReadModules with NULL output buffer + */ +TEST_F(DmodReadModulesTest, ReadModulesNullOutput) { + size_t count = Dmod_ReadModules(NULL, 10); + EXPECT_EQ(count, 0); +} + +/** + * @brief Test Dmod_ReadModules with zero max size + */ +TEST_F(DmodReadModulesTest, ReadModulesZeroMax) { + Dmod_ModuleInfo_t modules[10]; + size_t count = Dmod_ReadModules(modules, 0); + EXPECT_EQ(count, 0); +} + +/** + * @brief Test Dmod_ReadModules with no modules loaded + */ +TEST_F(DmodReadModulesTest, ReadModulesNoModulesLoaded) { + Dmod_ModuleInfo_t modules[10]; + size_t count = Dmod_ReadModules(modules, 10); + // Should return 0 or more (depending on available modules in search paths) + EXPECT_GE(count, 0); +} + +/** + * @brief Test module info structure fields + */ +TEST_F(DmodReadModulesTest, ModuleInfoStructure) { + Dmod_ModuleInfo_t info; + + // Verify structure can hold expected data + strncpy(info.ModuleName, "test_module", DMOD_MAX_MODULE_NAME_LENGTH - 1); + info.ModuleName[DMOD_MAX_MODULE_NAME_LENGTH - 1] = '\0'; + + strncpy(info.Version, "1.0", DMOD_MAX_VERSION_LENGTH - 1); + info.Version[DMOD_MAX_VERSION_LENGTH - 1] = '\0'; + + info.State = Dmod_ModuleState_Available; + + EXPECT_STREQ(info.ModuleName, "test_module"); + EXPECT_STREQ(info.Version, "1.0"); + EXPECT_EQ(info.State, Dmod_ModuleState_Available); +} + +/** + * @brief Test module state enumeration + */ +TEST_F(DmodReadModulesTest, ModuleStateEnum) { + // Verify all states are defined + EXPECT_EQ(Dmod_ModuleState_Available, 0); + EXPECT_EQ(Dmod_ModuleState_Loaded, 1); + EXPECT_EQ(Dmod_ModuleState_Enabled, 2); + EXPECT_EQ(Dmod_ModuleState_Running, 3); +} + +/** + * @brief Test Dmod_ReadModules respects max limit + */ +TEST_F(DmodReadModulesTest, ReadModulesRespectsMaxLimit) { + Dmod_ModuleInfo_t modules[5]; + size_t count = Dmod_ReadModules(modules, 5); + // Should never return more than max + EXPECT_LE(count, 5); +} + +/** + * @brief Test Dmod_ReadModules with large array + */ +TEST_F(DmodReadModulesTest, ReadModulesLargeArray) { + Dmod_ModuleInfo_t modules[100]; + size_t count = Dmod_ReadModules(modules, 100); + // Should return a reasonable number + EXPECT_LE(count, 100); + EXPECT_GE(count, 0); +} From 0601708f5bf24c03247f61a6ff52fe9d2aecf714 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 19 Nov 2025 20:23:42 +0000 Subject: [PATCH 5/5] Refactor to iterator-based API pattern and add documentation - Changed from Dmod_ReadModules to OpenModules/ReadModule/CloseModules pattern - Added Doxygen documentation for Dmod_ModuleState_t enum - Added Doxygen documentation for Dmod_ModuleInfo_t struct - Added Dmod_ModulesIterator_t opaque handle type - Updated example application to use new API - Updated unit tests (8 tests passing) - Memory efficient: no large array allocation required Co-authored-by: JohnAmadis <17320783+JohnAmadis@users.noreply.github.com> --- examples/system/list_modules/main.c | 86 +++-- inc/dmod.h | 4 +- inc/dmod_types.h | 23 +- src/system/dmod_system.c | 336 +++++++++++------- .../system/public/tests_dmod_read_modules.cpp | 101 ++++-- 5 files changed, 364 insertions(+), 186 deletions(-) diff --git a/examples/system/list_modules/main.c b/examples/system/list_modules/main.c index 3ce374f..ac099f2 100644 --- a/examples/system/list_modules/main.c +++ b/examples/system/list_modules/main.c @@ -5,7 +5,7 @@ /** * @brief Simple test application to list all modules * - * This application demonstrates the usage of Dmod_ReadModules API + * This application demonstrates the usage of Dmod_OpenModules/ReadModule/CloseModules API */ int main( int argc, char *argv[] ) { @@ -36,53 +36,63 @@ int main( int argc, char *argv[] ) printf("\n"); } - // Allocate array for module info - Dmod_ModuleInfo_t modules[100]; - size_t count = Dmod_ReadModules( modules, 100 ); + // Open modules iterator + Dmod_ModulesIterator_t iterator = Dmod_OpenModules(); + if( iterator == NULL ) + { + printf("Error: Failed to open modules iterator\n"); + return -1; + } - printf("Found %zu modules:\n\n", count); + printf("Listing modules:\n\n"); + printf("%-30s %-15s %-15s\n", "Module Name", "Version", "State"); + printf("%-30s %-15s %-15s\n", "--------------------------------", "---------------", "---------------"); - if( count > 0 ) + // Iterate through modules + size_t count = 0; + const Dmod_ModuleInfo_t* module; + while( (module = Dmod_ReadModule( iterator )) != NULL ) { - // Print header - printf("%-30s %-15s %-15s\n", "Module Name", "Version", "State"); - printf("%-30s %-15s %-15s\n", "--------------------------------", "---------------", "---------------"); - - // Print each module - for( size_t i = 0; i < count; i++ ) + const char* stateStr = "Unknown"; + switch( module->State ) { - const char* stateStr = "Unknown"; - switch( modules[i].State ) - { - case Dmod_ModuleState_Available: - stateStr = "Available"; - break; - case Dmod_ModuleState_Loaded: - stateStr = "Loaded"; - break; - case Dmod_ModuleState_Enabled: - stateStr = "Enabled"; - break; - case Dmod_ModuleState_Running: - stateStr = "Running"; - break; - default: - stateStr = "Unknown"; - break; - } - - printf("%-30s %-15s %-15s\n", - modules[i].ModuleName, - modules[i].Version[0] != '\0' ? modules[i].Version : "N/A", - stateStr); + case Dmod_ModuleState_Available: + stateStr = "Available"; + break; + case Dmod_ModuleState_Loaded: + stateStr = "Loaded"; + break; + case Dmod_ModuleState_Enabled: + stateStr = "Enabled"; + break; + case Dmod_ModuleState_Running: + stateStr = "Running"; + break; + default: + stateStr = "Unknown"; + break; } + + printf("%-30s %-15s %-15s\n", + module->ModuleName, + module->Version[0] != '\0' ? module->Version : "N/A", + stateStr); + count++; } - else + + // Close iterator + Dmod_CloseModules( iterator ); + + if( count == 0 ) { - printf("No modules found. Try:\n"); + printf("\nNo modules found. Try:\n"); printf(" - Setting DMOD_REPO_PATHS environment variable to a directory with .dmf/.dmfc files\n"); printf(" - Running with module name arguments: %s \n", argv[0]); } + else + { + printf("\nTotal modules found: %zu\n", count); + } printf("\n"); return 0; diff --git a/inc/dmod.h b/inc/dmod.h index bab8057..bc63bce 100644 --- a/inc/dmod.h +++ b/inc/dmod.h @@ -126,7 +126,9 @@ DMOD_BUILTIN_API( Dmod, 1.0, size_t , _GetNumberOfPackages, ( void ) ); DMOD_BUILTIN_API( Dmod, 1.0, bool , _GetPackageInfo, ( uint32_t PackageIndex, char* outName, size_t NameMaxLength, size_t* outSize ) ); DMOD_BUILTIN_API( Dmod, 1.0, uint32_t , _GetMainIndexFromPackage, ( const char* PackageName ) ); -DMOD_BUILTIN_API( Dmod, 1.0, size_t , _ReadModules, ( Dmod_ModuleInfo_t* outModules, size_t Max ) ); +DMOD_BUILTIN_API( Dmod, 1.0, Dmod_ModulesIterator_t, _OpenModules, ( void ) ); +DMOD_BUILTIN_API( Dmod, 1.0, const Dmod_ModuleInfo_t*, _ReadModule, ( Dmod_ModulesIterator_t Iterator ) ); +DMOD_BUILTIN_API( Dmod, 1.0, void , _CloseModules, ( Dmod_ModulesIterator_t Iterator ) ); //! @} diff --git a/inc/dmod_types.h b/inc/dmod_types.h index e9ee7bc..adcb7d7 100644 --- a/inc/dmod_types.h +++ b/inc/dmod_types.h @@ -27,6 +27,11 @@ typedef enum Dmod_ModuleType_Count, } Dmod_ModuleType_t; +/** + * @brief Module state enumeration + * + * Describes the current state of a module in the system. + */ typedef enum { Dmod_ModuleState_Available, //!< Module is available but not loaded @@ -37,13 +42,25 @@ typedef enum Dmod_ModuleState_Count, } Dmod_ModuleState_t; +/** + * @brief Module information structure + * + * Contains information about a module including its name, version, and current state. + */ typedef struct { - char ModuleName[DMOD_MAX_MODULE_NAME_LENGTH]; - char Version[DMOD_MAX_VERSION_LENGTH]; - Dmod_ModuleState_t State; + char ModuleName[DMOD_MAX_MODULE_NAME_LENGTH]; //!< Name of the module + char Version[DMOD_MAX_VERSION_LENGTH]; //!< Version string of the module + Dmod_ModuleState_t State; //!< Current state of the module } Dmod_ModuleInfo_t; +/** + * @brief Modules iterator handle + * + * @note Opaque handle for iterating through modules. Use Dmod_OpenModules, Dmod_ReadModule, and Dmod_CloseModules. + */ +typedef void* Dmod_ModulesIterator_t; + typedef struct { uint32_t Size; diff --git a/src/system/dmod_system.c b/src/system/dmod_system.c index 1bf9861..0197531 100644 --- a/src/system/dmod_system.c +++ b/src/system/dmod_system.c @@ -1635,19 +1635,37 @@ static bool CheckModuleArchitecture( const char* FilePath, const char* ExpectedA } /** - * @brief Helper function to check if module is already in the list + * @brief Internal structure for modules iterator + */ +typedef struct +{ + size_t ContextIndex; //!< Current index in Dmod_Contexts array + Dmod_SearchNode_t* SearchNodeHead; //!< Head of search path list + Dmod_SearchNode_t* CurrentSearchNode; //!< Current search node being processed + void* CurrentDir; //!< Current directory handle + size_t PackageIndex; //!< Current package index + size_t PackageModuleIndex; //!< Current module index within package + bool LoadedPhaseComplete; //!< True when loaded modules phase is complete + bool SearchPathsPhaseComplete; //!< True when search paths phase is complete + bool PackagesPhaseComplete; //!< True when packages phase is complete + Dmod_ModuleInfo_t CurrentModule; //!< Buffer for current module info + char SeenModules[DMOD_MAX_MODULES][DMOD_MAX_MODULE_NAME_LENGTH]; //!< Track seen modules + size_t SeenModulesCount; //!< Number of seen modules +} Dmod_ModulesIteratorInternal_t; + +/** + * @brief Helper function to check if module was already seen * - * @param outModules Array of module info structures - * @param Count Current count of modules in the array + * @param Iterator Iterator state * @param ModuleName Name of the module to check * - * @return True if module is already in the list, false otherwise + * @return True if module was already seen, false otherwise */ -static bool IsModuleInList( const Dmod_ModuleInfo_t* outModules, size_t Count, const char* ModuleName ) +static bool IsModuleSeen( Dmod_ModulesIteratorInternal_t* Iterator, const char* ModuleName ) { - for( size_t i = 0; i < Count; i++ ) + for( size_t i = 0; i < Iterator->SeenModulesCount; i++ ) { - if( strcmp( outModules[i].ModuleName, ModuleName ) == 0 ) + if( strcmp( Iterator->SeenModules[i], ModuleName ) == 0 ) { return true; } @@ -1656,172 +1674,252 @@ static bool IsModuleInList( const Dmod_ModuleInfo_t* outModules, size_t Count, c } /** - * @brief Helper function to add module info to the list + * @brief Helper function to mark module as seen * - * @param outModules Array of module info structures - * @param Count Current count of modules in the array - * @param Max Maximum number of modules that can be stored - * @param ModuleName Name of the module - * @param Version Version of the module - * @param State State of the module - * - * @return True if module was added successfully, false if array is full or module already exists + * @param Iterator Iterator state + * @param ModuleName Name of the module to mark as seen */ -static bool AddModuleToList( Dmod_ModuleInfo_t* outModules, size_t* Count, size_t Max, - const char* ModuleName, const char* Version, Dmod_ModuleState_t State ) +static void MarkModuleSeen( Dmod_ModulesIteratorInternal_t* Iterator, const char* ModuleName ) { - if( *Count >= Max ) + if( Iterator->SeenModulesCount < DMOD_MAX_MODULES ) { - return false; + strncpy( Iterator->SeenModules[Iterator->SeenModulesCount], ModuleName, DMOD_MAX_MODULE_NAME_LENGTH - 1 ); + Iterator->SeenModules[Iterator->SeenModulesCount][DMOD_MAX_MODULE_NAME_LENGTH - 1] = '\0'; + Iterator->SeenModulesCount++; } +} - if( IsModuleInList( outModules, *Count, ModuleName ) ) +/** + * @brief Open modules iterator + * + * Creates an iterator for listing all loaded and available modules. + * Use Dmod_ReadModule to get the next module and Dmod_CloseModules to free resources. + * + * @return Modules iterator handle, or NULL on error + */ +Dmod_ModulesIterator_t Dmod_OpenModules( void ) +{ + Dmod_ModulesIteratorInternal_t* iterator = (Dmod_ModulesIteratorInternal_t*)Dmod_Malloc( sizeof(Dmod_ModulesIteratorInternal_t) ); + if( iterator == NULL ) { - return true; // Module already in list, skip + DMOD_LOG_ERROR("Cannot open modules iterator - out of memory\n"); + return NULL; } - strncpy( outModules[*Count].ModuleName, ModuleName, DMOD_MAX_MODULE_NAME_LENGTH - 1 ); - outModules[*Count].ModuleName[DMOD_MAX_MODULE_NAME_LENGTH - 1] = '\0'; - - strncpy( outModules[*Count].Version, Version, DMOD_MAX_VERSION_LENGTH - 1 ); - outModules[*Count].Version[DMOD_MAX_VERSION_LENGTH - 1] = '\0'; - - outModules[*Count].State = State; - (*Count)++; - - return true; + // Initialize iterator state + memset( iterator, 0, sizeof(Dmod_ModulesIteratorInternal_t) ); + iterator->ContextIndex = 0; + iterator->SearchNodeHead = Dmod_Hlp_PrepareModulesSearchNodes(); + iterator->CurrentSearchNode = iterator->SearchNodeHead; + iterator->CurrentDir = NULL; + iterator->PackageIndex = 0; + iterator->PackageModuleIndex = 0; + iterator->LoadedPhaseComplete = false; + iterator->SearchPathsPhaseComplete = false; + iterator->PackagesPhaseComplete = false; + iterator->SeenModulesCount = 0; + + return (Dmod_ModulesIterator_t)iterator; } /** - * @brief Read modules (loaded and available) + * @brief Read next module from iterator * - * This function lists all loaded modules and available modules in known search paths and packages. + * Returns the next module in the iteration. Returns NULL when no more modules are available. + * The returned pointer is valid until the next call to Dmod_ReadModule or Dmod_CloseModules. * - * @param outModules Array of module info structures to fill - * @param Max Maximum number of modules that can be stored in the array + * @param Iterator Modules iterator handle * - * @return Number of modules found (both loaded and available) + * @return Pointer to module info, or NULL if no more modules */ -size_t Dmod_ReadModules( Dmod_ModuleInfo_t* outModules, size_t Max ) +const Dmod_ModuleInfo_t* Dmod_ReadModule( Dmod_ModulesIterator_t Iterator ) { - if( outModules == NULL || Max == 0 ) + if( Iterator == NULL ) { - DMOD_LOG_ERROR("Cannot read modules - invalid parameters\n"); - return 0; + DMOD_LOG_ERROR("Cannot read module - invalid iterator\n"); + return NULL; } - size_t count = 0; + Dmod_ModulesIteratorInternal_t* iter = (Dmod_ModulesIteratorInternal_t*)Iterator; - // 1. Add loaded modules from Dmod_Contexts array - for( size_t i = 0; i < DMOD_MAX_MODULES; i++ ) + // Phase 1: Iterate through loaded modules + if( !iter->LoadedPhaseComplete ) { - if( Dmod_Contexts[i] != NULL && Dmod_Contexts[i]->Header != NULL ) + while( iter->ContextIndex < DMOD_MAX_MODULES ) { - Dmod_ModuleState_t state = Dmod_ModuleState_Loaded; - - if( Dmod_Contexts[i]->Running ) - { - state = Dmod_ModuleState_Running; - } - else if( Dmod_Contexts[i]->Enabled ) + if( Dmod_Contexts[iter->ContextIndex] != NULL && Dmod_Contexts[iter->ContextIndex]->Header != NULL ) { - state = Dmod_ModuleState_Enabled; - } + const char* moduleName = Dmod_Contexts[iter->ContextIndex]->Header->Name; + + if( !IsModuleSeen( iter, moduleName ) ) + { + // Prepare module info + Dmod_ModuleState_t state = Dmod_ModuleState_Loaded; + if( Dmod_Contexts[iter->ContextIndex]->Running ) + { + state = Dmod_ModuleState_Running; + } + else if( Dmod_Contexts[iter->ContextIndex]->Enabled ) + { + state = Dmod_ModuleState_Enabled; + } - if( !AddModuleToList( outModules, &count, Max, - Dmod_Contexts[i]->Header->Name, - Dmod_Contexts[i]->Header->Version, - state ) ) - { - return count; // Array is full + strncpy( iter->CurrentModule.ModuleName, moduleName, DMOD_MAX_MODULE_NAME_LENGTH - 1 ); + iter->CurrentModule.ModuleName[DMOD_MAX_MODULE_NAME_LENGTH - 1] = '\0'; + + strncpy( iter->CurrentModule.Version, Dmod_Contexts[iter->ContextIndex]->Header->Version, DMOD_MAX_VERSION_LENGTH - 1 ); + iter->CurrentModule.Version[DMOD_MAX_VERSION_LENGTH - 1] = '\0'; + + iter->CurrentModule.State = state; + + MarkModuleSeen( iter, moduleName ); + iter->ContextIndex++; + return &iter->CurrentModule; + } } + iter->ContextIndex++; } + iter->LoadedPhaseComplete = true; } - // 2. Scan available modules in search paths - Dmod_SearchNode_t* searchNode = Dmod_Hlp_PrepareModulesSearchNodes(); - Dmod_SearchNode_t* currentNode = searchNode; - - while( currentNode != NULL && count < Max ) + // Phase 2: Scan available modules in search paths + if( !iter->SearchPathsPhaseComplete ) { - const char* repoDir = currentNode->Path; - void* dir = Dmod_OpenDir( repoDir ); - - if( dir != NULL ) + while( iter->CurrentSearchNode != NULL ) { - const char* fileName; - while( (fileName = Dmod_ReadDir( dir )) != NULL && count < Max ) + if( iter->CurrentDir == NULL ) { - // Check if it's a .dmf or .dmfc file - size_t len = strlen( fileName ); - bool isDmf = (len > 4 && strcmp( &fileName[len - 4], ".dmf" ) == 0); - bool isDmfc = (len > 5 && strcmp( &fileName[len - 5], ".dmfc" ) == 0); - - if( isDmf || isDmfc ) + iter->CurrentDir = Dmod_OpenDir( iter->CurrentSearchNode->Path ); + } + + if( iter->CurrentDir != NULL ) + { + const char* fileName; + while( (fileName = Dmod_ReadDir( iter->CurrentDir )) != NULL ) { - // Extract module name (remove extension) - char moduleName[DMOD_MAX_MODULE_NAME_LENGTH]; - size_t nameLen = isDmf ? len - 4 : len - 5; - if( nameLen >= DMOD_MAX_MODULE_NAME_LENGTH ) + // Check if it's a .dmf or .dmfc file + size_t len = strlen( fileName ); + bool isDmf = (len > 4 && strcmp( &fileName[len - 4], ".dmf" ) == 0); + bool isDmfc = (len > 5 && strcmp( &fileName[len - 5], ".dmfc" ) == 0); + + if( isDmf || isDmfc ) { - nameLen = DMOD_MAX_MODULE_NAME_LENGTH - 1; - } - strncpy( moduleName, fileName, nameLen ); - moduleName[nameLen] = '\0'; + // Extract module name (remove extension) + char moduleName[DMOD_MAX_MODULE_NAME_LENGTH]; + size_t nameLen = isDmf ? len - 4 : len - 5; + if( nameLen >= DMOD_MAX_MODULE_NAME_LENGTH ) + { + nameLen = DMOD_MAX_MODULE_NAME_LENGTH - 1; + } + strncpy( moduleName, fileName, nameLen ); + moduleName[nameLen] = '\0'; - // Check if module is already loaded or in list - if( !IsModuleInList( outModules, count, moduleName ) ) - { - // Read module header to get version - char filePath[DMOD_MAX_FILE_PATH_LENGTH]; - Dmod_SnPrintf( filePath, sizeof(filePath), "%s/%s", repoDir, fileName ); - - Dmod_ModuleHeader_t header; - if( Dmod_ReadModuleHeader( filePath, &header ) ) + if( !IsModuleSeen( iter, moduleName ) ) { - // Check architecture match - if( strcmp( header.Arch, DMOD_ARCH ) == 0 ) + // Read module header to get version + char filePath[DMOD_MAX_FILE_PATH_LENGTH]; + Dmod_SnPrintf( filePath, sizeof(filePath), "%s/%s", iter->CurrentSearchNode->Path, fileName ); + + Dmod_ModuleHeader_t header; + if( Dmod_ReadModuleHeader( filePath, &header ) ) { - AddModuleToList( outModules, &count, Max, - header.Name, header.Version, - Dmod_ModuleState_Available ); + // Check architecture match + if( strcmp( header.Arch, DMOD_ARCH ) == 0 ) + { + strncpy( iter->CurrentModule.ModuleName, header.Name, DMOD_MAX_MODULE_NAME_LENGTH - 1 ); + iter->CurrentModule.ModuleName[DMOD_MAX_MODULE_NAME_LENGTH - 1] = '\0'; + + strncpy( iter->CurrentModule.Version, header.Version, DMOD_MAX_VERSION_LENGTH - 1 ); + iter->CurrentModule.Version[DMOD_MAX_VERSION_LENGTH - 1] = '\0'; + + iter->CurrentModule.State = Dmod_ModuleState_Available; + + MarkModuleSeen( iter, header.Name ); + return &iter->CurrentModule; + } } } } } } - Dmod_CloseDir( dir ); + + // Close current directory and move to next search node + if( iter->CurrentDir != NULL ) + { + Dmod_CloseDir( iter->CurrentDir ); + iter->CurrentDir = NULL; + } + iter->CurrentSearchNode = iter->CurrentSearchNode->Prev; } - - currentNode = currentNode->Prev; + iter->SearchPathsPhaseComplete = true; } - - Dmod_Hlp_FreeSearchPathList( searchNode ); - // 3. Add available modules from packages - for( size_t i = 0; i < DMOD_MAX_NUMBER_OF_PACKAGES && count < Max; i++ ) + // Phase 3: Add available modules from packages + if( !iter->PackagesPhaseComplete ) { - if( Dmod_Pck_IsSlotUsed( &Dmod_Packages[i] ) && Dmod_Pck_IsValidSlot( &Dmod_Packages[i] ) ) + while( iter->PackageIndex < DMOD_MAX_NUMBER_OF_PACKAGES ) { - Dmod_PackageSlot_t* slot = &Dmod_Packages[i]; - - for( size_t j = 0; j < slot->DmpHeader->ModuleCount && count < Max; j++ ) + if( Dmod_Pck_IsSlotUsed( &Dmod_Packages[iter->PackageIndex] ) && + Dmod_Pck_IsValidSlot( &Dmod_Packages[iter->PackageIndex] ) ) { - const char* moduleName = slot->ModuleEntries[j].ModuleName; + Dmod_PackageSlot_t* slot = &Dmod_Packages[iter->PackageIndex]; - if( !IsModuleInList( outModules, count, moduleName ) ) + while( iter->PackageModuleIndex < slot->DmpHeader->ModuleCount ) { - // For package modules, we need to read the header to get version - // This requires reading from the package, which is complex - // For now, we'll add them with empty version - AddModuleToList( outModules, &count, Max, - moduleName, "", - Dmod_ModuleState_Available ); + const char* moduleName = slot->ModuleEntries[iter->PackageModuleIndex].ModuleName; + + if( !IsModuleSeen( iter, moduleName ) ) + { + strncpy( iter->CurrentModule.ModuleName, moduleName, DMOD_MAX_MODULE_NAME_LENGTH - 1 ); + iter->CurrentModule.ModuleName[DMOD_MAX_MODULE_NAME_LENGTH - 1] = '\0'; + + iter->CurrentModule.Version[0] = '\0'; // No version info available for package modules + iter->CurrentModule.State = Dmod_ModuleState_Available; + + MarkModuleSeen( iter, moduleName ); + iter->PackageModuleIndex++; + return &iter->CurrentModule; + } + iter->PackageModuleIndex++; } + iter->PackageModuleIndex = 0; } + iter->PackageIndex++; } + iter->PackagesPhaseComplete = true; + } + + // No more modules + return NULL; +} + +/** + * @brief Close modules iterator and free resources + * + * @param Iterator Modules iterator handle + */ +void Dmod_CloseModules( Dmod_ModulesIterator_t Iterator ) +{ + if( Iterator == NULL ) + { + return; + } + + Dmod_ModulesIteratorInternal_t* iter = (Dmod_ModulesIteratorInternal_t*)Iterator; + + // Close any open directory + if( iter->CurrentDir != NULL ) + { + Dmod_CloseDir( iter->CurrentDir ); + } + + // Free search path list + if( iter->SearchNodeHead != NULL ) + { + Dmod_Hlp_FreeSearchPathList( iter->SearchNodeHead ); } - return count; + // Free iterator + Dmod_Free( iter ); } diff --git a/tests/system/public/tests_dmod_read_modules.cpp b/tests/system/public/tests_dmod_read_modules.cpp index 1d33d40..0a38207 100644 --- a/tests/system/public/tests_dmod_read_modules.cpp +++ b/tests/system/public/tests_dmod_read_modules.cpp @@ -37,30 +37,41 @@ class DmodReadModulesTest : public ::testing::Test { // =============================================================== /** - * @brief Test Dmod_ReadModules with NULL output buffer + * @brief Test Dmod_OpenModules with NULL iterator */ -TEST_F(DmodReadModulesTest, ReadModulesNullOutput) { - size_t count = Dmod_ReadModules(NULL, 10); - EXPECT_EQ(count, 0); +TEST_F(DmodReadModulesTest, OpenModulesSuccess) { + Dmod_ModulesIterator_t iterator = Dmod_OpenModules(); + EXPECT_NE(iterator, nullptr); + Dmod_CloseModules(iterator); } /** - * @brief Test Dmod_ReadModules with zero max size + * @brief Test Dmod_ReadModule with NULL iterator */ -TEST_F(DmodReadModulesTest, ReadModulesZeroMax) { - Dmod_ModuleInfo_t modules[10]; - size_t count = Dmod_ReadModules(modules, 0); - EXPECT_EQ(count, 0); +TEST_F(DmodReadModulesTest, ReadModuleNullIterator) { + const Dmod_ModuleInfo_t* module = Dmod_ReadModule(NULL); + EXPECT_EQ(module, nullptr); } /** - * @brief Test Dmod_ReadModules with no modules loaded + * @brief Test Dmod_ReadModule with no modules loaded */ TEST_F(DmodReadModulesTest, ReadModulesNoModulesLoaded) { - Dmod_ModuleInfo_t modules[10]; - size_t count = Dmod_ReadModules(modules, 10); + Dmod_ModulesIterator_t iterator = Dmod_OpenModules(); + ASSERT_NE(iterator, nullptr); + + // Count how many modules are available + size_t count = 0; + const Dmod_ModuleInfo_t* module; + while( (module = Dmod_ReadModule(iterator)) != NULL ) + { + count++; + } + // Should return 0 or more (depending on available modules in search paths) EXPECT_GE(count, 0); + + Dmod_CloseModules(iterator); } /** @@ -95,22 +106,62 @@ TEST_F(DmodReadModulesTest, ModuleStateEnum) { } /** - * @brief Test Dmod_ReadModules respects max limit + * @brief Test Dmod_CloseModules with NULL iterator */ -TEST_F(DmodReadModulesTest, ReadModulesRespectsMaxLimit) { - Dmod_ModuleInfo_t modules[5]; - size_t count = Dmod_ReadModules(modules, 5); - // Should never return more than max - EXPECT_LE(count, 5); +TEST_F(DmodReadModulesTest, CloseModulesNull) { + // Should not crash + Dmod_CloseModules(NULL); } /** - * @brief Test Dmod_ReadModules with large array + * @brief Test iterating through modules multiple times */ -TEST_F(DmodReadModulesTest, ReadModulesLargeArray) { - Dmod_ModuleInfo_t modules[100]; - size_t count = Dmod_ReadModules(modules, 100); - // Should return a reasonable number - EXPECT_LE(count, 100); - EXPECT_GE(count, 0); +TEST_F(DmodReadModulesTest, IterateMultipleTimes) { + // First iteration + Dmod_ModulesIterator_t iterator1 = Dmod_OpenModules(); + ASSERT_NE(iterator1, nullptr); + + size_t count1 = 0; + const Dmod_ModuleInfo_t* module; + while( (module = Dmod_ReadModule(iterator1)) != NULL ) + { + count1++; + } + + Dmod_CloseModules(iterator1); + + // Second iteration should give same count + Dmod_ModulesIterator_t iterator2 = Dmod_OpenModules(); + ASSERT_NE(iterator2, nullptr); + + size_t count2 = 0; + while( (module = Dmod_ReadModule(iterator2)) != NULL ) + { + count2++; + } + + Dmod_CloseModules(iterator2); + + EXPECT_EQ(count1, count2); +} + +/** + * @brief Test that iterator returns consistent module info + */ +TEST_F(DmodReadModulesTest, ModuleInfoConsistent) { + Dmod_ModulesIterator_t iterator = Dmod_OpenModules(); + ASSERT_NE(iterator, nullptr); + + const Dmod_ModuleInfo_t* module = Dmod_ReadModule(iterator); + if( module != NULL ) + { + // Module name should not be empty + EXPECT_NE(module->ModuleName[0], '\0'); + + // State should be valid + EXPECT_GE(module->State, Dmod_ModuleState_Available); + EXPECT_LT(module->State, Dmod_ModuleState_Count); + } + + Dmod_CloseModules(iterator); }