From cd03a6f8f8d5b0804cbea3ea51295e87bed33370 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Sat, 15 Dec 2018 08:53:23 -0800 Subject: [PATCH 01/20] Add LittleFS as internal flash filesystem Adds a LittleFS object which uses the ARMmbed littlefs embedded filesystem, https://github.com/ARMmbed/littlefs, to enable a new filesystem for onboard flash utilizing the exact same API as the existing SPIFFS filesystem. LittleFS is built for low memory systems that are subject to random power losses, is actively supported by the ARMmbed community, supports directories, and seems to be much faster in the large-ish read-mostly applications I use. LittleFS, however, has a larger minimum file allocation unit and does not do static wear levelling. This means that for systems that need many little files (<4K), have small SPIFFS areas (64K), or which have a large static set of files covering the majority of flash coupled with a frequently updated set of other files, it may not perform as well. Simply replace SPIFFS.begin() with LittleFS.begin() in your sketch, use LittleFS.open in place of SPIFFS.open to open files, and everything else just works thanks to the magic of @igrr's File base class. **LITTLEFS FLASH LAYOUT IS INCOMPATIBLE WITH SPIFFS** Since it is a completely different filesystem, you will need to reformat your flash (and lose any data therein) to use it. Tools to build the flash filesystem and upload are at https://github.com/earlephilhower/arduino-esp8266littlefs-plugin and https://github.com/earlephilhower/mklittlefs/ . The mklittlefs tool is installed as part of the Arduino platform installation, automatically. The included example shows a contrived read-mostly example and demonstrates how the same calls work on either SPIFFS.* or LittleFS.* Host tests are also included as part of CI. Directories are fully supported in LittleFS. This means that LittleFS will have a slight difference vs. SPIFFS when you use LittleFS.openDir()/Dir.next(). On SPIFFS dir.next() will return all filesystem entries, including ones in "subdirs" (because in SPIFFS there are no subdirs and "/" is the same as any other character in a filename). On LittleFS, dir.next() will only return entries in the directory specified, not subdirs. So to list files in "/subdir/..." you need to actually openDir("/subdir") and use Dir.next() to parse through just those elements. The returned filenames also only have the filename returned, not full paths. So on a FS with "/a/1", "/a/2" when you do openDir("/a"); dir.next().getName(); you get "1" and "2" and not "/a/1" and "/a/2" like in SPIFFS. This is consistent with POSIX ideas about reading directories and more natural for a FS. Most code will not be affected by this, but if you depend on openDir/Dir.next() you need to be aware of it. Corresponding ::mkdir, ::rmdir, ::isDirectory, ::isFile, ::openNextFile, and ::rewind methods added to Filesystem objects. Documentation has been updated with this and other LittleFS information. Subdirectories are made silently when they do not exist when you try and create a file in a subdir. They are silently removed when the last file in them is deleted. This is consistent with what SPIFFS does but is obviously not normal POSIX behavior. Since there has never been a "FS.mkdir()" method this is the only way to be compatible with legacy SPIFFS code. SPIFFS code has been refactored to pull out common flash_hal_* ops and placed in its own namespace, like LittleFS. --- .gitmodules | 3 + README.md | 2 + cores/esp8266/Esp.cpp | 4 +- cores/esp8266/FS.cpp | 91 ++- cores/esp8266/FS.h | 30 +- cores/esp8266/FSImpl.h | 9 +- cores/esp8266/Updater.cpp | 20 +- cores/esp8266/Updater.h | 2 +- .../esp8266/{spiffs_hal.cpp => flash_hal.cpp} | 30 +- cores/esp8266/flash_hal.h | 49 ++ cores/esp8266/spiffs/spiffs.h | 4 +- cores/esp8266/spiffs_api.cpp | 14 +- cores/esp8266/spiffs_api.h | 81 ++- doc/filesystem.rst | 208 ++++++- libraries/ArduinoOTA/ArduinoOTA.cpp | 2 +- libraries/ArduinoOTA/ArduinoOTA.h | 2 +- .../ArduinoOTA/examples/BasicOTA/BasicOTA.ino | 4 +- libraries/EEPROM/EEPROM.cpp | 4 +- .../src/ESP8266httpUpdate.cpp | 10 +- .../LittleFS/examples/SpeedTest/SpeedTest.ino | 125 +++++ libraries/LittleFS/lib/littlefs | 1 + libraries/LittleFS/library.properties | 10 + libraries/LittleFS/src/LittleFS.cpp | 198 +++++++ libraries/LittleFS/src/LittleFS.h | 527 ++++++++++++++++++ libraries/LittleFS/src/lfs.c | 7 + libraries/LittleFS/src/lfs_util.c | 3 + .../package_esp8266com_index.template.json | 63 ++- platform.txt | 4 + tests/host/Makefile | 5 + tests/host/common/flash_hal_mock.cpp | 36 ++ tests/host/common/flash_hal_mock.h | 19 + tests/host/common/littlefs_mock.cpp | 126 +++++ tests/host/common/littlefs_mock.h | 56 ++ tests/host/common/spiffs_mock.cpp | 36 +- tests/host/common/spiffs_mock.h | 2 +- tests/host/fs/test_fs.cpp | 202 ++----- tests/host/fs/test_fs.inc | 289 ++++++++++ tools/boards.txt.py | 8 +- tools/sdk/ld/eagle.flash.16m14m.ld | 8 +- tools/sdk/ld/eagle.flash.16m15m.ld | 8 +- tools/sdk/ld/eagle.flash.1m.ld | 8 +- tools/sdk/ld/eagle.flash.1m128.ld | 8 +- tools/sdk/ld/eagle.flash.1m144.ld | 8 +- tools/sdk/ld/eagle.flash.1m160.ld | 8 +- tools/sdk/ld/eagle.flash.1m192.ld | 8 +- tools/sdk/ld/eagle.flash.1m256.ld | 8 +- tools/sdk/ld/eagle.flash.1m512.ld | 8 +- tools/sdk/ld/eagle.flash.1m64.ld | 8 +- tools/sdk/ld/eagle.flash.2m.ld | 8 +- tools/sdk/ld/eagle.flash.2m128.ld | 8 +- tools/sdk/ld/eagle.flash.2m1m.ld | 8 +- tools/sdk/ld/eagle.flash.2m256.ld | 8 +- tools/sdk/ld/eagle.flash.2m512.ld | 8 +- tools/sdk/ld/eagle.flash.4m.ld | 8 +- tools/sdk/ld/eagle.flash.4m1m.ld | 8 +- tools/sdk/ld/eagle.flash.4m2m.ld | 8 +- tools/sdk/ld/eagle.flash.4m3m.ld | 8 +- tools/sdk/ld/eagle.flash.512k.ld | 8 +- tools/sdk/ld/eagle.flash.512k128.ld | 8 +- tools/sdk/ld/eagle.flash.512k32.ld | 8 +- tools/sdk/ld/eagle.flash.512k64.ld | 8 +- tools/sdk/ld/eagle.flash.8m6m.ld | 8 +- tools/sdk/ld/eagle.flash.8m7m.ld | 8 +- 63 files changed, 2094 insertions(+), 392 deletions(-) rename cores/esp8266/{spiffs_hal.cpp => flash_hal.cpp} (90%) create mode 100644 cores/esp8266/flash_hal.h create mode 100644 libraries/LittleFS/examples/SpeedTest/SpeedTest.ino create mode 160000 libraries/LittleFS/lib/littlefs create mode 100644 libraries/LittleFS/library.properties create mode 100644 libraries/LittleFS/src/LittleFS.cpp create mode 100644 libraries/LittleFS/src/LittleFS.h create mode 100644 libraries/LittleFS/src/lfs.c create mode 100644 libraries/LittleFS/src/lfs_util.c create mode 100644 tests/host/common/flash_hal_mock.cpp create mode 100644 tests/host/common/flash_hal_mock.h create mode 100644 tests/host/common/littlefs_mock.cpp create mode 100644 tests/host/common/littlefs_mock.h create mode 100644 tests/host/fs/test_fs.inc diff --git a/.gitmodules b/.gitmodules index 6ccf7f096b..2df23420f3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "libraries/SoftwareSerial"] path = libraries/SoftwareSerial url = https://github.com/plerup/espsoftwareserial.git +[submodule "libraries/LittleFS/lib/littlefs"] + path = libraries/LittleFS/lib/littlefs + url = https://github.com/ARMmbed/littlefs diff --git a/README.md b/README.md index 7b2d2b5831..024fd5b4d3 100644 --- a/README.md +++ b/README.md @@ -138,3 +138,5 @@ ESP8266 core files are licensed under LGPL. [axTLS](http://axtls.sourceforge.net/) library written by Cameron Rich, built from https://github.com/igrr/axtls-8266, is used in this project. It is distributed under [BSD license](https://github.com/igrr/axtls-8266/blob/master/LICENSE). [BearSSL](https://bearssl.org) library written by Thomas Pornin, built from https://github.com/earlephilhower/bearssl-esp8266, is used in this project. It is distributed under the [MIT License](https://bearssl.org/#legal-details). + +[LittleFS](https://github.com/ARMmbed/littlefs) library written by ARM Limited and released under the [BSD 3-clause license](https://github.com/ARMmbed/littlefs/blob/master/LICENSE.md). diff --git a/cores/esp8266/Esp.cpp b/cores/esp8266/Esp.cpp index 8dc37ff2a5..51513e8fe6 100644 --- a/cores/esp8266/Esp.cpp +++ b/cores/esp8266/Esp.cpp @@ -510,14 +510,14 @@ uint32_t EspClass::getSketchSize() { return result; } -extern "C" uint32_t _SPIFFS_start; +extern "C" uint32_t _FS_start; uint32_t EspClass::getFreeSketchSpace() { uint32_t usedSize = getSketchSize(); // round one sector up uint32_t freeSpaceStart = (usedSize + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1)); - uint32_t freeSpaceEnd = (uint32_t)&_SPIFFS_start - 0x40200000; + uint32_t freeSpaceEnd = (uint32_t)&_FS_start - 0x40200000; #ifdef DEBUG_SERIAL DEBUG_SERIAL.printf("usedSize=%u freeSpaceStart=%u freeSpaceEnd=%u\r\n", usedSize, freeSpaceStart, freeSpaceEnd); diff --git a/cores/esp8266/FS.cpp b/cores/esp8266/FS.cpp index 6ae11e1782..0d53a5b1df 100644 --- a/cores/esp8266/FS.cpp +++ b/cores/esp8266/FS.cpp @@ -121,6 +121,44 @@ const char* File::name() const { return _p->name(); } +const char* File::fullName() const { + if (!_p) + return nullptr; + + return _p->fullName(); +} + +bool File::isFile() const { + if (!_p) + return false; + + return _p->isFile(); +} + +bool File::isDirectory() const { + if (!_p) + return false; + + return _p->isDirectory(); +} + +void File::rewindDirectory() { + if (!_fakeDir) { + _fakeDir = std::make_shared(_baseFS->openDir(fullName())); + } else { + _fakeDir->rewind(); + } +} + +File File::openNextFile() { + if (!_fakeDir) { + _fakeDir = std::make_shared(_baseFS->openDir(fullName())); + } + _fakeDir->next(); + return _fakeDir->openFile("r"); +} + + File Dir::openFile(const char* mode) { if (!_impl) { return File(); @@ -133,7 +171,7 @@ File Dir::openFile(const char* mode) { return File(); } - return File(_impl->openFile(om, am)); + return File(_impl->openFile(om, am), _baseFS); } String Dir::fileName() { @@ -152,6 +190,20 @@ size_t Dir::fileSize() { return _impl->fileSize(); } +bool Dir::isFile() const { + if (!_impl) + return false; + + return _impl->isFile(); +} + +bool Dir::isDirectory() const { + if (!_impl) + return false; + + return _impl->isDirectory(); +} + bool Dir::next() { if (!_impl) { return false; @@ -160,6 +212,14 @@ bool Dir::next() { return _impl->next(); } +bool Dir::rewind() { + if (!_impl) { + return false; + } + + return _impl->rewind(); +} + bool FS::begin() { if (!_impl) { return false; @@ -202,8 +262,7 @@ File FS::open(const char* path, const char* mode) { DEBUGV("FS::open: invalid mode `%s`\r\n", mode); return File(); } - - return File(_impl->open(path, om, am)); + return File(_impl->open(path, om, am), this); } bool FS::exists(const char* path) { @@ -221,7 +280,8 @@ Dir FS::openDir(const char* path) { if (!_impl) { return Dir(); } - return Dir(_impl->openDir(path)); + DirImplPtr p = _impl->openDir(path); + return Dir(p, this); } Dir FS::openDir(const String& path) { @@ -239,6 +299,28 @@ bool FS::remove(const String& path) { return remove(path.c_str()); } +bool FS::rmdir(const char* path) { + if (!_impl) { + return false; + } + return _impl->rmdir(path); +} + +bool FS::rmdir(const String& path) { + return rmdir(path.c_str()); +} + +bool FS::mkdir(const char* path) { + if (!_impl) { + return false; + } + return _impl->mkdir(path); +} + +bool FS::mkdir(const String& path) { + return mkdir(path.c_str()); +} + bool FS::rename(const char* pathFrom, const char* pathTo) { if (!_impl) { return false; @@ -251,6 +333,7 @@ bool FS::rename(const String& pathFrom, const String& pathTo) { } + static bool sflags(const char* mode, OpenMode& om, AccessMode& am) { switch (mode[0]) { case 'r': diff --git a/cores/esp8266/FS.h b/cores/esp8266/FS.h index 79620f96c8..0df42f9f8e 100644 --- a/cores/esp8266/FS.h +++ b/cores/esp8266/FS.h @@ -28,6 +28,7 @@ namespace fs { class File; class Dir; +class FS; class FileImpl; typedef std::shared_ptr FileImplPtr; @@ -48,7 +49,7 @@ enum SeekMode { class File : public Stream { public: - File(FileImplPtr p = FileImplPtr()) : _p(p) {} + File(FileImplPtr p = FileImplPtr(), FS *baseFS = nullptr) : _p(p), _fakeDir(nullptr), _baseFS(baseFS) { } // Print methods: size_t write(uint8_t) override; @@ -72,22 +73,41 @@ class File : public Stream void close(); operator bool() const; const char* name() const; + const char* fullName() const; // Includes path + + bool isFile() const; + bool isDirectory() const; + + // Arduino "class SD" methods for compatibility + size_t write(const char *str) { return write((const uint8_t*)str, strlen(str)); } + void rewindDirectory(); + File openNextFile(); protected: FileImplPtr _p; + + // Arduino SD class emulation + std::shared_ptr _fakeDir; + FS *_baseFS; }; class Dir { public: - Dir(DirImplPtr impl = DirImplPtr()): _impl(impl) { } + Dir(DirImplPtr impl = DirImplPtr(), FS *baseFS = nullptr): _impl(impl), _baseFS(baseFS) { } File openFile(const char* mode); + String fileName(); size_t fileSize(); + bool isFile() const; + bool isDirectory() const; + bool next(); + bool rewind(); protected: DirImplPtr _impl; + FS *_baseFS; }; struct FSInfo { @@ -125,6 +145,12 @@ class FS bool rename(const char* pathFrom, const char* pathTo); bool rename(const String& pathFrom, const String& pathTo); + bool mkdir(const char* path); + bool mkdir(const String& path); + + bool rmdir(const char* path); + bool rmdir(const String& path); + protected: FSImplPtr _impl; }; diff --git a/cores/esp8266/FSImpl.h b/cores/esp8266/FSImpl.h index cf84be6a6e..2574563189 100644 --- a/cores/esp8266/FSImpl.h +++ b/cores/esp8266/FSImpl.h @@ -36,6 +36,9 @@ class FileImpl { virtual size_t size() const = 0; virtual void close() = 0; virtual const char* name() const = 0; + virtual const char* fullName() const = 0; + virtual bool isFile() const = 0; + virtual bool isDirectory() const = 0; }; enum OpenMode { @@ -57,7 +60,10 @@ class DirImpl { virtual FileImplPtr openFile(OpenMode openMode, AccessMode accessMode) = 0; virtual const char* fileName() = 0; virtual size_t fileSize() = 0; + virtual bool isFile() const = 0; + virtual bool isDirectory() const = 0; virtual bool next() = 0; + virtual bool rewind() = 0; }; class FSImpl { @@ -72,7 +78,8 @@ class FSImpl { virtual DirImplPtr openDir(const char* path) = 0; virtual bool rename(const char* pathFrom, const char* pathTo) = 0; virtual bool remove(const char* path) = 0; - + virtual bool mkdir(const char* path) = 0; + virtual bool rmdir(const char* path) = 0; }; } // namespace fs diff --git a/cores/esp8266/Updater.cpp b/cores/esp8266/Updater.cpp index de57ef3c66..c0d2359296 100644 --- a/cores/esp8266/Updater.cpp +++ b/cores/esp8266/Updater.cpp @@ -24,7 +24,7 @@ extern "C" { #include "user_interface.h" } -extern "C" uint32_t _SPIFFS_start; +extern "C" uint32_t _FS_start; UpdaterClass::UpdaterClass() : _async(false) @@ -81,8 +81,8 @@ bool UpdaterClass::begin(size_t size, int command, int ledPin, uint8_t ledOn) { } #ifdef DEBUG_UPDATER - if (command == U_SPIFFS) { - DEBUG_UPDATER.println(F("[begin] Update SPIFFS.")); + if (command == U_FS) { + DEBUG_UPDATER.println(F("[begin] Update Filesystem.")); } #endif @@ -106,7 +106,7 @@ bool UpdaterClass::begin(size_t size, int command, int ledPin, uint8_t ledOn) { //size of current sketch rounded to a sector size_t currentSketchSize = (ESP.getSketchSize() + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1)); //address of the end of the space available for sketch and update - uintptr_t updateEndAddress = (uintptr_t)&_SPIFFS_start - 0x40200000; + uintptr_t updateEndAddress = (uintptr_t)&_FS_start - 0x40200000; //size of the update rounded to a sector size_t roundedSize = (size + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1)); //address where we will start writing the update @@ -124,8 +124,8 @@ bool UpdaterClass::begin(size_t size, int command, int ledPin, uint8_t ledOn) { return false; } } - else if (command == U_SPIFFS) { - updateStartAddress = (uintptr_t)&_SPIFFS_start - 0x40200000; + else if (command == U_FS) { + updateStartAddress = (uintptr_t)&_FS_start - 0x40200000; } else { // unknown command @@ -273,8 +273,8 @@ bool UpdaterClass::end(bool evenIfRemaining){ #ifdef DEBUG_UPDATER DEBUG_UPDATER.printf_P(PSTR("Staged: address:0x%08X, size:0x%08zX\n"), _startAddress, _size); } - else if (_command == U_SPIFFS) { - DEBUG_UPDATER.printf_P(PSTR("SPIFFS: address:0x%08X, size:0x%08zX\n"), _startAddress, _size); + else if (_command == U_FS) { + DEBUG_UPDATER.printf_P(PSTR("Filesystem: address:0x%08X, size:0x%08zX\n"), _startAddress, _size); #endif } @@ -386,7 +386,7 @@ bool UpdaterClass::_verifyHeader(uint8_t data) { return false; } return true; - } else if(_command == U_SPIFFS) { + } else if(_command == U_FS) { // no check of SPIFFS possible with first byte. return true; } @@ -420,7 +420,7 @@ bool UpdaterClass::_verifyEnd() { } return true; - } else if(_command == U_SPIFFS) { + } else if(_command == U_FS) { // SPIFFS is already over written checks make no sense any more. return true; } diff --git a/cores/esp8266/Updater.h b/cores/esp8266/Updater.h index e9eea05ef7..8e486afcc8 100644 --- a/cores/esp8266/Updater.h +++ b/cores/esp8266/Updater.h @@ -20,7 +20,7 @@ #define UPDATE_ERROR_SIGN (12) #define U_FLASH 0 -#define U_SPIFFS 100 +#define U_FS 100 #define U_AUTH 200 #ifdef DEBUG_ESP_UPDATER diff --git a/cores/esp8266/spiffs_hal.cpp b/cores/esp8266/flash_hal.cpp similarity index 90% rename from cores/esp8266/spiffs_hal.cpp rename to cores/esp8266/flash_hal.cpp index 2d66bd54df..4602f8309a 100644 --- a/cores/esp8266/spiffs_hal.cpp +++ b/cores/esp8266/flash_hal.cpp @@ -21,8 +21,8 @@ #include #include #include -#include "spiffs/spiffs.h" #include "debug.h" +#include "flash_hal.h" extern "C" { #include "c_types.h" @@ -42,10 +42,10 @@ alignedBegin: ^ alignedEnd: ^ */ -int32_t spiffs_hal_read(uint32_t addr, uint32_t size, uint8_t *dst) { +int32_t flash_hal_read(uint32_t addr, uint32_t size, uint8_t *dst) { optimistic_yield(10000); - uint32_t result = SPIFFS_OK; + uint32_t result = FLASH_HAL_OK; uint32_t alignedBegin = (addr + 3) & (~3); uint32_t alignedEnd = (addr + size) & (~3); if (alignedEnd < alignedBegin) { @@ -58,7 +58,7 @@ int32_t spiffs_hal_read(uint32_t addr, uint32_t size, uint8_t *dst) { if (!ESP.flashRead(alignedBegin - 4, &tmp, 4)) { DEBUGV("_spif_read(%d) addr=%x size=%x ab=%x ae=%x\r\n", __LINE__, addr, size, alignedBegin, alignedEnd); - return SPIFFS_ERR_INTERNAL; + return FLASH_HAL_READ_ERROR; } memcpy(dst, ((uint8_t*) &tmp) + 4 - nb, nb); } @@ -68,7 +68,7 @@ int32_t spiffs_hal_read(uint32_t addr, uint32_t size, uint8_t *dst) { alignedEnd - alignedBegin)) { DEBUGV("_spif_read(%d) addr=%x size=%x ab=%x ae=%x\r\n", __LINE__, addr, size, alignedBegin, alignedEnd); - return SPIFFS_ERR_INTERNAL; + return FLASH_HAL_READ_ERROR; } } @@ -78,7 +78,7 @@ int32_t spiffs_hal_read(uint32_t addr, uint32_t size, uint8_t *dst) { if (!ESP.flashRead(alignedEnd, &tmp, 4)) { DEBUGV("_spif_read(%d) addr=%x size=%x ab=%x ae=%x\r\n", __LINE__, addr, size, alignedBegin, alignedEnd); - return SPIFFS_ERR_INTERNAL; + return FLASH_HAL_READ_ERROR; } memcpy(dst + size - nb, &tmp, nb); @@ -99,7 +99,7 @@ int32_t spiffs_hal_read(uint32_t addr, uint32_t size, uint8_t *dst) { static const int UNALIGNED_WRITE_BUFFER_SIZE = 512; -int32_t spiffs_hal_write(uint32_t addr, uint32_t size, uint8_t *src) { +int32_t flash_hal_write(uint32_t addr, uint32_t size, const uint8_t *src) { optimistic_yield(10000); uint32_t alignedBegin = (addr + 3) & (~3); @@ -116,7 +116,7 @@ int32_t spiffs_hal_write(uint32_t addr, uint32_t size, uint8_t *src) { if (!ESP.flashWrite(alignedBegin - 4, (uint32_t*) tmp, 4)) { DEBUGV("_spif_write(%d) addr=%x size=%x ab=%x ae=%x\r\n", __LINE__, addr, size, alignedBegin, alignedEnd); - return SPIFFS_ERR_INTERNAL; + return FLASH_HAL_WRITE_ERROR; } } @@ -128,7 +128,7 @@ int32_t spiffs_hal_write(uint32_t addr, uint32_t size, uint8_t *src) { alignedEnd - alignedBegin)) { DEBUGV("_spif_write(%d) addr=%x size=%x ab=%x ae=%x\r\n", __LINE__, addr, size, alignedBegin, alignedEnd); - return SPIFFS_ERR_INTERNAL; + return FLASH_HAL_WRITE_ERROR; } } else { @@ -140,7 +140,7 @@ int32_t spiffs_hal_write(uint32_t addr, uint32_t size, uint8_t *src) { if (!ESP.flashWrite(alignedBegin, (uint32_t*) buf, willCopy)) { DEBUGV("_spif_write(%d) addr=%x size=%x ab=%x ae=%x\r\n", __LINE__, addr, size, alignedBegin, alignedEnd); - return SPIFFS_ERR_INTERNAL; + return FLASH_HAL_WRITE_ERROR; } sizeLeft -= willCopy; @@ -158,14 +158,14 @@ int32_t spiffs_hal_write(uint32_t addr, uint32_t size, uint8_t *src) { if (!ESP.flashWrite(alignedEnd, &tmp, 4)) { DEBUGV("_spif_write(%d) addr=%x size=%x ab=%x ae=%x\r\n", __LINE__, addr, size, alignedBegin, alignedEnd); - return SPIFFS_ERR_INTERNAL; + return FLASH_HAL_WRITE_ERROR; } } - return SPIFFS_OK; + return FLASH_HAL_OK; } -int32_t spiffs_hal_erase(uint32_t addr, uint32_t size) { +int32_t flash_hal_erase(uint32_t addr, uint32_t size) { if ((size & (SPI_FLASH_SEC_SIZE - 1)) != 0 || (addr & (SPI_FLASH_SEC_SIZE - 1)) != 0) { DEBUGV("_spif_erase called with addr=%x, size=%d\r\n", addr, size); @@ -177,8 +177,8 @@ int32_t spiffs_hal_erase(uint32_t addr, uint32_t size) { optimistic_yield(10000); if (!ESP.flashEraseSector(sector + i)) { DEBUGV("_spif_erase addr=%x size=%d i=%d\r\n", addr, size, i); - return SPIFFS_ERR_INTERNAL; + return FLASH_HAL_ERASE_ERROR; } } - return SPIFFS_OK; + return FLASH_HAL_OK; } diff --git a/cores/esp8266/flash_hal.h b/cores/esp8266/flash_hal.h new file mode 100644 index 0000000000..13219bb18f --- /dev/null +++ b/cores/esp8266/flash_hal.h @@ -0,0 +1,49 @@ +#ifndef flash_hal_h +#define flash_hal_h + +/* + flash_hal.h - API for accessing raw flash for filesystems + Copyright (c) 2015 Ivan Grokhotkov. All rights reserved. + + This code was influenced by NodeMCU and Sming libraries, and first version of + Arduino wrapper written by Hristo Gochkov. + + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +#ifdef ARDUINO +extern "C" uint32_t _FS_start; +extern "C" uint32_t _FS_end; +extern "C" uint32_t _FS_page; +extern "C" uint32_t _FS_block; + +#define FS_PHYS_ADDR ((uint32_t) (&_FS_start) - 0x40200000) +#define FS_PHYS_SIZE ((uint32_t) (&_FS_end) - (uint32_t) (&_FS_start)) +#define FS_PHYS_PAGE ((uint32_t) &_FS_page) +#define FS_PHYS_BLOCK ((uint32_t) &_FS_block) +#endif + +// Return values of the following functions +#define FLASH_HAL_OK (0) +#define FLASH_HAL_READ_ERROR (-1) +#define FLASH_HAL_WRITE_ERROR (-2) +#define FLASH_HAL_ERASE_ERROR (-3) + +extern int32_t flash_hal_write(uint32_t addr, uint32_t size, const uint8_t *src); +extern int32_t flash_hal_erase(uint32_t addr, uint32_t size); +extern int32_t flash_hal_read(uint32_t addr, uint32_t size, uint8_t *dst); + +#endif // !defined(flash_hal_h) diff --git a/cores/esp8266/spiffs/spiffs.h b/cores/esp8266/spiffs/spiffs.h index 534c3df8bd..e659bf2f37 100644 --- a/cores/esp8266/spiffs/spiffs.h +++ b/cores/esp8266/spiffs/spiffs.h @@ -84,7 +84,7 @@ struct spiffs_t; /* spi read call function type */ typedef s32_t (*spiffs_read)(struct spiffs_t *fs, u32_t addr, u32_t size, u8_t *dst); /* spi write call function type */ -typedef s32_t (*spiffs_write)(struct spiffs_t *fs, u32_t addr, u32_t size, u8_t *src); +typedef s32_t (*spiffs_write)(struct spiffs_t *fs, u32_t addr, u32_t size, const u8_t *src); /* spi erase call function type */ typedef s32_t (*spiffs_erase)(struct spiffs_t *fs, u32_t addr, u32_t size); @@ -93,7 +93,7 @@ typedef s32_t (*spiffs_erase)(struct spiffs_t *fs, u32_t addr, u32_t size); /* spi read call function type */ typedef s32_t (*spiffs_read)(u32_t addr, u32_t size, u8_t *dst); /* spi write call function type */ -typedef s32_t (*spiffs_write)(u32_t addr, u32_t size, u8_t *src); +typedef s32_t (*spiffs_write)(u32_t addr, u32_t size, const u8_t *src); /* spi erase call function type */ typedef s32_t (*spiffs_erase)(u32_t addr, u32_t size); #endif // SPIFFS_HAL_CALLBACK_EXTRA diff --git a/cores/esp8266/spiffs_api.cpp b/cores/esp8266/spiffs_api.cpp index f3fcfa2354..833dd3edb0 100644 --- a/cores/esp8266/spiffs_api.cpp +++ b/cores/esp8266/spiffs_api.cpp @@ -25,6 +25,8 @@ using namespace fs; +namespace spiffs_impl { + FileImplPtr SPIFFSImpl::open(const char* path, OpenMode openMode, AccessMode accessMode) { if (!isSpiffsFilenameValid(path)) { @@ -108,6 +110,8 @@ bool isSpiffsFilenameValid(const char* name) return len > 0 && len < SPIFFS_OBJ_NAME_LEN; } +}; // namespace + // these symbols should be defined in the linker script for each flash layout #ifndef CORE_MOCK #ifdef ARDUINO @@ -116,11 +120,11 @@ bool isSpiffsFilenameValid(const char* name) #endif #if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SPIFFS) -FS SPIFFS = FS(FSImplPtr(new SPIFFSImpl( - SPIFFS_PHYS_ADDR, - SPIFFS_PHYS_SIZE, - SPIFFS_PHYS_PAGE, - SPIFFS_PHYS_BLOCK, +FS SPIFFS = FS(FSImplPtr(new spiffs_impl::SPIFFSImpl( + FS_PHYS_ADDR, + FS_PHYS_SIZE, + FS_PHYS_PAGE, + FS_PHYS_BLOCK, SPIFFS_MAX_OPEN_FILES))); #endif // ARDUINO #endif // !CORE_MOCK diff --git a/cores/esp8266/spiffs_api.h b/cores/esp8266/spiffs_api.h index 08f9eb8791..fbfdd1a641 100644 --- a/cores/esp8266/spiffs_api.h +++ b/cores/esp8266/spiffs_api.h @@ -32,24 +32,11 @@ #include "spiffs/spiffs.h" #include "debug.h" #include "flash_utils.h" +#include "flash_hal.h" using namespace fs; -#ifdef ARDUINO -extern "C" uint32_t _SPIFFS_start; -extern "C" uint32_t _SPIFFS_end; -extern "C" uint32_t _SPIFFS_page; -extern "C" uint32_t _SPIFFS_block; - -#define SPIFFS_PHYS_ADDR ((uint32_t) (&_SPIFFS_start) - 0x40200000) -#define SPIFFS_PHYS_SIZE ((uint32_t) (&_SPIFFS_end) - (uint32_t) (&_SPIFFS_start)) -#define SPIFFS_PHYS_PAGE ((uint32_t) &_SPIFFS_page) -#define SPIFFS_PHYS_BLOCK ((uint32_t) &_SPIFFS_block) -#endif - -extern int32_t spiffs_hal_write(uint32_t addr, uint32_t size, uint8_t *src); -extern int32_t spiffs_hal_erase(uint32_t addr, uint32_t size); -extern int32_t spiffs_hal_read(uint32_t addr, uint32_t size, uint8_t *dst); +namespace spiffs_impl { int getSpiffsMode(OpenMode openMode, AccessMode accessMode); bool isSpiffsFilenameValid(const char* name); @@ -124,10 +111,22 @@ class SPIFFSImpl : public FSImpl return true; } + bool mkdir(const char* path) override + { + (void)path; + return false; + } + + bool rmdir(const char* path) override + { + (void)path; + return false; + } + bool begin() override { #if defined(ARDUINO) && !defined(CORE_MOCK) - if (&_SPIFFS_end <= &_SPIFFS_start) + if (&_FS_end <= &_FS_start) return false; #endif if (SPIFFS_mounted(&_fs) != 0) { @@ -271,6 +270,17 @@ class SPIFFSImpl : public FSImpl spiffs _fs; + // Flash hal wrapper functions to get proper SPIFFS error codes + static int32_t spiffs_hal_write(uint32_t addr, uint32_t size, const uint8_t *src) { + return flash_hal_write(addr, size, src) == FLASH_HAL_OK ? SPIFFS_OK : SPIFFS_ERR_INTERNAL; + } + static int32_t spiffs_hal_erase(uint32_t addr, uint32_t size) { + return flash_hal_erase(addr, size) == FLASH_HAL_OK ? SPIFFS_OK : SPIFFS_ERR_INTERNAL; + } + static int32_t spiffs_hal_read(uint32_t addr, uint32_t size, uint8_t *dst) { + return flash_hal_read(addr, size, dst) == FLASH_HAL_OK ? SPIFFS_OK : SPIFFS_ERR_INTERNAL; + } + uint32_t _start; uint32_t _size; uint32_t _pageSize; @@ -376,6 +386,18 @@ class SPIFFSFileImpl : public FileImpl return _stat.size; } + bool isFile() const override + { + // No such thing as directories on SPIFFS + return _fd ? true : false; + } + + bool isDirectory() const override + { + // No such thing as directories on SPIFFS + return false; + } + void close() override { CHECKFD(); @@ -391,6 +413,11 @@ class SPIFFSFileImpl : public FileImpl return (const char*) _stat.name; } + const char* fullName() const override + { + return name(); // No dirs, they're the same on SPIFFS + } + protected: void _getStat() const { @@ -416,6 +443,7 @@ class SPIFFSDirImpl : public DirImpl : _pattern(pattern) , _fs(fs) , _dir(dir) + , _dirHead(dir) , _valid(false) { } @@ -459,6 +487,18 @@ class SPIFFSDirImpl : public DirImpl return _dirent.size; } + bool isFile() const override + { + // No such thing as directories on SPIFFS + return _valid; + } + + bool isDirectory() const override + { + // No such thing as directories on SPIFFS + return false; + } + bool next() override { const int n = _pattern.length(); @@ -469,13 +509,22 @@ class SPIFFSDirImpl : public DirImpl return _valid; } + bool rewind() override + { + _dir = _dirHead; + _valid = false; + return true; + } + protected: String _pattern; SPIFFSImpl* _fs; spiffs_DIR _dir; + spiffs_DIR _dirHead; // The pointer to the start of this dir spiffs_dirent _dirent; bool _valid; }; +}; // namespace #endif//spiffs_api_h diff --git a/doc/filesystem.rst b/doc/filesystem.rst index 606a2707d5..69505548f0 100644 --- a/doc/filesystem.rst +++ b/doc/filesystem.rst @@ -63,10 +63,39 @@ following include to the sketch: #include "FS.h" -File system limitations ------------------------ +SPIFFS and LittleFS +------------------- -The filesystem implementation for ESP8266 had to accomodate the +There are two filesystems for utilizing the onboard flash on the ESP8266: +SPIFFS and LittleFS. + +SPIFFS is the original filesystem and is ideal for space and RAM +constrained applications that utilize many small files and care +about static and dynamic wear levelling and don't need true directory +support. Filesystem overhead on the flash is minimal as well. + +LittleFS is recently added and focuses on higher performance and +directory support, but has higher filesystem and per-file overhead +(4K minimum vs. SPIFFS' 256 byte minimum file allocation unit). + +They share a compatible API but have incompatible on-flash +implementations, so it is important to choose one or the per project +as attempting to mount a SPIFFS volume under LittleFS may result +in a format operation and definitely will not preserve any files, +and vice-versa. + +The actual ``File`` and ``Dir`` objects returned from either +filesystem behave in the same manner and documentation is applicable +to both. To convert most applications from SPIFFS to LittleFS +simply requires changing the ``SPIFFS.begin()`` to ``LittleFS.begin()`` +and ``SPIFFS.open()`` to ``LittleFS.open()`` with the rest of the +code remaining untouched. + + +SPIFFS file system limitations +------------------------------ + +The SPIFFS implementation for ESP8266 had to accomodate the constraints of the chip, among which its limited RAM. `SPIFFS `__ was selected because it is designed for small systems, but that comes at the cost of some @@ -100,6 +129,37 @@ For more details on the internals of SPIFFS implementation, see the `SPIFFS readme file `__. + +LittleFS file system limitations +-------------------------------- + +The LittleFS implementation for the ESP8266 supports filenames of up +to 31 characters + terminating zero (i.e. ``char filename[32]``), and +as many subdirectories as space permits. + +Filenames are assumed to be in the root directory if no initial "/" is +present. + +Opening files in subdirectories requires specifying the complete path to +the file (i.e. ``open("/sub/dir/file.txt");``). Subdirectories are +automatically created when you attempt to create a file in a subdirectory, +and when the last file in a subdirectory is removed the subdirectory +itself is automatically deleted. This is because there was no ``mkdir()`` +method in the existing SPIFFS filesystem. + +Unlike SPIFFS, the actual file descriptors are allocated as requested +by the application, so in low memory conditions you may not be able to +open new files. Conversely, this also means that only file descriptors +used will actually take space on the heap. + +Because there are directories, the ``openDir`` method behaves differently +than SPIFFS. Whereas SPIFFS will return files in "subdirectories" when +you traverse a ``Dir::next()`` (because they really aren't subdirs but +simply files with "/"s in their names), LittleFS will only return files +in the specific subdirectory. This mimics the POSIX behavior for +directory traversal most C programmers are used to. + + Uploading files to file system ------------------------------ @@ -122,8 +182,15 @@ directory into ESP8266 flash file system. uploading the files into ESP8266 flash file system. When done, IDE status bar will display ``SPIFFS Image Uploaded`` message. -File system object (SPIFFS) ---------------------------- +*ESP8266LittleFS* is the equivalent tool for LittleFS. + +- Download the tool: https://github.com/earlephilhower/arduino-esp8266littlefs-plugin/releases +- Install as above +- To upload a LittleFS filesystem use Tools > ESP8266 LittleFS Data Upload + + +File system object (SPIFFS/LittleFS) +------------------------------------ begin ~~~~~ @@ -131,20 +198,27 @@ begin .. code:: cpp SPIFFS.begin() + or LittleFS.begin() -This method mounts SPIFFS file system. It must be called before any +This method mounts file system. It must be called before any other FS APIs are used. Returns *true* if file system was mounted successfully, false otherwise. +Note that both methods will automatically format the filesystem +if one is not detected. This means that if you attempt a +``SPIFFS.begin()`` on a LittleFS filesystem you will lose all data +on that filesystem, and vice-versa. + end ~~~ .. code:: cpp SPIFFS.end() + or LittleFS.end() -This method unmounts SPIFFS file system. Use this method before updating -SPIFFS using OTA. +This method unmounts the file system. Use this method before updating +the file system using OTA. format ~~~~~~ @@ -152,6 +226,7 @@ format .. code:: cpp SPIFFS.format() + or LittleFS.format() Formats the file system. May be called either before or after calling ``begin``. Returns *true* if formatting was successful. @@ -162,6 +237,7 @@ open .. code:: cpp SPIFFS.open(path, mode) + or LittleFS.open(path, mode) Opens a file. ``path`` should be an absolute path starting with a slash (e.g. ``/dir/filename.txt``). ``mode`` is a string specifying access @@ -208,17 +284,40 @@ exists .. code:: cpp SPIFFS.exists(path) + or LittleFS.exists(path) Returns *true* if a file with given path exists, *false* otherwise. +mkdir +~~~~~ + +.. code:: cpp + + LittleFS.mkdir(path) + +Returns *true* if the directory creation succeeded, *false* otherwise. + +rmdir +~~~~~ + +.. code:: cpp + + LittleFS.rmdir(path) + +Returns *true* if the directory was successfully removed, *false* otherwise. + + openDir ~~~~~~~ .. code:: cpp SPIFFS.openDir(path) + or LittleFS.openDir(path) Opens a directory given its absolute path. Returns a *Dir* object. +Please note the previous discussion on the difference in behavior between +LittleFS and SPIFFS for this call. remove ~~~~~~ @@ -226,6 +325,7 @@ remove .. code:: cpp SPIFFS.remove(path) + or LittleFS.remove(path) Deletes the file given its absolute path. Returns *true* if file was deleted successfully. @@ -236,6 +336,7 @@ rename .. code:: cpp SPIFFS.rename(pathFrom, pathTo) + or LittleFS.rename(pathFrom, pathTo) Renames file from ``pathFrom`` to ``pathTo``. Paths must be absolute. Returns *true* if file was renamed successfully. @@ -247,6 +348,7 @@ info FSInfo fs_info; SPIFFS.info(fs_info); + or LittleFS.info(fs_info); Fills `FSInfo structure <#filesystem-information-structure>`__ with information about the file system. Returns ``true`` is successful, @@ -268,8 +370,8 @@ Filesystem information structure This is the structure which may be filled using FS::info method. - ``totalBytes`` — total size of useful data on the file system - -``usedBytes`` — number of bytes used by files - ``blockSize`` — SPIFFS -block size - ``pageSize`` — SPIFFS logical page size - ``maxOpenFiles`` +``usedBytes`` — number of bytes used by files - ``blockSize`` — filesystem +block size - ``pageSize`` — filesystem logical page size - ``maxOpenFiles`` — max number of files which may be open simultaneously - ``maxPathLength`` — max file name length (including one byte for zero termination) @@ -278,14 +380,14 @@ Directory object (Dir) ---------------------- The purpose of *Dir* object is to iterate over files inside a directory. -It provides the methods: ``next()``, ``fileName()``, ``fileSize()`` , and -``openFile(mode)``. +It provides multiple access methods. The following example shows how it should be used: .. code:: cpp Dir dir = SPIFFS.openDir("/data"); + // or Dir dir = LittleFS.openDir("/data"); while (dir.next()) { Serial.print(dir.fileName()); if(dir.fileSize()) { @@ -313,16 +415,33 @@ fileSize Returns the size of the current file pointed to by the internal iterator. +isFile +~~~~~~ + +Returns *true* if the current file pointed to by +the internal iterator is a File. + +isDirectory +~~~~~~~~~~~ + +Returns *true* if the current file pointed to by +the internal iterator is a Directory. + openFile ~~~~~~~~ This method takes *mode* argument which has the same meaning as -for ``SPIFFS.open()`` function. +for ``SPIFFS/LittleFS.open()`` function. + +rewind +~~~~~~ + +Resets the internal pointer to the start of the directory. File object ----------- -``SPIFFS.open()`` and ``dir.openFile()`` functions return a *File* object. +``SPIFFS/LittleFS.open()`` and ``dir.openFile()`` functions return a *File* object. This object supports all the functions of *Stream*, so you can use ``readBytes``, ``findUntil``, ``parseInt``, ``println``, and all other *Stream* methods. @@ -373,9 +492,42 @@ name String name = file.name(); -Returns file name, as ``const char*``. Convert it to *String* for +Returns short (no-path) file name, as ``const char*``. Convert it to *String* for storage. +fullName +~~~~~~~~ + +.. code:: cpp + + // Filesystem: + // testdir/ + // file1 + Dir d = LittleFS.openDir("testdir/"); + File f = d.openFile("r"); + // f.name() == "file1", f.fullName() == "testdir/file1" + +Returns the full path file name as a ``const char*``. + +isFile +~~~~~~ + +.. code:: cpp + + bool amIAFile = file.isFile(); + +Returns *true* if this File points to a real file. + +isDirectory +~~~~~~~~~~~ + +.. code:: cpp + + bool amIADir = file.isDir(); + +Returns *true* if this File points to a directory (used for emulation +of the SD.* interfaces with the ``openNextFile`` method). + close ~~~~~ @@ -385,3 +537,29 @@ close Close the file. No other operations should be performed on *File* object after ``close`` function was called. + +openNextFile (compatibiity method, not recommended for new code) +~~~~~~~~~~~~ + +.. code:: cpp + + File root = LittleFS.open("/"); + File file1 = root.openNextFile(); + File files = root.openNextFile(); + +Opens the next file in the directory pointed to by the File. Only valid +when ``File.isDirectory() == true``. + +rewindDirectory (compatibiity method, not recommended for new code) +~~~~~~~~~~~~~~~ + +.. code:: cpp + + File root = LittleFS.open("/"); + File file1 = root.openNextFile(); + file1.close(); + root.rewindDirectory(); + file1 = root.openNextFile(); // Opens first file in dir again + +Resets the ``openNextFile`` pointer to the top of the directory. Only +valid when ``File.isDirectory() == true``. diff --git a/libraries/ArduinoOTA/ArduinoOTA.cpp b/libraries/ArduinoOTA/ArduinoOTA.cpp index e2125732be..d89b9ccb73 100644 --- a/libraries/ArduinoOTA/ArduinoOTA.cpp +++ b/libraries/ArduinoOTA/ArduinoOTA.cpp @@ -182,7 +182,7 @@ void ArduinoOTAClass::_onRx(){ if (_state == OTA_IDLE) { int cmd = parseInt(); - if (cmd != U_FLASH && cmd != U_SPIFFS) + if (cmd != U_FLASH && cmd != U_FS) return; _ota_ip = _udp_ota->getRemoteAddress(); _cmd = cmd; diff --git a/libraries/ArduinoOTA/ArduinoOTA.h b/libraries/ArduinoOTA/ArduinoOTA.h index a02dbc485c..1dfcaeed38 100644 --- a/libraries/ArduinoOTA/ArduinoOTA.h +++ b/libraries/ArduinoOTA/ArduinoOTA.h @@ -65,7 +65,7 @@ class ArduinoOTAClass //Call this in loop() to run the service. Also calls MDNS.update() when begin() or begin(true) is used. void handle(); - //Gets update command type after OTA has started. Either U_FLASH or U_SPIFFS + //Gets update command type after OTA has started. Either U_FLASH or U_FS int getCommand(); private: diff --git a/libraries/ArduinoOTA/examples/BasicOTA/BasicOTA.ino b/libraries/ArduinoOTA/examples/BasicOTA/BasicOTA.ino index 214eb82952..ddc5629741 100644 --- a/libraries/ArduinoOTA/examples/BasicOTA/BasicOTA.ino +++ b/libraries/ArduinoOTA/examples/BasicOTA/BasicOTA.ino @@ -39,11 +39,11 @@ void setup() { String type; if (ArduinoOTA.getCommand() == U_FLASH) { type = "sketch"; - } else { // U_SPIFFS + } else { // U_FS type = "filesystem"; } - // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end() + // NOTE: if updating FS this would be the place to unmount FS using FS.end() Serial.println("Start updating " + type); }); ArduinoOTA.onEnd([]() { diff --git a/libraries/EEPROM/EEPROM.cpp b/libraries/EEPROM/EEPROM.cpp index 0ec376abc1..fa1aa3ee06 100644 --- a/libraries/EEPROM/EEPROM.cpp +++ b/libraries/EEPROM/EEPROM.cpp @@ -30,7 +30,7 @@ extern "C" { #include "spi_flash.h" } -extern "C" uint32_t _SPIFFS_end; +extern "C" uint32_t _FS_end; EEPROMClass::EEPROMClass(uint32_t sector) : _sector(sector) @@ -41,7 +41,7 @@ EEPROMClass::EEPROMClass(uint32_t sector) } EEPROMClass::EEPROMClass(void) -: _sector((((uint32_t)&_SPIFFS_end - 0x40200000) / SPI_FLASH_SEC_SIZE)) +: _sector((((uint32_t)&_FS_end - 0x40200000) / SPI_FLASH_SEC_SIZE)) , _data(0) , _size(0) , _dirty(false) diff --git a/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.cpp b/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.cpp index b23ad4631c..764ee3e8d5 100644 --- a/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.cpp +++ b/libraries/ESP8266httpUpdate/src/ESP8266httpUpdate.cpp @@ -26,8 +26,8 @@ #include "ESP8266httpUpdate.h" #include -extern "C" uint32_t _SPIFFS_start; -extern "C" uint32_t _SPIFFS_end; +extern "C" uint32_t _FS_start; +extern "C" uint32_t _FS_end; ESP8266HTTPUpdate::ESP8266HTTPUpdate(void) : _httpClientTimeout(8000), _ledPin(-1) @@ -320,7 +320,7 @@ HTTPUpdateResult ESP8266HTTPUpdate::handleUpdate(HTTPClient& http, const String& if(len > 0) { bool startUpdate = true; if(spiffs) { - size_t spiffsSize = ((size_t) &_SPIFFS_end - (size_t) &_SPIFFS_start); + size_t spiffsSize = ((size_t) &_FS_end - (size_t) &_FS_start); if(len > (int) spiffsSize) { DEBUG_HTTP_UPDATE("[httpUpdate] spiffsSize to low (%d) needed: %d\n", spiffsSize, len); startUpdate = false; @@ -347,8 +347,8 @@ HTTPUpdateResult ESP8266HTTPUpdate::handleUpdate(HTTPClient& http, const String& int command; if(spiffs) { - command = U_SPIFFS; - DEBUG_HTTP_UPDATE("[httpUpdate] runUpdate spiffs...\n"); + command = U_FS; + DEBUG_HTTP_UPDATE("[httpUpdate] runUpdate filesystem...\n"); } else { command = U_FLASH; DEBUG_HTTP_UPDATE("[httpUpdate] runUpdate flash...\n"); diff --git a/libraries/LittleFS/examples/SpeedTest/SpeedTest.ino b/libraries/LittleFS/examples/SpeedTest/SpeedTest.ino new file mode 100644 index 0000000000..5b7dd453a0 --- /dev/null +++ b/libraries/LittleFS/examples/SpeedTest/SpeedTest.ino @@ -0,0 +1,125 @@ +// Simple speed test for filesystem objects +// Released to the public domain by Earle F. Philhower, III + +#include + +#define TESTSIZEKB 512 + +void DoTest(FS *fs) { + if (!fs->format()) { + Serial.printf("Unable to format(), aborting\n"); + return; + } + if (!fs->begin()) { + Serial.printf("Unable to begin(), aborting\n"); + return; + } + + uint8_t data[256]; + for (int i = 0; i < 256; i++) { + data[i] = (uint8_t) i; + } + + Serial.printf("Creating %dKB file, may take a while...\n", TESTSIZEKB); + long start = millis(); + File f = fs->open("/testwrite.bin", "w"); + if (!f) { + Serial.printf("Unable to open file for writing, aborting\n"); + return; + } + for (int i = 0; i < TESTSIZEKB; i++) { + for (int j = 0; j < 4; j++) { + f.write(data, 256); + } + } + f.close(); + long stop = millis(); + Serial.printf("==> Time to write %dKB in 256b chunks = %ld milliseconds\n", TESTSIZEKB, stop - start); + + f = fs->open("/testwrite.bin", "r"); + Serial.printf("==> Created file size = %d\n", f.size()); + f.close(); + + Serial.printf("Reading %dKB file sequentially in 256b chunks\n", TESTSIZEKB); + start = millis(); + f = fs->open("/testwrite.bin", "r"); + for (int i = 0; i < TESTSIZEKB; i++) { + for (int j = 0; j < 4; j++) { + f.read(data, 256); + } + } + f.close(); + stop = millis(); + Serial.printf("==> Time to read %dKB sequentially in 256b chunks = %ld milliseconds = %ld bytes/s\n", TESTSIZEKB, stop - start, TESTSIZEKB * 1024 / (stop - start) * 1000); + + Serial.printf("Reading %dKB file MISALIGNED in flash and RAM sequentially in 256b chunks\n", TESTSIZEKB); + start = millis(); + f = fs->open("/testwrite.bin", "r"); + f.read(); + for (int i = 0; i < TESTSIZEKB; i++) { + for (int j = 0; j < 4; j++) { + f.read(data + 1, 256); + } + } + f.close(); + stop = millis(); + Serial.printf("==> Time to read %dKB sequentially MISALIGNED in flash and RAM in 256b chunks = %ld milliseconds = %ld bytes/s\n", TESTSIZEKB, stop - start, TESTSIZEKB * 1024 / (stop - start) * 1000); + + + Serial.printf("Reading %dKB file in reverse by 256b chunks\n", TESTSIZEKB); + start = millis(); + f = fs->open("/testwrite.bin", "r"); + for (int i = 0; i < TESTSIZEKB; i++) { + for (int j = 0; j < 4; j++) { + if (!f.seek(256 + 256 * j * i, SeekEnd)) { + Serial.printf("Unable to seek to %d, aborting\n", -256 - 256 * j * i); + return; + } + if (256 != f.read(data, 256)) { + Serial.printf("Unable to read 256 bytes, aborting\n"); + return; + } + } + } + f.close(); + stop = millis(); + Serial.printf("==> Time to read %dKB in reverse in 256b chunks = %ld milliseconds = %ld bytes/s\n", TESTSIZEKB, stop - start, TESTSIZEKB * 1024 / (stop - start) * 1000); + + + Serial.printf("Writing 64K file in 1-byte chunks\n"); + start = millis(); + f = fs->open("/test1b.bin", "w"); + for (int i = 0; i < 65536; i++) { + f.write((uint8_t*)&i, 1); + } + f.close(); + stop = millis(); + Serial.printf("==> Time to write 64KB in 1b chunks = %ld milliseconds = %ld bytes/s\n", stop - start, 65536 / (stop - start) * 1000); + + Serial.printf("Reading 64K file in 1-byte chunks\n"); + start = millis(); + f = fs->open("/test1b.bin", "r"); + for (int i = 0; i < 65536; i++) { + char c; + f.read((uint8_t*)&c, 1); + } + f.close(); + stop = millis(); + Serial.printf("==> Time to read 64KB in 1b chunks = %ld milliseconds = %ld bytes/s\n", stop - start, 65536 / (stop - start) * 1000); + + +} + +void setup() { + Serial.begin(115200); + Serial.printf("Beginning LittleFS test\n"); + Serial.flush(); + DoTest(&LittleFS); + Serial.printf("Beginning SPIFFS test\n"); + Serial.flush(); + DoTest(&SPIFFS); +} + +void loop() { + delay(10000); +} diff --git a/libraries/LittleFS/lib/littlefs b/libraries/LittleFS/lib/littlefs new file mode 160000 index 0000000000..ec4d8b68ad --- /dev/null +++ b/libraries/LittleFS/lib/littlefs @@ -0,0 +1 @@ +Subproject commit ec4d8b68add6a7de021dc09ef08123ab323cbc38 diff --git a/libraries/LittleFS/library.properties b/libraries/LittleFS/library.properties new file mode 100644 index 0000000000..a13e6eb755 --- /dev/null +++ b/libraries/LittleFS/library.properties @@ -0,0 +1,10 @@ +name=LittleFS(esp8266) +version=0.1.0 +author=Earle F. Philhower, III +maintainer=Earle F. Philhower, III +sentence=Port of LittleFS to ESP8266 Arduino +paragraph=Replacement for SPIFFS to manage a filesystem in the onboard flash, supporting power fail safety and higher performance than SPIFFS at the cost of a lower maximum number of files. +category=Data Storage +url=https://github.com/esp8266/Arduino/libraries/LittleFS +architectures=esp8266 +dot_a_linkage=true diff --git a/libraries/LittleFS/src/LittleFS.cpp b/libraries/LittleFS/src/LittleFS.cpp new file mode 100644 index 0000000000..75366acc09 --- /dev/null +++ b/libraries/LittleFS/src/LittleFS.cpp @@ -0,0 +1,198 @@ +/* + LittleFS.cpp - Wrapper for LittleFS for ESP8266 + Copyright (c_ 2019 Earle F. Philhower, III. All rights reserved. + + Based extensively off of the ESP8266 SPIFFS code, which is + Copyright (c) 2015 Ivan Grokhotkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include "LittleFS.h" +#include "debug.h" +#include "flash_hal.h" + +extern "C" { +#include "c_types.h" +#include "spi_flash.h" +} + +namespace littlefs_impl { + +FileImplPtr LittleFSImpl::open(const char* path, OpenMode openMode, AccessMode accessMode) { + if (!_mounted) { + DEBUGV("LittleFSImpl::open() called on unmounted FS\n"); + return FileImplPtr(); + } + if (!path || !path[0]) { + DEBUGV("LittleFSImpl::open() called with invalid filename\n"); + return FileImplPtr(); + } + if (!LittleFSImpl::pathValid(path)) { + DEBUGV("LittleFSImpl::open() called with too long filename\n"); + return FileImplPtr(); + } + + int flags = _getFlags(openMode, accessMode); + auto fd = std::make_shared(); + + if ((openMode && OM_CREATE) && strchr(path, '/')) { + // For file creation, silently make subdirs as needed. If any fail, + // it will be caught by the real file open later on + char *pathStr = strdup(path); + if (pathStr) { + // Make dirs up to the final fnamepart + char *ptr = strchr(pathStr, '/'); + while (ptr) { + *ptr = 0; + lfs_mkdir(&_lfs, pathStr); + *ptr = '/'; + ptr = strchr(ptr+1, '/'); + } + } + free(pathStr); + } + int rc = lfs_file_open(&_lfs, fd.get(), path, flags); + if (rc == LFS_ERR_ISDIR) { + // To support the SD.openNextFile, a null FD indicates to the LittleFSFile this is just + // a directory whose name we are carrying around but which cannot be read or written + return std::make_shared(this, path, nullptr); + } else if (rc == 0) { + return std::make_shared(this, path, fd); + } else { + DEBUGV("LittleFSDirImpl::openFile: rc=%d fd=%p path=`%s` openMode=%d accessMode=%d err=%d\n", + rc, fd.get(), path, openMode, accessMode, rc); + return FileImplPtr(); + } +} + +DirImplPtr LittleFSImpl::openDir(const char *path) { + if (!_mounted || !path) { + return DirImplPtr(); + } + char *pathStr = strdup(path); // Allow edits on our scratch copy + // Get rid of any trailing slashes + while (strlen(pathStr) && (pathStr[strlen(pathStr)-1]=='/')) { + pathStr[strlen(pathStr)-1] = 0; + } + // At this point we have a name of "blah/blah/blah" or "blah" or "" + // If that references a directory, just open it and we're done. + lfs_info info; + auto dir = std::make_shared(); + int rc; + const char *filter = ""; + if (!pathStr[0]) { + // openDir("") === openDir("/") + rc = lfs_dir_open(&_lfs, dir.get(), "/"); + filter = ""; + } else if (lfs_stat(&_lfs, pathStr, &info) >= 0) { + if (info.type == LFS_TYPE_DIR) { + // Easy peasy, path specifies an existing dir! + rc = lfs_dir_open(&_lfs, dir.get(), pathStr); + filter = ""; + } else { + // This is a file, so open the containing dir + char *ptr = strrchr(pathStr, '/'); + if (!ptr) { + // No slashes, open the root dir + rc = lfs_dir_open(&_lfs, dir.get(), "/"); + filter = pathStr; + } else { + // We've got slashes, open the dir one up + *ptr = 0; // Remove slash, truncare string + rc = lfs_dir_open(&_lfs, dir.get(), pathStr); + filter = ptr + 1; + } + } + } else { + // Name doesn't exist, so use the parent dir of whatever was sent in + // This is a file, so open the containing dir + char *ptr = strrchr(pathStr, '/'); + if (!ptr) { + // No slashes, open the root dir + rc = lfs_dir_open(&_lfs, dir.get(), "/"); + filter = pathStr; + } else { + // We've got slashes, open the dir one up + *ptr = 0; // Remove slash, truncare string + rc = lfs_dir_open(&_lfs, dir.get(), pathStr); + filter = ptr + 1; + } + } + if (rc < 0) { + DEBUGV("LittleFSImpl::openDir: path=`%s` err=%d\n", path, rc); + free(pathStr); + return DirImplPtr(); + } + // Skip the . and .. entries + lfs_info dirent; + lfs_dir_read(&_lfs, dir.get(), &dirent); + lfs_dir_read(&_lfs, dir.get(), &dirent); + + auto ret = std::make_shared(filter, this, dir, pathStr); + free(pathStr); + return ret; +} + +int LittleFSImpl::lfs_flash_read(const struct lfs_config *c, + lfs_block_t block, lfs_off_t off, void *dst, lfs_size_t size) { + LittleFSImpl *me = reinterpret_cast(c->context); + uint32_t addr = me->_start + (block * me->_blockSize) + off; + return flash_hal_read(addr, size, static_cast(dst)) == FLASH_HAL_OK ? 0 : -1; +} + +int LittleFSImpl::lfs_flash_prog(const struct lfs_config *c, + lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size) { + LittleFSImpl *me = reinterpret_cast(c->context); + uint32_t addr = me->_start + (block * me->_blockSize) + off; + const uint8_t *src = reinterpret_cast(buffer); + return flash_hal_write(addr, size, static_cast(src)) == FLASH_HAL_OK ? 0 : -1; +} + +int LittleFSImpl::lfs_flash_erase(const struct lfs_config *c, lfs_block_t block) { + LittleFSImpl *me = reinterpret_cast(c->context); + uint32_t addr = me->_start + (block * me->_blockSize); + uint32_t size = me->_blockSize; + return flash_hal_erase(addr, size) == FLASH_HAL_OK ? 0 : -1; +} + +int LittleFSImpl::lfs_flash_sync(const struct lfs_config *c) { + /* NOOP */ + (void) c; + return 0; +} + + +}; // namespace + +// these symbols should be defined in the linker script for each flash layout +#ifndef CORE_MOCK +#ifdef ARDUINO +#ifndef FS_MAX_OPEN_FILES +#define FS_MAX_OPEN_FILES 5 +#endif + +#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_LITTLEFS) +FS LittleFS = FS(FSImplPtr(new littlefs_impl::LittleFSImpl(FS_PHYS_ADDR, FS_PHYS_SIZE, FS_PHYS_PAGE, FS_PHYS_BLOCK, FS_MAX_OPEN_FILES))); +#endif + +#endif // !CORE_MOCK + + +#endif diff --git a/libraries/LittleFS/src/LittleFS.h b/libraries/LittleFS/src/LittleFS.h new file mode 100644 index 0000000000..e3b6d154e6 --- /dev/null +++ b/libraries/LittleFS/src/LittleFS.h @@ -0,0 +1,527 @@ +/* + LittleFS.h - Filesystem wrapper for LittleFS on the ESP8266 + Copyright (c) 2019 Earle F. Philhower, III. All rights reserved. + + Based heavily off of the SPIFFS equivalent code in the ESP8266 core + "Copyright (c) 2015 Ivan Grokhotkov. All rights reserved." + + This code was influenced by NodeMCU and Sming libraries, and first version of + Arduino wrapper written by Hristo Gochkov. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#ifndef __LITTLEFS_H +#define __LITTLEFS_H + +#include +#include +#include +#include +#include +#include + +#define LFS_NAME_MAX 32 +#include "../lib/littlefs/lfs.h" + +using namespace fs; + +namespace littlefs_impl { + +class LittleFSFileImpl; +class LittleFSDirImpl; + +class LittleFSImpl : public FSImpl +{ +public: + LittleFSImpl(uint32_t start, uint32_t size, uint32_t pageSize, uint32_t blockSize, uint32_t maxOpenFds) + : _start(start) , _size(size) , _pageSize(pageSize) , _blockSize(blockSize) , _maxOpenFds(maxOpenFds), + _mounted(false) { + memset(&_lfs, 0, sizeof(_lfs)); + memset(&_lfs_cfg, 0, sizeof(_lfs_cfg)); + _lfs_cfg.context = (void*) this; + _lfs_cfg.read = lfs_flash_read; + _lfs_cfg.prog = lfs_flash_prog; + _lfs_cfg.erase = lfs_flash_erase; + _lfs_cfg.sync = lfs_flash_sync; + _lfs_cfg.read_size = _pageSize; + _lfs_cfg.prog_size = _pageSize; + _lfs_cfg.block_size = _blockSize; + _lfs_cfg.block_count = _size / _blockSize; + _lfs_cfg.lookahead = 256; + _lfs_cfg.read_buffer = nullptr; + _lfs_cfg.prog_buffer = nullptr; + _lfs_cfg.lookahead_buffer = nullptr; + _lfs_cfg.file_buffer = nullptr; + } + + ~LittleFSImpl() { + if (_mounted) { + lfs_unmount(&_lfs); + } + } + + FileImplPtr open(const char* path, OpenMode openMode, AccessMode accessMode) override; + DirImplPtr openDir(const char *path) override; + + bool exists(const char* path) override { + if ( !_mounted || !path || !path[0] ) { + return false; + } + lfs_info info; + int rc = lfs_stat(&_lfs, path, &info); + return rc == 0; + } + + bool rename(const char* pathFrom, const char* pathTo) override { + if (!_mounted || !pathFrom || !pathFrom[0] || !pathTo || !pathTo[0]) { + return false; + } + int rc = lfs_rename(&_lfs, pathFrom, pathTo); + if (rc != 0) { + DEBUGV("lfs_rename: rc=%d, from=`%s`, to=`%s`\n", rc, pathFrom, pathTo); + return false; + } + return true; + } + + bool info(FSInfo& info) override { + if (!_mounted) { + return false; + } + info.maxOpenFiles = _maxOpenFds; + info.blockSize = _blockSize; + info.pageSize = _pageSize; + info.maxOpenFiles = _maxOpenFds; + info.maxPathLength = LFS_NAME_MAX; + info.totalBytes = _size; + info.usedBytes = _size - _getUsedBlocks() * _blockSize; + return true; + } + + bool remove(const char* path) override { + if (!_mounted || !path || !path[0]) { + return false; + } + int rc = lfs_remove(&_lfs, path); + if (rc != 0) { + DEBUGV("lfs_remove: rc=%d path=`%s`\n", rc, path); + return false; + } + // Now try and remove any empty subdirs this makes, silently + char *pathStr = strdup(path); + if (pathStr) { + char *ptr = strrchr(pathStr, '/'); + while (ptr) { + *ptr = 0; + lfs_remove(&_lfs, pathStr); // Don't care if fails if there are files left + ptr = strrchr(pathStr, '/'); + } + free(pathStr); + } + return true; + } + + bool mkdir(const char* path) override { + if (!_mounted || !path || !path[0]) { + return false; + } + int rc = lfs_mkdir(&_lfs, path); + return (rc==0); + } + + bool rmdir(const char* path) override { + return remove(path); // Same call on LittleFS + } + + bool begin() override { + if (_size <= 0) { + DEBUGV("LittleFS size is <= zero"); + return false; + } + if (_tryMount()) { + return true; + } + if (!format()) { + return false; + } + return _tryMount(); + } + + void end() override { + if (!_mounted) { + return; + } + lfs_unmount(&_lfs); + _mounted = false; + } + + bool format() override { + if (_size == 0) { + DEBUGV("lfs size is zero\n"); + return false; + } + + bool wasMounted = _mounted; + if (_mounted) { + lfs_unmount(&_lfs); + _mounted = false; + } + + memset(&_lfs, 0, sizeof(_lfs)); + int rc = lfs_format(&_lfs, &_lfs_cfg); + if (rc != 0) { + DEBUGV("lfs_format: rc=%d\n", rc); + return false; + } + + if (wasMounted) { + return _tryMount(); + } + + return true; + } + +protected: + friend class LittleFSFileImpl; + friend class LittleFSDirImpl; + + lfs_t* getFS() { + return &_lfs; + } + + bool _tryMount() { + if (_mounted) { + lfs_unmount(&_lfs); + _mounted = false; + } + memset(&_lfs, 0, sizeof(_lfs)); + int rc = lfs_mount(&_lfs, &_lfs_cfg); + if (rc==0) { + _mounted = true; + } + return _mounted; + } + + static int _lfs_traverse_cb(void *p, lfs_block_t b) { + (void) b; + *(lfs_size_t *)p += 1; + return 0; + } + + int _getUsedBlocks() { + lfs_size_t in_use = 0; + if (!_mounted) { + return 0; + } + int err = lfs_traverse(&_lfs, _lfs_traverse_cb, &in_use); + return err ? 0 : in_use; + } + + static int _getFlags(OpenMode openMode, AccessMode accessMode) { + int mode = 0; + if (openMode & OM_CREATE) { + mode |= LFS_O_CREAT; + } + if (openMode & OM_APPEND) { + mode |= LFS_O_APPEND; + } + if (openMode & OM_TRUNCATE) { + mode |= LFS_O_TRUNC; + } + if (accessMode & AM_READ) { + mode |= LFS_O_RDONLY; + } + if (accessMode & AM_WRITE) { + mode |= LFS_O_WRONLY; + } + return mode; + } + + // Check that no components of path beyond max len + static bool pathValid(const char *path) { + while (*path) { + const char *slash = strchr(path, '/'); + if (!slash) { + if (strlen(path) >= LFS_NAME_MAX) { + // Terminal filename is too long + return false; + } + break; + } + if ((slash - path) >= LFS_NAME_MAX) { + // This subdir name too long + return false; + } + path = slash + 1; + } + return true; + } + + // The actual flash accessing routines + static int lfs_flash_read(const struct lfs_config *c, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size); + static int lfs_flash_prog(const struct lfs_config *c, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size); + static int lfs_flash_erase(const struct lfs_config *c, lfs_block_t block); + static int lfs_flash_sync(const struct lfs_config *c); + + lfs_t _lfs; + lfs_config _lfs_cfg; + + uint32_t _start; + uint32_t _size; + uint32_t _pageSize; + uint32_t _blockSize; + uint32_t _maxOpenFds; + + bool _mounted; +}; + + +class LittleFSFileImpl : public FileImpl +{ +public: + LittleFSFileImpl(LittleFSImpl* fs, const char *name, std::shared_ptr fd) : _fs(fs), _fd(fd), _opened(true) { + _name = std::shared_ptr(new char[strlen(name) + 1], std::default_delete()); + strcpy(_name.get(), name); + } + + ~LittleFSFileImpl() override { + if (_opened) { + close(); + } + } + + size_t write(const uint8_t *buf, size_t size) override { + if (!_opened || !_fd || !buf) { + return 0; + } + int result = lfs_file_write(_fs->getFS(), _getFD(), (void*) buf, size); + if (result < 0) { + DEBUGV("lfs_write rc=%d\n", result); + return 0; + } + return result; + } + + size_t read(uint8_t* buf, size_t size) override { + if (!_opened || !_fd | !buf) { + return 0; + } + int result = lfs_file_read(_fs->getFS(), _getFD(), (void*) buf, size); + if (result < 0) { + DEBUGV("lfs_read rc=%d\n", result); + return 0; + } + + return result; + } + + void flush() override { + if (!_opened || !_fd) { + return; + } + int rc = lfs_file_sync(_fs->getFS(), _getFD()); + if (rc < 0) { + DEBUGV("lfs_file_sync rc=%d\n", rc); + } + } + + bool seek(uint32_t pos, SeekMode mode) override { + if (!_opened || !_fd) { + return false; + } + int32_t offset = static_cast(pos); + if (mode == SeekEnd) { + offset = -offset; // TODO - this seems like its plain wrong vs. POSIX + } + int rc = lfs_file_seek(_fs->getFS(), _getFD(), offset, (int)mode); // NB. SeekMode === LFS_SEEK_TYPES + if (rc < 0) { + DEBUGV("lfs_file_seek rc=%d\n", rc); + return false; + } + return true; + } + + size_t position() const override { + if (!_opened || !_fd) { + return 0; + } + int result = lfs_file_tell(_fs->getFS(), _getFD()); + if (result < 0) { + DEBUGV("lfs_file_tell rc=%d\n", result); + return 0; + } + + return result; + } + + size_t size() const override { + return (_opened && _fd)? lfs_file_size(_fs->getFS(), _getFD()) : 0; + } + + void close() override { + if (_opened && _fd) { + lfs_file_close(_fs->getFS(), _getFD()); + _opened = false; + DEBUGV("lfs_file_close: fd=%p\n", _getFD()); + } + } + + const char* name() const override { + if (!_opened) { + return nullptr; + } else { + const char *p = _name.get(); + const char *slash = strrchr(p, '/'); + return (slash && slash[1]) ? slash + 1 : p; + } + } + + const char* fullName() const override { + return _opened ? _name.get() : nullptr; + } + + bool isFile() const override { + if (!_opened || !_fd) { + return false; + } + lfs_info info; + int rc = lfs_stat(_fs->getFS(), fullName(), &info); + return (rc == 0) && (info.type == LFS_TYPE_REG); + } + + bool isDirectory() const override { + if (!_opened) { + return false; + } else if (!_fd) { + return true; + } + lfs_info info; + int rc = lfs_stat(_fs->getFS(), fullName(), &info); + return (rc == 0) && (info.type == LFS_TYPE_DIR); + } + +protected: + lfs_file_t *_getFD() const { + return _fd.get(); + } + + LittleFSImpl *_fs; + std::shared_ptr _fd; + std::shared_ptr _name; + bool _opened; +}; + +class LittleFSDirImpl : public DirImpl +{ +public: + LittleFSDirImpl(const String& pattern, LittleFSImpl* fs, std::shared_ptr dir, const char *dirPath = nullptr) + : _pattern(pattern) , _fs(fs) , _dir(dir) , _dirPath(nullptr), _valid(false), _opened(true) + { + memset(&_dirent, 0, sizeof(_dirent)); + if (dirPath) { + _dirPath = std::shared_ptr(new char[strlen(dirPath) + 1], std::default_delete()); + strcpy(_dirPath.get(), dirPath); + } + } + + ~LittleFSDirImpl() override { + if (_opened) { + lfs_dir_close(_fs->getFS(), _getDir()); + } + } + + FileImplPtr openFile(OpenMode openMode, AccessMode accessMode) override { + if (!_valid) { + return FileImplPtr(); + } + int nameLen = 3; // Slashes, terminator + nameLen += _dirPath.get() ? strlen(_dirPath.get()) : 0; + nameLen += strlen(_dirent.name); + char *tmpName = (char*)malloc(nameLen); + if (!tmpName) { + return FileImplPtr(); + } + snprintf(tmpName, nameLen, "%s%s%s", _dirPath.get() ? _dirPath.get() : "", _dirPath.get()&&_dirPath.get()[0]?"/":"", _dirent.name); + auto ret = _fs->open((const char *)tmpName, openMode, accessMode); + free(tmpName); + return ret; + } + + const char* fileName() override { + if (!_valid) { + return nullptr; + } + return (const char*) _dirent.name; + } + + size_t fileSize() override { + if (!_valid) { + return 0; + } + return _dirent.size; + } + + bool isFile() const override { + return _valid && (_dirent.type == LFS_TYPE_REG); + } + + bool isDirectory() const override { + return _valid && (_dirent.type == LFS_TYPE_DIR); + } + + bool rewind() override { + _valid = false; + int rc = lfs_dir_rewind(_fs->getFS(), _getDir()); + return (rc == 0); + } + + bool next() override { + const int n = _pattern.length(); + bool match; + do { + _dirent.name[0] = 0; + int rc = lfs_dir_read(_fs->getFS(), _getDir(), &_dirent); + _valid = (rc == 1); + match = (!n || !strncmp((const char*) _dirent.name, _pattern.c_str(), n)); + } while (_valid && !match); + return _valid; + } + +protected: + lfs_dir_t *_getDir() const { + return _dir.get(); + } + + String _pattern; + LittleFSImpl *_fs; + std::shared_ptr _dir; + std::shared_ptr _dirPath; + lfs_info _dirent; + bool _valid; + bool _opened; +}; + +}; + +#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_LITTLEFS) +extern FS LittleFS; +#endif // ARDUINO + + +#endif // !defined(__LITTLEFS_H) diff --git a/libraries/LittleFS/src/lfs.c b/libraries/LittleFS/src/lfs.c new file mode 100644 index 0000000000..f68368bed3 --- /dev/null +++ b/libraries/LittleFS/src/lfs.c @@ -0,0 +1,7 @@ +// Can't place library in ths src/ directory, Arduino will attempt to build the tests/etc. +// Just have a stub here that redirects to the actual source file + +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#define LFS_NAME_MAX 32 + +#include "../lib/littlefs/lfs.c" diff --git a/libraries/LittleFS/src/lfs_util.c b/libraries/LittleFS/src/lfs_util.c new file mode 100644 index 0000000000..0db909222f --- /dev/null +++ b/libraries/LittleFS/src/lfs_util.c @@ -0,0 +1,3 @@ +#define LFS_NAME_MAX 32 + +#include "../lib/littlefs/lfs_util.c" diff --git a/package/package_esp8266com_index.template.json b/package/package_esp8266com_index.template.json index ac94797537..ce86c7fd77 100644 --- a/package/package_esp8266com_index.template.json +++ b/package/package_esp8266com_index.template.json @@ -2,10 +2,12 @@ "packages": [ { "maintainer": "ESP8266 Community", + "email": "ivan@esp8266.com", + "name": "esp8266", + "websiteURL": "https://github.com/esp8266/Arduino", "help": { "online": "http://esp8266.com/arduino" }, - "websiteURL": "https://github.com/esp8266/Arduino", "platforms": [ { "category": "ESP8266", @@ -121,6 +123,11 @@ "packager": "esp8266", "version": "2.5.0-3-20ed2b9", "name": "mkspiffs" + }, + { + "packager": "esp8266", + "version": "2.5.0-3-4a23057", + "name": "mklittlefs" } ], "help": { @@ -272,10 +279,56 @@ "size": "350035" } ] + }, + { + "version": "2.5.0-3-4a23057", + "name": "mklittlefs", + "systems": [ + { + "host": "aarch64-linux-gnu", + "url": "https://github.com/earlephilhower/mklittlefs/releases/download/2.5.0-3/aarch64-linux-gnu-mklittlefs-4a23057.tar.gz", + "archiveFileName": "aarch64-linux-gnu-mklittlefs-4a23057.tar.gz", + "checksum": "SHA-256:436f1593c4075bd023e66fcb266532d910c07298d4dfde58a56ea78e57feb5a6", + "size": "39151" + }, + { + "host": "arm-linux-gnueabihf", + "url": "https://github.com/earlephilhower/mklittlefs/releases/download/2.5.0-3/arm-linux-gnueabihf-mklittlefs-4a23057.tar.gz", + "archiveFileName": "arm-linux-gnueabihf-mklittlefs-4a23057.tar.gz", + "checksum": "SHA-256:f20cb21aebda0db732c337d92ddd628d7f6d7e7110a19d39107ed2bdb357b193", + "size": "31561" + }, + { + "host": "i686-mingw32", + "url": "https://github.com/earlephilhower/mklittlefs/releases/download/2.5.0-3/i686-w64-mingw32-mklittlefs-4a23057.zip", + "archiveFileName": "i686-w64-mingw32-mklittlefs-4a23057.zip", + "checksum": "SHA-256:64e038c5fc172341247b3ebcb9f5ea190b6fc2f046948b668dbc588a38572f9e", + "size": "327466" + }, + { + "host": "x86_64-apple-darwin", + "url": "https://github.com/earlephilhower/mklittlefs/releases/download/2.5.0-3/x86_64-apple-darwin14-mklittlefs-4a23057.tar.gz", + "archiveFileName": "x86_64-apple-darwin14-mklittlefs-4a23057.tar.gz", + "checksum": "SHA-256:b8e08f21e433375fe7cef406bf80924e56b8cbb2b89722e018926e2ffb06d533", + "size": "356900" + }, + { + "host": "x86_64-pc-linux-gnu", + "url": "https://github.com/earlephilhower/mklittlefs/releases/download/2.5.0-3/x86_64-linux-gnu-mklittlefs-4a23057.tar.gz", + "archiveFileName": "x86_64-linux-gnu-mklittlefs-4a23057.tar.gz", + "checksum": "SHA-256:add92ff1d71a1fdadfd8132c48d6bdc00c88d1f2a80b8843af7a5a08205f9490", + "size": "40450" + }, + { + "host": "x86_64-mingw32", + "url": "https://github.com/earlephilhower/mklittlefs/releases/download/2.5.0-3/x86_64-w64-mingw32-mklittlefs-4a23057.zip", + "archiveFileName": "x86_64-w64-mingw32-mklittlefs-4a23057.zip", + "checksum": "SHA-256:944493f35dfdf01cbc628f99f5b2649843f827d8bafbeb11160c72ef6822356e", + "size": "339768" + } + ] } - ], - "email": "ivan@esp8266.com", - "name": "esp8266" + ] } ] -} \ No newline at end of file +} diff --git a/platform.txt b/platform.txt index 225c66fa57..b52ccf50b6 100644 --- a/platform.txt +++ b/platform.txt @@ -143,3 +143,7 @@ tools.esptool.upload.network_pattern="{network_cmd}" "{runtime.platform.path}/to tools.mkspiffs.cmd=mkspiffs tools.mkspiffs.cmd.windows=mkspiffs.exe tools.mkspiffs.path={runtime.platform.path}/tools/mkspiffs + +tools.mklittlefs.cmd=mklittlefs +tools.mklittlefs.cmd.windows=mklittlefs.exe +tools.mklittlefs.path={runtime.platform.path}/tools/mklittlefs diff --git a/tests/host/Makefile b/tests/host/Makefile index e778d9605c..825e79e781 100644 --- a/tests/host/Makefile +++ b/tests/host/Makefile @@ -56,6 +56,7 @@ CORE_CPP_FILES := $(addprefix $(CORE_PATH)/,\ FS.cpp \ spiffs_api.cpp \ MD5Builder.cpp \ + ../../libraries/LittleFS/src/LittleFS.cpp \ ) CORE_C_FILES := $(addprefix $(CORE_PATH)/,\ @@ -66,11 +67,15 @@ CORE_C_FILES := $(addprefix $(CORE_PATH)/,\ spiffs/spiffs_hydrogen.c \ spiffs/spiffs_nucleus.c \ libb64/cencode.c \ + ../../libraries/LittleFS/src/lfs.c \ + ../../libraries/LittleFS/src/lfs_util.c \ ) MOCK_CPP_FILES_COMMON := $(addprefix common/,\ Arduino.cpp \ + flash_hal_mock.cpp \ spiffs_mock.cpp \ + littlefs_mock.cpp \ WMath.cpp \ MockSerial.cpp \ MockTools.cpp \ diff --git a/tests/host/common/flash_hal_mock.cpp b/tests/host/common/flash_hal_mock.cpp new file mode 100644 index 0000000000..5304d7553a --- /dev/null +++ b/tests/host/common/flash_hal_mock.cpp @@ -0,0 +1,36 @@ +/* Emulate the flash read/write HAL */ + +#include +#include + +extern "C" +{ + uint32_t s_phys_addr = 0; + uint32_t s_phys_size = 0; + uint32_t s_phys_page = 0; + uint32_t s_phys_block = 0; + uint8_t* s_phys_data = nullptr; +} + +int32_t flash_hal_read(uint32_t addr, uint32_t size, uint8_t *dst) { + memcpy(dst, s_phys_data + addr, size); + return 0; +} + +int32_t flash_hal_write(uint32_t addr, uint32_t size, const uint8_t *src) { + memcpy(s_phys_data + addr, src, size); + return 0; +} + +int32_t flash_hal_erase(uint32_t addr, uint32_t size) { + if ((size & (FLASH_SECTOR_SIZE - 1)) != 0 || + (addr & (FLASH_SECTOR_SIZE - 1)) != 0) { + abort(); + } + const uint32_t sector = addr / FLASH_SECTOR_SIZE; + const uint32_t sectorCount = size / FLASH_SECTOR_SIZE; + for (uint32_t i = 0; i < sectorCount; ++i) { + memset(s_phys_data + (sector + i) * FLASH_SECTOR_SIZE, 0xff, FLASH_SECTOR_SIZE); + } + return 0; +} diff --git a/tests/host/common/flash_hal_mock.h b/tests/host/common/flash_hal_mock.h new file mode 100644 index 0000000000..af5035eaa5 --- /dev/null +++ b/tests/host/common/flash_hal_mock.h @@ -0,0 +1,19 @@ +#ifndef flash_hal_mock_h +#define flash_hal_mock_h + +#include + +extern "C" +{ + extern uint32_t s_phys_addr; + extern uint32_t s_phys_size; + extern uint32_t s_phys_page; + extern uint32_t s_phys_block; + extern uint8_t* s_phys_data; +} + +extern int32_t flash_hal_read(uint32_t addr, uint32_t size, uint8_t *dst); +extern int32_t flash_hal_write(uint32_t addr, uint32_t size, const uint8_t *src); +extern int32_t flash_hal_erase(uint32_t addr, uint32_t size); + +#endif diff --git a/tests/host/common/littlefs_mock.cpp b/tests/host/common/littlefs_mock.cpp new file mode 100644 index 0000000000..7fad8e8b7b --- /dev/null +++ b/tests/host/common/littlefs_mock.cpp @@ -0,0 +1,126 @@ +/* + littlefs_mock.cpp - SPIFFS HAL mock for host side testing + Copyright © 2019 Earle F. Philhower, III + + Based off spiffs_mock.cpp: + Copyright © 2016 Ivan Grokhotkov + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. +*/ + + +#include "littlefs_mock.h" +#include "spiffs_mock.h" +#include "spiffs/spiffs.h" +#include "debug.h" +#include +#include +#include + +#include + +#include +#include +#include +#include + +#define LITTLEFS_FILE_NAME "littlefs.bin" + +extern "C" +{ + extern uint32_t s_phys_size; + extern uint32_t s_phys_page; + extern uint32_t s_phys_block; + extern uint8_t* s_phys_data; +} + +FS LittleFS(nullptr); + +LittleFSMock::LittleFSMock(size_t fs_size, size_t fs_block, size_t fs_page, bool storage) +{ + fprintf(stderr, "LittleFS: %zd bytes\n", fs_size); + + m_storage = storage; + m_fs = new uint8_t[m_fs_size = fs_size]; + memset(&m_fs[0], 0xff, m_fs_size); + + s_phys_size = static_cast(fs_size); + s_phys_page = static_cast(fs_page); + s_phys_block = static_cast(fs_block); + s_phys_data = &m_fs[0]; + reset(); +} + +void LittleFSMock::reset() +{ + LittleFS = FS(FSImplPtr(new littlefs_impl::LittleFSImpl(0, s_phys_size, s_phys_page, s_phys_block, 5))); + if (m_storage) + load(); +} + +LittleFSMock::~LittleFSMock() +{ + if (m_storage) + save(); + s_phys_size = 0; + s_phys_page = 0; + s_phys_block = 0; + s_phys_data = nullptr; + delete [] m_fs; + m_fs = nullptr; + m_fs_size = 0; + LittleFS = FS(FSImplPtr(nullptr)); +} + +void LittleFSMock::load () +{ + if (!m_fs_size) + return; + + const char* fname = getenv("LITTLEFS_PATH"); + if (!fname) + fname = DEFAULT_LITTLEFS_FILE_NAME; + int fs = ::open(LITTLEFS_FILE_NAME, O_RDONLY); + if (fs == -1) + { + fprintf(stderr, "LittleFS: loading '%s': %s\n", fname, strerror(errno)); + return; + } + fprintf(stderr, "LittleFS: loading %zi bytes from '%s'\n", m_fs_size, fname); + if (::read(fs, &m_fs[0], m_fs_size) != (ssize_t)m_fs_size) + fprintf(stderr, "LittleFS: reading %zi bytes: %s\n", m_fs_size, strerror(errno)); + ::close(fs); +} + +void LittleFSMock::save () +{ + if (!m_fs_size) + return; + + const char* fname = getenv("LITTLEFS_PATH"); + if (!fname) + fname = DEFAULT_LITTLEFS_FILE_NAME; + int fs = ::open(LITTLEFS_FILE_NAME, O_CREAT | O_TRUNC | O_WRONLY, 0644); + if (fs == -1) + { + fprintf(stderr, "LittleFS: saving '%s': %s\n", fname, strerror(errno)); + return; + } + fprintf(stderr, "LittleFS: saving %zi bytes to '%s'\n", m_fs_size, fname); + +// this can be a valgrind error, I don't understand how it happens +//for (size_t i = 0; i < m_fs_size; i++) printf("\r%zd:%d ", i, (int)m_fs[i]); + + if (::write(fs, &m_fs[0], m_fs_size) != (ssize_t)m_fs_size) + fprintf(stderr, "LittleFS: writing %zi bytes: %s\n", m_fs_size, strerror(errno)); + if (::close(fs) == -1) + fprintf(stderr, "LittleFS: closing %s: %s\n", fname, strerror(errno)); +} diff --git a/tests/host/common/littlefs_mock.h b/tests/host/common/littlefs_mock.h new file mode 100644 index 0000000000..17ce8c48dd --- /dev/null +++ b/tests/host/common/littlefs_mock.h @@ -0,0 +1,56 @@ +/* + littlefs_mock.h - LittleFS HAL mock for host side testing + Copyright © 2019 Earle F. Philhower, III + + Based on spiffs_mock: + Copyright © 2016 Ivan Grokhotkov + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. +*/ + +#ifndef littlefs_mock_hpp +#define littlefs_mock_hpp + +#include +#include +#include +#include +#include "flash_hal_mock.h" + +#define DEFAULT_LITTLEFS_FILE_NAME "littlefs.bin" + +class LittleFSMock { +public: + LittleFSMock(size_t fs_size, size_t fs_block, size_t fs_page, bool storage = true); + void reset(); + ~LittleFSMock(); + +protected: + void load (); + void save (); + + // it was a vector, but CI tests & valgrind complain with: + // Syscall param write(buf) points to uninitialised byte(s) + // by 0x43E9FF: SpiffsMock::save() (littlefs_mock.cpp:116) + // = if (::write(fs, &m_fs[0], m_fs_size) != (ssize_t)m_fs_size) + // so switched to a regular array + // and that bug is still here + // XXXWIPTODO + + uint8_t* m_fs; + size_t m_fs_size; + bool m_storage; +}; + +#define LITTLEFS_MOCK_DECLARE(size_kb, block_kb, page_b, storage) LittleFSMock littlefs_mock(size_kb * 1024, block_kb * 1024, page_b, storage) +#define LITTLEFS_MOCK_RESET() littlefs_mock.reset() + +#endif /* littlefs_mock_hpp */ diff --git a/tests/host/common/spiffs_mock.cpp b/tests/host/common/spiffs_mock.cpp index e63abae179..f191261e82 100644 --- a/tests/host/common/spiffs_mock.cpp +++ b/tests/host/common/spiffs_mock.cpp @@ -27,16 +27,9 @@ #include #include -#define SPIFFS_FILE_NAME "spiffs.bin" +#include "flash_hal_mock.h" -extern "C" -{ - static uint32_t s_phys_addr = 0; - uint32_t s_phys_size = 0; - uint32_t s_phys_page = 0; - uint32_t s_phys_block = 0; - uint8_t* s_phys_data = nullptr; -} +#define SPIFFS_FILE_NAME "spiffs.bin" FS SPIFFS(nullptr); @@ -58,7 +51,7 @@ SpiffsMock::SpiffsMock(size_t fs_size, size_t fs_block, size_t fs_page, bool sto void SpiffsMock::reset() { - SPIFFS = FS(FSImplPtr(new SPIFFSImpl(0, s_phys_size, s_phys_page, s_phys_block, 5))); + SPIFFS = FS(FSImplPtr(new spiffs_impl::SPIFFSImpl(0, s_phys_size, s_phys_page, s_phys_block, 5))); if (m_storage) load(); } @@ -122,26 +115,3 @@ void SpiffsMock::save () if (::close(fs) == -1) fprintf(stderr, "SPIFFS: closing %s: %s\n", fname, strerror(errno)); } - -int32_t spiffs_hal_read(uint32_t addr, uint32_t size, uint8_t *dst) { - memcpy(dst, s_phys_data + addr, size); - return SPIFFS_OK; -} - -int32_t spiffs_hal_write(uint32_t addr, uint32_t size, uint8_t *src) { - memcpy(s_phys_data + addr, src, size); - return SPIFFS_OK; -} - -int32_t spiffs_hal_erase(uint32_t addr, uint32_t size) { - if ((size & (FLASH_SECTOR_SIZE - 1)) != 0 || - (addr & (FLASH_SECTOR_SIZE - 1)) != 0) { - abort(); - } - const uint32_t sector = addr / FLASH_SECTOR_SIZE; - const uint32_t sectorCount = size / FLASH_SECTOR_SIZE; - for (uint32_t i = 0; i < sectorCount; ++i) { - memset(s_phys_data + (sector + i) * FLASH_SECTOR_SIZE, 0xff, FLASH_SECTOR_SIZE); - } - return SPIFFS_OK; -} diff --git a/tests/host/common/spiffs_mock.h b/tests/host/common/spiffs_mock.h index 6b0c2c5102..08ef0f6ce4 100644 --- a/tests/host/common/spiffs_mock.h +++ b/tests/host/common/spiffs_mock.h @@ -20,6 +20,7 @@ #include #include #include +#include "flash_hal_mock.h" #define DEFAULT_SPIFFS_FILE_NAME "spiffs.bin" @@ -49,5 +50,4 @@ class SpiffsMock { #define SPIFFS_MOCK_DECLARE(size_kb, block_kb, page_b, storage) SpiffsMock spiffs_mock(size_kb * 1024, block_kb * 1024, page_b, storage) #define SPIFFS_MOCK_RESET() spiffs_mock.reset() - #endif /* spiffs_mock_hpp */ diff --git a/tests/host/fs/test_fs.cpp b/tests/host/fs/test_fs.cpp index c97e0f718b..d98c57ec75 100644 --- a/tests/host/fs/test_fs.cpp +++ b/tests/host/fs/test_fs.cpp @@ -17,170 +17,42 @@ #include #include #include "../common/spiffs_mock.h" +#include "../common/littlefs_mock.h" #include - -static void createFile (const char* name, const char* content) -{ - auto f = SPIFFS.open(name, "w"); - REQUIRE(f); - if (content) { - f.print(content); - } -} - -static String readFile (const char* name) -{ - auto f = SPIFFS.open(name, "r"); - if (f) { - return f.readString(); - } - return String(); -} - -static std::set listDir (const char* path) -{ - std::set result; - Dir dir = SPIFFS.openDir(path); - while (dir.next()) { - REQUIRE(result.find(dir.fileName()) == std::end(result)); - result.insert(dir.fileName()); - } - return result; -} - -TEST_CASE("FS can begin","[fs]") -{ - SPIFFS_MOCK_DECLARE(64, 8, 512, false); - REQUIRE(SPIFFS.begin()); -} - -TEST_CASE("FS can't begin with zero size","[fs]") -{ - SPIFFS_MOCK_DECLARE(0, 8, 512, false); - REQUIRE_FALSE(SPIFFS.begin()); -} - -TEST_CASE("Before begin is called, open will fail","[fs]") -{ - SPIFFS_MOCK_DECLARE(64, 8, 512, false); - REQUIRE_FALSE(SPIFFS.open("/foo", "w")); -} - -TEST_CASE("FS can create file","[fs]") -{ - SPIFFS_MOCK_DECLARE(64, 8, 512, false); - REQUIRE(SPIFFS.begin()); - createFile("/test", ""); - REQUIRE(SPIFFS.exists("/test")); -} - -TEST_CASE("Files can be written and appended to","[fs]") -{ - SPIFFS_MOCK_DECLARE(64, 8, 512, false); - REQUIRE(SPIFFS.begin()); - { - File f = SPIFFS.open("config1.txt", "w"); - REQUIRE(f); - f.println("file 1"); - } - { - File f = SPIFFS.open("config1.txt", "a"); - REQUIRE(f); - f.println("file 1 again"); - } - { - File f = SPIFFS.open("config1.txt", "r"); - REQUIRE(f); - char buf[128]; - size_t len = f.read((uint8_t*)buf, sizeof(buf)); - buf[len] = 0; - REQUIRE(strcmp(buf, "file 1\r\nfile 1 again\r\n") == 0); - } -} - -TEST_CASE("Files persist after reset", "[fs]") -{ - SPIFFS_MOCK_DECLARE(64, 8, 512, false); - REQUIRE(SPIFFS.begin()); - createFile("config1.txt", "file 1"); - - SPIFFS_MOCK_RESET(); - REQUIRE(SPIFFS.begin()); - REQUIRE(readFile("config1.txt") == "file 1"); -} - - -TEST_CASE("Filesystem is empty after format", "[fs]") -{ - SPIFFS_MOCK_DECLARE(64, 8, 512, false); - REQUIRE(SPIFFS.format()); - REQUIRE(SPIFFS.begin()); - createFile("/1", "first"); - createFile("/2", "second"); - REQUIRE(SPIFFS.format()); - Dir root = SPIFFS.openDir("/"); - size_t count = 0; - while (root.next()) { - ++count; - } - REQUIRE(count == 0); -} - -TEST_CASE("Dir lists all files", "[fs]") -{ - SPIFFS_MOCK_DECLARE(64, 8, 512, false); - REQUIRE(SPIFFS.begin()); - createFile("/empty", ""); - createFile("/not_empty", "some text"); - createFile("/another", "more text"); - createFile("/subdir/empty", ""); - createFile("/subdir/not_empty", "text again"); - auto files = listDir("/"); - REQUIRE(files.size() == 5); - REQUIRE(files.find("/empty") != std::end(files)); - REQUIRE(files.find("/not_empty") != std::end(files)); - REQUIRE(files.find("/another") != std::end(files)); - REQUIRE(files.find("/subdir/empty") != std::end(files)); - REQUIRE(files.find("/subdir/not_empty") != std::end(files)); -} - -TEST_CASE("File names which are too long are rejected", "[fs]") -{ - SPIFFS_MOCK_DECLARE(64, 8, 512, false); - REQUIRE(SPIFFS.begin()); - const char* emptyName = ""; - const char* longName_31 = "/234567890123456789012345678901"; - const char* longName_32 = "/2345678901234567890123456789012"; - REQUIRE_FALSE(SPIFFS.open(emptyName, "w")); - REQUIRE_FALSE(SPIFFS.open(emptyName, "r")); - REQUIRE_FALSE(SPIFFS.exists(emptyName)); - REQUIRE_FALSE(SPIFFS.open(longName_32, "w")); - REQUIRE_FALSE(SPIFFS.open(longName_32, "r")); - REQUIRE_FALSE(SPIFFS.exists(longName_32)); - REQUIRE(SPIFFS.open(longName_31, "w")); - REQUIRE(SPIFFS.open(longName_31, "r")); - REQUIRE(SPIFFS.exists(longName_31)); -} - -TEST_CASE("#1685 Duplicate files", "[fs][bugreport]") -{ - SPIFFS_MOCK_DECLARE(64, 8, 512, false); - REQUIRE(SPIFFS.begin()); - createFile("/config", "some text"); - createFile("/data", ""); - readFile("/config"); - createFile("/data", "more text"); - listDir("/"); -} - -TEST_CASE("#1819 Can list all files with openDir(\"\")", "[fs][bugreport]") -{ - SPIFFS_MOCK_DECLARE(64, 8, 512, false); - REQUIRE(SPIFFS.begin()); - createFile("/file1", "some text"); - createFile("/file2", "other text"); - createFile("file3", "more text"); - createFile("sorta-dir/file4", "\n"); - auto files = listDir(""); - REQUIRE(files.size() == 4); +#include + +namespace spiffs_test { +#define FSTYPE SPIFFS +#define TESTPRE "SPIFFS - " +#define TESTPAT "[fs]" +#define TOOLONGFILENAME "/2345678901234567890123456789012" +#define FS_MOCK_DECLARE SPIFFS_MOCK_DECLARE +#define FS_MOCK_RESET SPIFFS_MOCK_RESET +#undef FS_HAS_DIRS +#include "test_fs.inc" +#undef FSTYPE +#undef TESTPRE +#undef TESTPAT +#undef TOOLONGFILENAME +#undef FS_MOCK_DECLARE +#undef FS_MOCK_RESET +}; + + +namespace littlefs_test { +#define FSTYPE LittleFS +#define TESTPRE "LittleFS - " +#define TESTPAT "[lfs]" +// LittleFS routines strip leading slashes before doing anything, so up to 31 char names are allowable +#define TOOLONGFILENAME "/12345678901234567890123456789012" +#define FS_MOCK_DECLARE LITTLEFS_MOCK_DECLARE +#define FS_MOCK_RESET LITTLEFS_MOCK_RESET +#define FS_HAS_DIRS +#include "test_fs.inc" +#undef FSTYPE +#undef TESTPRE +#undef TESTPAT +#undef TOOLONGFILENAME +#undef FS_MOCK_DECLARE +#undef FS_MOCK_RESET } diff --git a/tests/host/fs/test_fs.inc b/tests/host/fs/test_fs.inc new file mode 100644 index 0000000000..977d1a6413 --- /dev/null +++ b/tests/host/fs/test_fs.inc @@ -0,0 +1,289 @@ +static void createFile (const char* name, const char* content) +{ + auto f = FSTYPE.open(name, "w"); + REQUIRE(f); + if (content) { + f.print(content); + } +} + +static String readFile (const char* name) +{ + auto f = FSTYPE.open(name, "r"); + if (f) { + return f.readString(); + } + return String(); +} + +static std::set listDir (const char* path) +{ + std::set result; + Dir dir = FSTYPE.openDir(path); + while (dir.next()) { + REQUIRE(result.find(dir.fileName()) == std::end(result)); + result.insert(dir.fileName()); + } + return result; +} + +TEST_CASE(TESTPRE "FS can begin",TESTPAT) +{ + FS_MOCK_DECLARE(64, 8, 512, false); + REQUIRE(FSTYPE.begin()); +} + +TEST_CASE(TESTPRE "FS can't begin with zero size",TESTPAT) +{ + FS_MOCK_DECLARE(0, 8, 512, false); + REQUIRE_FALSE(FSTYPE.begin()); +} + +TEST_CASE(TESTPRE "Before begin is called, open will fail",TESTPAT) +{ + FS_MOCK_DECLARE(64, 8, 512, false); + REQUIRE_FALSE(FSTYPE.open("/foo", "w")); +} + +TEST_CASE(TESTPRE "FS can create file",TESTPAT) +{ + FS_MOCK_DECLARE(64, 8, 512, false); + REQUIRE(FSTYPE.begin()); + createFile("/test", ""); + REQUIRE(FSTYPE.exists("/test")); +} + +TEST_CASE(TESTPRE "Files can be written and appended to",TESTPAT) +{ + FS_MOCK_DECLARE(64, 8, 512, false); + REQUIRE(FSTYPE.begin()); + { + File f = FSTYPE.open("config1.txt", "w"); + REQUIRE(f); + f.println("file 1"); + } + { + File f = FSTYPE.open("config1.txt", "a"); + REQUIRE(f); + f.println("file 1 again"); + } + { + File f = FSTYPE.open("config1.txt", "r"); + REQUIRE(f); + char buf[128]; + size_t len = f.read((uint8_t*)buf, sizeof(buf)); + buf[len] = 0; + REQUIRE(strcmp(buf, "file 1\r\nfile 1 again\r\n") == 0); + } +} + +TEST_CASE(TESTPRE "Files persist after reset", TESTPAT) +{ + FS_MOCK_DECLARE(64, 8, 512, false); + REQUIRE(FSTYPE.begin()); + createFile("config1.txt", "file 1"); + + FS_MOCK_RESET(); + REQUIRE(FSTYPE.begin()); + REQUIRE(readFile("config1.txt") == "file 1"); +} + + +TEST_CASE(TESTPRE "Filesystem is empty after format", TESTPAT) +{ + FS_MOCK_DECLARE(64, 8, 512, false); + REQUIRE(FSTYPE.format()); + REQUIRE(FSTYPE.begin()); + createFile("/1", "first"); + createFile("/2", "second"); + REQUIRE(FSTYPE.format()); + Dir root = FSTYPE.openDir("/"); + size_t count = 0; + while (root.next()) { + ++count; + } + REQUIRE(count == 0); +} + +TEST_CASE(TESTPRE "File names which are too long are rejected", TESTPAT) +{ + FS_MOCK_DECLARE(64, 8, 512, false); + REQUIRE(FSTYPE.begin()); + const char* emptyName = ""; + const char* longName_31 = "/234567890123456789012345678901"; + const char* longName_32 = TOOLONGFILENAME; + REQUIRE_FALSE(FSTYPE.open(emptyName, "w")); + REQUIRE_FALSE(FSTYPE.open(emptyName, "r")); + REQUIRE_FALSE(FSTYPE.exists(emptyName)); + REQUIRE_FALSE(FSTYPE.open(longName_32, "w")); + REQUIRE_FALSE(FSTYPE.open(longName_32, "r")); + REQUIRE_FALSE(FSTYPE.exists(longName_32)); + REQUIRE(FSTYPE.open(longName_31, "w")); + REQUIRE(FSTYPE.open(longName_31, "r")); + REQUIRE(FSTYPE.exists(longName_31)); +} + +TEST_CASE(TESTPRE "#1685 Duplicate files", "[fs][bugreport]") +{ + FS_MOCK_DECLARE(64, 8, 512, false); + REQUIRE(FSTYPE.begin()); + createFile("/config", "some text"); + createFile("/data", ""); + readFile("/config"); + createFile("/data", "more text"); + auto files = listDir("/"); + REQUIRE(files.size() == 2); +} + +TEST_CASE(TESTPRE "#1819 Can list all files with openDir(\"\")", "[fs][bugreport]") +{ + FS_MOCK_DECLARE(96, 8, 512, false); + REQUIRE(FSTYPE.begin()); + createFile("/file1", "some text"); + createFile("/file2", "other text"); + createFile("file3", "more text"); + createFile("sorta-dir/file4", "\n"); + auto files = listDir(""); + REQUIRE(files.size() == 4); +} + +#ifdef FS_HAS_DIRS + +// We silently make subdirectories if they do not exist and silently remove +// them when they're no longer needed, so make sure we can clean up after +// ourselves. At some point we may drop this and go to normal POSIX mkdir +// behavior and expose the FS::mkdir() method, but for now this works OK. +TEST_CASE(TESTPRE "Removing all files in a subdir removes that subdir", TESTPAT) +{ + FS_MOCK_DECLARE(128, 8, 512, false); + REQUIRE(FSTYPE.begin()); + createFile("/empty", ""); + createFile("/not_empty", "some text"); + createFile("/another", "more text"); + createFile("/subdir/empty", ""); + createFile("/subdir/not_empty", "text again"); + auto files = listDir("/"); + REQUIRE(files.size() == 4); + files = listDir("/subdir"); + REQUIRE(files.size() == 2); + // Delete one of subdir, should still exist afterwards + FSTYPE.remove("subdir/empty"); + files = listDir("/subdir"); + REQUIRE(files.size() == 1); + FSTYPE.remove("subdir/not_empty"); + files = listDir("/subdir"); + REQUIRE(files.size() == 0); + files = listDir("/"); + REQUIRE(files.size() == 3); + REQUIRE(files.find("subdir") == std::end(files)); +} + +// LittleFS openDir is slightly different than SPIFFS. In SPIFFS there +// are no directories and "/" is just another character, so "/a/b/c" is a +// file in the root dir whose name is "/a/b/c". In LittleFS we have full +// directory support, so "/a/b/c" is a file "c" in the "/a/b" dir. +// This means that if you iterate over dirOpen("/") on SPIFFS you get +// a list of every file, including "subdirs". On LittleFS, you need to +// explicitly open the subdir to see its files. This behavior is the +// same as POSIX readdir(), and helps isolate subdirs from each other. +// Also note that the returned filenames in the "dir.next()" operator +// will be in that subdir (i.e. if you opendir("/a/b"); f=dir.next();" +// f.name == "c" and not "/a/b/c" as you would see in SPIFFS. +TEST_CASE(TESTPRE "Dir lists all files", TESTPAT) +{ + FS_MOCK_DECLARE(64, 8, 512, false); + REQUIRE(FSTYPE.begin()); + createFile("/empty", ""); + createFile("/not_empty", "some text"); + createFile("/another", "more text"); + createFile("/subdir/empty", ""); + createFile("/subdir/not_empty", "text again"); + auto files = listDir("/"); + REQUIRE(files.size() == 4); + bool empty = (files.find("/empty") != std::end(files)) || (files.find("empty") != std::end(files)); + REQUIRE(empty); + bool not_empty = (files.find("/not_empty") != std::end(files)) || (files.find("not_empty") != std::end(files)); + REQUIRE(not_empty); + bool another = (files.find("/another") != std::end(files)) || (files.find("another") != std::end(files)); + REQUIRE(another); + + files = listDir("/subdir"); + REQUIRE(files.size() == 2); + bool sub_empty = (files.find("/empty") != std::end(files)) || (files.find("empty") != std::end(files)); + REQUIRE(sub_empty); + bool sub_not_empty = (files.find("/not_empty") != std::end(files)) || (files.find("not_empty") != std::end(files)); + REQUIRE(sub_not_empty); +} + +TEST_CASE(TESTPRE "Listfiles.ino example", TESTPAT) +{ + FS_MOCK_DECLARE(128, 8, 512, false); + REQUIRE(FSTYPE.format()); + REQUIRE(FSTYPE.begin()); + + createFile("file1", "hello"); + createFile("file2", "hola"); + createFile("dir1/file3", "nihao"); + createFile("dir2/dir3/file4", "bonjour"); + + File root = FSTYPE.open("/", "r"); + File file1 = root.openNextFile(); + File file2 = root.openNextFile(); + File dir1 = root.openNextFile(); + File dir1_file3 = dir1.openNextFile(); + File dir2 = root.openNextFile(); + File dir2_dir3 = dir2.openNextFile(); + File dir2_dir3_file4 = dir2_dir3.openNextFile(); + + bool ok; + ok = root.isDirectory() && !root.isFile() && !strcmp(root.name(), "/"); + REQUIRE(ok); + ok = !file1.isDirectory() && file1.isFile() && !strcmp(file1.name(), "file1"); + REQUIRE(ok); + ok = !file2.isDirectory() && file2.isFile() && !strcmp(file2.name(), "file2"); + REQUIRE(ok); + ok = dir1.isDirectory() && !dir1.isFile() && !strcmp(dir1.name(), "dir1"); + REQUIRE(ok); + ok = !dir1_file3.isDirectory() && dir1_file3.isFile() && !strcmp(dir1_file3.name(), "file3") && + !strcmp(dir1_file3.fullName(), "dir1/file3"); + REQUIRE(ok); + ok = dir2.isDirectory() && !dir2.isFile() && !strcmp(dir2.name(), "dir2"); + REQUIRE(ok); + ok = dir2_dir3.isDirectory() && !dir2_dir3.isFile() && !strcmp(dir2_dir3.name(), "dir3"); + REQUIRE(ok); + ok = !dir2_dir3_file4.isDirectory() && dir2_dir3_file4.isFile() && !strcmp(dir2_dir3_file4.name(), "file4") && + !strcmp(dir2_dir3_file4.fullName(), "dir2/dir3/file4"); + REQUIRE(ok); + + REQUIRE(readFile("/file1") == "hello"); + REQUIRE(readFile("file2") == "hola"); + REQUIRE(readFile("dir1/file3") == "nihao"); + REQUIRE(readFile("/dir2/dir3/file4") == "bonjour"); +} + +#else // !FS_HAS_DIRS + +TEST_CASE(TESTPRE "Dir lists all files", TESTPAT) +{ + FS_MOCK_DECLARE(64, 8, 512, false); + REQUIRE(FSTYPE.begin()); + createFile("/empty", ""); + createFile("/not_empty", "some text"); + createFile("/another", "more text"); + createFile("/subdir/empty", ""); + createFile("/subdir/not_empty", "text again"); + auto files = listDir("/"); + REQUIRE(files.size() == 5); + bool empty = (files.find("/empty") != std::end(files)) || (files.find("empty") != std::end(files)); + REQUIRE(empty); + bool not_empty = (files.find("/not_empty") != std::end(files)) || (files.find("not_empty") != std::end(files)); + REQUIRE(not_empty); + bool another = (files.find("/another") != std::end(files)) || (files.find("another") != std::end(files)); + REQUIRE(another); + bool sub_empty = (files.find("/subdir/empty") != std::end(files)) || (files.find("subdir/empty") != std::end(files)); + REQUIRE(sub_empty); + bool sub_not_empty = (files.find("/subdir/not_empty") != std::end(files)) || (files.find("subdir/not_empty") != std::end(files)); + REQUIRE(sub_not_empty); +} + +#endif diff --git a/tools/boards.txt.py b/tools/boards.txt.py index 212fc27e77..3525700132 100755 --- a/tools/boards.txt.py +++ b/tools/boards.txt.py @@ -1233,10 +1233,10 @@ def flash_map (flashsize_kb, spiffs_kb = 0): print(" irom0_0_seg : org = 0x40201010, len = 0x%x" % max_upload_size) print("}") print("") - print("PROVIDE ( _SPIFFS_start = 0x%08X );" % (0x40200000 + spiffs_start)) - print("PROVIDE ( _SPIFFS_end = 0x%08X );" % (0x40200000 + spiffs_end)) - print("PROVIDE ( _SPIFFS_page = 0x%X );" % page) - print("PROVIDE ( _SPIFFS_block = 0x%X );" % block) + print("PROVIDE ( _FS_start = 0x%08X );" % (0x40200000 + spiffs_start)) + print("PROVIDE ( _FS_end = 0x%08X );" % (0x40200000 + spiffs_end)) + print("PROVIDE ( _FS_page = 0x%X );" % page) + print("PROVIDE ( _FS_block = 0x%X );" % block) print("") print('INCLUDE "local.eagle.app.v6.common.ld"') diff --git a/tools/sdk/ld/eagle.flash.16m14m.ld b/tools/sdk/ld/eagle.flash.16m14m.ld index ccce5ddc39..cba3a57b46 100644 --- a/tools/sdk/ld/eagle.flash.16m14m.ld +++ b/tools/sdk/ld/eagle.flash.16m14m.ld @@ -14,9 +14,9 @@ MEMORY irom0_0_seg : org = 0x40201010, len = 0xfeff0 } -PROVIDE ( _SPIFFS_start = 0x40400000 ); -PROVIDE ( _SPIFFS_end = 0x411FB000 ); -PROVIDE ( _SPIFFS_page = 0x100 ); -PROVIDE ( _SPIFFS_block = 0x2000 ); +PROVIDE ( _FS_start = 0x40400000 ); +PROVIDE ( _FS_end = 0x411FB000 ); +PROVIDE ( _FS_page = 0x100 ); +PROVIDE ( _FS_block = 0x2000 ); INCLUDE "local.eagle.app.v6.common.ld" diff --git a/tools/sdk/ld/eagle.flash.16m15m.ld b/tools/sdk/ld/eagle.flash.16m15m.ld index 93b30d219b..4e4c30a8df 100644 --- a/tools/sdk/ld/eagle.flash.16m15m.ld +++ b/tools/sdk/ld/eagle.flash.16m15m.ld @@ -14,9 +14,9 @@ MEMORY irom0_0_seg : org = 0x40201010, len = 0xfeff0 } -PROVIDE ( _SPIFFS_start = 0x40300000 ); -PROVIDE ( _SPIFFS_end = 0x411FB000 ); -PROVIDE ( _SPIFFS_page = 0x100 ); -PROVIDE ( _SPIFFS_block = 0x2000 ); +PROVIDE ( _FS_start = 0x40300000 ); +PROVIDE ( _FS_end = 0x411FB000 ); +PROVIDE ( _FS_page = 0x100 ); +PROVIDE ( _FS_block = 0x2000 ); INCLUDE "local.eagle.app.v6.common.ld" diff --git a/tools/sdk/ld/eagle.flash.1m.ld b/tools/sdk/ld/eagle.flash.1m.ld index b198d8b737..57c8ab2da6 100644 --- a/tools/sdk/ld/eagle.flash.1m.ld +++ b/tools/sdk/ld/eagle.flash.1m.ld @@ -14,9 +14,9 @@ MEMORY irom0_0_seg : org = 0x40201010, len = 0xf9ff0 } -PROVIDE ( _SPIFFS_start = 0x402FB000 ); -PROVIDE ( _SPIFFS_end = 0x402FB000 ); -PROVIDE ( _SPIFFS_page = 0x0 ); -PROVIDE ( _SPIFFS_block = 0x0 ); +PROVIDE ( _FS_start = 0x402FB000 ); +PROVIDE ( _FS_end = 0x402FB000 ); +PROVIDE ( _FS_page = 0x0 ); +PROVIDE ( _FS_block = 0x0 ); INCLUDE "local.eagle.app.v6.common.ld" diff --git a/tools/sdk/ld/eagle.flash.1m128.ld b/tools/sdk/ld/eagle.flash.1m128.ld index c6ab02feb6..729f613986 100644 --- a/tools/sdk/ld/eagle.flash.1m128.ld +++ b/tools/sdk/ld/eagle.flash.1m128.ld @@ -14,9 +14,9 @@ MEMORY irom0_0_seg : org = 0x40201010, len = 0xd9ff0 } -PROVIDE ( _SPIFFS_start = 0x402DB000 ); -PROVIDE ( _SPIFFS_end = 0x402FB000 ); -PROVIDE ( _SPIFFS_page = 0x100 ); -PROVIDE ( _SPIFFS_block = 0x1000 ); +PROVIDE ( _FS_start = 0x402DB000 ); +PROVIDE ( _FS_end = 0x402FB000 ); +PROVIDE ( _FS_page = 0x100 ); +PROVIDE ( _FS_block = 0x1000 ); INCLUDE "local.eagle.app.v6.common.ld" diff --git a/tools/sdk/ld/eagle.flash.1m144.ld b/tools/sdk/ld/eagle.flash.1m144.ld index 0da7edf593..e644897c49 100644 --- a/tools/sdk/ld/eagle.flash.1m144.ld +++ b/tools/sdk/ld/eagle.flash.1m144.ld @@ -14,9 +14,9 @@ MEMORY irom0_0_seg : org = 0x40201010, len = 0xd5ff0 } -PROVIDE ( _SPIFFS_start = 0x402D7000 ); -PROVIDE ( _SPIFFS_end = 0x402FB000 ); -PROVIDE ( _SPIFFS_page = 0x100 ); -PROVIDE ( _SPIFFS_block = 0x1000 ); +PROVIDE ( _FS_start = 0x402D7000 ); +PROVIDE ( _FS_end = 0x402FB000 ); +PROVIDE ( _FS_page = 0x100 ); +PROVIDE ( _FS_block = 0x1000 ); INCLUDE "local.eagle.app.v6.common.ld" diff --git a/tools/sdk/ld/eagle.flash.1m160.ld b/tools/sdk/ld/eagle.flash.1m160.ld index e927985243..08b54f51f1 100644 --- a/tools/sdk/ld/eagle.flash.1m160.ld +++ b/tools/sdk/ld/eagle.flash.1m160.ld @@ -14,9 +14,9 @@ MEMORY irom0_0_seg : org = 0x40201010, len = 0xd1ff0 } -PROVIDE ( _SPIFFS_start = 0x402D3000 ); -PROVIDE ( _SPIFFS_end = 0x402FB000 ); -PROVIDE ( _SPIFFS_page = 0x100 ); -PROVIDE ( _SPIFFS_block = 0x1000 ); +PROVIDE ( _FS_start = 0x402D3000 ); +PROVIDE ( _FS_end = 0x402FB000 ); +PROVIDE ( _FS_page = 0x100 ); +PROVIDE ( _FS_block = 0x1000 ); INCLUDE "local.eagle.app.v6.common.ld" diff --git a/tools/sdk/ld/eagle.flash.1m192.ld b/tools/sdk/ld/eagle.flash.1m192.ld index c45d7e6afe..77ddac33db 100644 --- a/tools/sdk/ld/eagle.flash.1m192.ld +++ b/tools/sdk/ld/eagle.flash.1m192.ld @@ -14,9 +14,9 @@ MEMORY irom0_0_seg : org = 0x40201010, len = 0xc9ff0 } -PROVIDE ( _SPIFFS_start = 0x402CB000 ); -PROVIDE ( _SPIFFS_end = 0x402FB000 ); -PROVIDE ( _SPIFFS_page = 0x100 ); -PROVIDE ( _SPIFFS_block = 0x1000 ); +PROVIDE ( _FS_start = 0x402CB000 ); +PROVIDE ( _FS_end = 0x402FB000 ); +PROVIDE ( _FS_page = 0x100 ); +PROVIDE ( _FS_block = 0x1000 ); INCLUDE "local.eagle.app.v6.common.ld" diff --git a/tools/sdk/ld/eagle.flash.1m256.ld b/tools/sdk/ld/eagle.flash.1m256.ld index 37bce0c948..b197a2b1cc 100644 --- a/tools/sdk/ld/eagle.flash.1m256.ld +++ b/tools/sdk/ld/eagle.flash.1m256.ld @@ -14,9 +14,9 @@ MEMORY irom0_0_seg : org = 0x40201010, len = 0xb9ff0 } -PROVIDE ( _SPIFFS_start = 0x402BB000 ); -PROVIDE ( _SPIFFS_end = 0x402FB000 ); -PROVIDE ( _SPIFFS_page = 0x100 ); -PROVIDE ( _SPIFFS_block = 0x1000 ); +PROVIDE ( _FS_start = 0x402BB000 ); +PROVIDE ( _FS_end = 0x402FB000 ); +PROVIDE ( _FS_page = 0x100 ); +PROVIDE ( _FS_block = 0x1000 ); INCLUDE "local.eagle.app.v6.common.ld" diff --git a/tools/sdk/ld/eagle.flash.1m512.ld b/tools/sdk/ld/eagle.flash.1m512.ld index f2df1a0734..811f7a1505 100644 --- a/tools/sdk/ld/eagle.flash.1m512.ld +++ b/tools/sdk/ld/eagle.flash.1m512.ld @@ -14,9 +14,9 @@ MEMORY irom0_0_seg : org = 0x40201010, len = 0x79ff0 } -PROVIDE ( _SPIFFS_start = 0x4027B000 ); -PROVIDE ( _SPIFFS_end = 0x402FB000 ); -PROVIDE ( _SPIFFS_page = 0x100 ); -PROVIDE ( _SPIFFS_block = 0x2000 ); +PROVIDE ( _FS_start = 0x4027B000 ); +PROVIDE ( _FS_end = 0x402FB000 ); +PROVIDE ( _FS_page = 0x100 ); +PROVIDE ( _FS_block = 0x2000 ); INCLUDE "local.eagle.app.v6.common.ld" diff --git a/tools/sdk/ld/eagle.flash.1m64.ld b/tools/sdk/ld/eagle.flash.1m64.ld index eacaa8c0eb..92447c0f9c 100644 --- a/tools/sdk/ld/eagle.flash.1m64.ld +++ b/tools/sdk/ld/eagle.flash.1m64.ld @@ -14,9 +14,9 @@ MEMORY irom0_0_seg : org = 0x40201010, len = 0xe9ff0 } -PROVIDE ( _SPIFFS_start = 0x402EB000 ); -PROVIDE ( _SPIFFS_end = 0x402FB000 ); -PROVIDE ( _SPIFFS_page = 0x100 ); -PROVIDE ( _SPIFFS_block = 0x1000 ); +PROVIDE ( _FS_start = 0x402EB000 ); +PROVIDE ( _FS_end = 0x402FB000 ); +PROVIDE ( _FS_page = 0x100 ); +PROVIDE ( _FS_block = 0x1000 ); INCLUDE "local.eagle.app.v6.common.ld" diff --git a/tools/sdk/ld/eagle.flash.2m.ld b/tools/sdk/ld/eagle.flash.2m.ld index b2d06238d6..a9aa5c8522 100644 --- a/tools/sdk/ld/eagle.flash.2m.ld +++ b/tools/sdk/ld/eagle.flash.2m.ld @@ -14,9 +14,9 @@ MEMORY irom0_0_seg : org = 0x40201010, len = 0xfeff0 } -PROVIDE ( _SPIFFS_start = 0x40400000 ); -PROVIDE ( _SPIFFS_end = 0x403FB000 ); -PROVIDE ( _SPIFFS_page = 0x0 ); -PROVIDE ( _SPIFFS_block = 0x0 ); +PROVIDE ( _FS_start = 0x40400000 ); +PROVIDE ( _FS_end = 0x403FB000 ); +PROVIDE ( _FS_page = 0x0 ); +PROVIDE ( _FS_block = 0x0 ); INCLUDE "local.eagle.app.v6.common.ld" diff --git a/tools/sdk/ld/eagle.flash.2m128.ld b/tools/sdk/ld/eagle.flash.2m128.ld index ba04e939c0..1e96769b9a 100644 --- a/tools/sdk/ld/eagle.flash.2m128.ld +++ b/tools/sdk/ld/eagle.flash.2m128.ld @@ -14,9 +14,9 @@ MEMORY irom0_0_seg : org = 0x40201010, len = 0xfeff0 } -PROVIDE ( _SPIFFS_start = 0x403E0000 ); -PROVIDE ( _SPIFFS_end = 0x403FB000 ); -PROVIDE ( _SPIFFS_page = 0x100 ); -PROVIDE ( _SPIFFS_block = 0x1000 ); +PROVIDE ( _FS_start = 0x403E0000 ); +PROVIDE ( _FS_end = 0x403FB000 ); +PROVIDE ( _FS_page = 0x100 ); +PROVIDE ( _FS_block = 0x1000 ); INCLUDE "local.eagle.app.v6.common.ld" diff --git a/tools/sdk/ld/eagle.flash.2m1m.ld b/tools/sdk/ld/eagle.flash.2m1m.ld index 08fb1b152a..2e0cbe704a 100644 --- a/tools/sdk/ld/eagle.flash.2m1m.ld +++ b/tools/sdk/ld/eagle.flash.2m1m.ld @@ -14,9 +14,9 @@ MEMORY irom0_0_seg : org = 0x40201010, len = 0xfeff0 } -PROVIDE ( _SPIFFS_start = 0x40300000 ); -PROVIDE ( _SPIFFS_end = 0x403FB000 ); -PROVIDE ( _SPIFFS_page = 0x100 ); -PROVIDE ( _SPIFFS_block = 0x2000 ); +PROVIDE ( _FS_start = 0x40300000 ); +PROVIDE ( _FS_end = 0x403FB000 ); +PROVIDE ( _FS_page = 0x100 ); +PROVIDE ( _FS_block = 0x2000 ); INCLUDE "local.eagle.app.v6.common.ld" diff --git a/tools/sdk/ld/eagle.flash.2m256.ld b/tools/sdk/ld/eagle.flash.2m256.ld index 50610cc42b..4b59a1b15a 100644 --- a/tools/sdk/ld/eagle.flash.2m256.ld +++ b/tools/sdk/ld/eagle.flash.2m256.ld @@ -14,9 +14,9 @@ MEMORY irom0_0_seg : org = 0x40201010, len = 0xfeff0 } -PROVIDE ( _SPIFFS_start = 0x403C0000 ); -PROVIDE ( _SPIFFS_end = 0x403FB000 ); -PROVIDE ( _SPIFFS_page = 0x100 ); -PROVIDE ( _SPIFFS_block = 0x1000 ); +PROVIDE ( _FS_start = 0x403C0000 ); +PROVIDE ( _FS_end = 0x403FB000 ); +PROVIDE ( _FS_page = 0x100 ); +PROVIDE ( _FS_block = 0x1000 ); INCLUDE "local.eagle.app.v6.common.ld" diff --git a/tools/sdk/ld/eagle.flash.2m512.ld b/tools/sdk/ld/eagle.flash.2m512.ld index 2d8f3e78a6..7f3b264ea3 100644 --- a/tools/sdk/ld/eagle.flash.2m512.ld +++ b/tools/sdk/ld/eagle.flash.2m512.ld @@ -14,9 +14,9 @@ MEMORY irom0_0_seg : org = 0x40201010, len = 0xfeff0 } -PROVIDE ( _SPIFFS_start = 0x40380000 ); -PROVIDE ( _SPIFFS_end = 0x403FB000 ); -PROVIDE ( _SPIFFS_page = 0x100 ); -PROVIDE ( _SPIFFS_block = 0x2000 ); +PROVIDE ( _FS_start = 0x40380000 ); +PROVIDE ( _FS_end = 0x403FB000 ); +PROVIDE ( _FS_page = 0x100 ); +PROVIDE ( _FS_block = 0x2000 ); INCLUDE "local.eagle.app.v6.common.ld" diff --git a/tools/sdk/ld/eagle.flash.4m.ld b/tools/sdk/ld/eagle.flash.4m.ld index 09166b9e92..fdaaeb183c 100644 --- a/tools/sdk/ld/eagle.flash.4m.ld +++ b/tools/sdk/ld/eagle.flash.4m.ld @@ -14,9 +14,9 @@ MEMORY irom0_0_seg : org = 0x40201010, len = 0xfeff0 } -PROVIDE ( _SPIFFS_start = 0x40600000 ); -PROVIDE ( _SPIFFS_end = 0x405FB000 ); -PROVIDE ( _SPIFFS_page = 0x0 ); -PROVIDE ( _SPIFFS_block = 0x0 ); +PROVIDE ( _FS_start = 0x40600000 ); +PROVIDE ( _FS_end = 0x405FB000 ); +PROVIDE ( _FS_page = 0x0 ); +PROVIDE ( _FS_block = 0x0 ); INCLUDE "local.eagle.app.v6.common.ld" diff --git a/tools/sdk/ld/eagle.flash.4m1m.ld b/tools/sdk/ld/eagle.flash.4m1m.ld index 74bf04bc3d..9feb6039de 100644 --- a/tools/sdk/ld/eagle.flash.4m1m.ld +++ b/tools/sdk/ld/eagle.flash.4m1m.ld @@ -14,9 +14,9 @@ MEMORY irom0_0_seg : org = 0x40201010, len = 0xfeff0 } -PROVIDE ( _SPIFFS_start = 0x40500000 ); -PROVIDE ( _SPIFFS_end = 0x405FB000 ); -PROVIDE ( _SPIFFS_page = 0x100 ); -PROVIDE ( _SPIFFS_block = 0x2000 ); +PROVIDE ( _FS_start = 0x40500000 ); +PROVIDE ( _FS_end = 0x405FB000 ); +PROVIDE ( _FS_page = 0x100 ); +PROVIDE ( _FS_block = 0x2000 ); INCLUDE "local.eagle.app.v6.common.ld" diff --git a/tools/sdk/ld/eagle.flash.4m2m.ld b/tools/sdk/ld/eagle.flash.4m2m.ld index 6eb95b1be9..5fe26ccd9d 100644 --- a/tools/sdk/ld/eagle.flash.4m2m.ld +++ b/tools/sdk/ld/eagle.flash.4m2m.ld @@ -14,9 +14,9 @@ MEMORY irom0_0_seg : org = 0x40201010, len = 0xfeff0 } -PROVIDE ( _SPIFFS_start = 0x40400000 ); -PROVIDE ( _SPIFFS_end = 0x405FB000 ); -PROVIDE ( _SPIFFS_page = 0x100 ); -PROVIDE ( _SPIFFS_block = 0x2000 ); +PROVIDE ( _FS_start = 0x40400000 ); +PROVIDE ( _FS_end = 0x405FB000 ); +PROVIDE ( _FS_page = 0x100 ); +PROVIDE ( _FS_block = 0x2000 ); INCLUDE "local.eagle.app.v6.common.ld" diff --git a/tools/sdk/ld/eagle.flash.4m3m.ld b/tools/sdk/ld/eagle.flash.4m3m.ld index 37acf69daf..f4e9e2461d 100644 --- a/tools/sdk/ld/eagle.flash.4m3m.ld +++ b/tools/sdk/ld/eagle.flash.4m3m.ld @@ -14,9 +14,9 @@ MEMORY irom0_0_seg : org = 0x40201010, len = 0xfeff0 } -PROVIDE ( _SPIFFS_start = 0x40300000 ); -PROVIDE ( _SPIFFS_end = 0x405FB000 ); -PROVIDE ( _SPIFFS_page = 0x100 ); -PROVIDE ( _SPIFFS_block = 0x2000 ); +PROVIDE ( _FS_start = 0x40300000 ); +PROVIDE ( _FS_end = 0x405FB000 ); +PROVIDE ( _FS_page = 0x100 ); +PROVIDE ( _FS_block = 0x2000 ); INCLUDE "local.eagle.app.v6.common.ld" diff --git a/tools/sdk/ld/eagle.flash.512k.ld b/tools/sdk/ld/eagle.flash.512k.ld index 65c5444bd8..4f421ab8eb 100644 --- a/tools/sdk/ld/eagle.flash.512k.ld +++ b/tools/sdk/ld/eagle.flash.512k.ld @@ -14,9 +14,9 @@ MEMORY irom0_0_seg : org = 0x40201010, len = 0x79ff0 } -PROVIDE ( _SPIFFS_start = 0x4027B000 ); -PROVIDE ( _SPIFFS_end = 0x4027B000 ); -PROVIDE ( _SPIFFS_page = 0x0 ); -PROVIDE ( _SPIFFS_block = 0x0 ); +PROVIDE ( _FS_start = 0x4027B000 ); +PROVIDE ( _FS_end = 0x4027B000 ); +PROVIDE ( _FS_page = 0x0 ); +PROVIDE ( _FS_block = 0x0 ); INCLUDE "local.eagle.app.v6.common.ld" diff --git a/tools/sdk/ld/eagle.flash.512k128.ld b/tools/sdk/ld/eagle.flash.512k128.ld index fbd64a6364..7a3c573af0 100644 --- a/tools/sdk/ld/eagle.flash.512k128.ld +++ b/tools/sdk/ld/eagle.flash.512k128.ld @@ -14,9 +14,9 @@ MEMORY irom0_0_seg : org = 0x40201010, len = 0x59ff0 } -PROVIDE ( _SPIFFS_start = 0x4025B000 ); -PROVIDE ( _SPIFFS_end = 0x4027B000 ); -PROVIDE ( _SPIFFS_page = 0x100 ); -PROVIDE ( _SPIFFS_block = 0x1000 ); +PROVIDE ( _FS_start = 0x4025B000 ); +PROVIDE ( _FS_end = 0x4027B000 ); +PROVIDE ( _FS_page = 0x100 ); +PROVIDE ( _FS_block = 0x1000 ); INCLUDE "local.eagle.app.v6.common.ld" diff --git a/tools/sdk/ld/eagle.flash.512k32.ld b/tools/sdk/ld/eagle.flash.512k32.ld index 0e1d86fc88..1334b8d8e4 100644 --- a/tools/sdk/ld/eagle.flash.512k32.ld +++ b/tools/sdk/ld/eagle.flash.512k32.ld @@ -14,9 +14,9 @@ MEMORY irom0_0_seg : org = 0x40201010, len = 0x71ff0 } -PROVIDE ( _SPIFFS_start = 0x40273000 ); -PROVIDE ( _SPIFFS_end = 0x4027B000 ); -PROVIDE ( _SPIFFS_page = 0x100 ); -PROVIDE ( _SPIFFS_block = 0x1000 ); +PROVIDE ( _FS_start = 0x40273000 ); +PROVIDE ( _FS_end = 0x4027B000 ); +PROVIDE ( _FS_page = 0x100 ); +PROVIDE ( _FS_block = 0x1000 ); INCLUDE "local.eagle.app.v6.common.ld" diff --git a/tools/sdk/ld/eagle.flash.512k64.ld b/tools/sdk/ld/eagle.flash.512k64.ld index 496c47d23b..fdea4da8e3 100644 --- a/tools/sdk/ld/eagle.flash.512k64.ld +++ b/tools/sdk/ld/eagle.flash.512k64.ld @@ -14,9 +14,9 @@ MEMORY irom0_0_seg : org = 0x40201010, len = 0x69ff0 } -PROVIDE ( _SPIFFS_start = 0x4026B000 ); -PROVIDE ( _SPIFFS_end = 0x4027B000 ); -PROVIDE ( _SPIFFS_page = 0x100 ); -PROVIDE ( _SPIFFS_block = 0x1000 ); +PROVIDE ( _FS_start = 0x4026B000 ); +PROVIDE ( _FS_end = 0x4027B000 ); +PROVIDE ( _FS_page = 0x100 ); +PROVIDE ( _FS_block = 0x1000 ); INCLUDE "local.eagle.app.v6.common.ld" diff --git a/tools/sdk/ld/eagle.flash.8m6m.ld b/tools/sdk/ld/eagle.flash.8m6m.ld index b65d46751a..c0cd9ed40a 100644 --- a/tools/sdk/ld/eagle.flash.8m6m.ld +++ b/tools/sdk/ld/eagle.flash.8m6m.ld @@ -14,9 +14,9 @@ MEMORY irom0_0_seg : org = 0x40201010, len = 0xfeff0 } -PROVIDE ( _SPIFFS_start = 0x40400000 ); -PROVIDE ( _SPIFFS_end = 0x409FB000 ); -PROVIDE ( _SPIFFS_page = 0x100 ); -PROVIDE ( _SPIFFS_block = 0x2000 ); +PROVIDE ( _FS_start = 0x40400000 ); +PROVIDE ( _FS_end = 0x409FB000 ); +PROVIDE ( _FS_page = 0x100 ); +PROVIDE ( _FS_block = 0x2000 ); INCLUDE "local.eagle.app.v6.common.ld" diff --git a/tools/sdk/ld/eagle.flash.8m7m.ld b/tools/sdk/ld/eagle.flash.8m7m.ld index 9ec7cc128b..c5f7c26552 100644 --- a/tools/sdk/ld/eagle.flash.8m7m.ld +++ b/tools/sdk/ld/eagle.flash.8m7m.ld @@ -14,9 +14,9 @@ MEMORY irom0_0_seg : org = 0x40201010, len = 0xfeff0 } -PROVIDE ( _SPIFFS_start = 0x40300000 ); -PROVIDE ( _SPIFFS_end = 0x409FB000 ); -PROVIDE ( _SPIFFS_page = 0x100 ); -PROVIDE ( _SPIFFS_block = 0x2000 ); +PROVIDE ( _FS_start = 0x40300000 ); +PROVIDE ( _FS_end = 0x409FB000 ); +PROVIDE ( _FS_page = 0x100 ); +PROVIDE ( _FS_block = 0x2000 ); INCLUDE "local.eagle.app.v6.common.ld" From c22b9d4cd388faffa7ad8698d19b7d9575ac5626 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Mon, 7 Jan 2019 10:57:58 -0800 Subject: [PATCH 02/20] Fix up merge blank line issue --- tools/sdk/ld/eagle.flash.2m.ld | 1 - tools/sdk/ld/eagle.flash.4m.ld | 1 - 2 files changed, 2 deletions(-) diff --git a/tools/sdk/ld/eagle.flash.2m.ld b/tools/sdk/ld/eagle.flash.2m.ld index dc757fe423..00b11a0734 100644 --- a/tools/sdk/ld/eagle.flash.2m.ld +++ b/tools/sdk/ld/eagle.flash.2m.ld @@ -14,7 +14,6 @@ MEMORY irom0_0_seg : org = 0x40201010, len = 0xfeff0 } - PROVIDE ( _FS_start = 0x403FB000 ); PROVIDE ( _FS_end = 0x403FB000 ); PROVIDE ( _FS_page = 0x0 ); diff --git a/tools/sdk/ld/eagle.flash.4m.ld b/tools/sdk/ld/eagle.flash.4m.ld index 84081dca3f..f77c95ae1e 100644 --- a/tools/sdk/ld/eagle.flash.4m.ld +++ b/tools/sdk/ld/eagle.flash.4m.ld @@ -14,7 +14,6 @@ MEMORY irom0_0_seg : org = 0x40201010, len = 0xfeff0 } - PROVIDE ( _FS_start = 0x405FB000 ); PROVIDE ( _FS_end = 0x405FB000 ); PROVIDE ( _FS_page = 0x0 ); From a42bc4880646e912869556baad227ef42770338e Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Mon, 7 Jan 2019 11:20:32 -0800 Subject: [PATCH 03/20] Merge in the FSConfig changs from SDFS PR Enable setConfig for LittleFS as well plys merge the SPIFFS changes done in the SDFS PR. --- cores/esp8266/FS.cpp | 8 ++++++++ cores/esp8266/FS.h | 31 +++++++++++++++++++++++++++++++ cores/esp8266/FSImpl.h | 1 + cores/esp8266/spiffs_api.h | 24 +++++++++++++++++++----- libraries/LittleFS/src/LittleFS.h | 23 ++++++++++++++++++++++- 5 files changed, 81 insertions(+), 6 deletions(-) diff --git a/cores/esp8266/FS.cpp b/cores/esp8266/FS.cpp index 0d53a5b1df..938a833748 100644 --- a/cores/esp8266/FS.cpp +++ b/cores/esp8266/FS.cpp @@ -220,6 +220,14 @@ bool Dir::rewind() { return _impl->rewind(); } +bool FS::setConfig(const FSConfig *cfg) { + if (!_impl || !cfg) { + return false; + } + + return _impl->setConfig(cfg); +} + bool FS::begin() { if (!_impl) { return false; diff --git a/cores/esp8266/FS.h b/cores/esp8266/FS.h index 0df42f9f8e..2abb5062dd 100644 --- a/cores/esp8266/FS.h +++ b/cores/esp8266/FS.h @@ -119,11 +119,41 @@ struct FSInfo { size_t maxPathLength; }; +class FSConfig +{ +public: + FSConfig(bool autoFormat = true) { + _type = FSConfig::fsid::FSId; + _autoFormat = autoFormat; + } + + enum fsid { FSId = 0x00000000 }; + FSConfig setAutoFormat(bool val = true) { + _autoFormat = val; + return *this; + } + + uint32_t _type; + bool _autoFormat; +}; + +class SPIFFSConfig : public FSConfig +{ +public: + SPIFFSConfig(bool autoFormat = true) { + _type = SPIFFSConfig::fsid::FSId; + _autoFormat = autoFormat; + } + enum fsid { FSId = 0x53504946 }; +}; + class FS { public: FS(FSImplPtr impl) : _impl(impl) { } + bool setConfig(const FSConfig *cfg); + bool begin(); void end(); @@ -166,6 +196,7 @@ using fs::SeekSet; using fs::SeekCur; using fs::SeekEnd; using fs::FSInfo; +using fs::FSConfig; #endif //FS_NO_GLOBALS #if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SPIFFS) diff --git a/cores/esp8266/FSImpl.h b/cores/esp8266/FSImpl.h index 2574563189..1ed4f5a23b 100644 --- a/cores/esp8266/FSImpl.h +++ b/cores/esp8266/FSImpl.h @@ -69,6 +69,7 @@ class DirImpl { class FSImpl { public: virtual ~FSImpl () { } + virtual bool setConfig(const FSConfig *cfg) = 0; virtual bool begin() = 0; virtual void end() = 0; virtual bool format() = 0; diff --git a/cores/esp8266/spiffs_api.h b/cores/esp8266/spiffs_api.h index fbfdd1a641..9685b6266c 100644 --- a/cores/esp8266/spiffs_api.h +++ b/cores/esp8266/spiffs_api.h @@ -123,6 +123,15 @@ class SPIFFSImpl : public FSImpl return false; } + bool setConfig(const FSConfig *cfg) override + { + if ((cfg->_type != SPIFFSConfig::fsid::FSId) || (SPIFFS_mounted(&_fs) != 0)) { + return false; + } + _cfg = *static_cast(cfg); + return true; + } + bool begin() override { #if defined(ARDUINO) && !defined(CORE_MOCK) @@ -139,12 +148,15 @@ class SPIFFSImpl : public FSImpl if (_tryMount()) { return true; } - auto rc = SPIFFS_format(&_fs); - if (rc != SPIFFS_OK) { - DEBUGV("SPIFFS_format: rc=%d, err=%d\r\n", rc, _fs.err_code); - return false; + if (_cfg._autoFormat) { + auto rc = SPIFFS_format(&_fs); + if (rc != SPIFFS_OK) { + DEBUGV("SPIFFS_format: rc=%d, err=%d\r\n", rc, _fs.err_code); + return false; + } + return _tryMount(); } - return _tryMount(); + return false; } void end() override @@ -290,6 +302,8 @@ class SPIFFSImpl : public FSImpl std::unique_ptr _workBuf; std::unique_ptr _fdsBuf; std::unique_ptr _cacheBuf; + + SPIFFSConfig _cfg; }; #define CHECKFD() while (_fd == 0) { panic(); } diff --git a/libraries/LittleFS/src/LittleFS.h b/libraries/LittleFS/src/LittleFS.h index e3b6d154e6..16de249e1a 100644 --- a/libraries/LittleFS/src/LittleFS.h +++ b/libraries/LittleFS/src/LittleFS.h @@ -44,6 +44,16 @@ namespace littlefs_impl { class LittleFSFileImpl; class LittleFSDirImpl; +class LittleFSConfig : public FSConfig +{ +public: + LittleFSConfig(bool autoFormat = true) { + _type = LittleFSConfig::fsid::FSId; + _autoFormat = autoFormat; + } + enum fsid { FSId = 0x4c495454 }; +}; + class LittleFSImpl : public FSImpl { public: @@ -147,6 +157,14 @@ class LittleFSImpl : public FSImpl return remove(path); // Same call on LittleFS } + bool setConfig(const FSConfig *cfg) override { + if ((cfg->_type != LittleFSConfig::fsid::FSId) || _mounted) { + return false; + } + _cfg = *static_cast(cfg); + return true; + } + bool begin() override { if (_size <= 0) { DEBUGV("LittleFS size is <= zero"); @@ -155,7 +173,7 @@ class LittleFSImpl : public FSImpl if (_tryMount()) { return true; } - if (!format()) { + if (!_cfg._autoFormat || !format()) { return false; } return _tryMount(); @@ -282,6 +300,8 @@ class LittleFSImpl : public FSImpl lfs_t _lfs; lfs_config _lfs_cfg; + LittleFSConfig _cfg; + uint32_t _start; uint32_t _size; uint32_t _pageSize; @@ -521,6 +541,7 @@ class LittleFSDirImpl : public DirImpl #if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_LITTLEFS) extern FS LittleFS; +using littlefs_impl::LittleFSConfig; #endif // ARDUINO From e3e8de31d5e94a9b2122f62c7979f180759bea00 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Wed, 27 Feb 2019 12:12:00 -0800 Subject: [PATCH 04/20] Fix merge errors --- tests/host/common/littlefs_mock.cpp | 91 ++++++++++++++++------------- tests/host/common/littlefs_mock.h | 16 ++--- tests/host/fs/test_fs.inc | 28 ++++----- 3 files changed, 67 insertions(+), 68 deletions(-) diff --git a/tests/host/common/littlefs_mock.cpp b/tests/host/common/littlefs_mock.cpp index 7fad8e8b7b..9959893be6 100644 --- a/tests/host/common/littlefs_mock.cpp +++ b/tests/host/common/littlefs_mock.cpp @@ -31,31 +31,26 @@ #include #include #include +#include "flash_hal_mock.h" #define LITTLEFS_FILE_NAME "littlefs.bin" -extern "C" -{ - extern uint32_t s_phys_size; - extern uint32_t s_phys_page; - extern uint32_t s_phys_block; - extern uint8_t* s_phys_data; -} - FS LittleFS(nullptr); -LittleFSMock::LittleFSMock(size_t fs_size, size_t fs_block, size_t fs_page, bool storage) +LittleFSMock::LittleFSMock(ssize_t fs_size, size_t fs_block, size_t fs_page, const String& storage) { - fprintf(stderr, "LittleFS: %zd bytes\n", fs_size); - m_storage = storage; - m_fs = new uint8_t[m_fs_size = fs_size]; - memset(&m_fs[0], 0xff, m_fs_size); + if ((m_overwrite = (fs_size < 0))) + fs_size = -fs_size; + fprintf(stderr, "LittleFS: %zd bytes\n", fs_size); + + m_fs.resize(fs_size, 0xff); + s_phys_addr = 0; s_phys_size = static_cast(fs_size); s_phys_page = static_cast(fs_page); s_phys_block = static_cast(fs_block); - s_phys_data = &m_fs[0]; + s_phys_data = m_fs.data(); reset(); } @@ -68,59 +63,71 @@ void LittleFSMock::reset() LittleFSMock::~LittleFSMock() { - if (m_storage) - save(); + save(); + s_phys_addr = 0; s_phys_size = 0; s_phys_page = 0; s_phys_block = 0; s_phys_data = nullptr; - delete [] m_fs; - m_fs = nullptr; - m_fs_size = 0; + m_fs.resize(0); LittleFS = FS(FSImplPtr(nullptr)); } void LittleFSMock::load () { - if (!m_fs_size) + if (!m_fs.size() || !m_storage.length()) return; - - const char* fname = getenv("LITTLEFS_PATH"); - if (!fname) - fname = DEFAULT_LITTLEFS_FILE_NAME; - int fs = ::open(LITTLEFS_FILE_NAME, O_RDONLY); + + int fs = ::open(m_storage.c_str(), O_RDONLY); if (fs == -1) { - fprintf(stderr, "LittleFS: loading '%s': %s\n", fname, strerror(errno)); + fprintf(stderr, "LittleFS: loading '%s': %s\n", m_storage.c_str(), strerror(errno)); return; } - fprintf(stderr, "LittleFS: loading %zi bytes from '%s'\n", m_fs_size, fname); - if (::read(fs, &m_fs[0], m_fs_size) != (ssize_t)m_fs_size) - fprintf(stderr, "LittleFS: reading %zi bytes: %s\n", m_fs_size, strerror(errno)); + + off_t flen = lseek(fs, 0, SEEK_END); + if (flen == (off_t)-1) + { + fprintf(stderr, "LittleFS: checking size of '%s': %s\n", m_storage.c_str(), strerror(errno)); + return; + } + lseek(fs, 0, SEEK_SET); + + if (flen != (off_t)m_fs.size()) + { + fprintf(stderr, "LittleFS: size of '%s': %d does not match requested size %zd\n", m_storage.c_str(), (int)flen, m_fs.size()); + if (!m_overwrite) + { + fprintf(stderr, "LittleFS: aborting at user request\n"); + exit(1); + } + fprintf(stderr, "LittleFS: continuing without loading at user request, '%s' will be overwritten\n", m_storage.c_str()); + } + else + { + fprintf(stderr, "LittleFS: loading %zi bytes from '%s'\n", m_fs.size(), m_storage.c_str()); + ssize_t r = ::read(fs, m_fs.data(), m_fs.size()); + if (r != (ssize_t)m_fs.size()) + fprintf(stderr, "LittleFS: reading %zi bytes: returned %zd: %s\n", m_fs.size(), r, strerror(errno)); + } ::close(fs); } void LittleFSMock::save () { - if (!m_fs_size) + if (!m_fs.size() || !m_storage.length()) return; - const char* fname = getenv("LITTLEFS_PATH"); - if (!fname) - fname = DEFAULT_LITTLEFS_FILE_NAME; - int fs = ::open(LITTLEFS_FILE_NAME, O_CREAT | O_TRUNC | O_WRONLY, 0644); + int fs = ::open(m_storage.c_str(), O_CREAT | O_TRUNC | O_WRONLY, 0644); if (fs == -1) { - fprintf(stderr, "LittleFS: saving '%s': %s\n", fname, strerror(errno)); + fprintf(stderr, "LittleFS: saving '%s': %s\n", m_storage.c_str(), strerror(errno)); return; } - fprintf(stderr, "LittleFS: saving %zi bytes to '%s'\n", m_fs_size, fname); - -// this can be a valgrind error, I don't understand how it happens -//for (size_t i = 0; i < m_fs_size; i++) printf("\r%zd:%d ", i, (int)m_fs[i]); + fprintf(stderr, "LittleFS: saving %zi bytes to '%s'\n", m_fs.size(), m_storage.c_str()); - if (::write(fs, &m_fs[0], m_fs_size) != (ssize_t)m_fs_size) - fprintf(stderr, "LittleFS: writing %zi bytes: %s\n", m_fs_size, strerror(errno)); + if (::write(fs, m_fs.data(), m_fs.size()) != (ssize_t)m_fs.size()) + fprintf(stderr, "LittleFS: writing %zi bytes: %s\n", m_fs.size(), strerror(errno)); if (::close(fs) == -1) - fprintf(stderr, "LittleFS: closing %s: %s\n", fname, strerror(errno)); + fprintf(stderr, "LittleFS: closing %s: %s\n", m_storage.c_str(), strerror(errno)); } diff --git a/tests/host/common/littlefs_mock.h b/tests/host/common/littlefs_mock.h index 17ce8c48dd..0905fa9322 100644 --- a/tests/host/common/littlefs_mock.h +++ b/tests/host/common/littlefs_mock.h @@ -29,7 +29,7 @@ class LittleFSMock { public: - LittleFSMock(size_t fs_size, size_t fs_block, size_t fs_page, bool storage = true); + LittleFSMock(ssize_t fs_size, size_t fs_block, size_t fs_page, const String& storage = emptyString); void reset(); ~LittleFSMock(); @@ -37,17 +37,9 @@ class LittleFSMock { void load (); void save (); - // it was a vector, but CI tests & valgrind complain with: - // Syscall param write(buf) points to uninitialised byte(s) - // by 0x43E9FF: SpiffsMock::save() (littlefs_mock.cpp:116) - // = if (::write(fs, &m_fs[0], m_fs_size) != (ssize_t)m_fs_size) - // so switched to a regular array - // and that bug is still here - // XXXWIPTODO - - uint8_t* m_fs; - size_t m_fs_size; - bool m_storage; + std::vector m_fs; + String m_storage; + bool m_overwrite; }; #define LITTLEFS_MOCK_DECLARE(size_kb, block_kb, page_b, storage) LittleFSMock littlefs_mock(size_kb * 1024, block_kb * 1024, page_b, storage) diff --git a/tests/host/fs/test_fs.inc b/tests/host/fs/test_fs.inc index 977d1a6413..a96a6caa14 100644 --- a/tests/host/fs/test_fs.inc +++ b/tests/host/fs/test_fs.inc @@ -29,25 +29,25 @@ static std::set listDir (const char* path) TEST_CASE(TESTPRE "FS can begin",TESTPAT) { - FS_MOCK_DECLARE(64, 8, 512, false); + FS_MOCK_DECLARE(64, 8, 512, ""); REQUIRE(FSTYPE.begin()); } TEST_CASE(TESTPRE "FS can't begin with zero size",TESTPAT) { - FS_MOCK_DECLARE(0, 8, 512, false); + FS_MOCK_DECLARE(0, 8, 512, ""); REQUIRE_FALSE(FSTYPE.begin()); } TEST_CASE(TESTPRE "Before begin is called, open will fail",TESTPAT) { - FS_MOCK_DECLARE(64, 8, 512, false); + FS_MOCK_DECLARE(64, 8, 512, ""); REQUIRE_FALSE(FSTYPE.open("/foo", "w")); } TEST_CASE(TESTPRE "FS can create file",TESTPAT) { - FS_MOCK_DECLARE(64, 8, 512, false); + FS_MOCK_DECLARE(64, 8, 512, ""); REQUIRE(FSTYPE.begin()); createFile("/test", ""); REQUIRE(FSTYPE.exists("/test")); @@ -55,7 +55,7 @@ TEST_CASE(TESTPRE "FS can create file",TESTPAT) TEST_CASE(TESTPRE "Files can be written and appended to",TESTPAT) { - FS_MOCK_DECLARE(64, 8, 512, false); + FS_MOCK_DECLARE(64, 8, 512, ""); REQUIRE(FSTYPE.begin()); { File f = FSTYPE.open("config1.txt", "w"); @@ -79,7 +79,7 @@ TEST_CASE(TESTPRE "Files can be written and appended to",TESTPAT) TEST_CASE(TESTPRE "Files persist after reset", TESTPAT) { - FS_MOCK_DECLARE(64, 8, 512, false); + FS_MOCK_DECLARE(64, 8, 512, ""); REQUIRE(FSTYPE.begin()); createFile("config1.txt", "file 1"); @@ -91,7 +91,7 @@ TEST_CASE(TESTPRE "Files persist after reset", TESTPAT) TEST_CASE(TESTPRE "Filesystem is empty after format", TESTPAT) { - FS_MOCK_DECLARE(64, 8, 512, false); + FS_MOCK_DECLARE(64, 8, 512, ""); REQUIRE(FSTYPE.format()); REQUIRE(FSTYPE.begin()); createFile("/1", "first"); @@ -107,7 +107,7 @@ TEST_CASE(TESTPRE "Filesystem is empty after format", TESTPAT) TEST_CASE(TESTPRE "File names which are too long are rejected", TESTPAT) { - FS_MOCK_DECLARE(64, 8, 512, false); + FS_MOCK_DECLARE(64, 8, 512, ""); REQUIRE(FSTYPE.begin()); const char* emptyName = ""; const char* longName_31 = "/234567890123456789012345678901"; @@ -125,7 +125,7 @@ TEST_CASE(TESTPRE "File names which are too long are rejected", TESTPAT) TEST_CASE(TESTPRE "#1685 Duplicate files", "[fs][bugreport]") { - FS_MOCK_DECLARE(64, 8, 512, false); + FS_MOCK_DECLARE(64, 8, 512, ""); REQUIRE(FSTYPE.begin()); createFile("/config", "some text"); createFile("/data", ""); @@ -137,7 +137,7 @@ TEST_CASE(TESTPRE "#1685 Duplicate files", "[fs][bugreport]") TEST_CASE(TESTPRE "#1819 Can list all files with openDir(\"\")", "[fs][bugreport]") { - FS_MOCK_DECLARE(96, 8, 512, false); + FS_MOCK_DECLARE(96, 8, 512, ""); REQUIRE(FSTYPE.begin()); createFile("/file1", "some text"); createFile("/file2", "other text"); @@ -155,7 +155,7 @@ TEST_CASE(TESTPRE "#1819 Can list all files with openDir(\"\")", "[fs][bugreport // behavior and expose the FS::mkdir() method, but for now this works OK. TEST_CASE(TESTPRE "Removing all files in a subdir removes that subdir", TESTPAT) { - FS_MOCK_DECLARE(128, 8, 512, false); + FS_MOCK_DECLARE(128, 8, 512, ""); REQUIRE(FSTYPE.begin()); createFile("/empty", ""); createFile("/not_empty", "some text"); @@ -191,7 +191,7 @@ TEST_CASE(TESTPRE "Removing all files in a subdir removes that subdir", TESTPAT) // f.name == "c" and not "/a/b/c" as you would see in SPIFFS. TEST_CASE(TESTPRE "Dir lists all files", TESTPAT) { - FS_MOCK_DECLARE(64, 8, 512, false); + FS_MOCK_DECLARE(64, 8, 512, ""); REQUIRE(FSTYPE.begin()); createFile("/empty", ""); createFile("/not_empty", "some text"); @@ -217,7 +217,7 @@ TEST_CASE(TESTPRE "Dir lists all files", TESTPAT) TEST_CASE(TESTPRE "Listfiles.ino example", TESTPAT) { - FS_MOCK_DECLARE(128, 8, 512, false); + FS_MOCK_DECLARE(128, 8, 512, ""); REQUIRE(FSTYPE.format()); REQUIRE(FSTYPE.begin()); @@ -265,7 +265,7 @@ TEST_CASE(TESTPRE "Listfiles.ino example", TESTPAT) TEST_CASE(TESTPRE "Dir lists all files", TESTPAT) { - FS_MOCK_DECLARE(64, 8, 512, false); + FS_MOCK_DECLARE(64, 8, 512, ""); REQUIRE(FSTYPE.begin()); createFile("/empty", ""); createFile("/not_empty", "some text"); From 13e985c0675328707f97d6f65b011736cc6f2d45 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Fri, 1 Mar 2019 13:46:54 -0800 Subject: [PATCH 05/20] Update to use v2-alpha branch The V2-alpha branch supports small file optimizations which can help increase the utilization of flash when small files are prevalent. It also adds support for metadata, which means we can start adding things like file creation times, if desired (not yet). --- libraries/LittleFS/lib/littlefs | 2 +- libraries/LittleFS/src/LittleFS.h | 18 +++++++----------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/libraries/LittleFS/lib/littlefs b/libraries/LittleFS/lib/littlefs index ec4d8b68ad..dbf97be09d 160000 --- a/libraries/LittleFS/lib/littlefs +++ b/libraries/LittleFS/lib/littlefs @@ -1 +1 @@ -Subproject commit ec4d8b68add6a7de021dc09ef08123ab323cbc38 +Subproject commit dbf97be09d3a6ed6c803d1a3d8edaf09c1fd8be8 diff --git a/libraries/LittleFS/src/LittleFS.h b/libraries/LittleFS/src/LittleFS.h index 16de249e1a..07c5ac7929 100644 --- a/libraries/LittleFS/src/LittleFS.h +++ b/libraries/LittleFS/src/LittleFS.h @@ -71,11 +71,15 @@ class LittleFSImpl : public FSImpl _lfs_cfg.prog_size = _pageSize; _lfs_cfg.block_size = _blockSize; _lfs_cfg.block_count = _size / _blockSize; - _lfs_cfg.lookahead = 256; + _lfs_cfg.block_cycles = 16; // TODO - need better explanation + _lfs_cfg.cache_size = _blockSize; + _lfs_cfg.lookahead_size = 256; _lfs_cfg.read_buffer = nullptr; _lfs_cfg.prog_buffer = nullptr; _lfs_cfg.lookahead_buffer = nullptr; - _lfs_cfg.file_buffer = nullptr; + _lfs_cfg.name_max = 0; + _lfs_cfg.file_max = 0; + _lfs_cfg.attr_max = 0; } ~LittleFSImpl() { @@ -234,19 +238,11 @@ class LittleFSImpl : public FSImpl return _mounted; } - static int _lfs_traverse_cb(void *p, lfs_block_t b) { - (void) b; - *(lfs_size_t *)p += 1; - return 0; - } - int _getUsedBlocks() { - lfs_size_t in_use = 0; if (!_mounted) { return 0; } - int err = lfs_traverse(&_lfs, _lfs_traverse_cb, &in_use); - return err ? 0 : in_use; + return lfs_fs_size(&_lfs); } static int _getFlags(OpenMode openMode, AccessMode accessMode) { From 3340b95e835e3903d37665b16a154a884f8e11e4 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Thu, 11 Apr 2019 11:59:39 -0700 Subject: [PATCH 06/20] V2 of littlefs is now in upstream/master --- libraries/LittleFS/lib/littlefs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/LittleFS/lib/littlefs b/libraries/LittleFS/lib/littlefs index dbf97be09d..0907ba7813 160000 --- a/libraries/LittleFS/lib/littlefs +++ b/libraries/LittleFS/lib/littlefs @@ -1 +1 @@ -Subproject commit dbf97be09d3a6ed6c803d1a3d8edaf09c1fd8be8 +Subproject commit 0907ba7813173901bc9a7689e8781434629a3696 From 31e925a4c233c69fcd66f6f25025a19083538cc1 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Thu, 11 Apr 2019 12:11:20 -0700 Subject: [PATCH 07/20] Update test to support non-creation-ordered files In a directory, the order in which "readNextFile()" will return a name is undefined. SPIFFS may return it in order, but LittleFS does not as of V2. Update the test to look for files by name when doing readNextFile() testing. --- tests/host/fs/test_fs.inc | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/tests/host/fs/test_fs.inc b/tests/host/fs/test_fs.inc index a96a6caa14..2e78d23885 100644 --- a/tests/host/fs/test_fs.inc +++ b/tests/host/fs/test_fs.inc @@ -215,6 +215,14 @@ TEST_CASE(TESTPRE "Dir lists all files", TESTPAT) REQUIRE(sub_not_empty); } +File FindFileByName(const File f[], int count, const char *name) +{ + for (int i=0; i Date: Thu, 11 Apr 2019 16:12:08 -0700 Subject: [PATCH 08/20] Fix LittleFS.truncate implementation --- libraries/LittleFS/src/LittleFS.cpp | 4 ++-- libraries/LittleFS/src/LittleFS.h | 2 +- tests/host/common/sdfs_mock.h | 2 +- tests/host/fs/test_fs.cpp | 2 +- tests/host/fs/test_fs.inc | 25 ++++++++++++------------- 5 files changed, 17 insertions(+), 18 deletions(-) diff --git a/libraries/LittleFS/src/LittleFS.cpp b/libraries/LittleFS/src/LittleFS.cpp index 75366acc09..b4aba1970a 100644 --- a/libraries/LittleFS/src/LittleFS.cpp +++ b/libraries/LittleFS/src/LittleFS.cpp @@ -115,7 +115,7 @@ DirImplPtr LittleFSImpl::openDir(const char *path) { filter = pathStr; } else { // We've got slashes, open the dir one up - *ptr = 0; // Remove slash, truncare string + *ptr = 0; // Remove slash, truncate string rc = lfs_dir_open(&_lfs, dir.get(), pathStr); filter = ptr + 1; } @@ -130,7 +130,7 @@ DirImplPtr LittleFSImpl::openDir(const char *path) { filter = pathStr; } else { // We've got slashes, open the dir one up - *ptr = 0; // Remove slash, truncare string + *ptr = 0; // Remove slash, truncate string rc = lfs_dir_open(&_lfs, dir.get(), pathStr); filter = ptr + 1; } diff --git a/libraries/LittleFS/src/LittleFS.h b/libraries/LittleFS/src/LittleFS.h index b9e205bf70..a27b90b370 100644 --- a/libraries/LittleFS/src/LittleFS.h +++ b/libraries/LittleFS/src/LittleFS.h @@ -392,7 +392,7 @@ class LittleFSFileImpl : public FileImpl } bool truncate(uint32_t size) override { - if (_opened || !_fd) { + if (!_opened || !_fd) { return false; } int rc = lfs_file_truncate(_fs->getFS(), _getFD(), size); diff --git a/tests/host/common/sdfs_mock.h b/tests/host/common/sdfs_mock.h index e89b1b2a28..8dd471c87d 100644 --- a/tests/host/common/sdfs_mock.h +++ b/tests/host/common/sdfs_mock.h @@ -28,7 +28,7 @@ class SDFSMock { ~SDFSMock() { } }; -#define SDFS_MOCK_DECLARE(size_kb, block_kb, page_b, storage) SDFSMock sdfs_mock(size_kb * 1024, block_kb * 1024, page_b, storage) +#define SDFS_MOCK_DECLARE(size_kb, block_kb, page_b, storage) SDFSMock sdfs_mock(size_kb * 1024, block_kb * 1024, page_b, storage); SDFS.format(); #define SDFS_MOCK_RESET() sdfs_mock.reset() #endif /* spiffs_mock_hpp */ diff --git a/tests/host/fs/test_fs.cpp b/tests/host/fs/test_fs.cpp index 1c2371c6aa..7fc2ac4c76 100644 --- a/tests/host/fs/test_fs.cpp +++ b/tests/host/fs/test_fs.cpp @@ -91,7 +91,7 @@ TEST_CASE("LittleFS checks the config object passed in", "[fs]") }; namespace sdfs_test { -#define FSTYPE LittleFS +#define FSTYPE SDFS #define TESTPRE "SDFS - " #define TESTPAT "[sdfs]" // SDFS routines strip leading slashes before doing anything, so up to 31 char names are allowable diff --git a/tests/host/fs/test_fs.inc b/tests/host/fs/test_fs.inc index e570ffd183..df5183675a 100644 --- a/tests/host/fs/test_fs.inc +++ b/tests/host/fs/test_fs.inc @@ -147,6 +147,18 @@ TEST_CASE(TESTPRE "#1819 Can list all files with openDir(\"\")", "[fs][bugreport REQUIRE(files.size() == 4); } +TEST_CASE(TESTPRE "truncate", TESTPAT) +{ + FS_MOCK_DECLARE(64, 8, 512, ""); + REQUIRE(FSTYPE.begin()); + createFile("/file1", "some text"); + auto f = FSTYPE.open("/file1", "r+"); + REQUIRE(f.truncate(4)); + f.close(); + String s = readFile("/file1"); + REQUIRE( s == "some" ); +} + #ifdef FS_HAS_DIRS // We silently make subdirectories if they do not exist and silently remove @@ -215,19 +227,6 @@ TEST_CASE(TESTPRE "Dir lists all files", TESTPAT) REQUIRE(sub_not_empty); } -TEST_CASE(TESTPRE "truncate", TESTPAT) -{ - FS_MOCK_DECLARE(64, 8, 512, ""); - REQUIRE(FSTYPE.begin()); - createFile("/file1", "some text"); - auto f = FSTYPE.open("/file1", "r"); - f.truncate(4); - f.close(); - String s = readFile("/file1"); - REQUIRE( s == "some" ); -} - - File FindFileByName(const File f[], int count, const char *name) { for (int i=0; i Date: Thu, 11 Apr 2019 18:46:30 -0700 Subject: [PATCH 09/20] Fix SDFS tests SDFS, SPIFFS, and LittleFS now all share the same common set of tests, greatly increasing the SDFS test coverage. --- libraries/ESP8266SdFat | 2 +- tests/host/common/sdfs_mock.cpp | 4 ++-- tests/host/common/sdfs_mock.h | 11 ++++++++++- tests/host/fs/test_fs.cpp | 12 ++++++++---- tests/host/fs/test_fs.inc | 4 ++++ 5 files changed, 25 insertions(+), 8 deletions(-) diff --git a/libraries/ESP8266SdFat b/libraries/ESP8266SdFat index 92ab4b59eb..dbe6bc3c79 160000 --- a/libraries/ESP8266SdFat +++ b/libraries/ESP8266SdFat @@ -1 +1 @@ -Subproject commit 92ab4b59eb920bf3134347dbe07e7c059eb64d46 +Subproject commit dbe6bc3c79b6479869a5ee132365caa02f67a142 diff --git a/tests/host/common/sdfs_mock.cpp b/tests/host/common/sdfs_mock.cpp index 2a0433756b..ef2ae44152 100644 --- a/tests/host/common/sdfs_mock.cpp +++ b/tests/host/common/sdfs_mock.cpp @@ -17,5 +17,5 @@ #include "../../../libraries/SDFS/src/SDFS.h" #define SDSIZE 16LL -uint64_t _sdCardSizeB = SDSIZE * 1024LL * 1024LL; -uint8_t _sdCard[SDSIZE * 1024LL * 1024LL]; +uint64_t _sdCardSizeB = 0; +uint8_t *_sdCard = nullptr; diff --git a/tests/host/common/sdfs_mock.h b/tests/host/common/sdfs_mock.h index 8dd471c87d..4508e1a903 100644 --- a/tests/host/common/sdfs_mock.h +++ b/tests/host/common/sdfs_mock.h @@ -28,7 +28,16 @@ class SDFSMock { ~SDFSMock() { } }; -#define SDFS_MOCK_DECLARE(size_kb, block_kb, page_b, storage) SDFSMock sdfs_mock(size_kb * 1024, block_kb * 1024, page_b, storage); SDFS.format(); +extern uint64_t _sdCardSizeB; +extern uint8_t *_sdCard; + +#define SDFS_MOCK_DECLARE(size_kb, block_kb, page_b, storage) \ + SDFS.end(); \ + SDFSMock sdfs_mock(size_kb * 1024, block_kb * 1024, page_b, storage); free(_sdCard); \ + _sdCardSizeB = size_kb ? 16 * 1024 * 1024 : 0; \ + if (_sdCardSizeB) _sdCard = (uint8_t*)calloc(_sdCardSizeB, 1); \ + else _sdCard = nullptr; \ + SDFS.setConfig(SDFSConfig().setAutoFormat(true)); #define SDFS_MOCK_RESET() sdfs_mock.reset() #endif /* spiffs_mock_hpp */ diff --git a/tests/host/fs/test_fs.cpp b/tests/host/fs/test_fs.cpp index 7fc2ac4c76..a9327a2907 100644 --- a/tests/host/fs/test_fs.cpp +++ b/tests/host/fs/test_fs.cpp @@ -24,6 +24,7 @@ #include "../../../libraries/SDFS/src/SDFS.h" #include "../../../libraries/SD/src/SD.h" +#if 1 namespace spiffs_test { #define FSTYPE SPIFFS #define TESTPRE "SPIFFS - " @@ -89,13 +90,16 @@ TEST_CASE("LittleFS checks the config object passed in", "[fs]") } }; - +#endif namespace sdfs_test { #define FSTYPE SDFS #define TESTPRE "SDFS - " #define TESTPAT "[sdfs]" -// SDFS routines strip leading slashes before doing anything, so up to 31 char names are allowable -#define TOOLONGFILENAME "/12345678901234567890123456789012" +// SDFS supports long paths (MAXPATH) +#define TOOLONGFILENAME "/" \ + "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" \ + "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" \ + "12345678901234567890123456789012345678901234567890123456" #define FS_MOCK_DECLARE SDFS_MOCK_DECLARE #define FS_MOCK_RESET SDFS_MOCK_RESET #define FS_HAS_DIRS @@ -118,7 +122,7 @@ TEST_CASE("SDFS checks the config object passed in", "[fs]") REQUIRE_FALSE(SDFS.setConfig(f)); REQUIRE_FALSE(SDFS.setConfig(s)); REQUIRE(SDFS.setConfig(d)); - REQUIRE_FALSE(LittleFS.setConfig(l)); + REQUIRE_FALSE(SDFS.setConfig(l)); } }; diff --git a/tests/host/fs/test_fs.inc b/tests/host/fs/test_fs.inc index df5183675a..e6bedfa181 100644 --- a/tests/host/fs/test_fs.inc +++ b/tests/host/fs/test_fs.inc @@ -96,7 +96,9 @@ TEST_CASE(TESTPRE "Filesystem is empty after format", TESTPAT) REQUIRE(FSTYPE.begin()); createFile("/1", "first"); createFile("/2", "second"); + FSTYPE.end(); REQUIRE(FSTYPE.format()); + REQUIRE(FSTYPE.begin()); Dir root = FSTYPE.openDir("/"); size_t count = 0; while (root.next()) { @@ -161,6 +163,7 @@ TEST_CASE(TESTPRE "truncate", TESTPAT) #ifdef FS_HAS_DIRS +#if FSTYPE != SDFS // We silently make subdirectories if they do not exist and silently remove // them when they're no longer needed, so make sure we can clean up after // ourselves. At some point we may drop this and go to normal POSIX mkdir @@ -189,6 +192,7 @@ TEST_CASE(TESTPRE "Removing all files in a subdir removes that subdir", TESTPAT) REQUIRE(files.size() == 3); REQUIRE(files.find("subdir") == std::end(files)); } +#endif // LittleFS openDir is slightly different than SPIFFS. In SPIFFS there // are no directories and "/" is just another character, so "/a/b/c" is a From c3ac05853122efde49884c54f4ba56ffb25699f9 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Thu, 11 Apr 2019 19:39:32 -0700 Subject: [PATCH 10/20] Update to point to mklittlefs v2 Upgrade mklittlefs to V2 format support --- .../package_esp8266com_index.template.json | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/package/package_esp8266com_index.template.json b/package/package_esp8266com_index.template.json index ec8b1daf53..4326c87373 100644 --- a/package/package_esp8266com_index.template.json +++ b/package/package_esp8266com_index.template.json @@ -121,7 +121,7 @@ }, { "packager": "esp8266", - "version": "2.5.0-3-4a23057", + "version": "2.5.1-1", "name": "mklittlefs" }, { @@ -302,50 +302,50 @@ ] }, { - "version": "2.5.0-3-4a23057", + "version": "2.5.1-1", "name": "mklittlefs", "systems": [ { "host": "aarch64-linux-gnu", - "url": "https://github.com/earlephilhower/mklittlefs/releases/download/2.5.0-3/aarch64-linux-gnu-mklittlefs-4a23057.tar.gz", - "archiveFileName": "aarch64-linux-gnu-mklittlefs-4a23057.tar.gz", - "checksum": "SHA-256:436f1593c4075bd023e66fcb266532d910c07298d4dfde58a56ea78e57feb5a6", - "size": "39151" + "url": "https://github.com/earlephilhower/mklittlefs/releases/download/2.5.1-1/aarch64-linux-gnu-mklittlefs-47e8167.tar.gz", + "archiveFileName": "aarch64-linux-gnu-mklittlefs-47e8167.tar.gz", + "checksum": "SHA-256:f5951f0f5c0649992cccbed0146e5ec91dd0a2294f391956bf65c32404a98e96", + "size": "44128" }, { "host": "arm-linux-gnueabihf", - "url": "https://github.com/earlephilhower/mklittlefs/releases/download/2.5.0-3/arm-linux-gnueabihf-mklittlefs-4a23057.tar.gz", - "archiveFileName": "arm-linux-gnueabihf-mklittlefs-4a23057.tar.gz", - "checksum": "SHA-256:f20cb21aebda0db732c337d92ddd628d7f6d7e7110a19d39107ed2bdb357b193", - "size": "31561" + "url": "https://github.com/earlephilhower/mklittlefs/releases/download/2.5.1-1/arm-linux-gnueabihf-mklittlefs-47e8167.tar.gz", + "archiveFileName": "arm-linux-gnueabihf-mklittlefs-47e8167.tar.gz", + "checksum": "SHA-256:c5252ce0ae177238efecfa6835950ae12b3cb9dbb7f6d04caedf90afcf0b27b6", + "size": "36590" }, { "host": "i686-mingw32", - "url": "https://github.com/earlephilhower/mklittlefs/releases/download/2.5.0-3/i686-w64-mingw32-mklittlefs-4a23057.zip", - "archiveFileName": "i686-w64-mingw32-mklittlefs-4a23057.zip", - "checksum": "SHA-256:64e038c5fc172341247b3ebcb9f5ea190b6fc2f046948b668dbc588a38572f9e", - "size": "327466" + "url": "https://github.com/earlephilhower/mklittlefs/releases/download/2.5.1-1/i686-w64-mingw32-mklittlefs-47e8167.zip", + "archiveFileName": "i686-w64-mingw32-mklittlefs-47e8167.zip", + "checksum": "SHA-256:85b2dccc9c00ab2747ffbcee8b949c2dc88747f7a7e14cd0bac5b8a8afe19a4b", + "size": "332150" }, { "host": "x86_64-apple-darwin", - "url": "https://github.com/earlephilhower/mklittlefs/releases/download/2.5.0-3/x86_64-apple-darwin14-mklittlefs-4a23057.tar.gz", - "archiveFileName": "x86_64-apple-darwin14-mklittlefs-4a23057.tar.gz", - "checksum": "SHA-256:b8e08f21e433375fe7cef406bf80924e56b8cbb2b89722e018926e2ffb06d533", - "size": "356900" + "url": "https://github.com/earlephilhower/mklittlefs/releases/download/2.5.1-1/x86_64-apple-darwin14-mklittlefs-47e8167.tar.gz", + "archiveFileName": "x86_64-apple-darwin14-mklittlefs-47e8167.tar.gz", + "checksum": "SHA-256:47ba021a929d62b1f5f85f61c935b23fdc3a07bdf01eb3d5f36f064ff4e42154", + "size": "362031" }, { "host": "x86_64-pc-linux-gnu", - "url": "https://github.com/earlephilhower/mklittlefs/releases/download/2.5.0-3/x86_64-linux-gnu-mklittlefs-4a23057.tar.gz", - "archiveFileName": "x86_64-linux-gnu-mklittlefs-4a23057.tar.gz", - "checksum": "SHA-256:add92ff1d71a1fdadfd8132c48d6bdc00c88d1f2a80b8843af7a5a08205f9490", - "size": "40450" + "url": "https://github.com/earlephilhower/mklittlefs/releases/download/2.5.1-1/x86_64-linux-gnu-mklittlefs-47e8167.tar.gz", + "archiveFileName": "x86_64-linux-gnu-mklittlefs-47e8167.tar.gz", + "checksum": "SHA-256:1e93a62cd550aa2fdb874820ead827880b964bea845a6fa053d12ddb8e39f8f0", + "size": "46226" }, { "host": "x86_64-mingw32", - "url": "https://github.com/earlephilhower/mklittlefs/releases/download/2.5.0-3/x86_64-w64-mingw32-mklittlefs-4a23057.zip", - "archiveFileName": "x86_64-w64-mingw32-mklittlefs-4a23057.zip", - "checksum": "SHA-256:944493f35dfdf01cbc628f99f5b2649843f827d8bafbeb11160c72ef6822356e", - "size": "339768" + "url": "https://github.com/earlephilhower/mklittlefs/releases/download/2.5.1-1/x86_64-w64-mingw32-mklittlefs-47e8167.zip", + "archiveFileName": "x86_64-w64-mingw32-mklittlefs-47e8167.zip", + "checksum": "SHA-256:0136c76a710c90b0f47d9f01ab4bf46e93daba9935dd498eb9d8b52f23550626", + "size": "344669" } ] } From e3e686076829992d849d28a750babd7fe554c1b8 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Thu, 11 Apr 2019 19:56:50 -0700 Subject: [PATCH 11/20] Remove extra FS::write(const char *s) method This was removed in #5861 and erroneously re-introduced here. --- cores/esp8266/FS.h | 1 - 1 file changed, 1 deletion(-) diff --git a/cores/esp8266/FS.h b/cores/esp8266/FS.h index fda6b116a0..f15024b6a6 100644 --- a/cores/esp8266/FS.h +++ b/cores/esp8266/FS.h @@ -81,7 +81,6 @@ class File : public Stream bool isDirectory() const; // Arduino "class SD" methods for compatibility - size_t write(const char *str) { return write((const uint8_t*)str, strlen(str)); } void rewindDirectory(); File openNextFile(); From 4a827a502e52baac95b67358500abf2fdcc49fbd Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Thu, 11 Apr 2019 20:09:01 -0700 Subject: [PATCH 12/20] Minimize spurious differences from master --- cores/esp8266/FS.h | 9 +++++---- package/package_esp8266com_index.template.json | 2 +- tests/host/common/littlefs_mock.cpp | 5 ++--- tests/host/common/spiffs_mock.cpp | 3 +-- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/cores/esp8266/FS.h b/cores/esp8266/FS.h index f15024b6a6..a36c3fb8cd 100644 --- a/cores/esp8266/FS.h +++ b/cores/esp8266/FS.h @@ -58,7 +58,6 @@ class File : public Stream // Stream methods: int available() override; int read() override; - String readString() override; int peek() override; void flush() override; size_t readBytes(char *buffer, size_t length) override { @@ -81,9 +80,6 @@ class File : public Stream bool isDirectory() const; // Arduino "class SD" methods for compatibility - void rewindDirectory(); - File openNextFile(); - template size_t write(T &src){ uint8_t obuf[256]; size_t doneLen = 0; @@ -107,6 +103,11 @@ class File : public Stream } using Print::write; + void rewindDirectory(); + File openNextFile(); + + String readString() override; + protected: FileImplPtr _p; diff --git a/package/package_esp8266com_index.template.json b/package/package_esp8266com_index.template.json index 4326c87373..b3f3ce31bf 100644 --- a/package/package_esp8266com_index.template.json +++ b/package/package_esp8266com_index.template.json @@ -3,8 +3,8 @@ { "name": "esp8266", "maintainer": "ESP8266 Community", - "email": "ivan@esp8266.com", "websiteURL": "https://github.com/esp8266/Arduino", + "email": "ivan@esp8266.com", "help": { "online": "http://esp8266.com/arduino" }, diff --git a/tests/host/common/littlefs_mock.cpp b/tests/host/common/littlefs_mock.cpp index 9959893be6..0f21d7f71c 100644 --- a/tests/host/common/littlefs_mock.cpp +++ b/tests/host/common/littlefs_mock.cpp @@ -1,5 +1,5 @@ /* - littlefs_mock.cpp - SPIFFS HAL mock for host side testing + littlefs_mock.cpp - LittleFS mock for host side testing Copyright © 2019 Earle F. Philhower, III Based off spiffs_mock.cpp: @@ -57,8 +57,7 @@ LittleFSMock::LittleFSMock(ssize_t fs_size, size_t fs_block, size_t fs_page, con void LittleFSMock::reset() { LittleFS = FS(FSImplPtr(new littlefs_impl::LittleFSImpl(0, s_phys_size, s_phys_page, s_phys_block, 5))); - if (m_storage) - load(); + load(); } LittleFSMock::~LittleFSMock() diff --git a/tests/host/common/spiffs_mock.cpp b/tests/host/common/spiffs_mock.cpp index 588a63d583..3e85fa06ab 100644 --- a/tests/host/common/spiffs_mock.cpp +++ b/tests/host/common/spiffs_mock.cpp @@ -53,8 +53,7 @@ SpiffsMock::SpiffsMock(ssize_t fs_size, size_t fs_block, size_t fs_page, const S void SpiffsMock::reset() { SPIFFS = FS(FSImplPtr(new spiffs_impl::SPIFFSImpl(0, s_phys_size, s_phys_page, s_phys_block, 5))); - if (m_storage) - load(); + load(); } SpiffsMock::~SpiffsMock() From 0874498f92f1a08cb0107e384867262e6d3f0eeb Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Sun, 14 Apr 2019 11:26:03 -0700 Subject: [PATCH 13/20] Dramatically reduce memory usage Reduce the program and read chunk sizes which impacts performance minimally but reduces per-file RAM usage of 16KB to <1KB. --- libraries/LittleFS/src/LittleFS.h | 8 ++++---- libraries/LittleFS/src/lfs.c | 3 +++ libraries/LittleFS/src/lfs_util.c | 3 +++ 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/libraries/LittleFS/src/LittleFS.h b/libraries/LittleFS/src/LittleFS.h index a27b90b370..5537a6b89c 100644 --- a/libraries/LittleFS/src/LittleFS.h +++ b/libraries/LittleFS/src/LittleFS.h @@ -67,13 +67,13 @@ class LittleFSImpl : public FSImpl _lfs_cfg.prog = lfs_flash_prog; _lfs_cfg.erase = lfs_flash_erase; _lfs_cfg.sync = lfs_flash_sync; - _lfs_cfg.read_size = _pageSize; - _lfs_cfg.prog_size = _pageSize; + _lfs_cfg.read_size = 64; + _lfs_cfg.prog_size = 64; _lfs_cfg.block_size = _blockSize; _lfs_cfg.block_count = _size / _blockSize; _lfs_cfg.block_cycles = 16; // TODO - need better explanation - _lfs_cfg.cache_size = _blockSize; - _lfs_cfg.lookahead_size = 256; + _lfs_cfg.cache_size = 64; + _lfs_cfg.lookahead_size = 64; _lfs_cfg.read_buffer = nullptr; _lfs_cfg.prog_buffer = nullptr; _lfs_cfg.lookahead_buffer = nullptr; diff --git a/libraries/LittleFS/src/lfs.c b/libraries/LittleFS/src/lfs.c index f68368bed3..62022fc375 100644 --- a/libraries/LittleFS/src/lfs.c +++ b/libraries/LittleFS/src/lfs.c @@ -3,5 +3,8 @@ #pragma GCC diagnostic ignored "-Wmissing-field-initializers" #define LFS_NAME_MAX 32 +#define LFS_NO_DEBUG +#define LFS_NO_WARN +#define LFS_NO_ERROR #include "../lib/littlefs/lfs.c" diff --git a/libraries/LittleFS/src/lfs_util.c b/libraries/LittleFS/src/lfs_util.c index 0db909222f..43ada31207 100644 --- a/libraries/LittleFS/src/lfs_util.c +++ b/libraries/LittleFS/src/lfs_util.c @@ -1,3 +1,6 @@ #define LFS_NAME_MAX 32 +#define LFS_NO_DEBUG +#define LFS_NO_WARN +#define LFS_NO_ERROR #include "../lib/littlefs/lfs_util.c" From dc3e14a48fea25f73bce71a8356f9657a5e531b0 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Sun, 14 Apr 2019 19:09:44 -0700 Subject: [PATCH 14/20] Add @d-a-v's host emulation for LittleFS --- libraries/SoftwareSerial | 2 +- tests/host/Makefile | 1 + tests/host/common/ArduinoMain.cpp | 18 +++++++++++++++++- tests/host/common/mock.h | 2 ++ 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/libraries/SoftwareSerial b/libraries/SoftwareSerial index b330eba900..b178c0ff02 160000 --- a/libraries/SoftwareSerial +++ b/libraries/SoftwareSerial @@ -1 +1 @@ -Subproject commit b330eba900d9f69b0c2310ba36192d0d14354510 +Subproject commit b178c0ff023ff87a4754636b624a6d18e6036a07 diff --git a/tests/host/Makefile b/tests/host/Makefile index 77c5867c18..fee3cc6332 100644 --- a/tests/host/Makefile +++ b/tests/host/Makefile @@ -106,6 +106,7 @@ MOCK_CPP_FILES_EMU := $(MOCK_CPP_FILES_COMMON) $(addprefix common/,\ ArduinoMain.cpp \ ArduinoMainUdp.cpp \ ArduinoMainSpiffs.cpp \ + ArduinoMainLittlefs.cpp \ user_interface.cpp \ ) diff --git a/tests/host/common/ArduinoMain.cpp b/tests/host/common/ArduinoMain.cpp index dba9ab30fb..b0fb164ef0 100644 --- a/tests/host/common/ArduinoMain.cpp +++ b/tests/host/common/ArduinoMain.cpp @@ -44,6 +44,7 @@ bool user_exit = false; const char* host_interface = nullptr; size_t spiffs_kb = 1024; +size_t littlefs_kb = 1024; bool ignore_sigint = false; bool restore_tty = false; bool mockdebug = false; @@ -127,9 +128,10 @@ void help (const char* argv0, int exitcode) " -f - no throttle (possibly 100%%CPU)\n" " -b - blocking tty/mocked-uart (default: not blocking tty)\n" " -S - spiffs size in KBytes (default: %zd)\n" + " -L - littlefs size in KBytes (default: %zd)\n" " -v - mock verbose\n" " (negative value will force mismatched size)\n" - , argv0, MOCK_PORT_SHIFTER, spiffs_kb); + , argv0, MOCK_PORT_SHIFTER, spiffs_kb, littlefs_kb); exit(exitcode); } @@ -143,12 +145,14 @@ static struct option options[] = { "verbose", no_argument, NULL, 'v' }, { "interface", required_argument, NULL, 'i' }, { "spiffskb", required_argument, NULL, 'S' }, + { "littlefskb", required_argument, NULL, 'L' }, { "portshifter", required_argument, NULL, 's' }, }; void cleanup () { mock_stop_spiffs(); + mock_stop_littlefs(); mock_stop_uart(); } @@ -204,6 +208,9 @@ int main (int argc, char* const argv []) case 'S': spiffs_kb = atoi(optarg); break; + case 'L': + littlefs_kb = atoi(optarg); + break; case 'b': blocking_uart = true; break; @@ -226,6 +233,15 @@ int main (int argc, char* const argv []) mock_start_spiffs(name, spiffs_kb); } + if (littlefs_kb) + { + String name = argv[0]; + name += "-littlefs"; + name += String(littlefs_kb > 0? littlefs_kb: -littlefs_kb, DEC); + name += "KB"; + mock_start_littlefs(name, littlefs_kb); + } + // setup global global_ipv4_netfmt wifi_get_ip_info(0, nullptr); diff --git a/tests/host/common/mock.h b/tests/host/common/mock.h index 067411139f..9ec319f1f6 100644 --- a/tests/host/common/mock.h +++ b/tests/host/common/mock.h @@ -137,6 +137,8 @@ class InterruptLock { }; void mock_start_spiffs (const String& fname, size_t size_kb, size_t block_kb = 8, size_t page_b = 512); void mock_stop_spiffs (); +void mock_start_littlefs (const String& fname, size_t size_kb, size_t block_kb = 8, size_t page_b = 512); +void mock_stop_littlefs (); // From 6f9ac166860ba558ceea6d674ff6ba54ceb876c5 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Mon, 15 Apr 2019 08:38:13 -0700 Subject: [PATCH 15/20] Fix SW Serial library version --- libraries/SoftwareSerial | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/SoftwareSerial b/libraries/SoftwareSerial index b178c0ff02..b330eba900 160000 --- a/libraries/SoftwareSerial +++ b/libraries/SoftwareSerial @@ -1 +1 @@ -Subproject commit b178c0ff023ff87a4754636b624a6d18e6036a07 +Subproject commit b330eba900d9f69b0c2310ba36192d0d14354510 From 78fbb648756fd291eb416f05e92959b9c4862180 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Thu, 18 Apr 2019 14:54:23 -0700 Subject: [PATCH 16/20] Fix free space reporting Thanks to @TD-er for discovering the issue --- libraries/LittleFS/src/LittleFS.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/LittleFS/src/LittleFS.h b/libraries/LittleFS/src/LittleFS.h index 5537a6b89c..f81541557c 100644 --- a/libraries/LittleFS/src/LittleFS.h +++ b/libraries/LittleFS/src/LittleFS.h @@ -122,7 +122,7 @@ class LittleFSImpl : public FSImpl info.maxOpenFiles = _maxOpenFds; info.maxPathLength = LFS_NAME_MAX; info.totalBytes = _size; - info.usedBytes = _size - _getUsedBlocks() * _blockSize; + info.usedBytes = _getUsedBlocks() * _blockSize; return true; } From 88a9cf6b9e6199f1946160fa2228124cef0df29c Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Mon, 22 Apr 2019 17:15:40 -0700 Subject: [PATCH 17/20] Update littlefs to latest upstream --- libraries/LittleFS/lib/littlefs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/LittleFS/lib/littlefs b/libraries/LittleFS/lib/littlefs index 0907ba7813..f35fb8c148 160000 --- a/libraries/LittleFS/lib/littlefs +++ b/libraries/LittleFS/lib/littlefs @@ -1 +1 @@ -Subproject commit 0907ba7813173901bc9a7689e8781434629a3696 +Subproject commit f35fb8c14866a4a4677756f6dbeca78f8a9b4001 From 7bdfaf04a6b42137aacc87869336d906070d5a0e Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Wed, 1 May 2019 00:03:01 -0700 Subject: [PATCH 18/20] Remove sdfat version included by accident --- libraries/ESP8266SdFat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP8266SdFat b/libraries/ESP8266SdFat index dbe6bc3c79..f096292195 160000 --- a/libraries/ESP8266SdFat +++ b/libraries/ESP8266SdFat @@ -1 +1 @@ -Subproject commit dbe6bc3c79b6479869a5ee132365caa02f67a142 +Subproject commit f0962921955e2503243f28de0fdc9ac188177ffb From f0fb05115ad459ee520edac1b6a79c1721f91315 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Wed, 1 May 2019 07:27:45 -0700 Subject: [PATCH 19/20] Update SDFAT to include MOCK changes required --- libraries/ESP8266SdFat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP8266SdFat b/libraries/ESP8266SdFat index f096292195..2499d4e0c6 160000 --- a/libraries/ESP8266SdFat +++ b/libraries/ESP8266SdFat @@ -1 +1 @@ -Subproject commit f0962921955e2503243f28de0fdc9ac188177ffb +Subproject commit 2499d4e0c60db1a67a47532fc44c17d2d5116e4c From e016613950359abd0cf2e02dd6e07d61e9bacda1 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Fri, 17 May 2019 08:30:46 -0700 Subject: [PATCH 20/20] Update to include SD.h test of file append --- libraries/SD/src/SD.h | 2 +- tests/host/fs/test_fs.cpp | 38 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/libraries/SD/src/SD.h b/libraries/SD/src/SD.h index 2c376f8e42..7b4e33df6f 100644 --- a/libraries/SD/src/SD.h +++ b/libraries/SD/src/SD.h @@ -27,7 +27,7 @@ #undef FILE_READ #define FILE_READ sdfat::O_READ #undef FILE_WRITE -#define FILE_WRITE (sdfat::O_READ | sdfat::O_WRITE | sdfat::O_CREAT) +#define FILE_WRITE (sdfat::O_READ | sdfat::O_WRITE | sdfat::O_CREAT | sdfat::O_APPEND) class SDClass { public: diff --git a/tests/host/fs/test_fs.cpp b/tests/host/fs/test_fs.cpp index a9327a2907..1b54a21576 100644 --- a/tests/host/fs/test_fs.cpp +++ b/tests/host/fs/test_fs.cpp @@ -24,7 +24,7 @@ #include "../../../libraries/SDFS/src/SDFS.h" #include "../../../libraries/SD/src/SD.h" -#if 1 + namespace spiffs_test { #define FSTYPE SPIFFS #define TESTPRE "SPIFFS - " @@ -90,7 +90,7 @@ TEST_CASE("LittleFS checks the config object passed in", "[fs]") } }; -#endif + namespace sdfs_test { #define FSTYPE SDFS #define TESTPRE "SDFS - " @@ -125,4 +125,38 @@ TEST_CASE("SDFS checks the config object passed in", "[fs]") REQUIRE_FALSE(SDFS.setConfig(l)); } +// Also a SD specific test to check that FILE_OPEN is really an append operation: + +TEST_CASE("SD.h FILE_WRITE macro is append", "[fs]") +{ + SDFS_MOCK_DECLARE(64, 8, 512, ""); + REQUIRE(SDFS.begin()); + REQUIRE(SD.begin(4)); + + File f = SD.open("/file.txt", FILE_WRITE); + f.write('a'); + f.write(65); + f.write("bbcc"); + f.write("theend", 6); + char block[3]={'x','y','z'}; + f.write(block, 3); + uint32_t bigone = 0x40404040; + f.write((const uint8_t*)&bigone, 4); + f.close(); + REQUIRE(readFile("/file.txt") == "aAbbcctheendxyz@@@@"); + f = SD.open("/file.txt", FILE_WRITE); + f.write("append", 6); + f.close(); + REQUIRE(readFile("/file.txt") == "aAbbcctheendxyz@@@@append"); + + File g = SD.open("/file2.txt", FILE_WRITE); + g.write(0); + g.close(); + g = SD.open("/file2.txt", FILE_READ); + uint8_t u = 0x66; + g.read(&u, 1); + g.close(); + REQUIRE(u == 0); +} + };