Skip to content
Closed
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
8 changes: 4 additions & 4 deletions nvx-rs/src/host/platform/linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -655,14 +655,14 @@ impl Directory {
.map(Self)
}

/// Returns the device and inode of its entry `name`, not following a link, as
/// [`opened_file_identity`] gives them, or `None` if it has no such entry.
pub(crate) fn entry_identity(&self, name: &str) -> io::Result<Option<(u64, u64)>> {
/// Returns the device and inode of its entry `name`, not following a link, as the entry's
/// [`FileSeal`](crate::host::seal::FileSeal) holds them, or `None` if it has no such entry.
pub(crate) fn entry_identity(&self, name: &str) -> io::Result<Option<(u64, u128)>> {
use nix::fcntl::AtFlags;
use nix::sys::stat::fstatat;

match fstatat(&self.0, name, AtFlags::AT_SYMLINK_NOFOLLOW) {
Ok(status) => Ok(Some((status.st_dev, status.st_ino))),
Ok(status) => Ok(Some((status.st_dev, u128::from(status.st_ino)))),
Err(Errno::ENOENT) => Ok(None),
Err(errno) => Err(errno.into()),
}
Expand Down
2 changes: 1 addition & 1 deletion nvx-rs/src/host/platform/other.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ impl Directory {
}

/// Never called: no directory is open.
pub(crate) fn entry_identity(&self, _name: &str) -> io::Result<Option<(u64, u64)>> {
pub(crate) fn entry_identity(&self, _name: &str) -> io::Result<Option<(u64, u128)>> {
match *self {}
}

Expand Down
16 changes: 11 additions & 5 deletions nvx-rs/src/host/platform/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ use windows_sys::Win32::System::WindowsProgramming::QueryInterruptTimePrecise;

use super::Transport;
use super::inherit::Inherited;
use crate::host::seal::FileSeal;
use crate::hypervisor::Hypervisor;

/// Whether this host can run NVX microVMs.
Expand Down Expand Up @@ -1407,10 +1408,12 @@ impl Directory {
.map(Self)
}

/// Returns the volume serial number and file index of its entry `name`, not following a link
/// or other reparse point, as [`opened_file_identity`] gives them, or `None` if it has no such
/// entry.
pub(crate) fn entry_identity(&self, name: &str) -> io::Result<Option<(u64, u64)>> {
/// Returns the identity of its entry `name`, not following a link or other reparse point, as
/// the entry's [`FileSeal`] holds it, or `None` if it has no such entry: the volume serial
/// number and the 128-bit file ID of `FILE_ID_INFO`, which tell the files of ReFS volumes
/// apart where their 64-bit file indexes may not, or those of `BY_HANDLE_FILE_INFORMATION` on
/// a volume that has no file ID information.
pub(crate) fn entry_identity(&self, name: &str) -> io::Result<Option<(u64, u128)>> {
use windows_sys::Wdk::Storage::FileSystem::{
FILE_OPEN_REPARSE_POINT, FILE_SYNCHRONOUS_IO_NONALERT,
};
Expand All @@ -1422,7 +1425,10 @@ impl Directory {
FILE_READ_ATTRIBUTES | SYNCHRONIZE,
FILE_OPEN_REPARSE_POINT | FILE_SYNCHRONOUS_IO_NONALERT,
) {
Ok(entry) => handle_identity(raw(&entry)).map(Some),
Ok(entry) => {
let seal = FileSeal::of(&fs::File::from(entry))?;
Ok(Some((seal.device(), seal.file_id())))
}
Err(STATUS_OBJECT_NAME_NOT_FOUND) => Ok(None),
Err(status) => Err(status_error(status)),
}
Expand Down
7 changes: 4 additions & 3 deletions nvx-rs/src/host/store/lifecycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use std::path::{Path, PathBuf};
use std::time::Instant;

use super::caller::{is_report_staging, is_store_temporary};
use super::regular;
use super::{
CLONE_BINDING_NAME, CallerFiles, FILE_NAMES, JOURNAL_NAME, LOCK_NAME, LockedStore,
RUNTIME_NAME, Races, SandboxStore, Taken, absolute, pause, transition_in_progress,
Expand Down Expand Up @@ -246,8 +247,8 @@ impl LockedStore {
return;
}
let path = self.path(LOCK_NAME);
let held = platform::opened_file_identity(&self.lock);
if matches!((held, platform::file_identity(&path)), (Ok(held), Ok(current)) if held == current)
let held = regular::identity_of(&self.lock);
if matches!((held, regular::identity_at(&path)), (Ok(held), Ok(current)) if held == current)
{
let _ = fs::remove_file(&path);
}
Expand Down Expand Up @@ -380,7 +381,7 @@ impl LockedStore {
move |error: io::Error| RemovalError::Io { path, error }
};
let directory = platform::Directory::open(dir).map_err(failed)?;
let held = platform::opened_file_identity(&self.lock).map_err(entry_failed(LOCK_NAME))?;
let held = regular::identity_of(&self.lock).map_err(entry_failed(LOCK_NAME))?;
if directory
.entry_identity(LOCK_NAME)
.map_err(entry_failed(LOCK_NAME))?
Expand Down
31 changes: 30 additions & 1 deletion nvx-rs/src/host/store/lifecycle/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ fn a_removal_makes_the_absence_of_the_clone_binding_durable_before_it_removes_an
assert_eq!(
synced.into_inner(),
[(
Some(platform::file_identity(&dir.join(LOCK_NAME)).unwrap()),
Some(regular::identity_at(&dir.join(LOCK_NAME)).unwrap()),
sorted(&[BOOT, CONFIG, LOCK_NAME, LOG_NAME])
)]
);
Expand Down Expand Up @@ -728,6 +728,35 @@ fn directories_are_identified_as_their_seals_identify_them() {
assert_eq!(hold_directory(directory.path()).unwrap().0, expected);
}

#[cfg(any(target_os = "linux", windows))]
#[test]
fn lock_files_are_identified_as_their_seals_identify_them() {
use crate::host::seal::{FileSeal, open_metadata_only};

// The lock, the path, and the directory's entry identify a lock file alike, and two files of
// one directory apart: on Windows, by the 128-bit file ID of `FILE_ID_INFO`, which tells the
// files of ReFS volumes apart where their 64-bit file indexes may not.
let (_directory, dir) = sandbox();
let other = "other.lock";
fs::write(dir.join(other), b"").unwrap();
let locked = SandboxStore::lock_existing(&dir, &files(), None).unwrap();
let directory = platform::Directory::open(&dir).unwrap();
let identities = [LOCK_NAME, other].map(|name| {
let path = dir.join(name);
let seal = FileSeal::of(&open_metadata_only(&path).unwrap()).unwrap();
let expected = (seal.device(), seal.file_id());
assert_eq!(regular::identity_at(&path).unwrap(), expected, "{name}");
assert_eq!(
directory.entry_identity(name).unwrap(),
Some(expected),
"{name}"
);
expected
});
assert_ne!(identities[0], identities[1]);
assert_eq!(regular::identity_of(&locked.lock).unwrap(), identities[0]);
}

#[test]
fn a_removal_needs_the_configuration_record() {
let (_directory, dir) = sandbox();
Expand Down
5 changes: 3 additions & 2 deletions nvx-rs/src/host/store/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ pub use self::lifecycle::{Lingering, RemovalError};
pub use self::record::{
LaunchId, LaunchJournal, LaunchState, LogClaim, MAX_ENDPOINT_LEN, RuntimeRecord,
};
use self::regular::{identity_at, identity_of};
use super::filesystem::FileIdentity;
use super::platform;
use super::process::{BootId, Process, ProcessRef};
Expand Down Expand Up @@ -595,8 +596,8 @@ impl SandboxStore {
}
},
}
let locked = platform::opened_file_identity(&file).map_err(failed)?;
match platform::file_identity(&path) {
let locked = identity_of(&file).map_err(failed)?;
match identity_at(&path) {
Ok(current) if current == locked => Ok(Taken::Locked(file)),
Ok(_) => Ok(Taken::Replaced),
Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(Taken::Replaced),
Expand Down
38 changes: 36 additions & 2 deletions nvx-rs/src/host/store/regular.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! The files that the template modules read, and that a sandbox's store reads as its clone
//! binding: regular files that are not links, which they read within a bound, and the identity of
//! a file that a template module holds open.
//! binding: regular files that are not links, which they read within a bound, and the identities
//! by which the template modules and the stores tell files apart: of a file that one holds open,
//! and of the file that a path names.

use std::fs::{File, OpenOptions};
use std::io::{self, Read};
Expand Down Expand Up @@ -90,3 +91,36 @@ pub(crate) fn identity_of(file: &File) -> io::Result<(u64, u128)> {
let seal = FileSeal::of(file)?;
Ok((seal.device(), seal.file_id()))
}

/// Returns the identity of the file or directory that `path` names, following links, as
/// [`identity_of`] gives it for the object open.
///
/// On Windows, the object is opened without access rights, as [`platform::file_identity`] opens
/// it, which succeeds even while another process holds it open without sharing it. A file whose
/// deletion is pending, which a file system without POSIX deletion keeps under its name until its
/// last handle closes, refuses even that open, with a denied access.
pub(crate) fn identity_at(path: &Path) -> io::Result<(u64, u128)> {
#[cfg(unix)]
{
use std::os::unix::fs::MetadataExt;

let metadata = std::fs::metadata(path)?;
Ok((metadata.dev(), u128::from(metadata.ino())))
}
#[cfg(windows)]
{
use std::os::windows::fs::OpenOptionsExt;

/// `FILE_FLAG_BACKUP_SEMANTICS`, which opens a directory too.
const BACKUP_SEMANTICS: u32 = 0x0200_0000;
let file = OpenOptions::new()
.access_mode(0)
.custom_flags(BACKUP_SEMANTICS)
.open(path)?;
identity_of(&file)
}
#[cfg(not(any(unix, windows)))]
{
platform::file_identity(path).map(|(device, index)| (device, u128::from(index)))
}
}
7 changes: 4 additions & 3 deletions nvx-rs/src/host/store/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,9 +322,10 @@ fn a_lock_file_replaced_while_waiting_locks_nothing() {
let store = store.clone();
move || {
let locked = store.lock(None).unwrap();
// The waiter holds the lock of the file that the path names now.
let current = platform::file_identity(&locked.path(LOCK_NAME)).unwrap();
let held = platform::opened_file_identity(&locked.lock).unwrap();
// The waiter holds the lock of the file that the path names now, by the identities
// that the lock compares.
let current = identity_at(&locked.path(LOCK_NAME)).unwrap();
let held = identity_of(&locked.lock).unwrap();
assert_eq!(current, held);
}
});
Expand Down
5 changes: 3 additions & 2 deletions nvx-rs/src/host/template/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use crate::host::platform::{self, parse_hex_id};
use crate::host::process::BootId;
use crate::host::store::files::{open_lock, write_private};
use crate::host::store::lease::{ID_LEN, hex, read_private, read_record, store_error};
use crate::host::store::regular::{identity_at, identity_of};

pub use self::capture::{CAPTURE_RECORD_NAME, CAPTURES_NAME, Capture, Created, TRANSACTION_NAME};
pub use self::leases::{Acquired, LEASES_NAME, Retirement, TOMBSTONES_NAME};
Expand Down Expand Up @@ -878,8 +879,8 @@ fn take_lock(dir: &Path, deadline: Option<Instant>) -> Result<File> {
}
},
}
let locked = platform::opened_file_identity(&file).map_err(failed)?;
match platform::file_identity(&path) {
let locked = identity_of(&file).map_err(failed)?;
match identity_at(&path) {
Ok(current) if current == locked => return Ok(file),
Ok(_) => {}
Err(error) if error.kind() == io::ErrorKind::NotFound => {}
Expand Down
Loading