Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use LOOP_CONFIGURE ioctl for attaching to loop devices #57

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 44 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@
//! ld.detach().unwrap();
//! ```
use crate::bindings::{
loop_info64, LOOP_CLR_FD, LOOP_CTL_GET_FREE, LOOP_SET_CAPACITY, LOOP_SET_FD, LOOP_SET_STATUS64,
LO_FLAGS_AUTOCLEAR, LO_FLAGS_PARTSCAN, LO_FLAGS_READ_ONLY,
loop_config, loop_info64, LOOP_CLR_FD, LOOP_CONFIGURE, LOOP_CTL_GET_FREE, LOOP_SET_CAPACITY,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using LOOP_CONFIGURE will cause problems with toolchains that do not contain that define and the libc headers are generated from a kernel prior LOOP_CONFIGURE. The devicemapper crate had similar problems with features added over the years.

LOOP_CONFIGURE is great but must be guarded by a feature.
I'm not sure if it's possible to reliably detect the kernel, libc header version etc... from a build.rs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just checked a build for aarch64-linux-android with cross:

~/loopdev ‹loop-configure› cross build --target=aarch64-linux-android
   Compiling memchr v2.5.0
   Compiling glob v0.3.0
   Compiling libc v0.2.137
   Compiling proc-macro2 v1.0.47
   Compiling minimal-lexical v0.2.1
   Compiling quote v1.0.21
   Compiling cfg-if v1.0.0
   Compiling unicode-ident v1.0.5
   Compiling bindgen v0.60.1
   Compiling regex-syntax v0.6.27
   Compiling lazycell v1.3.0
   Compiling bitflags v1.3.2
   Compiling shlex v1.1.0
   Compiling peeking_take_while v0.1.2
   Compiling rustc-hash v1.1.0
   Compiling lazy_static v1.4.0
   Compiling libloading v0.7.3
   Compiling clang-sys v1.4.0
   Compiling nom v7.1.1
   Compiling errno v0.2.8
   Compiling regex v1.6.0
   Compiling cexpr v0.6.0
   Compiling loopdev v0.5.0 (/project)
error[E0432]: unresolved imports `crate::bindings::loop_config`, `crate::bindings::LOOP_CONFIGURE`
  --> src/lib.rs:41:5
   |
41 |     loop_config, loop_info64, LOOP_CLR_FD, LOOP_CONFIGURE, LOOP_CTL_GET_FREE, LOOP_SET_CAPACITY,
   |     ^^^^^^^^^^^                            ^^^^^^^^^^^^^^ no `LOOP_CONFIGURE` in `bindings`
   |     |
   |     no `loop_config` in `bindings`

For more information about this error, try `rustc --explain E0432`.
error: could not compile `loopdev` due to previous error

I don't know the exact version of the libc headers used in the NDK here but it's notorious outdated ;-)

LOOP_SET_FD, LOOP_SET_STATUS64, LO_FLAGS_AUTOCLEAR, LO_FLAGS_PARTSCAN, LO_FLAGS_READ_ONLY,
};
#[cfg(feature = "direct_io")]
use bindings::LOOP_SET_DIRECT_IO;
Expand All @@ -50,6 +50,8 @@ use std::{
io,
os::unix::prelude::*,
path::{Path, PathBuf},
thread::sleep,
time::Duration,
};

#[allow(non_camel_case_types)]
Expand Down Expand Up @@ -231,8 +233,47 @@ impl LoopDevice {
self.attach_fd_with_loop_info(bf, info)
}

/// Attach the loop device to a fd with `loop_info`.
/// 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<()> {
let cfg = loop_config {
fd: bf.as_raw_fd().try_into().unwrap(),
info,
..Default::default()
};

// First attempt the `LOOP_CONFIGURE` ioctl, which makes the operation
// atomic because the device is created with a single ioctl.
let mut retries = 0;
loop {
let result = unsafe {
ioctl(
self.device.as_raw_fd() as c_int,
LOOP_CONFIGURE as IoctlRequest,
&cfg,
)
};

match ioctl_to_error(result) {
Err(err) => match err.raw_os_error() {
// According to the `losetup` source code, these conditions
// trigger the fallback logic.
Some(libc::EINVAL) | Some(libc::ENOTTY) | Some(libc::ENOSYS) => break,
Some(libc::EAGAIN) => {
// Use the same max retries and sleep duration from the
// `losetup` source.
if retries >= 10 {
return Err(err);
}
retries += 1;
sleep(Duration::from_micros(25000));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a specific reason the value?

Depending on the use case 25ms can be in acceptable long time.

continue;
}
_ => return Err(err),
},
Ok(_) => return Ok(()),
};
}

// Attach the file
ioctl_to_error(unsafe {
ioctl(
Expand Down