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