Skip to content

Fix stat when the last path component is a symlink to ... #105

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Aug 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions benches/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -381,13 +381,13 @@ fn recursive_create_delete(b: &mut test::Bencher) {
let dir = unsafe { cap_tempfile::tempdir().unwrap() };

let mut path = PathBuf::new();
for _ in 0..256 {
path.push("abc");
for depth in 0..256 {
path.push(format!("depth{}", depth));
}

b.iter(|| {
dir.create_dir_all(&path).unwrap();
dir.remove_dir_all(&path).unwrap();
dir.remove_dir_all("depth0").unwrap();
});
}

Expand All @@ -397,13 +397,13 @@ fn recursive_create_delete_baseline(b: &mut test::Bencher) {

let mut path = PathBuf::new();
path.push(dir);
for _ in 0..256 {
path.push("abc");
for depth in 0..256 {
path.push(format!("depth{}", depth));
}

b.iter(|| {
fs::create_dir_all(&path).unwrap();
fs::remove_dir_all(&path).unwrap();
fs::remove_dir_all("depth0").unwrap();
});
}

Expand Down
5 changes: 5 additions & 0 deletions cap-async-std/src/fs/dir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ pub struct Dir {

impl Dir {
/// Constructs a new instance of `Self` from the given `async_std::fs::File`.
///
/// To prevent race conditions on Windows, the file must be opened without
/// `FILE_SHARE_DELETE`.
#[inline]
pub fn from_std_file(std_file: fs::File) -> Self {
Self { std_file }
Expand Down Expand Up @@ -624,6 +627,8 @@ impl FromRawFd for Dir {

#[cfg(windows)]
impl FromRawHandle for Dir {
/// To prevent race conditions on Windows, the handle must be opened without
/// `FILE_SHARE_DELETE`.
#[inline]
unsafe fn from_raw_handle(handle: RawHandle) -> Self {
Self::from_std_file(fs::File::from_raw_handle(handle))
Expand Down
5 changes: 5 additions & 0 deletions cap-async-std/src/fs_utf8/dir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ pub struct Dir {

impl Dir {
/// Constructs a new instance of `Self` from the given `async_std::fs::File`.
///
/// To prevent race conditions on Windows, the file must be opened without
/// `FILE_SHARE_DELETE`.
#[inline]
pub fn from_std_file(std_file: fs::File) -> Self {
Self::from_cap_std(crate::fs::Dir::from_std_file(std_file))
Expand Down Expand Up @@ -543,6 +546,8 @@ impl FromRawFd for Dir {

#[cfg(windows)]
impl FromRawHandle for Dir {
/// To prevent race conditions on Windows, the handle must be opened without
/// `FILE_SHARE_DELETE`.
#[inline]
unsafe fn from_raw_handle(handle: RawHandle) -> Self {
Self::from_std_file(fs::File::from_raw_handle(handle))
Expand Down
71 changes: 71 additions & 0 deletions cap-primitives/src/fs/canonical_path.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use std::{
ffi::OsStr,
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(not(feature = "no_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(not(feature = "no_racy_asserts"))]
debug: PathBuf::new(),

path,
}
}

pub(super) fn push(&mut self, one: &OsStr) {
#[cfg(not(feature = "no_racy_asserts"))]
self.debug.push(one);

if let Some(path) = &mut self.path {
path.push(one)
}
}

pub(super) fn pop(&mut self) -> bool {
#[cfg(not(feature = "no_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;
}
}
}
4 changes: 2 additions & 2 deletions cap-primitives/src/fs/canonicalize_manually.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Manual path canonicalization, one component at a time, with manual symlink
//! resolution, in order to enforce sandboxing.

use crate::fs::{canonicalize_options, open_manually, FollowSymlinks, MaybeOwnedFile};
use crate::fs::{canonicalize_options, open_manually_impl, FollowSymlinks, MaybeOwnedFile};
use std::{
fs, io,
path::{Path, PathBuf},
Expand All @@ -28,7 +28,7 @@ pub(crate) fn canonicalize_manually(
let mut canonical_path = PathBuf::new();
let start = MaybeOwnedFile::borrowed(start);

if let Err(e) = open_manually(
if let Err(e) = open_manually_impl(
start,
path,
canonicalize_options().follow(follow),
Expand Down
32 changes: 32 additions & 0 deletions cap-primitives/src/fs/cow_component.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use std::{borrow::Cow, ffi::OsStr, 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.
#[derive(Debug)]
pub(super) enum CowComponent<'borrow> {
PrefixOrRootDir,
CurDir,
ParentDir,
Normal(Cow<'borrow, OsStr>),
}

/// Convert a `Component` into a `CowComponent` which borrows strings.
pub(super) fn to_borrowed_component(component: Component) -> CowComponent {
match component {
Component::Prefix(_) | Component::RootDir => CowComponent::PrefixOrRootDir,
Component::CurDir => CowComponent::CurDir,
Component::ParentDir => CowComponent::ParentDir,
Component::Normal(os_str) => CowComponent::Normal(os_str.into()),
}
}

/// Convert a `Component` into a `CowComponent` which owns strings.
pub(super) fn to_owned_component<'borrow>(component: Component) -> CowComponent<'borrow> {
match component {
Component::Prefix(_) | Component::RootDir => CowComponent::PrefixOrRootDir,
Component::CurDir => CowComponent::CurDir,
Component::ParentDir => CowComponent::ParentDir,
Component::Normal(os_str) => CowComponent::Normal(os_str.to_os_string().into()),
}
}
12 changes: 6 additions & 6 deletions cap-primitives/src/fs/maybe_owned_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ enum Inner<'borrow> {
///
/// And, this type has the special `descend_to`, which just does an assignment,
/// but also some useful assertion checks.
pub(crate) struct MaybeOwnedFile<'borrow> {
pub(super) struct MaybeOwnedFile<'borrow> {
inner: Inner<'borrow>,

#[cfg(not(feature = "no_racy_asserts"))]
Expand All @@ -28,7 +28,7 @@ pub(crate) struct MaybeOwnedFile<'borrow> {

impl<'borrow> MaybeOwnedFile<'borrow> {
/// Constructs a new `MaybeOwnedFile` which is not owned.
pub(crate) fn borrowed(file: &'borrow fs::File) -> Self {
pub(super) fn borrowed(file: &'borrow fs::File) -> Self {
#[cfg(not(feature = "no_racy_asserts"))]
let path = get_path(file);

Expand All @@ -41,7 +41,7 @@ impl<'borrow> MaybeOwnedFile<'borrow> {
}

/// Constructs a new `MaybeOwnedFile` which is owned.
pub(crate) fn owned(file: fs::File) -> Self {
pub(super) fn owned(file: fs::File) -> Self {
#[cfg(not(feature = "no_racy_asserts"))]
let path = get_path(&file);

Expand All @@ -56,7 +56,7 @@ impl<'borrow> MaybeOwnedFile<'borrow> {
/// 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(crate) fn descend_to(&mut self, to: MaybeOwnedFile<'borrow>) -> Self {
pub(super) fn descend_to(&mut self, to: MaybeOwnedFile<'borrow>) -> Self {
#[cfg(not(feature = "no_racy_asserts"))]
let path = self.path.clone();

Expand All @@ -83,7 +83,7 @@ impl<'borrow> MaybeOwnedFile<'borrow> {

/// Produce an owned `File`. This uses `open` on "." if needed to convert a
/// borrowed `File` to an owned one.
pub(crate) fn into_file(self, options: &OpenOptions) -> io::Result<fs::File> {
pub(super) fn into_file(self, options: &OpenOptions) -> io::Result<fs::File> {
match self.inner {
Inner::Owned(file) => Ok(file),
Inner::Borrowed(file) => {
Expand All @@ -97,7 +97,7 @@ impl<'borrow> MaybeOwnedFile<'borrow> {

/// Assuming `self` holds an owned `File`, return it.
#[cfg_attr(windows, allow(dead_code))]
pub(crate) fn unwrap_owned(self) -> fs::File {
pub(super) fn unwrap_owned(self) -> fs::File {
match self.inner {
Inner::Owned(file) => file,
Inner::Borrowed(_) => panic!("expected owned file"),
Expand Down
22 changes: 22 additions & 0 deletions cap-primitives/src/fs/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,35 @@ impl Metadata {
/// Constructs a new instance of `Self` from the given `std::fs::Metadata`.
#[inline]
pub fn from_std(std: fs::Metadata) -> Self {
// TODO: Initialize `created` on Linux with `std.created().ok()` once yanix
// has `statx` and we make use of it.
Self {
file_type: FileType::from_std(std.file_type()),
len: std.len(),
permissions: Permissions::from_std(std.permissions()),
modified: std.modified().ok(),
accessed: std.accessed().ok(),

#[cfg(any(
target_os = "freebsd",
target_os = "openbsd",
target_os = "macos",
target_os = "ios",
target_os = "netbsd",
windows,
))]
created: std.created().ok(),

#[cfg(not(any(
target_os = "freebsd",
target_os = "openbsd",
target_os = "macos",
target_os = "ios",
target_os = "netbsd",
windows,
)))]
created: None,

ext: MetadataExt::from_std(std),
}
}
Expand Down
15 changes: 10 additions & 5 deletions cap-primitives/src/fs/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
//! Filesystem utilities.

mod canonical_path;
mod canonicalize;
mod canonicalize_manually;
mod copy;
mod cow_component;
mod dir_builder;
mod dir_entry;
mod dir_options;
Expand All @@ -24,6 +26,7 @@ mod open_entry_manually;
mod open_manually;
mod open_options;
mod open_parent;
mod open_unchecked_error;
mod permissions;
mod read_dir;
mod readlink;
Expand All @@ -40,32 +43,34 @@ mod set_permissions;
#[cfg(not(target_os = "linux"))] // doesn't work reliably on linux
mod set_permissions_via_parent;
mod stat;
mod stat_via_parent;
mod symlink;
mod symlink_via_parent;
mod unlink;
mod unlink_via_parent;

pub(crate) mod errors;

use canonical_path::CanonicalPath;
use cow_component::{to_borrowed_component, to_owned_component, CowComponent};
use maybe_owned_file::MaybeOwnedFile;
use open_parent::open_parent;
use readlink_one::readlink_one;

pub(crate) use canonicalize_manually::*;
#[cfg(not(feature = "no_racy_asserts"))]
pub(crate) use get_path::*;
pub(crate) use link_via_parent::*;
pub(crate) use maybe_owned_file::*;
pub(crate) use mkdir_via_parent::*;
#[cfg(not(windows))] // not needed on windows
pub(crate) use open_entry_manually::*;
pub(crate) use open_manually::*;
pub(crate) use open_parent::*;
pub(crate) use readlink_one::*;
pub(crate) use open_unchecked_error::*;
#[cfg(not(windows))] // doesn't work on windows; use a windows-specific impl
pub(crate) use readlink_via_parent::*;
pub(crate) use rename_via_parent::*;
pub(crate) use rmdir_via_parent::*;
#[cfg(not(target_os = "linux"))] // doesn't work reliably on linux
pub(crate) use set_permissions_via_parent::*;
pub(crate) use stat_via_parent::*;
pub(crate) use symlink_via_parent::*;
pub(crate) use unlink_via_parent::*;

Expand Down
4 changes: 2 additions & 2 deletions cap-primitives/src/fs/open_entry_manually.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::fs::{
open_manually, open_unchecked, readlink_one, FollowSymlinks, MaybeOwnedFile, OpenOptions,
open_manually_impl, open_unchecked, readlink_one, FollowSymlinks, MaybeOwnedFile, OpenOptions,
OpenUncheckedError,
};
use std::{ffi::OsStr, fs, io};
Expand All @@ -19,7 +19,7 @@ pub(crate) fn open_entry_manually(
let mut symlink_count = 0;
let destination = readlink_one(start, path, &mut symlink_count)?;
let maybe = MaybeOwnedFile::borrowed(start);
open_manually(maybe, &destination, options, &mut symlink_count, None)
open_manually_impl(maybe, &destination, options, &mut symlink_count, None)
.map(MaybeOwnedFile::unwrap_owned)
}
Err(OpenUncheckedError::NotFound(err)) | Err(OpenUncheckedError::Other(err)) => Err(err),
Expand Down
Loading