use super::PidFd as InternalPidFd;
use crate::assert_matches;
use crate::os::fd::AsRawFd;
use crate::os::linux::process::{ChildExt, CommandExt as _};
use crate::os::unix::process::{CommandExt as _, ExitStatusExt};
use crate::process::Command;
use crate::sys::AsInner;

#[test]
fn test_command_pidfd() {
    let pidfd_open_available = probe_pidfd_support();

    // always exercise creation attempts
    let mut child = Command::new("false").create_pidfd(true).spawn().unwrap();

    // but only check if we know that the kernel supports pidfds.
    // We don't assert the precise value, since the standard library
    // might have opened other file descriptors before our code runs.
    if pidfd_open_available {
        assert!(child.pidfd().is_ok());
    }
    if let Ok(pidfd) = child.pidfd() {
        let flags = super::cvt(unsafe { libc::fcntl(pidfd.as_raw_fd(), libc::F_GETFD) }).unwrap();
        assert!(flags & libc::FD_CLOEXEC != 0);
    }
    assert!(child.id() > 0 && child.id() < -1i32 as u32);
    let status = child.wait().expect("error waiting on pidfd");
    assert_eq!(status.code(), Some(1));

    let mut child = Command::new("sleep").arg("1000").create_pidfd(true).spawn().unwrap();
    assert_matches!(child.try_wait(), Ok(None));
    child.kill().expect("failed to kill child");
    let status = child.wait().expect("error waiting on pidfd");
    assert_eq!(status.signal(), Some(libc::SIGKILL));

    let _ = Command::new("echo")
        .create_pidfd(false)
        .spawn()
        .unwrap()
        .pidfd()
        .expect_err("pidfd should not have been created when create_pid(false) is set");

    let _ = Command::new("echo")
        .spawn()
        .unwrap()
        .pidfd()
        .expect_err("pidfd should not have been created");

    // exercise the fork/exec path since the earlier attempts may have used pidfd_spawnp()
    let mut cmd = Command::new("false");
    let mut child = unsafe { cmd.pre_exec(|| Ok(())) }.create_pidfd(true).spawn().unwrap();

    let id = child.id();

    assert!(id > 0 && id < -1i32 as u32, "spawning with pidfd still returns a sane pid");

    if pidfd_open_available {
        assert!(child.pidfd().is_ok())
    }

    if let Ok(pidfd) = child.pidfd() {
        match pidfd.as_inner().pid() {
            Ok(pid) => assert_eq!(pid, id),
            Err(e) if matches!(e.raw_os_error(), Some(libc::EINVAL | libc::ENOTTY)) => {
                /* older kernel */
            }
            Err(e) => panic!("unexpected error getting pid from pidfd: {}", e),
        }
    }

    child.wait().expect("error waiting on child");
}

#[test]
fn test_pidfd() {
    if !probe_pidfd_support() {
        return;
    }

    let child = Command::new("sleep")
        .arg("1000")
        .create_pidfd(true)
        .spawn()
        .expect("executing 'sleep' failed");

    let fd = child.into_pidfd().unwrap();

    assert_matches!(fd.try_wait(), Ok(None));
    fd.kill().expect("kill failed");
    fd.kill().expect("sending kill twice failed");
    let status = fd.wait().expect("1st wait failed");
    assert_eq!(status.signal(), Some(libc::SIGKILL));

    // Trying to wait again for a reaped child is safe since there's no pid-recycling race.
    // But doing so may return an error.
    let res = fd.wait();
    match res {
        // older kernels
        Err(e) if e.raw_os_error() == Some(libc::ECHILD) => {}
        // 6.15+
        Ok(exit) if exit.signal() == Some(libc::SIGKILL) => {}
        other => panic!("expected ECHILD error, got {:?}", other),
    }

    // Ditto for additional attempts to kill an already-dead child.
    let res = fd.kill();
    assert_matches!(res, Err(e) if e.raw_os_error() == Some(libc::ESRCH));
}

fn probe_pidfd_support() -> bool {
    InternalPidFd::current_process().is_ok()
}
