From 5c11ca19aaaf9c1c32d3d1b0fdbe102b4dc7ede1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Feb 2026 23:31:51 +0000 Subject: [PATCH 1/7] Initial plan From 2e6cce26885d7f1a038cab30e009750e0daa2f0c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Feb 2026 23:37:54 +0000 Subject: [PATCH 2/7] Add syscall aliases support for signal-related syscalls - Modified declare_syscalls! macro to support alias definitions - Added aliases for signal syscalls (sigaction, sigprocmask, etc.) - Aliases map user-friendly names to kernel rt_* variants - Added unit tests to verify alias resolution Co-authored-by: kov <1271291+kov@users.noreply.github.com> --- pinchy-common/src/syscalls/aarch64.rs | 10 ++++ pinchy-common/src/syscalls/mod.rs | 11 ++++ pinchy-common/src/syscalls/x86_64.rs | 10 ++++ pinchy/src/client.rs | 73 +++++++++++++++++++++++++++ 4 files changed, 104 insertions(+) diff --git a/pinchy-common/src/syscalls/aarch64.rs b/pinchy-common/src/syscalls/aarch64.rs index ef6bad6..c40b4b2 100644 --- a/pinchy-common/src/syscalls/aarch64.rs +++ b/pinchy-common/src/syscalls/aarch64.rs @@ -309,4 +309,14 @@ declare_syscalls! { SYS_futex_waitv = 449, SYS_set_mempolicy_home_node = 450, SYS_mseal = 462, +; + aliases: + // Signal-related syscalls that libc users might know by their non-rt_ names + "sigaction" => SYS_rt_sigaction, + "sigprocmask" => SYS_rt_sigprocmask, + "sigreturn" => SYS_rt_sigreturn, + "sigpending" => SYS_rt_sigpending, + "sigtimedwait" => SYS_rt_sigtimedwait, + "sigqueueinfo" => SYS_rt_sigqueueinfo, + "sigsuspend" => SYS_rt_sigsuspend, } diff --git a/pinchy-common/src/syscalls/mod.rs b/pinchy-common/src/syscalls/mod.rs index 906616c..cb4d07c 100644 --- a/pinchy-common/src/syscalls/mod.rs +++ b/pinchy-common/src/syscalls/mod.rs @@ -21,9 +21,20 @@ pub const SYS_generic_parse_test: i64 = i64::MAX; // for testing only macro_rules! declare_syscalls { ( $( $name:ident = $num:expr ),* $(,)? + $(; aliases: $( $alias:literal => $target:ident ),* $(,)? )? ) => { $(pub const $name: i64 = $num;)* pub fn syscall_nr_from_name(name: &str) -> Option { + // First check aliases + $( + $( + if name == $alias { + return Some($target); + } + )* + )? + + // Then check canonical names match name { $( x if x == &stringify!($name)[4..] => Some($name), )* _ => None, diff --git a/pinchy-common/src/syscalls/x86_64.rs b/pinchy-common/src/syscalls/x86_64.rs index 06efd7c..cba26a5 100644 --- a/pinchy-common/src/syscalls/x86_64.rs +++ b/pinchy-common/src/syscalls/x86_64.rs @@ -369,4 +369,14 @@ declare_syscalls! { SYS_set_mempolicy_home_node = 450, SYS_fchmodat2 = 452, SYS_mseal = 462, +; + aliases: + // Signal-related syscalls that libc users might know by their non-rt_ names + "sigaction" => SYS_rt_sigaction, + "sigprocmask" => SYS_rt_sigprocmask, + "sigreturn" => SYS_rt_sigreturn, + "sigpending" => SYS_rt_sigpending, + "sigtimedwait" => SYS_rt_sigtimedwait, + "sigqueueinfo" => SYS_rt_sigqueueinfo, + "sigsuspend" => SYS_rt_sigsuspend, } diff --git a/pinchy/src/client.rs b/pinchy/src/client.rs index faa872a..8ac175f 100644 --- a/pinchy/src/client.rs +++ b/pinchy/src/client.rs @@ -125,3 +125,76 @@ async fn relay_trace(fd: OwnedFd, formatting_style: FormattingStyle) -> Result<( } Ok(stdout.flush().await?) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_syscall_names_canonical() { + // Test canonical syscall names work + let result = parse_syscall_names(&["read".to_string(), "write".to_string()]); + assert!(result.is_ok()); + let syscalls = result.unwrap(); + assert_eq!(syscalls.len(), 2); + } + + #[test] + fn test_parse_syscall_names_with_rt_prefix() { + // Test that rt_ prefixed names work + let result = parse_syscall_names(&["rt_sigaction".to_string()]); + assert!(result.is_ok()); + let syscalls = result.unwrap(); + assert_eq!(syscalls.len(), 1); + } + + #[test] + fn test_parse_syscall_names_aliases() { + // Test that aliases work (non-rt_ versions should map to rt_ versions) + let result = parse_syscall_names(&["sigaction".to_string()]); + assert!(result.is_ok()); + let syscalls = result.unwrap(); + assert_eq!(syscalls.len(), 1); + + // Verify it resolves to the same number as rt_sigaction + let rt_result = parse_syscall_names(&["rt_sigaction".to_string()]); + assert!(rt_result.is_ok()); + assert_eq!(syscalls[0], rt_result.unwrap()[0]); + } + + #[test] + fn test_parse_syscall_names_multiple_aliases() { + // Test multiple aliases at once + let result = parse_syscall_names(&[ + "sigaction".to_string(), + "sigprocmask".to_string(), + "sigreturn".to_string(), + ]); + assert!(result.is_ok()); + let syscalls = result.unwrap(); + assert_eq!(syscalls.len(), 3); + } + + #[test] + fn test_parse_syscall_names_mixed() { + // Test mixing canonical names and aliases + let result = parse_syscall_names(&[ + "read".to_string(), + "sigaction".to_string(), + "write".to_string(), + ]); + assert!(result.is_ok()); + let syscalls = result.unwrap(); + assert_eq!(syscalls.len(), 3); + } + + #[test] + fn test_parse_syscall_names_unknown() { + // Test that unknown syscall names produce an error + let result = parse_syscall_names(&["nonexistent_syscall".to_string()]); + assert!(result.is_err()); + assert!(result + .unwrap_err() + .contains("Unknown syscall name: nonexistent_syscall")); + } +} From 15260a98a84f8ff84a6e95153679adf2e9be3b85 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Feb 2026 23:38:36 +0000 Subject: [PATCH 3/7] Add documentation for syscall aliases feature - Updated README with usage examples and alias documentation - Updated help text in client.rs to mention alias support - Documented all supported signal-related aliases Co-authored-by: kov <1271291+kov@users.noreply.github.com> --- README.md | 40 ++++++++++++++++++++++++++++++++++++++++ pinchy/src/client.rs | 2 +- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0af106c..03e983f 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,46 @@ cargo run --release --config 'target."cfg(all())".runner="sudo -E"' Cargo build scripts are used to automatically build the eBPF correctly and include it in the program. +## Usage + +Pinchy can trace syscalls for a running process or launch a new process and trace it. You can specify which syscalls to trace using the `-e` or `--event` option. + +### Basic Examples + +Trace all syscalls for a command: +```shell +pinchy ls /tmp +``` + +Trace specific syscalls: +```shell +pinchy -e read,write,open ls /tmp +``` + +Attach to a running process: +```shell +pinchy -p -e open,close +``` + +### Syscall Aliases + +Pinchy supports common syscall aliases that users might be familiar with from `libc` or other tools. For example, on x86_64 and aarch64, signal-related syscalls use `rt_*` prefixes in the kernel, but you can use the more familiar names without the prefix: + +```shell +# These are equivalent: +pinchy -e sigaction,sigprocmask ./myprogram +pinchy -e rt_sigaction,rt_sigprocmask ./myprogram +``` + +Supported aliases include: +- `sigaction` → `rt_sigaction` +- `sigprocmask` → `rt_sigprocmask` +- `sigreturn` → `rt_sigreturn` +- `sigpending` → `rt_sigpending` +- `sigtimedwait` → `rt_sigtimedwait` +- `sigqueueinfo` → `rt_sigqueueinfo` +- `sigsuspend` → `rt_sigsuspend` + ## Cross-compiling on macOS Cross compilation should work on both Intel and Apple Silicon Macs. diff --git a/pinchy/src/client.rs b/pinchy/src/client.rs index 8ac175f..a7ba408 100644 --- a/pinchy/src/client.rs +++ b/pinchy/src/client.rs @@ -40,7 +40,7 @@ fn parse_syscall_names(names: &[String]) -> Result, String> { #[derive(Parser, Debug)] #[command(author, version, about, trailing_var_arg = true)] struct Args { - /// Syscall(s) to trace (can be repeated or comma-separated) + /// Syscall(s) to trace (can be repeated or comma-separated). Supports aliases like 'sigaction' for 'rt_sigaction'. #[arg(short = 'e', long = "event", value_delimiter = ',', action = clap::ArgAction::Append)] syscalls: Vec, From cb4cc8ca7b8da35ef2930a0bd370d9019f19e0ab Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 22:23:16 +0000 Subject: [PATCH 4/7] Add aarch64-specific syscall aliases for legacy syscalls - Added aliases for syscalls that exist on x86_64 but not on aarch64 - Maps user-facing names (open, stat, poll, etc.) to *at variants - Includes aliases for: open, stat, lstat, poll, dup2, pipe, access, chmod, chown, link, mkdir, mknod, rename, rmdir, symlink, unlink - Updated documentation to explain architecture-specific aliases - Added architecture-specific unit tests Co-authored-by: kov <1271291+kov@users.noreply.github.com> --- README.md | 38 +++++++++++++++++++++++++-- pinchy-common/src/syscalls/aarch64.rs | 19 ++++++++++++++ pinchy/src/client.rs | 33 +++++++++++++++++++++++ 3 files changed, 88 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 03e983f..82a9f8b 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,11 @@ pinchy -p -e open,close ### Syscall Aliases -Pinchy supports common syscall aliases that users might be familiar with from `libc` or other tools. For example, on x86_64 and aarch64, signal-related syscalls use `rt_*` prefixes in the kernel, but you can use the more familiar names without the prefix: +Pinchy supports common syscall aliases that users might be familiar with from `libc` or other tools. + +#### Signal Syscalls + +On x86_64 and aarch64, signal-related syscalls use `rt_*` prefixes in the kernel, but you can use the more familiar names without the prefix: ```shell # These are equivalent: @@ -55,7 +59,7 @@ pinchy -e sigaction,sigprocmask ./myprogram pinchy -e rt_sigaction,rt_sigprocmask ./myprogram ``` -Supported aliases include: +Supported signal aliases (all architectures): - `sigaction` → `rt_sigaction` - `sigprocmask` → `rt_sigprocmask` - `sigreturn` → `rt_sigreturn` @@ -64,6 +68,36 @@ Supported aliases include: - `sigqueueinfo` → `rt_sigqueueinfo` - `sigsuspend` → `rt_sigsuspend` +#### Architecture-Specific Aliases + +On **aarch64**, many traditional syscalls don't exist in the kernel but are provided by glibc as wrappers around newer `*at` variants. Pinchy supports these for convenience: + +```shell +# On aarch64, these are equivalent: +pinchy -e open,stat ./myprogram +pinchy -e openat,newfstatat ./myprogram +``` + +Supported aarch64 aliases: +- `open` → `openat` +- `stat` → `newfstatat` +- `lstat` → `newfstatat` +- `poll` → `ppoll` +- `dup2` → `dup3` +- `pipe` → `pipe2` +- `access` → `faccessat` +- `chmod` → `fchmodat` +- `chown` → `fchownat` +- `link` → `linkat` +- `mkdir` → `mkdirat` +- `mknod` → `mknodat` +- `rename` → `renameat` +- `rmdir` → `unlinkat` +- `symlink` → `symlinkat` +- `unlink` → `unlinkat` + +On **x86_64**, these traditional syscalls exist directly in the kernel, so no aliases are needed. + ## Cross-compiling on macOS Cross compilation should work on both Intel and Apple Silicon Macs. diff --git a/pinchy-common/src/syscalls/aarch64.rs b/pinchy-common/src/syscalls/aarch64.rs index c40b4b2..30fdf37 100644 --- a/pinchy-common/src/syscalls/aarch64.rs +++ b/pinchy-common/src/syscalls/aarch64.rs @@ -319,4 +319,23 @@ declare_syscalls! { "sigtimedwait" => SYS_rt_sigtimedwait, "sigqueueinfo" => SYS_rt_sigqueueinfo, "sigsuspend" => SYS_rt_sigsuspend, + + // File/directory syscalls that don't exist on aarch64 but are provided by glibc + // These map to the *at variants with AT_FDCWD + "open" => SYS_openat, + "stat" => SYS_newfstatat, + "lstat" => SYS_newfstatat, + "poll" => SYS_ppoll, + "dup2" => SYS_dup3, + "pipe" => SYS_pipe2, + "access" => SYS_faccessat, + "chmod" => SYS_fchmodat, + "chown" => SYS_fchownat, + "link" => SYS_linkat, + "mkdir" => SYS_mkdirat, + "mknod" => SYS_mknodat, + "rename" => SYS_renameat, + "rmdir" => SYS_unlinkat, + "symlink" => SYS_symlinkat, + "unlink" => SYS_unlinkat, } diff --git a/pinchy/src/client.rs b/pinchy/src/client.rs index a7ba408..afad7fa 100644 --- a/pinchy/src/client.rs +++ b/pinchy/src/client.rs @@ -197,4 +197,37 @@ mod tests { .unwrap_err() .contains("Unknown syscall name: nonexistent_syscall")); } + + #[test] + #[cfg(target_arch = "aarch64")] + fn test_parse_syscall_names_aarch64_aliases() { + // Test aarch64-specific aliases that map to *at variants + let result = parse_syscall_names(&[ + "open".to_string(), + "stat".to_string(), + "poll".to_string(), + ]); + assert!(result.is_ok()); + let syscalls = result.unwrap(); + assert_eq!(syscalls.len(), 3); + + // Verify aliases resolve to the *at variants + let open_nr = syscall_nr_from_name("open"); + let openat_nr = syscall_nr_from_name("openat"); + assert_eq!(open_nr, openat_nr); + } + + #[test] + #[cfg(target_arch = "x86_64")] + fn test_parse_syscall_names_x86_64_direct() { + // On x86_64, these syscalls exist directly (not as aliases) + let result = parse_syscall_names(&[ + "open".to_string(), + "stat".to_string(), + "poll".to_string(), + ]); + assert!(result.is_ok()); + let syscalls = result.unwrap(); + assert_eq!(syscalls.len(), 3); + } } From d7ba54b727731c0674080253e44a532654de051e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Feb 2026 00:20:22 +0000 Subject: [PATCH 5/7] Add integration tests for syscall aliases - Added test for signal alias: sigprocmask -> rt_sigprocmask - Added test for signal alias: sigaction -> rt_sigaction - Added aarch64-specific test for open -> openat alias - Added aarch64-specific test for stat -> newfstatat alias - Added test for mixed aliases and canonical names - Tests reuse existing workloads to verify aliases work correctly Co-authored-by: kov <1271291+kov@users.noreply.github.com> --- pinchy/tests/integration.rs | 142 ++++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) diff --git a/pinchy/tests/integration.rs b/pinchy/tests/integration.rs index ff3ad90..1ff767b 100644 --- a/pinchy/tests/integration.rs +++ b/pinchy/tests/integration.rs @@ -2190,3 +2190,145 @@ fn process_identity_test() { .success() .stdout(predicate::str::ends_with("Exiting...\n")); } + +// Tests for syscall aliases - these verify that users can use aliased names +// and get the same behavior as using canonical names + +#[test] +#[serial] +#[ignore = "runs in special environment"] +fn signal_alias_sigprocmask() { + let pinchy = PinchyTest::new(None, None); + + // Use the alias "sigprocmask" instead of "rt_sigprocmask" + let handle = run_workload(&["sigprocmask"], "rt_sig"); + + // Expected output should show rt_sigprocmask (the canonical name) + let expected_output = escaped_regex(indoc! {r#" + @PID@ rt_sigprocmask(how: SIG_BLOCK, set: [SIGUSR1], oldset: [], sigsetsize: 8) = 0 (success) + @PID@ rt_sigprocmask(how: SIG_SETMASK, set: NULL, oldset: [SIGUSR1], sigsetsize: 8) = 0 (success) + @PID@ rt_sigprocmask(how: SIG_UNBLOCK, set: [SIGUSR1], oldset: NULL, sigsetsize: 8) = 0 (success) + @PID@ rt_sigprocmask(how: SIG_SETMASK, set: [], oldset: NULL, sigsetsize: 8) = 0 (success) + "#}); + + let output = handle.join().unwrap(); + Assert::new(output) + .success() + .stdout(predicate::str::is_match(&expected_output).unwrap()); + + let output = pinchy.wait(); + Assert::new(output) + .success() + .stdout(predicate::str::ends_with("Exiting...\n")); +} + +#[test] +#[serial] +#[ignore = "runs in special environment"] +fn signal_alias_sigaction() { + let pinchy = PinchyTest::new(None, None); + + // Use the alias "sigaction" instead of "rt_sigaction" + let handle = run_workload(&["sigaction"], "rt_sigaction_standard"); + + // Expected output should show rt_sigaction (the canonical name) + let expected_output = escaped_regex(indoc! {r#" + @PID@ rt_sigaction(signum: SIGUSR1, act: @ADDR@, oldact: @ADDR@, sigsetsize: 8) = 0 (success) + @PID@ rt_sigaction(signum: SIGUSR1, act: 0x0, oldact: @ADDR@, sigsetsize: 8) = 0 (success) + @PID@ rt_sigaction(signum: SIGUSR1, act: @ADDR@, oldact: 0x0, sigsetsize: 8) = 0 (success) + "#}); + + let output = handle.join().unwrap(); + Assert::new(output) + .success() + .stdout(predicate::str::is_match(&expected_output).unwrap()); + + let output = pinchy.wait(); + Assert::new(output) + .success() + .stdout(predicate::str::ends_with("Exiting...\n")); +} + +#[test] +#[serial] +#[ignore = "runs in special environment"] +#[cfg(target_arch = "aarch64")] +fn aarch64_alias_open() { + let pinchy = PinchyTest::new(None, None); + + // On aarch64, use the alias "open" which maps to "openat" + let handle = run_workload(&["open", "read", "lseek"], "pinchy_reads"); + + // Expected output should show openat (the canonical name on aarch64) + let expected_output = escaped_regex(indoc! {r#" + @PID@ openat(dirfd: AT_FDCWD, pathname: "pinchy/tests/GPLv2", flags: 0x0 (O_RDONLY), mode: 0) = 3 + @PID@ read(fd: 3, buf: @READBUF@, count: 128) = 128 (bytes) + @PID@ lseek(fd: 3, offset: 0, whence: SEEK_SET) = 0 (bytes) + "#}); + + let output = handle.join().unwrap(); + Assert::new(output) + .success() + .stdout(predicate::str::is_match(&expected_output).unwrap()); + + let output = pinchy.wait(); + Assert::new(output) + .success() + .stdout(predicate::str::ends_with("Exiting...\n")); +} + +#[test] +#[serial] +#[ignore = "runs in special environment"] +#[cfg(target_arch = "aarch64")] +fn aarch64_alias_stat() { + let pinchy = PinchyTest::new(None, None); + + // On aarch64, use the alias "stat" which maps to "newfstatat" + let handle = run_workload(&["stat", "fstat"], "filesystem_syscalls_test"); + + // Expected output should show newfstatat (the canonical name on aarch64) + // Note: We only filter for stat/newfstatat, so we won't see getdents64 + let expected_output = escaped_regex(indoc! {r#" + @PID@ fstat(fd: @NUMBER@, struct stat: { mode: 0o@NUMBER@ (@MODE@), ino: @NUMBER@, dev: @NUMBER@, nlink: @NUMBER@, uid: @NUMBER@, gid: @NUMBER@, size: 18092, blksize: @NUMBER@, blocks: @NUMBER@, atime: @NUMBER@, mtime: @NUMBER@, ctime: @NUMBER@ }) = 0 (success) + @PID@ newfstatat(dirfd: AT_FDCWD, pathname: "pinchy/tests/GPLv2", struct stat: { mode: 0o@NUMBER@ (@MODE@), ino: @NUMBER@, dev: @NUMBER@, nlink: @NUMBER@, uid: @NUMBER@, gid: @NUMBER@, size: 18092, blksize: @NUMBER@, blocks: @NUMBER@, atime: @NUMBER@, mtime: @NUMBER@, ctime: @NUMBER@ }, flags: 0) = 0 (success) + "#}); + + let output = handle.join().unwrap(); + Assert::new(output) + .success() + .stdout(predicate::str::is_match(&expected_output).unwrap()); + + let output = pinchy.wait(); + Assert::new(output) + .success() + .stdout(predicate::str::ends_with("Exiting...\n")); +} + +#[test] +#[serial] +#[ignore = "runs in special environment"] +fn mixed_aliases_and_canonical() { + let pinchy = PinchyTest::new(None, None); + + // Mix aliases and canonical names in the same filter + let handle = run_workload(&["sigprocmask", "rt_sigaction"], "rt_sig"); + + // Should capture rt_sigprocmask calls (from sigprocmask alias) + let expected_output = escaped_regex(indoc! {r#" + @PID@ rt_sigprocmask(how: SIG_BLOCK, set: [SIGUSR1], oldset: [], sigsetsize: 8) = 0 (success) + @PID@ rt_sigprocmask(how: SIG_SETMASK, set: NULL, oldset: [SIGUSR1], sigsetsize: 8) = 0 (success) + @PID@ rt_sigprocmask(how: SIG_UNBLOCK, set: [SIGUSR1], oldset: NULL, sigsetsize: 8) = 0 (success) + @PID@ rt_sigprocmask(how: SIG_SETMASK, set: [], oldset: NULL, sigsetsize: 8) = 0 (success) + "#}); + + let output = handle.join().unwrap(); + Assert::new(output) + .success() + .stdout(predicate::str::is_match(&expected_output).unwrap()); + + let output = pinchy.wait(); + Assert::new(output) + .success() + .stdout(predicate::str::ends_with("Exiting...\n")); +} From d964570c87682d3b801834982e51053ed920fc22 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Feb 2026 00:44:29 +0000 Subject: [PATCH 6/7] Enable user feature for nix crate to fix build The build was failing because nix::unistd::User requires the "user" feature to be enabled in the nix crate dependency. Co-authored-by: kov <1271291+kov@users.noreply.github.com> --- pinchy/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pinchy/Cargo.toml b/pinchy/Cargo.toml index 33cfab9..861151c 100644 --- a/pinchy/Cargo.toml +++ b/pinchy/Cargo.toml @@ -29,7 +29,7 @@ bytes = "1.10.1" zbus = { version = "5.7", features = ["tokio"] } zbus_macros = "5.7" clap = { workspace = true, default-features = true, features = ["derive"] } -nix = "0.30.1" +nix = { version = "0.30.1", features = ["user"] } [build-dependencies] anyhow = { workspace = true } From d46529a4071e92f12c8162034d6386c2ea11cbaa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Feb 2026 16:37:34 +0000 Subject: [PATCH 7/7] Fix test build failure by moving unit tests to tests module Moved parse_syscall_names unit tests from inline definition in client.rs to a separate tests/client.rs file. This fixes the duplicate module definition error that was preventing tests from building. Co-authored-by: kov <1271291+kov@users.noreply.github.com> --- pinchy/src/client.rs | 106 --------------------------------- pinchy/src/tests/client.rs | 117 +++++++++++++++++++++++++++++++++++++ pinchy/src/tests/mod.rs | 1 + 3 files changed, 118 insertions(+), 106 deletions(-) create mode 100644 pinchy/src/tests/client.rs diff --git a/pinchy/src/client.rs b/pinchy/src/client.rs index afad7fa..f807183 100644 --- a/pinchy/src/client.rs +++ b/pinchy/src/client.rs @@ -125,109 +125,3 @@ async fn relay_trace(fd: OwnedFd, formatting_style: FormattingStyle) -> Result<( } Ok(stdout.flush().await?) } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_parse_syscall_names_canonical() { - // Test canonical syscall names work - let result = parse_syscall_names(&["read".to_string(), "write".to_string()]); - assert!(result.is_ok()); - let syscalls = result.unwrap(); - assert_eq!(syscalls.len(), 2); - } - - #[test] - fn test_parse_syscall_names_with_rt_prefix() { - // Test that rt_ prefixed names work - let result = parse_syscall_names(&["rt_sigaction".to_string()]); - assert!(result.is_ok()); - let syscalls = result.unwrap(); - assert_eq!(syscalls.len(), 1); - } - - #[test] - fn test_parse_syscall_names_aliases() { - // Test that aliases work (non-rt_ versions should map to rt_ versions) - let result = parse_syscall_names(&["sigaction".to_string()]); - assert!(result.is_ok()); - let syscalls = result.unwrap(); - assert_eq!(syscalls.len(), 1); - - // Verify it resolves to the same number as rt_sigaction - let rt_result = parse_syscall_names(&["rt_sigaction".to_string()]); - assert!(rt_result.is_ok()); - assert_eq!(syscalls[0], rt_result.unwrap()[0]); - } - - #[test] - fn test_parse_syscall_names_multiple_aliases() { - // Test multiple aliases at once - let result = parse_syscall_names(&[ - "sigaction".to_string(), - "sigprocmask".to_string(), - "sigreturn".to_string(), - ]); - assert!(result.is_ok()); - let syscalls = result.unwrap(); - assert_eq!(syscalls.len(), 3); - } - - #[test] - fn test_parse_syscall_names_mixed() { - // Test mixing canonical names and aliases - let result = parse_syscall_names(&[ - "read".to_string(), - "sigaction".to_string(), - "write".to_string(), - ]); - assert!(result.is_ok()); - let syscalls = result.unwrap(); - assert_eq!(syscalls.len(), 3); - } - - #[test] - fn test_parse_syscall_names_unknown() { - // Test that unknown syscall names produce an error - let result = parse_syscall_names(&["nonexistent_syscall".to_string()]); - assert!(result.is_err()); - assert!(result - .unwrap_err() - .contains("Unknown syscall name: nonexistent_syscall")); - } - - #[test] - #[cfg(target_arch = "aarch64")] - fn test_parse_syscall_names_aarch64_aliases() { - // Test aarch64-specific aliases that map to *at variants - let result = parse_syscall_names(&[ - "open".to_string(), - "stat".to_string(), - "poll".to_string(), - ]); - assert!(result.is_ok()); - let syscalls = result.unwrap(); - assert_eq!(syscalls.len(), 3); - - // Verify aliases resolve to the *at variants - let open_nr = syscall_nr_from_name("open"); - let openat_nr = syscall_nr_from_name("openat"); - assert_eq!(open_nr, openat_nr); - } - - #[test] - #[cfg(target_arch = "x86_64")] - fn test_parse_syscall_names_x86_64_direct() { - // On x86_64, these syscalls exist directly (not as aliases) - let result = parse_syscall_names(&[ - "open".to_string(), - "stat".to_string(), - "poll".to_string(), - ]); - assert!(result.is_ok()); - let syscalls = result.unwrap(); - assert_eq!(syscalls.len(), 3); - } -} diff --git a/pinchy/src/tests/client.rs b/pinchy/src/tests/client.rs new file mode 100644 index 0000000..01d2228 --- /dev/null +++ b/pinchy/src/tests/client.rs @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// Copyright (c) 2025 Gustavo Noronha Silva + +use pinchy_common::syscalls::{syscall_nr_from_name, ALL_SYSCALLS}; + +fn parse_syscall_names(names: &[String]) -> Result, String> { + let mut out = Vec::new(); + for name in names { + match syscall_nr_from_name(name) { + Some(nr) if ALL_SYSCALLS.contains(&nr) => out.push(nr), + Some(_) => return Err(format!("Syscall '{name}' is not supported by this build")), + None => return Err(format!("Unknown syscall name: {name}")), + } + } + Ok(out) +} + +#[test] +fn test_parse_syscall_names_canonical() { + // Test canonical syscall names work + let result = parse_syscall_names(&["read".to_string(), "write".to_string()]); + assert!(result.is_ok()); + let syscalls = result.unwrap(); + assert_eq!(syscalls.len(), 2); +} + +#[test] +fn test_parse_syscall_names_with_rt_prefix() { + // Test that rt_ prefixed names work + let result = parse_syscall_names(&["rt_sigaction".to_string()]); + assert!(result.is_ok()); + let syscalls = result.unwrap(); + assert_eq!(syscalls.len(), 1); +} + +#[test] +fn test_parse_syscall_names_aliases() { + // Test that aliases work (non-rt_ versions should map to rt_ versions) + let result = parse_syscall_names(&["sigaction".to_string()]); + assert!(result.is_ok()); + let syscalls = result.unwrap(); + assert_eq!(syscalls.len(), 1); + + // Verify it resolves to the same number as rt_sigaction + let rt_result = parse_syscall_names(&["rt_sigaction".to_string()]); + assert!(rt_result.is_ok()); + assert_eq!(syscalls[0], rt_result.unwrap()[0]); +} + +#[test] +fn test_parse_syscall_names_multiple_aliases() { + // Test multiple aliases at once + let result = parse_syscall_names(&[ + "sigaction".to_string(), + "sigprocmask".to_string(), + "sigreturn".to_string(), + ]); + assert!(result.is_ok()); + let syscalls = result.unwrap(); + assert_eq!(syscalls.len(), 3); +} + +#[test] +fn test_parse_syscall_names_mixed() { + // Test mixing canonical names and aliases + let result = parse_syscall_names(&[ + "read".to_string(), + "sigaction".to_string(), + "write".to_string(), + ]); + assert!(result.is_ok()); + let syscalls = result.unwrap(); + assert_eq!(syscalls.len(), 3); +} + +#[test] +fn test_parse_syscall_names_unknown() { + // Test that unknown syscall names produce an error + let result = parse_syscall_names(&["nonexistent_syscall".to_string()]); + assert!(result.is_err()); + assert!(result + .unwrap_err() + .contains("Unknown syscall name: nonexistent_syscall")); +} + +#[test] +#[cfg(target_arch = "aarch64")] +fn test_parse_syscall_names_aarch64_aliases() { + // Test aarch64-specific aliases that map to *at variants + let result = parse_syscall_names(&[ + "open".to_string(), + "stat".to_string(), + "poll".to_string(), + ]); + assert!(result.is_ok()); + let syscalls = result.unwrap(); + assert_eq!(syscalls.len(), 3); + + // Verify aliases resolve to the *at variants + let open_nr = syscall_nr_from_name("open"); + let openat_nr = syscall_nr_from_name("openat"); + assert_eq!(open_nr, openat_nr); +} + +#[test] +#[cfg(target_arch = "x86_64")] +fn test_parse_syscall_names_x86_64_direct() { + // On x86_64, these syscalls exist directly (not as aliases) + let result = parse_syscall_names(&[ + "open".to_string(), + "stat".to_string(), + "poll".to_string(), + ]); + assert!(result.is_ok()); + let syscalls = result.unwrap(); + assert_eq!(syscalls.len(), 3); +} diff --git a/pinchy/src/tests/mod.rs b/pinchy/src/tests/mod.rs index c380d1e..3603274 100644 --- a/pinchy/src/tests/mod.rs +++ b/pinchy/src/tests/mod.rs @@ -2,6 +2,7 @@ // Copyright (c) 2025 Gustavo Noronha Silva mod basic_io; +mod client; mod filesystem; mod ipc; mod memory;