use crate::{Engine, HostCallError, InnerVm, OuterVm};
use core::ffi::CStr;
use corevm_host::{fs, Outcome, OutputStream};
use jam_types::Hash;
use polkakernel::{
libc, write_dir_entry, Environment, Error, FileSystem, Machine, MachineError, Metadata,
SeekFrom, WriteDirEntryErr,
};
pub struct KernelContext<'a, O: OuterVm> {
pub engine: &'a mut Engine<O>,
pub error: Option<HostCallError>,
}
impl<O: OuterVm> KernelContext<'_, O> {
fn append(&mut self, stream: OutputStream, data: &[u8]) -> Result<(), HostCallError> {
self.engine.append_output_slice(stream, data)
}
}
impl<O: OuterVm> Machine for KernelContext<'_, O> {
fn reg(&self, name: polkakernel::Reg) -> u64 {
self.engine.args.regs[name as usize]
}
fn set_reg(&mut self, name: polkakernel::Reg, value: u64) {
self.engine.args.regs[name as usize] = value;
}
fn read_u64(&mut self, address: u64) -> Result<u64, MachineError> {
let mut buf = [0_u8; 8];
self.read_memory_into(address, &mut buf)?;
Ok(u64::from_le_bytes(buf))
}
fn read_u32(&mut self, address: u64) -> Result<u32, MachineError> {
let mut buf = [0_u8; 4];
self.read_memory_into(address, &mut buf)?;
Ok(u32::from_le_bytes(buf))
}
fn read_u16(&mut self, address: u64) -> Result<u16, MachineError> {
let mut buf = [0_u8; 2];
self.read_memory_into(address, &mut buf)?;
Ok(u16::from_le_bytes(buf))
}
fn read_u8(&mut self, address: u64) -> Result<u8, MachineError> {
let mut buf = [0_u8; 1];
self.read_memory_into(address, &mut buf)?;
Ok(u8::from_le_bytes(buf))
}
fn read_memory_into(&mut self, address: u64, buf: &mut [u8]) -> Result<(), MachineError> {
use MachineError::BadAddress;
if self.error.is_some() {
return Err(BadAddress);
}
let address: u32 = address.try_into().map_err(|_| BadAddress)?;
let length: u32 = buf.len().try_into().map_err(|_| BadAddress)?;
self.engine.validate_guest_can_read(address, length).map_err(|_| BadAddress)?;
match self.engine.touch_once_range(address, length) {
Ok(()) => {},
Err(e) => {
self.error = Some(e.into());
return Err(BadAddress);
},
};
self.engine.inner_vm.peek_into(buf, address).map_err(|_| BadAddress)
}
fn write_u64(&mut self, address: u64, value: u64) -> Result<(), MachineError> {
let bytes = value.to_le_bytes();
self.write_memory(address, &bytes[..])
}
fn write_u32(&mut self, address: u64, value: u32) -> Result<(), MachineError> {
let bytes = value.to_le_bytes();
self.write_memory(address, &bytes[..])
}
fn write_u16(&mut self, address: u64, value: u16) -> Result<(), MachineError> {
let bytes = value.to_le_bytes();
self.write_memory(address, &bytes[..])
}
fn write_u8(&mut self, address: u64, value: u8) -> Result<(), MachineError> {
let bytes = value.to_le_bytes();
self.write_memory(address, &bytes[..])
}
fn write_memory(&mut self, address: u64, buf: &[u8]) -> Result<(), MachineError> {
use MachineError::BadAddress;
if self.error.is_some() {
return Err(BadAddress);
}
let address: u32 = address.try_into().map_err(|_| BadAddress)?;
let length: u32 = buf.len().try_into().map_err(|_| BadAddress)?;
self.engine.validate_guest_can_write(address, length).map_err(|_| BadAddress)?;
match self.engine.touch_once_range(address, length) {
Ok(()) => {},
Err(e) => {
self.error = Some(e.into());
return Err(BadAddress);
},
}
self.engine.inner_vm.poke(buf, address).map_err(|_| BadAddress)
}
}
impl<O: OuterVm> Environment for KernelContext<'_, O> {
fn write_to_stdout(&mut self, data: &[u8]) -> Result<u64, Error> {
if self.error.is_some() {
return Err(Error(libc::EIO));
}
if self.append(OutputStream::Stdout, data).is_err() {
self.error = Some(Outcome::OutputLimitReached.into());
return Err(Error(libc::EIO));
}
Ok(data.len() as u64)
}
fn write_to_stderr(&mut self, data: &[u8]) -> Result<u64, Error> {
if self.error.is_some() {
return Err(Error(libc::EIO));
}
if self.append(OutputStream::Stderr, data).is_err() {
self.error = Some(Outcome::OutputLimitReached.into());
return Err(Error(libc::EIO));
}
Ok(data.len() as u64)
}
}
impl<O: OuterVm> FileSystem for KernelContext<'_, O> {
type Fd = OpenedFile;
fn open(&mut self, path: &CStr, flags: u64) -> Result<OpenedFile, Error> {
let mut block_reader = self.engine.outer_vm.block_reader();
let block_ref = fs::resolve_path(self.engine.exec.root_dir, c"", path, &mut block_reader)
.map_err(|e| match e {
fs::Error::Path => Error(libc::ENOENT),
fs::Error::Node => Error(libc::ENOTDIR),
fs::Error::Io | fs::Error::Block | fs::Error::Loop => Error(libc::EIO),
})?;
let node = fs::Node::open(&block_ref, &mut block_reader).map_err(io_error)?;
if flags & libc::O_DIRECTORY != 0 && !matches!(node, fs::Node::Dir { .. }) {
return Err(Error(libc::ENOTDIR));
}
Ok(OpenedFile { node: node.into(), block_ref })
}
fn seek(&mut self, fd: &mut OpenedFile, from: SeekFrom) -> Result<u64, Error> {
use SeekFrom::*;
match fd.node {
FsNode::File(ref mut file) => {
let new_position = match from {
Start(offset) => offset,
Current(offset) =>
file.position().checked_add_signed(offset).ok_or(Error(libc::EINVAL))?,
End(offset) => file
.main_block()
.file_size()
.checked_add_signed(offset)
.ok_or(Error(libc::EINVAL))?,
};
file.seek(new_position).map_err(io_error)?;
Ok(file.position())
},
FsNode::Dir { ref dir, ref mut position } => {
let new_position = match from {
Start(offset) => offset,
Current(offset) =>
position.checked_add_signed(offset).ok_or(Error(libc::EINVAL))?,
End(offset) => (dir.0.len() as u64)
.checked_add_signed(offset)
.ok_or(Error(libc::EINVAL))?,
};
if new_position >= dir.0.len() as u64 {
return Err(Error(libc::EINVAL));
}
*position = new_position;
Ok(new_position)
},
}
}
fn read(&mut self, fd: &mut OpenedFile, buf: &mut [u8]) -> Result<usize, Error> {
let FsNode::File(ref mut file) = fd.node else {
return Err(Error(libc::EBADF));
};
let mut block_reader = self.engine.outer_vm.block_reader();
let num_bytes_read = file.read(buf, &mut block_reader).map_err(io_error)?;
Ok(num_bytes_read)
}
fn metadata(&mut self, path: &CStr) -> Result<Metadata, Error> {
let mut block_reader = self.engine.outer_vm.block_reader();
let block_ref = fs::resolve_path(self.engine.exec.root_dir, c"", path, &mut block_reader)
.map_err(|e| match e {
fs::Error::Path => Error(libc::ENOENT),
fs::Error::Node => Error(libc::ENOTDIR),
fs::Error::Io | fs::Error::Block | fs::Error::Loop => Error(libc::EIO),
})?;
let file = fs::File::open(&block_ref, &mut block_reader).map_err(io_error)?;
Ok(Metadata {
id: hash_to_id(&block_ref.hash.0),
size: file.main_block().file_size(),
block_size: file.main_block().block_size(),
mode: match file.main_block().kind() {
fs::NodeKind::File => 0o100644,
fs::NodeKind::Dir => 0o40755,
},
})
}
fn read_dir(&mut self, fd: &mut OpenedFile, buf: &mut [u8]) -> Result<usize, Error> {
let FsNode::Dir { ref dir, ref mut position } = fd.node else {
return Err(Error(libc::ENOTDIR));
};
let start = *position as usize;
let mut offset = 0;
for (name, block_ref) in dir.0.iter().skip(start) {
if offset == buf.len() {
break;
}
let id = hash_to_id(&block_ref.hash.0);
match write_dir_entry(id, name.as_ref(), &mut buf[offset..]) {
Ok(n) => {
offset += n;
*position += 1;
},
Err(WriteDirEntryErr::NameTooLong) => {
const _: () = assert!(fs::MAX_FILE_NAME_LEN <= u16::MAX as usize);
unreachable!()
},
Err(WriteDirEntryErr::BufferTooSmall) => break,
}
}
Ok(offset)
}
}
const fn hash_to_id(h: &Hash) -> u64 {
u64::from_le_bytes([h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7]])
}
fn io_error<E>(_error: E) -> Error {
Error(libc::EIO)
}
pub enum FsNode {
File(fs::File),
Dir { dir: fs::Dir, position: u64 },
}
impl FsNode {
pub fn position(&self) -> u64 {
match self {
Self::File(file) => file.position(),
Self::Dir { position, .. } => *position,
}
}
}
impl From<fs::Node> for FsNode {
fn from(other: fs::Node) -> Self {
match other {
fs::Node::File(file) => Self::File(file),
fs::Node::Dir(dir) => Self::Dir { dir, position: 0 },
}
}
}
pub struct OpenedFile {
pub node: FsNode,
pub block_ref: fs::BlockRef,
}