#[doc(hidden)]
pub struct RemoteFn {
pub uuid: &'static str,
pub function: fn(args: &[u8]) -> Vec<u8>,
}
inventory::collect!(RemoteFn);
#[doc(hidden)]
#[derive(serde::Deserialize, serde::Serialize)]
pub struct RpcPayload {
pub uuid: String,
pub data: Vec<u8>,
}
pub fn remote_rpc() -> anyhow_serde::Result<()> {
std::fs::remove_file(std::env::current_exe()?)?;
let m = inventory::iter::<RemoteFn>
.into_iter()
.map(|rfn| (rfn.uuid, rfn.function))
.collect::<std::collections::HashMap<_, _>>();
if m.is_empty() {
return Ok(());
}
let mut stdin = std::io::stdin().lock();
let mut stdout = std::io::stdout().lock();
let mut buffer = Vec::with_capacity(4096);
loop {
buffer.clear();
use std::io::BufRead as _;
match stdin.read_until(0x00, &mut buffer) {
Ok(0) => break,
Ok(_) => {
let parsed_payload = match postcard::from_bytes_cobs::<RpcPayload>(&mut buffer) {
Ok(payload) => payload,
Err(err) => panic!("Corrupted packet received: {err}"),
};
let result = m
.get(parsed_payload.uuid.as_str())
.expect("Called an unregistered remote function")(
&parsed_payload.data
);
use std::io::Write as _;
stdout.write_all(&result).expect("Couldn't write to stdout");
stdout.flush().expect("Couldn't flush stdout");
}
Err(err) => panic!("Error reading stdin: {err}"),
}
}
Ok(())
}
pub use anyhow_serde::Result;
pub use anyhow_serde::bail;
pub use inventory;
#[doc(hidden)]
pub use postcard;
#[doc(hidden)]
pub use serde;
pub use fox_macros::{main, remote, rpc};
pub mod builtin {
use std::path::PathBuf;
use super as fox;
use fox_macros::remote;
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
pub struct Mode(pub u32);
impl From<Mode> for std::fs::Permissions {
fn from(Mode(value): Mode) -> Self {
use std::os::unix::fs::PermissionsExt as _;
std::fs::Permissions::from_mode(value)
}
}
impl From<u32> for Mode {
fn from(value: u32) -> Self {
Mode(value)
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct CopyArgs {
pub source: Vec<u8>,
pub destination: PathBuf,
pub owner: String,
pub group: String,
pub mode: Mode,
}
#[derive(Debug, thiserror::Error)]
#[fox::rpc]
pub enum CopyError {
#[error("asdf")]
InvalidUser,
#[error("asdf")]
InvalidGroup,
#[error("asdf")]
ParentDirUnavailable,
#[error("asdf")]
CannotCreateTempFile,
#[error("asdf")]
CannotWriteToTempFile,
#[error("asdf")]
Chown,
#[error("asdf")]
Chmod,
#[error("asdf")]
TempFilePersist,
}
#[remote]
pub fn copy(args: CopyArgs) -> Result<(), CopyError> {
use std::io::Write as _;
let user = nix::unistd::User::from_name(&args.owner)
.ok()
.flatten()
.ok_or(CopyError::InvalidUser)?;
let group = nix::unistd::Group::from_name(&args.group)
.ok()
.flatten()
.ok_or(CopyError::InvalidGroup)?;
let mut tmp_file = tempfile::Builder::new()
.prefix(".fox.")
.tempfile_in(
args.destination
.parent()
.ok_or(CopyError::ParentDirUnavailable)?,
)
.map_err(|_| CopyError::CannotCreateTempFile)?;
tmp_file
.write_all(&args.source)
.map_err(|_| CopyError::CannotWriteToTempFile)?;
nix::unistd::chown(tmp_file.path(), Some(user.uid), Some(group.gid))
.map_err(|_| CopyError::Chown)?;
std::fs::set_permissions(tmp_file.path(), args.mode.into())
.map_err(|_| CopyError::Chmod)?;
tmp_file
.persist(&args.destination)
.map_err(|_| CopyError::TempFilePersist)?;
Ok(())
}
}