diff --git a/Cargo.lock b/Cargo.lock index 59a43810..33a1437f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -268,6 +268,15 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0fc239e0f6cb375d2402d48afb92f76f5404fd1df208a41930ec81eda078bea" +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror 2.0.19", +] + [[package]] name = "compression-codecs" version = "0.4.38" @@ -1630,6 +1639,16 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "serde", +] + [[package]] name = "potential_utf" version = "0.1.5" @@ -1665,6 +1684,7 @@ name = "process-backend" version = "0.1.0" dependencies = [ "libc", + "postcard", "process-reader", "serde", "thiserror 2.0.19", diff --git a/Cargo.toml b/Cargo.toml index f936a1d9..9e08d2fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,7 +59,7 @@ byteorder = "1.4" error-graph = { version = "0.1.1", features = ["serde"] } failspot = "0.2.0" plain = { version = "0.2.3", default-features = false } -process-backend = { version = "0.1.0", path = "crates/linux/process-backend", features = ["testing"] } +process-backend = { version = "0.1.0", path = "crates/linux/process-backend", features = ["postcard", "unix_stream", "testing"] } # Used for parsing procfs info. # default-features is disabled since it pulls in chrono diff --git a/crates/linux/process-backend/Cargo.toml b/crates/linux/process-backend/Cargo.toml index d2329ec2..36e7b4b1 100644 --- a/crates/linux/process-backend/Cargo.toml +++ b/crates/linux/process-backend/Cargo.toml @@ -9,10 +9,13 @@ rust-version.workspace = true license.workspace = true [features] +postcard = ["dep:postcard"] testing = [] +unix_stream = [] [target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] libc.workspace = true +postcard = { version = "1.1.3", default-features = false, optional = true } process-reader = { version = "0.1.0", path = "../../process-reader", features = ["serde"] } serde.workspace = true thiserror.workspace = true diff --git a/crates/linux/process-backend/src/lib.rs b/crates/linux/process-backend/src/lib.rs index 38b513e2..29636379 100644 --- a/crates/linux/process-backend/src/lib.rs +++ b/crates/linux/process-backend/src/lib.rs @@ -1,4 +1,5 @@ #![no_std] +#![allow(dead_code)] #![cfg(any(target_os = "linux", target_os = "android"))] use core::ffi::CStr; @@ -12,6 +13,7 @@ mod drop_fail_handler; pub mod local; pub mod regs; +pub mod remote; mod wrapper; diff --git a/crates/linux/process-backend/src/remote/io/mod.rs b/crates/linux/process-backend/src/remote/io/mod.rs new file mode 100644 index 00000000..b43b4f4c --- /dev/null +++ b/crates/linux/process-backend/src/remote/io/mod.rs @@ -0,0 +1,22 @@ +use core::ffi::c_int; + +#[cfg(feature = "unix_stream")] +pub use unix_stream::UnixStream; + +#[cfg(feature = "unix_stream")] +mod unix_stream; + +pub type Result = core::result::Result; + +pub trait Io: core::fmt::Debug { + fn read_exact(&mut self, buf: &mut [u8]) -> Result<()>; + fn write_all(&mut self, buf: &[u8]) -> Result<()>; +} + +#[derive(Debug, serde::Deserialize, serde::Serialize, thiserror::Error)] +pub enum Error { + #[error("an OS error occurred. Errno: {0}")] + OsError(c_int), + #[error("unexpected end-of-file reached")] + UnexpectedEof, +} diff --git a/crates/linux/process-backend/src/remote/io/unix_stream.rs b/crates/linux/process-backend/src/remote/io/unix_stream.rs new file mode 100644 index 00000000..8c9793d6 --- /dev/null +++ b/crates/linux/process-backend/src/remote/io/unix_stream.rs @@ -0,0 +1,71 @@ +use super as io; +use crate::wrapper::{OwnedFd, errno}; +use core::ffi::c_int; +use io::{Error, Result}; + +#[derive(Debug)] +pub struct UnixStream(OwnedFd); + +impl UnixStream { + pub fn pair() -> Result<(UnixStream, UnixStream)> { + let mut fds = [0; 2]; + let rv = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }; + if rv == -1 { + return Err(Error::OsError(errno())); + } + let stream0 = unsafe { Self::from_raw_fd(fds[0]) }; + let stream1 = unsafe { Self::from_raw_fd(fds[1]) }; + Ok((stream0, stream1)) + } + + /// # Safety + /// + /// 'fd' must be a valid UNIX stream socket + pub unsafe fn from_raw_fd(fd: c_int) -> Self { + unsafe { Self(OwnedFd::new(fd)) } + } + + pub fn as_raw_fd(&self) -> c_int { + self.0.as_raw_fd() + } + + fn read(&mut self, buf: &mut [u8]) -> Result { + let rv = unsafe { libc::read(self.0.as_raw_fd(), buf.as_mut_ptr().cast(), buf.len()) }; + if rv == -1 { + return Err(Error::OsError(errno())); + } + let bytes_read = usize::try_from(rv).unwrap(); + Ok(bytes_read) + } + + fn write(&mut self, buf: &[u8]) -> Result { + let rv = unsafe { libc::write(self.0.as_raw_fd(), buf.as_ptr().cast(), buf.len()) }; + if rv == -1 { + return Err(Error::OsError(errno())); + } + let bytes_written = usize::try_from(rv).unwrap(); + Ok(bytes_written) + } +} + +impl io::Io for UnixStream { + fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<()> { + while !buf.is_empty() { + let bytes_read = self.read(buf)?; + if bytes_read == 0 { + return Err(Error::UnexpectedEof); + } + buf = &mut buf[bytes_read..]; + } + Ok(()) + } + + fn write_all(&mut self, mut buf: &[u8]) -> Result<()> { + while !buf.is_empty() { + let bytes_written = self.write(buf)?; + assert_ne!(bytes_written, 0); + buf = &buf[bytes_written..]; + } + Ok(()) + } +} diff --git a/crates/linux/process-backend/src/remote/mod.rs b/crates/linux/process-backend/src/remote/mod.rs new file mode 100644 index 00000000..e3c47574 --- /dev/null +++ b/crates/linux/process-backend/src/remote/mod.rs @@ -0,0 +1,4 @@ +pub mod io; +pub mod transport; + +mod wire; diff --git a/crates/linux/process-backend/src/remote/transport/mod.rs b/crates/linux/process-backend/src/remote/transport/mod.rs new file mode 100644 index 00000000..2343fb88 --- /dev/null +++ b/crates/linux/process-backend/src/remote/transport/mod.rs @@ -0,0 +1,55 @@ +use super as remote; +use remote::io; +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "postcard")] +pub mod postcard; + +pub trait Backend: core::fmt::Debug { + fn max_response_output_len(&self) -> usize; + fn send_request<'output, Req: Serialize, Resp: Deserialize<'output>>( + &'output mut self, + req: Req, + ) -> Result; +} + +pub trait Executor: core::fmt::Debug { + fn read_request<'args, D: Deserialize<'args>>(&'args mut self) -> Result; + fn write_response(&mut self, resp: S) -> Result<(), ExecutorError>; +} + +#[derive(Debug, thiserror::Error, Deserialize, Serialize)] +pub enum BackendError { + #[error("an error occurred serializing the request")] + SerializeRequest(#[source] SerializeError), + #[error("an error occurred deserializing the response")] + DeserializeResponse(#[source] DeserializeError), +} + +#[derive(Debug, thiserror::Error, Deserialize, Serialize)] +pub enum ExecutorError { + #[error("an error occurred deserializing the request")] + DeserializeRequest(#[source] DeserializeError), + #[error("an error occurred serializing the response")] + SerializeResponse(#[source] SerializeError), +} + +#[derive(Debug, thiserror::Error, serde::Deserialize, serde::Serialize)] +pub enum SerializeError { + #[cfg(feature = "postcard")] + #[error("a Postcard-specific serialize error occurred")] + Postcard(#[source] postcard::Error), + #[error("an I/O occurred during serialization")] + Io(#[source] io::Error), +} + +#[derive(Debug, thiserror::Error, serde::Deserialize, serde::Serialize)] +pub enum DeserializeError { + #[cfg(feature = "postcard")] + #[error("a Postcard-specific deserialize error occurred")] + Postcard(#[source] postcard::Error), + #[error("an I/O occurred during deserialization")] + Io(#[source] io::Error), + #[error("the I/O buffer was too small")] + BufferTooSmall, +} diff --git a/crates/linux/process-backend/src/remote/transport/postcard.rs b/crates/linux/process-backend/src/remote/transport/postcard.rs new file mode 100644 index 00000000..7aefc7c1 --- /dev/null +++ b/crates/linux/process-backend/src/remote/transport/postcard.rs @@ -0,0 +1,303 @@ +use super as transport; +use core::{marker::PhantomData, ops::Range}; +use remote::io; +use serde::{Deserialize, Serialize}; +use transport::remote; + +pub use postcard::Error; + +// Postcard requires an extra scratch buffer for deserializing a few fixed-size types. +// f64 is the largest type it does this for. +const MAX_TEMP_LEN: usize = size_of::(); + +/// The backend side of a Postcard transport +/// +/// See remote/wire.rs `operations!()` macro for notes on buffer sizing +#[derive(Debug)] +pub struct Backend { + io: Io, + output_buf: Buf, +} + +impl Backend { + pub fn new(io: Io, output_buf: Buf) -> Self { + Self { io, output_buf } + } +} + +impl transport::Backend for Backend +where + Io: io::Io, + Buf: AsRef<[u8]> + AsMut<[u8]> + core::fmt::Debug, +{ + fn max_response_output_len(&self) -> usize { + self.output_buf.as_ref().len() + } + + fn send_request<'output, Req: Serialize, Resp: Deserialize<'output>>( + &'output mut self, + req: Req, + ) -> Result { + serialize_to_io(&mut self.io, req).map_err(transport::BackendError::SerializeRequest)?; + deserialize_from_io(&mut self.io, self.output_buf.as_mut()) + .map_err(transport::BackendError::DeserializeResponse) + } +} + +/// The executor side of a Postcard transport +/// +/// See remote/wire.rs `operations!()` macro for notes on buffer sizing +#[derive(Debug)] +pub struct Executor { + io: Io, + args_buf: Buf, +} + +impl Executor { + pub fn new(io: Io, args_buf: Buf) -> Self { + Self { io, args_buf } + } +} + +impl transport::Executor for Executor +where + Io: io::Io, + Buf: AsMut<[u8]> + core::fmt::Debug, +{ + fn read_request<'args, D: Deserialize<'args>>( + &'args mut self, + ) -> Result { + deserialize_from_io(&mut self.io, self.args_buf.as_mut()) + .map_err(transport::ExecutorError::DeserializeRequest) + } + + fn write_response(&mut self, resp: S) -> Result<(), transport::ExecutorError> { + serialize_to_io(&mut self.io, resp).map_err(transport::ExecutorError::SerializeResponse) + } +} + +fn serialize_to_io( + io: &mut Io, + s: S, +) -> Result<(), transport::SerializeError> { + let mut serializer = postcard::Serializer { + output: SerFlavor::new(io), + }; + match s.serialize(&mut serializer) { + Ok(()) => Ok(()), + Err(e) => { + let e = postcard::ser_flavors::Flavor::finalize(serializer.output) + .unwrap() + .unwrap_or(transport::SerializeError::Postcard(e)); + Err(e) + } + } +} + +fn deserialize_from_io<'output, Io: io::Io, D: Deserialize<'output>>( + io: &'output mut Io, + buf: &'output mut [u8], +) -> Result { + let mut deserializer = postcard::Deserializer::from_flavor(DeFlavor::new(io, buf)); + match D::deserialize(&mut deserializer) { + Ok(d) => Ok(d), + Err(e) => { + let e = deserializer + .finalize() + .unwrap() + .unwrap_or(transport::DeserializeError::Postcard(e)); + Err(e) + } + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// +// So... What's the deal with us returning `postcard::Error::NotYetImplemented` below? +// =================================================================================== +// +// The Postcard wire format is awesome, but the way it propagates errors is... Less awesome. +// (To be fair - the authors are aware and are trying to fix this in v2). +// +// We need a way to smuggle errors past Postcard's `Error` unit enum, so when an error +// occurs we record the real cause in a field of our flavor structs and return +// `NotYetImplemented` because it's an error Postcard would never actually return (since we don't +// use any of its unimplemented functionality). +// +// On the other end, after the Serde call completes, we call `finalize()` to unwrap our flavor +// object, allowing us to read the real error that occurred (if any). +// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +struct SerFlavor<'io, Io> { + io: &'io mut Io, + error: Option, +} + +impl<'io, Io> SerFlavor<'io, Io> { + pub fn new(io: &'io mut Io) -> Self { + Self { io, error: None } + } +} + +impl<'io, Io: io::Io> postcard::ser_flavors::Flavor for SerFlavor<'io, Io> { + type Output = Option; + + fn try_push(&mut self, data: u8) -> postcard::Result<()> { + match self.io.write_all(&[data]) { + Ok(()) => Ok(()), + Err(e) => { + self.error = Some(transport::SerializeError::Io(e)); + Err(postcard::Error::NotYetImplemented) + } + } + } + + fn finalize(self) -> postcard::Result { + Ok(self.error) + } + + fn try_extend(&mut self, data: &[u8]) -> postcard::Result<()> { + match self.io.write_all(data) { + Ok(()) => Ok(()), + Err(e) => { + self.error = Some(transport::SerializeError::Io(e)); + Err(postcard::Error::NotYetImplemented) + } + } + } +} + +struct DeFlavor<'io, 'buf, Io> { + inner: DeFlavorInner<'io, 'buf, Io>, + error: Option, +} + +impl<'io, 'buf, Io> DeFlavor<'io, 'buf, Io> { + fn new(io: &'io mut Io, buf: &'buf mut [u8]) -> Self { + Self { + inner: DeFlavorInner { + io, + alloc_buf: BumpAllocator::new(buf), + temp_buf: [0u8; MAX_TEMP_LEN], + }, + error: None, + } + } + + fn with_inner<'a, F, T>(&'a mut self, f: F) -> postcard::Result + where + F: FnOnce(&'a mut DeFlavorInner<'io, 'buf, Io>) -> Result, + { + let (inner, error) = (&mut self.inner, &mut self.error); + + match f(inner) { + Ok(t) => Ok(t), + Err(e) => { + *error = Some(e); + Err(Error::NotYetImplemented) + } + } + } +} + +impl<'io, 'buf, Io: io::Io> postcard::de_flavors::Flavor<'buf> for DeFlavor<'io, 'buf, Io> +where + Self: 'buf, +{ + type Remainder = Option; + + type Source = (); + + fn pop(&mut self) -> postcard::Result { + self.with_inner(|inner| inner.pop()) + } + + fn try_take_n(&mut self, ct: usize) -> postcard::Result<&'buf [u8]> { + self.with_inner(|inner| inner.try_take_n(ct)) + } + + fn finalize(self) -> postcard::Result { + Ok(self.error) + } + + fn try_take_n_temp<'a>(&'a mut self, ct: usize) -> postcard::Result<&'a [u8]> + where + 'buf: 'a, + { + self.with_inner(|inner| inner.try_take_n_temp(ct)) + } +} + +struct DeFlavorInner<'io, 'buf, Io> { + io: &'io mut Io, + alloc_buf: BumpAllocator<'buf>, + temp_buf: [u8; MAX_TEMP_LEN], +} + +impl<'io, 'buf, Io: io::Io> DeFlavorInner<'io, 'buf, Io> +where + Self: 'buf, +{ + fn pop(&mut self) -> Result { + let mut buf = [0u8]; + self.io + .read_exact(&mut buf) + .map_err(transport::DeserializeError::Io)?; + Ok(buf[0]) + } + + fn try_take_n(&mut self, ct: usize) -> Result<&'buf [u8], transport::DeserializeError> { + let buf = self + .alloc_buf + .allocate(ct) + .ok_or(transport::DeserializeError::BufferTooSmall)?; + self.io + .read_exact(buf) + .map_err(transport::DeserializeError::Io)?; + Ok(buf) + } + + fn try_take_n_temp<'a>(&'a mut self, ct: usize) -> Result<&'a [u8], transport::DeserializeError> + where + 'buf: 'a, + { + let buf = self + .temp_buf + .get_mut(0..ct) + .ok_or(transport::DeserializeError::BufferTooSmall)?; + self.io + .read_exact(buf) + .map_err(transport::DeserializeError::Io)?; + Ok(buf) + } +} + +struct BumpAllocator<'buf> { + start: *mut u8, + end: *mut u8, + phantom: PhantomData<&'buf mut [u8]>, +} + +impl<'buf> BumpAllocator<'buf> { + pub fn new(buf: &'buf mut [u8]) -> Self { + let Range { start, end } = buf.as_mut_ptr_range(); + Self { + start, + end, + phantom: PhantomData, + } + } + + pub fn allocate(&mut self, n: usize) -> Option<&'buf mut [u8]> { + let bytes_left = (self.end as usize) - (self.start as usize); + if bytes_left < n { + return None; + } + unsafe { + let buf = core::slice::from_raw_parts_mut(self.start, n); + self.start = self.start.add(n); + Some(buf) + } + } +} diff --git a/crates/linux/process-backend/src/remote/wire/cstr_with_null.rs b/crates/linux/process-backend/src/remote/wire/cstr_with_null.rs new file mode 100644 index 00000000..d6179e37 --- /dev/null +++ b/crates/linux/process-backend/src/remote/wire/cstr_with_null.rs @@ -0,0 +1,72 @@ +use core::ffi::CStr; + +/// A version of &CStr that is serialized and deserialized with null terminator +/// +/// The default Serde `Serialize` for this serializes it without the NULL terminator, and as a +/// result `Deserialize` is only implemented for `CString` but not `&CStr` (since you can't have +/// a `&CStr` with no null terminator. +/// +/// This is a wrapper around &CStr that serializes and deserializes the null terminator - which is +/// what we need for directly making syscalls. +#[derive(Debug)] +#[repr(transparent)] +pub(crate) struct CStrWithNull<'a>(&'a CStr); + +impl<'a> From> for &'a CStr { + fn from(value: CStrWithNull<'a>) -> Self { + value.0 + } +} + +impl<'a> From<&'a CStr> for CStrWithNull<'a> { + fn from(value: &'a CStr) -> Self { + Self(value) + } +} + +impl<'a> core::ops::Deref for CStrWithNull<'a> { + type Target = CStr; + fn deref(&self) -> &Self::Target { + self.0 + } +} + +impl<'a> serde::Serialize for CStrWithNull<'a> { + fn serialize(&self, serializer: S) -> core::result::Result + where + S: serde::Serializer, + { + serializer.serialize_bytes(self.0.to_bytes_with_nul()) + } +} + +impl<'de: 'a, 'a> serde::Deserialize<'de> for CStrWithNull<'a> { + fn deserialize(deserializer: D) -> core::result::Result + where + D: serde::Deserializer<'de>, + { + struct Visitor<'a>(core::marker::PhantomData<&'a [u8]>); + + impl<'de: 'a, 'a> serde::de::Visitor<'de> for Visitor<'a> { + type Value = CStrWithNull<'a>; + + fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { + write!( + formatter, + "a null-terminated byte slice borrowed from the Deserializer" + ) + } + + fn visit_borrowed_bytes(self, v: &'de [u8]) -> core::result::Result + where + E: serde::de::Error, + { + CStr::from_bytes_with_nul(v).map(CStrWithNull).map_err(|_| { + serde::de::Error::invalid_value(serde::de::Unexpected::Bytes(v), &self) + }) + } + } + + deserializer.deserialize_bytes(Visitor(core::marker::PhantomData)) + } +} diff --git a/crates/linux/process-backend/src/remote/wire/mod.rs b/crates/linux/process-backend/src/remote/wire/mod.rs new file mode 100644 index 00000000..b039a60f --- /dev/null +++ b/crates/linux/process-backend/src/remote/wire/mod.rs @@ -0,0 +1,190 @@ +use super as remote; +use crate::{ProcessReaderKind, regs::*, wrapper::Stat}; +use remote::transport; +use serde::{Deserialize, Serialize}; + +pub(crate) use cstr_with_null::CStrWithNull; + +mod cstr_with_null; + +pub(crate) trait Operation { + type Args<'args>: Deserialize<'args> + Serialize; + type Output<'output>: Deserialize<'output> + Serialize; + fn request<'output, T>( + transport: &'output mut T, + args: Self::Args<'_>, + ) -> Result, RemoteError>, transport::BackendError> + where + T: transport::Backend; +} + +// TEMPORARY - Will be the executor::Error type when the executor is implemented +pub(crate) type RemoteError = (); + +macro_rules! operations({ + $( + $(#[cfg($($cfg_tt: tt)*)])? + $name: ident($arg: ty) -> $output: ty, + )* +} => { + pub(crate) mod ops { + use super::*; + + $( + + $(#[cfg($($cfg_tt)*)])? + pub(crate) enum $name {} + + $(#[cfg($($cfg_tt)*)])? + impl Operation for $name { + type Args<'args> = $arg; + type Output<'output> = $output; + fn request<'output, T>( + transport: &'output mut T, + args: Self::Args<'_>, + ) -> Result, RemoteError>, transport::BackendError> + where + T: transport::Backend, + { + transport.send_request::<'output, Request, Result, RemoteError>>( + Request::$name(args), + ) + } + } + + )* + } + + pub(crate) trait HandlesRequests { + $( + $(#[cfg($($cfg_tt)*)])? + #[allow(non_snake_case)] + fn $name<'args, 'output>(self, args: $arg) -> Result<$output, RemoteError> + where + Self: 'output; + )* + } + + pub(crate) fn serve_request( + transport: &mut T, + handler: H, + ) -> Result<(), transport::ExecutorError> + where + T: transport::Executor, + H: HandlesRequests, + { + let request = transport.read_request::<'_, Request<'_>>()?; + match request { + $( + $(#[cfg($($cfg_tt)*)])? + Request::$name(args) => { + let response = HandlesRequests::$name(handler, args); + transport.write_response::::Output<'_>, RemoteError>>( + response, + ) + } + )* + } + } + + #[derive(Debug, Deserialize, Serialize)] + enum Request<'args> { + $( + $(#[cfg($($cfg_tt)*)])? + $name(#[serde(borrow)] ::Args<'args>), + )* + } +}); + +#[derive(Debug, Deserialize, Serialize)] +pub(crate) struct FileReaderArgs { + pub(crate) handle: usize, + pub(crate) requested_len: usize, +} + +#[derive(Debug, Deserialize, Serialize)] +pub(crate) struct ProcessReaderReadAtArgs { + pub(crate) address: usize, + pub(crate) requested_len: usize, +} + +#[derive(Debug, Deserialize, Serialize)] +pub(crate) struct MappedModuleMemoryReaderOpenArgs<'args> { + #[serde(borrow)] + pub(crate) path: CStrWithNull<'args>, + pub(crate) offset: u64, +} + +#[derive(Debug, Deserialize, Serialize)] +pub(crate) struct MappedModuleMemoryReaderReadAtArgs { + pub(crate) handle: usize, + pub(crate) offset: usize, + pub(crate) requested_len: usize, +} + +#[derive(Debug, Deserialize, Serialize)] +pub(crate) struct ReadLinkArgs<'args> { + #[serde(borrow)] + pub(crate) path: CStrWithNull<'args>, + pub(crate) requested_len: usize, +} + +// Notes on buffer sizing: +// +// The size of buffer-based arguments in these operations are limited by the +// `executor::Transport` argument buffer, the `backend::Transport` output buffer, and +// the `Executor` scratch buffer. +// +// Currently: +// +// The length of the argument buffer limits the maximum path length for: +// read_file(), read_dir(), map_module_into_memory(), stat_file(), read_link() +// +// Sizing the Executor scratch buffer larger than the Backend output buffer is a bad idea - it +// means the Executor could request more bytes than the Backend is capable of holding, causing a +// `BufferTooSmall` error. Sizing the scratch buffer smaller is fine, but then the output buffer +// would never be totally utilized. +// +// So, in practice, the scratch buffer and output buffer should be the same size. +// +// The length of both limits the maximum readable path length for: +// DirReader::read(), read_link() +// +// TL;DR +// +// The argument buffer should be the maximum length of a path you'd ever request for any operation +// that takes a path. +// +// The output buffer should be the maximum length of any directory name you'd expect returned +// by read_dir() and any path you'd ever expect returned by read_link(). + +operations! { + GetPid(()) -> libc::pid_t, + ProcessReaderReadAt(ProcessReaderReadAtArgs) -> &'output [u8], + StopProcess(()) -> (), + ContinueProcess(()) -> (), + SuspendThread(libc::pid_t) -> (), + ResumeThread(libc::pid_t) -> (), + MappedModuleMemoryReaderOpen(MappedModuleMemoryReaderOpenArgs<'args>) -> usize, + MappedModuleMemoryReaderReadAt(MappedModuleMemoryReaderReadAtArgs) -> &'output [u8], + MappedModuleMemoryReaderLen(usize) -> usize, + MappedModuleMemoryReaderClose(usize) -> (), + StatFile(CStrWithNull<'args>) -> Stat, + FileReaderOpen(CStrWithNull<'args>) -> usize, + FileReaderRead(FileReaderArgs) -> &'output [u8], + FileReaderClose(usize) -> (), + DirReaderOpen(CStrWithNull<'args>) -> usize, + DirReaderRead(usize) -> Option<&'output [u8]>, + DirReaderClose(usize) -> (), + ReadLink(ReadLinkArgs<'args>) -> &'output [u8], + GetGenRegs(libc::pid_t) -> GenRegs, + GetFpRegs(libc::pid_t) -> FpRegs, + #[cfg(target_arch = "x86")] + GetFpxRegs(libc::pid_t) -> FpxRegs, + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + PtracePeekUser(usize) -> [u8; crate::PTRACE_DATA_LEN], + ForceProcessReaderKind(ProcessReaderKind) -> (), + #[cfg(feature = "testing")] + FailOneSyscallWith(core::ffi::c_int) -> (), + Quit(()) -> (), +}