use std::fs::OpenOptions;
use std::{cell::Cell, os::fd::AsFd, time::Duration};
use std::{
fmt::Debug,
fs::{self, File, read_dir},
io::{self, Read, Write},
os::unix::net::UnixStream,
path::{Path, PathBuf},
};
use nix::{
fcntl::{FcntlArg, OFlag, fcntl},
poll::{PollFd, PollFlags, PollTimeout, poll},
};
use thiserror::Error;
use crate::Stream;
use crate::transports::BusdStream;
use crate::{Transport, codecs::BaseStreams};
#[derive(Debug)]
pub struct TimeoutFile {
file: File,
timeout: Cell<Option<Duration>>,
}
impl std::ops::DerefMut for TimeoutFile {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.file
}
}
impl std::ops::Deref for TimeoutFile {
type Target = File;
fn deref(&self) -> &Self::Target {
&self.file
}
}
impl From<File> for TimeoutFile {
fn from(file: File) -> Self {
Self {
file,
timeout: Cell::new(None),
}
}
}
impl Read for TimeoutFile {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let timeout = self.timeout.get();
if let Some(timeout) = timeout {
let pollfd = PollFd::new(self.as_fd(), PollFlags::POLLIN);
let timeout = PollTimeout::try_from(timeout).unwrap_or(PollTimeout::MAX);
if poll(&mut [pollfd], timeout)? == 0 {
return Err(io::Error::new(
io::ErrorKind::TimedOut,
"File read timed out",
));
}
}
self.file.read(buf)
}
}
impl Write for TimeoutFile {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.file.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.file.flush()
}
}
impl Stream for TimeoutFile {
fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
let mut flags = OFlag::from_bits_retain(fcntl(&**self, FcntlArg::F_GETFL)?);
if nonblocking {
flags |= OFlag::O_NONBLOCK;
} else {
flags &= !OFlag::O_NONBLOCK;
}
fcntl(&**self, FcntlArg::F_SETFL(flags))?;
Ok(())
}
fn set_read_timeout(&self, timeout: Option<Duration>) -> io::Result<()> {
if timeout == Some(Duration::ZERO) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"zero timeout passed to set_read_timeout",
));
}
self.timeout.set(timeout);
Ok(())
}
}
#[derive(Clone, Debug)]
pub struct LinuxVirtio {
rpc: PathBuf,
blob: PathBuf,
event: PathBuf,
}
#[derive(Debug, Error)]
pub enum NewVirtioError {
#[error("I/O error: {0}")]
Io(#[from] io::Error),
#[error("Some or all virtio ports not found")]
NoPorts,
}
impl LinuxVirtio {
pub fn new() -> Result<Self, NewVirtioError> {
let mut rpc = None;
let mut blob = None;
let mut event = None;
for port in read_dir("/sys/class/virtio-ports")? {
let port = port?;
let name = fs::read_to_string(port.path().join("name"))?
.trim_end()
.to_owned();
if name == "oc2.rpc.0" {
rpc = Some(Path::new("/dev/").join(port.file_name()));
} else if name == "oc2.blob.0" {
blob = Some(Path::new("/dev/").join(port.file_name()));
} else if name == "oc2.event.0" {
event = Some(Path::new("/dev/").join(port.file_name()));
}
}
if let Some(rpc) = rpc
&& let Some(blob) = blob
&& let Some(event) = event
{
Ok(Self { rpc, blob, event })
} else {
Err(NewVirtioError::NoPorts)
}
}
}
impl Transport for LinuxVirtio {
type Streams = BaseStreams<TimeoutFile>;
type Error = io::Error;
fn open_streams(&mut self) -> Result<Self::Streams, Self::Error> {
let mut open_opts = OpenOptions::new();
open_opts.read(true);
open_opts.write(true);
open_opts.append(true);
open_opts.truncate(false);
Ok(BaseStreams {
rpc: open_opts.open(&self.rpc)?.into(),
blob: open_opts.open(&self.blob)?.into(),
event: open_opts.open(&self.event)?.into(),
})
}
}
#[derive(Clone, Debug)]
pub struct BusdUnixSocket {
path: PathBuf,
}
impl BusdUnixSocket {
pub const DEFAULT_PATH: &str = "/run/oc2/bus";
#[must_use]
pub fn new() -> Self {
if let Ok(path) = std::env::var("OC2_BUS_SOCKET") {
Self::new_with_path(path)
} else {
Self::new_with_path(Self::DEFAULT_PATH)
}
}
#[must_use]
pub fn new_with_path<P: Into<PathBuf>>(path: P) -> Self {
Self { path: path.into() }
}
}
impl Default for BusdUnixSocket {
fn default() -> Self {
Self::new()
}
}
impl Transport for BusdUnixSocket {
type Streams = BusdStream<UnixStream>;
type Error = io::Error;
fn open_streams(&mut self) -> Result<Self::Streams, Self::Error> {
Ok(BusdStream(UnixStream::connect(&self.path)?))
}
}