• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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