use crate::error::{Error, Result};
use crate::extent;
use crate::fs::Filesystem;
use crate::indirect;
use crate::inline_data;
use crate::inode::{Inode, InodeFlags};
pub fn read(
fs: &Filesystem,
inode: &Inode,
offset: u64,
length: u64,
out: &mut [u8],
) -> Result<u64> {
if length == 0 || out.is_empty() {
return Ok(0);
}
if (inode.flags & InodeFlags::INLINE_DATA.bits()) != 0 {
return Err(Error::Corrupt(
"inline-data file: caller must use file_io::read_inline with raw inode bytes",
));
}
if offset >= inode.size {
return Ok(0);
}
let max_read = (inode.size - offset).min(length).min(out.len() as u64);
if max_read == 0 {
return Ok(0);
}
let block_size = fs.sb.block_size() as u64;
let bs32 = fs.sb.block_size();
let mut written: u64 = 0;
let mut cur_offset = offset;
let end_offset = offset + max_read;
let uses_extents = (inode.flags & InodeFlags::EXTENTS.bits()) != 0;
let mut cached_extent: Option<extent::Extent> = None;
let mut indirect_cache = indirect::IndirectCache::new();
while cur_offset < end_offset {
let logical_block = cur_offset / block_size;
let off_in_block = (cur_offset % block_size) as usize;
let bytes_available_in_block = block_size as usize - off_in_block;
let bytes_remaining = (end_offset - cur_offset) as usize;
let copy_len = bytes_available_in_block.min(bytes_remaining);
let dst = &mut out[written as usize..written as usize + copy_len];
if uses_extents {
let ext_opt = match cached_extent {
Some(e) if e.contains(logical_block) => Some(e),
_ => {
let fresh = extent::lookup(&inode.block, fs.dev.as_ref(), bs32, logical_block)?;
cached_extent = fresh;
fresh
}
};
match ext_opt {
None => dst.fill(0),
Some(ext) if ext.uninitialized => dst.fill(0),
Some(ext) => {
let physical_block = ext.map(logical_block);
let phys_byte_offset = physical_block
.checked_mul(block_size)
.and_then(|b| b.checked_add(off_in_block as u64))
.ok_or(Error::CorruptExtentTree("physical block offset overflow"))?;
fs.dev.read_at(phys_byte_offset, dst)?;
}
}
} else {
let phys_opt = indirect::lookup(
&inode.block,
fs.dev.as_ref(),
bs32,
logical_block,
&mut indirect_cache,
)?;
match phys_opt {
None => dst.fill(0),
Some(physical_block) => {
let phys_byte_offset = physical_block
.checked_mul(block_size)
.and_then(|b| b.checked_add(off_in_block as u64))
.ok_or(Error::Corrupt("indirect: physical block offset overflow"))?;
fs.dev.read_at(phys_byte_offset, dst)?;
}
}
}
written += copy_len as u64;
cur_offset += copy_len as u64;
}
Ok(written)
}
pub fn read_all(fs: &Filesystem, inode: &Inode) -> Result<Vec<u8>> {
let filesystem_bytes = fs.sb.blocks_count.saturating_mul(fs.sb.block_size() as u64);
if inode.size > filesystem_bytes {
return Err(Error::Corrupt(
"reading this file whole would need more memory than the filesystem has bytes",
));
}
let size = inode.size as usize;
let mut buf = vec![0u8; size];
let n = read(fs, inode, 0, size as u64, &mut buf)?;
buf.truncate(n as usize);
Ok(buf)
}
pub fn read_inline(fs: &Filesystem, inode: &Inode, inode_raw: &[u8]) -> Result<Vec<u8>> {
inline_data::read_all(
fs.dev.as_ref(),
inode,
inode_raw,
fs.sb.inode_size,
fs.sb.block_size(),
)
}
pub fn read_with_raw(
fs: &Filesystem,
inode: &Inode,
inode_raw: &[u8],
offset: u64,
length: u64,
out: &mut [u8],
) -> Result<u64> {
if (inode.flags & InodeFlags::INLINE_DATA.bits()) != 0 {
let n = inline_data::read_range(
fs.dev.as_ref(),
inode,
inode_raw,
fs.sb.inode_size,
fs.sb.block_size(),
offset,
out,
)?;
return Ok(n as u64);
}
read(fs, inode, offset, length, out)
}
pub fn read_verified(
fs: &Filesystem,
inode: &Inode,
ino: u32,
offset: u64,
length: u64,
out: &mut [u8],
) -> Result<u64> {
if length == 0 || out.is_empty() {
return Ok(0);
}
if (inode.flags & InodeFlags::INLINE_DATA.bits()) != 0 {
return Err(Error::Corrupt(
"inline-data file: caller must use file_io::read_inline with raw inode bytes",
));
}
if offset >= inode.size {
return Ok(0);
}
let max_read = (inode.size - offset).min(length).min(out.len() as u64);
if max_read == 0 {
return Ok(0);
}
let block_size = fs.sb.block_size() as u64;
let bs32 = fs.sb.block_size();
let ctx = extent::ExtentVerifyCtx {
ino,
generation: inode.generation,
csum: &fs.csum,
};
let uses_extents = (inode.flags & InodeFlags::EXTENTS.bits()) != 0;
let mut written: u64 = 0;
let mut cur_offset = offset;
let end_offset = offset + max_read;
let mut cached_extent: Option<extent::Extent> = None;
let mut indirect_cache = indirect::IndirectCache::new();
while cur_offset < end_offset {
let logical_block = cur_offset / block_size;
let off_in_block = (cur_offset % block_size) as usize;
let bytes_available_in_block = block_size as usize - off_in_block;
let bytes_remaining = (end_offset - cur_offset) as usize;
let copy_len = bytes_available_in_block.min(bytes_remaining);
let dst = &mut out[written as usize..written as usize + copy_len];
if uses_extents {
let ext_opt = match cached_extent {
Some(e) if e.contains(logical_block) => Some(e),
_ => {
let fresh = extent::lookup_verified(
&inode.block,
fs.dev.as_ref(),
bs32,
logical_block,
Some(&ctx),
)?;
cached_extent = fresh;
fresh
}
};
match ext_opt {
None => dst.fill(0),
Some(ext) if ext.uninitialized => dst.fill(0),
Some(ext) => {
let physical_block = ext.map(logical_block);
let phys_byte_offset = physical_block
.checked_mul(block_size)
.and_then(|b| b.checked_add(off_in_block as u64))
.ok_or(Error::CorruptExtentTree("physical block offset overflow"))?;
fs.dev.read_at(phys_byte_offset, dst)?;
}
}
} else {
let phys_opt = indirect::lookup(
&inode.block,
fs.dev.as_ref(),
bs32,
logical_block,
&mut indirect_cache,
)?;
match phys_opt {
None => dst.fill(0),
Some(physical_block) => {
let phys_byte_offset = physical_block
.checked_mul(block_size)
.and_then(|b| b.checked_add(off_in_block as u64))
.ok_or(Error::Corrupt("indirect: physical block offset overflow"))?;
fs.dev.read_at(phys_byte_offset, dst)?;
}
}
}
written += copy_len as u64;
cur_offset += copy_len as u64;
}
Ok(written)
}
pub fn read_with_raw_verified(
fs: &Filesystem,
inode: &Inode,
inode_raw: &[u8],
ino: u32,
offset: u64,
length: u64,
out: &mut [u8],
) -> Result<u64> {
if (inode.flags & InodeFlags::INLINE_DATA.bits()) != 0 {
let n = inline_data::read_range(
fs.dev.as_ref(),
inode,
inode_raw,
fs.sb.inode_size,
fs.sb.block_size(),
offset,
out,
)?;
return Ok(n as u64);
}
read_verified(fs, inode, ino, offset, length, out)
}