DocsFilesystem

libkore

Filesystem

libkore's native DagFS client: views a task actually holds, transfers with partial-write semantics, explicit resize and sync.

Source: docs/filesystem.md

What you hold

kore::fs is the client for DagFS, Kore's filesystem service. It doesn't implement another filesystem, cache permissions or create a global root. A program attaches with the filesystem face its founding supplied, and every path resolves inside the view it was endowed with:

  • Fs is the attached client. Fs::attach returns None when the program's founding carried no filesystem face.
  • View is an affine handle to one node, opened with explicit Rights. It can't be copied, so a view's authority can't be duplicated by accident.
  • Rights are DagFS's own: READ, WRITE, MUTATE_NAMESPACE (create, rename, link, unlink beneath a directory) and SHARE (create an attenuated edge for another session). Absence of a bit is authoritative, and a name never carries rights.

There is no POSIX namespace and no ambient root: a program that wasn't handed a directory can't name what's above it. Native path walks, listing, stat, creation, resize, sync, links, grants, revisions and queries stay on the same client through kore::fs::dag.

Rust
use kore::prelude::*;
use kore::fs::{self, TransferError};

// The caller supplies an attached client and an affine view it actually owns.
async fn store(fs: &mut Fs<'_, '_>, file: &View, bytes: &[u8])
    -> Result<(), TransferError>
{
    fs::write_all_at(fs, file, 0, bytes).await
}

A complete program that attaches, opens, writes and syncs is in Getting started.

Transfers

Fs::read_at and Fs::write_at move at most what fits: the shared data window, the service's advertised transfer limit and the wire's byte-count limit. The 4 KiB protocol floor is not a ceiling; durable providers can advertise 64 KiB. A short read need not be the end of the file; a zero-byte read is.

Large reads can take a faster path. A read_at whose destination is at least 256 KiB, or a read_file over a file at least that large, may stream through the launch's storage ring from one pinned version of the file. That depends on the destination's size, not the file's: a loop over a small buffer stays on the window, and one whole-file call reads a single version where a loop of positional reads pins one per call. Whether a launch holds a storage ring at all is decided at founding.

Partial writes and failure

read_exact_at and write_all_at loop bounded transfers without allocating, after checking the whole range up front. On failure both report only the acknowledged prefix in TransferError::completed:

  • For a write, the chunk in flight may already have committed.
  • For a read, the destination past the acknowledged count may hold streamed bytes: treat it as clobbered, not preserved.

Neither count is a retry offset, and neither helper replays a mutation for you.

An outstanding request owns the data window, so a later write refuses before touching it. Cancelling is not completing: keep the client alive and reconcile the outstanding request through its reply.

Closing

close_checked takes the view and returns success only after the service acknowledges the close. On refusal it hands back the same view with the error, and a retry matches the outstanding close, so it can't consume another handle's late reply. Drive the future to completion: dropping it is neither an acknowledged close nor a cancellation.

From C, Zig and Lua

The foreign surfaces hold native views for you. kore-ffi's session keeps views and copied buffers behind generational handles, so copying a handle's bits never clones a view, and a use after close is refused rather than reaching a recycled slot. kore-lua does the same for scripts. See C, Zig, Lua and WASI.