onelf-rt 0.1.0

Runtime stub for onelf packed binaries
mod cache;
mod env;
mod fuse;
mod interp;
mod loader;
mod memfd;
mod metadata;
mod multicall;
mod portable;

use std::os::unix::process::CommandExt;

fn main() {
    let args: Vec<String> = std::env::args().collect();
    let argv0 = args.first().map(|s| s.as_str()).unwrap_or("onelf");

    let exec_path = std::fs::read_link("/proc/self/exe")
        .ok()
        .and_then(|p| p.to_str().map(String::from))
        .unwrap_or_default();

    let exe_path = std::path::Path::new(&exec_path);
    let exe_dir = exe_path.parent().unwrap_or(std::path::Path::new("."));
    let exe_name = exe_path
        .file_name()
        .and_then(|n| n.to_str())
        .unwrap_or("onelf");

    // Handle --onelf-portable-* flags (create dirs and exit)
    if portable::handle_portable_flags(&args, exe_dir, exe_name) {
        return;
    }

    let mut pkg = match loader::load() {
        Ok(p) => p,
        Err(e) => {
            eprintln!("onelf-rt: failed to load package: {e}");
            std::process::exit(1);
        }
    };

    let ep_idx = multicall::resolve_entrypoint(&pkg.manifest, argv0);

    if ep_idx >= pkg.manifest.entrypoints.len() {
        eprintln!("onelf-rt: no valid entrypoint found");
        std::process::exit(1);
    }

    let ep_name = pkg
        .manifest
        .get_string(pkg.manifest.entrypoints[ep_idx].name)
        .to_string();

    // Handle --onelf-icon / --onelf-desktop before dispatching
    if metadata::handle_metadata_flags(&args, &mut pkg, &ep_name) {
        return;
    }

    let ep_target_entry = pkg.manifest.entrypoints[ep_idx].target_entry as usize;
    let ep_working_dir = pkg.manifest.entrypoints[ep_idx].working_dir;
    let ep_memfd = pkg.manifest.entrypoints[ep_idx].is_memfd_eligible();

    let target_blocks = pkg.manifest.entries[ep_target_entry].blocks.clone();

    let ep_args_str = pkg
        .manifest
        .get_string(pkg.manifest.entrypoints[ep_idx].args)
        .to_string();
    let extra_args: Vec<String> = if ep_args_str.is_empty() {
        Vec::new()
    } else {
        ep_args_str.split('\x1f').map(String::from).collect()
    };

    // Build final args: extra_args + remaining argv (skip argv[0])
    let mut final_args = extra_args;
    if args.len() > 1 {
        final_args.extend_from_slice(&args[1..]);
    }

    // ONELF_MODE: "memfd", "fuse", or "cache" to force a specific mode.
    // Default order: memfd (if eligible) -> fuse -> cache
    let forced_mode = std::env::var("ONELF_MODE").ok();
    let force = forced_mode.as_deref();

    // Memfd mode: single static binary, no libs needed
    if force == Some("memfd") || (force.is_none() && ep_memfd) {
        if let Ok(data) = loader::read_payload_blocks(
            &mut pkg.file,
            pkg.footer.payload_offset,
            &target_blocks,
            pkg.dict.as_deref(),
        ) {
            let lib_paths_str = pkg.manifest.lib_dirs().join(":");
            env::setup_env("", argv0, &exec_path, &ep_name, "memfd", &lib_paths_str);
            portable::setup_portable(exe_dir, exe_name);

            if let Err(e) = memfd::execute_memfd(&data, argv0, &final_args) {
                if force == Some("memfd") {
                    eprintln!("onelf-rt: memfd execution failed: {e}");
                    std::process::exit(1);
                }
            }
        } else if force == Some("memfd") {
            eprintln!("onelf-rt: failed to read payload for memfd");
            std::process::exit(1);
        }
    }

    // Read interpreter metadata for cross-libc portability (if packed with interp patching)
    let interp_data = read_package_file(&mut pkg, ".onelf/interp");

    // FUSE mode: mount package as filesystem (default for non-memfd)
    if force != Some("cache") {
        fuse::execute_fuse(
            &mut pkg,
            ep_idx,
            argv0,
            &exec_path,
            &final_args,
            interp_data.as_deref(),
        );
        // Only reaches here if FUSE fell back (fusermount3 unavailable)
        if force == Some("fuse") {
            eprintln!("onelf-rt: FUSE mode unavailable");
            std::process::exit(1);
        }
    }

    // Cache extraction mode (fallback)
    let pkg_dir = match cache::ensure_extracted(&mut pkg) {
        Ok(d) => d,
        Err(e) => {
            eprintln!("onelf-rt: extraction failed: {e}");
            std::process::exit(1);
        }
    };

    let package_id = cache::hex(&pkg.manifest.header.package_id);
    let cache_base = cache::base_dir();

    // Auto-GC: prune stale cache entries
    let gc_max_age = std::env::var("ONELF_GC_MAX_AGE")
        .ok()
        .and_then(|v| v.parse::<u64>().ok())
        .unwrap_or(30);
    if gc_max_age > 0 {
        cache::auto_gc(&cache_base, gc_max_age * 86400, &package_id);
    }

    let target_path_str = pkg.manifest.entry_path(ep_target_entry);
    let target_path = pkg_dir.join(&target_path_str);

    if !target_path.exists() {
        eprintln!(
            "onelf-rt: entrypoint target does not exist: {}",
            target_path.display()
        );
        std::process::exit(1);
    }

    let pkg_dir_str = pkg_dir.to_str().unwrap_or("");
    let lib_paths_str = pkg.manifest.lib_dirs().join(":");
    env::setup_env(
        pkg_dir_str,
        argv0,
        &exec_path,
        &ep_name,
        "cache",
        &lib_paths_str,
    );
    portable::setup_portable(exe_dir, exe_name);

    // Handle working directory
    match ep_working_dir {
        onelf_format::WorkingDir::PackageRoot => {
            let _ = std::env::set_current_dir(&pkg_dir);
        }
        onelf_format::WorkingDir::EntrypointParent => {
            if let Some(parent) = target_path.parent() {
                let _ = std::env::set_current_dir(parent);
            }
        }
        onelf_format::WorkingDir::Inherit => {}
    }

    let lib_dirs = pkg.manifest.lib_dirs();
    if let Some(ref data) = interp_data {
        interp::setup_interp_symlink(data, &pkg_dir);
    }
    let mut cmd = interp::build_exec_command(&target_path, &pkg_dir, &lib_dirs, argv0, &final_args);

    let err = cmd.exec();

    eprintln!("onelf-rt: exec failed: {err}");
    std::process::exit(1);
}

fn read_package_file(pkg: &mut loader::PackageData, path: &str) -> Option<Vec<u8>> {
    let idx = (0..pkg.manifest.entries.len()).find(|&i| {
        pkg.manifest.entries[i].kind == onelf_format::EntryKind::File
            && pkg.manifest.entry_path(i) == path
    })?;
    loader::read_payload_blocks(
        &mut pkg.file,
        pkg.footer.payload_offset,
        &pkg.manifest.entries[idx].blocks,
        pkg.dict.as_deref(),
    )
    .ok()
}