1use crate::error::{FsError, FsResult};
2use rand::RngCore;
3use std::collections::HashMap;
4use std::fs::{File, OpenOptions};
5use std::io::{Seek, SeekFrom, Write};
6use std::os::unix::fs::FileExt;
7use std::sync::Mutex;
8
9pub trait BlockStore: Send + Sync {
12 fn block_size(&self) -> usize;
14
15 fn total_blocks(&self) -> u64;
17
18 fn read_block(&self, block_id: u64) -> FsResult<Vec<u8>>;
20
21 fn write_block(&self, block_id: u64, data: &[u8]) -> FsResult<()>;
23
24 fn sync(&self) -> FsResult<()> {
26 Ok(())
27 }
28
29 fn read_blocks(&self, block_ids: &[u64]) -> FsResult<Vec<Vec<u8>>> {
34 block_ids.iter().map(|&id| self.read_block(id)).collect()
35 }
36
37 fn write_blocks(&self, blocks: &[(u64, &[u8])]) -> FsResult<()> {
42 for &(id, data) in blocks {
43 self.write_block(id, data)?;
44 }
45 Ok(())
46 }
47}
48
49pub struct MemoryBlockStore {
51 block_size: usize,
52 total_blocks: u64,
53 blocks: Mutex<HashMap<u64, Vec<u8>>>,
54}
55
56impl MemoryBlockStore {
57 pub fn new(block_size: usize, total_blocks: u64) -> Self {
58 Self {
59 block_size,
60 total_blocks,
61 blocks: Mutex::new(HashMap::new()),
62 }
63 }
64}
65
66impl BlockStore for MemoryBlockStore {
67 fn block_size(&self) -> usize {
68 self.block_size
69 }
70
71 fn total_blocks(&self) -> u64 {
72 self.total_blocks
73 }
74
75 fn read_block(&self, block_id: u64) -> FsResult<Vec<u8>> {
76 if block_id >= self.total_blocks {
77 return Err(FsError::BlockOutOfRange(block_id));
78 }
79 let blocks = self
80 .blocks
81 .lock()
82 .map_err(|e| FsError::Internal(e.to_string()))?;
83 match blocks.get(&block_id) {
84 Some(data) => Ok(data.clone()),
85 None => {
86 Ok(vec![0u8; self.block_size])
88 }
89 }
90 }
91
92 fn write_block(&self, block_id: u64, data: &[u8]) -> FsResult<()> {
93 if block_id >= self.total_blocks {
94 return Err(FsError::BlockOutOfRange(block_id));
95 }
96 if data.len() != self.block_size {
97 return Err(FsError::BlockSizeMismatch {
98 expected: self.block_size,
99 got: data.len(),
100 });
101 }
102 let mut blocks = self
103 .blocks
104 .lock()
105 .map_err(|e| FsError::Internal(e.to_string()))?;
106 blocks.insert(block_id, data.to_vec());
107 Ok(())
108 }
109}
110
111pub struct DiskBlockStore {
116 file: File,
117 block_size: usize,
118 total_blocks: u64,
119}
120
121impl DiskBlockStore {
122 pub fn open(path: &str, block_size: usize, total_blocks: u64) -> FsResult<Self> {
127 let file = OpenOptions::new()
128 .read(true)
129 .write(true)
130 .open(path)
131 .map_err(|e| FsError::Internal(format!("open {path}: {e}")))?;
132
133 let file_len = file
134 .metadata()
135 .map_err(|e| FsError::Internal(format!("stat {path}: {e}")))?
136 .len();
137
138 let total_blocks = if total_blocks == 0 {
139 file_len / block_size as u64
140 } else {
141 total_blocks
142 };
143
144 let required = total_blocks * block_size as u64;
145 if file_len < required {
146 return Err(FsError::Internal(format!(
147 "file too small: {file_len} bytes, need {required}"
148 )));
149 }
150
151 Ok(Self {
152 file,
153 block_size,
154 total_blocks,
155 })
156 }
157
158 pub fn create(path: &str, block_size: usize, total_blocks: u64) -> FsResult<Self> {
163 let mut file = OpenOptions::new()
164 .read(true)
165 .write(true)
166 .create_new(true)
167 .open(path)
168 .map_err(|e| FsError::Internal(format!("create {path}: {e}")))?;
169
170 let mut rng = rand::thread_rng();
172 let mut buf = vec![0u8; block_size];
173 for _ in 0..total_blocks {
174 rng.fill_bytes(&mut buf);
175 file.write_all(&buf)
176 .map_err(|e| FsError::Internal(format!("write {path}: {e}")))?;
177 }
178 file.sync_all()
179 .map_err(|e| FsError::Internal(format!("sync {path}: {e}")))?;
180
181 Ok(Self {
182 file,
183 block_size,
184 total_blocks,
185 })
186 }
187}
188
189impl BlockStore for DiskBlockStore {
190 fn block_size(&self) -> usize {
191 self.block_size
192 }
193
194 fn total_blocks(&self) -> u64 {
195 self.total_blocks
196 }
197
198 fn read_block(&self, block_id: u64) -> FsResult<Vec<u8>> {
199 if block_id >= self.total_blocks {
200 return Err(FsError::BlockOutOfRange(block_id));
201 }
202 let offset = block_id * self.block_size as u64;
203 let mut buf = vec![0u8; self.block_size];
204 self.file
205 .read_exact_at(&mut buf, offset)
206 .map_err(|e| FsError::Internal(format!("read block {block_id}: {e}")))?;
207 Ok(buf)
208 }
209
210 fn write_block(&self, block_id: u64, data: &[u8]) -> FsResult<()> {
211 if block_id >= self.total_blocks {
212 return Err(FsError::BlockOutOfRange(block_id));
213 }
214 if data.len() != self.block_size {
215 return Err(FsError::BlockSizeMismatch {
216 expected: self.block_size,
217 got: data.len(),
218 });
219 }
220 let offset = block_id * self.block_size as u64;
221 self.file
222 .write_all_at(data, offset)
223 .map_err(|e| FsError::Internal(format!("write block {block_id}: {e}")))?;
224 Ok(())
225 }
226
227 fn sync(&self) -> FsResult<()> {
228 self.file
229 .sync_all()
230 .map_err(|e| FsError::Internal(format!("fsync: {e}")))
231 }
232}
233
234pub struct DeviceBlockStore {
245 file: File,
246 block_size: usize,
247 total_blocks: u64,
248}
249
250impl DeviceBlockStore {
251 pub fn open(path: &str, block_size: usize, total_blocks: u64) -> FsResult<Self> {
255 let mut file = OpenOptions::new()
256 .read(true)
257 .write(true)
258 .open(path)
259 .map_err(|e| FsError::Internal(format!("open device {path}: {e}")))?;
260
261 let device_size = file
262 .seek(SeekFrom::End(0))
263 .map_err(|e| FsError::Internal(format!("seek device {path}: {e}")))?;
264
265 let total_blocks = if total_blocks == 0 {
266 device_size / block_size as u64
267 } else {
268 total_blocks
269 };
270
271 let required = total_blocks * block_size as u64;
272 if device_size < required {
273 return Err(FsError::Internal(format!(
274 "device too small: {device_size} bytes, need {required}"
275 )));
276 }
277
278 Ok(Self {
279 file,
280 block_size,
281 total_blocks,
282 })
283 }
284
285 pub fn initialize(path: &str, block_size: usize, total_blocks: u64) -> FsResult<Self> {
293 let mut file = OpenOptions::new()
294 .read(true)
295 .write(true)
296 .open(path)
297 .map_err(|e| FsError::Internal(format!("open device {path}: {e}")))?;
298
299 let device_size = file
300 .seek(SeekFrom::End(0))
301 .map_err(|e| FsError::Internal(format!("seek device {path}: {e}")))?;
302
303 let total_blocks = if total_blocks == 0 {
304 device_size / block_size as u64
305 } else {
306 total_blocks
307 };
308
309 let required = total_blocks * block_size as u64;
310 if device_size < required {
311 return Err(FsError::Internal(format!(
312 "device too small: {device_size} bytes, need {required}"
313 )));
314 }
315
316 file.seek(SeekFrom::Start(0))
318 .map_err(|e| FsError::Internal(format!("seek device {path}: {e}")))?;
319
320 let mut rng = rand::thread_rng();
321 let mut buf = vec![0u8; block_size];
322 for _ in 0..total_blocks {
323 rng.fill_bytes(&mut buf);
324 file.write_all(&buf)
325 .map_err(|e| FsError::Internal(format!("write device {path}: {e}")))?;
326 }
327 file.sync_all()
328 .map_err(|e| FsError::Internal(format!("sync device {path}: {e}")))?;
329
330 Ok(Self {
331 file,
332 block_size,
333 total_blocks,
334 })
335 }
336}
337
338impl BlockStore for DeviceBlockStore {
339 fn block_size(&self) -> usize {
340 self.block_size
341 }
342
343 fn total_blocks(&self) -> u64 {
344 self.total_blocks
345 }
346
347 fn read_block(&self, block_id: u64) -> FsResult<Vec<u8>> {
348 if block_id >= self.total_blocks {
349 return Err(FsError::BlockOutOfRange(block_id));
350 }
351 let offset = block_id * self.block_size as u64;
352 let mut buf = vec![0u8; self.block_size];
353 self.file
354 .read_exact_at(&mut buf, offset)
355 .map_err(|e| FsError::Internal(format!("read block {block_id}: {e}")))?;
356 Ok(buf)
357 }
358
359 fn write_block(&self, block_id: u64, data: &[u8]) -> FsResult<()> {
360 if block_id >= self.total_blocks {
361 return Err(FsError::BlockOutOfRange(block_id));
362 }
363 if data.len() != self.block_size {
364 return Err(FsError::BlockSizeMismatch {
365 expected: self.block_size,
366 got: data.len(),
367 });
368 }
369 let offset = block_id * self.block_size as u64;
370 self.file
371 .write_all_at(data, offset)
372 .map_err(|e| FsError::Internal(format!("write block {block_id}: {e}")))?;
373 Ok(())
374 }
375
376 fn sync(&self) -> FsResult<()> {
377 self.file
378 .sync_all()
379 .map_err(|e| FsError::Internal(format!("fsync: {e}")))
380 }
381}
382
383#[cfg(test)]
384mod tests {
385 use super::*;
386
387 #[test]
388 fn test_memory_block_store_roundtrip() {
389 let store = MemoryBlockStore::new(64, 10);
390 let data = vec![0xAB; 64];
391 store.write_block(0, &data).unwrap();
392 let read = store.read_block(0).unwrap();
393 assert_eq!(read, data);
394 }
395
396 #[test]
397 fn test_unwritten_block_returns_zeroes() {
398 let store = MemoryBlockStore::new(64, 10);
399 let read = store.read_block(5).unwrap();
400 assert_eq!(read, vec![0u8; 64]);
401 }
402
403 #[test]
404 fn test_out_of_range_read() {
405 let store = MemoryBlockStore::new(64, 10);
406 assert!(store.read_block(10).is_err());
407 }
408
409 #[test]
410 fn test_block_size_mismatch() {
411 let store = MemoryBlockStore::new(64, 10);
412 assert!(store.write_block(0, &[0u8; 32]).is_err());
413 }
414
415 #[test]
416 fn test_disk_block_store_roundtrip() {
417 let dir = std::env::temp_dir();
418 let path = dir.join(format!("doublecrypt_test_{}.img", std::process::id()));
419 let path_str = path.to_str().unwrap();
420
421 let _ = std::fs::remove_file(&path);
423
424 let store = DiskBlockStore::create(path_str, 512, 16).unwrap();
425 let data = vec![0xAB; 512];
426 store.write_block(0, &data).unwrap();
427 store.sync().unwrap();
428 let read = store.read_block(0).unwrap();
429 assert_eq!(read, data);
430
431 let unwritten = store.read_block(10).unwrap();
433 assert_eq!(unwritten.len(), 512);
434 assert!(unwritten.iter().any(|&b| b != 0));
436
437 assert!(store.read_block(16).is_err());
439 assert!(store.write_block(16, &data).is_err());
440
441 assert!(store.write_block(0, &[0u8; 64]).is_err());
443
444 drop(store);
445 std::fs::remove_file(&path).unwrap();
446 }
447
448 #[test]
449 fn test_disk_block_store_open_existing() {
450 let dir = std::env::temp_dir();
451 let path = dir.join(format!("doublecrypt_test_open_{}.img", std::process::id()));
452 let path_str = path.to_str().unwrap();
453 let _ = std::fs::remove_file(&path);
454
455 {
457 let store = DiskBlockStore::create(path_str, 256, 8).unwrap();
458 let data = vec![0xCD; 256];
459 store.write_block(3, &data).unwrap();
460 store.sync().unwrap();
461 }
462
463 {
465 let store = DiskBlockStore::open(path_str, 256, 8).unwrap();
466 let read = store.read_block(3).unwrap();
467 assert_eq!(read, vec![0xCD; 256]);
468 }
469
470 {
472 let store = DiskBlockStore::open(path_str, 256, 0).unwrap();
473 assert_eq!(store.total_blocks(), 8);
474 }
475
476 std::fs::remove_file(&path).unwrap();
477 }
478
479 #[test]
480 fn test_disk_block_store_file_too_small() {
481 let dir = std::env::temp_dir();
482 let path = dir.join(format!("doublecrypt_test_small_{}.img", std::process::id()));
483 let path_str = path.to_str().unwrap();
484 let _ = std::fs::remove_file(&path);
485
486 std::fs::write(&path, vec![0u8; 100]).unwrap();
488
489 assert!(DiskBlockStore::open(path_str, 256, 8).is_err());
491
492 std::fs::remove_file(&path).unwrap();
493 }
494}