Nothing to see here, move along meow
1use lancer_lancerfs::integrity::{self, ReadHealOutcome};
2use lancer_lancerfs::test_helpers::setup_fs;
3
4#[test]
5fn data_block_corruption_detected_on_read() {
6 let mut fs = setup_fs(4096);
7 let root = fs.root_block();
8 let (_inode, block) = fs.create_file(root, b"corrupt_me");
9
10 let data: Vec<u8> = (0..4096u16).map(|i| (i % 251) as u8).collect();
11 fs.write_file(block, 0, &data);
12 fs.commit();
13
14 let inode = fs.read_inode(block);
15 let phys = lancer_lancerfs::blockref_block_num(&inode.direct[0]);
16
17 let raw = fs.bio.raw_block_mut(phys).unwrap();
18 raw[0] ^= 0xFF;
19 raw[1] ^= 0xFF;
20
21 fs.cache = lancer_lancerfs::test_helpers::make_cache();
22
23 let result = fs.read_file_result(block, 0, 4096);
24 assert!(
25 result.is_err(),
26 "reading a corrupted data block must return IntegrityFailure"
27 );
28}
29
30#[test]
31fn metadata_healing_repairs_from_ditto_on_committed_block() {
32 let mut fs = setup_fs(16384);
33 let root = fs.root_block();
34
35 (0..10u32).for_each(|i| {
36 let name = format!("f_{:03}", i).into_bytes();
37 let (_inode, block) = fs.create_file(root, &name);
38 let data = vec![(i & 0xFF) as u8; 128];
39 fs.write_file(block, 0, &data);
40 });
41 fs.commit();
42
43 let tree_root = fs.state.sb_pair.active().tree_root;
44 let tree_root_block = lancer_lancerfs::blockref_block_num(&tree_root);
45 let expected_crc = tree_root.integrity_crc;
46
47 let ditto_block = match fs.state.ditto.ditto_addr(tree_root_block) {
48 Some(d) => d,
49 None => {
50 eprintln!(
51 "tree root block {} has no ditto mapping, skipping healing test",
52 tree_root_block
53 );
54 return;
55 }
56 };
57
58 let ditto_crc = {
59 let mut buf = [0u8; 4096];
60 fs.bio.read_blocks(ditto_block, 1, &mut buf).unwrap();
61 integrity::crc32c(&buf)
62 };
63
64 match ditto_crc == expected_crc {
65 true => {}
66 false => {
67 eprintln!(
68 "ditto copy was overwritten (collision), skipping healing test. \
69 primary crc={expected_crc}, ditto crc={ditto_crc}"
70 );
71 return;
72 }
73 }
74
75 let raw = fs.bio.raw_block_mut(tree_root_block).unwrap();
76 raw[0] ^= 0xFF;
77 raw[100] ^= 0xFF;
78 raw[200] ^= 0xFF;
79
80 fs.cache = lancer_lancerfs::test_helpers::make_cache();
81
82 let result = integrity::metadata_read_healing(
83 &mut fs.cache,
84 &mut fs.bio,
85 &fs.state.ditto,
86 tree_root_block,
87 expected_crc,
88 );
89
90 match result {
91 Ok(ReadHealOutcome::Repaired) => {}
92 Ok(ReadHealOutcome::Clean) => panic!("expected Repaired, got Clean"),
93 Err(_) => panic!("expected Repaired, got Err"),
94 }
95
96 let mut healed_buf = [0u8; 4096];
97 fs.bio
98 .read_blocks(tree_root_block, 1, &mut healed_buf)
99 .unwrap();
100 let healed_crc = integrity::crc32c(&healed_buf);
101 assert_eq!(
102 healed_crc, expected_crc,
103 "primary block must have correct CRC after healing"
104 );
105
106 fs.cache = lancer_lancerfs::test_helpers::make_cache();
107 (0..10u32).for_each(|i| {
108 let name = format!("f_{:03}", i).into_bytes();
109 let file_block = fs
110 .lookup_file(root, &name)
111 .unwrap_or_else(|| panic!("file {} must be readable after healing", i));
112 let data = fs.read_file(file_block, 0, 128);
113 assert_eq!(
114 data,
115 vec![(i & 0xFF) as u8; 128],
116 "file {} data must be correct after metadata healing",
117 i
118 );
119 });
120}
121
122#[test]
123fn both_copies_corrupt_returns_error() {
124 let mut fs = setup_fs(16384);
125 let root = fs.root_block();
126
127 (0..10u32).for_each(|i| {
128 let name = format!("g_{:03}", i).into_bytes();
129 fs.create_file(root, &name);
130 });
131 fs.commit();
132
133 let tree_root = fs.state.sb_pair.active().tree_root;
134 let tree_root_block = lancer_lancerfs::blockref_block_num(&tree_root);
135 let expected_crc = tree_root.integrity_crc;
136 let ditto_block = match fs.state.ditto.ditto_addr(tree_root_block) {
137 Some(d) => d,
138 None => {
139 eprintln!("tree root block has no ditto mapping, skipping");
140 return;
141 }
142 };
143
144 let raw_primary = fs.bio.raw_block_mut(tree_root_block).unwrap();
145 raw_primary[0] ^= 0xFF;
146
147 let raw_ditto = fs.bio.raw_block_mut(ditto_block).unwrap();
148 raw_ditto[0] ^= 0xFF;
149
150 fs.cache = lancer_lancerfs::test_helpers::make_cache();
151
152 let result = integrity::metadata_read_healing(
153 &mut fs.cache,
154 &mut fs.bio,
155 &fs.state.ditto,
156 tree_root_block,
157 expected_crc,
158 );
159
160 assert!(result.is_err(), "both copies corrupt must return error");
161}
162
163#[test]
164fn verify_path_detects_corruption() {
165 let mut fs = setup_fs(8192);
166 let root = fs.root_block();
167
168 (0..5u32).for_each(|i| {
169 let name = format!("vp_{:03}", i).into_bytes();
170 fs.create_file(root, &name);
171 });
172 fs.commit();
173
174 let tree_root = fs.state.sb_pair.active().tree_root;
175
176 let clean_result = integrity::verify_path(&mut fs.cache, &mut fs.bio, &tree_root, 1);
177 assert!(
178 clean_result.is_ok(),
179 "clean tree path must verify successfully"
180 );
181
182 let tree_root_block = lancer_lancerfs::blockref_block_num(&tree_root);
183 let raw = fs.bio.raw_block_mut(tree_root_block).unwrap();
184 raw[0] ^= 0xFF;
185
186 fs.cache = lancer_lancerfs::test_helpers::make_cache();
187
188 let corrupt_result = integrity::verify_path(&mut fs.cache, &mut fs.bio, &tree_root, 1);
189 assert!(
190 corrupt_result.is_err(),
191 "corrupted tree root must fail path verification"
192 );
193}