From 030ad14027e0fa7f03c4e7d5715684a9dc069872 Mon Sep 17 00:00:00 2001 From: Frank Rehwinkel Date: Sat, 4 Feb 2023 16:25:01 -0500 Subject: [PATCH 1/4] separate the File::statx method to own file --- src/fs/file.rs | 25 +------------------------ src/fs/mod.rs | 2 ++ src/fs/statx.rs | 28 ++++++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 24 deletions(-) create mode 100644 src/fs/statx.rs diff --git a/src/fs/file.rs b/src/fs/file.rs index c25ce30b..4510886c 100644 --- a/src/fs/file.rs +++ b/src/fs/file.rs @@ -55,7 +55,7 @@ use std::path::Path; /// ``` pub struct File { /// Open file descriptor - fd: SharedFd, + pub(crate) fd: SharedFd, } impl File { @@ -801,29 +801,6 @@ impl File { Op::fallocate(&self.fd, offset, len, flags)?.await } - /// Metadata information about a file. - /// - /// # Examples - /// - /// ```no_run - /// use tokio_uring::fs::File; - /// - /// fn main() -> Result<(), Box> { - /// tokio_uring::start(async { - /// let f = File::create("foo.txt").await?; - /// - /// // Fetch file metadata - /// let statx = f.statx().await?; - /// - /// // Close the file - /// f.close().await?; - /// Ok(()) - /// }) - /// } - pub async fn statx(&self) -> io::Result { - Op::statx(&self.fd)?.await - } - /// Closes the file. /// /// The method completes once the close operation has completed, diff --git a/src/fs/mod.rs b/src/fs/mod.rs index 82b33cb1..b3d698d5 100644 --- a/src/fs/mod.rs +++ b/src/fs/mod.rs @@ -11,3 +11,5 @@ pub use file::File; mod open_options; pub use open_options::OpenOptions; + +mod statx; diff --git a/src/fs/statx.rs b/src/fs/statx.rs new file mode 100644 index 00000000..daaadb00 --- /dev/null +++ b/src/fs/statx.rs @@ -0,0 +1,28 @@ +use super::File; +use crate::runtime::driver::op::Op; +use std::io; + +impl File { + /// Metadata information about a file. + /// + /// # Examples + /// + /// ```no_run + /// use tokio_uring::fs::File; + /// + /// fn main() -> Result<(), Box> { + /// tokio_uring::start(async { + /// let f = File::create("foo.txt").await?; + /// + /// // Fetch file metadata + /// let statx = f.statx().await?; + /// + /// // Close the file + /// f.close().await?; + /// Ok(()) + /// }) + /// } + pub async fn statx(&self) -> io::Result { + Op::statx(&self.fd)?.await + } +} From ee735ea6f9f155e4030b75438f7c93848399db49 Mon Sep 17 00:00:00 2001 From: Frank Rehwinkel Date: Sun, 5 Feb 2023 12:14:50 -0500 Subject: [PATCH 2/4] add more options for statx This adds a top level statx function that takes a path obviating the need to open a file first. This also adds a builder, named StatxBuilder, to give more control over the dirfd, path, flags and mask that are ultimately being passed to the uring statx operation. The builder can be gotten with or without an open file. Also add statx tests to the example, test_create_dir_all. --- examples/test_create_dir_all.rs | 140 +++++++++++++-- src/fs/create_dir_all.rs | 26 +-- src/fs/mod.rs | 3 + src/fs/statx.rs | 299 ++++++++++++++++++++++++++++++-- src/io/mod.rs | 1 + src/io/statx.rs | 41 ++++- src/io/util.rs | 2 +- 7 files changed, 456 insertions(+), 56 deletions(-) diff --git a/examples/test_create_dir_all.rs b/examples/test_create_dir_all.rs index e09658e9..a598c89c 100644 --- a/examples/test_create_dir_all.rs +++ b/examples/test_create_dir_all.rs @@ -1,4 +1,5 @@ use std::io; +use std::path::Path; use tokio_uring::fs; fn tests() -> std::slice::Iter<'static, Expected<'static>> { @@ -17,6 +18,14 @@ fn tests() -> std::slice::Iter<'static, Expected<'static>> { // A sequence of steps where assumption is /tmp exists and /tmp/test-good does not. // Expected::Pass(Op::create_dir("/tmp/test-good")), + Expected::Pass(Op::statx("/tmp/test-good")), + Expected::Pass(Op::StatxBuilder("/tmp/test-good")), + Expected::Pass(Op::StatxBuilder2("/tmp", "test-good")), + Expected::Pass(Op::StatxBuilder2("/tmp", "./test-good")), + Expected::Pass(Op::StatxBuilder2("/tmp/", "./test-good")), + Expected::Pass(Op::StatxBuilder2("/etc/", "/tmp/test-good")), + Expected::Pass(Op::is_dir("/tmp/test-good")), + Expected::Fail(Op::is_regfile("/tmp/test-good")), Expected::Pass(Op::create_dir("/tmp/test-good/x1")), Expected::Fail(Op::create_dir("/tmp/test-good/x1")), Expected::Pass(Op::remove_dir("/tmp/test-good/x1")), @@ -28,18 +37,38 @@ fn tests() -> std::slice::Iter<'static, Expected<'static>> { Expected::Pass(Op::remove_dir("/tmp/test-good/lots/lots")), Expected::Pass(Op::remove_dir("/tmp/test-good/lots")), Expected::Pass(Op::remove_dir("/tmp/test-good")), + Expected::Fail(Op::statx("/tmp/test-good")), + Expected::Fail(Op::StatxBuilder("/tmp/test-good")), // // A sequence that tests when mode is passed as 0, the directory can't be written to. // Expected::Pass(Op::DirBuilder2("/tmp/test-good", true, 0)), + Expected::Pass(Op::matches_mode("/tmp/test-good", 0)), Expected::Fail(Op::create_dir("/tmp/test-good/x1")), Expected::Pass(Op::remove_dir("/tmp/test-good")), // + // A sequence that tests creation of a user rwx only directory + // + Expected::Pass(Op::DirBuilder2("/tmp/test-good", true, 0o700)), + Expected::Pass(Op::matches_mode("/tmp/test-good", 0o700)), + Expected::Pass(Op::create_dir("/tmp/test-good/x1")), + Expected::Pass(Op::remove_dir("/tmp/test-good/x1")), + Expected::Pass(Op::remove_dir("/tmp/test-good")), + // // Same sequence but with recursive = false // Expected::Pass(Op::DirBuilder2("/tmp/test-good", false, 0)), Expected::Fail(Op::create_dir("/tmp/test-good/x1")), Expected::Pass(Op::remove_dir("/tmp/test-good")), + // + // Some file operations + // + Expected::Pass(Op::touch_file("/tmp/test-good-file")), + Expected::Pass(Op::is_regfile("/tmp/test-good-file")), + Expected::Fail(Op::is_dir("/tmp/test-good-file")), + Expected::Pass(Op::remove_file("/tmp/test-good-file")), + Expected::Fail(Op::is_regfile("/tmp/test-good-file")), + Expected::Fail(Op::is_dir("/tmp/test-good-file")), ] .iter() } @@ -50,10 +79,13 @@ type OpPath<'a> = &'a str; #[allow(dead_code)] #[derive(Debug)] enum Op<'a> { - FileExists(OpPath<'a>), - HasMode(OpPath<'a>, u32), - DirExists(OpPath<'a>), - TouchFile(OpPath<'a>), + statx(OpPath<'a>), + StatxBuilder(OpPath<'a>), + StatxBuilder2(OpPath<'a>, OpPath<'a>), + matches_mode(OpPath<'a>, u16), + is_regfile(OpPath<'a>), + is_dir(OpPath<'a>), + touch_file(OpPath<'a>), create_dir(OpPath<'a>), create_dir_all(OpPath<'a>), DirBuilder(OpPath<'a>), @@ -77,18 +109,13 @@ async fn main1() -> io::Result<()> { Expected::Fail(op) => (false, op), }; let res = match op { - Op::FileExists(_path) => { - unreachable!("FileExists unimplemented"); - } - Op::HasMode(_path, _mode) => { - unreachable!("HasMode unimplemented"); - } - Op::DirExists(_path) => { - unreachable!("DirExists unimplemented"); - } - Op::TouchFile(_path) => { - unreachable!("TouchFile unimplemented"); - } + Op::statx(path) => statx(path).await, + Op::StatxBuilder(path) => statx_builder(path).await, + Op::StatxBuilder2(path, rel_path) => statx_builder2(path, rel_path).await, + Op::matches_mode(path, mode) => matches_mode(path, *mode).await, + Op::is_regfile(path) => is_regfile(path).await, + Op::is_dir(path) => is_dir(path).await, + Op::touch_file(path) => touch_file(path).await, Op::create_dir(path) => fs::create_dir(path).await, Op::create_dir_all(path) => fs::create_dir_all(path).await, Op::DirBuilder(path) => fs::DirBuilder::new().create(path).await, @@ -103,7 +130,7 @@ async fn main1() -> io::Result<()> { Op::remove_dir(path) => fs::remove_dir(path).await, }; - let verbose = false; + let verbose = true; match res { Ok(_) => { @@ -143,6 +170,85 @@ async fn main1() -> io::Result<()> { } } +async fn statx>(path: P) -> io::Result<()> { + let _statx = tokio_uring::fs::statx(path).await?; + Ok(()) +} + +async fn statx_builder>(path: P) -> io::Result<()> { + let _statx = tokio_uring::fs::StatxBuilder::new() + .statx_path(path) + .await?; + Ok(()) +} + +async fn statx_builder2>(dir_path: P, rel_path: P) -> io::Result<()> { + // This shows the power of combining an open file, presumably a directory, and the relative + // path to have the statx operation return the meta data for the child of the opened directory + // descriptor. + let f = tokio_uring::fs::File::open(dir_path).await.unwrap(); + + // Fetch file metadata + let res = f.statx_builder().statx_path(rel_path).await; + + // Close the file + f.close().await.unwrap(); + + res.map(|_| ()) +} + +async fn matches_mode>(path: P, want_mode: u16) -> io::Result<()> { + let statx = tokio_uring::fs::StatxBuilder::new() + .mask(libc::STATX_MODE) + .statx_path(path) + .await?; + let got_mode = statx.stx_mode & 0o7777; + if want_mode == got_mode { + Ok(()) + } else { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + format!("want mode {want_mode:#o}, got mode {got_mode:#o}"), + )) + } +} + +async fn touch_file>(path: P) -> io::Result<()> { + let file = tokio_uring::fs::OpenOptions::new() + .append(true) + .create(true) + .open(path) + .await?; + + file.close().await +} + +async fn is_regfile>(path: P) -> io::Result<()> { + let (_is_dir, is_regfile) = tokio_uring::fs::is_dir_regfile(path).await; + + if is_regfile { + Ok(()) + } else { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "not regular file", + )) + } +} + +async fn is_dir>(path: P) -> io::Result<()> { + let (is_dir, _is_regfile) = tokio_uring::fs::is_dir_regfile(path).await; + + if is_dir { + Ok(()) + } else { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "not directory", + )) + } +} + fn main() { tokio_uring::start(async { if let Err(e) = main1().await { diff --git a/src/fs/create_dir_all.rs b/src/fs/create_dir_all.rs index 114e6431..220ef670 100644 --- a/src/fs/create_dir_all.rs +++ b/src/fs/create_dir_all.rs @@ -124,10 +124,6 @@ impl DirBuilder { // recursion when only the first level of the directory needs to be built. For now, this serves // its purpose. - // TODO this is a bit expensive for the case of creating a directory that already exists - // because after the call to mkdir fails, it will make three more async calls to determine the - // path already exists and is a directory. - fn recurse_create_dir_all<'a>(&'a self, path: &'a Path) -> LocalBoxFuture> { Box::pin(async move { if path == Path::new("") { @@ -194,20 +190,14 @@ mod fs_imp { // Returns true if the path represents a directory. // -// Uses three asynchronous uring calls to determine this. +// Uses one asynchronous uring call to determine this. async fn is_dir>(path: P) -> bool { - let f = match crate::fs::File::open(path).await { - Ok(f) => f, - _ => return false, - }; - - // f is closed asynchronously, regardless of the statx result. - - let b: bool = match f.statx().await { + let res = crate::fs::StatxBuilder::new() + .mask(libc::STATX_TYPE) + .statx_path(path) + .await; + match res { Ok(statx) => (u32::from(statx.stx_mode) & libc::S_IFMT) == libc::S_IFDIR, - _ => false, - }; - - let _ = f.close().await; - b + Err(_) => false, + } } diff --git a/src/fs/mod.rs b/src/fs/mod.rs index 7c957fd5..89b392d2 100644 --- a/src/fs/mod.rs +++ b/src/fs/mod.rs @@ -17,3 +17,6 @@ mod open_options; pub use open_options::OpenOptions; mod statx; +pub use statx::is_dir_regfile; +pub use statx::statx; +pub use statx::StatxBuilder; diff --git a/src/fs/statx.rs b/src/fs/statx.rs index daaadb00..d167dd6a 100644 --- a/src/fs/statx.rs +++ b/src/fs/statx.rs @@ -1,28 +1,303 @@ use super::File; use crate::runtime::driver::op::Op; use std::io; +use std::path::Path; impl File { - /// Metadata information about a file. + /// Returns statx(2) metadata for an open file via a uring call. + /// + /// The libc::statx structure returned is described in the statx(2) man page. + /// + /// This high level version of the statx function uses `flags` set to libc::AT_EMPTY_PATH and + /// `mask` set to libc::STATX_ALL which are described in the same man page. + /// + /// More specific uring statx(2) calls can be made with the StatxBuilder. + /// + /// # Examples + /// + /// ```no_run + /// use tokio_uring::fs::File; + /// + /// tokio_uring::start(async { + /// let f = File::create("foo.txt").await.unwrap(); + /// + /// // Fetch file metadata + /// let statx = f.statx().await.unwrap(); + /// + /// // Close the file + /// f.close().await.unwrap(); + /// }) + /// ``` + pub async fn statx(&self) -> io::Result { + let flags = libc::AT_EMPTY_PATH; + let mask = libc::STATX_ALL; + Op::statx(Some(&self.fd), None, flags, mask)?.await + } + + /// Returns a builder that can return statx(2) metadata for an open file using the uring + /// device. + /// + /// `flags` and `mask` can be changed from their defaults and a `path` that is absolule or + /// relative can also be provided. + /// + /// `flags` defaults to libc::AT_EMPTY_PATH. + /// + /// `mask` defaults to libc::STATX_ALL. + /// + /// Refer to statx(2) for details on the arguments and the returned value. + /// + /// A little from the man page: + /// + /// - statx(2) uses path, dirfd, and flags to identify the target file. + /// - statx(2) uses mask to tell the kernel which fields the caller is interested in. + /// + /// # Examples + /// + /// ```no_run + /// use tokio_uring::fs::File; + /// + /// tokio_uring::start(async { + /// let f = File::create("foo.txt").await.unwrap(); + /// + /// // Fetch file metadata + /// let statx = f.statx_builder() + /// .flags(libc::AT_NO_AUTOMOUNT) + /// .statx().await.unwrap(); + /// + /// // Close the file + /// f.close().await.unwrap(); + /// }) + /// ``` + pub fn statx_builder(&self) -> StatxBuilder { + StatxBuilder { + file: Some(self), + flags: libc::AT_EMPTY_PATH, + mask: libc::STATX_ALL, + } + } +} + +/// Returns statx(2) metadata for a path via a uring call. +/// +/// The libc::statx structure returned is described in the statx(2) man page. +/// +/// This high level version of the statx function uses `flags` set to libc::AT_EMPTY_PATH and +/// `mask` set to libc::STATX_ALL which are described in the same man page. +/// +/// And this version of the function does not work on an open file descriptor can be more expedient +/// when an open file descriptor isn't necessary for other reasons anyway. +/// +/// The path can be absolute or relative. A relative path is interpreted against the current +/// working direcgtory. +/// +/// More specific uring statx(2) calls can be made with the StatxBuilder. +/// +/// # Examples +/// +/// ```no_run +/// tokio_uring::start(async { +/// +/// // Fetch file metadata +/// let statx = tokio_uring::fs::statx("foo.txt").await.unwrap(); +/// }) +/// ``` +pub async fn statx>(path: P) -> io::Result { + StatxBuilder::new().statx_path(path).await +} + +/// A builder used to make a uring statx(2) call. +/// +/// This builder supports the `flags` and `mask` options and can be finished with a call to +/// `statx()` or to `statx_path(). +/// +/// See StatxBuilder::new for more details. +#[derive(Debug)] +pub struct StatxBuilder<'a> { + file: Option<&'a File>, + flags: i32, + mask: u32, +} + +impl<'a> Default for StatxBuilder<'a> { + fn default() -> Self { + Self::new() + } +} + +impl<'a> StatxBuilder<'a> { + /// Returns a builder to fully specify the arguments to the uring statx(2) operation. + /// + /// The libc::statx structure returned in described in the statx(2) man page. + /// + /// This builder defaults to having no open file descriptor and defaults `flags` to + /// libc::AT_EMPTY_PATH and `mask` to libc::STATX_ALL. + /// + /// Refer to the man page for details about the `flags`, `mask` values and returned structure, + /// libc::statx. + /// + /// # Examples + /// + /// ```no_run + /// tokio_uring::start(async { + /// let want_mode: u16 = 0o775; + /// + /// // Fetch file metadata + /// let statx = tokio_uring::fs::StatxBuilder::new() + /// .mask(libc::STATX_MODE) + /// .statx_path("foo.txt").await.unwrap(); + /// let got_mode = statx.stx_mode & 0o7777; + /// + /// if want_mode == got_mode { + /// println!("Success: wanted mode {want_mode:#o}, got mode {got_mode:#o}"); + /// } else { + /// println!("Fail: wanted mode {want_mode:#o}, got mode {got_mode:#o}"); + /// } + /// }) + /// ``` + #[must_use] + pub fn new() -> StatxBuilder<'a> { + StatxBuilder { + file: None, + flags: libc::AT_EMPTY_PATH, + mask: libc::STATX_ALL, + } + } + + /// Sets the `flags` option, replacing the default. + /// + /// See statx(2) for a full description of `flags`. + /// + /// # Examples + /// + /// ```no_run + /// tokio_uring::start(async { + /// // Fetch file metadata + /// let statx = tokio_uring::fs::StatxBuilder::new() + /// .flags(libc::AT_NO_AUTOMOUNT) + /// .statx_path("foo.txt").await.unwrap(); + /// }) + /// ``` + #[must_use] + pub fn flags(&mut self, flags: i32) -> &mut Self { + self.flags = flags; + self + } + + /// Sets the `mask` option, replacing the default. + /// + /// # Examples + /// + /// ```no_run + /// tokio_uring::start(async { + /// // Fetch file metadata + /// let statx = tokio_uring::fs::StatxBuilder::new() + /// .mask(libc::STATX_BASIC_STATS) + /// .statx_path("foo.txt").await.unwrap(); + /// }) + /// ``` + #[must_use] + pub fn mask(&mut self, mask: u32) -> &mut Self { + self.mask = mask; + self + } + + /// Returns the metadata requested for the optional open file. If no open file was provided, + /// the metadata for the current working directory is returned. /// /// # Examples /// /// ```no_run /// use tokio_uring::fs::File; /// - /// fn main() -> Result<(), Box> { - /// tokio_uring::start(async { - /// let f = File::create("foo.txt").await?; + /// tokio_uring::start(async { + /// let f = File::create("foo.txt").await.unwrap(); /// - /// // Fetch file metadata - /// let statx = f.statx().await?; + /// // Fetch file metadata + /// let statx = f.statx_builder() + /// .mask(libc::STATX_TYPE) + /// .statx().await.unwrap(); /// - /// // Close the file - /// f.close().await?; - /// Ok(()) - /// }) - /// } + /// // Close the file + /// f.close().await.unwrap(); + /// }) + /// ``` pub async fn statx(&self) -> io::Result { - Op::statx(&self.fd)?.await + Op::statx(self.file.map(|x| &x.fd), None, self.flags, self.mask)?.await + } + + // TODO should the statx() terminator be renamed to something like nopath() + // and the statx_path() be renamed to path(AsRef)? + + /// Returns the metadata requested for the given path. The path can be absolute or relative. + /// + /// When the path is relative, it works against the open file discriptor if it has one + /// else it works against the current working directory. + /// + /// # Examples + /// + /// ```no_run + /// tokio_uring::start(async { + /// // Fetch metadata for relative path against current working directory. + /// let statx = tokio_uring::fs::StatxBuilder::new() + /// .mask(libc::STATX_BASIC_STATS) + /// .statx_path("foo.txt").await.unwrap(); + /// }) + /// ``` + /// + /// # Examples + /// + /// ```no_run + /// tokio_uring::start(async { + /// // This shows the power of combining an open file, presumably a directory, and the relative + /// // path to have the statx operation return the meta data for the child of the opened directory + /// // descriptor. + /// let dir_path = "/home/ubuntu"; + /// let rel_path = "./work"; + /// + /// let open_dir = tokio_uring::fs::File::open(dir_path).await.unwrap(); + /// + /// // Fetch metadata for relative path against open directory. + /// let statx_res = open_dir.statx_builder().statx_path(rel_path).await; + /// + /// // Close the directory descriptor. + /// open_dir.close().await.unwrap(); + /// + /// // Use the relative path's statx information. + /// let _ = statx_res.unwrap(); + /// }) + /// ``` + pub async fn statx_path>(&self, path: P) -> io::Result { + // It's almost an accident there are two terminators for the StatxBuilder, one that doesn't + // take a path, and one that does. + // + // Because the > is of unknown size, it can't be stored in the builder without + // boxing it, and the conversion to CString can't be done without potentially returning an + // error and returning an error can't be done by a method that returns a &mut self, and + // trying to return a Result<&mut self> may have led to trouble, but what really seemed + // like trouble was using the CString field in the other terminator. Have to look into this + // again. + use crate::io::cstr; + + let path = cstr(path.as_ref())?; + Op::statx(self.file.map(|x| &x.fd), Some(path), self.flags, self.mask)?.await + } +} + +// TODO consider replacing this with a Statx struct with useful helper methods. +/// Returns two bools, is_dir and is_regfile. +/// +/// They both can't be true at the same time and there are many reasons they may both be false. +#[allow(dead_code)] +pub async fn is_dir_regfile>(path: P) -> (bool, bool) { + let res = crate::fs::StatxBuilder::new() + .mask(libc::STATX_TYPE) + .statx_path(path) + .await; + match res { + Ok(statx) => ( + (u32::from(statx.stx_mode) & libc::S_IFMT) == libc::S_IFDIR, + (u32::from(statx.stx_mode) & libc::S_IFMT) == libc::S_IFREG, + ), + Err(_) => (false, false), } } diff --git a/src/io/mod.rs b/src/io/mod.rs index fe30e99f..4d7c57b4 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -43,6 +43,7 @@ mod statx; mod unlink_at; mod util; +pub(crate) use util::cstr; mod write; diff --git a/src/io/statx.rs b/src/io/statx.rs index 69d05cf7..c0b69ba0 100644 --- a/src/io/statx.rs +++ b/src/io/statx.rs @@ -1,3 +1,4 @@ +use std::ffi::CString; use std::{ffi::CStr, io}; use io_uring::{opcode, types}; @@ -10,27 +11,51 @@ use crate::runtime::{ use super::SharedFd; pub(crate) struct Statx { - fd: SharedFd, + #[allow(dead_code)] + fd: Option, + #[allow(dead_code)] + path: CString, statx: Box, } impl Op { - pub(crate) fn statx(fd: &SharedFd) -> io::Result> { + // If we are passed a reference to a shared fd, clone it so we keep it live during the + // Future. If we aren't, use the libc::AT_FDCWD value. + // If Path is None, the flags is combined with libc::AT_EMPTY_PATH automatically. + pub(crate) fn statx( + fd: Option<&SharedFd>, + path: Option, + flags: i32, + mask: u32, + ) -> io::Result> { + let (fd, raw) = match fd { + Some(shared) => (Some(shared.clone()), shared.raw_fd()), + None => (None, libc::AT_FDCWD), + }; + let mut flags = flags; + let path = match path { + Some(path) => path, + None => { + flags |= libc::AT_EMPTY_PATH; + CStr::from_bytes_with_nul(b"\0").unwrap().into() // Is there a constant CString we + // could use here. + } + }; CONTEXT.with(|x| { - let empty_path = CStr::from_bytes_with_nul(b"\0").unwrap(); x.handle().expect("not in a runtime context").submit_op( Statx { - fd: fd.clone(), + fd, + path, statx: Box::new(unsafe { std::mem::zeroed() }), }, |statx| { opcode::Statx::new( - types::Fd(statx.fd.raw_fd()), - empty_path.as_ptr(), + types::Fd(raw), + statx.path.as_ptr(), &mut *statx.statx as *mut libc::statx as *mut types::statx, ) - .flags(libc::AT_EMPTY_PATH) - .mask(libc::STATX_ALL) + .flags(flags) + .mask(mask) .build() }, ) diff --git a/src/io/util.rs b/src/io/util.rs index d15280a3..f3f69a8d 100644 --- a/src/io/util.rs +++ b/src/io/util.rs @@ -2,7 +2,7 @@ use std::ffi::CString; use std::io; use std::path::Path; -pub(super) fn cstr(p: &Path) -> io::Result { +pub(crate) fn cstr(p: &Path) -> io::Result { use std::os::unix::ffi::OsStrExt; Ok(CString::new(p.as_os_str().as_bytes())?) } From bbbb36551df2a00adb8df173a2388c2b9a34eb6f Mon Sep 17 00:00:00 2001 From: Frank Rehwinkel Date: Sun, 5 Feb 2023 18:07:29 -0500 Subject: [PATCH 3/4] adds dirfd option to StatxBuilder Stores the cloned SharedFd in the builder, rather than a reference to the File. The clone was going to be made anyway. --- src/fs/statx.rs | 89 ++++++++++++++++++++++++++++++++++--------------- src/io/statx.rs | 10 +++--- 2 files changed, 66 insertions(+), 33 deletions(-) diff --git a/src/fs/statx.rs b/src/fs/statx.rs index d167dd6a..7fa3c4a7 100644 --- a/src/fs/statx.rs +++ b/src/fs/statx.rs @@ -1,4 +1,5 @@ use super::File; +use crate::io::SharedFd; use crate::runtime::driver::op::Op; use std::io; use std::path::Path; @@ -31,7 +32,7 @@ impl File { pub async fn statx(&self) -> io::Result { let flags = libc::AT_EMPTY_PATH; let mask = libc::STATX_ALL; - Op::statx(Some(&self.fd), None, flags, mask)?.await + Op::statx(Some(self.fd.clone()), None, flags, mask)?.await } /// Returns a builder that can return statx(2) metadata for an open file using the uring @@ -70,7 +71,7 @@ impl File { /// ``` pub fn statx_builder(&self) -> StatxBuilder { StatxBuilder { - file: Some(self), + file: Some(self.fd.clone()), flags: libc::AT_EMPTY_PATH, mask: libc::STATX_ALL, } @@ -111,20 +112,19 @@ pub async fn statx>(path: P) -> io::Result { /// `statx()` or to `statx_path(). /// /// See StatxBuilder::new for more details. -#[derive(Debug)] -pub struct StatxBuilder<'a> { - file: Option<&'a File>, +pub struct StatxBuilder { + file: Option, flags: i32, mask: u32, } -impl<'a> Default for StatxBuilder<'a> { +impl Default for StatxBuilder { fn default() -> Self { Self::new() } } -impl<'a> StatxBuilder<'a> { +impl StatxBuilder { /// Returns a builder to fully specify the arguments to the uring statx(2) operation. /// /// The libc::statx structure returned in described in the statx(2) man page. @@ -155,7 +155,7 @@ impl<'a> StatxBuilder<'a> { /// }) /// ``` #[must_use] - pub fn new() -> StatxBuilder<'a> { + pub fn new() -> StatxBuilder { StatxBuilder { file: None, flags: libc::AT_EMPTY_PATH, @@ -163,6 +163,36 @@ impl<'a> StatxBuilder<'a> { } } + /// Sets the `dirfd` option, setting or replacing the file descriptor which may be for a + /// directory but doesn't have to be. When used with a path, it should be a directory but when + /// used without a path, can be any file type. So `dirfd` is a bit of a misnomer but it is what + /// the statx(2) man page calls it. + /// + /// # Examples + /// + /// ```no_run + /// use tokio_uring::fs::{self, File}; + /// + /// tokio_uring::start(async { + /// let dir = fs::OpenOptions::new() + /// .open("/home/linux") + /// .await.unwrap(); + /// + /// // Fetch file metadata + /// let statx = fs::StatxBuilder::new() + /// .dirfd(&dir) + /// .mask(libc::STATX_TYPE) + /// .statx_path(".vimrc").await.unwrap(); + /// + /// dir.close().await.unwrap(); + /// }) + /// ``` + #[must_use] + pub fn dirfd(&mut self, file: &File) -> &mut Self { + self.file = Some(file.fd.clone()); + self + } + /// Sets the `flags` option, replacing the default. /// /// See statx(2) for a full description of `flags`. @@ -207,22 +237,25 @@ impl<'a> StatxBuilder<'a> { /// # Examples /// /// ```no_run - /// use tokio_uring::fs::File; + /// use tokio_uring::fs::{self, File}; /// /// tokio_uring::start(async { - /// let f = File::create("foo.txt").await.unwrap(); + /// let dir = fs::OpenOptions::new() + /// .open("/home/linux") + /// .await.unwrap(); /// /// // Fetch file metadata - /// let statx = f.statx_builder() + /// let statx = fs::StatxBuilder::new() + /// .dirfd(&dir) /// .mask(libc::STATX_TYPE) /// .statx().await.unwrap(); /// - /// // Close the file - /// f.close().await.unwrap(); + /// dir.close().await.unwrap(); /// }) /// ``` - pub async fn statx(&self) -> io::Result { - Op::statx(self.file.map(|x| &x.fd), None, self.flags, self.mask)?.await + pub async fn statx(&mut self) -> io::Result { + let fd = self.file.take(); + Op::statx(fd, None, self.flags, self.mask)?.await } // TODO should the statx() terminator be renamed to something like nopath() @@ -247,26 +280,27 @@ impl<'a> StatxBuilder<'a> { /// # Examples /// /// ```no_run + /// use tokio_uring::fs; + /// /// tokio_uring::start(async { /// // This shows the power of combining an open file, presumably a directory, and the relative /// // path to have the statx operation return the meta data for the child of the opened directory /// // descriptor. - /// let dir_path = "/home/ubuntu"; - /// let rel_path = "./work"; - /// - /// let open_dir = tokio_uring::fs::File::open(dir_path).await.unwrap(); + /// let dir = fs::OpenOptions::new() + /// .open("/home/linux") + /// .await.unwrap(); /// - /// // Fetch metadata for relative path against open directory. - /// let statx_res = open_dir.statx_builder().statx_path(rel_path).await; - /// - /// // Close the directory descriptor. - /// open_dir.close().await.unwrap(); + /// // Fetch file metadata + /// let statx_res = fs::StatxBuilder::new() + /// .dirfd(&dir) + /// .mask(libc::STATX_TYPE) + /// .statx().await; /// - /// // Use the relative path's statx information. + /// dir.close().await.unwrap(); /// let _ = statx_res.unwrap(); /// }) /// ``` - pub async fn statx_path>(&self, path: P) -> io::Result { + pub async fn statx_path>(&mut self, path: P) -> io::Result { // It's almost an accident there are two terminators for the StatxBuilder, one that doesn't // take a path, and one that does. // @@ -278,8 +312,9 @@ impl<'a> StatxBuilder<'a> { // again. use crate::io::cstr; + let fd = self.file.take(); let path = cstr(path.as_ref())?; - Op::statx(self.file.map(|x| &x.fd), Some(path), self.flags, self.mask)?.await + Op::statx(fd, Some(path), self.flags, self.mask)?.await } } diff --git a/src/io/statx.rs b/src/io/statx.rs index c0b69ba0..56ccb027 100644 --- a/src/io/statx.rs +++ b/src/io/statx.rs @@ -23,21 +23,19 @@ impl Op { // Future. If we aren't, use the libc::AT_FDCWD value. // If Path is None, the flags is combined with libc::AT_EMPTY_PATH automatically. pub(crate) fn statx( - fd: Option<&SharedFd>, + fd: Option, path: Option, flags: i32, mask: u32, ) -> io::Result> { - let (fd, raw) = match fd { - Some(shared) => (Some(shared.clone()), shared.raw_fd()), - None => (None, libc::AT_FDCWD), - }; + let raw = fd.as_ref().map_or(libc::AT_FDCWD, |fd| fd.raw_fd()); let mut flags = flags; let path = match path { Some(path) => path, None => { + // If there is no path, add appropriate bit to flags. flags |= libc::AT_EMPTY_PATH; - CStr::from_bytes_with_nul(b"\0").unwrap().into() // Is there a constant CString we + CStr::from_bytes_with_nul(b"\0").unwrap().into() // TODO Is there a constant CString we // could use here. } }; From 6cabed86d4de18366aa036b6f8724a34fb5647d1 Mon Sep 17 00:00:00 2001 From: Frank Rehwinkel Date: Sun, 5 Feb 2023 18:52:15 -0500 Subject: [PATCH 4/4] adds pathname option to StatxBuilder And removes the statx_path(path) terminator to the builder. The builder api is both simplificied and made more complex. Simplied because there is just one terminator now, statx(). More complex because the pathname(path) option can fail to create a CString so needs to return an io::Result<&mut Self> so the builder's chain of options has this little hiccup in it when setting the path. --- examples/test_create_dir_all.rs | 12 +-- src/fs/create_dir_all.rs | 10 ++- src/fs/statx.rs | 126 ++++++++++++++------------------ src/io/statx.rs | 3 + 4 files changed, 69 insertions(+), 82 deletions(-) diff --git a/examples/test_create_dir_all.rs b/examples/test_create_dir_all.rs index a598c89c..3404de8e 100644 --- a/examples/test_create_dir_all.rs +++ b/examples/test_create_dir_all.rs @@ -177,7 +177,8 @@ async fn statx>(path: P) -> io::Result<()> { async fn statx_builder>(path: P) -> io::Result<()> { let _statx = tokio_uring::fs::StatxBuilder::new() - .statx_path(path) + .pathname(path)? + .statx() .await?; Ok(()) } @@ -186,13 +187,13 @@ async fn statx_builder2>(dir_path: P, rel_path: P) -> io::Result< // This shows the power of combining an open file, presumably a directory, and the relative // path to have the statx operation return the meta data for the child of the opened directory // descriptor. - let f = tokio_uring::fs::File::open(dir_path).await.unwrap(); + let f = tokio_uring::fs::File::open(dir_path).await?; // Fetch file metadata - let res = f.statx_builder().statx_path(rel_path).await; + let res = f.statx_builder().pathname(rel_path)?.statx().await; // Close the file - f.close().await.unwrap(); + f.close().await?; res.map(|_| ()) } @@ -200,7 +201,8 @@ async fn statx_builder2>(dir_path: P, rel_path: P) -> io::Result< async fn matches_mode>(path: P, want_mode: u16) -> io::Result<()> { let statx = tokio_uring::fs::StatxBuilder::new() .mask(libc::STATX_MODE) - .statx_path(path) + .pathname(path)? + .statx() .await?; let got_mode = statx.stx_mode & 0o7777; if want_mode == got_mode { diff --git a/src/fs/create_dir_all.rs b/src/fs/create_dir_all.rs index 220ef670..ef0c29f7 100644 --- a/src/fs/create_dir_all.rs +++ b/src/fs/create_dir_all.rs @@ -192,10 +192,12 @@ mod fs_imp { // // Uses one asynchronous uring call to determine this. async fn is_dir>(path: P) -> bool { - let res = crate::fs::StatxBuilder::new() - .mask(libc::STATX_TYPE) - .statx_path(path) - .await; + let mut builder = crate::fs::StatxBuilder::new(); + if builder.mask(libc::STATX_TYPE).pathname(path).is_err() { + return false; + } + + let res = builder.statx().await; match res { Ok(statx) => (u32::from(statx.stx_mode) & libc::S_IFMT) == libc::S_IFDIR, Err(_) => false, diff --git a/src/fs/statx.rs b/src/fs/statx.rs index 7fa3c4a7..5a3ccb1b 100644 --- a/src/fs/statx.rs +++ b/src/fs/statx.rs @@ -1,8 +1,7 @@ use super::File; -use crate::io::SharedFd; +use crate::io::{cstr, SharedFd}; use crate::runtime::driver::op::Op; -use std::io; -use std::path::Path; +use std::{ffi::CString, io, path::Path}; impl File { /// Returns statx(2) metadata for an open file via a uring call. @@ -72,6 +71,7 @@ impl File { pub fn statx_builder(&self) -> StatxBuilder { StatxBuilder { file: Some(self.fd.clone()), + path: None, flags: libc::AT_EMPTY_PATH, mask: libc::STATX_ALL, } @@ -103,17 +103,18 @@ impl File { /// }) /// ``` pub async fn statx>(path: P) -> io::Result { - StatxBuilder::new().statx_path(path).await + StatxBuilder::new().pathname(path).unwrap().statx().await } /// A builder used to make a uring statx(2) call. /// /// This builder supports the `flags` and `mask` options and can be finished with a call to -/// `statx()` or to `statx_path(). +/// `statx()`. /// /// See StatxBuilder::new for more details. pub struct StatxBuilder { file: Option, + path: Option, flags: i32, mask: u32, } @@ -144,7 +145,8 @@ impl StatxBuilder { /// // Fetch file metadata /// let statx = tokio_uring::fs::StatxBuilder::new() /// .mask(libc::STATX_MODE) - /// .statx_path("foo.txt").await.unwrap(); + /// .pathname("foo.txt").unwrap() + /// .statx().await.unwrap(); /// let got_mode = statx.stx_mode & 0o7777; /// /// if want_mode == got_mode { @@ -158,6 +160,7 @@ impl StatxBuilder { pub fn new() -> StatxBuilder { StatxBuilder { file: None, + path: None, flags: libc::AT_EMPTY_PATH, mask: libc::STATX_ALL, } @@ -182,7 +185,8 @@ impl StatxBuilder { /// let statx = fs::StatxBuilder::new() /// .dirfd(&dir) /// .mask(libc::STATX_TYPE) - /// .statx_path(".vimrc").await.unwrap(); + /// .pathname(".cargo").unwrap() + /// .statx().await.unwrap(); /// /// dir.close().await.unwrap(); /// }) @@ -193,6 +197,34 @@ impl StatxBuilder { self } + /// Sets the `path` option, setting or replacing the path option to the command. + /// The path may be absolute or relative. + /// + /// # Examples + /// + /// ```no_run + /// use tokio_uring::fs::{self, File}; + /// + /// tokio_uring::start(async { + /// let dir = fs::OpenOptions::new() + /// .open("/home/linux") + /// .await.unwrap(); + /// + /// // Fetch file metadata + /// let statx = fs::StatxBuilder::new() + /// .dirfd(&dir) + /// .pathname(".cargo").unwrap() + /// .mask(libc::STATX_TYPE) + /// .statx().await.unwrap(); + /// + /// dir.close().await.unwrap(); + /// }) + /// ``` + pub fn pathname>(&mut self, path: P) -> io::Result<&mut Self> { + self.path = Some(cstr(path.as_ref())?); + Ok(self) + } + /// Sets the `flags` option, replacing the default. /// /// See statx(2) for a full description of `flags`. @@ -204,7 +236,8 @@ impl StatxBuilder { /// // Fetch file metadata /// let statx = tokio_uring::fs::StatxBuilder::new() /// .flags(libc::AT_NO_AUTOMOUNT) - /// .statx_path("foo.txt").await.unwrap(); + /// .pathname("foo.txt").unwrap() + /// .statx().await.unwrap(); /// }) /// ``` #[must_use] @@ -222,7 +255,8 @@ impl StatxBuilder { /// // Fetch file metadata /// let statx = tokio_uring::fs::StatxBuilder::new() /// .mask(libc::STATX_BASIC_STATS) - /// .statx_path("foo.txt").await.unwrap(); + /// .pathname("foo.txt").unwrap() + /// .statx().await.unwrap(); /// }) /// ``` #[must_use] @@ -247,6 +281,7 @@ impl StatxBuilder { /// // Fetch file metadata /// let statx = fs::StatxBuilder::new() /// .dirfd(&dir) + /// .pathname(".cargo").unwrap() /// .mask(libc::STATX_TYPE) /// .statx().await.unwrap(); /// @@ -254,67 +289,10 @@ impl StatxBuilder { /// }) /// ``` pub async fn statx(&mut self) -> io::Result { + // TODO should the statx() terminator be renamed to something like submit()? let fd = self.file.take(); - Op::statx(fd, None, self.flags, self.mask)?.await - } - - // TODO should the statx() terminator be renamed to something like nopath() - // and the statx_path() be renamed to path(AsRef)? - - /// Returns the metadata requested for the given path. The path can be absolute or relative. - /// - /// When the path is relative, it works against the open file discriptor if it has one - /// else it works against the current working directory. - /// - /// # Examples - /// - /// ```no_run - /// tokio_uring::start(async { - /// // Fetch metadata for relative path against current working directory. - /// let statx = tokio_uring::fs::StatxBuilder::new() - /// .mask(libc::STATX_BASIC_STATS) - /// .statx_path("foo.txt").await.unwrap(); - /// }) - /// ``` - /// - /// # Examples - /// - /// ```no_run - /// use tokio_uring::fs; - /// - /// tokio_uring::start(async { - /// // This shows the power of combining an open file, presumably a directory, and the relative - /// // path to have the statx operation return the meta data for the child of the opened directory - /// // descriptor. - /// let dir = fs::OpenOptions::new() - /// .open("/home/linux") - /// .await.unwrap(); - /// - /// // Fetch file metadata - /// let statx_res = fs::StatxBuilder::new() - /// .dirfd(&dir) - /// .mask(libc::STATX_TYPE) - /// .statx().await; - /// - /// dir.close().await.unwrap(); - /// let _ = statx_res.unwrap(); - /// }) - /// ``` - pub async fn statx_path>(&mut self, path: P) -> io::Result { - // It's almost an accident there are two terminators for the StatxBuilder, one that doesn't - // take a path, and one that does. - // - // Because the > is of unknown size, it can't be stored in the builder without - // boxing it, and the conversion to CString can't be done without potentially returning an - // error and returning an error can't be done by a method that returns a &mut self, and - // trying to return a Result<&mut self> may have led to trouble, but what really seemed - // like trouble was using the CString field in the other terminator. Have to look into this - // again. - use crate::io::cstr; - - let fd = self.file.take(); - let path = cstr(path.as_ref())?; - Op::statx(fd, Some(path), self.flags, self.mask)?.await + let path = self.path.take(); + Op::statx(fd, path, self.flags, self.mask)?.await } } @@ -324,10 +302,12 @@ impl StatxBuilder { /// They both can't be true at the same time and there are many reasons they may both be false. #[allow(dead_code)] pub async fn is_dir_regfile>(path: P) -> (bool, bool) { - let res = crate::fs::StatxBuilder::new() - .mask(libc::STATX_TYPE) - .statx_path(path) - .await; + let mut builder = crate::fs::StatxBuilder::new(); + if builder.mask(libc::STATX_TYPE).pathname(path).is_err() { + return (false, false); + } + + let res = builder.statx().await; match res { Ok(statx) => ( (u32::from(statx.stx_mode) & libc::S_IFMT) == libc::S_IFDIR, diff --git a/src/io/statx.rs b/src/io/statx.rs index 56ccb027..e7796b0e 100644 --- a/src/io/statx.rs +++ b/src/io/statx.rs @@ -15,6 +15,9 @@ pub(crate) struct Statx { fd: Option, #[allow(dead_code)] path: CString, + + // TODO consider returning this type when the operation is complete so the caller has the boxed value. + // The builder could even recycle an old boxed value and pass it in here. statx: Box, }