From 66766056c88c418f888b6a56b6a2c2c883c018d3 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 27 Aug 2026 10:51:00 -0700 Subject: [PATCH 01/34] Import the cap-primitives crate Copy the entire contents of this crate into `crates/wasi/src/filesystem/primitives` for future modifications to get it building. --- .../wasi/src/filesystem/primitives/access.rs | 89 +++ .../filesystem/primitives/assert_same_file.rs | 85 +++ .../src/filesystem/primitives/canonicalize.rs | 74 +++ crates/wasi/src/filesystem/primitives/copy.rs | 17 + .../src/filesystem/primitives/create_dir.rs | 104 ++++ .../src/filesystem/primitives/dir_builder.rs | 84 +++ .../src/filesystem/primitives/dir_entry.rs | 137 +++++ .../src/filesystem/primitives/dir_options.rs | 63 ++ .../wasi/src/filesystem/primitives/errors.rs | 14 + crates/wasi/src/filesystem/primitives/file.rs | 190 ++++++ .../primitives/file_path_by_searching.rs | 52 ++ .../src/filesystem/primitives/file_type.rs | 163 +++++ .../filesystem/primitives/follow_symlinks.rs | 30 + .../src/filesystem/primitives/hard_link.rs | 130 ++++ .../primitives/is_file_read_write.rs | 9 + .../primitives/manually/canonical_path.rs | 70 +++ .../primitives/manually/canonicalize.rs | 65 ++ .../primitives/manually/cow_component.rs | 44 ++ .../src/filesystem/primitives/manually/mod.rs | 23 + .../filesystem/primitives/manually/open.rs | 561 ++++++++++++++++++ .../primitives/manually/open_entry.rs | 29 + .../primitives/manually/read_link_one.rs | 36 ++ .../filesystem/primitives/maybe_owned_file.rs | 145 +++++ .../src/filesystem/primitives/metadata.rs | 523 ++++++++++++++++ crates/wasi/src/filesystem/primitives/mod.rs | 131 ++++ crates/wasi/src/filesystem/primitives/open.rs | 108 ++++ .../src/filesystem/primitives/open_ambient.rs | 22 + .../src/filesystem/primitives/open_dir.rs | 81 +++ .../src/filesystem/primitives/open_options.rs | 481 +++++++++++++++ .../primitives/open_unchecked_error.rs | 37 ++ .../src/filesystem/primitives/permissions.rs | 150 +++++ .../src/filesystem/primitives/read_dir.rs | 71 +++ .../src/filesystem/primitives/read_link.rs | 110 ++++ .../src/filesystem/primitives/remove_dir.rs | 148 +++++ .../filesystem/primitives/remove_dir_all.rs | 9 + .../src/filesystem/primitives/remove_file.rs | 129 ++++ .../filesystem/primitives/remove_open_dir.rs | 16 + .../wasi/src/filesystem/primitives/rename.rs | 158 +++++ .../wasi/src/filesystem/primitives/reopen.rs | 36 ++ .../primitives/rustix/darwin/fs/file_path.rs | 21 + .../primitives/rustix/darwin/fs/mod.rs | 3 + .../primitives/rustix/darwin/mod.rs | 1 + .../primitives/rustix/freebsd/fs/check.rs | 40 ++ .../primitives/rustix/freebsd/fs/mod.rs | 18 + .../rustix/freebsd/fs/open_entry_impl.rs | 12 + .../primitives/rustix/freebsd/fs/open_impl.rs | 30 + .../rustix/freebsd/fs/remove_dir_impl.rs | 16 + .../rustix/freebsd/fs/remove_file_impl.rs | 12 + .../rustix/freebsd/fs/set_permissions_impl.rs | 21 + .../rustix/freebsd/fs/set_times_impl.rs | 45 ++ .../primitives/rustix/freebsd/fs/stat_impl.rs | 22 + .../primitives/rustix/freebsd/mod.rs | 1 + .../primitives/rustix/fs/access_unchecked.rs | 38 ++ .../primitives/rustix/fs/copy_impl.rs | 248 ++++++++ .../rustix/fs/create_dir_unchecked.rs | 19 + .../filesystem/primitives/rustix/fs/cvt.rs | 19 + .../primitives/rustix/fs/dir_entry_inner.rs | 96 +++ .../primitives/rustix/fs/dir_options_ext.rs | 21 + .../primitives/rustix/fs/dir_utils.rs | 232 ++++++++ .../filesystem/primitives/rustix/fs/errors.rs | 27 + .../primitives/rustix/fs/file_path.rs | 19 + .../primitives/rustix/fs/file_type_ext.rs | 114 ++++ .../rustix/fs/hard_link_unchecked.rs | 24 + .../rustix/fs/is_file_read_write_impl.rs | 43 ++ .../primitives/rustix/fs/is_root_dir.rs | 6 + .../primitives/rustix/fs/is_same_file.rs | 33 ++ .../primitives/rustix/fs/metadata_ext.rs | 363 ++++++++++++ .../filesystem/primitives/rustix/fs/mod.rs | 190 ++++++ .../filesystem/primitives/rustix/fs/oflags.rs | 101 ++++ .../primitives/rustix/fs/open_options_ext.rs | 24 + .../primitives/rustix/fs/open_unchecked.rs | 71 +++ .../primitives/rustix/fs/permissions_ext.rs | 74 +++ .../primitives/rustix/fs/read_dir_inner.rs | 136 +++++ .../rustix/fs/read_link_unchecked.rs | 19 + .../rustix/fs/remove_dir_all_impl.rs | 41 ++ .../rustix/fs/remove_dir_unchecked.rs | 9 + .../rustix/fs/remove_file_unchecked.rs | 9 + .../rustix/fs/remove_open_dir_by_searching.rs | 36 ++ .../primitives/rustix/fs/rename_unchecked.rs | 14 + .../primitives/rustix/fs/reopen_impl.rs | 18 + .../rustix/fs/set_permissions_impl.rs | 49 ++ .../fs/set_symlink_permissions_unchecked.rs | 17 + .../primitives/rustix/fs/set_times_impl.rs | 56 ++ .../primitives/rustix/fs/stat_unchecked.rs | 79 +++ .../primitives/rustix/fs/symlink_unchecked.rs | 13 + .../filesystem/primitives/rustix/fs/times.rs | 62 ++ .../rustix/linux/fs/canonicalize_impl.rs | 66 +++ .../rustix/linux/fs/file_metadata.rs | 27 + .../primitives/rustix/linux/fs/file_path.rs | 22 + .../primitives/rustix/linux/fs/mod.rs | 37 ++ .../rustix/linux/fs/open_entry_impl.rs | 19 + .../primitives/rustix/linux/fs/open_impl.rs | 150 +++++ .../primitives/rustix/linux/fs/procfs.rs | 82 +++ .../rustix/linux/fs/set_permissions_impl.rs | 55 ++ .../rustix/linux/fs/set_times_impl.rs | 48 ++ .../primitives/rustix/linux/fs/stat_impl.rs | 56 ++ .../filesystem/primitives/rustix/linux/mod.rs | 6 + .../src/filesystem/primitives/rustix/mod.rs | 17 + .../filesystem/primitives/set_permissions.rs | 89 +++ .../src/filesystem/primitives/set_times.rs | 32 + crates/wasi/src/filesystem/primitives/stat.rs | 81 +++ .../wasi/src/filesystem/primitives/symlink.rs | 354 +++++++++++ .../filesystem/primitives/system_time_spec.rs | 45 ++ .../primitives/via_parent/access.rs | 19 + .../primitives/via_parent/create_dir.rs | 19 + .../primitives/via_parent/hard_link.rs | 26 + .../filesystem/primitives/via_parent/mod.rs | 41 ++ .../primitives/via_parent/open_parent.rs | 126 ++++ .../primitives/via_parent/read_link.rs | 21 + .../primitives/via_parent/remove_dir.rs | 14 + .../primitives/via_parent/remove_file.rs | 14 + .../primitives/via_parent/rename.rs | 50 ++ .../primitives/via_parent/set_permissions.rs | 13 + .../via_parent/set_symlink_permissions.rs | 17 + .../via_parent/set_times_nofollow.rs | 18 + .../primitives/via_parent/symlink.rs | 48 ++ .../primitives/windows/fs/access_unchecked.rs | 32 + .../filesystem/primitives/windows/fs/copy.rs | 19 + .../windows/fs/create_dir_unchecked.rs | 18 + .../primitives/windows/fs/create_file_at_w.rs | 279 +++++++++ .../primitives/windows/fs/dir_entry_inner.rs | 122 ++++ .../primitives/windows/fs/dir_options_ext.rs | 8 + .../primitives/windows/fs/dir_utils.rs | 204 +++++++ .../primitives/windows/fs/errors.rs | 22 + .../primitives/windows/fs/file_type_ext.rs | 121 ++++ .../primitives/windows/fs/get_path.rs | 31 + .../windows/fs/hard_link_unchecked.rs | 16 + .../windows/fs/is_file_read_write_impl.rs | 11 + .../primitives/windows/fs/is_same_file.rs | 57 ++ .../primitives/windows/fs/metadata_ext.rs | 208 +++++++ .../filesystem/primitives/windows/fs/mod.rs | 88 +++ .../primitives/windows/fs/oflags.rs | 84 +++ .../primitives/windows/fs/open_impl.rs | 66 +++ .../primitives/windows/fs/open_options_ext.rs | 121 ++++ .../primitives/windows/fs/open_unchecked.rs | 230 +++++++ .../primitives/windows/fs/read_dir_inner.rs | 55 ++ .../primitives/windows/fs/read_link_impl.rs | 20 + .../windows/fs/read_link_unchecked.rs | 14 + .../windows/fs/remove_dir_all_impl.rs | 28 + .../windows/fs/remove_dir_unchecked.rs | 10 + .../windows/fs/remove_file_unchecked.rs | 10 + .../windows/fs/remove_open_dir_impl.rs | 24 + .../primitives/windows/fs/rename_unchecked.rs | 16 + .../primitives/windows/fs/reopen_impl.rs | 85 +++ .../windows/fs/set_permissions_unchecked.rs | 21 + .../fs/set_symlink_permissions_unchecked.rs | 20 + .../primitives/windows/fs/set_times_impl.rs | 48 ++ .../primitives/windows/fs/stat_unchecked.rs | 36 ++ .../windows/fs/symlink_unchecked.rs | 25 + .../src/filesystem/primitives/windows/mod.rs | 4 + 150 files changed, 10751 insertions(+) create mode 100644 crates/wasi/src/filesystem/primitives/access.rs create mode 100644 crates/wasi/src/filesystem/primitives/assert_same_file.rs create mode 100644 crates/wasi/src/filesystem/primitives/canonicalize.rs create mode 100644 crates/wasi/src/filesystem/primitives/copy.rs create mode 100644 crates/wasi/src/filesystem/primitives/create_dir.rs create mode 100644 crates/wasi/src/filesystem/primitives/dir_builder.rs create mode 100644 crates/wasi/src/filesystem/primitives/dir_entry.rs create mode 100644 crates/wasi/src/filesystem/primitives/dir_options.rs create mode 100644 crates/wasi/src/filesystem/primitives/errors.rs create mode 100644 crates/wasi/src/filesystem/primitives/file.rs create mode 100644 crates/wasi/src/filesystem/primitives/file_path_by_searching.rs create mode 100644 crates/wasi/src/filesystem/primitives/file_type.rs create mode 100644 crates/wasi/src/filesystem/primitives/follow_symlinks.rs create mode 100644 crates/wasi/src/filesystem/primitives/hard_link.rs create mode 100644 crates/wasi/src/filesystem/primitives/is_file_read_write.rs create mode 100644 crates/wasi/src/filesystem/primitives/manually/canonical_path.rs create mode 100644 crates/wasi/src/filesystem/primitives/manually/canonicalize.rs create mode 100644 crates/wasi/src/filesystem/primitives/manually/cow_component.rs create mode 100644 crates/wasi/src/filesystem/primitives/manually/mod.rs create mode 100644 crates/wasi/src/filesystem/primitives/manually/open.rs create mode 100644 crates/wasi/src/filesystem/primitives/manually/open_entry.rs create mode 100644 crates/wasi/src/filesystem/primitives/manually/read_link_one.rs create mode 100644 crates/wasi/src/filesystem/primitives/maybe_owned_file.rs create mode 100644 crates/wasi/src/filesystem/primitives/metadata.rs create mode 100644 crates/wasi/src/filesystem/primitives/mod.rs create mode 100644 crates/wasi/src/filesystem/primitives/open.rs create mode 100644 crates/wasi/src/filesystem/primitives/open_ambient.rs create mode 100644 crates/wasi/src/filesystem/primitives/open_dir.rs create mode 100644 crates/wasi/src/filesystem/primitives/open_options.rs create mode 100644 crates/wasi/src/filesystem/primitives/open_unchecked_error.rs create mode 100644 crates/wasi/src/filesystem/primitives/permissions.rs create mode 100644 crates/wasi/src/filesystem/primitives/read_dir.rs create mode 100644 crates/wasi/src/filesystem/primitives/read_link.rs create mode 100644 crates/wasi/src/filesystem/primitives/remove_dir.rs create mode 100644 crates/wasi/src/filesystem/primitives/remove_dir_all.rs create mode 100644 crates/wasi/src/filesystem/primitives/remove_file.rs create mode 100644 crates/wasi/src/filesystem/primitives/remove_open_dir.rs create mode 100644 crates/wasi/src/filesystem/primitives/rename.rs create mode 100644 crates/wasi/src/filesystem/primitives/reopen.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/darwin/fs/file_path.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/darwin/fs/mod.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/darwin/mod.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/check.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/mod.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/open_entry_impl.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/open_impl.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/remove_dir_impl.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/remove_file_impl.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/set_permissions_impl.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/set_times_impl.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/stat_impl.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/freebsd/mod.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/access_unchecked.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/copy_impl.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/create_dir_unchecked.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/cvt.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/dir_entry_inner.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/dir_options_ext.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/dir_utils.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/errors.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/file_path.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/file_type_ext.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/hard_link_unchecked.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/is_file_read_write_impl.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/is_root_dir.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/is_same_file.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/metadata_ext.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/mod.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/oflags.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/open_options_ext.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/open_unchecked.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/permissions_ext.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/read_dir_inner.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/read_link_unchecked.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/remove_dir_all_impl.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/remove_dir_unchecked.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/remove_file_unchecked.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/remove_open_dir_by_searching.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/rename_unchecked.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/reopen_impl.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/set_permissions_impl.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/set_symlink_permissions_unchecked.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/set_times_impl.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/stat_unchecked.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/symlink_unchecked.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/times.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/linux/fs/canonicalize_impl.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/linux/fs/file_metadata.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/linux/fs/file_path.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/linux/fs/mod.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/linux/fs/open_entry_impl.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/linux/fs/open_impl.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/linux/fs/procfs.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/linux/fs/set_permissions_impl.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/linux/fs/set_times_impl.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/linux/fs/stat_impl.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/linux/mod.rs create mode 100644 crates/wasi/src/filesystem/primitives/rustix/mod.rs create mode 100644 crates/wasi/src/filesystem/primitives/set_permissions.rs create mode 100644 crates/wasi/src/filesystem/primitives/set_times.rs create mode 100644 crates/wasi/src/filesystem/primitives/stat.rs create mode 100644 crates/wasi/src/filesystem/primitives/symlink.rs create mode 100644 crates/wasi/src/filesystem/primitives/system_time_spec.rs create mode 100644 crates/wasi/src/filesystem/primitives/via_parent/access.rs create mode 100644 crates/wasi/src/filesystem/primitives/via_parent/create_dir.rs create mode 100644 crates/wasi/src/filesystem/primitives/via_parent/hard_link.rs create mode 100644 crates/wasi/src/filesystem/primitives/via_parent/mod.rs create mode 100644 crates/wasi/src/filesystem/primitives/via_parent/open_parent.rs create mode 100644 crates/wasi/src/filesystem/primitives/via_parent/read_link.rs create mode 100644 crates/wasi/src/filesystem/primitives/via_parent/remove_dir.rs create mode 100644 crates/wasi/src/filesystem/primitives/via_parent/remove_file.rs create mode 100644 crates/wasi/src/filesystem/primitives/via_parent/rename.rs create mode 100644 crates/wasi/src/filesystem/primitives/via_parent/set_permissions.rs create mode 100644 crates/wasi/src/filesystem/primitives/via_parent/set_symlink_permissions.rs create mode 100644 crates/wasi/src/filesystem/primitives/via_parent/set_times_nofollow.rs create mode 100644 crates/wasi/src/filesystem/primitives/via_parent/symlink.rs create mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/access_unchecked.rs create mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/copy.rs create mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/create_dir_unchecked.rs create mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/create_file_at_w.rs create mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/dir_entry_inner.rs create mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/dir_options_ext.rs create mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/dir_utils.rs create mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/errors.rs create mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/file_type_ext.rs create mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/get_path.rs create mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/hard_link_unchecked.rs create mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/is_file_read_write_impl.rs create mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/is_same_file.rs create mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/metadata_ext.rs create mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/mod.rs create mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/oflags.rs create mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/open_impl.rs create mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/open_options_ext.rs create mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/open_unchecked.rs create mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/read_dir_inner.rs create mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/read_link_impl.rs create mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/read_link_unchecked.rs create mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/remove_dir_all_impl.rs create mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/remove_dir_unchecked.rs create mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/remove_file_unchecked.rs create mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/remove_open_dir_impl.rs create mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/rename_unchecked.rs create mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/reopen_impl.rs create mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/set_permissions_unchecked.rs create mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/set_symlink_permissions_unchecked.rs create mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/set_times_impl.rs create mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/stat_unchecked.rs create mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/symlink_unchecked.rs create mode 100644 crates/wasi/src/filesystem/primitives/windows/mod.rs diff --git a/crates/wasi/src/filesystem/primitives/access.rs b/crates/wasi/src/filesystem/primitives/access.rs new file mode 100644 index 000000000000..987762519426 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/access.rs @@ -0,0 +1,89 @@ +//! Access test functions. + +use crate::fs::{FollowSymlinks, access_impl}; +#[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, + ), + } +} diff --git a/crates/wasi/src/filesystem/primitives/assert_same_file.rs b/crates/wasi/src/filesystem/primitives/assert_same_file.rs new file mode 100644 index 000000000000..f638bbf808cc --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/assert_same_file.rs @@ -0,0 +1,85 @@ +/// Assert that two arguments refer to the same underlying file. +macro_rules! assert_same_file { + ($left:expr, $right:expr) => ({ + let (left, right) = (&($left), &($right)); + if crate::rustix::fs::is_different_file(left, right).unwrap() { + panic!("assertion failed: left is a different file from right\n left: `{:?}`,\n right: `{:?}`", + left, right); + } + }); + ($left:expr, $right:expr, ) => ({ + assert_le!($left, $right); + }); + ($left:expr, $right:expr, $($msg_args:tt)+) => ({ + let (left, right) = (&($left), &($right)); + if crate::rustix::fs::is_different_file(left, right).unwrap() { + panic!("assertion failed: left is a different file from right\n left: `{:?}`,\n right: `{:?}`: {}", + left, right, format_args!($($msg_args)+)); + } + }) +} + +/// Assert that two arguments are metadata for the same underlying file. +macro_rules! assert_same_file_metadata { + ($left:expr, $right:expr) => ({ + let (left, right) = (&($left), &($right)); + if crate::rustix::fs::is_different_file_metadata(left, right).unwrap() { + panic!("assertion failed: left is a different file from right\n left: `{:?}`,\n right: `{:?}`", + left, right); + } + }); + ($left:expr, $right:expr, ) => ({ + assert_le!($left, $right); + }); + ($left:expr, $right:expr, $($msg_args:tt)+) => ({ + let (left, right) = (&($left), &($right)); + if crate::rustix::fs::is_different_file_metadata(left, right).unwrap() { + panic!("assertion failed: left is a different file from right\n left: `{:?}`,\n right: `{:?}`: {}", + left, right, format_args!($($msg_args)+)); + } + }) +} + +/// Assert that two arguments refer to different underlying files. +#[allow(unused_macros)] +macro_rules! assert_different_file { + ($left:expr, $right:expr) => ({ + let (left, right) = (&($left), &($right)); + if !crate::rustix::fs::is_different_file(left, right).unwrap() { + panic!("assertion failed: left is the same file as right\n left: `{:?}`,\n right: `{:?}`", + left, right); + } + }); + ($left:expr, $right:expr, ) => ({ + assert_le!($left, $right); + }); + ($left:expr, $right:expr, $($msg_args:tt)+) => ({ + let (left, right) = (&($left), &($right)); + if !crate::rustix::fs::is_different_file(left, right).unwrap() { + panic!("assertion failed: left is the same file as right\n left: `{:?}`,\n right: `{:?}`: {}", + left, right, format_args!($($msg_args)+)); + } + }) +} + +/// Assert that two arguments are metadata for the same underlying file. +#[allow(unused_macros)] +macro_rules! assert_different_metadata { + ($left:expr, $right:expr) => ({ + let (left, right) = (&($left), &($right)); + if !crate::rustix::fs::is_different_file_metadata(left, right).unwrap() { + panic!("assertion failed: left is the same file as right\n left: `{:?}`,\n right: `{:?}`", + left, right); + } + }); + ($left:expr, $right:expr, ) => ({ + assert_le!($left, $right); + }); + ($left:expr, $right:expr, $($msg_args:tt)+) => ({ + let (left, right) = (&($left), &($right)); + if !crate::rustix::fs::is_different_file_metadata(left, right).unwrap() { + panic!("assertion failed: left is the same file as right\n left: `{:?}`,\n right: `{:?}`: {}", + left, right, format_args!($($msg_args)+)); + } + }) +} diff --git a/crates/wasi/src/filesystem/primitives/canonicalize.rs b/crates/wasi/src/filesystem/primitives/canonicalize.rs new file mode 100644 index 000000000000..e552e60d36f6 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/canonicalize.rs @@ -0,0 +1,74 @@ +//! Sandboxed path canonicalization. + +use crate::fs::canonicalize_impl; +#[cfg(racy_asserts)] +use crate::fs::{OpenOptions, file_path, open}; +use std::path::{Path, PathBuf}; +use std::{fs, io}; + +/// 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))] +#[inline] +pub fn canonicalize(start: &fs::File, path: &Path) -> io::Result { + // Call the underlying implementation. + let result = canonicalize_impl(start, path); + + #[cfg(racy_asserts)] + check_canonicalize(start, path, &result); + + result +} + +#[cfg(racy_asserts)] +fn check_canonicalize(start: &fs::File, path: &Path, result: &io::Result) { + if let Ok(canonical_path) = result { + let path_result = open(start, path, OpenOptions::new().read(true)); + let canonical_result = open(start, canonical_path, OpenOptions::new().read(true)); + match (path_result, canonical_result) { + (Ok(path_file), Ok(canonical_file)) => assert_same_file!( + &path_file, + &canonical_file, + "we should be able to stat paths that we just canonicalized" + ), + (Err(path_err), Err(canonical_err)) => { + assert_eq!(path_err.to_string(), canonical_err.to_string()) + } + other => { + // TODO: Checking in the case it does end with ".". + if !path.to_string_lossy().ends_with("/.") { + panic!("inconsistent canonicalize checks: {:?}", other); + } + } + } + + // On operating systems which can tell us the path of a file descriptor, + // assert that the path we computed canonicalizes to the same thing as + // the input canonicalizes too. + if let Some(start_abspath) = file_path(start) { + let check_abspath = start_abspath.join(path); + let result_abspath = start_abspath.join(canonical_path); + if let Ok(check_abspath) = fs::canonicalize(check_abspath) { + let result_abspath = + fs::canonicalize(result_abspath).expect("we already canonicalized this"); + assert_eq!( + check_abspath, + result_abspath, + "incorrect canonicalization: start='{:?}' path='{}' result='{}'", + start, + path.display(), + canonical_path.display() + ); + // TODO: When porting to Windows, check whether `start_abspath` not being + // a canonicalized path leads to `\\?\` extended path prefix differences. + assert!( + result_abspath.starts_with(start_abspath), + "sandbox escape: start='{:?}' path='{}' result='{}'", + start, + path.display(), + canonical_path.display() + ); + } + } + } +} diff --git a/crates/wasi/src/filesystem/primitives/copy.rs b/crates/wasi/src/filesystem/primitives/copy.rs new file mode 100644 index 000000000000..f40b954c638f --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/copy.rs @@ -0,0 +1,17 @@ +use crate::fs::copy_impl; +use std::path::Path; +use std::{fs, io}; + +/// Copies the contents of one file to another. +#[inline] +pub fn copy( + from_start: &fs::File, + from_path: &Path, + to_start: &fs::File, + to_path: &Path, +) -> io::Result { + // In theory we could do extra sanity checks here, but `copy_impl` + // implementations use other sandboxed routines to open the files, + // so it'd be mostly redundant. + copy_impl(from_start, from_path, to_start, to_path) +} diff --git a/crates/wasi/src/filesystem/primitives/create_dir.rs b/crates/wasi/src/filesystem/primitives/create_dir.rs new file mode 100644 index 000000000000..9b593c940040 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/create_dir.rs @@ -0,0 +1,104 @@ +//! This defines `create_dir`, the primary entrypoint to sandboxed directory +//! creation. + +#[cfg(racy_asserts)] +use crate::fs::{ + canonicalize, create_dir_unchecked, map_result, stat_unchecked, FollowSymlinks, Metadata, +}; +use crate::fs::{create_dir_impl, DirOptions}; +use std::path::Path; +use std::{fs, io}; + +/// Perform a `mkdirat`-like operation, 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))] +#[inline] +pub fn create_dir(start: &fs::File, path: &Path, options: &DirOptions) -> io::Result<()> { + #[cfg(racy_asserts)] + let stat_before = stat_unchecked(start, path, FollowSymlinks::No); + + // Call the underlying implementation. + let result = create_dir_impl(start, path, options); + + #[cfg(racy_asserts)] + let stat_after = stat_unchecked(start, path, FollowSymlinks::No); + + #[cfg(racy_asserts)] + check_create_dir(start, path, options, &stat_before, &result, &stat_after); + + result +} + +#[cfg(racy_asserts)] +#[allow(clippy::enum_glob_use)] +fn check_create_dir( + start: &fs::File, + path: &Path, + options: &DirOptions, + stat_before: &io::Result, + result: &io::Result<()>, + stat_after: &io::Result, +) { + use io::ErrorKind::*; + + match ( + map_result(stat_before), + map_result(result), + map_result(stat_after), + ) { + (Err((NotFound, _)), Ok(()), Ok(metadata)) => { + assert!(metadata.is_dir()); + assert_same_file_metadata!( + &stat_unchecked( + start, + &canonicalize(start, path).unwrap(), + FollowSymlinks::No + ) + .unwrap(), + &metadata + ); + } + + (Ok(metadata_before), Err((AlreadyExists, _)), Ok(metadata_after)) => { + assert_same_file_metadata!(&metadata_before, &metadata_after); + } + + (_, Err((kind, message)), _) => { + // TODO: Checking in the case it does end with ".". + if !path.to_string_lossy().ends_with(".") { + match map_result(&canonicalize(start, path)) { + Ok(canon) => match map_result(&create_dir_unchecked(start, &canon, options)) { + Err((unchecked_kind, unchecked_message)) => { + assert_eq!( + kind, + unchecked_kind, + "unexpected error kind from create_dir start='{:?}', \ + path='{}':\nstat_before={:#?}\nresult={:#?}\nstat_after={:#?}", + start, + path.display(), + stat_before, + result, + stat_after + ); + assert_eq!(message, unchecked_message); + } + _ => panic!("unsandboxed create_dir success"), + }, + Err((_canon_kind, _canon_message)) => { + /* TODO: Check error messages + assert_eq!(kind, canon_kind); + assert_eq!(message, canon_message); + */ + } + } + } + } + + other => panic!( + "inconsistent create_dir checks: start='{:?}' path='{}':\n{:#?}", + start, + path.display(), + other, + ), + } +} diff --git a/crates/wasi/src/filesystem/primitives/dir_builder.rs b/crates/wasi/src/filesystem/primitives/dir_builder.rs new file mode 100644 index 000000000000..53b5174f44ee --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/dir_builder.rs @@ -0,0 +1,84 @@ +use crate::fs::DirOptions; +use std::fmt; + +/// A builder used to create directories in various manners. +/// +/// This corresponds to [`std::fs::DirBuilder`]. +/// +/// Unlike `std::fs::DirBuilder`, this API has no `DirBuilder::create`, because +/// creating directories requires a capability. Use [`Dir::create_dir_with`] +/// instead. +/// +/// [`Dir::create_dir_with`]: https://docs.rs/cap-std/latest/cap_std/fs/struct.Dir.html#method.create_dir_with +/// +///
+/// We need to define our own version because the libstd `DirBuilder` doesn't +/// have public accessors that we can use. +///
+pub struct DirBuilder { + pub(crate) recursive: bool, + pub(crate) options: DirOptions, +} + +impl DirBuilder { + /// Creates a new set of options with default mode/security settings for + /// all platforms and also non-recursive. + /// + /// This corresponds to [`std::fs::DirBuilder::new`]. + #[allow(clippy::new_without_default)] + #[inline] + pub const fn new() -> Self { + Self { + recursive: false, + options: DirOptions::new(), + } + } + + /// Indicates that directories should be created recursively, creating all + /// parent directories. + /// + /// This corresponds to [`std::fs::DirBuilder::recursive`]. + #[inline] + pub fn recursive(&mut self, recursive: bool) -> &mut Self { + self.recursive = recursive; + self + } + + /// Return the `DirOptions` contained in this `DirBuilder`. + #[inline] + pub const fn options(&self) -> &DirOptions { + &self.options + } + + /// Return the value of the `recursive` flag. + #[inline] + pub const fn is_recursive(&self) -> bool { + self.recursive + } +} + +/// Unix-specific extensions to [`fs::DirBuilder`]. +#[cfg(unix)] +pub trait DirBuilderExt { + /// Sets the mode to create new directories with. This option defaults to + /// 0o777. + fn mode(&mut self, mode: u32) -> &mut Self; +} + +#[cfg(unix)] +impl DirBuilderExt for DirBuilder { + #[inline] + fn mode(&mut self, mode: u32) -> &mut Self { + self.options.mode(mode); + self + } +} + +impl fmt::Debug for DirBuilder { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut b = f.debug_struct("DirBuilder"); + b.field("recursive", &self.recursive); + b.field("options", &self.options); + b.finish() + } +} diff --git a/crates/wasi/src/filesystem/primitives/dir_entry.rs b/crates/wasi/src/filesystem/primitives/dir_entry.rs new file mode 100644 index 000000000000..22dcae9b5634 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/dir_entry.rs @@ -0,0 +1,137 @@ +use crate::fs::{ + dir_options, DirEntryInner, FileType, FollowSymlinks, Metadata, OpenOptions, ReadDir, +}; +#[cfg(not(windows))] +use rustix::fs::DirEntryExt; +use std::ffi::OsString; +use std::{fmt, fs, io}; + +/// Entries returned by the `ReadDir` iterator. +/// +/// This corresponds to [`std::fs::DirEntry`]. +/// +/// Unlike `std::fs::DirEntry`, this API has no `DirEntry::path`, because +/// absolute paths don't interoperate well with the capability model. +/// +/// There is a `file_name` function, however there are also `open`, +/// `open_with`, `open_dir`, `remove_file`, and `remove_dir` functions for +/// opening or removing the entry directly, which can be more efficient and +/// convenient. +/// +/// There is no `from_std` method, as `std::fs::DirEntry` doesn't provide a way +/// to construct a `DirEntry` without opening directories by ambient paths. +pub struct DirEntry { + pub(crate) inner: DirEntryInner, +} + +impl DirEntry { + /// Open the file for reading. + #[inline] + pub fn open(&self) -> io::Result { + self.open_with(OpenOptions::new().read(true)) + } + + /// Open the file with the given options. + #[inline] + pub fn open_with(&self, options: &OpenOptions) -> io::Result { + self.inner.open(options) + } + + /// Open the entry as a directory. + #[inline] + pub fn open_dir(&self) -> io::Result { + self.open_with(&dir_options()) + } + + /// Removes the file from its filesystem. + #[inline] + pub fn remove_file(&self) -> io::Result<()> { + self.inner.remove_file() + } + + /// Removes the directory from its filesystem. + #[inline] + pub fn remove_dir(&self) -> io::Result<()> { + self.inner.remove_dir() + } + + /// Returns an iterator over the entries within the subdirectory. + #[inline] + pub fn read_dir(&self) -> io::Result { + self.inner.read_dir(FollowSymlinks::Yes) + } + + /// Returns the metadata for the file that this entry points at. + /// + /// This corresponds to [`std::fs::DirEntry::metadata`]. + /// + /// # Platform-specific behavior + /// + /// On Windows, this produces a `Metadata` object which does not contain + /// the optional values returned by [`MetadataExt`]. Use + /// [`cap_fs_ext::DirEntryExt::full_metadata`] to obtain a `Metadata` with + /// the values filled in. + /// + /// [`MetadataExt`]: https://doc.rust-lang.org/std/os/windows/fs/trait.MetadataExt.html + /// [`cap_fs_ext::DirEntryExt::full_metadata`]: https://docs.rs/cap-fs-ext/latest/cap_fs_ext/trait.DirEntryExt.html#tymethod.full_metadata + #[inline] + pub fn metadata(&self) -> io::Result { + self.inner.metadata() + } + + /// Returns the file type for the file that this entry points at. + /// + /// This corresponds to [`std::fs::DirEntry::file_type`]. + /// + /// # Platform-specific behavior + /// + /// On Windows and most Unix platforms this function is free (no extra system calls needed), but + /// some Unix platforms may require the equivalent call to `metadata` to learn about the target + /// file type. + #[inline] + pub fn file_type(&self) -> io::Result { + self.inner.file_type() + } + + /// Returns the bare file name of this directory entry without any other + /// leading path component. + /// + /// This corresponds to [`std::fs::DirEntry::file_name`]. + #[inline] + pub fn file_name(&self) -> OsString { + self.inner.file_name() + } + + #[cfg(any(not(windows), windows_by_handle))] + #[cfg_attr(windows, allow(dead_code))] + #[inline] + pub(crate) fn is_same_file(&self, metadata: &Metadata) -> io::Result { + self.inner.is_same_file(metadata) + } +} + +#[cfg(not(windows))] +impl DirEntryExt for DirEntry { + #[inline] + fn ino(&self) -> u64 { + self.inner.ino() + } +} + +impl fmt::Debug for DirEntry { + // Like libstd's version, but doesn't print the path. + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.inner.fmt(f) + } +} + +/// Extension trait to allow `full_metadata` etc. to be exposed by +/// the `cap-fs-ext` crate. +/// +/// This is hidden from the main API since this functionality isn't present in +/// `std`. Use `cap_fs_ext::DirEntryExt` instead of calling this directly. +#[cfg(windows)] +#[doc(hidden)] +pub trait _WindowsDirEntryExt { + fn full_metadata(&self) -> io::Result; +} diff --git a/crates/wasi/src/filesystem/primitives/dir_options.rs b/crates/wasi/src/filesystem/primitives/dir_options.rs new file mode 100644 index 000000000000..d7dd8129b66e --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/dir_options.rs @@ -0,0 +1,63 @@ +#[cfg(not(target_os = "wasi"))] +use crate::fs::DirOptionsExt; + +/// Options and flags which can be used to configure how a directory is +/// created. +/// +/// This is to `create_dir` what to `OpenOptions` is to `open`. +#[derive(Debug, Clone)] +pub struct DirOptions { + #[cfg(not(target_os = "wasi"))] + #[allow(dead_code)] + pub(crate) ext: DirOptionsExt, +} + +impl DirOptions { + /// Creates a blank new set of options ready for configuration. + #[allow(clippy::new_without_default)] + #[inline] + pub const fn new() -> Self { + Self { + #[cfg(not(target_os = "wasi"))] + ext: DirOptionsExt::new(), + } + } +} + +#[cfg(unix)] +impl crate::fs::DirBuilderExt for DirOptions { + #[inline] + fn mode(&mut self, mode: u32) -> &mut Self { + self.ext.mode(mode); + self + } +} + +#[cfg(target_os = "vxworks")] +impl crate::fs::DirBuilderExt for DirOptions { + #[inline] + fn mode(&mut self, mode: u32) -> &mut Self { + self.ext.mode(mode); + self + } +} + +#[cfg(feature = "arbitrary")] +impl arbitrary::Arbitrary<'_> for DirOptions { + fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { + #[cfg(any(unix, target_os = "vxworks"))] + use crate::fs::DirBuilderExt; + + #[allow(unused_mut)] + let mut dir_options = Self::new(); + + #[cfg(any(unix, target_os = "vxworks"))] + dir_options.mode(u.int_in_range(0..=0o777)?); + + // Unix is currently the only platform with a `DirBuilderExt`. + #[cfg(not(any(unix, target_os = "vxworks")))] + let _ = u; + + Ok(dir_options) + } +} diff --git a/crates/wasi/src/filesystem/primitives/errors.rs b/crates/wasi/src/filesystem/primitives/errors.rs new file mode 100644 index 000000000000..150f762c73af --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/errors.rs @@ -0,0 +1,14 @@ +use std::io; + +#[cfg(not(windows))] +pub(crate) use crate::rustix::fs::errors::*; +#[cfg(windows)] +pub(crate) use crate::windows::fs::errors::*; + +#[cold] +pub(crate) fn escape_attempt() -> io::Error { + io::Error::new( + io::ErrorKind::PermissionDenied, + "a path led outside of the filesystem", + ) +} diff --git a/crates/wasi/src/filesystem/primitives/file.rs b/crates/wasi/src/filesystem/primitives/file.rs new file mode 100644 index 000000000000..3acf1bef4f07 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/file.rs @@ -0,0 +1,190 @@ +use std::io; + +/// Unix-specific extensions to [`fs::File`]. +#[cfg(unix)] +pub trait FileExt { + /// Reads a number of bytes starting from a given offset. + fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result; + + /// Like `read_at`, except that it reads into a slice of buffers. + #[cfg(unix_file_vectored_at)] + fn read_vectored_at(&self, bufs: &mut [io::IoSliceMut<'_>], offset: u64) -> io::Result { + default_read_vectored(|b| self.read_at(b, offset), bufs) + } + + /// Reads the exact number of bytes required to fill `buf` from the given offset. + fn read_exact_at(&self, mut buf: &mut [u8], mut offset: u64) -> io::Result<()> { + while !buf.is_empty() { + match self.read_at(buf, offset) { + Ok(0) => break, + Ok(n) => { + let tmp = buf; + buf = &mut tmp[n..]; + offset += n as u64; + } + Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {} + Err(e) => return Err(e), + } + } + if !buf.is_empty() { + Err(io::Error::new( + io::ErrorKind::UnexpectedEof, + "failed to fill whole buffer", + )) + } else { + Ok(()) + } + } + + /// Writes a number of bytes starting from a given offset. + fn write_at(&self, buf: &[u8], offset: u64) -> io::Result; + + /// Like `write_at`, except that it writes from a slice of buffers. + #[cfg(unix_file_vectored_at)] + fn write_vectored_at(&self, bufs: &[io::IoSlice<'_>], offset: u64) -> io::Result { + default_write_vectored(|b| self.write_at(b, offset), bufs) + } + + /// Attempts to write an entire buffer starting from a given offset. + fn write_all_at(&self, mut buf: &[u8], mut offset: u64) -> io::Result<()> { + while !buf.is_empty() { + match self.write_at(buf, offset) { + Ok(0) => { + return Err(io::Error::new( + io::ErrorKind::WriteZero, + "failed to write whole buffer", + )); + } + Ok(n) => { + buf = &buf[n..]; + offset += n as u64 + } + Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {} + Err(e) => return Err(e), + } + } + Ok(()) + } +} + +#[cfg(unix_file_vectored_at)] +fn default_read_vectored(read: F, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result +where + F: FnOnce(&mut [u8]) -> io::Result, +{ + let buf = bufs + .iter_mut() + .find(|b| !b.is_empty()) + .map_or(&mut [][..], |b| &mut **b); + read(buf) +} + +#[cfg(unix_file_vectored_at)] +fn default_write_vectored(write: F, bufs: &[io::IoSlice<'_>]) -> io::Result +where + F: FnOnce(&[u8]) -> io::Result, +{ + let buf = bufs + .iter() + .find(|b| !b.is_empty()) + .map_or(&[][..], |b| &**b); + write(buf) +} + +/// WASI-specific extensions to [`fs::File`]. +#[cfg(target_os = "wasi")] +pub trait FileExt { + /// Reads a number of bytes starting from a given offset. + fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result { + let bufs = &mut [io::IoSliceMut::new(buf)]; + self.read_vectored_at(bufs, offset) + } + + /// Reads a number of bytes starting from a given offset. + fn read_vectored_at(&self, bufs: &mut [io::IoSliceMut<'_>], offset: u64) -> io::Result; + + /// Reads the exact number of byte required to fill `buf` from the given offset. + fn read_exact_at(&self, mut buf: &mut [u8], mut offset: u64) -> io::Result<()> { + while !buf.is_empty() { + match self.read_at(buf, offset) { + Ok(0) => break, + Ok(n) => { + let tmp = buf; + buf = &mut tmp[n..]; + offset += n as u64; + } + Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {} + Err(e) => return Err(e), + } + } + if !buf.is_empty() { + Err(io::Error::new( + io::ErrorKind::UnexpectedEof, + "failed to fill whole buffer", + )) + } else { + Ok(()) + } + } + + /// Writes a number of bytes starting from a given offset. + fn write_at(&self, buf: &[u8], offset: u64) -> io::Result { + let bufs = &[io::IoSlice::new(buf)]; + self.write_vectored_at(bufs, offset) + } + + /// Writes a number of bytes starting from a given offset. + fn write_vectored_at(&self, bufs: &[io::IoSlice<'_>], offset: u64) -> io::Result; + + /// Attempts to write an entire buffer starting from a given offset. + fn write_all_at(&self, mut buf: &[u8], mut offset: u64) -> io::Result<()> { + while !buf.is_empty() { + match self.write_at(buf, offset) { + Ok(0) => { + return Err(io::Error::new( + io::ErrorKind::WriteZero, + "failed to write whole buffer", + )); + } + Ok(n) => { + buf = &buf[n..]; + offset += n as u64 + } + Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {} + Err(e) => return Err(e), + } + } + Ok(()) + } + + /// Adjust the flags associated with this file. + fn fdstat_set_flags(&self, flags: u16) -> io::Result<()>; + + /// Adjust the rights associated with this file. + fn fdstat_set_rights(&self, rights: u64, inheriting: u64) -> io::Result<()>; + + /// Provide file advisory information on a file descriptor. + fn advise(&self, offset: u64, len: u64, advice: u8) -> io::Result<()>; + + /// Force the allocation of space in a file. + fn allocate(&self, offset: u64, len: u64) -> io::Result<()>; + + /// Create a directory. + fn create_directory>(&self, dir: P) -> io::Result<()>; + + /// Unlink a file. + fn remove_file>(&self, path: P) -> io::Result<()>; + + /// Remove a directory. + fn remove_directory>(&self, path: P) -> io::Result<()>; +} + +/// Windows-specific extensions to [`fs::File`]. +#[cfg(windows)] +pub trait FileExt { + /// Seeks to a given position and reads a number of bytes. + fn seek_read(&self, buf: &mut [u8], offset: u64) -> io::Result; + + /// Seeks to a given position and writes a number of bytes. + fn seek_write(&self, buf: &[u8], offset: u64) -> io::Result; +} diff --git a/crates/wasi/src/filesystem/primitives/file_path_by_searching.rs b/crates/wasi/src/filesystem/primitives/file_path_by_searching.rs new file mode 100644 index 000000000000..a357f53a4173 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/file_path_by_searching.rs @@ -0,0 +1,52 @@ +use crate::fs::{ + is_root_dir, open_dir_unchecked, read_dir_unchecked, FollowSymlinks, MaybeOwnedFile, Metadata, +}; +use std::fs; +use std::path::{Component, PathBuf}; + +/// Implementation of `file_path` for directories by opening `..` and searching +/// for a directory among `..`'s children to find its name. +pub(crate) fn file_path_by_searching(file: &fs::File) -> Option { + // Use the `_noassert` functions because the asserts depend on `file_path`, + // which is what we're implementing here. + let mut base = MaybeOwnedFile::borrowed_noassert(file); + let mut components = Vec::new(); + + // Iterate with `..` until we reach the root directory. + 'next_component: loop { + // Open `..`. + let mut iter = + read_dir_unchecked(&base, Component::ParentDir.as_ref(), FollowSymlinks::No).ok()?; + let metadata = Metadata::from_file(&*base).ok()?; + + // Search the children until we find one with matching metadata, and + // then record its name. + while let Some(child) = iter.next() { + let child = child.ok()?; + if child.is_same_file(&metadata).ok()? { + // Found a match. Record the name and continue to the next component. + components.push(child.file_name()); + base = MaybeOwnedFile::owned_noassert( + open_dir_unchecked(&base, Component::ParentDir.as_ref()).ok()?, + ); + continue 'next_component; + } + } + + // We didn't find the directory among its parent's children. If we're at + // the root directory, we're done. + if is_root_dir(&base, &iter).ok()? { + break; + } + + // Otherwise, something went wrong and we can't determine the path. + return None; + } + + let mut path = PathBuf::new(); + path.push(Component::RootDir); + for component in components.iter().rev() { + path.push(component); + } + Some(path) +} diff --git a/crates/wasi/src/filesystem/primitives/file_type.rs b/crates/wasi/src/filesystem/primitives/file_type.rs new file mode 100644 index 000000000000..8f2170099d3b --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/file_type.rs @@ -0,0 +1,163 @@ +//! The `FileType` struct. + +use crate::fs::ImplFileTypeExt; + +/// `FileType`'s inner state. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +enum Inner { + /// A directory. + Dir, + + /// A file. + File, + + /// An unknown entity. + Unknown, + + /// A `FileTypeExt` type. + Ext(ImplFileTypeExt), +} + +/// A structure representing a type of file with accessors for each file type. +/// +/// This corresponds to [`std::fs::FileType`]. +/// +///
+/// We need to define our own version because the libstd `FileType` doesn't +/// have a public constructor that we can use. +///
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[repr(transparent)] +pub struct FileType(Inner); + +impl FileType { + /// Creates a `FileType` for which `is_dir()` returns `true`. + #[inline] + pub const fn dir() -> Self { + Self(Inner::Dir) + } + + /// Creates a `FileType` for which `is_file()` returns `true`. + #[inline] + pub const fn file() -> Self { + Self(Inner::File) + } + + /// Creates a `FileType` for which `is_unknown()` returns `true`. + #[inline] + pub const fn unknown() -> Self { + Self(Inner::Unknown) + } + + /// Creates a `FileType` from extension type. + #[inline] + pub(crate) const fn ext(ext: ImplFileTypeExt) -> Self { + Self(Inner::Ext(ext)) + } + + /// Tests whether this file type represents a directory. + /// + /// This corresponds to [`std::fs::FileType::is_dir`]. + #[inline] + pub fn is_dir(&self) -> bool { + self.0 == Inner::Dir + } + + /// Tests whether this file type represents a regular file. + /// + /// This corresponds to [`std::fs::FileType::is_file`]. + #[inline] + pub fn is_file(&self) -> bool { + self.0 == Inner::File + } + + /// Tests whether this file type represents a symbolic link. + /// + /// This corresponds to [`std::fs::FileType::is_symlink`]. + #[inline] + pub fn is_symlink(&self) -> bool { + if let Inner::Ext(ext) = self.0 { + ext.is_symlink() + } else { + false + } + } +} + +/// Unix-specific extensions for [`FileType`]. +/// +/// This corresponds to [`std::os::unix::fs::FileTypeExt`]. +#[cfg(any(unix, target_os = "vxworks"))] +pub trait FileTypeExt { + /// Returns `true` if this file type is a block device. + fn is_block_device(&self) -> bool; + /// Returns `true` if this file type is a character device. + fn is_char_device(&self) -> bool; + /// Returns `true` if this file type is a fifo. + fn is_fifo(&self) -> bool; + /// Returns `true` if this file type is a socket. + fn is_socket(&self) -> bool; +} + +#[cfg(any(unix, target_os = "vxworks"))] +impl FileTypeExt for FileType { + #[inline] + fn is_block_device(&self) -> bool { + self.0 == Inner::Ext(ImplFileTypeExt::block_device()) + } + + #[inline] + fn is_char_device(&self) -> bool { + self.0 == Inner::Ext(ImplFileTypeExt::char_device()) + } + + #[inline] + fn is_fifo(&self) -> bool { + self.0 == Inner::Ext(ImplFileTypeExt::fifo()) + } + + #[inline] + fn is_socket(&self) -> bool { + self.0 == Inner::Ext(ImplFileTypeExt::socket()) + } +} + +/// Windows-specific extensions for [`FileType`]. +/// +/// This corresponds to [`std::os::windows::fs::FileTypeExt`]. +#[cfg(all(windows, windows_file_type_ext))] +pub trait FileTypeExt { + /// Returns `true` if this file type is a symbolic link that is also a + /// directory. + fn is_symlink_dir(&self) -> bool; + /// Returns `true` if this file type is a symbolic link that is also a + /// file. + fn is_symlink_file(&self) -> bool; +} + +#[cfg(all(windows, windows_file_type_ext))] +impl FileTypeExt for FileType { + #[inline] + fn is_symlink_dir(&self) -> bool { + self.0 == Inner::Ext(ImplFileTypeExt::symlink_dir()) + } + + #[inline] + fn is_symlink_file(&self) -> bool { + self.0 == Inner::Ext(ImplFileTypeExt::symlink_file()) + } +} + +/// Extension trait to allow `is_block_device` etc. to be exposed by +/// the `cap-fs-ext` crate. +/// +/// This is hidden from the main API since this functionality isn't present in +/// `std`. Use `cap_fs_ext::FileTypeExt` instead of calling this directly. +#[cfg(windows)] +#[doc(hidden)] +pub trait _WindowsFileTypeExt { + fn is_block_device(&self) -> bool; + fn is_char_device(&self) -> bool; + fn is_fifo(&self) -> bool; + fn is_socket(&self) -> bool; +} diff --git a/crates/wasi/src/filesystem/primitives/follow_symlinks.rs b/crates/wasi/src/filesystem/primitives/follow_symlinks.rs new file mode 100644 index 000000000000..b6f1853c9b70 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/follow_symlinks.rs @@ -0,0 +1,30 @@ +/// Should symlinks be followed in the last component of a path? +/// +/// This doesn't affect path components other than the last. So for example in +/// "foo/bar/baz", if "foo" or "bar" are symlinks, they will always be +/// followed. This enum value only determines whether "baz" is followed. +/// +/// Instead of passing bare `bool`s as parameters, pass a distinct enum so that +/// the intent is clear. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +pub enum FollowSymlinks { + /// Yes, do follow symlinks in the last component of a path. + Yes, + + /// No, do not follow symlinks in the last component of a path. + No, +} + +impl FollowSymlinks { + /// Convert a bool where true means "follow" and false means "don't follow" + /// to a `FollowSymlinks`. + #[inline] + pub const fn follow(follow: bool) -> Self { + if follow { + Self::Yes + } else { + Self::No + } + } +} diff --git a/crates/wasi/src/filesystem/primitives/hard_link.rs b/crates/wasi/src/filesystem/primitives/hard_link.rs new file mode 100644 index 000000000000..ff05473c5ea6 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/hard_link.rs @@ -0,0 +1,130 @@ +//! This defines `hard_link`, the primary entrypoint to sandboxed hard-link +//! creation. + +use crate::fs::hard_link_impl; +#[cfg(racy_asserts)] +use crate::fs::{ + canonicalize, hard_link_unchecked, map_result, stat_unchecked, FollowSymlinks, Metadata, +}; +use std::path::Path; +use std::{fs, io}; + +/// Perform a `linkat`-like operation, 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))] +#[inline] +pub fn hard_link( + old_start: &fs::File, + old_path: &Path, + new_start: &fs::File, + new_path: &Path, +) -> io::Result<()> { + #[cfg(racy_asserts)] + let (old_metadata_before, new_metadata_before) = ( + stat_unchecked(old_start, old_path, FollowSymlinks::No), + stat_unchecked(new_start, new_path, FollowSymlinks::No), + ); + + // Call the underlying implementation. + let result = hard_link_impl(old_start, old_path, new_start, new_path); + + #[cfg(racy_asserts)] + let (old_metadata_after, new_metadata_after) = ( + stat_unchecked(old_start, old_path, FollowSymlinks::No), + stat_unchecked(new_start, new_path, FollowSymlinks::No), + ); + + #[cfg(racy_asserts)] + check_hard_link( + old_start, + old_path, + new_start, + new_path, + &old_metadata_before, + &new_metadata_before, + &result, + &old_metadata_after, + &new_metadata_after, + ); + + result +} + +#[cfg(racy_asserts)] +#[allow(clippy::too_many_arguments)] +#[allow(clippy::enum_glob_use)] +fn check_hard_link( + old_start: &fs::File, + old_path: &Path, + new_start: &fs::File, + new_path: &Path, + old_metadata_before: &io::Result, + new_metadata_before: &io::Result, + result: &io::Result<()>, + old_metadata_after: &io::Result, + new_metadata_after: &io::Result, +) { + use io::ErrorKind::*; + + match ( + map_result(old_metadata_before), + map_result(new_metadata_before), + map_result(result), + map_result(old_metadata_after), + map_result(new_metadata_after), + ) { + ( + Ok(old_metadata_before), + Err((NotFound, _)), + Ok(()), + Ok(old_metadata_after), + Ok(new_metadata_after), + ) => { + assert_same_file_metadata!(old_metadata_before, old_metadata_after); + assert_same_file_metadata!(old_metadata_before, new_metadata_after); + } + + (_, Ok(new_metadata_before), Err((AlreadyExists, _)), _, Ok(new_metadata_after)) => { + assert_same_file_metadata!(&new_metadata_before, &new_metadata_after); + } + + (_, _, Err((_kind, _message)), _, _) => match ( + map_result(&canonicalize(old_start, old_path)), + map_result(&canonicalize(new_start, new_path)), + ) { + (Ok(old_canon), Ok(new_canon)) => match map_result(&hard_link_unchecked( + old_start, &old_canon, new_start, &new_canon, + )) { + Err((_unchecked_kind, _unchecked_message)) => { + /* TODO: Check error messages. + assert_eq!(kind, unchecked_kind); + assert_eq!(message, unchecked_message); + */ + } + _ => panic!("unsandboxed link success"), + }, + (Err((_old_canon_kind, _old_canon_message)), _) => { + /* TODO: Check error messages. + assert_eq!(kind, old_canon_kind); + assert_eq!(message, old_canon_message); + */ + } + (_, Err((_new_canon_kind, _new_canon_message))) => { + /* TODO: Check error messages. + assert_eq!(kind, new_canon_kind); + assert_eq!(message, new_canon_message); + */ + } + }, + + other => panic!( + "inconsistent link checks: old_start='{:?}', old_path='{}', new_start='{:?}', \ + new_path='{}':\n{:#?}", + old_start, + old_path.display(), + new_start, + new_path.display(), + other + ), + } +} diff --git a/crates/wasi/src/filesystem/primitives/is_file_read_write.rs b/crates/wasi/src/filesystem/primitives/is_file_read_write.rs new file mode 100644 index 000000000000..99d8b16e8806 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/is_file_read_write.rs @@ -0,0 +1,9 @@ +use crate::fs::is_file_read_write_impl; +use std::{fs, io}; + +/// Return a pair of booleans indicating whether the given file is opened +/// for reading and writing, respectively. +#[inline] +pub fn is_file_read_write(file: &fs::File) -> io::Result<(bool, bool)> { + is_file_read_write_impl(file) +} diff --git a/crates/wasi/src/filesystem/primitives/manually/canonical_path.rs b/crates/wasi/src/filesystem/primitives/manually/canonical_path.rs new file mode 100644 index 000000000000..11d09bd180e3 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/manually/canonical_path.rs @@ -0,0 +1,70 @@ +use std::ffi::OsStr; +use std::path::{Component, PathBuf}; + +/// Utility for collecting the canonical path components. +pub(super) struct CanonicalPath<'path_buf> { + /// If the user requested a canonical path, a reference to the `PathBuf` to + /// write it to. + path: Option<&'path_buf mut PathBuf>, + + /// Our own private copy of the canonical path, for assertion checking. + #[cfg(racy_asserts)] + pub(super) debug: PathBuf, +} + +impl<'path_buf> CanonicalPath<'path_buf> { + pub(super) fn new(path: Option<&'path_buf mut PathBuf>) -> Self { + Self { + #[cfg(racy_asserts)] + debug: PathBuf::new(), + + path, + } + } + + pub(super) fn push(&mut self, one: &OsStr) { + #[cfg(racy_asserts)] + self.debug.push(one); + + if let Some(path) = &mut self.path { + path.push(one) + } + } + + pub(super) fn pop(&mut self) -> bool { + #[cfg(racy_asserts)] + self.debug.pop(); + + if let Some(path) = &mut self.path { + path.pop() + } else { + true + } + } + + /// The complete canonical path has been scanned. Set `path` to `None` + /// so that it isn't cleared when `self` is dropped. + pub(super) fn complete(&mut self) { + // Replace "" with ".", since "" as a relative path is interpreted as + // an error. + if let Some(path) = &mut self.path { + if path.as_os_str().is_empty() { + path.push(Component::CurDir); + } + self.path = None; + } + } +} + +impl<'path_buf> Drop for CanonicalPath<'path_buf> { + fn drop(&mut self) { + // If `self.path` is still `Some` here, it means that we haven't called + // `complete()` yet, meaning the `CanonicalPath` is being dropped + // before the complete path has been processed. In that case, clear + // `path` to indicate that we weren't able to obtain a complete path. + if let Some(path) = &mut self.path { + path.clear(); + self.path = None; + } + } +} diff --git a/crates/wasi/src/filesystem/primitives/manually/canonicalize.rs b/crates/wasi/src/filesystem/primitives/manually/canonicalize.rs new file mode 100644 index 000000000000..e0d8b552fbd9 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/manually/canonicalize.rs @@ -0,0 +1,65 @@ +//! Manual path canonicalization, one component at a time, with manual symlink +//! resolution, in order to enforce sandboxing. + +use super::internal_open; +use crate::fs::{canonicalize_options, FollowSymlinks, MaybeOwnedFile}; +use std::path::{Path, PathBuf}; +use std::{fs, io}; + +/// Implement `canonicalize` by breaking up the path into components and +/// resolving each component individually, and resolving symbolic links +/// manually. +pub(crate) fn canonicalize(start: &fs::File, path: &Path) -> io::Result { + canonicalize_with(start, path, FollowSymlinks::Yes) +} + +/// The main body of `canonicalize`, which takes an extra `follow` flag +/// allowing the caller to disable following symlinks in the last component. +pub(crate) fn canonicalize_with( + start: &fs::File, + path: &Path, + follow: FollowSymlinks, +) -> io::Result { + let mut symlink_count = 0; + let mut canonical_path = PathBuf::new(); + let start = MaybeOwnedFile::borrowed(start); + + match internal_open( + start, + path, + canonicalize_options().follow(follow), + &mut symlink_count, + Some(&mut canonical_path), + ) { + // If the open succeeded, we got our path. + Ok(_) => (), + + // If it failed due to an invalid argument or filename, report it. + Err(err) if err.kind() == io::ErrorKind::InvalidInput => { + return Err(err); + } + #[cfg(io_error_more)] + Err(err) if err.kind() == io::ErrorKind::InvalidFilename => { + return Err(err); + } + #[cfg(windows)] + Err(err) + if err.raw_os_error() + == Some(windows_sys::Win32::Foundation::ERROR_INVALID_NAME as _) + || err.raw_os_error() + == Some(windows_sys::Win32::Foundation::ERROR_DIRECTORY as _) => + { + return Err(err); + } + + // For any other error, like permission denied, it's ok as long as + // we got our path. + Err(err) => { + if canonical_path.as_os_str().is_empty() { + return Err(err); + } + } + } + + Ok(canonical_path) +} diff --git a/crates/wasi/src/filesystem/primitives/manually/cow_component.rs b/crates/wasi/src/filesystem/primitives/manually/cow_component.rs new file mode 100644 index 000000000000..0cf454ffd222 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/manually/cow_component.rs @@ -0,0 +1,44 @@ +use std::borrow::Cow; +use std::ffi::OsStr; +use std::path::Component; + +/// Like `std::path::Component` except we combine `Prefix` and `RootDir` since +/// we don't support absolute paths, and `Normal` has a `Cow` instead of a +/// plain `OsStr` reference, so it can optionally own its own string. +pub(super) enum CowComponent<'borrow> { + PrefixOrRootDir, + CurDir, + ParentDir, + Normal(Cow<'borrow, OsStr>), +} + +impl<'borrow> CowComponent<'borrow> { + /// Convert a `Component` into a `CowComponent` which borrows strings. + pub(super) fn borrowed(component: Component<'borrow>) -> Self { + match component { + Component::Prefix(_) | Component::RootDir => Self::PrefixOrRootDir, + Component::CurDir => Self::CurDir, + Component::ParentDir => Self::ParentDir, + Component::Normal(os_str) => Self::Normal(os_str.into()), + } + } + + /// Convert a `Component` into a `CowComponent` which owns strings. + pub(super) fn owned(component: Component) -> Self { + match component { + Component::Prefix(_) | Component::RootDir => Self::PrefixOrRootDir, + Component::CurDir => Self::CurDir, + Component::ParentDir => Self::ParentDir, + Component::Normal(os_str) => Self::Normal(os_str.to_os_string().into()), + } + } + + /// Test whether `self` is `Component::Normal`. + #[cfg(windows)] + pub(super) fn is_normal(&self) -> bool { + match self { + CowComponent::Normal(_) => true, + _ => false, + } + } +} diff --git a/crates/wasi/src/filesystem/primitives/manually/mod.rs b/crates/wasi/src/filesystem/primitives/manually/mod.rs new file mode 100644 index 000000000000..34da44f924de --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/manually/mod.rs @@ -0,0 +1,23 @@ +//! Functions that perform path lookup manually, one component +//! at a time, with manual symlink resolution. + +mod canonical_path; +mod canonicalize; +mod cow_component; +mod open; +#[cfg(not(any(windows, target_os = "freebsd")))] +mod open_entry; +mod read_link_one; + +use canonical_path::CanonicalPath; +use cow_component::CowComponent; +use open::internal_open; +use read_link_one::read_link_one; + +#[cfg(racy_asserts)] +pub(super) use canonicalize::canonicalize_with; + +pub(crate) use canonicalize::canonicalize; +pub(crate) use open::{open, stat}; +#[cfg(not(any(windows, target_os = "freebsd")))] +pub(crate) use open_entry::open_entry; diff --git a/crates/wasi/src/filesystem/primitives/manually/open.rs b/crates/wasi/src/filesystem/primitives/manually/open.rs new file mode 100644 index 000000000000..83129071a212 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/manually/open.rs @@ -0,0 +1,561 @@ +//! Manual path resolution, one component at a time, with manual symlink +//! resolution, in order to enforce sandboxing. + +use super::{read_link_one, CanonicalPath, CowComponent}; +use crate::fs::{ + dir_options, errors, open_unchecked, path_has_trailing_dot, path_has_trailing_slash, + stat_unchecked, FollowSymlinks, MaybeOwnedFile, Metadata, OpenOptions, OpenUncheckedError, +}; +#[cfg(any(target_os = "android", target_os = "linux", target_os = "freebsd"))] +use rustix::fs::OFlags; +use std::ffi::OsStr; +use std::path::{Component, Path, PathBuf}; +use std::{fs, io, mem}; +#[cfg(windows)] +use { + crate::fs::{open_dir_unchecked, path_really_has_trailing_dot, SymlinkKind}, + windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_DIRECTORY, +}; + +/// Implement `open` by breaking up the path into components, resolving each +/// component individually, and resolving symbolic links manually. +pub(crate) fn open(start: &fs::File, path: &Path, options: &OpenOptions) -> io::Result { + let mut symlink_count = 0; + let start = MaybeOwnedFile::borrowed(start); + let maybe_owned = internal_open(start, path, options, &mut symlink_count, None)?; + maybe_owned.into_file(options) +} + +/// Context for performing manual component-at-a-time path resolution. +struct Context<'start> { + /// The current base directory handle for path lookups. + base: MaybeOwnedFile<'start>, + + /// The stack of directory handles below the base. + dirs: Vec>, + + /// The current worklist stack of path components to process. + components: Vec>, + + /// If requested, the canonical path is constructed here. + canonical_path: CanonicalPath<'start>, + + /// Does the path end in `/` or similar, so it requires a directory? + dir_required: bool, + + /// Are we requesting write permissions, so we can't open a directory? + dir_precluded: bool, + + /// Where there a trailing slash on the path? + trailing_slash: bool, + + /// If a path ends in `.`, `..`, or `/`, including after expanding + /// symlinks, we need to follow path resolution by opening `.` so that we + /// obtain a full `dir_options` file descriptor and confirm that we have + /// search rights in the last component. + follow_with_dot: bool, + + /// A `PathBuf` that we reuse for calling `read_link_one` to minimize + /// allocations. + reuse: PathBuf, + + #[cfg(racy_asserts)] + start_clone: MaybeOwnedFile<'start>, +} + +impl<'start> Context<'start> { + /// Construct a new instance of `Self`. + fn new( + start: MaybeOwnedFile<'start>, + path: &'start Path, + _options: &OpenOptions, + canonical_path: Option<&'start mut PathBuf>, + ) -> Self { + let trailing_slash = path_has_trailing_slash(path); + let trailing_dot = path_has_trailing_dot(path); + let trailing_dotdot = path.ends_with(Component::ParentDir); + + let mut components: Vec = Vec::new(); + + #[cfg(windows)] + { + // Windows resolves `..` before doing filesystem lookups. + for component in path.components().map(CowComponent::borrowed) { + match component { + CowComponent::ParentDir + if !components.is_empty() && components.last().unwrap().is_normal() => + { + let _ = components.pop(); + } + _ => components.push(component), + } + } + components.reverse(); + } + + #[cfg(not(windows))] + { + // Add the path components to the worklist. Rust's `Path` + // normalizes away `.` components, however a trailing `.` affects + // path lookup, so special-case it here. + if trailing_dot { + components.push(CowComponent::CurDir); + } + components.extend(path.components().rev().map(CowComponent::borrowed)); + } + + #[cfg(racy_asserts)] + let start_clone = MaybeOwnedFile::owned(start.try_clone().unwrap()); + + Self { + base: start, + dirs: Vec::with_capacity(components.len()), + components, + canonical_path: CanonicalPath::new(canonical_path), + dir_required: trailing_slash, + + #[cfg(not(windows))] + dir_precluded: _options.write || _options.append, + + #[cfg(windows)] + dir_precluded: false, + + trailing_slash, + + follow_with_dot: trailing_dot | trailing_dotdot, + + reuse: PathBuf::new(), + + #[cfg(racy_asserts)] + start_clone, + } + } + + fn check_dot_access(&self) -> io::Result<()> { + // Manually check that we have permissions to search `self.base` to + // search for `.` in it, since we otherwise resolve `.` and `..` + // ourselves by just manipulating the `dirs` stack. + #[cfg(not(windows))] + { + // Use `faccess` with `AT_EACCESS`. `AT_EACCESS` is not often the + // right tool for the job; in POSIX, it's better to ask for errno + // than to ask for permission. But we use `check_dot_access` to + // check access for opening `.` and `..` in situations where we + // already have open handles to them, and now we're accessing them + // through different paths, and we need to check whether these + // paths allow us access. + // + // Android and Emscripten lack `AT_EACCESS`. + // + #[cfg(any(target_os = "emscripten", target_os = "android"))] + let at_flags = rustix::fs::AtFlags::empty(); + #[cfg(not(any(target_os = "emscripten", target_os = "android")))] + let at_flags = rustix::fs::AtFlags::EACCESS; + + // Always use `CurDir`, even though this code is used to check + // permissions for both `.` and `..`, because in both cases we + // already know we can access the referenced directory, and we + // just need to check for the ability to search for `.` or `..` + // within `self.base`, which should always be the same. And + // using `.` means we avoid asking the OS to access a `..` path + // for us. + Ok(rustix::fs::accessat( + &*self.base, + Component::CurDir.as_os_str(), + rustix::fs::Access::EXEC_OK, + at_flags, + )?) + } + #[cfg(windows)] + open_dir_unchecked(&self.base, Component::CurDir.as_ref()).map(|_| ()) + } + + /// Handle a "." path component. + fn cur_dir(&mut self) -> io::Result<()> { + // This is a no-op. If this occurs at the end of the path, it does + // imply that we need search access to the directory, and it requires + // we open a directory, however we'll handle that in the + // `follow_with_dot` check. + Ok(()) + } + + /// Handle a ".." path component. + fn parent_dir(&mut self) -> io::Result<()> { + #[cfg(racy_asserts)] + if !self.dirs.is_empty() { + assert_different_file!(&self.start_clone, &self.base); + } + + // We hold onto all the parent directory descriptors so that we + // don't have to re-open anything when we encounter a `..`. This + // way, even if the directory is concurrently moved, we don't have + // to worry about `..` leaving the sandbox. + match self.dirs.pop() { + Some(dir) => { + // Check that we have permission to look up `..`. + self.check_dot_access()?; + + // Looks good. + self.base = dir; + } + None => return Err(errors::escape_attempt()), + } + assert!(self.canonical_path.pop()); + + Ok(()) + } + + /// Handle a "normal" path component. + fn normal( + &mut self, + one: &OsStr, + options: &OpenOptions, + symlink_count: &mut u8, + ) -> io::Result<()> { + // If there are more named components left, this will be a base + // directory from which to open subsequent components, so use "path" + // options (`O_PATH` on Linux). + let use_options = if self.components.is_empty() { + options.clone() + } else { + dir_options() + }; + + let dir_required = self.dir_required || use_options.dir_required; + + #[allow(clippy::redundant_clone)] + match open_unchecked( + &self.base, + one.as_ref(), + use_options + .clone() + .follow(FollowSymlinks::No) + .dir_required(dir_required), + ) { + Ok(file) => { + // Emulate `O_PATH` + `FollowSymlinks::Yes` on Linux. If `file` + // is a symlink, follow it. + #[cfg(any(target_os = "android", target_os = "linux", target_os = "freebsd"))] + if should_emulate_o_path(&use_options) { + match read_link_one( + &file, + Default::default(), + symlink_count, + mem::take(&mut self.reuse), + ) { + Ok(destination) => { + return self.push_symlink_destination(destination); + } + // If it isn't a symlink, handle it as normal. + // `readlinkat` returns `ENOENT` if the file isn't a + // symlink in this situation. + Err(err) if err.kind() == io::ErrorKind::NotFound => (), + // If `readlinkat` fails any other way, pass it on. + Err(err) => return Err(err), + } + } + + // Normal case + let prev_base = self.base.descend_to(MaybeOwnedFile::owned(file)); + self.dirs.push(prev_base); + self.canonical_path.push(one); + + Ok(()) + } + #[cfg(not(windows))] + Err(OpenUncheckedError::Symlink(err, ())) => { + self.maybe_last_component_symlink(one, symlink_count, options.follow, err) + } + #[cfg(windows)] + Err(OpenUncheckedError::Symlink(err, SymlinkKind::Dir)) => { + // If this is a Windows directory symlink, require a directory. + self.dir_required |= self.components.is_empty(); + self.maybe_last_component_symlink(one, symlink_count, options.follow, err) + } + #[cfg(windows)] + Err(OpenUncheckedError::Symlink(err, SymlinkKind::File)) => { + // If this is a Windows file symlink, preclude a directory. + self.dir_precluded = true; + self.maybe_last_component_symlink(one, symlink_count, options.follow, err) + } + Err(OpenUncheckedError::NotFound(err)) => Err(err), + Err(OpenUncheckedError::Other(err)) => { + // An error occurred. If this was the last component, and the + // error wasn't due to invalid inputs (eg. the path has an + // embedded NUL), record it as the last component of the + // canonical path, even if we couldn't open it. + if self.components.is_empty() && err.kind() != io::ErrorKind::InvalidInput { + self.canonical_path.push(one); + self.canonical_path.complete(); + } + Err(err) + } + } + } + + /// Dereference one symlink level. + fn symlink(&mut self, one: &OsStr, symlink_count: &mut u8) -> io::Result<()> { + let destination = + read_link_one(&self.base, one, symlink_count, mem::take(&mut self.reuse))?; + self.push_symlink_destination(destination) + } + + /// Push the components of `destination` onto the worklist stack. + fn push_symlink_destination(&mut self, destination: PathBuf) -> io::Result<()> { + let at_end = self.components.is_empty(); + let trailing_slash = path_has_trailing_slash(&destination); + let trailing_dot = path_has_trailing_dot(&destination); + let trailing_dotdot = destination.ends_with(Component::ParentDir); + + #[cfg(windows)] + { + // `path_has_trailing_dot` returns false so that we don't open `.` + // at the end of path resolution. But for determining the Windows + // symlink restrictions, we need to know whether the path really + // ends in a `.`. + let trailing_dot_really = path_really_has_trailing_dot(&destination); + + // Windows appears to disallow symlinks to paths with trailing + // slashes, slashdots, or slashdotdots. + if trailing_slash + || (trailing_dot_really && destination.as_os_str() != Component::CurDir.as_os_str()) + || (trailing_dotdot && destination.as_os_str() != Component::ParentDir.as_os_str()) + { + return Err(io::Error::from_raw_os_error(123)); + } + + // Windows resolves `..` before doing filesystem lookups. + let mut components: Vec = Vec::new(); + for component in destination.components().map(CowComponent::owned) { + match component { + CowComponent::ParentDir + if !components.is_empty() && components.last().unwrap().is_normal() => + { + let _ = components.pop(); + } + _ => components.push(component), + } + } + self.components.extend(components.into_iter().rev()); + } + + #[cfg(not(windows))] + { + // Rust's `Path` hides a trailing dot, so handle it manually. + if trailing_dot { + self.components.push(CowComponent::CurDir); + } + self.components + .extend(destination.components().rev().map(CowComponent::owned)); + } + + // Record whether the new components ended with a path that implies + // an open of `.` at the end of path resolution. + if at_end { + self.follow_with_dot |= trailing_dot | trailing_dotdot; + self.trailing_slash |= trailing_slash; + self.dir_required |= trailing_slash; + } + + // As an optimization, hold onto the `PathBuf` buffer for later reuse. + self.reuse = destination; + + Ok(()) + } + + /// Check whether this is the last component and we don't need + /// to dereference; otherwise call `Self::symlink`. + fn maybe_last_component_symlink( + &mut self, + one: &OsStr, + symlink_count: &mut u8, + follow: FollowSymlinks, + err: io::Error, + ) -> io::Result<()> { + if follow == FollowSymlinks::No && !self.trailing_slash && self.components.is_empty() { + self.canonical_path.push(one); + self.canonical_path.complete(); + return Err(err); + } + + self.symlink(one, symlink_count) + } +} + +/// Internal implementation of manual `open`, exposing some additional +/// parameters. +/// +/// Callers can request the canonical path by passing `Some` to +/// `canonical_path`. If the complete canonical path is processed, it will be +/// stored in the provided `&mut PathBuf`, even if the actual open fails. If +/// a failure occurs before the complete canonical path is processed, the +/// provided `&mut PathBuf` is cleared to empty. +/// +/// A note on lifetimes: `path` and `canonical_path` here don't strictly +/// need `'start`, but using them makes it easier to store them in the +/// `Context` struct. +pub(super) fn internal_open<'start>( + start: MaybeOwnedFile<'start>, + path: &'start Path, + options: &OpenOptions, + symlink_count: &mut u8, + canonical_path: Option<&'start mut PathBuf>, +) -> io::Result> { + // POSIX returns `ENOENT` on an empty path. TODO: On Windows, we should + // be compatible with what Windows does instead. + if path.as_os_str().is_empty() { + return Err(errors::no_such_file_or_directory()); + } + + let mut ctx = Context::new(start, path, options, canonical_path); + + while let Some(c) = ctx.components.pop() { + match c { + CowComponent::PrefixOrRootDir => return Err(errors::escape_attempt()), + CowComponent::CurDir => ctx.cur_dir()?, + CowComponent::ParentDir => ctx.parent_dir()?, + CowComponent::Normal(one) => ctx.normal(&one, options, symlink_count)?, + } + } + + // We've now finished all the path components other than any trailing `.`s, + // so we have the complete canonical path. + ctx.canonical_path.complete(); + + // If the path ended in `.` (explicit or implied) or `..`, we may have + // opened the directory with eg. `O_PATH` on Linux, or we may have skipped + // checking for search access to `.`, so re-open it. + if ctx.follow_with_dot { + if ctx.dir_precluded { + return Err(errors::is_directory()); + } + ctx.base = MaybeOwnedFile::owned(open_unchecked( + &ctx.base, + Component::CurDir.as_ref(), + options, + )?); + } + + #[cfg(racy_asserts)] + check_internal_open(&ctx, path, options); + + Ok(ctx.base) +} + +/// Implement manual `stat` in a similar manner as manual `open`. +pub(crate) fn stat(start: &fs::File, path: &Path, follow: FollowSymlinks) -> io::Result { + // POSIX returns `ENOENT` on an empty path. TODO: On Windows, we should + // be compatible with what Windows does instead. + if path.as_os_str().is_empty() { + return Err(errors::no_such_file_or_directory()); + } + + let mut options = OpenOptions::new(); + options.follow(follow); + let mut symlink_count = 0; + let mut ctx = Context::new(MaybeOwnedFile::borrowed(start), path, &options, None); + assert!(!ctx.dir_precluded); + + while let Some(c) = ctx.components.pop() { + match c { + CowComponent::PrefixOrRootDir => return Err(errors::escape_attempt()), + CowComponent::CurDir => ctx.cur_dir()?, + CowComponent::ParentDir => ctx.parent_dir()?, + CowComponent::Normal(one) => { + if ctx.components.is_empty() { + // If this is the last component, do a non-following + // `stat_unchecked` on it. + let stat = stat_unchecked(&ctx.base, one.as_ref(), FollowSymlinks::No)?; + + // If we weren't asked to follow symlinks, or it wasn't a + // symlink, we're done. + if options.follow == FollowSymlinks::No || !stat.file_type().is_symlink() { + if stat.is_dir() { + if ctx.dir_precluded { + return Err(errors::is_directory()); + } + } else if ctx.dir_required { + return Err(errors::is_not_directory()); + } + return Ok(stat); + } + + // On Windows, symlinks know whether they are a file or + // directory. + #[cfg(windows)] + if stat.file_attributes() & FILE_ATTRIBUTE_DIRECTORY != 0 { + ctx.dir_required = true; + } else { + ctx.dir_precluded = true; + } + + // If it was a symlink and we're asked to follow symlinks, + // dereference it. + ctx.symlink(&one, &mut symlink_count)? + } else { + // Otherwise open the path component normally. + ctx.normal(&one, &options, &mut symlink_count)? + } + } + } + } + + // If the path ended in `.` (explicit or implied) or `..`, we may have + // opened the directory with eg. `O_PATH` on Linux, or we may have skipped + // checking for search access to `.`, so re-check it. + if ctx.follow_with_dot { + if ctx.dir_precluded { + return Err(errors::is_directory()); + } + + ctx.check_dot_access()?; + } + + // If the path ended in `.` or `..`, we already have it open, so just do + // `.metadata()` on it. + Metadata::from_file(&ctx.base) +} + +/// Test whether the given options imply that we should treat an open file as +/// potentially being a symlink we need to follow, due to use of `O_PATH`. +#[cfg(any(target_os = "android", target_os = "linux", target_os = "freebsd"))] +fn should_emulate_o_path(use_options: &OpenOptions) -> bool { + (use_options.ext.custom_flags & (OFlags::PATH.bits() as i32)) == (OFlags::PATH.bits() as i32) + && use_options.follow == FollowSymlinks::Yes +} + +#[cfg(racy_asserts)] +fn check_internal_open(ctx: &Context, path: &Path, options: &OpenOptions) { + match open_unchecked( + &ctx.start_clone, + ctx.canonical_path.debug.as_ref(), + options + .clone() + .create(false) + .create_new(false) + .truncate(false), + ) { + Ok(unchecked_file) => { + assert_same_file!( + &ctx.base, + &unchecked_file, + "path resolution inconsistency: start='{:?}', path='{}'; canonical_path='{}'", + ctx.start_clone, + path.display(), + ctx.canonical_path.debug.display(), + ); + } + Err(_unchecked_error) => { + /* TODO: Check error messages. + panic!( + "unexpected success opening result={:?} start='{:?}', path='{}'; canonical_path='{}'; \ + expected {:?}", + ctx.base, + ctx.start_clone, + path.display(), + ctx.canonical_path.debug.display(), + unchecked_error, + */ + } + } +} diff --git a/crates/wasi/src/filesystem/primitives/manually/open_entry.rs b/crates/wasi/src/filesystem/primitives/manually/open_entry.rs new file mode 100644 index 000000000000..efea4c41cbc2 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/manually/open_entry.rs @@ -0,0 +1,29 @@ +use super::{internal_open, read_link_one}; +use crate::fs::{open_unchecked, FollowSymlinks, MaybeOwnedFile, OpenOptions, OpenUncheckedError}; +use std::ffi::OsStr; +use std::path::PathBuf; +use std::{fs, io}; + +pub(crate) fn open_entry( + start: &fs::File, + path: &OsStr, + options: &OpenOptions, +) -> io::Result { + match open_unchecked( + start, + path.as_ref(), + options.clone().follow(FollowSymlinks::No), + ) { + Ok(file) => Ok(file), + Err(OpenUncheckedError::Symlink(_, _)) if options.follow == FollowSymlinks::Yes => { + let mut symlink_count = 0; + let destination = read_link_one(start, path, &mut symlink_count, PathBuf::new())?; + let maybe = MaybeOwnedFile::borrowed(start); + internal_open(maybe, &destination, options, &mut symlink_count, None) + .map(MaybeOwnedFile::unwrap_owned) + } + Err(OpenUncheckedError::NotFound(err)) + | Err(OpenUncheckedError::Other(err)) + | Err(OpenUncheckedError::Symlink(err, _)) => Err(err), + } +} diff --git a/crates/wasi/src/filesystem/primitives/manually/read_link_one.rs b/crates/wasi/src/filesystem/primitives/manually/read_link_one.rs new file mode 100644 index 000000000000..9c7c6f9204cb --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/manually/read_link_one.rs @@ -0,0 +1,36 @@ +use crate::fs::{errors, read_link_unchecked, MAX_SYMLINK_EXPANSIONS}; +use std::ffi::OsStr; +use std::path::{Path, PathBuf}; +use std::{fs, io}; + +/// This is a wrapper around `read_link_unchecked` which performs a single +/// symlink expansion on a single path component, and which enforces the +/// recursion limit. +pub(super) fn read_link_one( + base: &fs::File, + name: &OsStr, + symlink_count: &mut u8, + reuse: PathBuf, +) -> io::Result { + let name: &Path = name.as_ref(); + assert!( + name.as_os_str().is_empty() || name.file_name().is_some(), + "read_link_one expects a single normal path component, got '{}'", + name.display() + ); + assert!( + name.as_os_str().is_empty() || name.parent().unwrap().as_os_str().is_empty(), + "read_link_one expects a single normal path component, got '{}'", + name.display() + ); + + if *symlink_count == MAX_SYMLINK_EXPANSIONS { + return Err(errors::too_many_symlinks()); + } + + let destination = read_link_unchecked(base, name, reuse)?; + + *symlink_count += 1; + + Ok(destination) +} diff --git a/crates/wasi/src/filesystem/primitives/maybe_owned_file.rs b/crates/wasi/src/filesystem/primitives/maybe_owned_file.rs new file mode 100644 index 000000000000..443cba2437db --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/maybe_owned_file.rs @@ -0,0 +1,145 @@ +use crate::fs::{open_unchecked, OpenOptions}; +use maybe_owned::MaybeOwned; +use std::ops::Deref; +use std::path::Component; +use std::{fmt, fs, io, mem}; +#[cfg(racy_asserts)] +use {crate::fs::file_path, std::path::PathBuf}; + +/// Several places in the code need to be able to handle either owned or +/// borrowed [`std::fs::File]`s. Cloning a `File` to let them always have an +/// owned `File` is expensive and fallible, so use this `struct` to hold either +/// one, and implement [`Deref`] to allow them to be handled in a uniform way. +/// +/// This is similar to [`Cow`], except without the copy-on-write part ;-). +/// `Cow` requires a `Clone` implementation, which `File` doesn't have, and +/// most users of this type don't need copy-on-write behavior. +/// +/// And, this type has the special `descend_to`, which just does an assignment, +/// but also some useful assertion checks. +/// +/// [`Deref`]: std::ops::Deref +/// [`Cow`]: std::borrow::Cow +pub(super) struct MaybeOwnedFile<'borrow> { + inner: MaybeOwned<'borrow, fs::File>, + + #[cfg(racy_asserts)] + path: Option, +} + +impl<'borrow> MaybeOwnedFile<'borrow> { + /// Constructs a new `MaybeOwnedFile` which is not owned. + pub(super) fn borrowed(file: &'borrow fs::File) -> Self { + #[cfg(racy_asserts)] + let path = file_path(file); + + Self { + inner: MaybeOwned::Borrowed(file), + + #[cfg(racy_asserts)] + path, + } + } + + /// Constructs a new `MaybeOwnedFile` which is owned. + pub(super) fn owned(file: fs::File) -> Self { + #[cfg(racy_asserts)] + let path = file_path(&file); + + Self { + inner: MaybeOwned::Owned(file), + + #[cfg(racy_asserts)] + path, + } + } + + /// Like `borrowed` but does not do path checks. + #[allow(dead_code)] + pub(super) const fn borrowed_noassert(file: &'borrow fs::File) -> Self { + Self { + inner: MaybeOwned::Borrowed(file), + + #[cfg(racy_asserts)] + path: None, + } + } + + /// Like `owned` but does not do path checks. + #[allow(dead_code)] + pub(super) const fn owned_noassert(file: fs::File) -> Self { + Self { + inner: MaybeOwned::Owned(file), + + #[cfg(racy_asserts)] + path: None, + } + } + + /// Set this `MaybeOwnedFile` to a new owned file which is from a subtree + /// of the current file. Return a `MaybeOwnedFile` representing the + /// previous state. + pub(super) fn descend_to(&mut self, to: MaybeOwnedFile<'borrow>) -> Self { + #[cfg(racy_asserts)] + let path = self.path.clone(); + + #[cfg(racy_asserts)] + if let Some(to_path) = file_path(&to) { + if let Some(current_path) = &self.path { + assert!( + to_path.starts_with(current_path), + "attempted to descend from {:?} to {:?}", + to_path.display(), + current_path.display() + ); + } + self.path = Some(to_path); + } + + Self { + inner: mem::replace(&mut self.inner, to.inner), + + #[cfg(racy_asserts)] + path, + } + } + + /// Produce an owned `File`. This uses `open` on "." if needed to convert a + /// borrowed `File` to an owned one. + #[cfg_attr(windows, allow(dead_code))] + pub(super) fn into_file(self, options: &OpenOptions) -> io::Result { + match self.inner { + MaybeOwned::Owned(file) => Ok(file), + MaybeOwned::Borrowed(file) => { + // The only situation in which we'd be asked to produce an owned + // `File` is when there's a need to open "." within a directory + // to obtain a new handle. + open_unchecked(file, Component::CurDir.as_ref(), options).map_err(Into::into) + } + } + } + + /// Assuming `self` holds an owned `File`, return it. + #[cfg_attr(any(windows, target_os = "freebsd"), allow(dead_code))] + pub(super) fn unwrap_owned(self) -> fs::File { + match self.inner { + MaybeOwned::Owned(file) => file, + MaybeOwned::Borrowed(_) => panic!("expected owned file"), + } + } +} + +impl<'borrow> Deref for MaybeOwnedFile<'borrow> { + type Target = fs::File; + + #[inline] + fn deref(&self) -> &Self::Target { + self.inner.as_ref() + } +} + +impl<'borrow> fmt::Debug for MaybeOwnedFile<'borrow> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.deref().fmt(f) + } +} diff --git a/crates/wasi/src/filesystem/primitives/metadata.rs b/crates/wasi/src/filesystem/primitives/metadata.rs new file mode 100644 index 000000000000..934983739ae4 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/metadata.rs @@ -0,0 +1,523 @@ +use crate::fs::{FileType, ImplFileTypeExt, ImplMetadataExt, Permissions}; +use crate::time::SystemTime; +use std::{fs, io}; + +/// Metadata information about a file. +/// +/// This corresponds to [`std::fs::Metadata`]. +/// +///
+/// We need to define our own version because the libstd `Metadata` doesn't +/// have a public constructor that we can use. +///
+#[derive(Debug, Clone)] +pub struct Metadata { + pub(crate) file_type: FileType, + pub(crate) len: u64, + pub(crate) permissions: Permissions, + pub(crate) modified: Option, + pub(crate) accessed: Option, + pub(crate) created: Option, + pub(crate) ext: ImplMetadataExt, +} + +#[allow(clippy::len_without_is_empty)] +impl Metadata { + /// Constructs a new instance of `Self` from the given [`std::fs::File`]. + #[inline] + pub fn from_file(file: &fs::File) -> io::Result { + let std = file.metadata()?; + let ext = ImplMetadataExt::from(file, &std)?; + let file_type = ImplFileTypeExt::from(file, &std)?; + Ok(Self::from_parts(std, ext, file_type)) + } + + /// Constructs a new instance of `Self` from the given + /// [`std::fs::Metadata`]. + /// + /// As with the comments in [`std::fs::Metadata::volume_serial_number`] and + /// nearby functions, some fields of the resulting metadata will be `None`. + /// + /// [`std::fs::Metadata::volume_serial_number`]: https://doc.rust-lang.org/std/os/windows/fs/trait.MetadataExt.html#tymethod.volume_serial_number + #[inline] + pub fn from_just_metadata(std: fs::Metadata) -> Self { + let ext = ImplMetadataExt::from_just_metadata(&std); + let file_type = ImplFileTypeExt::from_just_metadata(&std); + Self::from_parts(std, ext, file_type) + } + + #[inline] + fn from_parts(std: fs::Metadata, ext: ImplMetadataExt, file_type: FileType) -> Self { + Self { + file_type, + len: std.len(), + permissions: Permissions::from_std(std.permissions()), + modified: std.modified().ok().map(SystemTime::from_std), + accessed: std.accessed().ok().map(SystemTime::from_std), + created: std.created().ok().map(SystemTime::from_std), + ext, + } + } + + /// Returns the file type for this metadata. + /// + /// This corresponds to [`std::fs::Metadata::file_type`]. + #[inline] + pub const fn file_type(&self) -> FileType { + self.file_type + } + + /// Returns `true` if this metadata is for a directory. + /// + /// This corresponds to [`std::fs::Metadata::is_dir`]. + #[inline] + pub fn is_dir(&self) -> bool { + self.file_type.is_dir() + } + + /// Returns `true` if this metadata is for a regular file. + /// + /// This corresponds to [`std::fs::Metadata::is_file`]. + #[inline] + pub fn is_file(&self) -> bool { + self.file_type.is_file() + } + + /// Returns `true` if this metadata is for a symbolic link. + /// + /// This corresponds to [`std::fs::Metadata::is_symlink`]. + #[inline] + pub fn is_symlink(&self) -> bool { + self.file_type.is_symlink() + } + + /// Returns the size of the file, in bytes, this metadata is for. + /// + /// This corresponds to [`std::fs::Metadata::len`]. + #[inline] + pub const fn len(&self) -> u64 { + self.len + } + + /// Returns the permissions of the file this metadata is for. + /// + /// This corresponds to [`std::fs::Metadata::permissions`]. + #[inline] + pub fn permissions(&self) -> Permissions { + self.permissions.clone() + } + + /// Returns the last modification time listed in this metadata. + /// + /// This corresponds to [`std::fs::Metadata::modified`]. + #[inline] + pub fn modified(&self) -> io::Result { + #[cfg(io_error_uncategorized)] + { + self.modified.ok_or_else(|| { + io::Error::new( + io::ErrorKind::Unsupported, + "modified time metadata not available on this platform", + ) + }) + } + #[cfg(not(io_error_uncategorized))] + { + self.modified.ok_or_else(|| { + io::Error::new( + io::ErrorKind::Other, + "modified time metadata not available on this platform", + ) + }) + } + } + + /// Returns the last access time of this metadata. + /// + /// This corresponds to [`std::fs::Metadata::accessed`]. + #[inline] + pub fn accessed(&self) -> io::Result { + #[cfg(io_error_uncategorized)] + { + self.accessed.ok_or_else(|| { + io::Error::new( + io::ErrorKind::Unsupported, + "accessed time metadata not available on this platform", + ) + }) + } + #[cfg(not(io_error_uncategorized))] + { + self.accessed.ok_or_else(|| { + io::Error::new( + io::ErrorKind::Other, + "accessed time metadata not available on this platform", + ) + }) + } + } + + /// Returns the creation time listed in this metadata. + /// + /// This corresponds to [`std::fs::Metadata::created`]. + #[inline] + pub fn created(&self) -> io::Result { + #[cfg(io_error_uncategorized)] + { + self.created.ok_or_else(|| { + io::Error::new( + io::ErrorKind::Unsupported, + "created time metadata not available on this platform", + ) + }) + } + #[cfg(not(io_error_uncategorized))] + { + self.created.ok_or_else(|| { + io::Error::new( + io::ErrorKind::Other, + "created time metadata not available on this platform", + ) + }) + } + } + + /// Determine if `self` and `other` refer to the same inode on the same + /// device. + #[cfg(any(not(windows), windows_by_handle))] + pub(crate) fn is_same_file(&self, other: &Self) -> bool { + self.ext.is_same_file(&other.ext) + } + + /// `MetadataExt` requires nightly to be implemented, but we sometimes + /// just need the file attributes. + #[cfg(windows)] + #[inline] + pub(crate) fn file_attributes(&self) -> u32 { + self.ext.file_attributes() + } +} + +/// Unix-specific extensions for [`MetadataExt`]. +/// +/// This corresponds to [`std::os::unix::fs::MetadataExt`]. +#[cfg(any(unix, target_os = "vxworks"))] +pub trait MetadataExt { + /// Returns the ID of the device containing the file. + fn dev(&self) -> u64; + /// Returns the inode number. + fn ino(&self) -> u64; + /// Returns the rights applied to this file. + fn mode(&self) -> u32; + /// Returns the number of hard links pointing to this file. + fn nlink(&self) -> u64; + /// Returns the user ID of the owner of this file. + fn uid(&self) -> u32; + /// Returns the group ID of the owner of this file. + fn gid(&self) -> u32; + /// Returns the device ID of this file (if it is a special one). + fn rdev(&self) -> u64; + /// Returns the total size of this file in bytes. + fn size(&self) -> u64; + /// Returns the last access time of the file, in seconds since Unix Epoch. + fn atime(&self) -> i64; + /// Returns the last access time of the file, in nanoseconds since [`atime`]. + fn atime_nsec(&self) -> i64; + /// Returns the last modification time of the file, in seconds since Unix Epoch. + fn mtime(&self) -> i64; + /// Returns the last modification time of the file, in nanoseconds since [`mtime`]. + fn mtime_nsec(&self) -> i64; + /// Returns the last status change time of the file, in seconds since Unix Epoch. + fn ctime(&self) -> i64; + /// Returns the last status change time of the file, in nanoseconds since [`ctime`]. + fn ctime_nsec(&self) -> i64; + /// Returns the block size for filesystem I/O. + fn blksize(&self) -> u64; + /// Returns the number of blocks allocated to the file, in 512-byte units. + fn blocks(&self) -> u64; + #[cfg(target_os = "vxworks")] + fn attrib(&self) -> u8; +} + +/// WASI-specific extensions for [`MetadataExt`]. +/// +/// This corresponds to [`std::os::wasi::fs::MetadataExt`]. +#[cfg(target_os = "wasi")] +pub trait MetadataExt { + /// Returns the ID of the device containing the file. + fn dev(&self) -> u64; + /// Returns the inode number. + fn ino(&self) -> u64; + /// Returns the number of hard links pointing to this file. + fn nlink(&self) -> u64; +} + +/// Windows-specific extensions to [`Metadata`]. +/// +/// This corresponds to [`std::os::windows::fs::MetadataExt`]. +#[cfg(windows)] +pub trait MetadataExt { + /// Returns the value of the `dwFileAttributes` field of this metadata. + fn file_attributes(&self) -> u32; + /// Returns the value of the `ftCreationTime` field of this metadata. + fn creation_time(&self) -> u64; + /// Returns the value of the `ftLastAccessTime` field of this metadata. + fn last_access_time(&self) -> u64; + /// Returns the value of the `ftLastWriteTime` field of this metadata. + fn last_write_time(&self) -> u64; + /// Returns the value of the `nFileSize{High,Low}` fields of this metadata. + fn file_size(&self) -> u64; + /// Returns the value of the `dwVolumeSerialNumber` field of this metadata. + #[cfg(windows_by_handle)] + fn volume_serial_number(&self) -> Option; + /// Returns the value of the `nNumberOfLinks` field of this metadata. + #[cfg(windows_by_handle)] + fn number_of_links(&self) -> Option; + /// Returns the value of the `nFileIndex{Low,High}` fields of this metadata. + #[cfg(windows_by_handle)] + fn file_index(&self) -> Option; +} + +#[cfg(unix)] +impl MetadataExt for Metadata { + #[inline] + fn dev(&self) -> u64 { + crate::fs::MetadataExt::dev(&self.ext) + } + + #[inline] + fn ino(&self) -> u64 { + crate::fs::MetadataExt::ino(&self.ext) + } + + #[inline] + fn mode(&self) -> u32 { + crate::fs::MetadataExt::mode(&self.ext) + } + + #[inline] + fn nlink(&self) -> u64 { + crate::fs::MetadataExt::nlink(&self.ext) + } + + #[inline] + fn uid(&self) -> u32 { + crate::fs::MetadataExt::uid(&self.ext) + } + + #[inline] + fn gid(&self) -> u32 { + crate::fs::MetadataExt::gid(&self.ext) + } + + #[inline] + fn rdev(&self) -> u64 { + crate::fs::MetadataExt::rdev(&self.ext) + } + + #[inline] + fn size(&self) -> u64 { + crate::fs::MetadataExt::size(&self.ext) + } + + #[inline] + fn atime(&self) -> i64 { + crate::fs::MetadataExt::atime(&self.ext) + } + + #[inline] + fn atime_nsec(&self) -> i64 { + crate::fs::MetadataExt::atime_nsec(&self.ext) + } + + #[inline] + fn mtime(&self) -> i64 { + crate::fs::MetadataExt::mtime(&self.ext) + } + + #[inline] + fn mtime_nsec(&self) -> i64 { + crate::fs::MetadataExt::mtime_nsec(&self.ext) + } + + #[inline] + fn ctime(&self) -> i64 { + crate::fs::MetadataExt::ctime(&self.ext) + } + + #[inline] + fn ctime_nsec(&self) -> i64 { + crate::fs::MetadataExt::ctime_nsec(&self.ext) + } + + #[inline] + fn blksize(&self) -> u64 { + crate::fs::MetadataExt::blksize(&self.ext) + } + + #[inline] + fn blocks(&self) -> u64 { + crate::fs::MetadataExt::blocks(&self.ext) + } +} + +#[cfg(target_os = "wasi")] +impl MetadataExt for Metadata { + #[inline] + fn dev(&self) -> u64 { + crate::fs::MetadataExt::dev(&self.ext) + } + + #[inline] + fn ino(&self) -> u64 { + crate::fs::MetadataExt::ino(&self.ext) + } + + #[inline] + fn nlink(&self) -> u64 { + crate::fs::MetadataExt::nlink(&self.ext) + } +} + +#[cfg(target_os = "vxworks")] +impl MetadataExt for Metadata { + #[inline] + fn dev(&self) -> u64 { + self.ext.dev() + } + + #[inline] + fn ino(&self) -> u64 { + self.ext.ino() + } + + #[inline] + fn mode(&self) -> u32 { + self.ext.mode() + } + + #[inline] + fn nlink(&self) -> u64 { + self.ext.nlink() + } + + #[inline] + fn uid(&self) -> u32 { + self.ext.uid() + } + + #[inline] + fn gid(&self) -> u32 { + self.ext.gid() + } + + #[inline] + fn rdev(&self) -> u64 { + self.ext.rdev() + } + + #[inline] + fn size(&self) -> u64 { + self.ext.size() + } + + #[inline] + fn atime(&self) -> i64 { + self.ext.atime() + } + + #[inline] + fn atime_nsec(&self) -> i64 { + self.ext.atime_nsec() + } + + #[inline] + fn mtime(&self) -> i64 { + self.ext.mtime() + } + + #[inline] + fn mtime_nsec(&self) -> i64 { + self.ext.mtime_nsec() + } + + #[inline] + fn ctime(&self) -> i64 { + self.ext.ctime() + } + + #[inline] + fn ctime_nsec(&self) -> i64 { + self.ext.ctime_nsec() + } + + #[inline] + fn blksize(&self) -> u64 { + self.ext.blksize() + } + + #[inline] + fn blocks(&self) -> u64 { + self.ext.blocks() + } +} + +#[cfg(windows)] +impl MetadataExt for Metadata { + #[inline] + fn file_attributes(&self) -> u32 { + self.ext.file_attributes() + } + + #[inline] + fn creation_time(&self) -> u64 { + self.ext.creation_time() + } + + #[inline] + fn last_access_time(&self) -> u64 { + self.ext.last_access_time() + } + + #[inline] + fn last_write_time(&self) -> u64 { + self.ext.last_write_time() + } + + #[inline] + fn file_size(&self) -> u64 { + self.ext.file_size() + } + + #[inline] + #[cfg(windows_by_handle)] + fn volume_serial_number(&self) -> Option { + self.ext.volume_serial_number() + } + + #[inline] + #[cfg(windows_by_handle)] + fn number_of_links(&self) -> Option { + self.ext.number_of_links() + } + + #[inline] + #[cfg(windows_by_handle)] + fn file_index(&self) -> Option { + self.ext.file_index() + } +} + +/// Extension trait to allow `volume_serial_number` etc. to be exposed by +/// the `cap-fs-ext` crate. +/// +/// This is hidden from the main API since this functionality isn't present in +/// `std`. Use `cap_fs_ext::MetadataExt` instead of calling this directly. +#[cfg(windows)] +#[doc(hidden)] +pub trait _WindowsByHandle { + fn file_attributes(&self) -> u32; + fn volume_serial_number(&self) -> Option; + fn number_of_links(&self) -> Option; + fn file_index(&self) -> Option; +} diff --git a/crates/wasi/src/filesystem/primitives/mod.rs b/crates/wasi/src/filesystem/primitives/mod.rs new file mode 100644 index 000000000000..2b77fab706f6 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/mod.rs @@ -0,0 +1,131 @@ +//! Filesystem utilities. + +#[cfg(racy_asserts)] +#[macro_use] +pub(crate) mod assert_same_file; + +mod access; +mod canonicalize; +mod copy; +mod create_dir; +mod dir_builder; +mod dir_entry; +mod dir_options; +mod file; +#[cfg(not(any(target_os = "android", target_os = "linux", windows)))] +mod file_path_by_searching; +mod file_type; +mod follow_symlinks; +mod hard_link; +mod is_file_read_write; +mod maybe_owned_file; +mod metadata; +mod open; +mod open_ambient; +mod open_dir; +mod open_options; +mod open_unchecked_error; +mod permissions; +mod read_dir; +mod read_link; +mod remove_dir; +mod remove_dir_all; +mod remove_file; +mod remove_open_dir; +mod rename; +mod reopen; +#[cfg(not(target_os = "wasi"))] +mod set_permissions; +mod set_times; +mod stat; +mod symlink; +mod system_time_spec; + +pub(crate) mod errors; +pub(crate) mod manually; +pub(crate) mod via_parent; + +use maybe_owned_file::MaybeOwnedFile; + +#[cfg(not(any(target_os = "android", target_os = "linux", windows)))] +pub(crate) use file_path_by_searching::file_path_by_searching; +pub(crate) use open_unchecked_error::*; + +#[cfg(not(windows))] +pub(crate) use super::rustix::fs::*; +#[cfg(windows)] +pub(crate) use super::windows::fs::*; + +#[cfg(not(windows))] +pub(crate) use read_dir::{read_dir_nofollow, read_dir_unchecked}; + +pub use access::{access, AccessModes, AccessType}; +pub use canonicalize::canonicalize; +pub use copy::copy; +pub use create_dir::create_dir; +pub use dir_builder::*; +#[cfg(windows)] +pub use dir_entry::_WindowsDirEntryExt; +pub use dir_entry::DirEntry; +pub use dir_options::DirOptions; +pub use file::FileExt; +#[cfg(windows)] +pub use file_type::_WindowsFileTypeExt; +pub use file_type::FileType; +#[cfg(any(unix, target_os = "vxworks", all(windows, windows_file_type_ext)))] +pub use file_type::FileTypeExt; +pub use follow_symlinks::FollowSymlinks; +pub use hard_link::hard_link; +pub use is_file_read_write::is_file_read_write; +#[cfg(windows)] +pub use metadata::_WindowsByHandle; +pub use metadata::{Metadata, MetadataExt}; +pub use open::open; +pub use open_ambient::open_ambient; +pub use open_dir::*; +pub use open_options::*; +pub use permissions::Permissions; +#[cfg(unix)] +pub use permissions::PermissionsExt; +pub use read_dir::{read_base_dir, read_dir, ReadDir}; +pub use read_link::{read_link, read_link_contents}; +pub use remove_dir::remove_dir; +pub use remove_dir_all::remove_dir_all; +pub use remove_file::remove_file; +pub use remove_open_dir::{remove_open_dir, remove_open_dir_all}; +pub use rename::rename; +pub use reopen::reopen; +#[cfg(not(target_os = "wasi"))] +pub use set_permissions::{set_permissions, set_symlink_permissions}; +pub use set_times::{set_times, set_times_nofollow}; +pub use stat::stat; +#[cfg(not(windows))] +pub use symlink::{symlink, symlink_contents}; +#[cfg(windows)] +pub use symlink::{symlink_dir, symlink_file}; +pub use system_time_spec::SystemTimeSpec; + +#[cfg(racy_asserts)] +fn map_result(result: &std::io::Result) -> Result { + match result { + Ok(t) => Ok(t.clone()), + Err(e) => Err((e.kind(), e.to_string())), + } +} + +/// Test that `file_path` works on a few miscellaneous directory paths. +#[test] +fn dir_paths() { + use crate::ambient_authority; + + for path in [std::env::current_dir().unwrap(), std::env::temp_dir()] { + let dir = open_ambient_dir(&path, ambient_authority()).unwrap(); + assert_eq!( + file_path(&dir) + .as_ref() + .map(std::fs::canonicalize) + .map(Result::unwrap), + Some(std::fs::canonicalize(path).unwrap()) + ); + } +} diff --git a/crates/wasi/src/filesystem/primitives/open.rs b/crates/wasi/src/filesystem/primitives/open.rs new file mode 100644 index 000000000000..e193a8f8650a --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/open.rs @@ -0,0 +1,108 @@ +//! This defines `open`, the primary entrypoint to sandboxed file and directory +//! opening. + +#[cfg(racy_asserts)] +use crate::fs::{file_path, open_unchecked, stat_unchecked, Metadata}; +use crate::fs::{open_impl, OpenOptions}; +use std::path::Path; +use std::{fs, io}; + +/// Perform an `openat`-like operation, 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))] +#[inline] +pub fn open(start: &fs::File, path: &Path, options: &OpenOptions) -> io::Result { + #[cfg(racy_asserts)] + let stat_before = stat_unchecked(start, path, options.follow); + + // Call the underlying implementation. + let result = open_impl(start, path, options); + + #[cfg(racy_asserts)] + let stat_after = stat_unchecked(start, path, options.follow); + + #[cfg(racy_asserts)] + check_open(start, path, options, &stat_before, &result, &stat_after); + + result +} + +#[cfg(racy_asserts)] +fn check_open( + start: &fs::File, + path: &Path, + options: &OpenOptions, + _stat_before: &io::Result, + result: &io::Result, + _stat_after: &io::Result, +) { + let unchecked_result = open_unchecked( + start, + path, + options + .clone() + .create(false) + .create_new(false) + .truncate(false), + ); + + match (&result, &unchecked_result) { + (Ok(result_file), Ok(unchecked_file)) => { + assert_same_file!( + &result_file, + &unchecked_file, + "path resolution inconsistency: start='{:?}', path='{}'", + start, + path.display(), + ); + } + (Ok(result_file), Err(unchecked_error)) => { + if unchecked_error.kind() == io::ErrorKind::PermissionDenied { + assert!(options.create || options.create_new); + } else { + panic!( + "unexpected success opening start='{:?}', path='{}'; expected {:?}; got {:?}", + start, + path.display(), + unchecked_error, + result_file + ); + } + } + (Err(result_error), Ok(_unchecked_file)) => match result_error.kind() { + io::ErrorKind::PermissionDenied | io::ErrorKind::InvalidInput => (), + io::ErrorKind::AlreadyExists if options.create_new => (), + _ => panic!( + "unexpected error opening start='{:?}', path='{}': {:?}", + start, + path.display(), + result_error + ), + }, + (Err(result_error), Err(_unchecked_error)) => match result_error.kind() { + io::ErrorKind::PermissionDenied | io::ErrorKind::InvalidInput => (), + _ => { + /* TODO: Check error messages. + let unchecked_error = unchecked_error.into(); + assert_eq!(result_error.to_string(), unchecked_error.to_string()); + assert_eq!(result_error.kind(), unchecked_error.kind()); + */ + } + }, + } + + // On operating systems which can tell us the path of a file descriptor, + // assert that the start path is a parent of the result path. + if let Ok(result_file) = &result { + if let Some(result_path) = file_path(result_file) { + if let Some(start_path) = file_path(start) { + assert!( + result_path.starts_with(start_path), + "sandbox escape: start='{:?}' result='{}'", + start, + result_path.display() + ); + } + } + } +} diff --git a/crates/wasi/src/filesystem/primitives/open_ambient.rs b/crates/wasi/src/filesystem/primitives/open_ambient.rs new file mode 100644 index 000000000000..c874c3e5fb75 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/open_ambient.rs @@ -0,0 +1,22 @@ +//! This defines `open_ambient`, for unsandboxed file opening. + +use crate::fs::{open_ambient_impl, OpenOptions}; +use crate::AmbientAuthority; +use std::path::Path; +use std::{fs, io}; + +/// Open a file named by a bare path, using the host process' ambient +/// authority. +/// +/// # Ambient Authority +/// +/// This function is not sandboxed and may trivially access any path that the +/// host process has access to. +#[inline] +pub fn open_ambient( + path: &Path, + options: &OpenOptions, + ambient_authority: AmbientAuthority, +) -> io::Result { + Ok(open_ambient_impl(path, options, ambient_authority)?) +} diff --git a/crates/wasi/src/filesystem/primitives/open_dir.rs b/crates/wasi/src/filesystem/primitives/open_dir.rs new file mode 100644 index 000000000000..ec6ab4c3d013 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/open_dir.rs @@ -0,0 +1,81 @@ +//! This defines `open_dir`, a wrapper around `open` which can be used to open +//! path as a directory. + +#[allow(unused_imports)] +use crate::fs::open_unchecked; +use crate::fs::{dir_options, open, open_ambient_dir_impl, readdir_options, FollowSymlinks}; +use ambient_authority::AmbientAuthority; +use std::path::{Component, Path}; +use std::{fs, io}; + +/// Open a directory by performing an `openat`-like operation, +/// ensuring that the resolution of the path never escapes +/// the directory tree rooted at `start`. +#[inline] +pub fn open_dir(start: &fs::File, path: &Path) -> io::Result { + open(start, path, &dir_options()) +} + +/// Like `open_dir`, but additionally request the ability to read the directory +/// entries. +#[cfg(not(windows))] +#[inline] +pub(crate) fn open_dir_for_reading( + start: &fs::File, + path: &Path, + follow: FollowSymlinks, +) -> io::Result { + open(start, path, readdir_options().follow(follow)) +} + +/// Similar to `open_dir`, but fails if the path names a symlink. +#[inline] +pub fn open_dir_nofollow(start: &fs::File, path: &Path) -> io::Result { + open(start, path, dir_options().follow(FollowSymlinks::No)) +} + +/// Open a directory by performing an unsandboxed `openat`-like operation. +#[inline] +#[allow(dead_code)] +pub(crate) fn open_dir_unchecked(start: &fs::File, path: &Path) -> io::Result { + open_unchecked(start, path, &dir_options()).map_err(Into::into) +} + +/// Like `open_dir_unchecked`, but additionally request the ability to read the +/// directory entries. +#[inline] +#[allow(dead_code)] +pub(crate) fn open_dir_for_reading_unchecked( + start: &fs::File, + path: &Path, + follow: FollowSymlinks, +) -> io::Result { + open_unchecked(start, path, readdir_options().follow(follow)).map_err(Into::into) +} + +/// Open a directory named by a bare path, using the host process' ambient +/// authority. +/// +/// # Ambient Authority +/// +/// This function is not sandboxed and may trivially access any path that the +/// host process has access to. +#[inline] +pub fn open_ambient_dir(path: &Path, ambient_authority: AmbientAuthority) -> io::Result { + open_ambient_dir_impl(path, ambient_authority) +} + +/// Open the parent directory of a given open directory, using the host +/// process' ambient authority. +/// +/// # Ambient Authority +/// +/// This function accesses a path outside of the `start` directory subtree. +#[inline] +pub fn open_parent_dir( + start: &fs::File, + ambient_authority: AmbientAuthority, +) -> io::Result { + let _ = ambient_authority; + open_dir_unchecked(start, Component::ParentDir.as_ref()) +} diff --git a/crates/wasi/src/filesystem/primitives/open_options.rs b/crates/wasi/src/filesystem/primitives/open_options.rs new file mode 100644 index 000000000000..3faec096e783 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/open_options.rs @@ -0,0 +1,481 @@ +use crate::fs::{FollowSymlinks, ImplOpenOptionsExt}; + +/// Options and flags which can be used to configure how a file is opened. +/// +/// This corresponds to [`std::fs::OpenOptions`]. +/// +/// This `OpenOptions` has no `open` method. To open a file with an +/// `OptionOptions`, first obtain a [`Dir`] containing the path, and then call +/// [`Dir::open_with`]. +/// +/// [`Dir`]: https://docs.rs/cap-std/latest/cap_std/fs/struct.Dir.html +/// [`Dir::open_with`]: https://docs.rs/cap-std/latest/cap_std/fs/struct.Dir.html#method.open_with +/// +///
+/// We need to define our own version because the libstd `OpenOptions` doesn't +/// have public accessors that we can use. +///
+#[derive(Debug, Clone)] +pub struct OpenOptions { + pub(crate) read: bool, + pub(crate) write: bool, + pub(crate) append: bool, + pub(crate) truncate: bool, + pub(crate) create: bool, + pub(crate) create_new: bool, + pub(crate) dir_required: bool, + pub(crate) maybe_dir: bool, + pub(crate) sync: bool, + pub(crate) dsync: bool, + pub(crate) rsync: bool, + pub(crate) nonblock: bool, + pub(crate) readdir_required: bool, + pub(crate) follow: FollowSymlinks, + + #[cfg(any(unix, windows, target_os = "vxworks"))] + pub(crate) ext: ImplOpenOptionsExt, +} + +impl OpenOptions { + /// Creates a blank new set of options ready for configuration. + /// + /// This corresponds to [`std::fs::OpenOptions::new`]. + #[allow(clippy::new_without_default)] + #[inline] + pub const fn new() -> Self { + Self { + read: false, + write: false, + append: false, + truncate: false, + create: false, + create_new: false, + dir_required: false, + maybe_dir: false, + sync: false, + dsync: false, + rsync: false, + nonblock: false, + readdir_required: false, + follow: FollowSymlinks::Yes, + + #[cfg(any(unix, windows, target_os = "vxworks"))] + ext: ImplOpenOptionsExt::new(), + } + } + + /// Sets the option for read access. + /// + /// This corresponds to [`std::fs::OpenOptions::read`]. + #[inline] + pub fn read(&mut self, read: bool) -> &mut Self { + self.read = read; + self + } + + /// Sets the option for write access. + /// + /// This corresponds to [`std::fs::OpenOptions::write`]. + #[inline] + pub fn write(&mut self, write: bool) -> &mut Self { + self.write = write; + self + } + + /// Sets the option for the append mode. + /// + /// This corresponds to [`std::fs::OpenOptions::append`]. + #[inline] + pub fn append(&mut self, append: bool) -> &mut Self { + self.append = append; + self + } + + /// Sets the option for truncating a previous file. + /// + /// This corresponds to [`std::fs::OpenOptions::truncate`]. + #[inline] + pub fn truncate(&mut self, truncate: bool) -> &mut Self { + self.truncate = truncate; + self + } + + /// Sets the option to create a new file. + /// + /// This corresponds to [`std::fs::OpenOptions::create`]. + #[inline] + pub fn create(&mut self, create: bool) -> &mut Self { + self.create = create; + self + } + + /// Sets the option to always create a new file. + /// + /// This corresponds to [`std::fs::OpenOptions::create_new`]. + #[inline] + pub fn create_new(&mut self, create_new: bool) -> &mut Self { + self.create_new = create_new; + self + } + + /// Sets the option to enable or suppress following of symlinks. + #[inline] + pub(crate) fn follow(&mut self, follow: FollowSymlinks) -> &mut Self { + self.follow = follow; + self + } + + /// Sets the option to enable an error if the opened object is not a + /// directory. + #[inline] + pub(crate) fn dir_required(&mut self, dir_required: bool) -> &mut Self { + self.dir_required = dir_required; + self + } + + /// Sets the option to disable an error if the opened object is a + /// directory. + #[inline] + pub(crate) fn maybe_dir(&mut self, maybe_dir: bool) -> &mut Self { + self.maybe_dir = maybe_dir; + self + } + + /// Requests write operations complete as defined by synchronized I/O file + /// integrity completion. + #[inline] + pub(crate) fn sync(&mut self, enable: bool) -> &mut Self { + self.sync = enable; + self + } + + /// Requests write operations complete as defined by synchronized I/O data + /// integrity completion. + #[inline] + pub(crate) fn dsync(&mut self, enable: bool) -> &mut Self { + self.dsync = enable; + self + } + + /// Requests read operations complete as defined by the level of integrity + /// specified by `sync` and `dsync`. + #[inline] + pub(crate) fn rsync(&mut self, enable: bool) -> &mut Self { + self.rsync = enable; + self + } + + /// Requests that I/O operations fail with `std::io::ErrorKind::WouldBlock` + /// if they would otherwise block. + /// + /// This option is commonly not implemented for regular files, so blocking + /// may still occur. + #[inline] + pub(crate) fn nonblock(&mut self, enable: bool) -> &mut Self { + self.nonblock = enable; + self + } + + /// Sets the option to request the ability to read directory entries. + #[inline] + pub(crate) fn readdir_required(&mut self, readdir_required: bool) -> &mut Self { + self.readdir_required = readdir_required; + self + } + + /// Wrapper to allow `follow` to be exposed by the `cap-fs-ext` crate. + /// + /// This is hidden from the main API since this functionality isn't present + /// in `std`. Use `cap_fs_ext::OpenOptionsFollowExt` instead of calling + /// this directly. + #[doc(hidden)] + #[inline] + pub fn _cap_fs_ext_follow(&mut self, follow: FollowSymlinks) -> &mut Self { + self.follow(follow) + } + + /// Wrapper to allow `maybe_dir` to be exposed by the `cap-fs-ext` crate. + /// + /// This is hidden from the main API since this functionality isn't present + /// in `std`. Use `cap_fs_ext::OpenOptionsMaybeDirExt` instead of + /// calling this directly. + #[doc(hidden)] + #[inline] + pub fn _cap_fs_ext_maybe_dir(&mut self, maybe_dir: bool) -> &mut Self { + self.maybe_dir(maybe_dir) + } + + /// Wrapper to allow `sync` to be exposed by the `cap-fs-ext` crate. + /// + /// This is hidden from the main API since this functionality isn't present + /// in `std`. Use `cap_fs_ext::OpenOptionsSyncExt` instead of + /// calling this directly. + #[doc(hidden)] + #[inline] + pub fn _cap_fs_ext_sync(&mut self, enable: bool) -> &mut Self { + self.sync(enable) + } + + /// Wrapper to allow `dsync` to be exposed by the `cap-fs-ext` crate. + /// + /// This is hidden from the main API since this functionality isn't present + /// in `std`. Use `cap_fs_ext::OpenOptionsSyncExt` instead of + /// calling this directly. + #[doc(hidden)] + #[inline] + pub fn _cap_fs_ext_dsync(&mut self, enable: bool) -> &mut Self { + self.dsync(enable) + } + + /// Wrapper to allow `rsync` to be exposed by the `cap-fs-ext` crate. + /// + /// This is hidden from the main API since this functionality isn't present + /// in `std`. Use `cap_fs_ext::OpenOptionsSyncExt` instead of + /// calling this directly. + #[doc(hidden)] + #[inline] + pub fn _cap_fs_ext_rsync(&mut self, enable: bool) -> &mut Self { + self.rsync(enable) + } + + /// Wrapper to allow `nonblock` to be exposed by the `cap-fs-ext` crate. + /// + /// This is hidden from the main API since this functionality isn't present + /// in `std`. Use `cap_fs_ext::OpenOptionsSyncExt` instead of + /// calling this directly. + #[doc(hidden)] + #[inline] + pub fn _cap_fs_ext_nonblock(&mut self, enable: bool) -> &mut Self { + self.nonblock(enable) + } +} + +/// Unix-specific extensions to [`fs::OpenOptions`]. +#[cfg(unix)] +pub trait OpenOptionsExt { + /// Sets the mode bits that a new file will be created with. + fn mode(&mut self, mode: u32) -> &mut Self; + + /// Pass custom flags to the `flags` argument of `open`. + fn custom_flags(&mut self, flags: i32) -> &mut Self; +} + +/// WASI-specific extensions to [`fs::OpenOptions`]. +#[cfg(target_os = "wasi")] +pub trait OpenOptionsExt { + /// Pass custom `dirflags` argument to `path_open`. + fn lookup_flags(&mut self, flags: u32) -> &mut Self; + + /// Indicates whether `OpenOptions` must open a directory or not. + fn directory(&mut self, dir: bool) -> &mut Self; + + /// Indicates whether `__WASI_FDFLAG_DSYNC` is passed in the `fs_flags` + /// field of `path_open`. + fn dsync(&mut self, dsync: bool) -> &mut Self; + + /// Indicates whether `__WASI_FDFLAG_NONBLOCK` is passed in the `fs_flags` + /// field of `path_open`. + fn nonblock(&mut self, nonblock: bool) -> &mut Self; + + /// Indicates whether `__WASI_FDFLAG_RSYNC` is passed in the `fs_flags` + /// field of `path_open`. + fn rsync(&mut self, rsync: bool) -> &mut Self; + + /// Indicates whether `__WASI_FDFLAG_SYNC` is passed in the `fs_flags` + /// field of `path_open`. + fn sync(&mut self, sync: bool) -> &mut Self; + + /// Indicates the value that should be passed in for the `fs_rights_base` + /// parameter of `path_open`. + fn fs_rights_base(&mut self, rights: u64) -> &mut Self; + + /// Indicates the value that should be passed in for the + /// `fs_rights_inheriting` parameter of `path_open`. + fn fs_rights_inheriting(&mut self, rights: u64) -> &mut Self; + + /// Open a file or directory. + fn open_at>( + &self, + file: &std::fs::File, + path: P, + ) -> std::io::Result; +} + +/// Windows-specific extensions to [`fs::OpenOptions`]. +#[cfg(windows)] +pub trait OpenOptionsExt { + /// Overrides the `dwDesiredAccess` argument to the call to [`CreateFile`] + /// with the specified value. + fn access_mode(&mut self, access: u32) -> &mut Self; + + /// Overrides the `dwShareMode` argument to the call to [`CreateFile`] with + /// the specified value. + fn share_mode(&mut self, val: u32) -> &mut Self; + + /// Sets extra flags for the `dwFileFlags` argument to the call to + /// [`CreateFile2`] to the specified value (or combines it with + /// `attributes` and `security_qos_flags` to set the `dwFlagsAndAttributes` + /// for [`CreateFile`]). + /// + /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea + /// [`CreateFile2`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfile2 + fn custom_flags(&mut self, flags: u32) -> &mut Self; + + /// Sets the `dwFileAttributes` argument to the call to [`CreateFile2`] to + /// the specified value (or combines it with `custom_flags` and + /// `security_qos_flags` to set the `dwFlagsAndAttributes` for + /// [`CreateFile`]). + /// + /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea + /// [`CreateFile2`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfile2 + fn attributes(&mut self, val: u32) -> &mut Self; + + /// Sets the `dwSecurityQosFlags` argument to the call to [`CreateFile2`] to + /// the specified value (or combines it with `custom_flags` and `attributes` + /// to set the `dwFlagsAndAttributes` for [`CreateFile`]). + /// + /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea + /// [`CreateFile2`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfile2 + /// [Impersonation Levels]: + /// https://docs.microsoft.com/en-us/windows/win32/api/winnt/ne-winnt-security_impersonation_level + fn security_qos_flags(&mut self, flags: u32) -> &mut Self; +} + +#[cfg(unix)] +impl OpenOptionsExt for OpenOptions { + #[inline] + fn mode(&mut self, mode: u32) -> &mut Self { + self.ext.mode(mode); + self + } + + #[inline] + fn custom_flags(&mut self, flags: i32) -> &mut Self { + self.ext.custom_flags(flags); + self + } +} + +#[cfg(target_os = "wasi")] +impl OpenOptionsExt for OpenOptions { + fn lookup_flags(&mut self, _: u32) -> &mut Self { + todo!() + } + + fn directory(&mut self, dir_required: bool) -> &mut Self { + self.dir_required = dir_required; + self + } + + fn dsync(&mut self, _: bool) -> &mut Self { + todo!() + } + + fn nonblock(&mut self, _: bool) -> &mut Self { + todo!() + } + + fn rsync(&mut self, _: bool) -> &mut Self { + todo!() + } + + fn sync(&mut self, _: bool) -> &mut Self { + todo!() + } + + fn fs_rights_base(&mut self, _: u64) -> &mut Self { + todo!() + } + + fn fs_rights_inheriting(&mut self, _: u64) -> &mut Self { + todo!() + } + + fn open_at

(&self, dirfd: &std::fs::File, path: P) -> Result + where + P: AsRef, + { + crate::fs::open(dirfd, path.as_ref(), self) + } +} + +#[cfg(target_os = "vxworks")] +impl OpenOptionsExt for OpenOptions { + #[inline] + fn mode(&mut self, mode: u32) -> &mut Self { + self.ext.mode(mode); + self + } + + #[inline] + fn custom_flags(&mut self, flags: i32) -> &mut Self { + self.ext.custom_flags(flags); + self + } +} + +#[cfg(windows)] +impl OpenOptionsExt for OpenOptions { + #[inline] + fn access_mode(&mut self, access: u32) -> &mut Self { + self.ext.access_mode(access); + self + } + + /// To prevent race conditions on Windows, handles for directories must be + /// opened without `FILE_SHARE_DELETE`. + #[inline] + fn share_mode(&mut self, val: u32) -> &mut Self { + self.ext.share_mode(val); + self + } + + #[inline] + fn custom_flags(&mut self, flags: u32) -> &mut Self { + self.ext.custom_flags(flags); + self + } + + #[inline] + fn attributes(&mut self, val: u32) -> &mut Self { + self.ext.attributes(val); + self + } + + #[inline] + fn security_qos_flags(&mut self, flags: u32) -> &mut Self { + self.ext.security_qos_flags(flags); + self + } +} + +#[cfg(feature = "arbitrary")] +impl arbitrary::Arbitrary<'_> for OpenOptions { + fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { + use arbitrary::Arbitrary; + let (read, write) = match u.int_in_range(0..=2)? { + 0 => (true, false), + 1 => (false, true), + 2 => (true, true), + _ => panic!(), + }; + // TODO: `OpenOptionsExt` options. + Ok(Self::new() + .read(read) + .write(write) + .create(::arbitrary(u)?) + .append(::arbitrary(u)?) + .truncate(::arbitrary(u)?) + .create(::arbitrary(u)?) + .create_new(::arbitrary(u)?) + .dir_required(::arbitrary(u)?) + .maybe_dir(::arbitrary(u)?) + .sync(::arbitrary(u)?) + .dsync(::arbitrary(u)?) + .rsync(::arbitrary(u)?) + .nonblock(::arbitrary(u)?) + .readdir_required(::arbitrary(u)?) + .follow(::arbitrary(u)?) + .clone()) + } +} diff --git a/crates/wasi/src/filesystem/primitives/open_unchecked_error.rs b/crates/wasi/src/filesystem/primitives/open_unchecked_error.rs new file mode 100644 index 000000000000..09ad9a6fca6b --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/open_unchecked_error.rs @@ -0,0 +1,37 @@ +use std::io; + +#[derive(Debug)] +pub(crate) enum OpenUncheckedError { + Other(io::Error), + Symlink(io::Error, SymlinkKind), + NotFound(io::Error), +} + +#[cfg(not(windows))] +pub(crate) type SymlinkKind = (); + +#[cfg(windows)] +#[derive(Debug)] +pub(crate) enum SymlinkKind { + File, + Dir, +} + +impl OpenUncheckedError { + #[allow(dead_code)] + pub(crate) fn kind(&self) -> io::ErrorKind { + match self { + Self::Other(err) | Self::Symlink(err, _) | Self::NotFound(err) => err.kind(), + } + } +} + +impl From for io::Error { + fn from(error: OpenUncheckedError) -> Self { + match error { + OpenUncheckedError::Other(err) + | OpenUncheckedError::Symlink(err, _) + | OpenUncheckedError::NotFound(err) => err, + } + } +} diff --git a/crates/wasi/src/filesystem/primitives/permissions.rs b/crates/wasi/src/filesystem/primitives/permissions.rs new file mode 100644 index 000000000000..f20c36fc9d59 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/permissions.rs @@ -0,0 +1,150 @@ +#[cfg(not(windows))] +use crate::fs::ImplPermissionsExt; +#[cfg(unix)] +use rustix::fs::RawMode; +use std::{fs, io}; + +/// Representation of the various permissions on a file. +/// +/// This corresponds to [`std::fs::Permissions`]. +/// +///

+/// We need to define our own version because the libstd `Permissions` doesn't +/// have a public constructor that we can use. +///
+#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Permissions { + pub(crate) readonly: bool, + + #[cfg(any(unix, target_os = "vxworks"))] + pub(crate) ext: ImplPermissionsExt, +} + +impl Permissions { + /// Constructs a new instance of `Self` from the given + /// `std::fs::Permissions`. + #[inline] + pub fn from_std(std: fs::Permissions) -> Self { + Self { + readonly: std.readonly(), + + #[cfg(any(unix, target_os = "vxworks"))] + ext: ImplPermissionsExt::from_std(std), + } + } + + /// Consumes `self` and produces a new instance of `std::fs::Permissions`. + /// + ///
+ /// The `file` parameter works around the fact that we can't construct a + /// `Permissions` object ourselves on Windows. + ///
+ #[inline] + pub fn into_std(self, file: &fs::File) -> io::Result { + self._into_std(file) + } + + #[cfg(unix)] + #[inline] + #[allow(clippy::unnecessary_wraps)] + fn _into_std(self, _file: &fs::File) -> io::Result { + use std::os::unix::fs::PermissionsExt; + Ok(fs::Permissions::from_mode(crate::fs::PermissionsExt::mode( + &self.ext, + ))) + } + + #[cfg(target_os = "wasi")] + #[inline] + #[allow(clippy::unnecessary_wraps)] + fn _into_std(self, file: &fs::File) -> io::Result { + let mut permissions = file.metadata()?.permissions(); + permissions.set_readonly(self.readonly()); + Ok(permissions) + } + + #[cfg(windows)] + #[inline] + fn _into_std(self, file: &fs::File) -> io::Result { + let mut permissions = file.metadata()?.permissions(); + permissions.set_readonly(self.readonly()); + Ok(permissions) + } + + /// Returns `true` if these permissions describe a readonly (unwritable) + /// file. + /// + /// This corresponds to [`std::fs::Permissions::readonly`]. + #[inline] + pub const fn readonly(&self) -> bool { + self.readonly + } + + /// Modifies the readonly flag for this set of permissions. + /// + /// This corresponds to [`std::fs::Permissions::set_readonly`]. + #[inline] + pub fn set_readonly(&mut self, readonly: bool) { + self.readonly = readonly; + + #[cfg(any(unix, target_os = "vxworks"))] + self.ext.set_readonly(readonly); + } +} + +/// Unix-specific extensions to [`Permissions`]. +#[cfg(unix)] +pub trait PermissionsExt { + /// Returns the underlying raw `st_mode` bits that contain the standard + /// Unix permissions for this file. + fn mode(&self) -> u32; + + /// Sets the underlying raw bits for this set of permissions. + fn set_mode(&mut self, mode: u32); + + /// Creates a new instance of `Permissions` from the given set of Unix + /// permission bits. + fn from_mode(mode: u32) -> Self; +} + +#[cfg(unix)] +impl PermissionsExt for Permissions { + #[inline] + fn mode(&self) -> u32 { + crate::fs::PermissionsExt::mode(&self.ext) + } + + #[inline] + fn set_mode(&mut self, mode: u32) { + crate::fs::PermissionsExt::set_mode(&mut self.ext, mode) + } + + #[inline] + fn from_mode(mode: u32) -> Self { + Self { + readonly: ImplPermissionsExt::readonly(mode as RawMode), + ext: crate::fs::PermissionsExt::from_mode(mode), + } + } +} + +#[cfg(target_os = "vxworks")] +impl PermissionsExt for Permissions { + #[inline] + fn mode(&self) -> u32 { + self.ext.mode() + } + + #[inline] + fn set_mode(&mut self, mode: u32) { + self.ext.set_mode(mode) + } + + #[inline] + fn from_mode(mode: u32) -> Self { + Self { + readonly: ImplPermissionsExt::readonly(mode), + ext: ImplPermissionsExt::from(mode), + } + } +} diff --git a/crates/wasi/src/filesystem/primitives/read_dir.rs b/crates/wasi/src/filesystem/primitives/read_dir.rs new file mode 100644 index 000000000000..9496e01e42d8 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/read_dir.rs @@ -0,0 +1,71 @@ +use crate::fs::{DirEntry, FollowSymlinks, ReadDirInner}; +use std::path::Path; +use std::{fmt, fs, io}; + +/// Construct a `ReadDir` to iterate over the contents of a directory, +/// ensuring that the resolution of the path never escapes the directory +/// tree rooted at `start`. +#[inline] +pub fn read_dir(start: &fs::File, path: &Path) -> io::Result { + Ok(ReadDir { + inner: ReadDirInner::new(start, path, FollowSymlinks::Yes)?, + }) +} + +/// Like `read_dir`, but fails if `path` names a symlink. +#[inline] +#[cfg(not(windows))] +pub(crate) fn read_dir_nofollow(start: &fs::File, path: &Path) -> io::Result { + Ok(ReadDir { + inner: ReadDirInner::new(start, path, FollowSymlinks::No)?, + }) +} + +/// Like `read_dir` but operates on the base directory itself, rather than +/// on a path based on it. +#[inline] +pub fn read_base_dir(start: &fs::File) -> io::Result { + Ok(ReadDir { + inner: ReadDirInner::read_base_dir(start)?, + }) +} + +/// Like `read_dir`, but doesn't perform sandboxing. +#[inline] +#[cfg(not(windows))] +pub(crate) fn read_dir_unchecked( + start: &fs::File, + path: &Path, + follow: FollowSymlinks, +) -> io::Result { + Ok(ReadDir { + inner: ReadDirInner::new_unchecked(start, path, follow)?, + }) +} + +/// Iterator over the entries in a directory. +/// +/// This corresponds to [`std::fs::ReadDir`]. +/// +/// There is no `from_std` method, as `std::fs::ReadDir` doesn't provide a way +/// to construct a `ReadDir` without opening directories by ambient paths. +pub struct ReadDir { + pub(crate) inner: ReadDirInner, +} + +impl Iterator for ReadDir { + type Item = io::Result; + + #[inline] + fn next(&mut self) -> Option { + self.inner + .next() + .map(|inner| inner.map(|inner| DirEntry { inner })) + } +} + +impl fmt::Debug for ReadDir { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.inner.fmt(f) + } +} diff --git a/crates/wasi/src/filesystem/primitives/read_link.rs b/crates/wasi/src/filesystem/primitives/read_link.rs new file mode 100644 index 000000000000..f1e91ef83b1b --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/read_link.rs @@ -0,0 +1,110 @@ +//! This defines `read_link`, the primary entrypoint to sandboxed symbolic link +//! dereferencing. + +use crate::fs::{errors, read_link_impl}; +#[cfg(racy_asserts)] +use crate::fs::{map_result, read_link_unchecked, stat, FollowSymlinks}; +use std::path::{Path, PathBuf}; +use std::{fs, io}; + +/// Perform a `readlinkat`-like operation, ensuring that the resolution of the +/// link path never escapes the directory tree rooted at `start`. +#[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))] +#[inline] +pub fn read_link_contents(start: &fs::File, path: &Path) -> io::Result { + // Call the underlying implementation. + let result = read_link_impl(start, path); + + #[cfg(racy_asserts)] + let unchecked = read_link_unchecked(start, path, PathBuf::new()); + + #[cfg(racy_asserts)] + check_read_link(start, path, &result, &unchecked); + + result +} + +/// Perform a `readlinkat`-like operation, ensuring that the resolution of the +/// path never escapes the directory tree rooted at `start`, and also verifies +/// that the link target is not absolute. +#[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))] +#[inline] +pub fn read_link(start: &fs::File, path: &Path) -> io::Result { + // Call the underlying implementation. + let result = read_link_contents(start, path); + + // Don't allow reading symlinks to absolute paths. This isn't strictly + // necessary to preserve the sandbox, since `open` will refuse to follow + // absolute paths in any case. However, it is useful to enforce this + // restriction to avoid leaking information about the host filesystem + // outside the sandbox. + if let Ok(path) = &result { + if path.has_root() { + return Err(errors::escape_attempt()); + } + } + + result +} + +#[cfg(racy_asserts)] +#[allow(clippy::enum_glob_use)] +fn check_read_link( + start: &fs::File, + path: &Path, + result: &io::Result, + unchecked: &io::Result, +) { + use io::ErrorKind::*; + + match (map_result(result), map_result(unchecked)) { + (Ok(target), Ok(unchecked_target)) => { + assert_eq!(target, unchecked_target); + } + + (Err((PermissionDenied, message)), _) => { + match map_result(&stat(start, path, FollowSymlinks::No)) { + Err((PermissionDenied, canon_message)) => { + assert_eq!(message, canon_message); + } + _ => panic!("read_link failed where canonicalize succeeded"), + } + } + + (Err((_kind, _message)), Err((_unchecked_kind, _unchecked_message))) => { + /* TODO: Check error messages. + assert_eq!(kind, unchecked_kind); + assert_eq!(message, unchecked_message); + */ + } + + other => panic!( + "unexpected result from read_link start='{:?}', path='{}':\n{:#?}", + start, + path.display(), + other, + ), + } +} + +#[cfg(not(windows))] +#[test] +fn test_read_link_contents() { + use io_lifetimes::AsFilelike; + let td = cap_tempfile::tempdir(cap_tempfile::ambient_authority()).unwrap(); + let td_view = &td.as_filelike_view::(); + let valid = [ + "relative/path", + "/some/absolute/path", + "/", + "../", + "basepath", + ]; + for case in valid { + let linkname = Path::new("linkname"); + crate::fs::symlink_contents(case, td_view, linkname).unwrap(); + let contents = crate::fs::read_link_contents(td_view, linkname).unwrap(); + assert_eq!(contents.to_str().unwrap(), case); + crate::fs::remove_file(td_view, linkname).unwrap(); + } +} diff --git a/crates/wasi/src/filesystem/primitives/remove_dir.rs b/crates/wasi/src/filesystem/primitives/remove_dir.rs new file mode 100644 index 000000000000..5226792fbc27 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/remove_dir.rs @@ -0,0 +1,148 @@ +//! This defines `remove_dir`, the primary entrypoint to sandboxed file +//! removal. + +use crate::fs::remove_dir_impl; +#[cfg(racy_asserts)] +use crate::fs::{ + manually, map_result, remove_dir_unchecked, stat_unchecked, FollowSymlinks, Metadata, +}; +use std::path::Path; +use std::{fs, io}; + +/// Perform a `rmdirat`-like operation, 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))] +#[inline] +pub fn remove_dir(start: &fs::File, path: &Path) -> io::Result<()> { + #[cfg(racy_asserts)] + let stat_before = stat_unchecked(start, path, FollowSymlinks::No); + + // Call the underlying implementation. + let result = remove_dir_impl(start, path); + + #[cfg(racy_asserts)] + let stat_after = stat_unchecked(start, path, FollowSymlinks::No); + + #[cfg(racy_asserts)] + check_remove_dir(start, path, &stat_before, &result, &stat_after); + + result +} + +#[cfg(racy_asserts)] +#[allow(clippy::enum_glob_use)] +fn check_remove_dir( + start: &fs::File, + path: &Path, + stat_before: &io::Result, + result: &io::Result<()>, + stat_after: &io::Result, +) { + use io::ErrorKind::*; + + match ( + map_result(stat_before), + map_result(result), + map_result(stat_after), + ) { + (Ok(metadata), Ok(()), Err((NotFound, _))) => { + // TODO: Check that the path was inside the sandbox. + assert!(metadata.is_dir()); + } + + (Err((Other, _)), Ok(()), Err((NotFound, _))) => { + // TODO: Check that the path was inside the sandbox. + } + + (_, Err((InvalidInput, _)), _) => { + // `remove_dir(".")` apparently returns `EINVAL` + } + + (_, Err((kind, message)), _) => { + match map_result(&manually::canonicalize_with( + start, + path, + FollowSymlinks::No, + )) { + Ok(canon) => match map_result(&remove_dir_unchecked(start, &canon)) { + Err((_unchecked_kind, _unchecked_message)) => { + /* TODO: Check error messages. + assert_eq!( + kind, + unchecked_kind, + "unexpected error kind from remove_dir start='{:?}', \ + path='{}':\nstat_before={:#?}\nresult={:#?}\nstat_after={:#?}", + start, + path.display(), + stat_before, + result, + stat_after + ); + assert_eq!(message, unchecked_message); + */ + } + _ => { + // TODO: Checking in the case it does end with ".". + if !path.to_string_lossy().ends_with(".") { + panic!( + "unsandboxed remove_dir success on start={:?} path={:?}; expected \ + {:?}: {}", + start, path, kind, message + ); + } + } + }, + Err((_canon_kind, _canon_message)) => { + /* TODO: Check error messages. + assert_eq!(kind, canon_kind, "'{}' vs '{}'", message, canon_message); + assert_eq!(message, canon_message); + */ + } + } + } + + other => panic!( + "inconsistent remove_dir checks: start='{:?}' path='{}':\n{:#?}", + start, + path.display(), + other, + ), + } + + match stat_after { + Ok(unchecked_metadata) => match &result { + Ok(()) => panic!( + "file still exists after remove_dir start='{:?}', path='{}'", + start, + path.display() + ), + Err(e) => match e.kind() { + #[cfg(io_error_more)] + io::ErrorKind::NotADirectory => assert!(!unchecked_metadata.is_dir()), + #[cfg(io_error_more)] + io::ErrorKind::DirectoryNotEmpty => (), + io::ErrorKind::PermissionDenied + | io::ErrorKind::InvalidInput // `remove_dir(".")` apparently returns `EINVAL` + | io::ErrorKind::Other => (), // directory not empty, among other things + _ => panic!( + "unexpected error remove_dir'ing start='{:?}', path='{}': {:?}", + start, + path.display(), + e + ), + }, + }, + Err(_unchecked_error) => match &result { + Ok(()) => (), + Err(result_error) => match result_error.kind() { + io::ErrorKind::PermissionDenied => (), + _ => { + /* TODO: Check error messages. + assert_eq!(result_error.to_string(), unchecked_error.to_string()); + assert_eq!(result_error.kind(), unchecked_error.kind()); + */ + } + }, + }, + } +} diff --git a/crates/wasi/src/filesystem/primitives/remove_dir_all.rs b/crates/wasi/src/filesystem/primitives/remove_dir_all.rs new file mode 100644 index 000000000000..1bec5a6187e4 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/remove_dir_all.rs @@ -0,0 +1,9 @@ +use crate::fs::remove_dir_all_impl; +use std::path::Path; +use std::{fs, io}; + +/// Removes a directory and all of its contents. +#[inline] +pub fn remove_dir_all(start: &fs::File, path: &Path) -> io::Result<()> { + remove_dir_all_impl(start, path) +} diff --git a/crates/wasi/src/filesystem/primitives/remove_file.rs b/crates/wasi/src/filesystem/primitives/remove_file.rs new file mode 100644 index 000000000000..70c68008720a --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/remove_file.rs @@ -0,0 +1,129 @@ +//! This defines `remove_file`, the primary entrypoint to sandboxed file +//! removal. + +use crate::fs::remove_file_impl; +#[cfg(racy_asserts)] +use crate::fs::{ + manually, map_result, remove_file_unchecked, stat_unchecked, FollowSymlinks, Metadata, +}; +use std::path::Path; +use std::{fs, io}; + +/// Perform a `remove_fileat`-like operation, 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))] +#[inline] +pub fn remove_file(start: &fs::File, path: &Path) -> io::Result<()> { + #[cfg(racy_asserts)] + let stat_before = stat_unchecked(start, path, FollowSymlinks::No); + + // Call the underlying implementation. + let result = remove_file_impl(start, path); + + #[cfg(racy_asserts)] + let stat_after = stat_unchecked(start, path, FollowSymlinks::No); + + #[cfg(racy_asserts)] + check_remove_file(start, path, &stat_before, &result, &stat_after); + + result +} + +#[cfg(racy_asserts)] +#[allow(clippy::enum_glob_use)] +fn check_remove_file( + start: &fs::File, + path: &Path, + stat_before: &io::Result, + result: &io::Result<()>, + stat_after: &io::Result, +) { + use io::ErrorKind::*; + + match ( + map_result(stat_before), + map_result(result), + map_result(stat_after), + ) { + (Ok(metadata), Ok(()), Err((NotFound, _))) => { + // TODO: Check that the path was inside the sandbox. + assert!(!metadata.is_dir()); + } + + (Err((Other, _)), Ok(()), Err((NotFound, _))) => { + // TODO: Check that the path was inside the sandbox. + } + + (_, Err((_kind, _message)), _) => { + match map_result(&manually::canonicalize_with( + start, + path, + FollowSymlinks::No, + )) { + Ok(canon) => match map_result(&remove_file_unchecked(start, &canon)) { + Err((_unchecked_kind, _unchecked_message)) => { + /* TODO: Check error messages. + assert_eq!( + kind, + unchecked_kind, + "unexpected error kind from remove_file start='{:?}', \ + path='{}':\nstat_before={:#?}\nresult={:#?}\nstat_after={:#?}", + start, + path.display(), + stat_before, + result, + stat_after + ); + assert_eq!(message, unchecked_message); + */ + } + _ => panic!("unsandboxed remove_file success"), + }, + Err((_canon_kind, _canon_message)) => { + /* TODO: Check error messages. + assert_eq!(kind, canon_kind, "'{}' vs '{}'", message, canon_message); + assert_eq!(message, canon_message); + */ + } + } + } + + other => panic!( + "inconsistent remove_file checks: start='{:?}' path='{}':\n{:#?}", + start, + path.display(), + other, + ), + } + + match (result, stat_after) { + (Ok(()), Ok(_unchecked_metadata)) => panic!( + "file still exists after remove_file start='{:?}', path='{}'", + start, + path.display() + ), + (Err(e), Ok(unchecked_metadata)) => match e.kind() { + #[cfg(io_error_more)] + io::ErrorKind::IsADirectory => assert!(unchecked_metadata.is_dir()), + io::ErrorKind::PermissionDenied => (), + #[cfg(not(io_error_more))] + io::ErrorKind::Other if unchecked_metadata.is_dir() => (), + _ => panic!( + "unexpected error removing file start='{:?}', path='{}': {:?}", + start, + path.display(), + e + ), + }, + (Ok(()), Err(_unchecked_error)) => (), + (Err(result_error), Err(_unchecked_error)) => match result_error.kind() { + io::ErrorKind::PermissionDenied => (), + _ => { + /* TODO: Check error messages. + assert_eq!(result_error.to_string(), unchecked_error.to_string()); + assert_eq!(result_error.kind(), unchecked_error.kind()); + */ + } + }, + } +} diff --git a/crates/wasi/src/filesystem/primitives/remove_open_dir.rs b/crates/wasi/src/filesystem/primitives/remove_open_dir.rs new file mode 100644 index 000000000000..0a87c3e66e69 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/remove_open_dir.rs @@ -0,0 +1,16 @@ +use crate::fs::{remove_open_dir_all_impl, remove_open_dir_impl}; +use std::{fs, io}; + +/// Given an open directory handle, delete the directory. +#[inline] +pub fn remove_open_dir(dir: fs::File) -> io::Result<()> { + remove_open_dir_impl(dir) +} + +/// Given an open directory handle, recursively delete the contents of the +/// directory plus the directory itself. +#[allow(clippy::module_name_repetitions)] +#[inline] +pub fn remove_open_dir_all(dir: fs::File) -> io::Result<()> { + remove_open_dir_all_impl(dir) +} diff --git a/crates/wasi/src/filesystem/primitives/rename.rs b/crates/wasi/src/filesystem/primitives/rename.rs new file mode 100644 index 000000000000..2af4ef95960c --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rename.rs @@ -0,0 +1,158 @@ +//! This defines `rename`, the primary entrypoint to sandboxed renaming. + +#[cfg(all(racy_asserts, not(windows)))] +use crate::fs::append_dir_suffix; +use crate::fs::rename_impl; +use std::path::Path; +use std::{fs, io}; +#[cfg(racy_asserts)] +use { + crate::fs::{ + manually, map_result, path_requires_dir, rename_unchecked, stat_unchecked, FollowSymlinks, + Metadata, + }, + std::path::PathBuf, +}; + +/// Perform a `renameat`-like operation, ensuring that the resolution of both +/// the old and new paths never escape the directory tree rooted at their +/// respective starts. +#[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))] +#[inline] +pub fn rename( + old_start: &fs::File, + old_path: &Path, + new_start: &fs::File, + new_path: &Path, +) -> io::Result<()> { + #[cfg(racy_asserts)] + let (old_metadata_before, new_metadata_before) = ( + stat_unchecked(old_start, old_path, FollowSymlinks::No), + stat_unchecked(new_start, new_path, FollowSymlinks::No), + ); + + // Call the underlying implementation. + let result = rename_impl(old_start, old_path, new_start, new_path); + + #[cfg(racy_asserts)] + let (old_metadata_after, new_metadata_after) = ( + stat_unchecked(old_start, old_path, FollowSymlinks::No), + stat_unchecked(new_start, new_path, FollowSymlinks::No), + ); + + #[cfg(racy_asserts)] + check_rename( + old_start, + old_path, + new_start, + new_path, + &old_metadata_before, + &new_metadata_before, + &result, + &old_metadata_after, + &new_metadata_after, + ); + + result +} + +#[cfg(racy_asserts)] +#[allow(clippy::too_many_arguments)] +#[allow(clippy::enum_glob_use)] +fn check_rename( + old_start: &fs::File, + old_path: &Path, + new_start: &fs::File, + new_path: &Path, + old_metadata_before: &io::Result, + new_metadata_before: &io::Result, + result: &io::Result<()>, + old_metadata_after: &io::Result, + new_metadata_after: &io::Result, +) { + use io::ErrorKind::*; + + match ( + map_result(old_metadata_before), + map_result(new_metadata_before), + map_result(result), + map_result(old_metadata_after), + map_result(new_metadata_after), + ) { + ( + Ok(old_metadata_before), + Err((NotFound, _)), + Ok(()), + Err((NotFound, _)), + Ok(new_metadata_after), + ) => { + assert_same_file_metadata!(&old_metadata_before, &new_metadata_after); + } + + (_, Ok(new_metadata_before), Err((AlreadyExists, _)), _, Ok(new_metadata_after)) => { + assert_same_file_metadata!(&new_metadata_before, &new_metadata_after); + } + + (_, _, Err((kind, message)), _, _) => match ( + map_result(&canonicalize_for_rename(old_start, old_path)), + map_result(&canonicalize_for_rename(new_start, new_path)), + ) { + (Ok(old_canon), Ok(new_canon)) => match map_result(&rename_unchecked( + old_start, &old_canon, new_start, &new_canon, + )) { + Err((_unchecked_kind, _unchecked_message)) => { + /* TODO: Check error messages. + assert_eq!(kind, unchecked_kind); + assert_eq!(message, unchecked_message); + */ + } + other => panic!( + "unsandboxed rename success:\n{:#?}\n{:?} {:?}", + other, kind, message + ), + }, + (Err((_old_canon_kind, _old_canon_message)), _) => { + /* TODO: Check error messages. + assert_eq!(kind, old_canon_kind); + assert_eq!(message, old_canon_message); + */ + } + (_, Err((_new_canon_kind, _new_canon_message))) => { + /* TODO: Check error messages. + assert_eq!(kind, new_canon_kind); + assert_eq!(message, new_canon_message); + */ + } + }, + + _other => { + /* TODO: Check error messages. + panic!( + "inconsistent rename checks: old_start='{:?}', old_path='{}', new_start='{:?}', \ + new_path='{}':\n{:#?}", + old_start, + old_path.display(), + new_start, + new_path.display(), + other + ) + */ + } + } +} + +#[cfg(racy_asserts)] +fn canonicalize_for_rename(start: &fs::File, path: &Path) -> io::Result { + let mut canon = manually::canonicalize_with(start, path, FollowSymlinks::No)?; + + // Rename on paths ending in `.` or `/.` fails due to the directory already + // being open. Ensure that this happens on the canonical paths too. + #[cfg(not(windows))] + if path_requires_dir(path) { + canon = append_dir_suffix(path.to_path_buf()); + + assert!(path_requires_dir(&canon)); + } + + Ok(canon) +} diff --git a/crates/wasi/src/filesystem/primitives/reopen.rs b/crates/wasi/src/filesystem/primitives/reopen.rs new file mode 100644 index 000000000000..b19bbcd0ad2b --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/reopen.rs @@ -0,0 +1,36 @@ +//! Re-open a `fs::File` to produce an independent handle. + +use crate::fs::{is_file_read_write, is_same_file, reopen_impl, OpenOptions}; +use std::{fs, io}; + +/// Re-open an `fs::File` to produce an independent handle. +/// +/// This operation isn't supported by all operating systems in all +/// circumstances, or in some operating systems in any circumstances, +/// so it may return an `io::ErrorKind::Other` error if the file +/// cannot be reopened. +#[inline] +pub fn reopen(file: &fs::File, options: &OpenOptions) -> io::Result { + let (read, write) = is_file_read_write(file)?; + + // Don't grant more rights than the original file had. And don't allow + // it to create a file. + if options.create + || options.create_new + || (!read && options.read) + || (!write && (options.write || options.append || options.truncate)) + { + return Err(io::Error::new( + io::ErrorKind::PermissionDenied, + "Couldn't reopen file", + )); + } + + let new = reopen_impl(file, options)?; + + if !is_same_file(file, &new)? { + return Err(io::Error::new(io::ErrorKind::Other, "Couldn't reopen file")); + } + + Ok(new) +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/darwin/fs/file_path.rs b/crates/wasi/src/filesystem/primitives/rustix/darwin/fs/file_path.rs new file mode 100644 index 000000000000..377309bea730 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/darwin/fs/file_path.rs @@ -0,0 +1,21 @@ +//! `get_path` translation code for macOS derived from Rust's +//! library/std/src/sys/unix/fs.rs at revision +//! 108e90ca78f052c0c1c49c42a22c85620be19712. + +use crate::rustix::fs::file_path_by_ttyname_or_seaching; +use rustix::fs::getpath; +use std::ffi::OsString; +use std::fs; +#[cfg(unix)] +use std::os::unix::ffi::OsStringExt; +#[cfg(target_os = "wasi")] +use std::os::wasi::ffi::OsStringExt; +use std::path::PathBuf; + +pub(crate) fn file_path(file: &fs::File) -> Option { + if let Ok(path) = getpath(file) { + return Some(OsString::from_vec(path.into_bytes()).into()); + } + + file_path_by_ttyname_or_seaching(file) +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/darwin/fs/mod.rs b/crates/wasi/src/filesystem/primitives/rustix/darwin/fs/mod.rs new file mode 100644 index 000000000000..32e3eec364bd --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/darwin/fs/mod.rs @@ -0,0 +1,3 @@ +mod file_path; + +pub(crate) use file_path::*; diff --git a/crates/wasi/src/filesystem/primitives/rustix/darwin/mod.rs b/crates/wasi/src/filesystem/primitives/rustix/darwin/mod.rs new file mode 100644 index 000000000000..aabc2afc5ad4 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/darwin/mod.rs @@ -0,0 +1 @@ +pub(crate) mod fs; diff --git a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/check.rs b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/check.rs new file mode 100644 index 000000000000..a8eaddc97d31 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/check.rs @@ -0,0 +1,40 @@ +use rustix::cstr; +use rustix::fs::{openat, statat, AtFlags, Mode, OFlags, CWD}; +use rustix::io::Errno; +use std::sync::atomic::{AtomicBool, Ordering::Relaxed}; + +static WORKING: AtomicBool = AtomicBool::new(false); +static CHECKED: AtomicBool = AtomicBool::new(false); + +#[inline] +pub(crate) fn beneath_supported() -> bool { + if WORKING.load(Relaxed) { + return true; + } + if CHECKED.load(Relaxed) { + return false; + } + check_beneath_supported() +} + +#[cold] +fn check_beneath_supported() -> bool { + // `RESOLVE_BENEATH` was introduced in FreeBSD 13, but opening `..` within + // the root directory re-opened the root directory. In FreeBSD 14, it fails + // as cap-std expects. + if let Ok(root) = openat( + CWD, + cstr!("/"), + OFlags::RDONLY | OFlags::CLOEXEC, + Mode::empty(), + ) { + // Unknown O_ flags get ignored but AT_ flags have strict checks, so we use that. + if let Err(Errno::NOTCAPABLE) = statat(root, cstr!(".."), AtFlags::RESOLVE_BENEATH) { + WORKING.store(true, Relaxed); + return true; + } + } + + CHECKED.store(true, Relaxed); + false +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/mod.rs b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/mod.rs new file mode 100644 index 000000000000..2f1ef26170cb --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/mod.rs @@ -0,0 +1,18 @@ +mod check; +mod open_entry_impl; +mod open_impl; +mod remove_dir_impl; +mod remove_file_impl; +mod set_permissions_impl; +mod set_times_impl; +mod stat_impl; + +pub(crate) use crate::fs::manually::canonicalize as canonicalize_impl; +pub(crate) use check::beneath_supported; +pub(crate) use open_entry_impl::open_entry_impl; +pub(crate) use open_impl::open_impl; +pub(crate) use remove_dir_impl::remove_dir_impl; +pub(crate) use remove_file_impl::remove_file_impl; +pub(crate) use set_permissions_impl::set_permissions_impl; +pub(crate) use set_times_impl::{set_times_impl, set_times_nofollow_impl}; +pub(crate) use stat_impl::stat_impl; diff --git a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/open_entry_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/open_entry_impl.rs new file mode 100644 index 000000000000..2bf5645bd116 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/open_entry_impl.rs @@ -0,0 +1,12 @@ +use crate::fs::{open_impl, OpenOptions}; +use std::ffi::OsStr; +use std::{fs, io}; + +#[inline(always)] +pub(crate) fn open_entry_impl( + start: &fs::File, + path: &OsStr, + options: &OpenOptions, +) -> io::Result { + open_impl(start, path.as_ref(), options) +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/open_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/open_impl.rs new file mode 100644 index 000000000000..e7c8f843c87e --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/open_impl.rs @@ -0,0 +1,30 @@ +use super::super::super::fs::compute_oflags; +use crate::fs::{errors, manually, OpenOptions}; +use io_lifetimes::FromFd; +use rustix::fs::{openat, Mode, OFlags, RawMode}; +use std::path::Path; +use std::{fs, io}; + +pub(crate) fn open_impl( + start: &fs::File, + path: &Path, + options: &OpenOptions, +) -> io::Result { + if !super::beneath_supported() { + return manually::open(start, path, options); + } + + let oflags = compute_oflags(options)? | OFlags::RESOLVE_BENEATH; + + let mode = if oflags.contains(OFlags::CREATE) { + Mode::from_bits((options.ext.mode & 0o7777) as RawMode).unwrap() + } else { + Mode::empty() + }; + + match openat(start, path, oflags, mode) { + Ok(file) => Ok(fs::File::from_into_fd(file)), + Err(rustix::io::Errno::NOTCAPABLE) => Err(errors::escape_attempt()), + Err(err) => Err(err.into()), + } +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/remove_dir_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/remove_dir_impl.rs new file mode 100644 index 000000000000..d486a375d657 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/remove_dir_impl.rs @@ -0,0 +1,16 @@ +use crate::fs::via_parent; +use rustix::fs::{unlinkat, AtFlags}; +use std::path::Path; +use std::{fs, io}; + +pub(crate) fn remove_dir_impl(start: &fs::File, path: &Path) -> io::Result<()> { + if !super::beneath_supported() { + return via_parent::remove_dir(start, path); + } + + Ok(unlinkat( + start, + path, + AtFlags::RESOLVE_BENEATH | AtFlags::REMOVEDIR, + )?) +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/remove_file_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/remove_file_impl.rs new file mode 100644 index 000000000000..c49767b50fc3 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/remove_file_impl.rs @@ -0,0 +1,12 @@ +use crate::fs::via_parent; +use rustix::fs::{unlinkat, AtFlags}; +use std::path::Path; +use std::{fs, io}; + +pub(crate) fn remove_file_impl(start: &fs::File, path: &Path) -> io::Result<()> { + if !super::beneath_supported() { + return via_parent::remove_file(start, path); + } + + Ok(unlinkat(start, path, AtFlags::RESOLVE_BENEATH)?) +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/set_permissions_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/set_permissions_impl.rs new file mode 100644 index 000000000000..ab9bc31144ec --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/set_permissions_impl.rs @@ -0,0 +1,21 @@ +use crate::fs::{Permissions, PermissionsExt}; +use rustix::fs::{chmodat, AtFlags, Mode}; +use std::path::Path; +use std::{fs, io}; + +pub(crate) fn set_permissions_impl( + start: &fs::File, + path: &Path, + perm: Permissions, +) -> io::Result<()> { + if !super::beneath_supported() { + return super::super::super::fs::set_permissions_manually(start, path, perm); + } + + Ok(chmodat( + start, + path, + Mode::from_raw_mode(perm.mode() as _), + AtFlags::RESOLVE_BENEATH, + )?) +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/set_times_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/set_times_impl.rs new file mode 100644 index 000000000000..16b520047c6e --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/set_times_impl.rs @@ -0,0 +1,45 @@ +use crate::fs::{to_timespec, via_parent, SystemTimeSpec}; +use rustix::fs::{utimensat, AtFlags, Timestamps}; +use std::path::Path; +use std::{fs, io}; + +pub(crate) fn set_times_impl( + start: &fs::File, + path: &Path, + atime: Option, + mtime: Option, +) -> io::Result<()> { + if !super::beneath_supported() { + return super::super::super::fs::set_times_manually(start, path, atime, mtime); + } + + let times = Timestamps { + last_access: to_timespec(atime)?, + last_modification: to_timespec(mtime)?, + }; + + Ok(utimensat(start, path, ×, AtFlags::RESOLVE_BENEATH)?) +} + +pub(crate) fn set_times_nofollow_impl( + start: &fs::File, + path: &Path, + atime: Option, + mtime: Option, +) -> io::Result<()> { + if !super::beneath_supported() { + return via_parent::set_times_nofollow(start, path, atime, mtime); + } + + let times = Timestamps { + last_access: to_timespec(atime)?, + last_modification: to_timespec(mtime)?, + }; + + Ok(utimensat( + start, + path, + ×, + AtFlags::RESOLVE_BENEATH | AtFlags::SYMLINK_NOFOLLOW, + )?) +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/stat_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/stat_impl.rs new file mode 100644 index 000000000000..aa4bbe651c2b --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/stat_impl.rs @@ -0,0 +1,22 @@ +use crate::fs::{manually, FollowSymlinks, ImplMetadataExt, Metadata}; +use rustix::fs::{statat, AtFlags}; +use std::path::Path; +use std::{fs, io}; + +pub(crate) fn stat_impl( + start: &fs::File, + path: &Path, + follow: FollowSymlinks, +) -> io::Result { + if !super::beneath_supported() { + return manually::stat(start, path, follow); + } + + let flags = AtFlags::RESOLVE_BENEATH + | if follow == FollowSymlinks::Yes { + AtFlags::empty() + } else { + AtFlags::SYMLINK_NOFOLLOW + }; + Ok(ImplMetadataExt::from_rustix(statat(start, path, flags)?)) +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/freebsd/mod.rs b/crates/wasi/src/filesystem/primitives/rustix/freebsd/mod.rs new file mode 100644 index 000000000000..aabc2afc5ad4 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/freebsd/mod.rs @@ -0,0 +1 @@ +pub(crate) mod fs; diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/access_unchecked.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/access_unchecked.rs new file mode 100644 index 000000000000..8bbda62229c6 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/access_unchecked.rs @@ -0,0 +1,38 @@ +use crate::fs::{AccessType, FollowSymlinks}; +use rustix::fs::{Access, AtFlags}; +use std::path::Path; +use std::{fs, io}; + +/// *Unsandboxed* function similar to `access`, but which does not perform +/// sandboxing. +pub(crate) fn access_unchecked( + start: &fs::File, + path: &Path, + type_: AccessType, + follow: FollowSymlinks, +) -> io::Result<()> { + let mut access_type = Access::empty(); + match type_ { + AccessType::Exists => access_type |= Access::EXISTS, + AccessType::Access(modes) => { + if modes.readable { + access_type |= Access::READ_OK; + } + if modes.writable { + access_type |= Access::WRITE_OK; + } + if modes.executable { + access_type |= Access::EXEC_OK; + } + } + } + + let atflags = match follow { + FollowSymlinks::Yes => AtFlags::empty(), + FollowSymlinks::No => AtFlags::SYMLINK_NOFOLLOW, + }; + + rustix::fs::accessat(start, path, access_type, atflags)?; + + Ok(()) +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/copy_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/copy_impl.rs new file mode 100644 index 000000000000..c4e5be7591f4 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/copy_impl.rs @@ -0,0 +1,248 @@ +// Implementation derived from `copy` in Rust's +// library/std/src/sys/unix/fs.rs at revision +// 108e90ca78f052c0c1c49c42a22c85620be19712. + +use crate::fs::{open, OpenOptions}; +#[cfg(any(target_os = "android", target_os = "linux"))] +use rustix::fs::copy_file_range; +#[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "watchos", + target_os = "visionos", +))] +use rustix::fs::{ + copyfile_state_alloc, copyfile_state_free, copyfile_state_get_copied, copyfile_state_t, + fclonefileat, fcopyfile, CloneFlags, CopyfileFlags, +}; +use std::path::Path; +use std::{fs, io}; + +fn open_from(start: &fs::File, path: &Path) -> io::Result<(fs::File, fs::Metadata)> { + let reader = open(start, path, OpenOptions::new().read(true))?; + let metadata = reader.metadata()?; + if !metadata.is_file() { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "the source path is not an existing regular file", + )); + } + Ok((reader, metadata)) +} + +#[cfg(not(target_os = "wasi"))] +fn open_to_and_set_permissions( + start: &fs::File, + path: &Path, + reader_metadata: fs::Metadata, +) -> io::Result<(fs::File, fs::Metadata)> { + use crate::fs::OpenOptionsExt; + use std::os::unix::fs::PermissionsExt; + + let perm = reader_metadata.permissions(); + let writer = open( + start, + path, + OpenOptions::new() + // create the file with the correct mode right away + .mode(perm.mode()) + .write(true) + .create(true) + .truncate(true), + )?; + let writer_metadata = writer.metadata()?; + if writer_metadata.is_file() { + // Set the correct file permissions, in case the file already existed. + // Don't set the permissions on already existing non-files like + // pipes/FIFOs or device nodes. + writer.set_permissions(perm)?; + } + Ok((writer, writer_metadata)) +} + +#[cfg(target_os = "wasi")] +fn open_to_and_set_permissions( + start: &fs::File, + path: &Path, + reader_metadata: fs::Metadata, +) -> io::Result<(fs::File, fs::Metadata)> { + let writer = open( + start, + path, + OpenOptions::new() + // create the file with the correct mode right away + .write(true) + .create(true) + .truncate(true), + )?; + let writer_metadata = writer.metadata()?; + Ok((writer, writer_metadata)) +} + +#[cfg(not(any( + target_os = "linux", + target_os = "android", + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "watchos", + target_os = "visionos", +)))] +pub(crate) fn copy_impl( + from_start: &fs::File, + from_path: &Path, + to_start: &fs::File, + to_path: &Path, +) -> io::Result { + let (mut reader, reader_metadata) = open_from(from_start, from_path)?; + let (mut writer, _) = open_to_and_set_permissions(to_start, to_path, reader_metadata)?; + + io::copy(&mut reader, &mut writer) +} + +#[cfg(any(target_os = "android", target_os = "linux"))] +pub(crate) fn copy_impl( + from_start: &fs::File, + from_path: &Path, + to_start: &fs::File, + to_path: &Path, +) -> io::Result { + use std::cmp; + use std::sync::atomic::{AtomicBool, Ordering}; + + // Kernel prior to 4.5 don't have copy_file_range + // We store the availability in a global to avoid unnecessary syscalls + static HAS_COPY_FILE_RANGE: AtomicBool = AtomicBool::new(true); + + let (mut reader, reader_metadata) = open_from(from_start, from_path)?; + let len = reader_metadata.len(); + let (mut writer, _) = open_to_and_set_permissions(to_start, to_path, reader_metadata)?; + + let has_copy_file_range = HAS_COPY_FILE_RANGE.load(Ordering::Relaxed); + let mut written = 0_u64; + while written < len { + let copy_result = if has_copy_file_range { + let bytes_to_copy = cmp::min(len - written, usize::MAX as u64); + + // `copy_file_range` takes a `usize`; convert with saturation so + // that we copy as many bytes as we can. + let bytes_to_copy = usize::try_from(bytes_to_copy).unwrap_or(usize::MAX); + + // We actually don't have to adjust the offsets, + // because copy_file_range adjusts the file offset automatically + let copy_result = copy_file_range(&reader, None, &writer, None, bytes_to_copy); + if let Err(copy_err) = copy_result { + match copy_err { + rustix::io::Errno::NOSYS | rustix::io::Errno::PERM => { + HAS_COPY_FILE_RANGE.store(false, Ordering::Relaxed); + } + _ => {} + } + } + copy_result + } else { + Err(rustix::io::Errno::NOSYS) + }; + match copy_result { + Ok(ret) => written += ret as u64, + Err(err) => { + match err { + rustix::io::Errno::NOSYS + | rustix::io::Errno::XDEV + | rustix::io::Errno::INVAL + | rustix::io::Errno::PERM => { + // Try fallback io::copy if either: + // - Kernel version is < 4.5 (ENOSYS) + // - Files are mounted on different fs (EXDEV) + // - copy_file_range is disallowed, for example by seccomp (EPERM) + // - copy_file_range cannot be used with pipes or device nodes (EINVAL) + assert_eq!(written, 0); + return io::copy(&mut reader, &mut writer); + } + _ => return Err(err.into()), + } + } + } + } + Ok(written) +} + +#[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "watchos", + target_os = "visionos", +))] +#[allow(non_upper_case_globals)] +#[allow(unsafe_code)] +pub(crate) fn copy_impl( + from_start: &fs::File, + from_path: &Path, + to_start: &fs::File, + to_path: &Path, +) -> io::Result { + use std::sync::atomic::{AtomicBool, Ordering}; + + struct FreeOnDrop(copyfile_state_t); + impl Drop for FreeOnDrop { + fn drop(&mut self) { + // Safety: This is the only place where we free the state, and we + // never let it escape. + unsafe { + copyfile_state_free(self.0).ok(); + } + } + } + + // MacOS prior to 10.12 don't support `fclonefileat` + // We store the availability in a global to avoid unnecessary syscalls + static HAS_FCLONEFILEAT: AtomicBool = AtomicBool::new(true); + + let (reader, reader_metadata) = open_from(from_start, from_path)?; + + // Opportunistically attempt to create a copy-on-write clone of `from_path` + // using `fclonefileat`. + if HAS_FCLONEFILEAT.load(Ordering::Relaxed) { + let clonefile_result = fclonefileat(&reader, to_start, to_path, CloneFlags::empty()); + match clonefile_result { + Ok(_) => return Ok(reader_metadata.len()), + Err(err) => match err { + // `fclonefileat` will fail on non-APFS volumes, if the + // destination already exists, or if the source and destination + // are on different devices. In all these cases `fcopyfile` + // should succeed. + rustix::io::Errno::NOTSUP | rustix::io::Errno::EXIST | rustix::io::Errno::XDEV => { + () + } + rustix::io::Errno::NOSYS => HAS_FCLONEFILEAT.store(false, Ordering::Relaxed), + _ => return Err(err.into()), + }, + } + } + + // Fall back to using `fcopyfile` if `fclonefileat` does not succeed. + let (writer, writer_metadata) = + open_to_and_set_permissions(to_start, to_path, reader_metadata)?; + + // We ensure that `FreeOnDrop` never contains a null pointer so it is + // always safe to call `copyfile_state_free` + let state = { + let state = copyfile_state_alloc()?; + FreeOnDrop(state) + }; + + let flags = if writer_metadata.is_file() { + CopyfileFlags::ALL + } else { + CopyfileFlags::DATA + }; + + // Safety: We allocated `state` above so it's still live here. + unsafe { + fcopyfile(&reader, &writer, state.0, flags)?; + + Ok(copyfile_state_get_copied(state.0)?) + } +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/create_dir_unchecked.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/create_dir_unchecked.rs new file mode 100644 index 000000000000..652162c76e26 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/create_dir_unchecked.rs @@ -0,0 +1,19 @@ +use crate::fs::DirOptions; +use rustix::fs::{mkdirat, Mode, RawMode}; +use std::path::Path; +use std::{fs, io}; + +/// *Unsandboxed* function similar to `create_dir`, but which does not perform +/// sandboxing. +pub(crate) fn create_dir_unchecked( + start: &fs::File, + path: &Path, + options: &DirOptions, +) -> io::Result<()> { + #[cfg(not(target_os = "wasi"))] + let raw_mode = options.ext.mode as RawMode; + #[cfg(target_os = "wasi")] + let raw_mode = 0; + + Ok(mkdirat(start, path, Mode::from_bits(raw_mode).unwrap())?) +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/cvt.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/cvt.rs new file mode 100644 index 000000000000..a6aa15b35fd4 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/cvt.rs @@ -0,0 +1,19 @@ +use std::io; + +#[allow(dead_code)] +pub(crate) fn cvt_i32(t: i32) -> io::Result { + if t == -1 { + Err(io::Error::last_os_error()) + } else { + Ok(t) + } +} + +#[allow(dead_code)] +pub(crate) fn cvt_i64(t: i64) -> io::Result { + if t == -1 { + Err(io::Error::last_os_error()) + } else { + Ok(t) + } +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/dir_entry_inner.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/dir_entry_inner.rs new file mode 100644 index 000000000000..5b82b624ec2f --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/dir_entry_inner.rs @@ -0,0 +1,96 @@ +use crate::fs::{ + FileType, FollowSymlinks, Metadata, MetadataExt, OpenOptions, ReadDir, ReadDirInner, +}; +use rustix::fs::DirEntry; +use std::ffi::{OsStr, OsString}; +#[cfg(unix)] +use std::os::unix::ffi::OsStrExt; +#[cfg(target_os = "wasi")] +use std::os::wasi::ffi::OsStrExt; +use std::{fmt, fs, io}; + +pub(crate) struct DirEntryInner { + pub(super) rustix: DirEntry, + pub(super) read_dir: ReadDirInner, +} + +impl DirEntryInner { + #[inline] + pub(crate) fn open(&self, options: &OpenOptions) -> io::Result { + self.read_dir.open(self.file_name_bytes(), options) + } + + #[inline] + pub(crate) fn metadata(&self) -> io::Result { + self.read_dir.metadata(self.file_name_bytes()) + } + + #[inline] + pub(crate) fn remove_file(&self) -> io::Result<()> { + self.read_dir.remove_file(self.file_name_bytes()) + } + + #[inline] + pub(crate) fn read_dir(&self, follow: FollowSymlinks) -> io::Result { + self.read_dir.read_dir(self.file_name_bytes(), follow) + } + + #[inline] + pub(crate) fn remove_dir(&self) -> io::Result<()> { + self.read_dir.remove_dir(self.file_name_bytes()) + } + + #[cfg(not(any(target_os = "illumos", target_os = "solaris")))] + #[inline] + #[allow(clippy::unnecessary_wraps)] + pub(crate) fn file_type(&self) -> io::Result { + use crate::fs::ImplFileTypeExt; + + Ok(match self.rustix.file_type() { + rustix::fs::FileType::Directory => FileType::dir(), + rustix::fs::FileType::RegularFile => FileType::file(), + rustix::fs::FileType::Symlink => FileType::ext(ImplFileTypeExt::symlink()), + #[cfg(not(target_os = "wasi"))] + rustix::fs::FileType::Fifo => FileType::ext(ImplFileTypeExt::fifo()), + #[cfg(not(target_os = "wasi"))] + rustix::fs::FileType::Socket => FileType::ext(ImplFileTypeExt::socket()), + rustix::fs::FileType::CharacterDevice => FileType::ext(ImplFileTypeExt::char_device()), + rustix::fs::FileType::BlockDevice => FileType::ext(ImplFileTypeExt::block_device()), + rustix::fs::FileType::Unknown => FileType::unknown(), + }) + } + + #[cfg(any(target_os = "illumos", target_os = "solaris"))] + #[inline] + pub(crate) fn file_type(&self) -> io::Result { + // These platforms don't have the file type on the dirent, so we must lstat the file. + self.metadata().map(|m| m.file_type()) + } + + #[inline] + pub(crate) fn file_name(&self) -> OsString { + self.file_name_bytes().to_os_string() + } + + #[inline] + pub(crate) fn ino(&self) -> u64 { + self.rustix.ino() + } + + #[inline] + pub(crate) fn is_same_file(&self, metadata: &Metadata) -> io::Result { + let self_md = self.metadata()?; + Ok(self_md.ino() == metadata.ino() && self_md.dev() == metadata.dev()) + } + + fn file_name_bytes(&self) -> &OsStr { + OsStr::from_bytes(self.rustix.file_name().to_bytes()) + } +} + +impl fmt::Debug for DirEntryInner { + // Like libstd's version, but doesn't print the path. + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("DirEntry").field(&self.file_name()).finish() + } +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/dir_options_ext.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/dir_options_ext.rs new file mode 100644 index 000000000000..07b56bcf9afb --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/dir_options_ext.rs @@ -0,0 +1,21 @@ +#[derive(Debug, Clone)] +pub(crate) struct DirOptionsExt { + pub(super) mode: u32, +} + +impl DirOptionsExt { + pub(crate) const fn new() -> Self { + Self { + // The default value; see + // + mode: 0o777, + } + } +} + +impl crate::fs::DirBuilderExt for DirOptionsExt { + fn mode(&mut self, mode: u32) -> &mut Self { + self.mode = mode; + self + } +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/dir_utils.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/dir_utils.rs new file mode 100644 index 000000000000..50011e71de8c --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/dir_utils.rs @@ -0,0 +1,232 @@ +use crate::fs::OpenOptions; +use ambient_authority::AmbientAuthority; +use rustix::fs::OFlags; +use std::ffi::{OsStr, OsString}; +use std::ops::Deref; +#[cfg(unix)] +use std::os::unix::{ + ffi::{OsStrExt, OsStringExt}, + fs::OpenOptionsExt, +}; +#[cfg(target_os = "wasi")] +use std::os::wasi::{ + ffi::{OsStrExt, OsStringExt}, + fs::OpenOptionsExt, +}; +use std::path::{Path, PathBuf}; +use std::{fs, io}; + +/// Rust's `Path` implicitly strips redundant slashes, however they aren't +/// redundant in one case: at the end of a path they indicate that a path is +/// expected to name a directory. +pub(crate) fn path_requires_dir(path: &Path) -> bool { + let bytes = path.as_os_str().as_bytes(); + + // If a path ends with '/' or '.', it's a directory. These aren't the only + // cases, but they are the only cases that Rust's `Path` implicitly + // normalizes away. + bytes.ends_with(b"/") || bytes.ends_with(b"/.") +} + +/// Rust's `Path` implicitly strips trailing `.` components, however they +/// aren't redundant in one case: at the end of a path they are the final path +/// component, which has different path lookup behavior. +pub(crate) fn path_has_trailing_dot(path: &Path) -> bool { + let mut bytes = path.as_os_str().as_bytes(); + + // If a path ends with '.' followed by any number of '/'s, it's a trailing dot. + while let Some((last, rest)) = bytes.split_last() { + if *last == b'/' { + bytes = rest; + } else { + break; + } + } + + bytes.ends_with(b"/.") || bytes == b"." +} + +/// Rust's `Path` implicitly strips trailing `/`s, however they aren't +/// redundant in one case: at the end of a path they are the final path +/// component, which has different path lookup behavior. +pub(crate) fn path_has_trailing_slash(path: &Path) -> bool { + let bytes = path.as_os_str().as_bytes(); + + bytes.ends_with(b"/") +} + +/// Append a trailing `/`. This can be used to require that the given `path` +/// names a directory. +pub(crate) fn append_dir_suffix(path: PathBuf) -> PathBuf { + let mut bytes = path.into_os_string().into_vec(); + bytes.push(b'/'); + OsString::from_vec(bytes).into() +} + +/// Strip trailing `/`s, unless this reduces `path` to `/` itself. This is +/// used by `create_dir` and others to prevent paths like `foo/` from +/// canonicalizing to `foo/.` since these syscalls treat these differently. +#[allow(clippy::indexing_slicing)] +pub(crate) fn strip_dir_suffix(path: &Path) -> impl Deref + '_ { + let mut bytes = path.as_os_str().as_bytes(); + while bytes.len() > 1 && *bytes.last().unwrap() == b'/' { + bytes = &bytes[..bytes.len() - 1]; + } + OsStr::from_bytes(bytes).as_ref() +} + +/// Return an `OpenOptions` for opening directories. +pub(crate) fn dir_options() -> OpenOptions { + OpenOptions::new().read(true).dir_required(true).clone() +} + +/// Like `dir_options`, but additionally request the ability to read the +/// directory entries. +pub(crate) fn readdir_options() -> OpenOptions { + OpenOptions::new() + .read(true) + .dir_required(true) + .readdir_required(true) + .clone() +} + +/// Return an `OpenOptions` for canonicalizing paths. +pub(crate) fn canonicalize_options() -> OpenOptions { + OpenOptions::new().read(true).clone() +} + +/// Open a directory named by a bare path, using the host process' ambient +/// authority. +/// +/// # Ambient Authority +/// +/// This function is not sandboxed and may trivially access any path that the +/// host process has access to. +pub(crate) fn open_ambient_dir_impl( + path: &Path, + ambient_authority: AmbientAuthority, +) -> io::Result { + let _ = ambient_authority; + + let mut options = fs::OpenOptions::new(); + options.read(true); + + // This is for `std::fs`, so we don't have `dir_required`, so set + // `O_DIRECTORY` manually. + options.custom_flags((OFlags::DIRECTORY | target_o_path()).bits() as i32); + + options.open(path) +} + +/// Use `O_PATH` on platforms which have it, or none otherwise. +#[inline] +pub(crate) const fn target_o_path() -> OFlags { + #[cfg(any( + target_os = "android", + target_os = "emscripten", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "linux", + target_os = "redox", + ))] + { + OFlags::PATH + } + + #[cfg(any( + target_os = "dragonfly", + target_os = "ios", + target_os = "macos", + target_os = "tvos", + target_os = "watchos", + target_os = "visionos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "wasi", + target_os = "illumos", + target_os = "solaris", + ))] + { + OFlags::empty() + } +} + +#[cfg(racy_asserts)] +#[test] +fn append_dir_suffix_tests() { + assert!(append_dir_suffix(Path::new("foo").to_path_buf()) + .display() + .to_string() + .ends_with('/')); +} + +#[test] +fn strip_dir_suffix_tests() { + assert_eq!(&*strip_dir_suffix(Path::new("/foo//")), Path::new("/foo")); + assert_eq!(&*strip_dir_suffix(Path::new("/foo/")), Path::new("/foo")); + assert_eq!(&*strip_dir_suffix(Path::new("foo/")), Path::new("foo")); + assert_eq!(&*strip_dir_suffix(Path::new("foo")), Path::new("foo")); + assert_eq!(&*strip_dir_suffix(Path::new("/")), Path::new("/")); + assert_eq!(&*strip_dir_suffix(Path::new("//")), Path::new("/")); + assert_eq!(&*strip_dir_suffix(Path::new("/.")), Path::new("/.")); + assert_eq!(&*strip_dir_suffix(Path::new("//.")), Path::new("/.")); + assert_eq!(&*strip_dir_suffix(Path::new(".")), Path::new(".")); + assert_eq!(&*strip_dir_suffix(Path::new("foo/.")), Path::new("foo/.")); +} + +#[test] +fn test_path_requires_dir() { + assert!(!path_requires_dir(Path::new("."))); + assert!(path_requires_dir(Path::new("/"))); + assert!(path_requires_dir(Path::new("//"))); + assert!(path_requires_dir(Path::new("/./."))); + assert!(path_requires_dir(Path::new("foo/"))); + assert!(path_requires_dir(Path::new("foo//"))); + assert!(path_requires_dir(Path::new("foo//."))); + assert!(path_requires_dir(Path::new("foo/./."))); + assert!(path_requires_dir(Path::new("foo/./"))); + assert!(path_requires_dir(Path::new("foo/.//"))); +} + +#[test] +fn test_path_has_trailing_dot() { + assert!(!path_has_trailing_dot(Path::new("foo"))); + assert!(!path_has_trailing_dot(Path::new("foo."))); + + assert!(!path_has_trailing_dot(Path::new("/./foo"))); + assert!(!path_has_trailing_dot(Path::new(".."))); + assert!(!path_has_trailing_dot(Path::new("/.."))); + + assert!(!path_has_trailing_dot(Path::new("/"))); + assert!(!path_has_trailing_dot(Path::new("//"))); + assert!(!path_has_trailing_dot(Path::new("foo//"))); + assert!(!path_has_trailing_dot(Path::new("foo/"))); + + assert!(path_has_trailing_dot(Path::new("."))); + + assert!(path_has_trailing_dot(Path::new("/./."))); + assert!(path_has_trailing_dot(Path::new("foo//."))); + assert!(path_has_trailing_dot(Path::new("foo/./."))); + assert!(path_has_trailing_dot(Path::new("foo/./"))); + assert!(path_has_trailing_dot(Path::new("foo/.//"))); +} + +#[test] +fn test_path_has_trailing_slash() { + assert!(path_has_trailing_slash(Path::new("/"))); + assert!(path_has_trailing_slash(Path::new("//"))); + assert!(path_has_trailing_slash(Path::new("foo//"))); + assert!(path_has_trailing_slash(Path::new("foo/"))); + assert!(path_has_trailing_slash(Path::new("foo/./"))); + assert!(path_has_trailing_slash(Path::new("foo/.//"))); + + assert!(!path_has_trailing_slash(Path::new("foo"))); + assert!(!path_has_trailing_slash(Path::new("foo."))); + assert!(!path_has_trailing_slash(Path::new("/./foo"))); + assert!(!path_has_trailing_slash(Path::new(".."))); + assert!(!path_has_trailing_slash(Path::new("/.."))); + assert!(!path_has_trailing_slash(Path::new("."))); + assert!(!path_has_trailing_slash(Path::new("/./."))); + assert!(!path_has_trailing_slash(Path::new("foo//."))); + assert!(!path_has_trailing_slash(Path::new("foo/./."))); +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/errors.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/errors.rs new file mode 100644 index 000000000000..8a9de56f322e --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/errors.rs @@ -0,0 +1,27 @@ +use std::io; + +#[cfg(any(target_os = "android", target_os = "linux"))] +#[cold] +pub(crate) fn invalid_flags() -> io::Error { + rustix::io::Errno::INVAL.into() +} + +#[cold] +pub(crate) fn no_such_file_or_directory() -> io::Error { + rustix::io::Errno::NOENT.into() +} + +#[cold] +pub(crate) fn is_directory() -> io::Error { + rustix::io::Errno::ISDIR.into() +} + +#[cold] +pub(crate) fn is_not_directory() -> io::Error { + rustix::io::Errno::NOTDIR.into() +} + +#[cold] +pub(crate) fn too_many_symlinks() -> io::Error { + rustix::io::Errno::LOOP.into() +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/file_path.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/file_path.rs new file mode 100644 index 000000000000..f68d36f6b362 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/file_path.rs @@ -0,0 +1,19 @@ +use crate::fs::file_path_by_searching; +#[cfg(not(any(target_os = "wasi", target_os = "fuchsia")))] +use rustix::termios::ttyname; +#[cfg(not(any(target_os = "wasi", target_os = "fuchsia")))] +use std::ffi::OsString; +use std::fs; +#[cfg(unix)] +use std::os::unix::ffi::OsStringExt; +use std::path::PathBuf; + +pub(crate) fn file_path_by_ttyname_or_seaching(file: &fs::File) -> Option { + // If it happens to be a tty, we can look up its name. + #[cfg(not(any(target_os = "wasi", target_os = "fuchsia")))] + if let Ok(name) = ttyname(file, Vec::new()) { + return Some(OsString::from_vec(name.into_bytes()).into()); + } + + file_path_by_searching(file) +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/file_type_ext.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/file_type_ext.rs new file mode 100644 index 000000000000..643dc0dd2abb --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/file_type_ext.rs @@ -0,0 +1,114 @@ +use crate::fs::FileType; +use rustix::fs::RawMode; +use std::{fs, io}; + +/// A type that implements `FileTypeExt` for this platform. +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] +pub(crate) enum ImplFileTypeExt { + Symlink, + BlockDevice, + CharDevice, + Fifo, + Socket, +} + +impl ImplFileTypeExt { + /// Constructs a new instance of `FileType` from the given + /// [`std::fs::File`] and [`std::fs::FileType`]. + #[inline] + #[allow(clippy::unnecessary_wraps)] + pub(crate) fn from(_file: &fs::File, metadata: &fs::Metadata) -> io::Result { + // On `rustix`-style platforms, the `Metadata` has everything we need. + Ok(Self::from_just_metadata(metadata)) + } + + /// Constructs a new instance of `FileType` from the given + /// [`std::fs::Metadata`]. + #[inline] + pub(crate) fn from_just_metadata(metadata: &fs::Metadata) -> FileType { + let std = metadata.file_type(); + Self::from_std(std) + } + + /// Constructs a new instance of `Self` from the given + /// [`std::fs::FileType`]. + #[inline] + pub(crate) fn from_std(std: fs::FileType) -> FileType { + use rustix::fs::FileTypeExt; + if std.is_file() { + FileType::file() + } else if std.is_dir() { + FileType::dir() + } else if std.is_symlink() { + FileType::ext(Self::Symlink) + } else if std.is_block_device() { + FileType::ext(Self::BlockDevice) + } else if std.is_char_device() { + FileType::ext(Self::CharDevice) + } else { + #[cfg(not(target_os = "wasi"))] + if std.is_fifo() { + return FileType::ext(Self::Fifo); + } + #[cfg(not(target_os = "wasi"))] + if std.is_socket() { + return FileType::ext(Self::Socket); + } + FileType::unknown() + } + } + + /// Constructs a new instance of `FileType` from the given + /// [`RawMode`]. + #[inline] + pub(crate) const fn from_raw_mode(mode: RawMode) -> FileType { + match rustix::fs::FileType::from_raw_mode(mode) { + rustix::fs::FileType::RegularFile => FileType::file(), + rustix::fs::FileType::Directory => FileType::dir(), + rustix::fs::FileType::Symlink => FileType::ext(Self::symlink()), + #[cfg(not(target_os = "wasi"))] + rustix::fs::FileType::Fifo => FileType::ext(Self::fifo()), + rustix::fs::FileType::CharacterDevice => FileType::ext(Self::char_device()), + rustix::fs::FileType::BlockDevice => FileType::ext(Self::block_device()), + #[cfg(not(target_os = "wasi"))] + rustix::fs::FileType::Socket => FileType::ext(Self::socket()), + _ => FileType::unknown(), + } + } + + /// Creates a `FileType` for which `is_symlink()` returns `true`. + #[inline] + pub(crate) const fn symlink() -> Self { + Self::Symlink + } + + /// Creates a `FileType` for which `is_block_device()` returns `true`. + #[inline] + pub(crate) const fn block_device() -> Self { + Self::BlockDevice + } + + /// Creates a `FileType` for which `is_char_device()` returns `true`. + #[inline] + pub(crate) const fn char_device() -> Self { + Self::CharDevice + } + + /// Creates a `FileType` for which `is_fifo()` returns `true`. + #[inline] + pub(crate) const fn fifo() -> Self { + Self::Fifo + } + + /// Creates a `FileType` for which `is_socket()` returns `true`. + #[inline] + pub(crate) const fn socket() -> Self { + Self::Socket + } + + /// Tests whether this file type represents a symbolic link. + #[inline] + pub(crate) fn is_symlink(&self) -> bool { + *self == Self::Symlink + } +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/hard_link_unchecked.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/hard_link_unchecked.rs new file mode 100644 index 000000000000..a9f4bccee38e --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/hard_link_unchecked.rs @@ -0,0 +1,24 @@ +use rustix::fs::{linkat, AtFlags}; +use std::path::Path; +use std::{fs, io}; + +/// *Unsandboxed* function similar to `hard_link`, but which does not perform +/// sandboxing. +/// +/// Even though POSIX `linkat` has the ability to follow symlinks in +/// `old_path`, using `AT_SYMLINK_FOLLOW`, Rust's `hard_link` doesn't need +/// that, so we don't expose it here. +pub(crate) fn hard_link_unchecked( + old_start: &fs::File, + old_path: &Path, + new_start: &fs::File, + new_path: &Path, +) -> io::Result<()> { + Ok(linkat( + old_start, + old_path, + new_start, + new_path, + AtFlags::empty(), + )?) +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/is_file_read_write_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/is_file_read_write_impl.rs new file mode 100644 index 000000000000..ff4388f69ecf --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/is_file_read_write_impl.rs @@ -0,0 +1,43 @@ +use rustix::fd::{AsFd, BorrowedFd}; +use rustix::fs::{fcntl_getfl, OFlags}; +use std::{fs, io}; + +#[inline] +pub(crate) fn is_file_read_write_impl(file: &fs::File) -> io::Result<(bool, bool)> { + Ok(is_file_read_write(file)?) +} + +/// `fcntl(fd, F_GETFL) & O_ACCMODE` +/// +/// Returns a pair of booleans indicating whether the file descriptor is +/// readable and/or writable, respectively. This is only reliable on files; for +/// example, it doesn't reflect whether sockets have been shut down; for +/// general I/O handle support, use [`io::is_read_write`]. +#[inline] +fn is_file_read_write(fd: Fd) -> io::Result<(bool, bool)> { + _is_file_read_write(fd.as_fd()) +} + +fn _is_file_read_write(fd: BorrowedFd<'_>) -> io::Result<(bool, bool)> { + let mode = fcntl_getfl(fd)?; + + // Check for `O_PATH`. + #[cfg(any( + target_os = "linux", + target_os = "android", + target_os = "emscripten", + target_os = "fuchsia" + ))] + if mode.contains(OFlags::PATH) { + return Ok((false, false)); + } + + // Use `RWMODE` rather than `ACCMODE` as `ACCMODE` may include `O_PATH`. + // We handled `O_PATH` above. + match mode & OFlags::RWMODE { + OFlags::RDONLY => Ok((true, false)), + OFlags::RDWR => Ok((true, true)), + OFlags::WRONLY => Ok((false, true)), + _ => unreachable!(), + } +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/is_root_dir.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/is_root_dir.rs new file mode 100644 index 000000000000..64a0838ef57b --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/is_root_dir.rs @@ -0,0 +1,6 @@ +use crate::fs::{Metadata, ReadDir}; +use std::{fs, io}; + +pub(crate) fn is_root_dir(dir: &fs::File, parent_iter: &ReadDir) -> io::Result { + Ok(Metadata::from_file(dir)?.is_same_file(&parent_iter.inner.self_metadata()?)) +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/is_same_file.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/is_same_file.rs new file mode 100644 index 000000000000..c6acf9cae26f --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/is_same_file.rs @@ -0,0 +1,33 @@ +use crate::fs::{Metadata, MetadataExt}; +use std::{fs, io}; + +/// Determine if `a` and `b` refer to the same inode on the same device. +pub(crate) fn is_same_file(a: &fs::File, b: &fs::File) -> io::Result { + let a_metadata = Metadata::from_file(a)?; + let b_metadata = Metadata::from_file(b)?; + is_same_file_metadata(&a_metadata, &b_metadata) +} + +/// Determine if `a` and `b` are metadata for the same inode on the same +/// device. +pub(crate) fn is_same_file_metadata(a: &Metadata, b: &Metadata) -> io::Result { + Ok(a.dev() == b.dev() && a.ino() == b.ino()) +} + +/// Determine if `a` and `b` definitely refer to different inodes. +/// +/// This is similar to `is_same_file`, but is conservative, and doesn't depend +/// on nightly-only features. +#[allow(dead_code)] +pub(crate) fn is_different_file(a: &fs::File, b: &fs::File) -> io::Result { + is_same_file(a, b).map(|same| !same) +} + +/// Determine if `a` and `b` are metadata for definitely different inodes. +/// +/// This is similar to `is_same_file_metadata`, but is conservative, and +/// doesn't depend on nightly-only features. +#[allow(dead_code)] +pub(crate) fn is_different_file_metadata(a: &Metadata, b: &Metadata) -> io::Result { + is_same_file_metadata(a, b).map(|same| !same) +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/metadata_ext.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/metadata_ext.rs new file mode 100644 index 000000000000..b13ab13684be --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/metadata_ext.rs @@ -0,0 +1,363 @@ +#![allow(clippy::useless_conversion)] + +use crate::fs::{ImplFileTypeExt, ImplPermissionsExt, Metadata}; +use crate::time::{Duration, SystemClock, SystemTime}; +#[cfg(target_os = "linux")] +use rustix::fs::{makedev, Statx, StatxFlags}; +use rustix::fs::{RawMode, Stat}; +use std::{fs, io}; + +#[derive(Debug, Clone)] +pub(crate) struct ImplMetadataExt { + dev: u64, + ino: u64, + #[cfg(not(target_os = "wasi"))] + mode: u32, + nlink: u64, + #[cfg(not(target_os = "wasi"))] + uid: u32, + #[cfg(not(target_os = "wasi"))] + gid: u32, + #[cfg(not(target_os = "wasi"))] + rdev: u64, + #[cfg(not(target_os = "wasi"))] + size: u64, + #[cfg(not(target_os = "wasi"))] + atime: i64, + #[cfg(not(target_os = "wasi"))] + atime_nsec: i64, + #[cfg(not(target_os = "wasi"))] + mtime: i64, + #[cfg(not(target_os = "wasi"))] + mtime_nsec: i64, + #[cfg(not(target_os = "wasi"))] + ctime: i64, + #[cfg(not(target_os = "wasi"))] + ctime_nsec: i64, + #[cfg(not(target_os = "wasi"))] + blksize: u64, + #[cfg(not(target_os = "wasi"))] + blocks: u64, +} + +impl ImplMetadataExt { + /// Constructs a new instance of `Self` from the given [`std::fs::File`] + /// and [`std::fs::Metadata`]. + #[inline] + #[allow(clippy::unnecessary_wraps)] + pub(crate) fn from(_file: &fs::File, std: &fs::Metadata) -> io::Result { + // On `rustix`-style platforms, the `Metadata` has everything we need. + Ok(Self::from_just_metadata(std)) + } + + /// Constructs a new instance of `Self` from the given + /// [`std::fs::Metadata`]. + #[inline] + pub(crate) fn from_just_metadata(std: &fs::Metadata) -> Self { + use rustix::fs::MetadataExt; + Self { + dev: std.dev(), + ino: std.ino(), + #[cfg(not(target_os = "wasi"))] + mode: std.mode(), + nlink: std.nlink(), + #[cfg(not(target_os = "wasi"))] + uid: std.uid(), + #[cfg(not(target_os = "wasi"))] + gid: std.gid(), + #[cfg(not(target_os = "wasi"))] + rdev: std.rdev(), + #[cfg(not(target_os = "wasi"))] + size: std.size(), + #[cfg(not(target_os = "wasi"))] + atime: std.atime(), + #[cfg(not(target_os = "wasi"))] + atime_nsec: std.atime_nsec(), + #[cfg(not(target_os = "wasi"))] + mtime: std.mtime(), + #[cfg(not(target_os = "wasi"))] + mtime_nsec: std.mtime_nsec(), + #[cfg(not(target_os = "wasi"))] + ctime: std.ctime(), + #[cfg(not(target_os = "wasi"))] + ctime_nsec: std.ctime_nsec(), + #[cfg(not(target_os = "wasi"))] + blksize: std.blksize(), + #[cfg(not(target_os = "wasi"))] + blocks: std.blocks(), + } + } + + /// Constructs a new instance of `Metadata` from the given `Stat`. + #[inline] + #[allow(unused_comparisons)] // NB: rust-lang/rust#115823 requires this here instead of on `st_dev` processing below + pub(crate) fn from_rustix(stat: Stat) -> Metadata { + Metadata { + file_type: ImplFileTypeExt::from_raw_mode(stat.st_mode as RawMode), + len: u64::try_from(stat.st_size).unwrap(), + #[cfg(not(target_os = "wasi"))] + permissions: ImplPermissionsExt::from_raw_mode(stat.st_mode as RawMode), + #[cfg(target_os = "wasi")] + permissions: ImplPermissionsExt::default(), + + #[cfg(not(target_os = "wasi"))] + modified: system_time_from_rustix( + stat.st_mtime.try_into().unwrap(), + stat.st_mtime_nsec as _, + ), + #[cfg(not(target_os = "wasi"))] + accessed: system_time_from_rustix( + stat.st_atime.try_into().unwrap(), + stat.st_atime_nsec as _, + ), + + #[cfg(target_os = "wasi")] + modified: system_time_from_rustix(stat.st_mtim.tv_sec, stat.st_mtim.tv_nsec as _), + #[cfg(target_os = "wasi")] + accessed: system_time_from_rustix(stat.st_atim.tv_sec, stat.st_atim.tv_nsec as _), + + #[cfg(any( + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd", + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "watchos", + target_os = "visionos", + ))] + created: system_time_from_rustix( + stat.st_birthtime.try_into().unwrap(), + stat.st_birthtime_nsec as _, + ), + + // `stat.st_ctime` is the latest status change; we want the creation. + #[cfg(not(any( + target_os = "freebsd", + target_os = "openbsd", + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "watchos", + target_os = "visionos", + target_os = "netbsd" + )))] + created: None, + + ext: Self { + // The type of `st_dev` is `dev_t` which is signed on some + // platforms and unsigned on other platforms. A `u64` is enough + // to work for all unsigned platforms, and for signed platforms + // perform a sign extension to `i64` and then view that as an + // unsigned 64-bit number instead. + // + // Note that the `unused_comparisons` is ignored here for + // platforms where it's unsigned since the first branch here + // will never be taken. + dev: if stat.st_dev < 0 { + i64::try_from(stat.st_dev).unwrap() as u64 + } else { + u64::try_from(stat.st_dev).unwrap() + }, + ino: stat.st_ino.into(), + #[cfg(not(target_os = "wasi"))] + mode: u32::from(stat.st_mode), + nlink: u64::from(stat.st_nlink), + #[cfg(not(target_os = "wasi"))] + uid: stat.st_uid, + #[cfg(not(target_os = "wasi"))] + gid: stat.st_gid, + #[cfg(not(target_os = "wasi"))] + rdev: u64::try_from(stat.st_rdev).unwrap(), + #[cfg(not(target_os = "wasi"))] + size: u64::try_from(stat.st_size).unwrap(), + #[cfg(not(target_os = "wasi"))] + atime: i64::try_from(stat.st_atime).unwrap(), + #[cfg(not(target_os = "wasi"))] + atime_nsec: stat.st_atime_nsec as _, + #[cfg(not(target_os = "wasi"))] + mtime: i64::try_from(stat.st_mtime).unwrap(), + #[cfg(not(target_os = "wasi"))] + mtime_nsec: stat.st_mtime_nsec as _, + #[cfg(not(target_os = "wasi"))] + ctime: i64::try_from(stat.st_ctime).unwrap(), + #[cfg(not(target_os = "wasi"))] + ctime_nsec: stat.st_ctime_nsec as _, + #[cfg(not(target_os = "wasi"))] + blksize: u64::try_from(stat.st_blksize).unwrap(), + #[cfg(not(target_os = "wasi"))] + blocks: u64::try_from(stat.st_blocks).unwrap(), + }, + } + } + + /// Constructs a new instance of `Metadata` from the given `Statx`. + #[cfg(target_os = "linux")] + #[inline] + pub(crate) fn from_rustix_statx(statx: Statx) -> Metadata { + Metadata { + file_type: ImplFileTypeExt::from_raw_mode(RawMode::from(statx.stx_mode)), + len: u64::try_from(statx.stx_size).unwrap(), + permissions: ImplPermissionsExt::from_raw_mode(RawMode::from(statx.stx_mode)), + modified: if statx.stx_mask & StatxFlags::MTIME.bits() != 0 { + system_time_from_rustix(statx.stx_mtime.tv_sec, statx.stx_mtime.tv_nsec as _) + } else { + None + }, + accessed: if statx.stx_mask & StatxFlags::ATIME.bits() != 0 { + system_time_from_rustix(statx.stx_atime.tv_sec, statx.stx_atime.tv_nsec as _) + } else { + None + }, + created: if statx.stx_mask & StatxFlags::BTIME.bits() != 0 { + system_time_from_rustix(statx.stx_btime.tv_sec, statx.stx_btime.tv_nsec as _) + } else { + None + }, + + ext: Self { + dev: makedev(statx.stx_dev_major, statx.stx_dev_minor), + ino: statx.stx_ino.into(), + mode: u32::from(statx.stx_mode), + nlink: u64::from(statx.stx_nlink), + uid: statx.stx_uid, + gid: statx.stx_gid, + rdev: makedev(statx.stx_rdev_major, statx.stx_rdev_minor), + size: statx.stx_size, + atime: i64::from(statx.stx_atime.tv_sec), + atime_nsec: statx.stx_atime.tv_nsec as _, + mtime: i64::from(statx.stx_mtime.tv_sec), + mtime_nsec: statx.stx_mtime.tv_nsec as _, + ctime: i64::from(statx.stx_ctime.tv_sec), + ctime_nsec: statx.stx_ctime.tv_nsec as _, + blksize: u64::from(statx.stx_blksize), + blocks: statx.stx_blocks, + }, + } + } + + /// Determine if `self` and `other` refer to the same inode on the same + /// device. + pub(crate) const fn is_same_file(&self, other: &Self) -> bool { + self.dev == other.dev && self.ino == other.ino + } +} + +#[allow(clippy::similar_names)] +fn system_time_from_rustix(sec: i64, nsec: u64) -> Option { + if sec >= 0 { + SystemClock::UNIX_EPOCH.checked_add(Duration::new(u64::try_from(sec).unwrap(), nsec as _)) + } else { + SystemClock::UNIX_EPOCH + .checked_sub(Duration::new(u64::try_from(-sec).unwrap(), 0)) + .map(|t| t.checked_add(Duration::new(0, nsec as u32))) + .flatten() + } +} + +impl crate::fs::MetadataExt for ImplMetadataExt { + #[inline] + fn dev(&self) -> u64 { + self.dev + } + + #[inline] + fn ino(&self) -> u64 { + self.ino + } + + #[cfg(not(target_os = "wasi"))] + #[inline] + fn mode(&self) -> u32 { + self.mode + } + + #[inline] + fn nlink(&self) -> u64 { + self.nlink + } + + #[cfg(not(target_os = "wasi"))] + #[inline] + fn uid(&self) -> u32 { + self.uid + } + + #[cfg(not(target_os = "wasi"))] + #[inline] + fn gid(&self) -> u32 { + self.gid + } + + #[cfg(not(target_os = "wasi"))] + #[inline] + fn rdev(&self) -> u64 { + self.rdev + } + + #[cfg(not(target_os = "wasi"))] + #[inline] + fn size(&self) -> u64 { + self.size + } + + #[cfg(not(target_os = "wasi"))] + #[inline] + fn atime(&self) -> i64 { + self.atime + } + + #[cfg(not(target_os = "wasi"))] + #[inline] + fn atime_nsec(&self) -> i64 { + self.atime_nsec + } + + #[cfg(not(target_os = "wasi"))] + #[inline] + fn mtime(&self) -> i64 { + self.mtime + } + + #[cfg(not(target_os = "wasi"))] + #[inline] + fn mtime_nsec(&self) -> i64 { + self.mtime_nsec + } + + #[cfg(not(target_os = "wasi"))] + #[inline] + fn ctime(&self) -> i64 { + self.ctime + } + + #[cfg(not(target_os = "wasi"))] + #[inline] + fn ctime_nsec(&self) -> i64 { + self.ctime_nsec + } + + #[cfg(not(target_os = "wasi"))] + #[inline] + fn blksize(&self) -> u64 { + self.blksize + } + + #[cfg(not(target_os = "wasi"))] + #[inline] + fn blocks(&self) -> u64 { + self.blocks + } +} + +/// It should be possible to represent times before the Epoch. +/// https://github.com/bytecodealliance/cap-std/issues/328 +#[test] +fn negative_time() { + let system_time = system_time_from_rustix(-1, 1).unwrap(); + let d = SystemClock::UNIX_EPOCH.duration_since(system_time).unwrap(); + assert_eq!(d.as_secs(), 0); + if !cfg!(emulate_second_only_system) { + assert_eq!(d.subsec_nanos(), 999999999); + } +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/mod.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/mod.rs new file mode 100644 index 000000000000..b6a0def2743c --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/mod.rs @@ -0,0 +1,190 @@ +mod access_unchecked; +mod copy_impl; +mod create_dir_unchecked; +mod dir_entry_inner; +#[cfg(not(target_os = "wasi"))] +mod dir_options_ext; +mod dir_utils; +#[cfg(not(any(target_os = "android", target_os = "linux")))] +mod file_path; +mod file_type_ext; +mod hard_link_unchecked; +mod is_file_read_write_impl; +mod is_root_dir; +mod is_same_file; +mod metadata_ext; +mod oflags; +mod open_options_ext; +mod open_unchecked; +mod permissions_ext; +mod read_dir_inner; +mod read_link_unchecked; +mod remove_dir_all_impl; +mod remove_dir_unchecked; +mod remove_file_unchecked; +mod remove_open_dir_by_searching; +mod rename_unchecked; +mod reopen_impl; +#[cfg(not(any(target_os = "android", target_os = "linux", target_os = "wasi")))] +mod set_permissions_impl; +#[cfg(not(target_os = "wasi"))] +mod set_symlink_permissions_unchecked; +#[cfg(not(any(target_os = "android", target_os = "linux")))] +mod set_times_impl; +mod stat_unchecked; +mod symlink_unchecked; +mod times; + +pub(crate) mod errors; + +// On Linux, use optimized implementations based on +// `openat2` and `O_PATH` when available. +// +// On FreeBSD, use optimized implementations based on +// `O_RESOLVE_BENEATH`/`AT_RESOLVE_BENEATH` and `O_PATH` when available. +#[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "watchos", + target_os = "visionos", +))] +pub(crate) use crate::rustix::darwin::fs::*; +#[cfg(target_os = "freebsd")] +pub(crate) use crate::rustix::freebsd::fs::*; +#[cfg(any(target_os = "android", target_os = "linux"))] +pub(crate) use crate::rustix::linux::fs::*; +#[cfg(not(any(target_os = "android", target_os = "linux", target_os = "freebsd")))] +#[rustfmt::skip] +pub(crate) use crate::fs::{ + manually::open_entry as open_entry_impl, + manually::open as open_impl, + manually::stat as stat_impl, + manually::canonicalize as canonicalize_impl, + via_parent::set_times_nofollow as set_times_nofollow_impl, +}; +#[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "watchos", + target_os = "visionos", +))] +pub(super) use file_path::file_path_by_ttyname_or_seaching; +#[cfg(not(any( + target_os = "android", + target_os = "linux", + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "watchos", + target_os = "visionos", +)))] +pub(crate) use file_path::file_path_by_ttyname_or_seaching as file_path; +#[cfg(not(any( + target_os = "android", + target_os = "linux", + target_os = "freebsd", + target_os = "wasi" +)))] +pub(crate) use set_permissions_impl::set_permissions_impl; +#[cfg(target_os = "freebsd")] +pub(crate) use set_permissions_impl::set_permissions_impl as set_permissions_manually; +#[cfg(not(target_os = "wasi"))] +pub(crate) use set_symlink_permissions_unchecked::set_symlink_permissions_unchecked; +#[cfg(not(any(target_os = "android", target_os = "linux", target_os = "freebsd")))] +pub(crate) use set_times_impl::set_times_impl; +#[cfg(target_os = "freebsd")] +pub(crate) use set_times_impl::set_times_impl as set_times_manually; +#[rustfmt::skip] +pub(crate) use crate::fs::{ + via_parent::access as access_impl, + via_parent::hard_link as hard_link_impl, + via_parent::create_dir as create_dir_impl, + via_parent::read_link as read_link_impl, + via_parent::rename as rename_impl, + via_parent::symlink as symlink_impl, + remove_open_dir_by_searching as remove_open_dir_impl, +}; +#[cfg(not(target_os = "wasi"))] +pub(crate) use crate::fs::via_parent::set_symlink_permissions as set_symlink_permissions_impl; +#[cfg(not(target_os = "freebsd"))] +#[rustfmt::skip] +pub(crate) use crate::fs::{ + via_parent::remove_dir as remove_dir_impl, + via_parent::remove_file as remove_file_impl, +}; + +pub(crate) use access_unchecked::access_unchecked; +pub(crate) use copy_impl::copy_impl; +pub(crate) use create_dir_unchecked::create_dir_unchecked; +pub(crate) use dir_entry_inner::DirEntryInner; +#[cfg(not(target_os = "wasi"))] +pub(crate) use dir_options_ext::DirOptionsExt; +pub(crate) use dir_utils::*; +pub(crate) use file_type_ext::ImplFileTypeExt; +pub(crate) use hard_link_unchecked::hard_link_unchecked; +pub(crate) use is_file_read_write_impl::is_file_read_write_impl; +pub(crate) use is_root_dir::is_root_dir; +#[allow(unused_imports)] +pub(crate) use is_same_file::{is_different_file, is_different_file_metadata, is_same_file}; +pub(crate) use metadata_ext::ImplMetadataExt; +pub(crate) use open_options_ext::ImplOpenOptionsExt; +pub(crate) use open_unchecked::{open_ambient_impl, open_unchecked}; +pub(crate) use permissions_ext::ImplPermissionsExt; +pub(crate) use read_dir_inner::ReadDirInner; +pub(crate) use read_link_unchecked::read_link_unchecked; +pub(crate) use remove_dir_all_impl::{remove_dir_all_impl, remove_open_dir_all_impl}; +pub(crate) use remove_dir_unchecked::remove_dir_unchecked; +pub(crate) use remove_file_unchecked::remove_file_unchecked; +pub(crate) use remove_open_dir_by_searching::remove_open_dir_by_searching; +pub(crate) use rename_unchecked::rename_unchecked; +pub(crate) use reopen_impl::reopen_impl; +pub(crate) use stat_unchecked::stat_unchecked; +pub(crate) use symlink_unchecked::symlink_unchecked; +#[allow(unused_imports)] +pub(crate) use times::{set_times_follow_unchecked, set_times_nofollow_unchecked, to_timespec}; + +// On Linux, there is a limit of 40 symlink expansions. +// Source: +pub(crate) const MAX_SYMLINK_EXPANSIONS: u8 = 40; + +pub(super) use oflags::*; + +/// Test that `file_path` works on a tty path. +#[test] +fn tty_path() { + #[cfg(unix)] + use std::os::unix::fs::FileTypeExt; + + let paths: &[&str] = if cfg!(target_os = "freebsd") { + // On FreeBSD, /dev/{tty,stdin,stdout,stderr} are aliases to different + // real devices. + &["/dev/ttyv0", "/dev/pts/0"] + } else if cfg!(target_os = "illumos") { + // On illumos, /dev/std{in,out,err} only exist if they're open. + &["/dev/tty", "/dev/pts/0"] + } else { + &["/dev/tty", "/dev/stdin", "/dev/stdout", "/dev/stderr"] + }; + + for path in paths { + // Not all host configurations have these, so only test them if we can + // open and canonicalize them, and if they're not FIFOs, which some + // OS's use for stdin/stdout/stderr. + if let Ok(file) = std::fs::File::open(path) { + if !file.metadata().unwrap().file_type().is_fifo() { + if let Ok(canonical) = std::fs::canonicalize(path) { + assert_eq!( + file_path(&file) + .as_ref() + .map(std::fs::canonicalize) + .map(Result::unwrap), + Some(canonical), + "for path {path}, file_path matches canonicalized path" + ); + } + } + } + } +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/oflags.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/oflags.rs new file mode 100644 index 000000000000..d9349673b45a --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/oflags.rs @@ -0,0 +1,101 @@ +use crate::fs::{target_o_path, FollowSymlinks, OpenOptions}; +use rustix::fs::OFlags; +use std::io; + +pub(in super::super) fn compute_oflags(options: &OpenOptions) -> io::Result { + let mut oflags = OFlags::CLOEXEC; + oflags |= get_access_mode(options)?; + oflags |= get_creation_mode(options)?; + if options.follow == FollowSymlinks::No { + oflags |= OFlags::NOFOLLOW; + } + if options.sync { + oflags |= OFlags::SYNC; + } + if options.dsync { + #[cfg(not(target_os = "freebsd"))] + { + oflags |= OFlags::DSYNC; + } + + // Where needed, approximate `DSYNC` with `SYNC`. + #[cfg(target_os = "freebsd")] + { + oflags |= OFlags::SYNC; + } + } + #[cfg(not(any( + target_os = "ios", + target_os = "macos", + target_os = "tvos", + target_os = "watchos", + target_os = "visionos", + target_os = "freebsd", + target_os = "fuchsia" + )))] + if options.rsync { + oflags |= OFlags::RSYNC; + } + if options.nonblock { + oflags |= OFlags::NONBLOCK; + } + if options.dir_required { + oflags |= OFlags::DIRECTORY; + + // If the target has `O_PATH`, we don't need to read the directory + // entries, and we're not requesting write access (which need to + // fail on a directory), use it. + if !options.readdir_required && !options.write && !options.append { + oflags |= target_o_path(); + } + } + // Use `RWMODE` here instead of `ACCMODE` so that we preserve the `O_PATH` + // flag. + #[cfg(not(target_os = "wasi"))] + { + oflags |= OFlags::from_bits(options.ext.custom_flags as _).expect("unrecognized OFlags") + & !OFlags::RWMODE; + } + Ok(oflags) +} + +// `OpenOptions` translation code derived from Rust's +// library/std/src/sys/unix/fs.rs at revision +// 108e90ca78f052c0c1c49c42a22c85620be19712. + +pub(crate) fn get_access_mode(options: &OpenOptions) -> io::Result { + match (options.read, options.write, options.append) { + (true, false, false) => Ok(OFlags::RDONLY), + (false, true, false) => Ok(OFlags::WRONLY), + (true, true, false) => Ok(OFlags::RDWR), + (false, _, true) => Ok(OFlags::WRONLY | OFlags::APPEND), + (true, _, true) => Ok(OFlags::RDWR | OFlags::APPEND), + (false, false, false) => Err(rustix::io::Errno::INVAL.into()), + } +} + +pub(crate) fn get_creation_mode(options: &OpenOptions) -> io::Result { + match (options.write, options.append) { + (true, false) => {} + (false, false) => { + if options.truncate || options.create || options.create_new { + return Err(rustix::io::Errno::INVAL.into()); + } + } + (_, true) => { + if options.truncate && !options.create_new { + return Err(rustix::io::Errno::INVAL.into()); + } + } + } + + Ok( + match (options.create, options.truncate, options.create_new) { + (false, false, false) => OFlags::empty(), + (true, false, false) => OFlags::CREATE, + (false, true, false) => OFlags::TRUNC, + (true, true, false) => OFlags::CREATE | OFlags::TRUNC, + (_, _, true) => OFlags::CREATE | OFlags::EXCL, + }, + ) +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/open_options_ext.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/open_options_ext.rs new file mode 100644 index 000000000000..dc8ca59fc7d8 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/open_options_ext.rs @@ -0,0 +1,24 @@ +#[derive(Debug, Clone)] +pub(crate) struct ImplOpenOptionsExt { + pub(crate) mode: u32, + pub(crate) custom_flags: i32, +} + +impl ImplOpenOptionsExt { + pub(crate) const fn new() -> Self { + Self { + mode: 0o666, + custom_flags: 0, + } + } + + pub(crate) fn mode(&mut self, mode: u32) -> &mut Self { + self.mode = mode; + self + } + + pub(crate) fn custom_flags(&mut self, flags: i32) -> &mut Self { + self.custom_flags = flags; + self + } +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/open_unchecked.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/open_unchecked.rs new file mode 100644 index 000000000000..3f75e0c6c683 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/open_unchecked.rs @@ -0,0 +1,71 @@ +use super::compute_oflags; +use crate::fs::{stat_unchecked, OpenOptions, OpenUncheckedError}; +use crate::AmbientAuthority; +use io_lifetimes::AsFilelike; +use rustix::fs::{openat, Mode, CWD}; +use rustix::io; +use std::fs; +use std::path::Path; + +/// *Unsandboxed* function similar to `open`, but which does not perform +/// sandboxing. +pub(crate) fn open_unchecked( + start: &fs::File, + path: &Path, + options: &OpenOptions, +) -> Result { + let oflags = compute_oflags(options).map_err(OpenUncheckedError::Other)?; + + #[allow(clippy::useless_conversion)] + #[cfg(not(target_os = "wasi"))] + let mode = Mode::from_bits_truncate(options.ext.mode as _); + #[cfg(target_os = "wasi")] + let mode = Mode::empty(); + + let err = match openat(start, path, oflags, mode) { + Ok(file) => { + return Ok(fs::File::from(file)); + } + Err(err) => err, + }; + match err { + // `ELOOP` is the POSIX standard and most widely used error code to + // indicate that a symlink was found when `O_NOFOLLOW` was set. + #[cfg(not(any(target_os = "freebsd", target_os = "dragonfly", target_os = "netbsd")))] + io::Errno::LOOP => Err(OpenUncheckedError::Symlink(err.into(), ())), + + // FreeBSD and similar (but not Darwin) use `EMLINK`. + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + io::Errno::MLINK => Err(OpenUncheckedError::Symlink(err.into(), ())), + + // NetBSD uses `EFTYPE`. + #[cfg(target_os = "netbsd")] + io::Errno::FTYPE => Err(OpenUncheckedError::Symlink(err.into(), ())), + + io::Errno::NOENT => Err(OpenUncheckedError::NotFound(err.into())), + io::Errno::NOTDIR => { + if options.dir_required + && stat_unchecked(start, path, options.follow) + .map(|m| m.file_type().is_symlink()) + .unwrap_or(false) + { + Err(OpenUncheckedError::Symlink(err.into(), ())) + } else { + Err(OpenUncheckedError::NotFound(err.into())) + } + } + _ => Err(OpenUncheckedError::Other(err.into())), + } +} + +/// *Unsandboxed* function similar to `open`, but which does not perform +/// sandboxing. +#[inline] +pub(crate) fn open_ambient_impl( + path: &Path, + options: &OpenOptions, + ambient_authority: AmbientAuthority, +) -> Result { + let _ = ambient_authority; + open_unchecked(&CWD.as_filelike_view::(), path, options) +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/permissions_ext.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/permissions_ext.rs new file mode 100644 index 000000000000..fef1cb44173c --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/permissions_ext.rs @@ -0,0 +1,74 @@ +use crate::fs::Permissions; +use rustix::fs::RawMode; +use std::fs; + +#[derive(Debug, Clone, Eq, PartialEq)] +pub(crate) struct ImplPermissionsExt { + #[cfg(not(target_os = "wasi"))] + mode: RawMode, +} + +#[cfg(not(target_os = "wasi"))] +impl ImplPermissionsExt { + /// Constructs a new instance of `Self` from the given + /// [`std::fs::Permissions`]. + #[inline] + pub(crate) fn from_std(std: fs::Permissions) -> Self { + use std::os::unix::fs::PermissionsExt; + Self { + mode: std.mode() as RawMode, + } + } + + /// Constructs a new instance of `Permissions` from the given + /// `RawMode`. + #[inline] + pub(crate) const fn from_raw_mode(mode: RawMode) -> Permissions { + Permissions { + readonly: Self::readonly(mode), + ext: Self { mode }, + } + } + + /// Test whether the given `RawMode` lacks write permissions. + #[inline] + pub(crate) const fn readonly(mode: RawMode) -> bool { + mode & 0o222 == 0 + } + + /// Test whether the given `RawMode` lacks write permissions. + #[inline] + pub(crate) fn set_readonly(&mut self, readonly: bool) { + if readonly { + // remove write permission for all classes; equivalent to `chmod a-w ` + self.mode &= !0o222; + } else { + // add write permission for all classes; equivalent to `chmod a+w ` + self.mode |= 0o222; + } + } +} + +#[cfg(not(target_os = "wasi"))] +impl crate::fs::PermissionsExt for ImplPermissionsExt { + fn mode(&self) -> u32 { + self.mode as u32 + } + + fn set_mode(&mut self, mode: u32) { + self.mode = mode as RawMode & 0o7777; + } + + fn from_mode(mode: u32) -> Self { + Self { + mode: mode as RawMode & 0o7777, + } + } +} + +#[cfg(target_os = "wasi")] +impl ImplPermissionsExt { + pub(crate) fn default() -> Permissions { + Permissions { readonly: false } + } +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/read_dir_inner.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/read_dir_inner.rs new file mode 100644 index 000000000000..20626987d9fb --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/read_dir_inner.rs @@ -0,0 +1,136 @@ +use crate::fs::{ + open_dir_for_reading, open_dir_for_reading_unchecked, open_entry_impl, read_dir_unchecked, + remove_dir_unchecked, remove_file_unchecked, stat_unchecked, DirEntryInner, FollowSymlinks, + Metadata, OpenOptions, ReadDir, +}; +use io_extras::os::rustix::{AsRawFd, FromRawFd, RawFd}; +use io_lifetimes::AsFd; +use rustix::fd::OwnedFd; +use rustix::fs::Dir; +use std::ffi::OsStr; +use std::mem::ManuallyDrop; +#[cfg(unix)] +use std::os::unix::ffi::OsStrExt; +#[cfg(target_os = "wasi")] +use std::os::wasi::ffi::OsStrExt; +use std::path::{Component, Path}; +use std::sync::{Arc, Mutex}; +use std::{fmt, fs, io}; + +pub(crate) struct ReadDirInner { + raw_fd: RawFd, + + // `Dir` doesn't implement `AsFd`, because libc `fdopendir` has UB if the + // file descriptor is used in almost any way, so we hold a separate + // `OwnedFd` that we can do `as_fd()` on. + rustix: Arc>, +} + +impl ReadDirInner { + pub(crate) fn new(start: &fs::File, path: &Path, follow: FollowSymlinks) -> io::Result { + let fd = open_dir_for_reading(start, path, follow)?; + let dir = Dir::read_from(fd.as_fd())?; + Ok(Self { + raw_fd: fd.as_fd().as_raw_fd(), + rustix: Arc::new(Mutex::new((dir, OwnedFd::from(fd)))), + }) + } + + pub(crate) fn read_base_dir(start: &fs::File) -> io::Result { + // Open ".", to obtain a new independent file descriptor. Don't use + // `dup` since in that case the resulting file descriptor would share + // a current position with the original, and `read_dir` calls after + // the first `read_dir` call wouldn't start from the beginning. + let fd = + open_dir_for_reading_unchecked(start, Component::CurDir.as_ref(), FollowSymlinks::No)?; + let dir = Dir::read_from(fd.as_fd())?; + Ok(Self { + raw_fd: fd.as_fd().as_raw_fd(), + rustix: Arc::new(Mutex::new((dir, fd.into()))), + }) + } + + pub(crate) fn new_unchecked( + start: &fs::File, + path: &Path, + follow: FollowSymlinks, + ) -> io::Result { + let fd = open_dir_for_reading_unchecked(start, path, follow)?; + let dir = Dir::read_from(fd.as_fd())?; + Ok(Self { + raw_fd: fd.as_fd().as_raw_fd(), + rustix: Arc::new(Mutex::new((dir, fd.into()))), + }) + } + + pub(super) fn open(&self, file_name: &OsStr, options: &OpenOptions) -> io::Result { + open_entry_impl(&self.as_file_view(), file_name, options) + } + + pub(super) fn metadata(&self, file_name: &OsStr) -> io::Result { + stat_unchecked(&self.as_file_view(), file_name.as_ref(), FollowSymlinks::No) + } + + pub(super) fn remove_file(&self, file_name: &OsStr) -> io::Result<()> { + remove_file_unchecked(&self.as_file_view(), file_name.as_ref()) + } + + pub(super) fn remove_dir(&self, file_name: &OsStr) -> io::Result<()> { + remove_dir_unchecked(&self.as_file_view(), file_name.as_ref()) + } + + pub(super) fn self_metadata(&self) -> io::Result { + Metadata::from_file(&self.as_file_view()) + } + + pub(super) fn read_dir( + &self, + file_name: &OsStr, + follow: FollowSymlinks, + ) -> io::Result { + read_dir_unchecked(&self.as_file_view(), file_name.as_ref(), follow) + } + + #[allow(unsafe_code)] + fn as_file_view(&self) -> ManuallyDrop { + // Safety: `self.rustix` owns the file descriptor. We just hold a + // copy outside so that we can read it without taking a lock. + ManuallyDrop::new(unsafe { fs::File::from_raw_fd(self.raw_fd) }) + } +} + +impl Iterator for ReadDirInner { + type Item = io::Result; + + fn next(&mut self) -> Option { + loop { + let entry = self.rustix.lock().unwrap().0.read()?; + let entry = match entry { + Ok(entry) => entry, + Err(e) => return Some(Err(e.into())), + }; + let file_name = entry.file_name().to_bytes(); + if file_name != Component::CurDir.as_os_str().as_bytes() + && file_name != Component::ParentDir.as_os_str().as_bytes() + { + let clone = Arc::clone(&self.rustix); + return Some(Ok(DirEntryInner { + rustix: entry, + read_dir: Self { + raw_fd: self.raw_fd, + rustix: clone, + }, + })); + } + } + } +} + +impl fmt::Debug for ReadDirInner { + // Like libstd's version, but doesn't print the path. + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut b = f.debug_struct("ReadDir"); + b.field("raw_fd", &self.raw_fd); + b.finish() + } +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/read_link_unchecked.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/read_link_unchecked.rs new file mode 100644 index 000000000000..08ef42dac1e2 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/read_link_unchecked.rs @@ -0,0 +1,19 @@ +use rustix::fs::readlinkat; +use std::ffi::OsString; +#[cfg(unix)] +use std::os::unix::ffi::OsStringExt; +#[cfg(target_os = "wasi")] +use std::os::wasi::ffi::OsStringExt; +use std::path::{Path, PathBuf}; +use std::{fs, io}; + +/// *Unsandboxed* function similar to `read_link`, but which does not perform +/// sandboxing. +pub(crate) fn read_link_unchecked( + start: &fs::File, + path: &Path, + reuse: PathBuf, +) -> io::Result { + Ok(readlinkat(start, path, reuse.into_os_string().into_vec()) + .map(|path| OsString::from_vec(path.into_bytes()).into())?) +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/remove_dir_all_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/remove_dir_all_impl.rs new file mode 100644 index 000000000000..cf2ed1db4c0e --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/remove_dir_all_impl.rs @@ -0,0 +1,41 @@ +use crate::fs::{ + read_dir_nofollow, read_dir_unchecked, remove_dir, remove_file, remove_open_dir, stat, + FollowSymlinks, ReadDir, +}; +use std::path::{Component, Path}; +use std::{fs, io}; + +pub(crate) fn remove_dir_all_impl(start: &fs::File, path: &Path) -> io::Result<()> { + // Code adapted from `remove_dir_all` in Rust's + // library/std/src/sys_common/fs.rs at revision + // 108e90ca78f052c0c1c49c42a22c85620be19712. + let filetype = stat(start, path, FollowSymlinks::No)?.file_type(); + if filetype.is_symlink() { + remove_file(start, path) + } else { + remove_dir_all_recursive(read_dir_nofollow(start, path)?)?; + remove_dir(start, path) + } +} + +pub(crate) fn remove_open_dir_all_impl(dir: fs::File) -> io::Result<()> { + remove_dir_all_recursive(read_dir_unchecked( + &dir, + Component::CurDir.as_ref(), + FollowSymlinks::No, + )?)?; + remove_open_dir(dir) +} + +fn remove_dir_all_recursive(children: ReadDir) -> io::Result<()> { + for child in children { + let child = child?; + if child.file_type()?.is_dir() { + remove_dir_all_recursive(child.inner.read_dir(FollowSymlinks::No)?)?; + child.remove_dir()?; + } else { + child.remove_file()?; + } + } + Ok(()) +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/remove_dir_unchecked.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/remove_dir_unchecked.rs new file mode 100644 index 000000000000..5899bb3cd169 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/remove_dir_unchecked.rs @@ -0,0 +1,9 @@ +use rustix::fs::{unlinkat, AtFlags}; +use std::path::Path; +use std::{fs, io}; + +/// *Unsandboxed* function similar to `remove_dir`, but which does not perform +/// sandboxing. +pub(crate) fn remove_dir_unchecked(start: &fs::File, path: &Path) -> io::Result<()> { + Ok(unlinkat(start, path, AtFlags::REMOVEDIR)?) +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/remove_file_unchecked.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/remove_file_unchecked.rs new file mode 100644 index 000000000000..7e6f97dc8b32 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/remove_file_unchecked.rs @@ -0,0 +1,9 @@ +use rustix::fs::{unlinkat, AtFlags}; +use std::path::Path; +use std::{fs, io}; + +/// *Unsandboxed* function similar to `remove_file`, but which does not perform +/// sandboxing. +pub(crate) fn remove_file_unchecked(start: &fs::File, path: &Path) -> io::Result<()> { + Ok(unlinkat(start, path, AtFlags::empty())?) +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/remove_open_dir_by_searching.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/remove_open_dir_by_searching.rs new file mode 100644 index 000000000000..a08cde8d44ca --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/remove_open_dir_by_searching.rs @@ -0,0 +1,36 @@ +use crate::fs::{errors, is_root_dir, read_dir_unchecked, FollowSymlinks, Metadata}; +use std::path::Component; +use std::{fs, io}; + +/// Delete the directory referenced by the given handle by searching for it in +/// its `..`. This requires search permission in `..`, but that's usually +/// available. +pub(crate) fn remove_open_dir_by_searching(dir: fs::File) -> io::Result<()> { + let metadata = Metadata::from_file(&dir)?; + let mut iter = read_dir_unchecked(&dir, Component::ParentDir.as_ref(), FollowSymlinks::No)?; + while let Some(child) = iter.next() { + let child = child?; + + // Test if the child we found by iteration matches the directory we're + // looking for. Ignore `NotFound` errors, which can happen if another + // process removes a different directory in the same parent. + let same = match child.is_same_file(&metadata) { + Ok(same) => same, + Err(err) if err.kind() == std::io::ErrorKind::NotFound => false, + Err(err) => Err(err)?, + }; + + if same { + return child.remove_dir(); + } + } + + // We didn't find the directory among its parent's children. Check for the + // root directory and handle it specially -- removal will probably fail, so + // we'll get the appropriate error code. + if is_root_dir(&dir, &iter)? { + fs::remove_dir(Component::RootDir.as_os_str()) + } else { + Err(errors::no_such_file_or_directory()) + } +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/rename_unchecked.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/rename_unchecked.rs new file mode 100644 index 000000000000..a929ebb1ad5d --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/rename_unchecked.rs @@ -0,0 +1,14 @@ +use rustix::fs::renameat; +use std::path::Path; +use std::{fs, io}; + +/// *Unsandboxed* function similar to `rename`, but which does not perform +/// sandboxing. +pub(crate) fn rename_unchecked( + old_start: &fs::File, + old_path: &Path, + new_start: &fs::File, + new_path: &Path, +) -> io::Result<()> { + Ok(renameat(old_start, old_path, new_start, new_path)?) +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/reopen_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/reopen_impl.rs new file mode 100644 index 000000000000..e010a4d36b06 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/reopen_impl.rs @@ -0,0 +1,18 @@ +use crate::fs::{open_unchecked, OpenOptions}; +use crate::rustix::fs::file_path; +use io_lifetimes::AsFilelike; +use rustix::fs::CWD; +use std::{fs, io}; + +/// Implementation of `reopen`. +pub(crate) fn reopen_impl(file: &fs::File, options: &OpenOptions) -> io::Result { + if let Some(path) = file_path(file) { + Ok(open_unchecked( + &CWD.as_filelike_view::(), + &path, + options, + )?) + } else { + Err(io::Error::new(io::ErrorKind::Other, "Couldn't reopen file")) + } +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/set_permissions_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/set_permissions_impl.rs new file mode 100644 index 000000000000..ff32e3f4efed --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/set_permissions_impl.rs @@ -0,0 +1,49 @@ +use crate::fs::{open, OpenOptions, Permissions}; +use rustix::fs::{fchmod, Mode}; +use rustix::io::Errno; +#[cfg(unix)] +use std::os::unix::fs::PermissionsExt; +use std::path::Path; +use std::{fs, io}; + +/// This sounds like it should be a job for `fchmodat`, however `fchmodat` +/// handles symlinks in an incompatible way. It either follows symlinks +/// without guaranteeing to stay in the sandbox, or with `AT_SYMLINK_NOFOLLOW` +/// it attempts to change the permissions of symlinks themselves. What we'd +/// need is for it to fail if it encounters a symlink, like `O_NOFOLLOW` does. +pub(crate) fn set_permissions_impl( + start: &fs::File, + path: &Path, + perm: Permissions, +) -> io::Result<()> { + let std_perm = perm.into_std(start)?; + + // Try `fchmod` with a normal handle. Normal handles need some kind of + // access, so first try read. + match open(start, path, OpenOptions::new().read(true)) { + Ok(file) => return set_file_permissions(&file, std_perm), + Err(err) => match Errno::from_io_error(&err) { + Some(Errno::ACCESS) => (), + _ => return Err(err), + }, + } + + // Next try write. + match open(start, path, OpenOptions::new().write(true)) { + Ok(file) => return set_file_permissions(&file, std_perm), + Err(err) => match Errno::from_io_error(&err) { + Some(Errno::ACCESS) | Some(Errno::ISDIR) => (), + _ => return Err(err), + }, + } + + // If neither of those worked, we're out of luck. + Err(Errno::NOTSUP.into()) +} + +pub(crate) fn set_file_permissions(file: &fs::File, perm: fs::Permissions) -> io::Result<()> { + // Use `from_bits_truncate` for compatibility with std, which allows + // non-permission bits to propagate through. + let mode = Mode::from_bits_truncate(perm.mode().try_into().unwrap()); + Ok(fchmod(file, mode)?) +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/set_symlink_permissions_unchecked.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/set_symlink_permissions_unchecked.rs new file mode 100644 index 000000000000..fb3a8f02af64 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/set_symlink_permissions_unchecked.rs @@ -0,0 +1,17 @@ +use crate::fs::Permissions; +#[cfg(unix)] +use crate::fs::PermissionsExt; +use rustix::fs::{chmodat, AtFlags, Mode}; +use std::path::Path; +use std::{fs, io}; + +/// This can just use `AT_SYMLINK_NOFOLLOW`. +pub(crate) fn set_symlink_permissions_unchecked( + start: &fs::File, + path: &Path, + perm: Permissions, +) -> io::Result<()> { + let mode = Mode::from_bits_truncate(perm.mode().try_into().unwrap()); + + Ok(chmodat(start, path, mode, AtFlags::SYMLINK_NOFOLLOW)?) +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/set_times_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/set_times_impl.rs new file mode 100644 index 000000000000..3b3619c6016b --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/set_times_impl.rs @@ -0,0 +1,56 @@ +//! This module consists of helper types and functions for dealing +//! with setting the file times. + +use crate::fs::{open, OpenOptions, SystemTimeSpec}; +use rustix::io::Errno; +use std::path::Path; +use std::{fs, io}; + +pub(crate) fn set_times_impl( + start: &fs::File, + path: &Path, + atime: Option, + mtime: Option, +) -> io::Result<()> { + // Try `futimens` with a normal handle. Normal handles need some kind of + // access, so first try write. + match open(start, path, OpenOptions::new().write(true)) { + Ok(file) => { + return fs_set_times::SetTimes::set_times( + &file, + atime.map(SystemTimeSpec::into_std), + mtime.map(SystemTimeSpec::into_std), + ) + } + Err(err) => match Errno::from_io_error(&err) { + Some(Errno::ACCESS) | Some(Errno::ISDIR) => (), + _ => return Err(err), + }, + } + + // Next try read. + match open(start, path, OpenOptions::new().read(true)) { + Ok(file) => { + return fs_set_times::SetTimes::set_times( + &file, + atime.map(SystemTimeSpec::into_std), + mtime.map(SystemTimeSpec::into_std), + ) + } + Err(err) => match Errno::from_io_error(&err) { + Some(Errno::ACCESS) => (), + _ => return Err(err), + }, + } + + // It's not possible to do anything else with generic POSIX. Plain + // `utimensat` has two options: + // - Follow symlinks, which would open up a race in which a concurrent + // modification of the symlink could point outside the sandbox and we + // wouldn't be able to detect it, or + // - Don't follow symlinks, which would modify the timestamp of the symlink + // instead of the file we're trying to get to. + // + // So neither does what we need. + Err(Errno::NOTSUP.into()) +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/stat_unchecked.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/stat_unchecked.rs new file mode 100644 index 000000000000..f10b926be847 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/stat_unchecked.rs @@ -0,0 +1,79 @@ +use crate::fs::{FollowSymlinks, ImplMetadataExt, Metadata}; +use rustix::fs::{statat, AtFlags}; +use std::path::Path; +use std::{fs, io}; + +#[cfg(target_os = "linux")] +use rustix::fs::{statx, StatxFlags}; +#[cfg(target_os = "linux")] +use std::sync::atomic::{AtomicU8, Ordering}; + +/// *Unsandboxed* function similar to `stat`, but which does not perform +/// sandboxing. +pub(crate) fn stat_unchecked( + start: &fs::File, + path: &Path, + follow: FollowSymlinks, +) -> io::Result { + let atflags = match follow { + FollowSymlinks::Yes => AtFlags::empty(), + FollowSymlinks::No => AtFlags::SYMLINK_NOFOLLOW, + }; + + // `statx` is preferred on regular Linux because it can return creation + // times. Linux kernels prior to 4.11 don't have `statx` and return + // `ENOSYS`. Older versions of Docker/seccomp would return `EPERM` for + // `statx`; see . We store + // the availability in a global to avoid unnecessary syscalls. + // + // On Android, the [seccomp policy] prevents us from even + // detecting whether `statx` is supported, so don't even try. + // + // [seccomp policy]: https://android-developers.googleblog.com/2017/07/seccomp-filter-in-android-o.html + #[cfg(target_os = "linux")] + { + // 0: Unknown + // 1: Not available + // 2: Available + static STATX_STATE: AtomicU8 = AtomicU8::new(0); + let state = STATX_STATE.load(Ordering::Relaxed); + + if state != 1 { + let statx_result = statx( + start, + path, + atflags, + StatxFlags::BASIC_STATS | StatxFlags::BTIME, + ); + match statx_result { + Ok(statx) => { + if state == 0 { + STATX_STATE.store(2, Ordering::Relaxed); + } + return Ok(ImplMetadataExt::from_rustix_statx(statx)); + } + Err(rustix::io::Errno::NOSYS) => STATX_STATE.store(1, Ordering::Relaxed), + Err(rustix::io::Errno::PERM) if state == 0 => { + // This is an unlikely case, as `statx` doesn't normally + // return `PERM` errors. One way this can happen is when + // running on old versions of seccomp/Docker. If `statx` on + // the current working directory returns a similar error, + // then stop using `statx`. + if let Err(rustix::io::Errno::PERM) = statx( + rustix::fs::CWD, + "", + AtFlags::EMPTY_PATH, + StatxFlags::empty(), + ) { + STATX_STATE.store(1, Ordering::Relaxed); + } else { + return Err(rustix::io::Errno::PERM.into()); + } + } + Err(e) => return Err(e.into()), + } + } + } + + Ok(statat(start, path, atflags).map(ImplMetadataExt::from_rustix)?) +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/symlink_unchecked.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/symlink_unchecked.rs new file mode 100644 index 000000000000..bdd9141289a9 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/symlink_unchecked.rs @@ -0,0 +1,13 @@ +use rustix::fs::symlinkat; +use std::path::Path; +use std::{fs, io}; + +/// *Unsandboxed* function similar to `symlink`, but which does not perform +/// sandboxing. +pub(crate) fn symlink_unchecked( + old_path: &Path, + new_start: &fs::File, + new_path: &Path, +) -> io::Result<()> { + Ok(symlinkat(old_path, new_start, new_path)?) +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/times.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/times.rs new file mode 100644 index 000000000000..555ebe631558 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/times.rs @@ -0,0 +1,62 @@ +use crate::fs::SystemTimeSpec; +use crate::time::SystemClock; +use io_lifetimes::BorrowedFd; +use rustix::fs::{utimensat, AtFlags, Timestamps, UTIME_NOW, UTIME_OMIT}; +use rustix::time::Timespec; +use std::path::Path; +use std::{fs, io}; + +#[allow(clippy::useless_conversion)] +pub(crate) fn to_timespec(ft: Option) -> io::Result { + Ok(match ft { + None => Timespec { + tv_sec: 0, + tv_nsec: UTIME_OMIT.into(), + }, + Some(SystemTimeSpec::SymbolicNow) => Timespec { + tv_sec: 0, + tv_nsec: UTIME_NOW.into(), + }, + Some(SystemTimeSpec::Absolute(ft)) => { + let duration = ft.duration_since(SystemClock::UNIX_EPOCH).unwrap(); + let nanoseconds = duration.subsec_nanos(); + assert_ne!(i64::from(nanoseconds), i64::from(UTIME_OMIT)); + assert_ne!(i64::from(nanoseconds), i64::from(UTIME_NOW)); + Timespec { + tv_sec: duration + .as_secs() + .try_into() + .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?, + tv_nsec: nanoseconds.try_into().unwrap(), + } + } + }) +} + +#[allow(dead_code)] +pub(crate) fn set_times_nofollow_unchecked( + start: &fs::File, + path: &Path, + atime: Option, + mtime: Option, +) -> io::Result<()> { + let times = Timestamps { + last_access: to_timespec(atime)?, + last_modification: to_timespec(mtime)?, + }; + Ok(utimensat(start, path, ×, AtFlags::SYMLINK_NOFOLLOW)?) +} + +#[allow(dead_code)] +pub(crate) fn set_times_follow_unchecked( + start: BorrowedFd<'_>, + path: &Path, + atime: Option, + mtime: Option, +) -> io::Result<()> { + let times = Timestamps { + last_access: to_timespec(atime)?, + last_modification: to_timespec(mtime)?, + }; + Ok(utimensat(start, path, ×, AtFlags::empty())?) +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/canonicalize_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/canonicalize_impl.rs new file mode 100644 index 000000000000..87dd9ebe654d --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/canonicalize_impl.rs @@ -0,0 +1,66 @@ +//! Path canonicalization using `/proc/self/fd`. + +use super::procfs::get_path_from_proc_self_fd; +use crate::fs::OpenOptionsExt; +use crate::fs::{manually, open_beneath, FollowSymlinks, OpenOptions}; +use rustix::fs::OFlags; +use std::path::{Component, Path, PathBuf}; +use std::{fs, io}; + +/// Implement `canonicalize` by using readlink on `/proc/self/fd/*`. +pub(crate) fn canonicalize_impl(start: &fs::File, path: &Path) -> io::Result { + // Open the path with `O_PATH`. Use `read(true)` even though we don't need + // `read` permissions, because Rust's libstd requires an access mode, and + // Linux ignores `O_RDONLY` with `O_PATH`. + let result = open_beneath( + start, + path, + OpenOptions::new() + .read(true) + .follow(FollowSymlinks::Yes) + .custom_flags(OFlags::PATH.bits() as i32), + ); + + // If that worked, call `readlink`. + match result { + Ok(file) => { + if let Ok(start_path) = get_path_from_proc_self_fd(start) { + if let Ok(file_path) = get_path_from_proc_self_fd(&file) { + if let Ok(canonical_path) = file_path.strip_prefix(start_path) { + #[cfg(racy_asserts)] + if canonical_path.as_os_str().is_empty() { + assert_eq!( + Component::CurDir.as_os_str(), + manually::canonicalize(start, path).unwrap() + ); + } else { + assert_eq!( + canonical_path, + manually::canonicalize(start, path).unwrap() + ); + } + + let mut path_buf = canonical_path.to_path_buf(); + + // Replace "" with ".", since "" as a relative path is + // interpreted as an error. + if path_buf.as_os_str().is_empty() { + path_buf.push(Component::CurDir); + } + + return Ok(path_buf); + } + } + } + } + Err(err) => match rustix::io::Errno::from_io_error(&err) { + // `ENOSYS` from `open_beneath` means `openat2` is unavailable + // and we should use a fallback. + Some(rustix::io::Errno::NOSYS) => (), + _ => return Err(err), + }, + } + + // Use a fallback. + manually::canonicalize(start, path) +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/file_metadata.rs b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/file_metadata.rs new file mode 100644 index 000000000000..4f24a5dcbf28 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/file_metadata.rs @@ -0,0 +1,27 @@ +use crate::fs::{ImplMetadataExt, Metadata}; +use rustix::fs::{statat, AtFlags}; +use std::sync::atomic::AtomicBool; +use std::sync::atomic::Ordering::Relaxed; +use std::{fs, io}; + +/// Like `file.metadata()`, but works with `O_PATH` descriptors on old (pre +/// 3.6) versions of Linux too. +pub(super) fn file_metadata(file: &fs::File) -> io::Result { + // Record whether we've seen an `EBADF` from an `fstat` on an `O_PATH` + // file descriptor, meaning we're on a Linux that doesn't support it. + static FSTAT_PATH_BADF: AtomicBool = AtomicBool::new(false); + + if !FSTAT_PATH_BADF.load(Relaxed) { + match Metadata::from_file(file) { + Ok(metadata) => return Ok(metadata), + Err(err) => match rustix::io::Errno::from_io_error(&err) { + // Before Linux 3.6, `fstat` with `O_PATH` returned `EBADF`. + Some(rustix::io::Errno::BADF) => FSTAT_PATH_BADF.store(true, Relaxed), + _ => return Err(err), + }, + } + } + + // If `fstat` with `O_PATH` isn't supported, use `statat` with `AT_EMPTY_PATH`. + Ok(statat(file, "", AtFlags::EMPTY_PATH).map(ImplMetadataExt::from_rustix)?) +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/file_path.rs b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/file_path.rs new file mode 100644 index 000000000000..430f10a04488 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/file_path.rs @@ -0,0 +1,22 @@ +use super::procfs::get_path_from_proc_self_fd; +use std::fs; +use std::path::PathBuf; + +pub(crate) fn file_path(file: &fs::File) -> Option { + use std::os::unix::fs::MetadataExt; + + // Ignore paths that don't start with '/', which are things like + // `socket:[3556564]` or similar. + let path = get_path_from_proc_self_fd(file) + .ok() + .filter(|path| path.starts_with("/"))?; + + // Linux appends the string " (deleted)" when a file is deleted; avoid + // treating that as the actual name. Check this after doing the `readlink` + // above so that we're conservative about concurrent deletions. + if file.metadata().ok()?.nlink() == 0 { + return None; + } + + Some(path) +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/mod.rs b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/mod.rs new file mode 100644 index 000000000000..fd85ffce99c3 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/mod.rs @@ -0,0 +1,37 @@ +#[cfg(target_os = "linux")] +mod canonicalize_impl; +#[cfg(target_os = "linux")] +mod file_metadata; +mod file_path; +#[cfg(target_os = "linux")] +mod open_entry_impl; +mod open_impl; +mod procfs; +mod set_permissions_impl; +mod set_times_impl; +#[cfg(target_os = "linux")] +mod stat_impl; + +#[cfg(target_os = "android")] +pub(crate) use crate::fs::manually::canonicalize as canonicalize_impl; +#[cfg(target_os = "android")] +pub(crate) use crate::fs::manually::open_entry as open_entry_impl; +#[cfg(target_os = "android")] +pub(crate) use crate::fs::manually::stat as stat_impl; +pub(crate) use crate::fs::via_parent::set_times_nofollow as set_times_nofollow_impl; +#[cfg(target_os = "linux")] +pub(crate) use canonicalize_impl::canonicalize_impl; +pub(crate) use file_path::file_path; +#[cfg(target_os = "linux")] +pub(crate) use open_entry_impl::open_entry_impl; +#[cfg(target_os = "linux")] +pub(crate) use open_impl::open_beneath; +pub(crate) use open_impl::open_impl; +pub(crate) use set_permissions_impl::set_permissions_impl; +pub(crate) use set_times_impl::set_times_impl; +#[cfg(target_os = "linux")] +pub(crate) use stat_impl::stat_impl; + +// In theory we could optimize `link` using `openat2` with `O_PATH` and +// `linkat` with `AT_EMPTY_PATH`, however that requires `CAP_DAC_READ_SEARCH`, +// so it isn't very widely applicable. diff --git a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/open_entry_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/open_entry_impl.rs new file mode 100644 index 000000000000..b5bc0f6970d9 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/open_entry_impl.rs @@ -0,0 +1,19 @@ +use crate::fs::{manually, open_beneath, OpenOptions}; +use std::ffi::OsStr; +use std::{fs, io}; + +pub(crate) fn open_entry_impl( + start: &fs::File, + path: &OsStr, + options: &OpenOptions, +) -> io::Result { + let result = open_beneath(start, path.as_ref(), options); + + match result { + Ok(file) => Ok(file), + Err(err) => match rustix::io::Errno::from_io_error(&err) { + Some(rustix::io::Errno::NOSYS) => manually::open_entry(start, path, options), + _ => Err(err), + }, + } +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/open_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/open_impl.rs new file mode 100644 index 000000000000..edb4c8bd2554 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/open_impl.rs @@ -0,0 +1,150 @@ +//! Linux 5.6 and later have a syscall `openat2`, with flags that allow it to +//! enforce the sandboxing property we want. See the [LWN article] for an +//! overview and the [`openat2` documentation] for details. +//! +//! [LWN article]: https://lwn.net/Articles/796868/ +//! [`openat2` documentation]: https://man7.org/linux/man-pages/man2/openat2.2.html +//! +//! On older Linux, fall back to `manually::open`. + +#[cfg(racy_asserts)] +use crate::fs::is_same_file; +use crate::fs::{manually, OpenOptions}; +use std::path::Path; +use std::{fs, io}; +#[cfg(target_os = "linux")] +use { + super::super::super::fs::compute_oflags, + crate::fs::errors, + io_lifetimes::FromFd, + rustix::fs::{openat2, Mode, OFlags, RawMode, ResolveFlags}, + rustix::path::Arg, + std::sync::atomic::AtomicBool, + std::sync::atomic::Ordering::Relaxed, +}; + +/// Call the `openat2` system call, or use a fallback if that's unavailable. +pub(crate) fn open_impl( + start: &fs::File, + path: &Path, + options: &OpenOptions, +) -> io::Result { + // On regular Linux, attempt to use `openat2` to accelerate sandboxed + // lookups. On Android, the [seccomp policy] prevents us from even + // detecting whether `openat2` is supported, so don't even try. + // + // [seccomp policy]: https://android-developers.googleblog.com/2017/07/seccomp-filter-in-android-o.html + #[cfg(target_os = "linux")] + { + let result = open_beneath(start, path, options); + + // If we got anything other than a `ENOSYS` error, that's our result. + match result { + Err(err) if err.raw_os_error() == Some(rustix::io::Errno::NOSYS.raw_os_error()) => {} + Err(err) => return Err(err), + Ok(fd) => return Ok(fd), + } + } + + manually::open(start, path, options) +} + +/// Call the `openat2` system call with `RESOLVE_BENEATH`. If the syscall is +/// unavailable, mark it so for future calls. If `openat2` is unavailable +/// either permanently or temporarily, return `ENOSYS`. +#[cfg(target_os = "linux")] +pub(crate) fn open_beneath( + start: &fs::File, + path: &Path, + options: &OpenOptions, +) -> io::Result { + static INVALID: AtomicBool = AtomicBool::new(false); + if INVALID.load(Relaxed) { + // `openat2` is permanently unavailable. + return Err(rustix::io::Errno::NOSYS.into()); + } + + let oflags = compute_oflags(options)?; + + // Do two `contains` checks because `TMPFILE` may be represented with + // multiple flags and we need to ensure they're all set. + let mode = if oflags.contains(OFlags::CREATE) || oflags.contains(OFlags::TMPFILE) { + Mode::from_bits((options.ext.mode & 0o7777) as RawMode).unwrap() + } else { + Mode::empty() + }; + + // We know `openat2` needs a `&CStr` internally; to avoid allocating on + // each iteration of the loop below, allocate the `CString` now. + path.into_with_c_str(|path_c_str| { + // `openat2` fails with `EAGAIN` if a rename happens anywhere on the host + // while it's running, so use a loop to retry it a few times. But not too many + // times, because there's no limit on how often this can happen. The actual + // number here is currently an arbitrarily chosen guess. + for _ in 0..4 { + match openat2( + start, + path_c_str, + oflags, + mode, + ResolveFlags::BENEATH | ResolveFlags::NO_MAGICLINKS, + ) { + Ok(file) => { + let file = fs::File::from_into_fd(file); + + #[cfg(racy_asserts)] + check_open(start, path, options, &file); + + return Ok(file); + } + Err(err) => match err { + // A rename or similar happened. Try again. + rustix::io::Errno::AGAIN => continue, + + // `EPERM` is used by some `seccomp` sandboxes to indicate + // that `openat2` is unimplemented: + // + // + // However, `EPERM` may also indicate a failed `O_NOATIME` + // or a file seal prevented the operation, and it's complex + // to detect those cases, so exit the loop and use the + // fallback. + rustix::io::Errno::PERM => break, + + // `ENOSYS` means `openat2` is permanently unavailable; + // mark it so and exit the loop. + rustix::io::Errno::NOSYS => { + INVALID.store(true, Relaxed); + break; + } + + _ => return Err(err), + }, + } + } + + Err(rustix::io::Errno::NOSYS) + }) + .map_err(|err| match err { + rustix::io::Errno::XDEV => errors::escape_attempt(), + err => err.into(), + }) +} + +#[cfg(racy_asserts)] +fn check_open(start: &fs::File, path: &Path, options: &OpenOptions, file: &fs::File) { + let check = manually::open( + start, + path, + options + .clone() + .create(false) + .create_new(false) + .truncate(false), + ) + .expect("manually::open failed when open_openat2 succeeded"); + assert!( + is_same_file(file, &check).unwrap(), + "manually::open should open the same inode as open_openat2" + ); +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/procfs.rs b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/procfs.rs new file mode 100644 index 000000000000..aa87a0a8268c --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/procfs.rs @@ -0,0 +1,82 @@ +//! Utilities for working with `/proc`, where Linux's `procfs` is typically +//! mounted. `/proc` serves as an adjunct to Linux's main syscall surface area, +//! providing additional features with an awkward interface. +//! +//! This module does a considerable amount of work to determine whether `/proc` +//! is mounted, with actual `procfs`, and without any additional mount points +//! on top of the paths we open. + +use crate::fs::OpenOptionsExt; +use crate::fs::{ + errors, open, read_link_unchecked, set_times_follow_unchecked, OpenOptions, SystemTimeSpec, +}; +use io_lifetimes::{AsFd, AsFilelike}; +use rustix::fs::{chmodat, AtFlags, Mode, OFlags, RawMode}; +use rustix::path::DecInt; +use rustix_linux_procfs::proc_self_fd; +use std::os::unix::fs::PermissionsExt; +use std::path::{Path, PathBuf}; +use std::{fs, io}; + +pub(crate) fn get_path_from_proc_self_fd(file: &fs::File) -> io::Result { + read_link_unchecked( + &proc_self_fd()?.as_filelike_view::(), + DecInt::from_fd(file).as_ref(), + PathBuf::new(), + ) +} + +/// Linux's `fchmodat` doesn't support `AT_NOFOLLOW_SYMLINK`, so we can't trust +/// that it won't follow a symlink outside the sandbox. As an alternative, the +/// symlinks in Linux's /proc/self/fd/* aren't ordinary symlinks, they're +/// "magic links", which are more transparent, to the point of allowing chmod +/// to work. So we open the file with `O_PATH` and then do `fchmodat` on the +/// corresponding /proc/self/fd/* link. +pub(crate) fn set_permissions_through_proc_self_fd( + start: &fs::File, + path: &Path, + perm: fs::Permissions, +) -> io::Result<()> { + let opath = open( + start, + path, + OpenOptions::new() + .read(true) + .custom_flags(OFlags::PATH.bits() as i32), + )?; + + let dirfd = proc_self_fd()?; + let mode = Mode::from_bits(perm.mode() as RawMode).ok_or_else(errors::invalid_flags)?; + Ok(chmodat( + dirfd, + DecInt::from_fd(&opath), + mode, + AtFlags::empty(), + )?) +} + +pub(crate) fn set_times_through_proc_self_fd( + start: &fs::File, + path: &Path, + atime: Option, + mtime: Option, +) -> io::Result<()> { + let opath = open( + start, + path, + OpenOptions::new() + .read(true) + .custom_flags(OFlags::PATH.bits() as i32), + )?; + + // Don't pass `AT_SYMLINK_NOFOLLOW`, because we do actually want to follow + // the first symlink. We don't want to follow any subsequent symlinks, but + // omitting `O_NOFOLLOW` above ensures that the destination of the link + // isn't a symlink. + set_times_follow_unchecked( + proc_self_fd()?.as_fd(), + DecInt::from_fd(&opath).as_ref(), + atime, + mtime, + ) +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/set_permissions_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/set_permissions_impl.rs new file mode 100644 index 000000000000..e799bb43ff01 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/set_permissions_impl.rs @@ -0,0 +1,55 @@ +use super::procfs::set_permissions_through_proc_self_fd; +use crate::fs::{open, OpenOptions, Permissions}; +use rustix::fs::{fchmod, Mode, RawMode}; +use std::os::unix::fs::PermissionsExt; +use std::path::Path; +use std::{fs, io}; + +pub(crate) fn set_permissions_impl( + start: &fs::File, + path: &Path, + perm: Permissions, +) -> io::Result<()> { + let std_perm = perm.into_std(start)?; + + // First try using `O_PATH` and doing a chmod on `/proc/self/fd/{}` + // (`fchmod` doesn't work on `O_PATH` file descriptors). + // + // This may fail, due to older Linux versions not supporting `O_PATH`, or + // due to procfs being unavailable, but if it does work, go with it, as + // `O_PATH` tells Linux that we don't actually need to read or write the + // file, which may avoid side effects associated with opening device files. + let result = set_permissions_through_proc_self_fd(start, path, std_perm.clone()); + if let Ok(()) = result { + return Ok(()); + } + + // Then try `fchmod` with a normal handle. Normal handles need some kind of + // access, so first try read. + match open(start, path, OpenOptions::new().read(true)) { + Ok(file) => return set_file_permissions(&file, std_perm), + Err(err) => match rustix::io::Errno::from_io_error(&err) { + Some(rustix::io::Errno::ACCESS) => (), + _ => return Err(err), + }, + } + + // Next try write. + match open(start, path, OpenOptions::new().write(true)) { + Ok(file) => return set_file_permissions(&file, std_perm), + Err(err) => match rustix::io::Errno::from_io_error(&err) { + Some(rustix::io::Errno::ACCESS) | Some(rustix::io::Errno::ISDIR) => (), + _ => return Err(err), + }, + } + + // Nothing worked, so just return the original error. + result +} + +fn set_file_permissions(file: &fs::File, perm: fs::Permissions) -> io::Result<()> { + // Use `from_bits_truncate` for compatibility with std, which allows + // non-permission bits to propagate through. + let mode = Mode::from_bits_truncate(perm.mode() as RawMode); + Ok(fchmod(file, mode)?) +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/set_times_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/set_times_impl.rs new file mode 100644 index 000000000000..5844c7d7d118 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/set_times_impl.rs @@ -0,0 +1,48 @@ +//! This module consists of helper types and functions for dealing +//! with setting the file times specific to Linux. + +use super::procfs::set_times_through_proc_self_fd; +use crate::fs::{open, OpenOptions, SystemTimeSpec}; +use std::path::Path; +use std::{fs, io}; + +pub(crate) fn set_times_impl( + start: &fs::File, + path: &Path, + atime: Option, + mtime: Option, +) -> io::Result<()> { + // Try `futimens` with a normal handle. Normal handles need some kind of + // access, so first try write. + match open(start, path, OpenOptions::new().write(true)) { + Ok(file) => { + return fs_set_times::SetTimes::set_times( + &file, + atime.map(SystemTimeSpec::into_std), + mtime.map(SystemTimeSpec::into_std), + ) + } + Err(err) => match rustix::io::Errno::from_io_error(&err) { + Some(rustix::io::Errno::ACCESS) | Some(rustix::io::Errno::ISDIR) => (), + _ => return Err(err), + }, + } + + // Next try read. + match open(start, path, OpenOptions::new().read(true)) { + Ok(file) => { + return fs_set_times::SetTimes::set_times( + &file, + atime.map(SystemTimeSpec::into_std), + mtime.map(SystemTimeSpec::into_std), + ) + } + Err(err) => match rustix::io::Errno::from_io_error(&err) { + Some(rustix::io::Errno::ACCESS) => (), + _ => return Err(err), + }, + } + + // If neither of those worked, turn to `/proc`. + set_times_through_proc_self_fd(start, path, atime, mtime) +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/stat_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/stat_impl.rs new file mode 100644 index 000000000000..df358a019f1e --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/stat_impl.rs @@ -0,0 +1,56 @@ +//! Linux has an `O_PATH` flag which allows opening a file without necessary +//! having read or write access to it; we can use that with `openat2` and +//! `fstat` to perform a fast sandboxed `stat`. + +use super::file_metadata::file_metadata; +use crate::fs::{manually, open_beneath, FollowSymlinks, Metadata, OpenOptions}; +use rustix::fs::OFlags; +use std::path::Path; +use std::{fs, io}; + +/// Use `openat2` with `O_PATH` and `fstat`. If that's not available, fallback +/// to `manually::stat`. +pub(crate) fn stat_impl( + start: &fs::File, + path: &Path, + follow: FollowSymlinks, +) -> io::Result { + use crate::fs::{stat_unchecked, OpenOptionsExt}; + use std::path::Component; + + // Optimization: if path has exactly one component and it's not ".." or + // anything non-normal and we're not following symlinks we can go straight + // to `stat_unchecked`, which is faster than doing an open with a separate + // `fstat`. + if follow == FollowSymlinks::No { + let mut components = path.components(); + if let Some(Component::Normal(component)) = components.next() { + if components.next().is_none() { + return stat_unchecked(start, component.as_ref(), FollowSymlinks::No); + } + } + } + + // Open the path with `O_PATH`. Use `read(true)` even though we don't need + // `read` permissions, because Rust's libstd requires an access mode, and + // Linux ignores `O_RDONLY` with `O_PATH`. + let result = open_beneath( + start, + path, + OpenOptions::new() + .read(true) + .follow(follow) + .custom_flags(OFlags::PATH.bits() as i32), + ); + + // If that worked, call `fstat`. + match result { + Ok(file) => file_metadata(&file), + Err(err) => match rustix::io::Errno::from_io_error(&err) { + // `ENOSYS` from `open_beneath` means `openat2` is unavailable + // and we should use a fallback. + Some(rustix::io::Errno::NOSYS) => manually::stat(start, path, follow), + _ => Err(err), + }, + } +} diff --git a/crates/wasi/src/filesystem/primitives/rustix/linux/mod.rs b/crates/wasi/src/filesystem/primitives/rustix/linux/mod.rs new file mode 100644 index 000000000000..3ddb2c3266bb --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/linux/mod.rs @@ -0,0 +1,6 @@ +//! Following [`std`], we don't carry workarounds for Linux versions +//! older than 2.6.32. +//! +//! [`std`]: https://github.com/rust-lang/rust/pull/74163 + +pub(crate) mod fs; diff --git a/crates/wasi/src/filesystem/primitives/rustix/mod.rs b/crates/wasi/src/filesystem/primitives/rustix/mod.rs new file mode 100644 index 000000000000..0c0fd2437257 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/rustix/mod.rs @@ -0,0 +1,17 @@ +//! The `rustix` module contains code specific to the Posix-ish platforms +//! supported by the `rustix` crate. + +pub(crate) mod fs; + +#[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "watchos", + target_os = "visionos", +))] +mod darwin; +#[cfg(target_os = "freebsd")] +mod freebsd; +#[cfg(any(target_os = "android", target_os = "linux"))] +mod linux; diff --git a/crates/wasi/src/filesystem/primitives/set_permissions.rs b/crates/wasi/src/filesystem/primitives/set_permissions.rs new file mode 100644 index 000000000000..bc701945a007 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/set_permissions.rs @@ -0,0 +1,89 @@ +//! This defines `set_permissions`, the primary entrypoint to sandboxed +//! filesystem permissions modification. + +#[cfg(racy_asserts)] +use crate::fs::{map_result, stat, stat_unchecked, FollowSymlinks, Metadata}; +use crate::fs::{set_permissions_impl, set_symlink_permissions_impl, Permissions}; +use std::path::Path; +use std::{fs, io}; + +/// Perform a `chmodat`-like operation, 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))] +#[inline] +pub fn set_permissions(start: &fs::File, path: &Path, perm: Permissions) -> io::Result<()> { + #[cfg(racy_asserts)] + let perm_clone = perm.clone(); + + #[cfg(racy_asserts)] + let stat_before = stat(start, path, FollowSymlinks::Yes); + + // Call the underlying implementation. + let result = set_permissions_impl(start, path, perm); + + #[cfg(racy_asserts)] + let stat_after = stat_unchecked(start, path, FollowSymlinks::Yes); + + #[cfg(racy_asserts)] + check_set_permissions(start, path, perm_clone, &stat_before, &result, &stat_after); + + result +} + +/// Perform a `chmodat`-like operation, ensuring that the resolution of the +/// path never escapes the directory tree rooted at `start`, without following +/// symlinks. +#[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))] +#[inline] +pub fn set_symlink_permissions(start: &fs::File, path: &Path, perm: Permissions) -> io::Result<()> { + #[cfg(racy_asserts)] + let perm_clone = perm.clone(); + + #[cfg(racy_asserts)] + let stat_before = stat(start, path, FollowSymlinks::No); + + // Call the underlying implementation. + let result = set_symlink_permissions_impl(start, path, perm); + + #[cfg(racy_asserts)] + let stat_after = stat_unchecked(start, path, FollowSymlinks::No); + + #[cfg(racy_asserts)] + check_set_permissions(start, path, perm_clone, &stat_before, &result, &stat_after); + + result +} + +#[cfg(racy_asserts)] +fn check_set_permissions( + start: &fs::File, + path: &Path, + perm: Permissions, + stat_before: &io::Result, + result: &io::Result<()>, + stat_after: &io::Result, +) { + match ( + map_result(stat_before), + map_result(result), + map_result(stat_after), + ) { + (Ok(_), Ok(()), Ok(metadata)) => { + assert_eq!(perm, metadata.permissions()); + } + + (Ok(metadata_before), Err(_), Ok(metadata_after)) => { + assert_eq!(metadata_before.permissions(), metadata_after.permissions()); + } + + // TODO: Check error messages + (Err(_), Err(_), Err(_)) => (), + + other => panic!( + "inconsistent set_permissions checks: start='{:?}' path='{}':\n{:#?}", + start, + path.display(), + other, + ), + } +} diff --git a/crates/wasi/src/filesystem/primitives/set_times.rs b/crates/wasi/src/filesystem/primitives/set_times.rs new file mode 100644 index 000000000000..44fca3b7681e --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/set_times.rs @@ -0,0 +1,32 @@ +//! This defines `set_times`, the primary entrypoint to sandboxed +//! filesystem times modification. +//! +//! TODO: `check_set_times` etc. + +use crate::fs::{set_times_impl, set_times_nofollow_impl, SystemTimeSpec}; +use std::path::Path; +use std::{fs, io}; + +/// Perform a `utimensat`-like operation, ensuring that the resolution of the +/// path never escapes the directory tree rooted at `start`. This function +/// follows symlinks. +#[inline] +pub fn set_times( + start: &fs::File, + path: &Path, + atime: Option, + mtime: Option, +) -> io::Result<()> { + set_times_impl(start, path, atime, mtime) +} + +/// Like `set_times`, but never follows symlinks. +#[inline] +pub fn set_times_nofollow( + start: &fs::File, + path: &Path, + atime: Option, + mtime: Option, +) -> io::Result<()> { + set_times_nofollow_impl(start, path, atime, mtime) +} diff --git a/crates/wasi/src/filesystem/primitives/stat.rs b/crates/wasi/src/filesystem/primitives/stat.rs new file mode 100644 index 000000000000..4aff5db5f0e9 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/stat.rs @@ -0,0 +1,81 @@ +//! This defines `stat`, the primary entrypoint to sandboxed metadata querying. + +#[cfg(racy_asserts)] +use crate::fs::{canonicalize, map_result, stat_unchecked}; +use crate::fs::{stat_impl, FollowSymlinks, Metadata}; +use std::path::Path; +use std::{fs, io}; + +/// Perform an `fstatat`-like operation, 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))] +#[inline] +pub fn stat(start: &fs::File, path: &Path, follow: FollowSymlinks) -> io::Result { + // Call the underlying implementation. + let result = stat_impl(start, path, follow); + + #[cfg(racy_asserts)] + let stat = stat_unchecked(start, path, follow); + + #[cfg(racy_asserts)] + check_stat(start, path, follow, &result, &stat); + + result +} + +#[cfg(racy_asserts)] +#[allow(clippy::enum_glob_use)] +fn check_stat( + start: &fs::File, + path: &Path, + follow: FollowSymlinks, + result: &io::Result, + stat: &io::Result, +) { + use io::ErrorKind::*; + + match (map_result(result), map_result(stat)) { + (Ok(metadata), Ok(unchecked_metadata)) => { + assert_same_file_metadata!( + metadata, + unchecked_metadata, + "path resolution inconsistency: start='{:?}', path='{}'", + start, + path.display(), + ); + } + + (Err((PermissionDenied, message)), _) => { + if let FollowSymlinks::Yes = follow { + match map_result(&canonicalize(start, path)) { + Ok(_) => (), + Err((PermissionDenied, canon_message)) => { + assert_eq!(message, canon_message); + } + err => panic!("stat failed where canonicalize succeeded: {:?}", err), + } + } else { + // TODO: Check that stat 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 stat start='{:?}', path='{}':\n{:#?}", + start, + path.display(), + other, + ), + } +} diff --git a/crates/wasi/src/filesystem/primitives/symlink.rs b/crates/wasi/src/filesystem/primitives/symlink.rs new file mode 100644 index 000000000000..d43d809e99d4 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/symlink.rs @@ -0,0 +1,354 @@ +//! This defines `symlink`, the primary entrypoint to sandboxed symlink +//! creation. + +use crate::fs::errors; +#[cfg(all(racy_asserts, not(windows)))] +use crate::fs::symlink_unchecked; +#[cfg(racy_asserts)] +use crate::fs::{canonicalize, manually, map_result, stat_unchecked, FollowSymlinks, Metadata}; +#[cfg(all(racy_asserts, windows))] +use crate::fs::{symlink_dir_unchecked, symlink_file_unchecked}; +use std::path::Path; +use std::{fs, io}; + +/// Perform a `symlinkat`-like operation, ensuring that the resolution of the +/// path never escapes the directory tree rooted at `start`. An error is +/// returned if the target path is absolute. +#[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))] +#[cfg(not(windows))] +#[inline] +pub fn symlink(old_path: &Path, new_start: &fs::File, new_path: &Path) -> io::Result<()> { + // Don't allow creating symlinks to absolute paths. This isn't strictly + // necessary to preserve the sandbox, since `open` will refuse to follow + // absolute symlinks in any case. However, it is useful to enforce this + // restriction so that a WASI program can't trick some other non-WASI + // program into following an absolute path. + if old_path.has_root() { + return Err(errors::escape_attempt()); + } + + write_symlink_impl(old_path, new_start, new_path) +} + +#[cfg(not(windows))] +fn write_symlink_impl(old_path: &Path, new_start: &fs::File, new_path: &Path) -> io::Result<()> { + use crate::fs::symlink_impl; + + #[cfg(racy_asserts)] + let stat_before = stat_unchecked(new_start, new_path, FollowSymlinks::No); + + // Call the underlying implementation. + let result = symlink_impl(old_path, new_start, new_path); + + #[cfg(racy_asserts)] + let stat_after = stat_unchecked(new_start, new_path, FollowSymlinks::No); + + #[cfg(racy_asserts)] + check_symlink( + old_path, + new_start, + new_path, + &stat_before, + &result, + &stat_after, + ); + + result +} + +/// Perform a `symlinkat`-like operation, ensuring that the resolution of the +/// link path never escapes the directory tree rooted at `start`. +#[cfg(not(windows))] +pub fn symlink_contents, Q: AsRef>( + old_path: P, + new_start: &fs::File, + new_path: Q, +) -> io::Result<()> { + write_symlink_impl(old_path.as_ref(), new_start, new_path.as_ref()) +} + +/// Perform a `symlink_file`-like operation, 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))] +#[cfg(windows)] +#[inline] +pub fn symlink_file(old_path: &Path, new_start: &fs::File, new_path: &Path) -> io::Result<()> { + use crate::fs::symlink_file_impl; + + // As above, don't allow creating symlinks to absolute paths. + if old_path.has_root() { + return Err(errors::escape_attempt()); + } + + #[cfg(racy_asserts)] + let stat_before = stat_unchecked(new_start, new_path, FollowSymlinks::No); + + // Call the underlying implementation. + let result = symlink_file_impl(old_path, new_start, new_path); + + #[cfg(racy_asserts)] + let stat_after = stat_unchecked(new_start, new_path, FollowSymlinks::No); + + #[cfg(racy_asserts)] + check_symlink_file( + old_path, + new_start, + new_path, + &stat_before, + &result, + &stat_after, + ); + + result +} + +/// Perform a `symlink_dir`-like operation, 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))] +#[cfg(windows)] +#[inline] +pub fn symlink_dir(old_path: &Path, new_start: &fs::File, new_path: &Path) -> io::Result<()> { + use crate::fs::symlink_dir_impl; + + // As above, don't allow creating symlinks to absolute paths. + if old_path.has_root() { + return Err(errors::escape_attempt()); + } + + #[cfg(racy_asserts)] + let stat_before = stat_unchecked(new_start, new_path, FollowSymlinks::No); + + // Call the underlying implementation. + let result = symlink_dir_impl(old_path, new_start, new_path); + + #[cfg(racy_asserts)] + let stat_after = stat_unchecked(new_start, new_path, FollowSymlinks::No); + + #[cfg(racy_asserts)] + check_symlink_dir( + old_path, + new_start, + new_path, + &stat_before, + &result, + &stat_after, + ); + + result +} + +#[cfg(all(not(windows), racy_asserts))] +#[allow(clippy::enum_glob_use)] +fn check_symlink( + old_path: &Path, + new_start: &fs::File, + new_path: &Path, + stat_before: &io::Result, + result: &io::Result<()>, + stat_after: &io::Result, +) { + use io::ErrorKind::*; + + match ( + map_result(stat_before), + map_result(result), + map_result(stat_after), + ) { + (Err((NotFound, _)), Ok(()), Ok(metadata)) => { + assert!(metadata.file_type().is_symlink()); + let canon = + manually::canonicalize_with(new_start, new_path, FollowSymlinks::No).unwrap(); + assert_same_file_metadata!( + &stat_unchecked(new_start, &canon, FollowSymlinks::No).unwrap(), + &metadata + ); + } + + (Ok(metadata_before), Err((AlreadyExists, _)), Ok(metadata_after)) => { + assert_same_file_metadata!(&metadata_before, &metadata_after); + } + + (_, Err((_kind, _message)), _) => match map_result(&canonicalize(new_start, new_path)) { + Ok(canon) => match map_result(&symlink_unchecked(old_path, new_start, &canon)) { + Err((_unchecked_kind, _unchecked_message)) => { + /* TODO: Check error messages. + assert_eq!( + kind, + unchecked_kind, + "unexpected error kind from symlink new_start='{:?}', \ + new_path='{}':\nstat_before={:#?}\nresult={:#?}\nstat_after={:#?}", + new_start, + new_path.display(), + stat_before, + result, + stat_after + ); + assert_eq!(message, unchecked_message); + */ + } + _ => panic!("unsandboxed symlink success"), + }, + Err((_canon_kind, _canon_message)) => { + /* TODO: Check error messages. + assert_eq!(kind, canon_kind); + assert_eq!(message, canon_message); + */ + } + }, + + _other => { + /* TODO: Check error messages. + panic!( + "inconsistent symlink checks: new_start='{:?}' new_path='{}':\n{:#?}", + new_start, + new_path.display(), + other, + ) + */ + } + } +} + +#[cfg(all(windows, racy_asserts))] +#[allow(clippy::enum_glob_use)] +fn check_symlink_file( + old_path: &Path, + new_start: &fs::File, + new_path: &Path, + stat_before: &io::Result, + result: &io::Result<()>, + stat_after: &io::Result, +) { + use io::ErrorKind::*; + + match ( + map_result(stat_before), + map_result(result), + map_result(stat_after), + ) { + (Err((NotFound, _)), Ok(()), Ok(metadata)) => { + assert!(metadata.file_type().is_symlink()); + let canon = + manually::canonicalize_with(new_start, new_path, FollowSymlinks::No).unwrap(); + assert_same_file_metadata!( + &stat_unchecked(new_start, &canon, FollowSymlinks::No).unwrap(), + &metadata + ); + } + + (Ok(metadata_before), Err((AlreadyExists, _)), Ok(metadata_after)) => { + assert_same_file_metadata!(&metadata_before, &metadata_after); + } + + (_, Err((_kind, _message)), _) => match map_result(&canonicalize(new_start, new_path)) { + Ok(canon) => match map_result(&symlink_file_unchecked(old_path, new_start, &canon)) { + Err((_unchecked_kind, _unchecked_message)) => { + /* TODO: Check error messages. + assert_eq!( + kind, + unchecked_kind, + "unexpected error kind from symlink new_start='{:?}', \ + new_path='{}':\nstat_before={:#?}\nresult={:#?}\nstat_after={:#?}", + new_start, + new_path.display(), + stat_before, + result, + stat_after + ); + assert_eq!(message, unchecked_message); + */ + } + _ => panic!("unsandboxed symlink success"), + }, + Err((_canon_kind, _canon_message)) => { + /* TODO: Check error messages. + assert_eq!(kind, canon_kind); + assert_eq!(message, canon_message); + */ + } + }, + + _other => { + /* TODO: Check error messages. + panic!( + "inconsistent symlink checks: new_start='{:?}' new_path='{}':\n{:#?}", + new_start, + new_path.display(), + other, + ) + */ + } + } +} + +#[cfg(all(windows, racy_asserts))] +#[allow(clippy::enum_glob_use)] +fn check_symlink_dir( + old_path: &Path, + new_start: &fs::File, + new_path: &Path, + stat_before: &io::Result, + result: &io::Result<()>, + stat_after: &io::Result, +) { + use io::ErrorKind::*; + + match ( + map_result(stat_before), + map_result(result), + map_result(stat_after), + ) { + (Err((NotFound, _)), Ok(()), Ok(metadata)) => { + assert!(metadata.file_type().is_symlink()); + let canon = + manually::canonicalize_with(new_start, new_path, FollowSymlinks::No).unwrap(); + assert_same_file_metadata!( + &stat_unchecked(new_start, &canon, FollowSymlinks::No).unwrap(), + &metadata + ); + } + + (Ok(metadata_before), Err((AlreadyExists, _)), Ok(metadata_after)) => { + assert_same_file_metadata!(&metadata_before, &metadata_after); + } + + (_, Err((_kind, _message)), _) => match map_result(&canonicalize(new_start, new_path)) { + Ok(canon) => match map_result(&symlink_dir_unchecked(old_path, new_start, &canon)) { + Err((_unchecked_kind, _unchecked_message)) => { + /* TODO: Check error messages. + assert_eq!( + kind, + unchecked_kind, + "unexpected error kind from symlink new_start='{:?}', \ + new_path='{}':\nstat_before={:#?}\nresult={:#?}\nstat_after={:#?}", + new_start, + new_path.display(), + stat_before, + result, + stat_after + ); + assert_eq!(message, unchecked_message); + */ + } + _ => panic!("unsandboxed symlink success"), + }, + Err((_canon_kind, _canon_message)) => { + /* TODO: Check error messages. + assert_eq!(kind, canon_kind); + assert_eq!(message, canon_message); + */ + } + }, + + _other => { + /* TODO: Check error messages. + panic!( + "inconsistent symlink checks: new_start='{:?}' new_path='{}':\n{:#?}", + new_start, + new_path.display(), + other, + ) + */ + } + } +} diff --git a/crates/wasi/src/filesystem/primitives/system_time_spec.rs b/crates/wasi/src/filesystem/primitives/system_time_spec.rs new file mode 100644 index 000000000000..5f2a3bd75bee --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/system_time_spec.rs @@ -0,0 +1,45 @@ +use crate::time::SystemTime; + +/// A value for specifying a time. +#[derive(Debug)] +#[allow(clippy::module_name_repetitions)] +pub enum SystemTimeSpec { + /// A value which always represents the current time, in symbolic form, so + /// that even as time elapses, it continues to represent the current time. + SymbolicNow, + + /// An absolute time value. + Absolute(SystemTime), +} + +impl SystemTimeSpec { + /// Constructs a new instance of `Self` from the given + /// [`fs_set_times::SystemTimeSpec`]. + // TODO: Make this a `const fn` once `SystemTime::from_std` is a `const fn`. + #[inline] + pub fn from_std(std: fs_set_times::SystemTimeSpec) -> Self { + match std { + fs_set_times::SystemTimeSpec::SymbolicNow => Self::SymbolicNow, + fs_set_times::SystemTimeSpec::Absolute(time) => { + Self::Absolute(SystemTime::from_std(time)) + } + } + } + + /// Constructs a new instance of [`fs_set_times::SystemTimeSpec`] from the + /// given `Self`. + #[inline] + pub const fn into_std(self) -> fs_set_times::SystemTimeSpec { + match self { + Self::SymbolicNow => fs_set_times::SystemTimeSpec::SymbolicNow, + Self::Absolute(time) => fs_set_times::SystemTimeSpec::Absolute(time.into_std()), + } + } +} + +impl From for SystemTimeSpec { + #[inline] + fn from(time: SystemTime) -> Self { + Self::Absolute(time) + } +} diff --git a/crates/wasi/src/filesystem/primitives/via_parent/access.rs b/crates/wasi/src/filesystem/primitives/via_parent/access.rs new file mode 100644 index 000000000000..591c6221a294 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/via_parent/access.rs @@ -0,0 +1,19 @@ +use super::open_parent; +use crate::fs::{access_unchecked, AccessType, FollowSymlinks, MaybeOwnedFile}; +use std::path::Path; +use std::{fs, io}; + +/// Implement `access` by `open`ing up the parent component of the path and +/// then calling `access_unchecked` on the last component. +pub(crate) fn access( + start: &fs::File, + path: &Path, + type_: AccessType, + follow: FollowSymlinks, +) -> io::Result<()> { + let start = MaybeOwnedFile::borrowed(start); + + let (dir, basename) = open_parent(start, path)?; + + access_unchecked(&dir, basename.as_ref(), type_, follow) +} diff --git a/crates/wasi/src/filesystem/primitives/via_parent/create_dir.rs b/crates/wasi/src/filesystem/primitives/via_parent/create_dir.rs new file mode 100644 index 000000000000..b1700f118d03 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/via_parent/create_dir.rs @@ -0,0 +1,19 @@ +use super::open_parent; +use crate::fs::{create_dir_unchecked, strip_dir_suffix, DirOptions, MaybeOwnedFile}; +use std::path::Path; +use std::{fs, io}; + +/// Implement `create_dir` by `open`ing up the parent component of the path and +/// then calling `create_dir_unchecked` on the last component. +pub(crate) fn create_dir(start: &fs::File, path: &Path, options: &DirOptions) -> io::Result<()> { + let start = MaybeOwnedFile::borrowed(start); + + // As a special case, `create_dir` ignores a trailing slash rather than + // treating it as equivalent to a trailing slash-dot, so strip any trailing + // slashes. + let path = strip_dir_suffix(path); + + let (dir, basename) = open_parent(start, &path)?; + + create_dir_unchecked(&dir, basename.as_ref(), options) +} diff --git a/crates/wasi/src/filesystem/primitives/via_parent/hard_link.rs b/crates/wasi/src/filesystem/primitives/via_parent/hard_link.rs new file mode 100644 index 000000000000..649df9c768cb --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/via_parent/hard_link.rs @@ -0,0 +1,26 @@ +use super::open_parent; +use crate::fs::{hard_link_unchecked, MaybeOwnedFile}; +use std::path::Path; +use std::{fs, io}; + +/// Implement `hard_link` by `open`ing up the parent component of the path and +/// then calling `hard_link_unchecked` on the last component. +pub(crate) fn hard_link( + old_start: &fs::File, + old_path: &Path, + new_start: &fs::File, + new_path: &Path, +) -> io::Result<()> { + let old_start = MaybeOwnedFile::borrowed(old_start); + let new_start = MaybeOwnedFile::borrowed(new_start); + + let (old_dir, old_basename) = open_parent(old_start, old_path)?; + let (new_dir, new_basename) = open_parent(new_start, new_path)?; + + hard_link_unchecked( + &old_dir, + old_basename.as_ref(), + &new_dir, + new_basename.as_ref(), + ) +} diff --git a/crates/wasi/src/filesystem/primitives/via_parent/mod.rs b/crates/wasi/src/filesystem/primitives/via_parent/mod.rs new file mode 100644 index 000000000000..84884cdc7db1 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/via_parent/mod.rs @@ -0,0 +1,41 @@ +//! In many operations, the last component of a path is special. For example, +//! in `create_dir`, the last component names the path to be created, while the +//! rest of the components just name the place to create it in. + +mod access; +mod create_dir; +mod hard_link; +mod open_parent; +#[cfg(not(windows))] // doesn't work on windows; use a windows-specific impl +mod read_link; +mod remove_dir; +mod remove_file; +mod rename; +#[cfg(windows)] +mod set_permissions; +#[cfg(not(target_os = "wasi"))] +mod set_symlink_permissions; +#[cfg(not(windows))] +mod set_times_nofollow; +mod symlink; + +use open_parent::open_parent; + +pub(crate) use access::access; +pub(crate) use create_dir::create_dir; +pub(crate) use hard_link::hard_link; +#[cfg(not(windows))] // doesn't work on windows; use a windows-specific impl +pub(crate) use read_link::read_link; +pub(crate) use remove_dir::remove_dir; +pub(crate) use remove_file::remove_file; +pub(crate) use rename::rename; +#[cfg(windows)] +pub(crate) use set_permissions::set_permissions; +#[cfg(not(target_os = "wasi"))] +pub(crate) use set_symlink_permissions::set_symlink_permissions; +#[cfg(not(windows))] +pub(crate) use set_times_nofollow::set_times_nofollow; +#[cfg(not(windows))] +pub(crate) use symlink::symlink; +#[cfg(windows)] +pub(crate) use symlink::{symlink_dir, symlink_file}; diff --git a/crates/wasi/src/filesystem/primitives/via_parent/open_parent.rs b/crates/wasi/src/filesystem/primitives/via_parent/open_parent.rs new file mode 100644 index 000000000000..fe52e6c7d562 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/via_parent/open_parent.rs @@ -0,0 +1,126 @@ +/// `open_parent` is the key building block in all `via_parent` functions. +/// It opens the parent directory of the given path, and returns the basename, +/// so that all the `via_parent` functions need to do is make sure they +/// don't follow symlinks in the basename. +use crate::fs::{errors, open_dir, path_requires_dir, MaybeOwnedFile}; +use std::ffi::OsStr; +use std::io; +use std::path::{Component, Path}; + +/// Open the "parent" of `path`, relative to `start`. The return value on +/// success is a tuple of the newly opened directory and an `OsStr` referencing +/// the single remaining path component. This last component will not be `..`, +/// though it may be `.` or a symbolic link to anywhere (possibly +/// including `..` or an absolute path). +pub(super) fn open_parent<'path, 'borrow>( + start: MaybeOwnedFile<'borrow>, + path: &'path Path, +) -> io::Result<(MaybeOwnedFile<'borrow>, &'path OsStr)> { + let (dirname, basename) = split_parent(path).ok_or_else(errors::no_such_file_or_directory)?; + + let dir = if dirname.as_os_str().is_empty() { + start + } else { + MaybeOwnedFile::owned(open_dir(&start, dirname)?) + }; + + Ok((dir, basename.as_os_str())) +} + +/// Split `path` into parent and basename parts. Return `None` if `path` +/// is empty. +/// +/// This differs from `path.parent()` and `path.file_name()` in several +/// respects: +/// - Treat paths ending in `/` or `/.` as implying a directory. +/// - Treat the path `.` as a normal component rather than a parent. +/// - Append a `.` to a path with a trailing `..` to avoid requiring our +/// callers to special-case `..`. +/// - Bare absolute paths are ok. +fn split_parent(path: &Path) -> Option<(&Path, Component<'_>)> { + if path.as_os_str().is_empty() { + return None; + } + + if !path_requires_dir(path) { + let mut comps = path.components(); + if let Some(p) = comps.next_back() { + match p { + Component::Normal(_) | Component::CurDir => return Some((comps.as_path(), p)), + _ => (), + } + } + } + + Some((path, Component::CurDir)) +} + +#[test] +fn split_parent_basics() { + assert_eq!( + split_parent(Path::new("foo/bar/qux")).unwrap(), + ( + Path::new("foo/bar"), + Component::Normal(Path::new("qux").as_ref()) + ) + ); + assert_eq!( + split_parent(Path::new("foo/bar")).unwrap(), + ( + Path::new("foo"), + Component::Normal(Path::new("bar").as_ref()) + ) + ); + assert_eq!( + split_parent(Path::new("foo")).unwrap(), + (Path::new(""), Component::Normal(Path::new("foo").as_ref())) + ); +} + +#[test] +fn split_parent_special_cases() { + assert!(split_parent(Path::new("")).is_none()); + assert_eq!( + split_parent(Path::new("foo/")).unwrap(), + (Path::new("foo"), Component::CurDir) + ); + assert_eq!( + split_parent(Path::new("foo/.")).unwrap(), + (Path::new("foo"), Component::CurDir) + ); + assert_eq!( + split_parent(Path::new(".")).unwrap(), + (Path::new(""), Component::CurDir) + ); + assert_eq!( + split_parent(Path::new("..")).unwrap(), + (Path::new(".."), Component::CurDir) + ); + assert_eq!( + split_parent(Path::new("../..")).unwrap(), + (Path::new("../.."), Component::CurDir) + ); + assert_eq!( + split_parent(Path::new("../foo")).unwrap(), + ( + Path::new(".."), + Component::Normal(Path::new("foo").as_ref()) + ) + ); + assert_eq!( + split_parent(Path::new("foo/..")).unwrap(), + (Path::new("foo/.."), Component::CurDir) + ); + assert_eq!( + split_parent(Path::new("/foo")).unwrap(), + (Path::new("/"), Component::Normal(Path::new("foo").as_ref())) + ); + assert_eq!( + split_parent(Path::new("/foo/")).unwrap(), + (Path::new("/foo"), Component::CurDir) + ); + assert_eq!( + split_parent(Path::new("/")).unwrap(), + (Path::new("/"), Component::CurDir) + ); +} diff --git a/crates/wasi/src/filesystem/primitives/via_parent/read_link.rs b/crates/wasi/src/filesystem/primitives/via_parent/read_link.rs new file mode 100644 index 000000000000..0ca8d8c30680 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/via_parent/read_link.rs @@ -0,0 +1,21 @@ +use super::open_parent; +use crate::fs::{read_link_unchecked, MaybeOwnedFile}; +use std::path::{Path, PathBuf}; +use std::{fs, io}; + +/// Implement `read_link` by `open`ing up the parent component of the path and +/// then calling `read_link_unchecked` on the last component. +/// +/// This technique doesn't work in all cases on Windows. In particular, a +/// directory symlink such as `C:\Documents and Settings` may not grant any +/// access other than what is needed to resolve the symlink, so `open_parent`'s +/// technique of returning a relative path of `.` from that point doesn't work, +/// because opening `.` within such a directory is denied. Consequently, we use +/// a different implementation on Windows. +pub(crate) fn read_link(start: &fs::File, path: &Path) -> io::Result { + let start = MaybeOwnedFile::borrowed(start); + + let (dir, basename) = open_parent(start, path)?; + + read_link_unchecked(&dir, basename.as_ref(), PathBuf::new()) +} diff --git a/crates/wasi/src/filesystem/primitives/via_parent/remove_dir.rs b/crates/wasi/src/filesystem/primitives/via_parent/remove_dir.rs new file mode 100644 index 000000000000..94d36b1913d7 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/via_parent/remove_dir.rs @@ -0,0 +1,14 @@ +use super::open_parent; +use crate::fs::{remove_dir_unchecked, MaybeOwnedFile}; +use std::path::Path; +use std::{fs, io}; + +/// Implement `remove_dir` by `open`ing up the parent component of the path and +/// then calling `remove_dir_unchecked` on the last component. +pub(crate) fn remove_dir(start: &fs::File, path: &Path) -> io::Result<()> { + let start = MaybeOwnedFile::borrowed(start); + + let (dir, basename) = open_parent(start, path)?; + + remove_dir_unchecked(&dir, basename.as_ref()) +} diff --git a/crates/wasi/src/filesystem/primitives/via_parent/remove_file.rs b/crates/wasi/src/filesystem/primitives/via_parent/remove_file.rs new file mode 100644 index 000000000000..84a898efba1f --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/via_parent/remove_file.rs @@ -0,0 +1,14 @@ +use super::open_parent; +use crate::fs::{remove_file_unchecked, MaybeOwnedFile}; +use std::path::Path; +use std::{fs, io}; + +/// Implement `remove_file` by `open`ing up the parent component of the path +/// and then calling `remove_file_unchecked` on the last component. +pub(crate) fn remove_file(start: &fs::File, path: &Path) -> io::Result<()> { + let start = MaybeOwnedFile::borrowed(start); + + let (dir, basename) = open_parent(start, path)?; + + remove_file_unchecked(&dir, basename.as_ref()) +} diff --git a/crates/wasi/src/filesystem/primitives/via_parent/rename.rs b/crates/wasi/src/filesystem/primitives/via_parent/rename.rs new file mode 100644 index 000000000000..aeb345e566fa --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/via_parent/rename.rs @@ -0,0 +1,50 @@ +use super::open_parent; +#[cfg(unix)] +use crate::fs::{append_dir_suffix, path_has_trailing_slash}; +use crate::fs::{rename_unchecked, strip_dir_suffix, MaybeOwnedFile}; +use std::path::Path; +use std::{fs, io}; + +/// Implement `rename` by `open`ing up the parent component of the path and +/// then calling `rename_unchecked` on the last component. +pub(crate) fn rename( + old_start: &fs::File, + old_path: &Path, + new_start: &fs::File, + new_path: &Path, +) -> io::Result<()> { + let old_start = MaybeOwnedFile::borrowed(old_start); + let new_start = MaybeOwnedFile::borrowed(new_start); + + // As a special case, `rename` ignores a trailing slash rather than treating + // it as equivalent to a trailing slash-dot, so strip any trailing slashes + // for the purposes of `open_parent`. + // + // And on Unix, remember whether the source started with a slash so that we + // can still fail if it is and the source is a regular file. + #[cfg(unix)] + let old_starts_with_slash = path_has_trailing_slash(old_path); + let old_path = strip_dir_suffix(old_path); + let new_path = strip_dir_suffix(new_path); + + let (old_dir, old_basename) = open_parent(old_start, &old_path)?; + let (new_dir, new_basename) = open_parent(new_start, &new_path)?; + + // On Unix, re-append a slash if needed. + #[cfg(unix)] + let concat; + #[cfg(unix)] + let old_basename = if old_starts_with_slash { + concat = append_dir_suffix(old_basename.to_owned().into()); + concat.as_os_str() + } else { + old_basename + }; + + rename_unchecked( + &old_dir, + old_basename.as_ref(), + &new_dir, + new_basename.as_ref(), + ) +} diff --git a/crates/wasi/src/filesystem/primitives/via_parent/set_permissions.rs b/crates/wasi/src/filesystem/primitives/via_parent/set_permissions.rs new file mode 100644 index 000000000000..9c0c5a085e85 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/via_parent/set_permissions.rs @@ -0,0 +1,13 @@ +use super::open_parent; +use crate::fs::{set_permissions_unchecked, MaybeOwnedFile, Permissions}; +use std::path::Path; +use std::{fs, io}; + +#[inline] +pub(crate) fn set_permissions(start: &fs::File, path: &Path, perm: Permissions) -> io::Result<()> { + let start = MaybeOwnedFile::borrowed(start); + + let (dir, basename) = open_parent(start, &path)?; + + set_permissions_unchecked(&dir, basename.as_ref(), perm) +} diff --git a/crates/wasi/src/filesystem/primitives/via_parent/set_symlink_permissions.rs b/crates/wasi/src/filesystem/primitives/via_parent/set_symlink_permissions.rs new file mode 100644 index 000000000000..99adf7668bc1 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/via_parent/set_symlink_permissions.rs @@ -0,0 +1,17 @@ +use super::open_parent; +use crate::fs::{set_symlink_permissions_unchecked, MaybeOwnedFile, Permissions}; +use std::path::Path; +use std::{fs, io}; + +#[inline] +pub(crate) fn set_symlink_permissions( + start: &fs::File, + path: &Path, + perm: Permissions, +) -> io::Result<()> { + let start = MaybeOwnedFile::borrowed(start); + + let (dir, basename) = open_parent(start, path)?; + + set_symlink_permissions_unchecked(&dir, basename.as_ref(), perm) +} diff --git a/crates/wasi/src/filesystem/primitives/via_parent/set_times_nofollow.rs b/crates/wasi/src/filesystem/primitives/via_parent/set_times_nofollow.rs new file mode 100644 index 000000000000..ea880726e24e --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/via_parent/set_times_nofollow.rs @@ -0,0 +1,18 @@ +use super::open_parent; +use crate::fs::{set_times_nofollow_unchecked, MaybeOwnedFile, SystemTimeSpec}; +use std::path::Path; +use std::{fs, io}; + +#[inline] +pub(crate) fn set_times_nofollow( + start: &fs::File, + path: &Path, + atime: Option, + mtime: Option, +) -> io::Result<()> { + let start = MaybeOwnedFile::borrowed(start); + + let (dir, basename) = open_parent(start, path)?; + + set_times_nofollow_unchecked(&dir, basename.as_ref(), atime, mtime) +} diff --git a/crates/wasi/src/filesystem/primitives/via_parent/symlink.rs b/crates/wasi/src/filesystem/primitives/via_parent/symlink.rs new file mode 100644 index 000000000000..13599f31d485 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/via_parent/symlink.rs @@ -0,0 +1,48 @@ +use super::open_parent; +use crate::fs::MaybeOwnedFile; +use std::path::Path; +use std::{fs, io}; + +/// Implement `symlink` by `open`ing up the parent component of the path and +/// then calling `symlink_unchecked` on the last component. +#[cfg(not(windows))] +pub(crate) fn symlink(old_path: &Path, new_start: &fs::File, new_path: &Path) -> io::Result<()> { + use crate::fs::symlink_unchecked; + let new_start = MaybeOwnedFile::borrowed(new_start); + + let (new_dir, new_basename) = open_parent(new_start, new_path)?; + + symlink_unchecked(old_path, &new_dir, new_basename.as_ref()) +} + +/// Implement `symlink_file` by `open`ing up the parent component of the path +/// and then calling `symlink_file` on the last component. +#[cfg(windows)] +pub(crate) fn symlink_file( + old_path: &Path, + new_start: &fs::File, + new_path: &Path, +) -> io::Result<()> { + use crate::fs::symlink_file_unchecked; + let new_start = MaybeOwnedFile::borrowed(new_start); + + let (new_dir, new_basename) = open_parent(new_start, new_path)?; + + symlink_file_unchecked(old_path, &new_dir, new_basename.as_ref()) +} + +/// Implement `symlink_dir` by `open`ing up the parent component of the path +/// and then calling `symlink_dir` on the last component. +#[cfg(windows)] +pub(crate) fn symlink_dir( + old_path: &Path, + new_start: &fs::File, + new_path: &Path, +) -> io::Result<()> { + use crate::fs::symlink_dir_unchecked; + let new_start = MaybeOwnedFile::borrowed(new_start); + + let (new_dir, new_basename) = open_parent(new_start, new_path)?; + + symlink_dir_unchecked(old_path, &new_dir, new_basename.as_ref()) +} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/access_unchecked.rs b/crates/wasi/src/filesystem/primitives/windows/fs/access_unchecked.rs new file mode 100644 index 000000000000..0e6864b9f272 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/windows/fs/access_unchecked.rs @@ -0,0 +1,32 @@ +use crate::fs::{open, AccessType, FollowSymlinks, OpenOptions}; +use std::path::Path; +use std::{fs, io}; + +/// *Unsandboxed* function similar to `access`, but which does not perform +/// sandboxing. +pub(crate) fn access_unchecked( + start: &fs::File, + path: &Path, + type_: AccessType, + follow: FollowSymlinks, +) -> io::Result<()> { + let mut options = OpenOptions::new(); + options.follow(follow); + match type_ { + AccessType::Exists => { + options.read(true); + } + AccessType::Access(modes) => { + if modes.readable { + options.read(true); + } + if modes.writable { + options.write(true); + } + if modes.executable { + options.read(true); + } + } + } + open(start, path, &options).map(|_| ()) +} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/copy.rs b/crates/wasi/src/filesystem/primitives/windows/fs/copy.rs new file mode 100644 index 000000000000..b0b710b4dcdb --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/windows/fs/copy.rs @@ -0,0 +1,19 @@ +use super::get_path::get_path; +use crate::fs::{open, OpenOptions}; +use std::path::Path; +use std::{fs, io}; + +pub(crate) fn copy_impl( + from_start: &fs::File, + from_path: &Path, + to_start: &fs::File, + to_path: &Path, +) -> io::Result { + let from = open(from_start, from_path, OpenOptions::new().read(true))?; + let to = open( + to_start, + to_path, + OpenOptions::new().create(true).truncate(true).write(true), + )?; + fs::copy(get_path(&from)?, get_path(&to)?) +} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/create_dir_unchecked.rs b/crates/wasi/src/filesystem/primitives/windows/fs/create_dir_unchecked.rs new file mode 100644 index 000000000000..4cd445e10750 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/windows/fs/create_dir_unchecked.rs @@ -0,0 +1,18 @@ +use super::get_path::concatenate; +use crate::fs::DirOptions; +use std::path::Path; +use std::{fs, io}; + +/// *Unsandboxed* function similar to `create_dir`, but which does not perform +/// sandboxing. +/// +/// Windows doesn't have any extra flags in `DirOptions`, so the `options` +/// parameter is ignored. +pub(crate) fn create_dir_unchecked( + start: &fs::File, + path: &Path, + _options: &DirOptions, +) -> io::Result<()> { + let out_path = concatenate(start, path)?; + fs::create_dir(out_path) +} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/create_file_at_w.rs b/crates/wasi/src/filesystem/primitives/windows/fs/create_file_at_w.rs new file mode 100644 index 000000000000..392bc8866313 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/windows/fs/create_file_at_w.rs @@ -0,0 +1,279 @@ +#![allow(unsafe_code)] + +use std::mem; +use std::os::windows::io::HandleOrInvalid; +use std::ptr::null_mut; +use windows_sys::Wdk::Foundation::OBJECT_ATTRIBUTES; +use windows_sys::Wdk::Storage::FileSystem::{ + NtCreateFile, FILE_CREATE, FILE_DELETE_ON_CLOSE, FILE_NON_DIRECTORY_FILE, + FILE_NO_INTERMEDIATE_BUFFERING, FILE_OPEN, FILE_OPEN_FOR_BACKUP_INTENT, FILE_OPEN_IF, + FILE_OPEN_REPARSE_POINT, FILE_OVERWRITE, FILE_OVERWRITE_IF, FILE_RANDOM_ACCESS, + FILE_SEQUENTIAL_ONLY, FILE_SYNCHRONOUS_IO_NONALERT, FILE_WRITE_THROUGH, +}; +use windows_sys::Win32::Foundation::{ + RtlNtStatusToDosError, SetLastError, ERROR_ALREADY_EXISTS, ERROR_FILE_EXISTS, + ERROR_INVALID_NAME, ERROR_INVALID_PARAMETER, ERROR_NOT_SUPPORTED, GENERIC_ALL, GENERIC_READ, + GENERIC_WRITE, HANDLE, INVALID_HANDLE_VALUE, STATUS_OBJECT_NAME_COLLISION, STATUS_PENDING, + STATUS_SUCCESS, SUCCESS, UNICODE_STRING, +}; +use windows_sys::Win32::Foundation::{OBJ_CASE_INSENSITIVE, OBJ_INHERIT}; +use windows_sys::Win32::Security::{ + SECURITY_ATTRIBUTES, SECURITY_DESCRIPTOR, SECURITY_DYNAMIC_TRACKING, + SECURITY_QUALITY_OF_SERVICE, SECURITY_STATIC_TRACKING, +}; +use windows_sys::Win32::Storage::FileSystem::{ + CREATE_ALWAYS, CREATE_NEW, DELETE, FILE_ATTRIBUTE_ARCHIVE, FILE_ATTRIBUTE_COMPRESSED, + FILE_ATTRIBUTE_DEVICE, FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_EA, FILE_ATTRIBUTE_ENCRYPTED, + FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_INTEGRITY_STREAM, FILE_ATTRIBUTE_NORMAL, + FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, FILE_ATTRIBUTE_NO_SCRUB_DATA, FILE_ATTRIBUTE_OFFLINE, + FILE_ATTRIBUTE_PINNED, FILE_ATTRIBUTE_READONLY, FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS, + FILE_ATTRIBUTE_RECALL_ON_OPEN, FILE_ATTRIBUTE_REPARSE_POINT, FILE_ATTRIBUTE_SPARSE_FILE, + FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_TEMPORARY, FILE_ATTRIBUTE_UNPINNED, + FILE_ATTRIBUTE_VIRTUAL, FILE_CREATION_DISPOSITION, FILE_FLAGS_AND_ATTRIBUTES, + FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_DELETE_ON_CLOSE, FILE_FLAG_NO_BUFFERING, + FILE_FLAG_OPEN_NO_RECALL, FILE_FLAG_OPEN_REPARSE_POINT, FILE_FLAG_OVERLAPPED, + FILE_FLAG_POSIX_SEMANTICS, FILE_FLAG_RANDOM_ACCESS, FILE_FLAG_SEQUENTIAL_SCAN, + FILE_FLAG_SESSION_AWARE, FILE_FLAG_WRITE_THROUGH, FILE_READ_ATTRIBUTES, FILE_SHARE_MODE, + OPEN_ALWAYS, OPEN_EXISTING, SECURITY_CONTEXT_TRACKING, SECURITY_EFFECTIVE_ONLY, + SECURITY_SQOS_PRESENT, SYNCHRONIZE, TRUNCATE_EXISTING, +}; +use windows_sys::Win32::System::WindowsProgramming::{ + FILE_OPENED, FILE_OPEN_NO_RECALL, FILE_OPEN_REMOTE_INSTANCE, FILE_OVERWRITTEN, +}; +use windows_sys::Win32::System::IO::IO_STATUS_BLOCK; + +// All currently known `FILE_ATTRIBUTE_*` constants, according to +// windows-sys' documentation. +const FILE_ATTRIBUTE_VALID_FLAGS: FILE_FLAGS_AND_ATTRIBUTES = FILE_ATTRIBUTE_EA + | FILE_ATTRIBUTE_DEVICE + | FILE_ATTRIBUTE_HIDDEN + | FILE_ATTRIBUTE_NORMAL + | FILE_ATTRIBUTE_PINNED + | FILE_ATTRIBUTE_SYSTEM + | FILE_ATTRIBUTE_ARCHIVE + | FILE_ATTRIBUTE_OFFLINE + | FILE_ATTRIBUTE_VIRTUAL + | FILE_ATTRIBUTE_READONLY + | FILE_ATTRIBUTE_UNPINNED + | FILE_ATTRIBUTE_DIRECTORY + | FILE_ATTRIBUTE_ENCRYPTED + | FILE_ATTRIBUTE_TEMPORARY + | FILE_ATTRIBUTE_COMPRESSED + | FILE_ATTRIBUTE_SPARSE_FILE + | FILE_ATTRIBUTE_NO_SCRUB_DATA + | FILE_ATTRIBUTE_REPARSE_POINT + | FILE_ATTRIBUTE_RECALL_ON_OPEN + | FILE_ATTRIBUTE_INTEGRITY_STREAM + | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED + | FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS; + +/// Like Windows' `CreateFileW`, but takes a `dir` argument to use as the +/// root directory. +/// +/// Also, the `lpfilename` is a Rust slice instead of a C-style NUL-terminated +/// array, because that's what our callers have and it's closer to what +/// `NtCreatePath` takes. +#[allow(non_snake_case)] +pub unsafe fn CreateFileAtW( + dir: HANDLE, + lpfilename: &[u16], + dwdesiredaccess: u32, + dwsharemode: FILE_SHARE_MODE, + lpsecurityattributes: *const SECURITY_ATTRIBUTES, + dwcreationdisposition: FILE_CREATION_DISPOSITION, + dwflagsandattributes: FILE_FLAGS_AND_ATTRIBUTES, + htemplatefile: HANDLE, +) -> HandleOrInvalid { + // Absolute paths are not yet implemented here. + // + // It seems like `NtCreatePath` needs the apparently NT-internal `\??\` + // prefix prepended to absolute paths. It's possible it needs other + // path transforms as well. `RtlDosPathNameToNtPathName_U` may be a + // function that does these things, though it's not available in + // windows-sys and not documented, though one can find + // [unofficial blog posts], though even they say things like "I`m + // sorry that I cannot give more details on these functions". + // + // [unofficial blog posts]: https://mecanik.dev/en/posts/convert-dos-and-nt-paths-using-rtl-functions/ + assert!(dir != 0 as HANDLE); + + // Extended attributes are not implemented yet. + if htemplatefile != 0 as HANDLE { + SetLastError(ERROR_NOT_SUPPORTED); + return HandleOrInvalid::from_raw_handle(INVALID_HANDLE_VALUE as _); + } + + // Convert `dwcreationdisposition` to the `createdisposition` argument + // to `NtCreateFile`. Do this before converting `lpfilename` so that + // we can return early on failure. + let createdisposition = match dwcreationdisposition { + CREATE_NEW => FILE_CREATE, + CREATE_ALWAYS => FILE_OVERWRITE_IF, + OPEN_EXISTING => FILE_OPEN, + OPEN_ALWAYS => FILE_OPEN_IF, + TRUNCATE_EXISTING => FILE_OVERWRITE, + _ => { + SetLastError(ERROR_INVALID_PARAMETER); + return HandleOrInvalid::from_raw_handle(INVALID_HANDLE_VALUE as _); + } + }; + + // Convert `lpfilename` to a `UNICODE_STRING`. + let byte_length = lpfilename.len() * mem::size_of::(); + let length: u16 = match byte_length.try_into() { + Ok(length) => length, + Err(_) => { + SetLastError(ERROR_INVALID_NAME); + return HandleOrInvalid::from_raw_handle(INVALID_HANDLE_VALUE as _); + } + }; + let mut unicode_string = UNICODE_STRING { + Buffer: lpfilename.as_ptr() as *mut u16, + Length: length, + MaximumLength: length, + }; + + let mut handle = INVALID_HANDLE_VALUE; + + // Convert `dwdesiredaccess` and `dwflagsandattributes` to the + // `desiredaccess` argument to `NtCreateFile`. + let mut desiredaccess = dwdesiredaccess | SYNCHRONIZE | FILE_READ_ATTRIBUTES; + if dwflagsandattributes & FILE_FLAG_DELETE_ON_CLOSE != 0 { + desiredaccess |= DELETE; + } + + // Compute `objectattributes`' `Attributes` field. Case-insensitive is + // the expected behavior on Windows. + let mut attributes = 0; + if dwflagsandattributes & FILE_FLAG_POSIX_SEMANTICS != 0 { + attributes |= OBJ_CASE_INSENSITIVE as u32; + }; + if !lpsecurityattributes.is_null() && (*lpsecurityattributes).bInheritHandle != 0 { + attributes |= OBJ_INHERIT as u32; + } + + // Compute the `objectattributes` argument to `NtCreateFile`. + let mut objectattributes = mem::zeroed::(); + objectattributes.Length = mem::size_of::() as _; + objectattributes.RootDirectory = dir; + objectattributes.ObjectName = &mut unicode_string; + objectattributes.Attributes = attributes; + if !lpsecurityattributes.is_null() { + objectattributes.SecurityDescriptor = (*lpsecurityattributes) + .lpSecurityDescriptor + .cast::(); + } + + // If needed, set `objectattributes`' `SecurityQualityOfService` field. + let mut qos; + if dwflagsandattributes & SECURITY_SQOS_PRESENT != 0 { + qos = mem::zeroed::(); + qos.Length = mem::size_of::() as _; + qos.ImpersonationLevel = ((dwflagsandattributes >> 16) & 0x3) as _; + qos.ContextTrackingMode = if dwflagsandattributes & SECURITY_CONTEXT_TRACKING != 0 { + SECURITY_DYNAMIC_TRACKING + } else { + SECURITY_STATIC_TRACKING + } as u8; + qos.EffectiveOnly = ((dwflagsandattributes & SECURITY_EFFECTIVE_ONLY) != 0) as _; + + objectattributes.SecurityQualityOfService = + (&mut qos as *mut SECURITY_QUALITY_OF_SERVICE).cast(); + } + + let mut iostatusblock = mem::zeroed::(); + iostatusblock.Anonymous.Status = STATUS_PENDING; + + // Compute the `fileattributes` argument to `NtCreateFile`. Mask off + // unrecognized flags. + let mut fileattributes = dwflagsandattributes & FILE_ATTRIBUTE_VALID_FLAGS; + if fileattributes == 0 { + fileattributes = FILE_ATTRIBUTE_NORMAL; + } + + // Compute the `createoptions` argument to `NtCreateFile`. + let mut createoptions = 0; + if dwflagsandattributes & FILE_FLAG_BACKUP_SEMANTICS == 0 { + createoptions |= FILE_NON_DIRECTORY_FILE; + } else { + if dwdesiredaccess & GENERIC_ALL != 0 { + createoptions |= FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REMOTE_INSTANCE; + } else { + if dwdesiredaccess & GENERIC_READ != 0 { + createoptions |= FILE_OPEN_FOR_BACKUP_INTENT; + } + if dwdesiredaccess & GENERIC_WRITE != 0 { + createoptions |= FILE_OPEN_REMOTE_INSTANCE; + } + } + } + if dwflagsandattributes & FILE_FLAG_DELETE_ON_CLOSE != 0 { + createoptions |= FILE_DELETE_ON_CLOSE; + } + if dwflagsandattributes & FILE_FLAG_NO_BUFFERING != 0 { + createoptions |= FILE_NO_INTERMEDIATE_BUFFERING; + } + if dwflagsandattributes & FILE_FLAG_OPEN_NO_RECALL != 0 { + createoptions |= FILE_OPEN_NO_RECALL; + } + if dwflagsandattributes & FILE_FLAG_OPEN_REPARSE_POINT != 0 { + createoptions |= FILE_OPEN_REPARSE_POINT; + } + if dwflagsandattributes & FILE_FLAG_OVERLAPPED == 0 { + createoptions |= FILE_SYNCHRONOUS_IO_NONALERT; + } + // FILE_FLAG_POSIX_SEMANTICS is handled above. + if dwflagsandattributes & FILE_FLAG_RANDOM_ACCESS != 0 { + createoptions |= FILE_RANDOM_ACCESS; + } + if dwflagsandattributes & FILE_FLAG_SESSION_AWARE != 0 { + // TODO: How should we handle FILE_FLAG_SESSION_AWARE? + SetLastError(ERROR_NOT_SUPPORTED); + return HandleOrInvalid::from_raw_handle(INVALID_HANDLE_VALUE as _); + } + if dwflagsandattributes & FILE_FLAG_SEQUENTIAL_SCAN != 0 { + createoptions |= FILE_SEQUENTIAL_ONLY; + } + if dwflagsandattributes & FILE_FLAG_WRITE_THROUGH != 0 { + createoptions |= FILE_WRITE_THROUGH; + } + + // Ok, we have what we need to call `NtCreateFile` now! + let status = NtCreateFile( + &mut handle, + desiredaccess, + &mut objectattributes, + &mut iostatusblock, + null_mut(), + fileattributes, + dwsharemode, + createdisposition, + createoptions, + null_mut(), + 0, + ); + + // Check for errors. + if status != STATUS_SUCCESS { + handle = INVALID_HANDLE_VALUE; + if status == STATUS_OBJECT_NAME_COLLISION { + SetLastError(ERROR_FILE_EXISTS); + } else { + SetLastError(RtlNtStatusToDosError(status)); + } + } else if (dwcreationdisposition == CREATE_ALWAYS + && iostatusblock.Information == FILE_OVERWRITTEN as _) + || (dwcreationdisposition == OPEN_ALWAYS && iostatusblock.Information == FILE_OPENED as _) + { + // Set `ERROR_ALREADY_EXISTS` according to the table for + // `dwCreationDisposition` in the [`CreateFileW` docs]. + // + // [`CreateFileW` docs]: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew + SetLastError(ERROR_ALREADY_EXISTS); + } else { + // Otherwise indicate that we succeeded. + SetLastError(SUCCESS); + } + + HandleOrInvalid::from_raw_handle(handle as _) +} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/dir_entry_inner.rs b/crates/wasi/src/filesystem/primitives/windows/fs/dir_entry_inner.rs new file mode 100644 index 000000000000..1b93c848a53e --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/windows/fs/dir_entry_inner.rs @@ -0,0 +1,122 @@ +use super::open_options_to_std; +use crate::ambient_authority; +use crate::fs::OpenOptionsExt; +use crate::fs::{ + open, open_ambient_dir, FileType, FollowSymlinks, ImplFileTypeExt, Metadata, OpenOptions, + ReadDir, ReadDirInner, +}; +use std::ffi::OsString; +use std::{fmt, fs, io}; +use windows_sys::Win32::Storage::FileSystem::{ + FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, +}; + +pub(crate) struct DirEntryInner { + std: fs::DirEntry, +} + +impl DirEntryInner { + #[inline] + pub(crate) fn open(&self, options: &OpenOptions) -> io::Result { + match options.follow { + FollowSymlinks::No => { + let (opts, manually_trunc) = open_options_to_std(options); + let file = opts.open(self.std.path())?; + if manually_trunc { + // Unwrap is ok because 0 never overflows, and we'll only + // have `manually_trunc` set when the file is opened for + // writing. + file.set_len(0).unwrap(); + } + Ok(file) + } + FollowSymlinks::Yes => { + let path = self.std.path(); + open( + &open_ambient_dir(path.parent().unwrap(), ambient_authority())?, + path.file_name().unwrap().as_ref(), + options, + ) + } + } + } + + #[inline] + pub(crate) fn metadata(&self) -> io::Result { + self.std.metadata().map(Metadata::from_just_metadata) + } + + #[inline] + pub(crate) fn full_metadata(&self) -> io::Result { + // If we can open the file, we can get a more complete Metadata which + // includes `file_index`, `volume_serial_number`, and + // `number_of_links`. + let mut opts = OpenOptions::new(); + opts.access_mode(0); + opts.custom_flags(FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS); + opts.follow(FollowSymlinks::No); + let opened = self.open(&opts)?; + Metadata::from_file(&opened) + } + + #[inline] + pub(crate) fn remove_file(&self) -> io::Result<()> { + fs::remove_file(self.std.path()) + } + + #[inline] + pub(crate) fn remove_dir(&self) -> io::Result<()> { + fs::remove_dir(self.std.path()) + } + + #[inline] + pub(crate) fn read_dir(&self, follow: FollowSymlinks) -> io::Result { + assert_eq!( + follow, + FollowSymlinks::Yes, + "`read_dir` without following symlinks is not implemented yet" + ); + let std = fs::read_dir(self.std.path())?; + let inner = ReadDirInner::from_std(std); + Ok(ReadDir { inner }) + } + + #[inline] + pub(crate) fn file_type(&self) -> io::Result { + self.std.file_type().map(ImplFileTypeExt::from_std) + } + + #[inline] + pub(crate) fn file_name(&self) -> OsString { + self.std.file_name() + } + + #[inline] + #[cfg(windows_by_handle)] + pub(crate) fn is_same_file(&self, metadata: &Metadata) -> io::Result { + // Don't use `self.metadata()`, because that doesn't include the + // volume serial number which we need. + // + Ok(Metadata::from_just_metadata(fs::metadata(self.std.path())?).is_same_file(metadata)) + } + + #[inline] + pub(super) fn from_std(std: fs::DirEntry) -> Self { + Self { std } + } +} + +impl fmt::Debug for DirEntryInner { + // Like libstd's version, but doesn't print the path. + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("DirEntry").field(&self.file_name()).finish() + } +} + +#[doc(hidden)] +impl crate::fs::_WindowsDirEntryExt for crate::fs::DirEntry { + #[inline] + fn full_metadata(&self) -> io::Result { + DirEntryInner::full_metadata(&self.inner) + } +} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/dir_options_ext.rs b/crates/wasi/src/filesystem/primitives/windows/fs/dir_options_ext.rs new file mode 100644 index 000000000000..728f59629b66 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/windows/fs/dir_options_ext.rs @@ -0,0 +1,8 @@ +#[derive(Debug, Clone)] +pub(crate) struct DirOptionsExt {} + +impl DirOptionsExt { + pub(crate) const fn new() -> Self { + Self {} + } +} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/dir_utils.rs b/crates/wasi/src/filesystem/primitives/windows/fs/dir_utils.rs new file mode 100644 index 000000000000..5a0241bfba50 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/windows/fs/dir_utils.rs @@ -0,0 +1,204 @@ +use crate::fs::OpenOptionsExt; +use crate::fs::{errors, OpenOptions}; +use crate::AmbientAuthority; +use std::ffi::OsString; +use std::ops::Deref; +use std::os::windows::ffi::{OsStrExt, OsStringExt}; +use std::path::{Path, PathBuf}; +use std::{fs, io}; +use windows_sys::Win32::Storage::FileSystem::{ + FILE_FLAG_BACKUP_SEMANTICS, FILE_SHARE_READ, FILE_SHARE_WRITE, +}; + +/// Rust's `Path` implicitly strips redundant slashes, however they aren't +/// redundant in one case: at the end of a path they indicate that a path is +/// expected to name a directory. +pub(crate) fn path_requires_dir(path: &Path) -> bool { + let wide: Vec = path.as_os_str().encode_wide().collect(); + wide.ends_with(&['/' as u16]) + || wide.ends_with(&['/' as u16, '.' as _]) + || wide.ends_with(&['\\' as u16]) + || wide.ends_with(&['\\' as u16, '.' as _]) +} + +/// Windows treats `foo/.` as equivalent to `foo` even if `foo` does not +/// exist or is not a directory. So we don't do the special trailing-dot +/// handling that we do on Posix-ish platforms. +pub(crate) fn path_has_trailing_dot(_path: &Path) -> bool { + false +} + +/// For the purposes of emulating Windows symlink resolution, we sometimes +/// need to know whether a path really does end in a trailing dot though. +pub(crate) fn path_really_has_trailing_dot(path: &Path) -> bool { + let wide: Vec = path.as_os_str().encode_wide().collect(); + + wide.ends_with(&['/' as u16, '.' as u16]) || wide.ends_with(&['\\' as u16, '.' as u16]) +} + +/// Rust's `Path` implicitly strips trailing `/`s, however they aren't +/// redundant in one case: at the end of a path they are the final path +/// component, which has different path lookup behavior. +pub(crate) fn path_has_trailing_slash(path: &Path) -> bool { + let wide: Vec = path.as_os_str().encode_wide().collect(); + + wide.ends_with(&['/' as u16]) || wide.ends_with(&['\\' as u16]) +} + +/// Strip trailing `/`s, unless this reduces `path` to `/` itself. This is +/// used by `create_dir` and others to prevent paths like `foo/` from +/// canonicalizing to `foo/.` since these syscalls treat these differently. +pub(crate) fn strip_dir_suffix(path: &Path) -> impl Deref + '_ { + let mut wide: Vec = path.as_os_str().encode_wide().collect(); + while wide.len() > 1 + && (*wide.last().unwrap() == '/' as u16 || *wide.last().unwrap() == '\\' as u16) + { + wide.pop(); + } + PathBuf::from(OsString::from_wide(&wide)) +} + +/// Return an `OpenOptions` for opening directories. +pub(crate) fn dir_options() -> OpenOptions { + // Set `FILE_FLAG_BACKUP_SEMANTICS` so that we can open directories. Unset + // `FILE_SHARE_DELETE` so that directories can't be renamed or deleted + // underneath us, since we use paths to implement many directory operations. + OpenOptions::new() + .read(true) + .dir_required(true) + .custom_flags(FILE_FLAG_BACKUP_SEMANTICS) + .share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE) + .clone() +} + +/// Like `dir_options`, but additionally request the ability to read the +/// directory entries. +pub(crate) fn readdir_options() -> OpenOptions { + dir_options().readdir_required(true).clone() +} + +/// Return an `OpenOptions` for canonicalizing paths. +pub(crate) fn canonicalize_options() -> OpenOptions { + OpenOptions::new() + .read(true) + .custom_flags(FILE_FLAG_BACKUP_SEMANTICS) + .clone() +} + +/// Open a directory named by a bare path, using the host process' ambient +/// authority. +/// +/// # Ambient Authority +/// +/// This function is not sandboxed and may trivially access any path that the +/// host process has access to. +pub(crate) fn open_ambient_dir_impl( + path: &Path, + ambient_authority: AmbientAuthority, +) -> io::Result { + use std::os::windows::fs::OpenOptionsExt; + + let _ = ambient_authority; + + // Set `FILE_FLAG_BACKUP_SEMANTICS` so that we can open directories. Unset + // `FILE_SHARE_DELETE` so that directories can't be renamed or deleted + // underneath us, since we use paths to implement many directory operations. + let dir = fs::OpenOptions::new() + .read(true) + .custom_flags(FILE_FLAG_BACKUP_SEMANTICS) + .share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE) + .open(path)?; + + // Require a directory. It may seem possible to eliminate this `metadata()` + // call by appending a slash to the path before opening it so that the OS + // requires a directory for us, however on Windows in some circumstances + // this leads to "The filename, directory name, or volume label syntax is + // incorrect." errors. + if !dir.metadata()?.is_dir() { + return Err(errors::is_not_directory()); + } + + Ok(dir) +} + +#[test] +fn strip_dir_suffix_tests() { + assert_eq!(&*strip_dir_suffix(Path::new("/foo//")), Path::new("/foo")); + assert_eq!(&*strip_dir_suffix(Path::new("/foo/")), Path::new("/foo")); + assert_eq!(&*strip_dir_suffix(Path::new("foo/")), Path::new("foo")); + assert_eq!(&*strip_dir_suffix(Path::new("foo")), Path::new("foo")); + assert_eq!(&*strip_dir_suffix(Path::new("/")), Path::new("/")); + assert_eq!(&*strip_dir_suffix(Path::new("//")), Path::new("/")); + + assert_eq!( + &*strip_dir_suffix(Path::new("\\foo\\\\")), + Path::new("\\foo") + ); + assert_eq!(&*strip_dir_suffix(Path::new("\\foo\\")), Path::new("\\foo")); + assert_eq!(&*strip_dir_suffix(Path::new("foo\\")), Path::new("foo")); + assert_eq!(&*strip_dir_suffix(Path::new("foo")), Path::new("foo")); + assert_eq!(&*strip_dir_suffix(Path::new("\\")), Path::new("\\")); + assert_eq!(&*strip_dir_suffix(Path::new("\\\\")), Path::new("\\")); +} + +#[test] +fn test_path_requires_dir() { + assert!(!path_requires_dir(Path::new("."))); + assert!(path_requires_dir(Path::new("/"))); + assert!(path_requires_dir(Path::new("//"))); + assert!(path_requires_dir(Path::new("/./."))); + assert!(path_requires_dir(Path::new("foo/"))); + assert!(path_requires_dir(Path::new("foo//"))); + assert!(path_requires_dir(Path::new("foo//."))); + assert!(path_requires_dir(Path::new("foo/./."))); + assert!(path_requires_dir(Path::new("foo/./"))); + assert!(path_requires_dir(Path::new("foo/.//"))); + + assert!(path_requires_dir(Path::new("\\"))); + assert!(path_requires_dir(Path::new("\\\\"))); + assert!(path_requires_dir(Path::new("\\.\\."))); + assert!(path_requires_dir(Path::new("foo\\"))); + assert!(path_requires_dir(Path::new("foo\\\\"))); + assert!(path_requires_dir(Path::new("foo\\\\."))); + assert!(path_requires_dir(Path::new("foo\\.\\."))); + assert!(path_requires_dir(Path::new("foo\\.\\"))); + assert!(path_requires_dir(Path::new("foo\\.\\\\"))); +} + +#[test] +fn test_path_has_trailing_slash() { + assert!(path_has_trailing_slash(Path::new("/"))); + assert!(path_has_trailing_slash(Path::new("//"))); + assert!(path_has_trailing_slash(Path::new("foo/"))); + assert!(path_has_trailing_slash(Path::new("foo//"))); + assert!(path_has_trailing_slash(Path::new("foo/./"))); + assert!(path_has_trailing_slash(Path::new("foo/.//"))); + + assert!(path_has_trailing_slash(Path::new("\\"))); + assert!(path_has_trailing_slash(Path::new("\\\\"))); + assert!(path_has_trailing_slash(Path::new("foo\\"))); + assert!(path_has_trailing_slash(Path::new("foo\\\\"))); + assert!(path_has_trailing_slash(Path::new("foo\\.\\"))); + assert!(path_has_trailing_slash(Path::new("foo\\.\\\\"))); + + assert!(!path_has_trailing_slash(Path::new("foo"))); + assert!(!path_has_trailing_slash(Path::new("foo."))); + + assert!(!path_has_trailing_slash(Path::new("/./foo"))); + assert!(!path_has_trailing_slash(Path::new(".."))); + assert!(!path_has_trailing_slash(Path::new("/.."))); + + assert!(!path_has_trailing_slash(Path::new("\\.\\foo"))); + assert!(!path_has_trailing_slash(Path::new(".."))); + assert!(!path_has_trailing_slash(Path::new("\\.."))); + + assert!(!path_has_trailing_slash(Path::new("/./."))); + assert!(!path_has_trailing_slash(Path::new("foo//."))); + assert!(!path_has_trailing_slash(Path::new("foo/./."))); + + assert!(!path_has_trailing_slash(Path::new("."))); + + assert!(!path_has_trailing_slash(Path::new("\\.\\."))); + assert!(!path_has_trailing_slash(Path::new("foo\\\\."))); + assert!(!path_has_trailing_slash(Path::new("foo\\.\\."))); +} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/errors.rs b/crates/wasi/src/filesystem/primitives/windows/fs/errors.rs new file mode 100644 index 000000000000..f4bffc9868ab --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/windows/fs/errors.rs @@ -0,0 +1,22 @@ +use std::io; +use windows_sys::Win32::Foundation; + +#[cold] +pub(crate) fn no_such_file_or_directory() -> io::Error { + io::Error::from_raw_os_error(Foundation::ERROR_FILE_NOT_FOUND as i32) +} + +#[cold] +pub(crate) fn is_directory() -> io::Error { + io::Error::from_raw_os_error(Foundation::ERROR_DIRECTORY_NOT_SUPPORTED as i32) +} + +#[cold] +pub(crate) fn is_not_directory() -> io::Error { + io::Error::from_raw_os_error(Foundation::ERROR_DIRECTORY as i32) +} + +#[cold] +pub(crate) fn too_many_symlinks() -> io::Error { + io::Error::from_raw_os_error(Foundation::ERROR_TOO_MANY_LINKS as i32) +} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/file_type_ext.rs b/crates/wasi/src/filesystem/primitives/windows/fs/file_type_ext.rs new file mode 100644 index 000000000000..f7d2a8fe1cba --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/windows/fs/file_type_ext.rs @@ -0,0 +1,121 @@ +use crate::fs::FileType; +use std::{fs, io}; + +/// A type that implements `FileTypeExt` for this platform. +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] +pub(crate) enum ImplFileTypeExt { + CharacterDevice, + Fifo, + #[cfg(windows_file_type_ext)] + SymlinkFile, + #[cfg(windows_file_type_ext)] + SymlinkDir, + SymlinkUnknown, +} + +impl ImplFileTypeExt { + /// Constructs a new instance of `Self` from the given [`std::fs::File`] + /// and [`std::fs::Metadata`]. + pub(crate) fn from(file: &fs::File, metadata: &fs::Metadata) -> io::Result { + // Check for the things we can do with just metadata. + let file_type = Self::from_just_metadata(metadata); + if file_type != FileType::unknown() { + return Ok(file_type); + } + + // Use the open file to check for one of the exotic file types. + let file_type = winx::winapi_util::file::typ(file)?; + if file_type.is_char() { + return Ok(FileType::ext(ImplFileTypeExt::CharacterDevice)); + } + if file_type.is_pipe() { + return Ok(FileType::ext(ImplFileTypeExt::Fifo)); + } + + Ok(FileType::unknown()) + } + + /// Constructs a new instance of `Self` from the given + /// [`std::fs::Metadata`]. + #[inline] + pub(crate) fn from_just_metadata(metadata: &fs::Metadata) -> FileType { + let std = metadata.file_type(); + Self::from_std(std) + } + + /// Constructs a new instance of `Self` from the given + /// [`std::fs::FileType`]. + #[inline] + pub(crate) fn from_std(std: fs::FileType) -> FileType { + if std.is_file() { + return FileType::file(); + } + if std.is_dir() { + return FileType::dir(); + } + + #[cfg(windows_file_type_ext)] + { + use std::os::windows::fs::FileTypeExt; + if std.is_symlink_file() { + return FileType::ext(Self::SymlinkFile); + } + if std.is_symlink_dir() { + return FileType::ext(Self::SymlinkDir); + } + } + + if std.is_symlink() { + return FileType::ext(Self::SymlinkUnknown); + } + + FileType::unknown() + } + + /// Creates a `FileType` for which `is_symlink_file()` returns `true`. + #[cfg(windows_file_type_ext)] + #[inline] + pub(crate) const fn symlink_file() -> Self { + Self::SymlinkFile + } + + /// Creates a `FileType` for which `is_symlink_dir()` returns `true`. + #[cfg(windows_file_type_ext)] + #[inline] + pub(crate) const fn symlink_dir() -> Self { + Self::SymlinkDir + } + + #[inline] + pub(crate) fn is_symlink(&self) -> bool { + match self { + #[cfg(windows_file_type_ext)] + Self::SymlinkFile | Self::SymlinkDir => true, + Self::SymlinkUnknown => true, + _ => false, + } + } +} + +#[doc(hidden)] +impl crate::fs::_WindowsFileTypeExt for crate::fs::FileType { + #[inline] + fn is_block_device(&self) -> bool { + false + } + + #[inline] + fn is_char_device(&self) -> bool { + *self == FileType::ext(ImplFileTypeExt::CharacterDevice) + } + + #[inline] + fn is_fifo(&self) -> bool { + *self == FileType::ext(ImplFileTypeExt::Fifo) + } + + #[inline] + fn is_socket(&self) -> bool { + false + } +} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/get_path.rs b/crates/wasi/src/filesystem/primitives/windows/fs/get_path.rs new file mode 100644 index 000000000000..c2b44ad8837d --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/windows/fs/get_path.rs @@ -0,0 +1,31 @@ +use std::ffi::OsString; +use std::os::windows::ffi::{OsStrExt, OsStringExt}; +use std::path::{Path, PathBuf}; +use std::{fs, io}; + +/// Calculates system path of `file`. +/// +/// This function will automatically strip the extended prefix from the +/// resultant path to allow for joining this resultant path with relative +/// components. +pub(crate) fn get_path(file: &fs::File) -> io::Result { + // get system path to the handle + let path = winx::file::get_file_path(file)?; + + // strip extended prefix; otherwise we will error out on any relative + // components with `out_path` + let wide: Vec<_> = path.as_os_str().encode_wide().collect(); + let wide_final = if wide.starts_with(&['\\' as u16, '\\' as _, '?' as _, '\\' as _]) { + &wide[4..] + } else { + &wide + }; + Ok(PathBuf::from(OsString::from_wide(wide_final))) +} + +/// Convenience function for calling `get_path` and concatenating the result +/// with `path`. +pub(super) fn concatenate(file: &fs::File, path: &Path) -> io::Result { + let file_path = get_path(file)?; + Ok(file_path.join(path)) +} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/hard_link_unchecked.rs b/crates/wasi/src/filesystem/primitives/windows/fs/hard_link_unchecked.rs new file mode 100644 index 000000000000..68408d06c0d1 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/windows/fs/hard_link_unchecked.rs @@ -0,0 +1,16 @@ +use super::get_path::concatenate; +use std::path::Path; +use std::{fs, io}; + +/// *Unsandboxed* function similar to `hard_link`, but which does not perform +/// sandboxing. +pub(crate) fn hard_link_unchecked( + old_start: &fs::File, + old_path: &Path, + new_start: &fs::File, + new_path: &Path, +) -> io::Result<()> { + let old_full_path = concatenate(old_start, old_path)?; + let new_full_path = concatenate(new_start, new_path)?; + fs::hard_link(old_full_path, new_full_path) +} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/is_file_read_write_impl.rs b/crates/wasi/src/filesystem/primitives/windows/fs/is_file_read_write_impl.rs new file mode 100644 index 000000000000..87674e589abd --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/windows/fs/is_file_read_write_impl.rs @@ -0,0 +1,11 @@ +use io_lifetimes::AsHandle; +use std::{fs, io}; + +pub(crate) fn is_file_read_write_impl(file: &fs::File) -> io::Result<(bool, bool)> { + let handle = file.as_handle(); + let access_mode = winx::file::query_access_information(handle)?; + let read = access_mode.contains(winx::file::AccessMode::FILE_READ_DATA); + let write = access_mode.contains(winx::file::AccessMode::FILE_WRITE_DATA) + || access_mode.contains(winx::file::AccessMode::FILE_APPEND_DATA); + Ok((read, write)) +} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/is_same_file.rs b/crates/wasi/src/filesystem/primitives/windows/fs/is_same_file.rs new file mode 100644 index 000000000000..9bf7aba4d575 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/windows/fs/is_same_file.rs @@ -0,0 +1,57 @@ +use crate::fs::ImplMetadataExt; +#[cfg(windows_by_handle)] +use crate::fs::Metadata; +use std::{fs, io}; + +/// Determine if `a` and `b` refer to the same inode on the same device. +pub(crate) fn is_same_file(a: &fs::File, b: &fs::File) -> io::Result { + let a_metadata = ImplMetadataExt::from(a, &a.metadata()?)?; + let b_metadata = ImplMetadataExt::from(b, &b.metadata()?)?; + Ok(a_metadata.is_same_file(&b_metadata)) +} + +/// Determine if `a` and `b` are metadata for the same inode on the same +/// device. +#[cfg(windows_by_handle)] +#[allow(dead_code)] +pub(crate) fn is_same_file_metadata(a: &Metadata, b: &Metadata) -> io::Result { + use crate::fs::MetadataExt; + Ok(a.volume_serial_number() == b.volume_serial_number() && a.file_index() == b.file_index()) +} + +/// Determine if `a` and `b` definitely refer to different inodes. +/// +/// This is similar to `is_same_file`, but is conservative, and doesn't depend +/// on nightly-only features. +#[cfg(racy_asserts)] +pub(crate) fn is_different_file(a: &fs::File, b: &fs::File) -> io::Result { + #[cfg(windows_by_handle)] + { + is_same_file(a, b).map(|same| !same) + } + + #[cfg(not(windows_by_handle))] + { + let a_metadata = Metadata::from_std(a.metadata()?); + let b_metadata = Metadata::from_std(b.metadata()?); + is_different_file_metadata(&a_metadata, &b_metadata) + } +} + +/// Determine if `a` and `b` are metadata for definitely different inodes. +/// +/// This is similar to `is_same_file_metadata`, but is conservative, and +/// doesn't depend on nightly-only features. +#[cfg(racy_asserts)] +pub(crate) fn is_different_file_metadata(a: &Metadata, b: &Metadata) -> io::Result { + #[cfg(windows_by_handle)] + { + is_same_file_metadata(a, b).map(|same| !same) + } + + #[cfg(not(windows_by_handle))] + { + // Conservatively just compare creation times. + Ok(a.created()? != b.created()?) + } +} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/metadata_ext.rs b/crates/wasi/src/filesystem/primitives/windows/fs/metadata_ext.rs new file mode 100644 index 000000000000..ed692855bedc --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/windows/fs/metadata_ext.rs @@ -0,0 +1,208 @@ +#![allow(clippy::useless_conversion)] + +use crate::fs::MetadataExt; +use std::{fs, io}; + +#[derive(Debug, Clone)] +pub(crate) struct ImplMetadataExt { + file_attributes: u32, + creation_time: u64, + last_access_time: u64, + last_write_time: u64, + file_size: u64, + volume_serial_number: Option, + number_of_links: Option, + file_index: Option, +} + +impl ImplMetadataExt { + /// Constructs a new instance of `Self` from the given [`std::fs::File`] + /// and [`std::fs::Metadata`]. + #[inline] + #[allow(unused_variables)] + pub(crate) fn from(file: &fs::File, std: &fs::Metadata) -> io::Result { + let (mut volume_serial_number, mut number_of_links, mut file_index) = (None, None, None); + + #[cfg(windows_by_handle)] + { + use std::os::windows::fs::MetadataExt; + if let Some(some) = std.volume_serial_number() { + volume_serial_number = Some(some); + } + if let Some(some) = std.number_of_links() { + number_of_links = Some(some); + } + if let Some(some) = std.file_index() { + file_index = Some(some); + } + } + + #[cfg(not(windows_by_handle))] + if volume_serial_number.is_none() || number_of_links.is_none() || file_index.is_none() { + let fileinfo = winx::winapi_util::file::information(file)?; + if volume_serial_number.is_none() { + let t64: u64 = fileinfo.volume_serial_number(); + let t32: u32 = t64.try_into().unwrap(); + volume_serial_number = Some(t32); + } + if number_of_links.is_none() { + let t64: u64 = fileinfo.number_of_links(); + let t32: u32 = t64.try_into().unwrap(); + number_of_links = Some(t32); + } + if file_index.is_none() { + file_index = Some(fileinfo.file_index()); + } + } + + Ok(Self::from_parts( + std, + volume_serial_number, + number_of_links, + file_index, + )) + } + + /// Constructs a new instance of `Self` from the given + /// [`std::fs::Metadata`]. + /// + /// As with the comments in [`std::fs::Metadata::volume_serial_number`] and + /// nearby functions, some fields of the resulting metadata will be `None`. + /// + /// [`std::fs::Metadata::volume_serial_number`]: https://doc.rust-lang.org/std/os/windows/fs/trait.MetadataExt.html#tymethod.volume_serial_number + #[inline] + #[allow(unused_mut)] + pub(crate) fn from_just_metadata(std: &fs::Metadata) -> Self { + let (mut volume_serial_number, mut number_of_links, mut file_index) = (None, None, None); + + #[cfg(windows_by_handle)] + { + use std::os::windows::fs::MetadataExt; + if let Some(some) = std.volume_serial_number() { + volume_serial_number = Some(some); + } + if let Some(some) = std.number_of_links() { + number_of_links = Some(some); + } + if let Some(some) = std.file_index() { + file_index = Some(some); + } + } + + Self::from_parts(std, volume_serial_number, number_of_links, file_index) + } + + #[inline] + fn from_parts( + std: &fs::Metadata, + volume_serial_number: Option, + number_of_links: Option, + file_index: Option, + ) -> Self { + use std::os::windows::fs::MetadataExt; + Self { + file_attributes: std.file_attributes(), + creation_time: std.creation_time(), + last_access_time: std.last_access_time(), + last_write_time: std.last_write_time(), + file_size: std.file_size(), + volume_serial_number, + number_of_links, + file_index, + } + } + + /// Determine if `self` and `other` refer to the same inode on the same + /// device. + pub(crate) fn is_same_file(&self, other: &Self) -> bool { + // From [MSDN]: + // The identifier (low and high parts) and the volume serial number + // uniquely identify a file on a single computer. To determine whether + // two open handles represent the same file, combine the identifier + // and the volume serial number for each file and compare them. + // [MSDN]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/ns-fileapi-by_handle_file_information + let self_vsn = self + .volume_serial_number + .expect("could extract volume serial number of `self`"); + let other_vsn = other + .volume_serial_number + .expect("could extract volume serial number of `other`"); + let self_file_index = self.file_index.expect("could extract file index `self`"); + let other_file_index = other.file_index.expect("could extract file index `other`"); + self_vsn == other_vsn && self_file_index == other_file_index + } + + /// `MetadataExt` requires nightly to be implemented, but we sometimes + /// just need the file attributes. + #[inline] + pub(crate) fn file_attributes(&self) -> u32 { + self.file_attributes + } +} + +impl MetadataExt for ImplMetadataExt { + fn file_attributes(&self) -> u32 { + self.file_attributes + } + + #[inline] + fn creation_time(&self) -> u64 { + self.creation_time + } + + #[inline] + fn last_access_time(&self) -> u64 { + self.last_access_time + } + + #[inline] + fn last_write_time(&self) -> u64 { + self.last_write_time + } + + #[inline] + fn file_size(&self) -> u64 { + self.file_size + } + + #[inline] + #[cfg(windows_by_handle)] + fn volume_serial_number(&self) -> Option { + self.volume_serial_number + } + + #[inline] + #[cfg(windows_by_handle)] + fn number_of_links(&self) -> Option { + self.number_of_links + } + + #[inline] + #[cfg(windows_by_handle)] + fn file_index(&self) -> Option { + self.file_index + } +} + +#[doc(hidden)] +impl crate::fs::_WindowsByHandle for crate::fs::Metadata { + #[inline] + fn file_attributes(&self) -> u32 { + self.ext.file_attributes + } + + #[inline] + fn volume_serial_number(&self) -> Option { + self.ext.volume_serial_number + } + + #[inline] + fn number_of_links(&self) -> Option { + self.ext.number_of_links + } + + #[inline] + fn file_index(&self) -> Option { + self.ext.file_index + } +} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/mod.rs b/crates/wasi/src/filesystem/primitives/windows/fs/mod.rs new file mode 100644 index 000000000000..0de735adbda1 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/windows/fs/mod.rs @@ -0,0 +1,88 @@ +mod access_unchecked; +mod copy; +mod create_dir_unchecked; +mod create_file_at_w; +mod dir_entry_inner; +mod dir_options_ext; +mod dir_utils; +mod file_type_ext; +mod get_path; +mod hard_link_unchecked; +mod is_file_read_write_impl; +mod is_same_file; +mod metadata_ext; +mod oflags; +mod open_impl; +mod open_options_ext; +mod open_unchecked; +mod read_dir_inner; +mod read_link_impl; +mod read_link_unchecked; +mod remove_dir_all_impl; +mod remove_dir_unchecked; +mod remove_file_unchecked; +mod remove_open_dir_impl; +mod rename_unchecked; +mod reopen_impl; +mod set_permissions_unchecked; +mod set_symlink_permissions_unchecked; +mod set_times_impl; +mod stat_unchecked; +mod symlink_unchecked; + +pub(crate) mod errors; + +#[rustfmt::skip] +pub(crate) use crate::fs::{ + manually::canonicalize as canonicalize_impl, + via_parent::access as access_impl, + via_parent::hard_link as hard_link_impl, + via_parent::create_dir as create_dir_impl, + via_parent::rename as rename_impl, + via_parent::remove_dir as remove_dir_impl, + via_parent::set_permissions as set_permissions_impl, + via_parent::set_symlink_permissions as set_symlink_permissions_impl, + manually::stat as stat_impl, + via_parent::symlink_dir as symlink_dir_impl, + via_parent::symlink_file as symlink_file_impl, + via_parent::remove_file as remove_file_impl, +}; + +pub(crate) use access_unchecked::*; +pub(crate) use copy::*; +pub(crate) use create_dir_unchecked::*; +pub(crate) use dir_entry_inner::*; +pub(crate) use dir_options_ext::*; +pub(crate) use dir_utils::*; +pub(crate) use file_type_ext::*; +pub(crate) use hard_link_unchecked::*; +pub(crate) use is_file_read_write_impl::*; +pub(crate) use is_same_file::*; +pub(crate) use metadata_ext::*; +pub(crate) use open_impl::open_impl; +pub(crate) use open_options_ext::*; +pub(crate) use open_unchecked::*; +pub(crate) use read_dir_inner::*; +pub(crate) use read_link_impl::*; +pub(crate) use read_link_unchecked::*; +pub(crate) use remove_dir_all_impl::*; +pub(crate) use remove_dir_unchecked::*; +pub(crate) use remove_file_unchecked::*; +pub(crate) use remove_open_dir_impl::*; +pub(crate) use rename_unchecked::*; +pub(crate) use reopen_impl::reopen_impl; +pub(crate) use set_permissions_unchecked::*; +pub(crate) use set_symlink_permissions_unchecked::*; +pub(crate) use set_times_impl::*; +pub(crate) use stat_unchecked::*; +pub(crate) use symlink_unchecked::*; + +// On Windows, there is a limit of 63 reparse points on any given path. +// +pub(crate) const MAX_SYMLINK_EXPANSIONS: u8 = 63; + +pub(crate) fn file_path(file: &std::fs::File) -> Option { + get_path::get_path(file).ok() +} + +pub(super) use oflags::*; diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/oflags.rs b/crates/wasi/src/filesystem/primitives/windows/fs/oflags.rs new file mode 100644 index 000000000000..b78133c0cb17 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/windows/fs/oflags.rs @@ -0,0 +1,84 @@ +use crate::fs::{FollowSymlinks, OpenOptions, OpenOptionsExt}; +use std::fs; +use windows_sys::Win32::Storage::FileSystem::{ + FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, FILE_FLAG_WRITE_THROUGH, + FILE_SHARE_DELETE, +}; + +/// Adjust an `OpenOptions` after all the flags are set, in preparation +/// for the to call a Windows API `open` function. Also return a bool +/// indicating that the `trunc` flag was requested but could not be set, +/// so the file should be truncated manually after opening. +pub(in super::super) fn prepare_open_options_for_open(opts: &mut OpenOptions) -> bool { + let mut trunc = opts.truncate; + let mut manually_trunc = false; + + let mut custom_flags = match opts.follow { + FollowSymlinks::Yes => opts.ext.custom_flags, + FollowSymlinks::No => { + if trunc && !opts.create_new && !opts.append && opts.write { + // On Windows, truncating overwrites a symlink with a + // non-symlink. + manually_trunc = true; + trunc = false; + } + opts.ext.custom_flags | FILE_FLAG_OPEN_REPARSE_POINT + } + }; + let mut share_mode = opts.ext.share_mode; + if opts.maybe_dir { + custom_flags |= FILE_FLAG_BACKUP_SEMANTICS; + + // Only allow `FILE_SHARE_READ` and `FILE_SHARE_WRITE`; this mirrors + // the values in `dir_options()` and is done to prevent directories + // from being deleted or renamed underneath cap-std's sandboxed path + // lookups on Windows. + share_mode &= !FILE_SHARE_DELETE; + } + // This matches system-interface's `set_fd_flags` interpretation of these + // flags on Windows. + if opts.sync || opts.dsync { + custom_flags |= FILE_FLAG_WRITE_THROUGH; + } + + opts.truncate(trunc) + .share_mode(share_mode) + .custom_flags(custom_flags); + + manually_trunc +} + +/// Translate the given `cap_std` into `std` options. Also return a bool +/// indicating that the `trunc` flag was requested but could not be set, +/// so the file should be truncated manually after opening. +pub(in super::super) fn open_options_to_std(opts: &OpenOptions) -> (fs::OpenOptions, bool) { + use std::os::windows::fs::OpenOptionsExt; + + let mut opts = opts.clone(); + let manually_trunc = prepare_open_options_for_open(&mut opts); + + let mut std_opts = fs::OpenOptions::new(); + std_opts + .read(opts.read) + .write(opts.write) + .append(opts.append) + .truncate(opts.truncate) + .create(opts.create) + .create_new(opts.create_new) + .share_mode(opts.ext.share_mode) + .custom_flags(opts.ext.custom_flags) + .attributes(opts.ext.attributes); + + // Calling `sequence_qos_flags` with a value of 0 has the side effect + // of setting `SECURITY_SQOS_PRESENT`, so don't call it if we don't + // have any flags. + if opts.ext.security_qos_flags != 0 { + std_opts.security_qos_flags(opts.ext.security_qos_flags); + } + + if let Some(access_mode) = opts.ext.access_mode { + std_opts.access_mode(access_mode); + } + + (std_opts, manually_trunc) +} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/open_impl.rs b/crates/wasi/src/filesystem/primitives/windows/fs/open_impl.rs new file mode 100644 index 000000000000..a2695e8ae473 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/windows/fs/open_impl.rs @@ -0,0 +1,66 @@ +use crate::fs::{manually, OpenOptions}; +use std::ffi::OsStr; +use std::path::Path; +use std::{fs, io}; +use windows_sys::Win32::Foundation::ERROR_FILE_NOT_FOUND; + +pub(crate) fn open_impl( + start: &fs::File, + path: &Path, + options: &OpenOptions, +) -> io::Result { + // Windows reserves several special device paths. Disallow opening any + // of them. + // See: https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions + if let Some(stem) = file_prefix(path) { + if let Some(stemstr) = stem.to_str() { + match stemstr.trim_end().to_uppercase().as_str() { + "CON" | "PRN" | "AUX" | "NUL" | "COM0" | "COM1" | "COM2" | "COM3" | "COM4" + | "COM5" | "COM6" | "COM7" | "COM8" | "COM9" | "COM¹" | "COM²" | "COM³" + | "LPT0" | "LPT1" | "LPT2" | "LPT3" | "LPT4" | "LPT5" | "LPT6" | "LPT7" + | "LPT8" | "LPT9" | "LPT¹" | "LPT²" | "LPT³" => { + return Err(io::Error::from_raw_os_error(ERROR_FILE_NOT_FOUND as i32)); + } + _ => {} + } + } + } + + manually::open(start, path, options) +} + +// TODO: Replace this with `Path::file_prefix` once that's stable. For now, +// we use a copy of the code. This code is derived from +// https://github.com/rust-lang/rust/blob/9fe9041cc8eddaed402d17aa4facb2ce8f222e95/library/std/src/path.rs#L2648 +fn file_prefix(path: &Path) -> Option<&OsStr> { + path.file_name() + .map(split_file_at_dot) + .and_then(|(before, _after)| Some(before)) +} + +// This code is derived from +// https://github.com/rust-lang/rust/blob/9fe9041cc8eddaed402d17aa4facb2ce8f222e95/library/std/src/path.rs#L340 +#[allow(unsafe_code)] +fn split_file_at_dot(file: &OsStr) -> (&OsStr, Option<&OsStr>) { + let slice = file.as_encoded_bytes(); + if slice == b".." { + return (file, None); + } + + // The unsafety here stems from converting between &OsStr and &[u8] + // and back. This is safe to do because (1) we only look at ASCII + // contents of the encoding and (2) new &OsStr values are produced + // only from ASCII-bounded slices of existing &OsStr values. + let i = match slice[1..].iter().position(|b| *b == b'.') { + Some(i) => i + 1, + None => return (file, None), + }; + let before = &slice[..i]; + let after = &slice[i + 1..]; + unsafe { + ( + OsStr::from_encoded_bytes_unchecked(before), + Some(OsStr::from_encoded_bytes_unchecked(after)), + ) + } +} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/open_options_ext.rs b/crates/wasi/src/filesystem/primitives/windows/fs/open_options_ext.rs new file mode 100644 index 000000000000..8eb57917b644 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/windows/fs/open_options_ext.rs @@ -0,0 +1,121 @@ +#![allow(unsafe_code)] + +use crate::fs::OpenOptions; +use std::io; +use std::ptr::null_mut; +use windows_sys::Win32::Foundation::{ERROR_INVALID_PARAMETER, GENERIC_READ, GENERIC_WRITE}; +use windows_sys::Win32::Security::SECURITY_ATTRIBUTES; +use windows_sys::Win32::Storage::FileSystem::{ + CREATE_ALWAYS, CREATE_NEW, FILE_FLAG_OPEN_REPARSE_POINT, FILE_GENERIC_WRITE, FILE_SHARE_DELETE, + FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_WRITE_DATA, OPEN_ALWAYS, OPEN_EXISTING, + SECURITY_SQOS_PRESENT, TRUNCATE_EXISTING, +}; + +#[derive(Debug, Clone)] +pub(crate) struct ImplOpenOptionsExt { + pub(super) access_mode: Option, + pub(super) share_mode: u32, + pub(super) custom_flags: u32, + pub(super) attributes: u32, + pub(super) security_attributes: *mut SECURITY_ATTRIBUTES, + pub(super) security_qos_flags: u32, +} + +unsafe impl Send for ImplOpenOptionsExt {} +unsafe impl Sync for ImplOpenOptionsExt {} + +impl ImplOpenOptionsExt { + pub(crate) const fn new() -> Self { + Self { + access_mode: None, + share_mode: FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + custom_flags: 0, + attributes: 0, + security_attributes: null_mut(), + security_qos_flags: 0, + } + } + + pub(crate) fn access_mode(&mut self, mode: u32) -> &mut Self { + self.access_mode = Some(mode); + self + } + + pub(crate) fn share_mode(&mut self, share: u32) -> &mut Self { + self.share_mode = share; + self + } + + pub(crate) fn custom_flags(&mut self, flags: u32) -> &mut Self { + self.custom_flags = flags; + self + } + + pub(crate) fn attributes(&mut self, attributes: u32) -> &mut Self { + self.attributes = attributes; + self + } + + pub(crate) fn security_qos_flags(&mut self, flags: u32) -> &mut Self { + self.security_qos_flags = flags | SECURITY_SQOS_PRESENT; + self + } +} + +pub(crate) fn get_access_mode(options: &OpenOptions) -> io::Result { + match ( + options.read, + options.write, + options.append, + options.ext.access_mode, + ) { + (.., Some(mode)) => Ok(mode), + (true, false, false, None) => Ok(GENERIC_READ), + (false, true, false, None) => Ok(GENERIC_WRITE), + (true, true, false, None) => Ok(GENERIC_READ | GENERIC_WRITE), + (false, _, true, None) => Ok(FILE_GENERIC_WRITE & !FILE_WRITE_DATA), + (true, _, true, None) => Ok(GENERIC_READ | (FILE_GENERIC_WRITE & !FILE_WRITE_DATA)), + (false, false, false, None) => { + Err(io::Error::from_raw_os_error(ERROR_INVALID_PARAMETER as i32)) + } + } +} + +pub(crate) fn get_flags_and_attributes(options: &OpenOptions) -> u32 { + options.ext.custom_flags + | options.ext.attributes + | options.ext.security_qos_flags + | if options.create_new { + FILE_FLAG_OPEN_REPARSE_POINT + } else { + 0 + } +} + +pub(crate) fn get_creation_mode(options: &OpenOptions) -> io::Result { + const ERROR_INVALID_PARAMETER: i32 = 87; + + match (options.write, options.append) { + (true, false) => {} + (false, false) => { + if options.truncate || options.create || options.create_new { + return Err(io::Error::from_raw_os_error(ERROR_INVALID_PARAMETER)); + } + } + (_, true) => { + if options.truncate && !options.create_new { + return Err(io::Error::from_raw_os_error(ERROR_INVALID_PARAMETER)); + } + } + } + + Ok( + match (options.create, options.truncate, options.create_new) { + (false, false, false) => OPEN_EXISTING, + (true, false, false) => OPEN_ALWAYS, + (false, true, false) => TRUNCATE_EXISTING, + (true, true, false) => CREATE_ALWAYS, + (_, _, true) => CREATE_NEW, + }, + ) +} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/open_unchecked.rs b/crates/wasi/src/filesystem/primitives/windows/fs/open_unchecked.rs new file mode 100644 index 000000000000..36243b0b693f --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/windows/fs/open_unchecked.rs @@ -0,0 +1,230 @@ +//! Windows implementation of `openat` functionality. + +#![allow(unsafe_code)] + +use super::create_file_at_w::CreateFileAtW; +use super::{open_options_to_std, prepare_open_options_for_open}; +use crate::fs::{ + errors, file_path, get_access_mode, get_creation_mode, get_flags_and_attributes, + FollowSymlinks, OpenOptions, OpenUncheckedError, SymlinkKind, +}; +use crate::{ambient_authority, AmbientAuthority}; +use std::ffi::OsStr; +use std::os::windows::ffi::OsStrExt; +use std::os::windows::fs::MetadataExt; +use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle}; +use std::path::{Component, Path, PathBuf}; +use std::{fs, io}; +use windows_sys::Win32::Foundation::{self, ERROR_ACCESS_DENIED, HANDLE, INVALID_HANDLE_VALUE}; +use windows_sys::Win32::Storage::FileSystem::{ + CreateFileW, FILE_ATTRIBUTE_DIRECTORY, FILE_FLAG_OPEN_REPARSE_POINT, +}; + +/// *Unsandboxed* function similar to `open`, but which does not perform +/// sandboxing. +pub(crate) fn open_unchecked( + start: &fs::File, + path: &Path, + options: &OpenOptions, +) -> Result { + let _ = ambient_authority; + + // We have the final `OpenOptions`; now prepare it for an `open`. + let mut prepared_opts = options.clone(); + let manually_trunc = prepare_open_options_for_open(&mut prepared_opts); + + handle_open_result( + open_at(start, path, &prepared_opts), + options, + manually_trunc, + ) +} + +// The following is derived from Rust's library/std/src/sys/windows/fs.rs +// at revision 56888c1e9b4135b511abd2d8e907099003d12281, except with a +// directory `start` parameter added and using `CreateFileAtW` instead of +// `CreateFileW`. + +fn open_at(start: &fs::File, path: &Path, opts: &OpenOptions) -> io::Result { + let mut dir = start.as_raw_handle() as HANDLE; + + // `PathCchCanonicalizeEx` and friends don't seem to work with relative + // paths. Or at least, when I tried it, they canonicalized "a" to "", + // which isn't what we want. So we manually canonicalize `..` and `.`. + // Hopefully there aren't other mysterious Windows path conventions that + // we're missing here. + let mut rebuilt = PathBuf::new(); + for component in path.components() { + match component { + Component::Prefix(_) | Component::RootDir => { + rebuilt.push(component); + dir = 0 as HANDLE; + } + Component::Normal(_) => { + rebuilt.push(component); + } + Component::ParentDir => { + if !rebuilt.pop() { + // We popped past the beginning of `path`. Substitute in + // the path of `start` and convert this to an ambient + // path by dropping the directory base. It's ok to do + // this because we're not sandboxing at this level of the + // code. + if dir == 0 as HANDLE { + return Err(io::Error::from_raw_os_error(ERROR_ACCESS_DENIED as _)); + } + rebuilt = match file_path(start) { + Some(path) => path, + None => { + return Err(io::Error::from_raw_os_error(ERROR_ACCESS_DENIED as _)); + } + }; + dir = 0 as HANDLE; + // And then pop the last component of that. + let _ = rebuilt.pop(); + } + } + Component::CurDir => (), + } + } + + let mut wide = OsStr::encode_wide(rebuilt.as_os_str()).collect::>(); + + // If we ended up re-rooting, use Windows' `CreateFileW` instead of our + // own `CreateFileAtW` so that it does the requisite magic for absolute + // paths. + if dir == 0 as HANDLE { + // We're calling the windows-sys `CreateFileW` which expects a + // NUL-terminated filename, so add a NUL terminator. + wide.push(0); + + let handle = unsafe { + CreateFileW( + wide.as_ptr(), + get_access_mode(opts)?, + opts.ext.share_mode, + opts.ext.security_attributes, + get_creation_mode(opts)?, + get_flags_and_attributes(opts), + 0 as HANDLE, + ) + }; + if handle != INVALID_HANDLE_VALUE { + Ok(unsafe { fs::File::from_raw_handle(handle as _) }) + } else { + Err(io::Error::last_os_error()) + } + } else { + // Our own `CreateFileAtW` is similar to `CreateFileW` except it + // takes the filename as a Rust slice directly, so we can skip + // the NUL terminator. + let handle = unsafe { + CreateFileAtW( + dir, + &wide, + get_access_mode(opts)?, + opts.ext.share_mode, + opts.ext.security_attributes, + get_creation_mode(opts)?, + get_flags_and_attributes(opts), + 0 as HANDLE, + ) + }; + + if let Ok(handle) = handle.try_into() { + Ok(>::from(handle)) + } else { + Err(io::Error::last_os_error()) + } + } +} + +/// *Unsandboxed* function similar to `open_unchecked`, but which just operates +/// on a bare path, rather than starting with a handle. +pub(crate) fn open_ambient_impl( + path: &Path, + options: &OpenOptions, + ambient_authority: AmbientAuthority, +) -> Result { + let _ = ambient_authority; + let (std_opts, manually_trunc) = open_options_to_std(options); + handle_open_result(std_opts.open(path), options, manually_trunc) +} + +fn handle_open_result( + result: io::Result, + options: &OpenOptions, + manually_trunc: bool, +) -> Result { + match result { + Ok(f) => { + let enforce_dir = options.dir_required; + let enforce_nofollow = options.follow == FollowSymlinks::No + && (options.ext.custom_flags & FILE_FLAG_OPEN_REPARSE_POINT) == 0; + + if enforce_dir || enforce_nofollow { + let metadata = f.metadata().map_err(OpenUncheckedError::Other)?; + + if enforce_dir { + // Require a directory. It may seem possible to eliminate + // this `metadata()` call by appending a slash to the path + // before opening it so that the OS requires a directory + // for us, however on Windows in some circumstances this + // leads to "The filename, directory name, or volume label + // syntax is incorrect." errors. + // + // We check `file_attributes()` instead of using `is_dir()` + // since the latter returns false if we're looking at a + // directory symlink. + if metadata.file_attributes() & FILE_ATTRIBUTE_DIRECTORY == 0 { + return Err(OpenUncheckedError::Other(errors::is_not_directory())); + } + } + + if enforce_nofollow { + // Windows doesn't have a way to return errors like + // `O_NOFOLLOW`, so if we're not following symlinks and + // we're not using `FILE_FLAG_OPEN_REPARSE_POINT` manually + // to open a symlink itself, check for symlinks and report + // them as a distinct error. + if metadata.file_type().is_symlink() { + return Err(OpenUncheckedError::Symlink( + io::Error::from_raw_os_error( + Foundation::ERROR_STOPPED_ON_SYMLINK as i32, + ), + if metadata.file_attributes() & FILE_ATTRIBUTE_DIRECTORY + == FILE_ATTRIBUTE_DIRECTORY + { + SymlinkKind::Dir + } else { + SymlinkKind::File + }, + )); + } + } + } + + // Windows truncates symlinks into normal files, so truncation + // may be disabled above; do it manually if needed. Note that this + // is expected to always succeed for normal files, but this will + // fail if a directory was opened as directories don't support + // truncation. + if manually_trunc { + if let Err(e) = f.set_len(0) { + return Err(OpenUncheckedError::Other(e)); + } + } + Ok(f) + } + Err(e) if e.kind() == io::ErrorKind::NotFound => Err(OpenUncheckedError::NotFound(e)), + Err(e) => match e.raw_os_error() { + Some(code) => match code as u32 { + Foundation::ERROR_FILE_NOT_FOUND | Foundation::ERROR_PATH_NOT_FOUND => { + Err(OpenUncheckedError::NotFound(e)) + } + _ => Err(OpenUncheckedError::Other(e)), + }, + None => Err(OpenUncheckedError::Other(e)), + }, + } +} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/read_dir_inner.rs b/crates/wasi/src/filesystem/primitives/windows/fs/read_dir_inner.rs new file mode 100644 index 000000000000..1f87eb9756e7 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/windows/fs/read_dir_inner.rs @@ -0,0 +1,55 @@ +use super::get_path::concatenate; +use crate::fs::{open_dir, DirEntryInner, FollowSymlinks}; +use std::path::{Component, Path}; +use std::{fmt, fs, io}; + +pub(crate) struct ReadDirInner { + std: fs::ReadDir, +} + +impl ReadDirInner { + pub(crate) fn new(start: &fs::File, path: &Path, follow: FollowSymlinks) -> io::Result { + assert_eq!( + follow, + FollowSymlinks::Yes, + "`read_dir` without following symlinks is not implemented yet" + ); + let dir = open_dir(start, path)?; + Self::new_unchecked(&dir, Component::CurDir.as_ref()) + } + + pub(crate) fn read_base_dir(start: &fs::File) -> io::Result { + Self::new_unchecked(&start, Component::CurDir.as_ref()) + } + + pub(crate) fn new_unchecked(start: &fs::File, path: &Path) -> io::Result { + let full_path = concatenate(start, path)?; + Ok(Self { + std: fs::read_dir(full_path)?, + }) + } + + pub(super) fn from_std(std: fs::ReadDir) -> Self { + Self { std } + } +} + +impl Iterator for ReadDirInner { + type Item = io::Result; + + fn next(&mut self) -> Option { + self.std + .next() + .map(|result| result.map(DirEntryInner::from_std)) + } +} + +impl fmt::Debug for ReadDirInner { + // Like libstd's version, but doesn't print the path. + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut b = f.debug_struct("ReadDir"); + // `fs::ReadDir`'s `Debug` just prints the path, and since we're not + // printing that, we don't have anything else to print. + b.finish() + } +} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/read_link_impl.rs b/crates/wasi/src/filesystem/primitives/windows/fs/read_link_impl.rs new file mode 100644 index 000000000000..d63e8135ad6d --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/windows/fs/read_link_impl.rs @@ -0,0 +1,20 @@ +use crate::fs::{open, FollowSymlinks, OpenOptions, OpenOptionsExt}; +use std::path::{Path, PathBuf}; +use std::{fs, io}; +use windows_sys::Win32::Storage::FileSystem::{ + FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, +}; + +/// *Unsandboxed* function similar to `read_link`, but which does not perform +/// sandboxing. +pub(crate) fn read_link_impl(start: &fs::File, path: &Path) -> io::Result { + // Open the link with no access mode, instead of generic read. + // By default FILE_LIST_DIRECTORY is denied for the junction "C:\Documents and + // Settings", so this is needed for a common case. + let mut opts = OpenOptions::new(); + opts.access_mode(0); + opts.custom_flags(FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS); + opts.follow(FollowSymlinks::No); + let file = open(start, path, &opts)?; + winx::file::read_link(&file) +} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/read_link_unchecked.rs b/crates/wasi/src/filesystem/primitives/windows/fs/read_link_unchecked.rs new file mode 100644 index 000000000000..5d5bf218f19f --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/windows/fs/read_link_unchecked.rs @@ -0,0 +1,14 @@ +use super::get_path::concatenate; +use std::path::{Path, PathBuf}; +use std::{fs, io}; + +/// *Unsandboxed* function similar to `read_link`, but which does not perform +/// sandboxing. +pub(crate) fn read_link_unchecked( + start: &fs::File, + path: &Path, + _reuse: PathBuf, +) -> io::Result { + let full_path = concatenate(start, path)?; + fs::read_link(full_path) +} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/remove_dir_all_impl.rs b/crates/wasi/src/filesystem/primitives/windows/fs/remove_dir_all_impl.rs new file mode 100644 index 000000000000..22dbd4155194 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/windows/fs/remove_dir_all_impl.rs @@ -0,0 +1,28 @@ +use super::get_path::get_path; +use crate::fs::{open_dir, open_dir_nofollow, remove_dir, stat, FollowSymlinks}; +use std::path::Path; +use std::{fs, io}; + +pub(crate) fn remove_dir_all_impl(start: &fs::File, path: &Path) -> io::Result<()> { + // Open the directory, following symlinks, to make sure it is a directory. + let file = open_dir(start, path)?; + // Test whether the path is a symlink. + let md = stat(start, path, FollowSymlinks::No)?; + drop(file); + if md.is_symlink() { + // If so, just remove the link. + remove_dir(start, path) + } else { + // Otherwise, remove the tree. + let dir = open_dir_nofollow(start, path)?; + remove_open_dir_all_impl(dir) + } +} + +pub(crate) fn remove_open_dir_all_impl(dir: fs::File) -> io::Result<()> { + // Close the directory so that we can delete it. This is racy; see the + // comments in `remove_open_dir_impl` for details. + let path = get_path(&dir)?; + drop(dir); + fs::remove_dir_all(&path) +} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/remove_dir_unchecked.rs b/crates/wasi/src/filesystem/primitives/windows/fs/remove_dir_unchecked.rs new file mode 100644 index 000000000000..3b80cac3e913 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/windows/fs/remove_dir_unchecked.rs @@ -0,0 +1,10 @@ +use super::get_path::concatenate; +use std::path::Path; +use std::{fs, io}; + +/// *Unsandboxed* function similar to `remove_dir`, but which does not perform +/// sandboxing. +pub(crate) fn remove_dir_unchecked(start: &fs::File, path: &Path) -> io::Result<()> { + let full_path = concatenate(start, path)?; + fs::remove_dir(full_path) +} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/remove_file_unchecked.rs b/crates/wasi/src/filesystem/primitives/windows/fs/remove_file_unchecked.rs new file mode 100644 index 000000000000..9486e8d5e399 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/windows/fs/remove_file_unchecked.rs @@ -0,0 +1,10 @@ +use super::get_path::concatenate; +use std::path::Path; +use std::{fs, io}; + +/// *Unsandboxed* function similar to `remove_file`, but which does not perform +/// sandboxing. +pub(crate) fn remove_file_unchecked(start: &fs::File, path: &Path) -> io::Result<()> { + let full_path = concatenate(start, path)?; + fs::remove_file(full_path) +} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/remove_open_dir_impl.rs b/crates/wasi/src/filesystem/primitives/windows/fs/remove_open_dir_impl.rs new file mode 100644 index 000000000000..184402197a63 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/windows/fs/remove_open_dir_impl.rs @@ -0,0 +1,24 @@ +use super::get_path::get_path; +use std::{fs, io}; + +pub(crate) fn remove_open_dir_impl(dir: fs::File) -> io::Result<()> { + let path = get_path(&dir)?; + + // Drop the directory before removing it, since we open directories without + // `FILE_SHARE_DELETE`, and removing it requires accessing via its name + // rather than its handle. + // + // There is a window here in which another process could remove or rename + // a directory with this path after the handle is dropped, however it's + // unlikely to happen by accident, and unlikely to cause major problems. + // It may cause spurious failures, or failures with different error codes, + // but this appears to be unavoidable. + // + // Even if we did have `FILE_SHARE_DELETE` and we kept the handle open + // while doing the `remove_dir, `FILE_SHARE_DELETE` would grant other + // processes the right to remove or rename the directory. So there + // doesn't seem to be a race-free way of removing opened directories. + drop(dir); + + fs::remove_dir(path) +} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/rename_unchecked.rs b/crates/wasi/src/filesystem/primitives/windows/fs/rename_unchecked.rs new file mode 100644 index 000000000000..5afbdb5c5b53 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/windows/fs/rename_unchecked.rs @@ -0,0 +1,16 @@ +use super::get_path::concatenate; +use std::path::Path; +use std::{fs, io}; + +/// *Unsandboxed* function similar to `rename`, but which does not perform +/// sandboxing. +pub(crate) fn rename_unchecked( + old_start: &fs::File, + old_path: &Path, + new_start: &fs::File, + new_path: &Path, +) -> io::Result<()> { + let old_full_path = concatenate(old_start, old_path)?; + let new_full_path = concatenate(new_start, new_path)?; + fs::rename(old_full_path, new_full_path) +} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/reopen_impl.rs b/crates/wasi/src/filesystem/primitives/windows/fs/reopen_impl.rs new file mode 100644 index 000000000000..31c865241dfa --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/windows/fs/reopen_impl.rs @@ -0,0 +1,85 @@ +use crate::fs::{get_access_mode, get_flags_and_attributes, OpenOptions}; +use io_lifetimes::AsHandle; +use std::{fs, io}; +use windows_sys::Win32::Foundation::{GENERIC_READ, GENERIC_WRITE}; +use windows_sys::Win32::Storage::FileSystem::{ + FILE_FLAG_DELETE_ON_CLOSE, FILE_FLAG_WRITE_THROUGH, FILE_GENERIC_READ, FILE_GENERIC_WRITE, + SECURITY_CONTEXT_TRACKING, SECURITY_DELEGATION, SECURITY_EFFECTIVE_ONLY, + SECURITY_IDENTIFICATION, SECURITY_IMPERSONATION, +}; +use winx::file::{AccessMode, Flags, ShareMode}; + +/// Implementation of `reopen`. +pub(crate) fn reopen_impl(file: &fs::File, options: &OpenOptions) -> io::Result { + let old_access_mode = winx::file::query_access_information(file.as_handle())?; + let new_access_mode = get_access_mode(options)?; + let flags = get_flags_and_attributes(options); + + let new_access_mode = AccessMode::from_bits(new_access_mode).unwrap(); + let flags = Flags::from_bits(flags).unwrap(); + + // Disallow custom access modes might imply or require more access than we + // have. + if new_access_mode + .intersects(AccessMode::from_bits(GENERIC_WRITE | FILE_GENERIC_WRITE).unwrap()) + && !old_access_mode + .intersects(AccessMode::from_bits(GENERIC_WRITE | FILE_GENERIC_WRITE).unwrap()) + { + return Err(io::Error::new( + io::ErrorKind::PermissionDenied, + "Can't reopen file", + )); + } + if new_access_mode.intersects(AccessMode::from_bits(GENERIC_READ | FILE_GENERIC_READ).unwrap()) + && !old_access_mode + .intersects(AccessMode::from_bits(GENERIC_READ | FILE_GENERIC_READ).unwrap()) + { + return Err(io::Error::new( + io::ErrorKind::PermissionDenied, + "Can't reopen file", + )); + } + + // Disallow custom flags which might imply or require more access than we have. + if flags + .intersects(Flags::from_bits(FILE_FLAG_DELETE_ON_CLOSE | FILE_FLAG_WRITE_THROUGH).unwrap()) + && !old_access_mode + .intersects(AccessMode::from_bits(GENERIC_WRITE | FILE_GENERIC_WRITE).unwrap()) + { + return Err(io::Error::new( + io::ErrorKind::PermissionDenied, + "Can't reopen file", + )); + } + + // For now, disallow all non-anonymous security modes. + /* + if flags.intersects( + Flags::from_bits( + SECURITY_CONTEXT_TRACKING + | SECURITY_DELEGATION + | SECURITY_EFFECTIVE_ONLY + | SECURITY_IDENTIFICATION + | SECURITY_IMPERSONATION, + ) + .unwrap(), + ) { + return Err(io::Error::new(io::ErrorKind::Other, "Can't reopen file")); + } + */ + // And for now, do the bit tests manually. + if (flags.bits() + & (SECURITY_CONTEXT_TRACKING + | SECURITY_DELEGATION + | SECURITY_EFFECTIVE_ONLY + | SECURITY_IDENTIFICATION + | SECURITY_IMPERSONATION)) + != 0 + { + return Err(io::Error::new(io::ErrorKind::Other, "Can't reopen file")); + } + + let new_share_mode = + ShareMode::FILE_SHARE_READ | ShareMode::FILE_SHARE_WRITE | ShareMode::FILE_SHARE_DELETE; + winx::file::reopen_file(file.as_handle(), new_access_mode, new_share_mode, flags) +} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/set_permissions_unchecked.rs b/crates/wasi/src/filesystem/primitives/windows/fs/set_permissions_unchecked.rs new file mode 100644 index 000000000000..a91f36956fa4 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/windows/fs/set_permissions_unchecked.rs @@ -0,0 +1,21 @@ +use super::get_path::concatenate; +use crate::fs::Permissions; +use std::path::Path; +use std::{fs, io}; + +/// *Unsandboxed* function similar to `set_permissions`, but which does not +/// perform sandboxing. +pub(crate) fn set_permissions_unchecked( + start: &fs::File, + path: &Path, + perm: Permissions, +) -> io::Result<()> { + // According to [Rust's documentation], `fs::set_permissions` uses + // `SetFileAttributes`, and according to [Windows' documentation] + // `SetFileAttributes` does not follow symbolic links. + // + // [Windows' documentation]: https://docs.microsoft.com/en-us/windows/win32/fileio/symbolic-link-effects-on-file-systems-functions#setfileattributes + // [Rust's documentation]: https://doc.rust-lang.org/std/fs/fn.set_permissions.html#platform-specific-behavior + let out_path = concatenate(start, path)?; + fs::set_permissions(out_path, perm.into_std(start)?) +} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/set_symlink_permissions_unchecked.rs b/crates/wasi/src/filesystem/primitives/windows/fs/set_symlink_permissions_unchecked.rs new file mode 100644 index 000000000000..e7a4919e6c8d --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/windows/fs/set_symlink_permissions_unchecked.rs @@ -0,0 +1,20 @@ +use super::get_path::concatenate; +use crate::fs::Permissions; +use std::path::Path; +use std::{fs, io}; + +/// This can just use `AT_SYMLINK_NOFOLLOW`. +pub(crate) fn set_symlink_permissions_unchecked( + start: &fs::File, + path: &Path, + perm: Permissions, +) -> io::Result<()> { + // According to [Rust's documentation], `fs::set_permissions` uses + // `SetFileAttributes`, and according to [Windows' documentation] + // `SetFileAttributes` does not follow symbolic links. + // + // [Windows' documentation]: https://docs.microsoft.com/en-us/windows/win32/fileio/symbolic-link-effects-on-file-systems-functions#setfileattributes + // [Rust's documentation]: https://doc.rust-lang.org/std/fs/fn.set_permissions.html#platform-specific-behavior + let out_path = concatenate(start, path)?; + fs::set_permissions(out_path, perm.into_std(start)?) +} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/set_times_impl.rs b/crates/wasi/src/filesystem/primitives/windows/fs/set_times_impl.rs new file mode 100644 index 000000000000..83d0e3383d9a --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/windows/fs/set_times_impl.rs @@ -0,0 +1,48 @@ +use crate::fs::{open, OpenOptions, OpenOptionsExt, SystemTimeSpec}; +use std::path::Path; +use std::{fs, io}; +use windows_sys::Win32::Storage::FileSystem::{ + FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, +}; + +#[inline] +pub(crate) fn set_times_impl( + start: &fs::File, + path: &Path, + atime: Option, + mtime: Option, +) -> io::Result<()> { + set_times_inner(start, path, atime, mtime, 0) +} + +#[inline] +pub(crate) fn set_times_nofollow_impl( + start: &fs::File, + path: &Path, + atime: Option, + mtime: Option, +) -> io::Result<()> { + set_times_inner(start, path, atime, mtime, FILE_FLAG_OPEN_REPARSE_POINT) +} + +fn set_times_inner( + start: &fs::File, + path: &Path, + atime: Option, + mtime: Option, + custom_flags: u32, +) -> io::Result<()> { + let custom_flags = custom_flags | FILE_FLAG_BACKUP_SEMANTICS; + + // On Windows, `set_times` requires write permissions. + let file = open( + start, + path, + OpenOptions::new().write(true).custom_flags(custom_flags), + )?; + fs_set_times::SetTimes::set_times( + &file, + atime.map(SystemTimeSpec::into_std), + mtime.map(SystemTimeSpec::into_std), + ) +} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/stat_unchecked.rs b/crates/wasi/src/filesystem/primitives/windows/fs/stat_unchecked.rs new file mode 100644 index 000000000000..e0b3d41514bc --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/windows/fs/stat_unchecked.rs @@ -0,0 +1,36 @@ +use crate::fs::OpenOptionsExt; +use crate::fs::{open_unchecked, FollowSymlinks, Metadata, OpenOptions}; +use std::path::Path; +use std::{fs, io}; +use windows_sys::Win32::Storage::FileSystem::{ + FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, +}; + +/// *Unsandboxed* function similar to `stat`, but which does not perform +/// sandboxing. +pub(crate) fn stat_unchecked( + start: &fs::File, + path: &Path, + follow: FollowSymlinks, +) -> io::Result { + // Attempt to open the file to get the metadata that way, as that gives + // us all the info. + let mut opts = OpenOptions::new(); + + // Explicitly request no access, because we're just querying metadata. + opts.access_mode(0); + + match follow { + FollowSymlinks::Yes => { + opts.custom_flags(FILE_FLAG_BACKUP_SEMANTICS); + opts.follow(FollowSymlinks::Yes); + } + FollowSymlinks::No => { + opts.custom_flags(FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS); + opts.follow(FollowSymlinks::No); + } + } + + let file = open_unchecked(start, path, &opts)?; + Metadata::from_file(&file) +} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/symlink_unchecked.rs b/crates/wasi/src/filesystem/primitives/windows/fs/symlink_unchecked.rs new file mode 100644 index 000000000000..73c6794da31b --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/windows/fs/symlink_unchecked.rs @@ -0,0 +1,25 @@ +use super::get_path::concatenate; +use std::path::Path; +use std::{fs, io}; + +/// *Unsandboxed* function similar to `symlink_file`, but which does not +/// perform sandboxing. +pub(crate) fn symlink_file_unchecked( + old_path: &Path, + new_start: &fs::File, + new_path: &Path, +) -> io::Result<()> { + let new_full_path = concatenate(new_start, new_path)?; + std::os::windows::fs::symlink_file(old_path, new_full_path) +} + +/// *Unsandboxed* function similar to `symlink_dir`, but which does not perform +/// sandboxing. +pub(crate) fn symlink_dir_unchecked( + old_path: &Path, + new_start: &fs::File, + new_path: &Path, +) -> io::Result<()> { + let new_full_path = concatenate(new_start, new_path)?; + std::os::windows::fs::symlink_dir(old_path, new_full_path) +} diff --git a/crates/wasi/src/filesystem/primitives/windows/mod.rs b/crates/wasi/src/filesystem/primitives/windows/mod.rs new file mode 100644 index 000000000000..5553d02ec536 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/windows/mod.rs @@ -0,0 +1,4 @@ +//! The `winx` module contains code specific to Windows, supported by the +//! `winx` crate. + +pub(crate) mod fs; From 8c6d738a0dc8718c81ed582aaab35e579be6166e Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 27 Aug 2026 11:19:34 -0700 Subject: [PATCH 02/34] Get cap-primitives vendor compiling * Add necessary dependencies * Adjust imports --- Cargo.lock | 7 +++ crates/wasi/Cargo.toml | 8 ++++ crates/wasi/src/filesystem.rs | 2 + .../wasi/src/filesystem/primitives/access.rs | 4 +- .../src/filesystem/primitives/canonicalize.rs | 4 +- crates/wasi/src/filesystem/primitives/copy.rs | 2 +- .../src/filesystem/primitives/create_dir.rs | 6 +-- .../src/filesystem/primitives/dir_builder.rs | 2 +- .../src/filesystem/primitives/dir_entry.rs | 4 +- .../src/filesystem/primitives/dir_options.rs | 6 +-- .../wasi/src/filesystem/primitives/errors.rs | 4 +- .../primitives/file_path_by_searching.rs | 2 +- .../src/filesystem/primitives/file_type.rs | 2 +- .../src/filesystem/primitives/hard_link.rs | 6 +-- .../primitives/is_file_read_write.rs | 2 +- .../primitives/manually/canonicalize.rs | 2 +- .../filesystem/primitives/manually/open.rs | 12 +++-- .../primitives/manually/open_entry.rs | 4 +- .../primitives/manually/read_link_one.rs | 2 +- .../filesystem/primitives/maybe_owned_file.rs | 2 +- .../src/filesystem/primitives/metadata.rs | 48 +++++++++---------- crates/wasi/src/filesystem/primitives/mod.rs | 14 ++++-- crates/wasi/src/filesystem/primitives/open.rs | 4 +- .../src/filesystem/primitives/open_ambient.rs | 4 +- .../src/filesystem/primitives/open_dir.rs | 6 ++- .../src/filesystem/primitives/open_options.rs | 2 +- .../src/filesystem/primitives/permissions.rs | 14 +++--- .../src/filesystem/primitives/read_dir.rs | 2 +- .../src/filesystem/primitives/read_link.rs | 4 +- .../src/filesystem/primitives/remove_dir.rs | 6 +-- .../filesystem/primitives/remove_dir_all.rs | 2 +- .../src/filesystem/primitives/remove_file.rs | 6 +-- .../filesystem/primitives/remove_open_dir.rs | 2 +- .../wasi/src/filesystem/primitives/rename.rs | 8 ++-- .../wasi/src/filesystem/primitives/reopen.rs | 2 +- .../primitives/rustix/darwin/fs/file_path.rs | 2 +- .../primitives/rustix/freebsd/fs/mod.rs | 2 +- .../rustix/freebsd/fs/open_entry_impl.rs | 2 +- .../primitives/rustix/freebsd/fs/open_impl.rs | 2 +- .../rustix/freebsd/fs/remove_dir_impl.rs | 2 +- .../rustix/freebsd/fs/remove_file_impl.rs | 2 +- .../rustix/freebsd/fs/set_permissions_impl.rs | 2 +- .../rustix/freebsd/fs/set_times_impl.rs | 2 +- .../primitives/rustix/freebsd/fs/stat_impl.rs | 2 +- .../primitives/rustix/fs/access_unchecked.rs | 2 +- .../primitives/rustix/fs/copy_impl.rs | 8 ++-- .../rustix/fs/create_dir_unchecked.rs | 4 +- .../primitives/rustix/fs/dir_entry_inner.rs | 4 +- .../primitives/rustix/fs/dir_options_ext.rs | 2 +- .../primitives/rustix/fs/dir_utils.rs | 12 +++-- .../primitives/rustix/fs/file_path.rs | 2 +- .../primitives/rustix/fs/file_type_ext.rs | 2 +- .../primitives/rustix/fs/is_root_dir.rs | 2 +- .../primitives/rustix/fs/is_same_file.rs | 2 +- .../primitives/rustix/fs/metadata_ext.rs | 16 +++---- .../filesystem/primitives/rustix/fs/mod.rs | 14 +++--- .../filesystem/primitives/rustix/fs/oflags.rs | 2 +- .../primitives/rustix/fs/open_unchecked.rs | 4 +- .../primitives/rustix/fs/permissions_ext.rs | 4 +- .../primitives/rustix/fs/read_dir_inner.rs | 2 +- .../rustix/fs/remove_dir_all_impl.rs | 2 +- .../rustix/fs/remove_open_dir_by_searching.rs | 2 +- .../primitives/rustix/fs/reopen_impl.rs | 4 +- .../rustix/fs/set_permissions_impl.rs | 2 +- .../fs/set_symlink_permissions_unchecked.rs | 4 +- .../primitives/rustix/fs/set_times_impl.rs | 2 +- .../primitives/rustix/fs/stat_unchecked.rs | 2 +- .../filesystem/primitives/rustix/fs/times.rs | 8 ++-- .../rustix/linux/fs/canonicalize_impl.rs | 4 +- .../rustix/linux/fs/file_metadata.rs | 2 +- .../primitives/rustix/linux/fs/mod.rs | 8 ++-- .../rustix/linux/fs/open_entry_impl.rs | 2 +- .../primitives/rustix/linux/fs/open_impl.rs | 8 ++-- .../primitives/rustix/linux/fs/procfs.rs | 4 +- .../rustix/linux/fs/set_permissions_impl.rs | 2 +- .../rustix/linux/fs/set_times_impl.rs | 2 +- .../primitives/rustix/linux/fs/stat_impl.rs | 4 +- .../filesystem/primitives/set_permissions.rs | 6 ++- .../src/filesystem/primitives/set_times.rs | 2 +- crates/wasi/src/filesystem/primitives/stat.rs | 4 +- .../wasi/src/filesystem/primitives/symlink.rs | 14 +++--- .../filesystem/primitives/system_time_spec.rs | 8 ++-- .../primitives/via_parent/access.rs | 2 +- .../primitives/via_parent/create_dir.rs | 4 +- .../primitives/via_parent/hard_link.rs | 2 +- .../primitives/via_parent/open_parent.rs | 2 +- .../primitives/via_parent/read_link.rs | 2 +- .../primitives/via_parent/remove_dir.rs | 2 +- .../primitives/via_parent/remove_file.rs | 2 +- .../primitives/via_parent/rename.rs | 4 +- .../primitives/via_parent/set_permissions.rs | 2 +- .../via_parent/set_symlink_permissions.rs | 4 +- .../via_parent/set_times_nofollow.rs | 2 +- .../primitives/via_parent/symlink.rs | 8 ++-- .../primitives/windows/fs/access_unchecked.rs | 2 +- .../filesystem/primitives/windows/fs/copy.rs | 2 +- .../windows/fs/create_dir_unchecked.rs | 2 +- .../primitives/windows/fs/create_file_at_w.rs | 39 +++++++-------- .../primitives/windows/fs/dir_entry_inner.rs | 14 +++--- .../primitives/windows/fs/dir_utils.rs | 6 +-- .../primitives/windows/fs/file_type_ext.rs | 6 ++- .../primitives/windows/fs/is_same_file.rs | 6 +-- .../primitives/windows/fs/metadata_ext.rs | 4 +- .../filesystem/primitives/windows/fs/mod.rs | 2 +- .../primitives/windows/fs/oflags.rs | 2 +- .../primitives/windows/fs/open_impl.rs | 2 +- .../primitives/windows/fs/open_options_ext.rs | 2 +- .../primitives/windows/fs/open_unchecked.rs | 8 ++-- .../primitives/windows/fs/read_dir_inner.rs | 2 +- .../primitives/windows/fs/read_link_impl.rs | 2 +- .../windows/fs/remove_dir_all_impl.rs | 2 +- .../primitives/windows/fs/reopen_impl.rs | 2 +- .../windows/fs/set_permissions_unchecked.rs | 2 +- .../fs/set_symlink_permissions_unchecked.rs | 2 +- .../primitives/windows/fs/set_times_impl.rs | 2 +- .../primitives/windows/fs/stat_unchecked.rs | 4 +- 116 files changed, 297 insertions(+), 257 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2202f6137d35..dc18cc23a3a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5162,15 +5162,21 @@ dependencies = [ name = "wasmtime-wasi" version = "49.0.0-dev" dependencies = [ + "ambient-authority", "async-trait", "bitflags 2.11.1", "bytes", "cap-fs-ext", "cap-primitives", "env_logger 0.11.5", + "fs-set-times", "futures", + "io-extras", + "io-lifetimes 3.0.1", + "maybe-owned", "rand 0.10.1", "rustix 1.1.4", + "rustix-linux-procfs", "tempfile", "test-log", "test-programs-artifacts", @@ -5184,6 +5190,7 @@ dependencies = [ "wasmtime-wasi-io", "wiggle", "windows-sys 0.61.2", + "winx", ] [[package]] diff --git a/crates/wasi/Cargo.toml b/crates/wasi/Cargo.toml index 48775b62c56b..5921ed9ee827 100644 --- a/crates/wasi/Cargo.toml +++ b/crates/wasi/Cargo.toml @@ -34,6 +34,13 @@ futures = { workspace = true } url = { workspace = true } rand = { workspace = true, features = ['std_rng', 'thread_rng'] } +maybe-owned = "0.3" +ambient-authority = "*" +io-extras = "0.19.0" +io-lifetimes = { version = "3.0.1", default-features = false } +winx = "0.36.0" +fs-set-times = "0.20.0" + [dev-dependencies] tokio = { workspace = true, features = ["time", "sync", "io-std", "io-util", "rt", "rt-multi-thread", "net", "macros", "fs"] } test-log = { workspace = true } @@ -46,6 +53,7 @@ env_logger = { workspace = true } [target.'cfg(unix)'.dependencies] rustix = { workspace = true, features = ["event", "fs", "net"] } +rustix-linux-procfs = "0.1.1" [target.'cfg(windows)'.dependencies] rustix = { workspace = true, features = ["event", "net"] } diff --git a/crates/wasi/src/filesystem.rs b/crates/wasi/src/filesystem.rs index e7b9190bb68c..7e62c1a8e66e 100644 --- a/crates/wasi/src/filesystem.rs +++ b/crates/wasi/src/filesystem.rs @@ -17,6 +17,8 @@ pub(crate) mod windows; #[cfg(windows)] pub(crate) use windows as sys; +pub(crate) mod primitives; + /// A helper struct which implements [`HasData`] for the `wasi:filesystem` APIs. /// /// This can be useful when directly calling `add_to_linker` functions directly, diff --git a/crates/wasi/src/filesystem/primitives/access.rs b/crates/wasi/src/filesystem/primitives/access.rs index 987762519426..09b67a0c517b 100644 --- a/crates/wasi/src/filesystem/primitives/access.rs +++ b/crates/wasi/src/filesystem/primitives/access.rs @@ -1,8 +1,8 @@ //! Access test functions. -use crate::fs::{FollowSymlinks, access_impl}; +use crate::filesystem::primitives::{FollowSymlinks, access_impl}; #[cfg(racy_asserts)] -use crate::fs::{access_unchecked, file_path}; +use crate::filesystem::primitives::{access_unchecked, file_path}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/canonicalize.rs b/crates/wasi/src/filesystem/primitives/canonicalize.rs index e552e60d36f6..9ee5e65db1b7 100644 --- a/crates/wasi/src/filesystem/primitives/canonicalize.rs +++ b/crates/wasi/src/filesystem/primitives/canonicalize.rs @@ -1,8 +1,8 @@ //! Sandboxed path canonicalization. -use crate::fs::canonicalize_impl; +use crate::filesystem::primitives::canonicalize_impl; #[cfg(racy_asserts)] -use crate::fs::{OpenOptions, file_path, open}; +use crate::filesystem::primitives::{OpenOptions, file_path, open}; use std::path::{Path, PathBuf}; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/copy.rs b/crates/wasi/src/filesystem/primitives/copy.rs index f40b954c638f..8fda7df160b2 100644 --- a/crates/wasi/src/filesystem/primitives/copy.rs +++ b/crates/wasi/src/filesystem/primitives/copy.rs @@ -1,4 +1,4 @@ -use crate::fs::copy_impl; +use crate::filesystem::primitives::copy_impl; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/create_dir.rs b/crates/wasi/src/filesystem/primitives/create_dir.rs index 9b593c940040..836f3498ce2a 100644 --- a/crates/wasi/src/filesystem/primitives/create_dir.rs +++ b/crates/wasi/src/filesystem/primitives/create_dir.rs @@ -1,11 +1,11 @@ //! This defines `create_dir`, the primary entrypoint to sandboxed directory //! creation. +use crate::filesystem::primitives::{DirOptions, create_dir_impl}; #[cfg(racy_asserts)] -use crate::fs::{ - canonicalize, create_dir_unchecked, map_result, stat_unchecked, FollowSymlinks, Metadata, +use crate::filesystem::primitives::{ + FollowSymlinks, Metadata, canonicalize, create_dir_unchecked, map_result, stat_unchecked, }; -use crate::fs::{create_dir_impl, DirOptions}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/dir_builder.rs b/crates/wasi/src/filesystem/primitives/dir_builder.rs index 53b5174f44ee..57e04b5e4168 100644 --- a/crates/wasi/src/filesystem/primitives/dir_builder.rs +++ b/crates/wasi/src/filesystem/primitives/dir_builder.rs @@ -1,4 +1,4 @@ -use crate::fs::DirOptions; +use crate::filesystem::primitives::DirOptions; use std::fmt; /// A builder used to create directories in various manners. diff --git a/crates/wasi/src/filesystem/primitives/dir_entry.rs b/crates/wasi/src/filesystem/primitives/dir_entry.rs index 22dcae9b5634..3b586a1f6d18 100644 --- a/crates/wasi/src/filesystem/primitives/dir_entry.rs +++ b/crates/wasi/src/filesystem/primitives/dir_entry.rs @@ -1,5 +1,5 @@ -use crate::fs::{ - dir_options, DirEntryInner, FileType, FollowSymlinks, Metadata, OpenOptions, ReadDir, +use crate::filesystem::primitives::{ + DirEntryInner, FileType, FollowSymlinks, Metadata, OpenOptions, ReadDir, dir_options, }; #[cfg(not(windows))] use rustix::fs::DirEntryExt; diff --git a/crates/wasi/src/filesystem/primitives/dir_options.rs b/crates/wasi/src/filesystem/primitives/dir_options.rs index d7dd8129b66e..9ef3dfff6134 100644 --- a/crates/wasi/src/filesystem/primitives/dir_options.rs +++ b/crates/wasi/src/filesystem/primitives/dir_options.rs @@ -1,5 +1,5 @@ #[cfg(not(target_os = "wasi"))] -use crate::fs::DirOptionsExt; +use crate::filesystem::primitives::DirOptionsExt; /// Options and flags which can be used to configure how a directory is /// created. @@ -25,7 +25,7 @@ impl DirOptions { } #[cfg(unix)] -impl crate::fs::DirBuilderExt for DirOptions { +impl crate::filesystem::primitives::DirBuilderExt for DirOptions { #[inline] fn mode(&mut self, mode: u32) -> &mut Self { self.ext.mode(mode); @@ -46,7 +46,7 @@ impl crate::fs::DirBuilderExt for DirOptions { impl arbitrary::Arbitrary<'_> for DirOptions { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { #[cfg(any(unix, target_os = "vxworks"))] - use crate::fs::DirBuilderExt; + use crate::filesystem::primitives::DirBuilderExt; #[allow(unused_mut)] let mut dir_options = Self::new(); diff --git a/crates/wasi/src/filesystem/primitives/errors.rs b/crates/wasi/src/filesystem/primitives/errors.rs index 150f762c73af..d0dcc949111a 100644 --- a/crates/wasi/src/filesystem/primitives/errors.rs +++ b/crates/wasi/src/filesystem/primitives/errors.rs @@ -1,9 +1,9 @@ use std::io; #[cfg(not(windows))] -pub(crate) use crate::rustix::fs::errors::*; +pub(crate) use crate::filesystem::primitives::rustix::fs::errors::*; #[cfg(windows)] -pub(crate) use crate::windows::fs::errors::*; +pub(crate) use crate::filesystem::primitives::windows::fs::errors::*; #[cold] pub(crate) fn escape_attempt() -> io::Error { diff --git a/crates/wasi/src/filesystem/primitives/file_path_by_searching.rs b/crates/wasi/src/filesystem/primitives/file_path_by_searching.rs index a357f53a4173..244ee70a8f72 100644 --- a/crates/wasi/src/filesystem/primitives/file_path_by_searching.rs +++ b/crates/wasi/src/filesystem/primitives/file_path_by_searching.rs @@ -1,4 +1,4 @@ -use crate::fs::{ +use crate::filesystem::primitives::{ is_root_dir, open_dir_unchecked, read_dir_unchecked, FollowSymlinks, MaybeOwnedFile, Metadata, }; use std::fs; diff --git a/crates/wasi/src/filesystem/primitives/file_type.rs b/crates/wasi/src/filesystem/primitives/file_type.rs index 8f2170099d3b..6fcab1d83f18 100644 --- a/crates/wasi/src/filesystem/primitives/file_type.rs +++ b/crates/wasi/src/filesystem/primitives/file_type.rs @@ -1,6 +1,6 @@ //! The `FileType` struct. -use crate::fs::ImplFileTypeExt; +use crate::filesystem::primitives::ImplFileTypeExt; /// `FileType`'s inner state. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] diff --git a/crates/wasi/src/filesystem/primitives/hard_link.rs b/crates/wasi/src/filesystem/primitives/hard_link.rs index ff05473c5ea6..761033f402f5 100644 --- a/crates/wasi/src/filesystem/primitives/hard_link.rs +++ b/crates/wasi/src/filesystem/primitives/hard_link.rs @@ -1,10 +1,10 @@ //! This defines `hard_link`, the primary entrypoint to sandboxed hard-link //! creation. -use crate::fs::hard_link_impl; +use crate::filesystem::primitives::hard_link_impl; #[cfg(racy_asserts)] -use crate::fs::{ - canonicalize, hard_link_unchecked, map_result, stat_unchecked, FollowSymlinks, Metadata, +use crate::filesystem::primitives::{ + FollowSymlinks, Metadata, canonicalize, hard_link_unchecked, map_result, stat_unchecked, }; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/is_file_read_write.rs b/crates/wasi/src/filesystem/primitives/is_file_read_write.rs index 99d8b16e8806..62d386e24541 100644 --- a/crates/wasi/src/filesystem/primitives/is_file_read_write.rs +++ b/crates/wasi/src/filesystem/primitives/is_file_read_write.rs @@ -1,4 +1,4 @@ -use crate::fs::is_file_read_write_impl; +use crate::filesystem::primitives::is_file_read_write_impl; use std::{fs, io}; /// Return a pair of booleans indicating whether the given file is opened diff --git a/crates/wasi/src/filesystem/primitives/manually/canonicalize.rs b/crates/wasi/src/filesystem/primitives/manually/canonicalize.rs index e0d8b552fbd9..0faf8e2572b9 100644 --- a/crates/wasi/src/filesystem/primitives/manually/canonicalize.rs +++ b/crates/wasi/src/filesystem/primitives/manually/canonicalize.rs @@ -2,7 +2,7 @@ //! resolution, in order to enforce sandboxing. use super::internal_open; -use crate::fs::{canonicalize_options, FollowSymlinks, MaybeOwnedFile}; +use crate::filesystem::primitives::{FollowSymlinks, MaybeOwnedFile, canonicalize_options}; use std::path::{Path, PathBuf}; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/manually/open.rs b/crates/wasi/src/filesystem/primitives/manually/open.rs index 83129071a212..13041a6e9028 100644 --- a/crates/wasi/src/filesystem/primitives/manually/open.rs +++ b/crates/wasi/src/filesystem/primitives/manually/open.rs @@ -1,10 +1,10 @@ //! Manual path resolution, one component at a time, with manual symlink //! resolution, in order to enforce sandboxing. -use super::{read_link_one, CanonicalPath, CowComponent}; -use crate::fs::{ - dir_options, errors, open_unchecked, path_has_trailing_dot, path_has_trailing_slash, - stat_unchecked, FollowSymlinks, MaybeOwnedFile, Metadata, OpenOptions, OpenUncheckedError, +use super::{CanonicalPath, CowComponent, read_link_one}; +use crate::filesystem::primitives::{ + FollowSymlinks, MaybeOwnedFile, Metadata, OpenOptions, OpenUncheckedError, dir_options, errors, + open_unchecked, path_has_trailing_dot, path_has_trailing_slash, stat_unchecked, }; #[cfg(any(target_os = "android", target_os = "linux", target_os = "freebsd"))] use rustix::fs::OFlags; @@ -13,7 +13,9 @@ use std::path::{Component, Path, PathBuf}; use std::{fs, io, mem}; #[cfg(windows)] use { - crate::fs::{open_dir_unchecked, path_really_has_trailing_dot, SymlinkKind}, + crate::filesystem::primitives::{ + SymlinkKind, open_dir_unchecked, path_really_has_trailing_dot, + }, windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_DIRECTORY, }; diff --git a/crates/wasi/src/filesystem/primitives/manually/open_entry.rs b/crates/wasi/src/filesystem/primitives/manually/open_entry.rs index efea4c41cbc2..bd84f9238d4f 100644 --- a/crates/wasi/src/filesystem/primitives/manually/open_entry.rs +++ b/crates/wasi/src/filesystem/primitives/manually/open_entry.rs @@ -1,5 +1,7 @@ use super::{internal_open, read_link_one}; -use crate::fs::{open_unchecked, FollowSymlinks, MaybeOwnedFile, OpenOptions, OpenUncheckedError}; +use crate::filesystem::primitives::{ + FollowSymlinks, MaybeOwnedFile, OpenOptions, OpenUncheckedError, open_unchecked, +}; use std::ffi::OsStr; use std::path::PathBuf; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/manually/read_link_one.rs b/crates/wasi/src/filesystem/primitives/manually/read_link_one.rs index 9c7c6f9204cb..9f54f58bdc60 100644 --- a/crates/wasi/src/filesystem/primitives/manually/read_link_one.rs +++ b/crates/wasi/src/filesystem/primitives/manually/read_link_one.rs @@ -1,4 +1,4 @@ -use crate::fs::{errors, read_link_unchecked, MAX_SYMLINK_EXPANSIONS}; +use crate::filesystem::primitives::{MAX_SYMLINK_EXPANSIONS, errors, read_link_unchecked}; use std::ffi::OsStr; use std::path::{Path, PathBuf}; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/maybe_owned_file.rs b/crates/wasi/src/filesystem/primitives/maybe_owned_file.rs index 443cba2437db..41422c41fa55 100644 --- a/crates/wasi/src/filesystem/primitives/maybe_owned_file.rs +++ b/crates/wasi/src/filesystem/primitives/maybe_owned_file.rs @@ -1,4 +1,4 @@ -use crate::fs::{open_unchecked, OpenOptions}; +use crate::filesystem::primitives::{OpenOptions, open_unchecked}; use maybe_owned::MaybeOwned; use std::ops::Deref; use std::path::Component; diff --git a/crates/wasi/src/filesystem/primitives/metadata.rs b/crates/wasi/src/filesystem/primitives/metadata.rs index 934983739ae4..087b4eae9a0c 100644 --- a/crates/wasi/src/filesystem/primitives/metadata.rs +++ b/crates/wasi/src/filesystem/primitives/metadata.rs @@ -1,5 +1,5 @@ -use crate::fs::{FileType, ImplFileTypeExt, ImplMetadataExt, Permissions}; -use crate::time::SystemTime; +use crate::filesystem::primitives::{FileType, ImplFileTypeExt, ImplMetadataExt, Permissions}; +use std::time::SystemTime; use std::{fs, io}; /// Metadata information about a file. @@ -52,9 +52,9 @@ impl Metadata { file_type, len: std.len(), permissions: Permissions::from_std(std.permissions()), - modified: std.modified().ok().map(SystemTime::from_std), - accessed: std.accessed().ok().map(SystemTime::from_std), - created: std.created().ok().map(SystemTime::from_std), + modified: std.modified().ok(), + accessed: std.accessed().ok(), + created: std.created().ok(), ext, } } @@ -282,82 +282,82 @@ pub trait MetadataExt { impl MetadataExt for Metadata { #[inline] fn dev(&self) -> u64 { - crate::fs::MetadataExt::dev(&self.ext) + crate::filesystem::primitives::MetadataExt::dev(&self.ext) } #[inline] fn ino(&self) -> u64 { - crate::fs::MetadataExt::ino(&self.ext) + crate::filesystem::primitives::MetadataExt::ino(&self.ext) } #[inline] fn mode(&self) -> u32 { - crate::fs::MetadataExt::mode(&self.ext) + crate::filesystem::primitives::MetadataExt::mode(&self.ext) } #[inline] fn nlink(&self) -> u64 { - crate::fs::MetadataExt::nlink(&self.ext) + crate::filesystem::primitives::MetadataExt::nlink(&self.ext) } #[inline] fn uid(&self) -> u32 { - crate::fs::MetadataExt::uid(&self.ext) + crate::filesystem::primitives::MetadataExt::uid(&self.ext) } #[inline] fn gid(&self) -> u32 { - crate::fs::MetadataExt::gid(&self.ext) + crate::filesystem::primitives::MetadataExt::gid(&self.ext) } #[inline] fn rdev(&self) -> u64 { - crate::fs::MetadataExt::rdev(&self.ext) + crate::filesystem::primitives::MetadataExt::rdev(&self.ext) } #[inline] fn size(&self) -> u64 { - crate::fs::MetadataExt::size(&self.ext) + crate::filesystem::primitives::MetadataExt::size(&self.ext) } #[inline] fn atime(&self) -> i64 { - crate::fs::MetadataExt::atime(&self.ext) + crate::filesystem::primitives::MetadataExt::atime(&self.ext) } #[inline] fn atime_nsec(&self) -> i64 { - crate::fs::MetadataExt::atime_nsec(&self.ext) + crate::filesystem::primitives::MetadataExt::atime_nsec(&self.ext) } #[inline] fn mtime(&self) -> i64 { - crate::fs::MetadataExt::mtime(&self.ext) + crate::filesystem::primitives::MetadataExt::mtime(&self.ext) } #[inline] fn mtime_nsec(&self) -> i64 { - crate::fs::MetadataExt::mtime_nsec(&self.ext) + crate::filesystem::primitives::MetadataExt::mtime_nsec(&self.ext) } #[inline] fn ctime(&self) -> i64 { - crate::fs::MetadataExt::ctime(&self.ext) + crate::filesystem::primitives::MetadataExt::ctime(&self.ext) } #[inline] fn ctime_nsec(&self) -> i64 { - crate::fs::MetadataExt::ctime_nsec(&self.ext) + crate::filesystem::primitives::MetadataExt::ctime_nsec(&self.ext) } #[inline] fn blksize(&self) -> u64 { - crate::fs::MetadataExt::blksize(&self.ext) + crate::filesystem::primitives::MetadataExt::blksize(&self.ext) } #[inline] fn blocks(&self) -> u64 { - crate::fs::MetadataExt::blocks(&self.ext) + crate::filesystem::primitives::MetadataExt::blocks(&self.ext) } } @@ -365,17 +365,17 @@ impl MetadataExt for Metadata { impl MetadataExt for Metadata { #[inline] fn dev(&self) -> u64 { - crate::fs::MetadataExt::dev(&self.ext) + crate::filesystem::primitives::MetadataExt::dev(&self.ext) } #[inline] fn ino(&self) -> u64 { - crate::fs::MetadataExt::ino(&self.ext) + crate::filesystem::primitives::MetadataExt::ino(&self.ext) } #[inline] fn nlink(&self) -> u64 { - crate::fs::MetadataExt::nlink(&self.ext) + crate::filesystem::primitives::MetadataExt::nlink(&self.ext) } } diff --git a/crates/wasi/src/filesystem/primitives/mod.rs b/crates/wasi/src/filesystem/primitives/mod.rs index 2b77fab706f6..215c97d27ce8 100644 --- a/crates/wasi/src/filesystem/primitives/mod.rs +++ b/crates/wasi/src/filesystem/primitives/mod.rs @@ -1,5 +1,7 @@ //! Filesystem utilities. +#![allow(warnings)] + #[cfg(racy_asserts)] #[macro_use] pub(crate) mod assert_same_file; @@ -52,14 +54,18 @@ pub(crate) use file_path_by_searching::file_path_by_searching; pub(crate) use open_unchecked_error::*; #[cfg(not(windows))] -pub(crate) use super::rustix::fs::*; +mod rustix; +#[cfg(not(windows))] +pub(crate) use self::rustix::fs::*; +#[cfg(windows)] +mod windows; #[cfg(windows)] -pub(crate) use super::windows::fs::*; +pub(crate) use self::windows::fs::*; #[cfg(not(windows))] pub(crate) use read_dir::{read_dir_nofollow, read_dir_unchecked}; -pub use access::{access, AccessModes, AccessType}; +pub use access::{AccessModes, AccessType, access}; pub use canonicalize::canonicalize; pub use copy::copy; pub use create_dir::create_dir; @@ -87,7 +93,7 @@ pub use open_options::*; pub use permissions::Permissions; #[cfg(unix)] pub use permissions::PermissionsExt; -pub use read_dir::{read_base_dir, read_dir, ReadDir}; +pub use read_dir::{ReadDir, read_base_dir, read_dir}; pub use read_link::{read_link, read_link_contents}; pub use remove_dir::remove_dir; pub use remove_dir_all::remove_dir_all; diff --git a/crates/wasi/src/filesystem/primitives/open.rs b/crates/wasi/src/filesystem/primitives/open.rs index e193a8f8650a..070b98aeafd6 100644 --- a/crates/wasi/src/filesystem/primitives/open.rs +++ b/crates/wasi/src/filesystem/primitives/open.rs @@ -1,9 +1,9 @@ //! This defines `open`, the primary entrypoint to sandboxed file and directory //! opening. +use crate::filesystem::primitives::{OpenOptions, open_impl}; #[cfg(racy_asserts)] -use crate::fs::{file_path, open_unchecked, stat_unchecked, Metadata}; -use crate::fs::{open_impl, OpenOptions}; +use crate::filesystem::primitives::{Metadata, file_path, open_unchecked, stat_unchecked}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/open_ambient.rs b/crates/wasi/src/filesystem/primitives/open_ambient.rs index c874c3e5fb75..a22841c13e35 100644 --- a/crates/wasi/src/filesystem/primitives/open_ambient.rs +++ b/crates/wasi/src/filesystem/primitives/open_ambient.rs @@ -1,7 +1,7 @@ //! This defines `open_ambient`, for unsandboxed file opening. -use crate::fs::{open_ambient_impl, OpenOptions}; -use crate::AmbientAuthority; +use crate::filesystem::primitives::{OpenOptions, open_ambient_impl}; +use ambient_authority::AmbientAuthority; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/open_dir.rs b/crates/wasi/src/filesystem/primitives/open_dir.rs index ec6ab4c3d013..d0e66a2d54e9 100644 --- a/crates/wasi/src/filesystem/primitives/open_dir.rs +++ b/crates/wasi/src/filesystem/primitives/open_dir.rs @@ -2,8 +2,10 @@ //! path as a directory. #[allow(unused_imports)] -use crate::fs::open_unchecked; -use crate::fs::{dir_options, open, open_ambient_dir_impl, readdir_options, FollowSymlinks}; +use crate::filesystem::primitives::open_unchecked; +use crate::filesystem::primitives::{ + FollowSymlinks, dir_options, open, open_ambient_dir_impl, readdir_options, +}; use ambient_authority::AmbientAuthority; use std::path::{Component, Path}; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/open_options.rs b/crates/wasi/src/filesystem/primitives/open_options.rs index 3faec096e783..79d44a80b1ca 100644 --- a/crates/wasi/src/filesystem/primitives/open_options.rs +++ b/crates/wasi/src/filesystem/primitives/open_options.rs @@ -1,4 +1,4 @@ -use crate::fs::{FollowSymlinks, ImplOpenOptionsExt}; +use crate::filesystem::primitives::{FollowSymlinks, ImplOpenOptionsExt}; /// Options and flags which can be used to configure how a file is opened. /// diff --git a/crates/wasi/src/filesystem/primitives/permissions.rs b/crates/wasi/src/filesystem/primitives/permissions.rs index f20c36fc9d59..b6e141e9983e 100644 --- a/crates/wasi/src/filesystem/primitives/permissions.rs +++ b/crates/wasi/src/filesystem/primitives/permissions.rs @@ -1,5 +1,5 @@ #[cfg(not(windows))] -use crate::fs::ImplPermissionsExt; +use crate::filesystem::primitives::ImplPermissionsExt; #[cfg(unix)] use rustix::fs::RawMode; use std::{fs, io}; @@ -49,9 +49,9 @@ impl Permissions { #[allow(clippy::unnecessary_wraps)] fn _into_std(self, _file: &fs::File) -> io::Result { use std::os::unix::fs::PermissionsExt; - Ok(fs::Permissions::from_mode(crate::fs::PermissionsExt::mode( - &self.ext, - ))) + Ok(fs::Permissions::from_mode( + crate::filesystem::primitives::PermissionsExt::mode(&self.ext), + )) } #[cfg(target_os = "wasi")] @@ -111,19 +111,19 @@ pub trait PermissionsExt { impl PermissionsExt for Permissions { #[inline] fn mode(&self) -> u32 { - crate::fs::PermissionsExt::mode(&self.ext) + crate::filesystem::primitives::PermissionsExt::mode(&self.ext) } #[inline] fn set_mode(&mut self, mode: u32) { - crate::fs::PermissionsExt::set_mode(&mut self.ext, mode) + crate::filesystem::primitives::PermissionsExt::set_mode(&mut self.ext, mode) } #[inline] fn from_mode(mode: u32) -> Self { Self { readonly: ImplPermissionsExt::readonly(mode as RawMode), - ext: crate::fs::PermissionsExt::from_mode(mode), + ext: crate::filesystem::primitives::PermissionsExt::from_mode(mode), } } } diff --git a/crates/wasi/src/filesystem/primitives/read_dir.rs b/crates/wasi/src/filesystem/primitives/read_dir.rs index 9496e01e42d8..4652e6f49de0 100644 --- a/crates/wasi/src/filesystem/primitives/read_dir.rs +++ b/crates/wasi/src/filesystem/primitives/read_dir.rs @@ -1,4 +1,4 @@ -use crate::fs::{DirEntry, FollowSymlinks, ReadDirInner}; +use crate::filesystem::primitives::{DirEntry, FollowSymlinks, ReadDirInner}; use std::path::Path; use std::{fmt, fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/read_link.rs b/crates/wasi/src/filesystem/primitives/read_link.rs index f1e91ef83b1b..1cad434bceb5 100644 --- a/crates/wasi/src/filesystem/primitives/read_link.rs +++ b/crates/wasi/src/filesystem/primitives/read_link.rs @@ -1,9 +1,9 @@ //! This defines `read_link`, the primary entrypoint to sandboxed symbolic link //! dereferencing. -use crate::fs::{errors, read_link_impl}; +use crate::filesystem::primitives::{errors, read_link_impl}; #[cfg(racy_asserts)] -use crate::fs::{map_result, read_link_unchecked, stat, FollowSymlinks}; +use crate::filesystem::primitives::{FollowSymlinks, map_result, read_link_unchecked, stat}; use std::path::{Path, PathBuf}; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/remove_dir.rs b/crates/wasi/src/filesystem/primitives/remove_dir.rs index 5226792fbc27..f70a60c0c260 100644 --- a/crates/wasi/src/filesystem/primitives/remove_dir.rs +++ b/crates/wasi/src/filesystem/primitives/remove_dir.rs @@ -1,10 +1,10 @@ //! This defines `remove_dir`, the primary entrypoint to sandboxed file //! removal. -use crate::fs::remove_dir_impl; +use crate::filesystem::primitives::remove_dir_impl; #[cfg(racy_asserts)] -use crate::fs::{ - manually, map_result, remove_dir_unchecked, stat_unchecked, FollowSymlinks, Metadata, +use crate::filesystem::primitives::{ + FollowSymlinks, Metadata, manually, map_result, remove_dir_unchecked, stat_unchecked, }; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/remove_dir_all.rs b/crates/wasi/src/filesystem/primitives/remove_dir_all.rs index 1bec5a6187e4..75ccbdab02cd 100644 --- a/crates/wasi/src/filesystem/primitives/remove_dir_all.rs +++ b/crates/wasi/src/filesystem/primitives/remove_dir_all.rs @@ -1,4 +1,4 @@ -use crate::fs::remove_dir_all_impl; +use crate::filesystem::primitives::remove_dir_all_impl; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/remove_file.rs b/crates/wasi/src/filesystem/primitives/remove_file.rs index 70c68008720a..d82ed3177a83 100644 --- a/crates/wasi/src/filesystem/primitives/remove_file.rs +++ b/crates/wasi/src/filesystem/primitives/remove_file.rs @@ -1,10 +1,10 @@ //! This defines `remove_file`, the primary entrypoint to sandboxed file //! removal. -use crate::fs::remove_file_impl; +use crate::filesystem::primitives::remove_file_impl; #[cfg(racy_asserts)] -use crate::fs::{ - manually, map_result, remove_file_unchecked, stat_unchecked, FollowSymlinks, Metadata, +use crate::filesystem::primitives::{ + FollowSymlinks, Metadata, manually, map_result, remove_file_unchecked, stat_unchecked, }; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/remove_open_dir.rs b/crates/wasi/src/filesystem/primitives/remove_open_dir.rs index 0a87c3e66e69..71cd2434d043 100644 --- a/crates/wasi/src/filesystem/primitives/remove_open_dir.rs +++ b/crates/wasi/src/filesystem/primitives/remove_open_dir.rs @@ -1,4 +1,4 @@ -use crate::fs::{remove_open_dir_all_impl, remove_open_dir_impl}; +use crate::filesystem::primitives::{remove_open_dir_all_impl, remove_open_dir_impl}; use std::{fs, io}; /// Given an open directory handle, delete the directory. diff --git a/crates/wasi/src/filesystem/primitives/rename.rs b/crates/wasi/src/filesystem/primitives/rename.rs index 2af4ef95960c..cdbe8fea3165 100644 --- a/crates/wasi/src/filesystem/primitives/rename.rs +++ b/crates/wasi/src/filesystem/primitives/rename.rs @@ -1,15 +1,15 @@ //! This defines `rename`, the primary entrypoint to sandboxed renaming. +use crate::filesystem::primitives::rename_impl; #[cfg(all(racy_asserts, not(windows)))] -use crate::fs::append_dir_suffix; -use crate::fs::rename_impl; +use crate::filesystem::primitives::append_dir_suffix; use std::path::Path; use std::{fs, io}; #[cfg(racy_asserts)] use { crate::fs::{ - manually, map_result, path_requires_dir, rename_unchecked, stat_unchecked, FollowSymlinks, - Metadata, + FollowSymlinks, Metadata, manually, map_result, path_requires_dir, rename_unchecked, + stat_unchecked, }, std::path::PathBuf, }; diff --git a/crates/wasi/src/filesystem/primitives/reopen.rs b/crates/wasi/src/filesystem/primitives/reopen.rs index b19bbcd0ad2b..833ed0066232 100644 --- a/crates/wasi/src/filesystem/primitives/reopen.rs +++ b/crates/wasi/src/filesystem/primitives/reopen.rs @@ -1,6 +1,6 @@ //! Re-open a `fs::File` to produce an independent handle. -use crate::fs::{is_file_read_write, is_same_file, reopen_impl, OpenOptions}; +use crate::filesystem::primitives::{OpenOptions, is_file_read_write, is_same_file, reopen_impl}; use std::{fs, io}; /// Re-open an `fs::File` to produce an independent handle. diff --git a/crates/wasi/src/filesystem/primitives/rustix/darwin/fs/file_path.rs b/crates/wasi/src/filesystem/primitives/rustix/darwin/fs/file_path.rs index 377309bea730..7fbb3b954a35 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/darwin/fs/file_path.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/darwin/fs/file_path.rs @@ -2,7 +2,7 @@ //! library/std/src/sys/unix/fs.rs at revision //! 108e90ca78f052c0c1c49c42a22c85620be19712. -use crate::rustix::fs::file_path_by_ttyname_or_seaching; +use crate::filesystem::primitives::rustix::fs::file_path_by_ttyname_or_seaching; use rustix::fs::getpath; use std::ffi::OsString; use std::fs; diff --git a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/mod.rs b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/mod.rs index 2f1ef26170cb..23863313652e 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/mod.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/mod.rs @@ -7,7 +7,7 @@ mod set_permissions_impl; mod set_times_impl; mod stat_impl; -pub(crate) use crate::fs::manually::canonicalize as canonicalize_impl; +pub(crate) use crate::filesystem::primitives::manually::canonicalize as canonicalize_impl; pub(crate) use check::beneath_supported; pub(crate) use open_entry_impl::open_entry_impl; pub(crate) use open_impl::open_impl; diff --git a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/open_entry_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/open_entry_impl.rs index 2bf5645bd116..41e035f24ecb 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/open_entry_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/open_entry_impl.rs @@ -1,4 +1,4 @@ -use crate::fs::{open_impl, OpenOptions}; +use crate::filesystem::primitives::{open_impl, OpenOptions}; use std::ffi::OsStr; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/open_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/open_impl.rs index e7c8f843c87e..61d080db5054 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/open_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/open_impl.rs @@ -1,5 +1,5 @@ use super::super::super::fs::compute_oflags; -use crate::fs::{errors, manually, OpenOptions}; +use crate::filesystem::primitives::{errors, manually, OpenOptions}; use io_lifetimes::FromFd; use rustix::fs::{openat, Mode, OFlags, RawMode}; use std::path::Path; diff --git a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/remove_dir_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/remove_dir_impl.rs index d486a375d657..2198fbf42937 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/remove_dir_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/remove_dir_impl.rs @@ -1,4 +1,4 @@ -use crate::fs::via_parent; +use crate::filesystem::primitives::via_parent; use rustix::fs::{unlinkat, AtFlags}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/remove_file_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/remove_file_impl.rs index c49767b50fc3..b7310c2f5ce8 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/remove_file_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/remove_file_impl.rs @@ -1,4 +1,4 @@ -use crate::fs::via_parent; +use crate::filesystem::primitives::via_parent; use rustix::fs::{unlinkat, AtFlags}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/set_permissions_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/set_permissions_impl.rs index ab9bc31144ec..8fd0494afd1d 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/set_permissions_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/set_permissions_impl.rs @@ -1,4 +1,4 @@ -use crate::fs::{Permissions, PermissionsExt}; +use crate::filesystem::primitives::{Permissions, PermissionsExt}; use rustix::fs::{chmodat, AtFlags, Mode}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/set_times_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/set_times_impl.rs index 16b520047c6e..d9c89b324000 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/set_times_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/set_times_impl.rs @@ -1,4 +1,4 @@ -use crate::fs::{to_timespec, via_parent, SystemTimeSpec}; +use crate::filesystem::primitives::{to_timespec, via_parent, SystemTimeSpec}; use rustix::fs::{utimensat, AtFlags, Timestamps}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/stat_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/stat_impl.rs index aa4bbe651c2b..a51da309fa06 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/stat_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/stat_impl.rs @@ -1,4 +1,4 @@ -use crate::fs::{manually, FollowSymlinks, ImplMetadataExt, Metadata}; +use crate::filesystem::primitives::{manually, FollowSymlinks, ImplMetadataExt, Metadata}; use rustix::fs::{statat, AtFlags}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/access_unchecked.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/access_unchecked.rs index 8bbda62229c6..c9745a39d66c 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/access_unchecked.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/access_unchecked.rs @@ -1,4 +1,4 @@ -use crate::fs::{AccessType, FollowSymlinks}; +use crate::filesystem::primitives::{AccessType, FollowSymlinks}; use rustix::fs::{Access, AtFlags}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/copy_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/copy_impl.rs index c4e5be7591f4..008a6b9ecd7c 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/copy_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/copy_impl.rs @@ -2,7 +2,7 @@ // library/std/src/sys/unix/fs.rs at revision // 108e90ca78f052c0c1c49c42a22c85620be19712. -use crate::fs::{open, OpenOptions}; +use crate::filesystem::primitives::{OpenOptions, open}; #[cfg(any(target_os = "android", target_os = "linux"))] use rustix::fs::copy_file_range; #[cfg(any( @@ -13,8 +13,8 @@ use rustix::fs::copy_file_range; target_os = "visionos", ))] use rustix::fs::{ - copyfile_state_alloc, copyfile_state_free, copyfile_state_get_copied, copyfile_state_t, - fclonefileat, fcopyfile, CloneFlags, CopyfileFlags, + CloneFlags, CopyfileFlags, copyfile_state_alloc, copyfile_state_free, + copyfile_state_get_copied, copyfile_state_t, fclonefileat, fcopyfile, }; use std::path::Path; use std::{fs, io}; @@ -37,7 +37,7 @@ fn open_to_and_set_permissions( path: &Path, reader_metadata: fs::Metadata, ) -> io::Result<(fs::File, fs::Metadata)> { - use crate::fs::OpenOptionsExt; + use crate::filesystem::primitives::OpenOptionsExt; use std::os::unix::fs::PermissionsExt; let perm = reader_metadata.permissions(); diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/create_dir_unchecked.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/create_dir_unchecked.rs index 652162c76e26..246bed20da64 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/create_dir_unchecked.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/create_dir_unchecked.rs @@ -1,5 +1,5 @@ -use crate::fs::DirOptions; -use rustix::fs::{mkdirat, Mode, RawMode}; +use crate::filesystem::primitives::DirOptions; +use rustix::fs::{Mode, RawMode, mkdirat}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/dir_entry_inner.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/dir_entry_inner.rs index 5b82b624ec2f..bb3101c7e94e 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/dir_entry_inner.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/dir_entry_inner.rs @@ -1,4 +1,4 @@ -use crate::fs::{ +use crate::filesystem::primitives::{ FileType, FollowSymlinks, Metadata, MetadataExt, OpenOptions, ReadDir, ReadDirInner, }; use rustix::fs::DirEntry; @@ -44,7 +44,7 @@ impl DirEntryInner { #[inline] #[allow(clippy::unnecessary_wraps)] pub(crate) fn file_type(&self) -> io::Result { - use crate::fs::ImplFileTypeExt; + use crate::filesystem::primitives::ImplFileTypeExt; Ok(match self.rustix.file_type() { rustix::fs::FileType::Directory => FileType::dir(), diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/dir_options_ext.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/dir_options_ext.rs index 07b56bcf9afb..103b22496bae 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/dir_options_ext.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/dir_options_ext.rs @@ -13,7 +13,7 @@ impl DirOptionsExt { } } -impl crate::fs::DirBuilderExt for DirOptionsExt { +impl crate::filesystem::primitives::DirBuilderExt for DirOptionsExt { fn mode(&mut self, mode: u32) -> &mut Self { self.mode = mode; self diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/dir_utils.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/dir_utils.rs index 50011e71de8c..b0eab6f211d5 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/dir_utils.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/dir_utils.rs @@ -1,4 +1,4 @@ -use crate::fs::OpenOptions; +use crate::filesystem::primitives::OpenOptions; use ambient_authority::AmbientAuthority; use rustix::fs::OFlags; use std::ffi::{OsStr, OsString}; @@ -154,10 +154,12 @@ pub(crate) const fn target_o_path() -> OFlags { #[cfg(racy_asserts)] #[test] fn append_dir_suffix_tests() { - assert!(append_dir_suffix(Path::new("foo").to_path_buf()) - .display() - .to_string() - .ends_with('/')); + assert!( + append_dir_suffix(Path::new("foo").to_path_buf()) + .display() + .to_string() + .ends_with('/') + ); } #[test] diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/file_path.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/file_path.rs index f68d36f6b362..6d4f8a4764e3 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/file_path.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/file_path.rs @@ -1,4 +1,4 @@ -use crate::fs::file_path_by_searching; +use crate::filesystem::primitives::file_path_by_searching; #[cfg(not(any(target_os = "wasi", target_os = "fuchsia")))] use rustix::termios::ttyname; #[cfg(not(any(target_os = "wasi", target_os = "fuchsia")))] diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/file_type_ext.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/file_type_ext.rs index 643dc0dd2abb..72504cc1b404 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/file_type_ext.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/file_type_ext.rs @@ -1,4 +1,4 @@ -use crate::fs::FileType; +use crate::filesystem::primitives::FileType; use rustix::fs::RawMode; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/is_root_dir.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/is_root_dir.rs index 64a0838ef57b..933965058586 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/is_root_dir.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/is_root_dir.rs @@ -1,4 +1,4 @@ -use crate::fs::{Metadata, ReadDir}; +use crate::filesystem::primitives::{Metadata, ReadDir}; use std::{fs, io}; pub(crate) fn is_root_dir(dir: &fs::File, parent_iter: &ReadDir) -> io::Result { diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/is_same_file.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/is_same_file.rs index c6acf9cae26f..dc74f1d99c90 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/is_same_file.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/is_same_file.rs @@ -1,4 +1,4 @@ -use crate::fs::{Metadata, MetadataExt}; +use crate::filesystem::primitives::{Metadata, MetadataExt}; use std::{fs, io}; /// Determine if `a` and `b` refer to the same inode on the same device. diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/metadata_ext.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/metadata_ext.rs index b13ab13684be..c557f66a1c81 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/metadata_ext.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/metadata_ext.rs @@ -1,10 +1,10 @@ #![allow(clippy::useless_conversion)] -use crate::fs::{ImplFileTypeExt, ImplPermissionsExt, Metadata}; -use crate::time::{Duration, SystemClock, SystemTime}; -#[cfg(target_os = "linux")] -use rustix::fs::{makedev, Statx, StatxFlags}; +use crate::filesystem::primitives::{ImplFileTypeExt, ImplPermissionsExt, Metadata}; use rustix::fs::{RawMode, Stat}; +#[cfg(target_os = "linux")] +use rustix::fs::{Statx, StatxFlags, makedev}; +use std::time::{Duration, SystemTime}; use std::{fs, io}; #[derive(Debug, Clone)] @@ -246,16 +246,16 @@ impl ImplMetadataExt { #[allow(clippy::similar_names)] fn system_time_from_rustix(sec: i64, nsec: u64) -> Option { if sec >= 0 { - SystemClock::UNIX_EPOCH.checked_add(Duration::new(u64::try_from(sec).unwrap(), nsec as _)) + SystemTime::UNIX_EPOCH.checked_add(Duration::new(u64::try_from(sec).unwrap(), nsec as _)) } else { - SystemClock::UNIX_EPOCH + SystemTime::UNIX_EPOCH .checked_sub(Duration::new(u64::try_from(-sec).unwrap(), 0)) .map(|t| t.checked_add(Duration::new(0, nsec as u32))) .flatten() } } -impl crate::fs::MetadataExt for ImplMetadataExt { +impl crate::filesystem::primitives::MetadataExt for ImplMetadataExt { #[inline] fn dev(&self) -> u64 { self.dev @@ -355,7 +355,7 @@ impl crate::fs::MetadataExt for ImplMetadataExt { #[test] fn negative_time() { let system_time = system_time_from_rustix(-1, 1).unwrap(); - let d = SystemClock::UNIX_EPOCH.duration_since(system_time).unwrap(); + let d = SystemTime::UNIX_EPOCH.duration_since(system_time).unwrap(); assert_eq!(d.as_secs(), 0); if !cfg!(emulate_second_only_system) { assert_eq!(d.subsec_nanos(), 999999999); diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/mod.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/mod.rs index b6a0def2743c..00f210a88d2b 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/mod.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/mod.rs @@ -49,14 +49,14 @@ pub(crate) mod errors; target_os = "watchos", target_os = "visionos", ))] -pub(crate) use crate::rustix::darwin::fs::*; +pub(crate) use crate::filesystem::primitives::rustix::darwin::fs::*; #[cfg(target_os = "freebsd")] -pub(crate) use crate::rustix::freebsd::fs::*; +pub(crate) use crate::filesystem::primitives::rustix::freebsd::fs::*; #[cfg(any(target_os = "android", target_os = "linux"))] -pub(crate) use crate::rustix::linux::fs::*; +pub(crate) use crate::filesystem::primitives::rustix::linux::fs::*; #[cfg(not(any(target_os = "android", target_os = "linux", target_os = "freebsd")))] #[rustfmt::skip] -pub(crate) use crate::fs::{ +pub(crate) use crate::filesystem::primitives::{ manually::open_entry as open_entry_impl, manually::open as open_impl, manually::stat as stat_impl, @@ -97,7 +97,7 @@ pub(crate) use set_times_impl::set_times_impl; #[cfg(target_os = "freebsd")] pub(crate) use set_times_impl::set_times_impl as set_times_manually; #[rustfmt::skip] -pub(crate) use crate::fs::{ +pub(crate) use crate::filesystem::primitives::{ via_parent::access as access_impl, via_parent::hard_link as hard_link_impl, via_parent::create_dir as create_dir_impl, @@ -107,10 +107,10 @@ pub(crate) use crate::fs::{ remove_open_dir_by_searching as remove_open_dir_impl, }; #[cfg(not(target_os = "wasi"))] -pub(crate) use crate::fs::via_parent::set_symlink_permissions as set_symlink_permissions_impl; +pub(crate) use crate::filesystem::primitives::via_parent::set_symlink_permissions as set_symlink_permissions_impl; #[cfg(not(target_os = "freebsd"))] #[rustfmt::skip] -pub(crate) use crate::fs::{ +pub(crate) use crate::filesystem::primitives::{ via_parent::remove_dir as remove_dir_impl, via_parent::remove_file as remove_file_impl, }; diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/oflags.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/oflags.rs index d9349673b45a..aa7a4280ddcb 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/oflags.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/oflags.rs @@ -1,4 +1,4 @@ -use crate::fs::{target_o_path, FollowSymlinks, OpenOptions}; +use crate::filesystem::primitives::{target_o_path, FollowSymlinks, OpenOptions}; use rustix::fs::OFlags; use std::io; diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/open_unchecked.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/open_unchecked.rs index 3f75e0c6c683..0f1b786baf90 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/open_unchecked.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/open_unchecked.rs @@ -1,6 +1,6 @@ use super::compute_oflags; -use crate::fs::{stat_unchecked, OpenOptions, OpenUncheckedError}; -use crate::AmbientAuthority; +use crate::filesystem::primitives::{stat_unchecked, OpenOptions, OpenUncheckedError}; +use ambient_authority::AmbientAuthority; use io_lifetimes::AsFilelike; use rustix::fs::{openat, Mode, CWD}; use rustix::io; diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/permissions_ext.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/permissions_ext.rs index fef1cb44173c..dac5ea508bc1 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/permissions_ext.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/permissions_ext.rs @@ -1,4 +1,4 @@ -use crate::fs::Permissions; +use crate::filesystem::primitives::Permissions; use rustix::fs::RawMode; use std::fs; @@ -50,7 +50,7 @@ impl ImplPermissionsExt { } #[cfg(not(target_os = "wasi"))] -impl crate::fs::PermissionsExt for ImplPermissionsExt { +impl crate::filesystem::primitives::PermissionsExt for ImplPermissionsExt { fn mode(&self) -> u32 { self.mode as u32 } diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/read_dir_inner.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/read_dir_inner.rs index 20626987d9fb..5713bef317a0 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/read_dir_inner.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/read_dir_inner.rs @@ -1,4 +1,4 @@ -use crate::fs::{ +use crate::filesystem::primitives::{ open_dir_for_reading, open_dir_for_reading_unchecked, open_entry_impl, read_dir_unchecked, remove_dir_unchecked, remove_file_unchecked, stat_unchecked, DirEntryInner, FollowSymlinks, Metadata, OpenOptions, ReadDir, diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/remove_dir_all_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/remove_dir_all_impl.rs index cf2ed1db4c0e..cfb0373f7dd5 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/remove_dir_all_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/remove_dir_all_impl.rs @@ -1,4 +1,4 @@ -use crate::fs::{ +use crate::filesystem::primitives::{ read_dir_nofollow, read_dir_unchecked, remove_dir, remove_file, remove_open_dir, stat, FollowSymlinks, ReadDir, }; diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/remove_open_dir_by_searching.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/remove_open_dir_by_searching.rs index a08cde8d44ca..28900758602d 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/remove_open_dir_by_searching.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/remove_open_dir_by_searching.rs @@ -1,4 +1,4 @@ -use crate::fs::{errors, is_root_dir, read_dir_unchecked, FollowSymlinks, Metadata}; +use crate::filesystem::primitives::{errors, is_root_dir, read_dir_unchecked, FollowSymlinks, Metadata}; use std::path::Component; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/reopen_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/reopen_impl.rs index e010a4d36b06..e74f0fe9b0f5 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/reopen_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/reopen_impl.rs @@ -1,5 +1,5 @@ -use crate::fs::{open_unchecked, OpenOptions}; -use crate::rustix::fs::file_path; +use crate::filesystem::primitives::rustix::fs::file_path; +use crate::filesystem::primitives::{OpenOptions, open_unchecked}; use io_lifetimes::AsFilelike; use rustix::fs::CWD; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/set_permissions_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/set_permissions_impl.rs index ff32e3f4efed..17eb89809b78 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/set_permissions_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/set_permissions_impl.rs @@ -1,4 +1,4 @@ -use crate::fs::{open, OpenOptions, Permissions}; +use crate::filesystem::primitives::{open, OpenOptions, Permissions}; use rustix::fs::{fchmod, Mode}; use rustix::io::Errno; #[cfg(unix)] diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/set_symlink_permissions_unchecked.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/set_symlink_permissions_unchecked.rs index fb3a8f02af64..efeed3b5000c 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/set_symlink_permissions_unchecked.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/set_symlink_permissions_unchecked.rs @@ -1,6 +1,6 @@ -use crate::fs::Permissions; +use crate::filesystem::primitives::Permissions; #[cfg(unix)] -use crate::fs::PermissionsExt; +use crate::filesystem::primitives::PermissionsExt; use rustix::fs::{chmodat, AtFlags, Mode}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/set_times_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/set_times_impl.rs index 3b3619c6016b..7deaf92bb409 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/set_times_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/set_times_impl.rs @@ -1,7 +1,7 @@ //! This module consists of helper types and functions for dealing //! with setting the file times. -use crate::fs::{open, OpenOptions, SystemTimeSpec}; +use crate::filesystem::primitives::{open, OpenOptions, SystemTimeSpec}; use rustix::io::Errno; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/stat_unchecked.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/stat_unchecked.rs index f10b926be847..fe75b692708f 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/stat_unchecked.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/stat_unchecked.rs @@ -1,4 +1,4 @@ -use crate::fs::{FollowSymlinks, ImplMetadataExt, Metadata}; +use crate::filesystem::primitives::{FollowSymlinks, ImplMetadataExt, Metadata}; use rustix::fs::{statat, AtFlags}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/times.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/times.rs index 555ebe631558..84f0caa896be 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/times.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/times.rs @@ -1,9 +1,9 @@ -use crate::fs::SystemTimeSpec; -use crate::time::SystemClock; +use crate::filesystem::primitives::SystemTimeSpec; use io_lifetimes::BorrowedFd; -use rustix::fs::{utimensat, AtFlags, Timestamps, UTIME_NOW, UTIME_OMIT}; +use rustix::fs::{AtFlags, Timestamps, UTIME_NOW, UTIME_OMIT, utimensat}; use rustix::time::Timespec; use std::path::Path; +use std::time::SystemTime; use std::{fs, io}; #[allow(clippy::useless_conversion)] @@ -18,7 +18,7 @@ pub(crate) fn to_timespec(ft: Option) -> io::Result { tv_nsec: UTIME_NOW.into(), }, Some(SystemTimeSpec::Absolute(ft)) => { - let duration = ft.duration_since(SystemClock::UNIX_EPOCH).unwrap(); + let duration = ft.duration_since(SystemTime::UNIX_EPOCH).unwrap(); let nanoseconds = duration.subsec_nanos(); assert_ne!(i64::from(nanoseconds), i64::from(UTIME_OMIT)); assert_ne!(i64::from(nanoseconds), i64::from(UTIME_NOW)); diff --git a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/canonicalize_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/canonicalize_impl.rs index 87dd9ebe654d..6b9551d6e519 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/canonicalize_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/canonicalize_impl.rs @@ -1,8 +1,8 @@ //! Path canonicalization using `/proc/self/fd`. use super::procfs::get_path_from_proc_self_fd; -use crate::fs::OpenOptionsExt; -use crate::fs::{manually, open_beneath, FollowSymlinks, OpenOptions}; +use crate::filesystem::primitives::OpenOptionsExt; +use crate::filesystem::primitives::{manually, open_beneath, FollowSymlinks, OpenOptions}; use rustix::fs::OFlags; use std::path::{Component, Path, PathBuf}; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/file_metadata.rs b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/file_metadata.rs index 4f24a5dcbf28..e0bbd312ff38 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/file_metadata.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/file_metadata.rs @@ -1,4 +1,4 @@ -use crate::fs::{ImplMetadataExt, Metadata}; +use crate::filesystem::primitives::{ImplMetadataExt, Metadata}; use rustix::fs::{statat, AtFlags}; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering::Relaxed; diff --git a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/mod.rs b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/mod.rs index fd85ffce99c3..454487f72244 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/mod.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/mod.rs @@ -13,12 +13,12 @@ mod set_times_impl; mod stat_impl; #[cfg(target_os = "android")] -pub(crate) use crate::fs::manually::canonicalize as canonicalize_impl; +pub(crate) use crate::filesystem::primitives::manually::canonicalize as canonicalize_impl; #[cfg(target_os = "android")] -pub(crate) use crate::fs::manually::open_entry as open_entry_impl; +pub(crate) use crate::filesystem::primitives::manually::open_entry as open_entry_impl; #[cfg(target_os = "android")] -pub(crate) use crate::fs::manually::stat as stat_impl; -pub(crate) use crate::fs::via_parent::set_times_nofollow as set_times_nofollow_impl; +pub(crate) use crate::filesystem::primitives::manually::stat as stat_impl; +pub(crate) use crate::filesystem::primitives::via_parent::set_times_nofollow as set_times_nofollow_impl; #[cfg(target_os = "linux")] pub(crate) use canonicalize_impl::canonicalize_impl; pub(crate) use file_path::file_path; diff --git a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/open_entry_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/open_entry_impl.rs index b5bc0f6970d9..97ba390ac8ad 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/open_entry_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/open_entry_impl.rs @@ -1,4 +1,4 @@ -use crate::fs::{manually, open_beneath, OpenOptions}; +use crate::filesystem::primitives::{manually, open_beneath, OpenOptions}; use std::ffi::OsStr; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/open_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/open_impl.rs index edb4c8bd2554..6326020225d1 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/open_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/open_impl.rs @@ -8,16 +8,16 @@ //! On older Linux, fall back to `manually::open`. #[cfg(racy_asserts)] -use crate::fs::is_same_file; -use crate::fs::{manually, OpenOptions}; +use crate::filesystem::primitives::is_same_file; +use crate::filesystem::primitives::{OpenOptions, manually}; use std::path::Path; use std::{fs, io}; #[cfg(target_os = "linux")] use { super::super::super::fs::compute_oflags, - crate::fs::errors, + crate::filesystem::primitives::errors, io_lifetimes::FromFd, - rustix::fs::{openat2, Mode, OFlags, RawMode, ResolveFlags}, + rustix::fs::{Mode, OFlags, RawMode, ResolveFlags, openat2}, rustix::path::Arg, std::sync::atomic::AtomicBool, std::sync::atomic::Ordering::Relaxed, diff --git a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/procfs.rs b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/procfs.rs index aa87a0a8268c..27c5fb5bde49 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/procfs.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/procfs.rs @@ -6,8 +6,8 @@ //! is mounted, with actual `procfs`, and without any additional mount points //! on top of the paths we open. -use crate::fs::OpenOptionsExt; -use crate::fs::{ +use crate::filesystem::primitives::OpenOptionsExt; +use crate::filesystem::primitives::{ errors, open, read_link_unchecked, set_times_follow_unchecked, OpenOptions, SystemTimeSpec, }; use io_lifetimes::{AsFd, AsFilelike}; diff --git a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/set_permissions_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/set_permissions_impl.rs index e799bb43ff01..296f6cbf2d4a 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/set_permissions_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/set_permissions_impl.rs @@ -1,5 +1,5 @@ use super::procfs::set_permissions_through_proc_self_fd; -use crate::fs::{open, OpenOptions, Permissions}; +use crate::filesystem::primitives::{open, OpenOptions, Permissions}; use rustix::fs::{fchmod, Mode, RawMode}; use std::os::unix::fs::PermissionsExt; use std::path::Path; diff --git a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/set_times_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/set_times_impl.rs index 5844c7d7d118..8424f21f3284 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/set_times_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/set_times_impl.rs @@ -2,7 +2,7 @@ //! with setting the file times specific to Linux. use super::procfs::set_times_through_proc_self_fd; -use crate::fs::{open, OpenOptions, SystemTimeSpec}; +use crate::filesystem::primitives::{open, OpenOptions, SystemTimeSpec}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/stat_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/stat_impl.rs index df358a019f1e..7682733c5bda 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/stat_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/stat_impl.rs @@ -3,7 +3,7 @@ //! `fstat` to perform a fast sandboxed `stat`. use super::file_metadata::file_metadata; -use crate::fs::{manually, open_beneath, FollowSymlinks, Metadata, OpenOptions}; +use crate::filesystem::primitives::{manually, open_beneath, FollowSymlinks, Metadata, OpenOptions}; use rustix::fs::OFlags; use std::path::Path; use std::{fs, io}; @@ -15,7 +15,7 @@ pub(crate) fn stat_impl( path: &Path, follow: FollowSymlinks, ) -> io::Result { - use crate::fs::{stat_unchecked, OpenOptionsExt}; + use crate::filesystem::primitives::{stat_unchecked, OpenOptionsExt}; use std::path::Component; // Optimization: if path has exactly one component and it's not ".." or diff --git a/crates/wasi/src/filesystem/primitives/set_permissions.rs b/crates/wasi/src/filesystem/primitives/set_permissions.rs index bc701945a007..f878d2cdde75 100644 --- a/crates/wasi/src/filesystem/primitives/set_permissions.rs +++ b/crates/wasi/src/filesystem/primitives/set_permissions.rs @@ -1,9 +1,11 @@ //! This defines `set_permissions`, the primary entrypoint to sandboxed //! filesystem permissions modification. +use crate::filesystem::primitives::{ + Permissions, set_permissions_impl, set_symlink_permissions_impl, +}; #[cfg(racy_asserts)] -use crate::fs::{map_result, stat, stat_unchecked, FollowSymlinks, Metadata}; -use crate::fs::{set_permissions_impl, set_symlink_permissions_impl, Permissions}; +use crate::filesystem::primitives::{FollowSymlinks, Metadata, map_result, stat, stat_unchecked}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/set_times.rs b/crates/wasi/src/filesystem/primitives/set_times.rs index 44fca3b7681e..b3c72f161a56 100644 --- a/crates/wasi/src/filesystem/primitives/set_times.rs +++ b/crates/wasi/src/filesystem/primitives/set_times.rs @@ -3,7 +3,7 @@ //! //! TODO: `check_set_times` etc. -use crate::fs::{set_times_impl, set_times_nofollow_impl, SystemTimeSpec}; +use crate::filesystem::primitives::{SystemTimeSpec, set_times_impl, set_times_nofollow_impl}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/stat.rs b/crates/wasi/src/filesystem/primitives/stat.rs index 4aff5db5f0e9..984e400bb5f3 100644 --- a/crates/wasi/src/filesystem/primitives/stat.rs +++ b/crates/wasi/src/filesystem/primitives/stat.rs @@ -1,8 +1,8 @@ //! This defines `stat`, the primary entrypoint to sandboxed metadata querying. +use crate::filesystem::primitives::{FollowSymlinks, Metadata, stat_impl}; #[cfg(racy_asserts)] -use crate::fs::{canonicalize, map_result, stat_unchecked}; -use crate::fs::{stat_impl, FollowSymlinks, Metadata}; +use crate::filesystem::primitives::{canonicalize, map_result, stat_unchecked}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/symlink.rs b/crates/wasi/src/filesystem/primitives/symlink.rs index d43d809e99d4..1b3a9b60ffa6 100644 --- a/crates/wasi/src/filesystem/primitives/symlink.rs +++ b/crates/wasi/src/filesystem/primitives/symlink.rs @@ -1,13 +1,13 @@ //! This defines `symlink`, the primary entrypoint to sandboxed symlink //! creation. -use crate::fs::errors; +use crate::filesystem::primitives::errors; #[cfg(all(racy_asserts, not(windows)))] -use crate::fs::symlink_unchecked; +use crate::filesystem::primitives::symlink_unchecked; #[cfg(racy_asserts)] -use crate::fs::{canonicalize, manually, map_result, stat_unchecked, FollowSymlinks, Metadata}; +use crate::filesystem::primitives::{FollowSymlinks, Metadata, canonicalize, manually, map_result, stat_unchecked}; #[cfg(all(racy_asserts, windows))] -use crate::fs::{symlink_dir_unchecked, symlink_file_unchecked}; +use crate::filesystem::primitives::{symlink_dir_unchecked, symlink_file_unchecked}; use std::path::Path; use std::{fs, io}; @@ -32,7 +32,7 @@ pub fn symlink(old_path: &Path, new_start: &fs::File, new_path: &Path) -> io::Re #[cfg(not(windows))] fn write_symlink_impl(old_path: &Path, new_start: &fs::File, new_path: &Path) -> io::Result<()> { - use crate::fs::symlink_impl; + use crate::filesystem::primitives::symlink_impl; #[cfg(racy_asserts)] let stat_before = stat_unchecked(new_start, new_path, FollowSymlinks::No); @@ -73,7 +73,7 @@ pub fn symlink_contents, Q: AsRef>( #[cfg(windows)] #[inline] pub fn symlink_file(old_path: &Path, new_start: &fs::File, new_path: &Path) -> io::Result<()> { - use crate::fs::symlink_file_impl; + use crate::filesystem::primitives::symlink_file_impl; // As above, don't allow creating symlinks to absolute paths. if old_path.has_root() { @@ -108,7 +108,7 @@ pub fn symlink_file(old_path: &Path, new_start: &fs::File, new_path: &Path) -> i #[cfg(windows)] #[inline] pub fn symlink_dir(old_path: &Path, new_start: &fs::File, new_path: &Path) -> io::Result<()> { - use crate::fs::symlink_dir_impl; + use crate::filesystem::primitives::symlink_dir_impl; // As above, don't allow creating symlinks to absolute paths. if old_path.has_root() { diff --git a/crates/wasi/src/filesystem/primitives/system_time_spec.rs b/crates/wasi/src/filesystem/primitives/system_time_spec.rs index 5f2a3bd75bee..64bd445436ac 100644 --- a/crates/wasi/src/filesystem/primitives/system_time_spec.rs +++ b/crates/wasi/src/filesystem/primitives/system_time_spec.rs @@ -1,4 +1,4 @@ -use crate::time::SystemTime; +use std::time::SystemTime; /// A value for specifying a time. #[derive(Debug)] @@ -20,9 +20,7 @@ impl SystemTimeSpec { pub fn from_std(std: fs_set_times::SystemTimeSpec) -> Self { match std { fs_set_times::SystemTimeSpec::SymbolicNow => Self::SymbolicNow, - fs_set_times::SystemTimeSpec::Absolute(time) => { - Self::Absolute(SystemTime::from_std(time)) - } + fs_set_times::SystemTimeSpec::Absolute(time) => Self::Absolute(time), } } @@ -32,7 +30,7 @@ impl SystemTimeSpec { pub const fn into_std(self) -> fs_set_times::SystemTimeSpec { match self { Self::SymbolicNow => fs_set_times::SystemTimeSpec::SymbolicNow, - Self::Absolute(time) => fs_set_times::SystemTimeSpec::Absolute(time.into_std()), + Self::Absolute(time) => fs_set_times::SystemTimeSpec::Absolute(time), } } } diff --git a/crates/wasi/src/filesystem/primitives/via_parent/access.rs b/crates/wasi/src/filesystem/primitives/via_parent/access.rs index 591c6221a294..93a3f7a778d2 100644 --- a/crates/wasi/src/filesystem/primitives/via_parent/access.rs +++ b/crates/wasi/src/filesystem/primitives/via_parent/access.rs @@ -1,5 +1,5 @@ use super::open_parent; -use crate::fs::{access_unchecked, AccessType, FollowSymlinks, MaybeOwnedFile}; +use crate::filesystem::primitives::{AccessType, FollowSymlinks, MaybeOwnedFile, access_unchecked}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/via_parent/create_dir.rs b/crates/wasi/src/filesystem/primitives/via_parent/create_dir.rs index b1700f118d03..900420de1e02 100644 --- a/crates/wasi/src/filesystem/primitives/via_parent/create_dir.rs +++ b/crates/wasi/src/filesystem/primitives/via_parent/create_dir.rs @@ -1,5 +1,7 @@ use super::open_parent; -use crate::fs::{create_dir_unchecked, strip_dir_suffix, DirOptions, MaybeOwnedFile}; +use crate::filesystem::primitives::{ + DirOptions, MaybeOwnedFile, create_dir_unchecked, strip_dir_suffix, +}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/via_parent/hard_link.rs b/crates/wasi/src/filesystem/primitives/via_parent/hard_link.rs index 649df9c768cb..b725ca7f6f1f 100644 --- a/crates/wasi/src/filesystem/primitives/via_parent/hard_link.rs +++ b/crates/wasi/src/filesystem/primitives/via_parent/hard_link.rs @@ -1,5 +1,5 @@ use super::open_parent; -use crate::fs::{hard_link_unchecked, MaybeOwnedFile}; +use crate::filesystem::primitives::{MaybeOwnedFile, hard_link_unchecked}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/via_parent/open_parent.rs b/crates/wasi/src/filesystem/primitives/via_parent/open_parent.rs index fe52e6c7d562..974e4ebfd565 100644 --- a/crates/wasi/src/filesystem/primitives/via_parent/open_parent.rs +++ b/crates/wasi/src/filesystem/primitives/via_parent/open_parent.rs @@ -2,7 +2,7 @@ /// It opens the parent directory of the given path, and returns the basename, /// so that all the `via_parent` functions need to do is make sure they /// don't follow symlinks in the basename. -use crate::fs::{errors, open_dir, path_requires_dir, MaybeOwnedFile}; +use crate::filesystem::primitives::{MaybeOwnedFile, errors, open_dir, path_requires_dir}; use std::ffi::OsStr; use std::io; use std::path::{Component, Path}; diff --git a/crates/wasi/src/filesystem/primitives/via_parent/read_link.rs b/crates/wasi/src/filesystem/primitives/via_parent/read_link.rs index 0ca8d8c30680..edd03a4bafd0 100644 --- a/crates/wasi/src/filesystem/primitives/via_parent/read_link.rs +++ b/crates/wasi/src/filesystem/primitives/via_parent/read_link.rs @@ -1,5 +1,5 @@ use super::open_parent; -use crate::fs::{read_link_unchecked, MaybeOwnedFile}; +use crate::filesystem::primitives::{MaybeOwnedFile, read_link_unchecked}; use std::path::{Path, PathBuf}; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/via_parent/remove_dir.rs b/crates/wasi/src/filesystem/primitives/via_parent/remove_dir.rs index 94d36b1913d7..9a46149df73a 100644 --- a/crates/wasi/src/filesystem/primitives/via_parent/remove_dir.rs +++ b/crates/wasi/src/filesystem/primitives/via_parent/remove_dir.rs @@ -1,5 +1,5 @@ use super::open_parent; -use crate::fs::{remove_dir_unchecked, MaybeOwnedFile}; +use crate::filesystem::primitives::{MaybeOwnedFile, remove_dir_unchecked}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/via_parent/remove_file.rs b/crates/wasi/src/filesystem/primitives/via_parent/remove_file.rs index 84a898efba1f..7b06f727e4d7 100644 --- a/crates/wasi/src/filesystem/primitives/via_parent/remove_file.rs +++ b/crates/wasi/src/filesystem/primitives/via_parent/remove_file.rs @@ -1,5 +1,5 @@ use super::open_parent; -use crate::fs::{remove_file_unchecked, MaybeOwnedFile}; +use crate::filesystem::primitives::{MaybeOwnedFile, remove_file_unchecked}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/via_parent/rename.rs b/crates/wasi/src/filesystem/primitives/via_parent/rename.rs index aeb345e566fa..0e62eb36258b 100644 --- a/crates/wasi/src/filesystem/primitives/via_parent/rename.rs +++ b/crates/wasi/src/filesystem/primitives/via_parent/rename.rs @@ -1,7 +1,7 @@ use super::open_parent; +use crate::filesystem::primitives::{MaybeOwnedFile, rename_unchecked, strip_dir_suffix}; #[cfg(unix)] -use crate::fs::{append_dir_suffix, path_has_trailing_slash}; -use crate::fs::{rename_unchecked, strip_dir_suffix, MaybeOwnedFile}; +use crate::filesystem::primitives::{append_dir_suffix, path_has_trailing_slash}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/via_parent/set_permissions.rs b/crates/wasi/src/filesystem/primitives/via_parent/set_permissions.rs index 9c0c5a085e85..556d94a4468d 100644 --- a/crates/wasi/src/filesystem/primitives/via_parent/set_permissions.rs +++ b/crates/wasi/src/filesystem/primitives/via_parent/set_permissions.rs @@ -1,5 +1,5 @@ use super::open_parent; -use crate::fs::{set_permissions_unchecked, MaybeOwnedFile, Permissions}; +use crate::filesystem::primitives::{set_permissions_unchecked, MaybeOwnedFile, Permissions}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/via_parent/set_symlink_permissions.rs b/crates/wasi/src/filesystem/primitives/via_parent/set_symlink_permissions.rs index 99adf7668bc1..bd90141a0e77 100644 --- a/crates/wasi/src/filesystem/primitives/via_parent/set_symlink_permissions.rs +++ b/crates/wasi/src/filesystem/primitives/via_parent/set_symlink_permissions.rs @@ -1,5 +1,7 @@ use super::open_parent; -use crate::fs::{set_symlink_permissions_unchecked, MaybeOwnedFile, Permissions}; +use crate::filesystem::primitives::{ + MaybeOwnedFile, Permissions, set_symlink_permissions_unchecked, +}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/via_parent/set_times_nofollow.rs b/crates/wasi/src/filesystem/primitives/via_parent/set_times_nofollow.rs index ea880726e24e..f01befd96efd 100644 --- a/crates/wasi/src/filesystem/primitives/via_parent/set_times_nofollow.rs +++ b/crates/wasi/src/filesystem/primitives/via_parent/set_times_nofollow.rs @@ -1,5 +1,5 @@ use super::open_parent; -use crate::fs::{set_times_nofollow_unchecked, MaybeOwnedFile, SystemTimeSpec}; +use crate::filesystem::primitives::{MaybeOwnedFile, SystemTimeSpec, set_times_nofollow_unchecked}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/via_parent/symlink.rs b/crates/wasi/src/filesystem/primitives/via_parent/symlink.rs index 13599f31d485..6ea53afc8f43 100644 --- a/crates/wasi/src/filesystem/primitives/via_parent/symlink.rs +++ b/crates/wasi/src/filesystem/primitives/via_parent/symlink.rs @@ -1,5 +1,5 @@ use super::open_parent; -use crate::fs::MaybeOwnedFile; +use crate::filesystem::primitives::MaybeOwnedFile; use std::path::Path; use std::{fs, io}; @@ -7,7 +7,7 @@ use std::{fs, io}; /// then calling `symlink_unchecked` on the last component. #[cfg(not(windows))] pub(crate) fn symlink(old_path: &Path, new_start: &fs::File, new_path: &Path) -> io::Result<()> { - use crate::fs::symlink_unchecked; + use crate::filesystem::primitives::symlink_unchecked; let new_start = MaybeOwnedFile::borrowed(new_start); let (new_dir, new_basename) = open_parent(new_start, new_path)?; @@ -23,7 +23,7 @@ pub(crate) fn symlink_file( new_start: &fs::File, new_path: &Path, ) -> io::Result<()> { - use crate::fs::symlink_file_unchecked; + use crate::filesystem::primitives::symlink_file_unchecked; let new_start = MaybeOwnedFile::borrowed(new_start); let (new_dir, new_basename) = open_parent(new_start, new_path)?; @@ -39,7 +39,7 @@ pub(crate) fn symlink_dir( new_start: &fs::File, new_path: &Path, ) -> io::Result<()> { - use crate::fs::symlink_dir_unchecked; + use crate::filesystem::primitives::symlink_dir_unchecked; let new_start = MaybeOwnedFile::borrowed(new_start); let (new_dir, new_basename) = open_parent(new_start, new_path)?; diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/access_unchecked.rs b/crates/wasi/src/filesystem/primitives/windows/fs/access_unchecked.rs index 0e6864b9f272..6a0ae4d5f5fd 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/access_unchecked.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/access_unchecked.rs @@ -1,4 +1,4 @@ -use crate::fs::{open, AccessType, FollowSymlinks, OpenOptions}; +use crate::filesystem::primitives::{open, AccessType, FollowSymlinks, OpenOptions}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/copy.rs b/crates/wasi/src/filesystem/primitives/windows/fs/copy.rs index b0b710b4dcdb..bd0a4c1346ea 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/copy.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/copy.rs @@ -1,5 +1,5 @@ use super::get_path::get_path; -use crate::fs::{open, OpenOptions}; +use crate::filesystem::primitives::{open, OpenOptions}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/create_dir_unchecked.rs b/crates/wasi/src/filesystem/primitives/windows/fs/create_dir_unchecked.rs index 4cd445e10750..442b38916999 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/create_dir_unchecked.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/create_dir_unchecked.rs @@ -1,5 +1,5 @@ use super::get_path::concatenate; -use crate::fs::DirOptions; +use crate::filesystem::primitives::DirOptions; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/create_file_at_w.rs b/crates/wasi/src/filesystem/primitives/windows/fs/create_file_at_w.rs index 392bc8866313..be0b7c6649ca 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/create_file_at_w.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/create_file_at_w.rs @@ -5,16 +5,16 @@ use std::os::windows::io::HandleOrInvalid; use std::ptr::null_mut; use windows_sys::Wdk::Foundation::OBJECT_ATTRIBUTES; use windows_sys::Wdk::Storage::FileSystem::{ - NtCreateFile, FILE_CREATE, FILE_DELETE_ON_CLOSE, FILE_NON_DIRECTORY_FILE, - FILE_NO_INTERMEDIATE_BUFFERING, FILE_OPEN, FILE_OPEN_FOR_BACKUP_INTENT, FILE_OPEN_IF, - FILE_OPEN_REPARSE_POINT, FILE_OVERWRITE, FILE_OVERWRITE_IF, FILE_RANDOM_ACCESS, - FILE_SEQUENTIAL_ONLY, FILE_SYNCHRONOUS_IO_NONALERT, FILE_WRITE_THROUGH, + FILE_CREATE, FILE_DELETE_ON_CLOSE, FILE_NO_INTERMEDIATE_BUFFERING, FILE_NON_DIRECTORY_FILE, + FILE_OPEN, FILE_OPEN_FOR_BACKUP_INTENT, FILE_OPEN_IF, FILE_OPEN_REPARSE_POINT, FILE_OVERWRITE, + FILE_OVERWRITE_IF, FILE_RANDOM_ACCESS, FILE_SEQUENTIAL_ONLY, FILE_SYNCHRONOUS_IO_NONALERT, + FILE_WRITE_THROUGH, NtCreateFile, }; use windows_sys::Win32::Foundation::{ - RtlNtStatusToDosError, SetLastError, ERROR_ALREADY_EXISTS, ERROR_FILE_EXISTS, - ERROR_INVALID_NAME, ERROR_INVALID_PARAMETER, ERROR_NOT_SUPPORTED, GENERIC_ALL, GENERIC_READ, - GENERIC_WRITE, HANDLE, INVALID_HANDLE_VALUE, STATUS_OBJECT_NAME_COLLISION, STATUS_PENDING, - STATUS_SUCCESS, SUCCESS, UNICODE_STRING, + ERROR_ALREADY_EXISTS, ERROR_FILE_EXISTS, ERROR_INVALID_NAME, ERROR_INVALID_PARAMETER, + ERROR_NOT_SUPPORTED, GENERIC_ALL, GENERIC_READ, GENERIC_WRITE, HANDLE, INVALID_HANDLE_VALUE, + RtlNtStatusToDosError, STATUS_OBJECT_NAME_COLLISION, STATUS_PENDING, STATUS_SUCCESS, SUCCESS, + SetLastError, UNICODE_STRING, }; use windows_sys::Win32::Foundation::{OBJ_CASE_INSENSITIVE, OBJ_INHERIT}; use windows_sys::Win32::Security::{ @@ -24,23 +24,23 @@ use windows_sys::Win32::Security::{ use windows_sys::Win32::Storage::FileSystem::{ CREATE_ALWAYS, CREATE_NEW, DELETE, FILE_ATTRIBUTE_ARCHIVE, FILE_ATTRIBUTE_COMPRESSED, FILE_ATTRIBUTE_DEVICE, FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_EA, FILE_ATTRIBUTE_ENCRYPTED, - FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_INTEGRITY_STREAM, FILE_ATTRIBUTE_NORMAL, - FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, FILE_ATTRIBUTE_NO_SCRUB_DATA, FILE_ATTRIBUTE_OFFLINE, + FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_INTEGRITY_STREAM, FILE_ATTRIBUTE_NO_SCRUB_DATA, + FILE_ATTRIBUTE_NORMAL, FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, FILE_ATTRIBUTE_OFFLINE, FILE_ATTRIBUTE_PINNED, FILE_ATTRIBUTE_READONLY, FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS, FILE_ATTRIBUTE_RECALL_ON_OPEN, FILE_ATTRIBUTE_REPARSE_POINT, FILE_ATTRIBUTE_SPARSE_FILE, FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_TEMPORARY, FILE_ATTRIBUTE_UNPINNED, - FILE_ATTRIBUTE_VIRTUAL, FILE_CREATION_DISPOSITION, FILE_FLAGS_AND_ATTRIBUTES, - FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_DELETE_ON_CLOSE, FILE_FLAG_NO_BUFFERING, - FILE_FLAG_OPEN_NO_RECALL, FILE_FLAG_OPEN_REPARSE_POINT, FILE_FLAG_OVERLAPPED, - FILE_FLAG_POSIX_SEMANTICS, FILE_FLAG_RANDOM_ACCESS, FILE_FLAG_SEQUENTIAL_SCAN, - FILE_FLAG_SESSION_AWARE, FILE_FLAG_WRITE_THROUGH, FILE_READ_ATTRIBUTES, FILE_SHARE_MODE, + FILE_ATTRIBUTE_VIRTUAL, FILE_CREATION_DISPOSITION, FILE_FLAG_BACKUP_SEMANTICS, + FILE_FLAG_DELETE_ON_CLOSE, FILE_FLAG_NO_BUFFERING, FILE_FLAG_OPEN_NO_RECALL, + FILE_FLAG_OPEN_REPARSE_POINT, FILE_FLAG_OVERLAPPED, FILE_FLAG_POSIX_SEMANTICS, + FILE_FLAG_RANDOM_ACCESS, FILE_FLAG_SEQUENTIAL_SCAN, FILE_FLAG_SESSION_AWARE, + FILE_FLAG_WRITE_THROUGH, FILE_FLAGS_AND_ATTRIBUTES, FILE_READ_ATTRIBUTES, FILE_SHARE_MODE, OPEN_ALWAYS, OPEN_EXISTING, SECURITY_CONTEXT_TRACKING, SECURITY_EFFECTIVE_ONLY, SECURITY_SQOS_PRESENT, SYNCHRONIZE, TRUNCATE_EXISTING, }; +use windows_sys::Win32::System::IO::IO_STATUS_BLOCK; use windows_sys::Win32::System::WindowsProgramming::{ - FILE_OPENED, FILE_OPEN_NO_RECALL, FILE_OPEN_REMOTE_INSTANCE, FILE_OVERWRITTEN, + FILE_OPEN_NO_RECALL, FILE_OPEN_REMOTE_INSTANCE, FILE_OPENED, FILE_OVERWRITTEN, }; -use windows_sys::Win32::System::IO::IO_STATUS_BLOCK; // All currently known `FILE_ATTRIBUTE_*` constants, according to // windows-sys' documentation. @@ -262,8 +262,9 @@ pub unsafe fn CreateFileAtW( SetLastError(RtlNtStatusToDosError(status)); } } else if (dwcreationdisposition == CREATE_ALWAYS - && iostatusblock.Information == FILE_OVERWRITTEN as _) - || (dwcreationdisposition == OPEN_ALWAYS && iostatusblock.Information == FILE_OPENED as _) + && iostatusblock.Information == FILE_OVERWRITTEN as usize) + || (dwcreationdisposition == OPEN_ALWAYS + && iostatusblock.Information == FILE_OPENED as usize) { // Set `ERROR_ALREADY_EXISTS` according to the table for // `dwCreationDisposition` in the [`CreateFileW` docs]. diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/dir_entry_inner.rs b/crates/wasi/src/filesystem/primitives/windows/fs/dir_entry_inner.rs index 1b93c848a53e..b7960b76c5f7 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/dir_entry_inner.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/dir_entry_inner.rs @@ -1,10 +1,10 @@ use super::open_options_to_std; -use crate::ambient_authority; -use crate::fs::OpenOptionsExt; -use crate::fs::{ - open, open_ambient_dir, FileType, FollowSymlinks, ImplFileTypeExt, Metadata, OpenOptions, - ReadDir, ReadDirInner, +use crate::filesystem::primitives::OpenOptionsExt; +use crate::filesystem::primitives::{ + FileType, FollowSymlinks, ImplFileTypeExt, Metadata, OpenOptions, ReadDir, ReadDirInner, open, + open_ambient_dir, }; +use ambient_authority::ambient_authority; use std::ffi::OsString; use std::{fmt, fs, io}; use windows_sys::Win32::Storage::FileSystem::{ @@ -114,7 +114,9 @@ impl fmt::Debug for DirEntryInner { } #[doc(hidden)] -impl crate::fs::_WindowsDirEntryExt for crate::fs::DirEntry { +impl crate::filesystem::primitives::_WindowsDirEntryExt + for crate::filesystem::primitives::DirEntry +{ #[inline] fn full_metadata(&self) -> io::Result { DirEntryInner::full_metadata(&self.inner) diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/dir_utils.rs b/crates/wasi/src/filesystem/primitives/windows/fs/dir_utils.rs index 5a0241bfba50..c44ed0048ed3 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/dir_utils.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/dir_utils.rs @@ -1,6 +1,6 @@ -use crate::fs::OpenOptionsExt; -use crate::fs::{errors, OpenOptions}; -use crate::AmbientAuthority; +use crate::filesystem::primitives::OpenOptionsExt; +use crate::filesystem::primitives::{errors, OpenOptions}; +use ambient_authority::AmbientAuthority; use std::ffi::OsString; use std::ops::Deref; use std::os::windows::ffi::{OsStrExt, OsStringExt}; diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/file_type_ext.rs b/crates/wasi/src/filesystem/primitives/windows/fs/file_type_ext.rs index f7d2a8fe1cba..e98b5e2d6767 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/file_type_ext.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/file_type_ext.rs @@ -1,4 +1,4 @@ -use crate::fs::FileType; +use crate::filesystem::primitives::FileType; use std::{fs, io}; /// A type that implements `FileTypeExt` for this platform. @@ -98,7 +98,9 @@ impl ImplFileTypeExt { } #[doc(hidden)] -impl crate::fs::_WindowsFileTypeExt for crate::fs::FileType { +impl crate::filesystem::primitives::_WindowsFileTypeExt + for crate::filesystem::primitives::FileType +{ #[inline] fn is_block_device(&self) -> bool { false diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/is_same_file.rs b/crates/wasi/src/filesystem/primitives/windows/fs/is_same_file.rs index 9bf7aba4d575..e14325650a1c 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/is_same_file.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/is_same_file.rs @@ -1,6 +1,6 @@ -use crate::fs::ImplMetadataExt; +use crate::filesystem::primitives::ImplMetadataExt; #[cfg(windows_by_handle)] -use crate::fs::Metadata; +use crate::filesystem::primitives::Metadata; use std::{fs, io}; /// Determine if `a` and `b` refer to the same inode on the same device. @@ -15,7 +15,7 @@ pub(crate) fn is_same_file(a: &fs::File, b: &fs::File) -> io::Result { #[cfg(windows_by_handle)] #[allow(dead_code)] pub(crate) fn is_same_file_metadata(a: &Metadata, b: &Metadata) -> io::Result { - use crate::fs::MetadataExt; + use crate::filesystem::primitives::MetadataExt; Ok(a.volume_serial_number() == b.volume_serial_number() && a.file_index() == b.file_index()) } diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/metadata_ext.rs b/crates/wasi/src/filesystem/primitives/windows/fs/metadata_ext.rs index ed692855bedc..6f1add8547da 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/metadata_ext.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/metadata_ext.rs @@ -1,6 +1,6 @@ #![allow(clippy::useless_conversion)] -use crate::fs::MetadataExt; +use crate::filesystem::primitives::MetadataExt; use std::{fs, io}; #[derive(Debug, Clone)] @@ -185,7 +185,7 @@ impl MetadataExt for ImplMetadataExt { } #[doc(hidden)] -impl crate::fs::_WindowsByHandle for crate::fs::Metadata { +impl crate::filesystem::primitives::_WindowsByHandle for crate::filesystem::primitives::Metadata { #[inline] fn file_attributes(&self) -> u32 { self.ext.file_attributes diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/mod.rs b/crates/wasi/src/filesystem/primitives/windows/fs/mod.rs index 0de735adbda1..ed36211e3441 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/mod.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/mod.rs @@ -33,7 +33,7 @@ mod symlink_unchecked; pub(crate) mod errors; #[rustfmt::skip] -pub(crate) use crate::fs::{ +pub(crate) use crate::filesystem::primitives::{ manually::canonicalize as canonicalize_impl, via_parent::access as access_impl, via_parent::hard_link as hard_link_impl, diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/oflags.rs b/crates/wasi/src/filesystem/primitives/windows/fs/oflags.rs index b78133c0cb17..e01a7f183113 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/oflags.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/oflags.rs @@ -1,4 +1,4 @@ -use crate::fs::{FollowSymlinks, OpenOptions, OpenOptionsExt}; +use crate::filesystem::primitives::{FollowSymlinks, OpenOptions, OpenOptionsExt}; use std::fs; use windows_sys::Win32::Storage::FileSystem::{ FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, FILE_FLAG_WRITE_THROUGH, diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/open_impl.rs b/crates/wasi/src/filesystem/primitives/windows/fs/open_impl.rs index a2695e8ae473..887f6241aa1f 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/open_impl.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/open_impl.rs @@ -1,4 +1,4 @@ -use crate::fs::{manually, OpenOptions}; +use crate::filesystem::primitives::{manually, OpenOptions}; use std::ffi::OsStr; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/open_options_ext.rs b/crates/wasi/src/filesystem/primitives/windows/fs/open_options_ext.rs index 8eb57917b644..1a5de4d62196 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/open_options_ext.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/open_options_ext.rs @@ -1,6 +1,6 @@ #![allow(unsafe_code)] -use crate::fs::OpenOptions; +use crate::filesystem::primitives::OpenOptions; use std::io; use std::ptr::null_mut; use windows_sys::Win32::Foundation::{ERROR_INVALID_PARAMETER, GENERIC_READ, GENERIC_WRITE}; diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/open_unchecked.rs b/crates/wasi/src/filesystem/primitives/windows/fs/open_unchecked.rs index 36243b0b693f..1e04ce7dda39 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/open_unchecked.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/open_unchecked.rs @@ -4,11 +4,11 @@ use super::create_file_at_w::CreateFileAtW; use super::{open_options_to_std, prepare_open_options_for_open}; -use crate::fs::{ - errors, file_path, get_access_mode, get_creation_mode, get_flags_and_attributes, - FollowSymlinks, OpenOptions, OpenUncheckedError, SymlinkKind, +use crate::filesystem::primitives::{ + FollowSymlinks, OpenOptions, OpenUncheckedError, SymlinkKind, errors, file_path, + get_access_mode, get_creation_mode, get_flags_and_attributes, }; -use crate::{ambient_authority, AmbientAuthority}; +use ambient_authority::{AmbientAuthority, ambient_authority}; use std::ffi::OsStr; use std::os::windows::ffi::OsStrExt; use std::os::windows::fs::MetadataExt; diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/read_dir_inner.rs b/crates/wasi/src/filesystem/primitives/windows/fs/read_dir_inner.rs index 1f87eb9756e7..3ea05e88351d 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/read_dir_inner.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/read_dir_inner.rs @@ -1,5 +1,5 @@ use super::get_path::concatenate; -use crate::fs::{open_dir, DirEntryInner, FollowSymlinks}; +use crate::filesystem::primitives::{open_dir, DirEntryInner, FollowSymlinks}; use std::path::{Component, Path}; use std::{fmt, fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/read_link_impl.rs b/crates/wasi/src/filesystem/primitives/windows/fs/read_link_impl.rs index d63e8135ad6d..284b1327c2cf 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/read_link_impl.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/read_link_impl.rs @@ -1,4 +1,4 @@ -use crate::fs::{open, FollowSymlinks, OpenOptions, OpenOptionsExt}; +use crate::filesystem::primitives::{open, FollowSymlinks, OpenOptions, OpenOptionsExt}; use std::path::{Path, PathBuf}; use std::{fs, io}; use windows_sys::Win32::Storage::FileSystem::{ diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/remove_dir_all_impl.rs b/crates/wasi/src/filesystem/primitives/windows/fs/remove_dir_all_impl.rs index 22dbd4155194..60f7caba13c3 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/remove_dir_all_impl.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/remove_dir_all_impl.rs @@ -1,5 +1,5 @@ use super::get_path::get_path; -use crate::fs::{open_dir, open_dir_nofollow, remove_dir, stat, FollowSymlinks}; +use crate::filesystem::primitives::{open_dir, open_dir_nofollow, remove_dir, stat, FollowSymlinks}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/reopen_impl.rs b/crates/wasi/src/filesystem/primitives/windows/fs/reopen_impl.rs index 31c865241dfa..bd95ef15c75c 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/reopen_impl.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/reopen_impl.rs @@ -1,4 +1,4 @@ -use crate::fs::{get_access_mode, get_flags_and_attributes, OpenOptions}; +use crate::filesystem::primitives::{get_access_mode, get_flags_and_attributes, OpenOptions}; use io_lifetimes::AsHandle; use std::{fs, io}; use windows_sys::Win32::Foundation::{GENERIC_READ, GENERIC_WRITE}; diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/set_permissions_unchecked.rs b/crates/wasi/src/filesystem/primitives/windows/fs/set_permissions_unchecked.rs index a91f36956fa4..4ade872b9206 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/set_permissions_unchecked.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/set_permissions_unchecked.rs @@ -1,5 +1,5 @@ use super::get_path::concatenate; -use crate::fs::Permissions; +use crate::filesystem::primitives::Permissions; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/set_symlink_permissions_unchecked.rs b/crates/wasi/src/filesystem/primitives/windows/fs/set_symlink_permissions_unchecked.rs index e7a4919e6c8d..8351c2e95ec8 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/set_symlink_permissions_unchecked.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/set_symlink_permissions_unchecked.rs @@ -1,5 +1,5 @@ use super::get_path::concatenate; -use crate::fs::Permissions; +use crate::filesystem::primitives::Permissions; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/set_times_impl.rs b/crates/wasi/src/filesystem/primitives/windows/fs/set_times_impl.rs index 83d0e3383d9a..99f7252ce084 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/set_times_impl.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/set_times_impl.rs @@ -1,4 +1,4 @@ -use crate::fs::{open, OpenOptions, OpenOptionsExt, SystemTimeSpec}; +use crate::filesystem::primitives::{open, OpenOptions, OpenOptionsExt, SystemTimeSpec}; use std::path::Path; use std::{fs, io}; use windows_sys::Win32::Storage::FileSystem::{ diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/stat_unchecked.rs b/crates/wasi/src/filesystem/primitives/windows/fs/stat_unchecked.rs index e0b3d41514bc..6eb32ebe4892 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/stat_unchecked.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/stat_unchecked.rs @@ -1,5 +1,5 @@ -use crate::fs::OpenOptionsExt; -use crate::fs::{open_unchecked, FollowSymlinks, Metadata, OpenOptions}; +use crate::filesystem::primitives::OpenOptionsExt; +use crate::filesystem::primitives::{open_unchecked, FollowSymlinks, Metadata, OpenOptions}; use std::path::Path; use std::{fs, io}; use windows_sys::Win32::Storage::FileSystem::{ From f611129df28c1166c6b78d5e14cf906d4844483f Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 27 Aug 2026 11:20:30 -0700 Subject: [PATCH 03/34] Run `cargo fmt` over cap-primitives code --- .../src/filesystem/primitives/file_path_by_searching.rs | 2 +- crates/wasi/src/filesystem/primitives/follow_symlinks.rs | 6 +----- crates/wasi/src/filesystem/primitives/open.rs | 2 +- crates/wasi/src/filesystem/primitives/read_link.rs | 2 +- crates/wasi/src/filesystem/primitives/rename.rs | 2 +- .../src/filesystem/primitives/rustix/freebsd/fs/check.rs | 2 +- .../primitives/rustix/freebsd/fs/open_entry_impl.rs | 2 +- .../filesystem/primitives/rustix/freebsd/fs/open_impl.rs | 4 ++-- .../primitives/rustix/freebsd/fs/remove_dir_impl.rs | 2 +- .../primitives/rustix/freebsd/fs/remove_file_impl.rs | 2 +- .../primitives/rustix/freebsd/fs/set_permissions_impl.rs | 2 +- .../primitives/rustix/freebsd/fs/set_times_impl.rs | 4 ++-- .../filesystem/primitives/rustix/freebsd/fs/stat_impl.rs | 4 ++-- .../filesystem/primitives/rustix/fs/hard_link_unchecked.rs | 2 +- .../primitives/rustix/fs/is_file_read_write_impl.rs | 2 +- crates/wasi/src/filesystem/primitives/rustix/fs/oflags.rs | 2 +- .../src/filesystem/primitives/rustix/fs/open_unchecked.rs | 4 ++-- .../src/filesystem/primitives/rustix/fs/read_dir_inner.rs | 6 +++--- .../filesystem/primitives/rustix/fs/remove_dir_all_impl.rs | 4 ++-- .../filesystem/primitives/rustix/fs/remove_dir_unchecked.rs | 2 +- .../primitives/rustix/fs/remove_file_unchecked.rs | 2 +- .../primitives/rustix/fs/remove_open_dir_by_searching.rs | 4 +++- .../filesystem/primitives/rustix/fs/set_permissions_impl.rs | 4 ++-- .../rustix/fs/set_symlink_permissions_unchecked.rs | 2 +- .../src/filesystem/primitives/rustix/fs/set_times_impl.rs | 6 +++--- .../src/filesystem/primitives/rustix/fs/stat_unchecked.rs | 4 ++-- .../primitives/rustix/linux/fs/canonicalize_impl.rs | 2 +- .../filesystem/primitives/rustix/linux/fs/file_metadata.rs | 2 +- .../primitives/rustix/linux/fs/open_entry_impl.rs | 2 +- .../src/filesystem/primitives/rustix/linux/fs/procfs.rs | 4 ++-- .../primitives/rustix/linux/fs/set_permissions_impl.rs | 4 ++-- .../filesystem/primitives/rustix/linux/fs/set_times_impl.rs | 6 +++--- .../src/filesystem/primitives/rustix/linux/fs/stat_impl.rs | 6 ++++-- crates/wasi/src/filesystem/primitives/set_permissions.rs | 4 ++-- crates/wasi/src/filesystem/primitives/symlink.rs | 4 +++- .../src/filesystem/primitives/via_parent/set_permissions.rs | 2 +- .../filesystem/primitives/windows/fs/access_unchecked.rs | 2 +- crates/wasi/src/filesystem/primitives/windows/fs/copy.rs | 2 +- .../wasi/src/filesystem/primitives/windows/fs/dir_utils.rs | 2 +- .../wasi/src/filesystem/primitives/windows/fs/open_impl.rs | 2 +- .../src/filesystem/primitives/windows/fs/read_dir_inner.rs | 2 +- .../src/filesystem/primitives/windows/fs/read_link_impl.rs | 2 +- .../filesystem/primitives/windows/fs/remove_dir_all_impl.rs | 4 +++- .../src/filesystem/primitives/windows/fs/reopen_impl.rs | 2 +- .../src/filesystem/primitives/windows/fs/set_times_impl.rs | 2 +- .../src/filesystem/primitives/windows/fs/stat_unchecked.rs | 2 +- 46 files changed, 71 insertions(+), 67 deletions(-) diff --git a/crates/wasi/src/filesystem/primitives/file_path_by_searching.rs b/crates/wasi/src/filesystem/primitives/file_path_by_searching.rs index 244ee70a8f72..2f2a45433e38 100644 --- a/crates/wasi/src/filesystem/primitives/file_path_by_searching.rs +++ b/crates/wasi/src/filesystem/primitives/file_path_by_searching.rs @@ -1,5 +1,5 @@ use crate::filesystem::primitives::{ - is_root_dir, open_dir_unchecked, read_dir_unchecked, FollowSymlinks, MaybeOwnedFile, Metadata, + FollowSymlinks, MaybeOwnedFile, Metadata, is_root_dir, open_dir_unchecked, read_dir_unchecked, }; use std::fs; use std::path::{Component, PathBuf}; diff --git a/crates/wasi/src/filesystem/primitives/follow_symlinks.rs b/crates/wasi/src/filesystem/primitives/follow_symlinks.rs index b6f1853c9b70..de24fd346522 100644 --- a/crates/wasi/src/filesystem/primitives/follow_symlinks.rs +++ b/crates/wasi/src/filesystem/primitives/follow_symlinks.rs @@ -21,10 +21,6 @@ impl FollowSymlinks { /// to a `FollowSymlinks`. #[inline] pub const fn follow(follow: bool) -> Self { - if follow { - Self::Yes - } else { - Self::No - } + if follow { Self::Yes } else { Self::No } } } diff --git a/crates/wasi/src/filesystem/primitives/open.rs b/crates/wasi/src/filesystem/primitives/open.rs index 070b98aeafd6..ad351fc67f32 100644 --- a/crates/wasi/src/filesystem/primitives/open.rs +++ b/crates/wasi/src/filesystem/primitives/open.rs @@ -1,9 +1,9 @@ //! This defines `open`, the primary entrypoint to sandboxed file and directory //! opening. -use crate::filesystem::primitives::{OpenOptions, open_impl}; #[cfg(racy_asserts)] use crate::filesystem::primitives::{Metadata, file_path, open_unchecked, stat_unchecked}; +use crate::filesystem::primitives::{OpenOptions, open_impl}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/read_link.rs b/crates/wasi/src/filesystem/primitives/read_link.rs index 1cad434bceb5..b804513a67ce 100644 --- a/crates/wasi/src/filesystem/primitives/read_link.rs +++ b/crates/wasi/src/filesystem/primitives/read_link.rs @@ -1,9 +1,9 @@ //! This defines `read_link`, the primary entrypoint to sandboxed symbolic link //! dereferencing. -use crate::filesystem::primitives::{errors, read_link_impl}; #[cfg(racy_asserts)] use crate::filesystem::primitives::{FollowSymlinks, map_result, read_link_unchecked, stat}; +use crate::filesystem::primitives::{errors, read_link_impl}; use std::path::{Path, PathBuf}; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/rename.rs b/crates/wasi/src/filesystem/primitives/rename.rs index cdbe8fea3165..7b2e720aa262 100644 --- a/crates/wasi/src/filesystem/primitives/rename.rs +++ b/crates/wasi/src/filesystem/primitives/rename.rs @@ -1,8 +1,8 @@ //! This defines `rename`, the primary entrypoint to sandboxed renaming. -use crate::filesystem::primitives::rename_impl; #[cfg(all(racy_asserts, not(windows)))] use crate::filesystem::primitives::append_dir_suffix; +use crate::filesystem::primitives::rename_impl; use std::path::Path; use std::{fs, io}; #[cfg(racy_asserts)] diff --git a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/check.rs b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/check.rs index a8eaddc97d31..6e7c70605175 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/check.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/check.rs @@ -1,5 +1,5 @@ use rustix::cstr; -use rustix::fs::{openat, statat, AtFlags, Mode, OFlags, CWD}; +use rustix::fs::{AtFlags, CWD, Mode, OFlags, openat, statat}; use rustix::io::Errno; use std::sync::atomic::{AtomicBool, Ordering::Relaxed}; diff --git a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/open_entry_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/open_entry_impl.rs index 41e035f24ecb..dd314e40d7f5 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/open_entry_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/open_entry_impl.rs @@ -1,4 +1,4 @@ -use crate::filesystem::primitives::{open_impl, OpenOptions}; +use crate::filesystem::primitives::{OpenOptions, open_impl}; use std::ffi::OsStr; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/open_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/open_impl.rs index 61d080db5054..2d8fe8dfcd29 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/open_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/open_impl.rs @@ -1,7 +1,7 @@ use super::super::super::fs::compute_oflags; -use crate::filesystem::primitives::{errors, manually, OpenOptions}; +use crate::filesystem::primitives::{OpenOptions, errors, manually}; use io_lifetimes::FromFd; -use rustix::fs::{openat, Mode, OFlags, RawMode}; +use rustix::fs::{Mode, OFlags, RawMode, openat}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/remove_dir_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/remove_dir_impl.rs index 2198fbf42937..e4a440dbfbee 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/remove_dir_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/remove_dir_impl.rs @@ -1,5 +1,5 @@ use crate::filesystem::primitives::via_parent; -use rustix::fs::{unlinkat, AtFlags}; +use rustix::fs::{AtFlags, unlinkat}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/remove_file_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/remove_file_impl.rs index b7310c2f5ce8..5c50e640976e 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/remove_file_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/remove_file_impl.rs @@ -1,5 +1,5 @@ use crate::filesystem::primitives::via_parent; -use rustix::fs::{unlinkat, AtFlags}; +use rustix::fs::{AtFlags, unlinkat}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/set_permissions_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/set_permissions_impl.rs index 8fd0494afd1d..22d20f9c1791 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/set_permissions_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/set_permissions_impl.rs @@ -1,5 +1,5 @@ use crate::filesystem::primitives::{Permissions, PermissionsExt}; -use rustix::fs::{chmodat, AtFlags, Mode}; +use rustix::fs::{AtFlags, Mode, chmodat}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/set_times_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/set_times_impl.rs index d9c89b324000..f318d42ad699 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/set_times_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/set_times_impl.rs @@ -1,5 +1,5 @@ -use crate::filesystem::primitives::{to_timespec, via_parent, SystemTimeSpec}; -use rustix::fs::{utimensat, AtFlags, Timestamps}; +use crate::filesystem::primitives::{SystemTimeSpec, to_timespec, via_parent}; +use rustix::fs::{AtFlags, Timestamps, utimensat}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/stat_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/stat_impl.rs index a51da309fa06..12993601186d 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/stat_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/stat_impl.rs @@ -1,5 +1,5 @@ -use crate::filesystem::primitives::{manually, FollowSymlinks, ImplMetadataExt, Metadata}; -use rustix::fs::{statat, AtFlags}; +use crate::filesystem::primitives::{FollowSymlinks, ImplMetadataExt, Metadata, manually}; +use rustix::fs::{AtFlags, statat}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/hard_link_unchecked.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/hard_link_unchecked.rs index a9f4bccee38e..7b280797b987 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/hard_link_unchecked.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/hard_link_unchecked.rs @@ -1,4 +1,4 @@ -use rustix::fs::{linkat, AtFlags}; +use rustix::fs::{AtFlags, linkat}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/is_file_read_write_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/is_file_read_write_impl.rs index ff4388f69ecf..3c6c4c63e5dd 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/is_file_read_write_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/is_file_read_write_impl.rs @@ -1,5 +1,5 @@ use rustix::fd::{AsFd, BorrowedFd}; -use rustix::fs::{fcntl_getfl, OFlags}; +use rustix::fs::{OFlags, fcntl_getfl}; use std::{fs, io}; #[inline] diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/oflags.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/oflags.rs index aa7a4280ddcb..9953d015b99b 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/oflags.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/oflags.rs @@ -1,4 +1,4 @@ -use crate::filesystem::primitives::{target_o_path, FollowSymlinks, OpenOptions}; +use crate::filesystem::primitives::{FollowSymlinks, OpenOptions, target_o_path}; use rustix::fs::OFlags; use std::io; diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/open_unchecked.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/open_unchecked.rs index 0f1b786baf90..19e26b906f9d 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/open_unchecked.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/open_unchecked.rs @@ -1,8 +1,8 @@ use super::compute_oflags; -use crate::filesystem::primitives::{stat_unchecked, OpenOptions, OpenUncheckedError}; +use crate::filesystem::primitives::{OpenOptions, OpenUncheckedError, stat_unchecked}; use ambient_authority::AmbientAuthority; use io_lifetimes::AsFilelike; -use rustix::fs::{openat, Mode, CWD}; +use rustix::fs::{CWD, Mode, openat}; use rustix::io; use std::fs; use std::path::Path; diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/read_dir_inner.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/read_dir_inner.rs index 5713bef317a0..6ec549d2f4ea 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/read_dir_inner.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/read_dir_inner.rs @@ -1,7 +1,7 @@ use crate::filesystem::primitives::{ - open_dir_for_reading, open_dir_for_reading_unchecked, open_entry_impl, read_dir_unchecked, - remove_dir_unchecked, remove_file_unchecked, stat_unchecked, DirEntryInner, FollowSymlinks, - Metadata, OpenOptions, ReadDir, + DirEntryInner, FollowSymlinks, Metadata, OpenOptions, ReadDir, open_dir_for_reading, + open_dir_for_reading_unchecked, open_entry_impl, read_dir_unchecked, remove_dir_unchecked, + remove_file_unchecked, stat_unchecked, }; use io_extras::os::rustix::{AsRawFd, FromRawFd, RawFd}; use io_lifetimes::AsFd; diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/remove_dir_all_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/remove_dir_all_impl.rs index cfb0373f7dd5..38288843f677 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/remove_dir_all_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/remove_dir_all_impl.rs @@ -1,6 +1,6 @@ use crate::filesystem::primitives::{ - read_dir_nofollow, read_dir_unchecked, remove_dir, remove_file, remove_open_dir, stat, - FollowSymlinks, ReadDir, + FollowSymlinks, ReadDir, read_dir_nofollow, read_dir_unchecked, remove_dir, remove_file, + remove_open_dir, stat, }; use std::path::{Component, Path}; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/remove_dir_unchecked.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/remove_dir_unchecked.rs index 5899bb3cd169..6106566847d5 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/remove_dir_unchecked.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/remove_dir_unchecked.rs @@ -1,4 +1,4 @@ -use rustix::fs::{unlinkat, AtFlags}; +use rustix::fs::{AtFlags, unlinkat}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/remove_file_unchecked.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/remove_file_unchecked.rs index 7e6f97dc8b32..3b1adaa1a044 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/remove_file_unchecked.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/remove_file_unchecked.rs @@ -1,4 +1,4 @@ -use rustix::fs::{unlinkat, AtFlags}; +use rustix::fs::{AtFlags, unlinkat}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/remove_open_dir_by_searching.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/remove_open_dir_by_searching.rs index 28900758602d..006ca70c0d2c 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/remove_open_dir_by_searching.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/remove_open_dir_by_searching.rs @@ -1,4 +1,6 @@ -use crate::filesystem::primitives::{errors, is_root_dir, read_dir_unchecked, FollowSymlinks, Metadata}; +use crate::filesystem::primitives::{ + FollowSymlinks, Metadata, errors, is_root_dir, read_dir_unchecked, +}; use std::path::Component; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/set_permissions_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/set_permissions_impl.rs index 17eb89809b78..ab3d8ede015f 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/set_permissions_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/set_permissions_impl.rs @@ -1,5 +1,5 @@ -use crate::filesystem::primitives::{open, OpenOptions, Permissions}; -use rustix::fs::{fchmod, Mode}; +use crate::filesystem::primitives::{OpenOptions, Permissions, open}; +use rustix::fs::{Mode, fchmod}; use rustix::io::Errno; #[cfg(unix)] use std::os::unix::fs::PermissionsExt; diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/set_symlink_permissions_unchecked.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/set_symlink_permissions_unchecked.rs index efeed3b5000c..c1854e072c3e 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/set_symlink_permissions_unchecked.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/set_symlink_permissions_unchecked.rs @@ -1,7 +1,7 @@ use crate::filesystem::primitives::Permissions; #[cfg(unix)] use crate::filesystem::primitives::PermissionsExt; -use rustix::fs::{chmodat, AtFlags, Mode}; +use rustix::fs::{AtFlags, Mode, chmodat}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/set_times_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/set_times_impl.rs index 7deaf92bb409..183534396159 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/set_times_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/set_times_impl.rs @@ -1,7 +1,7 @@ //! This module consists of helper types and functions for dealing //! with setting the file times. -use crate::filesystem::primitives::{open, OpenOptions, SystemTimeSpec}; +use crate::filesystem::primitives::{OpenOptions, SystemTimeSpec, open}; use rustix::io::Errno; use std::path::Path; use std::{fs, io}; @@ -20,7 +20,7 @@ pub(crate) fn set_times_impl( &file, atime.map(SystemTimeSpec::into_std), mtime.map(SystemTimeSpec::into_std), - ) + ); } Err(err) => match Errno::from_io_error(&err) { Some(Errno::ACCESS) | Some(Errno::ISDIR) => (), @@ -35,7 +35,7 @@ pub(crate) fn set_times_impl( &file, atime.map(SystemTimeSpec::into_std), mtime.map(SystemTimeSpec::into_std), - ) + ); } Err(err) => match Errno::from_io_error(&err) { Some(Errno::ACCESS) => (), diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/stat_unchecked.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/stat_unchecked.rs index fe75b692708f..25bf59a62036 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/stat_unchecked.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/stat_unchecked.rs @@ -1,10 +1,10 @@ use crate::filesystem::primitives::{FollowSymlinks, ImplMetadataExt, Metadata}; -use rustix::fs::{statat, AtFlags}; +use rustix::fs::{AtFlags, statat}; use std::path::Path; use std::{fs, io}; #[cfg(target_os = "linux")] -use rustix::fs::{statx, StatxFlags}; +use rustix::fs::{StatxFlags, statx}; #[cfg(target_os = "linux")] use std::sync::atomic::{AtomicU8, Ordering}; diff --git a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/canonicalize_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/canonicalize_impl.rs index 6b9551d6e519..8f0a9d05d385 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/canonicalize_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/canonicalize_impl.rs @@ -2,7 +2,7 @@ use super::procfs::get_path_from_proc_self_fd; use crate::filesystem::primitives::OpenOptionsExt; -use crate::filesystem::primitives::{manually, open_beneath, FollowSymlinks, OpenOptions}; +use crate::filesystem::primitives::{FollowSymlinks, OpenOptions, manually, open_beneath}; use rustix::fs::OFlags; use std::path::{Component, Path, PathBuf}; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/file_metadata.rs b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/file_metadata.rs index e0bbd312ff38..b02b95c3b765 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/file_metadata.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/file_metadata.rs @@ -1,5 +1,5 @@ use crate::filesystem::primitives::{ImplMetadataExt, Metadata}; -use rustix::fs::{statat, AtFlags}; +use rustix::fs::{AtFlags, statat}; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering::Relaxed; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/open_entry_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/open_entry_impl.rs index 97ba390ac8ad..7a5831158ef3 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/open_entry_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/open_entry_impl.rs @@ -1,4 +1,4 @@ -use crate::filesystem::primitives::{manually, open_beneath, OpenOptions}; +use crate::filesystem::primitives::{OpenOptions, manually, open_beneath}; use std::ffi::OsStr; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/procfs.rs b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/procfs.rs index 27c5fb5bde49..ca4f0f84ff74 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/procfs.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/procfs.rs @@ -8,10 +8,10 @@ use crate::filesystem::primitives::OpenOptionsExt; use crate::filesystem::primitives::{ - errors, open, read_link_unchecked, set_times_follow_unchecked, OpenOptions, SystemTimeSpec, + OpenOptions, SystemTimeSpec, errors, open, read_link_unchecked, set_times_follow_unchecked, }; use io_lifetimes::{AsFd, AsFilelike}; -use rustix::fs::{chmodat, AtFlags, Mode, OFlags, RawMode}; +use rustix::fs::{AtFlags, Mode, OFlags, RawMode, chmodat}; use rustix::path::DecInt; use rustix_linux_procfs::proc_self_fd; use std::os::unix::fs::PermissionsExt; diff --git a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/set_permissions_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/set_permissions_impl.rs index 296f6cbf2d4a..a9b3dd5bf402 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/set_permissions_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/set_permissions_impl.rs @@ -1,6 +1,6 @@ use super::procfs::set_permissions_through_proc_self_fd; -use crate::filesystem::primitives::{open, OpenOptions, Permissions}; -use rustix::fs::{fchmod, Mode, RawMode}; +use crate::filesystem::primitives::{OpenOptions, Permissions, open}; +use rustix::fs::{Mode, RawMode, fchmod}; use std::os::unix::fs::PermissionsExt; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/set_times_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/set_times_impl.rs index 8424f21f3284..e24cda99ca5d 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/set_times_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/set_times_impl.rs @@ -2,7 +2,7 @@ //! with setting the file times specific to Linux. use super::procfs::set_times_through_proc_self_fd; -use crate::filesystem::primitives::{open, OpenOptions, SystemTimeSpec}; +use crate::filesystem::primitives::{OpenOptions, SystemTimeSpec, open}; use std::path::Path; use std::{fs, io}; @@ -20,7 +20,7 @@ pub(crate) fn set_times_impl( &file, atime.map(SystemTimeSpec::into_std), mtime.map(SystemTimeSpec::into_std), - ) + ); } Err(err) => match rustix::io::Errno::from_io_error(&err) { Some(rustix::io::Errno::ACCESS) | Some(rustix::io::Errno::ISDIR) => (), @@ -35,7 +35,7 @@ pub(crate) fn set_times_impl( &file, atime.map(SystemTimeSpec::into_std), mtime.map(SystemTimeSpec::into_std), - ) + ); } Err(err) => match rustix::io::Errno::from_io_error(&err) { Some(rustix::io::Errno::ACCESS) => (), diff --git a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/stat_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/stat_impl.rs index 7682733c5bda..01a6e09a5fce 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/stat_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/stat_impl.rs @@ -3,7 +3,9 @@ //! `fstat` to perform a fast sandboxed `stat`. use super::file_metadata::file_metadata; -use crate::filesystem::primitives::{manually, open_beneath, FollowSymlinks, Metadata, OpenOptions}; +use crate::filesystem::primitives::{ + FollowSymlinks, Metadata, OpenOptions, manually, open_beneath, +}; use rustix::fs::OFlags; use std::path::Path; use std::{fs, io}; @@ -15,7 +17,7 @@ pub(crate) fn stat_impl( path: &Path, follow: FollowSymlinks, ) -> io::Result { - use crate::filesystem::primitives::{stat_unchecked, OpenOptionsExt}; + use crate::filesystem::primitives::{OpenOptionsExt, stat_unchecked}; use std::path::Component; // Optimization: if path has exactly one component and it's not ".." or diff --git a/crates/wasi/src/filesystem/primitives/set_permissions.rs b/crates/wasi/src/filesystem/primitives/set_permissions.rs index f878d2cdde75..6bbdd98a3040 100644 --- a/crates/wasi/src/filesystem/primitives/set_permissions.rs +++ b/crates/wasi/src/filesystem/primitives/set_permissions.rs @@ -1,11 +1,11 @@ //! This defines `set_permissions`, the primary entrypoint to sandboxed //! filesystem permissions modification. +#[cfg(racy_asserts)] +use crate::filesystem::primitives::{FollowSymlinks, Metadata, map_result, stat, stat_unchecked}; use crate::filesystem::primitives::{ Permissions, set_permissions_impl, set_symlink_permissions_impl, }; -#[cfg(racy_asserts)] -use crate::filesystem::primitives::{FollowSymlinks, Metadata, map_result, stat, stat_unchecked}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/symlink.rs b/crates/wasi/src/filesystem/primitives/symlink.rs index 1b3a9b60ffa6..c276872c72be 100644 --- a/crates/wasi/src/filesystem/primitives/symlink.rs +++ b/crates/wasi/src/filesystem/primitives/symlink.rs @@ -5,7 +5,9 @@ use crate::filesystem::primitives::errors; #[cfg(all(racy_asserts, not(windows)))] use crate::filesystem::primitives::symlink_unchecked; #[cfg(racy_asserts)] -use crate::filesystem::primitives::{FollowSymlinks, Metadata, canonicalize, manually, map_result, stat_unchecked}; +use crate::filesystem::primitives::{ + FollowSymlinks, Metadata, canonicalize, manually, map_result, stat_unchecked, +}; #[cfg(all(racy_asserts, windows))] use crate::filesystem::primitives::{symlink_dir_unchecked, symlink_file_unchecked}; use std::path::Path; diff --git a/crates/wasi/src/filesystem/primitives/via_parent/set_permissions.rs b/crates/wasi/src/filesystem/primitives/via_parent/set_permissions.rs index 556d94a4468d..fc6d4ac04e01 100644 --- a/crates/wasi/src/filesystem/primitives/via_parent/set_permissions.rs +++ b/crates/wasi/src/filesystem/primitives/via_parent/set_permissions.rs @@ -1,5 +1,5 @@ use super::open_parent; -use crate::filesystem::primitives::{set_permissions_unchecked, MaybeOwnedFile, Permissions}; +use crate::filesystem::primitives::{MaybeOwnedFile, Permissions, set_permissions_unchecked}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/access_unchecked.rs b/crates/wasi/src/filesystem/primitives/windows/fs/access_unchecked.rs index 6a0ae4d5f5fd..8be2cbb2e515 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/access_unchecked.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/access_unchecked.rs @@ -1,4 +1,4 @@ -use crate::filesystem::primitives::{open, AccessType, FollowSymlinks, OpenOptions}; +use crate::filesystem::primitives::{AccessType, FollowSymlinks, OpenOptions, open}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/copy.rs b/crates/wasi/src/filesystem/primitives/windows/fs/copy.rs index bd0a4c1346ea..9080963a2256 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/copy.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/copy.rs @@ -1,5 +1,5 @@ use super::get_path::get_path; -use crate::filesystem::primitives::{open, OpenOptions}; +use crate::filesystem::primitives::{OpenOptions, open}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/dir_utils.rs b/crates/wasi/src/filesystem/primitives/windows/fs/dir_utils.rs index c44ed0048ed3..4acd6b443612 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/dir_utils.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/dir_utils.rs @@ -1,5 +1,5 @@ use crate::filesystem::primitives::OpenOptionsExt; -use crate::filesystem::primitives::{errors, OpenOptions}; +use crate::filesystem::primitives::{OpenOptions, errors}; use ambient_authority::AmbientAuthority; use std::ffi::OsString; use std::ops::Deref; diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/open_impl.rs b/crates/wasi/src/filesystem/primitives/windows/fs/open_impl.rs index 887f6241aa1f..95c94f710a1b 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/open_impl.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/open_impl.rs @@ -1,4 +1,4 @@ -use crate::filesystem::primitives::{manually, OpenOptions}; +use crate::filesystem::primitives::{OpenOptions, manually}; use std::ffi::OsStr; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/read_dir_inner.rs b/crates/wasi/src/filesystem/primitives/windows/fs/read_dir_inner.rs index 3ea05e88351d..decfe31e8079 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/read_dir_inner.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/read_dir_inner.rs @@ -1,5 +1,5 @@ use super::get_path::concatenate; -use crate::filesystem::primitives::{open_dir, DirEntryInner, FollowSymlinks}; +use crate::filesystem::primitives::{DirEntryInner, FollowSymlinks, open_dir}; use std::path::{Component, Path}; use std::{fmt, fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/read_link_impl.rs b/crates/wasi/src/filesystem/primitives/windows/fs/read_link_impl.rs index 284b1327c2cf..b93086268a29 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/read_link_impl.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/read_link_impl.rs @@ -1,4 +1,4 @@ -use crate::filesystem::primitives::{open, FollowSymlinks, OpenOptions, OpenOptionsExt}; +use crate::filesystem::primitives::{FollowSymlinks, OpenOptions, OpenOptionsExt, open}; use std::path::{Path, PathBuf}; use std::{fs, io}; use windows_sys::Win32::Storage::FileSystem::{ diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/remove_dir_all_impl.rs b/crates/wasi/src/filesystem/primitives/windows/fs/remove_dir_all_impl.rs index 60f7caba13c3..d09e91915697 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/remove_dir_all_impl.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/remove_dir_all_impl.rs @@ -1,5 +1,7 @@ use super::get_path::get_path; -use crate::filesystem::primitives::{open_dir, open_dir_nofollow, remove_dir, stat, FollowSymlinks}; +use crate::filesystem::primitives::{ + FollowSymlinks, open_dir, open_dir_nofollow, remove_dir, stat, +}; use std::path::Path; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/reopen_impl.rs b/crates/wasi/src/filesystem/primitives/windows/fs/reopen_impl.rs index bd95ef15c75c..25537574393d 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/reopen_impl.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/reopen_impl.rs @@ -1,4 +1,4 @@ -use crate::filesystem::primitives::{get_access_mode, get_flags_and_attributes, OpenOptions}; +use crate::filesystem::primitives::{OpenOptions, get_access_mode, get_flags_and_attributes}; use io_lifetimes::AsHandle; use std::{fs, io}; use windows_sys::Win32::Foundation::{GENERIC_READ, GENERIC_WRITE}; diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/set_times_impl.rs b/crates/wasi/src/filesystem/primitives/windows/fs/set_times_impl.rs index 99f7252ce084..a6166642c054 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/set_times_impl.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/set_times_impl.rs @@ -1,4 +1,4 @@ -use crate::filesystem::primitives::{open, OpenOptions, OpenOptionsExt, SystemTimeSpec}; +use crate::filesystem::primitives::{OpenOptions, OpenOptionsExt, SystemTimeSpec, open}; use std::path::Path; use std::{fs, io}; use windows_sys::Win32::Storage::FileSystem::{ diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/stat_unchecked.rs b/crates/wasi/src/filesystem/primitives/windows/fs/stat_unchecked.rs index 6eb32ebe4892..b39ce5a44db4 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/stat_unchecked.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/stat_unchecked.rs @@ -1,5 +1,5 @@ use crate::filesystem::primitives::OpenOptionsExt; -use crate::filesystem::primitives::{open_unchecked, FollowSymlinks, Metadata, OpenOptions}; +use crate::filesystem::primitives::{FollowSymlinks, Metadata, OpenOptions, open_unchecked}; use std::path::Path; use std::{fs, io}; use windows_sys::Win32::Storage::FileSystem::{ From a86e176d63308305a9939ab4d73f8a1f7ef5a049 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 27 Aug 2026 11:32:47 -0700 Subject: [PATCH 04/34] Hook up `crate::filesystem::primitives` * Rename `cap-primitives to `public-cap-primitives` * Move public-facing dependencies to `public-cap-primitives` * Move internal dependencies to `crate::filesystem::primitives` --- Cargo.toml | 5 -- crates/wasi/Cargo.toml | 4 +- crates/wasi/src/ctx.rs | 7 ++- crates/wasi/src/filesystem.rs | 72 ++++++++++++++------------- crates/wasi/src/filesystem/unix.rs | 14 +++--- crates/wasi/src/filesystem/windows.rs | 30 +++++------ crates/wasi/src/lib.rs | 2 +- crates/wasi/src/p2/host/filesystem.rs | 4 +- crates/wasi/src/p3/filesystem/host.rs | 6 +-- crates/wasi/src/p3/filesystem/mod.rs | 4 +- 10 files changed, 74 insertions(+), 74 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8af2632f615b..3c6425c6a2a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -364,12 +364,7 @@ regalloc2 = "0.15.2" wasip1 = { version = "1.0.0", default-features = false } # cap-std family: -# -# Note that `cap-fs-ext` should be avoided where possible to use -# `cap-primitives` instead. target-lexicon = "0.13.5" -cap-primitives = "4.0.3" -cap-fs-ext-avoid-using-this = { version = "4.0.3", package = 'cap-fs-ext' } rustix = "1.1.4" # wit-bindgen: wit-bindgen = { version = "0.61.1", default-features = false } diff --git a/crates/wasi/Cargo.toml b/crates/wasi/Cargo.toml index 5921ed9ee827..02dd642deb0c 100644 --- a/crates/wasi/Cargo.toml +++ b/crates/wasi/Cargo.toml @@ -26,8 +26,8 @@ tokio = { workspace = true, features = ["time", "sync", "io-std", "io-util", "r bytes = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true, features = ["std", "attributes"] } -cap-primitives = { workspace = true } -cap-fs-ext-avoid-using-this = { workspace = true } +public-cap-primitives = { version = "4.0.3", package = 'cap-primitives' } +public-cap-fs-ext = { version = "4.0.3", package = 'cap-fs-ext' } bitflags = { workspace = true } async-trait = { workspace = true } futures = { workspace = true } diff --git a/crates/wasi/src/ctx.rs b/crates/wasi/src/ctx.rs index 99a8c71c3a9c..a5da63c9f8f5 100644 --- a/crates/wasi/src/ctx.rs +++ b/crates/wasi/src/ctx.rs @@ -4,7 +4,7 @@ use crate::filesystem::{Dir, WasiFilesystemCtx}; use crate::random::WasiRandomCtx; use crate::sockets::{SocketAddrCheck, SocketAddrUse, WasiSocketsCtx}; use crate::{FsPerms, OpenMode}; -use cap_primitives::ambient_authority; +use ambient_authority::ambient_authority; use rand::Rng; use std::future::Future; use std::mem; @@ -300,7 +300,10 @@ impl WasiCtxBuilder { guest_path: impl AsRef, perms: FsPerms, ) -> Result<&mut Self> { - let dir = cap_primitives::fs::open_ambient_dir(host_path.as_ref(), ambient_authority())?; + let dir = crate::filesystem::primitives::open_ambient_dir( + host_path.as_ref(), + ambient_authority(), + )?; let open_mode = match perms { FsPerms::ReadOnly => OpenMode::READ, FsPerms::ReadWrite => OpenMode::READ | OpenMode::WRITE, diff --git a/crates/wasi/src/filesystem.rs b/crates/wasi/src/filesystem.rs index 7e62c1a8e66e..6d892a81c3cb 100644 --- a/crates/wasi/src/filesystem.rs +++ b/crates/wasi/src/filesystem.rs @@ -1,6 +1,8 @@ use crate::clocks::Datetime; +use crate::filesystem::primitives::{ + DirOptions, FollowSymlinks, Metadata, OpenOptions, SystemTimeSpec, +}; use crate::runtime::{AbortOnDropJoinHandle, spawn_blocking}; -use cap_primitives::fs::{DirOptions, FollowSymlinks, Metadata, OpenOptions, SystemTimeSpec}; use std::collections::hash_map; use std::sync::Arc; use std::time::SystemTime; @@ -276,8 +278,8 @@ pub(crate) enum DescriptorType { RegularFile, } -impl From for DescriptorType { - fn from(ft: cap_primitives::fs::FileType) -> Self { +impl From for DescriptorType { + fn from(ft: crate::filesystem::primitives::FileType) -> Self { if ft.is_dir() { DescriptorType::Directory } else if ft.is_symlink() { @@ -329,15 +331,12 @@ impl DescriptorStat { data_access_timestamp: meta .accessed() .ok() - .and_then(|t| Datetime::try_from(t.into_std()).ok()), + .and_then(|t| Datetime::try_from(t).ok()), data_modification_timestamp: meta .modified() .ok() - .and_then(|t| Datetime::try_from(t.into_std()).ok()), - status_change_timestamp: meta - .created() - .ok() - .and_then(|t| Datetime::try_from(t.into_std()).ok()), + .and_then(|t| Datetime::try_from(t).ok()), + status_change_timestamp: meta.created().ok().and_then(|t| Datetime::try_from(t).ok()), } } } @@ -526,7 +525,7 @@ impl Descriptor { } Self::Dir(d) => { d.run_blocking(|d| { - let d = cap_primitives::fs::open( + let d = crate::filesystem::primitives::open( d, std::path::Component::CurDir.as_ref(), OpenOptions::new().read(true), @@ -624,7 +623,7 @@ impl Descriptor { } Self::Dir(d) => { d.run_blocking(|d| { - let d = cap_primitives::fs::open( + let d = crate::filesystem::primitives::open( d, std::path::Component::CurDir.as_ref(), OpenOptions::new().read(true), @@ -857,7 +856,7 @@ impl Dir { return Err(ErrorCode::NotPermitted); } self.run_blocking(move |d| { - cap_primitives::fs::create_dir(d, path.as_ref(), &DirOptions::new()) + crate::filesystem::primitives::create_dir(d, path.as_ref(), &DirOptions::new()) }) .await?; Ok(()) @@ -889,16 +888,16 @@ impl Dir { if self.perms.write_not_permitted() { return Err(ErrorCode::NotPermitted); } - let atim = - atim.map(|t| SystemTimeSpec::Absolute(cap_primitives::time::SystemTime::from_std(t))); - let mtim = - mtim.map(|t| SystemTimeSpec::Absolute(cap_primitives::time::SystemTime::from_std(t))); + let atim = atim.map(|t| SystemTimeSpec::Absolute(t)); + let mtim = mtim.map(|t| SystemTimeSpec::Absolute(t)); if path_flags.contains(PathFlags::SYMLINK_FOLLOW) { - self.run_blocking(move |d| cap_primitives::fs::set_times(d, path.as_ref(), atim, mtim)) - .await?; + self.run_blocking(move |d| { + crate::filesystem::primitives::set_times(d, path.as_ref(), atim, mtim) + }) + .await?; } else { self.run_blocking(move |d| { - cap_primitives::fs::set_times_nofollow(d, path.as_ref(), atim, mtim) + crate::filesystem::primitives::set_times_nofollow(d, path.as_ref(), atim, mtim) }) .await?; } @@ -926,7 +925,12 @@ impl Dir { } let new_dir_handle = Arc::clone(&new_dir.dir); self.run_blocking(move |d| { - cap_primitives::fs::hard_link(d, old_path.as_ref(), &new_dir_handle, new_path.as_ref()) + crate::filesystem::primitives::hard_link( + d, + old_path.as_ref(), + &new_dir_handle, + new_path.as_ref(), + ) }) .await?; Ok(()) @@ -977,17 +981,10 @@ impl Dir { open_mode |= OpenMode::READ; } - // Note that this is intentionally scoped to a separate block to - // minimize the surface area that is depended on by cap-fs-ext. Ideally - // the underlying functionality in `cap-primitives` would get exposed, - // but that'll require an upstream PR. - { - use cap_fs_ext_avoid_using_this::OpenOptionsFollowExt; - if path_flags.contains(PathFlags::SYMLINK_FOLLOW) { - opts.follow(FollowSymlinks::Yes); - } else { - opts.follow(FollowSymlinks::No); - } + if path_flags.contains(PathFlags::SYMLINK_FOLLOW) { + opts.follow(FollowSymlinks::Yes); + } else { + opts.follow(FollowSymlinks::No); } // These flags are not yet supported in cap-primitives: @@ -1026,7 +1023,7 @@ impl Dir { let opened = self .run_blocking::<_, std::io::Result>(move |d| { - let opened = cap_primitives::fs::open(d, path.as_ref(), &opts)?; + let opened = crate::filesystem::primitives::open(d, path.as_ref(), &opts)?; if Metadata::from_file(&opened)?.is_dir() { Ok(OpenResult::Dir(opened)) } else if oflags.contains(OpenFlags::DIRECTORY) { @@ -1066,7 +1063,7 @@ impl Dir { pub(crate) async fn readlink_at(&self, path: String) -> Result { let link = self - .run_blocking(move |d| cap_primitives::fs::read_link(d, path.as_ref())) + .run_blocking(move |d| crate::filesystem::primitives::read_link(d, path.as_ref())) .await?; link.into_os_string() .into_string() @@ -1077,7 +1074,7 @@ impl Dir { if self.perms.write_not_permitted() { return Err(ErrorCode::NotPermitted); } - self.run_blocking(move |d| cap_primitives::fs::remove_dir(d, path.as_ref())) + self.run_blocking(move |d| crate::filesystem::primitives::remove_dir(d, path.as_ref())) .await?; Ok(()) } @@ -1099,7 +1096,12 @@ impl Dir { } let new_dir_handle = Arc::clone(&new_dir.dir); self.run_blocking(move |d| { - cap_primitives::fs::rename(d, old_path.as_ref(), &new_dir_handle, new_path.as_ref()) + crate::filesystem::primitives::rename( + d, + old_path.as_ref(), + &new_dir_handle, + new_path.as_ref(), + ) }) .await?; Ok(()) diff --git a/crates/wasi/src/filesystem/unix.rs b/crates/wasi/src/filesystem/unix.rs index ade351c2a643..9a14680d5c32 100644 --- a/crates/wasi/src/filesystem/unix.rs +++ b/crates/wasi/src/filesystem/unix.rs @@ -1,9 +1,9 @@ +use crate::filesystem::primitives::{ + FileType, FileTypeExt, FollowSymlinks, Metadata, MetadataExt, OpenOptions, +}; use crate::filesystem::{ Advice, DescriptorFlags, DescriptorStat, DescriptorType, MetadataHashValue, }; -use cap_primitives::fs::{ - FileType, FileTypeExt, FollowSymlinks, Metadata, MetadataExt, OpenOptions, -}; use rustix::fs::{OFlags, fcntl_getfl, fcntl_setfl}; use rustix::io::write; use std::fs::File; @@ -11,8 +11,8 @@ use std::io; use std::os::unix::fs::FileExt; use std::path::Path; -pub use cap_primitives::fs::remove_file as remove_file_or_symlink; -pub use cap_primitives::fs::symlink; +pub use crate::filesystem::primitives::remove_file as remove_file_or_symlink; +pub use crate::filesystem::primitives::symlink; pub(crate) fn get_flags(file: &File) -> io::Result { let flags = fcntl_getfl(file)?; @@ -122,7 +122,7 @@ pub(crate) fn metadata_hash_at( path: &Path, follow: FollowSymlinks, ) -> io::Result { - let meta = cap_primitives::fs::stat(start, path, follow)?; + let meta = crate::filesystem::primitives::stat(start, path, follow)?; Ok(MetadataHashValue::new(meta_identity(&meta))) } @@ -140,7 +140,7 @@ pub(crate) fn stat_at( path: &Path, follow: FollowSymlinks, ) -> io::Result { - let meta = cap_primitives::fs::stat(start, path, follow)?; + let meta = crate::filesystem::primitives::stat(start, path, follow)?; Ok(DescriptorStat::new(&meta, meta.nlink())) } diff --git a/crates/wasi/src/filesystem/windows.rs b/crates/wasi/src/filesystem/windows.rs index f0760d85c9a0..ab5d9209b705 100644 --- a/crates/wasi/src/filesystem/windows.rs +++ b/crates/wasi/src/filesystem/windows.rs @@ -1,7 +1,9 @@ +use crate::filesystem::primitives::{ + FileType, FollowSymlinks, Metadata, OpenOptions, OpenOptionsExt, +}; use crate::filesystem::{ Advice, DescriptorFlags, DescriptorStat, DescriptorType, MetadataHashValue, }; -use cap_primitives::fs::{FileType, FollowSymlinks, Metadata, OpenOptions, OpenOptionsExt}; use std::fs::File; use std::io::{self, Write}; use std::mem::{self, MaybeUninit}; @@ -122,18 +124,15 @@ fn open_metadata_handle(start: &File, path: &Path, follow: FollowSymlinks) -> io opts.custom_flags(FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT); } } - cap_primitives::fs::open(start, path, &opts) + crate::filesystem::primitives::open(start, path, &opts) } pub(crate) fn stat(f: &std::fs::File) -> io::Result { let meta = Metadata::from_file(f)?; - // Note that this is intentionally scoped to a separate block to - // minimize the surface area that is depended on by cap-fs-ext. - let link_count = { - use cap_fs_ext_avoid_using_this::MetadataExt; - meta.nlink() - }; + let link_count = crate::filesystem::primitives::_WindowsByHandle::number_of_links(&meta) + .unwrap() + .into(); Ok(DescriptorStat::new(&meta, link_count)) } @@ -179,10 +178,10 @@ fn is_char_device(ft: FileType) -> bool { } pub(crate) fn symlink(original: &Path, start: &File, link: &Path) -> io::Result<()> { - if cap_primitives::fs::stat(start, original, FollowSymlinks::Yes)?.is_dir() { - cap_primitives::fs::symlink_dir(original, start, link) + if crate::filesystem::primitives::stat(start, original, FollowSymlinks::Yes)?.is_dir() { + crate::filesystem::primitives::symlink_dir(original, start, link) } else { - cap_primitives::fs::symlink_file(original, start, link) + crate::filesystem::primitives::symlink_file(original, start, link) } } @@ -193,16 +192,17 @@ pub(crate) fn remove_file_or_symlink(start: &File, path: &Path) -> io::Result<() let mut opts = OpenOptions::new(); opts.access_mode(DELETE); opts.custom_flags(FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS); - let file = cap_primitives::fs::open(start, path, &opts)?; + let file = crate::filesystem::primitives::open(start, path, &opts)?; let meta = Metadata::from_file(&file)?; if meta.file_type().is_symlink() - && cap_primitives::fs::MetadataExt::file_attributes(&meta) & FILE_ATTRIBUTE_DIRECTORY + && crate::filesystem::primitives::MetadataExt::file_attributes(&meta) + & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY { - cap_primitives::fs::remove_dir(start, path)?; + crate::filesystem::primitives::remove_dir(start, path)?; } else { - cap_primitives::fs::remove_file(start, path)?; + crate::filesystem::primitives::remove_file(start, path)?; } // Drop the file after calling `remove_file` or `remove_dir`, since diff --git a/crates/wasi/src/lib.rs b/crates/wasi/src/lib.rs index 6dfd0b83d63b..756b67906206 100644 --- a/crates/wasi/src/lib.rs +++ b/crates/wasi/src/lib.rs @@ -59,7 +59,7 @@ pub use self::view::{WasiCtxView, WasiView}; #[doc(no_inline)] pub use async_trait::async_trait; #[doc(no_inline)] -pub use cap_primitives::fs::SystemTimeSpec; +pub use public_cap_primitives::fs::SystemTimeSpec; #[doc(no_inline)] pub use rand::Rng; #[doc(no_inline)] diff --git a/crates/wasi/src/p2/host/filesystem.rs b/crates/wasi/src/p2/host/filesystem.rs index 579c74129a05..7f5ef8aec74c 100644 --- a/crates/wasi/src/p2/host/filesystem.rs +++ b/crates/wasi/src/p2/host/filesystem.rs @@ -171,7 +171,7 @@ impl HostDescriptor for WasiFilesystemCtxView<'_> { // within this `block` call, rather than delay calculating the metadata // for entries when they're demanded later in the iterator chain. Ok::<_, std::io::Error>( - cap_primitives::fs::read_base_dir(d)? + crate::filesystem::primitives::read_base_dir(d)? .map(|entry| { let entry = entry?; let meta = entry.metadata()?; @@ -713,7 +713,7 @@ impl From for ErrorCode { } } -fn descriptortype_from(ft: cap_primitives::fs::FileType) -> types::DescriptorType { +fn descriptortype_from(ft: crate::filesystem::primitives::FileType) -> types::DescriptorType { crate::filesystem::DescriptorType::from(ft).into() } diff --git a/crates/wasi/src/p3/filesystem/host.rs b/crates/wasi/src/p3/filesystem/host.rs index f71e61d810ae..6ea173bbe43b 100644 --- a/crates/wasi/src/p3/filesystem/host.rs +++ b/crates/wasi/src/p3/filesystem/host.rs @@ -243,7 +243,7 @@ impl StreamProducer for ReadStreamProducer { } fn map_dir_entry( - entry: std::io::Result, + entry: std::io::Result, ) -> Result, ErrorCode> { match entry { Ok(entry) => { @@ -289,7 +289,7 @@ impl ReadDirStream { let (tx, rx) = mpsc::channel(1); ReadDirStream { task: spawn_blocking(move || { - let entries = cap_primitives::fs::read_base_dir(&dir)?; + let entries = crate::filesystem::primitives::read_base_dir(&dir)?; for entry in entries { if let Some(entry) = map_dir_entry(entry)? { if let Err(_) = tx.blocking_send(entry) { @@ -676,7 +676,7 @@ impl types::HostDescriptorWithStore for WasiFilesystem { let allow_blocking_current_thread = dir.allow_blocking_current_thread; let dir = Arc::clone(dir.as_dir()); if allow_blocking_current_thread { - match cap_primitives::fs::read_base_dir(&dir) { + match crate::filesystem::primitives::read_base_dir(&dir) { Ok(readdir) => StreamReader::new( &mut store, FallibleIteratorProducer::new( diff --git a/crates/wasi/src/p3/filesystem/mod.rs b/crates/wasi/src/p3/filesystem/mod.rs index 11816c12ea3d..31ccb52ed67b 100644 --- a/crates/wasi/src/p3/filesystem/mod.rs +++ b/crates/wasi/src/p3/filesystem/mod.rs @@ -265,8 +265,8 @@ impl From for types::DescriptorType { } } -impl From for types::DescriptorType { - fn from(ft: cap_primitives::fs::FileType) -> Self { +impl From for types::DescriptorType { + fn from(ft: crate::filesystem::primitives::FileType) -> Self { crate::filesystem::DescriptorType::from(ft).into() } } From 64a701b253da6ba109f2a3d596a3417a6b3a20f4 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 27 Aug 2026 11:50:14 -0700 Subject: [PATCH 05/34] Remove code related to cfg(racy_asserts) This isn't something used in Wasmtime so it's all dead code --- .../wasi/src/filesystem/primitives/access.rs | 53 +--- .../filesystem/primitives/assert_same_file.rs | 85 ------ .../src/filesystem/primitives/canonicalize.rs | 63 +--- .../src/filesystem/primitives/create_dir.rs | 92 +----- .../src/filesystem/primitives/hard_link.rs | 113 +------ .../primitives/manually/canonical_path.rs | 17 +- .../src/filesystem/primitives/manually/mod.rs | 3 - .../filesystem/primitives/manually/open.rs | 53 ---- .../filesystem/primitives/maybe_owned_file.rs | 42 --- crates/wasi/src/filesystem/primitives/mod.rs | 14 - crates/wasi/src/filesystem/primitives/open.rs | 96 +----- .../src/filesystem/primitives/read_link.rs | 54 +--- .../src/filesystem/primitives/remove_dir.rs | 136 +-------- .../src/filesystem/primitives/remove_file.rs | 117 +------ .../wasi/src/filesystem/primitives/rename.rs | 141 +-------- .../primitives/rustix/fs/dir_utils.rs | 11 - .../rustix/linux/fs/canonicalize_impl.rs | 13 - .../primitives/rustix/linux/fs/open_impl.rs | 23 -- .../filesystem/primitives/set_permissions.rs | 70 +---- crates/wasi/src/filesystem/primitives/stat.rs | 70 +---- .../wasi/src/filesystem/primitives/symlink.rs | 287 +----------------- .../primitives/windows/fs/is_same_file.rs | 37 --- 22 files changed, 16 insertions(+), 1574 deletions(-) delete mode 100644 crates/wasi/src/filesystem/primitives/assert_same_file.rs diff --git a/crates/wasi/src/filesystem/primitives/access.rs b/crates/wasi/src/filesystem/primitives/access.rs index 09b67a0c517b..d8c13fc5d797 100644 --- a/crates/wasi/src/filesystem/primitives/access.rs +++ b/crates/wasi/src/filesystem/primitives/access.rs @@ -1,8 +1,6 @@ //! Access test functions. use crate::filesystem::primitives::{FollowSymlinks, access_impl}; -#[cfg(racy_asserts)] -use crate::filesystem::primitives::{access_unchecked, file_path}; use std::path::Path; use std::{fs, io}; @@ -29,7 +27,6 @@ pub enum AccessType { /// 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, @@ -37,53 +34,5 @@ pub fn access( 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, - ), - } + access_impl(start, path, type_, follow) } diff --git a/crates/wasi/src/filesystem/primitives/assert_same_file.rs b/crates/wasi/src/filesystem/primitives/assert_same_file.rs deleted file mode 100644 index f638bbf808cc..000000000000 --- a/crates/wasi/src/filesystem/primitives/assert_same_file.rs +++ /dev/null @@ -1,85 +0,0 @@ -/// Assert that two arguments refer to the same underlying file. -macro_rules! assert_same_file { - ($left:expr, $right:expr) => ({ - let (left, right) = (&($left), &($right)); - if crate::rustix::fs::is_different_file(left, right).unwrap() { - panic!("assertion failed: left is a different file from right\n left: `{:?}`,\n right: `{:?}`", - left, right); - } - }); - ($left:expr, $right:expr, ) => ({ - assert_le!($left, $right); - }); - ($left:expr, $right:expr, $($msg_args:tt)+) => ({ - let (left, right) = (&($left), &($right)); - if crate::rustix::fs::is_different_file(left, right).unwrap() { - panic!("assertion failed: left is a different file from right\n left: `{:?}`,\n right: `{:?}`: {}", - left, right, format_args!($($msg_args)+)); - } - }) -} - -/// Assert that two arguments are metadata for the same underlying file. -macro_rules! assert_same_file_metadata { - ($left:expr, $right:expr) => ({ - let (left, right) = (&($left), &($right)); - if crate::rustix::fs::is_different_file_metadata(left, right).unwrap() { - panic!("assertion failed: left is a different file from right\n left: `{:?}`,\n right: `{:?}`", - left, right); - } - }); - ($left:expr, $right:expr, ) => ({ - assert_le!($left, $right); - }); - ($left:expr, $right:expr, $($msg_args:tt)+) => ({ - let (left, right) = (&($left), &($right)); - if crate::rustix::fs::is_different_file_metadata(left, right).unwrap() { - panic!("assertion failed: left is a different file from right\n left: `{:?}`,\n right: `{:?}`: {}", - left, right, format_args!($($msg_args)+)); - } - }) -} - -/// Assert that two arguments refer to different underlying files. -#[allow(unused_macros)] -macro_rules! assert_different_file { - ($left:expr, $right:expr) => ({ - let (left, right) = (&($left), &($right)); - if !crate::rustix::fs::is_different_file(left, right).unwrap() { - panic!("assertion failed: left is the same file as right\n left: `{:?}`,\n right: `{:?}`", - left, right); - } - }); - ($left:expr, $right:expr, ) => ({ - assert_le!($left, $right); - }); - ($left:expr, $right:expr, $($msg_args:tt)+) => ({ - let (left, right) = (&($left), &($right)); - if !crate::rustix::fs::is_different_file(left, right).unwrap() { - panic!("assertion failed: left is the same file as right\n left: `{:?}`,\n right: `{:?}`: {}", - left, right, format_args!($($msg_args)+)); - } - }) -} - -/// Assert that two arguments are metadata for the same underlying file. -#[allow(unused_macros)] -macro_rules! assert_different_metadata { - ($left:expr, $right:expr) => ({ - let (left, right) = (&($left), &($right)); - if !crate::rustix::fs::is_different_file_metadata(left, right).unwrap() { - panic!("assertion failed: left is the same file as right\n left: `{:?}`,\n right: `{:?}`", - left, right); - } - }); - ($left:expr, $right:expr, ) => ({ - assert_le!($left, $right); - }); - ($left:expr, $right:expr, $($msg_args:tt)+) => ({ - let (left, right) = (&($left), &($right)); - if !crate::rustix::fs::is_different_file_metadata(left, right).unwrap() { - panic!("assertion failed: left is the same file as right\n left: `{:?}`,\n right: `{:?}`: {}", - left, right, format_args!($($msg_args)+)); - } - }) -} diff --git a/crates/wasi/src/filesystem/primitives/canonicalize.rs b/crates/wasi/src/filesystem/primitives/canonicalize.rs index 9ee5e65db1b7..06153d5112c5 100644 --- a/crates/wasi/src/filesystem/primitives/canonicalize.rs +++ b/crates/wasi/src/filesystem/primitives/canonicalize.rs @@ -1,74 +1,13 @@ //! Sandboxed path canonicalization. use crate::filesystem::primitives::canonicalize_impl; -#[cfg(racy_asserts)] -use crate::filesystem::primitives::{OpenOptions, file_path, open}; use std::path::{Path, PathBuf}; use std::{fs, io}; /// 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))] #[inline] pub fn canonicalize(start: &fs::File, path: &Path) -> io::Result { // Call the underlying implementation. - let result = canonicalize_impl(start, path); - - #[cfg(racy_asserts)] - check_canonicalize(start, path, &result); - - result -} - -#[cfg(racy_asserts)] -fn check_canonicalize(start: &fs::File, path: &Path, result: &io::Result) { - if let Ok(canonical_path) = result { - let path_result = open(start, path, OpenOptions::new().read(true)); - let canonical_result = open(start, canonical_path, OpenOptions::new().read(true)); - match (path_result, canonical_result) { - (Ok(path_file), Ok(canonical_file)) => assert_same_file!( - &path_file, - &canonical_file, - "we should be able to stat paths that we just canonicalized" - ), - (Err(path_err), Err(canonical_err)) => { - assert_eq!(path_err.to_string(), canonical_err.to_string()) - } - other => { - // TODO: Checking in the case it does end with ".". - if !path.to_string_lossy().ends_with("/.") { - panic!("inconsistent canonicalize checks: {:?}", other); - } - } - } - - // On operating systems which can tell us the path of a file descriptor, - // assert that the path we computed canonicalizes to the same thing as - // the input canonicalizes too. - if let Some(start_abspath) = file_path(start) { - let check_abspath = start_abspath.join(path); - let result_abspath = start_abspath.join(canonical_path); - if let Ok(check_abspath) = fs::canonicalize(check_abspath) { - let result_abspath = - fs::canonicalize(result_abspath).expect("we already canonicalized this"); - assert_eq!( - check_abspath, - result_abspath, - "incorrect canonicalization: start='{:?}' path='{}' result='{}'", - start, - path.display(), - canonical_path.display() - ); - // TODO: When porting to Windows, check whether `start_abspath` not being - // a canonicalized path leads to `\\?\` extended path prefix differences. - assert!( - result_abspath.starts_with(start_abspath), - "sandbox escape: start='{:?}' path='{}' result='{}'", - start, - path.display(), - canonical_path.display() - ); - } - } - } + canonicalize_impl(start, path) } diff --git a/crates/wasi/src/filesystem/primitives/create_dir.rs b/crates/wasi/src/filesystem/primitives/create_dir.rs index 836f3498ce2a..0d33961817fb 100644 --- a/crates/wasi/src/filesystem/primitives/create_dir.rs +++ b/crates/wasi/src/filesystem/primitives/create_dir.rs @@ -2,103 +2,13 @@ //! creation. use crate::filesystem::primitives::{DirOptions, create_dir_impl}; -#[cfg(racy_asserts)] -use crate::filesystem::primitives::{ - FollowSymlinks, Metadata, canonicalize, create_dir_unchecked, map_result, stat_unchecked, -}; use std::path::Path; use std::{fs, io}; /// Perform a `mkdirat`-like operation, 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))] #[inline] pub fn create_dir(start: &fs::File, path: &Path, options: &DirOptions) -> io::Result<()> { - #[cfg(racy_asserts)] - let stat_before = stat_unchecked(start, path, FollowSymlinks::No); - // Call the underlying implementation. - let result = create_dir_impl(start, path, options); - - #[cfg(racy_asserts)] - let stat_after = stat_unchecked(start, path, FollowSymlinks::No); - - #[cfg(racy_asserts)] - check_create_dir(start, path, options, &stat_before, &result, &stat_after); - - result -} - -#[cfg(racy_asserts)] -#[allow(clippy::enum_glob_use)] -fn check_create_dir( - start: &fs::File, - path: &Path, - options: &DirOptions, - stat_before: &io::Result, - result: &io::Result<()>, - stat_after: &io::Result, -) { - use io::ErrorKind::*; - - match ( - map_result(stat_before), - map_result(result), - map_result(stat_after), - ) { - (Err((NotFound, _)), Ok(()), Ok(metadata)) => { - assert!(metadata.is_dir()); - assert_same_file_metadata!( - &stat_unchecked( - start, - &canonicalize(start, path).unwrap(), - FollowSymlinks::No - ) - .unwrap(), - &metadata - ); - } - - (Ok(metadata_before), Err((AlreadyExists, _)), Ok(metadata_after)) => { - assert_same_file_metadata!(&metadata_before, &metadata_after); - } - - (_, Err((kind, message)), _) => { - // TODO: Checking in the case it does end with ".". - if !path.to_string_lossy().ends_with(".") { - match map_result(&canonicalize(start, path)) { - Ok(canon) => match map_result(&create_dir_unchecked(start, &canon, options)) { - Err((unchecked_kind, unchecked_message)) => { - assert_eq!( - kind, - unchecked_kind, - "unexpected error kind from create_dir start='{:?}', \ - path='{}':\nstat_before={:#?}\nresult={:#?}\nstat_after={:#?}", - start, - path.display(), - stat_before, - result, - stat_after - ); - assert_eq!(message, unchecked_message); - } - _ => panic!("unsandboxed create_dir success"), - }, - Err((_canon_kind, _canon_message)) => { - /* TODO: Check error messages - assert_eq!(kind, canon_kind); - assert_eq!(message, canon_message); - */ - } - } - } - } - - other => panic!( - "inconsistent create_dir checks: start='{:?}' path='{}':\n{:#?}", - start, - path.display(), - other, - ), - } + create_dir_impl(start, path, options) } diff --git a/crates/wasi/src/filesystem/primitives/hard_link.rs b/crates/wasi/src/filesystem/primitives/hard_link.rs index 761033f402f5..e46bd3c7b134 100644 --- a/crates/wasi/src/filesystem/primitives/hard_link.rs +++ b/crates/wasi/src/filesystem/primitives/hard_link.rs @@ -2,16 +2,11 @@ //! creation. use crate::filesystem::primitives::hard_link_impl; -#[cfg(racy_asserts)] -use crate::filesystem::primitives::{ - FollowSymlinks, Metadata, canonicalize, hard_link_unchecked, map_result, stat_unchecked, -}; use std::path::Path; use std::{fs, io}; /// Perform a `linkat`-like operation, 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))] #[inline] pub fn hard_link( old_start: &fs::File, @@ -19,112 +14,6 @@ pub fn hard_link( new_start: &fs::File, new_path: &Path, ) -> io::Result<()> { - #[cfg(racy_asserts)] - let (old_metadata_before, new_metadata_before) = ( - stat_unchecked(old_start, old_path, FollowSymlinks::No), - stat_unchecked(new_start, new_path, FollowSymlinks::No), - ); - // Call the underlying implementation. - let result = hard_link_impl(old_start, old_path, new_start, new_path); - - #[cfg(racy_asserts)] - let (old_metadata_after, new_metadata_after) = ( - stat_unchecked(old_start, old_path, FollowSymlinks::No), - stat_unchecked(new_start, new_path, FollowSymlinks::No), - ); - - #[cfg(racy_asserts)] - check_hard_link( - old_start, - old_path, - new_start, - new_path, - &old_metadata_before, - &new_metadata_before, - &result, - &old_metadata_after, - &new_metadata_after, - ); - - result -} - -#[cfg(racy_asserts)] -#[allow(clippy::too_many_arguments)] -#[allow(clippy::enum_glob_use)] -fn check_hard_link( - old_start: &fs::File, - old_path: &Path, - new_start: &fs::File, - new_path: &Path, - old_metadata_before: &io::Result, - new_metadata_before: &io::Result, - result: &io::Result<()>, - old_metadata_after: &io::Result, - new_metadata_after: &io::Result, -) { - use io::ErrorKind::*; - - match ( - map_result(old_metadata_before), - map_result(new_metadata_before), - map_result(result), - map_result(old_metadata_after), - map_result(new_metadata_after), - ) { - ( - Ok(old_metadata_before), - Err((NotFound, _)), - Ok(()), - Ok(old_metadata_after), - Ok(new_metadata_after), - ) => { - assert_same_file_metadata!(old_metadata_before, old_metadata_after); - assert_same_file_metadata!(old_metadata_before, new_metadata_after); - } - - (_, Ok(new_metadata_before), Err((AlreadyExists, _)), _, Ok(new_metadata_after)) => { - assert_same_file_metadata!(&new_metadata_before, &new_metadata_after); - } - - (_, _, Err((_kind, _message)), _, _) => match ( - map_result(&canonicalize(old_start, old_path)), - map_result(&canonicalize(new_start, new_path)), - ) { - (Ok(old_canon), Ok(new_canon)) => match map_result(&hard_link_unchecked( - old_start, &old_canon, new_start, &new_canon, - )) { - Err((_unchecked_kind, _unchecked_message)) => { - /* TODO: Check error messages. - assert_eq!(kind, unchecked_kind); - assert_eq!(message, unchecked_message); - */ - } - _ => panic!("unsandboxed link success"), - }, - (Err((_old_canon_kind, _old_canon_message)), _) => { - /* TODO: Check error messages. - assert_eq!(kind, old_canon_kind); - assert_eq!(message, old_canon_message); - */ - } - (_, Err((_new_canon_kind, _new_canon_message))) => { - /* TODO: Check error messages. - assert_eq!(kind, new_canon_kind); - assert_eq!(message, new_canon_message); - */ - } - }, - - other => panic!( - "inconsistent link checks: old_start='{:?}', old_path='{}', new_start='{:?}', \ - new_path='{}':\n{:#?}", - old_start, - old_path.display(), - new_start, - new_path.display(), - other - ), - } + hard_link_impl(old_start, old_path, new_start, new_path) } diff --git a/crates/wasi/src/filesystem/primitives/manually/canonical_path.rs b/crates/wasi/src/filesystem/primitives/manually/canonical_path.rs index 11d09bd180e3..b843cb703b20 100644 --- a/crates/wasi/src/filesystem/primitives/manually/canonical_path.rs +++ b/crates/wasi/src/filesystem/primitives/manually/canonical_path.rs @@ -6,35 +6,20 @@ pub(super) struct CanonicalPath<'path_buf> { /// If the user requested a canonical path, a reference to the `PathBuf` to /// write it to. path: Option<&'path_buf mut PathBuf>, - - /// Our own private copy of the canonical path, for assertion checking. - #[cfg(racy_asserts)] - pub(super) debug: PathBuf, } impl<'path_buf> CanonicalPath<'path_buf> { pub(super) fn new(path: Option<&'path_buf mut PathBuf>) -> Self { - Self { - #[cfg(racy_asserts)] - debug: PathBuf::new(), - - path, - } + Self { path } } pub(super) fn push(&mut self, one: &OsStr) { - #[cfg(racy_asserts)] - self.debug.push(one); - if let Some(path) = &mut self.path { path.push(one) } } pub(super) fn pop(&mut self) -> bool { - #[cfg(racy_asserts)] - self.debug.pop(); - if let Some(path) = &mut self.path { path.pop() } else { diff --git a/crates/wasi/src/filesystem/primitives/manually/mod.rs b/crates/wasi/src/filesystem/primitives/manually/mod.rs index 34da44f924de..bba033eef23b 100644 --- a/crates/wasi/src/filesystem/primitives/manually/mod.rs +++ b/crates/wasi/src/filesystem/primitives/manually/mod.rs @@ -14,9 +14,6 @@ use cow_component::CowComponent; use open::internal_open; use read_link_one::read_link_one; -#[cfg(racy_asserts)] -pub(super) use canonicalize::canonicalize_with; - pub(crate) use canonicalize::canonicalize; pub(crate) use open::{open, stat}; #[cfg(not(any(windows, target_os = "freebsd")))] diff --git a/crates/wasi/src/filesystem/primitives/manually/open.rs b/crates/wasi/src/filesystem/primitives/manually/open.rs index 13041a6e9028..06bef1aba961 100644 --- a/crates/wasi/src/filesystem/primitives/manually/open.rs +++ b/crates/wasi/src/filesystem/primitives/manually/open.rs @@ -60,9 +60,6 @@ struct Context<'start> { /// A `PathBuf` that we reuse for calling `read_link_one` to minimize /// allocations. reuse: PathBuf, - - #[cfg(racy_asserts)] - start_clone: MaybeOwnedFile<'start>, } impl<'start> Context<'start> { @@ -106,9 +103,6 @@ impl<'start> Context<'start> { components.extend(path.components().rev().map(CowComponent::borrowed)); } - #[cfg(racy_asserts)] - let start_clone = MaybeOwnedFile::owned(start.try_clone().unwrap()); - Self { base: start, dirs: Vec::with_capacity(components.len()), @@ -127,9 +121,6 @@ impl<'start> Context<'start> { follow_with_dot: trailing_dot | trailing_dotdot, reuse: PathBuf::new(), - - #[cfg(racy_asserts)] - start_clone, } } @@ -183,11 +174,6 @@ impl<'start> Context<'start> { /// Handle a ".." path component. fn parent_dir(&mut self) -> io::Result<()> { - #[cfg(racy_asserts)] - if !self.dirs.is_empty() { - assert_different_file!(&self.start_clone, &self.base); - } - // We hold onto all the parent directory descriptors so that we // don't have to re-open anything when we encounter a `..`. This // way, even if the directory is concurrently moved, we don't have @@ -438,9 +424,6 @@ pub(super) fn internal_open<'start>( )?); } - #[cfg(racy_asserts)] - check_internal_open(&ctx, path, options); - Ok(ctx.base) } @@ -525,39 +508,3 @@ fn should_emulate_o_path(use_options: &OpenOptions) -> bool { (use_options.ext.custom_flags & (OFlags::PATH.bits() as i32)) == (OFlags::PATH.bits() as i32) && use_options.follow == FollowSymlinks::Yes } - -#[cfg(racy_asserts)] -fn check_internal_open(ctx: &Context, path: &Path, options: &OpenOptions) { - match open_unchecked( - &ctx.start_clone, - ctx.canonical_path.debug.as_ref(), - options - .clone() - .create(false) - .create_new(false) - .truncate(false), - ) { - Ok(unchecked_file) => { - assert_same_file!( - &ctx.base, - &unchecked_file, - "path resolution inconsistency: start='{:?}', path='{}'; canonical_path='{}'", - ctx.start_clone, - path.display(), - ctx.canonical_path.debug.display(), - ); - } - Err(_unchecked_error) => { - /* TODO: Check error messages. - panic!( - "unexpected success opening result={:?} start='{:?}', path='{}'; canonical_path='{}'; \ - expected {:?}", - ctx.base, - ctx.start_clone, - path.display(), - ctx.canonical_path.debug.display(), - unchecked_error, - */ - } - } -} diff --git a/crates/wasi/src/filesystem/primitives/maybe_owned_file.rs b/crates/wasi/src/filesystem/primitives/maybe_owned_file.rs index 41422c41fa55..22dd48411846 100644 --- a/crates/wasi/src/filesystem/primitives/maybe_owned_file.rs +++ b/crates/wasi/src/filesystem/primitives/maybe_owned_file.rs @@ -3,8 +3,6 @@ use maybe_owned::MaybeOwned; use std::ops::Deref; use std::path::Component; use std::{fmt, fs, io, mem}; -#[cfg(racy_asserts)] -use {crate::fs::file_path, std::path::PathBuf}; /// Several places in the code need to be able to handle either owned or /// borrowed [`std::fs::File]`s. Cloning a `File` to let them always have an @@ -22,35 +20,20 @@ use {crate::fs::file_path, std::path::PathBuf}; /// [`Cow`]: std::borrow::Cow pub(super) struct MaybeOwnedFile<'borrow> { inner: MaybeOwned<'borrow, fs::File>, - - #[cfg(racy_asserts)] - path: Option, } impl<'borrow> MaybeOwnedFile<'borrow> { /// Constructs a new `MaybeOwnedFile` which is not owned. pub(super) fn borrowed(file: &'borrow fs::File) -> Self { - #[cfg(racy_asserts)] - let path = file_path(file); - Self { inner: MaybeOwned::Borrowed(file), - - #[cfg(racy_asserts)] - path, } } /// Constructs a new `MaybeOwnedFile` which is owned. pub(super) fn owned(file: fs::File) -> Self { - #[cfg(racy_asserts)] - let path = file_path(&file); - Self { inner: MaybeOwned::Owned(file), - - #[cfg(racy_asserts)] - path, } } @@ -59,9 +42,6 @@ impl<'borrow> MaybeOwnedFile<'borrow> { pub(super) const fn borrowed_noassert(file: &'borrow fs::File) -> Self { Self { inner: MaybeOwned::Borrowed(file), - - #[cfg(racy_asserts)] - path: None, } } @@ -70,9 +50,6 @@ impl<'borrow> MaybeOwnedFile<'borrow> { pub(super) const fn owned_noassert(file: fs::File) -> Self { Self { inner: MaybeOwned::Owned(file), - - #[cfg(racy_asserts)] - path: None, } } @@ -80,27 +57,8 @@ impl<'borrow> MaybeOwnedFile<'borrow> { /// of the current file. Return a `MaybeOwnedFile` representing the /// previous state. pub(super) fn descend_to(&mut self, to: MaybeOwnedFile<'borrow>) -> Self { - #[cfg(racy_asserts)] - let path = self.path.clone(); - - #[cfg(racy_asserts)] - if let Some(to_path) = file_path(&to) { - if let Some(current_path) = &self.path { - assert!( - to_path.starts_with(current_path), - "attempted to descend from {:?} to {:?}", - to_path.display(), - current_path.display() - ); - } - self.path = Some(to_path); - } - Self { inner: mem::replace(&mut self.inner, to.inner), - - #[cfg(racy_asserts)] - path, } } diff --git a/crates/wasi/src/filesystem/primitives/mod.rs b/crates/wasi/src/filesystem/primitives/mod.rs index 215c97d27ce8..6d9d12278c3f 100644 --- a/crates/wasi/src/filesystem/primitives/mod.rs +++ b/crates/wasi/src/filesystem/primitives/mod.rs @@ -1,11 +1,5 @@ //! Filesystem utilities. -#![allow(warnings)] - -#[cfg(racy_asserts)] -#[macro_use] -pub(crate) mod assert_same_file; - mod access; mod canonicalize; mod copy; @@ -111,14 +105,6 @@ pub use symlink::{symlink, symlink_contents}; pub use symlink::{symlink_dir, symlink_file}; pub use system_time_spec::SystemTimeSpec; -#[cfg(racy_asserts)] -fn map_result(result: &std::io::Result) -> Result { - match result { - Ok(t) => Ok(t.clone()), - Err(e) => Err((e.kind(), e.to_string())), - } -} - /// Test that `file_path` works on a few miscellaneous directory paths. #[test] fn dir_paths() { diff --git a/crates/wasi/src/filesystem/primitives/open.rs b/crates/wasi/src/filesystem/primitives/open.rs index ad351fc67f32..5fc94be9eb4c 100644 --- a/crates/wasi/src/filesystem/primitives/open.rs +++ b/crates/wasi/src/filesystem/primitives/open.rs @@ -1,108 +1,14 @@ //! This defines `open`, the primary entrypoint to sandboxed file and directory //! opening. -#[cfg(racy_asserts)] -use crate::filesystem::primitives::{Metadata, file_path, open_unchecked, stat_unchecked}; use crate::filesystem::primitives::{OpenOptions, open_impl}; use std::path::Path; use std::{fs, io}; /// Perform an `openat`-like operation, 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))] #[inline] pub fn open(start: &fs::File, path: &Path, options: &OpenOptions) -> io::Result { - #[cfg(racy_asserts)] - let stat_before = stat_unchecked(start, path, options.follow); - // Call the underlying implementation. - let result = open_impl(start, path, options); - - #[cfg(racy_asserts)] - let stat_after = stat_unchecked(start, path, options.follow); - - #[cfg(racy_asserts)] - check_open(start, path, options, &stat_before, &result, &stat_after); - - result -} - -#[cfg(racy_asserts)] -fn check_open( - start: &fs::File, - path: &Path, - options: &OpenOptions, - _stat_before: &io::Result, - result: &io::Result, - _stat_after: &io::Result, -) { - let unchecked_result = open_unchecked( - start, - path, - options - .clone() - .create(false) - .create_new(false) - .truncate(false), - ); - - match (&result, &unchecked_result) { - (Ok(result_file), Ok(unchecked_file)) => { - assert_same_file!( - &result_file, - &unchecked_file, - "path resolution inconsistency: start='{:?}', path='{}'", - start, - path.display(), - ); - } - (Ok(result_file), Err(unchecked_error)) => { - if unchecked_error.kind() == io::ErrorKind::PermissionDenied { - assert!(options.create || options.create_new); - } else { - panic!( - "unexpected success opening start='{:?}', path='{}'; expected {:?}; got {:?}", - start, - path.display(), - unchecked_error, - result_file - ); - } - } - (Err(result_error), Ok(_unchecked_file)) => match result_error.kind() { - io::ErrorKind::PermissionDenied | io::ErrorKind::InvalidInput => (), - io::ErrorKind::AlreadyExists if options.create_new => (), - _ => panic!( - "unexpected error opening start='{:?}', path='{}': {:?}", - start, - path.display(), - result_error - ), - }, - (Err(result_error), Err(_unchecked_error)) => match result_error.kind() { - io::ErrorKind::PermissionDenied | io::ErrorKind::InvalidInput => (), - _ => { - /* TODO: Check error messages. - let unchecked_error = unchecked_error.into(); - assert_eq!(result_error.to_string(), unchecked_error.to_string()); - assert_eq!(result_error.kind(), unchecked_error.kind()); - */ - } - }, - } - - // On operating systems which can tell us the path of a file descriptor, - // assert that the start path is a parent of the result path. - if let Ok(result_file) = &result { - if let Some(result_path) = file_path(result_file) { - if let Some(start_path) = file_path(start) { - assert!( - result_path.starts_with(start_path), - "sandbox escape: start='{:?}' result='{}'", - start, - result_path.display() - ); - } - } - } + open_impl(start, path, options) } diff --git a/crates/wasi/src/filesystem/primitives/read_link.rs b/crates/wasi/src/filesystem/primitives/read_link.rs index b804513a67ce..51e49669e24b 100644 --- a/crates/wasi/src/filesystem/primitives/read_link.rs +++ b/crates/wasi/src/filesystem/primitives/read_link.rs @@ -1,33 +1,21 @@ //! This defines `read_link`, the primary entrypoint to sandboxed symbolic link //! dereferencing. -#[cfg(racy_asserts)] -use crate::filesystem::primitives::{FollowSymlinks, map_result, read_link_unchecked, stat}; use crate::filesystem::primitives::{errors, read_link_impl}; use std::path::{Path, PathBuf}; use std::{fs, io}; /// Perform a `readlinkat`-like operation, ensuring that the resolution of the /// link path never escapes the directory tree rooted at `start`. -#[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))] #[inline] pub fn read_link_contents(start: &fs::File, path: &Path) -> io::Result { // Call the underlying implementation. - let result = read_link_impl(start, path); - - #[cfg(racy_asserts)] - let unchecked = read_link_unchecked(start, path, PathBuf::new()); - - #[cfg(racy_asserts)] - check_read_link(start, path, &result, &unchecked); - - result + read_link_impl(start, path) } /// Perform a `readlinkat`-like operation, ensuring that the resolution of the /// path never escapes the directory tree rooted at `start`, and also verifies /// that the link target is not absolute. -#[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))] #[inline] pub fn read_link(start: &fs::File, path: &Path) -> io::Result { // Call the underlying implementation. @@ -47,46 +35,6 @@ pub fn read_link(start: &fs::File, path: &Path) -> io::Result { result } -#[cfg(racy_asserts)] -#[allow(clippy::enum_glob_use)] -fn check_read_link( - start: &fs::File, - path: &Path, - result: &io::Result, - unchecked: &io::Result, -) { - use io::ErrorKind::*; - - match (map_result(result), map_result(unchecked)) { - (Ok(target), Ok(unchecked_target)) => { - assert_eq!(target, unchecked_target); - } - - (Err((PermissionDenied, message)), _) => { - match map_result(&stat(start, path, FollowSymlinks::No)) { - Err((PermissionDenied, canon_message)) => { - assert_eq!(message, canon_message); - } - _ => panic!("read_link failed where canonicalize succeeded"), - } - } - - (Err((_kind, _message)), Err((_unchecked_kind, _unchecked_message))) => { - /* TODO: Check error messages. - assert_eq!(kind, unchecked_kind); - assert_eq!(message, unchecked_message); - */ - } - - other => panic!( - "unexpected result from read_link start='{:?}', path='{}':\n{:#?}", - start, - path.display(), - other, - ), - } -} - #[cfg(not(windows))] #[test] fn test_read_link_contents() { diff --git a/crates/wasi/src/filesystem/primitives/remove_dir.rs b/crates/wasi/src/filesystem/primitives/remove_dir.rs index f70a60c0c260..adb98caea2e4 100644 --- a/crates/wasi/src/filesystem/primitives/remove_dir.rs +++ b/crates/wasi/src/filesystem/primitives/remove_dir.rs @@ -2,147 +2,13 @@ //! removal. use crate::filesystem::primitives::remove_dir_impl; -#[cfg(racy_asserts)] -use crate::filesystem::primitives::{ - FollowSymlinks, Metadata, manually, map_result, remove_dir_unchecked, stat_unchecked, -}; use std::path::Path; use std::{fs, io}; /// Perform a `rmdirat`-like operation, 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))] #[inline] pub fn remove_dir(start: &fs::File, path: &Path) -> io::Result<()> { - #[cfg(racy_asserts)] - let stat_before = stat_unchecked(start, path, FollowSymlinks::No); - // Call the underlying implementation. - let result = remove_dir_impl(start, path); - - #[cfg(racy_asserts)] - let stat_after = stat_unchecked(start, path, FollowSymlinks::No); - - #[cfg(racy_asserts)] - check_remove_dir(start, path, &stat_before, &result, &stat_after); - - result -} - -#[cfg(racy_asserts)] -#[allow(clippy::enum_glob_use)] -fn check_remove_dir( - start: &fs::File, - path: &Path, - stat_before: &io::Result, - result: &io::Result<()>, - stat_after: &io::Result, -) { - use io::ErrorKind::*; - - match ( - map_result(stat_before), - map_result(result), - map_result(stat_after), - ) { - (Ok(metadata), Ok(()), Err((NotFound, _))) => { - // TODO: Check that the path was inside the sandbox. - assert!(metadata.is_dir()); - } - - (Err((Other, _)), Ok(()), Err((NotFound, _))) => { - // TODO: Check that the path was inside the sandbox. - } - - (_, Err((InvalidInput, _)), _) => { - // `remove_dir(".")` apparently returns `EINVAL` - } - - (_, Err((kind, message)), _) => { - match map_result(&manually::canonicalize_with( - start, - path, - FollowSymlinks::No, - )) { - Ok(canon) => match map_result(&remove_dir_unchecked(start, &canon)) { - Err((_unchecked_kind, _unchecked_message)) => { - /* TODO: Check error messages. - assert_eq!( - kind, - unchecked_kind, - "unexpected error kind from remove_dir start='{:?}', \ - path='{}':\nstat_before={:#?}\nresult={:#?}\nstat_after={:#?}", - start, - path.display(), - stat_before, - result, - stat_after - ); - assert_eq!(message, unchecked_message); - */ - } - _ => { - // TODO: Checking in the case it does end with ".". - if !path.to_string_lossy().ends_with(".") { - panic!( - "unsandboxed remove_dir success on start={:?} path={:?}; expected \ - {:?}: {}", - start, path, kind, message - ); - } - } - }, - Err((_canon_kind, _canon_message)) => { - /* TODO: Check error messages. - assert_eq!(kind, canon_kind, "'{}' vs '{}'", message, canon_message); - assert_eq!(message, canon_message); - */ - } - } - } - - other => panic!( - "inconsistent remove_dir checks: start='{:?}' path='{}':\n{:#?}", - start, - path.display(), - other, - ), - } - - match stat_after { - Ok(unchecked_metadata) => match &result { - Ok(()) => panic!( - "file still exists after remove_dir start='{:?}', path='{}'", - start, - path.display() - ), - Err(e) => match e.kind() { - #[cfg(io_error_more)] - io::ErrorKind::NotADirectory => assert!(!unchecked_metadata.is_dir()), - #[cfg(io_error_more)] - io::ErrorKind::DirectoryNotEmpty => (), - io::ErrorKind::PermissionDenied - | io::ErrorKind::InvalidInput // `remove_dir(".")` apparently returns `EINVAL` - | io::ErrorKind::Other => (), // directory not empty, among other things - _ => panic!( - "unexpected error remove_dir'ing start='{:?}', path='{}': {:?}", - start, - path.display(), - e - ), - }, - }, - Err(_unchecked_error) => match &result { - Ok(()) => (), - Err(result_error) => match result_error.kind() { - io::ErrorKind::PermissionDenied => (), - _ => { - /* TODO: Check error messages. - assert_eq!(result_error.to_string(), unchecked_error.to_string()); - assert_eq!(result_error.kind(), unchecked_error.kind()); - */ - } - }, - }, - } + remove_dir_impl(start, path) } diff --git a/crates/wasi/src/filesystem/primitives/remove_file.rs b/crates/wasi/src/filesystem/primitives/remove_file.rs index d82ed3177a83..a678ea039bd8 100644 --- a/crates/wasi/src/filesystem/primitives/remove_file.rs +++ b/crates/wasi/src/filesystem/primitives/remove_file.rs @@ -2,128 +2,13 @@ //! removal. use crate::filesystem::primitives::remove_file_impl; -#[cfg(racy_asserts)] -use crate::filesystem::primitives::{ - FollowSymlinks, Metadata, manually, map_result, remove_file_unchecked, stat_unchecked, -}; use std::path::Path; use std::{fs, io}; /// Perform a `remove_fileat`-like operation, 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))] #[inline] pub fn remove_file(start: &fs::File, path: &Path) -> io::Result<()> { - #[cfg(racy_asserts)] - let stat_before = stat_unchecked(start, path, FollowSymlinks::No); - // Call the underlying implementation. - let result = remove_file_impl(start, path); - - #[cfg(racy_asserts)] - let stat_after = stat_unchecked(start, path, FollowSymlinks::No); - - #[cfg(racy_asserts)] - check_remove_file(start, path, &stat_before, &result, &stat_after); - - result -} - -#[cfg(racy_asserts)] -#[allow(clippy::enum_glob_use)] -fn check_remove_file( - start: &fs::File, - path: &Path, - stat_before: &io::Result, - result: &io::Result<()>, - stat_after: &io::Result, -) { - use io::ErrorKind::*; - - match ( - map_result(stat_before), - map_result(result), - map_result(stat_after), - ) { - (Ok(metadata), Ok(()), Err((NotFound, _))) => { - // TODO: Check that the path was inside the sandbox. - assert!(!metadata.is_dir()); - } - - (Err((Other, _)), Ok(()), Err((NotFound, _))) => { - // TODO: Check that the path was inside the sandbox. - } - - (_, Err((_kind, _message)), _) => { - match map_result(&manually::canonicalize_with( - start, - path, - FollowSymlinks::No, - )) { - Ok(canon) => match map_result(&remove_file_unchecked(start, &canon)) { - Err((_unchecked_kind, _unchecked_message)) => { - /* TODO: Check error messages. - assert_eq!( - kind, - unchecked_kind, - "unexpected error kind from remove_file start='{:?}', \ - path='{}':\nstat_before={:#?}\nresult={:#?}\nstat_after={:#?}", - start, - path.display(), - stat_before, - result, - stat_after - ); - assert_eq!(message, unchecked_message); - */ - } - _ => panic!("unsandboxed remove_file success"), - }, - Err((_canon_kind, _canon_message)) => { - /* TODO: Check error messages. - assert_eq!(kind, canon_kind, "'{}' vs '{}'", message, canon_message); - assert_eq!(message, canon_message); - */ - } - } - } - - other => panic!( - "inconsistent remove_file checks: start='{:?}' path='{}':\n{:#?}", - start, - path.display(), - other, - ), - } - - match (result, stat_after) { - (Ok(()), Ok(_unchecked_metadata)) => panic!( - "file still exists after remove_file start='{:?}', path='{}'", - start, - path.display() - ), - (Err(e), Ok(unchecked_metadata)) => match e.kind() { - #[cfg(io_error_more)] - io::ErrorKind::IsADirectory => assert!(unchecked_metadata.is_dir()), - io::ErrorKind::PermissionDenied => (), - #[cfg(not(io_error_more))] - io::ErrorKind::Other if unchecked_metadata.is_dir() => (), - _ => panic!( - "unexpected error removing file start='{:?}', path='{}': {:?}", - start, - path.display(), - e - ), - }, - (Ok(()), Err(_unchecked_error)) => (), - (Err(result_error), Err(_unchecked_error)) => match result_error.kind() { - io::ErrorKind::PermissionDenied => (), - _ => { - /* TODO: Check error messages. - assert_eq!(result_error.to_string(), unchecked_error.to_string()); - assert_eq!(result_error.kind(), unchecked_error.kind()); - */ - } - }, - } + remove_file_impl(start, path) } diff --git a/crates/wasi/src/filesystem/primitives/rename.rs b/crates/wasi/src/filesystem/primitives/rename.rs index 7b2e720aa262..18ce6930c5cf 100644 --- a/crates/wasi/src/filesystem/primitives/rename.rs +++ b/crates/wasi/src/filesystem/primitives/rename.rs @@ -1,23 +1,12 @@ //! This defines `rename`, the primary entrypoint to sandboxed renaming. -#[cfg(all(racy_asserts, not(windows)))] -use crate::filesystem::primitives::append_dir_suffix; use crate::filesystem::primitives::rename_impl; use std::path::Path; use std::{fs, io}; -#[cfg(racy_asserts)] -use { - crate::fs::{ - FollowSymlinks, Metadata, manually, map_result, path_requires_dir, rename_unchecked, - stat_unchecked, - }, - std::path::PathBuf, -}; /// Perform a `renameat`-like operation, ensuring that the resolution of both /// the old and new paths never escape the directory tree rooted at their /// respective starts. -#[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))] #[inline] pub fn rename( old_start: &fs::File, @@ -25,134 +14,6 @@ pub fn rename( new_start: &fs::File, new_path: &Path, ) -> io::Result<()> { - #[cfg(racy_asserts)] - let (old_metadata_before, new_metadata_before) = ( - stat_unchecked(old_start, old_path, FollowSymlinks::No), - stat_unchecked(new_start, new_path, FollowSymlinks::No), - ); - // Call the underlying implementation. - let result = rename_impl(old_start, old_path, new_start, new_path); - - #[cfg(racy_asserts)] - let (old_metadata_after, new_metadata_after) = ( - stat_unchecked(old_start, old_path, FollowSymlinks::No), - stat_unchecked(new_start, new_path, FollowSymlinks::No), - ); - - #[cfg(racy_asserts)] - check_rename( - old_start, - old_path, - new_start, - new_path, - &old_metadata_before, - &new_metadata_before, - &result, - &old_metadata_after, - &new_metadata_after, - ); - - result -} - -#[cfg(racy_asserts)] -#[allow(clippy::too_many_arguments)] -#[allow(clippy::enum_glob_use)] -fn check_rename( - old_start: &fs::File, - old_path: &Path, - new_start: &fs::File, - new_path: &Path, - old_metadata_before: &io::Result, - new_metadata_before: &io::Result, - result: &io::Result<()>, - old_metadata_after: &io::Result, - new_metadata_after: &io::Result, -) { - use io::ErrorKind::*; - - match ( - map_result(old_metadata_before), - map_result(new_metadata_before), - map_result(result), - map_result(old_metadata_after), - map_result(new_metadata_after), - ) { - ( - Ok(old_metadata_before), - Err((NotFound, _)), - Ok(()), - Err((NotFound, _)), - Ok(new_metadata_after), - ) => { - assert_same_file_metadata!(&old_metadata_before, &new_metadata_after); - } - - (_, Ok(new_metadata_before), Err((AlreadyExists, _)), _, Ok(new_metadata_after)) => { - assert_same_file_metadata!(&new_metadata_before, &new_metadata_after); - } - - (_, _, Err((kind, message)), _, _) => match ( - map_result(&canonicalize_for_rename(old_start, old_path)), - map_result(&canonicalize_for_rename(new_start, new_path)), - ) { - (Ok(old_canon), Ok(new_canon)) => match map_result(&rename_unchecked( - old_start, &old_canon, new_start, &new_canon, - )) { - Err((_unchecked_kind, _unchecked_message)) => { - /* TODO: Check error messages. - assert_eq!(kind, unchecked_kind); - assert_eq!(message, unchecked_message); - */ - } - other => panic!( - "unsandboxed rename success:\n{:#?}\n{:?} {:?}", - other, kind, message - ), - }, - (Err((_old_canon_kind, _old_canon_message)), _) => { - /* TODO: Check error messages. - assert_eq!(kind, old_canon_kind); - assert_eq!(message, old_canon_message); - */ - } - (_, Err((_new_canon_kind, _new_canon_message))) => { - /* TODO: Check error messages. - assert_eq!(kind, new_canon_kind); - assert_eq!(message, new_canon_message); - */ - } - }, - - _other => { - /* TODO: Check error messages. - panic!( - "inconsistent rename checks: old_start='{:?}', old_path='{}', new_start='{:?}', \ - new_path='{}':\n{:#?}", - old_start, - old_path.display(), - new_start, - new_path.display(), - other - ) - */ - } - } -} - -#[cfg(racy_asserts)] -fn canonicalize_for_rename(start: &fs::File, path: &Path) -> io::Result { - let mut canon = manually::canonicalize_with(start, path, FollowSymlinks::No)?; - - // Rename on paths ending in `.` or `/.` fails due to the directory already - // being open. Ensure that this happens on the canonical paths too. - #[cfg(not(windows))] - if path_requires_dir(path) { - canon = append_dir_suffix(path.to_path_buf()); - - assert!(path_requires_dir(&canon)); - } - - Ok(canon) + rename_impl(old_start, old_path, new_start, new_path) } diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/dir_utils.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/dir_utils.rs index b0eab6f211d5..c3f16dad06e5 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/dir_utils.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/dir_utils.rs @@ -151,17 +151,6 @@ pub(crate) const fn target_o_path() -> OFlags { } } -#[cfg(racy_asserts)] -#[test] -fn append_dir_suffix_tests() { - assert!( - append_dir_suffix(Path::new("foo").to_path_buf()) - .display() - .to_string() - .ends_with('/') - ); -} - #[test] fn strip_dir_suffix_tests() { assert_eq!(&*strip_dir_suffix(Path::new("/foo//")), Path::new("/foo")); diff --git a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/canonicalize_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/canonicalize_impl.rs index 8f0a9d05d385..457df1b2d9c1 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/canonicalize_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/canonicalize_impl.rs @@ -27,19 +27,6 @@ pub(crate) fn canonicalize_impl(start: &fs::File, path: &Path) -> io::Result { let file = fs::File::from_into_fd(file); - #[cfg(racy_asserts)] - check_open(start, path, options, &file); - return Ok(file); } Err(err) => match err { @@ -130,21 +125,3 @@ pub(crate) fn open_beneath( err => err.into(), }) } - -#[cfg(racy_asserts)] -fn check_open(start: &fs::File, path: &Path, options: &OpenOptions, file: &fs::File) { - let check = manually::open( - start, - path, - options - .clone() - .create(false) - .create_new(false) - .truncate(false), - ) - .expect("manually::open failed when open_openat2 succeeded"); - assert!( - is_same_file(file, &check).unwrap(), - "manually::open should open the same inode as open_openat2" - ); -} diff --git a/crates/wasi/src/filesystem/primitives/set_permissions.rs b/crates/wasi/src/filesystem/primitives/set_permissions.rs index 6bbdd98a3040..86821288aa63 100644 --- a/crates/wasi/src/filesystem/primitives/set_permissions.rs +++ b/crates/wasi/src/filesystem/primitives/set_permissions.rs @@ -1,8 +1,6 @@ //! This defines `set_permissions`, the primary entrypoint to sandboxed //! filesystem permissions modification. -#[cfg(racy_asserts)] -use crate::filesystem::primitives::{FollowSymlinks, Metadata, map_result, stat, stat_unchecked}; use crate::filesystem::primitives::{ Permissions, set_permissions_impl, set_symlink_permissions_impl, }; @@ -11,81 +9,17 @@ use std::{fs, io}; /// Perform a `chmodat`-like operation, 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))] #[inline] pub fn set_permissions(start: &fs::File, path: &Path, perm: Permissions) -> io::Result<()> { - #[cfg(racy_asserts)] - let perm_clone = perm.clone(); - - #[cfg(racy_asserts)] - let stat_before = stat(start, path, FollowSymlinks::Yes); - // Call the underlying implementation. - let result = set_permissions_impl(start, path, perm); - - #[cfg(racy_asserts)] - let stat_after = stat_unchecked(start, path, FollowSymlinks::Yes); - - #[cfg(racy_asserts)] - check_set_permissions(start, path, perm_clone, &stat_before, &result, &stat_after); - - result + set_permissions_impl(start, path, perm) } /// Perform a `chmodat`-like operation, ensuring that the resolution of the /// path never escapes the directory tree rooted at `start`, without following /// symlinks. -#[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))] #[inline] pub fn set_symlink_permissions(start: &fs::File, path: &Path, perm: Permissions) -> io::Result<()> { - #[cfg(racy_asserts)] - let perm_clone = perm.clone(); - - #[cfg(racy_asserts)] - let stat_before = stat(start, path, FollowSymlinks::No); - // Call the underlying implementation. - let result = set_symlink_permissions_impl(start, path, perm); - - #[cfg(racy_asserts)] - let stat_after = stat_unchecked(start, path, FollowSymlinks::No); - - #[cfg(racy_asserts)] - check_set_permissions(start, path, perm_clone, &stat_before, &result, &stat_after); - - result -} - -#[cfg(racy_asserts)] -fn check_set_permissions( - start: &fs::File, - path: &Path, - perm: Permissions, - stat_before: &io::Result, - result: &io::Result<()>, - stat_after: &io::Result, -) { - match ( - map_result(stat_before), - map_result(result), - map_result(stat_after), - ) { - (Ok(_), Ok(()), Ok(metadata)) => { - assert_eq!(perm, metadata.permissions()); - } - - (Ok(metadata_before), Err(_), Ok(metadata_after)) => { - assert_eq!(metadata_before.permissions(), metadata_after.permissions()); - } - - // TODO: Check error messages - (Err(_), Err(_), Err(_)) => (), - - other => panic!( - "inconsistent set_permissions checks: start='{:?}' path='{}':\n{:#?}", - start, - path.display(), - other, - ), - } + set_symlink_permissions_impl(start, path, perm) } diff --git a/crates/wasi/src/filesystem/primitives/stat.rs b/crates/wasi/src/filesystem/primitives/stat.rs index 984e400bb5f3..f5ef8037953c 100644 --- a/crates/wasi/src/filesystem/primitives/stat.rs +++ b/crates/wasi/src/filesystem/primitives/stat.rs @@ -1,81 +1,13 @@ //! This defines `stat`, the primary entrypoint to sandboxed metadata querying. use crate::filesystem::primitives::{FollowSymlinks, Metadata, stat_impl}; -#[cfg(racy_asserts)] -use crate::filesystem::primitives::{canonicalize, map_result, stat_unchecked}; use std::path::Path; use std::{fs, io}; /// Perform an `fstatat`-like operation, 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))] #[inline] pub fn stat(start: &fs::File, path: &Path, follow: FollowSymlinks) -> io::Result { // Call the underlying implementation. - let result = stat_impl(start, path, follow); - - #[cfg(racy_asserts)] - let stat = stat_unchecked(start, path, follow); - - #[cfg(racy_asserts)] - check_stat(start, path, follow, &result, &stat); - - result -} - -#[cfg(racy_asserts)] -#[allow(clippy::enum_glob_use)] -fn check_stat( - start: &fs::File, - path: &Path, - follow: FollowSymlinks, - result: &io::Result, - stat: &io::Result, -) { - use io::ErrorKind::*; - - match (map_result(result), map_result(stat)) { - (Ok(metadata), Ok(unchecked_metadata)) => { - assert_same_file_metadata!( - metadata, - unchecked_metadata, - "path resolution inconsistency: start='{:?}', path='{}'", - start, - path.display(), - ); - } - - (Err((PermissionDenied, message)), _) => { - if let FollowSymlinks::Yes = follow { - match map_result(&canonicalize(start, path)) { - Ok(_) => (), - Err((PermissionDenied, canon_message)) => { - assert_eq!(message, canon_message); - } - err => panic!("stat failed where canonicalize succeeded: {:?}", err), - } - } else { - // TODO: Check that stat 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 stat start='{:?}', path='{}':\n{:#?}", - start, - path.display(), - other, - ), - } + stat_impl(start, path, follow) } diff --git a/crates/wasi/src/filesystem/primitives/symlink.rs b/crates/wasi/src/filesystem/primitives/symlink.rs index c276872c72be..49b1bf7f0f5b 100644 --- a/crates/wasi/src/filesystem/primitives/symlink.rs +++ b/crates/wasi/src/filesystem/primitives/symlink.rs @@ -2,21 +2,12 @@ //! creation. use crate::filesystem::primitives::errors; -#[cfg(all(racy_asserts, not(windows)))] -use crate::filesystem::primitives::symlink_unchecked; -#[cfg(racy_asserts)] -use crate::filesystem::primitives::{ - FollowSymlinks, Metadata, canonicalize, manually, map_result, stat_unchecked, -}; -#[cfg(all(racy_asserts, windows))] -use crate::filesystem::primitives::{symlink_dir_unchecked, symlink_file_unchecked}; use std::path::Path; use std::{fs, io}; /// Perform a `symlinkat`-like operation, ensuring that the resolution of the /// path never escapes the directory tree rooted at `start`. An error is /// returned if the target path is absolute. -#[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))] #[cfg(not(windows))] #[inline] pub fn symlink(old_path: &Path, new_start: &fs::File, new_path: &Path) -> io::Result<()> { @@ -36,26 +27,8 @@ pub fn symlink(old_path: &Path, new_start: &fs::File, new_path: &Path) -> io::Re fn write_symlink_impl(old_path: &Path, new_start: &fs::File, new_path: &Path) -> io::Result<()> { use crate::filesystem::primitives::symlink_impl; - #[cfg(racy_asserts)] - let stat_before = stat_unchecked(new_start, new_path, FollowSymlinks::No); - // Call the underlying implementation. - let result = symlink_impl(old_path, new_start, new_path); - - #[cfg(racy_asserts)] - let stat_after = stat_unchecked(new_start, new_path, FollowSymlinks::No); - - #[cfg(racy_asserts)] - check_symlink( - old_path, - new_start, - new_path, - &stat_before, - &result, - &stat_after, - ); - - result + symlink_impl(old_path, new_start, new_path) } /// Perform a `symlinkat`-like operation, ensuring that the resolution of the @@ -71,7 +44,6 @@ pub fn symlink_contents, Q: AsRef>( /// Perform a `symlink_file`-like operation, 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))] #[cfg(windows)] #[inline] pub fn symlink_file(old_path: &Path, new_start: &fs::File, new_path: &Path) -> io::Result<()> { @@ -82,31 +54,12 @@ pub fn symlink_file(old_path: &Path, new_start: &fs::File, new_path: &Path) -> i return Err(errors::escape_attempt()); } - #[cfg(racy_asserts)] - let stat_before = stat_unchecked(new_start, new_path, FollowSymlinks::No); - // Call the underlying implementation. - let result = symlink_file_impl(old_path, new_start, new_path); - - #[cfg(racy_asserts)] - let stat_after = stat_unchecked(new_start, new_path, FollowSymlinks::No); - - #[cfg(racy_asserts)] - check_symlink_file( - old_path, - new_start, - new_path, - &stat_before, - &result, - &stat_after, - ); - - result + symlink_file_impl(old_path, new_start, new_path) } /// Perform a `symlink_dir`-like operation, 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))] #[cfg(windows)] #[inline] pub fn symlink_dir(old_path: &Path, new_start: &fs::File, new_path: &Path) -> io::Result<()> { @@ -117,240 +70,6 @@ pub fn symlink_dir(old_path: &Path, new_start: &fs::File, new_path: &Path) -> io return Err(errors::escape_attempt()); } - #[cfg(racy_asserts)] - let stat_before = stat_unchecked(new_start, new_path, FollowSymlinks::No); - // Call the underlying implementation. - let result = symlink_dir_impl(old_path, new_start, new_path); - - #[cfg(racy_asserts)] - let stat_after = stat_unchecked(new_start, new_path, FollowSymlinks::No); - - #[cfg(racy_asserts)] - check_symlink_dir( - old_path, - new_start, - new_path, - &stat_before, - &result, - &stat_after, - ); - - result -} - -#[cfg(all(not(windows), racy_asserts))] -#[allow(clippy::enum_glob_use)] -fn check_symlink( - old_path: &Path, - new_start: &fs::File, - new_path: &Path, - stat_before: &io::Result, - result: &io::Result<()>, - stat_after: &io::Result, -) { - use io::ErrorKind::*; - - match ( - map_result(stat_before), - map_result(result), - map_result(stat_after), - ) { - (Err((NotFound, _)), Ok(()), Ok(metadata)) => { - assert!(metadata.file_type().is_symlink()); - let canon = - manually::canonicalize_with(new_start, new_path, FollowSymlinks::No).unwrap(); - assert_same_file_metadata!( - &stat_unchecked(new_start, &canon, FollowSymlinks::No).unwrap(), - &metadata - ); - } - - (Ok(metadata_before), Err((AlreadyExists, _)), Ok(metadata_after)) => { - assert_same_file_metadata!(&metadata_before, &metadata_after); - } - - (_, Err((_kind, _message)), _) => match map_result(&canonicalize(new_start, new_path)) { - Ok(canon) => match map_result(&symlink_unchecked(old_path, new_start, &canon)) { - Err((_unchecked_kind, _unchecked_message)) => { - /* TODO: Check error messages. - assert_eq!( - kind, - unchecked_kind, - "unexpected error kind from symlink new_start='{:?}', \ - new_path='{}':\nstat_before={:#?}\nresult={:#?}\nstat_after={:#?}", - new_start, - new_path.display(), - stat_before, - result, - stat_after - ); - assert_eq!(message, unchecked_message); - */ - } - _ => panic!("unsandboxed symlink success"), - }, - Err((_canon_kind, _canon_message)) => { - /* TODO: Check error messages. - assert_eq!(kind, canon_kind); - assert_eq!(message, canon_message); - */ - } - }, - - _other => { - /* TODO: Check error messages. - panic!( - "inconsistent symlink checks: new_start='{:?}' new_path='{}':\n{:#?}", - new_start, - new_path.display(), - other, - ) - */ - } - } -} - -#[cfg(all(windows, racy_asserts))] -#[allow(clippy::enum_glob_use)] -fn check_symlink_file( - old_path: &Path, - new_start: &fs::File, - new_path: &Path, - stat_before: &io::Result, - result: &io::Result<()>, - stat_after: &io::Result, -) { - use io::ErrorKind::*; - - match ( - map_result(stat_before), - map_result(result), - map_result(stat_after), - ) { - (Err((NotFound, _)), Ok(()), Ok(metadata)) => { - assert!(metadata.file_type().is_symlink()); - let canon = - manually::canonicalize_with(new_start, new_path, FollowSymlinks::No).unwrap(); - assert_same_file_metadata!( - &stat_unchecked(new_start, &canon, FollowSymlinks::No).unwrap(), - &metadata - ); - } - - (Ok(metadata_before), Err((AlreadyExists, _)), Ok(metadata_after)) => { - assert_same_file_metadata!(&metadata_before, &metadata_after); - } - - (_, Err((_kind, _message)), _) => match map_result(&canonicalize(new_start, new_path)) { - Ok(canon) => match map_result(&symlink_file_unchecked(old_path, new_start, &canon)) { - Err((_unchecked_kind, _unchecked_message)) => { - /* TODO: Check error messages. - assert_eq!( - kind, - unchecked_kind, - "unexpected error kind from symlink new_start='{:?}', \ - new_path='{}':\nstat_before={:#?}\nresult={:#?}\nstat_after={:#?}", - new_start, - new_path.display(), - stat_before, - result, - stat_after - ); - assert_eq!(message, unchecked_message); - */ - } - _ => panic!("unsandboxed symlink success"), - }, - Err((_canon_kind, _canon_message)) => { - /* TODO: Check error messages. - assert_eq!(kind, canon_kind); - assert_eq!(message, canon_message); - */ - } - }, - - _other => { - /* TODO: Check error messages. - panic!( - "inconsistent symlink checks: new_start='{:?}' new_path='{}':\n{:#?}", - new_start, - new_path.display(), - other, - ) - */ - } - } -} - -#[cfg(all(windows, racy_asserts))] -#[allow(clippy::enum_glob_use)] -fn check_symlink_dir( - old_path: &Path, - new_start: &fs::File, - new_path: &Path, - stat_before: &io::Result, - result: &io::Result<()>, - stat_after: &io::Result, -) { - use io::ErrorKind::*; - - match ( - map_result(stat_before), - map_result(result), - map_result(stat_after), - ) { - (Err((NotFound, _)), Ok(()), Ok(metadata)) => { - assert!(metadata.file_type().is_symlink()); - let canon = - manually::canonicalize_with(new_start, new_path, FollowSymlinks::No).unwrap(); - assert_same_file_metadata!( - &stat_unchecked(new_start, &canon, FollowSymlinks::No).unwrap(), - &metadata - ); - } - - (Ok(metadata_before), Err((AlreadyExists, _)), Ok(metadata_after)) => { - assert_same_file_metadata!(&metadata_before, &metadata_after); - } - - (_, Err((_kind, _message)), _) => match map_result(&canonicalize(new_start, new_path)) { - Ok(canon) => match map_result(&symlink_dir_unchecked(old_path, new_start, &canon)) { - Err((_unchecked_kind, _unchecked_message)) => { - /* TODO: Check error messages. - assert_eq!( - kind, - unchecked_kind, - "unexpected error kind from symlink new_start='{:?}', \ - new_path='{}':\nstat_before={:#?}\nresult={:#?}\nstat_after={:#?}", - new_start, - new_path.display(), - stat_before, - result, - stat_after - ); - assert_eq!(message, unchecked_message); - */ - } - _ => panic!("unsandboxed symlink success"), - }, - Err((_canon_kind, _canon_message)) => { - /* TODO: Check error messages. - assert_eq!(kind, canon_kind); - assert_eq!(message, canon_message); - */ - } - }, - - _other => { - /* TODO: Check error messages. - panic!( - "inconsistent symlink checks: new_start='{:?}' new_path='{}':\n{:#?}", - new_start, - new_path.display(), - other, - ) - */ - } - } + symlink_dir_impl(old_path, new_start, new_path) } diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/is_same_file.rs b/crates/wasi/src/filesystem/primitives/windows/fs/is_same_file.rs index e14325650a1c..d64986385c73 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/is_same_file.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/is_same_file.rs @@ -18,40 +18,3 @@ pub(crate) fn is_same_file_metadata(a: &Metadata, b: &Metadata) -> io::Result io::Result { - #[cfg(windows_by_handle)] - { - is_same_file(a, b).map(|same| !same) - } - - #[cfg(not(windows_by_handle))] - { - let a_metadata = Metadata::from_std(a.metadata()?); - let b_metadata = Metadata::from_std(b.metadata()?); - is_different_file_metadata(&a_metadata, &b_metadata) - } -} - -/// Determine if `a` and `b` are metadata for definitely different inodes. -/// -/// This is similar to `is_same_file_metadata`, but is conservative, and -/// doesn't depend on nightly-only features. -#[cfg(racy_asserts)] -pub(crate) fn is_different_file_metadata(a: &Metadata, b: &Metadata) -> io::Result { - #[cfg(windows_by_handle)] - { - is_same_file_metadata(a, b).map(|same| !same) - } - - #[cfg(not(windows_by_handle))] - { - // Conservatively just compare creation times. - Ok(a.created()? != b.created()?) - } -} From 2b6cc09237875fc78c4331a524be2c1613a5a33b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 27 Aug 2026 11:56:25 -0700 Subject: [PATCH 06/34] Remove windows-specific cfgs These are related to nightly features that cap-primitives optionally used, and Wasmtime won't be using any nightly features from Wasmtime. --- .../src/filesystem/primitives/dir_entry.rs | 3 +- .../src/filesystem/primitives/file_type.rs | 26 ---------- .../src/filesystem/primitives/metadata.rs | 29 +---------- crates/wasi/src/filesystem/primitives/mod.rs | 2 +- .../primitives/windows/fs/dir_entry_inner.rs | 9 ---- .../primitives/windows/fs/file_type_ext.rs | 31 ----------- .../primitives/windows/fs/is_same_file.rs | 11 ---- .../primitives/windows/fs/metadata_ext.rs | 51 +------------------ 8 files changed, 4 insertions(+), 158 deletions(-) diff --git a/crates/wasi/src/filesystem/primitives/dir_entry.rs b/crates/wasi/src/filesystem/primitives/dir_entry.rs index 3b586a1f6d18..7292d4311497 100644 --- a/crates/wasi/src/filesystem/primitives/dir_entry.rs +++ b/crates/wasi/src/filesystem/primitives/dir_entry.rs @@ -102,8 +102,7 @@ impl DirEntry { self.inner.file_name() } - #[cfg(any(not(windows), windows_by_handle))] - #[cfg_attr(windows, allow(dead_code))] + #[cfg(not(windows))] #[inline] pub(crate) fn is_same_file(&self, metadata: &Metadata) -> io::Result { self.inner.is_same_file(metadata) diff --git a/crates/wasi/src/filesystem/primitives/file_type.rs b/crates/wasi/src/filesystem/primitives/file_type.rs index 6fcab1d83f18..1166aa2640e1 100644 --- a/crates/wasi/src/filesystem/primitives/file_type.rs +++ b/crates/wasi/src/filesystem/primitives/file_type.rs @@ -122,32 +122,6 @@ impl FileTypeExt for FileType { } } -/// Windows-specific extensions for [`FileType`]. -/// -/// This corresponds to [`std::os::windows::fs::FileTypeExt`]. -#[cfg(all(windows, windows_file_type_ext))] -pub trait FileTypeExt { - /// Returns `true` if this file type is a symbolic link that is also a - /// directory. - fn is_symlink_dir(&self) -> bool; - /// Returns `true` if this file type is a symbolic link that is also a - /// file. - fn is_symlink_file(&self) -> bool; -} - -#[cfg(all(windows, windows_file_type_ext))] -impl FileTypeExt for FileType { - #[inline] - fn is_symlink_dir(&self) -> bool { - self.0 == Inner::Ext(ImplFileTypeExt::symlink_dir()) - } - - #[inline] - fn is_symlink_file(&self) -> bool { - self.0 == Inner::Ext(ImplFileTypeExt::symlink_file()) - } -} - /// Extension trait to allow `is_block_device` etc. to be exposed by /// the `cap-fs-ext` crate. /// diff --git a/crates/wasi/src/filesystem/primitives/metadata.rs b/crates/wasi/src/filesystem/primitives/metadata.rs index 087b4eae9a0c..96474c21eb94 100644 --- a/crates/wasi/src/filesystem/primitives/metadata.rs +++ b/crates/wasi/src/filesystem/primitives/metadata.rs @@ -184,7 +184,7 @@ impl Metadata { /// Determine if `self` and `other` refer to the same inode on the same /// device. - #[cfg(any(not(windows), windows_by_handle))] + #[cfg(not(windows))] pub(crate) fn is_same_file(&self, other: &Self) -> bool { self.ext.is_same_file(&other.ext) } @@ -267,15 +267,6 @@ pub trait MetadataExt { fn last_write_time(&self) -> u64; /// Returns the value of the `nFileSize{High,Low}` fields of this metadata. fn file_size(&self) -> u64; - /// Returns the value of the `dwVolumeSerialNumber` field of this metadata. - #[cfg(windows_by_handle)] - fn volume_serial_number(&self) -> Option; - /// Returns the value of the `nNumberOfLinks` field of this metadata. - #[cfg(windows_by_handle)] - fn number_of_links(&self) -> Option; - /// Returns the value of the `nFileIndex{Low,High}` fields of this metadata. - #[cfg(windows_by_handle)] - fn file_index(&self) -> Option; } #[cfg(unix)] @@ -488,24 +479,6 @@ impl MetadataExt for Metadata { fn file_size(&self) -> u64 { self.ext.file_size() } - - #[inline] - #[cfg(windows_by_handle)] - fn volume_serial_number(&self) -> Option { - self.ext.volume_serial_number() - } - - #[inline] - #[cfg(windows_by_handle)] - fn number_of_links(&self) -> Option { - self.ext.number_of_links() - } - - #[inline] - #[cfg(windows_by_handle)] - fn file_index(&self) -> Option { - self.ext.file_index() - } } /// Extension trait to allow `volume_serial_number` etc. to be exposed by diff --git a/crates/wasi/src/filesystem/primitives/mod.rs b/crates/wasi/src/filesystem/primitives/mod.rs index 6d9d12278c3f..ef32da161bd9 100644 --- a/crates/wasi/src/filesystem/primitives/mod.rs +++ b/crates/wasi/src/filesystem/primitives/mod.rs @@ -72,7 +72,7 @@ pub use file::FileExt; #[cfg(windows)] pub use file_type::_WindowsFileTypeExt; pub use file_type::FileType; -#[cfg(any(unix, target_os = "vxworks", all(windows, windows_file_type_ext)))] +#[cfg(any(unix, target_os = "vxworks"))] pub use file_type::FileTypeExt; pub use follow_symlinks::FollowSymlinks; pub use hard_link::hard_link; diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/dir_entry_inner.rs b/crates/wasi/src/filesystem/primitives/windows/fs/dir_entry_inner.rs index b7960b76c5f7..3dabd69d5cb0 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/dir_entry_inner.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/dir_entry_inner.rs @@ -91,15 +91,6 @@ impl DirEntryInner { self.std.file_name() } - #[inline] - #[cfg(windows_by_handle)] - pub(crate) fn is_same_file(&self, metadata: &Metadata) -> io::Result { - // Don't use `self.metadata()`, because that doesn't include the - // volume serial number which we need. - // - Ok(Metadata::from_just_metadata(fs::metadata(self.std.path())?).is_same_file(metadata)) - } - #[inline] pub(super) fn from_std(std: fs::DirEntry) -> Self { Self { std } diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/file_type_ext.rs b/crates/wasi/src/filesystem/primitives/windows/fs/file_type_ext.rs index e98b5e2d6767..084b805dc188 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/file_type_ext.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/file_type_ext.rs @@ -6,10 +6,6 @@ use std::{fs, io}; pub(crate) enum ImplFileTypeExt { CharacterDevice, Fifo, - #[cfg(windows_file_type_ext)] - SymlinkFile, - #[cfg(windows_file_type_ext)] - SymlinkDir, SymlinkUnknown, } @@ -54,17 +50,6 @@ impl ImplFileTypeExt { return FileType::dir(); } - #[cfg(windows_file_type_ext)] - { - use std::os::windows::fs::FileTypeExt; - if std.is_symlink_file() { - return FileType::ext(Self::SymlinkFile); - } - if std.is_symlink_dir() { - return FileType::ext(Self::SymlinkDir); - } - } - if std.is_symlink() { return FileType::ext(Self::SymlinkUnknown); } @@ -72,25 +57,9 @@ impl ImplFileTypeExt { FileType::unknown() } - /// Creates a `FileType` for which `is_symlink_file()` returns `true`. - #[cfg(windows_file_type_ext)] - #[inline] - pub(crate) const fn symlink_file() -> Self { - Self::SymlinkFile - } - - /// Creates a `FileType` for which `is_symlink_dir()` returns `true`. - #[cfg(windows_file_type_ext)] - #[inline] - pub(crate) const fn symlink_dir() -> Self { - Self::SymlinkDir - } - #[inline] pub(crate) fn is_symlink(&self) -> bool { match self { - #[cfg(windows_file_type_ext)] - Self::SymlinkFile | Self::SymlinkDir => true, Self::SymlinkUnknown => true, _ => false, } diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/is_same_file.rs b/crates/wasi/src/filesystem/primitives/windows/fs/is_same_file.rs index d64986385c73..8c50bbbab4e2 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/is_same_file.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/is_same_file.rs @@ -1,6 +1,4 @@ use crate::filesystem::primitives::ImplMetadataExt; -#[cfg(windows_by_handle)] -use crate::filesystem::primitives::Metadata; use std::{fs, io}; /// Determine if `a` and `b` refer to the same inode on the same device. @@ -9,12 +7,3 @@ pub(crate) fn is_same_file(a: &fs::File, b: &fs::File) -> io::Result { let b_metadata = ImplMetadataExt::from(b, &b.metadata()?)?; Ok(a_metadata.is_same_file(&b_metadata)) } - -/// Determine if `a` and `b` are metadata for the same inode on the same -/// device. -#[cfg(windows_by_handle)] -#[allow(dead_code)] -pub(crate) fn is_same_file_metadata(a: &Metadata, b: &Metadata) -> io::Result { - use crate::filesystem::primitives::MetadataExt; - Ok(a.volume_serial_number() == b.volume_serial_number() && a.file_index() == b.file_index()) -} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/metadata_ext.rs b/crates/wasi/src/filesystem/primitives/windows/fs/metadata_ext.rs index 6f1add8547da..cc8ded4d100e 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/metadata_ext.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/metadata_ext.rs @@ -19,25 +19,9 @@ impl ImplMetadataExt { /// Constructs a new instance of `Self` from the given [`std::fs::File`] /// and [`std::fs::Metadata`]. #[inline] - #[allow(unused_variables)] pub(crate) fn from(file: &fs::File, std: &fs::Metadata) -> io::Result { let (mut volume_serial_number, mut number_of_links, mut file_index) = (None, None, None); - #[cfg(windows_by_handle)] - { - use std::os::windows::fs::MetadataExt; - if let Some(some) = std.volume_serial_number() { - volume_serial_number = Some(some); - } - if let Some(some) = std.number_of_links() { - number_of_links = Some(some); - } - if let Some(some) = std.file_index() { - file_index = Some(some); - } - } - - #[cfg(not(windows_by_handle))] if volume_serial_number.is_none() || number_of_links.is_none() || file_index.is_none() { let fileinfo = winx::winapi_util::file::information(file)?; if volume_serial_number.is_none() { @@ -71,23 +55,8 @@ impl ImplMetadataExt { /// /// [`std::fs::Metadata::volume_serial_number`]: https://doc.rust-lang.org/std/os/windows/fs/trait.MetadataExt.html#tymethod.volume_serial_number #[inline] - #[allow(unused_mut)] pub(crate) fn from_just_metadata(std: &fs::Metadata) -> Self { - let (mut volume_serial_number, mut number_of_links, mut file_index) = (None, None, None); - - #[cfg(windows_by_handle)] - { - use std::os::windows::fs::MetadataExt; - if let Some(some) = std.volume_serial_number() { - volume_serial_number = Some(some); - } - if let Some(some) = std.number_of_links() { - number_of_links = Some(some); - } - if let Some(some) = std.file_index() { - file_index = Some(some); - } - } + let (volume_serial_number, number_of_links, file_index) = (None, None, None); Self::from_parts(std, volume_serial_number, number_of_links, file_index) } @@ -164,24 +133,6 @@ impl MetadataExt for ImplMetadataExt { fn file_size(&self) -> u64 { self.file_size } - - #[inline] - #[cfg(windows_by_handle)] - fn volume_serial_number(&self) -> Option { - self.volume_serial_number - } - - #[inline] - #[cfg(windows_by_handle)] - fn number_of_links(&self) -> Option { - self.number_of_links - } - - #[inline] - #[cfg(windows_by_handle)] - fn file_index(&self) -> Option { - self.file_index - } } #[doc(hidden)] From bf5fed7d1b4a1a0a0998c0d0c29af2988551280d Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 27 Aug 2026 11:58:10 -0700 Subject: [PATCH 07/34] Remove `arbitrary` integration in primitives Not going to be used by Wasmtime. --- .../src/filesystem/primitives/dir_options.rs | 20 ------------ .../filesystem/primitives/follow_symlinks.rs | 1 - .../src/filesystem/primitives/open_options.rs | 31 ------------------- 3 files changed, 52 deletions(-) diff --git a/crates/wasi/src/filesystem/primitives/dir_options.rs b/crates/wasi/src/filesystem/primitives/dir_options.rs index 9ef3dfff6134..60e3c98750bd 100644 --- a/crates/wasi/src/filesystem/primitives/dir_options.rs +++ b/crates/wasi/src/filesystem/primitives/dir_options.rs @@ -41,23 +41,3 @@ impl crate::fs::DirBuilderExt for DirOptions { self } } - -#[cfg(feature = "arbitrary")] -impl arbitrary::Arbitrary<'_> for DirOptions { - fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { - #[cfg(any(unix, target_os = "vxworks"))] - use crate::filesystem::primitives::DirBuilderExt; - - #[allow(unused_mut)] - let mut dir_options = Self::new(); - - #[cfg(any(unix, target_os = "vxworks"))] - dir_options.mode(u.int_in_range(0..=0o777)?); - - // Unix is currently the only platform with a `DirBuilderExt`. - #[cfg(not(any(unix, target_os = "vxworks")))] - let _ = u; - - Ok(dir_options) - } -} diff --git a/crates/wasi/src/filesystem/primitives/follow_symlinks.rs b/crates/wasi/src/filesystem/primitives/follow_symlinks.rs index de24fd346522..a23100f9dfb2 100644 --- a/crates/wasi/src/filesystem/primitives/follow_symlinks.rs +++ b/crates/wasi/src/filesystem/primitives/follow_symlinks.rs @@ -7,7 +7,6 @@ /// Instead of passing bare `bool`s as parameters, pass a distinct enum so that /// the intent is clear. #[derive(Copy, Clone, Debug, Eq, PartialEq)] -#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub enum FollowSymlinks { /// Yes, do follow symlinks in the last component of a path. Yes, diff --git a/crates/wasi/src/filesystem/primitives/open_options.rs b/crates/wasi/src/filesystem/primitives/open_options.rs index 79d44a80b1ca..ae2b67d76f3e 100644 --- a/crates/wasi/src/filesystem/primitives/open_options.rs +++ b/crates/wasi/src/filesystem/primitives/open_options.rs @@ -448,34 +448,3 @@ impl OpenOptionsExt for OpenOptions { self } } - -#[cfg(feature = "arbitrary")] -impl arbitrary::Arbitrary<'_> for OpenOptions { - fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { - use arbitrary::Arbitrary; - let (read, write) = match u.int_in_range(0..=2)? { - 0 => (true, false), - 1 => (false, true), - 2 => (true, true), - _ => panic!(), - }; - // TODO: `OpenOptionsExt` options. - Ok(Self::new() - .read(read) - .write(write) - .create(::arbitrary(u)?) - .append(::arbitrary(u)?) - .truncate(::arbitrary(u)?) - .create(::arbitrary(u)?) - .create_new(::arbitrary(u)?) - .dir_required(::arbitrary(u)?) - .maybe_dir(::arbitrary(u)?) - .sync(::arbitrary(u)?) - .dsync(::arbitrary(u)?) - .rsync(::arbitrary(u)?) - .nonblock(::arbitrary(u)?) - .readdir_required(::arbitrary(u)?) - .follow(::arbitrary(u)?) - .clone()) - } -} From 1cebfe4b601e7cf5209752945168fa803da9b03a Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 27 Aug 2026 12:00:41 -0700 Subject: [PATCH 08/34] Remove more optional usage of nightly features * `unix_file_vectored_at` * `io_error_more` * `io_error_uncategorized` --- crates/wasi/src/filesystem/primitives/file.rs | 36 ---------- .../primitives/manually/canonicalize.rs | 4 -- .../src/filesystem/primitives/metadata.rs | 72 +++++-------------- 3 files changed, 18 insertions(+), 94 deletions(-) diff --git a/crates/wasi/src/filesystem/primitives/file.rs b/crates/wasi/src/filesystem/primitives/file.rs index 3acf1bef4f07..d7ef18e1bdb0 100644 --- a/crates/wasi/src/filesystem/primitives/file.rs +++ b/crates/wasi/src/filesystem/primitives/file.rs @@ -6,12 +6,6 @@ pub trait FileExt { /// Reads a number of bytes starting from a given offset. fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result; - /// Like `read_at`, except that it reads into a slice of buffers. - #[cfg(unix_file_vectored_at)] - fn read_vectored_at(&self, bufs: &mut [io::IoSliceMut<'_>], offset: u64) -> io::Result { - default_read_vectored(|b| self.read_at(b, offset), bufs) - } - /// Reads the exact number of bytes required to fill `buf` from the given offset. fn read_exact_at(&self, mut buf: &mut [u8], mut offset: u64) -> io::Result<()> { while !buf.is_empty() { @@ -39,12 +33,6 @@ pub trait FileExt { /// Writes a number of bytes starting from a given offset. fn write_at(&self, buf: &[u8], offset: u64) -> io::Result; - /// Like `write_at`, except that it writes from a slice of buffers. - #[cfg(unix_file_vectored_at)] - fn write_vectored_at(&self, bufs: &[io::IoSlice<'_>], offset: u64) -> io::Result { - default_write_vectored(|b| self.write_at(b, offset), bufs) - } - /// Attempts to write an entire buffer starting from a given offset. fn write_all_at(&self, mut buf: &[u8], mut offset: u64) -> io::Result<()> { while !buf.is_empty() { @@ -67,30 +55,6 @@ pub trait FileExt { } } -#[cfg(unix_file_vectored_at)] -fn default_read_vectored(read: F, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result -where - F: FnOnce(&mut [u8]) -> io::Result, -{ - let buf = bufs - .iter_mut() - .find(|b| !b.is_empty()) - .map_or(&mut [][..], |b| &mut **b); - read(buf) -} - -#[cfg(unix_file_vectored_at)] -fn default_write_vectored(write: F, bufs: &[io::IoSlice<'_>]) -> io::Result -where - F: FnOnce(&[u8]) -> io::Result, -{ - let buf = bufs - .iter() - .find(|b| !b.is_empty()) - .map_or(&[][..], |b| &**b); - write(buf) -} - /// WASI-specific extensions to [`fs::File`]. #[cfg(target_os = "wasi")] pub trait FileExt { diff --git a/crates/wasi/src/filesystem/primitives/manually/canonicalize.rs b/crates/wasi/src/filesystem/primitives/manually/canonicalize.rs index 0faf8e2572b9..a646044dffd0 100644 --- a/crates/wasi/src/filesystem/primitives/manually/canonicalize.rs +++ b/crates/wasi/src/filesystem/primitives/manually/canonicalize.rs @@ -38,10 +38,6 @@ pub(crate) fn canonicalize_with( Err(err) if err.kind() == io::ErrorKind::InvalidInput => { return Err(err); } - #[cfg(io_error_more)] - Err(err) if err.kind() == io::ErrorKind::InvalidFilename => { - return Err(err); - } #[cfg(windows)] Err(err) if err.raw_os_error() diff --git a/crates/wasi/src/filesystem/primitives/metadata.rs b/crates/wasi/src/filesystem/primitives/metadata.rs index 96474c21eb94..badc577f4a83 100644 --- a/crates/wasi/src/filesystem/primitives/metadata.rs +++ b/crates/wasi/src/filesystem/primitives/metadata.rs @@ -112,24 +112,12 @@ impl Metadata { /// This corresponds to [`std::fs::Metadata::modified`]. #[inline] pub fn modified(&self) -> io::Result { - #[cfg(io_error_uncategorized)] - { - self.modified.ok_or_else(|| { - io::Error::new( - io::ErrorKind::Unsupported, - "modified time metadata not available on this platform", - ) - }) - } - #[cfg(not(io_error_uncategorized))] - { - self.modified.ok_or_else(|| { - io::Error::new( - io::ErrorKind::Other, - "modified time metadata not available on this platform", - ) - }) - } + self.modified.ok_or_else(|| { + io::Error::new( + io::ErrorKind::Other, + "modified time metadata not available on this platform", + ) + }) } /// Returns the last access time of this metadata. @@ -137,24 +125,12 @@ impl Metadata { /// This corresponds to [`std::fs::Metadata::accessed`]. #[inline] pub fn accessed(&self) -> io::Result { - #[cfg(io_error_uncategorized)] - { - self.accessed.ok_or_else(|| { - io::Error::new( - io::ErrorKind::Unsupported, - "accessed time metadata not available on this platform", - ) - }) - } - #[cfg(not(io_error_uncategorized))] - { - self.accessed.ok_or_else(|| { - io::Error::new( - io::ErrorKind::Other, - "accessed time metadata not available on this platform", - ) - }) - } + self.accessed.ok_or_else(|| { + io::Error::new( + io::ErrorKind::Other, + "accessed time metadata not available on this platform", + ) + }) } /// Returns the creation time listed in this metadata. @@ -162,24 +138,12 @@ impl Metadata { /// This corresponds to [`std::fs::Metadata::created`]. #[inline] pub fn created(&self) -> io::Result { - #[cfg(io_error_uncategorized)] - { - self.created.ok_or_else(|| { - io::Error::new( - io::ErrorKind::Unsupported, - "created time metadata not available on this platform", - ) - }) - } - #[cfg(not(io_error_uncategorized))] - { - self.created.ok_or_else(|| { - io::Error::new( - io::ErrorKind::Other, - "created time metadata not available on this platform", - ) - }) - } + self.created.ok_or_else(|| { + io::Error::new( + io::ErrorKind::Other, + "created time metadata not available on this platform", + ) + }) } /// Determine if `self` and `other` refer to the same inode on the same From 657f2f1f580be4a342ccd30860ebd5f019fb2081 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 27 Aug 2026 12:03:58 -0700 Subject: [PATCH 09/34] Remove a number of dead imports --- crates/wasi/src/filesystem/primitives/mod.rs | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/crates/wasi/src/filesystem/primitives/mod.rs b/crates/wasi/src/filesystem/primitives/mod.rs index ef32da161bd9..7085d016f7cf 100644 --- a/crates/wasi/src/filesystem/primitives/mod.rs +++ b/crates/wasi/src/filesystem/primitives/mod.rs @@ -59,16 +59,13 @@ pub(crate) use self::windows::fs::*; #[cfg(not(windows))] pub(crate) use read_dir::{read_dir_nofollow, read_dir_unchecked}; -pub use access::{AccessModes, AccessType, access}; -pub use canonicalize::canonicalize; -pub use copy::copy; +pub use access::AccessType; pub use create_dir::create_dir; pub use dir_builder::*; #[cfg(windows)] pub use dir_entry::_WindowsDirEntryExt; pub use dir_entry::DirEntry; pub use dir_options::DirOptions; -pub use file::FileExt; #[cfg(windows)] pub use file_type::_WindowsFileTypeExt; pub use file_type::FileType; @@ -81,26 +78,21 @@ pub use is_file_read_write::is_file_read_write; pub use metadata::_WindowsByHandle; pub use metadata::{Metadata, MetadataExt}; pub use open::open; -pub use open_ambient::open_ambient; pub use open_dir::*; pub use open_options::*; pub use permissions::Permissions; #[cfg(unix)] pub use permissions::PermissionsExt; -pub use read_dir::{ReadDir, read_base_dir, read_dir}; -pub use read_link::{read_link, read_link_contents}; +pub use read_dir::{ReadDir, read_base_dir}; +pub use read_link::read_link; pub use remove_dir::remove_dir; -pub use remove_dir_all::remove_dir_all; pub use remove_file::remove_file; -pub use remove_open_dir::{remove_open_dir, remove_open_dir_all}; +pub use remove_open_dir::remove_open_dir; pub use rename::rename; -pub use reopen::reopen; -#[cfg(not(target_os = "wasi"))] -pub use set_permissions::{set_permissions, set_symlink_permissions}; pub use set_times::{set_times, set_times_nofollow}; pub use stat::stat; #[cfg(not(windows))] -pub use symlink::{symlink, symlink_contents}; +pub use symlink::symlink; #[cfg(windows)] pub use symlink::{symlink_dir, symlink_file}; pub use system_time_spec::SystemTimeSpec; From 3e96333853f0332bb6f8de4b92443424b3e71061 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 27 Aug 2026 12:05:15 -0700 Subject: [PATCH 10/34] Allow some preexisting warnings in imported code Will work on handling these later. --- crates/wasi/src/filesystem/primitives/mod.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/wasi/src/filesystem/primitives/mod.rs b/crates/wasi/src/filesystem/primitives/mod.rs index 7085d016f7cf..90228c268118 100644 --- a/crates/wasi/src/filesystem/primitives/mod.rs +++ b/crates/wasi/src/filesystem/primitives/mod.rs @@ -1,5 +1,11 @@ //! Filesystem utilities. +// Allow preexisting warnings that were present in this module before it was +// imported from the cap-primitives crate. These still probably want to get +// resolved in a future change. +#![allow(trivial_numeric_casts)] +#![allow(unsafe_op_in_unsafe_fn)] + mod access; mod canonicalize; mod copy; From 4accdd89f96461e36d323710f664d136c2f3d495 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 27 Aug 2026 12:24:40 -0700 Subject: [PATCH 11/34] Delete dead code Much of cap-primitives isn't used by Wasmtime, so this commit goes through and resolves all rustc warnings and deletes a whole bunch of dead code. --- .../wasi/src/filesystem/primitives/access.rs | 38 --- .../src/filesystem/primitives/canonicalize.rs | 13 - crates/wasi/src/filesystem/primitives/copy.rs | 17 -- .../src/filesystem/primitives/dir_builder.rs | 84 ------ .../src/filesystem/primitives/dir_entry.rs | 73 +----- .../src/filesystem/primitives/dir_options.rs | 9 - crates/wasi/src/filesystem/primitives/file.rs | 154 ----------- .../src/filesystem/primitives/file_type.rs | 14 - .../filesystem/primitives/follow_symlinks.rs | 9 - .../primitives/is_file_read_write.rs | 9 - .../primitives/manually/canonicalize.rs | 61 ----- .../src/filesystem/primitives/manually/mod.rs | 7 - .../primitives/manually/open_entry.rs | 31 --- .../filesystem/primitives/maybe_owned_file.rs | 9 - .../src/filesystem/primitives/metadata.rs | 222 +--------------- crates/wasi/src/filesystem/primitives/mod.rs | 27 +- .../src/filesystem/primitives/open_ambient.rs | 22 -- .../src/filesystem/primitives/open_dir.rs | 35 +-- .../src/filesystem/primitives/open_options.rs | 147 ----------- .../src/filesystem/primitives/permissions.rs | 150 ----------- .../src/filesystem/primitives/read_dir.rs | 35 +-- .../filesystem/primitives/remove_dir_all.rs | 9 - .../filesystem/primitives/remove_open_dir.rs | 16 -- .../wasi/src/filesystem/primitives/reopen.rs | 36 --- .../primitives/rustix/fs/access_unchecked.rs | 38 --- .../primitives/rustix/fs/copy_impl.rs | 248 ------------------ .../primitives/rustix/fs/dir_entry_inner.rs | 59 +---- .../primitives/rustix/fs/dir_options_ext.rs | 7 - .../primitives/rustix/fs/dir_utils.rs | 5 - .../filesystem/primitives/rustix/fs/errors.rs | 6 - .../rustix/fs/is_file_read_write_impl.rs | 43 --- .../primitives/rustix/fs/is_root_dir.rs | 6 - .../primitives/rustix/fs/metadata_ext.rs | 182 +------------ .../filesystem/primitives/rustix/fs/mod.rs | 26 +- .../primitives/rustix/fs/open_options_ext.rs | 5 - .../primitives/rustix/fs/open_unchecked.rs | 16 +- .../primitives/rustix/fs/permissions_ext.rs | 74 ------ .../primitives/rustix/fs/read_dir_inner.rs | 52 +--- .../rustix/fs/remove_dir_all_impl.rs | 41 --- .../rustix/fs/remove_open_dir_by_searching.rs | 38 --- .../primitives/rustix/fs/reopen_impl.rs | 18 -- .../fs/set_symlink_permissions_unchecked.rs | 17 -- .../filesystem/primitives/rustix/fs/times.rs | 4 - .../rustix/linux/fs/canonicalize_impl.rs | 53 ---- .../primitives/rustix/linux/fs/file_path.rs | 22 -- .../primitives/rustix/linux/fs/mod.rs | 16 -- .../rustix/linux/fs/open_entry_impl.rs | 19 -- .../primitives/rustix/linux/fs/procfs.rs | 46 +--- .../rustix/linux/fs/set_permissions_impl.rs | 55 ---- .../filesystem/primitives/set_permissions.rs | 25 -- .../wasi/src/filesystem/primitives/symlink.rs | 11 - .../filesystem/primitives/system_time_spec.rs | 16 -- .../primitives/via_parent/access.rs | 19 -- .../filesystem/primitives/via_parent/mod.rs | 10 - .../primitives/via_parent/set_permissions.rs | 13 - .../via_parent/set_symlink_permissions.rs | 19 -- .../primitives/windows/fs/access_unchecked.rs | 32 --- .../filesystem/primitives/windows/fs/copy.rs | 19 -- .../primitives/windows/fs/dir_entry_inner.rs | 86 +----- .../primitives/windows/fs/dir_utils.rs | 8 - .../windows/fs/is_file_read_write_impl.rs | 11 - .../primitives/windows/fs/is_same_file.rs | 9 - .../primitives/windows/fs/metadata_ext.rs | 106 +------- .../filesystem/primitives/windows/fs/mod.rs | 22 -- .../primitives/windows/fs/oflags.rs | 36 --- .../primitives/windows/fs/open_options_ext.rs | 12 +- .../primitives/windows/fs/open_unchecked.rs | 16 +- .../primitives/windows/fs/read_dir_inner.rs | 16 +- .../windows/fs/remove_dir_all_impl.rs | 30 --- .../windows/fs/remove_open_dir_impl.rs | 24 -- .../primitives/windows/fs/reopen_impl.rs | 85 ------ .../windows/fs/set_permissions_unchecked.rs | 21 -- .../fs/set_symlink_permissions_unchecked.rs | 20 -- 73 files changed, 28 insertions(+), 2961 deletions(-) delete mode 100644 crates/wasi/src/filesystem/primitives/access.rs delete mode 100644 crates/wasi/src/filesystem/primitives/canonicalize.rs delete mode 100644 crates/wasi/src/filesystem/primitives/copy.rs delete mode 100644 crates/wasi/src/filesystem/primitives/dir_builder.rs delete mode 100644 crates/wasi/src/filesystem/primitives/file.rs delete mode 100644 crates/wasi/src/filesystem/primitives/is_file_read_write.rs delete mode 100644 crates/wasi/src/filesystem/primitives/manually/canonicalize.rs delete mode 100644 crates/wasi/src/filesystem/primitives/manually/open_entry.rs delete mode 100644 crates/wasi/src/filesystem/primitives/open_ambient.rs delete mode 100644 crates/wasi/src/filesystem/primitives/permissions.rs delete mode 100644 crates/wasi/src/filesystem/primitives/remove_dir_all.rs delete mode 100644 crates/wasi/src/filesystem/primitives/remove_open_dir.rs delete mode 100644 crates/wasi/src/filesystem/primitives/reopen.rs delete mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/access_unchecked.rs delete mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/copy_impl.rs delete mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/is_file_read_write_impl.rs delete mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/is_root_dir.rs delete mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/permissions_ext.rs delete mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/remove_dir_all_impl.rs delete mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/remove_open_dir_by_searching.rs delete mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/reopen_impl.rs delete mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/set_symlink_permissions_unchecked.rs delete mode 100644 crates/wasi/src/filesystem/primitives/rustix/linux/fs/canonicalize_impl.rs delete mode 100644 crates/wasi/src/filesystem/primitives/rustix/linux/fs/file_path.rs delete mode 100644 crates/wasi/src/filesystem/primitives/rustix/linux/fs/open_entry_impl.rs delete mode 100644 crates/wasi/src/filesystem/primitives/rustix/linux/fs/set_permissions_impl.rs delete mode 100644 crates/wasi/src/filesystem/primitives/set_permissions.rs delete mode 100644 crates/wasi/src/filesystem/primitives/via_parent/access.rs delete mode 100644 crates/wasi/src/filesystem/primitives/via_parent/set_permissions.rs delete mode 100644 crates/wasi/src/filesystem/primitives/via_parent/set_symlink_permissions.rs delete mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/access_unchecked.rs delete mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/copy.rs delete mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/is_file_read_write_impl.rs delete mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/is_same_file.rs delete mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/remove_dir_all_impl.rs delete mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/remove_open_dir_impl.rs delete mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/reopen_impl.rs delete mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/set_permissions_unchecked.rs delete mode 100644 crates/wasi/src/filesystem/primitives/windows/fs/set_symlink_permissions_unchecked.rs diff --git a/crates/wasi/src/filesystem/primitives/access.rs b/crates/wasi/src/filesystem/primitives/access.rs deleted file mode 100644 index d8c13fc5d797..000000000000 --- a/crates/wasi/src/filesystem/primitives/access.rs +++ /dev/null @@ -1,38 +0,0 @@ -//! Access test functions. - -use crate::filesystem::primitives::{FollowSymlinks, access_impl}; -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`. -pub fn access( - start: &fs::File, - path: &Path, - type_: AccessType, - follow: FollowSymlinks, -) -> io::Result<()> { - // Call the underlying implementation. - access_impl(start, path, type_, follow) -} diff --git a/crates/wasi/src/filesystem/primitives/canonicalize.rs b/crates/wasi/src/filesystem/primitives/canonicalize.rs deleted file mode 100644 index 06153d5112c5..000000000000 --- a/crates/wasi/src/filesystem/primitives/canonicalize.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! Sandboxed path canonicalization. - -use crate::filesystem::primitives::canonicalize_impl; -use std::path::{Path, PathBuf}; -use std::{fs, io}; - -/// Canonicalize the given path, ensuring that the resolution of the path never -/// escapes the directory tree rooted at `start`. -#[inline] -pub fn canonicalize(start: &fs::File, path: &Path) -> io::Result { - // Call the underlying implementation. - canonicalize_impl(start, path) -} diff --git a/crates/wasi/src/filesystem/primitives/copy.rs b/crates/wasi/src/filesystem/primitives/copy.rs deleted file mode 100644 index 8fda7df160b2..000000000000 --- a/crates/wasi/src/filesystem/primitives/copy.rs +++ /dev/null @@ -1,17 +0,0 @@ -use crate::filesystem::primitives::copy_impl; -use std::path::Path; -use std::{fs, io}; - -/// Copies the contents of one file to another. -#[inline] -pub fn copy( - from_start: &fs::File, - from_path: &Path, - to_start: &fs::File, - to_path: &Path, -) -> io::Result { - // In theory we could do extra sanity checks here, but `copy_impl` - // implementations use other sandboxed routines to open the files, - // so it'd be mostly redundant. - copy_impl(from_start, from_path, to_start, to_path) -} diff --git a/crates/wasi/src/filesystem/primitives/dir_builder.rs b/crates/wasi/src/filesystem/primitives/dir_builder.rs deleted file mode 100644 index 57e04b5e4168..000000000000 --- a/crates/wasi/src/filesystem/primitives/dir_builder.rs +++ /dev/null @@ -1,84 +0,0 @@ -use crate::filesystem::primitives::DirOptions; -use std::fmt; - -/// A builder used to create directories in various manners. -/// -/// This corresponds to [`std::fs::DirBuilder`]. -/// -/// Unlike `std::fs::DirBuilder`, this API has no `DirBuilder::create`, because -/// creating directories requires a capability. Use [`Dir::create_dir_with`] -/// instead. -/// -/// [`Dir::create_dir_with`]: https://docs.rs/cap-std/latest/cap_std/fs/struct.Dir.html#method.create_dir_with -/// -///
-/// We need to define our own version because the libstd `DirBuilder` doesn't -/// have public accessors that we can use. -///
-pub struct DirBuilder { - pub(crate) recursive: bool, - pub(crate) options: DirOptions, -} - -impl DirBuilder { - /// Creates a new set of options with default mode/security settings for - /// all platforms and also non-recursive. - /// - /// This corresponds to [`std::fs::DirBuilder::new`]. - #[allow(clippy::new_without_default)] - #[inline] - pub const fn new() -> Self { - Self { - recursive: false, - options: DirOptions::new(), - } - } - - /// Indicates that directories should be created recursively, creating all - /// parent directories. - /// - /// This corresponds to [`std::fs::DirBuilder::recursive`]. - #[inline] - pub fn recursive(&mut self, recursive: bool) -> &mut Self { - self.recursive = recursive; - self - } - - /// Return the `DirOptions` contained in this `DirBuilder`. - #[inline] - pub const fn options(&self) -> &DirOptions { - &self.options - } - - /// Return the value of the `recursive` flag. - #[inline] - pub const fn is_recursive(&self) -> bool { - self.recursive - } -} - -/// Unix-specific extensions to [`fs::DirBuilder`]. -#[cfg(unix)] -pub trait DirBuilderExt { - /// Sets the mode to create new directories with. This option defaults to - /// 0o777. - fn mode(&mut self, mode: u32) -> &mut Self; -} - -#[cfg(unix)] -impl DirBuilderExt for DirBuilder { - #[inline] - fn mode(&mut self, mode: u32) -> &mut Self { - self.options.mode(mode); - self - } -} - -impl fmt::Debug for DirBuilder { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut b = f.debug_struct("DirBuilder"); - b.field("recursive", &self.recursive); - b.field("options", &self.options); - b.finish() - } -} diff --git a/crates/wasi/src/filesystem/primitives/dir_entry.rs b/crates/wasi/src/filesystem/primitives/dir_entry.rs index 7292d4311497..013880d48926 100644 --- a/crates/wasi/src/filesystem/primitives/dir_entry.rs +++ b/crates/wasi/src/filesystem/primitives/dir_entry.rs @@ -1,10 +1,8 @@ -use crate::filesystem::primitives::{ - DirEntryInner, FileType, FollowSymlinks, Metadata, OpenOptions, ReadDir, dir_options, -}; +use crate::filesystem::primitives::{DirEntryInner, Metadata}; #[cfg(not(windows))] use rustix::fs::DirEntryExt; use std::ffi::OsString; -use std::{fmt, fs, io}; +use std::{fmt, io}; /// Entries returned by the `ReadDir` iterator. /// @@ -25,42 +23,6 @@ pub struct DirEntry { } impl DirEntry { - /// Open the file for reading. - #[inline] - pub fn open(&self) -> io::Result { - self.open_with(OpenOptions::new().read(true)) - } - - /// Open the file with the given options. - #[inline] - pub fn open_with(&self, options: &OpenOptions) -> io::Result { - self.inner.open(options) - } - - /// Open the entry as a directory. - #[inline] - pub fn open_dir(&self) -> io::Result { - self.open_with(&dir_options()) - } - - /// Removes the file from its filesystem. - #[inline] - pub fn remove_file(&self) -> io::Result<()> { - self.inner.remove_file() - } - - /// Removes the directory from its filesystem. - #[inline] - pub fn remove_dir(&self) -> io::Result<()> { - self.inner.remove_dir() - } - - /// Returns an iterator over the entries within the subdirectory. - #[inline] - pub fn read_dir(&self) -> io::Result { - self.inner.read_dir(FollowSymlinks::Yes) - } - /// Returns the metadata for the file that this entry points at. /// /// This corresponds to [`std::fs::DirEntry::metadata`]. @@ -79,20 +41,6 @@ impl DirEntry { self.inner.metadata() } - /// Returns the file type for the file that this entry points at. - /// - /// This corresponds to [`std::fs::DirEntry::file_type`]. - /// - /// # Platform-specific behavior - /// - /// On Windows and most Unix platforms this function is free (no extra system calls needed), but - /// some Unix platforms may require the equivalent call to `metadata` to learn about the target - /// file type. - #[inline] - pub fn file_type(&self) -> io::Result { - self.inner.file_type() - } - /// Returns the bare file name of this directory entry without any other /// leading path component. /// @@ -101,12 +49,6 @@ impl DirEntry { pub fn file_name(&self) -> OsString { self.inner.file_name() } - - #[cfg(not(windows))] - #[inline] - pub(crate) fn is_same_file(&self, metadata: &Metadata) -> io::Result { - self.inner.is_same_file(metadata) - } } #[cfg(not(windows))] @@ -123,14 +65,3 @@ impl fmt::Debug for DirEntry { self.inner.fmt(f) } } - -/// Extension trait to allow `full_metadata` etc. to be exposed by -/// the `cap-fs-ext` crate. -/// -/// This is hidden from the main API since this functionality isn't present in -/// `std`. Use `cap_fs_ext::DirEntryExt` instead of calling this directly. -#[cfg(windows)] -#[doc(hidden)] -pub trait _WindowsDirEntryExt { - fn full_metadata(&self) -> io::Result; -} diff --git a/crates/wasi/src/filesystem/primitives/dir_options.rs b/crates/wasi/src/filesystem/primitives/dir_options.rs index 60e3c98750bd..096bfa446988 100644 --- a/crates/wasi/src/filesystem/primitives/dir_options.rs +++ b/crates/wasi/src/filesystem/primitives/dir_options.rs @@ -24,15 +24,6 @@ impl DirOptions { } } -#[cfg(unix)] -impl crate::filesystem::primitives::DirBuilderExt for DirOptions { - #[inline] - fn mode(&mut self, mode: u32) -> &mut Self { - self.ext.mode(mode); - self - } -} - #[cfg(target_os = "vxworks")] impl crate::fs::DirBuilderExt for DirOptions { #[inline] diff --git a/crates/wasi/src/filesystem/primitives/file.rs b/crates/wasi/src/filesystem/primitives/file.rs deleted file mode 100644 index d7ef18e1bdb0..000000000000 --- a/crates/wasi/src/filesystem/primitives/file.rs +++ /dev/null @@ -1,154 +0,0 @@ -use std::io; - -/// Unix-specific extensions to [`fs::File`]. -#[cfg(unix)] -pub trait FileExt { - /// Reads a number of bytes starting from a given offset. - fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result; - - /// Reads the exact number of bytes required to fill `buf` from the given offset. - fn read_exact_at(&self, mut buf: &mut [u8], mut offset: u64) -> io::Result<()> { - while !buf.is_empty() { - match self.read_at(buf, offset) { - Ok(0) => break, - Ok(n) => { - let tmp = buf; - buf = &mut tmp[n..]; - offset += n as u64; - } - Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {} - Err(e) => return Err(e), - } - } - if !buf.is_empty() { - Err(io::Error::new( - io::ErrorKind::UnexpectedEof, - "failed to fill whole buffer", - )) - } else { - Ok(()) - } - } - - /// Writes a number of bytes starting from a given offset. - fn write_at(&self, buf: &[u8], offset: u64) -> io::Result; - - /// Attempts to write an entire buffer starting from a given offset. - fn write_all_at(&self, mut buf: &[u8], mut offset: u64) -> io::Result<()> { - while !buf.is_empty() { - match self.write_at(buf, offset) { - Ok(0) => { - return Err(io::Error::new( - io::ErrorKind::WriteZero, - "failed to write whole buffer", - )); - } - Ok(n) => { - buf = &buf[n..]; - offset += n as u64 - } - Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {} - Err(e) => return Err(e), - } - } - Ok(()) - } -} - -/// WASI-specific extensions to [`fs::File`]. -#[cfg(target_os = "wasi")] -pub trait FileExt { - /// Reads a number of bytes starting from a given offset. - fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result { - let bufs = &mut [io::IoSliceMut::new(buf)]; - self.read_vectored_at(bufs, offset) - } - - /// Reads a number of bytes starting from a given offset. - fn read_vectored_at(&self, bufs: &mut [io::IoSliceMut<'_>], offset: u64) -> io::Result; - - /// Reads the exact number of byte required to fill `buf` from the given offset. - fn read_exact_at(&self, mut buf: &mut [u8], mut offset: u64) -> io::Result<()> { - while !buf.is_empty() { - match self.read_at(buf, offset) { - Ok(0) => break, - Ok(n) => { - let tmp = buf; - buf = &mut tmp[n..]; - offset += n as u64; - } - Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {} - Err(e) => return Err(e), - } - } - if !buf.is_empty() { - Err(io::Error::new( - io::ErrorKind::UnexpectedEof, - "failed to fill whole buffer", - )) - } else { - Ok(()) - } - } - - /// Writes a number of bytes starting from a given offset. - fn write_at(&self, buf: &[u8], offset: u64) -> io::Result { - let bufs = &[io::IoSlice::new(buf)]; - self.write_vectored_at(bufs, offset) - } - - /// Writes a number of bytes starting from a given offset. - fn write_vectored_at(&self, bufs: &[io::IoSlice<'_>], offset: u64) -> io::Result; - - /// Attempts to write an entire buffer starting from a given offset. - fn write_all_at(&self, mut buf: &[u8], mut offset: u64) -> io::Result<()> { - while !buf.is_empty() { - match self.write_at(buf, offset) { - Ok(0) => { - return Err(io::Error::new( - io::ErrorKind::WriteZero, - "failed to write whole buffer", - )); - } - Ok(n) => { - buf = &buf[n..]; - offset += n as u64 - } - Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {} - Err(e) => return Err(e), - } - } - Ok(()) - } - - /// Adjust the flags associated with this file. - fn fdstat_set_flags(&self, flags: u16) -> io::Result<()>; - - /// Adjust the rights associated with this file. - fn fdstat_set_rights(&self, rights: u64, inheriting: u64) -> io::Result<()>; - - /// Provide file advisory information on a file descriptor. - fn advise(&self, offset: u64, len: u64, advice: u8) -> io::Result<()>; - - /// Force the allocation of space in a file. - fn allocate(&self, offset: u64, len: u64) -> io::Result<()>; - - /// Create a directory. - fn create_directory>(&self, dir: P) -> io::Result<()>; - - /// Unlink a file. - fn remove_file>(&self, path: P) -> io::Result<()>; - - /// Remove a directory. - fn remove_directory>(&self, path: P) -> io::Result<()>; -} - -/// Windows-specific extensions to [`fs::File`]. -#[cfg(windows)] -pub trait FileExt { - /// Seeks to a given position and reads a number of bytes. - fn seek_read(&self, buf: &mut [u8], offset: u64) -> io::Result; - - /// Seeks to a given position and writes a number of bytes. - fn seek_write(&self, buf: &[u8], offset: u64) -> io::Result; -} diff --git a/crates/wasi/src/filesystem/primitives/file_type.rs b/crates/wasi/src/filesystem/primitives/file_type.rs index 1166aa2640e1..20ebf31f2b14 100644 --- a/crates/wasi/src/filesystem/primitives/file_type.rs +++ b/crates/wasi/src/filesystem/primitives/file_type.rs @@ -93,10 +93,6 @@ pub trait FileTypeExt { fn is_block_device(&self) -> bool; /// Returns `true` if this file type is a character device. fn is_char_device(&self) -> bool; - /// Returns `true` if this file type is a fifo. - fn is_fifo(&self) -> bool; - /// Returns `true` if this file type is a socket. - fn is_socket(&self) -> bool; } #[cfg(any(unix, target_os = "vxworks"))] @@ -110,16 +106,6 @@ impl FileTypeExt for FileType { fn is_char_device(&self) -> bool { self.0 == Inner::Ext(ImplFileTypeExt::char_device()) } - - #[inline] - fn is_fifo(&self) -> bool { - self.0 == Inner::Ext(ImplFileTypeExt::fifo()) - } - - #[inline] - fn is_socket(&self) -> bool { - self.0 == Inner::Ext(ImplFileTypeExt::socket()) - } } /// Extension trait to allow `is_block_device` etc. to be exposed by diff --git a/crates/wasi/src/filesystem/primitives/follow_symlinks.rs b/crates/wasi/src/filesystem/primitives/follow_symlinks.rs index a23100f9dfb2..55774d4ae747 100644 --- a/crates/wasi/src/filesystem/primitives/follow_symlinks.rs +++ b/crates/wasi/src/filesystem/primitives/follow_symlinks.rs @@ -14,12 +14,3 @@ pub enum FollowSymlinks { /// No, do not follow symlinks in the last component of a path. No, } - -impl FollowSymlinks { - /// Convert a bool where true means "follow" and false means "don't follow" - /// to a `FollowSymlinks`. - #[inline] - pub const fn follow(follow: bool) -> Self { - if follow { Self::Yes } else { Self::No } - } -} diff --git a/crates/wasi/src/filesystem/primitives/is_file_read_write.rs b/crates/wasi/src/filesystem/primitives/is_file_read_write.rs deleted file mode 100644 index 62d386e24541..000000000000 --- a/crates/wasi/src/filesystem/primitives/is_file_read_write.rs +++ /dev/null @@ -1,9 +0,0 @@ -use crate::filesystem::primitives::is_file_read_write_impl; -use std::{fs, io}; - -/// Return a pair of booleans indicating whether the given file is opened -/// for reading and writing, respectively. -#[inline] -pub fn is_file_read_write(file: &fs::File) -> io::Result<(bool, bool)> { - is_file_read_write_impl(file) -} diff --git a/crates/wasi/src/filesystem/primitives/manually/canonicalize.rs b/crates/wasi/src/filesystem/primitives/manually/canonicalize.rs deleted file mode 100644 index a646044dffd0..000000000000 --- a/crates/wasi/src/filesystem/primitives/manually/canonicalize.rs +++ /dev/null @@ -1,61 +0,0 @@ -//! Manual path canonicalization, one component at a time, with manual symlink -//! resolution, in order to enforce sandboxing. - -use super::internal_open; -use crate::filesystem::primitives::{FollowSymlinks, MaybeOwnedFile, canonicalize_options}; -use std::path::{Path, PathBuf}; -use std::{fs, io}; - -/// Implement `canonicalize` by breaking up the path into components and -/// resolving each component individually, and resolving symbolic links -/// manually. -pub(crate) fn canonicalize(start: &fs::File, path: &Path) -> io::Result { - canonicalize_with(start, path, FollowSymlinks::Yes) -} - -/// The main body of `canonicalize`, which takes an extra `follow` flag -/// allowing the caller to disable following symlinks in the last component. -pub(crate) fn canonicalize_with( - start: &fs::File, - path: &Path, - follow: FollowSymlinks, -) -> io::Result { - let mut symlink_count = 0; - let mut canonical_path = PathBuf::new(); - let start = MaybeOwnedFile::borrowed(start); - - match internal_open( - start, - path, - canonicalize_options().follow(follow), - &mut symlink_count, - Some(&mut canonical_path), - ) { - // If the open succeeded, we got our path. - Ok(_) => (), - - // If it failed due to an invalid argument or filename, report it. - Err(err) if err.kind() == io::ErrorKind::InvalidInput => { - return Err(err); - } - #[cfg(windows)] - Err(err) - if err.raw_os_error() - == Some(windows_sys::Win32::Foundation::ERROR_INVALID_NAME as _) - || err.raw_os_error() - == Some(windows_sys::Win32::Foundation::ERROR_DIRECTORY as _) => - { - return Err(err); - } - - // For any other error, like permission denied, it's ok as long as - // we got our path. - Err(err) => { - if canonical_path.as_os_str().is_empty() { - return Err(err); - } - } - } - - Ok(canonical_path) -} diff --git a/crates/wasi/src/filesystem/primitives/manually/mod.rs b/crates/wasi/src/filesystem/primitives/manually/mod.rs index bba033eef23b..3712e9abaf65 100644 --- a/crates/wasi/src/filesystem/primitives/manually/mod.rs +++ b/crates/wasi/src/filesystem/primitives/manually/mod.rs @@ -2,19 +2,12 @@ //! at a time, with manual symlink resolution. mod canonical_path; -mod canonicalize; mod cow_component; mod open; -#[cfg(not(any(windows, target_os = "freebsd")))] -mod open_entry; mod read_link_one; use canonical_path::CanonicalPath; use cow_component::CowComponent; -use open::internal_open; use read_link_one::read_link_one; -pub(crate) use canonicalize::canonicalize; pub(crate) use open::{open, stat}; -#[cfg(not(any(windows, target_os = "freebsd")))] -pub(crate) use open_entry::open_entry; diff --git a/crates/wasi/src/filesystem/primitives/manually/open_entry.rs b/crates/wasi/src/filesystem/primitives/manually/open_entry.rs deleted file mode 100644 index bd84f9238d4f..000000000000 --- a/crates/wasi/src/filesystem/primitives/manually/open_entry.rs +++ /dev/null @@ -1,31 +0,0 @@ -use super::{internal_open, read_link_one}; -use crate::filesystem::primitives::{ - FollowSymlinks, MaybeOwnedFile, OpenOptions, OpenUncheckedError, open_unchecked, -}; -use std::ffi::OsStr; -use std::path::PathBuf; -use std::{fs, io}; - -pub(crate) fn open_entry( - start: &fs::File, - path: &OsStr, - options: &OpenOptions, -) -> io::Result { - match open_unchecked( - start, - path.as_ref(), - options.clone().follow(FollowSymlinks::No), - ) { - Ok(file) => Ok(file), - Err(OpenUncheckedError::Symlink(_, _)) if options.follow == FollowSymlinks::Yes => { - let mut symlink_count = 0; - let destination = read_link_one(start, path, &mut symlink_count, PathBuf::new())?; - let maybe = MaybeOwnedFile::borrowed(start); - internal_open(maybe, &destination, options, &mut symlink_count, None) - .map(MaybeOwnedFile::unwrap_owned) - } - Err(OpenUncheckedError::NotFound(err)) - | Err(OpenUncheckedError::Other(err)) - | Err(OpenUncheckedError::Symlink(err, _)) => Err(err), - } -} diff --git a/crates/wasi/src/filesystem/primitives/maybe_owned_file.rs b/crates/wasi/src/filesystem/primitives/maybe_owned_file.rs index 22dd48411846..2f21b90ba5c6 100644 --- a/crates/wasi/src/filesystem/primitives/maybe_owned_file.rs +++ b/crates/wasi/src/filesystem/primitives/maybe_owned_file.rs @@ -76,15 +76,6 @@ impl<'borrow> MaybeOwnedFile<'borrow> { } } } - - /// Assuming `self` holds an owned `File`, return it. - #[cfg_attr(any(windows, target_os = "freebsd"), allow(dead_code))] - pub(super) fn unwrap_owned(self) -> fs::File { - match self.inner { - MaybeOwned::Owned(file) => file, - MaybeOwned::Borrowed(_) => panic!("expected owned file"), - } - } } impl<'borrow> Deref for MaybeOwnedFile<'borrow> { diff --git a/crates/wasi/src/filesystem/primitives/metadata.rs b/crates/wasi/src/filesystem/primitives/metadata.rs index badc577f4a83..463579a0d771 100644 --- a/crates/wasi/src/filesystem/primitives/metadata.rs +++ b/crates/wasi/src/filesystem/primitives/metadata.rs @@ -1,4 +1,4 @@ -use crate::filesystem::primitives::{FileType, ImplFileTypeExt, ImplMetadataExt, Permissions}; +use crate::filesystem::primitives::{FileType, ImplFileTypeExt, ImplMetadataExt}; use std::time::SystemTime; use std::{fs, io}; @@ -14,7 +14,6 @@ use std::{fs, io}; pub struct Metadata { pub(crate) file_type: FileType, pub(crate) len: u64, - pub(crate) permissions: Permissions, pub(crate) modified: Option, pub(crate) accessed: Option, pub(crate) created: Option, @@ -51,7 +50,6 @@ impl Metadata { Self { file_type, len: std.len(), - permissions: Permissions::from_std(std.permissions()), modified: std.modified().ok(), accessed: std.accessed().ok(), created: std.created().ok(), @@ -75,22 +73,6 @@ impl Metadata { self.file_type.is_dir() } - /// Returns `true` if this metadata is for a regular file. - /// - /// This corresponds to [`std::fs::Metadata::is_file`]. - #[inline] - pub fn is_file(&self) -> bool { - self.file_type.is_file() - } - - /// Returns `true` if this metadata is for a symbolic link. - /// - /// This corresponds to [`std::fs::Metadata::is_symlink`]. - #[inline] - pub fn is_symlink(&self) -> bool { - self.file_type.is_symlink() - } - /// Returns the size of the file, in bytes, this metadata is for. /// /// This corresponds to [`std::fs::Metadata::len`]. @@ -99,14 +81,6 @@ impl Metadata { self.len } - /// Returns the permissions of the file this metadata is for. - /// - /// This corresponds to [`std::fs::Metadata::permissions`]. - #[inline] - pub fn permissions(&self) -> Permissions { - self.permissions.clone() - } - /// Returns the last modification time listed in this metadata. /// /// This corresponds to [`std::fs::Metadata::modified`]. @@ -146,13 +120,6 @@ impl Metadata { }) } - /// Determine if `self` and `other` refer to the same inode on the same - /// device. - #[cfg(not(windows))] - pub(crate) fn is_same_file(&self, other: &Self) -> bool { - self.ext.is_same_file(&other.ext) - } - /// `MetadataExt` requires nightly to be implemented, but we sometimes /// just need the file attributes. #[cfg(windows)] @@ -171,34 +138,8 @@ pub trait MetadataExt { fn dev(&self) -> u64; /// Returns the inode number. fn ino(&self) -> u64; - /// Returns the rights applied to this file. - fn mode(&self) -> u32; /// Returns the number of hard links pointing to this file. fn nlink(&self) -> u64; - /// Returns the user ID of the owner of this file. - fn uid(&self) -> u32; - /// Returns the group ID of the owner of this file. - fn gid(&self) -> u32; - /// Returns the device ID of this file (if it is a special one). - fn rdev(&self) -> u64; - /// Returns the total size of this file in bytes. - fn size(&self) -> u64; - /// Returns the last access time of the file, in seconds since Unix Epoch. - fn atime(&self) -> i64; - /// Returns the last access time of the file, in nanoseconds since [`atime`]. - fn atime_nsec(&self) -> i64; - /// Returns the last modification time of the file, in seconds since Unix Epoch. - fn mtime(&self) -> i64; - /// Returns the last modification time of the file, in nanoseconds since [`mtime`]. - fn mtime_nsec(&self) -> i64; - /// Returns the last status change time of the file, in seconds since Unix Epoch. - fn ctime(&self) -> i64; - /// Returns the last status change time of the file, in nanoseconds since [`ctime`]. - fn ctime_nsec(&self) -> i64; - /// Returns the block size for filesystem I/O. - fn blksize(&self) -> u64; - /// Returns the number of blocks allocated to the file, in 512-byte units. - fn blocks(&self) -> u64; #[cfg(target_os = "vxworks")] fn attrib(&self) -> u8; } @@ -223,14 +164,6 @@ pub trait MetadataExt { pub trait MetadataExt { /// Returns the value of the `dwFileAttributes` field of this metadata. fn file_attributes(&self) -> u32; - /// Returns the value of the `ftCreationTime` field of this metadata. - fn creation_time(&self) -> u64; - /// Returns the value of the `ftLastAccessTime` field of this metadata. - fn last_access_time(&self) -> u64; - /// Returns the value of the `ftLastWriteTime` field of this metadata. - fn last_write_time(&self) -> u64; - /// Returns the value of the `nFileSize{High,Low}` fields of this metadata. - fn file_size(&self) -> u64; } #[cfg(unix)] @@ -245,75 +178,10 @@ impl MetadataExt for Metadata { crate::filesystem::primitives::MetadataExt::ino(&self.ext) } - #[inline] - fn mode(&self) -> u32 { - crate::filesystem::primitives::MetadataExt::mode(&self.ext) - } - #[inline] fn nlink(&self) -> u64 { crate::filesystem::primitives::MetadataExt::nlink(&self.ext) } - - #[inline] - fn uid(&self) -> u32 { - crate::filesystem::primitives::MetadataExt::uid(&self.ext) - } - - #[inline] - fn gid(&self) -> u32 { - crate::filesystem::primitives::MetadataExt::gid(&self.ext) - } - - #[inline] - fn rdev(&self) -> u64 { - crate::filesystem::primitives::MetadataExt::rdev(&self.ext) - } - - #[inline] - fn size(&self) -> u64 { - crate::filesystem::primitives::MetadataExt::size(&self.ext) - } - - #[inline] - fn atime(&self) -> i64 { - crate::filesystem::primitives::MetadataExt::atime(&self.ext) - } - - #[inline] - fn atime_nsec(&self) -> i64 { - crate::filesystem::primitives::MetadataExt::atime_nsec(&self.ext) - } - - #[inline] - fn mtime(&self) -> i64 { - crate::filesystem::primitives::MetadataExt::mtime(&self.ext) - } - - #[inline] - fn mtime_nsec(&self) -> i64 { - crate::filesystem::primitives::MetadataExt::mtime_nsec(&self.ext) - } - - #[inline] - fn ctime(&self) -> i64 { - crate::filesystem::primitives::MetadataExt::ctime(&self.ext) - } - - #[inline] - fn ctime_nsec(&self) -> i64 { - crate::filesystem::primitives::MetadataExt::ctime_nsec(&self.ext) - } - - #[inline] - fn blksize(&self) -> u64 { - crate::filesystem::primitives::MetadataExt::blksize(&self.ext) - } - - #[inline] - fn blocks(&self) -> u64 { - crate::filesystem::primitives::MetadataExt::blocks(&self.ext) - } } #[cfg(target_os = "wasi")] @@ -346,75 +214,10 @@ impl MetadataExt for Metadata { self.ext.ino() } - #[inline] - fn mode(&self) -> u32 { - self.ext.mode() - } - #[inline] fn nlink(&self) -> u64 { self.ext.nlink() } - - #[inline] - fn uid(&self) -> u32 { - self.ext.uid() - } - - #[inline] - fn gid(&self) -> u32 { - self.ext.gid() - } - - #[inline] - fn rdev(&self) -> u64 { - self.ext.rdev() - } - - #[inline] - fn size(&self) -> u64 { - self.ext.size() - } - - #[inline] - fn atime(&self) -> i64 { - self.ext.atime() - } - - #[inline] - fn atime_nsec(&self) -> i64 { - self.ext.atime_nsec() - } - - #[inline] - fn mtime(&self) -> i64 { - self.ext.mtime() - } - - #[inline] - fn mtime_nsec(&self) -> i64 { - self.ext.mtime_nsec() - } - - #[inline] - fn ctime(&self) -> i64 { - self.ext.ctime() - } - - #[inline] - fn ctime_nsec(&self) -> i64 { - self.ext.ctime_nsec() - } - - #[inline] - fn blksize(&self) -> u64 { - self.ext.blksize() - } - - #[inline] - fn blocks(&self) -> u64 { - self.ext.blocks() - } } #[cfg(windows)] @@ -423,26 +226,6 @@ impl MetadataExt for Metadata { fn file_attributes(&self) -> u32 { self.ext.file_attributes() } - - #[inline] - fn creation_time(&self) -> u64 { - self.ext.creation_time() - } - - #[inline] - fn last_access_time(&self) -> u64 { - self.ext.last_access_time() - } - - #[inline] - fn last_write_time(&self) -> u64 { - self.ext.last_write_time() - } - - #[inline] - fn file_size(&self) -> u64 { - self.ext.file_size() - } } /// Extension trait to allow `volume_serial_number` etc. to be exposed by @@ -453,8 +236,5 @@ impl MetadataExt for Metadata { #[cfg(windows)] #[doc(hidden)] pub trait _WindowsByHandle { - fn file_attributes(&self) -> u32; - fn volume_serial_number(&self) -> Option; fn number_of_links(&self) -> Option; - fn file_index(&self) -> Option; } diff --git a/crates/wasi/src/filesystem/primitives/mod.rs b/crates/wasi/src/filesystem/primitives/mod.rs index 90228c268118..7fbc96ae86c4 100644 --- a/crates/wasi/src/filesystem/primitives/mod.rs +++ b/crates/wasi/src/filesystem/primitives/mod.rs @@ -6,38 +6,25 @@ #![allow(trivial_numeric_casts)] #![allow(unsafe_op_in_unsafe_fn)] -mod access; -mod canonicalize; -mod copy; mod create_dir; -mod dir_builder; mod dir_entry; mod dir_options; -mod file; #[cfg(not(any(target_os = "android", target_os = "linux", windows)))] mod file_path_by_searching; mod file_type; mod follow_symlinks; mod hard_link; -mod is_file_read_write; mod maybe_owned_file; mod metadata; mod open; -mod open_ambient; mod open_dir; mod open_options; mod open_unchecked_error; -mod permissions; mod read_dir; mod read_link; mod remove_dir; -mod remove_dir_all; mod remove_file; -mod remove_open_dir; mod rename; -mod reopen; -#[cfg(not(target_os = "wasi"))] -mod set_permissions; mod set_times; mod stat; mod symlink; @@ -62,14 +49,7 @@ mod windows; #[cfg(windows)] pub(crate) use self::windows::fs::*; -#[cfg(not(windows))] -pub(crate) use read_dir::{read_dir_nofollow, read_dir_unchecked}; - -pub use access::AccessType; pub use create_dir::create_dir; -pub use dir_builder::*; -#[cfg(windows)] -pub use dir_entry::_WindowsDirEntryExt; pub use dir_entry::DirEntry; pub use dir_options::DirOptions; #[cfg(windows)] @@ -79,21 +59,16 @@ pub use file_type::FileType; pub use file_type::FileTypeExt; pub use follow_symlinks::FollowSymlinks; pub use hard_link::hard_link; -pub use is_file_read_write::is_file_read_write; #[cfg(windows)] pub use metadata::_WindowsByHandle; pub use metadata::{Metadata, MetadataExt}; pub use open::open; pub use open_dir::*; pub use open_options::*; -pub use permissions::Permissions; -#[cfg(unix)] -pub use permissions::PermissionsExt; -pub use read_dir::{ReadDir, read_base_dir}; +pub use read_dir::read_base_dir; pub use read_link::read_link; pub use remove_dir::remove_dir; pub use remove_file::remove_file; -pub use remove_open_dir::remove_open_dir; pub use rename::rename; pub use set_times::{set_times, set_times_nofollow}; pub use stat::stat; diff --git a/crates/wasi/src/filesystem/primitives/open_ambient.rs b/crates/wasi/src/filesystem/primitives/open_ambient.rs deleted file mode 100644 index a22841c13e35..000000000000 --- a/crates/wasi/src/filesystem/primitives/open_ambient.rs +++ /dev/null @@ -1,22 +0,0 @@ -//! This defines `open_ambient`, for unsandboxed file opening. - -use crate::filesystem::primitives::{OpenOptions, open_ambient_impl}; -use ambient_authority::AmbientAuthority; -use std::path::Path; -use std::{fs, io}; - -/// Open a file named by a bare path, using the host process' ambient -/// authority. -/// -/// # Ambient Authority -/// -/// This function is not sandboxed and may trivially access any path that the -/// host process has access to. -#[inline] -pub fn open_ambient( - path: &Path, - options: &OpenOptions, - ambient_authority: AmbientAuthority, -) -> io::Result { - Ok(open_ambient_impl(path, options, ambient_authority)?) -} diff --git a/crates/wasi/src/filesystem/primitives/open_dir.rs b/crates/wasi/src/filesystem/primitives/open_dir.rs index d0e66a2d54e9..f30f8f47549f 100644 --- a/crates/wasi/src/filesystem/primitives/open_dir.rs +++ b/crates/wasi/src/filesystem/primitives/open_dir.rs @@ -7,7 +7,7 @@ use crate::filesystem::primitives::{ FollowSymlinks, dir_options, open, open_ambient_dir_impl, readdir_options, }; use ambient_authority::AmbientAuthority; -use std::path::{Component, Path}; +use std::path::Path; use std::{fs, io}; /// Open a directory by performing an `openat`-like operation, @@ -18,24 +18,6 @@ pub fn open_dir(start: &fs::File, path: &Path) -> io::Result { open(start, path, &dir_options()) } -/// Like `open_dir`, but additionally request the ability to read the directory -/// entries. -#[cfg(not(windows))] -#[inline] -pub(crate) fn open_dir_for_reading( - start: &fs::File, - path: &Path, - follow: FollowSymlinks, -) -> io::Result { - open(start, path, readdir_options().follow(follow)) -} - -/// Similar to `open_dir`, but fails if the path names a symlink. -#[inline] -pub fn open_dir_nofollow(start: &fs::File, path: &Path) -> io::Result { - open(start, path, dir_options().follow(FollowSymlinks::No)) -} - /// Open a directory by performing an unsandboxed `openat`-like operation. #[inline] #[allow(dead_code)] @@ -66,18 +48,3 @@ pub(crate) fn open_dir_for_reading_unchecked( pub fn open_ambient_dir(path: &Path, ambient_authority: AmbientAuthority) -> io::Result { open_ambient_dir_impl(path, ambient_authority) } - -/// Open the parent directory of a given open directory, using the host -/// process' ambient authority. -/// -/// # Ambient Authority -/// -/// This function accesses a path outside of the `start` directory subtree. -#[inline] -pub fn open_parent_dir( - start: &fs::File, - ambient_authority: AmbientAuthority, -) -> io::Result { - let _ = ambient_authority; - open_dir_unchecked(start, Component::ParentDir.as_ref()) -} diff --git a/crates/wasi/src/filesystem/primitives/open_options.rs b/crates/wasi/src/filesystem/primitives/open_options.rs index ae2b67d76f3e..ad07d3fc4681 100644 --- a/crates/wasi/src/filesystem/primitives/open_options.rs +++ b/crates/wasi/src/filesystem/primitives/open_options.rs @@ -82,15 +82,6 @@ impl OpenOptions { self } - /// Sets the option for the append mode. - /// - /// This corresponds to [`std::fs::OpenOptions::append`]. - #[inline] - pub fn append(&mut self, append: bool) -> &mut Self { - self.append = append; - self - } - /// Sets the option for truncating a previous file. /// /// This corresponds to [`std::fs::OpenOptions::truncate`]. @@ -133,49 +124,6 @@ impl OpenOptions { self } - /// Sets the option to disable an error if the opened object is a - /// directory. - #[inline] - pub(crate) fn maybe_dir(&mut self, maybe_dir: bool) -> &mut Self { - self.maybe_dir = maybe_dir; - self - } - - /// Requests write operations complete as defined by synchronized I/O file - /// integrity completion. - #[inline] - pub(crate) fn sync(&mut self, enable: bool) -> &mut Self { - self.sync = enable; - self - } - - /// Requests write operations complete as defined by synchronized I/O data - /// integrity completion. - #[inline] - pub(crate) fn dsync(&mut self, enable: bool) -> &mut Self { - self.dsync = enable; - self - } - - /// Requests read operations complete as defined by the level of integrity - /// specified by `sync` and `dsync`. - #[inline] - pub(crate) fn rsync(&mut self, enable: bool) -> &mut Self { - self.rsync = enable; - self - } - - /// Requests that I/O operations fail with `std::io::ErrorKind::WouldBlock` - /// if they would otherwise block. - /// - /// This option is commonly not implemented for regular files, so blocking - /// may still occur. - #[inline] - pub(crate) fn nonblock(&mut self, enable: bool) -> &mut Self { - self.nonblock = enable; - self - } - /// Sets the option to request the ability to read directory entries. #[inline] pub(crate) fn readdir_required(&mut self, readdir_required: bool) -> &mut Self { @@ -193,69 +141,11 @@ impl OpenOptions { pub fn _cap_fs_ext_follow(&mut self, follow: FollowSymlinks) -> &mut Self { self.follow(follow) } - - /// Wrapper to allow `maybe_dir` to be exposed by the `cap-fs-ext` crate. - /// - /// This is hidden from the main API since this functionality isn't present - /// in `std`. Use `cap_fs_ext::OpenOptionsMaybeDirExt` instead of - /// calling this directly. - #[doc(hidden)] - #[inline] - pub fn _cap_fs_ext_maybe_dir(&mut self, maybe_dir: bool) -> &mut Self { - self.maybe_dir(maybe_dir) - } - - /// Wrapper to allow `sync` to be exposed by the `cap-fs-ext` crate. - /// - /// This is hidden from the main API since this functionality isn't present - /// in `std`. Use `cap_fs_ext::OpenOptionsSyncExt` instead of - /// calling this directly. - #[doc(hidden)] - #[inline] - pub fn _cap_fs_ext_sync(&mut self, enable: bool) -> &mut Self { - self.sync(enable) - } - - /// Wrapper to allow `dsync` to be exposed by the `cap-fs-ext` crate. - /// - /// This is hidden from the main API since this functionality isn't present - /// in `std`. Use `cap_fs_ext::OpenOptionsSyncExt` instead of - /// calling this directly. - #[doc(hidden)] - #[inline] - pub fn _cap_fs_ext_dsync(&mut self, enable: bool) -> &mut Self { - self.dsync(enable) - } - - /// Wrapper to allow `rsync` to be exposed by the `cap-fs-ext` crate. - /// - /// This is hidden from the main API since this functionality isn't present - /// in `std`. Use `cap_fs_ext::OpenOptionsSyncExt` instead of - /// calling this directly. - #[doc(hidden)] - #[inline] - pub fn _cap_fs_ext_rsync(&mut self, enable: bool) -> &mut Self { - self.rsync(enable) - } - - /// Wrapper to allow `nonblock` to be exposed by the `cap-fs-ext` crate. - /// - /// This is hidden from the main API since this functionality isn't present - /// in `std`. Use `cap_fs_ext::OpenOptionsSyncExt` instead of - /// calling this directly. - #[doc(hidden)] - #[inline] - pub fn _cap_fs_ext_nonblock(&mut self, enable: bool) -> &mut Self { - self.nonblock(enable) - } } /// Unix-specific extensions to [`fs::OpenOptions`]. #[cfg(unix)] pub trait OpenOptionsExt { - /// Sets the mode bits that a new file will be created with. - fn mode(&mut self, mode: u32) -> &mut Self; - /// Pass custom flags to the `flags` argument of `open`. fn custom_flags(&mut self, flags: i32) -> &mut Self; } @@ -320,35 +210,10 @@ pub trait OpenOptionsExt { /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea /// [`CreateFile2`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfile2 fn custom_flags(&mut self, flags: u32) -> &mut Self; - - /// Sets the `dwFileAttributes` argument to the call to [`CreateFile2`] to - /// the specified value (or combines it with `custom_flags` and - /// `security_qos_flags` to set the `dwFlagsAndAttributes` for - /// [`CreateFile`]). - /// - /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea - /// [`CreateFile2`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfile2 - fn attributes(&mut self, val: u32) -> &mut Self; - - /// Sets the `dwSecurityQosFlags` argument to the call to [`CreateFile2`] to - /// the specified value (or combines it with `custom_flags` and `attributes` - /// to set the `dwFlagsAndAttributes` for [`CreateFile`]). - /// - /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea - /// [`CreateFile2`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfile2 - /// [Impersonation Levels]: - /// https://docs.microsoft.com/en-us/windows/win32/api/winnt/ne-winnt-security_impersonation_level - fn security_qos_flags(&mut self, flags: u32) -> &mut Self; } #[cfg(unix)] impl OpenOptionsExt for OpenOptions { - #[inline] - fn mode(&mut self, mode: u32) -> &mut Self { - self.ext.mode(mode); - self - } - #[inline] fn custom_flags(&mut self, flags: i32) -> &mut Self { self.ext.custom_flags(flags); @@ -435,16 +300,4 @@ impl OpenOptionsExt for OpenOptions { self.ext.custom_flags(flags); self } - - #[inline] - fn attributes(&mut self, val: u32) -> &mut Self { - self.ext.attributes(val); - self - } - - #[inline] - fn security_qos_flags(&mut self, flags: u32) -> &mut Self { - self.ext.security_qos_flags(flags); - self - } } diff --git a/crates/wasi/src/filesystem/primitives/permissions.rs b/crates/wasi/src/filesystem/primitives/permissions.rs deleted file mode 100644 index b6e141e9983e..000000000000 --- a/crates/wasi/src/filesystem/primitives/permissions.rs +++ /dev/null @@ -1,150 +0,0 @@ -#[cfg(not(windows))] -use crate::filesystem::primitives::ImplPermissionsExt; -#[cfg(unix)] -use rustix::fs::RawMode; -use std::{fs, io}; - -/// Representation of the various permissions on a file. -/// -/// This corresponds to [`std::fs::Permissions`]. -/// -///
-/// We need to define our own version because the libstd `Permissions` doesn't -/// have a public constructor that we can use. -///
-#[derive(Debug, Clone, Eq, PartialEq)] -pub struct Permissions { - pub(crate) readonly: bool, - - #[cfg(any(unix, target_os = "vxworks"))] - pub(crate) ext: ImplPermissionsExt, -} - -impl Permissions { - /// Constructs a new instance of `Self` from the given - /// `std::fs::Permissions`. - #[inline] - pub fn from_std(std: fs::Permissions) -> Self { - Self { - readonly: std.readonly(), - - #[cfg(any(unix, target_os = "vxworks"))] - ext: ImplPermissionsExt::from_std(std), - } - } - - /// Consumes `self` and produces a new instance of `std::fs::Permissions`. - /// - ///
- /// The `file` parameter works around the fact that we can't construct a - /// `Permissions` object ourselves on Windows. - ///
- #[inline] - pub fn into_std(self, file: &fs::File) -> io::Result { - self._into_std(file) - } - - #[cfg(unix)] - #[inline] - #[allow(clippy::unnecessary_wraps)] - fn _into_std(self, _file: &fs::File) -> io::Result { - use std::os::unix::fs::PermissionsExt; - Ok(fs::Permissions::from_mode( - crate::filesystem::primitives::PermissionsExt::mode(&self.ext), - )) - } - - #[cfg(target_os = "wasi")] - #[inline] - #[allow(clippy::unnecessary_wraps)] - fn _into_std(self, file: &fs::File) -> io::Result { - let mut permissions = file.metadata()?.permissions(); - permissions.set_readonly(self.readonly()); - Ok(permissions) - } - - #[cfg(windows)] - #[inline] - fn _into_std(self, file: &fs::File) -> io::Result { - let mut permissions = file.metadata()?.permissions(); - permissions.set_readonly(self.readonly()); - Ok(permissions) - } - - /// Returns `true` if these permissions describe a readonly (unwritable) - /// file. - /// - /// This corresponds to [`std::fs::Permissions::readonly`]. - #[inline] - pub const fn readonly(&self) -> bool { - self.readonly - } - - /// Modifies the readonly flag for this set of permissions. - /// - /// This corresponds to [`std::fs::Permissions::set_readonly`]. - #[inline] - pub fn set_readonly(&mut self, readonly: bool) { - self.readonly = readonly; - - #[cfg(any(unix, target_os = "vxworks"))] - self.ext.set_readonly(readonly); - } -} - -/// Unix-specific extensions to [`Permissions`]. -#[cfg(unix)] -pub trait PermissionsExt { - /// Returns the underlying raw `st_mode` bits that contain the standard - /// Unix permissions for this file. - fn mode(&self) -> u32; - - /// Sets the underlying raw bits for this set of permissions. - fn set_mode(&mut self, mode: u32); - - /// Creates a new instance of `Permissions` from the given set of Unix - /// permission bits. - fn from_mode(mode: u32) -> Self; -} - -#[cfg(unix)] -impl PermissionsExt for Permissions { - #[inline] - fn mode(&self) -> u32 { - crate::filesystem::primitives::PermissionsExt::mode(&self.ext) - } - - #[inline] - fn set_mode(&mut self, mode: u32) { - crate::filesystem::primitives::PermissionsExt::set_mode(&mut self.ext, mode) - } - - #[inline] - fn from_mode(mode: u32) -> Self { - Self { - readonly: ImplPermissionsExt::readonly(mode as RawMode), - ext: crate::filesystem::primitives::PermissionsExt::from_mode(mode), - } - } -} - -#[cfg(target_os = "vxworks")] -impl PermissionsExt for Permissions { - #[inline] - fn mode(&self) -> u32 { - self.ext.mode() - } - - #[inline] - fn set_mode(&mut self, mode: u32) { - self.ext.set_mode(mode) - } - - #[inline] - fn from_mode(mode: u32) -> Self { - Self { - readonly: ImplPermissionsExt::readonly(mode), - ext: ImplPermissionsExt::from(mode), - } - } -} diff --git a/crates/wasi/src/filesystem/primitives/read_dir.rs b/crates/wasi/src/filesystem/primitives/read_dir.rs index 4652e6f49de0..dd6fd0c07f8d 100644 --- a/crates/wasi/src/filesystem/primitives/read_dir.rs +++ b/crates/wasi/src/filesystem/primitives/read_dir.rs @@ -1,26 +1,6 @@ -use crate::filesystem::primitives::{DirEntry, FollowSymlinks, ReadDirInner}; -use std::path::Path; +use crate::filesystem::primitives::{DirEntry, ReadDirInner}; use std::{fmt, fs, io}; -/// Construct a `ReadDir` to iterate over the contents of a directory, -/// ensuring that the resolution of the path never escapes the directory -/// tree rooted at `start`. -#[inline] -pub fn read_dir(start: &fs::File, path: &Path) -> io::Result { - Ok(ReadDir { - inner: ReadDirInner::new(start, path, FollowSymlinks::Yes)?, - }) -} - -/// Like `read_dir`, but fails if `path` names a symlink. -#[inline] -#[cfg(not(windows))] -pub(crate) fn read_dir_nofollow(start: &fs::File, path: &Path) -> io::Result { - Ok(ReadDir { - inner: ReadDirInner::new(start, path, FollowSymlinks::No)?, - }) -} - /// Like `read_dir` but operates on the base directory itself, rather than /// on a path based on it. #[inline] @@ -30,19 +10,6 @@ pub fn read_base_dir(start: &fs::File) -> io::Result { }) } -/// Like `read_dir`, but doesn't perform sandboxing. -#[inline] -#[cfg(not(windows))] -pub(crate) fn read_dir_unchecked( - start: &fs::File, - path: &Path, - follow: FollowSymlinks, -) -> io::Result { - Ok(ReadDir { - inner: ReadDirInner::new_unchecked(start, path, follow)?, - }) -} - /// Iterator over the entries in a directory. /// /// This corresponds to [`std::fs::ReadDir`]. diff --git a/crates/wasi/src/filesystem/primitives/remove_dir_all.rs b/crates/wasi/src/filesystem/primitives/remove_dir_all.rs deleted file mode 100644 index 75ccbdab02cd..000000000000 --- a/crates/wasi/src/filesystem/primitives/remove_dir_all.rs +++ /dev/null @@ -1,9 +0,0 @@ -use crate::filesystem::primitives::remove_dir_all_impl; -use std::path::Path; -use std::{fs, io}; - -/// Removes a directory and all of its contents. -#[inline] -pub fn remove_dir_all(start: &fs::File, path: &Path) -> io::Result<()> { - remove_dir_all_impl(start, path) -} diff --git a/crates/wasi/src/filesystem/primitives/remove_open_dir.rs b/crates/wasi/src/filesystem/primitives/remove_open_dir.rs deleted file mode 100644 index 71cd2434d043..000000000000 --- a/crates/wasi/src/filesystem/primitives/remove_open_dir.rs +++ /dev/null @@ -1,16 +0,0 @@ -use crate::filesystem::primitives::{remove_open_dir_all_impl, remove_open_dir_impl}; -use std::{fs, io}; - -/// Given an open directory handle, delete the directory. -#[inline] -pub fn remove_open_dir(dir: fs::File) -> io::Result<()> { - remove_open_dir_impl(dir) -} - -/// Given an open directory handle, recursively delete the contents of the -/// directory plus the directory itself. -#[allow(clippy::module_name_repetitions)] -#[inline] -pub fn remove_open_dir_all(dir: fs::File) -> io::Result<()> { - remove_open_dir_all_impl(dir) -} diff --git a/crates/wasi/src/filesystem/primitives/reopen.rs b/crates/wasi/src/filesystem/primitives/reopen.rs deleted file mode 100644 index 833ed0066232..000000000000 --- a/crates/wasi/src/filesystem/primitives/reopen.rs +++ /dev/null @@ -1,36 +0,0 @@ -//! Re-open a `fs::File` to produce an independent handle. - -use crate::filesystem::primitives::{OpenOptions, is_file_read_write, is_same_file, reopen_impl}; -use std::{fs, io}; - -/// Re-open an `fs::File` to produce an independent handle. -/// -/// This operation isn't supported by all operating systems in all -/// circumstances, or in some operating systems in any circumstances, -/// so it may return an `io::ErrorKind::Other` error if the file -/// cannot be reopened. -#[inline] -pub fn reopen(file: &fs::File, options: &OpenOptions) -> io::Result { - let (read, write) = is_file_read_write(file)?; - - // Don't grant more rights than the original file had. And don't allow - // it to create a file. - if options.create - || options.create_new - || (!read && options.read) - || (!write && (options.write || options.append || options.truncate)) - { - return Err(io::Error::new( - io::ErrorKind::PermissionDenied, - "Couldn't reopen file", - )); - } - - let new = reopen_impl(file, options)?; - - if !is_same_file(file, &new)? { - return Err(io::Error::new(io::ErrorKind::Other, "Couldn't reopen file")); - } - - Ok(new) -} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/access_unchecked.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/access_unchecked.rs deleted file mode 100644 index c9745a39d66c..000000000000 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/access_unchecked.rs +++ /dev/null @@ -1,38 +0,0 @@ -use crate::filesystem::primitives::{AccessType, FollowSymlinks}; -use rustix::fs::{Access, AtFlags}; -use std::path::Path; -use std::{fs, io}; - -/// *Unsandboxed* function similar to `access`, but which does not perform -/// sandboxing. -pub(crate) fn access_unchecked( - start: &fs::File, - path: &Path, - type_: AccessType, - follow: FollowSymlinks, -) -> io::Result<()> { - let mut access_type = Access::empty(); - match type_ { - AccessType::Exists => access_type |= Access::EXISTS, - AccessType::Access(modes) => { - if modes.readable { - access_type |= Access::READ_OK; - } - if modes.writable { - access_type |= Access::WRITE_OK; - } - if modes.executable { - access_type |= Access::EXEC_OK; - } - } - } - - let atflags = match follow { - FollowSymlinks::Yes => AtFlags::empty(), - FollowSymlinks::No => AtFlags::SYMLINK_NOFOLLOW, - }; - - rustix::fs::accessat(start, path, access_type, atflags)?; - - Ok(()) -} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/copy_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/copy_impl.rs deleted file mode 100644 index 008a6b9ecd7c..000000000000 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/copy_impl.rs +++ /dev/null @@ -1,248 +0,0 @@ -// Implementation derived from `copy` in Rust's -// library/std/src/sys/unix/fs.rs at revision -// 108e90ca78f052c0c1c49c42a22c85620be19712. - -use crate::filesystem::primitives::{OpenOptions, open}; -#[cfg(any(target_os = "android", target_os = "linux"))] -use rustix::fs::copy_file_range; -#[cfg(any( - target_os = "macos", - target_os = "ios", - target_os = "tvos", - target_os = "watchos", - target_os = "visionos", -))] -use rustix::fs::{ - CloneFlags, CopyfileFlags, copyfile_state_alloc, copyfile_state_free, - copyfile_state_get_copied, copyfile_state_t, fclonefileat, fcopyfile, -}; -use std::path::Path; -use std::{fs, io}; - -fn open_from(start: &fs::File, path: &Path) -> io::Result<(fs::File, fs::Metadata)> { - let reader = open(start, path, OpenOptions::new().read(true))?; - let metadata = reader.metadata()?; - if !metadata.is_file() { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "the source path is not an existing regular file", - )); - } - Ok((reader, metadata)) -} - -#[cfg(not(target_os = "wasi"))] -fn open_to_and_set_permissions( - start: &fs::File, - path: &Path, - reader_metadata: fs::Metadata, -) -> io::Result<(fs::File, fs::Metadata)> { - use crate::filesystem::primitives::OpenOptionsExt; - use std::os::unix::fs::PermissionsExt; - - let perm = reader_metadata.permissions(); - let writer = open( - start, - path, - OpenOptions::new() - // create the file with the correct mode right away - .mode(perm.mode()) - .write(true) - .create(true) - .truncate(true), - )?; - let writer_metadata = writer.metadata()?; - if writer_metadata.is_file() { - // Set the correct file permissions, in case the file already existed. - // Don't set the permissions on already existing non-files like - // pipes/FIFOs or device nodes. - writer.set_permissions(perm)?; - } - Ok((writer, writer_metadata)) -} - -#[cfg(target_os = "wasi")] -fn open_to_and_set_permissions( - start: &fs::File, - path: &Path, - reader_metadata: fs::Metadata, -) -> io::Result<(fs::File, fs::Metadata)> { - let writer = open( - start, - path, - OpenOptions::new() - // create the file with the correct mode right away - .write(true) - .create(true) - .truncate(true), - )?; - let writer_metadata = writer.metadata()?; - Ok((writer, writer_metadata)) -} - -#[cfg(not(any( - target_os = "linux", - target_os = "android", - target_os = "macos", - target_os = "ios", - target_os = "tvos", - target_os = "watchos", - target_os = "visionos", -)))] -pub(crate) fn copy_impl( - from_start: &fs::File, - from_path: &Path, - to_start: &fs::File, - to_path: &Path, -) -> io::Result { - let (mut reader, reader_metadata) = open_from(from_start, from_path)?; - let (mut writer, _) = open_to_and_set_permissions(to_start, to_path, reader_metadata)?; - - io::copy(&mut reader, &mut writer) -} - -#[cfg(any(target_os = "android", target_os = "linux"))] -pub(crate) fn copy_impl( - from_start: &fs::File, - from_path: &Path, - to_start: &fs::File, - to_path: &Path, -) -> io::Result { - use std::cmp; - use std::sync::atomic::{AtomicBool, Ordering}; - - // Kernel prior to 4.5 don't have copy_file_range - // We store the availability in a global to avoid unnecessary syscalls - static HAS_COPY_FILE_RANGE: AtomicBool = AtomicBool::new(true); - - let (mut reader, reader_metadata) = open_from(from_start, from_path)?; - let len = reader_metadata.len(); - let (mut writer, _) = open_to_and_set_permissions(to_start, to_path, reader_metadata)?; - - let has_copy_file_range = HAS_COPY_FILE_RANGE.load(Ordering::Relaxed); - let mut written = 0_u64; - while written < len { - let copy_result = if has_copy_file_range { - let bytes_to_copy = cmp::min(len - written, usize::MAX as u64); - - // `copy_file_range` takes a `usize`; convert with saturation so - // that we copy as many bytes as we can. - let bytes_to_copy = usize::try_from(bytes_to_copy).unwrap_or(usize::MAX); - - // We actually don't have to adjust the offsets, - // because copy_file_range adjusts the file offset automatically - let copy_result = copy_file_range(&reader, None, &writer, None, bytes_to_copy); - if let Err(copy_err) = copy_result { - match copy_err { - rustix::io::Errno::NOSYS | rustix::io::Errno::PERM => { - HAS_COPY_FILE_RANGE.store(false, Ordering::Relaxed); - } - _ => {} - } - } - copy_result - } else { - Err(rustix::io::Errno::NOSYS) - }; - match copy_result { - Ok(ret) => written += ret as u64, - Err(err) => { - match err { - rustix::io::Errno::NOSYS - | rustix::io::Errno::XDEV - | rustix::io::Errno::INVAL - | rustix::io::Errno::PERM => { - // Try fallback io::copy if either: - // - Kernel version is < 4.5 (ENOSYS) - // - Files are mounted on different fs (EXDEV) - // - copy_file_range is disallowed, for example by seccomp (EPERM) - // - copy_file_range cannot be used with pipes or device nodes (EINVAL) - assert_eq!(written, 0); - return io::copy(&mut reader, &mut writer); - } - _ => return Err(err.into()), - } - } - } - } - Ok(written) -} - -#[cfg(any( - target_os = "macos", - target_os = "ios", - target_os = "tvos", - target_os = "watchos", - target_os = "visionos", -))] -#[allow(non_upper_case_globals)] -#[allow(unsafe_code)] -pub(crate) fn copy_impl( - from_start: &fs::File, - from_path: &Path, - to_start: &fs::File, - to_path: &Path, -) -> io::Result { - use std::sync::atomic::{AtomicBool, Ordering}; - - struct FreeOnDrop(copyfile_state_t); - impl Drop for FreeOnDrop { - fn drop(&mut self) { - // Safety: This is the only place where we free the state, and we - // never let it escape. - unsafe { - copyfile_state_free(self.0).ok(); - } - } - } - - // MacOS prior to 10.12 don't support `fclonefileat` - // We store the availability in a global to avoid unnecessary syscalls - static HAS_FCLONEFILEAT: AtomicBool = AtomicBool::new(true); - - let (reader, reader_metadata) = open_from(from_start, from_path)?; - - // Opportunistically attempt to create a copy-on-write clone of `from_path` - // using `fclonefileat`. - if HAS_FCLONEFILEAT.load(Ordering::Relaxed) { - let clonefile_result = fclonefileat(&reader, to_start, to_path, CloneFlags::empty()); - match clonefile_result { - Ok(_) => return Ok(reader_metadata.len()), - Err(err) => match err { - // `fclonefileat` will fail on non-APFS volumes, if the - // destination already exists, or if the source and destination - // are on different devices. In all these cases `fcopyfile` - // should succeed. - rustix::io::Errno::NOTSUP | rustix::io::Errno::EXIST | rustix::io::Errno::XDEV => { - () - } - rustix::io::Errno::NOSYS => HAS_FCLONEFILEAT.store(false, Ordering::Relaxed), - _ => return Err(err.into()), - }, - } - } - - // Fall back to using `fcopyfile` if `fclonefileat` does not succeed. - let (writer, writer_metadata) = - open_to_and_set_permissions(to_start, to_path, reader_metadata)?; - - // We ensure that `FreeOnDrop` never contains a null pointer so it is - // always safe to call `copyfile_state_free` - let state = { - let state = copyfile_state_alloc()?; - FreeOnDrop(state) - }; - - let flags = if writer_metadata.is_file() { - CopyfileFlags::ALL - } else { - CopyfileFlags::DATA - }; - - // Safety: We allocated `state` above so it's still live here. - unsafe { - fcopyfile(&reader, &writer, state.0, flags)?; - - Ok(copyfile_state_get_copied(state.0)?) - } -} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/dir_entry_inner.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/dir_entry_inner.rs index bb3101c7e94e..450f6df688bd 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/dir_entry_inner.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/dir_entry_inner.rs @@ -1,13 +1,11 @@ -use crate::filesystem::primitives::{ - FileType, FollowSymlinks, Metadata, MetadataExt, OpenOptions, ReadDir, ReadDirInner, -}; +use crate::filesystem::primitives::{Metadata, ReadDirInner}; use rustix::fs::DirEntry; use std::ffi::{OsStr, OsString}; #[cfg(unix)] use std::os::unix::ffi::OsStrExt; #[cfg(target_os = "wasi")] use std::os::wasi::ffi::OsStrExt; -use std::{fmt, fs, io}; +use std::{fmt, io}; pub(crate) struct DirEntryInner { pub(super) rustix: DirEntry, @@ -15,58 +13,11 @@ pub(crate) struct DirEntryInner { } impl DirEntryInner { - #[inline] - pub(crate) fn open(&self, options: &OpenOptions) -> io::Result { - self.read_dir.open(self.file_name_bytes(), options) - } - #[inline] pub(crate) fn metadata(&self) -> io::Result { self.read_dir.metadata(self.file_name_bytes()) } - #[inline] - pub(crate) fn remove_file(&self) -> io::Result<()> { - self.read_dir.remove_file(self.file_name_bytes()) - } - - #[inline] - pub(crate) fn read_dir(&self, follow: FollowSymlinks) -> io::Result { - self.read_dir.read_dir(self.file_name_bytes(), follow) - } - - #[inline] - pub(crate) fn remove_dir(&self) -> io::Result<()> { - self.read_dir.remove_dir(self.file_name_bytes()) - } - - #[cfg(not(any(target_os = "illumos", target_os = "solaris")))] - #[inline] - #[allow(clippy::unnecessary_wraps)] - pub(crate) fn file_type(&self) -> io::Result { - use crate::filesystem::primitives::ImplFileTypeExt; - - Ok(match self.rustix.file_type() { - rustix::fs::FileType::Directory => FileType::dir(), - rustix::fs::FileType::RegularFile => FileType::file(), - rustix::fs::FileType::Symlink => FileType::ext(ImplFileTypeExt::symlink()), - #[cfg(not(target_os = "wasi"))] - rustix::fs::FileType::Fifo => FileType::ext(ImplFileTypeExt::fifo()), - #[cfg(not(target_os = "wasi"))] - rustix::fs::FileType::Socket => FileType::ext(ImplFileTypeExt::socket()), - rustix::fs::FileType::CharacterDevice => FileType::ext(ImplFileTypeExt::char_device()), - rustix::fs::FileType::BlockDevice => FileType::ext(ImplFileTypeExt::block_device()), - rustix::fs::FileType::Unknown => FileType::unknown(), - }) - } - - #[cfg(any(target_os = "illumos", target_os = "solaris"))] - #[inline] - pub(crate) fn file_type(&self) -> io::Result { - // These platforms don't have the file type on the dirent, so we must lstat the file. - self.metadata().map(|m| m.file_type()) - } - #[inline] pub(crate) fn file_name(&self) -> OsString { self.file_name_bytes().to_os_string() @@ -77,12 +28,6 @@ impl DirEntryInner { self.rustix.ino() } - #[inline] - pub(crate) fn is_same_file(&self, metadata: &Metadata) -> io::Result { - let self_md = self.metadata()?; - Ok(self_md.ino() == metadata.ino() && self_md.dev() == metadata.dev()) - } - fn file_name_bytes(&self) -> &OsStr { OsStr::from_bytes(self.rustix.file_name().to_bytes()) } diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/dir_options_ext.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/dir_options_ext.rs index 103b22496bae..474d6aea40ed 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/dir_options_ext.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/dir_options_ext.rs @@ -12,10 +12,3 @@ impl DirOptionsExt { } } } - -impl crate::filesystem::primitives::DirBuilderExt for DirOptionsExt { - fn mode(&mut self, mode: u32) -> &mut Self { - self.mode = mode; - self - } -} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/dir_utils.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/dir_utils.rs index c3f16dad06e5..b70b576667ad 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/dir_utils.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/dir_utils.rs @@ -90,11 +90,6 @@ pub(crate) fn readdir_options() -> OpenOptions { .clone() } -/// Return an `OpenOptions` for canonicalizing paths. -pub(crate) fn canonicalize_options() -> OpenOptions { - OpenOptions::new().read(true).clone() -} - /// Open a directory named by a bare path, using the host process' ambient /// authority. /// diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/errors.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/errors.rs index 8a9de56f322e..203fa114966b 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/errors.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/errors.rs @@ -1,11 +1,5 @@ use std::io; -#[cfg(any(target_os = "android", target_os = "linux"))] -#[cold] -pub(crate) fn invalid_flags() -> io::Error { - rustix::io::Errno::INVAL.into() -} - #[cold] pub(crate) fn no_such_file_or_directory() -> io::Error { rustix::io::Errno::NOENT.into() diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/is_file_read_write_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/is_file_read_write_impl.rs deleted file mode 100644 index 3c6c4c63e5dd..000000000000 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/is_file_read_write_impl.rs +++ /dev/null @@ -1,43 +0,0 @@ -use rustix::fd::{AsFd, BorrowedFd}; -use rustix::fs::{OFlags, fcntl_getfl}; -use std::{fs, io}; - -#[inline] -pub(crate) fn is_file_read_write_impl(file: &fs::File) -> io::Result<(bool, bool)> { - Ok(is_file_read_write(file)?) -} - -/// `fcntl(fd, F_GETFL) & O_ACCMODE` -/// -/// Returns a pair of booleans indicating whether the file descriptor is -/// readable and/or writable, respectively. This is only reliable on files; for -/// example, it doesn't reflect whether sockets have been shut down; for -/// general I/O handle support, use [`io::is_read_write`]. -#[inline] -fn is_file_read_write(fd: Fd) -> io::Result<(bool, bool)> { - _is_file_read_write(fd.as_fd()) -} - -fn _is_file_read_write(fd: BorrowedFd<'_>) -> io::Result<(bool, bool)> { - let mode = fcntl_getfl(fd)?; - - // Check for `O_PATH`. - #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "emscripten", - target_os = "fuchsia" - ))] - if mode.contains(OFlags::PATH) { - return Ok((false, false)); - } - - // Use `RWMODE` rather than `ACCMODE` as `ACCMODE` may include `O_PATH`. - // We handled `O_PATH` above. - match mode & OFlags::RWMODE { - OFlags::RDONLY => Ok((true, false)), - OFlags::RDWR => Ok((true, true)), - OFlags::WRONLY => Ok((false, true)), - _ => unreachable!(), - } -} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/is_root_dir.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/is_root_dir.rs deleted file mode 100644 index 933965058586..000000000000 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/is_root_dir.rs +++ /dev/null @@ -1,6 +0,0 @@ -use crate::filesystem::primitives::{Metadata, ReadDir}; -use std::{fs, io}; - -pub(crate) fn is_root_dir(dir: &fs::File, parent_iter: &ReadDir) -> io::Result { - Ok(Metadata::from_file(dir)?.is_same_file(&parent_iter.inner.self_metadata()?)) -} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/metadata_ext.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/metadata_ext.rs index c557f66a1c81..ddd4f0f6b9cb 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/metadata_ext.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/metadata_ext.rs @@ -1,6 +1,6 @@ #![allow(clippy::useless_conversion)] -use crate::filesystem::primitives::{ImplFileTypeExt, ImplPermissionsExt, Metadata}; +use crate::filesystem::primitives::{ImplFileTypeExt, Metadata}; use rustix::fs::{RawMode, Stat}; #[cfg(target_os = "linux")] use rustix::fs::{Statx, StatxFlags, makedev}; @@ -11,33 +11,7 @@ use std::{fs, io}; pub(crate) struct ImplMetadataExt { dev: u64, ino: u64, - #[cfg(not(target_os = "wasi"))] - mode: u32, nlink: u64, - #[cfg(not(target_os = "wasi"))] - uid: u32, - #[cfg(not(target_os = "wasi"))] - gid: u32, - #[cfg(not(target_os = "wasi"))] - rdev: u64, - #[cfg(not(target_os = "wasi"))] - size: u64, - #[cfg(not(target_os = "wasi"))] - atime: i64, - #[cfg(not(target_os = "wasi"))] - atime_nsec: i64, - #[cfg(not(target_os = "wasi"))] - mtime: i64, - #[cfg(not(target_os = "wasi"))] - mtime_nsec: i64, - #[cfg(not(target_os = "wasi"))] - ctime: i64, - #[cfg(not(target_os = "wasi"))] - ctime_nsec: i64, - #[cfg(not(target_os = "wasi"))] - blksize: u64, - #[cfg(not(target_os = "wasi"))] - blocks: u64, } impl ImplMetadataExt { @@ -58,33 +32,7 @@ impl ImplMetadataExt { Self { dev: std.dev(), ino: std.ino(), - #[cfg(not(target_os = "wasi"))] - mode: std.mode(), nlink: std.nlink(), - #[cfg(not(target_os = "wasi"))] - uid: std.uid(), - #[cfg(not(target_os = "wasi"))] - gid: std.gid(), - #[cfg(not(target_os = "wasi"))] - rdev: std.rdev(), - #[cfg(not(target_os = "wasi"))] - size: std.size(), - #[cfg(not(target_os = "wasi"))] - atime: std.atime(), - #[cfg(not(target_os = "wasi"))] - atime_nsec: std.atime_nsec(), - #[cfg(not(target_os = "wasi"))] - mtime: std.mtime(), - #[cfg(not(target_os = "wasi"))] - mtime_nsec: std.mtime_nsec(), - #[cfg(not(target_os = "wasi"))] - ctime: std.ctime(), - #[cfg(not(target_os = "wasi"))] - ctime_nsec: std.ctime_nsec(), - #[cfg(not(target_os = "wasi"))] - blksize: std.blksize(), - #[cfg(not(target_os = "wasi"))] - blocks: std.blocks(), } } @@ -95,10 +43,6 @@ impl ImplMetadataExt { Metadata { file_type: ImplFileTypeExt::from_raw_mode(stat.st_mode as RawMode), len: u64::try_from(stat.st_size).unwrap(), - #[cfg(not(target_os = "wasi"))] - permissions: ImplPermissionsExt::from_raw_mode(stat.st_mode as RawMode), - #[cfg(target_os = "wasi")] - permissions: ImplPermissionsExt::default(), #[cfg(not(target_os = "wasi"))] modified: system_time_from_rustix( @@ -160,33 +104,7 @@ impl ImplMetadataExt { u64::try_from(stat.st_dev).unwrap() }, ino: stat.st_ino.into(), - #[cfg(not(target_os = "wasi"))] - mode: u32::from(stat.st_mode), nlink: u64::from(stat.st_nlink), - #[cfg(not(target_os = "wasi"))] - uid: stat.st_uid, - #[cfg(not(target_os = "wasi"))] - gid: stat.st_gid, - #[cfg(not(target_os = "wasi"))] - rdev: u64::try_from(stat.st_rdev).unwrap(), - #[cfg(not(target_os = "wasi"))] - size: u64::try_from(stat.st_size).unwrap(), - #[cfg(not(target_os = "wasi"))] - atime: i64::try_from(stat.st_atime).unwrap(), - #[cfg(not(target_os = "wasi"))] - atime_nsec: stat.st_atime_nsec as _, - #[cfg(not(target_os = "wasi"))] - mtime: i64::try_from(stat.st_mtime).unwrap(), - #[cfg(not(target_os = "wasi"))] - mtime_nsec: stat.st_mtime_nsec as _, - #[cfg(not(target_os = "wasi"))] - ctime: i64::try_from(stat.st_ctime).unwrap(), - #[cfg(not(target_os = "wasi"))] - ctime_nsec: stat.st_ctime_nsec as _, - #[cfg(not(target_os = "wasi"))] - blksize: u64::try_from(stat.st_blksize).unwrap(), - #[cfg(not(target_os = "wasi"))] - blocks: u64::try_from(stat.st_blocks).unwrap(), }, } } @@ -198,7 +116,6 @@ impl ImplMetadataExt { Metadata { file_type: ImplFileTypeExt::from_raw_mode(RawMode::from(statx.stx_mode)), len: u64::try_from(statx.stx_size).unwrap(), - permissions: ImplPermissionsExt::from_raw_mode(RawMode::from(statx.stx_mode)), modified: if statx.stx_mask & StatxFlags::MTIME.bits() != 0 { system_time_from_rustix(statx.stx_mtime.tv_sec, statx.stx_mtime.tv_nsec as _) } else { @@ -218,29 +135,10 @@ impl ImplMetadataExt { ext: Self { dev: makedev(statx.stx_dev_major, statx.stx_dev_minor), ino: statx.stx_ino.into(), - mode: u32::from(statx.stx_mode), nlink: u64::from(statx.stx_nlink), - uid: statx.stx_uid, - gid: statx.stx_gid, - rdev: makedev(statx.stx_rdev_major, statx.stx_rdev_minor), - size: statx.stx_size, - atime: i64::from(statx.stx_atime.tv_sec), - atime_nsec: statx.stx_atime.tv_nsec as _, - mtime: i64::from(statx.stx_mtime.tv_sec), - mtime_nsec: statx.stx_mtime.tv_nsec as _, - ctime: i64::from(statx.stx_ctime.tv_sec), - ctime_nsec: statx.stx_ctime.tv_nsec as _, - blksize: u64::from(statx.stx_blksize), - blocks: statx.stx_blocks, }, } } - - /// Determine if `self` and `other` refer to the same inode on the same - /// device. - pub(crate) const fn is_same_file(&self, other: &Self) -> bool { - self.dev == other.dev && self.ino == other.ino - } } #[allow(clippy::similar_names)] @@ -266,88 +164,10 @@ impl crate::filesystem::primitives::MetadataExt for ImplMetadataExt { self.ino } - #[cfg(not(target_os = "wasi"))] - #[inline] - fn mode(&self) -> u32 { - self.mode - } - #[inline] fn nlink(&self) -> u64 { self.nlink } - - #[cfg(not(target_os = "wasi"))] - #[inline] - fn uid(&self) -> u32 { - self.uid - } - - #[cfg(not(target_os = "wasi"))] - #[inline] - fn gid(&self) -> u32 { - self.gid - } - - #[cfg(not(target_os = "wasi"))] - #[inline] - fn rdev(&self) -> u64 { - self.rdev - } - - #[cfg(not(target_os = "wasi"))] - #[inline] - fn size(&self) -> u64 { - self.size - } - - #[cfg(not(target_os = "wasi"))] - #[inline] - fn atime(&self) -> i64 { - self.atime - } - - #[cfg(not(target_os = "wasi"))] - #[inline] - fn atime_nsec(&self) -> i64 { - self.atime_nsec - } - - #[cfg(not(target_os = "wasi"))] - #[inline] - fn mtime(&self) -> i64 { - self.mtime - } - - #[cfg(not(target_os = "wasi"))] - #[inline] - fn mtime_nsec(&self) -> i64 { - self.mtime_nsec - } - - #[cfg(not(target_os = "wasi"))] - #[inline] - fn ctime(&self) -> i64 { - self.ctime - } - - #[cfg(not(target_os = "wasi"))] - #[inline] - fn ctime_nsec(&self) -> i64 { - self.ctime_nsec - } - - #[cfg(not(target_os = "wasi"))] - #[inline] - fn blksize(&self) -> u64 { - self.blksize - } - - #[cfg(not(target_os = "wasi"))] - #[inline] - fn blocks(&self) -> u64 { - self.blocks - } } /// It should be possible to represent times before the Epoch. diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/mod.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/mod.rs index 00f210a88d2b..c1c735d80d94 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/mod.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/mod.rs @@ -1,5 +1,3 @@ -mod access_unchecked; -mod copy_impl; mod create_dir_unchecked; mod dir_entry_inner; #[cfg(not(target_os = "wasi"))] @@ -9,26 +7,18 @@ mod dir_utils; mod file_path; mod file_type_ext; mod hard_link_unchecked; -mod is_file_read_write_impl; -mod is_root_dir; mod is_same_file; mod metadata_ext; mod oflags; mod open_options_ext; mod open_unchecked; -mod permissions_ext; mod read_dir_inner; mod read_link_unchecked; -mod remove_dir_all_impl; mod remove_dir_unchecked; mod remove_file_unchecked; -mod remove_open_dir_by_searching; mod rename_unchecked; -mod reopen_impl; #[cfg(not(any(target_os = "android", target_os = "linux", target_os = "wasi")))] mod set_permissions_impl; -#[cfg(not(target_os = "wasi"))] -mod set_symlink_permissions_unchecked; #[cfg(not(any(target_os = "android", target_os = "linux")))] mod set_times_impl; mod stat_unchecked; @@ -90,24 +80,18 @@ pub(crate) use file_path::file_path_by_ttyname_or_seaching as file_path; pub(crate) use set_permissions_impl::set_permissions_impl; #[cfg(target_os = "freebsd")] pub(crate) use set_permissions_impl::set_permissions_impl as set_permissions_manually; -#[cfg(not(target_os = "wasi"))] -pub(crate) use set_symlink_permissions_unchecked::set_symlink_permissions_unchecked; #[cfg(not(any(target_os = "android", target_os = "linux", target_os = "freebsd")))] pub(crate) use set_times_impl::set_times_impl; #[cfg(target_os = "freebsd")] pub(crate) use set_times_impl::set_times_impl as set_times_manually; #[rustfmt::skip] pub(crate) use crate::filesystem::primitives::{ - via_parent::access as access_impl, via_parent::hard_link as hard_link_impl, via_parent::create_dir as create_dir_impl, via_parent::read_link as read_link_impl, via_parent::rename as rename_impl, via_parent::symlink as symlink_impl, - remove_open_dir_by_searching as remove_open_dir_impl, }; -#[cfg(not(target_os = "wasi"))] -pub(crate) use crate::filesystem::primitives::via_parent::set_symlink_permissions as set_symlink_permissions_impl; #[cfg(not(target_os = "freebsd"))] #[rustfmt::skip] pub(crate) use crate::filesystem::primitives::{ @@ -115,8 +99,6 @@ pub(crate) use crate::filesystem::primitives::{ via_parent::remove_file as remove_file_impl, }; -pub(crate) use access_unchecked::access_unchecked; -pub(crate) use copy_impl::copy_impl; pub(crate) use create_dir_unchecked::create_dir_unchecked; pub(crate) use dir_entry_inner::DirEntryInner; #[cfg(not(target_os = "wasi"))] @@ -124,22 +106,16 @@ pub(crate) use dir_options_ext::DirOptionsExt; pub(crate) use dir_utils::*; pub(crate) use file_type_ext::ImplFileTypeExt; pub(crate) use hard_link_unchecked::hard_link_unchecked; -pub(crate) use is_file_read_write_impl::is_file_read_write_impl; -pub(crate) use is_root_dir::is_root_dir; #[allow(unused_imports)] pub(crate) use is_same_file::{is_different_file, is_different_file_metadata, is_same_file}; pub(crate) use metadata_ext::ImplMetadataExt; pub(crate) use open_options_ext::ImplOpenOptionsExt; -pub(crate) use open_unchecked::{open_ambient_impl, open_unchecked}; -pub(crate) use permissions_ext::ImplPermissionsExt; +pub(crate) use open_unchecked::open_unchecked; pub(crate) use read_dir_inner::ReadDirInner; pub(crate) use read_link_unchecked::read_link_unchecked; -pub(crate) use remove_dir_all_impl::{remove_dir_all_impl, remove_open_dir_all_impl}; pub(crate) use remove_dir_unchecked::remove_dir_unchecked; pub(crate) use remove_file_unchecked::remove_file_unchecked; -pub(crate) use remove_open_dir_by_searching::remove_open_dir_by_searching; pub(crate) use rename_unchecked::rename_unchecked; -pub(crate) use reopen_impl::reopen_impl; pub(crate) use stat_unchecked::stat_unchecked; pub(crate) use symlink_unchecked::symlink_unchecked; #[allow(unused_imports)] diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/open_options_ext.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/open_options_ext.rs index dc8ca59fc7d8..08402cb0ca69 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/open_options_ext.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/open_options_ext.rs @@ -12,11 +12,6 @@ impl ImplOpenOptionsExt { } } - pub(crate) fn mode(&mut self, mode: u32) -> &mut Self { - self.mode = mode; - self - } - pub(crate) fn custom_flags(&mut self, flags: i32) -> &mut Self { self.custom_flags = flags; self diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/open_unchecked.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/open_unchecked.rs index 19e26b906f9d..5e429f8c3d38 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/open_unchecked.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/open_unchecked.rs @@ -1,8 +1,6 @@ use super::compute_oflags; use crate::filesystem::primitives::{OpenOptions, OpenUncheckedError, stat_unchecked}; -use ambient_authority::AmbientAuthority; -use io_lifetimes::AsFilelike; -use rustix::fs::{CWD, Mode, openat}; +use rustix::fs::{Mode, openat}; use rustix::io; use std::fs; use std::path::Path; @@ -57,15 +55,3 @@ pub(crate) fn open_unchecked( _ => Err(OpenUncheckedError::Other(err.into())), } } - -/// *Unsandboxed* function similar to `open`, but which does not perform -/// sandboxing. -#[inline] -pub(crate) fn open_ambient_impl( - path: &Path, - options: &OpenOptions, - ambient_authority: AmbientAuthority, -) -> Result { - let _ = ambient_authority; - open_unchecked(&CWD.as_filelike_view::(), path, options) -} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/permissions_ext.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/permissions_ext.rs deleted file mode 100644 index dac5ea508bc1..000000000000 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/permissions_ext.rs +++ /dev/null @@ -1,74 +0,0 @@ -use crate::filesystem::primitives::Permissions; -use rustix::fs::RawMode; -use std::fs; - -#[derive(Debug, Clone, Eq, PartialEq)] -pub(crate) struct ImplPermissionsExt { - #[cfg(not(target_os = "wasi"))] - mode: RawMode, -} - -#[cfg(not(target_os = "wasi"))] -impl ImplPermissionsExt { - /// Constructs a new instance of `Self` from the given - /// [`std::fs::Permissions`]. - #[inline] - pub(crate) fn from_std(std: fs::Permissions) -> Self { - use std::os::unix::fs::PermissionsExt; - Self { - mode: std.mode() as RawMode, - } - } - - /// Constructs a new instance of `Permissions` from the given - /// `RawMode`. - #[inline] - pub(crate) const fn from_raw_mode(mode: RawMode) -> Permissions { - Permissions { - readonly: Self::readonly(mode), - ext: Self { mode }, - } - } - - /// Test whether the given `RawMode` lacks write permissions. - #[inline] - pub(crate) const fn readonly(mode: RawMode) -> bool { - mode & 0o222 == 0 - } - - /// Test whether the given `RawMode` lacks write permissions. - #[inline] - pub(crate) fn set_readonly(&mut self, readonly: bool) { - if readonly { - // remove write permission for all classes; equivalent to `chmod a-w ` - self.mode &= !0o222; - } else { - // add write permission for all classes; equivalent to `chmod a+w ` - self.mode |= 0o222; - } - } -} - -#[cfg(not(target_os = "wasi"))] -impl crate::filesystem::primitives::PermissionsExt for ImplPermissionsExt { - fn mode(&self) -> u32 { - self.mode as u32 - } - - fn set_mode(&mut self, mode: u32) { - self.mode = mode as RawMode & 0o7777; - } - - fn from_mode(mode: u32) -> Self { - Self { - mode: mode as RawMode & 0o7777, - } - } -} - -#[cfg(target_os = "wasi")] -impl ImplPermissionsExt { - pub(crate) fn default() -> Permissions { - Permissions { readonly: false } - } -} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/read_dir_inner.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/read_dir_inner.rs index 6ec549d2f4ea..fd8601bfef3f 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/read_dir_inner.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/read_dir_inner.rs @@ -1,7 +1,5 @@ use crate::filesystem::primitives::{ - DirEntryInner, FollowSymlinks, Metadata, OpenOptions, ReadDir, open_dir_for_reading, - open_dir_for_reading_unchecked, open_entry_impl, read_dir_unchecked, remove_dir_unchecked, - remove_file_unchecked, stat_unchecked, + DirEntryInner, FollowSymlinks, Metadata, open_dir_for_reading_unchecked, stat_unchecked, }; use io_extras::os::rustix::{AsRawFd, FromRawFd, RawFd}; use io_lifetimes::AsFd; @@ -13,7 +11,7 @@ use std::mem::ManuallyDrop; use std::os::unix::ffi::OsStrExt; #[cfg(target_os = "wasi")] use std::os::wasi::ffi::OsStrExt; -use std::path::{Component, Path}; +use std::path::Component; use std::sync::{Arc, Mutex}; use std::{fmt, fs, io}; @@ -27,15 +25,6 @@ pub(crate) struct ReadDirInner { } impl ReadDirInner { - pub(crate) fn new(start: &fs::File, path: &Path, follow: FollowSymlinks) -> io::Result { - let fd = open_dir_for_reading(start, path, follow)?; - let dir = Dir::read_from(fd.as_fd())?; - Ok(Self { - raw_fd: fd.as_fd().as_raw_fd(), - rustix: Arc::new(Mutex::new((dir, OwnedFd::from(fd)))), - }) - } - pub(crate) fn read_base_dir(start: &fs::File) -> io::Result { // Open ".", to obtain a new independent file descriptor. Don't use // `dup` since in that case the resulting file descriptor would share @@ -50,47 +39,10 @@ impl ReadDirInner { }) } - pub(crate) fn new_unchecked( - start: &fs::File, - path: &Path, - follow: FollowSymlinks, - ) -> io::Result { - let fd = open_dir_for_reading_unchecked(start, path, follow)?; - let dir = Dir::read_from(fd.as_fd())?; - Ok(Self { - raw_fd: fd.as_fd().as_raw_fd(), - rustix: Arc::new(Mutex::new((dir, fd.into()))), - }) - } - - pub(super) fn open(&self, file_name: &OsStr, options: &OpenOptions) -> io::Result { - open_entry_impl(&self.as_file_view(), file_name, options) - } - pub(super) fn metadata(&self, file_name: &OsStr) -> io::Result { stat_unchecked(&self.as_file_view(), file_name.as_ref(), FollowSymlinks::No) } - pub(super) fn remove_file(&self, file_name: &OsStr) -> io::Result<()> { - remove_file_unchecked(&self.as_file_view(), file_name.as_ref()) - } - - pub(super) fn remove_dir(&self, file_name: &OsStr) -> io::Result<()> { - remove_dir_unchecked(&self.as_file_view(), file_name.as_ref()) - } - - pub(super) fn self_metadata(&self) -> io::Result { - Metadata::from_file(&self.as_file_view()) - } - - pub(super) fn read_dir( - &self, - file_name: &OsStr, - follow: FollowSymlinks, - ) -> io::Result { - read_dir_unchecked(&self.as_file_view(), file_name.as_ref(), follow) - } - #[allow(unsafe_code)] fn as_file_view(&self) -> ManuallyDrop { // Safety: `self.rustix` owns the file descriptor. We just hold a diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/remove_dir_all_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/remove_dir_all_impl.rs deleted file mode 100644 index 38288843f677..000000000000 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/remove_dir_all_impl.rs +++ /dev/null @@ -1,41 +0,0 @@ -use crate::filesystem::primitives::{ - FollowSymlinks, ReadDir, read_dir_nofollow, read_dir_unchecked, remove_dir, remove_file, - remove_open_dir, stat, -}; -use std::path::{Component, Path}; -use std::{fs, io}; - -pub(crate) fn remove_dir_all_impl(start: &fs::File, path: &Path) -> io::Result<()> { - // Code adapted from `remove_dir_all` in Rust's - // library/std/src/sys_common/fs.rs at revision - // 108e90ca78f052c0c1c49c42a22c85620be19712. - let filetype = stat(start, path, FollowSymlinks::No)?.file_type(); - if filetype.is_symlink() { - remove_file(start, path) - } else { - remove_dir_all_recursive(read_dir_nofollow(start, path)?)?; - remove_dir(start, path) - } -} - -pub(crate) fn remove_open_dir_all_impl(dir: fs::File) -> io::Result<()> { - remove_dir_all_recursive(read_dir_unchecked( - &dir, - Component::CurDir.as_ref(), - FollowSymlinks::No, - )?)?; - remove_open_dir(dir) -} - -fn remove_dir_all_recursive(children: ReadDir) -> io::Result<()> { - for child in children { - let child = child?; - if child.file_type()?.is_dir() { - remove_dir_all_recursive(child.inner.read_dir(FollowSymlinks::No)?)?; - child.remove_dir()?; - } else { - child.remove_file()?; - } - } - Ok(()) -} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/remove_open_dir_by_searching.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/remove_open_dir_by_searching.rs deleted file mode 100644 index 006ca70c0d2c..000000000000 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/remove_open_dir_by_searching.rs +++ /dev/null @@ -1,38 +0,0 @@ -use crate::filesystem::primitives::{ - FollowSymlinks, Metadata, errors, is_root_dir, read_dir_unchecked, -}; -use std::path::Component; -use std::{fs, io}; - -/// Delete the directory referenced by the given handle by searching for it in -/// its `..`. This requires search permission in `..`, but that's usually -/// available. -pub(crate) fn remove_open_dir_by_searching(dir: fs::File) -> io::Result<()> { - let metadata = Metadata::from_file(&dir)?; - let mut iter = read_dir_unchecked(&dir, Component::ParentDir.as_ref(), FollowSymlinks::No)?; - while let Some(child) = iter.next() { - let child = child?; - - // Test if the child we found by iteration matches the directory we're - // looking for. Ignore `NotFound` errors, which can happen if another - // process removes a different directory in the same parent. - let same = match child.is_same_file(&metadata) { - Ok(same) => same, - Err(err) if err.kind() == std::io::ErrorKind::NotFound => false, - Err(err) => Err(err)?, - }; - - if same { - return child.remove_dir(); - } - } - - // We didn't find the directory among its parent's children. Check for the - // root directory and handle it specially -- removal will probably fail, so - // we'll get the appropriate error code. - if is_root_dir(&dir, &iter)? { - fs::remove_dir(Component::RootDir.as_os_str()) - } else { - Err(errors::no_such_file_or_directory()) - } -} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/reopen_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/reopen_impl.rs deleted file mode 100644 index e74f0fe9b0f5..000000000000 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/reopen_impl.rs +++ /dev/null @@ -1,18 +0,0 @@ -use crate::filesystem::primitives::rustix::fs::file_path; -use crate::filesystem::primitives::{OpenOptions, open_unchecked}; -use io_lifetimes::AsFilelike; -use rustix::fs::CWD; -use std::{fs, io}; - -/// Implementation of `reopen`. -pub(crate) fn reopen_impl(file: &fs::File, options: &OpenOptions) -> io::Result { - if let Some(path) = file_path(file) { - Ok(open_unchecked( - &CWD.as_filelike_view::(), - &path, - options, - )?) - } else { - Err(io::Error::new(io::ErrorKind::Other, "Couldn't reopen file")) - } -} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/set_symlink_permissions_unchecked.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/set_symlink_permissions_unchecked.rs deleted file mode 100644 index c1854e072c3e..000000000000 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/set_symlink_permissions_unchecked.rs +++ /dev/null @@ -1,17 +0,0 @@ -use crate::filesystem::primitives::Permissions; -#[cfg(unix)] -use crate::filesystem::primitives::PermissionsExt; -use rustix::fs::{AtFlags, Mode, chmodat}; -use std::path::Path; -use std::{fs, io}; - -/// This can just use `AT_SYMLINK_NOFOLLOW`. -pub(crate) fn set_symlink_permissions_unchecked( - start: &fs::File, - path: &Path, - perm: Permissions, -) -> io::Result<()> { - let mode = Mode::from_bits_truncate(perm.mode().try_into().unwrap()); - - Ok(chmodat(start, path, mode, AtFlags::SYMLINK_NOFOLLOW)?) -} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/times.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/times.rs index 84f0caa896be..8c62f0129ddb 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/times.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/times.rs @@ -13,10 +13,6 @@ pub(crate) fn to_timespec(ft: Option) -> io::Result { tv_sec: 0, tv_nsec: UTIME_OMIT.into(), }, - Some(SystemTimeSpec::SymbolicNow) => Timespec { - tv_sec: 0, - tv_nsec: UTIME_NOW.into(), - }, Some(SystemTimeSpec::Absolute(ft)) => { let duration = ft.duration_since(SystemTime::UNIX_EPOCH).unwrap(); let nanoseconds = duration.subsec_nanos(); diff --git a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/canonicalize_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/canonicalize_impl.rs deleted file mode 100644 index 457df1b2d9c1..000000000000 --- a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/canonicalize_impl.rs +++ /dev/null @@ -1,53 +0,0 @@ -//! Path canonicalization using `/proc/self/fd`. - -use super::procfs::get_path_from_proc_self_fd; -use crate::filesystem::primitives::OpenOptionsExt; -use crate::filesystem::primitives::{FollowSymlinks, OpenOptions, manually, open_beneath}; -use rustix::fs::OFlags; -use std::path::{Component, Path, PathBuf}; -use std::{fs, io}; - -/// Implement `canonicalize` by using readlink on `/proc/self/fd/*`. -pub(crate) fn canonicalize_impl(start: &fs::File, path: &Path) -> io::Result { - // Open the path with `O_PATH`. Use `read(true)` even though we don't need - // `read` permissions, because Rust's libstd requires an access mode, and - // Linux ignores `O_RDONLY` with `O_PATH`. - let result = open_beneath( - start, - path, - OpenOptions::new() - .read(true) - .follow(FollowSymlinks::Yes) - .custom_flags(OFlags::PATH.bits() as i32), - ); - - // If that worked, call `readlink`. - match result { - Ok(file) => { - if let Ok(start_path) = get_path_from_proc_self_fd(start) { - if let Ok(file_path) = get_path_from_proc_self_fd(&file) { - if let Ok(canonical_path) = file_path.strip_prefix(start_path) { - let mut path_buf = canonical_path.to_path_buf(); - - // Replace "" with ".", since "" as a relative path is - // interpreted as an error. - if path_buf.as_os_str().is_empty() { - path_buf.push(Component::CurDir); - } - - return Ok(path_buf); - } - } - } - } - Err(err) => match rustix::io::Errno::from_io_error(&err) { - // `ENOSYS` from `open_beneath` means `openat2` is unavailable - // and we should use a fallback. - Some(rustix::io::Errno::NOSYS) => (), - _ => return Err(err), - }, - } - - // Use a fallback. - manually::canonicalize(start, path) -} diff --git a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/file_path.rs b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/file_path.rs deleted file mode 100644 index 430f10a04488..000000000000 --- a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/file_path.rs +++ /dev/null @@ -1,22 +0,0 @@ -use super::procfs::get_path_from_proc_self_fd; -use std::fs; -use std::path::PathBuf; - -pub(crate) fn file_path(file: &fs::File) -> Option { - use std::os::unix::fs::MetadataExt; - - // Ignore paths that don't start with '/', which are things like - // `socket:[3556564]` or similar. - let path = get_path_from_proc_self_fd(file) - .ok() - .filter(|path| path.starts_with("/"))?; - - // Linux appends the string " (deleted)" when a file is deleted; avoid - // treating that as the actual name. Check this after doing the `readlink` - // above so that we're conservative about concurrent deletions. - if file.metadata().ok()?.nlink() == 0 { - return None; - } - - Some(path) -} diff --git a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/mod.rs b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/mod.rs index 454487f72244..300e5a771472 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/mod.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/mod.rs @@ -1,33 +1,17 @@ #[cfg(target_os = "linux")] -mod canonicalize_impl; -#[cfg(target_os = "linux")] mod file_metadata; -mod file_path; -#[cfg(target_os = "linux")] -mod open_entry_impl; mod open_impl; mod procfs; -mod set_permissions_impl; mod set_times_impl; #[cfg(target_os = "linux")] mod stat_impl; -#[cfg(target_os = "android")] -pub(crate) use crate::filesystem::primitives::manually::canonicalize as canonicalize_impl; -#[cfg(target_os = "android")] -pub(crate) use crate::filesystem::primitives::manually::open_entry as open_entry_impl; #[cfg(target_os = "android")] pub(crate) use crate::filesystem::primitives::manually::stat as stat_impl; pub(crate) use crate::filesystem::primitives::via_parent::set_times_nofollow as set_times_nofollow_impl; #[cfg(target_os = "linux")] -pub(crate) use canonicalize_impl::canonicalize_impl; -pub(crate) use file_path::file_path; -#[cfg(target_os = "linux")] -pub(crate) use open_entry_impl::open_entry_impl; -#[cfg(target_os = "linux")] pub(crate) use open_impl::open_beneath; pub(crate) use open_impl::open_impl; -pub(crate) use set_permissions_impl::set_permissions_impl; pub(crate) use set_times_impl::set_times_impl; #[cfg(target_os = "linux")] pub(crate) use stat_impl::stat_impl; diff --git a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/open_entry_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/open_entry_impl.rs deleted file mode 100644 index 7a5831158ef3..000000000000 --- a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/open_entry_impl.rs +++ /dev/null @@ -1,19 +0,0 @@ -use crate::filesystem::primitives::{OpenOptions, manually, open_beneath}; -use std::ffi::OsStr; -use std::{fs, io}; - -pub(crate) fn open_entry_impl( - start: &fs::File, - path: &OsStr, - options: &OpenOptions, -) -> io::Result { - let result = open_beneath(start, path.as_ref(), options); - - match result { - Ok(file) => Ok(file), - Err(err) => match rustix::io::Errno::from_io_error(&err) { - Some(rustix::io::Errno::NOSYS) => manually::open_entry(start, path, options), - _ => Err(err), - }, - } -} diff --git a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/procfs.rs b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/procfs.rs index ca4f0f84ff74..d7f6ec71a27f 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/procfs.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/procfs.rs @@ -8,53 +8,15 @@ use crate::filesystem::primitives::OpenOptionsExt; use crate::filesystem::primitives::{ - OpenOptions, SystemTimeSpec, errors, open, read_link_unchecked, set_times_follow_unchecked, + OpenOptions, SystemTimeSpec, open, set_times_follow_unchecked, }; -use io_lifetimes::{AsFd, AsFilelike}; -use rustix::fs::{AtFlags, Mode, OFlags, RawMode, chmodat}; +use io_lifetimes::AsFd; +use rustix::fs::OFlags; use rustix::path::DecInt; use rustix_linux_procfs::proc_self_fd; -use std::os::unix::fs::PermissionsExt; -use std::path::{Path, PathBuf}; +use std::path::Path; use std::{fs, io}; -pub(crate) fn get_path_from_proc_self_fd(file: &fs::File) -> io::Result { - read_link_unchecked( - &proc_self_fd()?.as_filelike_view::(), - DecInt::from_fd(file).as_ref(), - PathBuf::new(), - ) -} - -/// Linux's `fchmodat` doesn't support `AT_NOFOLLOW_SYMLINK`, so we can't trust -/// that it won't follow a symlink outside the sandbox. As an alternative, the -/// symlinks in Linux's /proc/self/fd/* aren't ordinary symlinks, they're -/// "magic links", which are more transparent, to the point of allowing chmod -/// to work. So we open the file with `O_PATH` and then do `fchmodat` on the -/// corresponding /proc/self/fd/* link. -pub(crate) fn set_permissions_through_proc_self_fd( - start: &fs::File, - path: &Path, - perm: fs::Permissions, -) -> io::Result<()> { - let opath = open( - start, - path, - OpenOptions::new() - .read(true) - .custom_flags(OFlags::PATH.bits() as i32), - )?; - - let dirfd = proc_self_fd()?; - let mode = Mode::from_bits(perm.mode() as RawMode).ok_or_else(errors::invalid_flags)?; - Ok(chmodat( - dirfd, - DecInt::from_fd(&opath), - mode, - AtFlags::empty(), - )?) -} - pub(crate) fn set_times_through_proc_self_fd( start: &fs::File, path: &Path, diff --git a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/set_permissions_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/set_permissions_impl.rs deleted file mode 100644 index a9b3dd5bf402..000000000000 --- a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/set_permissions_impl.rs +++ /dev/null @@ -1,55 +0,0 @@ -use super::procfs::set_permissions_through_proc_self_fd; -use crate::filesystem::primitives::{OpenOptions, Permissions, open}; -use rustix::fs::{Mode, RawMode, fchmod}; -use std::os::unix::fs::PermissionsExt; -use std::path::Path; -use std::{fs, io}; - -pub(crate) fn set_permissions_impl( - start: &fs::File, - path: &Path, - perm: Permissions, -) -> io::Result<()> { - let std_perm = perm.into_std(start)?; - - // First try using `O_PATH` and doing a chmod on `/proc/self/fd/{}` - // (`fchmod` doesn't work on `O_PATH` file descriptors). - // - // This may fail, due to older Linux versions not supporting `O_PATH`, or - // due to procfs being unavailable, but if it does work, go with it, as - // `O_PATH` tells Linux that we don't actually need to read or write the - // file, which may avoid side effects associated with opening device files. - let result = set_permissions_through_proc_self_fd(start, path, std_perm.clone()); - if let Ok(()) = result { - return Ok(()); - } - - // Then try `fchmod` with a normal handle. Normal handles need some kind of - // access, so first try read. - match open(start, path, OpenOptions::new().read(true)) { - Ok(file) => return set_file_permissions(&file, std_perm), - Err(err) => match rustix::io::Errno::from_io_error(&err) { - Some(rustix::io::Errno::ACCESS) => (), - _ => return Err(err), - }, - } - - // Next try write. - match open(start, path, OpenOptions::new().write(true)) { - Ok(file) => return set_file_permissions(&file, std_perm), - Err(err) => match rustix::io::Errno::from_io_error(&err) { - Some(rustix::io::Errno::ACCESS) | Some(rustix::io::Errno::ISDIR) => (), - _ => return Err(err), - }, - } - - // Nothing worked, so just return the original error. - result -} - -fn set_file_permissions(file: &fs::File, perm: fs::Permissions) -> io::Result<()> { - // Use `from_bits_truncate` for compatibility with std, which allows - // non-permission bits to propagate through. - let mode = Mode::from_bits_truncate(perm.mode() as RawMode); - Ok(fchmod(file, mode)?) -} diff --git a/crates/wasi/src/filesystem/primitives/set_permissions.rs b/crates/wasi/src/filesystem/primitives/set_permissions.rs deleted file mode 100644 index 86821288aa63..000000000000 --- a/crates/wasi/src/filesystem/primitives/set_permissions.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! This defines `set_permissions`, the primary entrypoint to sandboxed -//! filesystem permissions modification. - -use crate::filesystem::primitives::{ - Permissions, set_permissions_impl, set_symlink_permissions_impl, -}; -use std::path::Path; -use std::{fs, io}; - -/// Perform a `chmodat`-like operation, ensuring that the resolution of the -/// path never escapes the directory tree rooted at `start`. -#[inline] -pub fn set_permissions(start: &fs::File, path: &Path, perm: Permissions) -> io::Result<()> { - // Call the underlying implementation. - set_permissions_impl(start, path, perm) -} - -/// Perform a `chmodat`-like operation, ensuring that the resolution of the -/// path never escapes the directory tree rooted at `start`, without following -/// symlinks. -#[inline] -pub fn set_symlink_permissions(start: &fs::File, path: &Path, perm: Permissions) -> io::Result<()> { - // Call the underlying implementation. - set_symlink_permissions_impl(start, path, perm) -} diff --git a/crates/wasi/src/filesystem/primitives/symlink.rs b/crates/wasi/src/filesystem/primitives/symlink.rs index 49b1bf7f0f5b..e297dcb399f1 100644 --- a/crates/wasi/src/filesystem/primitives/symlink.rs +++ b/crates/wasi/src/filesystem/primitives/symlink.rs @@ -31,17 +31,6 @@ fn write_symlink_impl(old_path: &Path, new_start: &fs::File, new_path: &Path) -> symlink_impl(old_path, new_start, new_path) } -/// Perform a `symlinkat`-like operation, ensuring that the resolution of the -/// link path never escapes the directory tree rooted at `start`. -#[cfg(not(windows))] -pub fn symlink_contents, Q: AsRef>( - old_path: P, - new_start: &fs::File, - new_path: Q, -) -> io::Result<()> { - write_symlink_impl(old_path.as_ref(), new_start, new_path.as_ref()) -} - /// Perform a `symlink_file`-like operation, ensuring that the resolution of /// the path never escapes the directory tree rooted at `start`. #[cfg(windows)] diff --git a/crates/wasi/src/filesystem/primitives/system_time_spec.rs b/crates/wasi/src/filesystem/primitives/system_time_spec.rs index 64bd445436ac..7f1553ae1ff9 100644 --- a/crates/wasi/src/filesystem/primitives/system_time_spec.rs +++ b/crates/wasi/src/filesystem/primitives/system_time_spec.rs @@ -4,32 +4,16 @@ use std::time::SystemTime; #[derive(Debug)] #[allow(clippy::module_name_repetitions)] pub enum SystemTimeSpec { - /// A value which always represents the current time, in symbolic form, so - /// that even as time elapses, it continues to represent the current time. - SymbolicNow, - /// An absolute time value. Absolute(SystemTime), } impl SystemTimeSpec { - /// Constructs a new instance of `Self` from the given - /// [`fs_set_times::SystemTimeSpec`]. - // TODO: Make this a `const fn` once `SystemTime::from_std` is a `const fn`. - #[inline] - pub fn from_std(std: fs_set_times::SystemTimeSpec) -> Self { - match std { - fs_set_times::SystemTimeSpec::SymbolicNow => Self::SymbolicNow, - fs_set_times::SystemTimeSpec::Absolute(time) => Self::Absolute(time), - } - } - /// Constructs a new instance of [`fs_set_times::SystemTimeSpec`] from the /// given `Self`. #[inline] pub const fn into_std(self) -> fs_set_times::SystemTimeSpec { match self { - Self::SymbolicNow => fs_set_times::SystemTimeSpec::SymbolicNow, Self::Absolute(time) => fs_set_times::SystemTimeSpec::Absolute(time), } } diff --git a/crates/wasi/src/filesystem/primitives/via_parent/access.rs b/crates/wasi/src/filesystem/primitives/via_parent/access.rs deleted file mode 100644 index 93a3f7a778d2..000000000000 --- a/crates/wasi/src/filesystem/primitives/via_parent/access.rs +++ /dev/null @@ -1,19 +0,0 @@ -use super::open_parent; -use crate::filesystem::primitives::{AccessType, FollowSymlinks, MaybeOwnedFile, access_unchecked}; -use std::path::Path; -use std::{fs, io}; - -/// Implement `access` by `open`ing up the parent component of the path and -/// then calling `access_unchecked` on the last component. -pub(crate) fn access( - start: &fs::File, - path: &Path, - type_: AccessType, - follow: FollowSymlinks, -) -> io::Result<()> { - let start = MaybeOwnedFile::borrowed(start); - - let (dir, basename) = open_parent(start, path)?; - - access_unchecked(&dir, basename.as_ref(), type_, follow) -} diff --git a/crates/wasi/src/filesystem/primitives/via_parent/mod.rs b/crates/wasi/src/filesystem/primitives/via_parent/mod.rs index 84884cdc7db1..5c6241e7f2c3 100644 --- a/crates/wasi/src/filesystem/primitives/via_parent/mod.rs +++ b/crates/wasi/src/filesystem/primitives/via_parent/mod.rs @@ -2,7 +2,6 @@ //! in `create_dir`, the last component names the path to be created, while the //! rest of the components just name the place to create it in. -mod access; mod create_dir; mod hard_link; mod open_parent; @@ -11,17 +10,12 @@ mod read_link; mod remove_dir; mod remove_file; mod rename; -#[cfg(windows)] -mod set_permissions; -#[cfg(not(target_os = "wasi"))] -mod set_symlink_permissions; #[cfg(not(windows))] mod set_times_nofollow; mod symlink; use open_parent::open_parent; -pub(crate) use access::access; pub(crate) use create_dir::create_dir; pub(crate) use hard_link::hard_link; #[cfg(not(windows))] // doesn't work on windows; use a windows-specific impl @@ -29,10 +23,6 @@ pub(crate) use read_link::read_link; pub(crate) use remove_dir::remove_dir; pub(crate) use remove_file::remove_file; pub(crate) use rename::rename; -#[cfg(windows)] -pub(crate) use set_permissions::set_permissions; -#[cfg(not(target_os = "wasi"))] -pub(crate) use set_symlink_permissions::set_symlink_permissions; #[cfg(not(windows))] pub(crate) use set_times_nofollow::set_times_nofollow; #[cfg(not(windows))] diff --git a/crates/wasi/src/filesystem/primitives/via_parent/set_permissions.rs b/crates/wasi/src/filesystem/primitives/via_parent/set_permissions.rs deleted file mode 100644 index fc6d4ac04e01..000000000000 --- a/crates/wasi/src/filesystem/primitives/via_parent/set_permissions.rs +++ /dev/null @@ -1,13 +0,0 @@ -use super::open_parent; -use crate::filesystem::primitives::{MaybeOwnedFile, Permissions, set_permissions_unchecked}; -use std::path::Path; -use std::{fs, io}; - -#[inline] -pub(crate) fn set_permissions(start: &fs::File, path: &Path, perm: Permissions) -> io::Result<()> { - let start = MaybeOwnedFile::borrowed(start); - - let (dir, basename) = open_parent(start, &path)?; - - set_permissions_unchecked(&dir, basename.as_ref(), perm) -} diff --git a/crates/wasi/src/filesystem/primitives/via_parent/set_symlink_permissions.rs b/crates/wasi/src/filesystem/primitives/via_parent/set_symlink_permissions.rs deleted file mode 100644 index bd90141a0e77..000000000000 --- a/crates/wasi/src/filesystem/primitives/via_parent/set_symlink_permissions.rs +++ /dev/null @@ -1,19 +0,0 @@ -use super::open_parent; -use crate::filesystem::primitives::{ - MaybeOwnedFile, Permissions, set_symlink_permissions_unchecked, -}; -use std::path::Path; -use std::{fs, io}; - -#[inline] -pub(crate) fn set_symlink_permissions( - start: &fs::File, - path: &Path, - perm: Permissions, -) -> io::Result<()> { - let start = MaybeOwnedFile::borrowed(start); - - let (dir, basename) = open_parent(start, path)?; - - set_symlink_permissions_unchecked(&dir, basename.as_ref(), perm) -} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/access_unchecked.rs b/crates/wasi/src/filesystem/primitives/windows/fs/access_unchecked.rs deleted file mode 100644 index 8be2cbb2e515..000000000000 --- a/crates/wasi/src/filesystem/primitives/windows/fs/access_unchecked.rs +++ /dev/null @@ -1,32 +0,0 @@ -use crate::filesystem::primitives::{AccessType, FollowSymlinks, OpenOptions, open}; -use std::path::Path; -use std::{fs, io}; - -/// *Unsandboxed* function similar to `access`, but which does not perform -/// sandboxing. -pub(crate) fn access_unchecked( - start: &fs::File, - path: &Path, - type_: AccessType, - follow: FollowSymlinks, -) -> io::Result<()> { - let mut options = OpenOptions::new(); - options.follow(follow); - match type_ { - AccessType::Exists => { - options.read(true); - } - AccessType::Access(modes) => { - if modes.readable { - options.read(true); - } - if modes.writable { - options.write(true); - } - if modes.executable { - options.read(true); - } - } - } - open(start, path, &options).map(|_| ()) -} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/copy.rs b/crates/wasi/src/filesystem/primitives/windows/fs/copy.rs deleted file mode 100644 index 9080963a2256..000000000000 --- a/crates/wasi/src/filesystem/primitives/windows/fs/copy.rs +++ /dev/null @@ -1,19 +0,0 @@ -use super::get_path::get_path; -use crate::filesystem::primitives::{OpenOptions, open}; -use std::path::Path; -use std::{fs, io}; - -pub(crate) fn copy_impl( - from_start: &fs::File, - from_path: &Path, - to_start: &fs::File, - to_path: &Path, -) -> io::Result { - let from = open(from_start, from_path, OpenOptions::new().read(true))?; - let to = open( - to_start, - to_path, - OpenOptions::new().create(true).truncate(true).write(true), - )?; - fs::copy(get_path(&from)?, get_path(&to)?) -} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/dir_entry_inner.rs b/crates/wasi/src/filesystem/primitives/windows/fs/dir_entry_inner.rs index 3dabd69d5cb0..c70104683ffe 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/dir_entry_inner.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/dir_entry_inner.rs @@ -1,91 +1,17 @@ -use super::open_options_to_std; -use crate::filesystem::primitives::OpenOptionsExt; -use crate::filesystem::primitives::{ - FileType, FollowSymlinks, ImplFileTypeExt, Metadata, OpenOptions, ReadDir, ReadDirInner, open, - open_ambient_dir, -}; -use ambient_authority::ambient_authority; +use crate::filesystem::primitives::Metadata; use std::ffi::OsString; use std::{fmt, fs, io}; -use windows_sys::Win32::Storage::FileSystem::{ - FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, -}; pub(crate) struct DirEntryInner { std: fs::DirEntry, } impl DirEntryInner { - #[inline] - pub(crate) fn open(&self, options: &OpenOptions) -> io::Result { - match options.follow { - FollowSymlinks::No => { - let (opts, manually_trunc) = open_options_to_std(options); - let file = opts.open(self.std.path())?; - if manually_trunc { - // Unwrap is ok because 0 never overflows, and we'll only - // have `manually_trunc` set when the file is opened for - // writing. - file.set_len(0).unwrap(); - } - Ok(file) - } - FollowSymlinks::Yes => { - let path = self.std.path(); - open( - &open_ambient_dir(path.parent().unwrap(), ambient_authority())?, - path.file_name().unwrap().as_ref(), - options, - ) - } - } - } - #[inline] pub(crate) fn metadata(&self) -> io::Result { self.std.metadata().map(Metadata::from_just_metadata) } - #[inline] - pub(crate) fn full_metadata(&self) -> io::Result { - // If we can open the file, we can get a more complete Metadata which - // includes `file_index`, `volume_serial_number`, and - // `number_of_links`. - let mut opts = OpenOptions::new(); - opts.access_mode(0); - opts.custom_flags(FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS); - opts.follow(FollowSymlinks::No); - let opened = self.open(&opts)?; - Metadata::from_file(&opened) - } - - #[inline] - pub(crate) fn remove_file(&self) -> io::Result<()> { - fs::remove_file(self.std.path()) - } - - #[inline] - pub(crate) fn remove_dir(&self) -> io::Result<()> { - fs::remove_dir(self.std.path()) - } - - #[inline] - pub(crate) fn read_dir(&self, follow: FollowSymlinks) -> io::Result { - assert_eq!( - follow, - FollowSymlinks::Yes, - "`read_dir` without following symlinks is not implemented yet" - ); - let std = fs::read_dir(self.std.path())?; - let inner = ReadDirInner::from_std(std); - Ok(ReadDir { inner }) - } - - #[inline] - pub(crate) fn file_type(&self) -> io::Result { - self.std.file_type().map(ImplFileTypeExt::from_std) - } - #[inline] pub(crate) fn file_name(&self) -> OsString { self.std.file_name() @@ -103,13 +29,3 @@ impl fmt::Debug for DirEntryInner { f.debug_tuple("DirEntry").field(&self.file_name()).finish() } } - -#[doc(hidden)] -impl crate::filesystem::primitives::_WindowsDirEntryExt - for crate::filesystem::primitives::DirEntry -{ - #[inline] - fn full_metadata(&self) -> io::Result { - DirEntryInner::full_metadata(&self.inner) - } -} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/dir_utils.rs b/crates/wasi/src/filesystem/primitives/windows/fs/dir_utils.rs index 4acd6b443612..8f1de11d6301 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/dir_utils.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/dir_utils.rs @@ -77,14 +77,6 @@ pub(crate) fn readdir_options() -> OpenOptions { dir_options().readdir_required(true).clone() } -/// Return an `OpenOptions` for canonicalizing paths. -pub(crate) fn canonicalize_options() -> OpenOptions { - OpenOptions::new() - .read(true) - .custom_flags(FILE_FLAG_BACKUP_SEMANTICS) - .clone() -} - /// Open a directory named by a bare path, using the host process' ambient /// authority. /// diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/is_file_read_write_impl.rs b/crates/wasi/src/filesystem/primitives/windows/fs/is_file_read_write_impl.rs deleted file mode 100644 index 87674e589abd..000000000000 --- a/crates/wasi/src/filesystem/primitives/windows/fs/is_file_read_write_impl.rs +++ /dev/null @@ -1,11 +0,0 @@ -use io_lifetimes::AsHandle; -use std::{fs, io}; - -pub(crate) fn is_file_read_write_impl(file: &fs::File) -> io::Result<(bool, bool)> { - let handle = file.as_handle(); - let access_mode = winx::file::query_access_information(handle)?; - let read = access_mode.contains(winx::file::AccessMode::FILE_READ_DATA); - let write = access_mode.contains(winx::file::AccessMode::FILE_WRITE_DATA) - || access_mode.contains(winx::file::AccessMode::FILE_APPEND_DATA); - Ok((read, write)) -} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/is_same_file.rs b/crates/wasi/src/filesystem/primitives/windows/fs/is_same_file.rs deleted file mode 100644 index 8c50bbbab4e2..000000000000 --- a/crates/wasi/src/filesystem/primitives/windows/fs/is_same_file.rs +++ /dev/null @@ -1,9 +0,0 @@ -use crate::filesystem::primitives::ImplMetadataExt; -use std::{fs, io}; - -/// Determine if `a` and `b` refer to the same inode on the same device. -pub(crate) fn is_same_file(a: &fs::File, b: &fs::File) -> io::Result { - let a_metadata = ImplMetadataExt::from(a, &a.metadata()?)?; - let b_metadata = ImplMetadataExt::from(b, &b.metadata()?)?; - Ok(a_metadata.is_same_file(&b_metadata)) -} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/metadata_ext.rs b/crates/wasi/src/filesystem/primitives/windows/fs/metadata_ext.rs index cc8ded4d100e..4aaf4cb0f521 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/metadata_ext.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/metadata_ext.rs @@ -6,13 +6,7 @@ use std::{fs, io}; #[derive(Debug, Clone)] pub(crate) struct ImplMetadataExt { file_attributes: u32, - creation_time: u64, - last_access_time: u64, - last_write_time: u64, - file_size: u64, - volume_serial_number: Option, number_of_links: Option, - file_index: Option, } impl ImplMetadataExt { @@ -20,31 +14,11 @@ impl ImplMetadataExt { /// and [`std::fs::Metadata`]. #[inline] pub(crate) fn from(file: &fs::File, std: &fs::Metadata) -> io::Result { - let (mut volume_serial_number, mut number_of_links, mut file_index) = (None, None, None); + let fileinfo = winx::winapi_util::file::information(file)?; + let t64: u64 = fileinfo.number_of_links(); + let t32: u32 = t64.try_into().unwrap(); - if volume_serial_number.is_none() || number_of_links.is_none() || file_index.is_none() { - let fileinfo = winx::winapi_util::file::information(file)?; - if volume_serial_number.is_none() { - let t64: u64 = fileinfo.volume_serial_number(); - let t32: u32 = t64.try_into().unwrap(); - volume_serial_number = Some(t32); - } - if number_of_links.is_none() { - let t64: u64 = fileinfo.number_of_links(); - let t32: u32 = t64.try_into().unwrap(); - number_of_links = Some(t32); - } - if file_index.is_none() { - file_index = Some(fileinfo.file_index()); - } - } - - Ok(Self::from_parts( - std, - volume_serial_number, - number_of_links, - file_index, - )) + Ok(Self::from_parts(std, Some(t32))) } /// Constructs a new instance of `Self` from the given @@ -56,51 +30,18 @@ impl ImplMetadataExt { /// [`std::fs::Metadata::volume_serial_number`]: https://doc.rust-lang.org/std/os/windows/fs/trait.MetadataExt.html#tymethod.volume_serial_number #[inline] pub(crate) fn from_just_metadata(std: &fs::Metadata) -> Self { - let (volume_serial_number, number_of_links, file_index) = (None, None, None); - - Self::from_parts(std, volume_serial_number, number_of_links, file_index) + Self::from_parts(std, None) } #[inline] - fn from_parts( - std: &fs::Metadata, - volume_serial_number: Option, - number_of_links: Option, - file_index: Option, - ) -> Self { + fn from_parts(std: &fs::Metadata, number_of_links: Option) -> Self { use std::os::windows::fs::MetadataExt; Self { file_attributes: std.file_attributes(), - creation_time: std.creation_time(), - last_access_time: std.last_access_time(), - last_write_time: std.last_write_time(), - file_size: std.file_size(), - volume_serial_number, number_of_links, - file_index, } } - /// Determine if `self` and `other` refer to the same inode on the same - /// device. - pub(crate) fn is_same_file(&self, other: &Self) -> bool { - // From [MSDN]: - // The identifier (low and high parts) and the volume serial number - // uniquely identify a file on a single computer. To determine whether - // two open handles represent the same file, combine the identifier - // and the volume serial number for each file and compare them. - // [MSDN]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/ns-fileapi-by_handle_file_information - let self_vsn = self - .volume_serial_number - .expect("could extract volume serial number of `self`"); - let other_vsn = other - .volume_serial_number - .expect("could extract volume serial number of `other`"); - let self_file_index = self.file_index.expect("could extract file index `self`"); - let other_file_index = other.file_index.expect("could extract file index `other`"); - self_vsn == other_vsn && self_file_index == other_file_index - } - /// `MetadataExt` requires nightly to be implemented, but we sometimes /// just need the file attributes. #[inline] @@ -113,47 +54,12 @@ impl MetadataExt for ImplMetadataExt { fn file_attributes(&self) -> u32 { self.file_attributes } - - #[inline] - fn creation_time(&self) -> u64 { - self.creation_time - } - - #[inline] - fn last_access_time(&self) -> u64 { - self.last_access_time - } - - #[inline] - fn last_write_time(&self) -> u64 { - self.last_write_time - } - - #[inline] - fn file_size(&self) -> u64 { - self.file_size - } } #[doc(hidden)] impl crate::filesystem::primitives::_WindowsByHandle for crate::filesystem::primitives::Metadata { - #[inline] - fn file_attributes(&self) -> u32 { - self.ext.file_attributes - } - - #[inline] - fn volume_serial_number(&self) -> Option { - self.ext.volume_serial_number - } - #[inline] fn number_of_links(&self) -> Option { self.ext.number_of_links } - - #[inline] - fn file_index(&self) -> Option { - self.ext.file_index - } } diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/mod.rs b/crates/wasi/src/filesystem/primitives/windows/fs/mod.rs index ed36211e3441..75ff00fbe8ba 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/mod.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/mod.rs @@ -1,5 +1,3 @@ -mod access_unchecked; -mod copy; mod create_dir_unchecked; mod create_file_at_w; mod dir_entry_inner; @@ -8,8 +6,6 @@ mod dir_utils; mod file_type_ext; mod get_path; mod hard_link_unchecked; -mod is_file_read_write_impl; -mod is_same_file; mod metadata_ext; mod oflags; mod open_impl; @@ -18,14 +14,9 @@ mod open_unchecked; mod read_dir_inner; mod read_link_impl; mod read_link_unchecked; -mod remove_dir_all_impl; mod remove_dir_unchecked; mod remove_file_unchecked; -mod remove_open_dir_impl; mod rename_unchecked; -mod reopen_impl; -mod set_permissions_unchecked; -mod set_symlink_permissions_unchecked; mod set_times_impl; mod stat_unchecked; mod symlink_unchecked; @@ -34,30 +25,22 @@ pub(crate) mod errors; #[rustfmt::skip] pub(crate) use crate::filesystem::primitives::{ - manually::canonicalize as canonicalize_impl, - via_parent::access as access_impl, via_parent::hard_link as hard_link_impl, via_parent::create_dir as create_dir_impl, via_parent::rename as rename_impl, via_parent::remove_dir as remove_dir_impl, - via_parent::set_permissions as set_permissions_impl, - via_parent::set_symlink_permissions as set_symlink_permissions_impl, manually::stat as stat_impl, via_parent::symlink_dir as symlink_dir_impl, via_parent::symlink_file as symlink_file_impl, via_parent::remove_file as remove_file_impl, }; -pub(crate) use access_unchecked::*; -pub(crate) use copy::*; pub(crate) use create_dir_unchecked::*; pub(crate) use dir_entry_inner::*; pub(crate) use dir_options_ext::*; pub(crate) use dir_utils::*; pub(crate) use file_type_ext::*; pub(crate) use hard_link_unchecked::*; -pub(crate) use is_file_read_write_impl::*; -pub(crate) use is_same_file::*; pub(crate) use metadata_ext::*; pub(crate) use open_impl::open_impl; pub(crate) use open_options_ext::*; @@ -65,14 +48,9 @@ pub(crate) use open_unchecked::*; pub(crate) use read_dir_inner::*; pub(crate) use read_link_impl::*; pub(crate) use read_link_unchecked::*; -pub(crate) use remove_dir_all_impl::*; pub(crate) use remove_dir_unchecked::*; pub(crate) use remove_file_unchecked::*; -pub(crate) use remove_open_dir_impl::*; pub(crate) use rename_unchecked::*; -pub(crate) use reopen_impl::reopen_impl; -pub(crate) use set_permissions_unchecked::*; -pub(crate) use set_symlink_permissions_unchecked::*; pub(crate) use set_times_impl::*; pub(crate) use stat_unchecked::*; pub(crate) use symlink_unchecked::*; diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/oflags.rs b/crates/wasi/src/filesystem/primitives/windows/fs/oflags.rs index e01a7f183113..30d80197456c 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/oflags.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/oflags.rs @@ -1,5 +1,4 @@ use crate::filesystem::primitives::{FollowSymlinks, OpenOptions, OpenOptionsExt}; -use std::fs; use windows_sys::Win32::Storage::FileSystem::{ FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, FILE_FLAG_WRITE_THROUGH, FILE_SHARE_DELETE, @@ -47,38 +46,3 @@ pub(in super::super) fn prepare_open_options_for_open(opts: &mut OpenOptions) -> manually_trunc } - -/// Translate the given `cap_std` into `std` options. Also return a bool -/// indicating that the `trunc` flag was requested but could not be set, -/// so the file should be truncated manually after opening. -pub(in super::super) fn open_options_to_std(opts: &OpenOptions) -> (fs::OpenOptions, bool) { - use std::os::windows::fs::OpenOptionsExt; - - let mut opts = opts.clone(); - let manually_trunc = prepare_open_options_for_open(&mut opts); - - let mut std_opts = fs::OpenOptions::new(); - std_opts - .read(opts.read) - .write(opts.write) - .append(opts.append) - .truncate(opts.truncate) - .create(opts.create) - .create_new(opts.create_new) - .share_mode(opts.ext.share_mode) - .custom_flags(opts.ext.custom_flags) - .attributes(opts.ext.attributes); - - // Calling `sequence_qos_flags` with a value of 0 has the side effect - // of setting `SECURITY_SQOS_PRESENT`, so don't call it if we don't - // have any flags. - if opts.ext.security_qos_flags != 0 { - std_opts.security_qos_flags(opts.ext.security_qos_flags); - } - - if let Some(access_mode) = opts.ext.access_mode { - std_opts.access_mode(access_mode); - } - - (std_opts, manually_trunc) -} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/open_options_ext.rs b/crates/wasi/src/filesystem/primitives/windows/fs/open_options_ext.rs index 1a5de4d62196..af8361d463ea 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/open_options_ext.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/open_options_ext.rs @@ -8,7 +8,7 @@ use windows_sys::Win32::Security::SECURITY_ATTRIBUTES; use windows_sys::Win32::Storage::FileSystem::{ CREATE_ALWAYS, CREATE_NEW, FILE_FLAG_OPEN_REPARSE_POINT, FILE_GENERIC_WRITE, FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_WRITE_DATA, OPEN_ALWAYS, OPEN_EXISTING, - SECURITY_SQOS_PRESENT, TRUNCATE_EXISTING, + TRUNCATE_EXISTING, }; #[derive(Debug, Clone)] @@ -50,16 +50,6 @@ impl ImplOpenOptionsExt { self.custom_flags = flags; self } - - pub(crate) fn attributes(&mut self, attributes: u32) -> &mut Self { - self.attributes = attributes; - self - } - - pub(crate) fn security_qos_flags(&mut self, flags: u32) -> &mut Self { - self.security_qos_flags = flags | SECURITY_SQOS_PRESENT; - self - } } pub(crate) fn get_access_mode(options: &OpenOptions) -> io::Result { diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/open_unchecked.rs b/crates/wasi/src/filesystem/primitives/windows/fs/open_unchecked.rs index 1e04ce7dda39..0678b1d9af41 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/open_unchecked.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/open_unchecked.rs @@ -3,12 +3,12 @@ #![allow(unsafe_code)] use super::create_file_at_w::CreateFileAtW; -use super::{open_options_to_std, prepare_open_options_for_open}; +use super::prepare_open_options_for_open; use crate::filesystem::primitives::{ FollowSymlinks, OpenOptions, OpenUncheckedError, SymlinkKind, errors, file_path, get_access_mode, get_creation_mode, get_flags_and_attributes, }; -use ambient_authority::{AmbientAuthority, ambient_authority}; +use ambient_authority::ambient_authority; use std::ffi::OsStr; use std::os::windows::ffi::OsStrExt; use std::os::windows::fs::MetadataExt; @@ -139,18 +139,6 @@ fn open_at(start: &fs::File, path: &Path, opts: &OpenOptions) -> io::Result Result { - let _ = ambient_authority; - let (std_opts, manually_trunc) = open_options_to_std(options); - handle_open_result(std_opts.open(path), options, manually_trunc) -} - fn handle_open_result( result: io::Result, options: &OpenOptions, diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/read_dir_inner.rs b/crates/wasi/src/filesystem/primitives/windows/fs/read_dir_inner.rs index decfe31e8079..5d8941804cf6 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/read_dir_inner.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/read_dir_inner.rs @@ -1,5 +1,5 @@ use super::get_path::concatenate; -use crate::filesystem::primitives::{DirEntryInner, FollowSymlinks, open_dir}; +use crate::filesystem::primitives::DirEntryInner; use std::path::{Component, Path}; use std::{fmt, fs, io}; @@ -8,16 +8,6 @@ pub(crate) struct ReadDirInner { } impl ReadDirInner { - pub(crate) fn new(start: &fs::File, path: &Path, follow: FollowSymlinks) -> io::Result { - assert_eq!( - follow, - FollowSymlinks::Yes, - "`read_dir` without following symlinks is not implemented yet" - ); - let dir = open_dir(start, path)?; - Self::new_unchecked(&dir, Component::CurDir.as_ref()) - } - pub(crate) fn read_base_dir(start: &fs::File) -> io::Result { Self::new_unchecked(&start, Component::CurDir.as_ref()) } @@ -28,10 +18,6 @@ impl ReadDirInner { std: fs::read_dir(full_path)?, }) } - - pub(super) fn from_std(std: fs::ReadDir) -> Self { - Self { std } - } } impl Iterator for ReadDirInner { diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/remove_dir_all_impl.rs b/crates/wasi/src/filesystem/primitives/windows/fs/remove_dir_all_impl.rs deleted file mode 100644 index d09e91915697..000000000000 --- a/crates/wasi/src/filesystem/primitives/windows/fs/remove_dir_all_impl.rs +++ /dev/null @@ -1,30 +0,0 @@ -use super::get_path::get_path; -use crate::filesystem::primitives::{ - FollowSymlinks, open_dir, open_dir_nofollow, remove_dir, stat, -}; -use std::path::Path; -use std::{fs, io}; - -pub(crate) fn remove_dir_all_impl(start: &fs::File, path: &Path) -> io::Result<()> { - // Open the directory, following symlinks, to make sure it is a directory. - let file = open_dir(start, path)?; - // Test whether the path is a symlink. - let md = stat(start, path, FollowSymlinks::No)?; - drop(file); - if md.is_symlink() { - // If so, just remove the link. - remove_dir(start, path) - } else { - // Otherwise, remove the tree. - let dir = open_dir_nofollow(start, path)?; - remove_open_dir_all_impl(dir) - } -} - -pub(crate) fn remove_open_dir_all_impl(dir: fs::File) -> io::Result<()> { - // Close the directory so that we can delete it. This is racy; see the - // comments in `remove_open_dir_impl` for details. - let path = get_path(&dir)?; - drop(dir); - fs::remove_dir_all(&path) -} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/remove_open_dir_impl.rs b/crates/wasi/src/filesystem/primitives/windows/fs/remove_open_dir_impl.rs deleted file mode 100644 index 184402197a63..000000000000 --- a/crates/wasi/src/filesystem/primitives/windows/fs/remove_open_dir_impl.rs +++ /dev/null @@ -1,24 +0,0 @@ -use super::get_path::get_path; -use std::{fs, io}; - -pub(crate) fn remove_open_dir_impl(dir: fs::File) -> io::Result<()> { - let path = get_path(&dir)?; - - // Drop the directory before removing it, since we open directories without - // `FILE_SHARE_DELETE`, and removing it requires accessing via its name - // rather than its handle. - // - // There is a window here in which another process could remove or rename - // a directory with this path after the handle is dropped, however it's - // unlikely to happen by accident, and unlikely to cause major problems. - // It may cause spurious failures, or failures with different error codes, - // but this appears to be unavoidable. - // - // Even if we did have `FILE_SHARE_DELETE` and we kept the handle open - // while doing the `remove_dir, `FILE_SHARE_DELETE` would grant other - // processes the right to remove or rename the directory. So there - // doesn't seem to be a race-free way of removing opened directories. - drop(dir); - - fs::remove_dir(path) -} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/reopen_impl.rs b/crates/wasi/src/filesystem/primitives/windows/fs/reopen_impl.rs deleted file mode 100644 index 25537574393d..000000000000 --- a/crates/wasi/src/filesystem/primitives/windows/fs/reopen_impl.rs +++ /dev/null @@ -1,85 +0,0 @@ -use crate::filesystem::primitives::{OpenOptions, get_access_mode, get_flags_and_attributes}; -use io_lifetimes::AsHandle; -use std::{fs, io}; -use windows_sys::Win32::Foundation::{GENERIC_READ, GENERIC_WRITE}; -use windows_sys::Win32::Storage::FileSystem::{ - FILE_FLAG_DELETE_ON_CLOSE, FILE_FLAG_WRITE_THROUGH, FILE_GENERIC_READ, FILE_GENERIC_WRITE, - SECURITY_CONTEXT_TRACKING, SECURITY_DELEGATION, SECURITY_EFFECTIVE_ONLY, - SECURITY_IDENTIFICATION, SECURITY_IMPERSONATION, -}; -use winx::file::{AccessMode, Flags, ShareMode}; - -/// Implementation of `reopen`. -pub(crate) fn reopen_impl(file: &fs::File, options: &OpenOptions) -> io::Result { - let old_access_mode = winx::file::query_access_information(file.as_handle())?; - let new_access_mode = get_access_mode(options)?; - let flags = get_flags_and_attributes(options); - - let new_access_mode = AccessMode::from_bits(new_access_mode).unwrap(); - let flags = Flags::from_bits(flags).unwrap(); - - // Disallow custom access modes might imply or require more access than we - // have. - if new_access_mode - .intersects(AccessMode::from_bits(GENERIC_WRITE | FILE_GENERIC_WRITE).unwrap()) - && !old_access_mode - .intersects(AccessMode::from_bits(GENERIC_WRITE | FILE_GENERIC_WRITE).unwrap()) - { - return Err(io::Error::new( - io::ErrorKind::PermissionDenied, - "Can't reopen file", - )); - } - if new_access_mode.intersects(AccessMode::from_bits(GENERIC_READ | FILE_GENERIC_READ).unwrap()) - && !old_access_mode - .intersects(AccessMode::from_bits(GENERIC_READ | FILE_GENERIC_READ).unwrap()) - { - return Err(io::Error::new( - io::ErrorKind::PermissionDenied, - "Can't reopen file", - )); - } - - // Disallow custom flags which might imply or require more access than we have. - if flags - .intersects(Flags::from_bits(FILE_FLAG_DELETE_ON_CLOSE | FILE_FLAG_WRITE_THROUGH).unwrap()) - && !old_access_mode - .intersects(AccessMode::from_bits(GENERIC_WRITE | FILE_GENERIC_WRITE).unwrap()) - { - return Err(io::Error::new( - io::ErrorKind::PermissionDenied, - "Can't reopen file", - )); - } - - // For now, disallow all non-anonymous security modes. - /* - if flags.intersects( - Flags::from_bits( - SECURITY_CONTEXT_TRACKING - | SECURITY_DELEGATION - | SECURITY_EFFECTIVE_ONLY - | SECURITY_IDENTIFICATION - | SECURITY_IMPERSONATION, - ) - .unwrap(), - ) { - return Err(io::Error::new(io::ErrorKind::Other, "Can't reopen file")); - } - */ - // And for now, do the bit tests manually. - if (flags.bits() - & (SECURITY_CONTEXT_TRACKING - | SECURITY_DELEGATION - | SECURITY_EFFECTIVE_ONLY - | SECURITY_IDENTIFICATION - | SECURITY_IMPERSONATION)) - != 0 - { - return Err(io::Error::new(io::ErrorKind::Other, "Can't reopen file")); - } - - let new_share_mode = - ShareMode::FILE_SHARE_READ | ShareMode::FILE_SHARE_WRITE | ShareMode::FILE_SHARE_DELETE; - winx::file::reopen_file(file.as_handle(), new_access_mode, new_share_mode, flags) -} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/set_permissions_unchecked.rs b/crates/wasi/src/filesystem/primitives/windows/fs/set_permissions_unchecked.rs deleted file mode 100644 index 4ade872b9206..000000000000 --- a/crates/wasi/src/filesystem/primitives/windows/fs/set_permissions_unchecked.rs +++ /dev/null @@ -1,21 +0,0 @@ -use super::get_path::concatenate; -use crate::filesystem::primitives::Permissions; -use std::path::Path; -use std::{fs, io}; - -/// *Unsandboxed* function similar to `set_permissions`, but which does not -/// perform sandboxing. -pub(crate) fn set_permissions_unchecked( - start: &fs::File, - path: &Path, - perm: Permissions, -) -> io::Result<()> { - // According to [Rust's documentation], `fs::set_permissions` uses - // `SetFileAttributes`, and according to [Windows' documentation] - // `SetFileAttributes` does not follow symbolic links. - // - // [Windows' documentation]: https://docs.microsoft.com/en-us/windows/win32/fileio/symbolic-link-effects-on-file-systems-functions#setfileattributes - // [Rust's documentation]: https://doc.rust-lang.org/std/fs/fn.set_permissions.html#platform-specific-behavior - let out_path = concatenate(start, path)?; - fs::set_permissions(out_path, perm.into_std(start)?) -} diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/set_symlink_permissions_unchecked.rs b/crates/wasi/src/filesystem/primitives/windows/fs/set_symlink_permissions_unchecked.rs deleted file mode 100644 index 8351c2e95ec8..000000000000 --- a/crates/wasi/src/filesystem/primitives/windows/fs/set_symlink_permissions_unchecked.rs +++ /dev/null @@ -1,20 +0,0 @@ -use super::get_path::concatenate; -use crate::filesystem::primitives::Permissions; -use std::path::Path; -use std::{fs, io}; - -/// This can just use `AT_SYMLINK_NOFOLLOW`. -pub(crate) fn set_symlink_permissions_unchecked( - start: &fs::File, - path: &Path, - perm: Permissions, -) -> io::Result<()> { - // According to [Rust's documentation], `fs::set_permissions` uses - // `SetFileAttributes`, and according to [Windows' documentation] - // `SetFileAttributes` does not follow symbolic links. - // - // [Windows' documentation]: https://docs.microsoft.com/en-us/windows/win32/fileio/symbolic-link-effects-on-file-systems-functions#setfileattributes - // [Rust's documentation]: https://doc.rust-lang.org/std/fs/fn.set_permissions.html#platform-specific-behavior - let out_path = concatenate(start, path)?; - fs::set_permissions(out_path, perm.into_std(start)?) -} From 237b0a2e4f41c791e78b23ee661dc9e10ded0915 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 27 Aug 2026 12:44:17 -0700 Subject: [PATCH 12/34] Import tests from cap-std --- crates/wasi/src/filesystem/primitives/mod.rs | 3 + .../primitives/tests/canonicalize.rs | 143 ++ .../filesystem/primitives/tests/cap_basics.rs | 227 +++ .../primitives/tests/cap_net_ext_tcp.rs | 1180 ++++++++++++ .../primitives/tests/cap_net_ext_tcp_split.rs | 1372 +++++++++++++ .../primitives/tests/cap_net_ext_udp.rs | 474 +++++ .../primitives/tests/cap_net_ext_udp_split.rs | 549 ++++++ .../primitives/tests/dir_entry_ext.rs | 33 + .../filesystem/primitives/tests/dir_ext.rs | 111 ++ .../primitives/tests/file_type_ext.rs | 37 + .../src/filesystem/primitives/tests/fs.rs | 1663 ++++++++++++++++ .../primitives/tests/fs_additional.rs | 1511 +++++++++++++++ .../filesystem/primitives/tests/fs_utf8.rs | 1704 +++++++++++++++++ .../primitives/tests/is_file_read_write.rs | 43 + .../primitives/tests/issue_22577.rs | 31 + .../primitives/tests/metadata_ext.rs | 85 + .../src/filesystem/primitives/tests/mod.rs | 35 + .../src/filesystem/primitives/tests/net.rs | 76 + .../filesystem/primitives/tests/net_tcp.rs | 1050 ++++++++++ .../filesystem/primitives/tests/net_udp.rs | 451 +++++ .../primitives/tests/open_ambient.rs | 29 + .../primitives/tests/paths_containing_nul.rs | 70 + .../src/filesystem/primitives/tests/rand.rs | 5 + .../filesystem/primitives/tests/readdir.rs | 85 + .../src/filesystem/primitives/tests/rename.rs | 178 ++ .../primitives/tests/rename_directory.rs | 36 + .../src/filesystem/primitives/tests/reopen.rs | 93 + .../filesystem/primitives/tests/reopendir.rs | 66 + .../src/filesystem/primitives/tests/root.rs | 32 + .../src/filesystem/primitives/tests/set.rs | 132 ++ .../filesystem/primitives/tests/set_times.rs | 134 ++ .../filesystem/primitives/tests/symlinks.rs | 580 ++++++ .../filesystem/primitives/tests/sys/mod.rs | 7 + .../primitives/tests/sys/unix/mod.rs | 1 + .../primitives/tests/sys/unix/weak.rs | 238 +++ .../primitives/tests/sys_common/io.rs | 27 + .../primitives/tests/sys_common/mod.rs | 96 + .../tests/sys_common/symlink_junction.rs | 251 +++ .../src/filesystem/primitives/tests/time.rs | 200 ++ .../primitives/tests/windows_open.rs | 176 ++ .../primitives/tests/windows_symlinks.rs | 93 + 41 files changed, 13307 insertions(+) create mode 100644 crates/wasi/src/filesystem/primitives/tests/canonicalize.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/cap_basics.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/cap_net_ext_tcp.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/cap_net_ext_tcp_split.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/cap_net_ext_udp.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/cap_net_ext_udp_split.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/dir_entry_ext.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/dir_ext.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/file_type_ext.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/fs.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/fs_additional.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/fs_utf8.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/is_file_read_write.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/issue_22577.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/metadata_ext.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/mod.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/net.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/net_tcp.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/net_udp.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/open_ambient.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/paths_containing_nul.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/rand.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/readdir.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/rename.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/rename_directory.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/reopen.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/reopendir.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/root.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/set.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/set_times.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/symlinks.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/sys/mod.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/sys/unix/mod.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/sys/unix/weak.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/sys_common/io.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/sys_common/mod.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/sys_common/symlink_junction.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/time.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/windows_open.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/windows_symlinks.rs diff --git a/crates/wasi/src/filesystem/primitives/mod.rs b/crates/wasi/src/filesystem/primitives/mod.rs index 7fbc96ae86c4..84dffd6eecfd 100644 --- a/crates/wasi/src/filesystem/primitives/mod.rs +++ b/crates/wasi/src/filesystem/primitives/mod.rs @@ -94,3 +94,6 @@ fn dir_paths() { ); } } + +#[cfg(test)] +mod tests; diff --git a/crates/wasi/src/filesystem/primitives/tests/canonicalize.rs b/crates/wasi/src/filesystem/primitives/tests/canonicalize.rs new file mode 100644 index 000000000000..d59f6cce9fac --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/canonicalize.rs @@ -0,0 +1,143 @@ +#[macro_use] +mod sys_common; + +use std::path::Path; +use sys_common::io::tmpdir; + +#[test] +fn canonicalize_dot() { + let tmpdir = tmpdir(); + assert_eq!(check!(tmpdir.canonicalize(".")), Path::new(".")); +} + +#[test] +fn canonicalize_edge_cases() { + let tmpdir = tmpdir(); + assert_eq!(check!(tmpdir.canonicalize(".")), Path::new(".")); + assert_eq!(check!(tmpdir.canonicalize("./")), Path::new(".")); + assert_eq!(check!(tmpdir.canonicalize("./.")), Path::new(".")); + + #[cfg(not(windows))] + error!(tmpdir.canonicalize(""), "No such file"); + #[cfg(windows)] + error!(tmpdir.canonicalize(""), 2); + + #[cfg(not(windows))] + error!(tmpdir.canonicalize("foo"), "No such file"); + #[cfg(windows)] + error!(tmpdir.canonicalize("foo"), 2); + + error_contains!( + tmpdir.canonicalize("/"), + "a path led outside of the filesystem" + ); + error_contains!( + tmpdir.canonicalize("/."), + "a path led outside of the filesystem" + ); + error_contains!( + tmpdir.canonicalize("/.."), + "a path led outside of the filesystem" + ); + error_contains!( + tmpdir.canonicalize("/./"), + "a path led outside of the filesystem" + ); + error_contains!( + tmpdir.canonicalize("/../"), + "a path led outside of the filesystem" + ); + error_contains!( + tmpdir.canonicalize("/../."), + "a path led outside of the filesystem" + ); + error_contains!( + tmpdir.canonicalize("/./."), + "a path led outside of the filesystem" + ); + error_contains!( + tmpdir.canonicalize("/./.."), + "a path led outside of the filesystem" + ); + error_contains!( + tmpdir.canonicalize("/./../"), + "a path led outside of the filesystem" + ); + error_contains!( + tmpdir.canonicalize("/../.."), + "a path led outside of the filesystem" + ); + error_contains!( + tmpdir.canonicalize("/../../"), + "a path led outside of the filesystem" + ); + error_contains!( + tmpdir.canonicalize(".."), + "a path led outside of the filesystem" + ); + error_contains!( + tmpdir.canonicalize("../"), + "a path led outside of the filesystem" + ); + error_contains!( + tmpdir.canonicalize("../."), + "a path led outside of the filesystem" + ); + error_contains!( + tmpdir.canonicalize("./.."), + "a path led outside of the filesystem" + ); + error_contains!( + tmpdir.canonicalize("../.."), + "a path led outside of the filesystem" + ); + + check!(tmpdir.create_dir_all("foo/bar")); + assert_eq!(check!(tmpdir.canonicalize("foo")), Path::new("foo")); + assert_eq!(check!(tmpdir.canonicalize("foo/")), Path::new("foo")); + assert_eq!(check!(tmpdir.canonicalize("foo/")).to_str(), Some("foo")); + assert_eq!(check!(tmpdir.canonicalize("foo/.")), Path::new("foo")); + assert_eq!(check!(tmpdir.canonicalize("foo/./")), Path::new("foo")); + assert_eq!(check!(tmpdir.canonicalize("foo/./")).to_str(), Some("foo")); + assert_eq!(check!(tmpdir.canonicalize("foo/..")), Path::new(".")); + assert_eq!(check!(tmpdir.canonicalize("foo/../")), Path::new(".")); + assert_eq!(check!(tmpdir.canonicalize("foo/../.")), Path::new(".")); + + assert_eq!( + check!(tmpdir.canonicalize("foo/bar")), + Path::new("foo").join("bar"), + ); + assert_eq!( + check!(tmpdir.canonicalize("foo/bar/")), + Path::new("foo").join("bar") + ); + assert_eq!( + check!(tmpdir.canonicalize("foo/bar/")).to_str(), + Path::new("foo").join("bar").to_str(), + ); + assert_eq!( + check!(tmpdir.canonicalize("foo/../foo/bar")), + Path::new("foo").join("bar") + ); + assert_eq!( + check!(tmpdir.canonicalize("foo/../foo/bar/")), + Path::new("foo").join("bar") + ); + assert_eq!( + check!(tmpdir.canonicalize("foo/../foo/bar/")).to_str(), + Path::new("foo").join("bar").to_str() + ); + error_contains!( + tmpdir.canonicalize("foo/../.."), + "a path led outside of the filesystem" + ); + error_contains!( + tmpdir.canonicalize("foo/../../"), + "a path led outside of the filesystem" + ); + + #[cfg(not(windows))] + error!(tmpdir.canonicalize("foo/bar/qux"), "No such file"); + #[cfg(windows)] + error!(tmpdir.canonicalize("foo/bar/qux"), 2); +} diff --git a/crates/wasi/src/filesystem/primitives/tests/cap_basics.rs b/crates/wasi/src/filesystem/primitives/tests/cap_basics.rs new file mode 100644 index 000000000000..b1f37f0a6ea1 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/cap_basics.rs @@ -0,0 +1,227 @@ +#[macro_use] +mod sys_common; + +use sys_common::io::tmpdir; +use sys_common::symlink_supported; + +#[test] +fn cap_smoke_test() { + let tmpdir = tmpdir(); + check!(tmpdir.create_dir_all("dir/inner")); + check!(tmpdir.write("red.txt", b"hello world\n")); + check!(tmpdir.write("dir/green.txt", b"goodmight moon\n")); + check!(tmpdir.write("dir/inner/blue.txt", b"hey mars\n")); + + let inner = check!(tmpdir.open_dir("dir/inner")); + + check!(tmpdir.open("red.txt")); + + #[cfg(not(windows))] + error!(tmpdir.open("blue.txt"), "No such file"); + #[cfg(windows)] + error!(tmpdir.open("blue.txt"), 2); + + #[cfg(not(windows))] + error!(tmpdir.open("green.txt"), "No such file"); + #[cfg(windows)] + error!(tmpdir.open("green.txt"), 2); + + check!(tmpdir.open("./red.txt")); + + #[cfg(not(windows))] + error!(tmpdir.open("./blue.txt"), "No such file"); + #[cfg(windows)] + error!(tmpdir.open("./blue.txt"), 2); + + #[cfg(not(windows))] + error!(tmpdir.open("./green.txt"), "No such file"); + #[cfg(windows)] + error!(tmpdir.open("./green.txt"), 2); + + #[cfg(not(windows))] + error!(tmpdir.open("dir/red.txt"), "No such file"); + #[cfg(windows)] + error!(tmpdir.open("dir/red.txt"), 2); + + check!(tmpdir.open("dir/green.txt")); + + #[cfg(not(windows))] + error!(tmpdir.open("dir/blue.txt"), "No such file"); + #[cfg(windows)] + error!(tmpdir.open("dir/blue.txt"), 2); + + #[cfg(not(windows))] + error!(tmpdir.open("dir/inner/red.txt"), "No such file"); + #[cfg(windows)] + error!(tmpdir.open("dir/inner/red.txt"), 2); + + #[cfg(not(windows))] + error!(tmpdir.open("dir/inner/green.txt"), "No such file"); + #[cfg(windows)] + error!(tmpdir.open("dir/inner/green.txt"), 2); + + check!(tmpdir.open("dir/inner/blue.txt")); + + check!(tmpdir.open("dir/../red.txt")); + check!(tmpdir.open("dir/inner/../../red.txt")); + check!(tmpdir.open("dir/inner/../inner/../../red.txt")); + + #[cfg(not(windows))] + error!(inner.open("red.txt"), "No such file"); + #[cfg(windows)] + error!(inner.open("red.txt"), 2); + + #[cfg(not(windows))] + error!(inner.open("green.txt"), "No such file"); + #[cfg(windows)] + error!(inner.open("green.txt"), 2); + + error_contains!( + inner.open("../inner/blue.txt"), + "a path led outside of the filesystem" + ); + error_contains!( + inner.open("../inner/red.txt"), + "a path led outside of the filesystem" + ); + + #[cfg(not(windows))] + error!(inner.open_dir(""), "No such file"); + #[cfg(windows)] + error!(inner.open_dir(""), 2); + + error_contains!(inner.open_dir("/"), "a path led outside of the filesystem"); + error_contains!( + inner.open_dir("/etc/services"), + "a path led outside of the filesystem" + ); + check!(inner.open_dir(".")); + check!(inner.open_dir("./")); + check!(inner.open_dir("./.")); + error_contains!(inner.open_dir(".."), "a path led outside of the filesystem"); + error_contains!( + inner.open_dir("../"), + "a path led outside of the filesystem" + ); + error_contains!( + inner.open_dir("../."), + "a path led outside of the filesystem" + ); + error_contains!( + inner.open_dir("./.."), + "a path led outside of the filesystem" + ); +} + +#[test] +fn symlinks() { + #[cfg(windows)] + use cap_fs_ext::DirExt; + + if !symlink_supported() { + return; + } + + let tmpdir = tmpdir(); + check!(tmpdir.create_dir_all("dir/inner")); + check!(tmpdir.write("red.txt", b"hello world\n")); + check!(tmpdir.write("dir/green.txt", b"goodmight moon\n")); + check!(tmpdir.write("dir/inner/blue.txt", b"hey mars\n")); + + let inner = check!(tmpdir.open_dir("dir/inner")); + + check!(tmpdir.symlink("dir", "link")); + #[cfg(not(windows))] + check!(tmpdir.symlink("does_not_exist", "badlink")); + + check!(tmpdir.open("link/../red.txt")); + check!(tmpdir.open("link/green.txt")); + check!(tmpdir.open("link/inner/blue.txt")); + #[cfg(not(windows))] + { + error_contains!(tmpdir.open("link/red.txt"), "No such file"); + error_contains!(tmpdir.open("link/../green.txt"), "No such file"); + } + #[cfg(windows)] + { + error_contains!( + tmpdir.open("link/red.txt"), + "The system cannot find the file specified." + ); + error_contains!( + tmpdir.open("link/../green.txt"), + "The system cannot find the file specified." + ); + } + + check!(tmpdir.open("./dir/.././/link/..///./red.txt")); + check!(tmpdir.open("link/inner/../inner/../../red.txt")); + error_contains!( + inner.open("../inner/../inner/../../link/other.txt"), + "a path led outside of the filesystem" + ); + #[cfg(not(windows))] + { + error_contains!( + tmpdir.open("./dir/.././/link/..///./not.txt"), + "No such file" + ); + error_contains!(tmpdir.open("link/other.txt"), "No such file"); + error_contains!(tmpdir.open("badlink/../red.txt"), "No such file"); + } + #[cfg(windows)] + { + error_contains!( + tmpdir.open("./dir/.././/link/..///./not.txt"), + "The system cannot find the file specified." + ); + error_contains!( + tmpdir.open("link/other.txt"), + "The system cannot find the file specified." + ); + } +} + +#[test] +#[cfg(not(windows))] +fn symlink_loop() { + #[cfg(windows)] + use cap_fs_ext::DirExt; + + let tmpdir = tmpdir(); + check!(tmpdir.symlink("link", "link")); + // TODO: Check the error message + error_contains!(tmpdir.open("link"), ""); +} + +#[test] +fn symlink_loop_from_rename() { + #[cfg(windows)] + use cap_fs_ext::DirExt; + + if !symlink_supported() { + return; + } + + let tmpdir = tmpdir(); + check!(tmpdir.create("file")); + check!(tmpdir.symlink("file", "link")); + check!(tmpdir.open("link")); + check!(tmpdir.rename("file", &tmpdir, "renamed")); + error_contains!(tmpdir.open("link"), ""); + check!(tmpdir.rename("link", &tmpdir, "file")); + error_contains!(tmpdir.open("file"), ""); + check!(tmpdir.rename("file", &tmpdir, "link")); + error_contains!(tmpdir.open("link"), ""); + check!(tmpdir.rename("renamed", &tmpdir, "file")); + check!(tmpdir.open("link")); +} + +#[cfg(target_os = "linux")] +#[test] +fn proc_self_fd() { + let fd = check!(std::fs::File::open("/proc/self/fd")); + let dir = cap_std::fs::Dir::from_std_file(fd); + // This should fail with "too many levels of symbolic links". + dir.open("0").unwrap_err(); +} diff --git a/crates/wasi/src/filesystem/primitives/tests/cap_net_ext_tcp.rs b/crates/wasi/src/filesystem/primitives/tests/cap_net_ext_tcp.rs new file mode 100644 index 000000000000..b7ddd5a769db --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/cap_net_ext_tcp.rs @@ -0,0 +1,1180 @@ +// This file is derived from Rust's library/std/src/net/tcp/tests.rs at +// revision 377d1a984cd2a53327092b90aa1d8b7e22d1e347. + +mod net; + +use cap_net_ext::{AddressFamily, Blocking, PoolExt, TcpListenerExt}; +use cap_std::ambient_authority; +use cap_std::net::*; +use net::{next_test_ip4, next_test_ip6}; +use std::io::prelude::*; +use std::io::{ErrorKind, IoSlice, IoSliceMut}; +use std::sync::mpsc::channel; +use std::time::{Duration, Instant}; +use std::{fmt, thread}; + +fn each_ip(f: &mut dyn FnMut(SocketAddr)) { + f(next_test_ip4()); + f(next_test_ip6()); +} + +macro_rules! t { + ($e:expr) => { + match $e { + Ok(t) => t, + Err(e) => panic!("received error for `{}`: {}", stringify!($e), e), + } + }; +} + +#[test] +fn bind_error() { + let mut pool = Pool::new(); + pool.insert_socket_addr("1.1.1.1:9999".parse().unwrap(), ambient_authority()); + + let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + match pool.bind_existing_tcp_listener(&listener, "1.1.1.1:9999") { + Ok(..) => panic!(), + Err(e) => assert_eq!(e.kind(), ErrorKind::AddrNotAvailable), + } +} + +#[test] +fn connect_error() { + let mut pool = Pool::new(); + pool.insert_socket_addr("0.0.0.0:1".parse().unwrap(), ambient_authority()); + + let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + match pool.connect_into_tcp_stream(socket, "0.0.0.0:1") { + Ok(..) => panic!(), + Err(e) => assert!( + e.kind() == ErrorKind::ConnectionRefused + || e.kind() == ErrorKind::InvalidInput + || e.kind() == ErrorKind::AddrInUse + || e.kind() == ErrorKind::AddrNotAvailable + || e.kind() == ErrorKind::TimedOut + // Rust >=1.83 adds `ErrorKind::NetworkUnreachable`. + || e.to_string().contains("network unreachable") + || e.to_string().contains("Network is unreachable"), + "bad error: {} {:?}", + e, + e.kind() + ), + } +} + +#[test] +fn listen_localhost() { + let socket_addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(socket_addr, ambient_authority()); + for resolved in format!("localhost:{}", socket_addr.port()) + .to_socket_addrs() + .unwrap() + { + pool.insert_socket_addr(resolved, ambient_authority()); + } + + let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + t!(pool.bind_existing_tcp_listener(&listener, &socket_addr)); + listener.listen(None).unwrap(); + + let _t = thread::spawn(move || { + let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + let mut stream = + t!(pool.connect_into_tcp_stream(socket, &("localhost", socket_addr.port()))); + t!(stream.write(&[144])); + }); + + let mut stream = t!(listener.accept()).0; + let mut buf = [0]; + t!(stream.read(&mut buf)); + assert!(buf[0] == 144); +} + +#[test] +fn connect_loopback() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let acceptor = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool.bind_existing_tcp_listener(&acceptor, &addr)); + acceptor.listen(None).unwrap(); + + let _t = thread::spawn(move || { + let host = match addr { + SocketAddr::V4(..) => "127.0.0.1", + SocketAddr::V6(..) => "::1", + }; + let socket = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let mut stream = t!(pool.connect_into_tcp_stream(socket, &(host, addr.port()))); + t!(stream.write(&[66])); + }); + + let mut stream = t!(acceptor.accept()).0; + let mut buf = [0]; + t!(stream.read(&mut buf)); + assert!(buf[0] == 66); + }) +} + +#[test] +fn smoke_test() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let acceptor = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool.bind_existing_tcp_listener(&acceptor, &addr)); + acceptor.listen(None).unwrap(); + + let (tx, rx) = channel(); + let _t = thread::spawn(move || { + let socket = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let mut stream = t!(pool.connect_into_tcp_stream(socket, &addr)); + t!(stream.write(&[99])); + tx.send(t!(stream.local_addr())).unwrap(); + }); + + let (mut stream, addr) = t!(acceptor.accept()); + let mut buf = [0]; + t!(stream.read(&mut buf)); + assert!(buf[0] == 99); + assert_eq!(addr, t!(rx.recv())); + }) +} + +#[test] +fn read_eof() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let acceptor = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool.bind_existing_tcp_listener(&acceptor, &addr)); + acceptor.listen(None).unwrap(); + + let _t = thread::spawn(move || { + let socket = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let _stream = t!(pool.connect_into_tcp_stream(socket, &addr)); + // Close + }); + + let mut stream = t!(acceptor.accept()).0; + let mut buf = [0]; + let nread = t!(stream.read(&mut buf)); + assert_eq!(nread, 0); + let nread = t!(stream.read(&mut buf)); + assert_eq!(nread, 0); + }) +} + +#[test] +fn write_close() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let acceptor = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool.bind_existing_tcp_listener(&acceptor, &addr)); + acceptor.listen(None).unwrap(); + + let (tx, rx) = channel(); + let _t = thread::spawn(move || { + let socket = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + drop(t!(pool.connect_into_tcp_stream(socket, &addr))); + tx.send(()).unwrap(); + }); + + let mut stream = t!(acceptor.accept()).0; + rx.recv().unwrap(); + let buf = [0]; + match stream.write(&buf) { + Ok(..) => {} + Err(e) => { + assert!( + e.kind() == ErrorKind::ConnectionReset + || e.kind() == ErrorKind::BrokenPipe + || e.kind() == ErrorKind::ConnectionAborted, + "unknown error: {}", + e + ); + } + } + }) +} + +#[test] +fn multiple_connect_serial() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let max = 10; + let acceptor = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool.bind_existing_tcp_listener(&acceptor, &addr)); + acceptor.listen(None).unwrap(); + + let _t = thread::spawn(move || { + for _ in 0..max { + let socket = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let mut stream = t!(pool.connect_into_tcp_stream(socket, &addr)); + t!(stream.write(&[99])); + } + }); + + for stream in acceptor.incoming().take(max) { + let mut stream = t!(stream); + let mut buf = [0]; + t!(stream.read(&mut buf)); + assert_eq!(buf[0], 99); + } + }) +} + +#[test] +fn multiple_connect_interleaved_greedy_schedule() { + const MAX: usize = 10; + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let acceptor = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool.bind_existing_tcp_listener(&acceptor, &addr)); + acceptor.listen(None).unwrap(); + + let _t = thread::spawn(move || { + let acceptor = acceptor; + for (i, stream) in acceptor.incoming().enumerate().take(MAX) { + // Start another thread to handle the connection + let _t = thread::spawn(move || { + let mut stream = t!(stream); + let mut buf = [0]; + t!(stream.read(&mut buf)); + assert!(buf[0] == i as u8); + }); + } + }); + + connect(0, addr, pool); + }); + + fn connect(i: usize, addr: SocketAddr, pool: Pool) { + if i == MAX { + return; + } + + let t = thread::spawn(move || { + let socket = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let mut stream = t!(pool.connect_into_tcp_stream(socket, &addr)); + // Connect again before writing + connect(i + 1, addr, pool); + t!(stream.write(&[i as u8])); + }); + t.join().ok().expect("thread panicked"); + } +} + +#[test] +fn multiple_connect_interleaved_lazy_schedule() { + const MAX: usize = 10; + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let acceptor = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool.bind_existing_tcp_listener(&acceptor, &addr)); + acceptor.listen(None).unwrap(); + + let _t = thread::spawn(move || { + for stream in acceptor.incoming().take(MAX) { + // Start another thread to handle the connection + let _t = thread::spawn(move || { + let mut stream = t!(stream); + let mut buf = [0]; + t!(stream.read(&mut buf)); + assert!(buf[0] == 99); + }); + } + }); + + connect(0, addr, pool); + }); + + fn connect(i: usize, addr: SocketAddr, pool: Pool) { + if i == MAX { + return; + } + + let t = thread::spawn(move || { + let socket = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let mut stream = t!(pool.connect_into_tcp_stream(socket, &addr)); + connect(i + 1, addr, pool); + t!(stream.write(&[99])); + }); + t.join().ok().expect("thread panicked"); + } +} + +#[test] +fn socket_and_peer_name() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let listener = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool.bind_existing_tcp_listener(&listener, &addr)); + listener.listen(None).unwrap(); + let so_name = t!(listener.local_addr()); + assert_eq!(addr, so_name); + let _t = thread::spawn(move || { + t!(listener.accept()); + }); + + let socket = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let stream = t!(pool.connect_into_tcp_stream(socket, &addr)); + assert_eq!(addr, t!(stream.peer_addr())); + }) +} + +#[test] +fn partial_read() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let (tx, rx) = channel(); + let srv = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool.bind_existing_tcp_listener(&srv, &addr)); + srv.listen(None).unwrap(); + let _t = thread::spawn(move || { + let mut cl = t!(srv.accept()).0; + cl.write(&[10]).unwrap(); + let mut b = [0]; + t!(cl.read(&mut b)); + tx.send(()).unwrap(); + }); + + let socket = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let mut c = t!(pool.connect_into_tcp_stream(socket, &addr)); + let mut b = [0; 10]; + assert_eq!(c.read(&mut b).unwrap(), 1); + t!(c.write(&[1])); + rx.recv().unwrap(); + }) +} + +#[test] +fn read_vectored() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let srv = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool.bind_existing_tcp_listener(&srv, &addr)); + srv.listen(None).unwrap(); + let socket = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let mut s1 = t!(pool.connect_into_tcp_stream(socket, &addr)); + let mut s2 = t!(srv.accept()).0; + + let len = s1.write(&[10, 11, 12]).unwrap(); + assert_eq!(len, 3); + + let mut a = []; + let mut b = [0]; + let mut c = [0; 3]; + let len = t!(s2.read_vectored(&mut [ + IoSliceMut::new(&mut a), + IoSliceMut::new(&mut b), + IoSliceMut::new(&mut c) + ])); + assert!(len > 0); + assert_eq!(b, [10]); + // some implementations don't support readv, so we may only fill the first + // buffer + assert!(len == 1 || c == [11, 12, 0]); + }) +} + +#[test] +fn write_vectored() { + let mut pool = Pool::new(); + + each_ip(&mut |addr| { + pool.insert_socket_addr(addr, ambient_authority()); + + let srv = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool.bind_existing_tcp_listener(&srv, &addr)); + srv.listen(None).unwrap(); + let socket = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let mut s1 = t!(pool.connect_into_tcp_stream(socket, &addr)); + let mut s2 = t!(srv.accept()).0; + + let a = []; + let b = [10]; + let c = [11, 12]; + t!(s1.write_vectored(&[IoSlice::new(&a), IoSlice::new(&b), IoSlice::new(&c)])); + + let mut buf = [0; 4]; + let len = t!(s2.read(&mut buf)); + // some implementations don't support writev, so we may only write the first + // buffer + if len == 1 { + assert_eq!(buf, [10, 0, 0, 0]); + } else { + assert_eq!(len, 3); + assert_eq!(buf, [10, 11, 12, 0]); + } + }) +} + +#[test] +fn double_bind() { + let mut pool = Pool::new(); + + each_ip(&mut |addr| { + pool.insert_socket_addr(addr, ambient_authority()); + + let listener1 = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool.bind_existing_tcp_listener(&listener1, &addr)); + listener1.listen(None).unwrap(); + let listener2 = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + match pool.bind_existing_tcp_listener(&listener2, &addr) { + Ok(()) => panic!( + "This system (perhaps due to options set by pool.bind_existing_tcp_listener) \ + permits double binding: {:?} and {:?}", + listener1, listener2 + ), + Err(e) => { + assert!( + e.kind() == ErrorKind::ConnectionRefused + || e.kind() == ErrorKind::Other + || e.kind() == ErrorKind::AddrInUse, + "unknown error: {} {:?}", + e, + e.kind() + ); + } + } + }) +} + +#[test] +fn tcp_clone_smoke() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let acceptor = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool.bind_existing_tcp_listener(&acceptor, &addr)); + acceptor.listen(None).unwrap(); + + let _t = thread::spawn(move || { + let socket = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let mut s = t!(pool.connect_into_tcp_stream(socket, &addr)); + let mut buf = [0, 0]; + assert_eq!(s.read(&mut buf).unwrap(), 1); + assert_eq!(buf[0], 1); + t!(s.write(&[2])); + }); + + let mut s1 = t!(acceptor.accept()).0; + let s2 = t!(s1.try_clone()); + + let (tx1, rx1) = channel(); + let (tx2, rx2) = channel(); + let _t = thread::spawn(move || { + let mut s2 = s2; + rx1.recv().unwrap(); + t!(s2.write(&[1])); + tx2.send(()).unwrap(); + }); + tx1.send(()).unwrap(); + let mut buf = [0, 0]; + assert_eq!(s1.read(&mut buf).unwrap(), 1); + rx2.recv().unwrap(); + }) +} + +#[test] +fn tcp_clone_two_read() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let acceptor = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool.bind_existing_tcp_listener(&acceptor, &addr)); + acceptor.listen(None).unwrap(); + let (tx1, rx) = channel(); + let tx2 = tx1.clone(); + + let _t = thread::spawn(move || { + let socket = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let mut s = t!(pool.connect_into_tcp_stream(socket, &addr)); + t!(s.write(&[1])); + rx.recv().unwrap(); + t!(s.write(&[2])); + rx.recv().unwrap(); + }); + + let mut s1 = t!(acceptor.accept()).0; + let s2 = t!(s1.try_clone()); + + let (done, rx) = channel(); + let _t = thread::spawn(move || { + let mut s2 = s2; + let mut buf = [0, 0]; + t!(s2.read(&mut buf)); + tx2.send(()).unwrap(); + done.send(()).unwrap(); + }); + let mut buf = [0, 0]; + t!(s1.read(&mut buf)); + tx1.send(()).unwrap(); + + rx.recv().unwrap(); + }) +} + +#[test] +fn tcp_clone_two_write() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let acceptor = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool.bind_existing_tcp_listener(&acceptor, &addr)); + acceptor.listen(None).unwrap(); + + let _t = thread::spawn(move || { + let socket = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let mut s = t!(pool.connect_into_tcp_stream(socket, &addr)); + let mut buf = [0, 1]; + t!(s.read(&mut buf)); + t!(s.read(&mut buf)); + }); + + let mut s1 = t!(acceptor.accept()).0; + let s2 = t!(s1.try_clone()); + + let (done, rx) = channel(); + let _t = thread::spawn(move || { + let mut s2 = s2; + t!(s2.write(&[1])); + done.send(()).unwrap(); + }); + t!(s1.write(&[2])); + + rx.recv().unwrap(); + }) +} + +#[test] +// FIXME: https://github.com/fortanix/rust-sgx/issues/110 +#[cfg_attr(target_env = "sgx", ignore)] +fn shutdown_smoke() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let a = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool.bind_existing_tcp_listener(&a, &addr)); + a.listen(None).unwrap(); + let _t = thread::spawn(move || { + let mut c = t!(a.accept()).0; + let mut b = [0]; + assert_eq!(c.read(&mut b).unwrap(), 0); + t!(c.write(&[1])); + }); + + let socket = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let mut s = t!(pool.connect_into_tcp_stream(socket, &addr)); + t!(s.shutdown(Shutdown::Write)); + assert!(s.write(&[1]).is_err()); + let mut b = [0, 0]; + assert_eq!(t!(s.read(&mut b)), 1); + assert_eq!(b[0], 1); + }) +} + +#[test] +// FIXME: https://github.com/fortanix/rust-sgx/issues/110 +#[cfg_attr(target_env = "sgx", ignore)] +fn close_readwrite_smoke() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let a = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool.bind_existing_tcp_listener(&a, &addr)); + a.listen(None).unwrap(); + let (tx, rx) = channel::<()>(); + let _t = thread::spawn(move || { + let _s = t!(a.accept()); + let _ = rx.recv(); + }); + + let mut b = [0]; + let socket = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let mut s = t!(pool.connect_into_tcp_stream(socket, &addr)); + let mut s2 = t!(s.try_clone()); + + // closing should prevent reads/writes + t!(s.shutdown(Shutdown::Write)); + assert!(s.write(&[0]).is_err()); + t!(s.shutdown(Shutdown::Read)); + assert_eq!(s.read(&mut b).unwrap(), 0); + + // closing should affect previous handles + assert!(s2.write(&[0]).is_err()); + assert_eq!(s2.read(&mut b).unwrap(), 0); + + // closing should affect new handles + let mut s3 = t!(s.try_clone()); + assert!(s3.write(&[0]).is_err()); + assert_eq!(s3.read(&mut b).unwrap(), 0); + + // make sure these don't die + let _ = s2.shutdown(Shutdown::Read); + let _ = s2.shutdown(Shutdown::Write); + let _ = s3.shutdown(Shutdown::Read); + let _ = s3.shutdown(Shutdown::Write); + drop(tx); + }) +} + +#[test] +#[cfg(unix)] // test doesn't work on Windows, see #31657 +fn close_read_wakes_up() { + let mut pool = Pool::new(); + + each_ip(&mut |addr| { + pool.insert_socket_addr(addr, ambient_authority()); + + let a = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool.bind_existing_tcp_listener(&a, &addr)); + a.listen(None).unwrap(); + let (tx1, rx) = channel::<()>(); + let _t = thread::spawn(move || { + let _s = t!(a.accept()); + let _ = rx.recv(); + }); + + let socket = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let s = t!(pool.connect_into_tcp_stream(socket, &addr)); + let s2 = t!(s.try_clone()); + let (tx, rx) = channel(); + let _t = thread::spawn(move || { + let mut s2 = s2; + assert_eq!(t!(s2.read(&mut [0])), 0); + tx.send(()).unwrap(); + }); + // this should wake up the child thread + t!(s.shutdown(Shutdown::Read)); + + // this test will never finish if the child doesn't wake up + rx.recv().unwrap(); + drop(tx1); + }) +} + +#[test] +fn clone_while_reading() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let accept = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool.bind_existing_tcp_listener(&accept, &addr)); + accept.listen(None).unwrap(); + + // Enqueue a thread to write to a socket + let (tx, rx) = channel(); + let (txdone, rxdone) = channel(); + let txdone2 = txdone.clone(); + let _t = thread::spawn(move || { + let socket = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let mut tcp = t!(pool.connect_into_tcp_stream(socket, &addr)); + rx.recv().unwrap(); + t!(tcp.write(&[0])); + txdone2.send(()).unwrap(); + }); + + // Spawn off a reading clone + let tcp = t!(accept.accept()).0; + let tcp2 = t!(tcp.try_clone()); + let txdone3 = txdone.clone(); + let _t = thread::spawn(move || { + let mut tcp2 = tcp2; + t!(tcp2.read(&mut [0])); + txdone3.send(()).unwrap(); + }); + + // Try to ensure that the reading clone is indeed reading + for _ in 0..50 { + thread::yield_now(); + } + + // clone the handle again while it's reading, then let it finish the + // read. + let _ = t!(tcp.try_clone()); + tx.send(()).unwrap(); + rxdone.recv().unwrap(); + rxdone.recv().unwrap(); + }) +} + +#[test] +fn clone_accept_smoke() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let a = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool.bind_existing_tcp_listener(&a, &addr)); + a.listen(None).unwrap(); + let a2 = t!(a.try_clone()); + + let p = pool.clone(); + let _t = thread::spawn(move || { + let socket = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let _ = p.connect_into_tcp_stream(socket, &addr); + }); + let p = pool.clone(); + let _t = thread::spawn(move || { + let socket = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let _ = p.connect_into_tcp_stream(socket, &addr); + }); + + t!(a.accept()); + t!(a2.accept()); + }) +} + +#[test] +fn clone_accept_concurrent() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let a = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool.bind_existing_tcp_listener(&a, &addr)); + a.listen(None).unwrap(); + let a2 = t!(a.try_clone()); + + let (tx, rx) = channel(); + let tx2 = tx.clone(); + + let _t = thread::spawn(move || { + tx.send(t!(a.accept())).unwrap(); + }); + let _t = thread::spawn(move || { + tx2.send(t!(a2.accept())).unwrap(); + }); + + let p = pool.clone(); + let _t = thread::spawn(move || { + let socket = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let _ = p.connect_into_tcp_stream(socket, &addr); + }); + let p = pool.clone(); + let _t = thread::spawn(move || { + let socket = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let _ = p.connect_into_tcp_stream(socket, &addr); + }); + + rx.recv().unwrap(); + rx.recv().unwrap(); + }) +} + +#[test] +fn debug() { + let mut pool = Pool::new(); + + #[cfg(not(target_env = "sgx"))] + fn render_socket_addr<'a>(addr: &'a SocketAddr) -> impl fmt::Debug + 'a { + addr + } + #[cfg(target_env = "sgx")] + fn render_socket_addr<'a>(addr: &'a SocketAddr) -> impl fmt::Debug + 'a { + addr.to_string() + } + + #[cfg(target_env = "sgx")] + use std::os::fortanix_sgx::io::AsRawFd; + #[cfg(unix)] + use std::os::unix::io::AsRawFd; + #[cfg(not(windows))] + fn render_inner(addr: &dyn AsRawFd) -> impl fmt::Debug { + addr.as_raw_fd() + } + #[cfg(windows)] + fn render_inner(addr: &dyn std::os::windows::io::AsRawSocket) -> impl fmt::Debug { + addr.as_raw_socket() + } + + let inner_name = if cfg!(windows) { "socket" } else { "fd" }; + let socket_addr = next_test_ip4(); + pool.insert_socket_addr(socket_addr, ambient_authority()); + for resolved in format!("localhost:{}", socket_addr.port()) + .to_socket_addrs() + .unwrap() + { + pool.insert_socket_addr(resolved, ambient_authority()); + } + + let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + t!(pool.bind_existing_tcp_listener(&listener, &socket_addr)); + listener.listen(None).unwrap(); + let compare = format!( + "TcpListener {{ addr: {:?}, {}: {:?} }}", + render_socket_addr(&socket_addr), + inner_name, + render_inner(&listener) + ); + assert_eq!(format!("{:?}", listener), compare); + + let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + let stream = t!(pool.connect_into_tcp_stream(socket, &("localhost", socket_addr.port()))); + let compare = format!( + "TcpStream {{ addr: {:?}, peer: {:?}, {}: {:?} }}", + render_socket_addr(&stream.local_addr().unwrap()), + render_socket_addr(&stream.peer_addr().unwrap()), + inner_name, + render_inner(&stream) + ); + assert_eq!(format!("{:?}", stream), compare); +} + +// FIXME: re-enabled openbsd tests once their socket timeout code +// no longer has rounding errors. +// VxWorks ignores SO_SNDTIMEO. +#[cfg_attr( + any(target_os = "netbsd", target_os = "openbsd", target_os = "vxworks"), + ignore +)] +#[cfg_attr(target_env = "sgx", ignore)] // FIXME: https://github.com/fortanix/rust-sgx/issues/31 +#[test] +fn timeouts() { + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + for resolved in format!("localhost:{}", addr.port()) + .to_socket_addrs() + .unwrap() + { + pool.insert_socket_addr(resolved, ambient_authority()); + } + + let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + t!(pool.bind_existing_tcp_listener(&listener, &addr)); + listener.listen(None).unwrap(); + + let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + let stream = t!(pool.connect_into_tcp_stream(socket, &("localhost", addr.port()))); + let dur = Duration::new(15410, 0); + + assert_eq!(None, t!(stream.read_timeout())); + + t!(stream.set_read_timeout(Some(dur))); + assert_eq!(Some(dur), t!(stream.read_timeout())); + + assert_eq!(None, t!(stream.write_timeout())); + + t!(stream.set_write_timeout(Some(dur))); + assert_eq!(Some(dur), t!(stream.write_timeout())); + + t!(stream.set_read_timeout(None)); + assert_eq!(None, t!(stream.read_timeout())); + + t!(stream.set_write_timeout(None)); + assert_eq!(None, t!(stream.write_timeout())); + drop(listener); +} + +#[test] +#[cfg_attr(target_env = "sgx", ignore)] // FIXME: https://github.com/fortanix/rust-sgx/issues/31 +fn test_read_timeout() { + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + for resolved in format!("localhost:{}", addr.port()) + .to_socket_addrs() + .unwrap() + { + pool.insert_socket_addr(resolved, ambient_authority()); + } + + let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + t!(pool.bind_existing_tcp_listener(&listener, &addr)); + listener.listen(None).unwrap(); + + let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + let mut stream = t!(pool.connect_into_tcp_stream(socket, &("localhost", addr.port()))); + t!(stream.set_read_timeout(Some(Duration::from_millis(1000)))); + + let mut buf = [0; 10]; + let start = Instant::now(); + let kind = stream + .read_exact(&mut buf) + .err() + .expect("expected error") + .kind(); + assert!( + kind == ErrorKind::WouldBlock || kind == ErrorKind::TimedOut, + "unexpected_error: {:?}", + kind + ); + assert!(start.elapsed() > Duration::from_millis(400)); + drop(listener); +} + +#[test] +#[cfg_attr(target_env = "sgx", ignore)] // FIXME: https://github.com/fortanix/rust-sgx/issues/31 +fn test_read_with_timeout() { + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + for resolved in format!("localhost:{}", addr.port()) + .to_socket_addrs() + .unwrap() + { + pool.insert_socket_addr(resolved, ambient_authority()); + } + + let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + t!(pool.bind_existing_tcp_listener(&listener, &addr)); + listener.listen(None).unwrap(); + + let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + let mut stream = t!(pool.connect_into_tcp_stream(socket, &("localhost", addr.port()))); + t!(stream.set_read_timeout(Some(Duration::from_millis(1000)))); + + let mut other_end = t!(listener.accept()).0; + t!(other_end.write_all(b"hello world")); + + let mut buf = [0; 11]; + t!(stream.read(&mut buf)); + assert_eq!(b"hello world", &buf[..]); + + let start = Instant::now(); + let kind = stream + .read_exact(&mut buf) + .err() + .expect("expected error") + .kind(); + assert!( + kind == ErrorKind::WouldBlock || kind == ErrorKind::TimedOut, + "unexpected_error: {:?}", + kind + ); + assert!(start.elapsed() > Duration::from_millis(400)); + drop(listener); +} + +// Ensure the `set_read_timeout` and `set_write_timeout` calls return errors +// when passed zero Durations +#[test] +fn test_timeout_zero_duration() { + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + t!(pool.bind_existing_tcp_listener(&listener, &addr)); + listener.listen(None).unwrap(); + let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + let stream = t!(pool.connect_into_tcp_stream(socket, &addr)); + + let result = stream.set_write_timeout(Some(Duration::new(0, 0))); + let err = result.unwrap_err(); + assert_eq!(err.kind(), ErrorKind::InvalidInput); + + let result = stream.set_read_timeout(Some(Duration::new(0, 0))); + let err = result.unwrap_err(); + assert_eq!(err.kind(), ErrorKind::InvalidInput); + + drop(listener); +} + +#[test] +#[cfg_attr(target_env = "sgx", ignore)] +fn nodelay() { + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + for resolved in format!("localhost:{}", addr.port()) + .to_socket_addrs() + .unwrap() + { + pool.insert_socket_addr(resolved, ambient_authority()); + } + + let _listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + t!(pool.bind_existing_tcp_listener(&_listener, &addr)); + _listener.listen(None).unwrap(); + + let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + let stream = t!(pool.connect_into_tcp_stream(socket, &("localhost", addr.port()))); + + assert_eq!(false, t!(stream.nodelay())); + t!(stream.set_nodelay(true)); + assert_eq!(true, t!(stream.nodelay())); + t!(stream.set_nodelay(false)); + assert_eq!(false, t!(stream.nodelay())); +} + +#[test] +#[cfg_attr(target_env = "sgx", ignore)] +fn ttl() { + let ttl = 100; + + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + for resolved in format!("localhost:{}", addr.port()) + .to_socket_addrs() + .unwrap() + { + pool.insert_socket_addr(resolved, ambient_authority()); + } + + let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + t!(pool.bind_existing_tcp_listener(&listener, &addr)); + listener.listen(None).unwrap(); + + t!(listener.set_ttl(ttl)); + assert_eq!(ttl, t!(listener.ttl())); + + let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + let stream = t!(pool.connect_into_tcp_stream(socket, &("localhost", addr.port()))); + + t!(stream.set_ttl(ttl)); + assert_eq!(ttl, t!(stream.ttl())); +} + +#[test] +#[cfg_attr(target_env = "sgx", ignore)] +fn set_nonblocking() { + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + for resolved in format!("localhost:{}", addr.port()) + .to_socket_addrs() + .unwrap() + { + pool.insert_socket_addr(resolved, ambient_authority()); + } + + let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + t!(pool.bind_existing_tcp_listener(&listener, &addr)); + listener.listen(None).unwrap(); + + t!(listener.set_nonblocking(true)); + t!(listener.set_nonblocking(false)); + + let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + let mut stream = t!(pool.connect_into_tcp_stream(socket, &("localhost", addr.port()))); + + t!(stream.set_nonblocking(false)); + t!(stream.set_nonblocking(true)); + + let mut buf = [0]; + match stream.read(&mut buf) { + Ok(_) => panic!("expected error"), + Err(ref e) if e.kind() == ErrorKind::WouldBlock => {} + Err(e) => panic!("unexpected error {}", e), + } +} + +#[test] +#[cfg_attr(target_env = "sgx", ignore)] // FIXME: https://github.com/fortanix/rust-sgx/issues/31 +fn peek() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let (txdone, rxdone) = channel(); + + let srv = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool.bind_existing_tcp_listener(&srv, &addr)); + srv.listen(None).unwrap(); + let _t = thread::spawn(move || { + let mut cl = t!(srv.accept()).0; + cl.write(&[1, 3, 3, 7]).unwrap(); + t!(rxdone.recv()); + }); + + let socket = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let mut c = t!(pool.connect_into_tcp_stream(socket, &addr)); + let mut b = [0; 10]; + for _ in 1..3 { + let len = c.peek(&mut b).unwrap(); + assert_eq!(len, 4); + } + let len = c.read(&mut b).unwrap(); + assert_eq!(len, 4); + + t!(c.set_nonblocking(true)); + match c.peek(&mut b) { + Ok(_) => panic!("expected error"), + Err(ref e) if e.kind() == ErrorKind::WouldBlock => {} + Err(e) => panic!("unexpected error {}", e), + } + t!(txdone.send(())); + }) +} + +#[test] +#[cfg_attr(target_env = "sgx", ignore)] // FIXME: https://github.com/fortanix/rust-sgx/issues/31 +fn connect_timeout_valid() { + let mut pool = Pool::new(); + pool.insert_socket_addr("127.0.0.1:0".parse().unwrap(), ambient_authority()); + let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + pool.bind_existing_tcp_listener(&listener, "127.0.0.1:0") + .unwrap(); + listener.listen(None).unwrap(); + let addr = listener.local_addr().unwrap(); + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + pool.connect_timeout_tcp_stream(&addr, Duration::from_secs(2)) + .unwrap(); +} diff --git a/crates/wasi/src/filesystem/primitives/tests/cap_net_ext_tcp_split.rs b/crates/wasi/src/filesystem/primitives/tests/cap_net_ext_tcp_split.rs new file mode 100644 index 000000000000..17fdae36da50 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/cap_net_ext_tcp_split.rs @@ -0,0 +1,1372 @@ +// This file is derived from Rust's library/std/src/net/tcp/tests.rs at +// revision 377d1a984cd2a53327092b90aa1d8b7e22d1e347. +// +// This is like cap-net-ext-tcp.rs but uses the binder/connecter APIs. + +mod net; + +use cap_net_ext::{AddressFamily, Blocking, PoolExt, TcpListenerExt}; +use cap_std::ambient_authority; +use cap_std::net::*; +use net::{next_test_ip4, next_test_ip6}; +use std::io::prelude::*; +use std::io::{ErrorKind, IoSlice, IoSliceMut}; +use std::sync::mpsc::channel; +use std::time::{Duration, Instant}; +use std::{fmt, thread}; + +fn each_ip(f: &mut dyn FnMut(SocketAddr)) { + f(next_test_ip4()); + f(next_test_ip6()); +} + +macro_rules! t { + ($e:expr) => { + match $e { + Ok(t) => t, + Err(e) => panic!("received error for `{}`: {}", stringify!($e), e), + } + }; +} + +#[test] +fn bind_error() { + let mut pool = Pool::new(); + pool.insert_socket_addr("1.1.1.1:9999".parse().unwrap(), ambient_authority()); + + let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + match pool + .tcp_binder("1.1.1.1:9999") + .unwrap() + .bind_existing_tcp_listener(&listener) + { + Ok(..) => panic!(), + Err(e) => assert_eq!(e.kind(), ErrorKind::AddrNotAvailable), + } +} + +#[test] +fn connect_error() { + let mut pool = Pool::new(); + pool.insert_socket_addr("0.0.0.0:1".parse().unwrap(), ambient_authority()); + + let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + match pool + .tcp_connecter("0.0.0.0:1") + .unwrap() + .connect_into_tcp_stream(socket) + { + Ok(..) => panic!(), + Err(e) => assert!( + e.kind() == ErrorKind::ConnectionRefused + || e.kind() == ErrorKind::InvalidInput + || e.kind() == ErrorKind::AddrInUse + || e.kind() == ErrorKind::AddrNotAvailable + || e.kind() == ErrorKind::TimedOut + // Rust >=1.83 adds `ErrorKind::NetworkUnreachable`. + || e.to_string().contains("network unreachable") + || e.to_string().contains("Network is unreachable"), + "bad error: {} {:?}", + e, + e.kind() + ), + } +} + +#[test] +fn listen_localhost() { + let socket_addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(socket_addr, ambient_authority()); + for resolved in format!("localhost:{}", socket_addr.port()) + .to_socket_addrs() + .unwrap() + { + pool.insert_socket_addr(resolved, ambient_authority()); + } + + let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + t!(pool + .tcp_binder(&socket_addr) + .unwrap() + .bind_existing_tcp_listener(&listener)); + listener.listen(None).unwrap(); + + let _t = thread::spawn(move || { + let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + let mut stream = t!(pool + .tcp_connecter(&("localhost", socket_addr.port())) + .unwrap() + .connect_into_tcp_stream(socket)); + t!(stream.write(&[144])); + }); + + let mut stream = t!(listener.accept()).0; + let mut buf = [0]; + t!(stream.read(&mut buf)); + assert!(buf[0] == 144); +} + +#[test] +fn connect_loopback() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let acceptor = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool + .tcp_binder(&addr) + .unwrap() + .bind_existing_tcp_listener(&acceptor)); + acceptor.listen(None).unwrap(); + + let _t = thread::spawn(move || { + let host = match addr { + SocketAddr::V4(..) => "127.0.0.1", + SocketAddr::V6(..) => "::1", + }; + let socket = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let mut stream = t!(pool + .tcp_connecter(&(host, addr.port())) + .unwrap() + .connect_into_tcp_stream(socket)); + t!(stream.write(&[66])); + }); + + let mut stream = t!(acceptor.accept()).0; + let mut buf = [0]; + t!(stream.read(&mut buf)); + assert!(buf[0] == 66); + }) +} + +#[test] +fn smoke_test() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let acceptor = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool + .tcp_binder(&addr) + .unwrap() + .bind_existing_tcp_listener(&acceptor)); + acceptor.listen(None).unwrap(); + + let (tx, rx) = channel(); + let _t = thread::spawn(move || { + let socket = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let mut stream = t!(pool + .tcp_connecter(&addr) + .unwrap() + .connect_into_tcp_stream(socket)); + t!(stream.write(&[99])); + tx.send(t!(stream.local_addr())).unwrap(); + }); + + let (mut stream, addr) = t!(acceptor.accept()); + let mut buf = [0]; + t!(stream.read(&mut buf)); + assert!(buf[0] == 99); + assert_eq!(addr, t!(rx.recv())); + }) +} + +#[test] +fn read_eof() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let acceptor = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool + .tcp_binder(&addr) + .unwrap() + .bind_existing_tcp_listener(&acceptor)); + acceptor.listen(None).unwrap(); + + let _t = thread::spawn(move || { + let socket = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let _stream = t!(pool + .tcp_connecter(&addr) + .unwrap() + .connect_into_tcp_stream(socket)); + // Close + }); + + let mut stream = t!(acceptor.accept()).0; + let mut buf = [0]; + let nread = t!(stream.read(&mut buf)); + assert_eq!(nread, 0); + let nread = t!(stream.read(&mut buf)); + assert_eq!(nread, 0); + }) +} + +#[test] +fn write_close() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let acceptor = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool + .tcp_binder(&addr) + .unwrap() + .bind_existing_tcp_listener(&acceptor)); + acceptor.listen(None).unwrap(); + + let (tx, rx) = channel(); + let _t = thread::spawn(move || { + let socket = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + drop(t!(pool + .tcp_connecter(&addr) + .unwrap() + .connect_into_tcp_stream(socket))); + tx.send(()).unwrap(); + }); + + let mut stream = t!(acceptor.accept()).0; + rx.recv().unwrap(); + let buf = [0]; + match stream.write(&buf) { + Ok(..) => {} + Err(e) => { + assert!( + e.kind() == ErrorKind::ConnectionReset + || e.kind() == ErrorKind::BrokenPipe + || e.kind() == ErrorKind::ConnectionAborted, + "unknown error: {}", + e + ); + } + } + }) +} + +#[test] +fn multiple_connect_serial() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let max = 10; + let acceptor = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool + .tcp_binder(&addr) + .unwrap() + .bind_existing_tcp_listener(&acceptor)); + acceptor.listen(None).unwrap(); + + let _t = thread::spawn(move || { + for _ in 0..max { + let socket = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let mut stream = t!(pool + .tcp_connecter(&addr) + .unwrap() + .connect_into_tcp_stream(socket)); + t!(stream.write(&[99])); + } + }); + + for stream in acceptor.incoming().take(max) { + let mut stream = t!(stream); + let mut buf = [0]; + t!(stream.read(&mut buf)); + assert_eq!(buf[0], 99); + } + }) +} + +#[test] +fn multiple_connect_interleaved_greedy_schedule() { + const MAX: usize = 10; + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let acceptor = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool + .tcp_binder(&addr) + .unwrap() + .bind_existing_tcp_listener(&acceptor)); + acceptor.listen(None).unwrap(); + + let _t = thread::spawn(move || { + let acceptor = acceptor; + for (i, stream) in acceptor.incoming().enumerate().take(MAX) { + // Start another thread to handle the connection + let _t = thread::spawn(move || { + let mut stream = t!(stream); + let mut buf = [0]; + t!(stream.read(&mut buf)); + assert!(buf[0] == i as u8); + }); + } + }); + + connect(0, addr, pool); + }); + + fn connect(i: usize, addr: SocketAddr, pool: Pool) { + if i == MAX { + return; + } + + let t = thread::spawn(move || { + let socket = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let mut stream = t!(pool + .tcp_connecter(&addr) + .unwrap() + .connect_into_tcp_stream(socket)); + // Connect again before writing + connect(i + 1, addr, pool); + t!(stream.write(&[i as u8])); + }); + t.join().ok().expect("thread panicked"); + } +} + +#[test] +fn multiple_connect_interleaved_lazy_schedule() { + const MAX: usize = 10; + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let acceptor = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool + .tcp_binder(&addr) + .unwrap() + .bind_existing_tcp_listener(&acceptor)); + acceptor.listen(None).unwrap(); + + let _t = thread::spawn(move || { + for stream in acceptor.incoming().take(MAX) { + // Start another thread to handle the connection + let _t = thread::spawn(move || { + let mut stream = t!(stream); + let mut buf = [0]; + t!(stream.read(&mut buf)); + assert!(buf[0] == 99); + }); + } + }); + + connect(0, addr, pool); + }); + + fn connect(i: usize, addr: SocketAddr, pool: Pool) { + if i == MAX { + return; + } + + let t = thread::spawn(move || { + let socket = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let mut stream = t!(pool + .tcp_connecter(&addr) + .unwrap() + .connect_into_tcp_stream(socket)); + connect(i + 1, addr, pool); + t!(stream.write(&[99])); + }); + t.join().ok().expect("thread panicked"); + } +} + +#[test] +fn socket_and_peer_name() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let listener = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool + .tcp_binder(&addr) + .unwrap() + .bind_existing_tcp_listener(&listener)); + listener.listen(None).unwrap(); + let so_name = t!(listener.local_addr()); + assert_eq!(addr, so_name); + let _t = thread::spawn(move || { + t!(listener.accept()); + }); + + let socket = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let stream = t!(pool + .tcp_connecter(&addr) + .unwrap() + .connect_into_tcp_stream(socket)); + assert_eq!(addr, t!(stream.peer_addr())); + }) +} + +#[test] +fn partial_read() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let (tx, rx) = channel(); + let srv = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool + .tcp_binder(&addr) + .unwrap() + .bind_existing_tcp_listener(&srv)); + srv.listen(None).unwrap(); + let _t = thread::spawn(move || { + let mut cl = t!(srv.accept()).0; + cl.write(&[10]).unwrap(); + let mut b = [0]; + t!(cl.read(&mut b)); + tx.send(()).unwrap(); + }); + + let socket = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let mut c = t!(pool + .tcp_connecter(&addr) + .unwrap() + .connect_into_tcp_stream(socket)); + let mut b = [0; 10]; + assert_eq!(c.read(&mut b).unwrap(), 1); + t!(c.write(&[1])); + rx.recv().unwrap(); + }) +} + +#[test] +fn read_vectored() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let srv = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool + .tcp_binder(&addr) + .unwrap() + .bind_existing_tcp_listener(&srv)); + srv.listen(None).unwrap(); + let socket = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let mut s1 = t!(pool + .tcp_connecter(&addr) + .unwrap() + .connect_into_tcp_stream(socket)); + let mut s2 = t!(srv.accept()).0; + + let len = s1.write(&[10, 11, 12]).unwrap(); + assert_eq!(len, 3); + + let mut a = []; + let mut b = [0]; + let mut c = [0; 3]; + let len = t!(s2.read_vectored(&mut [ + IoSliceMut::new(&mut a), + IoSliceMut::new(&mut b), + IoSliceMut::new(&mut c) + ])); + assert!(len > 0); + assert_eq!(b, [10]); + // some implementations don't support readv, so we may only fill the first + // buffer + assert!(len == 1 || c == [11, 12, 0]); + }) +} + +#[test] +fn write_vectored() { + let mut pool = Pool::new(); + + each_ip(&mut |addr| { + pool.insert_socket_addr(addr, ambient_authority()); + + let srv = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool + .tcp_binder(&addr) + .unwrap() + .bind_existing_tcp_listener(&srv)); + srv.listen(None).unwrap(); + let socket = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let mut s1 = t!(pool + .tcp_connecter(&addr) + .unwrap() + .connect_into_tcp_stream(socket)); + let mut s2 = t!(srv.accept()).0; + + let a = []; + let b = [10]; + let c = [11, 12]; + t!(s1.write_vectored(&[IoSlice::new(&a), IoSlice::new(&b), IoSlice::new(&c)])); + + let mut buf = [0; 4]; + let len = t!(s2.read(&mut buf)); + // some implementations don't support writev, so we may only write the first + // buffer + if len == 1 { + assert_eq!(buf, [10, 0, 0, 0]); + } else { + assert_eq!(len, 3); + assert_eq!(buf, [10, 11, 12, 0]); + } + }) +} + +#[test] +fn double_bind() { + let mut pool = Pool::new(); + + each_ip(&mut |addr| { + pool.insert_socket_addr(addr, ambient_authority()); + + let listener1 = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool + .tcp_binder(&addr) + .unwrap() + .bind_existing_tcp_listener(&listener1)); + listener1.listen(None).unwrap(); + let listener2 = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + match pool + .tcp_binder(&addr) + .unwrap() + .bind_existing_tcp_listener(&listener2) + { + Ok(()) => panic!( + "This system (perhaps due to options set by pool.bind_existing_tcp_listener) \ + permits double binding: {:?} and {:?}", + listener1, listener2 + ), + Err(e) => { + assert!( + e.kind() == ErrorKind::ConnectionRefused + || e.kind() == ErrorKind::Other + || e.kind() == ErrorKind::AddrInUse, + "unknown error: {} {:?}", + e, + e.kind() + ); + } + } + }) +} + +#[test] +fn tcp_clone_smoke() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let acceptor = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool + .tcp_binder(&addr) + .unwrap() + .bind_existing_tcp_listener(&acceptor)); + acceptor.listen(None).unwrap(); + + let _t = thread::spawn(move || { + let socket = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let mut s = t!(pool + .tcp_connecter(&addr) + .unwrap() + .connect_into_tcp_stream(socket)); + let mut buf = [0, 0]; + assert_eq!(s.read(&mut buf).unwrap(), 1); + assert_eq!(buf[0], 1); + t!(s.write(&[2])); + }); + + let mut s1 = t!(acceptor.accept()).0; + let s2 = t!(s1.try_clone()); + + let (tx1, rx1) = channel(); + let (tx2, rx2) = channel(); + let _t = thread::spawn(move || { + let mut s2 = s2; + rx1.recv().unwrap(); + t!(s2.write(&[1])); + tx2.send(()).unwrap(); + }); + tx1.send(()).unwrap(); + let mut buf = [0, 0]; + assert_eq!(s1.read(&mut buf).unwrap(), 1); + rx2.recv().unwrap(); + }) +} + +#[test] +fn tcp_clone_two_read() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let acceptor = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool + .tcp_binder(&addr) + .unwrap() + .bind_existing_tcp_listener(&acceptor)); + acceptor.listen(None).unwrap(); + let (tx1, rx) = channel(); + let tx2 = tx1.clone(); + + let _t = thread::spawn(move || { + let socket = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let mut s = t!(pool + .tcp_connecter(&addr) + .unwrap() + .connect_into_tcp_stream(socket)); + t!(s.write(&[1])); + rx.recv().unwrap(); + t!(s.write(&[2])); + rx.recv().unwrap(); + }); + + let mut s1 = t!(acceptor.accept()).0; + let s2 = t!(s1.try_clone()); + + let (done, rx) = channel(); + let _t = thread::spawn(move || { + let mut s2 = s2; + let mut buf = [0, 0]; + t!(s2.read(&mut buf)); + tx2.send(()).unwrap(); + done.send(()).unwrap(); + }); + let mut buf = [0, 0]; + t!(s1.read(&mut buf)); + tx1.send(()).unwrap(); + + rx.recv().unwrap(); + }) +} + +#[test] +fn tcp_clone_two_write() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let acceptor = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool + .tcp_binder(&addr) + .unwrap() + .bind_existing_tcp_listener(&acceptor)); + acceptor.listen(None).unwrap(); + + let _t = thread::spawn(move || { + let socket = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let mut s = t!(pool + .tcp_connecter(&addr) + .unwrap() + .connect_into_tcp_stream(socket)); + let mut buf = [0, 1]; + t!(s.read(&mut buf)); + t!(s.read(&mut buf)); + }); + + let mut s1 = t!(acceptor.accept()).0; + let s2 = t!(s1.try_clone()); + + let (done, rx) = channel(); + let _t = thread::spawn(move || { + let mut s2 = s2; + t!(s2.write(&[1])); + done.send(()).unwrap(); + }); + t!(s1.write(&[2])); + + rx.recv().unwrap(); + }) +} + +#[test] +// FIXME: https://github.com/fortanix/rust-sgx/issues/110 +#[cfg_attr(target_env = "sgx", ignore)] +fn shutdown_smoke() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let a = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool + .tcp_binder(&addr) + .unwrap() + .bind_existing_tcp_listener(&a)); + a.listen(None).unwrap(); + let _t = thread::spawn(move || { + let mut c = t!(a.accept()).0; + let mut b = [0]; + assert_eq!(c.read(&mut b).unwrap(), 0); + t!(c.write(&[1])); + }); + + let socket = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let mut s = t!(pool + .tcp_connecter(&addr) + .unwrap() + .connect_into_tcp_stream(socket)); + t!(s.shutdown(Shutdown::Write)); + assert!(s.write(&[1]).is_err()); + let mut b = [0, 0]; + assert_eq!(t!(s.read(&mut b)), 1); + assert_eq!(b[0], 1); + }) +} + +#[test] +// FIXME: https://github.com/fortanix/rust-sgx/issues/110 +#[cfg_attr(target_env = "sgx", ignore)] +fn close_readwrite_smoke() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let a = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool + .tcp_binder(&addr) + .unwrap() + .bind_existing_tcp_listener(&a)); + a.listen(None).unwrap(); + let (tx, rx) = channel::<()>(); + let _t = thread::spawn(move || { + let _s = t!(a.accept()); + let _ = rx.recv(); + }); + + let mut b = [0]; + let socket = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let mut s = t!(pool + .tcp_connecter(&addr) + .unwrap() + .connect_into_tcp_stream(socket)); + let mut s2 = t!(s.try_clone()); + + // closing should prevent reads/writes + t!(s.shutdown(Shutdown::Write)); + assert!(s.write(&[0]).is_err()); + t!(s.shutdown(Shutdown::Read)); + assert_eq!(s.read(&mut b).unwrap(), 0); + + // closing should affect previous handles + assert!(s2.write(&[0]).is_err()); + assert_eq!(s2.read(&mut b).unwrap(), 0); + + // closing should affect new handles + let mut s3 = t!(s.try_clone()); + assert!(s3.write(&[0]).is_err()); + assert_eq!(s3.read(&mut b).unwrap(), 0); + + // make sure these don't die + let _ = s2.shutdown(Shutdown::Read); + let _ = s2.shutdown(Shutdown::Write); + let _ = s3.shutdown(Shutdown::Read); + let _ = s3.shutdown(Shutdown::Write); + drop(tx); + }) +} + +#[test] +#[cfg(unix)] // test doesn't work on Windows, see #31657 +fn close_read_wakes_up() { + let mut pool = Pool::new(); + + each_ip(&mut |addr| { + pool.insert_socket_addr(addr, ambient_authority()); + + let a = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool + .tcp_binder(&addr) + .unwrap() + .bind_existing_tcp_listener(&a)); + a.listen(None).unwrap(); + let (tx1, rx) = channel::<()>(); + let _t = thread::spawn(move || { + let _s = t!(a.accept()); + let _ = rx.recv(); + }); + + let socket = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let s = t!(pool + .tcp_connecter(&addr) + .unwrap() + .connect_into_tcp_stream(socket)); + let s2 = t!(s.try_clone()); + let (tx, rx) = channel(); + let _t = thread::spawn(move || { + let mut s2 = s2; + assert_eq!(t!(s2.read(&mut [0])), 0); + tx.send(()).unwrap(); + }); + // this should wake up the child thread + t!(s.shutdown(Shutdown::Read)); + + // this test will never finish if the child doesn't wake up + rx.recv().unwrap(); + drop(tx1); + }) +} + +#[test] +fn clone_while_reading() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let accept = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool + .tcp_binder(&addr) + .unwrap() + .bind_existing_tcp_listener(&accept)); + accept.listen(None).unwrap(); + + // Enqueue a thread to write to a socket + let (tx, rx) = channel(); + let (txdone, rxdone) = channel(); + let txdone2 = txdone.clone(); + let _t = thread::spawn(move || { + let socket = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let mut tcp = t!(pool + .tcp_connecter(&addr) + .unwrap() + .connect_into_tcp_stream(socket)); + rx.recv().unwrap(); + t!(tcp.write(&[0])); + txdone2.send(()).unwrap(); + }); + + // Spawn off a reading clone + let tcp = t!(accept.accept()).0; + let tcp2 = t!(tcp.try_clone()); + let txdone3 = txdone.clone(); + let _t = thread::spawn(move || { + let mut tcp2 = tcp2; + t!(tcp2.read(&mut [0])); + txdone3.send(()).unwrap(); + }); + + // Try to ensure that the reading clone is indeed reading + for _ in 0..50 { + thread::yield_now(); + } + + // clone the handle again while it's reading, then let it finish the + // read. + let _ = t!(tcp.try_clone()); + tx.send(()).unwrap(); + rxdone.recv().unwrap(); + rxdone.recv().unwrap(); + }) +} + +#[test] +fn clone_accept_smoke() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let a = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool + .tcp_binder(&addr) + .unwrap() + .bind_existing_tcp_listener(&a)); + a.listen(None).unwrap(); + let a2 = t!(a.try_clone()); + + let p = pool.clone(); + let _t = thread::spawn(move || { + let socket = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let _ = p.connect_into_tcp_stream(socket, &addr); + }); + let p = pool.clone(); + let _t = thread::spawn(move || { + let socket = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let _ = p.connect_into_tcp_stream(socket, &addr); + }); + + t!(a.accept()); + t!(a2.accept()); + }) +} + +#[test] +fn clone_accept_concurrent() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let a = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool + .tcp_binder(&addr) + .unwrap() + .bind_existing_tcp_listener(&a)); + a.listen(None).unwrap(); + let a2 = t!(a.try_clone()); + + let (tx, rx) = channel(); + let tx2 = tx.clone(); + + let _t = thread::spawn(move || { + tx.send(t!(a.accept())).unwrap(); + }); + let _t = thread::spawn(move || { + tx2.send(t!(a2.accept())).unwrap(); + }); + + let p = pool.clone(); + let _t = thread::spawn(move || { + let socket = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let _ = p.connect_into_tcp_stream(socket, &addr); + }); + let p = pool.clone(); + let _t = thread::spawn(move || { + let socket = + TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let _ = p.connect_into_tcp_stream(socket, &addr); + }); + + rx.recv().unwrap(); + rx.recv().unwrap(); + }) +} + +#[test] +fn debug() { + let mut pool = Pool::new(); + + #[cfg(not(target_env = "sgx"))] + fn render_socket_addr<'a>(addr: &'a SocketAddr) -> impl fmt::Debug + 'a { + addr + } + #[cfg(target_env = "sgx")] + fn render_socket_addr<'a>(addr: &'a SocketAddr) -> impl fmt::Debug + 'a { + addr.to_string() + } + + #[cfg(target_env = "sgx")] + use std::os::fortanix_sgx::io::AsRawFd; + #[cfg(unix)] + use std::os::unix::io::AsRawFd; + #[cfg(not(windows))] + fn render_inner(addr: &dyn AsRawFd) -> impl fmt::Debug { + addr.as_raw_fd() + } + #[cfg(windows)] + fn render_inner(addr: &dyn std::os::windows::io::AsRawSocket) -> impl fmt::Debug { + addr.as_raw_socket() + } + + let inner_name = if cfg!(windows) { "socket" } else { "fd" }; + let socket_addr = next_test_ip4(); + pool.insert_socket_addr(socket_addr, ambient_authority()); + for resolved in format!("localhost:{}", socket_addr.port()) + .to_socket_addrs() + .unwrap() + { + pool.insert_socket_addr(resolved, ambient_authority()); + } + + let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + t!(pool + .tcp_binder(&socket_addr) + .unwrap() + .bind_existing_tcp_listener(&listener)); + listener.listen(None).unwrap(); + let compare = format!( + "TcpListener {{ addr: {:?}, {}: {:?} }}", + render_socket_addr(&socket_addr), + inner_name, + render_inner(&listener) + ); + assert_eq!(format!("{:?}", listener), compare); + + let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + let stream = t!(pool + .tcp_connecter(&("localhost", socket_addr.port())) + .unwrap() + .connect_into_tcp_stream(socket)); + let compare = format!( + "TcpStream {{ addr: {:?}, peer: {:?}, {}: {:?} }}", + render_socket_addr(&stream.local_addr().unwrap()), + render_socket_addr(&stream.peer_addr().unwrap()), + inner_name, + render_inner(&stream) + ); + assert_eq!(format!("{:?}", stream), compare); +} + +// FIXME: re-enabled openbsd tests once their socket timeout code +// no longer has rounding errors. +// VxWorks ignores SO_SNDTIMEO. +#[cfg_attr( + any(target_os = "netbsd", target_os = "openbsd", target_os = "vxworks"), + ignore +)] +#[cfg_attr(target_env = "sgx", ignore)] // FIXME: https://github.com/fortanix/rust-sgx/issues/31 +#[test] +fn timeouts() { + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + for resolved in format!("localhost:{}", addr.port()) + .to_socket_addrs() + .unwrap() + { + pool.insert_socket_addr(resolved, ambient_authority()); + } + + let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + t!(pool + .tcp_binder(&addr) + .unwrap() + .bind_existing_tcp_listener(&listener)); + listener.listen(None).unwrap(); + + let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + let stream = t!(pool + .tcp_connecter(&("localhost", addr.port())) + .unwrap() + .connect_into_tcp_stream(socket)); + let dur = Duration::new(15410, 0); + + assert_eq!(None, t!(stream.read_timeout())); + + t!(stream.set_read_timeout(Some(dur))); + assert_eq!(Some(dur), t!(stream.read_timeout())); + + assert_eq!(None, t!(stream.write_timeout())); + + t!(stream.set_write_timeout(Some(dur))); + assert_eq!(Some(dur), t!(stream.write_timeout())); + + t!(stream.set_read_timeout(None)); + assert_eq!(None, t!(stream.read_timeout())); + + t!(stream.set_write_timeout(None)); + assert_eq!(None, t!(stream.write_timeout())); + drop(listener); +} + +#[test] +#[cfg_attr(target_env = "sgx", ignore)] // FIXME: https://github.com/fortanix/rust-sgx/issues/31 +fn test_read_timeout() { + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + for resolved in format!("localhost:{}", addr.port()) + .to_socket_addrs() + .unwrap() + { + pool.insert_socket_addr(resolved, ambient_authority()); + } + + let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + t!(pool + .tcp_binder(&addr) + .unwrap() + .bind_existing_tcp_listener(&listener)); + listener.listen(None).unwrap(); + + let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + let mut stream = t!(pool + .tcp_connecter(&("localhost", addr.port())) + .unwrap() + .connect_into_tcp_stream(socket)); + t!(stream.set_read_timeout(Some(Duration::from_millis(1000)))); + + let mut buf = [0; 10]; + let start = Instant::now(); + let kind = stream + .read_exact(&mut buf) + .err() + .expect("expected error") + .kind(); + assert!( + kind == ErrorKind::WouldBlock || kind == ErrorKind::TimedOut, + "unexpected_error: {:?}", + kind + ); + assert!(start.elapsed() > Duration::from_millis(400)); + drop(listener); +} + +#[test] +#[cfg_attr(target_env = "sgx", ignore)] // FIXME: https://github.com/fortanix/rust-sgx/issues/31 +fn test_read_with_timeout() { + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + for resolved in format!("localhost:{}", addr.port()) + .to_socket_addrs() + .unwrap() + { + pool.insert_socket_addr(resolved, ambient_authority()); + } + + let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + t!(pool + .tcp_binder(&addr) + .unwrap() + .bind_existing_tcp_listener(&listener)); + listener.listen(None).unwrap(); + + let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + let mut stream = t!(pool + .tcp_connecter(&("localhost", addr.port())) + .unwrap() + .connect_into_tcp_stream(socket)); + t!(stream.set_read_timeout(Some(Duration::from_millis(1000)))); + + let mut other_end = t!(listener.accept()).0; + t!(other_end.write_all(b"hello world")); + + let mut buf = [0; 11]; + t!(stream.read(&mut buf)); + assert_eq!(b"hello world", &buf[..]); + + let start = Instant::now(); + let kind = stream + .read_exact(&mut buf) + .err() + .expect("expected error") + .kind(); + assert!( + kind == ErrorKind::WouldBlock || kind == ErrorKind::TimedOut, + "unexpected_error: {:?}", + kind + ); + assert!(start.elapsed() > Duration::from_millis(400)); + drop(listener); +} + +// Ensure the `set_read_timeout` and `set_write_timeout` calls return errors +// when passed zero Durations +#[test] +fn test_timeout_zero_duration() { + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + t!(pool + .tcp_binder(&addr) + .unwrap() + .bind_existing_tcp_listener(&listener)); + listener.listen(None).unwrap(); + let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + let stream = t!(pool + .tcp_connecter(&addr) + .unwrap() + .connect_into_tcp_stream(socket)); + + let result = stream.set_write_timeout(Some(Duration::new(0, 0))); + let err = result.unwrap_err(); + assert_eq!(err.kind(), ErrorKind::InvalidInput); + + let result = stream.set_read_timeout(Some(Duration::new(0, 0))); + let err = result.unwrap_err(); + assert_eq!(err.kind(), ErrorKind::InvalidInput); + + drop(listener); +} + +#[test] +#[cfg_attr(target_env = "sgx", ignore)] +fn nodelay() { + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + for resolved in format!("localhost:{}", addr.port()) + .to_socket_addrs() + .unwrap() + { + pool.insert_socket_addr(resolved, ambient_authority()); + } + + let _listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + t!(pool + .tcp_binder(&addr) + .unwrap() + .bind_existing_tcp_listener(&_listener)); + _listener.listen(None).unwrap(); + + let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + let stream = t!(pool + .tcp_connecter(&("localhost", addr.port())) + .unwrap() + .connect_into_tcp_stream(socket)); + + assert_eq!(false, t!(stream.nodelay())); + t!(stream.set_nodelay(true)); + assert_eq!(true, t!(stream.nodelay())); + t!(stream.set_nodelay(false)); + assert_eq!(false, t!(stream.nodelay())); +} + +#[test] +#[cfg_attr(target_env = "sgx", ignore)] +fn ttl() { + let ttl = 100; + + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + for resolved in format!("localhost:{}", addr.port()) + .to_socket_addrs() + .unwrap() + { + pool.insert_socket_addr(resolved, ambient_authority()); + } + + let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + t!(pool + .tcp_binder(&addr) + .unwrap() + .bind_existing_tcp_listener(&listener)); + listener.listen(None).unwrap(); + + t!(listener.set_ttl(ttl)); + assert_eq!(ttl, t!(listener.ttl())); + + let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + let stream = t!(pool + .tcp_connecter(&("localhost", addr.port())) + .unwrap() + .connect_into_tcp_stream(socket)); + + t!(stream.set_ttl(ttl)); + assert_eq!(ttl, t!(stream.ttl())); +} + +#[test] +#[cfg_attr(target_env = "sgx", ignore)] +fn set_nonblocking() { + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + for resolved in format!("localhost:{}", addr.port()) + .to_socket_addrs() + .unwrap() + { + pool.insert_socket_addr(resolved, ambient_authority()); + } + + let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + t!(pool + .tcp_binder(&addr) + .unwrap() + .bind_existing_tcp_listener(&listener)); + listener.listen(None).unwrap(); + + t!(listener.set_nonblocking(true)); + t!(listener.set_nonblocking(false)); + + let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + let mut stream = t!(pool + .tcp_connecter(&("localhost", addr.port())) + .unwrap() + .connect_into_tcp_stream(socket)); + + t!(stream.set_nonblocking(false)); + t!(stream.set_nonblocking(true)); + + let mut buf = [0]; + match stream.read(&mut buf) { + Ok(_) => panic!("expected error"), + Err(ref e) if e.kind() == ErrorKind::WouldBlock => {} + Err(e) => panic!("unexpected error {}", e), + } +} + +#[test] +#[cfg_attr(target_env = "sgx", ignore)] // FIXME: https://github.com/fortanix/rust-sgx/issues/31 +fn peek() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let (txdone, rxdone) = channel(); + + let srv = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool + .tcp_binder(&addr) + .unwrap() + .bind_existing_tcp_listener(&srv)); + srv.listen(None).unwrap(); + let _t = thread::spawn(move || { + let mut cl = t!(srv.accept()).0; + cl.write(&[1, 3, 3, 7]).unwrap(); + t!(rxdone.recv()); + }); + + let socket = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + let mut c = t!(pool + .tcp_connecter(&addr) + .unwrap() + .connect_into_tcp_stream(socket)); + let mut b = [0; 10]; + for _ in 1..3 { + let len = c.peek(&mut b).unwrap(); + assert_eq!(len, 4); + } + let len = c.read(&mut b).unwrap(); + assert_eq!(len, 4); + + t!(c.set_nonblocking(true)); + match c.peek(&mut b) { + Ok(_) => panic!("expected error"), + Err(ref e) if e.kind() == ErrorKind::WouldBlock => {} + Err(e) => panic!("unexpected error {}", e), + } + t!(txdone.send(())); + }) +} + +#[test] +#[cfg_attr(target_env = "sgx", ignore)] // FIXME: https://github.com/fortanix/rust-sgx/issues/31 +fn connect_timeout_valid() { + let mut pool = Pool::new(); + pool.insert_socket_addr("127.0.0.1:0".parse().unwrap(), ambient_authority()); + let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + pool.tcp_binder("127.0.0.1:0") + .unwrap() + .bind_existing_tcp_listener(&listener) + .unwrap(); + listener.listen(None).unwrap(); + let addr = listener.local_addr().unwrap(); + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + pool.connect_timeout_tcp_stream(&addr, Duration::from_secs(2)) + .unwrap(); +} diff --git a/crates/wasi/src/filesystem/primitives/tests/cap_net_ext_udp.rs b/crates/wasi/src/filesystem/primitives/tests/cap_net_ext_udp.rs new file mode 100644 index 000000000000..68159b951764 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/cap_net_ext_udp.rs @@ -0,0 +1,474 @@ +// This file is derived from Rust's library/std/src/net/udp/tests.rs at +// revision 377d1a984cd2a53327092b90aa1d8b7e22d1e347. + +mod net; +mod sys_common; + +use cap_net_ext::{AddressFamily, Blocking, PoolExt, UdpSocketExt}; +use cap_std::ambient_authority; +use cap_std::net::*; +use net::{next_test_ip4, next_test_ip6}; +use std::io::ErrorKind; +use std::sync::mpsc::channel; +//use sys_common::AsInner; +use std::thread; +use std::time::{Duration, Instant}; + +fn each_ip(f: &mut dyn FnMut(SocketAddr, SocketAddr)) { + f(next_test_ip4(), next_test_ip4()); + f(next_test_ip6(), next_test_ip6()); +} + +macro_rules! t { + ($e:expr) => { + match $e { + Ok(t) => t, + Err(e) => panic!("received error for `{}`: {}", stringify!($e), e), + } + }; +} + +#[test] +fn bind_error() { + let mut pool = Pool::new(); + pool.insert_socket_addr("1.1.1.1:9999".parse().unwrap(), ambient_authority()); + + let socket = UdpSocket::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + match pool.bind_existing_udp_socket(&socket, "1.1.1.1:9999") { + Ok(..) => panic!(), + Err(e) => assert_eq!(e.kind(), ErrorKind::AddrNotAvailable), + } +} + +#[test] +fn socket_smoke_test_ip4() { + each_ip(&mut |server_ip, client_ip| { + let mut client_pool = Pool::new(); + client_pool.insert_socket_addr(client_ip, ambient_authority()); + let mut server_pool = Pool::new(); + server_pool.insert_socket_addr(server_ip, ambient_authority()); + + let (tx1, rx1) = channel(); + let (tx2, rx2) = channel(); + + let p = server_pool.clone(); + let _t = thread::spawn(move || { + let client = + UdpSocket::new(AddressFamily::of_socket_addr(client_ip), Blocking::Yes).unwrap(); + t!(client_pool.bind_existing_udp_socket(&client, &client_ip)); + rx1.recv().unwrap(); + t!(p.send_to_udp_socket_addr(&client, &[99], &server_ip)); + tx2.send(()).unwrap(); + }); + + let server = + UdpSocket::new(AddressFamily::of_socket_addr(server_ip), Blocking::Yes).unwrap(); + t!(server_pool.bind_existing_udp_socket(&server, &server_ip)); + tx1.send(()).unwrap(); + let mut buf = [0]; + let (nread, src) = t!(server.recv_from(&mut buf)); + assert_eq!(nread, 1); + assert_eq!(buf[0], 99); + assert_eq!(src, client_ip); + rx2.recv().unwrap(); + }) +} + +#[test] +fn socket_name() { + each_ip(&mut |addr, _| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let server = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool.bind_existing_udp_socket(&server, &addr)); + assert_eq!(addr, t!(server.local_addr())); + }) +} + +#[test] +fn socket_peer() { + each_ip(&mut |addr1, addr2| { + let mut pool1 = Pool::new(); + pool1.insert_socket_addr(addr1, ambient_authority()); + let mut pool2 = Pool::new(); + pool2.insert_socket_addr(addr2, ambient_authority()); + + let server = UdpSocket::new(AddressFamily::of_socket_addr(addr1), Blocking::Yes).unwrap(); + t!(pool1.bind_existing_udp_socket(&server, &addr1)); + assert_eq!( + server.peer_addr().unwrap_err().kind(), + ErrorKind::NotConnected + ); + t!(pool2.connect_existing_udp_socket(&server, &addr2)); + assert_eq!(addr2, t!(server.peer_addr())); + }) +} + +#[test] +fn udp_clone_smoke() { + each_ip(&mut |addr1, addr2| { + let mut pool1 = Pool::new(); + pool1.insert_socket_addr(addr1, ambient_authority()); + let mut pool2 = Pool::new(); + pool2.insert_socket_addr(addr2, ambient_authority()); + + let sock1 = UdpSocket::new(AddressFamily::of_socket_addr(addr1), Blocking::Yes).unwrap(); + t!(pool1.bind_existing_udp_socket(&sock1, &addr1)); + let sock2 = UdpSocket::new(AddressFamily::of_socket_addr(addr2), Blocking::Yes).unwrap(); + t!(pool2.bind_existing_udp_socket(&sock2, &addr2)); + + let _t = thread::spawn(move || { + let mut buf = [0, 0]; + assert_eq!(sock2.recv_from(&mut buf).unwrap(), (1, addr1)); + assert_eq!(buf[0], 1); + t!(pool1.send_to_udp_socket_addr(&sock2, &[2], &addr1)); + }); + + let sock3 = t!(sock1.try_clone()); + + let (tx1, rx1) = channel(); + let (tx2, rx2) = channel(); + let p = pool2.clone(); + let _t = thread::spawn(move || { + rx1.recv().unwrap(); + t!(p.send_to_udp_socket_addr(&sock3, &[1], &addr2)); + tx2.send(()).unwrap(); + }); + tx1.send(()).unwrap(); + let mut buf = [0, 0]; + assert_eq!(sock1.recv_from(&mut buf).unwrap(), (1, addr2)); + rx2.recv().unwrap(); + }) +} + +#[test] +fn udp_clone_two_read() { + each_ip(&mut |addr1, addr2| { + let mut pool1 = Pool::new(); + pool1.insert_socket_addr(addr1, ambient_authority()); + let mut pool2 = Pool::new(); + pool2.insert_socket_addr(addr2, ambient_authority()); + + let sock1 = UdpSocket::new(AddressFamily::of_socket_addr(addr1), Blocking::Yes).unwrap(); + t!(pool1.bind_existing_udp_socket(&sock1, &addr1)); + let sock2 = UdpSocket::new(AddressFamily::of_socket_addr(addr2), Blocking::Yes).unwrap(); + t!(pool2.bind_existing_udp_socket(&sock2, &addr2)); + let (tx1, rx) = channel(); + let tx2 = tx1.clone(); + + let _t = thread::spawn(move || { + t!(pool1.send_to_udp_socket_addr(&sock2, &[1], &addr1)); + rx.recv().unwrap(); + t!(pool1.send_to_udp_socket_addr(&sock2, &[2], &addr1)); + rx.recv().unwrap(); + }); + + let sock3 = t!(sock1.try_clone()); + + let (done, rx) = channel(); + let _t = thread::spawn(move || { + let mut buf = [0, 0]; + t!(sock3.recv_from(&mut buf)); + tx2.send(()).unwrap(); + done.send(()).unwrap(); + }); + let mut buf = [0, 0]; + t!(sock1.recv_from(&mut buf)); + tx1.send(()).unwrap(); + + rx.recv().unwrap(); + }) +} + +#[test] +fn udp_clone_two_write() { + each_ip(&mut |addr1, addr2| { + let mut pool1 = Pool::new(); + pool1.insert_socket_addr(addr1, ambient_authority()); + let mut pool2 = Pool::new(); + pool2.insert_socket_addr(addr2, ambient_authority()); + + let sock1 = UdpSocket::new(AddressFamily::of_socket_addr(addr1), Blocking::Yes).unwrap(); + t!(pool1.bind_existing_udp_socket(&sock1, &addr1)); + let sock2 = UdpSocket::new(AddressFamily::of_socket_addr(addr2), Blocking::Yes).unwrap(); + t!(pool2.bind_existing_udp_socket(&sock2, &addr2)); + + let (tx, rx) = channel(); + let (serv_tx, serv_rx) = channel(); + + let _t = thread::spawn(move || { + let mut buf = [0, 1]; + rx.recv().unwrap(); + t!(sock2.recv_from(&mut buf)); + serv_tx.send(()).unwrap(); + }); + + let sock3 = t!(sock1.try_clone()); + + let (done, rx) = channel(); + let tx2 = tx.clone(); + let p = pool2.clone(); + let _t = thread::spawn(move || { + if p.send_to_udp_socket_addr(&sock3, &[1], &addr2).is_ok() { + let _ = tx2.send(()); + } + done.send(()).unwrap(); + }); + if pool2.send_to_udp_socket_addr(&sock1, &[2], &addr2).is_ok() { + let _ = tx.send(()); + } + drop(tx); + + rx.recv().unwrap(); + serv_rx.recv().unwrap(); + }) +} + +/* Disable this test, as it depends on Rust-internal details. +#[test] +fn debug() { + let name = if cfg!(windows) { "socket" } else { "fd" }; + let socket_addr = next_test_ip4(); + + let mut pool = Pool::new(); + + let udpsock_inner = udpsock.0.socket().as_inner(); + let compare = format!("UdpSocket {{ addr: {:?}, {}: {:?} }}", socket_addr, name, udpsock_inner); + assert_eq!(format!("{:?}", udpsock), compare); +} +*/ + +// FIXME: re-enabled openbsd/netbsd tests once their socket timeout code +// no longer has rounding errors. +// VxWorks ignores SO_SNDTIMEO. +#[cfg_attr( + any(target_os = "netbsd", target_os = "openbsd", target_os = "vxworks"), + ignore +)] +#[test] +fn timeouts() { + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let stream = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool.bind_existing_udp_socket(&stream, &addr)); + let dur = Duration::new(15410, 0); + + assert_eq!(None, t!(stream.read_timeout())); + + t!(stream.set_read_timeout(Some(dur))); + assert_eq!(Some(dur), t!(stream.read_timeout())); + + assert_eq!(None, t!(stream.write_timeout())); + + t!(stream.set_write_timeout(Some(dur))); + assert_eq!(Some(dur), t!(stream.write_timeout())); + + t!(stream.set_read_timeout(None)); + assert_eq!(None, t!(stream.read_timeout())); + + t!(stream.set_write_timeout(None)); + assert_eq!(None, t!(stream.write_timeout())); +} + +#[test] +fn test_read_timeout() { + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let stream = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool.bind_existing_udp_socket(&stream, &addr)); + t!(stream.set_read_timeout(Some(Duration::from_millis(1000)))); + + let mut buf = [0; 10]; + + let start = Instant::now(); + loop { + let kind = stream + .recv_from(&mut buf) + .err() + .expect("expected error") + .kind(); + if kind != ErrorKind::Interrupted { + assert!( + kind == ErrorKind::WouldBlock || kind == ErrorKind::TimedOut, + "unexpected_error: {:?}", + kind + ); + break; + } + } + assert!(start.elapsed() > Duration::from_millis(400)); +} + +#[test] +fn test_read_with_timeout() { + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let stream = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool.bind_existing_udp_socket(&stream, &addr)); + t!(stream.set_read_timeout(Some(Duration::from_millis(1000)))); + + t!(pool.send_to_udp_socket_addr(&stream, b"hello world", &addr)); + + let mut buf = [0; 11]; + t!(stream.recv_from(&mut buf)); + assert_eq!(b"hello world", &buf[..]); + + let start = Instant::now(); + loop { + let kind = stream + .recv_from(&mut buf) + .err() + .expect("expected error") + .kind(); + if kind != ErrorKind::Interrupted { + assert!( + kind == ErrorKind::WouldBlock || kind == ErrorKind::TimedOut, + "unexpected_error: {:?}", + kind + ); + break; + } + } + assert!(start.elapsed() > Duration::from_millis(400)); +} + +// Ensure the `set_read_timeout` and `set_write_timeout` calls return errors +// when passed zero Durations +#[test] +fn test_timeout_zero_duration() { + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let socket = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool.bind_existing_udp_socket(&socket, &addr)); + + let result = socket.set_write_timeout(Some(Duration::new(0, 0))); + let err = result.unwrap_err(); + assert_eq!(err.kind(), ErrorKind::InvalidInput); + + let result = socket.set_read_timeout(Some(Duration::new(0, 0))); + let err = result.unwrap_err(); + assert_eq!(err.kind(), ErrorKind::InvalidInput); +} + +#[test] +fn connect_send_recv() { + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let socket = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool.bind_existing_udp_socket(&socket, &addr)); + t!(pool.connect_existing_udp_socket(&socket, addr)); + + t!(socket.send(b"hello world")); + + let mut buf = [0; 11]; + t!(socket.recv(&mut buf)); + assert_eq!(b"hello world", &buf[..]); +} + +#[test] +fn connect_send_peek_recv() { + each_ip(&mut |addr, _| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let socket = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool.bind_existing_udp_socket(&socket, &addr)); + t!(pool.connect_existing_udp_socket(&socket, addr)); + + t!(socket.send(b"hello world")); + + for _ in 1..3 { + let mut buf = [0; 11]; + let size = t!(socket.peek(&mut buf)); + assert_eq!(b"hello world", &buf[..]); + assert_eq!(size, 11); + } + + let mut buf = [0; 11]; + let size = t!(socket.recv(&mut buf)); + assert_eq!(b"hello world", &buf[..]); + assert_eq!(size, 11); + }) +} + +#[test] +fn peek_from() { + each_ip(&mut |addr, _| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let socket = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool.bind_existing_udp_socket(&socket, &addr)); + t!(pool.send_to_udp_socket_addr(&socket, b"hello world", &addr)); + + for _ in 1..3 { + let mut buf = [0; 11]; + let (size, _) = t!(socket.peek_from(&mut buf)); + assert_eq!(b"hello world", &buf[..]); + assert_eq!(size, 11); + } + + let mut buf = [0; 11]; + let (size, _) = t!(socket.recv_from(&mut buf)); + assert_eq!(b"hello world", &buf[..]); + assert_eq!(size, 11); + }) +} + +#[test] +fn ttl() { + let ttl = 100; + + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let stream = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool.bind_existing_udp_socket(&stream, &addr)); + + t!(stream.set_ttl(ttl)); + assert_eq!(ttl, t!(stream.ttl())); +} + +#[test] +fn set_nonblocking() { + each_ip(&mut |addr, _| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let socket = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool.bind_existing_udp_socket(&socket, &addr)); + + t!(socket.set_nonblocking(true)); + t!(socket.set_nonblocking(false)); + + t!(pool.connect_existing_udp_socket(&socket, addr)); + + t!(socket.set_nonblocking(false)); + t!(socket.set_nonblocking(true)); + + let mut buf = [0]; + match socket.recv(&mut buf) { + Ok(_) => panic!("expected error"), + Err(ref e) if e.kind() == ErrorKind::WouldBlock => {} + Err(e) => panic!("unexpected error {}", e), + } + }) +} diff --git a/crates/wasi/src/filesystem/primitives/tests/cap_net_ext_udp_split.rs b/crates/wasi/src/filesystem/primitives/tests/cap_net_ext_udp_split.rs new file mode 100644 index 000000000000..916d6f7c6bb9 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/cap_net_ext_udp_split.rs @@ -0,0 +1,549 @@ +// This file is derived from Rust's library/std/src/net/udp/tests.rs at +// revision 377d1a984cd2a53327092b90aa1d8b7e22d1e347. +// +// This is like cap-net-ext-udp.rs but uses the binder/connecter APIs. + +mod net; +mod sys_common; + +use cap_net_ext::{AddressFamily, Blocking, PoolExt, UdpSocketExt}; +use cap_std::ambient_authority; +use cap_std::net::*; +use net::{next_test_ip4, next_test_ip6}; +use std::io::ErrorKind; +use std::sync::mpsc::channel; +//use sys_common::AsInner; +use std::thread; +use std::time::{Duration, Instant}; + +fn each_ip(f: &mut dyn FnMut(SocketAddr, SocketAddr)) { + f(next_test_ip4(), next_test_ip4()); + f(next_test_ip6(), next_test_ip6()); +} + +macro_rules! t { + ($e:expr) => { + match $e { + Ok(t) => t, + Err(e) => panic!("received error for `{}`: {}", stringify!($e), e), + } + }; +} + +#[test] +fn bind_error() { + let mut pool = Pool::new(); + pool.insert_socket_addr("1.1.1.1:9999".parse().unwrap(), ambient_authority()); + + let socket = UdpSocket::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); + match pool + .udp_binder("1.1.1.1:9999") + .unwrap() + .bind_existing_udp_socket(&socket) + { + Ok(..) => panic!(), + Err(e) => assert_eq!(e.kind(), ErrorKind::AddrNotAvailable), + } +} + +#[test] +fn socket_smoke_test_ip4() { + each_ip(&mut |server_ip, client_ip| { + let mut client_pool = Pool::new(); + client_pool.insert_socket_addr(client_ip, ambient_authority()); + let mut server_pool = Pool::new(); + server_pool.insert_socket_addr(server_ip, ambient_authority()); + + let (tx1, rx1) = channel(); + let (tx2, rx2) = channel(); + + let p = server_pool.clone(); + let _t = thread::spawn(move || { + let client = + UdpSocket::new(AddressFamily::of_socket_addr(client_ip), Blocking::Yes).unwrap(); + t!(client_pool + .udp_binder(&client_ip) + .unwrap() + .bind_existing_udp_socket(&client)); + rx1.recv().unwrap(); + t!(p.send_to_udp_socket_addr(&client, &[99], &server_ip)); + tx2.send(()).unwrap(); + }); + + let server = + UdpSocket::new(AddressFamily::of_socket_addr(server_ip), Blocking::Yes).unwrap(); + t!(server_pool + .udp_binder(&server_ip) + .unwrap() + .bind_existing_udp_socket(&server)); + tx1.send(()).unwrap(); + let mut buf = [0]; + let (nread, src) = t!(server.recv_from(&mut buf)); + assert_eq!(nread, 1); + assert_eq!(buf[0], 99); + assert_eq!(src, client_ip); + rx2.recv().unwrap(); + }) +} + +#[test] +fn socket_name() { + each_ip(&mut |addr, _| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let server = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool + .udp_binder(&addr) + .unwrap() + .bind_existing_udp_socket(&server)); + assert_eq!(addr, t!(server.local_addr())); + }) +} + +#[test] +fn socket_peer() { + each_ip(&mut |addr1, addr2| { + let mut pool1 = Pool::new(); + pool1.insert_socket_addr(addr1, ambient_authority()); + let mut pool2 = Pool::new(); + pool2.insert_socket_addr(addr2, ambient_authority()); + + let server = UdpSocket::new(AddressFamily::of_socket_addr(addr1), Blocking::Yes).unwrap(); + t!(pool1 + .udp_binder(&addr1) + .unwrap() + .bind_existing_udp_socket(&server)); + assert_eq!( + server.peer_addr().unwrap_err().kind(), + ErrorKind::NotConnected + ); + t!(pool2 + .udp_connecter(&addr2) + .unwrap() + .connect_existing_udp_socket(&server)); + assert_eq!(addr2, t!(server.peer_addr())); + }) +} + +#[test] +fn udp_clone_smoke() { + each_ip(&mut |addr1, addr2| { + let mut pool1 = Pool::new(); + pool1.insert_socket_addr(addr1, ambient_authority()); + let mut pool2 = Pool::new(); + pool2.insert_socket_addr(addr2, ambient_authority()); + + let sock1 = UdpSocket::new(AddressFamily::of_socket_addr(addr1), Blocking::Yes).unwrap(); + t!(pool1 + .udp_binder(&addr1) + .unwrap() + .bind_existing_udp_socket(&sock1)); + let sock2 = UdpSocket::new(AddressFamily::of_socket_addr(addr2), Blocking::Yes).unwrap(); + t!(pool2 + .udp_binder(&addr2) + .unwrap() + .bind_existing_udp_socket(&sock2)); + + let _t = thread::spawn(move || { + let mut buf = [0, 0]; + assert_eq!(sock2.recv_from(&mut buf).unwrap(), (1, addr1)); + assert_eq!(buf[0], 1); + t!(pool1.send_to_udp_socket_addr(&sock2, &[2], &addr1)); + }); + + let sock3 = t!(sock1.try_clone()); + + let (tx1, rx1) = channel(); + let (tx2, rx2) = channel(); + let p = pool2.clone(); + let _t = thread::spawn(move || { + rx1.recv().unwrap(); + t!(p.send_to_udp_socket_addr(&sock3, &[1], &addr2)); + tx2.send(()).unwrap(); + }); + tx1.send(()).unwrap(); + let mut buf = [0, 0]; + assert_eq!(sock1.recv_from(&mut buf).unwrap(), (1, addr2)); + rx2.recv().unwrap(); + }) +} + +#[test] +fn udp_clone_two_read() { + each_ip(&mut |addr1, addr2| { + let mut pool1 = Pool::new(); + pool1.insert_socket_addr(addr1, ambient_authority()); + let mut pool2 = Pool::new(); + pool2.insert_socket_addr(addr2, ambient_authority()); + + let sock1 = UdpSocket::new(AddressFamily::of_socket_addr(addr1), Blocking::Yes).unwrap(); + t!(pool1 + .udp_binder(&addr1) + .unwrap() + .bind_existing_udp_socket(&sock1)); + let sock2 = UdpSocket::new(AddressFamily::of_socket_addr(addr2), Blocking::Yes).unwrap(); + t!(pool2 + .udp_binder(&addr2) + .unwrap() + .bind_existing_udp_socket(&sock2)); + let (tx1, rx) = channel(); + let tx2 = tx1.clone(); + + let _t = thread::spawn(move || { + t!(pool1.send_to_udp_socket_addr(&sock2, &[1], &addr1)); + rx.recv().unwrap(); + t!(pool1.send_to_udp_socket_addr(&sock2, &[2], &addr1)); + rx.recv().unwrap(); + }); + + let sock3 = t!(sock1.try_clone()); + + let (done, rx) = channel(); + let _t = thread::spawn(move || { + let mut buf = [0, 0]; + t!(sock3.recv_from(&mut buf)); + tx2.send(()).unwrap(); + done.send(()).unwrap(); + }); + let mut buf = [0, 0]; + t!(sock1.recv_from(&mut buf)); + tx1.send(()).unwrap(); + + rx.recv().unwrap(); + }) +} + +#[test] +fn udp_clone_two_write() { + each_ip(&mut |addr1, addr2| { + let mut pool1 = Pool::new(); + pool1.insert_socket_addr(addr1, ambient_authority()); + let mut pool2 = Pool::new(); + pool2.insert_socket_addr(addr2, ambient_authority()); + + let sock1 = UdpSocket::new(AddressFamily::of_socket_addr(addr1), Blocking::Yes).unwrap(); + t!(pool1 + .udp_binder(&addr1) + .unwrap() + .bind_existing_udp_socket(&sock1)); + let sock2 = UdpSocket::new(AddressFamily::of_socket_addr(addr2), Blocking::Yes).unwrap(); + t!(pool2 + .udp_binder(&addr2) + .unwrap() + .bind_existing_udp_socket(&sock2)); + + let (tx, rx) = channel(); + let (serv_tx, serv_rx) = channel(); + + let _t = thread::spawn(move || { + let mut buf = [0, 1]; + rx.recv().unwrap(); + t!(sock2.recv_from(&mut buf)); + serv_tx.send(()).unwrap(); + }); + + let sock3 = t!(sock1.try_clone()); + + let (done, rx) = channel(); + let tx2 = tx.clone(); + let p = pool2.clone(); + let _t = thread::spawn(move || { + if p.send_to_udp_socket_addr(&sock3, &[1], &addr2).is_ok() { + let _ = tx2.send(()); + } + done.send(()).unwrap(); + }); + if pool2.send_to_udp_socket_addr(&sock1, &[2], &addr2).is_ok() { + let _ = tx.send(()); + } + drop(tx); + + rx.recv().unwrap(); + serv_rx.recv().unwrap(); + }) +} + +/* Disable this test, as it depends on Rust-internal details. +#[test] +fn debug() { + let name = if cfg!(windows) { "socket" } else { "fd" }; + let socket_addr = next_test_ip4(); + + let mut pool = Pool::new(); + + let udpsock_inner = udpsock.0.socket().as_inner(); + let compare = format!("UdpSocket {{ addr: {:?}, {}: {:?} }}", socket_addr, name, udpsock_inner); + assert_eq!(format!("{:?}", udpsock), compare); +} +*/ + +// FIXME: re-enabled openbsd/netbsd tests once their socket timeout code +// no longer has rounding errors. +// VxWorks ignores SO_SNDTIMEO. +#[cfg_attr( + any(target_os = "netbsd", target_os = "openbsd", target_os = "vxworks"), + ignore +)] +#[test] +fn timeouts() { + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let stream = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool + .udp_binder(&addr) + .unwrap() + .bind_existing_udp_socket(&stream)); + let dur = Duration::new(15410, 0); + + assert_eq!(None, t!(stream.read_timeout())); + + t!(stream.set_read_timeout(Some(dur))); + assert_eq!(Some(dur), t!(stream.read_timeout())); + + assert_eq!(None, t!(stream.write_timeout())); + + t!(stream.set_write_timeout(Some(dur))); + assert_eq!(Some(dur), t!(stream.write_timeout())); + + t!(stream.set_read_timeout(None)); + assert_eq!(None, t!(stream.read_timeout())); + + t!(stream.set_write_timeout(None)); + assert_eq!(None, t!(stream.write_timeout())); +} + +#[test] +fn test_read_timeout() { + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let stream = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool + .udp_binder(&addr) + .unwrap() + .bind_existing_udp_socket(&stream)); + t!(stream.set_read_timeout(Some(Duration::from_millis(1000)))); + + let mut buf = [0; 10]; + + let start = Instant::now(); + loop { + let kind = stream + .recv_from(&mut buf) + .err() + .expect("expected error") + .kind(); + if kind != ErrorKind::Interrupted { + assert!( + kind == ErrorKind::WouldBlock || kind == ErrorKind::TimedOut, + "unexpected_error: {:?}", + kind + ); + break; + } + } + assert!(start.elapsed() > Duration::from_millis(400)); +} + +#[test] +fn test_read_with_timeout() { + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let stream = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool + .udp_binder(&addr) + .unwrap() + .bind_existing_udp_socket(&stream)); + t!(stream.set_read_timeout(Some(Duration::from_millis(1000)))); + + t!(pool.send_to_udp_socket_addr(&stream, b"hello world", &addr)); + + let mut buf = [0; 11]; + t!(stream.recv_from(&mut buf)); + assert_eq!(b"hello world", &buf[..]); + + let start = Instant::now(); + loop { + let kind = stream + .recv_from(&mut buf) + .err() + .expect("expected error") + .kind(); + if kind != ErrorKind::Interrupted { + assert!( + kind == ErrorKind::WouldBlock || kind == ErrorKind::TimedOut, + "unexpected_error: {:?}", + kind + ); + break; + } + } + assert!(start.elapsed() > Duration::from_millis(400)); +} + +// Ensure the `set_read_timeout` and `set_write_timeout` calls return errors +// when passed zero Durations +#[test] +fn test_timeout_zero_duration() { + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let socket = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool + .udp_binder(&addr) + .unwrap() + .bind_existing_udp_socket(&socket)); + + let result = socket.set_write_timeout(Some(Duration::new(0, 0))); + let err = result.unwrap_err(); + assert_eq!(err.kind(), ErrorKind::InvalidInput); + + let result = socket.set_read_timeout(Some(Duration::new(0, 0))); + let err = result.unwrap_err(); + assert_eq!(err.kind(), ErrorKind::InvalidInput); +} + +#[test] +fn connect_send_recv() { + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let socket = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool + .udp_binder(&addr) + .unwrap() + .bind_existing_udp_socket(&socket)); + t!(pool + .udp_connecter(addr) + .unwrap() + .connect_existing_udp_socket(&socket)); + + t!(socket.send(b"hello world")); + + let mut buf = [0; 11]; + t!(socket.recv(&mut buf)); + assert_eq!(b"hello world", &buf[..]); +} + +#[test] +fn connect_send_peek_recv() { + each_ip(&mut |addr, _| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let socket = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool + .udp_binder(&addr) + .unwrap() + .bind_existing_udp_socket(&socket)); + t!(pool + .udp_connecter(addr) + .unwrap() + .connect_existing_udp_socket(&socket)); + + t!(socket.send(b"hello world")); + + for _ in 1..3 { + let mut buf = [0; 11]; + let size = t!(socket.peek(&mut buf)); + assert_eq!(b"hello world", &buf[..]); + assert_eq!(size, 11); + } + + let mut buf = [0; 11]; + let size = t!(socket.recv(&mut buf)); + assert_eq!(b"hello world", &buf[..]); + assert_eq!(size, 11); + }) +} + +#[test] +fn peek_from() { + each_ip(&mut |addr, _| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let socket = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool + .udp_binder(&addr) + .unwrap() + .bind_existing_udp_socket(&socket)); + t!(pool.send_to_udp_socket_addr(&socket, b"hello world", &addr)); + + for _ in 1..3 { + let mut buf = [0; 11]; + let (size, _) = t!(socket.peek_from(&mut buf)); + assert_eq!(b"hello world", &buf[..]); + assert_eq!(size, 11); + } + + let mut buf = [0; 11]; + let (size, _) = t!(socket.recv_from(&mut buf)); + assert_eq!(b"hello world", &buf[..]); + assert_eq!(size, 11); + }) +} + +#[test] +fn ttl() { + let ttl = 100; + + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let stream = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool + .udp_binder(&addr) + .unwrap() + .bind_existing_udp_socket(&stream)); + + t!(stream.set_ttl(ttl)); + assert_eq!(ttl, t!(stream.ttl())); +} + +#[test] +fn set_nonblocking() { + each_ip(&mut |addr, _| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let socket = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); + t!(pool + .udp_binder(&addr) + .unwrap() + .bind_existing_udp_socket(&socket)); + + t!(socket.set_nonblocking(true)); + t!(socket.set_nonblocking(false)); + + t!(pool + .udp_connecter(addr) + .unwrap() + .connect_existing_udp_socket(&socket)); + + t!(socket.set_nonblocking(false)); + t!(socket.set_nonblocking(true)); + + let mut buf = [0]; + match socket.recv(&mut buf) { + Ok(_) => panic!("expected error"), + Err(ref e) if e.kind() == ErrorKind::WouldBlock => {} + Err(e) => panic!("unexpected error {}", e), + } + }) +} diff --git a/crates/wasi/src/filesystem/primitives/tests/dir_entry_ext.rs b/crates/wasi/src/filesystem/primitives/tests/dir_entry_ext.rs new file mode 100644 index 000000000000..5b36b6a457f2 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/dir_entry_ext.rs @@ -0,0 +1,33 @@ +// This file contains tests for `cap_fs_ext::FileTypeExt`. + +#![cfg_attr(all(windows, windows_by_handle), feature(windows_by_handle))] + +#[macro_use] +mod sys_common; + +use sys_common::io::tmpdir; + +#[test] +fn test_dir_entry_ext() { + let tmpdir = tmpdir(); + check!(tmpdir.create("a")); + + // First try with the regular `metadata()`. All nones. + #[cfg(all(windows, windows_by_handle))] + for entry in check!(tmpdir.entries()) { + use cap_std::fs::MetadataExt; + let entry = check!(entry); + assert!(check!(entry.metadata()).volume_serial_number().is_none()); + assert!(check!(entry.metadata()).number_of_links().is_none()); + assert!(check!(entry.metadata()).file_index().is_none()); + } + + // Now try with `full_metadata()`. + for entry in check!(tmpdir.entries()) { + use cap_fs_ext::{DirEntryExt, MetadataExt}; + let entry = check!(entry); + check!(entry.full_metadata()).dev(); + check!(entry.full_metadata()).nlink(); + check!(entry.full_metadata()).ino(); + } +} diff --git a/crates/wasi/src/filesystem/primitives/tests/dir_ext.rs b/crates/wasi/src/filesystem/primitives/tests/dir_ext.rs new file mode 100644 index 000000000000..99d376bc0dac --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/dir_ext.rs @@ -0,0 +1,111 @@ +#[macro_use] +mod sys_common; + +use cap_fs_ext::DirExt; +use cap_tempfile::{ambient_authority, TempDir}; + +use sys_common::symlink_supported; + +#[test] +fn remove_file() { + let tempdir = TempDir::new(ambient_authority()).expect("create tempdir"); + let file = tempdir.create("file").expect("create file to delete"); + drop(file); + tempdir.remove_file_or_symlink("file").expect("delete file"); + assert!(!tempdir.exists("file"), "deletion worked"); +} + +#[test] +fn remove_symlink_to_file() { + if !symlink_supported() { + return; + } + + let tempdir = TempDir::new(ambient_authority()).expect("create tempdir"); + let target = tempdir.create("target").expect("create target file"); + drop(target); + tempdir.symlink("target", "link").expect("create symlink"); + assert!(tempdir.exists("link"), "link exists"); + tempdir + .remove_file_or_symlink("link") + .expect("delete symlink"); + assert!(!tempdir.exists("link"), "link deleted"); + assert!(tempdir.exists("target"), "target not deleted"); +} + +#[test] +fn remove_symlink_to_dir() { + if !symlink_supported() { + return; + } + + let tempdir = TempDir::new(ambient_authority()).expect("create tempdir"); + { + let _target = tempdir.create_dir("target").expect("create target dir"); + } + tempdir.symlink("target", "link").expect("create symlink"); + assert!(tempdir.exists("link"), "link exists"); + tempdir + .remove_file_or_symlink("link") + .expect("delete symlink"); + assert!(!tempdir.exists("link"), "link deleted"); + assert!(tempdir.exists("target"), "target not deleted"); +} + +#[test] +fn do_not_remove_dir() { + let tempdir = TempDir::new(ambient_authority()).expect("create tempdir"); + { + let _subdir = tempdir.create_dir("subdir").expect("create dir"); + } + assert!(tempdir.exists("subdir"), "subdir created"); + tempdir + .remove_file_or_symlink("subdir") + .expect_err("should not delete empty directory"); + assert!(tempdir.exists("subdir"), "subdir not deleted"); +} + +#[test] +fn test_set_symlink_permissions() { + use crate::sys_common::io::tmpdir; + use cap_fs_ext::DirExt; + + let tmpdir = tmpdir(); + check!(tmpdir.create_dir("dir")); + let _file = check!(tmpdir.create("file")); + let _other = check!(tmpdir.create("other")); + check!(tmpdir.symlink_file("file", "symlink")); + + let permissions = tmpdir.metadata("other").unwrap().permissions(); + assert!(!permissions.readonly()); + let mut permissions = tmpdir.metadata("file").unwrap().permissions(); + assert!(!permissions.readonly()); + + permissions.set_readonly(true); + + match tmpdir.set_symlink_permissions("symlink", permissions.clone()) { + Ok(()) => {} + Err(err) if err.raw_os_error() == Some(libc::EOPNOTSUPP) => return, + Err(err) => Err(err).unwrap(), + } + tmpdir + .set_symlink_permissions("other", permissions.clone()) + .unwrap(); + + assert!(!tmpdir.metadata("file").unwrap().permissions().readonly()); + assert!(tmpdir.metadata("other").unwrap().permissions().readonly()); +} + +#[test] +fn test_access() { + use crate::sys_common::io::tmpdir; + use cap_fs_ext::AccessType; + use cap_fs_ext::DirExt; + + let tmpdir = tmpdir(); + check!(tmpdir.create_dir("dir")); + let _file = check!(tmpdir.create("file")); + + tmpdir.access("file", AccessType::Exists).unwrap(); + tmpdir.access("nope", AccessType::Exists).unwrap_err(); +} diff --git a/crates/wasi/src/filesystem/primitives/tests/file_type_ext.rs b/crates/wasi/src/filesystem/primitives/tests/file_type_ext.rs new file mode 100644 index 000000000000..94e52f5ddcc7 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/file_type_ext.rs @@ -0,0 +1,37 @@ +// This file contains tests for `cap_fs_ext::FileTypeExt`. + +#[macro_use] +mod sys_common; + +use cap_fs_ext::FileTypeExt; +use sys_common::io::tmpdir; + +#[test] +fn test_file_type_ext() { + let tmpdir = tmpdir(); + let a = check!(tmpdir.create("a")); + + let tmpdir_metadata = check!(tmpdir.dir_metadata()); + let a_metadata = check!(a.metadata()); + + assert!(!tmpdir_metadata.file_type().is_char_device()); + assert!(!a_metadata.file_type().is_char_device()); + + assert!(!tmpdir_metadata.file_type().is_block_device()); + assert!(!a_metadata.file_type().is_block_device()); + + assert!(!tmpdir_metadata.file_type().is_fifo()); + assert!(!a_metadata.file_type().is_fifo()); + + assert!(!tmpdir_metadata.file_type().is_socket()); + assert!(!a_metadata.file_type().is_socket()); + + assert!(tmpdir_metadata.file_type().is_dir()); + assert!(!a_metadata.file_type().is_dir()); + + assert!(!tmpdir_metadata.file_type().is_file()); + assert!(a_metadata.file_type().is_file()); + + assert!(!tmpdir_metadata.file_type().is_symlink()); + assert!(!a_metadata.file_type().is_symlink()); +} diff --git a/crates/wasi/src/filesystem/primitives/tests/fs.rs b/crates/wasi/src/filesystem/primitives/tests/fs.rs new file mode 100644 index 000000000000..2009d4756165 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/fs.rs @@ -0,0 +1,1663 @@ +// This file is derived from Rust's library/std/src/fs/tests.rs at revision +// e4b1d5841494d6eb7f4944c91a057e16b0f0a9ea. + +#![cfg_attr(io_error_uncategorized, feature(io_error_uncategorized))] + +#[macro_use] +mod sys_common; +#[macro_use] +mod sys; + +use std::io::prelude::*; + +#[cfg(target_os = "macos")] +use crate::sys::weak::weak; +use cap_std::ambient_authority; +use cap_std::fs::{self, Dir, OpenOptions}; +#[cfg(target_os = "macos")] +use libc::{c_char, c_int}; +use std::io::{self, ErrorKind, SeekFrom}; +use std::path::{Path, PathBuf}; +use std::str; +use std::sync::Arc; +#[cfg(not(racy_asserts))] // racy asserts are racy +use std::thread; +use std::time::{Duration, Instant}; +use sys_common::io::{tmpdir, TempDir}; +use sys_common::symlink_junction; + +use rand::rngs::StdRng; +use rand::{RngCore, SeedableRng}; + +#[cfg(not(windows))] +fn symlink_contents, Q: AsRef>( + src: P, + tmpdir: &TempDir, + dst: Q, +) -> io::Result<()> { + tmpdir.symlink_contents(src, dst) +} +#[cfg(not(windows))] +fn symlink_dir, Q: AsRef>(src: P, tmpdir: &TempDir, dst: Q) -> io::Result<()> { + tmpdir.symlink(src, dst) +} +#[cfg(not(windows))] +fn symlink_file, Q: AsRef>( + src: P, + tmpdir: &TempDir, + dst: Q, +) -> io::Result<()> { + tmpdir.symlink(src, dst) +} +#[cfg(windows)] +fn symlink_dir, Q: AsRef>(src: P, tmpdir: &TempDir, dst: Q) -> io::Result<()> { + tmpdir.symlink_dir(src, dst) +} +#[cfg(windows)] +fn symlink_file, Q: AsRef>( + src: P, + tmpdir: &TempDir, + dst: Q, +) -> io::Result<()> { + tmpdir.symlink_file(src, dst) +} + +// Several test fail on windows if the user does not have permission to +// create symlinks (the `SeCreateSymbolicLinkPrivilege`). Instead of +// disabling these test on Windows, use this function to test whether we +// have permission, and return otherwise. This way, we still don't run these +// tests most of the time, but at least we do if the user has the right +// permissions. +pub fn got_symlink_permission(tmpdir: &TempDir) -> bool { + if cfg!(unix) { + return true; + } + let link = "some_hopefully_unique_link_name"; + + match symlink_file(r"nonexisting_target", tmpdir, link) { + // ERROR_PRIVILEGE_NOT_HELD = 1314 + Err(ref err) if err.raw_os_error() == Some(1314) => false, + Ok(_) | Err(_) => true, + } +} + +#[cfg(target_os = "macos")] +fn able_to_not_follow_symlinks_while_hard_linking() -> bool { + weak!(fn linkat(c_int, *const c_char, c_int, *const c_char, c_int) -> c_int); + linkat.get().is_some() +} + +#[cfg(not(target_os = "macos"))] +fn able_to_not_follow_symlinks_while_hard_linking() -> bool { + return true; +} + +#[test] +fn file_test_io_smoke_test() { + let message = "it's alright. have a good time"; + let tmpdir = tmpdir(); + let filename = "file_rt_io_file_test.txt"; + { + let mut write_stream = check!(tmpdir.create(filename)); + check!(write_stream.write(message.as_bytes())); + } + { + let mut read_stream = check!(tmpdir.open(filename)); + let mut read_buf = [0; 1028]; + let read_str = match check!(read_stream.read(&mut read_buf)) { + 0 => panic!("shouldn't happen"), + n => str::from_utf8(&read_buf[..n]).unwrap().to_string(), + }; + assert_eq!(read_str, message); + } + check!(tmpdir.remove_file(filename)); +} + +#[test] +fn invalid_path_raises() { + let tmpdir = tmpdir(); + let filename = "file_that_does_not_exist.txt"; + let result = tmpdir.open(filename); + + #[cfg(any(all(unix, not(target_os = "vxworks")), target_os = "wasi"))] + error!(result, "No such file or directory"); + #[cfg(target_os = "vxworks")] + error!(result, "no such file or directory"); + #[cfg(windows)] + error!(result, 2); // ERROR_FILE_NOT_FOUND +} + +#[test] +fn file_test_iounlinking_invalid_path_should_raise_condition() { + let tmpdir = tmpdir(); + let filename = "file_another_file_that_does_not_exist.txt"; + + let result = tmpdir.remove_file(filename); + + #[cfg(any(all(unix, not(target_os = "vxworks")), target_os = "wasi"))] + error!(result, "No such file or directory"); + #[cfg(target_os = "vxworks")] + error!(result, "no such file or directory"); + #[cfg(windows)] + error!(result, 2); // ERROR_FILE_NOT_FOUND +} + +#[test] +fn file_test_io_non_positional_read() { + let message: &str = "ten-four"; + let mut read_mem = [0; 8]; + let tmpdir = tmpdir(); + let filename = "file_rt_io_file_test_positional.txt"; + { + let mut rw_stream = check!(tmpdir.create(filename)); + check!(rw_stream.write(message.as_bytes())); + } + { + let mut read_stream = check!(tmpdir.open(filename)); + { + let read_buf = &mut read_mem[0..4]; + check!(read_stream.read(read_buf)); + } + { + let read_buf = &mut read_mem[4..8]; + check!(read_stream.read(read_buf)); + } + } + check!(tmpdir.remove_file(filename)); + let read_str = str::from_utf8(&read_mem).unwrap(); + assert_eq!(read_str, message); +} + +#[test] +fn file_test_io_seek_and_tell_smoke_test() { + let message = "ten-four"; + let mut read_mem = [0; 4]; + let set_cursor = 4 as u64; + let tell_pos_pre_read; + let tell_pos_post_read; + let tmpdir = tmpdir(); + let filename = "file_rt_io_file_test_seeking.txt"; + { + let mut rw_stream = check!(tmpdir.create(filename)); + check!(rw_stream.write(message.as_bytes())); + } + { + let mut read_stream = check!(tmpdir.open(filename)); + check!(read_stream.seek(SeekFrom::Start(set_cursor))); + tell_pos_pre_read = check!(read_stream.seek(SeekFrom::Current(0))); + check!(read_stream.read(&mut read_mem)); + tell_pos_post_read = check!(read_stream.seek(SeekFrom::Current(0))); + } + check!(tmpdir.remove_file(filename)); + let read_str = str::from_utf8(&read_mem).unwrap(); + assert_eq!(read_str, &message[4..8]); + assert_eq!(tell_pos_pre_read, set_cursor); + assert_eq!(tell_pos_post_read, message.len() as u64); +} + +#[test] +fn file_test_io_seek_and_write() { + let initial_msg = "food-is-yummy"; + let overwrite_msg = "-the-bar!!"; + let final_msg = "foo-the-bar!!"; + let seek_idx = 3; + let mut read_mem = [0; 13]; + let tmpdir = tmpdir(); + let filename = "file_rt_io_file_test_seek_and_write.txt"; + { + let mut rw_stream = check!(tmpdir.create(filename)); + check!(rw_stream.write(initial_msg.as_bytes())); + check!(rw_stream.seek(SeekFrom::Start(seek_idx))); + check!(rw_stream.write(overwrite_msg.as_bytes())); + } + { + let mut read_stream = check!(tmpdir.open(filename)); + check!(read_stream.read(&mut read_mem)); + } + check!(tmpdir.remove_file(filename)); + let read_str = str::from_utf8(&read_mem).unwrap(); + assert!(read_str == final_msg); +} + +#[test] +fn file_test_io_seek_shakedown() { + // 01234567890123 + let initial_msg = "qwer-asdf-zxcv"; + let chunk_one: &str = "qwer"; + let chunk_two: &str = "asdf"; + let chunk_three: &str = "zxcv"; + let mut read_mem = [0; 4]; + let tmpdir = tmpdir(); + let filename = "file_rt_io_file_test_seek_shakedown.txt"; + { + let mut rw_stream = check!(tmpdir.create(filename)); + check!(rw_stream.write(initial_msg.as_bytes())); + } + { + let mut read_stream = check!(tmpdir.open(filename)); + + check!(read_stream.seek(SeekFrom::End(-4))); + check!(read_stream.read(&mut read_mem)); + assert_eq!(str::from_utf8(&read_mem).unwrap(), chunk_three); + + check!(read_stream.seek(SeekFrom::Current(-9))); + check!(read_stream.read(&mut read_mem)); + assert_eq!(str::from_utf8(&read_mem).unwrap(), chunk_two); + + check!(read_stream.seek(SeekFrom::Start(0))); + check!(read_stream.read(&mut read_mem)); + assert_eq!(str::from_utf8(&read_mem).unwrap(), chunk_one); + } + check!(tmpdir.remove_file(filename)); +} + +#[test] +fn file_test_io_eof() { + let tmpdir = tmpdir(); + let filename = "file_rt_io_file_test_eof.txt"; + let mut buf = [0; 256]; + { + let oo = OpenOptions::new() + .create_new(true) + .write(true) + .read(true) + .clone(); + let mut rw = check!(tmpdir.open_with(filename, &oo)); + assert_eq!(check!(rw.read(&mut buf)), 0); + assert_eq!(check!(rw.read(&mut buf)), 0); + } + check!(tmpdir.remove_file(filename)); +} + +#[test] +#[cfg(unix)] +fn file_test_io_read_write_at() { + use cap_std::fs::FileExt; + + let tmpdir = tmpdir(); + let filename = "file_rt_io_file_test_read_write_at.txt"; + let mut buf = [0; 256]; + let write1 = "asdf"; + let write2 = "qwer-"; + let write3 = "-zxcv"; + let content = "qwer-asdf-zxcv"; + { + let oo = OpenOptions::new() + .create_new(true) + .write(true) + .read(true) + .clone(); + let mut rw = check!(tmpdir.open_with(filename, &oo)); + assert_eq!(check!(rw.write_at(write1.as_bytes(), 5)), write1.len()); + assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 0); + assert_eq!(check!(rw.read_at(&mut buf, 5)), write1.len()); + assert_eq!(str::from_utf8(&buf[..write1.len()]), Ok(write1)); + assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 0); + assert_eq!( + check!(rw.read_at(&mut buf[..write2.len()], 0)), + write2.len() + ); + assert_eq!(str::from_utf8(&buf[..write2.len()]), Ok("\0\0\0\0\0")); + assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 0); + assert_eq!(check!(rw.write(write2.as_bytes())), write2.len()); + assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 5); + assert_eq!(check!(rw.read(&mut buf)), write1.len()); + assert_eq!(str::from_utf8(&buf[..write1.len()]), Ok(write1)); + assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9); + assert_eq!( + check!(rw.read_at(&mut buf[..write2.len()], 0)), + write2.len() + ); + assert_eq!(str::from_utf8(&buf[..write2.len()]), Ok(write2)); + assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9); + assert_eq!(check!(rw.write_at(write3.as_bytes(), 9)), write3.len()); + assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9); + } + { + let mut read = check!(tmpdir.open(filename)); + assert_eq!(check!(read.read_at(&mut buf, 0)), content.len()); + assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content)); + assert_eq!(check!(read.seek(SeekFrom::Current(0))), 0); + assert_eq!(check!(read.seek(SeekFrom::End(-5))), 9); + assert_eq!(check!(read.read_at(&mut buf, 0)), content.len()); + assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content)); + assert_eq!(check!(read.seek(SeekFrom::Current(0))), 9); + assert_eq!(check!(read.read(&mut buf)), write3.len()); + assert_eq!(str::from_utf8(&buf[..write3.len()]), Ok(write3)); + assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14); + assert_eq!(check!(read.read_at(&mut buf, 0)), content.len()); + assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content)); + assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14); + assert_eq!(check!(read.read_at(&mut buf, 14)), 0); + assert_eq!(check!(read.read_at(&mut buf, 15)), 0); + assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14); + } + check!(tmpdir.remove_file(filename)); +} + +// Darwin doesn't have a way to change the permissions on a file, relative +// to a directory handle, with no read or write access, without blindly +// following symlinks. +#[test] +#[cfg(unix)] +#[cfg_attr( + any( + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "watchos", + target_os = "visionos", + ), + ignore +)] +fn set_get_unix_permissions() { + use cap_std::fs::PermissionsExt; + + let tmpdir = tmpdir(); + let filename = "set_get_unix_permissions"; + check!(tmpdir.create_dir(filename)); + let mask = 0o7777; + + check!(tmpdir.set_permissions(filename, fs::Permissions::from_mode(0))); + let metadata0 = check!(tmpdir.metadata(filename)); + assert_eq!(mask & metadata0.permissions().mode(), 0); + + check!(tmpdir.set_permissions(filename, fs::Permissions::from_mode(0o1777))); + let metadata1 = check!(tmpdir.metadata(filename)); + #[cfg(any(all(unix, not(target_os = "vxworks")), target_os = "wasi"))] + assert_eq!(mask & metadata1.permissions().mode(), 0o1777); + #[cfg(target_os = "vxworks")] + assert_eq!(mask & metadata1.permissions().mode(), 0o0777); +} + +#[test] +#[cfg(windows)] +fn file_test_io_seek_read_write() { + use cap_std::fs::FileExt; + + let tmpdir = tmpdir(); + let filename = "file_rt_io_file_test_seek_read_write.txt"; + let mut buf = [0; 256]; + let write1 = "asdf"; + let write2 = "qwer-"; + let write3 = "-zxcv"; + let content = "qwer-asdf-zxcv"; + { + let oo = OpenOptions::new() + .create_new(true) + .write(true) + .read(true) + .clone(); + let mut rw = check!(tmpdir.open_with(filename, &oo)); + assert_eq!(check!(rw.seek_write(write1.as_bytes(), 5)), write1.len()); + assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9); + assert_eq!(check!(rw.seek_read(&mut buf, 5)), write1.len()); + assert_eq!(str::from_utf8(&buf[..write1.len()]), Ok(write1)); + assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9); + assert_eq!(check!(rw.seek(SeekFrom::Start(0))), 0); + assert_eq!(check!(rw.write(write2.as_bytes())), write2.len()); + assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 5); + assert_eq!(check!(rw.read(&mut buf)), write1.len()); + assert_eq!(str::from_utf8(&buf[..write1.len()]), Ok(write1)); + assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9); + assert_eq!( + check!(rw.seek_read(&mut buf[..write2.len()], 0)), + write2.len() + ); + assert_eq!(str::from_utf8(&buf[..write2.len()]), Ok(write2)); + assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 5); + assert_eq!(check!(rw.seek_write(write3.as_bytes(), 9)), write3.len()); + assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 14); + } + { + let mut read = check!(tmpdir.open(filename)); + assert_eq!(check!(read.seek_read(&mut buf, 0)), content.len()); + assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content)); + assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14); + assert_eq!(check!(read.seek(SeekFrom::End(-5))), 9); + assert_eq!(check!(read.seek_read(&mut buf, 0)), content.len()); + assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content)); + assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14); + assert_eq!(check!(read.seek(SeekFrom::End(-5))), 9); + assert_eq!(check!(read.read(&mut buf)), write3.len()); + assert_eq!(str::from_utf8(&buf[..write3.len()]), Ok(write3)); + assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14); + assert_eq!(check!(read.seek_read(&mut buf, 0)), content.len()); + assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content)); + assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14); + assert_eq!(check!(read.seek_read(&mut buf, 14)), 0); + assert_eq!(check!(read.seek_read(&mut buf, 15)), 0); + } + check!(tmpdir.remove_file(filename)); +} + +#[test] +fn file_test_stat_is_correct_on_is_file() { + let tmpdir = tmpdir(); + let filename = "file_stat_correct_on_is_file.txt"; + { + let mut opts = OpenOptions::new(); + let mut fs = check!(tmpdir.open_with(filename, opts.read(true).write(true).create(true))); + let msg = "hw"; + fs.write(msg.as_bytes()).unwrap(); + + let fstat_res = check!(fs.metadata()); + assert!(fstat_res.is_file()); + } + let stat_res_fn = check!(tmpdir.metadata(filename)); + assert!(stat_res_fn.is_file()); + check!(tmpdir.remove_file(filename)); +} + +#[test] +fn file_test_stat_is_correct_on_is_dir() { + let tmpdir = tmpdir(); + let filename = "file_stat_correct_on_is_dir"; + check!(tmpdir.create_dir(filename)); + let stat_res_fn = check!(tmpdir.metadata(filename)); + assert!(stat_res_fn.is_dir()); + check!(tmpdir.remove_dir(filename)); +} + +#[test] +fn file_test_fileinfo_false_when_checking_is_file_on_a_directory() { + let tmpdir = tmpdir(); + let dir = "fileinfo_false_on_dir"; + check!(tmpdir.create_dir(dir)); + assert!(!tmpdir.is_file(dir)); + check!(tmpdir.remove_dir(dir)); +} + +#[test] +fn file_test_fileinfo_check_exists_before_and_after_file_creation() { + let tmpdir = tmpdir(); + let file = "fileinfo_check_exists_b_and_a.txt"; + check!(check!(tmpdir.create(file)).write(b"foo")); + assert!(tmpdir.exists(file)); + check!(tmpdir.remove_file(file)); + assert!(!tmpdir.exists(file)); +} + +#[test] +fn file_test_directoryinfo_check_exists_before_and_after_mkdir() { + let tmpdir = tmpdir(); + let dir = "before_and_after_dir"; + assert!(!tmpdir.exists(dir)); + check!(tmpdir.create_dir(dir)); + assert!(tmpdir.exists(dir)); + assert!(tmpdir.is_dir(dir)); + check!(tmpdir.remove_dir(dir)); + assert!(!tmpdir.exists(dir)); +} + +#[test] +fn file_test_directoryinfo_readdir() { + let tmpdir = tmpdir(); + let dir = "di_readdir"; + check!(tmpdir.create_dir(dir)); + let prefix = "foo"; + for n in 0..3 { + let f = format!("{}.txt", n); + let mut w = check!(tmpdir.create(&f)); + let msg_str = format!("{}{}", prefix, n.to_string()); + let msg = msg_str.as_bytes(); + check!(w.write(msg)); + } + let files = check!(tmpdir.read_dir(dir)); + let mut mem = [0; 4]; + for f in files { + let f = f.unwrap().file_name(); + { + check!(check!(tmpdir.open(&f)).read(&mut mem)); + let read_str = str::from_utf8(&mem).unwrap(); + let expected = format!("{}{}", prefix, f.to_str().unwrap()); + assert_eq!(expected, read_str); + } + check!(tmpdir.remove_file(&f)); + } + check!(tmpdir.remove_dir(dir)); +} + +#[test] +fn file_create_new_already_exists_error() { + let tmpdir = tmpdir(); + let file = "file_create_new_error_exists"; + check!(tmpdir.create(file)); + let e = tmpdir + .open_with(file, &fs::OpenOptions::new().write(true).create_new(true)) + .unwrap_err(); + assert_eq!(e.kind(), ErrorKind::AlreadyExists); +} + +#[test] +fn mkdir_path_already_exists_error() { + let tmpdir = tmpdir(); + let dir = "mkdir_error_twice"; + check!(tmpdir.create_dir(dir)); + let e = tmpdir.create_dir(dir).unwrap_err(); + assert_eq!(e.kind(), ErrorKind::AlreadyExists); +} + +#[test] +fn recursive_mkdir() { + let tmpdir = tmpdir(); + let dir = "d1/d2"; + check!(tmpdir.create_dir_all(dir)); + assert!(tmpdir.is_dir(dir)); +} + +#[test] +fn recursive_mkdir_failure() { + let tmpdir = tmpdir(); + let dir = "d1"; + let file = "f1"; + + check!(tmpdir.create_dir_all(&dir)); + check!(tmpdir.create(&file)); + + let result = tmpdir.create_dir_all(&file); + + assert!(result.is_err()); +} + +#[test] +#[cfg(not(racy_asserts))] // racy asserts are racy +fn concurrent_recursive_mkdir() { + for _ in 0..100 { + let dir = tmpdir(); + let mut name = PathBuf::from("a"); + for _ in 0..40 { + name = name.join("a"); + } + let mut join = vec![]; + for _ in 0..8 { + let dir = check!(dir.try_clone()); + let name = name.clone(); + join.push(thread::spawn(move || { + check!(dir.create_dir_all(&name)); + })) + } + + // No `Display` on result of `join()` + join.drain(..).map(|join| join.join().unwrap()).count(); + } +} + +#[test] +fn recursive_mkdir_slash() { + let tmpdir = tmpdir(); + error_contains!( + tmpdir.create_dir_all(Path::new("/")), + "a path led outside of the filesystem" + ); +} + +#[test] +fn recursive_mkdir_dot() { + let tmpdir = tmpdir(); + check!(tmpdir.create_dir_all(Path::new("."))); +} + +#[test] +fn recursive_mkdir_empty() { + let tmpdir = tmpdir(); + check!(tmpdir.create_dir_all(Path::new(""))); +} + +#[test] +fn recursive_rmdir() { + let tmpdir = tmpdir(); + let d1 = PathBuf::from("d1"); + let dt = d1.join("t"); + let dtt = dt.join("t"); + let d2 = PathBuf::from("d2"); + let canary = d2.join("do_not_delete"); + check!(tmpdir.create_dir_all(&dtt)); + check!(tmpdir.create_dir_all(&d2)); + check!(check!(tmpdir.create(&canary)).write(b"foo")); + check!(symlink_junction(d2, &tmpdir, &dt.join("d2"))); + let _ = symlink_file(&canary, &tmpdir, &d1.join("canary")); + check!(tmpdir.remove_dir_all(&d1)); + + assert!(!tmpdir.is_dir(d1)); + assert!(tmpdir.exists(canary)); +} + +// See the comments on `create_dir_all_with_junctions` about Windows. +#[test] +#[cfg_attr(windows, ignore)] +fn recursive_rmdir_of_symlink() { + // test we do not recursively delete a symlink but only dirs. + let tmpdir = tmpdir(); + let link = "d1"; + let dir = "d2"; + let canary = "do_not_delete"; + check!(tmpdir.create_dir_all(&dir)); + check!(check!(tmpdir.create(&canary)).write(b"foo")); + check!(symlink_junction(&dir, &tmpdir, link)); + check!(tmpdir.remove_dir_all(link)); + + assert!(!tmpdir.is_dir(link)); + assert!(tmpdir.exists(canary)); +} + +#[test] +fn recursive_rmdir_of_file_fails() { + // test we do not delete a directly specified file. + let tmpdir = tmpdir(); + let canary = "do_not_delete"; + check!(check!(tmpdir.create(canary)).write(b"foo")); + let result = tmpdir.remove_dir_all(canary); + #[cfg(unix)] + error!(result, "Not a directory"); + #[cfg(windows)] + error!(result, 267); // ERROR_DIRECTORY - The directory name is invalid. + assert!(result.is_err()); + assert!(tmpdir.exists(canary)); +} + +#[test] +// only Windows makes a distinction between file and directory symlinks. +#[cfg(windows)] +fn recursive_rmdir_of_file_symlink() { + let tmpdir = tmpdir(); + if !got_symlink_permission(&tmpdir) { + return; + }; + + let f1 = "f1"; + let f2 = "f2"; + check!(check!(tmpdir.create(&f1)).write(b"foo")); + check!(symlink_file(&f1, &tmpdir, &f2)); + match tmpdir.remove_dir_all(&f2) { + Ok(..) => panic!("wanted a failure"), + Err(..) => {} + } +} + +#[test] +#[ignore] // takes too much time +fn recursive_rmdir_toctou() { + // Test for time-of-check to time-of-use issues. + // + // Scenario: + // The attacker wants to get directory contents deleted, to which he does not + // have access. He has a way to get a privileged Rust binary call + // `std::fs::remove_dir_all()` on a directory he controls, e.g. in his home + // directory. + // + // The POC sets up the `attack_dest/attack_file` which the attacker wants to + // have deleted. The attacker repeatedly creates a directory and replaces + // it with a symlink from `victim_del` to `attack_dest` while the victim + // code calls `std::fs::remove_dir_all()` on `victim_del`. After a few + // seconds the attack has succeeded and `attack_dest/attack_file` is + // deleted. + let tmpdir = tmpdir(); + let victim_del_path = "victim_del"; + + // setup dest + let attack_dest_dir = "attack_dest"; + let attack_dest_dir = Path::new(attack_dest_dir); + tmpdir.create_dir(attack_dest_dir).unwrap(); + let attack_dest_file = "attack_dest/attack_file"; + tmpdir.create(attack_dest_file).unwrap(); + + let drop_canary_arc = Arc::new(()); + let drop_canary_weak = Arc::downgrade(&drop_canary_arc); + + eprintln!("x: {:?}", &victim_del_path); + + // victim just continuously removes `victim_del` + let tmpdir_clone = tmpdir.try_clone().unwrap(); + let _t = thread::spawn(move || { + while drop_canary_weak.upgrade().is_some() { + let _ = tmpdir_clone.remove_dir_all(victim_del_path); + } + }); + + // attacker (could of course be in a separate process) + let start_time = Instant::now(); + while Instant::now().duration_since(start_time) < Duration::from_secs(1000) { + if !tmpdir.exists(attack_dest_file) { + panic!( + "Victim deleted symlinked file outside of victim_del. Attack succeeded in {:?}.", + Instant::now().duration_since(start_time) + ); + } + let _ = tmpdir.create_dir(victim_del_path); + let _ = tmpdir.remove_dir(victim_del_path); + let _ = symlink_dir(attack_dest_dir, &tmpdir, victim_del_path); + } +} + +#[test] +fn unicode_path_is_dir() { + let tmpdir = tmpdir(); + + assert!(tmpdir.is_dir(Path::new("."))); + assert!(!tmpdir.is_dir(Path::new("test/stdtest/fs.rs"))); + + let mut dirpath = PathBuf::new(); + dirpath.push("test-가一ー你好"); + check!(tmpdir.create_dir(&dirpath)); + assert!(tmpdir.is_dir(&dirpath)); + + let mut filepath = dirpath; + filepath.push("unicode-file-\u{ac00}\u{4e00}\u{30fc}\u{4f60}\u{597d}.rs"); + check!(tmpdir.create(&filepath)); // ignore return; touch only + assert!(!tmpdir.is_dir(&filepath)); + assert!(tmpdir.exists(filepath)); +} + +#[test] +fn unicode_path_exists() { + let tmpdir = tmpdir(); + + assert!(tmpdir.exists(Path::new("."))); + assert!(!tmpdir.exists(Path::new("test/nonexistent-bogus-path"))); + + let unicode = PathBuf::new(); + let unicode = unicode.join("test-각丁ー再见"); + check!(tmpdir.create_dir(&unicode)); + assert!(tmpdir.exists(unicode)); + assert!(!tmpdir.exists(Path::new("test/unicode-bogus-path-각丁ー再见"))); +} + +#[test] +fn copy_file_does_not_exist() { + let tmpdir = tmpdir(); + let from = Path::new("test/nonexistent-bogus-path"); + let to = Path::new("test/other-bogus-path"); + + match tmpdir.copy(&from, &tmpdir, &to) { + Ok(..) => panic!(), + Err(..) => { + assert!(!tmpdir.exists(from)); + assert!(!tmpdir.exists(to)); + } + } +} + +#[test] +fn copy_src_does_not_exist() { + let tmpdir = tmpdir(); + let from = Path::new("test/nonexistent-bogus-path"); + let to = "out.txt"; + check!(check!(tmpdir.create(&to)).write(b"hello")); + assert!(tmpdir.copy(&from, &tmpdir, &to).is_err()); + assert!(!tmpdir.exists(from)); + let mut v = Vec::new(); + check!(check!(tmpdir.open(&to)).read_to_end(&mut v)); + assert_eq!(v, b"hello"); +} + +#[test] +fn copy_file_ok() { + let tmpdir = tmpdir(); + let input = "in.txt"; + let out = "out.txt"; + + check!(check!(tmpdir.create(&input)).write(b"hello")); + check!(tmpdir.copy(&input, &tmpdir, &out)); + let mut v = Vec::new(); + check!(check!(tmpdir.open(&out)).read_to_end(&mut v)); + assert_eq!(v, b"hello"); + + assert_eq!( + check!(tmpdir.metadata(input)).permissions(), + check!(tmpdir.metadata(out)).permissions() + ); +} + +#[test] +fn copy_file_dst_dir() { + let tmpdir = tmpdir(); + let out = "out"; + + check!(tmpdir.create(&out)); + match tmpdir.copy(&*out, &tmpdir, ".") { + Ok(..) => panic!(), + Err(..) => {} + } +} + +#[test] +fn copy_file_dst_exists() { + let tmpdir = tmpdir(); + let input = "in"; + let output = "out"; + + check!(check!(tmpdir.create(&input)).write("foo".as_bytes())); + check!(check!(tmpdir.create(&output)).write("bar".as_bytes())); + check!(tmpdir.copy(&input, &tmpdir, &output)); + + let mut v = Vec::new(); + check!(check!(tmpdir.open(&output)).read_to_end(&mut v)); + assert_eq!(v, b"foo".to_vec()); +} + +#[test] +fn copy_file_src_dir() { + let tmpdir = tmpdir(); + let out = "out"; + + match tmpdir.copy(".", &tmpdir, &out) { + Ok(..) => panic!(), + Err(..) => {} + } + assert!(!tmpdir.exists(out)); +} + +#[test] +fn copy_file_preserves_perm_bits() { + let tmpdir = tmpdir(); + let input = "in.txt"; + let out = "out.txt"; + + let attr = check!(check!(tmpdir.create(&input)).metadata()); + let mut p = attr.permissions(); + p.set_readonly(true); + check!(tmpdir.set_permissions(&input, p)); + check!(tmpdir.copy(&input, &tmpdir, &out)); + assert!(check!(tmpdir.metadata(out)).permissions().readonly()); + check!(tmpdir.set_permissions(&input, attr.permissions())); + check!(tmpdir.set_permissions(&out, attr.permissions())); +} + +#[test] +#[cfg(windows)] +fn copy_file_preserves_streams() { + let tmp = tmpdir(); + check!(check!(tmp.create("in.txt:bunny")).write("carrot".as_bytes())); + assert_eq!(check!(tmp.copy("in.txt", &tmp, "out.txt")), 0); + assert_eq!(check!(tmp.metadata("out.txt")).len(), 0); + let mut v = Vec::new(); + check!(check!(tmp.open("out.txt:bunny")).read_to_end(&mut v)); + assert_eq!(v, b"carrot".to_vec()); +} + +#[test] +fn copy_file_returns_metadata_len() { + let tmp = tmpdir(); + let in_path = "in.txt"; + let out_path = "out.txt"; + check!(check!(tmp.create(&in_path)).write(b"lettuce")); + #[cfg(windows)] + check!(check!(tmp.create("in.txt:bunny")).write(b"carrot")); + let copied_len = check!(tmp.copy(&in_path, &tmp, &out_path)); + assert_eq!(check!(tmp.metadata(out_path)).len(), copied_len); +} + +#[test] +fn copy_file_follows_dst_symlink() { + let tmp = tmpdir(); + if !got_symlink_permission(&tmp) { + return; + }; + + let in_path = "in.txt"; + let out_path = "out.txt"; + let out_path_symlink = "out_symlink.txt"; + + check!(tmp.write(&in_path, "foo")); + check!(tmp.write(&out_path, "bar")); + check!(symlink_file(&out_path, &tmp, &out_path_symlink)); + + check!(tmp.copy(&in_path, &tmp, &out_path_symlink)); + + assert!(check!(tmp.symlink_metadata(out_path_symlink)) + .file_type() + .is_symlink()); + assert_eq!(check!(tmp.read(&out_path_symlink)), b"foo".to_vec()); + assert_eq!(check!(tmp.read(&out_path)), b"foo".to_vec()); +} + +#[test] +fn symlinks_work() { + let tmpdir = tmpdir(); + if !got_symlink_permission(&tmpdir) { + return; + }; + + let input = "in.txt"; + let out = "out.txt"; + + check!(check!(tmpdir.create(&input)).write("foobar".as_bytes())); + check!(symlink_file(&input, &tmpdir, &out)); + assert!(check!(tmpdir.symlink_metadata(out)) + .file_type() + .is_symlink()); + assert_eq!( + check!(tmpdir.metadata(&out)).len(), + check!(tmpdir.metadata(&input)).len() + ); + let mut v = Vec::new(); + check!(check!(tmpdir.open(&out)).read_to_end(&mut v)); + assert_eq!(v, b"foobar".to_vec()); +} + +#[test] +fn symlink_noexist() { + // Symlinks can point to things that don't exist + let tmpdir = tmpdir(); + if !got_symlink_permission(&tmpdir) { + return; + }; + + // Use a relative path for testing. Symlinks get normalized by Windows, + // so we might not get the same path back for absolute paths + check!(symlink_file(&"foo", &tmpdir, "bar")); + assert_eq!(check!(tmpdir.read_link("bar")).to_str().unwrap(), "foo"); +} + +#[test] +fn read_link() { + if cfg!(windows) { + // directory symlink + let root = Dir::open_ambient_dir(r"C:\", ambient_authority()).unwrap(); + error_contains!( + root.read_link(r"Users\All Users"), + "a path led outside of the filesystem" + ); + // junction + error_contains!( + root.read_link(r"Users\Default User"), + "a path led outside of the filesystem" + ); + // junction with special permissions + error_contains!( + root.read_link(r"Documents and Settings\"), + "a path led outside of the filesystem" + ); + } + let tmpdir = tmpdir(); + let link = "link"; + if !got_symlink_permission(&tmpdir) { + return; + }; + check!(symlink_file(&"foo", &tmpdir, &link)); + assert_eq!(check!(tmpdir.read_link(&link)).to_str().unwrap(), "foo"); +} + +#[test] +fn readlink_not_symlink() { + let tmpdir = tmpdir(); + match tmpdir.read_link(".") { + Ok(..) => panic!("wanted a failure"), + Err(..) => {} + } +} + +#[cfg(not(windows))] +#[test] +fn read_link_contents() { + let tmpdir = tmpdir(); + let link = "link"; + if !got_symlink_permission(&tmpdir) { + return; + }; + check!(symlink_file(&"foo", &tmpdir, &link)); + assert_eq!( + check!(tmpdir.read_link_contents(&link)).to_str().unwrap(), + "foo" + ); +} + +#[cfg(not(windows))] +#[test] +fn read_link_contents_absolute() { + let tmpdir = tmpdir(); + let link = "link"; + if !got_symlink_permission(&tmpdir) { + return; + }; + check!(symlink_contents(&"/foo", &tmpdir, &link)); + assert_eq!( + check!(tmpdir.read_link_contents(&link)).to_str().unwrap(), + "/foo" + ); +} + +#[test] +fn links_work() { + let tmpdir = tmpdir(); + let input = "in.txt"; + let out = "out.txt"; + + check!(check!(tmpdir.create(&input)).write("foobar".as_bytes())); + check!(tmpdir.hard_link(&input, &tmpdir, &out)); + assert_eq!( + check!(tmpdir.metadata(&out)).len(), + check!(tmpdir.metadata(&input)).len() + ); + assert_eq!( + check!(tmpdir.metadata(&out)).len(), + check!(tmpdir.metadata(input)).len() + ); + let mut v = Vec::new(); + check!(check!(tmpdir.open(&out)).read_to_end(&mut v)); + assert_eq!(v, b"foobar".to_vec()); + + // can't link to yourself + match tmpdir.hard_link(&input, &tmpdir, &input) { + Ok(..) => panic!("wanted a failure"), + Err(..) => {} + } + // can't link to something that doesn't exist + match tmpdir.hard_link("foo", &tmpdir, "bar") { + Ok(..) => panic!("wanted a failure"), + Err(..) => {} + } +} + +#[test] +fn chmod_works() { + let tmpdir = tmpdir(); + let file = "in.txt"; + + check!(tmpdir.create(&file)); + let attr = check!(tmpdir.metadata(&file)); + assert!(!attr.permissions().readonly()); + let mut p = attr.permissions(); + p.set_readonly(true); + check!(tmpdir.set_permissions(&file, p.clone())); + let attr = check!(tmpdir.metadata(&file)); + assert!(attr.permissions().readonly()); + + match tmpdir.set_permissions("foo", p.clone()) { + Ok(..) => panic!("wanted an error"), + Err(..) => {} + } + + p.set_readonly(false); + check!(tmpdir.set_permissions(&file, p)); +} + +#[test] +fn fchmod_works() { + let tmpdir = tmpdir(); + let path = "in.txt"; + + let file = check!(tmpdir.create(&path)); + let attr = check!(tmpdir.metadata(&path)); + assert!(!attr.permissions().readonly()); + let mut p = attr.permissions(); + p.set_readonly(true); + check!(file.set_permissions(p.clone())); + let attr = check!(tmpdir.metadata(&path)); + assert!(attr.permissions().readonly()); + + p.set_readonly(false); + check!(file.set_permissions(p)); +} + +#[test] +fn sync_doesnt_kill_anything() { + let tmpdir = tmpdir(); + let path = "in.txt"; + + let mut file = check!(tmpdir.create(&path)); + check!(file.sync_all()); + check!(file.sync_data()); + check!(file.write(b"foo")); + check!(file.sync_all()); + check!(file.sync_data()); +} + +#[test] +fn truncate_works() { + let tmpdir = tmpdir(); + let path = "in.txt"; + + let mut file = check!(tmpdir.create(&path)); + check!(file.write(b"foo")); + check!(file.sync_all()); + + // Do some simple things with truncation + assert_eq!(check!(file.metadata()).len(), 3); + check!(file.set_len(10)); + assert_eq!(check!(file.metadata()).len(), 10); + check!(file.write(b"bar")); + check!(file.sync_all()); + assert_eq!(check!(file.metadata()).len(), 10); + + let mut v = Vec::new(); + check!(check!(tmpdir.open(&path)).read_to_end(&mut v)); + assert_eq!(v, b"foobar\0\0\0\0".to_vec()); + + // Truncate to a smaller length, don't seek, and then write something. + // Ensure that the intermediate zeroes are all filled in (we have `seek`ed + // past the end of the file). + check!(file.set_len(2)); + assert_eq!(check!(file.metadata()).len(), 2); + check!(file.write(b"wut")); + check!(file.sync_all()); + assert_eq!(check!(file.metadata()).len(), 9); + let mut v = Vec::new(); + check!(check!(tmpdir.open(&path)).read_to_end(&mut v)); + assert_eq!(v, b"fo\0\0\0\0wut".to_vec()); +} + +#[test] +fn open_flavors() { + use cap_std::fs::OpenOptions as OO; + fn c(t: &T) -> T { + t.clone() + } + + let tmpdir = tmpdir(); + + let mut r = OO::new(); + r.read(true); + let mut w = OO::new(); + w.write(true); + let mut rw = OO::new(); + rw.read(true).write(true); + let mut a = OO::new(); + a.append(true); + let mut ra = OO::new(); + ra.read(true).append(true); + + #[cfg(windows)] + let invalid_options = 87; // ERROR_INVALID_PARAMETER + #[cfg(any(all(unix, not(target_os = "vxworks")), target_os = "wasi"))] + let invalid_options = "Invalid argument"; + #[cfg(target_os = "vxworks")] + let invalid_options = "invalid argument"; + + // Test various combinations of creation modes and access modes. + // + // Allowed: + // creation mode | read | write | read-write | append | read-append + // | :-----------------------|:-----:|:-----:|:----------:|:------:|: + // -----------:| not set (open existing) | X | X | X | X + // | X | create | | X | X | + // X | X | truncate | | X | X + // | | | create and truncate | | X | X + // | | | create_new | | X | X + // | X | X | + // + // tested in reverse order, so 'create_new' creates the file, and 'open + // existing' opens it. + + // write-only + check!(tmpdir.open_with("a", c(&w).create_new(true))); + check!(tmpdir.open_with("a", c(&w).create(true).truncate(true))); + check!(tmpdir.open_with("a", c(&w).truncate(true))); + check!(tmpdir.open_with("a", c(&w).create(true))); + check!(tmpdir.open_with("a", &c(&w))); + + // read-only + error!( + tmpdir.open_with("b", c(&r).create_new(true)), + invalid_options + ); + error!( + tmpdir.open_with("b", c(&r).create(true).truncate(true)), + invalid_options + ); + error!(tmpdir.open_with("b", c(&r).truncate(true)), invalid_options); + error!(tmpdir.open_with("b", c(&r).create(true)), invalid_options); + check!(tmpdir.open_with("a", &c(&r))); // try opening the file created with write_only + + // read-write + check!(tmpdir.open_with("c", c(&rw).create_new(true))); + check!(tmpdir.open_with("c", c(&rw).create(true).truncate(true))); + check!(tmpdir.open_with("c", c(&rw).truncate(true))); + check!(tmpdir.open_with("c", c(&rw).create(true))); + check!(tmpdir.open_with("c", &c(&rw))); + + // append + check!(tmpdir.open_with("d", c(&a).create_new(true))); + error!( + tmpdir.open_with("d", c(&a).create(true).truncate(true)), + invalid_options + ); + error!(tmpdir.open_with("d", c(&a).truncate(true)), invalid_options); + check!(tmpdir.open_with("d", c(&a).create(true))); + check!(tmpdir.open_with("d", &c(&a))); + + // read-append + check!(tmpdir.open_with("e", c(&ra).create_new(true))); + error!( + tmpdir.open_with("e", c(&ra).create(true).truncate(true)), + invalid_options + ); + error!( + tmpdir.open_with("e", c(&ra).truncate(true)), + invalid_options + ); + check!(tmpdir.open_with("e", c(&ra).create(true))); + check!(tmpdir.open_with("e", &c(&ra))); + + // Test opening a file without setting an access mode + let mut blank = OO::new(); + error!(tmpdir.open_with("f", blank.create(true)), invalid_options); + + // Test write works + check!(check!(tmpdir.create("h")).write("foobar".as_bytes())); + + // Test write fails for read-only + check!(tmpdir.open_with("h", &r)); + { + let mut f = check!(tmpdir.open_with("h", &r)); + assert!(f.write("wut".as_bytes()).is_err()); + } + + // Test write overwrites + { + let mut f = check!(tmpdir.open_with("h", &c(&w))); + check!(f.write("baz".as_bytes())); + } + { + let mut f = check!(tmpdir.open_with("h", &c(&r))); + let mut b = vec![0; 6]; + check!(f.read(&mut b)); + assert_eq!(b, "bazbar".as_bytes()); + } + + // Test truncate works + { + let mut f = check!(tmpdir.open_with("h", c(&w).truncate(true))); + check!(f.write("foo".as_bytes())); + } + assert_eq!(check!(tmpdir.metadata("h")).len(), 3); + + // Test append works + assert_eq!(check!(tmpdir.metadata("h")).len(), 3); + { + let mut f = check!(tmpdir.open_with("h", &c(&a))); + check!(f.write("bar".as_bytes())); + } + assert_eq!(check!(tmpdir.metadata("h")).len(), 6); + + // Test .append(true) equals .write(true).append(true) + { + let mut f = check!(tmpdir.open_with("h", c(&w).append(true))); + check!(f.write("baz".as_bytes())); + } + assert_eq!(check!(tmpdir.metadata("h")).len(), 9); +} + +#[test] +fn _assert_send_sync() { + fn _assert_send_sync() {} + _assert_send_sync::(); +} + +#[test] +fn binary_file() { + let mut bytes = [0; 1024]; + StdRng::from_os_rng().fill_bytes(&mut bytes); + + let tmpdir = tmpdir(); + + check!(check!(tmpdir.create("test")).write(&bytes)); + let mut v = Vec::new(); + check!(check!(tmpdir.open("test")).read_to_end(&mut v)); + assert!(v == &bytes[..]); +} + +#[test] +fn write_then_read() { + let mut bytes = [0; 1024]; + StdRng::from_os_rng().fill_bytes(&mut bytes); + + let tmpdir = tmpdir(); + + check!(tmpdir.write("test", &bytes[..])); + let v = check!(tmpdir.read("test")); + assert!(v == &bytes[..]); + + check!(tmpdir.write("not-utf8", &[0xFF])); + error_contains!( + tmpdir.read_to_string("not-utf8"), + "stream did not contain valid UTF-8" + ); + + let s = "𐁁𐀓𐀠𐀴𐀍"; + check!(tmpdir.write("utf8", s.as_bytes())); + let string = check!(tmpdir.read_to_string("utf8")); + assert_eq!(string, s); +} + +#[test] +fn file_try_clone() { + let tmpdir = tmpdir(); + + let mut f1 = check!(tmpdir.open_with( + "test", + OpenOptions::new().read(true).write(true).create(true) + )); + let mut f2 = check!(f1.try_clone()); + + check!(f1.write_all(b"hello world")); + check!(f1.seek(SeekFrom::Start(2))); + + let mut buf = vec![]; + check!(f2.read_to_end(&mut buf)); + assert_eq!(buf, b"llo world"); + drop(f2); + + check!(f1.write_all(b"!")); +} + +#[test] +#[cfg(not(windows))] +fn unlink_readonly() { + let tmpdir = tmpdir(); + let path = "file"; + check!(tmpdir.create(&path)); + let mut perm = check!(tmpdir.metadata(&path)).permissions(); + perm.set_readonly(true); + check!(tmpdir.set_permissions(&path, perm)); + check!(tmpdir.remove_file(&path)); +} + +#[test] +fn mkdir_trailing_slash() { + let tmpdir = tmpdir(); + let path = PathBuf::from("file"); + check!(tmpdir.create_dir_all(&path.join("a/"))); +} + +#[test] +fn canonicalize_works_simple() { + let tmpdir = tmpdir(); + let file = Path::new("test"); + tmpdir.create(&file).unwrap(); + assert_eq!(tmpdir.canonicalize(&file).unwrap(), file); +} + +#[test] +fn realpath_works() { + let tmpdir = tmpdir(); + if !got_symlink_permission(&tmpdir) { + return; + }; + + let file = PathBuf::from("test"); + let dir = PathBuf::from("test2"); + let link = dir.join("link"); + let linkdir = PathBuf::from("test3"); + + tmpdir.create(&file).unwrap(); + tmpdir.create_dir(&dir).unwrap(); + symlink_file(Path::new("..").join(&file), &tmpdir, &link).unwrap(); + symlink_dir(&dir, &tmpdir, &linkdir).unwrap(); + + assert!(tmpdir + .symlink_metadata(&link) + .unwrap() + .file_type() + .is_symlink()); + + assert_eq!(tmpdir.canonicalize(".").unwrap(), PathBuf::from(".")); + assert_eq!(tmpdir.canonicalize(&file).unwrap(), file); + assert_eq!(tmpdir.canonicalize(&link).unwrap(), file); + assert_eq!(tmpdir.canonicalize(&linkdir).unwrap(), dir); + assert_eq!(tmpdir.canonicalize(&linkdir.join("link")).unwrap(), file); +} + +#[test] +fn realpath_works_tricky() { + let tmpdir = tmpdir(); + if !got_symlink_permission(&tmpdir) { + return; + }; + + let a = PathBuf::from("a"); + let b = a.join("b"); + let c = b.join("c"); + let d = a.join("d"); + let e = d.join("e"); + let f = a.join("f"); + + tmpdir.create_dir_all(&b).unwrap(); + tmpdir.create_dir_all(&d).unwrap(); + tmpdir.create(&f).unwrap(); + if cfg!(not(windows)) { + symlink_file("../d/e", &tmpdir, &c).unwrap(); + symlink_file("../f", &tmpdir, &e).unwrap(); + } + if cfg!(windows) { + symlink_file(r"..\d\e", &tmpdir, &c).unwrap(); + symlink_file(r"..\f", &tmpdir, &e).unwrap(); + } + + assert_eq!(tmpdir.canonicalize(&c).unwrap(), f); + assert_eq!(tmpdir.canonicalize(&e).unwrap(), f); +} + +#[test] +fn dir_entry_methods() { + let tmpdir = tmpdir(); + + tmpdir.create_dir_all("a").unwrap(); + tmpdir.create("b").unwrap(); + + for file in tmpdir.read_dir(".").unwrap().map(|f| f.unwrap()) { + let fname = file.file_name(); + match fname.to_str() { + Some("a") => { + assert!(file.file_type().unwrap().is_dir()); + assert!(file.metadata().unwrap().is_dir()); + } + Some("b") => { + assert!(file.file_type().unwrap().is_file()); + assert!(file.metadata().unwrap().is_file()); + } + f => panic!("unknown file name: {:?}", f), + } + } +} + +#[test] +fn dir_entry_debug() { + let tmpdir = tmpdir(); + tmpdir.create("b").unwrap(); + let mut read_dir = tmpdir.read_dir(".").unwrap(); + let dir_entry = read_dir.next().unwrap().unwrap(); + let actual = format!("{:?}", dir_entry); + let expected = format!("DirEntry({:?})", dir_entry.file_name()); + assert_eq!(actual, expected); +} + +#[test] +fn read_dir_not_found() { + let tmpdir = tmpdir(); + let res = tmpdir.read_dir("path/that/does/not/exist"); + assert_eq!(res.err().unwrap().kind(), ErrorKind::NotFound); +} + +// On Windows, `symlink_junction` somehow creates a symlink where `read_link` +// returns a relative path prefixed with "\\\\?\\", which `std::path::Path` +// parses as a `Prefix`, making cap-std think it's an absolute path and +// therefore a sandbox escape attempt. This only seems to happen with +// `symlink_junction`, and not symlinks created with standard library APIs. I +// don't know what the right thing to do here is. For now, disable these tests. +#[cfg_attr(windows, ignore)] +#[test] +fn create_dir_all_with_junctions() { + let tmpdir = tmpdir(); + let target = "target"; + + let junction = PathBuf::from("junction"); + let b = junction.join("a/b"); + + let link = PathBuf::from("link"); + let d = link.join("c/d"); + + tmpdir.create_dir(&target).unwrap(); + + check!(symlink_junction(&target, &tmpdir, &junction)); + check!(tmpdir.create_dir_all(&b)); + // the junction itself is not a directory, but `is_dir()` on a Path + // follows links + assert!(tmpdir.is_dir(junction)); + assert!(tmpdir.exists(b)); + + if !got_symlink_permission(&tmpdir) { + return; + }; + check!(symlink_dir(&target, &tmpdir, &link)); + check!(tmpdir.create_dir_all(&d)); + assert!(tmpdir.is_dir(link)); + assert!(tmpdir.exists(d)); +} + +#[test] +fn metadata_access_times() { + let tmpdir = tmpdir(); + + let b = "b"; + tmpdir.create(&b).unwrap(); + + let a = check!(tmpdir.metadata(".")); + let b = check!(tmpdir.metadata(&b)); + + assert_eq!(check!(a.accessed()), check!(a.accessed())); + assert_eq!(check!(a.modified()), check!(a.modified())); + // This assert from std's testsuite is racy. + //assert_eq!(check!(b.accessed()), check!(b.modified())); + + if cfg!(target_os = "macos") || cfg!(target_os = "windows") { + check!(a.created()); + check!(b.created()); + } + + if cfg!(any(target_os = "android", target_os = "linux")) { + // Not always available + match (a.created(), b.created()) { + (Ok(t1), Ok(t2)) => assert!(t1 <= t2), + #[cfg(not(io_error_uncategorized))] + (Err(e1), Err(e2)) + if e1.kind() == ErrorKind::Other && e2.kind() == ErrorKind::Other => {} + #[cfg(io_error_uncategorized)] + (Err(e1), Err(e2)) + if e1.kind() == ErrorKind::Uncategorized + && e2.kind() == ErrorKind::Uncategorized + || e1.kind() == ErrorKind::Unsupported + && e2.kind() == ErrorKind::Unsupported => {} + (a, b) => panic!( + "creation time must be always supported or not supported: {:?} {:?}", + a, b, + ), + } + } +} + +/// Test creating hard links to symlinks. +#[test] +fn symlink_hard_link() { + let tmpdir = tmpdir(); + if !got_symlink_permission(&tmpdir) { + return; + } + if !able_to_not_follow_symlinks_while_hard_linking() { + return; + } + + // Create "file", a file. + check!(tmpdir.create("file")); + + // Create "symlink", a symlink to "file". + check!(symlink_file("file", &tmpdir, "symlink")); + + // Create "hard_link", a hard link to "symlink". + check!(tmpdir.hard_link("symlink", &tmpdir, "hard_link")); + + // "hard_link" should appear as a symlink. + assert!(check!(tmpdir.symlink_metadata("hard_link")) + .file_type() + .is_symlink()); + + // We sould be able to open "file" via any of the above names. + let _ = check!(tmpdir.open("file")); + assert!(tmpdir.open("file.renamed").is_err()); + let _ = check!(tmpdir.open("symlink")); + let _ = check!(tmpdir.open("hard_link")); + + // Rename "file" to "file.renamed". + check!(tmpdir.rename("file", &tmpdir, "file.renamed")); + + // Now, the symlink and the hard link should be dangling. + assert!(tmpdir.open("file").is_err()); + let _ = check!(tmpdir.open("file.renamed")); + assert!(tmpdir.open("symlink").is_err()); + assert!(tmpdir.open("hard_link").is_err()); + + // The symlink and the hard link should both still point to "file". + assert!(tmpdir.read_link("file").is_err()); + assert!(tmpdir.read_link("file.renamed").is_err()); + assert_eq!(check!(tmpdir.read_link("symlink")), Path::new("file")); + assert_eq!(check!(tmpdir.read_link("hard_link")), Path::new("file")); + + // Remove "file.renamed". + check!(tmpdir.remove_file("file.renamed")); + + // Now, we can't open the file by any name. + assert!(tmpdir.open("file").is_err()); + assert!(tmpdir.open("file.renamed").is_err()); + assert!(tmpdir.open("symlink").is_err()); + assert!(tmpdir.open("hard_link").is_err()); + + // "hard_link" should still appear as a symlink. + assert!(check!(tmpdir.symlink_metadata("hard_link")) + .file_type() + .is_symlink()); +} + +/// Ensure `fs::create_dir` works on Windows with longer paths. +#[test] +#[cfg(windows)] +fn create_dir_long_paths() { + use std::ffi::OsStr; + use std::iter; + use std::os::windows::ffi::OsStrExt; + const PATH_LEN: usize = 247; + + let tmpdir = tmpdir(); + let mut path = PathBuf::new(); + path.push("a"); + let mut path = path.into_os_string(); + + let utf16_len = path.encode_wide().count(); + if utf16_len >= PATH_LEN { + // Skip the test in the unlikely event the local user has a long temp directory + // path. This should not affect CI. + return; + } + // Increase the length of the path. + path.extend(iter::repeat(OsStr::new("a")).take(PATH_LEN - utf16_len)); + + // This should succeed. + tmpdir.create_dir(&path).unwrap(); + + // This will fail if the path isn't converted to verbatim. + path.push("a"); + tmpdir.create_dir(&path).unwrap(); + + // #90940: Ensure an empty path returns the "Not Found" error. + let path = Path::new(""); + assert_eq!( + tmpdir.canonicalize(path).unwrap_err().kind(), + std::io::ErrorKind::NotFound + ); +} + +/// Ensure that opening a directory with the truncation flag set is an error, +/// not a panic inside of cap-std. +#[test] +fn open_directory_with_truncate_is_error() { + use cap_fs_ext::OpenOptionsMaybeDirExt; + let tmpdir = tmpdir(); + let mut options = OpenOptions::new(); + options + .truncate(true) + .maybe_dir(true) + .read(true) + .write(true); + tmpdir.create_dir("test").unwrap(); + assert!(tmpdir.open_with("test", &options).is_err()); +} diff --git a/crates/wasi/src/filesystem/primitives/tests/fs_additional.rs b/crates/wasi/src/filesystem/primitives/tests/fs_additional.rs new file mode 100644 index 000000000000..967a3cab7cd7 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/fs_additional.rs @@ -0,0 +1,1511 @@ +// This file contains additional fs tests that didn't make it into `fs.rs`. +// The reason for additional module to contain those is so that `fs.rs` mirrors +// Rust's libstd tests. + +#[macro_use] +mod sys_common; + +use cap_std::ambient_authority; +use cap_std::fs::{Dir, DirBuilder, OpenOptions}; +use cap_std::time::SystemClock; +use std::io::{self, Read, Write}; +use std::path::Path; +use std::str; +use sys_common::io::tmpdir; +use sys_common::symlink_supported; + +#[cfg(not(windows))] +fn symlink_dir, Q: AsRef>(src: P, tmpdir: &Dir, dst: Q) -> io::Result<()> { + tmpdir.symlink(src, dst) +} +#[cfg(not(windows))] +fn symlink_file, Q: AsRef>(src: P, tmpdir: &Dir, dst: Q) -> io::Result<()> { + tmpdir.symlink(src, dst) +} +#[cfg(windows)] +fn symlink_dir, Q: AsRef>(src: P, tmpdir: &Dir, dst: Q) -> io::Result<()> { + tmpdir.symlink_dir(src, dst) +} +#[cfg(windows)] +fn symlink_file, Q: AsRef>(src: P, tmpdir: &Dir, dst: Q) -> io::Result<()> { + tmpdir.symlink_file(src, dst) +} + +#[test] +fn recursive_mkdir() { + let tmpdir = tmpdir(); + let dir = "d1/d2"; + check!(tmpdir.create_dir_all(dir)); + assert!(tmpdir.is_dir("d1")); + let dir = check!(tmpdir.open_dir("d1")); + assert!(dir.is_dir("d2")); + assert!(tmpdir.is_dir("d1/d2")); +} + +#[test] +#[cfg_attr(windows, ignore)] // TODO investigate why this one is failing +fn open_various() { + let tmpdir = tmpdir(); + #[cfg(not(windows))] + error!(tmpdir.create(""), "No such file"); + #[cfg(windows)] + error!(tmpdir.create(""), 2); + + #[cfg(not(windows))] + error!(tmpdir.create("."), "Is a directory"); + #[cfg(windows)] + error!(tmpdir.create("."), 2); +} + +#[test] +#[cfg_attr(windows, ignore)] // TODO investigate why this one is failing +fn dir_writable() { + let tmpdir = tmpdir(); + check!(tmpdir.create_dir("dir")); + #[cfg(not(windows))] + error_contains!(tmpdir.create("dir"), "Is a directory"); + #[cfg(windows)] + error!(tmpdir.create("dir"), 5); + error_contains!( + tmpdir.open_with("dir", OpenOptions::new().write(true)), + "Is a directory" + ); + error_contains!( + tmpdir.open_with("dir", OpenOptions::new().append(true)), + "Is a directory" + ); + + error_contains!(tmpdir.create("dir/."), "Is a directory"); + error_contains!( + tmpdir.open_with("dir/.", OpenOptions::new().write(true)), + "Is a directory" + ); + error_contains!( + tmpdir.open_with("dir/.", OpenOptions::new().append(true)), + "Is a directory" + ); + + error_contains!(tmpdir.create("dir/.."), "Is a directory"); + error_contains!( + tmpdir.open_with("dir/..", OpenOptions::new().write(true)), + "Is a directory" + ); + error_contains!( + tmpdir.open_with("dir/..", OpenOptions::new().append(true)), + "Is a directory" + ); +} + +#[test] +fn trailing_slash() { + let tmpdir = tmpdir(); + check!(tmpdir.create("file")); + + #[cfg(not(windows))] + { + error!(tmpdir.open("file/../file"), "Not a directory"); + error!(tmpdir.open("file/.."), "Not a directory"); + error!(tmpdir.open("file/."), "Not a directory"); + error!(tmpdir.open("file/../file/"), "Not a directory"); + error!(tmpdir.open("file/"), "Not a directory"); + error!(tmpdir.open_dir("file/../file/"), "Not a directory"); + error!(tmpdir.open_dir("file/../file"), "Not a directory"); + error!(tmpdir.open_dir("file/.."), "Not a directory"); + error!(tmpdir.open_dir("file/."), "Not a directory"); + error!(tmpdir.open_dir("file/"), "Not a directory"); + } + + #[cfg(windows)] + { + assert!(check!(check!(tmpdir.open("file/../file")).metadata()).is_file()); + assert!(check!(check!(tmpdir.open_dir("file/..")).dir_metadata()).is_dir()); + assert!(check!(check!(tmpdir.open("file/.")).metadata()).is_file()); + assert!(tmpdir.open_dir("file/../file/").is_err()); + assert!(tmpdir.open_dir("file/./").is_err()); + assert!(tmpdir.open_dir("file//").is_err()); + assert!(tmpdir.open_dir("file/../file").is_err()); + assert!(tmpdir.open_dir("file/.").is_err()); + assert!(tmpdir.open_dir("file/").is_err()); + } +} + +#[test] +fn trailing_slash_in_dir() { + let tmpdir = tmpdir(); + check!(tmpdir.create_dir("dir")); + check!(tmpdir.create("dir/file")); + + #[cfg(not(windows))] + { + error!(tmpdir.open("dir/file/../file"), "Not a directory"); + error!(tmpdir.open("dir/file/.."), "Not a directory"); + error!(tmpdir.open("dir/file/."), "Not a directory"); + error!(tmpdir.open("dir/file/../file/"), "Not a directory"); + error!(tmpdir.open("dir/file/"), "Not a directory"); + error!(tmpdir.open_dir("dir/file/../file/"), "Not a directory"); + error!(tmpdir.open_dir("dir/file/../file"), "Not a directory"); + error!(tmpdir.open_dir("dir/file/.."), "Not a directory"); + error!(tmpdir.open_dir("dir/file/."), "Not a directory"); + error!(tmpdir.open_dir("dir/file/"), "Not a directory"); + } + + #[cfg(windows)] + { + assert!(check!(check!(tmpdir.open("dir/file/../file")).metadata()).is_file()); + assert!(check!(check!(tmpdir.open_dir("dir/file/..")).dir_metadata()).is_dir()); + assert!(check!(check!(tmpdir.open("dir/file/.")).metadata()).is_file()); + assert!(tmpdir.open("dir/file/../file/").is_err()); + let _ = check!(tmpdir.open("dir/file/../file/.")); + assert!(tmpdir.open("dir/file/../file/./").is_err()); + assert!(tmpdir.open("dir/file/").is_err()); + let _ = check!(tmpdir.open("dir/file/.")); + let _ = check!(tmpdir.open("dir/file/../file/.")); + assert!(tmpdir.open("dir/file/../file/./").is_err()); + assert!(tmpdir.open("dir/file/").is_err()); + let _ = check!(tmpdir.open("dir/file/.")); + assert!(tmpdir.open("dir/file/./").is_err()); + assert!(tmpdir.open_dir("dir/file/../file/").is_err()); + assert!(tmpdir.open_dir("dir/file/../file/.").is_err()); + assert!(tmpdir.open_dir("dir/file/../file/./").is_err()); + assert!(tmpdir.open_dir("dir/file/../file").is_err()); + assert!(tmpdir.open_dir("dir/file/.").is_err()); + assert!(tmpdir.open_dir("dir/file/./").is_err()); + assert!(tmpdir.open_dir("dir/file/").is_err()); + } +} + +#[test] +#[cfg_attr(windows, ignore)] // TODO investigate why this one is failing +fn rename_slashdots() { + let tmpdir = tmpdir(); + check!(tmpdir.create_dir("dir")); + check!(tmpdir.rename("dir", &tmpdir, "dir")); + check!(tmpdir.rename("dir", &tmpdir, "dir/")); + check!(tmpdir.rename("dir/", &tmpdir, "dir")); + check!(tmpdir.rename("dir/", &tmpdir, "dir/")); + + // TODO: Platform-specific error code. + error_contains!(tmpdir.rename("dir", &tmpdir, "dir/."), ""); + error_contains!(tmpdir.rename("dir/.", &tmpdir, "dir"), ""); +} + +#[test] +#[cfg_attr(windows, ignore)] // TODO investigate why this one is failing +fn rename_slashdots_ambient() { + let dir = tempfile::tempdir().unwrap(); + + check!(std::fs::create_dir_all(dir.path().join("dir"))); + check!(std::fs::rename( + dir.path().join("dir"), + dir.path().join("dir") + )); + check!(std::fs::rename( + dir.path().join("dir"), + dir.path().join("dir/") + )); + check!(std::fs::rename( + dir.path().join("dir/"), + dir.path().join("dir") + )); + check!(std::fs::rename( + dir.path().join("dir/"), + dir.path().join("dir/") + )); + + // TODO: Platform-specific error code. + error_contains!( + std::fs::rename(dir.path().join("dir"), dir.path().join("dir/.")), + "" + ); + error_contains!( + std::fs::rename(dir.path().join("dir/."), dir.path().join("dir")), + "" + ); +} + +#[test] +fn optionally_recursive_mkdir() { + let tmpdir = tmpdir(); + let dir = "d1/d2"; + check!(tmpdir.create_dir_with(dir, DirBuilder::new().recursive(true))); + assert!(tmpdir.is_dir(dir)); +} + +#[test] +fn try_exists() { + let tmpdir = tmpdir(); + assert_eq!(tmpdir.try_exists("somefile").unwrap(), false); + let dir = Path::new("d1/d2"); + let parent = dir.parent().unwrap(); + assert_eq!(tmpdir.try_exists(parent).unwrap(), false); + assert_eq!(tmpdir.try_exists(dir).unwrap(), false); + check!(tmpdir.create_dir(parent)); + assert_eq!(tmpdir.try_exists(parent).unwrap(), true); + assert_eq!(tmpdir.try_exists(dir).unwrap(), false); + check!(tmpdir.create_dir(dir)); + assert_eq!(tmpdir.try_exists(dir).unwrap(), true); +} + +#[test] +fn optionally_nonrecursive_mkdir() { + let tmpdir = tmpdir(); + let dir = "d1/d2"; + #[cfg(not(windows))] + error!( + tmpdir.create_dir_with(dir, &DirBuilder::new()), + "No such file" + ); + #[cfg(windows)] + error!(tmpdir.create_dir_with(dir, &DirBuilder::new()), 2); + + assert!(!tmpdir.exists(dir)); +} + +#[test] +fn file_test_directoryinfo_readdir() { + let tmpdir = tmpdir(); + let dir = "di_readdir"; + check!(tmpdir.create_dir(dir)); + let prefix = "foo"; + for n in 0..3 { + let f = format!("{}.txt", n); + let mut w = check!(tmpdir.create(&f)); + let msg_str = format!("{}{}", prefix, n.to_string()); + let msg = msg_str.as_bytes(); + check!(w.write(msg)); + } + let files = check!(tmpdir.read_dir(dir)); + let mut mem = [0; 4]; + for f in files { + let f = f.unwrap(); + { + check!(check!(f.open()).read(&mut mem)); + let read_str = str::from_utf8(&mem).unwrap(); + let expected = format!("{}{}", prefix, f.file_name().to_str().unwrap()); + assert_eq!(expected, read_str); + } + check!(f.remove_file()); + } + check!(tmpdir.remove_dir(dir)); +} + +#[test] +fn follow_dotdot_symlink() { + if !symlink_supported() { + return; + } + + let tmpdir = tmpdir(); + check!(tmpdir.create_dir_all("a/b")); + check!(symlink_dir("..", &tmpdir, "a/b/c")); + check!(symlink_dir("../..", &tmpdir, "a/b/d")); + check!(symlink_dir("../../..", &tmpdir, "a/b/e")); + check!(symlink_dir("../../../..", &tmpdir, "a/b/f")); + + check!(tmpdir.open_dir("a/b/c")); + assert!(check!(tmpdir.metadata("a/b/c")).is_dir()); + + #[cfg(windows)] + { + error!(tmpdir.open_dir("a/b/d"), 123); + error!(tmpdir.metadata("a/b/d"), 123); + + error!(tmpdir.open_dir("a/b/e"), 123); + error!(tmpdir.metadata("a/b/e"), 123); + + error!(tmpdir.open_dir("a/b/f"), 123); + error!(tmpdir.metadata("a/b/f"), 123); + } + + #[cfg(not(windows))] + { + check!(tmpdir.open_dir("a/b/d")); + assert!(check!(tmpdir.metadata("a/b/d")).is_dir()); + + assert!(tmpdir.open_dir("a/b/e").is_err()); + assert!(tmpdir.metadata("a/b/e").is_err()); + + assert!(tmpdir.open_dir("a/b/f").is_err()); + assert!(tmpdir.metadata("a/b/f").is_err()); + } +} + +#[test] +fn follow_dotdot_symlink_ambient() { + use cap_std::ambient_authority; + use cap_std::fs::Dir; + #[cfg(unix)] + use std::os::unix::fs::symlink as symlink_dir; + #[cfg(windows)] + use std::os::windows::fs::symlink_dir; + + if !symlink_supported() { + return; + } + + let dir = tempfile::tempdir().unwrap(); + check!(std::fs::create_dir_all(dir.path().join("a/b"))); + check!(symlink_dir("..", dir.path().join("a/b/c"))); + check!(symlink_dir("../..", dir.path().join("a/b/d"))); + check!(symlink_dir("../../..", dir.path().join("a/b/e"))); + check!(symlink_dir("../../../..", dir.path().join("a/b/f"))); + + check!(Dir::open_ambient_dir( + dir.path().join("a/b/c"), + ambient_authority() + )); + assert!(check!(std::fs::metadata(dir.path().join("a/b/c"))).is_dir()); + + #[cfg(windows)] + { + error!( + Dir::open_ambient_dir(dir.path().join("a/b/d"), ambient_authority()), + 123 + ); + error!(std::fs::metadata(dir.path().join("a/b/d")), 123); + + error!( + Dir::open_ambient_dir(dir.path().join("a/b/e"), ambient_authority()), + 123 + ); + error!(std::fs::metadata(dir.path().join("a/b/e")), 123); + + error!( + Dir::open_ambient_dir(dir.path().join("a/b/f"), ambient_authority()), + 123 + ); + error!(std::fs::metadata(dir.path().join("a/b/f")), 123); + } + + #[cfg(not(windows))] + { + check!(Dir::open_ambient_dir( + dir.path().join("a/b/d"), + ambient_authority() + )); + assert!(check!(std::fs::metadata(dir.path().join("a/b/d"))).is_dir()); + + check!(Dir::open_ambient_dir( + dir.path().join("a/b/e"), + ambient_authority() + )); + assert!(check!(std::fs::metadata(dir.path().join("a/b/e"))).is_dir()); + + check!(Dir::open_ambient_dir( + dir.path().join("a/b/f"), + ambient_authority() + )); + assert!(check!(std::fs::metadata(dir.path().join("a/b/f"))).is_dir()); + } +} + +#[test] +fn follow_file_symlink() { + if !symlink_supported() { + return; + } + + let tmpdir = tmpdir(); + + check!(tmpdir.create("file")); + + check!(symlink_file("file", &tmpdir, "link")); + check!(symlink_dir("file/", &tmpdir, "link_slash")); + check!(symlink_file("file/.", &tmpdir, "link_slashdot")); + check!(symlink_dir("file/..", &tmpdir, "link_slashdotdot")); + + check!(tmpdir.open("link")); + assert!(tmpdir.open("link_slash").is_err()); + + #[cfg(windows)] + { + error!(tmpdir.open("link_slashdot"), 123); + error!(tmpdir.open_dir("link_slashdotdot"), 123); + } + #[cfg(not(windows))] + { + assert!(tmpdir.open("link_slash").is_err()); + assert!(tmpdir.open("link_slashdot").is_err()); + assert!(tmpdir.open_dir("link_slashdotdot").is_err()); + } +} + +#[cfg(unix)] +#[test] +fn check_dot_access() { + use cap_std::fs::{DirBuilder, DirBuilderExt}; + + let tmpdir = tmpdir(); + + let mut options = DirBuilder::new(); + options.mode(0o477); + check!(tmpdir.create_dir_with("dir", &options)); + + check!(tmpdir.metadata(".")); + check!(tmpdir.metadata("dir")); + check!(tmpdir.metadata("dir/")); + check!(tmpdir.metadata("dir//")); + + if !cfg!(target_os = "freebsd") { + assert!(tmpdir.metadata("dir/.").is_err()); + assert!(tmpdir.metadata("dir/./").is_err()); + assert!(tmpdir.metadata("dir/.//").is_err()); + assert!(tmpdir.metadata("dir/./.").is_err()); + assert!(tmpdir.metadata("dir/.//.").is_err()); + assert!(tmpdir.metadata("dir/..").is_err()); + assert!(tmpdir.metadata("dir/../").is_err()); + assert!(tmpdir.metadata("dir/..//").is_err()); + assert!(tmpdir.metadata("dir/../.").is_err()); + assert!(tmpdir.metadata("dir/..//.").is_err()); + } +} + +/// This test is the same as `check_dot_access` but uses `std::fs`' +/// ambient API instead of `cap_std`. The purpose of this test is to +/// confirm fundamentally OS-specific differences. +#[cfg(unix)] +#[test] +fn check_dot_access_ambient() { + use std::fs; + use std::os::unix::fs::DirBuilderExt; + + let dir = tempfile::tempdir().unwrap(); + + let mut options = std::fs::DirBuilder::new(); + options.mode(0o477); + check!(options.create(dir.path().join("dir"))); + + check!(fs::metadata(dir.path().join("."))); + check!(fs::metadata(dir.path().join("dir"))); + check!(fs::metadata(dir.path().join("dir/"))); + check!(fs::metadata(dir.path().join("dir//"))); + + if !cfg!(target_os = "freebsd") { + assert!(fs::metadata(dir.path().join("dir/.")).is_err()); + assert!(fs::metadata(dir.path().join("dir/./")).is_err()); + assert!(fs::metadata(dir.path().join("dir/.//")).is_err()); + assert!(fs::metadata(dir.path().join("dir/./.")).is_err()); + assert!(fs::metadata(dir.path().join("dir/.//.")).is_err()); + assert!(fs::metadata(dir.path().join("dir/..")).is_err()); + assert!(fs::metadata(dir.path().join("dir/../")).is_err()); + assert!(fs::metadata(dir.path().join("dir/..//")).is_err()); + assert!(fs::metadata(dir.path().join("dir/../.")).is_err()); + assert!(fs::metadata(dir.path().join("dir/..//.")).is_err()); + } +} + +// Windows allows one to open "file/." and "file/.." and similar, however it +// doesn't allow "file/" or similar. +#[cfg(windows)] +#[test] +fn file_with_trailing_slashdot() { + let tmpdir = tmpdir(); + check!(tmpdir.create("file")); + check!(tmpdir.open("file")); + check!(tmpdir.open("file\\.")); + check!(tmpdir.open("file/.")); + check!(tmpdir.open("file\\.\\.")); + check!(tmpdir.open("file/./.")); + assert!(tmpdir.open("file\\").is_err()); + assert!(tmpdir.open("file/").is_err()); + assert!(tmpdir.open("file\\.\\").is_err()); + assert!(tmpdir.open("file/./").is_err()); + check!(tmpdir.open_dir("file\\..")); + check!(tmpdir.open_dir("file/..")); + check!(tmpdir.open_dir("file\\.\\..")); + check!(tmpdir.open_dir("file/./..")); + check!(tmpdir.open_dir("file\\..\\.")); + check!(tmpdir.open_dir("file/../.")); + check!(tmpdir.open_dir("file\\..\\")); + check!(tmpdir.open_dir("file/../")); + assert!(tmpdir.open_dir("file\\...").is_err()); + assert!(tmpdir.open_dir("file/...").is_err()); +} + +/// This is just to confirm that Windows really does allow one to open "file/." +/// and "file/..", and similar, however it doesn't allow "file/" or similar. +#[cfg(windows)] +#[test] +fn file_with_trailing_slashdot_ambient() { + use cap_std::ambient_authority; + use cap_std::fs::Dir; + let dir = tempfile::tempdir().unwrap(); + check!(std::fs::File::create(dir.path().join("file"))); + check!(std::fs::File::open(dir.path().join("file"))); + check!(std::fs::File::open(dir.path().join("file\\."))); + check!(std::fs::File::open(dir.path().join("file/."))); + check!(std::fs::File::open(dir.path().join("file\\.\\."))); + check!(std::fs::File::open(dir.path().join("file/./."))); + assert!(std::fs::File::open(dir.path().join("file\\")).is_err()); + assert!(std::fs::File::open(dir.path().join("file/")).is_err()); + assert!(std::fs::File::open(dir.path().join("file\\.\\")).is_err()); + assert!(std::fs::File::open(dir.path().join("file/./")).is_err()); + check!(Dir::open_ambient_dir( + dir.path().join("file/.."), + ambient_authority() + )); + check!(Dir::open_ambient_dir( + dir.path().join("file\\.\\.."), + ambient_authority() + )); + check!(Dir::open_ambient_dir( + dir.path().join("file/./.."), + ambient_authority() + )); + check!(Dir::open_ambient_dir( + dir.path().join("file\\..\\."), + ambient_authority() + )); + check!(Dir::open_ambient_dir( + dir.path().join("file/../."), + ambient_authority() + )); + check!(Dir::open_ambient_dir( + dir.path().join("file\\..\\"), + ambient_authority() + )); + check!(Dir::open_ambient_dir( + dir.path().join("file/../"), + ambient_authority() + )); + assert!(Dir::open_ambient_dir(dir.path().join("file\\..."), ambient_authority()).is_err()); + assert!(Dir::open_ambient_dir(dir.path().join("file/..."), ambient_authority()).is_err()); +} + +#[cfg(all( + unix, + not(any( + target_os = "ios", + target_os = "macos", + target_os = "tvos", + target_os = "watchos", + target_os = "visionos", + )) +))] +#[test] +fn dir_searchable_unreadable() { + use cap_std::fs::{DirBuilder, DirBuilderExt}; + + let tmpdir = tmpdir(); + + let mut options = DirBuilder::new(); + options.mode(0o333); + check!(tmpdir.create_dir_with("dir", &options)); + check!(tmpdir.create_dir_with("dir/writeable_subdir", &options)); + options.mode(0o111); + check!(tmpdir.create_dir_with("dir/subdir", &options)); + + assert!(check!(tmpdir.metadata("dir/.")).is_dir()); + assert!(check!(tmpdir.metadata("dir/subdir")).is_dir()); + assert!(check!(tmpdir.metadata("dir/subdir/.")).is_dir()); +} + +/// This test is the same as `dir_searchable_unreadable` but uses `std::fs`' +/// ambient API instead of `cap_std`. The purpose of this test is to +/// confirm fundamentally OS-specific differences. +#[cfg(all( + unix, + not(any( + target_os = "ios", + target_os = "macos", + target_os = "tvos", + target_os = "watchos", + target_os = "visionos", + )) +))] +#[test] +fn dir_searchable_unreadable_ambient() { + use std::fs; + use std::os::unix::fs::DirBuilderExt; + + let dir = tempfile::tempdir().unwrap(); + + let mut options = std::fs::DirBuilder::new(); + options.mode(0o333); + check!(options.create(dir.path().join("dir"))); + check!(options.create(dir.path().join("dir/writeable_subdir"))); + options.mode(0o111); + check!(options.create(dir.path().join("dir/subdir"))); + + assert!(check!(fs::metadata(dir.path().join("dir/."))).is_dir()); + assert!(check!(fs::metadata(dir.path().join("dir/subdir"))).is_dir()); + assert!(check!(fs::metadata(dir.path().join("dir/subdir/."))).is_dir()); +} + +/// On Darwin, we don't have a race-free way to create a subdirectory within +/// a directory that we don't have read access to. +#[cfg(any( + target_os = "ios", + target_os = "macos", + target_os = "tvos", + target_os = "watchos", + target_os = "visionos", +))] +#[test] +fn dir_searchable_unreadable() { + use cap_std::fs::{DirBuilder, DirBuilderExt}; + + let tmpdir = tmpdir(); + + let mut options = DirBuilder::new(); + options.mode(0o333); + check!(tmpdir.create_dir_with("dir", &options)); + assert!(tmpdir + .create_dir_with("dir/writeable_subdir", &options) + .is_err()); +} + +/// Test opening a directory with no permissions. +#[cfg(unix)] +#[test] +fn dir_unsearchable_unreadable() { + use cap_std::fs::{DirBuilder, DirBuilderExt}; + + let tmpdir = tmpdir(); + + let mut options = DirBuilder::new(); + options.mode(0o000); + check!(tmpdir.create_dir_with("dir", &options)); + + // Platforms with `O_PATH` can open a directory with no permissions. + if cfg!(any( + target_os = "android", + target_os = "linux", + target_os = "redox", + )) { + let dir = check!(tmpdir.open_dir("dir")); + assert!(dir.entries().is_err()); + assert!(dir.open_dir(".").is_err()); + } else if !cfg!(target_os = "freebsd") { + assert!(tmpdir.open_dir("dir").is_err()); + } +} + +/// Like `dir_unsearchable_unreadable`, but uses ambient-authority APIs +/// to test underlying host functionality. +#[cfg(unix)] +#[test] +fn dir_unsearchable_unreadable_ambient() { + use std::fs::DirBuilder; + use std::os::unix::fs::DirBuilderExt; + + let dir = tempfile::tempdir().unwrap(); + + let mut options = DirBuilder::new(); + options.mode(0o000); + check!(options.create(dir.path().join("dir"))); + + if cfg!(any( + target_os = "android", + target_os = "linux", + target_os = "redox", + )) { + assert!(std::fs::File::open(dir.path().join("dir")).is_err()); + assert!(std::fs::read_dir(dir.path().join("dir")).is_err()); + assert!(std::fs::File::open(dir.path().join("dir/.")).is_err()); + } +} + +/// This test is the same as `symlink_hard_link` but uses `std::fs`' +/// ambient API instead of `cap_std`. The purpose of this test is to +/// confirm fundamentally OS-specific behaviors. +#[test] +fn symlink_hard_link_ambient() { + #[cfg(unix)] + use std::os::unix::fs::symlink; + #[cfg(windows)] + use std::os::windows::fs::symlink_file; + + if !symlink_supported() { + return; + } + + let dir = tempfile::tempdir().unwrap(); + + check!(std::fs::File::create(dir.path().join("file"))); + #[cfg(not(windows))] + check!(symlink("file", dir.path().join("symlink"))); + #[cfg(windows)] + check!(symlink_file("file", dir.path().join("symlink"))); + check!(std::fs::hard_link( + dir.path().join("symlink"), + dir.path().join("hard_link") + )); + assert!( + check!(std::fs::symlink_metadata(dir.path().join("hard_link"))) + .file_type() + .is_symlink() + ); + let _ = check!(std::fs::File::open(dir.path().join("file"))); + assert!(std::fs::File::open(dir.path().join("file.renamed")).is_err()); + let _ = check!(std::fs::File::open(dir.path().join("symlink"))); + let _ = check!(std::fs::File::open(dir.path().join("hard_link"))); + check!(std::fs::rename( + dir.path().join("file"), + dir.path().join("file.renamed") + )); + assert!(std::fs::File::open(dir.path().join("file")).is_err()); + let _ = check!(std::fs::File::open(dir.path().join("file.renamed"))); + assert!(std::fs::File::open(dir.path().join("symlink")).is_err()); + assert!(std::fs::File::open(dir.path().join("hard_link")).is_err()); + assert!(std::fs::read_link(dir.path().join("file")).is_err()); + assert!(std::fs::read_link(dir.path().join("file.renamed")).is_err()); + assert_eq!( + check!(std::fs::read_link(dir.path().join("symlink"))), + Path::new("file") + ); + assert_eq!( + check!(std::fs::read_link(dir.path().join("hard_link"))), + Path::new("file") + ); + check!(std::fs::remove_file(dir.path().join("file.renamed"))); + assert!(std::fs::File::open(dir.path().join("file")).is_err()); + assert!(std::fs::File::open(dir.path().join("file.renamed")).is_err()); + assert!(std::fs::File::open(dir.path().join("symlink")).is_err()); + assert!(std::fs::File::open(dir.path().join("hard_link")).is_err()); + assert!( + check!(std::fs::symlink_metadata(dir.path().join("hard_link"))) + .file_type() + .is_symlink() + ); +} + +/// POSIX says that whether or not `link` follows symlinks in the `old` +/// path is implementation-defined. We want `hard_link` to not follow +/// symbolic links. +#[test] +fn symlink_hard_link() { + if !symlink_supported() { + return; + } + + let tmpdir = tmpdir(); + + check!(tmpdir.create("file")); + check!(symlink_file("file", &tmpdir, "symlink")); + check!(tmpdir.hard_link("symlink", &tmpdir, "hard_link")); + assert!(check!(tmpdir.symlink_metadata("hard_link")) + .file_type() + .is_symlink()); + let _ = check!(tmpdir.open("file")); + assert!(tmpdir.open("file.renamed").is_err()); + let _ = check!(tmpdir.open("symlink")); + let _ = check!(tmpdir.open("hard_link")); + check!(tmpdir.rename("file", &tmpdir, "file.renamed")); + assert!(tmpdir.open("file").is_err()); + let _ = check!(tmpdir.open("file.renamed")); + assert!(tmpdir.open("symlink").is_err()); + assert!(tmpdir.open("hard_link").is_err()); + assert!(tmpdir.read_link("file").is_err()); + assert!(tmpdir.read_link("file.renamed").is_err()); + assert_eq!(check!(tmpdir.read_link("symlink")), Path::new("file")); + assert_eq!(check!(tmpdir.read_link("hard_link")), Path::new("file")); + check!(tmpdir.remove_file("file.renamed")); + assert!(tmpdir.open("file").is_err()); + assert!(tmpdir.open("file.renamed").is_err()); + assert!(tmpdir.open("symlink").is_err()); + assert!(tmpdir.open("hard_link").is_err()); + assert!(check!(tmpdir.symlink_metadata("hard_link")) + .file_type() + .is_symlink()); +} + +#[test] +fn readdir_with_trailing_slashdot() { + let tmpdir = tmpdir(); + check!(tmpdir.create_dir("dir")); + check!(tmpdir.create("dir/red")); + check!(tmpdir.create("dir/green")); + check!(tmpdir.create("dir/blue")); + + assert_eq!(check!(tmpdir.read_dir("dir")).count(), 3); + assert_eq!(check!(tmpdir.read_dir("dir/")).count(), 3); + assert_eq!(check!(tmpdir.read_dir("dir/.")).count(), 3); +} + +#[test] +fn readdir_write() { + let tmpdir = tmpdir(); + check!(tmpdir.create_dir("dir")); + assert!(tmpdir + .open_with("dir", OpenOptions::new().write(true)) + .is_err()); + assert!(tmpdir + .open_with("dir", OpenOptions::new().append(true)) + .is_err()); + assert!(tmpdir + .open_with("dir/", OpenOptions::new().write(true)) + .is_err()); + assert!(tmpdir + .open_with("dir/", OpenOptions::new().append(true)) + .is_err()); + + #[cfg(any(target_os = "android", target_os = "linux"))] + { + use cap_std::fs::OpenOptionsExt; + assert!(tmpdir + .open_with( + "dir", + OpenOptions::new() + .write(true) + .custom_flags(rustix::fs::OFlags::DIRECTORY.bits() as i32) + ) + .is_err()); + assert!(tmpdir + .open_with( + "dir", + OpenOptions::new() + .append(true) + .custom_flags(rustix::fs::OFlags::DIRECTORY.bits() as i32) + ) + .is_err()); + } +} + +#[test] +fn maybe_dir() { + use cap_fs_ext::OpenOptionsMaybeDirExt; + + let tmpdir = tmpdir(); + check!(tmpdir.create_dir("dir")); + + // Opening directories works on non-Windows platforms. + #[cfg(not(windows))] + check!(tmpdir.open("dir")); + + // Opening directories fails on Windows. + #[cfg(windows)] + assert!(tmpdir.open("dir").is_err()); + + // Opening directories works on all platforms with `maybe_dir`. + check!(tmpdir.open_with("dir", OpenOptions::new().read(true).maybe_dir(true))); +} + +#[test] +fn sync() { + use cap_fs_ext::OpenOptionsSyncExt; + + let tmpdir = tmpdir(); + check!(tmpdir.create("file")); + + check!(tmpdir.open_with("file", OpenOptions::new().write(true).sync(true))); + check!(tmpdir.open_with("file", OpenOptions::new().write(true).dsync(true))); + check!(tmpdir.open_with( + "file", + OpenOptions::new() + .read(true) + .write(true) + .sync(true) + .rsync(true) + )); + check!(tmpdir.open_with( + "file", + OpenOptions::new() + .read(true) + .write(true) + .dsync(true) + .rsync(true) + )); +} + +#[test] +fn reopen_fd() { + use io_lifetimes::AsFilelike; + let tmpdir = tmpdir(); + check!(tmpdir.create_dir("subdir")); + let tmpdir2 = check!(cap_std::fs::Dir::reopen_dir(&tmpdir.as_filelike())); + assert!(tmpdir2.exists("subdir")); +} + +#[test] +fn metadata_vs_std_fs() { + let tmpdir = tmpdir(); + check!(tmpdir.create_dir("dir")); + let dir = check!(tmpdir.open_dir("dir")); + let file = check!(dir.create("file")); + + let cap_std_dir = check!(dir.dir_metadata()); + let cap_std_file = check!(file.metadata()); + let cap_std_dir_entry = { + let mut entries = check!(dir.entries()); + let entry = check!(entries.next().unwrap()); + assert_eq!(entry.file_name(), "file"); + assert!(entries.next().is_none(), "unexpected dir entry"); + check!(entry.metadata()) + }; + + let std_dir = check!(dir.into_std_file().metadata()); + let std_file = check!(file.into_std().metadata()); + + match std_dir.created() { + Ok(_) => println!("std::fs supports file created times"), + Err(e) => println!("std::fs doesn't support file created times: {}", e), + } + + check_metadata(&std_dir, &cap_std_dir); + check_metadata(&std_file, &cap_std_file); + check_metadata(&std_file, &cap_std_dir_entry); +} + +fn check_metadata(std: &std::fs::Metadata, cap: &cap_std::fs::Metadata) { + assert_eq!(std.is_dir(), cap.is_dir()); + assert_eq!(std.is_file(), cap.is_file()); + assert_eq!(std.is_symlink(), cap.is_symlink()); + assert_eq!(std.file_type().is_dir(), cap.file_type().is_dir()); + assert_eq!(std.file_type().is_file(), cap.file_type().is_file()); + assert_eq!(std.file_type().is_symlink(), cap.file_type().is_symlink()); + #[cfg(unix)] + { + assert_eq!( + std::os::unix::fs::FileTypeExt::is_block_device(&std.file_type()), + cap_std::fs::FileTypeExt::is_block_device(&cap.file_type()) + ); + assert_eq!( + std::os::unix::fs::FileTypeExt::is_char_device(&std.file_type()), + cap_std::fs::FileTypeExt::is_char_device(&cap.file_type()) + ); + assert_eq!( + std::os::unix::fs::FileTypeExt::is_fifo(&std.file_type()), + cap_std::fs::FileTypeExt::is_fifo(&cap.file_type()) + ); + assert_eq!( + std::os::unix::fs::FileTypeExt::is_socket(&std.file_type()), + cap_std::fs::FileTypeExt::is_socket(&cap.file_type()) + ); + } + + assert_eq!(std.len(), cap.len()); + + assert_eq!(std.permissions().readonly(), cap.permissions().readonly()); + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + assert_eq!( + std.permissions().mode(), + cap_std::fs::PermissionsExt::mode(&cap.permissions()) + ); + } + + // If the standard library supports file modified/accessed/created times, + // then cap-std should too. + match std.modified() { + Ok(expected) => assert_eq!(expected, check!(cap.modified()).into_std()), + Err(e) => assert!( + cap.modified().is_err(), + "modified time should be error ({}), got {:#?}", + e, + cap.modified() + ), + } + // The access times might be a little different due to either our own + // or concurrent accesses. + const ACCESS_TOLERANCE_SEC: u32 = 60; + match std.accessed() { + Ok(expected) => { + let access_tolerance = std::time::Duration::from_secs(ACCESS_TOLERANCE_SEC.into()); + assert!( + ((expected - access_tolerance)..(expected + access_tolerance)) + .contains(&check!(cap.accessed()).into_std()), + "std accessed {:#?}, cap accessed {:#?}", + expected, + cap.accessed() + ); + } + Err(e) => assert!( + cap.accessed().is_err(), + "accessed time should be error ({}), got {:#?}", + e, + cap.accessed() + ), + } + match std.created() { + Ok(expected) => assert_eq!(expected, check!(cap.created()).into_std()), + Err(e) => { + // An earlier bug returned the Unix epoch instead of `None` when + // created times were unavailable. This tries to catch such errors, + // while also allowing some targets to return valid created times + // even when std doesn't. + if let Ok(actual) = cap.created() { + println!( + "std returned error for created time ({}) but got {:#?}", + e, actual + ); + assert_ne!(actual, SystemClock::UNIX_EPOCH); + } + } + } + + #[cfg(unix)] + { + use std::os::unix::fs::MetadataExt; + assert_eq!(std.dev(), cap_std::fs::MetadataExt::dev(cap)); + assert_eq!(std.ino(), cap_std::fs::MetadataExt::ino(cap)); + assert_eq!(std.mode(), cap_std::fs::MetadataExt::mode(cap)); + assert_eq!(std.nlink(), cap_std::fs::MetadataExt::nlink(cap)); + assert_eq!(std.uid(), cap_std::fs::MetadataExt::uid(cap)); + assert_eq!(std.gid(), cap_std::fs::MetadataExt::gid(cap)); + assert_eq!(std.rdev(), cap_std::fs::MetadataExt::rdev(cap)); + assert_eq!(std.size(), cap_std::fs::MetadataExt::size(cap)); + assert!( + ((std.atime() - i64::from(ACCESS_TOLERANCE_SEC)) + ..(std.atime() + i64::from(ACCESS_TOLERANCE_SEC))) + .contains(&cap_std::fs::MetadataExt::atime(cap)), + "std atime {}, cap atime {}", + std.atime(), + cap_std::fs::MetadataExt::atime(cap) + ); + assert!((0..1_000_000_000).contains(&cap_std::fs::MetadataExt::atime_nsec(cap))); + assert_eq!(std.mtime(), cap_std::fs::MetadataExt::mtime(cap)); + assert_eq!(std.mtime_nsec(), cap_std::fs::MetadataExt::mtime_nsec(cap)); + assert_eq!(std.ctime(), cap_std::fs::MetadataExt::ctime(cap)); + assert_eq!(std.ctime_nsec(), cap_std::fs::MetadataExt::ctime_nsec(cap)); + assert_eq!(std.blksize(), cap_std::fs::MetadataExt::blksize(cap)); + assert_eq!(std.blocks(), cap_std::fs::MetadataExt::blocks(cap)); + } +} + +/// Test that a symlink in the middle of a path containing ".." doesn't cause +/// the path to be treated as if it ends with "..". +#[test] +fn dotdot_in_middle_of_symlink() { + let tmpdir = tmpdir(); + + let foo = b"foo"; + check!(tmpdir.write("target", foo)); + check!(tmpdir.create_dir("b")); + let b = check!(tmpdir.open_dir("b")); + check!(symlink_dir("..", &b, "up")); + + let path = "b/up/target"; + let mut file = check!(tmpdir.open(path)); + let mut data = Vec::new(); + check!(file.read_to_end(&mut data)); + assert_eq!(data, foo); +} + +/// Like `dotdot_in_middle_of_symlink` but with a `/.` at the end. +/// +/// Windows doesn't appear to like symlinks that end with `/.`. +#[test] +#[cfg_attr(windows, ignore)] +fn dotdot_slashdot_in_middle_of_symlink() { + let tmpdir = tmpdir(); + + let foo = b"foo"; + check!(tmpdir.write("target", foo)); + check!(tmpdir.create_dir("b")); + let b = check!(tmpdir.open_dir("b")); + check!(symlink_dir("../.", &b, "up")); + + let path = "b/up/target"; + let mut file = check!(tmpdir.open(path)); + let mut data = Vec::new(); + check!(file.read_to_end(&mut data)); + assert_eq!(data, foo); +} + +/// Same as `dotdot_in_middle_of_symlink`, but use two levels of `..`. +/// +/// Windows doesn't appear to like symlinks that end with `/..`. +#[test] +#[cfg_attr(windows, ignore)] +fn dotdot_more_in_middle_of_symlink() { + let tmpdir = tmpdir(); + + let foo = b"foo"; + check!(tmpdir.write("target", foo)); + check!(tmpdir.create_dir_all("b/c")); + let b = check!(tmpdir.open_dir("b")); + check!(symlink_dir("c/../..", &b, "up")); + + let path = "b/up/target"; + let mut file = check!(tmpdir.open(path)); + let mut data = Vec::new(); + check!(file.read_to_end(&mut data)); + assert_eq!(data, foo); +} + +/// Like `dotdot_more_in_middle_of_symlink`, but with a `/.` at the end. +/// +/// Windows doesn't appear to like symlinks that end with `/.`. +#[test] +#[cfg_attr(windows, ignore)] +fn dotdot_slashdot_more_in_middle_of_symlink() { + let tmpdir = tmpdir(); + + let foo = b"foo"; + check!(tmpdir.write("target", foo)); + check!(tmpdir.create_dir_all("b/c")); + let b = check!(tmpdir.open_dir("b")); + check!(symlink_dir("c/../../.", &b, "up")); + + let path = "b/up/target"; + let mut file = check!(tmpdir.open(path)); + let mut data = Vec::new(); + check!(file.read_to_end(&mut data)); + assert_eq!(data, foo); +} + +/// Same as `dotdot_more_in_middle_of_symlink`, but the symlink doesn't +/// include `c`. +/// +/// Windows doesn't appear to like symlinks that end with `/..`. +#[test] +#[cfg_attr(windows, ignore)] +fn dotdot_other_in_middle_of_symlink() { + let tmpdir = tmpdir(); + + let foo = b"foo"; + check!(tmpdir.write("target", foo)); + check!(tmpdir.create_dir_all("b/c")); + let c = check!(tmpdir.open_dir("b/c")); + check!(symlink_dir("../..", &c, "up")); + + let path = "b/c/up/target"; + let mut file = check!(tmpdir.open(path)); + let mut data = Vec::new(); + check!(file.read_to_end(&mut data)); + assert_eq!(data, foo); +} + +/// Like `dotdot_other_in_middle_of_symlink`, but with `/.` at the end. +/// +/// Windows doesn't appear to like symlinks that end with `/.`. +#[test] +#[cfg_attr(windows, ignore)] +fn dotdot_slashdot_other_in_middle_of_symlink() { + let tmpdir = tmpdir(); + + let foo = b"foo"; + check!(tmpdir.write("target", foo)); + check!(tmpdir.create_dir_all("b/c")); + let c = check!(tmpdir.open_dir("b/c")); + check!(symlink_dir("../../.", &c, "up")); + + let path = "b/c/up/target"; + let mut file = check!(tmpdir.open(path)); + let mut data = Vec::new(); + check!(file.read_to_end(&mut data)); + assert_eq!(data, foo); +} + +/// Same as `dotdot_more_in_middle_of_symlink`, but use a symlink that +/// doesn't end with `..`. +#[test] +fn dotdot_even_more_in_middle_of_symlink() { + let tmpdir = tmpdir(); + + let foo = b"foo"; + check!(tmpdir.create_dir_all("b/c")); + check!(tmpdir.write("b/target", foo)); + let b = check!(tmpdir.open_dir("b")); + check!(symlink_dir("c/../../b", &b, "up")); + + let path = "b/up/target"; + let mut file = check!(tmpdir.open(path)); + let mut data = Vec::new(); + check!(file.read_to_end(&mut data)); + assert_eq!(data, foo); +} + +/// Like `dotdot_even_more_in_middle_of_symlink`, but with a `/.` at the end. +/// +/// Windows doesn't appear to like symlinks that end with `/.`. +#[test] +#[cfg_attr(windows, ignore)] +fn dotdot_slashdot_even_more_in_middle_of_symlink() { + let tmpdir = tmpdir(); + + let foo = b"foo"; + check!(tmpdir.create_dir_all("b/c")); + check!(tmpdir.write("b/target", foo)); + let b = check!(tmpdir.open_dir("b")); + check!(symlink_dir("c/../../b/.", &b, "up")); + + let path = "b/up/target"; + let mut file = check!(tmpdir.open(path)); + let mut data = Vec::new(); + check!(file.read_to_end(&mut data)); + assert_eq!(data, foo); +} + +/// Same as `dotdot_even_more_in_middle_of_symlink`, but the symlink doesn't +/// include `c`. +#[test] +fn dotdot_even_other_in_middle_of_symlink() { + let tmpdir = tmpdir(); + + let foo = b"foo"; + check!(tmpdir.create_dir_all("b/c")); + check!(tmpdir.write("b/target", foo)); + let c = check!(tmpdir.open_dir("b/c")); + check!(symlink_dir("../../b", &c, "up")); + + let path = "b/c/up/target"; + let mut file = check!(tmpdir.open(path)); + let mut data = Vec::new(); + check!(file.read_to_end(&mut data)); + assert_eq!(data, foo); +} + +/// Like `dotdot_even_other_in_middle_of_symlink`, but with a `/.` at the end. +/// +/// Windows doesn't appear to like symlinks that end with `/.`. +#[test] +#[cfg_attr(windows, ignore)] +fn dotdot_slashdot_even_other_in_middle_of_symlink() { + let tmpdir = tmpdir(); + + let foo = b"foo"; + check!(tmpdir.create_dir_all("b/c")); + check!(tmpdir.write("b/target", foo)); + let c = check!(tmpdir.open_dir("b/c")); + check!(symlink_dir("../../b/.", &c, "up")); + + let path = "b/c/up/target"; + let mut file = check!(tmpdir.open(path)); + let mut data = Vec::new(); + check!(file.read_to_end(&mut data)); + assert_eq!(data, foo); +} + +/// Similar to `dotdot_in_middle_of_symlink`, but this time the symlink to +/// `..` does happen to be the end of the path, so we need to make sure +/// the implementation doesn't just do a stack pop when it sees the `..` +/// leaving us with an `O_PATH` directory handle. +#[test] +fn dotdot_at_end_of_symlink() { + let tmpdir = tmpdir(); + + let foo = b"foo"; + check!(tmpdir.write("target", foo)); + check!(tmpdir.create_dir("b")); + let b = check!(tmpdir.open_dir("b")); + check!(symlink_dir("..", &b, "up")); + + // Do some things with `path` that might break with an `O_PATH` fd. + // On Linux, the `permissions` part doesn't because cap-std uses + // /proc/self/fd. But the `read_dir` part does. + let path = "b/up"; + + let perms = check!(tmpdir.metadata(path)).permissions(); + check!(tmpdir.set_permissions(path, perms)); + + let contents = check!(tmpdir.read_dir(path)); + for entry in contents { + let _entry = check!(entry); + } +} + +/// Like `dotdot_at_end_of_symlink`, but with a `/.` at the end. +/// +/// Windows doesn't appear to like symlinks that end with `/.`. +#[test] +#[cfg_attr(windows, ignore)] +fn dotdot_slashdot_at_end_of_symlink() { + let tmpdir = tmpdir(); + + let foo = b"foo"; + check!(tmpdir.write("target", foo)); + check!(tmpdir.create_dir("b")); + let b = check!(tmpdir.open_dir("b")); + check!(symlink_dir("../.", &b, "up")); + + // Do some things with `path` that might break with an `O_PATH` fd. + // On Linux, the `permissions` part doesn't because cap-std uses + // /proc/self/fd. But the `read_dir` part does. + let path = "b/up"; + + let perms = check!(tmpdir.metadata(path)).permissions(); + check!(tmpdir.set_permissions(path, perms)); + + let contents = check!(tmpdir.read_dir(path)); + for entry in contents { + let _entry = check!(entry); + } +} + +/// Like `dotdot_at_end_of_symlink`, but do everything inside a new directory, +/// so that `MaybeOwnedFile` doesn't reopen `.` which would artificially give +/// us a non-`O_PATH` fd. +#[test] +fn dotdot_at_end_of_symlink_all_inside_dir() { + let tmpdir = tmpdir(); + + let foo = b"foo"; + check!(tmpdir.create_dir("dir")); + check!(tmpdir.write("dir/target", foo)); + check!(tmpdir.create_dir("dir/b")); + let b = check!(tmpdir.open_dir("dir/b")); + check!(symlink_dir("..", &b, "up")); + + // Do some things with `path` that might break with an `O_PATH` fd. + // On Linux, the `permissions` part doesn't because cap-std uses + // /proc/self/fd. But the `read_dir` part does. + let path = "dir/b/up"; + + let perms = check!(tmpdir.metadata(path)).permissions(); + check!(tmpdir.set_permissions(path, perms)); + + let contents = check!(tmpdir.read_dir(path)); + for entry in contents { + let _entry = check!(entry); + } +} + +/// `dotdot_at_end_of_symlink_all_inside_dir`, but with a `/.` at the end. +/// +/// Windows doesn't appear to like symlinks that end with `/.`. +#[test] +#[cfg_attr(windows, ignore)] +fn dotdot_slashdot_at_end_of_symlink_all_inside_dir() { + let tmpdir = tmpdir(); + + let foo = b"foo"; + check!(tmpdir.create_dir("dir")); + check!(tmpdir.write("dir/target", foo)); + check!(tmpdir.create_dir("dir/b")); + let b = check!(tmpdir.open_dir("dir/b")); + check!(symlink_dir("../.", &b, "up")); + + // Do some things with `path` that might break with an `O_PATH` fd. + // On Linux, the `permissions` part doesn't because cap-std uses + // /proc/self/fd. But the `read_dir` part does. + let path = "dir/b/up"; + + let perms = check!(tmpdir.metadata(path)).permissions(); + check!(tmpdir.set_permissions(path, perms)); + + let contents = check!(tmpdir.read_dir(path)); + for entry in contents { + let _entry = check!(entry); + } +} + +/// Ensure that a path of "/" is rejected. +#[test] +fn statat_slash() { + let tmpdir = tmpdir(); + + // FreeBSD 14+ uses `O_RESOLVE_BENEATH` which issues different errors. + #[cfg(target_os = "freebsd")] + { + error_contains!(tmpdir.metadata("/"), "Capabilities insufficient"); + error_contains!(tmpdir.metadata("/foo"), "Capabilities insufficient"); + error_contains!(tmpdir.symlink_metadata("/"), "Capabilities insufficient"); + error_contains!(tmpdir.symlink_metadata("/foo"), "Capabilities insufficient"); + } + + #[cfg(not(target_os = "freebsd"))] + { + error_contains!(tmpdir.metadata("/"), "a path led outside of the filesystem"); + error_contains!( + tmpdir.metadata("/foo"), + "a path led outside of the filesystem" + ); + error_contains!( + tmpdir.symlink_metadata("/"), + "a path led outside of the filesyste" + ); + error_contains!( + tmpdir.symlink_metadata("/foo"), + "a path led outside of the filesyste" + ); + } +} + +/// Test interactions between symlinks and trailing slashes. +#[test] +fn trailing_slash_symlink() { + let tmpdir = tmpdir(); + + check!(tmpdir.create_dir("sandbox")); + check!(symlink_dir("../outside", &tmpdir, "sandbox/hidden")); + check!(symlink_dir("hidden/", &tmpdir, "sandbox/indirect")); + + let sandbox = check!(tmpdir.open_dir("sandbox")); + + for path in ["hidden", "hidden/", "indirect", "indirect/"] { + error!( + sandbox.open_dir(path), + "a path led outside of the filesystem" + ); + error!( + sandbox.read_dir(path), + "a path led outside of the filesystem" + ); + error!( + sandbox.canonicalize(path), + "a path led outside of the filesystem" + ); + } +} + +/// Similar to `trailing_slash_symlink`, but populates the test directory +/// outside the sandbox, so it can cover more cases. +#[test] +fn trailing_slash_symlink_more() { + let tmpdir = tempfile::tempdir().unwrap(); + + check!(std::fs::create_dir(tmpdir.path().join("sandbox"))); + #[cfg(unix)] + { + check!(std::os::unix::fs::symlink( + "../outside", + tmpdir.path().join("sandbox/hidden") + )); + check!(std::os::unix::fs::symlink( + "hidden/", + tmpdir.path().join("sandbox/indirect") + )); + check!(std::os::unix::fs::symlink( + "/.", + tmpdir.path().join("sandbox/root_link") + )); + } + #[cfg(windows)] + { + check!(std::os::windows::fs::symlink_dir( + "../outside", + tmpdir.path().join("sandbox/hidden") + )); + check!(std::os::windows::fs::symlink_dir( + "hidden/", + tmpdir.path().join("sandbox/indirect") + )); + check!(std::os::windows::fs::symlink_dir( + "/.", + tmpdir.path().join("sandbox/root_link") + )); + } + #[cfg(not(any(unix, windows)))] + { + compile_error!("not implemented yet"); + } + + let tmpdir = check!(Dir::open_ambient_dir(tmpdir.path(), ambient_authority())); + + let sandbox = check!(tmpdir.open_dir("sandbox")); + + for path in [ + "hidden", + "hidden/", + "indirect", + "indirect/", + "root_link", + "root_link/", + ] { + error!( + sandbox.open_dir(path), + "a path led outside of the filesystem" + ); + error!( + sandbox.read_dir(path), + "a path led outside of the filesystem" + ); + error!( + sandbox.canonicalize(path), + "a path led outside of the filesystem" + ); + } +} diff --git a/crates/wasi/src/filesystem/primitives/tests/fs_utf8.rs b/crates/wasi/src/filesystem/primitives/tests/fs_utf8.rs new file mode 100644 index 000000000000..642569e2db2e --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/fs_utf8.rs @@ -0,0 +1,1704 @@ +// This file is derived from Rust's library/std/src/fs/tests.rs at revision +// e4b1d5841494d6eb7f4944c91a057e16b0f0a9ea. + +#![cfg_attr(io_error_uncategorized, feature(io_error_uncategorized))] +#![cfg(feature = "fs_utf8")] + +#[macro_use] +mod sys_common; +#[macro_use] +mod sys; + +use std::io::prelude::*; + +#[cfg(target_os = "macos")] +use crate::sys::weak::weak; +use camino::{Utf8Path as Path, Utf8PathBuf as PathBuf}; +use cap_std::ambient_authority; +use cap_std::fs_utf8::{self as fs, Dir, OpenOptions}; +#[cfg(target_os = "macos")] +use libc::{c_char, c_int}; +use std::io::{self, ErrorKind, SeekFrom}; +use std::str; +use std::sync::Arc; +#[cfg(not(racy_asserts))] // racy asserts are racy +use std::thread; +use std::time::{Duration, Instant}; +use sys_common::io::{tmpdir_utf8 as tmpdir, TempDirUtf8 as TempDir}; +use sys_common::symlink_junction_utf8 as symlink_junction; + +use rand::rngs::StdRng; +use rand::{RngCore, SeedableRng}; + +#[cfg(not(windows))] +fn symlink_contents, Q: AsRef>( + src: P, + tmpdir: &TempDir, + dst: Q, +) -> io::Result<()> { + tmpdir.symlink_contents(src, dst) +} +#[cfg(not(windows))] +fn symlink_dir, Q: AsRef>(src: P, tmpdir: &TempDir, dst: Q) -> io::Result<()> { + tmpdir.symlink(src, dst) +} +#[cfg(not(windows))] +fn symlink_file, Q: AsRef>( + src: P, + tmpdir: &TempDir, + dst: Q, +) -> io::Result<()> { + tmpdir.symlink(src, dst) +} +#[cfg(windows)] +fn symlink_dir, Q: AsRef>(src: P, tmpdir: &TempDir, dst: Q) -> io::Result<()> { + tmpdir.symlink_dir(src, dst) +} +#[cfg(windows)] +fn symlink_file, Q: AsRef>( + src: P, + tmpdir: &TempDir, + dst: Q, +) -> io::Result<()> { + tmpdir.symlink_file(src, dst) +} + +// Several test fail on windows if the user does not have permission to +// create symlinks (the `SeCreateSymbolicLinkPrivilege`). Instead of +// disabling these test on Windows, use this function to test whether we +// have permission, and return otherwise. This way, we still don't run these +// tests most of the time, but at least we do if the user has the right +// permissions. +pub fn got_symlink_permission(tmpdir: &TempDir) -> bool { + if cfg!(unix) { + return true; + } + let link = "some_hopefully_unique_link_name"; + + match symlink_file(r"nonexisting_target", tmpdir, link) { + // ERROR_PRIVILEGE_NOT_HELD = 1314 + Err(ref err) if err.raw_os_error() == Some(1314) => false, + Ok(_) | Err(_) => true, + } +} + +#[cfg(target_os = "macos")] +fn able_to_not_follow_symlinks_while_hard_linking() -> bool { + weak!(fn linkat(c_int, *const c_char, c_int, *const c_char, c_int) -> c_int); + linkat.get().is_some() +} + +#[cfg(not(target_os = "macos"))] +fn able_to_not_follow_symlinks_while_hard_linking() -> bool { + return true; +} + +#[test] +fn file_test_io_smoke_test() { + let message = "it's alright. have a good time"; + let tmpdir = tmpdir(); + let filename = "file_rt_io_file_test.txt"; + { + let mut write_stream = check!(tmpdir.create(filename)); + check!(write_stream.write(message.as_bytes())); + } + { + let mut read_stream = check!(tmpdir.open(filename)); + let mut read_buf = [0; 1028]; + let read_str = match check!(read_stream.read(&mut read_buf)) { + 0 => panic!("shouldn't happen"), + n => str::from_utf8(&read_buf[..n]).unwrap().to_string(), + }; + assert_eq!(read_str, message); + } + check!(tmpdir.remove_file(filename)); +} + +#[test] +fn invalid_path_raises() { + let tmpdir = tmpdir(); + let filename = "file_that_does_not_exist.txt"; + let result = tmpdir.open(filename); + + #[cfg(any(all(unix, not(target_os = "vxworks")), target_os = "wasi"))] + error!(result, "No such file or directory"); + #[cfg(target_os = "vxworks")] + error!(result, "no such file or directory"); + #[cfg(windows)] + error!(result, 2); // ERROR_FILE_NOT_FOUND +} + +#[test] +fn file_test_iounlinking_invalid_path_should_raise_condition() { + let tmpdir = tmpdir(); + let filename = "file_another_file_that_does_not_exist.txt"; + + let result = tmpdir.remove_file(filename); + + #[cfg(any(all(unix, not(target_os = "vxworks")), target_os = "wasi"))] + error!(result, "No such file or directory"); + #[cfg(target_os = "vxworks")] + error!(result, "no such file or directory"); + #[cfg(windows)] + error!(result, 2); // ERROR_FILE_NOT_FOUND +} + +#[test] +fn file_test_io_non_positional_read() { + let message: &str = "ten-four"; + let mut read_mem = [0; 8]; + let tmpdir = tmpdir(); + let filename = "file_rt_io_file_test_positional.txt"; + { + let mut rw_stream = check!(tmpdir.create(filename)); + check!(rw_stream.write(message.as_bytes())); + } + { + let mut read_stream = check!(tmpdir.open(filename)); + { + let read_buf = &mut read_mem[0..4]; + check!(read_stream.read(read_buf)); + } + { + let read_buf = &mut read_mem[4..8]; + check!(read_stream.read(read_buf)); + } + } + check!(tmpdir.remove_file(filename)); + let read_str = str::from_utf8(&read_mem).unwrap(); + assert_eq!(read_str, message); +} + +#[test] +fn file_test_io_seek_and_tell_smoke_test() { + let message = "ten-four"; + let mut read_mem = [0; 4]; + let set_cursor = 4 as u64; + let tell_pos_pre_read; + let tell_pos_post_read; + let tmpdir = tmpdir(); + let filename = "file_rt_io_file_test_seeking.txt"; + { + let mut rw_stream = check!(tmpdir.create(filename)); + check!(rw_stream.write(message.as_bytes())); + } + { + let mut read_stream = check!(tmpdir.open(filename)); + check!(read_stream.seek(SeekFrom::Start(set_cursor))); + tell_pos_pre_read = check!(read_stream.seek(SeekFrom::Current(0))); + check!(read_stream.read(&mut read_mem)); + tell_pos_post_read = check!(read_stream.seek(SeekFrom::Current(0))); + } + check!(tmpdir.remove_file(filename)); + let read_str = str::from_utf8(&read_mem).unwrap(); + assert_eq!(read_str, &message[4..8]); + assert_eq!(tell_pos_pre_read, set_cursor); + assert_eq!(tell_pos_post_read, message.len() as u64); +} + +#[test] +fn file_test_io_seek_and_write() { + let initial_msg = "food-is-yummy"; + let overwrite_msg = "-the-bar!!"; + let final_msg = "foo-the-bar!!"; + let seek_idx = 3; + let mut read_mem = [0; 13]; + let tmpdir = tmpdir(); + let filename = "file_rt_io_file_test_seek_and_write.txt"; + { + let mut rw_stream = check!(tmpdir.create(filename)); + check!(rw_stream.write(initial_msg.as_bytes())); + check!(rw_stream.seek(SeekFrom::Start(seek_idx))); + check!(rw_stream.write(overwrite_msg.as_bytes())); + } + { + let mut read_stream = check!(tmpdir.open(filename)); + check!(read_stream.read(&mut read_mem)); + } + check!(tmpdir.remove_file(filename)); + let read_str = str::from_utf8(&read_mem).unwrap(); + assert!(read_str == final_msg); +} + +#[test] +fn file_test_io_seek_shakedown() { + // 01234567890123 + let initial_msg = "qwer-asdf-zxcv"; + let chunk_one: &str = "qwer"; + let chunk_two: &str = "asdf"; + let chunk_three: &str = "zxcv"; + let mut read_mem = [0; 4]; + let tmpdir = tmpdir(); + let filename = "file_rt_io_file_test_seek_shakedown.txt"; + { + let mut rw_stream = check!(tmpdir.create(filename)); + check!(rw_stream.write(initial_msg.as_bytes())); + } + { + let mut read_stream = check!(tmpdir.open(filename)); + + check!(read_stream.seek(SeekFrom::End(-4))); + check!(read_stream.read(&mut read_mem)); + assert_eq!(str::from_utf8(&read_mem).unwrap(), chunk_three); + + check!(read_stream.seek(SeekFrom::Current(-9))); + check!(read_stream.read(&mut read_mem)); + assert_eq!(str::from_utf8(&read_mem).unwrap(), chunk_two); + + check!(read_stream.seek(SeekFrom::Start(0))); + check!(read_stream.read(&mut read_mem)); + assert_eq!(str::from_utf8(&read_mem).unwrap(), chunk_one); + } + check!(tmpdir.remove_file(filename)); +} + +#[test] +fn file_test_io_eof() { + let tmpdir = tmpdir(); + let filename = "file_rt_io_file_test_eof.txt"; + let mut buf = [0; 256]; + { + let oo = OpenOptions::new() + .create_new(true) + .write(true) + .read(true) + .clone(); + let mut rw = check!(tmpdir.open_with(filename, &oo)); + assert_eq!(check!(rw.read(&mut buf)), 0); + assert_eq!(check!(rw.read(&mut buf)), 0); + } + check!(tmpdir.remove_file(filename)); +} + +#[test] +#[cfg(unix)] +fn file_test_io_read_write_at() { + use cap_std::fs_utf8::FileExt; + + let tmpdir = tmpdir(); + let filename = "file_rt_io_file_test_read_write_at.txt"; + let mut buf = [0; 256]; + let write1 = "asdf"; + let write2 = "qwer-"; + let write3 = "-zxcv"; + let content = "qwer-asdf-zxcv"; + { + let oo = OpenOptions::new() + .create_new(true) + .write(true) + .read(true) + .clone(); + let mut rw = check!(tmpdir.open_with(filename, &oo)); + assert_eq!(check!(rw.write_at(write1.as_bytes(), 5)), write1.len()); + assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 0); + assert_eq!(check!(rw.read_at(&mut buf, 5)), write1.len()); + assert_eq!(str::from_utf8(&buf[..write1.len()]), Ok(write1)); + assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 0); + assert_eq!( + check!(rw.read_at(&mut buf[..write2.len()], 0)), + write2.len() + ); + assert_eq!(str::from_utf8(&buf[..write2.len()]), Ok("\0\0\0\0\0")); + assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 0); + assert_eq!(check!(rw.write(write2.as_bytes())), write2.len()); + assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 5); + assert_eq!(check!(rw.read(&mut buf)), write1.len()); + assert_eq!(str::from_utf8(&buf[..write1.len()]), Ok(write1)); + assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9); + assert_eq!( + check!(rw.read_at(&mut buf[..write2.len()], 0)), + write2.len() + ); + assert_eq!(str::from_utf8(&buf[..write2.len()]), Ok(write2)); + assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9); + assert_eq!(check!(rw.write_at(write3.as_bytes(), 9)), write3.len()); + assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9); + } + { + let mut read = check!(tmpdir.open(filename)); + assert_eq!(check!(read.read_at(&mut buf, 0)), content.len()); + assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content)); + assert_eq!(check!(read.seek(SeekFrom::Current(0))), 0); + assert_eq!(check!(read.seek(SeekFrom::End(-5))), 9); + assert_eq!(check!(read.read_at(&mut buf, 0)), content.len()); + assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content)); + assert_eq!(check!(read.seek(SeekFrom::Current(0))), 9); + assert_eq!(check!(read.read(&mut buf)), write3.len()); + assert_eq!(str::from_utf8(&buf[..write3.len()]), Ok(write3)); + assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14); + assert_eq!(check!(read.read_at(&mut buf, 0)), content.len()); + assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content)); + assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14); + assert_eq!(check!(read.read_at(&mut buf, 14)), 0); + assert_eq!(check!(read.read_at(&mut buf, 15)), 0); + assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14); + } + check!(tmpdir.remove_file(filename)); +} + +// Darwin doesn't have a way to change the permissions on a file, relative +// to a directory handle, with no read or write access, without blindly +// following symlinks. +#[test] +#[cfg(unix)] +#[cfg_attr( + any( + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "watchos", + target_os = "visionos", + ), + ignore +)] +fn set_get_unix_permissions() { + use cap_std::fs::PermissionsExt; + + let tmpdir = tmpdir(); + let filename = "set_get_unix_permissions"; + check!(tmpdir.create_dir(filename)); + let mask = 0o7777; + + check!(tmpdir.set_permissions(filename, fs::Permissions::from_mode(0))); + let metadata0 = check!(tmpdir.metadata(filename)); + assert_eq!(mask & metadata0.permissions().mode(), 0); + + check!(tmpdir.set_permissions(filename, fs::Permissions::from_mode(0o1777))); + let metadata1 = check!(tmpdir.metadata(filename)); + #[cfg(any(all(unix, not(target_os = "vxworks")), target_os = "wasi"))] + assert_eq!(mask & metadata1.permissions().mode(), 0o1777); + #[cfg(target_os = "vxworks")] + assert_eq!(mask & metadata1.permissions().mode(), 0o0777); +} + +#[test] +#[cfg(windows)] +fn file_test_io_seek_read_write() { + use cap_std::fs_utf8::FileExt; + + let tmpdir = tmpdir(); + let filename = "file_rt_io_file_test_seek_read_write.txt"; + let mut buf = [0; 256]; + let write1 = "asdf"; + let write2 = "qwer-"; + let write3 = "-zxcv"; + let content = "qwer-asdf-zxcv"; + { + let oo = OpenOptions::new() + .create_new(true) + .write(true) + .read(true) + .clone(); + let mut rw = check!(tmpdir.open_with(filename, &oo)); + assert_eq!(check!(rw.seek_write(write1.as_bytes(), 5)), write1.len()); + assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9); + assert_eq!(check!(rw.seek_read(&mut buf, 5)), write1.len()); + assert_eq!(str::from_utf8(&buf[..write1.len()]), Ok(write1)); + assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9); + assert_eq!(check!(rw.seek(SeekFrom::Start(0))), 0); + assert_eq!(check!(rw.write(write2.as_bytes())), write2.len()); + assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 5); + assert_eq!(check!(rw.read(&mut buf)), write1.len()); + assert_eq!(str::from_utf8(&buf[..write1.len()]), Ok(write1)); + assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9); + assert_eq!( + check!(rw.seek_read(&mut buf[..write2.len()], 0)), + write2.len() + ); + assert_eq!(str::from_utf8(&buf[..write2.len()]), Ok(write2)); + assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 5); + assert_eq!(check!(rw.seek_write(write3.as_bytes(), 9)), write3.len()); + assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 14); + } + { + let mut read = check!(tmpdir.open(filename)); + assert_eq!(check!(read.seek_read(&mut buf, 0)), content.len()); + assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content)); + assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14); + assert_eq!(check!(read.seek(SeekFrom::End(-5))), 9); + assert_eq!(check!(read.seek_read(&mut buf, 0)), content.len()); + assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content)); + assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14); + assert_eq!(check!(read.seek(SeekFrom::End(-5))), 9); + assert_eq!(check!(read.read(&mut buf)), write3.len()); + assert_eq!(str::from_utf8(&buf[..write3.len()]), Ok(write3)); + assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14); + assert_eq!(check!(read.seek_read(&mut buf, 0)), content.len()); + assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content)); + assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14); + assert_eq!(check!(read.seek_read(&mut buf, 14)), 0); + assert_eq!(check!(read.seek_read(&mut buf, 15)), 0); + } + check!(tmpdir.remove_file(filename)); +} + +#[test] +fn file_test_stat_is_correct_on_is_file() { + let tmpdir = tmpdir(); + let filename = "file_stat_correct_on_is_file.txt"; + { + let mut opts = OpenOptions::new(); + let mut fs = check!(tmpdir.open_with(filename, opts.read(true).write(true).create(true))); + let msg = "hw"; + fs.write(msg.as_bytes()).unwrap(); + + let fstat_res = check!(fs.metadata()); + assert!(fstat_res.is_file()); + } + let stat_res_fn = check!(tmpdir.metadata(filename)); + assert!(stat_res_fn.is_file()); + check!(tmpdir.remove_file(filename)); +} + +#[test] +fn file_test_stat_is_correct_on_is_dir() { + let tmpdir = tmpdir(); + let filename = "file_stat_correct_on_is_dir"; + check!(tmpdir.create_dir(filename)); + let stat_res_fn = check!(tmpdir.metadata(filename)); + assert!(stat_res_fn.is_dir()); + check!(tmpdir.remove_dir(filename)); +} + +#[test] +fn file_test_fileinfo_false_when_checking_is_file_on_a_directory() { + let tmpdir = tmpdir(); + let dir = "fileinfo_false_on_dir"; + check!(tmpdir.create_dir(dir)); + assert!(!tmpdir.is_file(dir)); + check!(tmpdir.remove_dir(dir)); +} + +#[test] +fn file_test_fileinfo_check_exists_before_and_after_file_creation() { + let tmpdir = tmpdir(); + let file = "fileinfo_check_exists_b_and_a.txt"; + check!(check!(tmpdir.create(file)).write(b"foo")); + assert!(tmpdir.exists(file)); + assert!(tmpdir.try_exists(file).unwrap()); + check!(tmpdir.remove_file(file)); + assert!(!tmpdir.exists(file)); + assert!(!tmpdir.try_exists(file).unwrap()); +} + +#[test] +fn file_test_directoryinfo_check_exists_before_and_after_mkdir() { + let tmpdir = tmpdir(); + let dir = "before_and_after_dir"; + assert!(!tmpdir.exists(dir)); + check!(tmpdir.create_dir(dir)); + assert!(tmpdir.exists(dir)); + assert!(tmpdir.is_dir(dir)); + check!(tmpdir.remove_dir(dir)); + assert!(!tmpdir.exists(dir)); +} + +#[test] +fn file_test_directoryinfo_readdir() { + let tmpdir = tmpdir(); + let dir = "di_readdir"; + check!(tmpdir.create_dir(dir)); + let prefix = "foo"; + for n in 0..3 { + let f = format!("{}.txt", n); + let mut w = check!(tmpdir.create(&f)); + let msg_str = format!("{}{}", prefix, n.to_string()); + let msg = msg_str.as_bytes(); + check!(w.write(msg)); + } + let files = check!(tmpdir.read_dir(dir)); + let mut mem = [0; 4]; + for f in files { + let f = f.unwrap().file_name().unwrap(); + { + check!(check!(tmpdir.open(&f)).read(&mut mem)); + let read_str = str::from_utf8(&mem).unwrap(); + let expected = format!("{}{}", prefix, f.as_str()); + assert_eq!(expected, read_str); + } + check!(tmpdir.remove_file(&f)); + } + check!(tmpdir.remove_dir(dir)); +} + +#[test] +fn file_create_new_already_exists_error() { + let tmpdir = tmpdir(); + let file = "file_create_new_error_exists"; + check!(tmpdir.create(file)); + let e = tmpdir + .open_with(file, &fs::OpenOptions::new().write(true).create_new(true)) + .unwrap_err(); + assert_eq!(e.kind(), ErrorKind::AlreadyExists); +} + +#[test] +fn mkdir_path_already_exists_error() { + let tmpdir = tmpdir(); + let dir = "mkdir_error_twice"; + check!(tmpdir.create_dir(dir)); + let e = tmpdir.create_dir(dir).unwrap_err(); + assert_eq!(e.kind(), ErrorKind::AlreadyExists); +} + +#[test] +fn recursive_mkdir() { + let tmpdir = tmpdir(); + let dir = "d1/d2"; + check!(tmpdir.create_dir_all(dir)); + assert!(tmpdir.is_dir(dir)); +} + +#[test] +fn recursive_mkdir_failure() { + let tmpdir = tmpdir(); + let dir = "d1"; + let file = "f1"; + + check!(tmpdir.create_dir_all(&dir)); + check!(tmpdir.create(&file)); + + let result = tmpdir.create_dir_all(&file); + + assert!(result.is_err()); +} + +#[test] +#[cfg(not(racy_asserts))] // racy asserts are racy +fn concurrent_recursive_mkdir() { + for _ in 0..100 { + let dir = tmpdir(); + let mut name = PathBuf::from("a"); + for _ in 0..40 { + name = name.join("a"); + } + let mut join = vec![]; + for _ in 0..8 { + let dir = check!(dir.try_clone()); + let name = name.clone(); + join.push(thread::spawn(move || { + check!(dir.create_dir_all(&name)); + })) + } + + // No `Display` on result of `join()` + join.drain(..).map(|join| join.join().unwrap()).count(); + } +} + +#[test] +fn recursive_mkdir_slash() { + let tmpdir = tmpdir(); + error_contains!( + tmpdir.create_dir_all(Path::new("/")), + "a path led outside of the filesystem" + ); +} + +#[test] +fn recursive_mkdir_dot() { + let tmpdir = tmpdir(); + check!(tmpdir.create_dir_all(Path::new("."))); +} + +#[test] +fn recursive_mkdir_empty() { + let tmpdir = tmpdir(); + check!(tmpdir.create_dir_all(Path::new(""))); +} + +#[test] +fn recursive_rmdir() { + let tmpdir = tmpdir(); + let d1 = PathBuf::from("d1"); + let dt = d1.join("t"); + let dtt = dt.join("t"); + let d2 = PathBuf::from("d2"); + let canary = d2.join("do_not_delete"); + check!(tmpdir.create_dir_all(&dtt)); + check!(tmpdir.create_dir_all(&d2)); + check!(check!(tmpdir.create(&canary)).write(b"foo")); + check!(symlink_junction(d2, &tmpdir, &dt.join("d2"))); + let _ = symlink_file(&canary, &tmpdir, &d1.join("canary")); + check!(tmpdir.remove_dir_all(&d1)); + + assert!(!tmpdir.is_dir(d1)); + assert!(tmpdir.exists(canary)); +} + +// See the comments on `create_dir_all_with_junctions` in tests/fs.rs about Windows. +#[cfg_attr(windows, ignore)] +#[test] +fn recursive_rmdir_of_symlink() { + // test we do not recursively delete a symlink but only dirs. + let tmpdir = tmpdir(); + let link = "d1"; + let dir = "d2"; + let canary = "do_not_delete"; + check!(tmpdir.create_dir_all(&dir)); + check!(check!(tmpdir.create(&canary)).write(b"foo")); + check!(symlink_junction(&dir, &tmpdir, link)); + check!(tmpdir.remove_dir_all(link)); + + assert!(!tmpdir.is_dir(link)); + assert!(tmpdir.exists(canary)); +} + +#[test] +fn recursive_rmdir_of_file_fails() { + // test we do not delete a directly specified file. + let tmpdir = tmpdir(); + let canary = "do_not_delete"; + check!(check!(tmpdir.create(canary)).write(b"foo")); + let result = tmpdir.remove_dir_all(canary); + #[cfg(unix)] + error!(result, "Not a directory"); + #[cfg(windows)] + error!(result, 267); // ERROR_DIRECTORY - The directory name is invalid. + assert!(result.is_err()); + assert!(tmpdir.exists(canary)); +} + +#[test] +// only Windows makes a distinction between file and directory symlinks. +#[cfg(windows)] +fn recursive_rmdir_of_file_symlink() { + let tmpdir = tmpdir(); + if !got_symlink_permission(&tmpdir) { + return; + }; + + let f1 = "f1"; + let f2 = "f2"; + check!(check!(tmpdir.create(&f1)).write(b"foo")); + check!(symlink_file(&f1, &tmpdir, &f2)); + match tmpdir.remove_dir_all(&f2) { + Ok(..) => panic!("wanted a failure"), + Err(..) => {} + } +} + +#[test] +#[ignore] // takes too much time +fn recursive_rmdir_toctou() { + // Test for time-of-check to time-of-use issues. + // + // Scenario: + // The attacker wants to get directory contents deleted, to which he does not + // have access. He has a way to get a privileged Rust binary call + // `std::fs::remove_dir_all()` on a directory he controls, e.g. in his home + // directory. + // + // The POC sets up the `attack_dest/attack_file` which the attacker wants to + // have deleted. The attacker repeatedly creates a directory and replaces + // it with a symlink from `victim_del` to `attack_dest` while the victim + // code calls `std::fs::remove_dir_all()` on `victim_del`. After a few + // seconds the attack has succeeded and `attack_dest/attack_file` is + // deleted. + let tmpdir = tmpdir(); + let victim_del_path = "victim_del"; + + // setup dest + let attack_dest_dir = "attack_dest"; + let attack_dest_dir = Path::new(attack_dest_dir); + tmpdir.create_dir(attack_dest_dir).unwrap(); + let attack_dest_file = "attack_dest/attack_file"; + tmpdir.create(attack_dest_file).unwrap(); + + let drop_canary_arc = Arc::new(()); + let drop_canary_weak = Arc::downgrade(&drop_canary_arc); + + eprintln!("x: {:?}", &victim_del_path); + + // victim just continuously removes `victim_del` + let tmpdir_clone = tmpdir.try_clone().unwrap(); + let _t = thread::spawn(move || { + while drop_canary_weak.upgrade().is_some() { + let _ = tmpdir_clone.remove_dir_all(victim_del_path); + } + }); + + // attacker (could of course be in a separate process) + let start_time = Instant::now(); + while Instant::now().duration_since(start_time) < Duration::from_secs(1000) { + if !tmpdir.exists(attack_dest_file) { + panic!( + "Victim deleted symlinked file outside of victim_del. Attack succeeded in {:?}.", + Instant::now().duration_since(start_time) + ); + } + let _ = tmpdir.create_dir(victim_del_path); + let _ = tmpdir.remove_dir(victim_del_path); + let _ = symlink_dir(attack_dest_dir, &tmpdir, victim_del_path); + } +} + +#[test] +fn unicode_path_is_dir() { + let tmpdir = tmpdir(); + + assert!(tmpdir.is_dir(Path::new("."))); + assert!(!tmpdir.is_dir(Path::new("test/stdtest/fs.rs"))); + + let mut dirpath = PathBuf::new(); + dirpath.push("test-가一ー你好"); + check!(tmpdir.create_dir(&dirpath)); + assert!(tmpdir.is_dir(&dirpath)); + + let mut filepath = dirpath; + filepath.push("unicode-file-\u{ac00}\u{4e00}\u{30fc}\u{4f60}\u{597d}.rs"); + check!(tmpdir.create(&filepath)); // ignore return; touch only + assert!(!tmpdir.is_dir(&filepath)); + assert!(tmpdir.exists(filepath)); +} + +#[test] +fn unicode_path_exists() { + let tmpdir = tmpdir(); + + assert!(tmpdir.exists(Path::new("."))); + assert!(!tmpdir.exists(Path::new("test/nonexistent-bogus-path"))); + + let unicode = PathBuf::new(); + let unicode = unicode.join("test-각丁ー再见"); + check!(tmpdir.create_dir(&unicode)); + assert!(tmpdir.exists(unicode)); + assert!(!tmpdir.exists(Path::new("test/unicode-bogus-path-각丁ー再见"))); +} + +#[test] +fn copy_file_does_not_exist() { + let tmpdir = tmpdir(); + let from = Path::new("test/nonexistent-bogus-path"); + let to = Path::new("test/other-bogus-path"); + + match tmpdir.copy(&from, &tmpdir, &to) { + Ok(..) => panic!(), + Err(..) => { + assert!(!tmpdir.exists(from)); + assert!(!tmpdir.exists(to)); + } + } +} + +#[test] +fn copy_src_does_not_exist() { + let tmpdir = tmpdir(); + let from = Path::new("test/nonexistent-bogus-path"); + let to = "out.txt"; + check!(check!(tmpdir.create(&to)).write(b"hello")); + assert!(tmpdir.copy(&from, &tmpdir, &to).is_err()); + assert!(!tmpdir.exists(from)); + let mut v = Vec::new(); + check!(check!(tmpdir.open(&to)).read_to_end(&mut v)); + assert_eq!(v, b"hello"); +} + +#[test] +fn copy_file_ok() { + let tmpdir = tmpdir(); + let input = "in.txt"; + let out = "out.txt"; + + check!(check!(tmpdir.create(&input)).write(b"hello")); + check!(tmpdir.copy(&input, &tmpdir, &out)); + let mut v = Vec::new(); + check!(check!(tmpdir.open(&out)).read_to_end(&mut v)); + assert_eq!(v, b"hello"); + + assert_eq!( + check!(tmpdir.metadata(input)).permissions(), + check!(tmpdir.metadata(out)).permissions() + ); +} + +#[test] +fn copy_file_dst_dir() { + let tmpdir = tmpdir(); + let out = "out"; + + check!(tmpdir.create(&out)); + match tmpdir.copy(&*out, &tmpdir, ".") { + Ok(..) => panic!(), + Err(..) => {} + } +} + +#[test] +fn copy_file_dst_exists() { + let tmpdir = tmpdir(); + let input = "in"; + let output = "out"; + + check!(check!(tmpdir.create(&input)).write("foo".as_bytes())); + check!(check!(tmpdir.create(&output)).write("bar".as_bytes())); + check!(tmpdir.copy(&input, &tmpdir, &output)); + + let mut v = Vec::new(); + check!(check!(tmpdir.open(&output)).read_to_end(&mut v)); + assert_eq!(v, b"foo".to_vec()); +} + +#[test] +fn copy_file_src_dir() { + let tmpdir = tmpdir(); + let out = "out"; + + match tmpdir.copy(".", &tmpdir, &out) { + Ok(..) => panic!(), + Err(..) => {} + } + assert!(!tmpdir.exists(out)); +} + +#[test] +fn copy_file_preserves_perm_bits() { + let tmpdir = tmpdir(); + let input = "in.txt"; + let out = "out.txt"; + + let attr = check!(check!(tmpdir.create(&input)).metadata()); + let mut p = attr.permissions(); + p.set_readonly(true); + check!(tmpdir.set_permissions(&input, p)); + check!(tmpdir.copy(&input, &tmpdir, &out)); + assert!(check!(tmpdir.metadata(out)).permissions().readonly()); + check!(tmpdir.set_permissions(&input, attr.permissions())); + check!(tmpdir.set_permissions(&out, attr.permissions())); +} + +#[test] +#[cfg(windows)] +fn copy_file_preserves_streams() { + let tmp = tmpdir(); + check!(check!(tmp.create("in.txt:bunny")).write("carrot".as_bytes())); + assert_eq!(check!(tmp.copy("in.txt", &tmp, "out.txt")), 0); + assert_eq!(check!(tmp.metadata("out.txt")).len(), 0); + let mut v = Vec::new(); + check!(check!(tmp.open("out.txt:bunny")).read_to_end(&mut v)); + assert_eq!(v, b"carrot".to_vec()); +} + +#[test] +fn copy_file_returns_metadata_len() { + let tmp = tmpdir(); + let in_path = "in.txt"; + let out_path = "out.txt"; + check!(check!(tmp.create(&in_path)).write(b"lettuce")); + #[cfg(windows)] + check!(check!(tmp.create("in.txt:bunny")).write(b"carrot")); + let copied_len = check!(tmp.copy(&in_path, &tmp, &out_path)); + assert_eq!(check!(tmp.metadata(out_path)).len(), copied_len); +} + +#[test] +fn copy_file_follows_dst_symlink() { + let tmp = tmpdir(); + if !got_symlink_permission(&tmp) { + return; + }; + + let in_path = "in.txt"; + let out_path = "out.txt"; + let out_path_symlink = "out_symlink.txt"; + + check!(tmp.write(&in_path, "foo")); + check!(tmp.write(&out_path, "bar")); + check!(symlink_file(&out_path, &tmp, &out_path_symlink)); + + check!(tmp.copy(&in_path, &tmp, &out_path_symlink)); + + assert!(check!(tmp.symlink_metadata(out_path_symlink)) + .file_type() + .is_symlink()); + assert_eq!(check!(tmp.read(&out_path_symlink)), b"foo".to_vec()); + assert_eq!(check!(tmp.read(&out_path)), b"foo".to_vec()); +} + +#[test] +fn symlinks_work() { + let tmpdir = tmpdir(); + if !got_symlink_permission(&tmpdir) { + return; + }; + + let input = "in.txt"; + let out = "out.txt"; + + check!(check!(tmpdir.create(&input)).write("foobar".as_bytes())); + check!(symlink_file(&input, &tmpdir, &out)); + assert!(check!(tmpdir.symlink_metadata(out)) + .file_type() + .is_symlink()); + assert_eq!( + check!(tmpdir.metadata(&out)).len(), + check!(tmpdir.metadata(&input)).len() + ); + let mut v = Vec::new(); + check!(check!(tmpdir.open(&out)).read_to_end(&mut v)); + assert_eq!(v, b"foobar".to_vec()); +} + +#[test] +fn symlink_noexist() { + // Symlinks can point to things that don't exist + let tmpdir = tmpdir(); + if !got_symlink_permission(&tmpdir) { + return; + }; + + // Use a relative path for testing. Symlinks get normalized by Windows, + // so we might not get the same path back for absolute paths + check!(symlink_file(&"foo", &tmpdir, "bar")); + assert_eq!(check!(tmpdir.read_link("bar")).as_str(), "foo"); +} + +#[test] +fn read_link() { + if cfg!(windows) { + // directory symlink + let root = Dir::open_ambient_dir(r"C:\", ambient_authority()).unwrap(); + error_contains!( + root.read_link(r"Users\All Users"), + "a path led outside of the filesystem" + ); + // junction + error_contains!( + root.read_link(r"Users\Default User"), + "a path led outside of the filesystem" + ); + // junction with special permissions + error_contains!( + root.read_link(r"Documents and Settings\"), + "a path led outside of the filesystem" + ); + } + let tmpdir = tmpdir(); + let link = "link"; + if !got_symlink_permission(&tmpdir) { + return; + }; + check!(symlink_file(&"foo", &tmpdir, &link)); + assert_eq!(check!(tmpdir.read_link(&link)).as_str(), "foo"); +} + +#[test] +fn readlink_not_symlink() { + let tmpdir = tmpdir(); + match tmpdir.read_link(".") { + Ok(..) => panic!("wanted a failure"), + Err(..) => {} + } +} + +#[cfg(not(windows))] +#[test] +fn read_link_contents() { + let tmpdir = tmpdir(); + let link = "link"; + if !got_symlink_permission(&tmpdir) { + return; + }; + check!(symlink_file(&"foo", &tmpdir, &link)); + assert_eq!(check!(tmpdir.read_link_contents(&link)).as_str(), "foo"); +} + +#[cfg(not(windows))] +#[test] +fn read_link_contents_absolute() { + let tmpdir = tmpdir(); + let link = "link"; + if !got_symlink_permission(&tmpdir) { + return; + }; + check!(symlink_contents(&"/foo", &tmpdir, &link)); + assert_eq!(check!(tmpdir.read_link_contents(&link)).as_str(), "/foo"); +} + +#[test] +fn links_work() { + let tmpdir = tmpdir(); + let input = "in.txt"; + let out = "out.txt"; + + check!(check!(tmpdir.create(&input)).write("foobar".as_bytes())); + check!(tmpdir.hard_link(&input, &tmpdir, &out)); + assert_eq!( + check!(tmpdir.metadata(&out)).len(), + check!(tmpdir.metadata(&input)).len() + ); + assert_eq!( + check!(tmpdir.metadata(&out)).len(), + check!(tmpdir.metadata(input)).len() + ); + let mut v = Vec::new(); + check!(check!(tmpdir.open(&out)).read_to_end(&mut v)); + assert_eq!(v, b"foobar".to_vec()); + + // can't link to yourself + match tmpdir.hard_link(&input, &tmpdir, &input) { + Ok(..) => panic!("wanted a failure"), + Err(..) => {} + } + // can't link to something that doesn't exist + match tmpdir.hard_link("foo", &tmpdir, "bar") { + Ok(..) => panic!("wanted a failure"), + Err(..) => {} + } +} + +#[test] +fn chmod_works() { + let tmpdir = tmpdir(); + let file = "in.txt"; + + check!(tmpdir.create(&file)); + let attr = check!(tmpdir.metadata(&file)); + assert!(!attr.permissions().readonly()); + let mut p = attr.permissions(); + p.set_readonly(true); + check!(tmpdir.set_permissions(&file, p.clone())); + let attr = check!(tmpdir.metadata(&file)); + assert!(attr.permissions().readonly()); + + match tmpdir.set_permissions("foo", p.clone()) { + Ok(..) => panic!("wanted an error"), + Err(..) => {} + } + + p.set_readonly(false); + check!(tmpdir.set_permissions(&file, p)); +} + +#[test] +fn fchmod_works() { + let tmpdir = tmpdir(); + let path = "in.txt"; + + let file = check!(tmpdir.create(&path)); + let attr = check!(tmpdir.metadata(&path)); + assert!(!attr.permissions().readonly()); + let mut p = attr.permissions(); + p.set_readonly(true); + check!(file.set_permissions(p.clone())); + let attr = check!(tmpdir.metadata(&path)); + assert!(attr.permissions().readonly()); + + p.set_readonly(false); + check!(file.set_permissions(p)); +} + +#[test] +fn sync_doesnt_kill_anything() { + let tmpdir = tmpdir(); + let path = "in.txt"; + + let mut file = check!(tmpdir.create(&path)); + check!(file.sync_all()); + check!(file.sync_data()); + check!(file.write(b"foo")); + check!(file.sync_all()); + check!(file.sync_data()); +} + +#[test] +fn truncate_works() { + let tmpdir = tmpdir(); + let path = "in.txt"; + + let mut file = check!(tmpdir.create(&path)); + check!(file.write(b"foo")); + check!(file.sync_all()); + + // Do some simple things with truncation + assert_eq!(check!(file.metadata()).len(), 3); + check!(file.set_len(10)); + assert_eq!(check!(file.metadata()).len(), 10); + check!(file.write(b"bar")); + check!(file.sync_all()); + assert_eq!(check!(file.metadata()).len(), 10); + + let mut v = Vec::new(); + check!(check!(tmpdir.open(&path)).read_to_end(&mut v)); + assert_eq!(v, b"foobar\0\0\0\0".to_vec()); + + // Truncate to a smaller length, don't seek, and then write something. + // Ensure that the intermediate zeroes are all filled in (we have `seek`ed + // past the end of the file). + check!(file.set_len(2)); + assert_eq!(check!(file.metadata()).len(), 2); + check!(file.write(b"wut")); + check!(file.sync_all()); + assert_eq!(check!(file.metadata()).len(), 9); + let mut v = Vec::new(); + check!(check!(tmpdir.open(&path)).read_to_end(&mut v)); + assert_eq!(v, b"fo\0\0\0\0wut".to_vec()); +} + +#[test] +fn open_flavors() { + use cap_std::fs::OpenOptions as OO; + fn c(t: &T) -> T { + t.clone() + } + + let tmpdir = tmpdir(); + + let mut r = OO::new(); + r.read(true); + let mut w = OO::new(); + w.write(true); + let mut rw = OO::new(); + rw.read(true).write(true); + let mut a = OO::new(); + a.append(true); + let mut ra = OO::new(); + ra.read(true).append(true); + + #[cfg(windows)] + let invalid_options = 87; // ERROR_INVALID_PARAMETER + #[cfg(any(all(unix, not(target_os = "vxworks")), target_os = "wasi"))] + let invalid_options = "Invalid argument"; + #[cfg(target_os = "vxworks")] + let invalid_options = "invalid argument"; + + // Test various combinations of creation modes and access modes. + // + // Allowed: + // creation mode | read | write | read-write | append | read-append + // | :-----------------------|:-----:|:-----:|:----------:|:------:|: + // -----------:| not set (open existing) | X | X | X | X + // | X | create | | X | X | + // X | X | truncate | | X | X + // | | | create and truncate | | X | X + // | | | create_new | | X | X + // | X | X | + // + // tested in reverse order, so 'create_new' creates the file, and 'open + // existing' opens it. + + // write-only + check!(tmpdir.open_with("a", c(&w).create_new(true))); + check!(tmpdir.open_with("a", c(&w).create(true).truncate(true))); + check!(tmpdir.open_with("a", c(&w).truncate(true))); + check!(tmpdir.open_with("a", c(&w).create(true))); + check!(tmpdir.open_with("a", &c(&w))); + + // read-only + error!( + tmpdir.open_with("b", c(&r).create_new(true)), + invalid_options + ); + error!( + tmpdir.open_with("b", c(&r).create(true).truncate(true)), + invalid_options + ); + error!(tmpdir.open_with("b", c(&r).truncate(true)), invalid_options); + error!(tmpdir.open_with("b", c(&r).create(true)), invalid_options); + check!(tmpdir.open_with("a", &c(&r))); // try opening the file created with write_only + + // read-write + check!(tmpdir.open_with("c", c(&rw).create_new(true))); + check!(tmpdir.open_with("c", c(&rw).create(true).truncate(true))); + check!(tmpdir.open_with("c", c(&rw).truncate(true))); + check!(tmpdir.open_with("c", c(&rw).create(true))); + check!(tmpdir.open_with("c", &c(&rw))); + + // append + check!(tmpdir.open_with("d", c(&a).create_new(true))); + error!( + tmpdir.open_with("d", c(&a).create(true).truncate(true)), + invalid_options + ); + error!(tmpdir.open_with("d", c(&a).truncate(true)), invalid_options); + check!(tmpdir.open_with("d", c(&a).create(true))); + check!(tmpdir.open_with("d", &c(&a))); + + // read-append + check!(tmpdir.open_with("e", c(&ra).create_new(true))); + error!( + tmpdir.open_with("e", c(&ra).create(true).truncate(true)), + invalid_options + ); + error!( + tmpdir.open_with("e", c(&ra).truncate(true)), + invalid_options + ); + check!(tmpdir.open_with("e", c(&ra).create(true))); + check!(tmpdir.open_with("e", &c(&ra))); + + // Test opening a file without setting an access mode + let mut blank = OO::new(); + error!(tmpdir.open_with("f", blank.create(true)), invalid_options); + + // Test write works + check!(check!(tmpdir.create("h")).write("foobar".as_bytes())); + + // Test write fails for read-only + check!(tmpdir.open_with("h", &r)); + { + let mut f = check!(tmpdir.open_with("h", &r)); + assert!(f.write("wut".as_bytes()).is_err()); + } + + // Test write overwrites + { + let mut f = check!(tmpdir.open_with("h", &c(&w))); + check!(f.write("baz".as_bytes())); + } + { + let mut f = check!(tmpdir.open_with("h", &c(&r))); + let mut b = vec![0; 6]; + check!(f.read(&mut b)); + assert_eq!(b, "bazbar".as_bytes()); + } + + // Test truncate works + { + let mut f = check!(tmpdir.open_with("h", c(&w).truncate(true))); + check!(f.write("foo".as_bytes())); + } + assert_eq!(check!(tmpdir.metadata("h")).len(), 3); + + // Test append works + assert_eq!(check!(tmpdir.metadata("h")).len(), 3); + { + let mut f = check!(tmpdir.open_with("h", &c(&a))); + check!(f.write("bar".as_bytes())); + } + assert_eq!(check!(tmpdir.metadata("h")).len(), 6); + + // Test .append(true) equals .write(true).append(true) + { + let mut f = check!(tmpdir.open_with("h", c(&w).append(true))); + check!(f.write("baz".as_bytes())); + } + assert_eq!(check!(tmpdir.metadata("h")).len(), 9); +} + +#[test] +fn _assert_send_sync() { + fn _assert_send_sync() {} + _assert_send_sync::(); +} + +#[test] +fn binary_file() { + let mut bytes = [0; 1024]; + StdRng::from_os_rng().fill_bytes(&mut bytes); + + let tmpdir = tmpdir(); + + check!(check!(tmpdir.create("test")).write(&bytes)); + let mut v = Vec::new(); + check!(check!(tmpdir.open("test")).read_to_end(&mut v)); + assert!(v == &bytes[..]); +} + +#[test] +fn write_then_read() { + let mut bytes = [0; 1024]; + StdRng::from_os_rng().fill_bytes(&mut bytes); + + let tmpdir = tmpdir(); + + check!(tmpdir.write("test", &bytes[..])); + let v = check!(tmpdir.read("test")); + assert!(v == &bytes[..]); + + check!(tmpdir.write("not-utf8", &[0xFF])); + error_contains!( + tmpdir.read_to_string("not-utf8"), + "stream did not contain valid UTF-8" + ); + + let s = "𐁁𐀓𐀠𐀴𐀍"; + check!(tmpdir.write("utf8", s.as_bytes())); + let string = check!(tmpdir.read_to_string("utf8")); + assert_eq!(string, s); +} + +#[test] +fn file_try_clone() { + let tmpdir = tmpdir(); + + let mut f1 = check!(tmpdir.open_with( + "test", + OpenOptions::new().read(true).write(true).create(true) + )); + let mut f2 = check!(f1.try_clone()); + + check!(f1.write_all(b"hello world")); + check!(f1.seek(SeekFrom::Start(2))); + + let mut buf = vec![]; + check!(f2.read_to_end(&mut buf)); + assert_eq!(buf, b"llo world"); + drop(f2); + + check!(f1.write_all(b"!")); +} + +#[test] +#[cfg(not(windows))] +fn unlink_readonly() { + let tmpdir = tmpdir(); + let path = "file"; + check!(tmpdir.create(&path)); + let mut perm = check!(tmpdir.metadata(&path)).permissions(); + perm.set_readonly(true); + check!(tmpdir.set_permissions(&path, perm)); + check!(tmpdir.remove_file(&path)); +} + +#[test] +fn mkdir_trailing_slash() { + let tmpdir = tmpdir(); + let path = PathBuf::from("file"); + check!(tmpdir.create_dir_all(&path.join("a/"))); +} + +#[test] +fn canonicalize_works_simple() { + let tmpdir = tmpdir(); + let file = Path::new("test"); + tmpdir.create(&file).unwrap(); + assert_eq!(tmpdir.canonicalize(&file).unwrap(), file); +} + +#[test] +fn realpath_works() { + let tmpdir = tmpdir(); + if !got_symlink_permission(&tmpdir) { + return; + }; + + let file = PathBuf::from("test"); + let dir = PathBuf::from("test2"); + let link = dir.join("link"); + let linkdir = PathBuf::from("test3"); + + tmpdir.create(&file).unwrap(); + tmpdir.create_dir(&dir).unwrap(); + symlink_file(Path::new("..").join(&file), &tmpdir, &link).unwrap(); + symlink_dir(&dir, &tmpdir, &linkdir).unwrap(); + + assert!(tmpdir + .symlink_metadata(&link) + .unwrap() + .file_type() + .is_symlink()); + + assert_eq!(tmpdir.canonicalize(".").unwrap(), PathBuf::from(".")); + assert_eq!(tmpdir.canonicalize(&file).unwrap(), file); + assert_eq!(tmpdir.canonicalize(&link).unwrap(), file); + assert_eq!(tmpdir.canonicalize(&linkdir).unwrap(), dir); + assert_eq!(tmpdir.canonicalize(&linkdir.join("link")).unwrap(), file); +} + +#[test] +fn realpath_works_tricky() { + let tmpdir = tmpdir(); + if !got_symlink_permission(&tmpdir) { + return; + }; + + let a = PathBuf::from("a"); + let b = a.join("b"); + let c = b.join("c"); + let d = a.join("d"); + let e = d.join("e"); + let f = a.join("f"); + + tmpdir.create_dir_all(&b).unwrap(); + tmpdir.create_dir_all(&d).unwrap(); + tmpdir.create(&f).unwrap(); + if cfg!(not(windows)) { + symlink_file("../d/e", &tmpdir, &c).unwrap(); + symlink_file("../f", &tmpdir, &e).unwrap(); + } + if cfg!(windows) { + symlink_file(r"..\d\e", &tmpdir, &c).unwrap(); + symlink_file(r"..\f", &tmpdir, &e).unwrap(); + } + + assert_eq!(tmpdir.canonicalize(&c).unwrap(), f); + assert_eq!(tmpdir.canonicalize(&e).unwrap(), f); +} + +#[test] +fn dir_entry_methods() { + let tmpdir = tmpdir(); + + tmpdir.create_dir_all("a").unwrap(); + tmpdir.create("b").unwrap(); + + for file in tmpdir.read_dir(".").unwrap().map(|f| f.unwrap()) { + let fname = file.file_name().unwrap(); + match fname.as_str() { + "a" => { + assert!(file.file_type().unwrap().is_dir()); + assert!(file.metadata().unwrap().is_dir()); + } + "b" => { + assert!(file.file_type().unwrap().is_file()); + assert!(file.metadata().unwrap().is_file()); + } + f => panic!("unknown file name: {:?}", f), + } + } +} + +#[test] +fn dir_entry_debug() { + let tmpdir = tmpdir(); + tmpdir.create("b").unwrap(); + let mut read_dir = tmpdir.read_dir(".").unwrap(); + let dir_entry = read_dir.next().unwrap().unwrap(); + let actual = format!("{:?}", dir_entry); + let expected = format!("DirEntry({:?})", dir_entry.file_name().unwrap()); + assert_eq!(actual, expected); +} + +#[test] +fn read_dir_not_found() { + let tmpdir = tmpdir(); + let res = tmpdir.read_dir("path/that/does/not/exist"); + assert_eq!(res.err().unwrap().kind(), ErrorKind::NotFound); +} + +// See the comments on `create_dir_all_with_junctions` in tests/fs.rs about Windows. +#[test] +#[cfg_attr(windows, ignore)] +fn create_dir_all_with_junctions() { + let tmpdir = tmpdir(); + let target = "target"; + + let junction = PathBuf::from("junction"); + let b = junction.join("a/b"); + + let link = PathBuf::from("link"); + let d = link.join("c/d"); + + tmpdir.create_dir(&target).unwrap(); + + check!(symlink_junction(&target, &tmpdir, &junction)); + check!(tmpdir.create_dir_all(&b)); + // the junction itself is not a directory, but `is_dir()` on a Path + // follows links + assert!(tmpdir.is_dir(junction)); + assert!(tmpdir.exists(b)); + + if !got_symlink_permission(&tmpdir) { + return; + }; + check!(symlink_dir(&target, &tmpdir, &link)); + check!(tmpdir.create_dir_all(&d)); + assert!(tmpdir.is_dir(link)); + assert!(tmpdir.exists(d)); +} + +#[test] +fn metadata_access_times() { + let tmpdir = tmpdir(); + + let b = "b"; + tmpdir.create(&b).unwrap(); + + let a = check!(tmpdir.metadata(".")); + let b = check!(tmpdir.metadata(&b)); + + assert_eq!(check!(a.accessed()), check!(a.accessed())); + assert_eq!(check!(a.modified()), check!(a.modified())); + // This assert from std's testsuite is racy. + //assert_eq!(check!(b.accessed()), check!(b.modified())); + + if cfg!(target_os = "macos") || cfg!(target_os = "windows") { + check!(a.created()); + check!(b.created()); + } + + if cfg!(any(target_os = "android", target_os = "linux")) { + // Not always available + match (a.created(), b.created()) { + (Ok(t1), Ok(t2)) => assert!(t1 <= t2), + #[cfg(not(io_error_uncategorized))] + (Err(e1), Err(e2)) + if e1.kind() == ErrorKind::Other && e2.kind() == ErrorKind::Other => {} + #[cfg(io_error_uncategorized)] + (Err(e1), Err(e2)) + if e1.kind() == ErrorKind::Uncategorized + && e2.kind() == ErrorKind::Uncategorized + || e1.kind() == ErrorKind::Unsupported + && e2.kind() == ErrorKind::Unsupported => {} + (a, b) => panic!( + "creation time must be always supported or not supported: {:?} {:?}", + a, b, + ), + } + } +} + +/// Test creating hard links to symlinks. +#[test] +fn symlink_hard_link() { + let tmpdir = tmpdir(); + if !got_symlink_permission(&tmpdir) { + return; + } + if !able_to_not_follow_symlinks_while_hard_linking() { + return; + } + + // Create "file", a file. + check!(tmpdir.create("file")); + + // Create "symlink", a symlink to "file". + check!(symlink_file("file", &tmpdir, "symlink")); + + // Create "hard_link", a hard link to "symlink". + check!(tmpdir.hard_link("symlink", &tmpdir, "hard_link")); + + // "hard_link" should appear as a symlink. + assert!(check!(tmpdir.symlink_metadata("hard_link")) + .file_type() + .is_symlink()); + + // We sould be able to open "file" via any of the above names. + let _ = check!(tmpdir.open("file")); + assert!(tmpdir.open("file.renamed").is_err()); + let _ = check!(tmpdir.open("symlink")); + let _ = check!(tmpdir.open("hard_link")); + + // Rename "file" to "file.renamed". + check!(tmpdir.rename("file", &tmpdir, "file.renamed")); + + // Now, the symlink and the hard link should be dangling. + assert!(tmpdir.open("file").is_err()); + let _ = check!(tmpdir.open("file.renamed")); + assert!(tmpdir.open("symlink").is_err()); + assert!(tmpdir.open("hard_link").is_err()); + + // The symlink and the hard link should both still point to "file". + assert!(tmpdir.read_link("file").is_err()); + assert!(tmpdir.read_link("file.renamed").is_err()); + assert_eq!(check!(tmpdir.read_link("symlink")), Path::new("file")); + assert_eq!(check!(tmpdir.read_link("hard_link")), Path::new("file")); + + // Remove "file.renamed". + check!(tmpdir.remove_file("file.renamed")); + + // Now, we can't open the file by any name. + assert!(tmpdir.open("file").is_err()); + assert!(tmpdir.open("file.renamed").is_err()); + assert!(tmpdir.open("symlink").is_err()); + assert!(tmpdir.open("hard_link").is_err()); + + // "hard_link" should still appear as a symlink. + assert!(check!(tmpdir.symlink_metadata("hard_link")) + .file_type() + .is_symlink()); +} + +/// Ensure `fs::create_dir` works on Windows with longer paths. +#[test] +#[cfg(windows)] +fn create_dir_long_paths() { + use std::ffi::OsStr; + use std::iter; + use std::os::windows::ffi::OsStrExt; + const PATH_LEN: usize = 247; + + let tmpdir = tmpdir(); + let mut path = PathBuf::new(); + path.push("a"); + let mut path = path.into_os_string(); + + let utf16_len = path.encode_wide().count(); + if utf16_len >= PATH_LEN { + // Skip the test in the unlikely event the local user has a long temp directory + // path. This should not affect CI. + return; + } + // Increase the length of the path. + path.extend(iter::repeat(OsStr::new("a")).take(PATH_LEN - utf16_len)); + + // This should succeed. + tmpdir + .create_dir(&PathBuf::from_path_buf(path.clone().into()).unwrap()) + .unwrap(); + + // This will fail if the path isn't converted to verbatim. + path.push("a"); + tmpdir + .create_dir(&PathBuf::from_path_buf(path.clone().into()).unwrap()) + .unwrap(); + + // #90940: Ensure an empty path returns the "Not Found" error. + let path = Path::new(""); + assert_eq!( + tmpdir.canonicalize(path).unwrap_err().kind(), + std::io::ErrorKind::NotFound + ); +} + +/// Test creating files with invalid names and reading their parent +/// directories. +#[test] +fn test_invalid_utf8() { + use camino::Utf8Path; + #[cfg(not(windows))] + use std::ffi::OsStr; + #[cfg(windows)] + use std::ffi::OsString; + #[cfg(unix)] + use std::os::unix::ffi::OsStrExt; + #[cfg(target_os = "wasi")] + use std::os::wasi::ffi::OsStrExt; + #[cfg(windows)] + use std::os::windows::ffi::OsStringExt; + + let dir = tempfile::tempdir().unwrap(); + #[cfg(not(windows))] + let invalid_path = dir.path().join(OsStr::from_bytes(b"invalid\xffbyte")); + #[cfg(windows)] + let invalid_path = dir.path().join(OsString::from_wide(&[ + 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0xd800, 0x62, 0x79, 0x74, 0x65, + ])); + + // On some OS's, it's not possible to create files with invalid paths. + // If it is, test that we do the right thing with them. + if let Ok(_) = std::fs::File::create(invalid_path) { + let cap_dir = cap_std::fs_utf8::Dir::open_ambient_dir( + Utf8Path::from_path(dir.path()).unwrap(), + cap_std::ambient_authority(), + ) + .unwrap(); + + for entry in cap_dir.entries().unwrap() { + let entry = entry.unwrap(); + #[cfg(feature = "arf_strings")] + { + let name: String = entry.file_name().unwrap(); + let _ = cap_dir.open(name).unwrap(); + } + #[cfg(not(feature = "arf_strings"))] + { + let _ = entry.file_name().unwrap_err(); + } + } + } +} + +#[test] +fn from_cap_std() { + let tmpdir = sys_common::io::tmpdir(); + let dir = "d1/d2"; + check!(tmpdir.create_dir_all(dir)); + let d1_entry = tmpdir.entries_utf8().unwrap().next().unwrap().unwrap(); + assert_eq!(d1_entry.file_name().unwrap(), "d1"); + + let d1 = tmpdir.open_dir_utf8("d1").unwrap(); + let d2_entry = d1.entries().unwrap().next().unwrap().unwrap(); + assert_eq!(d2_entry.file_name().unwrap(), "d2"); +} diff --git a/crates/wasi/src/filesystem/primitives/tests/is_file_read_write.rs b/crates/wasi/src/filesystem/primitives/tests/is_file_read_write.rs new file mode 100644 index 000000000000..21e68baccee7 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/is_file_read_write.rs @@ -0,0 +1,43 @@ +#[macro_use] +mod sys_common; + +use cap_fs_ext::{IsFileReadWrite, OpenOptions}; +use sys_common::io::tmpdir; + +#[test] +fn basic_is_file_read_write() { + let tmpdir = tmpdir(); + + let file = check!(tmpdir.open_with( + "file", + OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .read(true) + )); + assert_eq!(check!(file.is_file_read_write()), (true, true)); + + let file = check!(tmpdir.open_with("file", OpenOptions::new().append(true).read(true))); + assert_eq!(check!(file.is_file_read_write()), (true, true)); + + let file = check!(tmpdir.open_with( + "file", + OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .read(false) + )); + assert_eq!(check!(file.is_file_read_write()), (false, true)); + + let file = check!(tmpdir.open_with( + "file", + OpenOptions::new() + .create(false) + .truncate(false) + .write(false) + .read(true) + )); + assert_eq!(check!(file.is_file_read_write()), (true, false)); +} diff --git a/crates/wasi/src/filesystem/primitives/tests/issue_22577.rs b/crates/wasi/src/filesystem/primitives/tests/issue_22577.rs new file mode 100644 index 000000000000..6c80c1abc828 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/issue_22577.rs @@ -0,0 +1,31 @@ +// This test module derived from Rust's src/test/ui/issues/issue-22577.rs +// at revision 108e90ca78f052c0c1c49c42a22c85620be19712. + +// run-pass +#![allow(dead_code)] +// pretty-expanded FIXME #23616 +// ignore-cloudabi no std::fs + +use cap_std::{fs, net}; + +fn assert_both() {} +fn assert_send() {} + +#[test] +fn issue_22577() { + assert_both::(); + assert_both::(); + assert_both::(); + assert_both::(); + assert_both::(); + assert_both::(); + + assert_both::(); + assert_both::(); + assert_both::(); + assert_both::(); + assert_both::(); + assert_both::(); + assert_both::(); + assert_both::(); +} diff --git a/crates/wasi/src/filesystem/primitives/tests/metadata_ext.rs b/crates/wasi/src/filesystem/primitives/tests/metadata_ext.rs new file mode 100644 index 000000000000..0aad8cc52705 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/metadata_ext.rs @@ -0,0 +1,85 @@ +// This file contains tests for `cap_fs_ext::MetatadaExt`. + +#[macro_use] +mod sys_common; + +use cap_fs_ext::{DirExt, MetadataExt}; +use sys_common::io::tmpdir; +use sys_common::symlink_supported; + +#[test] +fn test_metadata_ext() { + let tmpdir = tmpdir(); + let a = check!(tmpdir.create("a")); + let b = check!(tmpdir.create("b")); + let tmpdir_metadata = check!(tmpdir.dir_metadata()); + let a_metadata = check!(a.metadata()); + let b_metadata = check!(b.metadata()); + let a_dir_metadata = check!(tmpdir.metadata("a")); + let b_dir_metadata = check!(tmpdir.metadata("b")); + let a_symlink_metadata = check!(tmpdir.symlink_metadata("a")); + let b_symlink_metadata = check!(tmpdir.symlink_metadata("b")); + + // The directory and files inside it should be on the same device. + assert_eq!(tmpdir_metadata.dev(), a_metadata.dev()); + assert_eq!(a_metadata.dev(), b_metadata.dev()); + + // They should all have distinct inodes. + assert_ne!(tmpdir_metadata.ino(), a_metadata.ino()); + assert_ne!(tmpdir_metadata.ino(), b_metadata.ino()); + assert_ne!(a_metadata.ino(), b_metadata.ino()); + + // The files should start with just one link. + assert_eq!(a_metadata.nlink(), 1); + assert_eq!(b_metadata.nlink(), 1); + + // Add another link and check for it. + check!(tmpdir.hard_link("b", &tmpdir, "c")); + let b_metadata = check!(b.metadata()); + assert_eq!(b_metadata.nlink(), 2); + + // Check that the metadata has dev/nlink/ino. + tmpdir_metadata.dev(); + tmpdir_metadata.nlink(); + tmpdir_metadata.ino(); + a_metadata.dev(); + a_metadata.nlink(); + a_metadata.ino(); + b_metadata.dev(); + b_metadata.nlink(); + b_metadata.ino(); + a_dir_metadata.dev(); + a_dir_metadata.nlink(); + a_dir_metadata.ino(); + b_dir_metadata.dev(); + b_dir_metadata.nlink(); + b_dir_metadata.ino(); + a_symlink_metadata.dev(); + a_symlink_metadata.nlink(); + a_symlink_metadata.ino(); + b_symlink_metadata.dev(); + b_symlink_metadata.nlink(); + b_symlink_metadata.ino(); + + if symlink_supported() { + check!(DirExt::symlink_file(&*tmpdir, "b", "d")); + let d_metadata = check!(tmpdir.metadata("d")); + let d_symlink_metadata = check!(tmpdir.symlink_metadata("d")); + + d_metadata.dev(); + d_metadata.nlink(); + d_metadata.ino(); + d_symlink_metadata.dev(); + d_symlink_metadata.nlink(); + d_symlink_metadata.ino(); + + assert_ne!( + (d_symlink_metadata.ino(), d_symlink_metadata.dev()), + (b_metadata.ino(), b_metadata.dev()) + ); + assert_eq!( + (d_metadata.ino(), d_metadata.dev()), + (b_metadata.ino(), b_metadata.dev()) + ); + } +} diff --git a/crates/wasi/src/filesystem/primitives/tests/mod.rs b/crates/wasi/src/filesystem/primitives/tests/mod.rs new file mode 100644 index 000000000000..b1c32776e010 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/mod.rs @@ -0,0 +1,35 @@ +mod canonicalize; +mod cap_basics; +mod cap_net_ext_tcp; +mod cap_net_ext_tcp_split; +mod cap_net_ext_udp; +mod cap_net_ext_udp_split; +mod dir_entry_ext; +mod dir_ext; +mod file_type_ext; +mod fs; +mod fs_additional; +mod fs_utf8; +mod is_file_read_write; +mod issue_22577; +mod metadata_ext; +mod net; +mod net_tcp; +mod net_udp; +mod open_ambient; +mod paths_containing_nul; +mod rand; +mod readdir; +mod rename; +mod rename_directory; +mod reopen; +mod reopendir; +mod root; +mod set; +mod set_times; +mod symlinks; +mod sys; +mod sys_common; +mod time; +mod windows_open; +mod windows_symlinks; diff --git a/crates/wasi/src/filesystem/primitives/tests/net.rs b/crates/wasi/src/filesystem/primitives/tests/net.rs new file mode 100644 index 000000000000..45e52f21e7b8 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/net.rs @@ -0,0 +1,76 @@ +// This file is derived from Rust's library/std/src/net/test.rs at +// revision 377d1a984cd2a53327092b90aa1d8b7e22d1e347. + +#![allow(warnings)] // not used on emscripten + +use cap_std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs}; +use std::env; +use std::sync::atomic::{AtomicUsize, Ordering}; + +static PORT: AtomicUsize = AtomicUsize::new(0); + +pub fn next_test_ip4() -> SocketAddr { + let port = PORT.fetch_add(1, Ordering::SeqCst) as u16 + base_port(); + SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), port)) +} + +pub fn next_test_ip6() -> SocketAddr { + let port = PORT.fetch_add(1, Ordering::SeqCst) as u16 + base_port(); + SocketAddr::V6(SocketAddrV6::new( + Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1), + port, + 0, + 0, + )) +} + +pub fn sa4(a: Ipv4Addr, p: u16) -> SocketAddr { + SocketAddr::V4(SocketAddrV4::new(a, p)) +} + +pub fn sa6(a: Ipv6Addr, p: u16) -> SocketAddr { + SocketAddr::V6(SocketAddrV6::new(a, p, 0, 0)) +} + +pub fn tsa(a: A) -> Result, String> { + match a.to_socket_addrs() { + Ok(a) => Ok(a.collect()), + Err(e) => Err(e.to_string()), + } +} + +// The bots run multiple builds at the same time, and these builds +// all want to use ports. This function figures out which workspace +// it is running in and assigns a port range based on it. +fn base_port() -> u16 { + let cwd = if cfg!(target_env = "sgx") { + String::from("sgx") + } else { + env::current_dir() + .unwrap() + .into_os_string() + .into_string() + .unwrap() + }; + let dirs = [ + "32-opt", + "32-nopt", + "musl-64-opt", + "cross-opt", + "64-opt", + "64-nopt", + "64-opt-vg", + "64-debug-opt", + "all-opt", + "snap3", + "dist", + "sgx", + ]; + dirs.iter() + .enumerate() + .find(|&(_, dir)| cwd.contains(dir)) + .map(|p| p.0) + .unwrap_or(0) as u16 + * 1000 + + 19600 +} diff --git a/crates/wasi/src/filesystem/primitives/tests/net_tcp.rs b/crates/wasi/src/filesystem/primitives/tests/net_tcp.rs new file mode 100644 index 000000000000..108e558e976a --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/net_tcp.rs @@ -0,0 +1,1050 @@ +// This file is derived from Rust's library/std/src/net/tcp/tests.rs at +// revision 377d1a984cd2a53327092b90aa1d8b7e22d1e347. + +mod net; + +use cap_std::ambient_authority; +use cap_std::net::*; +use net::{next_test_ip4, next_test_ip6}; +use std::io::prelude::*; +use std::io::{ErrorKind, IoSlice, IoSliceMut}; +use std::sync::mpsc::channel; +use std::time::{Duration, Instant}; +use std::{fmt, thread}; + +fn each_ip(f: &mut dyn FnMut(SocketAddr)) { + f(next_test_ip4()); + f(next_test_ip6()); +} + +macro_rules! t { + ($e:expr) => { + match $e { + Ok(t) => t, + Err(e) => panic!("received error for `{}`: {}", stringify!($e), e), + } + }; +} + +#[test] +fn bind_error() { + let mut pool = Pool::new(); + pool.insert_socket_addr("1.1.1.1:9999".parse().unwrap(), ambient_authority()); + + match pool.bind_tcp_listener("1.1.1.1:9999") { + Ok(..) => panic!(), + Err(e) => assert_eq!(e.kind(), ErrorKind::AddrNotAvailable), + } +} + +#[test] +fn connect_error() { + let mut pool = Pool::new(); + pool.insert_socket_addr("0.0.0.0:1".parse().unwrap(), ambient_authority()); + + match pool.connect_tcp_stream("0.0.0.0:1") { + Ok(..) => panic!(), + Err(e) => assert!( + e.kind() == ErrorKind::ConnectionRefused + || e.kind() == ErrorKind::InvalidInput + || e.kind() == ErrorKind::AddrInUse + || e.kind() == ErrorKind::AddrNotAvailable + || e.kind() == ErrorKind::TimedOut + // Rust >=1.83 adds `ErrorKind::NetworkUnreachable`. + || e.to_string().contains("network unreachable") + || e.to_string().contains("Network is unreachable"), + "bad error: {} {:?}", + e, + e.kind() + ), + } +} + +#[test] +fn listen_localhost() { + let socket_addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(socket_addr, ambient_authority()); + for resolved in format!("localhost:{}", socket_addr.port()) + .to_socket_addrs() + .unwrap() + { + pool.insert_socket_addr(resolved, ambient_authority()); + } + + let listener = t!(pool.bind_tcp_listener(&socket_addr)); + + let _t = thread::spawn(move || { + let mut stream = t!(pool.connect_tcp_stream(&("localhost", socket_addr.port()))); + t!(stream.write(&[144])); + }); + + let mut stream = t!(listener.accept()).0; + let mut buf = [0]; + t!(stream.read(&mut buf)); + assert!(buf[0] == 144); +} + +#[test] +fn connect_loopback() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let acceptor = t!(pool.bind_tcp_listener(&addr)); + + let _t = thread::spawn(move || { + let host = match addr { + SocketAddr::V4(..) => "127.0.0.1", + SocketAddr::V6(..) => "::1", + }; + let mut stream = t!(pool.connect_tcp_stream(&(host, addr.port()))); + t!(stream.write(&[66])); + }); + + let mut stream = t!(acceptor.accept()).0; + let mut buf = [0]; + t!(stream.read(&mut buf)); + assert!(buf[0] == 66); + }) +} + +#[test] +fn smoke_test() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let acceptor = t!(pool.bind_tcp_listener(&addr)); + + let (tx, rx) = channel(); + let _t = thread::spawn(move || { + let mut stream = t!(pool.connect_tcp_stream(&addr)); + t!(stream.write(&[99])); + tx.send(t!(stream.local_addr())).unwrap(); + }); + + let (mut stream, addr) = t!(acceptor.accept()); + let mut buf = [0]; + t!(stream.read(&mut buf)); + assert!(buf[0] == 99); + assert_eq!(addr, t!(rx.recv())); + }) +} + +#[test] +fn read_eof() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let acceptor = t!(pool.bind_tcp_listener(&addr)); + + let _t = thread::spawn(move || { + let _stream = t!(pool.connect_tcp_stream(&addr)); + // Close + }); + + let mut stream = t!(acceptor.accept()).0; + let mut buf = [0]; + let nread = t!(stream.read(&mut buf)); + assert_eq!(nread, 0); + let nread = t!(stream.read(&mut buf)); + assert_eq!(nread, 0); + }) +} + +#[test] +fn write_close() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let acceptor = t!(pool.bind_tcp_listener(&addr)); + + let (tx, rx) = channel(); + let _t = thread::spawn(move || { + drop(t!(pool.connect_tcp_stream(&addr))); + tx.send(()).unwrap(); + }); + + let mut stream = t!(acceptor.accept()).0; + rx.recv().unwrap(); + let buf = [0]; + match stream.write(&buf) { + Ok(..) => {} + Err(e) => { + assert!( + e.kind() == ErrorKind::ConnectionReset + || e.kind() == ErrorKind::BrokenPipe + || e.kind() == ErrorKind::ConnectionAborted, + "unknown error: {}", + e + ); + } + } + }) +} + +#[test] +fn multiple_connect_serial() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let max = 10; + let acceptor = t!(pool.bind_tcp_listener(&addr)); + + let _t = thread::spawn(move || { + for _ in 0..max { + let mut stream = t!(pool.connect_tcp_stream(&addr)); + t!(stream.write(&[99])); + } + }); + + for stream in acceptor.incoming().take(max) { + let mut stream = t!(stream); + let mut buf = [0]; + t!(stream.read(&mut buf)); + assert_eq!(buf[0], 99); + } + }) +} + +#[test] +fn multiple_connect_interleaved_greedy_schedule() { + const MAX: usize = 10; + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let acceptor = t!(pool.bind_tcp_listener(&addr)); + + let _t = thread::spawn(move || { + let acceptor = acceptor; + for (i, stream) in acceptor.incoming().enumerate().take(MAX) { + // Start another thread to handle the connection + let _t = thread::spawn(move || { + let mut stream = t!(stream); + let mut buf = [0]; + t!(stream.read(&mut buf)); + assert!(buf[0] == i as u8); + }); + } + }); + + connect(0, addr, pool); + }); + + fn connect(i: usize, addr: SocketAddr, pool: Pool) { + if i == MAX { + return; + } + + let t = thread::spawn(move || { + let mut stream = t!(pool.connect_tcp_stream(&addr)); + // Connect again before writing + connect(i + 1, addr, pool); + t!(stream.write(&[i as u8])); + }); + t.join().ok().expect("thread panicked"); + } +} + +#[test] +fn multiple_connect_interleaved_lazy_schedule() { + const MAX: usize = 10; + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let acceptor = t!(pool.bind_tcp_listener(&addr)); + + let _t = thread::spawn(move || { + for stream in acceptor.incoming().take(MAX) { + // Start another thread to handle the connection + let _t = thread::spawn(move || { + let mut stream = t!(stream); + let mut buf = [0]; + t!(stream.read(&mut buf)); + assert!(buf[0] == 99); + }); + } + }); + + connect(0, addr, pool); + }); + + fn connect(i: usize, addr: SocketAddr, pool: Pool) { + if i == MAX { + return; + } + + let t = thread::spawn(move || { + let mut stream = t!(pool.connect_tcp_stream(&addr)); + connect(i + 1, addr, pool); + t!(stream.write(&[99])); + }); + t.join().ok().expect("thread panicked"); + } +} + +#[test] +fn socket_and_peer_name() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let listener = t!(pool.bind_tcp_listener(&addr)); + let so_name = t!(listener.local_addr()); + assert_eq!(addr, so_name); + let _t = thread::spawn(move || { + t!(listener.accept()); + }); + + let stream = t!(pool.connect_tcp_stream(&addr)); + assert_eq!(addr, t!(stream.peer_addr())); + }) +} + +#[test] +fn partial_read() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let (tx, rx) = channel(); + let srv = t!(pool.bind_tcp_listener(&addr)); + let _t = thread::spawn(move || { + let mut cl = t!(srv.accept()).0; + cl.write(&[10]).unwrap(); + let mut b = [0]; + t!(cl.read(&mut b)); + tx.send(()).unwrap(); + }); + + let mut c = t!(pool.connect_tcp_stream(&addr)); + let mut b = [0; 10]; + assert_eq!(c.read(&mut b).unwrap(), 1); + t!(c.write(&[1])); + rx.recv().unwrap(); + }) +} + +#[test] +fn read_vectored() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let srv = t!(pool.bind_tcp_listener(&addr)); + let mut s1 = t!(pool.connect_tcp_stream(&addr)); + let mut s2 = t!(srv.accept()).0; + + let len = s1.write(&[10, 11, 12]).unwrap(); + assert_eq!(len, 3); + + let mut a = []; + let mut b = [0]; + let mut c = [0; 3]; + let len = t!(s2.read_vectored(&mut [ + IoSliceMut::new(&mut a), + IoSliceMut::new(&mut b), + IoSliceMut::new(&mut c) + ])); + assert!(len > 0); + assert_eq!(b, [10]); + // some implementations don't support readv, so we may only fill the first + // buffer + assert!(len == 1 || c == [11, 12, 0]); + }) +} + +#[test] +fn write_vectored() { + let mut pool = Pool::new(); + + each_ip(&mut |addr| { + pool.insert_socket_addr(addr, ambient_authority()); + + let srv = t!(pool.bind_tcp_listener(&addr)); + let mut s1 = t!(pool.connect_tcp_stream(&addr)); + let mut s2 = t!(srv.accept()).0; + + let a = []; + let b = [10]; + let c = [11, 12]; + t!(s1.write_vectored(&[IoSlice::new(&a), IoSlice::new(&b), IoSlice::new(&c)])); + + let mut buf = [0; 4]; + let len = t!(s2.read(&mut buf)); + // some implementations don't support writev, so we may only write the first + // buffer + if len == 1 { + assert_eq!(buf, [10, 0, 0, 0]); + } else { + assert_eq!(len, 3); + assert_eq!(buf, [10, 11, 12, 0]); + } + }) +} + +#[test] +fn double_bind() { + let mut pool = Pool::new(); + + each_ip(&mut |addr| { + pool.insert_socket_addr(addr, ambient_authority()); + + let listener1 = t!(pool.bind_tcp_listener(&addr)); + match pool.bind_tcp_listener(&addr) { + Ok(listener2) => panic!( + "This system (perhaps due to options set by pool.bind_tcp_listener) \ + permits double binding: {:?} and {:?}", + listener1, listener2 + ), + Err(e) => { + assert!( + e.kind() == ErrorKind::ConnectionRefused + || e.kind() == ErrorKind::Other + || e.kind() == ErrorKind::AddrInUse, + "unknown error: {} {:?}", + e, + e.kind() + ); + } + } + }) +} + +#[test] +fn tcp_clone_smoke() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let acceptor = t!(pool.bind_tcp_listener(&addr)); + + let _t = thread::spawn(move || { + let mut s = t!(pool.connect_tcp_stream(&addr)); + let mut buf = [0, 0]; + assert_eq!(s.read(&mut buf).unwrap(), 1); + assert_eq!(buf[0], 1); + t!(s.write(&[2])); + }); + + let mut s1 = t!(acceptor.accept()).0; + let s2 = t!(s1.try_clone()); + + let (tx1, rx1) = channel(); + let (tx2, rx2) = channel(); + let _t = thread::spawn(move || { + let mut s2 = s2; + rx1.recv().unwrap(); + t!(s2.write(&[1])); + tx2.send(()).unwrap(); + }); + tx1.send(()).unwrap(); + let mut buf = [0, 0]; + assert_eq!(s1.read(&mut buf).unwrap(), 1); + rx2.recv().unwrap(); + }) +} + +#[test] +fn tcp_clone_two_read() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let acceptor = t!(pool.bind_tcp_listener(&addr)); + let (tx1, rx) = channel(); + let tx2 = tx1.clone(); + + let _t = thread::spawn(move || { + let mut s = t!(pool.connect_tcp_stream(&addr)); + t!(s.write(&[1])); + rx.recv().unwrap(); + t!(s.write(&[2])); + rx.recv().unwrap(); + }); + + let mut s1 = t!(acceptor.accept()).0; + let s2 = t!(s1.try_clone()); + + let (done, rx) = channel(); + let _t = thread::spawn(move || { + let mut s2 = s2; + let mut buf = [0, 0]; + t!(s2.read(&mut buf)); + tx2.send(()).unwrap(); + done.send(()).unwrap(); + }); + let mut buf = [0, 0]; + t!(s1.read(&mut buf)); + tx1.send(()).unwrap(); + + rx.recv().unwrap(); + }) +} + +#[test] +fn tcp_clone_two_write() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let acceptor = t!(pool.bind_tcp_listener(&addr)); + + let _t = thread::spawn(move || { + let mut s = t!(pool.connect_tcp_stream(&addr)); + let mut buf = [0, 1]; + t!(s.read(&mut buf)); + t!(s.read(&mut buf)); + }); + + let mut s1 = t!(acceptor.accept()).0; + let s2 = t!(s1.try_clone()); + + let (done, rx) = channel(); + let _t = thread::spawn(move || { + let mut s2 = s2; + t!(s2.write(&[1])); + done.send(()).unwrap(); + }); + t!(s1.write(&[2])); + + rx.recv().unwrap(); + }) +} + +#[test] +// FIXME: https://github.com/fortanix/rust-sgx/issues/110 +#[cfg_attr(target_env = "sgx", ignore)] +fn shutdown_smoke() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let a = t!(pool.bind_tcp_listener(&addr)); + let _t = thread::spawn(move || { + let mut c = t!(a.accept()).0; + let mut b = [0]; + assert_eq!(c.read(&mut b).unwrap(), 0); + t!(c.write(&[1])); + }); + + let mut s = t!(pool.connect_tcp_stream(&addr)); + t!(s.shutdown(Shutdown::Write)); + assert!(s.write(&[1]).is_err()); + let mut b = [0, 0]; + assert_eq!(t!(s.read(&mut b)), 1); + assert_eq!(b[0], 1); + }) +} + +#[test] +// FIXME: https://github.com/fortanix/rust-sgx/issues/110 +#[cfg_attr(target_env = "sgx", ignore)] +fn close_readwrite_smoke() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let a = t!(pool.bind_tcp_listener(&addr)); + let (tx, rx) = channel::<()>(); + let _t = thread::spawn(move || { + let _s = t!(a.accept()); + let _ = rx.recv(); + }); + + let mut b = [0]; + let mut s = t!(pool.connect_tcp_stream(&addr)); + let mut s2 = t!(s.try_clone()); + + // closing should prevent reads/writes + t!(s.shutdown(Shutdown::Write)); + assert!(s.write(&[0]).is_err()); + t!(s.shutdown(Shutdown::Read)); + assert_eq!(s.read(&mut b).unwrap(), 0); + + // closing should affect previous handles + assert!(s2.write(&[0]).is_err()); + assert_eq!(s2.read(&mut b).unwrap(), 0); + + // closing should affect new handles + let mut s3 = t!(s.try_clone()); + assert!(s3.write(&[0]).is_err()); + assert_eq!(s3.read(&mut b).unwrap(), 0); + + // make sure these don't die + let _ = s2.shutdown(Shutdown::Read); + let _ = s2.shutdown(Shutdown::Write); + let _ = s3.shutdown(Shutdown::Read); + let _ = s3.shutdown(Shutdown::Write); + drop(tx); + }) +} + +#[test] +#[cfg(unix)] // test doesn't work on Windows, see #31657 +fn close_read_wakes_up() { + let mut pool = Pool::new(); + + each_ip(&mut |addr| { + pool.insert_socket_addr(addr, ambient_authority()); + + let a = t!(pool.bind_tcp_listener(&addr)); + let (tx1, rx) = channel::<()>(); + let _t = thread::spawn(move || { + let _s = t!(a.accept()); + let _ = rx.recv(); + }); + + let s = t!(pool.connect_tcp_stream(&addr)); + let s2 = t!(s.try_clone()); + let (tx, rx) = channel(); + let _t = thread::spawn(move || { + let mut s2 = s2; + assert_eq!(t!(s2.read(&mut [0])), 0); + tx.send(()).unwrap(); + }); + // this should wake up the child thread + t!(s.shutdown(Shutdown::Read)); + + // this test will never finish if the child doesn't wake up + rx.recv().unwrap(); + drop(tx1); + }) +} + +#[test] +fn clone_while_reading() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let accept = t!(pool.bind_tcp_listener(&addr)); + + // Enqueue a thread to write to a socket + let (tx, rx) = channel(); + let (txdone, rxdone) = channel(); + let txdone2 = txdone.clone(); + let _t = thread::spawn(move || { + let mut tcp = t!(pool.connect_tcp_stream(&addr)); + rx.recv().unwrap(); + t!(tcp.write(&[0])); + txdone2.send(()).unwrap(); + }); + + // Spawn off a reading clone + let tcp = t!(accept.accept()).0; + let tcp2 = t!(tcp.try_clone()); + let txdone3 = txdone.clone(); + let _t = thread::spawn(move || { + let mut tcp2 = tcp2; + t!(tcp2.read(&mut [0])); + txdone3.send(()).unwrap(); + }); + + // Try to ensure that the reading clone is indeed reading + for _ in 0..50 { + thread::yield_now(); + } + + // clone the handle again while it's reading, then let it finish the + // read. + let _ = t!(tcp.try_clone()); + tx.send(()).unwrap(); + rxdone.recv().unwrap(); + rxdone.recv().unwrap(); + }) +} + +#[test] +fn clone_accept_smoke() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let a = t!(pool.bind_tcp_listener(&addr)); + let a2 = t!(a.try_clone()); + + let p = pool.clone(); + let _t = thread::spawn(move || { + let _ = p.connect_tcp_stream(&addr); + }); + let p = pool.clone(); + let _t = thread::spawn(move || { + let _ = p.connect_tcp_stream(&addr); + }); + + t!(a.accept()); + t!(a2.accept()); + }) +} + +#[test] +fn clone_accept_concurrent() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let a = t!(pool.bind_tcp_listener(&addr)); + let a2 = t!(a.try_clone()); + + let (tx, rx) = channel(); + let tx2 = tx.clone(); + + let _t = thread::spawn(move || { + tx.send(t!(a.accept())).unwrap(); + }); + let _t = thread::spawn(move || { + tx2.send(t!(a2.accept())).unwrap(); + }); + + let p = pool.clone(); + let _t = thread::spawn(move || { + let _ = p.connect_tcp_stream(&addr); + }); + let p = pool.clone(); + let _t = thread::spawn(move || { + let _ = p.connect_tcp_stream(&addr); + }); + + rx.recv().unwrap(); + rx.recv().unwrap(); + }) +} + +#[test] +fn debug() { + let mut pool = Pool::new(); + + #[cfg(not(target_env = "sgx"))] + fn render_socket_addr<'a>(addr: &'a SocketAddr) -> impl fmt::Debug + 'a { + addr + } + #[cfg(target_env = "sgx")] + fn render_socket_addr<'a>(addr: &'a SocketAddr) -> impl fmt::Debug + 'a { + addr.to_string() + } + + #[cfg(target_env = "sgx")] + use std::os::fortanix_sgx::io::AsRawFd; + #[cfg(unix)] + use std::os::unix::io::AsRawFd; + #[cfg(not(windows))] + fn render_inner(addr: &dyn AsRawFd) -> impl fmt::Debug { + addr.as_raw_fd() + } + #[cfg(windows)] + fn render_inner(addr: &dyn std::os::windows::io::AsRawSocket) -> impl fmt::Debug { + addr.as_raw_socket() + } + + let inner_name = if cfg!(windows) { "socket" } else { "fd" }; + let socket_addr = next_test_ip4(); + pool.insert_socket_addr(socket_addr, ambient_authority()); + for resolved in format!("localhost:{}", socket_addr.port()) + .to_socket_addrs() + .unwrap() + { + pool.insert_socket_addr(resolved, ambient_authority()); + } + + let listener = t!(pool.bind_tcp_listener(&socket_addr)); + let compare = format!( + "TcpListener {{ addr: {:?}, {}: {:?} }}", + render_socket_addr(&socket_addr), + inner_name, + render_inner(&listener) + ); + assert_eq!(format!("{:?}", listener), compare); + + let stream = t!(pool.connect_tcp_stream(&("localhost", socket_addr.port()))); + let compare = format!( + "TcpStream {{ addr: {:?}, peer: {:?}, {}: {:?} }}", + render_socket_addr(&stream.local_addr().unwrap()), + render_socket_addr(&stream.peer_addr().unwrap()), + inner_name, + render_inner(&stream) + ); + assert_eq!(format!("{:?}", stream), compare); +} + +// FIXME: re-enabled openbsd tests once their socket timeout code +// no longer has rounding errors. +// VxWorks ignores SO_SNDTIMEO. +#[cfg_attr( + any(target_os = "netbsd", target_os = "openbsd", target_os = "vxworks"), + ignore +)] +#[cfg_attr(target_env = "sgx", ignore)] // FIXME: https://github.com/fortanix/rust-sgx/issues/31 +#[test] +fn timeouts() { + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + for resolved in format!("localhost:{}", addr.port()) + .to_socket_addrs() + .unwrap() + { + pool.insert_socket_addr(resolved, ambient_authority()); + } + + let listener = t!(pool.bind_tcp_listener(&addr)); + + let stream = t!(pool.connect_tcp_stream(&("localhost", addr.port()))); + let dur = Duration::new(15410, 0); + + assert_eq!(None, t!(stream.read_timeout())); + + t!(stream.set_read_timeout(Some(dur))); + assert_eq!(Some(dur), t!(stream.read_timeout())); + + assert_eq!(None, t!(stream.write_timeout())); + + t!(stream.set_write_timeout(Some(dur))); + assert_eq!(Some(dur), t!(stream.write_timeout())); + + t!(stream.set_read_timeout(None)); + assert_eq!(None, t!(stream.read_timeout())); + + t!(stream.set_write_timeout(None)); + assert_eq!(None, t!(stream.write_timeout())); + drop(listener); +} + +#[test] +#[cfg_attr(target_env = "sgx", ignore)] // FIXME: https://github.com/fortanix/rust-sgx/issues/31 +fn test_read_timeout() { + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + for resolved in format!("localhost:{}", addr.port()) + .to_socket_addrs() + .unwrap() + { + pool.insert_socket_addr(resolved, ambient_authority()); + } + + let listener = t!(pool.bind_tcp_listener(&addr)); + + let mut stream = t!(pool.connect_tcp_stream(&("localhost", addr.port()))); + t!(stream.set_read_timeout(Some(Duration::from_millis(1000)))); + + let mut buf = [0; 10]; + let start = Instant::now(); + let kind = stream + .read_exact(&mut buf) + .err() + .expect("expected error") + .kind(); + assert!( + kind == ErrorKind::WouldBlock || kind == ErrorKind::TimedOut, + "unexpected_error: {:?}", + kind + ); + assert!(start.elapsed() > Duration::from_millis(400)); + drop(listener); +} + +#[test] +#[cfg_attr(target_env = "sgx", ignore)] // FIXME: https://github.com/fortanix/rust-sgx/issues/31 +fn test_read_with_timeout() { + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + for resolved in format!("localhost:{}", addr.port()) + .to_socket_addrs() + .unwrap() + { + pool.insert_socket_addr(resolved, ambient_authority()); + } + + let listener = t!(pool.bind_tcp_listener(&addr)); + + let mut stream = t!(pool.connect_tcp_stream(&("localhost", addr.port()))); + t!(stream.set_read_timeout(Some(Duration::from_millis(1000)))); + + let mut other_end = t!(listener.accept()).0; + t!(other_end.write_all(b"hello world")); + + let mut buf = [0; 11]; + t!(stream.read(&mut buf)); + assert_eq!(b"hello world", &buf[..]); + + let start = Instant::now(); + let kind = stream + .read_exact(&mut buf) + .err() + .expect("expected error") + .kind(); + assert!( + kind == ErrorKind::WouldBlock || kind == ErrorKind::TimedOut, + "unexpected_error: {:?}", + kind + ); + assert!(start.elapsed() > Duration::from_millis(400)); + drop(listener); +} + +// Ensure the `set_read_timeout` and `set_write_timeout` calls return errors +// when passed zero Durations +#[test] +fn test_timeout_zero_duration() { + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let listener = t!(pool.bind_tcp_listener(&addr)); + let stream = t!(pool.connect_tcp_stream(&addr)); + + let result = stream.set_write_timeout(Some(Duration::new(0, 0))); + let err = result.unwrap_err(); + assert_eq!(err.kind(), ErrorKind::InvalidInput); + + let result = stream.set_read_timeout(Some(Duration::new(0, 0))); + let err = result.unwrap_err(); + assert_eq!(err.kind(), ErrorKind::InvalidInput); + + drop(listener); +} + +#[test] +#[cfg_attr(target_env = "sgx", ignore)] +fn nodelay() { + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + for resolved in format!("localhost:{}", addr.port()) + .to_socket_addrs() + .unwrap() + { + pool.insert_socket_addr(resolved, ambient_authority()); + } + + let _listener = t!(pool.bind_tcp_listener(&addr)); + + let stream = t!(pool.connect_tcp_stream(&("localhost", addr.port()))); + + assert_eq!(false, t!(stream.nodelay())); + t!(stream.set_nodelay(true)); + assert_eq!(true, t!(stream.nodelay())); + t!(stream.set_nodelay(false)); + assert_eq!(false, t!(stream.nodelay())); +} + +#[test] +#[cfg_attr(target_env = "sgx", ignore)] +fn ttl() { + let ttl = 100; + + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + for resolved in format!("localhost:{}", addr.port()) + .to_socket_addrs() + .unwrap() + { + pool.insert_socket_addr(resolved, ambient_authority()); + } + + let listener = t!(pool.bind_tcp_listener(&addr)); + + t!(listener.set_ttl(ttl)); + assert_eq!(ttl, t!(listener.ttl())); + + let stream = t!(pool.connect_tcp_stream(&("localhost", addr.port()))); + + t!(stream.set_ttl(ttl)); + assert_eq!(ttl, t!(stream.ttl())); +} + +#[test] +#[cfg_attr(target_env = "sgx", ignore)] +fn set_nonblocking() { + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + for resolved in format!("localhost:{}", addr.port()) + .to_socket_addrs() + .unwrap() + { + pool.insert_socket_addr(resolved, ambient_authority()); + } + + let listener = t!(pool.bind_tcp_listener(&addr)); + + t!(listener.set_nonblocking(true)); + t!(listener.set_nonblocking(false)); + + let mut stream = t!(pool.connect_tcp_stream(&("localhost", addr.port()))); + + t!(stream.set_nonblocking(false)); + t!(stream.set_nonblocking(true)); + + let mut buf = [0]; + match stream.read(&mut buf) { + Ok(_) => panic!("expected error"), + Err(ref e) if e.kind() == ErrorKind::WouldBlock => {} + Err(e) => panic!("unexpected error {}", e), + } +} + +#[test] +#[cfg_attr(target_env = "sgx", ignore)] // FIXME: https://github.com/fortanix/rust-sgx/issues/31 +fn peek() { + each_ip(&mut |addr| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let (txdone, rxdone) = channel(); + + let srv = t!(pool.bind_tcp_listener(&addr)); + let _t = thread::spawn(move || { + let mut cl = t!(srv.accept()).0; + cl.write(&[1, 3, 3, 7]).unwrap(); + t!(rxdone.recv()); + }); + + let mut c = t!(pool.connect_tcp_stream(&addr)); + let mut b = [0; 10]; + for _ in 1..3 { + let len = c.peek(&mut b).unwrap(); + assert_eq!(len, 4); + } + let len = c.read(&mut b).unwrap(); + assert_eq!(len, 4); + + t!(c.set_nonblocking(true)); + match c.peek(&mut b) { + Ok(_) => panic!("expected error"), + Err(ref e) if e.kind() == ErrorKind::WouldBlock => {} + Err(e) => panic!("unexpected error {}", e), + } + t!(txdone.send(())); + }) +} + +#[test] +#[cfg_attr(target_env = "sgx", ignore)] // FIXME: https://github.com/fortanix/rust-sgx/issues/31 +fn connect_timeout_valid() { + let mut pool = Pool::new(); + pool.insert_socket_addr("127.0.0.1:0".parse().unwrap(), ambient_authority()); + let listener = pool.bind_tcp_listener("127.0.0.1:0").unwrap(); + let addr = listener.local_addr().unwrap(); + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + pool.connect_timeout_tcp_stream(&addr, Duration::from_secs(2)) + .unwrap(); +} diff --git a/crates/wasi/src/filesystem/primitives/tests/net_udp.rs b/crates/wasi/src/filesystem/primitives/tests/net_udp.rs new file mode 100644 index 000000000000..f692f35769bc --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/net_udp.rs @@ -0,0 +1,451 @@ +// This file is derived from Rust's library/std/src/net/udp/tests.rs at +// revision 377d1a984cd2a53327092b90aa1d8b7e22d1e347. + +mod net; +mod sys_common; + +use cap_std::ambient_authority; +use cap_std::net::*; +use net::{next_test_ip4, next_test_ip6}; +use std::io::ErrorKind; +use std::sync::mpsc::channel; +//use sys_common::AsInner; +use std::thread; +use std::time::{Duration, Instant}; + +fn each_ip(f: &mut dyn FnMut(SocketAddr, SocketAddr)) { + f(next_test_ip4(), next_test_ip4()); + f(next_test_ip6(), next_test_ip6()); +} + +macro_rules! t { + ($e:expr) => { + match $e { + Ok(t) => t, + Err(e) => panic!("received error for `{}`: {}", stringify!($e), e), + } + }; +} + +#[test] +fn bind_error() { + let mut pool = Pool::new(); + pool.insert_socket_addr("1.1.1.1:9999".parse().unwrap(), ambient_authority()); + + match pool.bind_udp_socket("1.1.1.1:9999") { + Ok(..) => panic!(), + Err(e) => assert_eq!(e.kind(), ErrorKind::AddrNotAvailable), + } +} + +#[test] +fn socket_smoke_test_ip4() { + each_ip(&mut |server_ip, client_ip| { + let mut client_pool = Pool::new(); + client_pool.insert_socket_addr(client_ip, ambient_authority()); + let mut server_pool = Pool::new(); + server_pool.insert_socket_addr(server_ip, ambient_authority()); + + let (tx1, rx1) = channel(); + let (tx2, rx2) = channel(); + + let p = server_pool.clone(); + let _t = thread::spawn(move || { + let client = t!(client_pool.bind_udp_socket(&client_ip)); + rx1.recv().unwrap(); + t!(p.send_to_udp_socket_addr(&client, &[99], &server_ip)); + tx2.send(()).unwrap(); + }); + + let server = t!(server_pool.bind_udp_socket(&server_ip)); + tx1.send(()).unwrap(); + let mut buf = [0]; + let (nread, src) = t!(server.recv_from(&mut buf)); + assert_eq!(nread, 1); + assert_eq!(buf[0], 99); + assert_eq!(src, client_ip); + rx2.recv().unwrap(); + }) +} + +#[test] +fn socket_name() { + each_ip(&mut |addr, _| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let server = t!(pool.bind_udp_socket(&addr)); + assert_eq!(addr, t!(server.local_addr())); + }) +} + +#[test] +fn socket_peer() { + each_ip(&mut |addr1, addr2| { + let mut pool1 = Pool::new(); + pool1.insert_socket_addr(addr1, ambient_authority()); + let mut pool2 = Pool::new(); + pool2.insert_socket_addr(addr2, ambient_authority()); + + let server = t!(pool1.bind_udp_socket(&addr1)); + assert_eq!( + server.peer_addr().unwrap_err().kind(), + ErrorKind::NotConnected + ); + t!(pool2.connect_udp_socket(&server, &addr2)); + assert_eq!(addr2, t!(server.peer_addr())); + }) +} + +#[test] +fn udp_clone_smoke() { + each_ip(&mut |addr1, addr2| { + let mut pool1 = Pool::new(); + pool1.insert_socket_addr(addr1, ambient_authority()); + let mut pool2 = Pool::new(); + pool2.insert_socket_addr(addr2, ambient_authority()); + + let sock1 = t!(pool1.bind_udp_socket(&addr1)); + let sock2 = t!(pool2.bind_udp_socket(&addr2)); + + let _t = thread::spawn(move || { + let mut buf = [0, 0]; + assert_eq!(sock2.recv_from(&mut buf).unwrap(), (1, addr1)); + assert_eq!(buf[0], 1); + t!(pool1.send_to_udp_socket_addr(&sock2, &[2], &addr1)); + }); + + let sock3 = t!(sock1.try_clone()); + + let (tx1, rx1) = channel(); + let (tx2, rx2) = channel(); + let p = pool2.clone(); + let _t = thread::spawn(move || { + rx1.recv().unwrap(); + t!(p.send_to_udp_socket_addr(&sock3, &[1], &addr2)); + tx2.send(()).unwrap(); + }); + tx1.send(()).unwrap(); + let mut buf = [0, 0]; + assert_eq!(sock1.recv_from(&mut buf).unwrap(), (1, addr2)); + rx2.recv().unwrap(); + }) +} + +#[test] +fn udp_clone_two_read() { + each_ip(&mut |addr1, addr2| { + let mut pool1 = Pool::new(); + pool1.insert_socket_addr(addr1, ambient_authority()); + let mut pool2 = Pool::new(); + pool2.insert_socket_addr(addr2, ambient_authority()); + + let sock1 = t!(pool1.bind_udp_socket(&addr1)); + let sock2 = t!(pool2.bind_udp_socket(&addr2)); + let (tx1, rx) = channel(); + let tx2 = tx1.clone(); + + let _t = thread::spawn(move || { + t!(pool1.send_to_udp_socket_addr(&sock2, &[1], &addr1)); + rx.recv().unwrap(); + t!(pool1.send_to_udp_socket_addr(&sock2, &[2], &addr1)); + rx.recv().unwrap(); + }); + + let sock3 = t!(sock1.try_clone()); + + let (done, rx) = channel(); + let _t = thread::spawn(move || { + let mut buf = [0, 0]; + t!(sock3.recv_from(&mut buf)); + tx2.send(()).unwrap(); + done.send(()).unwrap(); + }); + let mut buf = [0, 0]; + t!(sock1.recv_from(&mut buf)); + tx1.send(()).unwrap(); + + rx.recv().unwrap(); + }) +} + +#[test] +fn udp_clone_two_write() { + each_ip(&mut |addr1, addr2| { + let mut pool1 = Pool::new(); + pool1.insert_socket_addr(addr1, ambient_authority()); + let mut pool2 = Pool::new(); + pool2.insert_socket_addr(addr2, ambient_authority()); + + let sock1 = t!(pool1.bind_udp_socket(&addr1)); + let sock2 = t!(pool2.bind_udp_socket(&addr2)); + + let (tx, rx) = channel(); + let (serv_tx, serv_rx) = channel(); + + let _t = thread::spawn(move || { + let mut buf = [0, 1]; + rx.recv().unwrap(); + t!(sock2.recv_from(&mut buf)); + serv_tx.send(()).unwrap(); + }); + + let sock3 = t!(sock1.try_clone()); + + let (done, rx) = channel(); + let tx2 = tx.clone(); + let p = pool2.clone(); + let _t = thread::spawn(move || { + if p.send_to_udp_socket_addr(&sock3, &[1], &addr2).is_ok() { + let _ = tx2.send(()); + } + done.send(()).unwrap(); + }); + if pool2.send_to_udp_socket_addr(&sock1, &[2], &addr2).is_ok() { + let _ = tx.send(()); + } + drop(tx); + + rx.recv().unwrap(); + serv_rx.recv().unwrap(); + }) +} + +/* Disable this test, as it depends on Rust-internal details. +#[test] +fn debug() { + let name = if cfg!(windows) { "socket" } else { "fd" }; + let socket_addr = next_test_ip4(); + + let mut pool = Pool::new(); + + let udpsock_inner = udpsock.0.socket().as_inner(); + let compare = format!("UdpSocket {{ addr: {:?}, {}: {:?} }}", socket_addr, name, udpsock_inner); + assert_eq!(format!("{:?}", udpsock), compare); +} +*/ + +// FIXME: re-enabled openbsd/netbsd tests once their socket timeout code +// no longer has rounding errors. +// VxWorks ignores SO_SNDTIMEO. +#[cfg_attr( + any(target_os = "netbsd", target_os = "openbsd", target_os = "vxworks"), + ignore +)] +#[test] +fn timeouts() { + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let stream = t!(pool.bind_udp_socket(&addr)); + let dur = Duration::new(15410, 0); + + assert_eq!(None, t!(stream.read_timeout())); + + t!(stream.set_read_timeout(Some(dur))); + assert_eq!(Some(dur), t!(stream.read_timeout())); + + assert_eq!(None, t!(stream.write_timeout())); + + t!(stream.set_write_timeout(Some(dur))); + assert_eq!(Some(dur), t!(stream.write_timeout())); + + t!(stream.set_read_timeout(None)); + assert_eq!(None, t!(stream.read_timeout())); + + t!(stream.set_write_timeout(None)); + assert_eq!(None, t!(stream.write_timeout())); +} + +#[test] +fn test_read_timeout() { + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let stream = t!(pool.bind_udp_socket(&addr)); + t!(stream.set_read_timeout(Some(Duration::from_millis(1000)))); + + let mut buf = [0; 10]; + + let start = Instant::now(); + loop { + let kind = stream + .recv_from(&mut buf) + .err() + .expect("expected error") + .kind(); + if kind != ErrorKind::Interrupted { + assert!( + kind == ErrorKind::WouldBlock || kind == ErrorKind::TimedOut, + "unexpected_error: {:?}", + kind + ); + break; + } + } + assert!(start.elapsed() > Duration::from_millis(400)); +} + +#[test] +fn test_read_with_timeout() { + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let stream = t!(pool.bind_udp_socket(&addr)); + t!(stream.set_read_timeout(Some(Duration::from_millis(1000)))); + + t!(pool.send_to_udp_socket_addr(&stream, b"hello world", &addr)); + + let mut buf = [0; 11]; + t!(stream.recv_from(&mut buf)); + assert_eq!(b"hello world", &buf[..]); + + let start = Instant::now(); + loop { + let kind = stream + .recv_from(&mut buf) + .err() + .expect("expected error") + .kind(); + if kind != ErrorKind::Interrupted { + assert!( + kind == ErrorKind::WouldBlock || kind == ErrorKind::TimedOut, + "unexpected_error: {:?}", + kind + ); + break; + } + } + assert!(start.elapsed() > Duration::from_millis(400)); +} + +// Ensure the `set_read_timeout` and `set_write_timeout` calls return errors +// when passed zero Durations +#[test] +fn test_timeout_zero_duration() { + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let socket = t!(pool.bind_udp_socket(&addr)); + + let result = socket.set_write_timeout(Some(Duration::new(0, 0))); + let err = result.unwrap_err(); + assert_eq!(err.kind(), ErrorKind::InvalidInput); + + let result = socket.set_read_timeout(Some(Duration::new(0, 0))); + let err = result.unwrap_err(); + assert_eq!(err.kind(), ErrorKind::InvalidInput); +} + +#[test] +fn connect_send_recv() { + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let socket = t!(pool.bind_udp_socket(&addr)); + t!(pool.connect_udp_socket(&socket, addr)); + + t!(socket.send(b"hello world")); + + let mut buf = [0; 11]; + t!(socket.recv(&mut buf)); + assert_eq!(b"hello world", &buf[..]); +} + +#[test] +fn connect_send_peek_recv() { + each_ip(&mut |addr, _| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let socket = t!(pool.bind_udp_socket(&addr)); + t!(pool.connect_udp_socket(&socket, addr)); + + t!(socket.send(b"hello world")); + + for _ in 1..3 { + let mut buf = [0; 11]; + let size = t!(socket.peek(&mut buf)); + assert_eq!(b"hello world", &buf[..]); + assert_eq!(size, 11); + } + + let mut buf = [0; 11]; + let size = t!(socket.recv(&mut buf)); + assert_eq!(b"hello world", &buf[..]); + assert_eq!(size, 11); + }) +} + +#[test] +fn peek_from() { + each_ip(&mut |addr, _| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let socket = t!(pool.bind_udp_socket(&addr)); + t!(pool.send_to_udp_socket_addr(&socket, b"hello world", &addr)); + + for _ in 1..3 { + let mut buf = [0; 11]; + let (size, _) = t!(socket.peek_from(&mut buf)); + assert_eq!(b"hello world", &buf[..]); + assert_eq!(size, 11); + } + + let mut buf = [0; 11]; + let (size, _) = t!(socket.recv_from(&mut buf)); + assert_eq!(b"hello world", &buf[..]); + assert_eq!(size, 11); + }) +} + +#[test] +fn ttl() { + let ttl = 100; + + let addr = next_test_ip4(); + + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let stream = t!(pool.bind_udp_socket(&addr)); + + t!(stream.set_ttl(ttl)); + assert_eq!(ttl, t!(stream.ttl())); +} + +#[test] +fn set_nonblocking() { + each_ip(&mut |addr, _| { + let mut pool = Pool::new(); + pool.insert_socket_addr(addr, ambient_authority()); + + let socket = t!(pool.bind_udp_socket(&addr)); + + t!(socket.set_nonblocking(true)); + t!(socket.set_nonblocking(false)); + + t!(pool.connect_udp_socket(&socket, addr)); + + t!(socket.set_nonblocking(false)); + t!(socket.set_nonblocking(true)); + + let mut buf = [0]; + match socket.recv(&mut buf) { + Ok(_) => panic!("expected error"), + Err(ref e) if e.kind() == ErrorKind::WouldBlock => {} + Err(e) => panic!("unexpected error {}", e), + } + }) +} diff --git a/crates/wasi/src/filesystem/primitives/tests/open_ambient.rs b/crates/wasi/src/filesystem/primitives/tests/open_ambient.rs new file mode 100644 index 000000000000..870ea0976cbb --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/open_ambient.rs @@ -0,0 +1,29 @@ +#[macro_use] +mod sys_common; + +use cap_std::ambient_authority; +use cap_std::fs::{Dir, File}; + +#[test] +fn test_open_ambient() { + let _ = File::open_ambient("Cargo.toml", ambient_authority()).unwrap(); +} + +#[test] +fn test_create_ambient() { + let dir = tempfile::tempdir().unwrap(); + let foo_path = dir.path().join("foo"); + let _ = File::create_ambient(&foo_path, ambient_authority()).unwrap(); + let _ = File::open_ambient(&foo_path, ambient_authority()).unwrap(); + let _ = File::create_ambient(&foo_path, ambient_authority()).unwrap(); +} + +#[test] +fn test_create_dir_ambient() { + let dir = tempfile::tempdir().unwrap(); + let foo_path = dir.path().join("foo"); + Dir::create_ambient_dir_all(&foo_path, ambient_authority()).unwrap(); + let foo = Dir::open_ambient_dir(&foo_path, ambient_authority()).unwrap(); + let base = foo.open_parent_dir(ambient_authority()).unwrap(); + let _foo_again = base.open_dir("foo").unwrap(); +} diff --git a/crates/wasi/src/filesystem/primitives/tests/paths_containing_nul.rs b/crates/wasi/src/filesystem/primitives/tests/paths_containing_nul.rs new file mode 100644 index 000000000000..2eefc9c808a5 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/paths_containing_nul.rs @@ -0,0 +1,70 @@ +// This test module derived from Rust's src/test/ui/paths-containing-nul.rs +// at revision 108e90ca78f052c0c1c49c42a22c85620be19712. + +// run-pass + +#![allow(deprecated)] +// ignore-cloudabi no files or I/O +// ignore-wasm32-bare no files or I/O +// ignore-emscripten no files +// ignore-sgx no files + +mod sys_common; + +use std::io; +use sys_common::io::tmpdir; + +fn assert_invalid_input(on: &str, result: io::Result) { + fn inner(on: &str, result: io::Result<()>) { + match result { + Ok(()) => panic!("{} didn't return an error on a path with NUL", on), + Err(_e) => { + // TODO: Re-enable this assertion once the `io_error_more` + // feature is available. + /* + assert_eq!( + e.kind(), + io::ErrorKind::InvalidInput || io::ErrorKind::InvalidFilename, + "{} returned a strange {:?} on a path with NUL", + on, + e + ); + */ + } + } + } + inner(on, result.map(drop)) +} + +#[test] +fn paths_containing_nul() { + let tmpdir = tmpdir(); + + assert_invalid_input("File::open", tmpdir.open("\0")); + assert_invalid_input("File::create", tmpdir.create("\0")); + assert_invalid_input("remove_file", tmpdir.remove_file("\0")); + assert_invalid_input("metadata", tmpdir.metadata("\0")); + assert_invalid_input("symlink_metadata", tmpdir.symlink_metadata("\0")); + + // Create a file inside the sandbox. + let dummy_file = "dummy_file"; + tmpdir.create(dummy_file).expect("creating dummy_file"); + + assert_invalid_input("rename1", tmpdir.rename("\0", &tmpdir, "a")); + assert_invalid_input("rename2", tmpdir.rename(&dummy_file, &tmpdir, "\0")); + assert_invalid_input("copy1", tmpdir.copy("\0", &tmpdir, "a")); + assert_invalid_input("copy2", tmpdir.copy(&dummy_file, &tmpdir, "\0")); + assert_invalid_input("hard_link1", tmpdir.hard_link("\0", &tmpdir, "a")); + assert_invalid_input("hard_link2", tmpdir.hard_link(&dummy_file, &tmpdir, "\0")); + //fixmeassert_invalid_input("soft_link1", tmpdir.soft_link("\0", &tmpdir, + // "a")); fixmeassert_invalid_input("soft_link2", + // tmpdir.soft_link(&dummy_file, &tmpdir, "\0")); + assert_invalid_input("read_link", tmpdir.read_link("\0")); + assert_invalid_input("canonicalize", tmpdir.canonicalize("\0")); + assert_invalid_input("create_dir", tmpdir.create_dir("\0")); + assert_invalid_input("create_dir_all", tmpdir.create_dir_all("\0")); + assert_invalid_input("remove_dir", tmpdir.remove_dir("\0")); + assert_invalid_input("remove_dir_all", tmpdir.remove_dir_all("\0")); + assert_invalid_input("read_dir", tmpdir.read_dir("\0")); + // `Dir` has no `set_permissions` function. +} diff --git a/crates/wasi/src/filesystem/primitives/tests/rand.rs b/crates/wasi/src/filesystem/primitives/tests/rand.rs new file mode 100644 index 000000000000..b779e23fa4c5 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/rand.rs @@ -0,0 +1,5 @@ +#[test] +fn test_std_rng_from_entropy() { + let rng = cap_rand::std_rng_from_os_rng(cap_rand::ambient_authority()); + assert_eq!(rng.clone(), rng); +} diff --git a/crates/wasi/src/filesystem/primitives/tests/readdir.rs b/crates/wasi/src/filesystem/primitives/tests/readdir.rs new file mode 100644 index 000000000000..5b94c0d8c72e --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/readdir.rs @@ -0,0 +1,85 @@ +use cap_std::ambient_authority; +use cap_std::fs::{Dir, DirEntry}; +use std::collections::HashMap; +use std::path::Path; + +#[test] +fn test_dir_entries() { + let tmpdir = tempfile::tempdir().expect("construct tempdir"); + + let entries = dir_entries(&tmpdir.path()); + assert_eq!(entries.len(), 0, "empty dir"); + + let _f1 = std::fs::File::create(tmpdir.path().join("file1")).expect("create file1"); + + let entries = dir_entries(&tmpdir.path()); + assert!( + entries.get("file1").is_some(), + "directory contains `file1`: {:?}", + entries + ); + assert_eq!(entries.len(), 1); + + let _f2 = std::fs::File::create(tmpdir.path().join("file2")).expect("create file1"); + let entries = dir_entries(&tmpdir.path()); + assert!( + entries.get("file1").is_some(), + "directory contains `file1`: {:?}", + entries + ); + assert!( + entries.get("file2").is_some(), + "directory contains `file2`: {:?}", + entries + ); + assert_eq!(entries.len(), 2); +} + +#[test] +fn test_reread_entries() { + let tmpdir = tempfile::tempdir().expect("construct tempdir"); + let dir = Dir::open_ambient_dir(tmpdir.path(), ambient_authority()).unwrap(); + + let entries = read_entries(&dir); + assert_eq!(entries.len(), 0, "empty dir"); + + let _f1 = std::fs::File::create(tmpdir.path().join("file1")).expect("create file1"); + + let entries = read_entries(&dir); + assert!( + entries.get("file1").is_some(), + "directory contains `file1`: {:?}", + entries + ); + assert_eq!(entries.len(), 1); + + let _f2 = std::fs::File::create(tmpdir.path().join("file2")).expect("create file1"); + let entries = read_entries(&dir); + assert!( + entries.get("file1").is_some(), + "directory contains `file1`: {:?}", + entries + ); + assert!( + entries.get("file2").is_some(), + "directory contains `file2`: {:?}", + entries + ); + assert_eq!(entries.len(), 2); +} + +fn dir_entries(path: &Path) -> HashMap { + let dir = Dir::open_ambient_dir(path, ambient_authority()).unwrap(); + read_entries(&dir) +} + +fn read_entries(dir: &Dir) -> HashMap { + let mut out = HashMap::new(); + for e in dir.entries().unwrap() { + let e = e.expect("non-error entry"); + let name = e.file_name().to_str().expect("utf8 filename").to_owned(); + assert!(out.get(&name).is_none(), "name already read: {}", name); + out.insert(name, e); + } + out +} diff --git a/crates/wasi/src/filesystem/primitives/tests/rename.rs b/crates/wasi/src/filesystem/primitives/tests/rename.rs new file mode 100644 index 000000000000..7b487fa891fb --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/rename.rs @@ -0,0 +1,178 @@ +#[macro_use] +mod sys_common; + +use sys_common::io::tmpdir; + +/* +#[cfg(not(windows))] +fn rename_path_in_use() -> String { + rustix::io::Errno::BUSY.into().to_string() +} +#[cfg(windows)] +fn rename_path_in_use() -> String { + todo!("work out error for rename_path_in_use condition") +} +*/ + +#[cfg(not(windows))] +fn no_such_file_or_directory() -> String { + rustix::io::Errno::NOENT.to_string() +} +#[cfg(windows)] +fn no_such_file_or_directory() -> String { + std::io::Error::from_raw_os_error(windows_sys::Win32::Foundation::ERROR_FILE_NOT_FOUND as i32) + .to_string() +} + +/* // TODO: Platform-specific error code. +cfg_if::cfg_if! { + if #[cfg(any( + target_os = "macos", + target_os = "netbsd", + target_os = "freebsd", + target_os = "openbsd", + target_os = "ios", + target_os = "tvos", + target_os = "watchos", + target_os = "visionos", + target_os = "dragonfly" + ))] { + fn rename_file_over_dir() -> String { + rustix::io::Errno::ISDIR.into().to_string() + } + + fn rename_file_over_dot() -> String { + rename_file_over_dir() + } + + fn rename_dot_over_file() -> String { + rustix::io::Errno::INVAL.into().to_string() + } + } else { + fn rename_file_over_dir() -> String { + rustix::io::Errno::NOTEMPTY.into().to_string() + } + + fn rename_file_over_dot() -> String { + rename_path_in_use() + } + + fn rename_dot_over_file() -> String { + rename_path_in_use() + } + } +} +*/ + +#[test] +#[cfg_attr(windows, ignore)] // TODO: Blocked on error message discrepancies +fn rename_basics() { + let tmpdir = tmpdir(); + + check!(tmpdir.create_dir_all("foo/bar")); + check!(tmpdir.create("foo/bar/file.txt")); + + check!(tmpdir.rename("foo/bar/file.txt", &tmpdir, "foo/bar/renamed.txt")); + assert!(!tmpdir.exists("foo/bar/file.txt")); + assert!(tmpdir.exists("foo/bar/renamed.txt")); + + check!(tmpdir.rename("foo/bar/renamed.txt", &tmpdir, "foo/bar/renamed.txt")); + error_contains!( + tmpdir.rename("foo/bar/renamed.txt", &tmpdir, ".."), + "a path led outside of the filesystem" + ); + error_contains!( + tmpdir.rename("foo/bar/renamed.txt", &tmpdir, "foo/../.."), + "a path led outside of the filesystem" + ); + error_contains!( + tmpdir.rename("foo/bar/renamed.txt", &tmpdir, "/tmp"), + "a path led outside of the filesystem" + ); + error_contains!( + tmpdir.rename("foo/bar/renamed.txt", &tmpdir, "foo/bar/baz/.."), + &no_such_file_or_directory() + ); + /* // TODO: Platform-specific error code. + error!( + tmpdir.rename("foo/bar/renamed.txt", &tmpdir, "foo/bar"), + &rename_file_over_dir() + ); + */ + check!(tmpdir.rename("foo/bar", &tmpdir, "foo/bar")); + check!(tmpdir.rename("foo/bar/renamed.txt", &tmpdir, "file.txt")); + assert!(!tmpdir.exists("foo/bar/renamed.txt")); + assert!(tmpdir.exists("file.txt")); + + /* // TODO: Platform-specific error code. + error_contains!( + tmpdir.rename("file.txt", &tmpdir, "foo/.."), + &rename_path_in_use() + ); + error_contains!( + tmpdir.rename("file.txt", &tmpdir, "foo/."), + &rename_path_in_use() + ); + error_contains!( + tmpdir.rename("file.txt", &tmpdir, "foo/bar/../.."), + &rename_path_in_use() + ); + */ + error_contains!( + tmpdir.rename("file.txt", &tmpdir, "foo/bar/../../.."), + "a path led outside of the filesystem" + ); + error_contains!( + tmpdir.rename("file.txt", &tmpdir, "foo/bar/../../../something"), + "a path led outside of the filesystem" + ); + error_contains!(tmpdir.rename("file.txt", &tmpdir, ""), "No such file"); + error_contains!( + tmpdir.rename("file.txt", &tmpdir, "/"), + "a path led outside of the filesystem" + ); + error_contains!( + tmpdir.rename("file.txt", &tmpdir, "/."), + "a path led outside of the filesystem" + ); + error_contains!( + tmpdir.rename("file.txt", &tmpdir, "/.."), + "a path led outside of the filesystem" + ); + error_contains!( + tmpdir.rename("/", &tmpdir, "nope.txt"), + "a path led outside of the filesystem" + ); + error_contains!( + tmpdir.rename("/..", &tmpdir, "nope.txt"), + "a path led outside of the filesystem" + ); + error_contains!( + tmpdir.rename("file.txt/", &tmpdir, "nope.txt"), + "Not a directory" + ); + + /* // TODO: Platform-specific error code. + error!( + tmpdir.rename("file.txt", &tmpdir, "."), + &rename_file_over_dot() + ); + error!( + tmpdir.rename("file.txt", &tmpdir, ".."), + &rename_path_in_use() + ); + error!( + tmpdir.rename("..", &tmpdir, "nope.txt"), + &rename_path_in_use() + ); + error!( + tmpdir.rename(".", &tmpdir, "nope.txt"), + &rename_dot_over_file() + ); + */ + + check!(tmpdir.create("existing.txt")); + check!(tmpdir.rename("file.txt", &tmpdir, "existing.txt")); + assert!(!tmpdir.exists("file.txt")); + assert!(tmpdir.exists("existing.txt")); +} diff --git a/crates/wasi/src/filesystem/primitives/tests/rename_directory.rs b/crates/wasi/src/filesystem/primitives/tests/rename_directory.rs new file mode 100644 index 000000000000..46bc2f95459e --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/rename_directory.rs @@ -0,0 +1,36 @@ +// This test module derived from Rust's +// src/test/ui-fulldeps/rename-directory.rs at revision +// 108e90ca78f052c0c1c49c42a22c85620be19712. + +// run-pass + +#![allow(unused_must_use)] +#![allow(unused_imports)] +// This test can't be a unit test in std, +// because it needs `TempDir`, which is in extra + +// ignore-cross-compile + +mod sys_common; + +use cap_std::fs::{self, File}; +use std::env; +use std::ffi::CString; +use std::path::{Path, PathBuf}; +use sys_common::io::tmpdir; + +#[test] +fn rename_directory() { + let tmpdir = tmpdir(); + let old_path = Path::new("foo/bar/baz"); + tmpdir.create_dir_all(&old_path).unwrap(); + let test_file = &old_path.join("temp.txt"); + + tmpdir.create(test_file).unwrap(); + + let new_path = Path::new("quux/blat"); + tmpdir.create_dir_all(&new_path).unwrap(); + tmpdir.rename(&old_path, &tmpdir, &new_path.join("newdir")); + assert!(tmpdir.is_dir(new_path.join("newdir"))); + assert!(tmpdir.exists(new_path.join("newdir/temp.txt"))); +} diff --git a/crates/wasi/src/filesystem/primitives/tests/reopen.rs b/crates/wasi/src/filesystem/primitives/tests/reopen.rs new file mode 100644 index 000000000000..251bf1d22542 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/reopen.rs @@ -0,0 +1,93 @@ +#![cfg(not(target_os = "freebsd"))] // FreeBSD can't reopen arbitrary files. + +#[macro_use] +mod sys_common; + +use cap_fs_ext::{OpenOptions, Reopen}; +use std::io::{Read, Write}; +use sys_common::io::tmpdir; +#[cfg(windows)] +use windows_sys::Win32::Storage::FileSystem::FILE_GENERIC_READ; + +#[test] +fn basic_reopen() { + let tmpdir = tmpdir(); + + // Open the file with both read and write privileges, so that we can + // write to it, and then reopen it for reading. + let mut file = check!(tmpdir.open_with( + "file", + OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .read(true) + )); + check!(file.write_all(b"hello, world")); + + let mut buf = String::new(); + check!(file.read_to_string(&mut buf)); + assert!(buf.is_empty()); + + let mut ro = check!(file.reopen(OpenOptions::new().read(true))); + let mut another = check!(file.reopen(OpenOptions::new().read(true))); + check!(ro.read_to_string(&mut buf)); + assert_eq!(buf, "hello, world"); + buf.clear(); + check!(another.read_to_string(&mut buf)); + assert_eq!(buf, "hello, world"); + buf.clear(); + check!(another.read_to_string(&mut buf)); + assert!(buf.is_empty()); +} + +/// Don't allow `reopen` to grant new permissions. +#[test] +fn reopen_perms() { + let tmpdir = tmpdir(); + + let mut file = check!(tmpdir.create("file")); + check!(file.write_all(b"hello, world")); + + let mut buf = String::new(); + assert!(file.read_to_string(&mut buf).is_err()); + + // `file` is write-only, so can't re-open for reading. + assert!(file.reopen(OpenOptions::new().read(true)).is_err()); + + // `ro` is read-only, so can't re-open for writing. + let mut ro = check!(tmpdir.open("file")); + assert!(ro.write_all(b"hello, world").is_err()); + assert!(ro.reopen(OpenOptions::new().write(true)).is_err()); + check!(ro.read_to_string(&mut buf)); + assert_eq!(buf, "hello, world"); +} + +/// Test reopen using `access_mode` explicitly. +#[cfg(windows)] +#[test] +fn reopen_explicit_access() { + use cap_std::fs::OpenOptionsExt; + + let tmpdir = tmpdir(); + + // Open the file with both read and write privileges, so that we can + // write to it, and then reopen it for reading. + let mut file = check!(tmpdir.open_with( + "file", + OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .read(true) + )); + check!(file.write_all(b"hello, world")); + + let mut buf = String::new(); + check!(file.read_to_string(&mut buf)); + assert!(buf.is_empty()); + + let mut ro = check!(file.reopen(OpenOptions::new().access_mode(FILE_GENERIC_READ))); + check!(ro.read_to_string(&mut buf)); + assert_eq!(buf, "hello, world"); +} diff --git a/crates/wasi/src/filesystem/primitives/tests/reopendir.rs b/crates/wasi/src/filesystem/primitives/tests/reopendir.rs new file mode 100644 index 000000000000..3347ddcf0cd2 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/reopendir.rs @@ -0,0 +1,66 @@ +//! Tests for various forms of reopening a directory handle. + +#[macro_use] +mod sys_common; + +use sys_common::io::tmpdir; + +#[test] +fn reopendir_a() { + let tmpdir = tmpdir(); + check!(tmpdir.create_dir_all("dir/inner")); + + let inner = check!(tmpdir.open_dir("dir/inner")); + + check!(inner.open_dir(".")); +} + +#[test] +fn reopendir_b() { + let tmpdir = tmpdir(); + check!(tmpdir.create_dir_all("dir/inner")); + + let inner = check!(tmpdir.open_dir("dir/inner")); + + check!(inner.open_dir("./")); +} + +#[test] +fn reopendir_c() { + let tmpdir = tmpdir(); + check!(tmpdir.create_dir_all("dir/inner")); + + let inner = check!(tmpdir.open_dir("dir/inner")); + + check!(inner.open_dir("./.")); +} + +#[test] +fn reopendir_d() { + let tmpdir = tmpdir(); + check!(tmpdir.create_dir_all("dir/inner")); + + let _inner = check!(tmpdir.open_dir("dir/inner")); + + check!(tmpdir.open_dir("dir/inner")); +} + +#[test] +fn reopendir_e() { + let tmpdir = tmpdir(); + check!(tmpdir.create_dir_all("dir/inner")); + + let _inner = check!(tmpdir.open_dir("dir/inner")); + + check!(tmpdir.open_dir("dir/inner/.")); +} + +#[test] +fn reopendir_f() { + let tmpdir = tmpdir(); + check!(tmpdir.create_dir_all("dir/inner")); + + let _inner = check!(tmpdir.open_dir("dir/inner")); + + check!(tmpdir.open_dir("dir/inner/")); +} diff --git a/crates/wasi/src/filesystem/primitives/tests/root.rs b/crates/wasi/src/filesystem/primitives/tests/root.rs new file mode 100644 index 000000000000..2a158d422951 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/root.rs @@ -0,0 +1,32 @@ +#[macro_use] +mod sys_common; + +use cap_std::ambient_authority; +use cap_std::fs::Dir; +use std::fs; +use std::path::Component; + +#[test] +fn open_root() { + let root = Dir::open_ambient_dir(Component::RootDir.as_os_str(), ambient_authority()) + .expect("expect to be able to open the root directory"); + error_contains!( + root.read_dir(Component::ParentDir.as_os_str()), + "a path led outside of the filesystem" + ); +} + +/// Attempt to remove the root directory, which should fail, and check that the +/// error message is as expected. +#[test] +fn remove_root() { + let _observed = { + let root = Dir::open_ambient_dir(Component::RootDir.as_os_str(), ambient_authority()) + .expect("expect to be able to open the root directory"); + root.remove_open_dir().unwrap_err() + }; + let _expected = fs::remove_dir(Component::RootDir.as_os_str()).unwrap_err(); + // TODO: Investigate why these asserts spuriously fail on Windows and QEMU. + //assert_eq!(expected.to_string(), observed.to_string()); + //assert_eq!(expected.kind(), observed.kind()); +} diff --git a/crates/wasi/src/filesystem/primitives/tests/set.rs b/crates/wasi/src/filesystem/primitives/tests/set.rs new file mode 100644 index 000000000000..6f42864ac4fc --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/set.rs @@ -0,0 +1,132 @@ +// Test `set_permissions` and `set_times`. + +#[macro_use] +mod sys_common; + +use std::io; +use std::path::Path; +use sys_common::io::{tmpdir, TempDir}; +use sys_common::symlink_supported; + +#[cfg(not(windows))] +fn symlink_dir, Q: AsRef>(src: P, tmpdir: &TempDir, dst: Q) -> io::Result<()> { + tmpdir.symlink(src, dst) +} +#[cfg(not(windows))] +fn symlink_file, Q: AsRef>( + src: P, + tmpdir: &TempDir, + dst: Q, +) -> io::Result<()> { + tmpdir.symlink(src, dst) +} +#[cfg(windows)] +fn symlink_dir, Q: AsRef>(src: P, tmpdir: &TempDir, dst: Q) -> io::Result<()> { + tmpdir.symlink_dir(src, dst) +} +#[cfg(windows)] +fn symlink_file, Q: AsRef>( + src: P, + tmpdir: &TempDir, + dst: Q, +) -> io::Result<()> { + tmpdir.symlink_file(src, dst) +} + +#[test] +fn basic_perms() { + if !symlink_supported() { + return; + } + + let tmpdir = tmpdir(); + check!(tmpdir.create("file")); + check!(tmpdir.create_dir("dir")); + check!(symlink_file("file", &tmpdir, "symlink_file")); + check!(symlink_dir("dir", &tmpdir, "symlink_dir")); + + let mut file_perms = check!(tmpdir.metadata("file")).permissions(); + assert!(!file_perms.readonly()); + file_perms.set_readonly(true); + check!(tmpdir.set_permissions("file", file_perms.clone())); + assert_eq!( + check!(tmpdir.metadata("file")).permissions(), + file_perms.clone() + ); + assert_eq!( + check!(tmpdir.metadata("symlink_file")).permissions(), + file_perms + ); + + let mut dir_perms = check!(tmpdir.metadata("dir")).permissions(); + assert!(!dir_perms.readonly()); + dir_perms.set_readonly(true); + check!(tmpdir.set_permissions("dir", dir_perms.clone())); + assert_eq!( + check!(tmpdir.metadata("dir")).permissions(), + dir_perms.clone() + ); + assert_eq!( + check!(tmpdir.metadata("symlink_dir")).permissions(), + dir_perms + ); +} + +#[test] +fn symlink_perms() { + if !symlink_supported() { + return; + } + + let tmpdir = tmpdir(); + check!(tmpdir.create("file")); + check!(tmpdir.create_dir("dir")); + check!(symlink_file("file", &tmpdir, "symlink_file")); + check!(symlink_dir("dir", &tmpdir, "symlink_dir")); + + let mut file_perms = check!(tmpdir.metadata("symlink_file")).permissions(); + assert!(!file_perms.readonly()); + file_perms.set_readonly(true); + check!(tmpdir.set_permissions("symlink_file", file_perms.clone())); + + // On Windows, `set_permissions` does not follow symlinks. On non-Windows, + // it does. See https://github.com/rust-lang/rust/issues/75942 for discussion. + if cfg!(windows) { + assert_eq!( + check!(tmpdir.symlink_metadata("symlink_file")).permissions(), + file_perms + ); + } else { + assert_eq!( + check!(tmpdir.metadata("file")).permissions(), + file_perms.clone() + ); + assert_eq!( + check!(tmpdir.metadata("symlink_file")).permissions(), + file_perms + ); + } + + let mut dir_perms = check!(tmpdir.metadata("symlink_dir")).permissions(); + assert!(!dir_perms.readonly()); + dir_perms.set_readonly(true); + check!(tmpdir.set_permissions("symlink_dir", dir_perms.clone())); + + // On Windows, `set_permissions` does not follow symlinks. On non-Windows, + // it does. See https://github.com/rust-lang/rust/issues/75942 for discussion. + if cfg!(windows) { + assert_eq!( + check!(tmpdir.symlink_metadata("symlink_dir")).permissions(), + dir_perms + ); + } else { + assert_eq!( + check!(tmpdir.metadata("dir")).permissions(), + dir_perms.clone() + ); + assert_eq!( + check!(tmpdir.metadata("symlink_dir")).permissions(), + dir_perms + ); + } +} diff --git a/crates/wasi/src/filesystem/primitives/tests/set_times.rs b/crates/wasi/src/filesystem/primitives/tests/set_times.rs new file mode 100644 index 000000000000..d9f90ab3baaf --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/set_times.rs @@ -0,0 +1,134 @@ +#[macro_use] +mod sys_common; + +use cap_fs_ext::DirExt; +use cap_std::time::{SystemClock, SystemTime}; +use sys_common::io::tmpdir; +use sys_common::symlink_supported; + +fn modified_time(meta: cap_std::fs::Metadata) -> SystemTime { + meta.modified().unwrap() +} + +#[test] +fn basic_times() { + let test_symlinks = symlink_supported(); + + let tmpdir = tmpdir(); + check!(tmpdir.create("file")); + check!(tmpdir.create_dir("dir")); + if test_symlinks { + check!(tmpdir.symlink_file("file", "file_symlink_file")); + check!(tmpdir.symlink_dir("dir", "dir_symlink_dir")); + } + + let file_time = SystemClock::UNIX_EPOCH; + check!(tmpdir.set_times("file", None, Some(file_time.into()))); + assert_eq!(modified_time(check!(tmpdir.metadata("file"))), file_time); + if test_symlinks { + assert_eq!( + modified_time(check!(tmpdir.metadata("file_symlink_file"))), + file_time + ); + } + + let dir_time = SystemClock::UNIX_EPOCH; + check!(tmpdir.set_times("dir", None, Some(dir_time.into()))); + assert_eq!(modified_time(check!(tmpdir.metadata("dir"))), dir_time); + if test_symlinks { + assert_eq!( + modified_time(check!(tmpdir.metadata("dir_symlink_dir"))), + dir_time + ); + } +} + +#[test] +fn symlink_times() { + if !symlink_supported() { + return; + } + + let tmpdir = tmpdir(); + check!(tmpdir.create("file")); + check!(tmpdir.create_dir("dir")); + check!(tmpdir.symlink_file("file", "file_symlink_file")); + check!(tmpdir.symlink_dir("dir", "dir_symlink_dir")); + + let file_time = SystemClock::UNIX_EPOCH; + check!(tmpdir.set_times("file_symlink_file", None, Some(file_time.into()))); + assert_eq!(modified_time(check!(tmpdir.metadata("file"))), file_time); + assert_eq!( + modified_time(check!(tmpdir.metadata("file_symlink_file"))), + file_time + ); + assert_eq!( + modified_time(check!(tmpdir.symlink_metadata("file"))), + file_time + ); + assert_ne!( + modified_time(check!(tmpdir.symlink_metadata("file_symlink_file"))), + file_time + ); + + let dir_time = SystemClock::UNIX_EPOCH; + check!(tmpdir.set_times("dir_symlink_dir", None, Some(file_time.into()))); + assert_eq!(modified_time(check!(tmpdir.metadata("dir"))), dir_time); + assert_eq!( + modified_time(check!(tmpdir.metadata("dir_symlink_dir"))), + dir_time + ); + assert_eq!( + modified_time(check!(tmpdir.symlink_metadata("dir"))), + dir_time + ); + assert_ne!( + modified_time(check!(tmpdir.symlink_metadata("dir_symlink_dir"))), + dir_time + ); +} + +#[test] +fn symlink_itself_times() { + if !symlink_supported() { + return; + } + + let tmpdir = tmpdir(); + check!(tmpdir.create("file")); + check!(tmpdir.create_dir("dir")); + check!(tmpdir.symlink_file("file", "file_symlink_file")); + check!(tmpdir.symlink_dir("dir", "dir_symlink_dir")); + + let file_time = SystemClock::UNIX_EPOCH; + check!(tmpdir.set_symlink_times("file_symlink_file", None, Some(file_time.into()))); + assert_ne!(modified_time(check!(tmpdir.metadata("file"))), file_time); + assert_ne!( + modified_time(check!(tmpdir.metadata("file_symlink_file"))), + file_time + ); + assert_ne!( + modified_time(check!(tmpdir.symlink_metadata("file"))), + file_time + ); + assert_eq!( + modified_time(check!(tmpdir.symlink_metadata("file_symlink_file"))), + file_time + ); + + let dir_time = SystemClock::UNIX_EPOCH; + check!(tmpdir.set_symlink_times("dir_symlink_dir", None, Some(file_time.into()))); + assert_ne!(modified_time(check!(tmpdir.metadata("dir"))), dir_time); + assert_ne!( + modified_time(check!(tmpdir.metadata("dir_symlink_dir"))), + dir_time + ); + assert_ne!( + modified_time(check!(tmpdir.symlink_metadata("dir"))), + dir_time + ); + assert_eq!( + modified_time(check!(tmpdir.symlink_metadata("dir_symlink_dir"))), + dir_time + ); +} diff --git a/crates/wasi/src/filesystem/primitives/tests/symlinks.rs b/crates/wasi/src/filesystem/primitives/tests/symlinks.rs new file mode 100644 index 000000000000..5323a88147b5 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/symlinks.rs @@ -0,0 +1,580 @@ +#[macro_use] +mod sys_common; + +use cap_fs_ext::DirExt; +use cap_std::ambient_authority; +use cap_std::fs::Dir; +use sys_common::io::tmpdir; +use sys_common::symlink_supported; + +#[test] +fn basic_symlinks() { + if !symlink_supported() { + return; + } + + let tmpdir = tmpdir(); + + check!(tmpdir.create("file")); + check!(tmpdir.create_dir("dir")); + assert!(check!(tmpdir.metadata("file")).is_file()); + assert!(!check!(tmpdir.metadata("file")).is_dir()); + assert!(check!(tmpdir.metadata("dir")).is_dir()); + assert!(!check!(tmpdir.metadata("dir")).is_file()); + assert!(!check!(tmpdir.metadata("file")).is_symlink()); + assert!(!check!(tmpdir.metadata("dir")).is_symlink()); + + check!(tmpdir.symlink_file("file", "file_symlink_file")); + check!(tmpdir.symlink_dir("dir", "dir_symlink_dir")); + check!(tmpdir.symlink("file", "file_symlink")); + check!(tmpdir.symlink("dir", "dir_symlink")); + + assert!(check!(tmpdir.metadata("file_symlink_file")).is_file()); + assert!(check!(tmpdir.metadata("dir_symlink_dir")).is_dir()); + assert!(check!(tmpdir.metadata("file_symlink")).is_file()); + assert!(check!(tmpdir.metadata("dir_symlink")).is_dir()); + + assert!(!check!(tmpdir.metadata("file_symlink_file")).is_symlink()); + assert!(!check!(tmpdir.metadata("dir_symlink_dir")).is_symlink()); + assert!(!check!(tmpdir.metadata("file_symlink")).is_symlink()); + assert!(!check!(tmpdir.metadata("dir_symlink")).is_symlink()); + + assert!(check!(tmpdir.symlink_metadata("file_symlink_file")).is_symlink()); + assert!(check!(tmpdir.symlink_metadata("dir_symlink_dir")).is_symlink()); + assert!(check!(tmpdir.symlink_metadata("file_symlink")).is_symlink()); + assert!(check!(tmpdir.symlink_metadata("dir_symlink")).is_symlink()); + + assert!(!check!(tmpdir.metadata("file_symlink_file")) + .file_type() + .is_symlink()); + assert!(!check!(tmpdir.metadata("dir_symlink_dir")) + .file_type() + .is_symlink()); + assert!(!check!(tmpdir.metadata("file_symlink")) + .file_type() + .is_symlink()); + assert!(!check!(tmpdir.metadata("dir_symlink")) + .file_type() + .is_symlink()); + + assert!(check!(tmpdir.symlink_metadata("file_symlink_file")) + .file_type() + .is_symlink()); + assert!(check!(tmpdir.symlink_metadata("dir_symlink_dir")) + .file_type() + .is_symlink()); + assert!(check!(tmpdir.symlink_metadata("file_symlink")) + .file_type() + .is_symlink()); + assert!(check!(tmpdir.symlink_metadata("dir_symlink")) + .file_type() + .is_symlink()); +} + +#[test] +fn symlink_absolute() { + let tmpdir = tmpdir(); + + error_contains!( + tmpdir.symlink("/thing", "thing_symlink_file"), + "a path led outside of the filesystem" + ); + error_contains!( + tmpdir.symlink_file("/file", "file_symlink_file"), + "a path led outside of the filesystem" + ); + error_contains!( + tmpdir.symlink_dir("/dir", "dir_symlink_dir"), + "a path led outside of the filesystem" + ); +} + +#[test] +fn readlink_absolute() { + if !symlink_supported() { + return; + } + + let dir = tempfile::tempdir().unwrap(); + + #[cfg(not(windows))] + check!(std::os::unix::fs::symlink( + "/thing", + dir.path().join("thing_symlink") + )); + #[cfg(windows)] + check!(std::os::windows::fs::symlink_file( + "/file", + dir.path().join("file_symlink_file") + )); + #[cfg(windows)] + check!(std::os::windows::fs::symlink_dir( + "/dir", + dir.path().join("dir_symlink_dir") + )); + + let tmpdir = check!(Dir::open_ambient_dir(dir.path(), ambient_authority())); + + #[cfg(not(windows))] + error_contains!( + tmpdir.read_link("thing_symlink"), + "a path led outside of the filesystem" + ); + #[cfg(windows)] + error_contains!( + tmpdir.read_link("file_symlink_file"), + "a path led outside of the filesystem" + ); + #[cfg(windows)] + error_contains!( + tmpdir.read_link("dir_symlink_dir"), + "a path led outside of the filesystem" + ); +} + +/// Opening directories without following symlinks. +#[test] +fn open_dir_nofollow() { + use cap_fs_ext::DirExt; + + if !symlink_supported() { + return; + } + + let tmpdir = tmpdir(); + + check!(tmpdir.create("file")); + check!(tmpdir.create_dir("dir")); + check!(tmpdir.symlink_file("file", "symlink_file")); + check!(tmpdir.symlink_dir("dir", "symlink_dir")); + check!(tmpdir.symlink_dir("dir/", "symlink_dir_slash")); + check!(tmpdir.symlink_dir("dir/.", "symlink_dir_slashdot")); + check!(tmpdir.symlink_dir("dir/..", "symlink_dir_slashdotdot")); + check!(tmpdir.symlink_dir("dir/../", "symlink_dir_slashdotdotslash")); + check!(tmpdir.symlink_dir(".", "symlink_dot")); + check!(tmpdir.symlink_dir("./", "symlink_dotslash")); + + // First try without `nofollow`. The "symlink_dir" case should succeed. + assert!(tmpdir.open_dir("file").is_err()); + assert!(tmpdir.open_dir("symlink_file").is_err()); + check!(tmpdir.open_dir("symlink_dir")); + #[cfg(windows)] + check!(tmpdir.open_dir("symlink_dir\\")); + check!(tmpdir.open_dir("symlink_dir/")); + #[cfg(windows)] + { + error!(tmpdir.open_dir("symlink_dir_slash"), 123); + error!(tmpdir.open_dir("symlink_dir_slashdotdot"), 123); + error!(tmpdir.open_dir("symlink_dir_slashdotdotslash"), 123); + error!(tmpdir.open_dir("symlink_dotslash"), 123); + error!(tmpdir.open_dir("symlink_dir_slashdot"), 123); + } + #[cfg(not(windows))] + { + check!(tmpdir.open_dir("symlink_dir_slash")); + check!(tmpdir.open_dir("symlink_dir_slashdotdot")); + check!(tmpdir.open_dir("symlink_dir_slashdotdotslash")); + check!(tmpdir.open_dir("symlink_dotslash")); + check!(tmpdir.open_dir("symlink_dir_slashdot")); + } + check!(tmpdir.open_dir("symlink_dot")); + check!(tmpdir.open_dir("dir")); + + // Next try with `nofollow`. The "symlink_dir" case should fail. + assert!(tmpdir.open_dir_nofollow("file").is_err()); + assert!(tmpdir.open_dir_nofollow("symlink_file").is_err()); + assert!(tmpdir.open_dir_nofollow("symlink_dir").is_err()); + assert!(tmpdir.open_dir_nofollow("symlink_dir_slash").is_err()); + assert!(tmpdir.open_dir_nofollow("symlink_dir_slashdot").is_err()); + assert!(tmpdir.open_dir_nofollow("symlink_dir_slashdotdot").is_err()); + assert!(tmpdir + .open_dir_nofollow("symlink_dir_slashdotdotslash") + .is_err()); + assert!(tmpdir.open_dir_nofollow("symlink_dot").is_err()); + assert!(tmpdir.open_dir_nofollow("symlink_dotslash").is_err()); + check!(tmpdir.open_dir_nofollow("dir")); + + // Check various ways of spelling `dir/../symlink_dir`. + for dir_name in &["dir", "symlink_dir"] { + let name = format!("{}/../symlink_dir", dir_name); + check!(tmpdir.open_dir(&name)); + assert!(tmpdir.open_dir_nofollow(&name).is_err()); + } + + // Check various paths which end with a symlink (even though the symlink + // expansion may end with `/` or a non-symlink). + for suffix in &[""] { + for symlink_dir in &["symlink_dot"] { + let name = format!("{}{}", symlink_dir, suffix); + check!(tmpdir.open_dir(&name)); + assert!(tmpdir.open_dir_nofollow(&name).is_err()); + for dir_name in &["dir", "symlink_dir"] { + let name = format!("{}/../{}", dir_name, name); + check!(tmpdir.open_dir(&name)); + assert!(tmpdir.open_dir_nofollow(&name).is_err()); + } + } + } + + // Check more paths which end with a symlink. On Windows, these fail due to + // the symlink-to-path-ending-in-trailing-slash error. + for suffix in &[""] { + for symlink_dir in &[ + "symlink_dir_slashdotdot", + "symlink_dir_slashdot", + "symlink_dir_slash", + "symlink_dir_slashdotdotslash", + "symlink_dotslash", + ] { + let name = format!("{}{}", symlink_dir, suffix); + #[cfg(windows)] + { + error!(tmpdir.open_dir(&name), 123); + } + #[cfg(not(windows))] + { + check!(tmpdir.open_dir(&name)); + } + assert!(tmpdir.open_dir_nofollow(&name).is_err()); + for dir_name in &["dir", "symlink_dir"] { + let name = format!("{}/../{}", dir_name, name); + #[cfg(windows)] + { + error!(tmpdir.open_dir(&name), 123); + } + #[cfg(not(windows))] + { + check!(tmpdir.open_dir(&name)); + } + assert!(tmpdir.open_dir_nofollow(&name).is_err()); + } + } + } + + // Check those same paths, but with various suffixes appended, so that + // `open_dir_nofollow` can open them. + for suffix in &["/", "/.", "/./"] { + for symlink_dir in &["symlink_dir", "symlink_dot"] { + let name = format!("{}{}", symlink_dir, suffix); + check!(tmpdir.open_dir(&name)); + // On Windows, a trailing dot is stripped early. + if cfg!(not(windows)) || suffix != &"/." { + check!(tmpdir.open_dir_nofollow(&name)); + } else { + assert!(tmpdir.open_dir_nofollow(&name).is_err()); + } + for dir_name in &["dir", "symlink_dir"] { + let name = format!("{}/../{}", dir_name, name); + check!(tmpdir.open_dir(&name)); + // On Windows, a trailing dot is stripped early. + if cfg!(not(windows)) || suffix != &"/." { + check!(tmpdir.open_dir_nofollow(&name)); + } else { + assert!(tmpdir.open_dir_nofollow(&name).is_err()); + } + } + } + } + + // Check those same paths, but with various suffixes appended. On + // Windows, these fail due to the symlink-to-path-ending-in-trailing-slash + // error. + for suffix in &["/", "/.", "/./"] { + for symlink_dir in &[ + "symlink_dir_slash", + "symlink_dir_slashdot", + "symlink_dir_slashdotdot", + "symlink_dir_slashdotdotslash", + "symlink_dotslash", + ] { + let name = format!("{}{}", symlink_dir, suffix); + #[cfg(windows)] + { + error!(tmpdir.open_dir(&name), 123); + assert!(tmpdir.open_dir_nofollow(&name).is_err()); + } + #[cfg(not(windows))] + { + check!(tmpdir.open_dir(&name)); + check!(tmpdir.open_dir_nofollow(&name)); + } + for dir_name in &["dir", "symlink_dir"] { + let name = format!("{}/../{}", dir_name, name); + #[cfg(windows)] + { + error!(tmpdir.open_dir(&name), 123); + assert!(tmpdir.open_dir_nofollow(&name).is_err()); + } + #[cfg(not(windows))] + { + check!(tmpdir.open_dir(&name)); + check!(tmpdir.open_dir_nofollow(&name)); + } + } + } + } + + // Check various ways of spelling `.`. + for cur_dir in &["dir/..", "dir/../", ".", "./"] { + check!(tmpdir.open_dir(cur_dir)); + check!(tmpdir.open_dir_nofollow(cur_dir)); + } +} + +/// This test is the same as `open_dir_nofollow` but uses ambient APIs instead +/// of `cap_std`. The purpose of this test is to confirm fundamentally +/// OS-specific behaviors. +#[test] +fn open_dir_nofollow_ambient() { + #[cfg(unix)] + use std::os::unix::fs::{symlink as symlink_file, symlink as symlink_dir}; + #[cfg(windows)] + use std::os::windows::fs::{symlink_dir, symlink_file}; + + if !symlink_supported() { + return; + } + + let dir = tempfile::tempdir().unwrap(); + + check!(std::fs::File::create(dir.path().join("file"))); + check!(std::fs::create_dir(dir.path().join("dir"))); + check!(symlink_file("file", dir.path().join("symlink_file"))); + check!(symlink_dir("dir", dir.path().join("symlink_dir"))); + check!(symlink_dir("dir/", dir.path().join("symlink_dir_slash"))); + check!(symlink_dir( + "dir/.", + dir.path().join("symlink_dir_slashdot") + )); + check!(symlink_dir( + "dir/..", + dir.path().join("symlink_dir_slashdotdot") + )); + check!(symlink_dir( + "dir/../", + dir.path().join("symlink_dir_slashdotdotslash") + )); + check!(symlink_dir("./", dir.path().join("symlink_dotslash"))); + check!(symlink_dir(".", dir.path().join("symlink_dot"))); + + assert!(Dir::open_ambient_dir(dir.path().join("file"), ambient_authority()).is_err()); + assert!(Dir::open_ambient_dir(dir.path().join("symlink_file"), ambient_authority()).is_err()); + check!(Dir::open_ambient_dir( + dir.path().join("symlink_dir"), + ambient_authority() + )); + #[cfg(windows)] + check!(Dir::open_ambient_dir( + dir.path().join("symlink_dir\\"), + ambient_authority() + )); + check!(Dir::open_ambient_dir( + dir.path().join("symlink_dir/"), + ambient_authority() + )); + #[cfg(windows)] + { + error!( + Dir::open_ambient_dir(dir.path().join("symlink_dir_slash"), ambient_authority()), + 123 + ); + error!( + Dir::open_ambient_dir( + dir.path().join("symlink_dir_slashdotdot"), + ambient_authority() + ), + 123 + ); + error!( + Dir::open_ambient_dir( + dir.path().join("symlink_dir_slashdotdotslash"), + ambient_authority() + ), + 123 + ); + error!( + Dir::open_ambient_dir(dir.path().join("symlink_dotslash"), ambient_authority()), + 123 + ); + error!( + Dir::open_ambient_dir(dir.path().join("symlink_dir_slashdot"), ambient_authority()), + 123 + ); + } + #[cfg(not(windows))] + { + check!(Dir::open_ambient_dir( + dir.path().join("symlink_dir_slash"), + ambient_authority() + )); + check!(Dir::open_ambient_dir( + dir.path().join("symlink_dir_slashdotdot"), + ambient_authority() + )); + check!(Dir::open_ambient_dir( + dir.path().join("symlink_dir_slashdotdotslash"), + ambient_authority() + )); + check!(Dir::open_ambient_dir( + dir.path().join("symlink_dotslash"), + ambient_authority() + )); + check!(Dir::open_ambient_dir( + dir.path().join("symlink_dir_slashdot"), + ambient_authority() + )); + } + check!(Dir::open_ambient_dir( + dir.path().join("symlink_dot"), + ambient_authority() + )); + check!(Dir::open_ambient_dir( + dir.path().join("dir"), + ambient_authority() + )); + + // Check various ways of spelling `dir/../symlink_dir`. + for dir_name in &["dir", "symlink_dir"] { + let name = format!("{}/../symlink_dir", dir_name); + check!(Dir::open_ambient_dir( + dir.path().join(&name), + ambient_authority() + )); + } + + // Check various paths which end with a symlink (even though the symlink + // expansion may end with `/` or a non-symlink). + for suffix in &[""] { + for symlink_dir in &["symlink_dot"] { + let name = format!("{}{}", symlink_dir, suffix); + check!(Dir::open_ambient_dir( + dir.path().join(&name), + ambient_authority() + )); + for dir_name in &["dir", "symlink_dir"] { + let name = format!("{}/../{}", dir_name, name); + check!(Dir::open_ambient_dir( + dir.path().join(&name), + ambient_authority() + )); + } + } + } + + // Check more paths which end with a symlink. On Windows, these fail due to + // the symlink-to-path-ending-in-trailing-slash error. + for suffix in &[""] { + for symlink_dir in &[ + "symlink_dir_slashdotdot", + "symlink_dir_slashdot", + "symlink_dir_slash", + "symlink_dir_slashdotdotslash", + "symlink_dotslash", + ] { + let name = format!("{}{}", symlink_dir, suffix); + #[cfg(windows)] + { + error!( + Dir::open_ambient_dir(dir.path().join(&name), ambient_authority()), + 123 + ); + } + #[cfg(not(windows))] + { + check!(Dir::open_ambient_dir( + dir.path().join(&name), + ambient_authority() + )); + } + for dir_name in &["dir", "symlink_dir"] { + let name = format!("{}/../{}", dir_name, name); + #[cfg(windows)] + { + error!( + Dir::open_ambient_dir(dir.path().join(&name), ambient_authority()), + 123 + ); + } + #[cfg(not(windows))] + { + check!(Dir::open_ambient_dir( + dir.path().join(&name), + ambient_authority() + )); + } + } + } + } + + // Check those same paths, but with various suffixes appended. + for suffix in &["/", "/.", "/./"] { + for symlink_dir in &["symlink_dir", "symlink_dot"] { + let name = format!("{}{}", symlink_dir, suffix); + check!(Dir::open_ambient_dir( + dir.path().join(&name), + ambient_authority() + )); + for dir_name in &["dir", "symlink_dir"] { + let name = format!("{}/../{}", dir_name, name); + check!(Dir::open_ambient_dir( + dir.path().join(&name), + ambient_authority() + )); + } + } + } + + // Check those same paths, but with various suffixes appended. On + // Windows, these fail due to the + // symlink-to-path-ending-in-trailing-slash error. + for suffix in &["/", "/.", "/./"] { + for symlink_dir in &[ + "symlink_dir_slash", + "symlink_dir_slashdot", + "symlink_dir_slashdotdot", + "symlink_dir_slashdotdotslash", + "symlink_dotslash", + ] { + let name = format!("{}{}", symlink_dir, suffix); + #[cfg(windows)] + { + error!( + Dir::open_ambient_dir(dir.path().join(&name), ambient_authority()), + 123 + ); + } + #[cfg(not(windows))] + { + check!(Dir::open_ambient_dir( + dir.path().join(&name), + ambient_authority() + )); + } + for dir_name in &["dir", "symlink_dir"] { + let name = format!("{}/../{}", dir_name, name); + #[cfg(windows)] + { + error!( + Dir::open_ambient_dir(dir.path().join(&name), ambient_authority()), + 123 + ); + } + #[cfg(not(windows))] + { + check!(Dir::open_ambient_dir( + dir.path().join(&name), + ambient_authority() + )); + } + } + } + } + + // Check various ways of spelling `.`. + for cur_dir in &["dir/..", "dir/../", ".", "./"] { + check!(Dir::open_ambient_dir( + dir.path().join(cur_dir), + ambient_authority() + )); + } +} diff --git a/crates/wasi/src/filesystem/primitives/tests/sys/mod.rs b/crates/wasi/src/filesystem/primitives/tests/sys/mod.rs new file mode 100644 index 000000000000..cc2c50f97c74 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/sys/mod.rs @@ -0,0 +1,7 @@ +#![allow(unused_imports)] + +#[cfg(unix)] +mod unix; + +#[cfg(unix)] +pub use self::unix::*; diff --git a/crates/wasi/src/filesystem/primitives/tests/sys/unix/mod.rs b/crates/wasi/src/filesystem/primitives/tests/sys/unix/mod.rs new file mode 100644 index 000000000000..b8df4bedd41e --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/sys/unix/mod.rs @@ -0,0 +1 @@ +pub mod weak; diff --git a/crates/wasi/src/filesystem/primitives/tests/sys/unix/weak.rs b/crates/wasi/src/filesystem/primitives/tests/sys/unix/weak.rs new file mode 100644 index 000000000000..ecc58eef72a0 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/sys/unix/weak.rs @@ -0,0 +1,238 @@ +//! This file is derived from Rust's library/std/src/sys/unix/weak.rs at +//! revision e4b1d5841494d6eb7f4944c91a057e16b0f0a9ea. +//! +//! Support for "weak linkage" to symbols on Unix +//! +//! Some I/O operations we do in libstd require newer versions of OSes but we +//! need to maintain binary compatibility with older releases for now. In order +//! to use the new functionality when available we use this module for +//! detection. +//! +//! One option to use here is weak linkage, but that is unfortunately only +//! really workable with ELF. Otherwise, use dlsym to get the symbol value at +//! runtime. This is also done for compatibility with older versions of glibc, +//! and to avoid creating dependencies on GLIBC_PRIVATE symbols. It assumes +//! that we've been dynamically linked to the library the symbol comes from, +//! but that is currently always the case for things like libpthread/libc. +//! +//! A long time ago this used weak linkage for the __pthread_get_minstack +//! symbol, but that caused Debian to detect an unnecessarily strict versioned +//! dependency on libc6 (#23628) because it is GLIBC_PRIVATE. We now use +//! `dlsym` for a runtime lookup of that symbol to avoid the ELF versioned +//! dependency. + +// There are a variety of `#[cfg]`s controlling which targets are involved in +// each instance of `weak!` and `syscall!`. Rather than trying to unify all of +// that, we'll just allow that some unix targets don't use this module at all. +#![allow(dead_code, unused_macros)] + +use std::ffi::CStr; +use std::marker::PhantomData; +use std::mem; +use std::sync::atomic::{self, AtomicUsize, Ordering}; + +// We can use true weak linkage on ELF targets. +#[macro_export] +#[cfg(not(any( + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "watchos", + target_os = "visionos", +)))] +macro_rules! weak { + (fn $name:ident($($t:ty),*) -> $ret:ty) => ( + let ref $name: ExternWeak $ret> = { + extern "C" { + #[linkage = "extern_weak"] + static $name: *const libc::c_void; + } + #[allow(unused_unsafe)] + ExternWeak::new(unsafe { $name }) + }; + ) +} + +// On non-ELF targets, use the dlsym approximation of weak linkage. +#[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "watchos", + target_os = "visionos", +))] +pub(crate) use crate::dlsym as weak; + +pub(crate) struct ExternWeak { + weak_ptr: *const libc::c_void, + _marker: PhantomData, +} + +impl ExternWeak { + #[inline] + pub(crate) fn new(weak_ptr: *const libc::c_void) -> Self { + ExternWeak { + weak_ptr, + _marker: PhantomData, + } + } +} + +impl ExternWeak { + #[inline] + pub(crate) fn get(&self) -> Option { + unsafe { + if self.weak_ptr.is_null() { + None + } else { + Some(mem::transmute_copy::<*const libc::c_void, F>( + &self.weak_ptr, + )) + } + } + } +} + +#[macro_export] +macro_rules! dlsym { + (fn $name:ident($($t:ty),*) -> $ret:ty) => ( + dlsym!(fn $name($($t),*) -> $ret, stringify!($name)); + ); + (fn $name:ident($($t:ty),*) -> $ret:ty, $sym:expr) => ( + static DLSYM: $crate::sys::weak::DlsymWeak $ret> = + $crate::sys::weak::DlsymWeak::new(concat!($sym, '\0')); + let $name = &DLSYM; + ) +} + +pub(crate) struct DlsymWeak { + name: &'static str, + addr: AtomicUsize, + _marker: PhantomData, +} + +impl DlsymWeak { + pub(crate) const fn new(name: &'static str) -> Self { + DlsymWeak { + name, + addr: AtomicUsize::new(1), + _marker: PhantomData, + } + } + + #[inline] + pub(crate) fn get(&self) -> Option { + unsafe { + // Relaxed is fine here because we fence before reading through the + // pointer (see the comment below). + match self.addr.load(Ordering::Relaxed) { + 1 => self.initialize(), + 0 => None, + addr => { + let func = mem::transmute_copy::(&addr); + // The caller is presumably going to read through this value + // (by calling the function we've dlsymed). This means we'd + // need to have loaded it with at least C11's consume + // ordering in order to be guaranteed that the data we read + // from the pointer isn't from before the pointer was + // stored. Rust has no equivalent to memory_order_consume, + // so we use an acquire fence (sorry, ARM). + // + // Now, in practice this likely isn't needed even on CPUs + // where relaxed and consume mean different things. The + // symbols we're loading are probably present (or not) at + // init, and even if they aren't the runtime dynamic loader + // is extremely likely have sufficient barriers internally + // (possibly implicitly, for example the ones provided by + // invoking `mprotect`). + // + // That said, none of that's *guaranteed*, and so we fence. + atomic::fence(Ordering::Acquire); + Some(func) + } + } + } + } + + // Cold because it should only happen during first-time initialization. + #[cold] + unsafe fn initialize(&self) -> Option { + assert_eq!(mem::size_of::(), mem::size_of::()); + + let val = fetch(self.name); + // This synchronizes with the acquire fence in `get`. + self.addr.store(val, Ordering::Release); + + match val { + 0 => None, + addr => Some(mem::transmute_copy::(&addr)), + } + } +} + +unsafe fn fetch(name: &str) -> usize { + let name = match CStr::from_bytes_with_nul(name.as_bytes()) { + Ok(cstr) => cstr, + Err(..) => return 0, + }; + libc::dlsym(libc::RTLD_DEFAULT, name.as_ptr()) as usize +} + +#[macro_export] +#[cfg(not(any(target_os = "linux", target_os = "android")))] +macro_rules! syscall { + (fn $name:ident($($arg_name:ident: $t:ty),*) -> $ret:ty) => ( + unsafe fn $name($($arg_name: $t),*) -> $ret { + weak! { fn $name($($t),*) -> $ret } + + if let Some(fun) = $name.get() { + fun($($arg_name),*) + } else { + super::os::set_errno(libc::ENOSYS); + -1 + } + } + ) +} + +#[macro_export] +#[cfg(any(target_os = "linux", target_os = "android"))] +macro_rules! syscall { + (fn $name:ident($($arg_name:ident: $t:ty),*) -> $ret:ty) => ( + unsafe fn $name($($arg_name:$t),*) -> $ret { + weak! { fn $name($($t),*) -> $ret } + + // Use a weak symbol from libc when possible, allowing `LD_PRELOAD` + // interposition, but if it's not found just use a raw syscall. + if let Some(fun) = $name.get() { + fun($($arg_name),*) + } else { + // This looks like a hack, but concat_idents only accepts idents + // (not paths). + use libc::*; + + syscall( + concat_idents!(SYS_, $name), + $($arg_name),* + ) as $ret + } + } + ) +} + +#[macro_export] +#[cfg(any(target_os = "linux", target_os = "android"))] +macro_rules! raw_syscall { + (fn $name:ident($($arg_name:ident: $t:ty),*) -> $ret:ty) => ( + unsafe fn $name($($arg_name:$t),*) -> $ret { + // This looks like a hack, but concat_idents only accepts idents + // (not paths). + use libc::*; + + syscall( + concat_idents!(SYS_, $name), + $($arg_name),* + ) as $ret + } + ) +} diff --git a/crates/wasi/src/filesystem/primitives/tests/sys_common/io.rs b/crates/wasi/src/filesystem/primitives/tests/sys_common/io.rs new file mode 100644 index 000000000000..e41ec157f7e1 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/sys_common/io.rs @@ -0,0 +1,27 @@ +use cap_tempfile::tempdir; + +#[cfg(feature = "fs_utf8")] +pub use cap_tempfile::utf8::TempDir as TempDirUtf8; +pub use cap_tempfile::TempDir; + +#[allow(unused)] +pub fn tmpdir() -> TempDir { + use cap_tempfile::ambient_authority; + + // It's ok to call `ambient_authority()` here, rather than take an + // `AmbientAuthority` argument, because this function is only used + // by tests. + tempdir(ambient_authority()).expect("expected to be able to create a temporary directory") +} + +#[cfg(feature = "fs_utf8")] +#[allow(unused)] +pub fn tmpdir_utf8() -> TempDirUtf8 { + use cap_tempfile::ambient_authority; + use cap_tempfile::utf8::tempdir; + + // It's ok to call `ambient_authority()` here, rather than take an + // `AmbientAuthority` argument, because this function is only used + // by tests. + tempdir(ambient_authority()).expect("expected to be able to create a temporary directory") +} diff --git a/crates/wasi/src/filesystem/primitives/tests/sys_common/mod.rs b/crates/wasi/src/filesystem/primitives/tests/sys_common/mod.rs new file mode 100644 index 000000000000..d13e417d64e4 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/sys_common/mod.rs @@ -0,0 +1,96 @@ +#![allow(unused_imports)] + +mod symlink_junction; + +pub mod io; + +pub use symlink_junction::*; + +#[allow(unused)] +macro_rules! check { + ($e:expr) => { + match $e { + Ok(t) => t, + Err(e) => panic!("{} failed with: {}", stringify!($e), e), + } + }; +} + +#[cfg(windows)] +#[allow(unused)] +macro_rules! error { + ($e:expr, $s:expr) => { + match $e { + Ok(_) => panic!("Unexpected success. Should've been: {:?}", $s), + Err(ref err) => { + assert!( + err.raw_os_error() == Some($s), + "`{}` did not have a code of `{}`", + err, + $s + ) + } + } + }; +} + +#[cfg(any(unix, target_os = "wasi"))] +#[allow(unused)] +macro_rules! error { + ($e:expr, $s:expr) => { + error_contains!($e, $s) + }; +} + +#[allow(unused)] +macro_rules! error_contains { + ($e:expr, $s:expr) => { + match $e { + Ok(_) => panic!("Unexpected success. Should've been: {:?}", $s), + Err(ref err) => { + assert!( + err.to_string().contains($s), + "`{}` did not contain `{}`", + err, + $s + ) + } + } + }; +} + +// The following is derived from Rust's +// src/tools/cargo/crates/cargo-test-support/src/lib.rs at revision +// a78a62fc996ba16f7a111c99520b23f77029f4eb. + +#[cfg(windows)] +#[allow(dead_code)] +pub fn symlink_supported() -> bool { + let dir = tempfile::tempdir().unwrap(); + + let src = dir.path().join("symlink_src"); + std::fs::write(&src, "").unwrap(); + let dst = dir.path().join("symlink_dst"); + let result = match std::os::windows::fs::symlink_file(&src, &dst) { + Ok(_) => { + std::fs::remove_file(&dst).unwrap(); + true + } + Err(e) => { + eprintln!( + "symlinks not supported: {:?}\n\ + Windows 10 users should enable developer mode.", + e + ); + false + } + }; + std::fs::remove_file(&src).unwrap(); + return result; +} + +#[cfg(not(windows))] +#[allow(dead_code)] +pub fn symlink_supported() -> bool { + true +} diff --git a/crates/wasi/src/filesystem/primitives/tests/sys_common/symlink_junction.rs b/crates/wasi/src/filesystem/primitives/tests/sys_common/symlink_junction.rs new file mode 100644 index 000000000000..31da73bcf738 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/sys_common/symlink_junction.rs @@ -0,0 +1,251 @@ +// Implementation derived from `symlink_junction` and related code in Rust's +// library/std/src/sys/windows/fs.rs at revision +// 3ffb27ff89db780e88abe829783565a7122be1c5. + +#[cfg(feature = "fs_utf8")] +use camino::Utf8Path; +use cap_std::fs::Dir; +use std::io; +use std::path::Path; + +#[cfg(not(windows))] +#[allow(dead_code)] +pub fn symlink_junction, Q: AsRef>( + src: P, + dst_dir: &Dir, + dst: Q, +) -> io::Result<()> { + dst_dir.symlink(src, dst) +} + +#[cfg(windows)] +#[allow(dead_code)] +pub fn symlink_junction, Q: AsRef>( + src: P, + dst_dir: &Dir, + dst: Q, +) -> io::Result<()> { + symlink_junction_inner(src.as_ref(), dst_dir, dst.as_ref()) +} + +#[cfg(feature = "fs_utf8")] +#[cfg(not(windows))] +#[allow(dead_code)] +pub fn symlink_junction_utf8, Q: AsRef>( + src: P, + dst_dir: &cap_std::fs_utf8::Dir, + dst: Q, +) -> io::Result<()> { + dst_dir.symlink(src, dst) +} + +#[cfg(feature = "fs_utf8")] +#[cfg(windows)] +#[allow(dead_code)] +pub fn symlink_junction_utf8, Q: AsRef>( + src: P, + dst_dir: &cap_std::fs_utf8::Dir, + dst: Q, +) -> io::Result<()> { + symlink_junction_inner_utf8(src.as_ref(), dst_dir, dst.as_ref()) +} + +/// Align the inner value to 8 bytes. +/// +/// This is enough for almost all of the buffers we're likely to work with in +/// the Windows APIs we use. +#[cfg(windows)] +#[repr(C, align(8))] +#[derive(Copy, Clone)] +struct Align8(pub T); + +#[cfg(windows)] +#[allow(dead_code)] +#[allow(non_snake_case)] +#[repr(C)] +pub struct REPARSE_MOUNTPOINT_DATA_BUFFER { + pub ReparseTag: u32, + pub ReparseDataLength: u32, + pub Reserved: u16, + pub ReparseTargetLength: u16, + pub ReparseTargetMaximumLength: u16, + pub Reserved1: u16, + pub ReparseTarget: libc::wchar_t, +} + +#[cfg(windows)] +#[allow(dead_code)] +pub fn cvt(i: windows_sys::core::BOOL) -> io::Result { + if i == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(i) + } +} + +// Creating a directory junction on windows involves dealing with reparse +// points and the `DeviceIoControl` function, and this code is a skeleton of +// what can be found here: +// +// http://www.flexhex.com/docs/articles/hard-links.phtml +#[cfg(windows)] +#[allow(dead_code)] +fn symlink_junction_inner(original: &Path, dir: &Dir, junction: &Path) -> io::Result<()> { + use cap_std::fs::{OpenOptions, OpenOptionsExt}; + use std::mem::MaybeUninit; + use std::os::windows::ffi::OsStrExt; + use std::os::windows::io::AsRawHandle; + use std::{mem, ptr}; + use windows_sys::Win32::Storage::FileSystem::MAXIMUM_REPARSE_DATA_BUFFER_SIZE; + + dir.create_dir(junction)?; + + let mut opts = OpenOptions::new(); + opts.write(true); + opts.custom_flags( + windows_sys::Win32::Storage::FileSystem::FILE_FLAG_OPEN_REPARSE_POINT + | windows_sys::Win32::Storage::FileSystem::FILE_FLAG_BACKUP_SEMANTICS, + ); + let f = dir.open_with(junction, &opts)?; + let h = f.as_raw_handle(); + unsafe { + let mut data = + Align8([MaybeUninit::::uninit(); MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize]); + let data_ptr = data.0.as_mut_ptr(); + let data_end = data_ptr.add(MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize); + let db = data_ptr.cast::(); + // Zero the header to ensure it's fully initialized, including reserved parameters. + *db = mem::zeroed(); + let reparse_target_slice = { + let buf_start = ptr::addr_of_mut!((*db).ReparseTarget).cast::(); + // Compute offset in bytes and then divide so that we round down + // rather than hit any UB (admittedly this arithmetic should work + // out so that this isn't necessary) + // TODO: use `byte_offfset_from` when pointer_byte_offsets is stable + // let buf_len_bytes = usize::try_from(data_end.byte_offset_from(buf_start)).unwrap(); + let buf_len_bytes = + usize::try_from(data_end.cast::().offset_from(buf_start.cast::())).unwrap(); + let buf_len_wchars = buf_len_bytes / core::mem::size_of::(); + core::slice::from_raw_parts_mut(buf_start, buf_len_wchars) + }; + // FIXME: this conversion is very hacky + let iter = br"\??\" + .iter() + .map(|x| *x as u16) + .chain(original.as_os_str().encode_wide()) + .chain(core::iter::once(0)); + let mut i = 0; + for c in iter { + if i >= reparse_target_slice.len() { + return Err(io::Error::new( + // TODO: use io::ErrorKind::InvalidFilename when io_error_more is stabilized + io::ErrorKind::Other, + "Input filename is too long", + )); + } + reparse_target_slice[i] = c; + i += 1; + } + (*db).ReparseTag = windows_sys::Win32::System::SystemServices::IO_REPARSE_TAG_MOUNT_POINT; + (*db).ReparseTargetMaximumLength = (i * 2) as u16; + (*db).ReparseTargetLength = ((i - 1) * 2) as u16; + (*db).ReparseDataLength = (*db).ReparseTargetLength as u32 + 12; + + let mut ret = 0; + cvt(windows_sys::Win32::System::IO::DeviceIoControl( + h as _, + windows_sys::Win32::System::Ioctl::FSCTL_SET_REPARSE_POINT, + data_ptr.cast(), + (*db).ReparseDataLength + 8, + ptr::null_mut(), + 0, + &mut ret, + ptr::null_mut(), + )) + .map(drop) + } +} + +/// Same as above, but for fs_utf8. +#[cfg(feature = "fs_utf8")] +#[cfg(windows)] +#[allow(dead_code)] +fn symlink_junction_inner_utf8( + original: &Utf8Path, + dir: &cap_std::fs_utf8::Dir, + junction: &Utf8Path, +) -> io::Result<()> { + use cap_std::fs::{OpenOptions, OpenOptionsExt}; + use std::mem::MaybeUninit; + use std::os::windows::ffi::OsStrExt; + use std::os::windows::io::AsRawHandle; + use std::{mem, ptr}; + use windows_sys::Win32::Storage::FileSystem::MAXIMUM_REPARSE_DATA_BUFFER_SIZE; + + dir.create_dir(junction)?; + + let mut opts = OpenOptions::new(); + opts.write(true); + opts.custom_flags( + windows_sys::Win32::Storage::FileSystem::FILE_FLAG_OPEN_REPARSE_POINT + | windows_sys::Win32::Storage::FileSystem::FILE_FLAG_BACKUP_SEMANTICS, + ); + let f = dir.open_with(junction, &opts)?; + let h = f.as_raw_handle(); + unsafe { + let mut data = + Align8([MaybeUninit::::uninit(); MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize]); + let data_ptr = data.0.as_mut_ptr(); + let data_end = data_ptr.add(MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize); + let db = data_ptr.cast::(); + // Zero the header to ensure it's fully initialized, including reserved parameters. + *db = mem::zeroed(); + let reparse_target_slice = { + let buf_start = ptr::addr_of_mut!((*db).ReparseTarget).cast::(); + // Compute offset in bytes and then divide so that we round down + // rather than hit any UB (admittedly this arithmetic should work + // out so that this isn't necessary) + // TODO: use `byte_offfset_from` when pointer_byte_offsets is stable + // let buf_len_bytes = usize::try_from(data_end.byte_offset_from(buf_start)).unwrap(); + let buf_len_bytes = + usize::try_from(data_end.cast::().offset_from(buf_start.cast::())).unwrap(); + let buf_len_wchars = buf_len_bytes / core::mem::size_of::(); + core::slice::from_raw_parts_mut(buf_start, buf_len_wchars) + }; + // FIXME: this conversion is very hacky + let iter = br"\??\" + .iter() + .map(|x| *x as u16) + .chain(original.as_os_str().encode_wide()) + .chain(core::iter::once(0)); + let mut i = 0; + for c in iter { + if i >= reparse_target_slice.len() { + return Err(io::Error::new( + // TODO: use io::ErrorKind::InvalidFilename when io_error_more is stabilized + io::ErrorKind::Other, + "Input filename is too long", + )); + } + reparse_target_slice[i] = c; + i += 1; + } + (*db).ReparseTag = windows_sys::Win32::System::SystemServices::IO_REPARSE_TAG_MOUNT_POINT; + (*db).ReparseTargetMaximumLength = (i * 2) as u16; + (*db).ReparseTargetLength = ((i - 1) * 2) as u16; + (*db).ReparseDataLength = (*db).ReparseTargetLength as u32 + 12; + + let mut ret = 0; + cvt(windows_sys::Win32::System::IO::DeviceIoControl( + h as _, + windows_sys::Win32::System::Ioctl::FSCTL_SET_REPARSE_POINT, + data_ptr.cast(), + (*db).ReparseDataLength + 8, + ptr::null_mut(), + 0, + &mut ret, + ptr::null_mut(), + )) + .map(drop) + } +} diff --git a/crates/wasi/src/filesystem/primitives/tests/time.rs b/crates/wasi/src/filesystem/primitives/tests/time.rs new file mode 100644 index 000000000000..efc032817dd7 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/time.rs @@ -0,0 +1,200 @@ +// This file is derived from Rust's library/std/src/time/tests.rs at revision +// 108e90ca78f052c0c1c49c42a22c85620be19712. + +use cap_std::ambient_authority; +use cap_std::time::{Duration, MonotonicClock, SystemClock}; + +macro_rules! assert_almost_eq { + ($a:expr, $b:expr) => {{ + let (a, b) = ($a, $b); + if a != b { + let (a, b) = if a > b { (a, b) } else { (b, a) }; + assert!( + a - Duration::new(0, 1000) <= b, + "{:?} is not almost equal to {:?}", + a, + b + ); + } + }}; +} + +#[test] +fn instant_monotonic() { + let clock = MonotonicClock::new(ambient_authority()); + let a = clock.now(); + let b = clock.now(); + assert!(b >= a); +} + +#[test] +fn instant_elapsed() { + let clock = MonotonicClock::new(ambient_authority()); + let a = clock.now(); + clock.elapsed(a); +} + +#[test] +fn instant_math() { + let clock = MonotonicClock::new(ambient_authority()); + let a = clock.now(); + let b = clock.now(); + println!("a: {:?}", a); + println!("b: {:?}", b); + let dur = b.duration_since(a); + println!("dur: {:?}", dur); + assert_almost_eq!(b - dur, a); + assert_almost_eq!(a + dur, b); + + let second = Duration::new(1, 0); + assert_almost_eq!(a - second + second, a); + assert_almost_eq!( + a.checked_sub(second).unwrap().checked_add(second).unwrap(), + a + ); + + // checked_add_duration will not panic on overflow + let mut maybe_t = Some(clock.now()); + let max_duration = Duration::from_secs(u64::MAX); + // in case `Instant` can store `>= now + max_duration`. + for _ in 0..2 { + maybe_t = maybe_t.and_then(|t| t.checked_add(max_duration)); + } + assert_eq!(maybe_t, None); + + // checked_add_duration calculates the right time and will work for another + // year + let year = Duration::from_secs(60 * 60 * 24 * 365); + assert_eq!(a + year, a.checked_add(year).unwrap()); +} + +#[test] +fn instant_math_is_associative() { + let clock = MonotonicClock::new(ambient_authority()); + let now = clock.now(); + let offset = Duration::from_millis(5); + // Changing the order of instant math shouldn't change the results, + // especially when the expression reduces to X + identity. + assert_eq!((now + offset) - now, (now - now) + offset); +} + +// Ignore this test for now. As of rust-lang/rust#89926, Rust no longer panics +// on this test. +// +// TODO: Once our MSRV no longer contains the old behavior, we should update +// to the latest version of this test. +#[test] +#[should_panic] +#[ignore] +fn instant_duration_since_panic() { + let clock = MonotonicClock::new(ambient_authority()); + let a = clock.now(); + (a - Duration::new(1, 0)).duration_since(a); +} + +#[test] +fn instant_checked_duration_since_nopanic() { + let clock = MonotonicClock::new(ambient_authority()); + let now = clock.now(); + let earlier = now - Duration::new(1, 0); + let later = now + Duration::new(1, 0); + assert_eq!(earlier.checked_duration_since(now), None); + assert_eq!(later.checked_duration_since(now), Some(Duration::new(1, 0))); + assert_eq!(now.checked_duration_since(now), Some(Duration::new(0, 0))); +} + +#[test] +fn instant_saturating_duration_since_nopanic() { + let clock = MonotonicClock::new(ambient_authority()); + let a = clock.now(); + let ret = (a - Duration::new(1, 0)).saturating_duration_since(a); + assert_eq!(ret, Duration::new(0, 0)); +} + +#[test] +fn system_time_math() { + let clock = SystemClock::new(ambient_authority()); + let a = clock.now(); + let b = clock.now(); + match b.duration_since(a) { + Ok(dur) if dur == Duration::new(0, 0) => { + assert_almost_eq!(a, b); + } + Ok(dur) => { + assert!(b > a); + assert_almost_eq!(b - dur, a); + assert_almost_eq!(a + dur, b); + } + Err(dur) => { + let dur = dur.duration(); + assert!(a > b); + assert_almost_eq!(b + dur, a); + assert_almost_eq!(a - dur, b); + } + } + + let second = Duration::new(1, 0); + assert_almost_eq!(a.duration_since(a - second).unwrap(), second); + assert_almost_eq!(a.duration_since(a + second).unwrap_err().duration(), second); + + assert_almost_eq!(a - second + second, a); + assert_almost_eq!( + a.checked_sub(second).unwrap().checked_add(second).unwrap(), + a + ); + + let one_second_from_epoch = SystemClock::UNIX_EPOCH + Duration::new(1, 0); + let one_second_from_epoch2 = + SystemClock::UNIX_EPOCH + Duration::new(0, 500_000_000) + Duration::new(0, 500_000_000); + assert_eq!(one_second_from_epoch, one_second_from_epoch2); + + // checked_add_duration will not panic on overflow + let mut maybe_t = Some(SystemClock::UNIX_EPOCH); + let max_duration = Duration::from_secs(u64::MAX); + // in case `SystemTime` can store `>= UNIX_EPOCH + max_duration`. + for _ in 0..2 { + maybe_t = maybe_t.and_then(|t| t.checked_add(max_duration)); + } + assert_eq!(maybe_t, None); + + // checked_add_duration calculates the right time and will work for another + // year + let year = Duration::from_secs(60 * 60 * 24 * 365); + assert_eq!(a + year, a.checked_add(year).unwrap()); +} + +#[test] +fn system_time_elapsed() { + let clock = SystemClock::new(ambient_authority()); + let a = clock.now(); + drop(clock.elapsed(a)); +} + +#[test] +fn since_epoch() { + let clock = SystemClock::new(ambient_authority()); + let ts = clock.now(); + let a = ts + .duration_since(SystemClock::UNIX_EPOCH + Duration::new(1, 0)) + .unwrap(); + let b = ts.duration_since(SystemClock::UNIX_EPOCH).unwrap(); + assert!(b > a); + assert_eq!(b - a, Duration::new(1, 0)); + + let thirty_years = Duration::new(1, 0) * 60 * 60 * 24 * 365 * 30; + + // Right now for CI this test is run in an emulator, and apparently the + // aarch64 emulator's sense of time is that we're still living in the + // 70s. This is also true for riscv (also qemu) + // + // Otherwise let's assume that we're all running computers later than + // 2000. + if !cfg!(target_arch = "aarch64") && !cfg!(target_arch = "riscv64") { + assert!(a > thirty_years); + } + + // let's assume that we're all running computers earlier than 2090. + // Should give us ~70 years to fix this! + let hundred_twenty_years = thirty_years * 4; + assert!(a < hundred_twenty_years); +} diff --git a/crates/wasi/src/filesystem/primitives/tests/windows_open.rs b/crates/wasi/src/filesystem/primitives/tests/windows_open.rs new file mode 100644 index 000000000000..a6c8b2bbb616 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/windows_open.rs @@ -0,0 +1,176 @@ +//! On Windows, cap-std uses the technique of looking up absolute paths for +//! directory handles. This would be racy, except that cap-std uses Windows' +//! sharing modes to prevent open directories from being removed or renamed. +//! Test that this works. + +#[cfg(windows)] +#[macro_use] +mod sys_common; + +#[cfg(windows)] +use sys_common::io::tmpdir; + +#[test] +#[cfg(windows)] +fn windows_open_one() { + let tmpdir = tmpdir(); + check!(tmpdir.create_dir("aaa")); + + let dir = check!(tmpdir.open_dir("aaa")); + + // Attempts to remove or rename the open directory should fail. + tmpdir.remove_dir("aaa").unwrap_err(); + tmpdir.rename("aaa", &tmpdir, "zzz").unwrap_err(); + + drop(dir); + + // Now that we've dropped the handle, the same operations should succeed. + check!(tmpdir.rename("aaa", &tmpdir, "xxx")); + check!(tmpdir.remove_dir("xxx")); +} + +#[test] +#[cfg(windows)] +fn windows_open_multiple() { + let tmpdir = tmpdir(); + check!(tmpdir.create_dir_all("aaa/bbb")); + + let dir = check!(tmpdir.open_dir("aaa/bbb")); + + // Attempts to remove or rename any component of the open directory should + // fail. + tmpdir.remove_dir("aaa/bbb").unwrap_err(); + tmpdir.remove_dir("aaa").unwrap_err(); + tmpdir.rename("aaa/bbb", &tmpdir, "aaa/yyy").unwrap_err(); + tmpdir.rename("aaa", &tmpdir, "zzz").unwrap_err(); + + drop(dir); + + // Now that we've dropped the handle, the same operations should succeed. + check!(tmpdir.rename("aaa/bbb", &tmpdir, "aaa/www")); + check!(tmpdir.rename("aaa", &tmpdir, "xxx")); + check!(tmpdir.remove_dir("xxx/www")); + check!(tmpdir.remove_dir("xxx")); +} + +/// Like `windows_open_multiple`, but does so within a directory that we +/// can close and then independently mutate. +#[test] +#[cfg(windows)] +fn windows_open_tricky() { + let tmpdir = tmpdir(); + check!(tmpdir.create_dir("qqq")); + + let qqq = check!(tmpdir.open_dir("qqq")); + check!(qqq.create_dir_all("aaa/bbb")); + + let dir = check!(qqq.open_dir("aaa/bbb")); + + // Now drop `qqq`. + drop(qqq); + + // Attempts to remove or rename any component of the open directory should + // fail. + dir.remove_dir("aaa/bbb").unwrap_err(); + dir.remove_dir("aaa").unwrap_err(); + dir.rename("aaa/bbb", &tmpdir, "aaa/yyy").unwrap_err(); + dir.rename("aaa", &tmpdir, "zzz").unwrap_err(); + tmpdir.remove_dir("qqq/aaa/bbb").unwrap_err(); + tmpdir.remove_dir("qqq/aaa").unwrap_err(); + tmpdir.remove_dir("qqq").unwrap_err(); + tmpdir + .rename("qqq/aaa/bbb", &tmpdir, "qqq/aaa/yyy") + .unwrap_err(); + tmpdir.rename("qqq/aaa", &tmpdir, "qqq/zzz").unwrap_err(); + tmpdir.rename("qqq", &tmpdir, "vvv").unwrap_err(); + + drop(dir); + + // Now that we've dropped the handle, the same operations should succeed. + check!(tmpdir.rename("qqq/aaa/bbb", &tmpdir, "qqq/aaa/www")); + check!(tmpdir.rename("qqq/aaa", &tmpdir, "qqq/xxx")); + check!(tmpdir.rename("qqq", &tmpdir, "uuu")); + check!(tmpdir.remove_dir("uuu/xxx/www")); + check!(tmpdir.remove_dir("uuu/xxx")); + check!(tmpdir.remove_dir("uuu")); +} + +/// Like `windows_open_one` but uses `open_ambient_dir` instead of `open_dir`. +#[test] +#[cfg(windows)] +fn windows_open_ambient() { + use cap_std::ambient_authority; + use cap_std::fs::Dir; + + let ambient_dir = tempfile::tempdir().unwrap(); + + let tmpdir = check!(Dir::open_ambient_dir( + ambient_dir.path(), + ambient_authority() + )); + check!(tmpdir.create_dir("aaa")); + + let dir = check!(Dir::open_ambient_dir( + ambient_dir.path().join("aaa"), + ambient_authority() + )); + + // Attempts to remove or rename the open directory should fail. + tmpdir.remove_dir("aaa").unwrap_err(); + tmpdir.rename("aaa", &tmpdir, "zzz").unwrap_err(); + + drop(dir); + + // Now that we've dropped the handle, the same operations should succeed. + check!(tmpdir.rename("aaa", &tmpdir, "xxx")); + check!(tmpdir.remove_dir("xxx")); +} + +#[test] +#[cfg(windows)] +fn windows_open_special() { + let tmpdir = tmpdir(); + + // Opening any of these should fail. + for device in &[ + "CON", "PRN", "AUX", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", + "COM8", "COM9", "COM¹", "COM²", "COM³", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", + "LPT6", "LPT7", "LPT8", "LPT9", "LPT¹", "LPT²", "LPT³", + ] { + for suffix in &[ + "", + " ", + ".", + ". ", + ".ext", + ".ext.", + ".ext. ", + ".ext ", + ".ext.more", + ".ext.more.", + ".ext.more ", + ".ext.more. ", + ".ext.more .", + ] { + let name = format!("{}{}", device, suffix); + eprintln!("testing '{}'", name); + + match tmpdir.open(&name).unwrap_err().kind() { + std::io::ErrorKind::NotFound | std::io::ErrorKind::PermissionDenied => {} + kind => panic!("unexpected error: {:?}", kind), + } + + let mut options = cap_std::fs::OpenOptions::new(); + options.write(true); + match tmpdir.open_with(&name, &options).unwrap_err().kind() { + std::io::ErrorKind::NotFound | std::io::ErrorKind::PermissionDenied => {} + kind => panic!("unexpected error: {:?}", kind), + } + + match tmpdir.create(&name).unwrap_err().kind() { + std::io::ErrorKind::NotFound | std::io::ErrorKind::PermissionDenied => {} + kind => panic!("unexpected error: {:?}", kind), + } + } + } +} diff --git a/crates/wasi/src/filesystem/primitives/tests/windows_symlinks.rs b/crates/wasi/src/filesystem/primitives/tests/windows_symlinks.rs new file mode 100644 index 000000000000..cc8b425f2b38 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/windows_symlinks.rs @@ -0,0 +1,93 @@ +#![cfg(windows)] + +#[macro_use] +mod sys_common; + +use sys_common::io::tmpdir; +use sys_common::symlink_supported; + +#[test] +fn windows_symlinks() { + if !symlink_supported() { + return; + } + + let tmpdir = tmpdir(); + + check!(tmpdir.create("file")); + check!(tmpdir.create_dir("dir")); + + // Windows lets these succeed. + check!(tmpdir.symlink_dir("file", "file_symlink_dir")); + check!(tmpdir.symlink_file("dir", "dir_symlink_file")); + + // But accessing them fails. + assert!(tmpdir.open("dir_symlink_file").is_err()); + assert!(tmpdir.open("file_symlink_dir").is_err()); + assert!(tmpdir.open_dir("dir_symlink_file").is_err()); + assert!(tmpdir.open_dir("file_symlink_dir").is_err()); + assert!(tmpdir.metadata("dir_symlink_file").is_err()); + assert!(tmpdir.metadata("file_symlink_dir").is_err()); + + assert!(check!(tmpdir.symlink_metadata("file_symlink_dir")) + .file_type() + .is_symlink()); + assert!(check!(tmpdir.symlink_metadata("dir_symlink_file")) + .file_type() + .is_symlink()); +} + +#[test] +fn windows_symlinks_ambient() { + use cap_std::ambient_authority; + use cap_std::fs::Dir; + use std::fs; + use std::os::windows::fs::{symlink_dir, symlink_file}; + + if !symlink_supported() { + return; + } + + let dir = tempfile::tempdir().unwrap(); + + check!(fs::File::create(dir.path().join("file"))); + check!(fs::create_dir(dir.path().join("dir"))); + + // Windows lets these succeed. + check!(symlink_dir("file", dir.path().join("file_symlink_dir"))); + check!(symlink_file("dir", dir.path().join("dir_symlink_file"))); + + // But accessing them fails. + assert!(fs::File::open(dir.path().join("dir_symlink_file")).is_err()); + assert!(fs::File::open(dir.path().join("file_symlink_dir")).is_err()); + + // Windows 2016 doesn't issue errors here, so disable these tests. According to + // + // + // the build numbers for Windows 2016 are up to 17134. Also it appears we need + // to apply a mask to the version `nt_version` gives us. This is guesswork. + let (_maj, _min, build) = nt_version::get(); + if (build & 0xffff) > 17134 { + assert!( + Dir::open_ambient_dir(dir.path().join("dir_symlink_file"), ambient_authority()) + .is_err() + ); + assert!( + Dir::open_ambient_dir(dir.path().join("file_symlink_dir"), ambient_authority()) + .is_err() + ); + assert!(fs::metadata(dir.path().join("dir_symlink_file")).is_err()); + assert!(fs::metadata(dir.path().join("file_symlink_dir")).is_err()); + } + + assert!( + check!(fs::symlink_metadata(dir.path().join("file_symlink_dir"))) + .file_type() + .is_symlink() + ); + assert!( + check!(fs::symlink_metadata(dir.path().join("dir_symlink_file"))) + .file_type() + .is_symlink() + ); +} From e7e01c7cd861d15ba40880202f143bfdb5554173 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 27 Aug 2026 12:46:24 -0700 Subject: [PATCH 13/34] Remove some tests we don't need --- .../primitives/tests/cap_net_ext_tcp.rs | 1180 ------------ .../primitives/tests/cap_net_ext_tcp_split.rs | 1372 ------------- .../primitives/tests/cap_net_ext_udp.rs | 474 ----- .../primitives/tests/cap_net_ext_udp_split.rs | 549 ------ .../filesystem/primitives/tests/fs_utf8.rs | 1704 ----------------- .../src/filesystem/primitives/tests/mod.rs | 9 - .../src/filesystem/primitives/tests/net.rs | 76 - .../filesystem/primitives/tests/net_tcp.rs | 1050 ---------- .../filesystem/primitives/tests/net_udp.rs | 451 ----- .../src/filesystem/primitives/tests/time.rs | 200 -- 10 files changed, 7065 deletions(-) delete mode 100644 crates/wasi/src/filesystem/primitives/tests/cap_net_ext_tcp.rs delete mode 100644 crates/wasi/src/filesystem/primitives/tests/cap_net_ext_tcp_split.rs delete mode 100644 crates/wasi/src/filesystem/primitives/tests/cap_net_ext_udp.rs delete mode 100644 crates/wasi/src/filesystem/primitives/tests/cap_net_ext_udp_split.rs delete mode 100644 crates/wasi/src/filesystem/primitives/tests/fs_utf8.rs delete mode 100644 crates/wasi/src/filesystem/primitives/tests/net.rs delete mode 100644 crates/wasi/src/filesystem/primitives/tests/net_tcp.rs delete mode 100644 crates/wasi/src/filesystem/primitives/tests/net_udp.rs delete mode 100644 crates/wasi/src/filesystem/primitives/tests/time.rs diff --git a/crates/wasi/src/filesystem/primitives/tests/cap_net_ext_tcp.rs b/crates/wasi/src/filesystem/primitives/tests/cap_net_ext_tcp.rs deleted file mode 100644 index b7ddd5a769db..000000000000 --- a/crates/wasi/src/filesystem/primitives/tests/cap_net_ext_tcp.rs +++ /dev/null @@ -1,1180 +0,0 @@ -// This file is derived from Rust's library/std/src/net/tcp/tests.rs at -// revision 377d1a984cd2a53327092b90aa1d8b7e22d1e347. - -mod net; - -use cap_net_ext::{AddressFamily, Blocking, PoolExt, TcpListenerExt}; -use cap_std::ambient_authority; -use cap_std::net::*; -use net::{next_test_ip4, next_test_ip6}; -use std::io::prelude::*; -use std::io::{ErrorKind, IoSlice, IoSliceMut}; -use std::sync::mpsc::channel; -use std::time::{Duration, Instant}; -use std::{fmt, thread}; - -fn each_ip(f: &mut dyn FnMut(SocketAddr)) { - f(next_test_ip4()); - f(next_test_ip6()); -} - -macro_rules! t { - ($e:expr) => { - match $e { - Ok(t) => t, - Err(e) => panic!("received error for `{}`: {}", stringify!($e), e), - } - }; -} - -#[test] -fn bind_error() { - let mut pool = Pool::new(); - pool.insert_socket_addr("1.1.1.1:9999".parse().unwrap(), ambient_authority()); - - let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - match pool.bind_existing_tcp_listener(&listener, "1.1.1.1:9999") { - Ok(..) => panic!(), - Err(e) => assert_eq!(e.kind(), ErrorKind::AddrNotAvailable), - } -} - -#[test] -fn connect_error() { - let mut pool = Pool::new(); - pool.insert_socket_addr("0.0.0.0:1".parse().unwrap(), ambient_authority()); - - let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - match pool.connect_into_tcp_stream(socket, "0.0.0.0:1") { - Ok(..) => panic!(), - Err(e) => assert!( - e.kind() == ErrorKind::ConnectionRefused - || e.kind() == ErrorKind::InvalidInput - || e.kind() == ErrorKind::AddrInUse - || e.kind() == ErrorKind::AddrNotAvailable - || e.kind() == ErrorKind::TimedOut - // Rust >=1.83 adds `ErrorKind::NetworkUnreachable`. - || e.to_string().contains("network unreachable") - || e.to_string().contains("Network is unreachable"), - "bad error: {} {:?}", - e, - e.kind() - ), - } -} - -#[test] -fn listen_localhost() { - let socket_addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(socket_addr, ambient_authority()); - for resolved in format!("localhost:{}", socket_addr.port()) - .to_socket_addrs() - .unwrap() - { - pool.insert_socket_addr(resolved, ambient_authority()); - } - - let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - t!(pool.bind_existing_tcp_listener(&listener, &socket_addr)); - listener.listen(None).unwrap(); - - let _t = thread::spawn(move || { - let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - let mut stream = - t!(pool.connect_into_tcp_stream(socket, &("localhost", socket_addr.port()))); - t!(stream.write(&[144])); - }); - - let mut stream = t!(listener.accept()).0; - let mut buf = [0]; - t!(stream.read(&mut buf)); - assert!(buf[0] == 144); -} - -#[test] -fn connect_loopback() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let acceptor = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool.bind_existing_tcp_listener(&acceptor, &addr)); - acceptor.listen(None).unwrap(); - - let _t = thread::spawn(move || { - let host = match addr { - SocketAddr::V4(..) => "127.0.0.1", - SocketAddr::V6(..) => "::1", - }; - let socket = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let mut stream = t!(pool.connect_into_tcp_stream(socket, &(host, addr.port()))); - t!(stream.write(&[66])); - }); - - let mut stream = t!(acceptor.accept()).0; - let mut buf = [0]; - t!(stream.read(&mut buf)); - assert!(buf[0] == 66); - }) -} - -#[test] -fn smoke_test() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let acceptor = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool.bind_existing_tcp_listener(&acceptor, &addr)); - acceptor.listen(None).unwrap(); - - let (tx, rx) = channel(); - let _t = thread::spawn(move || { - let socket = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let mut stream = t!(pool.connect_into_tcp_stream(socket, &addr)); - t!(stream.write(&[99])); - tx.send(t!(stream.local_addr())).unwrap(); - }); - - let (mut stream, addr) = t!(acceptor.accept()); - let mut buf = [0]; - t!(stream.read(&mut buf)); - assert!(buf[0] == 99); - assert_eq!(addr, t!(rx.recv())); - }) -} - -#[test] -fn read_eof() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let acceptor = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool.bind_existing_tcp_listener(&acceptor, &addr)); - acceptor.listen(None).unwrap(); - - let _t = thread::spawn(move || { - let socket = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let _stream = t!(pool.connect_into_tcp_stream(socket, &addr)); - // Close - }); - - let mut stream = t!(acceptor.accept()).0; - let mut buf = [0]; - let nread = t!(stream.read(&mut buf)); - assert_eq!(nread, 0); - let nread = t!(stream.read(&mut buf)); - assert_eq!(nread, 0); - }) -} - -#[test] -fn write_close() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let acceptor = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool.bind_existing_tcp_listener(&acceptor, &addr)); - acceptor.listen(None).unwrap(); - - let (tx, rx) = channel(); - let _t = thread::spawn(move || { - let socket = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - drop(t!(pool.connect_into_tcp_stream(socket, &addr))); - tx.send(()).unwrap(); - }); - - let mut stream = t!(acceptor.accept()).0; - rx.recv().unwrap(); - let buf = [0]; - match stream.write(&buf) { - Ok(..) => {} - Err(e) => { - assert!( - e.kind() == ErrorKind::ConnectionReset - || e.kind() == ErrorKind::BrokenPipe - || e.kind() == ErrorKind::ConnectionAborted, - "unknown error: {}", - e - ); - } - } - }) -} - -#[test] -fn multiple_connect_serial() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let max = 10; - let acceptor = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool.bind_existing_tcp_listener(&acceptor, &addr)); - acceptor.listen(None).unwrap(); - - let _t = thread::spawn(move || { - for _ in 0..max { - let socket = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let mut stream = t!(pool.connect_into_tcp_stream(socket, &addr)); - t!(stream.write(&[99])); - } - }); - - for stream in acceptor.incoming().take(max) { - let mut stream = t!(stream); - let mut buf = [0]; - t!(stream.read(&mut buf)); - assert_eq!(buf[0], 99); - } - }) -} - -#[test] -fn multiple_connect_interleaved_greedy_schedule() { - const MAX: usize = 10; - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let acceptor = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool.bind_existing_tcp_listener(&acceptor, &addr)); - acceptor.listen(None).unwrap(); - - let _t = thread::spawn(move || { - let acceptor = acceptor; - for (i, stream) in acceptor.incoming().enumerate().take(MAX) { - // Start another thread to handle the connection - let _t = thread::spawn(move || { - let mut stream = t!(stream); - let mut buf = [0]; - t!(stream.read(&mut buf)); - assert!(buf[0] == i as u8); - }); - } - }); - - connect(0, addr, pool); - }); - - fn connect(i: usize, addr: SocketAddr, pool: Pool) { - if i == MAX { - return; - } - - let t = thread::spawn(move || { - let socket = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let mut stream = t!(pool.connect_into_tcp_stream(socket, &addr)); - // Connect again before writing - connect(i + 1, addr, pool); - t!(stream.write(&[i as u8])); - }); - t.join().ok().expect("thread panicked"); - } -} - -#[test] -fn multiple_connect_interleaved_lazy_schedule() { - const MAX: usize = 10; - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let acceptor = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool.bind_existing_tcp_listener(&acceptor, &addr)); - acceptor.listen(None).unwrap(); - - let _t = thread::spawn(move || { - for stream in acceptor.incoming().take(MAX) { - // Start another thread to handle the connection - let _t = thread::spawn(move || { - let mut stream = t!(stream); - let mut buf = [0]; - t!(stream.read(&mut buf)); - assert!(buf[0] == 99); - }); - } - }); - - connect(0, addr, pool); - }); - - fn connect(i: usize, addr: SocketAddr, pool: Pool) { - if i == MAX { - return; - } - - let t = thread::spawn(move || { - let socket = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let mut stream = t!(pool.connect_into_tcp_stream(socket, &addr)); - connect(i + 1, addr, pool); - t!(stream.write(&[99])); - }); - t.join().ok().expect("thread panicked"); - } -} - -#[test] -fn socket_and_peer_name() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let listener = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool.bind_existing_tcp_listener(&listener, &addr)); - listener.listen(None).unwrap(); - let so_name = t!(listener.local_addr()); - assert_eq!(addr, so_name); - let _t = thread::spawn(move || { - t!(listener.accept()); - }); - - let socket = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let stream = t!(pool.connect_into_tcp_stream(socket, &addr)); - assert_eq!(addr, t!(stream.peer_addr())); - }) -} - -#[test] -fn partial_read() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let (tx, rx) = channel(); - let srv = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool.bind_existing_tcp_listener(&srv, &addr)); - srv.listen(None).unwrap(); - let _t = thread::spawn(move || { - let mut cl = t!(srv.accept()).0; - cl.write(&[10]).unwrap(); - let mut b = [0]; - t!(cl.read(&mut b)); - tx.send(()).unwrap(); - }); - - let socket = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let mut c = t!(pool.connect_into_tcp_stream(socket, &addr)); - let mut b = [0; 10]; - assert_eq!(c.read(&mut b).unwrap(), 1); - t!(c.write(&[1])); - rx.recv().unwrap(); - }) -} - -#[test] -fn read_vectored() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let srv = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool.bind_existing_tcp_listener(&srv, &addr)); - srv.listen(None).unwrap(); - let socket = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let mut s1 = t!(pool.connect_into_tcp_stream(socket, &addr)); - let mut s2 = t!(srv.accept()).0; - - let len = s1.write(&[10, 11, 12]).unwrap(); - assert_eq!(len, 3); - - let mut a = []; - let mut b = [0]; - let mut c = [0; 3]; - let len = t!(s2.read_vectored(&mut [ - IoSliceMut::new(&mut a), - IoSliceMut::new(&mut b), - IoSliceMut::new(&mut c) - ])); - assert!(len > 0); - assert_eq!(b, [10]); - // some implementations don't support readv, so we may only fill the first - // buffer - assert!(len == 1 || c == [11, 12, 0]); - }) -} - -#[test] -fn write_vectored() { - let mut pool = Pool::new(); - - each_ip(&mut |addr| { - pool.insert_socket_addr(addr, ambient_authority()); - - let srv = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool.bind_existing_tcp_listener(&srv, &addr)); - srv.listen(None).unwrap(); - let socket = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let mut s1 = t!(pool.connect_into_tcp_stream(socket, &addr)); - let mut s2 = t!(srv.accept()).0; - - let a = []; - let b = [10]; - let c = [11, 12]; - t!(s1.write_vectored(&[IoSlice::new(&a), IoSlice::new(&b), IoSlice::new(&c)])); - - let mut buf = [0; 4]; - let len = t!(s2.read(&mut buf)); - // some implementations don't support writev, so we may only write the first - // buffer - if len == 1 { - assert_eq!(buf, [10, 0, 0, 0]); - } else { - assert_eq!(len, 3); - assert_eq!(buf, [10, 11, 12, 0]); - } - }) -} - -#[test] -fn double_bind() { - let mut pool = Pool::new(); - - each_ip(&mut |addr| { - pool.insert_socket_addr(addr, ambient_authority()); - - let listener1 = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool.bind_existing_tcp_listener(&listener1, &addr)); - listener1.listen(None).unwrap(); - let listener2 = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - match pool.bind_existing_tcp_listener(&listener2, &addr) { - Ok(()) => panic!( - "This system (perhaps due to options set by pool.bind_existing_tcp_listener) \ - permits double binding: {:?} and {:?}", - listener1, listener2 - ), - Err(e) => { - assert!( - e.kind() == ErrorKind::ConnectionRefused - || e.kind() == ErrorKind::Other - || e.kind() == ErrorKind::AddrInUse, - "unknown error: {} {:?}", - e, - e.kind() - ); - } - } - }) -} - -#[test] -fn tcp_clone_smoke() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let acceptor = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool.bind_existing_tcp_listener(&acceptor, &addr)); - acceptor.listen(None).unwrap(); - - let _t = thread::spawn(move || { - let socket = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let mut s = t!(pool.connect_into_tcp_stream(socket, &addr)); - let mut buf = [0, 0]; - assert_eq!(s.read(&mut buf).unwrap(), 1); - assert_eq!(buf[0], 1); - t!(s.write(&[2])); - }); - - let mut s1 = t!(acceptor.accept()).0; - let s2 = t!(s1.try_clone()); - - let (tx1, rx1) = channel(); - let (tx2, rx2) = channel(); - let _t = thread::spawn(move || { - let mut s2 = s2; - rx1.recv().unwrap(); - t!(s2.write(&[1])); - tx2.send(()).unwrap(); - }); - tx1.send(()).unwrap(); - let mut buf = [0, 0]; - assert_eq!(s1.read(&mut buf).unwrap(), 1); - rx2.recv().unwrap(); - }) -} - -#[test] -fn tcp_clone_two_read() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let acceptor = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool.bind_existing_tcp_listener(&acceptor, &addr)); - acceptor.listen(None).unwrap(); - let (tx1, rx) = channel(); - let tx2 = tx1.clone(); - - let _t = thread::spawn(move || { - let socket = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let mut s = t!(pool.connect_into_tcp_stream(socket, &addr)); - t!(s.write(&[1])); - rx.recv().unwrap(); - t!(s.write(&[2])); - rx.recv().unwrap(); - }); - - let mut s1 = t!(acceptor.accept()).0; - let s2 = t!(s1.try_clone()); - - let (done, rx) = channel(); - let _t = thread::spawn(move || { - let mut s2 = s2; - let mut buf = [0, 0]; - t!(s2.read(&mut buf)); - tx2.send(()).unwrap(); - done.send(()).unwrap(); - }); - let mut buf = [0, 0]; - t!(s1.read(&mut buf)); - tx1.send(()).unwrap(); - - rx.recv().unwrap(); - }) -} - -#[test] -fn tcp_clone_two_write() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let acceptor = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool.bind_existing_tcp_listener(&acceptor, &addr)); - acceptor.listen(None).unwrap(); - - let _t = thread::spawn(move || { - let socket = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let mut s = t!(pool.connect_into_tcp_stream(socket, &addr)); - let mut buf = [0, 1]; - t!(s.read(&mut buf)); - t!(s.read(&mut buf)); - }); - - let mut s1 = t!(acceptor.accept()).0; - let s2 = t!(s1.try_clone()); - - let (done, rx) = channel(); - let _t = thread::spawn(move || { - let mut s2 = s2; - t!(s2.write(&[1])); - done.send(()).unwrap(); - }); - t!(s1.write(&[2])); - - rx.recv().unwrap(); - }) -} - -#[test] -// FIXME: https://github.com/fortanix/rust-sgx/issues/110 -#[cfg_attr(target_env = "sgx", ignore)] -fn shutdown_smoke() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let a = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool.bind_existing_tcp_listener(&a, &addr)); - a.listen(None).unwrap(); - let _t = thread::spawn(move || { - let mut c = t!(a.accept()).0; - let mut b = [0]; - assert_eq!(c.read(&mut b).unwrap(), 0); - t!(c.write(&[1])); - }); - - let socket = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let mut s = t!(pool.connect_into_tcp_stream(socket, &addr)); - t!(s.shutdown(Shutdown::Write)); - assert!(s.write(&[1]).is_err()); - let mut b = [0, 0]; - assert_eq!(t!(s.read(&mut b)), 1); - assert_eq!(b[0], 1); - }) -} - -#[test] -// FIXME: https://github.com/fortanix/rust-sgx/issues/110 -#[cfg_attr(target_env = "sgx", ignore)] -fn close_readwrite_smoke() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let a = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool.bind_existing_tcp_listener(&a, &addr)); - a.listen(None).unwrap(); - let (tx, rx) = channel::<()>(); - let _t = thread::spawn(move || { - let _s = t!(a.accept()); - let _ = rx.recv(); - }); - - let mut b = [0]; - let socket = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let mut s = t!(pool.connect_into_tcp_stream(socket, &addr)); - let mut s2 = t!(s.try_clone()); - - // closing should prevent reads/writes - t!(s.shutdown(Shutdown::Write)); - assert!(s.write(&[0]).is_err()); - t!(s.shutdown(Shutdown::Read)); - assert_eq!(s.read(&mut b).unwrap(), 0); - - // closing should affect previous handles - assert!(s2.write(&[0]).is_err()); - assert_eq!(s2.read(&mut b).unwrap(), 0); - - // closing should affect new handles - let mut s3 = t!(s.try_clone()); - assert!(s3.write(&[0]).is_err()); - assert_eq!(s3.read(&mut b).unwrap(), 0); - - // make sure these don't die - let _ = s2.shutdown(Shutdown::Read); - let _ = s2.shutdown(Shutdown::Write); - let _ = s3.shutdown(Shutdown::Read); - let _ = s3.shutdown(Shutdown::Write); - drop(tx); - }) -} - -#[test] -#[cfg(unix)] // test doesn't work on Windows, see #31657 -fn close_read_wakes_up() { - let mut pool = Pool::new(); - - each_ip(&mut |addr| { - pool.insert_socket_addr(addr, ambient_authority()); - - let a = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool.bind_existing_tcp_listener(&a, &addr)); - a.listen(None).unwrap(); - let (tx1, rx) = channel::<()>(); - let _t = thread::spawn(move || { - let _s = t!(a.accept()); - let _ = rx.recv(); - }); - - let socket = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let s = t!(pool.connect_into_tcp_stream(socket, &addr)); - let s2 = t!(s.try_clone()); - let (tx, rx) = channel(); - let _t = thread::spawn(move || { - let mut s2 = s2; - assert_eq!(t!(s2.read(&mut [0])), 0); - tx.send(()).unwrap(); - }); - // this should wake up the child thread - t!(s.shutdown(Shutdown::Read)); - - // this test will never finish if the child doesn't wake up - rx.recv().unwrap(); - drop(tx1); - }) -} - -#[test] -fn clone_while_reading() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let accept = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool.bind_existing_tcp_listener(&accept, &addr)); - accept.listen(None).unwrap(); - - // Enqueue a thread to write to a socket - let (tx, rx) = channel(); - let (txdone, rxdone) = channel(); - let txdone2 = txdone.clone(); - let _t = thread::spawn(move || { - let socket = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let mut tcp = t!(pool.connect_into_tcp_stream(socket, &addr)); - rx.recv().unwrap(); - t!(tcp.write(&[0])); - txdone2.send(()).unwrap(); - }); - - // Spawn off a reading clone - let tcp = t!(accept.accept()).0; - let tcp2 = t!(tcp.try_clone()); - let txdone3 = txdone.clone(); - let _t = thread::spawn(move || { - let mut tcp2 = tcp2; - t!(tcp2.read(&mut [0])); - txdone3.send(()).unwrap(); - }); - - // Try to ensure that the reading clone is indeed reading - for _ in 0..50 { - thread::yield_now(); - } - - // clone the handle again while it's reading, then let it finish the - // read. - let _ = t!(tcp.try_clone()); - tx.send(()).unwrap(); - rxdone.recv().unwrap(); - rxdone.recv().unwrap(); - }) -} - -#[test] -fn clone_accept_smoke() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let a = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool.bind_existing_tcp_listener(&a, &addr)); - a.listen(None).unwrap(); - let a2 = t!(a.try_clone()); - - let p = pool.clone(); - let _t = thread::spawn(move || { - let socket = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let _ = p.connect_into_tcp_stream(socket, &addr); - }); - let p = pool.clone(); - let _t = thread::spawn(move || { - let socket = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let _ = p.connect_into_tcp_stream(socket, &addr); - }); - - t!(a.accept()); - t!(a2.accept()); - }) -} - -#[test] -fn clone_accept_concurrent() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let a = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool.bind_existing_tcp_listener(&a, &addr)); - a.listen(None).unwrap(); - let a2 = t!(a.try_clone()); - - let (tx, rx) = channel(); - let tx2 = tx.clone(); - - let _t = thread::spawn(move || { - tx.send(t!(a.accept())).unwrap(); - }); - let _t = thread::spawn(move || { - tx2.send(t!(a2.accept())).unwrap(); - }); - - let p = pool.clone(); - let _t = thread::spawn(move || { - let socket = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let _ = p.connect_into_tcp_stream(socket, &addr); - }); - let p = pool.clone(); - let _t = thread::spawn(move || { - let socket = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let _ = p.connect_into_tcp_stream(socket, &addr); - }); - - rx.recv().unwrap(); - rx.recv().unwrap(); - }) -} - -#[test] -fn debug() { - let mut pool = Pool::new(); - - #[cfg(not(target_env = "sgx"))] - fn render_socket_addr<'a>(addr: &'a SocketAddr) -> impl fmt::Debug + 'a { - addr - } - #[cfg(target_env = "sgx")] - fn render_socket_addr<'a>(addr: &'a SocketAddr) -> impl fmt::Debug + 'a { - addr.to_string() - } - - #[cfg(target_env = "sgx")] - use std::os::fortanix_sgx::io::AsRawFd; - #[cfg(unix)] - use std::os::unix::io::AsRawFd; - #[cfg(not(windows))] - fn render_inner(addr: &dyn AsRawFd) -> impl fmt::Debug { - addr.as_raw_fd() - } - #[cfg(windows)] - fn render_inner(addr: &dyn std::os::windows::io::AsRawSocket) -> impl fmt::Debug { - addr.as_raw_socket() - } - - let inner_name = if cfg!(windows) { "socket" } else { "fd" }; - let socket_addr = next_test_ip4(); - pool.insert_socket_addr(socket_addr, ambient_authority()); - for resolved in format!("localhost:{}", socket_addr.port()) - .to_socket_addrs() - .unwrap() - { - pool.insert_socket_addr(resolved, ambient_authority()); - } - - let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - t!(pool.bind_existing_tcp_listener(&listener, &socket_addr)); - listener.listen(None).unwrap(); - let compare = format!( - "TcpListener {{ addr: {:?}, {}: {:?} }}", - render_socket_addr(&socket_addr), - inner_name, - render_inner(&listener) - ); - assert_eq!(format!("{:?}", listener), compare); - - let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - let stream = t!(pool.connect_into_tcp_stream(socket, &("localhost", socket_addr.port()))); - let compare = format!( - "TcpStream {{ addr: {:?}, peer: {:?}, {}: {:?} }}", - render_socket_addr(&stream.local_addr().unwrap()), - render_socket_addr(&stream.peer_addr().unwrap()), - inner_name, - render_inner(&stream) - ); - assert_eq!(format!("{:?}", stream), compare); -} - -// FIXME: re-enabled openbsd tests once their socket timeout code -// no longer has rounding errors. -// VxWorks ignores SO_SNDTIMEO. -#[cfg_attr( - any(target_os = "netbsd", target_os = "openbsd", target_os = "vxworks"), - ignore -)] -#[cfg_attr(target_env = "sgx", ignore)] // FIXME: https://github.com/fortanix/rust-sgx/issues/31 -#[test] -fn timeouts() { - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - for resolved in format!("localhost:{}", addr.port()) - .to_socket_addrs() - .unwrap() - { - pool.insert_socket_addr(resolved, ambient_authority()); - } - - let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - t!(pool.bind_existing_tcp_listener(&listener, &addr)); - listener.listen(None).unwrap(); - - let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - let stream = t!(pool.connect_into_tcp_stream(socket, &("localhost", addr.port()))); - let dur = Duration::new(15410, 0); - - assert_eq!(None, t!(stream.read_timeout())); - - t!(stream.set_read_timeout(Some(dur))); - assert_eq!(Some(dur), t!(stream.read_timeout())); - - assert_eq!(None, t!(stream.write_timeout())); - - t!(stream.set_write_timeout(Some(dur))); - assert_eq!(Some(dur), t!(stream.write_timeout())); - - t!(stream.set_read_timeout(None)); - assert_eq!(None, t!(stream.read_timeout())); - - t!(stream.set_write_timeout(None)); - assert_eq!(None, t!(stream.write_timeout())); - drop(listener); -} - -#[test] -#[cfg_attr(target_env = "sgx", ignore)] // FIXME: https://github.com/fortanix/rust-sgx/issues/31 -fn test_read_timeout() { - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - for resolved in format!("localhost:{}", addr.port()) - .to_socket_addrs() - .unwrap() - { - pool.insert_socket_addr(resolved, ambient_authority()); - } - - let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - t!(pool.bind_existing_tcp_listener(&listener, &addr)); - listener.listen(None).unwrap(); - - let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - let mut stream = t!(pool.connect_into_tcp_stream(socket, &("localhost", addr.port()))); - t!(stream.set_read_timeout(Some(Duration::from_millis(1000)))); - - let mut buf = [0; 10]; - let start = Instant::now(); - let kind = stream - .read_exact(&mut buf) - .err() - .expect("expected error") - .kind(); - assert!( - kind == ErrorKind::WouldBlock || kind == ErrorKind::TimedOut, - "unexpected_error: {:?}", - kind - ); - assert!(start.elapsed() > Duration::from_millis(400)); - drop(listener); -} - -#[test] -#[cfg_attr(target_env = "sgx", ignore)] // FIXME: https://github.com/fortanix/rust-sgx/issues/31 -fn test_read_with_timeout() { - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - for resolved in format!("localhost:{}", addr.port()) - .to_socket_addrs() - .unwrap() - { - pool.insert_socket_addr(resolved, ambient_authority()); - } - - let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - t!(pool.bind_existing_tcp_listener(&listener, &addr)); - listener.listen(None).unwrap(); - - let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - let mut stream = t!(pool.connect_into_tcp_stream(socket, &("localhost", addr.port()))); - t!(stream.set_read_timeout(Some(Duration::from_millis(1000)))); - - let mut other_end = t!(listener.accept()).0; - t!(other_end.write_all(b"hello world")); - - let mut buf = [0; 11]; - t!(stream.read(&mut buf)); - assert_eq!(b"hello world", &buf[..]); - - let start = Instant::now(); - let kind = stream - .read_exact(&mut buf) - .err() - .expect("expected error") - .kind(); - assert!( - kind == ErrorKind::WouldBlock || kind == ErrorKind::TimedOut, - "unexpected_error: {:?}", - kind - ); - assert!(start.elapsed() > Duration::from_millis(400)); - drop(listener); -} - -// Ensure the `set_read_timeout` and `set_write_timeout` calls return errors -// when passed zero Durations -#[test] -fn test_timeout_zero_duration() { - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - t!(pool.bind_existing_tcp_listener(&listener, &addr)); - listener.listen(None).unwrap(); - let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - let stream = t!(pool.connect_into_tcp_stream(socket, &addr)); - - let result = stream.set_write_timeout(Some(Duration::new(0, 0))); - let err = result.unwrap_err(); - assert_eq!(err.kind(), ErrorKind::InvalidInput); - - let result = stream.set_read_timeout(Some(Duration::new(0, 0))); - let err = result.unwrap_err(); - assert_eq!(err.kind(), ErrorKind::InvalidInput); - - drop(listener); -} - -#[test] -#[cfg_attr(target_env = "sgx", ignore)] -fn nodelay() { - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - for resolved in format!("localhost:{}", addr.port()) - .to_socket_addrs() - .unwrap() - { - pool.insert_socket_addr(resolved, ambient_authority()); - } - - let _listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - t!(pool.bind_existing_tcp_listener(&_listener, &addr)); - _listener.listen(None).unwrap(); - - let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - let stream = t!(pool.connect_into_tcp_stream(socket, &("localhost", addr.port()))); - - assert_eq!(false, t!(stream.nodelay())); - t!(stream.set_nodelay(true)); - assert_eq!(true, t!(stream.nodelay())); - t!(stream.set_nodelay(false)); - assert_eq!(false, t!(stream.nodelay())); -} - -#[test] -#[cfg_attr(target_env = "sgx", ignore)] -fn ttl() { - let ttl = 100; - - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - for resolved in format!("localhost:{}", addr.port()) - .to_socket_addrs() - .unwrap() - { - pool.insert_socket_addr(resolved, ambient_authority()); - } - - let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - t!(pool.bind_existing_tcp_listener(&listener, &addr)); - listener.listen(None).unwrap(); - - t!(listener.set_ttl(ttl)); - assert_eq!(ttl, t!(listener.ttl())); - - let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - let stream = t!(pool.connect_into_tcp_stream(socket, &("localhost", addr.port()))); - - t!(stream.set_ttl(ttl)); - assert_eq!(ttl, t!(stream.ttl())); -} - -#[test] -#[cfg_attr(target_env = "sgx", ignore)] -fn set_nonblocking() { - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - for resolved in format!("localhost:{}", addr.port()) - .to_socket_addrs() - .unwrap() - { - pool.insert_socket_addr(resolved, ambient_authority()); - } - - let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - t!(pool.bind_existing_tcp_listener(&listener, &addr)); - listener.listen(None).unwrap(); - - t!(listener.set_nonblocking(true)); - t!(listener.set_nonblocking(false)); - - let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - let mut stream = t!(pool.connect_into_tcp_stream(socket, &("localhost", addr.port()))); - - t!(stream.set_nonblocking(false)); - t!(stream.set_nonblocking(true)); - - let mut buf = [0]; - match stream.read(&mut buf) { - Ok(_) => panic!("expected error"), - Err(ref e) if e.kind() == ErrorKind::WouldBlock => {} - Err(e) => panic!("unexpected error {}", e), - } -} - -#[test] -#[cfg_attr(target_env = "sgx", ignore)] // FIXME: https://github.com/fortanix/rust-sgx/issues/31 -fn peek() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let (txdone, rxdone) = channel(); - - let srv = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool.bind_existing_tcp_listener(&srv, &addr)); - srv.listen(None).unwrap(); - let _t = thread::spawn(move || { - let mut cl = t!(srv.accept()).0; - cl.write(&[1, 3, 3, 7]).unwrap(); - t!(rxdone.recv()); - }); - - let socket = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let mut c = t!(pool.connect_into_tcp_stream(socket, &addr)); - let mut b = [0; 10]; - for _ in 1..3 { - let len = c.peek(&mut b).unwrap(); - assert_eq!(len, 4); - } - let len = c.read(&mut b).unwrap(); - assert_eq!(len, 4); - - t!(c.set_nonblocking(true)); - match c.peek(&mut b) { - Ok(_) => panic!("expected error"), - Err(ref e) if e.kind() == ErrorKind::WouldBlock => {} - Err(e) => panic!("unexpected error {}", e), - } - t!(txdone.send(())); - }) -} - -#[test] -#[cfg_attr(target_env = "sgx", ignore)] // FIXME: https://github.com/fortanix/rust-sgx/issues/31 -fn connect_timeout_valid() { - let mut pool = Pool::new(); - pool.insert_socket_addr("127.0.0.1:0".parse().unwrap(), ambient_authority()); - let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - pool.bind_existing_tcp_listener(&listener, "127.0.0.1:0") - .unwrap(); - listener.listen(None).unwrap(); - let addr = listener.local_addr().unwrap(); - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - pool.connect_timeout_tcp_stream(&addr, Duration::from_secs(2)) - .unwrap(); -} diff --git a/crates/wasi/src/filesystem/primitives/tests/cap_net_ext_tcp_split.rs b/crates/wasi/src/filesystem/primitives/tests/cap_net_ext_tcp_split.rs deleted file mode 100644 index 17fdae36da50..000000000000 --- a/crates/wasi/src/filesystem/primitives/tests/cap_net_ext_tcp_split.rs +++ /dev/null @@ -1,1372 +0,0 @@ -// This file is derived from Rust's library/std/src/net/tcp/tests.rs at -// revision 377d1a984cd2a53327092b90aa1d8b7e22d1e347. -// -// This is like cap-net-ext-tcp.rs but uses the binder/connecter APIs. - -mod net; - -use cap_net_ext::{AddressFamily, Blocking, PoolExt, TcpListenerExt}; -use cap_std::ambient_authority; -use cap_std::net::*; -use net::{next_test_ip4, next_test_ip6}; -use std::io::prelude::*; -use std::io::{ErrorKind, IoSlice, IoSliceMut}; -use std::sync::mpsc::channel; -use std::time::{Duration, Instant}; -use std::{fmt, thread}; - -fn each_ip(f: &mut dyn FnMut(SocketAddr)) { - f(next_test_ip4()); - f(next_test_ip6()); -} - -macro_rules! t { - ($e:expr) => { - match $e { - Ok(t) => t, - Err(e) => panic!("received error for `{}`: {}", stringify!($e), e), - } - }; -} - -#[test] -fn bind_error() { - let mut pool = Pool::new(); - pool.insert_socket_addr("1.1.1.1:9999".parse().unwrap(), ambient_authority()); - - let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - match pool - .tcp_binder("1.1.1.1:9999") - .unwrap() - .bind_existing_tcp_listener(&listener) - { - Ok(..) => panic!(), - Err(e) => assert_eq!(e.kind(), ErrorKind::AddrNotAvailable), - } -} - -#[test] -fn connect_error() { - let mut pool = Pool::new(); - pool.insert_socket_addr("0.0.0.0:1".parse().unwrap(), ambient_authority()); - - let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - match pool - .tcp_connecter("0.0.0.0:1") - .unwrap() - .connect_into_tcp_stream(socket) - { - Ok(..) => panic!(), - Err(e) => assert!( - e.kind() == ErrorKind::ConnectionRefused - || e.kind() == ErrorKind::InvalidInput - || e.kind() == ErrorKind::AddrInUse - || e.kind() == ErrorKind::AddrNotAvailable - || e.kind() == ErrorKind::TimedOut - // Rust >=1.83 adds `ErrorKind::NetworkUnreachable`. - || e.to_string().contains("network unreachable") - || e.to_string().contains("Network is unreachable"), - "bad error: {} {:?}", - e, - e.kind() - ), - } -} - -#[test] -fn listen_localhost() { - let socket_addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(socket_addr, ambient_authority()); - for resolved in format!("localhost:{}", socket_addr.port()) - .to_socket_addrs() - .unwrap() - { - pool.insert_socket_addr(resolved, ambient_authority()); - } - - let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - t!(pool - .tcp_binder(&socket_addr) - .unwrap() - .bind_existing_tcp_listener(&listener)); - listener.listen(None).unwrap(); - - let _t = thread::spawn(move || { - let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - let mut stream = t!(pool - .tcp_connecter(&("localhost", socket_addr.port())) - .unwrap() - .connect_into_tcp_stream(socket)); - t!(stream.write(&[144])); - }); - - let mut stream = t!(listener.accept()).0; - let mut buf = [0]; - t!(stream.read(&mut buf)); - assert!(buf[0] == 144); -} - -#[test] -fn connect_loopback() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let acceptor = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool - .tcp_binder(&addr) - .unwrap() - .bind_existing_tcp_listener(&acceptor)); - acceptor.listen(None).unwrap(); - - let _t = thread::spawn(move || { - let host = match addr { - SocketAddr::V4(..) => "127.0.0.1", - SocketAddr::V6(..) => "::1", - }; - let socket = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let mut stream = t!(pool - .tcp_connecter(&(host, addr.port())) - .unwrap() - .connect_into_tcp_stream(socket)); - t!(stream.write(&[66])); - }); - - let mut stream = t!(acceptor.accept()).0; - let mut buf = [0]; - t!(stream.read(&mut buf)); - assert!(buf[0] == 66); - }) -} - -#[test] -fn smoke_test() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let acceptor = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool - .tcp_binder(&addr) - .unwrap() - .bind_existing_tcp_listener(&acceptor)); - acceptor.listen(None).unwrap(); - - let (tx, rx) = channel(); - let _t = thread::spawn(move || { - let socket = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let mut stream = t!(pool - .tcp_connecter(&addr) - .unwrap() - .connect_into_tcp_stream(socket)); - t!(stream.write(&[99])); - tx.send(t!(stream.local_addr())).unwrap(); - }); - - let (mut stream, addr) = t!(acceptor.accept()); - let mut buf = [0]; - t!(stream.read(&mut buf)); - assert!(buf[0] == 99); - assert_eq!(addr, t!(rx.recv())); - }) -} - -#[test] -fn read_eof() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let acceptor = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool - .tcp_binder(&addr) - .unwrap() - .bind_existing_tcp_listener(&acceptor)); - acceptor.listen(None).unwrap(); - - let _t = thread::spawn(move || { - let socket = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let _stream = t!(pool - .tcp_connecter(&addr) - .unwrap() - .connect_into_tcp_stream(socket)); - // Close - }); - - let mut stream = t!(acceptor.accept()).0; - let mut buf = [0]; - let nread = t!(stream.read(&mut buf)); - assert_eq!(nread, 0); - let nread = t!(stream.read(&mut buf)); - assert_eq!(nread, 0); - }) -} - -#[test] -fn write_close() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let acceptor = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool - .tcp_binder(&addr) - .unwrap() - .bind_existing_tcp_listener(&acceptor)); - acceptor.listen(None).unwrap(); - - let (tx, rx) = channel(); - let _t = thread::spawn(move || { - let socket = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - drop(t!(pool - .tcp_connecter(&addr) - .unwrap() - .connect_into_tcp_stream(socket))); - tx.send(()).unwrap(); - }); - - let mut stream = t!(acceptor.accept()).0; - rx.recv().unwrap(); - let buf = [0]; - match stream.write(&buf) { - Ok(..) => {} - Err(e) => { - assert!( - e.kind() == ErrorKind::ConnectionReset - || e.kind() == ErrorKind::BrokenPipe - || e.kind() == ErrorKind::ConnectionAborted, - "unknown error: {}", - e - ); - } - } - }) -} - -#[test] -fn multiple_connect_serial() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let max = 10; - let acceptor = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool - .tcp_binder(&addr) - .unwrap() - .bind_existing_tcp_listener(&acceptor)); - acceptor.listen(None).unwrap(); - - let _t = thread::spawn(move || { - for _ in 0..max { - let socket = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let mut stream = t!(pool - .tcp_connecter(&addr) - .unwrap() - .connect_into_tcp_stream(socket)); - t!(stream.write(&[99])); - } - }); - - for stream in acceptor.incoming().take(max) { - let mut stream = t!(stream); - let mut buf = [0]; - t!(stream.read(&mut buf)); - assert_eq!(buf[0], 99); - } - }) -} - -#[test] -fn multiple_connect_interleaved_greedy_schedule() { - const MAX: usize = 10; - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let acceptor = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool - .tcp_binder(&addr) - .unwrap() - .bind_existing_tcp_listener(&acceptor)); - acceptor.listen(None).unwrap(); - - let _t = thread::spawn(move || { - let acceptor = acceptor; - for (i, stream) in acceptor.incoming().enumerate().take(MAX) { - // Start another thread to handle the connection - let _t = thread::spawn(move || { - let mut stream = t!(stream); - let mut buf = [0]; - t!(stream.read(&mut buf)); - assert!(buf[0] == i as u8); - }); - } - }); - - connect(0, addr, pool); - }); - - fn connect(i: usize, addr: SocketAddr, pool: Pool) { - if i == MAX { - return; - } - - let t = thread::spawn(move || { - let socket = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let mut stream = t!(pool - .tcp_connecter(&addr) - .unwrap() - .connect_into_tcp_stream(socket)); - // Connect again before writing - connect(i + 1, addr, pool); - t!(stream.write(&[i as u8])); - }); - t.join().ok().expect("thread panicked"); - } -} - -#[test] -fn multiple_connect_interleaved_lazy_schedule() { - const MAX: usize = 10; - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let acceptor = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool - .tcp_binder(&addr) - .unwrap() - .bind_existing_tcp_listener(&acceptor)); - acceptor.listen(None).unwrap(); - - let _t = thread::spawn(move || { - for stream in acceptor.incoming().take(MAX) { - // Start another thread to handle the connection - let _t = thread::spawn(move || { - let mut stream = t!(stream); - let mut buf = [0]; - t!(stream.read(&mut buf)); - assert!(buf[0] == 99); - }); - } - }); - - connect(0, addr, pool); - }); - - fn connect(i: usize, addr: SocketAddr, pool: Pool) { - if i == MAX { - return; - } - - let t = thread::spawn(move || { - let socket = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let mut stream = t!(pool - .tcp_connecter(&addr) - .unwrap() - .connect_into_tcp_stream(socket)); - connect(i + 1, addr, pool); - t!(stream.write(&[99])); - }); - t.join().ok().expect("thread panicked"); - } -} - -#[test] -fn socket_and_peer_name() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let listener = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool - .tcp_binder(&addr) - .unwrap() - .bind_existing_tcp_listener(&listener)); - listener.listen(None).unwrap(); - let so_name = t!(listener.local_addr()); - assert_eq!(addr, so_name); - let _t = thread::spawn(move || { - t!(listener.accept()); - }); - - let socket = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let stream = t!(pool - .tcp_connecter(&addr) - .unwrap() - .connect_into_tcp_stream(socket)); - assert_eq!(addr, t!(stream.peer_addr())); - }) -} - -#[test] -fn partial_read() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let (tx, rx) = channel(); - let srv = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool - .tcp_binder(&addr) - .unwrap() - .bind_existing_tcp_listener(&srv)); - srv.listen(None).unwrap(); - let _t = thread::spawn(move || { - let mut cl = t!(srv.accept()).0; - cl.write(&[10]).unwrap(); - let mut b = [0]; - t!(cl.read(&mut b)); - tx.send(()).unwrap(); - }); - - let socket = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let mut c = t!(pool - .tcp_connecter(&addr) - .unwrap() - .connect_into_tcp_stream(socket)); - let mut b = [0; 10]; - assert_eq!(c.read(&mut b).unwrap(), 1); - t!(c.write(&[1])); - rx.recv().unwrap(); - }) -} - -#[test] -fn read_vectored() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let srv = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool - .tcp_binder(&addr) - .unwrap() - .bind_existing_tcp_listener(&srv)); - srv.listen(None).unwrap(); - let socket = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let mut s1 = t!(pool - .tcp_connecter(&addr) - .unwrap() - .connect_into_tcp_stream(socket)); - let mut s2 = t!(srv.accept()).0; - - let len = s1.write(&[10, 11, 12]).unwrap(); - assert_eq!(len, 3); - - let mut a = []; - let mut b = [0]; - let mut c = [0; 3]; - let len = t!(s2.read_vectored(&mut [ - IoSliceMut::new(&mut a), - IoSliceMut::new(&mut b), - IoSliceMut::new(&mut c) - ])); - assert!(len > 0); - assert_eq!(b, [10]); - // some implementations don't support readv, so we may only fill the first - // buffer - assert!(len == 1 || c == [11, 12, 0]); - }) -} - -#[test] -fn write_vectored() { - let mut pool = Pool::new(); - - each_ip(&mut |addr| { - pool.insert_socket_addr(addr, ambient_authority()); - - let srv = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool - .tcp_binder(&addr) - .unwrap() - .bind_existing_tcp_listener(&srv)); - srv.listen(None).unwrap(); - let socket = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let mut s1 = t!(pool - .tcp_connecter(&addr) - .unwrap() - .connect_into_tcp_stream(socket)); - let mut s2 = t!(srv.accept()).0; - - let a = []; - let b = [10]; - let c = [11, 12]; - t!(s1.write_vectored(&[IoSlice::new(&a), IoSlice::new(&b), IoSlice::new(&c)])); - - let mut buf = [0; 4]; - let len = t!(s2.read(&mut buf)); - // some implementations don't support writev, so we may only write the first - // buffer - if len == 1 { - assert_eq!(buf, [10, 0, 0, 0]); - } else { - assert_eq!(len, 3); - assert_eq!(buf, [10, 11, 12, 0]); - } - }) -} - -#[test] -fn double_bind() { - let mut pool = Pool::new(); - - each_ip(&mut |addr| { - pool.insert_socket_addr(addr, ambient_authority()); - - let listener1 = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool - .tcp_binder(&addr) - .unwrap() - .bind_existing_tcp_listener(&listener1)); - listener1.listen(None).unwrap(); - let listener2 = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - match pool - .tcp_binder(&addr) - .unwrap() - .bind_existing_tcp_listener(&listener2) - { - Ok(()) => panic!( - "This system (perhaps due to options set by pool.bind_existing_tcp_listener) \ - permits double binding: {:?} and {:?}", - listener1, listener2 - ), - Err(e) => { - assert!( - e.kind() == ErrorKind::ConnectionRefused - || e.kind() == ErrorKind::Other - || e.kind() == ErrorKind::AddrInUse, - "unknown error: {} {:?}", - e, - e.kind() - ); - } - } - }) -} - -#[test] -fn tcp_clone_smoke() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let acceptor = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool - .tcp_binder(&addr) - .unwrap() - .bind_existing_tcp_listener(&acceptor)); - acceptor.listen(None).unwrap(); - - let _t = thread::spawn(move || { - let socket = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let mut s = t!(pool - .tcp_connecter(&addr) - .unwrap() - .connect_into_tcp_stream(socket)); - let mut buf = [0, 0]; - assert_eq!(s.read(&mut buf).unwrap(), 1); - assert_eq!(buf[0], 1); - t!(s.write(&[2])); - }); - - let mut s1 = t!(acceptor.accept()).0; - let s2 = t!(s1.try_clone()); - - let (tx1, rx1) = channel(); - let (tx2, rx2) = channel(); - let _t = thread::spawn(move || { - let mut s2 = s2; - rx1.recv().unwrap(); - t!(s2.write(&[1])); - tx2.send(()).unwrap(); - }); - tx1.send(()).unwrap(); - let mut buf = [0, 0]; - assert_eq!(s1.read(&mut buf).unwrap(), 1); - rx2.recv().unwrap(); - }) -} - -#[test] -fn tcp_clone_two_read() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let acceptor = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool - .tcp_binder(&addr) - .unwrap() - .bind_existing_tcp_listener(&acceptor)); - acceptor.listen(None).unwrap(); - let (tx1, rx) = channel(); - let tx2 = tx1.clone(); - - let _t = thread::spawn(move || { - let socket = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let mut s = t!(pool - .tcp_connecter(&addr) - .unwrap() - .connect_into_tcp_stream(socket)); - t!(s.write(&[1])); - rx.recv().unwrap(); - t!(s.write(&[2])); - rx.recv().unwrap(); - }); - - let mut s1 = t!(acceptor.accept()).0; - let s2 = t!(s1.try_clone()); - - let (done, rx) = channel(); - let _t = thread::spawn(move || { - let mut s2 = s2; - let mut buf = [0, 0]; - t!(s2.read(&mut buf)); - tx2.send(()).unwrap(); - done.send(()).unwrap(); - }); - let mut buf = [0, 0]; - t!(s1.read(&mut buf)); - tx1.send(()).unwrap(); - - rx.recv().unwrap(); - }) -} - -#[test] -fn tcp_clone_two_write() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let acceptor = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool - .tcp_binder(&addr) - .unwrap() - .bind_existing_tcp_listener(&acceptor)); - acceptor.listen(None).unwrap(); - - let _t = thread::spawn(move || { - let socket = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let mut s = t!(pool - .tcp_connecter(&addr) - .unwrap() - .connect_into_tcp_stream(socket)); - let mut buf = [0, 1]; - t!(s.read(&mut buf)); - t!(s.read(&mut buf)); - }); - - let mut s1 = t!(acceptor.accept()).0; - let s2 = t!(s1.try_clone()); - - let (done, rx) = channel(); - let _t = thread::spawn(move || { - let mut s2 = s2; - t!(s2.write(&[1])); - done.send(()).unwrap(); - }); - t!(s1.write(&[2])); - - rx.recv().unwrap(); - }) -} - -#[test] -// FIXME: https://github.com/fortanix/rust-sgx/issues/110 -#[cfg_attr(target_env = "sgx", ignore)] -fn shutdown_smoke() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let a = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool - .tcp_binder(&addr) - .unwrap() - .bind_existing_tcp_listener(&a)); - a.listen(None).unwrap(); - let _t = thread::spawn(move || { - let mut c = t!(a.accept()).0; - let mut b = [0]; - assert_eq!(c.read(&mut b).unwrap(), 0); - t!(c.write(&[1])); - }); - - let socket = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let mut s = t!(pool - .tcp_connecter(&addr) - .unwrap() - .connect_into_tcp_stream(socket)); - t!(s.shutdown(Shutdown::Write)); - assert!(s.write(&[1]).is_err()); - let mut b = [0, 0]; - assert_eq!(t!(s.read(&mut b)), 1); - assert_eq!(b[0], 1); - }) -} - -#[test] -// FIXME: https://github.com/fortanix/rust-sgx/issues/110 -#[cfg_attr(target_env = "sgx", ignore)] -fn close_readwrite_smoke() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let a = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool - .tcp_binder(&addr) - .unwrap() - .bind_existing_tcp_listener(&a)); - a.listen(None).unwrap(); - let (tx, rx) = channel::<()>(); - let _t = thread::spawn(move || { - let _s = t!(a.accept()); - let _ = rx.recv(); - }); - - let mut b = [0]; - let socket = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let mut s = t!(pool - .tcp_connecter(&addr) - .unwrap() - .connect_into_tcp_stream(socket)); - let mut s2 = t!(s.try_clone()); - - // closing should prevent reads/writes - t!(s.shutdown(Shutdown::Write)); - assert!(s.write(&[0]).is_err()); - t!(s.shutdown(Shutdown::Read)); - assert_eq!(s.read(&mut b).unwrap(), 0); - - // closing should affect previous handles - assert!(s2.write(&[0]).is_err()); - assert_eq!(s2.read(&mut b).unwrap(), 0); - - // closing should affect new handles - let mut s3 = t!(s.try_clone()); - assert!(s3.write(&[0]).is_err()); - assert_eq!(s3.read(&mut b).unwrap(), 0); - - // make sure these don't die - let _ = s2.shutdown(Shutdown::Read); - let _ = s2.shutdown(Shutdown::Write); - let _ = s3.shutdown(Shutdown::Read); - let _ = s3.shutdown(Shutdown::Write); - drop(tx); - }) -} - -#[test] -#[cfg(unix)] // test doesn't work on Windows, see #31657 -fn close_read_wakes_up() { - let mut pool = Pool::new(); - - each_ip(&mut |addr| { - pool.insert_socket_addr(addr, ambient_authority()); - - let a = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool - .tcp_binder(&addr) - .unwrap() - .bind_existing_tcp_listener(&a)); - a.listen(None).unwrap(); - let (tx1, rx) = channel::<()>(); - let _t = thread::spawn(move || { - let _s = t!(a.accept()); - let _ = rx.recv(); - }); - - let socket = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let s = t!(pool - .tcp_connecter(&addr) - .unwrap() - .connect_into_tcp_stream(socket)); - let s2 = t!(s.try_clone()); - let (tx, rx) = channel(); - let _t = thread::spawn(move || { - let mut s2 = s2; - assert_eq!(t!(s2.read(&mut [0])), 0); - tx.send(()).unwrap(); - }); - // this should wake up the child thread - t!(s.shutdown(Shutdown::Read)); - - // this test will never finish if the child doesn't wake up - rx.recv().unwrap(); - drop(tx1); - }) -} - -#[test] -fn clone_while_reading() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let accept = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool - .tcp_binder(&addr) - .unwrap() - .bind_existing_tcp_listener(&accept)); - accept.listen(None).unwrap(); - - // Enqueue a thread to write to a socket - let (tx, rx) = channel(); - let (txdone, rxdone) = channel(); - let txdone2 = txdone.clone(); - let _t = thread::spawn(move || { - let socket = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let mut tcp = t!(pool - .tcp_connecter(&addr) - .unwrap() - .connect_into_tcp_stream(socket)); - rx.recv().unwrap(); - t!(tcp.write(&[0])); - txdone2.send(()).unwrap(); - }); - - // Spawn off a reading clone - let tcp = t!(accept.accept()).0; - let tcp2 = t!(tcp.try_clone()); - let txdone3 = txdone.clone(); - let _t = thread::spawn(move || { - let mut tcp2 = tcp2; - t!(tcp2.read(&mut [0])); - txdone3.send(()).unwrap(); - }); - - // Try to ensure that the reading clone is indeed reading - for _ in 0..50 { - thread::yield_now(); - } - - // clone the handle again while it's reading, then let it finish the - // read. - let _ = t!(tcp.try_clone()); - tx.send(()).unwrap(); - rxdone.recv().unwrap(); - rxdone.recv().unwrap(); - }) -} - -#[test] -fn clone_accept_smoke() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let a = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool - .tcp_binder(&addr) - .unwrap() - .bind_existing_tcp_listener(&a)); - a.listen(None).unwrap(); - let a2 = t!(a.try_clone()); - - let p = pool.clone(); - let _t = thread::spawn(move || { - let socket = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let _ = p.connect_into_tcp_stream(socket, &addr); - }); - let p = pool.clone(); - let _t = thread::spawn(move || { - let socket = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let _ = p.connect_into_tcp_stream(socket, &addr); - }); - - t!(a.accept()); - t!(a2.accept()); - }) -} - -#[test] -fn clone_accept_concurrent() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let a = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool - .tcp_binder(&addr) - .unwrap() - .bind_existing_tcp_listener(&a)); - a.listen(None).unwrap(); - let a2 = t!(a.try_clone()); - - let (tx, rx) = channel(); - let tx2 = tx.clone(); - - let _t = thread::spawn(move || { - tx.send(t!(a.accept())).unwrap(); - }); - let _t = thread::spawn(move || { - tx2.send(t!(a2.accept())).unwrap(); - }); - - let p = pool.clone(); - let _t = thread::spawn(move || { - let socket = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let _ = p.connect_into_tcp_stream(socket, &addr); - }); - let p = pool.clone(); - let _t = thread::spawn(move || { - let socket = - TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let _ = p.connect_into_tcp_stream(socket, &addr); - }); - - rx.recv().unwrap(); - rx.recv().unwrap(); - }) -} - -#[test] -fn debug() { - let mut pool = Pool::new(); - - #[cfg(not(target_env = "sgx"))] - fn render_socket_addr<'a>(addr: &'a SocketAddr) -> impl fmt::Debug + 'a { - addr - } - #[cfg(target_env = "sgx")] - fn render_socket_addr<'a>(addr: &'a SocketAddr) -> impl fmt::Debug + 'a { - addr.to_string() - } - - #[cfg(target_env = "sgx")] - use std::os::fortanix_sgx::io::AsRawFd; - #[cfg(unix)] - use std::os::unix::io::AsRawFd; - #[cfg(not(windows))] - fn render_inner(addr: &dyn AsRawFd) -> impl fmt::Debug { - addr.as_raw_fd() - } - #[cfg(windows)] - fn render_inner(addr: &dyn std::os::windows::io::AsRawSocket) -> impl fmt::Debug { - addr.as_raw_socket() - } - - let inner_name = if cfg!(windows) { "socket" } else { "fd" }; - let socket_addr = next_test_ip4(); - pool.insert_socket_addr(socket_addr, ambient_authority()); - for resolved in format!("localhost:{}", socket_addr.port()) - .to_socket_addrs() - .unwrap() - { - pool.insert_socket_addr(resolved, ambient_authority()); - } - - let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - t!(pool - .tcp_binder(&socket_addr) - .unwrap() - .bind_existing_tcp_listener(&listener)); - listener.listen(None).unwrap(); - let compare = format!( - "TcpListener {{ addr: {:?}, {}: {:?} }}", - render_socket_addr(&socket_addr), - inner_name, - render_inner(&listener) - ); - assert_eq!(format!("{:?}", listener), compare); - - let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - let stream = t!(pool - .tcp_connecter(&("localhost", socket_addr.port())) - .unwrap() - .connect_into_tcp_stream(socket)); - let compare = format!( - "TcpStream {{ addr: {:?}, peer: {:?}, {}: {:?} }}", - render_socket_addr(&stream.local_addr().unwrap()), - render_socket_addr(&stream.peer_addr().unwrap()), - inner_name, - render_inner(&stream) - ); - assert_eq!(format!("{:?}", stream), compare); -} - -// FIXME: re-enabled openbsd tests once their socket timeout code -// no longer has rounding errors. -// VxWorks ignores SO_SNDTIMEO. -#[cfg_attr( - any(target_os = "netbsd", target_os = "openbsd", target_os = "vxworks"), - ignore -)] -#[cfg_attr(target_env = "sgx", ignore)] // FIXME: https://github.com/fortanix/rust-sgx/issues/31 -#[test] -fn timeouts() { - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - for resolved in format!("localhost:{}", addr.port()) - .to_socket_addrs() - .unwrap() - { - pool.insert_socket_addr(resolved, ambient_authority()); - } - - let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - t!(pool - .tcp_binder(&addr) - .unwrap() - .bind_existing_tcp_listener(&listener)); - listener.listen(None).unwrap(); - - let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - let stream = t!(pool - .tcp_connecter(&("localhost", addr.port())) - .unwrap() - .connect_into_tcp_stream(socket)); - let dur = Duration::new(15410, 0); - - assert_eq!(None, t!(stream.read_timeout())); - - t!(stream.set_read_timeout(Some(dur))); - assert_eq!(Some(dur), t!(stream.read_timeout())); - - assert_eq!(None, t!(stream.write_timeout())); - - t!(stream.set_write_timeout(Some(dur))); - assert_eq!(Some(dur), t!(stream.write_timeout())); - - t!(stream.set_read_timeout(None)); - assert_eq!(None, t!(stream.read_timeout())); - - t!(stream.set_write_timeout(None)); - assert_eq!(None, t!(stream.write_timeout())); - drop(listener); -} - -#[test] -#[cfg_attr(target_env = "sgx", ignore)] // FIXME: https://github.com/fortanix/rust-sgx/issues/31 -fn test_read_timeout() { - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - for resolved in format!("localhost:{}", addr.port()) - .to_socket_addrs() - .unwrap() - { - pool.insert_socket_addr(resolved, ambient_authority()); - } - - let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - t!(pool - .tcp_binder(&addr) - .unwrap() - .bind_existing_tcp_listener(&listener)); - listener.listen(None).unwrap(); - - let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - let mut stream = t!(pool - .tcp_connecter(&("localhost", addr.port())) - .unwrap() - .connect_into_tcp_stream(socket)); - t!(stream.set_read_timeout(Some(Duration::from_millis(1000)))); - - let mut buf = [0; 10]; - let start = Instant::now(); - let kind = stream - .read_exact(&mut buf) - .err() - .expect("expected error") - .kind(); - assert!( - kind == ErrorKind::WouldBlock || kind == ErrorKind::TimedOut, - "unexpected_error: {:?}", - kind - ); - assert!(start.elapsed() > Duration::from_millis(400)); - drop(listener); -} - -#[test] -#[cfg_attr(target_env = "sgx", ignore)] // FIXME: https://github.com/fortanix/rust-sgx/issues/31 -fn test_read_with_timeout() { - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - for resolved in format!("localhost:{}", addr.port()) - .to_socket_addrs() - .unwrap() - { - pool.insert_socket_addr(resolved, ambient_authority()); - } - - let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - t!(pool - .tcp_binder(&addr) - .unwrap() - .bind_existing_tcp_listener(&listener)); - listener.listen(None).unwrap(); - - let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - let mut stream = t!(pool - .tcp_connecter(&("localhost", addr.port())) - .unwrap() - .connect_into_tcp_stream(socket)); - t!(stream.set_read_timeout(Some(Duration::from_millis(1000)))); - - let mut other_end = t!(listener.accept()).0; - t!(other_end.write_all(b"hello world")); - - let mut buf = [0; 11]; - t!(stream.read(&mut buf)); - assert_eq!(b"hello world", &buf[..]); - - let start = Instant::now(); - let kind = stream - .read_exact(&mut buf) - .err() - .expect("expected error") - .kind(); - assert!( - kind == ErrorKind::WouldBlock || kind == ErrorKind::TimedOut, - "unexpected_error: {:?}", - kind - ); - assert!(start.elapsed() > Duration::from_millis(400)); - drop(listener); -} - -// Ensure the `set_read_timeout` and `set_write_timeout` calls return errors -// when passed zero Durations -#[test] -fn test_timeout_zero_duration() { - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - t!(pool - .tcp_binder(&addr) - .unwrap() - .bind_existing_tcp_listener(&listener)); - listener.listen(None).unwrap(); - let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - let stream = t!(pool - .tcp_connecter(&addr) - .unwrap() - .connect_into_tcp_stream(socket)); - - let result = stream.set_write_timeout(Some(Duration::new(0, 0))); - let err = result.unwrap_err(); - assert_eq!(err.kind(), ErrorKind::InvalidInput); - - let result = stream.set_read_timeout(Some(Duration::new(0, 0))); - let err = result.unwrap_err(); - assert_eq!(err.kind(), ErrorKind::InvalidInput); - - drop(listener); -} - -#[test] -#[cfg_attr(target_env = "sgx", ignore)] -fn nodelay() { - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - for resolved in format!("localhost:{}", addr.port()) - .to_socket_addrs() - .unwrap() - { - pool.insert_socket_addr(resolved, ambient_authority()); - } - - let _listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - t!(pool - .tcp_binder(&addr) - .unwrap() - .bind_existing_tcp_listener(&_listener)); - _listener.listen(None).unwrap(); - - let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - let stream = t!(pool - .tcp_connecter(&("localhost", addr.port())) - .unwrap() - .connect_into_tcp_stream(socket)); - - assert_eq!(false, t!(stream.nodelay())); - t!(stream.set_nodelay(true)); - assert_eq!(true, t!(stream.nodelay())); - t!(stream.set_nodelay(false)); - assert_eq!(false, t!(stream.nodelay())); -} - -#[test] -#[cfg_attr(target_env = "sgx", ignore)] -fn ttl() { - let ttl = 100; - - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - for resolved in format!("localhost:{}", addr.port()) - .to_socket_addrs() - .unwrap() - { - pool.insert_socket_addr(resolved, ambient_authority()); - } - - let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - t!(pool - .tcp_binder(&addr) - .unwrap() - .bind_existing_tcp_listener(&listener)); - listener.listen(None).unwrap(); - - t!(listener.set_ttl(ttl)); - assert_eq!(ttl, t!(listener.ttl())); - - let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - let stream = t!(pool - .tcp_connecter(&("localhost", addr.port())) - .unwrap() - .connect_into_tcp_stream(socket)); - - t!(stream.set_ttl(ttl)); - assert_eq!(ttl, t!(stream.ttl())); -} - -#[test] -#[cfg_attr(target_env = "sgx", ignore)] -fn set_nonblocking() { - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - for resolved in format!("localhost:{}", addr.port()) - .to_socket_addrs() - .unwrap() - { - pool.insert_socket_addr(resolved, ambient_authority()); - } - - let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - t!(pool - .tcp_binder(&addr) - .unwrap() - .bind_existing_tcp_listener(&listener)); - listener.listen(None).unwrap(); - - t!(listener.set_nonblocking(true)); - t!(listener.set_nonblocking(false)); - - let socket = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - let mut stream = t!(pool - .tcp_connecter(&("localhost", addr.port())) - .unwrap() - .connect_into_tcp_stream(socket)); - - t!(stream.set_nonblocking(false)); - t!(stream.set_nonblocking(true)); - - let mut buf = [0]; - match stream.read(&mut buf) { - Ok(_) => panic!("expected error"), - Err(ref e) if e.kind() == ErrorKind::WouldBlock => {} - Err(e) => panic!("unexpected error {}", e), - } -} - -#[test] -#[cfg_attr(target_env = "sgx", ignore)] // FIXME: https://github.com/fortanix/rust-sgx/issues/31 -fn peek() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let (txdone, rxdone) = channel(); - - let srv = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool - .tcp_binder(&addr) - .unwrap() - .bind_existing_tcp_listener(&srv)); - srv.listen(None).unwrap(); - let _t = thread::spawn(move || { - let mut cl = t!(srv.accept()).0; - cl.write(&[1, 3, 3, 7]).unwrap(); - t!(rxdone.recv()); - }); - - let socket = TcpListener::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - let mut c = t!(pool - .tcp_connecter(&addr) - .unwrap() - .connect_into_tcp_stream(socket)); - let mut b = [0; 10]; - for _ in 1..3 { - let len = c.peek(&mut b).unwrap(); - assert_eq!(len, 4); - } - let len = c.read(&mut b).unwrap(); - assert_eq!(len, 4); - - t!(c.set_nonblocking(true)); - match c.peek(&mut b) { - Ok(_) => panic!("expected error"), - Err(ref e) if e.kind() == ErrorKind::WouldBlock => {} - Err(e) => panic!("unexpected error {}", e), - } - t!(txdone.send(())); - }) -} - -#[test] -#[cfg_attr(target_env = "sgx", ignore)] // FIXME: https://github.com/fortanix/rust-sgx/issues/31 -fn connect_timeout_valid() { - let mut pool = Pool::new(); - pool.insert_socket_addr("127.0.0.1:0".parse().unwrap(), ambient_authority()); - let listener = TcpListener::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - pool.tcp_binder("127.0.0.1:0") - .unwrap() - .bind_existing_tcp_listener(&listener) - .unwrap(); - listener.listen(None).unwrap(); - let addr = listener.local_addr().unwrap(); - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - pool.connect_timeout_tcp_stream(&addr, Duration::from_secs(2)) - .unwrap(); -} diff --git a/crates/wasi/src/filesystem/primitives/tests/cap_net_ext_udp.rs b/crates/wasi/src/filesystem/primitives/tests/cap_net_ext_udp.rs deleted file mode 100644 index 68159b951764..000000000000 --- a/crates/wasi/src/filesystem/primitives/tests/cap_net_ext_udp.rs +++ /dev/null @@ -1,474 +0,0 @@ -// This file is derived from Rust's library/std/src/net/udp/tests.rs at -// revision 377d1a984cd2a53327092b90aa1d8b7e22d1e347. - -mod net; -mod sys_common; - -use cap_net_ext::{AddressFamily, Blocking, PoolExt, UdpSocketExt}; -use cap_std::ambient_authority; -use cap_std::net::*; -use net::{next_test_ip4, next_test_ip6}; -use std::io::ErrorKind; -use std::sync::mpsc::channel; -//use sys_common::AsInner; -use std::thread; -use std::time::{Duration, Instant}; - -fn each_ip(f: &mut dyn FnMut(SocketAddr, SocketAddr)) { - f(next_test_ip4(), next_test_ip4()); - f(next_test_ip6(), next_test_ip6()); -} - -macro_rules! t { - ($e:expr) => { - match $e { - Ok(t) => t, - Err(e) => panic!("received error for `{}`: {}", stringify!($e), e), - } - }; -} - -#[test] -fn bind_error() { - let mut pool = Pool::new(); - pool.insert_socket_addr("1.1.1.1:9999".parse().unwrap(), ambient_authority()); - - let socket = UdpSocket::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - match pool.bind_existing_udp_socket(&socket, "1.1.1.1:9999") { - Ok(..) => panic!(), - Err(e) => assert_eq!(e.kind(), ErrorKind::AddrNotAvailable), - } -} - -#[test] -fn socket_smoke_test_ip4() { - each_ip(&mut |server_ip, client_ip| { - let mut client_pool = Pool::new(); - client_pool.insert_socket_addr(client_ip, ambient_authority()); - let mut server_pool = Pool::new(); - server_pool.insert_socket_addr(server_ip, ambient_authority()); - - let (tx1, rx1) = channel(); - let (tx2, rx2) = channel(); - - let p = server_pool.clone(); - let _t = thread::spawn(move || { - let client = - UdpSocket::new(AddressFamily::of_socket_addr(client_ip), Blocking::Yes).unwrap(); - t!(client_pool.bind_existing_udp_socket(&client, &client_ip)); - rx1.recv().unwrap(); - t!(p.send_to_udp_socket_addr(&client, &[99], &server_ip)); - tx2.send(()).unwrap(); - }); - - let server = - UdpSocket::new(AddressFamily::of_socket_addr(server_ip), Blocking::Yes).unwrap(); - t!(server_pool.bind_existing_udp_socket(&server, &server_ip)); - tx1.send(()).unwrap(); - let mut buf = [0]; - let (nread, src) = t!(server.recv_from(&mut buf)); - assert_eq!(nread, 1); - assert_eq!(buf[0], 99); - assert_eq!(src, client_ip); - rx2.recv().unwrap(); - }) -} - -#[test] -fn socket_name() { - each_ip(&mut |addr, _| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let server = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool.bind_existing_udp_socket(&server, &addr)); - assert_eq!(addr, t!(server.local_addr())); - }) -} - -#[test] -fn socket_peer() { - each_ip(&mut |addr1, addr2| { - let mut pool1 = Pool::new(); - pool1.insert_socket_addr(addr1, ambient_authority()); - let mut pool2 = Pool::new(); - pool2.insert_socket_addr(addr2, ambient_authority()); - - let server = UdpSocket::new(AddressFamily::of_socket_addr(addr1), Blocking::Yes).unwrap(); - t!(pool1.bind_existing_udp_socket(&server, &addr1)); - assert_eq!( - server.peer_addr().unwrap_err().kind(), - ErrorKind::NotConnected - ); - t!(pool2.connect_existing_udp_socket(&server, &addr2)); - assert_eq!(addr2, t!(server.peer_addr())); - }) -} - -#[test] -fn udp_clone_smoke() { - each_ip(&mut |addr1, addr2| { - let mut pool1 = Pool::new(); - pool1.insert_socket_addr(addr1, ambient_authority()); - let mut pool2 = Pool::new(); - pool2.insert_socket_addr(addr2, ambient_authority()); - - let sock1 = UdpSocket::new(AddressFamily::of_socket_addr(addr1), Blocking::Yes).unwrap(); - t!(pool1.bind_existing_udp_socket(&sock1, &addr1)); - let sock2 = UdpSocket::new(AddressFamily::of_socket_addr(addr2), Blocking::Yes).unwrap(); - t!(pool2.bind_existing_udp_socket(&sock2, &addr2)); - - let _t = thread::spawn(move || { - let mut buf = [0, 0]; - assert_eq!(sock2.recv_from(&mut buf).unwrap(), (1, addr1)); - assert_eq!(buf[0], 1); - t!(pool1.send_to_udp_socket_addr(&sock2, &[2], &addr1)); - }); - - let sock3 = t!(sock1.try_clone()); - - let (tx1, rx1) = channel(); - let (tx2, rx2) = channel(); - let p = pool2.clone(); - let _t = thread::spawn(move || { - rx1.recv().unwrap(); - t!(p.send_to_udp_socket_addr(&sock3, &[1], &addr2)); - tx2.send(()).unwrap(); - }); - tx1.send(()).unwrap(); - let mut buf = [0, 0]; - assert_eq!(sock1.recv_from(&mut buf).unwrap(), (1, addr2)); - rx2.recv().unwrap(); - }) -} - -#[test] -fn udp_clone_two_read() { - each_ip(&mut |addr1, addr2| { - let mut pool1 = Pool::new(); - pool1.insert_socket_addr(addr1, ambient_authority()); - let mut pool2 = Pool::new(); - pool2.insert_socket_addr(addr2, ambient_authority()); - - let sock1 = UdpSocket::new(AddressFamily::of_socket_addr(addr1), Blocking::Yes).unwrap(); - t!(pool1.bind_existing_udp_socket(&sock1, &addr1)); - let sock2 = UdpSocket::new(AddressFamily::of_socket_addr(addr2), Blocking::Yes).unwrap(); - t!(pool2.bind_existing_udp_socket(&sock2, &addr2)); - let (tx1, rx) = channel(); - let tx2 = tx1.clone(); - - let _t = thread::spawn(move || { - t!(pool1.send_to_udp_socket_addr(&sock2, &[1], &addr1)); - rx.recv().unwrap(); - t!(pool1.send_to_udp_socket_addr(&sock2, &[2], &addr1)); - rx.recv().unwrap(); - }); - - let sock3 = t!(sock1.try_clone()); - - let (done, rx) = channel(); - let _t = thread::spawn(move || { - let mut buf = [0, 0]; - t!(sock3.recv_from(&mut buf)); - tx2.send(()).unwrap(); - done.send(()).unwrap(); - }); - let mut buf = [0, 0]; - t!(sock1.recv_from(&mut buf)); - tx1.send(()).unwrap(); - - rx.recv().unwrap(); - }) -} - -#[test] -fn udp_clone_two_write() { - each_ip(&mut |addr1, addr2| { - let mut pool1 = Pool::new(); - pool1.insert_socket_addr(addr1, ambient_authority()); - let mut pool2 = Pool::new(); - pool2.insert_socket_addr(addr2, ambient_authority()); - - let sock1 = UdpSocket::new(AddressFamily::of_socket_addr(addr1), Blocking::Yes).unwrap(); - t!(pool1.bind_existing_udp_socket(&sock1, &addr1)); - let sock2 = UdpSocket::new(AddressFamily::of_socket_addr(addr2), Blocking::Yes).unwrap(); - t!(pool2.bind_existing_udp_socket(&sock2, &addr2)); - - let (tx, rx) = channel(); - let (serv_tx, serv_rx) = channel(); - - let _t = thread::spawn(move || { - let mut buf = [0, 1]; - rx.recv().unwrap(); - t!(sock2.recv_from(&mut buf)); - serv_tx.send(()).unwrap(); - }); - - let sock3 = t!(sock1.try_clone()); - - let (done, rx) = channel(); - let tx2 = tx.clone(); - let p = pool2.clone(); - let _t = thread::spawn(move || { - if p.send_to_udp_socket_addr(&sock3, &[1], &addr2).is_ok() { - let _ = tx2.send(()); - } - done.send(()).unwrap(); - }); - if pool2.send_to_udp_socket_addr(&sock1, &[2], &addr2).is_ok() { - let _ = tx.send(()); - } - drop(tx); - - rx.recv().unwrap(); - serv_rx.recv().unwrap(); - }) -} - -/* Disable this test, as it depends on Rust-internal details. -#[test] -fn debug() { - let name = if cfg!(windows) { "socket" } else { "fd" }; - let socket_addr = next_test_ip4(); - - let mut pool = Pool::new(); - - let udpsock_inner = udpsock.0.socket().as_inner(); - let compare = format!("UdpSocket {{ addr: {:?}, {}: {:?} }}", socket_addr, name, udpsock_inner); - assert_eq!(format!("{:?}", udpsock), compare); -} -*/ - -// FIXME: re-enabled openbsd/netbsd tests once their socket timeout code -// no longer has rounding errors. -// VxWorks ignores SO_SNDTIMEO. -#[cfg_attr( - any(target_os = "netbsd", target_os = "openbsd", target_os = "vxworks"), - ignore -)] -#[test] -fn timeouts() { - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let stream = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool.bind_existing_udp_socket(&stream, &addr)); - let dur = Duration::new(15410, 0); - - assert_eq!(None, t!(stream.read_timeout())); - - t!(stream.set_read_timeout(Some(dur))); - assert_eq!(Some(dur), t!(stream.read_timeout())); - - assert_eq!(None, t!(stream.write_timeout())); - - t!(stream.set_write_timeout(Some(dur))); - assert_eq!(Some(dur), t!(stream.write_timeout())); - - t!(stream.set_read_timeout(None)); - assert_eq!(None, t!(stream.read_timeout())); - - t!(stream.set_write_timeout(None)); - assert_eq!(None, t!(stream.write_timeout())); -} - -#[test] -fn test_read_timeout() { - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let stream = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool.bind_existing_udp_socket(&stream, &addr)); - t!(stream.set_read_timeout(Some(Duration::from_millis(1000)))); - - let mut buf = [0; 10]; - - let start = Instant::now(); - loop { - let kind = stream - .recv_from(&mut buf) - .err() - .expect("expected error") - .kind(); - if kind != ErrorKind::Interrupted { - assert!( - kind == ErrorKind::WouldBlock || kind == ErrorKind::TimedOut, - "unexpected_error: {:?}", - kind - ); - break; - } - } - assert!(start.elapsed() > Duration::from_millis(400)); -} - -#[test] -fn test_read_with_timeout() { - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let stream = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool.bind_existing_udp_socket(&stream, &addr)); - t!(stream.set_read_timeout(Some(Duration::from_millis(1000)))); - - t!(pool.send_to_udp_socket_addr(&stream, b"hello world", &addr)); - - let mut buf = [0; 11]; - t!(stream.recv_from(&mut buf)); - assert_eq!(b"hello world", &buf[..]); - - let start = Instant::now(); - loop { - let kind = stream - .recv_from(&mut buf) - .err() - .expect("expected error") - .kind(); - if kind != ErrorKind::Interrupted { - assert!( - kind == ErrorKind::WouldBlock || kind == ErrorKind::TimedOut, - "unexpected_error: {:?}", - kind - ); - break; - } - } - assert!(start.elapsed() > Duration::from_millis(400)); -} - -// Ensure the `set_read_timeout` and `set_write_timeout` calls return errors -// when passed zero Durations -#[test] -fn test_timeout_zero_duration() { - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let socket = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool.bind_existing_udp_socket(&socket, &addr)); - - let result = socket.set_write_timeout(Some(Duration::new(0, 0))); - let err = result.unwrap_err(); - assert_eq!(err.kind(), ErrorKind::InvalidInput); - - let result = socket.set_read_timeout(Some(Duration::new(0, 0))); - let err = result.unwrap_err(); - assert_eq!(err.kind(), ErrorKind::InvalidInput); -} - -#[test] -fn connect_send_recv() { - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let socket = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool.bind_existing_udp_socket(&socket, &addr)); - t!(pool.connect_existing_udp_socket(&socket, addr)); - - t!(socket.send(b"hello world")); - - let mut buf = [0; 11]; - t!(socket.recv(&mut buf)); - assert_eq!(b"hello world", &buf[..]); -} - -#[test] -fn connect_send_peek_recv() { - each_ip(&mut |addr, _| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let socket = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool.bind_existing_udp_socket(&socket, &addr)); - t!(pool.connect_existing_udp_socket(&socket, addr)); - - t!(socket.send(b"hello world")); - - for _ in 1..3 { - let mut buf = [0; 11]; - let size = t!(socket.peek(&mut buf)); - assert_eq!(b"hello world", &buf[..]); - assert_eq!(size, 11); - } - - let mut buf = [0; 11]; - let size = t!(socket.recv(&mut buf)); - assert_eq!(b"hello world", &buf[..]); - assert_eq!(size, 11); - }) -} - -#[test] -fn peek_from() { - each_ip(&mut |addr, _| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let socket = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool.bind_existing_udp_socket(&socket, &addr)); - t!(pool.send_to_udp_socket_addr(&socket, b"hello world", &addr)); - - for _ in 1..3 { - let mut buf = [0; 11]; - let (size, _) = t!(socket.peek_from(&mut buf)); - assert_eq!(b"hello world", &buf[..]); - assert_eq!(size, 11); - } - - let mut buf = [0; 11]; - let (size, _) = t!(socket.recv_from(&mut buf)); - assert_eq!(b"hello world", &buf[..]); - assert_eq!(size, 11); - }) -} - -#[test] -fn ttl() { - let ttl = 100; - - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let stream = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool.bind_existing_udp_socket(&stream, &addr)); - - t!(stream.set_ttl(ttl)); - assert_eq!(ttl, t!(stream.ttl())); -} - -#[test] -fn set_nonblocking() { - each_ip(&mut |addr, _| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let socket = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool.bind_existing_udp_socket(&socket, &addr)); - - t!(socket.set_nonblocking(true)); - t!(socket.set_nonblocking(false)); - - t!(pool.connect_existing_udp_socket(&socket, addr)); - - t!(socket.set_nonblocking(false)); - t!(socket.set_nonblocking(true)); - - let mut buf = [0]; - match socket.recv(&mut buf) { - Ok(_) => panic!("expected error"), - Err(ref e) if e.kind() == ErrorKind::WouldBlock => {} - Err(e) => panic!("unexpected error {}", e), - } - }) -} diff --git a/crates/wasi/src/filesystem/primitives/tests/cap_net_ext_udp_split.rs b/crates/wasi/src/filesystem/primitives/tests/cap_net_ext_udp_split.rs deleted file mode 100644 index 916d6f7c6bb9..000000000000 --- a/crates/wasi/src/filesystem/primitives/tests/cap_net_ext_udp_split.rs +++ /dev/null @@ -1,549 +0,0 @@ -// This file is derived from Rust's library/std/src/net/udp/tests.rs at -// revision 377d1a984cd2a53327092b90aa1d8b7e22d1e347. -// -// This is like cap-net-ext-udp.rs but uses the binder/connecter APIs. - -mod net; -mod sys_common; - -use cap_net_ext::{AddressFamily, Blocking, PoolExt, UdpSocketExt}; -use cap_std::ambient_authority; -use cap_std::net::*; -use net::{next_test_ip4, next_test_ip6}; -use std::io::ErrorKind; -use std::sync::mpsc::channel; -//use sys_common::AsInner; -use std::thread; -use std::time::{Duration, Instant}; - -fn each_ip(f: &mut dyn FnMut(SocketAddr, SocketAddr)) { - f(next_test_ip4(), next_test_ip4()); - f(next_test_ip6(), next_test_ip6()); -} - -macro_rules! t { - ($e:expr) => { - match $e { - Ok(t) => t, - Err(e) => panic!("received error for `{}`: {}", stringify!($e), e), - } - }; -} - -#[test] -fn bind_error() { - let mut pool = Pool::new(); - pool.insert_socket_addr("1.1.1.1:9999".parse().unwrap(), ambient_authority()); - - let socket = UdpSocket::new(AddressFamily::Ipv4, Blocking::Yes).unwrap(); - match pool - .udp_binder("1.1.1.1:9999") - .unwrap() - .bind_existing_udp_socket(&socket) - { - Ok(..) => panic!(), - Err(e) => assert_eq!(e.kind(), ErrorKind::AddrNotAvailable), - } -} - -#[test] -fn socket_smoke_test_ip4() { - each_ip(&mut |server_ip, client_ip| { - let mut client_pool = Pool::new(); - client_pool.insert_socket_addr(client_ip, ambient_authority()); - let mut server_pool = Pool::new(); - server_pool.insert_socket_addr(server_ip, ambient_authority()); - - let (tx1, rx1) = channel(); - let (tx2, rx2) = channel(); - - let p = server_pool.clone(); - let _t = thread::spawn(move || { - let client = - UdpSocket::new(AddressFamily::of_socket_addr(client_ip), Blocking::Yes).unwrap(); - t!(client_pool - .udp_binder(&client_ip) - .unwrap() - .bind_existing_udp_socket(&client)); - rx1.recv().unwrap(); - t!(p.send_to_udp_socket_addr(&client, &[99], &server_ip)); - tx2.send(()).unwrap(); - }); - - let server = - UdpSocket::new(AddressFamily::of_socket_addr(server_ip), Blocking::Yes).unwrap(); - t!(server_pool - .udp_binder(&server_ip) - .unwrap() - .bind_existing_udp_socket(&server)); - tx1.send(()).unwrap(); - let mut buf = [0]; - let (nread, src) = t!(server.recv_from(&mut buf)); - assert_eq!(nread, 1); - assert_eq!(buf[0], 99); - assert_eq!(src, client_ip); - rx2.recv().unwrap(); - }) -} - -#[test] -fn socket_name() { - each_ip(&mut |addr, _| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let server = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool - .udp_binder(&addr) - .unwrap() - .bind_existing_udp_socket(&server)); - assert_eq!(addr, t!(server.local_addr())); - }) -} - -#[test] -fn socket_peer() { - each_ip(&mut |addr1, addr2| { - let mut pool1 = Pool::new(); - pool1.insert_socket_addr(addr1, ambient_authority()); - let mut pool2 = Pool::new(); - pool2.insert_socket_addr(addr2, ambient_authority()); - - let server = UdpSocket::new(AddressFamily::of_socket_addr(addr1), Blocking::Yes).unwrap(); - t!(pool1 - .udp_binder(&addr1) - .unwrap() - .bind_existing_udp_socket(&server)); - assert_eq!( - server.peer_addr().unwrap_err().kind(), - ErrorKind::NotConnected - ); - t!(pool2 - .udp_connecter(&addr2) - .unwrap() - .connect_existing_udp_socket(&server)); - assert_eq!(addr2, t!(server.peer_addr())); - }) -} - -#[test] -fn udp_clone_smoke() { - each_ip(&mut |addr1, addr2| { - let mut pool1 = Pool::new(); - pool1.insert_socket_addr(addr1, ambient_authority()); - let mut pool2 = Pool::new(); - pool2.insert_socket_addr(addr2, ambient_authority()); - - let sock1 = UdpSocket::new(AddressFamily::of_socket_addr(addr1), Blocking::Yes).unwrap(); - t!(pool1 - .udp_binder(&addr1) - .unwrap() - .bind_existing_udp_socket(&sock1)); - let sock2 = UdpSocket::new(AddressFamily::of_socket_addr(addr2), Blocking::Yes).unwrap(); - t!(pool2 - .udp_binder(&addr2) - .unwrap() - .bind_existing_udp_socket(&sock2)); - - let _t = thread::spawn(move || { - let mut buf = [0, 0]; - assert_eq!(sock2.recv_from(&mut buf).unwrap(), (1, addr1)); - assert_eq!(buf[0], 1); - t!(pool1.send_to_udp_socket_addr(&sock2, &[2], &addr1)); - }); - - let sock3 = t!(sock1.try_clone()); - - let (tx1, rx1) = channel(); - let (tx2, rx2) = channel(); - let p = pool2.clone(); - let _t = thread::spawn(move || { - rx1.recv().unwrap(); - t!(p.send_to_udp_socket_addr(&sock3, &[1], &addr2)); - tx2.send(()).unwrap(); - }); - tx1.send(()).unwrap(); - let mut buf = [0, 0]; - assert_eq!(sock1.recv_from(&mut buf).unwrap(), (1, addr2)); - rx2.recv().unwrap(); - }) -} - -#[test] -fn udp_clone_two_read() { - each_ip(&mut |addr1, addr2| { - let mut pool1 = Pool::new(); - pool1.insert_socket_addr(addr1, ambient_authority()); - let mut pool2 = Pool::new(); - pool2.insert_socket_addr(addr2, ambient_authority()); - - let sock1 = UdpSocket::new(AddressFamily::of_socket_addr(addr1), Blocking::Yes).unwrap(); - t!(pool1 - .udp_binder(&addr1) - .unwrap() - .bind_existing_udp_socket(&sock1)); - let sock2 = UdpSocket::new(AddressFamily::of_socket_addr(addr2), Blocking::Yes).unwrap(); - t!(pool2 - .udp_binder(&addr2) - .unwrap() - .bind_existing_udp_socket(&sock2)); - let (tx1, rx) = channel(); - let tx2 = tx1.clone(); - - let _t = thread::spawn(move || { - t!(pool1.send_to_udp_socket_addr(&sock2, &[1], &addr1)); - rx.recv().unwrap(); - t!(pool1.send_to_udp_socket_addr(&sock2, &[2], &addr1)); - rx.recv().unwrap(); - }); - - let sock3 = t!(sock1.try_clone()); - - let (done, rx) = channel(); - let _t = thread::spawn(move || { - let mut buf = [0, 0]; - t!(sock3.recv_from(&mut buf)); - tx2.send(()).unwrap(); - done.send(()).unwrap(); - }); - let mut buf = [0, 0]; - t!(sock1.recv_from(&mut buf)); - tx1.send(()).unwrap(); - - rx.recv().unwrap(); - }) -} - -#[test] -fn udp_clone_two_write() { - each_ip(&mut |addr1, addr2| { - let mut pool1 = Pool::new(); - pool1.insert_socket_addr(addr1, ambient_authority()); - let mut pool2 = Pool::new(); - pool2.insert_socket_addr(addr2, ambient_authority()); - - let sock1 = UdpSocket::new(AddressFamily::of_socket_addr(addr1), Blocking::Yes).unwrap(); - t!(pool1 - .udp_binder(&addr1) - .unwrap() - .bind_existing_udp_socket(&sock1)); - let sock2 = UdpSocket::new(AddressFamily::of_socket_addr(addr2), Blocking::Yes).unwrap(); - t!(pool2 - .udp_binder(&addr2) - .unwrap() - .bind_existing_udp_socket(&sock2)); - - let (tx, rx) = channel(); - let (serv_tx, serv_rx) = channel(); - - let _t = thread::spawn(move || { - let mut buf = [0, 1]; - rx.recv().unwrap(); - t!(sock2.recv_from(&mut buf)); - serv_tx.send(()).unwrap(); - }); - - let sock3 = t!(sock1.try_clone()); - - let (done, rx) = channel(); - let tx2 = tx.clone(); - let p = pool2.clone(); - let _t = thread::spawn(move || { - if p.send_to_udp_socket_addr(&sock3, &[1], &addr2).is_ok() { - let _ = tx2.send(()); - } - done.send(()).unwrap(); - }); - if pool2.send_to_udp_socket_addr(&sock1, &[2], &addr2).is_ok() { - let _ = tx.send(()); - } - drop(tx); - - rx.recv().unwrap(); - serv_rx.recv().unwrap(); - }) -} - -/* Disable this test, as it depends on Rust-internal details. -#[test] -fn debug() { - let name = if cfg!(windows) { "socket" } else { "fd" }; - let socket_addr = next_test_ip4(); - - let mut pool = Pool::new(); - - let udpsock_inner = udpsock.0.socket().as_inner(); - let compare = format!("UdpSocket {{ addr: {:?}, {}: {:?} }}", socket_addr, name, udpsock_inner); - assert_eq!(format!("{:?}", udpsock), compare); -} -*/ - -// FIXME: re-enabled openbsd/netbsd tests once their socket timeout code -// no longer has rounding errors. -// VxWorks ignores SO_SNDTIMEO. -#[cfg_attr( - any(target_os = "netbsd", target_os = "openbsd", target_os = "vxworks"), - ignore -)] -#[test] -fn timeouts() { - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let stream = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool - .udp_binder(&addr) - .unwrap() - .bind_existing_udp_socket(&stream)); - let dur = Duration::new(15410, 0); - - assert_eq!(None, t!(stream.read_timeout())); - - t!(stream.set_read_timeout(Some(dur))); - assert_eq!(Some(dur), t!(stream.read_timeout())); - - assert_eq!(None, t!(stream.write_timeout())); - - t!(stream.set_write_timeout(Some(dur))); - assert_eq!(Some(dur), t!(stream.write_timeout())); - - t!(stream.set_read_timeout(None)); - assert_eq!(None, t!(stream.read_timeout())); - - t!(stream.set_write_timeout(None)); - assert_eq!(None, t!(stream.write_timeout())); -} - -#[test] -fn test_read_timeout() { - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let stream = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool - .udp_binder(&addr) - .unwrap() - .bind_existing_udp_socket(&stream)); - t!(stream.set_read_timeout(Some(Duration::from_millis(1000)))); - - let mut buf = [0; 10]; - - let start = Instant::now(); - loop { - let kind = stream - .recv_from(&mut buf) - .err() - .expect("expected error") - .kind(); - if kind != ErrorKind::Interrupted { - assert!( - kind == ErrorKind::WouldBlock || kind == ErrorKind::TimedOut, - "unexpected_error: {:?}", - kind - ); - break; - } - } - assert!(start.elapsed() > Duration::from_millis(400)); -} - -#[test] -fn test_read_with_timeout() { - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let stream = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool - .udp_binder(&addr) - .unwrap() - .bind_existing_udp_socket(&stream)); - t!(stream.set_read_timeout(Some(Duration::from_millis(1000)))); - - t!(pool.send_to_udp_socket_addr(&stream, b"hello world", &addr)); - - let mut buf = [0; 11]; - t!(stream.recv_from(&mut buf)); - assert_eq!(b"hello world", &buf[..]); - - let start = Instant::now(); - loop { - let kind = stream - .recv_from(&mut buf) - .err() - .expect("expected error") - .kind(); - if kind != ErrorKind::Interrupted { - assert!( - kind == ErrorKind::WouldBlock || kind == ErrorKind::TimedOut, - "unexpected_error: {:?}", - kind - ); - break; - } - } - assert!(start.elapsed() > Duration::from_millis(400)); -} - -// Ensure the `set_read_timeout` and `set_write_timeout` calls return errors -// when passed zero Durations -#[test] -fn test_timeout_zero_duration() { - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let socket = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool - .udp_binder(&addr) - .unwrap() - .bind_existing_udp_socket(&socket)); - - let result = socket.set_write_timeout(Some(Duration::new(0, 0))); - let err = result.unwrap_err(); - assert_eq!(err.kind(), ErrorKind::InvalidInput); - - let result = socket.set_read_timeout(Some(Duration::new(0, 0))); - let err = result.unwrap_err(); - assert_eq!(err.kind(), ErrorKind::InvalidInput); -} - -#[test] -fn connect_send_recv() { - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let socket = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool - .udp_binder(&addr) - .unwrap() - .bind_existing_udp_socket(&socket)); - t!(pool - .udp_connecter(addr) - .unwrap() - .connect_existing_udp_socket(&socket)); - - t!(socket.send(b"hello world")); - - let mut buf = [0; 11]; - t!(socket.recv(&mut buf)); - assert_eq!(b"hello world", &buf[..]); -} - -#[test] -fn connect_send_peek_recv() { - each_ip(&mut |addr, _| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let socket = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool - .udp_binder(&addr) - .unwrap() - .bind_existing_udp_socket(&socket)); - t!(pool - .udp_connecter(addr) - .unwrap() - .connect_existing_udp_socket(&socket)); - - t!(socket.send(b"hello world")); - - for _ in 1..3 { - let mut buf = [0; 11]; - let size = t!(socket.peek(&mut buf)); - assert_eq!(b"hello world", &buf[..]); - assert_eq!(size, 11); - } - - let mut buf = [0; 11]; - let size = t!(socket.recv(&mut buf)); - assert_eq!(b"hello world", &buf[..]); - assert_eq!(size, 11); - }) -} - -#[test] -fn peek_from() { - each_ip(&mut |addr, _| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let socket = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool - .udp_binder(&addr) - .unwrap() - .bind_existing_udp_socket(&socket)); - t!(pool.send_to_udp_socket_addr(&socket, b"hello world", &addr)); - - for _ in 1..3 { - let mut buf = [0; 11]; - let (size, _) = t!(socket.peek_from(&mut buf)); - assert_eq!(b"hello world", &buf[..]); - assert_eq!(size, 11); - } - - let mut buf = [0; 11]; - let (size, _) = t!(socket.recv_from(&mut buf)); - assert_eq!(b"hello world", &buf[..]); - assert_eq!(size, 11); - }) -} - -#[test] -fn ttl() { - let ttl = 100; - - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let stream = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool - .udp_binder(&addr) - .unwrap() - .bind_existing_udp_socket(&stream)); - - t!(stream.set_ttl(ttl)); - assert_eq!(ttl, t!(stream.ttl())); -} - -#[test] -fn set_nonblocking() { - each_ip(&mut |addr, _| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let socket = UdpSocket::new(AddressFamily::of_socket_addr(addr), Blocking::Yes).unwrap(); - t!(pool - .udp_binder(&addr) - .unwrap() - .bind_existing_udp_socket(&socket)); - - t!(socket.set_nonblocking(true)); - t!(socket.set_nonblocking(false)); - - t!(pool - .udp_connecter(addr) - .unwrap() - .connect_existing_udp_socket(&socket)); - - t!(socket.set_nonblocking(false)); - t!(socket.set_nonblocking(true)); - - let mut buf = [0]; - match socket.recv(&mut buf) { - Ok(_) => panic!("expected error"), - Err(ref e) if e.kind() == ErrorKind::WouldBlock => {} - Err(e) => panic!("unexpected error {}", e), - } - }) -} diff --git a/crates/wasi/src/filesystem/primitives/tests/fs_utf8.rs b/crates/wasi/src/filesystem/primitives/tests/fs_utf8.rs deleted file mode 100644 index 642569e2db2e..000000000000 --- a/crates/wasi/src/filesystem/primitives/tests/fs_utf8.rs +++ /dev/null @@ -1,1704 +0,0 @@ -// This file is derived from Rust's library/std/src/fs/tests.rs at revision -// e4b1d5841494d6eb7f4944c91a057e16b0f0a9ea. - -#![cfg_attr(io_error_uncategorized, feature(io_error_uncategorized))] -#![cfg(feature = "fs_utf8")] - -#[macro_use] -mod sys_common; -#[macro_use] -mod sys; - -use std::io::prelude::*; - -#[cfg(target_os = "macos")] -use crate::sys::weak::weak; -use camino::{Utf8Path as Path, Utf8PathBuf as PathBuf}; -use cap_std::ambient_authority; -use cap_std::fs_utf8::{self as fs, Dir, OpenOptions}; -#[cfg(target_os = "macos")] -use libc::{c_char, c_int}; -use std::io::{self, ErrorKind, SeekFrom}; -use std::str; -use std::sync::Arc; -#[cfg(not(racy_asserts))] // racy asserts are racy -use std::thread; -use std::time::{Duration, Instant}; -use sys_common::io::{tmpdir_utf8 as tmpdir, TempDirUtf8 as TempDir}; -use sys_common::symlink_junction_utf8 as symlink_junction; - -use rand::rngs::StdRng; -use rand::{RngCore, SeedableRng}; - -#[cfg(not(windows))] -fn symlink_contents, Q: AsRef>( - src: P, - tmpdir: &TempDir, - dst: Q, -) -> io::Result<()> { - tmpdir.symlink_contents(src, dst) -} -#[cfg(not(windows))] -fn symlink_dir, Q: AsRef>(src: P, tmpdir: &TempDir, dst: Q) -> io::Result<()> { - tmpdir.symlink(src, dst) -} -#[cfg(not(windows))] -fn symlink_file, Q: AsRef>( - src: P, - tmpdir: &TempDir, - dst: Q, -) -> io::Result<()> { - tmpdir.symlink(src, dst) -} -#[cfg(windows)] -fn symlink_dir, Q: AsRef>(src: P, tmpdir: &TempDir, dst: Q) -> io::Result<()> { - tmpdir.symlink_dir(src, dst) -} -#[cfg(windows)] -fn symlink_file, Q: AsRef>( - src: P, - tmpdir: &TempDir, - dst: Q, -) -> io::Result<()> { - tmpdir.symlink_file(src, dst) -} - -// Several test fail on windows if the user does not have permission to -// create symlinks (the `SeCreateSymbolicLinkPrivilege`). Instead of -// disabling these test on Windows, use this function to test whether we -// have permission, and return otherwise. This way, we still don't run these -// tests most of the time, but at least we do if the user has the right -// permissions. -pub fn got_symlink_permission(tmpdir: &TempDir) -> bool { - if cfg!(unix) { - return true; - } - let link = "some_hopefully_unique_link_name"; - - match symlink_file(r"nonexisting_target", tmpdir, link) { - // ERROR_PRIVILEGE_NOT_HELD = 1314 - Err(ref err) if err.raw_os_error() == Some(1314) => false, - Ok(_) | Err(_) => true, - } -} - -#[cfg(target_os = "macos")] -fn able_to_not_follow_symlinks_while_hard_linking() -> bool { - weak!(fn linkat(c_int, *const c_char, c_int, *const c_char, c_int) -> c_int); - linkat.get().is_some() -} - -#[cfg(not(target_os = "macos"))] -fn able_to_not_follow_symlinks_while_hard_linking() -> bool { - return true; -} - -#[test] -fn file_test_io_smoke_test() { - let message = "it's alright. have a good time"; - let tmpdir = tmpdir(); - let filename = "file_rt_io_file_test.txt"; - { - let mut write_stream = check!(tmpdir.create(filename)); - check!(write_stream.write(message.as_bytes())); - } - { - let mut read_stream = check!(tmpdir.open(filename)); - let mut read_buf = [0; 1028]; - let read_str = match check!(read_stream.read(&mut read_buf)) { - 0 => panic!("shouldn't happen"), - n => str::from_utf8(&read_buf[..n]).unwrap().to_string(), - }; - assert_eq!(read_str, message); - } - check!(tmpdir.remove_file(filename)); -} - -#[test] -fn invalid_path_raises() { - let tmpdir = tmpdir(); - let filename = "file_that_does_not_exist.txt"; - let result = tmpdir.open(filename); - - #[cfg(any(all(unix, not(target_os = "vxworks")), target_os = "wasi"))] - error!(result, "No such file or directory"); - #[cfg(target_os = "vxworks")] - error!(result, "no such file or directory"); - #[cfg(windows)] - error!(result, 2); // ERROR_FILE_NOT_FOUND -} - -#[test] -fn file_test_iounlinking_invalid_path_should_raise_condition() { - let tmpdir = tmpdir(); - let filename = "file_another_file_that_does_not_exist.txt"; - - let result = tmpdir.remove_file(filename); - - #[cfg(any(all(unix, not(target_os = "vxworks")), target_os = "wasi"))] - error!(result, "No such file or directory"); - #[cfg(target_os = "vxworks")] - error!(result, "no such file or directory"); - #[cfg(windows)] - error!(result, 2); // ERROR_FILE_NOT_FOUND -} - -#[test] -fn file_test_io_non_positional_read() { - let message: &str = "ten-four"; - let mut read_mem = [0; 8]; - let tmpdir = tmpdir(); - let filename = "file_rt_io_file_test_positional.txt"; - { - let mut rw_stream = check!(tmpdir.create(filename)); - check!(rw_stream.write(message.as_bytes())); - } - { - let mut read_stream = check!(tmpdir.open(filename)); - { - let read_buf = &mut read_mem[0..4]; - check!(read_stream.read(read_buf)); - } - { - let read_buf = &mut read_mem[4..8]; - check!(read_stream.read(read_buf)); - } - } - check!(tmpdir.remove_file(filename)); - let read_str = str::from_utf8(&read_mem).unwrap(); - assert_eq!(read_str, message); -} - -#[test] -fn file_test_io_seek_and_tell_smoke_test() { - let message = "ten-four"; - let mut read_mem = [0; 4]; - let set_cursor = 4 as u64; - let tell_pos_pre_read; - let tell_pos_post_read; - let tmpdir = tmpdir(); - let filename = "file_rt_io_file_test_seeking.txt"; - { - let mut rw_stream = check!(tmpdir.create(filename)); - check!(rw_stream.write(message.as_bytes())); - } - { - let mut read_stream = check!(tmpdir.open(filename)); - check!(read_stream.seek(SeekFrom::Start(set_cursor))); - tell_pos_pre_read = check!(read_stream.seek(SeekFrom::Current(0))); - check!(read_stream.read(&mut read_mem)); - tell_pos_post_read = check!(read_stream.seek(SeekFrom::Current(0))); - } - check!(tmpdir.remove_file(filename)); - let read_str = str::from_utf8(&read_mem).unwrap(); - assert_eq!(read_str, &message[4..8]); - assert_eq!(tell_pos_pre_read, set_cursor); - assert_eq!(tell_pos_post_read, message.len() as u64); -} - -#[test] -fn file_test_io_seek_and_write() { - let initial_msg = "food-is-yummy"; - let overwrite_msg = "-the-bar!!"; - let final_msg = "foo-the-bar!!"; - let seek_idx = 3; - let mut read_mem = [0; 13]; - let tmpdir = tmpdir(); - let filename = "file_rt_io_file_test_seek_and_write.txt"; - { - let mut rw_stream = check!(tmpdir.create(filename)); - check!(rw_stream.write(initial_msg.as_bytes())); - check!(rw_stream.seek(SeekFrom::Start(seek_idx))); - check!(rw_stream.write(overwrite_msg.as_bytes())); - } - { - let mut read_stream = check!(tmpdir.open(filename)); - check!(read_stream.read(&mut read_mem)); - } - check!(tmpdir.remove_file(filename)); - let read_str = str::from_utf8(&read_mem).unwrap(); - assert!(read_str == final_msg); -} - -#[test] -fn file_test_io_seek_shakedown() { - // 01234567890123 - let initial_msg = "qwer-asdf-zxcv"; - let chunk_one: &str = "qwer"; - let chunk_two: &str = "asdf"; - let chunk_three: &str = "zxcv"; - let mut read_mem = [0; 4]; - let tmpdir = tmpdir(); - let filename = "file_rt_io_file_test_seek_shakedown.txt"; - { - let mut rw_stream = check!(tmpdir.create(filename)); - check!(rw_stream.write(initial_msg.as_bytes())); - } - { - let mut read_stream = check!(tmpdir.open(filename)); - - check!(read_stream.seek(SeekFrom::End(-4))); - check!(read_stream.read(&mut read_mem)); - assert_eq!(str::from_utf8(&read_mem).unwrap(), chunk_three); - - check!(read_stream.seek(SeekFrom::Current(-9))); - check!(read_stream.read(&mut read_mem)); - assert_eq!(str::from_utf8(&read_mem).unwrap(), chunk_two); - - check!(read_stream.seek(SeekFrom::Start(0))); - check!(read_stream.read(&mut read_mem)); - assert_eq!(str::from_utf8(&read_mem).unwrap(), chunk_one); - } - check!(tmpdir.remove_file(filename)); -} - -#[test] -fn file_test_io_eof() { - let tmpdir = tmpdir(); - let filename = "file_rt_io_file_test_eof.txt"; - let mut buf = [0; 256]; - { - let oo = OpenOptions::new() - .create_new(true) - .write(true) - .read(true) - .clone(); - let mut rw = check!(tmpdir.open_with(filename, &oo)); - assert_eq!(check!(rw.read(&mut buf)), 0); - assert_eq!(check!(rw.read(&mut buf)), 0); - } - check!(tmpdir.remove_file(filename)); -} - -#[test] -#[cfg(unix)] -fn file_test_io_read_write_at() { - use cap_std::fs_utf8::FileExt; - - let tmpdir = tmpdir(); - let filename = "file_rt_io_file_test_read_write_at.txt"; - let mut buf = [0; 256]; - let write1 = "asdf"; - let write2 = "qwer-"; - let write3 = "-zxcv"; - let content = "qwer-asdf-zxcv"; - { - let oo = OpenOptions::new() - .create_new(true) - .write(true) - .read(true) - .clone(); - let mut rw = check!(tmpdir.open_with(filename, &oo)); - assert_eq!(check!(rw.write_at(write1.as_bytes(), 5)), write1.len()); - assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 0); - assert_eq!(check!(rw.read_at(&mut buf, 5)), write1.len()); - assert_eq!(str::from_utf8(&buf[..write1.len()]), Ok(write1)); - assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 0); - assert_eq!( - check!(rw.read_at(&mut buf[..write2.len()], 0)), - write2.len() - ); - assert_eq!(str::from_utf8(&buf[..write2.len()]), Ok("\0\0\0\0\0")); - assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 0); - assert_eq!(check!(rw.write(write2.as_bytes())), write2.len()); - assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 5); - assert_eq!(check!(rw.read(&mut buf)), write1.len()); - assert_eq!(str::from_utf8(&buf[..write1.len()]), Ok(write1)); - assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9); - assert_eq!( - check!(rw.read_at(&mut buf[..write2.len()], 0)), - write2.len() - ); - assert_eq!(str::from_utf8(&buf[..write2.len()]), Ok(write2)); - assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9); - assert_eq!(check!(rw.write_at(write3.as_bytes(), 9)), write3.len()); - assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9); - } - { - let mut read = check!(tmpdir.open(filename)); - assert_eq!(check!(read.read_at(&mut buf, 0)), content.len()); - assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content)); - assert_eq!(check!(read.seek(SeekFrom::Current(0))), 0); - assert_eq!(check!(read.seek(SeekFrom::End(-5))), 9); - assert_eq!(check!(read.read_at(&mut buf, 0)), content.len()); - assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content)); - assert_eq!(check!(read.seek(SeekFrom::Current(0))), 9); - assert_eq!(check!(read.read(&mut buf)), write3.len()); - assert_eq!(str::from_utf8(&buf[..write3.len()]), Ok(write3)); - assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14); - assert_eq!(check!(read.read_at(&mut buf, 0)), content.len()); - assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content)); - assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14); - assert_eq!(check!(read.read_at(&mut buf, 14)), 0); - assert_eq!(check!(read.read_at(&mut buf, 15)), 0); - assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14); - } - check!(tmpdir.remove_file(filename)); -} - -// Darwin doesn't have a way to change the permissions on a file, relative -// to a directory handle, with no read or write access, without blindly -// following symlinks. -#[test] -#[cfg(unix)] -#[cfg_attr( - any( - target_os = "macos", - target_os = "ios", - target_os = "tvos", - target_os = "watchos", - target_os = "visionos", - ), - ignore -)] -fn set_get_unix_permissions() { - use cap_std::fs::PermissionsExt; - - let tmpdir = tmpdir(); - let filename = "set_get_unix_permissions"; - check!(tmpdir.create_dir(filename)); - let mask = 0o7777; - - check!(tmpdir.set_permissions(filename, fs::Permissions::from_mode(0))); - let metadata0 = check!(tmpdir.metadata(filename)); - assert_eq!(mask & metadata0.permissions().mode(), 0); - - check!(tmpdir.set_permissions(filename, fs::Permissions::from_mode(0o1777))); - let metadata1 = check!(tmpdir.metadata(filename)); - #[cfg(any(all(unix, not(target_os = "vxworks")), target_os = "wasi"))] - assert_eq!(mask & metadata1.permissions().mode(), 0o1777); - #[cfg(target_os = "vxworks")] - assert_eq!(mask & metadata1.permissions().mode(), 0o0777); -} - -#[test] -#[cfg(windows)] -fn file_test_io_seek_read_write() { - use cap_std::fs_utf8::FileExt; - - let tmpdir = tmpdir(); - let filename = "file_rt_io_file_test_seek_read_write.txt"; - let mut buf = [0; 256]; - let write1 = "asdf"; - let write2 = "qwer-"; - let write3 = "-zxcv"; - let content = "qwer-asdf-zxcv"; - { - let oo = OpenOptions::new() - .create_new(true) - .write(true) - .read(true) - .clone(); - let mut rw = check!(tmpdir.open_with(filename, &oo)); - assert_eq!(check!(rw.seek_write(write1.as_bytes(), 5)), write1.len()); - assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9); - assert_eq!(check!(rw.seek_read(&mut buf, 5)), write1.len()); - assert_eq!(str::from_utf8(&buf[..write1.len()]), Ok(write1)); - assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9); - assert_eq!(check!(rw.seek(SeekFrom::Start(0))), 0); - assert_eq!(check!(rw.write(write2.as_bytes())), write2.len()); - assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 5); - assert_eq!(check!(rw.read(&mut buf)), write1.len()); - assert_eq!(str::from_utf8(&buf[..write1.len()]), Ok(write1)); - assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9); - assert_eq!( - check!(rw.seek_read(&mut buf[..write2.len()], 0)), - write2.len() - ); - assert_eq!(str::from_utf8(&buf[..write2.len()]), Ok(write2)); - assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 5); - assert_eq!(check!(rw.seek_write(write3.as_bytes(), 9)), write3.len()); - assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 14); - } - { - let mut read = check!(tmpdir.open(filename)); - assert_eq!(check!(read.seek_read(&mut buf, 0)), content.len()); - assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content)); - assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14); - assert_eq!(check!(read.seek(SeekFrom::End(-5))), 9); - assert_eq!(check!(read.seek_read(&mut buf, 0)), content.len()); - assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content)); - assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14); - assert_eq!(check!(read.seek(SeekFrom::End(-5))), 9); - assert_eq!(check!(read.read(&mut buf)), write3.len()); - assert_eq!(str::from_utf8(&buf[..write3.len()]), Ok(write3)); - assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14); - assert_eq!(check!(read.seek_read(&mut buf, 0)), content.len()); - assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content)); - assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14); - assert_eq!(check!(read.seek_read(&mut buf, 14)), 0); - assert_eq!(check!(read.seek_read(&mut buf, 15)), 0); - } - check!(tmpdir.remove_file(filename)); -} - -#[test] -fn file_test_stat_is_correct_on_is_file() { - let tmpdir = tmpdir(); - let filename = "file_stat_correct_on_is_file.txt"; - { - let mut opts = OpenOptions::new(); - let mut fs = check!(tmpdir.open_with(filename, opts.read(true).write(true).create(true))); - let msg = "hw"; - fs.write(msg.as_bytes()).unwrap(); - - let fstat_res = check!(fs.metadata()); - assert!(fstat_res.is_file()); - } - let stat_res_fn = check!(tmpdir.metadata(filename)); - assert!(stat_res_fn.is_file()); - check!(tmpdir.remove_file(filename)); -} - -#[test] -fn file_test_stat_is_correct_on_is_dir() { - let tmpdir = tmpdir(); - let filename = "file_stat_correct_on_is_dir"; - check!(tmpdir.create_dir(filename)); - let stat_res_fn = check!(tmpdir.metadata(filename)); - assert!(stat_res_fn.is_dir()); - check!(tmpdir.remove_dir(filename)); -} - -#[test] -fn file_test_fileinfo_false_when_checking_is_file_on_a_directory() { - let tmpdir = tmpdir(); - let dir = "fileinfo_false_on_dir"; - check!(tmpdir.create_dir(dir)); - assert!(!tmpdir.is_file(dir)); - check!(tmpdir.remove_dir(dir)); -} - -#[test] -fn file_test_fileinfo_check_exists_before_and_after_file_creation() { - let tmpdir = tmpdir(); - let file = "fileinfo_check_exists_b_and_a.txt"; - check!(check!(tmpdir.create(file)).write(b"foo")); - assert!(tmpdir.exists(file)); - assert!(tmpdir.try_exists(file).unwrap()); - check!(tmpdir.remove_file(file)); - assert!(!tmpdir.exists(file)); - assert!(!tmpdir.try_exists(file).unwrap()); -} - -#[test] -fn file_test_directoryinfo_check_exists_before_and_after_mkdir() { - let tmpdir = tmpdir(); - let dir = "before_and_after_dir"; - assert!(!tmpdir.exists(dir)); - check!(tmpdir.create_dir(dir)); - assert!(tmpdir.exists(dir)); - assert!(tmpdir.is_dir(dir)); - check!(tmpdir.remove_dir(dir)); - assert!(!tmpdir.exists(dir)); -} - -#[test] -fn file_test_directoryinfo_readdir() { - let tmpdir = tmpdir(); - let dir = "di_readdir"; - check!(tmpdir.create_dir(dir)); - let prefix = "foo"; - for n in 0..3 { - let f = format!("{}.txt", n); - let mut w = check!(tmpdir.create(&f)); - let msg_str = format!("{}{}", prefix, n.to_string()); - let msg = msg_str.as_bytes(); - check!(w.write(msg)); - } - let files = check!(tmpdir.read_dir(dir)); - let mut mem = [0; 4]; - for f in files { - let f = f.unwrap().file_name().unwrap(); - { - check!(check!(tmpdir.open(&f)).read(&mut mem)); - let read_str = str::from_utf8(&mem).unwrap(); - let expected = format!("{}{}", prefix, f.as_str()); - assert_eq!(expected, read_str); - } - check!(tmpdir.remove_file(&f)); - } - check!(tmpdir.remove_dir(dir)); -} - -#[test] -fn file_create_new_already_exists_error() { - let tmpdir = tmpdir(); - let file = "file_create_new_error_exists"; - check!(tmpdir.create(file)); - let e = tmpdir - .open_with(file, &fs::OpenOptions::new().write(true).create_new(true)) - .unwrap_err(); - assert_eq!(e.kind(), ErrorKind::AlreadyExists); -} - -#[test] -fn mkdir_path_already_exists_error() { - let tmpdir = tmpdir(); - let dir = "mkdir_error_twice"; - check!(tmpdir.create_dir(dir)); - let e = tmpdir.create_dir(dir).unwrap_err(); - assert_eq!(e.kind(), ErrorKind::AlreadyExists); -} - -#[test] -fn recursive_mkdir() { - let tmpdir = tmpdir(); - let dir = "d1/d2"; - check!(tmpdir.create_dir_all(dir)); - assert!(tmpdir.is_dir(dir)); -} - -#[test] -fn recursive_mkdir_failure() { - let tmpdir = tmpdir(); - let dir = "d1"; - let file = "f1"; - - check!(tmpdir.create_dir_all(&dir)); - check!(tmpdir.create(&file)); - - let result = tmpdir.create_dir_all(&file); - - assert!(result.is_err()); -} - -#[test] -#[cfg(not(racy_asserts))] // racy asserts are racy -fn concurrent_recursive_mkdir() { - for _ in 0..100 { - let dir = tmpdir(); - let mut name = PathBuf::from("a"); - for _ in 0..40 { - name = name.join("a"); - } - let mut join = vec![]; - for _ in 0..8 { - let dir = check!(dir.try_clone()); - let name = name.clone(); - join.push(thread::spawn(move || { - check!(dir.create_dir_all(&name)); - })) - } - - // No `Display` on result of `join()` - join.drain(..).map(|join| join.join().unwrap()).count(); - } -} - -#[test] -fn recursive_mkdir_slash() { - let tmpdir = tmpdir(); - error_contains!( - tmpdir.create_dir_all(Path::new("/")), - "a path led outside of the filesystem" - ); -} - -#[test] -fn recursive_mkdir_dot() { - let tmpdir = tmpdir(); - check!(tmpdir.create_dir_all(Path::new("."))); -} - -#[test] -fn recursive_mkdir_empty() { - let tmpdir = tmpdir(); - check!(tmpdir.create_dir_all(Path::new(""))); -} - -#[test] -fn recursive_rmdir() { - let tmpdir = tmpdir(); - let d1 = PathBuf::from("d1"); - let dt = d1.join("t"); - let dtt = dt.join("t"); - let d2 = PathBuf::from("d2"); - let canary = d2.join("do_not_delete"); - check!(tmpdir.create_dir_all(&dtt)); - check!(tmpdir.create_dir_all(&d2)); - check!(check!(tmpdir.create(&canary)).write(b"foo")); - check!(symlink_junction(d2, &tmpdir, &dt.join("d2"))); - let _ = symlink_file(&canary, &tmpdir, &d1.join("canary")); - check!(tmpdir.remove_dir_all(&d1)); - - assert!(!tmpdir.is_dir(d1)); - assert!(tmpdir.exists(canary)); -} - -// See the comments on `create_dir_all_with_junctions` in tests/fs.rs about Windows. -#[cfg_attr(windows, ignore)] -#[test] -fn recursive_rmdir_of_symlink() { - // test we do not recursively delete a symlink but only dirs. - let tmpdir = tmpdir(); - let link = "d1"; - let dir = "d2"; - let canary = "do_not_delete"; - check!(tmpdir.create_dir_all(&dir)); - check!(check!(tmpdir.create(&canary)).write(b"foo")); - check!(symlink_junction(&dir, &tmpdir, link)); - check!(tmpdir.remove_dir_all(link)); - - assert!(!tmpdir.is_dir(link)); - assert!(tmpdir.exists(canary)); -} - -#[test] -fn recursive_rmdir_of_file_fails() { - // test we do not delete a directly specified file. - let tmpdir = tmpdir(); - let canary = "do_not_delete"; - check!(check!(tmpdir.create(canary)).write(b"foo")); - let result = tmpdir.remove_dir_all(canary); - #[cfg(unix)] - error!(result, "Not a directory"); - #[cfg(windows)] - error!(result, 267); // ERROR_DIRECTORY - The directory name is invalid. - assert!(result.is_err()); - assert!(tmpdir.exists(canary)); -} - -#[test] -// only Windows makes a distinction between file and directory symlinks. -#[cfg(windows)] -fn recursive_rmdir_of_file_symlink() { - let tmpdir = tmpdir(); - if !got_symlink_permission(&tmpdir) { - return; - }; - - let f1 = "f1"; - let f2 = "f2"; - check!(check!(tmpdir.create(&f1)).write(b"foo")); - check!(symlink_file(&f1, &tmpdir, &f2)); - match tmpdir.remove_dir_all(&f2) { - Ok(..) => panic!("wanted a failure"), - Err(..) => {} - } -} - -#[test] -#[ignore] // takes too much time -fn recursive_rmdir_toctou() { - // Test for time-of-check to time-of-use issues. - // - // Scenario: - // The attacker wants to get directory contents deleted, to which he does not - // have access. He has a way to get a privileged Rust binary call - // `std::fs::remove_dir_all()` on a directory he controls, e.g. in his home - // directory. - // - // The POC sets up the `attack_dest/attack_file` which the attacker wants to - // have deleted. The attacker repeatedly creates a directory and replaces - // it with a symlink from `victim_del` to `attack_dest` while the victim - // code calls `std::fs::remove_dir_all()` on `victim_del`. After a few - // seconds the attack has succeeded and `attack_dest/attack_file` is - // deleted. - let tmpdir = tmpdir(); - let victim_del_path = "victim_del"; - - // setup dest - let attack_dest_dir = "attack_dest"; - let attack_dest_dir = Path::new(attack_dest_dir); - tmpdir.create_dir(attack_dest_dir).unwrap(); - let attack_dest_file = "attack_dest/attack_file"; - tmpdir.create(attack_dest_file).unwrap(); - - let drop_canary_arc = Arc::new(()); - let drop_canary_weak = Arc::downgrade(&drop_canary_arc); - - eprintln!("x: {:?}", &victim_del_path); - - // victim just continuously removes `victim_del` - let tmpdir_clone = tmpdir.try_clone().unwrap(); - let _t = thread::spawn(move || { - while drop_canary_weak.upgrade().is_some() { - let _ = tmpdir_clone.remove_dir_all(victim_del_path); - } - }); - - // attacker (could of course be in a separate process) - let start_time = Instant::now(); - while Instant::now().duration_since(start_time) < Duration::from_secs(1000) { - if !tmpdir.exists(attack_dest_file) { - panic!( - "Victim deleted symlinked file outside of victim_del. Attack succeeded in {:?}.", - Instant::now().duration_since(start_time) - ); - } - let _ = tmpdir.create_dir(victim_del_path); - let _ = tmpdir.remove_dir(victim_del_path); - let _ = symlink_dir(attack_dest_dir, &tmpdir, victim_del_path); - } -} - -#[test] -fn unicode_path_is_dir() { - let tmpdir = tmpdir(); - - assert!(tmpdir.is_dir(Path::new("."))); - assert!(!tmpdir.is_dir(Path::new("test/stdtest/fs.rs"))); - - let mut dirpath = PathBuf::new(); - dirpath.push("test-가一ー你好"); - check!(tmpdir.create_dir(&dirpath)); - assert!(tmpdir.is_dir(&dirpath)); - - let mut filepath = dirpath; - filepath.push("unicode-file-\u{ac00}\u{4e00}\u{30fc}\u{4f60}\u{597d}.rs"); - check!(tmpdir.create(&filepath)); // ignore return; touch only - assert!(!tmpdir.is_dir(&filepath)); - assert!(tmpdir.exists(filepath)); -} - -#[test] -fn unicode_path_exists() { - let tmpdir = tmpdir(); - - assert!(tmpdir.exists(Path::new("."))); - assert!(!tmpdir.exists(Path::new("test/nonexistent-bogus-path"))); - - let unicode = PathBuf::new(); - let unicode = unicode.join("test-각丁ー再见"); - check!(tmpdir.create_dir(&unicode)); - assert!(tmpdir.exists(unicode)); - assert!(!tmpdir.exists(Path::new("test/unicode-bogus-path-각丁ー再见"))); -} - -#[test] -fn copy_file_does_not_exist() { - let tmpdir = tmpdir(); - let from = Path::new("test/nonexistent-bogus-path"); - let to = Path::new("test/other-bogus-path"); - - match tmpdir.copy(&from, &tmpdir, &to) { - Ok(..) => panic!(), - Err(..) => { - assert!(!tmpdir.exists(from)); - assert!(!tmpdir.exists(to)); - } - } -} - -#[test] -fn copy_src_does_not_exist() { - let tmpdir = tmpdir(); - let from = Path::new("test/nonexistent-bogus-path"); - let to = "out.txt"; - check!(check!(tmpdir.create(&to)).write(b"hello")); - assert!(tmpdir.copy(&from, &tmpdir, &to).is_err()); - assert!(!tmpdir.exists(from)); - let mut v = Vec::new(); - check!(check!(tmpdir.open(&to)).read_to_end(&mut v)); - assert_eq!(v, b"hello"); -} - -#[test] -fn copy_file_ok() { - let tmpdir = tmpdir(); - let input = "in.txt"; - let out = "out.txt"; - - check!(check!(tmpdir.create(&input)).write(b"hello")); - check!(tmpdir.copy(&input, &tmpdir, &out)); - let mut v = Vec::new(); - check!(check!(tmpdir.open(&out)).read_to_end(&mut v)); - assert_eq!(v, b"hello"); - - assert_eq!( - check!(tmpdir.metadata(input)).permissions(), - check!(tmpdir.metadata(out)).permissions() - ); -} - -#[test] -fn copy_file_dst_dir() { - let tmpdir = tmpdir(); - let out = "out"; - - check!(tmpdir.create(&out)); - match tmpdir.copy(&*out, &tmpdir, ".") { - Ok(..) => panic!(), - Err(..) => {} - } -} - -#[test] -fn copy_file_dst_exists() { - let tmpdir = tmpdir(); - let input = "in"; - let output = "out"; - - check!(check!(tmpdir.create(&input)).write("foo".as_bytes())); - check!(check!(tmpdir.create(&output)).write("bar".as_bytes())); - check!(tmpdir.copy(&input, &tmpdir, &output)); - - let mut v = Vec::new(); - check!(check!(tmpdir.open(&output)).read_to_end(&mut v)); - assert_eq!(v, b"foo".to_vec()); -} - -#[test] -fn copy_file_src_dir() { - let tmpdir = tmpdir(); - let out = "out"; - - match tmpdir.copy(".", &tmpdir, &out) { - Ok(..) => panic!(), - Err(..) => {} - } - assert!(!tmpdir.exists(out)); -} - -#[test] -fn copy_file_preserves_perm_bits() { - let tmpdir = tmpdir(); - let input = "in.txt"; - let out = "out.txt"; - - let attr = check!(check!(tmpdir.create(&input)).metadata()); - let mut p = attr.permissions(); - p.set_readonly(true); - check!(tmpdir.set_permissions(&input, p)); - check!(tmpdir.copy(&input, &tmpdir, &out)); - assert!(check!(tmpdir.metadata(out)).permissions().readonly()); - check!(tmpdir.set_permissions(&input, attr.permissions())); - check!(tmpdir.set_permissions(&out, attr.permissions())); -} - -#[test] -#[cfg(windows)] -fn copy_file_preserves_streams() { - let tmp = tmpdir(); - check!(check!(tmp.create("in.txt:bunny")).write("carrot".as_bytes())); - assert_eq!(check!(tmp.copy("in.txt", &tmp, "out.txt")), 0); - assert_eq!(check!(tmp.metadata("out.txt")).len(), 0); - let mut v = Vec::new(); - check!(check!(tmp.open("out.txt:bunny")).read_to_end(&mut v)); - assert_eq!(v, b"carrot".to_vec()); -} - -#[test] -fn copy_file_returns_metadata_len() { - let tmp = tmpdir(); - let in_path = "in.txt"; - let out_path = "out.txt"; - check!(check!(tmp.create(&in_path)).write(b"lettuce")); - #[cfg(windows)] - check!(check!(tmp.create("in.txt:bunny")).write(b"carrot")); - let copied_len = check!(tmp.copy(&in_path, &tmp, &out_path)); - assert_eq!(check!(tmp.metadata(out_path)).len(), copied_len); -} - -#[test] -fn copy_file_follows_dst_symlink() { - let tmp = tmpdir(); - if !got_symlink_permission(&tmp) { - return; - }; - - let in_path = "in.txt"; - let out_path = "out.txt"; - let out_path_symlink = "out_symlink.txt"; - - check!(tmp.write(&in_path, "foo")); - check!(tmp.write(&out_path, "bar")); - check!(symlink_file(&out_path, &tmp, &out_path_symlink)); - - check!(tmp.copy(&in_path, &tmp, &out_path_symlink)); - - assert!(check!(tmp.symlink_metadata(out_path_symlink)) - .file_type() - .is_symlink()); - assert_eq!(check!(tmp.read(&out_path_symlink)), b"foo".to_vec()); - assert_eq!(check!(tmp.read(&out_path)), b"foo".to_vec()); -} - -#[test] -fn symlinks_work() { - let tmpdir = tmpdir(); - if !got_symlink_permission(&tmpdir) { - return; - }; - - let input = "in.txt"; - let out = "out.txt"; - - check!(check!(tmpdir.create(&input)).write("foobar".as_bytes())); - check!(symlink_file(&input, &tmpdir, &out)); - assert!(check!(tmpdir.symlink_metadata(out)) - .file_type() - .is_symlink()); - assert_eq!( - check!(tmpdir.metadata(&out)).len(), - check!(tmpdir.metadata(&input)).len() - ); - let mut v = Vec::new(); - check!(check!(tmpdir.open(&out)).read_to_end(&mut v)); - assert_eq!(v, b"foobar".to_vec()); -} - -#[test] -fn symlink_noexist() { - // Symlinks can point to things that don't exist - let tmpdir = tmpdir(); - if !got_symlink_permission(&tmpdir) { - return; - }; - - // Use a relative path for testing. Symlinks get normalized by Windows, - // so we might not get the same path back for absolute paths - check!(symlink_file(&"foo", &tmpdir, "bar")); - assert_eq!(check!(tmpdir.read_link("bar")).as_str(), "foo"); -} - -#[test] -fn read_link() { - if cfg!(windows) { - // directory symlink - let root = Dir::open_ambient_dir(r"C:\", ambient_authority()).unwrap(); - error_contains!( - root.read_link(r"Users\All Users"), - "a path led outside of the filesystem" - ); - // junction - error_contains!( - root.read_link(r"Users\Default User"), - "a path led outside of the filesystem" - ); - // junction with special permissions - error_contains!( - root.read_link(r"Documents and Settings\"), - "a path led outside of the filesystem" - ); - } - let tmpdir = tmpdir(); - let link = "link"; - if !got_symlink_permission(&tmpdir) { - return; - }; - check!(symlink_file(&"foo", &tmpdir, &link)); - assert_eq!(check!(tmpdir.read_link(&link)).as_str(), "foo"); -} - -#[test] -fn readlink_not_symlink() { - let tmpdir = tmpdir(); - match tmpdir.read_link(".") { - Ok(..) => panic!("wanted a failure"), - Err(..) => {} - } -} - -#[cfg(not(windows))] -#[test] -fn read_link_contents() { - let tmpdir = tmpdir(); - let link = "link"; - if !got_symlink_permission(&tmpdir) { - return; - }; - check!(symlink_file(&"foo", &tmpdir, &link)); - assert_eq!(check!(tmpdir.read_link_contents(&link)).as_str(), "foo"); -} - -#[cfg(not(windows))] -#[test] -fn read_link_contents_absolute() { - let tmpdir = tmpdir(); - let link = "link"; - if !got_symlink_permission(&tmpdir) { - return; - }; - check!(symlink_contents(&"/foo", &tmpdir, &link)); - assert_eq!(check!(tmpdir.read_link_contents(&link)).as_str(), "/foo"); -} - -#[test] -fn links_work() { - let tmpdir = tmpdir(); - let input = "in.txt"; - let out = "out.txt"; - - check!(check!(tmpdir.create(&input)).write("foobar".as_bytes())); - check!(tmpdir.hard_link(&input, &tmpdir, &out)); - assert_eq!( - check!(tmpdir.metadata(&out)).len(), - check!(tmpdir.metadata(&input)).len() - ); - assert_eq!( - check!(tmpdir.metadata(&out)).len(), - check!(tmpdir.metadata(input)).len() - ); - let mut v = Vec::new(); - check!(check!(tmpdir.open(&out)).read_to_end(&mut v)); - assert_eq!(v, b"foobar".to_vec()); - - // can't link to yourself - match tmpdir.hard_link(&input, &tmpdir, &input) { - Ok(..) => panic!("wanted a failure"), - Err(..) => {} - } - // can't link to something that doesn't exist - match tmpdir.hard_link("foo", &tmpdir, "bar") { - Ok(..) => panic!("wanted a failure"), - Err(..) => {} - } -} - -#[test] -fn chmod_works() { - let tmpdir = tmpdir(); - let file = "in.txt"; - - check!(tmpdir.create(&file)); - let attr = check!(tmpdir.metadata(&file)); - assert!(!attr.permissions().readonly()); - let mut p = attr.permissions(); - p.set_readonly(true); - check!(tmpdir.set_permissions(&file, p.clone())); - let attr = check!(tmpdir.metadata(&file)); - assert!(attr.permissions().readonly()); - - match tmpdir.set_permissions("foo", p.clone()) { - Ok(..) => panic!("wanted an error"), - Err(..) => {} - } - - p.set_readonly(false); - check!(tmpdir.set_permissions(&file, p)); -} - -#[test] -fn fchmod_works() { - let tmpdir = tmpdir(); - let path = "in.txt"; - - let file = check!(tmpdir.create(&path)); - let attr = check!(tmpdir.metadata(&path)); - assert!(!attr.permissions().readonly()); - let mut p = attr.permissions(); - p.set_readonly(true); - check!(file.set_permissions(p.clone())); - let attr = check!(tmpdir.metadata(&path)); - assert!(attr.permissions().readonly()); - - p.set_readonly(false); - check!(file.set_permissions(p)); -} - -#[test] -fn sync_doesnt_kill_anything() { - let tmpdir = tmpdir(); - let path = "in.txt"; - - let mut file = check!(tmpdir.create(&path)); - check!(file.sync_all()); - check!(file.sync_data()); - check!(file.write(b"foo")); - check!(file.sync_all()); - check!(file.sync_data()); -} - -#[test] -fn truncate_works() { - let tmpdir = tmpdir(); - let path = "in.txt"; - - let mut file = check!(tmpdir.create(&path)); - check!(file.write(b"foo")); - check!(file.sync_all()); - - // Do some simple things with truncation - assert_eq!(check!(file.metadata()).len(), 3); - check!(file.set_len(10)); - assert_eq!(check!(file.metadata()).len(), 10); - check!(file.write(b"bar")); - check!(file.sync_all()); - assert_eq!(check!(file.metadata()).len(), 10); - - let mut v = Vec::new(); - check!(check!(tmpdir.open(&path)).read_to_end(&mut v)); - assert_eq!(v, b"foobar\0\0\0\0".to_vec()); - - // Truncate to a smaller length, don't seek, and then write something. - // Ensure that the intermediate zeroes are all filled in (we have `seek`ed - // past the end of the file). - check!(file.set_len(2)); - assert_eq!(check!(file.metadata()).len(), 2); - check!(file.write(b"wut")); - check!(file.sync_all()); - assert_eq!(check!(file.metadata()).len(), 9); - let mut v = Vec::new(); - check!(check!(tmpdir.open(&path)).read_to_end(&mut v)); - assert_eq!(v, b"fo\0\0\0\0wut".to_vec()); -} - -#[test] -fn open_flavors() { - use cap_std::fs::OpenOptions as OO; - fn c(t: &T) -> T { - t.clone() - } - - let tmpdir = tmpdir(); - - let mut r = OO::new(); - r.read(true); - let mut w = OO::new(); - w.write(true); - let mut rw = OO::new(); - rw.read(true).write(true); - let mut a = OO::new(); - a.append(true); - let mut ra = OO::new(); - ra.read(true).append(true); - - #[cfg(windows)] - let invalid_options = 87; // ERROR_INVALID_PARAMETER - #[cfg(any(all(unix, not(target_os = "vxworks")), target_os = "wasi"))] - let invalid_options = "Invalid argument"; - #[cfg(target_os = "vxworks")] - let invalid_options = "invalid argument"; - - // Test various combinations of creation modes and access modes. - // - // Allowed: - // creation mode | read | write | read-write | append | read-append - // | :-----------------------|:-----:|:-----:|:----------:|:------:|: - // -----------:| not set (open existing) | X | X | X | X - // | X | create | | X | X | - // X | X | truncate | | X | X - // | | | create and truncate | | X | X - // | | | create_new | | X | X - // | X | X | - // - // tested in reverse order, so 'create_new' creates the file, and 'open - // existing' opens it. - - // write-only - check!(tmpdir.open_with("a", c(&w).create_new(true))); - check!(tmpdir.open_with("a", c(&w).create(true).truncate(true))); - check!(tmpdir.open_with("a", c(&w).truncate(true))); - check!(tmpdir.open_with("a", c(&w).create(true))); - check!(tmpdir.open_with("a", &c(&w))); - - // read-only - error!( - tmpdir.open_with("b", c(&r).create_new(true)), - invalid_options - ); - error!( - tmpdir.open_with("b", c(&r).create(true).truncate(true)), - invalid_options - ); - error!(tmpdir.open_with("b", c(&r).truncate(true)), invalid_options); - error!(tmpdir.open_with("b", c(&r).create(true)), invalid_options); - check!(tmpdir.open_with("a", &c(&r))); // try opening the file created with write_only - - // read-write - check!(tmpdir.open_with("c", c(&rw).create_new(true))); - check!(tmpdir.open_with("c", c(&rw).create(true).truncate(true))); - check!(tmpdir.open_with("c", c(&rw).truncate(true))); - check!(tmpdir.open_with("c", c(&rw).create(true))); - check!(tmpdir.open_with("c", &c(&rw))); - - // append - check!(tmpdir.open_with("d", c(&a).create_new(true))); - error!( - tmpdir.open_with("d", c(&a).create(true).truncate(true)), - invalid_options - ); - error!(tmpdir.open_with("d", c(&a).truncate(true)), invalid_options); - check!(tmpdir.open_with("d", c(&a).create(true))); - check!(tmpdir.open_with("d", &c(&a))); - - // read-append - check!(tmpdir.open_with("e", c(&ra).create_new(true))); - error!( - tmpdir.open_with("e", c(&ra).create(true).truncate(true)), - invalid_options - ); - error!( - tmpdir.open_with("e", c(&ra).truncate(true)), - invalid_options - ); - check!(tmpdir.open_with("e", c(&ra).create(true))); - check!(tmpdir.open_with("e", &c(&ra))); - - // Test opening a file without setting an access mode - let mut blank = OO::new(); - error!(tmpdir.open_with("f", blank.create(true)), invalid_options); - - // Test write works - check!(check!(tmpdir.create("h")).write("foobar".as_bytes())); - - // Test write fails for read-only - check!(tmpdir.open_with("h", &r)); - { - let mut f = check!(tmpdir.open_with("h", &r)); - assert!(f.write("wut".as_bytes()).is_err()); - } - - // Test write overwrites - { - let mut f = check!(tmpdir.open_with("h", &c(&w))); - check!(f.write("baz".as_bytes())); - } - { - let mut f = check!(tmpdir.open_with("h", &c(&r))); - let mut b = vec![0; 6]; - check!(f.read(&mut b)); - assert_eq!(b, "bazbar".as_bytes()); - } - - // Test truncate works - { - let mut f = check!(tmpdir.open_with("h", c(&w).truncate(true))); - check!(f.write("foo".as_bytes())); - } - assert_eq!(check!(tmpdir.metadata("h")).len(), 3); - - // Test append works - assert_eq!(check!(tmpdir.metadata("h")).len(), 3); - { - let mut f = check!(tmpdir.open_with("h", &c(&a))); - check!(f.write("bar".as_bytes())); - } - assert_eq!(check!(tmpdir.metadata("h")).len(), 6); - - // Test .append(true) equals .write(true).append(true) - { - let mut f = check!(tmpdir.open_with("h", c(&w).append(true))); - check!(f.write("baz".as_bytes())); - } - assert_eq!(check!(tmpdir.metadata("h")).len(), 9); -} - -#[test] -fn _assert_send_sync() { - fn _assert_send_sync() {} - _assert_send_sync::(); -} - -#[test] -fn binary_file() { - let mut bytes = [0; 1024]; - StdRng::from_os_rng().fill_bytes(&mut bytes); - - let tmpdir = tmpdir(); - - check!(check!(tmpdir.create("test")).write(&bytes)); - let mut v = Vec::new(); - check!(check!(tmpdir.open("test")).read_to_end(&mut v)); - assert!(v == &bytes[..]); -} - -#[test] -fn write_then_read() { - let mut bytes = [0; 1024]; - StdRng::from_os_rng().fill_bytes(&mut bytes); - - let tmpdir = tmpdir(); - - check!(tmpdir.write("test", &bytes[..])); - let v = check!(tmpdir.read("test")); - assert!(v == &bytes[..]); - - check!(tmpdir.write("not-utf8", &[0xFF])); - error_contains!( - tmpdir.read_to_string("not-utf8"), - "stream did not contain valid UTF-8" - ); - - let s = "𐁁𐀓𐀠𐀴𐀍"; - check!(tmpdir.write("utf8", s.as_bytes())); - let string = check!(tmpdir.read_to_string("utf8")); - assert_eq!(string, s); -} - -#[test] -fn file_try_clone() { - let tmpdir = tmpdir(); - - let mut f1 = check!(tmpdir.open_with( - "test", - OpenOptions::new().read(true).write(true).create(true) - )); - let mut f2 = check!(f1.try_clone()); - - check!(f1.write_all(b"hello world")); - check!(f1.seek(SeekFrom::Start(2))); - - let mut buf = vec![]; - check!(f2.read_to_end(&mut buf)); - assert_eq!(buf, b"llo world"); - drop(f2); - - check!(f1.write_all(b"!")); -} - -#[test] -#[cfg(not(windows))] -fn unlink_readonly() { - let tmpdir = tmpdir(); - let path = "file"; - check!(tmpdir.create(&path)); - let mut perm = check!(tmpdir.metadata(&path)).permissions(); - perm.set_readonly(true); - check!(tmpdir.set_permissions(&path, perm)); - check!(tmpdir.remove_file(&path)); -} - -#[test] -fn mkdir_trailing_slash() { - let tmpdir = tmpdir(); - let path = PathBuf::from("file"); - check!(tmpdir.create_dir_all(&path.join("a/"))); -} - -#[test] -fn canonicalize_works_simple() { - let tmpdir = tmpdir(); - let file = Path::new("test"); - tmpdir.create(&file).unwrap(); - assert_eq!(tmpdir.canonicalize(&file).unwrap(), file); -} - -#[test] -fn realpath_works() { - let tmpdir = tmpdir(); - if !got_symlink_permission(&tmpdir) { - return; - }; - - let file = PathBuf::from("test"); - let dir = PathBuf::from("test2"); - let link = dir.join("link"); - let linkdir = PathBuf::from("test3"); - - tmpdir.create(&file).unwrap(); - tmpdir.create_dir(&dir).unwrap(); - symlink_file(Path::new("..").join(&file), &tmpdir, &link).unwrap(); - symlink_dir(&dir, &tmpdir, &linkdir).unwrap(); - - assert!(tmpdir - .symlink_metadata(&link) - .unwrap() - .file_type() - .is_symlink()); - - assert_eq!(tmpdir.canonicalize(".").unwrap(), PathBuf::from(".")); - assert_eq!(tmpdir.canonicalize(&file).unwrap(), file); - assert_eq!(tmpdir.canonicalize(&link).unwrap(), file); - assert_eq!(tmpdir.canonicalize(&linkdir).unwrap(), dir); - assert_eq!(tmpdir.canonicalize(&linkdir.join("link")).unwrap(), file); -} - -#[test] -fn realpath_works_tricky() { - let tmpdir = tmpdir(); - if !got_symlink_permission(&tmpdir) { - return; - }; - - let a = PathBuf::from("a"); - let b = a.join("b"); - let c = b.join("c"); - let d = a.join("d"); - let e = d.join("e"); - let f = a.join("f"); - - tmpdir.create_dir_all(&b).unwrap(); - tmpdir.create_dir_all(&d).unwrap(); - tmpdir.create(&f).unwrap(); - if cfg!(not(windows)) { - symlink_file("../d/e", &tmpdir, &c).unwrap(); - symlink_file("../f", &tmpdir, &e).unwrap(); - } - if cfg!(windows) { - symlink_file(r"..\d\e", &tmpdir, &c).unwrap(); - symlink_file(r"..\f", &tmpdir, &e).unwrap(); - } - - assert_eq!(tmpdir.canonicalize(&c).unwrap(), f); - assert_eq!(tmpdir.canonicalize(&e).unwrap(), f); -} - -#[test] -fn dir_entry_methods() { - let tmpdir = tmpdir(); - - tmpdir.create_dir_all("a").unwrap(); - tmpdir.create("b").unwrap(); - - for file in tmpdir.read_dir(".").unwrap().map(|f| f.unwrap()) { - let fname = file.file_name().unwrap(); - match fname.as_str() { - "a" => { - assert!(file.file_type().unwrap().is_dir()); - assert!(file.metadata().unwrap().is_dir()); - } - "b" => { - assert!(file.file_type().unwrap().is_file()); - assert!(file.metadata().unwrap().is_file()); - } - f => panic!("unknown file name: {:?}", f), - } - } -} - -#[test] -fn dir_entry_debug() { - let tmpdir = tmpdir(); - tmpdir.create("b").unwrap(); - let mut read_dir = tmpdir.read_dir(".").unwrap(); - let dir_entry = read_dir.next().unwrap().unwrap(); - let actual = format!("{:?}", dir_entry); - let expected = format!("DirEntry({:?})", dir_entry.file_name().unwrap()); - assert_eq!(actual, expected); -} - -#[test] -fn read_dir_not_found() { - let tmpdir = tmpdir(); - let res = tmpdir.read_dir("path/that/does/not/exist"); - assert_eq!(res.err().unwrap().kind(), ErrorKind::NotFound); -} - -// See the comments on `create_dir_all_with_junctions` in tests/fs.rs about Windows. -#[test] -#[cfg_attr(windows, ignore)] -fn create_dir_all_with_junctions() { - let tmpdir = tmpdir(); - let target = "target"; - - let junction = PathBuf::from("junction"); - let b = junction.join("a/b"); - - let link = PathBuf::from("link"); - let d = link.join("c/d"); - - tmpdir.create_dir(&target).unwrap(); - - check!(symlink_junction(&target, &tmpdir, &junction)); - check!(tmpdir.create_dir_all(&b)); - // the junction itself is not a directory, but `is_dir()` on a Path - // follows links - assert!(tmpdir.is_dir(junction)); - assert!(tmpdir.exists(b)); - - if !got_symlink_permission(&tmpdir) { - return; - }; - check!(symlink_dir(&target, &tmpdir, &link)); - check!(tmpdir.create_dir_all(&d)); - assert!(tmpdir.is_dir(link)); - assert!(tmpdir.exists(d)); -} - -#[test] -fn metadata_access_times() { - let tmpdir = tmpdir(); - - let b = "b"; - tmpdir.create(&b).unwrap(); - - let a = check!(tmpdir.metadata(".")); - let b = check!(tmpdir.metadata(&b)); - - assert_eq!(check!(a.accessed()), check!(a.accessed())); - assert_eq!(check!(a.modified()), check!(a.modified())); - // This assert from std's testsuite is racy. - //assert_eq!(check!(b.accessed()), check!(b.modified())); - - if cfg!(target_os = "macos") || cfg!(target_os = "windows") { - check!(a.created()); - check!(b.created()); - } - - if cfg!(any(target_os = "android", target_os = "linux")) { - // Not always available - match (a.created(), b.created()) { - (Ok(t1), Ok(t2)) => assert!(t1 <= t2), - #[cfg(not(io_error_uncategorized))] - (Err(e1), Err(e2)) - if e1.kind() == ErrorKind::Other && e2.kind() == ErrorKind::Other => {} - #[cfg(io_error_uncategorized)] - (Err(e1), Err(e2)) - if e1.kind() == ErrorKind::Uncategorized - && e2.kind() == ErrorKind::Uncategorized - || e1.kind() == ErrorKind::Unsupported - && e2.kind() == ErrorKind::Unsupported => {} - (a, b) => panic!( - "creation time must be always supported or not supported: {:?} {:?}", - a, b, - ), - } - } -} - -/// Test creating hard links to symlinks. -#[test] -fn symlink_hard_link() { - let tmpdir = tmpdir(); - if !got_symlink_permission(&tmpdir) { - return; - } - if !able_to_not_follow_symlinks_while_hard_linking() { - return; - } - - // Create "file", a file. - check!(tmpdir.create("file")); - - // Create "symlink", a symlink to "file". - check!(symlink_file("file", &tmpdir, "symlink")); - - // Create "hard_link", a hard link to "symlink". - check!(tmpdir.hard_link("symlink", &tmpdir, "hard_link")); - - // "hard_link" should appear as a symlink. - assert!(check!(tmpdir.symlink_metadata("hard_link")) - .file_type() - .is_symlink()); - - // We sould be able to open "file" via any of the above names. - let _ = check!(tmpdir.open("file")); - assert!(tmpdir.open("file.renamed").is_err()); - let _ = check!(tmpdir.open("symlink")); - let _ = check!(tmpdir.open("hard_link")); - - // Rename "file" to "file.renamed". - check!(tmpdir.rename("file", &tmpdir, "file.renamed")); - - // Now, the symlink and the hard link should be dangling. - assert!(tmpdir.open("file").is_err()); - let _ = check!(tmpdir.open("file.renamed")); - assert!(tmpdir.open("symlink").is_err()); - assert!(tmpdir.open("hard_link").is_err()); - - // The symlink and the hard link should both still point to "file". - assert!(tmpdir.read_link("file").is_err()); - assert!(tmpdir.read_link("file.renamed").is_err()); - assert_eq!(check!(tmpdir.read_link("symlink")), Path::new("file")); - assert_eq!(check!(tmpdir.read_link("hard_link")), Path::new("file")); - - // Remove "file.renamed". - check!(tmpdir.remove_file("file.renamed")); - - // Now, we can't open the file by any name. - assert!(tmpdir.open("file").is_err()); - assert!(tmpdir.open("file.renamed").is_err()); - assert!(tmpdir.open("symlink").is_err()); - assert!(tmpdir.open("hard_link").is_err()); - - // "hard_link" should still appear as a symlink. - assert!(check!(tmpdir.symlink_metadata("hard_link")) - .file_type() - .is_symlink()); -} - -/// Ensure `fs::create_dir` works on Windows with longer paths. -#[test] -#[cfg(windows)] -fn create_dir_long_paths() { - use std::ffi::OsStr; - use std::iter; - use std::os::windows::ffi::OsStrExt; - const PATH_LEN: usize = 247; - - let tmpdir = tmpdir(); - let mut path = PathBuf::new(); - path.push("a"); - let mut path = path.into_os_string(); - - let utf16_len = path.encode_wide().count(); - if utf16_len >= PATH_LEN { - // Skip the test in the unlikely event the local user has a long temp directory - // path. This should not affect CI. - return; - } - // Increase the length of the path. - path.extend(iter::repeat(OsStr::new("a")).take(PATH_LEN - utf16_len)); - - // This should succeed. - tmpdir - .create_dir(&PathBuf::from_path_buf(path.clone().into()).unwrap()) - .unwrap(); - - // This will fail if the path isn't converted to verbatim. - path.push("a"); - tmpdir - .create_dir(&PathBuf::from_path_buf(path.clone().into()).unwrap()) - .unwrap(); - - // #90940: Ensure an empty path returns the "Not Found" error. - let path = Path::new(""); - assert_eq!( - tmpdir.canonicalize(path).unwrap_err().kind(), - std::io::ErrorKind::NotFound - ); -} - -/// Test creating files with invalid names and reading their parent -/// directories. -#[test] -fn test_invalid_utf8() { - use camino::Utf8Path; - #[cfg(not(windows))] - use std::ffi::OsStr; - #[cfg(windows)] - use std::ffi::OsString; - #[cfg(unix)] - use std::os::unix::ffi::OsStrExt; - #[cfg(target_os = "wasi")] - use std::os::wasi::ffi::OsStrExt; - #[cfg(windows)] - use std::os::windows::ffi::OsStringExt; - - let dir = tempfile::tempdir().unwrap(); - #[cfg(not(windows))] - let invalid_path = dir.path().join(OsStr::from_bytes(b"invalid\xffbyte")); - #[cfg(windows)] - let invalid_path = dir.path().join(OsString::from_wide(&[ - 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0xd800, 0x62, 0x79, 0x74, 0x65, - ])); - - // On some OS's, it's not possible to create files with invalid paths. - // If it is, test that we do the right thing with them. - if let Ok(_) = std::fs::File::create(invalid_path) { - let cap_dir = cap_std::fs_utf8::Dir::open_ambient_dir( - Utf8Path::from_path(dir.path()).unwrap(), - cap_std::ambient_authority(), - ) - .unwrap(); - - for entry in cap_dir.entries().unwrap() { - let entry = entry.unwrap(); - #[cfg(feature = "arf_strings")] - { - let name: String = entry.file_name().unwrap(); - let _ = cap_dir.open(name).unwrap(); - } - #[cfg(not(feature = "arf_strings"))] - { - let _ = entry.file_name().unwrap_err(); - } - } - } -} - -#[test] -fn from_cap_std() { - let tmpdir = sys_common::io::tmpdir(); - let dir = "d1/d2"; - check!(tmpdir.create_dir_all(dir)); - let d1_entry = tmpdir.entries_utf8().unwrap().next().unwrap().unwrap(); - assert_eq!(d1_entry.file_name().unwrap(), "d1"); - - let d1 = tmpdir.open_dir_utf8("d1").unwrap(); - let d2_entry = d1.entries().unwrap().next().unwrap().unwrap(); - assert_eq!(d2_entry.file_name().unwrap(), "d2"); -} diff --git a/crates/wasi/src/filesystem/primitives/tests/mod.rs b/crates/wasi/src/filesystem/primitives/tests/mod.rs index b1c32776e010..8adc2abd510b 100644 --- a/crates/wasi/src/filesystem/primitives/tests/mod.rs +++ b/crates/wasi/src/filesystem/primitives/tests/mod.rs @@ -1,21 +1,13 @@ mod canonicalize; mod cap_basics; -mod cap_net_ext_tcp; -mod cap_net_ext_tcp_split; -mod cap_net_ext_udp; -mod cap_net_ext_udp_split; mod dir_entry_ext; mod dir_ext; mod file_type_ext; mod fs; mod fs_additional; -mod fs_utf8; mod is_file_read_write; mod issue_22577; mod metadata_ext; -mod net; -mod net_tcp; -mod net_udp; mod open_ambient; mod paths_containing_nul; mod rand; @@ -30,6 +22,5 @@ mod set_times; mod symlinks; mod sys; mod sys_common; -mod time; mod windows_open; mod windows_symlinks; diff --git a/crates/wasi/src/filesystem/primitives/tests/net.rs b/crates/wasi/src/filesystem/primitives/tests/net.rs deleted file mode 100644 index 45e52f21e7b8..000000000000 --- a/crates/wasi/src/filesystem/primitives/tests/net.rs +++ /dev/null @@ -1,76 +0,0 @@ -// This file is derived from Rust's library/std/src/net/test.rs at -// revision 377d1a984cd2a53327092b90aa1d8b7e22d1e347. - -#![allow(warnings)] // not used on emscripten - -use cap_std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs}; -use std::env; -use std::sync::atomic::{AtomicUsize, Ordering}; - -static PORT: AtomicUsize = AtomicUsize::new(0); - -pub fn next_test_ip4() -> SocketAddr { - let port = PORT.fetch_add(1, Ordering::SeqCst) as u16 + base_port(); - SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), port)) -} - -pub fn next_test_ip6() -> SocketAddr { - let port = PORT.fetch_add(1, Ordering::SeqCst) as u16 + base_port(); - SocketAddr::V6(SocketAddrV6::new( - Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1), - port, - 0, - 0, - )) -} - -pub fn sa4(a: Ipv4Addr, p: u16) -> SocketAddr { - SocketAddr::V4(SocketAddrV4::new(a, p)) -} - -pub fn sa6(a: Ipv6Addr, p: u16) -> SocketAddr { - SocketAddr::V6(SocketAddrV6::new(a, p, 0, 0)) -} - -pub fn tsa(a: A) -> Result, String> { - match a.to_socket_addrs() { - Ok(a) => Ok(a.collect()), - Err(e) => Err(e.to_string()), - } -} - -// The bots run multiple builds at the same time, and these builds -// all want to use ports. This function figures out which workspace -// it is running in and assigns a port range based on it. -fn base_port() -> u16 { - let cwd = if cfg!(target_env = "sgx") { - String::from("sgx") - } else { - env::current_dir() - .unwrap() - .into_os_string() - .into_string() - .unwrap() - }; - let dirs = [ - "32-opt", - "32-nopt", - "musl-64-opt", - "cross-opt", - "64-opt", - "64-nopt", - "64-opt-vg", - "64-debug-opt", - "all-opt", - "snap3", - "dist", - "sgx", - ]; - dirs.iter() - .enumerate() - .find(|&(_, dir)| cwd.contains(dir)) - .map(|p| p.0) - .unwrap_or(0) as u16 - * 1000 - + 19600 -} diff --git a/crates/wasi/src/filesystem/primitives/tests/net_tcp.rs b/crates/wasi/src/filesystem/primitives/tests/net_tcp.rs deleted file mode 100644 index 108e558e976a..000000000000 --- a/crates/wasi/src/filesystem/primitives/tests/net_tcp.rs +++ /dev/null @@ -1,1050 +0,0 @@ -// This file is derived from Rust's library/std/src/net/tcp/tests.rs at -// revision 377d1a984cd2a53327092b90aa1d8b7e22d1e347. - -mod net; - -use cap_std::ambient_authority; -use cap_std::net::*; -use net::{next_test_ip4, next_test_ip6}; -use std::io::prelude::*; -use std::io::{ErrorKind, IoSlice, IoSliceMut}; -use std::sync::mpsc::channel; -use std::time::{Duration, Instant}; -use std::{fmt, thread}; - -fn each_ip(f: &mut dyn FnMut(SocketAddr)) { - f(next_test_ip4()); - f(next_test_ip6()); -} - -macro_rules! t { - ($e:expr) => { - match $e { - Ok(t) => t, - Err(e) => panic!("received error for `{}`: {}", stringify!($e), e), - } - }; -} - -#[test] -fn bind_error() { - let mut pool = Pool::new(); - pool.insert_socket_addr("1.1.1.1:9999".parse().unwrap(), ambient_authority()); - - match pool.bind_tcp_listener("1.1.1.1:9999") { - Ok(..) => panic!(), - Err(e) => assert_eq!(e.kind(), ErrorKind::AddrNotAvailable), - } -} - -#[test] -fn connect_error() { - let mut pool = Pool::new(); - pool.insert_socket_addr("0.0.0.0:1".parse().unwrap(), ambient_authority()); - - match pool.connect_tcp_stream("0.0.0.0:1") { - Ok(..) => panic!(), - Err(e) => assert!( - e.kind() == ErrorKind::ConnectionRefused - || e.kind() == ErrorKind::InvalidInput - || e.kind() == ErrorKind::AddrInUse - || e.kind() == ErrorKind::AddrNotAvailable - || e.kind() == ErrorKind::TimedOut - // Rust >=1.83 adds `ErrorKind::NetworkUnreachable`. - || e.to_string().contains("network unreachable") - || e.to_string().contains("Network is unreachable"), - "bad error: {} {:?}", - e, - e.kind() - ), - } -} - -#[test] -fn listen_localhost() { - let socket_addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(socket_addr, ambient_authority()); - for resolved in format!("localhost:{}", socket_addr.port()) - .to_socket_addrs() - .unwrap() - { - pool.insert_socket_addr(resolved, ambient_authority()); - } - - let listener = t!(pool.bind_tcp_listener(&socket_addr)); - - let _t = thread::spawn(move || { - let mut stream = t!(pool.connect_tcp_stream(&("localhost", socket_addr.port()))); - t!(stream.write(&[144])); - }); - - let mut stream = t!(listener.accept()).0; - let mut buf = [0]; - t!(stream.read(&mut buf)); - assert!(buf[0] == 144); -} - -#[test] -fn connect_loopback() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let acceptor = t!(pool.bind_tcp_listener(&addr)); - - let _t = thread::spawn(move || { - let host = match addr { - SocketAddr::V4(..) => "127.0.0.1", - SocketAddr::V6(..) => "::1", - }; - let mut stream = t!(pool.connect_tcp_stream(&(host, addr.port()))); - t!(stream.write(&[66])); - }); - - let mut stream = t!(acceptor.accept()).0; - let mut buf = [0]; - t!(stream.read(&mut buf)); - assert!(buf[0] == 66); - }) -} - -#[test] -fn smoke_test() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let acceptor = t!(pool.bind_tcp_listener(&addr)); - - let (tx, rx) = channel(); - let _t = thread::spawn(move || { - let mut stream = t!(pool.connect_tcp_stream(&addr)); - t!(stream.write(&[99])); - tx.send(t!(stream.local_addr())).unwrap(); - }); - - let (mut stream, addr) = t!(acceptor.accept()); - let mut buf = [0]; - t!(stream.read(&mut buf)); - assert!(buf[0] == 99); - assert_eq!(addr, t!(rx.recv())); - }) -} - -#[test] -fn read_eof() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let acceptor = t!(pool.bind_tcp_listener(&addr)); - - let _t = thread::spawn(move || { - let _stream = t!(pool.connect_tcp_stream(&addr)); - // Close - }); - - let mut stream = t!(acceptor.accept()).0; - let mut buf = [0]; - let nread = t!(stream.read(&mut buf)); - assert_eq!(nread, 0); - let nread = t!(stream.read(&mut buf)); - assert_eq!(nread, 0); - }) -} - -#[test] -fn write_close() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let acceptor = t!(pool.bind_tcp_listener(&addr)); - - let (tx, rx) = channel(); - let _t = thread::spawn(move || { - drop(t!(pool.connect_tcp_stream(&addr))); - tx.send(()).unwrap(); - }); - - let mut stream = t!(acceptor.accept()).0; - rx.recv().unwrap(); - let buf = [0]; - match stream.write(&buf) { - Ok(..) => {} - Err(e) => { - assert!( - e.kind() == ErrorKind::ConnectionReset - || e.kind() == ErrorKind::BrokenPipe - || e.kind() == ErrorKind::ConnectionAborted, - "unknown error: {}", - e - ); - } - } - }) -} - -#[test] -fn multiple_connect_serial() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let max = 10; - let acceptor = t!(pool.bind_tcp_listener(&addr)); - - let _t = thread::spawn(move || { - for _ in 0..max { - let mut stream = t!(pool.connect_tcp_stream(&addr)); - t!(stream.write(&[99])); - } - }); - - for stream in acceptor.incoming().take(max) { - let mut stream = t!(stream); - let mut buf = [0]; - t!(stream.read(&mut buf)); - assert_eq!(buf[0], 99); - } - }) -} - -#[test] -fn multiple_connect_interleaved_greedy_schedule() { - const MAX: usize = 10; - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let acceptor = t!(pool.bind_tcp_listener(&addr)); - - let _t = thread::spawn(move || { - let acceptor = acceptor; - for (i, stream) in acceptor.incoming().enumerate().take(MAX) { - // Start another thread to handle the connection - let _t = thread::spawn(move || { - let mut stream = t!(stream); - let mut buf = [0]; - t!(stream.read(&mut buf)); - assert!(buf[0] == i as u8); - }); - } - }); - - connect(0, addr, pool); - }); - - fn connect(i: usize, addr: SocketAddr, pool: Pool) { - if i == MAX { - return; - } - - let t = thread::spawn(move || { - let mut stream = t!(pool.connect_tcp_stream(&addr)); - // Connect again before writing - connect(i + 1, addr, pool); - t!(stream.write(&[i as u8])); - }); - t.join().ok().expect("thread panicked"); - } -} - -#[test] -fn multiple_connect_interleaved_lazy_schedule() { - const MAX: usize = 10; - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let acceptor = t!(pool.bind_tcp_listener(&addr)); - - let _t = thread::spawn(move || { - for stream in acceptor.incoming().take(MAX) { - // Start another thread to handle the connection - let _t = thread::spawn(move || { - let mut stream = t!(stream); - let mut buf = [0]; - t!(stream.read(&mut buf)); - assert!(buf[0] == 99); - }); - } - }); - - connect(0, addr, pool); - }); - - fn connect(i: usize, addr: SocketAddr, pool: Pool) { - if i == MAX { - return; - } - - let t = thread::spawn(move || { - let mut stream = t!(pool.connect_tcp_stream(&addr)); - connect(i + 1, addr, pool); - t!(stream.write(&[99])); - }); - t.join().ok().expect("thread panicked"); - } -} - -#[test] -fn socket_and_peer_name() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let listener = t!(pool.bind_tcp_listener(&addr)); - let so_name = t!(listener.local_addr()); - assert_eq!(addr, so_name); - let _t = thread::spawn(move || { - t!(listener.accept()); - }); - - let stream = t!(pool.connect_tcp_stream(&addr)); - assert_eq!(addr, t!(stream.peer_addr())); - }) -} - -#[test] -fn partial_read() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let (tx, rx) = channel(); - let srv = t!(pool.bind_tcp_listener(&addr)); - let _t = thread::spawn(move || { - let mut cl = t!(srv.accept()).0; - cl.write(&[10]).unwrap(); - let mut b = [0]; - t!(cl.read(&mut b)); - tx.send(()).unwrap(); - }); - - let mut c = t!(pool.connect_tcp_stream(&addr)); - let mut b = [0; 10]; - assert_eq!(c.read(&mut b).unwrap(), 1); - t!(c.write(&[1])); - rx.recv().unwrap(); - }) -} - -#[test] -fn read_vectored() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let srv = t!(pool.bind_tcp_listener(&addr)); - let mut s1 = t!(pool.connect_tcp_stream(&addr)); - let mut s2 = t!(srv.accept()).0; - - let len = s1.write(&[10, 11, 12]).unwrap(); - assert_eq!(len, 3); - - let mut a = []; - let mut b = [0]; - let mut c = [0; 3]; - let len = t!(s2.read_vectored(&mut [ - IoSliceMut::new(&mut a), - IoSliceMut::new(&mut b), - IoSliceMut::new(&mut c) - ])); - assert!(len > 0); - assert_eq!(b, [10]); - // some implementations don't support readv, so we may only fill the first - // buffer - assert!(len == 1 || c == [11, 12, 0]); - }) -} - -#[test] -fn write_vectored() { - let mut pool = Pool::new(); - - each_ip(&mut |addr| { - pool.insert_socket_addr(addr, ambient_authority()); - - let srv = t!(pool.bind_tcp_listener(&addr)); - let mut s1 = t!(pool.connect_tcp_stream(&addr)); - let mut s2 = t!(srv.accept()).0; - - let a = []; - let b = [10]; - let c = [11, 12]; - t!(s1.write_vectored(&[IoSlice::new(&a), IoSlice::new(&b), IoSlice::new(&c)])); - - let mut buf = [0; 4]; - let len = t!(s2.read(&mut buf)); - // some implementations don't support writev, so we may only write the first - // buffer - if len == 1 { - assert_eq!(buf, [10, 0, 0, 0]); - } else { - assert_eq!(len, 3); - assert_eq!(buf, [10, 11, 12, 0]); - } - }) -} - -#[test] -fn double_bind() { - let mut pool = Pool::new(); - - each_ip(&mut |addr| { - pool.insert_socket_addr(addr, ambient_authority()); - - let listener1 = t!(pool.bind_tcp_listener(&addr)); - match pool.bind_tcp_listener(&addr) { - Ok(listener2) => panic!( - "This system (perhaps due to options set by pool.bind_tcp_listener) \ - permits double binding: {:?} and {:?}", - listener1, listener2 - ), - Err(e) => { - assert!( - e.kind() == ErrorKind::ConnectionRefused - || e.kind() == ErrorKind::Other - || e.kind() == ErrorKind::AddrInUse, - "unknown error: {} {:?}", - e, - e.kind() - ); - } - } - }) -} - -#[test] -fn tcp_clone_smoke() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let acceptor = t!(pool.bind_tcp_listener(&addr)); - - let _t = thread::spawn(move || { - let mut s = t!(pool.connect_tcp_stream(&addr)); - let mut buf = [0, 0]; - assert_eq!(s.read(&mut buf).unwrap(), 1); - assert_eq!(buf[0], 1); - t!(s.write(&[2])); - }); - - let mut s1 = t!(acceptor.accept()).0; - let s2 = t!(s1.try_clone()); - - let (tx1, rx1) = channel(); - let (tx2, rx2) = channel(); - let _t = thread::spawn(move || { - let mut s2 = s2; - rx1.recv().unwrap(); - t!(s2.write(&[1])); - tx2.send(()).unwrap(); - }); - tx1.send(()).unwrap(); - let mut buf = [0, 0]; - assert_eq!(s1.read(&mut buf).unwrap(), 1); - rx2.recv().unwrap(); - }) -} - -#[test] -fn tcp_clone_two_read() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let acceptor = t!(pool.bind_tcp_listener(&addr)); - let (tx1, rx) = channel(); - let tx2 = tx1.clone(); - - let _t = thread::spawn(move || { - let mut s = t!(pool.connect_tcp_stream(&addr)); - t!(s.write(&[1])); - rx.recv().unwrap(); - t!(s.write(&[2])); - rx.recv().unwrap(); - }); - - let mut s1 = t!(acceptor.accept()).0; - let s2 = t!(s1.try_clone()); - - let (done, rx) = channel(); - let _t = thread::spawn(move || { - let mut s2 = s2; - let mut buf = [0, 0]; - t!(s2.read(&mut buf)); - tx2.send(()).unwrap(); - done.send(()).unwrap(); - }); - let mut buf = [0, 0]; - t!(s1.read(&mut buf)); - tx1.send(()).unwrap(); - - rx.recv().unwrap(); - }) -} - -#[test] -fn tcp_clone_two_write() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let acceptor = t!(pool.bind_tcp_listener(&addr)); - - let _t = thread::spawn(move || { - let mut s = t!(pool.connect_tcp_stream(&addr)); - let mut buf = [0, 1]; - t!(s.read(&mut buf)); - t!(s.read(&mut buf)); - }); - - let mut s1 = t!(acceptor.accept()).0; - let s2 = t!(s1.try_clone()); - - let (done, rx) = channel(); - let _t = thread::spawn(move || { - let mut s2 = s2; - t!(s2.write(&[1])); - done.send(()).unwrap(); - }); - t!(s1.write(&[2])); - - rx.recv().unwrap(); - }) -} - -#[test] -// FIXME: https://github.com/fortanix/rust-sgx/issues/110 -#[cfg_attr(target_env = "sgx", ignore)] -fn shutdown_smoke() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let a = t!(pool.bind_tcp_listener(&addr)); - let _t = thread::spawn(move || { - let mut c = t!(a.accept()).0; - let mut b = [0]; - assert_eq!(c.read(&mut b).unwrap(), 0); - t!(c.write(&[1])); - }); - - let mut s = t!(pool.connect_tcp_stream(&addr)); - t!(s.shutdown(Shutdown::Write)); - assert!(s.write(&[1]).is_err()); - let mut b = [0, 0]; - assert_eq!(t!(s.read(&mut b)), 1); - assert_eq!(b[0], 1); - }) -} - -#[test] -// FIXME: https://github.com/fortanix/rust-sgx/issues/110 -#[cfg_attr(target_env = "sgx", ignore)] -fn close_readwrite_smoke() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let a = t!(pool.bind_tcp_listener(&addr)); - let (tx, rx) = channel::<()>(); - let _t = thread::spawn(move || { - let _s = t!(a.accept()); - let _ = rx.recv(); - }); - - let mut b = [0]; - let mut s = t!(pool.connect_tcp_stream(&addr)); - let mut s2 = t!(s.try_clone()); - - // closing should prevent reads/writes - t!(s.shutdown(Shutdown::Write)); - assert!(s.write(&[0]).is_err()); - t!(s.shutdown(Shutdown::Read)); - assert_eq!(s.read(&mut b).unwrap(), 0); - - // closing should affect previous handles - assert!(s2.write(&[0]).is_err()); - assert_eq!(s2.read(&mut b).unwrap(), 0); - - // closing should affect new handles - let mut s3 = t!(s.try_clone()); - assert!(s3.write(&[0]).is_err()); - assert_eq!(s3.read(&mut b).unwrap(), 0); - - // make sure these don't die - let _ = s2.shutdown(Shutdown::Read); - let _ = s2.shutdown(Shutdown::Write); - let _ = s3.shutdown(Shutdown::Read); - let _ = s3.shutdown(Shutdown::Write); - drop(tx); - }) -} - -#[test] -#[cfg(unix)] // test doesn't work on Windows, see #31657 -fn close_read_wakes_up() { - let mut pool = Pool::new(); - - each_ip(&mut |addr| { - pool.insert_socket_addr(addr, ambient_authority()); - - let a = t!(pool.bind_tcp_listener(&addr)); - let (tx1, rx) = channel::<()>(); - let _t = thread::spawn(move || { - let _s = t!(a.accept()); - let _ = rx.recv(); - }); - - let s = t!(pool.connect_tcp_stream(&addr)); - let s2 = t!(s.try_clone()); - let (tx, rx) = channel(); - let _t = thread::spawn(move || { - let mut s2 = s2; - assert_eq!(t!(s2.read(&mut [0])), 0); - tx.send(()).unwrap(); - }); - // this should wake up the child thread - t!(s.shutdown(Shutdown::Read)); - - // this test will never finish if the child doesn't wake up - rx.recv().unwrap(); - drop(tx1); - }) -} - -#[test] -fn clone_while_reading() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let accept = t!(pool.bind_tcp_listener(&addr)); - - // Enqueue a thread to write to a socket - let (tx, rx) = channel(); - let (txdone, rxdone) = channel(); - let txdone2 = txdone.clone(); - let _t = thread::spawn(move || { - let mut tcp = t!(pool.connect_tcp_stream(&addr)); - rx.recv().unwrap(); - t!(tcp.write(&[0])); - txdone2.send(()).unwrap(); - }); - - // Spawn off a reading clone - let tcp = t!(accept.accept()).0; - let tcp2 = t!(tcp.try_clone()); - let txdone3 = txdone.clone(); - let _t = thread::spawn(move || { - let mut tcp2 = tcp2; - t!(tcp2.read(&mut [0])); - txdone3.send(()).unwrap(); - }); - - // Try to ensure that the reading clone is indeed reading - for _ in 0..50 { - thread::yield_now(); - } - - // clone the handle again while it's reading, then let it finish the - // read. - let _ = t!(tcp.try_clone()); - tx.send(()).unwrap(); - rxdone.recv().unwrap(); - rxdone.recv().unwrap(); - }) -} - -#[test] -fn clone_accept_smoke() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let a = t!(pool.bind_tcp_listener(&addr)); - let a2 = t!(a.try_clone()); - - let p = pool.clone(); - let _t = thread::spawn(move || { - let _ = p.connect_tcp_stream(&addr); - }); - let p = pool.clone(); - let _t = thread::spawn(move || { - let _ = p.connect_tcp_stream(&addr); - }); - - t!(a.accept()); - t!(a2.accept()); - }) -} - -#[test] -fn clone_accept_concurrent() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let a = t!(pool.bind_tcp_listener(&addr)); - let a2 = t!(a.try_clone()); - - let (tx, rx) = channel(); - let tx2 = tx.clone(); - - let _t = thread::spawn(move || { - tx.send(t!(a.accept())).unwrap(); - }); - let _t = thread::spawn(move || { - tx2.send(t!(a2.accept())).unwrap(); - }); - - let p = pool.clone(); - let _t = thread::spawn(move || { - let _ = p.connect_tcp_stream(&addr); - }); - let p = pool.clone(); - let _t = thread::spawn(move || { - let _ = p.connect_tcp_stream(&addr); - }); - - rx.recv().unwrap(); - rx.recv().unwrap(); - }) -} - -#[test] -fn debug() { - let mut pool = Pool::new(); - - #[cfg(not(target_env = "sgx"))] - fn render_socket_addr<'a>(addr: &'a SocketAddr) -> impl fmt::Debug + 'a { - addr - } - #[cfg(target_env = "sgx")] - fn render_socket_addr<'a>(addr: &'a SocketAddr) -> impl fmt::Debug + 'a { - addr.to_string() - } - - #[cfg(target_env = "sgx")] - use std::os::fortanix_sgx::io::AsRawFd; - #[cfg(unix)] - use std::os::unix::io::AsRawFd; - #[cfg(not(windows))] - fn render_inner(addr: &dyn AsRawFd) -> impl fmt::Debug { - addr.as_raw_fd() - } - #[cfg(windows)] - fn render_inner(addr: &dyn std::os::windows::io::AsRawSocket) -> impl fmt::Debug { - addr.as_raw_socket() - } - - let inner_name = if cfg!(windows) { "socket" } else { "fd" }; - let socket_addr = next_test_ip4(); - pool.insert_socket_addr(socket_addr, ambient_authority()); - for resolved in format!("localhost:{}", socket_addr.port()) - .to_socket_addrs() - .unwrap() - { - pool.insert_socket_addr(resolved, ambient_authority()); - } - - let listener = t!(pool.bind_tcp_listener(&socket_addr)); - let compare = format!( - "TcpListener {{ addr: {:?}, {}: {:?} }}", - render_socket_addr(&socket_addr), - inner_name, - render_inner(&listener) - ); - assert_eq!(format!("{:?}", listener), compare); - - let stream = t!(pool.connect_tcp_stream(&("localhost", socket_addr.port()))); - let compare = format!( - "TcpStream {{ addr: {:?}, peer: {:?}, {}: {:?} }}", - render_socket_addr(&stream.local_addr().unwrap()), - render_socket_addr(&stream.peer_addr().unwrap()), - inner_name, - render_inner(&stream) - ); - assert_eq!(format!("{:?}", stream), compare); -} - -// FIXME: re-enabled openbsd tests once their socket timeout code -// no longer has rounding errors. -// VxWorks ignores SO_SNDTIMEO. -#[cfg_attr( - any(target_os = "netbsd", target_os = "openbsd", target_os = "vxworks"), - ignore -)] -#[cfg_attr(target_env = "sgx", ignore)] // FIXME: https://github.com/fortanix/rust-sgx/issues/31 -#[test] -fn timeouts() { - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - for resolved in format!("localhost:{}", addr.port()) - .to_socket_addrs() - .unwrap() - { - pool.insert_socket_addr(resolved, ambient_authority()); - } - - let listener = t!(pool.bind_tcp_listener(&addr)); - - let stream = t!(pool.connect_tcp_stream(&("localhost", addr.port()))); - let dur = Duration::new(15410, 0); - - assert_eq!(None, t!(stream.read_timeout())); - - t!(stream.set_read_timeout(Some(dur))); - assert_eq!(Some(dur), t!(stream.read_timeout())); - - assert_eq!(None, t!(stream.write_timeout())); - - t!(stream.set_write_timeout(Some(dur))); - assert_eq!(Some(dur), t!(stream.write_timeout())); - - t!(stream.set_read_timeout(None)); - assert_eq!(None, t!(stream.read_timeout())); - - t!(stream.set_write_timeout(None)); - assert_eq!(None, t!(stream.write_timeout())); - drop(listener); -} - -#[test] -#[cfg_attr(target_env = "sgx", ignore)] // FIXME: https://github.com/fortanix/rust-sgx/issues/31 -fn test_read_timeout() { - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - for resolved in format!("localhost:{}", addr.port()) - .to_socket_addrs() - .unwrap() - { - pool.insert_socket_addr(resolved, ambient_authority()); - } - - let listener = t!(pool.bind_tcp_listener(&addr)); - - let mut stream = t!(pool.connect_tcp_stream(&("localhost", addr.port()))); - t!(stream.set_read_timeout(Some(Duration::from_millis(1000)))); - - let mut buf = [0; 10]; - let start = Instant::now(); - let kind = stream - .read_exact(&mut buf) - .err() - .expect("expected error") - .kind(); - assert!( - kind == ErrorKind::WouldBlock || kind == ErrorKind::TimedOut, - "unexpected_error: {:?}", - kind - ); - assert!(start.elapsed() > Duration::from_millis(400)); - drop(listener); -} - -#[test] -#[cfg_attr(target_env = "sgx", ignore)] // FIXME: https://github.com/fortanix/rust-sgx/issues/31 -fn test_read_with_timeout() { - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - for resolved in format!("localhost:{}", addr.port()) - .to_socket_addrs() - .unwrap() - { - pool.insert_socket_addr(resolved, ambient_authority()); - } - - let listener = t!(pool.bind_tcp_listener(&addr)); - - let mut stream = t!(pool.connect_tcp_stream(&("localhost", addr.port()))); - t!(stream.set_read_timeout(Some(Duration::from_millis(1000)))); - - let mut other_end = t!(listener.accept()).0; - t!(other_end.write_all(b"hello world")); - - let mut buf = [0; 11]; - t!(stream.read(&mut buf)); - assert_eq!(b"hello world", &buf[..]); - - let start = Instant::now(); - let kind = stream - .read_exact(&mut buf) - .err() - .expect("expected error") - .kind(); - assert!( - kind == ErrorKind::WouldBlock || kind == ErrorKind::TimedOut, - "unexpected_error: {:?}", - kind - ); - assert!(start.elapsed() > Duration::from_millis(400)); - drop(listener); -} - -// Ensure the `set_read_timeout` and `set_write_timeout` calls return errors -// when passed zero Durations -#[test] -fn test_timeout_zero_duration() { - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let listener = t!(pool.bind_tcp_listener(&addr)); - let stream = t!(pool.connect_tcp_stream(&addr)); - - let result = stream.set_write_timeout(Some(Duration::new(0, 0))); - let err = result.unwrap_err(); - assert_eq!(err.kind(), ErrorKind::InvalidInput); - - let result = stream.set_read_timeout(Some(Duration::new(0, 0))); - let err = result.unwrap_err(); - assert_eq!(err.kind(), ErrorKind::InvalidInput); - - drop(listener); -} - -#[test] -#[cfg_attr(target_env = "sgx", ignore)] -fn nodelay() { - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - for resolved in format!("localhost:{}", addr.port()) - .to_socket_addrs() - .unwrap() - { - pool.insert_socket_addr(resolved, ambient_authority()); - } - - let _listener = t!(pool.bind_tcp_listener(&addr)); - - let stream = t!(pool.connect_tcp_stream(&("localhost", addr.port()))); - - assert_eq!(false, t!(stream.nodelay())); - t!(stream.set_nodelay(true)); - assert_eq!(true, t!(stream.nodelay())); - t!(stream.set_nodelay(false)); - assert_eq!(false, t!(stream.nodelay())); -} - -#[test] -#[cfg_attr(target_env = "sgx", ignore)] -fn ttl() { - let ttl = 100; - - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - for resolved in format!("localhost:{}", addr.port()) - .to_socket_addrs() - .unwrap() - { - pool.insert_socket_addr(resolved, ambient_authority()); - } - - let listener = t!(pool.bind_tcp_listener(&addr)); - - t!(listener.set_ttl(ttl)); - assert_eq!(ttl, t!(listener.ttl())); - - let stream = t!(pool.connect_tcp_stream(&("localhost", addr.port()))); - - t!(stream.set_ttl(ttl)); - assert_eq!(ttl, t!(stream.ttl())); -} - -#[test] -#[cfg_attr(target_env = "sgx", ignore)] -fn set_nonblocking() { - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - for resolved in format!("localhost:{}", addr.port()) - .to_socket_addrs() - .unwrap() - { - pool.insert_socket_addr(resolved, ambient_authority()); - } - - let listener = t!(pool.bind_tcp_listener(&addr)); - - t!(listener.set_nonblocking(true)); - t!(listener.set_nonblocking(false)); - - let mut stream = t!(pool.connect_tcp_stream(&("localhost", addr.port()))); - - t!(stream.set_nonblocking(false)); - t!(stream.set_nonblocking(true)); - - let mut buf = [0]; - match stream.read(&mut buf) { - Ok(_) => panic!("expected error"), - Err(ref e) if e.kind() == ErrorKind::WouldBlock => {} - Err(e) => panic!("unexpected error {}", e), - } -} - -#[test] -#[cfg_attr(target_env = "sgx", ignore)] // FIXME: https://github.com/fortanix/rust-sgx/issues/31 -fn peek() { - each_ip(&mut |addr| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let (txdone, rxdone) = channel(); - - let srv = t!(pool.bind_tcp_listener(&addr)); - let _t = thread::spawn(move || { - let mut cl = t!(srv.accept()).0; - cl.write(&[1, 3, 3, 7]).unwrap(); - t!(rxdone.recv()); - }); - - let mut c = t!(pool.connect_tcp_stream(&addr)); - let mut b = [0; 10]; - for _ in 1..3 { - let len = c.peek(&mut b).unwrap(); - assert_eq!(len, 4); - } - let len = c.read(&mut b).unwrap(); - assert_eq!(len, 4); - - t!(c.set_nonblocking(true)); - match c.peek(&mut b) { - Ok(_) => panic!("expected error"), - Err(ref e) if e.kind() == ErrorKind::WouldBlock => {} - Err(e) => panic!("unexpected error {}", e), - } - t!(txdone.send(())); - }) -} - -#[test] -#[cfg_attr(target_env = "sgx", ignore)] // FIXME: https://github.com/fortanix/rust-sgx/issues/31 -fn connect_timeout_valid() { - let mut pool = Pool::new(); - pool.insert_socket_addr("127.0.0.1:0".parse().unwrap(), ambient_authority()); - let listener = pool.bind_tcp_listener("127.0.0.1:0").unwrap(); - let addr = listener.local_addr().unwrap(); - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - pool.connect_timeout_tcp_stream(&addr, Duration::from_secs(2)) - .unwrap(); -} diff --git a/crates/wasi/src/filesystem/primitives/tests/net_udp.rs b/crates/wasi/src/filesystem/primitives/tests/net_udp.rs deleted file mode 100644 index f692f35769bc..000000000000 --- a/crates/wasi/src/filesystem/primitives/tests/net_udp.rs +++ /dev/null @@ -1,451 +0,0 @@ -// This file is derived from Rust's library/std/src/net/udp/tests.rs at -// revision 377d1a984cd2a53327092b90aa1d8b7e22d1e347. - -mod net; -mod sys_common; - -use cap_std::ambient_authority; -use cap_std::net::*; -use net::{next_test_ip4, next_test_ip6}; -use std::io::ErrorKind; -use std::sync::mpsc::channel; -//use sys_common::AsInner; -use std::thread; -use std::time::{Duration, Instant}; - -fn each_ip(f: &mut dyn FnMut(SocketAddr, SocketAddr)) { - f(next_test_ip4(), next_test_ip4()); - f(next_test_ip6(), next_test_ip6()); -} - -macro_rules! t { - ($e:expr) => { - match $e { - Ok(t) => t, - Err(e) => panic!("received error for `{}`: {}", stringify!($e), e), - } - }; -} - -#[test] -fn bind_error() { - let mut pool = Pool::new(); - pool.insert_socket_addr("1.1.1.1:9999".parse().unwrap(), ambient_authority()); - - match pool.bind_udp_socket("1.1.1.1:9999") { - Ok(..) => panic!(), - Err(e) => assert_eq!(e.kind(), ErrorKind::AddrNotAvailable), - } -} - -#[test] -fn socket_smoke_test_ip4() { - each_ip(&mut |server_ip, client_ip| { - let mut client_pool = Pool::new(); - client_pool.insert_socket_addr(client_ip, ambient_authority()); - let mut server_pool = Pool::new(); - server_pool.insert_socket_addr(server_ip, ambient_authority()); - - let (tx1, rx1) = channel(); - let (tx2, rx2) = channel(); - - let p = server_pool.clone(); - let _t = thread::spawn(move || { - let client = t!(client_pool.bind_udp_socket(&client_ip)); - rx1.recv().unwrap(); - t!(p.send_to_udp_socket_addr(&client, &[99], &server_ip)); - tx2.send(()).unwrap(); - }); - - let server = t!(server_pool.bind_udp_socket(&server_ip)); - tx1.send(()).unwrap(); - let mut buf = [0]; - let (nread, src) = t!(server.recv_from(&mut buf)); - assert_eq!(nread, 1); - assert_eq!(buf[0], 99); - assert_eq!(src, client_ip); - rx2.recv().unwrap(); - }) -} - -#[test] -fn socket_name() { - each_ip(&mut |addr, _| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let server = t!(pool.bind_udp_socket(&addr)); - assert_eq!(addr, t!(server.local_addr())); - }) -} - -#[test] -fn socket_peer() { - each_ip(&mut |addr1, addr2| { - let mut pool1 = Pool::new(); - pool1.insert_socket_addr(addr1, ambient_authority()); - let mut pool2 = Pool::new(); - pool2.insert_socket_addr(addr2, ambient_authority()); - - let server = t!(pool1.bind_udp_socket(&addr1)); - assert_eq!( - server.peer_addr().unwrap_err().kind(), - ErrorKind::NotConnected - ); - t!(pool2.connect_udp_socket(&server, &addr2)); - assert_eq!(addr2, t!(server.peer_addr())); - }) -} - -#[test] -fn udp_clone_smoke() { - each_ip(&mut |addr1, addr2| { - let mut pool1 = Pool::new(); - pool1.insert_socket_addr(addr1, ambient_authority()); - let mut pool2 = Pool::new(); - pool2.insert_socket_addr(addr2, ambient_authority()); - - let sock1 = t!(pool1.bind_udp_socket(&addr1)); - let sock2 = t!(pool2.bind_udp_socket(&addr2)); - - let _t = thread::spawn(move || { - let mut buf = [0, 0]; - assert_eq!(sock2.recv_from(&mut buf).unwrap(), (1, addr1)); - assert_eq!(buf[0], 1); - t!(pool1.send_to_udp_socket_addr(&sock2, &[2], &addr1)); - }); - - let sock3 = t!(sock1.try_clone()); - - let (tx1, rx1) = channel(); - let (tx2, rx2) = channel(); - let p = pool2.clone(); - let _t = thread::spawn(move || { - rx1.recv().unwrap(); - t!(p.send_to_udp_socket_addr(&sock3, &[1], &addr2)); - tx2.send(()).unwrap(); - }); - tx1.send(()).unwrap(); - let mut buf = [0, 0]; - assert_eq!(sock1.recv_from(&mut buf).unwrap(), (1, addr2)); - rx2.recv().unwrap(); - }) -} - -#[test] -fn udp_clone_two_read() { - each_ip(&mut |addr1, addr2| { - let mut pool1 = Pool::new(); - pool1.insert_socket_addr(addr1, ambient_authority()); - let mut pool2 = Pool::new(); - pool2.insert_socket_addr(addr2, ambient_authority()); - - let sock1 = t!(pool1.bind_udp_socket(&addr1)); - let sock2 = t!(pool2.bind_udp_socket(&addr2)); - let (tx1, rx) = channel(); - let tx2 = tx1.clone(); - - let _t = thread::spawn(move || { - t!(pool1.send_to_udp_socket_addr(&sock2, &[1], &addr1)); - rx.recv().unwrap(); - t!(pool1.send_to_udp_socket_addr(&sock2, &[2], &addr1)); - rx.recv().unwrap(); - }); - - let sock3 = t!(sock1.try_clone()); - - let (done, rx) = channel(); - let _t = thread::spawn(move || { - let mut buf = [0, 0]; - t!(sock3.recv_from(&mut buf)); - tx2.send(()).unwrap(); - done.send(()).unwrap(); - }); - let mut buf = [0, 0]; - t!(sock1.recv_from(&mut buf)); - tx1.send(()).unwrap(); - - rx.recv().unwrap(); - }) -} - -#[test] -fn udp_clone_two_write() { - each_ip(&mut |addr1, addr2| { - let mut pool1 = Pool::new(); - pool1.insert_socket_addr(addr1, ambient_authority()); - let mut pool2 = Pool::new(); - pool2.insert_socket_addr(addr2, ambient_authority()); - - let sock1 = t!(pool1.bind_udp_socket(&addr1)); - let sock2 = t!(pool2.bind_udp_socket(&addr2)); - - let (tx, rx) = channel(); - let (serv_tx, serv_rx) = channel(); - - let _t = thread::spawn(move || { - let mut buf = [0, 1]; - rx.recv().unwrap(); - t!(sock2.recv_from(&mut buf)); - serv_tx.send(()).unwrap(); - }); - - let sock3 = t!(sock1.try_clone()); - - let (done, rx) = channel(); - let tx2 = tx.clone(); - let p = pool2.clone(); - let _t = thread::spawn(move || { - if p.send_to_udp_socket_addr(&sock3, &[1], &addr2).is_ok() { - let _ = tx2.send(()); - } - done.send(()).unwrap(); - }); - if pool2.send_to_udp_socket_addr(&sock1, &[2], &addr2).is_ok() { - let _ = tx.send(()); - } - drop(tx); - - rx.recv().unwrap(); - serv_rx.recv().unwrap(); - }) -} - -/* Disable this test, as it depends on Rust-internal details. -#[test] -fn debug() { - let name = if cfg!(windows) { "socket" } else { "fd" }; - let socket_addr = next_test_ip4(); - - let mut pool = Pool::new(); - - let udpsock_inner = udpsock.0.socket().as_inner(); - let compare = format!("UdpSocket {{ addr: {:?}, {}: {:?} }}", socket_addr, name, udpsock_inner); - assert_eq!(format!("{:?}", udpsock), compare); -} -*/ - -// FIXME: re-enabled openbsd/netbsd tests once their socket timeout code -// no longer has rounding errors. -// VxWorks ignores SO_SNDTIMEO. -#[cfg_attr( - any(target_os = "netbsd", target_os = "openbsd", target_os = "vxworks"), - ignore -)] -#[test] -fn timeouts() { - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let stream = t!(pool.bind_udp_socket(&addr)); - let dur = Duration::new(15410, 0); - - assert_eq!(None, t!(stream.read_timeout())); - - t!(stream.set_read_timeout(Some(dur))); - assert_eq!(Some(dur), t!(stream.read_timeout())); - - assert_eq!(None, t!(stream.write_timeout())); - - t!(stream.set_write_timeout(Some(dur))); - assert_eq!(Some(dur), t!(stream.write_timeout())); - - t!(stream.set_read_timeout(None)); - assert_eq!(None, t!(stream.read_timeout())); - - t!(stream.set_write_timeout(None)); - assert_eq!(None, t!(stream.write_timeout())); -} - -#[test] -fn test_read_timeout() { - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let stream = t!(pool.bind_udp_socket(&addr)); - t!(stream.set_read_timeout(Some(Duration::from_millis(1000)))); - - let mut buf = [0; 10]; - - let start = Instant::now(); - loop { - let kind = stream - .recv_from(&mut buf) - .err() - .expect("expected error") - .kind(); - if kind != ErrorKind::Interrupted { - assert!( - kind == ErrorKind::WouldBlock || kind == ErrorKind::TimedOut, - "unexpected_error: {:?}", - kind - ); - break; - } - } - assert!(start.elapsed() > Duration::from_millis(400)); -} - -#[test] -fn test_read_with_timeout() { - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let stream = t!(pool.bind_udp_socket(&addr)); - t!(stream.set_read_timeout(Some(Duration::from_millis(1000)))); - - t!(pool.send_to_udp_socket_addr(&stream, b"hello world", &addr)); - - let mut buf = [0; 11]; - t!(stream.recv_from(&mut buf)); - assert_eq!(b"hello world", &buf[..]); - - let start = Instant::now(); - loop { - let kind = stream - .recv_from(&mut buf) - .err() - .expect("expected error") - .kind(); - if kind != ErrorKind::Interrupted { - assert!( - kind == ErrorKind::WouldBlock || kind == ErrorKind::TimedOut, - "unexpected_error: {:?}", - kind - ); - break; - } - } - assert!(start.elapsed() > Duration::from_millis(400)); -} - -// Ensure the `set_read_timeout` and `set_write_timeout` calls return errors -// when passed zero Durations -#[test] -fn test_timeout_zero_duration() { - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let socket = t!(pool.bind_udp_socket(&addr)); - - let result = socket.set_write_timeout(Some(Duration::new(0, 0))); - let err = result.unwrap_err(); - assert_eq!(err.kind(), ErrorKind::InvalidInput); - - let result = socket.set_read_timeout(Some(Duration::new(0, 0))); - let err = result.unwrap_err(); - assert_eq!(err.kind(), ErrorKind::InvalidInput); -} - -#[test] -fn connect_send_recv() { - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let socket = t!(pool.bind_udp_socket(&addr)); - t!(pool.connect_udp_socket(&socket, addr)); - - t!(socket.send(b"hello world")); - - let mut buf = [0; 11]; - t!(socket.recv(&mut buf)); - assert_eq!(b"hello world", &buf[..]); -} - -#[test] -fn connect_send_peek_recv() { - each_ip(&mut |addr, _| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let socket = t!(pool.bind_udp_socket(&addr)); - t!(pool.connect_udp_socket(&socket, addr)); - - t!(socket.send(b"hello world")); - - for _ in 1..3 { - let mut buf = [0; 11]; - let size = t!(socket.peek(&mut buf)); - assert_eq!(b"hello world", &buf[..]); - assert_eq!(size, 11); - } - - let mut buf = [0; 11]; - let size = t!(socket.recv(&mut buf)); - assert_eq!(b"hello world", &buf[..]); - assert_eq!(size, 11); - }) -} - -#[test] -fn peek_from() { - each_ip(&mut |addr, _| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let socket = t!(pool.bind_udp_socket(&addr)); - t!(pool.send_to_udp_socket_addr(&socket, b"hello world", &addr)); - - for _ in 1..3 { - let mut buf = [0; 11]; - let (size, _) = t!(socket.peek_from(&mut buf)); - assert_eq!(b"hello world", &buf[..]); - assert_eq!(size, 11); - } - - let mut buf = [0; 11]; - let (size, _) = t!(socket.recv_from(&mut buf)); - assert_eq!(b"hello world", &buf[..]); - assert_eq!(size, 11); - }) -} - -#[test] -fn ttl() { - let ttl = 100; - - let addr = next_test_ip4(); - - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let stream = t!(pool.bind_udp_socket(&addr)); - - t!(stream.set_ttl(ttl)); - assert_eq!(ttl, t!(stream.ttl())); -} - -#[test] -fn set_nonblocking() { - each_ip(&mut |addr, _| { - let mut pool = Pool::new(); - pool.insert_socket_addr(addr, ambient_authority()); - - let socket = t!(pool.bind_udp_socket(&addr)); - - t!(socket.set_nonblocking(true)); - t!(socket.set_nonblocking(false)); - - t!(pool.connect_udp_socket(&socket, addr)); - - t!(socket.set_nonblocking(false)); - t!(socket.set_nonblocking(true)); - - let mut buf = [0]; - match socket.recv(&mut buf) { - Ok(_) => panic!("expected error"), - Err(ref e) if e.kind() == ErrorKind::WouldBlock => {} - Err(e) => panic!("unexpected error {}", e), - } - }) -} diff --git a/crates/wasi/src/filesystem/primitives/tests/time.rs b/crates/wasi/src/filesystem/primitives/tests/time.rs deleted file mode 100644 index efc032817dd7..000000000000 --- a/crates/wasi/src/filesystem/primitives/tests/time.rs +++ /dev/null @@ -1,200 +0,0 @@ -// This file is derived from Rust's library/std/src/time/tests.rs at revision -// 108e90ca78f052c0c1c49c42a22c85620be19712. - -use cap_std::ambient_authority; -use cap_std::time::{Duration, MonotonicClock, SystemClock}; - -macro_rules! assert_almost_eq { - ($a:expr, $b:expr) => {{ - let (a, b) = ($a, $b); - if a != b { - let (a, b) = if a > b { (a, b) } else { (b, a) }; - assert!( - a - Duration::new(0, 1000) <= b, - "{:?} is not almost equal to {:?}", - a, - b - ); - } - }}; -} - -#[test] -fn instant_monotonic() { - let clock = MonotonicClock::new(ambient_authority()); - let a = clock.now(); - let b = clock.now(); - assert!(b >= a); -} - -#[test] -fn instant_elapsed() { - let clock = MonotonicClock::new(ambient_authority()); - let a = clock.now(); - clock.elapsed(a); -} - -#[test] -fn instant_math() { - let clock = MonotonicClock::new(ambient_authority()); - let a = clock.now(); - let b = clock.now(); - println!("a: {:?}", a); - println!("b: {:?}", b); - let dur = b.duration_since(a); - println!("dur: {:?}", dur); - assert_almost_eq!(b - dur, a); - assert_almost_eq!(a + dur, b); - - let second = Duration::new(1, 0); - assert_almost_eq!(a - second + second, a); - assert_almost_eq!( - a.checked_sub(second).unwrap().checked_add(second).unwrap(), - a - ); - - // checked_add_duration will not panic on overflow - let mut maybe_t = Some(clock.now()); - let max_duration = Duration::from_secs(u64::MAX); - // in case `Instant` can store `>= now + max_duration`. - for _ in 0..2 { - maybe_t = maybe_t.and_then(|t| t.checked_add(max_duration)); - } - assert_eq!(maybe_t, None); - - // checked_add_duration calculates the right time and will work for another - // year - let year = Duration::from_secs(60 * 60 * 24 * 365); - assert_eq!(a + year, a.checked_add(year).unwrap()); -} - -#[test] -fn instant_math_is_associative() { - let clock = MonotonicClock::new(ambient_authority()); - let now = clock.now(); - let offset = Duration::from_millis(5); - // Changing the order of instant math shouldn't change the results, - // especially when the expression reduces to X + identity. - assert_eq!((now + offset) - now, (now - now) + offset); -} - -// Ignore this test for now. As of rust-lang/rust#89926, Rust no longer panics -// on this test. -// -// TODO: Once our MSRV no longer contains the old behavior, we should update -// to the latest version of this test. -#[test] -#[should_panic] -#[ignore] -fn instant_duration_since_panic() { - let clock = MonotonicClock::new(ambient_authority()); - let a = clock.now(); - (a - Duration::new(1, 0)).duration_since(a); -} - -#[test] -fn instant_checked_duration_since_nopanic() { - let clock = MonotonicClock::new(ambient_authority()); - let now = clock.now(); - let earlier = now - Duration::new(1, 0); - let later = now + Duration::new(1, 0); - assert_eq!(earlier.checked_duration_since(now), None); - assert_eq!(later.checked_duration_since(now), Some(Duration::new(1, 0))); - assert_eq!(now.checked_duration_since(now), Some(Duration::new(0, 0))); -} - -#[test] -fn instant_saturating_duration_since_nopanic() { - let clock = MonotonicClock::new(ambient_authority()); - let a = clock.now(); - let ret = (a - Duration::new(1, 0)).saturating_duration_since(a); - assert_eq!(ret, Duration::new(0, 0)); -} - -#[test] -fn system_time_math() { - let clock = SystemClock::new(ambient_authority()); - let a = clock.now(); - let b = clock.now(); - match b.duration_since(a) { - Ok(dur) if dur == Duration::new(0, 0) => { - assert_almost_eq!(a, b); - } - Ok(dur) => { - assert!(b > a); - assert_almost_eq!(b - dur, a); - assert_almost_eq!(a + dur, b); - } - Err(dur) => { - let dur = dur.duration(); - assert!(a > b); - assert_almost_eq!(b + dur, a); - assert_almost_eq!(a - dur, b); - } - } - - let second = Duration::new(1, 0); - assert_almost_eq!(a.duration_since(a - second).unwrap(), second); - assert_almost_eq!(a.duration_since(a + second).unwrap_err().duration(), second); - - assert_almost_eq!(a - second + second, a); - assert_almost_eq!( - a.checked_sub(second).unwrap().checked_add(second).unwrap(), - a - ); - - let one_second_from_epoch = SystemClock::UNIX_EPOCH + Duration::new(1, 0); - let one_second_from_epoch2 = - SystemClock::UNIX_EPOCH + Duration::new(0, 500_000_000) + Duration::new(0, 500_000_000); - assert_eq!(one_second_from_epoch, one_second_from_epoch2); - - // checked_add_duration will not panic on overflow - let mut maybe_t = Some(SystemClock::UNIX_EPOCH); - let max_duration = Duration::from_secs(u64::MAX); - // in case `SystemTime` can store `>= UNIX_EPOCH + max_duration`. - for _ in 0..2 { - maybe_t = maybe_t.and_then(|t| t.checked_add(max_duration)); - } - assert_eq!(maybe_t, None); - - // checked_add_duration calculates the right time and will work for another - // year - let year = Duration::from_secs(60 * 60 * 24 * 365); - assert_eq!(a + year, a.checked_add(year).unwrap()); -} - -#[test] -fn system_time_elapsed() { - let clock = SystemClock::new(ambient_authority()); - let a = clock.now(); - drop(clock.elapsed(a)); -} - -#[test] -fn since_epoch() { - let clock = SystemClock::new(ambient_authority()); - let ts = clock.now(); - let a = ts - .duration_since(SystemClock::UNIX_EPOCH + Duration::new(1, 0)) - .unwrap(); - let b = ts.duration_since(SystemClock::UNIX_EPOCH).unwrap(); - assert!(b > a); - assert_eq!(b - a, Duration::new(1, 0)); - - let thirty_years = Duration::new(1, 0) * 60 * 60 * 24 * 365 * 30; - - // Right now for CI this test is run in an emulator, and apparently the - // aarch64 emulator's sense of time is that we're still living in the - // 70s. This is also true for riscv (also qemu) - // - // Otherwise let's assume that we're all running computers later than - // 2000. - if !cfg!(target_arch = "aarch64") && !cfg!(target_arch = "riscv64") { - assert!(a > thirty_years); - } - - // let's assume that we're all running computers earlier than 2090. - // Should give us ~70 years to fix this! - let hundred_twenty_years = thirty_years * 4; - assert!(a < hundred_twenty_years); -} From 1eb781862a636686c04d6a1ba45fcb5794b8ff58 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 27 Aug 2026 13:52:54 -0700 Subject: [PATCH 14/34] Get tests compiling This commit gets all relevant tests from cap-std compiling against the vendored version of cap-primitives. The goal here is to retain as much test coverage as possible so tests are modified pretty heavily to the new API structure where `cap-primitives` is effectively used instead of `cap-std`'s API surface area. This includes lots of various hlepers and such to keep tests relatively terse and such. --- crates/wasi/Cargo.toml | 1 + crates/wasi/src/filesystem/primitives/mod.rs | 18 - .../src/filesystem/primitives/read_link.rs | 22 - .../primitives/rustix/fs/metadata_ext.rs | 4 +- .../filesystem/primitives/rustix/fs/mod.rs | 38 - .../primitives/tests/canonicalize.rs | 143 -- .../filesystem/primitives/tests/cap_basics.rs | 205 +-- .../primitives/tests/dir_entry_ext.rs | 33 - .../filesystem/primitives/tests/dir_ext.rs | 111 -- .../primitives/tests/file_type_ext.rs | 47 +- .../src/filesystem/primitives/tests/fs.rs | 1318 ++++++---------- .../primitives/tests/fs_additional.rs | 1332 ++++++++--------- .../primitives/tests/helpers/mod.rs | 151 ++ .../primitives/tests/is_file_read_write.rs | 43 - .../primitives/tests/issue_22577.rs | 31 - .../primitives/tests/metadata_ext.rs | 48 +- .../src/filesystem/primitives/tests/mod.rs | 17 +- .../primitives/tests/open_ambient.rs | 29 - .../primitives/tests/paths_containing_nul.rs | 62 +- .../src/filesystem/primitives/tests/rand.rs | 5 - .../filesystem/primitives/tests/readdir.rs | 13 +- .../src/filesystem/primitives/tests/rename.rs | 136 +- .../primitives/tests/rename_directory.rs | 25 +- .../src/filesystem/primitives/tests/reopen.rs | 93 -- .../filesystem/primitives/tests/reopendir.rs | 50 +- .../src/filesystem/primitives/tests/root.rs | 32 - .../src/filesystem/primitives/tests/set.rs | 132 -- .../filesystem/primitives/tests/set_times.rs | 136 +- .../filesystem/primitives/tests/symlinks.rs | 476 +++--- .../filesystem/primitives/tests/sys/mod.rs | 7 - .../primitives/tests/sys/unix/mod.rs | 1 - .../primitives/tests/sys/unix/weak.rs | 238 --- .../primitives/tests/sys_common/io.rs | 25 +- .../tests/sys_common/symlink_junction.rs | 133 +- .../primitives/tests/windows_open.rs | 168 ++- .../primitives/tests/windows_symlinks.rs | 73 +- 36 files changed, 2008 insertions(+), 3388 deletions(-) delete mode 100644 crates/wasi/src/filesystem/primitives/tests/canonicalize.rs delete mode 100644 crates/wasi/src/filesystem/primitives/tests/dir_entry_ext.rs delete mode 100644 crates/wasi/src/filesystem/primitives/tests/dir_ext.rs create mode 100644 crates/wasi/src/filesystem/primitives/tests/helpers/mod.rs delete mode 100644 crates/wasi/src/filesystem/primitives/tests/is_file_read_write.rs delete mode 100644 crates/wasi/src/filesystem/primitives/tests/issue_22577.rs delete mode 100644 crates/wasi/src/filesystem/primitives/tests/open_ambient.rs delete mode 100644 crates/wasi/src/filesystem/primitives/tests/rand.rs delete mode 100644 crates/wasi/src/filesystem/primitives/tests/reopen.rs delete mode 100644 crates/wasi/src/filesystem/primitives/tests/root.rs delete mode 100644 crates/wasi/src/filesystem/primitives/tests/set.rs delete mode 100644 crates/wasi/src/filesystem/primitives/tests/sys/mod.rs delete mode 100644 crates/wasi/src/filesystem/primitives/tests/sys/unix/mod.rs delete mode 100644 crates/wasi/src/filesystem/primitives/tests/sys/unix/weak.rs diff --git a/crates/wasi/Cargo.toml b/crates/wasi/Cargo.toml index 02dd642deb0c..b6260b2b84d5 100644 --- a/crates/wasi/Cargo.toml +++ b/crates/wasi/Cargo.toml @@ -65,6 +65,7 @@ features = [ "Win32_Foundation", "Win32_Storage_FileSystem", "Win32_System_IO", + "Win32_System_Ioctl", "Win32_System_Performance", ] diff --git a/crates/wasi/src/filesystem/primitives/mod.rs b/crates/wasi/src/filesystem/primitives/mod.rs index 84dffd6eecfd..c649fca9071b 100644 --- a/crates/wasi/src/filesystem/primitives/mod.rs +++ b/crates/wasi/src/filesystem/primitives/mod.rs @@ -77,23 +77,5 @@ pub use symlink::symlink; #[cfg(windows)] pub use symlink::{symlink_dir, symlink_file}; pub use system_time_spec::SystemTimeSpec; - -/// Test that `file_path` works on a few miscellaneous directory paths. -#[test] -fn dir_paths() { - use crate::ambient_authority; - - for path in [std::env::current_dir().unwrap(), std::env::temp_dir()] { - let dir = open_ambient_dir(&path, ambient_authority()).unwrap(); - assert_eq!( - file_path(&dir) - .as_ref() - .map(std::fs::canonicalize) - .map(Result::unwrap), - Some(std::fs::canonicalize(path).unwrap()) - ); - } -} - #[cfg(test)] mod tests; diff --git a/crates/wasi/src/filesystem/primitives/read_link.rs b/crates/wasi/src/filesystem/primitives/read_link.rs index 51e49669e24b..9678ea08e1be 100644 --- a/crates/wasi/src/filesystem/primitives/read_link.rs +++ b/crates/wasi/src/filesystem/primitives/read_link.rs @@ -34,25 +34,3 @@ pub fn read_link(start: &fs::File, path: &Path) -> io::Result { result } - -#[cfg(not(windows))] -#[test] -fn test_read_link_contents() { - use io_lifetimes::AsFilelike; - let td = cap_tempfile::tempdir(cap_tempfile::ambient_authority()).unwrap(); - let td_view = &td.as_filelike_view::(); - let valid = [ - "relative/path", - "/some/absolute/path", - "/", - "../", - "basepath", - ]; - for case in valid { - let linkname = Path::new("linkname"); - crate::fs::symlink_contents(case, td_view, linkname).unwrap(); - let contents = crate::fs::read_link_contents(td_view, linkname).unwrap(); - assert_eq!(contents.to_str().unwrap(), case); - crate::fs::remove_file(td_view, linkname).unwrap(); - } -} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/metadata_ext.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/metadata_ext.rs index ddd4f0f6b9cb..f29b7959e20d 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/metadata_ext.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/metadata_ext.rs @@ -177,7 +177,5 @@ fn negative_time() { let system_time = system_time_from_rustix(-1, 1).unwrap(); let d = SystemTime::UNIX_EPOCH.duration_since(system_time).unwrap(); assert_eq!(d.as_secs(), 0); - if !cfg!(emulate_second_only_system) { - assert_eq!(d.subsec_nanos(), 999999999); - } + assert_eq!(d.subsec_nanos(), 999999999); } diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/mod.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/mod.rs index c1c735d80d94..4e336d2f1c82 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/mod.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/mod.rs @@ -126,41 +126,3 @@ pub(crate) use times::{set_times_follow_unchecked, set_times_nofollow_unchecked, pub(crate) const MAX_SYMLINK_EXPANSIONS: u8 = 40; pub(super) use oflags::*; - -/// Test that `file_path` works on a tty path. -#[test] -fn tty_path() { - #[cfg(unix)] - use std::os::unix::fs::FileTypeExt; - - let paths: &[&str] = if cfg!(target_os = "freebsd") { - // On FreeBSD, /dev/{tty,stdin,stdout,stderr} are aliases to different - // real devices. - &["/dev/ttyv0", "/dev/pts/0"] - } else if cfg!(target_os = "illumos") { - // On illumos, /dev/std{in,out,err} only exist if they're open. - &["/dev/tty", "/dev/pts/0"] - } else { - &["/dev/tty", "/dev/stdin", "/dev/stdout", "/dev/stderr"] - }; - - for path in paths { - // Not all host configurations have these, so only test them if we can - // open and canonicalize them, and if they're not FIFOs, which some - // OS's use for stdin/stdout/stderr. - if let Ok(file) = std::fs::File::open(path) { - if !file.metadata().unwrap().file_type().is_fifo() { - if let Ok(canonical) = std::fs::canonicalize(path) { - assert_eq!( - file_path(&file) - .as_ref() - .map(std::fs::canonicalize) - .map(Result::unwrap), - Some(canonical), - "for path {path}, file_path matches canonicalized path" - ); - } - } - } - } -} diff --git a/crates/wasi/src/filesystem/primitives/tests/canonicalize.rs b/crates/wasi/src/filesystem/primitives/tests/canonicalize.rs deleted file mode 100644 index d59f6cce9fac..000000000000 --- a/crates/wasi/src/filesystem/primitives/tests/canonicalize.rs +++ /dev/null @@ -1,143 +0,0 @@ -#[macro_use] -mod sys_common; - -use std::path::Path; -use sys_common::io::tmpdir; - -#[test] -fn canonicalize_dot() { - let tmpdir = tmpdir(); - assert_eq!(check!(tmpdir.canonicalize(".")), Path::new(".")); -} - -#[test] -fn canonicalize_edge_cases() { - let tmpdir = tmpdir(); - assert_eq!(check!(tmpdir.canonicalize(".")), Path::new(".")); - assert_eq!(check!(tmpdir.canonicalize("./")), Path::new(".")); - assert_eq!(check!(tmpdir.canonicalize("./.")), Path::new(".")); - - #[cfg(not(windows))] - error!(tmpdir.canonicalize(""), "No such file"); - #[cfg(windows)] - error!(tmpdir.canonicalize(""), 2); - - #[cfg(not(windows))] - error!(tmpdir.canonicalize("foo"), "No such file"); - #[cfg(windows)] - error!(tmpdir.canonicalize("foo"), 2); - - error_contains!( - tmpdir.canonicalize("/"), - "a path led outside of the filesystem" - ); - error_contains!( - tmpdir.canonicalize("/."), - "a path led outside of the filesystem" - ); - error_contains!( - tmpdir.canonicalize("/.."), - "a path led outside of the filesystem" - ); - error_contains!( - tmpdir.canonicalize("/./"), - "a path led outside of the filesystem" - ); - error_contains!( - tmpdir.canonicalize("/../"), - "a path led outside of the filesystem" - ); - error_contains!( - tmpdir.canonicalize("/../."), - "a path led outside of the filesystem" - ); - error_contains!( - tmpdir.canonicalize("/./."), - "a path led outside of the filesystem" - ); - error_contains!( - tmpdir.canonicalize("/./.."), - "a path led outside of the filesystem" - ); - error_contains!( - tmpdir.canonicalize("/./../"), - "a path led outside of the filesystem" - ); - error_contains!( - tmpdir.canonicalize("/../.."), - "a path led outside of the filesystem" - ); - error_contains!( - tmpdir.canonicalize("/../../"), - "a path led outside of the filesystem" - ); - error_contains!( - tmpdir.canonicalize(".."), - "a path led outside of the filesystem" - ); - error_contains!( - tmpdir.canonicalize("../"), - "a path led outside of the filesystem" - ); - error_contains!( - tmpdir.canonicalize("../."), - "a path led outside of the filesystem" - ); - error_contains!( - tmpdir.canonicalize("./.."), - "a path led outside of the filesystem" - ); - error_contains!( - tmpdir.canonicalize("../.."), - "a path led outside of the filesystem" - ); - - check!(tmpdir.create_dir_all("foo/bar")); - assert_eq!(check!(tmpdir.canonicalize("foo")), Path::new("foo")); - assert_eq!(check!(tmpdir.canonicalize("foo/")), Path::new("foo")); - assert_eq!(check!(tmpdir.canonicalize("foo/")).to_str(), Some("foo")); - assert_eq!(check!(tmpdir.canonicalize("foo/.")), Path::new("foo")); - assert_eq!(check!(tmpdir.canonicalize("foo/./")), Path::new("foo")); - assert_eq!(check!(tmpdir.canonicalize("foo/./")).to_str(), Some("foo")); - assert_eq!(check!(tmpdir.canonicalize("foo/..")), Path::new(".")); - assert_eq!(check!(tmpdir.canonicalize("foo/../")), Path::new(".")); - assert_eq!(check!(tmpdir.canonicalize("foo/../.")), Path::new(".")); - - assert_eq!( - check!(tmpdir.canonicalize("foo/bar")), - Path::new("foo").join("bar"), - ); - assert_eq!( - check!(tmpdir.canonicalize("foo/bar/")), - Path::new("foo").join("bar") - ); - assert_eq!( - check!(tmpdir.canonicalize("foo/bar/")).to_str(), - Path::new("foo").join("bar").to_str(), - ); - assert_eq!( - check!(tmpdir.canonicalize("foo/../foo/bar")), - Path::new("foo").join("bar") - ); - assert_eq!( - check!(tmpdir.canonicalize("foo/../foo/bar/")), - Path::new("foo").join("bar") - ); - assert_eq!( - check!(tmpdir.canonicalize("foo/../foo/bar/")).to_str(), - Path::new("foo").join("bar").to_str() - ); - error_contains!( - tmpdir.canonicalize("foo/../.."), - "a path led outside of the filesystem" - ); - error_contains!( - tmpdir.canonicalize("foo/../../"), - "a path led outside of the filesystem" - ); - - #[cfg(not(windows))] - error!(tmpdir.canonicalize("foo/bar/qux"), "No such file"); - #[cfg(windows)] - error!(tmpdir.canonicalize("foo/bar/qux"), 2); -} diff --git a/crates/wasi/src/filesystem/primitives/tests/cap_basics.rs b/crates/wasi/src/filesystem/primitives/tests/cap_basics.rs index b1f37f0a6ea1..3d9388f7f1ec 100644 --- a/crates/wasi/src/filesystem/primitives/tests/cap_basics.rs +++ b/crates/wasi/src/filesystem/primitives/tests/cap_basics.rs @@ -1,114 +1,123 @@ -#[macro_use] -mod sys_common; +use super::helpers as h; +use super::sys_common::io::tmpdir; +#[allow(unused_imports)] +use super::sys_common::symlink_supported; +use crate::filesystem::primitives as p; -use sys_common::io::tmpdir; -use sys_common::symlink_supported; +use std::path::Path; #[test] fn cap_smoke_test() { let tmpdir = tmpdir(); - check!(tmpdir.create_dir_all("dir/inner")); - check!(tmpdir.write("red.txt", b"hello world\n")); - check!(tmpdir.write("dir/green.txt", b"goodmight moon\n")); - check!(tmpdir.write("dir/inner/blue.txt", b"hey mars\n")); + let dir = h::dir_of(&tmpdir); + check!(h::create_dir_all(&dir, "dir/inner")); + check!(h::write(&dir, "red.txt", b"hello world\n")); + check!(h::write(&dir, "dir/green.txt", b"goodmight moon\n")); + check!(h::write(&dir, "dir/inner/blue.txt", b"hey mars\n")); - let inner = check!(tmpdir.open_dir("dir/inner")); + let inner = check!(p::open_dir(&dir, Path::new("dir/inner"))); - check!(tmpdir.open("red.txt")); + check!(h::open(&dir, "red.txt")); #[cfg(not(windows))] - error!(tmpdir.open("blue.txt"), "No such file"); + error!(h::open(&dir, "blue.txt"), "No such file"); #[cfg(windows)] - error!(tmpdir.open("blue.txt"), 2); + error!(h::open(&dir, "blue.txt"), 2); #[cfg(not(windows))] - error!(tmpdir.open("green.txt"), "No such file"); + error!(h::open(&dir, "green.txt"), "No such file"); #[cfg(windows)] - error!(tmpdir.open("green.txt"), 2); + error!(h::open(&dir, "green.txt"), 2); - check!(tmpdir.open("./red.txt")); + check!(h::open(&dir, "./red.txt")); #[cfg(not(windows))] - error!(tmpdir.open("./blue.txt"), "No such file"); + error!(h::open(&dir, "./blue.txt"), "No such file"); #[cfg(windows)] - error!(tmpdir.open("./blue.txt"), 2); + error!(h::open(&dir, "./blue.txt"), 2); #[cfg(not(windows))] - error!(tmpdir.open("./green.txt"), "No such file"); + error!(h::open(&dir, "./green.txt"), "No such file"); #[cfg(windows)] - error!(tmpdir.open("./green.txt"), 2); + error!(h::open(&dir, "./green.txt"), 2); #[cfg(not(windows))] - error!(tmpdir.open("dir/red.txt"), "No such file"); + error!(h::open(&dir, "dir/red.txt"), "No such file"); #[cfg(windows)] - error!(tmpdir.open("dir/red.txt"), 2); + error!(h::open(&dir, "dir/red.txt"), 2); - check!(tmpdir.open("dir/green.txt")); + check!(h::open(&dir, "dir/green.txt")); #[cfg(not(windows))] - error!(tmpdir.open("dir/blue.txt"), "No such file"); + error!(h::open(&dir, "dir/blue.txt"), "No such file"); #[cfg(windows)] - error!(tmpdir.open("dir/blue.txt"), 2); + error!(h::open(&dir, "dir/blue.txt"), 2); #[cfg(not(windows))] - error!(tmpdir.open("dir/inner/red.txt"), "No such file"); + error!(h::open(&dir, "dir/inner/red.txt"), "No such file"); #[cfg(windows)] - error!(tmpdir.open("dir/inner/red.txt"), 2); + error!(h::open(&dir, "dir/inner/red.txt"), 2); #[cfg(not(windows))] - error!(tmpdir.open("dir/inner/green.txt"), "No such file"); + error!(h::open(&dir, "dir/inner/green.txt"), "No such file"); #[cfg(windows)] - error!(tmpdir.open("dir/inner/green.txt"), 2); + error!(h::open(&dir, "dir/inner/green.txt"), 2); - check!(tmpdir.open("dir/inner/blue.txt")); + check!(h::open(&dir, "dir/inner/blue.txt")); - check!(tmpdir.open("dir/../red.txt")); - check!(tmpdir.open("dir/inner/../../red.txt")); - check!(tmpdir.open("dir/inner/../inner/../../red.txt")); + check!(h::open(&dir, "dir/../red.txt")); + check!(h::open(&dir, "dir/inner/../../red.txt")); + check!(h::open(&dir, "dir/inner/../inner/../../red.txt")); #[cfg(not(windows))] - error!(inner.open("red.txt"), "No such file"); + error!(h::open(&inner, "red.txt"), "No such file"); #[cfg(windows)] - error!(inner.open("red.txt"), 2); + error!(h::open(&inner, "red.txt"), 2); #[cfg(not(windows))] - error!(inner.open("green.txt"), "No such file"); + error!(h::open(&inner, "green.txt"), "No such file"); #[cfg(windows)] - error!(inner.open("green.txt"), 2); + error!(h::open(&inner, "green.txt"), 2); error_contains!( - inner.open("../inner/blue.txt"), + h::open(&inner, "../inner/blue.txt"), "a path led outside of the filesystem" ); error_contains!( - inner.open("../inner/red.txt"), + h::open(&inner, "../inner/red.txt"), "a path led outside of the filesystem" ); #[cfg(not(windows))] - error!(inner.open_dir(""), "No such file"); + error!(p::open_dir(&inner, Path::new("")), "No such file"); #[cfg(windows)] - error!(inner.open_dir(""), 2); + error!(p::open_dir(&inner, Path::new("")), 2); - error_contains!(inner.open_dir("/"), "a path led outside of the filesystem"); error_contains!( - inner.open_dir("/etc/services"), + p::open_dir(&inner, Path::new("/")), "a path led outside of the filesystem" ); - check!(inner.open_dir(".")); - check!(inner.open_dir("./")); - check!(inner.open_dir("./.")); - error_contains!(inner.open_dir(".."), "a path led outside of the filesystem"); error_contains!( - inner.open_dir("../"), + p::open_dir(&inner, Path::new("/etc/services")), "a path led outside of the filesystem" ); + check!(p::open_dir(&inner, Path::new("."))); + check!(p::open_dir(&inner, Path::new("./"))); + check!(p::open_dir(&inner, Path::new("./."))); error_contains!( - inner.open_dir("../."), + p::open_dir(&inner, Path::new("..")), "a path led outside of the filesystem" ); error_contains!( - inner.open_dir("./.."), + p::open_dir(&inner, Path::new("../")), + "a path led outside of the filesystem" + ); + error_contains!( + p::open_dir(&inner, Path::new("../.")), + "a path led outside of the filesystem" + ); + error_contains!( + p::open_dir(&inner, Path::new("./..")), "a path led outside of the filesystem" ); } @@ -116,67 +125,67 @@ fn cap_smoke_test() { #[test] fn symlinks() { #[cfg(windows)] - use cap_fs_ext::DirExt; - if !symlink_supported() { return; } let tmpdir = tmpdir(); - check!(tmpdir.create_dir_all("dir/inner")); - check!(tmpdir.write("red.txt", b"hello world\n")); - check!(tmpdir.write("dir/green.txt", b"goodmight moon\n")); - check!(tmpdir.write("dir/inner/blue.txt", b"hey mars\n")); - let inner = check!(tmpdir.open_dir("dir/inner")); + let dir = h::dir_of(&tmpdir); + check!(h::create_dir_all(&dir, "dir/inner")); + check!(h::write(&dir, "red.txt", b"hello world\n")); + check!(h::write(&dir, "dir/green.txt", b"goodmight moon\n")); + check!(h::write(&dir, "dir/inner/blue.txt", b"hey mars\n")); - check!(tmpdir.symlink("dir", "link")); + let inner = check!(p::open_dir(&dir, Path::new("dir/inner"))); + + check!(h::symlink(&dir, "dir", "link")); #[cfg(not(windows))] - check!(tmpdir.symlink("does_not_exist", "badlink")); + check!(h::symlink(&dir, "does_not_exist", "badlink")); - check!(tmpdir.open("link/../red.txt")); - check!(tmpdir.open("link/green.txt")); - check!(tmpdir.open("link/inner/blue.txt")); + check!(h::open(&dir, "link/../red.txt")); + check!(h::open(&dir, "link/green.txt")); + check!(h::open(&dir, "link/inner/blue.txt")); #[cfg(not(windows))] { - error_contains!(tmpdir.open("link/red.txt"), "No such file"); - error_contains!(tmpdir.open("link/../green.txt"), "No such file"); + error_contains!(h::open(&dir, "link/red.txt"), "No such file"); + error_contains!(h::open(&dir, "link/../green.txt"), "No such file"); } #[cfg(windows)] { error_contains!( - tmpdir.open("link/red.txt"), + h::open(&dir, "link/red.txt"), "The system cannot find the file specified." ); error_contains!( - tmpdir.open("link/../green.txt"), + h::open(&dir, "link/../green.txt"), "The system cannot find the file specified." ); } - check!(tmpdir.open("./dir/.././/link/..///./red.txt")); - check!(tmpdir.open("link/inner/../inner/../../red.txt")); + check!(h::open(&dir, "./dir/.././/link/..///./red.txt")); + check!(h::open(&dir, "link/inner/../inner/../../red.txt")); error_contains!( - inner.open("../inner/../inner/../../link/other.txt"), + h::open(&inner, "../inner/../inner/../../link/other.txt"), "a path led outside of the filesystem" ); #[cfg(not(windows))] { error_contains!( - tmpdir.open("./dir/.././/link/..///./not.txt"), + h::open(&dir, "./dir/.././/link/..///./not.txt"), "No such file" ); - error_contains!(tmpdir.open("link/other.txt"), "No such file"); - error_contains!(tmpdir.open("badlink/../red.txt"), "No such file"); + error_contains!(h::open(&dir, "link/other.txt"), "No such file"); + error_contains!(h::open(&dir, "badlink/../red.txt"), "No such file"); } #[cfg(windows)] { error_contains!( - tmpdir.open("./dir/.././/link/..///./not.txt"), + h::open(&dir, "./dir/.././/link/..///./not.txt"), "The system cannot find the file specified." ); error_contains!( - tmpdir.open("link/other.txt"), + h::open(&dir, "link/other.txt"), "The system cannot find the file specified." ); } @@ -185,43 +194,51 @@ fn symlinks() { #[test] #[cfg(not(windows))] fn symlink_loop() { - #[cfg(windows)] - use cap_fs_ext::DirExt; - let tmpdir = tmpdir(); - check!(tmpdir.symlink("link", "link")); + + let dir = h::dir_of(&tmpdir); + check!(h::symlink(&dir, "link", "link")); // TODO: Check the error message - error_contains!(tmpdir.open("link"), ""); + error_contains!(h::open(&dir, "link"), ""); } #[test] fn symlink_loop_from_rename() { #[cfg(windows)] - use cap_fs_ext::DirExt; - if !symlink_supported() { return; } let tmpdir = tmpdir(); - check!(tmpdir.create("file")); - check!(tmpdir.symlink("file", "link")); - check!(tmpdir.open("link")); - check!(tmpdir.rename("file", &tmpdir, "renamed")); - error_contains!(tmpdir.open("link"), ""); - check!(tmpdir.rename("link", &tmpdir, "file")); - error_contains!(tmpdir.open("file"), ""); - check!(tmpdir.rename("file", &tmpdir, "link")); - error_contains!(tmpdir.open("link"), ""); - check!(tmpdir.rename("renamed", &tmpdir, "file")); - check!(tmpdir.open("link")); + + let dir = h::dir_of(&tmpdir); + check!(h::create(&dir, "file")); + check!(h::symlink(&dir, "file", "link")); + check!(h::open(&dir, "link")); + check!(p::rename( + &dir, + Path::new("file"), + &dir, + Path::new("renamed") + )); + error_contains!(h::open(&dir, "link"), ""); + check!(p::rename(&dir, Path::new("link"), &dir, Path::new("file"))); + error_contains!(h::open(&dir, "file"), ""); + check!(p::rename(&dir, Path::new("file"), &dir, Path::new("link"))); + error_contains!(h::open(&dir, "link"), ""); + check!(p::rename( + &dir, + Path::new("renamed"), + &dir, + Path::new("file") + )); + check!(h::open(&dir, "link")); } #[cfg(target_os = "linux")] #[test] fn proc_self_fd() { - let fd = check!(std::fs::File::open("/proc/self/fd")); - let dir = cap_std::fs::Dir::from_std_file(fd); + let dir = check!(std::fs::File::open("/proc/self/fd")); // This should fail with "too many levels of symbolic links". - dir.open("0").unwrap_err(); + h::open(&dir, "0").unwrap_err(); } diff --git a/crates/wasi/src/filesystem/primitives/tests/dir_entry_ext.rs b/crates/wasi/src/filesystem/primitives/tests/dir_entry_ext.rs deleted file mode 100644 index 5b36b6a457f2..000000000000 --- a/crates/wasi/src/filesystem/primitives/tests/dir_entry_ext.rs +++ /dev/null @@ -1,33 +0,0 @@ -// This file contains tests for `cap_fs_ext::FileTypeExt`. - -#![cfg_attr(all(windows, windows_by_handle), feature(windows_by_handle))] - -#[macro_use] -mod sys_common; - -use sys_common::io::tmpdir; - -#[test] -fn test_dir_entry_ext() { - let tmpdir = tmpdir(); - check!(tmpdir.create("a")); - - // First try with the regular `metadata()`. All nones. - #[cfg(all(windows, windows_by_handle))] - for entry in check!(tmpdir.entries()) { - use cap_std::fs::MetadataExt; - let entry = check!(entry); - assert!(check!(entry.metadata()).volume_serial_number().is_none()); - assert!(check!(entry.metadata()).number_of_links().is_none()); - assert!(check!(entry.metadata()).file_index().is_none()); - } - - // Now try with `full_metadata()`. - for entry in check!(tmpdir.entries()) { - use cap_fs_ext::{DirEntryExt, MetadataExt}; - let entry = check!(entry); - check!(entry.full_metadata()).dev(); - check!(entry.full_metadata()).nlink(); - check!(entry.full_metadata()).ino(); - } -} diff --git a/crates/wasi/src/filesystem/primitives/tests/dir_ext.rs b/crates/wasi/src/filesystem/primitives/tests/dir_ext.rs deleted file mode 100644 index 99d376bc0dac..000000000000 --- a/crates/wasi/src/filesystem/primitives/tests/dir_ext.rs +++ /dev/null @@ -1,111 +0,0 @@ -#[macro_use] -mod sys_common; - -use cap_fs_ext::DirExt; -use cap_tempfile::{ambient_authority, TempDir}; - -use sys_common::symlink_supported; - -#[test] -fn remove_file() { - let tempdir = TempDir::new(ambient_authority()).expect("create tempdir"); - let file = tempdir.create("file").expect("create file to delete"); - drop(file); - tempdir.remove_file_or_symlink("file").expect("delete file"); - assert!(!tempdir.exists("file"), "deletion worked"); -} - -#[test] -fn remove_symlink_to_file() { - if !symlink_supported() { - return; - } - - let tempdir = TempDir::new(ambient_authority()).expect("create tempdir"); - let target = tempdir.create("target").expect("create target file"); - drop(target); - tempdir.symlink("target", "link").expect("create symlink"); - assert!(tempdir.exists("link"), "link exists"); - tempdir - .remove_file_or_symlink("link") - .expect("delete symlink"); - assert!(!tempdir.exists("link"), "link deleted"); - assert!(tempdir.exists("target"), "target not deleted"); -} - -#[test] -fn remove_symlink_to_dir() { - if !symlink_supported() { - return; - } - - let tempdir = TempDir::new(ambient_authority()).expect("create tempdir"); - { - let _target = tempdir.create_dir("target").expect("create target dir"); - } - tempdir.symlink("target", "link").expect("create symlink"); - assert!(tempdir.exists("link"), "link exists"); - tempdir - .remove_file_or_symlink("link") - .expect("delete symlink"); - assert!(!tempdir.exists("link"), "link deleted"); - assert!(tempdir.exists("target"), "target not deleted"); -} - -#[test] -fn do_not_remove_dir() { - let tempdir = TempDir::new(ambient_authority()).expect("create tempdir"); - { - let _subdir = tempdir.create_dir("subdir").expect("create dir"); - } - assert!(tempdir.exists("subdir"), "subdir created"); - tempdir - .remove_file_or_symlink("subdir") - .expect_err("should not delete empty directory"); - assert!(tempdir.exists("subdir"), "subdir not deleted"); -} - -#[test] -fn test_set_symlink_permissions() { - use crate::sys_common::io::tmpdir; - use cap_fs_ext::DirExt; - - let tmpdir = tmpdir(); - check!(tmpdir.create_dir("dir")); - let _file = check!(tmpdir.create("file")); - let _other = check!(tmpdir.create("other")); - check!(tmpdir.symlink_file("file", "symlink")); - - let permissions = tmpdir.metadata("other").unwrap().permissions(); - assert!(!permissions.readonly()); - let mut permissions = tmpdir.metadata("file").unwrap().permissions(); - assert!(!permissions.readonly()); - - permissions.set_readonly(true); - - match tmpdir.set_symlink_permissions("symlink", permissions.clone()) { - Ok(()) => {} - Err(err) if err.raw_os_error() == Some(libc::EOPNOTSUPP) => return, - Err(err) => Err(err).unwrap(), - } - tmpdir - .set_symlink_permissions("other", permissions.clone()) - .unwrap(); - - assert!(!tmpdir.metadata("file").unwrap().permissions().readonly()); - assert!(tmpdir.metadata("other").unwrap().permissions().readonly()); -} - -#[test] -fn test_access() { - use crate::sys_common::io::tmpdir; - use cap_fs_ext::AccessType; - use cap_fs_ext::DirExt; - - let tmpdir = tmpdir(); - check!(tmpdir.create_dir("dir")); - let _file = check!(tmpdir.create("file")); - - tmpdir.access("file", AccessType::Exists).unwrap(); - tmpdir.access("nope", AccessType::Exists).unwrap_err(); -} diff --git a/crates/wasi/src/filesystem/primitives/tests/file_type_ext.rs b/crates/wasi/src/filesystem/primitives/tests/file_type_ext.rs index 94e52f5ddcc7..f34482f96bf4 100644 --- a/crates/wasi/src/filesystem/primitives/tests/file_type_ext.rs +++ b/crates/wasi/src/filesystem/primitives/tests/file_type_ext.rs @@ -1,30 +1,33 @@ -// This file contains tests for `cap_fs_ext::FileTypeExt`. +// This file contains tests for `FileTypeExt`. -#[macro_use] -mod sys_common; - -use cap_fs_ext::FileTypeExt; -use sys_common::io::tmpdir; +use super::sys_common::io::tmpdir; +#[cfg(unix)] +use crate::filesystem::primitives::FileTypeExt; +use crate::filesystem::primitives::{Metadata, OpenOptions, open, open_ambient_dir}; +use ambient_authority::ambient_authority; +use std::path::Path; #[test] fn test_file_type_ext() { let tmpdir = tmpdir(); - let a = check!(tmpdir.create("a")); - - let tmpdir_metadata = check!(tmpdir.dir_metadata()); - let a_metadata = check!(a.metadata()); - - assert!(!tmpdir_metadata.file_type().is_char_device()); - assert!(!a_metadata.file_type().is_char_device()); - - assert!(!tmpdir_metadata.file_type().is_block_device()); - assert!(!a_metadata.file_type().is_block_device()); - - assert!(!tmpdir_metadata.file_type().is_fifo()); - assert!(!a_metadata.file_type().is_fifo()); - - assert!(!tmpdir_metadata.file_type().is_socket()); - assert!(!a_metadata.file_type().is_socket()); + let dir = check!(open_ambient_dir(tmpdir.path(), ambient_authority())); + let a = check!(open( + &dir, + Path::new("a"), + OpenOptions::new().write(true).create(true).truncate(true) + )); + + let tmpdir_metadata = check!(Metadata::from_file(&dir)); + let a_metadata = check!(Metadata::from_file(&a)); + + #[cfg(unix)] + { + assert!(!tmpdir_metadata.file_type().is_char_device()); + assert!(!a_metadata.file_type().is_char_device()); + + assert!(!tmpdir_metadata.file_type().is_block_device()); + assert!(!a_metadata.file_type().is_block_device()); + } assert!(tmpdir_metadata.file_type().is_dir()); assert!(!a_metadata.file_type().is_dir()); diff --git a/crates/wasi/src/filesystem/primitives/tests/fs.rs b/crates/wasi/src/filesystem/primitives/tests/fs.rs index 2009d4756165..daaa9ae0030a 100644 --- a/crates/wasi/src/filesystem/primitives/tests/fs.rs +++ b/crates/wasi/src/filesystem/primitives/tests/fs.rs @@ -1,66 +1,19 @@ // This file is derived from Rust's library/std/src/fs/tests.rs at revision // e4b1d5841494d6eb7f4944c91a057e16b0f0a9ea. -#![cfg_attr(io_error_uncategorized, feature(io_error_uncategorized))] - -#[macro_use] -mod sys_common; -#[macro_use] -mod sys; - +use super::helpers as h; +use super::sys_common::io::tmpdir; +use super::sys_common::symlink_junction; +use crate::filesystem::primitives as p; +use rand::Rng; +use std::fs::File; use std::io::prelude::*; - -#[cfg(target_os = "macos")] -use crate::sys::weak::weak; -use cap_std::ambient_authority; -use cap_std::fs::{self, Dir, OpenOptions}; -#[cfg(target_os = "macos")] -use libc::{c_char, c_int}; -use std::io::{self, ErrorKind, SeekFrom}; +use std::io::{ErrorKind, SeekFrom}; +#[cfg(unix)] +use std::os::unix::fs::FileExt; use std::path::{Path, PathBuf}; use std::str; -use std::sync::Arc; -#[cfg(not(racy_asserts))] // racy asserts are racy use std::thread; -use std::time::{Duration, Instant}; -use sys_common::io::{tmpdir, TempDir}; -use sys_common::symlink_junction; - -use rand::rngs::StdRng; -use rand::{RngCore, SeedableRng}; - -#[cfg(not(windows))] -fn symlink_contents, Q: AsRef>( - src: P, - tmpdir: &TempDir, - dst: Q, -) -> io::Result<()> { - tmpdir.symlink_contents(src, dst) -} -#[cfg(not(windows))] -fn symlink_dir, Q: AsRef>(src: P, tmpdir: &TempDir, dst: Q) -> io::Result<()> { - tmpdir.symlink(src, dst) -} -#[cfg(not(windows))] -fn symlink_file, Q: AsRef>( - src: P, - tmpdir: &TempDir, - dst: Q, -) -> io::Result<()> { - tmpdir.symlink(src, dst) -} -#[cfg(windows)] -fn symlink_dir, Q: AsRef>(src: P, tmpdir: &TempDir, dst: Q) -> io::Result<()> { - tmpdir.symlink_dir(src, dst) -} -#[cfg(windows)] -fn symlink_file, Q: AsRef>( - src: P, - tmpdir: &TempDir, - dst: Q, -) -> io::Result<()> { - tmpdir.symlink_file(src, dst) -} // Several test fail on windows if the user does not have permission to // create symlinks (the `SeCreateSymbolicLinkPrivilege`). Instead of @@ -68,41 +21,188 @@ fn symlink_file, Q: AsRef>( // have permission, and return otherwise. This way, we still don't run these // tests most of the time, but at least we do if the user has the right // permissions. -pub fn got_symlink_permission(tmpdir: &TempDir) -> bool { +pub fn got_symlink_permission(tmpdir: &File) -> bool { if cfg!(unix) { return true; } let link = "some_hopefully_unique_link_name"; - match symlink_file(r"nonexisting_target", tmpdir, link) { + match h::symlink_file(tmpdir, r"nonexisting_target", link) { // ERROR_PRIVILEGE_NOT_HELD = 1314 Err(ref err) if err.raw_os_error() == Some(1314) => false, Ok(_) | Err(_) => true, } } -#[cfg(target_os = "macos")] fn able_to_not_follow_symlinks_while_hard_linking() -> bool { - weak!(fn linkat(c_int, *const c_char, c_int, *const c_char, c_int) -> c_int); - linkat.get().is_some() + return true; } -#[cfg(not(target_os = "macos"))] -fn able_to_not_follow_symlinks_while_hard_linking() -> bool { - return true; +#[test] +fn open_directory_with_truncate_is_error() { + let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); + let mut options = p::OpenOptions::new(); + // The `maybe_dir` part of this test is gone along with the option itself. + options.truncate(true).read(true).write(true); + p::create_dir(&start, Path::new("test"), &p::DirOptions::new()).unwrap(); + assert!(p::open(&start, Path::new("test"), &options).is_err()); +} + +#[test] +fn dir_entry_methods() { + let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); + + h::create_dir_all(&start, "a").unwrap(); + h::create(&start, "b").unwrap(); + + // `DirEntry::file_type` is gone; the metadata checks still cover this. + for file in h::read_dir(&start, ".").unwrap().map(|f| f.unwrap()) { + let fname = file.file_name(); + match fname.to_str() { + Some("a") => { + assert!(file.metadata().unwrap().is_dir()); + } + Some("b") => { + assert!(file.metadata().unwrap().file_type().is_file()); + } + f => panic!("unknown file name: {:?}", f), + } + } +} + +#[test] +fn open_flavors() { + use crate::filesystem::primitives::OpenOptions as OO; + fn c(t: &T) -> T { + t.clone() + } + + let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); + + let mut r = OO::new(); + r.read(true); + let mut w = OO::new(); + w.write(true); + let mut rw = OO::new(); + rw.read(true).write(true); + + #[cfg(windows)] + let invalid_options = 87; // ERROR_INVALID_PARAMETER + #[cfg(any(all(unix, not(target_os = "vxworks")), target_os = "wasi"))] + let invalid_options = "Invalid argument"; + #[cfg(target_os = "vxworks")] + let invalid_options = "invalid argument"; + + // Test various combinations of creation modes and access modes. + // + // Allowed: + // creation mode | read | write | read-write | + // | :-----------------------|:-----:|:-----:|:----------:| + // not set (open existing) | X | X | X | + // create | | X | X | + // truncate | | X | X | + // create and truncate | | X | X | + // create_new | | X | X | + // + // tested in reverse order, so 'create_new' creates the file, and 'open + // existing' opens it. + // + // The append and read-append rows are not covered: `OpenOptions::append` + // was dropped in this vendoring. + + // write-only + check!(p::open(&start, Path::new("a"), c(&w).create_new(true))); + check!(p::open( + &start, + Path::new("a"), + c(&w).create(true).truncate(true) + )); + check!(p::open(&start, Path::new("a"), c(&w).truncate(true))); + check!(p::open(&start, Path::new("a"), c(&w).create(true))); + check!(p::open(&start, Path::new("a"), &c(&w))); + + // read-only + error!( + p::open(&start, Path::new("b"), c(&r).create_new(true)), + invalid_options + ); + error!( + p::open(&start, Path::new("b"), c(&r).create(true).truncate(true)), + invalid_options + ); + error!( + p::open(&start, Path::new("b"), c(&r).truncate(true)), + invalid_options + ); + error!( + p::open(&start, Path::new("b"), c(&r).create(true)), + invalid_options + ); + check!(p::open(&start, Path::new("a"), &c(&r))); // try opening the file created with write_only + + // read-write + check!(p::open(&start, Path::new("c"), c(&rw).create_new(true))); + check!(p::open( + &start, + Path::new("c"), + c(&rw).create(true).truncate(true) + )); + check!(p::open(&start, Path::new("c"), c(&rw).truncate(true))); + check!(p::open(&start, Path::new("c"), c(&rw).create(true))); + check!(p::open(&start, Path::new("c"), &c(&rw))); + + // Test opening a file without setting an access mode + let mut blank = OO::new(); + error!( + p::open(&start, Path::new("f"), blank.create(true)), + invalid_options + ); + + // Test write works + check!(check!(h::create(&start, "h")).write("foobar".as_bytes())); + + // Test write fails for read-only + check!(p::open(&start, Path::new("h"), &r)); + { + let mut f = check!(p::open(&start, Path::new("h"), &r)); + assert!(f.write("wut".as_bytes()).is_err()); + } + + // Test write overwrites + { + let mut f = check!(p::open(&start, Path::new("h"), &c(&w))); + check!(f.write("baz".as_bytes())); + } + { + let mut f = check!(p::open(&start, Path::new("h"), &c(&r))); + let mut b = vec![0; 6]; + check!(f.read(&mut b)); + assert_eq!(b, "bazbar".as_bytes()); + } + + // Test truncate works + { + let mut f = check!(p::open(&start, Path::new("h"), c(&w).truncate(true))); + check!(f.write("foo".as_bytes())); + } + assert_eq!(check!(h::metadata(&start, "h")).len(), 3); } #[test] fn file_test_io_smoke_test() { let message = "it's alright. have a good time"; let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let filename = "file_rt_io_file_test.txt"; { - let mut write_stream = check!(tmpdir.create(filename)); + let mut write_stream = check!(h::create(&start, filename)); check!(write_stream.write(message.as_bytes())); } { - let mut read_stream = check!(tmpdir.open(filename)); + let mut read_stream = check!(h::open(&start, filename)); let mut read_buf = [0; 1028]; let read_str = match check!(read_stream.read(&mut read_buf)) { 0 => panic!("shouldn't happen"), @@ -110,14 +210,15 @@ fn file_test_io_smoke_test() { }; assert_eq!(read_str, message); } - check!(tmpdir.remove_file(filename)); + check!(p::remove_file(&start, Path::new(filename))); } #[test] fn invalid_path_raises() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let filename = "file_that_does_not_exist.txt"; - let result = tmpdir.open(filename); + let result = h::open(&start, filename); #[cfg(any(all(unix, not(target_os = "vxworks")), target_os = "wasi"))] error!(result, "No such file or directory"); @@ -130,9 +231,10 @@ fn invalid_path_raises() { #[test] fn file_test_iounlinking_invalid_path_should_raise_condition() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let filename = "file_another_file_that_does_not_exist.txt"; - let result = tmpdir.remove_file(filename); + let result = p::remove_file(&start, Path::new(filename)); #[cfg(any(all(unix, not(target_os = "vxworks")), target_os = "wasi"))] error!(result, "No such file or directory"); @@ -147,13 +249,14 @@ fn file_test_io_non_positional_read() { let message: &str = "ten-four"; let mut read_mem = [0; 8]; let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let filename = "file_rt_io_file_test_positional.txt"; { - let mut rw_stream = check!(tmpdir.create(filename)); + let mut rw_stream = check!(h::create(&start, filename)); check!(rw_stream.write(message.as_bytes())); } { - let mut read_stream = check!(tmpdir.open(filename)); + let mut read_stream = check!(h::open(&start, filename)); { let read_buf = &mut read_mem[0..4]; check!(read_stream.read(read_buf)); @@ -163,7 +266,7 @@ fn file_test_io_non_positional_read() { check!(read_stream.read(read_buf)); } } - check!(tmpdir.remove_file(filename)); + check!(p::remove_file(&start, Path::new(filename))); let read_str = str::from_utf8(&read_mem).unwrap(); assert_eq!(read_str, message); } @@ -176,19 +279,20 @@ fn file_test_io_seek_and_tell_smoke_test() { let tell_pos_pre_read; let tell_pos_post_read; let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let filename = "file_rt_io_file_test_seeking.txt"; { - let mut rw_stream = check!(tmpdir.create(filename)); + let mut rw_stream = check!(h::create(&start, filename)); check!(rw_stream.write(message.as_bytes())); } { - let mut read_stream = check!(tmpdir.open(filename)); + let mut read_stream = check!(h::open(&start, filename)); check!(read_stream.seek(SeekFrom::Start(set_cursor))); tell_pos_pre_read = check!(read_stream.seek(SeekFrom::Current(0))); check!(read_stream.read(&mut read_mem)); tell_pos_post_read = check!(read_stream.seek(SeekFrom::Current(0))); } - check!(tmpdir.remove_file(filename)); + check!(p::remove_file(&start, Path::new(filename))); let read_str = str::from_utf8(&read_mem).unwrap(); assert_eq!(read_str, &message[4..8]); assert_eq!(tell_pos_pre_read, set_cursor); @@ -203,18 +307,19 @@ fn file_test_io_seek_and_write() { let seek_idx = 3; let mut read_mem = [0; 13]; let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let filename = "file_rt_io_file_test_seek_and_write.txt"; { - let mut rw_stream = check!(tmpdir.create(filename)); + let mut rw_stream = check!(h::create(&start, filename)); check!(rw_stream.write(initial_msg.as_bytes())); check!(rw_stream.seek(SeekFrom::Start(seek_idx))); check!(rw_stream.write(overwrite_msg.as_bytes())); } { - let mut read_stream = check!(tmpdir.open(filename)); + let mut read_stream = check!(h::open(&start, filename)); check!(read_stream.read(&mut read_mem)); } - check!(tmpdir.remove_file(filename)); + check!(p::remove_file(&start, Path::new(filename))); let read_str = str::from_utf8(&read_mem).unwrap(); assert!(read_str == final_msg); } @@ -228,13 +333,14 @@ fn file_test_io_seek_shakedown() { let chunk_three: &str = "zxcv"; let mut read_mem = [0; 4]; let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let filename = "file_rt_io_file_test_seek_shakedown.txt"; { - let mut rw_stream = check!(tmpdir.create(filename)); + let mut rw_stream = check!(h::create(&start, filename)); check!(rw_stream.write(initial_msg.as_bytes())); } { - let mut read_stream = check!(tmpdir.open(filename)); + let mut read_stream = check!(h::open(&start, filename)); check!(read_stream.seek(SeekFrom::End(-4))); check!(read_stream.read(&mut read_mem)); @@ -248,33 +354,34 @@ fn file_test_io_seek_shakedown() { check!(read_stream.read(&mut read_mem)); assert_eq!(str::from_utf8(&read_mem).unwrap(), chunk_one); } - check!(tmpdir.remove_file(filename)); + check!(p::remove_file(&start, Path::new(filename))); } #[test] fn file_test_io_eof() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let filename = "file_rt_io_file_test_eof.txt"; let mut buf = [0; 256]; { - let oo = OpenOptions::new() + let oo = p::OpenOptions::new() .create_new(true) .write(true) .read(true) .clone(); - let mut rw = check!(tmpdir.open_with(filename, &oo)); + let mut rw = check!(p::open(&start, Path::new(filename), &oo)); assert_eq!(check!(rw.read(&mut buf)), 0); assert_eq!(check!(rw.read(&mut buf)), 0); } - check!(tmpdir.remove_file(filename)); + check!(p::remove_file(&start, Path::new(filename))); } #[test] #[cfg(unix)] fn file_test_io_read_write_at() { - use cap_std::fs::FileExt; - let tmpdir = tmpdir(); + + let start = h::dir_of(&tmpdir); let filename = "file_rt_io_file_test_read_write_at.txt"; let mut buf = [0; 256]; let write1 = "asdf"; @@ -282,12 +389,12 @@ fn file_test_io_read_write_at() { let write3 = "-zxcv"; let content = "qwer-asdf-zxcv"; { - let oo = OpenOptions::new() + let oo = p::OpenOptions::new() .create_new(true) .write(true) .read(true) .clone(); - let mut rw = check!(tmpdir.open_with(filename, &oo)); + let mut rw = check!(p::open(&start, Path::new(filename), &oo)); assert_eq!(check!(rw.write_at(write1.as_bytes(), 5)), write1.len()); assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 0); assert_eq!(check!(rw.read_at(&mut buf, 5)), write1.len()); @@ -314,7 +421,7 @@ fn file_test_io_read_write_at() { assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9); } { - let mut read = check!(tmpdir.open(filename)); + let mut read = check!(h::open(&start, filename)); assert_eq!(check!(read.read_at(&mut buf, 0)), content.len()); assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content)); assert_eq!(check!(read.seek(SeekFrom::Current(0))), 0); @@ -332,7 +439,7 @@ fn file_test_io_read_write_at() { assert_eq!(check!(read.read_at(&mut buf, 15)), 0); assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14); } - check!(tmpdir.remove_file(filename)); + check!(p::remove_file(&start, Path::new(filename))); } // Darwin doesn't have a way to change the permissions on a file, relative @@ -350,32 +457,12 @@ fn file_test_io_read_write_at() { ), ignore )] -fn set_get_unix_permissions() { - use cap_std::fs::PermissionsExt; - - let tmpdir = tmpdir(); - let filename = "set_get_unix_permissions"; - check!(tmpdir.create_dir(filename)); - let mask = 0o7777; - - check!(tmpdir.set_permissions(filename, fs::Permissions::from_mode(0))); - let metadata0 = check!(tmpdir.metadata(filename)); - assert_eq!(mask & metadata0.permissions().mode(), 0); - - check!(tmpdir.set_permissions(filename, fs::Permissions::from_mode(0o1777))); - let metadata1 = check!(tmpdir.metadata(filename)); - #[cfg(any(all(unix, not(target_os = "vxworks")), target_os = "wasi"))] - assert_eq!(mask & metadata1.permissions().mode(), 0o1777); - #[cfg(target_os = "vxworks")] - assert_eq!(mask & metadata1.permissions().mode(), 0o0777); -} - #[test] #[cfg(windows)] fn file_test_io_seek_read_write() { - use cap_std::fs::FileExt; - let tmpdir = tmpdir(); + + let start = h::dir_of(&tmpdir); let filename = "file_rt_io_file_test_seek_read_write.txt"; let mut buf = [0; 256]; let write1 = "asdf"; @@ -383,12 +470,12 @@ fn file_test_io_seek_read_write() { let write3 = "-zxcv"; let content = "qwer-asdf-zxcv"; { - let oo = OpenOptions::new() + let oo = p::OpenOptions::new() .create_new(true) .write(true) .read(true) .clone(); - let mut rw = check!(tmpdir.open_with(filename, &oo)); + let mut rw = check!(p::open(&start, Path::new(filename), &oo)); assert_eq!(check!(rw.seek_write(write1.as_bytes(), 5)), write1.len()); assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 9); assert_eq!(check!(rw.seek_read(&mut buf, 5)), write1.len()); @@ -410,7 +497,7 @@ fn file_test_io_seek_read_write() { assert_eq!(check!(rw.seek(SeekFrom::Current(0))), 14); } { - let mut read = check!(tmpdir.open(filename)); + let mut read = check!(h::open(&start, filename)); assert_eq!(check!(read.seek_read(&mut buf, 0)), content.len()); assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content)); assert_eq!(check!(read.seek(SeekFrom::Current(0))), 14); @@ -428,153 +515,170 @@ fn file_test_io_seek_read_write() { assert_eq!(check!(read.seek_read(&mut buf, 14)), 0); assert_eq!(check!(read.seek_read(&mut buf, 15)), 0); } - check!(tmpdir.remove_file(filename)); + check!(p::remove_file(&start, Path::new(filename))); } #[test] fn file_test_stat_is_correct_on_is_file() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let filename = "file_stat_correct_on_is_file.txt"; { - let mut opts = OpenOptions::new(); - let mut fs = check!(tmpdir.open_with(filename, opts.read(true).write(true).create(true))); + let mut opts = p::OpenOptions::new(); + let mut fs = check!(p::open( + &start, + Path::new(filename), + opts.read(true).write(true).create(true) + )); let msg = "hw"; fs.write(msg.as_bytes()).unwrap(); let fstat_res = check!(fs.metadata()); assert!(fstat_res.is_file()); } - let stat_res_fn = check!(tmpdir.metadata(filename)); - assert!(stat_res_fn.is_file()); - check!(tmpdir.remove_file(filename)); + let stat_res_fn = check!(h::metadata(&start, filename)); + assert!(stat_res_fn.file_type().is_file()); + check!(p::remove_file(&start, Path::new(filename))); } #[test] fn file_test_stat_is_correct_on_is_dir() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let filename = "file_stat_correct_on_is_dir"; - check!(tmpdir.create_dir(filename)); - let stat_res_fn = check!(tmpdir.metadata(filename)); + check!(h::create_dir(&start, filename)); + let stat_res_fn = check!(h::metadata(&start, filename)); assert!(stat_res_fn.is_dir()); - check!(tmpdir.remove_dir(filename)); + check!(p::remove_dir(&start, Path::new(filename))); } #[test] fn file_test_fileinfo_false_when_checking_is_file_on_a_directory() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let dir = "fileinfo_false_on_dir"; - check!(tmpdir.create_dir(dir)); - assert!(!tmpdir.is_file(dir)); - check!(tmpdir.remove_dir(dir)); + check!(h::create_dir(&start, dir)); + assert!(!h::is_file(&start, dir)); + check!(p::remove_dir(&start, Path::new(dir))); } #[test] fn file_test_fileinfo_check_exists_before_and_after_file_creation() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let file = "fileinfo_check_exists_b_and_a.txt"; - check!(check!(tmpdir.create(file)).write(b"foo")); - assert!(tmpdir.exists(file)); - check!(tmpdir.remove_file(file)); - assert!(!tmpdir.exists(file)); + check!(check!(h::create(&start, file)).write(b"foo")); + assert!(h::exists(&start, file)); + check!(p::remove_file(&start, Path::new(file))); + assert!(!h::exists(&start, file)); } #[test] fn file_test_directoryinfo_check_exists_before_and_after_mkdir() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let dir = "before_and_after_dir"; - assert!(!tmpdir.exists(dir)); - check!(tmpdir.create_dir(dir)); - assert!(tmpdir.exists(dir)); - assert!(tmpdir.is_dir(dir)); - check!(tmpdir.remove_dir(dir)); - assert!(!tmpdir.exists(dir)); + assert!(!h::exists(&start, dir)); + check!(h::create_dir(&start, dir)); + assert!(h::exists(&start, dir)); + assert!(h::is_dir(&start, dir)); + check!(p::remove_dir(&start, Path::new(dir))); + assert!(!h::exists(&start, dir)); } #[test] fn file_test_directoryinfo_readdir() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let dir = "di_readdir"; - check!(tmpdir.create_dir(dir)); + check!(h::create_dir(&start, dir)); let prefix = "foo"; for n in 0..3 { let f = format!("{}.txt", n); - let mut w = check!(tmpdir.create(&f)); + let mut w = check!(h::create(&start, &f)); let msg_str = format!("{}{}", prefix, n.to_string()); let msg = msg_str.as_bytes(); check!(w.write(msg)); } - let files = check!(tmpdir.read_dir(dir)); + let files = check!(h::read_dir(&start, dir)); let mut mem = [0; 4]; for f in files { let f = f.unwrap().file_name(); { - check!(check!(tmpdir.open(&f)).read(&mut mem)); + check!(check!(h::open(&start, &f)).read(&mut mem)); let read_str = str::from_utf8(&mem).unwrap(); let expected = format!("{}{}", prefix, f.to_str().unwrap()); assert_eq!(expected, read_str); } - check!(tmpdir.remove_file(&f)); + check!(p::remove_file(&start, Path::new(&f))); } - check!(tmpdir.remove_dir(dir)); + check!(p::remove_dir(&start, Path::new(dir))); } #[test] fn file_create_new_already_exists_error() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let file = "file_create_new_error_exists"; - check!(tmpdir.create(file)); - let e = tmpdir - .open_with(file, &fs::OpenOptions::new().write(true).create_new(true)) - .unwrap_err(); + check!(h::create(&start, file)); + let e = p::open( + &start, + Path::new(file), + p::OpenOptions::new().write(true).create_new(true), + ) + .unwrap_err(); assert_eq!(e.kind(), ErrorKind::AlreadyExists); } #[test] fn mkdir_path_already_exists_error() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let dir = "mkdir_error_twice"; - check!(tmpdir.create_dir(dir)); - let e = tmpdir.create_dir(dir).unwrap_err(); + check!(h::create_dir(&start, dir)); + let e = h::create_dir(&start, dir).unwrap_err(); assert_eq!(e.kind(), ErrorKind::AlreadyExists); } #[test] fn recursive_mkdir() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let dir = "d1/d2"; - check!(tmpdir.create_dir_all(dir)); - assert!(tmpdir.is_dir(dir)); + check!(h::create_dir_all(&start, dir)); + assert!(h::is_dir(&start, dir)); } #[test] fn recursive_mkdir_failure() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let dir = "d1"; let file = "f1"; - check!(tmpdir.create_dir_all(&dir)); - check!(tmpdir.create(&file)); + check!(h::create_dir_all(&start, &dir)); + check!(h::create(&start, &file)); - let result = tmpdir.create_dir_all(&file); + let result = h::create_dir_all(&start, &file); assert!(result.is_err()); } #[test] -#[cfg(not(racy_asserts))] // racy asserts are racy fn concurrent_recursive_mkdir() { for _ in 0..100 { - let dir = tmpdir(); + let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let mut name = PathBuf::from("a"); for _ in 0..40 { name = name.join("a"); } let mut join = vec![]; for _ in 0..8 { - let dir = check!(dir.try_clone()); + let dir = check!(start.try_clone()); let name = name.clone(); join.push(thread::spawn(move || { - check!(dir.create_dir_all(&name)); + check!(h::create_dir_all(&dir, &name)); })) } @@ -586,8 +690,9 @@ fn concurrent_recursive_mkdir() { #[test] fn recursive_mkdir_slash() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); error_contains!( - tmpdir.create_dir_all(Path::new("/")), + h::create_dir_all(&start, Path::new("/")), "a path led outside of the filesystem" ); } @@ -595,344 +700,79 @@ fn recursive_mkdir_slash() { #[test] fn recursive_mkdir_dot() { let tmpdir = tmpdir(); - check!(tmpdir.create_dir_all(Path::new("."))); + let start = h::dir_of(&tmpdir); + check!(h::create_dir_all(&start, Path::new("."))); } #[test] fn recursive_mkdir_empty() { let tmpdir = tmpdir(); - check!(tmpdir.create_dir_all(Path::new(""))); -} - -#[test] -fn recursive_rmdir() { - let tmpdir = tmpdir(); - let d1 = PathBuf::from("d1"); - let dt = d1.join("t"); - let dtt = dt.join("t"); - let d2 = PathBuf::from("d2"); - let canary = d2.join("do_not_delete"); - check!(tmpdir.create_dir_all(&dtt)); - check!(tmpdir.create_dir_all(&d2)); - check!(check!(tmpdir.create(&canary)).write(b"foo")); - check!(symlink_junction(d2, &tmpdir, &dt.join("d2"))); - let _ = symlink_file(&canary, &tmpdir, &d1.join("canary")); - check!(tmpdir.remove_dir_all(&d1)); - - assert!(!tmpdir.is_dir(d1)); - assert!(tmpdir.exists(canary)); -} - -// See the comments on `create_dir_all_with_junctions` about Windows. -#[test] -#[cfg_attr(windows, ignore)] -fn recursive_rmdir_of_symlink() { - // test we do not recursively delete a symlink but only dirs. - let tmpdir = tmpdir(); - let link = "d1"; - let dir = "d2"; - let canary = "do_not_delete"; - check!(tmpdir.create_dir_all(&dir)); - check!(check!(tmpdir.create(&canary)).write(b"foo")); - check!(symlink_junction(&dir, &tmpdir, link)); - check!(tmpdir.remove_dir_all(link)); - - assert!(!tmpdir.is_dir(link)); - assert!(tmpdir.exists(canary)); -} - -#[test] -fn recursive_rmdir_of_file_fails() { - // test we do not delete a directly specified file. - let tmpdir = tmpdir(); - let canary = "do_not_delete"; - check!(check!(tmpdir.create(canary)).write(b"foo")); - let result = tmpdir.remove_dir_all(canary); - #[cfg(unix)] - error!(result, "Not a directory"); - #[cfg(windows)] - error!(result, 267); // ERROR_DIRECTORY - The directory name is invalid. - assert!(result.is_err()); - assert!(tmpdir.exists(canary)); -} - -#[test] -// only Windows makes a distinction between file and directory symlinks. -#[cfg(windows)] -fn recursive_rmdir_of_file_symlink() { - let tmpdir = tmpdir(); - if !got_symlink_permission(&tmpdir) { - return; - }; - - let f1 = "f1"; - let f2 = "f2"; - check!(check!(tmpdir.create(&f1)).write(b"foo")); - check!(symlink_file(&f1, &tmpdir, &f2)); - match tmpdir.remove_dir_all(&f2) { - Ok(..) => panic!("wanted a failure"), - Err(..) => {} - } -} - -#[test] -#[ignore] // takes too much time -fn recursive_rmdir_toctou() { - // Test for time-of-check to time-of-use issues. - // - // Scenario: - // The attacker wants to get directory contents deleted, to which he does not - // have access. He has a way to get a privileged Rust binary call - // `std::fs::remove_dir_all()` on a directory he controls, e.g. in his home - // directory. - // - // The POC sets up the `attack_dest/attack_file` which the attacker wants to - // have deleted. The attacker repeatedly creates a directory and replaces - // it with a symlink from `victim_del` to `attack_dest` while the victim - // code calls `std::fs::remove_dir_all()` on `victim_del`. After a few - // seconds the attack has succeeded and `attack_dest/attack_file` is - // deleted. - let tmpdir = tmpdir(); - let victim_del_path = "victim_del"; - - // setup dest - let attack_dest_dir = "attack_dest"; - let attack_dest_dir = Path::new(attack_dest_dir); - tmpdir.create_dir(attack_dest_dir).unwrap(); - let attack_dest_file = "attack_dest/attack_file"; - tmpdir.create(attack_dest_file).unwrap(); - - let drop_canary_arc = Arc::new(()); - let drop_canary_weak = Arc::downgrade(&drop_canary_arc); - - eprintln!("x: {:?}", &victim_del_path); - - // victim just continuously removes `victim_del` - let tmpdir_clone = tmpdir.try_clone().unwrap(); - let _t = thread::spawn(move || { - while drop_canary_weak.upgrade().is_some() { - let _ = tmpdir_clone.remove_dir_all(victim_del_path); - } - }); - - // attacker (could of course be in a separate process) - let start_time = Instant::now(); - while Instant::now().duration_since(start_time) < Duration::from_secs(1000) { - if !tmpdir.exists(attack_dest_file) { - panic!( - "Victim deleted symlinked file outside of victim_del. Attack succeeded in {:?}.", - Instant::now().duration_since(start_time) - ); - } - let _ = tmpdir.create_dir(victim_del_path); - let _ = tmpdir.remove_dir(victim_del_path); - let _ = symlink_dir(attack_dest_dir, &tmpdir, victim_del_path); - } + let start = h::dir_of(&tmpdir); + check!(h::create_dir_all(&start, Path::new(""))); } #[test] fn unicode_path_is_dir() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); - assert!(tmpdir.is_dir(Path::new("."))); - assert!(!tmpdir.is_dir(Path::new("test/stdtest/fs.rs"))); + assert!(h::is_dir(&start, Path::new("."))); + assert!(!h::is_dir(&start, Path::new("test/stdtest/fs.rs"))); let mut dirpath = PathBuf::new(); dirpath.push("test-가一ー你好"); - check!(tmpdir.create_dir(&dirpath)); - assert!(tmpdir.is_dir(&dirpath)); + check!(h::create_dir(&start, &dirpath)); + assert!(h::is_dir(&start, &dirpath)); let mut filepath = dirpath; filepath.push("unicode-file-\u{ac00}\u{4e00}\u{30fc}\u{4f60}\u{597d}.rs"); - check!(tmpdir.create(&filepath)); // ignore return; touch only - assert!(!tmpdir.is_dir(&filepath)); - assert!(tmpdir.exists(filepath)); + check!(h::create(&start, &filepath)); // ignore return; touch only + assert!(!h::is_dir(&start, &filepath)); + assert!(h::exists(&start, filepath)); } #[test] fn unicode_path_exists() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); - assert!(tmpdir.exists(Path::new("."))); - assert!(!tmpdir.exists(Path::new("test/nonexistent-bogus-path"))); + assert!(h::exists(&start, Path::new("."))); + assert!(!h::exists(&start, Path::new("test/nonexistent-bogus-path"))); let unicode = PathBuf::new(); let unicode = unicode.join("test-각丁ー再见"); - check!(tmpdir.create_dir(&unicode)); - assert!(tmpdir.exists(unicode)); - assert!(!tmpdir.exists(Path::new("test/unicode-bogus-path-각丁ー再见"))); -} - -#[test] -fn copy_file_does_not_exist() { - let tmpdir = tmpdir(); - let from = Path::new("test/nonexistent-bogus-path"); - let to = Path::new("test/other-bogus-path"); - - match tmpdir.copy(&from, &tmpdir, &to) { - Ok(..) => panic!(), - Err(..) => { - assert!(!tmpdir.exists(from)); - assert!(!tmpdir.exists(to)); - } - } -} - -#[test] -fn copy_src_does_not_exist() { - let tmpdir = tmpdir(); - let from = Path::new("test/nonexistent-bogus-path"); - let to = "out.txt"; - check!(check!(tmpdir.create(&to)).write(b"hello")); - assert!(tmpdir.copy(&from, &tmpdir, &to).is_err()); - assert!(!tmpdir.exists(from)); - let mut v = Vec::new(); - check!(check!(tmpdir.open(&to)).read_to_end(&mut v)); - assert_eq!(v, b"hello"); -} - -#[test] -fn copy_file_ok() { - let tmpdir = tmpdir(); - let input = "in.txt"; - let out = "out.txt"; - - check!(check!(tmpdir.create(&input)).write(b"hello")); - check!(tmpdir.copy(&input, &tmpdir, &out)); - let mut v = Vec::new(); - check!(check!(tmpdir.open(&out)).read_to_end(&mut v)); - assert_eq!(v, b"hello"); - - assert_eq!( - check!(tmpdir.metadata(input)).permissions(), - check!(tmpdir.metadata(out)).permissions() - ); -} - -#[test] -fn copy_file_dst_dir() { - let tmpdir = tmpdir(); - let out = "out"; - - check!(tmpdir.create(&out)); - match tmpdir.copy(&*out, &tmpdir, ".") { - Ok(..) => panic!(), - Err(..) => {} - } -} - -#[test] -fn copy_file_dst_exists() { - let tmpdir = tmpdir(); - let input = "in"; - let output = "out"; - - check!(check!(tmpdir.create(&input)).write("foo".as_bytes())); - check!(check!(tmpdir.create(&output)).write("bar".as_bytes())); - check!(tmpdir.copy(&input, &tmpdir, &output)); - - let mut v = Vec::new(); - check!(check!(tmpdir.open(&output)).read_to_end(&mut v)); - assert_eq!(v, b"foo".to_vec()); -} - -#[test] -fn copy_file_src_dir() { - let tmpdir = tmpdir(); - let out = "out"; - - match tmpdir.copy(".", &tmpdir, &out) { - Ok(..) => panic!(), - Err(..) => {} - } - assert!(!tmpdir.exists(out)); -} - -#[test] -fn copy_file_preserves_perm_bits() { - let tmpdir = tmpdir(); - let input = "in.txt"; - let out = "out.txt"; - - let attr = check!(check!(tmpdir.create(&input)).metadata()); - let mut p = attr.permissions(); - p.set_readonly(true); - check!(tmpdir.set_permissions(&input, p)); - check!(tmpdir.copy(&input, &tmpdir, &out)); - assert!(check!(tmpdir.metadata(out)).permissions().readonly()); - check!(tmpdir.set_permissions(&input, attr.permissions())); - check!(tmpdir.set_permissions(&out, attr.permissions())); -} - -#[test] -#[cfg(windows)] -fn copy_file_preserves_streams() { - let tmp = tmpdir(); - check!(check!(tmp.create("in.txt:bunny")).write("carrot".as_bytes())); - assert_eq!(check!(tmp.copy("in.txt", &tmp, "out.txt")), 0); - assert_eq!(check!(tmp.metadata("out.txt")).len(), 0); - let mut v = Vec::new(); - check!(check!(tmp.open("out.txt:bunny")).read_to_end(&mut v)); - assert_eq!(v, b"carrot".to_vec()); -} - -#[test] -fn copy_file_returns_metadata_len() { - let tmp = tmpdir(); - let in_path = "in.txt"; - let out_path = "out.txt"; - check!(check!(tmp.create(&in_path)).write(b"lettuce")); - #[cfg(windows)] - check!(check!(tmp.create("in.txt:bunny")).write(b"carrot")); - let copied_len = check!(tmp.copy(&in_path, &tmp, &out_path)); - assert_eq!(check!(tmp.metadata(out_path)).len(), copied_len); -} - -#[test] -fn copy_file_follows_dst_symlink() { - let tmp = tmpdir(); - if !got_symlink_permission(&tmp) { - return; - }; - - let in_path = "in.txt"; - let out_path = "out.txt"; - let out_path_symlink = "out_symlink.txt"; - - check!(tmp.write(&in_path, "foo")); - check!(tmp.write(&out_path, "bar")); - check!(symlink_file(&out_path, &tmp, &out_path_symlink)); - - check!(tmp.copy(&in_path, &tmp, &out_path_symlink)); - - assert!(check!(tmp.symlink_metadata(out_path_symlink)) - .file_type() - .is_symlink()); - assert_eq!(check!(tmp.read(&out_path_symlink)), b"foo".to_vec()); - assert_eq!(check!(tmp.read(&out_path)), b"foo".to_vec()); + check!(h::create_dir(&start, &unicode)); + assert!(h::exists(&start, unicode)); + assert!(!h::exists( + &start, + Path::new("test/unicode-bogus-path-각丁ー再见") + )); } #[test] fn symlinks_work() { let tmpdir = tmpdir(); - if !got_symlink_permission(&tmpdir) { + let start = h::dir_of(&tmpdir); + if !got_symlink_permission(&start) { return; }; let input = "in.txt"; let out = "out.txt"; - check!(check!(tmpdir.create(&input)).write("foobar".as_bytes())); - check!(symlink_file(&input, &tmpdir, &out)); - assert!(check!(tmpdir.symlink_metadata(out)) - .file_type() - .is_symlink()); + check!(check!(h::create(&start, &input)).write("foobar".as_bytes())); + check!(h::symlink_file(&start, &input, &out)); + assert!( + check!(h::symlink_metadata(&start, out)) + .file_type() + .is_symlink() + ); assert_eq!( - check!(tmpdir.metadata(&out)).len(), - check!(tmpdir.metadata(&input)).len() + check!(h::metadata(&start, &out)).len(), + check!(h::metadata(&start, &input)).len() ); let mut v = Vec::new(); - check!(check!(tmpdir.open(&out)).read_to_end(&mut v)); + check!(check!(h::open(&start, &out)).read_to_end(&mut v)); assert_eq!(v, b"foobar".to_vec()); } @@ -940,49 +780,62 @@ fn symlinks_work() { fn symlink_noexist() { // Symlinks can point to things that don't exist let tmpdir = tmpdir(); - if !got_symlink_permission(&tmpdir) { + let start = h::dir_of(&tmpdir); + if !got_symlink_permission(&start) { return; }; // Use a relative path for testing. Symlinks get normalized by Windows, // so we might not get the same path back for absolute paths - check!(symlink_file(&"foo", &tmpdir, "bar")); - assert_eq!(check!(tmpdir.read_link("bar")).to_str().unwrap(), "foo"); + check!(h::symlink_file(&start, &"foo", "bar")); + assert_eq!( + check!(p::read_link(&start, Path::new("bar"))) + .to_str() + .unwrap(), + "foo" + ); } #[test] fn read_link() { if cfg!(windows) { // directory symlink - let root = Dir::open_ambient_dir(r"C:\", ambient_authority()).unwrap(); + let root = h::open_ambient_dir(r"C:\").unwrap(); error_contains!( - root.read_link(r"Users\All Users"), + p::read_link(&root, Path::new(r"Users\All Users")), "a path led outside of the filesystem" ); // junction error_contains!( - root.read_link(r"Users\Default User"), + p::read_link(&root, Path::new(r"Users\Default User")), "a path led outside of the filesystem" ); // junction with special permissions error_contains!( - root.read_link(r"Documents and Settings\"), + p::read_link(&root, Path::new(r"Documents and Settings\")), "a path led outside of the filesystem" ); } let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let link = "link"; - if !got_symlink_permission(&tmpdir) { + if !got_symlink_permission(&start) { return; }; - check!(symlink_file(&"foo", &tmpdir, &link)); - assert_eq!(check!(tmpdir.read_link(&link)).to_str().unwrap(), "foo"); + check!(h::symlink_file(&start, &"foo", &link)); + assert_eq!( + check!(p::read_link(&start, Path::new(&link))) + .to_str() + .unwrap(), + "foo" + ); } #[test] fn readlink_not_symlink() { let tmpdir = tmpdir(); - match tmpdir.read_link(".") { + let start = h::dir_of(&tmpdir); + match p::read_link(&start, Path::new(".")) { Ok(..) => panic!("wanted a failure"), Err(..) => {} } @@ -992,13 +845,19 @@ fn readlink_not_symlink() { #[test] fn read_link_contents() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let link = "link"; - if !got_symlink_permission(&tmpdir) { + if !got_symlink_permission(&start) { return; }; - check!(symlink_file(&"foo", &tmpdir, &link)); + check!(h::symlink_file(&start, &"foo", &link)); assert_eq!( - check!(tmpdir.read_link_contents(&link)).to_str().unwrap(), + check!(super::super::read_link::read_link_contents( + &start, + Path::new(link) + )) + .to_str() + .unwrap(), "foo" ); } @@ -1007,13 +866,19 @@ fn read_link_contents() { #[test] fn read_link_contents_absolute() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let link = "link"; - if !got_symlink_permission(&tmpdir) { + if !got_symlink_permission(&start) { return; }; - check!(symlink_contents(&"/foo", &tmpdir, &link)); + check!(std::os::unix::fs::symlink("/foo", tmpdir.path().join(link))); assert_eq!( - check!(tmpdir.read_link_contents(&link)).to_str().unwrap(), + check!(super::super::read_link::read_link_contents( + &start, + Path::new(link) + )) + .to_str() + .unwrap(), "/foo" ); } @@ -1021,82 +886,48 @@ fn read_link_contents_absolute() { #[test] fn links_work() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let input = "in.txt"; let out = "out.txt"; - check!(check!(tmpdir.create(&input)).write("foobar".as_bytes())); - check!(tmpdir.hard_link(&input, &tmpdir, &out)); + check!(check!(h::create(&start, &input)).write("foobar".as_bytes())); + check!(p::hard_link( + &start, + Path::new(&input), + &start, + Path::new(&out) + )); assert_eq!( - check!(tmpdir.metadata(&out)).len(), - check!(tmpdir.metadata(&input)).len() + check!(h::metadata(&start, &out)).len(), + check!(h::metadata(&start, &input)).len() ); assert_eq!( - check!(tmpdir.metadata(&out)).len(), - check!(tmpdir.metadata(input)).len() + check!(h::metadata(&start, &out)).len(), + check!(h::metadata(&start, input)).len() ); let mut v = Vec::new(); - check!(check!(tmpdir.open(&out)).read_to_end(&mut v)); + check!(check!(h::open(&start, &out)).read_to_end(&mut v)); assert_eq!(v, b"foobar".to_vec()); // can't link to yourself - match tmpdir.hard_link(&input, &tmpdir, &input) { + match p::hard_link(&start, Path::new(&input), &start, Path::new(&input)) { Ok(..) => panic!("wanted a failure"), Err(..) => {} } // can't link to something that doesn't exist - match tmpdir.hard_link("foo", &tmpdir, "bar") { + match p::hard_link(&start, Path::new("foo"), &start, Path::new("bar")) { Ok(..) => panic!("wanted a failure"), Err(..) => {} } } -#[test] -fn chmod_works() { - let tmpdir = tmpdir(); - let file = "in.txt"; - - check!(tmpdir.create(&file)); - let attr = check!(tmpdir.metadata(&file)); - assert!(!attr.permissions().readonly()); - let mut p = attr.permissions(); - p.set_readonly(true); - check!(tmpdir.set_permissions(&file, p.clone())); - let attr = check!(tmpdir.metadata(&file)); - assert!(attr.permissions().readonly()); - - match tmpdir.set_permissions("foo", p.clone()) { - Ok(..) => panic!("wanted an error"), - Err(..) => {} - } - - p.set_readonly(false); - check!(tmpdir.set_permissions(&file, p)); -} - -#[test] -fn fchmod_works() { - let tmpdir = tmpdir(); - let path = "in.txt"; - - let file = check!(tmpdir.create(&path)); - let attr = check!(tmpdir.metadata(&path)); - assert!(!attr.permissions().readonly()); - let mut p = attr.permissions(); - p.set_readonly(true); - check!(file.set_permissions(p.clone())); - let attr = check!(tmpdir.metadata(&path)); - assert!(attr.permissions().readonly()); - - p.set_readonly(false); - check!(file.set_permissions(p)); -} - #[test] fn sync_doesnt_kill_anything() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let path = "in.txt"; - let mut file = check!(tmpdir.create(&path)); + let mut file = check!(h::create(&start, &path)); check!(file.sync_all()); check!(file.sync_data()); check!(file.write(b"foo")); @@ -1107,9 +938,10 @@ fn sync_doesnt_kill_anything() { #[test] fn truncate_works() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let path = "in.txt"; - let mut file = check!(tmpdir.create(&path)); + let mut file = check!(h::create(&start, &path)); check!(file.write(b"foo")); check!(file.sync_all()); @@ -1122,7 +954,7 @@ fn truncate_works() { assert_eq!(check!(file.metadata()).len(), 10); let mut v = Vec::new(); - check!(check!(tmpdir.open(&path)).read_to_end(&mut v)); + check!(check!(h::open(&start, &path)).read_to_end(&mut v)); assert_eq!(v, b"foobar\0\0\0\0".to_vec()); // Truncate to a smaller length, don't seek, and then write something. @@ -1134,200 +966,65 @@ fn truncate_works() { check!(file.sync_all()); assert_eq!(check!(file.metadata()).len(), 9); let mut v = Vec::new(); - check!(check!(tmpdir.open(&path)).read_to_end(&mut v)); + check!(check!(h::open(&start, &path)).read_to_end(&mut v)); assert_eq!(v, b"fo\0\0\0\0wut".to_vec()); } -#[test] -fn open_flavors() { - use cap_std::fs::OpenOptions as OO; - fn c(t: &T) -> T { - t.clone() - } - - let tmpdir = tmpdir(); - - let mut r = OO::new(); - r.read(true); - let mut w = OO::new(); - w.write(true); - let mut rw = OO::new(); - rw.read(true).write(true); - let mut a = OO::new(); - a.append(true); - let mut ra = OO::new(); - ra.read(true).append(true); - - #[cfg(windows)] - let invalid_options = 87; // ERROR_INVALID_PARAMETER - #[cfg(any(all(unix, not(target_os = "vxworks")), target_os = "wasi"))] - let invalid_options = "Invalid argument"; - #[cfg(target_os = "vxworks")] - let invalid_options = "invalid argument"; - - // Test various combinations of creation modes and access modes. - // - // Allowed: - // creation mode | read | write | read-write | append | read-append - // | :-----------------------|:-----:|:-----:|:----------:|:------:|: - // -----------:| not set (open existing) | X | X | X | X - // | X | create | | X | X | - // X | X | truncate | | X | X - // | | | create and truncate | | X | X - // | | | create_new | | X | X - // | X | X | - // - // tested in reverse order, so 'create_new' creates the file, and 'open - // existing' opens it. - - // write-only - check!(tmpdir.open_with("a", c(&w).create_new(true))); - check!(tmpdir.open_with("a", c(&w).create(true).truncate(true))); - check!(tmpdir.open_with("a", c(&w).truncate(true))); - check!(tmpdir.open_with("a", c(&w).create(true))); - check!(tmpdir.open_with("a", &c(&w))); - - // read-only - error!( - tmpdir.open_with("b", c(&r).create_new(true)), - invalid_options - ); - error!( - tmpdir.open_with("b", c(&r).create(true).truncate(true)), - invalid_options - ); - error!(tmpdir.open_with("b", c(&r).truncate(true)), invalid_options); - error!(tmpdir.open_with("b", c(&r).create(true)), invalid_options); - check!(tmpdir.open_with("a", &c(&r))); // try opening the file created with write_only - - // read-write - check!(tmpdir.open_with("c", c(&rw).create_new(true))); - check!(tmpdir.open_with("c", c(&rw).create(true).truncate(true))); - check!(tmpdir.open_with("c", c(&rw).truncate(true))); - check!(tmpdir.open_with("c", c(&rw).create(true))); - check!(tmpdir.open_with("c", &c(&rw))); - - // append - check!(tmpdir.open_with("d", c(&a).create_new(true))); - error!( - tmpdir.open_with("d", c(&a).create(true).truncate(true)), - invalid_options - ); - error!(tmpdir.open_with("d", c(&a).truncate(true)), invalid_options); - check!(tmpdir.open_with("d", c(&a).create(true))); - check!(tmpdir.open_with("d", &c(&a))); - - // read-append - check!(tmpdir.open_with("e", c(&ra).create_new(true))); - error!( - tmpdir.open_with("e", c(&ra).create(true).truncate(true)), - invalid_options - ); - error!( - tmpdir.open_with("e", c(&ra).truncate(true)), - invalid_options - ); - check!(tmpdir.open_with("e", c(&ra).create(true))); - check!(tmpdir.open_with("e", &c(&ra))); - - // Test opening a file without setting an access mode - let mut blank = OO::new(); - error!(tmpdir.open_with("f", blank.create(true)), invalid_options); - - // Test write works - check!(check!(tmpdir.create("h")).write("foobar".as_bytes())); - - // Test write fails for read-only - check!(tmpdir.open_with("h", &r)); - { - let mut f = check!(tmpdir.open_with("h", &r)); - assert!(f.write("wut".as_bytes()).is_err()); - } - - // Test write overwrites - { - let mut f = check!(tmpdir.open_with("h", &c(&w))); - check!(f.write("baz".as_bytes())); - } - { - let mut f = check!(tmpdir.open_with("h", &c(&r))); - let mut b = vec![0; 6]; - check!(f.read(&mut b)); - assert_eq!(b, "bazbar".as_bytes()); - } - - // Test truncate works - { - let mut f = check!(tmpdir.open_with("h", c(&w).truncate(true))); - check!(f.write("foo".as_bytes())); - } - assert_eq!(check!(tmpdir.metadata("h")).len(), 3); - - // Test append works - assert_eq!(check!(tmpdir.metadata("h")).len(), 3); - { - let mut f = check!(tmpdir.open_with("h", &c(&a))); - check!(f.write("bar".as_bytes())); - } - assert_eq!(check!(tmpdir.metadata("h")).len(), 6); - - // Test .append(true) equals .write(true).append(true) - { - let mut f = check!(tmpdir.open_with("h", c(&w).append(true))); - check!(f.write("baz".as_bytes())); - } - assert_eq!(check!(tmpdir.metadata("h")).len(), 9); -} - #[test] fn _assert_send_sync() { fn _assert_send_sync() {} - _assert_send_sync::(); + _assert_send_sync::(); } #[test] fn binary_file() { let mut bytes = [0; 1024]; - StdRng::from_os_rng().fill_bytes(&mut bytes); + rand::rng().fill_bytes(&mut bytes); let tmpdir = tmpdir(); - check!(check!(tmpdir.create("test")).write(&bytes)); + let start = h::dir_of(&tmpdir); + + check!(check!(h::create(&start, "test")).write(&bytes)); let mut v = Vec::new(); - check!(check!(tmpdir.open("test")).read_to_end(&mut v)); + check!(check!(h::open(&start, "test")).read_to_end(&mut v)); assert!(v == &bytes[..]); } #[test] fn write_then_read() { let mut bytes = [0; 1024]; - StdRng::from_os_rng().fill_bytes(&mut bytes); + rand::rng().fill_bytes(&mut bytes); let tmpdir = tmpdir(); - check!(tmpdir.write("test", &bytes[..])); - let v = check!(tmpdir.read("test")); + let start = h::dir_of(&tmpdir); + + check!(h::write(&start, "test", &bytes[..])); + let v = check!(h::read(&start, "test")); assert!(v == &bytes[..]); - check!(tmpdir.write("not-utf8", &[0xFF])); + check!(h::write(&start, "not-utf8", &[0xFF])); error_contains!( - tmpdir.read_to_string("not-utf8"), + h::read_to_string(&start, "not-utf8"), "stream did not contain valid UTF-8" ); let s = "𐁁𐀓𐀠𐀴𐀍"; - check!(tmpdir.write("utf8", s.as_bytes())); - let string = check!(tmpdir.read_to_string("utf8")); + check!(h::write(&start, "utf8", s.as_bytes())); + let string = check!(h::read_to_string(&start, "utf8")); assert_eq!(string, s); } #[test] fn file_try_clone() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); - let mut f1 = check!(tmpdir.open_with( - "test", - OpenOptions::new().read(true).write(true).create(true) + let mut f1 = check!(p::open( + &start, + Path::new("test"), + p::OpenOptions::new().read(true).write(true).create(true) )); let mut f2 = check!(f1.try_clone()); @@ -1342,121 +1039,20 @@ fn file_try_clone() { check!(f1.write_all(b"!")); } -#[test] -#[cfg(not(windows))] -fn unlink_readonly() { - let tmpdir = tmpdir(); - let path = "file"; - check!(tmpdir.create(&path)); - let mut perm = check!(tmpdir.metadata(&path)).permissions(); - perm.set_readonly(true); - check!(tmpdir.set_permissions(&path, perm)); - check!(tmpdir.remove_file(&path)); -} - #[test] fn mkdir_trailing_slash() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let path = PathBuf::from("file"); - check!(tmpdir.create_dir_all(&path.join("a/"))); -} - -#[test] -fn canonicalize_works_simple() { - let tmpdir = tmpdir(); - let file = Path::new("test"); - tmpdir.create(&file).unwrap(); - assert_eq!(tmpdir.canonicalize(&file).unwrap(), file); -} - -#[test] -fn realpath_works() { - let tmpdir = tmpdir(); - if !got_symlink_permission(&tmpdir) { - return; - }; - - let file = PathBuf::from("test"); - let dir = PathBuf::from("test2"); - let link = dir.join("link"); - let linkdir = PathBuf::from("test3"); - - tmpdir.create(&file).unwrap(); - tmpdir.create_dir(&dir).unwrap(); - symlink_file(Path::new("..").join(&file), &tmpdir, &link).unwrap(); - symlink_dir(&dir, &tmpdir, &linkdir).unwrap(); - - assert!(tmpdir - .symlink_metadata(&link) - .unwrap() - .file_type() - .is_symlink()); - - assert_eq!(tmpdir.canonicalize(".").unwrap(), PathBuf::from(".")); - assert_eq!(tmpdir.canonicalize(&file).unwrap(), file); - assert_eq!(tmpdir.canonicalize(&link).unwrap(), file); - assert_eq!(tmpdir.canonicalize(&linkdir).unwrap(), dir); - assert_eq!(tmpdir.canonicalize(&linkdir.join("link")).unwrap(), file); -} - -#[test] -fn realpath_works_tricky() { - let tmpdir = tmpdir(); - if !got_symlink_permission(&tmpdir) { - return; - }; - - let a = PathBuf::from("a"); - let b = a.join("b"); - let c = b.join("c"); - let d = a.join("d"); - let e = d.join("e"); - let f = a.join("f"); - - tmpdir.create_dir_all(&b).unwrap(); - tmpdir.create_dir_all(&d).unwrap(); - tmpdir.create(&f).unwrap(); - if cfg!(not(windows)) { - symlink_file("../d/e", &tmpdir, &c).unwrap(); - symlink_file("../f", &tmpdir, &e).unwrap(); - } - if cfg!(windows) { - symlink_file(r"..\d\e", &tmpdir, &c).unwrap(); - symlink_file(r"..\f", &tmpdir, &e).unwrap(); - } - - assert_eq!(tmpdir.canonicalize(&c).unwrap(), f); - assert_eq!(tmpdir.canonicalize(&e).unwrap(), f); -} - -#[test] -fn dir_entry_methods() { - let tmpdir = tmpdir(); - - tmpdir.create_dir_all("a").unwrap(); - tmpdir.create("b").unwrap(); - - for file in tmpdir.read_dir(".").unwrap().map(|f| f.unwrap()) { - let fname = file.file_name(); - match fname.to_str() { - Some("a") => { - assert!(file.file_type().unwrap().is_dir()); - assert!(file.metadata().unwrap().is_dir()); - } - Some("b") => { - assert!(file.file_type().unwrap().is_file()); - assert!(file.metadata().unwrap().is_file()); - } - f => panic!("unknown file name: {:?}", f), - } - } + check!(h::create_dir_all(&start, &path.join("a/"))); } #[test] fn dir_entry_debug() { let tmpdir = tmpdir(); - tmpdir.create("b").unwrap(); - let mut read_dir = tmpdir.read_dir(".").unwrap(); + let start = h::dir_of(&tmpdir); + h::create(&start, "b").unwrap(); + let mut read_dir = h::read_dir(&start, ".").unwrap(); let dir_entry = read_dir.next().unwrap().unwrap(); let actual = format!("{:?}", dir_entry); let expected = format!("DirEntry({:?})", dir_entry.file_name()); @@ -1466,7 +1062,8 @@ fn dir_entry_debug() { #[test] fn read_dir_not_found() { let tmpdir = tmpdir(); - let res = tmpdir.read_dir("path/that/does/not/exist"); + let start = h::dir_of(&tmpdir); + let res = h::read_dir(&start, "path/that/does/not/exist"); assert_eq!(res.err().unwrap().kind(), ErrorKind::NotFound); } @@ -1480,6 +1077,7 @@ fn read_dir_not_found() { #[test] fn create_dir_all_with_junctions() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let target = "target"; let junction = PathBuf::from("junction"); @@ -1488,33 +1086,34 @@ fn create_dir_all_with_junctions() { let link = PathBuf::from("link"); let d = link.join("c/d"); - tmpdir.create_dir(&target).unwrap(); + h::create_dir(&start, &target).unwrap(); - check!(symlink_junction(&target, &tmpdir, &junction)); - check!(tmpdir.create_dir_all(&b)); + check!(symlink_junction(&target, &start, &junction)); + check!(h::create_dir_all(&start, &b)); // the junction itself is not a directory, but `is_dir()` on a Path // follows links - assert!(tmpdir.is_dir(junction)); - assert!(tmpdir.exists(b)); + assert!(h::is_dir(&start, junction)); + assert!(h::exists(&start, b)); - if !got_symlink_permission(&tmpdir) { + if !got_symlink_permission(&start) { return; }; - check!(symlink_dir(&target, &tmpdir, &link)); - check!(tmpdir.create_dir_all(&d)); - assert!(tmpdir.is_dir(link)); - assert!(tmpdir.exists(d)); + check!(h::symlink_dir(&start, &target, &link)); + check!(h::create_dir_all(&start, &d)); + assert!(h::is_dir(&start, link)); + assert!(h::exists(&start, d)); } #[test] fn metadata_access_times() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let b = "b"; - tmpdir.create(&b).unwrap(); + h::create(&start, &b).unwrap(); - let a = check!(tmpdir.metadata(".")); - let b = check!(tmpdir.metadata(&b)); + let a = check!(h::metadata(&start, ".")); + let b = check!(h::metadata(&start, &b)); assert_eq!(check!(a.accessed()), check!(a.accessed())); assert_eq!(check!(a.modified()), check!(a.modified())); @@ -1530,15 +1129,8 @@ fn metadata_access_times() { // Not always available match (a.created(), b.created()) { (Ok(t1), Ok(t2)) => assert!(t1 <= t2), - #[cfg(not(io_error_uncategorized))] (Err(e1), Err(e2)) if e1.kind() == ErrorKind::Other && e2.kind() == ErrorKind::Other => {} - #[cfg(io_error_uncategorized)] - (Err(e1), Err(e2)) - if e1.kind() == ErrorKind::Uncategorized - && e2.kind() == ErrorKind::Uncategorized - || e1.kind() == ErrorKind::Unsupported - && e2.kind() == ErrorKind::Unsupported => {} (a, b) => panic!( "creation time must be always supported or not supported: {:?} {:?}", a, b, @@ -1551,7 +1143,8 @@ fn metadata_access_times() { #[test] fn symlink_hard_link() { let tmpdir = tmpdir(); - if !got_symlink_permission(&tmpdir) { + let start = h::dir_of(&tmpdir); + if !got_symlink_permission(&start) { return; } if !able_to_not_follow_symlinks_while_hard_linking() { @@ -1559,53 +1152,73 @@ fn symlink_hard_link() { } // Create "file", a file. - check!(tmpdir.create("file")); + check!(h::create(&start, "file")); // Create "symlink", a symlink to "file". - check!(symlink_file("file", &tmpdir, "symlink")); + check!(h::symlink_file(&start, "file", "symlink")); // Create "hard_link", a hard link to "symlink". - check!(tmpdir.hard_link("symlink", &tmpdir, "hard_link")); + check!(p::hard_link( + &start, + Path::new("symlink"), + &start, + Path::new("hard_link") + )); // "hard_link" should appear as a symlink. - assert!(check!(tmpdir.symlink_metadata("hard_link")) - .file_type() - .is_symlink()); + assert!( + check!(h::symlink_metadata(&start, "hard_link")) + .file_type() + .is_symlink() + ); // We sould be able to open "file" via any of the above names. - let _ = check!(tmpdir.open("file")); - assert!(tmpdir.open("file.renamed").is_err()); - let _ = check!(tmpdir.open("symlink")); - let _ = check!(tmpdir.open("hard_link")); + let _ = check!(h::open(&start, "file")); + assert!(h::open(&start, "file.renamed").is_err()); + let _ = check!(h::open(&start, "symlink")); + let _ = check!(h::open(&start, "hard_link")); // Rename "file" to "file.renamed". - check!(tmpdir.rename("file", &tmpdir, "file.renamed")); + check!(p::rename( + &start, + Path::new("file"), + &start, + Path::new("file.renamed") + )); // Now, the symlink and the hard link should be dangling. - assert!(tmpdir.open("file").is_err()); - let _ = check!(tmpdir.open("file.renamed")); - assert!(tmpdir.open("symlink").is_err()); - assert!(tmpdir.open("hard_link").is_err()); + assert!(h::open(&start, "file").is_err()); + let _ = check!(h::open(&start, "file.renamed")); + assert!(h::open(&start, "symlink").is_err()); + assert!(h::open(&start, "hard_link").is_err()); // The symlink and the hard link should both still point to "file". - assert!(tmpdir.read_link("file").is_err()); - assert!(tmpdir.read_link("file.renamed").is_err()); - assert_eq!(check!(tmpdir.read_link("symlink")), Path::new("file")); - assert_eq!(check!(tmpdir.read_link("hard_link")), Path::new("file")); + assert!(p::read_link(&start, Path::new("file")).is_err()); + assert!(p::read_link(&start, Path::new("file.renamed")).is_err()); + assert_eq!( + check!(p::read_link(&start, Path::new("symlink"))), + Path::new("file") + ); + assert_eq!( + check!(p::read_link(&start, Path::new("hard_link"))), + Path::new("file") + ); // Remove "file.renamed". - check!(tmpdir.remove_file("file.renamed")); + check!(p::remove_file(&start, Path::new("file.renamed"))); // Now, we can't open the file by any name. - assert!(tmpdir.open("file").is_err()); - assert!(tmpdir.open("file.renamed").is_err()); - assert!(tmpdir.open("symlink").is_err()); - assert!(tmpdir.open("hard_link").is_err()); + assert!(h::open(&start, "file").is_err()); + assert!(h::open(&start, "file.renamed").is_err()); + assert!(h::open(&start, "symlink").is_err()); + assert!(h::open(&start, "hard_link").is_err()); // "hard_link" should still appear as a symlink. - assert!(check!(tmpdir.symlink_metadata("hard_link")) - .file_type() - .is_symlink()); + assert!( + check!(h::symlink_metadata(&start, "hard_link")) + .file_type() + .is_symlink() + ); } /// Ensure `fs::create_dir` works on Windows with longer paths. @@ -1618,6 +1231,8 @@ fn create_dir_long_paths() { const PATH_LEN: usize = 247; let tmpdir = tmpdir(); + + let start = h::dir_of(&tmpdir); let mut path = PathBuf::new(); path.push("a"); let mut path = path.into_os_string(); @@ -1632,32 +1247,9 @@ fn create_dir_long_paths() { path.extend(iter::repeat(OsStr::new("a")).take(PATH_LEN - utf16_len)); // This should succeed. - tmpdir.create_dir(&path).unwrap(); + h::create_dir(&start, &path).unwrap(); // This will fail if the path isn't converted to verbatim. path.push("a"); - tmpdir.create_dir(&path).unwrap(); - - // #90940: Ensure an empty path returns the "Not Found" error. - let path = Path::new(""); - assert_eq!( - tmpdir.canonicalize(path).unwrap_err().kind(), - std::io::ErrorKind::NotFound - ); -} - -/// Ensure that opening a directory with the truncation flag set is an error, -/// not a panic inside of cap-std. -#[test] -fn open_directory_with_truncate_is_error() { - use cap_fs_ext::OpenOptionsMaybeDirExt; - let tmpdir = tmpdir(); - let mut options = OpenOptions::new(); - options - .truncate(true) - .maybe_dir(true) - .read(true) - .write(true); - tmpdir.create_dir("test").unwrap(); - assert!(tmpdir.open_with("test", &options).is_err()); + h::create_dir(&start, &path).unwrap(); } diff --git a/crates/wasi/src/filesystem/primitives/tests/fs_additional.rs b/crates/wasi/src/filesystem/primitives/tests/fs_additional.rs index 967a3cab7cd7..01b565ef5cfd 100644 --- a/crates/wasi/src/filesystem/primitives/tests/fs_additional.rs +++ b/crates/wasi/src/filesystem/primitives/tests/fs_additional.rs @@ -1,176 +1,352 @@ // This file contains additional fs tests that didn't make it into `fs.rs`. // The reason for additional module to contain those is so that `fs.rs` mirrors // Rust's libstd tests. - -#[macro_use] -mod sys_common; - -use cap_std::ambient_authority; -use cap_std::fs::{Dir, DirBuilder, OpenOptions}; -use cap_std::time::SystemClock; -use std::io::{self, Read, Write}; +use super::helpers as h; +use super::sys_common::io::tmpdir; +use super::sys_common::symlink_supported; +use crate::filesystem::primitives as p; +use std::io::{Read, Write}; use std::path::Path; use std::str; -use sys_common::io::tmpdir; -use sys_common::symlink_supported; -#[cfg(not(windows))] -fn symlink_dir, Q: AsRef>(src: P, tmpdir: &Dir, dst: Q) -> io::Result<()> { - tmpdir.symlink(src, dst) -} -#[cfg(not(windows))] -fn symlink_file, Q: AsRef>(src: P, tmpdir: &Dir, dst: Q) -> io::Result<()> { - tmpdir.symlink(src, dst) +#[test] +fn dir_writable() { + let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); + check!(h::create_dir(&start, "dir")); + #[cfg(not(windows))] + error_contains!(h::create(&start, "dir"), "Is a directory"); + #[cfg(windows)] + error!(h::create(&start, "dir"), 5); + error_contains!( + p::open(&start, Path::new("dir"), p::OpenOptions::new().write(true)), + "Is a directory" + ); + + error_contains!(h::create(&start, "dir/."), "Is a directory"); + error_contains!( + p::open( + &start, + Path::new("dir/."), + p::OpenOptions::new().write(true) + ), + "Is a directory" + ); + + error_contains!(h::create(&start, "dir/.."), "Is a directory"); + error_contains!( + p::open( + &start, + Path::new("dir/.."), + p::OpenOptions::new().write(true) + ), + "Is a directory" + ); } -#[cfg(windows)] -fn symlink_dir, Q: AsRef>(src: P, tmpdir: &Dir, dst: Q) -> io::Result<()> { - tmpdir.symlink_dir(src, dst) + +#[test] +fn readdir_write() { + let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); + check!(h::create_dir(&start, "dir")); + assert!(p::open(&start, Path::new("dir"), p::OpenOptions::new().write(true)).is_err()); + assert!(p::open(&start, Path::new("dir/"), p::OpenOptions::new().write(true)).is_err()); + + #[cfg(any(target_os = "android", target_os = "linux"))] + { + use crate::filesystem::primitives::OpenOptionsExt; + assert!( + p::open( + &start, + Path::new("dir"), + p::OpenOptions::new() + .write(true) + .custom_flags(rustix::fs::OFlags::DIRECTORY.bits() as i32) + ) + .is_err() + ); + } } -#[cfg(windows)] -fn symlink_file, Q: AsRef>(src: P, tmpdir: &Dir, dst: Q) -> io::Result<()> { - tmpdir.symlink_file(src, dst) + +#[test] +fn maybe_dir() { + let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); + check!(h::create_dir(&start, "dir")); + + // Opening directories works on non-Windows platforms. + #[cfg(not(windows))] + check!(h::open(&start, "dir")); + + // Opening directories fails on Windows. + #[cfg(windows)] + assert!(h::open(&start, "dir").is_err()); } #[test] -fn recursive_mkdir() { +fn optionally_recursive_mkdir() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let dir = "d1/d2"; - check!(tmpdir.create_dir_all(dir)); - assert!(tmpdir.is_dir("d1")); - let dir = check!(tmpdir.open_dir("d1")); - assert!(dir.is_dir("d2")); - assert!(tmpdir.is_dir("d1/d2")); + check!(h::create_dir_all(&start, dir)); + assert!(h::is_dir(&start, dir)); } #[test] -#[cfg_attr(windows, ignore)] // TODO investigate why this one is failing -fn open_various() { +fn optionally_nonrecursive_mkdir() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); + let dir = "d1/d2"; #[cfg(not(windows))] - error!(tmpdir.create(""), "No such file"); + error!( + p::create_dir(&start, Path::new(dir), &p::DirOptions::new()), + "No such file" + ); #[cfg(windows)] - error!(tmpdir.create(""), 2); + error!( + p::create_dir(&start, Path::new(dir), &p::DirOptions::new()), + 2 + ); - #[cfg(not(windows))] - error!(tmpdir.create("."), "Is a directory"); - #[cfg(windows)] - error!(tmpdir.create("."), 2); + assert!(!h::exists(&start, dir)); +} + +#[test] +fn dotdot_at_end_of_symlink() { + let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); + + let foo = b"foo"; + check!(h::write(&start, "target", foo)); + check!(h::create_dir(&start, "b")); + let b = check!(p::open_dir(&start, Path::new("b"))); + check!(h::symlink_dir(&b, "..", "up")); + + // Do some things with `path` that might break with an `O_PATH` fd. + // The `permissions` part of this test is gone with `set_permissions`, but + // the `read_dir` part is the part that exercises the `O_PATH` fd. + let path = "b/up"; + + check!(h::metadata(&start, path)); + + let contents = check!(h::read_dir(&start, path)); + for entry in contents { + let _entry = check!(entry); + } +} + +#[test] +fn dotdot_at_end_of_symlink_all_inside_dir() { + let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); + + let foo = b"foo"; + check!(h::create_dir(&start, "dir")); + check!(h::write(&start, "dir/target", foo)); + check!(h::create_dir(&start, "dir/b")); + let b = check!(p::open_dir(&start, Path::new("dir/b"))); + check!(h::symlink_dir(&b, "..", "up")); + + // Do some things with `path` that might break with an `O_PATH` fd. + // The `permissions` part of this test is gone with `set_permissions`, but + // the `read_dir` part is the part that exercises the `O_PATH` fd. + let path = "dir/b/up"; + + check!(h::metadata(&start, path)); + + let contents = check!(h::read_dir(&start, path)); + for entry in contents { + let _entry = check!(entry); + } +} + +#[test] +fn dotdot_slashdot_at_end_of_symlink() { + let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); + + let foo = b"foo"; + check!(h::write(&start, "target", foo)); + check!(h::create_dir(&start, "b")); + let b = check!(p::open_dir(&start, Path::new("b"))); + check!(h::symlink_dir(&b, "../.", "up")); + + // Do some things with `path` that might break with an `O_PATH` fd. + // The `permissions` part of this test is gone with `set_permissions`, but + // the `read_dir` part is the part that exercises the `O_PATH` fd. + let path = "b/up"; + + check!(h::metadata(&start, path)); + + let contents = check!(h::read_dir(&start, path)); + for entry in contents { + let _entry = check!(entry); + } +} + +#[test] +fn dotdot_slashdot_at_end_of_symlink_all_inside_dir() { + let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); + + let foo = b"foo"; + check!(h::create_dir(&start, "dir")); + check!(h::write(&start, "dir/target", foo)); + check!(h::create_dir(&start, "dir/b")); + let b = check!(p::open_dir(&start, Path::new("dir/b"))); + check!(h::symlink_dir(&b, "../.", "up")); + + // Do some things with `path` that might break with an `O_PATH` fd. + // The `permissions` part of this test is gone with `set_permissions`, but + // the `read_dir` part is the part that exercises the `O_PATH` fd. + let path = "dir/b/up"; + + check!(h::metadata(&start, path)); + + let contents = check!(h::read_dir(&start, path)); + for entry in contents { + let _entry = check!(entry); + } +} + +#[test] +fn recursive_mkdir() { + let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); + let dir = "d1/d2"; + check!(h::create_dir_all(&start, dir)); + assert!(h::is_dir(&start, "d1")); + let dir = check!(p::open_dir(&start, Path::new("d1"))); + assert!(h::is_dir(&dir, "d2")); + assert!(h::is_dir(&start, "d1/d2")); } #[test] #[cfg_attr(windows, ignore)] // TODO investigate why this one is failing -fn dir_writable() { +fn open_various() { let tmpdir = tmpdir(); - check!(tmpdir.create_dir("dir")); + let start = h::dir_of(&tmpdir); #[cfg(not(windows))] - error_contains!(tmpdir.create("dir"), "Is a directory"); + error!(h::create(&start, ""), "No such file"); #[cfg(windows)] - error!(tmpdir.create("dir"), 5); - error_contains!( - tmpdir.open_with("dir", OpenOptions::new().write(true)), - "Is a directory" - ); - error_contains!( - tmpdir.open_with("dir", OpenOptions::new().append(true)), - "Is a directory" - ); + error!(h::create(&start, ""), 2); - error_contains!(tmpdir.create("dir/."), "Is a directory"); - error_contains!( - tmpdir.open_with("dir/.", OpenOptions::new().write(true)), - "Is a directory" - ); - error_contains!( - tmpdir.open_with("dir/.", OpenOptions::new().append(true)), - "Is a directory" - ); - - error_contains!(tmpdir.create("dir/.."), "Is a directory"); - error_contains!( - tmpdir.open_with("dir/..", OpenOptions::new().write(true)), - "Is a directory" - ); - error_contains!( - tmpdir.open_with("dir/..", OpenOptions::new().append(true)), - "Is a directory" - ); + #[cfg(not(windows))] + error!(h::create(&start, "."), "Is a directory"); + #[cfg(windows)] + error!(h::create(&start, "."), 2); } #[test] fn trailing_slash() { let tmpdir = tmpdir(); - check!(tmpdir.create("file")); + let start = h::dir_of(&tmpdir); + check!(h::create(&start, "file")); #[cfg(not(windows))] { - error!(tmpdir.open("file/../file"), "Not a directory"); - error!(tmpdir.open("file/.."), "Not a directory"); - error!(tmpdir.open("file/."), "Not a directory"); - error!(tmpdir.open("file/../file/"), "Not a directory"); - error!(tmpdir.open("file/"), "Not a directory"); - error!(tmpdir.open_dir("file/../file/"), "Not a directory"); - error!(tmpdir.open_dir("file/../file"), "Not a directory"); - error!(tmpdir.open_dir("file/.."), "Not a directory"); - error!(tmpdir.open_dir("file/."), "Not a directory"); - error!(tmpdir.open_dir("file/"), "Not a directory"); + error!(h::open(&start, "file/../file"), "Not a directory"); + error!(h::open(&start, "file/.."), "Not a directory"); + error!(h::open(&start, "file/."), "Not a directory"); + error!(h::open(&start, "file/../file/"), "Not a directory"); + error!(h::open(&start, "file/"), "Not a directory"); + error!( + p::open_dir(&start, Path::new("file/../file/")), + "Not a directory" + ); + error!( + p::open_dir(&start, Path::new("file/../file")), + "Not a directory" + ); + error!(p::open_dir(&start, Path::new("file/..")), "Not a directory"); + error!(p::open_dir(&start, Path::new("file/.")), "Not a directory"); + error!(p::open_dir(&start, Path::new("file/")), "Not a directory"); } #[cfg(windows)] { - assert!(check!(check!(tmpdir.open("file/../file")).metadata()).is_file()); - assert!(check!(check!(tmpdir.open_dir("file/..")).dir_metadata()).is_dir()); - assert!(check!(check!(tmpdir.open("file/.")).metadata()).is_file()); - assert!(tmpdir.open_dir("file/../file/").is_err()); - assert!(tmpdir.open_dir("file/./").is_err()); - assert!(tmpdir.open_dir("file//").is_err()); - assert!(tmpdir.open_dir("file/../file").is_err()); - assert!(tmpdir.open_dir("file/.").is_err()); - assert!(tmpdir.open_dir("file/").is_err()); + assert!(check!(check!(h::open(&start, "file/../file")).metadata()).is_file()); + assert!( + check!(p::Metadata::from_file(&check!(p::open_dir( + &start, + Path::new("file/..") + )))) + .is_dir() + ); + assert!(check!(check!(h::open(&start, "file/.")).metadata()).is_file()); + assert!(p::open_dir(&start, Path::new("file/../file/")).is_err()); + assert!(p::open_dir(&start, Path::new("file/./")).is_err()); + assert!(p::open_dir(&start, Path::new("file//")).is_err()); + assert!(p::open_dir(&start, Path::new("file/../file")).is_err()); + assert!(p::open_dir(&start, Path::new("file/.")).is_err()); + assert!(p::open_dir(&start, Path::new("file/")).is_err()); } } #[test] fn trailing_slash_in_dir() { let tmpdir = tmpdir(); - check!(tmpdir.create_dir("dir")); - check!(tmpdir.create("dir/file")); + let start = h::dir_of(&tmpdir); + check!(h::create_dir(&start, "dir")); + check!(h::create(&start, "dir/file")); #[cfg(not(windows))] { - error!(tmpdir.open("dir/file/../file"), "Not a directory"); - error!(tmpdir.open("dir/file/.."), "Not a directory"); - error!(tmpdir.open("dir/file/."), "Not a directory"); - error!(tmpdir.open("dir/file/../file/"), "Not a directory"); - error!(tmpdir.open("dir/file/"), "Not a directory"); - error!(tmpdir.open_dir("dir/file/../file/"), "Not a directory"); - error!(tmpdir.open_dir("dir/file/../file"), "Not a directory"); - error!(tmpdir.open_dir("dir/file/.."), "Not a directory"); - error!(tmpdir.open_dir("dir/file/."), "Not a directory"); - error!(tmpdir.open_dir("dir/file/"), "Not a directory"); + error!(h::open(&start, "dir/file/../file"), "Not a directory"); + error!(h::open(&start, "dir/file/.."), "Not a directory"); + error!(h::open(&start, "dir/file/."), "Not a directory"); + error!(h::open(&start, "dir/file/../file/"), "Not a directory"); + error!(h::open(&start, "dir/file/"), "Not a directory"); + error!( + p::open_dir(&start, Path::new("dir/file/../file/")), + "Not a directory" + ); + error!( + p::open_dir(&start, Path::new("dir/file/../file")), + "Not a directory" + ); + error!( + p::open_dir(&start, Path::new("dir/file/..")), + "Not a directory" + ); + error!( + p::open_dir(&start, Path::new("dir/file/.")), + "Not a directory" + ); + error!( + p::open_dir(&start, Path::new("dir/file/")), + "Not a directory" + ); } #[cfg(windows)] { - assert!(check!(check!(tmpdir.open("dir/file/../file")).metadata()).is_file()); - assert!(check!(check!(tmpdir.open_dir("dir/file/..")).dir_metadata()).is_dir()); - assert!(check!(check!(tmpdir.open("dir/file/.")).metadata()).is_file()); - assert!(tmpdir.open("dir/file/../file/").is_err()); - let _ = check!(tmpdir.open("dir/file/../file/.")); - assert!(tmpdir.open("dir/file/../file/./").is_err()); - assert!(tmpdir.open("dir/file/").is_err()); - let _ = check!(tmpdir.open("dir/file/.")); - let _ = check!(tmpdir.open("dir/file/../file/.")); - assert!(tmpdir.open("dir/file/../file/./").is_err()); - assert!(tmpdir.open("dir/file/").is_err()); - let _ = check!(tmpdir.open("dir/file/.")); - assert!(tmpdir.open("dir/file/./").is_err()); - assert!(tmpdir.open_dir("dir/file/../file/").is_err()); - assert!(tmpdir.open_dir("dir/file/../file/.").is_err()); - assert!(tmpdir.open_dir("dir/file/../file/./").is_err()); - assert!(tmpdir.open_dir("dir/file/../file").is_err()); - assert!(tmpdir.open_dir("dir/file/.").is_err()); - assert!(tmpdir.open_dir("dir/file/./").is_err()); - assert!(tmpdir.open_dir("dir/file/").is_err()); + assert!(check!(check!(h::open(&start, "dir/file/../file")).metadata()).is_file()); + assert!( + check!(p::Metadata::from_file(&check!(p::open_dir( + &start, + Path::new("dir/file/..") + )))) + .is_dir() + ); + assert!(check!(check!(h::open(&start, "dir/file/.")).metadata()).is_file()); + assert!(h::open(&start, "dir/file/../file/").is_err()); + let _ = check!(h::open(&start, "dir/file/../file/.")); + assert!(h::open(&start, "dir/file/../file/./").is_err()); + assert!(h::open(&start, "dir/file/").is_err()); + let _ = check!(h::open(&start, "dir/file/.")); + let _ = check!(h::open(&start, "dir/file/../file/.")); + assert!(h::open(&start, "dir/file/../file/./").is_err()); + assert!(h::open(&start, "dir/file/").is_err()); + let _ = check!(h::open(&start, "dir/file/.")); + assert!(h::open(&start, "dir/file/./").is_err()); + assert!(p::open_dir(&start, Path::new("dir/file/../file/")).is_err()); + assert!(p::open_dir(&start, Path::new("dir/file/../file/.")).is_err()); + assert!(p::open_dir(&start, Path::new("dir/file/../file/./")).is_err()); + assert!(p::open_dir(&start, Path::new("dir/file/../file")).is_err()); + assert!(p::open_dir(&start, Path::new("dir/file/.")).is_err()); + assert!(p::open_dir(&start, Path::new("dir/file/./")).is_err()); + assert!(p::open_dir(&start, Path::new("dir/file/")).is_err()); } } @@ -178,15 +354,42 @@ fn trailing_slash_in_dir() { #[cfg_attr(windows, ignore)] // TODO investigate why this one is failing fn rename_slashdots() { let tmpdir = tmpdir(); - check!(tmpdir.create_dir("dir")); - check!(tmpdir.rename("dir", &tmpdir, "dir")); - check!(tmpdir.rename("dir", &tmpdir, "dir/")); - check!(tmpdir.rename("dir/", &tmpdir, "dir")); - check!(tmpdir.rename("dir/", &tmpdir, "dir/")); + let start = h::dir_of(&tmpdir); + check!(h::create_dir(&start, "dir")); + check!(p::rename( + &start, + Path::new("dir"), + &start, + Path::new("dir") + )); + check!(p::rename( + &start, + Path::new("dir"), + &start, + Path::new("dir/") + )); + check!(p::rename( + &start, + Path::new("dir/"), + &start, + Path::new("dir") + )); + check!(p::rename( + &start, + Path::new("dir/"), + &start, + Path::new("dir/") + )); // TODO: Platform-specific error code. - error_contains!(tmpdir.rename("dir", &tmpdir, "dir/."), ""); - error_contains!(tmpdir.rename("dir/.", &tmpdir, "dir"), ""); + error_contains!( + p::rename(&start, Path::new("dir"), &start, Path::new("dir/.")), + "" + ); + error_contains!( + p::rename(&start, Path::new("dir/."), &start, Path::new("dir")), + "" + ); } #[test] @@ -223,70 +426,50 @@ fn rename_slashdots_ambient() { ); } -#[test] -fn optionally_recursive_mkdir() { - let tmpdir = tmpdir(); - let dir = "d1/d2"; - check!(tmpdir.create_dir_with(dir, DirBuilder::new().recursive(true))); - assert!(tmpdir.is_dir(dir)); -} - #[test] fn try_exists() { let tmpdir = tmpdir(); - assert_eq!(tmpdir.try_exists("somefile").unwrap(), false); + let start = h::dir_of(&tmpdir); + assert_eq!(h::exists(&start, "somefile"), false); let dir = Path::new("d1/d2"); let parent = dir.parent().unwrap(); - assert_eq!(tmpdir.try_exists(parent).unwrap(), false); - assert_eq!(tmpdir.try_exists(dir).unwrap(), false); - check!(tmpdir.create_dir(parent)); - assert_eq!(tmpdir.try_exists(parent).unwrap(), true); - assert_eq!(tmpdir.try_exists(dir).unwrap(), false); - check!(tmpdir.create_dir(dir)); - assert_eq!(tmpdir.try_exists(dir).unwrap(), true); -} - -#[test] -fn optionally_nonrecursive_mkdir() { - let tmpdir = tmpdir(); - let dir = "d1/d2"; - #[cfg(not(windows))] - error!( - tmpdir.create_dir_with(dir, &DirBuilder::new()), - "No such file" - ); - #[cfg(windows)] - error!(tmpdir.create_dir_with(dir, &DirBuilder::new()), 2); - - assert!(!tmpdir.exists(dir)); + assert_eq!(h::exists(&start, parent), false); + assert_eq!(h::exists(&start, dir), false); + check!(h::create_dir(&start, parent)); + assert_eq!(h::exists(&start, parent), true); + assert_eq!(h::exists(&start, dir), false); + check!(h::create_dir(&start, dir)); + assert_eq!(h::exists(&start, dir), true); } #[test] fn file_test_directoryinfo_readdir() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let dir = "di_readdir"; - check!(tmpdir.create_dir(dir)); + check!(h::create_dir(&start, dir)); let prefix = "foo"; for n in 0..3 { let f = format!("{}.txt", n); - let mut w = check!(tmpdir.create(&f)); + let mut w = check!(h::create(&start, &f)); let msg_str = format!("{}{}", prefix, n.to_string()); let msg = msg_str.as_bytes(); check!(w.write(msg)); } - let files = check!(tmpdir.read_dir(dir)); + let sub = check!(p::open_dir(&start, Path::new(dir))); + let files = check!(p::read_base_dir(&sub)); let mut mem = [0; 4]; for f in files { let f = f.unwrap(); { - check!(check!(f.open()).read(&mut mem)); + check!(check!(h::open(&sub, f.file_name())).read(&mut mem)); let read_str = str::from_utf8(&mem).unwrap(); let expected = format!("{}{}", prefix, f.file_name().to_str().unwrap()); assert_eq!(expected, read_str); } - check!(f.remove_file()); + check!(p::remove_file(&sub, Path::new(&f.file_name()))); } - check!(tmpdir.remove_dir(dir)); + check!(p::remove_dir(&start, Path::new(dir))); } #[test] @@ -296,44 +479,44 @@ fn follow_dotdot_symlink() { } let tmpdir = tmpdir(); - check!(tmpdir.create_dir_all("a/b")); - check!(symlink_dir("..", &tmpdir, "a/b/c")); - check!(symlink_dir("../..", &tmpdir, "a/b/d")); - check!(symlink_dir("../../..", &tmpdir, "a/b/e")); - check!(symlink_dir("../../../..", &tmpdir, "a/b/f")); - check!(tmpdir.open_dir("a/b/c")); - assert!(check!(tmpdir.metadata("a/b/c")).is_dir()); + let start = h::dir_of(&tmpdir); + check!(h::create_dir_all(&start, "a/b")); + check!(h::symlink_dir(&start, "..", "a/b/c")); + check!(h::symlink_dir(&start, "../..", "a/b/d")); + check!(h::symlink_dir(&start, "../../..", "a/b/e")); + check!(h::symlink_dir(&start, "../../../..", "a/b/f")); + + check!(p::open_dir(&start, Path::new("a/b/c"))); + assert!(check!(h::metadata(&start, "a/b/c")).is_dir()); #[cfg(windows)] { - error!(tmpdir.open_dir("a/b/d"), 123); - error!(tmpdir.metadata("a/b/d"), 123); + error!(p::open_dir(&start, Path::new("a/b/d")), 123); + error!(h::metadata(&start, "a/b/d"), 123); - error!(tmpdir.open_dir("a/b/e"), 123); - error!(tmpdir.metadata("a/b/e"), 123); + error!(p::open_dir(&start, Path::new("a/b/e")), 123); + error!(h::metadata(&start, "a/b/e"), 123); - error!(tmpdir.open_dir("a/b/f"), 123); - error!(tmpdir.metadata("a/b/f"), 123); + error!(p::open_dir(&start, Path::new("a/b/f")), 123); + error!(h::metadata(&start, "a/b/f"), 123); } #[cfg(not(windows))] { - check!(tmpdir.open_dir("a/b/d")); - assert!(check!(tmpdir.metadata("a/b/d")).is_dir()); + check!(p::open_dir(&start, Path::new("a/b/d"))); + assert!(check!(h::metadata(&start, "a/b/d")).is_dir()); - assert!(tmpdir.open_dir("a/b/e").is_err()); - assert!(tmpdir.metadata("a/b/e").is_err()); + assert!(p::open_dir(&start, Path::new("a/b/e")).is_err()); + assert!(h::metadata(&start, "a/b/e").is_err()); - assert!(tmpdir.open_dir("a/b/f").is_err()); - assert!(tmpdir.metadata("a/b/f").is_err()); + assert!(p::open_dir(&start, Path::new("a/b/f")).is_err()); + assert!(h::metadata(&start, "a/b/f").is_err()); } } #[test] fn follow_dotdot_symlink_ambient() { - use cap_std::ambient_authority; - use cap_std::fs::Dir; #[cfg(unix)] use std::os::unix::fs::symlink as symlink_dir; #[cfg(windows)] @@ -350,51 +533,30 @@ fn follow_dotdot_symlink_ambient() { check!(symlink_dir("../../..", dir.path().join("a/b/e"))); check!(symlink_dir("../../../..", dir.path().join("a/b/f"))); - check!(Dir::open_ambient_dir( - dir.path().join("a/b/c"), - ambient_authority() - )); + check!(h::open_ambient_dir(dir.path().join("a/b/c"))); assert!(check!(std::fs::metadata(dir.path().join("a/b/c"))).is_dir()); #[cfg(windows)] { - error!( - Dir::open_ambient_dir(dir.path().join("a/b/d"), ambient_authority()), - 123 - ); + error!(h::open_ambient_dir(dir.path().join("a/b/d")), 123); error!(std::fs::metadata(dir.path().join("a/b/d")), 123); - error!( - Dir::open_ambient_dir(dir.path().join("a/b/e"), ambient_authority()), - 123 - ); + error!(h::open_ambient_dir(dir.path().join("a/b/e")), 123); error!(std::fs::metadata(dir.path().join("a/b/e")), 123); - error!( - Dir::open_ambient_dir(dir.path().join("a/b/f"), ambient_authority()), - 123 - ); + error!(h::open_ambient_dir(dir.path().join("a/b/f")), 123); error!(std::fs::metadata(dir.path().join("a/b/f")), 123); } #[cfg(not(windows))] { - check!(Dir::open_ambient_dir( - dir.path().join("a/b/d"), - ambient_authority() - )); + check!(h::open_ambient_dir(dir.path().join("a/b/d"))); assert!(check!(std::fs::metadata(dir.path().join("a/b/d"))).is_dir()); - check!(Dir::open_ambient_dir( - dir.path().join("a/b/e"), - ambient_authority() - )); + check!(h::open_ambient_dir(dir.path().join("a/b/e"))); assert!(check!(std::fs::metadata(dir.path().join("a/b/e"))).is_dir()); - check!(Dir::open_ambient_dir( - dir.path().join("a/b/f"), - ambient_authority() - )); + check!(h::open_ambient_dir(dir.path().join("a/b/f"))); assert!(check!(std::fs::metadata(dir.path().join("a/b/f"))).is_dir()); } } @@ -407,56 +569,28 @@ fn follow_file_symlink() { let tmpdir = tmpdir(); - check!(tmpdir.create("file")); + let start = h::dir_of(&tmpdir); + + check!(h::create(&start, "file")); - check!(symlink_file("file", &tmpdir, "link")); - check!(symlink_dir("file/", &tmpdir, "link_slash")); - check!(symlink_file("file/.", &tmpdir, "link_slashdot")); - check!(symlink_dir("file/..", &tmpdir, "link_slashdotdot")); + check!(h::symlink_file(&start, "file", "link")); + check!(h::symlink_dir(&start, "file/", "link_slash")); + check!(h::symlink_file(&start, "file/.", "link_slashdot")); + check!(h::symlink_dir(&start, "file/..", "link_slashdotdot")); - check!(tmpdir.open("link")); - assert!(tmpdir.open("link_slash").is_err()); + check!(h::open(&start, "link")); + assert!(h::open(&start, "link_slash").is_err()); #[cfg(windows)] { - error!(tmpdir.open("link_slashdot"), 123); - error!(tmpdir.open_dir("link_slashdotdot"), 123); + error!(h::open(&start, "link_slashdot"), 123); + error!(p::open_dir(&start, Path::new("link_slashdotdot")), 123); } #[cfg(not(windows))] { - assert!(tmpdir.open("link_slash").is_err()); - assert!(tmpdir.open("link_slashdot").is_err()); - assert!(tmpdir.open_dir("link_slashdotdot").is_err()); - } -} - -#[cfg(unix)] -#[test] -fn check_dot_access() { - use cap_std::fs::{DirBuilder, DirBuilderExt}; - - let tmpdir = tmpdir(); - - let mut options = DirBuilder::new(); - options.mode(0o477); - check!(tmpdir.create_dir_with("dir", &options)); - - check!(tmpdir.metadata(".")); - check!(tmpdir.metadata("dir")); - check!(tmpdir.metadata("dir/")); - check!(tmpdir.metadata("dir//")); - - if !cfg!(target_os = "freebsd") { - assert!(tmpdir.metadata("dir/.").is_err()); - assert!(tmpdir.metadata("dir/./").is_err()); - assert!(tmpdir.metadata("dir/.//").is_err()); - assert!(tmpdir.metadata("dir/./.").is_err()); - assert!(tmpdir.metadata("dir/.//.").is_err()); - assert!(tmpdir.metadata("dir/..").is_err()); - assert!(tmpdir.metadata("dir/../").is_err()); - assert!(tmpdir.metadata("dir/..//").is_err()); - assert!(tmpdir.metadata("dir/../.").is_err()); - assert!(tmpdir.metadata("dir/..//.").is_err()); + assert!(h::open(&start, "link_slash").is_err()); + assert!(h::open(&start, "link_slashdot").is_err()); + assert!(p::open_dir(&start, Path::new("link_slashdotdot")).is_err()); } } @@ -500,26 +634,27 @@ fn check_dot_access_ambient() { #[test] fn file_with_trailing_slashdot() { let tmpdir = tmpdir(); - check!(tmpdir.create("file")); - check!(tmpdir.open("file")); - check!(tmpdir.open("file\\.")); - check!(tmpdir.open("file/.")); - check!(tmpdir.open("file\\.\\.")); - check!(tmpdir.open("file/./.")); - assert!(tmpdir.open("file\\").is_err()); - assert!(tmpdir.open("file/").is_err()); - assert!(tmpdir.open("file\\.\\").is_err()); - assert!(tmpdir.open("file/./").is_err()); - check!(tmpdir.open_dir("file\\..")); - check!(tmpdir.open_dir("file/..")); - check!(tmpdir.open_dir("file\\.\\..")); - check!(tmpdir.open_dir("file/./..")); - check!(tmpdir.open_dir("file\\..\\.")); - check!(tmpdir.open_dir("file/../.")); - check!(tmpdir.open_dir("file\\..\\")); - check!(tmpdir.open_dir("file/../")); - assert!(tmpdir.open_dir("file\\...").is_err()); - assert!(tmpdir.open_dir("file/...").is_err()); + let start = h::dir_of(&tmpdir); + check!(h::create(&start, "file")); + check!(h::open(&start, "file")); + check!(h::open(&start, "file\\.")); + check!(h::open(&start, "file/.")); + check!(h::open(&start, "file\\.\\.")); + check!(h::open(&start, "file/./.")); + assert!(h::open(&start, "file\\").is_err()); + assert!(h::open(&start, "file/").is_err()); + assert!(h::open(&start, "file\\.\\").is_err()); + assert!(h::open(&start, "file/./").is_err()); + check!(p::open_dir(&start, Path::new("file\\.."))); + check!(p::open_dir(&start, Path::new("file/.."))); + check!(p::open_dir(&start, Path::new("file\\.\\.."))); + check!(p::open_dir(&start, Path::new("file/./.."))); + check!(p::open_dir(&start, Path::new("file\\..\\."))); + check!(p::open_dir(&start, Path::new("file/../."))); + check!(p::open_dir(&start, Path::new("file\\..\\"))); + check!(p::open_dir(&start, Path::new("file/../"))); + assert!(p::open_dir(&start, Path::new("file\\...")).is_err()); + assert!(p::open_dir(&start, Path::new("file/...")).is_err()); } /// This is just to confirm that Windows really does allow one to open "file/." @@ -527,8 +662,6 @@ fn file_with_trailing_slashdot() { #[cfg(windows)] #[test] fn file_with_trailing_slashdot_ambient() { - use cap_std::ambient_authority; - use cap_std::fs::Dir; let dir = tempfile::tempdir().unwrap(); check!(std::fs::File::create(dir.path().join("file"))); check!(std::fs::File::open(dir.path().join("file"))); @@ -540,36 +673,15 @@ fn file_with_trailing_slashdot_ambient() { assert!(std::fs::File::open(dir.path().join("file/")).is_err()); assert!(std::fs::File::open(dir.path().join("file\\.\\")).is_err()); assert!(std::fs::File::open(dir.path().join("file/./")).is_err()); - check!(Dir::open_ambient_dir( - dir.path().join("file/.."), - ambient_authority() - )); - check!(Dir::open_ambient_dir( - dir.path().join("file\\.\\.."), - ambient_authority() - )); - check!(Dir::open_ambient_dir( - dir.path().join("file/./.."), - ambient_authority() - )); - check!(Dir::open_ambient_dir( - dir.path().join("file\\..\\."), - ambient_authority() - )); - check!(Dir::open_ambient_dir( - dir.path().join("file/../."), - ambient_authority() - )); - check!(Dir::open_ambient_dir( - dir.path().join("file\\..\\"), - ambient_authority() - )); - check!(Dir::open_ambient_dir( - dir.path().join("file/../"), - ambient_authority() - )); - assert!(Dir::open_ambient_dir(dir.path().join("file\\..."), ambient_authority()).is_err()); - assert!(Dir::open_ambient_dir(dir.path().join("file/..."), ambient_authority()).is_err()); + check!(h::open_ambient_dir(dir.path().join("file/.."))); + check!(h::open_ambient_dir(dir.path().join("file\\.\\.."))); + check!(h::open_ambient_dir(dir.path().join("file/./.."))); + check!(h::open_ambient_dir(dir.path().join("file\\..\\."))); + check!(h::open_ambient_dir(dir.path().join("file/../."))); + check!(h::open_ambient_dir(dir.path().join("file\\..\\"))); + check!(h::open_ambient_dir(dir.path().join("file/../"))); + assert!(h::open_ambient_dir(dir.path().join("file\\...")).is_err()); + assert!(h::open_ambient_dir(dir.path().join("file/...")).is_err()); } #[cfg(all( @@ -582,23 +694,6 @@ fn file_with_trailing_slashdot_ambient() { target_os = "visionos", )) ))] -#[test] -fn dir_searchable_unreadable() { - use cap_std::fs::{DirBuilder, DirBuilderExt}; - - let tmpdir = tmpdir(); - - let mut options = DirBuilder::new(); - options.mode(0o333); - check!(tmpdir.create_dir_with("dir", &options)); - check!(tmpdir.create_dir_with("dir/writeable_subdir", &options)); - options.mode(0o111); - check!(tmpdir.create_dir_with("dir/subdir", &options)); - - assert!(check!(tmpdir.metadata("dir/.")).is_dir()); - assert!(check!(tmpdir.metadata("dir/subdir")).is_dir()); - assert!(check!(tmpdir.metadata("dir/subdir/.")).is_dir()); -} /// This test is the same as `dir_searchable_unreadable` but uses `std::fs`' /// ambient API instead of `cap_std`. The purpose of this test is to @@ -641,45 +736,6 @@ fn dir_searchable_unreadable_ambient() { target_os = "watchos", target_os = "visionos", ))] -#[test] -fn dir_searchable_unreadable() { - use cap_std::fs::{DirBuilder, DirBuilderExt}; - - let tmpdir = tmpdir(); - - let mut options = DirBuilder::new(); - options.mode(0o333); - check!(tmpdir.create_dir_with("dir", &options)); - assert!(tmpdir - .create_dir_with("dir/writeable_subdir", &options) - .is_err()); -} - -/// Test opening a directory with no permissions. -#[cfg(unix)] -#[test] -fn dir_unsearchable_unreadable() { - use cap_std::fs::{DirBuilder, DirBuilderExt}; - - let tmpdir = tmpdir(); - - let mut options = DirBuilder::new(); - options.mode(0o000); - check!(tmpdir.create_dir_with("dir", &options)); - - // Platforms with `O_PATH` can open a directory with no permissions. - if cfg!(any( - target_os = "android", - target_os = "linux", - target_os = "redox", - )) { - let dir = check!(tmpdir.open_dir("dir")); - assert!(dir.entries().is_err()); - assert!(dir.open_dir(".").is_err()); - } else if !cfg!(target_os = "freebsd") { - assert!(tmpdir.open_dir("dir").is_err()); - } -} /// Like `dir_unsearchable_unreadable`, but uses ambient-authority APIs /// to test underlying host functionality. @@ -781,161 +837,91 @@ fn symlink_hard_link() { let tmpdir = tmpdir(); - check!(tmpdir.create("file")); - check!(symlink_file("file", &tmpdir, "symlink")); - check!(tmpdir.hard_link("symlink", &tmpdir, "hard_link")); - assert!(check!(tmpdir.symlink_metadata("hard_link")) - .file_type() - .is_symlink()); - let _ = check!(tmpdir.open("file")); - assert!(tmpdir.open("file.renamed").is_err()); - let _ = check!(tmpdir.open("symlink")); - let _ = check!(tmpdir.open("hard_link")); - check!(tmpdir.rename("file", &tmpdir, "file.renamed")); - assert!(tmpdir.open("file").is_err()); - let _ = check!(tmpdir.open("file.renamed")); - assert!(tmpdir.open("symlink").is_err()); - assert!(tmpdir.open("hard_link").is_err()); - assert!(tmpdir.read_link("file").is_err()); - assert!(tmpdir.read_link("file.renamed").is_err()); - assert_eq!(check!(tmpdir.read_link("symlink")), Path::new("file")); - assert_eq!(check!(tmpdir.read_link("hard_link")), Path::new("file")); - check!(tmpdir.remove_file("file.renamed")); - assert!(tmpdir.open("file").is_err()); - assert!(tmpdir.open("file.renamed").is_err()); - assert!(tmpdir.open("symlink").is_err()); - assert!(tmpdir.open("hard_link").is_err()); - assert!(check!(tmpdir.symlink_metadata("hard_link")) - .file_type() - .is_symlink()); -} - -#[test] -fn readdir_with_trailing_slashdot() { - let tmpdir = tmpdir(); - check!(tmpdir.create_dir("dir")); - check!(tmpdir.create("dir/red")); - check!(tmpdir.create("dir/green")); - check!(tmpdir.create("dir/blue")); - - assert_eq!(check!(tmpdir.read_dir("dir")).count(), 3); - assert_eq!(check!(tmpdir.read_dir("dir/")).count(), 3); - assert_eq!(check!(tmpdir.read_dir("dir/.")).count(), 3); -} - -#[test] -fn readdir_write() { - let tmpdir = tmpdir(); - check!(tmpdir.create_dir("dir")); - assert!(tmpdir - .open_with("dir", OpenOptions::new().write(true)) - .is_err()); - assert!(tmpdir - .open_with("dir", OpenOptions::new().append(true)) - .is_err()); - assert!(tmpdir - .open_with("dir/", OpenOptions::new().write(true)) - .is_err()); - assert!(tmpdir - .open_with("dir/", OpenOptions::new().append(true)) - .is_err()); - - #[cfg(any(target_os = "android", target_os = "linux"))] - { - use cap_std::fs::OpenOptionsExt; - assert!(tmpdir - .open_with( - "dir", - OpenOptions::new() - .write(true) - .custom_flags(rustix::fs::OFlags::DIRECTORY.bits() as i32) - ) - .is_err()); - assert!(tmpdir - .open_with( - "dir", - OpenOptions::new() - .append(true) - .custom_flags(rustix::fs::OFlags::DIRECTORY.bits() as i32) - ) - .is_err()); - } -} - -#[test] -fn maybe_dir() { - use cap_fs_ext::OpenOptionsMaybeDirExt; + let start = h::dir_of(&tmpdir); - let tmpdir = tmpdir(); - check!(tmpdir.create_dir("dir")); - - // Opening directories works on non-Windows platforms. - #[cfg(not(windows))] - check!(tmpdir.open("dir")); - - // Opening directories fails on Windows. - #[cfg(windows)] - assert!(tmpdir.open("dir").is_err()); - - // Opening directories works on all platforms with `maybe_dir`. - check!(tmpdir.open_with("dir", OpenOptions::new().read(true).maybe_dir(true))); -} - -#[test] -fn sync() { - use cap_fs_ext::OpenOptionsSyncExt; - - let tmpdir = tmpdir(); - check!(tmpdir.create("file")); - - check!(tmpdir.open_with("file", OpenOptions::new().write(true).sync(true))); - check!(tmpdir.open_with("file", OpenOptions::new().write(true).dsync(true))); - check!(tmpdir.open_with( - "file", - OpenOptions::new() - .read(true) - .write(true) - .sync(true) - .rsync(true) + check!(h::create(&start, "file")); + check!(h::symlink_file(&start, "file", "symlink")); + check!(p::hard_link( + &start, + Path::new("symlink"), + &start, + Path::new("hard_link") )); - check!(tmpdir.open_with( - "file", - OpenOptions::new() - .read(true) - .write(true) - .dsync(true) - .rsync(true) + assert!( + check!(h::symlink_metadata(&start, "hard_link")) + .file_type() + .is_symlink() + ); + let _ = check!(h::open(&start, "file")); + assert!(h::open(&start, "file.renamed").is_err()); + let _ = check!(h::open(&start, "symlink")); + let _ = check!(h::open(&start, "hard_link")); + check!(p::rename( + &start, + Path::new("file"), + &start, + Path::new("file.renamed") )); + assert!(h::open(&start, "file").is_err()); + let _ = check!(h::open(&start, "file.renamed")); + assert!(h::open(&start, "symlink").is_err()); + assert!(h::open(&start, "hard_link").is_err()); + assert!(p::read_link(&start, Path::new("file")).is_err()); + assert!(p::read_link(&start, Path::new("file.renamed")).is_err()); + assert_eq!( + check!(p::read_link(&start, Path::new("symlink"))), + Path::new("file") + ); + assert_eq!( + check!(p::read_link(&start, Path::new("hard_link"))), + Path::new("file") + ); + check!(p::remove_file(&start, Path::new("file.renamed"))); + assert!(h::open(&start, "file").is_err()); + assert!(h::open(&start, "file.renamed").is_err()); + assert!(h::open(&start, "symlink").is_err()); + assert!(h::open(&start, "hard_link").is_err()); + assert!( + check!(h::symlink_metadata(&start, "hard_link")) + .file_type() + .is_symlink() + ); } #[test] -fn reopen_fd() { - use io_lifetimes::AsFilelike; +fn readdir_with_trailing_slashdot() { let tmpdir = tmpdir(); - check!(tmpdir.create_dir("subdir")); - let tmpdir2 = check!(cap_std::fs::Dir::reopen_dir(&tmpdir.as_filelike())); - assert!(tmpdir2.exists("subdir")); + let start = h::dir_of(&tmpdir); + check!(h::create_dir(&start, "dir")); + check!(h::create(&start, "dir/red")); + check!(h::create(&start, "dir/green")); + check!(h::create(&start, "dir/blue")); + + assert_eq!(check!(h::read_dir(&start, "dir")).count(), 3); + assert_eq!(check!(h::read_dir(&start, "dir/")).count(), 3); + assert_eq!(check!(h::read_dir(&start, "dir/.")).count(), 3); } #[test] fn metadata_vs_std_fs() { let tmpdir = tmpdir(); - check!(tmpdir.create_dir("dir")); - let dir = check!(tmpdir.open_dir("dir")); - let file = check!(dir.create("file")); + let start = h::dir_of(&tmpdir); + check!(h::create_dir(&start, "dir")); + let dir = check!(p::open_dir(&start, Path::new("dir"))); + let file = check!(h::create(&dir, "file")); - let cap_std_dir = check!(dir.dir_metadata()); - let cap_std_file = check!(file.metadata()); + let cap_std_dir = check!(p::Metadata::from_file(&dir)); + let cap_std_file = check!(p::Metadata::from_file(&file)); let cap_std_dir_entry = { - let mut entries = check!(dir.entries()); + let mut entries = check!(p::read_base_dir(&dir)); let entry = check!(entries.next().unwrap()); assert_eq!(entry.file_name(), "file"); assert!(entries.next().is_none(), "unexpected dir entry"); check!(entry.metadata()) }; - let std_dir = check!(dir.into_std_file().metadata()); - let std_file = check!(file.into_std().metadata()); + let std_dir = check!(dir.metadata()); + let std_file = check!(file.metadata()); match std_dir.created() { Ok(_) => println!("std::fs supports file created times"), @@ -947,10 +933,10 @@ fn metadata_vs_std_fs() { check_metadata(&std_file, &cap_std_dir_entry); } -fn check_metadata(std: &std::fs::Metadata, cap: &cap_std::fs::Metadata) { +fn check_metadata(std: &std::fs::Metadata, cap: &p::Metadata) { assert_eq!(std.is_dir(), cap.is_dir()); - assert_eq!(std.is_file(), cap.is_file()); - assert_eq!(std.is_symlink(), cap.is_symlink()); + assert_eq!(std.is_file(), cap.file_type().is_file()); + assert_eq!(std.is_symlink(), cap.file_type().is_symlink()); assert_eq!(std.file_type().is_dir(), cap.file_type().is_dir()); assert_eq!(std.file_type().is_file(), cap.file_type().is_file()); assert_eq!(std.file_type().is_symlink(), cap.file_type().is_symlink()); @@ -958,38 +944,20 @@ fn check_metadata(std: &std::fs::Metadata, cap: &cap_std::fs::Metadata) { { assert_eq!( std::os::unix::fs::FileTypeExt::is_block_device(&std.file_type()), - cap_std::fs::FileTypeExt::is_block_device(&cap.file_type()) + p::FileTypeExt::is_block_device(&cap.file_type()) ); assert_eq!( std::os::unix::fs::FileTypeExt::is_char_device(&std.file_type()), - cap_std::fs::FileTypeExt::is_char_device(&cap.file_type()) - ); - assert_eq!( - std::os::unix::fs::FileTypeExt::is_fifo(&std.file_type()), - cap_std::fs::FileTypeExt::is_fifo(&cap.file_type()) - ); - assert_eq!( - std::os::unix::fs::FileTypeExt::is_socket(&std.file_type()), - cap_std::fs::FileTypeExt::is_socket(&cap.file_type()) + p::FileTypeExt::is_char_device(&cap.file_type()) ); } assert_eq!(std.len(), cap.len()); - assert_eq!(std.permissions().readonly(), cap.permissions().readonly()); - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - assert_eq!( - std.permissions().mode(), - cap_std::fs::PermissionsExt::mode(&cap.permissions()) - ); - } - // If the standard library supports file modified/accessed/created times, - // then cap-std should too. + // then the primitives should too. match std.modified() { - Ok(expected) => assert_eq!(expected, check!(cap.modified()).into_std()), + Ok(expected) => assert_eq!(expected, check!(cap.modified())), Err(e) => assert!( cap.modified().is_err(), "modified time should be error ({}), got {:#?}", @@ -1005,7 +973,7 @@ fn check_metadata(std: &std::fs::Metadata, cap: &cap_std::fs::Metadata) { let access_tolerance = std::time::Duration::from_secs(ACCESS_TOLERANCE_SEC.into()); assert!( ((expected - access_tolerance)..(expected + access_tolerance)) - .contains(&check!(cap.accessed()).into_std()), + .contains(&check!(cap.accessed())), "std accessed {:#?}, cap accessed {:#?}", expected, cap.accessed() @@ -1019,7 +987,7 @@ fn check_metadata(std: &std::fs::Metadata, cap: &cap_std::fs::Metadata) { ), } match std.created() { - Ok(expected) => assert_eq!(expected, check!(cap.created()).into_std()), + Ok(expected) => assert_eq!(expected, check!(cap.created())), Err(e) => { // An earlier bug returned the Unix epoch instead of `None` when // created times were unavailable. This tries to catch such errors, @@ -1030,7 +998,7 @@ fn check_metadata(std: &std::fs::Metadata, cap: &cap_std::fs::Metadata) { "std returned error for created time ({}) but got {:#?}", e, actual ); - assert_ne!(actual, SystemClock::UNIX_EPOCH); + assert_ne!(actual, std::time::SystemTime::UNIX_EPOCH); } } } @@ -1038,29 +1006,9 @@ fn check_metadata(std: &std::fs::Metadata, cap: &cap_std::fs::Metadata) { #[cfg(unix)] { use std::os::unix::fs::MetadataExt; - assert_eq!(std.dev(), cap_std::fs::MetadataExt::dev(cap)); - assert_eq!(std.ino(), cap_std::fs::MetadataExt::ino(cap)); - assert_eq!(std.mode(), cap_std::fs::MetadataExt::mode(cap)); - assert_eq!(std.nlink(), cap_std::fs::MetadataExt::nlink(cap)); - assert_eq!(std.uid(), cap_std::fs::MetadataExt::uid(cap)); - assert_eq!(std.gid(), cap_std::fs::MetadataExt::gid(cap)); - assert_eq!(std.rdev(), cap_std::fs::MetadataExt::rdev(cap)); - assert_eq!(std.size(), cap_std::fs::MetadataExt::size(cap)); - assert!( - ((std.atime() - i64::from(ACCESS_TOLERANCE_SEC)) - ..(std.atime() + i64::from(ACCESS_TOLERANCE_SEC))) - .contains(&cap_std::fs::MetadataExt::atime(cap)), - "std atime {}, cap atime {}", - std.atime(), - cap_std::fs::MetadataExt::atime(cap) - ); - assert!((0..1_000_000_000).contains(&cap_std::fs::MetadataExt::atime_nsec(cap))); - assert_eq!(std.mtime(), cap_std::fs::MetadataExt::mtime(cap)); - assert_eq!(std.mtime_nsec(), cap_std::fs::MetadataExt::mtime_nsec(cap)); - assert_eq!(std.ctime(), cap_std::fs::MetadataExt::ctime(cap)); - assert_eq!(std.ctime_nsec(), cap_std::fs::MetadataExt::ctime_nsec(cap)); - assert_eq!(std.blksize(), cap_std::fs::MetadataExt::blksize(cap)); - assert_eq!(std.blocks(), cap_std::fs::MetadataExt::blocks(cap)); + assert_eq!(std.dev(), p::MetadataExt::dev(cap)); + assert_eq!(std.ino(), p::MetadataExt::ino(cap)); + assert_eq!(std.nlink(), p::MetadataExt::nlink(cap)); } } @@ -1069,15 +1017,16 @@ fn check_metadata(std: &std::fs::Metadata, cap: &cap_std::fs::Metadata) { #[test] fn dotdot_in_middle_of_symlink() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let foo = b"foo"; - check!(tmpdir.write("target", foo)); - check!(tmpdir.create_dir("b")); - let b = check!(tmpdir.open_dir("b")); - check!(symlink_dir("..", &b, "up")); + check!(h::write(&start, "target", foo)); + check!(h::create_dir(&start, "b")); + let b = check!(p::open_dir(&start, Path::new("b"))); + check!(h::symlink_dir(&b, "..", "up")); let path = "b/up/target"; - let mut file = check!(tmpdir.open(path)); + let mut file = check!(h::open(&start, path)); let mut data = Vec::new(); check!(file.read_to_end(&mut data)); assert_eq!(data, foo); @@ -1090,15 +1039,16 @@ fn dotdot_in_middle_of_symlink() { #[cfg_attr(windows, ignore)] fn dotdot_slashdot_in_middle_of_symlink() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let foo = b"foo"; - check!(tmpdir.write("target", foo)); - check!(tmpdir.create_dir("b")); - let b = check!(tmpdir.open_dir("b")); - check!(symlink_dir("../.", &b, "up")); + check!(h::write(&start, "target", foo)); + check!(h::create_dir(&start, "b")); + let b = check!(p::open_dir(&start, Path::new("b"))); + check!(h::symlink_dir(&b, "../.", "up")); let path = "b/up/target"; - let mut file = check!(tmpdir.open(path)); + let mut file = check!(h::open(&start, path)); let mut data = Vec::new(); check!(file.read_to_end(&mut data)); assert_eq!(data, foo); @@ -1111,15 +1061,16 @@ fn dotdot_slashdot_in_middle_of_symlink() { #[cfg_attr(windows, ignore)] fn dotdot_more_in_middle_of_symlink() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let foo = b"foo"; - check!(tmpdir.write("target", foo)); - check!(tmpdir.create_dir_all("b/c")); - let b = check!(tmpdir.open_dir("b")); - check!(symlink_dir("c/../..", &b, "up")); + check!(h::write(&start, "target", foo)); + check!(h::create_dir_all(&start, "b/c")); + let b = check!(p::open_dir(&start, Path::new("b"))); + check!(h::symlink_dir(&b, "c/../..", "up")); let path = "b/up/target"; - let mut file = check!(tmpdir.open(path)); + let mut file = check!(h::open(&start, path)); let mut data = Vec::new(); check!(file.read_to_end(&mut data)); assert_eq!(data, foo); @@ -1132,15 +1083,16 @@ fn dotdot_more_in_middle_of_symlink() { #[cfg_attr(windows, ignore)] fn dotdot_slashdot_more_in_middle_of_symlink() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let foo = b"foo"; - check!(tmpdir.write("target", foo)); - check!(tmpdir.create_dir_all("b/c")); - let b = check!(tmpdir.open_dir("b")); - check!(symlink_dir("c/../../.", &b, "up")); + check!(h::write(&start, "target", foo)); + check!(h::create_dir_all(&start, "b/c")); + let b = check!(p::open_dir(&start, Path::new("b"))); + check!(h::symlink_dir(&b, "c/../../.", "up")); let path = "b/up/target"; - let mut file = check!(tmpdir.open(path)); + let mut file = check!(h::open(&start, path)); let mut data = Vec::new(); check!(file.read_to_end(&mut data)); assert_eq!(data, foo); @@ -1154,15 +1106,16 @@ fn dotdot_slashdot_more_in_middle_of_symlink() { #[cfg_attr(windows, ignore)] fn dotdot_other_in_middle_of_symlink() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let foo = b"foo"; - check!(tmpdir.write("target", foo)); - check!(tmpdir.create_dir_all("b/c")); - let c = check!(tmpdir.open_dir("b/c")); - check!(symlink_dir("../..", &c, "up")); + check!(h::write(&start, "target", foo)); + check!(h::create_dir_all(&start, "b/c")); + let c = check!(p::open_dir(&start, Path::new("b/c"))); + check!(h::symlink_dir(&c, "../..", "up")); let path = "b/c/up/target"; - let mut file = check!(tmpdir.open(path)); + let mut file = check!(h::open(&start, path)); let mut data = Vec::new(); check!(file.read_to_end(&mut data)); assert_eq!(data, foo); @@ -1175,15 +1128,16 @@ fn dotdot_other_in_middle_of_symlink() { #[cfg_attr(windows, ignore)] fn dotdot_slashdot_other_in_middle_of_symlink() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let foo = b"foo"; - check!(tmpdir.write("target", foo)); - check!(tmpdir.create_dir_all("b/c")); - let c = check!(tmpdir.open_dir("b/c")); - check!(symlink_dir("../../.", &c, "up")); + check!(h::write(&start, "target", foo)); + check!(h::create_dir_all(&start, "b/c")); + let c = check!(p::open_dir(&start, Path::new("b/c"))); + check!(h::symlink_dir(&c, "../../.", "up")); let path = "b/c/up/target"; - let mut file = check!(tmpdir.open(path)); + let mut file = check!(h::open(&start, path)); let mut data = Vec::new(); check!(file.read_to_end(&mut data)); assert_eq!(data, foo); @@ -1194,15 +1148,16 @@ fn dotdot_slashdot_other_in_middle_of_symlink() { #[test] fn dotdot_even_more_in_middle_of_symlink() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let foo = b"foo"; - check!(tmpdir.create_dir_all("b/c")); - check!(tmpdir.write("b/target", foo)); - let b = check!(tmpdir.open_dir("b")); - check!(symlink_dir("c/../../b", &b, "up")); + check!(h::create_dir_all(&start, "b/c")); + check!(h::write(&start, "b/target", foo)); + let b = check!(p::open_dir(&start, Path::new("b"))); + check!(h::symlink_dir(&b, "c/../../b", "up")); let path = "b/up/target"; - let mut file = check!(tmpdir.open(path)); + let mut file = check!(h::open(&start, path)); let mut data = Vec::new(); check!(file.read_to_end(&mut data)); assert_eq!(data, foo); @@ -1215,15 +1170,16 @@ fn dotdot_even_more_in_middle_of_symlink() { #[cfg_attr(windows, ignore)] fn dotdot_slashdot_even_more_in_middle_of_symlink() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let foo = b"foo"; - check!(tmpdir.create_dir_all("b/c")); - check!(tmpdir.write("b/target", foo)); - let b = check!(tmpdir.open_dir("b")); - check!(symlink_dir("c/../../b/.", &b, "up")); + check!(h::create_dir_all(&start, "b/c")); + check!(h::write(&start, "b/target", foo)); + let b = check!(p::open_dir(&start, Path::new("b"))); + check!(h::symlink_dir(&b, "c/../../b/.", "up")); let path = "b/up/target"; - let mut file = check!(tmpdir.open(path)); + let mut file = check!(h::open(&start, path)); let mut data = Vec::new(); check!(file.read_to_end(&mut data)); assert_eq!(data, foo); @@ -1234,15 +1190,16 @@ fn dotdot_slashdot_even_more_in_middle_of_symlink() { #[test] fn dotdot_even_other_in_middle_of_symlink() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let foo = b"foo"; - check!(tmpdir.create_dir_all("b/c")); - check!(tmpdir.write("b/target", foo)); - let c = check!(tmpdir.open_dir("b/c")); - check!(symlink_dir("../../b", &c, "up")); + check!(h::create_dir_all(&start, "b/c")); + check!(h::write(&start, "b/target", foo)); + let c = check!(p::open_dir(&start, Path::new("b/c"))); + check!(h::symlink_dir(&c, "../../b", "up")); let path = "b/c/up/target"; - let mut file = check!(tmpdir.open(path)); + let mut file = check!(h::open(&start, path)); let mut data = Vec::new(); check!(file.read_to_end(&mut data)); assert_eq!(data, foo); @@ -1255,160 +1212,58 @@ fn dotdot_even_other_in_middle_of_symlink() { #[cfg_attr(windows, ignore)] fn dotdot_slashdot_even_other_in_middle_of_symlink() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); let foo = b"foo"; - check!(tmpdir.create_dir_all("b/c")); - check!(tmpdir.write("b/target", foo)); - let c = check!(tmpdir.open_dir("b/c")); - check!(symlink_dir("../../b/.", &c, "up")); + check!(h::create_dir_all(&start, "b/c")); + check!(h::write(&start, "b/target", foo)); + let c = check!(p::open_dir(&start, Path::new("b/c"))); + check!(h::symlink_dir(&c, "../../b/.", "up")); let path = "b/c/up/target"; - let mut file = check!(tmpdir.open(path)); + let mut file = check!(h::open(&start, path)); let mut data = Vec::new(); check!(file.read_to_end(&mut data)); assert_eq!(data, foo); } -/// Similar to `dotdot_in_middle_of_symlink`, but this time the symlink to -/// `..` does happen to be the end of the path, so we need to make sure -/// the implementation doesn't just do a stack pop when it sees the `..` -/// leaving us with an `O_PATH` directory handle. -#[test] -fn dotdot_at_end_of_symlink() { - let tmpdir = tmpdir(); - - let foo = b"foo"; - check!(tmpdir.write("target", foo)); - check!(tmpdir.create_dir("b")); - let b = check!(tmpdir.open_dir("b")); - check!(symlink_dir("..", &b, "up")); - - // Do some things with `path` that might break with an `O_PATH` fd. - // On Linux, the `permissions` part doesn't because cap-std uses - // /proc/self/fd. But the `read_dir` part does. - let path = "b/up"; - - let perms = check!(tmpdir.metadata(path)).permissions(); - check!(tmpdir.set_permissions(path, perms)); - - let contents = check!(tmpdir.read_dir(path)); - for entry in contents { - let _entry = check!(entry); - } -} - -/// Like `dotdot_at_end_of_symlink`, but with a `/.` at the end. -/// -/// Windows doesn't appear to like symlinks that end with `/.`. -#[test] -#[cfg_attr(windows, ignore)] -fn dotdot_slashdot_at_end_of_symlink() { - let tmpdir = tmpdir(); - - let foo = b"foo"; - check!(tmpdir.write("target", foo)); - check!(tmpdir.create_dir("b")); - let b = check!(tmpdir.open_dir("b")); - check!(symlink_dir("../.", &b, "up")); - - // Do some things with `path` that might break with an `O_PATH` fd. - // On Linux, the `permissions` part doesn't because cap-std uses - // /proc/self/fd. But the `read_dir` part does. - let path = "b/up"; - - let perms = check!(tmpdir.metadata(path)).permissions(); - check!(tmpdir.set_permissions(path, perms)); - - let contents = check!(tmpdir.read_dir(path)); - for entry in contents { - let _entry = check!(entry); - } -} - -/// Like `dotdot_at_end_of_symlink`, but do everything inside a new directory, -/// so that `MaybeOwnedFile` doesn't reopen `.` which would artificially give -/// us a non-`O_PATH` fd. -#[test] -fn dotdot_at_end_of_symlink_all_inside_dir() { - let tmpdir = tmpdir(); - - let foo = b"foo"; - check!(tmpdir.create_dir("dir")); - check!(tmpdir.write("dir/target", foo)); - check!(tmpdir.create_dir("dir/b")); - let b = check!(tmpdir.open_dir("dir/b")); - check!(symlink_dir("..", &b, "up")); - - // Do some things with `path` that might break with an `O_PATH` fd. - // On Linux, the `permissions` part doesn't because cap-std uses - // /proc/self/fd. But the `read_dir` part does. - let path = "dir/b/up"; - - let perms = check!(tmpdir.metadata(path)).permissions(); - check!(tmpdir.set_permissions(path, perms)); - - let contents = check!(tmpdir.read_dir(path)); - for entry in contents { - let _entry = check!(entry); - } -} - -/// `dotdot_at_end_of_symlink_all_inside_dir`, but with a `/.` at the end. -/// -/// Windows doesn't appear to like symlinks that end with `/.`. -#[test] -#[cfg_attr(windows, ignore)] -fn dotdot_slashdot_at_end_of_symlink_all_inside_dir() { - let tmpdir = tmpdir(); - - let foo = b"foo"; - check!(tmpdir.create_dir("dir")); - check!(tmpdir.write("dir/target", foo)); - check!(tmpdir.create_dir("dir/b")); - let b = check!(tmpdir.open_dir("dir/b")); - check!(symlink_dir("../.", &b, "up")); - - // Do some things with `path` that might break with an `O_PATH` fd. - // On Linux, the `permissions` part doesn't because cap-std uses - // /proc/self/fd. But the `read_dir` part does. - let path = "dir/b/up"; - - let perms = check!(tmpdir.metadata(path)).permissions(); - check!(tmpdir.set_permissions(path, perms)); - - let contents = check!(tmpdir.read_dir(path)); - for entry in contents { - let _entry = check!(entry); - } -} - /// Ensure that a path of "/" is rejected. #[test] fn statat_slash() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); // FreeBSD 14+ uses `O_RESOLVE_BENEATH` which issues different errors. #[cfg(target_os = "freebsd")] { - error_contains!(tmpdir.metadata("/"), "Capabilities insufficient"); - error_contains!(tmpdir.metadata("/foo"), "Capabilities insufficient"); - error_contains!(tmpdir.symlink_metadata("/"), "Capabilities insufficient"); - error_contains!(tmpdir.symlink_metadata("/foo"), "Capabilities insufficient"); + error_contains!(h::metadata(&start, "/"), "Capabilities insufficient"); + error_contains!(h::metadata(&start, "/foo"), "Capabilities insufficient"); + error_contains!( + h::symlink_metadata(&start, "/"), + "Capabilities insufficient" + ); + error_contains!( + h::symlink_metadata(&start, "/foo"), + "Capabilities insufficient" + ); } #[cfg(not(target_os = "freebsd"))] { - error_contains!(tmpdir.metadata("/"), "a path led outside of the filesystem"); error_contains!( - tmpdir.metadata("/foo"), + h::metadata(&start, "/"), + "a path led outside of the filesystem" + ); + error_contains!( + h::metadata(&start, "/foo"), "a path led outside of the filesystem" ); error_contains!( - tmpdir.symlink_metadata("/"), + h::symlink_metadata(&start, "/"), "a path led outside of the filesyste" ); error_contains!( - tmpdir.symlink_metadata("/foo"), + h::symlink_metadata(&start, "/foo"), "a path led outside of the filesyste" ); } @@ -1418,24 +1273,21 @@ fn statat_slash() { #[test] fn trailing_slash_symlink() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); - check!(tmpdir.create_dir("sandbox")); - check!(symlink_dir("../outside", &tmpdir, "sandbox/hidden")); - check!(symlink_dir("hidden/", &tmpdir, "sandbox/indirect")); + check!(h::create_dir(&start, "sandbox")); + check!(h::symlink_dir(&start, "../outside", "sandbox/hidden")); + check!(h::symlink_dir(&start, "hidden/", "sandbox/indirect")); - let sandbox = check!(tmpdir.open_dir("sandbox")); + let sandbox = check!(p::open_dir(&start, Path::new("sandbox"))); for path in ["hidden", "hidden/", "indirect", "indirect/"] { - error!( - sandbox.open_dir(path), - "a path led outside of the filesystem" - ); - error!( - sandbox.read_dir(path), + error_contains!( + p::open_dir(&sandbox, Path::new(path)), "a path led outside of the filesystem" ); - error!( - sandbox.canonicalize(path), + error_contains!( + h::read_dir(&sandbox, path), "a path led outside of the filesystem" ); } @@ -1483,9 +1335,9 @@ fn trailing_slash_symlink_more() { compile_error!("not implemented yet"); } - let tmpdir = check!(Dir::open_ambient_dir(tmpdir.path(), ambient_authority())); + let start = check!(h::open_ambient_dir(tmpdir.path())); - let sandbox = check!(tmpdir.open_dir("sandbox")); + let sandbox = check!(p::open_dir(&start, Path::new("sandbox"))); for path in [ "hidden", @@ -1495,16 +1347,12 @@ fn trailing_slash_symlink_more() { "root_link", "root_link/", ] { - error!( - sandbox.open_dir(path), - "a path led outside of the filesystem" - ); - error!( - sandbox.read_dir(path), + error_contains!( + p::open_dir(&sandbox, Path::new(path)), "a path led outside of the filesystem" ); - error!( - sandbox.canonicalize(path), + error_contains!( + h::read_dir(&sandbox, path), "a path led outside of the filesystem" ); } diff --git a/crates/wasi/src/filesystem/primitives/tests/helpers/mod.rs b/crates/wasi/src/filesystem/primitives/tests/helpers/mod.rs new file mode 100644 index 000000000000..a9fdb2ba53f9 --- /dev/null +++ b/crates/wasi/src/filesystem/primitives/tests/helpers/mod.rs @@ -0,0 +1,151 @@ +//! Shared helpers for the tests imported from `cap-std`. +//! +//! These tests were written against `cap_std::fs::Dir`, whose methods bundle up +//! several `crate::filesystem::primitives` calls. Anything that is a bare +//! forward to a primitive is called as `p::foo(..)` directly at the call site; +//! only the operations that add options, flags, or logic live here. + +use crate::filesystem::primitives as p; +use ambient_authority::ambient_authority; +use std::fs::File; +use std::io; +use std::path::{Path, PathBuf}; +use tempfile::TempDir; + +/// Open a temporary directory as a start-directory handle. +pub fn dir_of(t: &TempDir) -> File { + p::open_ambient_dir(t.path(), ambient_authority()).unwrap() +} + +/// `open_ambient_dir` with the ambient authority supplied. +pub fn open_ambient_dir(path: impl AsRef) -> io::Result { + p::open_ambient_dir(path.as_ref(), ambient_authority()) +} + +/// `Dir::create`: open for writing, creating and truncating. +pub fn create(d: &File, path: impl AsRef) -> io::Result { + p::open( + d, + path.as_ref(), + p::OpenOptions::new() + .write(true) + .create(true) + .truncate(true), + ) +} + +/// `Dir::open`: open for reading. +pub fn open(d: &File, path: impl AsRef) -> io::Result { + p::open(d, path.as_ref(), p::OpenOptions::new().read(true)) +} + +/// `Dir::create_dir`, with the default `DirOptions`. +pub fn create_dir(d: &File, path: impl AsRef) -> io::Result<()> { + p::create_dir(d, path.as_ref(), &p::DirOptions::new()) +} + +/// `Dir::create_dir_all`, in terms of the single-level `create_dir`. +pub fn create_dir_all(d: &File, path: impl AsRef) -> io::Result<()> { + let mut acc = PathBuf::new(); + for component in path.as_ref().components() { + acc.push(component); + match p::create_dir(d, &acc, &p::DirOptions::new()) { + Ok(()) => {} + Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {} + Err(e) => return Err(e), + } + } + Ok(()) +} + +/// `Dir::metadata`: stat, following symlinks. +pub fn metadata(d: &File, path: impl AsRef) -> io::Result { + p::stat(d, path.as_ref(), p::FollowSymlinks::Yes) +} + +/// `Dir::symlink_metadata`: stat, without following symlinks. +pub fn symlink_metadata(d: &File, path: impl AsRef) -> io::Result { + p::stat(d, path.as_ref(), p::FollowSymlinks::No) +} + +/// `DirExt::open_dir_nofollow`. +pub fn open_dir_nofollow(d: &File, path: impl AsRef) -> io::Result { + p::open( + d, + path.as_ref(), + p::dir_options().follow(p::FollowSymlinks::No), + ) +} + +/// `Dir::read_dir`: open the subdirectory, then read its entries. +pub fn read_dir(d: &File, path: impl AsRef) -> io::Result { + p::read_base_dir(&p::open_dir(d, path.as_ref())?) +} + +pub fn exists(d: &File, path: impl AsRef) -> bool { + metadata(d, path).is_ok() +} + +pub fn is_dir(d: &File, path: impl AsRef) -> bool { + metadata(d, path).map(|m| m.is_dir()).unwrap_or(false) +} + +pub fn is_file(d: &File, path: impl AsRef) -> bool { + metadata(d, path) + .map(|m| m.file_type().is_file()) + .unwrap_or(false) +} + +pub fn write(d: &File, path: impl AsRef, contents: impl AsRef<[u8]>) -> io::Result<()> { + use std::io::Write; + let mut f = create(d, path)?; + f.write_all(contents.as_ref()) +} + +pub fn read(d: &File, path: impl AsRef) -> io::Result> { + use std::io::Read; + let mut f = open(d, path)?; + let mut v = Vec::new(); + f.read_to_end(&mut v)?; + Ok(v) +} + +pub fn read_to_string(d: &File, path: impl AsRef) -> io::Result { + use std::io::Read; + let mut f = open(d, path)?; + let mut s = String::new(); + f.read_to_string(&mut s)?; + Ok(s) +} + +/// `DirExt::symlink`. On Unix there is only one flavour of symlink; on Windows +/// the file/dir distinction matters, so these dispatch accordingly. +#[cfg(not(windows))] +pub fn symlink(d: &File, src: impl AsRef, dst: impl AsRef) -> io::Result<()> { + p::symlink(src.as_ref(), d, dst.as_ref()) +} + +#[cfg(windows)] +pub fn symlink(d: &File, src: impl AsRef, dst: impl AsRef) -> io::Result<()> { + p::symlink_file(src.as_ref(), d, dst.as_ref()) +} + +#[cfg(not(windows))] +pub fn symlink_file(d: &File, src: impl AsRef, dst: impl AsRef) -> io::Result<()> { + p::symlink(src.as_ref(), d, dst.as_ref()) +} + +#[cfg(windows)] +pub fn symlink_file(d: &File, src: impl AsRef, dst: impl AsRef) -> io::Result<()> { + p::symlink_file(src.as_ref(), d, dst.as_ref()) +} + +#[cfg(not(windows))] +pub fn symlink_dir(d: &File, src: impl AsRef, dst: impl AsRef) -> io::Result<()> { + p::symlink(src.as_ref(), d, dst.as_ref()) +} + +#[cfg(windows)] +pub fn symlink_dir(d: &File, src: impl AsRef, dst: impl AsRef) -> io::Result<()> { + p::symlink_dir(src.as_ref(), d, dst.as_ref()) +} diff --git a/crates/wasi/src/filesystem/primitives/tests/is_file_read_write.rs b/crates/wasi/src/filesystem/primitives/tests/is_file_read_write.rs deleted file mode 100644 index 21e68baccee7..000000000000 --- a/crates/wasi/src/filesystem/primitives/tests/is_file_read_write.rs +++ /dev/null @@ -1,43 +0,0 @@ -#[macro_use] -mod sys_common; - -use cap_fs_ext::{IsFileReadWrite, OpenOptions}; -use sys_common::io::tmpdir; - -#[test] -fn basic_is_file_read_write() { - let tmpdir = tmpdir(); - - let file = check!(tmpdir.open_with( - "file", - OpenOptions::new() - .create(true) - .truncate(true) - .write(true) - .read(true) - )); - assert_eq!(check!(file.is_file_read_write()), (true, true)); - - let file = check!(tmpdir.open_with("file", OpenOptions::new().append(true).read(true))); - assert_eq!(check!(file.is_file_read_write()), (true, true)); - - let file = check!(tmpdir.open_with( - "file", - OpenOptions::new() - .create(true) - .truncate(true) - .write(true) - .read(false) - )); - assert_eq!(check!(file.is_file_read_write()), (false, true)); - - let file = check!(tmpdir.open_with( - "file", - OpenOptions::new() - .create(false) - .truncate(false) - .write(false) - .read(true) - )); - assert_eq!(check!(file.is_file_read_write()), (true, false)); -} diff --git a/crates/wasi/src/filesystem/primitives/tests/issue_22577.rs b/crates/wasi/src/filesystem/primitives/tests/issue_22577.rs deleted file mode 100644 index 6c80c1abc828..000000000000 --- a/crates/wasi/src/filesystem/primitives/tests/issue_22577.rs +++ /dev/null @@ -1,31 +0,0 @@ -// This test module derived from Rust's src/test/ui/issues/issue-22577.rs -// at revision 108e90ca78f052c0c1c49c42a22c85620be19712. - -// run-pass -#![allow(dead_code)] -// pretty-expanded FIXME #23616 -// ignore-cloudabi no std::fs - -use cap_std::{fs, net}; - -fn assert_both() {} -fn assert_send() {} - -#[test] -fn issue_22577() { - assert_both::(); - assert_both::(); - assert_both::(); - assert_both::(); - assert_both::(); - assert_both::(); - - assert_both::(); - assert_both::(); - assert_both::(); - assert_both::(); - assert_both::(); - assert_both::(); - assert_both::(); - assert_both::(); -} diff --git a/crates/wasi/src/filesystem/primitives/tests/metadata_ext.rs b/crates/wasi/src/filesystem/primitives/tests/metadata_ext.rs index 0aad8cc52705..b5322b747663 100644 --- a/crates/wasi/src/filesystem/primitives/tests/metadata_ext.rs +++ b/crates/wasi/src/filesystem/primitives/tests/metadata_ext.rs @@ -1,24 +1,30 @@ -// This file contains tests for `cap_fs_ext::MetatadaExt`. +// This file contains tests for `MetadataExt`. +// +// `dev`/`ino`/`nlink` are only on the Unix `MetadataExt`. +#![cfg(unix)] -#[macro_use] -mod sys_common; - -use cap_fs_ext::{DirExt, MetadataExt}; -use sys_common::io::tmpdir; -use sys_common::symlink_supported; +use super::helpers as h; +use super::sys_common::io::tmpdir; +use super::sys_common::symlink_supported; +use crate::filesystem::primitives::{ + FollowSymlinks, Metadata, MetadataExt, hard_link, open_ambient_dir, stat, +}; +use ambient_authority::ambient_authority; +use std::path::Path; #[test] fn test_metadata_ext() { let tmpdir = tmpdir(); - let a = check!(tmpdir.create("a")); - let b = check!(tmpdir.create("b")); - let tmpdir_metadata = check!(tmpdir.dir_metadata()); - let a_metadata = check!(a.metadata()); - let b_metadata = check!(b.metadata()); - let a_dir_metadata = check!(tmpdir.metadata("a")); - let b_dir_metadata = check!(tmpdir.metadata("b")); - let a_symlink_metadata = check!(tmpdir.symlink_metadata("a")); - let b_symlink_metadata = check!(tmpdir.symlink_metadata("b")); + let dir = check!(open_ambient_dir(tmpdir.path(), ambient_authority())); + let a = check!(h::create(&dir, "a")); + let b = check!(h::create(&dir, "b")); + let tmpdir_metadata = check!(Metadata::from_file(&dir)); + let a_metadata = check!(Metadata::from_file(&a)); + let b_metadata = check!(Metadata::from_file(&b)); + let a_dir_metadata = check!(stat(&dir, Path::new("a"), FollowSymlinks::Yes)); + let b_dir_metadata = check!(stat(&dir, Path::new("b"), FollowSymlinks::Yes)); + let a_symlink_metadata = check!(stat(&dir, Path::new("a"), FollowSymlinks::No)); + let b_symlink_metadata = check!(stat(&dir, Path::new("b"), FollowSymlinks::No)); // The directory and files inside it should be on the same device. assert_eq!(tmpdir_metadata.dev(), a_metadata.dev()); @@ -34,8 +40,8 @@ fn test_metadata_ext() { assert_eq!(b_metadata.nlink(), 1); // Add another link and check for it. - check!(tmpdir.hard_link("b", &tmpdir, "c")); - let b_metadata = check!(b.metadata()); + check!(hard_link(&dir, Path::new("b"), &dir, Path::new("c"))); + let b_metadata = check!(Metadata::from_file(&b)); assert_eq!(b_metadata.nlink(), 2); // Check that the metadata has dev/nlink/ino. @@ -62,9 +68,9 @@ fn test_metadata_ext() { b_symlink_metadata.ino(); if symlink_supported() { - check!(DirExt::symlink_file(&*tmpdir, "b", "d")); - let d_metadata = check!(tmpdir.metadata("d")); - let d_symlink_metadata = check!(tmpdir.symlink_metadata("d")); + check!(h::symlink_file(&dir, "b", "d")); + let d_metadata = check!(stat(&dir, Path::new("d"), FollowSymlinks::Yes)); + let d_symlink_metadata = check!(stat(&dir, Path::new("d"), FollowSymlinks::No)); d_metadata.dev(); d_metadata.nlink(); diff --git a/crates/wasi/src/filesystem/primitives/tests/mod.rs b/crates/wasi/src/filesystem/primitives/tests/mod.rs index 8adc2abd510b..85545e17dfd2 100644 --- a/crates/wasi/src/filesystem/primitives/tests/mod.rs +++ b/crates/wasi/src/filesystem/primitives/tests/mod.rs @@ -1,26 +1,19 @@ -mod canonicalize; +#[macro_use] +mod sys_common; + +mod helpers; + mod cap_basics; -mod dir_entry_ext; -mod dir_ext; mod file_type_ext; mod fs; mod fs_additional; -mod is_file_read_write; -mod issue_22577; mod metadata_ext; -mod open_ambient; mod paths_containing_nul; -mod rand; mod readdir; mod rename; mod rename_directory; -mod reopen; mod reopendir; -mod root; -mod set; mod set_times; mod symlinks; -mod sys; -mod sys_common; mod windows_open; mod windows_symlinks; diff --git a/crates/wasi/src/filesystem/primitives/tests/open_ambient.rs b/crates/wasi/src/filesystem/primitives/tests/open_ambient.rs deleted file mode 100644 index 870ea0976cbb..000000000000 --- a/crates/wasi/src/filesystem/primitives/tests/open_ambient.rs +++ /dev/null @@ -1,29 +0,0 @@ -#[macro_use] -mod sys_common; - -use cap_std::ambient_authority; -use cap_std::fs::{Dir, File}; - -#[test] -fn test_open_ambient() { - let _ = File::open_ambient("Cargo.toml", ambient_authority()).unwrap(); -} - -#[test] -fn test_create_ambient() { - let dir = tempfile::tempdir().unwrap(); - let foo_path = dir.path().join("foo"); - let _ = File::create_ambient(&foo_path, ambient_authority()).unwrap(); - let _ = File::open_ambient(&foo_path, ambient_authority()).unwrap(); - let _ = File::create_ambient(&foo_path, ambient_authority()).unwrap(); -} - -#[test] -fn test_create_dir_ambient() { - let dir = tempfile::tempdir().unwrap(); - let foo_path = dir.path().join("foo"); - Dir::create_ambient_dir_all(&foo_path, ambient_authority()).unwrap(); - let foo = Dir::open_ambient_dir(&foo_path, ambient_authority()).unwrap(); - let base = foo.open_parent_dir(ambient_authority()).unwrap(); - let _foo_again = base.open_dir("foo").unwrap(); -} diff --git a/crates/wasi/src/filesystem/primitives/tests/paths_containing_nul.rs b/crates/wasi/src/filesystem/primitives/tests/paths_containing_nul.rs index 2eefc9c808a5..9f7bbd977cc2 100644 --- a/crates/wasi/src/filesystem/primitives/tests/paths_containing_nul.rs +++ b/crates/wasi/src/filesystem/primitives/tests/paths_containing_nul.rs @@ -9,10 +9,14 @@ // ignore-emscripten no files // ignore-sgx no files -mod sys_common; - +use super::sys_common::io::tmpdir; +use crate::filesystem::primitives::{ + DirOptions, FollowSymlinks, OpenOptions, create_dir, hard_link, open, open_ambient_dir, + read_link, remove_dir, remove_file, rename, stat, +}; +use ambient_authority::ambient_authority; use std::io; -use sys_common::io::tmpdir; +use std::path::Path; fn assert_invalid_input(on: &str, result: io::Result) { fn inner(on: &str, result: io::Result<()>) { @@ -39,32 +43,36 @@ fn assert_invalid_input(on: &str, result: io::Result) { #[test] fn paths_containing_nul() { let tmpdir = tmpdir(); + let dir = open_ambient_dir(tmpdir.path(), ambient_authority()).unwrap(); + let nul = Path::new("\0"); - assert_invalid_input("File::open", tmpdir.open("\0")); - assert_invalid_input("File::create", tmpdir.create("\0")); - assert_invalid_input("remove_file", tmpdir.remove_file("\0")); - assert_invalid_input("metadata", tmpdir.metadata("\0")); - assert_invalid_input("symlink_metadata", tmpdir.symlink_metadata("\0")); + assert_invalid_input("open", open(&dir, nul, OpenOptions::new().read(true))); + assert_invalid_input( + "create", + open( + &dir, + nul, + OpenOptions::new().write(true).create(true).truncate(true), + ), + ); + assert_invalid_input("remove_file", remove_file(&dir, nul)); + assert_invalid_input("metadata", stat(&dir, nul, FollowSymlinks::Yes)); + assert_invalid_input("symlink_metadata", stat(&dir, nul, FollowSymlinks::No)); // Create a file inside the sandbox. - let dummy_file = "dummy_file"; - tmpdir.create(dummy_file).expect("creating dummy_file"); + let dummy_file = Path::new("dummy_file"); + open( + &dir, + dummy_file, + OpenOptions::new().write(true).create(true).truncate(true), + ) + .expect("creating dummy_file"); - assert_invalid_input("rename1", tmpdir.rename("\0", &tmpdir, "a")); - assert_invalid_input("rename2", tmpdir.rename(&dummy_file, &tmpdir, "\0")); - assert_invalid_input("copy1", tmpdir.copy("\0", &tmpdir, "a")); - assert_invalid_input("copy2", tmpdir.copy(&dummy_file, &tmpdir, "\0")); - assert_invalid_input("hard_link1", tmpdir.hard_link("\0", &tmpdir, "a")); - assert_invalid_input("hard_link2", tmpdir.hard_link(&dummy_file, &tmpdir, "\0")); - //fixmeassert_invalid_input("soft_link1", tmpdir.soft_link("\0", &tmpdir, - // "a")); fixmeassert_invalid_input("soft_link2", - // tmpdir.soft_link(&dummy_file, &tmpdir, "\0")); - assert_invalid_input("read_link", tmpdir.read_link("\0")); - assert_invalid_input("canonicalize", tmpdir.canonicalize("\0")); - assert_invalid_input("create_dir", tmpdir.create_dir("\0")); - assert_invalid_input("create_dir_all", tmpdir.create_dir_all("\0")); - assert_invalid_input("remove_dir", tmpdir.remove_dir("\0")); - assert_invalid_input("remove_dir_all", tmpdir.remove_dir_all("\0")); - assert_invalid_input("read_dir", tmpdir.read_dir("\0")); - // `Dir` has no `set_permissions` function. + assert_invalid_input("rename1", rename(&dir, nul, &dir, Path::new("a"))); + assert_invalid_input("rename2", rename(&dir, dummy_file, &dir, nul)); + assert_invalid_input("hard_link1", hard_link(&dir, nul, &dir, Path::new("a"))); + assert_invalid_input("hard_link2", hard_link(&dir, dummy_file, &dir, nul)); + assert_invalid_input("read_link", read_link(&dir, nul)); + assert_invalid_input("create_dir", create_dir(&dir, nul, &DirOptions::new())); + assert_invalid_input("remove_dir", remove_dir(&dir, nul)); } diff --git a/crates/wasi/src/filesystem/primitives/tests/rand.rs b/crates/wasi/src/filesystem/primitives/tests/rand.rs deleted file mode 100644 index b779e23fa4c5..000000000000 --- a/crates/wasi/src/filesystem/primitives/tests/rand.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[test] -fn test_std_rng_from_entropy() { - let rng = cap_rand::std_rng_from_os_rng(cap_rand::ambient_authority()); - assert_eq!(rng.clone(), rng); -} diff --git a/crates/wasi/src/filesystem/primitives/tests/readdir.rs b/crates/wasi/src/filesystem/primitives/tests/readdir.rs index 5b94c0d8c72e..8dcc5c34d280 100644 --- a/crates/wasi/src/filesystem/primitives/tests/readdir.rs +++ b/crates/wasi/src/filesystem/primitives/tests/readdir.rs @@ -1,6 +1,7 @@ -use cap_std::ambient_authority; -use cap_std::fs::{Dir, DirEntry}; +use crate::filesystem::primitives::{DirEntry, open_ambient_dir, read_base_dir}; +use ambient_authority::ambient_authority; use std::collections::HashMap; +use std::fs::File; use std::path::Path; #[test] @@ -38,7 +39,7 @@ fn test_dir_entries() { #[test] fn test_reread_entries() { let tmpdir = tempfile::tempdir().expect("construct tempdir"); - let dir = Dir::open_ambient_dir(tmpdir.path(), ambient_authority()).unwrap(); + let dir = open_ambient_dir(tmpdir.path(), ambient_authority()).unwrap(); let entries = read_entries(&dir); assert_eq!(entries.len(), 0, "empty dir"); @@ -69,13 +70,13 @@ fn test_reread_entries() { } fn dir_entries(path: &Path) -> HashMap { - let dir = Dir::open_ambient_dir(path, ambient_authority()).unwrap(); + let dir = open_ambient_dir(path, ambient_authority()).unwrap(); read_entries(&dir) } -fn read_entries(dir: &Dir) -> HashMap { +fn read_entries(dir: &File) -> HashMap { let mut out = HashMap::new(); - for e in dir.entries().unwrap() { + for e in read_base_dir(dir).unwrap() { let e = e.expect("non-error entry"); let name = e.file_name().to_str().expect("utf8 filename").to_owned(); assert!(out.get(&name).is_none(), "name already read: {}", name); diff --git a/crates/wasi/src/filesystem/primitives/tests/rename.rs b/crates/wasi/src/filesystem/primitives/tests/rename.rs index 7b487fa891fb..ca371eb6f7b3 100644 --- a/crates/wasi/src/filesystem/primitives/tests/rename.rs +++ b/crates/wasi/src/filesystem/primitives/tests/rename.rs @@ -1,7 +1,8 @@ -#[macro_use] -mod sys_common; +use super::helpers as h; +use super::sys_common::io::tmpdir; +use crate::filesystem::primitives as p; -use sys_common::io::tmpdir; +use std::path::Path; /* #[cfg(not(windows))] @@ -68,111 +69,170 @@ cfg_if::cfg_if! { #[cfg_attr(windows, ignore)] // TODO: Blocked on error message discrepancies fn rename_basics() { let tmpdir = tmpdir(); + let dir = h::dir_of(&tmpdir); - check!(tmpdir.create_dir_all("foo/bar")); - check!(tmpdir.create("foo/bar/file.txt")); + check!(h::create_dir_all(&dir, "foo/bar")); + check!(h::create(&dir, "foo/bar/file.txt")); - check!(tmpdir.rename("foo/bar/file.txt", &tmpdir, "foo/bar/renamed.txt")); - assert!(!tmpdir.exists("foo/bar/file.txt")); - assert!(tmpdir.exists("foo/bar/renamed.txt")); + check!(p::rename( + &dir, + Path::new("foo/bar/file.txt"), + &dir, + Path::new("foo/bar/renamed.txt") + )); + assert!(!h::exists(&dir, "foo/bar/file.txt")); + assert!(h::exists(&dir, "foo/bar/renamed.txt")); - check!(tmpdir.rename("foo/bar/renamed.txt", &tmpdir, "foo/bar/renamed.txt")); + check!(p::rename( + &dir, + Path::new("foo/bar/renamed.txt"), + &dir, + Path::new("foo/bar/renamed.txt") + )); error_contains!( - tmpdir.rename("foo/bar/renamed.txt", &tmpdir, ".."), + p::rename( + &dir, + Path::new("foo/bar/renamed.txt"), + &dir, + Path::new("..") + ), "a path led outside of the filesystem" ); error_contains!( - tmpdir.rename("foo/bar/renamed.txt", &tmpdir, "foo/../.."), + p::rename( + &dir, + Path::new("foo/bar/renamed.txt"), + &dir, + Path::new("foo/../..") + ), "a path led outside of the filesystem" ); error_contains!( - tmpdir.rename("foo/bar/renamed.txt", &tmpdir, "/tmp"), + p::rename( + &dir, + Path::new("foo/bar/renamed.txt"), + &dir, + Path::new("/tmp") + ), "a path led outside of the filesystem" ); error_contains!( - tmpdir.rename("foo/bar/renamed.txt", &tmpdir, "foo/bar/baz/.."), + p::rename( + &dir, + Path::new("foo/bar/renamed.txt"), + &dir, + Path::new("foo/bar/baz/..") + ), &no_such_file_or_directory() ); /* // TODO: Platform-specific error code. error!( - tmpdir.rename("foo/bar/renamed.txt", &tmpdir, "foo/bar"), + p::rename(&dir, Path::new("foo/bar/renamed.txt"), &dir, Path::new("foo/bar")), &rename_file_over_dir() ); */ - check!(tmpdir.rename("foo/bar", &tmpdir, "foo/bar")); - check!(tmpdir.rename("foo/bar/renamed.txt", &tmpdir, "file.txt")); - assert!(!tmpdir.exists("foo/bar/renamed.txt")); - assert!(tmpdir.exists("file.txt")); + check!(p::rename( + &dir, + Path::new("foo/bar"), + &dir, + Path::new("foo/bar") + )); + check!(p::rename( + &dir, + Path::new("foo/bar/renamed.txt"), + &dir, + Path::new("file.txt") + )); + assert!(!h::exists(&dir, "foo/bar/renamed.txt")); + assert!(h::exists(&dir, "file.txt")); /* // TODO: Platform-specific error code. error_contains!( - tmpdir.rename("file.txt", &tmpdir, "foo/.."), + p::rename(&dir, Path::new("file.txt"), &dir, Path::new("foo/..")), &rename_path_in_use() ); error_contains!( - tmpdir.rename("file.txt", &tmpdir, "foo/."), + p::rename(&dir, Path::new("file.txt"), &dir, Path::new("foo/.")), &rename_path_in_use() ); error_contains!( - tmpdir.rename("file.txt", &tmpdir, "foo/bar/../.."), + p::rename(&dir, Path::new("file.txt"), &dir, Path::new("foo/bar/../..")), &rename_path_in_use() ); */ error_contains!( - tmpdir.rename("file.txt", &tmpdir, "foo/bar/../../.."), + p::rename( + &dir, + Path::new("file.txt"), + &dir, + Path::new("foo/bar/../../..") + ), "a path led outside of the filesystem" ); error_contains!( - tmpdir.rename("file.txt", &tmpdir, "foo/bar/../../../something"), + p::rename( + &dir, + Path::new("file.txt"), + &dir, + Path::new("foo/bar/../../../something") + ), "a path led outside of the filesystem" ); - error_contains!(tmpdir.rename("file.txt", &tmpdir, ""), "No such file"); error_contains!( - tmpdir.rename("file.txt", &tmpdir, "/"), + p::rename(&dir, Path::new("file.txt"), &dir, Path::new("")), + "No such file" + ); + error_contains!( + p::rename(&dir, Path::new("file.txt"), &dir, Path::new("/")), "a path led outside of the filesystem" ); error_contains!( - tmpdir.rename("file.txt", &tmpdir, "/."), + p::rename(&dir, Path::new("file.txt"), &dir, Path::new("/.")), "a path led outside of the filesystem" ); error_contains!( - tmpdir.rename("file.txt", &tmpdir, "/.."), + p::rename(&dir, Path::new("file.txt"), &dir, Path::new("/..")), "a path led outside of the filesystem" ); error_contains!( - tmpdir.rename("/", &tmpdir, "nope.txt"), + p::rename(&dir, Path::new("/"), &dir, Path::new("nope.txt")), "a path led outside of the filesystem" ); error_contains!( - tmpdir.rename("/..", &tmpdir, "nope.txt"), + p::rename(&dir, Path::new("/.."), &dir, Path::new("nope.txt")), "a path led outside of the filesystem" ); error_contains!( - tmpdir.rename("file.txt/", &tmpdir, "nope.txt"), + p::rename(&dir, Path::new("file.txt/"), &dir, Path::new("nope.txt")), "Not a directory" ); /* // TODO: Platform-specific error code. error!( - tmpdir.rename("file.txt", &tmpdir, "."), + p::rename(&dir, Path::new("file.txt"), &dir, Path::new(".")), &rename_file_over_dot() ); error!( - tmpdir.rename("file.txt", &tmpdir, ".."), + p::rename(&dir, Path::new("file.txt"), &dir, Path::new("..")), &rename_path_in_use() ); error!( - tmpdir.rename("..", &tmpdir, "nope.txt"), + p::rename(&dir, Path::new(".."), &dir, Path::new("nope.txt")), &rename_path_in_use() ); error!( - tmpdir.rename(".", &tmpdir, "nope.txt"), + p::rename(&dir, Path::new("."), &dir, Path::new("nope.txt")), &rename_dot_over_file() ); */ - check!(tmpdir.create("existing.txt")); - check!(tmpdir.rename("file.txt", &tmpdir, "existing.txt")); - assert!(!tmpdir.exists("file.txt")); - assert!(tmpdir.exists("existing.txt")); + check!(h::create(&dir, "existing.txt")); + check!(p::rename( + &dir, + Path::new("file.txt"), + &dir, + Path::new("existing.txt") + )); + assert!(!h::exists(&dir, "file.txt")); + assert!(h::exists(&dir, "existing.txt")); } diff --git a/crates/wasi/src/filesystem/primitives/tests/rename_directory.rs b/crates/wasi/src/filesystem/primitives/tests/rename_directory.rs index 46bc2f95459e..a9e208994a1a 100644 --- a/crates/wasi/src/filesystem/primitives/tests/rename_directory.rs +++ b/crates/wasi/src/filesystem/primitives/tests/rename_directory.rs @@ -5,32 +5,29 @@ // run-pass #![allow(unused_must_use)] -#![allow(unused_imports)] // This test can't be a unit test in std, // because it needs `TempDir`, which is in extra // ignore-cross-compile -mod sys_common; - -use cap_std::fs::{self, File}; -use std::env; -use std::ffi::CString; -use std::path::{Path, PathBuf}; -use sys_common::io::tmpdir; +use super::helpers as h; +use super::sys_common::io::tmpdir; +use crate::filesystem::primitives::rename; +use std::path::Path; #[test] fn rename_directory() { let tmpdir = tmpdir(); + let dir = h::dir_of(&tmpdir); let old_path = Path::new("foo/bar/baz"); - tmpdir.create_dir_all(&old_path).unwrap(); + h::create_dir_all(&dir, &old_path).unwrap(); let test_file = &old_path.join("temp.txt"); - tmpdir.create(test_file).unwrap(); + h::create(&dir, test_file).unwrap(); let new_path = Path::new("quux/blat"); - tmpdir.create_dir_all(&new_path).unwrap(); - tmpdir.rename(&old_path, &tmpdir, &new_path.join("newdir")); - assert!(tmpdir.is_dir(new_path.join("newdir"))); - assert!(tmpdir.exists(new_path.join("newdir/temp.txt"))); + h::create_dir_all(&dir, &new_path).unwrap(); + rename(&dir, &old_path, &dir, &new_path.join("newdir")); + assert!(h::is_dir(&dir, &new_path.join("newdir"))); + assert!(h::exists(&dir, &new_path.join("newdir/temp.txt"))); } diff --git a/crates/wasi/src/filesystem/primitives/tests/reopen.rs b/crates/wasi/src/filesystem/primitives/tests/reopen.rs deleted file mode 100644 index 251bf1d22542..000000000000 --- a/crates/wasi/src/filesystem/primitives/tests/reopen.rs +++ /dev/null @@ -1,93 +0,0 @@ -#![cfg(not(target_os = "freebsd"))] // FreeBSD can't reopen arbitrary files. - -#[macro_use] -mod sys_common; - -use cap_fs_ext::{OpenOptions, Reopen}; -use std::io::{Read, Write}; -use sys_common::io::tmpdir; -#[cfg(windows)] -use windows_sys::Win32::Storage::FileSystem::FILE_GENERIC_READ; - -#[test] -fn basic_reopen() { - let tmpdir = tmpdir(); - - // Open the file with both read and write privileges, so that we can - // write to it, and then reopen it for reading. - let mut file = check!(tmpdir.open_with( - "file", - OpenOptions::new() - .create(true) - .truncate(true) - .write(true) - .read(true) - )); - check!(file.write_all(b"hello, world")); - - let mut buf = String::new(); - check!(file.read_to_string(&mut buf)); - assert!(buf.is_empty()); - - let mut ro = check!(file.reopen(OpenOptions::new().read(true))); - let mut another = check!(file.reopen(OpenOptions::new().read(true))); - check!(ro.read_to_string(&mut buf)); - assert_eq!(buf, "hello, world"); - buf.clear(); - check!(another.read_to_string(&mut buf)); - assert_eq!(buf, "hello, world"); - buf.clear(); - check!(another.read_to_string(&mut buf)); - assert!(buf.is_empty()); -} - -/// Don't allow `reopen` to grant new permissions. -#[test] -fn reopen_perms() { - let tmpdir = tmpdir(); - - let mut file = check!(tmpdir.create("file")); - check!(file.write_all(b"hello, world")); - - let mut buf = String::new(); - assert!(file.read_to_string(&mut buf).is_err()); - - // `file` is write-only, so can't re-open for reading. - assert!(file.reopen(OpenOptions::new().read(true)).is_err()); - - // `ro` is read-only, so can't re-open for writing. - let mut ro = check!(tmpdir.open("file")); - assert!(ro.write_all(b"hello, world").is_err()); - assert!(ro.reopen(OpenOptions::new().write(true)).is_err()); - check!(ro.read_to_string(&mut buf)); - assert_eq!(buf, "hello, world"); -} - -/// Test reopen using `access_mode` explicitly. -#[cfg(windows)] -#[test] -fn reopen_explicit_access() { - use cap_std::fs::OpenOptionsExt; - - let tmpdir = tmpdir(); - - // Open the file with both read and write privileges, so that we can - // write to it, and then reopen it for reading. - let mut file = check!(tmpdir.open_with( - "file", - OpenOptions::new() - .create(true) - .truncate(true) - .write(true) - .read(true) - )); - check!(file.write_all(b"hello, world")); - - let mut buf = String::new(); - check!(file.read_to_string(&mut buf)); - assert!(buf.is_empty()); - - let mut ro = check!(file.reopen(OpenOptions::new().access_mode(FILE_GENERIC_READ))); - check!(ro.read_to_string(&mut buf)); - assert_eq!(buf, "hello, world"); -} diff --git a/crates/wasi/src/filesystem/primitives/tests/reopendir.rs b/crates/wasi/src/filesystem/primitives/tests/reopendir.rs index 3347ddcf0cd2..3ce310a03fc5 100644 --- a/crates/wasi/src/filesystem/primitives/tests/reopendir.rs +++ b/crates/wasi/src/filesystem/primitives/tests/reopendir.rs @@ -1,66 +1,72 @@ //! Tests for various forms of reopening a directory handle. -#[macro_use] -mod sys_common; - -use sys_common::io::tmpdir; +use super::helpers as h; +use super::sys_common::io::tmpdir; +use crate::filesystem::primitives::open_dir; +use std::path::Path; #[test] fn reopendir_a() { let tmpdir = tmpdir(); - check!(tmpdir.create_dir_all("dir/inner")); + let dir = h::dir_of(&tmpdir); + check!(h::create_dir_all(&dir, "dir/inner")); - let inner = check!(tmpdir.open_dir("dir/inner")); + let inner = check!(open_dir(&dir, Path::new("dir/inner"))); - check!(inner.open_dir(".")); + check!(open_dir(&inner, Path::new("."))); } #[test] fn reopendir_b() { let tmpdir = tmpdir(); - check!(tmpdir.create_dir_all("dir/inner")); + let dir = h::dir_of(&tmpdir); + check!(h::create_dir_all(&dir, "dir/inner")); - let inner = check!(tmpdir.open_dir("dir/inner")); + let inner = check!(open_dir(&dir, Path::new("dir/inner"))); - check!(inner.open_dir("./")); + check!(open_dir(&inner, Path::new("./"))); } #[test] fn reopendir_c() { let tmpdir = tmpdir(); - check!(tmpdir.create_dir_all("dir/inner")); + let dir = h::dir_of(&tmpdir); + check!(h::create_dir_all(&dir, "dir/inner")); - let inner = check!(tmpdir.open_dir("dir/inner")); + let inner = check!(open_dir(&dir, Path::new("dir/inner"))); - check!(inner.open_dir("./.")); + check!(open_dir(&inner, Path::new("./."))); } #[test] fn reopendir_d() { let tmpdir = tmpdir(); - check!(tmpdir.create_dir_all("dir/inner")); + let dir = h::dir_of(&tmpdir); + check!(h::create_dir_all(&dir, "dir/inner")); - let _inner = check!(tmpdir.open_dir("dir/inner")); + let _inner = check!(open_dir(&dir, Path::new("dir/inner"))); - check!(tmpdir.open_dir("dir/inner")); + check!(open_dir(&dir, Path::new("dir/inner"))); } #[test] fn reopendir_e() { let tmpdir = tmpdir(); - check!(tmpdir.create_dir_all("dir/inner")); + let dir = h::dir_of(&tmpdir); + check!(h::create_dir_all(&dir, "dir/inner")); - let _inner = check!(tmpdir.open_dir("dir/inner")); + let _inner = check!(open_dir(&dir, Path::new("dir/inner"))); - check!(tmpdir.open_dir("dir/inner/.")); + check!(open_dir(&dir, Path::new("dir/inner/."))); } #[test] fn reopendir_f() { let tmpdir = tmpdir(); - check!(tmpdir.create_dir_all("dir/inner")); + let dir = h::dir_of(&tmpdir); + check!(h::create_dir_all(&dir, "dir/inner")); - let _inner = check!(tmpdir.open_dir("dir/inner")); + let _inner = check!(open_dir(&dir, Path::new("dir/inner"))); - check!(tmpdir.open_dir("dir/inner/")); + check!(open_dir(&dir, Path::new("dir/inner/"))); } diff --git a/crates/wasi/src/filesystem/primitives/tests/root.rs b/crates/wasi/src/filesystem/primitives/tests/root.rs deleted file mode 100644 index 2a158d422951..000000000000 --- a/crates/wasi/src/filesystem/primitives/tests/root.rs +++ /dev/null @@ -1,32 +0,0 @@ -#[macro_use] -mod sys_common; - -use cap_std::ambient_authority; -use cap_std::fs::Dir; -use std::fs; -use std::path::Component; - -#[test] -fn open_root() { - let root = Dir::open_ambient_dir(Component::RootDir.as_os_str(), ambient_authority()) - .expect("expect to be able to open the root directory"); - error_contains!( - root.read_dir(Component::ParentDir.as_os_str()), - "a path led outside of the filesystem" - ); -} - -/// Attempt to remove the root directory, which should fail, and check that the -/// error message is as expected. -#[test] -fn remove_root() { - let _observed = { - let root = Dir::open_ambient_dir(Component::RootDir.as_os_str(), ambient_authority()) - .expect("expect to be able to open the root directory"); - root.remove_open_dir().unwrap_err() - }; - let _expected = fs::remove_dir(Component::RootDir.as_os_str()).unwrap_err(); - // TODO: Investigate why these asserts spuriously fail on Windows and QEMU. - //assert_eq!(expected.to_string(), observed.to_string()); - //assert_eq!(expected.kind(), observed.kind()); -} diff --git a/crates/wasi/src/filesystem/primitives/tests/set.rs b/crates/wasi/src/filesystem/primitives/tests/set.rs deleted file mode 100644 index 6f42864ac4fc..000000000000 --- a/crates/wasi/src/filesystem/primitives/tests/set.rs +++ /dev/null @@ -1,132 +0,0 @@ -// Test `set_permissions` and `set_times`. - -#[macro_use] -mod sys_common; - -use std::io; -use std::path::Path; -use sys_common::io::{tmpdir, TempDir}; -use sys_common::symlink_supported; - -#[cfg(not(windows))] -fn symlink_dir, Q: AsRef>(src: P, tmpdir: &TempDir, dst: Q) -> io::Result<()> { - tmpdir.symlink(src, dst) -} -#[cfg(not(windows))] -fn symlink_file, Q: AsRef>( - src: P, - tmpdir: &TempDir, - dst: Q, -) -> io::Result<()> { - tmpdir.symlink(src, dst) -} -#[cfg(windows)] -fn symlink_dir, Q: AsRef>(src: P, tmpdir: &TempDir, dst: Q) -> io::Result<()> { - tmpdir.symlink_dir(src, dst) -} -#[cfg(windows)] -fn symlink_file, Q: AsRef>( - src: P, - tmpdir: &TempDir, - dst: Q, -) -> io::Result<()> { - tmpdir.symlink_file(src, dst) -} - -#[test] -fn basic_perms() { - if !symlink_supported() { - return; - } - - let tmpdir = tmpdir(); - check!(tmpdir.create("file")); - check!(tmpdir.create_dir("dir")); - check!(symlink_file("file", &tmpdir, "symlink_file")); - check!(symlink_dir("dir", &tmpdir, "symlink_dir")); - - let mut file_perms = check!(tmpdir.metadata("file")).permissions(); - assert!(!file_perms.readonly()); - file_perms.set_readonly(true); - check!(tmpdir.set_permissions("file", file_perms.clone())); - assert_eq!( - check!(tmpdir.metadata("file")).permissions(), - file_perms.clone() - ); - assert_eq!( - check!(tmpdir.metadata("symlink_file")).permissions(), - file_perms - ); - - let mut dir_perms = check!(tmpdir.metadata("dir")).permissions(); - assert!(!dir_perms.readonly()); - dir_perms.set_readonly(true); - check!(tmpdir.set_permissions("dir", dir_perms.clone())); - assert_eq!( - check!(tmpdir.metadata("dir")).permissions(), - dir_perms.clone() - ); - assert_eq!( - check!(tmpdir.metadata("symlink_dir")).permissions(), - dir_perms - ); -} - -#[test] -fn symlink_perms() { - if !symlink_supported() { - return; - } - - let tmpdir = tmpdir(); - check!(tmpdir.create("file")); - check!(tmpdir.create_dir("dir")); - check!(symlink_file("file", &tmpdir, "symlink_file")); - check!(symlink_dir("dir", &tmpdir, "symlink_dir")); - - let mut file_perms = check!(tmpdir.metadata("symlink_file")).permissions(); - assert!(!file_perms.readonly()); - file_perms.set_readonly(true); - check!(tmpdir.set_permissions("symlink_file", file_perms.clone())); - - // On Windows, `set_permissions` does not follow symlinks. On non-Windows, - // it does. See https://github.com/rust-lang/rust/issues/75942 for discussion. - if cfg!(windows) { - assert_eq!( - check!(tmpdir.symlink_metadata("symlink_file")).permissions(), - file_perms - ); - } else { - assert_eq!( - check!(tmpdir.metadata("file")).permissions(), - file_perms.clone() - ); - assert_eq!( - check!(tmpdir.metadata("symlink_file")).permissions(), - file_perms - ); - } - - let mut dir_perms = check!(tmpdir.metadata("symlink_dir")).permissions(); - assert!(!dir_perms.readonly()); - dir_perms.set_readonly(true); - check!(tmpdir.set_permissions("symlink_dir", dir_perms.clone())); - - // On Windows, `set_permissions` does not follow symlinks. On non-Windows, - // it does. See https://github.com/rust-lang/rust/issues/75942 for discussion. - if cfg!(windows) { - assert_eq!( - check!(tmpdir.symlink_metadata("symlink_dir")).permissions(), - dir_perms - ); - } else { - assert_eq!( - check!(tmpdir.metadata("dir")).permissions(), - dir_perms.clone() - ); - assert_eq!( - check!(tmpdir.metadata("symlink_dir")).permissions(), - dir_perms - ); - } -} diff --git a/crates/wasi/src/filesystem/primitives/tests/set_times.rs b/crates/wasi/src/filesystem/primitives/tests/set_times.rs index d9f90ab3baaf..0d4955c18b96 100644 --- a/crates/wasi/src/filesystem/primitives/tests/set_times.rs +++ b/crates/wasi/src/filesystem/primitives/tests/set_times.rs @@ -1,12 +1,11 @@ -#[macro_use] -mod sys_common; +use super::helpers as h; +use super::sys_common::io::tmpdir; +use super::sys_common::symlink_supported; +use crate::filesystem::primitives::{Metadata, SystemTimeSpec, set_times, set_times_nofollow}; +use std::path::Path; +use std::time::SystemTime; -use cap_fs_ext::DirExt; -use cap_std::time::{SystemClock, SystemTime}; -use sys_common::io::tmpdir; -use sys_common::symlink_supported; - -fn modified_time(meta: cap_std::fs::Metadata) -> SystemTime { +fn modified_time(meta: Metadata) -> SystemTime { meta.modified().unwrap() } @@ -15,29 +14,40 @@ fn basic_times() { let test_symlinks = symlink_supported(); let tmpdir = tmpdir(); - check!(tmpdir.create("file")); - check!(tmpdir.create_dir("dir")); + let dir = h::dir_of(&tmpdir); + check!(h::create(&dir, "file")); + check!(h::create_dir(&dir, "dir")); if test_symlinks { - check!(tmpdir.symlink_file("file", "file_symlink_file")); - check!(tmpdir.symlink_dir("dir", "dir_symlink_dir")); + check!(h::symlink_file(&dir, "file", "file_symlink_file")); + check!(h::symlink_dir(&dir, "dir", "dir_symlink_dir")); } - let file_time = SystemClock::UNIX_EPOCH; - check!(tmpdir.set_times("file", None, Some(file_time.into()))); - assert_eq!(modified_time(check!(tmpdir.metadata("file"))), file_time); + let file_time = SystemTime::UNIX_EPOCH; + check!(set_times( + &dir, + Path::new("file"), + None, + Some(SystemTimeSpec::from(file_time)) + )); + assert_eq!(modified_time(check!(h::metadata(&dir, "file"))), file_time); if test_symlinks { assert_eq!( - modified_time(check!(tmpdir.metadata("file_symlink_file"))), + modified_time(check!(h::metadata(&dir, "file_symlink_file"))), file_time ); } - let dir_time = SystemClock::UNIX_EPOCH; - check!(tmpdir.set_times("dir", None, Some(dir_time.into()))); - assert_eq!(modified_time(check!(tmpdir.metadata("dir"))), dir_time); + let dir_time = SystemTime::UNIX_EPOCH; + check!(set_times( + &dir, + Path::new("dir"), + None, + Some(SystemTimeSpec::from(dir_time)) + )); + assert_eq!(modified_time(check!(h::metadata(&dir, "dir"))), dir_time); if test_symlinks { assert_eq!( - modified_time(check!(tmpdir.metadata("dir_symlink_dir"))), + modified_time(check!(h::metadata(&dir, "dir_symlink_dir"))), dir_time ); } @@ -50,40 +60,51 @@ fn symlink_times() { } let tmpdir = tmpdir(); - check!(tmpdir.create("file")); - check!(tmpdir.create_dir("dir")); - check!(tmpdir.symlink_file("file", "file_symlink_file")); - check!(tmpdir.symlink_dir("dir", "dir_symlink_dir")); + let dir = h::dir_of(&tmpdir); + check!(h::create(&dir, "file")); + check!(h::create_dir(&dir, "dir")); + check!(h::symlink_file(&dir, "file", "file_symlink_file")); + check!(h::symlink_dir(&dir, "dir", "dir_symlink_dir")); - let file_time = SystemClock::UNIX_EPOCH; - check!(tmpdir.set_times("file_symlink_file", None, Some(file_time.into()))); - assert_eq!(modified_time(check!(tmpdir.metadata("file"))), file_time); + let file_time = SystemTime::UNIX_EPOCH; + check!(set_times( + &dir, + Path::new("file_symlink_file"), + None, + Some(SystemTimeSpec::from(file_time)) + )); + assert_eq!(modified_time(check!(h::metadata(&dir, "file"))), file_time); assert_eq!( - modified_time(check!(tmpdir.metadata("file_symlink_file"))), + modified_time(check!(h::metadata(&dir, "file_symlink_file"))), file_time ); assert_eq!( - modified_time(check!(tmpdir.symlink_metadata("file"))), + modified_time(check!(h::symlink_metadata(&dir, "file"))), file_time ); assert_ne!( - modified_time(check!(tmpdir.symlink_metadata("file_symlink_file"))), + modified_time(check!(h::symlink_metadata(&dir, "file_symlink_file"))), file_time ); - let dir_time = SystemClock::UNIX_EPOCH; - check!(tmpdir.set_times("dir_symlink_dir", None, Some(file_time.into()))); - assert_eq!(modified_time(check!(tmpdir.metadata("dir"))), dir_time); + let dir_time = SystemTime::UNIX_EPOCH; + check!(set_times( + &dir, + Path::new("dir_symlink_dir"), + None, + Some(SystemTimeSpec::from(file_time)) + )); + assert_eq!(modified_time(check!(h::metadata(&dir, "dir"))), dir_time); assert_eq!( - modified_time(check!(tmpdir.metadata("dir_symlink_dir"))), + modified_time(check!(h::metadata(&dir, "dir_symlink_dir"))), dir_time ); assert_eq!( - modified_time(check!(tmpdir.symlink_metadata("dir"))), + modified_time(check!(h::symlink_metadata(&dir, "dir"))), dir_time ); assert_ne!( - modified_time(check!(tmpdir.symlink_metadata("dir_symlink_dir"))), + modified_time(check!(h::symlink_metadata(&dir, "dir_symlink_dir"))), dir_time ); } @@ -95,40 +116,51 @@ fn symlink_itself_times() { } let tmpdir = tmpdir(); - check!(tmpdir.create("file")); - check!(tmpdir.create_dir("dir")); - check!(tmpdir.symlink_file("file", "file_symlink_file")); - check!(tmpdir.symlink_dir("dir", "dir_symlink_dir")); + let dir = h::dir_of(&tmpdir); + check!(h::create(&dir, "file")); + check!(h::create_dir(&dir, "dir")); + check!(h::symlink_file(&dir, "file", "file_symlink_file")); + check!(h::symlink_dir(&dir, "dir", "dir_symlink_dir")); - let file_time = SystemClock::UNIX_EPOCH; - check!(tmpdir.set_symlink_times("file_symlink_file", None, Some(file_time.into()))); - assert_ne!(modified_time(check!(tmpdir.metadata("file"))), file_time); + let file_time = SystemTime::UNIX_EPOCH; + check!(set_times_nofollow( + &dir, + Path::new("file_symlink_file"), + None, + Some(SystemTimeSpec::from(file_time)) + )); + assert_ne!(modified_time(check!(h::metadata(&dir, "file"))), file_time); assert_ne!( - modified_time(check!(tmpdir.metadata("file_symlink_file"))), + modified_time(check!(h::metadata(&dir, "file_symlink_file"))), file_time ); assert_ne!( - modified_time(check!(tmpdir.symlink_metadata("file"))), + modified_time(check!(h::symlink_metadata(&dir, "file"))), file_time ); assert_eq!( - modified_time(check!(tmpdir.symlink_metadata("file_symlink_file"))), + modified_time(check!(h::symlink_metadata(&dir, "file_symlink_file"))), file_time ); - let dir_time = SystemClock::UNIX_EPOCH; - check!(tmpdir.set_symlink_times("dir_symlink_dir", None, Some(file_time.into()))); - assert_ne!(modified_time(check!(tmpdir.metadata("dir"))), dir_time); + let dir_time = SystemTime::UNIX_EPOCH; + check!(set_times_nofollow( + &dir, + Path::new("dir_symlink_dir"), + None, + Some(SystemTimeSpec::from(file_time)) + )); + assert_ne!(modified_time(check!(h::metadata(&dir, "dir"))), dir_time); assert_ne!( - modified_time(check!(tmpdir.metadata("dir_symlink_dir"))), + modified_time(check!(h::metadata(&dir, "dir_symlink_dir"))), dir_time ); assert_ne!( - modified_time(check!(tmpdir.symlink_metadata("dir"))), + modified_time(check!(h::symlink_metadata(&dir, "dir"))), dir_time ); assert_eq!( - modified_time(check!(tmpdir.symlink_metadata("dir_symlink_dir"))), + modified_time(check!(h::symlink_metadata(&dir, "dir_symlink_dir"))), dir_time ); } diff --git a/crates/wasi/src/filesystem/primitives/tests/symlinks.rs b/crates/wasi/src/filesystem/primitives/tests/symlinks.rs index 5323a88147b5..55c9eff4acee 100644 --- a/crates/wasi/src/filesystem/primitives/tests/symlinks.rs +++ b/crates/wasi/src/filesystem/primitives/tests/symlinks.rs @@ -1,11 +1,9 @@ -#[macro_use] -mod sys_common; +use super::helpers as h; +use super::sys_common::io::tmpdir; +use super::sys_common::symlink_supported; +use crate::filesystem::primitives as p; -use cap_fs_ext::DirExt; -use cap_std::ambient_authority; -use cap_std::fs::Dir; -use sys_common::io::tmpdir; -use sys_common::symlink_supported; +use std::path::Path; #[test] fn basic_symlinks() { @@ -15,76 +13,135 @@ fn basic_symlinks() { let tmpdir = tmpdir(); - check!(tmpdir.create("file")); - check!(tmpdir.create_dir("dir")); - assert!(check!(tmpdir.metadata("file")).is_file()); - assert!(!check!(tmpdir.metadata("file")).is_dir()); - assert!(check!(tmpdir.metadata("dir")).is_dir()); - assert!(!check!(tmpdir.metadata("dir")).is_file()); - assert!(!check!(tmpdir.metadata("file")).is_symlink()); - assert!(!check!(tmpdir.metadata("dir")).is_symlink()); - - check!(tmpdir.symlink_file("file", "file_symlink_file")); - check!(tmpdir.symlink_dir("dir", "dir_symlink_dir")); - check!(tmpdir.symlink("file", "file_symlink")); - check!(tmpdir.symlink("dir", "dir_symlink")); - - assert!(check!(tmpdir.metadata("file_symlink_file")).is_file()); - assert!(check!(tmpdir.metadata("dir_symlink_dir")).is_dir()); - assert!(check!(tmpdir.metadata("file_symlink")).is_file()); - assert!(check!(tmpdir.metadata("dir_symlink")).is_dir()); - - assert!(!check!(tmpdir.metadata("file_symlink_file")).is_symlink()); - assert!(!check!(tmpdir.metadata("dir_symlink_dir")).is_symlink()); - assert!(!check!(tmpdir.metadata("file_symlink")).is_symlink()); - assert!(!check!(tmpdir.metadata("dir_symlink")).is_symlink()); - - assert!(check!(tmpdir.symlink_metadata("file_symlink_file")).is_symlink()); - assert!(check!(tmpdir.symlink_metadata("dir_symlink_dir")).is_symlink()); - assert!(check!(tmpdir.symlink_metadata("file_symlink")).is_symlink()); - assert!(check!(tmpdir.symlink_metadata("dir_symlink")).is_symlink()); - - assert!(!check!(tmpdir.metadata("file_symlink_file")) - .file_type() - .is_symlink()); - assert!(!check!(tmpdir.metadata("dir_symlink_dir")) - .file_type() - .is_symlink()); - assert!(!check!(tmpdir.metadata("file_symlink")) - .file_type() - .is_symlink()); - assert!(!check!(tmpdir.metadata("dir_symlink")) - .file_type() - .is_symlink()); - - assert!(check!(tmpdir.symlink_metadata("file_symlink_file")) - .file_type() - .is_symlink()); - assert!(check!(tmpdir.symlink_metadata("dir_symlink_dir")) - .file_type() - .is_symlink()); - assert!(check!(tmpdir.symlink_metadata("file_symlink")) - .file_type() - .is_symlink()); - assert!(check!(tmpdir.symlink_metadata("dir_symlink")) - .file_type() - .is_symlink()); + let dir = h::dir_of(&tmpdir); + + check!(h::create(&dir, "file")); + check!(h::create_dir(&dir, "dir")); + assert!(check!(h::metadata(&dir, "file")).file_type().is_file()); + assert!(!check!(h::metadata(&dir, "file")).is_dir()); + assert!(check!(h::metadata(&dir, "dir")).is_dir()); + assert!(!check!(h::metadata(&dir, "dir")).file_type().is_file()); + assert!(!check!(h::metadata(&dir, "file")).file_type().is_symlink()); + assert!(!check!(h::metadata(&dir, "dir")).file_type().is_symlink()); + + check!(h::symlink_file(&dir, "file", "file_symlink_file")); + check!(h::symlink_dir(&dir, "dir", "dir_symlink_dir")); + check!(h::symlink(&dir, "file", "file_symlink")); + check!(h::symlink(&dir, "dir", "dir_symlink")); + + assert!( + check!(h::metadata(&dir, "file_symlink_file")) + .file_type() + .is_file() + ); + assert!(check!(h::metadata(&dir, "dir_symlink_dir")).is_dir()); + assert!( + check!(h::metadata(&dir, "file_symlink")) + .file_type() + .is_file() + ); + assert!(check!(h::metadata(&dir, "dir_symlink")).is_dir()); + + assert!( + !check!(h::metadata(&dir, "file_symlink_file")) + .file_type() + .is_symlink() + ); + assert!( + !check!(h::metadata(&dir, "dir_symlink_dir")) + .file_type() + .is_symlink() + ); + assert!( + !check!(h::metadata(&dir, "file_symlink")) + .file_type() + .is_symlink() + ); + assert!( + !check!(h::metadata(&dir, "dir_symlink")) + .file_type() + .is_symlink() + ); + + assert!( + check!(h::symlink_metadata(&dir, "file_symlink_file")) + .file_type() + .is_symlink() + ); + assert!( + check!(h::symlink_metadata(&dir, "dir_symlink_dir")) + .file_type() + .is_symlink() + ); + assert!( + check!(h::symlink_metadata(&dir, "file_symlink")) + .file_type() + .is_symlink() + ); + assert!( + check!(h::symlink_metadata(&dir, "dir_symlink")) + .file_type() + .is_symlink() + ); + + assert!( + !check!(h::metadata(&dir, "file_symlink_file")) + .file_type() + .is_symlink() + ); + assert!( + !check!(h::metadata(&dir, "dir_symlink_dir")) + .file_type() + .is_symlink() + ); + assert!( + !check!(h::metadata(&dir, "file_symlink")) + .file_type() + .is_symlink() + ); + assert!( + !check!(h::metadata(&dir, "dir_symlink")) + .file_type() + .is_symlink() + ); + + assert!( + check!(h::symlink_metadata(&dir, "file_symlink_file")) + .file_type() + .is_symlink() + ); + assert!( + check!(h::symlink_metadata(&dir, "dir_symlink_dir")) + .file_type() + .is_symlink() + ); + assert!( + check!(h::symlink_metadata(&dir, "file_symlink")) + .file_type() + .is_symlink() + ); + assert!( + check!(h::symlink_metadata(&dir, "dir_symlink")) + .file_type() + .is_symlink() + ); } #[test] fn symlink_absolute() { let tmpdir = tmpdir(); + let dir = h::dir_of(&tmpdir); error_contains!( - tmpdir.symlink("/thing", "thing_symlink_file"), + h::symlink(&dir, "/thing", "thing_symlink_file"), "a path led outside of the filesystem" ); error_contains!( - tmpdir.symlink_file("/file", "file_symlink_file"), + h::symlink_file(&dir, "/file", "file_symlink_file"), "a path led outside of the filesystem" ); error_contains!( - tmpdir.symlink_dir("/dir", "dir_symlink_dir"), + h::symlink_dir(&dir, "/dir", "dir_symlink_dir"), "a path led outside of the filesystem" ); } @@ -113,21 +170,21 @@ fn readlink_absolute() { dir.path().join("dir_symlink_dir") )); - let tmpdir = check!(Dir::open_ambient_dir(dir.path(), ambient_authority())); + let dir = check!(h::open_ambient_dir(dir.path())); #[cfg(not(windows))] error_contains!( - tmpdir.read_link("thing_symlink"), + p::read_link(&dir, Path::new("thing_symlink")), "a path led outside of the filesystem" ); #[cfg(windows)] error_contains!( - tmpdir.read_link("file_symlink_file"), + p::read_link(&dir, Path::new("file_symlink_file")), "a path led outside of the filesystem" ); #[cfg(windows)] error_contains!( - tmpdir.read_link("dir_symlink_dir"), + p::read_link(&dir, Path::new("dir_symlink_dir")), "a path led outside of the filesystem" ); } @@ -135,70 +192,75 @@ fn readlink_absolute() { /// Opening directories without following symlinks. #[test] fn open_dir_nofollow() { - use cap_fs_ext::DirExt; - if !symlink_supported() { return; } let tmpdir = tmpdir(); - check!(tmpdir.create("file")); - check!(tmpdir.create_dir("dir")); - check!(tmpdir.symlink_file("file", "symlink_file")); - check!(tmpdir.symlink_dir("dir", "symlink_dir")); - check!(tmpdir.symlink_dir("dir/", "symlink_dir_slash")); - check!(tmpdir.symlink_dir("dir/.", "symlink_dir_slashdot")); - check!(tmpdir.symlink_dir("dir/..", "symlink_dir_slashdotdot")); - check!(tmpdir.symlink_dir("dir/../", "symlink_dir_slashdotdotslash")); - check!(tmpdir.symlink_dir(".", "symlink_dot")); - check!(tmpdir.symlink_dir("./", "symlink_dotslash")); + let dir = h::dir_of(&tmpdir); + + check!(h::create(&dir, "file")); + check!(h::create_dir(&dir, "dir")); + check!(h::symlink_file(&dir, "file", "symlink_file")); + check!(h::symlink_dir(&dir, "dir", "symlink_dir")); + check!(h::symlink_dir(&dir, "dir/", "symlink_dir_slash")); + check!(h::symlink_dir(&dir, "dir/.", "symlink_dir_slashdot")); + check!(h::symlink_dir(&dir, "dir/..", "symlink_dir_slashdotdot")); + check!(h::symlink_dir( + &dir, + "dir/../", + "symlink_dir_slashdotdotslash" + )); + check!(h::symlink_dir(&dir, ".", "symlink_dot")); + check!(h::symlink_dir(&dir, "./", "symlink_dotslash")); // First try without `nofollow`. The "symlink_dir" case should succeed. - assert!(tmpdir.open_dir("file").is_err()); - assert!(tmpdir.open_dir("symlink_file").is_err()); - check!(tmpdir.open_dir("symlink_dir")); + assert!(p::open_dir(&dir, Path::new("file")).is_err()); + assert!(p::open_dir(&dir, Path::new("symlink_file")).is_err()); + check!(p::open_dir(&dir, Path::new("symlink_dir"))); #[cfg(windows)] - check!(tmpdir.open_dir("symlink_dir\\")); - check!(tmpdir.open_dir("symlink_dir/")); + check!(p::open_dir(&dir, Path::new("symlink_dir\\"))); + check!(p::open_dir(&dir, Path::new("symlink_dir/"))); #[cfg(windows)] { - error!(tmpdir.open_dir("symlink_dir_slash"), 123); - error!(tmpdir.open_dir("symlink_dir_slashdotdot"), 123); - error!(tmpdir.open_dir("symlink_dir_slashdotdotslash"), 123); - error!(tmpdir.open_dir("symlink_dotslash"), 123); - error!(tmpdir.open_dir("symlink_dir_slashdot"), 123); + error!(p::open_dir(&dir, Path::new("symlink_dir_slash")), 123); + error!(p::open_dir(&dir, Path::new("symlink_dir_slashdotdot")), 123); + error!( + p::open_dir(&dir, Path::new("symlink_dir_slashdotdotslash")), + 123 + ); + error!(p::open_dir(&dir, Path::new("symlink_dotslash")), 123); + error!(p::open_dir(&dir, Path::new("symlink_dir_slashdot")), 123); } #[cfg(not(windows))] { - check!(tmpdir.open_dir("symlink_dir_slash")); - check!(tmpdir.open_dir("symlink_dir_slashdotdot")); - check!(tmpdir.open_dir("symlink_dir_slashdotdotslash")); - check!(tmpdir.open_dir("symlink_dotslash")); - check!(tmpdir.open_dir("symlink_dir_slashdot")); + check!(p::open_dir(&dir, Path::new("symlink_dir_slash"))); + check!(p::open_dir(&dir, Path::new("symlink_dir_slashdotdot"))); + check!(p::open_dir(&dir, Path::new("symlink_dir_slashdotdotslash"))); + check!(p::open_dir(&dir, Path::new("symlink_dotslash"))); + check!(p::open_dir(&dir, Path::new("symlink_dir_slashdot"))); } - check!(tmpdir.open_dir("symlink_dot")); - check!(tmpdir.open_dir("dir")); + check!(p::open_dir(&dir, Path::new("symlink_dot"))); + check!(p::open_dir(&dir, Path::new("dir"))); // Next try with `nofollow`. The "symlink_dir" case should fail. - assert!(tmpdir.open_dir_nofollow("file").is_err()); - assert!(tmpdir.open_dir_nofollow("symlink_file").is_err()); - assert!(tmpdir.open_dir_nofollow("symlink_dir").is_err()); - assert!(tmpdir.open_dir_nofollow("symlink_dir_slash").is_err()); - assert!(tmpdir.open_dir_nofollow("symlink_dir_slashdot").is_err()); - assert!(tmpdir.open_dir_nofollow("symlink_dir_slashdotdot").is_err()); - assert!(tmpdir - .open_dir_nofollow("symlink_dir_slashdotdotslash") - .is_err()); - assert!(tmpdir.open_dir_nofollow("symlink_dot").is_err()); - assert!(tmpdir.open_dir_nofollow("symlink_dotslash").is_err()); - check!(tmpdir.open_dir_nofollow("dir")); + assert!(h::open_dir_nofollow(&dir, "file").is_err()); + assert!(h::open_dir_nofollow(&dir, "symlink_file").is_err()); + assert!(h::open_dir_nofollow(&dir, "symlink_dir").is_err()); + assert!(h::open_dir_nofollow(&dir, "symlink_dir_slash").is_err()); + assert!(h::open_dir_nofollow(&dir, "symlink_dir_slashdot").is_err()); + assert!(h::open_dir_nofollow(&dir, "symlink_dir_slashdotdot").is_err()); + assert!(h::open_dir_nofollow(&dir, "symlink_dir_slashdotdotslash").is_err()); + assert!(h::open_dir_nofollow(&dir, "symlink_dot").is_err()); + assert!(h::open_dir_nofollow(&dir, "symlink_dotslash").is_err()); + check!(h::open_dir_nofollow(&dir, "dir")); // Check various ways of spelling `dir/../symlink_dir`. for dir_name in &["dir", "symlink_dir"] { let name = format!("{}/../symlink_dir", dir_name); - check!(tmpdir.open_dir(&name)); - assert!(tmpdir.open_dir_nofollow(&name).is_err()); + check!(p::open_dir(&dir, Path::new(&name))); + assert!(h::open_dir_nofollow(&dir, &name).is_err()); } // Check various paths which end with a symlink (even though the symlink @@ -206,12 +268,12 @@ fn open_dir_nofollow() { for suffix in &[""] { for symlink_dir in &["symlink_dot"] { let name = format!("{}{}", symlink_dir, suffix); - check!(tmpdir.open_dir(&name)); - assert!(tmpdir.open_dir_nofollow(&name).is_err()); + check!(p::open_dir(&dir, Path::new(&name))); + assert!(h::open_dir_nofollow(&dir, &name).is_err()); for dir_name in &["dir", "symlink_dir"] { let name = format!("{}/../{}", dir_name, name); - check!(tmpdir.open_dir(&name)); - assert!(tmpdir.open_dir_nofollow(&name).is_err()); + check!(p::open_dir(&dir, Path::new(&name))); + assert!(h::open_dir_nofollow(&dir, &name).is_err()); } } } @@ -229,24 +291,24 @@ fn open_dir_nofollow() { let name = format!("{}{}", symlink_dir, suffix); #[cfg(windows)] { - error!(tmpdir.open_dir(&name), 123); + error!(p::open_dir(&dir, Path::new(&name)), 123); } #[cfg(not(windows))] { - check!(tmpdir.open_dir(&name)); + check!(p::open_dir(&dir, Path::new(&name))); } - assert!(tmpdir.open_dir_nofollow(&name).is_err()); + assert!(h::open_dir_nofollow(&dir, &name).is_err()); for dir_name in &["dir", "symlink_dir"] { let name = format!("{}/../{}", dir_name, name); #[cfg(windows)] { - error!(tmpdir.open_dir(&name), 123); + error!(p::open_dir(&dir, Path::new(&name)), 123); } #[cfg(not(windows))] { - check!(tmpdir.open_dir(&name)); + check!(p::open_dir(&dir, Path::new(&name))); } - assert!(tmpdir.open_dir_nofollow(&name).is_err()); + assert!(h::open_dir_nofollow(&dir, &name).is_err()); } } } @@ -256,21 +318,21 @@ fn open_dir_nofollow() { for suffix in &["/", "/.", "/./"] { for symlink_dir in &["symlink_dir", "symlink_dot"] { let name = format!("{}{}", symlink_dir, suffix); - check!(tmpdir.open_dir(&name)); + check!(p::open_dir(&dir, Path::new(&name))); // On Windows, a trailing dot is stripped early. if cfg!(not(windows)) || suffix != &"/." { - check!(tmpdir.open_dir_nofollow(&name)); + check!(h::open_dir_nofollow(&dir, &name)); } else { - assert!(tmpdir.open_dir_nofollow(&name).is_err()); + assert!(h::open_dir_nofollow(&dir, &name).is_err()); } for dir_name in &["dir", "symlink_dir"] { let name = format!("{}/../{}", dir_name, name); - check!(tmpdir.open_dir(&name)); + check!(p::open_dir(&dir, Path::new(&name))); // On Windows, a trailing dot is stripped early. if cfg!(not(windows)) || suffix != &"/." { - check!(tmpdir.open_dir_nofollow(&name)); + check!(h::open_dir_nofollow(&dir, &name)); } else { - assert!(tmpdir.open_dir_nofollow(&name).is_err()); + assert!(h::open_dir_nofollow(&dir, &name).is_err()); } } } @@ -290,25 +352,25 @@ fn open_dir_nofollow() { let name = format!("{}{}", symlink_dir, suffix); #[cfg(windows)] { - error!(tmpdir.open_dir(&name), 123); - assert!(tmpdir.open_dir_nofollow(&name).is_err()); + error!(p::open_dir(&dir, Path::new(&name)), 123); + assert!(h::open_dir_nofollow(&dir, &name).is_err()); } #[cfg(not(windows))] { - check!(tmpdir.open_dir(&name)); - check!(tmpdir.open_dir_nofollow(&name)); + check!(p::open_dir(&dir, Path::new(&name))); + check!(h::open_dir_nofollow(&dir, &name)); } for dir_name in &["dir", "symlink_dir"] { let name = format!("{}/../{}", dir_name, name); #[cfg(windows)] { - error!(tmpdir.open_dir(&name), 123); - assert!(tmpdir.open_dir_nofollow(&name).is_err()); + error!(p::open_dir(&dir, Path::new(&name)), 123); + assert!(h::open_dir_nofollow(&dir, &name).is_err()); } #[cfg(not(windows))] { - check!(tmpdir.open_dir(&name)); - check!(tmpdir.open_dir_nofollow(&name)); + check!(p::open_dir(&dir, Path::new(&name))); + check!(h::open_dir_nofollow(&dir, &name)); } } } @@ -316,8 +378,8 @@ fn open_dir_nofollow() { // Check various ways of spelling `.`. for cur_dir in &["dir/..", "dir/../", ".", "./"] { - check!(tmpdir.open_dir(cur_dir)); - check!(tmpdir.open_dir_nofollow(cur_dir)); + check!(p::open_dir(&dir, Path::new(cur_dir))); + check!(h::open_dir_nofollow(&dir, cur_dir)); } } @@ -357,89 +419,54 @@ fn open_dir_nofollow_ambient() { check!(symlink_dir("./", dir.path().join("symlink_dotslash"))); check!(symlink_dir(".", dir.path().join("symlink_dot"))); - assert!(Dir::open_ambient_dir(dir.path().join("file"), ambient_authority()).is_err()); - assert!(Dir::open_ambient_dir(dir.path().join("symlink_file"), ambient_authority()).is_err()); - check!(Dir::open_ambient_dir( - dir.path().join("symlink_dir"), - ambient_authority() - )); + assert!(h::open_ambient_dir(dir.path().join("file")).is_err()); + assert!(h::open_ambient_dir(dir.path().join("symlink_file")).is_err()); + check!(h::open_ambient_dir(dir.path().join("symlink_dir"))); #[cfg(windows)] - check!(Dir::open_ambient_dir( - dir.path().join("symlink_dir\\"), - ambient_authority() - )); - check!(Dir::open_ambient_dir( - dir.path().join("symlink_dir/"), - ambient_authority() - )); + check!(h::open_ambient_dir(dir.path().join("symlink_dir\\"))); + check!(h::open_ambient_dir(dir.path().join("symlink_dir/"))); #[cfg(windows)] { error!( - Dir::open_ambient_dir(dir.path().join("symlink_dir_slash"), ambient_authority()), + h::open_ambient_dir(dir.path().join("symlink_dir_slash")), 123 ); error!( - Dir::open_ambient_dir( - dir.path().join("symlink_dir_slashdotdot"), - ambient_authority() - ), + h::open_ambient_dir(dir.path().join("symlink_dir_slashdotdot")), 123 ); error!( - Dir::open_ambient_dir( - dir.path().join("symlink_dir_slashdotdotslash"), - ambient_authority() - ), + h::open_ambient_dir(dir.path().join("symlink_dir_slashdotdotslash")), 123 ); error!( - Dir::open_ambient_dir(dir.path().join("symlink_dotslash"), ambient_authority()), + h::open_ambient_dir(dir.path().join("symlink_dotslash")), 123 ); error!( - Dir::open_ambient_dir(dir.path().join("symlink_dir_slashdot"), ambient_authority()), + h::open_ambient_dir(dir.path().join("symlink_dir_slashdot")), 123 ); } #[cfg(not(windows))] { - check!(Dir::open_ambient_dir( - dir.path().join("symlink_dir_slash"), - ambient_authority() - )); - check!(Dir::open_ambient_dir( - dir.path().join("symlink_dir_slashdotdot"), - ambient_authority() - )); - check!(Dir::open_ambient_dir( - dir.path().join("symlink_dir_slashdotdotslash"), - ambient_authority() - )); - check!(Dir::open_ambient_dir( - dir.path().join("symlink_dotslash"), - ambient_authority() + check!(h::open_ambient_dir(dir.path().join("symlink_dir_slash"))); + check!(h::open_ambient_dir( + dir.path().join("symlink_dir_slashdotdot") )); - check!(Dir::open_ambient_dir( - dir.path().join("symlink_dir_slashdot"), - ambient_authority() + check!(h::open_ambient_dir( + dir.path().join("symlink_dir_slashdotdotslash") )); + check!(h::open_ambient_dir(dir.path().join("symlink_dotslash"))); + check!(h::open_ambient_dir(dir.path().join("symlink_dir_slashdot"))); } - check!(Dir::open_ambient_dir( - dir.path().join("symlink_dot"), - ambient_authority() - )); - check!(Dir::open_ambient_dir( - dir.path().join("dir"), - ambient_authority() - )); + check!(h::open_ambient_dir(dir.path().join("symlink_dot"))); + check!(h::open_ambient_dir(dir.path().join("dir"))); // Check various ways of spelling `dir/../symlink_dir`. for dir_name in &["dir", "symlink_dir"] { let name = format!("{}/../symlink_dir", dir_name); - check!(Dir::open_ambient_dir( - dir.path().join(&name), - ambient_authority() - )); + check!(h::open_ambient_dir(dir.path().join(&name))); } // Check various paths which end with a symlink (even though the symlink @@ -447,16 +474,10 @@ fn open_dir_nofollow_ambient() { for suffix in &[""] { for symlink_dir in &["symlink_dot"] { let name = format!("{}{}", symlink_dir, suffix); - check!(Dir::open_ambient_dir( - dir.path().join(&name), - ambient_authority() - )); + check!(h::open_ambient_dir(dir.path().join(&name))); for dir_name in &["dir", "symlink_dir"] { let name = format!("{}/../{}", dir_name, name); - check!(Dir::open_ambient_dir( - dir.path().join(&name), - ambient_authority() - )); + check!(h::open_ambient_dir(dir.path().join(&name))); } } } @@ -474,33 +495,21 @@ fn open_dir_nofollow_ambient() { let name = format!("{}{}", symlink_dir, suffix); #[cfg(windows)] { - error!( - Dir::open_ambient_dir(dir.path().join(&name), ambient_authority()), - 123 - ); + error!(h::open_ambient_dir(dir.path().join(&name)), 123); } #[cfg(not(windows))] { - check!(Dir::open_ambient_dir( - dir.path().join(&name), - ambient_authority() - )); + check!(h::open_ambient_dir(dir.path().join(&name))); } for dir_name in &["dir", "symlink_dir"] { let name = format!("{}/../{}", dir_name, name); #[cfg(windows)] { - error!( - Dir::open_ambient_dir(dir.path().join(&name), ambient_authority()), - 123 - ); + error!(h::open_ambient_dir(dir.path().join(&name)), 123); } #[cfg(not(windows))] { - check!(Dir::open_ambient_dir( - dir.path().join(&name), - ambient_authority() - )); + check!(h::open_ambient_dir(dir.path().join(&name))); } } } @@ -510,16 +519,10 @@ fn open_dir_nofollow_ambient() { for suffix in &["/", "/.", "/./"] { for symlink_dir in &["symlink_dir", "symlink_dot"] { let name = format!("{}{}", symlink_dir, suffix); - check!(Dir::open_ambient_dir( - dir.path().join(&name), - ambient_authority() - )); + check!(h::open_ambient_dir(dir.path().join(&name))); for dir_name in &["dir", "symlink_dir"] { let name = format!("{}/../{}", dir_name, name); - check!(Dir::open_ambient_dir( - dir.path().join(&name), - ambient_authority() - )); + check!(h::open_ambient_dir(dir.path().join(&name))); } } } @@ -538,33 +541,21 @@ fn open_dir_nofollow_ambient() { let name = format!("{}{}", symlink_dir, suffix); #[cfg(windows)] { - error!( - Dir::open_ambient_dir(dir.path().join(&name), ambient_authority()), - 123 - ); + error!(h::open_ambient_dir(dir.path().join(&name)), 123); } #[cfg(not(windows))] { - check!(Dir::open_ambient_dir( - dir.path().join(&name), - ambient_authority() - )); + check!(h::open_ambient_dir(dir.path().join(&name))); } for dir_name in &["dir", "symlink_dir"] { let name = format!("{}/../{}", dir_name, name); #[cfg(windows)] { - error!( - Dir::open_ambient_dir(dir.path().join(&name), ambient_authority()), - 123 - ); + error!(h::open_ambient_dir(dir.path().join(&name)), 123); } #[cfg(not(windows))] { - check!(Dir::open_ambient_dir( - dir.path().join(&name), - ambient_authority() - )); + check!(h::open_ambient_dir(dir.path().join(&name))); } } } @@ -572,9 +563,6 @@ fn open_dir_nofollow_ambient() { // Check various ways of spelling `.`. for cur_dir in &["dir/..", "dir/../", ".", "./"] { - check!(Dir::open_ambient_dir( - dir.path().join(cur_dir), - ambient_authority() - )); + check!(h::open_ambient_dir(dir.path().join(cur_dir))); } } diff --git a/crates/wasi/src/filesystem/primitives/tests/sys/mod.rs b/crates/wasi/src/filesystem/primitives/tests/sys/mod.rs deleted file mode 100644 index cc2c50f97c74..000000000000 --- a/crates/wasi/src/filesystem/primitives/tests/sys/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![allow(unused_imports)] - -#[cfg(unix)] -mod unix; - -#[cfg(unix)] -pub use self::unix::*; diff --git a/crates/wasi/src/filesystem/primitives/tests/sys/unix/mod.rs b/crates/wasi/src/filesystem/primitives/tests/sys/unix/mod.rs deleted file mode 100644 index b8df4bedd41e..000000000000 --- a/crates/wasi/src/filesystem/primitives/tests/sys/unix/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod weak; diff --git a/crates/wasi/src/filesystem/primitives/tests/sys/unix/weak.rs b/crates/wasi/src/filesystem/primitives/tests/sys/unix/weak.rs deleted file mode 100644 index ecc58eef72a0..000000000000 --- a/crates/wasi/src/filesystem/primitives/tests/sys/unix/weak.rs +++ /dev/null @@ -1,238 +0,0 @@ -//! This file is derived from Rust's library/std/src/sys/unix/weak.rs at -//! revision e4b1d5841494d6eb7f4944c91a057e16b0f0a9ea. -//! -//! Support for "weak linkage" to symbols on Unix -//! -//! Some I/O operations we do in libstd require newer versions of OSes but we -//! need to maintain binary compatibility with older releases for now. In order -//! to use the new functionality when available we use this module for -//! detection. -//! -//! One option to use here is weak linkage, but that is unfortunately only -//! really workable with ELF. Otherwise, use dlsym to get the symbol value at -//! runtime. This is also done for compatibility with older versions of glibc, -//! and to avoid creating dependencies on GLIBC_PRIVATE symbols. It assumes -//! that we've been dynamically linked to the library the symbol comes from, -//! but that is currently always the case for things like libpthread/libc. -//! -//! A long time ago this used weak linkage for the __pthread_get_minstack -//! symbol, but that caused Debian to detect an unnecessarily strict versioned -//! dependency on libc6 (#23628) because it is GLIBC_PRIVATE. We now use -//! `dlsym` for a runtime lookup of that symbol to avoid the ELF versioned -//! dependency. - -// There are a variety of `#[cfg]`s controlling which targets are involved in -// each instance of `weak!` and `syscall!`. Rather than trying to unify all of -// that, we'll just allow that some unix targets don't use this module at all. -#![allow(dead_code, unused_macros)] - -use std::ffi::CStr; -use std::marker::PhantomData; -use std::mem; -use std::sync::atomic::{self, AtomicUsize, Ordering}; - -// We can use true weak linkage on ELF targets. -#[macro_export] -#[cfg(not(any( - target_os = "macos", - target_os = "ios", - target_os = "tvos", - target_os = "watchos", - target_os = "visionos", -)))] -macro_rules! weak { - (fn $name:ident($($t:ty),*) -> $ret:ty) => ( - let ref $name: ExternWeak $ret> = { - extern "C" { - #[linkage = "extern_weak"] - static $name: *const libc::c_void; - } - #[allow(unused_unsafe)] - ExternWeak::new(unsafe { $name }) - }; - ) -} - -// On non-ELF targets, use the dlsym approximation of weak linkage. -#[cfg(any( - target_os = "macos", - target_os = "ios", - target_os = "tvos", - target_os = "watchos", - target_os = "visionos", -))] -pub(crate) use crate::dlsym as weak; - -pub(crate) struct ExternWeak { - weak_ptr: *const libc::c_void, - _marker: PhantomData, -} - -impl ExternWeak { - #[inline] - pub(crate) fn new(weak_ptr: *const libc::c_void) -> Self { - ExternWeak { - weak_ptr, - _marker: PhantomData, - } - } -} - -impl ExternWeak { - #[inline] - pub(crate) fn get(&self) -> Option { - unsafe { - if self.weak_ptr.is_null() { - None - } else { - Some(mem::transmute_copy::<*const libc::c_void, F>( - &self.weak_ptr, - )) - } - } - } -} - -#[macro_export] -macro_rules! dlsym { - (fn $name:ident($($t:ty),*) -> $ret:ty) => ( - dlsym!(fn $name($($t),*) -> $ret, stringify!($name)); - ); - (fn $name:ident($($t:ty),*) -> $ret:ty, $sym:expr) => ( - static DLSYM: $crate::sys::weak::DlsymWeak $ret> = - $crate::sys::weak::DlsymWeak::new(concat!($sym, '\0')); - let $name = &DLSYM; - ) -} - -pub(crate) struct DlsymWeak { - name: &'static str, - addr: AtomicUsize, - _marker: PhantomData, -} - -impl DlsymWeak { - pub(crate) const fn new(name: &'static str) -> Self { - DlsymWeak { - name, - addr: AtomicUsize::new(1), - _marker: PhantomData, - } - } - - #[inline] - pub(crate) fn get(&self) -> Option { - unsafe { - // Relaxed is fine here because we fence before reading through the - // pointer (see the comment below). - match self.addr.load(Ordering::Relaxed) { - 1 => self.initialize(), - 0 => None, - addr => { - let func = mem::transmute_copy::(&addr); - // The caller is presumably going to read through this value - // (by calling the function we've dlsymed). This means we'd - // need to have loaded it with at least C11's consume - // ordering in order to be guaranteed that the data we read - // from the pointer isn't from before the pointer was - // stored. Rust has no equivalent to memory_order_consume, - // so we use an acquire fence (sorry, ARM). - // - // Now, in practice this likely isn't needed even on CPUs - // where relaxed and consume mean different things. The - // symbols we're loading are probably present (or not) at - // init, and even if they aren't the runtime dynamic loader - // is extremely likely have sufficient barriers internally - // (possibly implicitly, for example the ones provided by - // invoking `mprotect`). - // - // That said, none of that's *guaranteed*, and so we fence. - atomic::fence(Ordering::Acquire); - Some(func) - } - } - } - } - - // Cold because it should only happen during first-time initialization. - #[cold] - unsafe fn initialize(&self) -> Option { - assert_eq!(mem::size_of::(), mem::size_of::()); - - let val = fetch(self.name); - // This synchronizes with the acquire fence in `get`. - self.addr.store(val, Ordering::Release); - - match val { - 0 => None, - addr => Some(mem::transmute_copy::(&addr)), - } - } -} - -unsafe fn fetch(name: &str) -> usize { - let name = match CStr::from_bytes_with_nul(name.as_bytes()) { - Ok(cstr) => cstr, - Err(..) => return 0, - }; - libc::dlsym(libc::RTLD_DEFAULT, name.as_ptr()) as usize -} - -#[macro_export] -#[cfg(not(any(target_os = "linux", target_os = "android")))] -macro_rules! syscall { - (fn $name:ident($($arg_name:ident: $t:ty),*) -> $ret:ty) => ( - unsafe fn $name($($arg_name: $t),*) -> $ret { - weak! { fn $name($($t),*) -> $ret } - - if let Some(fun) = $name.get() { - fun($($arg_name),*) - } else { - super::os::set_errno(libc::ENOSYS); - -1 - } - } - ) -} - -#[macro_export] -#[cfg(any(target_os = "linux", target_os = "android"))] -macro_rules! syscall { - (fn $name:ident($($arg_name:ident: $t:ty),*) -> $ret:ty) => ( - unsafe fn $name($($arg_name:$t),*) -> $ret { - weak! { fn $name($($t),*) -> $ret } - - // Use a weak symbol from libc when possible, allowing `LD_PRELOAD` - // interposition, but if it's not found just use a raw syscall. - if let Some(fun) = $name.get() { - fun($($arg_name),*) - } else { - // This looks like a hack, but concat_idents only accepts idents - // (not paths). - use libc::*; - - syscall( - concat_idents!(SYS_, $name), - $($arg_name),* - ) as $ret - } - } - ) -} - -#[macro_export] -#[cfg(any(target_os = "linux", target_os = "android"))] -macro_rules! raw_syscall { - (fn $name:ident($($arg_name:ident: $t:ty),*) -> $ret:ty) => ( - unsafe fn $name($($arg_name:$t),*) -> $ret { - // This looks like a hack, but concat_idents only accepts idents - // (not paths). - use libc::*; - - syscall( - concat_idents!(SYS_, $name), - $($arg_name),* - ) as $ret - } - ) -} diff --git a/crates/wasi/src/filesystem/primitives/tests/sys_common/io.rs b/crates/wasi/src/filesystem/primitives/tests/sys_common/io.rs index e41ec157f7e1..e0dc74003acf 100644 --- a/crates/wasi/src/filesystem/primitives/tests/sys_common/io.rs +++ b/crates/wasi/src/filesystem/primitives/tests/sys_common/io.rs @@ -1,27 +1,6 @@ -use cap_tempfile::tempdir; - -#[cfg(feature = "fs_utf8")] -pub use cap_tempfile::utf8::TempDir as TempDirUtf8; -pub use cap_tempfile::TempDir; +pub use tempfile::TempDir; #[allow(unused)] pub fn tmpdir() -> TempDir { - use cap_tempfile::ambient_authority; - - // It's ok to call `ambient_authority()` here, rather than take an - // `AmbientAuthority` argument, because this function is only used - // by tests. - tempdir(ambient_authority()).expect("expected to be able to create a temporary directory") -} - -#[cfg(feature = "fs_utf8")] -#[allow(unused)] -pub fn tmpdir_utf8() -> TempDirUtf8 { - use cap_tempfile::ambient_authority; - use cap_tempfile::utf8::tempdir; - - // It's ok to call `ambient_authority()` here, rather than take an - // `AmbientAuthority` argument, because this function is only used - // by tests. - tempdir(ambient_authority()).expect("expected to be able to create a temporary directory") + tempfile::tempdir().expect("expected to be able to create a temporary directory") } diff --git a/crates/wasi/src/filesystem/primitives/tests/sys_common/symlink_junction.rs b/crates/wasi/src/filesystem/primitives/tests/sys_common/symlink_junction.rs index 31da73bcf738..840dd8fd147c 100644 --- a/crates/wasi/src/filesystem/primitives/tests/sys_common/symlink_junction.rs +++ b/crates/wasi/src/filesystem/primitives/tests/sys_common/symlink_junction.rs @@ -2,54 +2,29 @@ // library/std/src/sys/windows/fs.rs at revision // 3ffb27ff89db780e88abe829783565a7122be1c5. -#[cfg(feature = "fs_utf8")] -use camino::Utf8Path; -use cap_std::fs::Dir; -use std::io; use std::path::Path; +use std::{fs, io}; #[cfg(not(windows))] #[allow(dead_code)] pub fn symlink_junction, Q: AsRef>( src: P, - dst_dir: &Dir, + dst_dir: &fs::File, dst: Q, ) -> io::Result<()> { - dst_dir.symlink(src, dst) + crate::filesystem::primitives::symlink(src.as_ref(), dst_dir, dst.as_ref()) } #[cfg(windows)] #[allow(dead_code)] pub fn symlink_junction, Q: AsRef>( src: P, - dst_dir: &Dir, + dst_dir: &fs::File, dst: Q, ) -> io::Result<()> { symlink_junction_inner(src.as_ref(), dst_dir, dst.as_ref()) } -#[cfg(feature = "fs_utf8")] -#[cfg(not(windows))] -#[allow(dead_code)] -pub fn symlink_junction_utf8, Q: AsRef>( - src: P, - dst_dir: &cap_std::fs_utf8::Dir, - dst: Q, -) -> io::Result<()> { - dst_dir.symlink(src, dst) -} - -#[cfg(feature = "fs_utf8")] -#[cfg(windows)] -#[allow(dead_code)] -pub fn symlink_junction_utf8, Q: AsRef>( - src: P, - dst_dir: &cap_std::fs_utf8::Dir, - dst: Q, -) -> io::Result<()> { - symlink_junction_inner_utf8(src.as_ref(), dst_dir, dst.as_ref()) -} - /// Align the inner value to 8 bytes. /// /// This is enough for almost all of the buffers we're likely to work with in @@ -70,7 +45,7 @@ pub struct REPARSE_MOUNTPOINT_DATA_BUFFER { pub ReparseTargetLength: u16, pub ReparseTargetMaximumLength: u16, pub Reserved1: u16, - pub ReparseTarget: libc::wchar_t, + pub ReparseTarget: u16, } #[cfg(windows)] @@ -90,99 +65,17 @@ pub fn cvt(i: windows_sys::core::BOOL) -> io::Result { // http://www.flexhex.com/docs/articles/hard-links.phtml #[cfg(windows)] #[allow(dead_code)] -fn symlink_junction_inner(original: &Path, dir: &Dir, junction: &Path) -> io::Result<()> { - use cap_std::fs::{OpenOptions, OpenOptionsExt}; - use std::mem::MaybeUninit; - use std::os::windows::ffi::OsStrExt; - use std::os::windows::io::AsRawHandle; - use std::{mem, ptr}; - use windows_sys::Win32::Storage::FileSystem::MAXIMUM_REPARSE_DATA_BUFFER_SIZE; - - dir.create_dir(junction)?; - - let mut opts = OpenOptions::new(); - opts.write(true); - opts.custom_flags( - windows_sys::Win32::Storage::FileSystem::FILE_FLAG_OPEN_REPARSE_POINT - | windows_sys::Win32::Storage::FileSystem::FILE_FLAG_BACKUP_SEMANTICS, - ); - let f = dir.open_with(junction, &opts)?; - let h = f.as_raw_handle(); - unsafe { - let mut data = - Align8([MaybeUninit::::uninit(); MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize]); - let data_ptr = data.0.as_mut_ptr(); - let data_end = data_ptr.add(MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize); - let db = data_ptr.cast::(); - // Zero the header to ensure it's fully initialized, including reserved parameters. - *db = mem::zeroed(); - let reparse_target_slice = { - let buf_start = ptr::addr_of_mut!((*db).ReparseTarget).cast::(); - // Compute offset in bytes and then divide so that we round down - // rather than hit any UB (admittedly this arithmetic should work - // out so that this isn't necessary) - // TODO: use `byte_offfset_from` when pointer_byte_offsets is stable - // let buf_len_bytes = usize::try_from(data_end.byte_offset_from(buf_start)).unwrap(); - let buf_len_bytes = - usize::try_from(data_end.cast::().offset_from(buf_start.cast::())).unwrap(); - let buf_len_wchars = buf_len_bytes / core::mem::size_of::(); - core::slice::from_raw_parts_mut(buf_start, buf_len_wchars) - }; - // FIXME: this conversion is very hacky - let iter = br"\??\" - .iter() - .map(|x| *x as u16) - .chain(original.as_os_str().encode_wide()) - .chain(core::iter::once(0)); - let mut i = 0; - for c in iter { - if i >= reparse_target_slice.len() { - return Err(io::Error::new( - // TODO: use io::ErrorKind::InvalidFilename when io_error_more is stabilized - io::ErrorKind::Other, - "Input filename is too long", - )); - } - reparse_target_slice[i] = c; - i += 1; - } - (*db).ReparseTag = windows_sys::Win32::System::SystemServices::IO_REPARSE_TAG_MOUNT_POINT; - (*db).ReparseTargetMaximumLength = (i * 2) as u16; - (*db).ReparseTargetLength = ((i - 1) * 2) as u16; - (*db).ReparseDataLength = (*db).ReparseTargetLength as u32 + 12; - - let mut ret = 0; - cvt(windows_sys::Win32::System::IO::DeviceIoControl( - h as _, - windows_sys::Win32::System::Ioctl::FSCTL_SET_REPARSE_POINT, - data_ptr.cast(), - (*db).ReparseDataLength + 8, - ptr::null_mut(), - 0, - &mut ret, - ptr::null_mut(), - )) - .map(drop) - } -} - -/// Same as above, but for fs_utf8. -#[cfg(feature = "fs_utf8")] -#[cfg(windows)] -#[allow(dead_code)] -fn symlink_junction_inner_utf8( - original: &Utf8Path, - dir: &cap_std::fs_utf8::Dir, - junction: &Utf8Path, -) -> io::Result<()> { - use cap_std::fs::{OpenOptions, OpenOptionsExt}; +fn symlink_junction_inner(original: &Path, dir: &fs::File, junction: &Path) -> io::Result<()> { + use crate::filesystem::primitives::{ + DirOptions, OpenOptions, OpenOptionsExt, create_dir, open, + }; use std::mem::MaybeUninit; use std::os::windows::ffi::OsStrExt; use std::os::windows::io::AsRawHandle; use std::{mem, ptr}; use windows_sys::Win32::Storage::FileSystem::MAXIMUM_REPARSE_DATA_BUFFER_SIZE; - dir.create_dir(junction)?; + create_dir(dir, junction, &DirOptions::new())?; let mut opts = OpenOptions::new(); opts.write(true); @@ -190,7 +83,7 @@ fn symlink_junction_inner_utf8( windows_sys::Win32::Storage::FileSystem::FILE_FLAG_OPEN_REPARSE_POINT | windows_sys::Win32::Storage::FileSystem::FILE_FLAG_BACKUP_SEMANTICS, ); - let f = dir.open_with(junction, &opts)?; + let f = open(dir, junction, &opts)?; let h = f.as_raw_handle(); unsafe { let mut data = @@ -201,7 +94,7 @@ fn symlink_junction_inner_utf8( // Zero the header to ensure it's fully initialized, including reserved parameters. *db = mem::zeroed(); let reparse_target_slice = { - let buf_start = ptr::addr_of_mut!((*db).ReparseTarget).cast::(); + let buf_start = ptr::addr_of_mut!((*db).ReparseTarget).cast::(); // Compute offset in bytes and then divide so that we round down // rather than hit any UB (admittedly this arithmetic should work // out so that this isn't necessary) @@ -209,7 +102,7 @@ fn symlink_junction_inner_utf8( // let buf_len_bytes = usize::try_from(data_end.byte_offset_from(buf_start)).unwrap(); let buf_len_bytes = usize::try_from(data_end.cast::().offset_from(buf_start.cast::())).unwrap(); - let buf_len_wchars = buf_len_bytes / core::mem::size_of::(); + let buf_len_wchars = buf_len_bytes / core::mem::size_of::(); core::slice::from_raw_parts_mut(buf_start, buf_len_wchars) }; // FIXME: this conversion is very hacky diff --git a/crates/wasi/src/filesystem/primitives/tests/windows_open.rs b/crates/wasi/src/filesystem/primitives/tests/windows_open.rs index a6c8b2bbb616..d449e4a83891 100644 --- a/crates/wasi/src/filesystem/primitives/tests/windows_open.rs +++ b/crates/wasi/src/filesystem/primitives/tests/windows_open.rs @@ -3,133 +3,162 @@ //! sharing modes to prevent open directories from being removed or renamed. //! Test that this works. -#[cfg(windows)] -#[macro_use] -mod sys_common; +#![cfg(windows)] -#[cfg(windows)] -use sys_common::io::tmpdir; +use super::helpers as h; +use super::sys_common::io::tmpdir; +use crate::filesystem::primitives as p; +use std::path::Path; #[test] -#[cfg(windows)] fn windows_open_one() { let tmpdir = tmpdir(); - check!(tmpdir.create_dir("aaa")); + let start = h::dir_of(&tmpdir); + check!(h::create_dir(&start, "aaa")); - let dir = check!(tmpdir.open_dir("aaa")); + let dir = check!(p::open_dir(&start, Path::new("aaa"))); // Attempts to remove or rename the open directory should fail. - tmpdir.remove_dir("aaa").unwrap_err(); - tmpdir.rename("aaa", &tmpdir, "zzz").unwrap_err(); + p::remove_dir(&start, Path::new("aaa")).unwrap_err(); + p::rename(&start, Path::new("aaa"), &start, Path::new("zzz")).unwrap_err(); drop(dir); // Now that we've dropped the handle, the same operations should succeed. - check!(tmpdir.rename("aaa", &tmpdir, "xxx")); - check!(tmpdir.remove_dir("xxx")); + check!(p::rename( + &start, + Path::new("aaa"), + &start, + Path::new("xxx") + )); + check!(p::remove_dir(&start, Path::new("xxx"))); } #[test] -#[cfg(windows)] fn windows_open_multiple() { let tmpdir = tmpdir(); - check!(tmpdir.create_dir_all("aaa/bbb")); + let start = h::dir_of(&tmpdir); + check!(h::create_dir_all(&start, "aaa/bbb")); - let dir = check!(tmpdir.open_dir("aaa/bbb")); + let dir = check!(p::open_dir(&start, Path::new("aaa/bbb"))); // Attempts to remove or rename any component of the open directory should // fail. - tmpdir.remove_dir("aaa/bbb").unwrap_err(); - tmpdir.remove_dir("aaa").unwrap_err(); - tmpdir.rename("aaa/bbb", &tmpdir, "aaa/yyy").unwrap_err(); - tmpdir.rename("aaa", &tmpdir, "zzz").unwrap_err(); + p::remove_dir(&start, Path::new("aaa/bbb")).unwrap_err(); + p::remove_dir(&start, Path::new("aaa")).unwrap_err(); + p::rename(&start, Path::new("aaa/bbb"), &start, Path::new("aaa/yyy")).unwrap_err(); + p::rename(&start, Path::new("aaa"), &start, Path::new("zzz")).unwrap_err(); drop(dir); // Now that we've dropped the handle, the same operations should succeed. - check!(tmpdir.rename("aaa/bbb", &tmpdir, "aaa/www")); - check!(tmpdir.rename("aaa", &tmpdir, "xxx")); - check!(tmpdir.remove_dir("xxx/www")); - check!(tmpdir.remove_dir("xxx")); + check!(p::rename( + &start, + Path::new("aaa/bbb"), + &start, + Path::new("aaa/www") + )); + check!(p::rename( + &start, + Path::new("aaa"), + &start, + Path::new("xxx") + )); + check!(p::remove_dir(&start, Path::new("xxx/www"))); + check!(p::remove_dir(&start, Path::new("xxx"))); } /// Like `windows_open_multiple`, but does so within a directory that we /// can close and then independently mutate. #[test] -#[cfg(windows)] fn windows_open_tricky() { let tmpdir = tmpdir(); - check!(tmpdir.create_dir("qqq")); + let start = h::dir_of(&tmpdir); + check!(h::create_dir(&start, "qqq")); - let qqq = check!(tmpdir.open_dir("qqq")); - check!(qqq.create_dir_all("aaa/bbb")); + let qqq = check!(p::open_dir(&start, Path::new("qqq"))); + check!(h::create_dir_all(&qqq, "aaa/bbb")); - let dir = check!(qqq.open_dir("aaa/bbb")); + let dir = check!(p::open_dir(&qqq, Path::new("aaa/bbb"))); // Now drop `qqq`. drop(qqq); // Attempts to remove or rename any component of the open directory should // fail. - dir.remove_dir("aaa/bbb").unwrap_err(); - dir.remove_dir("aaa").unwrap_err(); - dir.rename("aaa/bbb", &tmpdir, "aaa/yyy").unwrap_err(); - dir.rename("aaa", &tmpdir, "zzz").unwrap_err(); - tmpdir.remove_dir("qqq/aaa/bbb").unwrap_err(); - tmpdir.remove_dir("qqq/aaa").unwrap_err(); - tmpdir.remove_dir("qqq").unwrap_err(); - tmpdir - .rename("qqq/aaa/bbb", &tmpdir, "qqq/aaa/yyy") - .unwrap_err(); - tmpdir.rename("qqq/aaa", &tmpdir, "qqq/zzz").unwrap_err(); - tmpdir.rename("qqq", &tmpdir, "vvv").unwrap_err(); + p::remove_dir(&dir, Path::new("aaa/bbb")).unwrap_err(); + p::remove_dir(&dir, Path::new("aaa")).unwrap_err(); + p::rename(&dir, Path::new("aaa/bbb"), &dir, Path::new("aaa/yyy")).unwrap_err(); + p::rename(&dir, Path::new("aaa"), &dir, Path::new("zzz")).unwrap_err(); + p::remove_dir(&start, Path::new("qqq/aaa/bbb")).unwrap_err(); + p::remove_dir(&start, Path::new("qqq/aaa")).unwrap_err(); + p::remove_dir(&start, Path::new("qqq")).unwrap_err(); + p::rename( + &dir, + Path::new("qqq/aaa/bbb"), + &dir, + Path::new("qqq/aaa/yyy"), + ) + .unwrap_err(); + p::rename(&start, Path::new("qqq/aaa"), &start, Path::new("qqq/zzz")).unwrap_err(); + p::rename(&start, Path::new("qqq"), &start, Path::new("vvv")).unwrap_err(); drop(dir); // Now that we've dropped the handle, the same operations should succeed. - check!(tmpdir.rename("qqq/aaa/bbb", &tmpdir, "qqq/aaa/www")); - check!(tmpdir.rename("qqq/aaa", &tmpdir, "qqq/xxx")); - check!(tmpdir.rename("qqq", &tmpdir, "uuu")); - check!(tmpdir.remove_dir("uuu/xxx/www")); - check!(tmpdir.remove_dir("uuu/xxx")); - check!(tmpdir.remove_dir("uuu")); + check!(p::rename( + &start, + Path::new("qqq/aaa/bbb"), + &start, + Path::new("qqq/aaa/www") + )); + check!(p::rename( + &start, + Path::new("qqq/aaa"), + &start, + Path::new("qqq/xxx") + )); + check!(p::rename( + &start, + Path::new("qqq"), + &start, + Path::new("uuu") + )); + check!(p::remove_dir(&start, Path::new("uuu/xxx/www"))); + check!(p::remove_dir(&start, Path::new("uuu/xxx"))); + check!(p::remove_dir(&start, Path::new("uuu"))); } /// Like `windows_open_one` but uses `open_ambient_dir` instead of `open_dir`. #[test] -#[cfg(windows)] fn windows_open_ambient() { - use cap_std::ambient_authority; - use cap_std::fs::Dir; - let ambient_dir = tempfile::tempdir().unwrap(); - let tmpdir = check!(Dir::open_ambient_dir( - ambient_dir.path(), - ambient_authority() - )); - check!(tmpdir.create_dir("aaa")); + let start = check!(h::open_ambient_dir(ambient_dir.path())); + check!(h::create_dir(&start, "aaa")); - let dir = check!(Dir::open_ambient_dir( - ambient_dir.path().join("aaa"), - ambient_authority() - )); + let dir = check!(h::open_ambient_dir(ambient_dir.path().join("aaa"))); // Attempts to remove or rename the open directory should fail. - tmpdir.remove_dir("aaa").unwrap_err(); - tmpdir.rename("aaa", &tmpdir, "zzz").unwrap_err(); + p::remove_dir(&start, Path::new("aaa")).unwrap_err(); + p::rename(&start, Path::new("aaa"), &start, Path::new("zzz")).unwrap_err(); drop(dir); // Now that we've dropped the handle, the same operations should succeed. - check!(tmpdir.rename("aaa", &tmpdir, "xxx")); - check!(tmpdir.remove_dir("xxx")); + check!(p::rename( + &start, + Path::new("aaa"), + &start, + Path::new("xxx") + )); + check!(p::remove_dir(&start, Path::new("xxx"))); } #[test] -#[cfg(windows)] fn windows_open_special() { let tmpdir = tmpdir(); + let start = h::dir_of(&tmpdir); // Opening any of these should fail. for device in &[ @@ -155,19 +184,22 @@ fn windows_open_special() { let name = format!("{}{}", device, suffix); eprintln!("testing '{}'", name); - match tmpdir.open(&name).unwrap_err().kind() { + match h::open(&start, &name).unwrap_err().kind() { std::io::ErrorKind::NotFound | std::io::ErrorKind::PermissionDenied => {} kind => panic!("unexpected error: {:?}", kind), } - let mut options = cap_std::fs::OpenOptions::new(); + let mut options = p::OpenOptions::new(); options.write(true); - match tmpdir.open_with(&name, &options).unwrap_err().kind() { + match p::open(&start, Path::new(&name), &options) + .unwrap_err() + .kind() + { std::io::ErrorKind::NotFound | std::io::ErrorKind::PermissionDenied => {} kind => panic!("unexpected error: {:?}", kind), } - match tmpdir.create(&name).unwrap_err().kind() { + match h::create(&start, &name).unwrap_err().kind() { std::io::ErrorKind::NotFound | std::io::ErrorKind::PermissionDenied => {} kind => panic!("unexpected error: {:?}", kind), } diff --git a/crates/wasi/src/filesystem/primitives/tests/windows_symlinks.rs b/crates/wasi/src/filesystem/primitives/tests/windows_symlinks.rs index cc8b425f2b38..3a8c88b7835f 100644 --- a/crates/wasi/src/filesystem/primitives/tests/windows_symlinks.rs +++ b/crates/wasi/src/filesystem/primitives/tests/windows_symlinks.rs @@ -1,10 +1,11 @@ #![cfg(windows)] -#[macro_use] -mod sys_common; +use super::helpers as h; +use super::sys_common::io::tmpdir; +use super::sys_common::symlink_supported; +use crate::filesystem::primitives as p; -use sys_common::io::tmpdir; -use sys_common::symlink_supported; +use std::path::Path; #[test] fn windows_symlinks() { @@ -14,33 +15,37 @@ fn windows_symlinks() { let tmpdir = tmpdir(); - check!(tmpdir.create("file")); - check!(tmpdir.create_dir("dir")); + let start = h::dir_of(&tmpdir); + + check!(h::create(&start, "file")); + check!(h::create_dir(&start, "dir")); // Windows lets these succeed. - check!(tmpdir.symlink_dir("file", "file_symlink_dir")); - check!(tmpdir.symlink_file("dir", "dir_symlink_file")); + check!(h::symlink_dir(&start, "file", "file_symlink_dir")); + check!(h::symlink_file(&start, "dir", "dir_symlink_file")); // But accessing them fails. - assert!(tmpdir.open("dir_symlink_file").is_err()); - assert!(tmpdir.open("file_symlink_dir").is_err()); - assert!(tmpdir.open_dir("dir_symlink_file").is_err()); - assert!(tmpdir.open_dir("file_symlink_dir").is_err()); - assert!(tmpdir.metadata("dir_symlink_file").is_err()); - assert!(tmpdir.metadata("file_symlink_dir").is_err()); - - assert!(check!(tmpdir.symlink_metadata("file_symlink_dir")) - .file_type() - .is_symlink()); - assert!(check!(tmpdir.symlink_metadata("dir_symlink_file")) - .file_type() - .is_symlink()); + assert!(h::open(&start, "dir_symlink_file").is_err()); + assert!(h::open(&start, "file_symlink_dir").is_err()); + assert!(p::open_dir(&start, Path::new("dir_symlink_file")).is_err()); + assert!(p::open_dir(&start, Path::new("file_symlink_dir")).is_err()); + assert!(h::metadata(&start, "dir_symlink_file").is_err()); + assert!(h::metadata(&start, "file_symlink_dir").is_err()); + + assert!( + check!(h::symlink_metadata(&start, "file_symlink_dir")) + .file_type() + .is_symlink() + ); + assert!( + check!(h::symlink_metadata(&start, "dir_symlink_file")) + .file_type() + .is_symlink() + ); } #[test] fn windows_symlinks_ambient() { - use cap_std::ambient_authority; - use cap_std::fs::Dir; use std::fs; use std::os::windows::fs::{symlink_dir, symlink_file}; @@ -61,24 +66,10 @@ fn windows_symlinks_ambient() { assert!(fs::File::open(dir.path().join("dir_symlink_file")).is_err()); assert!(fs::File::open(dir.path().join("file_symlink_dir")).is_err()); - // Windows 2016 doesn't issue errors here, so disable these tests. According to - // - // - // the build numbers for Windows 2016 are up to 17134. Also it appears we need - // to apply a mask to the version `nt_version` gives us. This is guesswork. - let (_maj, _min, build) = nt_version::get(); - if (build & 0xffff) > 17134 { - assert!( - Dir::open_ambient_dir(dir.path().join("dir_symlink_file"), ambient_authority()) - .is_err() - ); - assert!( - Dir::open_ambient_dir(dir.path().join("file_symlink_dir"), ambient_authority()) - .is_err() - ); - assert!(fs::metadata(dir.path().join("dir_symlink_file")).is_err()); - assert!(fs::metadata(dir.path().join("file_symlink_dir")).is_err()); - } + assert!(h::open_ambient_dir(dir.path().join("dir_symlink_file")).is_err()); + assert!(h::open_ambient_dir(dir.path().join("file_symlink_dir")).is_err()); + assert!(fs::metadata(dir.path().join("dir_symlink_file")).is_err()); + assert!(fs::metadata(dir.path().join("file_symlink_dir")).is_err()); assert!( check!(fs::symlink_metadata(dir.path().join("file_symlink_dir"))) From 414d10bce96017c9ebf5feda09f966fd62004657 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 27 Aug 2026 13:58:17 -0700 Subject: [PATCH 15/34] Fix remaining crate compiler warnings --- crates/wasi/src/filesystem/primitives/metadata.rs | 1 + crates/wasi/src/filesystem/primitives/open_options.rs | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/crates/wasi/src/filesystem/primitives/metadata.rs b/crates/wasi/src/filesystem/primitives/metadata.rs index 463579a0d771..ee2d9d770c4a 100644 --- a/crates/wasi/src/filesystem/primitives/metadata.rs +++ b/crates/wasi/src/filesystem/primitives/metadata.rs @@ -38,6 +38,7 @@ impl Metadata { /// nearby functions, some fields of the resulting metadata will be `None`. /// /// [`std::fs::Metadata::volume_serial_number`]: https://doc.rust-lang.org/std/os/windows/fs/trait.MetadataExt.html#tymethod.volume_serial_number + #[cfg(windows)] #[inline] pub fn from_just_metadata(std: fs::Metadata) -> Self { let ext = ImplMetadataExt::from_just_metadata(&std); diff --git a/crates/wasi/src/filesystem/primitives/open_options.rs b/crates/wasi/src/filesystem/primitives/open_options.rs index ad07d3fc4681..30db6a6c5d9e 100644 --- a/crates/wasi/src/filesystem/primitives/open_options.rs +++ b/crates/wasi/src/filesystem/primitives/open_options.rs @@ -24,10 +24,13 @@ pub struct OpenOptions { pub(crate) create: bool, pub(crate) create_new: bool, pub(crate) dir_required: bool, + #[cfg(windows)] pub(crate) maybe_dir: bool, pub(crate) sync: bool, pub(crate) dsync: bool, + #[cfg(not(windows))] pub(crate) rsync: bool, + #[cfg(not(windows))] pub(crate) nonblock: bool, pub(crate) readdir_required: bool, pub(crate) follow: FollowSymlinks, @@ -51,10 +54,13 @@ impl OpenOptions { create: false, create_new: false, dir_required: false, + #[cfg(windows)] maybe_dir: false, sync: false, dsync: false, + #[cfg(not(windows))] rsync: false, + #[cfg(not(windows))] nonblock: false, readdir_required: false, follow: FollowSymlinks::Yes, From aee2a3a8bad0463b9d40918c227751571fd26d2d Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 27 Aug 2026 14:07:48 -0700 Subject: [PATCH 16/34] Remove a test testing test-only code --- crates/wasi/src/filesystem/primitives/tests/fs.rs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/crates/wasi/src/filesystem/primitives/tests/fs.rs b/crates/wasi/src/filesystem/primitives/tests/fs.rs index daaa9ae0030a..427825b67468 100644 --- a/crates/wasi/src/filesystem/primitives/tests/fs.rs +++ b/crates/wasi/src/filesystem/primitives/tests/fs.rs @@ -649,21 +649,6 @@ fn recursive_mkdir() { assert!(h::is_dir(&start, dir)); } -#[test] -fn recursive_mkdir_failure() { - let tmpdir = tmpdir(); - let start = h::dir_of(&tmpdir); - let dir = "d1"; - let file = "f1"; - - check!(h::create_dir_all(&start, &dir)); - check!(h::create(&start, &file)); - - let result = h::create_dir_all(&start, &file); - - assert!(result.is_err()); -} - #[test] fn concurrent_recursive_mkdir() { for _ in 0..100 { From 4b0030705abfacd6baa0f4d4f90e5c6e4dfb491e Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 27 Aug 2026 14:11:02 -0700 Subject: [PATCH 17/34] Quash some lints --- crates/wasi/src/filesystem/primitives/mod.rs | 21 +++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/crates/wasi/src/filesystem/primitives/mod.rs b/crates/wasi/src/filesystem/primitives/mod.rs index c649fca9071b..c3817520d2ee 100644 --- a/crates/wasi/src/filesystem/primitives/mod.rs +++ b/crates/wasi/src/filesystem/primitives/mod.rs @@ -1,10 +1,21 @@ //! Filesystem utilities. -// Allow preexisting warnings that were present in this module before it was -// imported from the cap-primitives crate. These still probably want to get -// resolved in a future change. -#![allow(trivial_numeric_casts)] -#![allow(unsafe_op_in_unsafe_fn)] +#![allow( + trivial_numeric_casts, + reason = "preexisting from when cap-primitives was imported" +)] +#![allow( + unsafe_op_in_unsafe_fn, + reason = "preexisting from when cap-primitives was imported" +)] +#![allow( + clippy::unnecessary_fallible_conversions, + reason = "platform-agnostic code can't always take advantage of this" +)] +#![allow( + clippy::allow_attributes_without_reason, + reason = "preexisting from when cap-primitives was imported" +)] mod create_dir; mod dir_entry; From 98f8f071f57a071647455e420347e2d087f6eb41 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 27 Aug 2026 14:12:01 -0700 Subject: [PATCH 18/34] Fix a panic on my local filesystem This commit resolves a debug-mode-only panic about an overflow which happens internally within cap-std. --- crates/wasi/src/filesystem/primitives/rustix/fs/metadata_ext.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/metadata_ext.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/metadata_ext.rs index f29b7959e20d..8e4400ebf9eb 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/metadata_ext.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/metadata_ext.rs @@ -147,7 +147,7 @@ fn system_time_from_rustix(sec: i64, nsec: u64) -> Option { SystemTime::UNIX_EPOCH.checked_add(Duration::new(u64::try_from(sec).unwrap(), nsec as _)) } else { SystemTime::UNIX_EPOCH - .checked_sub(Duration::new(u64::try_from(-sec).unwrap(), 0)) + .checked_sub(Duration::new(sec.unsigned_abs(), 0)) .map(|t| t.checked_add(Duration::new(0, nsec as u32))) .flatten() } From 1d216fb6c41520513b66bf5f6db29145a43cb9a5 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 27 Aug 2026 14:14:57 -0700 Subject: [PATCH 19/34] Remove ambient-authority dependency Not needed from the cap-primitives vendoring --- Cargo.lock | 1 - crates/wasi/Cargo.toml | 1 - crates/wasi/src/ctx.rs | 6 +----- crates/wasi/src/filesystem/primitives/open_dir.rs | 5 ++--- .../wasi/src/filesystem/primitives/rustix/fs/dir_utils.rs | 8 +------- .../src/filesystem/primitives/windows/fs/dir_utils.rs | 8 +------- .../filesystem/primitives/windows/fs/open_unchecked.rs | 3 --- 7 files changed, 5 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dc18cc23a3a5..f5e1727d3672 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5162,7 +5162,6 @@ dependencies = [ name = "wasmtime-wasi" version = "49.0.0-dev" dependencies = [ - "ambient-authority", "async-trait", "bitflags 2.11.1", "bytes", diff --git a/crates/wasi/Cargo.toml b/crates/wasi/Cargo.toml index b6260b2b84d5..51c181d7941d 100644 --- a/crates/wasi/Cargo.toml +++ b/crates/wasi/Cargo.toml @@ -35,7 +35,6 @@ url = { workspace = true } rand = { workspace = true, features = ['std_rng', 'thread_rng'] } maybe-owned = "0.3" -ambient-authority = "*" io-extras = "0.19.0" io-lifetimes = { version = "3.0.1", default-features = false } winx = "0.36.0" diff --git a/crates/wasi/src/ctx.rs b/crates/wasi/src/ctx.rs index a5da63c9f8f5..a54ed070af52 100644 --- a/crates/wasi/src/ctx.rs +++ b/crates/wasi/src/ctx.rs @@ -4,7 +4,6 @@ use crate::filesystem::{Dir, WasiFilesystemCtx}; use crate::random::WasiRandomCtx; use crate::sockets::{SocketAddrCheck, SocketAddrUse, WasiSocketsCtx}; use crate::{FsPerms, OpenMode}; -use ambient_authority::ambient_authority; use rand::Rng; use std::future::Future; use std::mem; @@ -300,10 +299,7 @@ impl WasiCtxBuilder { guest_path: impl AsRef, perms: FsPerms, ) -> Result<&mut Self> { - let dir = crate::filesystem::primitives::open_ambient_dir( - host_path.as_ref(), - ambient_authority(), - )?; + let dir = crate::filesystem::primitives::open_ambient_dir(host_path.as_ref())?; let open_mode = match perms { FsPerms::ReadOnly => OpenMode::READ, FsPerms::ReadWrite => OpenMode::READ | OpenMode::WRITE, diff --git a/crates/wasi/src/filesystem/primitives/open_dir.rs b/crates/wasi/src/filesystem/primitives/open_dir.rs index f30f8f47549f..351b57af1a52 100644 --- a/crates/wasi/src/filesystem/primitives/open_dir.rs +++ b/crates/wasi/src/filesystem/primitives/open_dir.rs @@ -6,7 +6,6 @@ use crate::filesystem::primitives::open_unchecked; use crate::filesystem::primitives::{ FollowSymlinks, dir_options, open, open_ambient_dir_impl, readdir_options, }; -use ambient_authority::AmbientAuthority; use std::path::Path; use std::{fs, io}; @@ -45,6 +44,6 @@ pub(crate) fn open_dir_for_reading_unchecked( /// This function is not sandboxed and may trivially access any path that the /// host process has access to. #[inline] -pub fn open_ambient_dir(path: &Path, ambient_authority: AmbientAuthority) -> io::Result { - open_ambient_dir_impl(path, ambient_authority) +pub fn open_ambient_dir(path: &Path) -> io::Result { + open_ambient_dir_impl(path) } diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/dir_utils.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/dir_utils.rs index b70b576667ad..078b7042f24d 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/dir_utils.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/dir_utils.rs @@ -1,5 +1,4 @@ use crate::filesystem::primitives::OpenOptions; -use ambient_authority::AmbientAuthority; use rustix::fs::OFlags; use std::ffi::{OsStr, OsString}; use std::ops::Deref; @@ -97,12 +96,7 @@ pub(crate) fn readdir_options() -> OpenOptions { /// /// This function is not sandboxed and may trivially access any path that the /// host process has access to. -pub(crate) fn open_ambient_dir_impl( - path: &Path, - ambient_authority: AmbientAuthority, -) -> io::Result { - let _ = ambient_authority; - +pub(crate) fn open_ambient_dir_impl(path: &Path) -> io::Result { let mut options = fs::OpenOptions::new(); options.read(true); diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/dir_utils.rs b/crates/wasi/src/filesystem/primitives/windows/fs/dir_utils.rs index 8f1de11d6301..e4b8c28a449d 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/dir_utils.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/dir_utils.rs @@ -1,6 +1,5 @@ use crate::filesystem::primitives::OpenOptionsExt; use crate::filesystem::primitives::{OpenOptions, errors}; -use ambient_authority::AmbientAuthority; use std::ffi::OsString; use std::ops::Deref; use std::os::windows::ffi::{OsStrExt, OsStringExt}; @@ -84,14 +83,9 @@ pub(crate) fn readdir_options() -> OpenOptions { /// /// This function is not sandboxed and may trivially access any path that the /// host process has access to. -pub(crate) fn open_ambient_dir_impl( - path: &Path, - ambient_authority: AmbientAuthority, -) -> io::Result { +pub(crate) fn open_ambient_dir_impl(path: &Path) -> io::Result { use std::os::windows::fs::OpenOptionsExt; - let _ = ambient_authority; - // Set `FILE_FLAG_BACKUP_SEMANTICS` so that we can open directories. Unset // `FILE_SHARE_DELETE` so that directories can't be renamed or deleted // underneath us, since we use paths to implement many directory operations. diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/open_unchecked.rs b/crates/wasi/src/filesystem/primitives/windows/fs/open_unchecked.rs index 0678b1d9af41..9adb1ec84872 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/open_unchecked.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/open_unchecked.rs @@ -8,7 +8,6 @@ use crate::filesystem::primitives::{ FollowSymlinks, OpenOptions, OpenUncheckedError, SymlinkKind, errors, file_path, get_access_mode, get_creation_mode, get_flags_and_attributes, }; -use ambient_authority::ambient_authority; use std::ffi::OsStr; use std::os::windows::ffi::OsStrExt; use std::os::windows::fs::MetadataExt; @@ -27,8 +26,6 @@ pub(crate) fn open_unchecked( path: &Path, options: &OpenOptions, ) -> Result { - let _ = ambient_authority; - // We have the final `OpenOptions`; now prepare it for an `open`. let mut prepared_opts = options.clone(); let manually_trunc = prepare_open_options_for_open(&mut prepared_opts); From bee8c2a96749e1ac163f8d0ebb5d9e2524cc29eb Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 27 Aug 2026 14:21:51 -0700 Subject: [PATCH 20/34] Remove fs-set-times dependency --- Cargo.lock | 1 - crates/wasi/Cargo.toml | 1 - crates/wasi/src/filesystem.rs | 6 +--- crates/wasi/src/filesystem/primitives/mod.rs | 2 -- .../filesystem/primitives/rustix/fs/times.rs | 13 ++++---- .../primitives/rustix/linux/fs/procfs.rs | 9 +++--- .../rustix/linux/fs/set_times_impl.rs | 30 ++++++++----------- .../src/filesystem/primitives/set_times.rs | 11 +++---- .../filesystem/primitives/system_time_spec.rs | 27 ----------------- .../via_parent/set_times_nofollow.rs | 7 +++-- .../primitives/windows/fs/set_times_impl.rs | 28 +++++++++-------- 11 files changed, 50 insertions(+), 85 deletions(-) delete mode 100644 crates/wasi/src/filesystem/primitives/system_time_spec.rs diff --git a/Cargo.lock b/Cargo.lock index f5e1727d3672..7ef301248685 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5168,7 +5168,6 @@ dependencies = [ "cap-fs-ext", "cap-primitives", "env_logger 0.11.5", - "fs-set-times", "futures", "io-extras", "io-lifetimes 3.0.1", diff --git a/crates/wasi/Cargo.toml b/crates/wasi/Cargo.toml index 51c181d7941d..d8aa546a9f16 100644 --- a/crates/wasi/Cargo.toml +++ b/crates/wasi/Cargo.toml @@ -38,7 +38,6 @@ maybe-owned = "0.3" io-extras = "0.19.0" io-lifetimes = { version = "3.0.1", default-features = false } winx = "0.36.0" -fs-set-times = "0.20.0" [dev-dependencies] tokio = { workspace = true, features = ["time", "sync", "io-std", "io-util", "rt", "rt-multi-thread", "net", "macros", "fs"] } diff --git a/crates/wasi/src/filesystem.rs b/crates/wasi/src/filesystem.rs index 6d892a81c3cb..f74621ad0302 100644 --- a/crates/wasi/src/filesystem.rs +++ b/crates/wasi/src/filesystem.rs @@ -1,7 +1,5 @@ use crate::clocks::Datetime; -use crate::filesystem::primitives::{ - DirOptions, FollowSymlinks, Metadata, OpenOptions, SystemTimeSpec, -}; +use crate::filesystem::primitives::{DirOptions, FollowSymlinks, Metadata, OpenOptions}; use crate::runtime::{AbortOnDropJoinHandle, spawn_blocking}; use std::collections::hash_map; use std::sync::Arc; @@ -888,8 +886,6 @@ impl Dir { if self.perms.write_not_permitted() { return Err(ErrorCode::NotPermitted); } - let atim = atim.map(|t| SystemTimeSpec::Absolute(t)); - let mtim = mtim.map(|t| SystemTimeSpec::Absolute(t)); if path_flags.contains(PathFlags::SYMLINK_FOLLOW) { self.run_blocking(move |d| { crate::filesystem::primitives::set_times(d, path.as_ref(), atim, mtim) diff --git a/crates/wasi/src/filesystem/primitives/mod.rs b/crates/wasi/src/filesystem/primitives/mod.rs index c3817520d2ee..f88c807fd569 100644 --- a/crates/wasi/src/filesystem/primitives/mod.rs +++ b/crates/wasi/src/filesystem/primitives/mod.rs @@ -39,7 +39,6 @@ mod rename; mod set_times; mod stat; mod symlink; -mod system_time_spec; pub(crate) mod errors; pub(crate) mod manually; @@ -87,6 +86,5 @@ pub use stat::stat; pub use symlink::symlink; #[cfg(windows)] pub use symlink::{symlink_dir, symlink_file}; -pub use system_time_spec::SystemTimeSpec; #[cfg(test)] mod tests; diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/times.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/times.rs index 8c62f0129ddb..ec9bf3ea090e 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/times.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/times.rs @@ -1,4 +1,3 @@ -use crate::filesystem::primitives::SystemTimeSpec; use io_lifetimes::BorrowedFd; use rustix::fs::{AtFlags, Timestamps, UTIME_NOW, UTIME_OMIT, utimensat}; use rustix::time::Timespec; @@ -7,13 +6,13 @@ use std::time::SystemTime; use std::{fs, io}; #[allow(clippy::useless_conversion)] -pub(crate) fn to_timespec(ft: Option) -> io::Result { +pub(crate) fn to_timespec(ft: Option) -> io::Result { Ok(match ft { None => Timespec { tv_sec: 0, tv_nsec: UTIME_OMIT.into(), }, - Some(SystemTimeSpec::Absolute(ft)) => { + Some(ft) => { let duration = ft.duration_since(SystemTime::UNIX_EPOCH).unwrap(); let nanoseconds = duration.subsec_nanos(); assert_ne!(i64::from(nanoseconds), i64::from(UTIME_OMIT)); @@ -33,8 +32,8 @@ pub(crate) fn to_timespec(ft: Option) -> io::Result { pub(crate) fn set_times_nofollow_unchecked( start: &fs::File, path: &Path, - atime: Option, - mtime: Option, + atime: Option, + mtime: Option, ) -> io::Result<()> { let times = Timestamps { last_access: to_timespec(atime)?, @@ -47,8 +46,8 @@ pub(crate) fn set_times_nofollow_unchecked( pub(crate) fn set_times_follow_unchecked( start: BorrowedFd<'_>, path: &Path, - atime: Option, - mtime: Option, + atime: Option, + mtime: Option, ) -> io::Result<()> { let times = Timestamps { last_access: to_timespec(atime)?, diff --git a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/procfs.rs b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/procfs.rs index d7f6ec71a27f..b16f9cd13b25 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/procfs.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/procfs.rs @@ -7,21 +7,20 @@ //! on top of the paths we open. use crate::filesystem::primitives::OpenOptionsExt; -use crate::filesystem::primitives::{ - OpenOptions, SystemTimeSpec, open, set_times_follow_unchecked, -}; +use crate::filesystem::primitives::{OpenOptions, open, set_times_follow_unchecked}; use io_lifetimes::AsFd; use rustix::fs::OFlags; use rustix::path::DecInt; use rustix_linux_procfs::proc_self_fd; use std::path::Path; +use std::time::SystemTime; use std::{fs, io}; pub(crate) fn set_times_through_proc_self_fd( start: &fs::File, path: &Path, - atime: Option, - mtime: Option, + atime: Option, + mtime: Option, ) -> io::Result<()> { let opath = open( start, diff --git a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/set_times_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/set_times_impl.rs index e24cda99ca5d..9372ce23536a 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/set_times_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/set_times_impl.rs @@ -2,26 +2,28 @@ //! with setting the file times specific to Linux. use super::procfs::set_times_through_proc_self_fd; -use crate::filesystem::primitives::{OpenOptions, SystemTimeSpec, open}; +use crate::filesystem::primitives::{OpenOptions, open}; use std::path::Path; +use std::time::SystemTime; use std::{fs, io}; pub(crate) fn set_times_impl( start: &fs::File, path: &Path, - atime: Option, - mtime: Option, + atime: Option, + mtime: Option, ) -> io::Result<()> { + let mut times = fs::FileTimes::new(); + if let Some(atime) = atime { + times = times.set_accessed(atime); + } + if let Some(mtime) = mtime { + times = times.set_modified(mtime); + } // Try `futimens` with a normal handle. Normal handles need some kind of // access, so first try write. match open(start, path, OpenOptions::new().write(true)) { - Ok(file) => { - return fs_set_times::SetTimes::set_times( - &file, - atime.map(SystemTimeSpec::into_std), - mtime.map(SystemTimeSpec::into_std), - ); - } + Ok(file) => return file.set_times(times), Err(err) => match rustix::io::Errno::from_io_error(&err) { Some(rustix::io::Errno::ACCESS) | Some(rustix::io::Errno::ISDIR) => (), _ => return Err(err), @@ -30,13 +32,7 @@ pub(crate) fn set_times_impl( // Next try read. match open(start, path, OpenOptions::new().read(true)) { - Ok(file) => { - return fs_set_times::SetTimes::set_times( - &file, - atime.map(SystemTimeSpec::into_std), - mtime.map(SystemTimeSpec::into_std), - ); - } + Ok(file) => return file.set_times(times), Err(err) => match rustix::io::Errno::from_io_error(&err) { Some(rustix::io::Errno::ACCESS) => (), _ => return Err(err), diff --git a/crates/wasi/src/filesystem/primitives/set_times.rs b/crates/wasi/src/filesystem/primitives/set_times.rs index b3c72f161a56..f1aff11552d8 100644 --- a/crates/wasi/src/filesystem/primitives/set_times.rs +++ b/crates/wasi/src/filesystem/primitives/set_times.rs @@ -3,8 +3,9 @@ //! //! TODO: `check_set_times` etc. -use crate::filesystem::primitives::{SystemTimeSpec, set_times_impl, set_times_nofollow_impl}; +use crate::filesystem::primitives::{set_times_impl, set_times_nofollow_impl}; use std::path::Path; +use std::time::SystemTime; use std::{fs, io}; /// Perform a `utimensat`-like operation, ensuring that the resolution of the @@ -14,8 +15,8 @@ use std::{fs, io}; pub fn set_times( start: &fs::File, path: &Path, - atime: Option, - mtime: Option, + atime: Option, + mtime: Option, ) -> io::Result<()> { set_times_impl(start, path, atime, mtime) } @@ -25,8 +26,8 @@ pub fn set_times( pub fn set_times_nofollow( start: &fs::File, path: &Path, - atime: Option, - mtime: Option, + atime: Option, + mtime: Option, ) -> io::Result<()> { set_times_nofollow_impl(start, path, atime, mtime) } diff --git a/crates/wasi/src/filesystem/primitives/system_time_spec.rs b/crates/wasi/src/filesystem/primitives/system_time_spec.rs deleted file mode 100644 index 7f1553ae1ff9..000000000000 --- a/crates/wasi/src/filesystem/primitives/system_time_spec.rs +++ /dev/null @@ -1,27 +0,0 @@ -use std::time::SystemTime; - -/// A value for specifying a time. -#[derive(Debug)] -#[allow(clippy::module_name_repetitions)] -pub enum SystemTimeSpec { - /// An absolute time value. - Absolute(SystemTime), -} - -impl SystemTimeSpec { - /// Constructs a new instance of [`fs_set_times::SystemTimeSpec`] from the - /// given `Self`. - #[inline] - pub const fn into_std(self) -> fs_set_times::SystemTimeSpec { - match self { - Self::Absolute(time) => fs_set_times::SystemTimeSpec::Absolute(time), - } - } -} - -impl From for SystemTimeSpec { - #[inline] - fn from(time: SystemTime) -> Self { - Self::Absolute(time) - } -} diff --git a/crates/wasi/src/filesystem/primitives/via_parent/set_times_nofollow.rs b/crates/wasi/src/filesystem/primitives/via_parent/set_times_nofollow.rs index f01befd96efd..8a4146e0afd8 100644 --- a/crates/wasi/src/filesystem/primitives/via_parent/set_times_nofollow.rs +++ b/crates/wasi/src/filesystem/primitives/via_parent/set_times_nofollow.rs @@ -1,14 +1,15 @@ use super::open_parent; -use crate::filesystem::primitives::{MaybeOwnedFile, SystemTimeSpec, set_times_nofollow_unchecked}; +use crate::filesystem::primitives::{MaybeOwnedFile, set_times_nofollow_unchecked}; use std::path::Path; +use std::time::SystemTime; use std::{fs, io}; #[inline] pub(crate) fn set_times_nofollow( start: &fs::File, path: &Path, - atime: Option, - mtime: Option, + atime: Option, + mtime: Option, ) -> io::Result<()> { let start = MaybeOwnedFile::borrowed(start); diff --git a/crates/wasi/src/filesystem/primitives/windows/fs/set_times_impl.rs b/crates/wasi/src/filesystem/primitives/windows/fs/set_times_impl.rs index a6166642c054..c0acd4c9cc9b 100644 --- a/crates/wasi/src/filesystem/primitives/windows/fs/set_times_impl.rs +++ b/crates/wasi/src/filesystem/primitives/windows/fs/set_times_impl.rs @@ -1,5 +1,6 @@ -use crate::filesystem::primitives::{OpenOptions, OpenOptionsExt, SystemTimeSpec, open}; +use crate::filesystem::primitives::{OpenOptions, OpenOptionsExt, open}; use std::path::Path; +use std::time::SystemTime; use std::{fs, io}; use windows_sys::Win32::Storage::FileSystem::{ FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, @@ -9,8 +10,8 @@ use windows_sys::Win32::Storage::FileSystem::{ pub(crate) fn set_times_impl( start: &fs::File, path: &Path, - atime: Option, - mtime: Option, + atime: Option, + mtime: Option, ) -> io::Result<()> { set_times_inner(start, path, atime, mtime, 0) } @@ -19,8 +20,8 @@ pub(crate) fn set_times_impl( pub(crate) fn set_times_nofollow_impl( start: &fs::File, path: &Path, - atime: Option, - mtime: Option, + atime: Option, + mtime: Option, ) -> io::Result<()> { set_times_inner(start, path, atime, mtime, FILE_FLAG_OPEN_REPARSE_POINT) } @@ -28,8 +29,8 @@ pub(crate) fn set_times_nofollow_impl( fn set_times_inner( start: &fs::File, path: &Path, - atime: Option, - mtime: Option, + atime: Option, + mtime: Option, custom_flags: u32, ) -> io::Result<()> { let custom_flags = custom_flags | FILE_FLAG_BACKUP_SEMANTICS; @@ -40,9 +41,12 @@ fn set_times_inner( path, OpenOptions::new().write(true).custom_flags(custom_flags), )?; - fs_set_times::SetTimes::set_times( - &file, - atime.map(SystemTimeSpec::into_std), - mtime.map(SystemTimeSpec::into_std), - ) + let mut times = fs::FileTimes::new(); + if let Some(atime) = atime { + times = times.set_accessed(atime); + } + if let Some(mtime) = mtime { + times = times.set_modified(mtime); + } + file.set_times(times) } From 90662cfbd16813e341e4e1aa65c6c23de2c82064 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 27 Aug 2026 14:23:39 -0700 Subject: [PATCH 21/34] Fixing tests --- .../primitives/tests/file_type_ext.rs | 3 +-- .../primitives/tests/helpers/mod.rs | 5 ++-- .../primitives/tests/metadata_ext.rs | 3 +-- .../primitives/tests/paths_containing_nul.rs | 3 +-- .../filesystem/primitives/tests/readdir.rs | 5 ++-- .../filesystem/primitives/tests/set_times.rs | 24 ++++++------------- 6 files changed, 14 insertions(+), 29 deletions(-) diff --git a/crates/wasi/src/filesystem/primitives/tests/file_type_ext.rs b/crates/wasi/src/filesystem/primitives/tests/file_type_ext.rs index f34482f96bf4..d61114e75a6d 100644 --- a/crates/wasi/src/filesystem/primitives/tests/file_type_ext.rs +++ b/crates/wasi/src/filesystem/primitives/tests/file_type_ext.rs @@ -4,13 +4,12 @@ use super::sys_common::io::tmpdir; #[cfg(unix)] use crate::filesystem::primitives::FileTypeExt; use crate::filesystem::primitives::{Metadata, OpenOptions, open, open_ambient_dir}; -use ambient_authority::ambient_authority; use std::path::Path; #[test] fn test_file_type_ext() { let tmpdir = tmpdir(); - let dir = check!(open_ambient_dir(tmpdir.path(), ambient_authority())); + let dir = check!(open_ambient_dir(tmpdir.path(),)); let a = check!(open( &dir, Path::new("a"), diff --git a/crates/wasi/src/filesystem/primitives/tests/helpers/mod.rs b/crates/wasi/src/filesystem/primitives/tests/helpers/mod.rs index a9fdb2ba53f9..bf66e1dd7ba8 100644 --- a/crates/wasi/src/filesystem/primitives/tests/helpers/mod.rs +++ b/crates/wasi/src/filesystem/primitives/tests/helpers/mod.rs @@ -6,7 +6,6 @@ //! only the operations that add options, flags, or logic live here. use crate::filesystem::primitives as p; -use ambient_authority::ambient_authority; use std::fs::File; use std::io; use std::path::{Path, PathBuf}; @@ -14,12 +13,12 @@ use tempfile::TempDir; /// Open a temporary directory as a start-directory handle. pub fn dir_of(t: &TempDir) -> File { - p::open_ambient_dir(t.path(), ambient_authority()).unwrap() + p::open_ambient_dir(t.path()).unwrap() } /// `open_ambient_dir` with the ambient authority supplied. pub fn open_ambient_dir(path: impl AsRef) -> io::Result { - p::open_ambient_dir(path.as_ref(), ambient_authority()) + p::open_ambient_dir(path.as_ref()) } /// `Dir::create`: open for writing, creating and truncating. diff --git a/crates/wasi/src/filesystem/primitives/tests/metadata_ext.rs b/crates/wasi/src/filesystem/primitives/tests/metadata_ext.rs index b5322b747663..6d4fae861f78 100644 --- a/crates/wasi/src/filesystem/primitives/tests/metadata_ext.rs +++ b/crates/wasi/src/filesystem/primitives/tests/metadata_ext.rs @@ -9,13 +9,12 @@ use super::sys_common::symlink_supported; use crate::filesystem::primitives::{ FollowSymlinks, Metadata, MetadataExt, hard_link, open_ambient_dir, stat, }; -use ambient_authority::ambient_authority; use std::path::Path; #[test] fn test_metadata_ext() { let tmpdir = tmpdir(); - let dir = check!(open_ambient_dir(tmpdir.path(), ambient_authority())); + let dir = check!(open_ambient_dir(tmpdir.path(),)); let a = check!(h::create(&dir, "a")); let b = check!(h::create(&dir, "b")); let tmpdir_metadata = check!(Metadata::from_file(&dir)); diff --git a/crates/wasi/src/filesystem/primitives/tests/paths_containing_nul.rs b/crates/wasi/src/filesystem/primitives/tests/paths_containing_nul.rs index 9f7bbd977cc2..a3c95a842b80 100644 --- a/crates/wasi/src/filesystem/primitives/tests/paths_containing_nul.rs +++ b/crates/wasi/src/filesystem/primitives/tests/paths_containing_nul.rs @@ -14,7 +14,6 @@ use crate::filesystem::primitives::{ DirOptions, FollowSymlinks, OpenOptions, create_dir, hard_link, open, open_ambient_dir, read_link, remove_dir, remove_file, rename, stat, }; -use ambient_authority::ambient_authority; use std::io; use std::path::Path; @@ -43,7 +42,7 @@ fn assert_invalid_input(on: &str, result: io::Result) { #[test] fn paths_containing_nul() { let tmpdir = tmpdir(); - let dir = open_ambient_dir(tmpdir.path(), ambient_authority()).unwrap(); + let dir = open_ambient_dir(tmpdir.path()).unwrap(); let nul = Path::new("\0"); assert_invalid_input("open", open(&dir, nul, OpenOptions::new().read(true))); diff --git a/crates/wasi/src/filesystem/primitives/tests/readdir.rs b/crates/wasi/src/filesystem/primitives/tests/readdir.rs index 8dcc5c34d280..ac427bb10634 100644 --- a/crates/wasi/src/filesystem/primitives/tests/readdir.rs +++ b/crates/wasi/src/filesystem/primitives/tests/readdir.rs @@ -1,5 +1,4 @@ use crate::filesystem::primitives::{DirEntry, open_ambient_dir, read_base_dir}; -use ambient_authority::ambient_authority; use std::collections::HashMap; use std::fs::File; use std::path::Path; @@ -39,7 +38,7 @@ fn test_dir_entries() { #[test] fn test_reread_entries() { let tmpdir = tempfile::tempdir().expect("construct tempdir"); - let dir = open_ambient_dir(tmpdir.path(), ambient_authority()).unwrap(); + let dir = open_ambient_dir(tmpdir.path()).unwrap(); let entries = read_entries(&dir); assert_eq!(entries.len(), 0, "empty dir"); @@ -70,7 +69,7 @@ fn test_reread_entries() { } fn dir_entries(path: &Path) -> HashMap { - let dir = open_ambient_dir(path, ambient_authority()).unwrap(); + let dir = open_ambient_dir(path).unwrap(); read_entries(&dir) } diff --git a/crates/wasi/src/filesystem/primitives/tests/set_times.rs b/crates/wasi/src/filesystem/primitives/tests/set_times.rs index 0d4955c18b96..836b8d95fa41 100644 --- a/crates/wasi/src/filesystem/primitives/tests/set_times.rs +++ b/crates/wasi/src/filesystem/primitives/tests/set_times.rs @@ -1,7 +1,7 @@ use super::helpers as h; use super::sys_common::io::tmpdir; use super::sys_common::symlink_supported; -use crate::filesystem::primitives::{Metadata, SystemTimeSpec, set_times, set_times_nofollow}; +use crate::filesystem::primitives::{Metadata, set_times, set_times_nofollow}; use std::path::Path; use std::time::SystemTime; @@ -23,12 +23,7 @@ fn basic_times() { } let file_time = SystemTime::UNIX_EPOCH; - check!(set_times( - &dir, - Path::new("file"), - None, - Some(SystemTimeSpec::from(file_time)) - )); + check!(set_times(&dir, Path::new("file"), None, Some(file_time))); assert_eq!(modified_time(check!(h::metadata(&dir, "file"))), file_time); if test_symlinks { assert_eq!( @@ -38,12 +33,7 @@ fn basic_times() { } let dir_time = SystemTime::UNIX_EPOCH; - check!(set_times( - &dir, - Path::new("dir"), - None, - Some(SystemTimeSpec::from(dir_time)) - )); + check!(set_times(&dir, Path::new("dir"), None, Some(dir_time))); assert_eq!(modified_time(check!(h::metadata(&dir, "dir"))), dir_time); if test_symlinks { assert_eq!( @@ -71,7 +61,7 @@ fn symlink_times() { &dir, Path::new("file_symlink_file"), None, - Some(SystemTimeSpec::from(file_time)) + Some(file_time) )); assert_eq!(modified_time(check!(h::metadata(&dir, "file"))), file_time); assert_eq!( @@ -92,7 +82,7 @@ fn symlink_times() { &dir, Path::new("dir_symlink_dir"), None, - Some(SystemTimeSpec::from(file_time)) + Some(file_time) )); assert_eq!(modified_time(check!(h::metadata(&dir, "dir"))), dir_time); assert_eq!( @@ -127,7 +117,7 @@ fn symlink_itself_times() { &dir, Path::new("file_symlink_file"), None, - Some(SystemTimeSpec::from(file_time)) + Some(file_time) )); assert_ne!(modified_time(check!(h::metadata(&dir, "file"))), file_time); assert_ne!( @@ -148,7 +138,7 @@ fn symlink_itself_times() { &dir, Path::new("dir_symlink_dir"), None, - Some(SystemTimeSpec::from(file_time)) + Some(file_time) )); assert_ne!(modified_time(check!(h::metadata(&dir, "dir"))), dir_time); assert_ne!( From 518af059e26117676c4686626cf63266ccbd40de Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 27 Aug 2026 14:25:40 -0700 Subject: [PATCH 22/34] Remove io-extras dependency --- Cargo.lock | 1 - crates/wasi/Cargo.toml | 3 +-- .../wasi/src/filesystem/primitives/rustix/fs/read_dir_inner.rs | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7ef301248685..9393288307f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5169,7 +5169,6 @@ dependencies = [ "cap-primitives", "env_logger 0.11.5", "futures", - "io-extras", "io-lifetimes 3.0.1", "maybe-owned", "rand 0.10.1", diff --git a/crates/wasi/Cargo.toml b/crates/wasi/Cargo.toml index d8aa546a9f16..dcb2171b9a81 100644 --- a/crates/wasi/Cargo.toml +++ b/crates/wasi/Cargo.toml @@ -35,9 +35,7 @@ url = { workspace = true } rand = { workspace = true, features = ['std_rng', 'thread_rng'] } maybe-owned = "0.3" -io-extras = "0.19.0" io-lifetimes = { version = "3.0.1", default-features = false } -winx = "0.36.0" [dev-dependencies] tokio = { workspace = true, features = ["time", "sync", "io-std", "io-util", "rt", "rt-multi-thread", "net", "macros", "fs"] } @@ -55,6 +53,7 @@ rustix-linux-procfs = "0.1.1" [target.'cfg(windows)'.dependencies] rustix = { workspace = true, features = ["event", "net"] } +winx = "0.36.0" [target.'cfg(windows)'.dependencies.windows-sys] workspace = true diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/read_dir_inner.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/read_dir_inner.rs index fd8601bfef3f..5355f34bde9c 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/read_dir_inner.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/read_dir_inner.rs @@ -1,12 +1,12 @@ use crate::filesystem::primitives::{ DirEntryInner, FollowSymlinks, Metadata, open_dir_for_reading_unchecked, stat_unchecked, }; -use io_extras::os::rustix::{AsRawFd, FromRawFd, RawFd}; use io_lifetimes::AsFd; use rustix::fd::OwnedFd; use rustix::fs::Dir; use std::ffi::OsStr; use std::mem::ManuallyDrop; +use std::os::fd::{AsRawFd, FromRawFd, RawFd}; #[cfg(unix)] use std::os::unix::ffi::OsStrExt; #[cfg(target_os = "wasi")] From 975639254dc815e5bdc43e9dfb861e694c0e5f30 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 27 Aug 2026 14:30:19 -0700 Subject: [PATCH 23/34] Remove io-lifetimes dependency Also remove unused cap-fs-ext dependency --- Cargo.lock | 26 ------------------- crates/wasi/Cargo.toml | 2 -- .../primitives/rustix/fs/read_dir_inner.rs | 3 +-- .../filesystem/primitives/rustix/fs/times.rs | 2 +- .../primitives/rustix/linux/fs/open_impl.rs | 3 +-- .../primitives/rustix/linux/fs/procfs.rs | 2 +- 6 files changed, 4 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9393288307f7..eee788187f3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -346,18 +346,6 @@ dependencies = [ "serde", ] -[[package]] -name = "cap-fs-ext" -version = "4.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ff379b70af8e08307a8f65e7040c7301cb4a572538ade16b4984f0da77847f" -dependencies = [ - "cap-primitives", - "cap-std", - "io-lifetimes 3.0.1", - "windows-sys 0.61.2", -] - [[package]] name = "cap-primitives" version = "4.0.3" @@ -376,18 +364,6 @@ dependencies = [ "winx", ] -[[package]] -name = "cap-std" -version = "4.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ec78e242cfa2cfe276807ac2ecc00315a6c97786977414bcd1c3963b6c91b8" -dependencies = [ - "cap-primitives", - "io-extras", - "io-lifetimes 3.0.1", - "rustix 1.1.4", -] - [[package]] name = "capstone" version = "0.14.0" @@ -5165,11 +5141,9 @@ dependencies = [ "async-trait", "bitflags 2.11.1", "bytes", - "cap-fs-ext", "cap-primitives", "env_logger 0.11.5", "futures", - "io-lifetimes 3.0.1", "maybe-owned", "rand 0.10.1", "rustix 1.1.4", diff --git a/crates/wasi/Cargo.toml b/crates/wasi/Cargo.toml index dcb2171b9a81..82f1cc408df4 100644 --- a/crates/wasi/Cargo.toml +++ b/crates/wasi/Cargo.toml @@ -27,7 +27,6 @@ bytes = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true, features = ["std", "attributes"] } public-cap-primitives = { version = "4.0.3", package = 'cap-primitives' } -public-cap-fs-ext = { version = "4.0.3", package = 'cap-fs-ext' } bitflags = { workspace = true } async-trait = { workspace = true } futures = { workspace = true } @@ -35,7 +34,6 @@ url = { workspace = true } rand = { workspace = true, features = ['std_rng', 'thread_rng'] } maybe-owned = "0.3" -io-lifetimes = { version = "3.0.1", default-features = false } [dev-dependencies] tokio = { workspace = true, features = ["time", "sync", "io-std", "io-util", "rt", "rt-multi-thread", "net", "macros", "fs"] } diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/read_dir_inner.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/read_dir_inner.rs index 5355f34bde9c..558189b2c990 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/read_dir_inner.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/read_dir_inner.rs @@ -1,8 +1,7 @@ use crate::filesystem::primitives::{ DirEntryInner, FollowSymlinks, Metadata, open_dir_for_reading_unchecked, stat_unchecked, }; -use io_lifetimes::AsFd; -use rustix::fd::OwnedFd; +use rustix::fd::{AsFd, OwnedFd}; use rustix::fs::Dir; use std::ffi::OsStr; use std::mem::ManuallyDrop; diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/times.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/times.rs index ec9bf3ea090e..b3253e94c420 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/times.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/times.rs @@ -1,6 +1,6 @@ -use io_lifetimes::BorrowedFd; use rustix::fs::{AtFlags, Timestamps, UTIME_NOW, UTIME_OMIT, utimensat}; use rustix::time::Timespec; +use std::os::fd::BorrowedFd; use std::path::Path; use std::time::SystemTime; use std::{fs, io}; diff --git a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/open_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/open_impl.rs index cdfed1fddda3..0cd20cdde9e8 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/open_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/open_impl.rs @@ -14,7 +14,6 @@ use std::{fs, io}; use { super::super::super::fs::compute_oflags, crate::filesystem::primitives::errors, - io_lifetimes::FromFd, rustix::fs::{Mode, OFlags, RawMode, ResolveFlags, openat2}, rustix::path::Arg, std::sync::atomic::AtomicBool, @@ -88,7 +87,7 @@ pub(crate) fn open_beneath( ResolveFlags::BENEATH | ResolveFlags::NO_MAGICLINKS, ) { Ok(file) => { - let file = fs::File::from_into_fd(file); + let file = fs::File::from(file); return Ok(file); } diff --git a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/procfs.rs b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/procfs.rs index b16f9cd13b25..3fba32fadf3e 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/linux/fs/procfs.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/linux/fs/procfs.rs @@ -8,7 +8,7 @@ use crate::filesystem::primitives::OpenOptionsExt; use crate::filesystem::primitives::{OpenOptions, open, set_times_follow_unchecked}; -use io_lifetimes::AsFd; +use rustix::fd::AsFd; use rustix::fs::OFlags; use rustix::path::DecInt; use rustix_linux_procfs::proc_self_fd; From a3d79a0fbee48f1c82926949266faa4e1c9c81c3 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 27 Aug 2026 14:32:46 -0700 Subject: [PATCH 24/34] Remove the maybe-owned dependency --- Cargo.lock | 1 - crates/wasi/Cargo.toml | 2 - .../filesystem/primitives/maybe_owned_file.rs | 45 ++++++------------- 3 files changed, 13 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eee788187f3a..182b5286a4b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5144,7 +5144,6 @@ dependencies = [ "cap-primitives", "env_logger 0.11.5", "futures", - "maybe-owned", "rand 0.10.1", "rustix 1.1.4", "rustix-linux-procfs", diff --git a/crates/wasi/Cargo.toml b/crates/wasi/Cargo.toml index 82f1cc408df4..fcf250429fd5 100644 --- a/crates/wasi/Cargo.toml +++ b/crates/wasi/Cargo.toml @@ -33,8 +33,6 @@ futures = { workspace = true } url = { workspace = true } rand = { workspace = true, features = ['std_rng', 'thread_rng'] } -maybe-owned = "0.3" - [dev-dependencies] tokio = { workspace = true, features = ["time", "sync", "io-std", "io-util", "rt", "rt-multi-thread", "net", "macros", "fs"] } test-log = { workspace = true } diff --git a/crates/wasi/src/filesystem/primitives/maybe_owned_file.rs b/crates/wasi/src/filesystem/primitives/maybe_owned_file.rs index 2f21b90ba5c6..aba86dd0c9d4 100644 --- a/crates/wasi/src/filesystem/primitives/maybe_owned_file.rs +++ b/crates/wasi/src/filesystem/primitives/maybe_owned_file.rs @@ -1,5 +1,4 @@ use crate::filesystem::primitives::{OpenOptions, open_unchecked}; -use maybe_owned::MaybeOwned; use std::ops::Deref; use std::path::Component; use std::{fmt, fs, io, mem}; @@ -18,57 +17,36 @@ use std::{fmt, fs, io, mem}; /// /// [`Deref`]: std::ops::Deref /// [`Cow`]: std::borrow::Cow -pub(super) struct MaybeOwnedFile<'borrow> { - inner: MaybeOwned<'borrow, fs::File>, +pub(super) enum MaybeOwnedFile<'borrow> { + Borrowed(&'borrow fs::File), + Owned(fs::File), } impl<'borrow> MaybeOwnedFile<'borrow> { /// Constructs a new `MaybeOwnedFile` which is not owned. pub(super) fn borrowed(file: &'borrow fs::File) -> Self { - Self { - inner: MaybeOwned::Borrowed(file), - } + Self::Borrowed(file) } /// Constructs a new `MaybeOwnedFile` which is owned. pub(super) fn owned(file: fs::File) -> Self { - Self { - inner: MaybeOwned::Owned(file), - } - } - - /// Like `borrowed` but does not do path checks. - #[allow(dead_code)] - pub(super) const fn borrowed_noassert(file: &'borrow fs::File) -> Self { - Self { - inner: MaybeOwned::Borrowed(file), - } - } - - /// Like `owned` but does not do path checks. - #[allow(dead_code)] - pub(super) const fn owned_noassert(file: fs::File) -> Self { - Self { - inner: MaybeOwned::Owned(file), - } + Self::Owned(file) } /// Set this `MaybeOwnedFile` to a new owned file which is from a subtree /// of the current file. Return a `MaybeOwnedFile` representing the /// previous state. pub(super) fn descend_to(&mut self, to: MaybeOwnedFile<'borrow>) -> Self { - Self { - inner: mem::replace(&mut self.inner, to.inner), - } + mem::replace(self, to) } /// Produce an owned `File`. This uses `open` on "." if needed to convert a /// borrowed `File` to an owned one. #[cfg_attr(windows, allow(dead_code))] pub(super) fn into_file(self, options: &OpenOptions) -> io::Result { - match self.inner { - MaybeOwned::Owned(file) => Ok(file), - MaybeOwned::Borrowed(file) => { + match self { + Self::Owned(file) => Ok(file), + Self::Borrowed(file) => { // The only situation in which we'd be asked to produce an owned // `File` is when there's a need to open "." within a directory // to obtain a new handle. @@ -83,7 +61,10 @@ impl<'borrow> Deref for MaybeOwnedFile<'borrow> { #[inline] fn deref(&self) -> &Self::Target { - self.inner.as_ref() + match self { + Self::Borrowed(file) => file, + Self::Owned(file) => file, + } } } From 01c859645c046c59b8f4578df54757c38e1f9c16 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 27 Aug 2026 14:33:15 -0700 Subject: [PATCH 25/34] Update vets prtest:full --- supply-chain/imports.lock | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index 2f8b2855d62e..5532c77ffa05 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -340,13 +340,6 @@ user-id = 1 user-login = "alexcrichton" user-name = "Alex Crichton" -[[publisher.cap-fs-ext]] -version = "4.0.3" -when = "2026-08-20" -user-id = 6825 -user-login = "sunfishcode" -user-name = "Dan Gohman" - [[publisher.cap-primitives]] version = "4.0.3" when = "2026-08-20" @@ -354,13 +347,6 @@ user-id = 6825 user-login = "sunfishcode" user-name = "Dan Gohman" -[[publisher.cap-std]] -version = "4.0.3" -when = "2026-08-20" -user-id = 6825 -user-login = "sunfishcode" -user-name = "Dan Gohman" - [[publisher.cc]] version = "1.0.89" when = "2024-03-04" From a57075a3d144a7a262d861775884ba7e5bff7434 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 27 Aug 2026 15:20:23 -0700 Subject: [PATCH 26/34] Fix build on freebsd/macos --- .../primitives/file_path_by_searching.rs | 52 ------------------- crates/wasi/src/filesystem/primitives/mod.rs | 4 -- .../src/filesystem/primitives/open_options.rs | 8 +-- .../primitives/rustix/darwin/fs/file_path.rs | 21 -------- .../primitives/rustix/darwin/fs/mod.rs | 3 -- .../primitives/rustix/darwin/mod.rs | 1 - .../primitives/rustix/freebsd/fs/mod.rs | 5 -- .../rustix/freebsd/fs/open_entry_impl.rs | 12 ----- .../primitives/rustix/freebsd/fs/open_impl.rs | 3 +- .../rustix/freebsd/fs/set_permissions_impl.rs | 21 -------- .../rustix/freebsd/fs/set_times_impl.rs | 11 ++-- .../primitives/rustix/fs/file_path.rs | 19 ------- .../filesystem/primitives/rustix/fs/mod.rs | 41 --------------- .../primitives/rustix/fs/open_options_ext.rs | 1 + .../rustix/fs/set_permissions_impl.rs | 49 ----------------- .../primitives/rustix/fs/set_times_impl.rs | 30 +++++------ .../src/filesystem/primitives/rustix/mod.rs | 8 --- 17 files changed, 25 insertions(+), 264 deletions(-) delete mode 100644 crates/wasi/src/filesystem/primitives/file_path_by_searching.rs delete mode 100644 crates/wasi/src/filesystem/primitives/rustix/darwin/fs/file_path.rs delete mode 100644 crates/wasi/src/filesystem/primitives/rustix/darwin/fs/mod.rs delete mode 100644 crates/wasi/src/filesystem/primitives/rustix/darwin/mod.rs delete mode 100644 crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/open_entry_impl.rs delete mode 100644 crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/set_permissions_impl.rs delete mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/file_path.rs delete mode 100644 crates/wasi/src/filesystem/primitives/rustix/fs/set_permissions_impl.rs diff --git a/crates/wasi/src/filesystem/primitives/file_path_by_searching.rs b/crates/wasi/src/filesystem/primitives/file_path_by_searching.rs deleted file mode 100644 index 2f2a45433e38..000000000000 --- a/crates/wasi/src/filesystem/primitives/file_path_by_searching.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::filesystem::primitives::{ - FollowSymlinks, MaybeOwnedFile, Metadata, is_root_dir, open_dir_unchecked, read_dir_unchecked, -}; -use std::fs; -use std::path::{Component, PathBuf}; - -/// Implementation of `file_path` for directories by opening `..` and searching -/// for a directory among `..`'s children to find its name. -pub(crate) fn file_path_by_searching(file: &fs::File) -> Option { - // Use the `_noassert` functions because the asserts depend on `file_path`, - // which is what we're implementing here. - let mut base = MaybeOwnedFile::borrowed_noassert(file); - let mut components = Vec::new(); - - // Iterate with `..` until we reach the root directory. - 'next_component: loop { - // Open `..`. - let mut iter = - read_dir_unchecked(&base, Component::ParentDir.as_ref(), FollowSymlinks::No).ok()?; - let metadata = Metadata::from_file(&*base).ok()?; - - // Search the children until we find one with matching metadata, and - // then record its name. - while let Some(child) = iter.next() { - let child = child.ok()?; - if child.is_same_file(&metadata).ok()? { - // Found a match. Record the name and continue to the next component. - components.push(child.file_name()); - base = MaybeOwnedFile::owned_noassert( - open_dir_unchecked(&base, Component::ParentDir.as_ref()).ok()?, - ); - continue 'next_component; - } - } - - // We didn't find the directory among its parent's children. If we're at - // the root directory, we're done. - if is_root_dir(&base, &iter).ok()? { - break; - } - - // Otherwise, something went wrong and we can't determine the path. - return None; - } - - let mut path = PathBuf::new(); - path.push(Component::RootDir); - for component in components.iter().rev() { - path.push(component); - } - Some(path) -} diff --git a/crates/wasi/src/filesystem/primitives/mod.rs b/crates/wasi/src/filesystem/primitives/mod.rs index f88c807fd569..6ce84fb66ef9 100644 --- a/crates/wasi/src/filesystem/primitives/mod.rs +++ b/crates/wasi/src/filesystem/primitives/mod.rs @@ -20,8 +20,6 @@ mod create_dir; mod dir_entry; mod dir_options; -#[cfg(not(any(target_os = "android", target_os = "linux", windows)))] -mod file_path_by_searching; mod file_type; mod follow_symlinks; mod hard_link; @@ -46,8 +44,6 @@ pub(crate) mod via_parent; use maybe_owned_file::MaybeOwnedFile; -#[cfg(not(any(target_os = "android", target_os = "linux", windows)))] -pub(crate) use file_path_by_searching::file_path_by_searching; pub(crate) use open_unchecked_error::*; #[cfg(not(windows))] diff --git a/crates/wasi/src/filesystem/primitives/open_options.rs b/crates/wasi/src/filesystem/primitives/open_options.rs index 30db6a6c5d9e..2ee237507100 100644 --- a/crates/wasi/src/filesystem/primitives/open_options.rs +++ b/crates/wasi/src/filesystem/primitives/open_options.rs @@ -28,7 +28,7 @@ pub struct OpenOptions { pub(crate) maybe_dir: bool, pub(crate) sync: bool, pub(crate) dsync: bool, - #[cfg(not(windows))] + #[cfg(target_os = "linux")] pub(crate) rsync: bool, #[cfg(not(windows))] pub(crate) nonblock: bool, @@ -58,7 +58,7 @@ impl OpenOptions { maybe_dir: false, sync: false, dsync: false, - #[cfg(not(windows))] + #[cfg(target_os = "linux")] rsync: false, #[cfg(not(windows))] nonblock: false, @@ -150,7 +150,7 @@ impl OpenOptions { } /// Unix-specific extensions to [`fs::OpenOptions`]. -#[cfg(unix)] +#[cfg(target_os = "linux")] pub trait OpenOptionsExt { /// Pass custom flags to the `flags` argument of `open`. fn custom_flags(&mut self, flags: i32) -> &mut Self; @@ -218,7 +218,7 @@ pub trait OpenOptionsExt { fn custom_flags(&mut self, flags: u32) -> &mut Self; } -#[cfg(unix)] +#[cfg(target_os = "linux")] impl OpenOptionsExt for OpenOptions { #[inline] fn custom_flags(&mut self, flags: i32) -> &mut Self { diff --git a/crates/wasi/src/filesystem/primitives/rustix/darwin/fs/file_path.rs b/crates/wasi/src/filesystem/primitives/rustix/darwin/fs/file_path.rs deleted file mode 100644 index 7fbb3b954a35..000000000000 --- a/crates/wasi/src/filesystem/primitives/rustix/darwin/fs/file_path.rs +++ /dev/null @@ -1,21 +0,0 @@ -//! `get_path` translation code for macOS derived from Rust's -//! library/std/src/sys/unix/fs.rs at revision -//! 108e90ca78f052c0c1c49c42a22c85620be19712. - -use crate::filesystem::primitives::rustix::fs::file_path_by_ttyname_or_seaching; -use rustix::fs::getpath; -use std::ffi::OsString; -use std::fs; -#[cfg(unix)] -use std::os::unix::ffi::OsStringExt; -#[cfg(target_os = "wasi")] -use std::os::wasi::ffi::OsStringExt; -use std::path::PathBuf; - -pub(crate) fn file_path(file: &fs::File) -> Option { - if let Ok(path) = getpath(file) { - return Some(OsString::from_vec(path.into_bytes()).into()); - } - - file_path_by_ttyname_or_seaching(file) -} diff --git a/crates/wasi/src/filesystem/primitives/rustix/darwin/fs/mod.rs b/crates/wasi/src/filesystem/primitives/rustix/darwin/fs/mod.rs deleted file mode 100644 index 32e3eec364bd..000000000000 --- a/crates/wasi/src/filesystem/primitives/rustix/darwin/fs/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod file_path; - -pub(crate) use file_path::*; diff --git a/crates/wasi/src/filesystem/primitives/rustix/darwin/mod.rs b/crates/wasi/src/filesystem/primitives/rustix/darwin/mod.rs deleted file mode 100644 index aabc2afc5ad4..000000000000 --- a/crates/wasi/src/filesystem/primitives/rustix/darwin/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub(crate) mod fs; diff --git a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/mod.rs b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/mod.rs index 23863313652e..dd8fcfc5472b 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/mod.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/mod.rs @@ -1,18 +1,13 @@ mod check; -mod open_entry_impl; mod open_impl; mod remove_dir_impl; mod remove_file_impl; -mod set_permissions_impl; mod set_times_impl; mod stat_impl; -pub(crate) use crate::filesystem::primitives::manually::canonicalize as canonicalize_impl; pub(crate) use check::beneath_supported; -pub(crate) use open_entry_impl::open_entry_impl; pub(crate) use open_impl::open_impl; pub(crate) use remove_dir_impl::remove_dir_impl; pub(crate) use remove_file_impl::remove_file_impl; -pub(crate) use set_permissions_impl::set_permissions_impl; pub(crate) use set_times_impl::{set_times_impl, set_times_nofollow_impl}; pub(crate) use stat_impl::stat_impl; diff --git a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/open_entry_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/open_entry_impl.rs deleted file mode 100644 index dd314e40d7f5..000000000000 --- a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/open_entry_impl.rs +++ /dev/null @@ -1,12 +0,0 @@ -use crate::filesystem::primitives::{OpenOptions, open_impl}; -use std::ffi::OsStr; -use std::{fs, io}; - -#[inline(always)] -pub(crate) fn open_entry_impl( - start: &fs::File, - path: &OsStr, - options: &OpenOptions, -) -> io::Result { - open_impl(start, path.as_ref(), options) -} diff --git a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/open_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/open_impl.rs index 2d8fe8dfcd29..d29ce5b9f198 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/open_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/open_impl.rs @@ -1,6 +1,5 @@ use super::super::super::fs::compute_oflags; use crate::filesystem::primitives::{OpenOptions, errors, manually}; -use io_lifetimes::FromFd; use rustix::fs::{Mode, OFlags, RawMode, openat}; use std::path::Path; use std::{fs, io}; @@ -23,7 +22,7 @@ pub(crate) fn open_impl( }; match openat(start, path, oflags, mode) { - Ok(file) => Ok(fs::File::from_into_fd(file)), + Ok(file) => Ok(file.into()), Err(rustix::io::Errno::NOTCAPABLE) => Err(errors::escape_attempt()), Err(err) => Err(err.into()), } diff --git a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/set_permissions_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/set_permissions_impl.rs deleted file mode 100644 index 22d20f9c1791..000000000000 --- a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/set_permissions_impl.rs +++ /dev/null @@ -1,21 +0,0 @@ -use crate::filesystem::primitives::{Permissions, PermissionsExt}; -use rustix::fs::{AtFlags, Mode, chmodat}; -use std::path::Path; -use std::{fs, io}; - -pub(crate) fn set_permissions_impl( - start: &fs::File, - path: &Path, - perm: Permissions, -) -> io::Result<()> { - if !super::beneath_supported() { - return super::super::super::fs::set_permissions_manually(start, path, perm); - } - - Ok(chmodat( - start, - path, - Mode::from_raw_mode(perm.mode() as _), - AtFlags::RESOLVE_BENEATH, - )?) -} diff --git a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/set_times_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/set_times_impl.rs index f318d42ad699..d11303eb1169 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/set_times_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/freebsd/fs/set_times_impl.rs @@ -1,13 +1,14 @@ -use crate::filesystem::primitives::{SystemTimeSpec, to_timespec, via_parent}; +use crate::filesystem::primitives::{to_timespec, via_parent}; use rustix::fs::{AtFlags, Timestamps, utimensat}; use std::path::Path; +use std::time::SystemTime; use std::{fs, io}; pub(crate) fn set_times_impl( start: &fs::File, path: &Path, - atime: Option, - mtime: Option, + atime: Option, + mtime: Option, ) -> io::Result<()> { if !super::beneath_supported() { return super::super::super::fs::set_times_manually(start, path, atime, mtime); @@ -24,8 +25,8 @@ pub(crate) fn set_times_impl( pub(crate) fn set_times_nofollow_impl( start: &fs::File, path: &Path, - atime: Option, - mtime: Option, + atime: Option, + mtime: Option, ) -> io::Result<()> { if !super::beneath_supported() { return via_parent::set_times_nofollow(start, path, atime, mtime); diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/file_path.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/file_path.rs deleted file mode 100644 index 6d4f8a4764e3..000000000000 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/file_path.rs +++ /dev/null @@ -1,19 +0,0 @@ -use crate::filesystem::primitives::file_path_by_searching; -#[cfg(not(any(target_os = "wasi", target_os = "fuchsia")))] -use rustix::termios::ttyname; -#[cfg(not(any(target_os = "wasi", target_os = "fuchsia")))] -use std::ffi::OsString; -use std::fs; -#[cfg(unix)] -use std::os::unix::ffi::OsStringExt; -use std::path::PathBuf; - -pub(crate) fn file_path_by_ttyname_or_seaching(file: &fs::File) -> Option { - // If it happens to be a tty, we can look up its name. - #[cfg(not(any(target_os = "wasi", target_os = "fuchsia")))] - if let Ok(name) = ttyname(file, Vec::new()) { - return Some(OsString::from_vec(name.into_bytes()).into()); - } - - file_path_by_searching(file) -} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/mod.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/mod.rs index 4e336d2f1c82..df34362c9417 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/mod.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/mod.rs @@ -3,8 +3,6 @@ mod dir_entry_inner; #[cfg(not(target_os = "wasi"))] mod dir_options_ext; mod dir_utils; -#[cfg(not(any(target_os = "android", target_os = "linux")))] -mod file_path; mod file_type_ext; mod hard_link_unchecked; mod is_same_file; @@ -17,8 +15,6 @@ mod read_link_unchecked; mod remove_dir_unchecked; mod remove_file_unchecked; mod rename_unchecked; -#[cfg(not(any(target_os = "android", target_os = "linux", target_os = "wasi")))] -mod set_permissions_impl; #[cfg(not(any(target_os = "android", target_os = "linux")))] mod set_times_impl; mod stat_unchecked; @@ -32,14 +28,6 @@ pub(crate) mod errors; // // On FreeBSD, use optimized implementations based on // `O_RESOLVE_BENEATH`/`AT_RESOLVE_BENEATH` and `O_PATH` when available. -#[cfg(any( - target_os = "macos", - target_os = "ios", - target_os = "tvos", - target_os = "watchos", - target_os = "visionos", -))] -pub(crate) use crate::filesystem::primitives::rustix::darwin::fs::*; #[cfg(target_os = "freebsd")] pub(crate) use crate::filesystem::primitives::rustix::freebsd::fs::*; #[cfg(any(target_os = "android", target_os = "linux"))] @@ -47,39 +35,10 @@ pub(crate) use crate::filesystem::primitives::rustix::linux::fs::*; #[cfg(not(any(target_os = "android", target_os = "linux", target_os = "freebsd")))] #[rustfmt::skip] pub(crate) use crate::filesystem::primitives::{ - manually::open_entry as open_entry_impl, manually::open as open_impl, manually::stat as stat_impl, - manually::canonicalize as canonicalize_impl, via_parent::set_times_nofollow as set_times_nofollow_impl, }; -#[cfg(any( - target_os = "macos", - target_os = "ios", - target_os = "tvos", - target_os = "watchos", - target_os = "visionos", -))] -pub(super) use file_path::file_path_by_ttyname_or_seaching; -#[cfg(not(any( - target_os = "android", - target_os = "linux", - target_os = "macos", - target_os = "ios", - target_os = "tvos", - target_os = "watchos", - target_os = "visionos", -)))] -pub(crate) use file_path::file_path_by_ttyname_or_seaching as file_path; -#[cfg(not(any( - target_os = "android", - target_os = "linux", - target_os = "freebsd", - target_os = "wasi" -)))] -pub(crate) use set_permissions_impl::set_permissions_impl; -#[cfg(target_os = "freebsd")] -pub(crate) use set_permissions_impl::set_permissions_impl as set_permissions_manually; #[cfg(not(any(target_os = "android", target_os = "linux", target_os = "freebsd")))] pub(crate) use set_times_impl::set_times_impl; #[cfg(target_os = "freebsd")] diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/open_options_ext.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/open_options_ext.rs index 08402cb0ca69..cb43bdf73b55 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/open_options_ext.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/open_options_ext.rs @@ -12,6 +12,7 @@ impl ImplOpenOptionsExt { } } + #[cfg(target_os = "linux")] pub(crate) fn custom_flags(&mut self, flags: i32) -> &mut Self { self.custom_flags = flags; self diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/set_permissions_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/set_permissions_impl.rs deleted file mode 100644 index ab3d8ede015f..000000000000 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/set_permissions_impl.rs +++ /dev/null @@ -1,49 +0,0 @@ -use crate::filesystem::primitives::{OpenOptions, Permissions, open}; -use rustix::fs::{Mode, fchmod}; -use rustix::io::Errno; -#[cfg(unix)] -use std::os::unix::fs::PermissionsExt; -use std::path::Path; -use std::{fs, io}; - -/// This sounds like it should be a job for `fchmodat`, however `fchmodat` -/// handles symlinks in an incompatible way. It either follows symlinks -/// without guaranteeing to stay in the sandbox, or with `AT_SYMLINK_NOFOLLOW` -/// it attempts to change the permissions of symlinks themselves. What we'd -/// need is for it to fail if it encounters a symlink, like `O_NOFOLLOW` does. -pub(crate) fn set_permissions_impl( - start: &fs::File, - path: &Path, - perm: Permissions, -) -> io::Result<()> { - let std_perm = perm.into_std(start)?; - - // Try `fchmod` with a normal handle. Normal handles need some kind of - // access, so first try read. - match open(start, path, OpenOptions::new().read(true)) { - Ok(file) => return set_file_permissions(&file, std_perm), - Err(err) => match Errno::from_io_error(&err) { - Some(Errno::ACCESS) => (), - _ => return Err(err), - }, - } - - // Next try write. - match open(start, path, OpenOptions::new().write(true)) { - Ok(file) => return set_file_permissions(&file, std_perm), - Err(err) => match Errno::from_io_error(&err) { - Some(Errno::ACCESS) | Some(Errno::ISDIR) => (), - _ => return Err(err), - }, - } - - // If neither of those worked, we're out of luck. - Err(Errno::NOTSUP.into()) -} - -pub(crate) fn set_file_permissions(file: &fs::File, perm: fs::Permissions) -> io::Result<()> { - // Use `from_bits_truncate` for compatibility with std, which allows - // non-permission bits to propagate through. - let mode = Mode::from_bits_truncate(perm.mode().try_into().unwrap()); - Ok(fchmod(file, mode)?) -} diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/set_times_impl.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/set_times_impl.rs index 183534396159..4e7363c50d9c 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/set_times_impl.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/set_times_impl.rs @@ -1,27 +1,29 @@ //! This module consists of helper types and functions for dealing //! with setting the file times. -use crate::filesystem::primitives::{OpenOptions, SystemTimeSpec, open}; +use crate::filesystem::primitives::{OpenOptions, open}; use rustix::io::Errno; use std::path::Path; +use std::time::SystemTime; use std::{fs, io}; pub(crate) fn set_times_impl( start: &fs::File, path: &Path, - atime: Option, - mtime: Option, + atime: Option, + mtime: Option, ) -> io::Result<()> { + let mut times = fs::FileTimes::new(); + if let Some(atime) = atime { + times = times.set_accessed(atime); + } + if let Some(mtime) = mtime { + times = times.set_modified(mtime); + } // Try `futimens` with a normal handle. Normal handles need some kind of // access, so first try write. match open(start, path, OpenOptions::new().write(true)) { - Ok(file) => { - return fs_set_times::SetTimes::set_times( - &file, - atime.map(SystemTimeSpec::into_std), - mtime.map(SystemTimeSpec::into_std), - ); - } + Ok(file) => return fs::File::from(file).set_times(times), Err(err) => match Errno::from_io_error(&err) { Some(Errno::ACCESS) | Some(Errno::ISDIR) => (), _ => return Err(err), @@ -30,13 +32,7 @@ pub(crate) fn set_times_impl( // Next try read. match open(start, path, OpenOptions::new().read(true)) { - Ok(file) => { - return fs_set_times::SetTimes::set_times( - &file, - atime.map(SystemTimeSpec::into_std), - mtime.map(SystemTimeSpec::into_std), - ); - } + Ok(file) => return fs::File::from(file).set_times(times), Err(err) => match Errno::from_io_error(&err) { Some(Errno::ACCESS) => (), _ => return Err(err), diff --git a/crates/wasi/src/filesystem/primitives/rustix/mod.rs b/crates/wasi/src/filesystem/primitives/rustix/mod.rs index 0c0fd2437257..94e6b2f297db 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/mod.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/mod.rs @@ -3,14 +3,6 @@ pub(crate) mod fs; -#[cfg(any( - target_os = "macos", - target_os = "ios", - target_os = "tvos", - target_os = "watchos", - target_os = "visionos", -))] -mod darwin; #[cfg(target_os = "freebsd")] mod freebsd; #[cfg(any(target_os = "android", target_os = "linux"))] From 3d6dc6425fa97e6a32ac4ebdb7e1b144faf06688 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 27 Aug 2026 15:23:17 -0700 Subject: [PATCH 27/34] Update wasi crate blurb --- crates/wasi/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/wasi/src/lib.rs b/crates/wasi/src/lib.rs index 756b67906206..005d8155d5eb 100644 --- a/crates/wasi/src/lib.rs +++ b/crates/wasi/src/lib.rs @@ -2,10 +2,10 @@ //! # Wasmtime's WASI Implementation //! -//! This crate provides a Wasmtime host implementations of different versions of WASI. -//! WASI is implemented with the Rust crates [`tokio`] and -//! [`cap-primitives`](cap_primitives) primarily, meaning that operations are -//! implemented in terms of their native platform equivalents by default. +//! This crate provides a Wasmtime host implementations of different versions of +//! WASI. WASI is implemented with the Rust crate [`tokio`] combined with custom +//! implementations in this crate, and operations are implemented in terms of +//! their native platform equivalents by default. //! //! For components and WASIp2, see [`p2`]. //! For WASIp1 and core modules, see the [`p1`] module documentation. From 4fc0aa6114b7bcf0f678487f719f369f96f9f1c0 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 27 Aug 2026 15:25:03 -0700 Subject: [PATCH 28/34] Fix android build --- crates/wasi/src/filesystem/primitives/open_options.rs | 8 ++++---- .../filesystem/primitives/rustix/fs/open_options_ext.rs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/wasi/src/filesystem/primitives/open_options.rs b/crates/wasi/src/filesystem/primitives/open_options.rs index 2ee237507100..41923bfc24b4 100644 --- a/crates/wasi/src/filesystem/primitives/open_options.rs +++ b/crates/wasi/src/filesystem/primitives/open_options.rs @@ -28,7 +28,7 @@ pub struct OpenOptions { pub(crate) maybe_dir: bool, pub(crate) sync: bool, pub(crate) dsync: bool, - #[cfg(target_os = "linux")] + #[cfg(any(target_os = "linux", target_os = "android"))] pub(crate) rsync: bool, #[cfg(not(windows))] pub(crate) nonblock: bool, @@ -58,7 +58,7 @@ impl OpenOptions { maybe_dir: false, sync: false, dsync: false, - #[cfg(target_os = "linux")] + #[cfg(any(target_os = "linux", target_os = "android"))] rsync: false, #[cfg(not(windows))] nonblock: false, @@ -150,7 +150,7 @@ impl OpenOptions { } /// Unix-specific extensions to [`fs::OpenOptions`]. -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "linux", target_os = "android"))] pub trait OpenOptionsExt { /// Pass custom flags to the `flags` argument of `open`. fn custom_flags(&mut self, flags: i32) -> &mut Self; @@ -218,7 +218,7 @@ pub trait OpenOptionsExt { fn custom_flags(&mut self, flags: u32) -> &mut Self; } -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "linux", target_os = "android"))] impl OpenOptionsExt for OpenOptions { #[inline] fn custom_flags(&mut self, flags: i32) -> &mut Self { diff --git a/crates/wasi/src/filesystem/primitives/rustix/fs/open_options_ext.rs b/crates/wasi/src/filesystem/primitives/rustix/fs/open_options_ext.rs index cb43bdf73b55..dc457f11e44c 100644 --- a/crates/wasi/src/filesystem/primitives/rustix/fs/open_options_ext.rs +++ b/crates/wasi/src/filesystem/primitives/rustix/fs/open_options_ext.rs @@ -12,7 +12,7 @@ impl ImplOpenOptionsExt { } } - #[cfg(target_os = "linux")] + #[cfg(any(target_os = "linux", target_os = "android"))] pub(crate) fn custom_flags(&mut self, flags: i32) -> &mut Self { self.custom_flags = flags; self From 0d1754ca112114d4999cb58499167a8a097f2eab Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 27 Aug 2026 15:26:29 -0700 Subject: [PATCH 29/34] Fix illumos build --- .../src/filesystem/primitives/open_options.rs | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/crates/wasi/src/filesystem/primitives/open_options.rs b/crates/wasi/src/filesystem/primitives/open_options.rs index 41923bfc24b4..c7a417d4f93a 100644 --- a/crates/wasi/src/filesystem/primitives/open_options.rs +++ b/crates/wasi/src/filesystem/primitives/open_options.rs @@ -28,7 +28,15 @@ pub struct OpenOptions { pub(crate) maybe_dir: bool, pub(crate) sync: bool, pub(crate) dsync: bool, - #[cfg(any(target_os = "linux", target_os = "android"))] + #[cfg(not(any( + target_os = "ios", + target_os = "macos", + target_os = "tvos", + target_os = "watchos", + target_os = "visionos", + target_os = "freebsd", + target_os = "fuchsia" + )))] pub(crate) rsync: bool, #[cfg(not(windows))] pub(crate) nonblock: bool, @@ -58,7 +66,15 @@ impl OpenOptions { maybe_dir: false, sync: false, dsync: false, - #[cfg(any(target_os = "linux", target_os = "android"))] + #[cfg(not(any( + target_os = "ios", + target_os = "macos", + target_os = "tvos", + target_os = "watchos", + target_os = "visionos", + target_os = "freebsd", + target_os = "fuchsia" + )))] rsync: false, #[cfg(not(windows))] nonblock: false, From 8e210f4f820b5bc5713f5ad2ec697af4bcd3bc41 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 27 Aug 2026 15:33:37 -0700 Subject: [PATCH 30/34] Fix windows build again --- crates/wasi/src/filesystem/primitives/open_options.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/wasi/src/filesystem/primitives/open_options.rs b/crates/wasi/src/filesystem/primitives/open_options.rs index c7a417d4f93a..2f4e4d1cea6e 100644 --- a/crates/wasi/src/filesystem/primitives/open_options.rs +++ b/crates/wasi/src/filesystem/primitives/open_options.rs @@ -36,6 +36,7 @@ pub struct OpenOptions { target_os = "visionos", target_os = "freebsd", target_os = "fuchsia" + windows, )))] pub(crate) rsync: bool, #[cfg(not(windows))] @@ -74,6 +75,7 @@ impl OpenOptions { target_os = "visionos", target_os = "freebsd", target_os = "fuchsia" + windows, )))] rsync: false, #[cfg(not(windows))] From 40a9a14a05948e1df784ad8635c7643fd00f7967 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 27 Aug 2026 15:58:37 -0700 Subject: [PATCH 31/34] Fix many builds --- crates/wasi/src/filesystem/primitives/open_options.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/wasi/src/filesystem/primitives/open_options.rs b/crates/wasi/src/filesystem/primitives/open_options.rs index 2f4e4d1cea6e..73b43e2ef287 100644 --- a/crates/wasi/src/filesystem/primitives/open_options.rs +++ b/crates/wasi/src/filesystem/primitives/open_options.rs @@ -35,7 +35,7 @@ pub struct OpenOptions { target_os = "watchos", target_os = "visionos", target_os = "freebsd", - target_os = "fuchsia" + target_os = "fuchsia", windows, )))] pub(crate) rsync: bool, @@ -74,7 +74,7 @@ impl OpenOptions { target_os = "watchos", target_os = "visionos", target_os = "freebsd", - target_os = "fuchsia" + target_os = "fuchsia", windows, )))] rsync: false, From d0682a522fe99642ee5f416601dd8cc5f74e7f0c Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 27 Aug 2026 16:12:54 -0700 Subject: [PATCH 32/34] Fix clippy lints --- .../src/filesystem/primitives/tests/fs.rs | 15 ++++---- .../primitives/tests/fs_additional.rs | 9 ++--- .../primitives/tests/paths_containing_nul.rs | 2 +- .../filesystem/primitives/tests/readdir.rs | 20 ++++------- .../filesystem/primitives/tests/symlinks.rs | 36 +++++++++---------- 5 files changed, 36 insertions(+), 46 deletions(-) diff --git a/crates/wasi/src/filesystem/primitives/tests/fs.rs b/crates/wasi/src/filesystem/primitives/tests/fs.rs index 427825b67468..10f16ed9e9e6 100644 --- a/crates/wasi/src/filesystem/primitives/tests/fs.rs +++ b/crates/wasi/src/filesystem/primitives/tests/fs.rs @@ -67,7 +67,7 @@ fn dir_entry_methods() { Some("b") => { assert!(file.metadata().unwrap().file_type().is_file()); } - f => panic!("unknown file name: {:?}", f), + f => panic!("unknown file name: {f:?}"), } } } @@ -275,7 +275,7 @@ fn file_test_io_non_positional_read() { fn file_test_io_seek_and_tell_smoke_test() { let message = "ten-four"; let mut read_mem = [0; 4]; - let set_cursor = 4 as u64; + let set_cursor = 4_u64; let tell_pos_pre_read; let tell_pos_post_read; let tmpdir = tmpdir(); @@ -594,7 +594,7 @@ fn file_test_directoryinfo_readdir() { check!(h::create_dir(&start, dir)); let prefix = "foo"; for n in 0..3 { - let f = format!("{}.txt", n); + let f = format!("{n}.txt"); let mut w = check!(h::create(&start, &f)); let msg_str = format!("{}{}", prefix, n.to_string()); let msg = msg_str.as_bytes(); @@ -1039,7 +1039,7 @@ fn dir_entry_debug() { h::create(&start, "b").unwrap(); let mut read_dir = h::read_dir(&start, ".").unwrap(); let dir_entry = read_dir.next().unwrap().unwrap(); - let actual = format!("{:?}", dir_entry); + let actual = format!("{dir_entry:?}"); let expected = format!("DirEntry({:?})", dir_entry.file_name()); assert_eq!(actual, expected); } @@ -1116,10 +1116,9 @@ fn metadata_access_times() { (Ok(t1), Ok(t2)) => assert!(t1 <= t2), (Err(e1), Err(e2)) if e1.kind() == ErrorKind::Other && e2.kind() == ErrorKind::Other => {} - (a, b) => panic!( - "creation time must be always supported or not supported: {:?} {:?}", - a, b, - ), + (a, b) => { + panic!("creation time must be always supported or not supported: {a:?} {b:?}",) + } } } } diff --git a/crates/wasi/src/filesystem/primitives/tests/fs_additional.rs b/crates/wasi/src/filesystem/primitives/tests/fs_additional.rs index 01b565ef5cfd..bdccbfbaca0d 100644 --- a/crates/wasi/src/filesystem/primitives/tests/fs_additional.rs +++ b/crates/wasi/src/filesystem/primitives/tests/fs_additional.rs @@ -450,7 +450,7 @@ fn file_test_directoryinfo_readdir() { check!(h::create_dir(&start, dir)); let prefix = "foo"; for n in 0..3 { - let f = format!("{}.txt", n); + let f = format!("{n}.txt"); let mut w = check!(h::create(&start, &f)); let msg_str = format!("{}{}", prefix, n.to_string()); let msg = msg_str.as_bytes(); @@ -925,7 +925,7 @@ fn metadata_vs_std_fs() { match std_dir.created() { Ok(_) => println!("std::fs supports file created times"), - Err(e) => println!("std::fs doesn't support file created times: {}", e), + Err(e) => println!("std::fs doesn't support file created times: {e}"), } check_metadata(&std_dir, &cap_std_dir); @@ -994,10 +994,7 @@ fn check_metadata(std: &std::fs::Metadata, cap: &p::Metadata) { // while also allowing some targets to return valid created times // even when std doesn't. if let Ok(actual) = cap.created() { - println!( - "std returned error for created time ({}) but got {:#?}", - e, actual - ); + println!("std returned error for created time ({e}) but got {actual:#?}"); assert_ne!(actual, std::time::SystemTime::UNIX_EPOCH); } } diff --git a/crates/wasi/src/filesystem/primitives/tests/paths_containing_nul.rs b/crates/wasi/src/filesystem/primitives/tests/paths_containing_nul.rs index a3c95a842b80..4c9247ab19f5 100644 --- a/crates/wasi/src/filesystem/primitives/tests/paths_containing_nul.rs +++ b/crates/wasi/src/filesystem/primitives/tests/paths_containing_nul.rs @@ -20,7 +20,7 @@ use std::path::Path; fn assert_invalid_input(on: &str, result: io::Result) { fn inner(on: &str, result: io::Result<()>) { match result { - Ok(()) => panic!("{} didn't return an error on a path with NUL", on), + Ok(()) => panic!("{on} didn't return an error on a path with NUL"), Err(_e) => { // TODO: Re-enable this assertion once the `io_error_more` // feature is available. diff --git a/crates/wasi/src/filesystem/primitives/tests/readdir.rs b/crates/wasi/src/filesystem/primitives/tests/readdir.rs index ac427bb10634..8ceea29fae5c 100644 --- a/crates/wasi/src/filesystem/primitives/tests/readdir.rs +++ b/crates/wasi/src/filesystem/primitives/tests/readdir.rs @@ -15,8 +15,7 @@ fn test_dir_entries() { let entries = dir_entries(&tmpdir.path()); assert!( entries.get("file1").is_some(), - "directory contains `file1`: {:?}", - entries + "directory contains `file1`: {entries:?}" ); assert_eq!(entries.len(), 1); @@ -24,13 +23,11 @@ fn test_dir_entries() { let entries = dir_entries(&tmpdir.path()); assert!( entries.get("file1").is_some(), - "directory contains `file1`: {:?}", - entries + "directory contains `file1`: {entries:?}" ); assert!( entries.get("file2").is_some(), - "directory contains `file2`: {:?}", - entries + "directory contains `file2`: {entries:?}" ); assert_eq!(entries.len(), 2); } @@ -48,8 +45,7 @@ fn test_reread_entries() { let entries = read_entries(&dir); assert!( entries.get("file1").is_some(), - "directory contains `file1`: {:?}", - entries + "directory contains `file1`: {entries:?}" ); assert_eq!(entries.len(), 1); @@ -57,13 +53,11 @@ fn test_reread_entries() { let entries = read_entries(&dir); assert!( entries.get("file1").is_some(), - "directory contains `file1`: {:?}", - entries + "directory contains `file1`: {entries:?}" ); assert!( entries.get("file2").is_some(), - "directory contains `file2`: {:?}", - entries + "directory contains `file2`: {entries:?}" ); assert_eq!(entries.len(), 2); } @@ -78,7 +72,7 @@ fn read_entries(dir: &File) -> HashMap { for e in read_base_dir(dir).unwrap() { let e = e.expect("non-error entry"); let name = e.file_name().to_str().expect("utf8 filename").to_owned(); - assert!(out.get(&name).is_none(), "name already read: {}", name); + assert!(out.get(&name).is_none(), "name already read: {name}"); out.insert(name, e); } out diff --git a/crates/wasi/src/filesystem/primitives/tests/symlinks.rs b/crates/wasi/src/filesystem/primitives/tests/symlinks.rs index 55c9eff4acee..3c2e7d9ec243 100644 --- a/crates/wasi/src/filesystem/primitives/tests/symlinks.rs +++ b/crates/wasi/src/filesystem/primitives/tests/symlinks.rs @@ -258,7 +258,7 @@ fn open_dir_nofollow() { // Check various ways of spelling `dir/../symlink_dir`. for dir_name in &["dir", "symlink_dir"] { - let name = format!("{}/../symlink_dir", dir_name); + let name = format!("{dir_name}/../symlink_dir"); check!(p::open_dir(&dir, Path::new(&name))); assert!(h::open_dir_nofollow(&dir, &name).is_err()); } @@ -267,11 +267,11 @@ fn open_dir_nofollow() { // expansion may end with `/` or a non-symlink). for suffix in &[""] { for symlink_dir in &["symlink_dot"] { - let name = format!("{}{}", symlink_dir, suffix); + let name = format!("{symlink_dir}{suffix}"); check!(p::open_dir(&dir, Path::new(&name))); assert!(h::open_dir_nofollow(&dir, &name).is_err()); for dir_name in &["dir", "symlink_dir"] { - let name = format!("{}/../{}", dir_name, name); + let name = format!("{dir_name}/../{name}"); check!(p::open_dir(&dir, Path::new(&name))); assert!(h::open_dir_nofollow(&dir, &name).is_err()); } @@ -288,7 +288,7 @@ fn open_dir_nofollow() { "symlink_dir_slashdotdotslash", "symlink_dotslash", ] { - let name = format!("{}{}", symlink_dir, suffix); + let name = format!("{symlink_dir}{suffix}"); #[cfg(windows)] { error!(p::open_dir(&dir, Path::new(&name)), 123); @@ -299,7 +299,7 @@ fn open_dir_nofollow() { } assert!(h::open_dir_nofollow(&dir, &name).is_err()); for dir_name in &["dir", "symlink_dir"] { - let name = format!("{}/../{}", dir_name, name); + let name = format!("{dir_name}/../{name}"); #[cfg(windows)] { error!(p::open_dir(&dir, Path::new(&name)), 123); @@ -317,7 +317,7 @@ fn open_dir_nofollow() { // `open_dir_nofollow` can open them. for suffix in &["/", "/.", "/./"] { for symlink_dir in &["symlink_dir", "symlink_dot"] { - let name = format!("{}{}", symlink_dir, suffix); + let name = format!("{symlink_dir}{suffix}"); check!(p::open_dir(&dir, Path::new(&name))); // On Windows, a trailing dot is stripped early. if cfg!(not(windows)) || suffix != &"/." { @@ -326,7 +326,7 @@ fn open_dir_nofollow() { assert!(h::open_dir_nofollow(&dir, &name).is_err()); } for dir_name in &["dir", "symlink_dir"] { - let name = format!("{}/../{}", dir_name, name); + let name = format!("{dir_name}/../{name}"); check!(p::open_dir(&dir, Path::new(&name))); // On Windows, a trailing dot is stripped early. if cfg!(not(windows)) || suffix != &"/." { @@ -349,7 +349,7 @@ fn open_dir_nofollow() { "symlink_dir_slashdotdotslash", "symlink_dotslash", ] { - let name = format!("{}{}", symlink_dir, suffix); + let name = format!("{symlink_dir}{suffix}"); #[cfg(windows)] { error!(p::open_dir(&dir, Path::new(&name)), 123); @@ -361,7 +361,7 @@ fn open_dir_nofollow() { check!(h::open_dir_nofollow(&dir, &name)); } for dir_name in &["dir", "symlink_dir"] { - let name = format!("{}/../{}", dir_name, name); + let name = format!("{dir_name}/../{name}"); #[cfg(windows)] { error!(p::open_dir(&dir, Path::new(&name)), 123); @@ -465,7 +465,7 @@ fn open_dir_nofollow_ambient() { // Check various ways of spelling `dir/../symlink_dir`. for dir_name in &["dir", "symlink_dir"] { - let name = format!("{}/../symlink_dir", dir_name); + let name = format!("{dir_name}/../symlink_dir"); check!(h::open_ambient_dir(dir.path().join(&name))); } @@ -473,10 +473,10 @@ fn open_dir_nofollow_ambient() { // expansion may end with `/` or a non-symlink). for suffix in &[""] { for symlink_dir in &["symlink_dot"] { - let name = format!("{}{}", symlink_dir, suffix); + let name = format!("{symlink_dir}{suffix}"); check!(h::open_ambient_dir(dir.path().join(&name))); for dir_name in &["dir", "symlink_dir"] { - let name = format!("{}/../{}", dir_name, name); + let name = format!("{dir_name}/../{name}"); check!(h::open_ambient_dir(dir.path().join(&name))); } } @@ -492,7 +492,7 @@ fn open_dir_nofollow_ambient() { "symlink_dir_slashdotdotslash", "symlink_dotslash", ] { - let name = format!("{}{}", symlink_dir, suffix); + let name = format!("{symlink_dir}{suffix}"); #[cfg(windows)] { error!(h::open_ambient_dir(dir.path().join(&name)), 123); @@ -502,7 +502,7 @@ fn open_dir_nofollow_ambient() { check!(h::open_ambient_dir(dir.path().join(&name))); } for dir_name in &["dir", "symlink_dir"] { - let name = format!("{}/../{}", dir_name, name); + let name = format!("{dir_name}/../{name}"); #[cfg(windows)] { error!(h::open_ambient_dir(dir.path().join(&name)), 123); @@ -518,10 +518,10 @@ fn open_dir_nofollow_ambient() { // Check those same paths, but with various suffixes appended. for suffix in &["/", "/.", "/./"] { for symlink_dir in &["symlink_dir", "symlink_dot"] { - let name = format!("{}{}", symlink_dir, suffix); + let name = format!("{symlink_dir}{suffix}"); check!(h::open_ambient_dir(dir.path().join(&name))); for dir_name in &["dir", "symlink_dir"] { - let name = format!("{}/../{}", dir_name, name); + let name = format!("{dir_name}/../{name}"); check!(h::open_ambient_dir(dir.path().join(&name))); } } @@ -538,7 +538,7 @@ fn open_dir_nofollow_ambient() { "symlink_dir_slashdotdotslash", "symlink_dotslash", ] { - let name = format!("{}{}", symlink_dir, suffix); + let name = format!("{symlink_dir}{suffix}"); #[cfg(windows)] { error!(h::open_ambient_dir(dir.path().join(&name)), 123); @@ -548,7 +548,7 @@ fn open_dir_nofollow_ambient() { check!(h::open_ambient_dir(dir.path().join(&name))); } for dir_name in &["dir", "symlink_dir"] { - let name = format!("{}/../{}", dir_name, name); + let name = format!("{dir_name}/../{name}"); #[cfg(windows)] { error!(h::open_ambient_dir(dir.path().join(&name)), 123); From a80c848573d70a3d4a27383f05b4369ebbcfcb86 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 27 Aug 2026 16:33:27 -0700 Subject: [PATCH 33/34] Fix some tests on Windows --- crates/wasi/src/filesystem/primitives/tests/fs_additional.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/wasi/src/filesystem/primitives/tests/fs_additional.rs b/crates/wasi/src/filesystem/primitives/tests/fs_additional.rs index bdccbfbaca0d..d18a92cfb900 100644 --- a/crates/wasi/src/filesystem/primitives/tests/fs_additional.rs +++ b/crates/wasi/src/filesystem/primitives/tests/fs_additional.rs @@ -185,6 +185,7 @@ fn dotdot_slashdot_at_end_of_symlink() { } #[test] +#[cfg_attr(windows, ignore)] fn dotdot_slashdot_at_end_of_symlink_all_inside_dir() { let tmpdir = tmpdir(); let start = h::dir_of(&tmpdir); @@ -469,6 +470,7 @@ fn file_test_directoryinfo_readdir() { } check!(p::remove_file(&sub, Path::new(&f.file_name()))); } + drop(sub); check!(p::remove_dir(&start, Path::new(dir))); } From d19980b0c0e80ac76896c7a2610da7626737993e Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 27 Aug 2026 16:59:26 -0700 Subject: [PATCH 34/34] Try to fix more windows tests --- .../wasi/src/filesystem/primitives/tests/fs_additional.rs | 1 + .../wasi/src/filesystem/primitives/tests/helpers/mod.rs | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/wasi/src/filesystem/primitives/tests/fs_additional.rs b/crates/wasi/src/filesystem/primitives/tests/fs_additional.rs index d18a92cfb900..24de32f33873 100644 --- a/crates/wasi/src/filesystem/primitives/tests/fs_additional.rs +++ b/crates/wasi/src/filesystem/primitives/tests/fs_additional.rs @@ -161,6 +161,7 @@ fn dotdot_at_end_of_symlink_all_inside_dir() { } #[test] +#[cfg_attr(windows, ignore)] fn dotdot_slashdot_at_end_of_symlink() { let tmpdir = tmpdir(); let start = h::dir_of(&tmpdir); diff --git a/crates/wasi/src/filesystem/primitives/tests/helpers/mod.rs b/crates/wasi/src/filesystem/primitives/tests/helpers/mod.rs index bf66e1dd7ba8..22a36c6a0060 100644 --- a/crates/wasi/src/filesystem/primitives/tests/helpers/mod.rs +++ b/crates/wasi/src/filesystem/primitives/tests/helpers/mod.rs @@ -117,8 +117,6 @@ pub fn read_to_string(d: &File, path: impl AsRef) -> io::Result { Ok(s) } -/// `DirExt::symlink`. On Unix there is only one flavour of symlink; on Windows -/// the file/dir distinction matters, so these dispatch accordingly. #[cfg(not(windows))] pub fn symlink(d: &File, src: impl AsRef, dst: impl AsRef) -> io::Result<()> { p::symlink(src.as_ref(), d, dst.as_ref()) @@ -126,7 +124,11 @@ pub fn symlink(d: &File, src: impl AsRef, dst: impl AsRef) -> io::Re #[cfg(windows)] pub fn symlink(d: &File, src: impl AsRef, dst: impl AsRef) -> io::Result<()> { - p::symlink_file(src.as_ref(), d, dst.as_ref()) + if is_dir(d, src.as_ref()) { + p::symlink_dir(src.as_ref(), d, dst.as_ref()) + } else { + p::symlink_file(src.as_ref(), d, dst.as_ref()) + } } #[cfg(not(windows))]