diff --git a/examples/system/CMakeLists.txt b/examples/system/CMakeLists.txt index d73141f6..ab70d52b 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 00000000..fae9fe8e --- /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 00000000..ac099f22 --- /dev/null +++ b/examples/system/list_modules/main.c @@ -0,0 +1,99 @@ +#include +#include +#include "dmod.h" + +/** + * @brief Simple test application to list all modules + * + * This application demonstrates the usage of Dmod_OpenModules/ReadModule/CloseModules 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"); + } + + // Open modules iterator + Dmod_ModulesIterator_t iterator = Dmod_OpenModules(); + if( iterator == NULL ) + { + printf("Error: Failed to open modules iterator\n"); + return -1; + } + + printf("Listing modules:\n\n"); + printf("%-30s %-15s %-15s\n", "Module Name", "Version", "State"); + printf("%-30s %-15s %-15s\n", "--------------------------------", "---------------", "---------------"); + + // Iterate through modules + size_t count = 0; + const Dmod_ModuleInfo_t* module; + while( (module = Dmod_ReadModule( iterator )) != NULL ) + { + const char* stateStr = "Unknown"; + switch( module->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", + module->ModuleName, + module->Version[0] != '\0' ? module->Version : "N/A", + stateStr); + count++; + } + + // Close iterator + Dmod_CloseModules( iterator ); + + if( count == 0 ) + { + 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 3c3732d1..bc63bce6 100644 --- a/inc/dmod.h +++ b/inc/dmod.h @@ -126,6 +126,10 @@ 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, 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 ) ); + //! @} #ifdef __cplusplus diff --git a/inc/dmod_types.h b/inc/dmod_types.h index d72587f3..adcb7d70 100644 --- a/inc/dmod_types.h +++ b/inc/dmod_types.h @@ -27,6 +27,40 @@ 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 + 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; + +/** + * @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]; //!< 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 ae363424..01975319 100644 --- a/src/system/dmod_system.c +++ b/src/system/dmod_system.c @@ -1634,3 +1634,292 @@ static bool CheckModuleArchitecture( const char* FilePath, const char* ExpectedA return true; } +/** + * @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 Iterator Iterator state + * @param ModuleName Name of the module to check + * + * @return True if module was already seen, false otherwise + */ +static bool IsModuleSeen( Dmod_ModulesIteratorInternal_t* Iterator, const char* ModuleName ) +{ + for( size_t i = 0; i < Iterator->SeenModulesCount; i++ ) + { + if( strcmp( Iterator->SeenModules[i], ModuleName ) == 0 ) + { + return true; + } + } + return false; +} + +/** + * @brief Helper function to mark module as seen + * + * @param Iterator Iterator state + * @param ModuleName Name of the module to mark as seen + */ +static void MarkModuleSeen( Dmod_ModulesIteratorInternal_t* Iterator, const char* ModuleName ) +{ + if( Iterator->SeenModulesCount < DMOD_MAX_MODULES ) + { + 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++; + } +} + +/** + * @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 ) + { + DMOD_LOG_ERROR("Cannot open modules iterator - out of memory\n"); + return NULL; + } + + // 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 next module from iterator + * + * 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 Iterator Modules iterator handle + * + * @return Pointer to module info, or NULL if no more modules + */ +const Dmod_ModuleInfo_t* Dmod_ReadModule( Dmod_ModulesIterator_t Iterator ) +{ + if( Iterator == NULL ) + { + DMOD_LOG_ERROR("Cannot read module - invalid iterator\n"); + return NULL; + } + + Dmod_ModulesIteratorInternal_t* iter = (Dmod_ModulesIteratorInternal_t*)Iterator; + + // Phase 1: Iterate through loaded modules + if( !iter->LoadedPhaseComplete ) + { + while( iter->ContextIndex < DMOD_MAX_MODULES ) + { + if( Dmod_Contexts[iter->ContextIndex] != NULL && Dmod_Contexts[iter->ContextIndex]->Header != NULL ) + { + 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; + } + + 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; + } + + // Phase 2: Scan available modules in search paths + if( !iter->SearchPathsPhaseComplete ) + { + while( iter->CurrentSearchNode != NULL ) + { + if( iter->CurrentDir == NULL ) + { + iter->CurrentDir = Dmod_OpenDir( iter->CurrentSearchNode->Path ); + } + + if( iter->CurrentDir != NULL ) + { + const char* fileName; + while( (fileName = Dmod_ReadDir( 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 ) + { + // 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'; + + if( !IsModuleSeen( iter, moduleName ) ) + { + // 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 ) ) + { + // 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; + } + } + } + } + } + } + + // 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; + } + iter->SearchPathsPhaseComplete = true; + } + + // Phase 3: Add available modules from packages + if( !iter->PackagesPhaseComplete ) + { + while( iter->PackageIndex < DMOD_MAX_NUMBER_OF_PACKAGES ) + { + if( Dmod_Pck_IsSlotUsed( &Dmod_Packages[iter->PackageIndex] ) && + Dmod_Pck_IsValidSlot( &Dmod_Packages[iter->PackageIndex] ) ) + { + Dmod_PackageSlot_t* slot = &Dmod_Packages[iter->PackageIndex]; + + while( iter->PackageModuleIndex < slot->DmpHeader->ModuleCount ) + { + 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 ); + } + + // Free iterator + Dmod_Free( iter ); +} + diff --git a/src/system/private/dmod_hlp.c b/src/system/private/dmod_hlp.c index c2dc2969..cbd4ecbd 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 f8724ec2..25cbe921 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 00000000..0a38207a --- /dev/null +++ b/tests/system/public/tests_dmod_read_modules.cpp @@ -0,0 +1,167 @@ +#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_OpenModules with NULL iterator + */ +TEST_F(DmodReadModulesTest, OpenModulesSuccess) { + Dmod_ModulesIterator_t iterator = Dmod_OpenModules(); + EXPECT_NE(iterator, nullptr); + Dmod_CloseModules(iterator); +} + +/** + * @brief Test Dmod_ReadModule with NULL iterator + */ +TEST_F(DmodReadModulesTest, ReadModuleNullIterator) { + const Dmod_ModuleInfo_t* module = Dmod_ReadModule(NULL); + EXPECT_EQ(module, nullptr); +} + +/** + * @brief Test Dmod_ReadModule with no modules loaded + */ +TEST_F(DmodReadModulesTest, ReadModulesNoModulesLoaded) { + 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); +} + +/** + * @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_CloseModules with NULL iterator + */ +TEST_F(DmodReadModulesTest, CloseModulesNull) { + // Should not crash + Dmod_CloseModules(NULL); +} + +/** + * @brief Test iterating through modules multiple times + */ +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); +}