| import os |
| |
| import infra.basetest |
| |
| |
| class TestDdrescue(infra.basetest.BRTest): |
| |
| # A specific configuration is needed for testing ddrescue: |
| # - A kernel config fragment enables loop blk dev and device |
| # mapper dm-dust, which are used to simulate a failing storage |
| # block device. |
| # - lvm2 user space package is needed to configure dm-dust with dmsetup |
| kernel_fragment = \ |
| infra.filepath("tests/package/test_ddrescue/linux-ddrescue.fragment") |
| config = \ |
| f""" |
| BR2_aarch64=y |
| BR2_TOOLCHAIN_EXTERNAL=y |
| BR2_TARGET_GENERIC_GETTY_PORT="ttyAMA0" |
| BR2_LINUX_KERNEL=y |
| BR2_LINUX_KERNEL_CUSTOM_VERSION=y |
| BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.18.2" |
| BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y |
| BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="board/qemu/aarch64-virt/linux.config" |
| BR2_LINUX_KERNEL_CONFIG_FRAGMENT_FILES="{kernel_fragment}" |
| BR2_LINUX_KERNEL_NEEDS_HOST_OPENSSL=y |
| BR2_PACKAGE_DDRESCUE=y |
| BR2_PACKAGE_LVM2=y |
| BR2_TARGET_ROOTFS_CPIO=y |
| BR2_TARGET_ROOTFS_CPIO_GZIP=y |
| # BR2_TARGET_ROOTFS_TAR is not set |
| """ |
| |
| def test_run(self): |
| img = os.path.join(self.builddir, "images", "rootfs.cpio.gz") |
| kern = os.path.join(self.builddir, "images", "Image") |
| self.emulator.boot(arch="aarch64", |
| kernel=kern, |
| kernel_cmdline=["console=ttyAMA0"], |
| options=["-M", "virt", "-cpu", "cortex-a57", "-m", "256M", "-initrd", img]) |
| self.emulator.login() |
| |
| # Test variables: |
| dev_img = "/tmp/dev.img" |
| lo_dev = "/dev/loop0" |
| dm_dev_name = "dust0" |
| dm_dev = f"/dev/mapper/{dm_dev_name}" |
| ddrescue_img = "/tmp/ddrescue.img" |
| |
| # Test the program can execute |
| self.assertRunOk("ddrescue --version") |
| |
| # Create a 1MB file of zeroes for initial loopback block device |
| self.assertRunOk(f"dd if=/dev/zero of={dev_img} bs=1M count=1") |
| |
| # Setup lookback block device |
| self.assertRunOk(f"losetup {lo_dev} {dev_img}") |
| |
| # Create and setup dm-dust to simulate a failing block device |
| # The dev_img file is 1MB: 2048 blocks of 512 bytes each |
| self.assertRunOk(f"dmsetup create {dm_dev_name} --table '0 2048 dust {lo_dev} 0 512'") |
| |
| # Add few bad blocks and enable I/O error emulation |
| for badblock in [30, 40, 50, 60]: |
| self.assertRunOk(f"dmsetup message {dm_dev_name} 0 addbadblock {badblock}") |
| self.assertRunOk(f"dmsetup message {dm_dev_name} 0 enable") |
| |
| # Show device mapper status, to make debugging easier |
| self.assertRunOk(f"dmsetup status {dm_dev_name}") |
| |
| # A normal 'dd' is expected to fail with I/O error |
| cmd = f"dd if={dm_dev} of=/dev/null bs=512" |
| _, exit_code = self.emulator.run(cmd) |
| self.assertNotEqual(exit_code, 0) |
| |
| # Where a normal 'dd' fails, 'ddrescue' is expected to succeed |
| self.assertRunOk(f"ddrescue {dm_dev} {ddrescue_img}") |
| |
| # ddrescue does not normally write any output data when there |
| # is I/O error on the input. The intent is to preserve any |
| # data that could have been read in a previous pass. There is |
| # one exception, when the output is a non-existing regular |
| # file, ddrescue will initialize it with zeroes the first |
| # time. Since the original image file was also including |
| # zeroes, the recovered image is expected to be the same as |
| # the original one. See ddrescue manual: |
| # https://www.gnu.org/software/ddrescue/manual/ddrescue_manual.html#Introduction |
| # "Ddrescue does not write zeros to the output when it finds |
| # bad sectors in the input, and does not truncate the output |
| # file if not asked to." |
| # https://www.gnu.org/software/ddrescue/manual/ddrescue_manual.html#Algorithm |
| # "If the output file is a regular file created by ddrescue, |
| # the areas marked as bad-sector will contain zeros." |
| self.assertRunOk(f"cmp {dev_img} {ddrescue_img}") |