base/
write_zeroes.rs

1// Copyright 2018 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
5use std::fs::File;
6use std::io;
7use std::io::Error;
8use std::io::ErrorKind;
9
10/// A trait for deallocating space in a file.
11pub trait PunchHole {
12    /// Replace a range of bytes with a hole.
13    fn punch_hole(&self, offset: u64, length: u64) -> io::Result<()>;
14}
15
16impl PunchHole for File {
17    fn punch_hole(&self, offset: u64, length: u64) -> io::Result<()> {
18        crate::platform::file_punch_hole(self, offset, length)
19    }
20}
21
22/// A trait for writing zeroes to an arbitrary position in a file.
23pub trait WriteZeroesAt {
24    /// Write up to `length` bytes of zeroes starting at `offset`, returning how many bytes were
25    /// written.
26    fn write_zeroes_at(&self, offset: u64, length: usize) -> io::Result<usize>;
27
28    /// Write zeroes starting at `offset` until `length` bytes have been written.
29    ///
30    /// This method will continuously call `write_zeroes_at` until the requested
31    /// `length` is satisfied or an error is encountered.
32    fn write_zeroes_all_at(&self, mut offset: u64, mut length: usize) -> io::Result<()> {
33        while length > 0 {
34            match self.write_zeroes_at(offset, length) {
35                Ok(0) => return Err(Error::from(ErrorKind::WriteZero)),
36                Ok(bytes_written) => {
37                    length = length
38                        .checked_sub(bytes_written)
39                        .ok_or_else(|| Error::from(ErrorKind::Other))?;
40                    offset = offset
41                        .checked_add(bytes_written as u64)
42                        .ok_or_else(|| Error::from(ErrorKind::Other))?;
43                }
44                Err(e) => {
45                    if e.kind() != ErrorKind::Interrupted {
46                        return Err(e);
47                    }
48                }
49            }
50        }
51        Ok(())
52    }
53}
54
55impl WriteZeroesAt for File {
56    fn write_zeroes_at(&self, offset: u64, length: usize) -> io::Result<usize> {
57        crate::platform::file_write_zeroes_at(self, offset, length)
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use std::io::Read;
64    use std::io::Seek;
65    use std::io::SeekFrom;
66    use std::io::Write;
67
68    use tempfile::tempfile;
69
70    use super::*;
71
72    #[test]
73    fn simple_test() {
74        let mut f = tempfile().unwrap();
75
76        // Extend and fill the file with zero bytes.
77        // This is not using set_len() because that fails on wine.
78        let init_data = [0x00u8; 16384];
79        f.write_all(&init_data).unwrap();
80
81        // Write buffer of non-zero bytes to offset 1234
82        let orig_data = [0x55u8; 5678];
83        f.seek(SeekFrom::Start(1234)).unwrap();
84        f.write_all(&orig_data).unwrap();
85
86        // Read back the data plus some overlap on each side
87        let mut readback = [0u8; 16384];
88        f.seek(SeekFrom::Start(0)).unwrap();
89        f.read_exact(&mut readback).unwrap();
90        // Bytes before the write should still be 0
91        for read in &readback[0..1234] {
92            assert_eq!(*read, 0);
93        }
94        // Bytes that were just written should be 0x55
95        for read in &readback[1234..(1234 + 5678)] {
96            assert_eq!(*read, 0x55);
97        }
98        // Bytes after the written area should still be 0
99        for read in &readback[(1234 + 5678)..] {
100            assert_eq!(*read, 0);
101        }
102
103        // Overwrite some of the data with zeroes
104        f.write_zeroes_all_at(2345, 4321)
105            .expect("write_zeroes failed");
106
107        // Read back the data and verify that it is now zero
108        f.seek(SeekFrom::Start(0)).unwrap();
109        f.read_exact(&mut readback).unwrap();
110        // Bytes before the write should still be 0
111        for read in &readback[0..1234] {
112            assert_eq!(*read, 0);
113        }
114        // Original data should still exist before the write_zeroes region
115        for read in &readback[1234..2345] {
116            assert_eq!(*read, 0x55);
117        }
118        // The write_zeroes region should now be zero
119        for read in &readback[2345..(2345 + 4321)] {
120            assert_eq!(*read, 0);
121        }
122        // Original data should still exist after the write_zeroes region
123        for read in &readback[(2345 + 4321)..(1234 + 5678)] {
124            assert_eq!(*read, 0x55);
125        }
126        // The rest of the file should still be 0
127        for read in &readback[(1234 + 5678)..] {
128            assert_eq!(*read, 0);
129        }
130    }
131
132    #[test]
133    fn large_write_zeroes() {
134        let mut f = tempfile().unwrap();
135
136        // Extend and fill the file with zero bytes.
137        // This is not using set_len() because that fails on wine.
138        let init_data = [0x00u8; 16384];
139        f.write_all(&init_data).unwrap();
140
141        // Write buffer of non-zero bytes
142        let orig_data = [0x55u8; 0x20000];
143        f.seek(SeekFrom::Start(0)).unwrap();
144        f.write_all(&orig_data).unwrap();
145
146        // Overwrite some of the data with zeroes
147        f.write_zeroes_all_at(0, 0x10001)
148            .expect("write_zeroes failed");
149
150        // Read back the data and verify that it is now zero
151        let mut readback = [0u8; 0x20000];
152        f.seek(SeekFrom::Start(0)).unwrap();
153        f.read_exact(&mut readback).unwrap();
154        // The write_zeroes region should now be zero
155        for read in &readback[0..0x10001] {
156            assert_eq!(*read, 0);
157        }
158        // Original data should still exist after the write_zeroes region
159        for read in &readback[0x10001..0x20000] {
160            assert_eq!(*read, 0x55);
161        }
162    }
163}