From a2d053329c2aa4327a5d80fb9aeaa8455e5527e0 Mon Sep 17 00:00:00 2001 From: Sean Eric Fagan Date: Thu, 7 May 2026 21:22:38 +0100 Subject: [PATCH] Add some more file layout output, triggered by -v With one -v, the block type (parity or data) is printed (matching the ASCII-art version); with two -v, the offset into the file is also printed. This also updates the man page, and adds some simple test scripts. Sponsored-by: Klara, Inc. Sponsored-by: Wasabi Technology, Inc. Reviewed-by: Brian Behlendorf Reviewed-by: Sean Fagan Signed-off-by: Sean Fagan Closes #18470 --- cmd/zdb/zdb.c | 64 +++++++++++---- man/man8/zdb.8 | 7 +- tests/runfiles/common.run | 7 +- tests/zfs-tests/tests/Makefile.am | 4 + .../cli_root/zdb/zdb_file_layout_001.ksh | 78 +++++++++++++++++++ .../cli_root/zdb/zdb_file_layout_002.ksh | 78 +++++++++++++++++++ .../cli_root/zdb/zdb_file_layout_003.ksh | 78 +++++++++++++++++++ .../cli_root/zdb/zdb_file_layout_neg.ksh | 57 ++++++++++++++ 8 files changed, 353 insertions(+), 20 deletions(-) create mode 100755 tests/zfs-tests/tests/functional/cli_root/zdb/zdb_file_layout_001.ksh create mode 100755 tests/zfs-tests/tests/functional/cli_root/zdb/zdb_file_layout_002.ksh create mode 100755 tests/zfs-tests/tests/functional/cli_root/zdb/zdb_file_layout_003.ksh create mode 100755 tests/zfs-tests/tests/functional/cli_root/zdb/zdb_file_layout_neg.ksh diff --git a/cmd/zdb/zdb.c b/cmd/zdb/zdb.c index 68c9696a8aa..a2e65de6662 100644 --- a/cmd/zdb/zdb.c +++ b/cmd/zdb/zdb.c @@ -2802,18 +2802,18 @@ print_file_layout_raidz(vdev_t *vd, blkptr_t *bp, uint64_t file_offset, vd->vdev_children, vdrz->vd_nparity); raidz_row_t *rr = rm->rm_row[0]; - /* - * Account for out of order disks in raidz1. - * For now just reverse them back and adjust for it later. - */ - if (rr->rr_firstdatacol == 1 && (zio.io_offset & (1ULL << 20))) { - uint64_t devidx = rr->rr_col[0].rc_devidx; - rr->rr_col[0].rc_devidx = rr->rr_col[1].rc_devidx; - rr->rr_col[1].rc_devidx = devidx; - } - if (!dump_opt['H']) { int last_disk = vd->vdev_children - 1; + /* + * Account for out of order disks in raidz1. + * For now just reverse them back and adjust for it later. + */ + if (rr->rr_firstdatacol == 1 && + (zio.io_offset & (1ULL << 20))) { + uint64_t devidx = rr->rr_col[0].rc_devidx; + rr->rr_col[0].rc_devidx = rr->rr_col[1].rc_devidx; + rr->rr_col[1].rc_devidx = devidx; + } int first_disk = rr->rr_col[0].rc_devidx; (void) printf("%12llx", (u_longlong_t)file_offset); @@ -2843,23 +2843,49 @@ print_file_layout_raidz(vdev_t *vd, blkptr_t *bp, uint64_t file_offset, static uint64_t next_offset = 0; if (next_offset != file_offset) { - (void) printf("skip hole\t-\t%llx\n", - (u_longlong_t)((file_offset - next_offset) >> - vd->vdev_ashift)); + (void) printf("skip hole\t-\t\t%lld\n", + (u_longlong_t)((file_offset - next_offset) / 512)); } next_offset = file_offset + BP_GET_LSIZE(bp); + uint64_t tmp_offset = file_offset; + for (int c = 0; c < rr->rr_cols; c++) { + boolean_t pcol = c < rr->rr_firstdatacol; raidz_col_t *rc = &rr->rr_col[c]; char *path = vd->vdev_child[rc->rc_devidx]->vdev_path; - // c < rr->rr_firstdatacol + if (rc->rc_size == 0) continue; - (void) printf("%s\t%llu\t%d\n", + (void) printf("%s\t\t%llu\t%d", zfs_basename(path), (u_longlong_t)(rc->rc_offset + VDEV_LABEL_START_SIZE)/512, (int)rc->rc_size/512); + if (dump_opt['v']) { + char label = pcol ? 'P' : 'D'; + int num; + + if (c < 2) { + num = 0; + } else { + num = pcol ? c : + (c - rr->rr_firstdatacol); + } + printf("\t%c%d", label, num); + if (dump_opt['v'] > 1) { + unsigned long long off; + if (pcol) + off = file_offset; + else + off = tmp_offset; + off = off / 512ULL; + printf("\t%llu", off); + } + } + if (!pcol) + tmp_offset += rc->rc_size; + printf("\n"); } } } @@ -2989,7 +3015,12 @@ dump_indirect_layout(dnode_t *dn) * Start layout with a header */ if (dump_opt['H']) { - (void) printf("DISK\t\tLBA\t\tCOUNT\n"); + (void) printf("DISK\t\t\tLBA\tCOUNT"); + if (dump_opt['v']) + (void) printf("\tTYPE"); + if (dump_opt['v'] > 1) + (void) printf("\tOFFSET"); + printf("\n"); } else { char diskhdr[16]; @@ -10519,6 +10550,7 @@ main(int argc, char **argv) } if (dump_opt['f'] && os != NULL) { + dump_opt['v'] = verbose; dump_file_data_layout(os); } else if (dump_opt['B']) { dump_backup(target, objset_id, diff --git a/man/man8/zdb.8 b/man/man8/zdb.8 index f500e7e8a13..596e1d94e39 100644 --- a/man/man8/zdb.8 +++ b/man/man8/zdb.8 @@ -284,10 +284,15 @@ Decode and display block from an embedded block pointer specified by the arguments. .It Fl f , -file-layout Display the file layout of an object for the disks of a raidz vdev. +Numeric values in the disply are hexadecimal. With .Fl H , the output is in scripted mode for easy parsing, with all values -being presented as 512 byte blocks. +being presented as 512 byte blocks in decimal; with +.Fl v , +the block type (parity or data) is displayed; with +.Fl vv , +the offset into the file for each block is also printed. Only a single top-level raidz vdev is supported. .It Fl h , -history Display pool history similar to diff --git a/tests/runfiles/common.run b/tests/runfiles/common.run index df80437ad0c..14e4bd79f85 100644 --- a/tests/runfiles/common.run +++ b/tests/runfiles/common.run @@ -173,9 +173,10 @@ tests = ['zdb_002_pos', 'zdb_003_pos', 'zdb_004_pos', 'zdb_005_pos', 'zdb_006_pos', 'zdb_args_neg', 'zdb_args_pos', 'zdb_block_size_histogram', 'zdb_checksum', 'zdb_decompress', 'zdb_display_block', 'zdb_encrypted', 'zdb_encrypted_raw', - 'zdb_label_checksum', 'zdb_object_range_neg', 'zdb_object_range_pos', - 'zdb_objset_id', 'zdb_decompress_zstd', 'zdb_recover', 'zdb_recover_2', - 'zdb_backup', 'zdb_tunables'] + 'zdb_file_layout_001', 'zdb_file_layout_002', 'zdb_file_layout_003', + 'zdb_file_layout_neg', 'zdb_label_checksum', 'zdb_object_range_neg', + 'zdb_object_range_pos', 'zdb_objset_id', 'zdb_decompress_zstd', + 'zdb_recover', 'zdb_recover_2', 'zdb_backup', 'zdb_tunables'] pre = post = tags = ['functional', 'cli_root', 'zdb'] diff --git a/tests/zfs-tests/tests/Makefile.am b/tests/zfs-tests/tests/Makefile.am index a6242ba0f52..28acc6f3af1 100644 --- a/tests/zfs-tests/tests/Makefile.am +++ b/tests/zfs-tests/tests/Makefile.am @@ -650,6 +650,10 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \ functional/cli_root/zdb/zdb_encrypted.ksh \ functional/cli_root/zdb/zdb_encrypted_raw.ksh \ functional/cli_root/zdb/zdb_label_checksum.ksh \ + functional/cli_root/zdb/zdb_file_layout_001.ksh \ + functional/cli_root/zdb/zdb_file_layout_002.ksh \ + functional/cli_root/zdb/zdb_file_layout_003.ksh \ + functional/cli_root/zdb/zdb_file_layout_neg.ksh \ functional/cli_root/zdb/zdb_object_range_neg.ksh \ functional/cli_root/zdb/zdb_object_range_pos.ksh \ functional/cli_root/zdb/zdb_objset_id.ksh \ diff --git a/tests/zfs-tests/tests/functional/cli_root/zdb/zdb_file_layout_001.ksh b/tests/zfs-tests/tests/functional/cli_root/zdb/zdb_file_layout_001.ksh new file mode 100755 index 00000000000..f9c9555b84b --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zdb/zdb_file_layout_001.ksh @@ -0,0 +1,78 @@ +#!/bin/ksh +# SPDX-License-Identifier: CDDL-1.0 + +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright (c) 2019 by Datto, Inc. All rights reserved. +# Copyright (c) 2026, Klara Inc. +# + +. $STF_SUITE/include/libtest.shlib + +# +# Description: +# zdb -fHv will display block +# layouts for the object. +# +# Strategery: +# 1. Create a RAIDZ1 pool, set compression to none +# 2. Create a file filled with random data +# 3. Get the inode number of the file +# 4. Run zdb -fHv / & extract file +# 5. Compare real file and extracted file. + +DATA=/$TESTPOOL1/random.bin +BLOCKS=$(( $RANDOM % 16 )) +COMPARE=/tmp/compare.$$ + +function cleanup +{ + destroy_pool $TESTPOOL1 + rm -f $TESTDIR/file?.bin $COMPARE +} + +log_assert "Verify zdb -fHv displays correct offsets" +log_onexit cleanup + +# 1. Create a RAIDZ1 pool +log_must mkdir -p $TESTDIR +for file in 1 2 3 4 5 +do + rm -f $TESTDIR/file${file}.bin + touch $TESTDIR/file${file}.bin + log_must truncate -s 128m $TESTDIR/file${file}.bin +done + +log_must zpool create -O compression=off -O recordsize=16K $TESTPOOL1 raidz1 $TESTDIR/file[12345].bin +zfs get compression,recordsize $TESTPOOL1 +# 2. Create a file with random data +log_must rm -f $DATA +log_must dd if=/dev/urandom of=${DATA} bs=16k count=${BLOCKS} > /dev/null 2>&1 +log_must zpool sync $TESTPOOL1 + +# 3. Get the inode number of the file +INUM=$(ls -li $DATA | cut -f1 -d ' ') + +# 4. Extract the contents of the file using dd +rm -f $COMPARE +log_must touch ${COMPARE} +log_must zdb -fHv $TESTPOOL1/ ${INUM} | grep 'D.$' | + while read file offset count rest + do + log_must sh -c "dd if=$TESTDIR/${file} bs=512 skip=${offset} count=${count} >> ${COMPARE}" + done + +# 5. Compare files +log_must cmp ${COMPARE} ${DATA} + +log_pass "'zdb -fHv' works as expected." diff --git a/tests/zfs-tests/tests/functional/cli_root/zdb/zdb_file_layout_002.ksh b/tests/zfs-tests/tests/functional/cli_root/zdb/zdb_file_layout_002.ksh new file mode 100755 index 00000000000..455ec6ccb21 --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zdb/zdb_file_layout_002.ksh @@ -0,0 +1,78 @@ +#!/bin/ksh +# SPDX-License-Identifier: CDDL-1.0 + +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright (c) 2019 by Datto, Inc. All rights reserved. +# Copyright (c) 2026, Klara Inc. +# + +. $STF_SUITE/include/libtest.shlib + +# +# Description: +# zdb -fHv will display block +# layouts for the object. +# +# Strategery: +# 1. Create a RAIDZ2 pool, set compression to none +# 2. Create a file filled with random data +# 3. Get the inode number of the file +# 4. Run zdb -fHv / & extract file +# 5. Compare real file and extracted file. + +DATA=/$TESTPOOL1/random.bin +BLOCKS=$(( $RANDOM % 16 )) +COMPARE=/tmp/compare.$$ + +function cleanup +{ + destroy_pool $TESTPOOL1 + rm -f $TESTDIR/file?.bin $COMPARE +} + +log_assert "Verify zdb -fHv displays correct offsets" +log_onexit cleanup + +# 1. Create a RAIDZ1 pool +log_must mkdir -p $TESTDIR +for file in 1 2 3 4 5 6 +do + rm -f $TESTDIR/file${file}.bin + touch $TESTDIR/file${file}.bin + log_must truncate -s 128m $TESTDIR/file${file}.bin +done + +log_must zpool create -O compression=off -O recordsize=16K $TESTPOOL1 raidz2 $TESTDIR/file[123456].bin +zfs get compression,recordsize $TESTPOOL1 +# 2. Create a file with random data +log_must rm -f $DATA +log_must dd if=/dev/urandom of=${DATA} bs=16k count=${BLOCKS} > /dev/null 2>&1 +log_must zpool sync $TESTPOOL1 + +# 3. Get the inode number of the file +INUM=$(ls -li $DATA | cut -f1 -d ' ') + +# 4. Extract the contents of the file using dd +rm -f $COMPARE +log_must touch ${COMPARE} +log_must zdb -fHv $TESTPOOL1/ ${INUM} | grep 'D.$' | + while read file offset count rest + do + log_must sh -c "dd if=$TESTDIR/${file} bs=512 skip=${offset} count=${count} >> ${COMPARE}" + done + +# 5. Compare files +log_must cmp ${COMPARE} ${DATA} + +log_pass "'zdb -fHv' works as expected." diff --git a/tests/zfs-tests/tests/functional/cli_root/zdb/zdb_file_layout_003.ksh b/tests/zfs-tests/tests/functional/cli_root/zdb/zdb_file_layout_003.ksh new file mode 100755 index 00000000000..7673b3488c7 --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zdb/zdb_file_layout_003.ksh @@ -0,0 +1,78 @@ +#!/bin/ksh +# SPDX-License-Identifier: CDDL-1.0 + +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright (c) 2019 by Datto, Inc. All rights reserved. +# Copyright (c) 2026, Klara Inc. +# + +. $STF_SUITE/include/libtest.shlib + +# +# Description: +# zdb -fHv will display block +# layouts for the object. +# +# Strategery: +# 1. Create a RAIDZ3 pool, set compression to none +# 2. Create a file filled with random data +# 3. Get the inode number of the file +# 4. Run zdb -fHv / & extract file +# 5. Compare real file and extracted file. + +DATA=/$TESTPOOL1/random.bin +BLOCKS=$(( $RANDOM % 16 )) +COMPARE=/tmp/compare.$$ + +function cleanup +{ + destroy_pool $TESTPOOL1 + rm -f $TESTDIR/file?.bin $COMPARE +} + +log_assert "Verify zdb -fHv displays correct offsets" +log_onexit cleanup + +# 1. Create a RAIDZ1 pool +log_must mkdir -p $TESTDIR +for file in 1 2 3 4 5 6 7 +do + rm -f $TESTDIR/file${file}.bin + touch $TESTDIR/file${file}.bin + log_must truncate -s 128m $TESTDIR/file${file}.bin +done + +log_must zpool create -O compression=off -O recordsize=16K $TESTPOOL1 raidz3 $TESTDIR/file[123456].bin +zfs get compression,recordsize $TESTPOOL1 +# 2. Create a file with random data +log_must rm -f $DATA +log_must dd if=/dev/urandom of=${DATA} bs=16k count=${BLOCKS} > /dev/null 2>&1 +log_must zpool sync $TESTPOOL1 + +# 3. Get the inode number of the file +INUM=$(ls -li $DATA | cut -f1 -d ' ') + +# 4. Extract the contents of the file using dd +rm -f $COMPARE +log_must touch ${COMPARE} +log_must zdb -fHv $TESTPOOL1/ ${INUM} | grep 'D.$' | + while read file offset count rest + do + log_must sh -c "dd if=$TESTDIR/${file} bs=512 skip=${offset} count=${count} >> ${COMPARE}" + done + +# 5. Compare files +log_must cmp ${COMPARE} ${DATA} + +log_pass "'zdb -fHv' works as expected." diff --git a/tests/zfs-tests/tests/functional/cli_root/zdb/zdb_file_layout_neg.ksh b/tests/zfs-tests/tests/functional/cli_root/zdb/zdb_file_layout_neg.ksh new file mode 100755 index 00000000000..124bdb6b6b3 --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zdb/zdb_file_layout_neg.ksh @@ -0,0 +1,57 @@ +#!/bin/ksh +# SPDX-License-Identifier: CDDL-1.0 + +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright (c) 2019 by Datto, Inc. All rights reserved. +# Copyright (c) 2026, Klara Inc. +# + +. $STF_SUITE/include/libtest.shlib + +# +# Description: +# Ensure zdb -f only works on raidz +# +# Strategery: +# 1. Create a pool with one disk +# 2. Create a file +# 3. Get the inode number of the file +# 4. Run zdb -f +# 5. Confirm failure status + +function cleanup +{ + destroy_pool $TESTPOOL1 + rm -f $TESTDIR/file1.bin +} + +log_assert "Verify zdb -f fails on non-raidz pool" +log_onexit cleanup + +# 1. Create a RAIDZ1 pool +log_must mkdir -p $TESTDIR +touch $TESTDIR/file1.bin +log_must truncate -s 128m $TESTDIR/file1.bin +log_must zpool create -f $TESTPOOL1 $TESTDIR/file1.bin + +# 2. Create a file +log_must touch /$TESTPOOL1/file.txt + +# 3. Get the inode number of the file +INUM=$(ls -li /$TESTDIR/file1.txt | cut -f1 -d ' ') + +# 4. Run zdb -f +log_mustnot zdb -f $TESTDIR/ $INUM + +log_pass "'zdb -f' fails on non-raidz as expected."