Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/base/src/sys/windows/file_util.rs
5394 views
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.
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
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.
57
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 {
64
pub fn start(&self) -> u64 {
65
large_integer_as_u64(&self.file_offset)
66
}
67
68
pub fn end(&self) -> u64 {
69
self.start() + self.length()
70
}
71
72
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.
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]
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]
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]
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]
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
248