Skip to content

al8n/fmmap

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

102 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

fmmap

A flexible and convenient high-level mmap for zero-copy file I/O.

github LoC Build codecov

docs.rs crates.io crates.io license

Design

Inspired by Dgraph's mmap file implementation in ristretto.

A file-backed memory map exposes the kernel's view of an inode as a &[u8]/&mut [u8]. That makes it easy to reach for, but it also means UB the moment another actor truncates, unlinks, or rewrites the file out from under the mapping — SIGBUS on Unix, mapping detachment on Windows, silent torn reads in either. fmmap raises a safe API over memmapix by treating those concerns as first-class:

  • Auto-acquired advisory lock on every constructor — exclusive on writable maps, shared on read-only / COW maps. Aliased writable mappings of the same file (and mut-then-COW) are rejected up front.
  • Best-effort path-reuse mitigation on deletion. Identity is captured at open and re-checked before every unlink so a file someone else has swapped in at the path won't be silently deleted. POSIX uses (st_dev, st_ino); Windows uses (volumeSerial, fileIndex) from GetFileInformationByHandle (via windows-sys, no nightly required). This is not an absolute guarantee — see the path-reuse limitations below.
  • Pre-validated mapping ranges. Constructors reject offset/len overflow, ranges past EOF, and effective lengths > isize::MAX before any destructive set_len runs, so an invalid Options never zeroes or extends an existing file.
  • Crash-durable unlink. The parent directory is pinned by a handle opened before remove_file, then fsynced through that same handle. Failed-fsync retries fsync the same handle (not a freshly-opened parent), so a parent rename between unlink and fsync can't direct the durability to the wrong inode.
  • Reentrant-safe lock methods. LockFileEx deadlocks on the same Windows handle; lock / lock_shared short-circuit when the desired state is already held. The lock methods take &mut self so single-owner serialization is enforced by the borrow checker.
  • Poison-safe truncate / freeze. A failed truncate marks the wrapper poisoned; subsequent reads return &[] and writes/flushes/freezes return Err rather than handing back an anonymous-mapped placeholder pretending to be the original file.

std plus tokio and smol are first-class. The async surface is built from the same set of macros, so adding a new runtime is small and mechanical — see fmmap/src/disk/{tokio,smol}_impl.rs.

What identity-checked delete actually guarantees

Identity-checked deletion is built on the strongest atomic primitives each platform exposes; what's left is a small, documented set of irreducible races.

POSIX: probe + unlink + parent fsync are all bound to the same parent fd via rustix's fstatat + unlinkat. A parent rename mid-operation can't direct the unlink or fsync to a different directory than the one we verified. The original file's open-file description is held alive (via fcntl(F_DUPFD_CLOEXEC) or, in the tokio wrapper, tokio::fs::File::into_std()) across probe + unlink, so the kernel cannot recycle (dev, ino) to a fresh file in the window. Identity capture itself is allocation-free (fstat on a BorrowedFd), so EMFILE has no path to defeating the identity check.

Windows: probe and unlink are bound to a single handle. The handle is opened with DELETE | FILE_SHARE_* and FILE_FLAG_OPEN_REPARSE_POINT; we re-verify identity and refuse reparse points on that handle, then issue SetFileInformationByHandle(FileDispositionInfoEx) with POSIX_SEMANTICS | IGNORE_READONLY_ATTRIBUTE. Older Windows / FAT32 fall back to FileDispositionInfo after a ReOpenFile widens access to clear FILE_ATTRIBUTE_READONLY (using FILE_ATTRIBUTE_NORMAL as the cleared-state sentinel — Windows treats 0 as "no change"). Identity is captured directly via GetFileInformationByHandle on a borrowed HANDLE — no DuplicateHandle, no fd alloc.

API contract: explicit remove() (and drop_remove()) only returns Ok if fmmap itself observed the unlink succeed in the parent it then fsynced. NotFound from the probe or unlink is never converted into a durable-success retry — the wrapper stays in NeedsUnlink and surfaces the error, even when the inode's nlink has dropped to 0 (which can't distinguish "unlink in our parent" from "external rename + unlink elsewhere"). Drop's best-effort cleanup still fsyncs the parent in the common case, but the API doesn't promise durability we can't verify.

Residual races (irreducible at this layer)

  • One-syscall TOCTOU on POSIX. Between fstatat and unlinkat — both bound to the same parent fd — there's still a single-syscall window where the entry could be replaced. Closing this needs an inode-bound unlinkat primitive POSIX doesn't expose. The window is dramatically narrower than the handle-drop-to-retry window the identity check does close, but it's not zero.
  • External rename + unlink elsewhere. A concurrent actor can rename our file into a different directory and unlink it there. The inode's nlink drops to 0 but our parent's fsync doesn't commit their unlink. fmmap detects this only as "the file is gone" and surfaces NotFound; under that scenario, callers who need crash-durability should serialize external mutations or fsync the relevant parents themselves.
  • Smol consuming drop_remove(self) under EMFILE. smol's async-fs::File exposes no into_std(), so the inode pin is a fcntl_dupfd_cloexec of the underlying fd. Under fd pressure the dup fails, drop_remove returns Err deterministically (no hidden Drop-time retry), and the file remains on disk. Callers can recover via std::fs::remove_file(path) directly or AsyncMmapFileMut::remove(&mut self) which preserves self for an explicit retry. Tokio's into_std() allocates no fd so this limitation doesn't apply on tokio.

If your threat model includes an active local adversary, do not rely on identity-checked delete for safety — perform the cleanup yourself with whatever atomic primitives your platform provides.

Features

  • file-backed memory maps with auto-locked construction
  • read-only / copy-on-write / mutable / executable maps
  • identity-checked deletion bound to a single kernel-verified handle (POSIX fstatat+unlinkat on a parent fd; Windows SetFileInformationByHandle(FileDispositionInfoEx) on a DELETE | FILE_SHARE_DELETE handle); see Design for residual races
  • inode pin across probe + unlink (POSIX F_DUPFD_CLOEXEC or tokio into_std) — defends against (dev, ino) recycling on tmpfs / small-id filesystems
  • crash-durable unlink with pre-opened parent fsync (same handle reused on retry)
  • symlink / reparse-point refusal at the same syscall as the identity probe (POSIX AT_SYMLINK_NOFOLLOW, Windows FILE_FLAG_OPEN_REPARSE_POINT)
  • readonly-file delete on Windows (FileDispositionInfoEx with IGNORE_READONLY_ATTRIBUTE, legacy FileDispositionInfo fallback for pre-1607)
  • pre-validated mapping ranges (rejects past-EOF and > isize::MAX before any destructive set_len)
  • poison-safe truncate / freeze / freeze_exec
  • synchronous and asynchronous flushing
  • reader / writer adapters with byteorder + seek
  • dozens of file I/O util functions
  • stack support (MAP_STACK on Unix)
  • tokio
  • smol

Installation

fmmap requires Rust 1.75 or later.

  • std

    [dependencies]
    fmmap = "0.5"
  • tokio

    [dependencies]
    fmmap = { version = "0.5", default-features = false, features = ["tokio"] }
  • smol

    [dependencies]
    fmmap = { version = "0.5", default-features = false, features = ["smol"] }

The sync feature is on by default.

Examples

This crate is 100% documented, see docs.rs for examples.

License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

About

A flexible and convenient high-level mmap for zero-copy file I/O.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Sponsor this project

  •  

Packages

 
 
 

Contributors

Languages