diff --git a/Cargo.toml b/Cargo.toml index ba62f88..d601f9b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,5 +5,6 @@ members = [ "xencall", "xenclient", "hypha", + "loopdev", ] resolver = "2" diff --git a/hypha/Cargo.toml b/hypha/Cargo.toml index 1e7174e..4dbe6bf 100644 --- a/hypha/Cargo.toml +++ b/hypha/Cargo.toml @@ -7,6 +7,9 @@ resolver = "2" [dependencies.xenclient] path = "../xenclient" +[dependencies.loopdev] +path = "../loopdev" + [dependencies] log = "0.4.20" env_logger = "0.11.0" diff --git a/hypha/src/autoloop.rs b/hypha/src/autoloop.rs new file mode 100644 index 0000000..23c6cd3 --- /dev/null +++ b/hypha/src/autoloop.rs @@ -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 { + 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(()) + } +} diff --git a/hypha/src/ctl/cfgblk.rs b/hypha/src/ctl/cfgblk.rs index 92ca83e..c91e444 100644 --- a/hypha/src/ctl/cfgblk.rs +++ b/hypha/src/ctl/cfgblk.rs @@ -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> { - 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<()> { diff --git a/hypha/src/ctl/mod.rs b/hypha/src/ctl/mod.rs index 1c10e7b..a1d151d 100644 --- a/hypha/src/ctl/mod.rs +++ b/hypha/src/ctl/mod.rs @@ -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()) + } + } } } diff --git a/hypha/src/lib.rs b/hypha/src/lib.rs index 16776c5..6211ecd 100644 --- a/hypha/src/lib.rs +++ b/hypha/src/lib.rs @@ -1,3 +1,4 @@ +pub mod autoloop; pub mod container; pub mod ctl; pub mod error; diff --git a/loopdev/Cargo.toml b/loopdev/Cargo.toml new file mode 100644 index 0000000..3a29611 --- /dev/null +++ b/loopdev/Cargo.toml @@ -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"] diff --git a/loopdev/LICENSE b/loopdev/LICENSE new file mode 100644 index 0000000..2ffa167 --- /dev/null +++ b/loopdev/LICENSE @@ -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. diff --git a/loopdev/src/bindings.rs b/loopdev/src/bindings.rs new file mode 100644 index 0000000..b06ede1 --- /dev/null +++ b/loopdev/src/bindings.rs @@ -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; +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::::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::::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::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} diff --git a/loopdev/src/lib.rs b/loopdev/src/lib.rs new file mode 100644 index 0000000..59b18d6 --- /dev/null +++ b/loopdev/src/lib.rs @@ -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 { + 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 { + 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 { + 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>(dev: P) -> io::Result { + // 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>(&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, + 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 { + 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 { + 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 { + 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) -> 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 { + if ret < 0 { + Err(io::Error::last_os_error()) + } else { + Ok(ret) + } +} diff --git a/loopdev/src/linux.rs b/loopdev/src/linux.rs new file mode 100644 index 0000000..13ec680 --- /dev/null +++ b/loopdev/src/linux.rs @@ -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 +} diff --git a/xenclient/src/lib.rs b/xenclient/src/lib.rs index 79127e1..9255de9 100644 --- a/xenclient/src/lib.rs +++ b/xenclient/src/lib.rs @@ -80,9 +80,15 @@ impl From 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![