def test_get_partition_alignment_msdos(monkeypatch): generateStandardMock(monkeypatch, b"""Disk /dev/loop0: 7516 MB, 7516192768 bytes 255 heads, 63 sectors/track, 913 cylinders, total 14680064 sectors Units = sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disk identifier: 0x524f0bf8 Device Boot Start End Blocks Id System """, b"", 0, "msdos") manager = device.DeviceManager("msdos.img") result = manager.get_partition_alignment() # Basically, this is testing if the physical partition is different then the logical partition. If so, then sectors will need to be aligned properly. That isn't the case here. assert result == 1
def test_get_partition_table_type_gpt(monkeypatch): generateStandardMock(monkeypatch, b"""Model: (file) Disk /media/Data/Documents/Programming/testing-super/gpt.img: 524MB Sector size (logical/physical): 512B/512B Partition Table: gpt Number Start End Size File system Name Flags 1 17.4kB 50.0MB 50.0MB test 2 50.3MB 74.4MB 24.1MB cool 3 74.4MB 200MB 126MB nice 4 200MB 350MB 150MB great 5 350MB 500MB 150MB sweet 6 500MB 524MB 24.1MB""", b"", 0, None) manager = device.DeviceManager("/dev/sda") result = manager.get_partition_table_type() assert "gpt" == result
def test_get_partition_code_newer_format(monkeypatch): generateStandardMock(monkeypatch, b"""Disk /dev/sdb: 5 GiB, 5368709120 bytes, 10485760 sectors Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disklabel type: dos Disk identifier: 0x0ee18f9a Device Boot Start End Sectors Size Id Type /dev/sdb1 * 2048 350300 348253 170M 83 Linux /dev/sdb2 352347 10485759 10133413 4,9G 5 Extended /dev/sdb5 352349 10485759 10133411 4,9G 8e Linux LVM""", b"", 0, "msdos") manager = device.DeviceManager("/dev/sdb") result = manager.get_partition_code(5) assert "8e" == result
def test_get_sector_alignment_number(monkeypatch): generateStandardMock(monkeypatch, b"""Disk /dev/loop1: 512000 sectors, 250.0 MiB Logical sector size: 512 bytes Disk identifier (GUID): 4EB07926-DFE2-4D18-A2F4-75FB23616F71 Partition table holds up to 128 entries First usable sector is 34, last usable sector is 511966 Partitions will be aligned on 2048-sector boundaries Total free space is 5558 sectors (2.7 MiB) Number Start (sector) End (sector) Size Code Name 1 2048 309247 150.0 MiB 8300 Linux filesystem 2 309248 508422 97.3 MiB 8300 Linux filesystem """, b"", 0) manager = device.DeviceManager("gpt.img") result = manager.get_partition_alignment() assert result == 2048
def test_get_partition_table_type_mbr(monkeypatch): generateStandardMock(monkeypatch, b"""Model: (file) Disk /media/Data/Documents/Programming/testing-super/mbr.img: 524MB Sector size (logical/physical): 512B/512B Partition Table: msdos Number Start End Size Type File system Flags 1 1049kB 211MB 210MB primary 2 211MB 368MB 157MB primary 3 368MB 419MB 51.4MB primary 4 419MB 524MB 105MB primary """, b"", 0, None) manager = device.DeviceManager("mbr.img") result = manager.get_partition_table_type() assert result == "msdos"
def test_get_partition_table_type_unsupported(monkeypatch): generateStandardMock(monkeypatch, b"""Model: (file) Disk /media/Data/Documents/Programming/testing-super/mbr.img: 524MB Sector size (logical/physical): 512B/512B Partition Table: blah Number Start End Size Type File system Flags 1 1049kB 211MB 210MB primary 2 211MB 368MB 157MB primary 3 368MB 419MB 51.4MB primary 4 419MB 524MB 105MB primary """, b"", 0, None) manager = device.DeviceManager("blah.img") with pytest.raises(UnsupportedDeviceError) as execinfo: manager.get_partition_table_type() assert "blah" in str(execinfo)
def test_get_partition_code_mbr_invalid_value_passed(monkeypatch): generateStandardMock(monkeypatch, b"""Disk /dev/loop0: 524 MB, 524288000 bytes 255 heads, 63 sectors/track, 63 cylinders, total 1024000 sectors Units = sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disk identifier: 0x01517e72 Device Boot Start End Blocks Id System /dev/loop0p1 * 2048 411647 204800 83 Linux /dev/loop0p2 411648 718847 153600 83 Linux /dev/loop0p3 * 718848 819199 50176 83 Linux /dev/loop0p4 819200 1023999 102400 83 Linux """, b"", 0, "msdos") with pytest.raises(ValueError) as execinfo: manager = device.DeviceManager("/dev/loop0", "{0}p{1}") manager.get_partition_code(5)
def test_partition_code(monkeypatch): generateStandardMock(monkeypatch, b"""Disk /dev/nbd0: 16777216 sectors, 8.0 GiB Logical sector size: 512 bytes Disk identifier (GUID): 13E1C95B-5AC6-412B-930B-8F119760B86E Partition table holds up to 128 entries First usable sector is 34, last usable sector is 16777182 Partitions will be aligned on 2048-sector boundaries Total free space is 4029 sectors (2.0 MiB) Number Start (sector) End (sector) Size Code Name 1 976896 11718655 5.1 GiB 8300 2 11718656 14452735 1.3 GiB 8300 3 14452736 16775167 1.1 GiB 8200 4 2048 976895 476.0 MiB EF02 """, b"", 0) manager = device.DeviceManager("gpt.img") result = manager.get_partition_code(3) assert "8200" == result
def test_get_drive_empty_space(monkeypatch): generateStandardMock(monkeypatch, b"""Disk gpt.img: 1024000 sectors, 500.0 MiB Logical sector size: 512 bytes Disk identifier (GUID): 6FCE9962-D7B0-4BF3-B7BC-5E5CE8A5B0B0 Partition table holds up to 128 entries First usable sector is 34, last usable sector is 1023966 Partitions will be aligned on 2-sector boundaries Total free space is 647 sectors (323.5 KiB) Number Start (sector) End (sector) Size Code Name 1 34 97656 47.7 MiB 8300 test 2 98304 145407 23.0 MiB 8300 cool 3 145408 391167 120.0 MiB 8300 nice 4 391168 684031 143.0 MiB 8300 great 5 684032 976895 143.0 MiB 8300 sweet 6 976896 1023966 23.0 MiB 8300 """, b"", 0) manager = device.DeviceManager("gpt.img") result = manager.get_empty_space() assert result == 34
def test_get_partition_size(monkeypatch): generateStandardMock(monkeypatch, b"""Disk /dev/loop0: 1024000 sectors, 500.0 MiB Logical sector size: 512 bytes Disk identifier (GUID): 4EB07926-DFE2-4D18-A2F4-75FB23616F71 Partition table holds up to 128 entries First usable sector is 34, last usable sector is 1023966 Partitions will be aligned on 2048-sector boundaries Total free space is 2014 sectors (1007.0 KiB) Number Start (sector) End (sector) Size Code Name 1 2048 309247 150.0 MiB 8300 Linux filesystem 2 309248 821247 250.0 MiB 8300 Linux filesystem 3 821248 972799 74.0 MiB 8300 Linux filesystem 4 972800 1019903 23.0 MiB 8300 Linux filesystem 5 1019904 1023966 2.0 MiB 8300 Linux filesystem """, None, 0) monkeypatch.setattr( "weresync.device.DeviceManager.get_partition_table_type", lambda x: "gpt") manager = device.DeviceManager("gpt.img") result = manager.get_partition_size(5) assert 4062 == result
def test_mount_partition(monkeypatch): generateStandardMock(monkeypatch, b"", None, 0) manager = device.DeviceManager("/dev/sda") manager.mount_partition(3, "/mnt")
def test_get_partition_size_mbr(monkeypatch): generateStandardMock(monkeypatch, b"204800", b"", 0, "msdos") manager = device.DeviceManager("mbr.img") result = manager.get_partition_size(4) assert result == 204800
def test_get_partition_file_system_empty_return(monkeypatch): generateStandardMock(monkeypatch, b"", b"", 0) manager = device.DeviceManager("gpt.img") result = manager.get_partition_file_system(4) assert result == None
def test_get_partition_file_system_unsupported_type(monkeypatch): generateStandardMock(monkeypatch, b"completelyimpossiblefilesystemtype", b"", 0) manager = device.DeviceManager("gpt.img") result = manager.get_partition_file_system(4) assert result == None
def test_get_partition_alignment_msdos_non_zero_return_code(monkeypatch): generateStandardMock(monkeypatch, b"", b"Error msdos", 1, "msdos") manager = device.DeviceManager("msdos.img") with pytest.raises(DeviceError) as exceinfo: result = manager.get_partition_alignment()
def test_unmount_partition(monkeypatch): generateStandardMock(monkeypatch, b"", b"", 0) manager = device.DeviceManager("/dev/sda") manager.unmount_partition(5)
def test_get_sector_alignment_number_invalid_return(monkeypatch): generateStandardMock(monkeypatch, b"No alignment", b"", 0) manager = device.DeviceManager("gpt.img") with pytest.raises(DeviceError) as execinfo: result = manager.get_partition_alignment()
def test_get_drive_size_bytes(monkeypatch): generateStandardMock(monkeypatch, b"190", b"", 0) manager = device.DeviceManager("/dev/sda") result = manager.get_drive_size_bytes() assert 190 == result
def test_mount_point_no_mount_point(monkeypatch): generateStandardMock(monkeypatch, b"", None, 1) # findmnt returns 1 when there is no mount point manager = device.DeviceManager("/dev/sda") result = manager.mount_point(5) assert result == None
def test_get_partitions_no_partitions(monkeypatch): generateStandardMock(monkeypatch, b"Nope\nvery\nvery\nbad\ndata", None, 0) manager = device.DeviceManager("/dev/sda") result = manager.get_partitions() assert result == []
def copy_drive(source, target, check_if_valid_and_copy=False, source_part_mask="{0}{1}", target_part_mask="{0}{1}", excluded_partitions=[], ignore_copy_failures=True, root_partition=None, boot_partition=None, efi_partition=None, mount_points=None, rsync_args=device.DEFAULT_RSYNC_ARGS, lvm_source=None, lvm_target=None, bootloader="uuid_copy", part_callback=None, copy_callback=None, boot_callback=None): """Uses a DeviceCopier to clone the source drive to the target drive. **Note:** if using LVM, any uses of "partition" in the documentation actually refer to logical volumes. It is recommended to set ``check_if_valid_and_copy`` to True if the the two drives are not the same size with the same partitions. If either source or target ends in ".img" copy_drives will assume it is an image file, and mount if accordingly. :param source: The drive identifier ("/dev/sda" or the like) of the source drive. :param target: The drive identifier ("/dev/sda" or the like) of the target drive. :param check_if_valid=False: If true, the function checks if the target drive is compatible to receive the source drive's data. If it is not, erase the target drive and make a proper partition table. Defaults to False. :param source_part_mask: A string to be passed to the "format" method that expects to arguments, the drive name and the partition number. Applied to the source drive. Defaults to "{0}{1}". :param target_part_mask: Same as source_part_mask, but applied to target drive. Defaults to "{0}{1}" :param excluded_partitions: Partitions to not copy or test for boot capability. :param ignore_copy_failures: If True, errors during copying will be ignored and copying will continue. It is recommended that this be left to true, because errors frequently occur with swap partitions or other strange partitions. :param root_partition: If not None, this is an int that determines which partition grub should be installed to. Defaults to None. :param boot_partition: If not None, this is an int that represents the partition to mount at /boot when installing grub. :param efi_partition: If not None, this is an int that represents the partition to mount at /boot/efi when installing grub. :param mount_points: Expects a tuple containing two strings pointing to the directories where partitions should be mounted in case of testing. If None, the function will generate two random directories in the /tmp folder. Defaults to None. :param lvm: the Logical Volume Group to copy to the new drive. :param part_callback: a function that can be called to pass the progress of the partition function. The function should expect on float between 0 and 1, a negative value denoting an error, or a boolean True to indicate the progress is indeterminate. :param copy_callback: a function that can be called to pass the progress of copying partitions. The function should expect two arguments: an integer showing partition number and a float showing progress between 0 and 1 :param boot_callback: a function that can be called to pass the progress of making the clone bootable. The function should expect one argument: a boolean indicating whether or not the process has finished. :raises DeviceError: If there is an error reading data from one device or another. :raises CopyError: If there is an error copying the data between the two devices. :returns: True on success and an error message or exception on failure. """ try: source_loop = None target_loop = None if source.endswith(".img"): source_loop = mount_loop_device(source) source = source_loop source_part_mask = "{0}p{1}" if target.endswith(".img"): target_loop = mount_loop_device(target) target = target_loop target_part_mask = "{0}p{1}" LOGGER.warning( "Right now, WereSync does not properly install bootloaders on " "image files. You will have to handle that yourself if you " "want your image to be bootable.") source_manager = device.DeviceManager(source, source_part_mask) target_manager = device.DeviceManager(target, target_part_mask) try: target_manager.get_partition_table_type() except (DeviceError, UnsupportedDeviceError) as ex: # Since we're erasing the target drive anyway, we can just create # a new disk label proc = subprocess.Popen(["sgdisk", "-o", target_manager.device]) proc.communicate() copier = device.DeviceCopier(source_manager, target_manager) partitions_remade = False if check_if_valid_and_copy: copy_partitions(copier, part_callback) partitions_remade = True if lvm_source is not None: create_new_vg_if_not_exists(lvm_source, lvm_source + "-copy", target_manager) lvm_source = device.LVMDeviceManager(lvm_source) lvm_target = device.LVMDeviceManager(lvm_source.device + "-copy") copier.lvm_source = lvm_source copier.lvm_target = lvm_target if partitions_remade and check_if_valid_and_copy: copy_partitions(copier, part_callback, lvm=True) if mount_points is None or len( mount_points) < 2 or mount_points[0] == mount_points[1]: source_dir = "/tmp/" + str(random.randint(0, 100000)) target_dir = "/tmp/" + str(random.randint(-100000, -1)) os.makedirs(source_dir, exist_ok=True) os.makedirs(target_dir, exist_ok=True) mount_points = (source_dir, target_dir) print(_("Beginning to copy files.")) copier.copy_files(mount_points[0], mount_points[1], excluded_partitions, ignore_copy_failures, rsync_args, callback=copy_callback) print(_("Finished copying files.")) print(_("Making bootable")) try: copier.make_bootable(bootloader, mount_points[0], mount_points[1], excluded_partitions, root_partition, boot_partition, efi_partition, boot_callback) except DeviceError as ex: print(_("Error making drive bootable. All files should be fine.")) return ex print(_("All done, enjoy your drive!")) return True finally: def delete_loop(loop_name): subprocess.call(["losetup", "-d", loop_name]) if source_loop is not None: delete_loop(source_loop) if target_loop is not None: delete_loop(target_loop)