| // Copyright 2022 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| 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)); |
| } |