1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 // `loopdevice` module provides `attach` and `detach` functions that are for attaching and
18 // detaching a regular file to and from a loop device. Note that
19 // `loopdev`(https://crates.io/crates/loopdev) is a public alternative to this. In-house
20 // implementation was chosen to make Android-specific changes (like the use of the new
21 // LOOP_CONFIGURE instead of the legacy LOOP_SET_FD + LOOP_SET_STATUS64 combo which is considerably
22 // slower than the former).
23 
24 mod sys;
25 
26 use crate::util::*;
27 use anyhow::{Context, Result};
28 use libc::O_DIRECT;
29 use std::fs::{File, OpenOptions};
30 use std::os::unix::fs::OpenOptionsExt;
31 use std::os::unix::io::AsRawFd;
32 use std::path::{Path, PathBuf};
33 use std::thread;
34 use std::time::{Duration, Instant};
35 use zerocopy::FromZeroes;
36 
37 use crate::loopdevice::sys::*;
38 
39 // These are old-style ioctls, thus *_bad.
40 nix::ioctl_none_bad!(_loop_ctl_get_free, LOOP_CTL_GET_FREE);
41 nix::ioctl_write_ptr_bad!(_loop_configure, LOOP_CONFIGURE, loop_config);
42 nix::ioctl_none_bad!(_loop_clr_fd, LOOP_CLR_FD);
43 
loop_ctl_get_free(ctrl_file: &File) -> Result<i32>44 fn loop_ctl_get_free(ctrl_file: &File) -> Result<i32> {
45     // SAFETY: this ioctl changes the state in kernel, but not the state in this process.
46     // The returned device number is a global resource; not tied to this process. So, we don't
47     // need to keep track of it.
48     Ok(unsafe { _loop_ctl_get_free(ctrl_file.as_raw_fd()) }?)
49 }
50 
loop_configure(device_file: &File, config: &loop_config) -> Result<i32>51 fn loop_configure(device_file: &File, config: &loop_config) -> Result<i32> {
52     // SAFETY: this ioctl changes the state in kernel, but not the state in this process.
53     Ok(unsafe { _loop_configure(device_file.as_raw_fd(), config) }?)
54 }
55 
loop_clr_fd(device_file: &File) -> Result<i32>56 pub fn loop_clr_fd(device_file: &File) -> Result<i32> {
57     // SAFETY: this ioctl disassociates the loop device with `device_file`, where the FD will
58     // remain opened afterward. The association itself is kept for open FDs.
59     Ok(unsafe { _loop_clr_fd(device_file.as_raw_fd()) }?)
60 }
61 
62 /// Creates a loop device and attach the given file at `path` as the backing store.
attach<P: AsRef<Path>>( path: P, offset: u64, size_limit: u64, direct_io: bool, writable: bool, ) -> Result<PathBuf>63 pub fn attach<P: AsRef<Path>>(
64     path: P,
65     offset: u64,
66     size_limit: u64,
67     direct_io: bool,
68     writable: bool,
69 ) -> Result<PathBuf> {
70     // Attaching a file to a loop device can make a race condition; a loop device number obtained
71     // from LOOP_CTL_GET_FREE might have been used by another thread or process. In that case the
72     // subsequent LOOP_CONFIGURE ioctl returns with EBUSY. Try until it succeeds.
73     //
74     // Note that the timing parameters below are chosen rather arbitrarily. In practice (i.e.
75     // inside Microdroid) we can't experience the race condition because `apkverity` is the only
76     // user of /dev/loop-control at the moment. This loop is mostly for testing where multiple
77     // tests run concurrently.
78     const TIMEOUT: Duration = Duration::from_secs(1);
79     const INTERVAL: Duration = Duration::from_millis(10);
80 
81     let begin = Instant::now();
82     loop {
83         match try_attach(&path, offset, size_limit, direct_io, writable) {
84             Ok(loop_dev) => return Ok(loop_dev),
85             Err(e) => {
86                 if begin.elapsed() > TIMEOUT {
87                     return Err(e);
88                 }
89             }
90         };
91         thread::sleep(INTERVAL);
92     }
93 }
94 
95 #[cfg(not(target_os = "android"))]
96 const LOOP_DEV_PREFIX: &str = "/dev/loop";
97 
98 #[cfg(target_os = "android")]
99 const LOOP_DEV_PREFIX: &str = "/dev/block/loop";
100 
try_attach<P: AsRef<Path>>( path: P, offset: u64, size_limit: u64, direct_io: bool, writable: bool, ) -> Result<PathBuf>101 fn try_attach<P: AsRef<Path>>(
102     path: P,
103     offset: u64,
104     size_limit: u64,
105     direct_io: bool,
106     writable: bool,
107 ) -> Result<PathBuf> {
108     // Get a free loop device
109     wait_for_path(LOOP_CONTROL)?;
110     let ctrl_file = OpenOptions::new()
111         .read(true)
112         .write(true)
113         .open(LOOP_CONTROL)
114         .context("Failed to open loop control")?;
115     let num = loop_ctl_get_free(&ctrl_file).context("Failed to get free loop device")?;
116 
117     // Construct the loop_info64 struct
118     let backing_file = OpenOptions::new()
119         .read(true)
120         .write(writable)
121         .custom_flags(if direct_io { O_DIRECT } else { 0 })
122         .open(&path)
123         .context(format!("failed to open {:?}", path.as_ref()))?;
124     let mut config = loop_config::new_zeroed();
125     config.fd = backing_file.as_raw_fd() as u32;
126     config.block_size = 4096;
127     config.info.lo_offset = offset;
128     config.info.lo_sizelimit = size_limit;
129 
130     if !writable {
131         config.info.lo_flags = Flag::LO_FLAGS_READ_ONLY;
132     }
133 
134     if direct_io {
135         config.info.lo_flags.insert(Flag::LO_FLAGS_DIRECT_IO);
136     }
137 
138     // Configure the loop device to attach the backing file
139     let device_path = format!("{}{}", LOOP_DEV_PREFIX, num);
140     wait_for_path(&device_path)?;
141     let device_file = OpenOptions::new()
142         .read(true)
143         .write(true)
144         .open(&device_path)
145         .context(format!("failed to open {:?}", &device_path))?;
146     loop_configure(&device_file, &config)
147         .context(format!("Failed to configure {:?}", &device_path))?;
148 
149     Ok(PathBuf::from(device_path))
150 }
151 
152 /// Detaches backing file from the loop device `path`.
detach<P: AsRef<Path>>(path: P) -> Result<()>153 pub fn detach<P: AsRef<Path>>(path: P) -> Result<()> {
154     let device_file = OpenOptions::new().read(true).write(true).open(&path)?;
155     loop_clr_fd(&device_file)?;
156     Ok(())
157 }
158 
159 #[cfg(test)]
160 mod tests {
161     use super::*;
162     use std::fs;
163     use std::path::Path;
164 
create_empty_file(path: &Path, size: u64)165     fn create_empty_file(path: &Path, size: u64) {
166         let f = File::create(path).unwrap();
167         f.set_len(size).unwrap();
168     }
169 
is_direct_io(dev: &Path) -> bool170     fn is_direct_io(dev: &Path) -> bool {
171         let dio = Path::new("/sys/block").join(dev.file_name().unwrap()).join("loop/dio");
172         "1" == fs::read_to_string(dio).unwrap().trim()
173     }
174 
175     // kernel exposes /sys/block/loop*/ro which gives the read-only value
is_direct_io_writable(dev: &Path) -> bool176     fn is_direct_io_writable(dev: &Path) -> bool {
177         let ro = Path::new("/sys/block").join(dev.file_name().unwrap()).join("ro");
178         "0" == fs::read_to_string(ro).unwrap().trim()
179     }
180 
181     #[test]
attach_loop_device_with_dio()182     fn attach_loop_device_with_dio() {
183         let a_dir = tempfile::TempDir::new().unwrap();
184         let a_file = a_dir.path().join("test");
185         let a_size = 4096u64;
186         create_empty_file(&a_file, a_size);
187         let dev = attach(a_file, 0, a_size, /*direct_io*/ true, /*writable*/ false).unwrap();
188         scopeguard::defer! {
189             detach(&dev).unwrap();
190         }
191         assert!(is_direct_io(&dev));
192     }
193 
194     #[test]
attach_loop_device_without_dio()195     fn attach_loop_device_without_dio() {
196         let a_dir = tempfile::TempDir::new().unwrap();
197         let a_file = a_dir.path().join("test");
198         let a_size = 4096u64;
199         create_empty_file(&a_file, a_size);
200         let dev = attach(a_file, 0, a_size, /*direct_io*/ false, /*writable*/ false).unwrap();
201         scopeguard::defer! {
202             detach(&dev).unwrap();
203         }
204         assert!(!is_direct_io(&dev));
205     }
206 
207     #[test]
attach_loop_device_with_dio_writable()208     fn attach_loop_device_with_dio_writable() {
209         let a_dir = tempfile::TempDir::new().unwrap();
210         let a_file = a_dir.path().join("test");
211         let a_size = 4096u64;
212         create_empty_file(&a_file, a_size);
213         let dev = attach(a_file, 0, a_size, /*direct_io*/ true, /*writable*/ true).unwrap();
214         scopeguard::defer! {
215             detach(&dev).unwrap();
216         }
217         assert!(is_direct_io(&dev));
218         assert!(is_direct_io_writable(&dev));
219     }
220 }
221