Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

AnyFS — Getting Started Guide

A practical introduction with examples


Installation

Add to your Cargo.toml:

[dependencies]
anyfs = "0.1"

For additional backends:

[dependencies]
anyfs = { version = "0.1", features = ["stdfs", "vrootfs"] }

# SQLite storage (ecosystem crate)
anyfs-sqlite = "0.1"
anyfs-sqlite = { version = "0.1", features = ["encryption"] }  # With SQLCipher

# Hybrid backend: SQLite metadata + disk blobs (ecosystem crate)
anyfs-indexed = "0.1"

Available anyfs features:

  • memory — In-memory storage (default)
  • stdfs — Direct std::fs delegation (no containment)
  • vrootfs — Host filesystem backend with path containment
  • bytes — Zero-copy Bytes support (adds read_bytes() method)
  • tracing — Detailed audit logging (requires tracing crate)
  • fuse — Mount as filesystem on Linux/macOS
  • winfsp — Mount as filesystem on Windows

Core middleware (Quota, PathFilter, Restrictions, ReadOnly, RateLimit, Cache, DryRun, Overlay) is always available.


Quick Start

Examples below use FileStorage, so you can pass paths as &str. If you call core trait methods directly, use &Path.

Hello World

use anyfs::{MemoryBackend, FileStorage};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let fs = FileStorage::new(MemoryBackend::new());

    fs.write("/hello.txt", b"Hello, AnyFS!")?;
    let content = fs.read("/hello.txt")?;
    println!("{}", String::from_utf8_lossy(&content));

    Ok(())
}

With Quotas

use anyfs::{MemoryBackend, QuotaLayer, FileStorage};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let backend = QuotaLayer::builder()
        .max_total_size(100 * 1024 * 1024)  // 100 MB
        .max_file_size(10 * 1024 * 1024)    // 10 MB per file
        .build()
        .layer(MemoryBackend::new());

    let fs = FileStorage::new(backend);

    fs.create_dir_all("/documents")?;
    fs.write("/documents/notes.txt", b"Meeting notes")?;

    Ok(())
}

With Restrictions

use anyfs::{MemoryBackend, RestrictionsLayer, FileStorage};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Block permission changes for untrusted code
    let backend = RestrictionsLayer::builder()
        .deny_permissions()   // Block set_permissions() calls
        .build()
        .layer(MemoryBackend::new());

    let fs = FileStorage::new(backend);

    // All other operations work normally
    fs.write("/file.txt", b"content")?;

    Ok(())
}

Full Stack (Layer-based)

use anyfs::{MemoryBackend, QuotaLayer, RestrictionsLayer, TracingLayer, FileStorage};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let backend = MemoryBackend::new()
        .layer(QuotaLayer::builder()
            .max_total_size(100 * 1024 * 1024)
            .max_file_size(10 * 1024 * 1024)
            .build())
        .layer(RestrictionsLayer::builder()
            .deny_permissions()
            .build())
        .layer(TracingLayer::new());

    let fs = FileStorage::new(backend);

    fs.create_dir_all("/data")?;
    fs.write("/data/file.txt", b"hello")?;

    Ok(())
}

Common Operations

Creating Directories

#![allow(unused)]
fn main() {
fs.create_dir("/documents")?;              // Single level
fs.create_dir_all("/documents/2024/q1")?;  // Recursive
}

Reading and Writing Files

#![allow(unused)]
fn main() {
fs.write("/data.txt", b"line 1\n")?;       // Create or overwrite
fs.append("/data.txt", b"line 2\n")?;      // Append

let content = fs.read("/data.txt")?;                    // Read all
let partial = fs.read_range("/data.txt", 0, 6)?;        // Read range
let text = fs.read_to_string("/data.txt")?;             // Read as String
}

Listing Directories

#![allow(unused)]
fn main() {
for entry in fs.read_dir("/documents")? {
    println!("{}: {:?}", entry.name, entry.file_type);
}
}

Checking Existence and Metadata

#![allow(unused)]
fn main() {
if fs.exists("/file.txt")? {
    let meta = fs.metadata("/file.txt")?;
    println!("Size: {} bytes", meta.size);
}
}

Copying and Moving

#![allow(unused)]
fn main() {
fs.copy("/original.txt", "/copy.txt")?;
fs.rename("/original.txt", "/renamed.txt")?;
}

Deleting

#![allow(unused)]
fn main() {
fs.remove_file("/old-file.txt")?;
fs.remove_dir("/empty-folder")?;
fs.remove_dir_all("/old-folder")?;
}

Middleware

Quota — Resource Limits

#![allow(unused)]
fn main() {
use anyfs::{MemoryBackend, Quota};

let backend = QuotaLayer::builder()
    .max_total_size(500 * 1024 * 1024)   // 500 MB total
    .max_file_size(50 * 1024 * 1024)     // 50 MB per file
    .max_node_count(100_000)             // 100K files/dirs
    .max_dir_entries(5_000)              // 5K per directory
    .max_path_depth(32)                  // Max nesting
    .build()
    .layer(MemoryBackend::new());

// Check usage
let usage = backend.usage();
println!("Using {} bytes", usage.total_size);

// Check remaining
let remaining = backend.remaining();
if !remaining.can_write {
    println!("Storage full!");
}
}

Restrictions — Block Permission Changes

#![allow(unused)]
fn main() {
use anyfs::{MemoryBackend, RestrictionsLayer};

// Restrictions controls permission-related operations.
// Symlink/hard-link capability is determined by trait bounds (FsLink).
let backend = RestrictionsLayer::builder()
    .deny_permissions()   // Block set_permissions() calls
    .build()
    .layer(MemoryBackend::new());

// Blocked operations return FsError::FeatureNotEnabled
}

Tracing — Instrumentation

#![allow(unused)]
fn main() {
use anyfs::{MemoryBackend, TracingLayer};

// TracingLayer uses the global tracing subscriber by default
let backend = MemoryBackend::new().layer(TracingLayer::new());

// Or configure with custom settings
let backend = MemoryBackend::new()
    .layer(TracingLayer::new()
        .with_target("myapp::fs")
        .with_level(tracing::Level::DEBUG));
}

Error Handling

#![allow(unused)]
fn main() {
use anyfs_backend::FsError;

match fs.write("/file.txt", &large_data) {
    Ok(()) => println!("Written"),

    Err(FsError::NotFound { path, .. }) => println!("Not found: {}", path.display()),
    Err(FsError::AlreadyExists { path, .. }) => println!("Exists: {}", path.display()),
    Err(FsError::QuotaExceeded { .. }) => println!("Quota exceeded"),
    Err(FsError::FeatureNotEnabled { feature }) => println!("Feature disabled: {}", feature),

    Err(e) => println!("Error: {}", e),
}
}

Testing

#![allow(unused)]
fn main() {
use anyfs::{MemoryBackend, FileStorage};

#[test]
fn test_write_and_read() {
    let fs = FileStorage::new(MemoryBackend::new());

    fs.write("/test.txt", b"test data").unwrap();
    let content = fs.read("/test.txt").unwrap();

    assert_eq!(content, b"test data");
}
}

With limits:

#![allow(unused)]
fn main() {
use anyfs::{MemoryBackend, Quota, FileStorage};

#[test]
fn test_quota_exceeded() {
    let backend = QuotaLayer::builder()
        .max_total_size(1024)  // 1 KB
        .build()
        .layer(MemoryBackend::new());
    let fs = FileStorage::new(backend);

    let big_data = vec![0u8; 2048];  // 2 KB
    let result = fs.write("/big.bin", &big_data);

    assert!(result.is_err());
}
}

Best Practices

1. Use Appropriate Backend

Use CaseBackendCrate
TestingMemoryBackendanyfs
Production (portable)SqliteBackendanyfs-sqlite
Host filesystem (with containment)VRootFsBackendanyfs
Host filesystem (direct access)StdFsBackendanyfs

2. Compose Middleware for Your Needs

#![allow(unused)]
fn main() {
// Minimal: just storage
let fs = FileStorage::new(MemoryBackend::new());

// With limits (layer-based)
let fs = FileStorage::new(
    MemoryBackend::new()
        .layer(QuotaLayer::builder()
            .max_total_size(100 * 1024 * 1024)
            .build())
);

// Sandboxed (layer-based)
let temp_dir = tempfile::tempdir()?;
let fs = FileStorage::new(
    VRootFsBackend::new(temp_dir.path())?
        .layer(QuotaLayer::builder()
            .max_total_size(100 * 1024 * 1024)
            .build())
        .layer(RestrictionsLayer::builder()
            .deny_permissions()
            .build())
);
}

3. Handle Errors Gracefully

Always check for quota exceeded, feature not enabled, and other errors.


Advanced Use Cases

These use cases require the fuse or winfsp feature flags.

Database-Backed Drive with Live Monitoring

Mount a database-backed filesystem and query it directly for real-time analytics:

┌─────────────────────────────────────────────────────────────┐
│  Database (SQLite, PostgreSQL, etc.)                        │
├─────────────────────────────────────────────────────────────┤
│                         │                                   │
│    MountHandle          │         Stats Dashboard           │
│    (write + read)       │         (direct DB queries)       │
│         │               │               │                   │
│         ▼               │               ▼                   │
│  /mnt/workspace         │    SELECT SUM(size) FROM nodes    │
│  $ cp file.txt ./       │    SELECT COUNT(*) FROM nodes     │
│  $ mkdir projects/      │    SELECT * FROM audit_log        │
│                         │               │                   │
│                         │               ▼                   │
│                         │        ┌──────────────┐           │
│                         │        │ Live Graphs  │           │
│                         │        │ - Disk usage │           │
│                         │        │ - File count │           │
│                         │        │ - Recent ops │           │
│                         │        └──────────────┘           │
└─────────────────────────────────────────────────────────────┘

SQLite Example (using ecosystem crate - API sketch, planned):

#![allow(unused)]
fn main() {
use anyfs::{QuotaLayer, TracingLayer, MountHandle};
use anyfs_sqlite::SqliteBackend;  // Ecosystem crate

// Mount the drive
let backend = SqliteBackend::open("tenant.db")?
    .layer(TracingLayer::new())  // Logs operations to tracing subscriber
    .layer(QuotaLayer::builder()
        .max_total_size(1_000_000_000)
        .build());

let mount = MountHandle::mount(backend, "/mnt/workspace")?;
}
#![allow(unused)]
fn main() {
// Meanwhile, in a monitoring dashboard...
// Note: This queries SqliteBackend's internal schema (nodes, audit_log tables).
// See anyfs-sqlite documentation for schema details.
use rusqlite::{Connection, OpenFlags};

let conn = Connection::open_with_flags(
    "tenant.db",
    OpenFlags::SQLITE_OPEN_READ_ONLY,  // Safe concurrent reads
)?;

loop {
    let (file_count, total_bytes): (i64, i64) = conn.query_row(
        "SELECT COUNT(*), COALESCE(SUM(size), 0) FROM nodes WHERE type = 'file'",
        [],
        |row| Ok((row.get(0)?, row.get(1)?)),
    )?;

    let recent_ops: Vec<String> = conn
        .prepare("SELECT operation, path, timestamp FROM audit_log ORDER BY timestamp DESC LIMIT 10")?
        .query_map([], |row| Ok(format!("{}: {}", row.get::<_, String>(0)?, row.get::<_, String>(1)?)))?
        .collect::<Result<_, _>>()?;

    render_dashboard(file_count, total_bytes, &recent_ops);
    std::thread::sleep(Duration::from_secs(1));
}
}

Works with any database backend:

BackendDirect Query Method
SqliteBackendrusqlite with SQLITE_OPEN_READ_ONLY
Custom (user-implemented)Direct database driver connection

Third-party crates can implement additional database backends (PostgreSQL, MySQL, etc.) following the same pattern.

What you can visualize:

  • Real-time storage usage (gauges, bar charts)
  • File count over time (line graphs)
  • Operations log (live feed)
  • Most accessed files (heatmaps)
  • Directory tree maps (size visualization)
  • Per-tenant usage (multi-tenant dashboards)

This pattern is powerful because the database is the source of truth — you get filesystem semantics via FUSE and SQL analytics via direct queries, from the same data.

RAM Drive

#![allow(unused)]
fn main() {
use anyfs::{MemoryBackend, QuotaLayer, MountHandle};

// 4GB RAM drive
let mount = MountHandle::mount(
    MemoryBackend::new()
        .layer(QuotaLayer::builder()
            .max_total_size(4 * 1024 * 1024 * 1024)
            .build()),
    "/mnt/ramdisk"
)?;

// Use for fast compilation, temp files, etc.
// $ TMPDIR=/mnt/ramdisk cargo build
}

Sandboxed AI Agent Workspace

#![allow(unused)]
fn main() {
use anyfs::{MemoryBackend, QuotaLayer, PathFilterLayer, RestrictionsLayer, TracingLayer, MountHandle};

let mount = MountHandle::mount(
    MemoryBackend::new()
        .layer(PathFilterLayer::builder()
            .allow("/workspace/**")
            .deny("**/..*")           // No hidden files
            .deny("**/.*")            // No dotfiles
            .build())
        .layer(QuotaLayer::builder()
            .max_total_size(100 * 1024 * 1024)
            .max_file_size(10 * 1024 * 1024)
            .build())
        .layer(TracingLayer::new()),  // Full audit trail
    "/mnt/agent"
)?;

// Agent uses standard filesystem APIs
// All operations are sandboxed, quota-limited, and logged
}

Next Steps