Skip to content

Commit cf8c38c

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

3 files changed

Lines changed: 77 additions & 71 deletions

File tree

crates/fspy_preload_unix/src/client/convert.rs

Lines changed: 48 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,20 @@ 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>(allocator: A, fd: BorrowedFd<'_>) -> nix::Result<Option<Vec<u8, A>>> {
10+
fn get_fd_path<'a, A: Allocator>(
11+
allocator: &'a A,
12+
fd: BorrowedFd<'_>,
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
let mut path = [0; PROC_FD_PATH_CAPACITY];
1821
let path = proc_fd_path(fd, &mut path);
1922
match sigsafe_alloc::fs::readlinkat(allocator, CWD, path) {
20-
Ok(path) => Ok(Some(path)),
23+
Ok(path) => Ok(Some(path.leak().as_bstr())),
2124
Err(sigsafe::Errno::BADF | sigsafe::Errno::NOENT) => Ok(None),
2225
Err(errno) => Err(nix::errno::Errno::from_raw(errno.raw_os_error())),
2326
}
@@ -46,40 +49,40 @@ fn proc_fd_path<'buf>(
4649
}
4750

4851
#[cfg(target_os = "macos")]
49-
fn get_fd_path<A: Allocator>(allocator: A, fd: BorrowedFd<'_>) -> nix::Result<Option<Vec<u8, A>>> {
52+
fn get_fd_path<'a, A: Allocator>(
53+
allocator: &'a A,
54+
fd: BorrowedFd<'_>,
55+
) -> nix::Result<Option<&'a BStr>> {
5056
if fd.as_raw_fd() == CWD.as_raw_fd() {
5157
let path = sigsafe_alloc::fs::getcwd(allocator)
5258
.map_err(|errno| nix::errno::Errno::from_raw(errno.raw_os_error()))?
5359
.into_bytes();
54-
return Ok(Some(path));
60+
return Ok(Some(path.leak().as_bstr()));
5561
}
5662

5763
match sigsafe_alloc::fs::fcntl_getpath(allocator, fd) {
5864
Ok(path) => {
5965
// `F_GETPATH` does not return a length. Count at this caller before
6066
// converting its allocation into the returned path.
61-
Ok(Some(path.count().into_bytes()))
67+
Ok(Some(path.count().into_bytes().leak().as_bstr()))
6268
}
6369
Err(sigsafe::Errno::BADF | sigsafe::Errno::NOENT) => Ok(None),
6470
Err(errno) => Err(nix::errno::Errno::from_raw(errno.raw_os_error())),
6571
}
6672
}
6773

6874
pub trait ToAbsolutePath {
69-
fn to_absolute_path<R, F: FnOnce(Option<&BStr>) -> nix::Result<R>>(
70-
self,
71-
f: F,
72-
) -> nix::Result<R>;
75+
fn to_absolute_path<'a, A: Allocator>(self, allocator: &'a A) -> nix::Result<Option<&'a BStr>>
76+
where
77+
Self: 'a;
7378
}
7479

7580
impl ToAbsolutePath for BorrowedFd<'_> {
76-
fn to_absolute_path<R, F: FnOnce(Option<&BStr>) -> nix::Result<R>>(
77-
self,
78-
f: F,
79-
) -> nix::Result<R> {
80-
let arena = sigsafe_alloc::arena();
81-
let path = get_fd_path(&arena, self)?;
82-
f(path.as_ref().map(|path| path.as_slice().as_bstr()))
81+
fn to_absolute_path<'a, A: Allocator>(self, allocator: &'a A) -> nix::Result<Option<&'a BStr>>
82+
where
83+
Self: 'a,
84+
{
85+
get_fd_path(allocator, self)
8386
}
8487
}
8588

@@ -99,46 +102,43 @@ impl PathAt<'_, '_> {
99102
}
100103

101104
impl ToAbsolutePath for PathAt<'_, '_> {
102-
fn to_absolute_path<R, F: FnOnce(Option<&BStr>) -> nix::Result<R>>(
103-
self,
104-
f: F,
105-
) -> nix::Result<R> {
105+
fn to_absolute_path<'a, A: Allocator>(self, allocator: &'a A) -> nix::Result<Option<&'a BStr>>
106+
where
107+
Self: 'a,
108+
{
106109
let pathname = self.1.count().as_bytes().as_bstr();
107110

108111
if pathname.starts_with(b"/") {
109-
f(Some(pathname))
112+
Ok(Some(pathname))
110113
} else {
111-
self.0.to_absolute_path(|base| {
112-
let Some(base) = base else {
113-
return f(None);
114-
};
115-
if pathname.is_empty() {
116-
return f(Some(base));
117-
}
118-
119-
let arena = sigsafe_alloc::arena();
120-
let needs_separator = !base.ends_with(b"/");
121-
let mut abs_path = Vec::with_capacity_in(
122-
base.len() + usize::from(needs_separator) + pathname.len(),
123-
&arena,
124-
);
125-
abs_path.extend_from_slice(base);
126-
if needs_separator {
127-
abs_path.push(b'/');
128-
}
129-
abs_path.extend_from_slice(pathname);
130-
f(Some(abs_path.as_slice().as_bstr()))
131-
})
114+
let Some(base) = self.0.to_absolute_path(allocator)? else {
115+
return Ok(None);
116+
};
117+
if pathname.is_empty() {
118+
return Ok(Some(base));
119+
}
120+
121+
let needs_separator = !base.ends_with(b"/");
122+
let mut abs_path = Vec::with_capacity_in(
123+
base.len() + usize::from(needs_separator) + pathname.len(),
124+
allocator,
125+
);
126+
abs_path.extend_from_slice(base);
127+
if needs_separator {
128+
abs_path.push(b'/');
129+
}
130+
abs_path.extend_from_slice(pathname);
131+
Ok(Some(abs_path.leak().as_bstr()))
132132
}
133133
}
134134
}
135135

136136
impl ToAbsolutePath for sigsafe::CStr<'_, sigsafe::Thin> {
137-
fn to_absolute_path<R, F: FnOnce(Option<&BStr>) -> nix::Result<R>>(
138-
self,
139-
f: F,
140-
) -> nix::Result<R> {
141-
PathAt(CWD, self).to_absolute_path(f)
137+
fn to_absolute_path<'a, A: Allocator>(self, allocator: &'a A) -> nix::Result<Option<&'a BStr>>
138+
where
139+
Self: 'a,
140+
{
141+
PathAt(CWD, self).to_absolute_path(allocator)
142142
}
143143
}
144144

crates/fspy_preload_unix/src/client/mod.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -109,14 +109,11 @@ 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 () = path.to_absolute_path(|abs_path| {
113-
let Some(abs_path) = abs_path else {
114-
return Ok(Ok(()));
115-
};
116-
Ok(self.send(mode, Path::new(OsStr::from_bytes(abs_path))))
117-
})??;
118-
119-
Ok(())
112+
let arena = sigsafe_alloc::arena();
113+
let Some(abs_path) = path.to_absolute_path(&arena)? else {
114+
return Ok(());
115+
};
116+
self.send(mode, Path::new(OsStr::from_bytes(abs_path)))
120117
}
121118
}
122119

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

Lines changed: 24 additions & 15 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,39 @@ mod linux_only {
195192
reason = "suppresses unused warning on *::original"
196193
)]
197194
let _unused = execveat::original;
198-
// SAFETY: PathAt wraps a valid dirfd and pathname pointer from the interposed execveat call
199-
let abs_path_result = unsafe {
200-
PathAt::borrow_raw(dirfd, pathname).to_absolute_path(|path| {
201-
let Some(path) = path else {
202-
return Ok(None);
203-
};
204-
Ok(Some(CString::new(&**path).unwrap()))
205-
})
206-
};
207-
let abs_path = match abs_path_result {
195+
// SAFETY: pathname points to a valid C string, so its first byte is readable.
196+
if unsafe { pathname.cast::<u8>().read() } == b'/' {
197+
return handle_exec(
198+
ExecResolveConfig::search_path_disabled(),
199+
pathname,
200+
argv.cast(),
201+
envp.cast(),
202+
);
203+
}
204+
205+
let arena = sigsafe_alloc::arena();
206+
// SAFETY: dirfd and pathname are valid arguments from the interposed execveat call.
207+
let path = unsafe { PathAt::borrow_raw(dirfd, pathname) };
208+
let abs_path = match path.to_absolute_path(&arena) {
208209
Ok(None) => {
209210
// SAFETY: forwarding the original arguments to the real execveat syscall
210211
return unsafe { execveat::original()(dirfd, pathname, argv, envp, flags) };
211212
}
212-
Ok(Some(path)) => path.as_ptr(),
213+
Ok(Some(path)) => path,
213214
Err(errno) => {
214215
errno.set();
215216
return -1;
216217
}
217218
};
218-
handle_exec(ExecResolveConfig::search_path_disabled(), abs_path, argv.cast(), envp.cast())
219+
let mut c_path = allocator_api2::vec::Vec::with_capacity_in(abs_path.len() + 1, &arena);
220+
c_path.extend_from_slice(abs_path);
221+
c_path.push(0);
222+
handle_exec(
223+
ExecResolveConfig::search_path_disabled(),
224+
c_path.as_ptr().cast(),
225+
argv.cast(),
226+
envp.cast(),
227+
)
219228
}
220229

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

0 commit comments

Comments
 (0)