From 361eb65a2f9623b874e830cedcc36897a6bfcac8 Mon Sep 17 00:00:00 2001 From: Joseph Hickey Date: Fri, 5 Feb 2021 14:15:59 -0500 Subject: [PATCH] Fix #981, implement better filename parser routine Implmements a new function in FS to parse an input file name from a user. Initially applied to startup script processing. The new function produces fully-qualified output where the input may omit either a pathname or an extension. These items will be added from the specified defaults, if missing, and a complete fully-qualified filename will be output. If the input is already a fully qualified filename, then the output is the same as the input (basically a copy). This initially is used to provide better cross-platform startup script processing, where module suffix may differ across platforms. Only the "basename" of the filename needs to be specified in the startup script - everything else can come from defaults. --- cmake/sample_defs/cpu1_cfe_es_startup.scr | 13 +- cmake/sample_defs/cpu1_platform_cfg.h | 46 ++-- cmake/target/inc/target_config.h | 12 +- cmake/target/src/target_config.c | 5 +- fsw/cfe-core/src/es/cfe_es_apps.c | 45 +++- fsw/cfe-core/src/fs/cfe_fs_api.c | 298 ++++++++++++++++++++++ fsw/cfe-core/src/inc/cfe_fs.h | 122 +++++++++ fsw/cfe-core/unit-test/es_UT.c | 39 ++- fsw/cfe-core/unit-test/fs_UT.c | 221 ++++++++++++++++ fsw/cfe-core/unit-test/fs_UT.h | 34 +++ fsw/cfe-core/ut-stubs/ut_fs_stubs.c | 115 +++++++++ 11 files changed, 893 insertions(+), 57 deletions(-) diff --git a/cmake/sample_defs/cpu1_cfe_es_startup.scr b/cmake/sample_defs/cpu1_cfe_es_startup.scr index d03c0e77a..d0510376a 100644 --- a/cmake/sample_defs/cpu1_cfe_es_startup.scr +++ b/cmake/sample_defs/cpu1_cfe_es_startup.scr @@ -1,8 +1,8 @@ -CFE_LIB, /cf/sample_lib.so, SAMPLE_LIB_Init, SAMPLE_LIB, 0, 0, 0x0, 0; -CFE_APP, /cf/sample_app.so, SAMPLE_APP_Main, SAMPLE_APP, 50, 16384, 0x0, 0; -CFE_APP, /cf/ci_lab.so, CI_Lab_AppMain, CI_LAB_APP, 60, 16384, 0x0, 0; -CFE_APP, /cf/to_lab.so, TO_Lab_AppMain, TO_LAB_APP, 70, 16384, 0x0, 0; -CFE_APP, /cf/sch_lab.so, SCH_Lab_AppMain, SCH_LAB_APP, 80, 16384, 0x0, 0; +CFE_LIB, sample_lib, SAMPLE_LIB_Init, SAMPLE_LIB, 0, 0, 0x0, 0; +CFE_APP, sample_app, SAMPLE_APP_Main, SAMPLE_APP, 50, 16384, 0x0, 0; +CFE_APP, ci_lab, CI_Lab_AppMain, CI_LAB_APP, 60, 16384, 0x0, 0; +CFE_APP, to_lab, TO_Lab_AppMain, TO_LAB_APP, 70, 16384, 0x0, 0; +CFE_APP, sch_lab, SCH_Lab_AppMain, SCH_LAB_APP, 80, 16384, 0x0, 0; ! ! Startup script fields: ! 1. Object Type -- CFE_APP for an Application, or CFE_LIB for a library. @@ -27,4 +27,7 @@ CFE_APP, /cf/sch_lab.so, SCH_Lab_AppMain, SCH_LAB_APP, 80, 16384, 0x0, 0; ! vxWorks = .o ( ci.o ) ! RTEMS with S-record Loader = .s3r ( ci.s3r ) ! RTEMS with CEXP Loader = .o ( ci.o ) +! 3. The filename field (2) no longer requires a fully-qualified filename; the path and extension +! may be omitted. If omitted, the standard virtual path (/cf) and a platform-specific default +! extension will be used, which is derived from the build system. diff --git a/cmake/sample_defs/cpu1_platform_cfg.h b/cmake/sample_defs/cpu1_platform_cfg.h index 431c9960f..8ab9735b8 100644 --- a/cmake/sample_defs/cpu1_platform_cfg.h +++ b/cmake/sample_defs/cpu1_platform_cfg.h @@ -36,6 +36,35 @@ #ifndef _cfe_platform_cfg_ #define _cfe_platform_cfg_ + +/** +** \cfeescfg Default virtual path for persistent storage +** +** \par Description: +** This configures the default location in the virtual file system +** for persistent/non-volatile storage. Files such as the startup +** script, app/library dynamic modules, and configuration tables are +** expected to be stored in this directory. +** +*/ +#define CFE_PLATFORM_ES_NONVOL_DISK_MOUNT_STRING "/cf" + +/** +** \cfeescfg Default virtual path for volatile storage +** +** \par Description: +** The #CFE_PLATFORM_ES_RAM_DISK_MOUNT_STRING parameter is used to set the cFE mount path +** for the CFE RAM disk. This is a parameter for missions that do not want to +** use the default value of "/ram", or for missions that need to have a different +** value for different CPUs or Spacecraft. +** Note that the vxWorks OSAL cannot currently handle names that have more than one +** path separator in it. The names "/ram", "/ramdisk", "/disk123" will all work, but +** "/disks/ram" will not. +** Multiple separators can be used with the posix or RTEMS ports. +** +*/ +#define CFE_PLATFORM_ES_RAM_DISK_MOUNT_STRING "/ram" + /** ** \cfesbcfg Maximum Number of Unique Message IDs SB Routing Table can hold ** @@ -682,23 +711,6 @@ #define CFE_PLATFORM_ES_RAM_DISK_PERCENT_RESERVED 30 -/** -** \cfeescfg RAM Disk Mount string -** -** \par Description: -** The #CFE_PLATFORM_ES_RAM_DISK_MOUNT_STRING parameter is used to set the cFE mount path -** for the CFE RAM disk. This is a parameter for missions that do not want to -** use the default value of "/ram", or for missions that need to have a different -** value for different CPUs or Spacecraft. -** Note that the vxWorks OSAL cannot currently handle names that have more than one -** path separator in it. The names "/ram", "/ramdisk", "/disk123" will all work, but -** "/disks/ram" will not. -** Multiple separators can be used with the posix or RTEMS ports. -** -*/ -#define CFE_PLATFORM_ES_RAM_DISK_MOUNT_STRING "/ram" - - /** ** \cfeescfg Define Critical Data Store Size ** diff --git a/cmake/target/inc/target_config.h b/cmake/target/inc/target_config.h index c4af5307f..4c19bb6e1 100644 --- a/cmake/target/inc/target_config.h +++ b/cmake/target/inc/target_config.h @@ -106,7 +106,17 @@ typedef const struct uint32 RamDiskTotalSectors; /***< RAM disk number of sectors */ /** - * Default value for start up file + * Default value for nonvolatile file system mount point + */ + const char *NonvolMountPoint; + + /** + * Default value for volatile file system mount point + */ + const char *RamdiskMountPoint; + + /** + * File name of startup script */ const char *NonvolStartupFile; diff --git a/cmake/target/src/target_config.c b/cmake/target/src/target_config.c index bbf33104b..33c6d49aa 100644 --- a/cmake/target/src/target_config.c +++ b/cmake/target/src/target_config.c @@ -72,9 +72,10 @@ Target_CfeConfigData GLOBAL_CFE_CONFIGDATA = .SystemNotify = CFE_ES_ProcessAsyncEvent, /* - * Default values for Startup file. - * This is a suggested value, but the PSP may provide a different file + * Default values for various file paths */ + .NonvolMountPoint = CFE_PLATFORM_ES_NONVOL_DISK_MOUNT_STRING, + .RamdiskMountPoint = CFE_PLATFORM_ES_RAM_DISK_MOUNT_STRING, .NonvolStartupFile = CFE_PLATFORM_ES_NONVOL_STARTUP_FILE, /* diff --git a/fsw/cfe-core/src/es/cfe_es_apps.c b/fsw/cfe-core/src/es/cfe_es_apps.c index fddb1203b..4802ba75e 100644 --- a/fsw/cfe-core/src/es/cfe_es_apps.c +++ b/fsw/cfe-core/src/es/cfe_es_apps.c @@ -76,7 +76,8 @@ */ void CFE_ES_StartApplications(uint32 ResetType, const char *StartFilePath ) { - char ES_AppLoadBuffer[ES_START_BUFF_SIZE]; /* A buffer of for a line in a file */ + char ES_AppLoadBuffer[ES_START_BUFF_SIZE]; /* A buffer of for a line in a file */ + char ScriptFileName[OS_MAX_PATH_LEN]; const char *TokenList[CFE_ES_STARTSCRIPT_MAX_TOKENS_PER_LINE]; uint32 NumTokens; uint32 BuffLen; /* Length of the current buffer */ @@ -93,14 +94,20 @@ void CFE_ES_StartApplications(uint32 ResetType, const char *StartFilePath ) if ( ResetType == CFE_PSP_RST_TYPE_PROCESSOR ) { /* - ** Open the file in the volatile disk. + ** First Attempt to parse as file in the volatile disk (temp area). */ - Status = OS_OpenCreate(&AppFile, CFE_PLATFORM_ES_VOLATILE_STARTUP_FILE, OS_FILE_FLAG_NONE, OS_READ_ONLY); + Status = CFE_FS_ParseInputFileName(ScriptFileName, CFE_PLATFORM_ES_VOLATILE_STARTUP_FILE, + sizeof(ScriptFileName), CFE_FS_FileCategory_TEMP); + + if (Status == CFE_SUCCESS) + { + Status = OS_OpenCreate(&AppFile, ScriptFileName, OS_FILE_FLAG_NONE, OS_READ_ONLY); + } if ( Status >= 0 ) { CFE_ES_WriteToSysLog ("ES Startup: Opened ES App Startup file: %s\n", - CFE_PLATFORM_ES_VOLATILE_STARTUP_FILE); + ScriptFileName); FileOpened = true; } else @@ -120,11 +127,17 @@ void CFE_ES_StartApplications(uint32 ResetType, const char *StartFilePath ) /* ** Try to Open the file passed in to the cFE start. */ - Status = OS_OpenCreate(&AppFile, StartFilePath, OS_FILE_FLAG_NONE, OS_READ_ONLY); + Status = CFE_FS_ParseInputFileName(ScriptFileName, StartFilePath, + sizeof(ScriptFileName), CFE_FS_FileCategory_SCRIPT); + + if (Status == CFE_SUCCESS) + { + Status = OS_OpenCreate(&AppFile, ScriptFileName, OS_FILE_FLAG_NONE, OS_READ_ONLY); + } if ( Status >= 0 ) { - CFE_ES_WriteToSysLog ("ES Startup: Opened ES App Startup file: %s\n",StartFilePath); + CFE_ES_WriteToSysLog ("ES Startup: Opened ES App Startup file: %s\n",ScriptFileName); FileOpened = true; } else @@ -271,7 +284,7 @@ int32 CFE_ES_ParseFileEntry(const char **TokenList, uint32 NumTokens) CFE_ES_AppId_t AppId; CFE_ES_LibId_t LibId; } IdBuf; - int32 CreateStatus = CFE_ES_ERR_APP_CREATE; + int32 Status; CFE_ES_AppStartParams_t ParamBuf; /* @@ -280,7 +293,7 @@ int32 CFE_ES_ParseFileEntry(const char **TokenList, uint32 NumTokens) if (NumTokens < 8) { CFE_ES_WriteToSysLog("ES Startup: Invalid ES Startup file entry: %u\n", (unsigned int)NumTokens); - return (CreateStatus); + return CFE_ES_BAD_ARGUMENT; } /* Get pointers to specific tokens that are simple strings used as-is */ @@ -292,7 +305,14 @@ int32 CFE_ES_ParseFileEntry(const char **TokenList, uint32 NumTokens) * Both Libraries and Apps use File Name (1) and Symbol Name (2) fields so copy those now */ memset(&ParamBuf, 0, sizeof(ParamBuf)); - strncpy(ParamBuf.BasicInfo.FileName, TokenList[1], sizeof(ParamBuf.BasicInfo.FileName) - 1); + Status = CFE_FS_ParseInputFileName(ParamBuf.BasicInfo.FileName, TokenList[1], + sizeof(ParamBuf.BasicInfo.FileName), CFE_FS_FileCategory_DYNAMIC_MODULE); + if (Status != CFE_SUCCESS) + { + CFE_ES_WriteToSysLog("ES Startup: Invalid ES Startup script file name: %s\n", TokenList[1]); + return Status; + } + strncpy(ParamBuf.BasicInfo.InitSymbolName, TokenList[2], sizeof(ParamBuf.BasicInfo.InitSymbolName) - 1); if (strcmp(EntryType, "CFE_APP") == 0) @@ -338,7 +358,7 @@ int32 CFE_ES_ParseFileEntry(const char **TokenList, uint32 NumTokens) /* ** Now create the application */ - CreateStatus = CFE_ES_AppCreate(&IdBuf.AppId, ModuleName, &ParamBuf); + Status = CFE_ES_AppCreate(&IdBuf.AppId, ModuleName, &ParamBuf); } else if (strcmp(EntryType, "CFE_LIB") == 0) { @@ -347,14 +367,15 @@ int32 CFE_ES_ParseFileEntry(const char **TokenList, uint32 NumTokens) /* ** Now load the library */ - CreateStatus = CFE_ES_LoadLibrary(&IdBuf.LibId, ModuleName, &ParamBuf.BasicInfo); + Status = CFE_ES_LoadLibrary(&IdBuf.LibId, ModuleName, &ParamBuf.BasicInfo); } else { CFE_ES_WriteToSysLog("ES Startup: Unexpected EntryType %s in startup file.\n", EntryType); + Status = CFE_ES_ERR_APP_CREATE; } - return (CreateStatus); + return (Status); } /* diff --git a/fsw/cfe-core/src/fs/cfe_fs_api.c b/fsw/cfe-core/src/fs/cfe_fs_api.c index 7638472e9..6aa557cba 100644 --- a/fsw/cfe-core/src/fs/cfe_fs_api.c +++ b/fsw/cfe-core/src/fs/cfe_fs_api.c @@ -42,6 +42,74 @@ #include "cfe_es.h" #include +/* The target config allows refs into global CONFIGDATA object(s) */ +#include "target_config.h" + + +/* + * Fixed default file system extensions (not platform dependent) + */ +const char CFE_FS_DEFAULT_SCRIPT_EXTENSION[] = ".scr"; +const char CFE_FS_DEFAULT_DUMP_FILE_EXTENSION[] = ".dat"; +const char CFE_FS_DEFAULT_TEMP_FILE_EXTENSION[] = ".tmp"; +const char CFE_FS_DEFAULT_LOG_FILE_EXTENSION[] = ".log"; + + +const char *CFE_FS_GetDefaultMountPoint(CFE_FS_FileCategory_t FileCategory) +{ + const char *Result; + + switch(FileCategory) + { + case CFE_FS_FileCategory_SCRIPT: + case CFE_FS_FileCategory_DYNAMIC_MODULE: + /* scripts and app/lib modules reside in the non-volatile/CF mount by default */ + Result = GLOBAL_CFE_CONFIGDATA.NonvolMountPoint; + break; + case CFE_FS_FileCategory_TEMP: + case CFE_FS_FileCategory_BINARY_DATA_DUMP: + case CFE_FS_FileCategory_TEXT_LOG: + /* temporary and data dump files are put in the RAM DISK mount by default */ + Result = GLOBAL_CFE_CONFIGDATA.RamdiskMountPoint; + break; + default: + Result = NULL; /* Should not be used */ + break; + } + + return Result; +} + +const char *CFE_FS_GetDefaultExtension(CFE_FS_FileCategory_t FileCategory) +{ + const char *Result; + + switch(FileCategory) + { + case CFE_FS_FileCategory_SCRIPT: + Result = CFE_FS_DEFAULT_SCRIPT_EXTENSION; + break; + case CFE_FS_FileCategory_DYNAMIC_MODULE: + /* app/lib modules use a platform-specific extension, and the + * default is derived from the build system */ + Result = GLOBAL_CONFIGDATA.Default_ModuleExtension; + break; + case CFE_FS_FileCategory_TEMP: + Result = CFE_FS_DEFAULT_TEMP_FILE_EXTENSION; + break; + case CFE_FS_FileCategory_BINARY_DATA_DUMP: + Result = CFE_FS_DEFAULT_DUMP_FILE_EXTENSION; + break; + case CFE_FS_FileCategory_TEXT_LOG: + Result = CFE_FS_DEFAULT_LOG_FILE_EXTENSION; + break; + default: + Result = NULL; /* Should not be used */ + break; + } + + return Result; +} /* ** CFE_FS_ReadHeader() - See API and header file for details @@ -252,6 +320,236 @@ void CFE_FS_ByteSwapUint32(uint32 *Uint32ToSwapPtr) OutPtr[3] = InPtr[0]; } /* End of CFE_FS_ByteSwapUint32() */ +/* +**--------------------------------------------------------------------------------------- +** Name: CFE_FS_ParseInputFileNameEx +** +** Purpose: This reads a file name from user input with extra logic to make more user friendly +** - absolute path is optional; assume default dir if missing +** - module extension is optional; append default for OS/platform if missing +**--------------------------------------------------------------------------------------- +*/ +int32 CFE_FS_ParseInputFileNameEx(char *OutputBuffer, const char *InputBuffer, size_t OutputBufSize, size_t InputBufSize, + const char *DefaultInput, const char *DefaultPath, const char *DefaultExtension) +{ + int32 Status; + const char *InputPtr; + const char *ComponentPtr; + size_t ComponentLen; + char ComponentTerm; + size_t OutputLen; + size_t InputLen; + bool LastPathReached; + + /* The filename consists of a pathname, filename, and extension component. */ + enum + { + PROCESS_INIT, + PATHNAME_COMPONENT, + PATHNAME_SEPARATOR, + FILENAME_COMPONENT, + EXTENSION_SEPARATOR, + EXTENSION_COMPONENT, + END_COMPONENT + } Component; + + /* Sanity check buffer input */ + if (OutputBuffer == NULL || OutputBufSize == 0) + { + return CFE_FS_BAD_ARGUMENT; + } + + Status = CFE_FS_INVALID_PATH; + OutputLen = 0; + ComponentTerm = 0; + LastPathReached = false; + + /* If input buffer is not empty, then use it, otherwise use DefaultInput */ + if (InputBuffer != NULL && InputBufSize > 0 && InputBuffer[0] != 0) + { + InputPtr = InputBuffer; + InputLen = InputBufSize; + } + else if (DefaultInput != NULL) + { + /* This must be a normal null terminated string */ + InputPtr = DefaultInput; + InputLen = strlen(DefaultInput); + } + else + { + /* No input */ + InputPtr = NULL; + InputLen = 0; + } + + Component = PROCESS_INIT; + while (InputPtr != NULL && Component < END_COMPONENT) + { + /* Move to next component */ + if (Component == PATHNAME_SEPARATOR && !LastPathReached) + { + /* repeat until LastPathReached */ + Component = PATHNAME_COMPONENT; + } + else + { + ++Component; + } + + switch(Component) + { + case PATHNAME_COMPONENT: + /* path part ends with the last / char, which begins the filename */ + ComponentTerm = '/'; + ComponentPtr = memchr(InputPtr, ComponentTerm, InputLen); + if (ComponentPtr != NULL) + { + /* has path: use pathname from input, advance InputPtr to next part (filename) */ + ComponentLen = ComponentPtr - InputPtr; + ComponentPtr = InputPtr; + InputPtr += ComponentLen; + InputLen -= ComponentLen; + } + else + { + LastPathReached = true; + + /* no path: if no output at all yet, use default pathname, otherwise move on. */ + if (DefaultPath != NULL && OutputLen == 0) + { + ComponentLen = strlen(DefaultPath); + ComponentPtr = DefaultPath; + } + else + { + /* use no pathname at all */ + ComponentLen = 0; + ComponentPtr = NULL; + } + } + break; + + case FILENAME_COMPONENT: + /* filename ends with a . char, which begins the extension */ + ComponentTerm = '.'; + ComponentPtr = memchr(InputPtr, ComponentTerm, InputLen); + if (ComponentPtr != NULL) + { + /* has ext: use pathname from input, advance InputPtr to next part (extension) */ + ComponentLen = ComponentPtr - InputPtr; + ComponentPtr = InputPtr; + InputPtr += ComponentLen; + InputLen -= ComponentLen; + } + else + { + /* no ext: use remainder of input here - then use default extension for next part */ + ComponentLen = InputLen; + ComponentPtr = InputPtr; + if (DefaultExtension != NULL) + { + InputPtr = DefaultExtension; + InputLen = strlen(DefaultExtension); + } + else + { + /* Use no extension */ + InputPtr = NULL; + InputLen = 0; + } + } + + if (ComponentLen > 0 && *ComponentPtr != 0) + { + /* + * If the filename part is non-empty, then consider the conversion successful + * (note that extension is not really needed for an acceptable filename) + */ + Status = CFE_SUCCESS; + } + + break; + + case PATHNAME_SEPARATOR: + case EXTENSION_SEPARATOR: + /* Remove duplicate terminators that may have been in the input */ + while (OutputLen > 0 && OutputBuffer[OutputLen-1] == ComponentTerm) + { + --OutputLen; + } + + ComponentLen = 1; + ComponentPtr = &ComponentTerm; + + /* advance past any separators in input to get to the next content */ + while (*InputPtr == ComponentTerm && InputLen > 0) + { + ++InputPtr; + --InputLen; + } + break; + + + case EXTENSION_COMPONENT: + /* Intentional fall through to default case */ + + default: + /* Just consume the rest of input - + * should already be pointing to correct data */ + ComponentTerm = 0; + ComponentLen = InputLen; + ComponentPtr = InputPtr; + InputPtr = NULL; /* no more input */ + InputLen = 0; + break; + } + + /* Append component */ + while(ComponentLen > 0 && *ComponentPtr != 0) + { + OutputBuffer[OutputLen] = *ComponentPtr; + ++ComponentPtr; + ++OutputLen; + --ComponentLen; + + if (OutputLen >= OutputBufSize) + { + /* name is too long to fit in output buffer */ + Status = CFE_FS_FNAME_TOO_LONG; + InputPtr = NULL; /* no more input */ + InputLen = 0; + --OutputLen; /* back up one char for term */ + break; + } + } + } + + /* + * Always add a final terminating NUL char. + * + * Note that the loop above should never entirely fill + * buffer (length check includes extra char). + */ + OutputBuffer[OutputLen] = 0; + + return Status; +} + +/* +**--------------------------------------------------------------------------------------- +** Name: CFE_FS_ParseInputFileName +** +** Purpose: Simplified API for CFE_FS_ParseInputFileNameEx where input is always known to be +** a non-empty, null terminated string and the fixed-length input buffer not needed. +**--------------------------------------------------------------------------------------- +*/ +int32 CFE_FS_ParseInputFileName(char *OutputBuffer, const char *InputName, size_t OutputBufSize, CFE_FS_FileCategory_t FileCategory) +{ + return CFE_FS_ParseInputFileNameEx(OutputBuffer, NULL, OutputBufSize, 0, InputName, + CFE_FS_GetDefaultMountPoint(FileCategory), CFE_FS_GetDefaultExtension(FileCategory)); +} + /* ** CFE_FS_ExtractFilenameFromPath - See API and header file for details diff --git a/fsw/cfe-core/src/inc/cfe_fs.h b/fsw/cfe-core/src/inc/cfe_fs.h index 91d4b98af..13be4d0c3 100644 --- a/fsw/cfe-core/src/inc/cfe_fs.h +++ b/fsw/cfe-core/src/inc/cfe_fs.h @@ -42,6 +42,27 @@ #include "common_types.h" #include "cfe_time.h" +/** + * \brief Generalized file types/categories known to FS + * + * This defines different categories of files, where they + * may reside in different default locations of the virtualized file system. + * + * This is different from, and should not be confused with, the "SubType" + * field in the FS header. This value is only used at runtime for FS APIs + * and should not actually appear in any output file or message. + */ +typedef enum +{ + CFE_FS_FileCategory_UNKNOWN, /**< Placeholder, unknown file category */ + CFE_FS_FileCategory_DYNAMIC_MODULE, /**< Dynamically loadable apps/libraries (e.g. .so, .o, .dll, etc) */ + CFE_FS_FileCategory_BINARY_DATA_DUMP, /**< Binary log file generated by various data dump commands */ + CFE_FS_FileCategory_TEXT_LOG, /**< Text-based log file generated by various commands */ + CFE_FS_FileCategory_SCRIPT, /**< Text-based Script files (e.g. ES startup script) */ + CFE_FS_FileCategory_TEMP, /**< Temporary/Ephemeral files */ + CFE_FS_FileCategory_MAX /**< Placeholder, keep last */ +} CFE_FS_FileCategory_t; + /* * Because FS is a library not an app, it does not have its own context or * event IDs. The file writer runs in the context of the ES background task @@ -222,6 +243,107 @@ CFE_Status_t CFE_FS_SetTimestamp(osal_id_t FileDes, CFE_TIME_SysTime_t NewTimest * @{ */ +/*****************************************************************************/ +/** +** \brief Get the default virtual mount point for a file category +** +** Certain classes of files generally reside in a common directory, mainly +** either the persistent storage (/cf typically) or ram disk (/ram typically). +** +** Ephemeral status files are generally in the ram disk while application +** modules and scripts are generally in the persistent storage. +** +** This returns the expected directory for a given class of files in the form +** of a virtual OSAL mount point string. +** +** \returns String containing the mount point, or NULL if unkown/invalid +*/ +const char *CFE_FS_GetDefaultMountPoint(CFE_FS_FileCategory_t FileCategory); + +/*****************************************************************************/ +/** +** \brief Get the default filename extension for a file category +** +** Certain file types may have an extension that varies from system to system. +** This is primarily an issue for application modules which are ".so" on +** Linux systems, ".dll" on Windows, ".o" on VxWorks, ".obj" on RTEMS, and so on. +** +** This uses a combination of compile-time configuration and hints from the +** build environment to get the default/expected extension for a given file +** category. +** +** \returns String containing the extension, or NULL if unkown/invalid +*/ +const char *CFE_FS_GetDefaultExtension(CFE_FS_FileCategory_t FileCategory); + + +/*****************************************************************************/ +/** +** \brief Parse a filename input from an input buffer into a local buffer +** +** \par Description +** This provides a more user friendly way to specify file names, +** using default values for the path and extension, which can +** vary from system to system. +** +** If InputBuffer is null or its length is zero, then DefaultInput +** is used as if it was the content of the input buffer. +** +** If either the pathname or extension is missing from the input, +** it will be added from defaults, with the complete fully-qualified +** filename stored in the output buffer. +** +** \par Assumptions, External Events, and Notes: +** -# The paths and filenames used here are the standard unix style +** filenames separated by "/" (path) and "." (extension) characters. +** -# Input Buffer has a fixed max length. Parsing will not exceed InputBufSize, +** and does not need to be null terminated. However parsing will stop +** at the first null char, when the input is shorter than the maximum. +** +** \param[out] OutputBuffer Buffer to store result. +** \param[in] InputBuffer A input buffer that may contain a file name (e.g. from command). +** \param[in] OutputBufSize Maximum Size of output buffer. +** \param[in] InputBufSize Maximum Size of input buffer. +** \param[in] DefaultInput Default value to use for input if InputBffer is empty +** \param[in] DefaultPath Default value to use for pathname if omitted from input +** \param[in] DefaultExtension Default value to use for extension if omitted from input +** +** \return Execution status, see \ref CFEReturnCodes +** +******************************************************************************/ +int32 CFE_FS_ParseInputFileNameEx(char *OutputBuffer, const char *InputBuffer, size_t OutputBufSize, size_t InputBufSize, + const char *DefaultInput, const char *DefaultPath, const char *DefaultExtension); + + +/*****************************************************************************/ +/** +** \brief Parse a filename string from the user into a local buffer +** +** \par Description +** Simplified API for CFE_FS_ParseInputFileNameEx() where input is +** always known to be a non-empty, null terminated string and the fixed-length +** input buffer not needed. For instance this may be used where +** the input is a fixed string from cfe_platform_cfg.h or similar. +** +** \par Assumptions, External Events, and Notes: +** The parameters are organized such that this is basically like strncpy() with an +** extra argument, and existing file name accesses which use a direct copy can +** easily change to use this instead. +** +** \sa CFE_FS_ParseInputFileNameEx() +** +** \param[out] OutputBuffer Buffer to store result. +** \param[in] InputName A null terminated input string +** \param[in] OutputBufSize Maximum Size of output buffer. +** \param[in] FileCategory The generalized category of file (implies default path/extension) +** +** \return Execution status, see \ref CFEReturnCodes +** +**--------------------------------------------------------------------------------------- +*/ +int32 CFE_FS_ParseInputFileName(char *OutputBuffer, const char *InputName, size_t OutputBufSize, CFE_FS_FileCategory_t FileCategory); + + /*****************************************************************************/ /** ** \brief Extracts the filename from a unix style path and filename string. diff --git a/fsw/cfe-core/unit-test/es_UT.c b/fsw/cfe-core/unit-test/es_UT.c index 118dc6346..ad3587284 100644 --- a/fsw/cfe-core/unit-test/es_UT.c +++ b/fsw/cfe-core/unit-test/es_UT.c @@ -757,7 +757,7 @@ void TestInit(void) UT_SetDummyFuncRtn(OS_SUCCESS); UT_SetHookFunction(UT_KEY(OS_TaskCreate), ES_UT_SetAppStateHook, NULL); CFE_ES_Main(CFE_PSP_RST_TYPE_POWERON, CFE_PSP_RST_SUBTYPE_POWER_CYCLE, 1, - CFE_PLATFORM_ES_NONVOL_STARTUP_FILE); + "ut_startup"); UT_Report(__FILE__, __LINE__, UT_GetStubCount(UT_KEY(CFE_PSP_Panic)) == 0, "CFE_ES_Main", @@ -790,8 +790,7 @@ void TestStartupErrorPaths(void) UT_SetDefaultReturnValue(UT_KEY(OS_MutSemCreate), OS_ERROR); UT_SetReadBuffer(StartupScript, strlen(StartupScript)); UT_SetDataBuffer(UT_KEY(CFE_PSP_Panic), &PanicStatus, sizeof(PanicStatus), false); - CFE_ES_Main(CFE_PSP_RST_TYPE_POWERON, 1, 1, - CFE_PLATFORM_ES_NONVOL_STARTUP_FILE); + CFE_ES_Main(CFE_PSP_RST_TYPE_POWERON, 1, 1, "ut_startup"); UT_Report(__FILE__, __LINE__, PanicStatus == CFE_PSP_PANIC_STARTUP_SEM && UT_GetStubCount(UT_KEY(CFE_PSP_Panic)) == 1, @@ -803,8 +802,7 @@ void TestStartupErrorPaths(void) UT_SetDummyFuncRtn(OS_SUCCESS); UT_SetDefaultReturnValue(UT_KEY(OS_OpenCreate), OS_ERROR); UT_SetHookFunction(UT_KEY(OS_TaskCreate), ES_UT_SetAppStateHook, NULL); - CFE_ES_Main(CFE_PSP_RST_TYPE_POWERON, 1, 1, - (char *) CFE_PLATFORM_ES_NONVOL_STARTUP_FILE); + CFE_ES_Main(CFE_PSP_RST_TYPE_POWERON, 1, 1, "ut_startup"); UT_Report(__FILE__, __LINE__, UT_PrintfIsInHistory(UT_OSP_MESSAGES[UT_OSP_CANNOT_OPEN_ES_APP_STARTUP]), "CFE_ES_Main", @@ -816,8 +814,7 @@ void TestStartupErrorPaths(void) StateHook.AppType = CFE_ES_AppType_CORE; /* by only setting core apps, it will appear as if external apps did not start */ UT_SetHookFunction(UT_KEY(OS_TaskCreate), ES_UT_SetAppStateHook, &StateHook); UT_SetReadBuffer(StartupScript, strlen(StartupScript)); - CFE_ES_Main(CFE_PSP_RST_TYPE_POWERON, 1, 1, - (char *) CFE_PLATFORM_ES_NONVOL_STARTUP_FILE); + CFE_ES_Main(CFE_PSP_RST_TYPE_POWERON, 1, 1, "ut_startup"); UT_Report(__FILE__, __LINE__, UT_PrintfIsInHistory(UT_OSP_MESSAGES[UT_OSP_STARTUP_SYNC_FAIL_1]), "CFE_ES_Main", @@ -1228,8 +1225,7 @@ void TestApps(void) StartupScript[sizeof(StartupScript) - 1] = '\0'; NumBytes = strlen(StartupScript); UT_SetReadBuffer(StartupScript, NumBytes); - CFE_ES_StartApplications(CFE_PSP_RST_TYPE_PROCESSOR, - CFE_PLATFORM_ES_NONVOL_STARTUP_FILE); + CFE_ES_StartApplications(CFE_PSP_RST_TYPE_PROCESSOR, "ut_startup"); UtAssert_NONZERO(UT_PrintfIsInHistory(UT_OSP_MESSAGES[UT_OSP_FILE_LINE_TOO_LONG])); UtAssert_NONZERO(UT_PrintfIsInHistory(UT_OSP_MESSAGES[UT_OSP_ES_APP_STARTUP_OPEN])); @@ -1247,8 +1243,7 @@ void TestApps(void) /* Test starting an application with an error reading the startup file */ ES_ResetUnitTest(); UT_SetDeferredRetcode(UT_KEY(OS_read), 1, -1); - CFE_ES_StartApplications(CFE_PSP_RST_TYPE_PROCESSOR, - CFE_PLATFORM_ES_NONVOL_STARTUP_FILE); + CFE_ES_StartApplications(CFE_PSP_RST_TYPE_PROCESSOR, "ut_startup"); UT_Report(__FILE__, __LINE__, UT_PrintfIsInHistory(UT_OSP_MESSAGES[UT_OSP_STARTUP_READ]) && UT_PrintfIsInHistory(UT_OSP_MESSAGES[UT_OSP_ES_APP_STARTUP_OPEN]), @@ -1260,8 +1255,7 @@ void TestApps(void) */ ES_ResetUnitTest(); UT_SetDeferredRetcode(UT_KEY(OS_read), 1, 0); - CFE_ES_StartApplications(CFE_PSP_RST_TYPE_PROCESSOR, - CFE_PLATFORM_ES_NONVOL_STARTUP_FILE); + CFE_ES_StartApplications(CFE_PSP_RST_TYPE_PROCESSOR, "ut_startup"); UT_Report(__FILE__, __LINE__, UT_PrintfIsInHistory(UT_OSP_MESSAGES[UT_OSP_ES_APP_STARTUP_OPEN]), "CFE_ES_StartApplications", @@ -1270,8 +1264,7 @@ void TestApps(void) /* Test starting an application with an open failure */ ES_ResetUnitTest(); UT_SetDefaultReturnValue(UT_KEY(OS_OpenCreate), OS_ERROR); - CFE_ES_StartApplications(CFE_PSP_RST_TYPE_PROCESSOR, - CFE_PLATFORM_ES_NONVOL_STARTUP_FILE); + CFE_ES_StartApplications(CFE_PSP_RST_TYPE_PROCESSOR, "ut_startup"); UT_Report(__FILE__, __LINE__, UT_PrintfIsInHistory(UT_OSP_MESSAGES[UT_OSP_CANNOT_OPEN_ES_APP_STARTUP]), "CFE_ES_StartApplications", @@ -1281,8 +1274,7 @@ void TestApps(void) ES_ResetUnitTest(); UT_SetReadBuffer(StartupScript, NumBytes); UT_SetHookFunction(UT_KEY(OS_TaskCreate), ES_UT_SetAppStateHook, NULL); - CFE_ES_StartApplications(CFE_PSP_RST_TYPE_PROCESSOR, - CFE_PLATFORM_ES_NONVOL_STARTUP_FILE); + CFE_ES_StartApplications(CFE_PSP_RST_TYPE_PROCESSOR, "ut_startup"); UtAssert_NONZERO(UT_PrintfIsInHistory(UT_OSP_MESSAGES[UT_OSP_ES_APP_STARTUP_OPEN])); /* Test parsing the startup script with an unknown entry type */ @@ -1303,14 +1295,21 @@ void TestApps(void) CFE_ES_ParseFileEntry(TokenList, 8) == CFE_ES_ERR_APP_CREATE, "CFE_ES_ParseFileEntry", "Unknown entry type"); + + /* Test parsing the startup script with an invalid file name */ + UT_SetDefaultReturnValue(UT_KEY(CFE_FS_ParseInputFileName), CFE_FS_INVALID_PATH); + UT_Report(__FILE__, __LINE__, + CFE_ES_ParseFileEntry(TokenList, 8) == CFE_FS_INVALID_PATH, + "CFE_ES_ParseFileEntry", + "Invalid file name"); } - /* Test parsing the startup script with an invalid file entry */ + /* Test parsing the startup script with an invalid argument passed in */ ES_ResetUnitTest(); UT_Report(__FILE__, __LINE__, - CFE_ES_ParseFileEntry(NULL, 0) == CFE_ES_ERR_APP_CREATE, + CFE_ES_ParseFileEntry(NULL, 0) == CFE_ES_BAD_ARGUMENT, "CFE_ES_ParseFileEntry", - "Invalid file entry"); + "Invalid argument"); /* Test application loading and creation with a task creation failure */ ES_ResetUnitTest(); diff --git a/fsw/cfe-core/unit-test/fs_UT.c b/fsw/cfe-core/unit-test/fs_UT.c index 00335e0dd..d45013308 100644 --- a/fsw/cfe-core/unit-test/fs_UT.c +++ b/fsw/cfe-core/unit-test/fs_UT.c @@ -40,6 +40,8 @@ */ #include "fs_UT.h" +#include "target_config.h" + const char *FS_SYSLOG_MSGS[] = { NULL, @@ -79,8 +81,10 @@ void UtTest_Setup(void) UT_ADD_TEST(Test_CFE_FS_ReadHeader); UT_ADD_TEST(Test_CFE_FS_WriteHeader); UT_ADD_TEST(Test_CFE_FS_SetTimestamp); + UT_ADD_TEST(Test_CFE_FS_DefaultFileStrings); UT_ADD_TEST(Test_CFE_FS_ByteSwapCFEHeader); UT_ADD_TEST(Test_CFE_FS_ByteSwapUint32); + UT_ADD_TEST(Test_CFE_FS_ParseInputFileNameEx); UT_ADD_TEST(Test_CFE_FS_ExtractFileNameFromPath); UT_ADD_TEST(Test_CFE_FS_Private); @@ -251,6 +255,223 @@ void Test_CFE_FS_ByteSwapUint32(void) "Byte swap - successful"); } +/* +** Test CFE_FS_ParseInputFileNameEx function +*/ +void Test_CFE_FS_ParseInputFileNameEx(void) +{ + /* + * Test case for: + * int32 CFE_FS_ParseInputFileNameEx(char *OutputBuffer, const char *InputName, size_t OutputBufSize, + * const char *DefaultPath, const char *DefaultExtension) + */ + + const char TEST_INPUT_FULLY_QUALIFIED[] = "/path/to/file.log"; + const char TEST_INPUT_NO_PATH[] = "file.log"; + const char TEST_INPUT_NO_EXTENSION[] = "/path/to/file"; + const char TEST_INPUT_BASENAME[] = "file"; + const char TEST_XTRA_SEPARATOR_PATH[] = "//xtra//sep///file.log"; + const char TEST_NO_SEPARATOR[] = "nosep"; + const char TEST_DEFAULT_INPUT[] = "/dpath_in/dfile_in.dext_in"; + const char TEST_DEFAULT_PATH[] = "/dflpath"; + const char TEST_DEFAULT_EXTENSION[] = ".dflext"; + + char OutBuffer[64]; + + /* nominal with fully-qualified input */ + memset(OutBuffer, 0x7F, sizeof(OutBuffer)); + UtAssert_INT32_EQ(CFE_FS_ParseInputFileNameEx(OutBuffer, TEST_INPUT_FULLY_QUALIFIED, sizeof(OutBuffer), + sizeof(TEST_INPUT_FULLY_QUALIFIED), TEST_DEFAULT_INPUT, + TEST_DEFAULT_PATH, TEST_DEFAULT_EXTENSION), + CFE_SUCCESS); + UtAssert_StrCmp(OutBuffer, TEST_INPUT_FULLY_QUALIFIED, "Fully-qualified pass thru -> %s", OutBuffer); + + /* Same but as a default input, rather than in the buffer */ + UtAssert_INT32_EQ(CFE_FS_ParseInputFileNameEx(OutBuffer, NULL, sizeof(OutBuffer), 0, TEST_DEFAULT_INPUT, + TEST_DEFAULT_PATH, TEST_DEFAULT_EXTENSION), + CFE_SUCCESS); + UtAssert_StrCmp(OutBuffer, TEST_DEFAULT_INPUT, "Fully-qualified pass thru -> %s", OutBuffer); + + /* nominal with no path input */ + memset(OutBuffer, 0x7F, sizeof(OutBuffer)); + UtAssert_INT32_EQ(CFE_FS_ParseInputFileNameEx(OutBuffer, TEST_INPUT_NO_PATH, sizeof(OutBuffer), + sizeof(TEST_INPUT_NO_PATH), TEST_DEFAULT_INPUT, TEST_DEFAULT_PATH, + TEST_DEFAULT_EXTENSION), + CFE_SUCCESS); + UtAssert_StrCmp(OutBuffer, "/dflpath/file.log", "No Path input -> %s", OutBuffer); + + /* nominal with no path input - should remove duplicate path separators */ + memset(OutBuffer, 0x7F, sizeof(OutBuffer)); + UtAssert_INT32_EQ(CFE_FS_ParseInputFileNameEx(OutBuffer, TEST_XTRA_SEPARATOR_PATH, sizeof(OutBuffer), + sizeof(TEST_XTRA_SEPARATOR_PATH), TEST_DEFAULT_INPUT, + TEST_DEFAULT_PATH, TEST_DEFAULT_EXTENSION), + CFE_SUCCESS); + UtAssert_StrCmp(OutBuffer, "/xtra/sep/file.log", "No Path input, extra separators -> %s", OutBuffer); + + /* nominal with no extension input */ + memset(OutBuffer, 0x7F, sizeof(OutBuffer)); + UtAssert_INT32_EQ(CFE_FS_ParseInputFileNameEx(OutBuffer, TEST_INPUT_NO_EXTENSION, sizeof(OutBuffer), + sizeof(TEST_INPUT_NO_EXTENSION), TEST_DEFAULT_INPUT, + TEST_DEFAULT_PATH, TEST_DEFAULT_EXTENSION), + CFE_SUCCESS); + UtAssert_StrCmp(OutBuffer, "/path/to/file.dflext", "No Extension input -> %s", OutBuffer); + + /* nominal with no extension input, no separator (should be added) */ + memset(OutBuffer, 0x7F, sizeof(OutBuffer)); + UtAssert_INT32_EQ(CFE_FS_ParseInputFileNameEx(OutBuffer, TEST_INPUT_NO_EXTENSION, sizeof(OutBuffer), + sizeof(TEST_INPUT_NO_EXTENSION), TEST_DEFAULT_INPUT, + TEST_DEFAULT_PATH, TEST_NO_SEPARATOR), + CFE_SUCCESS); + UtAssert_StrCmp(OutBuffer, "/path/to/file.nosep", "No Extension input, no separator -> %s", OutBuffer); + + /* nominal with neither path nor extension input */ + memset(OutBuffer, 0x7F, sizeof(OutBuffer)); + UtAssert_INT32_EQ(CFE_FS_ParseInputFileNameEx(OutBuffer, TEST_INPUT_BASENAME, sizeof(OutBuffer), + sizeof(TEST_INPUT_BASENAME), TEST_DEFAULT_INPUT, TEST_DEFAULT_PATH, + TEST_DEFAULT_EXTENSION), + CFE_SUCCESS); + UtAssert_StrCmp(OutBuffer, "/dflpath/file.dflext", "No Path nor Extension input -> %s", OutBuffer); + + /* Bad arguments for buffer pointer/size */ + UtAssert_INT32_EQ(CFE_FS_ParseInputFileNameEx(NULL, TEST_INPUT_BASENAME, sizeof(OutBuffer), + sizeof(TEST_INPUT_BASENAME), NULL, NULL, NULL), + CFE_FS_BAD_ARGUMENT); + UtAssert_INT32_EQ( + CFE_FS_ParseInputFileNameEx(OutBuffer, TEST_INPUT_BASENAME, 0, sizeof(TEST_INPUT_BASENAME), NULL, NULL, NULL), + CFE_FS_BAD_ARGUMENT); + + /* Bad arguments for input */ + UtAssert_INT32_EQ( + CFE_FS_ParseInputFileNameEx(OutBuffer, NULL, sizeof(OutBuffer), 0, NULL, NULL, TEST_DEFAULT_EXTENSION), + CFE_FS_INVALID_PATH); + + /* Cases where the file name itself is actually an empty string */ + UtAssert_INT32_EQ( + CFE_FS_ParseInputFileNameEx(OutBuffer, "", sizeof(OutBuffer), 10, NULL, NULL, TEST_DEFAULT_EXTENSION), + CFE_FS_INVALID_PATH); + UtAssert_INT32_EQ(CFE_FS_ParseInputFileNameEx(OutBuffer, "/path/", sizeof(OutBuffer), 10, NULL, TEST_DEFAULT_PATH, + TEST_DEFAULT_EXTENSION), + CFE_FS_INVALID_PATH); + + /* if the default path/extension is null it is just ignored, not an error. */ + memset(OutBuffer, 0x7F, sizeof(OutBuffer)); + UtAssert_INT32_EQ(CFE_FS_ParseInputFileNameEx(OutBuffer, TEST_INPUT_BASENAME, sizeof(OutBuffer), + sizeof(TEST_INPUT_BASENAME), NULL, NULL, TEST_DEFAULT_EXTENSION), + CFE_SUCCESS); + /* If no path this still adds a leading / */ + UtAssert_StrCmp(OutBuffer, "/file.dflext", "No Path nor default -> %s", OutBuffer); + UtAssert_INT32_EQ(CFE_FS_ParseInputFileNameEx(OutBuffer, TEST_INPUT_BASENAME, sizeof(OutBuffer), + sizeof(TEST_INPUT_BASENAME), NULL, TEST_DEFAULT_PATH, NULL), + CFE_SUCCESS); + UtAssert_StrCmp(OutBuffer, "/dflpath/file", "No Extension nor default -> %s", OutBuffer); + + /* test corner case for termination where result fits exactly, including NUL (should work) */ + memset(OutBuffer, 0x7F, sizeof(OutBuffer)); + UtAssert_INT32_EQ(CFE_FS_ParseInputFileNameEx(OutBuffer, TEST_INPUT_NO_PATH, 18, sizeof(TEST_INPUT_NO_PATH), NULL, + TEST_DEFAULT_PATH, TEST_DEFAULT_EXTENSION), + CFE_SUCCESS); + UtAssert_StrCmp(OutBuffer, "/dflpath/file.log", "Exact Length input -> %s", OutBuffer); + UtAssert_INT32_EQ(OutBuffer[18], 0x7F); /* Confirm character after buffer was not touched */ + + /* test corner case for termination where result itself fits but cannot fit NUL char (should be error) */ + memset(OutBuffer, 0x7F, sizeof(OutBuffer)); + UtAssert_INT32_EQ(CFE_FS_ParseInputFileNameEx(OutBuffer, TEST_INPUT_NO_PATH, 17, sizeof(TEST_INPUT_NO_PATH), NULL, + TEST_DEFAULT_PATH, TEST_DEFAULT_EXTENSION), + CFE_FS_FNAME_TOO_LONG); + UtAssert_INT32_EQ(OutBuffer[17], 0x7F); /* Confirm character after buffer was not touched */ + + /* test corner case for termination where result can ONLY fit NUL char (result should be terminated) */ + memset(OutBuffer, 0x7F, sizeof(OutBuffer)); + UtAssert_INT32_EQ(CFE_FS_ParseInputFileNameEx(OutBuffer, TEST_INPUT_NO_PATH, 1, sizeof(TEST_INPUT_NO_PATH), NULL, + TEST_DEFAULT_PATH, TEST_DEFAULT_EXTENSION), + CFE_FS_FNAME_TOO_LONG); + UtAssert_INT32_EQ(OutBuffer[0], 0); + UtAssert_INT32_EQ(OutBuffer[1], 0x7F); + + /* test case for where input is not terminated */ + /* only the specified number of chars should be used from input */ + memset(OutBuffer, 0x7F, sizeof(OutBuffer)); + UtAssert_INT32_EQ(CFE_FS_ParseInputFileNameEx(OutBuffer, "abcdefgh", sizeof(OutBuffer), 4, NULL, TEST_DEFAULT_PATH, + TEST_DEFAULT_EXTENSION), + CFE_SUCCESS); + UtAssert_StrCmp(OutBuffer, "/dflpath/abcd.dflext", "Non terminated input -> %s", OutBuffer); + + + /* For coverage, also invoke the simplified CFE_FS_ParseInputFileName() */ + /* no more error paths here, as all real logic is in CFE_FS_ParseInputFileNameEx() */ + UtAssert_INT32_EQ(CFE_FS_ParseInputFileName(OutBuffer, TEST_INPUT_NO_EXTENSION, sizeof(OutBuffer), CFE_FS_FileCategory_TEXT_LOG), + CFE_SUCCESS); + UtAssert_StrCmp(OutBuffer, "/path/to/file.log", "Simplified API -> %s", OutBuffer); +} + +/* +** Test FS API that gets defaults for file system info +*/ +void Test_CFE_FS_DefaultFileStrings(void) +{ + /* + * Test case for: + * const char *CFE_FS_GetDefaultExtension(CFE_FS_FileCategory_t FileCategory) + * const char *CFE_FS_GetDefaultMountPoint(CFE_FS_FileCategory_t FileCategory) + * + * Note that some of these depend on platform-specific and/or user-configurable + * items, so the exact string outputs can vary. In general this just confirms + * that the returned pointer address, not the actual string content. + */ + + const char *Result; + + Result = CFE_FS_GetDefaultExtension(CFE_FS_FileCategory_UNKNOWN); + UtAssert_NULL(Result); + + Result = CFE_FS_GetDefaultExtension(CFE_FS_FileCategory_DYNAMIC_MODULE); + UtAssert_True(Result == GLOBAL_CONFIGDATA.Default_ModuleExtension, "Result (%lx) matches config (%lx)", + (unsigned long)Result, (unsigned long)GLOBAL_CONFIGDATA.Default_ModuleExtension); + + Result = CFE_FS_GetDefaultExtension(CFE_FS_FileCategory_BINARY_DATA_DUMP); + UtAssert_NOT_NULL(Result); + + Result = CFE_FS_GetDefaultExtension(CFE_FS_FileCategory_TEXT_LOG); + UtAssert_NOT_NULL(Result); + + Result = CFE_FS_GetDefaultExtension(CFE_FS_FileCategory_SCRIPT); + UtAssert_NOT_NULL(Result); + + Result = CFE_FS_GetDefaultExtension(CFE_FS_FileCategory_TEMP); + UtAssert_NOT_NULL(Result); + + Result = CFE_FS_GetDefaultExtension(CFE_FS_FileCategory_MAX); + UtAssert_NULL(Result); + + Result = CFE_FS_GetDefaultMountPoint(CFE_FS_FileCategory_UNKNOWN); + UtAssert_NULL(Result); + + Result = CFE_FS_GetDefaultMountPoint(CFE_FS_FileCategory_DYNAMIC_MODULE); + UtAssert_True(Result == GLOBAL_CFE_CONFIGDATA.NonvolMountPoint, "Result (%lx) matches config (%lx)", + (unsigned long)Result, (unsigned long)GLOBAL_CFE_CONFIGDATA.NonvolMountPoint); + + Result = CFE_FS_GetDefaultMountPoint(CFE_FS_FileCategory_BINARY_DATA_DUMP); + UtAssert_True(Result == GLOBAL_CFE_CONFIGDATA.RamdiskMountPoint, "Result (%lx) matches config (%lx)", + (unsigned long)Result, (unsigned long)GLOBAL_CFE_CONFIGDATA.RamdiskMountPoint); + + Result = CFE_FS_GetDefaultMountPoint(CFE_FS_FileCategory_TEXT_LOG); + UtAssert_True(Result == GLOBAL_CFE_CONFIGDATA.RamdiskMountPoint, "Result (%lx) matches config (%lx)", + (unsigned long)Result, (unsigned long)GLOBAL_CFE_CONFIGDATA.RamdiskMountPoint); + + Result = CFE_FS_GetDefaultMountPoint(CFE_FS_FileCategory_SCRIPT); + UtAssert_True(Result == GLOBAL_CFE_CONFIGDATA.NonvolMountPoint, "Result (%lx) matches config (%lx)", + (unsigned long)Result, (unsigned long)GLOBAL_CFE_CONFIGDATA.NonvolMountPoint); + + Result = CFE_FS_GetDefaultMountPoint(CFE_FS_FileCategory_TEMP); + UtAssert_True(Result == GLOBAL_CFE_CONFIGDATA.RamdiskMountPoint, "Result (%lx) matches config (%lx)", + (unsigned long)Result, (unsigned long)GLOBAL_CFE_CONFIGDATA.RamdiskMountPoint); + + Result = CFE_FS_GetDefaultMountPoint(CFE_FS_FileCategory_MAX); + UtAssert_NULL(Result); + + +} + /* ** Test FS API write extract file name from path function */ diff --git a/fsw/cfe-core/unit-test/fs_UT.h b/fsw/cfe-core/unit-test/fs_UT.h index 47b2573be..92c5fa1b5 100644 --- a/fsw/cfe-core/unit-test/fs_UT.h +++ b/fsw/cfe-core/unit-test/fs_UT.h @@ -123,6 +123,22 @@ void Test_CFE_FS_ReadHeader(void); ******************************************************************************/ void Test_CFE_FS_WriteHeader(void); +/*****************************************************************************/ +/** +** \brief Test FS API default file strings +** +** \par Description +** This function tests the FS API that gets filesystem info strings +** +** \par Assumptions, External Events, and Notes: +** None +** +** \returns +** This function does not return a value. +** +******************************************************************************/ +void Test_CFE_FS_DefaultFileStrings(void); + /*****************************************************************************/ /** ** \brief Test FS API set time stamp function @@ -196,6 +212,24 @@ void Test_CFE_FS_ByteSwapUint32(void); ******************************************************************************/ void Test_CFE_FS_IsGzFile(void); +/*****************************************************************************/ +/** +** \brief Test FS API parse input file name function +** +** \par Description +** This function tests the parse input file name function. +** +** \par Assumptions, External Events, and Notes: +** None +** +** \returns +** This function does not return a value. +** +** \sa #UT_InitData, #UT_Report, #CFE_FS_ParseInputFileName +** +******************************************************************************/ +void Test_CFE_FS_ParseInputFileNameEx(void); + /*****************************************************************************/ /** ** \brief Test FS API write extract file name from path function diff --git a/fsw/cfe-core/ut-stubs/ut_fs_stubs.c b/fsw/cfe-core/ut-stubs/ut_fs_stubs.c index f3bdaf3cd..f7238c0dd 100644 --- a/fsw/cfe-core/ut-stubs/ut_fs_stubs.c +++ b/fsw/cfe-core/ut-stubs/ut_fs_stubs.c @@ -40,6 +40,62 @@ /* ** Functions */ + +/* + * Stub for CFE_FS_GetDefaultMountPoint() + */ +const char *CFE_FS_GetDefaultMountPoint(CFE_FS_FileCategory_t FileCategory) +{ + UT_Stub_RegisterContextGenericArg(UT_KEY(CFE_FS_GetDefaultMountPoint), FileCategory); + + int32 Status; + static const char DEFAULT_MOUNTPOINT[] = "/ut"; + const char *Result; + + Status = UT_DEFAULT_IMPL(CFE_FS_GetDefaultMountPoint); + Result = NULL; + + if (Status == CFE_SUCCESS) + { + /* If the test case supplied a buffer, return it, otherwise return fixed value */ + UT_GetDataBuffer(UT_KEY(CFE_FS_GetDefaultMountPoint), (void**)&Result, NULL, NULL); + if (Result == NULL) + { + Result = DEFAULT_MOUNTPOINT; + } + } + + return Result; +} + +/* + * Stub for CFE_FS_GetDefaultExtension() + */ +const char *CFE_FS_GetDefaultExtension(CFE_FS_FileCategory_t FileCategory) +{ + UT_Stub_RegisterContextGenericArg(UT_KEY(CFE_FS_GetDefaultExtension), FileCategory); + + int32 Status; + static const char DEFAULT_EXTENSION[] = ".ut"; + const char *Result; + + Status = UT_DEFAULT_IMPL(CFE_FS_GetDefaultExtension); + Result = NULL; + + if (Status == CFE_SUCCESS) + { + /* If the test case supplied a buffer, return it, otherwise return fixed value */ + UT_GetDataBuffer(UT_KEY(CFE_FS_GetDefaultExtension), (void**)&Result, NULL, NULL); + if (Result == NULL) + { + Result = DEFAULT_EXTENSION; + } + } + + return Result; +} + + /*****************************************************************************/ /** ** \brief CFE_FS_InitHeader stub function @@ -199,6 +255,65 @@ int32 CFE_FS_EarlyInit(void) return status; } +/*****************************************************************************/ +/* + * Stub for CFE_FS_ParseInputFileNameEx - see prototype for description + */ +int32 CFE_FS_ParseInputFileNameEx(char *OutputBuffer, const char *InputBuffer, size_t OutputBufSize, size_t InputBufSize, + const char *DefaultInput, const char *DefaultPath, const char *DefaultExtension) +{ + UT_Stub_RegisterContextGenericArg(UT_KEY(CFE_FS_ParseInputFileNameEx), OutputBuffer); + UT_Stub_RegisterContextGenericArg(UT_KEY(CFE_FS_ParseInputFileNameEx), InputBuffer); + UT_Stub_RegisterContextGenericArg(UT_KEY(CFE_FS_ParseInputFileNameEx), OutputBufSize); + UT_Stub_RegisterContextGenericArg(UT_KEY(CFE_FS_ParseInputFileNameEx), InputBufSize); + UT_Stub_RegisterContextGenericArg(UT_KEY(CFE_FS_ParseInputFileNameEx), DefaultInput); + UT_Stub_RegisterContextGenericArg(UT_KEY(CFE_FS_ParseInputFileNameEx), DefaultPath); + UT_Stub_RegisterContextGenericArg(UT_KEY(CFE_FS_ParseInputFileNameEx), DefaultExtension); + + int32 status; + + status = UT_DEFAULT_IMPL(CFE_FS_ParseInputFileNameEx); + + /* Copy any specific output supplied by test case */ + if (status >= 0 && + UT_Stub_CopyToLocal(UT_KEY(CFE_FS_ParseInputFileNameEx), OutputBuffer, OutputBufSize) == 0 && + OutputBufSize > 0 && DefaultInput != NULL) + { + /* Otherwise fall back to simple copy */ + strncpy(OutputBuffer, DefaultInput, OutputBufSize); + } + + return status; +} + +/*****************************************************************************/ +/* + * Stub for CFE_FS_ParseInputFileName - see prototype for description + */ +int32 CFE_FS_ParseInputFileName(char *OutputBuffer, const char *InputName, size_t OutputBufSize, + CFE_FS_FileCategory_t FileCategory) +{ + UT_Stub_RegisterContextGenericArg(UT_KEY(CFE_FS_ParseInputFileName), OutputBuffer); + UT_Stub_RegisterContextGenericArg(UT_KEY(CFE_FS_ParseInputFileName), InputName); + UT_Stub_RegisterContextGenericArg(UT_KEY(CFE_FS_ParseInputFileName), OutputBufSize); + UT_Stub_RegisterContextGenericArg(UT_KEY(CFE_FS_ParseInputFileName), FileCategory); + + int32 status; + + status = UT_DEFAULT_IMPL(CFE_FS_ParseInputFileName); + + /* Copy any specific output supplied by test case */ + if (status >= 0 && + UT_Stub_CopyToLocal(UT_KEY(CFE_FS_ParseInputFileName), OutputBuffer, OutputBufSize) == 0 && + OutputBufSize > 0) + { + /* Otherwise fall back to simple copy */ + strncpy(OutputBuffer, InputName, OutputBufSize); + } + + return status; +} + /*****************************************************************************/ /**