2020-10-07 11:53:03 +00:00
|
|
|
//! Map a raw data reader as a loop device via FUSE
|
|
|
|
|
client: implement map/unmap commands for .img backups
Allows mapping fixed-index .img files (usually from VM backups) to be
mapped to a local loopback device.
The architecture uses a FUSE-backed temp file mapped to a loopdev:
/dev/loopX -> FUSE /run/pbs-loopdev/xxx -> backup client -> PBS
Since unmapping requires some cleanup (unmap the loopdev, stop FUSE,
remove the temp files) a special 'unmap' command is added, which uses a
PID file to send SIGINT to the backup-client instance started with
'map', which will handle the cleanup itself.
The polling with select! in mount.rs needs to be split in two, since we
have a chicken and egg problem between running FUSE and setting up the
loop device - so we need to do them concurrently, until the loopdev is
assigned, at which point we can report success and daemonize, and then
continue polling the FUSE loop future.
A loopdev module is added to tools containing all required functions for
mapping a loop device to the FUSE file, with the ioctls moved into an
inline module to avoid exposing them directly.
The client code is placed in the 'mount' module, which, while
admittedly a loose fit, allows reuse of the daemonizing code.
Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
2020-10-05 08:57:58 +00:00
|
|
|
use anyhow::{Error, format_err, bail};
|
|
|
|
use std::ffi::OsStr;
|
|
|
|
use std::path::{Path, PathBuf};
|
2020-10-07 11:53:05 +00:00
|
|
|
use std::fs::{File, remove_file, read_to_string, OpenOptions};
|
client: implement map/unmap commands for .img backups
Allows mapping fixed-index .img files (usually from VM backups) to be
mapped to a local loopback device.
The architecture uses a FUSE-backed temp file mapped to a loopdev:
/dev/loopX -> FUSE /run/pbs-loopdev/xxx -> backup client -> PBS
Since unmapping requires some cleanup (unmap the loopdev, stop FUSE,
remove the temp files) a special 'unmap' command is added, which uses a
PID file to send SIGINT to the backup-client instance started with
'map', which will handle the cleanup itself.
The polling with select! in mount.rs needs to be split in two, since we
have a chicken and egg problem between running FUSE and setting up the
loop device - so we need to do them concurrently, until the loopdev is
assigned, at which point we can report success and daemonize, and then
continue polling the FUSE loop future.
A loopdev module is added to tools containing all required functions for
mapping a loop device to the FUSE file, with the ioctls moved into an
inline module to avoid exposing them directly.
The client code is placed in the 'mount' module, which, while
admittedly a loose fit, allows reuse of the daemonizing code.
Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
2020-10-05 08:57:58 +00:00
|
|
|
use std::io::SeekFrom;
|
|
|
|
use std::io::prelude::*;
|
2020-10-07 11:53:05 +00:00
|
|
|
use std::collections::HashMap;
|
client: implement map/unmap commands for .img backups
Allows mapping fixed-index .img files (usually from VM backups) to be
mapped to a local loopback device.
The architecture uses a FUSE-backed temp file mapped to a loopdev:
/dev/loopX -> FUSE /run/pbs-loopdev/xxx -> backup client -> PBS
Since unmapping requires some cleanup (unmap the loopdev, stop FUSE,
remove the temp files) a special 'unmap' command is added, which uses a
PID file to send SIGINT to the backup-client instance started with
'map', which will handle the cleanup itself.
The polling with select! in mount.rs needs to be split in two, since we
have a chicken and egg problem between running FUSE and setting up the
loop device - so we need to do them concurrently, until the loopdev is
assigned, at which point we can report success and daemonize, and then
continue polling the FUSE loop future.
A loopdev module is added to tools containing all required functions for
mapping a loop device to the FUSE file, with the ioctls moved into an
inline module to avoid exposing them directly.
The client code is placed in the 'mount' module, which, while
admittedly a loose fit, allows reuse of the daemonizing code.
Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
2020-10-05 08:57:58 +00:00
|
|
|
|
2020-10-07 11:53:05 +00:00
|
|
|
use nix::unistd::Pid;
|
client: implement map/unmap commands for .img backups
Allows mapping fixed-index .img files (usually from VM backups) to be
mapped to a local loopback device.
The architecture uses a FUSE-backed temp file mapped to a loopdev:
/dev/loopX -> FUSE /run/pbs-loopdev/xxx -> backup client -> PBS
Since unmapping requires some cleanup (unmap the loopdev, stop FUSE,
remove the temp files) a special 'unmap' command is added, which uses a
PID file to send SIGINT to the backup-client instance started with
'map', which will handle the cleanup itself.
The polling with select! in mount.rs needs to be split in two, since we
have a chicken and egg problem between running FUSE and setting up the
loop device - so we need to do them concurrently, until the loopdev is
assigned, at which point we can report success and daemonize, and then
continue polling the FUSE loop future.
A loopdev module is added to tools containing all required functions for
mapping a loop device to the FUSE file, with the ioctls moved into an
inline module to avoid exposing them directly.
The client code is placed in the 'mount' module, which, while
admittedly a loose fit, allows reuse of the daemonizing code.
Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
2020-10-05 08:57:58 +00:00
|
|
|
use nix::sys::signal::{self, Signal};
|
|
|
|
|
|
|
|
use tokio::io::{AsyncRead, AsyncSeek, AsyncReadExt, AsyncSeekExt};
|
|
|
|
use futures::stream::{StreamExt, TryStreamExt};
|
|
|
|
use futures::channel::mpsc::{Sender, Receiver};
|
|
|
|
|
2020-10-07 11:53:06 +00:00
|
|
|
use proxmox::const_regex;
|
2020-10-07 11:53:07 +00:00
|
|
|
use proxmox::tools::time;
|
client: implement map/unmap commands for .img backups
Allows mapping fixed-index .img files (usually from VM backups) to be
mapped to a local loopback device.
The architecture uses a FUSE-backed temp file mapped to a loopdev:
/dev/loopX -> FUSE /run/pbs-loopdev/xxx -> backup client -> PBS
Since unmapping requires some cleanup (unmap the loopdev, stop FUSE,
remove the temp files) a special 'unmap' command is added, which uses a
PID file to send SIGINT to the backup-client instance started with
'map', which will handle the cleanup itself.
The polling with select! in mount.rs needs to be split in two, since we
have a chicken and egg problem between running FUSE and setting up the
loop device - so we need to do them concurrently, until the loopdev is
assigned, at which point we can report success and daemonize, and then
continue polling the FUSE loop future.
A loopdev module is added to tools containing all required functions for
mapping a loop device to the FUSE file, with the ioctls moved into an
inline module to avoid exposing them directly.
The client code is placed in the 'mount' module, which, while
admittedly a loose fit, allows reuse of the daemonizing code.
Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
2020-10-05 08:57:58 +00:00
|
|
|
use proxmox_fuse::{*, requests::FuseRequest};
|
|
|
|
use super::loopdev;
|
2020-10-07 11:53:05 +00:00
|
|
|
use super::fs;
|
client: implement map/unmap commands for .img backups
Allows mapping fixed-index .img files (usually from VM backups) to be
mapped to a local loopback device.
The architecture uses a FUSE-backed temp file mapped to a loopdev:
/dev/loopX -> FUSE /run/pbs-loopdev/xxx -> backup client -> PBS
Since unmapping requires some cleanup (unmap the loopdev, stop FUSE,
remove the temp files) a special 'unmap' command is added, which uses a
PID file to send SIGINT to the backup-client instance started with
'map', which will handle the cleanup itself.
The polling with select! in mount.rs needs to be split in two, since we
have a chicken and egg problem between running FUSE and setting up the
loop device - so we need to do them concurrently, until the loopdev is
assigned, at which point we can report success and daemonize, and then
continue polling the FUSE loop future.
A loopdev module is added to tools containing all required functions for
mapping a loop device to the FUSE file, with the ioctls moved into an
inline module to avoid exposing them directly.
The client code is placed in the 'mount' module, which, while
admittedly a loose fit, allows reuse of the daemonizing code.
Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
2020-10-05 08:57:58 +00:00
|
|
|
|
|
|
|
const RUN_DIR: &'static str = "/run/pbs-loopdev";
|
|
|
|
|
2020-10-07 11:53:05 +00:00
|
|
|
const_regex! {
|
|
|
|
pub LOOPDEV_REGEX = r"^loop\d+$";
|
|
|
|
}
|
|
|
|
|
2020-10-07 11:53:03 +00:00
|
|
|
/// Represents an ongoing FUSE-session that has been mapped onto a loop device.
|
|
|
|
/// Create with map_loop, then call 'main' and poll until startup_chan reports
|
|
|
|
/// success. Then, daemonize or otherwise finish setup, and continue polling
|
|
|
|
/// main's future until completion.
|
client: implement map/unmap commands for .img backups
Allows mapping fixed-index .img files (usually from VM backups) to be
mapped to a local loopback device.
The architecture uses a FUSE-backed temp file mapped to a loopdev:
/dev/loopX -> FUSE /run/pbs-loopdev/xxx -> backup client -> PBS
Since unmapping requires some cleanup (unmap the loopdev, stop FUSE,
remove the temp files) a special 'unmap' command is added, which uses a
PID file to send SIGINT to the backup-client instance started with
'map', which will handle the cleanup itself.
The polling with select! in mount.rs needs to be split in two, since we
have a chicken and egg problem between running FUSE and setting up the
loop device - so we need to do them concurrently, until the loopdev is
assigned, at which point we can report success and daemonize, and then
continue polling the FUSE loop future.
A loopdev module is added to tools containing all required functions for
mapping a loop device to the FUSE file, with the ioctls moved into an
inline module to avoid exposing them directly.
The client code is placed in the 'mount' module, which, while
admittedly a loose fit, allows reuse of the daemonizing code.
Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
2020-10-05 08:57:58 +00:00
|
|
|
pub struct FuseLoopSession<R: AsyncRead + AsyncSeek + Unpin> {
|
|
|
|
session: Option<Fuse>,
|
|
|
|
stat: libc::stat,
|
|
|
|
reader: R,
|
|
|
|
fuse_path: String,
|
|
|
|
pid_path: String,
|
|
|
|
pub loopdev_path: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<R: AsyncRead + AsyncSeek + Unpin> FuseLoopSession<R> {
|
|
|
|
|
|
|
|
/// Prepare for mapping the given reader as a block device node at
|
|
|
|
/// /dev/loopN. Creates a temporary file for FUSE and a PID file for unmap.
|
2020-10-07 11:53:05 +00:00
|
|
|
pub async fn map_loop<P: AsRef<str>>(size: u64, mut reader: R, name: P, options: &OsStr)
|
client: implement map/unmap commands for .img backups
Allows mapping fixed-index .img files (usually from VM backups) to be
mapped to a local loopback device.
The architecture uses a FUSE-backed temp file mapped to a loopdev:
/dev/loopX -> FUSE /run/pbs-loopdev/xxx -> backup client -> PBS
Since unmapping requires some cleanup (unmap the loopdev, stop FUSE,
remove the temp files) a special 'unmap' command is added, which uses a
PID file to send SIGINT to the backup-client instance started with
'map', which will handle the cleanup itself.
The polling with select! in mount.rs needs to be split in two, since we
have a chicken and egg problem between running FUSE and setting up the
loop device - so we need to do them concurrently, until the loopdev is
assigned, at which point we can report success and daemonize, and then
continue polling the FUSE loop future.
A loopdev module is added to tools containing all required functions for
mapping a loop device to the FUSE file, with the ioctls moved into an
inline module to avoid exposing them directly.
The client code is placed in the 'mount' module, which, while
admittedly a loose fit, allows reuse of the daemonizing code.
Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
2020-10-05 08:57:58 +00:00
|
|
|
-> Result<Self, Error>
|
|
|
|
{
|
|
|
|
// attempt a single read to check if the reader is configured correctly
|
|
|
|
let _ = reader.read_u8().await?;
|
|
|
|
|
|
|
|
std::fs::create_dir_all(RUN_DIR)?;
|
2020-10-07 11:53:05 +00:00
|
|
|
let mut path = PathBuf::from(RUN_DIR);
|
|
|
|
path.push(name.as_ref());
|
client: implement map/unmap commands for .img backups
Allows mapping fixed-index .img files (usually from VM backups) to be
mapped to a local loopback device.
The architecture uses a FUSE-backed temp file mapped to a loopdev:
/dev/loopX -> FUSE /run/pbs-loopdev/xxx -> backup client -> PBS
Since unmapping requires some cleanup (unmap the loopdev, stop FUSE,
remove the temp files) a special 'unmap' command is added, which uses a
PID file to send SIGINT to the backup-client instance started with
'map', which will handle the cleanup itself.
The polling with select! in mount.rs needs to be split in two, since we
have a chicken and egg problem between running FUSE and setting up the
loop device - so we need to do them concurrently, until the loopdev is
assigned, at which point we can report success and daemonize, and then
continue polling the FUSE loop future.
A loopdev module is added to tools containing all required functions for
mapping a loop device to the FUSE file, with the ioctls moved into an
inline module to avoid exposing them directly.
The client code is placed in the 'mount' module, which, while
admittedly a loose fit, allows reuse of the daemonizing code.
Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
2020-10-05 08:57:58 +00:00
|
|
|
let mut pid_path = path.clone();
|
|
|
|
pid_path.set_extension("pid");
|
|
|
|
|
2020-10-07 11:53:06 +00:00
|
|
|
// cleanup previous instance with same name
|
|
|
|
// if loopdev is actually still mapped, this will do nothing and the
|
|
|
|
// create_new below will fail as intended
|
|
|
|
cleanup_unused_run_files(Some(name.as_ref().to_owned()));
|
|
|
|
|
2020-10-07 11:53:05 +00:00
|
|
|
match OpenOptions::new().write(true).create_new(true).open(&path) {
|
|
|
|
Ok(_) => { /* file created, continue on */ },
|
|
|
|
Err(e) => {
|
|
|
|
if e.kind() == std::io::ErrorKind::AlreadyExists {
|
|
|
|
bail!("the given archive is already mapped, cannot map twice");
|
|
|
|
} else {
|
|
|
|
bail!("error while creating backing file ({:?}) - {}", &path, e);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2020-10-07 11:53:06 +00:00
|
|
|
let session = Fuse::builder("pbs-block-dev")?
|
|
|
|
.options_os(options)?
|
|
|
|
.enable_read()
|
|
|
|
.build()?
|
|
|
|
.mount(&path)?;
|
client: implement map/unmap commands for .img backups
Allows mapping fixed-index .img files (usually from VM backups) to be
mapped to a local loopback device.
The architecture uses a FUSE-backed temp file mapped to a loopdev:
/dev/loopX -> FUSE /run/pbs-loopdev/xxx -> backup client -> PBS
Since unmapping requires some cleanup (unmap the loopdev, stop FUSE,
remove the temp files) a special 'unmap' command is added, which uses a
PID file to send SIGINT to the backup-client instance started with
'map', which will handle the cleanup itself.
The polling with select! in mount.rs needs to be split in two, since we
have a chicken and egg problem between running FUSE and setting up the
loop device - so we need to do them concurrently, until the loopdev is
assigned, at which point we can report success and daemonize, and then
continue polling the FUSE loop future.
A loopdev module is added to tools containing all required functions for
mapping a loop device to the FUSE file, with the ioctls moved into an
inline module to avoid exposing them directly.
The client code is placed in the 'mount' module, which, while
admittedly a loose fit, allows reuse of the daemonizing code.
Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
2020-10-05 08:57:58 +00:00
|
|
|
|
2020-10-07 11:53:06 +00:00
|
|
|
let loopdev_path = loopdev::get_or_create_free_dev().map_err(|err| {
|
|
|
|
format_err!("loop-control GET_FREE failed - {}", err)
|
|
|
|
})?;
|
client: implement map/unmap commands for .img backups
Allows mapping fixed-index .img files (usually from VM backups) to be
mapped to a local loopback device.
The architecture uses a FUSE-backed temp file mapped to a loopdev:
/dev/loopX -> FUSE /run/pbs-loopdev/xxx -> backup client -> PBS
Since unmapping requires some cleanup (unmap the loopdev, stop FUSE,
remove the temp files) a special 'unmap' command is added, which uses a
PID file to send SIGINT to the backup-client instance started with
'map', which will handle the cleanup itself.
The polling with select! in mount.rs needs to be split in two, since we
have a chicken and egg problem between running FUSE and setting up the
loop device - so we need to do them concurrently, until the loopdev is
assigned, at which point we can report success and daemonize, and then
continue polling the FUSE loop future.
A loopdev module is added to tools containing all required functions for
mapping a loop device to the FUSE file, with the ioctls moved into an
inline module to avoid exposing them directly.
The client code is placed in the 'mount' module, which, while
admittedly a loose fit, allows reuse of the daemonizing code.
Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
2020-10-05 08:57:58 +00:00
|
|
|
|
2020-10-07 11:53:06 +00:00
|
|
|
// write pidfile so unmap can later send us a signal to exit
|
|
|
|
Self::write_pidfile(&pid_path)?;
|
client: implement map/unmap commands for .img backups
Allows mapping fixed-index .img files (usually from VM backups) to be
mapped to a local loopback device.
The architecture uses a FUSE-backed temp file mapped to a loopdev:
/dev/loopX -> FUSE /run/pbs-loopdev/xxx -> backup client -> PBS
Since unmapping requires some cleanup (unmap the loopdev, stop FUSE,
remove the temp files) a special 'unmap' command is added, which uses a
PID file to send SIGINT to the backup-client instance started with
'map', which will handle the cleanup itself.
The polling with select! in mount.rs needs to be split in two, since we
have a chicken and egg problem between running FUSE and setting up the
loop device - so we need to do them concurrently, until the loopdev is
assigned, at which point we can report success and daemonize, and then
continue polling the FUSE loop future.
A loopdev module is added to tools containing all required functions for
mapping a loop device to the FUSE file, with the ioctls moved into an
inline module to avoid exposing them directly.
The client code is placed in the 'mount' module, which, while
admittedly a loose fit, allows reuse of the daemonizing code.
Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
2020-10-05 08:57:58 +00:00
|
|
|
|
2020-10-07 11:53:06 +00:00
|
|
|
Ok(Self {
|
|
|
|
session: Some(session),
|
|
|
|
reader,
|
|
|
|
stat: minimal_stat(size as i64),
|
|
|
|
fuse_path: path.to_string_lossy().into_owned(),
|
|
|
|
pid_path: pid_path.to_string_lossy().into_owned(),
|
|
|
|
loopdev_path,
|
|
|
|
})
|
client: implement map/unmap commands for .img backups
Allows mapping fixed-index .img files (usually from VM backups) to be
mapped to a local loopback device.
The architecture uses a FUSE-backed temp file mapped to a loopdev:
/dev/loopX -> FUSE /run/pbs-loopdev/xxx -> backup client -> PBS
Since unmapping requires some cleanup (unmap the loopdev, stop FUSE,
remove the temp files) a special 'unmap' command is added, which uses a
PID file to send SIGINT to the backup-client instance started with
'map', which will handle the cleanup itself.
The polling with select! in mount.rs needs to be split in two, since we
have a chicken and egg problem between running FUSE and setting up the
loop device - so we need to do them concurrently, until the loopdev is
assigned, at which point we can report success and daemonize, and then
continue polling the FUSE loop future.
A loopdev module is added to tools containing all required functions for
mapping a loop device to the FUSE file, with the ioctls moved into an
inline module to avoid exposing them directly.
The client code is placed in the 'mount' module, which, while
admittedly a loose fit, allows reuse of the daemonizing code.
Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
2020-10-05 08:57:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn write_pidfile(path: &Path) -> Result<(), Error> {
|
|
|
|
let pid = unsafe { libc::getpid() };
|
|
|
|
let mut file = File::create(path)?;
|
|
|
|
write!(file, "{}", pid)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Runs the FUSE request loop and assigns the loop device. Will send a
|
|
|
|
/// message on startup_chan once the loop device is assigned (or assignment
|
|
|
|
/// fails). Send a message on abort_chan to trigger cleanup and exit FUSE.
|
|
|
|
/// An error on loopdev assignment does *not* automatically close the FUSE
|
|
|
|
/// handle or do cleanup, trigger abort_chan manually in case startup fails.
|
|
|
|
pub async fn main(
|
|
|
|
&mut self,
|
|
|
|
mut startup_chan: Sender<Result<(), Error>>,
|
|
|
|
abort_chan: Receiver<()>,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
|
|
|
|
if let None = self.session {
|
|
|
|
panic!("internal error: fuse_loop::main called before ::map_loop");
|
|
|
|
}
|
|
|
|
let mut session = self.session.take().unwrap().fuse();
|
|
|
|
let mut abort_chan = abort_chan.fuse();
|
|
|
|
|
|
|
|
let (loopdev_path, fuse_path) = (self.loopdev_path.clone(), self.fuse_path.clone());
|
|
|
|
tokio::task::spawn_blocking(move || {
|
|
|
|
if let Err(err) = loopdev::assign(loopdev_path, fuse_path) {
|
|
|
|
let _ = startup_chan.try_send(Err(format_err!("error while assigning loop device - {}", err)));
|
|
|
|
} else {
|
|
|
|
// device is assigned successfully, which means not only is the
|
|
|
|
// loopdev ready, but FUSE is also okay, since the assignment
|
|
|
|
// would have failed otherwise
|
|
|
|
let _ = startup_chan.try_send(Ok(()));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
let (loopdev_path, fuse_path, pid_path) =
|
|
|
|
(self.loopdev_path.clone(), self.fuse_path.clone(), self.pid_path.clone());
|
|
|
|
let cleanup = |session: futures::stream::Fuse<Fuse>| {
|
|
|
|
// only warn for errors on cleanup, if these fail nothing is lost
|
|
|
|
if let Err(err) = loopdev::unassign(&loopdev_path) {
|
|
|
|
eprintln!(
|
|
|
|
"cleanup: warning: could not unassign file {} from loop device {} - {}",
|
|
|
|
&fuse_path,
|
|
|
|
&loopdev_path,
|
|
|
|
err,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// force close FUSE handle before attempting to remove backing file
|
|
|
|
std::mem::drop(session);
|
|
|
|
|
|
|
|
if let Err(err) = remove_file(&fuse_path) {
|
|
|
|
eprintln!(
|
|
|
|
"cleanup: warning: could not remove temporary file {} - {}",
|
|
|
|
&fuse_path,
|
|
|
|
err,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if let Err(err) = remove_file(&pid_path) {
|
|
|
|
eprintln!(
|
|
|
|
"cleanup: warning: could not remove PID file {} - {}",
|
|
|
|
&pid_path,
|
|
|
|
err,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
loop {
|
|
|
|
tokio::select!{
|
|
|
|
_ = abort_chan.next() => {
|
|
|
|
// aborted, do cleanup and exit
|
|
|
|
break;
|
|
|
|
},
|
|
|
|
req = session.try_next() => {
|
|
|
|
let res = match req? {
|
|
|
|
Some(Request::Lookup(req)) => {
|
|
|
|
let stat = self.stat;
|
|
|
|
let entry = EntryParam::simple(stat.st_ino, stat);
|
|
|
|
req.reply(&entry)
|
|
|
|
},
|
|
|
|
Some(Request::Getattr(req)) => {
|
|
|
|
req.reply(&self.stat, std::f64::MAX)
|
|
|
|
},
|
|
|
|
Some(Request::Read(req)) => {
|
|
|
|
match self.reader.seek(SeekFrom::Start(req.offset)).await {
|
|
|
|
Ok(_) => {
|
|
|
|
let mut buf = vec![0u8; req.size];
|
|
|
|
match self.reader.read_exact(&mut buf).await {
|
|
|
|
Ok(_) => {
|
|
|
|
req.reply(&buf)
|
|
|
|
},
|
|
|
|
Err(e) => {
|
|
|
|
req.io_fail(e)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Err(e) => {
|
|
|
|
req.io_fail(e)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Some(_) => {
|
|
|
|
// only FUSE requests necessary for loop-mapping are implemented
|
|
|
|
eprintln!("Unimplemented FUSE request type encountered");
|
|
|
|
Ok(())
|
|
|
|
},
|
|
|
|
None => {
|
|
|
|
// FUSE connection closed
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
if let Err(err) = res {
|
|
|
|
// error during FUSE reply, cleanup and exit
|
|
|
|
cleanup(session);
|
|
|
|
bail!(err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// non-error FUSE exit
|
|
|
|
cleanup(session);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-07 11:53:06 +00:00
|
|
|
/// Clean up leftover files as well as FUSE instances without a loop device
|
|
|
|
/// connected. Best effort, never returns an error.
|
|
|
|
/// If filter_name is Some("..."), only this name will be cleaned up.
|
|
|
|
pub fn cleanup_unused_run_files(filter_name: Option<String>) {
|
|
|
|
if let Ok(maps) = find_all_mappings() {
|
|
|
|
for (name, loopdev) in maps {
|
|
|
|
if loopdev.is_none() &&
|
|
|
|
(filter_name.is_none() || &name == filter_name.as_ref().unwrap())
|
|
|
|
{
|
|
|
|
let mut path = PathBuf::from(RUN_DIR);
|
|
|
|
path.push(&name);
|
|
|
|
|
|
|
|
// clean leftover FUSE instances (e.g. user called 'losetup -d' or similar)
|
|
|
|
// does nothing if files are already stagnant (e.g. instance crashed etc...)
|
2020-10-07 11:53:08 +00:00
|
|
|
if let Ok(_) = unmap_from_backing(&path, None) {
|
2020-10-07 11:53:06 +00:00
|
|
|
// we have reaped some leftover instance, tell the user
|
|
|
|
eprintln!(
|
|
|
|
"Cleaned up dangling mapping '{}': no loop device assigned",
|
|
|
|
&name
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// remove remnant files
|
|
|
|
// these we're not doing anything, so no need to inform the user
|
|
|
|
let _ = remove_file(&path);
|
|
|
|
path.set_extension("pid");
|
|
|
|
let _ = remove_file(&path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-07 11:53:05 +00:00
|
|
|
fn get_backing_file(loopdev: &str) -> Result<String, Error> {
|
client: implement map/unmap commands for .img backups
Allows mapping fixed-index .img files (usually from VM backups) to be
mapped to a local loopback device.
The architecture uses a FUSE-backed temp file mapped to a loopdev:
/dev/loopX -> FUSE /run/pbs-loopdev/xxx -> backup client -> PBS
Since unmapping requires some cleanup (unmap the loopdev, stop FUSE,
remove the temp files) a special 'unmap' command is added, which uses a
PID file to send SIGINT to the backup-client instance started with
'map', which will handle the cleanup itself.
The polling with select! in mount.rs needs to be split in two, since we
have a chicken and egg problem between running FUSE and setting up the
loop device - so we need to do them concurrently, until the loopdev is
assigned, at which point we can report success and daemonize, and then
continue polling the FUSE loop future.
A loopdev module is added to tools containing all required functions for
mapping a loop device to the FUSE file, with the ioctls moved into an
inline module to avoid exposing them directly.
The client code is placed in the 'mount' module, which, while
admittedly a loose fit, allows reuse of the daemonizing code.
Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
2020-10-05 08:57:58 +00:00
|
|
|
let num = loopdev.split_at(9).1.parse::<u8>().map_err(|err|
|
|
|
|
format_err!("malformed loopdev path, does not end with valid number - {}", err))?;
|
|
|
|
|
|
|
|
let block_path = PathBuf::from(format!("/sys/devices/virtual/block/loop{}/loop/backing_file", num));
|
|
|
|
let backing_file = read_to_string(block_path).map_err(|err| {
|
|
|
|
if err.kind() == std::io::ErrorKind::NotFound {
|
|
|
|
format_err!("nothing mapped to {}", loopdev)
|
|
|
|
} else {
|
|
|
|
format_err!("error reading backing file - {}", err)
|
|
|
|
}
|
|
|
|
})?;
|
|
|
|
|
|
|
|
let backing_file = backing_file.trim();
|
2020-10-07 11:53:05 +00:00
|
|
|
|
client: implement map/unmap commands for .img backups
Allows mapping fixed-index .img files (usually from VM backups) to be
mapped to a local loopback device.
The architecture uses a FUSE-backed temp file mapped to a loopdev:
/dev/loopX -> FUSE /run/pbs-loopdev/xxx -> backup client -> PBS
Since unmapping requires some cleanup (unmap the loopdev, stop FUSE,
remove the temp files) a special 'unmap' command is added, which uses a
PID file to send SIGINT to the backup-client instance started with
'map', which will handle the cleanup itself.
The polling with select! in mount.rs needs to be split in two, since we
have a chicken and egg problem between running FUSE and setting up the
loop device - so we need to do them concurrently, until the loopdev is
assigned, at which point we can report success and daemonize, and then
continue polling the FUSE loop future.
A loopdev module is added to tools containing all required functions for
mapping a loop device to the FUSE file, with the ioctls moved into an
inline module to avoid exposing them directly.
The client code is placed in the 'mount' module, which, while
admittedly a loose fit, allows reuse of the daemonizing code.
Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
2020-10-05 08:57:58 +00:00
|
|
|
if !backing_file.starts_with(RUN_DIR) {
|
|
|
|
bail!(
|
|
|
|
"loopdev {} is in use, but not by proxmox-backup-client (mapped to '{}')",
|
|
|
|
loopdev,
|
|
|
|
backing_file,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-10-07 11:53:05 +00:00
|
|
|
Ok(backing_file.to_owned())
|
|
|
|
}
|
|
|
|
|
2020-10-07 11:53:08 +00:00
|
|
|
// call in broken state: we found the mapping, but the client is already dead,
|
|
|
|
// only thing to do is clean up what we can
|
|
|
|
fn emerg_cleanup (loopdev: Option<&str>, mut backing_file: PathBuf) {
|
|
|
|
eprintln!(
|
|
|
|
"warning: found mapping with dead process ({:?}), attempting cleanup",
|
|
|
|
&backing_file
|
|
|
|
);
|
|
|
|
|
|
|
|
if let Some(loopdev) = loopdev {
|
|
|
|
let _ = loopdev::unassign(loopdev);
|
|
|
|
}
|
|
|
|
|
|
|
|
// killing the backing process does not cancel the FUSE mount automatically
|
|
|
|
let mut command = std::process::Command::new("fusermount");
|
|
|
|
command.arg("-u");
|
|
|
|
command.arg(&backing_file);
|
|
|
|
let _ = crate::tools::run_command(command, None);
|
|
|
|
|
|
|
|
let _ = remove_file(&backing_file);
|
|
|
|
backing_file.set_extension("pid");
|
|
|
|
let _ = remove_file(&backing_file);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn unmap_from_backing(backing_file: &Path, loopdev: Option<&str>) -> Result<(), Error> {
|
client: implement map/unmap commands for .img backups
Allows mapping fixed-index .img files (usually from VM backups) to be
mapped to a local loopback device.
The architecture uses a FUSE-backed temp file mapped to a loopdev:
/dev/loopX -> FUSE /run/pbs-loopdev/xxx -> backup client -> PBS
Since unmapping requires some cleanup (unmap the loopdev, stop FUSE,
remove the temp files) a special 'unmap' command is added, which uses a
PID file to send SIGINT to the backup-client instance started with
'map', which will handle the cleanup itself.
The polling with select! in mount.rs needs to be split in two, since we
have a chicken and egg problem between running FUSE and setting up the
loop device - so we need to do them concurrently, until the loopdev is
assigned, at which point we can report success and daemonize, and then
continue polling the FUSE loop future.
A loopdev module is added to tools containing all required functions for
mapping a loop device to the FUSE file, with the ioctls moved into an
inline module to avoid exposing them directly.
The client code is placed in the 'mount' module, which, while
admittedly a loose fit, allows reuse of the daemonizing code.
Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
2020-10-05 08:57:58 +00:00
|
|
|
let mut pid_path = PathBuf::from(backing_file);
|
|
|
|
pid_path.set_extension("pid");
|
|
|
|
|
2020-10-07 11:53:08 +00:00
|
|
|
let pid_str = read_to_string(&pid_path).map_err(|err| {
|
|
|
|
if err.kind() == std::io::ErrorKind::NotFound {
|
|
|
|
emerg_cleanup(loopdev, backing_file.to_owned());
|
|
|
|
}
|
|
|
|
format_err!("error reading pidfile {:?}: {}", &pid_path, err)
|
|
|
|
})?;
|
client: implement map/unmap commands for .img backups
Allows mapping fixed-index .img files (usually from VM backups) to be
mapped to a local loopback device.
The architecture uses a FUSE-backed temp file mapped to a loopdev:
/dev/loopX -> FUSE /run/pbs-loopdev/xxx -> backup client -> PBS
Since unmapping requires some cleanup (unmap the loopdev, stop FUSE,
remove the temp files) a special 'unmap' command is added, which uses a
PID file to send SIGINT to the backup-client instance started with
'map', which will handle the cleanup itself.
The polling with select! in mount.rs needs to be split in two, since we
have a chicken and egg problem between running FUSE and setting up the
loop device - so we need to do them concurrently, until the loopdev is
assigned, at which point we can report success and daemonize, and then
continue polling the FUSE loop future.
A loopdev module is added to tools containing all required functions for
mapping a loop device to the FUSE file, with the ioctls moved into an
inline module to avoid exposing them directly.
The client code is placed in the 'mount' module, which, while
admittedly a loose fit, allows reuse of the daemonizing code.
Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
2020-10-05 08:57:58 +00:00
|
|
|
let pid = pid_str.parse::<i32>().map_err(|err|
|
|
|
|
format_err!("malformed PID ({}) in pidfile - {}", pid_str, err))?;
|
|
|
|
|
2020-10-07 11:53:07 +00:00
|
|
|
let pid = Pid::from_raw(pid);
|
|
|
|
|
client: implement map/unmap commands for .img backups
Allows mapping fixed-index .img files (usually from VM backups) to be
mapped to a local loopback device.
The architecture uses a FUSE-backed temp file mapped to a loopdev:
/dev/loopX -> FUSE /run/pbs-loopdev/xxx -> backup client -> PBS
Since unmapping requires some cleanup (unmap the loopdev, stop FUSE,
remove the temp files) a special 'unmap' command is added, which uses a
PID file to send SIGINT to the backup-client instance started with
'map', which will handle the cleanup itself.
The polling with select! in mount.rs needs to be split in two, since we
have a chicken and egg problem between running FUSE and setting up the
loop device - so we need to do them concurrently, until the loopdev is
assigned, at which point we can report success and daemonize, and then
continue polling the FUSE loop future.
A loopdev module is added to tools containing all required functions for
mapping a loop device to the FUSE file, with the ioctls moved into an
inline module to avoid exposing them directly.
The client code is placed in the 'mount' module, which, while
admittedly a loose fit, allows reuse of the daemonizing code.
Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
2020-10-05 08:57:58 +00:00
|
|
|
// send SIGINT to trigger cleanup and exit in target process
|
2020-10-07 11:53:08 +00:00
|
|
|
match signal::kill(pid, Signal::SIGINT) {
|
|
|
|
Ok(()) => {},
|
|
|
|
Err(nix::Error::Sys(nix::errno::Errno::ESRCH)) => {
|
|
|
|
emerg_cleanup(loopdev, backing_file.to_owned());
|
|
|
|
return Ok(());
|
|
|
|
},
|
|
|
|
Err(e) => return Err(e.into()),
|
|
|
|
}
|
2020-10-07 11:53:07 +00:00
|
|
|
|
|
|
|
// block until unmap is complete or timeout
|
|
|
|
let start = time::epoch_i64();
|
|
|
|
loop {
|
|
|
|
match signal::kill(pid, None) {
|
|
|
|
Ok(_) => {
|
|
|
|
// 10 second timeout, then assume failure
|
|
|
|
if (time::epoch_i64() - start) > 10 {
|
|
|
|
return Err(format_err!("timed out waiting for PID '{}' to exit", &pid));
|
|
|
|
}
|
|
|
|
std::thread::sleep(std::time::Duration::from_millis(100));
|
|
|
|
},
|
|
|
|
Err(nix::Error::Sys(nix::errno::Errno::ESRCH)) => {
|
|
|
|
break;
|
|
|
|
},
|
|
|
|
Err(e) => return Err(e.into()),
|
|
|
|
}
|
|
|
|
}
|
client: implement map/unmap commands for .img backups
Allows mapping fixed-index .img files (usually from VM backups) to be
mapped to a local loopback device.
The architecture uses a FUSE-backed temp file mapped to a loopdev:
/dev/loopX -> FUSE /run/pbs-loopdev/xxx -> backup client -> PBS
Since unmapping requires some cleanup (unmap the loopdev, stop FUSE,
remove the temp files) a special 'unmap' command is added, which uses a
PID file to send SIGINT to the backup-client instance started with
'map', which will handle the cleanup itself.
The polling with select! in mount.rs needs to be split in two, since we
have a chicken and egg problem between running FUSE and setting up the
loop device - so we need to do them concurrently, until the loopdev is
assigned, at which point we can report success and daemonize, and then
continue polling the FUSE loop future.
A loopdev module is added to tools containing all required functions for
mapping a loop device to the FUSE file, with the ioctls moved into an
inline module to avoid exposing them directly.
The client code is placed in the 'mount' module, which, while
admittedly a loose fit, allows reuse of the daemonizing code.
Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
2020-10-05 08:57:58 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-10-07 11:53:05 +00:00
|
|
|
/// Returns an Iterator over a set of currently active mappings, i.e.
|
|
|
|
/// FuseLoopSession instances. Returns ("backing-file-name", Some("/dev/loopX"))
|
|
|
|
/// where .1 is None when a user has manually called 'losetup -d' or similar but
|
|
|
|
/// the FUSE instance is still running.
|
|
|
|
pub fn find_all_mappings() -> Result<impl Iterator<Item = (String, Option<String>)>, Error> {
|
|
|
|
// get map of all /dev/loop mappings belonging to us
|
|
|
|
let mut loopmap = HashMap::new();
|
|
|
|
for ent in fs::scan_subdir(libc::AT_FDCWD, Path::new("/dev/"), &LOOPDEV_REGEX)? {
|
|
|
|
match ent {
|
|
|
|
Ok(ent) => {
|
|
|
|
let loopdev = format!("/dev/{}", ent.file_name().to_string_lossy());
|
|
|
|
match get_backing_file(&loopdev) {
|
|
|
|
Ok(file) => {
|
|
|
|
// insert filename only, strip RUN_DIR/
|
|
|
|
loopmap.insert(file[RUN_DIR.len()+1..].to_owned(), loopdev);
|
|
|
|
},
|
|
|
|
Err(_) => {},
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Err(_) => {},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(fs::read_subdir(libc::AT_FDCWD, Path::new(RUN_DIR))?
|
|
|
|
.filter_map(move |ent| {
|
|
|
|
match ent {
|
|
|
|
Ok(ent) => {
|
|
|
|
let file = ent.file_name().to_string_lossy();
|
|
|
|
if file == "." || file == ".." || file.ends_with(".pid") {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
let loopdev = loopmap.get(file.as_ref()).map(String::to_owned);
|
|
|
|
Some((file.into_owned(), loopdev))
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Err(_) => None,
|
|
|
|
}
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Try and unmap a running proxmox-backup-client instance from the given
|
|
|
|
/// /dev/loopN device
|
|
|
|
pub fn unmap_loopdev<S: AsRef<str>>(loopdev: S) -> Result<(), Error> {
|
|
|
|
let loopdev = loopdev.as_ref();
|
|
|
|
if loopdev.len() < 10 || !loopdev.starts_with("/dev/loop") {
|
|
|
|
bail!("malformed loopdev path, must be in format '/dev/loopX'");
|
|
|
|
}
|
|
|
|
|
|
|
|
let backing_file = get_backing_file(loopdev)?;
|
2020-10-07 11:53:08 +00:00
|
|
|
unmap_from_backing(Path::new(&backing_file), Some(loopdev))
|
2020-10-07 11:53:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Try and unmap a running proxmox-backup-client instance from the given name
|
|
|
|
pub fn unmap_name<S: AsRef<str>>(name: S) -> Result<(), Error> {
|
2020-10-07 11:53:08 +00:00
|
|
|
for (mapping, loopdev) in find_all_mappings()? {
|
2020-10-07 11:53:05 +00:00
|
|
|
if mapping.ends_with(name.as_ref()) {
|
|
|
|
let mut path = PathBuf::from(RUN_DIR);
|
|
|
|
path.push(&mapping);
|
2020-10-07 11:53:08 +00:00
|
|
|
return unmap_from_backing(&path, loopdev.as_deref());
|
2020-10-07 11:53:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(format_err!("no mapping for name '{}' found", name.as_ref()))
|
|
|
|
}
|
|
|
|
|
client: implement map/unmap commands for .img backups
Allows mapping fixed-index .img files (usually from VM backups) to be
mapped to a local loopback device.
The architecture uses a FUSE-backed temp file mapped to a loopdev:
/dev/loopX -> FUSE /run/pbs-loopdev/xxx -> backup client -> PBS
Since unmapping requires some cleanup (unmap the loopdev, stop FUSE,
remove the temp files) a special 'unmap' command is added, which uses a
PID file to send SIGINT to the backup-client instance started with
'map', which will handle the cleanup itself.
The polling with select! in mount.rs needs to be split in two, since we
have a chicken and egg problem between running FUSE and setting up the
loop device - so we need to do them concurrently, until the loopdev is
assigned, at which point we can report success and daemonize, and then
continue polling the FUSE loop future.
A loopdev module is added to tools containing all required functions for
mapping a loop device to the FUSE file, with the ioctls moved into an
inline module to avoid exposing them directly.
The client code is placed in the 'mount' module, which, while
admittedly a loose fit, allows reuse of the daemonizing code.
Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
2020-10-05 08:57:58 +00:00
|
|
|
fn minimal_stat(size: i64) -> libc::stat {
|
|
|
|
let mut stat: libc::stat = unsafe { std::mem::zeroed() };
|
|
|
|
stat.st_mode = libc::S_IFREG;
|
|
|
|
stat.st_ino = 1;
|
|
|
|
stat.st_nlink = 1;
|
|
|
|
stat.st_size = size;
|
|
|
|
stat
|
|
|
|
}
|