Skip to main content

btrfs_disk/
util.rs

1//! # Shared helpers for on-disk structures
2//!
3//! Little-endian writer functions for placing typed values into raw byte
4//! buffers at known offsets, and a raw CRC32C matching the kernel's format.
5
6use crate::{superblock::ChecksumType, tree::DiskKey};
7use bytes::{Buf, BufMut};
8use uuid::Uuid;
9
10/// Read a UUID (16 bytes) from a `Buf`, advancing the cursor.
11///
12/// # Panics
13///
14/// Panics if `buf` has fewer than 16 bytes remaining.
15pub fn get_uuid(buf: &mut &[u8]) -> Uuid {
16    let bytes: [u8; 16] = buf[..16].try_into().unwrap();
17    buf.advance(16);
18    Uuid::from_bytes(bytes)
19}
20
21/// Write a `DiskKey` (17 bytes) into `buf` at byte offset `off`.
22pub fn write_disk_key(buf: &mut [u8], off: usize, key: &DiskKey) {
23    (&mut buf[off..off + 8]).put_u64_le(key.objectid);
24    buf[off + 8] = key.key_type.to_raw();
25    (&mut buf[off + 9..off + 17]).put_u64_le(key.offset);
26}
27
28/// Write a little-endian u64 into `buf` at byte offset `off`.
29pub fn write_le_u64(buf: &mut [u8], off: usize, val: u64) {
30    buf[off..off + 8].copy_from_slice(&val.to_le_bytes());
31}
32
33/// Write a little-endian u32 into `buf` at byte offset `off`.
34pub fn write_le_u32(buf: &mut [u8], off: usize, val: u32) {
35    buf[off..off + 4].copy_from_slice(&val.to_le_bytes());
36}
37
38/// Write a little-endian u16 into `buf` at byte offset `off`.
39pub fn write_le_u16(buf: &mut [u8], off: usize, val: u16) {
40    buf[off..off + 2].copy_from_slice(&val.to_le_bytes());
41}
42
43/// Write a UUID (16 bytes) into `buf` at byte offset `off`.
44pub fn write_uuid(buf: &mut [u8], off: usize, uuid: &Uuid) {
45    buf[off..off + 16].copy_from_slice(uuid.as_bytes());
46}
47
48/// Raw CRC32C matching the kernel's `crc32c_le()` function.
49///
50/// The seed is passed through directly with no inversion on input or output,
51/// unlike the standard ISO 3309 CRC32C which inverts both. This is NOT the
52/// function used for on-disk checksums (superblocks, tree blocks, data csums).
53/// Use this only for internal hash computations like `extent_data_ref_hash`
54/// where the C code calls `crc32c(seed, data, len)` (which maps to
55/// `crc32c_le`).
56#[must_use]
57pub fn raw_crc32c(seed: u32, data: &[u8]) -> u32 {
58    // crc32c::crc32c_append(seed) computes: !crc32c_hw(!seed, data)
59    // We want: crc32c_hw(seed, data)
60    // So: !crc32c::crc32c_append(!seed, data)
61    !crc32c::crc32c_append(!seed, data)
62}
63
64/// Btrfs name hash for `DIR_ITEM` and `DIR_INDEX` key offsets.
65///
66/// Raw CRC32C with seed `0xFFFFFFFE` (`~1`) and no finalization XOR.
67/// This matches the kernel's `btrfs_name_hash()`.
68#[must_use]
69pub fn btrfs_name_hash(name: &[u8]) -> u32 {
70    raw_crc32c(!1u32, name)
71}
72
73/// Standard CRC32C matching the kernel's `hash_crc32c()` / btrfs on-disk
74/// checksum format.
75///
76/// This is the function used for all on-disk checksums: superblocks, tree
77/// blocks, and data checksums. The kernel computes these via `hash_crc32c`
78/// which calls `crc32c_le(~0, data, len)` and then inverts the result,
79/// which is equivalent to standard ISO 3309 CRC32C.
80#[must_use]
81pub fn btrfs_csum_data(data: &[u8]) -> u32 {
82    crc32c::crc32c(data)
83}
84
85/// Recompute the checksum of a tree block and write it into the header.
86///
87/// The checksum covers `buf[32..]` (everything after the csum field) and is
88/// computed using `csum_type`. The result fills the first
89/// `csum_type.size()` bytes of `buf` and the remainder of the 32-byte
90/// csum field is zeroed.
91///
92/// # Panics
93///
94/// Panics if `buf` is 32 bytes or smaller, or if `csum_type` is
95/// [`ChecksumType::Unknown`].
96pub fn csum_tree_block(buf: &mut [u8], csum_type: ChecksumType) {
97    assert!(buf.len() > 32, "buffer too small for tree block checksum");
98    let hash = csum_type.compute(&buf[32..]);
99    let n = csum_type.size();
100    buf[0..n].copy_from_slice(&hash[..n]);
101    buf[n..32].fill(0);
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn test_write_le_u64() {
110        let mut buf = [0u8; 8];
111        write_le_u64(&mut buf, 0, 0x0807060504030201);
112        assert_eq!(buf, [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]);
113    }
114
115    #[test]
116    fn test_write_le_u32() {
117        let mut buf = [0u8; 4];
118        write_le_u32(&mut buf, 0, 0x04030201);
119        assert_eq!(buf, [0x01, 0x02, 0x03, 0x04]);
120    }
121
122    #[test]
123    fn test_write_le_u16() {
124        let mut buf = [0u8; 2];
125        write_le_u16(&mut buf, 0, 0x0201);
126        assert_eq!(buf, [0x01, 0x02]);
127    }
128
129    #[test]
130    fn test_write_uuid() {
131        let uuid =
132            Uuid::parse_str("deadbeef-dead-beef-dead-beefdeadbeef").unwrap();
133        let mut buf = [0u8; 16];
134        write_uuid(&mut buf, 0, &uuid);
135        assert_eq!(buf, *uuid.as_bytes());
136    }
137
138    #[test]
139    fn test_roundtrip_u64() {
140        let mut buf = [0u8; 16];
141        write_le_u64(&mut buf, 4, 0xDEADBEEF_CAFEBABE);
142        assert_eq!(
143            u64::from_le_bytes(buf[4..12].try_into().unwrap()),
144            0xDEADBEEF_CAFEBABE
145        );
146    }
147
148    #[test]
149    fn test_btrfs_name_hash() {
150        // Verified against dump-tree output from a real btrfs filesystem
151        assert_eq!(btrfs_name_hash(b"hello.txt"), 0x415f_eb59);
152        // Different names produce different hashes
153        assert_ne!(
154            btrfs_name_hash(b"hello.txt"),
155            btrfs_name_hash(b"world.txt")
156        );
157    }
158
159    #[test]
160    fn test_roundtrip_uuid() {
161        let uuid =
162            Uuid::parse_str("01234567-89ab-cdef-0123-456789abcdef").unwrap();
163        let mut buf = [0u8; 16];
164        write_uuid(&mut buf, 0, &uuid);
165        assert_eq!(Uuid::from_bytes(buf), uuid);
166    }
167}