vmm_sys_util/linux/write_zeroes.rs
1// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2//
3// Copyright 2019 Intel Corporation. All Rights Reserved.
4//
5// Copyright 2018 The Chromium OS Authors. All rights reserved.
6//
7// SPDX-License-Identifier: BSD-3-Clause
8
9//! Traits for replacing a range with a hole and writing zeroes in a file.
10
11use std::cmp::min;
12use std::fs::File;
13use std::io::{Error, ErrorKind, Result, Seek, SeekFrom};
14use std::os::unix::fs::FileExt;
15
16use crate::fallocate::{fallocate, FallocateMode};
17
18/// A trait for deallocating space in a file.
19pub trait PunchHole {
20 /// Replace a range of bytes with a hole.
21 ///
22 /// # Arguments
23 ///
24 /// * `offset`: offset of the file where to replace with a hole.
25 /// * `length`: the number of bytes of the hole to replace with.
26 fn punch_hole(&mut self, offset: u64, length: u64) -> Result<()>;
27}
28
29impl PunchHole for File {
30 fn punch_hole(&mut self, offset: u64, length: u64) -> Result<()> {
31 fallocate(self, FallocateMode::PunchHole, true, offset, length)
32 .map_err(|e| Error::from_raw_os_error(e.errno()))
33 }
34}
35
36/// A trait for writing zeroes to a stream.
37pub trait WriteZeroes {
38 /// Write up to `length` bytes of zeroes to the stream, returning how many bytes were written.
39 ///
40 /// # Arguments
41 ///
42 /// * `length`: the number of bytes of zeroes to write to the stream.
43 fn write_zeroes(&mut self, length: usize) -> Result<usize>;
44
45 /// Write zeroes to the stream until `length` bytes have been written.
46 ///
47 /// This method will continuously write zeroes until the requested `length` is satisfied or an
48 /// unrecoverable error is encountered.
49 ///
50 /// # Arguments
51 ///
52 /// * `length`: the exact number of bytes of zeroes to write to the stream.
53 fn write_all_zeroes(&mut self, mut length: usize) -> Result<()> {
54 while length > 0 {
55 match self.write_zeroes(length) {
56 Ok(0) => return Err(Error::from(ErrorKind::WriteZero)),
57 Ok(bytes_written) => {
58 length = length
59 .checked_sub(bytes_written)
60 .ok_or_else(|| Error::from(ErrorKind::Other))?
61 }
62 // If the operation was interrupted, we should retry it.
63 Err(e) => {
64 if e.kind() != ErrorKind::Interrupted {
65 return Err(e);
66 }
67 }
68 }
69 }
70 Ok(())
71 }
72}
73
74/// A trait for writing zeroes to an arbitrary position in a file.
75pub trait WriteZeroesAt {
76 /// Write up to `length` bytes of zeroes starting at `offset`, returning how many bytes were
77 /// written.
78 ///
79 /// # Arguments
80 ///
81 /// * `offset`: offset of the file where to write zeroes.
82 /// * `length`: the number of bytes of zeroes to write to the stream.
83 fn write_zeroes_at(&mut self, offset: u64, length: usize) -> Result<usize>;
84
85 /// Write zeroes starting at `offset` until `length` bytes have been written.
86 ///
87 /// This method will continuously write zeroes until the requested `length` is satisfied or an
88 /// unrecoverable error is encountered.
89 ///
90 /// # Arguments
91 ///
92 /// * `offset`: offset of the file where to write zeroes.
93 /// * `length`: the exact number of bytes of zeroes to write to the stream.
94 fn write_all_zeroes_at(&mut self, mut offset: u64, mut length: usize) -> Result<()> {
95 while length > 0 {
96 match self.write_zeroes_at(offset, length) {
97 Ok(0) => return Err(Error::from(ErrorKind::WriteZero)),
98 Ok(bytes_written) => {
99 length = length
100 .checked_sub(bytes_written)
101 .ok_or_else(|| Error::from(ErrorKind::Other))?;
102 offset = offset
103 .checked_add(bytes_written as u64)
104 .ok_or_else(|| Error::from(ErrorKind::Other))?;
105 }
106 Err(e) => {
107 // If the operation was interrupted, we should retry it.
108 if e.kind() != ErrorKind::Interrupted {
109 return Err(e);
110 }
111 }
112 }
113 }
114 Ok(())
115 }
116}
117
118impl WriteZeroesAt for File {
119 fn write_zeroes_at(&mut self, offset: u64, length: usize) -> Result<usize> {
120 // Try to use fallocate() first, since it is more efficient than writing zeroes with
121 // write().
122 if fallocate(self, FallocateMode::ZeroRange, true, offset, length as u64).is_ok() {
123 return Ok(length);
124 }
125
126 // Fall back to write().
127 // fallocate() failed; fall back to writing a buffer of zeroes until we have written up
128 // to `length`.
129 let buf_size = min(length, 0x10000);
130 let buf = vec![0u8; buf_size];
131 let mut num_written: usize = 0;
132 while num_written < length {
133 let remaining = length - num_written;
134 let write_size = min(remaining, buf_size);
135 num_written += self.write_at(&buf[0..write_size], offset + num_written as u64)?;
136 }
137 Ok(length)
138 }
139}
140
141impl<T: WriteZeroesAt + Seek> WriteZeroes for T {
142 fn write_zeroes(&mut self, length: usize) -> Result<usize> {
143 let offset = self.stream_position()?;
144 let num_written = self.write_zeroes_at(offset, length)?;
145 // Advance the seek cursor as if we had done a real write().
146 self.seek(SeekFrom::Current(num_written as i64))?;
147 Ok(length)
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 use std::io::{Read, Seek, SeekFrom, Write};
156
157 use crate::tempfile::TempFile;
158
159 #[test]
160 fn test_small_write_zeroes() {
161 const NON_ZERO_VALUE: u8 = 0x55;
162 const BUF_SIZE: usize = 5678;
163
164 let mut f = TempFile::new().unwrap().into_file();
165 f.set_len(16384).unwrap();
166
167 // Write buffer of non-zero bytes to offset 1234.
168 let orig_data = [NON_ZERO_VALUE; BUF_SIZE];
169 f.seek(SeekFrom::Start(1234)).unwrap();
170 f.write_all(&orig_data).unwrap();
171
172 // Read back the data plus some overlap on each side.
173 let mut readback = [0u8; 16384];
174 f.rewind().unwrap();
175 f.read_exact(&mut readback).unwrap();
176 // Bytes before the write should still be 0.
177 for read in &readback[0..1234] {
178 assert_eq!(*read, 0);
179 }
180 // Bytes that were just written should have `NON_ZERO_VALUE` value.
181 for read in &readback[1234..(1234 + BUF_SIZE)] {
182 assert_eq!(*read, NON_ZERO_VALUE);
183 }
184 // Bytes after the written area should still be 0.
185 for read in &readback[(1234 + BUF_SIZE)..] {
186 assert_eq!(*read, 0);
187 }
188
189 // Overwrite some of the data with zeroes.
190 f.seek(SeekFrom::Start(2345)).unwrap();
191 f.write_all_zeroes(4321).unwrap();
192 // Verify seek position after `write_all_zeroes()`.
193 assert_eq!(f.stream_position().unwrap(), 2345 + 4321);
194
195 // Read back the data and verify that it is now zero.
196 f.rewind().unwrap();
197 f.read_exact(&mut readback).unwrap();
198 // Bytes before the write should still be 0.
199 for read in &readback[0..1234] {
200 assert_eq!(*read, 0);
201 }
202 // Original data should still exist before the zeroed region.
203 for read in &readback[1234..2345] {
204 assert_eq!(*read, NON_ZERO_VALUE);
205 }
206 // Verify that `write_all_zeroes()` zeroed the intended region.
207 for read in &readback[2345..(2345 + 4321)] {
208 assert_eq!(*read, 0);
209 }
210 // Original data should still exist after the zeroed region.
211 for read in &readback[(2345 + 4321)..(1234 + BUF_SIZE)] {
212 assert_eq!(*read, NON_ZERO_VALUE);
213 }
214 // The rest of the file should still be 0.
215 for read in &readback[(1234 + BUF_SIZE)..] {
216 assert_eq!(*read, 0);
217 }
218 }
219
220 #[test]
221 fn test_large_write_zeroes() {
222 const NON_ZERO_VALUE: u8 = 0x55;
223 const SIZE: usize = 0x2_0000;
224
225 let mut f = TempFile::new().unwrap().into_file();
226 f.set_len(16384).unwrap();
227
228 // Write buffer of non-zero bytes. The size of the buffer will be the new
229 // size of the file.
230 let orig_data = [NON_ZERO_VALUE; SIZE];
231 f.rewind().unwrap();
232 f.write_all(&orig_data).unwrap();
233 assert_eq!(f.metadata().unwrap().len(), SIZE as u64);
234
235 // Overwrite some of the data with zeroes.
236 f.rewind().unwrap();
237 f.write_all_zeroes(0x1_0001).unwrap();
238 // Verify seek position after `write_all_zeroes()`.
239 assert_eq!(f.stream_position().unwrap(), 0x1_0001);
240
241 // Read back the data and verify that it is now zero.
242 let mut readback = [0u8; SIZE];
243 f.rewind().unwrap();
244 f.read_exact(&mut readback).unwrap();
245 // Verify that `write_all_zeroes()` zeroed the intended region.
246 for read in &readback[0..0x1_0001] {
247 assert_eq!(*read, 0);
248 }
249 // Original data should still exist after the zeroed region.
250 for read in &readback[0x1_0001..SIZE] {
251 assert_eq!(*read, NON_ZERO_VALUE);
252 }
253
254 // Now let's zero a certain region by using `write_all_zeroes_at()`.
255 f.write_all_zeroes_at(0x1_8001, 0x200).unwrap();
256 f.rewind().unwrap();
257 f.read_exact(&mut readback).unwrap();
258
259 // Original data should still exist before the zeroed region.
260 for read in &readback[0x1_0001..0x1_8001] {
261 assert_eq!(*read, NON_ZERO_VALUE);
262 }
263 // Verify that `write_all_zeroes_at()` zeroed the intended region.
264 for read in &readback[0x1_8001..(0x1_8001 + 0x200)] {
265 assert_eq!(*read, 0);
266 }
267 // Original data should still exist after the zeroed region.
268 for read in &readback[(0x1_8001 + 0x200)..SIZE] {
269 assert_eq!(*read, NON_ZERO_VALUE);
270 }
271 }
272
273 #[test]
274 fn test_punch_hole() {
275 const NON_ZERO_VALUE: u8 = 0x55;
276 const SIZE: usize = 0x2_0000;
277
278 let mut f = TempFile::new().unwrap().into_file();
279 f.set_len(16384).unwrap();
280
281 // Write buffer of non-zero bytes. The size of the buffer will be the new
282 // size of the file.
283 let orig_data = [NON_ZERO_VALUE; SIZE];
284 f.rewind().unwrap();
285 f.write_all(&orig_data).unwrap();
286 assert_eq!(f.metadata().unwrap().len(), SIZE as u64);
287
288 // Punch a hole at offset 0x10001.
289 // Subsequent reads from this range will return zeros.
290 f.punch_hole(0x1_0001, 0x200).unwrap();
291
292 // Read back the data.
293 let mut readback = [0u8; SIZE];
294 f.rewind().unwrap();
295 f.read_exact(&mut readback).unwrap();
296 // Original data should still exist before the hole.
297 for read in &readback[0..0x1_0001] {
298 assert_eq!(*read, NON_ZERO_VALUE);
299 }
300 // Verify that `punch_hole()` zeroed the intended region.
301 for read in &readback[0x1_0001..(0x1_0001 + 0x200)] {
302 assert_eq!(*read, 0);
303 }
304 // Original data should still exist after the hole.
305 for read in &readback[(0x1_0001 + 0x200)..] {
306 assert_eq!(*read, NON_ZERO_VALUE);
307 }
308
309 // Punch a hole at the end of the file.
310 // Subsequent reads from this range should return zeros.
311 f.punch_hole(SIZE as u64 - 0x400, 0x400).unwrap();
312 // Even though we punched a hole at the end of the file, the file size should remain the
313 // same since FALLOC_FL_PUNCH_HOLE must be used with FALLOC_FL_KEEP_SIZE.
314 assert_eq!(f.metadata().unwrap().len(), SIZE as u64);
315
316 let mut readback = [0u8; 0x400];
317 f.seek(SeekFrom::Start(SIZE as u64 - 0x400)).unwrap();
318 f.read_exact(&mut readback).unwrap();
319 // Verify that `punch_hole()` zeroed the intended region.
320 for read in &readback[0..0x400] {
321 assert_eq!(*read, 0);
322 }
323
324 // Punching a hole of len 0 should return an error.
325 assert!(f.punch_hole(0x200, 0x0).is_err());
326 // Zeroing a region of len 0 should not return an error since we have a fallback path
327 // in `write_zeroes_at()` for `fallocate()` failure.
328 assert!(f.write_zeroes_at(0x200, 0x0).is_ok());
329 }
330}