Nothing to see here, move along meow
1use crate::block_io::BlockIo;
2use crate::btree;
3use crate::cache::BlockCache;
4use crate::compression;
5use crate::dedup;
6use crate::error::FsError;
7use crate::freemap::FreemapAllocator;
8use crate::integrity;
9use crate::pool::NodePool;
10use lancer_core::fs::{
11 BLOCK_SIZE_MIN, BLOCK_SIZE_MIN_LOG2, BlockRef, BlockType, Compression, DEDUP_SHARDS,
12 INODE_DIRECT_REFS, INODE_INLINE_MAX, INODE_SIZE, Inode, InodeFlags,
13};
14use zerocopy::IntoBytes;
15
16const BLOCK_SIZE: usize = BLOCK_SIZE_MIN as usize;
17const LZ4_HASH_SIZE: usize = 1 << 12;
18
19pub fn cow_inode(
20 cache: &mut BlockCache,
21 bio: &mut BlockIo,
22 freemap: &mut FreemapAllocator,
23 inode: &Inode,
24) -> Result<u64, FsError> {
25 let new_block = freemap.alloc_blocks(cache, bio, 1)?;
26 write_inode_to_cache(cache, bio, new_block, inode)?;
27 Ok(new_block)
28}
29const LZ4_LEN_PREFIX: usize = 4;
30
31pub fn read_inode(
32 cache: &mut BlockCache,
33 bio: &mut BlockIo,
34 block_num: u64,
35) -> Result<Inode, FsError> {
36 let data = cache.cache_read(bio, block_num)?;
37 Ok(unsafe { core::ptr::read_unaligned(data.as_ptr() as *const Inode) })
38}
39
40pub fn write_inode_to_cache(
41 cache: &mut BlockCache,
42 bio: &mut BlockIo,
43 block_num: u64,
44 inode: &Inode,
45) -> Result<(), FsError> {
46 let mut block = [0u8; BLOCK_SIZE];
47 block[..INODE_SIZE].copy_from_slice(inode.as_bytes());
48 cache.cache_write(bio, block_num, &block)?;
49 cache.mark_inode(block_num);
50 Ok(())
51}
52
53pub fn read_inline(inode: &Inode, offset: u64, buf: &mut [u8]) -> usize {
54 match inode.inline_data() {
55 None => 0,
56 Some(data) => {
57 let start = (offset as usize).min(data.len());
58 let available = data.len().saturating_sub(start);
59 let n = available.min(buf.len());
60 buf[..n].copy_from_slice(&data[start..start + n]);
61 n
62 }
63 }
64}
65
66pub fn write_inline(inode: &Inode, offset: u64, data: &[u8]) -> Result<Inode, FsError> {
67 let end = offset as usize + data.len();
68 match end <= INODE_INLINE_MAX {
69 false => Err(FsError::InvalidBlock),
70 true => {
71 let mut updated = inode.clone();
72 updated.direct.as_mut_bytes()[offset as usize..end].copy_from_slice(data);
73 let new_size = (offset + data.len() as u64).max(updated.size);
74 updated.size = new_size;
75 updated.flags |= InodeFlags::INLINE;
76 Ok(updated)
77 }
78 }
79}
80
81#[allow(clippy::too_many_arguments)]
82pub fn migrate_to_blocks(
83 pool: &mut NodePool,
84 cache: &mut BlockCache,
85 bio: &mut BlockIo,
86 freemap: &mut FreemapAllocator,
87 dedup_roots: &mut [BlockRef; DEDUP_SHARDS],
88 inode: &Inode,
89 txn: u64,
90 eff_compression: Compression,
91 scratch: &mut [u8],
92 hash_table: &mut [u16; LZ4_HASH_SIZE],
93) -> Result<Inode, FsError> {
94 let inline_len = inode.size.min(INODE_INLINE_MAX as u64) as usize;
95
96 let mut block_buf = [0u8; BLOCK_SIZE];
97 match inline_len {
98 0 => {}
99 n => {
100 let inline = inode.direct.as_bytes();
101 block_buf[..n].copy_from_slice(&inline[..n]);
102 }
103 }
104
105 let data_ref = write_data_block(
106 pool,
107 cache,
108 bio,
109 freemap,
110 dedup_roots,
111 &block_buf,
112 txn,
113 eff_compression,
114 scratch,
115 hash_table,
116 )?;
117
118 let mut updated = inode.clone();
119 updated.flags &= !InodeFlags::INLINE;
120 updated.direct = [BlockRef::ZERO; INODE_DIRECT_REFS];
121 updated.direct[0] = data_ref;
122 updated.block_count = 1;
123 updated.transaction_id = txn;
124
125 Ok(updated)
126}
127
128fn get_data_block_ref(
129 pool: &mut NodePool,
130 cache: &mut BlockCache,
131 bio: &mut BlockIo,
132 inode: &Inode,
133 block_idx: u64,
134) -> Result<Option<BlockRef>, FsError> {
135 match inode.flags & InodeFlags::INDIRECT != 0 {
136 false => match block_idx < INODE_DIRECT_REFS as u64 {
137 true => Ok(Some(inode.direct[block_idx as usize])),
138 false => Ok(None),
139 },
140 true => {
141 let key = block_idx * BLOCK_SIZE_MIN as u64;
142 btree::btree_lookup(pool, cache, bio, &inode.direct[0], key, None)
143 }
144 }
145}
146
147fn set_data_block_ref(
148 pool: &mut NodePool,
149 cache: &mut BlockCache,
150 bio: &mut BlockIo,
151 inode: &Inode,
152 block_idx: u64,
153 data_ref: BlockRef,
154 txn: u64,
155) -> Result<(Inode, bool), FsError> {
156 match inode.flags & InodeFlags::INDIRECT != 0 {
157 false => match block_idx < INODE_DIRECT_REFS as u64 {
158 true => {
159 let was_empty = inode.direct[block_idx as usize].is_null();
160 let mut updated = inode.clone();
161 updated.direct[block_idx as usize] = data_ref;
162 Ok((updated, was_empty))
163 }
164 false => {
165 let migrated = migrate_direct_to_indirect(pool, cache, bio, inode, txn)?;
166 let key = block_idx * BLOCK_SIZE_MIN as u64;
167 let new_root =
168 btree::btree_insert(pool, cache, bio, &migrated.direct[0], key, data_ref, txn)?;
169 let mut result = migrated;
170 result.direct[0] = new_root;
171 Ok((result, true))
172 }
173 },
174 true => {
175 let key = block_idx * BLOCK_SIZE_MIN as u64;
176 let mut updated = inode.clone();
177 match btree::btree_insert(pool, cache, bio, &updated.direct[0], key, data_ref, txn) {
178 Ok(new_root) => {
179 updated.direct[0] = new_root;
180 Ok((updated, true))
181 }
182 Err(FsError::DuplicateKey) => {
183 let after_delete =
184 btree::btree_delete(pool, cache, bio, &updated.direct[0], key, txn)?;
185 let root_after = after_delete.unwrap_or(updated.direct[0]);
186 let new_root =
187 btree::btree_insert(pool, cache, bio, &root_after, key, data_ref, txn)?;
188 updated.direct[0] = new_root;
189 Ok((updated, false))
190 }
191 Err(e) => Err(e),
192 }
193 }
194 }
195}
196
197fn migrate_direct_to_indirect(
198 pool: &mut NodePool,
199 cache: &mut BlockCache,
200 bio: &mut BlockIo,
201 inode: &Inode,
202 txn: u64,
203) -> Result<Inode, FsError> {
204 let root = (0..INODE_DIRECT_REFS as u64).try_fold(BlockRef::ZERO, |cur_root, i| {
205 let dref = inode.direct[i as usize];
206 match dref.is_null() {
207 true => Ok(cur_root),
208 false => {
209 let key = i * BLOCK_SIZE_MIN as u64;
210 btree::btree_insert(pool, cache, bio, &cur_root, key, dref, txn)
211 }
212 }
213 })?;
214
215 let mut updated = inode.clone();
216 updated.direct = [BlockRef::ZERO; INODE_DIRECT_REFS];
217 updated.direct[0] = root;
218 updated.flags |= InodeFlags::INDIRECT;
219 Ok(updated)
220}
221
222pub fn file_read(
223 pool: &mut NodePool,
224 cache: &mut BlockCache,
225 bio: &mut BlockIo,
226 inode: &Inode,
227 offset: u64,
228 buf: &mut [u8],
229) -> Result<usize, FsError> {
230 match offset >= inode.size {
231 true => Ok(0),
232 false => {
233 let readable = (inode.size - offset).min(buf.len() as u64) as usize;
234
235 match inode.is_inline() {
236 true => Ok(read_inline(inode, offset, &mut buf[..readable])),
237 false => read_blocks(pool, cache, bio, inode, offset, &mut buf[..readable]),
238 }
239 }
240 }
241}
242
243fn read_blocks(
244 pool: &mut NodePool,
245 cache: &mut BlockCache,
246 bio: &mut BlockIo,
247 inode: &Inode,
248 offset: u64,
249 buf: &mut [u8],
250) -> Result<usize, FsError> {
251 let block_size = BLOCK_SIZE_MIN as u64;
252 let first_block = offset / block_size;
253 let end_byte = offset + buf.len() as u64;
254 let last_block = match end_byte {
255 0 => return Ok(0),
256 n => (n - 1) / block_size,
257 };
258 let num_blocks = (last_block - first_block + 1) as usize;
259
260 (0..num_blocks).try_fold(0usize, |written, chunk_idx| {
261 let block_idx = first_block + chunk_idx as u64;
262 let block_start = block_idx * block_size;
263 let in_block_off = match block_idx == first_block {
264 true => (offset - block_start) as usize,
265 false => 0,
266 };
267 let in_block_end = match block_idx == last_block {
268 true => (end_byte - block_start) as usize,
269 false => BLOCK_SIZE,
270 };
271 let copy_len = in_block_end - in_block_off;
272
273 let block_ref = get_data_block_ref(pool, cache, bio, inode, block_idx)?;
274 match block_ref {
275 Some(r) if !r.is_null() => {
276 let block_num = crate::blockref_block_num(&r);
277 let raw = cache.cache_read(bio, block_num)?;
278
279 if r.integrity_crc != 0 && !integrity::verify_block_crc(raw, r.integrity_crc) {
280 return Err(FsError::IntegrityFailure);
281 }
282
283 let comp = r.compression_enum().unwrap_or(Compression::None);
284
285 match comp {
286 Compression::None => {
287 buf[written..written + copy_len]
288 .copy_from_slice(&raw[in_block_off..in_block_end]);
289 }
290 Compression::Lz4 => {
291 let clen = u32::from_le_bytes([raw[0], raw[1], raw[2], raw[3]]) as usize;
292 let compressed_end = (LZ4_LEN_PREFIX + clen).min(BLOCK_SIZE);
293 let mut decomp = [0u8; BLOCK_SIZE];
294 let _len = compression::decompress_block(
295 &raw[LZ4_LEN_PREFIX..compressed_end],
296 comp,
297 r.logical_size,
298 &mut decomp,
299 )?;
300 buf[written..written + copy_len]
301 .copy_from_slice(&decomp[in_block_off..in_block_end]);
302 }
303 }
304
305 Ok(written + copy_len)
306 }
307 _ => {
308 buf[written..written + copy_len].fill(0);
309 Ok(written + copy_len)
310 }
311 }
312 })
313}
314
315#[allow(clippy::too_many_arguments)]
316pub fn file_write(
317 pool: &mut NodePool,
318 cache: &mut BlockCache,
319 bio: &mut BlockIo,
320 freemap: &mut FreemapAllocator,
321 dedup_roots: &[BlockRef; DEDUP_SHARDS],
322 inode: &Inode,
323 offset: u64,
324 data: &[u8],
325 txn: u64,
326 eff_compression: Compression,
327 scratch: &mut [u8],
328 hash_table: &mut [u16; LZ4_HASH_SIZE],
329) -> Result<(Inode, usize, [BlockRef; DEDUP_SHARDS]), FsError> {
330 if data.is_empty() {
331 return Ok((inode.clone(), 0, *dedup_roots));
332 }
333
334 if inode.is_inline() && offset + data.len() as u64 <= INODE_INLINE_MAX as u64 {
335 return write_inline(inode, offset, data).map(|i| (i, data.len(), *dedup_roots));
336 }
337
338 let mut dedup_buf = *dedup_roots;
339
340 let cur_inode = match inode.is_inline() {
341 true => migrate_to_blocks(
342 pool,
343 cache,
344 bio,
345 freemap,
346 &mut dedup_buf,
347 inode,
348 txn,
349 eff_compression,
350 scratch,
351 hash_table,
352 )?,
353 false => inode.clone(),
354 };
355
356 let block_size = BLOCK_SIZE_MIN as u64;
357 let first_block = offset / block_size;
358 let end_byte = offset + data.len() as u64;
359 let last_block = (end_byte - 1) / block_size;
360 let num_blocks = (last_block - first_block + 1) as usize;
361
362 let (final_inode, written, new_blocks) = (0..num_blocks).try_fold(
363 (cur_inode, 0usize, 0u64),
364 |(inode_acc, written_acc, added_blocks), chunk_idx| -> Result<_, FsError> {
365 let block_idx = first_block + chunk_idx as u64;
366 let block_start = block_idx * block_size;
367 let in_block_off = match block_idx == first_block {
368 true => (offset - block_start) as usize,
369 false => 0,
370 };
371 let in_block_end = match block_idx == last_block {
372 true => (end_byte - block_start) as usize,
373 false => BLOCK_SIZE,
374 };
375 let data_start = (block_start + in_block_off as u64 - offset) as usize;
376 let data_end = (data_start + in_block_end - in_block_off).min(data.len());
377 let chunk_data = &data[data_start..data_end];
378
379 let mut block_buf = [0u8; BLOCK_SIZE];
380 let is_partial = in_block_off != 0 || in_block_end != BLOCK_SIZE;
381
382 match is_partial {
383 true => {
384 let existing = get_data_block_ref(pool, cache, bio, &inode_acc, block_idx)?;
385 match existing {
386 Some(r) if !r.is_null() => {
387 let raw = cache.cache_read(bio, crate::blockref_block_num(&r))?;
388 let existing_comp = r.compression_enum().unwrap_or(Compression::None);
389 match existing_comp {
390 Compression::None => block_buf.copy_from_slice(raw),
391 Compression::Lz4 => {
392 let clen = u32::from_le_bytes([raw[0], raw[1], raw[2], raw[3]])
393 as usize;
394 let end = (LZ4_LEN_PREFIX + clen).min(BLOCK_SIZE);
395 compression::decompress_block(
396 &raw[LZ4_LEN_PREFIX..end],
397 existing_comp,
398 r.logical_size,
399 &mut block_buf,
400 )?;
401 }
402 }
403 }
404 _ => {}
405 }
406 block_buf[in_block_off..in_block_off + chunk_data.len()]
407 .copy_from_slice(chunk_data);
408 }
409 false => {
410 block_buf[..chunk_data.len()].copy_from_slice(chunk_data);
411 }
412 }
413
414 let data_ref = write_data_block(
415 pool,
416 cache,
417 bio,
418 freemap,
419 &mut dedup_buf,
420 &block_buf,
421 txn,
422 eff_compression,
423 scratch,
424 hash_table,
425 )?;
426
427 let (updated, is_new) =
428 set_data_block_ref(pool, cache, bio, &inode_acc, block_idx, data_ref, txn)?;
429 let added = added_blocks
430 + match is_new {
431 true => 1,
432 false => 0,
433 };
434 Ok((updated, written_acc + chunk_data.len(), added))
435 },
436 )?;
437
438 let mut result = final_inode;
439 let new_end = offset + data.len() as u64;
440 if new_end > result.size {
441 result.size = new_end;
442 }
443 result.transaction_id = txn;
444 result.block_count += new_blocks;
445
446 Ok((result, written, dedup_buf))
447}
448
449#[cfg(test)]
450mod tests {
451 use super::*;
452
453 fn make_inline_inode(data: &[u8]) -> Inode {
454 let mut inode = Inode::new_file(1, 1, 1, 0, 0);
455 inode.flags |= InodeFlags::INLINE;
456 inode.size = data.len() as u64;
457 inode.direct.as_mut_bytes()[..data.len()].copy_from_slice(data);
458 inode
459 }
460
461 #[test]
462 fn read_inline_from_start() {
463 let inode = make_inline_inode(b"hello world");
464 let mut buf = [0u8; 32];
465 let n = read_inline(&inode, 0, &mut buf);
466 assert_eq!(n, 11);
467 assert_eq!(&buf[..n], b"hello world");
468 }
469
470 #[test]
471 fn read_inline_with_offset() {
472 let inode = make_inline_inode(b"hello world");
473 let mut buf = [0u8; 32];
474 let n = read_inline(&inode, 6, &mut buf);
475 assert_eq!(n, 5);
476 assert_eq!(&buf[..n], b"world");
477 }
478
479 #[test]
480 fn read_inline_offset_past_end() {
481 let inode = make_inline_inode(b"short");
482 let mut buf = [0u8; 32];
483 let n = read_inline(&inode, 100, &mut buf);
484 assert_eq!(n, 0);
485 }
486
487 #[test]
488 fn read_inline_small_buffer() {
489 let inode = make_inline_inode(b"hello world");
490 let mut buf = [0u8; 3];
491 let n = read_inline(&inode, 0, &mut buf);
492 assert_eq!(n, 3);
493 assert_eq!(&buf[..n], b"hel");
494 }
495
496 #[test]
497 fn read_inline_non_inline_inode_returns_zero() {
498 let inode = Inode::new_file(1, 1, 1, 0, 0);
499 let mut buf = [0u8; 32];
500 assert_eq!(read_inline(&inode, 0, &mut buf), 0);
501 }
502
503 #[test]
504 fn write_inline_basic() {
505 let inode = Inode::new_file(1, 1, 1, 0, 0);
506 let updated = write_inline(&inode, 0, b"test data").unwrap();
507 assert!(updated.is_inline());
508 assert_eq!(updated.size, 9);
509
510 let mut buf = [0u8; 32];
511 let n = read_inline(&updated, 0, &mut buf);
512 assert_eq!(&buf[..n], b"test data");
513 }
514
515 #[test]
516 fn write_inline_at_offset() {
517 let inode = make_inline_inode(b"hello world");
518 let updated = write_inline(&inode, 6, b"WORLD").unwrap();
519 assert_eq!(updated.size, 11);
520
521 let mut buf = [0u8; 32];
522 let n = read_inline(&updated, 0, &mut buf);
523 assert_eq!(&buf[..n], b"hello WORLD");
524 }
525
526 #[test]
527 fn write_inline_extends_size() {
528 let inode = make_inline_inode(b"hi");
529 let updated = write_inline(&inode, 2, b" there").unwrap();
530 assert_eq!(updated.size, 8);
531 }
532
533 #[test]
534 fn write_inline_exceeds_max_returns_error() {
535 let inode = Inode::new_file(1, 1, 1, 0, 0);
536 let big_data = [0u8; INODE_INLINE_MAX + 1];
537 assert!(write_inline(&inode, 0, &big_data).is_err());
538 }
539
540 #[test]
541 fn write_inline_at_max_boundary() {
542 let inode = Inode::new_file(1, 1, 1, 0, 0);
543 let data = [0xAB_u8; INODE_INLINE_MAX];
544 let updated = write_inline(&inode, 0, &data).unwrap();
545 assert!(updated.is_inline());
546 assert_eq!(updated.size, INODE_INLINE_MAX as u64);
547 }
548
549 #[test]
550 fn file_read_past_eof_returns_zero() {
551 let mut pool = crate::test_helpers::make_pool();
552 let mut cache = crate::test_helpers::make_cache();
553 let mut bio = crate::test_helpers::make_bio(32);
554
555 let inode = make_inline_inode(b"data");
556 let mut buf = [0u8; 32];
557 let n = file_read(&mut pool, &mut cache, &mut bio, &inode, 100, &mut buf).unwrap();
558 assert_eq!(n, 0);
559 }
560
561 #[test]
562 fn file_read_inline_via_file_read() {
563 let mut pool = crate::test_helpers::make_pool();
564 let mut cache = crate::test_helpers::make_cache();
565 let mut bio = crate::test_helpers::make_bio(32);
566
567 let inode = make_inline_inode(b"inline content");
568 let mut buf = [0u8; 64];
569 let n = file_read(&mut pool, &mut cache, &mut bio, &inode, 0, &mut buf).unwrap();
570 assert_eq!(n, 14);
571 assert_eq!(&buf[..n], b"inline content");
572 }
573
574 #[test]
575 fn file_read_inline_partial() {
576 let mut pool = crate::test_helpers::make_pool();
577 let mut cache = crate::test_helpers::make_cache();
578 let mut bio = crate::test_helpers::make_bio(32);
579
580 let inode = make_inline_inode(b"hello");
581 let mut buf = [0u8; 3];
582 let n = file_read(&mut pool, &mut cache, &mut bio, &inode, 0, &mut buf).unwrap();
583 assert_eq!(n, 3);
584 assert_eq!(&buf[..n], b"hel");
585 }
586
587 #[test]
588 fn file_write_inline_via_file_write() {
589 let mut pool = crate::test_helpers::make_pool();
590 let mut cache = crate::test_helpers::make_cache();
591 let mut bio = crate::test_helpers::make_bio(256);
592
593 let inode = make_inline_inode(b"");
594 let mut scratch = [0u8; 8192];
595 let mut hash_table = [0u16; 1 << 12];
596 let dedup_roots = [BlockRef::ZERO; DEDUP_SHARDS];
597
598 let (updated, written, _) = file_write(
599 &mut pool,
600 &mut cache,
601 &mut bio,
602 &mut crate::freemap::FreemapAllocator::init_empty(),
603 &dedup_roots,
604 &inode,
605 0,
606 b"small data",
607 1,
608 Compression::None,
609 &mut scratch,
610 &mut hash_table,
611 )
612 .unwrap();
613
614 assert_eq!(written, 10);
615 assert!(updated.is_inline());
616 assert_eq!(updated.size, 10);
617
618 let mut buf = [0u8; 32];
619 let n = read_inline(&updated, 0, &mut buf);
620 assert_eq!(&buf[..n], b"small data");
621 }
622
623 #[test]
624 fn file_write_empty_data_noop() {
625 let mut pool = crate::test_helpers::make_pool();
626 let mut cache = crate::test_helpers::make_cache();
627 let mut bio = crate::test_helpers::make_bio(32);
628
629 let inode = Inode::new_file(1, 1, 1, 0, 0);
630 let mut scratch = [0u8; 8192];
631 let mut hash_table = [0u16; 1 << 12];
632 let dedup_roots = [BlockRef::ZERO; DEDUP_SHARDS];
633
634 let (updated, written, _) = file_write(
635 &mut pool,
636 &mut cache,
637 &mut bio,
638 &mut crate::freemap::FreemapAllocator::init_empty(),
639 &dedup_roots,
640 &inode,
641 0,
642 &[],
643 1,
644 Compression::None,
645 &mut scratch,
646 &mut hash_table,
647 )
648 .unwrap();
649
650 assert_eq!(written, 0);
651 assert_eq!(updated.size, 0);
652 }
653}
654
655#[allow(clippy::too_many_arguments)]
656fn write_data_block(
657 pool: &mut NodePool,
658 cache: &mut BlockCache,
659 bio: &mut BlockIo,
660 freemap: &mut FreemapAllocator,
661 dedup_roots: &mut [BlockRef; DEDUP_SHARDS],
662 block_data: &[u8; BLOCK_SIZE],
663 txn: u64,
664 eff_compression: Compression,
665 scratch: &mut [u8],
666 hash_table: &mut [u16; LZ4_HASH_SIZE],
667) -> Result<BlockRef, FsError> {
668 let content_hash = integrity::xxhash128(block_data);
669 let shard = dedup::dedup_shard(content_hash);
670
671 match dedup::dedup_check(pool, cache, bio, &dedup_roots[shard], content_hash)? {
672 dedup::DedupResult::Reused(existing) => Ok(existing),
673 dedup::DedupResult::Unique => {
674 let comp =
675 compression::compress_block(block_data, eff_compression, scratch, hash_table);
676
677 let block_num = freemap.alloc_blocks(cache, bio, 1)?;
678
679 let mut write_buf = [0u8; BLOCK_SIZE];
680 match comp.algorithm {
681 Compression::None => write_buf.copy_from_slice(block_data),
682 Compression::Lz4 => {
683 let len_bytes = (comp.compressed_len as u32).to_le_bytes();
684 write_buf[..LZ4_LEN_PREFIX].copy_from_slice(&len_bytes);
685 write_buf[LZ4_LEN_PREFIX..LZ4_LEN_PREFIX + comp.compressed_len]
686 .copy_from_slice(&scratch[..comp.compressed_len]);
687 }
688 }
689
690 let on_disk_crc = integrity::crc32c(&write_buf);
691 cache.cache_write(bio, block_num, &write_buf)?;
692
693 let phys_addr = crate::block_num_to_phys(block_num);
694 let data_ref = BlockRef::new(
695 phys_addr,
696 BLOCK_SIZE_MIN_LOG2,
697 0,
698 txn,
699 content_hash,
700 on_disk_crc,
701 BlockType::Data,
702 comp.algorithm,
703 comp.logical_size,
704 0,
705 );
706
707 let new_shard_root = dedup::dedup_insert(
708 pool,
709 cache,
710 bio,
711 &dedup_roots[shard],
712 content_hash,
713 &data_ref,
714 txn,
715 )?;
716 dedup_roots[shard] = new_shard_root;
717
718 Ok(data_ref)
719 }
720 }
721}