sys_util/
write_zeroes.rs

1// Copyright 2018 The Chromium OS Authors. All rights reserved.
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::cmp::min;
6use std::fs::File;
7use std::io::{self, Seek, SeekFrom, Write};
8
9use fallocate;
10use FallocateMode;
11
12/// A trait for deallocating space in a file.
13pub trait PunchHole {
14    /// Replace a range of bytes with a hole.
15    fn punch_hole(&mut self, offset: u64, length: u64) -> io::Result<()>;
16}
17
18impl PunchHole for File {
19    fn punch_hole(&mut self, offset: u64, length: u64) -> io::Result<()> {
20        fallocate(self, FallocateMode::PunchHole, true, offset, length as u64)
21            .map_err(|e| io::Error::from_raw_os_error(e.errno()))
22    }
23}
24
25/// A trait for writing zeroes to a stream.
26pub trait WriteZeroes {
27    /// Write `length` bytes of zeroes to the stream, returning how many bytes were written.
28    fn write_zeroes(&mut self, length: usize) -> io::Result<usize>;
29}
30
31impl<T: PunchHole + Seek + Write> WriteZeroes for T {
32    fn write_zeroes(&mut self, length: usize) -> io::Result<usize> {
33        // Try to punch a hole first.
34        let offset = self.seek(SeekFrom::Current(0))?;
35        match self.punch_hole(offset, length as u64) {
36            Ok(()) => {
37                // Advance the seek cursor as if we had done a real write().
38                self.seek(SeekFrom::Current(length as i64))?;
39                return Ok(length);
40            }
41            Err(_) => {} // fall back to write()
42        }
43
44        // punch_hole() failed; fall back to writing a buffer of zeroes
45        // until we have written up to length.
46        let buf_size = min(length, 0x10000);
47        let buf = vec![0u8; buf_size];
48        let mut nwritten: usize = 0;
49        while nwritten < length {
50            let remaining = length - nwritten;
51            let write_size = min(remaining, buf_size);
52            nwritten += self.write(&buf[0..write_size])?;
53        }
54        Ok(length)
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61    use std::fs::OpenOptions;
62    use std::io::{Read, Seek, SeekFrom};
63    use std::path::PathBuf;
64
65    use TempDir;
66
67    #[test]
68    fn simple_test() {
69        let tempdir = TempDir::new("/tmp/write_zeroes_test").unwrap();
70        let mut path = PathBuf::from(tempdir.as_path().unwrap());
71        path.push("file");
72        let mut f = OpenOptions::new()
73            .read(true)
74            .write(true)
75            .create(true)
76            .open(&path)
77            .unwrap();
78        f.set_len(16384).unwrap();
79
80        // Write buffer of non-zero bytes to offset 1234
81        let orig_data = [0x55u8; 5678];
82        f.seek(SeekFrom::Start(1234)).unwrap();
83        f.write(&orig_data).unwrap();
84
85        // Read back the data plus some overlap on each side
86        let mut readback = [0u8; 16384];
87        f.seek(SeekFrom::Start(0)).unwrap();
88        f.read(&mut readback).unwrap();
89        // Bytes before the write should still be 0
90        for read in readback[0..1234].iter() {
91            assert_eq!(*read, 0);
92        }
93        // Bytes that were just written should be 0x55
94        for read in readback[1234..(1234 + 5678)].iter() {
95            assert_eq!(*read, 0x55);
96        }
97        // Bytes after the written area should still be 0
98        for read in readback[(1234 + 5678)..].iter() {
99            assert_eq!(*read, 0);
100        }
101
102        // Overwrite some of the data with zeroes
103        f.seek(SeekFrom::Start(2345)).unwrap();
104        f.write_zeroes(4321).expect("write_zeroes failed");
105        // Verify seek position after write_zeroes()
106        assert_eq!(f.seek(SeekFrom::Current(0)).unwrap(), 2345 + 4321);
107
108        // Read back the data and verify that it is now zero
109        f.seek(SeekFrom::Start(0)).unwrap();
110        f.read(&mut readback).unwrap();
111        // Bytes before the write should still be 0
112        for read in readback[0..1234].iter() {
113            assert_eq!(*read, 0);
114        }
115        // Original data should still exist before the write_zeroes region
116        for read in readback[1234..2345].iter() {
117            assert_eq!(*read, 0x55);
118        }
119        // The write_zeroes region should now be zero
120        for read in readback[2345..(2345 + 4321)].iter() {
121            assert_eq!(*read, 0);
122        }
123        // Original data should still exist after the write_zeroes region
124        for read in readback[(2345 + 4321)..(1234 + 5678)].iter() {
125            assert_eq!(*read, 0x55);
126        }
127        // The rest of the file should still be 0
128        for read in readback[(1234 + 5678)..].iter() {
129            assert_eq!(*read, 0);
130        }
131    }
132
133    #[test]
134    fn large_write_zeroes() {
135        let tempdir = TempDir::new("/tmp/write_zeroes_test").unwrap();
136        let mut path = PathBuf::from(tempdir.as_path().unwrap());
137        path.push("file");
138        let mut f = OpenOptions::new()
139            .read(true)
140            .write(true)
141            .create(true)
142            .open(&path)
143            .unwrap();
144        f.set_len(16384).unwrap();
145
146        // Write buffer of non-zero bytes
147        let orig_data = [0x55u8; 0x20000];
148        f.seek(SeekFrom::Start(0)).unwrap();
149        f.write(&orig_data).unwrap();
150
151        // Overwrite some of the data with zeroes
152        f.seek(SeekFrom::Start(0)).unwrap();
153        f.write_zeroes(0x10001).expect("write_zeroes failed");
154        // Verify seek position after write_zeroes()
155        assert_eq!(f.seek(SeekFrom::Current(0)).unwrap(), 0x10001);
156
157        // Read back the data and verify that it is now zero
158        let mut readback = [0u8; 0x20000];
159        f.seek(SeekFrom::Start(0)).unwrap();
160        f.read(&mut readback).unwrap();
161        // The write_zeroes region should now be zero
162        for read in readback[0..0x10001].iter() {
163            assert_eq!(*read, 0);
164        }
165        // Original data should still exist after the write_zeroes region
166        for read in readback[0x10001..0x20000].iter() {
167            assert_eq!(*read, 0x55);
168        }
169    }
170}