ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
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 pc_vfs_* calls
11 * just forward to whichever backend is mounted.
12 */
13
15
16#if PC_ENABLE_VFS
17
18#include <string.h>
19
20#if defined(ARDUINO)
21#include <FS.h>
22#endif
23namespace
24{
25// ---- RAM backend ----------------------------------------------------------
26struct RamFile
27{
28 bool used;
29 char name[PC_VFS_NAME_MAX];
30 size_t len;
31 uint8_t data[PC_VFS_RAM_FILE_SIZE];
32};
33struct RamHandle
34{
35 bool open;
36 int file;
37 size_t pos;
38 pc_vfs_mode mode;
39};
40
41// All VFS dispatch + RAM-backend state, owned by one instance (internal linkage): the
42// mounted backend vtable, the RAM file pool, and the handle table, grouped so it is one
43// named owner, unreachable from any other translation unit. (The RAM ops are fixed-signature
44// vtable entries, so they reach this single owner directly.)
45struct VfsCtx
46{
47 const pc_vfs_backend *backend = nullptr;
48 RamFile rf[PC_VFS_RAM_FILES];
49 RamHandle rh[PC_VFS_MAX_OPEN];
50};
51VfsCtx s_vfs;
52
53int ram_find(const char *name)
54{
55 for (int i = 0; i < PC_VFS_RAM_FILES; i++)
56 {
57 if (s_vfs.rf[i].used && strncmp(s_vfs.rf[i].name, name, PC_VFS_NAME_MAX) == 0)
58 {
59 return i;
60 }
61 }
62 return -1;
63}
64
65int ram_create(const char *name)
66{
67 if (strnlen(name, PC_VFS_NAME_MAX + 1) >= PC_VFS_NAME_MAX)
68 {
69 return -1;
70 }
71 for (int i = 0; i < PC_VFS_RAM_FILES; i++)
72 {
73 if (!s_vfs.rf[i].used)
74 {
75 s_vfs.rf[i].used = true;
76 strncpy(s_vfs.rf[i].name, name, PC_VFS_NAME_MAX - 1);
77 s_vfs.rf[i].name[PC_VFS_NAME_MAX - 1] = '\0';
78 s_vfs.rf[i].len = 0;
79 return i;
80 }
81 }
82 return -1;
83}
84
85bool ram_handle_ok(int h)
86{
87 return h >= 0 && h < PC_VFS_MAX_OPEN && s_vfs.rh[h].open;
88}
89
90int ram_open(const char *path, int mode)
91{
92 if (!path)
93 {
94 return -1;
95 }
96 const pc_vfs_mode m = (pc_vfs_mode)mode; // the backend ABI carries mode as int; read it back as the enum
97 int f = ram_find(path);
98 if (m == pc_vfs_mode::PC_VFS_READ)
99 {
100 if (f < 0)
101 {
102 return -1;
103 }
104 }
105 else
106 {
107 if (f < 0)
108 {
109 f = ram_create(path);
110 }
111 if (f < 0)
112 {
113 return -1;
114 }
115 if (m == pc_vfs_mode::PC_VFS_WRITE)
116 {
117 s_vfs.rf[f].len = 0;
118 }
119 }
120 for (int h = 0; h < PC_VFS_MAX_OPEN; h++)
121 {
122 if (!s_vfs.rh[h].open)
123 {
124 s_vfs.rh[h].open = true;
125 s_vfs.rh[h].file = f;
126 s_vfs.rh[h].mode = m;
127 s_vfs.rh[h].pos = (m == pc_vfs_mode::PC_VFS_APPEND) ? s_vfs.rf[f].len : 0;
128 return h;
129 }
130 }
131 return -1; // handle pool exhausted
132}
133
134int ram_read(int h, void *buf, size_t n)
135{
136 if (!ram_handle_ok(h))
137 {
138 return -1;
139 }
140 RamFile *f = &s_vfs.rf[s_vfs.rh[h].file];
141 size_t avail = (s_vfs.rh[h].pos < f->len) ? (f->len - s_vfs.rh[h].pos) : 0;
142 size_t k = n < avail ? n : avail;
143 memcpy(buf, f->data + s_vfs.rh[h].pos, k);
144 s_vfs.rh[h].pos += k;
145 return (int)k;
146}
147
148int ram_write(int h, const void *buf, size_t n)
149{
150 if (!ram_handle_ok(h) || s_vfs.rh[h].mode == pc_vfs_mode::PC_VFS_READ)
151 {
152 return -1;
153 }
154 RamFile *f = &s_vfs.rf[s_vfs.rh[h].file];
155 size_t cap = (s_vfs.rh[h].pos < PC_VFS_RAM_FILE_SIZE) ? (PC_VFS_RAM_FILE_SIZE - s_vfs.rh[h].pos) : 0;
156 size_t k = n < cap ? n : cap;
157 memcpy(f->data + s_vfs.rh[h].pos, buf, k);
158 s_vfs.rh[h].pos += k;
159 if (s_vfs.rh[h].pos > f->len)
160 {
161 f->len = s_vfs.rh[h].pos;
162 }
163 return (int)k;
164}
165
166void ram_close(int h)
167{
168 if (h >= 0 && h < PC_VFS_MAX_OPEN)
169 {
170 s_vfs.rh[h].open = false;
171 }
172}
173
174long ram_size(const char *path)
175{
176 int f = ram_find(path);
177 return f < 0 ? -1 : (long)s_vfs.rf[f].len;
178}
179
180bool ram_exists(const char *path)
181{
182 return ram_find(path) >= 0;
183}
184
185bool ram_remove(const char *path)
186{
187 int f = ram_find(path);
188 if (f < 0)
189 {
190 return false;
191 }
192 s_vfs.rf[f].used = false;
193 return true;
194}
195
196bool ram_rename(const char *from, const char *to)
197{
198 if (!from || !to || strnlen(to, PC_VFS_NAME_MAX + 1) >= PC_VFS_NAME_MAX)
199 {
200 return false;
201 }
202 int f = ram_find(from);
203 if (f < 0)
204 {
205 return false;
206 }
207 int dst = ram_find(to);
208 if (dst >= 0)
209 {
210 s_vfs.rf[dst].used = false; // overwrite an existing destination
211 }
212 strncpy(s_vfs.rf[f].name, to, PC_VFS_NAME_MAX - 1);
213 s_vfs.rf[f].name[PC_VFS_NAME_MAX - 1] = '\0';
214 return true;
215}
216
217const pc_vfs_backend s_ram_backend = {ram_open, ram_read, ram_write, ram_close,
218 ram_size, ram_exists, ram_remove, ram_rename};
219} // namespace
220
221void pc_vfs_mount(const pc_vfs_backend *backend)
222{
223 s_vfs.backend = backend;
224}
225
226const pc_vfs_backend *pc_vfs_ram(void)
227{
228 return &s_ram_backend;
229}
230
231void pc_vfs_ram_format(void)
232{
233 for (int i = 0; i < PC_VFS_RAM_FILES; i++)
234 {
235 s_vfs.rf[i].used = false;
236 }
237 for (int h = 0; h < PC_VFS_MAX_OPEN; h++)
238 {
239 s_vfs.rh[h].open = false;
240 }
241}
242
243int pc_vfs_open(const char *path, pc_vfs_mode mode)
244{
245 return s_vfs.backend ? s_vfs.backend->open(path, (int)mode) : -1; // cross the int backend ABI
246}
247int pc_vfs_read(int handle, void *buf, size_t n)
248{
249 return s_vfs.backend ? s_vfs.backend->read(handle, buf, n) : -1;
250}
251int pc_vfs_write(int handle, const void *buf, size_t n)
252{
253 return s_vfs.backend ? s_vfs.backend->write(handle, buf, n) : -1;
254}
255void pc_vfs_close(int handle)
256{
257 if (s_vfs.backend)
258 {
259 s_vfs.backend->close(handle);
260 }
261}
262long pc_vfs_size(const char *path)
263{
264 return s_vfs.backend ? s_vfs.backend->size(path) : -1;
265}
266bool pc_vfs_exists(const char *path)
267{
268 return s_vfs.backend ? s_vfs.backend->exists(path) : false;
269}
270bool pc_vfs_remove(const char *path)
271{
272 return s_vfs.backend ? s_vfs.backend->remove(path) : false;
273}
274bool pc_vfs_rename(const char *from, const char *to)
275{
276 return s_vfs.backend ? s_vfs.backend->rename(from, to) : false;
277}
278
279long pc_vfs_read_file(const char *path, void *buf, size_t cap)
280{
281 long sz = pc_vfs_size(path);
282 if (sz < 0 || (size_t)sz > cap)
283 {
284 return -1;
285 }
286 int h = pc_vfs_open(path, pc_vfs_mode::PC_VFS_READ);
287 if (h < 0)
288 {
289 return -1;
290 }
291 size_t total = 0;
292 uint8_t *out = (uint8_t *)buf;
293 while (total < (size_t)sz)
294 {
295 int r = pc_vfs_read(h, out + total, (size_t)sz - total);
296 if (r <= 0)
297 {
298 break;
299 }
300 total += (size_t)r;
301 }
302 pc_vfs_close(h);
303 return (long)total;
304}
305
306bool pc_vfs_write_file(const char *path, const void *buf, size_t n)
307{
308 int h = pc_vfs_open(path, pc_vfs_mode::PC_VFS_WRITE);
309 if (h < 0)
310 {
311 return false;
312 }
313 size_t total = 0;
314 const uint8_t *in = (const uint8_t *)buf;
315 while (total < n)
316 {
317 int w = pc_vfs_write(h, in + total, n - total);
318 if (w <= 0)
319 {
320 break;
321 }
322 total += (size_t)w;
323 }
324 pc_vfs_close(h);
325 return total == n;
326}
327
328// ---------------------------------------------------------------------------
329// Arduino FS backend (ESP32): wraps a real fs::FS over LittleFS / SD / SPIFFS.
330// ---------------------------------------------------------------------------
331#ifdef ARDUINO
332
333namespace
334{
335// All Arduino FS-backend state, owned by one instance (internal linkage): the mounted
336// filesystem and the open-file handle pool, grouped so it is one named owner. (The FS ops
337// are fixed-signature vtable entries, so they reach this single owner directly.)
338struct FsCtx
339{
340 fs::FS *fs = nullptr;
341 fs::File fsfile[PC_VFS_MAX_OPEN];
342 bool fsopen[PC_VFS_MAX_OPEN];
343};
344FsCtx s_vfs_fs;
345
346int fs_open(const char *path, int mode)
347{
348 if (!s_vfs_fs.fs || !path)
349 {
350 return -1;
351 }
352 const pc_vfs_mode md = (pc_vfs_mode)mode; // ABI int -> enum
353 const char *m = (md == pc_vfs_mode::PC_VFS_WRITE) ? FILE_WRITE
354 : (md == pc_vfs_mode::PC_VFS_APPEND) ? FILE_APPEND
355 : FILE_READ;
356 for (int h = 0; h < PC_VFS_MAX_OPEN; h++)
357 {
358 if (!s_vfs_fs.fsopen[h])
359 {
360 s_vfs_fs.fsfile[h] = s_vfs_fs.fs->open(path, m);
361 if (!s_vfs_fs.fsfile[h])
362 {
363 return -1;
364 }
365 s_vfs_fs.fsopen[h] = true;
366 return h;
367 }
368 }
369 return -1;
370}
371int fs_read(int h, void *buf, size_t n)
372{
373 if (h < 0 || h >= PC_VFS_MAX_OPEN || !s_vfs_fs.fsopen[h])
374 {
375 return -1;
376 }
377 return (int)s_vfs_fs.fsfile[h].read((uint8_t *)buf, n);
378}
379int fs_write(int h, const void *buf, size_t n)
380{
381 if (h < 0 || h >= PC_VFS_MAX_OPEN || !s_vfs_fs.fsopen[h])
382 {
383 return -1;
384 }
385 return (int)s_vfs_fs.fsfile[h].write((const uint8_t *)buf, n);
386}
387void fs_close(int h)
388{
389 if (h >= 0 && h < PC_VFS_MAX_OPEN && s_vfs_fs.fsopen[h])
390 {
391 s_vfs_fs.fsfile[h].close();
392 s_vfs_fs.fsopen[h] = false;
393 }
394}
395long fs_size(const char *path)
396{
397 if (!s_vfs_fs.fs)
398 {
399 return -1;
400 }
401 fs::File f = s_vfs_fs.fs->open(path, FILE_READ);
402 if (!f)
403 {
404 return -1;
405 }
406 long sz = (long)f.size();
407 f.close();
408 return sz;
409}
410bool fs_exists(const char *path)
411{
412 return s_vfs_fs.fs && s_vfs_fs.fs->exists(path);
413}
414bool fs_remove(const char *path)
415{
416 return s_vfs_fs.fs && s_vfs_fs.fs->remove(path);
417}
418bool fs_rename(const char *from, const char *to)
419{
420 return s_vfs_fs.fs && s_vfs_fs.fs->rename(from, to);
421}
422
423const pc_vfs_backend s_fs_backend = {fs_open, fs_read, fs_write, fs_close, fs_size, fs_exists, fs_remove, fs_rename};
424} // namespace
425
426const pc_vfs_backend *pc_vfs_fs(fs::FS *filesystem)
427{
428 s_vfs_fs.fs = filesystem;
429 for (int h = 0; h < PC_VFS_MAX_OPEN; h++)
430 {
431 s_vfs_fs.fsopen[h] = false;
432 }
433 return &s_fs_backend;
434}
435
436#endif // ARDUINO
437
438#endif // PC_ENABLE_VFS
Unified virtual filesystem wrapper (PC_ENABLE_VFS).