Remove old non-thread-safe ext2 implementation

Old implementation is now considered legacy. Thread-safety is now
enforced for any non-trivial operations. All trivial operations are
available on the regular `Ext2` struct found in `fs` module.
`Synced<Ext2>` is now the only way to go.
This commit is contained in:
Szymon Walter 2018-03-22 11:23:58 +01:00
parent 0d01a87992
commit 04d54ea320
2 changed files with 66 additions and 660 deletions

View file

@ -1,12 +1,10 @@
use core::mem;
use core::fmt::{self, Debug};
use core::nonzero::NonZero;
use alloc::Vec;
use error::Error;
use sector::{Address, SectorSize};
use volume::{Volume, VolumeSlice};
use volume::Volume;
use sys::superblock::Superblock;
use sys::block_group::BlockGroupDescriptor;
use sys::inode::Inode as RawInode;
@ -64,104 +62,11 @@ impl<S: SectorSize, V: Volume<u8, S>> Ext2<S, V> {
})
}
#[allow(dead_code)]
fn update_global(&mut self) -> Result<(), Error> {
// superblock
{
let slice = VolumeSlice::from_cast(
&self.superblock.inner,
self.superblock.offset,
);
let commit = slice.commit();
self.volume.commit(commit).map_err(|err| err.into())?;
}
// block group descriptors
let mut offset = self.block_groups.offset;
for descr in &self.block_groups.inner {
let slice = VolumeSlice::from_cast(descr, offset);
let commit = slice.commit();
self.volume.commit(commit).map_err(|err| err.into())?;
offset =
offset + Address::from(mem::size_of::<BlockGroupDescriptor>());
}
Ok(())
}
pub fn read_inode<'vol>(
&'vol self,
buf: &mut [u8],
inode: &Inode<'vol, S, V>,
) -> Result<usize, Error> {
let total_size = inode.size();
let block_size = self.block_size();
let mut offset = 0;
for block in inode.blocks() {
match block {
Ok((data, _)) => {
let data_size = block_size
.min(total_size - offset)
.min(buf.len() - offset);
let end = offset + data_size;
buf[offset..end].copy_from_slice(&data[..data_size]);
offset += data_size;
}
Err(err) => return Err(err.into()),
}
}
Ok(offset)
}
pub fn write_inode<'vol>(
&'vol self,
_inode: &(Inode<'vol, S, V>, Address<S>),
_buf: &[u8],
) -> Result<usize, Error> {
unimplemented!()
}
pub fn root_inode<'vol>(&'vol self) -> (Inode<'vol, S, V>, Address<S>) {
self.inode_nth(2).unwrap()
}
pub fn inode_nth<'vol>(
&'vol self,
index: usize,
) -> Option<(Inode<'vol, S, V>, Address<S>)> {
self.inodes_nth(index).next()
}
pub fn inodes<'vol>(&'vol self) -> Inodes<'vol, S, V> {
self.inodes_nth(1)
}
pub fn inodes_nth<'vol>(&'vol self, index: usize) -> Inodes<'vol, S, V> {
assert!(index > 0, "inodes are 1-indexed");
Inodes {
fs: self,
block_groups: &self.block_groups.inner,
log_block_size: self.log_block_size(),
inode_size: self.inode_size(),
inodes_per_group: self.inodes_count(),
inodes_count: self.total_inodes_count(),
index,
}
}
fn superblock(&self) -> &Superblock {
&self.superblock.inner
}
#[allow(dead_code)]
fn superblock_mut(&mut self) -> &mut Superblock {
&mut self.superblock.inner
}
pub fn version(&self) -> (u32, u16) {
(self.superblock().rev_major, self.superblock().rev_minor)
(
self.superblock.inner.rev_major,
self.superblock.inner.rev_minor,
)
}
pub fn inode_size(&self) -> usize {
@ -169,20 +74,21 @@ impl<S: SectorSize, V: Volume<u8, S>> Ext2<S, V> {
mem::size_of::<RawInode>()
} else {
// note: inodes bigger than 128 are not supported
self.superblock().inode_size as usize
self.superblock.inner.inode_size as usize
}
}
pub fn inodes_count(&self) -> usize {
self.superblock().inodes_per_group as _
self.superblock.inner.inodes_per_group as _
}
pub fn total_inodes_count(&self) -> usize {
self.superblock().inodes_count as _
self.superblock.inner.inodes_count as _
}
pub fn block_group_count(&self) -> Result<usize, Error> {
self.superblock()
self.superblock
.inner
.block_group_count()
.map(|count| count as usize)
.map_err(|(a, b)| Error::BadBlockGroupCount {
@ -192,19 +98,19 @@ impl<S: SectorSize, V: Volume<u8, S>> Ext2<S, V> {
}
pub fn total_block_count(&self) -> usize {
self.superblock().blocks_count as _
self.superblock.inner.blocks_count as _
}
pub fn free_block_count(&self) -> usize {
self.superblock().free_blocks_count as _
self.superblock.inner.free_blocks_count as _
}
pub fn block_size(&self) -> usize {
self.superblock().block_size()
self.superblock.inner.block_size()
}
pub fn log_block_size(&self) -> u32 {
self.superblock().log_block_size + 10
self.superblock.inner.log_block_size + 10
}
pub fn sector_size(&self) -> usize {
@ -216,340 +122,15 @@ impl<S: SectorSize, V: Volume<u8, S>> Ext2<S, V> {
}
}
impl<S: SectorSize, V: Volume<u8, S>> Debug for Ext2<S, V> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Ext2<{}>", S::SIZE)
}
}
pub struct Inodes<'vol, S: SectorSize, V: 'vol + Volume<u8, S>> {
fs: &'vol Ext2<S, V>,
block_groups: &'vol [BlockGroupDescriptor],
log_block_size: u32,
inode_size: usize,
inodes_per_group: usize,
inodes_count: usize,
index: usize,
}
impl<'vol, S: SectorSize, V: 'vol + Volume<u8, S>> Iterator
for Inodes<'vol, S, V>
{
type Item = (Inode<'vol, S, V>, Address<S>);
fn next(&mut self) -> Option<Self::Item> {
if self.index < self.inodes_count {
let block_group = (self.index - 1) / self.inodes_per_group;
let index = (self.index - 1) % self.inodes_per_group;
self.index += 1;
let inodes_block = self.block_groups[block_group].inode_table_block;
let offset = Address::with_block_size(
inodes_block,
(index * self.inode_size) as i32,
self.log_block_size,
);
let raw = unsafe {
RawInode::find_inode(&self.fs.volume, offset, self.inode_size)
.ok()
};
raw.map(|(raw, offset)| (Inode::new(self.fs, raw), offset))
} else {
None
}
}
}
#[derive(Debug, Clone)]
pub struct Inode<'vol, S: SectorSize, V: 'vol + Volume<u8, S>> {
fs: &'vol Ext2<S, V>,
inner: RawInode,
}
impl<'vol, S: SectorSize, V: 'vol + Volume<u8, S>> Inode<'vol, S, V> {
pub fn new(fs: &'vol Ext2<S, V>, inner: RawInode) -> Inode<'vol, S, V> {
Inode { fs, inner }
}
pub fn blocks<'inode>(&'inode self) -> InodeBlocks<'vol, 'inode, S, V> {
InodeBlocks {
inode: self,
index: 0,
}
}
pub fn directory<'inode>(
&'inode self,
) -> Option<Directory<'vol, 'inode, S, V>> {
use sys::inode::TypePerm;
if unsafe { self.inner.type_perm.contains(TypePerm::DIRECTORY) } {
Some(Directory {
blocks: self.blocks(),
offset: 0,
buffer: None,
block_size: self.fs.block_size(),
})
} else {
None
}
}
pub fn block(&self, index: usize) -> Option<NonZero<u32>> {
self.try_block(index).ok().and_then(|block| block)
}
pub fn try_block(
&self,
mut index: usize,
) -> Result<Option<NonZero<u32>>, Error> {
// number of blocks in direct table: 12
// number of blocks in indirect table: block_size/4
// why?
// - a block is n bytes long
// - a block address occupies 32 bits, or 4 bytes
// - thus, n/4
// number of blocks in doubly table: (block_size/4)^2
// why?
// - every entry in the doubly table points to another block
// - that's n/4 blocks, where n is the block size
// - every block contains n/4 block pointers
// - that's n/4 blocks with n/4 pointers each = (n/4)^2
// number of blocks in triply table: (block_size/4)^3
fn block_index<S: SectorSize, V: Volume<u8, S>>(
volume: &V,
block: u32,
index: usize,
log_block_size: u32,
) -> Result<Option<NonZero<u32>>, Error> {
let offset = (index * 4) as i32;
let end = offset + 4;
let addr = Address::with_block_size(block, offset, log_block_size);
let end = Address::with_block_size(block, end, log_block_size);
let block = volume.slice(addr..end);
match block {
Ok(block) => unsafe {
Ok(NonZero::new(block.dynamic_cast::<u32>().0))
},
Err(err) => Err(err.into()),
}
}
let bs4 = self.fs.block_size() / 4;
let log_block_size = self.fs.log_block_size();
if index < 12 {
return Ok(NonZero::new(self.inner.direct_pointer[index]));
}
index -= 12;
if index < bs4 {
let block = self.inner.indirect_pointer;
return block_index(&self.fs.volume, block, index, log_block_size);
}
index -= bs4;
if index < bs4 * bs4 {
let indirect_index = index >> (log_block_size + 2);
let block = match block_index(
&self.fs.volume,
self.inner.doubly_indirect,
indirect_index,
log_block_size,
) {
Ok(Some(block)) => block.get(),
Ok(None) => return Ok(None),
Err(err) => return Err(err),
};
return block_index(
&self.fs.volume,
block,
index & (bs4 - 1),
log_block_size,
);
}
index -= bs4 * bs4;
if index < bs4 * bs4 * bs4 {
let doubly_index = index >> (2 * log_block_size + 4);
let indirect = match block_index(
&self.fs.volume,
self.inner.triply_indirect,
doubly_index,
log_block_size,
) {
Ok(Some(block)) => block.get(),
Ok(None) => return Ok(None),
Err(err) => return Err(err),
};
let indirect_index = (index >> (log_block_size + 2)) & (bs4 - 1);
let block = match block_index(
&self.fs.volume,
indirect as u32,
indirect_index,
log_block_size,
) {
Ok(Some(block)) => block.get(),
Ok(None) => return Ok(None),
Err(err) => return Err(err),
};
return block_index(
&self.fs.volume,
block,
index & (bs4 - 1),
log_block_size,
);
}
Ok(None)
}
pub fn in_use(&self) -> bool {
self.inner.hard_links > 0
}
pub fn uid(&self) -> u16 {
self.inner.uid
}
pub fn sectors(&self) -> usize {
self.inner.sectors_count as usize
}
pub fn size32(&self) -> u32 {
self.inner.size_low
}
pub fn size64(&self) -> u64 {
self.inner.size_low as u64 | (self.inner.size_high as u64) << 32
}
#[cfg(target_pointer_width = "64")]
#[inline]
pub fn size(&self) -> usize {
self.size64() as usize
}
#[cfg(target_pointer_width = "32")]
#[inline]
pub fn size(&self) -> usize {
self.size32() as usize
}
}
pub struct InodeBlocks<
'vol: 'inode,
'inode,
S: SectorSize,
V: 'vol + Volume<u8, S>,
> {
inode: &'inode Inode<'vol, S, V>,
index: usize,
}
impl<'vol, 'inode, S: SectorSize, V: 'vol + Volume<u8, S>> Iterator
for InodeBlocks<'vol, 'inode, S, V>
{
type Item = Result<(VolumeSlice<'vol, u8, S>, Address<S>), Error>;
fn next(&mut self) -> Option<Self::Item> {
let block = self.inode.try_block(self.index);
let block = match block {
Ok(Some(ok)) => ok,
Ok(None) => return None,
Err(err) => return Some(Err(err)),
};
self.index += 1;
let block = block.get();
let log_block_size = self.inode.fs.log_block_size();
let offset = Address::with_block_size(block, 0, log_block_size);
let end = Address::with_block_size(block + 1, 0, log_block_size);
let slice = self.inode
.fs
.volume
.slice(offset..end)
.map(|slice| (slice, offset))
.map_err(|err| err.into());
Some(slice)
}
}
pub struct Directory<
'vol: 'inode,
'inode,
S: SectorSize,
V: 'vol + Volume<u8, S>,
> {
blocks: InodeBlocks<'vol, 'inode, S, V>,
offset: usize,
buffer: Option<VolumeSlice<'vol, u8, S>>,
block_size: usize,
}
impl<'vol, 'inode, S: SectorSize, V: 'vol + Volume<u8, S>> Iterator
for Directory<'vol, 'inode, S, V>
{
type Item = Result<DirectoryEntry, Error>;
fn next(&mut self) -> Option<Self::Item> {
if self.buffer.is_none() || self.offset >= self.block_size {
self.buffer = match self.blocks.next() {
None => return None,
Some(Ok((block, _))) => Some(block),
Some(Err(err)) => return Some(Err(err)),
};
self.offset = 0;
}
let buffer = &self.buffer.as_ref().unwrap()[self.offset..];
let inode = buffer[0] as u32 | (buffer[1] as u32) << 8
| (buffer[2] as u32) << 16
| (buffer[3] as u32) << 24;
if inode == 0 {
return None;
}
let size = buffer[4] as u16 | (buffer[5] as u16) << 8;
let len = buffer[6];
let ty = buffer[7];
let name = buffer[8..8 + len as usize].to_vec();
self.offset += size as usize;
Some(Ok(DirectoryEntry {
name: name,
inode: inode as usize,
ty: ty,
}))
}
}
#[derive(Clone)]
pub struct DirectoryEntry {
pub name: Vec<u8>,
pub inode: usize,
pub ty: u8,
}
#[cfg(test)]
mod tests {
use std::fs::File;
use std::cell::RefCell;
use sector::{Address, SectorSize, Size512};
use sector::{Address, Size512};
use volume::Volume;
use super::{Ext2, Inode};
use super::Ext2;
#[test]
fn file_len() {
@ -587,133 +168,4 @@ mod tests {
println!("version: {}.{}", vers.0, vers.1);
assert_eq!(128, fs.inode_size());
}
#[test]
fn inodes() {
let file = RefCell::new(File::open("ext2.img").unwrap());
let fs = Ext2::<Size512, _>::new(file);
assert!(
fs.is_ok(),
"Err({:?})",
fs.err().unwrap_or_else(|| unreachable!()),
);
let fs = fs.unwrap();
let inodes = fs.inodes().filter(|inode| inode.0.in_use());
for inode in inodes {
println!("{:?}", inode);
}
}
#[test]
fn inode_blocks() {
use std::str;
let file = RefCell::new(File::open("ext2.img").unwrap());
let fs = Ext2::<Size512, _>::new(file).unwrap();
let inodes = fs.inodes().filter(|inode| {
inode.0.in_use() && inode.0.uid() == 1000 && inode.0.size() < 1024
});
for inode in inodes {
println!("{:?}", inode.0);
let size = inode.0.size();
for block in inode.0.blocks() {
let (data, _) = block.unwrap();
assert_eq!(data.len(), fs.block_size());
println!("{:?}", &data[..size]);
let _ = str::from_utf8(&data[..size])
.map(|string| println!("{}", string));
}
}
}
#[test]
fn read_inode() {
let file = RefCell::new(File::open("ext2.img").unwrap());
let fs = Ext2::<Size512, _>::new(file).unwrap();
let inodes = fs.inodes().filter(|inode| {
inode.0.in_use() && inode.0.uid() == 1000 && inode.0.size() < 1024
});
for (inode, _) in inodes {
let mut buf = Vec::with_capacity(inode.size());
unsafe {
buf.set_len(inode.size());
}
let size = fs.read_inode(&mut buf[..], &inode);
assert!(size.is_ok());
let size = size.unwrap();
assert_eq!(size, inode.size());
unsafe {
buf.set_len(size);
}
}
}
#[test]
fn read_big() {
let file = RefCell::new(File::open("ext2.img").unwrap());
let fs = Ext2::<Size512, _>::new(file).unwrap();
let inodes = fs.inodes().filter(|inode| {
inode.0.in_use() && inode.0.uid() == 1000
&& inode.0.size() == 537600
});
for (inode, _) in inodes {
let mut buf = Vec::with_capacity(inode.size());
unsafe {
buf.set_len(inode.size());
}
let size = fs.read_inode(&mut buf[..], &inode);
assert!(size.is_ok());
let size = size.unwrap();
assert_eq!(size, inode.size());
unsafe {
buf.set_len(size);
}
for (i, &x) in buf.iter().enumerate() {
if i & 1 == 0 {
assert_eq!(x, b'u', "{}", i);
} else {
assert_eq!(x, b'\n', "{}", i);
}
}
}
}
#[test]
fn walkdir() {
use std::str;
fn walk<'vol, S: SectorSize, V: Volume<u8, S>>(
fs: &'vol Ext2<S, V>,
inode: Inode<'vol, S, V>,
name: String,
) {
inode.directory().map(|dir| {
for entry in dir {
assert!(entry.is_ok());
let entry = entry.unwrap();
let entry_name = str::from_utf8(&entry.name).unwrap_or("?");
println!("{}/{} => {}", name, entry_name, entry.inode,);
if entry_name != "." && entry_name != ".." {
walk(
fs,
fs.inode_nth(entry.inode).unwrap().0,
format!("{}/{}", name, entry_name),
);
}
}
});
}
let file = RefCell::new(File::open("ext2.img").unwrap());
let fs = Ext2::<Size512, _>::new(file).unwrap();
let (root, _) = fs.root_inode();
walk(&fs, root, String::new());
}
}

View file

@ -42,45 +42,6 @@ impl<S: SectorSize, V: Volume<u8, S>> Synced<Ext2<S, V>> {
Ext2::new(volume).map(|inner| Synced::with_inner(inner))
}
#[allow(dead_code)]
fn update_global(&mut self) -> Result<(), Error> {
self.inner().update_global()
}
pub fn read_inode(
&self,
buf: &mut [u8],
inode: &Inode<S, V>,
) -> Result<usize, Error> {
let total_size = inode.size();
let block_size = self.block_size();
let mut offset = 0;
for block in inode.blocks() {
match block {
Ok((data, _)) => {
let data_size = block_size
.min(total_size - offset)
.min(buf.len() - offset);
let end = offset + data_size;
buf[offset..end].copy_from_slice(&data[..data_size]);
offset += data_size;
}
Err(err) => return Err(err.into()),
}
}
Ok(offset)
}
pub fn write_inode(
&self,
_inode: &(Inode<S, V>, Address<S>),
_buf: &[u8],
) -> Result<usize, Error> {
unimplemented!()
}
pub fn root_inode(&self) -> (Inode<S, V>, Address<S>) {
self.inode_nth(2).unwrap()
}
@ -106,42 +67,6 @@ impl<S: SectorSize, V: Volume<u8, S>> Synced<Ext2<S, V>> {
}
}
pub fn version(&self) -> (u32, u16) {
self.inner().version()
}
pub fn inode_size(&self) -> usize {
self.inner().inode_size()
}
pub fn inodes_count(&self) -> usize {
self.inner().inodes_count()
}
pub fn total_inodes_count(&self) -> usize {
self.inner().total_inodes_count()
}
pub fn block_group_count(&self) -> Result<usize, Error> {
self.inner().block_group_count()
}
pub fn total_block_count(&self) -> usize {
self.inner().total_block_count()
}
pub fn free_block_count(&self) -> usize {
self.inner().free_block_count()
}
pub fn block_size(&self) -> usize {
self.inner().block_size()
}
pub fn log_block_size(&self) -> u32 {
self.inner().log_block_size()
}
pub fn sector_size(&self) -> usize {
S::SIZE
}
@ -216,6 +141,31 @@ impl<S: SectorSize, V: Volume<u8, S>> Inode<S, V> {
Inode { fs, inner }
}
pub fn read(&self, buf: &mut [u8]) -> Result<usize, Error> {
let total_size = self.size();
let block_size = {
let fs = self.fs.inner();
fs.block_size()
};
let mut offset = 0;
for block in self.blocks() {
match block {
Ok((data, _)) => {
let data_size = block_size
.min(total_size - offset)
.min(buf.len() - offset);
let end = offset + data_size;
buf[offset..end].copy_from_slice(&data[..data_size]);
offset += data_size;
}
Err(err) => return Err(err.into()),
}
}
Ok(offset)
}
pub fn blocks(&self) -> InodeBlocks<S, V> {
InodeBlocks {
inode: self.clone(),
@ -230,7 +180,10 @@ impl<S: SectorSize, V: Volume<u8, S>> Inode<S, V> {
blocks: self.blocks(),
offset: 0,
buffer: None,
block_size: self.fs.block_size(),
block_size: {
let fs = self.fs.inner();
fs.block_size()
},
})
} else {
None
@ -278,8 +231,10 @@ impl<S: SectorSize, V: Volume<u8, S>> Inode<S, V> {
}
}
let bs4 = self.fs.block_size() / 4;
let log_block_size = self.fs.log_block_size();
let fs = self.fs.inner();
let bs4 = fs.block_size() / 4;
let log_block_size = fs.log_block_size();
if index < 12 {
return Ok(NonZero::new(self.inner.direct_pointer[index]));
@ -289,12 +244,7 @@ impl<S: SectorSize, V: Volume<u8, S>> Inode<S, V> {
if index < bs4 {
let block = self.inner.indirect_pointer;
return block_index(
&self.fs.inner().volume,
block,
index,
log_block_size,
);
return block_index(&fs.volume, block, index, log_block_size);
}
index -= bs4;
@ -302,7 +252,7 @@ impl<S: SectorSize, V: Volume<u8, S>> Inode<S, V> {
if index < bs4 * bs4 {
let indirect_index = index >> (log_block_size + 2);
let block = match block_index(
&self.fs.inner().volume,
&fs.volume,
self.inner.doubly_indirect,
indirect_index,
log_block_size,
@ -312,7 +262,7 @@ impl<S: SectorSize, V: Volume<u8, S>> Inode<S, V> {
Err(err) => return Err(err),
};
return block_index(
&self.fs.inner().volume,
&fs.volume,
block,
index & (bs4 - 1),
log_block_size,
@ -324,7 +274,7 @@ impl<S: SectorSize, V: Volume<u8, S>> Inode<S, V> {
if index < bs4 * bs4 * bs4 {
let doubly_index = index >> (2 * log_block_size + 4);
let indirect = match block_index(
&self.fs.inner().volume,
&fs.volume,
self.inner.triply_indirect,
doubly_index,
log_block_size,
@ -335,7 +285,7 @@ impl<S: SectorSize, V: Volume<u8, S>> Inode<S, V> {
};
let indirect_index = (index >> (log_block_size + 2)) & (bs4 - 1);
let block = match block_index(
&self.fs.inner().volume,
&fs.volume,
indirect as u32,
indirect_index,
log_block_size,
@ -345,7 +295,7 @@ impl<S: SectorSize, V: Volume<u8, S>> Inode<S, V> {
Err(err) => return Err(err),
};
return block_index(
&self.fs.inner().volume,
&fs.volume,
block,
index & (bs4 - 1),
log_block_size,
@ -406,13 +356,13 @@ impl<S: SectorSize, V: Volume<u8, S>> Iterator for InodeBlocks<S, V> {
};
self.index += 1;
let fs = self.inode.fs.inner();
let block = block.get();
let log_block_size = self.inode.fs.log_block_size();
let log_block_size = fs.log_block_size();
let offset = Address::with_block_size(block, 0, log_block_size);
let end = Address::with_block_size(block + 1, 0, log_block_size);
let fs = self.inode.fs.inner();
let slice = fs.volume
.slice(offset..end)
.map(|slice| (slice.to_vec(), offset))
@ -497,10 +447,11 @@ mod tests {
);
let fs = fs.unwrap();
let inner = fs.inner();
let vers = fs.version();
let vers = inner.version();
println!("version: {}.{}", vers.0, vers.1);
assert_eq!(128, fs.inode_size());
assert_eq!(128, inner.inode_size());
}
#[test]
@ -536,7 +487,10 @@ mod tests {
let size = inode.0.size();
for block in inode.0.blocks() {
let (data, _) = block.unwrap();
assert_eq!(data.len(), fs.block_size());
assert_eq!(data.len(), {
let fs = fs.inner();
fs.block_size()
});
println!("{:?}", &data[..size]);
let _ = str::from_utf8(&data[..size])
.map(|string| println!("{}", string));
@ -557,7 +511,7 @@ mod tests {
unsafe {
buf.set_len(inode.size());
}
let size = fs.read_inode(&mut buf[..], &inode);
let size = inode.read(&mut buf[..]);
assert!(size.is_ok());
let size = size.unwrap();
assert_eq!(size, inode.size());
@ -581,7 +535,7 @@ mod tests {
unsafe {
buf.set_len(inode.size());
}
let size = fs.read_inode(&mut buf[..], &inode);
let size = inode.read(&mut buf[..]);
assert!(size.is_ok());
let size = size.unwrap();
assert_eq!(size, inode.size());