From 81cbc46c0df5de0264ec9f6c6a8bfb7caceb7fbf Mon Sep 17 00:00:00 2001 From: Joseph Hickey Date: Mon, 1 Feb 2021 14:56:26 -0500 Subject: [PATCH] Fix #139, move file writes to background task Implements a generic asynchronous "file write request" facility in the FS subsystem. Given a metadata/control block with the file details and writer state and appropriate callbacks,, this will execute the file write job as part of the ES background task. The following file requests are changed to use this facility: - ES ER Log dump - SB Pipe Info - SB Message Map - SB Route Info - TBL Registry Dump --- fsw/cfe-core/src/es/cfe_es_apps.h | 11 +- fsw/cfe-core/src/es/cfe_es_backgroundtask.c | 6 +- fsw/cfe-core/src/es/cfe_es_erlog.c | 186 +++--- fsw/cfe-core/src/es/cfe_es_task.c | 45 +- fsw/cfe-core/src/es/cfe_es_task.h | 6 +- fsw/cfe-core/src/fs/cfe_fs_api.c | 245 ++++++++ fsw/cfe-core/src/fs/cfe_fs_priv.h | 87 +++ fsw/cfe-core/src/inc/cfe_error.h | 9 + fsw/cfe-core/src/inc/cfe_es.h | 20 + fsw/cfe-core/src/inc/cfe_fs.h | 117 ++++ fsw/cfe-core/src/inc/cfe_sb_msg.h | 6 +- fsw/cfe-core/src/sb/cfe_sb_api.c | 16 +- fsw/cfe-core/src/sb/cfe_sb_priv.h | 59 +- fsw/cfe-core/src/sb/cfe_sb_task.c | 661 ++++++++++---------- fsw/cfe-core/src/tbl/cfe_tbl_internal.h | 9 + fsw/cfe-core/src/tbl/cfe_tbl_task.h | 18 + fsw/cfe-core/src/tbl/cfe_tbl_task_cmds.c | 352 ++++++----- fsw/cfe-core/unit-test/es_UT.c | 121 ++-- fsw/cfe-core/unit-test/fs_UT.c | 141 +++++ fsw/cfe-core/unit-test/fs_UT.h | 13 + fsw/cfe-core/unit-test/sb_UT.c | 346 +++++----- fsw/cfe-core/unit-test/sb_UT.h | 139 +--- fsw/cfe-core/unit-test/tbl_UT.c | 142 +++-- fsw/cfe-core/ut-stubs/ut_es_stubs.c | 5 + fsw/cfe-core/ut-stubs/ut_fs_stubs.c | 37 ++ 25 files changed, 1764 insertions(+), 1033 deletions(-) diff --git a/fsw/cfe-core/src/es/cfe_es_apps.h b/fsw/cfe-core/src/es/cfe_es_apps.h index abe038378..ed2d1a67a 100644 --- a/fsw/cfe-core/src/es/cfe_es_apps.h +++ b/fsw/cfe-core/src/es/cfe_es_apps.h @@ -249,9 +249,14 @@ bool CFE_ES_RunAppTableScan(uint32 ElapsedTime, void *Arg); bool CFE_ES_RunExceptionScan(uint32 ElapsedTime, void *Arg); /* -** Check if ER log dump request is pending -*/ -bool CFE_ES_RunERLogDump(uint32 ElapsedTime, void *Arg); + * Background file write data getter for ER log entry + */ +bool CFE_ES_BackgroundERLogFileDataGetter(void *Meta, uint32 RecordNum, void **Buffer, size_t *BufSize); + +/* + * Background file write event handler for ER log entry + */ +void CFE_ES_BackgroundERLogFileEventHandler(void *Meta, CFE_FS_FileWriteEvent_t Event, int32 Status, uint32 RecordNum, size_t BlockSize, size_t Position); /* ** Perform the requested control action for an application diff --git a/fsw/cfe-core/src/es/cfe_es_backgroundtask.c b/fsw/cfe-core/src/es/cfe_es_backgroundtask.c index e72be0a28..93b70867e 100644 --- a/fsw/cfe-core/src/es/cfe_es_backgroundtask.c +++ b/fsw/cfe-core/src/es/cfe_es_backgroundtask.c @@ -89,9 +89,9 @@ const CFE_ES_BackgroundJobEntry_t CFE_ES_BACKGROUND_JOB_TABLE[] = .ActivePeriod = CFE_PLATFORM_ES_APP_SCAN_RATE, .IdlePeriod = CFE_PLATFORM_ES_APP_SCAN_RATE }, - { /* Check for ER log write requests */ - .RunFunc = CFE_ES_RunERLogDump, - .JobArg = &CFE_ES_TaskData.BackgroundERLogDumpState, + { /* Call FS to handle background file writes */ + .RunFunc = CFE_FS_RunBackgroundFileDump, + .JobArg = NULL, .ActivePeriod = CFE_PLATFORM_ES_APP_SCAN_RATE, .IdlePeriod = CFE_PLATFORM_ES_APP_SCAN_RATE } diff --git a/fsw/cfe-core/src/es/cfe_es_erlog.c b/fsw/cfe-core/src/es/cfe_es_erlog.c index f132d918b..1ebd390ef 100644 --- a/fsw/cfe-core/src/es/cfe_es_erlog.c +++ b/fsw/cfe-core/src/es/cfe_es_erlog.c @@ -181,126 +181,118 @@ int32 CFE_ES_WriteToERLog( CFE_ES_LogEntryType_Enum_t EntryType, uint32 Reset } /* End of CFE_ES_WriteToERLog() */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -/* Function: CFE_ES_RunERLogDump() */ +/* Function: CFE_ES_BackgroundERLogFileDataGetter() */ /* */ /* Purpose: */ -/* Write exception & reset log to a file. */ -/* */ -/* Implemented as an ES background job, but the entire file write is done */ -/* in a single invocation, as the file is expected to be relatively small. */ +/* Gets a single record from exception & reset log to write to a file. */ /* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -bool CFE_ES_RunERLogDump(uint32 ElapsedTime, void *Arg) +bool CFE_ES_BackgroundERLogFileDataGetter(void *Meta, uint32 RecordNum, void **Buffer, size_t *BufSize) { - CFE_ES_BackgroundLogDumpGlobal_t *State = (CFE_ES_BackgroundLogDumpGlobal_t *)Arg; - int32 Status; - int32 PspStatus; - CFE_FS_Header_t FileHdr; - CFE_ES_ERLog_FileEntry_t FileEntry; + CFE_ES_BackgroundLogDumpGlobal_t *BgFilePtr; + CFE_ES_ERLog_FileEntry_t *FileBufferPtr; CFE_ES_ERLog_MetaData_t *EntryPtr; - uint32 FileSize; - uint32 i; - osal_id_t fd; - + int32 PspStatus; + + BgFilePtr = (CFE_ES_BackgroundLogDumpGlobal_t *)Meta; + FileBufferPtr = &BgFilePtr->EntryBuffer; - if (!State->IsPending) + if (RecordNum < CFE_PLATFORM_ES_ER_LOG_ENTRIES) { - return false; - } + EntryPtr = &CFE_ES_ResetDataPtr->ERLog[RecordNum]; - FileSize = 0; - Status = OS_OpenCreate(&fd, State->DataFileName, OS_FILE_FLAG_CREATE | OS_FILE_FLAG_TRUNCATE, OS_WRITE_ONLY); - if(Status < 0) - { - CFE_EVS_SendEvent(CFE_ES_ERLOG2_ERR_EID,CFE_EVS_EventType_ERROR, - "Error creating file %s, RC = %d", - State->DataFileName, (int)Status); - } - else - { - CFE_FS_InitHeader(&FileHdr, CFE_ES_ER_LOG_DESC, CFE_FS_SubType_ES_ERLOG); + /* First wipe the buffer before re-use */ + memset(FileBufferPtr, 0, sizeof(*FileBufferPtr)); + + CFE_ES_LockSharedData(__func__,__LINE__); + + /* The basic info comes directly from the ES log */ + FileBufferPtr->BaseInfo = EntryPtr->BaseInfo; - /* write the cFE header to the file */ - Status = CFE_FS_WriteHeader(fd, &FileHdr); - if(Status != sizeof(CFE_FS_Header_t)) + /* + * The context info, if available, comes from the PSP. + * This returns the actual size of the context info, or <0 on error. + */ + PspStatus = CFE_PSP_Exception_CopyContext(EntryPtr->PspContextId, &FileBufferPtr->Context, + sizeof(FileBufferPtr->Context)); + if (PspStatus > 0) { - CFE_ES_FileWriteByteCntErr(State->DataFileName,sizeof(CFE_FS_Header_t),Status); + FileBufferPtr->ContextSize = PspStatus; } else { - FileSize += Status; - - /* write a single ER log entry on each pass */ - for(i=0;iERLog[i]; - - /* The basic info comes directly from the ES log */ - FileEntry.BaseInfo = EntryPtr->BaseInfo; - - /* - * The context info, if available, comes from the PSP. - * This returns the actual size of the context info, or <0 on error. - */ - PspStatus = CFE_PSP_Exception_CopyContext(EntryPtr->PspContextId, &FileEntry.Context, sizeof(FileEntry.Context)); - if (PspStatus > 0) - { - FileEntry.ContextSize = PspStatus; - } - else - { - /* - * errors here are OK - just means there is no context available. - * Record a size of 0 in the log file. - */ - FileEntry.ContextSize = 0; - } - - /* - * any unused context space should be cleared. - * - * This is for binary compatibility with historical log files, where a fixed amount - * of space is given per-entry, regardless of the actual size. - */ - if (FileEntry.ContextSize < sizeof(FileEntry.Context)) - { - memset(&FileEntry.Context[FileEntry.ContextSize], 0, - sizeof(FileEntry.Context) - FileEntry.ContextSize); - } - - /* - * Now write to file - */ - Status = OS_write(fd,&FileEntry,sizeof(FileEntry)); - - if(Status != sizeof(FileEntry)) - { - CFE_ES_FileWriteByteCntErr(State->DataFileName,sizeof(FileEntry),Status); - break; - }/* end if */ + /* + * errors here are OK - just means there is no context available. + * Record a size of 0 in the log file. + */ + FileBufferPtr->ContextSize = 0; + } - FileSize += Status; + CFE_ES_UnlockSharedData(__func__,__LINE__); - } /* end for */ + /* + * Export data to caller for actual write + */ + *Buffer = FileBufferPtr; + *BufSize = sizeof(*FileBufferPtr); + } + else + { + *Buffer = NULL; + *BufSize = 0; + } - } /* end if */ + /* Check for EOF (last entry) */ + return (RecordNum >= (CFE_PLATFORM_ES_ER_LOG_ENTRIES-1)); +} - OS_close(fd); - CFE_EVS_SendEvent(CFE_ES_ERLOG2_EID, CFE_EVS_EventType_DEBUG, - "%s written:Size=%lu",State->DataFileName,(unsigned long)FileSize); +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/* Function: CFE_ER_BackgroundERLogFileEventHandler() */ +/* */ +/* Purpose: */ +/* Report events during writing exception & reset log to a file. */ +/* */ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +void CFE_ES_BackgroundERLogFileEventHandler(void *Meta, CFE_FS_FileWriteEvent_t Event, int32 Status, uint32 RecordNum, size_t BlockSize, size_t Position) +{ + CFE_ES_BackgroundLogDumpGlobal_t *BgFilePtr; - } /* end if */ + BgFilePtr = (CFE_ES_BackgroundLogDumpGlobal_t *)Meta; /* - * Always clear the "pending" flag whether successful or not. - * If unsuccessful, an operator needs to investigate the error and re-issue command. + * Note that this runs in the context of ES background task (file writer background job) + * It does NOT run in the context of the CFE_TBL app task. + * + * Events should use CFE_EVS_SendEventWithAppID() rather than CFE_EVS_SendEvent() + * to get proper association with TBL task. */ - State->IsPending = false; - - return false; -}/* end CFE_ES_RunERLogDump */ - + switch(Event) + { + case CFE_FS_FileWriteEvent_COMPLETE: + CFE_EVS_SendEvent(CFE_ES_ERLOG2_EID, CFE_EVS_EventType_DEBUG, + "%s written:Size=%lu", + BgFilePtr->FileWrite.FileName,(unsigned long)Position); + break; + + case CFE_FS_FileWriteEvent_HEADER_WRITE_ERROR: + case CFE_FS_FileWriteEvent_RECORD_WRITE_ERROR: + CFE_EVS_SendEvent(CFE_ES_FILEWRITE_ERR_EID,CFE_EVS_EventType_ERROR, + "File write,byte cnt err,file %s,request=%u,actual=%u", + BgFilePtr->FileWrite.FileName, (int)BlockSize, (int)Status); + break; + + case CFE_FS_FileWriteEvent_CREATE_ERROR: + CFE_EVS_SendEvent(CFE_ES_ERLOG2_ERR_EID,CFE_EVS_EventType_ERROR, + "Error creating file %s, RC = %d", + BgFilePtr->FileWrite.FileName, (int)Status); + break; + + default: + /* unhandled event - ignore */ + break; + } +} /* **--------------------------------------------------------------------------------------- diff --git a/fsw/cfe-core/src/es/cfe_es_task.c b/fsw/cfe-core/src/es/cfe_es_task.c index 3f7696876..06365bfa3 100644 --- a/fsw/cfe-core/src/es/cfe_es_task.c +++ b/fsw/cfe-core/src/es/cfe_es_task.c @@ -1612,26 +1612,51 @@ int32 CFE_ES_ClearERLogCmd(const CFE_ES_ClearERLogCmd_t *data) int32 CFE_ES_WriteERLogCmd(const CFE_ES_WriteERLogCmd_t *data) { const CFE_ES_FileNameCmd_Payload_t *CmdPtr = &data->Payload; + CFE_ES_BackgroundLogDumpGlobal_t *StatePtr; + int32 Status; + + StatePtr = &CFE_ES_TaskData.BackgroundERLogDumpState; + + /* check if pending before overwriting fields in the structure */ + if (CFE_FS_BackgroundFileDumpIsPending(&StatePtr->FileWrite)) + { + Status = CFE_STATUS_REQUEST_ALREADY_PENDING; + } + else + { + /* Reset the entire state object (just for good measure, ensure no stale data) */ + memset(StatePtr, 0, sizeof(*StatePtr)); + + /* + * Fill out the remainder of meta data. + * This data is currently the same for every request + */ + StatePtr->FileWrite.FileSubType = CFE_FS_SubType_ES_ERLOG; + snprintf(StatePtr->FileWrite.Description, sizeof(StatePtr->FileWrite.Description), CFE_ES_ER_LOG_DESC); + + StatePtr->FileWrite.GetData = CFE_ES_BackgroundERLogFileDataGetter; + StatePtr->FileWrite.OnEvent = CFE_ES_BackgroundERLogFileEventHandler; - if (CFE_ES_TaskData.BackgroundERLogDumpState.IsPending) + CFE_SB_MessageStringGet(StatePtr->FileWrite.FileName, CmdPtr->FileName, + CFE_PLATFORM_ES_DEFAULT_ER_LOG_FILE, + sizeof(StatePtr->FileWrite.FileName), sizeof(CmdPtr->FileName)); + + Status = CFE_FS_BackgroundFileDumpRequest(&StatePtr->FileWrite); + } + + if (Status != CFE_SUCCESS) { CFE_EVS_SendEvent(CFE_ES_ERLOG_PENDING_ERR_EID,CFE_EVS_EventType_ERROR, "Error log write to file %s already in progress", - CFE_ES_TaskData.BackgroundERLogDumpState.DataFileName); + StatePtr->FileWrite.FileName); /* background dump already running, consider this an error */ CFE_ES_TaskData.CommandErrorCounter++; - } + } else { - CFE_SB_MessageStringGet(CFE_ES_TaskData.BackgroundERLogDumpState.DataFileName, (char *)CmdPtr->FileName, - CFE_PLATFORM_ES_DEFAULT_ER_LOG_FILE, - sizeof(CFE_ES_TaskData.BackgroundERLogDumpState.DataFileName), sizeof(CmdPtr->FileName)); - - CFE_ES_TaskData.BackgroundERLogDumpState.IsPending = true; CFE_ES_TaskData.CommandCounter++; - CFE_ES_BackgroundWakeup(); - } + } return CFE_SUCCESS; }/* end CFE_ES_WriteERLogCmd */ diff --git a/fsw/cfe-core/src/es/cfe_es_task.h b/fsw/cfe-core/src/es/cfe_es_task.h index 479799d80..fb8d69cf6 100644 --- a/fsw/cfe-core/src/es/cfe_es_task.h +++ b/fsw/cfe-core/src/es/cfe_es_task.h @@ -44,6 +44,7 @@ #include "cfe_es_events.h" #include "cfe_es_msg.h" #include "cfe_es_perf.h" +#include "private/cfe_es_erlog_typedef.h" /*************************************************************************/ @@ -90,8 +91,8 @@ */ typedef struct { - volatile bool IsPending; - char DataFileName[OS_MAX_PATH_LEN]; + CFE_FS_FileWriteMetaData_t FileWrite; /**< FS state data - must be first */ + CFE_ES_ERLog_FileEntry_t EntryBuffer; /**< Temp holding area for record to write */ } CFE_ES_BackgroundLogDumpGlobal_t; /* @@ -164,7 +165,6 @@ void CFE_ES_TaskPipe(CFE_SB_Buffer_t *SBBufPtr); */ int32 CFE_ES_BackgroundInit(void); void CFE_ES_BackgroundTask(void); -void CFE_ES_BackgroundWakeup(void); void CFE_ES_BackgroundCleanup(void); /* diff --git a/fsw/cfe-core/src/fs/cfe_fs_api.c b/fsw/cfe-core/src/fs/cfe_fs_api.c index 4f83e5f13..7638472e9 100644 --- a/fsw/cfe-core/src/fs/cfe_fs_api.c +++ b/fsw/cfe-core/src/fs/cfe_fs_api.c @@ -328,6 +328,251 @@ int32 CFE_FS_ExtractFilenameFromPath(const char *OriginalPath, char *FileNameOnl return(ReturnCode); } +/* +** CFE_FS_RunBackgroundFileDump - See API and header file for details +*/ +bool CFE_FS_RunBackgroundFileDump(uint32 ElapsedTime, void *Arg) +{ + CFE_FS_CurrentFileState_t *State; + CFE_FS_BackgroundFileDumpEntry_t *Curr; + CFE_FS_FileWriteMetaData_t *Meta; + int32 Status; + CFE_FS_Header_t FileHdr; + void *RecordPtr; + size_t RecordSize; + bool IsEOF; + + State = &CFE_FS_Global.FileDump.Current; + Curr = NULL; + IsEOF = false; + RecordPtr = NULL; + RecordSize = 0; + + State->Credit += (ElapsedTime * CFE_FS_BACKGROUND_CREDIT_PER_SECOND) / 1000; + if (State->Credit > CFE_FS_BACKGROUND_MAX_CREDIT) + { + State->Credit = CFE_FS_BACKGROUND_MAX_CREDIT; + } + + /* + * Lock shared data. + * Not strictly necessary as the "CompleteCount" is only updated + * by this task but this helps in case the access isn't atomic. + */ + CFE_FS_LockSharedData(__func__); + + if (CFE_FS_Global.FileDump.CompleteCount != CFE_FS_Global.FileDump.RequestCount) + { + Curr = &CFE_FS_Global.FileDump.Entries[CFE_FS_Global.FileDump.CompleteCount & (CFE_FS_MAX_BACKGROUND_FILE_WRITES - 1)]; + } + + CFE_FS_UnlockSharedData(__func__); + + if (Curr == NULL) + { + return false; + } + + Meta = Curr->Meta; + + if (!OS_ObjectIdDefined(State->Fd) && Meta->IsPending) + { + /* First time processing this entry - open the file */ + Status = OS_OpenCreate(&State->Fd, Meta->FileName, OS_FILE_FLAG_CREATE | OS_FILE_FLAG_TRUNCATE, OS_WRITE_ONLY); + if(Status < 0) + { + State->Fd = OS_OBJECT_ID_UNDEFINED; + Meta->OnEvent(Meta, CFE_FS_FileWriteEvent_CREATE_ERROR, Status, 0, 0, 0); + } + else + { + CFE_FS_InitHeader(&FileHdr, Meta->Description, Meta->FileSubType); + + /* write the cFE header to the file */ + Status = CFE_FS_WriteHeader(State->Fd, &FileHdr); + if (Status != sizeof(CFE_FS_Header_t)) + { + OS_close(State->Fd); + State->Fd = OS_OBJECT_ID_UNDEFINED; + Meta->OnEvent(Meta, CFE_FS_FileWriteEvent_HEADER_WRITE_ERROR, Status, State->RecordNum, sizeof(CFE_FS_Header_t), State->FileSize); + } + else + { + State->FileSize = sizeof(CFE_FS_Header_t); + State->Credit -= sizeof(CFE_FS_Header_t); + State->RecordNum = 0; + } + } + } + + while (OS_ObjectIdDefined(State->Fd) && State->Credit > 0 && !IsEOF) + { + /* + * Getter should return false on EOF (last record), true if more data is still waiting + */ + IsEOF = Meta->GetData(Meta, State->RecordNum, &RecordPtr, &RecordSize); + + /* + * if the getter outputs a record size of 0, this means there is no data for + * this entry, but the cycle keeps going (in case of "holes" or unused table entries + * in the database). + */ + if (RecordSize > 0) + { + State->Credit -= RecordSize; + + /* + * Now write to file + */ + Status = OS_write(State->Fd,RecordPtr,RecordSize); + + if (Status != RecordSize) + { + /* end the file early (cannot set "IsEOF" as this would cause the complete event to be generated too) */ + OS_close(State->Fd); + State->Fd = OS_OBJECT_ID_UNDEFINED; + + /* generate write error event */ + Meta->OnEvent(Meta, CFE_FS_FileWriteEvent_RECORD_WRITE_ERROR, Status, State->RecordNum, RecordSize, State->FileSize); + break; + } + else + { + State->FileSize += RecordSize; + } + } + + ++State->RecordNum; + + } /* end if */ + + /* On normal EOF close the file and generate the complete event */ + if (IsEOF) + { + OS_close(State->Fd); + State->Fd = OS_OBJECT_ID_UNDEFINED; + + /* generate complete event */ + Meta->OnEvent(Meta, CFE_FS_FileWriteEvent_COMPLETE, CFE_SUCCESS, State->RecordNum, 0, State->FileSize); + } + + /* + * if the file is not open, consider this file complete, and advance the head position. + * (done this way so it also catches the case where the file failed to create, not just EOF) + */ + if (!OS_ObjectIdDefined(State->Fd)) + { + CFE_FS_LockSharedData(__func__); + + /* Wipe the entry structure, as it will be reused */ + memset(Curr, 0, sizeof(*Curr)); + ++CFE_FS_Global.FileDump.CompleteCount; + + /* Set the "IsPending" flag to false - this indicates that the originator may re-post now */ + Meta->IsPending = false; + + CFE_FS_UnlockSharedData(__func__); + + } /* end if */ + + return !IsEOF; +} + +/* +** CFE_FS_BackgroundFileDumpRequest - See API and header file for details +*/ +int32 CFE_FS_BackgroundFileDumpRequest(CFE_FS_FileWriteMetaData_t *Meta) +{ + CFE_FS_BackgroundFileDumpEntry_t *Curr; + int32 Status; + uint32 PendingRequestCount; + + /* Pre-validate inputs */ + if (Meta == NULL) + { + return CFE_FS_BAD_ARGUMENT; + } + + /* getter and event functions must be set */ + if (Meta->GetData == NULL || Meta->OnEvent == NULL) + { + return CFE_FS_BAD_ARGUMENT; + } + + /* filename cannot be empty */ + if (Meta->FileName[0] == 0) + { + return CFE_FS_INVALID_PATH; + } + + /* request must not already be pending */ + if (Meta->IsPending) + { + return CFE_STATUS_REQUEST_ALREADY_PENDING; + } + + + CFE_FS_LockSharedData(__func__); + + PendingRequestCount = CFE_FS_Global.FileDump.RequestCount + 1; + + /* Check if queue is full before writing to tail position */ + if (PendingRequestCount == (CFE_FS_Global.FileDump.CompleteCount + CFE_FS_MAX_BACKGROUND_FILE_WRITES)) + { + Status = CFE_STATUS_REQUEST_ALREADY_PENDING; + } + else + { + Curr = &CFE_FS_Global.FileDump.Entries[CFE_FS_Global.FileDump.RequestCount & (CFE_FS_MAX_BACKGROUND_FILE_WRITES - 1)]; + + /* + * store the meta object - note this retains the pointer that was submitted + * (caller must not reuse/change this object until request is completed) + */ + Curr->Meta = Meta; + + /* + * The "IsPending" Flag will be set true whenever while this is waiting in the request queue. + * It will be set false when the file is done. + * + * The requester can check this flag to determine if/when the request is complete + */ + Meta->IsPending = true; + + /* update tail position */ + CFE_FS_Global.FileDump.RequestCount = PendingRequestCount; + + Status = CFE_SUCCESS; + } + + CFE_FS_UnlockSharedData(__func__); + + if (Status == CFE_SUCCESS) + { + /* + * If successfully added to write queue, then wake the ES background task to get started. + * + * This may reduce the overall latency between request and completion (depending on other + * background task work). If this is the only pending job, this should get it started faster. + */ + CFE_ES_BackgroundWakeup(); + } + + return Status; +} + +/* +** CFE_FS_ExtractFilenameFromPath - See API and header file for details +*/ +bool CFE_FS_BackgroundFileDumpIsPending(const CFE_FS_FileWriteMetaData_t *Meta) +{ + if (Meta == NULL) + { + return false; + } + + return Meta->IsPending; +} /************************/ /* End of File Comment */ diff --git a/fsw/cfe-core/src/fs/cfe_fs_priv.h b/fsw/cfe-core/src/fs/cfe_fs_priv.h index c4772bad5..c2826b466 100644 --- a/fsw/cfe-core/src/fs/cfe_fs_priv.h +++ b/fsw/cfe-core/src/fs/cfe_fs_priv.h @@ -44,11 +44,93 @@ ** Macro Definitions */ +/* + * Max Number of file write requests that can be queued + * + * This needs to be a power of two to simplify the masking/wraparound (bitwise AND). + */ +#define CFE_FS_MAX_BACKGROUND_FILE_WRITES 4 + + +/* + * Background file credit accumulation rate + * + * The background file writer will limit the total bytes written over time. This + * controls the amount of "credit" (bytes that can be written) per second + * of elapsed time. + * + * This permits a file writing rate of up to 10kbytes/sec. + */ +#define CFE_FS_BACKGROUND_CREDIT_PER_SECOND 10000 + +/* + * Maximum credit that the background write task can accumulate + * + * The background file writer will limit the total bytes written over time, and + * will accumulate credit against this limit while no writes are in progress. + * This is an upper cap on the amount of credit that can be accumulated. + * + * Without this limit, after a long period of inactivity without any file + * writes, a large credit would essentially bypass the rate limiting for + * the next file write command(s) once they are issued. + */ +#define CFE_FS_BACKGROUND_MAX_CREDIT 10000 /* ** Type Definitions */ +/* + * Background file dump entry structure + * + * This structure is stored in global memory and keeps the state + * of the file dump from one iteration to the next. + */ +typedef struct +{ + CFE_ES_AppId_t RequestorAppId; + CFE_FS_FileWriteMetaData_t *Meta; +} CFE_FS_BackgroundFileDumpEntry_t; + +typedef struct +{ + osal_id_t Fd; + int32 Credit; + uint32 RecordNum; + size_t FileSize; +} CFE_FS_CurrentFileState_t; + + +/** + * \brief Background file dump queue structure + * + * This structure is stored in global memory and keeps the state + * of the file dump from one iteration to the next. + * + * Normally when idle the "RequestCount" and "CompleteCount" are the + * same value. When an application requests a background file dump, + * the "RequestCount" is incremented accordingly, and when the background + * job finishes, the "CompleteCount" is incremented accordingly. + */ +typedef struct +{ + uint32 RequestCount; /**< Total Number of background file writes requested */ + uint32 CompleteCount; /**< Total Number of background file writes completed */ + + /** + * Data related to each background file write request + */ + CFE_FS_BackgroundFileDumpEntry_t Entries[CFE_FS_MAX_BACKGROUND_FILE_WRITES]; + + /** + * Persistent storage for the current file write + * (reused for each file) + */ + CFE_FS_CurrentFileState_t Current; + +} CFE_FS_BackgroundFileDumpState_t; + + /****************************************************************************** ** Typedef: CFE_FS_Global_t ** @@ -59,8 +141,13 @@ typedef struct { osal_id_t SharedDataMutexId; + CFE_FS_BackgroundFileDumpState_t FileDump; + } CFE_FS_Global_t; + +extern CFE_FS_Global_t CFE_FS_Global; + /* ** FS Function Prototypes */ diff --git a/fsw/cfe-core/src/inc/cfe_error.h b/fsw/cfe-core/src/inc/cfe_error.h index 872c89f03..98b82dc26 100644 --- a/fsw/cfe-core/src/inc/cfe_error.h +++ b/fsw/cfe-core/src/inc/cfe_error.h @@ -172,6 +172,15 @@ typedef int32 CFE_Status_t; */ #define CFE_STATUS_EXTERNAL_RESOURCE_FAIL ((int32)0xc8000005) +/** + * @brief Request already pending + * + * Commands or requests are already pending or the pending request + * limit has been reached. No more requests can be made until + * the current request(s) complete. + */ +#define CFE_STATUS_REQUEST_ALREADY_PENDING ((int32)0xc8000006) + /** * @brief Not Implemented * diff --git a/fsw/cfe-core/src/inc/cfe_es.h b/fsw/cfe-core/src/inc/cfe_es.h index 499fe397f..2837aaf05 100644 --- a/fsw/cfe-core/src/inc/cfe_es.h +++ b/fsw/cfe-core/src/inc/cfe_es.h @@ -1105,6 +1105,26 @@ void CFE_ES_ExitChildTask(void); * @{ */ +/*****************************************************************************/ +/** +** \brief Wakes up the CFE background task +** +** \par Description +** Normally the ES background task wakes up at a periodic interval. +** Whenever new background work is added, this can be used to wake the task early, +** which may reduce the delay between adding the job and the job getting processed. +** +** \par Assumptions, External Events, and Notes: +** Note the amount of work that the background task will perform is pro-rated +** based on the amount of time elapsed since the last wakeup. Waking the task +** early will not cause the background task to do more work than it otherwise +** would - it just reduces the delay before work starts initially. +** +** +******************************************************************************/ +void CFE_ES_BackgroundWakeup(void); + + /*****************************************************************************/ /** ** \brief Write a string to the cFE System Log diff --git a/fsw/cfe-core/src/inc/cfe_fs.h b/fsw/cfe-core/src/inc/cfe_fs.h index d6230ab77..91d4b98af 100644 --- a/fsw/cfe-core/src/inc/cfe_fs.h +++ b/fsw/cfe-core/src/inc/cfe_fs.h @@ -42,6 +42,66 @@ #include "common_types.h" #include "cfe_time.h" +/* + * 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 + * on behalf of whatever App requested the file write. + * + * This is a list of abstract events associated with background file write jobs. + * An app requesting the file write must supply a callback function to translate + * these into its own event IDs for feedback (i.e. file complete, error conditions, etc). + */ +typedef enum +{ + CFE_FS_FileWriteEvent_UNDEFINED, /* placeholder, no-op, keep as 0 */ + + CFE_FS_FileWriteEvent_COMPLETE, /**< File is completed successfully */ + CFE_FS_FileWriteEvent_CREATE_ERROR, /**< Unable to create/open file */ + CFE_FS_FileWriteEvent_HEADER_WRITE_ERROR, /**< Unable to write FS header */ + CFE_FS_FileWriteEvent_RECORD_WRITE_ERROR, /**< Unable to write data record */ + + CFE_FS_FileWriteEvent_MAX /* placeholder, no-op, keep last */ + +} CFE_FS_FileWriteEvent_t; + + +/** + * Data Getter routine provided by requester + * + * Outputs a data block. Should return true if the file is complete (last record/EOF), otherwise return false. + */ +typedef bool (*CFE_FS_FileWriteGetData_t)(void *Meta, uint32 RecordNum, void **Buffer, size_t *BufSize); + +/** + * Event generator routine provided by requester + * + * Invoked from certain points in the file write process. Implementation may invoke CFE_EVS_SendEvent() appropriately + * to inform of progress. + */ +typedef void (*CFE_FS_FileWriteOnEvent_t)(void *Meta, CFE_FS_FileWriteEvent_t Event, int32 Status, uint32 RecordNum, size_t BlockSize, size_t Position); + +/** + * \brief External Metadata/State object associated with background file writes + * + * Applications intending to schedule background file write jobs should instantiate + * this object in static/global data memory. This keeps track of the state of the + * file write request(s). + */ +typedef struct CFE_FS_FileWriteMetaData +{ + volatile bool IsPending; /**< Whether request is pending (volatile as it may be checked outside lock) */ + + char FileName[OS_MAX_PATH_LEN]; /**< Name of file to write */ + + /* Data for FS header */ + uint32 FileSubType; /**< Type of file to write (for FS header) */ + char Description[CFE_FS_HDR_DESC_MAX_LEN]; /**< Description of file (for FS header) */ + + CFE_FS_FileWriteGetData_t GetData; /**< Application callback to get a data record */ + CFE_FS_FileWriteOnEvent_t OnEvent; /**< Application callback for abstract event processing */ + +} CFE_FS_FileWriteMetaData_t; + /** @defgroup CFEAPIFSHeader cFE File Header Management APIs * @{ @@ -183,6 +243,63 @@ CFE_Status_t CFE_FS_SetTimestamp(osal_id_t FileDes, CFE_TIME_SysTime_t NewTimest ** ******************************************************************************/ CFE_Status_t CFE_FS_ExtractFilenameFromPath(const char *OriginalPath, char *FileNameOnly); + +/*****************************************************************************/ +/** +** \brief Register a background file dump request +** +** \par Description +** Puts the previously-initialized metadata into the pending request queue +** +** \par Assumptions, External Events, and Notes: +** Metadata structure should be stored in a static memory area (not on heap) as it +** must persist and be accessible by the file writer task throughout the asynchronous +** job operation. +** +** \param[inout] Meta The background file write persistent state object +** +** \return Execution status, see \ref CFEReturnCodes +** +******************************************************************************/ +int32 CFE_FS_BackgroundFileDumpRequest(CFE_FS_FileWriteMetaData_t *Meta); + +/*****************************************************************************/ +/** +** \brief Query if a background file write request is currently pending +** +** \par Description +** This returns "true" while the request is on the background work queue +** This returns "false" once the request is complete and removed from the queue. +** +** \par Assumptions, External Events, and Notes: +** None +** +** \param[inout] Meta The background file write persistent state object +** +** \return true if request is already pending, false if not +** +******************************************************************************/ +bool CFE_FS_BackgroundFileDumpIsPending(const CFE_FS_FileWriteMetaData_t *Meta); + +/*****************************************************************************/ +/** +** \brief Execute the background file write job(s) +** +** \par Description +** Runs the state machine associated with background file write requests +** +** \par Assumptions, External Events, and Notes: +** This should only be invoked as a background job from the ES background task, +** it should not be invoked directly. +** +** \param[in] ElapsedTime The amount of time passed since last invocation (ms) +** \param[in] Arg Not used/ignored +** +** \return true if jobs are pending, false if idle +** +******************************************************************************/ +bool CFE_FS_RunBackgroundFileDump(uint32 ElapsedTime, void *Arg); + /**@}*/ #endif /* _cfe_fs_ */ diff --git a/fsw/cfe-core/src/inc/cfe_sb_msg.h b/fsw/cfe-core/src/inc/cfe_sb_msg.h index a9ed6f0e5..e504f4b37 100644 --- a/fsw/cfe-core/src/inc/cfe_sb_msg.h +++ b/fsw/cfe-core/src/inc/cfe_sb_msg.h @@ -591,11 +591,11 @@ typedef struct CFE_SB_PipeDepthStats { CFE_SB_PipeId_t PipeId;/**< \cfetlmmnemonic \SB_PDPIPEID \brief Pipe Id associated with the stats below */ - uint16 Depth;/**< \cfetlmmnemonic \SB_PDDEPTH + uint16 MaxQueueDepth;/**< \cfetlmmnemonic \SB_PDDEPTH \brief Number of messages the pipe can hold */ - uint16 InUse;/**< \cfetlmmnemonic \SB_PDINUSE + uint16 CurrentQueueDepth;/**< \cfetlmmnemonic \SB_PDINUSE \brief Number of messages currently on the pipe */ - uint16 PeakInUse;/**< \cfetlmmnemonic \SB_PDPKINUSE + uint16 PeakQueueDepth;/**< \cfetlmmnemonic \SB_PDPKINUSE \brief Peak number of messages that have been on the pipe */ uint16 Spare;/**< \cfetlmmnemonic \SB_PDSPARE \brief Spare word to ensure alignment */ diff --git a/fsw/cfe-core/src/sb/cfe_sb_api.c b/fsw/cfe-core/src/sb/cfe_sb_api.c index 772a84e53..8a1b33a98 100644 --- a/fsw/cfe-core/src/sb/cfe_sb_api.c +++ b/fsw/cfe-core/src/sb/cfe_sb_api.c @@ -194,9 +194,9 @@ int32 CFE_SB_CreatePipe(CFE_SB_PipeId_t *PipeIdPtr, uint16 Depth, const char * if (Status == CFE_SUCCESS) { /* fill in the pipe table fields */ - PipeDscPtr->SysQueueId = SysQueueId; - PipeDscPtr->QueueDepth = Depth; - PipeDscPtr->AppId = AppId; + PipeDscPtr->SysQueueId = SysQueueId; + PipeDscPtr->MaxQueueDepth = Depth; + PipeDscPtr->AppId = AppId; CFE_SB_PipeDescSetUsed(PipeDscPtr, PendingPipeId); @@ -1692,10 +1692,10 @@ int32 CFE_SB_TransmitBufferFull(CFE_SB_BufferD_t *BufDscPtr, DestPtr->BuffCount++; /* used for checking MsgId2PipeLimit */ DestPtr->DestCnt++; /* used for statistics */ - ++PipeDscPtr->QueueDepth; - if (PipeDscPtr->QueueDepth >= PipeDscPtr->PeakDepth) + ++PipeDscPtr->CurrentQueueDepth; + if (PipeDscPtr->CurrentQueueDepth >= PipeDscPtr->PeakQueueDepth) { - PipeDscPtr->PeakDepth = PipeDscPtr->QueueDepth; + PipeDscPtr->PeakQueueDepth = PipeDscPtr->CurrentQueueDepth; } Status = CFE_SUCCESS; @@ -2001,9 +2001,9 @@ int32 CFE_SB_ReceiveBuffer(CFE_SB_Buffer_t **BufPtr, DestPtr->BuffCount--; } - if (PipeDscPtr->CurrentDepth > 0) + if (PipeDscPtr->CurrentQueueDepth > 0) { - --PipeDscPtr->CurrentDepth; + --PipeDscPtr->CurrentQueueDepth; } } else diff --git a/fsw/cfe-core/src/sb/cfe_sb_priv.h b/fsw/cfe-core/src/sb/cfe_sb_priv.h index 23abe02ec..d01ab0b5d 100644 --- a/fsw/cfe-core/src/sb/cfe_sb_priv.h +++ b/fsw/cfe-core/src/sb/cfe_sb_priv.h @@ -149,10 +149,10 @@ typedef struct { uint8 Spare; CFE_ES_AppId_t AppId; osal_id_t SysQueueId; - uint16 QueueDepth; uint16 SendErrors; - uint16 CurrentDepth; - uint16 PeakDepth; + uint16 MaxQueueDepth; + uint16 CurrentQueueDepth; + uint16 PeakQueueDepth; CFE_SB_BufferD_t *LastBuffer; } CFE_SB_PipeD_t; @@ -169,6 +169,43 @@ typedef struct { } CFE_SB_MemParams_t; +/*******************************************************************************/ +/** +** \brief SB route info temporary structure +** +** This tracks the number of desinations along with destination data for 1 route. +** Each route may contain zero or more desinations (variable length). +*/ +typedef struct +{ + uint32 NumDestinations; + CFE_SB_RoutingFileEntry_t DestEntries[CFE_PLATFORM_SB_MAX_DEST_PER_PKT]; /**< Actual data written to file */ +} CFE_SB_BackgroundRouteInfoBuffer_t; + +/** + * \brief Temporary holding buffer for records being written to a file. + * + * This is shared/reused between all file types (msg map, route info, pipe info). + */ +typedef union +{ + CFE_SB_BackgroundRouteInfoBuffer_t RouteInfo; + CFE_SB_PipeInfoEntry_t PipeInfo; + CFE_SB_MsgMapFileEntry_t MsgMapInfo; +} CFE_SB_BackgroundFileBuffer_t; + +/** + * \brief SB Background file write state information + * + * Must be stored in persistent memory (e.g. global). + */ +typedef struct +{ + CFE_FS_FileWriteMetaData_t FileWrite; /**< FS state data - must be first */ + CFE_SB_BackgroundFileBuffer_t Buffer; /**< Temporary holding area for file record */ +} CFE_SB_BackgroundFileStateInfo_t; + + /****************************************************************************** ** Typedef: CFE_SB_Global_t @@ -192,6 +229,8 @@ typedef struct CFE_EVS_BinFilter_t EventFilters[CFE_SB_MAX_CFG_FILE_EVENTS_TO_FILTER]; CFE_SB_Qos_t Default_Qos; CFE_ResourceId_t LastPipeId; + + CFE_SB_BackgroundFileStateInfo_t BackgroundFile; } CFE_SB_Global_t; @@ -261,9 +300,6 @@ int32 CFE_SB_TransmitMsgValidate(CFE_MSG_Message_t *MsgPtr, CFE_SB_MsgId_t *MsgIdPtr, CFE_MSG_Size_t *SizePtr, CFE_SBR_RouteId_t *RouteIdPtr); -int32 CFE_SB_WriteRtgInfo(const char *Filename); -int32 CFE_SB_WritePipeInfo(const char *Filename); -int32 CFE_SB_WriteMapInfo(const char *Filename); int32 CFE_SB_ZeroCopyReleaseDesc(CFE_SB_Buffer_t *Ptr2Release, CFE_SB_ZeroCopyHandle_t BufferHandle); int32 CFE_SB_ZeroCopyReleaseAppId(CFE_ES_AppId_t AppId); void CFE_SB_IncrBufUseCnt(CFE_SB_BufferD_t *bd); @@ -271,7 +307,6 @@ void CFE_SB_DecrBufUseCnt(CFE_SB_BufferD_t *bd); int32 CFE_SB_ValidateMsgId(CFE_SB_MsgId_t MsgId); int32 CFE_SB_ValidatePipeId(CFE_SB_PipeId_t PipeId); void CFE_SB_IncrCmdCtr(int32 status); -void CFE_SB_FileWriteByteCntErr(const char *Filename,uint32 Requested,uint32 Actual); void CFE_SB_SetSubscriptionReporting(uint32 state); int32 CFE_SB_SendSubscriptionReport(CFE_SB_MsgId_t MsgId, CFE_SB_PipeId_t PipeId, CFE_SB_Qos_t Quality); uint32 CFE_SB_RequestToSendEvent(CFE_ES_TaskId_t TaskId, uint32 Bit); @@ -470,6 +505,16 @@ static inline bool CFE_SB_PipeDescIsMatch(const CFE_SB_PipeD_t *PipeDscPtr, CFE_ bool CFE_SB_CheckPipeDescSlotUsed(CFE_ResourceId_t CheckId); +/* + * Helper functions for background file write requests (callbacks) + */ +void CFE_SB_CollectMsgMapInfo(CFE_SBR_RouteId_t RouteId, void *ArgPtr); +bool CFE_SB_WriteMsgMapInfoDataGetter(void *Meta, uint32 RecordNum, void **Buffer, size_t *BufSize); +void CFE_SB_CollectRouteInfo(CFE_SBR_RouteId_t RouteId, void *ArgPtr); +bool CFE_SB_WriteRouteInfoDataGetter(void *Meta, uint32 RecordNum, void **Buffer, size_t *BufSize); +bool CFE_SB_WritePipeInfoDataGetter(void *Meta, uint32 RecordNum, void **Buffer, size_t *BufSize); +void CFE_SB_BackgroundFileEventHandler(void *Meta, CFE_FS_FileWriteEvent_t Event, int32 Status, uint32 RecordNum, size_t BlockSize, size_t Position); + /* * External variables private to the software bus module */ diff --git a/fsw/cfe-core/src/sb/cfe_sb_task.c b/fsw/cfe-core/src/sb/cfe_sb_task.c index 623b5d205..5c24e5fe6 100644 --- a/fsw/cfe-core/src/sb/cfe_sb_task.c +++ b/fsw/cfe-core/src/sb/cfe_sb_task.c @@ -806,9 +806,11 @@ int32 CFE_SB_SendStatsCmd(const CFE_SB_SendSbStatsCmd_t *data) if (CFE_SB_PipeDescIsUsed(PipeDscPtr)) { PipeStatPtr->PipeId = PipeDscPtr->PipeId; - PipeStatPtr->InUse = PipeDscPtr->CurrentDepth; - PipeStatPtr->PeakInUse = PipeDscPtr->PeakDepth; - PipeStatPtr->Depth = PipeDscPtr->QueueDepth; + + /* Copy depth info */ + PipeStatPtr->CurrentQueueDepth = PipeDscPtr->CurrentQueueDepth; + PipeStatPtr->PeakQueueDepth = PipeDscPtr->PeakQueueDepth; + PipeStatPtr->MaxQueueDepth = PipeDscPtr->MaxQueueDepth; ++PipeStatPtr; --PipeStatCount; @@ -839,156 +841,79 @@ int32 CFE_SB_SendStatsCmd(const CFE_SB_SendSbStatsCmd_t *data) return CFE_SUCCESS; }/* CFE_SB_SendStatsCmd */ - -/****************************************************************************** - * \brief SB internal function to handle processing of 'Write Routing Info' Cmd - * - * \param[in] data Pointer to command structure - * - * \return Execution status, see \ref CFEReturnCodes - */ -int32 CFE_SB_WriteRoutingInfoCmd(const CFE_SB_WriteRoutingInfoCmd_t *data) -{ - char LocalFilename[OS_MAX_PATH_LEN]; - int32 Stat; - - CFE_SB_MessageStringGet(LocalFilename, data->Payload.Filename, CFE_PLATFORM_SB_DEFAULT_ROUTING_FILENAME, - sizeof(LocalFilename), sizeof(data->Payload.Filename)); - - Stat = CFE_SB_WriteRtgInfo(LocalFilename); - CFE_SB_IncrCmdCtr(Stat); - - return CFE_SUCCESS; -} - - -/****************************************************************************** - * \brief SB internal function to handle processing of 'Write Pipe Info' Cmd - * - * \param[in] data Pointer to command structure - * - * \return Execution status, see \ref CFEReturnCodes - */ -int32 CFE_SB_WritePipeInfoCmd(const CFE_SB_WritePipeInfoCmd_t *data) -{ - char LocalFilename[OS_MAX_PATH_LEN]; - int32 Stat; - - CFE_SB_MessageStringGet(LocalFilename, data->Payload.Filename, CFE_PLATFORM_SB_DEFAULT_PIPE_FILENAME, - sizeof(LocalFilename), sizeof(data->Payload.Filename)); - - Stat = CFE_SB_WritePipeInfo(LocalFilename); - CFE_SB_IncrCmdCtr(Stat); - - return CFE_SUCCESS; -} - - -/****************************************************************************** - * \brief SB internal function to handle processing of 'Write Map Info' Cmd - * - * \param[in] data Pointer to command structure - * - * \return Execution status, see \ref CFEReturnCodes - */ -int32 CFE_SB_WriteMapInfoCmd(const CFE_SB_WriteMapInfoCmd_t *data) -{ - char LocalFilename[OS_MAX_PATH_LEN]; - int32 Stat; - - CFE_SB_MessageStringGet(LocalFilename, data->Payload.Filename, CFE_PLATFORM_SB_DEFAULT_MAP_FILENAME, - sizeof(LocalFilename), sizeof(data->Payload.Filename)); - - Stat = CFE_SB_WriteMapInfo(LocalFilename); - - CFE_SB_IncrCmdCtr(Stat); - - return CFE_SUCCESS; -} - /****************************************************************************** * Local callback helper for writing routing info to a file */ -void CFE_SB_WriteRouteToFile(CFE_SBR_RouteId_t RouteId, void *ArgPtr) +void CFE_SB_CollectRouteInfo(CFE_SBR_RouteId_t RouteId, void *ArgPtr) { - struct RouteInfo - { - CFE_SB_PipeId_t PipeId; - uint8 Active; - uint16 DestCnt; - }; - - CFE_SB_FileWriteCallback_t *args; - CFE_SB_DestinationD_t *destptr; - CFE_SB_PipeD_t *pipedptr; - int32 status; - CFE_SB_RoutingFileEntry_t entry; - struct RouteInfo RouteInfo[CFE_PLATFORM_SB_MAX_DEST_PER_PKT]; - struct RouteInfo *RouteInfoPtr; - uint32 NumDest; + CFE_SB_DestinationD_t *DestPtr; + CFE_SB_PipeD_t *PipeDscPtr; + CFE_SB_MsgId_t RouteMsgId; + CFE_SB_BackgroundRouteInfoBuffer_t *RouteBufferPtr; + CFE_SB_RoutingFileEntry_t *FileEntryPtr; + CFE_ES_AppId_t DestAppId[CFE_PLATFORM_SB_MAX_DEST_PER_PKT]; + uint32 i; /* Cast arguments for local use */ - args = (CFE_SB_FileWriteCallback_t *)ArgPtr; - - NumDest = 0; + RouteBufferPtr = (CFE_SB_BackgroundRouteInfoBuffer_t *)ArgPtr; + /* Extract data from runtime info, write into the temporary buffer */ /* Data must be locked to snapshot the route info */ CFE_SB_LockSharedData(__FILE__, __LINE__); - destptr = CFE_SBR_GetDestListHeadPtr(RouteId); - entry.MsgId = CFE_SBR_GetMsgId(RouteId); - RouteInfoPtr = RouteInfo; + RouteMsgId = CFE_SBR_GetMsgId(RouteId); + RouteBufferPtr->NumDestinations = 0; - while((destptr != NULL) && NumDest < CFE_PLATFORM_SB_MAX_DEST_PER_PKT) + /* If this is a valid route, get the destinations */ + if (CFE_SB_IsValidMsgId(RouteMsgId)) { - pipedptr = CFE_SB_LocatePipeDescByID(destptr->PipeId); + DestPtr = CFE_SBR_GetDestListHeadPtr(RouteId); - /* If invalid id, continue on to next entry */ - if (CFE_SB_PipeDescIsMatch(pipedptr,destptr->PipeId)) + /* copy relevant data from the destination list into the temp buffer */ + while(DestPtr != NULL && RouteBufferPtr->NumDestinations < CFE_PLATFORM_SB_MAX_DEST_PER_PKT) { - RouteInfoPtr->PipeId = destptr->PipeId; - RouteInfoPtr->Active = destptr->Active; - RouteInfoPtr->DestCnt = destptr->DestCnt; - ++RouteInfoPtr; - ++NumDest; - } + PipeDscPtr = CFE_SB_LocatePipeDescByID(DestPtr->PipeId); - destptr = destptr->Next; + /* If invalid id, continue on to next entry */ + if (CFE_SB_PipeDescIsMatch(PipeDscPtr,DestPtr->PipeId)) + { + FileEntryPtr = &RouteBufferPtr->DestEntries[RouteBufferPtr->NumDestinations]; + + /* clear all fields in the temp buffer before re-use */ + memset(FileEntryPtr, 0, sizeof(*FileEntryPtr)); + + FileEntryPtr->PipeId = DestPtr->PipeId; + FileEntryPtr->State = DestPtr->Active; + FileEntryPtr->MsgCnt = DestPtr->DestCnt; + + /* Stash the Pipe Owner AppId - App Name is looked up later (comes from ES) */ + DestAppId[RouteBufferPtr->NumDestinations] = PipeDscPtr->AppId; + + ++RouteBufferPtr->NumDestinations; + } + + DestPtr = DestPtr->Next; + } } CFE_SB_UnlockSharedData(__FILE__, __LINE__); - RouteInfoPtr = RouteInfo; - while (NumDest > 0) + /* Go through the temp buffer and fill in the remaining info for each dest */ + FileEntryPtr = RouteBufferPtr->DestEntries; + for(i = 0; i < RouteBufferPtr->NumDestinations; ++i) { - entry.PipeId = RouteInfoPtr->PipeId; - entry.State = RouteInfoPtr->Active; - entry.MsgCnt = RouteInfoPtr->DestCnt; - - entry.AppName[0] = 0; + /* All dest entries refer to the same MsgId (based on the route) */ + FileEntryPtr->MsgId = RouteMsgId; /* - * NOTE: as long as CFE_ES_GetAppName() returns success, then it - * guarantees null termination of the output. Return code is not - * checked here (bad) but in case of error it does not seem to touch - * the buffer, therefore the initialization above will protect for now + * NOTE: as long as CFE_ES_GetAppName() is given a nonzero-length + * output buffer, it guarantees null termination of the output, even + * if the AppID is invalid - in which case it returns an empty string. */ - CFE_ES_GetAppName(entry.AppName, pipedptr->AppId, sizeof(entry.AppName)); - CFE_SB_GetPipeName(entry.PipeName, sizeof(entry.PipeName), entry.PipeId); - - status = OS_write (args->Fd, &entry, sizeof(CFE_SB_RoutingFileEntry_t)); - if(status != sizeof(CFE_SB_RoutingFileEntry_t)) - { - CFE_SB_FileWriteByteCntErr(args->Filename, sizeof(CFE_SB_RoutingFileEntry_t), status); - OS_close(args->Fd); - args->Status = CFE_SB_FILE_IO_ERR; - } + CFE_ES_GetAppName(FileEntryPtr->AppName, DestAppId[i], sizeof(FileEntryPtr->AppName)); + CFE_SB_GetPipeName(FileEntryPtr->PipeName, sizeof(FileEntryPtr->PipeName), FileEntryPtr->PipeId); - args->FileSize += status; - args->EntryCount++; - - ++RouteInfoPtr; - --NumDest; + ++FileEntryPtr; } } @@ -1033,169 +958,257 @@ int32 CFE_SB_SendSubscriptionReport(CFE_SB_MsgId_t MsgId, CFE_SB_PipeId_t PipeId return Status; } +bool CFE_SB_WriteRouteInfoDataGetter(void *Meta, uint32 RecordNum, void **Buffer, size_t *BufSize) +{ + CFE_SB_BackgroundFileStateInfo_t *BgFilePtr; + CFE_SBR_Throttle_t Throttle; + + /* Cast arguments for local use */ + BgFilePtr = (CFE_SB_BackgroundFileStateInfo_t *)Meta; + + Throttle.StartIndex = RecordNum; + Throttle.MaxLoop = 1; + Throttle.NextIndex = 0; + + /* Reset NumDestinations to 0, just in case the CFE_SBR_ForEachRouteId() is a no-op */ + BgFilePtr->Buffer.RouteInfo.NumDestinations = 0; + + /* Collect info on the next route (limited to one per cycle via throttle) */ + CFE_SBR_ForEachRouteId(CFE_SB_CollectRouteInfo, &BgFilePtr->Buffer.RouteInfo, &Throttle); + + /* Pass the output of CFE_SB_CollectRouteInfo() back to be written */ + *Buffer = &BgFilePtr->Buffer.RouteInfo.DestEntries; + *BufSize = sizeof(CFE_SB_RoutingFileEntry_t) * BgFilePtr->Buffer.RouteInfo.NumDestinations; + + /* Check for EOF (last entry) - NextIndex is nonzero if more records left, zero at the end of the route table */ + return (Throttle.NextIndex == 0); +} + +void CFE_SB_BackgroundFileEventHandler(void *Meta, CFE_FS_FileWriteEvent_t Event, int32 Status, uint32 RecordNum, size_t BlockSize, size_t Position) +{ + CFE_SB_BackgroundFileStateInfo_t *BgFilePtr; + + BgFilePtr = (CFE_SB_BackgroundFileStateInfo_t *)Meta; + + /* + * Note that this runs in the context of ES background task (file writer background job) + * It does NOT run in the context of the CFE_TBL app task. + * + * Events should use CFE_EVS_SendEventWithAppID() rather than CFE_EVS_SendEvent() + * to get proper association with TBL task. + */ + switch(Event) + { + case CFE_FS_FileWriteEvent_COMPLETE: + CFE_EVS_SendEventWithAppID(CFE_SB_SND_RTG_EID,CFE_EVS_EventType_DEBUG, + CFE_SB_Global.AppId, + "%s written:Size=%d,Entries=%d", + BgFilePtr->FileWrite.FileName, (int)Position, (int)RecordNum); + break; + + case CFE_FS_FileWriteEvent_HEADER_WRITE_ERROR: + case CFE_FS_FileWriteEvent_RECORD_WRITE_ERROR: + CFE_EVS_SendEventWithAppID(CFE_SB_FILEWRITE_ERR_EID,CFE_EVS_EventType_ERROR, + CFE_SB_Global.AppId, + "File write,byte cnt err,file %s,request=%d,actual=%d", + BgFilePtr->FileWrite.FileName, (int)BlockSize, (int)Status); + break; + + case CFE_FS_FileWriteEvent_CREATE_ERROR: + CFE_EVS_SendEventWithAppID(CFE_SB_SND_RTG_ERR1_EID, CFE_EVS_EventType_ERROR, + CFE_SB_Global.AppId, + "Error creating file %s, stat=0x%x", + BgFilePtr->FileWrite.FileName, (int)Status); + break; + + default: + /* unhandled event - ignore */ + break; + } +} /****************************************************************************** - * \brief SB internal function to write the routing information to a file + * \brief SB internal function to handle processing of 'Write Routing Info' Cmd * - * \param[in] Filename Pointer the file name to write + * \param[in] data Pointer to command structure * * \return Execution status, see \ref CFEReturnCodes - * \retval #CFE_SUCCESS \copybrief CFE_SUCCESS - * \retval #CFE_SB_FILE_IO_ERR \copybrief CFE_SB_FILE_IO_ERR */ -int32 CFE_SB_WriteRtgInfo(const char *Filename) +int32 CFE_SB_WriteRoutingInfoCmd(const CFE_SB_WriteRoutingInfoCmd_t *data) { - CFE_SB_FileWriteCallback_t args = {0}; + const CFE_SB_WriteFileInfoCmd_Payload_t *CmdPtr; + CFE_SB_BackgroundFileStateInfo_t *StatePtr; int32 Status; - CFE_FS_Header_t FileHdr; - Status = OS_OpenCreate(&args.Fd, Filename, OS_FILE_FLAG_CREATE | OS_FILE_FLAG_TRUNCATE, OS_WRITE_ONLY); - if(Status < OS_SUCCESS) - { - CFE_EVS_SendEvent(CFE_SB_SND_RTG_ERR1_EID, CFE_EVS_EventType_ERROR, - "Error creating file %s, stat=0x%x", - Filename, (unsigned int)Status); - return CFE_SB_FILE_IO_ERR; - } - - /* clear out the cfe file header fields, then populate description and subtype */ - CFE_FS_InitHeader(&FileHdr, "SB Routing Information", CFE_FS_SubType_SB_ROUTEDATA); + StatePtr = &CFE_SB_Global.BackgroundFile; + CmdPtr = &data->Payload; - Status = CFE_FS_WriteHeader(args.Fd, &FileHdr); - if(Status != sizeof(CFE_FS_Header_t)) + /* If a routing info dump was already pending, do not overwrite the current request */ + if (!CFE_FS_BackgroundFileDumpIsPending(&StatePtr->FileWrite)) { - CFE_SB_FileWriteByteCntErr(Filename,sizeof(CFE_FS_Header_t),Status); - OS_close(args.Fd); - return CFE_SB_FILE_IO_ERR; - } + /* Reset the entire state object (just for good measure, ensure no stale data) */ + memset(StatePtr, 0, sizeof(*StatePtr)); - /* Initialize the reset of the nonzero callback argument elements */ - args.FileSize = Status; - args.Filename = Filename; + /* Copy the commanded filename into local buffer to ensure size limitation and to allow for modification */ + CFE_SB_MessageStringGet(StatePtr->FileWrite.FileName, CmdPtr->Filename, CFE_PLATFORM_SB_DEFAULT_ROUTING_FILENAME, + sizeof(StatePtr->FileWrite.FileName), sizeof(CmdPtr->Filename)); - /* Write route info to file */ - CFE_SBR_ForEachRouteId(CFE_SB_WriteRouteToFile, &args, NULL); + /* + * Fill out the remainder of meta data. + * This data is currently the same for every request + */ + StatePtr->FileWrite.FileSubType = CFE_FS_SubType_SB_ROUTEDATA; + snprintf(StatePtr->FileWrite.Description, sizeof(StatePtr->FileWrite.Description), "SB Routing Information"); - if (args.Status != 0) - { - return args.Status; + StatePtr->FileWrite.GetData = CFE_SB_WriteRouteInfoDataGetter; + StatePtr->FileWrite.OnEvent = CFE_SB_BackgroundFileEventHandler; + + Status = CFE_FS_BackgroundFileDumpRequest(&StatePtr->FileWrite); } else { - OS_close(args.Fd); - CFE_EVS_SendEvent(CFE_SB_SND_RTG_EID,CFE_EVS_EventType_DEBUG, - "%s written:Size=%d,Entries=%d", - Filename, (int)args.FileSize, (int)args.EntryCount); - return CFE_SUCCESS; + Status = CFE_STATUS_REQUEST_ALREADY_PENDING; } - -} + + if (Status != CFE_SUCCESS) + { + /* generate the same event as is generated when unable to create the file (same thing, really) */ + CFE_SB_BackgroundFileEventHandler(StatePtr, CFE_FS_FileWriteEvent_CREATE_ERROR, Status, 0, 0, 0); + } + + CFE_SB_IncrCmdCtr(Status); + + return CFE_SUCCESS; +}/* end CFE_SB_WriteRoutingInfoCmd */ -/****************************************************************************** - * \brief SB internal function to write the Pipe table to a file - * - * \param[in] Filename Pointer the file name to write - * - * \return Execution status, see \ref CFEReturnCodes - * \retval #CFE_SUCCESS \copybrief CFE_SUCCESS - * \retval #CFE_SB_FILE_IO_ERR \copybrief CFE_SB_FILE_IO_ERR - */ -int32 CFE_SB_WritePipeInfo(const char *Filename) +bool CFE_SB_WritePipeInfoDataGetter(void *Meta, uint32 RecordNum, void **Buffer, size_t *BufSize) { - uint16 i; - osal_id_t fd; - int32 Status; - uint32 FileSize = 0; - uint32 EntryCount = 0; - CFE_FS_Header_t FileHdr; + CFE_SB_BackgroundFileStateInfo_t *BgFilePtr; + CFE_SB_PipeInfoEntry_t *PipeBufferPtr; CFE_SB_PipeD_t *PipeDscPtr; - CFE_SB_PipeInfoEntry_t FileEntry; osal_id_t SysQueueId; + bool PipeIsValid; - Status = OS_OpenCreate(&fd, Filename, OS_FILE_FLAG_CREATE | OS_FILE_FLAG_TRUNCATE, OS_WRITE_ONLY); + BgFilePtr = (CFE_SB_BackgroundFileStateInfo_t *)Meta; + PipeDscPtr = NULL; + PipeIsValid = false; - if(Status < OS_SUCCESS){ - CFE_EVS_SendEvent(CFE_SB_SND_RTG_ERR1_EID,CFE_EVS_EventType_ERROR, - "Error creating file %s, stat=0x%x", - Filename,(unsigned int)Status); - return CFE_SB_FILE_IO_ERR; - }/* end if */ + PipeBufferPtr = &BgFilePtr->Buffer.PipeInfo; - /* clear out the cfe file header fields, then populate description and subtype */ - CFE_FS_InitHeader(&FileHdr, "SB Pipe Information", CFE_FS_SubType_SB_PIPEDATA); - - Status = CFE_FS_WriteHeader(fd, &FileHdr); - if(Status != sizeof(CFE_FS_Header_t)){ - CFE_SB_FileWriteByteCntErr(Filename,sizeof(CFE_FS_Header_t),Status); - OS_close(fd); - return CFE_SB_FILE_IO_ERR; - }/* end if */ + if (RecordNum < CFE_PLATFORM_SB_MAX_PIPES) + { + PipeDscPtr = &CFE_SB_Global.PipeTbl[RecordNum]; - FileSize = Status; + CFE_SB_LockSharedData(__FILE__,__LINE__); - /* loop through the pipe table */ - CFE_SB_LockSharedData(__FILE__,__LINE__); - PipeDscPtr = CFE_SB_Global.PipeTbl; + PipeIsValid = CFE_SB_PipeDescIsUsed(PipeDscPtr); - for (i=0;iAppId; - FileEntry.MaxQueueDepth = PipeDscPtr->QueueDepth; - FileEntry.CurrentQueueDepth = PipeDscPtr->CurrentDepth; - FileEntry.PeakQueueDepth = PipeDscPtr->PeakDepth; - FileEntry.SendErrors = PipeDscPtr->SendErrors; - FileEntry.Opts = PipeDscPtr->Opts; + PipeBufferPtr->PipeId = CFE_SB_PipeDescGetID(PipeDscPtr); + PipeBufferPtr->AppId = PipeDscPtr->AppId; + PipeBufferPtr->Opts = PipeDscPtr->Opts; + + /* copy stats info */ + PipeBufferPtr->SendErrors = PipeDscPtr->SendErrors; + PipeBufferPtr->MaxQueueDepth = PipeDscPtr->MaxQueueDepth; + PipeBufferPtr->CurrentQueueDepth = PipeDscPtr->CurrentQueueDepth; + PipeBufferPtr->PeakQueueDepth = PipeDscPtr->PeakQueueDepth; + SysQueueId = PipeDscPtr->SysQueueId; + } + else + { + SysQueueId = OS_OBJECT_ID_UNDEFINED; + } + + CFE_SB_UnlockSharedData(__FILE__,__LINE__); + } - CFE_SB_UnlockSharedData(__FILE__,__LINE__); + if (PipeIsValid) + { + /* + * Gather data from other subsystems while unlocked. + * This might fail if the pipe is deleted simultaneously while this runs, but in + * the unlikely event that happens, the name data will simply be blank as the ID(s) + * will not validate. + */ + OS_GetResourceName(SysQueueId, PipeBufferPtr->PipeName, sizeof(PipeBufferPtr->PipeName)); + CFE_ES_GetAppName(PipeBufferPtr->AppName, PipeBufferPtr->AppId, sizeof(PipeBufferPtr->AppName)); - /* - * Gather data from other subsystems while unlocked. - * This might fail if the pipe is deleted simultaneously while this runs, but in - * the unlikely event that happens, the name data will simply be blank as the ID(s) - * will not validate. - */ - OS_GetResourceName(SysQueueId, FileEntry.PipeName, sizeof(FileEntry.PipeName)); - CFE_ES_GetAppName(FileEntry.AppName, FileEntry.AppId, sizeof(FileEntry.AppName)); + *Buffer = PipeBufferPtr; + *BufSize = sizeof(*PipeBufferPtr); + } + else + { + *Buffer = NULL; + *BufSize = 0; + } + + /* Check for EOF (last entry) */ + return (RecordNum >= (CFE_PLATFORM_SB_MAX_PIPES-1)); +} - Status = OS_write (fd, &FileEntry, sizeof(FileEntry)); - if (Status != sizeof(FileEntry)) - { - CFE_SB_FileWriteByteCntErr(Filename,sizeof(FileEntry),Status); - OS_close(fd); - return CFE_SB_FILE_IO_ERR; - }/* end if */ - FileSize += Status; - EntryCount ++; +/****************************************************************************** + * \brief SB internal function to handle processing of 'Write Pipe Info' Cmd + * + * \param[in] data Pointer to command structure + * + * \return Execution status, see \ref CFEReturnCodes + */ +int32 CFE_SB_WritePipeInfoCmd(const CFE_SB_WritePipeInfoCmd_t *data) +{ + const CFE_SB_WriteFileInfoCmd_Payload_t *CmdPtr; + CFE_SB_BackgroundFileStateInfo_t *StatePtr; + int32 Status; - CFE_SB_LockSharedData(__FILE__,__LINE__); + StatePtr = &CFE_SB_Global.BackgroundFile; + CmdPtr = &data->Payload; - }/* end if */ + /* If a pipe info dump was already pending, do not overwrite the current request */ + if (!CFE_FS_BackgroundFileDumpIsPending(&StatePtr->FileWrite)) + { + /* Reset the entire state object (just for good measure, ensure no stale data) */ + memset(StatePtr, 0, sizeof(*StatePtr)); - ++PipeDscPtr; + /* Copy the commanded filename into local buffer to ensure size limitation and to allow for modification */ + CFE_SB_MessageStringGet(StatePtr->FileWrite.FileName, CmdPtr->Filename, CFE_PLATFORM_SB_DEFAULT_PIPE_FILENAME, + sizeof(StatePtr->FileWrite.FileName), sizeof(CmdPtr->Filename)); - }/* end for */ + /* + * Fill out the remainder of meta data. + * This data is currently the same for every request + */ + StatePtr->FileWrite.FileSubType = CFE_FS_SubType_SB_PIPEDATA; + snprintf(StatePtr->FileWrite.Description, sizeof(StatePtr->FileWrite.Description), "SB Pipe Information"); - CFE_SB_UnlockSharedData(__FILE__,__LINE__); + StatePtr->FileWrite.GetData = CFE_SB_WritePipeInfoDataGetter; + StatePtr->FileWrite.OnEvent = CFE_SB_BackgroundFileEventHandler; - OS_close(fd); + Status = CFE_FS_BackgroundFileDumpRequest(&StatePtr->FileWrite); + } + else + { + Status = CFE_STATUS_REQUEST_ALREADY_PENDING; + } + + if (Status != CFE_SUCCESS) + { + /* generate the same event as is generated when unable to create the file (same thing, really) */ + CFE_SB_BackgroundFileEventHandler(StatePtr, CFE_FS_FileWriteEvent_CREATE_ERROR, Status, 0, 0, 0); + } - CFE_EVS_SendEvent(CFE_SB_SND_RTG_EID,CFE_EVS_EventType_DEBUG, - "%s written:Size=%d,Entries=%d", - Filename,(int)FileSize,(int)EntryCount); + CFE_SB_IncrCmdCtr(Status); return CFE_SUCCESS; @@ -1204,91 +1217,112 @@ int32 CFE_SB_WritePipeInfo(const char *Filename) /****************************************************************************** * Local callback helper for writing map info to a file */ -void CFE_SB_WriteMapToFile(CFE_SBR_RouteId_t RouteId, void *ArgPtr) +void CFE_SB_CollectMsgMapInfo(CFE_SBR_RouteId_t RouteId, void *ArgPtr) { - CFE_SB_FileWriteCallback_t *args; - int32 status; - CFE_SB_MsgMapFileEntry_t entry; + CFE_SB_MsgMapFileEntry_t *BufferPtr; /* Cast arguments for local use */ - args = (CFE_SB_FileWriteCallback_t *)ArgPtr; + BufferPtr = (CFE_SB_MsgMapFileEntry_t *)ArgPtr; - if(args->Status != CFE_SB_FILE_IO_ERR) - { - CFE_SB_LockSharedData(__FILE__,__LINE__); - entry.MsgId = CFE_SBR_GetMsgId(RouteId); - entry.Index = CFE_SBR_RouteIdToValue(RouteId); - CFE_SB_UnlockSharedData(__FILE__,__LINE__); + /* Extract data from runtime info, write into the temporary buffer */ + /* Data must be locked to snapshot the route info */ + CFE_SB_LockSharedData(__FILE__, __LINE__); - status = OS_write (args->Fd, &entry, sizeof(CFE_SB_MsgMapFileEntry_t)); - if(status != sizeof(CFE_SB_MsgMapFileEntry_t)) - { - CFE_SB_FileWriteByteCntErr(args->Filename, sizeof(CFE_SB_MsgMapFileEntry_t), status); - OS_close(args->Fd); - args->Status = CFE_SB_FILE_IO_ERR; - } + BufferPtr->MsgId = CFE_SBR_GetMsgId(RouteId); + BufferPtr->Index = CFE_SBR_RouteIdToValue(RouteId); + + CFE_SB_UnlockSharedData(__FILE__,__LINE__); +} + + +bool CFE_SB_WriteMsgMapInfoDataGetter(void *Meta, uint32 RecordNum, void **Buffer, size_t *BufSize) +{ + CFE_SB_BackgroundFileStateInfo_t *BgFilePtr; + CFE_SBR_Throttle_t Throttle; + + /* Cast arguments for local use */ + BgFilePtr = (CFE_SB_BackgroundFileStateInfo_t *)Meta; + + Throttle.StartIndex = RecordNum; + Throttle.MaxLoop = 1; + Throttle.NextIndex = 0; + + /* Set the MsgId intially - will be overwritten with real info in CFE_SB_CollectMsgMapInfo */ + BgFilePtr->Buffer.MsgMapInfo.MsgId = CFE_SB_INVALID_MSG_ID; - args->FileSize += status; - args->EntryCount++; + /* Collect info on the next route (limited to one per cycle via throttle) */ + CFE_SBR_ForEachRouteId(CFE_SB_CollectMsgMapInfo, &BgFilePtr->Buffer.MsgMapInfo, &Throttle); + + /* If Map was valid, pass the output of CFE_SB_CollectMsgMapInfo() back to be written */ + if (CFE_SB_IsValidMsgId(BgFilePtr->Buffer.MsgMapInfo.MsgId)) + { + *Buffer = &BgFilePtr->Buffer.MsgMapInfo; + *BufSize = sizeof(CFE_SB_MsgMapFileEntry_t); } + else + { + *Buffer = NULL; + *BufSize = 0; + } + + /* Check for EOF (last entry) - NextIndex is nonzero if more records left, zero at the end of the route table */ + return (Throttle.NextIndex == 0); } + /****************************************************************************** - * \brief SB internal function to write the Message Map to a file + * \brief SB internal function to handle processing of 'Write Map Info' Cmd * - * \param[in] Filename Pointer the file name to write + * \param[in] data Pointer to command structure * * \return Execution status, see \ref CFEReturnCodes - * \retval #CFE_SUCCESS \copybrief CFE_SUCCESS - * \retval #CFE_SB_FILE_IO_ERR \copybrief CFE_SB_FILE_IO_ERR */ -int32 CFE_SB_WriteMapInfo(const char *Filename) +int32 CFE_SB_WriteMapInfoCmd(const CFE_SB_WriteMapInfoCmd_t *data) { - CFE_SB_FileWriteCallback_t args = {0}; - int32 Status; - CFE_FS_Header_t FileHdr; + const CFE_SB_WriteFileInfoCmd_Payload_t *CmdPtr; + CFE_SB_BackgroundFileStateInfo_t *StatePtr; + int32 Status; - Status = OS_OpenCreate(&args.Fd, Filename, OS_FILE_FLAG_CREATE | OS_FILE_FLAG_TRUNCATE, OS_WRITE_ONLY); + StatePtr = &CFE_SB_Global.BackgroundFile; + CmdPtr = &data->Payload; - if (Status < OS_SUCCESS) + /* If a pipe info dump was already pending, do not overwrite the current request */ + if (!CFE_FS_BackgroundFileDumpIsPending(&StatePtr->FileWrite)) { - CFE_EVS_SendEvent(CFE_SB_SND_RTG_ERR1_EID, CFE_EVS_EventType_ERROR, - "Error creating file %s, stat=0x%x", - Filename, (unsigned int)Status); - return CFE_SB_FILE_IO_ERR; - } + /* Reset the entire state object (just for good measure, ensure no stale data) */ + memset(StatePtr, 0, sizeof(*StatePtr)); - /* clear out the cfe file header fields, then populate description and subtype */ - CFE_FS_InitHeader(&FileHdr, "SB Message Map Information", CFE_FS_SubType_SB_MAPDATA); + /* Copy the commanded filename into local buffer to ensure size limitation and to allow for modification */ + CFE_SB_MessageStringGet(StatePtr->FileWrite.FileName, CmdPtr->Filename, CFE_PLATFORM_SB_DEFAULT_MAP_FILENAME, + sizeof(StatePtr->FileWrite.FileName), sizeof(CmdPtr->Filename)); - Status = CFE_FS_WriteHeader(args.Fd, &FileHdr); - if(Status != sizeof(CFE_FS_Header_t)) - { - CFE_SB_FileWriteByteCntErr(Filename, sizeof(CFE_FS_Header_t), Status); - OS_close(args.Fd); - return CFE_SB_FILE_IO_ERR; - } - - /* Initialize the reset of the nonzero callback argument elements */ - args.FileSize = Status; - args.Filename = Filename; + /* + * Fill out the remainder of meta data. + * This data is currently the same for every request + */ + StatePtr->FileWrite.FileSubType = CFE_FS_SubType_SB_MAPDATA; + snprintf(StatePtr->FileWrite.Description, sizeof(StatePtr->FileWrite.Description), "SB Map Information"); - /* Write route info to file */ - CFE_SBR_ForEachRouteId(CFE_SB_WriteMapToFile, &args, NULL); + StatePtr->FileWrite.GetData = CFE_SB_WriteMsgMapInfoDataGetter; + StatePtr->FileWrite.OnEvent = CFE_SB_BackgroundFileEventHandler; - if (args.Status != 0) - { - return args.Status; + Status = CFE_FS_BackgroundFileDumpRequest(&StatePtr->FileWrite); } else { - OS_close(args.Fd); - CFE_EVS_SendEvent(CFE_SB_SND_RTG_EID, CFE_EVS_EventType_DEBUG, - "%s written:Size=%d,Entries=%d", - Filename, (int)args.FileSize, (int)args.EntryCount); - return CFE_SUCCESS; + Status = CFE_STATUS_REQUEST_ALREADY_PENDING; } -} + + if (Status != CFE_SUCCESS) + { + /* generate the same event as is generated when unable to create the file (same thing, really) */ + CFE_SB_BackgroundFileEventHandler(StatePtr, CFE_FS_FileWriteEvent_CREATE_ERROR, Status, 0, 0, 0); + } + + CFE_SB_IncrCmdCtr(Status); + + return CFE_SUCCESS; +}/* end CFE_SB_WriteMapInfoCmd */ /****************************************************************************** * Local callback helper for sending route subscriptions @@ -1410,29 +1444,6 @@ void CFE_SB_IncrCmdCtr(int32 status){ }/* end CFE_SB_IncrCmdCtr */ - - -/****************************************************************************** -** Function: CFE_SB_FileWriteByteCntErr() -** -** Purpose: -** SB internal function to report a file write error -** -** Arguments: -** -** -** Return: -** None -*/ -void CFE_SB_FileWriteByteCntErr(const char *Filename,uint32 Requested,uint32 Actual){ - - CFE_EVS_SendEvent(CFE_SB_FILEWRITE_ERR_EID,CFE_EVS_EventType_ERROR, - "File write,byte cnt err,file %s,request=%d,actual=%d", - Filename,(int)Requested,(int)Actual); - -}/* end CFE_SB_FileWriteByteCntErr() */ - - /****************************************************************************** ** Function: CFE_SB_SetSubscriptionReporting() ** diff --git a/fsw/cfe-core/src/tbl/cfe_tbl_internal.h b/fsw/cfe-core/src/tbl/cfe_tbl_internal.h index 21cbfaee2..7fbdb25c3 100644 --- a/fsw/cfe-core/src/tbl/cfe_tbl_internal.h +++ b/fsw/cfe-core/src/tbl/cfe_tbl_internal.h @@ -578,6 +578,15 @@ int32 CFE_TBL_SendNotificationMsg(CFE_TBL_RegistryRec_t *RegRecPtr); ******************************************************************************/ extern void CFE_TBL_ByteSwapUint32(uint32 *Uint32ToSwapPtr); +/* + * Internal helper functions for Table Registry dump + * + * These callbacks are used with the FS background write request API + * and are implemented per that specification. + */ +void CFE_TBL_DumpRegistryEventHandler(void *Meta, CFE_FS_FileWriteEvent_t Event, int32 Status, uint32 RecordNum, size_t BlockSize, size_t Position); +bool CFE_TBL_DumpRegistryGetter(void *Meta, uint32 RecordNum, void **Buffer, size_t *BufSize); + /* ** Globals specific to the TBL module diff --git a/fsw/cfe-core/src/tbl/cfe_tbl_task.h b/fsw/cfe-core/src/tbl/cfe_tbl_task.h index 6ad6611f0..e17ddf804 100644 --- a/fsw/cfe-core/src/tbl/cfe_tbl_task.h +++ b/fsw/cfe-core/src/tbl/cfe_tbl_task.h @@ -271,6 +271,19 @@ typedef struct bool CriticalTable; /**< \brief Identifies whether table is Critical or Not */ } CFE_TBL_RegDumpRec_t; +/*******************************************************************************/ +/** \brief Table Registry Dump background state information +** +** State info for background table registry dump process and one temporary data record. +*/ +typedef struct +{ + CFE_FS_FileWriteMetaData_t FileWrite; /**< FS state data - must be first */ + + bool FileExisted; /**< Set true if the file already existed at the time of request */ + CFE_TBL_RegDumpRec_t DumpRecord; /**< Current record buffer (reused each entry) */ +} CFE_TBL_RegDumpStateInfo_t; + /*******************************************************************************/ /** \brief Table Task Global Data ** @@ -336,6 +349,11 @@ typedef struct CFE_TBL_ValidationResult_t ValidationResults[CFE_PLATFORM_TBL_MAX_NUM_VALIDATIONS]; /**< \brief Array of Table Validation Requests */ CFE_TBL_DumpControl_t DumpControlBlocks[CFE_PLATFORM_TBL_MAX_SIMULTANEOUS_LOADS]; /**< \brief Array of Dump-Only Dump Control Blocks */ + /* + * Registry dump state info (background job) + */ + CFE_TBL_RegDumpStateInfo_t RegDumpState; + } CFE_TBL_Global_t; diff --git a/fsw/cfe-core/src/tbl/cfe_tbl_task_cmds.c b/fsw/cfe-core/src/tbl/cfe_tbl_task_cmds.c index 21952d79e..5ac2b95d0 100644 --- a/fsw/cfe-core/src/tbl/cfe_tbl_task_cmds.c +++ b/fsw/cfe-core/src/tbl/cfe_tbl_task_cmds.c @@ -1114,183 +1114,237 @@ int32 CFE_TBL_ActivateCmd(const CFE_TBL_ActivateCmd_t *data) /******************************************************************* ** -** CFE_TBL_DumpRegistryCmd() -- Process Dump Table Registry to file Command Message +** CFE_TBL_DumpRegistryGetter() -- Helper function for dumping table registry ** -** NOTE: For complete prolog information, see 'cfe_tbl_task_cmds.h' ********************************************************************/ -int32 CFE_TBL_DumpRegistryCmd(const CFE_TBL_DumpRegistryCmd_t *data) +bool CFE_TBL_DumpRegistryGetter(void *Meta, uint32 RecordNum, void **Buffer, size_t *BufSize) { - CFE_TBL_CmdProcRet_t ReturnCode = CFE_TBL_INC_ERR_CTR; /* Assume failure */ - bool FileExistedPrev = false; - CFE_FS_Header_t StdFileHeader; - osal_id_t FileDescriptor; - int32 Status; - int16 RegIndex=0; - const CFE_TBL_DumpRegistryCmd_Payload_t *CmdPtr = &data->Payload; - char DumpFilename[OS_MAX_PATH_LEN]; + CFE_TBL_RegDumpStateInfo_t *StatePtr = (CFE_TBL_RegDumpStateInfo_t *)Meta; CFE_TBL_RegistryRec_t *RegRecPtr; CFE_TBL_Handle_t HandleIterator; - CFE_TBL_RegDumpRec_t DumpRecord; - int32 FileSize=0; - int32 NumEntries=0; - - /* Copy the commanded filename into local buffer to ensure size limitation and to allow for modification */ - CFE_SB_MessageStringGet(DumpFilename, (char *)CmdPtr->DumpFilename, CFE_PLATFORM_TBL_DEFAULT_REG_DUMP_FILE, - sizeof(DumpFilename), sizeof(CmdPtr->DumpFilename)); + CFE_ES_AppId_t OwnerAppId; + bool IsValidEntry; - /* Check to see if the dump file already exists */ - Status = OS_OpenCreate(&FileDescriptor, DumpFilename, OS_FILE_FLAG_NONE, OS_READ_ONLY); + IsValidEntry = false; + OwnerAppId = CFE_ES_APPID_UNDEFINED; - if (Status >= 0) + if (RecordNum < CFE_PLATFORM_TBL_MAX_NUM_TABLES) { - FileExistedPrev = true; - OS_close(FileDescriptor); - } + /* Make a pointer to simplify code look and to remove redundant indexing into registry */ + RegRecPtr = &CFE_TBL_Global.Registry[RecordNum]; - /* Create a new dump file, overwriting anything that may have existed previously */ - Status = OS_OpenCreate(&FileDescriptor, DumpFilename, - OS_FILE_FLAG_CREATE | OS_FILE_FLAG_TRUNCATE, OS_WRITE_ONLY); + /* should lock registry while copying out data to ensure its in consistent state */ + CFE_TBL_LockRegistry(); - if (Status >= OS_SUCCESS) - { - /* Initialize the standard cFE File Header for the Dump File */ - CFE_FS_InitHeader(&StdFileHeader, "Table Registry", CFE_FS_SubType_TBL_REG); + /* Check to see if the Registry entry is empty */ + if (!CFE_RESOURCEID_TEST_EQUAL(RegRecPtr->OwnerAppId, CFE_TBL_NOT_OWNED) || + (RegRecPtr->HeadOfAccessList != CFE_TBL_END_OF_LIST)) + { + IsValidEntry = true; + OwnerAppId = RegRecPtr->OwnerAppId; + + /* Fill Registry Dump Record with relevant information */ + StatePtr->DumpRecord.Size = CFE_ES_MEMOFFSET_C(RegRecPtr->Size); + StatePtr->DumpRecord.TimeOfLastUpdate = RegRecPtr->TimeOfLastUpdate; + StatePtr->DumpRecord.LoadInProgress = RegRecPtr->LoadInProgress; + StatePtr->DumpRecord.ValidationFunc = (RegRecPtr->ValidationFuncPtr != NULL); + StatePtr->DumpRecord.TableLoadedOnce = RegRecPtr->TableLoadedOnce; + StatePtr->DumpRecord.LoadPending = RegRecPtr->LoadPending; + StatePtr->DumpRecord.DumpOnly = RegRecPtr->DumpOnly; + StatePtr->DumpRecord.DoubleBuffered = RegRecPtr->DoubleBuffered; + StatePtr->DumpRecord.FileCreateTimeSecs = RegRecPtr->Buffers[RegRecPtr->ActiveBufferIndex].FileCreateTimeSecs; + StatePtr->DumpRecord.FileCreateTimeSubSecs = RegRecPtr->Buffers[RegRecPtr->ActiveBufferIndex].FileCreateTimeSubSecs; + StatePtr->DumpRecord.Crc = RegRecPtr->Buffers[RegRecPtr->ActiveBufferIndex].Crc; + StatePtr->DumpRecord.CriticalTable = RegRecPtr->CriticalTable; + + /* Convert LoadInProgress flag into more meaningful information */ + /* When a load is in progress, identify which buffer is being used as the inactive buffer */ + if (StatePtr->DumpRecord.LoadInProgress != CFE_TBL_NO_LOAD_IN_PROGRESS) + { + if (StatePtr->DumpRecord.DoubleBuffered) + { + /* For double buffered tables, the value of LoadInProgress, when a load is actually in progress, */ + /* should identify either buffer #0 or buffer #1. Convert these to enumerated value for ground */ + /* display. LoadInProgress = -2 means Buffer #1, LoadInProgress = -3 means Buffer #0. */ + StatePtr->DumpRecord.LoadInProgress = StatePtr->DumpRecord.LoadInProgress - 3; + } + /* For single buffered tables, the value of LoadInProgress, when a load is actually in progress, */ + /* indicates which shared buffer is allocated for the inactive buffer. Since the number of inactive */ + /* buffers is a platform configuration parameter, then 0 on up merely identifies the buffer number. */ + /* No translation is necessary for single buffered tables. */ + } + + strncpy(StatePtr->DumpRecord.Name, RegRecPtr->Name, sizeof(StatePtr->DumpRecord.Name)-1); + StatePtr->DumpRecord.Name[sizeof(StatePtr->DumpRecord.Name)-1] = 0; + + strncpy(StatePtr->DumpRecord.LastFileLoaded, RegRecPtr->LastFileLoaded, sizeof(StatePtr->DumpRecord.LastFileLoaded)-1); + StatePtr->DumpRecord.LastFileLoaded[sizeof(StatePtr->DumpRecord.LastFileLoaded)-1] = 0; - /* Output the Standard cFE File Header to the Dump File */ - Status = CFE_FS_WriteHeader(FileDescriptor, &StdFileHeader); - - /* Maintain statistics of amount of data written to file */ - FileSize += Status; + /* Walk the access descriptor list to determine the number of users */ + StatePtr->DumpRecord.NumUsers = 0; + HandleIterator = RegRecPtr->HeadOfAccessList; + while (HandleIterator != CFE_TBL_END_OF_LIST) + { + StatePtr->DumpRecord.NumUsers++; + HandleIterator = CFE_TBL_Global.Handles[HandleIterator].NextLink; + } + } - if (Status == sizeof(CFE_FS_Header_t)) + /* Unlock now - remainder of data gathering uses ES */ + CFE_TBL_UnlockRegistry(); + } + + /* + * If table record had data, then export now. + * Need to also get the App name from ES to complete the record. + */ + if (IsValidEntry) + { + /* Determine the name of the owning application */ + if (!CFE_RESOURCEID_TEST_EQUAL(OwnerAppId, CFE_TBL_NOT_OWNED)) { - Status = sizeof(CFE_TBL_RegDumpRec_t); - while ((RegIndex < CFE_PLATFORM_TBL_MAX_NUM_TABLES) && (Status == sizeof(CFE_TBL_RegDumpRec_t))) - { - /* Make a pointer to simplify code look and to remove redundant indexing into registry */ - RegRecPtr = &CFE_TBL_Global.Registry[RegIndex]; + CFE_ES_GetAppName(StatePtr->DumpRecord.OwnerAppName, OwnerAppId, sizeof(StatePtr->DumpRecord.OwnerAppName)); + } + else + { + strncpy(StatePtr->DumpRecord.OwnerAppName, "--UNOWNED--", sizeof(StatePtr->DumpRecord.OwnerAppName)-1); + StatePtr->DumpRecord.OwnerAppName[sizeof(StatePtr->DumpRecord.OwnerAppName)-1] = 0; + } - /* Check to see if the Registry entry is empty */ - if (!CFE_RESOURCEID_TEST_EQUAL(RegRecPtr->OwnerAppId, CFE_TBL_NOT_OWNED) || - (RegRecPtr->HeadOfAccessList != CFE_TBL_END_OF_LIST)) - { - /* Fill Registry Dump Record with relevant information */ - DumpRecord.Size = CFE_ES_MEMOFFSET_C(RegRecPtr->Size); - DumpRecord.TimeOfLastUpdate = RegRecPtr->TimeOfLastUpdate; - DumpRecord.LoadInProgress = RegRecPtr->LoadInProgress; - DumpRecord.ValidationFunc = (RegRecPtr->ValidationFuncPtr != NULL); - DumpRecord.TableLoadedOnce = RegRecPtr->TableLoadedOnce; - DumpRecord.LoadPending = RegRecPtr->LoadPending; - DumpRecord.DumpOnly = RegRecPtr->DumpOnly; - DumpRecord.DoubleBuffered = RegRecPtr->DoubleBuffered; - DumpRecord.FileCreateTimeSecs = RegRecPtr->Buffers[RegRecPtr->ActiveBufferIndex].FileCreateTimeSecs; - DumpRecord.FileCreateTimeSubSecs = RegRecPtr->Buffers[RegRecPtr->ActiveBufferIndex].FileCreateTimeSubSecs; - DumpRecord.Crc = RegRecPtr->Buffers[RegRecPtr->ActiveBufferIndex].Crc; - DumpRecord.CriticalTable = RegRecPtr->CriticalTable; - - /* Convert LoadInProgress flag into more meaningful information */ - /* When a load is in progress, identify which buffer is being used as the inactive buffer */ - if (DumpRecord.LoadInProgress != CFE_TBL_NO_LOAD_IN_PROGRESS) - { - if (DumpRecord.DoubleBuffered) - { - /* For double buffered tables, the value of LoadInProgress, when a load is actually in progress, */ - /* should identify either buffer #0 or buffer #1. Convert these to enumerated value for ground */ - /* display. LoadInProgress = -2 means Buffer #1, LoadInProgress = -3 means Buffer #0. */ - DumpRecord.LoadInProgress = DumpRecord.LoadInProgress - 3; - } - /* For single buffered tables, the value of LoadInProgress, when a load is actually in progress, */ - /* indicates which shared buffer is allocated for the inactive buffer. Since the number of inactive */ - /* buffers is a platform configuration parameter, then 0 on up merely identifies the buffer number. */ - /* No translation is necessary for single buffered tables. */ - } - - /* Zero character arrays to remove garbage text */ - memset(DumpRecord.Name, 0, CFE_TBL_MAX_FULL_NAME_LEN); - memset(DumpRecord.LastFileLoaded, 0, OS_MAX_PATH_LEN); - memset(DumpRecord.OwnerAppName, 0, OS_MAX_API_NAME); - - strncpy(DumpRecord.Name, RegRecPtr->Name, sizeof(DumpRecord.Name)-1); - strncpy(DumpRecord.LastFileLoaded, RegRecPtr->LastFileLoaded, sizeof(DumpRecord.LastFileLoaded)-1); - - /* Walk the access descriptor list to determine the number of users */ - DumpRecord.NumUsers = 0; - HandleIterator = RegRecPtr->HeadOfAccessList; - while (HandleIterator != CFE_TBL_END_OF_LIST) - { - DumpRecord.NumUsers++; - HandleIterator = CFE_TBL_Global.Handles[HandleIterator].NextLink; - } + /* export data to caller */ + *Buffer = &StatePtr->DumpRecord; + *BufSize = sizeof(StatePtr->DumpRecord); + } + else + { + /* No data to write for this record */ + *BufSize = 0; + *Buffer = NULL; + } - /* Determine the name of the owning application */ - if (!CFE_RESOURCEID_TEST_EQUAL(RegRecPtr->OwnerAppId, CFE_TBL_NOT_OWNED)) - { - CFE_ES_GetAppName(DumpRecord.OwnerAppName, RegRecPtr->OwnerAppId, sizeof(DumpRecord.OwnerAppName)); - } - else - { - strncpy(DumpRecord.OwnerAppName, "--UNOWNED--", sizeof(DumpRecord.OwnerAppName)-1); - } + /* Check for EOF (last entry) */ + return (RecordNum >= (CFE_PLATFORM_TBL_MAX_NUM_TABLES-1)); +} - /* Output Registry Dump Record to Registry Dump File */ - Status = OS_write(FileDescriptor, - &DumpRecord, - sizeof(CFE_TBL_RegDumpRec_t)); - - FileSize += Status; - NumEntries++; - } +/******************************************************************* +** +** CFE_TBL_DumpRegistryEventHandler() -- Helper function for dumping table registry +** +********************************************************************/ - /* Look at the next entry in the Registry */ - RegIndex++; - } +void CFE_TBL_DumpRegistryEventHandler(void *Meta, CFE_FS_FileWriteEvent_t Event, int32 Status, uint32 RecordNum, size_t BlockSize, size_t Position) +{ + CFE_TBL_RegDumpStateInfo_t *StatePtr = (CFE_TBL_RegDumpStateInfo_t *)Meta; - if (Status == sizeof(CFE_TBL_RegDumpRec_t)) + /* + * Note that this runs in the context of ES background task (file writer background job) + * It does NOT run in the context of the CFE_TBL app task. + * + * Events should use CFE_EVS_SendEventWithAppID() rather than CFE_EVS_SendEvent() + * to get proper association with TBL task. + */ + switch(Event) + { + case CFE_FS_FileWriteEvent_COMPLETE: + if (StatePtr->FileExisted) { - if (FileExistedPrev) - { - CFE_EVS_SendEvent(CFE_TBL_OVERWRITE_REG_DUMP_INF_EID, - CFE_EVS_EventType_DEBUG, - "Successfully overwrote '%s' with Table Registry:Size=%d,Entries=%d", - DumpFilename, (int)FileSize, (int)NumEntries); - } - else - { - CFE_EVS_SendEvent(CFE_TBL_WRITE_REG_DUMP_INF_EID, - CFE_EVS_EventType_DEBUG, - "Successfully dumped Table Registry to '%s':Size=%d,Entries=%d", - DumpFilename, (int)FileSize, (int)NumEntries); - } - - /* Increment Successful Command Counter */ - ReturnCode = CFE_TBL_INC_CMD_CTR; + CFE_EVS_SendEventWithAppID(CFE_TBL_OVERWRITE_REG_DUMP_INF_EID, + CFE_EVS_EventType_DEBUG, + CFE_TBL_Global.TableTaskAppId, + "Successfully overwrote '%s' with Table Registry:Size=%d,Entries=%d", + StatePtr->FileWrite.FileName, (int)Position, (int)RecordNum); } else { - CFE_EVS_SendEvent(CFE_TBL_WRITE_TBL_REG_ERR_EID, - CFE_EVS_EventType_ERROR, - "Error writing Registry to '%s', Status=0x%08X", - DumpFilename, (unsigned int)Status); + CFE_EVS_SendEventWithAppID(CFE_TBL_WRITE_REG_DUMP_INF_EID, + CFE_EVS_EventType_DEBUG, + CFE_TBL_Global.TableTaskAppId, + "Successfully dumped Table Registry to '%s':Size=%d,Entries=%d", + StatePtr->FileWrite.FileName, (int)Position, (int)RecordNum); } - } - else - { - CFE_EVS_SendEvent(CFE_TBL_WRITE_CFE_HDR_ERR_EID, + break; + + case CFE_FS_FileWriteEvent_RECORD_WRITE_ERROR: + CFE_EVS_SendEventWithAppID(CFE_TBL_WRITE_TBL_REG_ERR_EID, + CFE_EVS_EventType_ERROR, + CFE_TBL_Global.TableTaskAppId, + "Error writing Registry to '%s', Status=0x%08X", + StatePtr->FileWrite.FileName, (unsigned int)Status); + break; + + case CFE_FS_FileWriteEvent_HEADER_WRITE_ERROR: + CFE_EVS_SendEventWithAppID(CFE_TBL_WRITE_CFE_HDR_ERR_EID, CFE_EVS_EventType_ERROR, + CFE_TBL_Global.TableTaskAppId, "Error writing cFE File Header to '%s', Status=0x%08X", - DumpFilename, (unsigned int)Status); - } + StatePtr->FileWrite.FileName, (unsigned int)Status); + break; + + case CFE_FS_FileWriteEvent_CREATE_ERROR: + CFE_EVS_SendEventWithAppID(CFE_TBL_CREATING_DUMP_FILE_ERR_EID, + CFE_EVS_EventType_ERROR, + CFE_TBL_Global.TableTaskAppId, + "Error creating dump file '%s', Status=0x%08X", + StatePtr->FileWrite.FileName, (unsigned int)Status); + break; + + default: + /* unhandled event - ignore */ + break; + } +} - /* We are done outputting data to the dump file. Close it. */ - OS_close(FileDescriptor); - } - else +/******************************************************************* +** +** CFE_TBL_DumpRegistryCmd() -- Process Dump Table Registry to file Command Message +** +** NOTE: For complete prolog information, see 'cfe_tbl_task_cmds.h' +********************************************************************/ + +int32 CFE_TBL_DumpRegistryCmd(const CFE_TBL_DumpRegistryCmd_t *data) +{ + CFE_TBL_CmdProcRet_t ReturnCode = CFE_TBL_INC_ERR_CTR; /* Assume failure */ + int32 Status; + const CFE_TBL_DumpRegistryCmd_Payload_t *CmdPtr = &data->Payload; + os_fstat_t FileStat; + + CFE_TBL_RegDumpStateInfo_t *StatePtr; + + StatePtr = &CFE_TBL_Global.RegDumpState; + + /* If a reg dump was already pending, do not overwrite the current request */ + if (!CFE_FS_BackgroundFileDumpIsPending(&StatePtr->FileWrite)) { - CFE_EVS_SendEvent(CFE_TBL_CREATING_DUMP_FILE_ERR_EID, - CFE_EVS_EventType_ERROR, - "Error creating dump file '%s', Status=0x%08X", - DumpFilename, (unsigned int)Status); + /* Copy the commanded filename into local buffer to ensure size limitation and to allow for modification */ + CFE_SB_MessageStringGet(StatePtr->FileWrite.FileName, CmdPtr->DumpFilename, CFE_PLATFORM_TBL_DEFAULT_REG_DUMP_FILE, + sizeof(StatePtr->FileWrite.FileName), sizeof(CmdPtr->DumpFilename)); + + /* + * Fill out the remainder of meta data. + * This data is currently the same for every request + */ + StatePtr->FileWrite.FileSubType = CFE_FS_SubType_TBL_REG; + snprintf(StatePtr->FileWrite.Description, sizeof(StatePtr->FileWrite.Description), "Table Registry"); + + StatePtr->FileWrite.GetData = CFE_TBL_DumpRegistryGetter; + StatePtr->FileWrite.OnEvent = CFE_TBL_DumpRegistryEventHandler; + + /* + * Before submitting the background request, use OS_stat() to check if the file exists already. + * + * This is because TBL services issued a different event ID in some cases if + * it is overwriting a file vs. creating a new file. + */ + StatePtr->FileExisted = (OS_stat(StatePtr->FileWrite.FileName, &FileStat) == OS_SUCCESS); + + Status = CFE_FS_BackgroundFileDumpRequest(&StatePtr->FileWrite); + + if (Status == CFE_SUCCESS) + { + /* Increment the TBL generic command counter (successfully queued for background job) */ + ReturnCode = CFE_TBL_INC_CMD_CTR; + } } return ReturnCode; diff --git a/fsw/cfe-core/unit-test/es_UT.c b/fsw/cfe-core/unit-test/es_UT.c index 629abd399..91d8000ec 100644 --- a/fsw/cfe-core/unit-test/es_UT.c +++ b/fsw/cfe-core/unit-test/es_UT.c @@ -2306,6 +2306,9 @@ void TestLibs(void) void TestERLog(void) { int Return; + void *LocalBuffer; + size_t LocalBufSize; + CFE_ES_BackgroundLogDumpGlobal_t State; UtPrintf("Begin Test Exception and Reset Log"); @@ -2343,6 +2346,51 @@ void TestERLog(void) CFE_ES_ResetDataPtr->ERLogIndex == 1, "CFE_ES_WriteToERLog", "No log entry rollover; no description; no context"); + + /* Test ER log background write functions */ + memset(&State, 0, sizeof(State)); + LocalBuffer = NULL; + LocalBufSize = 0; + + UT_SetDeferredRetcode(UT_KEY(CFE_PSP_Exception_CopyContext), 1, 128); + UtAssert_True(!CFE_ES_BackgroundERLogFileDataGetter(&State, 0, &LocalBuffer, &LocalBufSize), + "CFE_ES_BackgroundERLogFileDataGetter at start, with context"); + UtAssert_UINT32_EQ(State.EntryBuffer.ContextSize, 128); + UtAssert_NOT_NULL(LocalBuffer); + UtAssert_NONZERO(LocalBufSize); + + memset(&State.EntryBuffer, 0xEE, sizeof(State.EntryBuffer)); + UT_SetDeferredRetcode(UT_KEY(CFE_PSP_Exception_CopyContext), 1, -1); + UtAssert_True(CFE_ES_BackgroundERLogFileDataGetter(&State, CFE_PLATFORM_ES_ER_LOG_ENTRIES-1, &LocalBuffer, &LocalBufSize), + "CFE_ES_BackgroundERLogFileDataGetter at EOF, no context"); + UtAssert_ZERO(State.EntryBuffer.ContextSize); + + UtAssert_True(CFE_ES_BackgroundERLogFileDataGetter(&State, CFE_PLATFORM_ES_ER_LOG_ENTRIES, &LocalBuffer, &LocalBufSize), + "CFE_ES_BackgroundERLogFileDataGetter beyond EOF"); + UtAssert_NULL(LocalBuffer); + UtAssert_ZERO(LocalBufSize); + + + /* Test ER log background write event handling */ + UT_ClearEventHistory(); + CFE_ES_BackgroundERLogFileEventHandler(&State, CFE_FS_FileWriteEvent_COMPLETE, CFE_SUCCESS, 10, 0, 100); + UtAssert_True(UT_EventIsInHistory(CFE_ES_ERLOG2_EID), "COMPLETE: CFE_ES_ERLOG2_EID generated"); + + UT_ClearEventHistory(); + CFE_ES_BackgroundERLogFileEventHandler(&State, CFE_FS_FileWriteEvent_HEADER_WRITE_ERROR, -1, 10, 10, 100); + UtAssert_True(UT_EventIsInHistory(CFE_ES_FILEWRITE_ERR_EID), "HEADER_WRITE_ERROR: CFE_ES_FILEWRITE_ERR_EID generated"); + + UT_ClearEventHistory(); + CFE_ES_BackgroundERLogFileEventHandler(&State, CFE_FS_FileWriteEvent_RECORD_WRITE_ERROR, -1, 10, 10, 100); + UtAssert_True(UT_EventIsInHistory(CFE_ES_FILEWRITE_ERR_EID), "RECORD_WRITE_ERROR: CFE_ES_FILEWRITE_ERR_EID generated"); + + UT_ClearEventHistory(); + CFE_ES_BackgroundERLogFileEventHandler(&State, CFE_FS_FileWriteEvent_CREATE_ERROR, -1, 10, 10, 100); + UtAssert_True(UT_EventIsInHistory(CFE_ES_ERLOG2_ERR_EID), "CREATE_ERROR: CFE_ES_ERLOG2_ERR_EID generated"); + + UT_ClearEventHistory(); + CFE_ES_BackgroundERLogFileEventHandler(&State, CFE_FS_FileWriteEvent_UNDEFINED, CFE_SUCCESS, 10, 0, 100); + UtAssert_True(UT_GetNumEventsSent() == 0, "UNDEFINED: No event generated"); } @@ -2592,7 +2640,6 @@ void TestGenericPool(void) void TestTask(void) { uint32 ResetType; - uint32 UT_ContextData; osal_id_t UT_ContextTask; union { @@ -3430,11 +3477,11 @@ void TestTask(void) strncpy(CmdBuf.WriteERLogCmd.Payload.FileName, "filename", sizeof(CmdBuf.WriteERLogCmd.Payload.FileName) - 1); CmdBuf.WriteERLogCmd.Payload.FileName[sizeof(CmdBuf.WriteERLogCmd.Payload.FileName) - 1] = '\0'; - CFE_ES_TaskData.BackgroundERLogDumpState.IsPending = false; + UT_SetDefaultReturnValue(UT_KEY(CFE_FS_BackgroundFileDumpIsPending), false); UT_CallTaskPipe(CFE_ES_TaskPipe, &CmdBuf.Msg, sizeof(CmdBuf.WriteERLogCmd), UT_TPID_CFE_ES_CMD_WRITE_ER_LOG_CC); UT_Report(__FILE__, __LINE__, - CFE_ES_TaskData.BackgroundERLogDumpState.IsPending, + UT_GetStubCount(UT_KEY(CFE_FS_BackgroundFileDumpRequest)) == 1, "CFE_ES_WriteERLogCmd", "Write E&R log command; pending"); UT_Report(__FILE__, __LINE__, @@ -3442,71 +3489,25 @@ void TestTask(void) "CFE_ES_WriteERLogCmd", "Write E&R log command; no events"); - /* sending the same command a second time should fail with an event - * indicating a file write is already pending. */ + /* Failure from CFE_FS_BackgroundFileDumpRequest() should send the pending error event ID */ + UT_ClearEventHistory(); + UT_SetDeferredRetcode(UT_KEY(CFE_FS_BackgroundFileDumpRequest), 1, CFE_STATUS_REQUEST_ALREADY_PENDING); UT_CallTaskPipe(CFE_ES_TaskPipe, &CmdBuf.Msg, sizeof(CmdBuf.WriteERLogCmd), UT_TPID_CFE_ES_CMD_WRITE_ER_LOG_CC); UT_Report(__FILE__, __LINE__, UT_EventIsInHistory(CFE_ES_ERLOG_PENDING_ERR_EID), "CFE_ES_WriteERLogCmd", - "Write E&R log command; already pending event"); - - /* calling the background job when no write pending should immediately return false, no event */ - ES_ResetUnitTest(); - memset(&CFE_ES_TaskData.BackgroundERLogDumpState, 0, sizeof(CFE_ES_TaskData.BackgroundERLogDumpState)); - UT_Report(__FILE__, __LINE__, - !CFE_ES_RunERLogDump(0, &CFE_ES_TaskData.BackgroundERLogDumpState), - "CFE_ES_RunERLogDump", - "Write E&R log; not pending"); - UT_Report(__FILE__, __LINE__, - !UT_EventIsInHistory(CFE_ES_ERLOG2_EID), - "CFE_ES_WriteERLogCmd", - "Write E&R log command; no file written event"); - - /* nominal condition - still returns false, but generates event */ - ES_ResetUnitTest(); - UT_ContextData = 42; - UT_SetDataBuffer(UT_KEY(CFE_PSP_Exception_CopyContext),&UT_ContextData, sizeof(UT_ContextData), false); - CFE_ES_TaskData.BackgroundERLogDumpState.IsPending = true; - CFE_ES_RunERLogDump(0, &CFE_ES_TaskData.BackgroundERLogDumpState); - UT_Report(__FILE__, __LINE__, - !CFE_ES_TaskData.BackgroundERLogDumpState.IsPending, - "CFE_ES_RunERLogDump", - "Write E&R log; nominal, clear flag"); - UT_Report(__FILE__, __LINE__, - UT_EventIsInHistory(CFE_ES_ERLOG2_EID), - "CFE_ES_WriteERLogCmd", - "Write E&R log command; file written event"); - - /* Test writing the E&R log with an OS create failure */ - ES_ResetUnitTest(); - CFE_ES_TaskData.BackgroundERLogDumpState.IsPending = true; - UT_SetDefaultReturnValue(UT_KEY(OS_OpenCreate), OS_ERROR); - CFE_ES_RunERLogDump(0, &CFE_ES_TaskData.BackgroundERLogDumpState); - UT_Report(__FILE__, __LINE__, - UT_EventIsInHistory(CFE_ES_ERLOG2_ERR_EID), - "CFE_ES_RunERLogDump", - "Write E&R log; OS create"); - - /* Test writing the E&R log with an OS write failure */ - ES_ResetUnitTest(); - CFE_ES_TaskData.BackgroundERLogDumpState.IsPending = true; - UT_SetDefaultReturnValue(UT_KEY(OS_write), OS_ERROR); - CFE_ES_RunERLogDump(0, &CFE_ES_TaskData.BackgroundERLogDumpState); - UT_Report(__FILE__, __LINE__, - UT_EventIsInHistory(CFE_ES_FILEWRITE_ERR_EID), - "CFE_ES_RunERLogDump", - "Write E&R log; OS write"); + "Write E&R log command; already pending event (from FS)"); - /* Test writing the E&R log with a write header failure */ - ES_ResetUnitTest(); - CFE_ES_TaskData.BackgroundERLogDumpState.IsPending = true; - UT_SetDeferredRetcode(UT_KEY(CFE_FS_WriteHeader), 1, OS_ERROR); - CFE_ES_RunERLogDump(0, &CFE_ES_TaskData.BackgroundERLogDumpState); + /* Same event but pending locally */ + UT_ClearEventHistory(); + UT_SetDefaultReturnValue(UT_KEY(CFE_FS_BackgroundFileDumpIsPending), true); + UT_CallTaskPipe(CFE_ES_TaskPipe, &CmdBuf.Msg, sizeof(CmdBuf.WriteERLogCmd), + UT_TPID_CFE_ES_CMD_WRITE_ER_LOG_CC); UT_Report(__FILE__, __LINE__, - UT_EventIsInHistory(CFE_ES_FILEWRITE_ERR_EID), + UT_EventIsInHistory(CFE_ES_ERLOG_PENDING_ERR_EID), "CFE_ES_WriteERLogCmd", - "Write E&R log; write header"); + "Write E&R log command; already pending event (local)"); /* Test scan for exceptions in the PSP, should invoke a Processor Reset */ ES_ResetUnitTest(); diff --git a/fsw/cfe-core/unit-test/fs_UT.c b/fsw/cfe-core/unit-test/fs_UT.c index 4b569cb19..00335e0dd 100644 --- a/fsw/cfe-core/unit-test/fs_UT.c +++ b/fsw/cfe-core/unit-test/fs_UT.c @@ -47,6 +47,24 @@ const char *FS_SYSLOG_MSGS[] = "FS SharedData Mutex Give Err Stat=0x%x,App=%lu,Function=%s\n" }; +/* counts the number of times UT_FS_OnEvent() was invoked (below) */ +uint32 UT_FS_FileWriteEventCount[CFE_FS_FileWriteEvent_MAX]; + + +/* UT helper stub compatible with background file write DataGetter */ +bool UT_FS_DataGetter(void *Meta, uint32 RecordNum, void **Buffer, size_t *BufSize) +{ + UT_GetDataBuffer(UT_KEY(UT_FS_DataGetter), Buffer, BufSize, NULL); + return UT_DEFAULT_IMPL(UT_FS_DataGetter); +} + +/* UT helper stub compatible with background file write OnEvent */ +void UT_FS_OnEvent(void *Meta, CFE_FS_FileWriteEvent_t Event, int32 Status, uint32 RecordNum, size_t BlockSize, size_t Position) +{ + ++UT_FS_FileWriteEventCount[Event]; + UT_DEFAULT_IMPL(UT_FS_OnEvent); +} + /* ** Functions */ @@ -65,6 +83,8 @@ void UtTest_Setup(void) UT_ADD_TEST(Test_CFE_FS_ByteSwapUint32); UT_ADD_TEST(Test_CFE_FS_ExtractFileNameFromPath); UT_ADD_TEST(Test_CFE_FS_Private); + + UT_ADD_TEST(Test_CFE_FS_BackgroundFileDump); } /* @@ -358,3 +378,124 @@ void Test_CFE_FS_Private(void) UtPrintf("End Test Private\n"); } +void Test_CFE_FS_BackgroundFileDump(void) +{ + /* + * Test routine for: + * bool CFE_FS_RunBackgroundFileDump(uint32 ElapsedTime, void *Arg) + */ + CFE_FS_FileWriteMetaData_t State; + uint32 MyBuffer[2]; + int32 Status; + + memset(UT_FS_FileWriteEventCount, 0, sizeof(UT_FS_FileWriteEventCount)); + memset(&State, 0, sizeof(State)); + memset(&CFE_FS_Global.FileDump, 0, sizeof(CFE_FS_Global.FileDump)); + + /* Nominal with nothing pending - should accumulate credit */ + UtAssert_True(!CFE_FS_RunBackgroundFileDump(1, NULL), "CFE_FS_RunBackgroundFileDump() nothing pending, short delay"); + UtAssert_True(CFE_FS_Global.FileDump.Current.Credit > 0, "Credit accumulating (%lu)", (unsigned long)CFE_FS_Global.FileDump.Current.Credit); + UtAssert_True(CFE_FS_Global.FileDump.Current.Credit < CFE_FS_BACKGROUND_MAX_CREDIT, "Credit not max (%lu)", (unsigned long)CFE_FS_Global.FileDump.Current.Credit); + + UtAssert_True(!CFE_FS_RunBackgroundFileDump(100000, NULL), "CFE_FS_RunBackgroundFileDump() nothing pending, long delay"); + UtAssert_True(CFE_FS_Global.FileDump.Current.Credit == CFE_FS_BACKGROUND_MAX_CREDIT, "Credit at max (%lu)", (unsigned long)CFE_FS_Global.FileDump.Current.Credit); + + Status = CFE_FS_BackgroundFileDumpRequest(NULL); + UtAssert_True(Status == CFE_FS_BAD_ARGUMENT, "CFE_FS_BackgroundFileDumpRequest(NULL) (%lu) == CFE_FS_BAD_ARGUMENT", (unsigned long)Status); + Status = CFE_FS_BackgroundFileDumpRequest(&State); + UtAssert_True(Status == CFE_FS_BAD_ARGUMENT, "CFE_FS_BackgroundFileDumpRequest(&State) (%lu) == CFE_FS_BAD_ARGUMENT", (unsigned long)Status); + UtAssert_True(!CFE_FS_BackgroundFileDumpIsPending(&State), "!CFE_FS_BackgroundFileDumpIsPending(&State)"); + UtAssert_STUB_COUNT(CFE_ES_BackgroundWakeup, 0); /* confirm CFE_ES_BackgroundWakeup() was not invoked */ + + /* Set the data except file name and description */ + State.FileSubType = 2; + State.GetData = UT_FS_DataGetter; + State.OnEvent = UT_FS_OnEvent; + Status = CFE_FS_BackgroundFileDumpRequest(&State); + UtAssert_True(Status == CFE_FS_INVALID_PATH, "CFE_FS_BackgroundFileDumpRequest(&State) (%lu) == CFE_FS_INVALID_PATH", (unsigned long)Status); + UtAssert_True(!CFE_FS_BackgroundFileDumpIsPending(&State), "!CFE_FS_BackgroundFileDumpIsPending(&State)"); + UtAssert_STUB_COUNT(CFE_ES_BackgroundWakeup, 0); /* confirm CFE_ES_BackgroundWakeup() was not invoked */ + + /* Set up remainder of fields, so entry is valid */ + strncpy(State.FileName, "/ram/UT.bin", sizeof(State.FileName)); + strncpy(State.Description, "UT", sizeof(State.Description)); + + Status = CFE_FS_BackgroundFileDumpRequest(&State); + UtAssert_True(Status == CFE_SUCCESS, "CFE_FS_BackgroundFileDumpRequest() (%lu) == CFE_SUCCESS", (unsigned long)Status); + UtAssert_True(CFE_FS_BackgroundFileDumpIsPending(&State), "CFE_FS_BackgroundFileDumpIsPending(&State)"); + UtAssert_STUB_COUNT(CFE_ES_BackgroundWakeup, 1); /* confirm CFE_ES_BackgroundWakeup() was invoked */ + + /* + * Set up a fixed data buffer which will be written, + * this will write sizeof(MyBuffer) until credit is gone + */ + MyBuffer[0] = 10; + MyBuffer[1] = 20; + UT_SetDataBuffer(UT_KEY(UT_FS_DataGetter),MyBuffer,sizeof(MyBuffer), false); + UtAssert_True(CFE_FS_RunBackgroundFileDump(1, NULL), "CFE_FS_RunBackgroundFileDump() request pending nominal"); + UtAssert_STUB_COUNT(OS_OpenCreate, 1); /* confirm OS_open() was invoked */ + UtAssert_True(CFE_FS_Global.FileDump.Current.Credit <= 0, "Credit exhausted (%lu)", (unsigned long)CFE_FS_Global.FileDump.Current.Credit); + UtAssert_STUB_COUNT(OS_close, 0); /* confirm OS_close() was not invoked */ + + UT_SetDeferredRetcode(UT_KEY(UT_FS_DataGetter), 2, true); /* return EOF */ + UtAssert_True(!CFE_FS_RunBackgroundFileDump(100, NULL), "CFE_FS_RunBackgroundFileDump() request pending EOF"); + UtAssert_STUB_COUNT(OS_OpenCreate, 1); /* confirm OS_open() was not invoked again */ + UtAssert_STUB_COUNT(OS_close, 1); /* confirm OS_close() was invoked */ + UtAssert_UINT32_EQ(CFE_FS_Global.FileDump.CompleteCount, CFE_FS_Global.FileDump.RequestCount); /* request was completed */ + UtAssert_UINT32_EQ(UT_FS_FileWriteEventCount[CFE_FS_FileWriteEvent_COMPLETE], 1); /* complete event was sent */ + UtAssert_True(!CFE_FS_BackgroundFileDumpIsPending(&State), "!CFE_FS_BackgroundFileDumpIsPending(&State)"); + + UT_ResetState(UT_KEY(UT_FS_DataGetter)); + + + /* Error opening file */ + Status = CFE_FS_BackgroundFileDumpRequest(&State); + UtAssert_True(Status == CFE_SUCCESS, "CFE_FS_BackgroundFileDumpRequest() (%lu) == CFE_SUCCESS", (unsigned long)Status); + UT_SetDeferredRetcode(UT_KEY(OS_OpenCreate), 1, OS_ERROR); + + UtAssert_True(CFE_FS_RunBackgroundFileDump(100, NULL), "CFE_FS_RunBackgroundFileDump() request pending, file open error"); + UtAssert_UINT32_EQ(UT_FS_FileWriteEventCount[CFE_FS_FileWriteEvent_CREATE_ERROR], 1); /* create error event was sent */ + UtAssert_True(!CFE_FS_BackgroundFileDumpIsPending(&State), "!CFE_FS_BackgroundFileDumpIsPending(&State)"); + UtAssert_UINT32_EQ(CFE_FS_Global.FileDump.CompleteCount, CFE_FS_Global.FileDump.RequestCount); /* request was completed */ + + /* Error writing header */ + Status = CFE_FS_BackgroundFileDumpRequest(&State); + UtAssert_True(Status == CFE_SUCCESS, "CFE_FS_BackgroundFileDumpRequest() (%lu) == CFE_SUCCESS", (unsigned long)Status); + UT_SetDeferredRetcode(UT_KEY(OS_write), 1, OS_ERROR); + + UtAssert_True(CFE_FS_RunBackgroundFileDump(100, NULL), "CFE_FS_RunBackgroundFileDump() request pending, file write header error"); + UtAssert_UINT32_EQ(UT_FS_FileWriteEventCount[CFE_FS_FileWriteEvent_HEADER_WRITE_ERROR], 1); /* header error event was sent */ + UtAssert_True(!CFE_FS_BackgroundFileDumpIsPending(&State), "!CFE_FS_BackgroundFileDumpIsPending(&State)"); + UtAssert_UINT32_EQ(CFE_FS_Global.FileDump.CompleteCount, CFE_FS_Global.FileDump.RequestCount); /* request was completed */ + + /* Error writing data */ + Status = CFE_FS_BackgroundFileDumpRequest(&State); + UtAssert_True(Status == CFE_SUCCESS, "CFE_FS_BackgroundFileDumpRequest() (%lu) == CFE_SUCCESS", (unsigned long)Status); + UT_SetDeferredRetcode(UT_KEY(OS_write), 2, OS_ERROR); + UT_SetDataBuffer(UT_KEY(UT_FS_DataGetter),MyBuffer,sizeof(MyBuffer), false); + UtAssert_True(CFE_FS_RunBackgroundFileDump(100, NULL), "CFE_FS_RunBackgroundFileDump() request pending, file write data error"); + UtAssert_UINT32_EQ(UT_FS_FileWriteEventCount[CFE_FS_FileWriteEvent_RECORD_WRITE_ERROR], 1); /* record error event was sent */ + UtAssert_True(!CFE_FS_BackgroundFileDumpIsPending(&State), "!CFE_FS_BackgroundFileDumpIsPending(&State)"); + UtAssert_UINT32_EQ(CFE_FS_Global.FileDump.CompleteCount, CFE_FS_Global.FileDump.RequestCount); /* request was completed */ + + UT_ResetState(UT_KEY(UT_FS_DataGetter)); + + /* Request multiple file dumps, check queing logic */ + Status = CFE_FS_BackgroundFileDumpRequest(&State); + UtAssert_True(Status == CFE_SUCCESS, "CFE_FS_BackgroundFileDumpRequest() (%lu) == CFE_SUCCESS", (unsigned long)Status); + Status = CFE_FS_BackgroundFileDumpRequest(&State); + UtAssert_True(Status == CFE_STATUS_REQUEST_ALREADY_PENDING, "CFE_FS_BackgroundFileDumpRequest() (%lu) == CFE_STATUS_REQUEST_ALREADY_PENDING", (unsigned long)Status); + + do + { + State.IsPending = false; /* UT hack to fill queue - Force not pending. Real code should not do this. */ + Status = CFE_FS_BackgroundFileDumpRequest(&State); + } + while (Status == CFE_SUCCESS); + + UtAssert_True(Status == CFE_STATUS_REQUEST_ALREADY_PENDING, "CFE_FS_BackgroundFileDumpRequest() (%lu) == CFE_STATUS_REQUEST_ALREADY_PENDING", (unsigned long)Status); + UtAssert_UINT32_EQ(CFE_FS_Global.FileDump.RequestCount, (CFE_FS_Global.FileDump.CompleteCount + CFE_FS_MAX_BACKGROUND_FILE_WRITES - 1)); + + /* Confirm null arg handling in CFE_FS_BackgroundFileDumpIsPending() */ + UtAssert_True(!CFE_FS_BackgroundFileDumpIsPending(NULL), "!CFE_FS_BackgroundFileDumpIsPending(NULL)"); +} diff --git a/fsw/cfe-core/unit-test/fs_UT.h b/fsw/cfe-core/unit-test/fs_UT.h index 1d9fdf1c2..47b2573be 100644 --- a/fsw/cfe-core/unit-test/fs_UT.h +++ b/fsw/cfe-core/unit-test/fs_UT.h @@ -270,4 +270,17 @@ void Test_CFE_FS_Decompress(void); ******************************************************************************/ void Test_CFE_FS_GetUncompressedFile(void); +/*****************************************************************************/ +/** +** \brief Tests for FS background file dump +** +** \par Assumptions, External Events, and Notes: +** None +** +** \returns +** This function does not return a value. +** +******************************************************************************/ +void Test_CFE_FS_BackgroundFileDump(void); + #endif /* _es_ut_h_ */ diff --git a/fsw/cfe-core/unit-test/sb_UT.c b/fsw/cfe-core/unit-test/sb_UT.c index 3e041c35d..e6afc7715 100644 --- a/fsw/cfe-core/unit-test/sb_UT.c +++ b/fsw/cfe-core/unit-test/sb_UT.c @@ -383,21 +383,16 @@ void Test_SB_Cmds(void) SB_UT_ADD_SUBTEST(Test_SB_Cmds_Noop); SB_UT_ADD_SUBTEST(Test_SB_Cmds_RstCtrs); SB_UT_ADD_SUBTEST(Test_SB_Cmds_Stats); + SB_UT_ADD_SUBTEST(Test_SB_Cmds_BackgroundFileWriteEvents); SB_UT_ADD_SUBTEST(Test_SB_Cmds_RoutingInfoDef); - SB_UT_ADD_SUBTEST(Test_SB_Cmds_RoutingInfoSpec); - SB_UT_ADD_SUBTEST(Test_SB_Cmds_RoutingInfoCreateFail); - SB_UT_ADD_SUBTEST(Test_SB_Cmds_RoutingInfoHdrFail); - SB_UT_ADD_SUBTEST(Test_SB_Cmds_RoutingInfoWriteFail); + SB_UT_ADD_SUBTEST(Test_SB_Cmds_RoutingInfoAlreadyPending); + SB_UT_ADD_SUBTEST(Test_SB_Cmds_RoutingInfoDataGetter); SB_UT_ADD_SUBTEST(Test_SB_Cmds_PipeInfoDef); - SB_UT_ADD_SUBTEST(Test_SB_Cmds_PipeInfoSpec); - SB_UT_ADD_SUBTEST(Test_SB_Cmds_PipeInfoCreateFail); - SB_UT_ADD_SUBTEST(Test_SB_Cmds_PipeInfoHdrFail); - SB_UT_ADD_SUBTEST(Test_SB_Cmds_PipeInfoWriteFail); + SB_UT_ADD_SUBTEST(Test_SB_Cmds_PipeInfoAlreadyPending); + SB_UT_ADD_SUBTEST(Test_SB_Cmds_PipeInfoDataGetter); SB_UT_ADD_SUBTEST(Test_SB_Cmds_MapInfoDef); - SB_UT_ADD_SUBTEST(Test_SB_Cmds_MapInfoSpec); - SB_UT_ADD_SUBTEST(Test_SB_Cmds_MapInfoCreateFail); - SB_UT_ADD_SUBTEST(Test_SB_Cmds_MapInfoHdrFail); - SB_UT_ADD_SUBTEST(Test_SB_Cmds_MapInfoWriteFail); + SB_UT_ADD_SUBTEST(Test_SB_Cmds_MapInfoAlreadyPending); + SB_UT_ADD_SUBTEST(Test_SB_Cmds_MapInfoDataGetter); SB_UT_ADD_SUBTEST(Test_SB_Cmds_EnRouteValParam); SB_UT_ADD_SUBTEST(Test_SB_Cmds_EnRouteNonExist); SB_UT_ADD_SUBTEST(Test_SB_Cmds_EnRouteInvParam); @@ -528,24 +523,20 @@ void Test_SB_Cmds_RoutingInfoDef(void) CFE_SB_ProcessCmdPipePkt(&WriteRoutingInfo.SBBuf); - EVTCNT(9); + EVTCNT(5); EVTSENT(CFE_SB_INIT_EID); EVTSENT(CFE_SB_SUBSCRIPTION_RCVD_EID); - EVTSENT(CFE_SB_INIT_EID); - - EVTSENT(CFE_SB_SND_RTG_EID); - TEARDOWN(CFE_SB_DeletePipe(CFE_SB_Global.CmdPipe)); } /* end Test_SB_Cmds_RoutingInfoDef */ /* -** Test write routing information command using a specified file name +** Test write routing information command with request already pending */ -void Test_SB_Cmds_RoutingInfoSpec(void) +void Test_SB_Cmds_RoutingInfoAlreadyPending(void) { union { @@ -556,34 +547,7 @@ void Test_SB_Cmds_RoutingInfoSpec(void) CFE_SB_MsgId_t MsgId = CFE_SB_ValueToMsgId(CFE_SB_CMD_MID); CFE_MSG_Size_t Size = sizeof(WriteRoutingInfo.Cmd); - UT_SetDataBuffer(UT_KEY(CFE_MSG_GetMsgId), &MsgId, sizeof(MsgId), false); - UT_SetDataBuffer(UT_KEY(CFE_MSG_GetSize), &Size, sizeof(Size), false); - UT_SetDataBuffer(UT_KEY(CFE_MSG_GetFcnCode), &FcnCode, sizeof(FcnCode), false); - strncpy(WriteRoutingInfo.Cmd.Payload.Filename, "RoutingTstFile", - sizeof(WriteRoutingInfo.Cmd.Payload.Filename) - 1); - WriteRoutingInfo.Cmd.Payload.Filename[sizeof(WriteRoutingInfo.Cmd.Payload.Filename) - 1] = '\0'; - - CFE_SB_ProcessCmdPipePkt(&WriteRoutingInfo.SBBuf); - - EVTCNT(1); - - EVTSENT(CFE_SB_SND_RTG_EID); - -} /* end Test_SB_Cmds_RoutingInfoSpec */ - -/* -** Test write routing information command with a file creation failure -*/ -void Test_SB_Cmds_RoutingInfoCreateFail(void) -{ - union - { - CFE_SB_Buffer_t SBBuf; - CFE_SB_WriteRoutingInfoCmd_t Cmd; - } WriteRoutingInfo; - CFE_MSG_FcnCode_t FcnCode = CFE_SB_WRITE_ROUTING_INFO_CC; - CFE_SB_MsgId_t MsgId = CFE_SB_ValueToMsgId(CFE_SB_CMD_MID); - CFE_MSG_Size_t Size = sizeof(WriteRoutingInfo.Cmd); + UT_SetDefaultReturnValue(UT_KEY(CFE_FS_BackgroundFileDumpIsPending), true); UT_SetDataBuffer(UT_KEY(CFE_MSG_GetMsgId), &MsgId, sizeof(MsgId), false); UT_SetDataBuffer(UT_KEY(CFE_MSG_GetSize), &Size, sizeof(Size), false); @@ -592,61 +556,63 @@ void Test_SB_Cmds_RoutingInfoCreateFail(void) sizeof(WriteRoutingInfo.Cmd.Payload.Filename) - 1); WriteRoutingInfo.Cmd.Payload.Filename[sizeof(WriteRoutingInfo.Cmd.Payload.Filename) - 1] = '\0'; - /* Make function CFE_SB_WriteRtgInfo return CFE_SB_FILE_IO_ERR */ - UT_SetDefaultReturnValue(UT_KEY(OS_OpenCreate), OS_ERROR); - CFE_SB_ProcessCmdPipePkt(&WriteRoutingInfo.SBBuf); EVTCNT(1); EVTSENT(CFE_SB_SND_RTG_ERR1_EID); -} /* end Test_SB_Cmds_RoutingInfoCreateFail */ - -/* -** Test write routing information command with a file header write failure -*/ -void Test_SB_Cmds_RoutingInfoHdrFail(void) -{ - UT_SetDeferredRetcode(UT_KEY(CFE_FS_WriteHeader), 1, -1); - - ASSERT_EQ(CFE_SB_WriteRtgInfo("RoutingTstFile"), CFE_SB_FILE_IO_ERR); - - EVTCNT(1); - - EVTSENT(CFE_SB_FILEWRITE_ERR_EID); - -} /* end Test_SB_Cmds_RoutingInfoHdrFail */ +} /* end Test_SB_Cmds_RoutingInfoSpec */ /* -** Test write routing information command with a file write failure on -** the second write +** Test send routing information command data getter */ -void Test_SB_Cmds_RoutingInfoWriteFail(void) +void Test_SB_Cmds_RoutingInfoDataGetter(void) { - /* Make some routing info by calling CFE_SB_AppInit */ - SETUP(CFE_SB_AppInit()); - - UT_SetDeferredRetcode(UT_KEY(OS_write), 2, -1); - - ASSERT_EQ(CFE_SB_WriteRtgInfo("RoutingTstFile"), CFE_SB_FILE_IO_ERR); - - EVTCNT(9); - - EVTSENT(CFE_SB_PIPE_ADDED_EID); + CFE_SB_PipeId_t PipeId1; + CFE_SB_PipeId_t PipeId2; + CFE_SB_PipeId_t PipeId3; + CFE_SB_MsgId_t MsgId0 = SB_UT_TLM_MID1; + CFE_SB_MsgId_t MsgId1 = SB_UT_TLM_MID2; + CFE_SB_MsgId_t MsgId2 = SB_UT_TLM_MID3; + CFE_SB_MsgId_t MsgId3 = SB_UT_TLM_MID4; + CFE_SB_MsgId_t MsgId4 = SB_UT_TLM_MID5; + CFE_SB_MsgId_t MsgId5 = SB_UT_TLM_MID6; + uint16 PipeDepth = 10; + void *LocalBuffer; + size_t LocalBufSize; + CFE_SB_BackgroundFileStateInfo_t State; - EVTSENT(CFE_SB_SUBSCRIPTION_RCVD_EID); + /* Create some map info */ + SETUP(CFE_SB_CreatePipe(&PipeId1, PipeDepth, "TestPipe1")); + SETUP(CFE_SB_CreatePipe(&PipeId2, PipeDepth, "TestPipe2")); + SETUP(CFE_SB_CreatePipe(&PipeId3, PipeDepth, "TestPipe3")); + SETUP(CFE_SB_Subscribe(MsgId0, PipeId1)); + SETUP(CFE_SB_Subscribe(MsgId0, PipeId2)); + SETUP(CFE_SB_Subscribe(MsgId1, PipeId1)); + SETUP(CFE_SB_Subscribe(MsgId2, PipeId3)); + SETUP(CFE_SB_Subscribe(MsgId3, PipeId3)); + SETUP(CFE_SB_Subscribe(MsgId4, PipeId3)); + SETUP(CFE_SB_Subscribe(MsgId5, PipeId2)); - EVTSENT(CFE_SB_INIT_EID); + memset(&State, 0, sizeof(State)); + LocalBuffer = NULL; + LocalBufSize = 0; - EVTSENT(CFE_SB_FILEWRITE_ERR_EID); + ASSERT_TRUE(!CFE_SB_WriteRouteInfoDataGetter(&State, 0, &LocalBuffer, &LocalBufSize)); + UtAssert_NOT_NULL(LocalBuffer); + UtAssert_NONZERO(LocalBufSize); - TEARDOWN(CFE_SB_DeletePipe(CFE_SB_Global.CmdPipe)); + ASSERT_TRUE(CFE_SB_WriteRouteInfoDataGetter(&State, CFE_PLATFORM_SB_MAX_MSG_IDS, &LocalBuffer, &LocalBufSize)); + UtAssert_ZERO(LocalBufSize); -} /* end Test_SB_Cmds_RoutingInfoWriteFail */ + TEARDOWN(CFE_SB_DeletePipe(PipeId1)); + TEARDOWN(CFE_SB_DeletePipe(PipeId2)); + TEARDOWN(CFE_SB_DeletePipe(PipeId3)); +} /* end Test_SB_Cmds_RoutingInfoDataGetter */ /* -** Test write pipe information command using the default file name +** Test send pipe information command default / nominal path */ void Test_SB_Cmds_PipeInfoDef(void) { @@ -675,10 +641,9 @@ void Test_SB_Cmds_PipeInfoDef(void) CFE_SB_ProcessCmdPipePkt(&WritePipeInfo.SBBuf); - EVTCNT(4); + EVTCNT(3); EVTSENT(CFE_SB_PIPE_ADDED_EID); - EVTSENT(CFE_SB_SND_RTG_EID); TEARDOWN(CFE_SB_DeletePipe(PipeId1)); TEARDOWN(CFE_SB_DeletePipe(PipeId2)); @@ -687,9 +652,9 @@ void Test_SB_Cmds_PipeInfoDef(void) } /* end Test_SB_Cmds_PipeInfoDef */ /* -** Test write pipe information command using a specified file name +** Test write pipe information command when already pending */ -void Test_SB_Cmds_PipeInfoSpec(void) +void Test_SB_Cmds_PipeInfoAlreadyPending(void) { union { @@ -700,6 +665,8 @@ void Test_SB_Cmds_PipeInfoSpec(void) CFE_SB_MsgId_t MsgId = CFE_SB_ValueToMsgId(CFE_SB_CMD_MID); CFE_MSG_Size_t Size = sizeof(WritePipeInfo.Cmd); + UT_SetDefaultReturnValue(UT_KEY(CFE_FS_BackgroundFileDumpIsPending), true); + UT_SetDataBuffer(UT_KEY(CFE_MSG_GetMsgId), &MsgId, sizeof(MsgId), false); UT_SetDataBuffer(UT_KEY(CFE_MSG_GetSize), &Size, sizeof(Size), false); UT_SetDataBuffer(UT_KEY(CFE_MSG_GetFcnCode), &FcnCode, sizeof(FcnCode), false); @@ -711,70 +678,131 @@ void Test_SB_Cmds_PipeInfoSpec(void) EVTCNT(1); - EVTSENT(CFE_SB_SND_RTG_EID); + EVTSENT(CFE_SB_SND_RTG_ERR1_EID); -} /* end Test_SB_Cmds_PipeInfoSpec */ +} /* end Test_SB_Cmds_PipeInfoAlreadyPending */ /* -** Test write pipe information command with a file creation failure +** Test write pipe information data getter */ -void Test_SB_Cmds_PipeInfoCreateFail(void) +void Test_SB_Cmds_PipeInfoDataGetter(void) { - UT_SetDefaultReturnValue(UT_KEY(OS_OpenCreate), OS_ERROR); - ASSERT_EQ(CFE_SB_WritePipeInfo("PipeTstFile"), CFE_SB_FILE_IO_ERR); + CFE_SB_PipeId_t PipeId1; + CFE_SB_PipeId_t PipeId2; + CFE_SB_PipeId_t PipeId3; + uint16 PipeDepth = 10; + void *LocalBuffer; + size_t LocalBufSize; + CFE_SB_BackgroundFileStateInfo_t State; - EVTCNT(1); + SETUP(CFE_SB_CreatePipe(&PipeId1, PipeDepth, "TestPipe1")); + SETUP(CFE_SB_CreatePipe(&PipeId2, PipeDepth, "TestPipe2")); + SETUP(CFE_SB_CreatePipe(&PipeId3, PipeDepth, "TestPipe3")); - EVTSENT(CFE_SB_SND_RTG_ERR1_EID); + memset(&State, 0, sizeof(State)); + LocalBuffer = NULL; + LocalBufSize = 0; + + /* Note that CFE_SB_CreatePipe() fills entry 1 first, so entry 0 is unused */ + ASSERT_TRUE(!CFE_SB_WritePipeInfoDataGetter(&State, 0, &LocalBuffer, &LocalBufSize)); + UtAssert_ZERO(LocalBufSize); + + ASSERT_TRUE(!CFE_SB_WritePipeInfoDataGetter(&State, 1, &LocalBuffer, &LocalBufSize)); + UtAssert_NOT_NULL(LocalBuffer); + UtAssert_NONZERO(LocalBufSize); -} /* end Test_SB_Cmds_PipeInfoCreateFail */ + ASSERT_TRUE(CFE_SB_WritePipeInfoDataGetter(&State, CFE_PLATFORM_SB_MAX_PIPES-1, &LocalBuffer, &LocalBufSize)); + UtAssert_ZERO(LocalBufSize); + + ASSERT_TRUE(CFE_SB_WritePipeInfoDataGetter(&State, CFE_PLATFORM_SB_MAX_PIPES, &LocalBuffer, &LocalBufSize)); + UtAssert_ZERO(LocalBufSize); + + TEARDOWN(CFE_SB_DeletePipe(PipeId1)); + TEARDOWN(CFE_SB_DeletePipe(PipeId2)); + TEARDOWN(CFE_SB_DeletePipe(PipeId3)); +} /* end Test_SB_Cmds_PipeInfoDataGetter */ /* -** Test write pipe information command with a file header write failure +** Test background file write event generator */ -void Test_SB_Cmds_PipeInfoHdrFail(void) +void Test_SB_Cmds_BackgroundFileWriteEvents(void) { - UT_SetDeferredRetcode(UT_KEY(CFE_FS_WriteHeader), 1, -1); - ASSERT_EQ(CFE_SB_WritePipeInfo("PipeTstFile"), CFE_SB_FILE_IO_ERR); + CFE_SB_BackgroundFileStateInfo_t State; - EVTCNT(1); + memset(&State, 0, sizeof(State)); + UT_ClearEventHistory(); + CFE_SB_BackgroundFileEventHandler(&State, CFE_FS_FileWriteEvent_COMPLETE, CFE_SUCCESS, 10, 0, 1000); + EVTSENT(CFE_SB_SND_RTG_EID); + + UT_ClearEventHistory(); + CFE_SB_BackgroundFileEventHandler(&State, CFE_FS_FileWriteEvent_RECORD_WRITE_ERROR, CFE_SUCCESS, 10, 10, 1000); EVTSENT(CFE_SB_FILEWRITE_ERR_EID); -} /* end Test_SB_Cmds_PipeInfoHdrFail */ + UT_ClearEventHistory(); + CFE_SB_BackgroundFileEventHandler(&State, CFE_FS_FileWriteEvent_HEADER_WRITE_ERROR, CFE_SUCCESS, 10, 10, 1000); + EVTSENT(CFE_SB_FILEWRITE_ERR_EID); + + UT_ClearEventHistory(); + CFE_SB_BackgroundFileEventHandler(&State, CFE_FS_FileWriteEvent_CREATE_ERROR, OS_ERROR, 10, 0, 0); + EVTSENT(CFE_SB_SND_RTG_ERR1_EID); + + UT_ClearEventHistory(); + CFE_SB_BackgroundFileEventHandler(&State, CFE_FS_FileWriteEvent_UNDEFINED, OS_ERROR, 0, 0, 0); + EVTCNT(0); + +} /* -** Test write pipe information command with a file write failure on -** the second write +** Test write pipe information data getter for background file write */ -void Test_SB_Cmds_PipeInfoWriteFail(void) +void Test_SB_Cmds_MapInfoDataGetter(void) { CFE_SB_PipeId_t PipeId1; CFE_SB_PipeId_t PipeId2; CFE_SB_PipeId_t PipeId3; + CFE_SB_MsgId_t MsgId0 = SB_UT_TLM_MID1; + CFE_SB_MsgId_t MsgId1 = SB_UT_TLM_MID2; + CFE_SB_MsgId_t MsgId2 = SB_UT_TLM_MID3; + CFE_SB_MsgId_t MsgId3 = SB_UT_TLM_MID4; + CFE_SB_MsgId_t MsgId4 = SB_UT_TLM_MID5; + CFE_SB_MsgId_t MsgId5 = SB_UT_TLM_MID6; uint16 PipeDepth = 10; + void *LocalBuffer; + size_t LocalBufSize; + CFE_SB_BackgroundFileStateInfo_t State; + /* Create some map info */ SETUP(CFE_SB_CreatePipe(&PipeId1, PipeDepth, "TestPipe1")); SETUP(CFE_SB_CreatePipe(&PipeId2, PipeDepth, "TestPipe2")); SETUP(CFE_SB_CreatePipe(&PipeId3, PipeDepth, "TestPipe3")); - UT_SetDeferredRetcode(UT_KEY(OS_write), 2, -1); - - ASSERT_EQ(CFE_SB_WritePipeInfo("PipeTstFile"), CFE_SB_FILE_IO_ERR); + SETUP(CFE_SB_Subscribe(MsgId0, PipeId1)); + SETUP(CFE_SB_Subscribe(MsgId0, PipeId2)); + SETUP(CFE_SB_Subscribe(MsgId1, PipeId1)); + SETUP(CFE_SB_Subscribe(MsgId2, PipeId3)); + SETUP(CFE_SB_Subscribe(MsgId3, PipeId3)); + SETUP(CFE_SB_Subscribe(MsgId4, PipeId3)); + SETUP(CFE_SB_Subscribe(MsgId5, PipeId2)); - EVTCNT(4); + memset(&State, 0, sizeof(State)); + LocalBuffer = NULL; + LocalBufSize = 0; - EVTSENT(CFE_SB_PIPE_ADDED_EID); + ASSERT_TRUE(!CFE_SB_WriteMsgMapInfoDataGetter(&State, 0, &LocalBuffer, &LocalBufSize)); + UtAssert_NOT_NULL(LocalBuffer); + UtAssert_NONZERO(LocalBufSize); - EVTSENT(CFE_SB_FILEWRITE_ERR_EID); + ASSERT_TRUE(CFE_SB_WriteMsgMapInfoDataGetter(&State, CFE_PLATFORM_SB_MAX_MSG_IDS, &LocalBuffer, &LocalBufSize)); + UtAssert_NULL(LocalBuffer); + UtAssert_ZERO(LocalBufSize); TEARDOWN(CFE_SB_DeletePipe(PipeId1)); TEARDOWN(CFE_SB_DeletePipe(PipeId2)); TEARDOWN(CFE_SB_DeletePipe(PipeId3)); - -} /* end Test_SB_Cmds_PipeInfoWriteFail */ +} /* end Test_SB_MapInfoDataGetter */ /* -** Test write map information command using the default file name +** Test write map information command nominal path */ void Test_SB_Cmds_MapInfoDef(void) { @@ -816,9 +844,7 @@ void Test_SB_Cmds_MapInfoDef(void) CFE_SB_ProcessCmdPipePkt(&WriteMapInfo.SBBuf); - EVTCNT(11); - - EVTSENT(CFE_SB_SND_RTG_EID); + EVTCNT(10); EVTSENT(CFE_SB_PIPE_ADDED_EID); @@ -831,9 +857,9 @@ void Test_SB_Cmds_MapInfoDef(void) } /* end Test_SB_Cmds_MapInfoDef */ /* -** Test write map information command using a specified file name +** Test write map information command when already pending */ -void Test_SB_Cmds_MapInfoSpec(void) +void Test_SB_Cmds_MapInfoAlreadyPending(void) { union { @@ -844,6 +870,8 @@ void Test_SB_Cmds_MapInfoSpec(void) CFE_SB_MsgId_t MsgId = CFE_SB_ValueToMsgId(CFE_SB_CMD_MID); CFE_MSG_Size_t Size = sizeof(WriteMapInfo.Cmd); + UT_SetDefaultReturnValue(UT_KEY(CFE_FS_BackgroundFileDumpIsPending), true); + UT_SetDataBuffer(UT_KEY(CFE_MSG_GetMsgId), &MsgId, sizeof(MsgId), false); UT_SetDataBuffer(UT_KEY(CFE_MSG_GetSize), &Size, sizeof(Size), false); UT_SetDataBuffer(UT_KEY(CFE_MSG_GetFcnCode), &FcnCode, sizeof(FcnCode), false); @@ -855,83 +883,9 @@ void Test_SB_Cmds_MapInfoSpec(void) EVTCNT(1); - EVTSENT(CFE_SB_SND_RTG_EID); - -} /* end Test_SB_Cmds_MapInfoSpec */ - -/* -** Test write map information command with a file creation failure -*/ -void Test_SB_Cmds_MapInfoCreateFail(void) -{ - UT_SetDefaultReturnValue(UT_KEY(OS_OpenCreate), OS_ERROR); - ASSERT_EQ(CFE_SB_WriteMapInfo("MapTstFile"), CFE_SB_FILE_IO_ERR); - - EVTCNT(1); - EVTSENT(CFE_SB_SND_RTG_ERR1_EID); -} /* end Test_SB_Cmds_MapInfoCreateFail */ - -/* -** Test write map information command with a file header write failure -*/ -void Test_SB_Cmds_MapInfoHdrFail(void) -{ - UT_SetDeferredRetcode(UT_KEY(CFE_FS_WriteHeader), 1, -1); - ASSERT_EQ(CFE_SB_WriteMapInfo("MapTstFile"), CFE_SB_FILE_IO_ERR); - - EVTCNT(1); - - EVTSENT(CFE_SB_FILEWRITE_ERR_EID); - -} /* end Test_SB_Cmds_MapInfoHdrFail */ - -/* -** Test write map information command with a file write failure on -** the second write -*/ -void Test_SB_Cmds_MapInfoWriteFail(void) -{ - CFE_SB_PipeId_t PipeId1; - CFE_SB_PipeId_t PipeId2; - CFE_SB_PipeId_t PipeId3; - CFE_SB_MsgId_t MsgId0 = SB_UT_TLM_MID1; - CFE_SB_MsgId_t MsgId1 = SB_UT_TLM_MID2; - CFE_SB_MsgId_t MsgId2 = SB_UT_TLM_MID3; - CFE_SB_MsgId_t MsgId3 = SB_UT_TLM_MID4; - CFE_SB_MsgId_t MsgId4 = SB_UT_TLM_MID5; - CFE_SB_MsgId_t MsgId5 = SB_UT_TLM_MID6; - uint16 PipeDepth = 10; - - /* Create some map info */ - SETUP(CFE_SB_CreatePipe(&PipeId1, PipeDepth, "TestPipe1")); - SETUP(CFE_SB_CreatePipe(&PipeId2, PipeDepth, "TestPipe2")); - SETUP(CFE_SB_CreatePipe(&PipeId3, PipeDepth, "TestPipe3")); - SETUP(CFE_SB_Subscribe(MsgId0, PipeId1)); - SETUP(CFE_SB_Subscribe(MsgId0, PipeId2)); - SETUP(CFE_SB_Subscribe(MsgId1, PipeId1)); - SETUP(CFE_SB_Subscribe(MsgId2, PipeId3)); - SETUP(CFE_SB_Subscribe(MsgId3, PipeId3)); - SETUP(CFE_SB_Subscribe(MsgId4, PipeId3)); - SETUP(CFE_SB_Subscribe(MsgId5, PipeId2)); - UT_SetDeferredRetcode(UT_KEY(OS_write), 2, -1); - - ASSERT_EQ(CFE_SB_WriteMapInfo("MapTstFile"), CFE_SB_FILE_IO_ERR); - - EVTCNT(11); - - EVTSENT(CFE_SB_FILEWRITE_ERR_EID); - - EVTSENT(CFE_SB_PIPE_ADDED_EID); - - EVTSENT(CFE_SB_SUBSCRIPTION_RCVD_EID); - - TEARDOWN(CFE_SB_DeletePipe(PipeId1)); - TEARDOWN(CFE_SB_DeletePipe(PipeId2)); - TEARDOWN(CFE_SB_DeletePipe(PipeId3)); - -} /* end Test_SB_Cmds_MapInfoWriteFail */ +} /* end Test_SB_Cmds_MapInfoSpec */ /* ** Test command to enable a specific route using a valid route diff --git a/fsw/cfe-core/unit-test/sb_UT.h b/fsw/cfe-core/unit-test/sb_UT.h index 7ebff942d..2f09ea386 100644 --- a/fsw/cfe-core/unit-test/sb_UT.h +++ b/fsw/cfe-core/unit-test/sb_UT.h @@ -365,7 +365,7 @@ void Test_SB_Cmds_Stats(void); /*****************************************************************************/ /** -** \brief Test send routing information command using the default file name +** \brief Test send routing information command default/nominal path ** ** \par Description ** This function tests the send routing information command using the @@ -381,7 +381,7 @@ void Test_SB_Cmds_RoutingInfoDef(void); /*****************************************************************************/ /** -** \brief Test send routing information command using a specified file name +** \brief Test send routing information command with request already pending ** ** \par Description ** This function tests the send routing information command using a @@ -393,15 +393,11 @@ void Test_SB_Cmds_RoutingInfoDef(void); ** \returns ** This function does not return a value. ******************************************************************************/ -void Test_SB_Cmds_RoutingInfoSpec(void); +void Test_SB_Cmds_RoutingInfoAlreadyPending(void); /*****************************************************************************/ /** -** \brief Test send routing information command with a file creation failure -** -** \par Description -** This function tests the send routing information command with a file -** creation failure. +** \brief Test routing information data getter ** ** \par Assumptions, External Events, and Notes: ** None @@ -409,7 +405,7 @@ void Test_SB_Cmds_RoutingInfoSpec(void); ** \returns ** This function does not return a value. ******************************************************************************/ -void Test_SB_Cmds_RoutingInfoCreateFail(void); +void Test_SB_Cmds_RoutingInfoDataGetter(void); /*****************************************************************************/ /** @@ -650,41 +646,7 @@ void Test_GetPipeIdByName(void); /*****************************************************************************/ /** -** \brief Test send routing information command with a file header -** write failure -** -** \par Description -** This function tests the send routing information command with a file -** header write failure. -** -** \par Assumptions, External Events, and Notes: -** None -** -** \returns -** This function does not return a value. -******************************************************************************/ -void Test_SB_Cmds_RoutingInfoHdrFail(void); - -/*****************************************************************************/ -/** -** \brief Test send routing information command with a file header write -** failure on the second write -** -** \par Description -** This function tests the send routing information command with a file -** header write failure on the second write. -** -** \par Assumptions, External Events, and Notes: -** None -** -** \returns -** This function does not return a value. -******************************************************************************/ -void Test_SB_Cmds_RoutingInfoWriteFail(void); - -/*****************************************************************************/ -/** -** \brief Test send pipe information command using the default file name +** \brief Test send pipe information command default / nominal path ** ** \par Description ** This function tests the send pipe information command using the @@ -700,11 +662,7 @@ void Test_SB_Cmds_PipeInfoDef(void); /*****************************************************************************/ /** -** \brief Test send pipe information command using a specified file name -** -** \par Description -** This function tests the send pipe information command using a -** specified file name. +** \brief Test send pipe information command when already pending ** ** \par Assumptions, External Events, and Notes: ** None @@ -712,15 +670,11 @@ void Test_SB_Cmds_PipeInfoDef(void); ** \returns ** This function does not return a value. ******************************************************************************/ -void Test_SB_Cmds_PipeInfoSpec(void); +void Test_SB_Cmds_PipeInfoAlreadyPending(void); /*****************************************************************************/ /** -** \brief Test send pipe information command with a file creation failure -** -** \par Description -** This function tests the send pipe information command with a file -** creation failure. +** \brief Test pipe information data getter ** ** \par Assumptions, External Events, and Notes: ** None @@ -728,16 +682,11 @@ void Test_SB_Cmds_PipeInfoSpec(void); ** \returns ** This function does not return a value. ******************************************************************************/ -void Test_SB_Cmds_PipeInfoCreateFail(void); +void Test_SB_Cmds_PipeInfoDataGetter(void); /*****************************************************************************/ /** -** \brief Test send pipe information command with a file header -** write failure -** -** \par Description -** This function tests the send pipe information command with a file -** header write failure. +** \brief Test background file writer event handler ** ** \par Assumptions, External Events, and Notes: ** None @@ -745,28 +694,11 @@ void Test_SB_Cmds_PipeInfoCreateFail(void); ** \returns ** This function does not return a value. ******************************************************************************/ -void Test_SB_Cmds_PipeInfoHdrFail(void); +void Test_SB_Cmds_BackgroundFileWriteEvents(void); /*****************************************************************************/ /** -** \brief Test send pipe information command with a file write failure on -** the second write -** -** \par Description -** This function tests the send pipe information command with a file -** write failure on the second write. -** -** \par Assumptions, External Events, and Notes: -** None -** -** \returns -** This function does not return a value. -******************************************************************************/ -void Test_SB_Cmds_PipeInfoWriteFail(void); - -/*****************************************************************************/ -/** -** \brief Test send map information command using the default file name +** \brief Test send map information command using the defaults / nominal path ** ** \par Description ** This function tests the send map information command using the @@ -782,7 +714,7 @@ void Test_SB_Cmds_MapInfoDef(void); /*****************************************************************************/ /** -** \brief Test send map information command using a specified file name +** \brief Test send map information command when already pending ** ** \par Description ** This function tests the send map information command using a @@ -794,48 +726,11 @@ void Test_SB_Cmds_MapInfoDef(void); ** \returns ** This function does not return a value. ******************************************************************************/ -void Test_SB_Cmds_MapInfoSpec(void); +void Test_SB_Cmds_MapInfoAlreadyPending(void); /*****************************************************************************/ /** -** \brief Test send map information command with a file creation failure -** -** \par Description -** This function tests the send map information command with a file -** creation failure. -** -** \par Assumptions, External Events, and Notes: -** None -** -** \returns -** This function does not return a value. -******************************************************************************/ -void Test_SB_Cmds_MapInfoCreateFail(void); - -/*****************************************************************************/ -/** -** \brief Test send map information command with a file header write failure -** -** \par Description -** This function tests the send map information command with a file -** header write failure. -** -** \par Assumptions, External Events, and Notes: -** None -** -** \returns -** This function does not return a value. -******************************************************************************/ -void Test_SB_Cmds_MapInfoHdrFail(void); - -/*****************************************************************************/ -/** -** \brief Test send map information command with a file write failure on -** the second write -** -** \par Description -** This function tests the send map information command with a file -** write failure on the second write. +** \brief Test map information data getter function ** ** \par Assumptions, External Events, and Notes: ** None @@ -843,7 +738,7 @@ void Test_SB_Cmds_MapInfoHdrFail(void); ** \returns ** This function does not return a value. ******************************************************************************/ -void Test_SB_Cmds_MapInfoWriteFail(void); +void Test_SB_Cmds_MapInfoDataGetter(void); /*****************************************************************************/ /** diff --git a/fsw/cfe-core/unit-test/tbl_UT.c b/fsw/cfe-core/unit-test/tbl_UT.c index a26d524c5..156c560a6 100644 --- a/fsw/cfe-core/unit-test/tbl_UT.c +++ b/fsw/cfe-core/unit-test/tbl_UT.c @@ -1145,6 +1145,9 @@ void Test_CFE_TBL_DumpRegCmd(void) int q; CFE_TBL_DumpRegistryCmd_t DumpRegCmd; CFE_ES_AppId_t AppID; + size_t LocalSize; + void *LocalBuf; + bool IsEOF; /* Get the AppID being used for UT */ CFE_ES_GetAppID(&AppID); @@ -1157,77 +1160,122 @@ void Test_CFE_TBL_DumpRegCmd(void) CFE_TBL_Global.Registry[q].HeadOfAccessList = CFE_TBL_END_OF_LIST; } - /* Test with an error creating the dump file */ + /* Test command using the default dump file name (nominal path) */ UT_InitData(); - DumpRegCmd.Payload.DumpFilename[0] = '\0'; - UT_SetDefaultReturnValue(UT_KEY(OS_OpenCreate), OS_ERROR); + UT_SetDefaultReturnValue(UT_KEY(CFE_FS_BackgroundFileDumpIsPending), false); + strncpy(DumpRegCmd.Payload.DumpFilename, "X", sizeof(DumpRegCmd.Payload.DumpFilename) - 1); + DumpRegCmd.Payload.DumpFilename[sizeof(DumpRegCmd.Payload.DumpFilename) - 1] = '\0'; + UT_Report(__FILE__, __LINE__, + CFE_TBL_DumpRegistryCmd(&DumpRegCmd) == + CFE_TBL_INC_CMD_CTR, + "CFE_TBL_DumpRegistryCmd", + "Default dump file name"); + + /* Test command with the dump file already pending (max requests pending) */ + UT_SetDefaultReturnValue(UT_KEY(CFE_FS_BackgroundFileDumpIsPending), true); + UT_SetDefaultReturnValue(UT_KEY(CFE_FS_BackgroundFileDumpRequest), CFE_STATUS_REQUEST_ALREADY_PENDING); UT_Report(__FILE__, __LINE__, CFE_TBL_DumpRegistryCmd(&DumpRegCmd) == CFE_TBL_INC_ERR_CTR, "CFE_TBL_DumpRegistryCmd", - "Error creating dump file"); + "Dump file already pending (FS max requests)"); + UT_ResetState(UT_KEY(CFE_FS_BackgroundFileDumpRequest)); - /* Test with an error writing the cFE File header */ - UT_InitData(); - UT_SetDeferredRetcode(UT_KEY(CFE_FS_WriteHeader), 1, sizeof(CFE_FS_Header_t) - 1); + /* Test command with the dump file already pending (local) */ + UT_SetDefaultReturnValue(UT_KEY(CFE_FS_BackgroundFileDumpIsPending), false); + UT_SetDefaultReturnValue(UT_KEY(CFE_FS_BackgroundFileDumpRequest), CFE_STATUS_REQUEST_ALREADY_PENDING); UT_Report(__FILE__, __LINE__, CFE_TBL_DumpRegistryCmd(&DumpRegCmd) == CFE_TBL_INC_ERR_CTR, "CFE_TBL_DumpRegistryCmd", - "Error writing cFE File header"); + "Dump file already pending (local)"); + + /* Check event generators */ + UT_ClearEventHistory(); + CFE_TBL_Global.RegDumpState.FileExisted = true; + CFE_TBL_DumpRegistryEventHandler(&CFE_TBL_Global.RegDumpState, CFE_FS_FileWriteEvent_COMPLETE, CFE_SUCCESS, 10, 0, 1000); + UT_Report(__FILE__, __LINE__, + UT_EventIsInHistory(CFE_TBL_OVERWRITE_REG_DUMP_INF_EID), + "CFE_TBL_DumpRegistryEventHandler", + "Dump file created event (overwrite)"); + + UT_ClearEventHistory(); + CFE_TBL_Global.RegDumpState.FileExisted = false; + CFE_TBL_DumpRegistryEventHandler(&CFE_TBL_Global.RegDumpState, CFE_FS_FileWriteEvent_COMPLETE, CFE_SUCCESS, 10, 0, 1000); + UT_Report(__FILE__, __LINE__, + UT_EventIsInHistory(CFE_TBL_WRITE_REG_DUMP_INF_EID), + "CFE_TBL_DumpRegistryEventHandler", + "Dump file created event (new)"); + + UT_ClearEventHistory(); + CFE_TBL_DumpRegistryEventHandler(&CFE_TBL_Global.RegDumpState, CFE_FS_FileWriteEvent_RECORD_WRITE_ERROR, CFE_SUCCESS, 10, 10, 1000); + UT_Report(__FILE__, __LINE__, + UT_EventIsInHistory(CFE_TBL_WRITE_TBL_REG_ERR_EID), + "CFE_TBL_DumpRegistryEventHandler", + "Dump file record write error event"); + + UT_ClearEventHistory(); + CFE_TBL_DumpRegistryEventHandler(&CFE_TBL_Global.RegDumpState, CFE_FS_FileWriteEvent_HEADER_WRITE_ERROR, CFE_SUCCESS, 10, 10, 1000); + UT_Report(__FILE__, __LINE__, + UT_EventIsInHistory(CFE_TBL_WRITE_CFE_HDR_ERR_EID), + "CFE_TBL_DumpRegistryEventHandler", + "Dump file header write error event"); + + UT_ClearEventHistory(); + CFE_TBL_DumpRegistryEventHandler(&CFE_TBL_Global.RegDumpState, CFE_FS_FileWriteEvent_CREATE_ERROR, OS_ERROR, 10, 0, 0); + UT_Report(__FILE__, __LINE__, + UT_EventIsInHistory(CFE_TBL_CREATING_DUMP_FILE_ERR_EID), + "CFE_TBL_DumpRegistryEventHandler", + "Dump file created error event"); + + UT_ClearEventHistory(); + CFE_TBL_DumpRegistryEventHandler(&CFE_TBL_Global.RegDumpState, CFE_FS_FileWriteEvent_UNDEFINED, OS_ERROR, 0, 0, 0); + UT_Report(__FILE__, __LINE__, + UT_GetNumEventsSent() == 0, + "CFE_TBL_DumpRegistryEventHandler", + "Undefined event is ignored"); /* Test where the table is owned, the file doesn't already exist, and the * table is successfully dumped */ UT_InitData(); - UT_SetDeferredRetcode(UT_KEY(CFE_FS_WriteHeader), 10, sizeof(CFE_FS_Header_t)); CFE_TBL_Global.Registry[0].OwnerAppId = AppID; CFE_TBL_Global.Registry[0].HeadOfAccessList = CFE_TBL_END_OF_LIST; CFE_TBL_Global.Registry[1].OwnerAppId = CFE_TBL_NOT_OWNED; CFE_TBL_Global.Registry[0].LoadInProgress = CFE_TBL_NO_LOAD_IN_PROGRESS + 1; CFE_TBL_Global.Registry[0].DoubleBuffered = true; - UT_SetDeferredRetcode(UT_KEY(OS_OpenCreate), 1, OS_ERROR); - UT_Report(__FILE__, __LINE__, - CFE_TBL_DumpRegistryCmd(&DumpRegCmd) == - CFE_TBL_INC_CMD_CTR, - "CFE_TBL_DumpRegistryCmd", - "Table is owned, file didn't exist previously: successfully " - "dumped table"); - - /* Test where the file did exist previously and the table is successfully - * overwritten - */ - UT_InitData(); - CFE_TBL_Global.Registry[0].LoadInProgress = CFE_TBL_NO_LOAD_IN_PROGRESS; - UT_Report(__FILE__, __LINE__, - CFE_TBL_DumpRegistryCmd(&DumpRegCmd) == - CFE_TBL_INC_CMD_CTR, - "CFE_TBL_DumpRegistryCmd", - "File did exist previously: successfully overwritten table"); - - /* Test where the table is not owned, the OS write fails, resulting in an - * error writing to the registry - */ - UT_InitData(); - UT_SetDeferredRetcode(UT_KEY(OS_write), 1, sizeof(CFE_TBL_RegDumpRec_t) - 1); - CFE_TBL_Global.Registry[0].OwnerAppId = CFE_TBL_NOT_OWNED; - CFE_TBL_Global.Registry[0].HeadOfAccessList = 2; + LocalBuf = NULL; + LocalSize = 0; + IsEOF = CFE_TBL_DumpRegistryGetter(&CFE_TBL_Global.RegDumpState, 0, &LocalBuf, &LocalSize); + UT_Report(__FILE__, __LINE__, + !IsEOF, + "CFE_TBL_DumpRegistryGetter", + "Nominal, first record, not end of file"); + UtAssert_NOT_NULL(LocalBuf); + UtAssert_NONZERO(LocalSize); + + CFE_TBL_Global.Registry[CFE_PLATFORM_TBL_MAX_NUM_TABLES-1].OwnerAppId = CFE_TBL_NOT_OWNED; + CFE_TBL_Global.Registry[CFE_PLATFORM_TBL_MAX_NUM_TABLES-1].HeadOfAccessList = 2; CFE_TBL_Global.Handles[2].NextLink = CFE_TBL_END_OF_LIST; + LocalBuf = NULL; + LocalSize = 0; + IsEOF = CFE_TBL_DumpRegistryGetter(&CFE_TBL_Global.RegDumpState, CFE_PLATFORM_TBL_MAX_NUM_TABLES-1, &LocalBuf, &LocalSize); UT_Report(__FILE__, __LINE__, - CFE_TBL_DumpRegistryCmd(&DumpRegCmd) == - CFE_TBL_INC_ERR_CTR, - "CFE_TBL_DumpRegistryCmd", - "Table is not owned, OS_write fails: Error writing Registry"); + IsEOF, + "CFE_TBL_DumpRegistryGetter", + "Nominal, last record, multiple accessors, end of file"); + UtAssert_NOT_NULL(LocalBuf); + UtAssert_NONZERO(LocalSize); - /* Test using the default dump file name */ - UT_InitData(); - strncpy(DumpRegCmd.Payload.DumpFilename, "X", sizeof(DumpRegCmd.Payload.DumpFilename) - 1); - DumpRegCmd.Payload.DumpFilename[sizeof(DumpRegCmd.Payload.DumpFilename) - 1] = '\0'; + /* Test with record numb beyond EOF (should be ignored, return null) */ + IsEOF = CFE_TBL_DumpRegistryGetter(&CFE_TBL_Global.RegDumpState, CFE_PLATFORM_TBL_MAX_NUM_TABLES+1, &LocalBuf, &LocalSize); UT_Report(__FILE__, __LINE__, - CFE_TBL_DumpRegistryCmd(&DumpRegCmd) == - CFE_TBL_INC_CMD_CTR, - "CFE_TBL_DumpRegistryCmd", - "Default dump file name"); + IsEOF, + "CFE_TBL_DumpRegistryGetter", + "Past end of file"); + UtAssert_NULL(LocalBuf); + UtAssert_ZERO(LocalSize); + } /* diff --git a/fsw/cfe-core/ut-stubs/ut_es_stubs.c b/fsw/cfe-core/ut-stubs/ut_es_stubs.c index f10629b61..57b1bd105 100644 --- a/fsw/cfe-core/ut-stubs/ut_es_stubs.c +++ b/fsw/cfe-core/ut-stubs/ut_es_stubs.c @@ -1336,3 +1336,8 @@ int32 CFE_ES_TaskID_ToIndex(CFE_ES_TaskId_t TaskID, uint32 *Idx) return return_code; } + +void CFE_ES_BackgroundWakeup(void) +{ + UT_DEFAULT_IMPL(CFE_ES_BackgroundWakeup); +} diff --git a/fsw/cfe-core/ut-stubs/ut_fs_stubs.c b/fsw/cfe-core/ut-stubs/ut_fs_stubs.c index 126fe2d3a..f3bdaf3cd 100644 --- a/fsw/cfe-core/ut-stubs/ut_fs_stubs.c +++ b/fsw/cfe-core/ut-stubs/ut_fs_stubs.c @@ -304,3 +304,40 @@ int32 CFE_FS_ExtractFilenameFromPath(const char *OriginalPath, char *FileNameOnl return status; } + +bool CFE_FS_RunBackgroundFileDump(uint32 ElapsedTime, void *Arg) +{ + UT_Stub_RegisterContextGenericArg(UT_KEY(CFE_FS_RunBackgroundFileDump), ElapsedTime); + UT_Stub_RegisterContextGenericArg(UT_KEY(CFE_FS_RunBackgroundFileDump), Arg); + + int32 status; + + status = UT_DEFAULT_IMPL(CFE_FS_RunBackgroundFileDump); + + return status; +} + +int32 CFE_FS_BackgroundFileDumpRequest(CFE_FS_FileWriteMetaData_t *Meta) +{ + UT_Stub_RegisterContextGenericArg(UT_KEY(CFE_FS_BackgroundFileDumpRequest), Meta); + + int32 status; + + status = UT_DEFAULT_IMPL(CFE_FS_BackgroundFileDumpRequest); + + if (status == CFE_SUCCESS) + { + /* Snapshot the request, in case the UT test case wants to look */ + UT_Stub_CopyFromLocal(UT_KEY(CFE_FS_BackgroundFileDumpRequest), Meta, sizeof(*Meta)); + } + + return status; +} + +bool CFE_FS_BackgroundFileDumpIsPending(const CFE_FS_FileWriteMetaData_t *Meta) +{ + UT_Stub_RegisterContextGenericArg(UT_KEY(CFE_FS_BackgroundFileDumpIsPending), Meta); + + return UT_DEFAULT_IMPL(CFE_FS_BackgroundFileDumpIsPending); + +}