diff --git a/CHANGELOG.md b/CHANGELOG.md index c0fb9e2102..4c548a57d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Added `nix::ptrace::{ptrace_get_data, ptrace_getsiginfo, ptrace_setsiginfo and nix::Error::UnsupportedOperation}` ([#614](https://github.com/nix-rust/nix/pull/614)) +- Added `nix::dirent::{opendir, fdopendir, readdir, telldir, seekdir}` + ([#558](https://github.com/nix-rust/nix/pull/558)) ### Changed - Changed ioctl! write to take argument by value instead as pointer. diff --git a/src/dirent.rs b/src/dirent.rs new file mode 100644 index 0000000000..8b2b29f7c3 --- /dev/null +++ b/src/dirent.rs @@ -0,0 +1,119 @@ +//! 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(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 libc::dirent); + +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) -> libc::ino_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) -> &libc::dirent { + 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)) + } +} + +/// Like `opendir()`, but returns a directory stream for the directory +/// referred to by 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(); + libc::readdir(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 f0cdb581ee..facb0e1e4f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,6 +34,7 @@ pub mod libc { pub use libc::{c_int, c_void}; 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 e2f5a02444..ace5e1e920 100644 --- a/test/test.rs +++ b/test/test.rs @@ -10,6 +10,7 @@ extern crate tempfile; extern crate nix_test as nixtest; mod sys; +mod test_dirent; mod test_fcntl; #[cfg(any(target_os = "linux"))] mod test_mq; diff --git a/test/test_dirent.rs b/test/test_dirent.rs new file mode 100644 index 0000000000..331e95a306 --- /dev/null +++ b/test/test_dirent.rs @@ -0,0 +1,51 @@ +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 letter1 = readdir(&mut dir) + .unwrap() + .unwrap() + .name() + .to_bytes()[0]; + assert_eq!(letter1, '.' as u8); + + let pos = telldir(&mut dir); + + let letter2 = readdir(&mut dir) + .unwrap() + .unwrap() + .name() + .to_bytes()[0]; + assert_eq!(letter2, '.' as u8); + + seekdir(&mut dir, pos); // same again + + let letter3 = readdir(&mut dir) + .unwrap() + .unwrap() + .name() + .to_bytes()[0]; + assert_eq!(letter3, '.' as u8); + + // end of directory + assert!(readdir(&mut dir).unwrap().is_none()); +} + +#[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()); +}