Nothing to see here, move along meow
1use crate::block_io::BlockIo;
2use crate::btree;
3use crate::cache::BlockCache;
4use crate::commit::SuperblockPair;
5use crate::cow::FreemapReserved;
6use crate::ditto::DittoRegion;
7use crate::error::FsError;
8use crate::freemap::FreemapAllocator;
9use crate::integrity;
10use crate::pool::NodePool;
11use crate::transaction;
12use lancer_core::fs::{BLOCK_SIZE_MIN_LOG2, BlockRef, BlockType, Compression, DEDUP_SHARDS};
13
14const MAX_SNAPSHOT_COLLISION_SLOTS: usize = 8;
15
16pub fn snapshot_name_key(name: &[u8]) -> u64 {
17 integrity::xxhash64(name) & !0xF
18}
19
20#[allow(clippy::too_many_arguments)]
21pub fn create_snapshot(
22 pool: &mut NodePool,
23 cache: &mut BlockCache,
24 bio: &mut BlockIo,
25 freemap: &mut FreemapAllocator,
26 reserved: &mut FreemapReserved,
27 sb_pair: &mut SuperblockPair,
28 tree_root: &BlockRef,
29 dedup_roots: &[BlockRef; DEDUP_SHARDS],
30 snapshot_root: &BlockRef,
31 name: &[u8],
32 ditto: Option<&DittoRegion>,
33 scrub_cursor: u64,
34 next_object_id: u64,
35) -> Result<BlockRef, FsError> {
36 transaction::transaction_commit(
37 pool,
38 cache,
39 bio,
40 freemap,
41 reserved,
42 sb_pair,
43 tree_root,
44 dedup_roots,
45 snapshot_root,
46 ditto,
47 scrub_cursor,
48 next_object_id,
49 )?;
50
51 let snap_txn = sb_pair.active().transaction_id;
52 let current_tree_root = sb_pair.active().tree_root;
53 let current_snapshot_root = sb_pair.active().snapshot_root;
54 let current_dedup_roots = sb_pair.active().dedup_roots;
55
56 let base_key = snapshot_name_key(name);
57
58 let (final_key, new_snapshot_root) = (0..MAX_SNAPSHOT_COLLISION_SLOTS)
59 .map(|slot| base_key | slot as u64)
60 .find_map(|candidate_key| {
61 let snap_entry = BlockRef::new(
62 current_tree_root.physical_block_addr(),
63 current_tree_root
64 .block_size_log2()
65 .unwrap_or(BLOCK_SIZE_MIN_LOG2),
66 candidate_key,
67 snap_txn,
68 current_tree_root.content_hash_u128(),
69 current_tree_root.integrity_crc,
70 BlockType::Indirect,
71 Compression::None,
72 name.len() as u32,
73 0,
74 );
75 btree::btree_insert(
76 pool,
77 cache,
78 bio,
79 ¤t_snapshot_root,
80 candidate_key,
81 snap_entry,
82 snap_txn,
83 )
84 .ok()
85 .map(|root| (candidate_key, root))
86 })
87 .ok_or(FsError::TreeFull)?;
88 let _ = final_key;
89
90 transaction::transaction_commit(
91 pool,
92 cache,
93 bio,
94 freemap,
95 reserved,
96 sb_pair,
97 ¤t_tree_root,
98 ¤t_dedup_roots,
99 &new_snapshot_root,
100 ditto,
101 scrub_cursor,
102 next_object_id,
103 )?;
104
105 Ok(sb_pair.active().snapshot_root)
106}
107
108pub fn snapshot_lookup(
109 pool: &mut NodePool,
110 cache: &mut BlockCache,
111 bio: &mut BlockIo,
112 snapshot_root: &BlockRef,
113 name: &[u8],
114) -> Result<Option<SnapshotRef>, FsError> {
115 let base_key = snapshot_name_key(name);
116 (0..MAX_SNAPSHOT_COLLISION_SLOTS)
117 .map(|slot| base_key | slot as u64)
118 .try_fold(None, |found, candidate_key| match found {
119 Some(_) => Ok(found),
120 None => btree::btree_lookup(pool, cache, bio, snapshot_root, candidate_key, None).map(
121 |opt| {
122 opt.filter(|entry| entry.logical_size == name.len() as u32)
123 .map(|entry| SnapshotRef {
124 root_blockref: entry,
125 snapshot_txn_id: entry.transaction_id,
126 })
127 },
128 ),
129 })
130}
131
132pub struct SnapshotRef {
133 pub root_blockref: BlockRef,
134 pub snapshot_txn_id: u64,
135}
136
137impl SnapshotRef {
138 pub fn txn_filter(&self) -> Option<u64> {
139 Some(self.snapshot_txn_id)
140 }
141}
142
143pub fn snapshot_read_lookup(
144 pool: &mut NodePool,
145 cache: &mut BlockCache,
146 bio: &mut BlockIo,
147 snap: &SnapshotRef,
148 key: u64,
149) -> Result<Option<BlockRef>, FsError> {
150 btree::btree_lookup(
151 pool,
152 cache,
153 bio,
154 &snap.root_blockref,
155 key,
156 snap.txn_filter(),
157 )
158}
159
160fn find_snapshot_slot_key(
161 pool: &mut NodePool,
162 cache: &mut BlockCache,
163 bio: &mut BlockIo,
164 snapshot_root: &BlockRef,
165 name: &[u8],
166) -> Result<Option<u64>, FsError> {
167 let base_key = snapshot_name_key(name);
168 (0..MAX_SNAPSHOT_COLLISION_SLOTS)
169 .map(|slot| base_key | slot as u64)
170 .try_fold(None, |found, candidate_key| match found {
171 Some(_) => Ok(found),
172 None => btree::btree_lookup(pool, cache, bio, snapshot_root, candidate_key, None).map(
173 |opt| {
174 opt.filter(|entry| entry.logical_size == name.len() as u32)
175 .map(|_| candidate_key)
176 },
177 ),
178 })
179}
180
181#[allow(clippy::too_many_arguments)]
182pub fn delete_snapshot(
183 pool: &mut NodePool,
184 cache: &mut BlockCache,
185 bio: &mut BlockIo,
186 freemap: &mut FreemapAllocator,
187 reserved: &mut FreemapReserved,
188 sb_pair: &mut SuperblockPair,
189 snapshot_root: &BlockRef,
190 name: &[u8],
191 ditto: Option<&DittoRegion>,
192 scrub_cursor: u64,
193 next_object_id: u64,
194) -> Result<BlockRef, FsError> {
195 let actual_key =
196 find_snapshot_slot_key(pool, cache, bio, snapshot_root, name)?.ok_or(FsError::NotFound)?;
197 let txn = sb_pair.active().transaction_id;
198 let live_tree_root = sb_pair.active().tree_root;
199 let live_dedup_roots = sb_pair.active().dedup_roots;
200
201 let new_snapshot_root = btree::btree_delete(pool, cache, bio, snapshot_root, actual_key, txn)?
202 .unwrap_or(BlockRef::ZERO);
203
204 transaction::transaction_commit(
205 pool,
206 cache,
207 bio,
208 freemap,
209 reserved,
210 sb_pair,
211 &live_tree_root,
212 &live_dedup_roots,
213 &new_snapshot_root,
214 ditto,
215 scrub_cursor,
216 next_object_id,
217 )?;
218
219 Ok(sb_pair.active().snapshot_root)
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225
226 #[test]
227 fn snapshot_name_key_deterministic() {
228 assert_eq!(snapshot_name_key(b"backup"), snapshot_name_key(b"backup"));
229 }
230
231 #[test]
232 fn snapshot_name_key_different_names_differ() {
233 assert_ne!(snapshot_name_key(b"snap1"), snapshot_name_key(b"snap2"));
234 }
235
236 #[test]
237 fn snapshot_name_key_empty() {
238 let _ = snapshot_name_key(b"");
239 }
240
241 #[test]
242 fn snapshot_name_key_long_name() {
243 let long: Vec<u8> = (0..1024).map(|i| (i & 0xFF) as u8).collect();
244 let key = snapshot_name_key(&long);
245 assert_eq!(key, snapshot_name_key(&long));
246 }
247
248 #[test]
249 fn snapshot_ref_txn_filter() {
250 let snap = SnapshotRef {
251 root_blockref: BlockRef::ZERO,
252 snapshot_txn_id: 42,
253 };
254 assert_eq!(snap.txn_filter(), Some(42));
255 }
256}
257
258#[allow(clippy::too_many_arguments)]
259pub fn writable_snapshot_commit(
260 pool: &mut NodePool,
261 cache: &mut BlockCache,
262 bio: &mut BlockIo,
263 freemap: &mut FreemapAllocator,
264 reserved: &mut FreemapReserved,
265 sb_pair: &mut SuperblockPair,
266 snapshot_root: &BlockRef,
267 name: &[u8],
268 new_snap_tree_root: &BlockRef,
269 snap_txn: u64,
270 ditto: Option<&DittoRegion>,
271 scrub_cursor: u64,
272 next_object_id: u64,
273) -> Result<BlockRef, FsError> {
274 let actual_key =
275 find_snapshot_slot_key(pool, cache, bio, snapshot_root, name)?.ok_or(FsError::NotFound)?;
276 let live_tree_root = sb_pair.active().tree_root;
277 let live_dedup_roots = sb_pair.active().dedup_roots;
278
279 let after_delete = btree::btree_delete(pool, cache, bio, snapshot_root, actual_key, snap_txn)?
280 .unwrap_or(BlockRef::ZERO);
281
282 let updated_entry = BlockRef::new(
283 new_snap_tree_root.physical_block_addr(),
284 new_snap_tree_root
285 .block_size_log2()
286 .unwrap_or(BLOCK_SIZE_MIN_LOG2),
287 actual_key,
288 snap_txn,
289 new_snap_tree_root.content_hash_u128(),
290 new_snap_tree_root.integrity_crc,
291 BlockType::Indirect,
292 Compression::None,
293 name.len() as u32,
294 0,
295 );
296
297 let new_snapshot_root = btree::btree_insert(
298 pool,
299 cache,
300 bio,
301 &after_delete,
302 actual_key,
303 updated_entry,
304 snap_txn,
305 )?;
306
307 transaction::transaction_commit(
308 pool,
309 cache,
310 bio,
311 freemap,
312 reserved,
313 sb_pair,
314 &live_tree_root,
315 &live_dedup_roots,
316 &new_snapshot_root,
317 ditto,
318 scrub_cursor,
319 next_object_id,
320 )?;
321
322 Ok(sb_pair.active().snapshot_root)
323}