Blame SOURCES/coreutils-8.22-cp-sparsecorrupt.patch

1514b7
From eea6b49210edf69682b2d0606bee17bbccb3765b Mon Sep 17 00:00:00 2001
1514b7
From: Dmitry Monakhov <dmonakhov@openvz.org>
1514b7
Date: Fri, 30 Oct 2015 22:04:46 +0000
1514b7
Subject: [PATCH] copy: fix copying of extents beyond the apparent file size
1514b7
1514b7
fallocate can allocate extents beyond EOF via FALLOC_FL_KEEP_SIZE.
1514b7
Where there is a gap (hole) between the extents, and EOF is within
1514b7
that gap, the final hole wasn't reproduced, resulting in silent
1514b7
data corruption in the copied file (size too small).
1514b7
1514b7
* src/copy.c (extent_copy): Ensure we don't process extents
1514b7
beyond the apparent file size, since processing and allocating
1514b7
those is not currently supported.
1514b7
* tests/cp/fiemap-extents.sh: Renamed from tests/cp/fiemap-empty.sh
1514b7
and re-enable parts checking the extents at and beyond EOF.
1514b7
* tests/local.mk: Reference the renamed test.
1514b7
Fixes http://bugs.gnu.org/21790
1514b7
---
1514b7
 src/copy.c                 |   19 ++++++++-
1514b7
 tests/cp/fiemap-empty.sh   |  102 --------------------------------------------
1514b7
 tests/cp/fiemap-extents.sh |   81 +++++++++++++++++++++++++++++++++++
1514b7
 tests/local.mk             |    2 +-
1514b7
 5 files changed, 100 insertions(+), 104 deletions(-)
1514b7
 delete mode 100755 tests/cp/fiemap-empty.sh
1514b7
 create mode 100755 tests/cp/fiemap-extents.sh
1514b7
1514b7
diff --git a/src/copy.c b/src/copy.c
1514b7
index dc1cd29..6771bb5 100644
1514b7
--- a/src/copy.c
1514b7
+++ b/src/copy.c
1514b7
@@ -432,6 +432,20 @@ extent_copy (int src_fd, int dest_fd, char *buf, size_t buf_size,
1514b7
               ext_len = 0;
1514b7
             }
1514b7
 
1514b7
+          /* Truncate extent to EOF.  Extents starting after EOF are
1514b7
+             treated as zero length extents starting right after EOF.
1514b7
+             Generally this will trigger with an extent starting after
1514b7
+             src_total_size, and result in creating a hole or zeros until EOF.
1514b7
+             Though in a file in which extents have changed since src_total_size
1514b7
+             was determined, we might have an extent spanning that size,
1514b7
+             in which case we'll only copy data up to that size.  */
1514b7
+          if (src_total_size < ext_start + ext_len)
1514b7
+            {
1514b7
+              if (src_total_size < ext_start)
1514b7
+                ext_start = src_total_size;
1514b7
+              ext_len = src_total_size - ext_start;
1514b7
+            }
1514b7
+
1514b7
           hole_size = ext_start - last_ext_start - last_ext_len;
1514b7
 
1514b7
           wrote_hole_at_eof = false;
1514b7
@@ -495,14 +509,17 @@ extent_copy (int src_fd, int dest_fd, char *buf, size_t buf_size,
1514b7
               off_t n_read;
1514b7
               empty_extent = false;
1514b7
               last_ext_len = ext_len;
1514b7
+              bool read_hole;
1514b7
 
1514b7
               if ( ! sparse_copy (src_fd, dest_fd, buf, buf_size,
1514b7
                                   sparse_mode == SPARSE_ALWAYS,
1514b7
                                   src_name, dst_name, ext_len, &n_read,
1514b7
-                                  &wrote_hole_at_eof))
1514b7
+                                  &read_hole))
1514b7
                 goto fail;
1514b7
 
1514b7
               dest_pos = ext_start + n_read;
1514b7
+              if (n_read)
1514b7
+                wrote_hole_at_eof = read_hole;
1514b7
             }
1514b7
 
1514b7
           /* If the file ends with unwritten extents not accounted for in the
1514b7
deleted file mode 100755
1514b7
index b3b2cd7..0000000
1514b7
--- a/tests/cp/fiemap-empty.sh
1514b7
+++ /dev/null
1514b7
@@ -1,101 +0,0 @@
1514b7
-#!/bin/sh
1514b7
-# Test cp reads unwritten extents efficiently
1514b7
-
1514b7
-# Copyright (C) 2011-2013 Free Software Foundation, Inc.
1514b7
-
1514b7
-# This program is free software: you can redistribute it and/or modify
1514b7
-# it under the terms of the GNU General Public License as published by
1514b7
-# the Free Software Foundation, either version 3 of the License, or
1514b7
-# (at your option) any later version.
1514b7
-
1514b7
-# This program is distributed in the hope that it will be useful,
1514b7
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
1514b7
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1514b7
-# GNU General Public License for more details.
1514b7
-
1514b7
-# You should have received a copy of the GNU General Public License
1514b7
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
1514b7
-
1514b7
-. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
1514b7
-print_ver_ cp
1514b7
-
1514b7
-# FIXME: enable any part of this test that is still relevant,
1514b7
-# or, if none are relevant (now that cp does not handle unwritten
1514b7
-# extents), just remove the test altogether.
1514b7
-skip_ 'disabled for now'
1514b7
-
1514b7
-touch fiemap_chk
1514b7
-fiemap_capable_ fiemap_chk ||
1514b7
-  skip_ 'this file system lacks FIEMAP support'
1514b7
-rm fiemap_chk
1514b7
-
1514b7
-# TODO: rather than requiring $(fallocate), possible add
1514b7
-# this functionality to truncate --alloc
1514b7
-fallocate --help >/dev/null || skip_ 'The fallocate utility is required'
1514b7
-fallocate -l 1 -n falloc.test ||
1514b7
-  skip_ 'this file system lacks FALLOCATE support'
1514b7
-rm falloc.test
1514b7
-
1514b7
-# Require more space than we'll actually use, so that
1514b7
-# tests run in parallel do not run out of space.
1514b7
-# Otherwise, with inadequate space, simply running the following
1514b7
-# fallocate command would induce a temporary disk-full condition,
1514b7
-# which would cause failure of unrelated tests run in parallel.
1514b7
-require_file_system_bytes_free_ 800000000
1514b7
-
1514b7
-fallocate -l 600MiB space.test ||
1514b7
-  skip_ 'this test needs at least 600MiB free space'
1514b7
-
1514b7
-# Disable this test on old BTRFS (e.g. Fedora 14)
1514b7
-# which reports ordinary extents for unwritten ones.
1514b7
-filefrag space.test || skip_ 'the 'filefrag' utility is missing'
1514b7
-filefrag -v space.test | grep -F 'unwritten' > /dev/null ||
1514b7
-  skip_ 'this file system does not report empty extents as "unwritten"'
1514b7
-
1514b7
-rm space.test
1514b7
-
1514b7
-# Ensure we read a large empty file quickly
1514b7
-fallocate -l 300MiB empty.big || framework_failure_
1514b7
-timeout 3 cp --sparse=always empty.big cp.test || fail=1
1514b7
-test $(stat -c %s empty.big) = $(stat -c %s cp.test) || fail=1
1514b7
-rm empty.big cp.test
1514b7
-
1514b7
-# Ensure we handle extents beyond file size correctly.
1514b7
-# Note until we support fallocate, we will not maintain
1514b7
-# the file allocation.  FIXME: amend this test when fallocate is supported.
1514b7
-fallocate -l 10MiB -n unwritten.withdata || framework_failure_
1514b7
-dd count=10 if=/dev/urandom conv=notrunc iflag=fullblock of=unwritten.withdata
1514b7
-cp unwritten.withdata cp.test || fail=1
1514b7
-test $(stat -c %s unwritten.withdata) = $(stat -c %s cp.test) || fail=1
1514b7
-cmp unwritten.withdata cp.test || fail=1
1514b7
-rm unwritten.withdata cp.test
1514b7
-
1514b7
-# The following to generate unaccounted extents followed by a hole, is not
1514b7
-# supported by ext4 at least. The ftruncate discards all extents not
1514b7
-# accounted for in the size.
1514b7
-#  fallocate -l 10MiB -n unacc.withholes
1514b7
-#  dd count=10 if=/dev/urandom conv=notrunc iflag=fullblock of=unacc.withholes
1514b7
-#  truncate -s20M unacc.withholes
1514b7
-
1514b7
-# Ensure we handle a hole after empty extents correctly.
1514b7
-# Since all extents are accounted for in the size,
1514b7
-# we can maintain the allocation independently from
1514b7
-# fallocate() support.
1514b7
-fallocate -l 10MiB empty.withholes
1514b7
-truncate -s 20M empty.withholes
1514b7
-sectors_per_block=$(expr $(stat -c %o .) / 512)
1514b7
-cp empty.withholes cp.test || fail=1
1514b7
-test $(stat -c %s empty.withholes) = $(stat -c %s cp.test) || fail=1
1514b7
-# These are usually equal but can vary by an IO block due to alignment
1514b7
-alloc_diff=$(expr $(stat -c %b empty.withholes) - $(stat -c %b cp.test))
1514b7
-alloc_diff=$(echo $alloc_diff | tr -d -- -) # abs()
1514b7
-test $alloc_diff -le $sectors_per_block || fail=1
1514b7
-# Again with SPARSE_ALWAYS
1514b7
-cp --sparse=always empty.withholes cp.test || fail=1
1514b7
-test $(stat -c %s empty.withholes) = $(stat -c %s cp.test) || fail=1
1514b7
-# cp.test should take 0 space, but allowing for some systems
1514b7
-# that store default extended attributes in data blocks
1514b7
-test $(stat -c %b cp.test) -le $sectors_per_block || fail=1
1514b7
-rm empty.withholes cp.test
1514b7
-
1514b7
-Exit $fail
1514b7
diff --git a/tests/cp/fiemap-extents.sh b/tests/cp/fiemap-extents.sh
1514b7
new file mode 100755
1514b7
index 0000000..55ec5df
1514b7
--- /dev/null
1514b7
+++ b/tests/cp/fiemap-extents.sh
1514b7
@@ -0,0 +1,81 @@
1514b7
+#!/bin/sh
1514b7
+# Test cp handles extents correctly
1514b7
+
1514b7
+# Copyright (C) 2011-2015 Free Software Foundation, Inc.
1514b7
+
1514b7
+# This program is free software: you can redistribute it and/or modify
1514b7
+# it under the terms of the GNU General Public License as published by
1514b7
+# the Free Software Foundation, either version 3 of the License, or
1514b7
+# (at your option) any later version.
1514b7
+
1514b7
+# This program is distributed in the hope that it will be useful,
1514b7
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1514b7
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1514b7
+# GNU General Public License for more details.
1514b7
+
1514b7
+# You should have received a copy of the GNU General Public License
1514b7
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
1514b7
+
1514b7
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
1514b7
+print_ver_ cp
1514b7
+
1514b7
+require_sparse_support_
1514b7
+
1514b7
+touch fiemap_chk
1514b7
+fiemap_capable_ fiemap_chk ||
1514b7
+  skip_ 'this file system lacks FIEMAP support'
1514b7
+rm fiemap_chk
1514b7
+
1514b7
+fallocate --help >/dev/null || skip_ 'The fallocate utility is required'
1514b7
+touch falloc.test || framework_failure_
1514b7
+fallocate -l 1 -o 0 -n falloc.test ||
1514b7
+  skip_ 'this file system lacks FALLOCATE support'
1514b7
+rm falloc.test
1514b7
+
1514b7
+# We don't currently handle unwritten extents specially
1514b7
+if false; then
1514b7
+# Require more space than we'll actually use, so that
1514b7
+# tests run in parallel do not run out of space.
1514b7
+# Otherwise, with inadequate space, simply running the following
1514b7
+# fallocate command would induce a temporary disk-full condition,
1514b7
+# which would cause failure of unrelated tests run in parallel.
1514b7
+require_file_system_bytes_free_ 800000000
1514b7
+
1514b7
+fallocate -l 600MiB space.test ||
1514b7
+  skip_ 'this test needs at least 600MiB free space'
1514b7
+
1514b7
+# Disable this test on old BTRFS (e.g. Fedora 14)
1514b7
+# which reports ordinary extents for unwritten ones.
1514b7
+filefrag space.test || skip_ 'the 'filefrag' utility is missing'
1514b7
+filefrag -v space.test | grep -F 'unwritten' > /dev/null ||
1514b7
+  skip_ 'this file system does not report empty extents as "unwritten"'
1514b7
+
1514b7
+rm space.test
1514b7
+
1514b7
+# Ensure we read a large empty file quickly
1514b7
+fallocate -l 300MiB empty.big || framework_failure_
1514b7
+timeout 3 cp --sparse=always empty.big cp.test || fail=1
1514b7
+test $(stat -c %s empty.big) = $(stat -c %s cp.test) || fail=1
1514b7
+rm empty.big cp.test
1514b7
+fi
1514b7
+
1514b7
+# Ensure we handle extents beyond file size correctly.
1514b7
+# Note until we support fallocate, we will not maintain
1514b7
+# the file allocation.  FIXME: amend this test if fallocate is supported.
1514b7
+# Note currently this only uses fiemap logic when the allocation (-l)
1514b7
+# is smaller than the size, thus identifying the file as sparse.
1514b7
+# Note the '-l 1' case is an effective noop, and just checks
1514b7
+# a file with a trailing hole is copied correctly.
1514b7
+for sparse_mode in always auto never; do
1514b7
+  for alloc in '-l 4MiB ' '-l 1MiB -o 4MiB' '-l 1'; do
1514b7
+    dd count=10 if=/dev/urandom iflag=fullblock of=unwritten.withdata
1514b7
+    truncate -s 2MiB unwritten.withdata || framework_failure_
1514b7
+    fallocate $alloc -n unwritten.withdata || framework_failure_
1514b7
+    cp --sparse=$sparse_mode unwritten.withdata cp.test || fail=1
1514b7
+    test $(stat -c %s unwritten.withdata) = $(stat -c %s cp.test) || fail=1
1514b7
+    cmp unwritten.withdata cp.test || fail=1
1514b7
+    rm unwritten.withdata cp.test
1514b7
+  done
1514b7
+done
1514b7
+
1514b7
+Exit $fail
1514b7
diff --git a/tests/local.mk b/tests/local.mk
1514b7
index adf96f0..89fdbb0 100644
1514b7
--- a/tests/local.mk
1514b7
+++ b/tests/local.mk
1514b7
@@ -446,7 +446,7 @@ all_tests =					\
1514b7
   tests/cp/existing-perm-dir.sh			\
1514b7
   tests/cp/existing-perm-race.sh		\
1514b7
   tests/cp/fail-perm.sh				\
1514b7
-  tests/cp/fiemap-empty.sh			\
1514b7
+  tests/cp/fiemap-extents.sh			\
1514b7
   tests/cp/fiemap-FMR.sh			\
1514b7
   tests/cp/fiemap-perf.sh			\
1514b7
   tests/cp/fiemap-2.sh				\
1514b7
-- 
1514b7
1.7.2.5
1514b7