//! An elf-specific extension of `super::Args` and parsing implementation to match gnu style
//! linkers.

use super::ArgumentParser;
use super::BSymbolicKind;
use super::Input;
use super::InputSpec;
use crate::alignment::Alignment;
use crate::arch::Architecture;
use crate::arch::SUPPORTED_EMULATIONS;
use crate::arch::SUPPORTED_TARGETS;
use crate::args::CommonArgs;
use crate::args::CopyRelocations;
use crate::args::CopyRelocationsDisabledReason;
use crate::args::FileReplacementMode;
use crate::args::Modifiers;
use crate::args::RelocationModel;
use crate::args::UnresolvedSymbols;
use crate::args::VersionMode;
use crate::args::parse_number;
use crate::bail;
use crate::error::Context as _;
use crate::error::Result;
use crate::linker_script::maybe_forced_sysroot;
use crate::output_kind::OutputKind;
use crate::output_section_id::SectionName;
use crate::platform;
use crate::platform::Args as _;
use hashbrown::HashMap;
use hashbrown::HashSet;
use indexmap::IndexSet;
use itertools::Itertools;
use object::Endianness;
use object::elf::GNU_PROPERTY_X86_ISA_1_BASELINE;
use object::elf::GNU_PROPERTY_X86_ISA_1_V2;
use object::elf::GNU_PROPERTY_X86_ISA_1_V3;
use object::elf::GNU_PROPERTY_X86_ISA_1_V4;
use std::ffi::CString;
use std::num::NonZero;
use std::num::NonZeroU32;
use std::num::NonZeroU64;
use std::num::NonZeroUsize;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::AtomicI64;

#[derive(Debug)]
pub struct ElfArgs {
    pub(crate) common: super::CommonArgs,

    pub(crate) arch: Architecture,
    pub(crate) lib_search_path: Vec<Box<Path>>,
    pub(crate) dynamic_linker: Option<Box<Path>>,
    pub(crate) strip: Strip,
    pub(crate) merge_sections: bool,
    pub(crate) version_script_path: Option<PathBuf>,
    pub(crate) should_write_eh_frame_hdr: bool,
    pub(crate) wrap: Vec<String>,
    pub(crate) rpath: Option<String>,
    pub(crate) soname: Option<String>,
    pub(crate) exclude_libs: ExcludeLibs,
    pub(crate) gc_sections: bool,
    pub(crate) build_id: BuildIdOption,

    // Whether to emit errors if our input objects have undefined symbols that we can't resolve. If
    // not specified, then the behaviour depends on whether we're emitting a shared object or an
    // executable.
    no_undefined: Option<bool>,

    pub(crate) allow_shlib_undefined: bool,
    pub(crate) needs_origin_handling: bool,
    pub(crate) needs_nodelete_handling: bool,
    pub(crate) copy_relocations: CopyRelocations,
    pub(crate) sysroot: Option<Box<Path>>,
    pub(crate) undefined: Vec<String>,
    pub(crate) relro: bool,
    pub(crate) entry: Option<String>,
    pub(crate) export_all_dynamic_symbols: bool,
    pub(crate) export_list: Vec<String>,
    pub(crate) export_list_path: Option<PathBuf>,
    pub(crate) auxiliary: Vec<String>,
    pub(crate) enable_new_dtags: bool,
    pub(crate) plugin_path: Option<String>,
    pub(crate) plugin_args: Vec<CString>,

    /// Symbol definitions from `--defsym` options. Each entry is (symbol_name, value_or_symbol).
    pub(crate) defsym: Vec<(String, String)>,

    /// Section start addresses from `--section-start` options. Maps section name to address.
    pub(crate) section_start: HashMap<Vec<u8>, u64>,

    /// Segment start address overrides from `-Ttext`, `-Tdata`, `-Tbss`.
    /// Used to implement `SEGMENT_START("name", default)` per GNU ld behaviour.
    pub(crate) ttext: Option<u64>,
    pub(crate) tdata: Option<u64>,
    pub(crate) tbss: Option<u64>,

    /// If set, GC stats will be written to the specified filename.
    pub(crate) write_gc_stats: Option<PathBuf>,

    /// If set, and we're writing GC stats, then ignore any input files that contain any of the
    /// specified substrings.
    pub(crate) gc_stats_ignore: Vec<String>,

    pub(crate) verbose_gc_stats: bool,

    pub(crate) dependency_file: Option<PathBuf>,
    pub(crate) execstack: bool,
    pub(crate) got_plt_syms: bool,
    pub(crate) b_symbolic: BSymbolicKind,
    pub(crate) relax: bool,
    pub(crate) should_write_linker_identity: bool,
    pub(crate) hash_style: HashStyle,
    pub(crate) unresolved_symbols: UnresolvedSymbols,
    pub(crate) error_unresolved_symbols: bool,
    pub(crate) allow_multiple_definitions: bool,
    pub(crate) z_interpose: bool,
    pub(crate) z_isa: Option<NonZeroU32>,
    pub(crate) z_stack_size: Option<NonZeroU64>,
    pub(crate) z_pack_relative_relocs: bool,
    pub(crate) max_page_size: Option<Alignment>,
    pub(crate) trace: bool,
    pack_dyn_relocs: PackDynRelocs,
    pub(crate) use_android_relr_tags: bool,
    pub(crate) discard_sframe: bool,

    pub(crate) should_output_executable: bool,
    pub(crate) should_output_partial_object: bool,

    pub(crate) nmagic: bool,
    pub(crate) rosegment: bool,
    pub(crate) gdb_index: bool,

    rpath_set: IndexSet<String>,

    experimental_sframe: bool,

    pub(crate) debug_compression_kind: Option<CompressionKind>,
    pub(crate) sort_section: Option<SortSectionMode>,
    pub(crate) output_format_endian: Option<Endianness>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum SortSectionMode {
    Name,
    Alignment,
}

#[derive(Debug)]
pub(crate) enum Strip {
    Nothing,
    Debug,
    All,
    Retain(HashSet<Vec<u8>>),
}

#[derive(Debug)]
pub(crate) enum BuildIdOption {
    None,
    Fast,
    Hex(Vec<u8>),
    Uuid,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum HashStyle {
    Gnu,
    Sysv,
    Both,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum ExcludeLibs {
    None,
    All,
    Some(HashSet<Box<str>>),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum PackDynRelocs {
    None,
    Android,
    AndroidRelr,
    Relr,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum CompressionKind {
    Zlib,
    Zstd,
}

impl ExcludeLibs {
    pub(crate) fn should_exclude(&self, lib_path: &[u8]) -> bool {
        match self {
            ExcludeLibs::None => false,
            ExcludeLibs::All => true,
            ExcludeLibs::Some(libs) => {
                let lib_path_str = String::from_utf8_lossy(lib_path);
                let lib_name = lib_path_str.rsplit('/').next().unwrap_or(&lib_path_str);

                libs.contains(lib_name)
            }
        }
    }
}

impl HashStyle {
    pub(crate) const fn includes_gnu(self) -> bool {
        matches!(self, HashStyle::Gnu | HashStyle::Both)
    }

    pub(crate) const fn includes_sysv(self) -> bool {
        matches!(self, HashStyle::Sysv | HashStyle::Both)
    }
}

// These flags don't currently affect our behaviour. TODO: Assess whether we should error or warn if
// these are given. This is tricky though. On the one hand we want to be a drop-in replacement for
// other linkers. On the other, we should perhaps somehow let the user know that we don't support a
// feature.
const SILENTLY_IGNORED_FLAGS: &[&str] = &[
    // Just like other modern linkers, we don't need groups in order to resolve cycles.
    "start-group",
    "end-group",
    // TODO: This is supposed to suppress built-in search paths, but I don't think we have any
    // built-in search paths. Perhaps we should?
    "nostdlib",
    // TODO
    "no-undefined-version",
    "fatal-warnings",
    "color-diagnostics",
    "undefined-version",
    "sort-common",
    "stats",
];
const SILENTLY_IGNORED_SHORT_FLAGS: &[&str] = &[
    "(",
    ")",
    // On Illumos, the Clang driver inserts a meaningless -C flag before calling any non-GNU ld
    // linker.
    #[cfg(target_os = "illumos")]
    "C",
];

const IGNORED_FLAGS: &[&str] = &[
    "fix-cortex-a53-835769",
    "fix-cortex-a53-843419",
    "discard-all",
    "x", // alias for --discard-all
];

// These flags map to the default behavior of the linker.
const DEFAULT_FLAGS: &[&str] = &[
    "no-call-graph-profile-sort",
    "no-copy-dt-needed-entries",
    "no-add-needed",
    "discard-locals",
    "no-fatal-warnings",
];
const DEFAULT_SHORT_FLAGS: &[&str] = &[
    "X", // alias for --discard-locals
];

impl Default for ElfArgs {
    fn default() -> Self {
        Self {
            common: CommonArgs::default(),

            arch: default_target_arch(),

            lib_search_path: Vec::new(),
            should_output_executable: true,
            should_output_partial_object: false,
            dynamic_linker: None,
            strip: Strip::Nothing,
            // For now, we default to --gc-sections. This is different to other linkers, but other
            // than being different, there doesn't seem to be any downside to doing
            // this. We don't currently do any less work if we're not GCing sections,
            // but do end up writing more, so --no-gc-sections will almost always be as
            // slow or slower than --gc-sections. For that reason, the latter is
            // probably a good default.
            gc_sections: true,
            merge_sections: true,
            copy_relocations: CopyRelocations::Allowed,
            version_script_path: None,
            should_write_eh_frame_hdr: false,
            write_gc_stats: None,
            wrap: Vec::new(),
            gc_stats_ignore: Vec::new(),
            verbose_gc_stats: false,
            rpath: None,
            soname: None,
            enable_new_dtags: true,
            execstack: false,
            needs_origin_handling: false,
            needs_nodelete_handling: false,
            should_write_linker_identity: true,
            build_id: BuildIdOption::None,
            exclude_libs: ExcludeLibs::None,
            no_undefined: None,
            allow_shlib_undefined: false,
            sysroot: None,
            dependency_file: None,
            undefined: Vec::new(),
            relro: true,
            entry: None,
            b_symbolic: BSymbolicKind::None,
            export_all_dynamic_symbols: false,
            export_list: Vec::new(),
            export_list_path: None,
            defsym: Vec::new(),
            section_start: HashMap::new(),
            ttext: None,
            tdata: None,
            tbss: None,
            got_plt_syms: false,
            relax: true,
            hash_style: HashStyle::Both,
            trace: false,
            pack_dyn_relocs: PackDynRelocs::None,
            use_android_relr_tags: false,
            discard_sframe: false,

            nmagic: false,
            rosegment: true,

            unresolved_symbols: UnresolvedSymbols::ReportAll,
            error_unresolved_symbols: true,
            allow_multiple_definitions: false,
            z_interpose: false,
            z_stack_size: None,
            z_isa: None,
            z_pack_relative_relocs: false,
            max_page_size: None,
            auxiliary: Vec::new(),
            rpath_set: Default::default(),
            plugin_path: None,
            plugin_args: Vec::new(),

            experimental_sframe: false,
            debug_compression_kind: None,
            sort_section: None,
            gdb_index: false,
            output_format_endian: None,
        }
    }
}

const fn default_target_arch() -> Architecture {
    // We default to targeting the architecture that we're running on. We don't support running on
    // architectures that we can't target.
    #[cfg(target_arch = "x86_64")]
    {
        return Architecture::X86_64;
    }
    #[cfg(target_arch = "aarch64")]
    {
        return Architecture::AArch64;
    }
    #[cfg(target_arch = "riscv64")]
    {
        return Architecture::RiscV64;
    }
    #[cfg(target_arch = "loongarch64")]
    {
        return Architecture::LoongArch64;
    }
    #[cfg(all(target_arch = "powerpc64", target_endian = "little"))]
    {
        return Architecture::Ppc64;
    }

    #[allow(unreachable_code)]
    Architecture::Unsupported
}

impl ElfArgs {
    pub(crate) fn new() -> Result<Self> {
        Ok(Self {
            common: CommonArgs::from_env()?,
            ..Default::default()
        })
    }

    pub(crate) fn is_relr_enabled(&self) -> bool {
        self.z_pack_relative_relocs
            || self.pack_dyn_relocs == PackDynRelocs::Relr
            || self.pack_dyn_relocs == PackDynRelocs::AndroidRelr
    }
}

// Parse the supplied input arguments, which should not include the program name.
pub(crate) fn parse<S: AsRef<str>, I: Iterator<Item = S>>(
    args: &mut ElfArgs,
    mut input: I,
) -> Result {
    let mut modifier_stack = vec![Modifiers::default()];

    let arg_parser = setup_argument_parser();
    while let Some(arg) = input.next() {
        let arg = arg.as_ref();

        arg_parser.handle_argument(args, &mut modifier_stack, arg, &mut input)?;
    }

    // Copy relocations are only permitted when building executables.
    if !args.should_output_executable {
        args.copy_relocations =
            CopyRelocations::Disallowed(CopyRelocationsDisabledReason::SharedObject);
    }

    if !args.rpath_set.is_empty() {
        args.rpath = Some(std::mem::take(&mut args.rpath_set).into_iter().join(":"));
    }

    args.common.report_unrecognized()?;

    if !args.auxiliary.is_empty() && args.should_output_executable {
        bail!("-f may not be used without -shared");
    }

    if args.pack_dyn_relocs == PackDynRelocs::Android
        || args.pack_dyn_relocs == PackDynRelocs::AndroidRelr
    {
        args.warn_unsupported("--pack-dyn-relocs=android")?;
    }

    if args.nmagic {
        // GNU_RELRO requires segments to start on page boundaries and cover an entire page
        args.relro = false;
        if args.max_page_size.is_some() {
            args.warning("-z max-page-size is incompatible with --nmagic");
        }
        args.common_mut()
            .inputs
            .iter_mut()
            .for_each(|input| input.modifiers.allow_shared = false);
    }

    if !args.experimental_sframe {
        args.discard_sframe = true;
    }

    Ok(())
}

fn setup_argument_parser() -> ArgumentParser<ElfArgs> {
    let mut parser = ArgumentParser::<ElfArgs>::new();

    parser
        .declare_with_param()
        .prefix("L")
        .help("Add directory to library search path")
        .execute(|args, _modifier_stack, value| {
            let handle_sysroot = |path| {
                args.sysroot
                    .as_ref()
                    .and_then(|sysroot| maybe_forced_sysroot(path, sysroot))
                    .unwrap_or_else(|| Box::from(path))
            };

            let dir = handle_sysroot(Path::new(value));
            args.common_mut().save_dir.handle_file(value);
            args.lib_search_path.push(dir);
            Ok(())
        });

    parser
        .declare_with_param()
        .prefix("l")
        .help("Link with library")
        .sub_option_with_value(
            ":filename",
            "Link with specific file",
            |args, modifier_stack, value| {
                let stripped = value.strip_prefix(':').unwrap_or(value);
                let spec = InputSpec::File(Box::from(Path::new(stripped)));
                args.common_mut().inputs.push(Input {
                    spec,
                    search_first: None,
                    modifiers: *modifier_stack.last().unwrap(),
                });
                Ok(())
            },
        )
        .sub_option_with_value(
            "libname",
            "Link with library libname.so or libname.a",
            |args, modifier_stack, value| {
                let spec = InputSpec::Lib(Box::from(value));
                args.common_mut().inputs.push(Input {
                    spec,
                    search_first: None,
                    modifiers: *modifier_stack.last().unwrap(),
                });
                Ok(())
            },
        )
        .execute(|args, modifier_stack, value| {
            let spec = if let Some(stripped) = value.strip_prefix(':') {
                InputSpec::Search(Box::from(stripped))
            } else {
                InputSpec::Lib(Box::from(value))
            };
            args.common_mut().inputs.push(Input {
                spec,
                search_first: None,
                modifiers: *modifier_stack.last().unwrap(),
            });
            Ok(())
        });

    parser
        .declare_with_param()
        .prefix("u")
        .help("Force resolution of the symbol")
        .execute(|args, _modifier_stack, value| {
            args.undefined.push(value.to_owned());
            Ok(())
        });

    parser
        .declare_with_param()
        .prefix("m")
        .help("Set target architecture")
        .sub_option("elf_x86_64", "x86-64 ELF target", |args, _| {
            args.arch = Architecture::X86_64;
            Ok(())
        })
        .sub_option(
            "elf_x86_64_sol2",
            "x86-64 ELF target (Solaris)",
            |args, _| {
                if args.dynamic_linker.is_none() {
                    args.dynamic_linker = Some(Path::new("/lib/amd64/ld.so.1").into());
                }
                args.arch = Architecture::X86_64;
                Ok(())
            },
        )
        .sub_option("aarch64elf", "AArch64 ELF target", |args, _| {
            args.arch = Architecture::AArch64;
            Ok(())
        })
        .sub_option("aarch64linux", "AArch64 ELF target (Linux)", |args, _| {
            args.arch = Architecture::AArch64;
            Ok(())
        })
        .sub_option("elf64lriscv", "RISC-V 64-bit ELF target", |args, _| {
            args.arch = Architecture::RiscV64;
            Ok(())
        })
        .sub_option(
            "elf64loongarch",
            "LoongArch 64-bit ELF target",
            |args, _| {
                args.arch = Architecture::LoongArch64;
                Ok(())
            },
        )
        .sub_option("elf64lppc", "PowerPC64 LE ELF target", |args, _| {
            args.arch = Architecture::Ppc64;
            Ok(())
        })
        .execute(|_args, _modifier_stack, value| {
            bail!("-m {value} is not yet supported");
        });

    parser
        .declare_with_param()
        .prefix("z")
        .help("Linker option")
        .sub_option("now", "Resolve all symbols immediately", |_, _| Ok(()))
        .sub_option(
            "origin",
            "Mark object as requiring immediate $ORIGIN",
            |args, _| {
                args.needs_origin_handling = true;
                Ok(())
            },
        )
        .sub_option("relro", "Enable RELRO program header", |args, _| {
            args.relro = true;
            Ok(())
        })
        .sub_option("norelro", "Disable RELRO program header", |args, _| {
            args.relro = false;
            Ok(())
        })
        .sub_option("notext", "Do not report DT_TEXTREL as an error", |_, _| {
            Ok(())
        })
        .sub_option("nostart-stop-gc", "Disable start/stop symbol GC", |_, _| {
            Ok(())
        })
        .sub_option(
            "execstack",
            "Mark object as requiring an executable stack",
            |args, _| {
                args.execstack = true;
                Ok(())
            },
        )
        .sub_option(
            "noexecstack",
            "Mark object as not requiring an executable stack",
            |args, _| {
                args.execstack = false;
                Ok(())
            },
        )
        .sub_option("nocopyreloc", "Disable copy relocations", |args, _| {
            args.copy_relocations =
                CopyRelocations::Disallowed(CopyRelocationsDisabledReason::Flag);
            Ok(())
        })
        .sub_option(
            "nodelete",
            "Mark shared object as non-deletable",
            |args, _| {
                args.needs_nodelete_handling = true;
                Ok(())
            },
        )
        .sub_option(
            "defs",
            "Report unresolved symbol references when writing shared object",
            |args, _| {
                args.no_undefined = Some(true);
                Ok(())
            },
        )
        .sub_option(
            "undefs",
            "Do not report unresolved symbol references when writing shared object",
            |args, _| {
                args.no_undefined = Some(false);
                Ok(())
            },
        )
        .sub_option("muldefs", "Allow multiple definitions", |args, _| {
            args.allow_multiple_definitions = true;
            Ok(())
        })
        .sub_option("lazy", "Use lazy binding (default)", |_, _| Ok(()))
        .sub_option(
            "interpose",
            "Mark object to interpose all DSOs but executable",
            |args, _| {
                args.z_interpose = true;
                Ok(())
            },
        )
        .sub_option_with_value(
            "stack-size=",
            "Set size of stack segment",
            |args, _, value| {
                let size: u64 = parse_number(value)?;
                args.z_stack_size = NonZero::new(size);

                Ok(())
            },
        )
        .sub_option(
            "pack-relative-relocs",
            "Pack relative relocations into SHT_RELR",
            |args, _| {
                args.z_pack_relative_relocs = true;
                Ok(())
            },
        )
        .sub_option(
            "nopack-relative-relocs",
            "Do not pack relative relocations into SHT_RELR (default)",
            |args, _| {
                args.z_pack_relative_relocs = false;
                Ok(())
            },
        )
        .sub_option(
            "x86-64-baseline",
            "Mark x86-64-baseline ISA as needed",
            |args, _| {
                args.z_isa = NonZero::new(GNU_PROPERTY_X86_ISA_1_BASELINE);
                Ok(())
            },
        )
        .sub_option("x86-64-v2", "Mark x86-64-v2 ISA as needed", |args, _| {
            args.z_isa = NonZero::new(GNU_PROPERTY_X86_ISA_1_V2);
            Ok(())
        })
        .sub_option("x86-64-v3", "Mark x86-64-v3 ISA as needed", |args, _| {
            args.z_isa = NonZero::new(GNU_PROPERTY_X86_ISA_1_V3);
            Ok(())
        })
        .sub_option("x86-64-v4", "Mark x86-64-v4 ISA as needed", |args, _| {
            args.z_isa = NonZero::new(GNU_PROPERTY_X86_ISA_1_V4);
            Ok(())
        })
        .sub_option_with_value(
            "max-page-size=",
            "Set maximum page size for load segments",
            |args, _, value| {
                let size: u64 = parse_number(value)?;
                if !size.is_power_of_two() {
                    bail!("Invalid alignment {size:#x}");
                }
                args.max_page_size = Some(Alignment {
                    exponent: size.trailing_zeros() as u8,
                });

                Ok(())
            },
        )
        .execute(|args, _modifier_stack, value| {
            args.warn_unsupported(&(format!("-z {value}")))?;
            Ok(())
        });

    parser
        .declare_with_param()
        .prefix("R")
        .help("Add runtime library search path")
        .execute(|args, _modifier_stack, value| {
            if Path::new(value).is_file() {
                args.common_mut()
                    .unrecognized_options
                    .push(format!("-R,{value}(filename)"));
            } else {
                args.rpath_set.insert(value.to_string());
            }
            Ok(())
        });

    parser
        .declare_with_param()
        .prefix("O")
        .execute(|_args, _modifier_stack, _value|
        // We don't use opt-level for now.
        Ok(()));

    parser
        .declare()
        .long("static")
        .long("Bstatic")
        .help("Disallow linking of shared libraries")
        .execute(|_args, modifier_stack| {
            modifier_stack.last_mut().unwrap().allow_shared = false;
            Ok(())
        });

    parser
        .declare()
        .long("Bdynamic")
        .help("Allow linking of shared libraries")
        .execute(|_args, modifier_stack| {
            modifier_stack.last_mut().unwrap().allow_shared = true;
            Ok(())
        });

    parser
        .declare_with_param()
        .long("output")
        .prefix("o")
        .help("Set the output filename")
        .execute(|args, _modifier_stack, value| {
            args.common.output = Arc::from(Path::new(value));
            Ok(())
        });

    parser
        .declare()
        .long("strip-all")
        .short("s")
        .help("Strip all symbols")
        .execute(|args, _modifier_stack| {
            args.strip = Strip::All;
            Ok(())
        });

    parser
        .declare()
        .long("strip-debug")
        .short("S")
        .help("Strip debug symbols")
        .execute(|args, _modifier_stack| {
            args.strip = Strip::Debug;
            Ok(())
        });

    parser
        .declare()
        .long("gc-sections")
        .help("Enable removal of unused sections")
        .execute(|args, _modifier_stack| {
            args.gc_sections = true;
            Ok(())
        });

    parser
        .declare()
        .long("no-gc-sections")
        .help("Disable removal of unused sections")
        .execute(|args, _modifier_stack| {
            args.gc_sections = false;
            Ok(())
        });

    parser
        .declare()
        .long("shared")
        .long("Bshareable")
        .help("Create a shared library")
        .execute(|args, _modifier_stack| {
            args.should_output_executable = false;
            Ok(())
        });

    parser
        .declare()
        .long("pie")
        .long("pic-executable")
        .help("Create a position-independent executable")
        .execute(|args, _modifier_stack| {
            args.common.relocation_model = RelocationModel::Relocatable;
            args.should_output_executable = true;
            Ok(())
        });

    parser
        .declare()
        .long("no-pie")
        .help("Do not create a position-independent executable (default)")
        .execute(|args, _modifier_stack| {
            args.common.relocation_model = RelocationModel::NonRelocatable;
            args.should_output_executable = true;
            Ok(())
        });

    parser
        .declare()
        .short("r")
        .long("relocatable")
        .help("Create a relocatable object file")
        .execute(|args, _modifier_stack| {
            args.should_output_executable = false;
            args.should_output_partial_object = true;
            args.gc_sections = false;
            args.relro = false;
            args.should_write_linker_identity = false;
            args.merge_sections = false;
            Ok(())
        });

    parser
        .declare_with_param()
        .long("pack-dyn-relocs")
        .help("Specify dynamic relocation packing format")
        .execute(|args, _modifier_stack, value| {
            match value {
                "none" => args.pack_dyn_relocs = PackDynRelocs::None,
                "relr" => args.pack_dyn_relocs = PackDynRelocs::Relr,
                "android" => args.pack_dyn_relocs = PackDynRelocs::Android,
                "android+relr" => args.pack_dyn_relocs = PackDynRelocs::AndroidRelr,
                value => {
                    args.warn_unsupported(&format!("--pack-dyn-relocs={value}"))?;
                }
            }
            Ok(())
        });

    parser
        .declare()
        .long("help")
        .help("Show this help message")
        .execute(|_args, _modifier_stack| {
            use std::io::Write as _;
            let parser = setup_argument_parser();
            let mut stdout = std::io::stdout().lock();
            writeln!(stdout, "{}", parser.generate_help())?;

            // The following listing is something autoconf detection relies on.
            writeln!(stdout, "wild: supported targets: {SUPPORTED_TARGETS}")?;
            writeln!(stdout, "wild: supported emulations: {SUPPORTED_EMULATIONS}")?;

            std::process::exit(0);
        });

    parser
        .declare()
        .long("version")
        .help("Show version information and exit")
        .execute(|args, _modifier_stack| {
            args.common.version_mode = VersionMode::ExitAfterPrint;
            Ok(())
        });

    parser
        .declare()
        .short("v")
        .help("Print version and continue linking if object files are specified")
        .execute(|args, _modifier_stack| {
            args.common.version_mode = VersionMode::Verbose;
            Ok(())
        });

    parser
        .declare()
        .short("V")
        .help("Print version along with supported emulations and continue linking if object files are specified")
        .execute(|args, _modifier_stack| {
            args.common.version_mode = VersionMode::VerboseWithEmulations;
            Ok(())
        });

    parser
        .declare()
        .long("demangle")
        .help("Enable symbol demangling")
        .execute(|args, _modifier_stack| {
            args.common_mut().demangle = true;
            Ok(())
        });

    parser
        .declare()
        .long("no-demangle")
        .help("Disable symbol demangling")
        .execute(|args, _modifier_stack| {
            args.common_mut().demangle = false;
            Ok(())
        });

    parser
        .declare_with_param()
        .long("dynamic-linker")
        .help("Set dynamic linker path")
        .execute(|args, _modifier_stack, value| {
            args.dynamic_linker = Some(Box::from(Path::new(value)));
            Ok(())
        });

    parser
        .declare()
        .long("no-dynamic-linker")
        .help("Omit the load-time dynamic linker request")
        .execute(|args, _modifier_stack| {
            args.dynamic_linker = None;
            Ok(())
        });

    parser
        .declare_with_param()
        .long("entry")
        .short("e")
        .help("Set the entry point")
        .execute(|args, _modifier_stack, value| {
            args.entry = Some(value.to_owned());
            Ok(())
        });

    parser
        .declare_with_param()
        .long("wild-experiments")
        .help("List of numbers. Used to tweak internal parameters. '_' keeps default value.")
        .execute(|args, _modifier_stack, value| {
            args.common_mut().numeric_experiments = value
                .split(',')
                .map(|p| {
                    if p == "_" {
                        Ok(None)
                    } else {
                        Ok(Some(p.parse()?))
                    }
                })
                .collect::<Result<Vec<Option<u64>>>>()?;
            Ok(())
        });

    parser
        .declare()
        .long("as-needed")
        .help("Set DT_NEEDED if used")
        .execute(|_args, modifier_stack| {
            modifier_stack.last_mut().unwrap().as_needed = true;
            Ok(())
        });

    parser
        .declare()
        .long("no-as-needed")
        .help("Always set DT_NEEDED")
        .execute(|_args, modifier_stack| {
            modifier_stack.last_mut().unwrap().as_needed = false;
            Ok(())
        });

    parser
        .declare()
        .long("whole-archive")
        .help("Include all objects from archives")
        .execute(|_args, modifier_stack| {
            modifier_stack.last_mut().unwrap().whole_archive = true;
            Ok(())
        });

    parser
        .declare()
        .long("no-whole-archive")
        .help("Disable --whole-archive")
        .execute(|_args, modifier_stack| {
            modifier_stack.last_mut().unwrap().whole_archive = false;
            Ok(())
        });

    parser
        .declare()
        .long("push-state")
        .help("Save current linker flags")
        .execute(|_args, modifier_stack| {
            modifier_stack.push(*modifier_stack.last().unwrap());
            Ok(())
        });

    parser
        .declare()
        .long("pop-state")
        .help("Restore previous linker flags")
        .execute(|_args, modifier_stack| {
            modifier_stack.pop();
            if modifier_stack.is_empty() {
                bail!("Mismatched --pop-state");
            }
            Ok(())
        });

    parser
        .declare()
        .long("eh-frame-hdr")
        .help("Create .eh_frame_hdr section")
        .execute(|args, _modifier_stack| {
            args.should_write_eh_frame_hdr = true;
            Ok(())
        });

    parser
        .declare()
        .long("no-eh-frame-hdr")
        .help("Don't create .eh_frame_hdr section")
        .execute(|args, _modifier_stack| {
            args.should_write_eh_frame_hdr = false;
            Ok(())
        });

    parser
        .declare()
        .long("export-dynamic")
        .short("E")
        .help("Export all dynamic symbols")
        .execute(|args, _modifier_stack| {
            args.export_all_dynamic_symbols = true;
            Ok(())
        });

    parser
        .declare()
        .long("no-export-dynamic")
        .help("Do not export dynamic symbols")
        .execute(|args, _modifier_stack| {
            args.export_all_dynamic_symbols = false;
            Ok(())
        });

    parser
        .declare_with_param()
        .long("compress-debug-sections")
        .help("Compress debug sections using zlib or zstd")
        .execute(|args, _modifier_stack, value| {
            match value {
                "none" => args.debug_compression_kind = None,
                "zlib" | "zlib-gabi" => args.debug_compression_kind = Some(CompressionKind::Zlib),
                "zstd" => args.debug_compression_kind = Some(CompressionKind::Zstd),
                value => bail!("--compress-debug-sections={value}"),
            }
            Ok(())
        });

    parser
        .declare()
        .long("gdb-index")
        .help("Generate GDB index")
        .execute(|args, _modifier_stack| {
            args.gdb_index = true;
            Ok(())
        });

    parser
        .declare()
        .long("no-gdb-index")
        .help("Disable GDB index generation")
        .execute(|args, _modifier_stack| {
            args.gdb_index = false;
            Ok(())
        });

    parser
        .declare_with_param()
        .long("soname")
        .prefix("h")
        .help("Set shared object name")
        .execute(|args, _modifier_stack, value| {
            args.soname = Some(value.to_owned());
            Ok(())
        });

    parser
        .declare_with_param()
        .long("rpath")
        .help("Add directory to runtime library search path")
        .execute(|args, _modifier_stack, value| {
            args.rpath_set.insert(value.to_string());
            Ok(())
        });

    parser
        .declare()
        .long("no-string-merge")
        .help("Disable section merging")
        .execute(|args, _modifier_stack| {
            args.merge_sections = false;
            Ok(())
        });

    parser
        .declare()
        .long("no-undefined")
        .help("Do not allow unresolved symbols in object files")
        .execute(|args, _modifier_stack| {
            args.no_undefined = Some(true);
            Ok(())
        });

    parser
        .declare()
        .long("allow-multiple-definition")
        .help("Allow multiple definitions of symbols")
        .execute(|args, _modifier_stack| {
            args.allow_multiple_definitions = true;
            Ok(())
        });

    parser
        .declare()
        .long("relax")
        .help("Enable target-specific optimization (instruction relaxation)")
        .execute(|args, _modifier_stack| {
            args.relax = true;
            Ok(())
        });

    parser
        .declare()
        .long("no-relax")
        .help("Disable relaxation")
        .execute(|args, _modifier_stack| {
            args.relax = false;
            Ok(())
        });

    parser
        .declare()
        .long("got-plt-syms")
        .help("Write symbol table entries that point to the GOT/PLT entry for symbols")
        .execute(|args, _modifier_stack| {
            args.got_plt_syms = true;
            Ok(())
        });

    parser
        .declare()
        .long("Bsymbolic")
        .help("Bind global references locally")
        .execute(|args, _modifier_stack| {
            args.b_symbolic = BSymbolicKind::All;
            Ok(())
        });

    parser
        .declare()
        .long("Bsymbolic-functions")
        .help("Bind global function references locally")
        .execute(|args, _modifier_stack| {
            args.b_symbolic = BSymbolicKind::Functions;
            Ok(())
        });

    parser
        .declare()
        .long("Bsymbolic-non-weak-functions")
        .help("Bind non-weak global function references locally")
        .execute(|args, _modifier_stack| {
            args.b_symbolic = BSymbolicKind::NonWeakFunctions;
            Ok(())
        });

    parser
        .declare()
        .long("Bsymbolic-non-weak")
        .help("Bind non-weak global references locally")
        .execute(|args, _modifier_stack| {
            args.b_symbolic = BSymbolicKind::NonWeak;
            Ok(())
        });

    parser
        .declare()
        .long("Bno-symbolic")
        .help("Do not bind global symbol references locally")
        .execute(|args, _modifier_stack| {
            args.b_symbolic = BSymbolicKind::None;
            Ok(())
        });

    parser
        .declare_with_param()
        .long("thread-count")
        .help("Set the number of threads to use")
        .execute(|args, _modifier_stack, value| {
            args.common_mut().num_threads = Some(NonZeroUsize::try_from(value.parse::<usize>()?)?);
            Ok(())
        });

    parser
        .declare_with_param()
        .long("exclude-libs")
        .help("Exclude libraries")
        .execute(|args, _modifier_stack, value| {
            for lib in value.split([',', ':']) {
                if lib.is_empty() {
                    continue;
                }

                if lib == "ALL" {
                    args.exclude_libs = ExcludeLibs::All;
                    return Ok(());
                }

                match &mut args.exclude_libs {
                    ExcludeLibs::All => {}
                    ExcludeLibs::None => {
                        let mut set = HashSet::new();
                        set.insert(Box::from(lib));
                        args.exclude_libs = ExcludeLibs::Some(set);
                    }
                    ExcludeLibs::Some(set) => {
                        set.insert(Box::from(lib));
                    }
                }
            }

            Ok(())
        });

    parser
        .declare_with_param()
        .long("version-script")
        .help("Use version script")
        .execute(|args, _modifier_stack, value| {
            args.common_mut().save_dir.handle_file(value);
            args.version_script_path = Some(PathBuf::from(value));
            Ok(())
        });

    parser
        .declare_with_param()
        .long("script")
        .prefix("T")
        .help("Use linker script")
        .execute(|args, _modifier_stack, value| {
            // -Ttext=ADDR, -Tdata=ADDR, -Tbss=ADDR are segment start overrides,
            // not linker script paths. Handle them here since they share the -T prefix.
            // The prefix handler gives us the part after "-T", which may be:
            //   "text=0x700000"  (from -Ttext=0x700000)
            // We only handle the "name=ADDR" form here.
            if let Some(addr) = value.strip_prefix("text=") {
                args.ttext = Some(
                    parse_number(addr)
                        .with_context(|| format!("Invalid address `{addr}` in -Ttext"))?,
                );
                return Ok(());
            }
            if let Some(addr) = value.strip_prefix("data=") {
                args.tdata = Some(
                    parse_number(addr)
                        .with_context(|| format!("Invalid address `{addr}` in -Tdata"))?,
                );
                return Ok(());
            }
            if let Some(addr) = value.strip_prefix("bss=") {
                args.tbss = Some(
                    parse_number(addr)
                        .with_context(|| format!("Invalid address `{addr}` in -Tbss"))?,
                );
                return Ok(());
            }
            args.common_mut().save_dir.handle_file(value);
            args.common_mut().add_script(value);
            Ok(())
        });

    parser
        .declare_with_param()
        .long("export-dynamic-symbol")
        .help("Export dynamic symbol")
        .execute(|args, _modifier_stack, value| {
            args.export_list.push(value.to_owned());
            Ok(())
        });

    parser
        .declare_with_param()
        .long("export-dynamic-symbol-list")
        .help("Export dynamic symbol list")
        .execute(|args, _modifier_stack, value| {
            args.export_list_path = Some(PathBuf::from(value));
            Ok(())
        });

    parser
        .declare_with_param()
        .long("dynamic-list")
        .help("Read the dynamic symbol list from a file")
        .execute(|args, _modifier_stack, value| {
            args.b_symbolic = BSymbolicKind::All;
            args.export_list_path = Some(PathBuf::from(value));
            Ok(())
        });

    parser
        .declare_with_param()
        .long("write-gc-stats")
        .help("Write GC statistics")
        .execute(|args, _modifier_stack, value| {
            args.write_gc_stats = Some(PathBuf::from(value));
            Ok(())
        });

    parser
        .declare_with_param()
        .long("gc-stats-ignore")
        .help("Ignore files in GC stats")
        .execute(|args, _modifier_stack, value| {
            args.gc_stats_ignore.push(value.to_owned());
            Ok(())
        });

    parser
        .declare()
        .long("no-identity-comment")
        .help("Don't write the linker name and version in .comment")
        .execute(|args, _modifier_stack| {
            args.should_write_linker_identity = false;
            Ok(())
        });

    parser
        .declare_with_param()
        .long("debug-fuel")
        .execute(|args, _modifier_stack, value| {
            args.common_mut().debug_fuel = Some(AtomicI64::new(value.parse()?));
            args.common_mut().num_threads = Some(NonZeroUsize::new(1).unwrap());
            Ok(())
        });

    parser
        .declare_with_param()
        .long("unresolved-symbols")
        .help("Specify how to handle unresolved symbols")
        .execute(|args, _modifier_stack, value| {
            args.unresolved_symbols = match value {
                "report-all" => UnresolvedSymbols::ReportAll,
                "ignore-in-shared-libs" => UnresolvedSymbols::IgnoreInSharedLibs,
                "ignore-in-object-files" => UnresolvedSymbols::IgnoreInObjectFiles,
                "ignore-all" => UnresolvedSymbols::IgnoreAll,
                _ => bail!("Invalid unresolved-symbols value {value}"),
            };
            Ok(())
        });

    parser
        .declare_with_param()
        .long("undefined")
        .help("Force resolution of the symbol")
        .execute(|args, _modifier_stack, value| {
            args.undefined.push(value.to_owned());
            Ok(())
        });

    parser
        .declare_with_param()
        .long("wrap")
        .help("Use a wrapper function")
        .execute(|args, _modifier_stack, value| {
            args.wrap.push(value.to_owned());
            Ok(())
        });

    parser
        .declare_with_param()
        .long("defsym")
        .help("Define a symbol alias: --defsym=symbol=value")
        .execute(|args, _modifier_stack, value| {
            let parts: Vec<&str> = value.splitn(2, '=').collect();
            if parts.len() != 2 {
                bail!("Invalid --defsym format. Expected: --defsym=symbol=value");
            }
            let symbol_name = parts[0].to_owned();
            let value_str = parts[1].to_owned();

            args.defsym.push((symbol_name, value_str));
            Ok(())
        });

    parser
        .declare_with_param()
        .long("section-start")
        .help("Set start address for a section: --section-start=.section=address")
        .execute(|args, _modifier_stack, value| {
            let parts: Vec<&str> = value.splitn(2, '=').collect();
            if parts.len() != 2 {
                bail!("Invalid --section-start format. Expected: --section-start=.section=address");
            }

            let section_name = parts[0].to_owned();
            let address = parse_number(parts[1]).with_context(|| {
                format!(
                    "Invalid address `{}` in --section-start={}",
                    parts[1], value
                )
            })?;
            args.section_start
                .insert(section_name.into_bytes(), address);

            Ok(())
        });

    parser
        .declare_with_param()
        .long("hash-style")
        .help("Set hash style")
        .execute(|args, _modifier_stack, value| {
            args.hash_style = match value {
                "gnu" => HashStyle::Gnu,
                "sysv" => HashStyle::Sysv,
                "both" => HashStyle::Both,
                _ => bail!("Unknown hash-style `{value}`"),
            };
            Ok(())
        });

    parser
        .declare()
        .long("enable-new-dtags")
        .help("Use DT_RUNPATH and DT_FLAGS/DT_FLAGS_1 (default)")
        .execute(|args, _modifier_stack| {
            args.enable_new_dtags = true;
            Ok(())
        });

    parser
        .declare()
        .long("disable-new-dtags")
        .help("Use DT_RPATH and individual dynamic entries instead of DT_FLAGS")
        .execute(|args, _modifier_stack| {
            args.enable_new_dtags = false;
            Ok(())
        });

    parser
        .declare_with_param()
        .long("retain-symbols-file")
        .help(
            "Filter symtab to contain only symbols listed in the supplied file. \
            One symbol per line.",
        )
        .execute(|args, _modifier_stack, value| {
            // The performance this flag is not especially optimised. For one, we copy each string
            // to the heap. We also do two lookups in the hashset for each symbol. This is a pretty
            // obscure flag that we don't expect to be used much, so at this stage, it doesn't seem
            // worthwhile to optimise it.
            let contents = std::fs::read_to_string(value)
                .with_context(|| format!("Failed to read `{value}`"))?;
            args.strip = Strip::Retain(
                contents
                    .lines()
                    .filter_map(|l| {
                        if l.is_empty() {
                            None
                        } else {
                            Some(l.as_bytes().to_owned())
                        }
                    })
                    .collect(),
            );
            Ok(())
        });

    parser
        .declare_with_param()
        .long("build-id")
        .help("Generate build ID")
        .execute(|args, _modifier_stack, value| {
            args.build_id = match value {
                "none" => BuildIdOption::None,
                "fast" | "md5" | "sha1" => BuildIdOption::Fast,
                "uuid" => BuildIdOption::Uuid,
                s if s.starts_with("0x") || s.starts_with("0X") => {
                    let hex_string = &s[2..];
                    let decoded_bytes = hex::decode(hex_string)
                        .with_context(|| format!("Invalid Hex Build Id `0x{hex_string}`"))?;
                    BuildIdOption::Hex(decoded_bytes)
                }
                s => bail!(
                    "Invalid build-id value `{s}` valid values are `none`, `fast`, `md5`, `sha1` and `uuid`"
                ),
            };
            Ok(())
        });

    parser
        .declare_with_param()
        .long("icf")
        .help("Enable identical code folding (merge duplicate functions)")
        .execute(|args, _modifier_stack, value| {
            match value {
                "none" => {}
                other => args.warn_unsupported(&format!("--icf={other}"))?,
            }
            Ok(())
        });

    parser
        .declare_with_param()
        .long("sort-section")
        .help("Specify section sorting criteria")
        .execute(|args, _modifier_stack, value| {
            args.sort_section = Some(match value {
                "name" => SortSectionMode::Name,
                "alignment" => SortSectionMode::Alignment,
                other => {
                    args.warn_unsupported(&format!("--sort-section={other}"))?;
                    return Ok(());
                }
            });
            Ok(())
        });

    parser
        .declare_with_param()
        .long("sysroot")
        .help("Set system root")
        .execute(|args, _modifier_stack, value| {
            args.common_mut().save_dir.handle_file(value);
            let sysroot = std::fs::canonicalize(value).unwrap_or_else(|_| PathBuf::from(value));
            args.sysroot = Some(Box::from(sysroot.as_path()));
            for path in &mut args.lib_search_path {
                if let Some(new_path) = maybe_forced_sysroot(path, &sysroot) {
                    *path = new_path;
                }
            }
            Ok(())
        });

    parser
        .declare_with_param()
        .long("auxiliary")
        .short("f")
        .help("Set DT_AUXILIARY to a given value")
        .execute(|args, _modifier_stack, value| {
            args.auxiliary.push(value.to_owned());
            Ok(())
        });

    parser
        .declare_with_param()
        .long("plugin-opt")
        .help("Pass options to the plugin")
        .execute(|args, _modifier_stack, value| {
            args.plugin_args
                .push(CString::new(value).context("Invalid --plugin-opt argument")?);
            Ok(())
        });

    parser
        .declare_with_param()
        .long("dependency-file")
        .help("Write dependency rules")
        .execute(|args, _modifier_stack, value| {
            args.dependency_file = Some(PathBuf::from(value));
            Ok(())
        });

    parser
        .declare_with_param()
        .short("t")
        .long("trace")
        .help("Print opened input files")
        .execute(|args, _modifier_stack, _value| {
            args.trace = true;
            Ok(())
        });

    parser
        .declare_with_param()
        .long("plugin")
        .help("Load plugin")
        .execute(|args, _modifier_stack, value| {
            args.plugin_path = Some(value.to_owned());
            Ok(())
        });

    parser
        .declare_with_param()
        .long("rpath-link")
        .help("Add runtime library search path")
        .execute(|_args, _modifier_stack, _value| {
            // TODO
            Ok(())
        });

    parser
        .declare()
        .long("start-lib")
        .help("Start library group")
        .execute(|_args, modifier_stack| {
            modifier_stack.last_mut().unwrap().archive_semantics = true;
            Ok(())
        });

    parser
        .declare()
        .long("end-lib")
        .help("End library group")
        .execute(|_args, modifier_stack| {
            modifier_stack.last_mut().unwrap().archive_semantics = false;
            Ok(())
        });

    parser
        .declare()
        .long("no-update-in-place")
        .help("Delete and recreate the file")
        .execute(|args, _modifier_stack| {
            args.common_mut().file_replacement_mode = Some(FileReplacementMode::UnlinkAndReplace);
            Ok(())
        });

    parser
        .declare()
        .short("EL")
        .help("Select the little-endian format in the OUTPUT_FORMAT command")
        .execute(|args, _modifier_stack| {
            args.output_format_endian = Some(Endianness::Little);
            Ok(())
        });

    parser
        .declare()
        .short("EB")
        .help("Select the big-endian format in the OUTPUT_FORMAT command")
        .execute(|args, _modifier_stack| {
            args.output_format_endian = Some(Endianness::Big);
            Ok(())
        });

    parser
        .declare()
        .long("prepopulate-maps")
        .help("Prepopulate maps")
        .execute(|args, _modifier_stack| {
            args.common_mut().prepopulate_maps = true;
            Ok(())
        });

    parser
        .declare()
        .long("verbose-gc-stats")
        .help("Show GC statistics")
        .execute(|args, _modifier_stack| {
            args.verbose_gc_stats = true;
            Ok(())
        });

    parser
        .declare()
        .long("allow-shlib-undefined")
        .help("Allow undefined symbol references in shared libraries")
        .execute(|args, _modifier_stack| {
            args.allow_shlib_undefined = true;
            Ok(())
        });

    parser
        .declare()
        .long("no-allow-shlib-undefined")
        .help("Disallow undefined symbol references in shared libraries")
        .execute(|args, _modifier_stack| {
            args.allow_shlib_undefined = false;
            Ok(())
        });

    parser
        .declare()
        .long("error-unresolved-symbols")
        .help("Treat unresolved symbols as errors")
        .execute(|args, _modifier_stack| {
            args.error_unresolved_symbols = true;
            Ok(())
        });

    parser
        .declare()
        .long("warn-unresolved-symbols")
        .help("Treat unresolved symbols as warnings")
        .execute(|args, _modifier_stack| {
            args.error_unresolved_symbols = false;
            Ok(())
        });

    parser
        .declare()
        .long("use-android-relr-tags")
        .help("Use Android version of SHT_RELR and DT_RELR")
        .execute(|args, _modifier_stack| {
            args.use_android_relr_tags = true;
            Ok(())
        });

    parser
        .declare()
        .long("no-use-android-relr-tags")
        .help("Do not use Android version of SHT_RELR and DT_RELR (default)")
        .execute(|args, _modifier_stack| {
            args.use_android_relr_tags = false;
            Ok(())
        });

    parser
        .declare()
        .long("wild-experimental-sframe")
        .help("Enable experimental support for SFrame V2 (this option may be removed at any time)")
        .execute(|args, _modifier_stack| {
            args.experimental_sframe = true;
            Ok(())
        });

    parser
        .declare()
        .short("n")
        .long("nmagic")
        .help("Disable page alignment of sections and disable linking against shared libraries")
        .execute(|args, _modifier_stack| {
            args.nmagic = true;
            Ok(())
        });

    parser
        .declare()
        .long("no-nmagic")
        .help("Page align sections (default)")
        .execute(|args, _modifier_stack| {
            args.nmagic = false;
            Ok(())
        });

    parser
        .declare()
        .long("rosegment")
        .help("Put read-only non-executable sections in their own segment (default)")
        .execute(|args, _modifier_stack| {
            args.rosegment = true;
            Ok(())
        });

    parser
        .declare()
        .long("no-rosegment")
        .help("Don't put read-only non-executable sections in their own segment")
        .execute(|args, _modifier_stack| {
            args.rosegment = false;
            Ok(())
        });

    parser
        .declare()
        .long("discard-sframe")
        .help("Discard SFrame section")
        .execute(|args, _modifier_stack| {
            args.discard_sframe = true;
            Ok(())
        });

    super::declare_common_args(&mut parser);

    add_silently_ignored_flags(&mut parser);
    add_default_flags(&mut parser);

    parser
}

fn add_silently_ignored_flags(parser: &mut ArgumentParser<ElfArgs>) {
    for flag in SILENTLY_IGNORED_FLAGS {
        let mut declaration = parser.declare();
        declaration = declaration.long(flag);
        declaration.execute(|_args, _modifier_stack| Ok(()));
    }
    for flag in SILENTLY_IGNORED_SHORT_FLAGS {
        let mut declaration = parser.declare();
        declaration = declaration.short(flag);
        declaration.execute(|_args, _modifier_stack| Ok(()));
    }
}

fn add_default_flags(parser: &mut ArgumentParser<ElfArgs>) {
    for flag in DEFAULT_FLAGS {
        let mut declaration = parser.declare();
        declaration = declaration.long(flag);
        declaration.execute(|_args, _modifier_stack| Ok(()));
    }
    for flag in DEFAULT_SHORT_FLAGS {
        let mut declaration = parser.declare();
        declaration = declaration.short(flag);
        declaration.execute(|_args, _modifier_stack| Ok(()));
    }
}

impl platform::Args for ElfArgs {
    fn parse<S, I>(&mut self, input: I) -> Result
    where
        S: AsRef<str>,
        I: Iterator<Item = S>,
    {
        parse(self, input)
    }

    fn gc_stats_output_file(&self) -> Option<&Path> {
        self.write_gc_stats.as_deref()
    }

    fn gc_stats_ignore(&self) -> &[String] {
        &self.gc_stats_ignore
    }

    fn verbose_gc_stats(&self) -> bool {
        self.verbose_gc_stats
    }

    fn rosegment(&self) -> bool {
        self.rosegment
    }

    fn common(&self) -> &crate::args::CommonArgs {
        &self.common
    }

    fn common_mut(&mut self) -> &mut crate::args::CommonArgs {
        &mut self.common
    }

    // TODO: Some linkers like ld and mold cleanup debug symbols when linking with -r. For now, we
    // ignore --strip-all and --strip-debug in partial link mode.
    fn should_strip_debug(&self) -> bool {
        !self.should_output_partial_object() && matches!(self.strip, Strip::All | Strip::Debug)
    }

    fn should_strip_all(&self) -> bool {
        !self.should_output_partial_object() && matches!(self.strip, Strip::All)
    }

    fn should_strip_symbol_named(&self, name: &[u8]) -> bool {
        let Strip::Retain(retain) = &self.strip else {
            return false;
        };
        !retain.contains(name)
    }

    fn force_undefined_symbol_names(&self) -> &[String] {
        &self.undefined
    }

    fn lib_search_path(&self) -> &[Box<Path>] {
        &self.lib_search_path
    }

    fn sysroot(&self) -> Option<&Path> {
        self.sysroot.as_deref()
    }

    fn should_gc_sections(&self) -> bool {
        self.gc_sections
    }

    fn should_merge_sections(&self) -> bool {
        self.merge_sections
    }

    fn force_export_symbol_names(&self) -> &[String] {
        &self.export_list
    }

    fn symbol_names_to_wrap(&self) -> &[String] {
        &self.wrap
    }

    fn entry_point<'a>(
        &'a self,
        linker_script_entry: Option<&'a [u8]>,
    ) -> platform::EntryPoint<'a> {
        // The --entry flag is used first, falling back to what the linker script says, or otherwise
        // defaults to `_start`.
        if let Some(entry) = self.entry.as_deref() {
            return parse_number(entry).map_or_else(
                |_| platform::EntryPoint::Symbol(entry.as_bytes()),
                platform::EntryPoint::Address,
            );
        }
        platform::EntryPoint::Symbol(linker_script_entry.unwrap_or(b"_start"))
    }

    fn start_address_for_section(&self, section_name: SectionName) -> Option<u64> {
        // --section-start takes precedence over -Ttext/-Tdata/-Tbss.
        if let Some(&addr) = self.section_start.get(section_name.bytes()) {
            return Some(addr);
        }
        match section_name.bytes() {
            b".text" => self.ttext,
            b".data" => self.tdata,
            b".bss" => self.tbss,
            _ => None,
        }
    }

    fn segment_start_override(&self, name: crate::parsing::SegmentName) -> Option<u64> {
        match name {
            crate::parsing::SegmentName::Text => self.ttext,
            crate::parsing::SegmentName::Data => self.tdata,
            crate::parsing::SegmentName::Bss => self.tbss,
            crate::parsing::SegmentName::Rodata | crate::parsing::SegmentName::Other => None,
        }
    }

    fn version_script_path(&self) -> Option<&Path> {
        self.version_script_path.as_deref()
    }

    fn export_list_path(&self) -> Option<&Path> {
        self.export_list_path.as_deref()
    }

    fn defsym(&self) -> &[(String, String)] {
        &self.defsym
    }

    fn should_relax(&self) -> bool {
        self.relax
    }

    fn sort_sections_by_name(&self) -> bool {
        self.sort_section == Some(SortSectionMode::Name)
    }

    fn should_emit_got_plt_syms(&self) -> bool {
        self.got_plt_syms
    }

    fn copy_relocations_enabled(&self) -> crate::args::CopyRelocations {
        self.copy_relocations
    }

    fn should_error_on_unresolved_symbols(&self) -> bool {
        self.error_unresolved_symbols
    }

    fn should_write_linker_identity(&self) -> bool {
        self.should_write_linker_identity
    }

    fn dynamic_linker(&self) -> Option<&Path> {
        self.dynamic_linker.as_deref()
    }

    fn should_allow_object_undefined(&self, output_kind: OutputKind) -> bool {
        !self.no_undefined.unwrap_or(output_kind.is_executable())
    }

    fn allow_multiple_definitions(&self) -> bool {
        self.allow_multiple_definitions
    }

    fn stack_size_override(&self) -> Option<NonZeroU64> {
        self.z_stack_size
    }

    fn unresolved_symbols_behaviour(&self) -> crate::args::UnresolvedSymbols {
        self.unresolved_symbols
    }

    fn is_ignored_flag(&self, flag: &str) -> bool {
        IGNORED_FLAGS.contains(&flag)
    }

    fn should_export_all_dynamic_symbols(&self) -> bool {
        self.export_all_dynamic_symbols
    }

    fn should_export_dynamic(&self, lib_name: &[u8]) -> bool {
        !self.exclude_libs.should_exclude(lib_name)
    }

    fn loadable_segment_alignment(&self) -> Alignment {
        if self.nmagic {
            return Alignment { exponent: 0 };
        }

        if let Some(max_page_size) = self.max_page_size {
            return max_page_size;
        }

        match self.arch {
            Architecture::X86_64 => Alignment { exponent: 12 },
            Architecture::AArch64 => Alignment { exponent: 16 },
            Architecture::RiscV64 => Alignment { exponent: 12 },
            Architecture::LoongArch64 => Alignment { exponent: 16 },
            Architecture::Ppc64 => Alignment { exponent: 16 },
            Architecture::Unsupported => unreachable!(),
        }
    }

    fn dependency_file(&self) -> Option<&Path> {
        self.dependency_file.as_deref()
    }

    fn should_write_trace_file(&self) -> bool {
        self.trace
    }

    fn should_output_executable(&self) -> bool {
        self.should_output_executable
    }

    fn should_output_partial_object(&self) -> bool {
        self.should_output_partial_object
    }

    fn should_write_gdb_index(&self) -> bool {
        self.gdb_index && !self.should_strip_debug()
    }

    fn architecture(&self) -> Architecture {
        self.arch
    }

    fn output_format_endian(&self) -> Option<Endianness> {
        self.output_format_endian
    }
}

#[cfg(test)]
mod tests {
    use super::ElfArgs;
    use super::SILENTLY_IGNORED_FLAGS;
    use super::VersionMode;
    use crate::args::InputSpec;
    use crate::platform::Args as _;
    use itertools::Itertools;
    use std::fs::File;
    use std::io::BufWriter;
    use std::io::Write;
    use std::num::NonZeroUsize;
    use std::path::Path;
    use std::path::PathBuf;
    use std::str::FromStr;
    use tempfile::NamedTempFile;

    const INPUT1: &[&str] = &[
        "-pie",
        "-z",
        "relro",
        "-zrelro",
        "-hash-style=gnu",
        "--hash-style=gnu",
        "-build-id",
        "--build-id",
        "--eh-frame-hdr",
        "-m",
        "elf_x86_64",
        "-dynamic-linker",
        "/lib64/ld-linux-x86-64.so.2",
        "-o/tmp/a.out",
        "-o",
        "/build/target/debug/deps/c1-a212b73b12b6d123",
        "/lib/x86_64-linux-gnu/Scrt1.o",
        "/lib/x86_64-linux-gnu/crti.o",
        "/usr/bin/../lib/gcc/x86_64-linux-gnu/12/crtbeginS.o",
        "-L/build/target/debug/deps",
        "-L/tool/lib/rustlib/x86_64/lib",
        "-L/tool/lib/rustlib/x86_64/lib",
        "-L/usr/bin/../lib/gcc/x86_64-linux-gnu/12",
        "-L/usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../lib64",
        "-L/lib/x86_64-linux-gnu",
        "-L/lib/../lib64",
        "-L/usr/lib/x86_64-linux-gnu",
        "-L/usr/lib/../lib64",
        "-L",
        "/lib",
        "-L/usr/lib",
        "/tmp/rustcDcR20O/symbols.o",
        "/build/target/debug/deps/c1-a212b73b12b6d123.1.rcgu.o",
        "/build/target/debug/deps/c1-a212b73b12b6d123.2.rcgu.o",
        "/build/target/debug/deps/c1-a212b73b12b6d123.3.rcgu.o",
        "/build/target/debug/deps/c1-a212b73b12b6d123.4.rcgu.o",
        "/build/target/debug/deps/c1-a212b73b12b6d123.5.rcgu.o",
        "/build/target/debug/deps/c1-a212b73b12b6d123.6.rcgu.o",
        "/build/target/debug/deps/c1-a212b73b12b6d123.7.rcgu.o",
        "--as-needed",
        "-as-needed",
        "-Bstatic",
        "/tool/lib/rustlib/x86_64/lib/libstd-6498d8891e016dca.rlib",
        "/tool/lib/rustlib/x86_64/lib/libpanic_unwind-3debdee1a9058d84.rlib",
        "/tool/lib/rustlib/x86_64/lib/libobject-8339c5bd5cbc92bf.rlib",
        "/tool/lib/rustlib/x86_64/lib/libmemchr-160ebcebb54c11ba.rlib",
        "/tool/lib/rustlib/x86_64/lib/libaddr2line-95c75789f1b65e37.rlib",
        "/tool/lib/rustlib/x86_64/lib/libgimli-7e8094f2d6258832.rlib",
        "/tool/lib/rustlib/x86_64/lib/librustc_demangle-bac9783ef1b45db0.rlib",
        "/tool/lib/rustlib/x86_64/lib/libstd_detect-a1cd87df2f2d8e76.rlib",
        "/tool/lib/rustlib/x86_64/lib/libhashbrown-7fd06d468d7dba16.rlib",
        "/tool/lib/rustlib/x86_64/lib/librustc_std_workspace_alloc-5ac19487656e05bf.rlib",
        "/tool/lib/rustlib/x86_64/lib/libminiz_oxide-c7c35d32cf825c11.rlib",
        "/tool/lib/rustlib/x86_64/lib/libadler-c523f1571362e70b.rlib",
        "/tool/lib/rustlib/x86_64/lib/libunwind-85f17c92b770a911.rlib",
        "/tool/lib/rustlib/x86_64/lib/libcfg_if-598d3ba148dadcea.rlib",
        "/tool/lib/rustlib/x86_64/lib/liblibc-a58ec2dab545caa4.rlib",
        "/tool/lib/rustlib/x86_64/lib/liballoc-f9dda8cca149f0fc.rlib",
        "/tool/lib/rustlib/x86_64/lib/librustc_std_workspace_core-7ba4c315dd7a3503.rlib",
        "/tool/lib/rustlib/x86_64/lib/libcore-5ac2993e19124966.rlib",
        "/tool/lib/rustlib/x86_64/lib/libcompiler_builtins-df2fb7f50dec519a.rlib",
        "-Bdynamic",
        "-lgcc_s",
        "-lutil",
        "-lrt",
        "-lpthread",
        "-lm",
        "-ldl",
        "-lc",
        "--eh-frame-hdr",
        "-z",
        "noexecstack",
        "-znoexecstack",
        "--gc-sections",
        "-z",
        "relro",
        "-z",
        "now",
        "-z",
        "lazy",
        "-soname=fpp",
        "-soname",
        "bar",
        "/usr/bin/../lib/gcc/x86_64-linux-gnu/12/crtendS.o",
        "/lib/x86_64-linux-gnu/crtn.o",
        "--version-script",
        "a.ver",
        "--no-threads",
        "--no-add-needed",
        "--no-copy-dt-needed-entries",
        "--discard-locals",
        "--use-android-relr-tags",
        "--pack-dyn-relocs=relr",
        "-X",
        "-EL",
        "-O",
        "1",
        "-O3",
        "-v",
        "--sysroot=/usr/aarch64-linux-gnu",
        "--demangle",
        "--no-demangle",
        "-l:lib85caec4suo0pxg06jm2ma7b0o.so",
        "-rpath",
        "foo/",
        "-rpath=bar/",
        "-Rbaz",
        "-R",
        "somewhere",
        // Adding the same rpath multiple times should not create duplicates
        "-rpath",
        "foo/",
        "-x",
        "--discard-all",
        "--dependency-file=deps.d",
        "--sort-section=alignment",
    ];

    const FILE_OPTIONS: &[&str] = &["-pie"];

    const INLINE_OPTIONS: &[&str] = &["-L", "/lib"];

    fn write_options_to_file(file: &File, options: &[&str]) {
        let mut writer = BufWriter::new(file);
        for option in options {
            writeln!(writer, "{option}").expect("Failed to write to temporary file");
        }
    }

    #[track_caller]
    fn assert_contains(c: &[Box<Path>], v: &str) {
        assert!(c.iter().any(|p| p.as_ref() == Path::new(v)));
    }

    fn input1_assertions(args: &ElfArgs) {
        assert_eq!(
            args.common
                .inputs
                .iter()
                .filter_map(|i| match &i.spec {
                    InputSpec::File(_) | InputSpec::Search(_) => None,
                    InputSpec::Lib(lib_name) => Some(lib_name.as_ref()),
                })
                .collect_vec(),
            &["gcc_s", "util", "rt", "pthread", "m", "dl", "c"]
        );
        assert_contains(&args.lib_search_path, "/lib");
        assert_contains(&args.lib_search_path, "/usr/lib");
        assert!(!args.common.inputs.iter().any(|i| match &i.spec {
            InputSpec::File(f) => f.as_ref() == Path::new("/usr/bin/ld"),
            InputSpec::Lib(_) | InputSpec::Search(_) => false,
        }));
        assert_eq!(
            args.version_script_path,
            Some(PathBuf::from_str("a.ver").unwrap())
        );
        assert_eq!(args.soname, Some("bar".to_owned()));
        assert_eq!(args.common.num_threads, Some(NonZeroUsize::new(1).unwrap()));
        assert_eq!(args.common.version_mode, VersionMode::Verbose);
        assert_eq!(
            args.sysroot,
            Some(Box::from(Path::new("/usr/aarch64-linux-gnu")))
        );
        assert!(args.common.inputs.iter().any(|i| match &i.spec {
            InputSpec::File(_) | InputSpec::Lib(_) => false,
            InputSpec::Search(lib) => lib.as_ref() == "lib85caec4suo0pxg06jm2ma7b0o.so",
        }));
        assert_eq!(args.rpath.as_deref(), Some("foo/:bar/:baz:somewhere"));
        assert_eq!(
            args.dependency_file,
            Some(PathBuf::from_str("deps.d").unwrap())
        );
    }

    fn inline_and_file_options_assertions(args: &ElfArgs) {
        assert_contains(&args.lib_search_path, "/lib");
    }

    #[test]
    fn test_parse_inline_only_options() {
        let mut args = ElfArgs::new().unwrap();
        args.parse(INPUT1.iter()).unwrap();
        input1_assertions(&args);
    }

    #[test]
    #[cfg_attr(target_os = "wasi", ignore = "wasi doesn't have a temp dir")]
    fn test_parse_file_only_options() {
        // Create a temporary file containing the same options (one per line) as INPUT1
        let file = NamedTempFile::new().expect("Could not create temp file");
        write_options_to_file(file.as_file(), INPUT1);

        // pass the name of the file where options are as the only inline option "@filename"
        let inline_options = [format!("@{}", file.path().to_str().unwrap())];
        let mut args = ElfArgs::new().unwrap();
        args.parse(inline_options.iter()).unwrap();
        input1_assertions(&args);
    }

    #[test]
    #[cfg_attr(target_os = "wasi", ignore = "wasi doesn't have a temp dir")]
    fn test_parse_mixed_file_and_inline_options() {
        // Create a temporary file containing some options
        let file = NamedTempFile::new().expect("Could not create temp file");
        write_options_to_file(file.as_file(), FILE_OPTIONS);

        // create an inline option referring to "@filename"
        let file_option = format!("@{}", file.path().to_str().unwrap());
        // start with the set of inline options
        let mut inline_options = INLINE_OPTIONS.to_vec();
        // and extend with the "@filename" option
        inline_options.push(&file_option);

        // confirm that this works and the resulting set of options is correct
        let mut args = ElfArgs::new().unwrap();
        args.parse(inline_options.iter()).unwrap();
        inline_and_file_options_assertions(&args);
    }

    #[test]
    #[cfg_attr(target_os = "wasi", ignore = "wasi doesn't have a temp dir")]
    fn test_parse_overlapping_file_and_inline_options() {
        // Create a set of file options that has a duplicate of an inline option
        let mut file_options = FILE_OPTIONS.to_vec();
        file_options.append(&mut INLINE_OPTIONS.to_vec());
        // and save them to a file
        let file = NamedTempFile::new().expect("Could not create temp file");
        write_options_to_file(file.as_file(), &file_options);

        // pass the name of the file where options are, as an inline option "@filename"
        let file_option = format!("@{}", file.path().to_str().unwrap());
        // start with the set of inline options
        let mut inline_options = INLINE_OPTIONS.to_vec();
        // and extend with the "@filename" option
        inline_options.push(&file_option);

        // confirm that this works and the resulting set of options is correct
        let mut args = ElfArgs::new().unwrap();
        args.parse(inline_options.iter()).unwrap();
        inline_and_file_options_assertions(&args);
    }

    #[test]
    #[cfg_attr(target_os = "wasi", ignore = "wasi doesn't have a temp dir")]
    fn test_parse_recursive_file_option() {
        // Create a temporary file containing a @file option
        let file1 = NamedTempFile::new().expect("Could not create temp file");
        let file2 = NamedTempFile::new().expect("Could not create temp file");
        let file_option = format!("@{}", file2.path().to_str().unwrap());
        write_options_to_file(file1.as_file(), &[&file_option]);
        write_options_to_file(file2.as_file(), INPUT1);

        // pass the name of the file where options are, as an inline option "@filename"
        let inline_options = [format!("@{}", file1.path().to_str().unwrap())];

        // confirm that this works and the resulting set of options is correct
        let mut args = ElfArgs::new().unwrap();
        args.parse(inline_options.iter())
            .expect("Recursive @file options should parse correctly but be ignored");
        input1_assertions(&args);
    }

    #[test]
    fn test_arguments_from_string() {
        use crate::args::arguments_from_string;

        assert!(arguments_from_string("").unwrap().is_empty());
        assert!(arguments_from_string("''").unwrap().is_empty());
        assert!(arguments_from_string("\"\"").unwrap().is_empty());
        assert_eq!(
            arguments_from_string(r#""foo" "bar""#).unwrap(),
            ["foo", "bar"]
        );
        assert_eq!(
            arguments_from_string(r#""foo\"" "\"b\"ar""#).unwrap(),
            ["foo\"", "\"b\"ar"]
        );
        assert_eq!(
            arguments_from_string("   foo  bar      ").unwrap(),
            ["foo", "bar"]
        );
        assert!(arguments_from_string("'foo''bar'").is_err());
        assert_eq!(
            arguments_from_string("'foo' 'bar' baz").unwrap(),
            ["foo", "bar", "baz"]
        );
        assert_eq!(arguments_from_string("foo\nbar").unwrap(), ["foo", "bar"]);
        assert_eq!(
            arguments_from_string(r#"'foo' "bar" baz"#).unwrap(),
            ["foo", "bar", "baz"]
        );
        assert_eq!(arguments_from_string("'foo bar'").unwrap(), ["foo bar"]);
        assert_eq!(
            arguments_from_string("'foo \"  bar'").unwrap(),
            ["foo \"  bar"]
        );
        assert!(arguments_from_string("foo\\").is_err());
        assert!(arguments_from_string("'foo").is_err());
        assert!(arguments_from_string("foo\"").is_err());
    }

    #[test]
    fn test_ignored_flags() {
        for flag in SILENTLY_IGNORED_FLAGS {
            assert!(!flag.starts_with('-'));
        }
    }

    // Helper: parse a small set of args and return the resulting ElfArgs.
    fn parse_args<'a>(args: impl IntoIterator<Item = &'a str>) -> ElfArgs {
        let mut elf_args = ElfArgs::new().unwrap();
        elf_args.parse(args.into_iter()).unwrap();
        elf_args
    }

    // Helper: parse args and expect a parse error.
    fn parse_args_err<'a>(args: impl IntoIterator<Item = &'a str>) -> crate::error::Error {
        let mut elf_args = ElfArgs::new().unwrap();
        elf_args.parse(args.into_iter()).unwrap_err()
    }

    #[test]
    fn test_ttext_hex_round_trip() {
        use crate::output_section_id::SectionName;
        let args = parse_args(["-Ttext=0x700000"]);
        assert_eq!(
            args.start_address_for_section(SectionName(b".text")),
            Some(0x700000)
        );
    }

    #[test]
    fn test_ttext_decimal_round_trip() {
        use crate::output_section_id::SectionName;
        // 7340032 == 0x700000
        let args = parse_args(["-Ttext=7340032"]);
        assert_eq!(
            args.start_address_for_section(SectionName(b".text")),
            Some(0x700000)
        );
    }

    #[test]
    fn test_tdata_hex_round_trip() {
        use crate::output_section_id::SectionName;
        let args = parse_args(["-Tdata=0x800000"]);
        assert_eq!(
            args.start_address_for_section(SectionName(b".data")),
            Some(0x800000)
        );
    }

    #[test]
    fn test_tdata_decimal_round_trip() {
        use crate::output_section_id::SectionName;
        // 8388608 == 0x800000
        let args = parse_args(["-Tdata=8388608"]);
        assert_eq!(
            args.start_address_for_section(SectionName(b".data")),
            Some(0x800000)
        );
    }

    #[test]
    fn test_tbss_hex_round_trip() {
        use crate::output_section_id::SectionName;
        let args = parse_args(["-Tbss=0x900000"]);
        assert_eq!(
            args.start_address_for_section(SectionName(b".bss")),
            Some(0x900000)
        );
    }

    #[test]
    fn test_tbss_decimal_round_trip() {
        use crate::output_section_id::SectionName;
        // 9437184 == 0x900000
        let args = parse_args(["-Tbss=9437184"]);
        assert_eq!(
            args.start_address_for_section(SectionName(b".bss")),
            Some(0x900000)
        );
    }

    #[test]
    fn test_ttext_invalid_address() {
        // Parsing a non-numeric address should return an error.
        parse_args_err(["-Ttext=notanumber"]);
    }

    #[test]
    fn test_section_start_takes_precedence_over_ttext() {
        use crate::output_section_id::SectionName;
        // --section-start=.text=0x600000 should win over -Ttext=0x700000
        let args = parse_args(["--section-start=.text=0x600000", "-Ttext=0x700000"]);
        assert_eq!(
            args.start_address_for_section(SectionName(b".text")),
            Some(0x600000)
        );
    }
}
