corevm-host 0.1.28

Types that are common across CoreVM service, builder, monitor, tooling
Documentation
use super::*;

use std::io::{Read, Seek, SeekFrom, Write};

impl<F: Read + Seek> HostFileRead for F {
	fn remaining_len(&mut self) -> Result<u64, IoError> {
		let cur_pos = self.stream_position().map_err(|_| IoError)?;
		let end_pos = self.seek(SeekFrom::End(0)).map_err(|_| IoError)?;
		if cur_pos != end_pos {
			self.seek(SeekFrom::Start(cur_pos)).map_err(|_| IoError)?;
		}
		Ok(end_pos - cur_pos)
	}

	fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), IoError> {
		Read::read_exact(self, buf).map_err(|_| IoError)
	}
}

impl<W: Write> HostFileWrite for W {
	fn write_all(&mut self, buf: &[u8]) -> Result<(), IoError> {
		Write::write_all(self, buf).map_err(|_| IoError)
	}
}

/// An implementation of [`HostDirRead`] for the local file system.
pub struct StdDirReader {
	path: std::path::PathBuf,
	read_dir: std::fs::ReadDir,
}

impl StdDirReader {
	pub fn new(path: std::path::PathBuf) -> Result<Self, IoError> {
		let read_dir = std::fs::read_dir(&path).map_err(|_| IoError)?;
		Ok(Self { read_dir, path })
	}

	pub fn path(&self) -> &std::path::Path {
		&self.path
	}
}

impl HostDirRead<std::fs::File> for StdDirReader {
	type Id = (u64, u64);

	fn next_entry(&mut self) -> Option<Result<HostDirEntry, IoError>> {
		loop {
			let Ok(entry) = self.read_dir.next()? else {
				return Some(Err(IoError));
			};
			let Ok(file_type) = entry.file_type() else {
				return Some(Err(IoError));
			};
			let kind = if file_type.is_dir() {
				NodeKind::Dir
			} else if file_type.is_file() {
				NodeKind::File
			} else {
				log::debug!("Skipping {:?}: unsupported file type {file_type:?}", entry.path());
				continue;
			};
			let Ok(file_name) = os_string_to_c_string(entry.file_name()) else {
				return Some(Err(IoError));
			};
			let file_name = FileName(file_name);
			return Some(Ok(HostDirEntry { kind, file_name }));
		}
	}

	fn open_file(&mut self, name: &FileName) -> Result<std::fs::File, IoError> {
		let name = c_str_to_os_str(name.0.as_c_str()).map_err(|_| IoError)?;
		let path = self.path.join(name);
		std::fs::File::open(&path).map_err(|_| IoError)
	}

	fn open_dir(&mut self, name: &FileName) -> Result<(Self, Option<Self::Id>), IoError> {
		let name = c_str_to_os_str(name.0.as_c_str()).map_err(|_| IoError)?;
		let path = self.path.join(name);
		let dir_id = get_file_id(&path)?;
		let dir = Self::new(path)?;
		Ok((dir, dir_id))
	}
}

impl FileName {
	/// Get the name as `OsStr`.
	pub fn as_os_str(&self) -> Result<&std::ffi::OsStr, InvalidPath> {
		c_str_to_os_str(self.0.as_c_str())
	}

	/// Convert the name into `OsString`.
	pub fn into_os_string(self) -> Result<std::ffi::OsString, InvalidPath> {
		c_string_to_os_string(self.0)
	}
}

#[cfg(unix)]
fn get_file_id(path: &std::path::Path) -> Result<Option<(u64, u64)>, IoError> {
	use crate::std::os::unix::fs::MetadataExt;
	let meta = std::fs::metadata(path).map_err(|_| IoError)?;
	let id = (meta.dev(), meta.ino());
	Ok(Some(id))
}

#[cfg(not(unix))]
fn get_file_id(_path: &std::path::Path) -> Result<Option<(u64, u64)>, IoError> {
	// Windows needs MetadataExt::volume_number and MetadataExt::file_index,
	// but they're nightly-only.
	Ok(None)
}

#[cfg(unix)]
fn os_string_to_c_string(file_name: std::ffi::OsString) -> Result<CString, InvalidPath> {
	use std::os::unix::ffi::OsStringExt;
	let mut bytes = file_name.into_vec();
	bytes.push(0_u8);
	Ok(CString::from_vec_with_nul(bytes)
		.expect("There are no interior NUL bytes on Unix, we added a NUL byte at the end"))
}

#[cfg(not(unix))]
fn os_string_to_c_string(file_name: std::ffi::OsString) -> Result<CString, InvalidPath> {
	let Ok(string) = file_name.into_string() else {
		// Non-UTF-8 file names are difficult to support on non-UNIX platforms.
		return Err(InvalidPath);
	};
	let mut bytes: Vec<u8> = string.into();
	bytes.push(0_u8);
	CString::from_vec_with_nul(bytes).map_err(|_| InvalidPath)
}

#[cfg(unix)]
fn c_str_to_os_str(file_name: &CStr) -> Result<&std::ffi::OsStr, InvalidPath> {
	use std::os::unix::ffi::OsStrExt;
	Ok(std::ffi::OsStr::from_bytes(file_name.to_bytes()))
}

#[cfg(not(unix))]
fn c_str_to_os_str(file_name: &CStr) -> Result<&std::ffi::OsStr, InvalidPath> {
	let Ok(name) = core::str::from_utf8(file_name.to_bytes()) else {
		// Non-UTF-8 file names are difficult to support on non-UNIX platforms.
		return Err(InvalidPath);
	};
	Ok(std::ffi::OsStr::new(name))
}

#[cfg(unix)]
fn c_string_to_os_string(file_name: CString) -> Result<std::ffi::OsString, InvalidPath> {
	use std::os::unix::ffi::OsStringExt;
	Ok(std::ffi::OsString::from_vec(file_name.into_bytes()))
}

#[cfg(not(unix))]
fn c_string_to_os_string(file_name: CString) -> Result<std::ffi::OsString, InvalidPath> {
	let Ok(name) = alloc::string::String::from_utf8(file_name.into_bytes()) else {
		// Non-UTF-8 file names are difficult to support on non-UNIX platforms.
		return Err(InvalidPath);
	};
	Ok(name.into())
}

/// An implementation of [`HostDirWrite`] for the local file system.
pub struct StdDirWriter {
	path: std::path::PathBuf,
	dir_created: bool,
}

impl StdDirWriter {
	pub fn new(path: std::path::PathBuf) -> Self {
		Self { path, dir_created: false }
	}

	pub fn path(&self) -> &std::path::Path {
		&self.path
	}
}

impl HostDirWrite for StdDirWriter {
	type FileWrite = std::fs::File;

	fn create_file(&mut self, name: &FileName) -> Result<std::fs::File, IoError> {
		if !self.dir_created {
			std::fs::create_dir_all(&self.path).map_err(|_| IoError)?;
			self.dir_created = true;
		}
		let name = c_str_to_os_str(name.0.as_c_str()).map_err(|_| IoError)?;
		let path = self.path.join(name);
		std::fs::File::create(&path).map_err(|_| IoError)
	}

	fn create_dir(&mut self, name: &FileName) -> Result<Self, IoError> {
		let name = c_str_to_os_str(name.0.as_c_str()).map_err(|_| IoError)?;
		let path = self.path.join(name);
		std::fs::create_dir_all(&path).map_err(|_| IoError)?;
		self.dir_created = true;
		Ok(Self { path, dir_created: true })
	}
}

/// An implementation of [`HostWrite`] for the local file system.
pub struct StdWriter {
	path: std::path::PathBuf,
}

impl StdWriter {
	pub fn new(path: std::path::PathBuf) -> Self {
		Self { path }
	}

	pub fn path(&self) -> &std::path::Path {
		&self.path
	}
}

impl HostWrite for StdWriter {
	type FileWrite = std::fs::File;
	type DirWrite = StdDirWriter;

	fn into_file_writer(self) -> Result<Self::FileWrite, IoError> {
		std::fs::File::create(&self.path).map_err(|_| IoError)
	}

	fn into_dir_writer(self) -> Result<Self::DirWrite, IoError> {
		Ok(StdDirWriter::new(self.path))
	}
}

impl<R: ReadBlock> Read for NodeReader<R> {
	fn read(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
		NodeReader::read(self, buf).map_err(std::io::Error::other)
	}

	fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), std::io::Error> {
		NodeReader::read_exact(self, buf).map_err(std::io::Error::other)
	}

	fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize, std::io::Error> {
		NodeReader::read_to_end(self, buf).map_err(std::io::Error::other)
	}
}

impl<R: ReadBlock> Seek for NodeReader<R> {
	fn seek(&mut self, relative: std::io::SeekFrom) -> Result<u64, std::io::Error> {
		let absolute = match relative {
			std::io::SeekFrom::Start(offset) => offset,
			std::io::SeekFrom::End(offset) =>
				(self.file().main_block().file_size() as i64 + offset) as u64,
			std::io::SeekFrom::Current(offset) => (self.file().position() as i64 + offset) as u64,
		};
		NodeReader::seek(self, absolute).map_err(std::io::Error::other)?;
		Ok(self.file().position())
	}
}

impl<W: WriteBlock> Write for NodeWriter<W> {
	fn write(&mut self, data: &[u8]) -> Result<usize, std::io::Error> {
		NodeWriter::write_all(self, data).map_err(std::io::Error::other)?;
		Ok(data.len())
	}

	fn write_all(&mut self, data: &[u8]) -> Result<(), std::io::Error> {
		NodeWriter::write_all(self, data).map_err(std::io::Error::other)
	}

	fn flush(&mut self) -> Result<(), std::io::Error> {
		Ok(())
	}
}