Updating Partition() information after mount and unmount. (#1508)

* Updating Partition() information after mount and unmount.

* Cleaned up raw_parted() to gracefully output relevant partition error information.

* Adding timestmap to cmd_history.txt as it's impossible to debug properly otherwise

* Adding more verbose debugging information

* Reinstating the lsblk retry code for PARTUUID and UUID on Partition()'s information

* Added error handling for JSON parsing
This commit is contained in:
Anton Hvornum 2022-10-12 14:17:14 +02:00 committed by GitHub
parent 5c8eb7144d
commit eec45643e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 70 additions and 17 deletions

3
.gitignore vendored
View File

@ -32,4 +32,5 @@ venv
.DS_Store .DS_Store
**/cmd_history.txt **/cmd_history.txt
**/*.*~ **/*.*~
/*.sig /*.sig
/*.json

View File

@ -274,12 +274,16 @@ class BlockDevice:
if not uuid and not partuuid: if not uuid and not partuuid:
raise ValueError(f"BlockDevice.get_partition() requires either a UUID or a PARTUUID for lookups.") raise ValueError(f"BlockDevice.get_partition() requires either a UUID or a PARTUUID for lookups.")
log(f"Retrieving partition PARTUUID={partuuid} or UUID={uuid}", level=logging.INFO, fg="teal")
for count in range(storage.get('DISK_RETRY_ATTEMPTS', 5)): for count in range(storage.get('DISK_RETRY_ATTEMPTS', 5)):
for partition_index, partition in self.partitions.items(): for partition_index, partition in self.partitions.items():
try: try:
if uuid and partition.uuid and partition.uuid.lower() == uuid.lower(): if uuid and partition.uuid and partition.uuid.lower() == uuid.lower():
log(f"Matched UUID={uuid} against {partition.uuid}", level=logging.INFO, fg="teal")
return partition return partition
elif partuuid and partition.part_uuid and partition.part_uuid.lower() == partuuid.lower(): elif partuuid and partition.part_uuid and partition.part_uuid.lower() == partuuid.lower():
log(f"Matched PARTUUID={partuuid} against {partition.part_uuid}", level=logging.INFO, fg="teal")
return partition return partition
except DiskError as error: except DiskError as error:
# Most likely a blockdevice that doesn't support or use UUID's # Most likely a blockdevice that doesn't support or use UUID's

View File

@ -189,10 +189,13 @@ class Filesystem:
return True return True
def raw_parted(self, string: str) -> SysCommand: def raw_parted(self, string: str) -> SysCommand:
if (cmd_handle := SysCommand(f'/usr/bin/parted -s {string}')).exit_code != 0: try:
log(f"Parted ended with a bad exit code: {cmd_handle}", level=logging.ERROR, fg="red") cmd_handle = SysCommand(f'/usr/bin/parted -s {string}')
time.sleep(0.5) time.sleep(0.5)
return cmd_handle return cmd_handle
except SysCallError as error:
log(f"Parted ended with a bad exit code: {error.exit_code} ({error})", level=logging.ERROR, fg="red")
return error
def parted(self, string: str) -> bool: def parted(self, string: str) -> bool:
""" """
@ -258,6 +261,9 @@ class Filesystem:
new_partition_uuids = [partition.part_uuid for partition in self.blockdevice.partitions.values()] new_partition_uuids = [partition.part_uuid for partition in self.blockdevice.partitions.values()]
new_partuuid_set = (set(previous_partuuids) ^ set(new_partition_uuids)) new_partuuid_set = (set(previous_partuuids) ^ set(new_partition_uuids))
log(f'Old partition set: {previous_partuuids}', level=logging.INFO, fg="teal")
log(f'New partition set: {new_partition_uuids}', level=logging.INFO, fg="teal")
if len(new_partuuid_set) and (new_partuuid := new_partuuid_set.pop()): if len(new_partuuid_set) and (new_partuuid := new_partuuid_set.pop()):
try: try:
return self.blockdevice.get_partition(partuuid=new_partuuid) return self.blockdevice.get_partition(partuuid=new_partuuid)
@ -282,6 +288,7 @@ class Filesystem:
log(f"Could not find the new PARTUUID after adding the partition.", level=logging.ERROR, fg="red") log(f"Could not find the new PARTUUID after adding the partition.", level=logging.ERROR, fg="red")
log(f"Previous partitions: {previous_partuuids}", level=logging.ERROR, fg="red") log(f"Previous partitions: {previous_partuuids}", level=logging.ERROR, fg="red")
log(f"New partitions: {total_partitions}", level=logging.ERROR, fg="red") log(f"New partitions: {total_partitions}", level=logging.ERROR, fg="red")
raise DiskError(f"Could not add partition using: {parted_string}") raise DiskError(f"Could not add partition using: {parted_string}")
def set_name(self, partition: int, name: str) -> bool: def set_name(self, partition: int, name: str) -> bool:

View File

@ -5,7 +5,7 @@ import json
import os import os
import hashlib import hashlib
import typing import typing
from dataclasses import dataclass from dataclasses import dataclass, field
from pathlib import Path from pathlib import Path
from typing import Optional, Dict, Any, List, Union, Iterator from typing import Optional, Dict, Any, List, Union, Iterator
@ -18,19 +18,51 @@ from ..general import SysCommand
from .btrfs.btrfs_helpers import subvolume_info_from_path from .btrfs.btrfs_helpers import subvolume_info_from_path
from .btrfs.btrfssubvolumeinfo import BtrfsSubvolumeInfo from .btrfs.btrfssubvolumeinfo import BtrfsSubvolumeInfo
@dataclass @dataclass
class PartitionInfo: class PartitionInfo:
pttype: str partition_object: 'Partition'
partuuid: str device_path: str # This would be /dev/sda1 for instance
uuid: str
start: Optional[int]
end: Optional[int]
bootable: bool bootable: bool
size: float size: float
sector_size: int sector_size: int
filesystem_type: str start: Optional[int]
mountpoints: List[Path] end: Optional[int]
pttype: Optional[str]
filesystem_type: Optional[str]
partuuid: Optional[str]
uuid: Optional[str]
mountpoints: List[Path] = field(default_factory=list)
def __post_init__(self):
if not all([self.partuuid, self.uuid]):
for i in range(storage['DISK_RETRY_ATTEMPTS']):
lsblk_info = SysCommand(f"lsblk --json -b -o+LOG-SEC,SIZE,PTTYPE,PARTUUID,UUID,FSTYPE {self.device_path}").decode('UTF-8')
try:
lsblk_info = json.loads(lsblk_info)
except json.decoder.JSONDecodeError:
log(f"Could not decode JSON: {lsblk_info}", fg="red", level=logging.ERROR)
raise DiskError(f'Failed to retrieve information for "{self.device_path}" with lsblk')
if not (device := lsblk_info.get('blockdevices', [None])[0]):
raise DiskError(f'Failed to retrieve information for "{self.device_path}" with lsblk')
self.partuuid = device.get('partuuid')
self.uuid = device.get('uuid')
# Lets build a list of requirements that we would like
# to retry and build (stuff that can take time between partprobes)
requirements = []
requirements.append(self.partuuid)
# Unformatted partitions won't have a UUID
if lsblk_info.get('fstype') is not None:
requirements.append(self.uuid)
if all(requirements):
break
self.partition_object.partprobe()
time.sleep(max(0.1, storage['DISK_TIMEOUTS'] * i))
def get_first_mountpoint(self) -> Optional[Path]: def get_first_mountpoint(self) -> Optional[Path]:
if len(self.mountpoints) > 0: if len(self.mountpoints) > 0:
@ -154,8 +186,11 @@ class Partition:
output = error.worker.decode('UTF-8') output = error.worker.decode('UTF-8')
if output: if output:
lsblk_info = json.loads(output) try:
return lsblk_info lsblk_info = json.loads(output)
return lsblk_info
except json.decoder.JSONDecodeError:
log(f"Could not decode JSON: {output}", fg="red", level=logging.ERROR)
raise DiskError(f'Failed to read disk "{self.device_path}" with lsblk') raise DiskError(f'Failed to read disk "{self.device_path}" with lsblk')
@ -185,6 +220,8 @@ class Partition:
bootable = sfdisk_info.get('bootable', False) or sfdisk_info.get('type', '') == 'C12A7328-F81F-11D2-BA4B-00A0C93EC93B' bootable = sfdisk_info.get('bootable', False) or sfdisk_info.get('type', '') == 'C12A7328-F81F-11D2-BA4B-00A0C93EC93B'
return PartitionInfo( return PartitionInfo(
partition_object=self,
device_path=self._path,
pttype=device['pttype'], pttype=device['pttype'],
partuuid=device['partuuid'], partuuid=device['partuuid'],
uuid=device['uuid'], uuid=device['uuid'],
@ -568,6 +605,8 @@ class Partition:
except SysCallError as err: except SysCallError as err:
raise err raise err
# Update the partition info since the mount info has changed after this call.
self._partition_info = self._fetch_information()
return True return True
return False return False
@ -582,6 +621,8 @@ class Partition:
if exit_code and 0 < exit_code < 8000: if exit_code and 0 < exit_code < 8000:
raise SysCallError(f"Could not unmount {self._path} properly: {worker}", exit_code=exit_code) raise SysCallError(f"Could not unmount {self._path} properly: {worker}", exit_code=exit_code)
# Update the partition info since the mount info has changed after this call.
self._partition_info = self._fetch_information()
return True return True
def filesystem_supported(self) -> bool: def filesystem_supported(self) -> bool:

View File

@ -379,7 +379,7 @@ class SysCommandWorker:
try: try:
with history_logfile.open("a") as cmd_log: with history_logfile.open("a") as cmd_log:
cmd_log.write(f"{self.cmd}\n") cmd_log.write(f"{time.time()} {self.cmd}\n")
if change_perm: if change_perm:
os.chmod(str(history_logfile), stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP) os.chmod(str(history_logfile), stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP)