Skip to content
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
30 changes: 20 additions & 10 deletions nvx-rs/src/host/platform/linux/private.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
//! their owners and modes, and the sparse files and the renames without replacement of the
//! stores.

use std::fs::{self, DirBuilder, File, OpenOptions, Permissions};
use std::fs::{self, DirBuilder, File, Permissions};
use std::io;
use std::os::fd::AsRawFd;
use std::os::unix::fs::{DirBuilderExt, MetadataExt, OpenOptionsExt, PermissionsExt};
use std::os::unix::fs::{DirBuilderExt, MetadataExt, PermissionsExt};
use std::path::Path;

use nix::sys::stat::Mode;
Expand Down Expand Up @@ -167,10 +167,7 @@ pub(crate) fn rename_no_replace(from: &Path, to: &Path) -> io::Result<()> {
pub(crate) fn restore_private_file_mode(path: &Path, mode: Mode) -> io::Result<()> {
use nix::fcntl::OFlag;

let opened = OpenOptions::new()
.read(true)
.custom_flags((OFlag::O_PATH | OFlag::O_NOFOLLOW).bits())
.open(path);
let opened = open_to_locate(path, OFlag::O_NOFOLLOW);
let file = match opened {
Ok(file) => file,
Err(error)
Expand Down Expand Up @@ -198,10 +195,7 @@ pub(crate) fn restore_private_file_mode(path: &Path, mode: Mode) -> io::Result<(
fn open_plain_dir(path: &Path) -> io::Result<Option<File>> {
use nix::fcntl::OFlag;

let opened = OpenOptions::new()
.read(true)
.custom_flags((OFlag::O_PATH | OFlag::O_DIRECTORY | OFlag::O_NOFOLLOW).bits())
.open(path);
let opened = open_to_locate(path, OFlag::O_DIRECTORY | OFlag::O_NOFOLLOW);
match opened {
Ok(directory) => Ok(Some(directory)),
Err(error)
Expand All @@ -213,3 +207,19 @@ fn open_plain_dir(path: &Path) -> io::Result<Option<File>> {
Err(error) => Err(error),
}
}

/// Opens `path` with `O_PATH` and the flags `flags`, only to locate it, which needs no permission
/// on the file itself, and never waits for a writer, as an open of a FIFO for reading would.
///
/// `nix` passes the flags on as they are, where `OpenOptionsExt::custom_flags` masks out the bits
/// of `O_ACCMODE`, which on musl include `O_PATH`: a musl build would open the file for reading.
fn open_to_locate(path: &Path, flags: nix::fcntl::OFlag) -> io::Result<File> {
use nix::fcntl::OFlag;

let file = nix::fcntl::open(
path,
OFlag::O_PATH | OFlag::O_CLOEXEC | flags,
Mode::empty(),
)?;
Ok(File::from(file))
}
46 changes: 46 additions & 0 deletions nvx-rs/src/host/platform/linux/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -466,3 +466,49 @@ fn a_unix_transport_reports_what_its_peer_has_not_read() {
peer.read_exact(&mut rest).unwrap();
assert_eq!(transport.unread().unwrap(), Some(0));
}

/// Returns the permission bits of the mode of the file at `path`, not following a link.
fn permission_bits(path: &std::path::Path) -> u32 {
fs::symlink_metadata(path).unwrap().permissions().mode() & 0o7777
}

#[cfg(feature = "cli")]
#[test]
fn a_fifo_in_place_of_a_private_file_is_left_as_it_is_without_waiting_for_a_writer() {
let directory = tempfile::tempdir().unwrap();
let path = directory.path().join("lock");
nix::unistd::mkfifo(&path, Mode::S_IRUSR).unwrap();
// An open of the FIFO for reading would wait for a writer, which never comes.
let (sender, receiver) = std::sync::mpsc::channel();
let fifo = path.clone();
let mode = Mode::S_IRUSR | Mode::S_IWUSR;
thread::spawn(move || sender.send(restore_private_file_mode(&fifo, mode)));
receiver
.recv_timeout(Duration::from_secs(10))
.expect("the FIFO was opened for reading")
.unwrap();
assert_eq!(permission_bits(&path), 0o400);
}

#[test]
fn a_store_directory_gets_mode_700_even_where_the_umask_withholds_reading_it() {
let directory = tempfile::tempdir().unwrap();
let path = directory.path().join("store");
// A umask that clears the owner's permission to read what it creates, in a thread of the
// test's own, whose umask no other thread shares.
let problem = thread::scope(|scope| {
scope
.spawn(|| {
nix::sched::unshare(nix::sched::CloneFlags::CLONE_FS).unwrap();
nix::sys::stat::umask(Mode::from_bits_truncate(0o477));
create_store_dir(&path)
})
.join()
.unwrap()
});
let mode = permission_bits(&path);
// Lets the temporary directory go, even if the umask's mode stayed.
fs::set_permissions(&path, fs::Permissions::from_mode(0o700)).unwrap();
assert_eq!(problem.unwrap(), None);
assert_eq!(mode, 0o700);
}
11 changes: 5 additions & 6 deletions nvx-rs/src/host/seal/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,13 @@ use super::*;
/// `EBADF`.
#[cfg(target_os = "linux")]
pub(crate) fn open_metadata_only(path: &Path) -> io::Result<File> {
use std::os::unix::fs::OpenOptionsExt;

use nix::fcntl::OFlag;
use nix::sys::stat::Mode;

OpenOptions::new()
.read(true)
.custom_flags(OFlag::O_PATH.bits())
.open(path)
// `nix` passes `O_PATH` on, where `OpenOptionsExt::custom_flags` masks out the bits of
// `O_ACCMODE`, which on musl include `O_PATH`: a musl build would open the file for reading.
let file = nix::fcntl::open(path, OFlag::O_PATH | OFlag::O_CLOEXEC, Mode::empty())?;
Ok(File::from(file))
}

/// Opens `path` for its metadata alone, as the Linux variant does. Without `FILE_READ_DATA`,
Expand Down
Loading