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