mirror of
https://github.com/edera-dev/krata.git
synced 2025-08-02 21:00:55 +00:00
hypha: implement loop device support directly to avoid devd
This commit is contained in:
parent
7d9652636f
commit
ba156e43da
@ -5,5 +5,6 @@ members = [
|
|||||||
"xencall",
|
"xencall",
|
||||||
"xenclient",
|
"xenclient",
|
||||||
"hypha",
|
"hypha",
|
||||||
|
"loopdev",
|
||||||
]
|
]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
@ -7,6 +7,9 @@ resolver = "2"
|
|||||||
[dependencies.xenclient]
|
[dependencies.xenclient]
|
||||||
path = "../xenclient"
|
path = "../xenclient"
|
||||||
|
|
||||||
|
[dependencies.loopdev]
|
||||||
|
path = "../loopdev"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
env_logger = "0.11.0"
|
env_logger = "0.11.0"
|
||||||
|
36
hypha/src/autoloop.rs
Normal file
36
hypha/src/autoloop.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
}
|
@ -9,15 +9,21 @@ use uuid::Uuid;
|
|||||||
pub struct ConfigBlock<'a> {
|
pub struct ConfigBlock<'a> {
|
||||||
pub image_info: &'a ImageInfo,
|
pub image_info: &'a ImageInfo,
|
||||||
pub file: PathBuf,
|
pub file: PathBuf,
|
||||||
|
pub dir: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConfigBlock<'_> {
|
impl ConfigBlock<'_> {
|
||||||
pub fn new<'a>(uuid: &Uuid, image_info: &'a ImageInfo) -> Result<ConfigBlock<'a>> {
|
pub fn new<'a>(uuid: &Uuid, image_info: &'a ImageInfo) -> Result<ConfigBlock<'a>> {
|
||||||
let mut file = std::env::temp_dir().clone();
|
let mut dir = std::env::temp_dir().clone();
|
||||||
file.push(format!("hypha-cfg-{}", uuid));
|
dir.push(format!("hypha-cfg-{}", uuid));
|
||||||
fs::create_dir_all(&file)?;
|
fs::create_dir_all(&dir)?;
|
||||||
|
let mut file = dir.clone();
|
||||||
file.push("config.squashfs");
|
file.push("config.squashfs");
|
||||||
Ok(ConfigBlock { image_info, file })
|
Ok(ConfigBlock {
|
||||||
|
image_info,
|
||||||
|
file,
|
||||||
|
dir,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build(&self) -> Result<()> {
|
pub fn build(&self) -> Result<()> {
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
mod cfgblk;
|
pub mod cfgblk;
|
||||||
|
|
||||||
|
use crate::autoloop::AutoLoop;
|
||||||
use crate::ctl::cfgblk::ConfigBlock;
|
use crate::ctl::cfgblk::ConfigBlock;
|
||||||
use crate::error::{HyphaError, Result};
|
use crate::error::Result;
|
||||||
use crate::image::cache::ImageCache;
|
use crate::image::cache::ImageCache;
|
||||||
use crate::image::name::ImageName;
|
use crate::image::name::ImageName;
|
||||||
use crate::image::{ImageCompiler, ImageInfo};
|
use crate::image::{ImageCompiler, ImageInfo};
|
||||||
|
use loopdev::LoopControl;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
@ -12,6 +14,7 @@ use xenclient::{DomainConfig, DomainDisk, XenClient};
|
|||||||
|
|
||||||
pub struct Controller {
|
pub struct Controller {
|
||||||
image_cache: ImageCache,
|
image_cache: ImageCache,
|
||||||
|
autoloop: AutoLoop,
|
||||||
image: String,
|
image: String,
|
||||||
client: XenClient,
|
client: XenClient,
|
||||||
kernel_path: String,
|
kernel_path: String,
|
||||||
@ -39,6 +42,7 @@ impl Controller {
|
|||||||
let image_cache = ImageCache::new(&image_cache_path)?;
|
let image_cache = ImageCache::new(&image_cache_path)?;
|
||||||
Ok(Controller {
|
Ok(Controller {
|
||||||
image_cache,
|
image_cache,
|
||||||
|
autoloop: AutoLoop::new(LoopControl::open()?),
|
||||||
image,
|
image,
|
||||||
client,
|
client,
|
||||||
kernel_path,
|
kernel_path,
|
||||||
@ -60,14 +64,13 @@ impl Controller {
|
|||||||
let image_info = self.compile()?;
|
let image_info = self.compile()?;
|
||||||
let cfgblk = ConfigBlock::new(&uuid, &image_info)?;
|
let cfgblk = ConfigBlock::new(&uuid, &image_info)?;
|
||||||
cfgblk.build()?;
|
cfgblk.build()?;
|
||||||
let cfgblk_squashfs_path = cfgblk
|
|
||||||
.file
|
let image_squashfs_path = image_info.image_squashfs.clone();
|
||||||
.to_str()
|
let cfgblk_squashfs_path = cfgblk.file.clone();
|
||||||
.ok_or_else(|| HyphaError::new("failed to convert config squashfs path to string"))?;
|
|
||||||
let image_squashfs_path = image_info
|
let image_squashfs_loop = self.autoloop.loopify(&image_squashfs_path)?;
|
||||||
.image_squashfs
|
let cfgblk_squashfs_loop = self.autoloop.loopify(&cfgblk_squashfs_path)?;
|
||||||
.to_str()
|
|
||||||
.ok_or_else(|| HyphaError::new("failed to convert image squashfs path to string"))?;
|
|
||||||
let config = DomainConfig {
|
let config = DomainConfig {
|
||||||
backend_domid: 0,
|
backend_domid: 0,
|
||||||
name: &name,
|
name: &name,
|
||||||
@ -79,17 +82,24 @@ impl Controller {
|
|||||||
disks: vec![
|
disks: vec![
|
||||||
DomainDisk {
|
DomainDisk {
|
||||||
vdev: "xvda",
|
vdev: "xvda",
|
||||||
pdev: image_squashfs_path,
|
block: &image_squashfs_loop,
|
||||||
writable: false,
|
writable: false,
|
||||||
},
|
},
|
||||||
DomainDisk {
|
DomainDisk {
|
||||||
vdev: "xvdb",
|
vdev: "xvdb",
|
||||||
pdev: cfgblk_squashfs_path,
|
block: &cfgblk_squashfs_loop,
|
||||||
writable: false,
|
writable: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
let domid = self.client.create(&config)?;
|
match self.client.create(&config) {
|
||||||
Ok(domid)
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
pub mod autoloop;
|
||||||
pub mod container;
|
pub mod container;
|
||||||
pub mod ctl;
|
pub mod ctl;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
20
loopdev/Cargo.toml
Normal file
20
loopdev/Cargo.toml
Normal 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
21
loopdev/LICENSE
Normal 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
147
loopdev/src/bindings.rs
Normal 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
475
loopdev/src/lib.rs
Normal 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
15
loopdev/src/linux.rs
Normal 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
|
||||||
|
}
|
@ -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 struct DomainDisk<'a> {
|
||||||
pub vdev: &'a str,
|
pub vdev: &'a str,
|
||||||
pub pdev: &'a str,
|
pub block: &'a BlockDeviceRef,
|
||||||
pub writable: bool,
|
pub writable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,8 +319,6 @@ impl XenClient {
|
|||||||
let id = (202 << 8) | (index << 4) as u64;
|
let id = (202 << 8) | (index << 4) as u64;
|
||||||
let backend_items: Vec<(&str, String)> = vec![
|
let backend_items: Vec<(&str, String)> = vec![
|
||||||
("frontend-id", domid.to_string()),
|
("frontend-id", domid.to_string()),
|
||||||
("params", disk.pdev.to_string()),
|
|
||||||
("script", "/etc/xen/scripts/block".to_string()),
|
|
||||||
("online", "1".to_string()),
|
("online", "1".to_string()),
|
||||||
("removable", "0".to_string()),
|
("removable", "0".to_string()),
|
||||||
("bootable", "1".to_string()),
|
("bootable", "1".to_string()),
|
||||||
@ -325,6 +329,11 @@ impl XenClient {
|
|||||||
("device-type", "disk".to_string()),
|
("device-type", "disk".to_string()),
|
||||||
("discard-enable", "0".to_string()),
|
("discard-enable", "0".to_string()),
|
||||||
("specification", "xen".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![
|
let frontend_items: Vec<(&str, String)> = vec![
|
||||||
|
Loading…
Reference in New Issue
Block a user