Add file path mapping for FileTransfer
Match by longest prefix to get host paths from guest ones
This commit is contained in:
parent
2626130659
commit
95bc64076d
2 changed files with 88 additions and 14 deletions
|
|
@ -4,6 +4,7 @@ mod vsock;
|
||||||
|
|
||||||
use bus::SharedHostedBus;
|
use bus::SharedHostedBus;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use eyre::OptionExt;
|
||||||
use futures::{TryFutureExt, stream::FuturesUnordered};
|
use futures::{TryFutureExt, stream::FuturesUnordered};
|
||||||
use std::{path::PathBuf, sync::Arc, time::Duration};
|
use std::{path::PathBuf, sync::Arc, time::Duration};
|
||||||
use tokio::{net::UnixListener, process::Command, sync::Mutex};
|
use tokio::{net::UnixListener, process::Command, sync::Mutex};
|
||||||
|
|
@ -36,6 +37,10 @@ struct BrokerCli {
|
||||||
#[clap(long, default_value = "/run/vm-doc-portal")]
|
#[clap(long, default_value = "/run/vm-doc-portal")]
|
||||||
guest_mountpoint: PathBuf,
|
guest_mountpoint: PathBuf,
|
||||||
|
|
||||||
|
/// Mappings from guest paths to host paths for passthrough file systems (for file transfer), in guest=host format
|
||||||
|
#[clap(long)]
|
||||||
|
path_mapping: Vec<String>,
|
||||||
|
|
||||||
/// Vsock port number to listen on for the VM bus
|
/// Vsock port number to listen on for the VM bus
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
vsock_port: Option<u32>,
|
vsock_port: Option<u32>,
|
||||||
|
|
@ -65,11 +70,31 @@ async fn new_hosted_bus() -> eyre::Result<(
|
||||||
Ok((Arc::new(Mutex::new(bus)), guid, owner_stream))
|
Ok((Arc::new(Mutex::new(bus)), guid, owner_stream))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_path_mapping(s: &str) -> eyre::Result<(PathBuf, PathBuf)> {
|
||||||
|
let mut split = s.split('=');
|
||||||
|
let guest_path = PathBuf::from(split.next().ok_or_eyre("failed to split mapping")?);
|
||||||
|
let host_path = PathBuf::from(split.next().ok_or_eyre("failed to split mapping")?);
|
||||||
|
Ok((guest_path, host_path))
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> eyre::Result<()> {
|
async fn main() -> eyre::Result<()> {
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
let cli = BrokerCli::parse();
|
let cli = BrokerCli::parse();
|
||||||
|
let mut path_prefix_to_host: Vec<(PathBuf, PathBuf)> = cli
|
||||||
|
.path_mapping
|
||||||
|
.iter()
|
||||||
|
.flat_map(|arg| match parse_path_mapping(arg) {
|
||||||
|
Ok(mapping) => Some(mapping),
|
||||||
|
Err(err) => {
|
||||||
|
error!(?err, %arg, "could not parse path mapping");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
path_prefix_to_host.sort_unstable_by_key(|(prefix, _)| -(prefix.as_os_str().len() as isize));
|
||||||
|
debug!(?path_prefix_to_host, "parsed path mappings");
|
||||||
|
|
||||||
let (priv_bus, _, mut priv_lst) = new_hosted_bus().await?;
|
let (priv_bus, _, mut priv_lst) = new_hosted_bus().await?;
|
||||||
|
|
||||||
|
|
@ -153,6 +178,7 @@ async fn main() -> eyre::Result<()> {
|
||||||
&host_session_conn,
|
&host_session_conn,
|
||||||
&priv_bus_conn,
|
&priv_bus_conn,
|
||||||
cli.guest_mountpoint,
|
cli.guest_mountpoint,
|
||||||
|
path_prefix_to_host,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let notification_imp = portal::notification::Notification::new(&host_session_conn).await?;
|
let notification_imp = portal::notification::Notification::new(&host_session_conn).await?;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
use std::{collections::HashMap, path::PathBuf};
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
os::fd::{AsFd, AsRawFd},
|
||||||
|
os::unix::ffi::OsStrExt,
|
||||||
|
path::PathBuf,
|
||||||
|
};
|
||||||
|
|
||||||
use tokio::sync::broadcast;
|
use tokio::sync::broadcast;
|
||||||
use tokio_stream::StreamExt;
|
use tokio_stream::StreamExt;
|
||||||
|
|
@ -14,6 +19,7 @@ pub struct FileTransfer {
|
||||||
host: FileTransferProxy<'static>,
|
host: FileTransferProxy<'static>,
|
||||||
file_transformer: FileTransformer,
|
file_transformer: FileTransformer,
|
||||||
tx: broadcast::Sender<ForwarderCommand>,
|
tx: broadcast::Sender<ForwarderCommand>,
|
||||||
|
path_prefix_to_host: Vec<(PathBuf, PathBuf)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
@ -32,13 +38,53 @@ enum ForwarderCommand {
|
||||||
impl FileTransfer {
|
impl FileTransfer {
|
||||||
async fn add_files(
|
async fn add_files(
|
||||||
&self,
|
&self,
|
||||||
_key: &str,
|
key: &str,
|
||||||
_fds: Vec<zbus::zvariant::Fd<'_>>,
|
fds: Vec<zbus::zvariant::Fd<'_>>,
|
||||||
_options: HashMap<&str, zvariant::Value<'_>>,
|
options: HashMap<&str, zvariant::Value<'_>>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
Err(zbus::fdo::Error::NotSupported(
|
let mut host_paths = Vec::with_capacity(fds.len());
|
||||||
"Adding files to transfer out is not yet implemented".to_owned(),
|
for fd in fds.iter() {
|
||||||
))
|
let link = rustix::fs::readlink(
|
||||||
|
format!("/proc/self/fd/{}", fd.as_fd().as_raw_fd()),
|
||||||
|
Vec::new(),
|
||||||
|
)
|
||||||
|
.map_err(|e| zbus::fdo::Error::Failed(e.to_string()))?;
|
||||||
|
let guest_path = std::path::PathBuf::from(std::ffi::OsStr::from_bytes(
|
||||||
|
&link.to_string_lossy().as_bytes(),
|
||||||
|
));
|
||||||
|
let (prefix, host_prefix) = self
|
||||||
|
.path_prefix_to_host
|
||||||
|
.iter()
|
||||||
|
.find(|(prefix, _)| guest_path.starts_with(prefix))
|
||||||
|
.ok_or_else(|| {
|
||||||
|
zbus::fdo::Error::Failed("Could not find host mapping for path".to_owned())
|
||||||
|
})?;
|
||||||
|
let guest_suffix = guest_path
|
||||||
|
.strip_prefix(prefix)
|
||||||
|
.map_err(|e| zbus::fdo::Error::Failed(e.to_string()))?;
|
||||||
|
let host_path = if guest_suffix.as_os_str().is_empty() {
|
||||||
|
// Edge case: a bind-mounted file exposed at the same path would get an extra '/' after its path
|
||||||
|
host_prefix.to_path_buf()
|
||||||
|
} else {
|
||||||
|
host_prefix.join(guest_suffix)
|
||||||
|
};
|
||||||
|
debug!(
|
||||||
|
?guest_path,
|
||||||
|
?prefix,
|
||||||
|
?guest_suffix,
|
||||||
|
?host_prefix,
|
||||||
|
?host_path,
|
||||||
|
"mapped path"
|
||||||
|
);
|
||||||
|
let path_fd = rustix::fs::open(
|
||||||
|
host_path,
|
||||||
|
rustix::fs::OFlags::PATH,
|
||||||
|
rustix::fs::Mode::empty(),
|
||||||
|
)
|
||||||
|
.map_err(|e| zbus::fdo::Error::Failed(e.to_string()))?;
|
||||||
|
host_paths.push(path_fd.into()); // OwnedFd variant of zbus's Fd enum, so still owned by the Vec
|
||||||
|
}
|
||||||
|
self.host.add_files(key, host_paths, options).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn retrieve_files(
|
async fn retrieve_files(
|
||||||
|
|
@ -56,7 +102,7 @@ impl FileTransfer {
|
||||||
{
|
{
|
||||||
result.push(guest_path.to_string_lossy().into_owned());
|
result.push(guest_path.to_string_lossy().into_owned());
|
||||||
} else {
|
} else {
|
||||||
debug!("could not expose path {host_path}");
|
debug!(%host_path, "could not add path as doc to retrieve file");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(result)
|
Ok(result)
|
||||||
|
|
@ -72,19 +118,19 @@ impl FileTransfer {
|
||||||
.ok_or_else(|| zbus::Error::MissingField)?
|
.ok_or_else(|| zbus::Error::MissingField)?
|
||||||
.to_owned();
|
.to_owned();
|
||||||
let key = self.host.start_transfer(options).await?;
|
let key = self.host.start_transfer(options).await?;
|
||||||
debug!("start_transfer: {key}");
|
debug!(%key, %sender, "started transfer");
|
||||||
if let Err(err) = self
|
if let Err(err) = self
|
||||||
.tx
|
.tx
|
||||||
.send(ForwarderCommand::Add(key.clone(), sender.into()))
|
.send(ForwarderCommand::Add(key.clone(), sender.into()))
|
||||||
{
|
{
|
||||||
error!("file_transfer internal channel error: {err:?}");
|
error!(?err, "file_transfer internal channel error");
|
||||||
return Err(zbus::fdo::Error::IOError("channel error".to_owned()));
|
return Err(zbus::fdo::Error::IOError("channel error".to_owned()));
|
||||||
}
|
}
|
||||||
Ok(key)
|
Ok(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn stop_transfer(&self, key: &str) -> Result<()> {
|
async fn stop_transfer(&self, key: &str) -> Result<()> {
|
||||||
debug!("stop_transfer: {key}");
|
debug!(%key, "stopping transfer");
|
||||||
self.host.stop_transfer(key).await
|
self.host.stop_transfer(key).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,6 +148,7 @@ impl FileTransfer {
|
||||||
host_session_conn: &Connection,
|
host_session_conn: &Connection,
|
||||||
priv_conn: &Connection,
|
priv_conn: &Connection,
|
||||||
guest_root: PathBuf,
|
guest_root: PathBuf,
|
||||||
|
path_prefix_to_host: Vec<(PathBuf, PathBuf)>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let host = FileTransferProxy::builder(host_session_conn)
|
let host = FileTransferProxy::builder(host_session_conn)
|
||||||
.build()
|
.build()
|
||||||
|
|
@ -119,6 +166,7 @@ impl FileTransfer {
|
||||||
host,
|
host,
|
||||||
file_transformer,
|
file_transformer,
|
||||||
tx,
|
tx,
|
||||||
|
path_prefix_to_host,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -137,15 +185,15 @@ impl FileTransfer {
|
||||||
// ForwarderCommand::Remove(key) => { receivers.remove(&key); },
|
// ForwarderCommand::Remove(key) => { receivers.remove(&key); },
|
||||||
},
|
},
|
||||||
Some(signal) = stream.next() => {
|
Some(signal) = stream.next() => {
|
||||||
debug!("transfer closed {signal:?}");
|
debug!(?signal, "transfer closed");
|
||||||
if let Ok((key,)) = signal.0.deserialize::<(&str,)>() {
|
if let Ok((key,)) = signal.0.deserialize::<(&str,)>() {
|
||||||
if let Some(bus_name) = receivers.remove(key) {
|
if let Some(bus_name) = receivers.remove(key) {
|
||||||
signal_emitter = signal_emitter.set_destination(zbus::names::BusName::Unique(bus_name.clone().into()));
|
signal_emitter = signal_emitter.set_destination(zbus::names::BusName::Unique(bus_name.clone().into()));
|
||||||
if let Err(err) = FileTransfer::transfer_closed(&signal_emitter, key).await {
|
if let Err(err) = FileTransfer::transfer_closed(&signal_emitter, key).await {
|
||||||
error!("could not forward signal for key {key}: {err:?}");
|
error!(?err, %key, "could not forward signal");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
error!("got a signal for unknown key {key}");
|
error!(%key, "got a signal for unknown key");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
error!("could not deserialize transfer closed signal");
|
error!("could not deserialize transfer closed signal");
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue