Durable local content-addressed blob storage.
Staging and ready files default to directories below the same cas root.
Commit syncs staged bytes, closes the file, atomically renames it, and syncs
both directories affected by the rename where the platform supports
directory handles. The configured staging and ready roots must share a
filesystem; a cross-device rename is rejected without copying or publishing
partial content.
Delete unlinks a loose blob and syncs its containing directory where supported. A directory-sync error after unlink is ambiguous but safely retryable: deleting an already absent blob syncs the directory again.
Filesystem operations and boundary faults are injectable per call through
:fs_module and :faults; no mutable global test state is used.
Summary
Functions
Publishes a checksum-verified repair, atomically replacing a corrupt path.
Durably deletes a loose CAS blob.
Ensures a hash is ready in the global CAS.
Recovers a completed caller-owned staging file without copying it.
Stages bytes from a state-threaded reader without buffering the full body.
Validates a bare hexadecimal SHA-256 digest without raising.
Types
Functions
@spec commit_repair( ExStorageService.BlobStore.StagedBlob.t(), keyword() ) :: {:ok, ExStorageService.BlobStore.ReadyBlob.t()} | {:error, term()}
Publishes a checksum-verified repair, atomically replacing a corrupt path.
The staged source is independently verified before the final same-filesystem rename. POSIX rename replacement keeps readers on either the old inode or the complete repaired inode; no partial destination is exposed.
Durably deletes a loose CAS blob.
After unlinking, the containing directory is synced where supported. If that sync fails, the blob is already absent but an error is returned; retrying is safe and syncs the directory again. Packed content is not modified.
Ensures a hash is ready in the global CAS.
Existing loose or packed blobs are returned unchanged. With :bucket,
legacy content is streamed for checksum verification, synced, and renamed
into the CAS using the normal durable commit path.
@spec recover_stage(Path.t(), String.t(), non_neg_integer(), keyword()) :: {:ok, ExStorageService.BlobStore.StagedBlob.t()} | {:error, term()}
Recovers a completed caller-owned staging file without copying it.
The path must name a regular file on the same filesystem as the destination
CAS root. Its size and SHA-256 digest are checked against the caller's durable
metadata before a StagedBlob is returned. Symlinks, non-regular files,
changed files, mismatches, and cross-filesystem publication fail closed.
The recovered value may be passed to commit/2, which performs the normal
file sync, atomic rename, and directory sync. The caller must retain exclusive
ownership of the path until commit or discard completes.
@spec stage_from_reader((state -> reader_result(state)), state, keyword()) :: {:ok, ExStorageService.BlobStore.StagedBlob.t(), state} | {:error, term(), state} when state: term()
Stages bytes from a state-threaded reader without buffering the full body.
The reader receives its current state and must return {:more, chunk, state}
while data remains, or {:ok, final_chunk, state} / {:done, state} when
complete. The final reader state is returned to callers so adapters retain
updates such as a consumed Plug.Conn.
:max_size defaults to the configured maximum object size. Reader, write,
and size-limit failures close and remove the partial staging file.
@spec validate_hash(term()) :: :ok | {:error, :invalid_hash}
Validates a bare hexadecimal SHA-256 digest without raising.