@@ -22,6 +22,18 @@ impl<T: FileExt> WriteAt for T {
2222 }
2323}
2424
25+ /// Trait for querying file length.
26+ /// Needed to detect writes past EOF that would create sparse holes.
27+ pub trait FileLen {
28+ fn file_len ( & self ) -> io:: Result < u64 > ;
29+ }
30+
31+ impl FileLen for std:: fs:: File {
32+ fn file_len ( & self ) -> io:: Result < u64 > {
33+ self . metadata ( ) . map ( |m| m. len ( ) )
34+ }
35+ }
36+
2537/// Decodes encrypted files.
2638///
2739/// Handles block-by-block decryption and MAC verification.
@@ -217,7 +229,7 @@ impl<'a, F: ReadAt> FileDecoder<'a, F> {
217229}
218230
219231/// Encodes encrypted files (writes).
220- pub struct FileEncoder < ' a , F : ReadAt + WriteAt > {
232+ pub struct FileEncoder < ' a , F : ReadAt + WriteAt + FileLen > {
221233 cipher : & ' a SslCipher ,
222234 file : & ' a F ,
223235 file_iv : u64 ,
@@ -226,7 +238,7 @@ pub struct FileEncoder<'a, F: ReadAt + WriteAt> {
226238 block_mac_bytes : u64 ,
227239}
228240
229- impl < ' a , F : ReadAt + WriteAt > FileEncoder < ' a , F > {
241+ impl < ' a , F : ReadAt + WriteAt + FileLen > FileEncoder < ' a , F > {
230242 pub fn new (
231243 cipher : & ' a SslCipher ,
232244 file : & ' a F ,
@@ -273,14 +285,6 @@ impl<'a, F: ReadAt + WriteAt> FileEncoder<'a, F> {
273285 }
274286
275287 pub fn write_at ( & self , buf : & [ u8 ] , offset : u64 ) -> io:: Result < usize > {
276- let size = buf. len ( ) as u64 ;
277- let mut bytes_remaining = size;
278- let mut total_written = 0 ;
279- let mut current_offset = offset;
280-
281- // Usage in `FileDecoder::read_at`: `self.block_size - self.block_mac_bytes`.
282- // I will follow `FileDecoder` convention here to avoid breaking things, assuming `self.block_size` = ON DISK SIZE.
283-
284288 if self . block_size <= self . block_mac_bytes {
285289 return Err ( io:: Error :: other (
286290 "Invalid config: block_size must be > block_mac_bytes" ,
@@ -299,6 +303,57 @@ impl<'a, F: ReadAt + WriteAt> FileEncoder<'a, F> {
299303 ) ) ;
300304 }
301305
306+ // Detect and fill gaps to prevent sparse files.
307+ // Sparse files cause MAC verification failures because reading a block
308+ // that partially spans a hole returns zeros that weren't properly encrypted.
309+ let physical_size = self . file . file_len ( ) ?;
310+ let current_logical_size = FileDecoder :: < F > :: calculate_logical_size (
311+ physical_size,
312+ self . header_size ,
313+ self . block_size ,
314+ self . block_mac_bytes ,
315+ ) ;
316+
317+ if offset > current_logical_size {
318+ // Fill the gap with encrypted zeros
319+ let gap_size = offset - current_logical_size;
320+ let zeros = vec ! [ 0u8 ; gap_size as usize ] ;
321+ // Recursively call write_at to fill the gap with properly encrypted zeros
322+ self . write_at_internal (
323+ & zeros,
324+ current_logical_size,
325+ data_block_size,
326+ physical_block_size,
327+ physical_block_size_usize,
328+ mac_len_usize,
329+ ) ?;
330+ }
331+
332+ // Now write the actual data
333+ self . write_at_internal (
334+ buf,
335+ offset,
336+ data_block_size,
337+ physical_block_size,
338+ physical_block_size_usize,
339+ mac_len_usize,
340+ )
341+ }
342+
343+ fn write_at_internal (
344+ & self ,
345+ buf : & [ u8 ] ,
346+ offset : u64 ,
347+ data_block_size : u64 ,
348+ physical_block_size : u64 ,
349+ physical_block_size_usize : usize ,
350+ mac_len_usize : usize ,
351+ ) -> io:: Result < usize > {
352+ let size = buf. len ( ) as u64 ;
353+ let mut bytes_remaining = size;
354+ let mut total_written = 0 ;
355+ let mut current_offset = offset;
356+
302357 while bytes_remaining > 0 {
303358 let block_num = current_offset / data_block_size;
304359 let block_offset = current_offset % data_block_size;
0 commit comments