Nothing to see here, move along meow
1use crate::block_io::BlockIo;
2use crate::cache::BlockCache;
3use crate::commit::{CommitRoots, SuperblockPair, atomic_commit};
4use crate::cow::FreemapReserved;
5use crate::ditto::DittoRegion;
6use crate::error::FsError;
7use crate::freemap::FreemapAllocator;
8use crate::integrity;
9use crate::pool::{NodeIdx, NodePool, addr_to_pool_idx, is_pool_ref};
10use lancer_core::fs::{BLOCKREF_SIZE, BlockRef, DEDUP_SHARDS, INODE_DIRECT_REFS, Inode};
11
12const MAX_DIRTY: usize = 8192;
13const MAX_FREEMAP_LEAVES: usize = 64;
14
15#[derive(Clone, Copy)]
16struct FlushEntry {
17 pool_idx: NodeIdx,
18 block_num: u64,
19 content_hash: u128,
20 integrity_crc: u32,
21}
22
23struct FlushMap {
24 entries: [FlushEntry; MAX_DIRTY],
25 count: usize,
26}
27
28impl FlushMap {
29 const fn new() -> Self {
30 Self {
31 entries: [FlushEntry {
32 pool_idx: 0,
33 block_num: 0,
34 content_hash: 0,
35 integrity_crc: 0,
36 }; MAX_DIRTY],
37 count: 0,
38 }
39 }
40
41 fn insert(
42 &mut self,
43 idx: NodeIdx,
44 block: u64,
45 content_hash: u128,
46 integrity_crc: u32,
47 ) -> Result<(), FsError> {
48 match self.count < MAX_DIRTY {
49 true => {
50 self.entries[self.count] = FlushEntry {
51 pool_idx: idx,
52 block_num: block,
53 content_hash,
54 integrity_crc,
55 };
56 self.count += 1;
57 Ok(())
58 }
59 false => Err(FsError::PoolExhausted),
60 }
61 }
62
63 fn lookup(&self, idx: NodeIdx) -> Option<&FlushEntry> {
64 self.entries[..self.count]
65 .iter()
66 .find(|e| e.pool_idx == idx)
67 }
68}
69
70#[allow(clippy::too_many_arguments)]
71pub fn transaction_commit(
72 pool: &mut NodePool,
73 cache: &mut BlockCache,
74 bio: &mut BlockIo,
75 freemap: &mut FreemapAllocator,
76 reserved: &mut FreemapReserved,
77 sb_pair: &mut SuperblockPair,
78 tree_root: &BlockRef,
79 dedup_roots: &[BlockRef; DEDUP_SHARDS],
80 snapshot_root: &BlockRef,
81 ditto: Option<&DittoRegion>,
82 scrub_cursor: u64,
83 next_object_id: u64,
84) -> Result<(), FsError> {
85 transaction_commit_full(
86 pool,
87 cache,
88 bio,
89 freemap,
90 reserved,
91 sb_pair,
92 tree_root,
93 dedup_roots,
94 snapshot_root,
95 ditto,
96 scrub_cursor,
97 next_object_id,
98 )
99}
100
101#[allow(clippy::too_many_arguments)]
102pub fn transaction_commit_full(
103 pool: &mut NodePool,
104 cache: &mut BlockCache,
105 bio: &mut BlockIo,
106 freemap: &mut FreemapAllocator,
107 reserved: &mut FreemapReserved,
108 sb_pair: &mut SuperblockPair,
109 tree_root: &BlockRef,
110 dedup_roots: &[BlockRef; DEDUP_SHARDS],
111 snapshot_root: &BlockRef,
112 ditto: Option<&DittoRegion>,
113 scrub_cursor: u64,
114 next_object_id: u64,
115) -> Result<(), FsError> {
116 let (dirty_indices, dirty_count) = pool.dirty_indices_by_level();
117 let mut flush_map = FlushMap::new();
118
119 (0..dirty_count).try_for_each(|i| {
120 let idx = dirty_indices[i];
121 let new_block = freemap.alloc_blocks(cache, bio, 1)?;
122
123 resolve_pool_refs_in_node(pool, idx, &flush_map);
124
125 let node_data = pool.node_bytes(idx);
126 let hashes = integrity::compute_block_hashes(node_data);
127 cache.cache_write(bio, new_block, node_data)?;
128
129 if let Some(ditto_region) = ditto {
130 let _ = ditto_region.write_ditto(bio, new_block, node_data);
131 }
132
133 flush_map.insert(idx, new_block, hashes.content_hash, hashes.integrity_crc)?;
134
135 Ok::<(), FsError>(())
136 })?;
137
138 let resolved_tree = resolve_root(tree_root, &flush_map);
139 let resolved_dedups: [BlockRef; DEDUP_SHARDS] =
140 core::array::from_fn(|i| resolve_root(&dedup_roots[i], &flush_map));
141 let resolved_snapshot = resolve_root(snapshot_root, &flush_map);
142
143 let modified_leaves = collect_modified_freemap_leaves(cache, freemap)?;
144 let new_freemap_root = modified_leaves
145 .iter()
146 .take_while(|&&k| k != u64::MAX)
147 .try_fold(freemap.root(), |current_root, &leaf_key| {
148 crate::cow::cow_freemap_path(cache, bio, ¤t_root, leaf_key, reserved)
149 })?;
150 freemap.set_root(new_freemap_root);
151
152 resolve_inode_pool_refs_in_cache(cache, bio, &flush_map)?;
153
154 let roots = CommitRoots {
155 tree_root: resolved_tree,
156 freemap_root: new_freemap_root,
157 dedup_roots: resolved_dedups,
158 snapshot_root: resolved_snapshot,
159 scrub_cursor,
160 next_object_id,
161 };
162
163 atomic_commit(bio, cache, sb_pair, &roots)?;
164 reserved.flip_after_commit();
165 pool.clear_all();
166
167 Ok(())
168}
169
170fn resolve_root(root: &BlockRef, flush_map: &FlushMap) -> BlockRef {
171 let addr = root.physical_block_addr();
172 match is_pool_ref(addr) {
173 true => {
174 let pidx = crate::pool::addr_to_pool_idx(addr);
175 flush_map.lookup(pidx).map_or(*root, |entry| {
176 let mut resolved = *root;
177 resolved.physical_addr =
178 crate::block_num_to_phys(entry.block_num) | (root.physical_addr & 0xF);
179 resolved.content_hash = entry.content_hash.to_le_bytes();
180 resolved.integrity_crc = entry.integrity_crc;
181 resolved
182 })
183 }
184 false => *root,
185 }
186}
187
188fn resolve_pool_refs_in_node(pool: &mut NodePool, idx: NodeIdx, flush_map: &FlushMap) {
189 let node = pool.get_mut(idx);
190 let count = node.entry_count();
191 (0..count).for_each(|i| {
192 let addr = node.entries[i].physical_block_addr();
193 if is_pool_ref(addr) {
194 let pidx = crate::pool::addr_to_pool_idx(addr);
195 if let Some(entry) = flush_map.lookup(pidx) {
196 let old_shift_bits = node.entries[i].physical_addr & 0xF;
197 node.entries[i].physical_addr =
198 crate::block_num_to_phys(entry.block_num) | old_shift_bits;
199 node.entries[i].content_hash = entry.content_hash.to_le_bytes();
200 node.entries[i].integrity_crc = entry.integrity_crc;
201 }
202 }
203 });
204}
205
206fn collect_modified_freemap_leaves(
207 cache: &BlockCache,
208 freemap: &FreemapAllocator,
209) -> Result<[u64; MAX_FREEMAP_LEAVES], FsError> {
210 let mut result = [u64::MAX; MAX_FREEMAP_LEAVES];
211 let mut count = 0usize;
212 let mut overflow = false;
213 let data_start = freemap.data_region_start();
214 let bits_per_leaf = lancer_core::fs::FREEMAP_BITS_PER_LEAF as u64;
215
216 cache.dirty_block_nums(|block_num| {
217 if block_num >= data_start {
218 let relative = block_num - data_start;
219 let leaf_key = relative / bits_per_leaf;
220 let already_present = (0..count).any(|i| result[i] == leaf_key);
221 if !already_present {
222 match count < MAX_FREEMAP_LEAVES {
223 true => {
224 result[count] = leaf_key;
225 count += 1;
226 }
227 false => {
228 overflow = true;
229 }
230 }
231 }
232 }
233 });
234
235 match overflow {
236 true => Err(FsError::PoolExhausted),
237 false => {
238 insertion_sort_leaves(&mut result, count);
239 Ok(result)
240 }
241 }
242}
243
244fn insertion_sort_leaves(arr: &mut [u64; MAX_FREEMAP_LEAVES], count: usize) {
245 (1..count).for_each(|i| {
246 let val = arr[i];
247 let insert_pos = arr[..i]
248 .iter()
249 .rposition(|&v| v <= val)
250 .map_or(0, |p| p + 1);
251 if insert_pos < i {
252 arr.copy_within(insert_pos..i, insert_pos + 1);
253 arr[insert_pos] = val;
254 }
255 });
256}
257
258const MAX_CACHE_DIRTY: usize = 512;
259
260fn resolve_inode_pool_refs_in_cache(
261 cache: &mut BlockCache,
262 bio: &mut BlockIo,
263 flush_map: &FlushMap,
264) -> Result<(), FsError> {
265 let mut dirty_blocks = [0u64; MAX_CACHE_DIRTY];
266 let mut count = 0usize;
267 cache.dirty_inode_block_nums(|block_num| {
268 dirty_blocks.get_mut(count).into_iter().for_each(|slot| {
269 *slot = block_num;
270 count += 1;
271 });
272 });
273
274 (0..count).try_for_each(|i| {
275 let data = cache.cache_get_mut(bio, dirty_blocks[i])?;
276 resolve_blockref_pool_refs(data, flush_map);
277 Ok::<(), FsError>(())
278 })
279}
280
281fn resolve_blockref_pool_refs(block_data: &mut [u8; 4096], flush_map: &FlushMap) {
282 let direct_offset = core::mem::offset_of!(Inode, direct);
283
284 (0..INODE_DIRECT_REFS).for_each(|i| {
285 let ref_start = direct_offset + i * BLOCKREF_SIZE;
286 let addr = u64::from_le_bytes(
287 block_data[ref_start..ref_start + 8]
288 .try_into()
289 .unwrap_or([0u8; 8]),
290 );
291
292 match is_pool_ref(addr) {
293 false => {}
294 true => {
295 let pidx = addr_to_pool_idx(addr);
296 if let Some(entry) = flush_map.lookup(pidx) {
297 let shift_bits = addr & 0xF;
298 let new_addr = crate::block_num_to_phys(entry.block_num) | shift_bits;
299 block_data[ref_start..ref_start + 8].copy_from_slice(&new_addr.to_le_bytes());
300
301 let hash_start = ref_start + core::mem::offset_of!(BlockRef, content_hash);
302 block_data[hash_start..hash_start + 16]
303 .copy_from_slice(&entry.content_hash.to_le_bytes());
304
305 let crc_start = ref_start + core::mem::offset_of!(BlockRef, integrity_crc);
306 block_data[crc_start..crc_start + 4]
307 .copy_from_slice(&entry.integrity_crc.to_le_bytes());
308 }
309 }
310 }
311 });
312}