diff --git a/CHANGELOG.md b/CHANGELOG.md index d5d4528bff..42010a8410 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). ([#739](https://github.com/nix-rust/nix/pull/739)) - Added nix::sys::ptrace::detach. ([#749](https://github.com/nix-rust/nix/pull/749)) +- Added `nix::dirent::{opendir, fdopendir, readdir, telldir, seekdir}` + ([#558](https://github.com/nix-rust/nix/pull/558)) ### Changed - Renamed existing `ptrace` wrappers to encourage namespacing ([#692](https://github.com/nix-rust/nix/pull/692)) diff --git a/src/dirent.rs b/src/dirent.rs new file mode 100644 index 0000000000..a29a33696b --- /dev/null +++ b/src/dirent.rs @@ -0,0 +1,135 @@ +//! Directory Stream functions +//! +//! [Further reading and details on the C API](http://man7.org/linux/man-pages/man3/opendir.3.html) + +use {Result, Error, Errno, NixPath}; +use errno; +use libc::{self, DIR, c_long}; +use std::convert::{AsRef, Into}; +use std::ffi::CStr; +use std::mem; + +#[cfg(any(target_os = "linux"))] +use libc::ino64_t; + +#[cfg(any(target_os = "android"))] +use libc::ino_t as ino64_t; + +#[cfg(any(target_os = "linux", target_os = "android"))] +use libc::{dirent64, readdir64}; + +#[cfg(not(any(target_os = "linux", target_os = "android")))] +use libc::{dirent as dirent64, ino_t as ino64_t, readdir as readdir64}; + +#[cfg(not(any(target_os = "ios", target_os = "macos")))] +use std::os::unix::io::RawFd; + +/// Directory Stream object +pub struct DirectoryStream(*mut DIR); + +impl AsRef for DirectoryStream { + fn as_ref(&self) -> &DIR { + unsafe { &*self.0 } + } +} + +/// Consumes directory stream and return underlying directory pointer. +/// +/// The pointer must be deallocated manually using `libc::closedir` +impl Into<*mut DIR> for DirectoryStream { + fn into(self) -> *mut DIR { + let dirp = self.0; + mem::forget(self); + dirp + } +} + +impl Drop for DirectoryStream { + fn drop(&mut self) { + unsafe { libc::closedir(self.0) }; + } +} + +/// A directory entry +pub struct DirectoryEntry<'a>(&'a dirent64); + +impl<'a> DirectoryEntry<'a> { + /// File name + pub fn name(&self) -> &CStr { + unsafe{ + CStr::from_ptr(self.0.d_name.as_ptr()) + } + } + + /// Inode number + pub fn inode(&self) -> ino64_t { + #[cfg(not(any(target_os = "freebsd", target_os = "netbsd", target_os="dragonfly")))] + return self.0.d_ino; + #[cfg(any(target_os = "freebsd", target_os = "netbsd", target_os="dragonfly"))] + return self.0.d_fileno; + } +} + +impl<'a> AsRef for DirectoryEntry<'a> { + fn as_ref(&self) -> &dirent64 { + self.0 + } +} + +/// Opens a directory stream corresponding to the directory name. +/// +/// The stream is positioned at the first entry in the directory. +pub fn opendir(name: &P) -> Result { + let dirp = try!(name.with_nix_path(|cstr| unsafe { libc::opendir(cstr.as_ptr()) })); + if dirp.is_null() { + Err(Error::last().into()) + } else { + Ok(DirectoryStream(dirp)) + } +} + +/// Returns directory stream corresponding to the open file descriptor `fd` +/// +/// After a successful call to this function, `fd` is used internally by +/// the implementation, and should not otherwise be used by the application +#[cfg(not(any(target_os = "ios", target_os = "macos")))] +pub fn fdopendir(fd: RawFd) -> Result { + let dirp = unsafe { libc::fdopendir(fd) }; + if dirp.is_null() { + Err(Error::last().into()) + } else { + Ok(DirectoryStream(dirp)) + } +} + +/// Returns the next directory entry in the directory stream. +/// +/// It returns `Some(None)` on reaching the end of the directory stream. +pub fn readdir<'a>(dir: &'a mut DirectoryStream) -> Result> { + let dirent = unsafe { + Errno::clear(); + readdir64(dir.0) + }; + if dirent.is_null() { + match Errno::last() { + errno::UnknownErrno => Ok(None), + _ => Err(Error::last().into()), + } + } else { + Ok(Some(DirectoryEntry(unsafe { &*dirent }))) + } +} + +/// Sets the location in the directory stream from which the next `readdir` call will start. +/// +/// The `loc` argument should be a value returned by a previous call to `telldir` +#[cfg(not(any(target_os = "android")))] +pub fn seekdir<'a>(dir: &'a mut DirectoryStream, loc: c_long) { + unsafe { libc::seekdir(dir.0, loc) }; +} + +/// Returns the current location associated with the directory stream. +#[cfg(not(any(target_os = "android")))] +pub fn telldir<'a>(dir: &'a mut DirectoryStream) -> c_long { + unsafe { libc::telldir(dir.0) } +} diff --git a/src/lib.rs b/src/lib.rs index cadc7fb41f..1c743abad3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,6 +27,7 @@ pub extern crate libc; pub use errno::Errno; +pub mod dirent; pub mod errno; pub mod features; pub mod fcntl; diff --git a/test/test.rs b/test/test.rs index 1b73c4ffa4..09a9da6963 100644 --- a/test/test.rs +++ b/test/test.rs @@ -12,6 +12,7 @@ extern crate tempfile; extern crate nix_test as nixtest; mod sys; +mod test_dirent; mod test_fcntl; #[cfg(target_os = "linux")] mod test_mq; diff --git a/test/test_dirent.rs b/test/test_dirent.rs new file mode 100644 index 0000000000..5df53c2456 --- /dev/null +++ b/test/test_dirent.rs @@ -0,0 +1,50 @@ +use libc; +use nix::dirent::{self, opendir, readdir, seekdir, telldir, DirectoryStream}; +use std::path::Path; +use tempdir::TempDir; + +fn test_readdir(open_fn: Open) + where Open: Fn(&Path) -> DirectoryStream +{ + let tempdir = TempDir::new("nix-test_readdir") + .unwrap_or_else(|e| panic!("tempdir failed: {}", e)); + let mut dir = open_fn(tempdir.path()); + let first_inode = readdir(&mut dir) + .unwrap() + .unwrap() + .inode(); + + let pos = telldir(&mut dir); + + let second_inode = readdir(&mut dir) + .unwrap() + .unwrap() + .inode(); + seekdir(&mut dir, pos); + + let second_inode_again = readdir(&mut dir) + .unwrap() + .unwrap() + .inode(); + + assert_ne!(first_inode, second_inode); + assert_eq!(second_inode, second_inode_again); + + // end of directory + assert!(readdir(&mut dir).unwrap().is_none()); + + unsafe { libc::closedir(dir.into()) }; +} + +#[test] +fn test_opendir() { + test_readdir(|path| opendir(path).unwrap()); +} + +#[cfg(not(any(target_os = "ios", target_os = "macos")))] +#[test] +fn test_fdopendir() { + use std::os::unix::io::IntoRawFd; + use std::fs::File; + test_readdir(|path| dirent::fdopendir(File::open(path).unwrap().into_raw_fd()).unwrap()); +}