From c92dace1e909e5b191b5796cd06dda734e24e5e4 Mon Sep 17 00:00:00 2001 From: Felix Obenhuber Date: Thu, 27 May 2021 08:43:45 +0200 Subject: [PATCH 01/10] Make LOOP_PREFIX target_os specific Android system setup loop devices at `/dev/block/loop` instead of `/dev/loop`. Add a `cfg` directive to that sets the `LOOP_PREFIX` for `target_os = "android"` acordingly. --- src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 9f8a9b7..c456edf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,7 +41,10 @@ const LOOP_SET_CAPACITY: u16 = 0x4C07; const LOOP_CTL_GET_FREE: u16 = 0x4C82; 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)] From 2e68fd94a09ca9185795abbd41ab91729810ffdc Mon Sep 17 00:00:00 2001 From: Felix Obenhuber Date: Thu, 27 May 2021 08:46:36 +0200 Subject: [PATCH 02/10] Implement `AsRawFd` and `IntoRawFd` for LoopControl Getting the raw fd of loop control can be neccecary because the `next_free` and `attach` operation is (unfortunately) not atomic. The fd can be used to acquire a `flock`. --- src/lib.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index c456edf..d7b4b70 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -81,6 +81,18 @@ impl LoopControl { } } +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 { From 63ce1154d3aa703e97f02623869f4cc3cdbf736f Mon Sep 17 00:00:00 2001 From: Felix Obenhuber Date: Thu, 27 May 2021 08:49:02 +0200 Subject: [PATCH 03/10] Add `metadata` for a `LoopDevice` Getting the `Metadata` is usefull to access the major/minor number of the device that is needed when using the loopdev in combination with device-mapper. --- src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index d7b4b70..8713673 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,6 +19,7 @@ extern crate libc; use std::fs::File; +use std::fs::Metadata; use std::fs::OpenOptions; use libc::{c_int, ioctl}; @@ -252,6 +253,11 @@ impl LoopDevice { std::fs::read_link(&p).ok() } + /// Get the device metadata + pub fn metadata(&self) -> io::Result { + self.device.metadata() + } + /// 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 From 25999d2d60e7ce7a3aabd9093438373a2a7dfd41 Mon Sep 17 00:00:00 2001 From: Felix Obenhuber Date: Thu, 27 May 2021 08:50:47 +0200 Subject: [PATCH 04/10] Add read_only, autoclear and direct IO Allow passing the read only and autoclear flag when attaching a backing file. Allow to pass a raw fd instead of a Path when attaching. Add a fn to set the direct IO flag. --- losetup/src/main.rs | 28 +++++++++++----- src/lib.rs | 80 ++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 92 insertions(+), 16 deletions(-) diff --git a/losetup/src/main.rs b/losetup/src/main.rs index ce71ea0..3af1260 100644 --- a/losetup/src/main.rs +++ b/losetup/src/main.rs @@ -2,9 +2,9 @@ extern crate clap; extern crate loopdev; +use loopdev::{LoopControl, LoopDevice}; use std::io::{self, Write}; use std::process::exit; -use loopdev::{LoopControl, LoopDevice}; fn find() -> io::Result<()> { let loopdev = LoopControl::open()?.next_free()?; @@ -13,16 +13,25 @@ fn find() -> io::Result<()> { } fn attach(matches: &clap::ArgMatches) -> io::Result<()> { - let quite = matches.is_present("quite"); + let quiet = matches.is_present("quiet"); let image = matches.value_of("image").unwrap(); let offset = value_t!(matches.value_of("offset"), u64).unwrap_or(0); - let sizelimit = value_t!(matches.value_of("sizelimit"), u64).unwrap_or(0); - let loopdev = match matches.value_of("loopdev") { + let size_limit = value_t!(matches.value_of("sizelimit"), u64).unwrap_or(0); + let read_only = matches.is_present("read-only"); + let autoclear = matches.is_present("autoclear"); + let mut loopdev = match matches.value_of("loopdev") { Some(loopdev) => LoopDevice::open(&loopdev)?, None => LoopControl::open().and_then(|lc| lc.next_free())?, }; - loopdev.attach_with_sizelimit(&image, offset, sizelimit)?; - if !quite { + loopdev + .with() + .offset(offset) + .size_limit(size_limit) + .read_only(read_only) + .autoclear(autoclear) + .attach(image)?; + + if !quiet { println!("{}", loopdev.path().unwrap().display()); } Ok(()) @@ -60,7 +69,9 @@ fn main() { (@arg loopdev: "the loop device to attach") (@arg offset: -o --offset +takes_value "the offset within the file to start at") (@arg sizelimit: -s --sizelimit +takes_value "the file is limited to this size") - (@arg quite: -q --quite "don't print the device name") + (@arg read_only: -r --read-only "set up a read-only loop device") + (@arg autoclear: -a --autoclear "set the autoclear flag") + (@arg quiet: -q --quiet "don't print the device name") ) (@subcommand detach => (about: "detach the loop device from the backing file") @@ -75,7 +86,8 @@ fn main() { (@arg free: -f --free "find free devices") (@arg used: -u --used "find used devices") ) - ).get_matches(); + ) + .get_matches(); let result = match matches.subcommand() { ("find", _) => find(), diff --git a/src/lib.rs b/src/lib.rs index 8713673..5f6eeb9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,13 +34,16 @@ const LOOP_CLR_FD: u16 = 0x4C01; const LOOP_SET_STATUS64: u16 = 0x4C04; //const LOOP_GET_STATUS64: u16 = 0x4C05; const LOOP_SET_CAPACITY: u16 = 0x4C07; -//const LOOP_SET_DIRECT_IO: u16 = 0x4C08; +const LOOP_SET_DIRECT_IO: u16 = 0x4C08; //const LOOP_SET_BLOCK_SIZE: u16 = 0x4C09; //const LOOP_CTL_ADD: u16 = 0x4C80; //const LOOP_CTL_REMOVE: u16 = 0x4C81; const LOOP_CTL_GET_FREE: u16 = 0x4C82; +const LOOP_FLAG_READ_ONLY: u32 = 0x01; +const LOOP_FLAG_AUTOCLEAR: u32 = 0x04; + const LOOP_CONTROL: &str = "/dev/loop-control"; #[cfg(not(target_os = "android"))] const LOOP_PREFIX: &str = "/dev/loop"; @@ -131,6 +134,7 @@ impl LoopDevice { AttachOptions { device: self, info: Default::default(), + direct_io: false, } } @@ -216,7 +220,11 @@ impl LoopDevice { .read(true) .write(true) .open(backing_file)?; + self.attach_fd_with_loop_info(bf.as_raw_fd(), info) + } + /// Attach the loop device to a fd with loop_info. + fn attach_fd_with_loop_info(&self, bf: impl AsRawFd, info: LoopInfo64) -> io::Result<()> { // Attach the file ioctl_to_error(unsafe { ioctl( @@ -226,18 +234,21 @@ impl LoopDevice { ) })?; - if let Err(err) = ioctl_to_error(unsafe { + let result = unsafe { ioctl( self.device.as_raw_fd() as c_int, LOOP_SET_STATUS64.into(), &info, ) - }) { - // Ignore the error to preserve the original error - let _ = self.detach(); - return Err(err); + }; + match ioctl_to_error(result) { + Err(err) => { + // Ignore the error to preserve the original error + let _ = self.detach(); + Err(err) + } + Ok(_) => Ok(()), } - Ok(()) } /// Get the path of the loop device. @@ -289,6 +300,18 @@ impl LoopDevice { })?; Ok(()) } + + // Enable or disable direct I/O for the backing file. + pub fn set_direct_io(&self, direct_io: bool) -> io::Result<()> { + ioctl_to_error(unsafe { + ioctl( + self.device.as_raw_fd() as c_int, + LOOP_SET_DIRECT_IO.into(), + if direct_io { 1 } else { 0 }, + ) + })?; + Ok(()) + } } /// Used to set options when attaching a device. Created with [LoopDevice::with()]. @@ -322,6 +345,7 @@ impl LoopDevice { pub struct AttachOptions<'d> { device: &'d mut LoopDevice, info: LoopInfo64, + direct_io: bool, } impl AttachOptions<'_> { @@ -337,6 +361,32 @@ impl AttachOptions<'_> { self } + /// Set read only flag + pub fn read_only(mut self, read_only: bool) -> Self { + if read_only { + self.info.lo_flags |= LOOP_FLAG_READ_ONLY; + } else { + self.info.lo_flags &= !LOOP_FLAG_READ_ONLY; + } + self + } + + /// Set autoclear flag + pub fn autoclear(mut self, read_only: bool) -> Self { + if read_only { + self.info.lo_flags |= LOOP_FLAG_AUTOCLEAR; + } else { + self.info.lo_flags &= !LOOP_FLAG_AUTOCLEAR; + } + self + } + + // Enable or disable direct I/O for the backing file. + pub fn set_direct_io(mut self, direct_io: bool) -> Self { + self.direct_io = direct_io; + 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 { @@ -350,7 +400,21 @@ impl AttachOptions<'_> { /// Attach the loop device to a file with the set options. pub fn attach(self, backing_file: impl AsRef) -> io::Result<()> { - self.device.attach_with_loop_info(backing_file, self.info) + self.device.attach_with_loop_info(backing_file, self.info)?; + if self.direct_io { + self.device.set_direct_io(self.direct_io)?; + } + Ok(()) + } + + /// Attach the loop device to an fd + pub fn attach_fd(self, backing_file_fd: impl AsRawFd) -> io::Result<()> { + self.device + .attach_fd_with_loop_info(backing_file_fd, self.info)?; + if self.direct_io { + self.device.set_direct_io(self.direct_io)?; + } + Ok(()) } } From 676d1b622ca7fd5194c119d306950f6af40d6326 Mon Sep 17 00:00:00 2001 From: Felix Obenhuber Date: Thu, 27 May 2021 09:17:19 +0200 Subject: [PATCH 05/10] Use bindgen to generate loopdev bindings Add a build.rs to generate Rust bindings for "linux/loop.h". --- Cargo.toml | 3 ++ build.rs | 18 ++++++++ src/lib.rs | 106 ++++++++++++++-------------------------------- tests/util/mod.rs | 10 +++-- 4 files changed, 58 insertions(+), 79 deletions(-) create mode 100644 build.rs diff --git a/Cargo.toml b/Cargo.toml index da29044..4c58537 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,9 @@ travis-ci = { repository = "serde-rs/serde" } errno = "0.2" libc = "0.2" +[build-dependencies] +bindgen = "0.58.1" + [dev-dependencies] tempfile = "3.1.0" lazy_static = "1.3.0" diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..26eb038 --- /dev/null +++ b/build.rs @@ -0,0 +1,18 @@ +extern crate bindgen; + +use bindgen::Builder; +use std::{env::var, path::PathBuf}; + +fn main() { + let bindings = Builder::default() + .header_contents("wrapper.h", "#include ") + .derive_default(true) + .generate() + .expect("Could not generate bindings"); + + let mut bindings_path = PathBuf::from(var("OUT_DIR").unwrap()); + bindings_path.push("bindings.rs"); + bindings + .write_to_file(&bindings_path) + .expect("Could not write bindings to file"); +} diff --git a/src/lib.rs b/src/lib.rs index 5f6eeb9..f69b9d8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,31 +18,25 @@ extern crate libc; -use std::fs::File; -use std::fs::Metadata; -use std::fs::OpenOptions; - +use bindings::{ + loop_info64, LOOP_CLR_FD, LOOP_CTL_GET_FREE, LOOP_SET_CAPACITY, LOOP_SET_DIRECT_IO, + LOOP_SET_FD, LOOP_SET_STATUS64, +}; use libc::{c_int, ioctl}; -use std::default::Default; -use std::io; -use std::os::unix::prelude::*; -use std::path::{Path, PathBuf}; - -// TODO support missing operations -const LOOP_SET_FD: u16 = 0x4C00; -const LOOP_CLR_FD: u16 = 0x4C01; -const LOOP_SET_STATUS64: u16 = 0x4C04; -//const LOOP_GET_STATUS64: u16 = 0x4C05; -const LOOP_SET_CAPACITY: u16 = 0x4C07; -const LOOP_SET_DIRECT_IO: u16 = 0x4C08; -//const LOOP_SET_BLOCK_SIZE: u16 = 0x4C09; - -//const LOOP_CTL_ADD: u16 = 0x4C80; -//const LOOP_CTL_REMOVE: u16 = 0x4C81; -const LOOP_CTL_GET_FREE: u16 = 0x4C82; - -const LOOP_FLAG_READ_ONLY: u32 = 0x01; -const LOOP_FLAG_AUTOCLEAR: u32 = 0x04; +use std::fs::{File, Metadata, OpenOptions}; +use std::{ + default::Default, + io, + os::unix::prelude::*, + path::{Path, PathBuf}, +}; + +#[allow(non_camel_case_types)] +#[allow(dead_code)] +#[allow(non_snake_case)] +mod bindings { + include!(concat!(env!("OUT_DIR"), "/bindings.rs")); +} const LOOP_CONTROL: &str = "/dev/loop-control"; #[cfg(not(target_os = "android"))] @@ -144,7 +138,7 @@ impl LoopDevice { note = "use `loop.with().offset(offset).attach(file)` instead" )] pub fn attach>(&self, backing_file: P, offset: u64) -> io::Result<()> { - let info = LoopInfo64 { + let info = loop_info64 { lo_offset: offset, ..Default::default() }; @@ -165,7 +159,7 @@ impl LoopDevice { /// # ld.detach().unwrap(); /// ``` pub fn attach_file>(&self, backing_file: P) -> io::Result<()> { - let info = LoopInfo64 { + let info = loop_info64 { ..Default::default() }; @@ -182,7 +176,7 @@ impl LoopDevice { backing_file: P, offset: u64, ) -> io::Result<()> { - let info = LoopInfo64 { + let info = loop_info64 { lo_offset: offset, ..Default::default() }; @@ -201,7 +195,7 @@ impl LoopDevice { offset: u64, size_limit: u64, ) -> io::Result<()> { - let info = LoopInfo64 { + let info = loop_info64 { lo_offset: offset, lo_sizelimit: size_limit, ..Default::default() @@ -210,11 +204,11 @@ impl LoopDevice { Self::attach_with_loop_info(self, backing_file, info) } - /// Attach the loop device to a file with loop_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: LoopInfo64, + info: loop_info64, ) -> io::Result<()> { let bf = OpenOptions::new() .read(true) @@ -223,8 +217,8 @@ impl LoopDevice { self.attach_fd_with_loop_info(bf.as_raw_fd(), info) } - /// Attach the loop device to a fd with loop_info. - fn attach_fd_with_loop_info(&self, bf: impl AsRawFd, info: LoopInfo64) -> io::Result<()> { + /// Attach the loop device to a fd with loop_info64. + fn attach_fd_with_loop_info(&self, bf: impl AsRawFd, info: loop_info64) -> io::Result<()> { // Attach the file ioctl_to_error(unsafe { ioctl( @@ -344,7 +338,7 @@ impl LoopDevice { /// ``` pub struct AttachOptions<'d> { device: &'d mut LoopDevice, - info: LoopInfo64, + info: loop_info64, direct_io: bool, } @@ -364,9 +358,9 @@ impl AttachOptions<'_> { /// Set read only flag pub fn read_only(mut self, read_only: bool) -> Self { if read_only { - self.info.lo_flags |= LOOP_FLAG_READ_ONLY; + self.info.lo_flags |= bindings::LO_FLAGS_READ_ONLY; } else { - self.info.lo_flags &= !LOOP_FLAG_READ_ONLY; + self.info.lo_flags &= !bindings::LO_FLAGS_READ_ONLY; } self } @@ -374,9 +368,9 @@ impl AttachOptions<'_> { /// Set autoclear flag pub fn autoclear(mut self, read_only: bool) -> Self { if read_only { - self.info.lo_flags |= LOOP_FLAG_AUTOCLEAR; + self.info.lo_flags |= bindings::LO_FLAGS_AUTOCLEAR; } else { - self.info.lo_flags &= !LOOP_FLAG_AUTOCLEAR; + self.info.lo_flags &= !bindings::LO_FLAGS_AUTOCLEAR; } self } @@ -418,44 +412,6 @@ impl AttachOptions<'_> { } } -// https://man7.org/linux/man-pages/man4/loop.4.html -#[repr(C)] -struct LoopInfo64 { - pub lo_device: u64, // ioctl r/o - pub lo_inode: u64, // ioctl r/o - pub lo_rdevice: u64, // ioctl r/o - pub lo_offset: u64, // - pub lo_sizelimit: u64, // bytes, 0 == max available - pub lo_number: u32, // ioctl r/o - pub lo_encrypt_type: u32, // - pub lo_encrypt_key_size: u32, // ioctl w/o - pub lo_flags: u32, // ioctl r/w (r/o before Linux 2.6.25) - pub lo_file_name: [u8; 64], // - pub lo_crypt_name: [u8; 64], // - pub lo_encrypt_key: [u8; 32], // ioctl w/o - pub lo_init: [u64; 2], // -} - -impl Default for LoopInfo64 { - fn default() -> Self { - Self { - lo_device: 0, - lo_inode: 0, - lo_rdevice: 0, - lo_offset: 0, - lo_sizelimit: 0, - lo_number: 0, - lo_encrypt_type: 0, - lo_encrypt_key_size: 0, - lo_flags: 0, - lo_file_name: [0; 64], - lo_crypt_name: [0; 64], - lo_encrypt_key: [0; 32], - lo_init: [0; 2], - } - } -} - fn ioctl_to_error(ret: i32) -> io::Result { if ret < 0 { Err(io::Error::last_os_error()) diff --git a/tests/util/mod.rs b/tests/util/mod.rs index f7cbd80..f225cb9 100644 --- a/tests/util/mod.rs +++ b/tests/util/mod.rs @@ -1,9 +1,11 @@ use libc::fallocate; use serde::{Deserialize, Deserializer}; -use std::io; -use std::os::unix::io::AsRawFd; -use std::process::Command; -use std::sync::{Arc, Mutex, MutexGuard}; +use std::{ + io, + os::unix::io::AsRawFd, + process::Command, + sync::{Arc, Mutex, MutexGuard}, +}; use tempfile::{NamedTempFile, TempPath}; From d0eee0770e3f38ab2315d6cea5c351fac7b744a8 Mon Sep 17 00:00:00 2001 From: Felix Obenhuber Date: Thu, 27 May 2021 09:40:01 +0200 Subject: [PATCH 06/10] Bionics ioctl signature differs from glibc Bionics ioctrl takes a i32 as request instead of a u64 on glibc. Add a type based on the target_os and convert the request parameter accordingly. --- src/lib.rs | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f69b9d8..1d90629 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,8 +19,8 @@ extern crate libc; use bindings::{ - loop_info64, LOOP_CLR_FD, LOOP_CTL_GET_FREE, LOOP_SET_CAPACITY, LOOP_SET_DIRECT_IO, - LOOP_SET_FD, LOOP_SET_STATUS64, + loop_info64, LOOP_CTL_GET_FREE, LOOP_SET_CAPACITY, LOOP_SET_DIRECT_IO, LOOP_SET_FD, + LOOP_SET_STATUS64, }; use libc::{c_int, ioctl}; use std::fs::{File, Metadata, OpenOptions}; @@ -38,6 +38,11 @@ mod bindings { include!(concat!(env!("OUT_DIR"), "/bindings.rs")); } +#[cfg(not(target_os = "android"))] +type IoctlRequest = libc::c_ulong; +#[cfg(target_os = "android")] +type IoctlRequest = libc::c_int; + const LOOP_CONTROL: &str = "/dev/loop-control"; #[cfg(not(target_os = "android"))] const LOOP_PREFIX: &str = "/dev/loop"; @@ -73,7 +78,10 @@ impl LoopControl { /// ``` 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.into()) + ioctl( + self.dev_file.as_raw_fd() as c_int, + LOOP_CTL_GET_FREE as IoctlRequest, + ) })?; LoopDevice::open(&format!("{}{}", LOOP_PREFIX, dev_num)) } @@ -223,7 +231,7 @@ impl LoopDevice { ioctl_to_error(unsafe { ioctl( self.device.as_raw_fd() as c_int, - LOOP_SET_FD.into(), + LOOP_SET_FD as IoctlRequest, bf.as_raw_fd() as c_int, ) })?; @@ -231,7 +239,7 @@ impl LoopDevice { let result = unsafe { ioctl( self.device.as_raw_fd() as c_int, - LOOP_SET_STATUS64.into(), + LOOP_SET_STATUS64 as IoctlRequest, &info, ) }; @@ -278,7 +286,13 @@ impl LoopDevice { /// ld.detach().unwrap(); /// ``` pub fn detach(&self) -> io::Result<()> { - ioctl_to_error(unsafe { ioctl(self.device.as_raw_fd() as c_int, LOOP_CLR_FD.into(), 0) })?; + ioctl_to_error(unsafe { + ioctl( + self.device.as_raw_fd() as c_int, + bindings::LOOP_CLR_FD as IoctlRequest, + 0, + ) + })?; Ok(()) } @@ -288,7 +302,7 @@ impl LoopDevice { ioctl_to_error(unsafe { ioctl( self.device.as_raw_fd() as c_int, - LOOP_SET_CAPACITY.into(), + LOOP_SET_CAPACITY as IoctlRequest, 0, ) })?; @@ -300,7 +314,7 @@ impl LoopDevice { ioctl_to_error(unsafe { ioctl( self.device.as_raw_fd() as c_int, - LOOP_SET_DIRECT_IO.into(), + LOOP_SET_DIRECT_IO as IoctlRequest, if direct_io { 1 } else { 0 }, ) })?; From c90d94245b56863091e088b226cf5e2a3201412e Mon Sep 17 00:00:00 2001 From: Felix Obenhuber Date: Thu, 27 May 2021 09:46:34 +0200 Subject: [PATCH 07/10] Add actions-rs quickstart CI --- .github/workflows/ci.yml | 64 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..2875c7b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,64 @@ +on: [push, pull_request] + +name: Continuous integration + +jobs: + check: + name: Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - uses: actions-rs/cargo@v1 + with: + command: check + + test: + name: Test Suite + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - uses: actions-rs/cargo@v1 + with: + command: test + + fmt: + name: Rustfmt + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - run: rustup component add rustfmt + - uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + + clippy: + name: Clippy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - run: rustup component add clippy + - uses: actions-rs/cargo@v1 + with: + command: clippy + args: -- -D warnings From 65d9cfd759a697e350c28b50a725936b001684e9 Mon Sep 17 00:00:00 2001 From: Felix Obenhuber Date: Thu, 27 May 2021 10:01:31 +0200 Subject: [PATCH 08/10] Do not test doc examples Running the examples in the docs fails because the loopdev actions need permissions and probably interfere with existing loop devices on the machine. --- src/lib.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1d90629..ccd6fbb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,7 @@ //! //! # Examples //! -//! ```rust +//! ```no_run //! use loopdev::LoopControl; //! let lc = LoopControl::open().unwrap(); //! let ld = lc.next_free().unwrap(); @@ -70,7 +70,7 @@ impl LoopControl { /// /// # Examples /// - /// ```rust + /// ```no_run /// use loopdev::LoopControl; /// let lc = LoopControl::open().unwrap(); /// let ld = lc.next_free().unwrap(); @@ -126,7 +126,7 @@ impl LoopDevice { /// /// Attach the device to a file. /// - /// ```rust + /// ```no_run /// use loopdev::LoopDevice; /// let mut ld = LoopDevice::open("/dev/loop3").unwrap(); /// ld.with().part_scan(true).attach("test.img").unwrap(); @@ -160,7 +160,7 @@ impl LoopDevice { /// /// Attach the device to a file. /// - /// ```rust + /// ```no_run /// use loopdev::LoopDevice; /// let ld = LoopDevice::open("/dev/loop4").unwrap(); /// ld.attach_file("test.img").unwrap(); @@ -279,7 +279,7 @@ impl LoopDevice { /// /// # Examples /// - /// ```rust + /// ```no_run /// use loopdev::LoopDevice; /// let ld = LoopDevice::open("/dev/loop5").unwrap(); /// # ld.attach_file("test.img").unwrap(); @@ -328,7 +328,7 @@ impl LoopDevice { /// /// Enable partition scanning on attach: /// -/// ```rust +/// ```no_run /// use loopdev::LoopDevice; /// let mut ld = LoopDevice::open("/dev/loop6").unwrap(); /// ld.with() @@ -340,7 +340,7 @@ impl LoopDevice { /// /// A 1MiB slice of the file located at 1KiB into the file. /// -/// ```rust +/// ```no_run /// use loopdev::LoopDevice; /// let mut ld = LoopDevice::open("/dev/loop7").unwrap(); /// ld.with() From 240689afc02230b61205387143157fe1f8b94f72 Mon Sep 17 00:00:00 2001 From: Felix Obenhuber Date: Thu, 27 May 2021 10:03:44 +0200 Subject: [PATCH 09/10] Add runner for x86_64-unknown-linux-gnu Add a runner that executes the integration tests with a sudo that is needed for for performing the various losetup actions. --- .cargo/config | 2 ++ .cargo/runner-x86_64-unknown-linux-gnu | 5 +++++ 2 files changed, 7 insertions(+) create mode 100755 .cargo/config create mode 100755 .cargo/runner-x86_64-unknown-linux-gnu diff --git a/.cargo/config b/.cargo/config new file mode 100755 index 0000000..c0f3d82 --- /dev/null +++ b/.cargo/config @@ -0,0 +1,2 @@ +[target.x86_64-unknown-linux-gnu] +runner = '.cargo/runner-x86_64-unknown-linux-gnu' diff --git a/.cargo/runner-x86_64-unknown-linux-gnu b/.cargo/runner-x86_64-unknown-linux-gnu new file mode 100755 index 0000000..8ca0ee5 --- /dev/null +++ b/.cargo/runner-x86_64-unknown-linux-gnu @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +binary_name=`basename $1` + +sudo -E --preserve-env=PATH $@ From 6f0582824a599e9c1dce5a03ec4766bc07986bc6 Mon Sep 17 00:00:00 2001 From: Felix Obenhuber Date: Thu, 27 May 2021 10:59:04 +0200 Subject: [PATCH 10/10] Use loopdev crate instead of losetup for integration test attach The `losetup` tools tends to fail with an EBUSY when running in the CI. --- tests/integration_test.rs | 27 ++++++++------------------ tests/util/mod.rs | 40 +++++++++++++++++++-------------------- 2 files changed, 28 insertions(+), 39 deletions(-) diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 9505672..e41e2c7 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -7,7 +7,6 @@ extern crate serde; extern crate serde_json; use loopdev::{LoopControl, LoopDevice}; -use std::path::PathBuf; mod util; use util::{attach_file, create_backing_file, detach_all, list_device, setup}; @@ -17,15 +16,8 @@ fn get_next_free_device() { let _lock = setup(); let lc = LoopControl::open().expect("should be able to open the LoopControl device"); - let ld0 = lc - .next_free() + lc.next_free() .expect("should not error finding the next free loopback device"); - - assert_eq!( - ld0.path(), - Some(PathBuf::from("/dev/loop0")), - "should find the first loopback device" - ); } #[test] @@ -148,17 +140,14 @@ fn detach_a_backing_file_with_sizelimit_overflow() { fn detach_a_backing_file(offset: u64, sizelimit: u64, file_size: i64) { let _lock = setup(); + let pre_num_devices = list_device(None).len(); + { let file = create_backing_file(file_size); - attach_file( - "/dev/loop0", - file.to_path_buf().to_str().unwrap(), - offset, - sizelimit, - ); + let ld = attach_file(file.to_path_buf().to_str().unwrap(), offset, sizelimit); - let ld0 = LoopDevice::open("/dev/loop0") - .expect("should be able to open the created loopback device"); + let ld0 = + LoopDevice::open(&ld).expect("should be able to open the created loopback device"); ld0.detach() .expect("should not error detaching the backing file from the loopdev"); @@ -171,8 +160,8 @@ fn detach_a_backing_file(offset: u64, sizelimit: u64, file_size: i64) { assert_eq!( devices.len(), - 0, - "there should be no loopback devices mounted" + pre_num_devices, + "there should be no additional loopback devices mounted" ); detach_all(); } diff --git a/tests/util/mod.rs b/tests/util/mod.rs index f225cb9..988c048 100644 --- a/tests/util/mod.rs +++ b/tests/util/mod.rs @@ -1,8 +1,10 @@ use libc::fallocate; +use loopdev::LoopControl; use serde::{Deserialize, Deserializer}; use std::{ io, - os::unix::io::AsRawFd, + os::unix::{io::AsRawFd, prelude::MetadataExt}, + path::PathBuf, process::Command, sync::{Arc, Mutex, MutexGuard}, }; @@ -32,22 +34,22 @@ pub fn setup() -> MutexGuard<'static, ()> { lock } -pub fn attach_file(loop_dev: &str, backing_file: &str, offset: u64, sizelimit: u64) { - if !Command::new("losetup") - .args(&[ - loop_dev, - backing_file, - "--offset", - &offset.to_string(), - "--sizelimit", - &sizelimit.to_string(), - ]) - .status() - .expect("failed to attach backing file to loop device") - .success() - { - panic!("failed to cleanup existing loop devices") - } +/// Attach `backing_file` and return path to loop device +pub fn attach_file(backing_file: &str, offset: u64, sizelimit: u64) -> PathBuf { + let lc = LoopControl::open().expect("should be able to open the LoopControl device"); + let mut ld = lc + .next_free() + .expect("should not error finding the next free loopback device"); + ld.with() + .offset(offset) + .size_limit(sizelimit) + .attach(&backing_file) + .expect("should not error attaching the backing file to the loopdev"); + let meta = ld.metadata().expect("should not error accessing metadata"); + let rdev = meta.rdev(); + let minor = ((rdev >> 12) & 0xFFFF_FF00) | (rdev & 0xFF); + + PathBuf::from(format!("/dev/loop{}", minor)) } pub fn detach_all() { @@ -69,9 +71,7 @@ pub fn list_device(dev_file: Option<&str>) -> Vec { if let Some(dev_file) = dev_file { output.arg(dev_file); } - let output = output - .output() - .expect("failed to cleanup existing loop devices"); + let output = output.output().expect("failed to list loop devices"); if output.stdout.is_empty() { Vec::new()