xref: /aosp_15_r20/external/crosvm/disk/src/sys/linux.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1 // Copyright 2022 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 use std::fs::File;
6 use std::io::Read;
7 use std::io::Seek;
8 use std::io::SeekFrom;
9 use std::os::fd::AsRawFd;
10 
11 use cros_async::Executor;
12 
13 use crate::DiskFileParams;
14 use crate::Error;
15 use crate::Result;
16 use crate::SingleFileDisk;
17 
open_raw_disk_image(params: &DiskFileParams) -> Result<File>18 pub fn open_raw_disk_image(params: &DiskFileParams) -> Result<File> {
19     let mut options = File::options();
20     options.read(true).write(!params.is_read_only);
21 
22     let raw_image = base::open_file_or_duplicate(&params.path, &options)
23         .map_err(|e| Error::OpenFile(params.path.display().to_string(), e))?;
24 
25     if params.lock {
26         // Lock the disk image to prevent other crosvm instances from using it.
27         let lock_op = if params.is_read_only {
28             base::FlockOperation::LockShared
29         } else {
30             base::FlockOperation::LockExclusive
31         };
32         base::flock(&raw_image, lock_op, true).map_err(Error::LockFileFailure)?;
33     }
34 
35     // If O_DIRECT is requested, set the flag via fcntl. It is not done at
36     // open_file_or_reuse time because it will reuse existing fd and will
37     // not actually use the given OpenOptions.
38     if params.is_direct {
39         base::add_fd_flags(raw_image.as_raw_fd(), libc::O_DIRECT).map_err(Error::DirectFailed)?;
40     }
41 
42     Ok(raw_image)
43 }
44 
apply_raw_disk_file_options(_raw_image: &File, _is_sparse_file: bool) -> Result<()>45 pub fn apply_raw_disk_file_options(_raw_image: &File, _is_sparse_file: bool) -> Result<()> {
46     // No op on unix.
47     Ok(())
48 }
49 
read_from_disk( mut file: &File, offset: u64, buf: &mut [u8], _overlapped_mode: bool, ) -> Result<()>50 pub fn read_from_disk(
51     mut file: &File,
52     offset: u64,
53     buf: &mut [u8],
54     _overlapped_mode: bool,
55 ) -> Result<()> {
56     file.seek(SeekFrom::Start(offset))
57         .map_err(Error::SeekingFile)?;
58     file.read_exact(buf).map_err(Error::ReadingHeader)
59 }
60 
61 impl SingleFileDisk {
new(disk: File, ex: &Executor) -> Result<Self>62     pub fn new(disk: File, ex: &Executor) -> Result<Self> {
63         let is_block_device_file =
64             base::linux::is_block_file(&disk).map_err(Error::BlockDeviceNew)?;
65         ex.async_from(disk)
66             .map_err(Error::CreateSingleFileDisk)
67             .map(|inner| SingleFileDisk {
68                 inner,
69                 is_block_device_file,
70             })
71     }
72 }
73 
74 #[cfg(test)]
75 mod tests {
76     use std::fs::File;
77     use std::fs::OpenOptions;
78     use std::io::Write;
79 
80     use base::pagesize;
81     use cros_async::Executor;
82     use cros_async::MemRegion;
83     use vm_memory::GuestAddress;
84     use vm_memory::GuestMemory;
85 
86     use crate::*;
87 
88     #[test]
read_async()89     fn read_async() {
90         async fn read_zeros_async(ex: &Executor) {
91             let guest_mem =
92                 Arc::new(GuestMemory::new(&[(GuestAddress(0), pagesize() as u64)]).unwrap());
93             let f = File::open("/dev/zero").unwrap();
94             let async_file = SingleFileDisk::new(f, ex).unwrap();
95             let result = async_file
96                 .read_to_mem(
97                     0,
98                     guest_mem,
99                     MemRegionIter::new(&[MemRegion { offset: 0, len: 48 }]),
100                 )
101                 .await;
102             assert_eq!(48, result.unwrap());
103         }
104 
105         let ex = Executor::new().unwrap();
106         ex.run_until(read_zeros_async(&ex)).unwrap();
107     }
108 
109     #[test]
write_async()110     fn write_async() {
111         async fn write_zeros_async(ex: &Executor) {
112             let guest_mem =
113                 Arc::new(GuestMemory::new(&[(GuestAddress(0), pagesize() as u64)]).unwrap());
114             let f = OpenOptions::new().write(true).open("/dev/null").unwrap();
115             let async_file = SingleFileDisk::new(f, ex).unwrap();
116             let result = async_file
117                 .write_from_mem(
118                     0,
119                     guest_mem,
120                     MemRegionIter::new(&[MemRegion { offset: 0, len: 48 }]),
121                 )
122                 .await;
123             assert_eq!(48, result.unwrap());
124         }
125 
126         let ex = Executor::new().unwrap();
127         ex.run_until(write_zeros_async(&ex)).unwrap();
128     }
129 
130     #[test]
detect_image_type_raw()131     fn detect_image_type_raw() {
132         let mut t = tempfile::tempfile().unwrap();
133         // Fill the first block of the file with "random" data.
134         let buf = "ABCD".as_bytes().repeat(1024);
135         t.write_all(&buf).unwrap();
136         let image_type = detect_image_type(&t, false).expect("failed to detect image type");
137         assert_eq!(image_type, ImageType::Raw);
138     }
139 
140     #[test]
141     #[cfg(feature = "qcow")]
detect_image_type_qcow2()142     fn detect_image_type_qcow2() {
143         let mut t = tempfile::tempfile().unwrap();
144         // Write the qcow2 magic signature. The rest of the header is not filled in, so if
145         // detect_image_type is ever updated to validate more of the header, this test would need
146         // to be updated.
147         let buf: &[u8] = &[0x51, 0x46, 0x49, 0xfb];
148         t.write_all(buf).unwrap();
149         let image_type = detect_image_type(&t, false).expect("failed to detect image type");
150         assert_eq!(image_type, ImageType::Qcow2);
151     }
152 
153     #[test]
154     #[cfg(feature = "android-sparse")]
detect_image_type_android_sparse()155     fn detect_image_type_android_sparse() {
156         let mut t = tempfile::tempfile().unwrap();
157         // Write the Android sparse magic signature. The rest of the header is not filled in, so if
158         // detect_image_type is ever updated to validate more of the header, this test would need
159         // to be updated.
160         let buf: &[u8] = &[0x3a, 0xff, 0x26, 0xed];
161         t.write_all(buf).unwrap();
162         let image_type = detect_image_type(&t, false).expect("failed to detect image type");
163         assert_eq!(image_type, ImageType::AndroidSparse);
164     }
165 
166     #[test]
167     #[cfg(feature = "composite-disk")]
detect_image_type_composite()168     fn detect_image_type_composite() {
169         let mut t = tempfile::tempfile().unwrap();
170         // Write the composite disk magic signature. The rest of the header is not filled in, so if
171         // detect_image_type is ever updated to validate more of the header, this test would need
172         // to be updated.
173         let buf = "composite_disk\x1d".as_bytes();
174         t.write_all(buf).unwrap();
175         let image_type = detect_image_type(&t, false).expect("failed to detect image type");
176         assert_eq!(image_type, ImageType::CompositeDisk);
177     }
178 
179     #[test]
detect_image_type_small_file()180     fn detect_image_type_small_file() {
181         let mut t = tempfile::tempfile().unwrap();
182         // Write a file smaller than the four-byte qcow2/sparse magic to ensure the small file logic
183         // works correctly and handles it as a raw file.
184         let buf: &[u8] = &[0xAA, 0xBB];
185         t.write_all(buf).unwrap();
186         let image_type = detect_image_type(&t, false).expect("failed to detect image type");
187         assert_eq!(image_type, ImageType::Raw);
188     }
189 }
190