| Crates.io | bitcoinleveldb-posixseqfile |
| lib.rs | bitcoinleveldb-posixseqfile |
| version | 0.1.1 |
| created_at | 2025-12-01 17:54:01.454885+00 |
| updated_at | 2025-12-01 17:54:01.454885+00 |
| description | POSIX-backed implementation of a LevelDB-compatible SequentialFile using read, lseek, and close for efficient sequential file access in Bitcoin-oriented storage engines. |
| homepage | |
| repository | |
| max_upload_size | |
| id | 1960195 |
| size | 118,170 |
A small, sharply focused crate that provides a POSIX-backed implementation of LevelDB's SequentialFile abstraction, suitable for integration into a Bitcoin-oriented LevelDB port or any storage engine that expects a LevelDB-compatible sequential file API.
bitcoinleveldb-posixseqfile exposes a single core type:
pub struct PosixSequentialFile {
fd: i32,
filename: String,
}
It is designed as a low-level adapter from raw POSIX file descriptors (read, lseek, close) to the traits that model LevelDB's sequential file interface:
SequentialFileSequentialFileReadSequentialFileSkipNamedThis crate does not own the higher-level database semantics; it only implements efficient, robust, sequential read access over an already-open file descriptor in a form that is idiomatic to a Rust port of LevelDB.
The design goals are:
Strict adherence to LevelDB semantics: sequential-only reads, explicit skip, and EOF behavior that callers can rely on.
POSIX-conformant behavior: uses read(2), lseek(2), and close(2) directly, with correct handling of EINTR and I/O errors.
Thread-friendly, but not thread-safe: instances can be used by multiple threads if externally synchronized, but internal methods assume exclusive &mut access as per the SequentialFile contract.
Minimal overhead: no allocation in the hot path except what is required to construct Status and log messages on error paths.
PosixSequentialFilepub struct PosixSequentialFile {
fd: i32,
filename: String,
}
impl PosixSequentialFile {
pub fn new(filename: String, fd: i32) -> Self { /* ... */ }
}
impl SequentialFile for PosixSequentialFile {}
impl SequentialFileRead for PosixSequentialFile {}
impl SequentialFileSkip for PosixSequentialFile {}
impl Named for PosixSequentialFile {}
use bitcoinleveldb_posixseqfile::PosixSequentialFile;
// `fd` must be an open file descriptor positioned at the desired starting offset.
let filename = "/var/lib/blocks/index.leveldb/000123.sst".to_owned();
let fd: i32 = open_file_descriptor_somehow();
let seq = PosixSequentialFile::new(filename, fd);
filename is stored only for diagnostics and introspection via Named.fd is assumed to be a valid, open, read-capable POSIX file descriptor.PosixSequentialFile takes ownership of fd and will close it in Drop.use bitcoinleveldb_posixseqfile::PosixSequentialFile;
use your_leveldb_traits::Named;
fn print_name(f: &PosixSequentialFile) {
println!("sequential file: {}", f.name());
}
The Named implementation returns a borrowed Cow<'_, str> referencing the stored filename. This allows logging and error reporting with minimal allocation.
The core of the abstraction is realized via SequentialFileRead:
impl SequentialFileRead for PosixSequentialFile {
fn read(
&mut self,
n: usize,
result: *mut Slice,
scratch: *mut u8,
) -> Status { /* ... */ }
}
Semantics:
n bytes from the current file offset into the caller-provided buffer at scratch.*result to a Slice pointing directly into the scratch buffer with the actual read size.read returns 0 (EOF), *result is set to an empty Slice.Status::ok().EINTR):
Status::io_error(ctx, detail) describing the failure.*result as an empty Slice.Status.EINTR:
read syscall in a loop.The function is deliberately low-level:
result and scratch are raw pointers to avoid any coupling to particular slice or buffer lifetimes in higher layers.scratch points to a writable buffer of at least n bytes for the duration of the call.Slice is a lightweight, non-owning view into arbitrary memory, compatible with typical LevelDB-style slice abstractions.This design isolates unsafe operations to the implementation, while presenting a deterministic interface to the rest of the LevelDB port.
Sequential skipping is implemented via lseek(2) relative to the current offset:
impl SequentialFileSkip for PosixSequentialFile {
fn skip(&mut self, n: u64) -> Status { /* ... */ }
}
Semantics:
n bytes using lseek(fd, n, SEEK_CUR).lseek succeeds:
Status::ok().lseek fails:
Status::io_error with:
ctx set to the file name.detail set to the OS error string.Status.Note that this is not a pread-style interface: it mutates the underlying file offset. This matches the expectations of LevelDB's SequentialFile, which models a forward-only scan.
PosixSequentialFile owns its fd and is responsible for closing it when dropped:
impl Drop for PosixSequentialFile {
fn drop(&mut self) {
// close(fd) with logging and error reporting
}
}
drop, it invokes libc::close(self.fd).close fails, it logs a warning including the errno and OS error text.This RAII discipline reduces the risk of descriptor leaks when used from higher-level code that may early-return on error or panic.
PosixSequentialFile is described as:
Instances of this class are thread-friendly but not thread-safe, as required by the SequentialFile API.
Interpretation:
read, lseek, and close are thread-safe for independent file descriptors. Multiple PosixSequentialFile instances that wrap distinct fds can be used concurrently without interference.PosixSequentialFile should not be accessed from multiple threads without synchronization. This is enforced at the Rust level by the use of &mut self in read and skip.In other words, concurrency is possible at the database level through sharding or independent files, but this object models a single-threaded cursor over one file.
All public trait methods return a Status object consistent with LevelDB conventions. While the exact Status type is defined elsewhere, this crate uses it as follows:
Status::ok() for successful operations.Status::io_error(ctx: &Slice, detail: Option<&Slice>) for OS-level I/O failures.For both read and skip:
io::Error::last_os_error().errno is obtained via raw_os_error().unwrap_or(0) for logging.This allows higher layers to distinguish between logical database errors (e.g., corruption) and environmental errors (e.g., disk full, permission denied), which is essential for robust storage systems.
The implementation uses structured logging macros (trace!, debug!, warn!) to emit:
new, read, skip, and drop.errno, file name, and human-readable error text.skip.The macros are assumed to be provided by an external logging facade (e.g., tracing or log-compatible macros). This crate focuses on ensuring that meaningful, structured context is always available for observability and diagnostics.
PosixSequentialFile is typically not used directly by application code. Instead, it is instantiated by an environment or file system adapter layer that implements the broader LevelDB Env API, for example:
use bitcoinleveldb_posixseqfile::PosixSequentialFile;
use your_leveldb_traits::{SequentialFile, SequentialFileRead, SequentialFileSkip, Slice, Status};
fn open_sequential_file(path: &str) -> Result<Box<dyn SequentialFile>, Status> {
use std::ffi::CString;
use libc::{open, O_RDONLY};
let c_path = CString::new(path).unwrap();
let fd = unsafe { open(c_path.as_ptr(), O_RDONLY) };
if fd < 0 {
// Map to Status::io_error via your environment layer
return Err(/* ... */);
}
Ok(Box::new(PosixSequentialFile::new(path.to_owned(), fd)))
}
Higher-level database components (iterators, table readers, compaction code) then interact only with the trait objects, not with raw file descriptors or POSIX primitives.
All direct interaction with POSIX syscalls is encapsulated in unsafe blocks inside this crate. The public API remains entirely safe under the following preconditions:
fd) that remains unique to this PosixSequentialFile instance.new.scratch buffer of length at least n bytes for read, with a lifetime sufficient for the duration of the call.scratch or result beyond the bounds specified by n and the read return value.Slice values.When used through the intended LevelDB environment layer, these invariants are typically enforced at a single integration point.
While the crate itself is small, its behavior matters for large sequential scans common in database workloads:
n and the buffer strategy.Slice is just a view.For high-throughput workloads (e.g., LevelDB table scans in block validation), you can tune performance by:
Choosing a large n and reusing a fixed-size scratch buffer across calls.
Aligning I/O sizes with filesystem or storage hardware characteristics.
The following example shows how you might manually loop over a file sequentially using the traits. This is mostly illustrative; real applications will likely use higher-level iterators.
use bitcoinleveldb_posixseqfile::PosixSequentialFile;
use your_leveldb_traits::{SequentialFileRead, Slice, Status};
fn scan_file(mut file: PosixSequentialFile) -> Status {
const BUF_SIZE: usize = 64 * 1024;
let mut buf = vec![0u8; BUF_SIZE];
loop {
let mut slice = Slice::default();
let status = file.read(BUF_SIZE, &mut slice as *mut Slice, buf.as_mut_ptr());
if !status.is_ok() {
return status;
}
if slice.is_empty() {
// EOF
break;
}
// Process `slice` as a view into `buf[0..slice.len()]`.
process_chunk(&slice);
}
Status::ok()
}
This crate is licensed under the MIT license. See the LICENSE file for details.
This crate targets Rust 2024 edition, leveraging its language semantics while remaining compatible with stable tooling.