pub trait Storage: ReadStorage {
// Required methods
fn write(
&self,
path: &Path,
contents: &[u8],
) -> impl Future<Output = Result<(), Error>>;
fn create_dir_all(
&self,
path: &Path,
) -> impl Future<Output = Result<(), Error>>;
fn remove_file(
&self,
path: &Path,
) -> impl Future<Output = Result<(), Error>>;
fn remove_dir_all(
&self,
path: &Path,
) -> impl Future<Output = Result<(), Error>>;
fn rename(
&self,
from: &Path,
to: &Path,
) -> impl Future<Output = Result<(), Error>>;
// Provided methods
fn copy_permissions(
&self,
from: &Path,
to: &Path,
) -> impl Future<Output = Result<(), Error>> { ... }
fn capabilities(&self) -> Capabilities { ... }
fn sync(
&self,
path: &Path,
need: Durability,
) -> impl Future<Output = Result<(), Error>> { ... }
fn write_atomic(
&self,
path: &Path,
contents: &[u8],
) -> impl Future<Output = Result<(), Error>> { ... }
}Expand description
An async filesystem backend a transaction can drive — ReadStorage plus everything
that changes bytes on disk.
Each method mirrors the std::fs function of the same name. Backends
implement the write/mutate/durability surface here and the read surface on
ReadStorage.
Required Methods§
Sourcefn write(
&self,
path: &Path,
contents: &[u8],
) -> impl Future<Output = Result<(), Error>>
fn write( &self, path: &Path, contents: &[u8], ) -> impl Future<Output = Result<(), Error>>
Write a file, replacing it if it already exists. Mirrors
std::fs::write.
Sourcefn create_dir_all(&self, path: &Path) -> impl Future<Output = Result<(), Error>>
fn create_dir_all(&self, path: &Path) -> impl Future<Output = Result<(), Error>>
Create a directory and all missing parents. Mirrors
std::fs::create_dir_all.
Sourcefn remove_file(&self, path: &Path) -> impl Future<Output = Result<(), Error>>
fn remove_file(&self, path: &Path) -> impl Future<Output = Result<(), Error>>
Remove a regular file. Mirrors std::fs::remove_file.
Sourcefn remove_dir_all(&self, path: &Path) -> impl Future<Output = Result<(), Error>>
fn remove_dir_all(&self, path: &Path) -> impl Future<Output = Result<(), Error>>
Recursively remove a directory and its contents. Mirrors
std::fs::remove_dir_all.
Provided Methods§
Sourcefn copy_permissions(
&self,
from: &Path,
to: &Path,
) -> impl Future<Output = Result<(), Error>>
fn copy_permissions( &self, from: &Path, to: &Path, ) -> impl Future<Output = Result<(), Error>>
Give to the same access permissions from has. A from that does not
exist is not an error — there is no prior state to carry over, so the
call has nothing to do.
This exists for write_atomic, which publishes
its bytes by renaming a freshly-created sibling over the target. A new
file is born with the backend’s default permissions, and a rename carries
those onto the name it replaces — so without this step, replacing a
document the user had deliberately restricted (chmod 600 on a private
journal entry) silently widens it to whatever the umask allows. A
content replacement must not be a permission change.
What this does not close is the window before it: the sibling holds the
new contents under default permissions from the moment it is written
until this call narrows it. Shutting that window means creating the file
with the final mode already on it, which is not something
write — a std::fs::write mirror — can express. The
sibling lives in the target’s own directory throughout, so whatever gates
access to the document gates access to it too.
The default is a no-op, which is the correct behavior for a backend
with no permission model at all — InMemoryFs, OPFS, IndexedDB. There
is nothing there to preserve, and nothing is lost by not preserving it.
Sourcefn capabilities(&self) -> Capabilities
fn capabilities(&self) -> Capabilities
What durability guarantees this backend can make. Defaults to
Capabilities::NONE — a backend promises a guarantee only by saying so,
so an adapter that forgets to override this degrades to the most defensive
path rather than to a false promise.
Sourcefn sync(
&self,
path: &Path,
need: Durability,
) -> impl Future<Output = Result<(), Error>>
fn sync( &self, path: &Path, need: Durability, ) -> impl Future<Output = Result<(), Error>>
Flush path — and nothing else — to the strength need asks for.
path names one object, and only that object is flushed. To make a
directory entry durable (the naming half of a create or a rename), sync
the directory itself: on a POSIX filesystem a directory is a thing that
can be opened and fsynced, and this crate’s own
write_atomic does exactly that after its
rename. Folding the parent into every call instead would flush twice as
much as any single step needs, and would leave the caller unable to say
which of the two it actually meant.
need is the weakest guarantee that is still correct at the call site,
not a wish. Durability::Ordered asks only that everything written to
path before this call land before anything written after it — enough to
stop a rename overtaking the bytes it publishes, and on some platforms far
cheaper than the real thing. Durability::Durable asks that the bytes
survive power loss. A backend may always answer with something stronger
than it was asked for; it may never answer with something weaker.
The default is a no-op, which is the correct behavior for any backend
whose capabilities report
SyncGuarantee::None: it cannot make the promise, so it must not
pretend to. A backend that can flush must both override this and report
the strongest request it genuinely honors — the two always travel
together, and SyncGuarantee::satisfies is how a caller asks.
Sourcefn write_atomic(
&self,
path: &Path,
contents: &[u8],
) -> impl Future<Output = Result<(), Error>>
fn write_atomic( &self, path: &Path, contents: &[u8], ) -> impl Future<Output = Result<(), Error>>
Replace path’s contents with contents atomically and durably: no
observer — concurrent reader or post-crash survivor — ever sees a splice
of old and new bytes, and once this returns the new contents outlive a
power loss.
The default composes the primitives into the standard protocol, whenever
capabilities report atomic_replace:
- write the bytes to a temporary sibling;
syncthat siblingOrdered, so the rename cannot be reordered ahead of the bytes it publishes;copy_permissionsfrom the target onto that sibling, so the replacement carries the target’s access permissions rather than a fresh file’s defaults;renameit over the target — this is the atomic instant;syncthe target’s parent directoryDurable, which is what carries the rename itself through a power cut.
Steps 2 and 3 are in that order because a backend may implement sync by
opening the path, and a mode faithfully copied from the target can be one
that forbids opening it to read — 0o200 is replaceable but not readable.
The cost is that the mode change lands after the flush and so is not
itself durable: a crash in that window can leave the new contents under
the default permissions. That is precisely the outcome every write had
before step 3 existed, so the window is a smaller bad case, never a new
one.
Two flushes, and each one is load-bearing. Neither of the two this protocol conspicuously does not do would buy anything. The bytes are never flushed under their final name, because a rename does not move an inode: the file the target now names is the very one step 2 flushed, and nothing has been written to it since. The sibling’s own directory entry is never flushed either, because nobody is owed a temporary that survives a crash — only the directory state after the rename is worth a barrier.
A backend that cannot rename atomically falls back to a plain durable
write, which is not crash-atomic; a caller that needs the guarantee
consults capabilities and leans on the journal instead of pretending
this call gave it. A backend with a better native path — a transactional
store — overrides this method wholesale.
The temporary is removed on any failure, so a torn attempt leaves the target exactly as it was and no litter behind. It is a dotted sibling in the target’s own directory, so the follow-up rename stays within one filesystem (a cross-device rename is neither atomic nor, often, even permitted).
Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".