Skip to content

Commit e668a24

Browse files
wan9chicodex
andcommitted
refactor(fspy): carry C strings through path resolution
Co-authored-by: GPT-5 Codex <codex@openai.com>
1 parent 87dc766 commit e668a24

7 files changed

Lines changed: 54 additions & 54 deletions

File tree

crates/fspy_preload_unix/src/client/convert.rs

Lines changed: 30 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,14 @@ fn get_fd_path<A: Allocator>(allocator: A, fd: BorrowedFd<'_>) -> nix::Result<Op
6666
}
6767

6868
pub trait ToAbsolutePath {
69-
unsafe fn to_absolute_path<R, F: FnOnce(Option<&BStr>) -> nix::Result<R>>(
69+
fn to_absolute_path<R, F: FnOnce(Option<&BStr>) -> nix::Result<R>>(
7070
self,
7171
f: F,
7272
) -> nix::Result<R>;
7373
}
7474

7575
impl ToAbsolutePath for BorrowedFd<'_> {
76-
unsafe fn to_absolute_path<R, F: FnOnce(Option<&BStr>) -> nix::Result<R>>(
76+
fn to_absolute_path<R, F: FnOnce(Option<&BStr>) -> nix::Result<R>>(
7777
self,
7878
f: F,
7979
) -> nix::Result<R> {
@@ -99,51 +99,46 @@ impl PathAt<'_, '_> {
9999
}
100100

101101
impl ToAbsolutePath for PathAt<'_, '_> {
102-
unsafe fn to_absolute_path<R, F: FnOnce(Option<&BStr>) -> nix::Result<R>>(
102+
fn to_absolute_path<R, F: FnOnce(Option<&BStr>) -> nix::Result<R>>(
103103
self,
104104
f: F,
105105
) -> nix::Result<R> {
106-
// SAFETY: self.1 is a valid NUL-terminated string.
107-
let pathname = unsafe { CStr::from_ptr(self.1.as_ptr()) }.to_bytes().as_bstr();
106+
let pathname = self.1.count().as_bytes().as_bstr();
108107

109-
if pathname.first().copied() == Some(b'/') {
110-
f(pathname.into())
108+
if pathname.starts_with(b"/") {
109+
f(Some(pathname))
111110
} else {
112-
// SAFETY: delegates the same caller-provided descriptor.
113-
unsafe {
114-
self.0.to_absolute_path(|base| {
115-
let Some(base) = base else {
116-
return f(None);
117-
};
118-
if pathname.is_empty() {
119-
return f(Some(base));
120-
}
121-
122-
let arena = sigsafe_alloc::arena();
123-
let needs_separator = !base.ends_with(b"/");
124-
let mut abs_path = Vec::with_capacity_in(
125-
base.len() + usize::from(needs_separator) + pathname.len(),
126-
&arena,
127-
);
128-
abs_path.extend_from_slice(base);
129-
if needs_separator {
130-
abs_path.push(b'/');
131-
}
132-
abs_path.extend_from_slice(pathname);
133-
f(Some(abs_path.as_slice().as_bstr()))
134-
})
135-
}
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+
})
136132
}
137133
}
138134
}
139135

140-
impl ToAbsolutePath for *const c_char {
141-
unsafe fn to_absolute_path<R, F: FnOnce(Option<&BStr>) -> nix::Result<R>>(
136+
impl ToAbsolutePath for sigsafe::CStr<'_, sigsafe::Thin> {
137+
fn to_absolute_path<R, F: FnOnce(Option<&BStr>) -> nix::Result<R>>(
142138
self,
143139
f: F,
144140
) -> nix::Result<R> {
145-
// SAFETY: delegates the same caller-provided C string pointer.
146-
unsafe { PathAt(CWD, sigsafe::CStr::from_ptr(self)).to_absolute_path(f) }
141+
PathAt(CWD, self).to_absolute_path(f)
147142
}
148143
}
149144

crates/fspy_preload_unix/src/client/mod.rs

Lines changed: 6 additions & 9 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-
// 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-
}??;
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+
})??;
121118

122119
Ok(())
123120
}

crates/fspy_preload_unix/src/interceptions/access.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ intercept!(access(64): unsafe extern "C" fn(pathname: *const c_char, mode: c_int
1010
unsafe extern "C" fn access(pathname: *const c_char, mode: c_int) -> c_int {
1111
// SAFETY: pathname is a valid C string pointer provided by the caller of the interposed function
1212
unsafe {
13-
handle_open(pathname, AccessMode::READ);
13+
handle_open(sigsafe::CStr::from_ptr(pathname), AccessMode::READ);
1414
}
1515
// SAFETY: calling the original libc access() with the same arguments forwarded from the interposed function
1616
unsafe { access::original()(pathname, mode) }

crates/fspy_preload_unix/src/interceptions/dirent.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ unsafe extern "C" fn scandir(
1717
compar: *const c_void,
1818
) -> c_int {
1919
// SAFETY: dirname is a valid C string pointer provided by the caller of the interposed function
20-
unsafe { handle_open(dirname, AccessMode::READ_DIR) }
20+
unsafe { handle_open(sigsafe::CStr::from_ptr(dirname), AccessMode::READ_DIR) }
2121
// SAFETY: calling the original libc scandir() with the same arguments forwarded from the interposed function
2222
unsafe { scandir::original()(dirname, namelist, select, compar) }
2323
}
@@ -39,7 +39,7 @@ mod macos_only {
3939
compar: *const c_void,
4040
) -> c_int {
4141
// SAFETY: dirname is a valid C string pointer provided by the caller of the interposed function
42-
unsafe { handle_open(dirname, AccessMode::READ_DIR) };
42+
unsafe { handle_open(sigsafe::CStr::from_ptr(dirname), AccessMode::READ_DIR) };
4343
// SAFETY: calling the original libc scandir_b() with the same arguments forwarded from the interposed function
4444
unsafe { scandir_b::original()(dirname, namelist, select, compar) }
4545
}
@@ -82,7 +82,7 @@ unsafe extern "C" fn fdopendir(fd: c_int) -> *mut DIR {
8282
intercept!(opendir(64): unsafe extern "C" fn (*const c_char) -> *mut DIR);
8383
unsafe extern "C" fn opendir(dir_name: *const c_char) -> *mut DIR {
8484
// SAFETY: dir_name is a valid C string pointer provided by the caller of the interposed function
85-
unsafe { handle_open(dir_name, AccessMode::READ_DIR) };
85+
unsafe { handle_open(sigsafe::CStr::from_ptr(dir_name), AccessMode::READ_DIR) };
8686
// SAFETY: calling the original libc opendir() with the same arguments forwarded from the interposed function
8787
unsafe { opendir::original()(dir_name) }
8888
}

crates/fspy_preload_unix/src/interceptions/open.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ type Mode = c_int;
2828
intercept!(open(64): unsafe extern "C" fn(*const c_char, c_int, args: ...) -> c_int);
2929
unsafe extern "C" fn open(path: *const c_char, flags: c_int, mut args: ...) -> c_int {
3030
// SAFETY: path is a valid C string pointer provided by the caller of the interposed function
31-
unsafe { handle_open(path, OpenFlags(flags)) };
31+
unsafe { handle_open(sigsafe::CStr::from_ptr(path), OpenFlags(flags)) };
3232
if has_mode_arg(flags) {
3333
// SAFETY: when O_CREAT or O_TMPFILE is set, a mode_t argument is required by the open() contract
3434
let mode: Mode = unsafe { args.next_arg() };
@@ -67,7 +67,7 @@ intercept!(open_nocancel: unsafe extern "C" fn(*const c_char, c_int, ...) -> c_i
6767
#[cfg(target_os = "macos")]
6868
unsafe extern "C" fn open_nocancel(path: *const c_char, flags: c_int, mut args: ...) -> c_int {
6969
// SAFETY: path is a valid C string pointer provided by the caller of open$NOCANCEL
70-
unsafe { handle_open(path, OpenFlags(flags)) };
70+
unsafe { handle_open(sigsafe::CStr::from_ptr(path), OpenFlags(flags)) };
7171
if has_mode_arg(flags) {
7272
// SAFETY: O_CREAT requires a mode argument, matching the open$NOCANCEL contract
7373
let mode: Mode = unsafe { args.next_arg() };
@@ -104,7 +104,7 @@ unsafe extern "C" fn openat_nocancel(
104104
intercept!(fopen(64): unsafe extern "C" fn(path: *const c_char, mode: *const c_char) -> *mut FILE);
105105
unsafe extern "C" fn fopen(path: *const c_char, mode: *const c_char) -> *mut libc::FILE {
106106
// SAFETY: path and mode are valid C string pointers provided by the caller of the interposed function
107-
unsafe { handle_open(path, ModeStr(mode)) };
107+
unsafe { handle_open(sigsafe::CStr::from_ptr(path), ModeStr(mode)) };
108108
// SAFETY: calling the original libc fopen() with the same arguments forwarded from the interposed function
109109
unsafe { fopen::original()(path, mode) }
110110
}
@@ -116,7 +116,7 @@ unsafe extern "C" fn freopen(
116116
stream: *mut FILE,
117117
) -> *mut FILE {
118118
// SAFETY: path and mode are valid C string pointers provided by the caller of the interposed function
119-
unsafe { handle_open(path, ModeStr(mode)) };
119+
unsafe { handle_open(sigsafe::CStr::from_ptr(path), ModeStr(mode)) };
120120
// SAFETY: calling the original libc freopen() with the same arguments forwarded from the interposed function
121121
unsafe { freopen::original()(path, mode, stream) }
122122
}

crates/fspy_preload_unix/src/interceptions/stat.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ intercept!(stat(64): unsafe extern "C" fn(path: *const c_char, buf: *mut stat_st
1212
unsafe extern "C" fn stat(path: *const c_char, buf: *mut stat_struct) -> c_int {
1313
// SAFETY: path is a valid C string pointer provided by the caller of the interposed function
1414
unsafe {
15-
handle_open(path, AccessMode::READ);
15+
handle_open(sigsafe::CStr::from_ptr(path), AccessMode::READ);
1616
}
1717
// SAFETY: calling the original libc stat() with the same arguments forwarded from the interposed function
1818
unsafe { stat::original()(path, buf) }
@@ -23,7 +23,7 @@ unsafe extern "C" fn lstat(path: *const c_char, buf: *mut stat_struct) -> c_int
2323
// TODO: add accessmode ReadNoFollow
2424
// SAFETY: path is a valid C string pointer provided by the caller of the interposed function
2525
unsafe {
26-
handle_open(path, AccessMode::READ);
26+
handle_open(sigsafe::CStr::from_ptr(path), AccessMode::READ);
2727
}
2828
// SAFETY: calling the original libc lstat() with the same arguments forwarded from the interposed function
2929
unsafe { lstat::original()(path, buf) }

crates/sigsafe/src/c_str.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,13 @@ impl<'a> CStr<'a, Fat> {
102102
}
103103
}
104104

105+
/// Returns the string's bytes without the terminating NUL.
106+
#[must_use]
107+
pub const fn as_bytes(&self) -> &'a [u8] {
108+
let bytes = self.as_bytes_with_nul();
109+
bytes.split_at(bytes.len() - 1).0
110+
}
111+
105112
/// Returns the string's bytes, including the terminating NUL.
106113
#[must_use]
107114
pub const fn as_bytes_with_nul(&self) -> &'a [u8] {
@@ -132,6 +139,7 @@ mod tests {
132139
assert_eq!(size_of::<CStr<'_, Thin>>(), size_of::<*const u8>());
133140
assert_eq!(size_of::<CStr<'_, Fat>>(), size_of::<(*const u8, usize)>());
134141
assert_eq!(fat.len_with_nul(), 4);
142+
assert_eq!(fat.as_bytes(), b"abc");
135143
assert_eq!(fat.as_bytes_with_nul(), b"abc\0");
136144
assert_eq!(counted.as_bytes_with_nul(), fat.as_bytes_with_nul());
137145
}

0 commit comments

Comments
 (0)