support loop-dev mount in mount_rootfs

Signed-off-by: zzzzzzzzzy9 <zhang.yu58@zte.com.cn>
This commit is contained in:
zzzzzzzzzy9 2025-06-13 16:15:47 +08:00 committed by Maksym Pavlenko
parent 15fbabcf8e
commit 11e97809b8
1 changed files with 223 additions and 1 deletions

View File

@ -18,8 +18,10 @@
use std::{
collections::HashMap,
env,
fs::File,
io::BufRead,
ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Not},
os::fd::AsRawFd,
path::Path,
};
@ -40,6 +42,55 @@ struct Flag {
flags: MsFlags,
}
#[cfg(target_os = "linux")]
#[derive(Debug, Default)]
pub struct LoopParams {
readonly: bool,
auto_clear: bool,
direct: bool,
}
#[repr(C)]
#[derive(Debug)]
pub struct LoopInfo {
device: u64,
inode: u64,
rdevice: u64,
offset: u64,
size_limit: u64,
number: u32,
encrypt_type: u32,
encrypt_key_size: u32,
flags: u32,
file_name: [u8; 64],
crypt_name: [u8; 64],
encrypt_key: [u8; 32],
init: [u64; 2],
}
impl Default for LoopInfo {
fn default() -> Self {
LoopInfo {
device: 0,
inode: 0,
rdevice: 0,
offset: 0,
size_limit: 0,
number: 0,
encrypt_type: 0,
encrypt_key_size: 0,
flags: 0,
file_name: [0; 64],
crypt_name: [0; 64],
encrypt_key: [0; 32],
init: [0; 2],
}
}
}
const LOOP_CONTROL_PATH: &str = "/dev/loop-control";
const LOOP_DEV_FORMAT: &str = "/dev/loop";
const EBUSY_STRING: &str = "device or resource busy";
const OVERLAY_LOWERDIR_PREFIX: &str = "lowerdir=";
#[derive(Debug, Default, Clone)]
@ -568,6 +619,8 @@ pub fn mount_rootfs(
let mut flags: MsFlags = MsFlags::empty();
let mut data = Vec::new();
let mut lo_setup = false;
let mut loop_params = LoopParams::default();
options.iter().for_each(|x| {
if let Some(f) = MOUNT_FLAGS.get(x.as_str()) {
if f.clear {
@ -575,6 +628,8 @@ pub fn mount_rootfs(
} else {
flags.bitor_assign(f.flags)
}
} else if x.as_str() == "loop" {
lo_setup = true;
} else {
data.push(x.as_str())
}
@ -595,9 +650,23 @@ pub fn mount_rootfs(
}
// mount with non-propagation first, or remount with changed data
let oflags = flags.bitand(PROPAGATION_TYPES.not());
if lo_setup {
loop_params = LoopParams {
readonly: oflags.bitand(MsFlags::MS_RDONLY) == MsFlags::MS_RDONLY,
auto_clear: true,
direct: false,
};
}
let zero: MsFlags = MsFlags::empty();
if flags.bitand(MsFlags::MS_REMOUNT).eq(&zero) || data.is_some() {
mount(source, target.as_ref(), fs_type, oflags, data).map_err(mount_error!(
let mut lo_file = String::new();
let s = if lo_setup {
lo_file = setup_loop(source, loop_params)?;
Some(lo_file.as_str())
} else {
source
};
mount(s, target.as_ref(), fs_type, oflags, data).map_err(mount_error!(
e,
"Mount {:?} to {}",
source,
@ -630,6 +699,159 @@ pub fn mount_rootfs(
Ok(())
}
fn setup_loop(source: Option<&str>, params: LoopParams) -> Result<String> {
let src = source.ok_or(other!("loop source is None"))?;
for _ in 0..100 {
let num = get_free_loop_dev()?;
let loop_dev = format!("{}{}", LOOP_DEV_FORMAT, num);
match setup_loop_dev(src, loop_dev.as_str(), &params) {
Ok(_) => return Ok(loop_dev),
Err(e) => {
if e.to_string().contains(EBUSY_STRING) {
continue;
} else {
return Err(e);
}
}
}
}
Err(Error::Other(
"creating new loopback device after 100 times".to_string(),
))
}
pub fn get_free_loop_dev() -> Result<i32> {
const LOOP_CTL_GET_FREE: i32 = 0x4c82;
let loop_control = File::options()
.read(true)
.write(true)
.open(LOOP_CONTROL_PATH)
.map_err(|e| Error::IoError {
context: format!("open {} error: ", LOOP_CONTROL_PATH),
err: e,
})?;
unsafe {
#[cfg(target_env = "gnu")]
let ret = libc::ioctl(
loop_control.as_raw_fd() as libc::c_int,
LOOP_CTL_GET_FREE as libc::c_ulong,
) as i32;
#[cfg(target_env = "musl")]
let ret = libc::ioctl(
loop_control.as_raw_fd() as libc::c_int,
LOOP_CTL_GET_FREE as libc::c_int,
) as i32;
match nix::errno::Errno::result(ret) {
Ok(ret) => Ok(ret),
Err(e) => Err(Error::Nix(e)),
}
}
}
pub fn setup_loop_dev(backing_file: &str, loop_dev: &str, params: &LoopParams) -> Result<File> {
const LOOP_SET_FD: u32 = 0x4c00;
const LOOP_CLR_FD: u32 = 0x4c01;
const LOOP_SET_STATUS64: u32 = 0x4c04;
const LOOP_SET_DIRECT_IO: u32 = 0x4c08;
const LO_FLAGS_READ_ONLY: u32 = 0x1;
const LO_FLAGS_AUTOCLEAR: u32 = 0x4;
let mut open_options = File::options();
open_options.read(true);
if !params.readonly {
open_options.write(true);
}
// 1. open backing file
let back = open_options
.open(backing_file)
.map_err(|e| Error::IoError {
context: format!("open {} error: ", backing_file),
err: e,
})?;
let loop_dev = open_options.open(loop_dev).map_err(|e| Error::IoError {
context: format!("open {} error: ", loop_dev),
err: e,
})?;
// 2. set FD
unsafe {
#[cfg(target_env = "gnu")]
let ret = libc::ioctl(
loop_dev.as_raw_fd() as libc::c_int,
LOOP_SET_FD as libc::c_ulong,
back.as_raw_fd() as libc::c_int,
);
#[cfg(target_env = "musl")]
let ret = libc::ioctl(
loop_dev.as_raw_fd() as libc::c_int,
LOOP_SET_FD as libc::c_int,
back.as_raw_fd() as libc::c_int,
);
if let Err(e) = nix::errno::Errno::result(ret) {
return Err(Error::Nix(e));
}
}
// 3. set info
let mut info = LoopInfo::default();
info.file_name[..backing_file.as_bytes().len()].copy_from_slice(backing_file.as_bytes());
if params.readonly {
info.flags |= LO_FLAGS_READ_ONLY;
}
if params.auto_clear {
info.flags |= LO_FLAGS_AUTOCLEAR;
}
unsafe {
#[cfg(target_env = "gnu")]
let ret = libc::ioctl(
loop_dev.as_raw_fd() as libc::c_int,
LOOP_SET_STATUS64 as libc::c_ulong,
info,
);
#[cfg(target_env = "musl")]
if let Err(e) = nix::errno::Errno::result(ret) {
libc::ioctl(
loop_dev.as_raw_fd() as libc::c_int,
LOOP_CLR_FD as libc::c_int,
0,
);
return Err(Error::Nix(e));
}
}
// 4. Set Direct IO
if params.direct {
unsafe {
#[cfg(target_env = "gnu")]
let ret = libc::ioctl(
loop_dev.as_raw_fd() as libc::c_int,
LOOP_SET_DIRECT_IO as libc::c_ulong,
1,
);
#[cfg(target_env = "musl")]
let ret = libc::ioctl(
loop_dev.as_raw_fd() as libc::c_int,
LOOP_SET_DIRECT_IO as libc::c_int,
1,
);
if let Err(e) = nix::errno::Errno::result(ret) {
#[cfg(target_env = "gnu")]
libc::ioctl(
loop_dev.as_raw_fd() as libc::c_int,
LOOP_CLR_FD as libc::c_ulong,
0,
);
#[cfg(target_env = "musl")]
libc::ioctl(
loop_dev.as_raw_fd() as libc::c_int,
LOOP_CLR_FD as libc::c_int,
0,
);
return Err(Error::Nix(e));
}
}
}
Ok(loop_dev)
}
pub fn umount_recursive(target: Option<&str>, flags: i32) -> Result<()> {
if let Some(target) = target {
let mut mounts = get_mounts(Some(prefix_filter(target.to_string())))?;