use std::process::ExitCode;
use libc::pid_t;
use nix::{
fcntl::{open, OFlag},
sys::stat::Mode,
unistd::Pid,
};
use serde_json::json;
use syd::{
config::HAVE_AT_EXECVE_CHECK,
fd::{check_executable, open_static_proc},
path::XPathBuf,
proc::proc_executables,
};
#[cfg(all(
not(coverage),
not(feature = "prof"),
not(target_os = "android"),
not(target_arch = "riscv64"),
target_page_size_4k,
target_pointer_width = "64"
))]
#[global_allocator]
static GLOBAL: hardened_malloc::HardenedMalloc = hardened_malloc::HardenedMalloc;
#[cfg(feature = "prof")]
#[global_allocator]
static GLOBAL: tcmalloc::TCMalloc = tcmalloc::TCMalloc;
syd::main! {
use lexopt::prelude::*;
syd::set_sigpipe_dfl()?;
let mut opt_check = false; let mut opt_print = false; let mut opt_procs = vec![]; let mut opt_files = vec![];
let mut parser = lexopt::Parser::from_env();
while let Some(arg) = parser.next()? {
match arg {
Short('h') => {
help();
return Ok(ExitCode::SUCCESS);
}
Short('c') => opt_check = true,
Short('v') => opt_print = true,
Short('l') => {
for pid in parser.raw_args()? {
opt_procs.push(pid.parse::<pid_t>().map(Pid::from_raw)?);
}
}
Value(file) => {
opt_files.push(file);
opt_files.extend(parser.raw_args()?);
}
_ => return Err(arg.unexpected().into()),
}
}
if opt_check {
if !opt_files.is_empty() || !opt_procs.is_empty() {
eprintln!("syd-x: -c accepts no arguments!");
help();
return Ok(ExitCode::FAILURE);
}
let ok = *HAVE_AT_EXECVE_CHECK;
println!(
"Current system {} AT_EXECVE_CHECK support.",
if ok { "has" } else { "does not have" }
);
return Ok(if ok {
ExitCode::SUCCESS
} else {
ExitCode::FAILURE
});
}
if !opt_procs.is_empty() && !opt_files.is_empty() {
eprintln!("Check and list mode are mutually exclusive!");
help();
return Ok(ExitCode::FAILURE);
}
if !opt_procs.is_empty() {
let _ = syd::log::log_init_simple(syd::syslog::LogLevel::Warn);
open_static_proc()?;
for pid in opt_procs {
let bins = match proc_executables(pid) {
Ok(bins) => bins,
Err(errno) => {
#[expect(clippy::disallowed_methods)]
let err = json!({
"pid": pid.as_raw(),
"err": errno as i32,
});
#[expect(clippy::disallowed_methods)]
let err = serde_json::to_string(&err).expect("JSON");
println!("{err}");
continue;
}
};
for bin in &bins {
#[expect(clippy::disallowed_methods)]
let msg = json!({
"pid": pid.as_raw(),
"dev": (bin.dev_major, bin.dev_minor),
"ino": bin.inode,
"exe": bin.path,
});
#[expect(clippy::disallowed_methods)]
let msg = serde_json::to_string(&msg).expect("JSON");
println!("{msg}");
}
}
return Ok(ExitCode::SUCCESS);
}
if opt_files.is_empty() {
help();
return Ok(ExitCode::FAILURE);
}
for path in opt_files {
let path = XPathBuf::from(path);
#[expect(clippy::disallowed_methods)]
let fd = match open(&path, OFlag::O_PATH | OFlag::O_CLOEXEC, Mode::empty()) {
Ok(fd) => fd,
Err(errno) => {
if opt_print {
eprintln!("syd-x: Error opening file `{path}': {errno}!");
}
return Ok(ExitCode::from(errno as u8));
}
};
if let Err(errno) = check_executable(fd) {
if opt_print {
eprintln!("syd-x: File `{path}' is not executable: {errno}!");
}
return Ok(ExitCode::from(errno as u8));
}
if opt_print {
eprintln!("syd-x: File `{path}' is executable.");
}
}
Ok(ExitCode::SUCCESS)
}
fn help() {
println!("Usage: syd-x [-hcv] [-l pid...] {{files...}}");
println!("Given filenames, check executability of files.");
println!("Given process IDs with -l, list executable files of the processes.");
println!("Exit with 0 on success or with errno on failure.");
println!("Use execveat(2) with AT_EXECVE_CHECK on Linux>=6.14.");
println!("Fallback to faccessat(2) with X_OK on older Linux.");
println!("Use -c to check for AT_EXECVE_CHECK support.");
println!("Use -v to print status information on standard error.");
}