From 86dde5df4347595b28d0e3b07ac0e4a84f0d2bda Mon Sep 17 00:00:00 2001 From: Martin Leitner-Ankerl Date: Thu, 23 Jul 2026 15:30:32 +0200 Subject: [PATCH] file_scan: handle sparse files with a trailing hole (#374) A file whose data ends before EOF (data followed by a hole) has a last mapped extent that ends before filesize; FIEMAP never reports the hole. process_extents() then broke in two ways once file_off entered the hole: - dummy = ext_end_off - filesize underflowed (size_t) because the last extent ends *before* filesize, so `file_off + dummy == ext_end_off` never held and the last real extent was never stored; - the next get_extent() returned NULL, which printed "unable to get extent" and returned 1, so the caller declared the file "changed" and abandoned it - the file was never hashed or deduped. Only subtract the past-EOF overshoot when the extent actually runs past filesize, and treat a NULL from get_extent() as "past the last extent = trailing hole, stop cleanly" rather than an error. Fixes: https://github.com/markfasheh/duperemove/issues/374 Co-Authored-By: Claude Opus 4.8 --- file_scan.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/file_scan.c b/file_scan.c index 0760b9cd9ee8..c0a402f2799a 100644 --- a/file_scan.c +++ b/file_scan.c @@ -887,15 +887,18 @@ static int process_extents(struct scan_ctxt *ctxt, struct buffer *buffer, while (file_off < ctxt->off + bytes) { extent = get_extent(ctxt->fiemap, file_off, NULL); if (!extent) { - eprintf("process_extents: unable to get extent\n"); - - /* Cleanup the partial checksum and skip - * the rest of the buffer + /* + * No extent covers this offset: we've walked past the + * last mapped extent into a trailing hole (data followed + * by a hole up to EOF, which FIEMAP never reports). That + * is not an error - finish the pending checksum and stop + * cleanly. Treating it as a failure made the caller + * declare the file "changed" and never hash or dedupe it. */ if (ctxt->extent_csum) finish_running_checksum(ctxt->extent_csum, NULL); ctxt->extent_csum = NULL; - return 1; + return 0; } ext_end_off = extent->fe_logical + extent->fe_length; @@ -929,7 +932,8 @@ static int process_extents(struct scan_ctxt *ctxt, struct buffer *buffer, * the part that will never exist. */ size_t dummy = 0; - if (extent->fe_flags & FIEMAP_EXTENT_LAST) + if ((extent->fe_flags & FIEMAP_EXTENT_LAST) && + ext_end_off > ctxt->filesize) dummy = ext_end_off - ctxt->filesize; if (file_off + dummy == ext_end_off) { ret = store_extent(ctxt, hashes, extent);