You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
calculate-utils-3-lib/pym/calculate/lib/utils/partition.py

1140 lines
36 KiB

# -*- 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, getRunCommands
from .tools import Sizes, classificate, traverse
from .device import humanreadableSize
import re
import os
_ = 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 _kill_pvscan_processes(self):
"""
Прибить lvm pvscan процессы, потому что они тормозят выполнением pvcreate
"""
pvscan_pids = (int(pid) for pid, cmd in getRunCommands(withpid=True)
if "pvscan" in cmd)
for pvscan_pid in pvscan_pids:
os.kill(pvscan_pid, 9)
def create_physical_volumes(self):
self.wipefs()
self._kill_pvscan_processes()
p = process(self.lvm_cmd, "pvcreate", "-y", "-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, "-y", "-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)