Write a single guest RAM region to the snapshot file at `dst_offset`, streaming populated extents via `SEEK_DATA` / `SEEK_HOLE` on the backing fd. `set_len(total)` must have been called on `dst` by the caller. Returns `Ok(true)` if the region was written sparsely, or `Ok(false)` if `SEEK_HOLE` is unsupported on the source fd (caller should fall back to dense write).
(
src: &File,
src_offset: u64,
dst: &File,
dst_offset: u64,
len: u64,
)
| 3080 | /// `Ok(false)` if `SEEK_HOLE` is unsupported on the source fd (caller |
| 3081 | /// should fall back to dense write). |
| 3082 | fn write_region_sparse( |
| 3083 | src: &File, |
| 3084 | src_offset: u64, |
| 3085 | dst: &File, |
| 3086 | dst_offset: u64, |
| 3087 | len: u64, |
| 3088 | ) -> io::Result<bool> { |
| 3089 | let src_fd = src.as_fd(); |
| 3090 | let end = src_offset |
| 3091 | .checked_add(len) |
| 3092 | .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "range overflow"))?; |
| 3093 | |
| 3094 | // First call to next_data_extent doubles as a SEEK_HOLE-support probe: |
| 3095 | // a non-ENXIO error means the filesystem doesn't support sparse-seek; |
| 3096 | // tell the caller to use the dense path instead. |
| 3097 | let mut next = match next_data_extent(src_fd, src_offset, end) { |
| 3098 | Ok(opt) => opt, |
| 3099 | Err(_) => return Ok(false), |
| 3100 | }; |
| 3101 | |
| 3102 | const CHUNK: usize = 1 << 20; |
| 3103 | let mut buf = vec![0u8; CHUNK]; |
| 3104 | |
| 3105 | while let Some((data_off, ext_len)) = next { |
| 3106 | debug_assert!(data_off >= src_offset); |
| 3107 | let in_region = data_off |
| 3108 | .checked_sub(src_offset) |
| 3109 | .expect("extent precedes src_offset"); |
| 3110 | let mut written = 0u64; |
| 3111 | while written < ext_len { |
| 3112 | let this = ((ext_len - written) as usize).min(CHUNK); |
| 3113 | let slice = &mut buf[..this]; |
| 3114 | let read = src.read_at(slice, data_off + written)?; |
| 3115 | if read == 0 { |
| 3116 | return Err(io::Error::new( |
| 3117 | io::ErrorKind::UnexpectedEof, |
| 3118 | "read_at returned 0 inside data extent", |
| 3119 | )); |
| 3120 | } |
| 3121 | let mut wrote_total = 0; |
| 3122 | while wrote_total < read { |
| 3123 | let n = dst.write_at( |
| 3124 | &slice[wrote_total..read], |
| 3125 | dst_offset + in_region + written + wrote_total as u64, |
| 3126 | )?; |
| 3127 | if n == 0 { |
| 3128 | return Err(io::Error::new( |
| 3129 | io::ErrorKind::WriteZero, |
| 3130 | "write_at returned 0", |
| 3131 | )); |
| 3132 | } |
| 3133 | wrote_total += n; |
| 3134 | } |
| 3135 | written += read as u64; |
| 3136 | } |
| 3137 | // Subsequent next_data_extent failures are real I/O errors, not |
| 3138 | // unsupported-FS, since the first probe succeeded. |
| 3139 | next = next_data_extent(src_fd, data_off + ext_len, end)?; |