391904b744
- 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
1310 lines
48 KiB
Python
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')
|