Пример #1
0
    def shrink(self, silent=False):
        """Shrink the image.

        This is accomplished by shrinking the last file system of the
        image and then updating the partition table. The shrinked device is
        returned.

        ATTENTION: make sure umount is called before shrink
        """
        def get_fstype(partition):
            """Get file system type"""
            device = "%s%d" % (self.guestfs_device, partition['part_num'])
            return self.g.vfs_type(device)

        def is_extended(partition):
            """Returns True if the partition is an extended partition"""
            if self.meta['PARTITION_TABLE'] == 'msdos':
                mbr_id = self.g.part_get_mbr_id(self.guestfs_device,
                                                partition['part_num'])
                return mbr_id in (0x5, 0xf)
            return False

        def part_del(partnum):
            """Delete a partition"""
            self.g.part_del(self.guestfs_device, partnum)

        MB = 2**20

        if self.is_unsupported():
            if not silent:
                self.out.warn("Shrinking is disabled for unsupported images")
            return None

        sector_size = self.g.blockdev_getss(self.guestfs_device)

        last_part = None
        fstype = None
        while True:
            last_part = self._last_partition()
            fstype = get_fstype(last_part)

            if fstype == 'swap':
                self.meta['SWAP'] = "%d:%s" % \
                    (last_part['part_num'],
                     (last_part['part_size'] + MB - 1) // MB)
                part_del(last_part['part_num'])
                continue
            elif is_extended(last_part):
                part_del(last_part['part_num'])
                continue

            # Most disk manipulation programs leave 2048 sectors after the last
            # partition
            new_size = last_part['part_end'] + 1 + 2048 * sector_size
            self.size = min(self.size, new_size)
            break

        if not fstype:
            if not silent:
                self.out.warn("Unable to shrink partition: %s. Reason: "
                              "Could not detect file system." %
                              last_part['part_num'])
            return None

        if not re.match("ext[234]", fstype):
            if not silent:
                self.out.warn("Unable to shrink partition: %s. Reason: "
                              "Don't know how to shrink %s file systems." %
                              (last_part['part_num'], fstype))
            return None

        part_dev = "%s%d" % (self.guestfs_device, last_part['part_num'])
        out = self.g.tune2fs_l(part_dev)
        block_size = int(filter(lambda x: x[0] == 'Block size', out)[0][1])
        old_block_cnt = int(filter(lambda x: x[0] == 'Block count', out)[0][1])

        try:
            if self.check_guestfs_version(1, 15, 17) >= 0:
                self.g.e2fsck(part_dev, forceall=1)
            else:
                self.g.e2fsck_f(part_dev)
        except RuntimeError as e:
            # There is a bug in some versions of libguestfs and a RuntimeError
            # is thrown although the command has successfully corrected the
            # found file system errors.
            if str(e).find('***** FILE SYSTEM WAS MODIFIED *****') == -1:
                raise

        self.g.resize2fs_M(part_dev)

        out = self.g.tune2fs_l(part_dev)
        assert block_size == \
            int(filter(lambda x: x[0] == 'Block size', out)[0][1])
        block_cnt = int(filter(lambda x: x[0] == 'Block count', out)[0][1])

        # Add some extra space for the image to be able to run.
        block_cnt = min(old_block_cnt, block_cnt + 8388608 / block_size)

        start = last_part['part_start'] / sector_size
        end = start + (block_size * block_cnt) / sector_size - 1

        self._resize_partition(end)

        # Enlarge the underlying file system to consume the available space
        self.g.resize2fs(part_dev)

        new_size = (end + 1) * sector_size

        assert new_size <= self.size

        if self.meta['PARTITION_TABLE'] == 'gpt':
            with self.raw_device(readonly=False) as raw:
                ptable = GPTPartitionTable(raw)
                self.size = ptable.shrink(new_size, self.size)
        else:
            self.size = min(new_size + 2048 * sector_size, self.size)

        if not silent:
            self.out.success("Image size is %dMB" %
                             ((self.size + MB - 1) // MB))

        return part_dev