diff --git a/crates/shim/src/mount_linux.rs b/crates/shim/src/mount_linux.rs index 31ea50e..dd929b6 100644 --- a/crates/shim/src/mount_linux.rs +++ b/crates/shim/src/mount_linux.rs @@ -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 { + 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(), ¶ms) { + 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 { + 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 { + 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())))?;