diff --git a/nvx-rs/src/host/platform/linux/private.rs b/nvx-rs/src/host/platform/linux/private.rs index 453e09613..2ff82605b 100644 --- a/nvx-rs/src/host/platform/linux/private.rs +++ b/nvx-rs/src/host/platform/linux/private.rs @@ -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; @@ -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) @@ -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> { 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) @@ -213,3 +207,19 @@ fn open_plain_dir(path: &Path) -> io::Result> { 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 { + use nix::fcntl::OFlag; + + let file = nix::fcntl::open( + path, + OFlag::O_PATH | OFlag::O_CLOEXEC | flags, + Mode::empty(), + )?; + Ok(File::from(file)) +} diff --git a/nvx-rs/src/host/platform/linux/tests.rs b/nvx-rs/src/host/platform/linux/tests.rs index 9819c9203..c257a8d05 100644 --- a/nvx-rs/src/host/platform/linux/tests.rs +++ b/nvx-rs/src/host/platform/linux/tests.rs @@ -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); +} diff --git a/nvx-rs/src/host/seal/tests.rs b/nvx-rs/src/host/seal/tests.rs index e89fec9f6..f5a75ba2f 100644 --- a/nvx-rs/src/host/seal/tests.rs +++ b/nvx-rs/src/host/seal/tests.rs @@ -12,14 +12,13 @@ use super::*; /// `EBADF`. #[cfg(target_os = "linux")] pub(crate) fn open_metadata_only(path: &Path) -> io::Result { - 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`,