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