Files
installer-gui/install_station/partition.py
T
ericbsd 391904b744 Update translations and new strings in po files
- Adjust line mappings in .po files for translation strings
- Add new translations for custom partitioning, password strength, keyboard setup, installer steps, and network setup strings
- Update POT-Creation-Date in Romanian and Slovak .po files
- Include various new UI strings for installation and try-live options
2025-07-12 21:24:33 -03:00

1310 lines
48 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
# Define required file paths
def get_disk_from_partition(part):
"""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):
"""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, partition_list):
"""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():
"""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):
"""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):
"""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):
"""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-sheme.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 = 2048
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 = 2048
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():
"""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():
"""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():
"""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():
"""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')