Add file path mapping for FileTransfer

Match by longest prefix to get host paths from guest ones
This commit is contained in:
Val Packett 2026-02-12 22:15:40 -03:00
parent 2626130659
commit 95bc64076d
2 changed files with 88 additions and 14 deletions

View file

@ -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_stream::StreamExt;
@ -14,6 +19,7 @@ pub struct FileTransfer {
host: FileTransferProxy<'static>,
file_transformer: FileTransformer,
tx: broadcast::Sender<ForwarderCommand>,
path_prefix_to_host: Vec<(PathBuf, PathBuf)>,
}
#[derive(Clone, Debug)]
@ -32,13 +38,53 @@ enum ForwarderCommand {
impl FileTransfer {
async fn add_files(
&self,
_key: &str,
_fds: Vec<zbus::zvariant::Fd<'_>>,
_options: HashMap<&str, zvariant::Value<'_>>,
key: &str,
fds: Vec<zbus::zvariant::Fd<'_>>,
options: HashMap<&str, zvariant::Value<'_>>,
) -> Result<()> {
Err(zbus::fdo::Error::NotSupported(
"Adding files to transfer out is not yet implemented".to_owned(),
))
let mut host_paths = Vec::with_capacity(fds.len());
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(
@ -56,7 +102,7 @@ impl FileTransfer {
{
result.push(guest_path.to_string_lossy().into_owned());
} else {
debug!("could not expose path {host_path}");
debug!(%host_path, "could not add path as doc to retrieve file");
}
}
Ok(result)
@ -72,19 +118,19 @@ impl FileTransfer {
.ok_or_else(|| zbus::Error::MissingField)?
.to_owned();
let key = self.host.start_transfer(options).await?;
debug!("start_transfer: {key}");
debug!(%key, %sender, "started transfer");
if let Err(err) = self
.tx
.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()));
}
Ok(key)
}
async fn stop_transfer(&self, key: &str) -> Result<()> {
debug!("stop_transfer: {key}");
debug!(%key, "stopping transfer");
self.host.stop_transfer(key).await
}
@ -102,6 +148,7 @@ impl FileTransfer {
host_session_conn: &Connection,
priv_conn: &Connection,
guest_root: PathBuf,
path_prefix_to_host: Vec<(PathBuf, PathBuf)>,
) -> Result<Self> {
let host = FileTransferProxy::builder(host_session_conn)
.build()
@ -119,6 +166,7 @@ impl FileTransfer {
host,
file_transformer,
tx,
path_prefix_to_host,
})
}
@ -137,15 +185,15 @@ impl FileTransfer {
// ForwarderCommand::Remove(key) => { receivers.remove(&key); },
},
Some(signal) = stream.next() => {
debug!("transfer closed {signal:?}");
debug!(?signal, "transfer closed");
if let Ok((key,)) = signal.0.deserialize::<(&str,)>() {
if let Some(bus_name) = receivers.remove(key) {
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 {
error!("could not forward signal for key {key}: {err:?}");
error!(?err, %key, "could not forward signal");
}
} else {
error!("got a signal for unknown key {key}");
error!(%key, "got a signal for unknown key");
}
} else {
error!("could not deserialize transfer closed signal");