Skip to content

Commit 16d9609

Browse files
Add VFS to enable POSIX file I/O operations (#2333)
* Add VFS to enable POSIX file I/O operations Enables use of FILE * operations on internal and external storage. fopen, fclose, fseek, fprintf, fscanf, etc. supported. * Add FS/File::stat and support POSIX stat/fstat
1 parent 2a73651 commit 16d9609

File tree

13 files changed

+518
-1
lines changed

13 files changed

+518
-1
lines changed

cores/rp2040/FS.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,21 @@ void File::setTimeCallback(time_t (*cb)(void)) {
225225
_timeCallback = cb;
226226
}
227227

228+
bool File::stat(FSStat *st) {
229+
if (!_p) {
230+
return false;
231+
}
232+
size_t pos = position();
233+
seek(0, SeekEnd);
234+
st->size = position() - pos;
235+
seek(pos, SeekSet);
236+
st->blocksize = 0; // Not set here
237+
st->ctime = getCreationTime();
238+
st->atime = getLastWrite();
239+
st->isDir = isDirectory();
240+
return true;
241+
}
242+
228243
File Dir::openFile(const char* mode) {
229244
if (!_impl) {
230245
return File();
@@ -455,6 +470,17 @@ bool FS::rename(const String& pathFrom, const String& pathTo) {
455470
return rename(pathFrom.c_str(), pathTo.c_str());
456471
}
457472

473+
bool FS::stat(const char *path, FSStat *st) {
474+
if (!_impl) {
475+
return false;
476+
}
477+
return _impl->stat(path, st);
478+
}
479+
480+
bool FS::stat(const String& path, FSStat *st) {
481+
return stat(path.c_str(), st);
482+
}
483+
458484
time_t FS::getCreationTime() {
459485
if (!_impl) {
460486
return 0;

cores/rp2040/FS.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ enum SeekMode {
4848
SeekEnd = 2
4949
};
5050

51+
struct FSStat {
52+
size_t size;
53+
size_t blocksize;
54+
time_t ctime;
55+
time_t atime;
56+
bool isDir;
57+
};
58+
5159
class File : public Stream {
5260
public:
5361
File(FileImplPtr p = FileImplPtr(), FS *baseFS = nullptr) : _p(p), _fakeDir(nullptr), _baseFS(baseFS) {
@@ -119,6 +127,8 @@ class File : public Stream {
119127
time_t getCreationTime();
120128
void setTimeCallback(time_t (*cb)(void));
121129

130+
bool stat(FSStat *st);
131+
122132
protected:
123133
FileImplPtr _p;
124134
time_t (*_timeCallback)(void) = nullptr;
@@ -212,6 +222,9 @@ class FS {
212222
bool rmdir(const char* path);
213223
bool rmdir(const String& path);
214224

225+
bool stat(const char *path, FSStat *st);
226+
bool stat(const String& path, FSStat *st);
227+
215228
// Low-level FS routines, not needed by most applications
216229
bool gc();
217230
bool check();

cores/rp2040/FSImpl.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ class FSImpl {
126126
virtual bool remove(const char* path) = 0;
127127
virtual bool mkdir(const char* path) = 0;
128128
virtual bool rmdir(const char* path) = 0;
129+
virtual bool stat(const char *path, FSStat *st) = 0;
129130
virtual bool gc() {
130131
return true; // May not be implemented in all file systems.
131132
}

docs/fs.rst

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,48 @@ second SPI port, ``SPI1``. Just use the following call in place of
151151
SD.begin(cspin, SPI1);
152152
153153
154+
Using VFS (Virtual File System) for POSIX support
155+
-------------------------------------------------
156+
The ``VFS`` library enables sketches to use standard POSIX file I/O operations using
157+
standard ``FILE *`` operations. Include the ``VFS`` library in your application and
158+
add a call to map the ``VFS.root()`` to your filesystem. I.e.:
159+
160+
.. code:: cpp
161+
162+
#include <VFS.h>
163+
#include <LittleFS.h>
164+
165+
void setup() {
166+
LittleFS.begin();
167+
VFS.root(LittleFS);
168+
FILE *fp = fopen("/thisfilelivesonflash.txt", "w");
169+
fprintf(fp, "Hello!\n");
170+
fclose(fp);
171+
}
172+
173+
Multiple filesystems can be ``VFS.map()`` into the VFS namespace under different directory
174+
names. For example, the following will make files on ``/sd`` reside on an external\
175+
SD card and files on ``/lfs`` live in internal flash.
176+
177+
.. code:: cpp
178+
179+
#include <VFS.h>
180+
#include <LittleFS.h>
181+
#include <SDFS.h>
182+
183+
void setup() {
184+
LittleFS.begin();
185+
SDFS.begin();
186+
VFS.map("/lfs", LittleFS);
187+
VFS.map("/sd", SDFS);
188+
FILE *onSD = fopen("/sd/thislivesonsd.txt", "wb");
189+
....
190+
}
191+
192+
See the examples in the ``VFS`` library for more information.
193+
194+
195+
154196
File system object (LittleFS/SD/SDFS/FatFS)
155197
-------------------------------------------
156198

libraries/FatFS/src/FatFS.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,26 @@ class FatFSImpl : public FSImpl {
117117
return _mounted ? (FR_OK == f_unlink(path)) : false;
118118
}
119119

120+
bool stat(const char *path, FSStat *st) override {
121+
if (!_mounted || !path || !path[0]) {
122+
return false;
123+
}
124+
bzero(st, sizeof(*st));
125+
FILINFO fno;
126+
if (FR_OK != f_stat(path, &fno)) {
127+
return false;
128+
}
129+
st->size = fno.fsize;
130+
st->blocksize = 0;
131+
st->isDir = (fno.fattrib & AM_DIR) == AM_DIR;
132+
if (st->isDir) {
133+
st->size = 0;
134+
}
135+
st->ctime = FatToTimeT(fno.fdate, fno.ftime);
136+
st->atime = FatToTimeT(fno.fdate, fno.ftime);
137+
return true;
138+
}
139+
120140
bool setConfig(const FSConfig &cfg) override {
121141
if ((cfg._type != FatFSConfig::FSId) || _mounted) {
122142
DEBUGV("FatFS::setConfig: invalid config or already mounted\n");

libraries/LittleFS/src/LittleFS.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,27 @@ class LittleFSImpl : public FSImpl {
237237
return true;
238238
}
239239

240+
bool stat(const char *path, FSStat *st) override {
241+
if (!_mounted || !path || !path[0]) {
242+
return false;
243+
}
244+
lfs_info info;
245+
if (lfs_stat(&_lfs, path, &info) < 0) {
246+
return false;
247+
}
248+
st->size = info.size;
249+
st->blocksize = _blockSize;
250+
st->isDir = info.type == LFS_TYPE_DIR;
251+
if (st->isDir) {
252+
st->size = 0;
253+
}
254+
if (lfs_getattr(&_lfs, path, 'c', (void *)&st->ctime, sizeof(st->ctime)) != sizeof(st->ctime)) {
255+
st->ctime = 0;
256+
}
257+
st->atime = st->ctime;
258+
return true;
259+
}
260+
240261
time_t getCreationTime() override {
241262
time_t t;
242263
uint32_t t32b;

libraries/SDFS/src/SDFS.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,34 @@ class SDFSImpl : public FSImpl {
148148

149149
bool format() override;
150150

151+
bool stat(const char *path, FSStat *st) override {
152+
if (!_mounted || !path || !path[0]) {
153+
return false;
154+
}
155+
bzero(st, sizeof(*st));
156+
File32 f;
157+
f = _fs.open(path, O_RDONLY);
158+
if (!f) {
159+
return false;
160+
}
161+
st->size = f.fileSize();
162+
st->blocksize = clusterSize();
163+
st->isDir = f.isDir();
164+
if (st->isDir) {
165+
st->size = 0;
166+
}
167+
uint16_t date;
168+
uint16_t time;
169+
if (f.getCreateDateTime(&date, &time)) {
170+
st->ctime = FatToTimeT(date, time);
171+
}
172+
if (f.getAccessDate(&date)) {
173+
st->atime = FatToTimeT(date, 0);
174+
}
175+
f.close();
176+
return true;
177+
}
178+
151179
// The following are not common FS interfaces, but are needed only to
152180
// support the older SD.h exports
153181
uint8_t type() {
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// Released to the piublic domain by Earle F. Philhower, III in 2024
2+
3+
#include <LittleFS.h>
4+
#include <VFS.h>
5+
#include <SPI.h>
6+
#include <SDFS.h>
7+
8+
// This are GP pins for SPI0 on the Raspberry Pi Pico board, and connect
9+
// to different *board* level pinouts. Check the PCB while wiring.
10+
// Only certain pins can be used by the SPI hardware, so if you change
11+
// these be sure they are legal or the program will crash.
12+
// See: https://datasheets.raspberrypi.com/picow/PicoW-A4-Pinout.pdf
13+
const int _MISO = 4; // AKA SPI RX
14+
const int _MOSI = 7; // AKA SPI TX
15+
const int _CS = 5;
16+
const int _SCK = 6;
17+
// SPI1
18+
//const int _MISO = 8; // AKA SPI RX
19+
//const int _MOSI = 11; // AKA SPI TX
20+
//const int _CS = 9;
21+
//const int _SCK = 10;
22+
23+
void setup() {
24+
delay(5000);
25+
if (!LittleFS.begin()) {
26+
Serial.printf("ERROR: Unable to start LittleFS. Did you select a filesystem size in the menus?\n");
27+
return;
28+
}
29+
30+
SDFSConfig cfg;
31+
bool sd = false;
32+
if (_MISO == 0 || _MISO == 4 || _MISO == 16) {
33+
SPI.setRX(_MISO);
34+
SPI.setTX(_MOSI);
35+
SPI.setSCK(_SCK);
36+
SDFS.setConfig(SDFSConfig(_CS, SPI_HALF_SPEED, SPI));
37+
sd = SDFS.begin();
38+
} else if (_MISO == 8 || _MISO == 12) {
39+
SPI1.setRX(_MISO);
40+
SPI1.setTX(_MOSI);
41+
SPI1.setSCK(_SCK);
42+
SDFS.setConfig(SDFSConfig(_CS, SPI_HALF_SPEED, SPI1));
43+
sd = SDFS.begin();
44+
} else {
45+
Serial.println(F("ERROR: Unknown SPI Configuration"));
46+
}
47+
48+
VFS.map("/lfs", LittleFS); // Onboard flash at /lfs
49+
if (sd) {
50+
VFS.map("/sd", SDFS); // SD card mapped to /sd
51+
}
52+
VFS.root(LittleFS); // Anything w/o a prefix maps to LittleFS
53+
54+
Serial.printf("Writing to /lfs/text.txt\n");
55+
FILE *f = fopen("/lfs/text.txt", "wb");
56+
fwrite("hello littlefs", 14, 1, f);
57+
fclose(f);
58+
59+
if (sd) {
60+
Serial.printf("Writing to /sd/test.txt, should not overwrite /lfs/text.txt!\n");
61+
f = fopen("/sd/text.txt", "wb");
62+
fwrite("hello sdfs", 10, 1, f);
63+
fclose(f);
64+
}
65+
66+
f = fopen("/lfs/text.txt", "rb");
67+
char buff[33];
68+
bzero(buff, 33);
69+
fread(buff, 1, 32, f);
70+
fclose(f);
71+
Serial.printf("READ LFS> '%s'\n", buff);
72+
73+
if (sd) {
74+
f = fopen("/sd/text.txt", "rb");
75+
bzero(buff, 33);
76+
fread(buff, 1, 32, f);
77+
fclose(f);
78+
Serial.printf("READ SDFS> '%s'\n", buff);
79+
}
80+
81+
f = fopen("/text.txt", "rb");
82+
bzero(buff, 33);
83+
fread(buff, 1, 32, f);
84+
fclose(f);
85+
Serial.printf("READ default FS (LittleFS)> '%s'\n", buff);
86+
87+
Serial.printf("\nTesting seeking within a file\n");
88+
f = fopen("/lfs/text.txt", "rb");
89+
for (int i = 0; i < 10; i ++) {
90+
fseek(f, i, SEEK_SET);
91+
bzero(buff, 33);
92+
fread(buff, 1, 32, f);
93+
Serial.printf("LFS SEEK %d> '%s'\n", i, buff);
94+
}
95+
fclose(f);
96+
97+
Serial.printf("\nTesting fprintf and fgetc from LFS\n");
98+
f = fopen("/lfs/printout.txt", "w");
99+
for (int i = 0; i < 10; i++) {
100+
fprintf(f, "INT: %d\n", i);
101+
}
102+
fclose(f);
103+
104+
Serial.printf("----\n");
105+
f = fopen("/printout.txt", "r");
106+
int x;
107+
while ((x = fgetc(f)) >= 0) {
108+
Serial.printf("%c", x);
109+
}
110+
Serial.printf("----\n");
111+
}
112+
113+
void loop() {
114+
}

libraries/VFS/keywords.txt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#######################################
2+
# Syntax Coloring Map
3+
#######################################
4+
5+
#######################################
6+
# Datatypes (KEYWORD1)
7+
#######################################
8+
9+
VFS KEYWORD1
10+
FILE KEYWORD1
11+
12+
#######################################
13+
# Methods and Functions (KEYWORD2)
14+
#######################################
15+
16+
map KEYWORD1
17+
root KEYWORD1
18+
19+
#######################################
20+
# Constants (LITERAL1)
21+
#######################################
22+

libraries/VFS/library.properties

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
name=VFS
2+
version=1.0
3+
author=Earle F. Philhower, III <[email protected]>
4+
maintainer=Earle F. Philhower, III <[email protected]>
5+
sentence=Use POSIX fopen/etc. with Arduino filesystems like LittleFS, SD, etc.
6+
paragraph=Use POSIX fopen/etc. with Arduino filesystems like LittleFS, SD, etc.
7+
category=Data Storage
8+
url=https://gifhub.com/earlephilhower/arduino-pico
9+
architectures=rp2040
10+
dot_a_linkage=true

0 commit comments

Comments
 (0)