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 writing zeroes to an arbitrary position in a file. 23 pub trait WriteZeroesAt { 24 /// Write up to `length` bytes of zeroes starting at `offset`, returning how many bytes were 25 /// written. write_zeroes_at(&mut self, offset: u64, length: usize) -> io::Result<usize>26 fn write_zeroes_at(&mut 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. write_zeroes_all_at(&mut self, mut offset: u64, mut length: usize) -> io::Result<()>32 fn write_zeroes_all_at(&mut 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 55 impl WriteZeroesAt for File { write_zeroes_at(&mut self, offset: u64, length: usize) -> io::Result<usize>56 fn write_zeroes_at(&mut self, offset: u64, length: usize) -> io::Result<usize> { 57 crate::platform::file_write_zeroes_at(self, offset, length) 58 } 59 } 60 61 #[cfg(test)] 62 mod 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] simple_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] large_write_zeroes()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 } 164