From d9cb74e4dd3fd2b638ccb4e09d5705e638f4829e Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 12 May 2026 15:38:04 -0500 Subject: [PATCH] XModemAdapter: ensure file truncation before receiving and add isBusy() method to prevent concurrent writes --- src/xmodem.cpp | 15 ++++++++++++++- src/xmodem.h | 6 ++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/xmodem.cpp b/src/xmodem.cpp index 596732975..b885e47e5 100644 --- a/src/xmodem.cpp +++ b/src/xmodem.cpp @@ -123,15 +123,24 @@ void XModemAdapter::handlePacket(meshtastic_XModem xmodemPacket) filename[sizeof(filename) - 1] = '\0'; if (xmodemPacket.control == meshtastic_XModem_Control_SOH) { // Receive this file and put to Flash + // Truncate the destination before opening. On Adafruit_LittleFS, + // `open(path, FILE_O_WRITE)` is *append* semantics (O_RDWR|O_CREAT + + // seek-to-EOF), so without a prior remove, our XModem-streamed + // payload would be concatenated onto whatever was there, producing + // a corrupt mixed file. spiLock->lock(); + if (FSCom.exists(filename)) + FSCom.remove(filename); file = FSCom.open(filename, FILE_O_WRITE); spiLock->unlock(); if (file) { + LOG_INFO("XModem: receiving %s", filename); sendControl(meshtastic_XModem_Control_ACK); isReceiving = true; packetno = 1; break; } + LOG_WARN("XModem: open(%s, WRITE) failed", filename); sendControl(meshtastic_XModem_Control_NAK); isReceiving = false; break; @@ -169,8 +178,12 @@ void XModemAdapter::handlePacket(meshtastic_XModem xmodemPacket) check(xmodemPacket.buffer.bytes, xmodemPacket.buffer.size, xmodemPacket.crc16)) { // valid packet spiLock->lock(); - file.write(xmodemPacket.buffer.bytes, xmodemPacket.buffer.size); + size_t written = file.write(xmodemPacket.buffer.bytes, xmodemPacket.buffer.size); spiLock->unlock(); + if (written != xmodemPacket.buffer.size) { + LOG_WARN("XModem: short write seq=%d expected=%d wrote=%d (LittleFS partition full?)", + (int)xmodemPacket.seq, (int)xmodemPacket.buffer.size, (int)written); + } sendControl(meshtastic_XModem_Control_ACK); packetno++; break; diff --git a/src/xmodem.h b/src/xmodem.h index 7b665e0ac..3a1183df1 100644 --- a/src/xmodem.h +++ b/src/xmodem.h @@ -52,6 +52,12 @@ class XModemAdapter meshtastic_XModem getForPhone(); void resetForPhone(); + // True while a file is being received from or transmitted to the phone. + // Callers (e.g. NodeDB::saveNodeDatabaseToDisk) consult this to avoid + // opening the same on-disk file for write in parallel, which races our + // long-lived xmodem `file` handle and ends up producing a 0-byte file. + bool isBusy() const { return isReceiving || isTransmitting; } + private: bool isReceiving = false; bool isTransmitting = false;