GCC Code Coverage Report


Directory: Svc/FileManager/
File: FileManager.cpp
Date: 2026-09-23 21:14:25
Exec Total Coverage
Lines: 315 350 90.0%
Functions: 19 20 95.0%
Branches: 282 342 82.5%

Line Branch Exec Source
1 // ======================================================================
2 // \title FileManager.hpp
3 // \author bocchino
4 // \brief hpp file for FileManager component implementation class
5 //
6 // \copyright
7 // Copyright 2009-2015, by the California Institute of Technology.
8 // ALL RIGHTS RESERVED. United States Government Sponsorship
9 // acknowledged.
10 //
11 // ======================================================================
12
13 #include <cstdio>
14 #include <cstdlib>
15
16 #include <Fw/FPrimeBasicTypes.hpp>
17 #include "Fw/Types/Assert.hpp"
18 #include "Fw/Types/ExternalString.hpp"
19 #include "Fw/Types/StringUtils.hpp"
20 #include "Os/Directory.hpp"
21 #include "Os/FilePathUtils.hpp"
22 #include "Svc/FileManager/FileManager.hpp"
23 #include "config/FileManagerConfig.hpp"
24
25 namespace Svc {
26
27 // ----------------------------------------------------------------------
28 // Construction, initialization, and destruction
29 // ----------------------------------------------------------------------
30
31 32 FileManager ::FileManager(const char* const compName //!< The component name
32 32 )
33 : FileManagerComponentBase(compName),
34 32 commandCount(0),
35 32 errorCount(0),
36
1/1
✓ Branch 4 taken 32 times.
32 m_sandboxDir(""),
37 32 m_sandboxConfigured(false),
38 32 m_listState(IDLE),
39 32 m_totalEntries(0),
40 32 m_currentOpCode(0),
41 32 m_currentCmdSeq(0),
42 32 m_runQueued(false),
43 32 m_dpState(DP_IDLE),
44 32 m_dpFileSize(0),
45 32 m_dpOffset(0),
46 32 m_dpChunkSize(0),
47 32 m_dpEndOffset(0),
48 32 m_dpPriority(0),
49 32 m_dpChunkCount(0),
50 32 m_dpOpCode(0),
51 32 m_dpCmdSeq(0),
52
4/4
✓ Branch 10 taken 32 times.
✓ Branch 16 taken 32 times.
✓ Branch 22 taken 32 times.
✓ Branch 28 taken 32 times.
128 m_dpBuffer{} {}
53
54 64 FileManager ::~FileManager() {}
55
56 37 void FileManager ::configure(const char* sandboxDir) {
57 37 FW_ASSERT(sandboxDir != nullptr);
58
59 // Resolve the sandbox directory (relative paths resolve against CWD)
60 37 char resolved[Os::FilePathUtils::MAX_PATH_LENGTH];
61 const Os::FilePathUtils::Status resolveStatus =
62
1/1
✓ Branch 1 taken 37 times.
37 Os::FilePathUtils::resolveFromCwd(sandboxDir, resolved, sizeof(resolved));
63 37 FW_ASSERT(resolveStatus == Os::FilePathUtils::VALID, static_cast<FwAssertArgType>(resolveStatus));
64
65 // Ensure trailing '/'
66
1/1
✓ Branch 1 taken 37 times.
37 const FwSizeType resolvedLen = Fw::StringUtils::string_length(resolved, Os::FilePathUtils::MAX_PATH_LENGTH);
67 37 FW_ASSERT(resolvedLen > 0);
68 37 FW_ASSERT(resolvedLen < Os::FilePathUtils::MAX_PATH_LENGTH);
69
2/2
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 35 times.
37 if (resolved[resolvedLen - 1] != '/') {
70 2 FW_ASSERT(resolvedLen + 2 <= Os::FilePathUtils::MAX_PATH_LENGTH);
71 2 resolved[resolvedLen] = '/';
72 2 resolved[resolvedLen + 1] = '\0';
73 }
74
75
1/1
✓ Branch 6 taken 37 times.
37 this->m_sandboxDir = resolved;
76 37 this->m_sandboxConfigured = true;
77 37 }
78
79 51 bool FileManager ::resolveInSandbox(const Fw::CmdStringArg& path,
80 Fw::FileNameString& resolved,
81 const FwOpcodeType opCode,
82 const U32 cmdSeq) {
83 51 bool accepted = false;
84
3/4
✗ Branch 3 not taken.
✓ Branch 4 taken 51 times.
✓ Branch 5 taken 50 times.
✓ Branch 6 taken 1 times.
51 if (this->m_sandboxConfigured) {
85 50 char resolvedBuffer[Os::FilePathUtils::MAX_PATH_LENGTH];
86 const Os::FilePathUtils::Status resolveStatus =
87
1/1
✓ Branch 5 taken 50 times.
50 Os::FilePathUtils::resolveFromCwd(path.toChar(), resolvedBuffer, sizeof(resolvedBuffer));
88
3/4
✓ Branch 0 taken 50 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 41 times.
✓ Branch 3 taken 9 times.
100 if ((resolveStatus == Os::FilePathUtils::VALID) &&
89
3/3
✓ Branch 7 taken 50 times.
✓ Branch 9 taken 41 times.
✓ Branch 10 taken 9 times.
50 (Os::FilePathUtils::checkContainment(resolvedBuffer, this->m_sandboxDir.toChar()) ==
90 Os::FilePathUtils::VALID)) {
91
1/1
✓ Branch 4 taken 41 times.
41 resolved = resolvedBuffer;
92 41 accepted = true;
93 }
94 }
95
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 41 times.
51 if (!accepted) {
96
2/2
✓ Branch 12 taken 10 times.
✓ Branch 16 taken 10 times.
20 this->log_WARNING_HI_PathOutsideSandbox(Fw::LogStringArg(path.toChar()),
97
1/1
✓ Branch 7 taken 10 times.
20 Fw::LogStringArg(this->m_sandboxDir.toChar()));
98 10 ++this->errorCount;
99
2/2
✓ Branch 6 taken 10 times.
✓ Branch 13 taken 10 times.
10 this->tlmWrite_Errors(this->errorCount);
100
2/2
✓ Branch 6 taken 10 times.
✓ Branch 9 taken 10 times.
10 this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::VALIDATION_ERROR);
101 }
102 51 return accepted;
103 }
104
105 // ----------------------------------------------------------------------
106 // Command handler implementations
107 // ----------------------------------------------------------------------
108
109 5 void FileManager ::CreateDirectory_cmdHandler(const FwOpcodeType opCode,
110 const U32 cmdSeq,
111 const Fw::CmdStringArg& dirName) {
112
1/1
✓ Branch 2 taken 5 times.
5 Fw::FileNameString resolvedDirName;
113
3/3
✓ Branch 4 taken 5 times.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 4 times.
5 if (!this->resolveInSandbox(dirName, resolvedDirName, opCode, cmdSeq)) {
114 1 return;
115 }
116
1/1
✓ Branch 6 taken 4 times.
4 Fw::LogStringArg logStringDirName(dirName.toChar());
117
1/1
✓ Branch 5 taken 4 times.
4 this->log_ACTIVITY_HI_CreateDirectoryStarted(logStringDirName);
118 4 bool errorIfDirExists = true;
119
1/1
✓ Branch 3 taken 4 times.
4 const Os::FileSystem::Status status = Os::FileSystem::createDirectory(resolvedDirName.toChar(), errorIfDirExists);
120
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 3 times.
4 if (status != Os::FileSystem::OP_OK) {
121
1/1
✓ Branch 5 taken 1 times.
1 this->log_WARNING_HI_DirectoryCreateError(logStringDirName, status);
122 } else {
123
1/1
✓ Branch 5 taken 3 times.
3 this->log_ACTIVITY_HI_CreateDirectorySucceeded(logStringDirName);
124 }
125
1/1
✓ Branch 4 taken 4 times.
4 this->emitTelemetry(status);
126
1/1
✓ Branch 4 taken 4 times.
4 this->sendCommandResponse(opCode, cmdSeq, status);
127 5 }
128
129 5 void FileManager ::RemoveFile_cmdHandler(const FwOpcodeType opCode,
130 const U32 cmdSeq,
131 const Fw::CmdStringArg& fileName,
132 const bool ignoreErrors) {
133
1/1
✓ Branch 2 taken 5 times.
5 Fw::FileNameString resolvedFileName;
134
3/3
✓ Branch 4 taken 5 times.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 3 times.
5 if (!this->resolveInSandbox(fileName, resolvedFileName, opCode, cmdSeq)) {
135 2 return;
136 }
137
1/1
✓ Branch 6 taken 3 times.
3 Fw::LogStringArg logStringFileName(fileName.toChar());
138
1/1
✓ Branch 5 taken 3 times.
3 this->log_ACTIVITY_HI_RemoveFileStarted(logStringFileName);
139
1/1
✓ Branch 3 taken 3 times.
3 const Os::FileSystem::Status status = Os::FileSystem::removeFile(resolvedFileName.toChar());
140
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
3 if (status != Os::FileSystem::OP_OK) {
141
1/1
✓ Branch 5 taken 1 times.
1 this->log_WARNING_HI_FileRemoveError(logStringFileName, status);
142
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (ignoreErrors == true) {
143 ✗ ++this->errorCount;
144 ✗ this->tlmWrite_Errors(this->errorCount);
145 ✗ this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
146 ✗ return;
147 }
148 } else {
149
1/1
✓ Branch 5 taken 2 times.
2 this->log_ACTIVITY_HI_RemoveFileSucceeded(logStringFileName);
150 }
151
1/1
✓ Branch 4 taken 3 times.
3 this->emitTelemetry(status);
152
1/1
✓ Branch 4 taken 3 times.
3 this->sendCommandResponse(opCode, cmdSeq, status);
153 5 }
154
155 4 void FileManager ::MoveFile_cmdHandler(const FwOpcodeType opCode,
156 const U32 cmdSeq,
157 const Fw::CmdStringArg& sourceFileName,
158 const Fw::CmdStringArg& destFileName) {
159
1/1
✓ Branch 2 taken 4 times.
4 Fw::FileNameString resolvedSource;
160
1/1
✓ Branch 2 taken 4 times.
4 Fw::FileNameString resolvedDest;
161
4/5
✓ Branch 4 taken 4 times.
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1 times.
✓ Branch 9 taken 3 times.
8 if (!this->resolveInSandbox(sourceFileName, resolvedSource, opCode, cmdSeq) ||
162
3/3
✓ Branch 4 taken 4 times.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 3 times.
4 !this->resolveInSandbox(destFileName, resolvedDest, opCode, cmdSeq)) {
163 1 return;
164 }
165
1/1
✓ Branch 6 taken 3 times.
3 Fw::LogStringArg logStringSource(sourceFileName.toChar());
166
1/1
✓ Branch 6 taken 3 times.
3 Fw::LogStringArg logStringDest(destFileName.toChar());
167
1/1
✓ Branch 5 taken 3 times.
3 this->log_ACTIVITY_HI_MoveFileStarted(logStringSource, logStringDest);
168
1/1
✓ Branch 5 taken 3 times.
3 const Os::FileSystem::Status status = Os::FileSystem::moveFile(resolvedSource.toChar(), resolvedDest.toChar());
169
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
3 if (status != Os::FileSystem::OP_OK) {
170
1/1
✓ Branch 5 taken 1 times.
1 this->log_WARNING_HI_FileMoveError(logStringSource, logStringDest, status);
171 } else {
172
1/1
✓ Branch 5 taken 2 times.
2 this->log_ACTIVITY_HI_MoveFileSucceeded(logStringSource, logStringDest);
173 }
174
1/1
✓ Branch 4 taken 3 times.
3 this->emitTelemetry(status);
175
1/1
✓ Branch 4 taken 3 times.
3 this->sendCommandResponse(opCode, cmdSeq, status);
176 5 }
177
178 3 void FileManager ::RemoveDirectory_cmdHandler(const FwOpcodeType opCode,
179 const U32 cmdSeq,
180 const Fw::CmdStringArg& dirName) {
181
1/1
✓ Branch 2 taken 3 times.
3 Fw::FileNameString resolvedDirName;
182
3/3
✓ Branch 4 taken 3 times.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 2 times.
3 if (!this->resolveInSandbox(dirName, resolvedDirName, opCode, cmdSeq)) {
183 1 return;
184 }
185
1/1
✓ Branch 6 taken 2 times.
2 Fw::LogStringArg logStringDirName(dirName.toChar());
186
1/1
✓ Branch 5 taken 2 times.
2 this->log_ACTIVITY_HI_RemoveDirectoryStarted(logStringDirName);
187
1/1
✓ Branch 3 taken 2 times.
2 const Os::FileSystem::Status status = Os::FileSystem::removeDirectory(resolvedDirName.toChar());
188
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (status != Os::FileSystem::OP_OK) {
189
1/1
✓ Branch 5 taken 1 times.
1 this->log_WARNING_HI_DirectoryRemoveError(logStringDirName, status);
190 } else {
191
1/1
✓ Branch 5 taken 1 times.
1 this->log_ACTIVITY_HI_RemoveDirectorySucceeded(logStringDirName);
192 }
193
1/1
✓ Branch 4 taken 2 times.
2 this->emitTelemetry(status);
194
1/1
✓ Branch 4 taken 2 times.
2 this->sendCommandResponse(opCode, cmdSeq, status);
195 3 }
196
197 4 void FileManager ::AppendFile_cmdHandler(const FwOpcodeType opCode,
198 const U32 cmdSeq,
199 const Fw::CmdStringArg& source,
200 const Fw::CmdStringArg& target) {
201
1/1
✓ Branch 2 taken 4 times.
4 Fw::FileNameString resolvedSource;
202
1/1
✓ Branch 2 taken 4 times.
4 Fw::FileNameString resolvedTarget;
203
5/5
✓ Branch 4 taken 4 times.
✓ Branch 6 taken 3 times.
✓ Branch 7 taken 1 times.
✓ Branch 8 taken 1 times.
✓ Branch 9 taken 3 times.
7 if (!this->resolveInSandbox(source, resolvedSource, opCode, cmdSeq) ||
204
2/3
✓ Branch 4 taken 3 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 3 times.
3 !this->resolveInSandbox(target, resolvedTarget, opCode, cmdSeq)) {
205 1 return;
206 }
207
1/1
✓ Branch 6 taken 3 times.
3 Fw::LogStringArg logStringSource(source.toChar());
208
1/1
✓ Branch 6 taken 3 times.
3 Fw::LogStringArg logStringTarget(target.toChar());
209
1/1
✓ Branch 5 taken 3 times.
3 this->log_ACTIVITY_HI_AppendFileStarted(logStringSource, logStringTarget);
210
211 Os::FileSystem::Status status;
212
1/1
✓ Branch 5 taken 3 times.
3 status = Os::FileSystem::appendFile(resolvedSource.toChar(), resolvedTarget.toChar(), true);
213
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
3 if (status != Os::FileSystem::OP_OK) {
214
1/1
✓ Branch 5 taken 1 times.
1 this->log_WARNING_HI_AppendFileFailed(logStringSource, logStringTarget, status);
215 } else {
216
1/1
✓ Branch 5 taken 2 times.
2 this->log_ACTIVITY_HI_AppendFileSucceeded(logStringSource, logStringTarget);
217 }
218
219
1/1
✓ Branch 4 taken 3 times.
3 this->emitTelemetry(status);
220
1/1
✓ Branch 4 taken 3 times.
3 this->sendCommandResponse(opCode, cmdSeq, status);
221 5 }
222
223 5 void FileManager ::FileSize_cmdHandler(const FwOpcodeType opCode, const U32 cmdSeq, const Fw::CmdStringArg& fileName) {
224
1/1
✓ Branch 2 taken 5 times.
5 Fw::FileNameString resolvedFileName;
225
3/3
✓ Branch 4 taken 5 times.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 4 times.
5 if (!this->resolveInSandbox(fileName, resolvedFileName, opCode, cmdSeq)) {
226 1 return;
227 }
228
1/1
✓ Branch 6 taken 4 times.
4 Fw::LogStringArg logStringFileName(fileName.toChar());
229
1/1
✓ Branch 5 taken 4 times.
4 this->log_ACTIVITY_HI_FileSizeStarted(logStringFileName);
230
231 4 FwSizeType size_arg;
232
1/1
✓ Branch 3 taken 4 times.
4 const Os::FileSystem::Status status = Os::FileSystem::getFileSize(resolvedFileName.toChar(), size_arg);
233
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 3 times.
4 if (status != Os::FileSystem::OP_OK) {
234
1/1
✓ Branch 5 taken 1 times.
1 this->log_WARNING_HI_FileSizeError(logStringFileName, status);
235 } else {
236
1/1
✓ Branch 5 taken 3 times.
3 this->log_ACTIVITY_HI_FileSizeSucceeded(logStringFileName, size_arg);
237 }
238
1/1
✓ Branch 4 taken 4 times.
4 this->emitTelemetry(status);
239
1/1
✓ Branch 4 taken 4 times.
4 this->sendCommandResponse(opCode, cmdSeq, status);
240 5 }
241
242 4 void FileManager ::ListDirectory_cmdHandler(const FwOpcodeType opCode,
243 const U32 cmdSeq,
244 const Fw::CmdStringArg& dirName) {
245 // Check if we're already listing a directory
246
2/4
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
4 if (m_listState == LISTING_IN_PROGRESS) {
247 ✗ this->log_WARNING_HI_ListDirectoryError(dirName, static_cast<U32>(Os::Directory::OTHER_ERROR));
248 ✗ this->emitTelemetry(Os::FileSystem::OTHER_ERROR);
249 ✗ this->sendCommandResponse(opCode, cmdSeq, Os::FileSystem::OTHER_ERROR);
250 ✗ return;
251 }
252
253
1/1
✓ Branch 2 taken 4 times.
4 Fw::FileNameString resolvedDirName;
254
3/3
✓ Branch 4 taken 4 times.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 3 times.
4 if (!this->resolveInSandbox(dirName, resolvedDirName, opCode, cmdSeq)) {
255 1 return;
256 }
257
258
1/1
✓ Branch 8 taken 3 times.
3 this->log_ACTIVITY_HI_ListDirectoryStarted(dirName);
259
260 // Open the directory for reading
261
1/1
✓ Branch 8 taken 3 times.
3 Os::Directory::Status status = m_currentDir.open(resolvedDirName.toChar(), Os::Directory::OpenMode::READ);
262
263
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
3 if (status != Os::Directory::OP_OK) {
264
1/1
✓ Branch 8 taken 1 times.
1 this->log_WARNING_HI_ListDirectoryError(dirName, static_cast<U32>(status));
265
1/1
✓ Branch 4 taken 1 times.
1 this->emitTelemetry(Os::FileSystem::OTHER_ERROR);
266
1/1
✓ Branch 4 taken 1 times.
1 this->sendCommandResponse(opCode, cmdSeq, Os::FileSystem::OTHER_ERROR);
267 1 return;
268 }
269
270 // Initialize state machine for asynchronous processing
271 2 m_listState = LISTING_IN_PROGRESS;
272
1/1
✓ Branch 9 taken 2 times.
2 m_currentDirName = dirName;
273 2 m_currentOpCode = opCode;
274 2 m_currentCmdSeq = cmdSeq;
275 2 m_totalEntries = 0;
276
277 // Directory listing will be processed asynchronously by the rate group.
278 // The schedIn_handler will process FILES_PER_RATE_TICK directory entries per rate tick to
279 // prevent event flooding while maintaining configurable performance.
280 // Command response will be sent when listing completes.
281 4 }
282
283 2 void FileManager ::CalculateCrc_cmdHandler(FwOpcodeType opCode, U32 cmdSeq, const Fw::CmdStringArg& filename) {
284
1/1
✓ Branch 2 taken 2 times.
2 Os::File file;
285 2 U32 crcValue = 0;
286
1/1
✓ Branch 2 taken 2 times.
2 Fw::FileNameString resolvedFilename;
287
3/3
✓ Branch 4 taken 2 times.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 1 times.
2 if (!this->resolveInSandbox(filename, resolvedFilename, opCode, cmdSeq)) {
288 1 return;
289 }
290
1/1
✓ Branch 8 taken 1 times.
1 this->log_ACTIVITY_HI_CalculateCrcStarted(filename);
291
292
1/1
✓ Branch 4 taken 1 times.
1 Os::File::Status status = file.open(resolvedFilename.toChar(), Os::File::OPEN_READ);
293
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (status == Os::File::OP_OK) {
294
1/1
✓ Branch 2 taken 1 times.
1 status = file.calculateCrc(crcValue);
295 }
296
297
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (status == Os::File::OP_OK) {
298
1/1
✓ Branch 8 taken 1 times.
1 this->log_ACTIVITY_HI_CalculateCrcSucceeded(filename, crcValue);
299
2/2
✓ Branch 6 taken 1 times.
✓ Branch 9 taken 1 times.
1 this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
300 } else {
301 ✗ this->log_WARNING_HI_CalculateCrcFailed(filename, status);
302 ✗ this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::EXECUTION_ERROR);
303 }
304
1/1
✓ Branch 2 taken 1 times.
1 file.close();
305 3 }
306
307 13 void FileManager ::GenerateDp_cmdHandler(FwOpcodeType opCode,
308 U32 cmdSeq,
309 const Fw::CmdStringArg& fileName,
310 U32 chunkSize,
311 U64 beginOffset,
312 U64 endOffset,
313 U32 priority,
314 const FileManager_GenerateDpMode& mode) {
315
1/1
✓ Branch 6 taken 13 times.
13 Fw::LogStringArg logFileName(fileName.toChar());
316
317 // Reject a second request while one is already running
318
3/4
✗ Branch 3 not taken.
✓ Branch 4 taken 13 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 12 times.
13 if (this->m_dpState != DP_IDLE) {
319
2/2
✓ Branch 6 taken 1 times.
✓ Branch 10 taken 1 times.
1 this->log_WARNING_HI_GenerateDpFailed(logFileName, FileManager_GenerateDpStage::BUSY, 0);
320
2/2
✓ Branch 6 taken 1 times.
✓ Branch 9 taken 1 times.
1 this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
321 1 return;
322 }
323
324 // Data products must be available
325
5/8
✓ Branch 5 taken 12 times.
✓ Branch 7 taken 12 times.
✗ Branch 8 not taken.
✓ Branch 14 taken 12 times.
✗ Branch 16 not taken.
✓ Branch 17 taken 12 times.
✗ Branch 18 not taken.
✓ Branch 19 taken 12 times.
12 if (!this->isConnected_productGetOut_OutputPort(0) || !this->isConnected_productSendOut_OutputPort(0)) {
326 ✗ this->log_WARNING_HI_GenerateDpBufferFailed(logFileName);
327 ✗ this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
328 ✗ return;
329 }
330
331 // Clamp the requested chunk size to the configured read buffer
332 12 U32 effectiveChunkSize = chunkSize;
333
4/4
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 10 times.
12 if ((effectiveChunkSize == 0) || (effectiveChunkSize > FileManagerConfig::GENERATE_DP_MAX_CHUNK_SIZE)) {
334 2 effectiveChunkSize = FileManagerConfig::GENERATE_DP_MAX_CHUNK_SIZE;
335 }
336
337
1/1
✓ Branch 2 taken 12 times.
12 Fw::FileNameString resolvedFileName;
338
3/3
✓ Branch 4 taken 12 times.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 11 times.
12 if (!this->resolveInSandbox(fileName, resolvedFileName, opCode, cmdSeq)) {
339 1 return;
340 }
341
342
1/1
✓ Branch 8 taken 11 times.
11 Os::File::Status status = this->m_dpFile.open(resolvedFileName.toChar(), Os::File::OPEN_READ);
343
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 10 times.
11 if (status != Os::File::OP_OK) {
344
2/2
✓ Branch 6 taken 1 times.
✓ Branch 10 taken 1 times.
1 this->log_WARNING_HI_GenerateDpFailed(logFileName, FileManager_GenerateDpStage::OPEN, static_cast<U32>(status));
345
2/2
✓ Branch 6 taken 1 times.
✓ Branch 9 taken 1 times.
1 this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
346 1 return;
347 }
348
349 10 FwSizeType fileSize = 0;
350
1/1
✓ Branch 6 taken 10 times.
10 status = this->m_dpFile.size(fileSize);
351
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (status != Os::File::OP_OK) {
352 ✗ this->m_dpFile.close();
353 ✗ this->log_WARNING_HI_GenerateDpFailed(logFileName, FileManager_GenerateDpStage::SIZE, static_cast<U32>(status));
354 ✗ this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
355 ✗ return;
356 }
357
358 // An end offset of zero, or one past the end of the file, means the end of
359 // the file. Ranges let an operator retransmit part of a file or spread the
360 // downlink over several commands.
361 10 U64 effectiveEnd = endOffset;
362
3/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
10 if ((effectiveEnd == 0) || (effectiveEnd > static_cast<U64>(fileSize))) {
363 8 effectiveEnd = static_cast<U64>(fileSize);
364 }
365
366 10 const bool emptyFile = (fileSize == 0);
367
5/6
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 8 times.
10 const bool badRange = (beginOffset > static_cast<U64>(fileSize)) || (!emptyFile && (beginOffset >= effectiveEnd));
368
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 9 times.
10 if (badRange) {
369
1/1
✓ Branch 6 taken 1 times.
1 this->m_dpFile.close();
370
1/1
✓ Branch 5 taken 1 times.
1 this->log_WARNING_HI_GenerateDpInvalidRange(logFileName, beginOffset, endOffset, static_cast<U64>(fileSize));
371
2/2
✓ Branch 6 taken 1 times.
✓ Branch 9 taken 1 times.
1 this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
372 1 return;
373 }
374
375 // Position the file at the start of the requested range
376
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 8 times.
9 if (beginOffset > 0) {
377
1/1
✓ Branch 6 taken 1 times.
1 status = this->m_dpFile.seek(static_cast<FwSignedSizeType>(beginOffset), Os::File::SeekType::ABSOLUTE);
378
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (status != Os::File::OP_OK) {
379 ✗ this->m_dpFile.close();
380 ✗ this->log_WARNING_HI_GenerateDpFailed(logFileName, FileManager_GenerateDpStage::SEEK,
381 static_cast<U32>(status));
382 ✗ this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
383 ✗ return;
384 }
385 }
386
387
2/2
✓ Branch 6 taken 9 times.
✓ Branch 15 taken 9 times.
9 this->m_dpFileName = Fw::String(fileName.toChar());
388 9 this->m_dpFileSize = fileSize;
389 9 this->m_dpOffset = beginOffset;
390 9 this->m_dpEndOffset = effectiveEnd;
391 9 this->m_dpChunkSize = effectiveChunkSize;
392 9 this->m_dpChunkCount = 0;
393 9 this->m_dpOpCode = opCode;
394 9 this->m_dpCmdSeq = cmdSeq;
395 // A priority of zero reverts to the configured default
396
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 8 times.
9 this->m_dpPriority = (priority == 0) ? static_cast<FwDpPriorityType>(FileManagerCfg::DEFAULT_DP_PRIORITY)
397 : static_cast<FwDpPriorityType>(priority);
398 9 this->m_dpState = DP_IN_PROGRESS;
399
400 // Report the number of bytes that will be written, which is the requested
401 // range rather than the size of the whole file
402
1/1
✓ Branch 13 taken 9 times.
9 this->log_ACTIVITY_HI_GenerateDpStarted(logFileName, this->m_dpEndOffset - this->m_dpOffset);
403
404 // An empty range produces no chunks, so complete immediately
405
2/2
✓ Branch 8 taken 1 times.
✓ Branch 9 taken 8 times.
9 if (this->m_dpOffset >= this->m_dpEndOffset) {
406
1/1
✓ Branch 9 taken 1 times.
1 this->log_ACTIVITY_HI_GenerateDpComplete(logFileName, this->m_dpChunkCount);
407
1/1
✓ Branch 4 taken 1 times.
1 this->finishDpGeneration();
408 1 return;
409 }
410
411 // In immediate mode the whole range is emitted here, so that a project that
412 // wants the file out quickly is not limited by the rate group. In paced
413 // mode the rate group meters the work out and the response is deferred.
414
2/2
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 6 times.
8 if (mode == FileManager_GenerateDpMode::IMMEDIATE) {
415
1/1
✓ Branch 4 taken 2 times.
2 this->processDpChunks(0);
416 }
417 17 }
418
419 19 void FileManager ::processDpChunks(U32 chunkLimit) {
420
1/1
✓ Branch 8 taken 19 times.
19 Fw::LogStringArg logFileName(this->m_dpFileName.toChar());
421
422 // A limit of zero means emit the whole remaining range in this call
423 19 const bool paced = (chunkLimit > 0);
424
425
4/4
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 28 times.
✓ Branch 2 taken 17 times.
✓ Branch 3 taken 11 times.
33 for (U32 chunk = 0; !paced || (chunk < chunkLimit); chunk++) {
426 // Number of bytes remaining in the requested range. The loop returns as
427 // soon as the range is exhausted, so this is always non-zero here.
428 22 const FwSizeType remaining = static_cast<FwSizeType>(this->m_dpEndOffset - this->m_dpOffset);
429
430 22 const FwSizeType requestedSize = (remaining < static_cast<FwSizeType>(this->m_dpChunkSize))
431 ? remaining
432 : static_cast<FwSizeType>(this->m_dpChunkSize);
433
434 // The file size is known, so a short read means the file changed underneath us
435 22 FwSizeType readSize = requestedSize;
436
1/1
✓ Branch 8 taken 22 times.
22 const Os::File::Status status = this->m_dpFile.read(this->m_dpBuffer, readSize);
437
2/4
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 22 times.
22 if ((status != Os::File::OP_OK) || (readSize != requestedSize)) {
438 ✗ this->log_WARNING_HI_GenerateDpFailed(logFileName, FileManager_GenerateDpStage::READ,
439 static_cast<U32>(status));
440 ✗ this->finishDpGeneration();
441 ✗ return;
442 }
443
444 // Request a container large enough for this chunk's header and data
445 22 const FwSizeType dpSize = SIZE_OF_FileChunkHeaderRecord_RECORD + SIZE_OF_FileChunkDataRecord_RECORD(readSize);
446
1/1
✓ Branch 2 taken 22 times.
22 DpContainer container;
447
1/1
✓ Branch 5 taken 22 times.
22 const Fw::Success::T dpStatus = this->dpGet_FileDpContainer(dpSize, container);
448
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 21 times.
22 if (dpStatus != Fw::Success::SUCCESS) {
449
1/1
✓ Branch 5 taken 1 times.
1 this->log_WARNING_HI_GenerateDpBufferFailed(logFileName);
450
1/1
✓ Branch 4 taken 1 times.
1 this->finishDpGeneration();
451 1 return;
452 }
453 21 container.setPriority(this->m_dpPriority);
454
455 // Each chunk is a metadata record followed by a data record, so that
456 // ground tools can reassemble the file from any number of containers
457
1/1
✓ Branch 9 taken 21 times.
21 const FileManager_FileChunkHeader header(this->m_dpFileName, this->m_dpOffset, static_cast<U32>(readSize));
458
1/1
✓ Branch 2 taken 21 times.
21 Fw::SerializeStatus serializeStatus = container.serializeRecord_FileChunkHeaderRecord(header);
459
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 1 times.
21 if (serializeStatus == Fw::FW_SERIALIZE_OK) {
460
1/1
✓ Branch 4 taken 20 times.
20 serializeStatus = container.serializeRecord_FileChunkDataRecord(this->m_dpBuffer, readSize);
461 }
462
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 20 times.
21 if (serializeStatus != Fw::FW_SERIALIZE_OK) {
463
2/2
✓ Branch 6 taken 1 times.
✓ Branch 10 taken 1 times.
1 this->log_WARNING_HI_GenerateDpFailed(logFileName, FileManager_GenerateDpStage::SERIALIZE,
464 static_cast<U32>(serializeStatus));
465
1/1
✓ Branch 4 taken 1 times.
1 this->finishDpGeneration();
466 1 return;
467 }
468
469
2/2
✓ Branch 7 taken 20 times.
✓ Branch 10 taken 20 times.
20 this->dpSend(container);
470
471 20 this->m_dpOffset += static_cast<U64>(readSize);
472 20 this->m_dpChunkCount++;
473
474 // Last chunk of the requested range
475
2/2
✓ Branch 8 taken 6 times.
✓ Branch 9 taken 14 times.
20 if (this->m_dpOffset >= this->m_dpEndOffset) {
476
1/1
✓ Branch 9 taken 6 times.
6 this->log_ACTIVITY_HI_GenerateDpComplete(logFileName, this->m_dpChunkCount);
477
1/1
✓ Branch 4 taken 6 times.
6 this->finishDpGeneration();
478 6 return;
479 }
480 29 }
481 19 }
482
483 9 void FileManager ::finishDpGeneration() {
484 9 this->m_dpFile.close();
485 9 this->m_dpState = DP_IDLE;
486 9 this->m_dpOffset = 0;
487 9 this->m_dpEndOffset = 0;
488 9 this->m_dpFileSize = 0;
489 // Failures emit a warning event but still respond with OK, so that a bad
490 // file name or a transient resource problem does not stop a whole sequence
491
2/2
✓ Branch 6 taken 9 times.
✓ Branch 17 taken 9 times.
9 this->cmdResponse_out(this->m_dpOpCode, this->m_dpCmdSeq, Fw::CmdResponse::OK);
492 9 }
493
494 ✗ void FileManager ::pingIn_handler(const FwIndexType portNum, U32 key) {
495 // return key
496 ✗ this->pingOut_out(0, key);
497 ✗ }
498
499 29 void FileManager ::schedIn_handler(const FwIndexType portNum, U32 context) {
500 29 bool isQueued = false;
501 // m_runQueued will be compared to isQueued (false). When equal (i.e. m_runQueued is false) the atomic will be
502 // set to true and the function will return true indicating that a run was successfully marked as queued and thus
503 // the internal handler should be invoked.
504 29 bool expects_enqueue = this->m_runQueued.compare_exchange_strong(isQueued, true);
505
1/2
✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
29 if (expects_enqueue) {
506
1/1
✓ Branch 5 taken 29 times.
29 this->run_internalInterfaceInvoke();
507 }
508 29 }
509
510 29 void FileManager ::run_internalInterfaceHandler() {
511 29 FW_ASSERT(this->m_runQueued);
512 29 this->m_runQueued = false; // Run is not queued anymore (we are running)
513 // Data product generation is paced the same way as directory listing
514
3/4
✗ Branch 3 not taken.
✓ Branch 4 taken 29 times.
✓ Branch 5 taken 17 times.
✓ Branch 6 taken 12 times.
29 if (this->m_dpState == DP_IN_PROGRESS) {
515 17 this->processDpChunks(FileManagerConfig::CHUNKS_PER_RATE_TICK);
516 }
517
518 // Only process if we're in the middle of a directory listing
519
3/4
✗ Branch 3 not taken.
✓ Branch 4 taken 29 times.
✓ Branch 5 taken 12 times.
✓ Branch 6 taken 17 times.
29 if (m_listState == LISTING_IN_PROGRESS) {
520 // Process multiple files per rate tick based on configuration
521
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 10 times.
22 for (U32 fileCount = 0; fileCount < Svc::FileManagerConfig::FILES_PER_RATE_TICK; fileCount++) {
522
1/1
✓ Branch 2 taken 12 times.
12 Fw::String filename;
523
1/1
✓ Branch 6 taken 12 times.
12 Os::Directory::Status status = m_currentDir.read(filename);
524
525
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 10 times.
12 if (status == Os::Directory::NO_MORE_FILES) {
526 // We're done listing - close directory and send response
527
1/1
✓ Branch 6 taken 2 times.
2 m_currentDir.close();
528 2 m_listState = IDLE;
529
530
1/1
✓ Branch 12 taken 2 times.
2 this->log_ACTIVITY_HI_ListDirectorySucceeded(m_currentDirName, m_totalEntries);
531
1/1
✓ Branch 4 taken 2 times.
2 this->emitTelemetry(Os::FileSystem::OP_OK);
532
1/1
✓ Branch 12 taken 2 times.
2 this->sendCommandResponse(m_currentOpCode, m_currentCmdSeq, Os::FileSystem::OP_OK);
533 2 break; // Exit the loop since we're done
534
535
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 } else if (status == Os::Directory::OP_OK) {
536 // Construct full path for type checking
537
1/1
✓ Branch 2 taken 10 times.
10 Fw::String fullPath;
538
1/1
✓ Branch 10 taken 10 times.
10 Fw::FormatStatus formatStatus = fullPath.format("%s/%s", m_currentDirName.toChar(), filename.toChar());
539
540 // Determine entry type
541 Os::FileSystem::PathType pathType = (formatStatus == Fw::FormatStatus::SUCCESS)
542
2/3
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 5 taken 10 times.
10 ? Os::FileSystem::getPathType(fullPath.toChar())
543 10 : Os::FileSystem::NOT_EXIST;
544
545
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (formatStatus != Fw::FormatStatus::SUCCESS) {
546 // Cannot determine the type of an entry whose path did not format
547 ✗ this->log_WARNING_HI_FileNameFormatError(filename,
548 static_cast<Fw::StringFormatStatus::T>(formatStatus));
549
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 4 times.
10 } else if (pathType == Os::FileSystem::FILE) {
550 // Regular file: get size and emit file event
551 6 FwSizeType fileSize;
552
1/1
✓ Branch 3 taken 6 times.
6 Os::FileSystem::Status sizeStatus = Os::FileSystem::getFileSize(fullPath.toChar(), fileSize);
553
2/3
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✓ Branch 10 taken 6 times.
6 this->log_ACTIVITY_HI_DirectoryListing(
554 m_currentDirName, filename,
555 (sizeStatus == Os::FileSystem::OP_OK) ? fileSize : static_cast<FwSizeType>(0));
556
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 } else if (pathType == Os::FileSystem::DIRECTORY) {
557 // Subdirectory: emit subdirectory event
558
1/1
✓ Branch 8 taken 4 times.
4 this->log_ACTIVITY_HI_DirectoryListingSubdir(m_currentDirName, filename);
559 } else {
560 // Special file or inaccessible: treat as file with 0 size
561 ✗ this->log_ACTIVITY_HI_DirectoryListing(m_currentDirName, filename, static_cast<FwSizeType>(0));
562 }
563
564 10 m_totalEntries++;
565
566 10 } else {
567 // Error reading directory - close and send error response
568 ✗ m_currentDir.close();
569 ✗ m_listState = IDLE;
570
571 ✗ this->log_WARNING_HI_ListDirectoryError(m_currentDirName, static_cast<U32>(status));
572 ✗ this->emitTelemetry(Os::FileSystem::OTHER_ERROR);
573 ✗ this->sendCommandResponse(m_currentOpCode, m_currentCmdSeq, Os::FileSystem::OTHER_ERROR);
574 ✗ break; // Exit the loop since we had an error
575 }
576 12 }
577 }
578 29 }
579
580 // ----------------------------------------------------------------------
581 // Helper methods
582 // ----------------------------------------------------------------------
583
584 22 void FileManager ::emitTelemetry(const Os::FileSystem::Status status) {
585
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 7 times.
22 if (status == Os::FileSystem::OP_OK) {
586 15 ++this->commandCount;
587
2/2
✓ Branch 6 taken 15 times.
✓ Branch 13 taken 15 times.
15 this->tlmWrite_CommandsExecuted(this->commandCount);
588 } else {
589 7 ++this->errorCount;
590
2/2
✓ Branch 6 taken 7 times.
✓ Branch 13 taken 7 times.
7 this->tlmWrite_Errors(this->errorCount);
591 }
592 22 }
593
594 22 void FileManager ::sendCommandResponse(const FwOpcodeType opCode,
595 const U32 cmdSeq,
596 const Os::FileSystem::Status status) {
597
4/4
✓ Branch 5 taken 15 times.
✓ Branch 6 taken 7 times.
✓ Branch 8 taken 22 times.
✓ Branch 11 taken 22 times.
22 this->cmdResponse_out(opCode, cmdSeq,
598 (status == Os::FileSystem::OP_OK) ? Fw::CmdResponse::OK : Fw::CmdResponse::EXECUTION_ERROR);
599 22 }
600
601 } // namespace Svc
602