krataloopdev/
lib.rs

1use libc::{c_int, ioctl};
2use std::{
3    fs::{File, OpenOptions},
4    io,
5    os::fd::{AsRawFd, IntoRawFd, RawFd},
6    os::unix::fs::MetadataExt,
7    path::{Path, PathBuf},
8};
9
10#[cfg(all(not(target_os = "android"), not(target_env = "musl")))]
11type IoctlRequest = libc::c_ulong;
12#[cfg(any(target_os = "android", target_env = "musl"))]
13type IoctlRequest = libc::c_int;
14
15const LOOP_CONTROL: &str = "/dev/loop-control";
16const LOOP_PREFIX: &str = "/dev/loop";
17
18/// Loop control interface IOCTLs.
19const LOOP_CTL_GET_FREE: IoctlRequest = 0x4C82;
20
21/// Loop device flags.
22const LO_FLAGS_READ_ONLY: u32 = 1;
23const LO_FLAGS_AUTOCLEAR: u32 = 4;
24const LO_FLAGS_PARTSCAN: u32 = 8;
25const LO_FLAGS_DIRECT_IO: u32 = 16;
26
27/// Loop device IOCTLs.
28const LOOP_SET_FD: IoctlRequest = 0x4C00;
29const LOOP_CLR_FD: IoctlRequest = 0x4C01;
30const LOOP_SET_STATUS64: IoctlRequest = 0x4C04;
31const LOOP_SET_CAPACITY: IoctlRequest = 0x4C07;
32const LOOP_SET_DIRECT_IO: IoctlRequest = 0x4C08;
33
34/// Interface which wraps a handle to the loop control device.
35#[derive(Debug)]
36pub struct LoopControl {
37    dev_file: File,
38}
39
40/// Translate ioctl results to errors if appropriate.
41fn translate_error(ret: i32) -> io::Result<i32> {
42    if ret < 0 {
43        Err(io::Error::last_os_error())
44    } else {
45        Ok(ret)
46    }
47}
48
49impl LoopControl {
50    /// Open the loop control device.
51    ///
52    /// # Errors
53    ///
54    /// Any errors from physically opening the loop control device are
55    /// bubbled up.
56    pub fn open() -> io::Result<Self> {
57        Ok(Self {
58            dev_file: OpenOptions::new()
59                .read(true)
60                .write(true)
61                .open(LOOP_CONTROL)?,
62        })
63    }
64
65    /// Requests the next available loop device from the kernel and opens it.
66    ///
67    /// # Examples
68    ///
69    /// ```no_run
70    /// use krataloopdev::LoopControl;
71    /// let lc = LoopControl::open().unwrap();
72    /// let ld = lc.next_free().unwrap();
73    /// println!("{}", ld.path().unwrap().display());
74    /// ```
75    ///
76    /// # Errors
77    ///
78    /// Any errors from opening the loop device are bubbled up.
79    pub fn next_free(&self) -> io::Result<LoopDevice> {
80        let dev_num = translate_error(unsafe {
81            ioctl(
82                self.dev_file.as_raw_fd() as c_int,
83                LOOP_CTL_GET_FREE as IoctlRequest,
84            )
85        })?;
86        LoopDevice::open(format!("{}{}", LOOP_PREFIX, dev_num))
87    }
88}
89
90/// Interface to a loop device itself, e.g. `/dev/loop0`.
91#[derive(Debug)]
92pub struct LoopDevice {
93    device: File,
94}
95
96impl AsRawFd for LoopDevice {
97    fn as_raw_fd(&self) -> RawFd {
98        self.device.as_raw_fd()
99    }
100}
101
102impl IntoRawFd for LoopDevice {
103    fn into_raw_fd(self) -> RawFd {
104        self.device.into_raw_fd()
105    }
106}
107
108impl LoopDevice {
109    /// Opens a loop device.
110    ///
111    /// # Errors
112    ///
113    /// Any errors from opening the underlying physical loop device are bubbled up.
114    pub fn open<P: AsRef<Path>>(dev: P) -> io::Result<Self> {
115        Ok(Self {
116            device: OpenOptions::new().read(true).write(true).open(dev)?,
117        })
118    }
119
120    /// Attach a loop device to a file with the given options.
121    pub fn with(&self) -> AttachOptions<'_> {
122        AttachOptions {
123            device: self,
124            info: LoopInfo64::default(),
125        }
126    }
127
128    /// Enables or disables Direct I/O mode.
129    pub fn set_direct_io(&self, direct_io: bool) -> io::Result<()> {
130        translate_error(unsafe {
131            ioctl(
132                self.device.as_raw_fd() as c_int,
133                LOOP_SET_DIRECT_IO as IoctlRequest,
134                if direct_io { 1 } else { 0 },
135            )
136        })?;
137        Ok(())
138    }
139
140    /// Attach the loop device to a fully-mapped file.
141    pub fn attach_file<P: AsRef<Path>>(&self, backing_file: P) -> io::Result<()> {
142        let info = LoopInfo64 {
143            ..Default::default()
144        };
145
146        Self::attach_with_loop_info(self, backing_file, info)
147    }
148
149    /// Attach the loop device to a file with `LoopInfo64`.
150    fn attach_with_loop_info(
151        &self,
152        backing_file: impl AsRef<Path>,
153        info: LoopInfo64,
154    ) -> io::Result<()> {
155        let write_access = (info.lo_flags & LO_FLAGS_READ_ONLY) == 0;
156        let bf = OpenOptions::new()
157            .read(true)
158            .write(write_access)
159            .open(backing_file)?;
160        self.attach_fd_with_loop_info(bf, info)
161    }
162
163    /// Attach the loop device to a file descriptor with `LoopInfo64`.
164    fn attach_fd_with_loop_info(&self, bf: impl AsRawFd, info: LoopInfo64) -> io::Result<()> {
165        translate_error(unsafe {
166            ioctl(
167                self.device.as_raw_fd() as c_int,
168                LOOP_SET_FD as IoctlRequest,
169                bf.as_raw_fd() as c_int,
170            )
171        })?;
172
173        let result = unsafe {
174            ioctl(
175                self.device.as_raw_fd() as c_int,
176                LOOP_SET_STATUS64 as IoctlRequest,
177                &info,
178            )
179        };
180
181        match translate_error(result) {
182            Err(err) => {
183                let _detach_err = self.detach();
184                Err(err)
185            }
186            Ok(_) => Ok(()),
187        }
188    }
189
190    /// Get the path for the loop device.
191    pub fn path(&self) -> Option<PathBuf> {
192        let mut p = PathBuf::from("/proc/self/fd");
193        p.push(self.device.as_raw_fd().to_string());
194        std::fs::read_link(&p).ok()
195    }
196
197    /// Detach a loop device.
198    pub fn detach(&self) -> io::Result<()> {
199        translate_error(unsafe {
200            ioctl(
201                self.device.as_raw_fd() as c_int,
202                LOOP_CLR_FD as IoctlRequest,
203                0,
204            )
205        })?;
206        Ok(())
207    }
208
209    /// Update a loop device's capacity.
210    pub fn set_capacity(&self) -> io::Result<()> {
211        translate_error(unsafe {
212            ioctl(
213                self.device.as_raw_fd() as c_int,
214                LOOP_SET_CAPACITY as IoctlRequest,
215                0,
216            )
217        })?;
218        Ok(())
219    }
220
221    /// Return the major device node number.
222    pub fn major(&self) -> io::Result<u32> {
223        self.device
224            .metadata()
225            .map(|m| unsafe { libc::major(m.rdev()) })
226    }
227
228    /// Return the minor device node number.
229    pub fn minor(&self) -> io::Result<u32> {
230        self.device
231            .metadata()
232            .map(|m| unsafe { libc::minor(m.rdev()) })
233    }
234}
235
236#[allow(dead_code)]
237#[derive(Clone)]
238pub struct LoopInfo64 {
239    lo_device: u64,
240    lo_inode: u64,
241    lo_rdevice: u64,
242    lo_offset: u64,
243    lo_sizelimit: u64,
244    lo_number: u32,
245    lo_encrypt_type: u32,
246    lo_encrypt_key_size: u32,
247    lo_flags: u32,
248    lo_file_name: [u8; 64],
249    lo_crypt_name: [u8; 64],
250    lo_encrypt_key: [u8; 32],
251    lo_init: [u64; 2],
252}
253
254impl Default for LoopInfo64 {
255    fn default() -> Self {
256        Self {
257            lo_device: 0,
258            lo_inode: 0,
259            lo_rdevice: 0,
260            lo_offset: 0,
261            lo_sizelimit: 0,
262            lo_number: 0,
263            lo_encrypt_type: 0,
264            lo_encrypt_key_size: 0,
265            lo_flags: 0,
266            lo_file_name: [0; 64],
267            lo_crypt_name: [0; 64],
268            lo_encrypt_key: [0; 32],
269            lo_init: [0, 2],
270        }
271    }
272}
273
274#[must_use]
275pub struct AttachOptions<'d> {
276    device: &'d LoopDevice,
277    info: LoopInfo64,
278}
279
280impl AttachOptions<'_> {
281    pub fn offset(mut self, offset: u64) -> Self {
282        self.info.lo_offset = offset;
283        self
284    }
285
286    pub fn size_limit(mut self, size_limit: u64) -> Self {
287        self.info.lo_sizelimit = size_limit;
288        self
289    }
290
291    pub fn read_only(mut self, read_only: bool) -> Self {
292        if read_only {
293            self.info.lo_flags |= LO_FLAGS_READ_ONLY;
294        } else {
295            self.info.lo_flags &= !LO_FLAGS_READ_ONLY;
296        }
297        self
298    }
299
300    pub fn autoclear(mut self, autoclear: bool) -> Self {
301        if autoclear {
302            self.info.lo_flags |= LO_FLAGS_AUTOCLEAR;
303        } else {
304            self.info.lo_flags &= !LO_FLAGS_AUTOCLEAR;
305        }
306        self
307    }
308
309    pub fn part_scan(mut self, part_scan: bool) -> Self {
310        if part_scan {
311            self.info.lo_flags |= LO_FLAGS_PARTSCAN;
312        } else {
313            self.info.lo_flags &= !LO_FLAGS_PARTSCAN;
314        }
315        self
316    }
317
318    pub fn set_direct_io(mut self, direct_io: bool) -> Self {
319        if direct_io {
320            self.info.lo_flags |= LO_FLAGS_DIRECT_IO;
321        } else {
322            self.info.lo_flags &= !LO_FLAGS_DIRECT_IO;
323        }
324        self
325    }
326
327    pub fn direct_io(&self) -> bool {
328        (self.info.lo_flags & LO_FLAGS_DIRECT_IO) == LO_FLAGS_DIRECT_IO
329    }
330
331    pub fn attach(&self, backing_file: impl AsRef<Path>) -> io::Result<()> {
332        self.device
333            .attach_with_loop_info(backing_file, self.info.clone())?;
334        if self.direct_io() {
335            self.device.set_direct_io(self.direct_io())?;
336        }
337        Ok(())
338    }
339
340    pub fn attach_fd(&self, backing_file_fd: impl AsRawFd) -> io::Result<()> {
341        self.device
342            .attach_fd_with_loop_info(backing_file_fd, self.info.clone())?;
343        if self.direct_io() {
344            self.device.set_direct_io(self.direct_io())?;
345        }
346        Ok(())
347    }
348}