diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cb4eae6f9..7f67271fe3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Added `personality` (#[1331](https://github.com/nix-rust/nix/pull/1331)) - Added limited Fuchsia support (#[1285](https://github.com/nix-rust/nix/pull/1285)) - Added `getpeereid` (#[1342](https://github.com/nix-rust/nix/pull/1342)) +- Implemented `IntoIterator` for `Dir` + (#[1333](https://github.com/nix-rust/nix/pull/1333)). +### Changed ### Fixed - Define `*_MAGIC` filesystem constants on Linux s390x (#[1372](https://github.com/nix-rust/nix/pull/1372)) diff --git a/src/dir.rs b/src/dir.rs index 1898950f71..7d4ab82f79 100644 --- a/src/dir.rs +++ b/src/dir.rs @@ -92,6 +92,28 @@ impl Drop for Dir { } } +fn next(dir: &mut Dir) -> Option> { + unsafe { + // Note: POSIX specifies that portable applications should dynamically allocate a + // buffer with room for a `d_name` field of size `pathconf(..., _PC_NAME_MAX)` plus 1 + // for the NUL byte. It doesn't look like the std library does this; it just uses + // fixed-sized buffers (and libc's dirent seems to be sized so this is appropriate). + // Probably fine here too then. + let mut ent = std::mem::MaybeUninit::::uninit(); + let mut result = ptr::null_mut(); + if let Err(e) = Errno::result( + readdir_r(dir.0.as_ptr(), ent.as_mut_ptr(), &mut result)) + { + return Some(Err(e)); + } + if result.is_null() { + return None; + } + assert_eq!(result, ent.as_mut_ptr()); + Some(Ok(Entry(ent.assume_init()))) + } +} + #[derive(Debug, Eq, Hash, PartialEq)] pub struct Iter<'d>(&'d mut Dir); @@ -99,25 +121,7 @@ impl<'d> Iterator for Iter<'d> { type Item = Result; fn next(&mut self) -> Option { - unsafe { - // Note: POSIX specifies that portable applications should dynamically allocate a - // buffer with room for a `d_name` field of size `pathconf(..., _PC_NAME_MAX)` plus 1 - // for the NUL byte. It doesn't look like the std library does this; it just uses - // fixed-sized buffers (and libc's dirent seems to be sized so this is appropriate). - // Probably fine here too then. - let mut ent = std::mem::MaybeUninit::::uninit(); - let mut result = ptr::null_mut(); - if let Err(e) = Errno::result( - readdir_r((self.0).0.as_ptr(), ent.as_mut_ptr(), &mut result)) - { - return Some(Err(e)); - } - if result.is_null() { - return None; - } - assert_eq!(result, ent.as_mut_ptr()); - Some(Ok(Entry(ent.assume_init()))) - } + next(self.0) } } @@ -127,6 +131,43 @@ impl<'d> Drop for Iter<'d> { } } +/// The return type of [Dir::into_iter] +#[derive(Debug, Eq, Hash, PartialEq)] +pub struct OwningIter(Dir); + +impl Iterator for OwningIter { + type Item = Result; + + fn next(&mut self) -> Option { + next(&mut self.0) + } +} + +impl IntoIterator for Dir { + type Item = Result; + type IntoIter = OwningIter; + + /// Creates a owning iterator, that is, one that takes ownership of the + /// `Dir`. The `Dir` cannot be used after calling this. This can be useful + /// when you have a function that both creates a `Dir` instance and returns + /// an `Iterator`. + /// + /// Example: + /// + /// ``` + /// use nix::{dir::Dir, fcntl::OFlag, sys::stat::Mode}; + /// use std::{iter::Iterator, string::String}; + /// + /// fn ls_upper(dirname: &str) -> impl Iterator { + /// let d = Dir::open(dirname, OFlag::O_DIRECTORY, Mode::S_IXUSR).unwrap(); + /// d.into_iter().map(|x| x.unwrap().file_name().as_ref().to_string_lossy().to_ascii_uppercase()) + /// } + /// ``` + fn into_iter(self) -> Self::IntoIter { + OwningIter(self) + } +} + /// A directory entry, similar to `std::fs::DirEntry`. /// /// Note that unlike the std version, this may represent the `.` or `..` entries. diff --git a/test/test_dir.rs b/test/test_dir.rs index c5f9c51735..505277e714 100644 --- a/test/test_dir.rs +++ b/test/test_dir.rs @@ -34,7 +34,9 @@ fn rewind() { Mode::empty()).unwrap(); let entries1: Vec<_> = dir.iter().map(|e| e.unwrap().file_name().to_owned()).collect(); let entries2: Vec<_> = dir.iter().map(|e| e.unwrap().file_name().to_owned()).collect(); + let entries3: Vec<_> = dir.into_iter().map(|e| e.unwrap().file_name().to_owned()).collect(); assert_eq!(entries1, entries2); + assert_eq!(entries2, entries3); } #[test]