use std::io::Read;
#[cfg(feature = "pdf-render")]
use std::io::Write;
use std::path::Path;
use std::process::{Command, Stdio};
pub const WORKER_ARG: &str = "--__cuttlefish_render_worker";
pub fn run_if_worker() {
let args: Vec<String> = std::env::args().collect();
let Some(position) = args.iter().position(|a| a == WORKER_ARG) else {
return;
};
#[cfg(not(feature = "pdf-render"))]
{
let _ = position;
eprintln!(
"this binary was spawned as a PDF render worker but was built without the \
`pdf-render` feature. The daemon binary does the rendering, so it needs the \
feature too: `cargo build -p cuttlefishd --features pdf-render`. Enabling it \
only on cuttlefish-host is not enough."
);
std::process::exit(2);
}
#[cfg(feature = "pdf-render")]
{
let fail = |message: &str| -> ! {
eprintln!("{message}");
std::process::exit(2);
};
let (Some(path), Some(page), Some(width)) = (
args.get(position + 1),
args.get(position + 2).and_then(|s| s.parse::<u32>().ok()),
args.get(position + 3).and_then(|s| s.parse::<u16>().ok()),
) else {
fail("render worker: expected <path> <page> <width>");
};
match crate::documents::render_page_in_process(Path::new(path), page, width) {
Ok(png) => {
if let Err(e) = std::io::stdout().write_all(&png) {
fail(&format!("render worker: writing output: {e}"));
}
let _ = std::io::stdout().flush();
std::process::exit(0);
}
Err(e) => fail(&format!("{e}")),
}
}
}
pub const WORKER_EXE_ENV: &str = "CUTTLEFISH_RENDER_WORKER";
pub fn render_page(path: &Path, page: u32, width: u16) -> anyhow::Result<Vec<u8>> {
let exe = match std::env::var(WORKER_EXE_ENV) {
Ok(exe) if !exe.is_empty() => std::path::PathBuf::from(exe),
_ => std::env::current_exe().map_err(|e| {
anyhow::anyhow!("locating this executable to spawn a render worker: {e}")
})?,
};
let mut child = Command::new(exe)
.arg(WORKER_ARG)
.arg(path)
.arg(page.to_string())
.arg(width.to_string())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.map_err(|e| anyhow::anyhow!("spawning the render worker: {e}"))?;
let mut png = Vec::new();
let mut message = String::new();
if let Some(mut out) = child.stdout.take() {
out.read_to_end(&mut png)?;
}
if let Some(mut err) = child.stderr.take() {
err.read_to_string(&mut message)?;
}
let status = child.wait()?;
if status.success() {
return Ok(png);
}
#[cfg(unix)]
{
use std::os::unix::process::ExitStatusExt;
if let Some(signal) = status.signal() {
anyhow::bail!(
"the PDF renderer crashed (signal {signal}) on {}. The page was \
not rendered, but the daemon is unaffected — this is why \
rendering runs in a separate process.",
path.display()
);
}
}
let message = message.trim();
if message.is_empty() {
anyhow::bail!(
"the PDF renderer failed with status {status} on {}",
path.display()
);
}
anyhow::bail!("{message}");
}