# -*- coding: utf-8 -*- # Copyright 2017 Mir Calculate. http://www.calculate-linux.org # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import sys from abc import ABCMeta, abstractmethod from .files import process, getProgPath from .tools import Sizes, classificate, traverse from .device import humanreadableSize import re _ = lambda x: x from ..cl_lang import setLocalTranslate setLocalTranslate('cl_lib3', sys.modules[__name__]) def round_disk_size(size): return size - (size % (4 * Sizes.M)) class VolumesError(Exception): pass class VolumesBuilder(metaclass=ABCMeta): class DeviceType(): Device = 0 RAID = 1 class VolumeType(): Generic = 0 Raid = 2 Fat32 = 3 Linux = 4 LinuxSwap = 5 Ntfs = 6 BiosBoot = 7 Efi = 8 Lvm = 9 Extended = 10 class Purpose(): Undefined = 100 EfiBoot = 101 BiosBoot = 102 MainRoot = 103 UpdateRoot = 104 Swap = 105 Calculate = 106 Lvm = 107 Windows = 108 Raid = 109 Linux = 110 purpose_type = { Purpose.Undefined: VolumeType.Generic, Purpose.Lvm: VolumeType.Lvm, Purpose.BiosBoot: VolumeType.BiosBoot, Purpose.EfiBoot: VolumeType.Efi, Purpose.MainRoot: VolumeType.Linux, Purpose.UpdateRoot: VolumeType.Linux, Purpose.Calculate: VolumeType.Linux, Purpose.Linux: VolumeType.Linux, Purpose.Swap: VolumeType.LinuxSwap, Purpose.Windows: VolumeType.Ntfs, Purpose.Raid: VolumeType.Raid } SizeAllFree = "allfree" @abstractmethod def create(self): """ Пересоздать таблицу разделов """ pass @abstractmethod def create_volume(self, size, name, purpose=0): pass @abstractmethod def create_last_volume(self, size, name, purpose=0): pass @abstractmethod def set_minimal_volume_size(self, size): pass @abstractmethod def finish(self): pass class DosDisk(metaclass=ABCMeta): @abstractmethod def get_partition_name(self, num): pass @abstractmethod def create_table(self): pass @abstractmethod def create_primary_partition(self, size): pass @abstractmethod def create_logical_partition(self, size): pass @abstractmethod def create_extended_partition(self): pass @abstractmethod def change_partition_type(self, partid, partnum=None): pass @abstractmethod def write(self): pass class GptDisk(metaclass=ABCMeta): @abstractmethod def create_table(self): pass @abstractmethod def create_partition(self, size): pass @abstractmethod def change_partition_type(self, partid, partnum=None): pass @abstractmethod def write(self): pass @abstractmethod def get_partition_name(self, num): pass class LvmDisk(metaclass=ABCMeta): @abstractmethod def create_lvm(self): pass @abstractmethod def create_logical_volume(self, name, size): pass @abstractmethod def write(self): pass class BlockDisk(): pass class VirtualDiskError(Exception): pass class SizeableDisk(): AllFree = None header_size = 0 def __init__(self, size): self.disk_size = round_disk_size(size) self.free_size = self.disk_size - self.header_size def occupe_size(self, size): if size is not self.AllFree: if size < 0: self.free_size = size else: self.free_size -= size else: self.free_size = 0 def size(self, size): if size is not self.AllFree: if size < 0: return self.free_size + size else: return size else: return self.free_size def check_free_size(self): if self.free_size < 0: needbytes = abs(self.free_size) availbytes = self.disk_size all_usebytes = availbytes + needbytes all_use = humanreadableSize(all_usebytes) raise VirtualDiskError( _("There is not enough space on this device, " "to perform the autopartition need {need}").format( need=all_use)) class VirtualDisk(SizeableDisk, DosDisk, GptDisk): parttype = "" class Partition(): def __init__(self, dev=None, mount=None, part=None, partid=None, size=None): self.dev = dev self.mount = mount self.part = part self.partid = partid self.size = size self.format = "" def __init__(self, parent, dev, size): self.parent = parent self.dev = dev self.partitions = [] super().__init__(size) volume_types_map = {} def get_partition_name(self, num): if self.dev[-1].isdigit(): return ("%sp%%d" % self.dev) % num else: return ("%s%%d" % self.dev) % num def create_table(self): self.partitions = [] return True def create_partition(self, size, parttype=None): real_size = self.size(size) self.occupe_size(real_size) self.partitions.append( self.Partition( dev=self.get_partition_name(len(self.partitions)+1), mount="", part=parttype or self.parttype, size=real_size) ) return True def create_primary_partition(self, size): self.create_partition(size, parttype="primary") return True def create_logical_partition(self, size): self.create_partition(size, parttype="logical") return True def create_extended_partition(self): self.create_partition(0, parttype="extended") self._get_partition().size = self.free_size self._get_partition().partid = DosPartitionId.get( VolumesBuilder.VolumeType.Extended) return True def _get_partition(self, partnum=None): if partnum is None: return self.partitions[-1] else: return self.partitions[partnum] def change_partition_type(self, partid, partnum=None): part = self._get_partition(partnum) part.partid = partid def change_purpose(self, purpose, partnum=None): part = self._get_partition(partnum) part.mount = self.parent.get_mount_point(purpose) if purpose == VolumesBuilder.Purpose.EfiBoot: self.parent.set_efi(part.dev) part.format = self.parent.get_format(purpose) if purpose is VolumesBuilder.Purpose.UpdateRoot: self.parent.dev_from = part.dev def write(self): self.check_free_size() devtype = self.parent.get_device_type(self.dev) for partition in self.partitions: disk_type = "%s-partition" % devtype if partition.partid in ( GptPartitionId.get(VolumesBuilder.VolumeType.BiosBoot), DosPartitionId.get(VolumesBuilder.VolumeType.Extended), DosPartitionId.get(VolumesBuilder.VolumeType.Lvm), GptPartitionId.get(VolumesBuilder.VolumeType.Lvm), ): if partition.partid == GptPartitionId.get( VolumesBuilder.VolumeType.BiosBoot): self.parent.set_mbr(self.dev) elif partition.partid in ( DosPartitionId.get(VolumesBuilder.VolumeType.Lvm), GptPartitionId.get(VolumesBuilder.VolumeType.Lvm)): self.parent.lvm_size[partition.dev] = partition.size self.parent.lvm_disk_type[partition.dev] = disk_type continue self.parent.disk_dev.append(partition.dev) self.parent.disk_mount.append(partition.mount) self.parent.disk_part.append(partition.part) self.parent.disk_size.append(partition.size) self.parent.disk_type.append(disk_type) if partition.format is None: self.parent.disk_format.append(self.parent.default_format) else: self.parent.disk_format.append(partition.format) return True class VirtualDosDisk(VirtualDisk, DosDisk): parttype = "dos" header_size = Sizes().M def write(self): res = super().write() self.parent.set_mbr(self.dev) return res class VirtualGptDisk(VirtualDisk, GptDisk): parttype = "gpt" header_size = Sizes().M + Sizes().Sector * 34 class VirtualLvm(SizeableDisk, LvmDisk): class Volume(): def __init__(self, dev=None, mount=None, size=None): self.dev = dev self.mount = mount self.size = size self.format = "" def __init__(self, parent, devices, vgname, vgsize, pesize=None): self.parent = parent self.devices = devices self.disk_size = vgsize self.vgname = vgname self.volumes = [] super().__init__(vgsize) volume_types_map = {} def get_partition_name(self, name): return "/dev/%s/%s" % (self.vgname, name) def create_lvm(self): self.volumes = [] return True def create_logical_volume(self, lvname, size): real_size = self.size(size) self.occupe_size(real_size) self.volumes.append( self.Volume( dev=self.get_partition_name(lvname), mount="", size=real_size) ) return True def _get_partition(self, name): if name is None: return self.volumes[-1] else: fullname = self.get_partition_name(name) for volume in self.volumes: if fullname == volume.dev: return volume raise VolumesError(_("Failed to find %s volume") % name) def change_purpose(self, purpose, partnum=None): part = self._get_partition(partnum) part.mount = self.parent.get_mount_point(purpose) part.format = self.parent.get_format(purpose) if purpose is VolumesBuilder.Purpose.UpdateRoot: self.parent.dev_from = part.dev def write(self): self.check_free_size() devtype = self.parent.get_device_type(self.devices[0]) for partition in self.volumes: self.parent.disk_dev.append(partition.dev) self.parent.disk_mount.append(partition.mount) self.parent.disk_part.append("") self.parent.disk_size.append(partition.size) self.parent.disk_type.append("%s-lvm" % devtype) self.parent.disk_format.append(partition.format) class Fdisk(SizeableDisk, metaclass=ABCMeta): def __init__(self, dev, disk_size): super().__init__(disk_size) self.fdisk_process = process(self.fdisk_cmd, dev) self.fdisk = [] self.dev = dev self.partnumber = 0 volume_types_map = {} @abstractmethod def create_table(self): pass @property def fdisk_cmd(self): cmd = "/sbin/fdisk" _fdisk_cmd = getProgPath(cmd) if not _fdisk_cmd: raise VolumesError(_("Command not found '%s'") % cmd) return _fdisk_cmd def _fdisksize(self, size): if size is not self.AllFree: if size < 0: bytesize = self.free_size + size else: bytesize = size return "+%dK" % (Sizes().to_K(bytesize)) else: return "" def change_partition_type(self, partid, partnum=None): if self.partnumber < 2: self.fdisk.append("t\n%s\n" % partid) else: if partnum is None: self.fdisk.append("t\n\n%s\n" % partid) else: self.fdisk.append("t\n%d\n%s\n" % (partnum, partid)) def search_errors(self, errors): return re.search(r"Value out of range[.]|: unknown command", errors) def write(self): self.fdisk_process.write("%sw\nq\n" % "".join(self.fdisk)) self.fdisk_process.success() self.update_kernel_devfs() return not self.search_errors(self.fdisk_process.readerr().strip()) def update_kernel_devfs(self): partprobe = getProgPath("/usr/sbin/partprobe") if partprobe: process(partprobe, self.dev).success() def get_partition_name(self, num): if self.dev[-1].isdigit(): return ("%sp%%d" % self.dev) % num else: return ("%s%%d" % self.dev) % num class DosFdisk(Fdisk, DosDisk): """ Fdisk для создания dos таблицы разделов """ header_size = Sizes().M def create_table(self): self.fdisk = [] self.partnumber = 0 self.fdisk.append("o\n") def create_primary_partition(self, size): end = self._fdisksize(size) self.occupe_size(size) self.fdisk.append("n\np\n\n\n%s\n" % end) self.partnumber += 1 def create_logical_partition(self, size): end = self._fdisksize(size) self.occupe_size(size) self.fdisk.append("n\n\n%s\n" % end) self.partnumber += 1 def create_extended_partition(self): self.fdisk.append("n\ne\n\n\n") self.partnumber += 1 class GptFdisk(Fdisk, GptDisk): """ Fdisk для создания dos таблицы разделов """ header_size = Sizes().M + Sizes().Sector * 34 def create_table(self): self.fdisk = [] self.fdisk.append("g\n") def create_partition(self, size): end = self._fdisksize(size) self.occupe_size(size) self.fdisk.append("n\n\n\n%s\n" % end) self.partnumber += 1 class LvmVolumeGroup(SizeableDisk, LvmDisk): def __init__(self, devices, vgname, vgsize, extsize=None): super().__init__(vgsize) self.devices = devices self.vgname = vgname self.vgsize = vgsize self.extsize = extsize @property def lvm_cmd(self): cmd = "/sbin/lvm" _lvm_cmd = getProgPath(cmd) if not _lvm_cmd: raise VolumesError(_("Command not found '%s'") % cmd) return _lvm_cmd def wipefs(self): cmd = "/sbin/wipefs" wipefs_cmd = getProgPath(cmd) if not wipefs_cmd: raise VolumesError(_("Command not found '%s'") % cmd) for dev in self.devices: process(wipefs_cmd, "-af", dev).success() def create_physical_volumes(self): self.wipefs() p = process(self.lvm_cmd, "pvcreate", "-ff", *self.devices) return p.success() def create_volumes_group(self): if self.extsize: p = process(self.lvm_cmd, "vgcreate", self.vgname, *(["-s%s" % self.extsize] + self.devices)) else: p = process(self.lvm_cmd, "vgcreate", self.vgname, *self.devices) return p.success() def create_lvm(self): return self.create_physical_volumes() and self.create_volumes_group() def create_logical_volume(self, lvname, size): size_param = self._lvmsize(size) self.occupe_size(size) return process(self.lvm_cmd, "lvcreate", size_param, self.vgname, "-n", lvname).success() def get_partition_name(self, name): return "/dev/%s/%s" % (self.vgname, name) def _lvmsize(self, size): if size is not self.AllFree: if size < 0: bytesize = self.free_size + size else: bytesize = size return "-L%sk" % (Sizes().to_K(bytesize)) else: return "-l100%FREE" def write(self): pass class DosPartitionId(): volume_types_map = { VolumesBuilder.VolumeType.Ntfs: "07", VolumesBuilder.VolumeType.Extended: "05", VolumesBuilder.VolumeType.Linux: "83", VolumesBuilder.VolumeType.LinuxSwap: "82", VolumesBuilder.VolumeType.Fat32: "0b", VolumesBuilder.VolumeType.Lvm: "8e", VolumesBuilder.VolumeType.Raid: "fd", VolumesBuilder.VolumeType.Generic: "83", } @classmethod def get(cls, _id, fallback=VolumesBuilder.VolumeType.Generic): return cls.volume_types_map.get( _id, cls.volume_types_map[fallback]) class GptPartitionId(): volume_types_map = { VolumesBuilder.VolumeType.Ntfs: "ebd0a0a2-b9e5-4433-87c0-68b6b72699c7", VolumesBuilder.VolumeType.Fat32: "ebd0a0a2-b9e5-4433-87c0-68b6b72699c7", VolumesBuilder.VolumeType.Linux: "0fc63daf-8483-4772-8e79-3d69d8477de4", VolumesBuilder.VolumeType.LinuxSwap: "0657fd6d-a4ab-43c4-84e5-0933c84b4f4f", VolumesBuilder.VolumeType.Raid: "a19d880f-05fc-4d3b-a006-743f0f84911e", VolumesBuilder.VolumeType.Lvm: "e6d6d379-f507-44c2-a23c-238f2a3df928", VolumesBuilder.VolumeType.Efi: "c12a7328-f81f-11d2-ba4b-00a0c93ec93b", VolumesBuilder.VolumeType.BiosBoot: "21686148-6449-6e6f-744e-656564454649", VolumesBuilder.VolumeType.Generic: "0fc63daf-8483-4772-8e79-3d69d8477de4", } @classmethod def get(cls, _id, fallback=VolumesBuilder.VolumeType.Generic): return cls.volume_types_map.get( _id, cls.volume_types_map[fallback]) class RoundableVolumesBuilder(VolumesBuilder): rounding = 0.05 free_size = 0 last_partition = False sizes_map = {} minimal = 0 def is_roundable_size(self, size): if (size is not VolumesBuilder.SizeAllFree and size and abs(size - self.free_size) < min(size * self.rounding, 110 * Sizes.M)): return True return False def refined_size(self, size): if (size is VolumesBuilder.SizeAllFree or size < 0) and self.minimal: check_size = self.minimal if size is not VolumesBuilder.SizeAllFree: check_size -= size if (check_size > self.free_size and not self.is_roundable_size(check_size)): return check_size if self.last_partition: if self.is_roundable_size(size): size = VolumesBuilder.SizeAllFree if size in self.sizes_map: return self.sizes_map[size] return size def create_last_volume(self, size, name, purpose=VolumesBuilder.Purpose.Undefined): self.last_partition = True return self.create_volume(size, name, purpose) def set_minimal_volume_size(self, size): self.minimal = size class LvmCalculator(): def __init__(self, parent): self.parent = parent self.size = 0 self.precreate_size = None def precreate(self, size, purpose): if purpose == VolumesBuilder.Purpose.Lvm: self.precreate_size = self.parent.free_size else: self.precreate_size = None def postcreate(self, size, purpose): if self.precreate_size and purpose == VolumesBuilder.Purpose.Lvm: self.size += self.precreate_size - self.parent.free_size self.precreate_size = None class DosBuilder(RoundableVolumesBuilder): def __init__(self, dosdisk): self.dosdisk = dosdisk self.sizes_map = {VolumesBuilder.SizeAllFree: dosdisk.AllFree} self.next_part_number = 1 self.lvm_data = LvmCalculator(self) def create(self): self.dosdisk.create_table() def create_volume(self, size, name, purpose=VolumesBuilder.Purpose.Undefined): self.lvm_data.precreate(size, purpose) if self.next_part_number < 4: self.create_primary_partition(size) else: if self.next_part_number == 4: self.create_extended_partition() self.next_part_number += 1 self.create_logical_partition(size) self.change_purpose(purpose) self.next_part_number += 1 self.lvm_data.postcreate(size, purpose) return self.dosdisk.get_partition_name(self.next_part_number - 1) @property def free_size(self): return self.dosdisk.free_size def change_purpose(self, purpose): parttype = VolumesBuilder.purpose_type.get( purpose, VolumesBuilder.purpose_type[VolumesBuilder.Purpose.Undefined]) volume_id = DosPartitionId.get(parttype) self.dosdisk.change_partition_type(volume_id) def create_primary_partition(self, size): self.dosdisk.create_primary_partition(self.refined_size(size)) def create_logical_partition(self, size): self.dosdisk.create_logical_partition(self.refined_size(size)) def create_extended_partition(self): self.dosdisk.create_extended_partition() def finish(self): if not self.dosdisk.write(): raise VolumesError(_("Failed to create DOS partition table")) class GptBuilder(RoundableVolumesBuilder): def __init__(self, gptdisk, table_rules): self.gptdisk = gptdisk self.table_rules = table_rules self.table_rules.parent = self self.sizes_map = {VolumesBuilder.SizeAllFree: self.gptdisk.AllFree} self.next_part_number = 1 self.lvm_data = LvmCalculator(self) def create(self): self.gptdisk.create_table() def create_volume(self, size, name, purpose=VolumesBuilder.Purpose.Undefined): return self.table_rules.create_volume(size, purpose) def get_last_partition_name(self): return self.gptdisk.get_partition_name(self.next_part_number - 1) @property def free_size(self): return self.gptdisk.free_size def create_partition(self, size, purpose): self.lvm_data.precreate(size, purpose) self.gptdisk.create_partition(self.refined_size(size)) self.lvm_data.postcreate(size, purpose) self.change_purpose(purpose) self.next_part_number += 1 def change_purpose(self, purpose): parttype = VolumesBuilder.purpose_type.get( purpose, VolumesBuilder.purpose_type[VolumesBuilder.Purpose.Undefined]) volume_id = GptPartitionId.get(parttype) self.gptdisk.change_partition_type(volume_id) def finish(self): self.table_rules.finish() if not self.gptdisk.write(): raise VolumesError(_("Failed to create GPT partition table")) class GptRules(): parent = None def finish(self): pass class GptOnlyRules(GptRules): def create_volume(self, size, purpose): self.parent.create_partition(size, purpose) return self.parent.get_last_partition_name() class GptFirstBootRules(GptRules): boot_purpose = VolumesBuilder.Purpose.BiosBoot def __init__(self, boot_size): self.boot_created = False self.boot_size = boot_size def create_boot(self, size=None): minimal = self.parent.minimal self.parent.minimal = 0 self.parent.create_partition(size or self.boot_size, self.boot_purpose) self.boot_created = True self.parent.minimal = minimal def create_volume(self, size, purpose): if not self.boot_created: if purpose != self.boot_purpose: self.create_boot() self.boot_created = True self.parent.create_partition(size, purpose) return self.parent.get_last_partition_name() def EfiGpt(obj, size=None): obj.boot_purpose = VolumesBuilder.Purpose.EfiBoot if size: obj.boot_size = size return obj def BiosBoot(obj, size=None): obj.boot_purpose = VolumesBuilder.Purpose.BiosBoot if size: obj.boot_size = size return obj class GptPosBootRules(GptFirstBootRules): def __init__(self, boot_size, part_pos=4): super().__init__(boot_size) self.part_pos = part_pos def create_volume(self, size, purpose): if purpose == self.boot_purpose: self.boot_created = True if not self.boot_created: if self.parent.next_part_number == self.part_pos: if purpose != self.boot_purpose: self.create_boot() elif self.parent.last_partition: if (size is VolumesBuilder.SizeAllFree or self.parent.is_roundable_size(size)): self.parent.create_partition(-self.boot_size, purpose) volname = self.parent.get_last_partition_name() self.create_boot(VolumesBuilder.SizeAllFree) else: self.parent.create_partition(size, purpose) volname = self.parent.get_last_partition_name() self.create_boot(self.boot_size) return volname self.parent.create_partition(size, purpose) return self.parent.get_last_partition_name() def finish(self): if not self.boot_created: self.create_boot(self.boot_size) class LvmBuilder(RoundableVolumesBuilder): """ Сборщик разделов для LVM """ def __init__(self, lvmdisk): self.lvmdisk = lvmdisk self.sizes_map = {VolumesBuilder.SizeAllFree: lvmdisk.AllFree} def create(self): if not self.lvmdisk.create_lvm(): raise VolumesError(_("Failed to create LVM")) @property def free_size(self): return self.lvmdisk.free_size def create_volume(self, size, name, purpose=VolumesBuilder.Purpose.Undefined): if not self.lvmdisk.create_logical_volume(name, self.refined_size(size)): raise VolumesError(_("Failed to create volume %s") % name) self.change_purpose(purpose) return self.lvmdisk.get_partition_name(name) def change_purpose(self, purpose): pass def finish(self): self.lvmdisk.write() class VolumesSet(): def __init__(self, builder): self.builder = builder self.params = [] def create_volume(self, size, name, purpose, minimal_size=None): self.params.append((size, name, purpose, minimal_size)) def __enter__(self): self.builder.create() return self def execute(self): for mark, data in classificate(self.params): size, name, purpose, minsize = data if minsize: self.builder.set_minimal_volume_size(minsize) if mark.last: self.builder.create_last_volume(size, name, purpose) else: self.builder.create_volume(size, name, purpose) def __exit__(self, exc_type, exc_val, exc_tb): self.execute() self.builder.finish() class BuilderFactory(metaclass=ABCMeta): @abstractmethod def createDosBuilder(self, device, size): pass @abstractmethod def createGptBuilder(self, device, size, table_rules): pass @abstractmethod def createLvmBuilder(self, devices, vgname, vgsize, extsize=None): pass class DiskFactory(BuilderFactory): def createDosBuilder(self, device, size): return DosBuilder(DosFdisk(device, size)) def createGptBuilder(self, device, size, table_rules): return GptBuilder(GptFdisk(device, size), table_rules) def createLvmBuilder(self, devices, vgname, vgsize, extsize=None): return LvmBuilder(LvmVolumeGroup(devices, vgname, vgsize, extsize=extsize)) class DeviceInfo(metaclass=ABCMeta): @abstractmethod def get_device_type(self, path=None, name=None): pass class PurposedDosBuilder(DosBuilder): def change_purpose(self, purpose): super().change_purpose(purpose) self.dosdisk.change_purpose(purpose) class PurposedGptBuilder(GptBuilder): def change_purpose(self, purpose): super().change_purpose(purpose) self.gptdisk.change_purpose(purpose) class PurposedLvmBuilder(LvmBuilder): def change_purpose(self, purpose): super().change_purpose(purpose) self.lvmdisk.change_purpose(purpose) class VariableFactory(BuilderFactory): def __init__(self, deviceinfo): self.disk_dev = [] self.disk_mount = [] self.disk_part = [] self.disk_size = [] self.disk_type = [] self.dev_from = "" self.disk_format = [] self.deviceinfo = deviceinfo self.lvm_size = {} self.lvm_disk_type = {} self.mbr = [] self.efi = [] self.purpose_mount = { VolumesBuilder.Purpose.Calculate: "/var/calculate", VolumesBuilder.Purpose.MainRoot: "/", VolumesBuilder.Purpose.Swap: "swap", } self.purpose_format = { VolumesBuilder.Purpose.Swap: "swap", VolumesBuilder.Purpose.EfiBoot: "vfat", VolumesBuilder.Purpose.Windows: "ntfs", VolumesBuilder.Purpose.UpdateRoot: "" } self.default_format = "ext4" self.pesize = None self.efinames = iter( traverse(("/boot/efi", ("/boot/efi%d" % x for x in range(2, 100))))) def get_mount_point(self, purpose): if purpose == VolumesBuilder.Purpose.EfiBoot: return next(self.efinames) return self.purpose_mount.get(purpose, '') def get_format(self, purpose): return self.purpose_format.get(purpose, self.default_format) def createDosBuilder(self, device, size): return PurposedDosBuilder(VirtualDosDisk(self, device, size)) def createGptBuilder(self, device, size, table_rules): return PurposedGptBuilder(VirtualGptDisk(self, device, size), table_rules) def createLvmBuilder(self, devices, vgname, vgsize, extsize=None): return PurposedLvmBuilder(VirtualLvm(self, devices, vgname, vgsize, pesize=self.pesize)) def get_device_type(self, name): if name in self.lvm_disk_type: return self.lvm_disk_type[name] return self.deviceinfo.get_device_type(name=name) def set_mbr(self, device): self.mbr.append(device) def set_efi(self, device): self.efi.append(device) class SchemeError(Exception): pass class DeviceSchemeError(SchemeError): pass class SchemeBuilder(): class PartitionTable(): GPT = 0 DOS = 1 def __init__(self): self.efi = False self.partition_table = self.PartitionTable.DOS self.lvm = False self.devices = [] self.swap_size = Sizes().G self.root_size = 10 * Sizes().G self.efi_size = 200 * Sizes().M self.biosboot_size = 50 * Sizes().M self.swap = False self.minimal_lvm_size = 1 * Sizes().G self.minimal_calculate_size = 500 * Sizes().M self.update = False self.calculate = False self.rootall = False self.rounding = 0.1 self.vgname = "calculate" self.extsize = None def add_device(self, device, _type, size): self.devices.append((device, _type, size)) def create_lvm_devices(self, volumes_factory): for dev, _type, size in self.devices: part_rules = GptFirstBootRules(self.biosboot_size) if self.efi: part_rules = EfiGpt(part_rules, size=self.efi_size) else: part_rules = BiosBoot(part_rules, size=self.biosboot_size) if _type == VolumesBuilder.DeviceType.RAID: yield dev, size else: if self.partition_table is SchemeBuilder.PartitionTable.DOS: builder = volumes_factory.createDosBuilder(dev, size) else: builder = volumes_factory.createGptBuilder( dev, size, part_rules) builder.create() builder.set_minimal_volume_size(self.minimal_lvm_size) devname = builder.create_last_volume( VolumesBuilder.SizeAllFree, "lvm", VolumesBuilder.Purpose.Lvm) builder.finish() devsize = builder.lvm_data.size yield devname, devsize def get_volume_builder(self, volumes_factory): if not self.devices: raise DeviceSchemeError( _("Please select devices for partitions changing")) if self.lvm: lvm_devices, lvm_sizes = list(zip( *self.create_lvm_devices(volumes_factory))) return volumes_factory.createLvmBuilder( list(lvm_devices), self.vgname, sum(lvm_sizes), extsize=self.extsize) else: if len(self.devices) > 1: raise DeviceSchemeError( _("You should use LVM to install on more that one device")) dev, devtype, size = self.devices[0] if self.partition_table is SchemeBuilder.PartitionTable.DOS: return volumes_factory.createDosBuilder(dev, size) else: if devtype is VolumesBuilder.DeviceType.RAID: return volumes_factory.createGptBuilder( dev, size, GptOnlyRules()) else: part_rules = GptPosBootRules("", 4) if self.efi: part_rules = EfiGpt(part_rules, size=self.efi_size) else: part_rules = BiosBoot(part_rules, size=self.biosboot_size) return volumes_factory.createGptBuilder(dev, size, part_rules) def process(self, volumes_factory): volume_builder = self.get_volume_builder(volumes_factory) volume_builder.rounding = self.rounding if self.rootall and (self.update or self.calculate): raise SchemeError( _("Root to all free size should not use with update partition " "or calculate partition")) with VolumesSet(volume_builder) as builder: if self.swap: builder.create_volume(self.swap_size, "swap", VolumesBuilder.Purpose.Swap) if self.rootall: builder.create_volume(VolumesBuilder.SizeAllFree, "system1", VolumesBuilder.Purpose.MainRoot, None) else: builder.create_volume(self.root_size, "system1", VolumesBuilder.Purpose.MainRoot, None) if self.update: builder.create_volume(self.root_size, "system2", VolumesBuilder.Purpose.UpdateRoot) if self.calculate: builder.create_volume(VolumesBuilder.SizeAllFree, "calculate", VolumesBuilder.Purpose.Calculate, self.minimal_calculate_size)