//! Helpers to work with /dev/loop* devices use anyhow::Error; use std::fs::{File, OpenOptions}; use std::path::Path; use std::os::unix::io::{RawFd, AsRawFd}; const LOOP_CONTROL: &str = "/dev/loop-control"; const LOOP_NAME: &str = "/dev/loop"; /// Implements a subset of loop device ioctls necessary to assign and release /// a single file from a free loopdev. mod loop_ioctl { use nix::{ioctl_none, ioctl_write_int_bad, ioctl_write_ptr_bad}; const LOOP_IOCTL: u16 = 0x4C; // 'L' const LOOP_SET_FD: u16 = 0x00; const LOOP_CLR_FD: u16 = 0x01; const LOOP_SET_STATUS64: u16 = 0x04; const LOOP_CTRL_GET_FREE: u16 = 0x82; ioctl_write_int_bad!(ioctl_set_fd, (LOOP_IOCTL << 8) | LOOP_SET_FD); ioctl_none!(ioctl_clr_fd, LOOP_IOCTL, LOOP_CLR_FD); ioctl_none!(ioctl_ctrl_get_free, LOOP_IOCTL, LOOP_CTRL_GET_FREE); ioctl_write_ptr_bad!(ioctl_set_status64, (LOOP_IOCTL << 8) | LOOP_SET_STATUS64, LoopInfo64); pub const LO_FLAGS_READ_ONLY: u32 = 1; pub const LO_FLAGS_PARTSCAN: u32 = 8; const LO_NAME_SIZE: usize = 64; const LO_KEY_SIZE: usize = 32; #[repr(C)] pub struct LoopInfo64 { 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; LO_NAME_SIZE], pub lo_crypt_name: [u8; LO_NAME_SIZE], pub lo_encrypt_key: [u8; LO_KEY_SIZE], pub lo_init: [u64; 2], } } // ioctl helpers create public fns, do not export them outside the module // users should use the wrapper functions below use loop_ioctl::*; /// Use the GET_FREE ioctl to get or add a free loop device, of which the /// /dev/loopN path will be returned. This is inherently racy because of the /// delay between this and calling assign, but since assigning is atomic it /// does not matter much and will simply cause assign to fail. pub fn get_or_create_free_dev() -> Result { let ctrl_file = File::open(LOOP_CONTROL)?; let free_num = unsafe { ioctl_ctrl_get_free(ctrl_file.as_raw_fd())? }; let loop_file_path = format!("{}{}", LOOP_NAME, free_num); Ok(loop_file_path) } fn assign_dev(fd: RawFd, backing_fd: RawFd) -> Result<(), Error> { unsafe { ioctl_set_fd(fd, backing_fd)?; } // set required read-only flag and partscan for convenience let mut info: LoopInfo64 = unsafe { std::mem::zeroed() }; info.lo_flags = LO_FLAGS_READ_ONLY | LO_FLAGS_PARTSCAN; unsafe { ioctl_set_status64(fd, &info)?; } Ok(()) } /// Open the next available /dev/loopN file and assign the given path to /// it as it's backing file in read-only mode. pub fn assign>(loop_dev: P, backing: P) -> Result<(), Error> { let loop_file = File::open(loop_dev)?; let backing_file = OpenOptions::new() .read(true) .open(backing)?; assign_dev(loop_file.as_raw_fd(), backing_file.as_raw_fd())?; Ok(()) } /// Unassign any file descriptors currently attached to the given /// /dev/loopN device. pub fn unassign>(path: P) -> Result<(), Error> { let loop_file = File::open(path)?; unsafe { ioctl_clr_fd(loop_file.as_raw_fd())?; } Ok(()) }