@@ -5,16 +5,10 @@ use crate::fcntl::{self, OFlag};
5
5
use crate :: sys;
6
6
use crate :: { NixPath , Result } ;
7
7
use cfg_if:: cfg_if;
8
- use std:: ffi;
8
+ use std:: ffi:: { CStr , CString } ;
9
9
use std:: os:: unix:: io:: { AsRawFd , IntoRawFd , RawFd } ;
10
10
use std:: ptr;
11
11
12
- #[ cfg( target_os = "linux" ) ]
13
- use libc:: { dirent64 as dirent, readdir64_r as readdir_r} ;
14
-
15
- #[ cfg( not( target_os = "linux" ) ) ]
16
- use libc:: { dirent, readdir_r} ;
17
-
18
12
/// An open directory.
19
13
///
20
14
/// This is a lower-level interface than [`std::fs::ReadDir`]. Notable differences:
@@ -26,7 +20,7 @@ use libc::{dirent, readdir_r};
26
20
/// * can be iterated through multiple times without closing and reopening the file
27
21
/// descriptor. Each iteration rewinds when finished.
28
22
/// * returns entries for `.` (current directory) and `..` (parent directory).
29
- /// * returns entries' names as a [`CStr`][cstr] (no allocation or conversion beyond whatever libc
23
+ /// * returns entries' names as a [`CStr`] (no allocation or conversion beyond whatever libc
30
24
/// does).
31
25
///
32
26
/// [AsFd]: std::os::fd::AsFd
@@ -124,10 +118,7 @@ impl Dir {
124
118
}
125
119
}
126
120
127
- // `Dir` is not `Sync`. With the current implementation, it could be, but according to
128
- // https://www.gnu.org/software/libc/manual/html_node/Reading_002fClosing-Directory.html,
129
- // future versions of POSIX are likely to obsolete `readdir_r` and specify that it's unsafe to
130
- // call `readdir` simultaneously from multiple threads.
121
+ // `Dir` is not `Sync` because it's unsafe to call `readdir` simultaneously from multiple threads.
131
122
//
132
123
// `Dir` is safe to pass from one thread to another, as it's not reference-counted.
133
124
unsafe impl Send for Dir { }
@@ -160,29 +151,22 @@ impl Drop for Dir {
160
151
}
161
152
162
153
// The pass by mut is technically needless only because the inner NonNull is
163
- // Copy. But philosophically we're mutating the Dir, so we pass by mut.
154
+ // Copy. But we are actually mutating the Dir, so we pass by mut.
164
155
#[ allow( clippy:: needless_pass_by_ref_mut) ]
165
- fn next ( dir : & mut Dir ) -> Option < Result < Entry > > {
156
+ fn readdir ( dir : & mut Dir ) -> Option < Result < Entry > > {
157
+ Errno :: clear ( ) ;
166
158
unsafe {
167
- // Note: POSIX specifies that portable applications should dynamically allocate a
168
- // buffer with room for a `d_name` field of size `pathconf(..., _PC_NAME_MAX)` plus 1
169
- // for the NUL byte. It doesn't look like the std library does this; it just uses
170
- // fixed-sized buffers (and libc's dirent seems to be sized so this is appropriate).
171
- // Probably fine here too then.
172
- let mut ent = std:: mem:: MaybeUninit :: < dirent > :: uninit ( ) ;
173
- let mut result = ptr:: null_mut ( ) ;
174
- if let Err ( e) = Errno :: result ( readdir_r (
175
- dir. 0 . as_ptr ( ) ,
176
- ent. as_mut_ptr ( ) ,
177
- & mut result,
178
- ) ) {
179
- return Some ( Err ( e) ) ;
180
- }
181
- if result. is_null ( ) {
182
- return None ;
159
+ let de = libc:: readdir ( dir. 0 . as_ptr ( ) ) ;
160
+ if de. is_null ( ) {
161
+ if Errno :: last_raw ( ) == 0 {
162
+ // EOF
163
+ None
164
+ } else {
165
+ Some ( Err ( Errno :: last ( ) ) )
166
+ }
167
+ } else {
168
+ Some ( Ok ( Entry :: from_raw ( & * de) ) )
183
169
}
184
- assert_eq ! ( result, ent. as_mut_ptr( ) ) ;
185
- Some ( Ok ( Entry ( ent. assume_init ( ) ) ) )
186
170
}
187
171
}
188
172
@@ -194,7 +178,7 @@ impl Iterator for Iter<'_> {
194
178
type Item = Result < Entry > ;
195
179
196
180
fn next ( & mut self ) -> Option < Self :: Item > {
197
- next ( self . 0 )
181
+ readdir ( self . 0 )
198
182
}
199
183
}
200
184
@@ -212,7 +196,7 @@ impl Iterator for OwningIter {
212
196
type Item = Result < Entry > ;
213
197
214
198
fn next ( & mut self ) -> Option < Self :: Item > {
215
- next ( & mut self . 0 )
199
+ readdir ( & mut self . 0 )
216
200
}
217
201
}
218
202
@@ -252,9 +236,18 @@ impl IntoIterator for Dir {
252
236
/// A directory entry, similar to `std::fs::DirEntry`.
253
237
///
254
238
/// Note that unlike the std version, this may represent the `.` or `..` entries.
255
- #[ derive( Copy , Clone , Debug , Eq , Hash , PartialEq ) ]
256
- #[ repr( transparent) ]
257
- pub struct Entry ( dirent ) ;
239
+ #[ derive( Clone , Debug , Eq , Hash , PartialEq ) ]
240
+ pub struct Entry {
241
+ ino : u64 ,
242
+ type_ : Option < Type > ,
243
+ // On some platforms libc::dirent is a "flexible-length structure", where there may be
244
+ // data beyond the end of the struct definition. So it isn't possible to copy it and subsequently
245
+ // dereference the copy's d_name field. Nor is it possible for Entry to wrap a &libc::dirent.
246
+ // At least, not if it is to work with the Iterator trait. Because the Entry would need to
247
+ // maintain a mutable reference to the Dir, which would conflict with the iterator's mutable
248
+ // reference to the same Dir. So we're forced to copy the name here.
249
+ name : CString ,
250
+ }
258
251
259
252
/// Type of file referenced by a directory entry
260
253
#[ derive( Copy , Clone , Debug , Eq , Hash , PartialEq ) ]
@@ -277,10 +270,28 @@ pub enum Type {
277
270
278
271
impl Entry {
279
272
/// Returns the inode number (`d_ino`) of the underlying `dirent`.
273
+ pub fn ino ( & self ) -> u64 {
274
+ self . ino
275
+ }
276
+
277
+ /// Returns the bare file name of this directory entry without any other leading path component.
278
+ pub fn file_name ( & self ) -> & CStr {
279
+ self . name . as_c_str ( )
280
+ }
281
+
282
+ /// Returns the type of this directory entry, if known.
283
+ ///
284
+ /// See platform `readdir(3)` or `dirent(5)` manpage for when the file type is known;
285
+ /// notably, some Linux filesystems don't implement this. The caller should use `stat` or
286
+ /// `fstat` if this returns `None`.
287
+ pub fn file_type ( & self ) -> Option < Type > {
288
+ self . type_
289
+ }
290
+
280
291
#[ allow( clippy:: useless_conversion) ] // Not useless on all OSes
281
292
// The cast is not unnecessary on all platforms.
282
293
#[ allow( clippy:: unnecessary_cast) ]
283
- pub fn ino ( & self ) -> u64 {
294
+ fn from_raw ( de : & libc :: dirent ) -> Self {
284
295
cfg_if ! {
285
296
if #[ cfg( any( target_os = "aix" ,
286
297
target_os = "emscripten" ,
@@ -291,38 +302,34 @@ impl Entry {
291
302
solarish,
292
303
linux_android,
293
304
apple_targets) ) ] {
294
- self . 0 . d_ino as u64
305
+ let ino = de . d_ino as u64 ;
295
306
} else {
296
- u64 :: from( self . 0 . d_fileno)
307
+ let ino = u64 :: from( de . d_fileno) ;
297
308
}
298
309
}
299
- }
300
-
301
- /// Returns the bare file name of this directory entry without any other leading path component.
302
- pub fn file_name ( & self ) -> & ffi:: CStr {
303
- unsafe { ffi:: CStr :: from_ptr ( self . 0 . d_name . as_ptr ( ) ) }
304
- }
305
-
306
- /// Returns the type of this directory entry, if known.
307
- ///
308
- /// See platform `readdir(3)` or `dirent(5)` manpage for when the file type is known;
309
- /// notably, some Linux filesystems don't implement this. The caller should use `stat` or
310
- /// `fstat` if this returns `None`.
311
- pub fn file_type ( & self ) -> Option < Type > {
312
- #[ cfg( not( any( solarish, target_os = "aix" , target_os = "haiku" ) ) ) ]
313
- match self . 0 . d_type {
314
- libc:: DT_FIFO => Some ( Type :: Fifo ) ,
315
- libc:: DT_CHR => Some ( Type :: CharacterDevice ) ,
316
- libc:: DT_DIR => Some ( Type :: Directory ) ,
317
- libc:: DT_BLK => Some ( Type :: BlockDevice ) ,
318
- libc:: DT_REG => Some ( Type :: File ) ,
319
- libc:: DT_LNK => Some ( Type :: Symlink ) ,
320
- libc:: DT_SOCK => Some ( Type :: Socket ) ,
321
- /* libc::DT_UNKNOWN | */ _ => None ,
310
+ cfg_if ! {
311
+ if #[ cfg( not( any( solarish, target_os = "aix" , target_os = "haiku" ) ) ) ] {
312
+ let type_ = match de. d_type {
313
+ libc:: DT_FIFO => Some ( Type :: Fifo ) ,
314
+ libc:: DT_CHR => Some ( Type :: CharacterDevice ) ,
315
+ libc:: DT_DIR => Some ( Type :: Directory ) ,
316
+ libc:: DT_BLK => Some ( Type :: BlockDevice ) ,
317
+ libc:: DT_REG => Some ( Type :: File ) ,
318
+ libc:: DT_LNK => Some ( Type :: Symlink ) ,
319
+ libc:: DT_SOCK => Some ( Type :: Socket ) ,
320
+ /* libc::DT_UNKNOWN | */ _ => None ,
321
+ } ;
322
+ } else {
323
+ // illumos, Solaris, and Haiku systems do not have the d_type member at all:
324
+ #[ cfg( any( solarish, target_os = "aix" , target_os = "haiku" ) ) ]
325
+ let type_ = None ;
326
+ }
322
327
}
328
+ // Annoyingly, since libc::dirent is open-ended, the easiest way to read the name field in
329
+ // Rust is to treat it like a pointer.
330
+ // Safety: safe because we knod that de.d_name is in valid memory.
331
+ let name = unsafe { CStr :: from_ptr ( de. d_name . as_ptr ( ) ) } . to_owned ( ) ;
323
332
324
- // illumos, Solaris, and Haiku systems do not have the d_type member at all:
325
- #[ cfg( any( solarish, target_os = "aix" , target_os = "haiku" ) ) ]
326
- None
333
+ Entry { ino, type_, name }
327
334
}
328
335
}
0 commit comments