From patchwork Fri Mar 23 16:54:41 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Darrick J. Wong" X-Patchwork-Id: 10305147 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id EEA78605F7 for ; Fri, 23 Mar 2018 16:54:48 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id DE53F28FDC for ; Fri, 23 Mar 2018 16:54:48 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id D280729091; Fri, 23 Mar 2018 16:54:48 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.8 required=2.0 tests=BAYES_00,DKIM_SIGNED, RCVD_IN_DNSWL_HI, T_DKIM_INVALID, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 0221C28FDF for ; Fri, 23 Mar 2018 16:54:48 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752101AbeCWQyr (ORCPT ); Fri, 23 Mar 2018 12:54:47 -0400 Received: from aserp2120.oracle.com ([141.146.126.78]:44968 "EHLO aserp2120.oracle.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751632AbeCWQyq (ORCPT ); Fri, 23 Mar 2018 12:54:46 -0400 Received: from pps.filterd (aserp2120.oracle.com [127.0.0.1]) by aserp2120.oracle.com (8.16.0.22/8.16.0.22) with SMTP id w2NGqwEn083078; Fri, 23 Mar 2018 16:54:44 GMT DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=oracle.com; h=subject : from : to : cc : date : message-id : in-reply-to : references : mime-version : content-type : content-transfer-encoding; s=corp-2017-10-26; bh=1BFhRNvUTZ4NDqXXUF0LkYQGUchjuZGREK6x2G0K4VU=; b=k3Wc6e6/p7SEw9EnAtfS3vqlWjQ5VaxqE+cJOWiIeAnQbZbLtp7OX9xsg1fjv1ek/5J5 KjzBgvdDuld9NvOD3o/3Ig/2qGPBVAsDYbWnMHrcHXr7m2nYue3PEsC2Jhd76nyTxjFx UlMG6WOdN7jOoA1/qrd9/DkwOdJu3qQKtJIOQ9hUScCaRyIIMADJV3UUpgv/ezoZmog5 a9q8LycljUvld0Y6mkVzZYMOkh9MKzdvLshntffVIzHgtgWvMmRJjfykksck6h9wpfkE ztfdSBioI8ZVralA0lsBKIKP0O+Ln3fhtQPCUr8aaZmnR5wZqcf1jLbcjXr1j8ewFMhn Pw== Received: from userv0021.oracle.com (userv0021.oracle.com [156.151.31.71]) by aserp2120.oracle.com with ESMTP id 2gw5g580bx-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK); Fri, 23 Mar 2018 16:54:43 +0000 Received: from userv0122.oracle.com (userv0122.oracle.com [156.151.31.75]) by userv0021.oracle.com (8.14.4/8.14.4) with ESMTP id w2NGshP3028603 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK); Fri, 23 Mar 2018 16:54:43 GMT Received: from abhmp0006.oracle.com (abhmp0006.oracle.com [141.146.116.12]) by userv0122.oracle.com (8.14.4/8.14.4) with ESMTP id w2NGsg01010772; Fri, 23 Mar 2018 16:54:42 GMT Received: from localhost (/67.169.218.210) by default (Oracle Beehive Gateway v4.0) with ESMTP ; Fri, 23 Mar 2018 09:54:42 -0700 Subject: [PATCH 3/4] xfs: test agfl reset on bad list wrapping From: "Darrick J. Wong" To: eguan@linux.alibaba.com, darrick.wong@oracle.com Cc: linux-xfs@vger.kernel.org, fstests@vger.kernel.org Date: Fri, 23 Mar 2018 09:54:41 -0700 Message-ID: <152182408130.14523.7755991950980642374.stgit@magnolia> In-Reply-To: <152182406249.14523.2186010254497227366.stgit@magnolia> References: <152182406249.14523.2186010254497227366.stgit@magnolia> User-Agent: StGit/0.17.1-dirty MIME-Version: 1.0 X-Proofpoint-Virus-Version: vendor=nai engine=5900 definitions=8841 signatures=668695 X-Proofpoint-Spam-Details: rule=notspam policy=default score=0 suspectscore=2 malwarescore=0 phishscore=0 bulkscore=0 spamscore=0 mlxscore=0 mlxlogscore=999 adultscore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.0.1-1711220000 definitions=main-1803200127 Sender: linux-xfs-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-xfs@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP From: Darrick J. Wong >From the kernel patch that this test examines ("xfs: detect agfl count corruption and reset agfl"): "The struct xfs_agfl v5 header was originally introduced with unexpected padding that caused the AGFL to operate with one less slot than intended. The header has since been packed, but the fix left an incompatibility for users who upgrade from an old kernel with the unpacked header to a newer kernel with the packed header while the AGFL happens to wrap around the end. The newer kernel recognizes one extra slot at the physical end of the AGFL that the previous kernel did not. The new kernel will eventually attempt to allocate a block from that slot, which contains invalid data, and cause a crash. "This condition can be detected by comparing the active range of the AGFL to the count. While this detects a padding mismatch, it can also trigger false positives for unrelated flcount corruption. Since we cannot distinguish a size mismatch due to padding from unrelated corruption, we can't trust the AGFL enough to simply repopulate the empty slot. "Instead, avoid unnecessarily complex detection logic and and use a solution that can handle any form of flcount corruption that slips through read verifiers: distrust the entire AGFL and reset it to an empty state. Any valid blocks within the AGFL are intentionally leaked. This requires xfs_repair to rectify (which was already necessary based on the state the AGFL was found in). The reset mitigates the side effect of the padding mismatch problem from a filesystem crash to a free space accounting inconsistency." This test exercises the reset code by mutating a fresh filesystem to contain an agfl with various list configurations of correctly wrapped, incorrectly wrapped, not wrapped, and actually corrupt free lists; then checks the success of the reset operation by fragmenting the free space btrees to exercise the agfl. Kernels without this reset fix will shut down the filesystem with corruption errors. Signed-off-by: Darrick J. Wong Reviewed-by: Brian Foster --- common/rc | 27 +++++- tests/xfs/709 | 258 +++++++++++++++++++++++++++++++++++++++++++++++++++++ tests/xfs/709.out | 13 +++ tests/xfs/group | 1 4 files changed, 295 insertions(+), 4 deletions(-) create mode 100755 tests/xfs/709 create mode 100644 tests/xfs/709.out -- To unsubscribe from this list: send the line "unsubscribe linux-xfs" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html diff --git a/common/rc b/common/rc index 2c29d55..18a438a 100644 --- a/common/rc +++ b/common/rc @@ -3440,6 +3440,28 @@ _get_device_size() grep `_short_dev $1` /proc/partitions | awk '{print $3}' } +# Make sure we actually have dmesg checking set up. +_require_check_dmesg() { + test -w /dev/kmsg || \ + _notrun "Test requires writable /dev/kmsg." +} + +# Return the dmesg log since the start of this test. Caller must ensure that +# /dev/kmsg was writable when the test was started so that we can find the +# beginning of this test's log messages; _require_check_dmesg does this. +_dmesg_since_test_start() { + # search the dmesg log of last run of $seqnum for possible failures + # use sed \cregexpc address type, since $seqnum contains "/" + dmesg | tac | sed -ne "0,\#run fstests $seqnum at $date_time#p" | \ + tac +} + +# check dmesg log for a specific string, subject to the same requirements as +# _dmesg_since_test_start. +_check_dmesg_for() { + _dmesg_since_test_start | egrep -q "$1" +} + # check dmesg log for WARNING/Oops/etc. _check_dmesg() { @@ -3453,10 +3475,7 @@ _check_dmesg() # filter out intentional WARNINGs or Oopses filter=${1:-cat} - # search the dmesg log of last run of $seqnum for possible failures - # use sed \cregexpc address type, since $seqnum contains "/" - dmesg | tac | sed -ne "0,\#run fstests $seqnum at $date_time#p" | \ - tac | $filter >$seqres.dmesg + _dmesg_since_test_start | $filter >$seqres.dmesg egrep -q -e "kernel BUG at" \ -e "WARNING:" \ -e "BUG:" \ diff --git a/tests/xfs/709 b/tests/xfs/709 new file mode 100755 index 0000000..fa83039 --- /dev/null +++ b/tests/xfs/709 @@ -0,0 +1,258 @@ +#! /bin/bash +# FS QA Test No. 709 +# +# Make sure XFS can fix a v5 AGFL that wraps over the last block. +# Refer to commit 96f859d52bcb ("libxfs: pack the agfl header structure so +# XFS_AGFL_SIZE is correct") for details on the original on-disk format error +# and the patch "xfs: detect agfl count corruption and reset agfl") for details +# about the fix. +# +#----------------------------------------------------------------------- +# Copyright (c) 2018 Oracle, Inc. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it would be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +#----------------------------------------------------------------------- +# + +seq=`basename $0` +seqres=$RESULT_DIR/$seq +echo "QA output created by $seq" + +here=`pwd` +tmp=/tmp/$$ +status=1 +trap "_cleanup; rm -f $tmp.*; exit \$status" 0 1 2 3 15 + +_cleanup() +{ + cd / + rm -f $tmp.* +} + +rm -f $seqres.full + +# get standard environment, filters and checks +. ./common/rc +. ./common/filter + +# real QA test starts here +_supported_fs xfs +_supported_os Linux + +_require_check_dmesg +_require_scratch +_require_test_program "punch-alternating" + +# This is only a v5 filesystem problem +_require_scratch_xfs_crc + +mount_loop() { + if ! _try_scratch_mount >> $seqres.full 2>&1; then + echo "scratch mount failed" >> $seqres.full + return + fi + + # Trigger agfl fixing by fragmenting free space enough to cause + # a bnobt split + blksz=$(_get_file_block_size ${SCRATCH_MNT}) + bno_maxrecs=$(( blksz / 8 )) + filesz=$((bno_maxrecs * 3 * blksz)) + rm -rf $SCRATCH_MNT/a + $XFS_IO_PROG -f -c "falloc 0 $filesz" $SCRATCH_MNT/a >> $seqres.full 2>&1 + test -e $SCRATCH_MNT/a && ./src/punch-alternating $SCRATCH_MNT/a + rm -rf $SCRATCH_MNT/a + + _scratch_unmount 2>&1 | _filter_scratch +} + +dump_ag0() { + _scratch_xfs_db -c 'sb 0' -c 'p' -c 'agf 0' -c 'p' -c 'agfl 0' -c 'p' +} + +runtest() { + cmd="$1" + + # Format filesystem + echo "TEST $cmd" | tee /dev/ttyprintk + echo "TEST $cmd" >> $seqres.full + _scratch_mkfs >> $seqres.full + + # Record what was here before + echo "FS BEFORE" >> $seqres.full + dump_ag0 > $tmp.before + cat $tmp.before >> $seqres.full + + sectsize=$(_scratch_xfs_get_metadata_field "sectsize" "sb 0") + flfirst=$(_scratch_xfs_get_metadata_field "flfirst" "agf 0") + fllast=$(_scratch_xfs_get_metadata_field "fllast" "agf 0") + flcount=$(_scratch_xfs_get_metadata_field "flcount" "agf 0") + + # Due to a padding bug in the original v5 struct xfs_agfl, + # XFS_AGFL_SIZE could be 36 on 32-bit or 40 on 64-bit. On a system + # with 512b sectors, this means that the AGFL length could be + # ((512 - 36) / 4) = 119 entries on 32-bit or ((512 - 40) / 4) = 118 + # entries on 64-bit. + # + # We now have code to figure out if the AGFL list wraps incorrectly + # according to the kernel's agfl size and fix it by resetting the agfl + # to zero length. Mutate ag 0's agfl to be in various configurations + # and see if we can trigger the reset. + # + # Don't hardcode the numbers, calculate them. + + # Have to have at least three agfl items to test full wrap + test "$flcount" -ge 3 || _notrun "insufficient agfl flcount" + + # mkfs should be able to make us a nice neat flfirst < fllast setup + test "$flfirst" -lt "$fllast" || _notrun "fresh agfl already wrapped?" + + bad_agfl_size=$(( (sectsize - 40) / 4 )) + good_agfl_size=$(( (sectsize - 36) / 4 )) + agfl_size= + case "$1" in + "fix_end") # fllast points to the end w/ 40-byte padding + new_flfirst=$(( bad_agfl_size - flcount )) + agfl_size=$bad_agfl_size;; + "fix_start") # flfirst points to the end w/ 40-byte padding + new_flfirst=$(( bad_agfl_size - 1)) + agfl_size=$bad_agfl_size;; + "fix_wrap") # list wraps around end w/ 40-byte padding + new_flfirst=$(( bad_agfl_size - (flcount / 2) )) + agfl_size=$bad_agfl_size;; + "start_zero") # flfirst points to the start + new_flfirst=0 + agfl_size=$good_agfl_size;; + "good_end") # fllast points to the end w/ 36-byte padding + new_flfirst=$(( good_agfl_size - flcount )) + agfl_size=$good_agfl_size;; + "good_start") # flfirst points to the end w/ 36-byte padding + new_flfirst=$(( good_agfl_size - 1 )) + agfl_size=$good_agfl_size;; + "good_wrap") # list wraps around end w/ 36-byte padding + new_flfirst=$(( good_agfl_size - (flcount / 2) )) + agfl_size=$good_agfl_size;; + "bad_start") # flfirst points off the end + new_flfirst=$good_agfl_size + agfl_size=$good_agfl_size;; + "no_move") # whatever mkfs formats (flfirst points to start) + new_flfirst=$flfirst + agfl_size=$good_agfl_size;; + "simple_move") # move list arbitrarily + new_flfirst=$((fllast + 1)) + agfl_size=$good_agfl_size;; + *) + _fail "Internal test error";; + esac + new_fllast=$(( (new_flfirst + flcount - 1) % agfl_size )) + + # Log what we're doing... + cat >> $seqres.full << ENDL +sector size: $sectsize +bad_agfl_size: $bad_agfl_size [0 - $((bad_agfl_size - 1))] +good_agfl_size: $good_agfl_size [0 - $((good_agfl_size - 1))] +agfl_size: $agfl_size +flfirst: $flfirst +fllast: $fllast +flcount: $flcount +new_flfirst: $new_flfirst +new_fllast: $new_fllast +ENDL + + # Remap the agfl blocks + echo "$((good_agfl_size - 1)) 0xffffffff" > $tmp.remap + seq "$flfirst" "$fllast" | while read f; do + list_pos=$((f - flfirst)) + dest_pos=$(( (new_flfirst + list_pos) % agfl_size )) + bno=$(_scratch_xfs_get_metadata_field "bno[$f]" "agfl 0") + echo "$dest_pos $bno" >> $tmp.remap + done + + cat $tmp.remap | while read dest_pos bno junk; do + _scratch_xfs_set_metadata_field "bno[$dest_pos]" "$bno" \ + "agfl 0" >> $seqres.full + done + + # Set new flfirst/fllast + _scratch_xfs_set_metadata_field "fllast" "$new_fllast" \ + "agf 0" >> $seqres.full + _scratch_xfs_set_metadata_field "flfirst" "$new_flfirst" \ + "agf 0" >> $seqres.full + + echo "FS AFTER" >> $seqres.full + dump_ag0 > $tmp.corrupt 2> /dev/null + diff -u $tmp.before $tmp.corrupt >> $seqres.full + + # Mount and see what happens + mount_loop + + # Did we end up with a non-wrapped list? + flfirst=$(_scratch_xfs_get_metadata_field "flfirst" "agf 0" 2>/dev/null) + fllast=$(_scratch_xfs_get_metadata_field "fllast" "agf 0" 2>/dev/null) + echo "flfirst=${flfirst} fllast=${fllast}" >> $seqres.full + if [ "${flfirst}" -ge "$((good_agfl_size - 1))" ]; then + echo "ASSERT flfirst < good_agfl_size - 1" | tee -a $seqres.full + fi + if [ "${fllast}" -ge "$((good_agfl_size - 1))" ]; then + echo "ASSERT fllast < good_agfl_size - 1" | tee -a $seqres.full + fi + if [ "${flfirst}" -ge "${fllast}" ]; then + echo "ASSERT flfirst < fllast" | tee -a $seqres.full + fi + + echo "FS MOUNTLOOP" >> $seqres.full + dump_ag0 > $tmp.mountloop 2> /dev/null + diff -u $tmp.corrupt $tmp.mountloop >> $seqres.full + + # Let's see what repair thinks + echo "REPAIR" >> $seqres.full + _scratch_xfs_repair >> $seqres.full 2>&1 + + echo "FS REPAIR" >> $seqres.full + dump_ag0 > $tmp.repair 2> /dev/null + diff -u $tmp.mountloop $tmp.repair >> $seqres.full + + # Exercise the filesystem again to make sure there aren't any lasting + # ill effects from either the agfl reset or the recommended subsequent + # repair run. + mount_loop + + echo "FS REMOUNT" >> $seqres.full + dump_ag0 > $tmp.remount 2> /dev/null + diff -u $tmp.repair $tmp.remount >> $seqres.full +} + +runtest fix_end +runtest fix_start +runtest fix_wrap +runtest start_zero +runtest good_end +runtest good_start +runtest good_wrap +runtest bad_start +runtest no_move +runtest simple_move + +# Did we get the kernel warning too? +warn_str='WARNING: Reset corrupted AGFL' +_check_dmesg_for "${warn_str}" || echo "Missing dmesg string \"${warn_str}\"." + +# Now run the regular dmesg check, filtering out the agfl warning +filter_agfl_reset_printk() { + grep -v "${warn_str}" +} +_check_dmesg filter_agfl_reset_printk + +status=0 +exit 0 diff --git a/tests/xfs/709.out b/tests/xfs/709.out new file mode 100644 index 0000000..f1fa9a3 --- /dev/null +++ b/tests/xfs/709.out @@ -0,0 +1,13 @@ +QA output created by 709 +TEST fix_end +TEST fix_start +TEST fix_wrap +TEST start_zero +TEST good_end +TEST good_start +TEST good_wrap +TEST bad_start +ASSERT flfirst < good_agfl_size - 1 +ASSERT flfirst < fllast +TEST no_move +TEST simple_move diff --git a/tests/xfs/group b/tests/xfs/group index e2397fe..472120e 100644 --- a/tests/xfs/group +++ b/tests/xfs/group @@ -441,3 +441,4 @@ 441 auto quick clone quota 442 auto stress clone quota 443 auto quick ioctl fsr +709 auto quick