diff --git a/cli/src/main.rs b/cli/src/main.rs index a8fc7a82..8af63064 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,6 +1,8 @@ use anyhow::Result; -async fn run() -> Result<()> { +/// The code called after we've done process global init and created +/// an async runtime. +async fn async_main() -> Result<()> { // Don't include timestamps and such because they're not really useful and // too verbose, and plus several log targets such as journald will already // include timestamps. @@ -15,12 +17,33 @@ async fn run() -> Result<()> { .with_writer(std::io::stderr) .init(); tracing::trace!("starting"); + // As you can see, the role of this file is mostly to just be a shim + // to call into the code that lives in the internal shared library. bootc_lib::cli::run_from_iter(std::env::args()).await } -#[tokio::main(flavor = "current_thread")] -async fn main() { - if let Err(e) = run().await { +/// Perform process global initialization, then create an async runtime +/// and do the rest of the work there. +fn run() -> Result<()> { + // Initialize global state before we've possibly created other threads, etc. + bootc_lib::cli::global_init()?; + // We only use the "current thread" runtime because we don't perform + // a lot of CPU heavy work in async tasks. Where we do work on the CPU, + // or we do want explicit concurrency, we typically use + // tokio::task::spawn_blocking to create a new OS thread explicitly. + let runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .expect("Failed to build tokio runtime"); + // And invoke the async_main + runtime.block_on(async move { async_main().await }) +} + +fn main() { + // In order to print the error in a custom format (with :#) our + // main simply invokes a run() where all the work is done. + // This code just captures any errors. + if let Err(e) = run() { tracing::error!("{:#}", e); std::process::exit(1); } diff --git a/lib/src/cli.rs b/lib/src/cli.rs index ee4dcb21..83d5ef0f 100644 --- a/lib/src/cli.rs +++ b/lib/src/cli.rs @@ -711,6 +711,21 @@ async fn usroverlay() -> Result<()> { .into()); } +/// Perform process global initialization. This should be called as early as possible +/// in the standard `main` function. +pub fn global_init() -> Result<()> { + let am_root = rustix::process::getuid().is_root(); + // Work around bootc-image-builder not setting HOME, in combination with podman (really c/common) + // bombing out if it is unset. + if std::env::var_os("HOME").is_none() && am_root { + // Setting the environment is thread-unsafe, but we ask calling code + // to invoke this as early as possible. (In practice, that's just the cli's `main.rs`) + // xref https://internals.rust-lang.org/t/synchronized-ffi-access-to-posix-environment-variable-functions/15475 + std::env::set_var("HOME", "/root"); + } + Ok(()) +} + /// Parse the provided arguments and execute. /// Calls [`structopt::clap::Error::exit`] on failure, printing the error message and aborting the program. pub async fn run_from_iter(args: I) -> Result<()>