use crate::error::{Error, Result};
use crate::file_io;
use crate::fs::Filesystem;
use crate::inode::{Inode, InodeFlags};
pub fn read_value_inode(fs: &Filesystem, value_inum: u32, declared: u32) -> Result<Vec<u8>> {
if value_inum == 0 {
return Err(Error::InvalidInode(value_inum));
}
let raw = fs.read_inode_raw(value_inum)?;
let inode = Inode::parse(&raw)?;
if inode.flags & InodeFlags::EA_INODE.bits() == 0 {
return Err(Error::Corrupt(
"e_value_inum target missing EA_INODE flag (0x200000)",
));
}
if inode.size != u64::from(declared) {
return Err(Error::Corrupt(
"an EA inode's length disagrees with the xattr entry's e_value_size",
));
}
if inode.flags & InodeFlags::INLINE_DATA.bits() != 0 {
return file_io::read_inline(fs, &inode, &raw);
}
file_io::read_all(fs, &inode)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::inode::InodeFlags;
#[test]
fn ea_inode_flag_bit_value() {
assert_eq!(InodeFlags::EA_INODE.bits(), 0x0020_0000);
}
mod value_size {
use super::*;
use crate::block_io::BlockDevice;
use crate::error::Error;
use crate::fs::Filesystem;
use std::sync::{Arc, Mutex};
const BS: u32 = 1024;
const VOL: u64 = 4 * 1024 * 1024;
const EA_INO: u32 = 12;
const VALUE: &[u8] = b"deadbeef";
struct MemDev(Mutex<Vec<u8>>);
impl BlockDevice for MemDev {
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
let b = self.0.lock().unwrap();
let (start, end) = (offset as usize, offset as usize + buf.len());
if end > b.len() {
return Err(Error::Corrupt("MemDev: read past end"));
}
buf.copy_from_slice(&b[start..end]);
Ok(())
}
fn write_at(&self, offset: u64, buf: &[u8]) -> Result<()> {
let mut b = self.0.lock().unwrap();
let (start, end) = (offset as usize, offset as usize + buf.len());
if end > b.len() {
return Err(Error::Corrupt("MemDev: write past end"));
}
b[start..end].copy_from_slice(buf);
Ok(())
}
fn size_bytes(&self) -> u64 {
VOL
}
fn flush(&self) -> Result<()> {
Ok(())
}
fn is_writable(&self) -> bool {
true
}
}
fn volume_with_ea_inode(size: u64) -> Filesystem {
let dev = Arc::new(MemDev(Mutex::new(vec![0u8; VOL as usize])));
crate::mkfs::format_filesystem(dev.as_ref(), None, None, VOL, BS).expect("format");
let fs = Filesystem::mount(dev).expect("mount");
let mut raw = fs.read_inode_raw(EA_INO).expect("the inode to borrow");
raw.fill(0);
raw[crate::inode::OFF_MODE..crate::inode::OFF_MODE + 2]
.copy_from_slice(&0o100_644u16.to_le_bytes());
raw[crate::inode::OFF_LINKS_COUNT..crate::inode::OFF_LINKS_COUNT + 2]
.copy_from_slice(&1u16.to_le_bytes());
let flags = crate::inode::InodeFlags::EA_INODE.bits()
| crate::inode::InodeFlags::INLINE_DATA.bits();
raw[crate::inode::OFF_FLAGS..crate::inode::OFF_FLAGS + 4]
.copy_from_slice(&flags.to_le_bytes());
raw[crate::inode::OFF_SIZE_LO..crate::inode::OFF_SIZE_LO + 4]
.copy_from_slice(&(size as u32).to_le_bytes());
raw[crate::inode::OFF_BLOCK..crate::inode::OFF_BLOCK + VALUE.len()]
.copy_from_slice(VALUE);
let generation = u32::from_le_bytes(raw[0x64..0x68].try_into().expect("4 bytes"));
fs.finalize_inode_raw(EA_INO, generation, &mut raw)
.expect("checksum");
fs.write_inode_raw(EA_INO, &raw)
.expect("write the EA inode");
fs
}
#[test]
fn a_value_the_entry_asked_for_is_returned() {
let fs = volume_with_ea_inode(VALUE.len() as u64);
let value = read_value_inode(&fs, EA_INO, VALUE.len() as u32).expect("the value");
assert_eq!(value, VALUE, "the entry's own bytes");
}
#[test]
fn an_ea_inode_longer_than_the_entry_declared_is_refused() {
let fs = volume_with_ea_inode(60);
match read_value_inode(&fs, EA_INO, VALUE.len() as u32) {
Err(Error::Corrupt(why)) => assert!(
why.contains("e_value_size"),
"refused, but not for the length it disagreed about: {why}"
),
Err(other) => panic!("refused for the wrong reason: {other}"),
Ok(value) => panic!(
"an entry declaring {} bytes was answered with {} — the EA inode's \
own idea of its length, reported as success",
VALUE.len(),
value.len()
),
}
}
#[test]
fn an_ea_inode_shorter_than_the_entry_declared_is_refused() {
let fs = volume_with_ea_inode(4);
assert!(
read_value_inode(&fs, EA_INO, VALUE.len() as u32).is_err(),
"an entry declaring more than the inode holds is a disagreement too"
);
}
}
#[test]
fn zero_inode_is_rejected() {
let err = Error::InvalidInode(0);
match err {
Error::InvalidInode(n) => assert_eq!(n, 0),
_ => panic!(),
}
}
}