2020-10-07 11:53:04 +00:00
|
|
|
//! Helpers to work with /dev/loop* devices
|
|
|
|
|
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::fs::{File, OpenOptions};
|
|
|
|
use std::path::Path;
|
|
|
|
use std::os::unix::io::{RawFd, AsRawFd};
|
|
|
|
|
2021-07-21 12:12:22 +00:00
|
|
|
use anyhow::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
|
|
|
const LOOP_CONTROL: &str = "/dev/loop-control";
|
|
|
|
const LOOP_NAME: &str = "/dev/loop";
|
|
|
|
|
|
|
|
/// Implements a subset of loop device ioctls necessary to assign and release
|
|
|
|
/// a single file from a free loopdev.
|
|
|
|
mod loop_ioctl {
|
|
|
|
use nix::{ioctl_none, ioctl_write_int_bad, ioctl_write_ptr_bad};
|
|
|
|
|
|
|
|
const LOOP_IOCTL: u16 = 0x4C; // 'L'
|
|
|
|
const LOOP_SET_FD: u16 = 0x00;
|
|
|
|
const LOOP_CLR_FD: u16 = 0x01;
|
|
|
|
const LOOP_SET_STATUS64: u16 = 0x04;
|
|
|
|
|
|
|
|
const LOOP_CTRL_GET_FREE: u16 = 0x82;
|
|
|
|
|
|
|
|
ioctl_write_int_bad!(ioctl_set_fd, (LOOP_IOCTL << 8) | LOOP_SET_FD);
|
|
|
|
ioctl_none!(ioctl_clr_fd, LOOP_IOCTL, LOOP_CLR_FD);
|
|
|
|
ioctl_none!(ioctl_ctrl_get_free, LOOP_IOCTL, LOOP_CTRL_GET_FREE);
|
|
|
|
ioctl_write_ptr_bad!(ioctl_set_status64, (LOOP_IOCTL << 8) | LOOP_SET_STATUS64, LoopInfo64);
|
|
|
|
|
|
|
|
pub const LO_FLAGS_READ_ONLY: u32 = 1;
|
|
|
|
pub const LO_FLAGS_PARTSCAN: u32 = 8;
|
|
|
|
|
|
|
|
const LO_NAME_SIZE: usize = 64;
|
|
|
|
const LO_KEY_SIZE: usize = 32;
|
|
|
|
|
|
|
|
#[repr(C)]
|
|
|
|
pub struct LoopInfo64 {
|
|
|
|
pub lo_device: u64,
|
|
|
|
pub lo_inode: u64,
|
|
|
|
pub lo_rdevice: u64,
|
|
|
|
pub lo_offset: u64,
|
|
|
|
pub lo_sizelimit: u64,
|
|
|
|
pub lo_number: u32,
|
|
|
|
pub lo_encrypt_type: u32,
|
|
|
|
pub lo_encrypt_key_size: u32,
|
|
|
|
pub lo_flags: u32,
|
|
|
|
pub lo_file_name: [u8; LO_NAME_SIZE],
|
|
|
|
pub lo_crypt_name: [u8; LO_NAME_SIZE],
|
|
|
|
pub lo_encrypt_key: [u8; LO_KEY_SIZE],
|
|
|
|
pub lo_init: [u64; 2],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ioctl helpers create public fns, do not export them outside the module
|
|
|
|
// users should use the wrapper functions below
|
|
|
|
use loop_ioctl::*;
|
|
|
|
|
|
|
|
/// Use the GET_FREE ioctl to get or add a free loop device, of which the
|
|
|
|
/// /dev/loopN path will be returned. This is inherently racy because of the
|
|
|
|
/// delay between this and calling assign, but since assigning is atomic it
|
|
|
|
/// does not matter much and will simply cause assign to fail.
|
|
|
|
pub fn get_or_create_free_dev() -> Result<String, Error> {
|
|
|
|
let ctrl_file = File::open(LOOP_CONTROL)?;
|
|
|
|
let free_num = unsafe { ioctl_ctrl_get_free(ctrl_file.as_raw_fd())? };
|
|
|
|
let loop_file_path = format!("{}{}", LOOP_NAME, free_num);
|
|
|
|
Ok(loop_file_path)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn assign_dev(fd: RawFd, backing_fd: RawFd) -> Result<(), Error> {
|
|
|
|
unsafe { ioctl_set_fd(fd, backing_fd)?; }
|
|
|
|
|
|
|
|
// set required read-only flag and partscan for convenience
|
|
|
|
let mut info: LoopInfo64 = unsafe { std::mem::zeroed() };
|
|
|
|
info.lo_flags = LO_FLAGS_READ_ONLY | LO_FLAGS_PARTSCAN;
|
|
|
|
unsafe { ioctl_set_status64(fd, &info)?; }
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Open the next available /dev/loopN file and assign the given path to
|
|
|
|
/// it as it's backing file in read-only mode.
|
|
|
|
pub fn assign<P: AsRef<Path>>(loop_dev: P, backing: P) -> Result<(), Error> {
|
|
|
|
let loop_file = File::open(loop_dev)?;
|
|
|
|
let backing_file = OpenOptions::new()
|
|
|
|
.read(true)
|
|
|
|
.open(backing)?;
|
|
|
|
assign_dev(loop_file.as_raw_fd(), backing_file.as_raw_fd())?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Unassign any file descriptors currently attached to the given
|
|
|
|
/// /dev/loopN device.
|
|
|
|
pub fn unassign<P: AsRef<Path>>(path: P) -> Result<(), Error> {
|
|
|
|
let loop_file = File::open(path)?;
|
|
|
|
unsafe { ioctl_clr_fd(loop_file.as_raw_fd())?; }
|
|
|
|
Ok(())
|
|
|
|
}
|