GCC Code Coverage Report


Directory: ./
File: Svc/FileWorker/FileWorker.cpp
Date: 2026-09-23 22:11:34
Exec Total Coverage
Lines: 0 340 0.0%
Functions: 0 15 0.0%
Branches: 0 272 0.0%

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 ✗ FileWorker ::FileWorker(const char* const compName)
16 : FileWorkerComponentBase(compName),
17 ✗ m_state(FileWorkerState::FW_STATE_IDLE),
18 ✗ m_abort(false),
19 ✗ m_chunkSize(BLOCK_SIZE_BYTES) {}
20
21 ✗ void FileWorker ::configure(U64 chunkSize) {
22 ✗ FW_ASSERT(chunkSize > 0);
23 ✗ this->m_chunkSize = chunkSize;
24 ✗ }
25
26 ✗ FileWorker ::~FileWorker() {}
27
28 // ----------------------------------------------------------------------
29 // Handler implementations for typed input ports
30 // ----------------------------------------------------------------------
31
32 ✗ void FileWorker ::cancelIn_handler(FwIndexType portNum) {
33 ✗ this->m_abort.store(true, std::memory_order_relaxed);
34 ✗ }
35
36 ✗ void FileWorker ::readIn_handler(FwIndexType portNum, const Fw::StringBase& path, Fw::Buffer& buffer) {
37 // Validate inputs before processing file
38 ✗ if (path.length() == 0) {
39 ✗ this->log_WARNING_HI_InvalidInput(Fw::LogStringArg("readIn"), Fw::LogStringArg("empty path"));
40 ✗ this->readDoneOut_out(0, FW_STATUS_INVALID_INPUT, 0);
41 ✗ 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 ✗ if (!FileWorker::pathFitsWithHashExtension(path)) {
46 ✗ this->log_WARNING_HI_InvalidInput(Fw::LogStringArg("readIn"), Fw::LogStringArg("path too long"));
47 ✗ this->readDoneOut_out(0, FW_STATUS_INVALID_INPUT, 0);
48 ✗ return;
49 }
50 ✗ if (!buffer.isValid()) {
51 ✗ this->log_WARNING_HI_InvalidInput(Fw::LogStringArg("readIn"), Fw::LogStringArg("invalid buffer"));
52 ✗ this->readDoneOut_out(0, FW_STATUS_INVALID_INPUT, 0);
53 ✗ return;
54 }
55
56 ✗ const char* const fileName = path.toChar();
57 ✗ FwSizeType fileSize = 0;
58
59 ✗ if (this->m_state != FW_STATE_IDLE) {
60 ✗ this->log_WARNING_HI_NotInIdle(this->m_state);
61 ✗ this->readDoneOut_out(0, FW_STATUS_NOT_IDLE, 0);
62 ✗ return;
63 }
64
65 // New read request overrides any leftover abort state
66 ✗ this->m_abort.store(false, std::memory_order_relaxed);
67
68 ✗ this->m_state = FW_STATE_READING;
69
70 // Check CRC
71 ✗ U32 crcFromFile = 0;
72 ✗ U32 crcCalculated = 0;
73 ✗ Utils::crc_stat_t crcStat = Utils::verify_checksum(fileName, crcFromFile, crcCalculated);
74 ✗ if (crcStat != Utils::PASSED_FILE_CRC_CHECK) {
75 ✗ this->log_WARNING_HI_CrcFailed(crcStat);
76 ✗ this->readDoneOut_out(0, FW_STATUS_FAILED_CRC, 0);
77 ✗ this->m_state = FW_STATE_IDLE;
78 ✗ return;
79 }
80
81 // Get filesize
82 ✗ Os::FileSystem::Status fsStat = Os::FileSystem::getFileSize(fileName, fileSize);
83 ✗ 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 ✗ FileWorkerStatus workerStat = this->readBufferFromFile(buffer, fileName);
93
94 // Report 0 bytes on a failed or aborted read so readDoneOut does not imply success.
95 ✗ this->readDoneOut_out(0, workerStat, (workerStat == FW_STATUS_DONE_READ) ? buffer.getSize() : 0);
96 ✗ this->m_state = FW_STATE_IDLE;
97 }
98
99 ✗ void FileWorker ::verifyIn_handler(FwIndexType portNum, const Fw::StringBase& path, U32 crc) {
100 // Validate inputs before processing file
101 ✗ if (path.length() == 0) {
102 ✗ this->log_WARNING_HI_InvalidInput(Fw::LogStringArg("verifyIn"), Fw::LogStringArg("empty path"));
103 ✗ this->verifyDoneOut_out(0, FW_STATUS_INVALID_INPUT, 0);
104 ✗ 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 ✗ if (!FileWorker::pathFitsWithHashExtension(path)) {
109 ✗ this->log_WARNING_HI_InvalidInput(Fw::LogStringArg("verifyIn"), Fw::LogStringArg("path too long"));
110 ✗ this->verifyDoneOut_out(0, FW_STATUS_INVALID_INPUT, 0);
111 ✗ return;
112 }
113
114 ✗ const char* const fileName = path.toChar();
115 ✗ FwSizeType fileSize = 0;
116 ✗ FileWorkerStatus workerStat = FW_STATUS_DONE;
117
118 ✗ U32 crcFromFile = 0;
119 ✗ U32 crcCalculated = 0;
120 ✗ Utils::crc_stat_t crcStat = Utils::verify_checksum(fileName, crcFromFile, crcCalculated);
121
122 ✗ if (crcStat != Utils::PASSED_FILE_CRC_CHECK) {
123 ✗ this->log_WARNING_HI_CrcFailed(crcStat);
124 ✗ workerStat = FW_STATUS_FAILED_CRC;
125 }
126
127 ✗ if (crc != crcCalculated) {
128 ✗ workerStat = FW_STATUS_FAILED_CRC;
129 ✗ this->log_WARNING_LO_CrcVerificationError(crc, crcCalculated);
130 }
131
132 // Get filesize
133 ✗ Os::FileSystem::Status fsStat = Os::FileSystem::getFileSize(fileName, fileSize);
134 ✗ if (fsStat != Os::FileSystem::OP_OK) {
135 ✗ this->log_WARNING_HI_ReadFailedFileSize(fsStat);
136 ✗ workerStat = FW_STATUS_FAILED_FILE_SIZE;
137 }
138
139 ✗ this->verifyDoneOut_out(0, workerStat, fileSize);
140 }
141
142 ✗ 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 ✗ if (path.length() == 0) {
149 ✗ this->log_WARNING_HI_InvalidInput(Fw::LogStringArg("writeIn"), Fw::LogStringArg("empty path"));
150 ✗ this->writeDoneOut_out(0, FW_STATUS_INVALID_INPUT, 0);
151 ✗ 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 ✗ if (!FileWorker::pathFitsWithHashExtension(path)) {
156 ✗ this->log_WARNING_HI_InvalidInput(Fw::LogStringArg("writeIn"), Fw::LogStringArg("path too long"));
157 ✗ this->writeDoneOut_out(0, FW_STATUS_INVALID_INPUT, 0);
158 ✗ return;
159 }
160 ✗ if (!buffer.isValid()) {
161 ✗ this->log_WARNING_HI_InvalidInput(Fw::LogStringArg("writeIn"), Fw::LogStringArg("invalid buffer"));
162 ✗ this->writeDoneOut_out(0, FW_STATUS_INVALID_INPUT, 0);
163 ✗ return;
164 }
165 ✗ if (offsetBytes > buffer.getSize()) {
166 ✗ this->log_WARNING_HI_InvalidInput(Fw::LogStringArg("writeIn"), Fw::LogStringArg("invalid offset"));
167 ✗ this->writeDoneOut_out(0, FW_STATUS_INVALID_INPUT, 0);
168 ✗ return;
169 }
170
171 char fileName[FileNameStringSize];
172
173 // Make sure we are in IDLE state before proceeding
174 ✗ if (this->m_state != FW_STATE_IDLE) {
175 ✗ this->log_WARNING_HI_NotInIdle(this->m_state);
176 ✗ this->writeDoneOut_out(0, FW_STATUS_NOT_IDLE, 0);
177 ✗ return;
178 }
179
180 ✗ this->m_state = FW_STATE_WRITING;
181
182 // New write request overrides any leftover abort state
183 ✗ 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 ✗ FwSizeType length = Fw::StringUtils::string_length(path.toChar(), FileNameStringSize);
189 ✗ 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 ✗ (void)Fw::StringUtils::string_copy(fileName, path.toChar(), sizeof(fileName));
198 ✗ fileName[sizeof(fileName) - 1] = 0; // guarantee termination
199
200 // Write
201 ✗ const bool isWrite = this->writeBufferToFile(buffer, fileName, offsetBytes, append);
202 ✗ if (isWrite) {
203 ✗ 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 ✗ 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 ✗ const FwSizeType writtenBytes = buffer.getSize() - offsetBytes;
214 ✗ this->writeDoneOut_out(0, writeStatus, isWrite ? writtenBytes : 0);
215 ✗ this->m_state = FW_STATE_IDLE;
216 ✗ return;
217 }
218
219 // ----------------------------------------------------------------------
220 // Helper functions
221 // ----------------------------------------------------------------------
222
223 ✗ bool FileWorker ::pathFitsWithHashExtension(const Fw::StringBase& path) {
224 // Fw::FileNameString holds FileNameStringSize characters plus the terminator
225 ✗ return (path.length() + Utils::Hash::getFileExtensionLength()) <= FileNameStringSize;
226 }
227
228 ✗ Svc ::FileWorkerStatus FileWorker ::readBufferFromFile(Fw::Buffer& buffer, const char* const fileName) {
229 ✗ FW_ASSERT(buffer.getData() != nullptr);
230 ✗ FW_ASSERT(fileName != nullptr);
231
232 ✗ Fw::LogStringArg fileNameStr(fileName);
233 ✗ Os::File file;
234
235 // Open file
236 ✗ Os::File::Status fileStat = file.open(fileName, Os::File::OPEN_READ);
237 ✗ if (fileStat != Os::File::OP_OK) {
238 ✗ this->log_WARNING_HI_OpenFileError(fileNameStr, fileStat);
239 ✗ return FW_STATUS_FAILED_TO_OPEN;
240 }
241
242 // Get buffer data and size
243 ✗ FwSizeType readSize = buffer.getSize();
244
245 // Read file
246 ✗ this->log_ACTIVITY_LO_ReadBegin(readSize, fileNameStr);
247 ✗ const FileWorkerReadStatus readStat = this->readFile(buffer, readSize, file, fileNameStr);
248
249 ✗ this->log_ACTIVITY_LO_ReadCompleted(readSize, fileNameStr);
250 ✗ file.close();
251
252 // Only a completed read is DONE_READ; error, abort, or timeout reports FAILED_TO_READ.
253 ✗ return (readStat == FileWorkerReadStatus::FW_READ_DONE) ? FileWorkerStatus::FW_STATUS_DONE_READ
254 ✗ : FileWorkerStatus::FW_STATUS_FAILED_TO_READ;
255 ✗ }
256
257 ✗ Svc ::FileWorkerReadStatus FileWorker ::readFile(Fw::Buffer& buffer,
258 FwSizeType size,
259 Os::File& file,
260 const Fw::LogStringArg& fileNameStr) {
261 ✗ FW_ASSERT(buffer.getData() != nullptr);
262 ✗ FW_ASSERT(size > 0);
263 ✗ FW_ASSERT(fileNameStr != nullptr);
264
265 ✗ FwSizeType bytesRead = 0;
266 ✗ FwSizeType numChunks = 0;
267 ✗ U64 timeout = 0;
268
269 ✗ if (!file.isOpen()) {
270 ✗ return FileWorkerReadStatus::FW_READ_ERROR;
271 }
272
273 ✗ FileWorkerReadStatus readStat = this->readFileBytes(buffer, size, file, bytesRead);
274
275 ✗ switch (readStat) {
276 ✗ case FW_READ_ERROR:
277 // Some read error
278 ✗ this->log_WARNING_HI_ReadError(bytesRead, size, fileNameStr);
279 ✗ break;
280
281 ✗ case FW_READ_DONE:
282 ✗ break;
283
284 ✗ case FW_READ_ABORT:
285 // Abort command was sent
286 ✗ this->log_WARNING_LO_ReadAborted(bytesRead, size, fileNameStr);
287 ✗ 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 ✗ return readStat;
312 }
313
314 ✗ Svc ::FileWorkerReadStatus FileWorker ::readFileBytes(Fw::Buffer& buffer,
315 FwSizeType size,
316 Os::File& file,
317 FwSizeType& bytesRead) {
318 ✗ FW_ASSERT(buffer.getData() != nullptr);
319 ✗ FW_ASSERT(size > 0);
320
321 // Determine true timeout
322 ✗ FW_ASSERT(this->m_chunkSize > 0);
323 ✗ FwSizeType numChunks = (size / this->m_chunkSize);
324 ✗ if (size % this->m_chunkSize > 0) {
325 ✗ numChunks += 1;
326 }
327 ✗ U64 timeout = numChunks * TIMEOUT_MS;
328
329 // Read loop
330 ✗ bytesRead = 0;
331 ✗ Fw::Time start = this->getTime();
332
333 ✗ for (FwSizeType i = 0; i < numChunks; i++) {
334 ✗ FwSizeType readAmt = FW_MIN(size - bytesRead, this->m_chunkSize);
335 ✗ FwSizeType readAmtActual = readAmt;
336 ✗ Os::File::Status ret = file.read(buffer.getData() + bytesRead, readAmtActual);
337
338 ✗ 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 ✗ bytesRead += readAmtActual;
344 ✗ return FileWorkerReadStatus::FW_READ_ERROR;
345 }
346
347 ✗ bool currAbort = this->m_abort.load(std::memory_order_relaxed);
348 ✗ if (currAbort) {
349 // Abort command was sent
350 ✗ return FileWorkerReadStatus::FW_READ_ABORT;
351 }
352
353 ✗ if (timeout > 0) {
354 // Only check timeout if > 0
355 ✗ Fw::Time now = this->getTime();
356 ✗ Fw::Time diff = Fw::Time::sub(now, start);
357 ✗ U64 elapsed = (diff.getSeconds() * 1000000) + diff.getUSeconds();
358 ✗ if (elapsed >= timeout) {
359 ✗ return FileWorkerReadStatus::FW_READ_TIMEOUT;
360 }
361 ✗ }
362
363 ✗ bytesRead += readAmt;
364 ✗ if (bytesRead >= size) {
365 // Finished, break out
366 ✗ return FileWorkerReadStatus::FW_READ_DONE;
367 }
368 }
369
370 ✗ return FileWorkerReadStatus::FW_READ_UNKNOWN;
371 ✗ }
372
373 ✗ 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 ✗ FW_ASSERT(hashFileName != nullptr);
379 ✗ FW_ASSERT(data != nullptr);
380 ✗ FW_ASSERT(size > 0);
381
382 // Open file
383 ✗ Os::File file;
384 ✗ Os::File::Status stat = file.open(hashFileName, Os::File::OPEN_READ);
385
386 // Read value if it exists
387 ✗ if (stat == Os::File::OP_OK) {
388 HASH_HANDLE_TYPE hashValue;
389 ✗ FwSizeType hashSize = sizeof(hashValue);
390 ✗ U8* hashValuePtr = reinterpret_cast<U8*>(&hashValue);
391 ✗ FW_ASSERT(hashValuePtr != nullptr);
392
393 ✗ Os::File::Status readStat = file.read(hashValuePtr, hashSize);
394 ✗ if (readStat != Os::File::OP_OK) {
395 ✗ Fw::LogStringArg s(hashFileName);
396 ✗ this->log_WARNING_HI_WriteValidationReadError(s, readStat);
397 ✗ return false;
398 ✗ }
399 ✗ Utils::HashBuffer tmp(hashValuePtr, hashSize);
400 ✗ hash.setHashValue(tmp);
401 ✗ hash.update(data, size);
402 ✗ hash.finalize(hashBuffer);
403
404 ✗ } else if (stat == Os::File::DOESNT_EXIST) {
405 ✗ hash.hash(data, size, hashBuffer);
406
407 } else {
408 ✗ Fw::LogStringArg s(hashFileName);
409 ✗ this->log_WARNING_HI_WriteValidationOpenError(s, stat);
410 ✗ return false;
411 ✗ }
412
413 ✗ return true;
414 ✗ }
415
416 ✗ bool FileWorker ::writeBufferToFile(Fw::Buffer& buffer, const char* fileName, FwSizeType offset, bool append) {
417 ✗ FW_ASSERT(buffer.getData() != nullptr);
418 ✗ FW_ASSERT(fileName != nullptr);
419
420 ✗ Fw::LogStringArg logStringArg(fileName);
421
422 // Get buffer data and size, then apply offset
423 ✗ FwSizeType size = buffer.getSize();
424 ✗ U8* const data = reinterpret_cast<U8*>(buffer.getData());
425 ✗ FW_ASSERT(data != nullptr);
426 ✗ FW_ASSERT(offset <= size);
427 ✗ 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 ✗ if (size == 0) {
432 ✗ this->log_ACTIVITY_LO_WriteCompleted(size, logStringArg);
433 ✗ return true;
434 }
435
436 ✗ U8* const dataFromOffset = reinterpret_cast<U8*>(data + offset);
437 ✗ FW_ASSERT(dataFromOffset != nullptr);
438
439 ✗ Os::File file;
440 ✗ Os::File::Status stat = Os::File::OP_OK;
441
442 // Open file
443 ✗ if (!append) {
444 ✗ stat = file.open(fileName, Os::File::Mode::OPEN_CREATE, Os::File::OverwriteType::OVERWRITE);
445 } else {
446 ✗ stat = file.open(fileName, Os::File::Mode::OPEN_APPEND);
447 }
448
449 ✗ if (stat != Os::File::OP_OK) {
450 ✗ this->log_WARNING_HI_OpenFileError(logStringArg, stat);
451 ✗ return false;
452 }
453
454 // Write file
455 ✗ this->log_ACTIVITY_LO_WriteBegin(size, logStringArg);
456 ✗ FwSizeType writtenSize = this->writeToFile(dataFromOffset, size, file, fileName);
457
458 // Check written size
459 ✗ if (writtenSize != size) {
460 ✗ return false;
461 }
462
463 ✗ this->log_ACTIVITY_LO_WriteCompleted(size, logStringArg);
464 ✗ return true;
465 ✗ }
466
467 ✗ void FileWorker ::writeBufferHashToFile(Fw::Buffer& buffer, const char* fileName, FwSizeType offset, bool append) {
468 ✗ FW_ASSERT(buffer.getData() != nullptr);
469 ✗ FW_ASSERT(fileName != nullptr);
470
471 // Construct hash file name
472 ✗ const char* ext = Utils::Hash::getFileExtensionString();
473 ✗ FW_ASSERT(ext != nullptr);
474 // Same capacity as the CRC helpers so read, verify, and write agree on the longest legal path
475 ✗ Fw::FileNameString hashFileNameString;
476 ✗ Fw::FormatStatus status = hashFileNameString.format("%s%s", fileName, ext);
477 ✗ 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 ✗ const char* const hashFileName = hashFileNameString.toChar();
483
484 // Compute hash
485 ✗ Utils::HashBuffer hashBuffer;
486 ✗ FwSizeType size = buffer.getSize();
487 ✗ U8* const data = reinterpret_cast<U8*>(buffer.getData());
488 ✗ FW_ASSERT(data != nullptr);
489
490 // Apply offset
491 ✗ FW_ASSERT(offset <= size);
492 ✗ 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 ✗ if (size == 0) {
499 ✗ return;
500 }
501
502 ✗ U8* const dataFromOffset = reinterpret_cast<U8*>(data + offset);
503 ✗ FW_ASSERT(dataFromOffset != nullptr);
504
505 ✗ Utils::Hash hash;
506 ✗ if (!append) {
507 ✗ hash.hash(dataFromOffset, size, hashBuffer);
508
509 } else {
510 ✗ bool isHash = this->getHash(hashFileName, hash, hashBuffer, dataFromOffset, size);
511 ✗ if (!isHash) {
512 ✗ return;
513 }
514 }
515
516 // Open file
517 ✗ Os::File file;
518 ✗ Os::File::Status stat = file.open(hashFileName, Os::File::Mode::OPEN_WRITE);
519 ✗ if (stat != Os::File::OP_OK) {
520 ✗ Fw::LogStringArg logStringArg(hashFileName);
521 ✗ this->log_WARNING_HI_OpenFileError(logStringArg, stat);
522 ✗ return;
523 ✗ }
524
525 // Write hash
526 ✗ FwSizeType writtenSize = this->writeToFile(hashBuffer.getBuffAddr(), hashBuffer.getSize(), file, hashFileName);
527
528 // Check written size
529 ✗ FwSizeType hashSize = hashBuffer.getSize();
530 ✗ if (writtenSize != hashSize) {
531 ✗ Fw::LogStringArg logStringArg(hashFileName);
532 ✗ this->log_WARNING_LO_WriteValidationError(logStringArg, writtenSize, hashSize);
533 ✗ return;
534 ✗ }
535
536 ✗ return;
537 ✗ }
538
539 ✗ FwSizeType FileWorker ::writeToFile(const U8* data, FwSizeType size, Os::File& file, const char* fileName) {
540 ✗ FW_ASSERT(data != nullptr);
541 ✗ FW_ASSERT(size > 0);
542 ✗ FW_ASSERT(file.isOpen());
543 ✗ FW_ASSERT(fileName != nullptr);
544
545 // Determine true timeout
546 ✗ FW_ASSERT(this->m_chunkSize > 0);
547 ✗ FwSizeType numChunks = (size / this->m_chunkSize);
548 ✗ if (size % this->m_chunkSize > 0) {
549 ✗ numChunks += 1;
550 }
551 ✗ 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 ✗ const FwSizeType maxIterations = numChunks + MAX_LOOP_ITERATIONS;
556 ✗ FwSizeType bytesWritten = 0;
557 ✗ Fw::Time start = this->getTime();
558 ✗ for (FwSizeType i = 0; (i < maxIterations) && (bytesWritten < size); i++) {
559 ✗ FwSizeType writeAmt = FW_MIN(size - bytesWritten, this->m_chunkSize);
560 ✗ Os::File::Status ret = file.write(data + bytesWritten, writeAmt);
561
562 ✗ if (Os::File::OP_OK != ret || writeAmt == 0) {
563 ✗ Fw::LogStringArg logStringArg(fileName);
564 ✗ this->log_WARNING_HI_WriteFileError(bytesWritten, size, logStringArg, ret);
565 ✗ break;
566 ✗ }
567
568 ✗ bool currAbort = this->m_abort.load(std::memory_order_relaxed);
569 ✗ if (currAbort) {
570 // Abort command was sent
571 ✗ Fw::LogStringArg logStringArg(fileName);
572 ✗ this->log_WARNING_LO_WriteAborted(bytesWritten, size, logStringArg);
573 ✗ break;
574 ✗ }
575
576 ✗ if (timeout > 0) {
577 // Only check timeout if > 0
578 ✗ Fw::Time now = this->getTime();
579 ✗ Fw::Time diff = Fw::Time::sub(now, start);
580 ✗ U64 elapsed = (diff.getSeconds() * 1000000) + diff.getUSeconds();
581
582 ✗ if (elapsed >= timeout) {
583 ✗ Fw::LogStringArg logStringArg(fileName);
584 ✗ this->log_WARNING_HI_WriteTimeout(bytesWritten, size, logStringArg, timeout);
585 ✗ break;
586 ✗ }
587 ✗ }
588
589 ✗ bytesWritten += writeAmt;
590 }
591
592 ✗ return bytesWritten;
593 ✗ }
594
595 } // namespace Svc
596