1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
//! # bllist
//!
//! `bllist` provides durable, crash-safe, checksummed block-based linked-list
//! allocators built on top of a single [`bstack`] file.
//!
//! Two allocator types are available:
//!
//! | Type | Block size | File magic | Use when |
//! |------|-----------|------------|----------|
//! | [`FixedBlockList<PAYLOAD_CAPACITY>`](FixedBlockList) | constant | `"BLLS"` | All records are the same size |
//! | [`DynamicBlockList`] | variable (power-of-two bins) | `"BLLD"` | Records vary in size |
//!
//! The two types use **different file formats** and cannot open each other's
//! files. Both inherit BStack's exclusive advisory lock, durable fsync writes,
//! and crash-recovery guarantees.
//!
//! ## Quick start — fixed-size blocks
//!
//! ```no_run
//! use bllist::FixedBlockList;
//!
//! // 52 bytes of payload per block (64 bytes total on disk).
//! let list = FixedBlockList::<52>::open("data.blls")?;
//!
//! list.push_front(b"hello")?;
//! list.push_front(b"world")?;
//!
//! while let Some(data) = list.pop_front()? {
//! println!("{}", String::from_utf8_lossy(&data));
//! }
//! // prints "world", then "hello"
//! # Ok::<(), bllist::Error>(())
//! ```
//!
//! ## Quick start — variable-size blocks
//!
//! ```no_run
//! use bllist::DynamicBlockList;
//!
//! // The total on-disk block size (header + payload) is a power of two.
//! // A 5-byte push occupies 32 bytes on disk (5+20=25 → 32, bin 5).
//! let list = DynamicBlockList::open("data.blld")?;
//!
//! list.push_front(b"short")?;
//! list.push_front(b"a somewhat longer record")?;
//!
//! while let Some(data) = list.pop_front()? {
//! println!("{}", String::from_utf8_lossy(&data));
//! }
//! # Ok::<(), bllist::Error>(())
//! ```
//!
//! ## File layouts
//!
//! ### `FixedBlockList` (`"BLLS"`)
//!
//! ```text
//! ┌──────────────────────────┬───────────────────────────────────────────┐
//! │ BStack header (16 B) │ bllist header (24 B at logical offset 0) │
//! │ "BSTK" magic + clen │ "BLLS" + version + root + free_head │
//! ├──────────────────────────┴───────────────────────────────────────────┤
//! │ Block 0 (PAYLOAD_CAPACITY+12 bytes, logical offset 24) │
//! │ checksum(4) │ next(8) │ payload(PAYLOAD_CAPACITY) │
//! ├──────────────────────────────────────────────────────────────────────┤
//! │ Block 1 … │
//! └──────────────────────────────────────────────────────────────────────┘
//! ```
//!
//! ### `DynamicBlockList` (`"BLLD"`)
//!
//! ```text
//! ┌──────────────────────────┬───────────────────────────────────────────────┐
//! │ BStack header (16 B) │ bllist-dynamic header (272 B, logical off 0) │
//! │ "BSTK" magic + clen │ "BLLD" + version + root + bin_heads[32] │
//! ├──────────────────────────┴───────────────────────────────────────────────┤
//! │ Block (total size = 2^k bytes, k ≥ 5) │
//! │ checksum(4) │ next(8) │ block_size(4) │ data_len(4) │ payload(bs-20 B) │
//! ├──────────────────────────────────────────────────────────────────────────┤
//! │ Block … │
//! └──────────────────────────────────────────────────────────────────────────┘
//! ```
//!
//! Bin *k* holds free blocks whose **total** on-disk size equals 2^*k* bytes.
//! The minimum block is bin 5 (32 bytes total, 12-byte payload). Large free
//! blocks may be split to satisfy smaller requests; adjacent free blocks may
//! be coalesced on open.
//!
//! The BStack header is managed transparently by the [`bstack`] crate;
//! callers only see logical offsets starting at 0.
//!
//! ## Crash safety
//!
//! Every mutation flushes durably (via `fsync` / `F_FULLFSYNC`) before
//! returning. If the process is killed mid-operation the worst case is one
//! *orphaned* block that is silently reclaimed the next time the file is
//! opened.
//!
//! ## Streaming reads (`DynamicBlockList`)
//!
//! [`read`](DynamicBlockList::read) and [`read_into`](DynamicBlockList::read_into)
//! always verify the CRC and copy or allocate on every call. For large
//! payloads — or when you need to pass a byte range to another layer (e.g.
//! `sendfile`, a scatter-gather buffer, or an async runtime) — you can compute
//! the exact file offsets and issue a single raw read through the underlying
//! [`BStack`](bstack::BStack):
//!
//! ```no_run
//! use bllist::DynamicBlockList;
//!
//! let list = DynamicBlockList::open("data.blld")?;
//! # let block = list.alloc(0)?;
//!
//! // data_start is pure — no I/O, no Result.
//! let start: u64 = block.data_start();
//! // data_end reads the 4-byte data_len field.
//! let end: u64 = list.data_end(block)?;
//!
//! // One pread directly into a caller-owned buffer; no CRC, no allocation.
//! let mut buf = vec![0u8; (end - start) as usize];
//! list.bstack().get_into(start, &mut buf)?;
//!
//! // Or stream a sub-range into an existing buffer:
//! # let mut frame = vec![0u8; 64];
//! # let frame_offset = 0usize;
//! list.bstack().get_into(start, &mut frame[frame_offset..])?;
//! # Ok::<(), bllist::Error>(())
//! ```
//!
//! **Only read-only BStack operations are safe** (`get`, `get_into`, `peek`,
//! `len`). Never call `push`, `pop`, or `set` on the handle returned by
//! [`bstack()`](DynamicBlockList::bstack) — doing so can silently corrupt the
//! list structure. Use [`read`](DynamicBlockList::read) or
//! [`read_into`](DynamicBlockList::read_into) when CRC verification matters.
//!
//! ## Direct file access — use with extreme caution
//!
//! Both list types produce valid BStack files, so you can open them with
//! [`bstack::BStack::open`] or inspect raw bytes with any file tool.
//! **Writing to the file outside of `bllist` is strongly discouraged.**
//! `bllist` does not re-validate structural invariants on every operation, so
//! direct writes can silently corrupt the list in ways that are not caught
//! until much later — or not caught at all.
//!
//! | Direct BStack operation | Risk |
//! |--------------------------------------|------|
//! | `BStack::push` | Appends raw bytes that are not a complete, aligned block; corrupts slot enumeration and orphan recovery |
//! | `BStack::pop` | May truncate a block mid-stream or destroy the list header |
//! | `BStack::set` at header offsets | Overwrites root or free-list / bin-head pointers |
//! | `BStack::set` inside a block | Invalidates the block's CRC; `read` returns [`Error::ChecksumMismatch`] |
//! | Raw file writes (`write(2)`, etc.) | Bypasses the advisory lock entirely; any of the above, plus torn writes |
//!
//! The exclusive advisory lock ([`flock`] on Unix, `LockFileEx` on Windows)
//! held by a live list prevents a second process from opening the same file
//! through BStack simultaneously. It does **not** prevent raw file-descriptor
//! access.
//!
//! **Safe read-only inspection** is possible: open the file with
//! [`bstack::BStack::open`] and use only `get`, `peek`, and `len`. These
//! calls do not write to the file and will not disturb the list state.
//! Mutating calls (`push`, `pop`, `set`) must not be used.
//!
//! [`flock`]: https://man7.org/linux/man-pages/man2/flock.2.html
pub use ;
pub use Error;
pub use ;