GCC Code Coverage Report


Directory: ./
File: Svc/FileWorker/FileWorker.cpp
Date: 2026-09-23 21:11:01
Exec Total Coverage
Lines: 306 342 89.5%
Functions: 15 15 100.0%
Branches: 243 292 83.2%

Line Branch Exec Source
1 // ======================================================================
2 // \title FileWorker.cpp
3 // \author racheljt
4 // \brief cpp file for FileWorker component implementation class
5 // ======================================================================
6
7 #include "Svc/FileWorker/FileWorker.hpp"
8
9 namespace Svc {
10
11 // ----------------------------------------------------------------------
12 // Component construction and destruction
13 // ----------------------------------------------------------------------
14
15 22 FileWorker ::FileWorker(const char* const compName)
16 : FileWorkerComponentBase(compName),
17 22 m_state(FileWorkerState::FW_STATE_IDLE),
18 22 m_abort(false),
19 44 m_chunkSize(BLOCK_SIZE_BYTES) {}
20
21 22 void FileWorker ::configure(U64 chunkSize) {
22 22 FW_ASSERT(chunkSize > 0);
23 22 this->m_chunkSize = chunkSize;
24 22 }
25
26 44 FileWorker ::~FileWorker() {}
27
28 // ----------------------------------------------------------------------
29 // Handler implementations for typed input ports
30 // ----------------------------------------------------------------------
31
32 2 void FileWorker ::cancelIn_handler(FwIndexType portNum) {
33 2 this->m_abort.store(true, std::memory_order_relaxed);
34 2 }
35
36 10 void FileWorker ::readIn_handler(FwIndexType portNum, const Fw::StringBase& path, Fw::Buffer& buffer) {
37 // Validate inputs before processing file
38
3/3
✓ Branch 11 taken 10 times.
✓ Branch 13 taken 1 times.
✓ Branch 14 taken 9 times.
10 if (path.length() == 0) {
39
3/3
✓ Branch 6 taken 1 times.
✓ Branch 11 taken 1 times.
✓ Branch 15 taken 1 times.
1 this->log_WARNING_HI_InvalidInput(Fw::LogStringArg("readIn"), Fw::LogStringArg("empty path"));
40
1/1
✓ Branch 5 taken 1 times.
1 this->readDoneOut_out(0, FW_STATUS_INVALID_INPUT, 0);
41 1 return;
42 }
43 // The path must leave room for the hash-file extension appended by the CRC helpers; a port
44 // argument can legally be up to FileNameStringSize characters, which is too long.
45
3/3
✓ Branch 1 taken 9 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 7 times.
9 if (!FileWorker::pathFitsWithHashExtension(path)) {
46
3/3
✓ Branch 6 taken 2 times.
✓ Branch 11 taken 2 times.
✓ Branch 15 taken 2 times.
2 this->log_WARNING_HI_InvalidInput(Fw::LogStringArg("readIn"), Fw::LogStringArg("path too long"));
47
1/1
✓ Branch 5 taken 2 times.
2 this->readDoneOut_out(0, FW_STATUS_INVALID_INPUT, 0);
48 2 return;
49 }
50
3/3
✓ Branch 4 taken 7 times.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 6 times.
7 if (!buffer.isValid()) {
51
3/3
✓ Branch 6 taken 1 times.
✓ Branch 11 taken 1 times.
✓ Branch 15 taken 1 times.
1 this->log_WARNING_HI_InvalidInput(Fw::LogStringArg("readIn"), Fw::LogStringArg("invalid buffer"));
52
1/1
✓ Branch 5 taken 1 times.
1 this->readDoneOut_out(0, FW_STATUS_INVALID_INPUT, 0);
53 1 return;
54 }
55
56
1/1
✓ Branch 11 taken 6 times.
6 const char* const fileName = path.toChar();
57 6 FwSizeType fileSize = 0;
58
59
3/4
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 5 times.
6 if (this->m_state != FW_STATE_IDLE) {
60
2/3
✗ Branch 7 not taken.
✓ Branch 8 taken 1 times.
✓ Branch 10 taken 1 times.
1 this->log_WARNING_HI_NotInIdle(this->m_state);
61
1/1
✓ Branch 5 taken 1 times.
1 this->readDoneOut_out(0, FW_STATUS_NOT_IDLE, 0);
62 1 return;
63 }
64
65 // New read request overrides any leftover abort state
66 5 this->m_abort.store(false, std::memory_order_relaxed);
67
68 5 this->m_state = FW_STATE_READING;
69
70 // Check CRC
71 5 U32 crcFromFile = 0;
72 5 U32 crcCalculated = 0;
73
1/1
✓ Branch 1 taken 5 times.
5 Utils::crc_stat_t crcStat = Utils::verify_checksum(fileName, crcFromFile, crcCalculated);
74
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
5 if (crcStat != Utils::PASSED_FILE_CRC_CHECK) {
75
1/1
✓ Branch 5 taken 3 times.
3 this->log_WARNING_HI_CrcFailed(crcStat);
76
1/1
✓ Branch 5 taken 3 times.
3 this->readDoneOut_out(0, FW_STATUS_FAILED_CRC, 0);
77 3 this->m_state = FW_STATE_IDLE;
78 3 return;
79 }
80
81 // Get filesize
82
1/1
✓ Branch 1 taken 2 times.
2 Os::FileSystem::Status fsStat = Os::FileSystem::getFileSize(fileName, fileSize);
83
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (fsStat != Os::FileSystem::OP_OK) {
84 // Path is ground-controlled and the file may change between the CRC check and here
85 ✗ this->log_WARNING_HI_ReadFailedFileSize(fsStat);
86 ✗ this->readDoneOut_out(0, FW_STATUS_FAILED_FILE_SIZE, 0);
87 ✗ this->m_state = FW_STATE_IDLE;
88 ✗ return;
89 }
90
91 // Start reading
92
1/1
✓ Branch 4 taken 2 times.
2 FileWorkerStatus workerStat = this->readBufferFromFile(buffer, fileName);
93
94 // Report 0 bytes on a failed or aborted read so readDoneOut does not imply success.
95
3/4
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 10 taken 2 times.
✓ Branch 13 taken 2 times.
2 this->readDoneOut_out(0, workerStat, (workerStat == FW_STATUS_DONE_READ) ? buffer.getSize() : 0);
96 2 this->m_state = FW_STATE_IDLE;
97 }
98
99 10 void FileWorker ::verifyIn_handler(FwIndexType portNum, const Fw::StringBase& path, U32 crc) {
100 // Validate inputs before processing file
101
3/3
✓ Branch 11 taken 10 times.
✓ Branch 13 taken 1 times.
✓ Branch 14 taken 9 times.
10 if (path.length() == 0) {
102
3/3
✓ Branch 6 taken 1 times.
✓ Branch 11 taken 1 times.
✓ Branch 15 taken 1 times.
1 this->log_WARNING_HI_InvalidInput(Fw::LogStringArg("verifyIn"), Fw::LogStringArg("empty path"));
103
1/1
✓ Branch 5 taken 1 times.
1 this->verifyDoneOut_out(0, FW_STATUS_INVALID_INPUT, 0);
104 1 return;
105 }
106 // The path must leave room for the hash-file extension appended by the CRC helpers; a port
107 // argument can legally be up to FileNameStringSize characters, which is too long.
108
3/3
✓ Branch 1 taken 9 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 7 times.
9 if (!FileWorker::pathFitsWithHashExtension(path)) {
109
3/3
✓ Branch 6 taken 2 times.
✓ Branch 11 taken 2 times.
✓ Branch 15 taken 2 times.
2 this->log_WARNING_HI_InvalidInput(Fw::LogStringArg("verifyIn"), Fw::LogStringArg("path too long"));
110
1/1
✓ Branch 5 taken 2 times.
2 this->verifyDoneOut_out(0, FW_STATUS_INVALID_INPUT, 0);
111 2 return;
112 }
113
114
1/1
✓ Branch 11 taken 7 times.
7 const char* const fileName = path.toChar();
115 7 FwSizeType fileSize = 0;
116 7 FileWorkerStatus workerStat = FW_STATUS_DONE;
117
118 7 U32 crcFromFile = 0;
119 7 U32 crcCalculated = 0;
120
1/1
✓ Branch 1 taken 7 times.
7 Utils::crc_stat_t crcStat = Utils::verify_checksum(fileName, crcFromFile, crcCalculated);
121
122
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 4 times.
7 if (crcStat != Utils::PASSED_FILE_CRC_CHECK) {
123
1/1
✓ Branch 5 taken 3 times.
3 this->log_WARNING_HI_CrcFailed(crcStat);
124 3 workerStat = FW_STATUS_FAILED_CRC;
125 }
126
127
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 5 times.
7 if (crc != crcCalculated) {
128 2 workerStat = FW_STATUS_FAILED_CRC;
129
1/1
✓ Branch 5 taken 2 times.
2 this->log_WARNING_LO_CrcVerificationError(crc, crcCalculated);
130 }
131
132 // Get filesize
133
1/1
✓ Branch 1 taken 7 times.
7 Os::FileSystem::Status fsStat = Os::FileSystem::getFileSize(fileName, fileSize);
134
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 5 times.
7 if (fsStat != Os::FileSystem::OP_OK) {
135
1/1
✓ Branch 5 taken 2 times.
2 this->log_WARNING_HI_ReadFailedFileSize(fsStat);
136 2 workerStat = FW_STATUS_FAILED_FILE_SIZE;
137 }
138
139
1/1
✓ Branch 5 taken 7 times.
7 this->verifyDoneOut_out(0, workerStat, fileSize);
140 }
141
142 15 void FileWorker ::writeIn_handler(FwIndexType portNum,
143 const Fw::StringBase& path,
144 Fw::Buffer& buffer,
145 FwSizeType offsetBytes,
146 bool append) {
147 // Validate inputs before processing file
148
3/3
✓ Branch 11 taken 15 times.
✓ Branch 13 taken 1 times.
✓ Branch 14 taken 14 times.
15 if (path.length() == 0) {
149
3/3
✓ Branch 6 taken 1 times.
✓ Branch 11 taken 1 times.
✓ Branch 15 taken 1 times.
1 this->log_WARNING_HI_InvalidInput(Fw::LogStringArg("writeIn"), Fw::LogStringArg("empty path"));
150
1/1
✓ Branch 5 taken 1 times.
1 this->writeDoneOut_out(0, FW_STATUS_INVALID_INPUT, 0);
151 1 return;
152 }
153 // The path must leave room for the hash-file extension appended by the CRC helpers; a port
154 // argument can legally be up to FileNameStringSize characters, which is too long.
155
3/3
✓ Branch 1 taken 14 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 12 times.
14 if (!FileWorker::pathFitsWithHashExtension(path)) {
156
3/3
✓ Branch 6 taken 2 times.
✓ Branch 11 taken 2 times.
✓ Branch 15 taken 2 times.
2 this->log_WARNING_HI_InvalidInput(Fw::LogStringArg("writeIn"), Fw::LogStringArg("path too long"));
157
1/1
✓ Branch 5 taken 2 times.
2 this->writeDoneOut_out(0, FW_STATUS_INVALID_INPUT, 0);
158 2 return;
159 }
160
3/3
✓ Branch 4 taken 12 times.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 11 times.
12 if (!buffer.isValid()) {
161
3/3
✓ Branch 6 taken 1 times.
✓ Branch 11 taken 1 times.
✓ Branch 15 taken 1 times.
1 this->log_WARNING_HI_InvalidInput(Fw::LogStringArg("writeIn"), Fw::LogStringArg("invalid buffer"));
162
1/1
✓ Branch 5 taken 1 times.
1 this->writeDoneOut_out(0, FW_STATUS_INVALID_INPUT, 0);
163 1 return;
164 }
165
3/3
✓ Branch 4 taken 11 times.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 10 times.
11 if (offsetBytes > buffer.getSize()) {
166
3/3
✓ Branch 6 taken 1 times.
✓ Branch 11 taken 1 times.
✓ Branch 15 taken 1 times.
1 this->log_WARNING_HI_InvalidInput(Fw::LogStringArg("writeIn"), Fw::LogStringArg("invalid offset"));
167
1/1
✓ Branch 5 taken 1 times.
1 this->writeDoneOut_out(0, FW_STATUS_INVALID_INPUT, 0);
168 1 return;
169 }
170
171 10 char fileName[FileNameStringSize];
172
173 // Make sure we are in IDLE state before proceeding
174
3/4
✗ Branch 3 not taken.
✓ Branch 4 taken 10 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 9 times.
10 if (this->m_state != FW_STATE_IDLE) {
175
2/3
✗ Branch 7 not taken.
✓ Branch 8 taken 1 times.
✓ Branch 10 taken 1 times.
1 this->log_WARNING_HI_NotInIdle(this->m_state);
176
1/1
✓ Branch 5 taken 1 times.
1 this->writeDoneOut_out(0, FW_STATUS_NOT_IDLE, 0);
177 1 return;
178 }
179
180 9 this->m_state = FW_STATE_WRITING;
181
182 // New write request overrides any leftover abort state
183 9 this->m_abort.store(false, std::memory_order_relaxed);
184
185 // Save file name
186 // NB: may count null terminator due to FPRIME/fprime-sw#57, but should still be less than FileNameStringSize in any
187 // case
188
2/2
✓ Branch 11 taken 9 times.
✓ Branch 14 taken 9 times.
9 FwSizeType length = Fw::StringUtils::string_length(path.toChar(), FileNameStringSize);
189
2/4
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 9 times.
9 if (length >= FileNameStringSize || length >= sizeof(fileName)) {
190 // Path length is ground-controlled, so an oversized path is invalid input, not a coding error.
191 ✗ this->log_WARNING_HI_InvalidInput(Fw::LogStringArg("writeIn"), Fw::LogStringArg("path too long"));
192 ✗ this->writeDoneOut_out(0, FW_STATUS_INVALID_INPUT, 0);
193 ✗ this->m_state = FW_STATE_IDLE;
194 ✗ return;
195 }
196
197
2/2
✓ Branch 11 taken 9 times.
✓ Branch 14 taken 9 times.
9 (void)Fw::StringUtils::string_copy(fileName, path.toChar(), sizeof(fileName));
198 9 fileName[sizeof(fileName) - 1] = 0; // guarantee termination
199
200 // Write
201
1/1
✓ Branch 4 taken 9 times.
9 const bool isWrite = this->writeBufferToFile(buffer, fileName, offsetBytes, append);
202
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 1 times.
9 if (isWrite) {
203
1/1
✓ Branch 4 taken 8 times.
8 this->writeBufferHashToFile(buffer, fileName, offsetBytes, append);
204 }
205
206 // Report the actual outcome of the write. A failed writeBufferToFile (open
207 // failure, permission denied, disk full, partial write) must not be reported
208 // to ground as a successful FW_STATUS_DONE_WRITE.
209
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 1 times.
9 const FileWorkerStatus writeStatus = isWrite ? FW_STATUS_DONE_WRITE : FW_STATUS_FAILED_TO_WRITE;
210 // Report bytes actually written, which excludes the skipped offset. Reporting the full
211 // buffer size over-reports by offsetBytes, and reports a whole buffer for a zero-length
212 // write. offsetBytes <= buffer.getSize() is checked above, so this cannot underflow.
213
1/1
✓ Branch 4 taken 9 times.
9 const FwSizeType writtenBytes = buffer.getSize() - offsetBytes;
214
3/3
✓ Branch 4 taken 8 times.
✓ Branch 5 taken 1 times.
✓ Branch 7 taken 9 times.
9 this->writeDoneOut_out(0, writeStatus, isWrite ? writtenBytes : 0);
215 9 this->m_state = FW_STATE_IDLE;
216 9 return;
217 }
218
219 // ----------------------------------------------------------------------
220 // Helper functions
221 // ----------------------------------------------------------------------
222
223 32 bool FileWorker ::pathFitsWithHashExtension(const Fw::StringBase& path) {
224 // Fw::FileNameString holds FileNameStringSize characters plus the terminator
225 32 return (path.length() + Utils::Hash::getFileExtensionLength()) <= FileNameStringSize;
226 }
227
228 6 Svc ::FileWorkerStatus FileWorker ::readBufferFromFile(Fw::Buffer& buffer, const char* const fileName) {
229 6 FW_ASSERT(buffer.getData() != nullptr);
230 6 FW_ASSERT(fileName != nullptr);
231
232
1/1
✓ Branch 2 taken 6 times.
6 Fw::LogStringArg fileNameStr(fileName);
233
1/1
✓ Branch 2 taken 6 times.
6 Os::File file;
234
235 // Open file
236
1/1
✓ Branch 2 taken 6 times.
6 Os::File::Status fileStat = file.open(fileName, Os::File::OPEN_READ);
237
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 5 times.
6 if (fileStat != Os::File::OP_OK) {
238
1/1
✓ Branch 5 taken 1 times.
1 this->log_WARNING_HI_OpenFileError(fileNameStr, fileStat);
239 1 return FW_STATUS_FAILED_TO_OPEN;
240 }
241
242 // Get buffer data and size
243
1/1
✓ Branch 4 taken 5 times.
5 FwSizeType readSize = buffer.getSize();
244
245 // Read file
246
1/1
✓ Branch 5 taken 5 times.
5 this->log_ACTIVITY_LO_ReadBegin(readSize, fileNameStr);
247
1/1
✓ Branch 4 taken 5 times.
5 const FileWorkerReadStatus readStat = this->readFile(buffer, readSize, file, fileNameStr);
248
249
1/1
✓ Branch 5 taken 5 times.
5 this->log_ACTIVITY_LO_ReadCompleted(readSize, fileNameStr);
250
1/1
✓ Branch 2 taken 5 times.
5 file.close();
251
252 // Only a completed read is DONE_READ; error, abort, or timeout reports FAILED_TO_READ.
253
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
5 return (readStat == FileWorkerReadStatus::FW_READ_DONE) ? FileWorkerStatus::FW_STATUS_DONE_READ
254 5 : FileWorkerStatus::FW_STATUS_FAILED_TO_READ;
255 6 }
256
257 8 Svc ::FileWorkerReadStatus FileWorker ::readFile(Fw::Buffer& buffer,
258 FwSizeType size,
259 Os::File& file,
260 const Fw::LogStringArg& fileNameStr) {
261 8 FW_ASSERT(buffer.getData() != nullptr);
262 8 FW_ASSERT(size > 0);
263 8 FW_ASSERT(fileNameStr != nullptr);
264
265 8 FwSizeType bytesRead = 0;
266 8 FwSizeType numChunks = 0;
267 8 U64 timeout = 0;
268
269
3/3
✓ Branch 4 taken 8 times.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 7 times.
8 if (!file.isOpen()) {
270 1 return FileWorkerReadStatus::FW_READ_ERROR;
271 }
272
273
1/1
✓ Branch 4 taken 7 times.
7 FileWorkerReadStatus readStat = this->readFileBytes(buffer, size, file, bytesRead);
274
275
3/6
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
7 switch (readStat) {
276 2 case FW_READ_ERROR:
277 // Some read error
278
1/1
✓ Branch 8 taken 2 times.
2 this->log_WARNING_HI_ReadError(bytesRead, size, fileNameStr);
279 2 break;
280
281 3 case FW_READ_DONE:
282 3 break;
283
284 2 case FW_READ_ABORT:
285 // Abort command was sent
286
1/1
✓ Branch 8 taken 2 times.
2 this->log_WARNING_LO_ReadAborted(bytesRead, size, fileNameStr);
287 2 break;
288
289 ✗ case FW_READ_TIMEOUT:
290 // Determine true timeout
291 ✗ FW_ASSERT(this->m_chunkSize > 0);
292 ✗ numChunks = (size / this->m_chunkSize);
293 ✗ if (size % this->m_chunkSize > 0) {
294 ✗ numChunks += 1;
295 }
296 ✗ timeout = numChunks * TIMEOUT_MS;
297 ✗ this->log_WARNING_HI_ReadTimeout(bytesRead, size, fileNameStr, timeout);
298 ✗ break;
299
300 ✗ case FW_READ_UNKNOWN:
301 // The read loop ran out of iterations: a read larger than
302 // MAX_LOOP_ITERATIONS * BLOCK_SIZE_BYTES cannot complete
303 ✗ this->log_WARNING_HI_ReadError(bytesRead, size, fileNameStr);
304 ✗ break;
305
306 ✗ default:
307 ✗ FW_ASSERT(false, static_cast<FwAssertArgType>(readStat));
308 ✗ break;
309 }
310
311 7 return readStat;
312 }
313
314 10 Svc ::FileWorkerReadStatus FileWorker ::readFileBytes(Fw::Buffer& buffer,
315 FwSizeType size,
316 Os::File& file,
317 FwSizeType& bytesRead) {
318 10 FW_ASSERT(buffer.getData() != nullptr);
319 10 FW_ASSERT(size > 0);
320
321 // Determine true timeout
322 10 FW_ASSERT(this->m_chunkSize > 0);
323
1/2
✗ Branch 4 not taken.
✓ Branch 5 taken 10 times.
10 FwSizeType numChunks = (size / this->m_chunkSize);
324
3/4
✗ Branch 4 not taken.
✓ Branch 5 taken 10 times.
✓ Branch 6 taken 4 times.
✓ Branch 7 taken 6 times.
10 if (size % this->m_chunkSize > 0) {
325 4 numChunks += 1;
326 }
327 10 U64 timeout = numChunks * TIMEOUT_MS;
328
329 // Read loop
330 10 bytesRead = 0;
331
1/1
✓ Branch 3 taken 10 times.
10 Fw::Time start = this->getTime();
332
333
1/2
✓ Branch 0 taken 1053 times.
✗ Branch 1 not taken.
1053 for (FwSizeType i = 0; i < numChunks; i++) {
334 1053 FwSizeType readAmt = FW_MIN(size - bytesRead, this->m_chunkSize);
335 1053 FwSizeType readAmtActual = readAmt;
336
2/2
✓ Branch 7 taken 1053 times.
✓ Branch 12 taken 1053 times.
1053 Os::File::Status ret = file.read(buffer.getData() + bytesRead, readAmtActual);
337
338
3/4
✓ Branch 0 taken 1050 times.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1050 times.
1053 if (Os::File::OP_OK != ret || readAmt != readAmtActual) {
339 // Count the bytes actually transferred so ReadError telemetry reports
340 // the true amount. A short read stays an error on purpose: FileWorker
341 // reads a fixed, caller-specified size and must not silently accept a
342 // file shorter than expected (e.g. truncated mid-read).
343 3 bytesRead += readAmtActual;
344 3 return FileWorkerReadStatus::FW_READ_ERROR;
345 }
346
347 1050 bool currAbort = this->m_abort.load(std::memory_order_relaxed);
348
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1047 times.
1050 if (currAbort) {
349 // Abort command was sent
350 3 return FileWorkerReadStatus::FW_READ_ABORT;
351 }
352
353
1/2
✓ Branch 0 taken 1047 times.
✗ Branch 1 not taken.
1047 if (timeout > 0) {
354 // Only check timeout if > 0
355
1/1
✓ Branch 3 taken 1047 times.
1047 Fw::Time now = this->getTime();
356
1/1
✓ Branch 2 taken 1047 times.
1047 Fw::Time diff = Fw::Time::sub(now, start);
357
2/2
✓ Branch 2 taken 1047 times.
✓ Branch 6 taken 1047 times.
1047 U64 elapsed = (diff.getSeconds() * 1000000) + diff.getUSeconds();
358
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1047 times.
1047 if (elapsed >= timeout) {
359 ✗ return FileWorkerReadStatus::FW_READ_TIMEOUT;
360 }
361 1047 }
362
363 1047 bytesRead += readAmt;
364
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 1043 times.
1047 if (bytesRead >= size) {
365 // Finished, break out
366 4 return FileWorkerReadStatus::FW_READ_DONE;
367 }
368 }
369
370 ✗ return FileWorkerReadStatus::FW_READ_UNKNOWN;
371 10 }
372
373 8 bool FileWorker ::getHash(const char* const hashFileName,
374 Utils::Hash& hash,
375 Utils::HashBuffer& hashBuffer,
376 const U8* const data,
377 const FwSizeType size) {
378 8 FW_ASSERT(hashFileName != nullptr);
379 8 FW_ASSERT(data != nullptr);
380 8 FW_ASSERT(size > 0);
381
382 // Open file
383
1/1
✓ Branch 2 taken 8 times.
8 Os::File file;
384
1/1
✓ Branch 2 taken 8 times.
8 Os::File::Status stat = file.open(hashFileName, Os::File::OPEN_READ);
385
386 // Read value if it exists
387
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 4 times.
8 if (stat == Os::File::OP_OK) {
388 4 HASH_HANDLE_TYPE hashValue;
389 4 FwSizeType hashSize = sizeof(hashValue);
390 4 U8* hashValuePtr = reinterpret_cast<U8*>(&hashValue);
391 4 FW_ASSERT(hashValuePtr != nullptr);
392
393
1/1
✓ Branch 2 taken 4 times.
4 Os::File::Status readStat = file.read(hashValuePtr, hashSize);
394
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 3 times.
4 if (readStat != Os::File::OP_OK) {
395
1/1
✓ Branch 2 taken 1 times.
1 Fw::LogStringArg s(hashFileName);
396
1/1
✓ Branch 5 taken 1 times.
1 this->log_WARNING_HI_WriteValidationReadError(s, readStat);
397 1 return false;
398 1 }
399
1/1
✓ Branch 2 taken 3 times.
3 Utils::HashBuffer tmp(hashValuePtr, hashSize);
400
1/1
✓ Branch 2 taken 3 times.
3 hash.setHashValue(tmp);
401
1/1
✓ Branch 2 taken 3 times.
3 hash.update(data, size);
402
1/1
✓ Branch 2 taken 3 times.
3 hash.finalize(hashBuffer);
403
404
2/2
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 1 times.
7 } else if (stat == Os::File::DOESNT_EXIST) {
405
1/1
✓ Branch 1 taken 3 times.
3 hash.hash(data, size, hashBuffer);
406
407 } else {
408
1/1
✓ Branch 2 taken 1 times.
1 Fw::LogStringArg s(hashFileName);
409
1/1
✓ Branch 5 taken 1 times.
1 this->log_WARNING_HI_WriteValidationOpenError(s, stat);
410 1 return false;
411 1 }
412
413 6 return true;
414 8 }
415
416 10 bool FileWorker ::writeBufferToFile(Fw::Buffer& buffer, const char* fileName, FwSizeType offset, bool append) {
417 10 FW_ASSERT(buffer.getData() != nullptr);
418 10 FW_ASSERT(fileName != nullptr);
419
420
1/1
✓ Branch 2 taken 10 times.
10 Fw::LogStringArg logStringArg(fileName);
421
422 // Get buffer data and size, then apply offset
423
1/1
✓ Branch 4 taken 10 times.
10 FwSizeType size = buffer.getSize();
424
1/1
✓ Branch 4 taken 10 times.
10 U8* const data = reinterpret_cast<U8*>(buffer.getData());
425 10 FW_ASSERT(data != nullptr);
426 10 FW_ASSERT(offset <= size);
427 10 size -= offset;
428
429 // A zero-length write is a successful no-op. Return before opening so no empty file is
430 // created and an existing file is not truncated for a no-op write.
431
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 9 times.
10 if (size == 0) {
432
1/1
✓ Branch 5 taken 1 times.
1 this->log_ACTIVITY_LO_WriteCompleted(size, logStringArg);
433 1 return true;
434 }
435
436 9 U8* const dataFromOffset = reinterpret_cast<U8*>(data + offset);
437 9 FW_ASSERT(dataFromOffset != nullptr);
438
439
1/1
✓ Branch 2 taken 9 times.
9 Os::File file;
440 9 Os::File::Status stat = Os::File::OP_OK;
441
442 // Open file
443
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 5 times.
9 if (!append) {
444
1/1
✓ Branch 2 taken 4 times.
4 stat = file.open(fileName, Os::File::Mode::OPEN_CREATE, Os::File::OverwriteType::OVERWRITE);
445 } else {
446
1/1
✓ Branch 2 taken 5 times.
5 stat = file.open(fileName, Os::File::Mode::OPEN_APPEND);
447 }
448
449
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 7 times.
9 if (stat != Os::File::OP_OK) {
450
1/1
✓ Branch 5 taken 2 times.
2 this->log_WARNING_HI_OpenFileError(logStringArg, stat);
451 2 return false;
452 }
453
454 // Write file
455
1/1
✓ Branch 5 taken 7 times.
7 this->log_ACTIVITY_LO_WriteBegin(size, logStringArg);
456
1/1
✓ Branch 4 taken 7 times.
7 FwSizeType writtenSize = this->writeToFile(dataFromOffset, size, file, fileName);
457
458 // Check written size
459
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 if (writtenSize != size) {
460 ✗ return false;
461 }
462
463
1/1
✓ Branch 5 taken 7 times.
7 this->log_ACTIVITY_LO_WriteCompleted(size, logStringArg);
464 7 return true;
465 10 }
466
467 10 void FileWorker ::writeBufferHashToFile(Fw::Buffer& buffer, const char* fileName, FwSizeType offset, bool append) {
468 10 FW_ASSERT(buffer.getData() != nullptr);
469 10 FW_ASSERT(fileName != nullptr);
470
471 // Construct hash file name
472
1/1
✓ Branch 1 taken 10 times.
10 const char* ext = Utils::Hash::getFileExtensionString();
473 10 FW_ASSERT(ext != nullptr);
474 // Same capacity as the CRC helpers so read, verify, and write agree on the longest legal path
475
1/1
✓ Branch 2 taken 10 times.
10 Fw::FileNameString hashFileNameString;
476
1/1
✓ Branch 2 taken 10 times.
10 Fw::FormatStatus status = hashFileNameString.format("%s%s", fileName, ext);
477
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (status != Fw::FormatStatus::SUCCESS) {
478 // writeIn_handler rejects such paths up front; report rather than assert if one gets here
479 ✗ this->log_WARNING_HI_InvalidInput(Fw::LogStringArg("writeIn"), Fw::LogStringArg("path too long"));
480 ✗ return;
481 }
482 10 const char* const hashFileName = hashFileNameString.toChar();
483
484 // Compute hash
485
1/1
✓ Branch 2 taken 10 times.
10 Utils::HashBuffer hashBuffer;
486
1/1
✓ Branch 4 taken 10 times.
10 FwSizeType size = buffer.getSize();
487
1/1
✓ Branch 4 taken 10 times.
10 U8* const data = reinterpret_cast<U8*>(buffer.getData());
488 10 FW_ASSERT(data != nullptr);
489
490 // Apply offset
491 10 FW_ASSERT(offset <= size);
492 10 size -= offset; // checked by assert
493
494 // A zero-length write changed no file contents, so the hash must not change either. Skip
495 // generation entirely: on the append path this would otherwise trip FW_ASSERT(size > 0) in
496 // getHash(), and on the non-append path it would silently overwrite a valid hash file with
497 // the hash of zero bytes.
498
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 9 times.
10 if (size == 0) {
499 1 return;
500 }
501
502 9 U8* const dataFromOffset = reinterpret_cast<U8*>(data + offset);
503 9 FW_ASSERT(dataFromOffset != nullptr);
504
505
1/1
✓ Branch 2 taken 9 times.
9 Utils::Hash hash;
506
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 5 times.
9 if (!append) {
507
1/1
✓ Branch 1 taken 4 times.
4 hash.hash(dataFromOffset, size, hashBuffer);
508
509 } else {
510
1/1
✓ Branch 4 taken 5 times.
5 bool isHash = this->getHash(hashFileName, hash, hashBuffer, dataFromOffset, size);
511
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (!isHash) {
512 ✗ return;
513 }
514 }
515
516 // Open file
517
1/1
✓ Branch 2 taken 9 times.
9 Os::File file;
518
1/1
✓ Branch 2 taken 9 times.
9 Os::File::Status stat = file.open(hashFileName, Os::File::Mode::OPEN_WRITE);
519
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 8 times.
9 if (stat != Os::File::OP_OK) {
520
1/1
✓ Branch 2 taken 1 times.
1 Fw::LogStringArg logStringArg(hashFileName);
521
1/1
✓ Branch 5 taken 1 times.
1 this->log_WARNING_HI_OpenFileError(logStringArg, stat);
522 1 return;
523 1 }
524
525 // Write hash
526
2/2
✓ Branch 5 taken 8 times.
✓ Branch 10 taken 8 times.
8 FwSizeType writtenSize = this->writeToFile(hashBuffer.getBuffAddr(), hashBuffer.getSize(), file, hashFileName);
527
528 // Check written size
529
1/1
✓ Branch 2 taken 8 times.
8 FwSizeType hashSize = hashBuffer.getSize();
530
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (writtenSize != hashSize) {
531 ✗ Fw::LogStringArg logStringArg(hashFileName);
532 ✗ this->log_WARNING_LO_WriteValidationError(logStringArg, writtenSize, hashSize);
533 ✗ return;
534 ✗ }
535
536 8 return;
537 10 }
538
539 17 FwSizeType FileWorker ::writeToFile(const U8* data, FwSizeType size, Os::File& file, const char* fileName) {
540 17 FW_ASSERT(data != nullptr);
541 17 FW_ASSERT(size > 0);
542 17 FW_ASSERT(file.isOpen());
543 17 FW_ASSERT(fileName != nullptr);
544
545 // Determine true timeout
546 17 FW_ASSERT(this->m_chunkSize > 0);
547
1/2
✗ Branch 4 not taken.
✓ Branch 5 taken 17 times.
17 FwSizeType numChunks = (size / this->m_chunkSize);
548
2/4
✗ Branch 4 not taken.
✓ Branch 5 taken 17 times.
✓ Branch 6 taken 17 times.
✗ Branch 7 not taken.
17 if (size % this->m_chunkSize > 0) {
549 17 numChunks += 1;
550 }
551 17 U64 timeout = numChunks * TIMEOUT_MS;
552
553 // Write loop: legal short writes make progress but consume an iteration, so
554 // allow extra iterations beyond the chunk count before giving up
555 17 const FwSizeType maxIterations = numChunks + MAX_LOOP_ITERATIONS;
556 17 FwSizeType bytesWritten = 0;
557
1/1
✓ Branch 3 taken 17 times.
17 Fw::Time start = this->getTime();
558
3/4
✓ Branch 0 taken 94 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 79 times.
✓ Branch 3 taken 15 times.
94 for (FwSizeType i = 0; (i < maxIterations) && (bytesWritten < size); i++) {
559 79 FwSizeType writeAmt = FW_MIN(size - bytesWritten, this->m_chunkSize);
560
1/1
✓ Branch 5 taken 79 times.
79 Os::File::Status ret = file.write(data + bytesWritten, writeAmt);
561
562
3/4
✓ Branch 0 taken 78 times.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 78 times.
79 if (Os::File::OP_OK != ret || writeAmt == 0) {
563
1/1
✓ Branch 2 taken 1 times.
1 Fw::LogStringArg logStringArg(fileName);
564
1/1
✓ Branch 5 taken 1 times.
1 this->log_WARNING_HI_WriteFileError(bytesWritten, size, logStringArg, ret);
565 1 break;
566 1 }
567
568 78 bool currAbort = this->m_abort.load(std::memory_order_relaxed);
569
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 77 times.
78 if (currAbort) {
570 // Abort command was sent
571
1/1
✓ Branch 2 taken 1 times.
1 Fw::LogStringArg logStringArg(fileName);
572
1/1
✓ Branch 5 taken 1 times.
1 this->log_WARNING_LO_WriteAborted(bytesWritten, size, logStringArg);
573 1 break;
574 1 }
575
576
1/2
✓ Branch 0 taken 77 times.
✗ Branch 1 not taken.
77 if (timeout > 0) {
577 // Only check timeout if > 0
578
1/1
✓ Branch 3 taken 77 times.
77 Fw::Time now = this->getTime();
579
1/1
✓ Branch 2 taken 77 times.
77 Fw::Time diff = Fw::Time::sub(now, start);
580
2/2
✓ Branch 2 taken 77 times.
✓ Branch 6 taken 77 times.
77 U64 elapsed = (diff.getSeconds() * 1000000) + diff.getUSeconds();
581
582
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 77 times.
77 if (elapsed >= timeout) {
583 ✗ Fw::LogStringArg logStringArg(fileName);
584 ✗ this->log_WARNING_HI_WriteTimeout(bytesWritten, size, logStringArg, timeout);
585 ✗ break;
586 ✗ }
587 77 }
588
589 77 bytesWritten += writeAmt;
590 }
591
592 17 return bytesWritten;
593 17 }
594
595 } // namespace Svc
596