From dbdb3d87721941523a1225ad32c7b0577d30b76a Mon Sep 17 00:00:00 2001 From: Lzzzt <101313294+Lzzzzzt@users.noreply.github.com> Date: Tue, 23 Jul 2024 11:41:26 +0800 Subject: [PATCH] feat(op, fs): :sparkles: Intergated `statx` into monoio (#268) * feat(op, fs): :sparkles: Intergated `statx` into monoio * feat(op, fs): :sparkles: Intergated `statx` into monoio * fix(op, fs): :bug: fix some platform-specific bug * fix(op, fs): :bug: fix some platform-specific bug * docs(fs): update permissions doc * fix(op, fs): :bug: fix some platform-specific bug * fix(fs): :bug: remove `unwrap` call that will cause panic * feat: :sparkles: make `Statx`(actually is stat) support macos * feat: :sparkles: make `Statx`(actually is stat) support macos * fix(tests): :bug: disable tests on windows * ci: :pushpin: upgrade the cross version with latest git branch upgrade the cross version with latest git branch, to fix some linker error when CI * refactor: refact the impl structure for different os * cross * refactor: refact the impl structure for different os --- monoio/src/driver/op.rs | 3 + monoio/src/driver/op/statx.rs | 212 ++++++++++++ monoio/src/fs/file.rs | 28 ++ monoio/src/fs/file_type.rs | 63 ++++ monoio/src/fs/metadata/mod.rs | 535 ++++++++++++++++++++++++++++++ monoio/src/fs/metadata/unix.rs | 82 +++++ monoio/src/fs/metadata/windows.rs | 0 monoio/src/fs/mod.rs | 15 + monoio/src/fs/permissions.rs | 86 +++++ monoio/tests/fs_metadata.rs | 82 +++++ 10 files changed, 1106 insertions(+) create mode 100644 monoio/src/driver/op/statx.rs create mode 100644 monoio/src/fs/file_type.rs create mode 100644 monoio/src/fs/metadata/mod.rs create mode 100644 monoio/src/fs/metadata/unix.rs create mode 100644 monoio/src/fs/metadata/windows.rs create mode 100644 monoio/src/fs/permissions.rs create mode 100644 monoio/tests/fs_metadata.rs diff --git a/monoio/src/driver/op.rs b/monoio/src/driver/op.rs index 3a1e60ef..6576933f 100644 --- a/monoio/src/driver/op.rs +++ b/monoio/src/driver/op.rs @@ -19,6 +19,9 @@ mod recv; mod send; mod write; +#[cfg(unix)] +mod statx; + #[cfg(all(target_os = "linux", feature = "splice"))] mod splice; diff --git a/monoio/src/driver/op/statx.rs b/monoio/src/driver/op/statx.rs new file mode 100644 index 00000000..97a365fc --- /dev/null +++ b/monoio/src/driver/op/statx.rs @@ -0,0 +1,212 @@ +use std::{ffi::CString, mem::MaybeUninit, path::Path}; + +#[cfg(all(target_os = "linux", feature = "iouring"))] +use io_uring::{opcode, types}; +#[cfg(target_os = "linux")] +use libc::statx; + +use super::{Op, OpAble}; +#[cfg(any(feature = "legacy", feature = "poll-io"))] +use crate::driver::ready::Direction; +use crate::driver::{shared_fd::SharedFd, util::cstr}; + +#[derive(Debug)] +pub(crate) struct Statx { + inner: T, + #[cfg(target_os = "linux")] + flags: i32, + #[cfg(target_os = "linux")] + statx_buf: Box>, + #[cfg(target_os = "macos")] + stat_buf: Box>, + #[cfg(target_os = "macos")] + follow_symlinks: bool, +} + +type FdStatx = Statx; + +impl Op { + /// submit a statx operation + #[cfg(target_os = "linux")] + pub(crate) fn statx_using_fd(fd: &SharedFd, flags: i32) -> std::io::Result { + Op::submit_with(Statx { + inner: fd.clone(), + flags, + statx_buf: Box::new(MaybeUninit::uninit()), + }) + } + + #[cfg(target_os = "linux")] + pub(crate) async fn statx_result(self) -> std::io::Result { + let complete = self.await; + complete.meta.result?; + + Ok(unsafe { MaybeUninit::assume_init(*complete.data.statx_buf) }) + } + + #[cfg(target_os = "macos")] + pub(crate) fn statx_using_fd(fd: &SharedFd, follow_symlinks: bool) -> std::io::Result { + Op::submit_with(Statx { + inner: fd.clone(), + follow_symlinks, + stat_buf: Box::new(MaybeUninit::uninit()), + }) + } + + #[cfg(target_os = "macos")] + pub(crate) async fn statx_result(self) -> std::io::Result { + let complete = self.await; + complete.meta.result?; + + Ok(unsafe { MaybeUninit::assume_init(*complete.data.stat_buf) }) + } +} + +impl OpAble for FdStatx { + #[cfg(all(target_os = "linux", feature = "iouring"))] + fn uring_op(&mut self) -> io_uring::squeue::Entry { + use std::os::fd::AsRawFd; + + let statxbuf = self.statx_buf.as_mut_ptr() as *mut _; + + opcode::Statx::new(types::Fd(self.inner.as_raw_fd()), c"".as_ptr(), statxbuf) + .flags(libc::AT_EMPTY_PATH | libc::AT_STATX_SYNC_AS_STAT) + .mask(libc::STATX_ALL) + .build() + } + + #[cfg(any(feature = "legacy", feature = "poll-io"))] + fn legacy_interest(&self) -> Option<(crate::driver::ready::Direction, usize)> { + self.inner + .registered_index() + .map(|idx| (Direction::Read, idx)) + } + + #[cfg(all(any(feature = "legacy", feature = "poll-io"), target_os = "linux"))] + fn legacy_call(&mut self) -> std::io::Result { + use std::os::fd::AsRawFd; + + use crate::syscall_u32; + + syscall_u32!(statx( + self.inner.as_raw_fd(), + c"".as_ptr(), + libc::AT_EMPTY_PATH, + libc::STATX_ALL, + self.statx_buf.as_mut_ptr() as *mut _ + )) + } + + #[cfg(all(any(feature = "legacy", feature = "poll-io"), windows))] + fn legacy_call(&mut self) -> std::io::Result { + unimplemented!() + } + + #[cfg(all(any(feature = "legacy", feature = "poll-io"), target_os = "macos"))] + fn legacy_call(&mut self) -> std::io::Result { + use std::os::fd::AsRawFd; + + use crate::syscall_u32; + + syscall_u32!(fstat( + self.inner.as_raw_fd(), + self.stat_buf.as_mut_ptr() as *mut _ + )) + } +} + +type PathStatx = Statx; + +impl Op { + /// submit a statx operation + #[cfg(target_os = "linux")] + pub(crate) fn statx_using_path>(path: P, flags: i32) -> std::io::Result { + let path = cstr(path.as_ref())?; + Op::submit_with(Statx { + inner: path, + flags, + statx_buf: Box::new(MaybeUninit::uninit()), + }) + } + + #[cfg(target_os = "linux")] + pub(crate) async fn statx_result(self) -> std::io::Result { + let complete = self.await; + complete.meta.result?; + + Ok(unsafe { MaybeUninit::assume_init(*complete.data.statx_buf) }) + } + + #[cfg(target_os = "macos")] + pub(crate) fn statx_using_path>( + path: P, + follow_symlinks: bool, + ) -> std::io::Result { + let path = cstr(path.as_ref())?; + Op::submit_with(Statx { + inner: path, + follow_symlinks, + stat_buf: Box::new(MaybeUninit::uninit()), + }) + } + + #[cfg(target_os = "macos")] + pub(crate) async fn statx_result(self) -> std::io::Result { + let complete = self.await; + complete.meta.result?; + + Ok(unsafe { MaybeUninit::assume_init(*complete.data.stat_buf) }) + } +} + +impl OpAble for PathStatx { + #[cfg(all(target_os = "linux", feature = "iouring"))] + fn uring_op(&mut self) -> io_uring::squeue::Entry { + let statxbuf = self.statx_buf.as_mut_ptr() as *mut _; + + opcode::Statx::new(types::Fd(libc::AT_FDCWD), self.inner.as_ptr(), statxbuf) + .flags(self.flags) + .mask(libc::STATX_ALL) + .build() + } + + #[cfg(any(feature = "legacy", feature = "poll-io"))] + fn legacy_interest(&self) -> Option<(crate::driver::ready::Direction, usize)> { + None + } + + #[cfg(all(any(feature = "legacy", feature = "poll-io"), target_os = "linux"))] + fn legacy_call(&mut self) -> std::io::Result { + use crate::syscall_u32; + + syscall_u32!(statx( + libc::AT_FDCWD, + self.inner.as_ptr(), + self.flags, + libc::STATX_ALL, + self.statx_buf.as_mut_ptr() as *mut _ + )) + } + + #[cfg(all(any(feature = "legacy", feature = "poll-io"), windows))] + fn legacy_call(&mut self) -> std::io::Result { + unimplemented!() + } + + #[cfg(all(any(feature = "legacy", feature = "poll-io"), target_os = "macos"))] + fn legacy_call(&mut self) -> std::io::Result { + use crate::syscall_u32; + + if self.follow_symlinks { + syscall_u32!(stat( + self.inner.as_ptr(), + self.stat_buf.as_mut_ptr() as *mut _ + )) + } else { + syscall_u32!(lstat( + self.inner.as_ptr(), + self.stat_buf.as_mut_ptr() as *mut _ + )) + } + } +} diff --git a/monoio/src/fs/file.rs b/monoio/src/fs/file.rs index a354eb93..05366750 100644 --- a/monoio/src/fs/file.rs +++ b/monoio/src/fs/file.rs @@ -10,6 +10,8 @@ use std::{ }; use std::{io, path::Path}; +#[cfg(unix)] +use super::{metadata::FileAttr, Metadata}; use crate::{ buf::{IoBuf, IoBufMut}, driver::{op::Op, shared_fd::SharedFd}, @@ -498,6 +500,32 @@ impl File { self.fd.close().await; Ok(()) } + + /// Queries metadata about the underlying file. + /// + /// # Examples + /// + /// ```no_run + /// use monoio::fs::File; + /// + /// #[monoio::main] + /// async fn main() -> std::io::Result<()> { + /// let mut f = File::open("foo.txt").await?; + /// let metadata = f.metadata().await?; + /// Ok(()) + /// } + /// ``` + #[cfg(unix)] + pub async fn metadata(&self) -> io::Result { + #[cfg(target_os = "linux")] + let flags = libc::AT_STATX_SYNC_AS_STAT | libc::AT_EMPTY_PATH; + #[cfg(target_os = "linux")] + let op = Op::statx_using_fd(&self.fd, flags)?; + #[cfg(target_os = "macos")] + let op = Op::statx_using_fd(&self.fd, true)?; + + op.statx_result().await.map(FileAttr::from).map(Metadata) + } } #[cfg(unix)] diff --git a/monoio/src/fs/file_type.rs b/monoio/src/fs/file_type.rs new file mode 100644 index 00000000..a5a8884c --- /dev/null +++ b/monoio/src/fs/file_type.rs @@ -0,0 +1,63 @@ +use std::{fmt::Debug, os::unix::fs::FileTypeExt}; + +use libc::mode_t; + +/// A structure representing a type of file with accessors for each file type. +#[derive(PartialEq, Eq, Clone, Copy, Hash)] +pub struct FileType { + pub(crate) mode: mode_t, +} + +#[cfg(unix)] +impl FileType { + /// Returns `true` if this file type is a directory. + pub fn is_dir(&self) -> bool { + self.is(libc::S_IFDIR) + } + + /// Returns `true` if this file type is a regular file. + pub fn is_file(&self) -> bool { + self.is(libc::S_IFREG) + } + + /// Returns `true` if this file type is a symbolic link. + pub fn is_symlink(&self) -> bool { + self.is(libc::S_IFLNK) + } + + pub(crate) fn is(&self, mode: mode_t) -> bool { + self.masked() == mode + } + + fn masked(&self) -> mode_t { + self.mode & libc::S_IFMT + } +} + +impl FileTypeExt for FileType { + fn is_block_device(&self) -> bool { + self.is(libc::S_IFBLK) + } + + fn is_char_device(&self) -> bool { + self.is(libc::S_IFCHR) + } + + fn is_fifo(&self) -> bool { + self.is(libc::S_IFIFO) + } + + fn is_socket(&self) -> bool { + self.is(libc::S_IFSOCK) + } +} + +impl Debug for FileType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("FileType") + .field("is_file", &self.is_file()) + .field("is_dir", &self.is_dir()) + .field("is_symlink", &self.is_symlink()) + .finish_non_exhaustive() + } +} diff --git a/monoio/src/fs/metadata/mod.rs b/monoio/src/fs/metadata/mod.rs new file mode 100644 index 00000000..ae0fc755 --- /dev/null +++ b/monoio/src/fs/metadata/mod.rs @@ -0,0 +1,535 @@ +mod unix; +mod windows; + +use crate::driver::op::Op; + +use super::file_type::FileType; +use super::permissions::Permissions; +use std::os::unix::fs::MetadataExt; +use std::path::Path; +use std::time::SystemTime; + +/// Given a path, query the file system to get information about a file, +/// directory, etc. +/// +/// This function will traverse symbolic links to query information about the +/// destination file. +/// +/// # Platform-specific behavior +/// +/// current implementation is only for Linux. +/// +/// # Errors +/// +/// This function will return an error in the following situations, but is not +/// limited to just these cases: +/// +/// * The user lacks permissions to perform `metadata` call on `path`. +/// * execute(search) permission is required on all of the directories in path that lead to the +/// file. +/// * `path` does not exist. +/// +/// # Examples +/// +/// ```rust,no_run +/// use monoio::fs; +/// +/// #[monoio::main] +/// async fn main() -> std::io::Result<()> { +/// let attr = fs::metadata("/some/file/path.txt").await?; +/// // inspect attr ... +/// Ok(()) +/// } +/// ``` +pub async fn metadata>(path: P) -> std::io::Result { + #[cfg(target_os = "linux")] + let flags = libc::AT_STATX_SYNC_AS_STAT; + + #[cfg(target_os = "linux")] + let op = Op::statx_using_path(path, flags)?; + + #[cfg(target_os = "macos")] + let op = Op::statx_using_path(path, true)?; + + op.statx_result().await.map(FileAttr::from).map(Metadata) +} + +/// Query the metadata about a file without following symlinks. +/// +/// # Platform-specific behavior +/// +/// This function currently corresponds to the `lstat` function on linux +/// +/// # Errors +/// +/// This function will return an error in the following situations, but is not +/// limited to just these cases: +/// +/// * The user lacks permissions to perform `metadata` call on `path`. +/// * execute(search) permission is required on all of the directories in path that lead to the +/// file. +/// * `path` does not exist. +/// +/// # Examples +/// ```rust,no_run +/// use monoio::fs; +/// +/// #[monoio::main] +/// async fn main() -> std::io::Result<()> { +/// let attr = fs::symlink_metadata("/some/file/path.txt").await?; +/// // inspect attr ... +/// Ok(()) +/// } +/// ``` +pub async fn symlink_metadata>(path: P) -> std::io::Result { + #[cfg(target_os = "linux")] + let flags = libc::AT_STATX_SYNC_AS_STAT | libc::AT_SYMLINK_NOFOLLOW; + + #[cfg(target_os = "linux")] + let op = Op::statx_using_path(path, flags)?; + + #[cfg(target_os = "macos")] + let op = Op::statx_using_path(path, false)?; + + op.statx_result().await.map(FileAttr::from).map(Metadata) +} + +#[cfg(unix)] +pub(crate) use unix::FileAttr; + +/// Metadata information about a file. +/// +/// This structure is returned from the [`metadata`] or +/// [`symlink_metadata`] function or method and represents known +/// metadata about a file such as its permissions, size, modification +/// times, etc. +#[cfg(unix)] +pub struct Metadata(pub(crate) FileAttr); + +impl Metadata { + /// Returns `true` if this metadata is for a directory. + /// + /// # Examples + /// + /// ```no_run + /// use monoio::fs; + /// + /// #[monoio::main] + /// async fn main() -> std::io::Result<()> { + /// let metadata = fs::metadata("path/to/dir").await?; + /// + /// println!("{:?}", metadata.is_dir()); + /// Ok(()) + /// } + /// ``` + pub fn is_dir(&self) -> bool { + self.0.stat.st_mode & libc::S_IFMT == libc::S_IFDIR + } + + /// Returns `true` if this metadata is for a regular file. + /// + /// # Examples + /// + /// ```no_run + /// use monoio::fs; + /// + /// #[monoio::main] + /// async fn main() -> std::io::Result<()> { + /// let metadata = fs::metadata("foo.txt").await?; + /// + /// println!("{:?}", metadata.is_file()); + /// Ok(()) + /// } + /// ``` + pub fn is_file(&self) -> bool { + self.0.stat.st_mode & libc::S_IFMT == libc::S_IFREG + } + + /// Returns `true` if this metadata is for a symbolic link. + /// + /// # Examples + /// + /// ```no_run + /// use monoio::fs; + /// + /// #[monoio::main] + /// async fn main() -> std::io::Result<()> { + /// let metadata = fs::metadata("foo.txt").await?; + /// + /// println!("{:?}", metadata.is_symlink()); + /// Ok(()) + /// } + /// ``` + pub fn is_symlink(&self) -> bool { + self.0.stat.st_mode & libc::S_IFMT == libc::S_IFLNK + } + + /// Returns the size of the file, in bytes, this metadata is for. + /// + /// # Examples + /// + /// ```no_run + /// use monoio::fs; + /// + /// #[monoio::main] + /// async fn main() -> std::io::Result<()> { + /// let metadata = fs::metadata("foo.txt").await?; + /// + /// println!("{:?}", metadata.len()); + /// Ok(()) + /// } + /// ``` + #[allow(clippy::len_without_is_empty)] + pub fn len(&self) -> u64 { + self.0.size() + } + + /// Returns the last modification time listed in this metadata. + /// + /// # Examples + /// + /// ```no_run + /// use monoio::fs; + /// + /// #[monoio::main] + /// async fn main() -> std::io::Result<()> { + /// let metadata = fs::metadata("foo.txt").await?; + /// + /// println!("{:?}", metadata.modified()); + /// Ok(()) + /// } + pub fn modified(&self) -> std::io::Result { + let mtime = self.0.stat.st_mtime; + let mtime_nsec = self.0.stat.st_mtime_nsec as u32; + + Ok(SystemTime::UNIX_EPOCH + std::time::Duration::new(mtime as u64, mtime_nsec)) + } + + /// Returns the last access time listed in this metadata. + /// + /// # Examples + /// + /// ```no_run + /// use monoio::fs; + /// + /// #[monoio::main] + /// async fn main() -> std::io::Result<()> { + /// let metadata = fs::metadata("foo.txt").await?; + /// + /// println!("{:?}", metadata.accessed()); + /// Ok(()) + /// } + /// ``` + pub fn accessed(&self) -> std::io::Result { + let atime = self.0.stat.st_atime; + let atime_nsec = self.0.stat.st_atime_nsec as u32; + + Ok(SystemTime::UNIX_EPOCH + std::time::Duration::new(atime as u64, atime_nsec)) + } + + /// Returns the creation time listed in this metadata. + /// + /// # Examples + /// + /// ```no_run + /// use monoio::fs; + /// + /// #[monoio::main] + /// async fn main() -> std::io::Result<()> { + /// let metadata = fs::metadata("foo.txt").await?; + /// + /// println!("{:?}", metadata.created()); + /// Ok(()) + /// } + /// ``` + #[cfg(target_os = "linux")] + pub fn created(&self) -> std::io::Result { + if let Some(extra) = self.0.statx_extra_fields.as_ref() { + return if extra.stx_mask & libc::STATX_BTIME != 0 { + let btime = extra.stx_btime.tv_sec; + let btime_nsec = extra.stx_btime.tv_nsec; + + Ok(SystemTime::UNIX_EPOCH + std::time::Duration::new(btime as u64, btime_nsec)) + } else { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "Creation time is not available", + )) + }; + } + + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "Creation time is not available", + )) + } + + /// Returns the permissions of the file this metadata is for. + /// + /// # Examples + /// + /// ```no_run + /// use monoio::fs; + /// + /// #[monoio::main] + /// async fn main() -> std::io::Result<()> { + /// let metadata = fs::metadata("foo.txt").await?; + /// + /// println!("{:?}", metadata.permissions()); + /// Ok(()) + /// } + /// ``` + #[cfg(unix)] + pub fn permissions(&self) -> Permissions { + use super::permissions::Permissions; + + Permissions(self.0.perm()) + } + + /// Returns the file type for this metadata. + /// + /// # Examples + /// + /// ```no_run + /// use monoio::fs; + /// + /// #[monoio::main] + /// async fn main() -> std::io::Result<()> { + /// let metadata = fs::metadata("foo.txt").await?; + /// + /// println!("{:?}", metadata.file_type()); + /// Ok(()) + /// } + /// ``` + #[cfg(unix)] + pub fn file_type(&self) -> FileType { + self.0.file_type() + } +} + +impl std::fmt::Debug for Metadata { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut debug = f.debug_struct("Metadata"); + // debug.field("file_type", &self.file_type()); + debug.field("permissions", &self.permissions()); + debug.field("len", &self.len()); + if let Ok(modified) = self.modified() { + debug.field("modified", &modified); + } + if let Ok(accessed) = self.accessed() { + debug.field("accessed", &accessed); + } + #[cfg(target_os = "linux")] + if let Ok(created) = self.created() { + debug.field("created", &created); + } + debug.finish_non_exhaustive() + } +} + +#[cfg(all(target_os = "linux", not(target_pointer_width = "32")))] +impl MetadataExt for Metadata { + fn dev(&self) -> u64 { + self.0.stat.st_dev + } + + fn ino(&self) -> u64 { + self.0.stat.st_ino + } + + fn mode(&self) -> u32 { + self.0.stat.st_mode + } + + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + fn nlink(&self) -> u64 { + self.0.stat.st_nlink.into() + } + + #[cfg(not(any(target_arch = "aarch64", target_arch = "riscv64")))] + fn nlink(&self) -> u64 { + self.0.stat.st_nlink + } + + fn uid(&self) -> u32 { + self.0.stat.st_uid + } + + fn gid(&self) -> u32 { + self.0.stat.st_gid + } + + fn rdev(&self) -> u64 { + self.0.stat.st_rdev + } + + fn size(&self) -> u64 { + self.0.stat.st_size as u64 + } + + fn atime(&self) -> i64 { + self.0.stat.st_atime + } + + fn atime_nsec(&self) -> i64 { + self.0.stat.st_atime_nsec + } + + fn mtime(&self) -> i64 { + self.0.stat.st_mtime + } + + fn mtime_nsec(&self) -> i64 { + self.0.stat.st_mtime_nsec + } + + fn ctime(&self) -> i64 { + self.0.stat.st_ctime + } + + fn ctime_nsec(&self) -> i64 { + self.0.stat.st_ctime_nsec + } + + fn blksize(&self) -> u64 { + self.0.stat.st_blksize as u64 + } + + fn blocks(&self) -> u64 { + self.0.stat.st_blocks as u64 + } +} + +#[cfg(all(target_os = "macos", not(target_pointer_width = "32")))] +impl MetadataExt for Metadata { + fn dev(&self) -> u64 { + self.0.stat.st_dev as u64 + } + + fn ino(&self) -> u64 { + self.0.stat.st_ino + } + + fn mode(&self) -> u32 { + self.0.stat.st_mode as u32 + } + + fn nlink(&self) -> u64 { + self.0.stat.st_nlink.into() + } + + fn uid(&self) -> u32 { + self.0.stat.st_uid + } + + fn gid(&self) -> u32 { + self.0.stat.st_gid + } + + fn rdev(&self) -> u64 { + self.0.stat.st_rdev as u64 + } + + fn size(&self) -> u64 { + self.0.stat.st_size as u64 + } + + fn atime(&self) -> i64 { + self.0.stat.st_atime + } + + fn atime_nsec(&self) -> i64 { + self.0.stat.st_atime_nsec + } + + fn mtime(&self) -> i64 { + self.0.stat.st_mtime + } + + fn mtime_nsec(&self) -> i64 { + self.0.stat.st_mtime_nsec + } + + fn ctime(&self) -> i64 { + self.0.stat.st_ctime + } + + fn ctime_nsec(&self) -> i64 { + self.0.stat.st_ctime_nsec + } + + fn blksize(&self) -> u64 { + self.0.stat.st_blksize as u64 + } + + fn blocks(&self) -> u64 { + self.0.stat.st_blocks as u64 + } +} + +#[cfg(all(unix, target_pointer_width = "32"))] +impl MetadataExt for Metadata { + fn dev(&self) -> u64 { + self.0.stat.st_dev.into() + } + + fn ino(&self) -> u64 { + self.0.stat.st_ino.into() + } + + fn mode(&self) -> u32 { + self.0.stat.st_mode + } + + fn nlink(&self) -> u64 { + self.0.stat.st_nlink.into() + } + + fn uid(&self) -> u32 { + self.0.stat.st_uid + } + + fn gid(&self) -> u32 { + self.0.stat.st_gid + } + + fn rdev(&self) -> u64 { + self.0.stat.st_rdev.into() + } + + fn size(&self) -> u64 { + self.0.stat.st_size as u64 + } + + fn atime(&self) -> i64 { + self.0.stat.st_atime.into() + } + + fn atime_nsec(&self) -> i64 { + self.0.stat.st_atime_nsec.into() + } + + fn mtime(&self) -> i64 { + self.0.stat.st_mtime.into() + } + + fn mtime_nsec(&self) -> i64 { + self.0.stat.st_mtime_nsec.into() + } + + fn ctime(&self) -> i64 { + self.0.stat.st_ctime.into() + } + + fn ctime_nsec(&self) -> i64 { + self.0.stat.st_ctime_nsec.into() + } + + fn blksize(&self) -> u64 { + self.0.stat.st_blksize as u64 + } + + fn blocks(&self) -> u64 { + self.0.stat.st_blocks as u64 + } +} diff --git a/monoio/src/fs/metadata/unix.rs b/monoio/src/fs/metadata/unix.rs new file mode 100644 index 00000000..352c8f78 --- /dev/null +++ b/monoio/src/fs/metadata/unix.rs @@ -0,0 +1,82 @@ +use libc::mode_t; + +use crate::fs::{file_type::FileType, permissions::FilePermissions}; + +pub(crate) struct FileAttr { + #[cfg(target_os = "linux")] + pub(crate) stat: libc::stat64, + #[cfg(target_os = "macos")] + pub(crate) stat: libc::stat, + #[cfg(target_os = "linux")] + pub(crate) statx_extra_fields: Option, +} + +#[cfg(unix)] +impl FileAttr { + pub(crate) fn size(&self) -> u64 { + self.stat.st_size as u64 + } + + pub(crate) fn perm(&self) -> FilePermissions { + FilePermissions { + mode: (self.stat.st_mode as mode_t), + } + } + + pub(crate) fn file_type(&self) -> FileType { + FileType { + mode: self.stat.st_mode as mode_t, + } + } +} + +/// Extra fields that are available in `statx` struct. +#[cfg(target_os = "linux")] +pub(crate) struct StatxExtraFields { + pub(crate) stx_mask: u32, + pub(crate) stx_btime: libc::statx_timestamp, +} + +/// Convert a `statx` struct to not platform-specific `FileAttr`. +/// Current implementation is only for Linux. +#[cfg(target_os = "linux")] +impl From for FileAttr { + fn from(buf: libc::statx) -> Self { + let mut stat: libc::stat64 = unsafe { std::mem::zeroed() }; + + stat.st_dev = libc::makedev(buf.stx_dev_major, buf.stx_dev_minor) as _; + stat.st_ino = buf.stx_ino as libc::ino64_t; + stat.st_nlink = buf.stx_nlink as libc::nlink_t; + stat.st_mode = buf.stx_mode as libc::mode_t; + stat.st_uid = buf.stx_uid as libc::uid_t; + stat.st_gid = buf.stx_gid as libc::gid_t; + stat.st_rdev = libc::makedev(buf.stx_rdev_major, buf.stx_rdev_minor) as _; + stat.st_size = buf.stx_size as libc::off64_t; + stat.st_blksize = buf.stx_blksize as libc::blksize_t; + stat.st_blocks = buf.stx_blocks as libc::blkcnt64_t; + stat.st_atime = buf.stx_atime.tv_sec as libc::time_t; + // `i64` on gnu-x86_64-x32, `c_ulong` otherwise. + stat.st_atime_nsec = buf.stx_atime.tv_nsec as _; + stat.st_mtime = buf.stx_mtime.tv_sec as libc::time_t; + stat.st_mtime_nsec = buf.stx_mtime.tv_nsec as _; + stat.st_ctime = buf.stx_ctime.tv_sec as libc::time_t; + stat.st_ctime_nsec = buf.stx_ctime.tv_nsec as _; + + let extra = StatxExtraFields { + stx_mask: buf.stx_mask, + stx_btime: buf.stx_btime, + }; + + Self { + stat, + statx_extra_fields: Some(extra), + } + } +} + +#[cfg(target_os = "macos")] +impl From for FileAttr { + fn from(stat: libc::stat) -> Self { + Self { stat } + } +} diff --git a/monoio/src/fs/metadata/windows.rs b/monoio/src/fs/metadata/windows.rs new file mode 100644 index 00000000..e69de29b diff --git a/monoio/src/fs/mod.rs b/monoio/src/fs/mod.rs index e4ec774d..0147bdc0 100644 --- a/monoio/src/fs/mod.rs +++ b/monoio/src/fs/mod.rs @@ -8,6 +8,21 @@ pub use file::File; mod open_options; pub use open_options::OpenOptions; +#[cfg(unix)] +mod metadata; +#[cfg(unix)] +pub use metadata::{metadata, symlink_metadata, Metadata}; + +#[cfg(unix)] +mod file_type; +#[cfg(target_os = "linux")] +pub use file_type::FileType; + +#[cfg(unix)] +mod permissions; +#[cfg(target_os = "linux")] +pub use permissions::Permissions; + use crate::buf::IoBuf; /// Read the entire contents of a file into a bytes vector. diff --git a/monoio/src/fs/permissions.rs b/monoio/src/fs/permissions.rs new file mode 100644 index 00000000..5b69ea5f --- /dev/null +++ b/monoio/src/fs/permissions.rs @@ -0,0 +1,86 @@ +use std::{fmt::Debug, os::unix::fs::PermissionsExt}; + +#[cfg(unix)] +use libc::mode_t; + +#[cfg(unix)] +#[derive(Clone, PartialEq, Eq)] +pub(crate) struct FilePermissions { + pub(crate) mode: mode_t, +} + +impl FilePermissions { + fn readonly(&self) -> bool { + self.mode & 0o222 == 0 + } + + #[cfg(target_os = "linux")] + fn mode(&self) -> u32 { + self.mode + } + + fn set_readonly(&mut self, readonly: bool) { + if readonly { + self.mode &= !0o222; + } else { + self.mode |= 0o222; + } + } + + #[cfg(not(target_os = "linux"))] + fn mode(&self) -> u32 { + unimplemented!() + } +} + +/// Representation of the various permissions on a file. +#[cfg(unix)] +pub struct Permissions(pub(crate) FilePermissions); + +impl Permissions { + /// Returns `true` if these permissions describe a readonly (unwritable) file. + pub fn readonly(&self) -> bool { + self.0.readonly() + } + + /// Set the readonly flag for this set of permissions. + /// + /// This will not change the file's permissions, only the in-memory representation. + /// Same with the `std::fs`, if you want to change the file's permissions, you should use + /// `monoio::fs::set_permissions`(currently not support) or `std::fs::set_permissions`. + #[allow(unused)] + pub fn set_readonly(&mut self, readonly: bool) { + self.0.set_readonly(readonly) + } +} + +impl PermissionsExt for Permissions { + /// Returns the underlying raw `mode_t` bits that are used by the OS. + fn mode(&self) -> u32 { + self.0.mode() + } + + /// Set the mode bits for this set of permissions. + /// + /// This will not change the file's permissions, only the in-memory representation. + /// Same with the `std::fs`, if you want to change the file's permissions, you should use + /// `monoio::fs::set_permissions`(currently not support) or `std::fs::set_permissions`. + fn set_mode(&mut self, mode: u32) { + *self = Self::from_mode(mode); + } + + /// Create a new instance of `Permissions` from the given mode bits. + fn from_mode(mode: u32) -> Self { + Self(FilePermissions { + mode: mode as mode_t, + }) + } +} + +impl Debug for Permissions { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Permissions") + .field("readonly", &self.readonly()) + .finish_non_exhaustive() + } +} diff --git a/monoio/tests/fs_metadata.rs b/monoio/tests/fs_metadata.rs new file mode 100644 index 00000000..0f1c40c1 --- /dev/null +++ b/monoio/tests/fs_metadata.rs @@ -0,0 +1,82 @@ +#![cfg(unix)] + +use std::io::Write; + +#[monoio::test_all] +async fn basic_file_metadata() { + let mut file = tempfile::NamedTempFile::new().unwrap(); + + assert_eq!(file.write(b"foo bar").unwrap(), 7); + + let m_file = monoio::fs::File::open(file.path()).await.unwrap(); + + let m_meta = monoio::fs::metadata(file.path()).await.unwrap(); + let mf_meta = m_file.metadata().await.unwrap(); + let std_meta = std::fs::metadata(file.path()).unwrap(); + + assert_eq!(m_meta.len(), std_meta.len()); + assert_eq!(mf_meta.len(), std_meta.len()); + + assert_eq!(m_meta.modified().unwrap(), std_meta.modified().unwrap()); + assert_eq!(mf_meta.modified().unwrap(), std_meta.modified().unwrap()); + + assert_eq!(m_meta.accessed().unwrap(), std_meta.accessed().unwrap()); + assert_eq!(mf_meta.accessed().unwrap(), std_meta.accessed().unwrap()); + + #[cfg(target_os = "linux")] + assert_eq!(m_meta.created().unwrap(), std_meta.created().unwrap()); + #[cfg(target_os = "linux")] + assert_eq!(mf_meta.created().unwrap(), std_meta.created().unwrap()); + + assert_eq!(m_meta.is_file(), std_meta.is_file()); + assert_eq!(mf_meta.is_file(), std_meta.is_file()); + + assert_eq!(m_meta.is_dir(), std_meta.is_dir()); + assert_eq!(mf_meta.is_dir(), std_meta.is_dir()); +} + +#[monoio::test_all] +async fn dir_metadata() { + let dir = tempfile::tempdir().unwrap(); + + let m_meta = monoio::fs::metadata(dir.path()).await.unwrap(); + let std_meta = std::fs::metadata(dir.path()).unwrap(); + + assert_eq!(m_meta.len(), std_meta.len()); + + assert_eq!(m_meta.modified().unwrap(), std_meta.modified().unwrap()); + + assert_eq!(m_meta.accessed().unwrap(), std_meta.accessed().unwrap()); + + #[cfg(target_os = "linux")] + assert_eq!(m_meta.created().unwrap(), std_meta.created().unwrap()); + + assert_eq!(m_meta.is_file(), std_meta.is_file()); + + assert_eq!(m_meta.is_dir(), std_meta.is_dir()); +} + +#[monoio::test_all] +async fn symlink_metadata() { + let dir = tempfile::tempdir().unwrap(); + let link = dir.path().join("link"); + std::os::unix::fs::symlink(dir.path(), &link).unwrap(); + + let m_meta = monoio::fs::symlink_metadata(&link).await.unwrap(); + let std_meta = std::fs::symlink_metadata(&link).unwrap(); + + assert_eq!(m_meta.len(), std_meta.len()); + + assert_eq!(m_meta.modified().unwrap(), std_meta.modified().unwrap()); + + assert_eq!(m_meta.accessed().unwrap(), std_meta.accessed().unwrap()); + + #[cfg(target_os = "linux")] + assert_eq!(m_meta.created().unwrap(), std_meta.created().unwrap()); + + assert_eq!(m_meta.is_file(), std_meta.is_file()); + + assert_eq!(m_meta.is_dir(), std_meta.is_dir()); + + assert_eq!(m_meta.is_symlink(), std_meta.is_symlink()); +}