Skip to content

Commit 341c727

Browse files
wan9chicodex
andcommitted
refactor(fspy): return resolved paths without callbacks
Co-authored-by: GPT-5 Codex <codex@openai.com>
1 parent d11b23e commit 341c727

3 files changed

Lines changed: 83 additions & 74 deletions

File tree

crates/fspy_preload_unix/src/client/convert.rs

Lines changed: 55 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,21 @@ use libc::{c_char, c_int};
77
use sigsafe::{AsRawFd as _, BorrowedFd, CWD};
88

99
#[cfg(target_os = "linux")]
10-
fn get_fd_path<A: Allocator>(fd: BorrowedFd<'_>, allocator: A) -> nix::Result<Option<Vec<u8, A>>> {
10+
fn get_fd_path<'a, A: Allocator>(
11+
fd: BorrowedFd<'_>,
12+
allocator: &'a A,
13+
) -> nix::Result<Option<&'a BStr>> {
1114
if fd.as_raw_fd() == CWD.as_raw_fd() {
1215
let path = sigsafe_alloc::fs::getcwd(allocator)
1316
.map_err(|errno| nix::errno::Errno::from_raw(errno.raw_os_error()))?
1417
.into_bytes();
15-
return Ok(Some(path));
18+
return Ok(Some(path.leak().as_bstr()));
1619
}
1720

1821
let mut path = [0; PROC_FD_PATH_CAPACITY];
1922
let path = proc_fd_path(fd, &mut path);
2023
match sigsafe_alloc::fs::readlink(allocator, path) {
21-
Ok(path) => Ok(Some(path)),
24+
Ok(path) => Ok(Some(path.leak().as_bstr())),
2225
Err(sigsafe::Errno::BADF | sigsafe::Errno::NOENT) => Ok(None),
2326
Err(errno) => Err(nix::errno::Errno::from_raw(errno.raw_os_error())),
2427
}
@@ -61,30 +64,35 @@ fn proc_fd_path<'buf>(
6164
}
6265

6366
#[cfg(target_os = "macos")]
64-
fn get_fd_path<A: Allocator>(fd: BorrowedFd<'_>, allocator: A) -> nix::Result<Option<Vec<u8, A>>> {
67+
fn get_fd_path<'a, A: Allocator>(
68+
fd: BorrowedFd<'_>,
69+
allocator: &'a A,
70+
) -> nix::Result<Option<&'a BStr>> {
6571
if fd.as_raw_fd() == CWD.as_raw_fd() {
6672
let path = sigsafe_alloc::fs::getcwd(allocator)
6773
.map_err(|errno| nix::errno::Errno::from_raw(errno.raw_os_error()))?
6874
.into_bytes();
69-
return Ok(Some(path));
75+
return Ok(Some(path.leak().as_bstr()));
7076
}
7177

7278
match sigsafe_alloc::fs::fcntl_getpath(allocator, fd) {
7379
Ok(path) => {
7480
// `F_GETPATH` does not return a length. Count at this caller before
7581
// converting its allocation into the returned path.
76-
Ok(Some(path.count().into_bytes()))
82+
Ok(Some(path.count().into_bytes().leak().as_bstr()))
7783
}
7884
Err(sigsafe::Errno::BADF | sigsafe::Errno::NOENT) => Ok(None),
7985
Err(errno) => Err(nix::errno::Errno::from_raw(errno.raw_os_error())),
8086
}
8187
}
8288

8389
pub trait ToAbsolutePath {
84-
unsafe fn to_absolute_path<R, F: FnOnce(Option<&BStr>) -> nix::Result<R>>(
90+
unsafe fn to_absolute_path<'a, A: Allocator>(
8591
self,
86-
f: F,
87-
) -> nix::Result<R>;
92+
allocator: &'a A,
93+
) -> nix::Result<Option<&'a BStr>>
94+
where
95+
Self: 'a;
8896
}
8997

9098
pub struct Fd<'fd>(pub BorrowedFd<'fd>);
@@ -106,23 +114,14 @@ impl Fd<'_> {
106114
}
107115

108116
impl ToAbsolutePath for Fd<'_> {
109-
unsafe fn to_absolute_path<R, F: FnOnce(Option<&BStr>) -> nix::Result<R>>(
117+
unsafe fn to_absolute_path<'a, A: Allocator>(
110118
self,
111-
f: F,
112-
) -> nix::Result<R> {
113-
#[cfg(target_os = "linux")]
114-
{
115-
let arena = sigsafe_alloc::arena();
116-
let path = get_fd_path(self.0, &arena)?;
117-
f(path.as_ref().map(|path| path.as_slice().as_bstr()))
118-
}
119-
120-
#[cfg(target_os = "macos")]
121-
{
122-
let arena = sigsafe_alloc::arena();
123-
let path = get_fd_path(self.0, &arena)?;
124-
f(path.as_ref().map(|path| path.as_slice().as_bstr()))
125-
}
119+
allocator: &'a A,
120+
) -> nix::Result<Option<&'a BStr>>
121+
where
122+
Self: 'a,
123+
{
124+
get_fd_path(self.0, allocator)
126125
}
127126
}
128127

@@ -141,51 +140,52 @@ impl<'path, Repr> PathAt<'_, 'path, Repr> {
141140
}
142141

143142
impl<Repr> ToAbsolutePath for PathAt<'_, '_, Repr> {
144-
unsafe fn to_absolute_path<R, F: FnOnce(Option<&BStr>) -> nix::Result<R>>(
143+
unsafe fn to_absolute_path<'a, A: Allocator>(
145144
self,
146-
f: F,
147-
) -> nix::Result<R> {
145+
allocator: &'a A,
146+
) -> nix::Result<Option<&'a BStr>>
147+
where
148+
Self: 'a,
149+
{
148150
// SAFETY: self.1 is a valid NUL-terminated string.
149151
let pathname = unsafe { CStr::from_ptr(self.1.as_ptr()) }.to_bytes().as_bstr();
150152

151153
if pathname.first().copied() == Some(b'/') {
152-
f(pathname.into())
154+
Ok(Some(pathname))
153155
} else {
154156
// SAFETY: delegates the same caller-provided descriptor to Fd.
155-
unsafe {
156-
Fd(self.0).to_absolute_path(|base| {
157-
let Some(base) = base else {
158-
return f(None);
159-
};
160-
if pathname.is_empty() {
161-
return f(Some(base));
162-
}
163-
164-
let arena = sigsafe_alloc::arena();
165-
let needs_separator = !base.ends_with(b"/");
166-
let mut abs_path = Vec::with_capacity_in(
167-
base.len() + usize::from(needs_separator) + pathname.len(),
168-
&arena,
169-
);
170-
abs_path.extend_from_slice(base);
171-
if needs_separator {
172-
abs_path.push(b'/');
173-
}
174-
abs_path.extend_from_slice(pathname);
175-
f(Some(abs_path.as_slice().as_bstr()))
176-
})
157+
let Some(base) = (unsafe { Fd(self.0).to_absolute_path(allocator) })? else {
158+
return Ok(None);
159+
};
160+
if pathname.is_empty() {
161+
return Ok(Some(base));
162+
}
163+
164+
let needs_separator = !base.ends_with(b"/");
165+
let mut abs_path = Vec::with_capacity_in(
166+
base.len() + usize::from(needs_separator) + pathname.len(),
167+
allocator,
168+
);
169+
abs_path.extend_from_slice(base);
170+
if needs_separator {
171+
abs_path.push(b'/');
177172
}
173+
abs_path.extend_from_slice(pathname);
174+
Ok(Some(abs_path.leak().as_bstr()))
178175
}
179176
}
180177
}
181178

182179
impl<Repr> ToAbsolutePath for sigsafe::CStr<'_, Repr> {
183-
unsafe fn to_absolute_path<R, F: FnOnce(Option<&BStr>) -> nix::Result<R>>(
180+
unsafe fn to_absolute_path<'a, A: Allocator>(
184181
self,
185-
f: F,
186-
) -> nix::Result<R> {
182+
allocator: &'a A,
183+
) -> nix::Result<Option<&'a BStr>>
184+
where
185+
Self: 'a,
186+
{
187187
// SAFETY: delegates the same caller-provided C string to PathAt.
188-
unsafe { PathAt(CWD, self).to_absolute_path(f) }
188+
unsafe { PathAt(CWD, self).to_absolute_path(allocator) }
189189
}
190190
}
191191

crates/fspy_preload_unix/src/client/mod.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -109,15 +109,12 @@ impl Client {
109109
) -> anyhow::Result<()> {
110110
// SAFETY: mode contains a valid pointer (if ModeStr) or a plain value, as provided by the caller
111111
let mode = unsafe { mode.to_access_mode() };
112+
let arena = sigsafe_alloc::arena();
112113
// SAFETY: path contains valid pointers to C strings/file descriptors, as provided by the caller
113-
let () = unsafe {
114-
path.to_absolute_path(|abs_path| {
115-
let Some(abs_path) = abs_path else {
116-
return Ok(Ok(()));
117-
};
118-
Ok(self.send(mode, Path::new(OsStr::from_bytes(abs_path))))
119-
})
120-
}??;
114+
let Some(abs_path) = (unsafe { path.to_absolute_path(&arena) })? else {
115+
return Ok(());
116+
};
117+
self.send(mode, Path::new(OsStr::from_bytes(abs_path)))?;
121118

122119
Ok(())
123120
}

crates/fspy_preload_unix/src/interceptions/spawn/exec/mod.rs

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
mod with_argv;
22

3-
#[cfg(target_os = "linux")]
4-
use std::ffi::CString;
5-
63
use fspy_shared_unix::exec::ExecResolveConfig;
74
use libc::{c_char, c_int};
85
use with_argv::with_argv;
@@ -195,27 +192,42 @@ mod linux_only {
195192
reason = "suppresses unused warning on *::original"
196193
)]
197194
let _unused = execveat::original;
195+
// SAFETY: pathname is a valid C string provided to execveat.
196+
let pathname_bytes = unsafe { std::ffi::CStr::from_ptr(pathname) }.to_bytes();
197+
if pathname_bytes.first().copied() == Some(b'/') {
198+
return handle_exec(
199+
ExecResolveConfig::search_path_disabled(),
200+
pathname,
201+
argv.cast(),
202+
envp.cast(),
203+
);
204+
}
205+
206+
let arena = sigsafe_alloc::arena();
198207
// SAFETY: PathAt wraps a valid dirfd and pathname pointer from the interposed execveat call
199208
let abs_path_result = unsafe {
200-
PathAt::borrow_raw(dirfd, sigsafe::CStr::from_ptr(pathname)).to_absolute_path(|path| {
201-
let Some(path) = path else {
202-
return Ok(None);
203-
};
204-
Ok(Some(CString::new(&**path).unwrap()))
205-
})
209+
PathAt::borrow_raw(dirfd, sigsafe::CStr::from_ptr(pathname)).to_absolute_path(&arena)
206210
};
207211
let abs_path = match abs_path_result {
208212
Ok(None) => {
209213
// SAFETY: forwarding the original arguments to the real execveat syscall
210214
return unsafe { execveat::original()(dirfd, pathname, argv, envp, flags) };
211215
}
212-
Ok(Some(path)) => path.as_ptr(),
216+
Ok(Some(path)) => path,
213217
Err(errno) => {
214218
errno.set();
215219
return -1;
216220
}
217221
};
218-
handle_exec(ExecResolveConfig::search_path_disabled(), abs_path, argv.cast(), envp.cast())
222+
let mut c_path = allocator_api2::vec::Vec::with_capacity_in(abs_path.len() + 1, &arena);
223+
c_path.extend_from_slice(abs_path);
224+
c_path.push(0);
225+
handle_exec(
226+
ExecResolveConfig::search_path_disabled(),
227+
c_path.as_ptr().cast(),
228+
argv.cast(),
229+
envp.cast(),
230+
)
219231
}
220232

221233
intercept!(fexecve(64): unsafe extern "C" fn(

0 commit comments

Comments
 (0)