// This file was derived from rust's own libstd/process.rs with the following
// copyright:
//
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
use std::{ffi::OsStr, os::unix::ffi::OsStrExt};

use nix::{errno::Errno, fcntl::OFlag, unistd::pipe2};

use crate::unshare::{config::Config, ffi_util::ToCString, Command, Executable};

impl Command {
    /// Constructs a new `Command` for launching the program at
    /// path `program`, with the following default configuration:
    ///
    /// * No arguments to the program
    /// * Inherit the current process's environment
    /// * Inherit the current process's working directory
    /// * Inherit stdin/stdout/stderr for `spawn` or `status`, but create pipes for `output`
    ///
    /// Builder methods are provided to change these defaults and
    /// otherwise configure the process.
    pub fn new<S: AsRef<OsStr>>(program: S) -> Result<Command, Errno> {
        let exe = if program.as_ref().as_bytes().ends_with(b".so") {
            // SAFETY:
            // 1. The constructors of the library run in Syd process *unsandboxed*!
            //    This comes with great responsibility.
            // 2. We use RTLD_NOW explicitly to avoid potential problems
            //    with lazy loading. This is slower but safer.
            let lib = match unsafe {
                libloading::os::unix::Library::open(
                    Some(program.as_ref()),
                    nix::libc::RTLD_NOW | nix::libc::RTLD_LOCAL,
                )
            } {
                Ok(lib) => lib,
                Err(libloading::Error::DlOpen { desc }) => {
                    let desc = format!("{desc:?}");
                    eprintln!("syd: {desc}");

                    if desc.contains("found") || desc.contains("o such") {
                        return Err(Errno::ENOENT);
                    } else if desc.contains("denied") {
                        return Err(Errno::EACCES);
                    } else if desc.contains("supported") {
                        return Err(Errno::ENOSYS);
                    } else if desc.contains("nvalid") {
                        return Err(Errno::EINVAL);
                    } else if desc.contains("format") {
                        return Err(Errno::ENOEXEC);
                    } else {
                        return Err(Errno::EPERM);
                    }
                }
                Err(err) => {
                    eprintln!("syd: {err}");
                    return Err(Errno::EPERM);
                }
            };
            Executable::Library(lib)
        } else {
            Executable::Program((program.to_cstring(), vec![program.to_cstring()]))
        };
        Ok(Command {
            exe,
            config: Config::default(),
            before_unfreeze: None,
            pre_exec: None,
            seccomp_filter: None,
            seccomp_pipefd: (
                pipe2(OFlag::O_CLOEXEC | OFlag::O_DIRECT)?,
                pipe2(OFlag::O_CLOEXEC | OFlag::O_DIRECT)?,
            ),
        })
    }

    /// Add an argument to pass to the program.
    pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Command {
        if let Executable::Program((_, ref mut args)) = self.exe {
            args.push(arg.to_cstring());
        }
        self
    }

    /// Add multiple arguments to pass to the program.
    pub fn args<I, S>(&mut self, args: I) -> &mut Command
    where
        I: IntoIterator<Item = S>,
        S: AsRef<OsStr>,
    {
        for arg in args {
            self.arg(arg.as_ref());
        }
        self
    }
}
