use super::Transport as TransportTrait;
use super::{
CloneSide as CloneSideTrait, FFIRequest, FFIResponse,
RuntimeSide as RuntimeSideTrait, ZygoteHandleBase
};
use crate::ffi::errors::FFIError;
use crate::platform::low;
use crate::worker::executeFFI;
use crate::worker::{takeLastErrno, takeLastOsError};
use crate::zygote::ZygoteFlag;
use fxhash::FxHashMap;
use ipc_channel::ipc::{self, IpcOneShotServer, IpcReceiver, IpcSender};
use libloading::Library;
use serde::{Deserialize, Serialize};
use std::env;
use std::io;
use std::path::PathBuf;
use std::process::{Child, Command, Stdio};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
use std::time::{Duration, Instant};
pub const CloneFlag: &str = "__zygoteClone";
const BackendName: &str = "windows-rtlcloneuserprocess";
const CloneBootstrapTimeout: Duration = Duration::from_secs(20);
const CloneExitSetupServer: u32 = 11;
const CloneExitConnect: u32 = 12;
const CloneExitHello: u32 = 13;
const CloneExitSetup: u32 = 14;
#[derive(Serialize, Deserialize)]
pub enum ZygoteCommand
{
SpawnClone { bootstrapName: String }
}
#[derive(Serialize, Deserialize)]
pub enum ZygoteReply
{
Cloned { pid: u32 },
SpawnFailed
}
#[derive(Serialize, Deserialize)]
struct BootstrapToRuntime
{
commandTx: IpcSender<ZygoteCommand>,
replyRx: IpcReceiver<ZygoteReply>
}
#[derive(Serialize, Deserialize)]
struct CloneHello
{
setupName: String
}
#[derive(Serialize, Deserialize)]
struct CloneSetup
{
requestRx: IpcReceiver<FFIRequest>,
responseTx: IpcSender<FFIResponse>
}
pub struct Transport;
pub struct ZygoteHandle
{
pub base: ZygoteHandleBase,
pub commandTx: IpcSender<ZygoteCommand>,
pub replyRx: IpcReceiver<ZygoteReply>
}
pub struct RuntimeSide
{
pub pid: u32,
pub requestTx: IpcSender<FFIRequest>,
pub responseRx: IpcReceiver<FFIResponse>
}
pub struct CloneSide
{
pub requestRx: IpcReceiver<FFIRequest>,
pub responseTx: IpcSender<FFIResponse>
}
#[derive(Serialize, Deserialize)]
pub struct Bootstrap
{
pub pid: u32,
pub requestTx: IpcSender<FFIRequest>,
pub responseRx: IpcReceiver<FFIResponse>
}
impl TransportTrait for Transport
{
type RuntimeSide = RuntimeSide;
type CloneSide = CloneSide;
type Bootstrap = Bootstrap;
type ZygoteHandle = ZygoteHandle;
#[allow(dead_code)]
fn name() -> &'static str
{
BackendName
}
fn spawnZygote() -> io::Result<Self::ZygoteHandle>
{
let (server, serverName): (
IpcOneShotServer<BootstrapToRuntime>,
String
) = IpcOneShotServer::new().map_err(io::Error::other)?;
let currentExe: PathBuf = env::current_exe()?;
let process: Child = Command::new(currentExe)
.arg(ZygoteFlag)
.arg(&serverName)
.stdin(Stdio::null())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()?;
let (_rx, bootstrap): (
IpcReceiver<BootstrapToRuntime>,
BootstrapToRuntime
) = server.accept().map_err(|e| {
io::Error::other(format!("zygote bootstrap accept: {e}"))
})?;
Ok(ZygoteHandle {
base: ZygoteHandleBase { process },
commandTx: bootstrap.commandTx,
replyRx: bootstrap.replyRx
})
}
fn sendSpawnClone(handle: &Self::ZygoteHandle) -> io::Result<Self::Bootstrap>
{
let (requestTx, requestRx): (
IpcSender<FFIRequest>,
IpcReceiver<FFIRequest>
) = ipc::channel::<FFIRequest>()?;
let (responseTx, responseRx): (
IpcSender<FFIResponse>,
IpcReceiver<FFIResponse>
) = ipc::channel::<FFIResponse>()?;
let (server, serverName): (
IpcOneShotServer<CloneHello>,
String
) = IpcOneShotServer::new().map_err(io::Error::other)?;
handle
.commandTx
.send(ZygoteCommand::SpawnClone { bootstrapName: serverName.clone() })
.map_err(|e| {
io::Error::new(
io::ErrorKind::BrokenPipe,
format!("SpawnClone send failed: {e}")
)
})?;
let reply: ZygoteReply = handle.replyRx.recv().map_err(|e| {
io::Error::new(
io::ErrorKind::BrokenPipe,
format!("SpawnClone reply failed: {e}")
)
})?;
let pid: u32 = match reply
{
ZygoteReply::Cloned { pid } => pid,
ZygoteReply::SpawnFailed => return Err(io::Error::other(
"Main zygote failed to create a clone (RtlCloneUserProcess failed)"
))
};
if pid == 0
{
return Err(io::Error::other(
"Main zygote failed to create a clone (pid=0)"
));
}
let hello: CloneHello = acceptHello(server, &serverName, pid)?;
let setupTx: IpcSender<CloneSetup> = IpcSender::connect(hello.setupName)
.map_err(|e| {
low::killProcess(pid);
io::Error::other(format!(
"connecting to the setup server of clone {pid} failed: {e} ({})",
cloneStatus(pid)
))
})?;
setupTx
.send(CloneSetup { requestRx, responseTx })
.map_err(|e| {
low::killProcess(pid);
io::Error::other(format!(
"sending the channels to clone {pid} failed: {e} ({})",
cloneStatus(pid)
))
})?;
drop(setupTx);
Ok(Bootstrap { pid, requestTx, responseRx })
}
fn bootstrapPid(bootstrap: &Self::Bootstrap) -> u32
{
bootstrap.pid
}
fn zygoteControlLoop(flag: Option<String>) -> !
{
let serverName: String =
flag.expect("windows::zygoteControlLoop: missing IpcOneShotServer name");
zygoteLoop(serverName)
}
#[allow(dead_code)]
fn cloneEnter(flag: Option<String>) -> io::Result<(Self::CloneSide, Self::Bootstrap)>
{
let serverName: String =
flag.expect("windows::cloneEnter: missing IpcOneShotServer name");
cloneBootstrapLoop(serverName)
}
fn runtimeConnect(bootstrap: Self::Bootstrap) -> io::Result<Self::RuntimeSide>
{
Ok(RuntimeSide {
pid: bootstrap.pid,
requestTx: bootstrap.requestTx,
responseRx: bootstrap.responseRx
})
}
}
impl RuntimeSideTrait for RuntimeSide
{
fn send(&self, request: &FFIRequest) -> Result<(), String>
{
self
.requestTx
.send(request.clone())
.map_err(|e| {
format!(
"Zygote clone IPC failed while sending request: {e} ({})",
cloneStatus(self.pid)
)
})
}
fn recv(&self) -> Result<FFIResponse, String>
{
self
.responseRx
.recv()
.map_err(|e| {
format!(
"Zygote clone IPC failed while reading response: {e} ({})",
cloneStatus(self.pid)
)
})
}
}
impl CloneSideTrait for CloneSide
{
#[allow(dead_code)]
fn run(self, cache: &mut FxHashMap<String, Library>) -> !
{
let Self { requestRx, responseTx } = self;
let mut libraryCache: FxHashMap<String, Library> = std::mem::take(cache);
loop
{
let request: FFIRequest = match requestRx.recv()
{
Ok(r) => r,
Err(_) => std::process::exit(0)
};
let response: FFIResponse =
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
handleRequest(request, &mut libraryCache)
})) {
Ok(r) => r,
Err(_) => FFIResponse::Err(FFIError::Other(
"clone panicked while handling request".into()
))
};
if responseTx.send(response).is_err() {
std::process::exit(0);
}
}
}
}
fn cloneStatus(pid: u32) -> String
{
match low::processExitCode(pid)
{
None => format!("clone {pid} is running"),
Some(code) => format!(
"clone {pid} exited with code {code:#x}: {}",
cloneExitReason(code)
)
}
}
const fn cloneExitReason(code: u32) -> &'static str
{
match code
{
CloneExitSetupServer => "could not create its setup server",
CloneExitConnect => "could not connect to the Runtime",
CloneExitHello => "could not send its greeting to the Runtime",
CloneExitSetup => "did not receive its channels",
1 => "killed by the Runtime",
0xC000_0005 => "access violation",
0xC000_0409 => "fast fail (abort, or a panic that cannot unwind)",
u32::MAX => "gone before it could be queried",
_ => "unknown"
}
}
fn acceptHello(
server: IpcOneShotServer<CloneHello>,
serverName: &str,
pid: u32
) -> io::Result<CloneHello>
{
let finished: Arc<AtomicBool> = Arc::new(AtomicBool::new(false));
let watchdogFinished: Arc<AtomicBool> = Arc::clone(&finished);
let watchdogName: String = serverName.to_owned();
let spawned: io::Result<thread::JoinHandle<Option<String>>> = thread::Builder::new()
.name("chillffi-clone-watchdog".into())
.spawn(move || {
let deadline: Instant = Instant::now() + CloneBootstrapTimeout;
while !watchdogFinished.load(Ordering::Acquire)
{
let reason: Option<String> = if low::processExitCode(pid).is_some()
{
Some(format!("{} before bootstrap", cloneStatus(pid)))
}
else if Instant::now() >= deadline
{
Some(format!("clone {pid} did not bootstrap within {CloneBootstrapTimeout:?}"))
}
else
{
None
};
if let Some(reason) = reason
{
low::killProcess(pid);
let _ = IpcSender::<CloneHello>::connect(watchdogName);
return Some(reason);
}
thread::sleep(Duration::from_millis(10));
}
None
});
let watchdog: thread::JoinHandle<Option<String>> = match spawned
{
Ok(handle) => handle,
Err(e) =>
{
low::killProcess(pid);
return Err(io::Error::other(format!("clone watchdog spawn failed: {e}")));
}
};
let accepted = server.accept();
finished.store(true, Ordering::Release);
let reason: Option<String> = watchdog.join().unwrap_or(None);
match accepted
{
Ok((_rx, hello)) => Ok(hello),
Err(e) =>
{
low::killProcess(pid);
Err(io::Error::other(
reason.unwrap_or_else(|| format!("clone {pid} bootstrap accept failed: {e}"))
))
}
}
}
fn handleRequest(request: FFIRequest, cache: &mut FxHashMap<String, Library>) -> FFIResponse
{
match executeFFI(request, cache)
{
Ok(v) => FFIResponse::Ok(v, takeLastErrno(), takeLastOsError()),
Err(e) => FFIResponse::Err(e)
}
}
fn zygoteLoop(serverName: String) -> !
{
low::ignoreChildExits();
low::resolveCsrPortHandle();
let (commandTx, commandRx): (
IpcSender<ZygoteCommand>,
IpcReceiver<ZygoteCommand>
) = match ipc::channel::<ZygoteCommand>() {
Ok(p) => p,
Err(_) => std::process::exit(1)
};
let (replyTx, replyRx): (IpcSender<ZygoteReply>, IpcReceiver<ZygoteReply>) =
match ipc::channel::<ZygoteReply>() {
Ok(p) => p,
Err(_) => std::process::exit(1)
};
let bootstrapTx: IpcSender<BootstrapToRuntime> =
match IpcSender::connect(serverName) {
Ok(tx) => tx,
Err(_) => std::process::exit(1)
};
if bootstrapTx
.send(BootstrapToRuntime { commandTx, replyRx })
.is_err()
{
std::process::exit(1);
}
drop(bootstrapTx);
loop
{
let cmd: ZygoteCommand = match commandRx.recv() {
Ok(c) => c,
Err(_) => std::process::exit(0)
};
match cmd
{
ZygoteCommand::SpawnClone { bootstrapName } =>
{
match low::cloneProcess() {
Ok(result) => {
let pid: low::ProcessId = result.pid;
low::closeCloneHandles(&result);
let _ = replyTx.send(ZygoteReply::Cloned { pid });
}
Err(low::StatusProcessCloned) => {
std::mem::forget(commandRx);
std::mem::forget(replyTx);
let csrOk: bool = low::reconnectCsr();
if csrOk {
low::reattachConsole();
}
low::silenceCrashReporting();
cloneBootstrapLoop(bootstrapName)
}
Err(_) => {
let _ = replyTx.send(ZygoteReply::SpawnFailed);
}
}
}
}
}
}
fn cloneExit(code: u32) -> !
{
std::process::exit(code as i32)
}
fn cloneBootstrapLoop(helloName: String) -> !
{
low::decorrelateRandom();
let (setupServer, setupName): (
IpcOneShotServer<CloneSetup>,
String
) = match IpcOneShotServer::new() {
Ok(v) => v,
Err(e) => {
eprintln!("[clone] setup server failed: {e}");
cloneExit(CloneExitSetupServer)
}
};
let helloTx: IpcSender<CloneHello> = match IpcSender::connect(helloName) {
Ok(tx) => tx,
Err(e) => {
eprintln!("[clone] connect to the Runtime failed: {e}");
cloneExit(CloneExitConnect)
}
};
if let Err(e) = helloTx.send(CloneHello { setupName })
{
eprintln!("[clone] greeting failed: {e}");
cloneExit(CloneExitHello);
}
drop(helloTx);
let (setupRx, setup): (IpcReceiver<CloneSetup>, CloneSetup) =
match setupServer.accept() {
Ok(v) => v,
Err(e) => {
eprintln!("[clone] receiving the channels failed: {e}");
cloneExit(CloneExitSetup)
}
};
drop(setupRx);
let cache: &mut FxHashMap<String, Library> =
Box::leak(Box::new(FxHashMap::default()));
CloneSide {
requestRx: setup.requestRx,
responseTx: setup.responseTx
}.run(cache)
}
pub fn runAsClone() -> !
{
let serverName: String = env::args()
.nth(2)
.expect("zygote clone: missing IpcOneShotServer name (argv[2])");
low::silenceCrashReporting();
cloneBootstrapLoop(serverName)
}