Skip to content
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

statx builder #226

Merged
merged 7 commits into from
Feb 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 125 additions & 17 deletions examples/test_create_dir_all.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::io;
use std::path::Path;
use tokio_uring::fs;

fn tests() -> std::slice::Iter<'static, Expected<'static>> {
Expand All @@ -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")),
Expand All @@ -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()
}
Expand All @@ -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>),
Expand All @@ -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,
Expand All @@ -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(_) => {
Expand Down Expand Up @@ -143,6 +170,87 @@ async fn main1() -> io::Result<()> {
}
}

async fn statx<P: AsRef<Path>>(path: P) -> io::Result<()> {
let _statx = tokio_uring::fs::statx(path).await?;
Ok(())
}

async fn statx_builder<P: AsRef<Path>>(path: P) -> io::Result<()> {
let _statx = tokio_uring::fs::StatxBuilder::new()
.pathname(path)?
.statx()
.await?;
Ok(())
}

async fn statx_builder2<P: AsRef<Path>>(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?;

// Fetch file metadata
let res = f.statx_builder().pathname(rel_path)?.statx().await;

// Close the file
f.close().await?;

res.map(|_| ())
}

async fn matches_mode<P: AsRef<Path>>(path: P, want_mode: u16) -> io::Result<()> {
let statx = tokio_uring::fs::StatxBuilder::new()
.mask(libc::STATX_MODE)
.pathname(path)?
.statx()
.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<P: AsRef<Path>>(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<P: AsRef<Path>>(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<P: AsRef<Path>>(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 {
Expand Down
26 changes: 9 additions & 17 deletions src/fs/create_dir_all.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<io::Result<()>> {
Box::pin(async move {
if path == Path::new("") {
Expand Down Expand Up @@ -194,20 +190,16 @@ 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<P: AsRef<Path>>(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 mut builder = crate::fs::StatxBuilder::new();
if builder.mask(libc::STATX_TYPE).pathname(path).is_err() {
return false;
}

let b: bool = match f.statx().await {
let res = builder.statx().await;
match res {
Ok(statx) => (u32::from(statx.stx_mode) & libc::S_IFMT) == libc::S_IFDIR,
_ => false,
};

let _ = f.close().await;
b
Err(_) => false,
}
}
25 changes: 1 addition & 24 deletions src/fs/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ use std::path::Path;
/// ```
pub struct File {
/// Open file descriptor
fd: SharedFd,
pub(crate) fd: SharedFd,
}

impl File {
Expand Down Expand Up @@ -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<dyn std::error::Error>> {
/// 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<libc::statx> {
Op::statx(&self.fd)?.await
}

/// Closes the file using the uring asynchronous close operation and returns the possible error
/// as described in the close(2) man page.
///
Expand Down
5 changes: 5 additions & 0 deletions src/fs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,8 @@ pub use file::File;

mod open_options;
pub use open_options::OpenOptions;

mod statx;
pub use statx::is_dir_regfile;
pub use statx::statx;
pub use statx::StatxBuilder;
Loading