Compare commits

...

2 commits
master ... dbg

Author SHA1 Message Date
Earle F. Philhower, III
ec6c8dcda1 Always enable USB serial port for reset
To allow for uploads when sketches don't manually start the Serial port,
start the USB port always in main().
2021-03-28 17:49:59 -07:00
Earle F. Philhower, III
24bfb00571 Add LittleFS Filesystem and File:: interface
Pull in the ESP8266 File/Dir/etc. filesystem and port LittleFS to the
RP2040.
2021-03-28 15:58:17 -07:00
12 changed files with 2028 additions and 0 deletions

3
.gitmodules vendored
View file

@ -7,3 +7,6 @@
[submodule "system/pyserial"]
path = tools/pyserial
url = https://github.com/pyserial/pyserial.git
[submodule "libraries/LittleFS/lib/littlefs"]
path = libraries/LittleFS/lib/littlefs
url = https://github.com/littlefs-project/littlefs.git

563
cores/rp2040/FS.cpp Normal file
View file

@ -0,0 +1,563 @@
/*
FS.cpp - file system wrapper
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 "FS.h"
#include "FSImpl.h"
using namespace fs;
static bool sflags(const char* mode, OpenMode& om, AccessMode& am);
size_t File::write(uint8_t c) {
if (!_p)
return 0;
return _p->write(&c, 1);
}
size_t File::write(const uint8_t *buf, size_t size) {
if (!_p)
return 0;
return _p->write(buf, size);
}
int File::available() {
if (!_p)
return false;
return _p->size() - _p->position();
}
int File::availableForWrite() {
if (!_p)
return false;
return _p->availableForWrite();
}
int File::read() {
if (!_p)
return -1;
uint8_t result;
if (_p->read(&result, 1) != 1) {
return -1;
}
return result;
}
int File::read(uint8_t* buf, size_t size) {
if (!_p)
return 0;
return _p->read(buf, size);
}
int File::peek() {
if (!_p)
return -1;
size_t curPos = _p->position();
int result = read();
seek(curPos, SeekSet);
return result;
}
void File::flush() {
if (!_p)
return;
_p->flush();
}
bool File::seek(uint32_t pos, SeekMode mode) {
if (!_p)
return false;
return _p->seek(pos, mode);
}
size_t File::position() const {
if (!_p)
return 0;
return _p->position();
}
size_t File::size() const {
if (!_p)
return 0;
return _p->size();
}
void File::close() {
if (_p) {
_p->close();
_p = nullptr;
}
}
File::operator bool() const {
return !!_p;
}
bool File::truncate(uint32_t size) {
if (!_p)
return false;
return _p->truncate(size);
}
const char* File::name() const {
if (!_p)
return nullptr;
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<Dir>(_baseFS->openDir(fullName()));
} else {
_fakeDir->rewind();
}
}
File File::openNextFile() {
if (!_fakeDir) {
_fakeDir = std::make_shared<Dir>(_baseFS->openDir(fullName()));
}
_fakeDir->next();
return _fakeDir->openFile("r");
}
String File::readString()
{
String ret;
ret.reserve(size() - position());
char temp[256+1];
int countRead = readBytes(temp, sizeof(temp)-1);
while (countRead > 0)
{
temp[countRead] = 0;
ret += temp;
countRead = readBytes(temp, sizeof(temp)-1);
}
return ret;
}
time_t File::getLastWrite() {
if (!_p)
return 0;
return _p->getLastWrite();
}
time_t File::getCreationTime() {
if (!_p)
return 0;
return _p->getCreationTime();
}
void File::setTimeCallback(time_t (*cb)(void)) {
if (!_p)
return;
_p->setTimeCallback(cb);
_timeCallback = cb;
}
File Dir::openFile(const char* mode) {
if (!_impl) {
return File();
}
OpenMode om;
AccessMode am;
if (!sflags(mode, om, am)) {
DEBUGV("Dir::openFile: invalid mode `%s`\r\n", mode);
return File();
}
File f(_impl->openFile(om, am), _baseFS);
f.setTimeCallback(_timeCallback);
return f;
}
String Dir::fileName() {
if (!_impl) {
return String();
}
return _impl->fileName();
}
time_t Dir::fileTime() {
if (!_impl)
return 0;
return _impl->fileTime();
}
time_t Dir::fileCreationTime() {
if (!_impl)
return 0;
return _impl->fileCreationTime();
}
size_t Dir::fileSize() {
if (!_impl) {
return 0;
}
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;
}
return _impl->next();
}
bool Dir::rewind() {
if (!_impl) {
return false;
}
return _impl->rewind();
}
void Dir::setTimeCallback(time_t (*cb)(void)) {
if (!_impl)
return;
_impl->setTimeCallback(cb);
_timeCallback = cb;
}
bool FS::setConfig(const FSConfig &cfg) {
if (!_impl) {
return false;
}
return _impl->setConfig(cfg);
}
bool FS::begin() {
if (!_impl) {
DEBUGV("#error: FS: no implementation");
return false;
}
_impl->setTimeCallback(_timeCallback);
bool ret = _impl->begin();
DEBUGV("%s\n", ret? "": "#error: FS could not start");
return ret;
}
void FS::end() {
if (_impl) {
_impl->end();
}
}
bool FS::gc() {
if (!_impl) {
return false;
}
return _impl->gc();
}
bool FS::check() {
if (!_impl) {
return false;
}
return _impl->check();
}
bool FS::format() {
if (!_impl) {
return false;
}
return _impl->format();
}
bool FS::info(FSInfo& info){
if (!_impl) {
return false;
}
return _impl->info(info);
}
bool FS::info64(FSInfo64& info){
if (!_impl) {
return false;
}
return _impl->info64(info);
}
File FS::open(const String& path, const char* mode) {
return open(path.c_str(), mode);
}
File FS::open(const char* path, const char* mode) {
if (!_impl) {
return File();
}
OpenMode om;
AccessMode am;
if (!sflags(mode, om, am)) {
DEBUGV("FS::open: invalid mode `%s`\r\n", mode);
return File();
}
File f(_impl->open(path, om, am), this);
f.setTimeCallback(_timeCallback);
return f;
}
bool FS::exists(const char* path) {
if (!_impl) {
return false;
}
return _impl->exists(path);
}
bool FS::exists(const String& path) {
return exists(path.c_str());
}
Dir FS::openDir(const char* path) {
if (!_impl) {
return Dir();
}
DirImplPtr p = _impl->openDir(path);
Dir d(p, this);
d.setTimeCallback(_timeCallback);
return d;
}
Dir FS::openDir(const String& path) {
return openDir(path.c_str());
}
bool FS::remove(const char* path) {
if (!_impl) {
return false;
}
return _impl->remove(path);
}
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;
}
return _impl->rename(pathFrom, pathTo);
}
bool FS::rename(const String& pathFrom, const String& pathTo) {
return rename(pathFrom.c_str(), pathTo.c_str());
}
time_t FS::getCreationTime() {
if (!_impl) {
return 0;
}
return _impl->getCreationTime();
}
void FS::setTimeCallback(time_t (*cb)(void)) {
if (!_impl)
return;
_impl->setTimeCallback(cb);
_timeCallback = cb;
}
static bool sflags(const char* mode, OpenMode& om, AccessMode& am) {
switch (mode[0]) {
case 'r':
am = AM_READ;
om = OM_DEFAULT;
break;
case 'w':
am = AM_WRITE;
om = (OpenMode) (OM_CREATE | OM_TRUNCATE);
break;
case 'a':
am = AM_WRITE;
om = (OpenMode) (OM_CREATE | OM_APPEND);
break;
default:
return false;
}
switch(mode[1]) {
case '+':
am = (AccessMode) (AM_WRITE | AM_READ);
break;
case 0:
break;
default:
return false;
}
return true;
}
#if defined(FS_FREESTANDING_FUNCTIONS)
/*
TODO: move these functions to public API:
*/
File open(const char* path, const char* mode);
File open(String& path, const char* mode);
Dir openDir(const char* path);
Dir openDir(String& path);
template<>
bool mount<FS>(FS& fs, const char* mountPoint);
/*
*/
struct MountEntry {
FSImplPtr fs;
String path;
MountEntry* next;
};
static MountEntry* s_mounted = nullptr;
template<>
bool mount<FS>(FS& fs, const char* mountPoint) {
FSImplPtr p = fs._impl;
if (!p || !p->mount()) {
DEBUGV("FSImpl mount failed\r\n");
return false;
}
!make sure mountPoint has trailing '/' here
MountEntry* entry = new MountEntry;
entry->fs = p;
entry->path = mountPoint;
entry->next = s_mounted;
s_mounted = entry;
return true;
}
/*
iterate over MountEntries and look for the ones which match the path
*/
File open(const char* path, const char* mode) {
OpenMode om;
AccessMode am;
if (!sflags(mode, om, am)) {
DEBUGV("open: invalid mode `%s`\r\n", mode);
return File();
}
for (MountEntry* entry = s_mounted; entry; entry = entry->next) {
size_t offset = entry->path.length();
if (strstr(path, entry->path.c_str())) {
File result = entry->fs->open(path + offset);
if (result)
return result;
}
}
return File();
}
File open(const String& path, const char* mode) {
return FS::open(path.c_str(), mode);
}
Dir openDir(const String& path) {
return openDir(path.c_str());
}
#endif

278
cores/rp2040/FS.h Normal file
View file

@ -0,0 +1,278 @@
/*
FS.h - file system wrapper
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
*/
#ifndef FS_H
#define FS_H
#include <memory>
#include <Arduino.h>
#include <../include/time.h> // See issue #6714
class SDClass;
namespace fs {
class File;
class Dir;
class FS;
class FileImpl;
typedef std::shared_ptr<FileImpl> FileImplPtr;
class FSImpl;
typedef std::shared_ptr<FSImpl> FSImplPtr;
class DirImpl;
typedef std::shared_ptr<DirImpl> DirImplPtr;
template <typename Tfs>
bool mount(Tfs& fs, const char* mountPoint);
enum SeekMode {
SeekSet = 0,
SeekCur = 1,
SeekEnd = 2
};
class File : public Stream
{
public:
File(FileImplPtr p = FileImplPtr(), FS *baseFS = nullptr) : _p(p), _fakeDir(nullptr), _baseFS(baseFS) { }
// Print methods:
size_t write(uint8_t) override;
size_t write(const uint8_t *buf, size_t size) override;
int availableForWrite() override;
// Stream methods:
int available() override;
int read() override;
int peek() override;
void flush() override;
size_t readBytes(char *buffer, size_t length) {
return read((uint8_t*)buffer, length);
}
int read(uint8_t* buf, size_t size);
bool seek(uint32_t pos, SeekMode mode);
bool seek(uint32_t pos) {
return seek(pos, SeekSet);
}
size_t position() const;
size_t size() const;
virtual ssize_t streamRemaining() { return (ssize_t)size() - (ssize_t)position(); }
void close();
operator bool() const;
const char* name() const;
const char* fullName() const; // Includes path
bool truncate(uint32_t size);
bool isFile() const;
bool isDirectory() const;
// Arduino "class SD" methods for compatibility
//TODO use stream::send / check read(buf,size) result
template<typename T> size_t write(T &src){
uint8_t obuf[256];
size_t doneLen = 0;
size_t sentLen;
int i;
while (src.available() > sizeof(obuf)){
src.read(obuf, sizeof(obuf));
sentLen = write(obuf, sizeof(obuf));
doneLen = doneLen + sentLen;
if(sentLen != sizeof(obuf)){
return doneLen;
}
}
size_t leftLen = src.available();
src.read(obuf, leftLen);
sentLen = write(obuf, leftLen);
doneLen = doneLen + sentLen;
return doneLen;
}
using Print::write;
void rewindDirectory();
File openNextFile();
String readString();
time_t getLastWrite();
time_t getCreationTime();
void setTimeCallback(time_t (*cb)(void));
protected:
FileImplPtr _p;
time_t (*_timeCallback)(void) = nullptr;
// Arduino SD class emulation
std::shared_ptr<Dir> _fakeDir;
FS *_baseFS;
};
class Dir {
public:
Dir(DirImplPtr impl = DirImplPtr(), FS *baseFS = nullptr): _impl(impl), _baseFS(baseFS) { }
File openFile(const char* mode);
String fileName();
size_t fileSize();
time_t fileTime();
time_t fileCreationTime();
bool isFile() const;
bool isDirectory() const;
bool next();
bool rewind();
void setTimeCallback(time_t (*cb)(void));
protected:
DirImplPtr _impl;
FS *_baseFS;
time_t (*_timeCallback)(void) = nullptr;
};
// Backwards compatible, <4GB filesystem usage
struct FSInfo {
size_t totalBytes;
size_t usedBytes;
size_t blockSize;
size_t pageSize;
size_t maxOpenFiles;
size_t maxPathLength;
};
// Support > 4GB filesystems (SD, etc.)
struct FSInfo64 {
uint64_t totalBytes;
uint64_t usedBytes;
size_t blockSize;
size_t pageSize;
size_t maxOpenFiles;
size_t maxPathLength;
};
class FSConfig
{
public:
static constexpr uint32_t FSId = 0x00000000;
FSConfig(uint32_t type = FSId, bool autoFormat = true) : _type(type), _autoFormat(autoFormat) { }
FSConfig setAutoFormat(bool val = true) {
_autoFormat = val;
return *this;
}
uint32_t _type;
bool _autoFormat;
};
class SPIFFSConfig : public FSConfig
{
public:
static constexpr uint32_t FSId = 0x53504946;
SPIFFSConfig(bool autoFormat = true) : FSConfig(FSId, autoFormat) { }
// Inherit _type and _autoFormat
// nothing yet, enableTime TBD when SPIFFS has metadate
};
class FS
{
public:
FS(FSImplPtr impl) : _impl(impl) { _timeCallback = _defaultTimeCB; }
bool setConfig(const FSConfig &cfg);
bool begin();
void end();
bool format();
bool info(FSInfo& info);
bool info64(FSInfo64& info);
File open(const char* path, const char* mode);
File open(const String& path, const char* mode);
bool exists(const char* path);
bool exists(const String& path);
Dir openDir(const char* path);
Dir openDir(const String& path);
bool remove(const char* path);
bool remove(const String& path);
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);
// Low-level FS routines, not needed by most applications
bool gc();
bool check();
time_t getCreationTime();
void setTimeCallback(time_t (*cb)(void));
friend class ::SDClass; // More of a frenemy, but SD needs internal implementation to get private FAT bits
protected:
FSImplPtr _impl;
FSImplPtr getImpl() { return _impl; }
time_t (*_timeCallback)(void) = nullptr;
static time_t _defaultTimeCB(void) { return time(NULL); }
};
} // namespace fs
extern "C"
{
void close_all_fs(void);
void littlefs_request_end(void);
void spiffs_request_end(void);
}
#ifndef FS_NO_GLOBALS
using fs::FS;
using fs::File;
using fs::Dir;
using fs::SeekMode;
using fs::SeekSet;
using fs::SeekCur;
using fs::SeekEnd;
using fs::FSInfo;
using fs::FSConfig;
using fs::SPIFFSConfig;
#endif //FS_NO_GLOBALS
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SPIFFS)
extern fs::FS SPIFFS __attribute__((deprecated("SPIFFS has been deprecated. Please consider moving to LittleFS or other filesystems.")));
#endif
#endif //FS_H

131
cores/rp2040/FSImpl.h Normal file
View file

@ -0,0 +1,131 @@
/*
FSImpl.h - base file system interface
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
*/
#ifndef FSIMPL_H
#define FSIMPL_H
#include <stddef.h>
#include <stdint.h>
#include <FS.h>
namespace fs {
class FileImpl {
public:
virtual ~FileImpl() { }
virtual size_t write(const uint8_t *buf, size_t size) = 0;
virtual int read(uint8_t* buf, size_t size) = 0;
virtual void flush() = 0;
virtual bool seek(uint32_t pos, SeekMode mode) = 0;
virtual size_t position() const = 0;
virtual size_t size() const = 0;
virtual int availableForWrite() { return 0; }
virtual bool truncate(uint32_t size) = 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;
// Filesystems *may* support a timestamp per-file, so allow the user to override with
// their own callback for *this specific* file (as opposed to the FSImpl call of the
// same name. The default implementation simply returns time(null)
virtual void setTimeCallback(time_t (*cb)(void)) { _timeCallback = cb; }
// Return the last written time for a file. Undefined when called on a writable file
// as the FS is allowed to return either the time of the last write() operation or the
// time present in the filesystem metadata (often the last time the file was closed)
virtual time_t getLastWrite() { return 0; } // Default is to not support timestamps
// Same for creation time.
virtual time_t getCreationTime() { return 0; } // Default is to not support timestamps
protected:
time_t (*_timeCallback)(void) = nullptr;
};
enum OpenMode {
OM_DEFAULT = 0,
OM_CREATE = 1,
OM_APPEND = 2,
OM_TRUNCATE = 4
};
enum AccessMode {
AM_READ = 1,
AM_WRITE = 2,
AM_RW = AM_READ | AM_WRITE
};
class DirImpl {
public:
virtual ~DirImpl() { }
virtual FileImplPtr openFile(OpenMode openMode, AccessMode accessMode) = 0;
virtual const char* fileName() = 0;
virtual size_t fileSize() = 0;
// Return the last written time for a file. Undefined when called on a writable file
// as the FS is allowed to return either the time of the last write() operation or the
// time present in the filesystem metadata (often the last time the file was closed)
virtual time_t fileTime() { return 0; } // By default, FS doesn't report file times
virtual time_t fileCreationTime() { return 0; } // By default, FS doesn't report file times
virtual bool isFile() const = 0;
virtual bool isDirectory() const = 0;
virtual bool next() = 0;
virtual bool rewind() = 0;
// Filesystems *may* support a timestamp per-file, so allow the user to override with
// their own callback for *this specific* file (as opposed to the FSImpl call of the
// same name. The default implementation simply returns time(null)
virtual void setTimeCallback(time_t (*cb)(void)) { _timeCallback = cb; }
protected:
time_t (*_timeCallback)(void) = nullptr;
};
class FSImpl {
public:
virtual ~FSImpl () { }
virtual bool setConfig(const FSConfig &cfg) = 0;
virtual bool begin() = 0;
virtual void end() = 0;
virtual bool format() = 0;
virtual bool info(FSInfo& info) = 0;
virtual bool info64(FSInfo64& info) = 0;
virtual FileImplPtr open(const char* path, OpenMode openMode, AccessMode accessMode) = 0;
virtual bool exists(const char* path) = 0;
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;
virtual bool gc() { return true; } // May not be implemented in all file systems.
virtual bool check() { return true; } // May not be implemented in all file systems.
virtual time_t getCreationTime() { return 0; } // May not be implemented in all file systems.
// Filesystems *may* support a timestamp per-file, so allow the user to override with
// their own callback for all files on this FS. The default implementation simply
// returns the present time as reported by time(null)
virtual void setTimeCallback(time_t (*cb)(void)) { _timeCallback = cb; }
protected:
time_t (*_timeCallback)(void) = nullptr;
};
} // namespace fs
#endif //FSIMPL_H

View file

@ -28,6 +28,12 @@ extern "C" int main() {
#if F_CPU != 125000000
set_sys_clock_khz(F_CPU / 1000, true);
#endif
#ifndef DISABLE_USB_SERIAL
// Enable serial port for reset/upload always
Serial.begin();
#endif
#if defined DEBUG_RP2040_PORT
DEBUG_RP2040_PORT.begin();
#endif

View file

@ -0,0 +1,131 @@
// Simple speed test for filesystem objects
// Released to the public domain by Earle F. Philhower, III
#include <FS.h>
#include <LittleFS.h>
// Choose the filesystem to test
// WARNING: The filesystem will be formatted at the start of the test!
#define TESTFS LittleFS
//#define TESTFS SDFS
// How large of a file to test
#define TESTSIZEKB 256
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);
delay(5000);
Serial.printf("Beginning test\n");
Serial.flush();
DoTest(&TESTFS);
}
void loop() {
delay(10000);
}

@ -0,0 +1 @@
Subproject commit 1863dc7883d82bd6ca79faa164b65341064d1c16

View file

@ -0,0 +1,10 @@
name=LittleFS
version=0.1.0
author=Earle F. Philhower, III
maintainer=Earle F. Philhower, III <earlephilhower@yahoo.com>
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=rp2040
dot_a_linkage=true

View file

@ -0,0 +1,203 @@
/*
LittleFS.cpp - Wrapper for LittleFS for RP2040
Copyright (c) 2021 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 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 <Arduino.h>
#include <stdlib.h>
#include <algorithm>
#include "LittleFS.h"
#include <hardware/flash.h>
#include <hardware/sync.h>
extern uint8_t _FS_start;
extern uint8_t _FS_end;
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<lfs_file_t>();
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);
}
time_t creation = 0;
if (_timeCallback && (openMode & OM_CREATE)) {
// O_CREATE means we *may* make the file, but not if it already exists.
// See if it exists, and only if not update the creation time
int rc = lfs_file_open(&_lfs, fd.get(), path, LFS_O_RDONLY);
if (rc == 0) {
lfs_file_close(&_lfs, fd.get()); // It exists, don't update create time
} else {
creation = _timeCallback(); // File didn't exist or otherwise, so we're going to create this time
}
}
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<LittleFSFileImpl>(this, path, nullptr, flags, creation);
} else if (rc == 0) {
lfs_file_sync(&_lfs, fd.get());
return std::make_shared<LittleFSFileImpl>(this, path, fd, flags, creation);
} 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<lfs_dir_t>();
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, truncate 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, truncate 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<LittleFSDirImpl>(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<LittleFSImpl*>(c->context);
// Serial.printf(" READ: %p, %d\n", me->_start + (block * me->_blockSize) + off, size);
memcpy(dst, me->_start + (block * me->_blockSize) + off, size);
return 0;
}
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<LittleFSImpl*>(c->context);
uint8_t *addr = me->_start + (block * me->_blockSize) + off;
uint32_t save = save_and_disable_interrupts();
// Serial.printf("WRITE: %p, $d\n", (intptr_t)addr - (intptr_t)XIP_BASE, size);
flash_range_program((intptr_t)addr - (intptr_t)XIP_BASE, (const uint8_t *)buffer, size);
restore_interrupts(save);
return 0;
}
int LittleFSImpl::lfs_flash_erase(const struct lfs_config *c, lfs_block_t block) {
LittleFSImpl *me = reinterpret_cast<LittleFSImpl*>(c->context);
uint8_t *addr = me->_start + (block * me->_blockSize);
// Serial.printf("ERASE: %p, %d\n", (intptr_t)addr - (intptr_t)XIP_BASE, me->_blockSize);
uint32_t save = save_and_disable_interrupts();
flash_range_erase((intptr_t)addr - (intptr_t)XIP_BASE, me->_blockSize);
restore_interrupts(save);
return 0;
}
int LittleFSImpl::lfs_flash_sync(const struct lfs_config *c) {
/* NOOP */
(void) c;
return 0;
}
}; // namespace
FS LittleFS = FS(FSImplPtr(new littlefs_impl::LittleFSImpl(&_FS_start, &_FS_end - &_FS_start, 256, 4096, 16)));

View file

@ -0,0 +1,686 @@
/*
LittleFS.h - Filesystem wrapper for LittleFS on the RP2040
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 <limits>
#include <FS.h>
#include <FSImpl.h>
#define LFS_NAME_MAX 32
#include "../lib/littlefs/lfs.h"
using namespace fs;
namespace littlefs_impl {
class LittleFSFileImpl;
class LittleFSDirImpl;
class LittleFSConfig : public FSConfig
{
public:
static constexpr uint32_t FSId = 0x4c495454;
LittleFSConfig(bool autoFormat = true) : FSConfig(FSId, autoFormat) { }
};
class LittleFSImpl : public FSImpl
{
public:
LittleFSImpl(uint8_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 = 256;
_lfs_cfg.prog_size = 256;
_lfs_cfg.block_size = _blockSize;
_lfs_cfg.block_count =_blockSize? _size / _blockSize: 0;
_lfs_cfg.block_cycles = 16; // TODO - need better explanation
_lfs_cfg.cache_size = 256;
_lfs_cfg.lookahead_size = 256;
_lfs_cfg.read_buffer = nullptr;
_lfs_cfg.prog_buffer = nullptr;
_lfs_cfg.lookahead_buffer = nullptr;
_lfs_cfg.name_max = 0;
_lfs_cfg.file_max = 0;
_lfs_cfg.attr_max = 0;
}
~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 = _getUsedBlocks() * _blockSize;
return true;
}
virtual bool info64(FSInfo64& info64) {
FSInfo i;
if (!info(i)) {
return false;
}
info64.blockSize = i.blockSize;
info64.pageSize = i.pageSize;
info64.maxOpenFiles = i.maxOpenFiles;
info64.maxPathLength = i.maxPathLength;
info64.totalBytes = i.totalBytes;
info64.usedBytes = i.usedBytes;
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 setConfig(const FSConfig &cfg) override {
if ((cfg._type != LittleFSConfig::FSId) || _mounted) {
return false;
}
_cfg = *static_cast<const LittleFSConfig *>(&cfg);
return true;
}
bool begin() override {
if (_size <= 0) {
DEBUGV("LittleFS size is <= zero");
return false;
}
if (_tryMount()) {
return true;
}
if (!_cfg._autoFormat || !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(_timeCallback && _tryMount()) {
// Mounting is required to set attributes
time_t t = _timeCallback();
rc = lfs_setattr(&_lfs, "/", 'c', &t, 8);
if (rc != 0) {
DEBUGV("lfs_format, lfs_setattr 'c': rc=%d\n", rc);
return false;
}
rc = lfs_setattr(&_lfs, "/", 't', &t, 8);
if (rc != 0) {
DEBUGV("lfs_format, lfs_setattr 't': rc=%d\n", rc);
return false;
}
lfs_unmount(&_lfs);
_mounted = false;
}
if (wasMounted) {
return _tryMount();
}
return true;
}
time_t getCreationTime() override {
time_t t;
uint32_t t32b;
if (lfs_getattr(&_lfs, "/", 'c', &t, 8) == 8) {
return t;
} else if (lfs_getattr(&_lfs, "/", 'c', &t32b, 4) == 4) {
return (time_t)t32b;
} else {
return 0;
}
}
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;
}
int _getUsedBlocks() {
if (!_mounted) {
return 0;
}
return lfs_fs_size(&_lfs);
}
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;
LittleFSConfig _cfg;
uint8_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<lfs_file_t> fd, int flags, time_t creation) : _fs(fs), _fd(fd), _opened(true), _flags(flags), _creation(creation) {
_name = std::shared_ptr<char>(new char[strlen(name) + 1], std::default_delete<char[]>());
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;
}
int 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<int32_t>(pos);
if (mode == SeekEnd) {
offset = -offset; // TODO - this seems like its plain wrong vs. POSIX
}
auto lastPos = position();
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;
}
if (position() > size()) {
seek(lastPos, SeekSet); // Pretend the seek() never happened
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;
}
bool truncate(uint32_t size) override {
if (!_opened || !_fd) {
return false;
}
int rc = lfs_file_truncate(_fs->getFS(), _getFD(), size);
if (rc < 0) {
DEBUGV("lfs_file_truncate rc=%d\n", rc);
return false;
}
return true;
}
void close() override {
if (_opened && _fd) {
lfs_file_close(_fs->getFS(), _getFD());
_opened = false;
DEBUGV("lfs_file_close: fd=%p\n", _getFD());
if (_timeCallback && (_flags & LFS_O_WRONLY)) {
// If the file opened with O_CREAT, write the creation time attribute
if (_creation) {
int rc = lfs_setattr(_fs->getFS(), _name.get(), 'c', (const void *)&_creation, sizeof(_creation));
if (rc < 0) {
DEBUGV("Unable to set creation time on '%s' to %d\n", _name.get(), _creation);
}
}
// Add metadata with last write time
time_t now = _timeCallback();
int rc = lfs_setattr(_fs->getFS(), _name.get(), 't', (const void *)&now, sizeof(now));
if (rc < 0) {
DEBUGV("Unable to set last write time on '%s' to %d\n", _name.get(), now);
}
}
}
}
time_t getLastWrite() override {
time_t ftime = 0;
if (_opened && _fd) {
int rc = lfs_getattr(_fs->getFS(), _name.get(), 't', (void *)&ftime, sizeof(ftime));
if (rc != sizeof(ftime))
ftime = 0; // Error, so clear read value
}
return ftime;
}
time_t getCreationTime() override {
time_t ftime = 0;
if (_opened && _fd) {
int rc = lfs_getattr(_fs->getFS(), _name.get(), 'c', (void *)&ftime, sizeof(ftime));
if (rc != sizeof(ftime))
ftime = 0; // Error, so clear read value
}
return ftime;
}
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<lfs_file_t> _fd;
std::shared_ptr<char> _name;
bool _opened;
int _flags;
time_t _creation;
};
class LittleFSDirImpl : public DirImpl
{
public:
LittleFSDirImpl(const String& pattern, LittleFSImpl* fs, std::shared_ptr<lfs_dir_t> 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<char>(new char[strlen(dirPath) + 1], std::default_delete<char[]>());
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[nameLen];
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);
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;
}
time_t fileTime() override {
time_t t;
int32_t t32b;
// If the attribute is 8-bytes, we're all set
if (_getAttr('t', 8, &t)) {
return t;
} else if (_getAttr('t', 4, &t32b)) {
// If it's 4 bytes silently promote to 64b
return (time_t)t32b;
} else {
// OTW, none present
return 0;
}
}
time_t fileCreationTime() override {
time_t t;
int32_t t32b;
// If the attribute is 8-bytes, we're all set
if (_getAttr('c', 8, &t)) {
return t;
} else if (_getAttr('c', 4, &t32b)) {
// If it's 4 bytes silently promote to 64b
return (time_t)t32b;
} else {
// OTW, none present
return 0;
}
}
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());
// Skip the . and .. entries
lfs_info dirent;
lfs_dir_read(_fs->getFS(), _getDir(), &dirent);
lfs_dir_read(_fs->getFS(), _getDir(), &dirent);
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();
}
bool _getAttr(char attr, int len, void *dest) {
if (!_valid || !len || !dest) {
return false;
}
int nameLen = 3; // Slashes, terminator
nameLen += _dirPath.get() ? strlen(_dirPath.get()) : 0;
nameLen += strlen(_dirent.name);
char tmpName[nameLen];
snprintf(tmpName, nameLen, "%s%s%s", _dirPath.get() ? _dirPath.get() : "", _dirPath.get()&&_dirPath.get()[0]?"/":"", _dirent.name);
int rc = lfs_getattr(_fs->getFS(), tmpName, attr, dest, len);
return (rc == len);
}
String _pattern;
LittleFSImpl *_fs;
std::shared_ptr<lfs_dir_t> _dir;
std::shared_ptr<char> _dirPath;
lfs_info _dirent;
bool _valid;
bool _opened;
};
};
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_LITTLEFS)
extern FS LittleFS;
using littlefs_impl::LittleFSConfig;
#endif // ARDUINO
#endif // !defined(__LITTLEFS_H)

View file

@ -0,0 +1,10 @@
// 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
#define LFS_NO_DEBUG
#define LFS_NO_WARN
#define LFS_NO_ERROR
#include "../lib/littlefs/lfs.c"

View file

@ -0,0 +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"