-
Notifications
You must be signed in to change notification settings - Fork 683
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add support for dirent related function #558
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<DIR> 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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A convention in |
||
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<dirent64> for DirectoryEntry<'a> { | ||
fn as_ref(&self) -> &dirent64 { | ||
self.0 | ||
} | ||
} | ||
|
||
/// Opens a directory stream corresponding to the directory name. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you run |
||
/// | ||
/// The stream is positioned at the first entry in the directory. | ||
pub fn opendir<P: ?Sized + NixPath>(name: &P) -> Result<DirectoryStream> { | ||
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<DirectoryStream> { | ||
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<Option<DirectoryEntry>> { | ||
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm thinking a newtype around |
||
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) } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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>(open_fn: Open) | ||
where Open: Fn(&Path) -> DirectoryStream | ||
{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you check the full name of the directory? It's confusing that you're checking just the first character of the filename. It'd also be helpful if you created a directory in the tempdir you made so you have 3 directories, then you iterate through all of them checking their names, and then seek back to check that seeking works. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no guarantee on the order of directory entries. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I still think it's worth checking their names, we can treat it as a set, where the order doesn't matter but the contents should still be the same. We'd expect to encounter both "." and "..", but we aren't certain of the order, but that's fine, we can still do that check. |
||
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()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why don't we call this
DirectoryStream
orDirStream
instead ofDir
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also renamed
Dirent
toDirectoryEntry
for consistency.