AnyFS — API Quick Reference
Condensed reference for developers
Installation
[dependencies]
anyfs = "0.1"
With optional features and ecosystem crates:
anyfs = { version = "0.1", features = ["vrootfs", "bytes"] }
anyfs-sqlite = "0.1" # SQLite backend (ecosystem crate)
Creating a Backend Stack
#![allow(unused)]
fn main() {
use anyfs::{MemoryBackend, QuotaLayer, RestrictionsLayer, TracingLayer, FileStorage};
// Simple
let fs = FileStorage::new(MemoryBackend::new());
// With limits
let fs = FileStorage::new(
MemoryBackend::new()
.layer(QuotaLayer::builder()
.max_total_size(100 * 1024 * 1024)
.max_file_size(10 * 1024 * 1024)
.build())
);
// Full stack (fluent composition)
let backend = MemoryBackend::new()
.layer(QuotaLayer::builder()
.max_total_size(100 * 1024 * 1024)
.build())
.layer(RestrictionsLayer::builder()
.deny_permissions()
.build())
.layer(TracingLayer::new());
let fs = FileStorage::new(backend);
// BackendStack builder (fluent API)
use anyfs::BackendStack;
let fs = BackendStack::new(MemoryBackend::new())
.limited(|l| l.max_total_size(100 * 1024 * 1024))
.restricted(|g| g.deny_permissions())
.traced()
.into_container();
}
BackendStack Methods
BackendStack provides a fluent API for building middleware stacks:
#![allow(unused)]
fn main() {
use anyfs::BackendStack;
BackendStack::new(backend) // Start with any backend
.limited(|l| l // -> Quota<B>
.max_total_size(bytes)
.max_file_size(bytes)
.max_node_count(count))
.restricted(|g| g // -> Restrictions<B>
.deny_permissions())
.traced() // -> Tracing<B>
.cached(|c| c // -> Cache<B>
.max_size(bytes)
.ttl(duration))
.read_only() // -> ReadOnly<B>
.into_container() // -> FileStorage<...>
}
| Method | Creates | Description |
|---|---|---|
.limited() | Quota<B> | Add quota limits |
.restricted() | Restrictions<B> | Add operation restrictions |
.traced() | Tracing<B> | Add tracing instrumentation |
.cached() | Cache<B> | Add LRU caching |
.read_only() | ReadOnly<B> | Make backend read-only |
.into_container() | FileStorage<B> | Finish and wrap in FileStorage |
Quota Methods
#![allow(unused)]
fn main() {
// Builder pattern (required - at least one limit must be set)
QuotaLayer::builder()
.max_total_size(bytes) // Total storage limit
.max_file_size(bytes) // Per-file limit
.max_node_count(count) // Max files/dirs
.max_dir_entries(count) // Max entries per dir
.max_path_depth(depth) // Max nesting
.build()
.layer(backend)
// Query
backend.usage() // -> Usage { total_size, file_count, ... }
backend.limits() // -> Limits { max_total_size, ... }
backend.remaining() // -> Remaining { bytes, can_write, ... }
}
Restrictions Methods
#![allow(unused)]
fn main() {
// Builder pattern
// Restrictions only controls permission-related operations.
// Symlink/hard-link capability is via trait bounds (FsLink), not middleware.
RestrictionsLayer::builder()
.deny_permissions() // Block set_permissions() calls
.build()
.layer(backend)
}
TracingLayer Methods
#![allow(unused)]
fn main() {
// TracingLayer configuration (applied via .layer())
TracingLayer::new()
.with_target("anyfs") // tracing target
.with_level(tracing::Level::DEBUG)
// Usage
let backend = inner.layer(TracingLayer::new().with_target("anyfs"));
}
PathFilter Methods
#![allow(unused)]
fn main() {
// Builder pattern (required - at least one rule must be set)
PathFilterLayer::builder()
.allow("/workspace/**") // Allow glob pattern
.deny("**/.env") // Deny glob pattern
.deny("**/secrets/**")
.build()
.layer(backend)
// Rules evaluated in order; first match wins
// No match = denied (deny by default)
}
ReadOnly Methods
#![allow(unused)]
fn main() {
ReadOnly::new(backend)
// All read operations pass through
// All write operations return FsError::ReadOnly
}
RateLimit Methods
#![allow(unused)]
fn main() {
// Builder pattern (required - must set ops and window)
RateLimitLayer::builder()
.max_ops(1000) // Operation limit
.per_second() // Window: 1 second
// or
.per_minute() // Window: 60 seconds
// or
.per(Duration::from_millis(500)) // Custom window
.build()
.layer(backend)
}
DryRun Methods
#![allow(unused)]
fn main() {
let dry_run = DryRun::new(backend);
let fs = FileStorage::new(dry_run);
// Read operations execute normally
// Write operations are logged but not executed
fs.write("/file.txt", b"data")?; // Logged, returns Ok
// Inspect logged operations (returns Vec<String>)
let ops: Vec<String> = dry_run.operations();
// e.g., ["write /file.txt (4 bytes)", "remove_file /old.txt"]
dry_run.clear(); // Clear the log
}
Cache Methods
#![allow(unused)]
fn main() {
// Builder pattern (required - at least max_entries must be set)
CacheLayer::builder()
.max_entries(1000) // LRU cache size
.max_entry_size(1024 * 1024) // 1MB max per entry
.ttl(Duration::from_secs(300)) // Optional: entry lifetime (default: no expiry)
.build()
.layer(backend)
}
IndexLayer Methods (Future)
#![allow(unused)]
fn main() {
// Builder pattern (required - set index path)
IndexLayer::builder()
.index_file("index.db") // Sidecar index file (SQLite default)
.consistency(IndexConsistency::Strict)
.track_reads(false) // Optional
.build()
.layer(backend)
}
Overlay Methods
#![allow(unused)]
fn main() {
use anyfs::{VRootFsBackend, MemoryBackend, Overlay};
let base = VRootFsBackend::new("/var/templates")?; // Read-only base
let upper = MemoryBackend::new(); // Writable upper
let overlay = Overlay::new(base, upper);
// Read: check upper first, fall back to base
// Write: always to upper layer
// Delete: whiteout marker in upper
}
FsExt Methods
Extension methods available on all backends:
#![allow(unused)]
fn main() {
use anyfs_backend::FsExt;
// JSON support (requires `serde` feature on anyfs-backend)
let config: Config = fs.read_json("/config.json")?;
fs.write_json("/config.json", &config)?;
// Type checks
if fs.is_file("/path")? { ... }
if fs.is_dir("/path")? { ... }
}
Note: JSON methods require
anyfs-backend = { version = "0.1", features = ["serde"] }
File Operations
Examples below assume FileStorage (std::fs-style paths). If you call core trait methods directly, pass &Path.
#![allow(unused)]
fn main() {
// Check existence
fs.exists("/path")? // -> bool
// Metadata
let meta = fs.metadata("/path")?;
meta.inode // unique identifier
meta.nlink // hard link count
meta.file_type // File | Directory | Symlink
meta.size // file size in bytes
meta.permissions // Permissions (default if unsupported)
meta.created // SystemTime (UNIX_EPOCH if unsupported)
meta.modified // SystemTime (UNIX_EPOCH if unsupported)
meta.accessed // SystemTime (UNIX_EPOCH if unsupported)
// Read
let bytes = fs.read("/path")?; // -> Vec<u8>
let text = fs.read_to_string("/path")?; // -> String
let chunk = fs.read_range("/path", 0, 1024)?;
// List directory
for entry in fs.read_dir("/path")? {
let entry = entry?;
entry.name // String (file/dir name only)
entry.path // PathBuf (full path)
entry.file_type // File | Directory | Symlink
entry.size // u64 (0 for directories)
entry.inode // u64 (0 if unsupported)
}
// Write
fs.write("/path", b"content")?; // Create or overwrite
fs.append("/path", b"more")?; // Append
// Directories
fs.create_dir("/path")?;
fs.create_dir_all("/path")?;
// Delete
fs.remove_file("/path")?;
fs.remove_dir("/path")?; // Empty only
fs.remove_dir_all("/path")?; // Recursive
// Move/Copy
fs.rename("/from", "/to")?;
fs.copy("/from", "/to")?;
// Links
fs.symlink("/target", "/link")?;
fs.hard_link("/original", "/link")?;
fs.read_link("/link")?; // -> PathBuf
fs.symlink_metadata("/link")?; // Metadata of link itself, not target
// Symlink capability is determined by FsLink trait bounds, not middleware.
// Permissions (requires FsPermissions)
fs.set_permissions("/path", perms)?;
// File size
fs.truncate("/path", 1024)?; // Resize to 1024 bytes
// Durability
fs.sync()?; // Flush all writes
fs.fsync("/path")?; // Flush writes for one file
}
Path Canonicalization
FileStorage provides path canonicalization that works on the virtual filesystem.
Note: Canonicalization requires FsLink because symlink resolution needs read_link() and symlink_metadata(). Backends that only implement Fs (without FsLink) cannot use these methods.
#![allow(unused)]
fn main() {
// Strict canonicalization - path must exist
let canonical = fs.canonicalize("/some/../path/./file.txt")?;
// Returns fully resolved absolute path, follows symlinks
// Soft canonicalization - handles non-existent paths
let resolved = fs.soft_canonicalize("/existing/dir/new_file.txt")?;
// Resolves existing components, appends non-existent remainder lexically
// Anchored canonicalization - sandboxed resolution
let safe = fs.anchored_canonicalize("/workspace/../etc/passwd", "/workspace")?;
// Clamps result within anchor directory (returns error if escape attempted)
}
Standalone utility (no backend needed):
#![allow(unused)]
fn main() {
use anyfs::normalize;
// Lexical path cleanup only
let clean = normalize("//foo///bar//"); // -> "/foo/bar"
// Does NOT resolve . or .. (those require filesystem context)
// Does NOT follow symlinks
}
Comparison:
| Function | Path Must Exist? | Follows Symlinks? | Resolves ..? |
|---|---|---|---|
canonicalize | Yes (all components) | Yes | Yes (symlink-aware) |
soft_canonicalize | No (appends non-existent) | Yes (for existing) | Yes (symlink-aware) |
anchored_canonicalize | No | Yes (for existing) | Yes (clamped to anchor) |
normalize | N/A (lexical only) | No | No |
Inode Operations (FsInode trait)
Backends implementing FsInode track inodes internally for hardlink support and FUSE mounting:
#![allow(unused)]
fn main() {
use anyfs::FileStorage;
// Convert between paths and inodes
let fs = FileStorage::new(backend);
let inode: u64 = fs.path_to_inode("/some/path")?;
let path: PathBuf = fs.inode_to_path(inode)?;
// Lookup child by name within a directory (FUSE-style)
let root_inode = fs.path_to_inode("/")?;
let child_inode = fs.lookup(root_inode, "filename.txt")?;
// Get metadata by inode (avoids path resolution)
let meta = fs.metadata_by_inode(inode)?;
// Hardlinks share the same inode
fs.hard_link("/original", "/link")?;
let ino1 = fs.path_to_inode("/original")?;
let ino2 = fs.path_to_inode("/link")?;
assert_eq!(ino1, ino2); // Same inode!
}
Inode sources by backend:
| Backend | Inode Source |
|---|---|
MemoryBackend | Internal node IDs (incrementing counter) |
anyfs-sqlite: SqliteBackend | SQLite row IDs (INTEGER PRIMARY KEY) |
VRootFsBackend | OS inode numbers (metadata.inode) |
Error Handling
#![allow(unused)]
fn main() {
use anyfs_backend::FsError;
match result {
// Path errors
Err(FsError::NotFound { path }) => {
// e.g., path="/file.txt"
}
Err(FsError::AlreadyExists { path, operation }) => ...
Err(FsError::NotADirectory { path }) => ...
Err(FsError::NotAFile { path }) => ...
Err(FsError::DirectoryNotEmpty { path }) => ...
Err(FsError::SymlinkLoop { path }) => ... // Circular symlink detected
// Quota middleware errors\n Err(FsError::QuotaExceeded { path, limit, requested, usage }) => ...\n Err(FsError::FileSizeExceeded { path, size, limit }) => ...\n\n // Restrictions middleware errors\n Err(FsError::FeatureNotEnabled { path, feature, operation }) => ...\n\n // PathFilter middleware errors\n Err(FsError::AccessDenied { path, reason }) => ...\n\n // ReadOnly middleware errors\n Err(FsError::ReadOnly { path, operation }) => ...\n\n // RateLimit middleware errors\n Err(FsError::RateLimitExceeded { path, limit, window_secs }) => ...
// FsExt errors
Err(FsError::Serialization(msg)) => ...
Err(FsError::Deserialization(msg)) => ...
// Optional feature not supported
Err(FsError::NotSupported { operation }) => ...
Err(e) => ...
}
}
Built-in Backends (anyfs crate)
| Type | Description |
|---|---|
MemoryBackend | In-memory storage (default) |
StdFsBackend | Direct std::fs (no containment) |
VRootFsBackend | Host filesystem (with path containment) |
Ecosystem Backends (Separate Crates)
| Crate | Type | Description |
|---|---|---|
anyfs-sqlite | SqliteBackend | Persistent single-file database (optional encryption via SQLCipher) |
anyfs-indexed | IndexedBackend | Virtual paths + disk blobs (large files) |
SqliteBackend Encryption (Ecosystem Crate)
Crate:
anyfs-sqlitewithencryptionfeature
#![allow(unused)]
fn main() {
use anyfs_sqlite::SqliteBackend;
// Standard (no encryption)
let backend = SqliteBackend::open("data.db")?;
// With encryption (requires `encryption` feature)
let backend = SqliteBackend::open_encrypted("encrypted.db", "password")?;
// With raw 256-bit key
let backend = SqliteBackend::open_with_key("encrypted.db", &key)?;
// Change password on open encrypted database
backend.change_password("new-password")?;
}
Without the correct password, the .db file appears as random bytes.
Feature:
anyfs-sqlite = { version = "0.1", features = ["encryption"] }
Middleware
| Type | Purpose |
|---|---|
Quota<B> | Quota enforcement |
Restrictions<B> | Least privilege |
PathFilter<B> | Path-based access control |
ReadOnly<B> | Prevent write operations |
RateLimit<B> | Operation throttling |
Tracing<B> | Instrumentation (tracing ecosystem) |
DryRun<B> | Log without executing |
Cache<B> | LRU read caching |
Overlay<B1,B2> | Union filesystem |
Layers
| Layer | Creates |
|---|---|
QuotaLayer | Quota<B> |
RestrictionsLayer | Restrictions<B> |
PathFilterLayer | PathFilter<B> |
ReadOnlyLayer | ReadOnly<B> |
RateLimitLayer | RateLimit<B> |
TracingLayer | Tracing<B> |
DryRunLayer | DryRun<B> |
CacheLayer | Cache<B> |
Note:
Overlay<B1, B2>is constructed directly viaOverlay::new(base, upper)rather than using a Layer, because it takes two backends.
Type Reference
From anyfs-backend
Core Traits (Layer 1):
| Trait | Description |
|---|---|
FsRead | Read operations: read, read_to_string, read_range, exists, metadata, open_read |
FsWrite | Write operations: write, append, remove_file, rename, copy, truncate, open_write |
FsDir | Directory operations: read_dir, create_dir*, remove_dir* |
Extended Traits (Layer 2):
| Trait | Description |
|---|---|
FsLink | Link operations: symlink, hard_link, read_link |
FsPermissions | Permission operations: set_permissions |
FsSync | Sync operations: sync, fsync |
FsStats | Stats operations: statfs |
Inode Trait (Layer 3):
| Trait | Description |
|---|---|
FsInode | Inode operations: path_to_inode, inode_to_path, lookup, metadata_by_inode |
POSIX Traits (Layer 4):
| Trait | Description |
|---|---|
FsHandles | Handle operations: open, read_at, write_at, close |
FsLock | Lock operations: lock, try_lock, unlock |
FsXattr | Extended attribute operations: get_xattr, set_xattr, list_xattr |
Convenience Supertraits:
| Trait | Combines |
|---|---|
Fs | FsRead + FsWrite + FsDir (90% of use cases) |
FsFull | Fs + FsLink + FsPermissions + FsSync + FsStats |
FsFuse | FsFull + FsInode (FUSE-mountable) |
FsPosix | FsFuse + FsHandles + FsLock + FsXattr (full POSIX) |
Other Types:
| Type | Description |
|---|---|
Layer | Middleware composition trait |
FsExt | Extension methods trait (JSON, type checks) |
FsError | Error type (with context) |
ROOT_INODE | Constant: root directory inode (= 1) |
FileType | File, Directory, Symlink |
Metadata | File/dir metadata (inode, nlink, size, times, permissions) |
DirEntry | Directory entry (name, inode, file_type) |
Permissions | File permissions (mode: u32) |
StatFs | Filesystem stats (bytes, inodes, block_size) |
From anyfs
| Type | Description |
|---|---|
MemoryBackend | In-memory backend |
StdFsBackend | Direct std::fs backend (no containment) |
VRootFsBackend | Host FS backend (with containment) |
Quota<B> | Quota middleware |
Restrictions<B> | Feature gate middleware |
PathFilter<B> | Path access control middleware |
ReadOnly<B> | Read-only middleware |
RateLimit<B> | Rate limiting middleware |
Tracing<B> | Tracing middleware |
DryRun<B> | Dry-run middleware |
Cache<B> | Caching middleware |
Overlay<B1,B2> | Union filesystem middleware |
QuotaLayer | Layer for Quota |
RestrictionsLayer | Layer for Restrictions |
PathFilterLayer | Layer for PathFilter |
ReadOnlyLayer | Layer for ReadOnly |
RateLimitLayer | Layer for RateLimit |
TracingLayer | Layer for Tracing |
DryRunLayer | Layer for DryRun |
CacheLayer | Layer for Cache |
OverlayLayer | Layer for Overlay |
From Ecosystem Crates
| Crate | Type | Description |
|---|---|---|
anyfs-sqlite | SqliteBackend | SQLite backend (optional encryption with feature) |
anyfs-indexed | IndexedBackend | Virtual paths + disk blobs |
Ergonomic Wrappers (in anyfs):
| Type | Description |
|---|---|
FileStorage<B> | Thin ergonomic wrapper (generic backend, boxed resolver) |
BackendStack | Fluent builder for middleware stacks |
.boxed() | Opt-in type erasure for FileStorage |
IterativeResolver | Default path resolver (symlink-aware for backends with FsLink) |
NoOpResolver | No-op resolver for SelfResolving backends |
CachingResolver<R> | LRU cache wrapper around another resolver |