Skip to content

Commit 5e32356

Browse files
authored
FreeBSD RESOLVE_BENEATH support (#296)
* Update FreeBSD versions in Cirrus CI * Enable O_PATH usage on FreeBSD (Available since 13.0) * tests/fs_additional: fix incorrect FreeBSD specifics These do not apply (anymore?) * Use AT_/O_RESOLVE_BENEATH on FreeBSD >= 13.0, fixes #180
1 parent cf92fe0 commit 5e32356

20 files changed

+270
-87
lines changed

.cirrus.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
task:
55
name: stable x86_64-unknown-freebsd-13
66
freebsd_instance:
7-
image_family: freebsd-13-0-snap
7+
image_family: freebsd-13-2
88
setup_script:
99
- pkg install -y curl
1010
- curl https://sh.rustup.rs -sSf --output rustup.sh
@@ -18,7 +18,7 @@ task:
1818
task:
1919
name: stable x86_64-unknown-freebsd-12
2020
freebsd_instance:
21-
image_family: freebsd-12-1
21+
image_family: freebsd-12-4
2222
setup_script:
2323
- pkg install -y curl
2424
- curl https://sh.rustup.rs -sSf --output rustup.sh

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,11 @@ utilize [`openat2`], [`O_PATH`], and [`/proc/self/fd`] (though only when /proc
168168
is mounted, it's really `procfs`, and there are no mounts on top of it) for
169169
fast path resolution as well.
170170

171+
On FreeBSD 13.0 and newer, `cap-std` uses [`openat(O_RESOLVE_BENEATH)`] to
172+
implement `Dir::open` with a single system call in common cases.
173+
Several other operations internally utilize `AT_RESOLVE_BENEATH` and `O_PATH` for
174+
fast path resolution as well.
175+
171176
Otherwise, `cap-std` opens each component of a path individually, in order to
172177
specially handle `..` and symlinks. The algorithm is carefully designed to
173178
minimize system calls, so opening `red/green/blue` performs just 5 system
@@ -177,6 +182,7 @@ and `green`.
177182
[`openat2`]: https://lwn.net/Articles/796868/
178183
[`O_PATH`]: https://man7.org/linux/man-pages/man2/open.2.html
179184
[`/proc/self/fd`]: https://man7.org/linux/man-pages/man5/proc.5.html
185+
[`openat(O_RESOLVE_BENEATH)`]: https://man.freebsd.org/cgi/man.cgi?openat
180186

181187
## What about networking?
182188

cap-primitives/src/fs/manually/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ mod canonical_path;
55
mod canonicalize;
66
mod cow_component;
77
mod open;
8-
#[cfg(not(windows))]
8+
#[cfg(not(any(windows, target_os = "freebsd")))]
99
mod open_entry;
1010
mod read_link_one;
1111

@@ -19,5 +19,5 @@ pub(super) use canonicalize::canonicalize_with;
1919

2020
pub(crate) use canonicalize::canonicalize;
2121
pub(crate) use open::{open, stat};
22-
#[cfg(not(windows))]
22+
#[cfg(not(any(windows, target_os = "freebsd")))]
2323
pub(crate) use open_entry::open_entry;

cap-primitives/src/fs/manually/open.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::fs::{
66
dir_options, errors, open_unchecked, path_has_trailing_dot, path_has_trailing_slash,
77
stat_unchecked, FollowSymlinks, MaybeOwnedFile, Metadata, OpenOptions, OpenUncheckedError,
88
};
9-
#[cfg(any(target_os = "android", target_os = "linux"))]
9+
#[cfg(any(target_os = "android", target_os = "linux", target_os = "freebsd"))]
1010
use rustix::fs::OFlags;
1111
use std::borrow::Cow;
1212
use std::ffi::OsStr;
@@ -247,7 +247,7 @@ impl<'start> Context<'start> {
247247
Ok(file) => {
248248
// Emulate `O_PATH` + `FollowSymlinks::Yes` on Linux. If `file`
249249
// is a symlink, follow it.
250-
#[cfg(any(target_os = "android", target_os = "linux"))]
250+
#[cfg(any(target_os = "android", target_os = "linux", target_os = "freebsd"))]
251251
if should_emulate_o_path(&use_options) {
252252
match read_link_one(
253253
&file,
@@ -527,7 +527,7 @@ pub(crate) fn stat(start: &fs::File, path: &Path, follow: FollowSymlinks) -> io:
527527

528528
/// Test whether the given options imply that we should treat an open file as
529529
/// potentially being a symlink we need to follow, due to use of `O_PATH`.
530-
#[cfg(any(target_os = "android", target_os = "linux"))]
530+
#[cfg(any(target_os = "android", target_os = "linux", target_os = "freebsd"))]
531531
fn should_emulate_o_path(use_options: &OpenOptions) -> bool {
532532
(use_options.ext.custom_flags & (OFlags::PATH.bits() as i32)) == (OFlags::PATH.bits() as i32)
533533
&& use_options.follow == FollowSymlinks::Yes

cap-primitives/src/fs/maybe_owned_file.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ impl<'borrow> MaybeOwnedFile<'borrow> {
120120
}
121121

122122
/// Assuming `self` holds an owned `File`, return it.
123-
#[cfg_attr(windows, allow(dead_code))]
123+
#[cfg_attr(any(windows, target_os = "freebsd"), allow(dead_code))]
124124
pub(super) fn unwrap_owned(self) -> fs::File {
125125
match self.inner {
126126
MaybeOwned::Owned(file) => file,
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
use rustix::fs::{statat, AtFlags};
2+
use std::fs;
3+
use std::sync::atomic::{AtomicBool, Ordering::Relaxed};
4+
5+
static WORKING: AtomicBool = AtomicBool::new(false);
6+
static CHECKED: AtomicBool = AtomicBool::new(false);
7+
8+
#[inline]
9+
pub(crate) fn beneath_supported(start: &fs::File) -> bool {
10+
if WORKING.load(Relaxed) {
11+
return true;
12+
}
13+
if CHECKED.load(Relaxed) {
14+
return false;
15+
}
16+
// Unknown O_ flags get ignored but AT_ flags have strict checks, so we use that.
17+
if let Err(rustix::io::Errno::INVAL) =
18+
statat(start, "", AtFlags::EMPTY_PATH | AtFlags::RESOLVE_BENEATH)
19+
{
20+
CHECKED.store(true, Relaxed);
21+
false
22+
} else {
23+
WORKING.store(true, Relaxed);
24+
true
25+
}
26+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
mod check;
2+
mod open_entry_impl;
3+
mod open_impl;
4+
mod remove_dir_impl;
5+
mod remove_file_impl;
6+
mod set_permissions_impl;
7+
mod set_times_impl;
8+
mod stat_impl;
9+
10+
pub(crate) use crate::fs::manually::canonicalize as canonicalize_impl;
11+
pub(crate) use check::beneath_supported;
12+
pub(crate) use open_entry_impl::open_entry_impl;
13+
pub(crate) use open_impl::open_impl;
14+
pub(crate) use remove_dir_impl::remove_dir_impl;
15+
pub(crate) use remove_file_impl::remove_file_impl;
16+
pub(crate) use set_permissions_impl::set_permissions_impl;
17+
pub(crate) use set_times_impl::{set_times_impl, set_times_nofollow_impl};
18+
pub(crate) use stat_impl::stat_impl;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
use crate::fs::{open_impl, OpenOptions};
2+
use std::ffi::OsStr;
3+
use std::{fs, io};
4+
5+
#[inline(always)]
6+
pub(crate) fn open_entry_impl(
7+
start: &fs::File,
8+
path: &OsStr,
9+
options: &OpenOptions,
10+
) -> io::Result<fs::File> {
11+
open_impl(start, path.as_ref(), options)
12+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
use super::super::super::fs::compute_oflags;
2+
use crate::fs::{errors, manually, OpenOptions};
3+
use io_lifetimes::FromFd;
4+
use rustix::fs::{openat, Mode, OFlags, RawMode};
5+
use std::path::Path;
6+
use std::{fs, io};
7+
8+
pub(crate) fn open_impl(
9+
start: &fs::File,
10+
path: &Path,
11+
options: &OpenOptions,
12+
) -> io::Result<fs::File> {
13+
if !super::beneath_supported(start) {
14+
return manually::open(start, path, options);
15+
}
16+
17+
let oflags = compute_oflags(options)? | OFlags::RESOLVE_BENEATH;
18+
19+
let mode = if oflags.contains(OFlags::CREATE) {
20+
Mode::from_bits((options.ext.mode & 0o7777) as RawMode).unwrap()
21+
} else {
22+
Mode::empty()
23+
};
24+
25+
match openat(start, path, oflags, mode) {
26+
Ok(file) => Ok(fs::File::from_into_fd(file)),
27+
Err(rustix::io::Errno::NOTCAPABLE) => Err(errors::escape_attempt()),
28+
Err(err) => Err(err.into()),
29+
}
30+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
use crate::fs::via_parent;
2+
use rustix::fs::{unlinkat, AtFlags};
3+
use std::path::Path;
4+
use std::{fs, io};
5+
6+
pub(crate) fn remove_dir_impl(start: &fs::File, path: &Path) -> io::Result<()> {
7+
if !super::beneath_supported(start) {
8+
return via_parent::remove_dir(start, path);
9+
}
10+
11+
Ok(unlinkat(
12+
start,
13+
path,
14+
AtFlags::RESOLVE_BENEATH | AtFlags::REMOVEDIR,
15+
)?)
16+
}

0 commit comments

Comments
 (0)