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
18const LOOP_CTL_GET_FREE: IoctlRequest = 0x4C82;
20
21const 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
27const 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#[derive(Debug)]
36pub struct LoopControl {
37 dev_file: File,
38}
39
40fn 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 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 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#[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 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 pub fn with(&self) -> AttachOptions<'_> {
122 AttachOptions {
123 device: self,
124 info: LoopInfo64::default(),
125 }
126 }
127
128 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 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 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 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 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 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 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 pub fn major(&self) -> io::Result<u32> {
223 self.device
224 .metadata()
225 .map(|m| unsafe { libc::major(m.rdev()) })
226 }
227
228 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}