11use std:: ffi:: CStr ;
22
33use allocator_api2:: { alloc:: Allocator , vec:: Vec } ;
4- use bstr:: { BStr , ByteSlice } ;
4+ use bstr:: ByteSlice ;
55use fspy_shared:: ipc:: AccessMode ;
66use libc:: { c_char, c_int} ;
77use sigsafe:: { AsRawFd as _, BorrowedFd , CWD } ;
@@ -24,7 +24,8 @@ fn get_fd_path<A: Allocator>(allocator: A, fd: BorrowedFd<'_>) -> nix::Result<Op
2424}
2525
2626#[ cfg( target_os = "linux" ) ]
27- const PROC_FD_PATH_CAPACITY : usize = b"/proc/self/fd/" . len ( ) + 11 + 1 ;
27+ const PROC_FD_PATH_CAPACITY : usize =
28+ b"/proc/self/fd/" . len ( ) + <c_int as itoa:: Integer >:: MAX_STR_LEN + 1 ;
2829
2930#[ cfg( target_os = "linux" ) ]
3031fn proc_fd_path < ' buf > (
@@ -66,20 +67,31 @@ fn get_fd_path<A: Allocator>(allocator: A, fd: BorrowedFd<'_>) -> nix::Result<Op
6667}
6768
6869pub trait ToAbsolutePath {
69- fn to_absolute_path < R , F : FnOnce ( Option < & BStr > ) -> nix:: Result < R > > (
70+ /// Resolves this argument to an absolute path allocated in `allocator`,
71+ /// or borrowed from the argument itself when it is already absolute.
72+ ///
73+ /// The result is a C string so that callers forwarding it to an exec —
74+ /// which needs a terminator — cannot be handed unterminated bytes;
75+ /// [`as_bytes`] gives the path without the NUL.
76+ ///
77+ /// [`as_bytes`]: sigsafe::CStr::as_bytes
78+ fn to_absolute_path < ' a , A : Allocator > (
7079 self ,
71- f : F ,
72- ) -> nix:: Result < R > ;
80+ allocator : & ' a A ,
81+ ) -> nix:: Result < Option < sigsafe:: CStr < ' a , sigsafe:: Fat > > >
82+ where
83+ Self : ' a ;
7384}
7485
7586impl ToAbsolutePath for BorrowedFd < ' _ > {
76- fn to_absolute_path < R , F : FnOnce ( Option < & BStr > ) -> nix :: Result < R > > (
87+ fn to_absolute_path < ' a , A : Allocator > (
7788 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 ( ) ) )
89+ allocator : & ' a A ,
90+ ) -> nix:: Result < Option < sigsafe:: CStr < ' a , sigsafe:: Fat > > >
91+ where
92+ Self : ' a ,
93+ {
94+ Ok ( get_fd_path ( allocator, self ) ?. map ( leak_path_with_nul) )
8395 }
8496}
8597
@@ -99,49 +111,59 @@ impl PathAt<'_, '_> {
99111}
100112
101113impl ToAbsolutePath for PathAt < ' _ , ' _ > {
102- fn to_absolute_path < R , F : FnOnce ( Option < & BStr > ) -> nix :: Result < R > > (
114+ fn to_absolute_path < ' a , A : Allocator > (
103115 self ,
104- f : F ,
105- ) -> nix:: Result < R > {
106- let pathname = self . 1 . count ( ) . as_bytes ( ) . as_bstr ( ) ;
116+ allocator : & ' a A ,
117+ ) -> nix:: Result < Option < sigsafe:: CStr < ' a , sigsafe:: Fat > > >
118+ where
119+ Self : ' a ,
120+ {
121+ let counted = self . 1 . count ( ) ;
122+ let pathname = counted. as_bytes ( ) ;
107123
108124 if pathname. starts_with ( b"/" ) {
109- f ( Some ( pathname) )
125+ // Already absolute, and already NUL-terminated by the caller.
126+ Ok ( Some ( counted) )
110127 } 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+ let Some ( mut base) = get_fd_path ( allocator, self . 0 ) ? else {
129+ return Ok ( None ) ;
130+ } ;
131+ if !pathname. is_empty ( ) {
132+ if !base. ends_with ( b"/" ) {
133+ base. push ( b'/' ) ;
128134 }
129- abs_path . extend_from_slice ( pathname) ;
130- f ( Some ( abs_path . as_slice ( ) . as_bstr ( ) ) )
131- } )
135+ base . extend_from_slice ( pathname) ;
136+ }
137+ Ok ( Some ( leak_path_with_nul ( base ) ) )
132138 }
133139 }
134140}
135141
136142impl ToAbsolutePath for sigsafe:: CStr < ' _ , sigsafe:: Thin > {
137- fn to_absolute_path < R , F : FnOnce ( Option < & BStr > ) -> nix :: Result < R > > (
143+ fn to_absolute_path < ' a , A : Allocator > (
138144 self ,
139- f : F ,
140- ) -> nix:: Result < R > {
141- PathAt ( CWD , self ) . to_absolute_path ( f)
145+ allocator : & ' a A ,
146+ ) -> nix:: Result < Option < sigsafe:: CStr < ' a , sigsafe:: Fat > > >
147+ where
148+ Self : ' a ,
149+ {
150+ PathAt ( CWD , self ) . to_absolute_path ( allocator)
142151 }
143152}
144153
154+ /// Terminates a resolved path and leaves it in the allocator it came from.
155+ ///
156+ /// The storage is reclaimed when that allocator is dropped, which for the
157+ /// per-call arena is the end of the intercepted call.
158+ fn leak_path_with_nul < ' a , A : Allocator + ' a > (
159+ mut path : Vec < u8 , A > ,
160+ ) -> sigsafe:: CStr < ' a , sigsafe:: Fat > {
161+ path. push ( 0 ) ;
162+ // SAFETY: a resolved path carries no interior NUL, and exactly one was
163+ // appended above.
164+ unsafe { sigsafe:: CStr :: from_bytes_with_nul_unchecked ( path. leak ( ) ) }
165+ }
166+
145167pub trait ToAccessMode {
146168 unsafe fn to_access_mode ( self ) -> AccessMode ;
147169}
0 commit comments