Files
HaloKeymind/src/helpers/AtomicFileWriter.h
T

182 lines
4.6 KiB
C++

#pragma once
#if defined(NRF52_PLATFORM)
#include <stddef.h>
#include <stdint.h>
#include <string.h>
namespace mesh {
/**
* Power-loss-safe file replacement for the nRF52 LittleFS backend.
*
* Data is written to a sibling .tmp file. commit() flushes and closes that
* file, reads it back while checking its size and CRC32, and only then uses
* LittleFS rename() to atomically replace the live file. A failed or abandoned
* write removes the temporary file and leaves the previous live file intact.
*/
class AtomicFileWriter {
static const size_t TEMP_PATH_CAPACITY = 96;
FILESYSTEM* _fs;
const char* _target_path;
char _temp_path[TEMP_PATH_CAPACITY];
File _file;
size_t _bytes_written;
uint32_t _crc;
bool _opened;
bool _write_ok;
bool _finished;
static uint32_t updateCRC32(uint32_t crc, const uint8_t* data, size_t len);
void removeTempFile();
bool validateTempFile();
public:
AtomicFileWriter(FILESYSTEM* fs, const char* target_path);
~AtomicFileWriter();
AtomicFileWriter(const AtomicFileWriter&) = delete;
AtomicFileWriter& operator=(const AtomicFileWriter&) = delete;
operator bool() const { return _opened && _write_ok; }
size_t write(uint8_t value);
size_t write(const uint8_t* data, size_t len);
/**
* Commit the staged file. content_valid lets callers include format-specific
* checks in the same decision; false always preserves the previous file.
*/
bool commit(bool content_valid = true);
size_t bytesWritten() const { return _bytes_written; }
const char* tempPath() const { return _temp_path; }
};
} // namespace mesh
#if defined(ATOMIC_FILE_WRITER_IMPLEMENTATION)
namespace mesh {
uint32_t AtomicFileWriter::updateCRC32(uint32_t crc, const uint8_t* data, size_t len) {
while (len-- > 0) {
crc ^= *data++;
for (uint8_t bit = 0; bit < 8; bit++) {
crc = (crc >> 1) ^ ((crc & 1) ? 0xEDB88320UL : 0);
}
}
return crc;
}
void AtomicFileWriter::removeTempFile() {
if (_temp_path[0] != 0 && _fs->exists(_temp_path)) {
_fs->remove(_temp_path);
}
}
bool AtomicFileWriter::validateTempFile() {
File verify(*_fs);
if (!verify.open(_temp_path, FILE_O_READ)) return false;
bool valid = verify.size() == _bytes_written;
uint32_t read_crc = 0xFFFFFFFFUL;
size_t remaining = _bytes_written;
uint8_t buf[64];
while (valid && remaining > 0) {
size_t chunk = remaining < sizeof(buf) ? remaining : sizeof(buf);
int count = verify.read(buf, (uint16_t)chunk);
if (count != (int)chunk) {
valid = false;
break;
}
read_crc = updateCRC32(read_crc, buf, chunk);
remaining -= chunk;
}
verify.close();
return valid && remaining == 0 && read_crc == _crc;
}
AtomicFileWriter::AtomicFileWriter(FILESYSTEM* fs, const char* target_path)
: _fs(fs), _target_path(target_path), _file(*fs), _bytes_written(0),
_crc(0xFFFFFFFFUL), _opened(false), _write_ok(true), _finished(false) {
_temp_path[0] = 0;
if (target_path == NULL) {
_write_ok = false;
return;
}
size_t target_len = strlen(target_path);
if (target_len + sizeof(".tmp") > sizeof(_temp_path)) {
_write_ok = false;
return;
}
memcpy(_temp_path, target_path, target_len);
memcpy(_temp_path + target_len, ".tmp", sizeof(".tmp"));
// A reset before a previous commit may leave a harmless stale temp file.
// Never append to it: remove it before opening the new transaction.
removeTempFile();
_opened = _file.open(_temp_path, FILE_O_WRITE);
_write_ok = _opened;
}
AtomicFileWriter::~AtomicFileWriter() {
if (_finished) return;
if (_opened) {
_file.close();
_opened = false;
}
removeTempFile();
}
size_t AtomicFileWriter::write(uint8_t value) {
return write(&value, 1);
}
size_t AtomicFileWriter::write(const uint8_t* data, size_t len) {
if (!_opened || !_write_ok || (data == NULL && len != 0)) {
_write_ok = false;
return 0;
}
size_t written = _file.write(data, len);
if (written != len) {
_write_ok = false;
return written;
}
_bytes_written += written;
_crc = updateCRC32(_crc, data, written);
return written;
}
bool AtomicFileWriter::commit(bool content_valid) {
if (_finished) return false;
bool success = _opened && _write_ok && content_valid;
if (_opened) {
_file.flush();
if (_file.size() != _bytes_written) success = false;
_file.close();
_opened = false;
}
if (success) success = validateTempFile();
if (success) success = _fs->rename(_temp_path, _target_path);
if (!success) removeTempFile();
_finished = true;
return success;
}
} // namespace mesh
#endif // ATOMIC_FILE_WRITER_IMPLEMENTATION
#endif // NRF52_PLATFORM