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}