1#![allow(deref_nullptr)]
3#![allow(unknown_lints)]
4
5extern crate libc;
42
43#[cfg(feature = "direct_io")]
44use bindings::LOOP_SET_DIRECT_IO;
45use bindings::{
46 loop_info64, LOOP_CLR_FD, LOOP_CTL_GET_FREE, LOOP_SET_CAPACITY, LOOP_SET_FD, LOOP_SET_STATUS64,
47 LO_FLAGS_AUTOCLEAR, LO_FLAGS_READ_ONLY,
48};
49use libc::{c_int, ioctl};
50use std::{
51 default::Default,
52 fs::{File, OpenOptions},
53 io,
54 os::unix::prelude::*,
55 path::{Path, PathBuf},
56};
57
58#[allow(non_camel_case_types)]
59#[allow(dead_code)]
60#[allow(non_snake_case)]
61mod bindings {
62 include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
63}
64
65#[cfg(all(not(target_os = "android"), not(target_env = "musl")))]
66type IoctlRequest = libc::c_ulong;
67#[cfg(any(target_os = "android", target_env = "musl"))]
68type IoctlRequest = libc::c_int;
69
70const LOOP_CONTROL: &str = "/dev/loop-control";
71#[cfg(not(target_os = "android"))]
72const LOOP_PREFIX: &str = "/dev/loop";
73#[cfg(target_os = "android")]
74const LOOP_PREFIX: &str = "/dev/block/loop";
75
76#[derive(Debug)]
78pub struct LoopControl {
79 dev_file: File,
80}
81
82impl LoopControl {
83 pub fn open() -> io::Result<Self> {
85 Ok(Self {
86 dev_file: OpenOptions::new()
87 .read(true)
88 .write(true)
89 .open(LOOP_CONTROL)?,
90 })
91 }
92
93 pub fn next_free(&self) -> io::Result<LoopDevice> {
104 let dev_num = ioctl_to_error(unsafe {
105 ioctl(
106 self.dev_file.as_raw_fd() as c_int,
107 LOOP_CTL_GET_FREE as IoctlRequest,
108 )
109 })?;
110 LoopDevice::open(&format!("{}{}", LOOP_PREFIX, dev_num))
111 }
112}
113
114impl AsRawFd for LoopControl {
115 fn as_raw_fd(&self) -> RawFd {
116 self.dev_file.as_raw_fd()
117 }
118}
119
120impl IntoRawFd for LoopControl {
121 fn into_raw_fd(self) -> RawFd {
122 self.dev_file.into_raw_fd()
123 }
124}
125
126#[derive(Debug)]
128pub struct LoopDevice {
129 device: File,
130}
131
132impl AsRawFd for LoopDevice {
133 fn as_raw_fd(&self) -> RawFd {
134 self.device.as_raw_fd()
135 }
136}
137
138impl IntoRawFd for LoopDevice {
139 fn into_raw_fd(self) -> RawFd {
140 self.device.into_raw_fd()
141 }
142}
143
144impl LoopDevice {
145 pub fn open<P: AsRef<Path>>(dev: P) -> io::Result<Self> {
147 Ok(Self {
149 device: OpenOptions::new().read(true).write(true).open(dev)?,
150 })
151 }
152
153 pub fn with(&self) -> AttachOptions<'_> {
166 AttachOptions {
167 device: self,
168 info: Default::default(),
169 #[cfg(feature = "direct_io")]
170 direct_io: false,
171 }
172 }
173
174 pub fn attach_file<P: AsRef<Path>>(&self, backing_file: P) -> io::Result<()> {
187 let info = loop_info64 {
188 ..Default::default()
189 };
190
191 Self::attach_with_loop_info(self, backing_file, info)
192 }
193
194 fn attach_with_loop_info(
196 &self, backing_file: impl AsRef<Path>,
198 info: loop_info64,
199 ) -> io::Result<()> {
200 let write_access = (info.lo_flags & LO_FLAGS_READ_ONLY) == 0;
201 let bf = OpenOptions::new()
202 .read(true)
203 .write(write_access)
204 .open(backing_file)?;
205 self.attach_fd_with_loop_info(bf, info)
206 }
207
208 fn attach_fd_with_loop_info(&self, bf: impl AsRawFd, info: loop_info64) -> io::Result<()> {
210 ioctl_to_error(unsafe {
212 ioctl(
213 self.device.as_raw_fd() as c_int,
214 LOOP_SET_FD as IoctlRequest,
215 bf.as_raw_fd() as c_int,
216 )
217 })?;
218
219 let result = unsafe {
220 ioctl(
221 self.device.as_raw_fd() as c_int,
222 LOOP_SET_STATUS64 as IoctlRequest,
223 &info,
224 )
225 };
226 match ioctl_to_error(result) {
227 Err(err) => {
228 let _ = self.detach();
230 Err(err)
231 }
232 Ok(_) => Ok(()),
233 }
234 }
235
236 pub fn path(&self) -> Option<PathBuf> {
238 let mut p = PathBuf::from("/proc/self/fd");
239 p.push(self.device.as_raw_fd().to_string());
240 std::fs::read_link(&p).ok()
241 }
242
243 pub fn major(&self) -> io::Result<u32> {
245 self.device
246 .metadata()
247 .map(|m| unsafe { libc::major(m.rdev()) as u32 })
248 }
249
250 pub fn minor(&self) -> io::Result<u32> {
252 self.device
253 .metadata()
254 .map(|m| unsafe { libc::minor(m.rdev()) as u32 })
255 }
256
257 pub fn detach(&self) -> io::Result<()> {
272 ioctl_to_error(unsafe {
273 ioctl(
274 self.device.as_raw_fd() as c_int,
275 LOOP_CLR_FD as IoctlRequest,
276 0,
277 )
278 })?;
279 Ok(())
280 }
281
282 pub fn set_capacity(&self) -> io::Result<()> {
285 ioctl_to_error(unsafe {
286 ioctl(
287 self.device.as_raw_fd() as c_int,
288 LOOP_SET_CAPACITY as IoctlRequest,
289 0,
290 )
291 })?;
292 Ok(())
293 }
294
295 #[cfg(feature = "direct_io")]
297 pub fn set_direct_io(&self, direct_io: bool) -> io::Result<()> {
298 ioctl_to_error(unsafe {
299 ioctl(
300 self.device.as_raw_fd() as c_int,
301 LOOP_SET_DIRECT_IO as IoctlRequest,
302 if direct_io { 1 } else { 0 },
303 )
304 })?;
305 Ok(())
306 }
307}
308
309pub struct AttachOptions<'d> {
338 device: &'d LoopDevice,
339 info: loop_info64,
340 #[cfg(feature = "direct_io")]
341 direct_io: bool,
342}
343
344impl AttachOptions<'_> {
345 pub fn offset(mut self, offset: u64) -> Self {
347 self.info.lo_offset = offset;
348 self
349 }
350
351 pub fn size_limit(mut self, size_limit: u64) -> Self {
353 self.info.lo_sizelimit = size_limit;
354 self
355 }
356
357 pub fn read_only(mut self, read_only: bool) -> Self {
359 if read_only {
360 self.info.lo_flags |= LO_FLAGS_READ_ONLY;
361 } else {
362 self.info.lo_flags &= !LO_FLAGS_READ_ONLY;
363 }
364 self
365 }
366
367 pub fn autoclear(mut self, read_only: bool) -> Self {
369 if read_only {
370 self.info.lo_flags |= LO_FLAGS_AUTOCLEAR;
371 } else {
372 self.info.lo_flags &= !LO_FLAGS_AUTOCLEAR;
373 }
374 self
375 }
376
377 #[cfg(feature = "direct_io")]
379 pub fn set_direct_io(mut self, direct_io: bool) -> Self {
380 self.direct_io = direct_io;
381 self
382 }
383
384 pub fn part_scan(mut self, enable: bool) -> Self {
387 if enable {
388 self.info.lo_flags |= 1 << 4;
389 } else {
390 self.info.lo_flags &= u32::max_value() - (1 << 4);
391 }
392 self
393 }
394
395 pub fn attach(self, backing_file: impl AsRef<Path>) -> io::Result<()> {
397 self.device.attach_with_loop_info(backing_file, self.info)?;
398 #[cfg(feature = "direct_io")]
399 if self.direct_io {
400 self.device.set_direct_io(self.direct_io)?;
401 }
402 Ok(())
403 }
404
405 pub fn attach_fd(self, backing_file_fd: impl AsRawFd) -> io::Result<()> {
407 self.device
408 .attach_fd_with_loop_info(backing_file_fd, self.info)?;
409 #[cfg(feature = "direct_io")]
410 if self.direct_io {
411 self.device.set_direct_io(self.direct_io)?;
412 }
413 Ok(())
414 }
415}
416
417fn ioctl_to_error(ret: i32) -> io::Result<i32> {
418 if ret < 0 {
419 Err(io::Error::last_os_error())
420 } else {
421 Ok(ret)
422 }
423}