use serde::{Deserialize, Serialize};
use tocat_api::normalize;
use crate::endpoint::{
Connection, EndpointStream, SyncHalves,
parse::{Opt, ParseEndpointError},
sys::size_if_pipe,
};
#[derive(Debug, Deserialize, Serialize)]
pub struct Stdio {
#[serde(default)]
pub name: Option<String>,
}
struct RawStd(std::mem::ManuallyDrop<std::fs::File>);
impl RawStd {
unsafe fn new(fd: std::os::fd::RawFd) -> Self {
use std::os::fd::FromRawFd;
Self(std::mem::ManuallyDrop::new(unsafe {
std::fs::File::from_raw_fd(fd)
}))
}
}
impl std::io::Read for RawStd {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
(&*self.0).read(buf)
}
}
impl std::io::Write for RawStd {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
(&*self.0).write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
(&*self.0).flush()
}
}
impl Stdio {
const SCHEME: &'static str = "stdio";
pub(super) fn parse<'a>(
_body: &str,
opts: impl Iterator<Item = Opt<'a>>,
) -> Result<Self, ParseEndpointError> {
let mut name = None;
for opt in opts {
match normalize(opt.key).as_str() {
"name" => name = Some(opt.string()?),
_ => return Err(opt.unsupported(Self::SCHEME)),
}
}
Ok(Self { name })
}
pub(super) fn label(&self) -> String {
self.name.clone().unwrap_or_else(|| "STDIO".to_string())
}
fn resize(buffer: usize) {
size_if_pipe(&std::io::stdin(), "stdin", buffer);
size_if_pipe(&std::io::stdout(), "stdout", buffer);
}
pub(super) fn connect(&self, buffer: usize) -> anyhow::Result<Connection> {
Self::resize(buffer);
Ok(EndpointStream::stdio().into_connection())
}
pub(super) fn connect_sync(&self, buffer: usize) -> SyncHalves {
Self::resize(buffer);
SyncHalves {
reader: Some(Box::new(unsafe { RawStd::new(0) })),
writer: Some(Box::new(unsafe { RawStd::new(1) })),
guard: None,
}
}
}