16
16
#[ cfg( test) ]
17
17
use std:: os:: unix:: ffi:: OsStringExt ;
18
18
19
- use std:: ffi:: { CStr , CString , OsStr , OsString } ;
19
+ use std:: ffi:: { CString , OsStr , OsString } ;
20
20
use std:: io;
21
21
use std:: os:: unix:: ffi:: OsStrExt ;
22
- use std:: os:: unix:: io:: { AsRawFd , RawFd } ;
22
+ use std:: os:: unix:: io:: { AsFd , AsRawFd , BorrowedFd , FromRawFd , OwnedFd , RawFd } ;
23
23
use std:: path:: Path ;
24
24
25
+ use nix:: dir:: Dir ;
26
+ use nix:: fcntl:: { OFlag , openat} ;
27
+ use nix:: sys:: stat:: { FileStat , Mode , fstatat} ;
28
+ use nix:: unistd:: { UnlinkatFlags , unlinkat} ;
29
+
25
30
// Custom error types for better error reporting
26
31
#[ derive( thiserror:: Error , Debug ) ]
27
32
pub enum SafeTraversalError {
@@ -71,136 +76,83 @@ impl From<SafeTraversalError> for io::Error {
71
76
}
72
77
}
73
78
74
- // RAII wrapper for DIR pointer
75
- struct Dir {
76
- dirp : * mut libc:: DIR ,
77
- }
78
-
79
- impl Dir {
80
- fn from_fd ( fd : RawFd ) -> io:: Result < Self > {
81
- let dirp = unsafe { libc:: fdopendir ( fd) } ;
82
- if dirp. is_null ( ) {
83
- Err ( io:: Error :: last_os_error ( ) )
84
- } else {
85
- Ok ( Dir { dirp } )
86
- }
87
- }
79
+ // Helper function to read directory entries using nix
80
+ fn read_dir_entries ( fd : & OwnedFd ) -> io:: Result < Vec < OsString > > {
81
+ let mut entries = Vec :: new ( ) ;
88
82
89
- fn read_entries ( & self ) -> io :: Result < Vec < OsString > > {
90
- let mut entries = Vec :: new ( ) ;
83
+ // Duplicate the fd for Dir (it takes ownership)
84
+ let dup_fd = nix :: unistd :: dup ( fd ) . map_err ( |e| io :: Error :: from_raw_os_error ( e as i32 ) ) ? ;
91
85
92
- loop {
93
- // Clear errno before readdir as per POSIX requirements
94
- unsafe { * libc:: __errno_location ( ) = 0 } ;
86
+ let mut dir = Dir :: from_fd ( dup_fd) . map_err ( |e| io:: Error :: from_raw_os_error ( e as i32 ) ) ?;
95
87
96
- let entry = unsafe { libc:: readdir ( self . dirp ) } ;
97
- if entry. is_null ( ) {
98
- let errno = unsafe { * libc:: __errno_location ( ) } ;
99
- if errno != 0 {
100
- return Err ( io:: Error :: from_raw_os_error ( errno) ) ;
101
- }
102
- break ;
103
- }
88
+ for entry_result in dir. iter ( ) {
89
+ let entry = entry_result. map_err ( |e| io:: Error :: from_raw_os_error ( e as i32 ) ) ?;
104
90
105
- let name = unsafe { CStr :: from_ptr ( ( * entry) . d_name . as_ptr ( ) ) } ;
106
- let name_os = OsStr :: from_bytes ( name. to_bytes ( ) ) ;
91
+ let name = entry. file_name ( ) ;
92
+ let name_os = OsStr :: from_bytes ( name. to_bytes ( ) ) ;
107
93
108
- if name_os != "." && name_os != ".." {
109
- entries. push ( name_os. to_os_string ( ) ) ;
110
- }
94
+ if name_os != "." && name_os != ".." {
95
+ entries. push ( name_os. to_os_string ( ) ) ;
111
96
}
112
-
113
- Ok ( entries)
114
97
}
115
- }
116
98
117
- impl Drop for Dir {
118
- fn drop ( & mut self ) {
119
- if !self . dirp . is_null ( ) {
120
- unsafe {
121
- libc:: closedir ( self . dirp ) ;
122
- }
123
- }
124
- }
99
+ Ok ( entries)
125
100
}
126
101
127
102
/// A directory file descriptor that enables safe traversal
128
103
pub struct DirFd {
129
- fd : RawFd ,
130
- owned : bool ,
104
+ fd : OwnedFd ,
131
105
}
132
106
133
107
impl DirFd {
134
108
/// Open a directory and return a file descriptor
135
109
pub fn open ( path : & Path ) -> io:: Result < Self > {
136
- let path_cstr = CString :: new ( path. as_os_str ( ) . as_bytes ( ) )
137
- . map_err ( |_| SafeTraversalError :: PathContainsNull ) ?;
138
-
139
- let fd = unsafe {
140
- libc:: open (
141
- path_cstr. as_ptr ( ) ,
142
- libc:: O_RDONLY | libc:: O_DIRECTORY | libc:: O_CLOEXEC ,
143
- )
144
- } ;
145
-
146
- if fd < 0 {
147
- Err ( SafeTraversalError :: OpenFailed {
110
+ let flags = OFlag :: O_RDONLY | OFlag :: O_DIRECTORY | OFlag :: O_CLOEXEC ;
111
+ let fd = nix:: fcntl:: open ( path, flags, Mode :: empty ( ) ) . map_err ( |e| {
112
+ SafeTraversalError :: OpenFailed {
148
113
path : path. to_string_lossy ( ) . into_owned ( ) ,
149
- source : io:: Error :: last_os_error ( ) ,
114
+ source : io:: Error :: from_raw_os_error ( e as i32 ) ,
150
115
}
151
- . into ( ) )
152
- } else {
153
- Ok ( DirFd { fd, owned : true } )
154
- }
116
+ } ) ?;
117
+
118
+ Ok ( DirFd { fd } )
155
119
}
156
120
157
121
/// Open a subdirectory relative to this directory
158
122
pub fn open_subdir ( & self , name : & OsStr ) -> io:: Result < Self > {
159
123
let name_cstr =
160
124
CString :: new ( name. as_bytes ( ) ) . map_err ( |_| SafeTraversalError :: PathContainsNull ) ?;
161
125
162
- let fd = unsafe {
163
- libc:: openat (
164
- self . fd ,
165
- name_cstr. as_ptr ( ) ,
166
- libc:: O_RDONLY | libc:: O_DIRECTORY | libc:: O_CLOEXEC ,
167
- )
168
- } ;
169
-
170
- if fd < 0 {
171
- Err ( SafeTraversalError :: OpenFailed {
126
+ let flags = OFlag :: O_RDONLY | OFlag :: O_DIRECTORY | OFlag :: O_CLOEXEC ;
127
+ let fd = openat ( & self . fd , name_cstr. as_c_str ( ) , flags, Mode :: empty ( ) ) . map_err ( |e| {
128
+ SafeTraversalError :: OpenFailed {
172
129
path : name. to_string_lossy ( ) . into_owned ( ) ,
173
- source : io:: Error :: last_os_error ( ) ,
130
+ source : io:: Error :: from_raw_os_error ( e as i32 ) ,
174
131
}
175
- . into ( ) )
176
- } else {
177
- Ok ( DirFd { fd, owned : true } )
178
- }
132
+ } ) ?;
133
+
134
+ Ok ( DirFd { fd } )
179
135
}
180
136
181
137
/// Get raw stat data for a file relative to this directory
182
- pub fn stat_at ( & self , name : & OsStr , follow_symlinks : bool ) -> io:: Result < libc :: stat > {
138
+ pub fn stat_at ( & self , name : & OsStr , follow_symlinks : bool ) -> io:: Result < FileStat > {
183
139
let name_cstr =
184
140
CString :: new ( name. as_bytes ( ) ) . map_err ( |_| SafeTraversalError :: PathContainsNull ) ?;
185
141
186
- let mut stat: libc:: stat = unsafe { std:: mem:: zeroed ( ) } ;
187
142
let flags = if follow_symlinks {
188
- 0
143
+ nix :: fcntl :: AtFlags :: empty ( )
189
144
} else {
190
- libc :: AT_SYMLINK_NOFOLLOW
145
+ nix :: fcntl :: AtFlags :: AT_SYMLINK_NOFOLLOW
191
146
} ;
192
147
193
- let ret = unsafe { libc:: fstatat ( self . fd , name_cstr. as_ptr ( ) , & mut stat, flags) } ;
194
-
195
- if ret < 0 {
196
- Err ( SafeTraversalError :: StatFailed {
148
+ let stat = fstatat ( & self . fd , name_cstr. as_c_str ( ) , flags) . map_err ( |e| {
149
+ SafeTraversalError :: StatFailed {
197
150
path : name. to_string_lossy ( ) . into_owned ( ) ,
198
- source : io:: Error :: last_os_error ( ) ,
151
+ source : io:: Error :: from_raw_os_error ( e as i32 ) ,
199
152
}
200
- . into ( ) )
201
- } else {
202
- Ok ( stat)
203
- }
153
+ } ) ?;
154
+
155
+ Ok ( stat)
204
156
}
205
157
206
158
/// Get metadata for a file relative to this directory
@@ -214,43 +166,18 @@ impl DirFd {
214
166
}
215
167
216
168
/// Get raw stat data for this directory
217
- pub fn fstat ( & self ) -> io:: Result < libc:: stat > {
218
- let mut stat: libc:: stat = unsafe { std:: mem:: zeroed ( ) } ;
219
-
220
- let ret = unsafe { libc:: fstat ( self . fd , & mut stat) } ;
169
+ pub fn fstat ( & self ) -> io:: Result < FileStat > {
170
+ let stat = nix:: sys:: stat:: fstat ( & self . fd ) . map_err ( |e| SafeTraversalError :: StatFailed {
171
+ path : "<current directory>" . to_string ( ) ,
172
+ source : io:: Error :: from_raw_os_error ( e as i32 ) ,
173
+ } ) ?;
221
174
222
- if ret < 0 {
223
- Err ( SafeTraversalError :: StatFailed {
224
- path : "<current directory>" . to_string ( ) ,
225
- source : io:: Error :: last_os_error ( ) ,
226
- }
227
- . into ( ) )
228
- } else {
229
- Ok ( stat)
230
- }
175
+ Ok ( stat)
231
176
}
232
177
233
178
/// Read directory entries
234
179
pub fn read_dir ( & self ) -> io:: Result < Vec < OsString > > {
235
- // Duplicate the fd for fdopendir (it takes ownership)
236
- let dup_fd = unsafe { libc:: dup ( self . fd ) } ;
237
- if dup_fd < 0 {
238
- return Err ( SafeTraversalError :: ReadDirFailed {
239
- path : "<directory>" . to_string ( ) ,
240
- source : io:: Error :: last_os_error ( ) ,
241
- }
242
- . into ( ) ) ;
243
- }
244
-
245
- let dir = Dir :: from_fd ( dup_fd) . map_err ( |e| {
246
- unsafe { libc:: close ( dup_fd) } ;
247
- SafeTraversalError :: ReadDirFailed {
248
- path : "<directory>" . to_string ( ) ,
249
- source : e,
250
- }
251
- } ) ?;
252
-
253
- dir. read_entries ( ) . map_err ( |e| {
180
+ read_dir_entries ( & self . fd ) . map_err ( |e| {
254
181
SafeTraversalError :: ReadDirFailed {
255
182
path : "<directory>" . to_string ( ) ,
256
183
source : e,
@@ -263,46 +190,45 @@ impl DirFd {
263
190
pub fn unlink_at ( & self , name : & OsStr , is_dir : bool ) -> io:: Result < ( ) > {
264
191
let name_cstr =
265
192
CString :: new ( name. as_bytes ( ) ) . map_err ( |_| SafeTraversalError :: PathContainsNull ) ?;
266
- let flags = if is_dir { libc:: AT_REMOVEDIR } else { 0 } ;
267
-
268
- let ret = unsafe { libc:: unlinkat ( self . fd , name_cstr. as_ptr ( ) , flags) } ;
193
+ let flags = if is_dir {
194
+ UnlinkatFlags :: RemoveDir
195
+ } else {
196
+ UnlinkatFlags :: NoRemoveDir
197
+ } ;
269
198
270
- if ret < 0 {
271
- Err ( SafeTraversalError :: UnlinkFailed {
199
+ unlinkat ( & self . fd , name_cstr . as_c_str ( ) , flags ) . map_err ( |e| {
200
+ SafeTraversalError :: UnlinkFailed {
272
201
path : name. to_string_lossy ( ) . into_owned ( ) ,
273
- source : io:: Error :: last_os_error ( ) ,
202
+ source : io:: Error :: from_raw_os_error ( e as i32 ) ,
274
203
}
275
- . into ( ) )
276
- } else {
277
- Ok ( ( ) )
278
- }
204
+ } ) ?;
205
+
206
+ Ok ( ( ) )
279
207
}
280
208
281
- /// Create a DirFd from an existing file descriptor (does not take ownership)
209
+ /// Create a DirFd from an existing file descriptor (takes ownership)
282
210
pub fn from_raw_fd ( fd : RawFd ) -> io:: Result < Self > {
283
211
if fd < 0 {
284
212
return Err ( io:: Error :: new (
285
213
io:: ErrorKind :: InvalidInput ,
286
214
"invalid file descriptor" ,
287
215
) ) ;
288
216
}
289
- Ok ( DirFd { fd, owned : false } )
217
+ // SAFETY: We've verified fd >= 0, and the caller is transferring ownership
218
+ let owned_fd = unsafe { OwnedFd :: from_raw_fd ( fd) } ;
219
+ Ok ( DirFd { fd : owned_fd } )
290
220
}
291
221
}
292
222
293
- impl Drop for DirFd {
294
- fn drop ( & mut self ) {
295
- if self . owned && self . fd >= 0 {
296
- unsafe {
297
- libc:: close ( self . fd ) ;
298
- }
299
- }
223
+ impl AsRawFd for DirFd {
224
+ fn as_raw_fd ( & self ) -> RawFd {
225
+ self . fd . as_raw_fd ( )
300
226
}
301
227
}
302
228
303
- impl AsRawFd for DirFd {
304
- fn as_raw_fd ( & self ) -> RawFd {
305
- self . fd
229
+ impl AsFd for DirFd {
230
+ fn as_fd ( & self ) -> BorrowedFd < ' _ > {
231
+ self . fd . as_fd ( )
306
232
}
307
233
}
308
234
@@ -374,11 +300,11 @@ impl FileType {
374
300
/// Metadata wrapper for safer access to file information
375
301
#[ derive( Debug , Clone ) ]
376
302
pub struct Metadata {
377
- stat : libc :: stat ,
303
+ stat : FileStat ,
378
304
}
379
305
380
306
impl Metadata {
381
- pub fn from_stat ( stat : libc :: stat ) -> Self {
307
+ pub fn from_stat ( stat : FileStat ) -> Self {
382
308
Self { stat }
383
309
}
384
310
@@ -410,11 +336,6 @@ impl Metadata {
410
336
}
411
337
}
412
338
413
- /// Get the raw libc::stat for compatibility with existing code
414
- pub fn as_raw_stat ( & self ) -> & libc:: stat {
415
- & self . stat
416
- }
417
-
418
339
/// Compatibility methods to match std::fs::Metadata interface
419
340
pub fn is_dir ( & self ) -> bool {
420
341
self . file_type ( ) . is_directory ( )
@@ -558,6 +479,7 @@ mod tests {
558
479
use super :: * ;
559
480
use std:: fs;
560
481
use std:: os:: unix:: fs:: symlink;
482
+ use std:: os:: unix:: io:: IntoRawFd ;
561
483
use tempfile:: TempDir ;
562
484
563
485
#[ test]
@@ -696,11 +618,16 @@ mod tests {
696
618
fn test_from_raw_fd ( ) {
697
619
let temp_dir = TempDir :: new ( ) . unwrap ( ) ;
698
620
let dir_fd = DirFd :: open ( temp_dir. path ( ) ) . unwrap ( ) ;
699
- let raw_fd = dir_fd. as_raw_fd ( ) ;
700
621
701
- let borrowed_fd = DirFd :: from_raw_fd ( raw_fd) . unwrap ( ) ;
702
- assert_eq ! ( borrowed_fd. as_raw_fd( ) , raw_fd) ;
703
- assert ! ( !borrowed_fd. owned) ; // Should not own the FD
622
+ // Duplicate the fd first so we don't have ownership conflicts
623
+ let dup_fd = nix:: unistd:: dup ( & dir_fd) . unwrap ( ) ;
624
+ let from_raw_fd = DirFd :: from_raw_fd ( dup_fd. into_raw_fd ( ) ) . unwrap ( ) ;
625
+
626
+ // Both should refer to the same directory
627
+ let stat1 = dir_fd. fstat ( ) . unwrap ( ) ;
628
+ let stat2 = from_raw_fd. fstat ( ) . unwrap ( ) ;
629
+ assert_eq ! ( stat1. st_ino, stat2. st_ino) ;
630
+ assert_eq ! ( stat1. st_dev, stat2. st_dev) ;
704
631
}
705
632
706
633
#[ test]
@@ -772,9 +699,7 @@ mod tests {
772
699
assert_eq ! ( metadata. mode( ) & libc:: S_IFMT as u32 , libc:: S_IFREG as u32 ) ;
773
700
assert_eq ! ( metadata. nlink( ) , 1 ) ;
774
701
775
- // Test raw stat access
776
- let raw_stat = metadata. as_raw_stat ( ) ;
777
- assert_eq ! ( raw_stat. st_size, metadata. size( ) as i64 ) ;
702
+ assert ! ( metadata. size( ) > 0 ) ;
778
703
}
779
704
780
705
#[ test]
0 commit comments