4d4c54671e
Redesign the ZFS configuration page with a two-column layout (settings on the left, disk list on the right), add a user-editable swap size field that defaults to actual RAM size, make the pool name always editable, and simplify pool type values to plain identifiers (stripe, mirror, raidz1/2/3). Consolidate duplicated next-button sensitivity logic into _update_next_button(), replace deprecated Gtk.STOCK icons with icon names, encrypt swap when GELI is enabled, and bump version to 0.4.
1311 lines
49 KiB
Python
1311 lines
49 KiB
Python
"""Disk partition management module for GhostBSD Install Station.
|
|
|
|
Provides disk partitioning functionality including detection, creation, deletion
|
|
of partitions for GPT/MBR schemes with ZFS support and manages the partition database.
|
|
"""
|
|
import re
|
|
from time import sleep
|
|
from subprocess import Popen, PIPE, STDOUT, call
|
|
from install_station.data import query, zfs_datasets, InstallationData
|
|
from install_station.system_calls import get_ram_size_mb
|
|
|
|
# Define required file paths
|
|
|
|
|
|
def get_disk_from_partition(part: str) -> str:
|
|
"""Extract the disk name from a partition identifier.
|
|
|
|
Args:
|
|
part (str): Partition identifier (e.g., 'ada0p1', 'ada0s1a')
|
|
|
|
Returns:
|
|
str: Disk name (e.g., 'ada0')
|
|
"""
|
|
if set("p") & set(part):
|
|
return part.partition('p')[0]
|
|
else:
|
|
return part.partition('s')[0]
|
|
|
|
|
|
def slice_number(part: str) -> int:
|
|
"""Extract the slice/partition number from a partition identifier.
|
|
|
|
Args:
|
|
part (str): Partition identifier (e.g., 'ada0p1', 'ada0s1')
|
|
|
|
Returns:
|
|
int: Slice/partition number
|
|
"""
|
|
if set("p") & set(part):
|
|
return int(part.partition('p')[2])
|
|
else:
|
|
return int(part.partition('s')[2])
|
|
|
|
|
|
def find_next_partition(partition_name: str, partition_list: list[str]) -> str:
|
|
"""Find the next available partition name with sequential numbering.
|
|
|
|
Args:
|
|
partition_name (str): Base partition name (e.g., 'freespace', 'ada0p')
|
|
partition_list (list): List of existing partition names
|
|
|
|
Returns:
|
|
str: Next available partition name (e.g., 'freespace1', 'ada0p2')
|
|
"""
|
|
for num in range(1, 10000):
|
|
if f'{partition_name}{num}' not in partition_list:
|
|
return f'{partition_name}{num}'
|
|
|
|
|
|
def disk_list() -> list[str]:
|
|
"""Get a list of available disk devices on the system.
|
|
|
|
Queries the FreeBSD kernel for available disks and filters out
|
|
optical drives (CD/DVD devices).
|
|
|
|
Returns:
|
|
list: Sorted list of disk device names (e.g., ['ada0', 'ada1'])
|
|
"""
|
|
disk_popen = Popen(
|
|
'sysctl -n kern.disks',
|
|
shell=True,
|
|
stdin=PIPE,
|
|
stdout=PIPE,
|
|
universal_newlines=True,
|
|
close_fds=True
|
|
)
|
|
disks = disk_popen.stdout.read()
|
|
cleaned_disk = re.sub(r'acd[0-9]*|cd[0-9]*|scd[0-9]*', '', disks)
|
|
return sorted(cleaned_disk.split())
|
|
|
|
|
|
def device_model(disk: str) -> str:
|
|
"""Get the model description of a disk device.
|
|
|
|
Args:
|
|
disk (str): Disk device name (e.g., 'ada0')
|
|
|
|
Returns:
|
|
str: Device model description
|
|
"""
|
|
device_popen = Popen(
|
|
f"diskinfo -v {disk} 2>/dev/null | grep 'Disk descr' | cut -d '#' -f1 | tr -d '\t'",
|
|
shell=True,
|
|
stdin=PIPE,
|
|
stdout=PIPE,
|
|
universal_newlines=True,
|
|
close_fds=True
|
|
)
|
|
return device_popen.stdout.read().strip()
|
|
|
|
|
|
def disk_size(disk: str) -> str:
|
|
"""Get the size of a disk device.
|
|
|
|
Args:
|
|
disk (str): Disk device name (e.g., 'ada0')
|
|
|
|
Returns:
|
|
str: Disk size information
|
|
"""
|
|
disk_size_output = Popen(
|
|
f"{query}/disk-info.sh {disk}",
|
|
shell=True,
|
|
stdin=PIPE,
|
|
stdout=PIPE,
|
|
universal_newlines=True,
|
|
stderr=STDOUT,
|
|
close_fds=True
|
|
)
|
|
return disk_size_output.stdout.readlines()[0].rstrip()
|
|
|
|
|
|
def get_scheme(disk: str) -> str:
|
|
"""Detect the partition scheme of a disk device.
|
|
|
|
Args:
|
|
disk (str): Disk device name (e.g., 'ada0')
|
|
|
|
Returns:
|
|
str: Partition scheme ('GPT', 'MBR', or empty if none)
|
|
"""
|
|
scheme_output = Popen(
|
|
f"{query}/detect-scheme.sh {disk}",
|
|
shell=True,
|
|
stdin=PIPE,
|
|
stdout=PIPE,
|
|
universal_newlines=True,
|
|
stderr=STDOUT,
|
|
close_fds=True
|
|
)
|
|
return scheme_output.stdout.readlines()[0].rstrip()
|
|
|
|
|
|
class DiskPartition:
|
|
"""Main class for disk partition detection and database management.
|
|
|
|
This class provides methods to scan disk devices, detect existing partitions,
|
|
and maintain an in-memory database of partition information for both GPT and
|
|
MBR partition schemes.
|
|
|
|
Attributes:
|
|
disk_database (dict): In-memory database of disk and partition information
|
|
query_partition (str): Path to disk partition query script
|
|
"""
|
|
disk_database: dict = {}
|
|
|
|
query_partition = f'{query}/disk-part.sh'
|
|
|
|
@classmethod
|
|
def mbr_partition_slice_db(cls, disk):
|
|
"""Create database of MBR slices and their partitions.
|
|
|
|
Args:
|
|
disk (str): Disk device name (e.g., 'ada0')
|
|
|
|
Returns:
|
|
dict: Database of slices with their partition information
|
|
"""
|
|
partition_output = Popen(
|
|
f'{cls.query_partition} {disk}',
|
|
shell=True,
|
|
stdin=PIPE,
|
|
stdout=PIPE,
|
|
universal_newlines=True
|
|
)
|
|
slice_db = {}
|
|
free_num = 1
|
|
for line in partition_output.stdout:
|
|
info = line.strip().split()
|
|
slice_name = info[0]
|
|
if 'freespace' in line:
|
|
slice_name = f'freespace{free_num}'
|
|
free_num += 1
|
|
part_db = cls.mbr_partition_db(info[0])
|
|
part_list = [] if part_db is None else list(part_db.keys())
|
|
partitions = {
|
|
'name': slice_name,
|
|
'size': info[1].partition('M')[0],
|
|
'mount-point': '',
|
|
'file-system': info[2],
|
|
'stat': None,
|
|
'partitions': part_db,
|
|
'partition-list': part_list
|
|
}
|
|
slice_db[slice_name] = partitions
|
|
return slice_db
|
|
|
|
@classmethod
|
|
def mbr_partition_db(cls, partition_slice):
|
|
"""Create database of partitions within an MBR slice.
|
|
|
|
Args:
|
|
partition_slice (str): Slice identifier (e.g., 'ada0s1')
|
|
|
|
Returns:
|
|
dict or None: Database of partitions within the slice, or None for freespace
|
|
"""
|
|
if 'freespace' in partition_slice:
|
|
return None
|
|
else:
|
|
slice_output = Popen(
|
|
f'{query}/disk-label.sh {partition_slice}',
|
|
shell=True,
|
|
stdin=PIPE,
|
|
stdout=PIPE,
|
|
universal_newlines=True
|
|
)
|
|
partition_db = {}
|
|
alph = ord('a')
|
|
free_num = 1
|
|
for line in slice_output.stdout:
|
|
info = line.strip().split()
|
|
if 'freespace' in line:
|
|
partition_name = f'freespace{free_num}'
|
|
free_num += 1
|
|
else:
|
|
letter = chr(alph)
|
|
partition_name = f'{partition_slice}{letter}'
|
|
alph += 1
|
|
partitions = {
|
|
'name': partition_name,
|
|
'size': info[0].partition('M')[0],
|
|
'mount-point': '',
|
|
'file-system': info[2],
|
|
'stat': None,
|
|
}
|
|
partition_db[partition_name] = partitions
|
|
if not partition_db:
|
|
return None
|
|
return partition_db
|
|
|
|
@classmethod
|
|
def gpt_partition_db(cls, disk):
|
|
"""Create database of GPT partitions on a disk.
|
|
|
|
Args:
|
|
disk (str): Disk device name (e.g., 'ada0')
|
|
|
|
Returns:
|
|
dict: Database of GPT partitions
|
|
"""
|
|
partition_output = Popen(
|
|
f'{cls.query_partition} {disk}',
|
|
shell=True,
|
|
stdin=PIPE,
|
|
stdout=PIPE,
|
|
universal_newlines=True
|
|
)
|
|
partition_db = {}
|
|
free_num = 1
|
|
for line in partition_output.stdout:
|
|
info = line.strip().split()
|
|
slice_name = info[0]
|
|
if 'freespace' in line:
|
|
slice_name = f'freespace{free_num}'
|
|
free_num += 1
|
|
partitions = {
|
|
'name': info[0],
|
|
'size': info[1].partition('M')[0],
|
|
'mount-point': '',
|
|
'file-system': info[2],
|
|
'stat': None,
|
|
'partitions': {},
|
|
'partition-list': []
|
|
}
|
|
partition_db[slice_name] = partitions
|
|
return partition_db
|
|
|
|
@classmethod
|
|
def create_partition_database(cls):
|
|
"""Scan all disks and create comprehensive partition database.
|
|
|
|
This method queries all available disks, detects their partition schemes,
|
|
and builds a complete database of disk and partition information.
|
|
"""
|
|
# if os.path.exists(disk_db_file):
|
|
# os.remove(disk_db_file)
|
|
# drives_database = open(disk_db_file, 'wb')
|
|
disk_db = {}
|
|
for disk in disk_list():
|
|
disk_info_db = {}
|
|
disk_info_db.setdefault('scheme', get_scheme(disk))
|
|
if disk_info_db['scheme'] == "GPT":
|
|
part_db = cls.gpt_partition_db(disk)
|
|
elif disk_info_db['scheme'] == "MBR":
|
|
part_db = cls.mbr_partition_slice_db(disk)
|
|
else:
|
|
disk_info_db['scheme'] = None
|
|
part_db = {}
|
|
part_list = [] if part_db is None else list(part_db.keys())
|
|
disk_info_db['size'] = disk_size(disk)
|
|
disk_info_db['device_model'] = device_model(disk)
|
|
disk_info_db['partitions'] = part_db
|
|
disk_info_db['partition-list'] = part_list
|
|
disk_info_db['stat'] = None
|
|
disk_db[disk] = disk_info_db
|
|
cls.disk_database = disk_db
|
|
|
|
@classmethod
|
|
def get_disk_database(cls):
|
|
"""Get the current disk database.
|
|
|
|
Returns:
|
|
dict: Current disk and partition database
|
|
"""
|
|
return cls.disk_database.copy()
|
|
|
|
@classmethod
|
|
def how_partition(cls, disk):
|
|
"""Get the number of partitions on a disk.
|
|
|
|
Args:
|
|
disk (str): Disk device name
|
|
|
|
Returns:
|
|
int: Number of partitions on the disk
|
|
"""
|
|
return len(cls.disk_database[disk]['partitions'])
|
|
|
|
@classmethod
|
|
def set_disk_scheme(cls, scheme, disk, size):
|
|
"""Set or update the partitioning scheme for a disk.
|
|
|
|
Args:
|
|
scheme (str or None): Partition scheme ('GPT' or 'MBR')
|
|
disk (str): Disk device name
|
|
size (str): Disk size
|
|
"""
|
|
if scheme is None:
|
|
cls.disk_database[disk]['scheme'] = 'GPT'
|
|
else:
|
|
cls.disk_database[disk]['scheme'] = scheme
|
|
# this need to data and not use pickle with open.
|
|
InstallationData.destroy[disk] = scheme
|
|
if not cls.disk_database[disk]['partitions']:
|
|
cls.disk_database[disk]['partitions'] = {
|
|
'freespace1': {
|
|
'name': 'freespace1',
|
|
'size': size,
|
|
'mount-point': '',
|
|
'file-system': 'none',
|
|
'stat': None,
|
|
'partitions': {},
|
|
'partition-list': []
|
|
}
|
|
}
|
|
cls.disk_database[disk]['partition-list'] = [
|
|
'freespace1'
|
|
]
|
|
|
|
|
|
class DeletePartition:
|
|
"""Class for handling partition deletion operations.
|
|
|
|
This class provides methods to delete partitions from both GPT and MBR
|
|
partition schemes, handling the consolidation of free space and updating
|
|
the partition database accordingly.
|
|
"""
|
|
|
|
def find_if_label(self, part):
|
|
"""Check if a partition identifier represents a BSD label.
|
|
|
|
Args:
|
|
part (str): Partition identifier
|
|
|
|
Returns:
|
|
bool: True if the partition has a BSD label suffix (letter)
|
|
"""
|
|
last = part[-1]
|
|
if re.search('[a-z]', last):
|
|
return True
|
|
return False
|
|
|
|
def delete_label(self, drive, label, partition, path):
|
|
"""Delete a BSD label partition and consolidate free space.
|
|
|
|
Args:
|
|
drive (str): Disk device name
|
|
label (str): Label partition to delete
|
|
partition (str): Parent slice containing the label
|
|
path (list): Path information for partition location
|
|
"""
|
|
disk_partitions = DiskPartition.disk_database[drive]['partitions'][partition]
|
|
partitions_info = disk_partitions['partitions']
|
|
label_list = disk_partitions['partition-list']
|
|
last_list_number = len(label_list) - 1
|
|
store_list_number = path[2]
|
|
size_free = int(partitions_info[label]['size'])
|
|
|
|
if last_list_number == store_list_number and len(label_list) > 1:
|
|
label_behind = label_list[store_list_number - 1]
|
|
if 'freespace' in label_behind:
|
|
size_free += int(partitions_info[label_behind]['size'])
|
|
label_list.remove(label)
|
|
disk_partitions['partitions'].pop(label, None)
|
|
disk_partitions['partitions'][label_behind] = {
|
|
'name': label_behind,
|
|
'size': size_free,
|
|
'mount-point': '',
|
|
'file-system': 'none',
|
|
'stat': None,
|
|
'partitions': {},
|
|
'partition-list': []
|
|
}
|
|
disk_partitions['partition-list'] = label_list
|
|
else:
|
|
free_name = find_next_partition('freespace', label_list)
|
|
label_list[store_list_number] = free_name
|
|
disk_partitions['partitions'].pop(label, None)
|
|
disk_partitions['partitions'][free_name] = {
|
|
'name': free_name,
|
|
'size': size_free,
|
|
'mount-point': '',
|
|
'file-system': 'none',
|
|
'stat': None,
|
|
'partitions': {},
|
|
'partition-list': []
|
|
}
|
|
disk_partitions['partition-list'] = label_list
|
|
elif store_list_number == 0 and len(label_list) > 1:
|
|
label_after = label_list[store_list_number + 1]
|
|
if 'freespace' in label_after:
|
|
size_free += int(partitions_info[label_after]['size'])
|
|
label_list.remove(label)
|
|
disk_partitions['partitions'].pop(label, None)
|
|
disk_partitions['partitions'][label_after] = {
|
|
'name': label_after,
|
|
'size': size_free,
|
|
'mount-point': '',
|
|
'file-system': 'none',
|
|
'stat': None,
|
|
'partitions': {},
|
|
'partition-list': []
|
|
}
|
|
disk_partitions['partition-list'] = label_list
|
|
else:
|
|
free_name = find_next_partition('freespace', label_list)
|
|
label_list[store_list_number] = free_name
|
|
disk_partitions['partitions'].pop(label, None)
|
|
disk_partitions['partitions'][free_name] = {
|
|
'name': free_name,
|
|
'size': size_free,
|
|
'mount-point': '',
|
|
'file-system': 'none',
|
|
'stat': None,
|
|
'partitions': {},
|
|
'partition-list': []
|
|
}
|
|
disk_partitions['partition-list'] = label_list
|
|
elif len(label_list) > 2:
|
|
label_behind = label_list[store_list_number - 1]
|
|
label_after = label_list[store_list_number + 1]
|
|
size_behind = int(partitions_info[label_behind]['size'])
|
|
size_after = int(partitions_info[label_after]['size'])
|
|
if ('freespace' in label_behind
|
|
and 'freespace' in label_after):
|
|
size_free += size_behind + size_after
|
|
label_list.remove(label)
|
|
label_list.remove(label_after)
|
|
disk_partitions['partitions'].pop(label, None)
|
|
disk_partitions['partitions'].pop(label_after, None)
|
|
disk_partitions['partitions'][label_behind] = {
|
|
'name': label_behind,
|
|
'size': size_free,
|
|
'mount-point': '',
|
|
'file-system': 'none',
|
|
'stat': None,
|
|
'partitions': {},
|
|
'partition-list': []
|
|
}
|
|
disk_partitions['partition-list'] = label_list
|
|
elif 'freespace' in label_behind:
|
|
size_free += size_behind
|
|
label_list.remove(label)
|
|
disk_partitions['partitions'].pop(label, None)
|
|
disk_partitions['partitions'][label_behind] = {
|
|
'name': label_behind,
|
|
'size': size_free,
|
|
'mount-point': '',
|
|
'file-system': 'none',
|
|
'stat': None,
|
|
'partitions': {},
|
|
'partition-list': []
|
|
}
|
|
disk_partitions['partition-list'] = label_list
|
|
elif 'freespace' in label_after:
|
|
size_free += size_after
|
|
label_list.remove(label)
|
|
disk_partitions['partitions'].pop(label, None)
|
|
disk_partitions['partitions'].pop(label_after, None)
|
|
disk_partitions['partitions'][label_after] = {
|
|
'name': label_after,
|
|
'size': size_free,
|
|
'mount-point': '',
|
|
'file-system': 'none',
|
|
'stat': None,
|
|
'partitions': {},
|
|
'partition-list': []
|
|
}
|
|
disk_partitions['partition-list'] = label_list
|
|
else:
|
|
free_name = find_next_partition('freespace', label_list)
|
|
label_list[store_list_number] = free_name
|
|
disk_partitions['partitions'].pop(label, None)
|
|
disk_partitions['partitions'][free_name] = {
|
|
'name': free_name,
|
|
'size': size_free,
|
|
'mount-point': '',
|
|
'file-system': 'none',
|
|
'stat': None,
|
|
'partitions': {},
|
|
'partition-list': []
|
|
}
|
|
disk_partitions['partition-list'] = label_list
|
|
else:
|
|
free_name = find_next_partition('freespace', label_list)
|
|
label_list[store_list_number] = free_name
|
|
disk_partitions['partitions'].pop(label, None)
|
|
disk_partitions['partitions'][free_name] = {
|
|
'name': free_name,
|
|
'size': size_free,
|
|
'mount-point': '',
|
|
'file-system': 'none',
|
|
'stat': None,
|
|
'partitions': {},
|
|
'partition-list': []
|
|
}
|
|
disk_partitions['partition-list'] = label_list
|
|
|
|
# Data is already updated in DiskPartition.disk_database - no need to save to file
|
|
remaining_partition = []
|
|
for part in label_list:
|
|
partitions_info = disk_partitions['partitions']
|
|
if part in partitions_info:
|
|
size = partitions_info[part]['size']
|
|
mount_point = partitions_info[part]['mount-point']
|
|
file_system = partitions_info[part]['file-system']
|
|
stat = partitions_info[part]['stat']
|
|
if stat == 'New':
|
|
remaining_partition.append(f'{file_system} {size} {mount_point}\n')
|
|
InstallationData.new_partition = remaining_partition
|
|
|
|
def __init__(self, part, path):
|
|
"""Initialize partition deletion operation.
|
|
|
|
Args:
|
|
part (str): Partition identifier to delete
|
|
path (list): Path information for partition location
|
|
"""
|
|
drive = get_disk_from_partition(part)
|
|
if part == "freespace":
|
|
pass
|
|
elif self.find_if_label(part) is True:
|
|
spart = part[:-1]
|
|
self.delete_label(drive, part, spart, path)
|
|
else:
|
|
self.delete_slice(drive, part, path)
|
|
|
|
def delete_slice(self, drive, partition, path):
|
|
"""Delete a slice/partition and consolidate adjacent free space.
|
|
|
|
Args:
|
|
drive (str): Disk device name
|
|
partition (str): Partition to delete
|
|
path (list): Path information for partition location
|
|
"""
|
|
disk_data = DiskPartition.disk_database
|
|
partitions_info = disk_data[drive]['partitions']
|
|
partition_list = disk_data[drive]['partition-list']
|
|
last_list_number = len(partition_list) - 1
|
|
store_list_number = path[1]
|
|
size_free = int(partitions_info[partition]['size'])
|
|
if last_list_number == store_list_number and len(partition_list) > 1:
|
|
partition_behind = partition_list[store_list_number - 1]
|
|
if 'freespace' in partition_behind:
|
|
size_free += int(partitions_info[partition_behind]['size'])
|
|
partition_list.remove(partition)
|
|
disk_data[drive]['partitions'].pop(partition, None)
|
|
disk_data[drive]['partitions'][partition_behind] = {
|
|
'name': partition_behind,
|
|
'size': size_free,
|
|
'mount-point': '',
|
|
'file-system': 'none',
|
|
'stat': None,
|
|
'partitions': {},
|
|
'partition-list': []
|
|
}
|
|
disk_data[drive]['partition-list'] = partition_list
|
|
else:
|
|
free_name = find_next_partition('freespace', partition_list)
|
|
partition_list[store_list_number] = free_name
|
|
disk_data[drive]['partitions'].pop(partition, None)
|
|
disk_data[drive]['partitions'][free_name] = {
|
|
'name': free_name,
|
|
'size': size_free,
|
|
'mount-point': '',
|
|
'file-system': 'none',
|
|
'stat': None,
|
|
'partitions': {},
|
|
'partition-list': []
|
|
}
|
|
disk_data[drive]['partition-list'] = partition_list
|
|
elif store_list_number == 0 and len(partition_list) > 1:
|
|
partition_after = partition_list[store_list_number + 1]
|
|
if 'freespace' in partition_after:
|
|
size_free += int(partitions_info[partition_after]['size'])
|
|
partition_list.remove(partition)
|
|
disk_data[drive]['partitions'].pop(partition, None)
|
|
disk_data[drive]['partitions'][partition_after] = {
|
|
'name': partition_after,
|
|
'size': size_free,
|
|
'mount-point': '',
|
|
'file-system': 'none',
|
|
'stat': None,
|
|
'partitions': {},
|
|
'partition-list': []
|
|
}
|
|
disk_data[drive]['partition-list'] = partition_list
|
|
else:
|
|
free_name = find_next_partition('freespace', partition_list)
|
|
partition_list[store_list_number] = free_name
|
|
disk_data[drive]['partitions'].pop(partition, None)
|
|
disk_data[drive]['partitions'][free_name] = {
|
|
'name': free_name,
|
|
'size': size_free,
|
|
'mount-point': '',
|
|
'file-system': 'none',
|
|
'stat': None,
|
|
'partitions': {},
|
|
'partition-list': []
|
|
}
|
|
disk_data[drive]['partition-list'] = partition_list
|
|
elif len(partition_list) > 2:
|
|
partition_behind = partition_list[store_list_number - 1]
|
|
partition_after = partition_list[store_list_number + 1]
|
|
size_behind = int(partitions_info[partition_behind]['size'])
|
|
size_after = int(partitions_info[partition_after]['size'])
|
|
if ('freespace' in partition_behind
|
|
and 'freespace' in partition_after):
|
|
size_free += size_behind + size_after
|
|
partition_list.remove(partition)
|
|
partition_list.remove(partition_after)
|
|
disk_data[drive]['partitions'].pop(partition, None)
|
|
disk_data[drive]['partitions'][partition_behind] = {
|
|
'name': partition_behind,
|
|
'size': size_free,
|
|
'mount-point': '',
|
|
'file-system': 'none',
|
|
'stat': None,
|
|
'partitions': {},
|
|
'partition-list': []
|
|
}
|
|
disk_data[drive]['partition-list'] = partition_list
|
|
elif 'freespace' in partition_behind:
|
|
size_free += size_behind
|
|
partition_list.remove(partition)
|
|
disk_data[drive]['partitions'].pop(partition, None)
|
|
disk_data[drive]['partitions'][partition_behind] = {
|
|
'name': partition_behind,
|
|
'size': size_free,
|
|
'mount-point': '',
|
|
'file-system': 'none',
|
|
'stat': None,
|
|
'partitions': {},
|
|
'partition-list': []
|
|
}
|
|
disk_data[drive]['partition-list'] = partition_list
|
|
elif 'freespace' in partition_after:
|
|
size_free += size_after
|
|
partition_list.remove(partition)
|
|
disk_data[drive]['partitions'].pop(partition, None)
|
|
disk_data[drive]['partitions'][partition_after] = {
|
|
'name': partition_after,
|
|
'size': size_free,
|
|
'mount-point': '',
|
|
'file-system': 'none',
|
|
'stat': None,
|
|
'partitions': {},
|
|
'partition-list': []
|
|
}
|
|
disk_data[drive]['partition-list'] = partition_list
|
|
else:
|
|
free_name = find_next_partition('freespace', partition_list)
|
|
partition_list[store_list_number] = free_name
|
|
disk_data[drive]['partitions'].pop(partition, None)
|
|
disk_data[drive]['partitions'][free_name] = {
|
|
'name': free_name,
|
|
'size': size_free,
|
|
'mount-point': '',
|
|
'file-system': 'none',
|
|
'stat': None,
|
|
'partitions': {},
|
|
'partition-list': []
|
|
}
|
|
disk_data[drive]['partition-list'] = partition_list
|
|
else:
|
|
free_name = find_next_partition('freespace', partition_list)
|
|
partition_list[store_list_number] = free_name
|
|
disk_data[drive]['partitions'].pop(partition, None)
|
|
disk_data[drive]['partitions'][free_name] = {
|
|
'name': free_name,
|
|
'size': size_free,
|
|
'mount-point': '',
|
|
'file-system': 'none',
|
|
'stat': None,
|
|
'partitions': {},
|
|
'partition-list': []
|
|
}
|
|
disk_data[drive]['partition-list'] = partition_list
|
|
|
|
# if delete file exist check if slice is in the list
|
|
|
|
if partition not in InstallationData.delete:
|
|
InstallationData.delete.append(partition)
|
|
|
|
if "p" in partition and InstallationData.new_partition:
|
|
remaining_partition = []
|
|
for part in partition_list:
|
|
partitions_info = disk_data[drive]['partitions']
|
|
size = partitions_info[part]['size']
|
|
mount_point = partitions_info[part]['mount-point']
|
|
file_system = partitions_info[part]['file-system']
|
|
stat = partitions_info[part]['stat']
|
|
if stat == 'New':
|
|
remaining_partition.append(f'{file_system} {size} {mount_point}\n')
|
|
InstallationData.new_partition = remaining_partition
|
|
|
|
|
|
class AutoFreeSpace:
|
|
"""Class for automatic partition creation in free space.
|
|
|
|
This class handles automatic partitioning of free space on disks,
|
|
creating appropriate partition layouts for both MBR and GPT schemes
|
|
with support for different filesystems.
|
|
"""
|
|
|
|
def create_mbr_partiton(self, drive, size, path, fs):
|
|
"""Create MBR partitions automatically in free space.
|
|
|
|
Creates a BSD slice with root and swap partitions.
|
|
|
|
Args:
|
|
drive (str): Disk device name
|
|
size (str): Available size in MB
|
|
path (list): Path information for partition location
|
|
fs (str): Filesystem type ('ZFS' or 'UFS')
|
|
"""
|
|
# remove 1M to size to avoid no space left
|
|
main_size = int(size) - 1
|
|
InstallationData.disk = drive
|
|
|
|
InstallationData.scheme = 'partscheme=MBR'
|
|
|
|
disk_db = DiskPartition.disk_database
|
|
slice_list = disk_db[drive]['partition-list']
|
|
store_list_number = path[1]
|
|
main_slice = find_next_partition(f'{drive}s', slice_list)
|
|
slice_list[store_list_number] = main_slice
|
|
disk_db[drive]['partitions'][main_slice] = {
|
|
'name': main_slice,
|
|
'size': size,
|
|
'mount-point': 'none',
|
|
'file-system': 'BSD',
|
|
'stat': 'New',
|
|
'partitions': {},
|
|
'partition-list': []
|
|
}
|
|
disk_db[drive]['partition-list'] = slice_list
|
|
|
|
InstallationData.slice = main_slice.replace(drive, "")
|
|
|
|
root_size = int(main_size)
|
|
swap_size = get_ram_size_mb()
|
|
root_size -= swap_size
|
|
|
|
part_list = disk_db[drive]['partitions'][main_slice]['partition-list']
|
|
|
|
if fs == "ZFS":
|
|
layout = zfs_datasets
|
|
else:
|
|
layout = '/'
|
|
|
|
root_part = f'{main_slice}a'
|
|
part_list.append(root_part)
|
|
disk_db[drive]['partitions'][main_slice]['partitions'][root_part] = {
|
|
'name': root_part,
|
|
'size': root_size,
|
|
'mount-point': layout,
|
|
'file-system': fs,
|
|
'stat': 'New',
|
|
'partitions': {},
|
|
'partition-list': []
|
|
}
|
|
|
|
swap_part = f'{main_slice}b'
|
|
part_list.append(swap_part)
|
|
disk_db[drive]['partitions'][main_slice]['partitions'][swap_part] = {
|
|
'name': swap_part,
|
|
'size': swap_size,
|
|
'mount-point': 'none',
|
|
'file-system': 'SWAP',
|
|
'stat': 'New',
|
|
'partitions': {},
|
|
'partition-list': []
|
|
}
|
|
|
|
disk_db[drive]['partitions'][main_slice]['partition-list'] = part_list
|
|
|
|
# Data is already updated in DiskPartition.disk_database - no need to save to file
|
|
|
|
# Add new partitions to InstallationData
|
|
InstallationData.new_partition = [
|
|
f'{fs} {root_size} {layout}\n',
|
|
f'SWAP {swap_size} none\n'
|
|
]
|
|
|
|
# Add to create list for partition creation operations
|
|
InstallationData.create.append([main_slice, main_size])
|
|
|
|
def __init__(self, path, size, fs, efi_exist, disk, scheme):
|
|
"""Initialize automatic partition creation.
|
|
|
|
Args:
|
|
path (list): Path information for partition location
|
|
size (str): Available size in MB
|
|
fs (str): Filesystem type ('ZFS' or 'UFS')
|
|
efi_exist (bool): Whether EFI partition already exists
|
|
disk (str): Disk device name
|
|
scheme (str): Partition scheme ('GPT' or 'MBR')
|
|
"""
|
|
self.bios_type = bios_or_uefi()
|
|
if scheme == "GPT":
|
|
self.create_gpt_partiton(disk, size, path, fs, efi_exist)
|
|
elif scheme == "MBR":
|
|
self.create_mbr_partiton(disk, size, path, fs)
|
|
|
|
def create_gpt_partiton(self, drive, size, path, fs, efi_exist):
|
|
"""Create GPT partitions automatically in free space.
|
|
|
|
Creates boot (if needed), root, and swap partitions with appropriate
|
|
sizing for the target filesystem.
|
|
|
|
Args:
|
|
drive (str): Disk device name
|
|
size (str): Available size in MB
|
|
path (list): Path information for partition location
|
|
fs (str): Filesystem type ('ZFS' or 'UFS')
|
|
efi_exist (bool): Whether EFI partition already exists
|
|
"""
|
|
# remove 1M to size to avoid no space left
|
|
main_size = int(size) - 1
|
|
InstallationData.disk = drive
|
|
InstallationData.scheme = 'partscheme=GPT'
|
|
root_size = int(main_size)
|
|
swap_size = get_ram_size_mb()
|
|
root_size -= int(swap_size)
|
|
if self.bios_type == "UEFI" and efi_exist is False:
|
|
boot_size = 256
|
|
else:
|
|
boot_size = 1 if self.bios_type == "BIOS" else 0
|
|
boot_name = 'UEFI' if self.bios_type == "UEFI" else 'BOOT'
|
|
root_size -= boot_size
|
|
disk_data = DiskPartition.disk_database
|
|
partition_list = disk_data[drive]['partition-list']
|
|
store_list_number = path[1]
|
|
if boot_size != 0:
|
|
boot_partition = find_next_partition(f'{drive}p', partition_list)
|
|
partition_list[store_list_number] = boot_partition
|
|
store_list_number += 1
|
|
disk_data[drive]['partitions'][boot_partition] = {
|
|
'name': boot_partition,
|
|
'size': boot_size,
|
|
'mount-point': 'none',
|
|
'file-system': boot_name,
|
|
'stat': 'New',
|
|
'partitions': {},
|
|
'partition-list': []
|
|
}
|
|
# Add boot partition to create list
|
|
InstallationData.create.append([boot_partition, boot_size])
|
|
|
|
if fs == "ZFS":
|
|
layout = zfs_datasets
|
|
else:
|
|
layout = '/'
|
|
|
|
root_partition = find_next_partition(f'{drive}p', partition_list)
|
|
if store_list_number == path[1]:
|
|
partition_list[store_list_number] = root_partition
|
|
else:
|
|
partition_list.insert(store_list_number, root_partition)
|
|
store_list_number += 1
|
|
disk_data[drive]['partitions'][root_partition] = {
|
|
'name': root_partition,
|
|
'size': root_size,
|
|
'mount-point': layout,
|
|
'file-system': fs,
|
|
'stat': 'New',
|
|
'partitions': {},
|
|
'partition-list': []
|
|
}
|
|
|
|
InstallationData.slice = root_partition.replace(drive, '')
|
|
|
|
swap_partition = find_next_partition(f'{drive}p', partition_list)
|
|
partition_list.insert(store_list_number, swap_partition)
|
|
disk_data[drive]['partitions'][swap_partition] = {
|
|
'name': swap_partition,
|
|
'size': swap_size,
|
|
'mount-point': 'none',
|
|
'file-system': 'SWAP',
|
|
'stat': 'New',
|
|
'partitions': {},
|
|
'partition-list': []
|
|
}
|
|
|
|
disk_data[drive]['partition-list'] = partition_list
|
|
|
|
# Data is already updated in DiskPartition.disk_database - no need to save to file
|
|
|
|
# Add new partitions to InstallationData
|
|
new_partitions = []
|
|
if self.bios_type == "UEFI" and efi_exist is False:
|
|
new_partitions.append(f'UEFI {boot_size} none\n')
|
|
elif self.bios_type == "BIOS":
|
|
new_partitions.append(f'BOOT {boot_size} none\n')
|
|
new_partitions.extend([
|
|
f'{fs} {root_size} {layout}\n',
|
|
f'SWAP {swap_size} none\n'
|
|
])
|
|
InstallationData.new_partition = new_partitions
|
|
|
|
|
|
class CreateLabel:
|
|
"""Class for creating BSD label partitions within MBR slices.
|
|
|
|
This class handles the creation of individual partitions (labels)
|
|
within BSD slices in MBR partition schemes.
|
|
"""
|
|
def __init__(self, path, drive, main_slice, size_left, create_size,
|
|
mountpoint, fs):
|
|
"""Create a new BSD label partition within a slice.
|
|
|
|
Args:
|
|
path (list): Path information for partition location
|
|
drive (str): Disk device name
|
|
main_slice (str): Parent slice identifier
|
|
size_left (int): Remaining size after partition creation
|
|
create_size (int): Size of new partition in MB
|
|
mountpoint (str): Mount point for the partition
|
|
fs (str): Filesystem type
|
|
"""
|
|
InstallationData.disk = drive
|
|
InstallationData.scheme = 'partscheme=MBR'
|
|
InstallationData.slice = main_slice.replace(drive, "")
|
|
disk_db = DiskPartition.disk_database
|
|
store_list_number = path[2]
|
|
part_list = disk_db[drive]['partitions'][main_slice]['partition-list']
|
|
alpha_num = ord('a')
|
|
alpha_num += store_list_number
|
|
letter = chr(alpha_num)
|
|
if fs == "ZFS":
|
|
mountpoint = zfs_datasets
|
|
|
|
partition = f'{main_slice}{letter}'
|
|
part_list[store_list_number] = partition
|
|
disk_db[drive]['partitions'][main_slice]['partitions'][partition] = {
|
|
'name': partition,
|
|
'size': create_size,
|
|
'mount-point': mountpoint,
|
|
'file-system': fs,
|
|
'stat': 'New',
|
|
'partitions': {},
|
|
'partition-list': []
|
|
}
|
|
if size_left != 0:
|
|
free = find_next_partition('freespace', part_list)
|
|
part_list.append(free)
|
|
disk_db[drive]['partitions'][main_slice]['partitions'][free] = {
|
|
'name': free,
|
|
'size': size_left,
|
|
'mount-point': '',
|
|
'file-system': 'none',
|
|
'stat': None,
|
|
'partitions': {},
|
|
'partition-list': []
|
|
}
|
|
|
|
disk_db[drive]['partitions'][main_slice]['partition-list'] = part_list
|
|
|
|
# Data is already updated in DiskPartition.disk_database - no need to save to file
|
|
|
|
# Update InstallationData with new partition information
|
|
new_partitions = []
|
|
for partition_name in part_list:
|
|
drive_part = disk_db[drive]['partitions']
|
|
part_info = drive_part[main_slice]['partitions'][partition_name]
|
|
if part_info['stat'] == 'New':
|
|
partition_text = f'{part_info["file-system"]} ' \
|
|
f'{part_info["size"]} ' \
|
|
f'{part_info["mount-point"]}\n'
|
|
new_partitions.append(partition_text)
|
|
|
|
InstallationData.new_partition = new_partitions
|
|
|
|
|
|
# class modifyLabel():
|
|
|
|
# def __init_get_text(self, path, size_left, create_size, mount_point, fs,
|
|
# data, disk):
|
|
# if not os.path.exists(disk_file):
|
|
# file_disk = open(disk_file, 'w')
|
|
# file_disk.writelines('%s\n' % disk)
|
|
# file_disk.close()
|
|
# sl = path[1] + 1
|
|
# lv = path[2]
|
|
# write_scheme = open(scheme_file, 'w')
|
|
# write_scheme.writelines('partscheme=MBR')
|
|
# write_scheme.close()
|
|
# write_slice = open(slice_file, 'w')
|
|
# write_slice.writelines('s%s\n' % sl)
|
|
# write_slice.close()
|
|
# alph = ord('a')
|
|
# alph += lv
|
|
# letter = chr(alph)
|
|
# llist = []
|
|
# mllist = label_query(disk + 's%s' % sl)
|
|
# plf = open(partitiondb + disk + 's%s' % sl, 'wb')
|
|
# if size_left == 0:
|
|
# create_size -= 1
|
|
# llist.extend(([disk + 's%s' % sl + letter, create_size, mount_point,
|
|
# fs]))
|
|
# mllist[lv] = llist
|
|
# llist = []
|
|
# if size_left > 0:
|
|
# llist.extend((['freespace', size_left, '', '']))
|
|
# mllist.append(llist)
|
|
# pickle.dump(mllist, plf)
|
|
# plf.close()
|
|
# llist = open(partitiondb + disk + 's%s' % sl, 'rb')
|
|
# sabeltlist = pickle.load(llist)
|
|
# write_partition = open(partition_label_file, 'w')
|
|
# for partlist in sabeltlist:
|
|
# if partlist[2] != '':
|
|
# write_partition.writelines('%s %s %s\n' % (partlist[3],
|
|
# partlist[1], partlist[2]))
|
|
# write_partition.close()
|
|
|
|
|
|
class CreateSlice:
|
|
"""Class for creating MBR slices (BSD partitions).
|
|
|
|
This class handles the creation of BSD slices within MBR partition
|
|
schemes, which can then contain multiple BSD label partitions.
|
|
"""
|
|
|
|
def __init__(self, create_size, size_left, path, drive):
|
|
"""Create a new MBR slice.
|
|
|
|
Args:
|
|
create_size (int): Size of new slice in MB
|
|
size_left (int): Remaining size after slice creation
|
|
path (list): Path information for slice location
|
|
drive (str): Disk device name
|
|
"""
|
|
InstallationData.disk = drive
|
|
InstallationData.scheme = 'partscheme=MBR'
|
|
|
|
disk_db = DiskPartition.disk_database
|
|
store_list_number = path[1]
|
|
partition_list = disk_db[drive]['partition-list']
|
|
|
|
partition = find_next_partition(f'{drive}s', partition_list)
|
|
|
|
partition_list[store_list_number] = partition
|
|
# Store slice partition
|
|
disk_db[drive]['partitions'][partition] = {
|
|
'name': partition,
|
|
'size': create_size,
|
|
'mount-point': 'none',
|
|
'file-system': 'BSD',
|
|
'stat': 'New',
|
|
'partitions': {},
|
|
'partition-list': ['freespace1']
|
|
}
|
|
# Store freespace for partition partition
|
|
disk_db[drive]['partitions'][partition]['partitions']['freespace1'] = {
|
|
'name': 'freespace1',
|
|
'size': create_size,
|
|
'mount-point': '',
|
|
'file-system': 'none',
|
|
'stat': None,
|
|
'partitions': {},
|
|
'partition-list': []
|
|
}
|
|
# Store freespace if some left
|
|
if size_left != 0:
|
|
free_name = find_next_partition('freespace', partition_list)
|
|
partition_list.append(free_name)
|
|
disk_db[drive]['partitions'][free_name] = {
|
|
'name': free_name,
|
|
'size': size_left,
|
|
'mount-point': '',
|
|
'file-system': 'none',
|
|
'stat': None,
|
|
'partitions': {},
|
|
'partition-list': []
|
|
}
|
|
|
|
disk_db[drive]['partition-list'] = partition_list
|
|
|
|
# Data is already updated in DiskPartition.disk_database - no need to save to file
|
|
|
|
InstallationData.slice = partition.replace(drive, '')
|
|
|
|
# Add to create list for partition creation operations
|
|
InstallationData.create.append([partition, create_size])
|
|
|
|
|
|
class CreatePartition():
|
|
"""Class for creating GPT partitions.
|
|
|
|
This class handles the creation of individual partitions within
|
|
GPT partition schemes, supporting various filesystem types and
|
|
mount points.
|
|
"""
|
|
def __init__(self, path, drive, size_left, create_size, mount_point, fs):
|
|
"""Create a new GPT partition.
|
|
|
|
Args:
|
|
path (list): Path information for partition location
|
|
drive (str): Disk device name
|
|
size_left (int): Remaining size after partition creation
|
|
create_size (int): Size of new partition in MB
|
|
mount_point (str): Mount point for the partition
|
|
fs (str): Filesystem type
|
|
"""
|
|
InstallationData.disk = drive
|
|
|
|
InstallationData.scheme = 'partscheme=GPT'
|
|
|
|
if fs == "ZFS":
|
|
mount_point = zfs_datasets
|
|
|
|
disk_data = DiskPartition.disk_database
|
|
store_list_number = path[1]
|
|
partition_list = disk_data[drive]['partition-list']
|
|
|
|
partition = find_next_partition(f'{drive}p', partition_list)
|
|
|
|
partition_list[store_list_number] = partition
|
|
# Store slice partition
|
|
disk_data[drive]['partitions'][partition] = {
|
|
'name': partition,
|
|
'size': create_size,
|
|
'mount-point': mount_point,
|
|
'file-system': fs,
|
|
'stat': 'New',
|
|
'partitions': {},
|
|
'partition-list': []
|
|
}
|
|
# Store freespace if some left
|
|
if size_left != 0:
|
|
free_name = find_next_partition('freespace', partition_list)
|
|
partition_list.append(free_name)
|
|
disk_data[drive]['partitions'][free_name] = {
|
|
'name': free_name,
|
|
'size': size_left,
|
|
'mount-point': '',
|
|
'file-system': 'none',
|
|
'stat': None,
|
|
'partitions': {},
|
|
'partition-list': []
|
|
}
|
|
|
|
disk_data[drive]['partition-list'] = partition_list
|
|
|
|
# Data is already updated in DiskPartition.disk_database - no need to save to file
|
|
|
|
if mount_point == '/' or fs == "ZFS":
|
|
InstallationData.slice = partition.replace(drive, '')
|
|
|
|
if fs == "UEFI" or fs == "BOOT":
|
|
# Add to create list for partition creation operations
|
|
InstallationData.create.append([partition, create_size])
|
|
|
|
# Update InstallationData with new partition information
|
|
new_partitions = []
|
|
for partition_name in partition_list:
|
|
partition_info = disk_data[drive]['partitions'][partition_name]
|
|
if partition_info['stat'] == 'New':
|
|
partition_text = f'{partition_info["file-system"]} ' \
|
|
f'{partition_info["size"]} ' \
|
|
f'{partition_info["mount-point"]}\n'
|
|
new_partitions.append(partition_text)
|
|
|
|
InstallationData.new_partition = new_partitions
|
|
|
|
|
|
def delete_partition() -> None:
|
|
"""Execute physical deletion of partitions marked for deletion.
|
|
|
|
Iterates through partitions marked for deletion in InstallationData
|
|
and removes them from the disk using FreeBSD gpart commands.
|
|
|
|
Raises:
|
|
RuntimeError: If no partitions are marked for deletion
|
|
"""
|
|
if InstallationData.delete:
|
|
for partition in InstallationData.delete:
|
|
num = slice_number(partition)
|
|
drive = get_disk_from_partition(partition)
|
|
call(f"sudo zpool labelclear -f {partition}", shell=True)
|
|
sleep(1)
|
|
call(f'sudo gpart delete -i {num} {drive}', shell=True)
|
|
sleep(1)
|
|
else:
|
|
raise RuntimeError('No partitions to delete')
|
|
|
|
|
|
def destroy_partition() -> None:
|
|
"""Destroy and recreate partition tables on disks.
|
|
|
|
Completely destroys existing partition tables and creates new ones
|
|
with the specified scheme for disks marked for destruction.
|
|
|
|
Raises:
|
|
RuntimeError: If no disks are marked for destruction
|
|
"""
|
|
if InstallationData.destroy:
|
|
for drive, scheme in InstallationData.destroy.items():
|
|
# Destroy the disk geom
|
|
gpart_destroy = f"sudo gpart destroy -F {drive}"
|
|
call(gpart_destroy, shell=True)
|
|
sleep(1)
|
|
clear_drive = f"sudo dd if=/dev/zero of={drive} bs=1m count=1"
|
|
call(clear_drive, shell=True)
|
|
sleep(1)
|
|
call(f'sudo gpart create -s {scheme} {drive}', shell=True)
|
|
sleep(1)
|
|
else:
|
|
raise RuntimeError('No disks to destroy')
|
|
|
|
|
|
def bios_or_uefi() -> str:
|
|
"""Detect the system boot method (BIOS or UEFI).
|
|
|
|
Returns:
|
|
str: 'BIOS' or 'UEFI' depending on the system boot method
|
|
"""
|
|
cmd = "sysctl -n machdep.bootmethod"
|
|
output1 = Popen(cmd, shell=True, stdout=PIPE,
|
|
universal_newlines=True, close_fds=True)
|
|
return output1.stdout.readlines()[0].rstrip()
|
|
|
|
|
|
def add_partition() -> None:
|
|
"""Execute physical creation of partitions marked for creation.
|
|
|
|
Creates actual partitions on disk using FreeBSD gpart commands
|
|
for all partitions marked for creation in InstallationData.
|
|
Handles different partition types (EFI, BIOS boot, FreeBSD, etc.).
|
|
|
|
Raises:
|
|
RuntimeError: If no partitions are marked for creation
|
|
"""
|
|
if InstallationData.create:
|
|
boot = InstallationData.boot
|
|
for partition_info in InstallationData.create:
|
|
part = partition_info[0]
|
|
size = int(partition_info[1])
|
|
drive = get_disk_from_partition(part)
|
|
sl = slice_number(part)
|
|
if set("p") & set(part):
|
|
if bios_or_uefi() == 'UEFI':
|
|
cmd = f'sudo gpart add -a 4k -s {size}M -t efi ' \
|
|
f'-i {sl} {drive}'
|
|
call(cmd, shell=True)
|
|
sleep(1)
|
|
call(f'sudo zpool labelclear -f {drive}p{sl}', shell=True)
|
|
cmd2 = f'sudo newfs_msdos -F 16 {drive}p{sl}'
|
|
call(cmd2, shell=True)
|
|
else:
|
|
if boot == "grub":
|
|
cmd = f'sudo gpart add -a 4k -s {size}M -t ' \
|
|
'bios-boot -i {sl} {drive}'
|
|
else:
|
|
# freebsd-boot partition must never be larger
|
|
# than 512B blocks.
|
|
cmd = 'sudo gpart add -a 4k -s 512 -t ' \
|
|
f'freebsd-boot -i {sl} {drive}'
|
|
call(cmd, shell=True)
|
|
call(f'sudo zpool labelclear -f {drive}p{sl}', shell=True)
|
|
elif set("s") & set(part):
|
|
cmd = f'sudo gpart add -a 4k -s {size}M -t freebsd ' \
|
|
f'-i {sl} {drive}'
|
|
call(cmd, shell=True)
|
|
sleep(2)
|
|
else:
|
|
raise RuntimeError('No partitions to create')
|