1import os 2 3import infra.basetest 4 5 6class TestDdrescue(infra.basetest.BRTest): 7 8 # A specific configuration is needed for testing ddrescue: 9 # - A kernel config fragment enables loop blk dev and device 10 # mapper dm-dust, which are used to simulate a failing storage 11 # block device. 12 # - dmraid user space package is needed to configure dm-dust 13 config = \ 14 """ 15 BR2_aarch64=y 16 BR2_TOOLCHAIN_EXTERNAL=y 17 BR2_TARGET_GENERIC_GETTY_PORT="ttyAMA0" 18 BR2_LINUX_KERNEL=y 19 BR2_LINUX_KERNEL_CUSTOM_VERSION=y 20 BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.1.15" 21 BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y 22 BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="board/qemu/aarch64-virt/linux.config" 23 BR2_LINUX_KERNEL_CONFIG_FRAGMENT_FILES="{}" 24 BR2_LINUX_KERNEL_NEEDS_HOST_OPENSSL=y 25 BR2_PACKAGE_DDRESCUE=y 26 BR2_PACKAGE_DMRAID=y 27 BR2_TARGET_ROOTFS_CPIO=y 28 BR2_TARGET_ROOTFS_CPIO_GZIP=y 29 # BR2_TARGET_ROOTFS_TAR is not set 30 """.format( 31 infra.filepath("tests/package/test_ddrescue/linux-ddrescue.fragment") 32 ) 33 34 def test_run(self): 35 img = os.path.join(self.builddir, "images", "rootfs.cpio.gz") 36 kern = os.path.join(self.builddir, "images", "Image") 37 self.emulator.boot(arch="aarch64", 38 kernel=kern, 39 kernel_cmdline=["console=ttyAMA0"], 40 options=["-M", "virt", "-cpu", "cortex-a57", "-m", "256M", "-initrd", img]) 41 self.emulator.login() 42 43 # Test variables: 44 dev_img = "/tmp/dev.img" 45 lo_dev = "/dev/loop0" 46 dm_dev_name = "dust0" 47 dm_dev = f"/dev/mapper/{dm_dev_name}" 48 ddrescue_img = "/tmp/ddrescue.img" 49 50 # Test the program can execute 51 self.assertRunOk("ddrescue --version") 52 53 # Create a 1MB file of zeroes for initial loopback block device 54 self.assertRunOk(f"dd if=/dev/zero of={dev_img} bs=1M count=1") 55 56 # Setup lookback block device 57 self.assertRunOk(f"losetup {lo_dev} {dev_img}") 58 59 # Create and setup dm-dust to simulate a failing block device 60 # The dev_img file is 1MB: 2048 blocks of 512 bytes each 61 self.assertRunOk(f"dmsetup create {dm_dev_name} --table '0 2048 dust {lo_dev} 0 512'") 62 63 # Add few bad blocks and enable I/O error emulation 64 for badblock in [30, 40, 50, 60]: 65 self.assertRunOk(f"dmsetup message {dm_dev_name} 0 addbadblock {badblock}") 66 self.assertRunOk(f"dmsetup message {dm_dev_name} 0 enable") 67 68 # Show device mapper status, to make debugging easier 69 self.assertRunOk(f"dmsetup status {dm_dev_name}") 70 71 # A normal 'dd' is expected to fail with I/O error 72 cmd = f"dd if={dm_dev} of=/dev/null bs=512" 73 _, exit_code = self.emulator.run(cmd) 74 self.assertNotEqual(exit_code, 0) 75 76 # Where a normal 'dd' fails, 'ddrescue' is expected to succeed 77 self.assertRunOk(f"ddrescue {dm_dev} {ddrescue_img}") 78 79 # ddrescue does not normaly write any output data when there 80 # is I/O error on the input. The intent is to preserve any 81 # data that could have been read in a previous pass. There is 82 # one exception, when the output is a non-existing regular 83 # file, ddrescue will initialize it with zeroes the first 84 # time. Since the original image file was also including 85 # zeroes, the recovered image is expected to be the same as 86 # the original one. See ddrescue manual: 87 # https://www.gnu.org/software/ddrescue/manual/ddrescue_manual.html#Introduction 88 # "Ddrescue does not write zeros to the output when it finds 89 # bad sectors in the input, and does not truncate the output 90 # file if not asked to." 91 # https://www.gnu.org/software/ddrescue/manual/ddrescue_manual.html#Algorithm 92 # "If the output file is a regular file created by ddrescue, 93 # the areas marked as bad-sector will contain zeros." 94 self.assertRunOk(f"cmp {dev_img} {ddrescue_img}") 95