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 
11 use std::cmp::min;
12 use std::fs::File;
13 use std::io::{Error, ErrorKind, Result, Seek, SeekFrom};
14 use std::os::unix::fs::FileExt;
15 
16 use crate::fallocate::{fallocate, FallocateMode};
17 
18 /// A trait for deallocating space in a file.
19 pub 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.
punch_hole(&mut self, offset: u64, length: u64) -> Result<()>26     fn punch_hole(&mut self, offset: u64, length: u64) -> Result<()>;
27 }
28 
29 impl PunchHole for File {
punch_hole(&mut self, offset: u64, length: u64) -> Result<()>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.
37 pub 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.
write_zeroes(&mut self, length: usize) -> Result<usize>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.
write_all_zeroes(&mut self, mut length: usize) -> Result<()>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.
75 pub 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.
write_zeroes_at(&mut self, offset: u64, length: usize) -> Result<usize>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.
write_all_zeroes_at(&mut self, mut offset: u64, mut length: usize) -> Result<()>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 
118 impl WriteZeroesAt for File {
write_zeroes_at(&mut self, offset: u64, length: usize) -> Result<usize>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 
141 impl<T: WriteZeroesAt + Seek> WriteZeroes for T {
write_zeroes(&mut self, length: usize) -> Result<usize>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)]
152 mod tests {
153     use super::*;
154 
155     use std::io::{Read, Seek, SeekFrom, Write};
156 
157     use crate::tempfile::TempFile;
158 
159     #[test]
test_small_write_zeroes()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]
test_large_write_zeroes()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]
test_punch_hole()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 }
331