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