use std::ops::{Bound, RangeBounds};
use super::*;
use crate::{err, InodeNum};
impl<R: Backend> Ufs<R> {
#[doc(alias("stat", "getattr"))]
pub fn inode_attr(&mut self, inr: InodeNum) -> IoResult<InodeAttr> {
log::trace!("inode_attr({inr});");
let ino = self.read_inode(inr)?;
Ok(ino.as_attr(inr))
}
pub fn inode_read(
&mut self,
inr: InodeNum,
mut offset: u64,
buffer: &mut [u8],
) -> IoResult<usize> {
log::trace!("inode_read({inr}, {offset}, {})", buffer.len());
let mut blockbuf = vec![0u8; self.superblock.bsize as usize];
let ino = self.read_inode(inr)?;
let mut boff = 0;
let len = (buffer.len() as u64).min(ino.size - offset);
let end = offset + len;
while offset < end {
let block = self.inode_find_block(inr, &ino, offset);
let num = (block.size - block.off).min(end - offset);
self.inode_read_block(
inr,
&ino,
block.blkidx,
&mut blockbuf[0..(block.size as usize)],
)?;
let off = block.off as usize;
buffer[boff..(boff + num as usize)]
.copy_from_slice(&blockbuf[off..(off + num as usize)]);
offset += num;
boff += num as usize;
}
Ok(boff)
}
pub fn inode_write(
&mut self,
inr: InodeNum,
mut offset: u64,
buffer: &[u8],
) -> IoResult<usize> {
log::trace!("inode_write({inr}, {offset}, {})", buffer.len());
self.assert_rw()?;
let mut blockbuf = vec![0u8; self.superblock.bsize as usize];
let mut ino = self.read_inode(inr)?;
ino.size = ino.size.max(offset + buffer.len() as u64);
self.write_inode(inr, &ino)?;
let mut boff = 0;
let len = (buffer.len() as u64).min(ino.size - offset);
let end = offset + len;
while offset < end {
let block = self.inode_find_block(inr, &ino, offset);
let num = (block.size - block.off).min(end - offset);
self.inode_read_block(
inr,
&ino,
block.blkidx,
&mut blockbuf[0..(block.size as usize)],
)?;
let off = block.off as usize;
blockbuf[off..(off + num as usize)]
.copy_from_slice(&buffer[boff..(boff + num as usize)]);
self.inode_write_block(
inr,
&mut ino,
block.blkidx,
&blockbuf[0..(block.size as usize)],
)?;
offset += num;
boff += num as usize;
}
Ok(boff)
}
pub(super) fn inode_copy_range(
&mut self,
inr: InodeNum,
ino: &Inode,
from: impl RangeBounds<u64>,
to: impl RangeBounds<u64>,
) -> IoResult<u64> {
fn decode(ino: &Inode, b: impl RangeBounds<u64>) -> (u64, u64, u64) {
let beg = match b.start_bound() {
Bound::Unbounded => 0,
Bound::Included(x) => *x,
Bound::Excluded(_) => todo!(),
};
let end = match b.end_bound() {
Bound::Unbounded => ino.size,
Bound::Included(x) => *x - 1,
Bound::Excluded(x) => *x,
};
assert!(beg <= end);
(beg, end, end - beg)
}
self.assert_rw()?;
let (fbeg, fend, flen) = decode(ino, from);
let (tbeg, mut tend, mut tlen) = decode(ino, to);
assert!(tlen >= flen);
if tlen > flen {
tend = tbeg + flen;
tlen = flen;
}
assert_eq!(flen, tlen);
let mut fpos = fbeg;
let mut tpos = tbeg;
let mut buf = [0u8; 512];
while fpos < fend {
assert!(tpos < tend);
let n = (fend - fpos).min(buf.len() as u64);
let nr = self.inode_read(inr, fpos, &mut buf)?;
assert_eq!(n, nr as u64);
let nw = self.inode_write(inr, tpos, &buf[..nr])?;
assert_eq!(n, nw as u64);
fpos += n;
tpos += n;
}
Ok(flen)
}
pub(super) fn read_inode(&mut self, inr: InodeNum) -> IoResult<Inode> {
log::trace!("read_inode({inr});");
let off = self.superblock.ino_to_fso(inr);
let ino: Inode = self.file.decode_at(off)?;
let mode = ino.mode;
if (mode & S_IFMT) == 0 {
log::warn!("invalid inode {inr}: Mode {mode:#o}, S_IFMT is 0");
return Err(err!(EINVAL));
}
Ok(ino)
}
pub(super) fn write_inode(&mut self, inr: InodeNum, ino: &Inode) -> IoResult<()> {
log::trace!("write_inode({inr});");
self.assert_rw()?;
let off = self.superblock.ino_to_fso(inr);
self.file.encode_at(off, &ino)?;
Ok(())
}
pub fn inode_modify(
&mut self,
inr: InodeNum,
f: impl FnOnce(InodeAttr) -> InodeAttr,
) -> IoResult<InodeAttr> {
self.assert_rw()?;
let mut ino = self.read_inode(inr)?;
let attr = f(ino.as_attr(inr));
ino.mode = (ino.mode & S_IFMT) | (attr.perm & !S_IFMT);
ino.uid = attr.uid;
ino.gid = attr.gid;
ino.set_atime(attr.atime);
ino.set_mtime(attr.mtime);
ino.set_ctime(attr.ctime);
ino.set_btime(attr.btime);
ino.flags = attr.flags;
self.write_inode(inr, &ino)?;
Ok(ino.as_attr(inr))
}
pub(super) fn inode_read_block(
&mut self,
inr: InodeNum,
ino: &Inode,
blkidx: u64,
buf: &mut [u8],
) -> IoResult<usize> {
log::trace!("inode_read_block({inr}, {blkidx}, [u8; {}]);", buf.len());
let fs = self.superblock.fsize as u64;
let size = self.inode_get_block_size(ino, blkidx);
match self.inode_resolve_block(inr, ino, blkidx)? {
Some(blkno) => {
self.file.read_at(blkno.get() * fs, &mut buf[0..size])?;
}
None => buf.fill(0u8),
}
Ok(size)
}
pub(super) fn inode_write_block(
&mut self,
inr: InodeNum,
ino: &mut Inode,
blkidx: u64,
buf: &[u8],
) -> IoResult<()> {
log::trace!("inode_write_block({inr}, {blkidx})");
let fs = self.superblock.fsize as u64;
let size = self.inode_get_block_size(ino, blkidx);
let blkno = match self.inode_resolve_block(inr, ino, blkidx)? {
Some(blkno) => blkno,
None => self.inode_alloc_block(inr, ino, blkidx, size as u64)?.0,
};
self.file.write_at(blkno.get() * fs, &buf[0..size])?;
Ok(())
}
pub(super) fn inode_find_block(
&mut self,
inr: InodeNum,
ino: &Inode,
offset: u64,
) -> BlockInfo {
let bs = self.superblock.bsize as u64;
let fs = self.superblock.fsize as u64;
let (blocks, frags) = ino.size(bs, fs);
log::trace!(
"inode_find_block({inr}, {offset}): size={}, bs={bs}, blocks={blocks}, fs={fs}, frags={frags}",
ino.size
);
let x = if offset < (bs * blocks) {
BlockInfo {
blkidx: offset / bs,
off: offset % bs,
size: bs,
}
} else if offset < (bs * blocks + fs * frags) {
BlockInfo {
blkidx: blocks,
off: offset % bs,
size: frags * fs,
}
} else {
panic!("inode_find_block({inr}, {offset}): out of bounds");
};
log::trace!("inode_find_block({inr}, {offset}) = {x:?}");
x
}
pub(super) fn inode_data_zones(&self) -> (u64, u64, u64, u64) {
let nd = UFS_NDADDR as u64;
let pbp = self.superblock.bsize as u64 / size_of::<u64>() as u64;
(
nd,
nd + pbp,
nd + pbp + (pbp * pbp),
nd + pbp + (pbp * pbp) + (pbp * pbp * pbp),
)
}
pub(super) fn decode_blkidx(&self, blkidx: u64) -> IoResult<InodeBlock> {
let bs = self.superblock.bsize as u64;
let pbp = bs / size_of::<u64>() as u64;
let (begin_indir1, begin_indir2, begin_indir3, begin_indir4) = self.inode_data_zones();
if blkidx < begin_indir1 {
Ok(InodeBlock::Direct(blkidx as usize))
} else if blkidx < begin_indir2 {
let x = blkidx - begin_indir1;
Ok(InodeBlock::Indirect1(x as usize))
} else if blkidx < begin_indir3 {
let x = blkidx - begin_indir2;
let high = x / pbp;
let low = x % pbp;
Ok(InodeBlock::Indirect2(high as usize, low as usize))
} else if blkidx < begin_indir4 {
let x = blkidx - begin_indir3;
let high = x / pbp / pbp;
let mid = x / pbp % pbp;
let low = x % pbp;
Ok(InodeBlock::Indirect3(
high as usize,
mid as usize,
low as usize,
))
} else {
Err(err!(EINVAL))
}
}
fn inode_resolve_block(
&mut self,
inr: InodeNum,
ino: &Inode,
blkno: u64,
) -> IoResult<Option<NonZeroU64>> {
let sb = &self.superblock;
let bs = sb.bsize as u64;
let su64 = size_of::<UfsDaddr>() as u64;
let pbp = bs / su64;
let InodeData::Blocks(InodeBlocks { direct, indirect }) = &ino.data else {
log::warn!("inode_resolve_block({inr}, {blkno}): inode doesn't have blocks");
return Err(err!(EIO));
};
let mut data = vec![0u64; pbp as usize];
let bno = match self.decode_blkidx(blkno)? {
InodeBlock::Direct(off) => NonZeroU64::new(direct[off] as u64),
InodeBlock::Indirect1(off) => {
let x1 = indirect[0] as u64;
if x1 == 0 {
return Ok(None);
}
self.read_pblock(x1, &mut data)?;
NonZeroU64::new(data[off])
}
InodeBlock::Indirect2(high, low) => {
let x1 = indirect[1] as u64;
if x1 == 0 {
return Ok(None);
}
self.read_pblock(x1, &mut data)?;
let x2 = data[high];
if x2 == 0 {
return Ok(None);
}
self.read_pblock(x2, &mut data)?;
NonZeroU64::new(data[low])
}
InodeBlock::Indirect3(high, mid, low) => {
let x1 = indirect[2] as u64;
if x1 == 0 {
return Ok(None);
}
self.read_pblock(x1, &mut data)?;
let x2 = data[high];
if x2 == 0 {
return Ok(None);
}
self.read_pblock(x2, &mut data)?;
let x3 = data[mid];
if x3 == 0 {
return Ok(None);
}
self.read_pblock(x3, &mut data)?;
NonZeroU64::new(data[low])
}
};
log::trace!("inode_resolve_block({inr}, {blkno}): bno={bno:?}");
Ok(bno)
}
pub(super) fn inode_get_block_size(&mut self, ino: &Inode, blkidx: u64) -> usize {
let bs = self.superblock.bsize as u64;
let fs = self.superblock.fsize as u64;
let (blocks, frags) = ino.size(bs, fs);
let res = if blkidx < blocks {
bs as usize
} else if blkidx < blocks + frags {
(fs * frags) as usize
} else {
panic!("out of bounds: blkidx={blkidx}, blocks={blocks}, frags={frags}");
};
log::trace!(
"inode_get_block_size(blkidx={blkidx}) = {res}; frags={frags}, blocks={blocks}"
);
res
}
}