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