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

Add set_symlink_permissions and access functions to DirExt #337

Merged
merged 1 commit into from
Nov 1, 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: 135 additions & 7 deletions cap-fs-ext/src/dir_ext.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
#[cfg(feature = "fs_utf8")]
use camino::Utf8Path;
#[cfg(all(windows, feature = "async_std", feature = "fs_utf8"))]
use cap_primitives::fs::stat;
#[cfg(not(windows))]
use cap_primitives::fs::symlink;
use cap_primitives::fs::{open_dir_nofollow, set_times, set_times_nofollow};
#[cfg(all(windows, feature = "async_std", feature = "fs_utf8"))]
use cap_primitives::fs::{stat, FollowSymlinks};
use cap_primitives::fs::{
access, open_dir_nofollow, set_symlink_permissions, set_times, set_times_nofollow,
FollowSymlinks, Permissions,
};
#[cfg(windows)]
use cap_primitives::fs::{symlink_dir, symlink_file};
use io_lifetimes::AsFilelike;
Expand All @@ -13,7 +16,7 @@ use std::path::Path;
#[cfg(feature = "async_std")]
use {async_std::task::spawn_blocking, async_trait::async_trait};

pub use cap_primitives::fs::SystemTimeSpec;
pub use cap_primitives::fs::{AccessType, SystemTimeSpec};

/// Extension trait for `Dir`.
pub trait DirExt {
Expand Down Expand Up @@ -95,6 +98,18 @@ pub trait DirExt {
/// points to a directory, it cannot be removed with the `remove_file`
/// operation. This method will remove files and all symlinks.
fn remove_file_or_symlink<P: AsRef<Path>>(&self, path: P) -> io::Result<()>;

/// Test for accessibility or existence of a filesystem object.
fn access<P: AsRef<Path>>(&self, path: P, type_: AccessType) -> io::Result<()>;

/// Test for accessibility or existence of a filesystem object, without
/// following symbolic links.
fn access_symlink<P: AsRef<Path>>(&self, path: P, type_: AccessType) -> io::Result<()>;

/// Changes the permissions found on a file or a directory, without following
/// symbolic links.
fn set_symlink_permissions<P: AsRef<Path>>(&self, path: P, perm: Permissions)
-> io::Result<()>;
}

/// Extension trait for `Dir`, async.
Expand Down Expand Up @@ -216,6 +231,17 @@ pub trait AsyncDirExt {
&self,
path: P,
) -> io::Result<()>;

/// Test for accessibility or existence of a filesystem object.
async fn access<P: AsRef<Path>>(&self, path: P, type_: AccessType) -> io::Result<()>;

/// Changes the permissions found on a file or a directory, without following
/// symbolic links.
async fn set_symlink_permissions<P: AsRef<Path>>(
&self,
path: P,
perm: Permissions,
) -> io::Result<()>;
}

/// `fs_utf8` version of `DirExt`.
Expand Down Expand Up @@ -304,6 +330,21 @@ pub trait DirExtUtf8 {
/// on symlinks to directories on Windows, similar to how `unlink` works
/// on symlinks to directories on Posix-ish platforms.
fn remove_file_or_symlink<P: AsRef<Utf8Path>>(&self, path: P) -> io::Result<()>;

/// Test for accessibility or existence of a filesystem object.
fn access<P: AsRef<Utf8Path>>(&self, path: P, type_: AccessType) -> io::Result<()>;

/// Test for accessibility or existence of a filesystem object, without
/// following symbolic links.
fn access_symlink<P: AsRef<Utf8Path>>(&self, path: P, type_: AccessType) -> io::Result<()>;

/// Changes the permissions found on a file or a directory, without following
/// symbolic links.
fn set_symlink_permissions<P: AsRef<Utf8Path>>(
&self,
path: P,
perm: Permissions,
) -> io::Result<()>;
}

/// `fs_utf8` version of `DirExt`.
Expand Down Expand Up @@ -410,6 +451,25 @@ pub trait AsyncDirExtUtf8 {
/// points to a directory, it cannot be removed with the `remove_file`
/// operation. This method will remove files and all symlinks.
async fn remove_file_or_symlink<P: AsRef<Utf8Path> + Send>(&self, path: P) -> io::Result<()>;

/// Test for accessibility or existence of a filesystem object.
async fn access<P: AsRef<Utf8Path>>(&self, path: P, type_: AccessType) -> io::Result<()>;

/// Test for accessibility or existence of a filesystem object, without
/// following symbolic links.
async fn access_symlink<P: AsRef<Utf8Path>>(
&self,
path: P,
type_: AccessType,
) -> io::Result<()>;

/// Changes the permissions found on a file or a directory, without following
/// symbolic links.
async fn set_symlink_permissions<P: AsRef<Utf8Path>>(
&self,
path: P,
perm: Permissions,
) -> io::Result<()>;
}

#[cfg(feature = "std")]
Expand Down Expand Up @@ -533,7 +593,7 @@ impl DirExt for cap_std::fs::Dir {
#[cfg(windows)]
#[inline]
fn remove_file_or_symlink<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
use crate::{FollowSymlinks, OpenOptionsFollowExt};
use crate::OpenOptionsFollowExt;
use cap_primitives::fs::_WindowsByHandle;
use cap_std::fs::OpenOptions;
use std::os::windows::fs::OpenOptionsExt;
Expand Down Expand Up @@ -566,6 +626,40 @@ impl DirExt for cap_std::fs::Dir {

Ok(())
}

/// Test for accessibility or existence of a filesystem object.
fn access<P: AsRef<Path>>(&self, path: P, type_: AccessType) -> io::Result<()> {
access(
&self.as_filelike_view::<std::fs::File>(),
path.as_ref(),
type_,
FollowSymlinks::Yes,
)
}

/// Test for accessibility or existence of a filesystem object.
fn access_symlink<P: AsRef<Path>>(&self, path: P, type_: AccessType) -> io::Result<()> {
access(
&self.as_filelike_view::<std::fs::File>(),
path.as_ref(),
type_,
FollowSymlinks::No,
)
}

/// Changes the permissions found on a file or a directory, without following
/// symbolic links.
fn set_symlink_permissions<P: AsRef<Path>>(
&self,
path: P,
perm: Permissions,
) -> io::Result<()> {
set_symlink_permissions(
&self.as_filelike_view::<std::fs::File>(),
path.as_ref(),
perm,
)
}
}

#[cfg(feature = "async_std")]
Expand Down Expand Up @@ -829,7 +923,7 @@ impl AsyncDirExt for cap_async_std::fs::Dir {
&self,
path: P,
) -> io::Result<()> {
use crate::{FollowSymlinks, OpenOptionsFollowExt};
use crate::OpenOptionsFollowExt;
use cap_primitives::fs::_WindowsByHandle;
use cap_std::fs::OpenOptions;
use std::os::windows::fs::OpenOptionsExt;
Expand Down Expand Up @@ -996,7 +1090,7 @@ impl DirExtUtf8 for cap_std::fs_utf8::Dir {
#[cfg(windows)]
#[inline]
fn remove_file_or_symlink<P: AsRef<Utf8Path>>(&self, path: P) -> io::Result<()> {
use crate::{FollowSymlinks, OpenOptionsFollowExt};
use crate::OpenOptionsFollowExt;
use cap_primitives::fs::_WindowsByHandle;
use cap_std::fs::OpenOptions;
use std::os::windows::fs::OpenOptionsExt;
Expand Down Expand Up @@ -1029,6 +1123,40 @@ impl DirExtUtf8 for cap_std::fs_utf8::Dir {

Ok(())
}

/// Test for accessibility or existence of a filesystem object.
fn access<P: AsRef<Utf8Path>>(&self, path: P, type_: AccessType) -> io::Result<()> {
access(
&self.as_filelike_view::<std::fs::File>(),
path.as_ref().as_ref(),
type_,
FollowSymlinks::Yes,
)
}

/// Test for accessibility or existence of a filesystem object.
fn access_symlink<P: AsRef<Utf8Path>>(&self, path: P, type_: AccessType) -> io::Result<()> {
access(
&self.as_filelike_view::<std::fs::File>(),
path.as_ref().as_ref(),
type_,
FollowSymlinks::No,
)
}

/// Changes the permissions found on a file or a directory, without following
/// symbolic links.
fn set_symlink_permissions<P: AsRef<Utf8Path>>(
&self,
path: P,
perm: Permissions,
) -> io::Result<()> {
set_symlink_permissions(
&self.as_filelike_view::<std::fs::File>(),
path.as_ref().as_ref(),
perm,
)
}
}

#[cfg(all(feature = "async_std", feature = "fs_utf8"))]
Expand Down
2 changes: 1 addition & 1 deletion cap-fs-ext/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ mod reopen;
pub use dir_entry_ext::DirEntryExt;
#[cfg(all(any(feature = "std", feature = "async_std"), feature = "fs_utf8"))]
pub use dir_ext::DirExtUtf8;
pub use dir_ext::{DirExt, SystemTimeSpec};
pub use dir_ext::{AccessType, DirExt, SystemTimeSpec};
pub use file_type_ext::FileTypeExt;
pub use is_file_read_write::IsFileReadWrite;
pub use metadata_ext::MetadataExt;
Expand Down
16 changes: 8 additions & 8 deletions cap-net-ext/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ pub trait PoolExt: private::Sealed {
/// Initiate a TCP connection, converting a [`TcpListener`] to a
/// [`TcpStream`].
///
/// This is simlar to to [`Pool::connect_tcp_stream`] in that it performs a
/// This is simlar to [`Pool::connect_tcp_stream`] in that it performs a
/// TCP connection, but instead of creating a new socket itself it takes a
/// [`TcpListener`], such as one created with [`TcpListenerExt::new`].
///
Expand All @@ -263,8 +263,8 @@ pub trait PoolExt: private::Sealed {

/// Initiate a TCP connection on a socket.
///
/// This is simlar to to [`Self::connect_into_tcp_stream`], however instead
/// of converting a `TcpListener` to a `TcpStream`, it leaves fd in the
/// This is simlar to [`Self::connect_into_tcp_stream`], however instead of
/// converting a `TcpListener` to a `TcpStream`, it leaves fd in the
/// existing `TcpListener`.
///
/// This function ensures that the address to connect to is permitted by
Expand All @@ -279,7 +279,7 @@ pub trait PoolExt: private::Sealed {

/// Initiate a UDP connection.
///
/// This is simlar to to [`Pool::connect_udp_socket`] in that it performs a
/// This is simlar to [`Pool::connect_udp_socket`] in that it performs a
/// UDP connection, but instead of creating a new socket itself it takes a
/// [`UdpSocket`], such as one created with [`UdpSocketExt::new`].
///
Expand Down Expand Up @@ -537,7 +537,7 @@ impl TcpConnecter {
/// Initiate a TCP connection, converting a [`TcpListener`] to a
/// [`TcpStream`].
///
/// This is simlar to to [`Pool::connect_tcp_stream`] in that it performs a
/// This is simlar to [`Pool::connect_tcp_stream`] in that it performs a
/// TCP connection, but instead of creating a new socket itself it takes a
/// [`TcpListener`], such as one created with [`TcpListenerExt::new`].
///
Expand All @@ -554,8 +554,8 @@ impl TcpConnecter {

/// Initiate a TCP connection on a socket.
///
/// This is simlar to to [`Pool::connect_into_tcp_stream`], however instead
/// of converting a `TcpListener` to a `TcpStream`, it leaves fd in the
/// This is simlar to [`Pool::connect_into_tcp_stream`], however instead of
/// converting a `TcpListener` to a `TcpStream`, it leaves fd in the
/// existing `TcpListener`.
///
/// This is similar to [`PoolExt::connect_existing_tcp_listener`] except
Expand Down Expand Up @@ -584,7 +584,7 @@ pub struct UdpConnecter(smallvec::SmallVec<[SocketAddr; 1]>);
impl UdpConnecter {
/// Initiate a UDP connection.
///
/// This is simlar to to [`Pool::connect_udp_socket`] in that it performs a
/// This is simlar to [`Pool::connect_udp_socket`] in that it performs a
/// UDP connection, but instead of creating a new socket itself it takes a
/// [`UdpSocket`], such as one created with [`UdpSocketExt::new`].
///
Expand Down
89 changes: 89 additions & 0 deletions cap-primitives/src/fs/access.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
//! Access test functions.

use crate::fs::{access_impl, FollowSymlinks};
#[cfg(racy_asserts)]
use crate::fs::{access_unchecked, file_path};
use std::path::Path;
use std::{fs, io};

/// Access modes for use with [`DirExt::access`].
#[derive(Clone, Copy, Debug)]
pub struct AccessModes {
/// Is the object readable?
pub readable: bool,
/// Is the object writable?
pub writable: bool,
/// Is the object executable?
pub executable: bool,
}

/// Access modes for use with [`DirExt::access`].
#[derive(Clone, Copy, Debug)]
pub enum AccessType {
/// Test whether the named object is accessible in the given modes.
Access(AccessModes),

/// Test whether the named object exists.
Exists,
}

/// Canonicalize the given path, ensuring that the resolution of the path never
/// escapes the directory tree rooted at `start`.
#[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))]
pub fn access(
start: &fs::File,
path: &Path,
type_: AccessType,
follow: FollowSymlinks,
) -> io::Result<()> {
// Call the underlying implementation.
let result = access_impl(start, path, type_, follow);

#[cfg(racy_asserts)]
let unchecked = access_unchecked(start, path, type_, follow);

#[cfg(racy_asserts)]
check_access(start, path, type_, follow, &result, &unchecked);

result
}

#[cfg(racy_asserts)]
#[allow(clippy::enum_glob_use)]
fn check_access(
start: &fs::File,
path: &Path,
_type_: AccessType,
_follow: FollowSymlinks,
result: &io::Result<()>,
unchecked: &io::Result<()>,
) {
use io::ErrorKind::*;

match (map_result(result), map_result(stat)) {
(Ok(()), Ok(())) => {}

(Err((PermissionDenied, message)), _) => {
// TODO: Check that access in the no-follow case got the right
// error.
}

(Err((kind, message)), Err((unchecked_kind, unchecked_message))) => {
assert_eq!(kind, unchecked_kind);
assert_eq!(
message,
unchecked_message,
"start='{:?}', path='{:?}'",
start,
path.display()
);
}

other => panic!(
"unexpected result from access start='{:?}', path='{}':\n{:#?}",
start,
path.display(),
other,
),
}
}
Loading
Loading