xref: /aosp_15_r20/external/crosvm/base/src/sys/windows/file_util.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::ffi::c_void;
6 use std::fs::File;
7 use std::fs::OpenOptions;
8 use std::io;
9 use std::mem::size_of;
10 use std::ops::Range;
11 use std::path::Path;
12 
13 use win_util::LargeInteger;
14 use winapi::um::fileapi::GetFileSizeEx;
15 pub use winapi::um::winioctl::FSCTL_QUERY_ALLOCATED_RANGES;
16 pub use winapi::um::winioctl::FSCTL_SET_SPARSE;
17 use winapi::um::winnt::LARGE_INTEGER;
18 
19 use crate::descriptor::AsRawDescriptor;
20 use crate::Error;
21 use crate::Result;
22 
23 /// Open the file with the given path.
24 ///
25 /// Note that on POSIX, this wrapper handles opening existing FDs via /proc/self/fd/N. On Windows,
26 /// this functionality doesn't exist, but we preserve this seemingly not very useful function to
27 /// simplify cross platform code.
open_file_or_duplicate<P: AsRef<Path>>(path: P, options: &OpenOptions) -> Result<File>28 pub fn open_file_or_duplicate<P: AsRef<Path>>(path: P, options: &OpenOptions) -> Result<File> {
29     Ok(options.open(path)?)
30 }
31 
32 /// Marks the given file as sparse. Required if we want hole punching to be performant.
33 /// (If a file is not marked as sparse, a hole punch will just write zeros.)
set_sparse_file(file: &File) -> io::Result<()>34 pub fn set_sparse_file(file: &File) -> io::Result<()> {
35     // SAFETY:
36     // Safe because we check the return value and handle is guaranteed to be a
37     // valid file handle by the caller.
38     let result = unsafe {
39         super::ioctl::ioctl_with_ptr(file, FSCTL_SET_SPARSE, std::ptr::null_mut::<c_void>())
40     };
41     if result != 0 {
42         return Err(io::Error::from_raw_os_error(result));
43     }
44     Ok(())
45 }
46 
47 #[repr(C)]
48 #[derive(Clone, Copy, Default)]
49 struct FileAllocatedRangeBuffer {
50     file_offset: LARGE_INTEGER,
51     length: LARGE_INTEGER,
52 }
53 
54 /// Helper routine that converts LARGE_INTEGER to u64
55 /// # Safety
56 /// Within this scope it is not possible to use LARGE_INTEGER as something else.
large_integer_as_u64(lint: &LARGE_INTEGER) -> u6457 fn large_integer_as_u64(lint: &LARGE_INTEGER) -> u64 {
58     // SAFETY:
59     // Safe because we use LARGE_INTEGER only as i64 or as u64 within this scope.
60     unsafe { *lint.QuadPart() as u64 }
61 }
62 
63 impl FileAllocatedRangeBuffer {
start(&self) -> u6464     pub fn start(&self) -> u64 {
65         large_integer_as_u64(&self.file_offset)
66     }
67 
end(&self) -> u6468     pub fn end(&self) -> u64 {
69         self.start() + self.length()
70     }
71 
length(&self) -> u6472     pub fn length(&self) -> u64 {
73         large_integer_as_u64(&self.length)
74     }
75 }
76 
77 /// On success returns a vector of ranges with a file that have storage allocated
78 /// for them. The range is half-open [start, end) offsets.
79 /// Contiguous allocated ranges may not be coalesced meaning the output may contain
80 /// two or more ranges which could have been coalesced into one - ex: Output may
81 /// contain [0..100, 100.. 200] instead of just one range [0..200]
82 /// # Safety
83 ///    descriptor *must* be File. We accept all AsRawDescriptors for convenience.
get_allocated_ranges<T: AsRawDescriptor>(descriptor: &T) -> Result<Vec<Range<u64>>>84 pub fn get_allocated_ranges<T: AsRawDescriptor>(descriptor: &T) -> Result<Vec<Range<u64>>> {
85     let mut ranges = vec![];
86     let mut file_size = *LargeInteger::new(0);
87 
88     // SAFETY:
89     // Safe because we check return value.
90     unsafe {
91         let failed = GetFileSizeEx(descriptor.as_raw_descriptor(), &mut file_size);
92         if failed == 0 {
93             return crate::errno_result();
94         }
95     };
96 
97     // Query the range for the entire file. This gets updated if the file has
98     // more ranges than what alloc_ranges can hold.
99     let mut query_range = FileAllocatedRangeBuffer {
100         file_offset: *LargeInteger::new(0),
101         length: *LargeInteger::new(0),
102     };
103     query_range.file_offset = *LargeInteger::new(0);
104     query_range.length = file_size;
105 
106     // Preallocated/initialized container for allocated ranges.
107     let mut alloc_ranges: Vec<FileAllocatedRangeBuffer> =
108         vec![Default::default(); if cfg!(test) { 1 } else { 1024 }];
109 
110     loop {
111         let mut bytes_ret: u32 = 0;
112         // SAFETY:
113         // Safe because we return error on failure and all references have
114         // bounded lifetime.
115         // If the `alloc_ranges` buffer is smaller than the actual allocated ranges,
116         // device_io_control returns error ERROR_MORE_DATA with `alloc_ranges` filled with
117         // `bytes_ret` bytes worth of allocated ranges. On getting `ERROR_MORE_DATA` error,
118         //  we update the query_range to reflect new offset range that we want to query.
119         unsafe {
120             crate::device_io_control(
121                 descriptor,
122                 FSCTL_QUERY_ALLOCATED_RANGES,
123                 &query_range,
124                 size_of::<FileAllocatedRangeBuffer>() as u32,
125                 alloc_ranges.as_mut_ptr(),
126                 (size_of::<FileAllocatedRangeBuffer>() * alloc_ranges.len()) as u32,
127                 &mut bytes_ret,
128             )
129             .or_else(|err| {
130                 if Error::new(winapi::shared::winerror::ERROR_MORE_DATA as i32) == err {
131                     Ok(())
132                 } else {
133                     Err(err)
134                 }
135             })?
136         };
137 
138         // Calculate number of entries populated by the syscall.
139         let range_count = (bytes_ret / size_of::<FileAllocatedRangeBuffer>() as u32) as usize;
140 
141         // This guards against somethis that went really wrong with the syscall
142         // to not return bytes that are multiple of struct size.
143         if (range_count * size_of::<FileAllocatedRangeBuffer>()) != bytes_ret as usize {
144             panic!("Something went wrong");
145         }
146 
147         // device_io_control returned successfully with empty output buffer implies
148         // that there are no more allocated ranges in the file.
149         if range_count == 0 {
150             break;
151         }
152 
153         for r in &alloc_ranges[0..range_count] {
154             let range = r.start()..r.end();
155             ranges.push(range);
156         }
157 
158         // Update offset so that we resume from where last call ended successfully.
159         query_range.file_offset = *LargeInteger::new(alloc_ranges[range_count - 1].end() as i64);
160         query_range.length =
161             *LargeInteger::new((large_integer_as_u64(&file_size) - query_range.start()) as i64);
162     }
163 
164     Ok(ranges)
165 }
166 
167 #[cfg(test)]
168 mod tests {
169     use std::io::Write;
170     use std::os::windows::prelude::FileExt;
171 
172     use tempfile::tempfile;
173 
174     use super::get_allocated_ranges;
175     use super::set_sparse_file;
176 
177     #[test]
get_allocated_ranges_for_empty_file()178     fn get_allocated_ranges_for_empty_file() {
179         let file = tempfile().unwrap();
180         set_sparse_file(&file).unwrap();
181         let ranges = get_allocated_ranges(&file).unwrap();
182         assert!(ranges.is_empty());
183     }
184 
185     #[test]
get_allocated_ranges_for_fully_allocated_file()186     fn get_allocated_ranges_for_fully_allocated_file() {
187         let mut file = tempfile().unwrap();
188         set_sparse_file(&file).unwrap();
189         let zeroes = vec![0; 1024 * 1024];
190         file.write_all(&zeroes).unwrap();
191         let ranges = get_allocated_ranges(&file).unwrap();
192         // Output will have at least one allocated range.
193         assert!(!ranges.is_empty());
194         let mut old_range: Option<std::ops::Range<u64>> = None;
195         for r in ranges {
196             if old_range.is_none() {
197                 assert_eq!(r.start, 0);
198             } else {
199                 assert_eq!(r.start, old_range.as_ref().unwrap().end);
200             }
201             old_range = Some(r.clone());
202         }
203     }
204 
205     #[test]
get_allocated_ranges_for_file_with_one_hole()206     fn get_allocated_ranges_for_file_with_one_hole() {
207         let mut file = tempfile().unwrap();
208         set_sparse_file(&file).unwrap();
209         let zeroes = vec![1; 1024 * 1024];
210         file.write_all(&zeroes).unwrap();
211         file.set_len(1024 * 1024 * 3).unwrap();
212         file.seek_write(&zeroes, 1024 * 1024 * 2).unwrap();
213         let ranges = get_allocated_ranges(&file).unwrap();
214         assert!(ranges.len() > 1);
215 
216         let mut old_range: Option<std::ops::Range<u64>> = None;
217         for r in ranges {
218             // First allocated range starts at 0 offset
219             if old_range.is_none() {
220                 assert_eq!(r.start, 0);
221             } else if r.start != old_range.as_ref().unwrap().end {
222                 // The allocated range before the hole ends at 1M offset.
223                 assert_eq!(old_range.as_ref().unwrap().end, 1024 * 1024 * 1);
224                 // The allocated range after the hole starts at 2M offset.
225                 assert_eq!(r.start, 1024 * 1024 * 2);
226             }
227             old_range = Some(r.clone());
228         }
229         assert_eq!(old_range.as_ref().unwrap().end, 1024 * 1024 * 3);
230     }
231 
232     #[test]
get_allocated_ranges_for_file_with_many_hole()233     fn get_allocated_ranges_for_file_with_many_hole() {
234         let mut file = tempfile().unwrap();
235         set_sparse_file(&file).unwrap();
236         let data = vec![1; 1024];
237         file.write_all(&data).unwrap();
238         const RANGE_COUNT: u64 = 2048;
239         file.set_len(1024 * 1024 * RANGE_COUNT).unwrap();
240         for i in 1..RANGE_COUNT {
241             file.seek_write(&data, 1024 * 1024 * i).unwrap();
242         }
243         let ranges = get_allocated_ranges(&file).unwrap();
244         assert_eq!(ranges.len(), RANGE_COUNT as usize);
245     }
246 }
247