ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
mnt.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file mnt.h
6 * @brief The mount: which store is behind the filesystem, and the vtable it answers through.
7 *
8 * This file answers one question - *what is mounted* - and nothing else. The operations a caller
9 * performs live on the filesystem accessor (server/filesystem.h), which resolves a request path
10 * against its root and then dispatches here. Splitting it that way means a backend author
11 * implements storage and never touches path policy, and the `..` guard cannot be bypassed by
12 * reaching a backend directly.
13 *
14 * Two backends ship, each its own module, because a backend is a footprint and this is a seam:
15 *
16 * - **RAM** (server/storage/mnt_ram, PROTOCORE_ENABLE_MNT): a fixed pool of PROTOCORE_MNT_RAM_FILES
17 * files of up to PROTOCORE_MNT_RAM_FILE_SIZE bytes each, all in BSS - deterministic, bounded, and
18 * identical on host and target. It is what lets the SFTP/SCP/WebDAV servers run under a native test.
19 *
20 * - **Board filesystem** (board layer): wraps the framework's own file object for persistent
21 * storage. It lives in test/core_setup/ because it speaks a vendor framework, which the core does
22 * not.
23 *
24 * Handles are small ints the backend assigns, and a directory cursor is one of them - so ::close
25 * releases either kind and there is no second lifetime to get wrong. Every entry point fails
26 * closed (-1 / false) when nothing is mounted.
27 *
28 * Single-accessor like the other services: use it from one context (a worker / loop), not
29 * concurrently.
30 *
31 * @author Douglas Quigg (dstroy0)
32 * @date 2026
33 */
34
35#ifndef PROTOCORE_MNT_H
36#define PROTOCORE_MNT_H
37
38#include "protocore_config.h"
39
41
42/**
43 * @brief Open modes.
44 *
45 * PROTOCORE_MNT_RDWR is the only one that admits a seek before a write. Under PROTOCORE_MNT_APPEND a write lands
46 * at end-of-file whatever the position says, which is what O_APPEND means on a real filesystem, so a
47 * caller that overwrites in place has to ask for RDWR and a backend that cannot offer it answers -1.
48 */
50{
51 PROTOCORE_MNT_READ = 0, ///< Read existing file (fails if absent).
52 PROTOCORE_MNT_WRITE = 1, ///< Create/truncate for writing.
53 PROTOCORE_MNT_APPEND = 2, ///< Create/open for appending at end.
54 PROTOCORE_MNT_RDWR = 3, ///< Open existing for random read+write, no truncation (fails if absent).
56
57/**
58 * @brief What a stat and a directory entry both answer: the facts about one file, and no name.
59 *
60 * The name is deliberately absent. A name buffer welded into this struct would set one length for
61 * every consumer, and the consumers do not agree: the SFTP codec sizes its entry scratch from what
62 * one NAME response must hold, WebDAV from an href, the RAM backend from its own file table. Each
63 * translation unit derives the length it needs and passes a buffer, so this type stays the four
64 * facts and carries no policy.
65 *
66 * Stat and readdir share it because they answer the same question about the same directory record
67 * - asking a backend for a size, then whether it is a directory, then its mtime is three lookups
68 * of one record, which on FAT is three seeks to learn what the first read already had.
69 */
70typedef struct
71{
73 uint64_t size; ///< 0 for a directory
74 uint32_t mtime; ///< unix epoch seconds, 0 if the backend keeps no timestamp
76
77/**
78 * @brief A storage backend. Each open call returns a small handle (>= 0) or -1.
79 *
80 * Implement this to add a store; the built-in RAM disk publishes one through
81 * @ref MntRamNs::backend (server/storage/mnt_ram).
82 *
83 * Every call here is one node. mnt is blind - it does not know what a path means, so it cannot know
84 * what a subtree is, and nothing here takes one. A whole-tree operation is composed from these by
85 * the accessor (server/storage/filesystem.h), which is the seam that does know.
86 */
88{
89 int (*open)(const char *path, int mode); ///< -> handle (>=0) or -1.
90 int (*read)(int handle, void *buf, size_t n); ///< bytes read, or -1.
91 int (*write)(int handle, const void *buf, size_t n); ///< bytes written, or -1.
92 void (*close)(int handle); ///< release a file OR directory handle.
93 proto_bool (*seek)(int handle, uint64_t off); ///< absolute seek; false if unsupported.
94 long (*size)(const char *path); ///< file size, or -1 if absent.
95 proto_bool (*exists)(const char *path); ///< true if the path exists.
96 proto_bool (*remove)(const char *path); ///< delete a file; true on success.
97 proto_bool (*rename)(const char *from, const char *to); ///< rename; true on success.
98 proto_bool (*mkdir)(const char *path); ///< create a directory; true on success.
99 proto_bool (*rmdir)(const char *path); ///< remove an empty directory; true on success.
100 proto_bool (*stat)(const char *path, protocore_mnt_stat *out); ///< fill @p out for @p path; false if absent.
101 int (*opendir)(const char *path); ///< -> directory handle (>=0) or -1.
102 /** @brief Next entry: facts into @p out, name into @p name (NUL-terminated). False at end. */
103 proto_bool (*readdir)(int handle, protocore_mnt_stat *out, char *name, size_t name_cap);
104 /**
105 * @brief Push everything written to @p handle past the backend's own buffering, and report
106 * whether it got there. May be NULL when the backend cannot promise it.
107 *
108 * This is the durability barrier, and it is the one call here whose absence is not the same as
109 * failure: a log that orders its writes around a barrier is only power-loss-safe if the barrier
110 * is real, so a backend that cannot make the promise says so with NULL and the caller refuses to
111 * mount rather than running as though it had one. Returning true without doing anything would
112 * make an unsafe store indistinguishable from a safe one.
113 */
114 proto_bool (*sync)(int handle);
116
117// Everything above and the two calls below are the HAL: the shape of a store, and which one is
118// mounted. All declarations plus one pointer, so a feature that reads through the seam pays nothing
119// and never has to enable a service just to name a type. The seam fails closed when nothing is
120// mounted, which is the honest answer rather than an error.
121/** @brief Mount the active backend (call once at setup; NULL unmounts). */
122/** @brief The id a route carries when it serves no mount point. */
123#define PROTOCORE_MNT_NONE 0xFFu
124
125/** @brief The mount point a call names, and what registering one takes. */
126typedef struct
127{
128 const protocore_mnt_backend *backend; ///< the filesystem behind the point
129 const char *root; ///< the path prefix it answers for
130 uint8_t id; ///< the point a lookup names
131} MntArgs;
132
133/**
134 * @brief The mount table and the active filesystem.
135 *
136 * A caller sets the members a call takes, invokes it through ::Mnt, and reads the outcome off the
137 * same handle. The table itself is behind @ref internal.
138 *
139 * @var MntNs::args the mount point a call names, and what registering one takes
140 * @var MntNs::u8 the point an add took
141 * @var MntNs::backend the filesystem a lookup reports, or NULL
142 * @var MntNs::text the root a lookup reports, or NULL
143 * @var MntNs::point_add register a mount point
144 * @var MntNs::point_of the filesystem behind a point
145 * @var MntNs::root_of the path prefix a point answers for
146 * @var MntNs::reset clear every registered point
147 * @var MntNs::mount make one filesystem the active one
148 * @var MntNs::active the active filesystem
149 */
150typedef struct
151{
153 uint8_t u8;
155 const char *text;
156} MntVars;
157
158/** @brief The operands and the outcome. */
159extern MntVars MntV;
160
161/** @brief The entries. */
162typedef struct
163{
164 void (*const point_add)(uint8_t *work);
165 void (*const point_of)(uint8_t *work);
166 void (*const root_of)(uint8_t *work);
167 void (*const reset)(uint8_t *work);
168 void (*const mount)(uint8_t *work);
169 void (*const active)(uint8_t *work);
170} MntNs;
171
172// What the table binds, defined once in the .c and taking one parameter each: everything
173// else an entry needs is an operand in MntV or a region of the borrow at a fixed offset.
174void protocore_mnt_point_add(uint8_t *work);
175void protocore_mnt_point_of(uint8_t *work);
176void protocore_mnt_root_of(uint8_t *work);
177void protocore_mnt_reset(uint8_t *work);
178void protocore_mnt_mount(uint8_t *work);
179void protocore_mnt_active(uint8_t *work);
180
181// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
182// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
183// `Mnt.point_add(work)` resolves to a named function and becomes a DIRECT call. An extern table
184// leaves the call indirect and the symbol live at every level, -O2 -flto included.
185static const MntNs Mnt __attribute__((unused)) = {
187 .point_of = protocore_mnt_point_of,
188 .root_of = protocore_mnt_root_of,
189 .reset = protocore_mnt_reset,
190 .mount = protocore_mnt_mount,
191 .active = protocore_mnt_active,
192};
193
195
196#endif // PROTOCORE_MNT_H
PROTO_ENUM_PACKED
Application protocol spoken on a listener port or connection slot.
void protocore_mnt_point_of(uint8_t *work)
void protocore_mnt_point_add(uint8_t *work)
void protocore_mnt_active(uint8_t *work)
MntVars MntV
The operands and the outcome.
void protocore_mnt_root_of(uint8_t *work)
void protocore_mnt_mount(uint8_t *work)
@ PROTOCORE_MNT_READ
Read existing file (fails if absent).
Definition mnt.h:51
@ PROTOCORE_MNT_WRITE
Create/truncate for writing.
Definition mnt.h:52
@ PROTOCORE_MNT_RDWR
Open existing for random read+write, no truncation (fails if absent).
Definition mnt.h:54
@ PROTOCORE_MNT_APPEND
Create/open for appending at end.
Definition mnt.h:53
PROTOCORE_BEGIN_DECLS enum PROTO_ENUM_PACKED protocore_mnt_mode
Open modes.
void protocore_mnt_reset(uint8_t *work)
The mount point a call names, and what registering one takes.
Definition mnt.h:127
const protocore_mnt_backend * backend
the filesystem behind the point
Definition mnt.h:128
uint8_t id
the point a lookup names
Definition mnt.h:130
const char * root
the path prefix it answers for
Definition mnt.h:129
The entries.
Definition mnt.h:163
void(*const point_add)(uint8_t *work)
Definition mnt.h:164
Definition mnt.h:151
const protocore_mnt_backend * backend
Definition mnt.h:154
MntArgs args
Definition mnt.h:152
uint8_t u8
Definition mnt.h:153
const char * text
Definition mnt.h:155
A storage backend. Each open call returns a small handle (>= 0) or -1.
Definition mnt.h:88
proto_bool(* rmdir)(const char *path)
remove an empty directory; true on success.
Definition mnt.h:99
proto_bool(* readdir)(int handle, protocore_mnt_stat *out, char *name, size_t name_cap)
Next entry: facts into out, name into name (NUL-terminated). False at end.
Definition mnt.h:103
long(* size)(const char *path)
file size, or -1 if absent.
Definition mnt.h:94
proto_bool(* exists)(const char *path)
true if the path exists.
Definition mnt.h:95
int(* open)(const char *path, int mode)
-> handle (>=0) or -1.
Definition mnt.h:89
proto_bool(* remove)(const char *path)
delete a file; true on success.
Definition mnt.h:96
proto_bool(* mkdir)(const char *path)
create a directory; true on success.
Definition mnt.h:98
proto_bool(* rename)(const char *from, const char *to)
rename; true on success.
Definition mnt.h:97
int(* write)(int handle, const void *buf, size_t n)
bytes written, or -1.
Definition mnt.h:91
void(* close)(int handle)
release a file OR directory handle.
Definition mnt.h:92
proto_bool(* sync)(int handle)
Push everything written to handle past the backend's own buffering, and report whether it got there....
Definition mnt.h:114
proto_bool(* stat)(const char *path, protocore_mnt_stat *out)
fill out for path; false if absent.
Definition mnt.h:100
int(* opendir)(const char *path)
-> directory handle (>=0) or -1.
Definition mnt.h:101
proto_bool(* seek)(int handle, uint64_t off)
absolute seek; false if unsupported.
Definition mnt.h:93
int(* read)(int handle, void *buf, size_t n)
bytes read, or -1.
Definition mnt.h:90
What a stat and a directory entry both answer: the facts about one file, and no name.
Definition mnt.h:71
uint64_t size
0 for a directory
Definition mnt.h:73
proto_bool is_dir
Definition mnt.h:72
uint32_t mtime
unix epoch seconds, 0 if the backend keeps no timestamp
Definition mnt.h:74
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97