summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS4
-rw-r--r--src/extent-scan.c34
2 files changed, 37 insertions, 1 deletions
diff --git a/NEWS b/NEWS
index 9af1cd9c1..0c1aa6b59 100644
--- a/NEWS
+++ b/NEWS
@@ -8,6 +8,10 @@ GNU coreutils NEWS -*- outline -*-
copying the symlink and then not preserving its timestamp.
[bug introduced in coreutils-8.0]
+ cp now avoids FIEMAP issues with BTRFS before Linux 2.6.38,
+ which could result in corrupt copies of sparse files.
+ [bug introduced in coreutils-8.10]
+
cut could segfault when invoked with a user-specified output
delimiter and an unbounded range like "-f1234567890-".
[bug introduced in coreutils-5.3.0]
diff --git a/src/extent-scan.c b/src/extent-scan.c
index f10d8e0a0..c0a5de6c2 100644
--- a/src/extent-scan.c
+++ b/src/extent-scan.c
@@ -122,7 +122,7 @@ extent_scan_read (struct extent_scan *scan)
for (i = 0; i < scan->ei_count; i++)
{
- assert (fm_extents[i].fe_logical <= OFF_T_MAX);
+ assert (fm_extents[i].fe_logical <= OFF_T_MAX - fm_extents[i].fe_length);
if (si && last_ei->ext_flags ==
(fm_extents[i].fe_flags & ~FIEMAP_EXTENT_LAST)
@@ -134,6 +134,38 @@ extent_scan_read (struct extent_scan *scan)
/* Copy flags in case different. */
last_ei->ext_flags = fm_extents[i].fe_flags;
}
+ else if ((si == 0 && scan->scan_start > fm_extents[i].fe_logical)
+ || (si && last_ei->ext_logical + last_ei->ext_length >
+ fm_extents[i].fe_logical))
+ {
+ /* BTRFS before 2.6.38 could return overlapping extents
+ for sparse files. We adjust the returned extents
+ rather than failing, as otherwise it would be inefficient
+ to detect this on the initial scan. */
+ uint64_t new_logical;
+ uint64_t length_adjust;
+ if (si == 0)
+ new_logical = scan->scan_start;
+ else
+ {
+ /* We could return here if scan->scan_start == 0
+ but don't so as to minimize special cases. */
+ new_logical = last_ei->ext_logical + last_ei->ext_length;
+ }
+ length_adjust = new_logical - fm_extents[i].fe_logical;
+ /* If an extent is contained within the previous one, just fail. */
+ if (length_adjust < fm_extents[i].fe_length)
+ {
+ if (scan->scan_start == 0)
+ scan->initial_scan_failed = true;
+ return false;
+ }
+ fm_extents[i].fe_logical = new_logical;
+ fm_extents[i].fe_length -= length_adjust;
+ /* Process the adjusted extent again. */
+ i--;
+ continue;
+ }
else
{
last_ei = scan->ext_info + si;