blob: 18ea80d12e5b32f086b12a49cba7e40e17255d57 [file] [log] [blame]
use crate::bit_vector::*;
use crate::blob_device::*;
use crate::block_device::*;
use crate::errors::*;
use crate::structs::*;
use crate::utils::*;
pub struct BlobFS<'a> {
pub bd: BlobDevice<'a>,
pub superblock: Superblock,
pub blockmap: BitVector<'a>,
}
// Public impl
impl<'a> BlobFS<'a> {
pub fn new(bd: &'a mut dyn BlockDevice, blockmap_bits: &'a mut [u32]) -> Self {
let result = BlobFS {
bd: BlobDevice::new(bd),
superblock: Superblock::default(),
blockmap: BitVector::new(blockmap_bits),
};
return result;
}
pub fn format(&mut self) -> Result<(), BFSErr> {
// Format the device.
self.bd.format()?;
return Ok(());
}
pub fn mount(&mut self) -> Result<(), BFSErr> {
// Read the superblock from the device.
self.bd.read_superblock(&mut self.superblock)?;
// Read the allocation bitmap from the device
self.bd.read_blockmap(&mut self.blockmap)?;
return Ok(());
}
pub fn sanity_check(&self) -> Result<(), BFSErr> {
return Ok(());
}
pub fn get_blob_size(&self, hash: u64) -> Result<usize, BFSErr> {
let result = self.find_inode(hash)?;
let inode = result.1;
return Ok(inode.blob_size as usize);
}
pub fn get_blob(&self, hash: u64, blob_out: &mut [u8]) -> Result<(), BFSErr> {
let result = self.find_inode(hash)?;
let inode = result.1;
dcheck!(
inode.blob_size as usize <= blob_out.len(),
BFSErr::OutOfBounds
);
let dst = &mut blob_out[..inode.blob_size as usize];
self.bd.read_blob(inode.inline_extent, dst)?;
return Ok(());
}
pub fn put_blob(&mut self, hash: u64, blob_in: &[u8]) -> Result<(), BFSErr> {
if self.find_inode(hash).is_ok() {
return Err(BFSErr::Duplicate);
}
let blob_block_count = self.bd.blob_size_in_blocks(blob_in);
// Find a place to put the blob.
let extent = self.find_extent(blob_block_count)?;
// Copy the blob to the block device.
self.bd.write_blob(extent, blob_in)?;
// Set the corresponding bits in our local bitmap
self.blockmap
.clear_range(extent.offset(), extent.offset() + extent.size as usize)?;
// FIXME flush bitmap to disk?
// Create the inode for the new blob
let inode = Inode {
header: NodeHeader {
flags: NodeHeader::FLAG_INODE,
version: 0x0,
next_node: 0xFFFFFFFF, // FIXME
},
hash0: hash, // FIXME just using 64-bit hash
hash1: hash,
hash2: hash,
hash3: hash,
blob_size: blob_in.len() as u64,
block_count: blob_block_count as u32,
extent_count: 1,
padding: 0xFFFF,
inline_extent: extent,
};
// Put the inode in the inode table
let inode_idx = self.find_free_inode()?;
self.bd.write_inode(inode_idx, &inode)?;
return Ok(());
}
pub fn delete_blob(&mut self, hash: u64) -> Result<(), BFSErr> {
let result = self.find_inode(hash)?;
let inode_idx = result.0;
let extent = result.1.inline_extent;
self.bd.invalidate_inode(inode_idx)?;
self.blockmap
.set_range(extent.offset(), extent.offset() + extent.size as usize)?;
self.bd.delete_blob(extent)?;
self.bd.delete_blob(extent)?;
return Ok(());
}
}
// Private impl
impl<'a> BlobFS<'a> {
fn find_inode(&self, hash: u64) -> Result<(usize, Inode), BFSErr> {
for i in 0..self.superblock.inode_count as usize {
let mut inode = Inode::default();
self.bd.read_inode(i, &mut inode)?;
if inode.header.flags == NodeHeader::FLAG_INODE && inode.hash0 == hash {
return Ok((i, inode));
}
}
return Err(BFSErr::NotFound);
}
// FIXME quick and dirty scan entire inode table for empty slot
fn find_free_inode(&self) -> Result<usize, BFSErr> {
for i in 0..self.superblock.inode_count as usize {
let mut header = NodeHeader::default();
self.bd.read_node_header(i, &mut header)?;
if header.flags == u16::MAX {
return Ok(i);
}
}
assert!(false);
return Err(BFSErr::NotFound);
}
#[allow(dead_code)]
fn count_inodes(&self) -> Result<usize, BFSErr> {
let mut count = 0;
for i in 0..self.superblock.inode_count as usize {
let mut header = NodeHeader::default();
self.bd.read_node_header(i, &mut header)?;
if header.flags == NodeHeader::FLAG_INODE {
count = count + 1;
}
}
return Ok(count);
}
fn find_extent(&self, blob_block_count: u16) -> Result<Extent, BFSErr> {
let block_count = self.bd.bd.geom().block_count;
let offset = self
.blockmap
.find_span(0, block_count, blob_block_count as usize)?;
return Ok(Extent {
size: blob_block_count as u16,
//offset_hi: (offset >> 32) as u16,
offset_hi: 0 as u16,
offset_lo: offset as u32,
});
}
}
// Unit tests
#[test]
fn test_basic() {
use crate::test_device::*;
const BLOCK_SIZE: usize = 8192;
const BLOCK_COUNT: usize = 32;
let geom = BlockDeviceGeometry {
block_size: BLOCK_SIZE,
block_count: BLOCK_COUNT,
};
let mut buf: [u8; BLOCK_SIZE * BLOCK_COUNT] = [0; BLOCK_SIZE * BLOCK_COUNT];
let mut dirty_bits = [0; BLOCK_SIZE * BLOCK_COUNT / 32];
let bd: &mut dyn BlockDevice = &mut TestDevice::new(geom, buf.as_mut_ptr(), &mut dirty_bits);
let mut blockmap_bits: [u32; BLOCK_COUNT / 32] = [0xFFFFFFFF; BLOCK_COUNT / 32];
let mut fs = BlobFS::new(bd, blockmap_bits.as_mut());
assert_ok!(fs.format());
assert_ok!(fs.mount());
assert_ok!(fs.sanity_check());
// Block map should start with 4 reserved blocks for metadata
assert_eq!(
fs.blockmap.count_range(0, BLOCK_COUNT).unwrap(),
BLOCK_COUNT - 4
);
// Store a small blob in the filesystem
let blob_hash: u64 = 0xDEADBEEFF00DCAFE;
let blob_text = "This is the contents of a blob";
let blob_contents = blob_text.as_bytes();
assert_ok!(fs.put_blob(blob_hash, blob_contents));
// Block map should have lost one free block
assert_eq!(
fs.blockmap.count_range(0, BLOCK_COUNT).unwrap(),
BLOCK_COUNT - 5
);
// Storing it a second time should fail.
assert_err!(fs.put_blob(blob_hash, blob_contents));
// Read it back out
let blob_len = fs.get_blob_size(blob_hash).unwrap();
let mut blob_contents: Vec<u8> = vec![0; blob_len];
assert_ok!(fs.get_blob(blob_hash, &mut blob_contents));
// Contents should match.
let new_blob_text = core::str::from_utf8(&blob_contents).unwrap();
assert_eq!(blob_text, new_blob_text);
// Delete it and lookups should fail
assert_ok!(fs.delete_blob(blob_hash));
assert_err!(fs.get_blob_size(blob_hash));
// Deleting it a second time should also fail.
assert_err!(fs.delete_blob(blob_hash));
// Block map should have gained one free block
assert_eq!(
fs.blockmap.count_range(0, BLOCK_COUNT).unwrap(),
BLOCK_COUNT - 4
);
// Reading a non-existent blob should cause an error
let bad_hash: u64 = 0xAAAAAAAAAAAAAAAA;
assert_err!(fs.get_blob_size(bad_hash));
let mut blob_contents: [u8; 256] = [0; 256];
assert_err!(fs.get_blob(bad_hash, &mut blob_contents));
}