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.
1137 lines
36 KiB
1137 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 calculate.lib.utils.files import process, getProgPath
|
|
from calculate.lib.utils.tools import Sizes, classificate, traverse
|
|
from calculate.lib.utils.device import humanreadableSize
|
|
import re
|
|
|
|
_ = lambda x: x
|
|
from calculate.lib.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(object):
|
|
|
|
__metaclass__ = ABCMeta
|
|
|
|
class DeviceType(object):
|
|
Device = 0
|
|
RAID = 1
|
|
|
|
class VolumeType(object):
|
|
Generic = 0
|
|
Raid = 2
|
|
Fat32 = 3
|
|
Linux = 4
|
|
LinuxSwap = 5
|
|
Ntfs = 6
|
|
BiosBoot = 7
|
|
Efi = 8
|
|
Lvm = 9
|
|
Extended = 10
|
|
|
|
class Purpose(object):
|
|
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(object):
|
|
__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(object):
|
|
__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(object):
|
|
__metaclass__ = ABCMeta
|
|
|
|
@abstractmethod
|
|
def create_lvm(self):
|
|
pass
|
|
|
|
@abstractmethod
|
|
def create_logical_volume(self, name, size):
|
|
pass
|
|
|
|
@abstractmethod
|
|
def write(self):
|
|
pass
|
|
|
|
|
|
class BlockDisk(object):
|
|
pass
|
|
|
|
class VirtualDiskError(Exception):
|
|
pass
|
|
|
|
class SizeableDisk(object):
|
|
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(object):
|
|
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(VirtualDisk, self).__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(VirtualDosDisk, self).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(object):
|
|
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(VirtualLvm, self).__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(Fdisk, self).__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("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(LvmVolumeGroup, self).__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(object):
|
|
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(object):
|
|
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(object):
|
|
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(object):
|
|
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(GptPosBootRules, self).__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(object):
|
|
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(object):
|
|
__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(object):
|
|
__metaclass__ = ABCMeta
|
|
|
|
@abstractmethod
|
|
def get_device_type(self, path=None, name=None):
|
|
pass
|
|
|
|
class PurposedDosBuilder(DosBuilder):
|
|
def change_purpose(self, purpose):
|
|
super(PurposedDosBuilder, self).change_purpose(purpose)
|
|
self.dosdisk.change_purpose(purpose)
|
|
|
|
class PurposedGptBuilder(GptBuilder):
|
|
def change_purpose(self, purpose):
|
|
super(PurposedGptBuilder, self).change_purpose(purpose)
|
|
self.gptdisk.change_purpose(purpose)
|
|
|
|
class PurposedLvmBuilder(LvmBuilder):
|
|
def change_purpose(self, purpose):
|
|
super(PurposedLvmBuilder, self).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(object):
|
|
class PartitionTable(object):
|
|
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 = 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)
|