Skip to content
Open
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
2 changes: 1 addition & 1 deletion nvx-rs/README.md

Large diffs are not rendered by default.

137 changes: 132 additions & 5 deletions nvx-rs/src/host/platform/linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::fs::{self, DirBuilder, File, OpenOptions, Permissions, TryLockError};
use std::io::{self, Read};
use std::os::fd::{AsFd, AsRawFd, RawFd};
use std::os::unix::fs::{DirBuilderExt, MetadataExt, OpenOptionsExt, PermissionsExt};
use std::os::unix::net::UnixStream;
use std::os::unix::net::{UnixListener, UnixStream};
use std::path::Path;
use std::process::Command;
use std::thread;
Expand Down Expand Up @@ -639,6 +639,25 @@ pub(crate) fn opened_file_identity(file: &File) -> io::Result<(u64, u64)> {
Ok((metadata.dev(), metadata.ino()))
}

/// Returns the identity by which the stores tell a file or directory apart from every other,
/// following its links: its device and inode, as [`opened_file_id`] gives them.
pub(crate) fn file_id(path: &Path) -> io::Result<(u64, u128)> {
let metadata = fs::metadata(path)?;
Ok((metadata.dev(), u128::from(metadata.ino())))
}

/// Returns the identity by which the stores tell the file that `file` holds open apart from every
/// other: its device and inode, as its [`FileSeal`](crate::host::seal::FileSeal) holds them.
pub(crate) fn opened_file_id(file: &File) -> io::Result<(u64, u128)> {
let metadata = file.metadata()?;
Ok((metadata.dev(), u128::from(metadata.ino())))
}

/// Returns the number of names, its hard links, of the file that `file` holds open.
pub(crate) fn link_count(file: &File) -> io::Result<u64> {
Ok(file.metadata()?.nlink())
}

/// An open directory, through which operations name its entries, so that they reach the entries
/// of the directory that it opened, whatever its path names meanwhile.
pub(crate) struct Directory(File);
Expand All @@ -655,19 +674,102 @@ 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 identity of its entry `name`, not following a link, as [`opened_file_id`] gives
/// it, or `None` if it has no such entry.
pub(crate) fn entry_id(&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()),
}
}

/// Opens its entry `name` for reading, not following a link, and without waiting, as an open
/// of a FIFO without a writer would, or returns `None` if it has no such entry. The caller
/// checks what the entry is.
pub(crate) fn open_file(&self, name: &str) -> io::Result<Option<File>> {
use nix::fcntl::{OFlag, openat};
use nix::sys::stat::Mode;

match openat(
&self.0,
name,
OFlag::O_RDONLY | OFlag::O_NOFOLLOW | OFlag::O_NONBLOCK | OFlag::O_CLOEXEC,
Mode::empty(),
) {
Ok(file) => Ok(Some(File::from(file))),
Err(Errno::ENOENT) => Ok(None),
Err(errno) => Err(errno.into()),
}
}

/// Opens the directory `path` with the right to search it alone, without following a link at
/// its own name, though links on the way to it are followed, or returns `None` if it does not
/// exist. Fails if it is a link, or not a directory.
///
/// The handle names the directory's entries, and needs no right to read the directory, which
/// an owner-only directory may withhold; it cannot [sync](Self::sync) it.
pub(crate) fn search(path: &Path) -> io::Result<Option<Self>> {
use nix::fcntl::OFlag;

match OpenOptions::new()
.read(true)
.custom_flags((OFlag::O_PATH | OFlag::O_DIRECTORY | OFlag::O_NOFOLLOW).bits())
.open(path)
{
Ok(dir) => Ok(Some(Self(dir))),
Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(None),
Err(error)
if [Errno::ELOOP, Errno::ENOTDIR]
.map(|errno| Some(errno as i32))
.contains(&error.raw_os_error()) =>
{
Err(io::Error::other(format!(
"{} is a link, or not a directory",
path.display()
)))
}
Err(error) => Err(error),
}
}

/// Opens its subdirectory `name` with the right to search it alone, not following a link, as
/// [`search`](Self::search) opens a directory, or returns `None` if it has no such entry.
/// Fails if the entry is a link, or not a directory.
pub(crate) fn search_dir(&self, name: &str) -> io::Result<Option<Self>> {
self.open_dir_with(name, nix::fcntl::OFlag::O_PATH)
}

/// Opens its subdirectory `name` for reading, not following a link, or returns `None` if it
/// has no such entry. Fails if the entry is a link, or not a directory.
pub(crate) fn open_dir(&self, name: &str) -> io::Result<Option<Self>> {
self.open_dir_with(name, nix::fcntl::OFlag::O_RDONLY)
}

/// Opens its subdirectory `name` with the access `access`, not following a link, as
/// [`open_dir`](Self::open_dir) and [`search_dir`](Self::search_dir) do.
fn open_dir_with(&self, name: &str, access: nix::fcntl::OFlag) -> io::Result<Option<Self>> {
use nix::fcntl::{OFlag, openat};
use nix::sys::stat::Mode;

match openat(
&self.0,
name,
access | OFlag::O_DIRECTORY | OFlag::O_NOFOLLOW | OFlag::O_CLOEXEC,
Mode::empty(),
) {
Ok(dir) => Ok(Some(Self(File::from(dir)))),
Err(Errno::ENOENT) => Ok(None),
Err(Errno::ELOOP | Errno::ENOTDIR) => Err(io::Error::other(format!(
"{name} is a link, or not a directory"
))),
Err(errno) => Err(errno.into()),
}
}

/// Removes its file `name`, if it has one.
pub(crate) fn remove_file(&self, name: &str) -> io::Result<()> {
use nix::unistd::{UnlinkatFlags, unlinkat};
Expand Down Expand Up @@ -753,6 +855,31 @@ pub(crate) fn control_endpoint(socket_path: &Path) -> io::Result<String> {
.ok_or_else(|| io::Error::other("control socket path is not valid UTF-8"))
}

/// The listener at which the launch of a clone awaits the restore-ready event of its VMM: a Unix
/// socket, which the VMM connects to once, and which queues its connection until the host
/// accepts it.
#[derive(Debug)]
pub(crate) struct ReadyListener {
/// The listening socket, which the VMM finds as long as it is open.
_socket: UnixListener,
}

/// Listens at the Unix socket `socket_path`, in place of a socket that an earlier launch left
/// there, as the restore-ready endpoint of a clone's launch, and returns the endpoint, as
/// OpenVMM's `--restore-ready-path` takes it, and its listener.
pub(crate) fn listen_ready(socket_path: &Path) -> io::Result<(String, ReadyListener)> {
let endpoint = socket_path
.to_str()
.map(str::to_owned)
.ok_or_else(|| io::Error::other("restore-ready socket path is not valid UTF-8"))?;
match fs::remove_file(socket_path) {
Err(error) if error.kind() != io::ErrorKind::NotFound => return Err(error),
_ => {}
}
let socket = UnixListener::bind(socket_path)?;
Ok((endpoint, ReadyListener { _socket: socket }))
}

/// Connects to a control endpoint served by the process `expected_pid`.
///
/// The connection attempt never blocks: a listener whose backlog is full yields
Expand Down
26 changes: 26 additions & 0 deletions nvx-rs/src/host/platform/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,4 +171,30 @@ mod tests {
Err(error) => panic!("{error}"),
}
}

/// The stores tell files apart by the identities that their seals hold, with the 128-bit file
/// IDs of `FILE_ID_INFO` on Windows, whether they examine a path, an open file, or an entry of
/// an open directory.
#[cfg(any(target_os = "linux", windows))]
#[test]
fn the_stores_tell_files_apart_by_the_identities_that_their_seals_hold() {
use crate::host::seal::FileSeal;

let directory = tempfile::tempdir().unwrap();
let opened = Directory::open(directory.path()).unwrap();
let mut ids = Vec::new();
for name in ["first", "second"] {
let path = directory.path().join(name);
std::fs::write(&path, b"").unwrap();
let file = std::fs::File::open(&path).unwrap();
let seal = FileSeal::of(&file).unwrap();
let id = (seal.device(), seal.file_id());
assert_eq!(opened_file_id(&file).unwrap(), id, "{name}");
assert_eq!(file_id(&path).unwrap(), id, "{name}");
assert_eq!(opened.entry_id(name).unwrap(), Some(id), "{name}");
ids.push(id);
}
assert_ne!(ids[0], ids[1]);
assert_eq!(opened.entry_id("missing").unwrap(), None);
}
}
44 changes: 43 additions & 1 deletion nvx-rs/src/host/platform/other.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,20 @@ pub(crate) fn opened_file_identity(_file: &File) -> io::Result<(u64, u64)> {
Err(unsupported())
}

/// Fails: this host keeps no store.
pub(crate) fn file_id(_path: &Path) -> io::Result<(u64, u128)> {
Err(unsupported())
}

/// Fails: this host keeps no store.
pub(crate) fn opened_file_id(_file: &File) -> io::Result<(u64, u128)> {
Err(unsupported())
}

pub(crate) fn link_count(_file: &File) -> io::Result<u64> {
Err(unsupported())
}

/// An open directory, which this host never holds: it keeps no sandbox's store.
pub(crate) enum Directory {}

Expand All @@ -108,7 +122,27 @@ 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_id(&self, _name: &str) -> io::Result<Option<(u64, u128)>> {
match *self {}
}

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

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

/// Fails: this host keeps no store.
pub(crate) fn search(_path: &Path) -> io::Result<Option<Self>> {
Err(unsupported())
}

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

Expand Down Expand Up @@ -210,6 +244,14 @@ pub(crate) fn control_endpoint(_socket_path: &Path) -> io::Result<String> {
Err(unsupported())
}

/// The listener of the restore-ready endpoint of a clone's launch, which this host never makes.
#[derive(Debug)]
pub(crate) struct ReadyListener;

pub(crate) fn listen_ready(_socket_path: &Path) -> io::Result<(String, ReadyListener)> {
Err(unsupported())
}

pub(crate) fn connect_endpoint(
_endpoint: &str,
_expected_pid: u32,
Expand Down
Loading
Loading