Files
firmware/src/SafeFile.cpp
T

118 lines
3.1 KiB
C++

#include "SafeFile.h"
#ifdef FSCom
// Only way to work on both esp32 and nrf52
static File openFile(const char *filename, bool fullAtomic)
{
concurrency::LockGuard g(spiLock);
LOG_DEBUG("Opening %s, fullAtomic=%d", filename, fullAtomic);
#ifdef HAPTIC_FEEDBACK_PIN
digitalWrite(HAPTIC_FEEDBACK_PIN, LOW); // the write stalls the loop that would end a haptic pulse
#endif
if (!fullAtomic) {
FSCom.remove(filename); // Nuke the old file to make space (ignore if it !exists)
}
String filenameTmp = filename;
filenameTmp += ".tmp";
// FILE_O_WRITE appends on Adafruit_LittleFS (nRF52) and STM32 LittleFS, so a tmp left by an interrupted
// write must go first. exists() guards it: a bare remove() of a missing file logs on Portduino.
if (FSCom.exists(filenameTmp.c_str())) {
LOG_DEBUG("Remove stale %s", filenameTmp.c_str());
// Opening anyway would append to the stale bytes, and the XOR readback is 8 bits wide, so
// polluted content has a real chance of verifying and being renamed over the good file.
if (!FSCom.remove(filenameTmp.c_str())) {
LOG_ERROR("Can't remove stale %s", filenameTmp.c_str());
return File();
}
}
// clear any previous LFS errors
return FSCom.open(filenameTmp.c_str(), FILE_O_WRITE);
}
SafeFile::SafeFile(const char *_filename, bool fullAtomic)
: filename(_filename), f(openFile(_filename, fullAtomic)), fullAtomic(fullAtomic)
{
}
size_t SafeFile::write(uint8_t ch)
{
if (!f)
return 0;
hash ^= ch;
return f.write(ch);
}
size_t SafeFile::write(const uint8_t *buffer, size_t size)
{
if (!f)
return 0;
for (size_t i = 0; i < size; i++) {
hash ^= buffer[i];
}
return f.write((uint8_t const *)buffer, size); // This nasty cast is _IMPORTANT_ otherwise the correct adafruit method does
// not get used (they made a mistake in their typing)
}
/**
* Atomically close the file (overwriting any old version) and readback the contents to confirm the hash matches
*
* @return false for failure
*/
bool SafeFile::close()
{
if (!f)
return false;
spiLock->lock();
f.close();
spiLock->unlock();
if (!testReadback())
return false;
// Rename or overwrite (atomic operation)
String filenameTmp = filename;
filenameTmp += ".tmp";
if (!renameFile(filenameTmp.c_str(), filename.c_str())) {
LOG_ERROR("Can't rename new pref file");
return false;
}
return true;
}
/// Read our (closed) tempfile back in and compare the hash
bool SafeFile::testReadback()
{
concurrency::LockGuard g(spiLock);
String filenameTmp = filename;
filenameTmp += ".tmp";
auto f2 = FSCom.open(filenameTmp.c_str(), FILE_O_READ);
if (!f2) {
LOG_ERROR("Can't open tmp file for readback");
return false;
}
int c = 0;
uint8_t test_hash = 0;
while ((c = f2.read()) >= 0) {
test_hash ^= (uint8_t)c;
}
f2.close();
if (test_hash != hash) {
LOG_ERROR("Readback failed hash mismatch");
return false;
}
return true;
}
#endif