Skip to main content

Crate io_m2dir

Crate io_m2dir 

Source
Expand description

§I/O m2dir Documentation Matrix Mastodon

M2dir client library, written in Rust.

This library is composed of 2 feature-gated layers:

  • Low-level I/O-free coroutines: these no_std-compatible state machines contain the whole m2dir logic and can be used anywhere
  • Mid-level std client: a standard, blocking m2dir client built on std::fs

§Table of contents

§Features

  • I/O-free coroutines: no_std state machines; no filesystem calls, no async runtime, no std required, drive against any blocking, async, or fuzz harness.
  • Standard, blocking client (requires client feature) backed by std::fs.
  • m2dir delivery protocol: the message-store coroutine writes to a temporary file in the same directory first, then atomically renames it to <date>,<checksum>.<nonce>.
  • Sidecar flag metadata via .meta/<id>.flags: add / remove / replace flags as a separate file alongside each entry.
  • No external base64 / rand / percent dependencies: the m2dir spec’s custom base64, FNV hashing, percent-encoding, and pseudo-random byte generation are kept as in-crate no_std modules.

[!TIP] I/O m2dir is written in Rust and uses cargo features to gate backend support. The default feature set is declared in Cargo.toml or on docs.rs.

§Specification coverage

This library implements the m2dir mail storage format as I/O-agnostic coroutines.

CoroutineWhat it does
M2dirCreateCreates an m2dir folder, its .meta sub-directory and the .m2dir marker file
M2dirDeleteRecursively removes an m2dir
M2dirListWalks the m2store tree and surfaces every directory carrying a .m2dir marker
M2dirEntryStoreWrites a temporary file, then atomically renames to <date>,<checksum>.<nonce>
M2dirEntryGetLocates an entry by id, reads its contents and validates the checksum
M2dirEntryListLists every confirmed entry inside an m2dir
M2dirEntryDeleteRemoves an entry file and every sibling .meta/<id>* file
M2dirFlagAddMerges flags into an entry’s .meta/<id>.flags sidecar
M2dirFlagRemoveRemoves flags from an entry’s .meta/<id>.flags sidecar (deletes the file when empty)
M2dirFlagSetReplaces an entry’s .meta/<id>.flags sidecar (deletes the file when the new set is empty)

§Usage

I/O m2dir can be consumed two ways, depending on how much of the I/O stack you want to own. Each mode is gated by cargo features.

Whichever mode you pick, every coroutine implements the M2dirCoroutine trait. Its resume(arg: Option<M2dirArg>) method returns a M2dirCoroutineState<Yield, Return> with two variants:

  • Yielded(Y): intermediate. Y is the per-coroutine yield type; every io-m2dir coroutine picks the standard M2dirYield, whose variants are WantsPid, WantsRandom { len }, WantsDirCreate, WantsDirRead, WantsDirRemove, WantsFileCreate, WantsFileRead, WantsFileExists, WantsFileRemove, WantsRename. The driver services the request and feeds back the matching M2dirArg variant on the next resume.
  • Complete(R): terminal. By convention R = Result<Output, Error> carrying the operation’s final value.

§I/O-free coroutines

No features required: works in #![no_std], no filesystem calls, no async runtime. You own the loop and the syscalls; the library only computes the operations to perform and consumes their results.

Drive a multi-step command (store an entry) against a blocking caller (the same shape works under async or in-memory replay):

use std::{collections::hash_map::RandomState, fs, hash::{BuildHasher, Hasher}, process};

use io_m2dir::{
    coroutine::*,
    entry::store::*,
    m2dir::types::M2dir,
    path::M2dirPath,
};

let m2dir = M2dir::from_path(M2dirPath::new("/path/to/m2dir/inbox"));
let bytes = b"From: alice@example.com\r\nSubject: Hello\r\n\r\nHello!\r\n".to_vec();

let mut coroutine = M2dirEntryStore::new(m2dir, bytes, M2dirEntryStoreOptions::default());
let mut arg: Option<M2dirArg> = None;

let entry = loop {
    match coroutine.resume(arg.take()) {
        M2dirCoroutineState::Complete(Ok(entry)) => break entry,
        M2dirCoroutineState::Complete(Err(err)) => panic!("{err}"),
        M2dirCoroutineState::Yielded(M2dirYield::WantsPid) => {
            arg = Some(M2dirArg::Pid(process::id()));
        }
        M2dirCoroutineState::Yielded(M2dirYield::WantsRandom { len }) => {
            // Replace with a stronger RNG if needed; the std client
            // ships a xorshift64* helper seeded from RandomState.
            let mut out = vec![0u8; len];
            let mut state = RandomState::new().build_hasher().finish();
            for byte in &mut out {
                state ^= state << 13;
                state ^= state >> 7;
                state ^= state << 17;
                *byte = state as u8;
            }
            arg = Some(M2dirArg::Random(out));
        }
        M2dirCoroutineState::Yielded(M2dirYield::WantsFileCreate(files)) => {
            for (path, bytes) in files {
                fs::write(path.as_str(), &bytes).unwrap();
            }
            arg = Some(M2dirArg::FileCreate);
        }
        M2dirCoroutineState::Yielded(M2dirYield::WantsRename(pairs)) => {
            for (from, to) in pairs {
                fs::rename(from.as_str(), to.as_str()).unwrap();
            }
            arg = Some(M2dirArg::Rename);
        }
        M2dirCoroutineState::Yielded(other) => unreachable!("M2dirEntryStore yielded {other:?}"),
    }
};

println!("stored {} at {}", entry.id(), entry.path());

§Std client

Enable the client feature (on by default). M2dirClient::new(root) wraps a filesystem root and exposes one method per coroutine; the resume loop is driven for you via M2dirClient::run and std::fs.

[dependencies]
io-m2dir = "0.1.0" # client is enabled by default
use io_m2dir::{client::M2dirClient, flag::types::M2dirFlags};

let client = M2dirClient::new("/path/to/store");

client.init_store().unwrap();
let inbox = client.create_m2dir("inbox").unwrap();

let bytes = b"From: alice@example.com\r\nSubject: Hello\r\n\r\nHello!\r\n".to_vec();
let entry = client.store(inbox.clone(), bytes).unwrap();

let mut flags = M2dirFlags::default();
flags.insert("$seen");
client.add_flags(&inbox, entry.id(), flags).unwrap();

println!("stored {} at {}", entry.id(), entry.path());

§Examples

Have a look at real-world projects built on top of this library:

§AI disclosure

This project is developed with AI assistance. This section documents how, so users and downstream packagers can make informed decisions.

  • Tools: Claude Code (Anthropic), Opus 4.7, invoked locally with a persistent project-scoped memory and a small set of repo-specific rules.

  • Used for: Refactors, mechanical multi-file edits, boilerplate (feature gates, error enums, derive macros, trait impls), test scaffolding, doc polish, exploratory design conversations.

  • Not used for: Engineering, critical code, git manipulation (commit, merge, rebase…), real-world tests.

  • Verification: Every AI-assisted change is read, compiled, tested, and formatted before commit (nix develop --command cargo check / cargo test / cargo fmt). Behavioural correctness is verified against the relevant spec, not assumed from the model output. Tests are never adjusted to fit AI-generated code; the code is adjusted to fit correct behaviour.

  • Limitations: AI models occasionally produce code that compiles and passes tests but is subtly wrong: off-by-one errors, missed edge cases, plausible but nonexistent APIs, stale spec references. The verification workflow catches most of this; it does not catch all of it. Bug reports are welcome and taken seriously.

  • Last reviewed: 30/05/2026

§License

This project is licensed under either of:

at your option.

§Social

§Sponsoring

nlnet

Special thanks to the NLnet foundation and the European Commission that have been financially supporting the project for years:

If you appreciate the project, feel free to donate using one of the following providers:

GitHub Ko-fi Buy Me a Coffee Liberapay thanks.dev PayPal

Modules§

clientclient
Standard, blocking m2dir client
coroutine
Generator-shape coroutine driver
entry
Entry-level m2dir coroutines: store, get, list, delete.
flag
Flag-level m2dir coroutines: add, remove, set.
m2dir
M2dir-level coroutines: create, delete, list.
path
‘/’-separated path used by m2dir and m2store.
store
Root m2store directory containing one or more m2dirs.