diff --git a/src/backend/libc/fs/dir.rs b/src/backend/libc/fs/dir.rs index 4b4676872..2e026e334 100644 --- a/src/backend/libc/fs/dir.rs +++ b/src/backend/libc/fs/dir.rs @@ -129,6 +129,14 @@ impl Dir { unsafe { c::rewinddir(self.libc_dir.as_ptr()) } } + /// `seekdir(self, offset)` + #[inline] + pub fn seekdir(&mut self, offset: i64) -> io::Result<()> { + self.any_errors = false; + unsafe { c::seekdir(self.libc_dir.as_ptr(), offset as c::c_long) } + Ok(()) + } + /// `readdir(self)`, where `None` means the end of the directory. pub fn read(&mut self) -> Option> { // If we've seen errors, don't continue to try to read anything further. @@ -171,6 +179,9 @@ impl Dir { #[cfg(not(any(freebsdlike, netbsdlike, target_os = "vita")))] d_ino: dirent.d_ino, + #[cfg(any(linux_like))] + d_off: dirent.d_off, + #[cfg(any(freebsdlike, netbsdlike))] d_fileno: dirent.d_fileno, @@ -277,6 +288,9 @@ pub struct DirEntry { d_fileno: c::ino_t, name: CString, + + #[cfg(any(linux_like))] + d_off: c::off_t, } impl DirEntry { @@ -286,6 +300,15 @@ impl DirEntry { &self.name } + /// Returns the "offset" of this directory entry. Note that this is not + /// a true numerical offset but an opaque cookie that identifies a + /// position in the given stream. + #[cfg(any(linux_like))] + #[inline] + pub fn offset(&self) -> i64 { + self.d_off as i64 + } + /// Returns the type of this directory entry. #[cfg(not(any( solarish, diff --git a/src/backend/linux_raw/fs/dir.rs b/src/backend/linux_raw/fs/dir.rs index 429dd023d..0ea77cf19 100644 --- a/src/backend/linux_raw/fs/dir.rs +++ b/src/backend/linux_raw/fs/dir.rs @@ -79,6 +79,21 @@ impl Dir { self.pos = self.buf.len(); } + /// `seekdir(self, offset)` + #[inline] + pub fn seekdir(&mut self, offset: i64) -> io::Result<()> { + self.any_errors = false; + match io::retry_on_intr(|| { + crate::backend::fs::syscalls::_seek(self.fd.as_fd(), offset, SEEK_SET) + }) { + Ok(_) => Ok(()), + Err(err) => { + self.any_errors = true; + Err(err) + } + } + } + /// `readdir(self)`, where `None` means the end of the directory. pub fn read(&mut self) -> Option> { // If we've seen errors, don't continue to try to read anything further. @@ -112,6 +127,7 @@ impl Dir { let offsetof_d_reclen = (as_ptr(&z.d_reclen) as usize) - base; let offsetof_d_name = (as_ptr(&z.d_name) as usize) - base; let offsetof_d_ino = (as_ptr(&z.d_ino) as usize) - base; + let offsetof_d_off = (as_ptr(&z.d_off) as usize) - base; let offsetof_d_type = (as_ptr(&z.d_type) as usize) - base; // Test if we need more entries, and if so, read more. @@ -145,7 +161,7 @@ impl Dir { let name = name.to_owned(); assert!(name.as_bytes().len() <= self.buf.len() - name_start); - // Do an unaligned u64 load. + // Do an unaligned u64 load for `d_ino`. let d_ino = u64::from_ne_bytes([ self.buf[pos + offsetof_d_ino], self.buf[pos + offsetof_d_ino + 1], @@ -157,12 +173,24 @@ impl Dir { self.buf[pos + offsetof_d_ino + 7], ]); + // Do an unaligned i64 load for `d_off` + let d_off = i64::from_ne_bytes([ + self.buf[pos + offsetof_d_off], + self.buf[pos + offsetof_d_off + 1], + self.buf[pos + offsetof_d_off + 2], + self.buf[pos + offsetof_d_off + 3], + self.buf[pos + offsetof_d_off + 4], + self.buf[pos + offsetof_d_off + 5], + self.buf[pos + offsetof_d_off + 6], + self.buf[pos + offsetof_d_off + 7], + ]); + let d_type = self.buf[pos + offsetof_d_type]; // Check that our types correspond to the `linux_dirent64` types. let _ = linux_dirent64 { d_ino, - d_off: 0, + d_off, d_type, d_reclen, d_name: Default::default(), @@ -170,6 +198,7 @@ impl Dir { Some(Ok(DirEntry { d_ino, + d_off, d_type, name, })) @@ -261,6 +290,7 @@ impl fmt::Debug for Dir { pub struct DirEntry { d_ino: u64, d_type: u8, + d_off: i64, name: CString, } @@ -271,6 +301,14 @@ impl DirEntry { &self.name } + /// Returns the "offset" of this directory entry. Note that this is not + /// a true numerical offset but an opaque cookie that identifies a + /// position in the given stream. + #[inline] + pub fn offset(&self) -> i64 { + self.d_off + } + /// Returns the type of this directory entry. #[inline] pub fn file_type(&self) -> FileType { diff --git a/tests/fs/dir.rs b/tests/fs/dir.rs index 949a78974..9fb939805 100644 --- a/tests/fs/dir.rs +++ b/tests/fs/dir.rs @@ -62,6 +62,51 @@ fn test_dir_read_from() { assert!(saw_cargo_toml); } +#[test] +fn test_dir_seek() { + let t = rustix::fs::openat( + rustix::fs::CWD, + rustix::cstr!("."), + rustix::fs::OFlags::RDONLY | rustix::fs::OFlags::CLOEXEC, + rustix::fs::Mode::empty(), + ) + .unwrap(); + + let mut dir = rustix::fs::Dir::read_from(&t).unwrap(); + + let _file = rustix::fs::openat( + &t, + rustix::cstr!("Cargo.toml"), + rustix::fs::OFlags::RDONLY | rustix::fs::OFlags::CLOEXEC, + rustix::fs::Mode::empty(), + ) + .unwrap(); + + // Read the first directory entry and record offset + let entry = dir.read().unwrap(); + let entry = entry.unwrap(); + let offset = entry.offset(); + + // Read the rest of the directory entries and record the names + let mut entries = Vec::new(); + while let Some(entry) = dir.read() { + let entry = entry.unwrap(); + entries.push(entry.file_name().to_string_lossy().into_owned()); + } + + // Seek to the stored position + dir.seekdir(offset).unwrap(); + + // Confirm that we're getting the same results as before + let mut entries2 = Vec::new(); + while let Some(entry) = dir.read() { + let entry = entry.unwrap(); + entries2.push(entry.file_name().to_string_lossy().into_owned()); + } + + assert_eq!(entries, entries2); +} + #[test] fn test_dir_new() { let t = rustix::fs::openat(