DeterministicESPAsyncWebServer v6.28.0
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
vfs.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file vfs.cpp
6 * @brief Unified VFS - dispatch + RAM backend (+ Arduino FS backend on ESP32).
7 *
8 * The RAM backend is a fixed pool of named in-BSS files and a fixed handle table;
9 * everything is bounded and host-identical. The Arduino backend maps handles to a
10 * pool of fs::File objects over a real filesystem. The public detws_vfs_* calls
11 * just forward to whichever backend is mounted.
12 */
13
14#include "services/vfs/vfs.h"
15
16#if DETWS_ENABLE_VFS
17
18#include <string.h>
19
20namespace
21{
22// ---- RAM backend ----------------------------------------------------------
23struct RamFile
24{
25 bool used;
26 char name[DETWS_VFS_NAME_MAX];
27 size_t len;
28 uint8_t data[DETWS_VFS_RAM_FILE_SIZE];
29};
30struct RamHandle
31{
32 bool open;
33 int file;
34 size_t pos;
35 DetwsVfsMode mode;
36};
37
38// All VFS dispatch + RAM-backend state, owned by one instance (internal linkage): the
39// mounted backend vtable, the RAM file pool, and the handle table, grouped so it is one
40// named owner, unreachable from any other translation unit. (The RAM ops are fixed-signature
41// vtable entries, so they reach this single owner directly.)
42struct VfsCtx
43{
44 const DetwsVfsBackend *backend = nullptr;
45 RamFile rf[DETWS_VFS_RAM_FILES];
46 RamHandle rh[DETWS_VFS_MAX_OPEN];
47};
48VfsCtx s_vfs;
49
50int ram_find(const char *name)
51{
52 for (int i = 0; i < DETWS_VFS_RAM_FILES; i++)
53 if (s_vfs.rf[i].used && strncmp(s_vfs.rf[i].name, name, DETWS_VFS_NAME_MAX) == 0)
54 return i;
55 return -1;
56}
57
58int ram_create(const char *name)
59{
60 if (strnlen(name, DETWS_VFS_NAME_MAX + 1) >= DETWS_VFS_NAME_MAX)
61 return -1;
62 for (int i = 0; i < DETWS_VFS_RAM_FILES; i++)
63 if (!s_vfs.rf[i].used)
64 {
65 s_vfs.rf[i].used = true;
66 strncpy(s_vfs.rf[i].name, name, DETWS_VFS_NAME_MAX - 1);
67 s_vfs.rf[i].name[DETWS_VFS_NAME_MAX - 1] = '\0';
68 s_vfs.rf[i].len = 0;
69 return i;
70 }
71 return -1;
72}
73
74bool ram_handle_ok(int h)
75{
76 return h >= 0 && h < DETWS_VFS_MAX_OPEN && s_vfs.rh[h].open;
77}
78
79int ram_open(const char *path, int mode)
80{
81 if (!path)
82 return -1;
83 const DetwsVfsMode m = (DetwsVfsMode)mode; // the backend ABI carries mode as int; read it back as the enum
84 int f = ram_find(path);
85 if (m == DetwsVfsMode::DETWS_VFS_READ)
86 {
87 if (f < 0)
88 return -1;
89 }
90 else
91 {
92 if (f < 0)
93 f = ram_create(path);
94 if (f < 0)
95 return -1;
96 if (m == DetwsVfsMode::DETWS_VFS_WRITE)
97 s_vfs.rf[f].len = 0;
98 }
99 for (int h = 0; h < DETWS_VFS_MAX_OPEN; h++)
100 if (!s_vfs.rh[h].open)
101 {
102 s_vfs.rh[h].open = true;
103 s_vfs.rh[h].file = f;
104 s_vfs.rh[h].mode = m;
105 s_vfs.rh[h].pos = (m == DetwsVfsMode::DETWS_VFS_APPEND) ? s_vfs.rf[f].len : 0;
106 return h;
107 }
108 return -1; // handle pool exhausted
109}
110
111int ram_read(int h, void *buf, size_t n)
112{
113 if (!ram_handle_ok(h))
114 return -1;
115 RamFile *f = &s_vfs.rf[s_vfs.rh[h].file];
116 size_t avail = (s_vfs.rh[h].pos < f->len) ? (f->len - s_vfs.rh[h].pos) : 0;
117 size_t k = n < avail ? n : avail;
118 memcpy(buf, f->data + s_vfs.rh[h].pos, k);
119 s_vfs.rh[h].pos += k;
120 return (int)k;
121}
122
123int ram_write(int h, const void *buf, size_t n)
124{
125 if (!ram_handle_ok(h) || s_vfs.rh[h].mode == DetwsVfsMode::DETWS_VFS_READ)
126 return -1;
127 RamFile *f = &s_vfs.rf[s_vfs.rh[h].file];
128 size_t cap = (s_vfs.rh[h].pos < DETWS_VFS_RAM_FILE_SIZE) ? (DETWS_VFS_RAM_FILE_SIZE - s_vfs.rh[h].pos) : 0;
129 size_t k = n < cap ? n : cap;
130 memcpy(f->data + s_vfs.rh[h].pos, buf, k);
131 s_vfs.rh[h].pos += k;
132 if (s_vfs.rh[h].pos > f->len)
133 f->len = s_vfs.rh[h].pos;
134 return (int)k;
135}
136
137void ram_close(int h)
138{
139 if (h >= 0 && h < DETWS_VFS_MAX_OPEN)
140 s_vfs.rh[h].open = false;
141}
142
143long ram_size(const char *path)
144{
145 int f = ram_find(path);
146 return f < 0 ? -1 : (long)s_vfs.rf[f].len;
147}
148
149bool ram_exists(const char *path)
150{
151 return ram_find(path) >= 0;
152}
153
154bool ram_remove(const char *path)
155{
156 int f = ram_find(path);
157 if (f < 0)
158 return false;
159 s_vfs.rf[f].used = false;
160 return true;
161}
162
163bool ram_rename(const char *from, const char *to)
164{
165 if (!from || !to || strnlen(to, DETWS_VFS_NAME_MAX + 1) >= DETWS_VFS_NAME_MAX)
166 return false;
167 int f = ram_find(from);
168 if (f < 0)
169 return false;
170 int dst = ram_find(to);
171 if (dst >= 0)
172 s_vfs.rf[dst].used = false; // overwrite an existing destination
173 strncpy(s_vfs.rf[f].name, to, DETWS_VFS_NAME_MAX - 1);
174 s_vfs.rf[f].name[DETWS_VFS_NAME_MAX - 1] = '\0';
175 return true;
176}
177
178const DetwsVfsBackend s_ram_backend = {ram_open, ram_read, ram_write, ram_close,
179 ram_size, ram_exists, ram_remove, ram_rename};
180} // namespace
181
182void detws_vfs_mount(const DetwsVfsBackend *backend)
183{
184 s_vfs.backend = backend;
185}
186
187const DetwsVfsBackend *detws_vfs_ram(void)
188{
189 return &s_ram_backend;
190}
191
192void detws_vfs_ram_format(void)
193{
194 for (int i = 0; i < DETWS_VFS_RAM_FILES; i++)
195 s_vfs.rf[i].used = false;
196 for (int h = 0; h < DETWS_VFS_MAX_OPEN; h++)
197 s_vfs.rh[h].open = false;
198}
199
200int detws_vfs_open(const char *path, DetwsVfsMode mode)
201{
202 return s_vfs.backend ? s_vfs.backend->open(path, (int)mode) : -1; // cross the int backend ABI
203}
204int detws_vfs_read(int handle, void *buf, size_t n)
205{
206 return s_vfs.backend ? s_vfs.backend->read(handle, buf, n) : -1;
207}
208int detws_vfs_write(int handle, const void *buf, size_t n)
209{
210 return s_vfs.backend ? s_vfs.backend->write(handle, buf, n) : -1;
211}
212void detws_vfs_close(int handle)
213{
214 if (s_vfs.backend)
215 s_vfs.backend->close(handle);
216}
217long detws_vfs_size(const char *path)
218{
219 return s_vfs.backend ? s_vfs.backend->size(path) : -1;
220}
221bool detws_vfs_exists(const char *path)
222{
223 return s_vfs.backend ? s_vfs.backend->exists(path) : false;
224}
225bool detws_vfs_remove(const char *path)
226{
227 return s_vfs.backend ? s_vfs.backend->remove(path) : false;
228}
229bool detws_vfs_rename(const char *from, const char *to)
230{
231 return s_vfs.backend ? s_vfs.backend->rename(from, to) : false;
232}
233
234long detws_vfs_read_file(const char *path, void *buf, size_t cap)
235{
236 long sz = detws_vfs_size(path);
237 if (sz < 0 || (size_t)sz > cap)
238 return -1;
239 int h = detws_vfs_open(path, DetwsVfsMode::DETWS_VFS_READ);
240 if (h < 0)
241 return -1;
242 size_t total = 0;
243 uint8_t *out = (uint8_t *)buf;
244 while (total < (size_t)sz)
245 {
246 int r = detws_vfs_read(h, out + total, (size_t)sz - total);
247 if (r <= 0)
248 break;
249 total += (size_t)r;
250 }
251 detws_vfs_close(h);
252 return (long)total;
253}
254
255bool detws_vfs_write_file(const char *path, const void *buf, size_t n)
256{
257 int h = detws_vfs_open(path, DetwsVfsMode::DETWS_VFS_WRITE);
258 if (h < 0)
259 return false;
260 size_t total = 0;
261 const uint8_t *in = (const uint8_t *)buf;
262 while (total < n)
263 {
264 int w = detws_vfs_write(h, in + total, n - total);
265 if (w <= 0)
266 break;
267 total += (size_t)w;
268 }
269 detws_vfs_close(h);
270 return total == n;
271}
272
273// ---------------------------------------------------------------------------
274// Arduino FS backend (ESP32): wraps a real fs::FS over LittleFS / SD / SPIFFS.
275// ---------------------------------------------------------------------------
276#ifdef ARDUINO
277
278#include <FS.h>
279
280namespace
281{
282// All Arduino FS-backend state, owned by one instance (internal linkage): the mounted
283// filesystem and the open-file handle pool, grouped so it is one named owner. (The FS ops
284// are fixed-signature vtable entries, so they reach this single owner directly.)
285struct FsCtx
286{
287 fs::FS *fs = nullptr;
288 fs::File fsfile[DETWS_VFS_MAX_OPEN];
289 bool fsopen[DETWS_VFS_MAX_OPEN];
290};
291FsCtx s_vfs_fs;
292
293int fs_open(const char *path, int mode)
294{
295 if (!s_vfs_fs.fs || !path)
296 return -1;
297 const DetwsVfsMode md = (DetwsVfsMode)mode; // ABI int -> enum
298 const char *m = (md == DetwsVfsMode::DETWS_VFS_WRITE) ? FILE_WRITE
299 : (md == DetwsVfsMode::DETWS_VFS_APPEND) ? FILE_APPEND
300 : FILE_READ;
301 for (int h = 0; h < DETWS_VFS_MAX_OPEN; h++)
302 if (!s_vfs_fs.fsopen[h])
303 {
304 s_vfs_fs.fsfile[h] = s_vfs_fs.fs->open(path, m);
305 if (!s_vfs_fs.fsfile[h])
306 return -1;
307 s_vfs_fs.fsopen[h] = true;
308 return h;
309 }
310 return -1;
311}
312int fs_read(int h, void *buf, size_t n)
313{
314 if (h < 0 || h >= DETWS_VFS_MAX_OPEN || !s_vfs_fs.fsopen[h])
315 return -1;
316 return (int)s_vfs_fs.fsfile[h].read((uint8_t *)buf, n);
317}
318int fs_write(int h, const void *buf, size_t n)
319{
320 if (h < 0 || h >= DETWS_VFS_MAX_OPEN || !s_vfs_fs.fsopen[h])
321 return -1;
322 return (int)s_vfs_fs.fsfile[h].write((const uint8_t *)buf, n);
323}
324void fs_close(int h)
325{
326 if (h >= 0 && h < DETWS_VFS_MAX_OPEN && s_vfs_fs.fsopen[h])
327 {
328 s_vfs_fs.fsfile[h].close();
329 s_vfs_fs.fsopen[h] = false;
330 }
331}
332long fs_size(const char *path)
333{
334 if (!s_vfs_fs.fs)
335 return -1;
336 fs::File f = s_vfs_fs.fs->open(path, FILE_READ);
337 if (!f)
338 return -1;
339 long sz = (long)f.size();
340 f.close();
341 return sz;
342}
343bool fs_exists(const char *path)
344{
345 return s_vfs_fs.fs && s_vfs_fs.fs->exists(path);
346}
347bool fs_remove(const char *path)
348{
349 return s_vfs_fs.fs && s_vfs_fs.fs->remove(path);
350}
351bool fs_rename(const char *from, const char *to)
352{
353 return s_vfs_fs.fs && s_vfs_fs.fs->rename(from, to);
354}
355
356const DetwsVfsBackend s_fs_backend = {fs_open, fs_read, fs_write, fs_close, fs_size, fs_exists, fs_remove, fs_rename};
357} // namespace
358
359const DetwsVfsBackend *detws_vfs_fs(fs::FS *filesystem)
360{
361 s_vfs_fs.fs = filesystem;
362 for (int h = 0; h < DETWS_VFS_MAX_OPEN; h++)
363 s_vfs_fs.fsopen[h] = false;
364 return &s_fs_backend;
365}
366
367#endif // ARDUINO
368
369#endif // DETWS_ENABLE_VFS
Unified virtual filesystem wrapper (DETWS_ENABLE_VFS).