hypha: implement loop device support directly to avoid devd

This commit is contained in:
Alex Zenla 2024-01-21 00:13:05 -08:00
parent 7d9652636f
commit ba156e43da
No known key found for this signature in database
GPG Key ID: 067B238899B51269
12 changed files with 765 additions and 21 deletions

View File

@ -5,5 +5,6 @@ members = [
"xencall",
"xenclient",
"hypha",
"loopdev",
]
resolver = "2"

View File

@ -7,6 +7,9 @@ resolver = "2"
[dependencies.xenclient]
path = "../xenclient"
[dependencies.loopdev]
path = "../loopdev"
[dependencies]
log = "0.4.20"
env_logger = "0.11.0"

36
hypha/src/autoloop.rs Normal file
View File

@ -0,0 +1,36 @@
use crate::error::{HyphaError, Result};
use loopdev::{LoopControl, LoopDevice};
use std::path::Path;
use xenclient::BlockDeviceRef;
pub struct AutoLoop {
control: LoopControl,
}
impl AutoLoop {
pub(crate) fn new(control: LoopControl) -> AutoLoop {
AutoLoop { control }
}
pub fn loopify(&self, file: &Path) -> Result<BlockDeviceRef> {
let device = self.control.next_free()?;
device.with().read_only(true).attach(file)?;
let path = device
.path()
.ok_or(HyphaError::new("unable to get loop device path"))?
.to_str()
.ok_or(HyphaError::new(
"unable to convert loop device path to string",
))?
.to_string();
let major = device.major()?;
let minor = device.minor()?;
Ok(BlockDeviceRef { path, major, minor })
}
pub fn unloop(&self, device: &str) -> Result<()> {
let device = LoopDevice::open(device)?;
device.detach()?;
Ok(())
}
}

View File

@ -9,15 +9,21 @@ use uuid::Uuid;
pub struct ConfigBlock<'a> {
pub image_info: &'a ImageInfo,
pub file: PathBuf,
pub dir: PathBuf,
}
impl ConfigBlock<'_> {
pub fn new<'a>(uuid: &Uuid, image_info: &'a ImageInfo) -> Result<ConfigBlock<'a>> {
let mut file = std::env::temp_dir().clone();
file.push(format!("hypha-cfg-{}", uuid));
fs::create_dir_all(&file)?;
let mut dir = std::env::temp_dir().clone();
dir.push(format!("hypha-cfg-{}", uuid));
fs::create_dir_all(&dir)?;
let mut file = dir.clone();
file.push("config.squashfs");
Ok(ConfigBlock { image_info, file })
Ok(ConfigBlock {
image_info,
file,
dir,
})
}
pub fn build(&self) -> Result<()> {

View File

@ -1,10 +1,12 @@
mod cfgblk;
pub mod cfgblk;
use crate::autoloop::AutoLoop;
use crate::ctl::cfgblk::ConfigBlock;
use crate::error::{HyphaError, Result};
use crate::error::Result;
use crate::image::cache::ImageCache;
use crate::image::name::ImageName;
use crate::image::{ImageCompiler, ImageInfo};
use loopdev::LoopControl;
use std::fs;
use std::path::PathBuf;
use uuid::Uuid;
@ -12,6 +14,7 @@ use xenclient::{DomainConfig, DomainDisk, XenClient};
pub struct Controller {
image_cache: ImageCache,
autoloop: AutoLoop,
image: String,
client: XenClient,
kernel_path: String,
@ -39,6 +42,7 @@ impl Controller {
let image_cache = ImageCache::new(&image_cache_path)?;
Ok(Controller {
image_cache,
autoloop: AutoLoop::new(LoopControl::open()?),
image,
client,
kernel_path,
@ -60,14 +64,13 @@ impl Controller {
let image_info = self.compile()?;
let cfgblk = ConfigBlock::new(&uuid, &image_info)?;
cfgblk.build()?;
let cfgblk_squashfs_path = cfgblk
.file
.to_str()
.ok_or_else(|| HyphaError::new("failed to convert config squashfs path to string"))?;
let image_squashfs_path = image_info
.image_squashfs
.to_str()
.ok_or_else(|| HyphaError::new("failed to convert image squashfs path to string"))?;
let image_squashfs_path = image_info.image_squashfs.clone();
let cfgblk_squashfs_path = cfgblk.file.clone();
let image_squashfs_loop = self.autoloop.loopify(&image_squashfs_path)?;
let cfgblk_squashfs_loop = self.autoloop.loopify(&cfgblk_squashfs_path)?;
let config = DomainConfig {
backend_domid: 0,
name: &name,
@ -79,17 +82,24 @@ impl Controller {
disks: vec![
DomainDisk {
vdev: "xvda",
pdev: image_squashfs_path,
block: &image_squashfs_loop,
writable: false,
},
DomainDisk {
vdev: "xvdb",
pdev: cfgblk_squashfs_path,
block: &cfgblk_squashfs_loop,
writable: false,
},
],
};
let domid = self.client.create(&config)?;
Ok(domid)
match self.client.create(&config) {
Ok(domid) => Ok(domid),
Err(error) => {
let _ = self.autoloop.unloop(&image_squashfs_loop.path);
let _ = self.autoloop.unloop(&cfgblk_squashfs_loop.path);
let _ = fs::remove_dir(&cfgblk.dir);
Err(error.into())
}
}
}
}

View File

@ -1,3 +1,4 @@
pub mod autoloop;
pub mod container;
pub mod ctl;
pub mod error;

20
loopdev/Cargo.toml Normal file
View File

@ -0,0 +1,20 @@
# This package is from https://github.com/stratis-storage/loopdev-3
# Mycelium maintains an in-tree version because the goals of hypha mean that
# there is as little binding generation as possible, especially bindings which
# prevent development from macOS, like the original library.
[package]
name = "loopdev"
version = "0.0.1"
license = "MIT"
edition = "2021"
[lib]
name = "loopdev"
[dependencies]
errno = "0.3.0"
libc = "0.2.105"
[dependencies.nix]
version = "0.27.1"
features = ["ioctl"]

21
loopdev/LICENSE Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2023 Anne Mulhern
Copyright (c) 2016 Michael Daffin
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

147
loopdev/src/bindings.rs Normal file
View File

@ -0,0 +1,147 @@
/* originally generated by rust-bindgen */
/* modified to remove unused content by Mycelium */
#![allow(non_camel_case_types)]
pub const __BITS_PER_LONG: u32 = 64;
pub const __FD_SETSIZE: u32 = 1024;
pub const LOOP_SET_FD: u32 = 19456;
pub const LOOP_CLR_FD: u32 = 19457;
pub const LOOP_SET_STATUS64: u32 = 19460;
pub const LOOP_SET_CAPACITY: u32 = 19463;
pub const LOOP_CTL_ADD: u32 = 19584;
pub const LOOP_CTL_GET_FREE: u32 = 19586;
pub const LO_FLAGS_READ_ONLY: _bindgen_ty_1 = 1;
pub const LO_FLAGS_AUTOCLEAR: _bindgen_ty_1 = 4;
pub const LO_FLAGS_PARTSCAN: _bindgen_ty_1 = 8;
pub type _bindgen_ty_1 = ::std::os::raw::c_uint;
pub type __kernel_old_uid_t = ::std::os::raw::c_ushort;
pub type __kernel_old_gid_t = ::std::os::raw::c_ushort;
pub type __kernel_old_dev_t = ::std::os::raw::c_ulong;
pub type __kernel_long_t = ::std::os::raw::c_long;
pub type __kernel_ulong_t = ::std::os::raw::c_ulong;
pub type __kernel_ino_t = __kernel_ulong_t;
pub type __kernel_mode_t = ::std::os::raw::c_uint;
pub type __kernel_pid_t = ::std::os::raw::c_int;
pub type __kernel_ipc_pid_t = ::std::os::raw::c_int;
pub type __kernel_uid_t = ::std::os::raw::c_uint;
pub type __kernel_gid_t = ::std::os::raw::c_uint;
pub type __kernel_suseconds_t = __kernel_long_t;
pub type __kernel_daddr_t = ::std::os::raw::c_int;
pub type __kernel_uid32_t = ::std::os::raw::c_uint;
pub type __kernel_gid32_t = ::std::os::raw::c_uint;
pub type __kernel_size_t = __kernel_ulong_t;
pub type __kernel_ssize_t = __kernel_long_t;
pub type __kernel_ptrdiff_t = __kernel_long_t;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct __kernel_fsid_t {
pub val: [::std::os::raw::c_int; 2usize],
}
pub type __kernel_off_t = __kernel_long_t;
pub type __kernel_loff_t = ::std::os::raw::c_longlong;
pub type __kernel_old_time_t = __kernel_long_t;
pub type __kernel_time_t = __kernel_long_t;
pub type __kernel_time64_t = ::std::os::raw::c_longlong;
pub type __kernel_clock_t = __kernel_long_t;
pub type __kernel_timer_t = ::std::os::raw::c_int;
pub type __kernel_clockid_t = ::std::os::raw::c_int;
pub type __kernel_caddr_t = *mut ::std::os::raw::c_char;
pub type __kernel_uid16_t = ::std::os::raw::c_ushort;
pub type __kernel_gid16_t = ::std::os::raw::c_ushort;
pub type __s8 = ::std::os::raw::c_schar;
pub type __u8 = ::std::os::raw::c_uchar;
pub type __s16 = ::std::os::raw::c_short;
pub type __u16 = ::std::os::raw::c_ushort;
pub type __s32 = ::std::os::raw::c_int;
pub type __u32 = ::std::os::raw::c_uint;
pub type __s64 = ::std::os::raw::c_longlong;
pub type __u64 = ::std::os::raw::c_ulonglong;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct __kernel_fd_set {
pub fds_bits: [::std::os::raw::c_ulong; 16usize],
}
pub type __kernel_sighandler_t = Option<unsafe extern "C" fn(arg1: ::std::os::raw::c_int)>;
pub type __kernel_key_t = ::std::os::raw::c_int;
pub type __kernel_mqd_t = ::std::os::raw::c_int;
pub type __le16 = __u16;
pub type __be16 = __u16;
pub type __le32 = __u32;
pub type __be32 = __u32;
pub type __le64 = __u64;
pub type __be64 = __u64;
pub type __sum16 = __u16;
pub type __wsum = __u32;
pub type __poll_t = ::std::os::raw::c_uint;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct loop_info {
pub lo_number: ::std::os::raw::c_int,
pub lo_device: __kernel_old_dev_t,
pub lo_inode: ::std::os::raw::c_ulong,
pub lo_rdevice: __kernel_old_dev_t,
pub lo_offset: ::std::os::raw::c_int,
pub lo_encrypt_type: ::std::os::raw::c_int,
pub lo_encrypt_key_size: ::std::os::raw::c_int,
pub lo_flags: ::std::os::raw::c_int,
pub lo_name: [::std::os::raw::c_char; 64usize],
pub lo_encrypt_key: [::std::os::raw::c_uchar; 32usize],
pub lo_init: [::std::os::raw::c_ulong; 2usize],
pub reserved: [::std::os::raw::c_char; 4usize],
}
impl Default for loop_info {
fn default() -> Self {
let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
unsafe {
::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
s.assume_init()
}
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct loop_info64 {
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; 64usize],
pub lo_crypt_name: [__u8; 64usize],
pub lo_encrypt_key: [__u8; 32usize],
pub lo_init: [__u64; 2usize],
}
impl Default for loop_info64 {
fn default() -> Self {
let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
unsafe {
::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
s.assume_init()
}
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct loop_config {
pub fd: __u32,
pub block_size: __u32,
pub info: loop_info64,
pub __reserved: [__u64; 8usize],
}
impl Default for loop_config {
fn default() -> Self {
let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
unsafe {
::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
s.assume_init()
}
}
}

475
loopdev/src/lib.rs Normal file
View File

@ -0,0 +1,475 @@
// Taken from https://github.com/stratis-storage/loopdev-3/blob/master/src/lib.rs
// Licensed under MIT.
//! Setup and control loop devices.
//!
//! Provides rust interface with similar functionality to the Linux utility `losetup`.
//!
//! # Examples
//!
//! Default options:
//!
//! ```no_run
//! use loopdev::LoopControl;
//! let lc = LoopControl::open().unwrap();
//! let ld = lc.next_free().unwrap();
//!
//! println!("{}", ld.path().unwrap().display());
//!
//! ld.attach_file("disk.img").unwrap();
//! // ...
//! ld.detach().unwrap();
//! ```
//!
//! Custom options:
//!
//! ```no_run
//! # use loopdev::LoopControl;
//! # let lc = LoopControl::open().unwrap();
//! # let ld = lc.next_free().unwrap();
//! #
//! ld.with()
//! .part_scan(true)
//! .offset(512 * 1024 * 1024) // 512 MiB
//! .size_limit(1024 * 1024 * 1024) // 1GiB
//! .attach("disk.img").unwrap();
//! // ...
//! ld.detach().unwrap();
//! ```
mod bindings;
mod linux;
use crate::bindings::{
loop_info64, LOOP_CLR_FD, LOOP_CTL_ADD, LOOP_CTL_GET_FREE, LOOP_SET_CAPACITY, LOOP_SET_FD,
LOOP_SET_STATUS64, LO_FLAGS_AUTOCLEAR, LO_FLAGS_PARTSCAN, LO_FLAGS_READ_ONLY,
};
use libc::ioctl;
use std::ffi::{c_int, c_ulong};
use std::{
default::Default,
fs::{File, OpenOptions},
io,
os::unix::prelude::*,
path::{Path, PathBuf},
};
#[cfg(all(not(target_os = "android"), not(target_env = "musl")))]
type IoctlRequest = c_ulong;
#[cfg(any(target_os = "android", target_env = "musl"))]
type IoctlRequest = c_int;
const LOOP_CONTROL: &str = "/dev/loop-control";
#[cfg(not(target_os = "android"))]
const LOOP_PREFIX: &str = "/dev/loop";
#[cfg(target_os = "android")]
const LOOP_PREFIX: &str = "/dev/block/loop";
/// Interface to the loop control device: `/dev/loop-control`.
#[derive(Debug)]
pub struct LoopControl {
dev_file: File,
}
impl LoopControl {
/// Opens the loop control device.
///
/// # Errors
///
/// This function will return an error for various reasons when opening
/// the loop control file `/dev/loop-control`. See
/// [`OpenOptions::open`](https://doc.rust-lang.org/std/fs/struct.OpenOptions.html)
/// for further details.
pub fn open() -> io::Result<Self> {
Ok(Self {
dev_file: OpenOptions::new()
.read(true)
.write(true)
.open(LOOP_CONTROL)?,
})
}
/// Finds and opens the next available loop device.
///
/// # Examples
///
/// ```no_run
/// use loopdev::LoopControl;
/// let lc = LoopControl::open().unwrap();
/// let ld = lc.next_free().unwrap();
/// println!("{}", ld.path().unwrap().display());
/// ```
///
/// # Errors
///
/// This function will return an error for various reasons when opening
/// the loop device file `/dev/loopX`. See
/// [`OpenOptions::open`](https://doc.rust-lang.org/std/fs/struct.OpenOptions.html)
/// for further details.
pub fn next_free(&self) -> io::Result<LoopDevice> {
let dev_num = ioctl_to_error(unsafe {
ioctl(
self.dev_file.as_raw_fd() as c_int,
LOOP_CTL_GET_FREE as IoctlRequest,
)
})?;
LoopDevice::open(format!("{}{}", LOOP_PREFIX, dev_num))
}
/// Add and opens a new loop device.
///
/// # Examples
///
/// ```no_run
/// use loopdev::LoopControl;
/// let lc = LoopControl::open().unwrap();
/// let ld = lc.add(1).unwrap();
/// println!("{}", ld.path().unwrap().display());
/// ```
///
/// # Errors
///
/// This funcitons will return an error when a loop device with the passed
/// number exists or opening the newly created device fails.
pub fn add(&self, n: u32) -> io::Result<LoopDevice> {
let dev_num = ioctl_to_error(unsafe {
ioctl(
self.dev_file.as_raw_fd() as c_int,
LOOP_CTL_ADD as IoctlRequest,
n as c_int,
)
})?;
LoopDevice::open(format!("{}{}", LOOP_PREFIX, dev_num))
}
}
impl AsRawFd for LoopControl {
fn as_raw_fd(&self) -> RawFd {
self.dev_file.as_raw_fd()
}
}
impl IntoRawFd for LoopControl {
fn into_raw_fd(self) -> RawFd {
self.dev_file.into_raw_fd()
}
}
/// Interface to a loop device ie `/dev/loop0`.
#[derive(Debug)]
pub struct LoopDevice {
device: File,
}
impl AsRawFd for LoopDevice {
fn as_raw_fd(&self) -> RawFd {
self.device.as_raw_fd()
}
}
impl IntoRawFd for LoopDevice {
fn into_raw_fd(self) -> RawFd {
self.device.into_raw_fd()
}
}
impl LoopDevice {
/// Opens a loop device.
///
/// # Errors
///
/// This function will return an error for various reasons when opening
/// the given loop device file. See
/// [`OpenOptions::open`](https://doc.rust-lang.org/std/fs/struct.OpenOptions.html)
/// for further details.
pub fn open<P: AsRef<Path>>(dev: P) -> io::Result<Self> {
// TODO create dev if it does not exist and begins with LOOP_PREFIX
Ok(Self {
device: OpenOptions::new().read(true).write(true).open(dev)?,
})
}
/// Attach the loop device to a file with given options.
///
/// # Examples
///
/// Attach the device to a file.
///
/// ```no_run
/// use loopdev::LoopDevice;
/// let mut ld = LoopDevice::open("/dev/loop0").unwrap();
/// ld.with().part_scan(true).attach("disk.img").unwrap();
/// # ld.detach().unwrap();
/// ```
pub fn with(&self) -> AttachOptions<'_> {
AttachOptions {
device: self,
info: loop_info64::default(),
}
}
/// Attach the loop device to a file that maps to the whole file.
///
/// # Examples
///
/// Attach the device to a file.
///
/// ```no_run
/// use loopdev::LoopDevice;
/// let ld = LoopDevice::open("/dev/loop0").unwrap();
/// ld.attach_file("disk.img").unwrap();
/// # ld.detach().unwrap();
/// ```
///
/// # Errors
///
/// This function will return an error for various reasons. Either when
/// opening the backing file (see
/// [`OpenOptions::open`](https://doc.rust-lang.org/std/fs/struct.OpenOptions.html)
/// for further details) or when calling the ioctl to attach the backing
/// file to the device.
pub fn attach_file<P: AsRef<Path>>(&self, backing_file: P) -> io::Result<()> {
let info = loop_info64 {
..Default::default()
};
Self::attach_with_loop_info(self, backing_file, info)
}
/// Attach the loop device to a file with `loop_info64`.
fn attach_with_loop_info(
&self, // TODO should be mut? - but changing it is a breaking change
backing_file: impl AsRef<Path>,
info: loop_info64,
) -> io::Result<()> {
let write_access = (info.lo_flags & LO_FLAGS_READ_ONLY) == 0;
let bf = OpenOptions::new()
.read(true)
.write(write_access)
.open(backing_file)?;
self.attach_fd_with_loop_info(bf, info)
}
/// Attach the loop device to a fd with `loop_info`.
fn attach_fd_with_loop_info(&self, bf: impl AsRawFd, info: loop_info64) -> io::Result<()> {
// Attach the file
ioctl_to_error(unsafe {
ioctl(
self.device.as_raw_fd() as c_int,
LOOP_SET_FD as IoctlRequest,
bf.as_raw_fd() as c_int,
)
})?;
let result = unsafe {
ioctl(
self.device.as_raw_fd() as c_int,
LOOP_SET_STATUS64 as IoctlRequest,
&info,
)
};
match ioctl_to_error(result) {
Err(err) => {
// Ignore the error to preserve the original error
let _detach_err = self.detach();
Err(err)
}
Ok(_) => Ok(()),
}
}
/// Get the path of the loop device.
pub fn path(&self) -> Option<PathBuf> {
let mut p = PathBuf::from("/proc/self/fd");
p.push(self.device.as_raw_fd().to_string());
std::fs::read_link(&p).ok()
}
/// Get the device major number
///
/// # Errors
///
/// This function needs to stat the backing file and can fail if there is
/// an IO error.
#[allow(clippy::unnecessary_cast)]
pub fn major(&self) -> io::Result<u32> {
self.device
.metadata()
.map(|m| linux::major(m.rdev()))
.map(|m| m as u32)
}
/// Get the device major number
///
/// # Errors
///
/// This function needs to stat the backing file and can fail if there is
/// an IO error.
#[allow(clippy::unnecessary_cast)]
pub fn minor(&self) -> io::Result<u32> {
self.device
.metadata()
.map(|m| linux::minor(m.rdev()))
.map(|m| m as u32)
}
/// Detach a loop device from its backing file.
///
/// Note that the device won't fully detach until a short delay after the underling device file
/// gets closed. This happens when `LoopDev` goes out of scope so you should ensure the `LoopDev`
/// lives for a short a time as possible.
///
/// # Examples
///
/// ```no_run
/// use loopdev::LoopDevice;
/// let ld = LoopDevice::open("/dev/loop0").unwrap();
/// # ld.attach_file("disk.img").unwrap();
/// ld.detach().unwrap();
/// ```
///
/// # Errors
///
/// This function will return an error for various reasons when calling the
/// ioctl to detach the backing file from the device.
pub fn detach(&self) -> io::Result<()> {
ioctl_to_error(unsafe {
ioctl(
self.device.as_raw_fd() as c_int,
LOOP_CLR_FD as IoctlRequest,
0,
)
})?;
Ok(())
}
/// Resize a live loop device. If the size of the backing file changes this can be called to
/// inform the loop driver about the new size.
///
/// # Errors
///
/// This function will return an error for various reasons when calling the
/// ioctl to set the capacity of the device.
pub fn set_capacity(&self) -> io::Result<()> {
ioctl_to_error(unsafe {
ioctl(
self.device.as_raw_fd() as c_int,
LOOP_SET_CAPACITY as IoctlRequest,
0,
)
})?;
Ok(())
}
}
/// Used to set options when attaching a device. Created with [`LoopDevice::with`()].
///
/// # Examples
///
/// Enable partition scanning on attach:
///
/// ```no_run
/// use loopdev::LoopDevice;
/// let mut ld = LoopDevice::open("/dev/loop0").unwrap();
/// ld.with()
/// .part_scan(true)
/// .attach("disk.img")
/// .unwrap();
/// # ld.detach().unwrap();
/// ```
///
/// A 1MiB slice of the file located at 1KiB into the file.
///
/// ```no_run
/// use loopdev::LoopDevice;
/// let mut ld = LoopDevice::open("/dev/loop0").unwrap();
/// ld.with()
/// .offset(1024*1024)
/// .size_limit(1024*1024*1024)
/// .attach("disk.img")
/// .unwrap();
/// # ld.detach().unwrap();
/// ```
#[must_use]
pub struct AttachOptions<'d> {
device: &'d LoopDevice,
info: loop_info64,
}
impl AttachOptions<'_> {
/// Offset in bytes from the start of the backing file the data will start at.
pub fn offset(mut self, offset: u64) -> Self {
self.info.lo_offset = offset;
self
}
/// Maximum size of the data in bytes.
pub fn size_limit(mut self, size_limit: u64) -> Self {
self.info.lo_sizelimit = size_limit;
self
}
/// Set read only flag
pub fn read_only(mut self, read_only: bool) -> Self {
if read_only {
self.info.lo_flags |= LO_FLAGS_READ_ONLY;
} else {
self.info.lo_flags &= !LO_FLAGS_READ_ONLY;
}
self
}
/// Set autoclear flag
pub fn autoclear(mut self, autoclear: bool) -> Self {
if autoclear {
self.info.lo_flags |= LO_FLAGS_AUTOCLEAR;
} else {
self.info.lo_flags &= !LO_FLAGS_AUTOCLEAR;
}
self
}
/// Force the kernel to scan the partition table on a newly created loop device. Note that the
/// partition table parsing depends on sector sizes. The default is sector size is 512 bytes
pub fn part_scan(mut self, enable: bool) -> Self {
if enable {
self.info.lo_flags |= LO_FLAGS_PARTSCAN;
} else {
self.info.lo_flags &= !LO_FLAGS_PARTSCAN;
}
self
}
/// Attach the loop device to a file with the set options.
///
/// # Errors
///
/// This function will return an error for various reasons. Either when
/// opening the backing file (see
/// [`OpenOptions::open`](https://doc.rust-lang.org/std/fs/struct.OpenOptions.html)
/// for further details) or when calling the ioctl to attach the backing
/// file to the device.
pub fn attach(self, backing_file: impl AsRef<Path>) -> io::Result<()> {
self.device.attach_with_loop_info(backing_file, self.info)?;
Ok(())
}
/// Attach the loop device to an fd
///
/// # Errors
///
/// This function will return an error for various reasons when calling the
/// ioctl to attach the backing file to the device.
pub fn attach_fd(self, backing_file_fd: impl AsRawFd) -> io::Result<()> {
self.device
.attach_fd_with_loop_info(backing_file_fd, self.info)?;
Ok(())
}
}
fn ioctl_to_error(ret: i32) -> io::Result<i32> {
if ret < 0 {
Err(io::Error::last_os_error())
} else {
Ok(ret)
}
}

15
loopdev/src/linux.rs Normal file
View File

@ -0,0 +1,15 @@
use std::ffi::c_uint;
pub fn major(dev: u64) -> c_uint {
let mut major = 0;
major |= (dev & 0x00000000000fff00) >> 8;
major |= (dev & 0xfffff00000000000) >> 32;
major as c_uint
}
pub fn minor(dev: u64) -> c_uint {
let mut minor = 0;
minor |= dev & 0x00000000000000ff;
minor |= (dev & 0x00000ffffff00000) >> 12;
minor as c_uint
}

View File

@ -80,9 +80,15 @@ impl From<EventChannelError> for XenClientError {
}
}
pub struct BlockDeviceRef {
pub path: String,
pub major: u32,
pub minor: u32,
}
pub struct DomainDisk<'a> {
pub vdev: &'a str,
pub pdev: &'a str,
pub block: &'a BlockDeviceRef,
pub writable: bool,
}
@ -313,8 +319,6 @@ impl XenClient {
let id = (202 << 8) | (index << 4) as u64;
let backend_items: Vec<(&str, String)> = vec![
("frontend-id", domid.to_string()),
("params", disk.pdev.to_string()),
("script", "/etc/xen/scripts/block".to_string()),
("online", "1".to_string()),
("removable", "0".to_string()),
("bootable", "1".to_string()),
@ -325,6 +329,11 @@ impl XenClient {
("device-type", "disk".to_string()),
("discard-enable", "0".to_string()),
("specification", "xen".to_string()),
("physical-device-path", disk.block.path.to_string()),
(
"physical-device",
format!("{:2x}:{:2x}", disk.block.major, disk.block.minor),
),
];
let frontend_items: Vec<(&str, String)> = vec![