src: log backtrace in unhandled error cases

Log an error message in known error cases and log a backtrace
otherwise.

Define a new error type OgError to be used in all the 'raise'
blocks to define the error message to log. The exception
propagates until it reaches send_internal_server_error() where
the exception type is checked. If the type is OgError we log
the exception message. Logs the backtrace for other types.

The initial error implementation printed a backtrace everytime
an error ocurred. The next iteration changed it to only print
a backtrace in a very particular case but ended up omiting too
much information such as syntax errors or unknown error context.
The actual implementation only logs the cases we already cover in
the codebase and logs a bracktrace in the others, enabling a
better debugging experience.
master
Alejandro Sirgo Rica 2024-04-02 12:51:34 +02:00
parent c5ccc3c7e2
commit dfde363aa6
15 changed files with 110 additions and 93 deletions

View File

@ -20,7 +20,7 @@ except ImportError:
from src.ogClient import * from src.ogClient import *
from src.log import configure_logging from src.log import *
def send_event_dgram(msg, ip='127.0.0.1', port=55885): def send_event_dgram(msg, ip='127.0.0.1', port=55885):
@ -92,7 +92,7 @@ def main():
client = ogClient(config=CONFIG) client = ogClient(config=CONFIG)
client.connect() client.connect()
client.run() client.run()
except Exception as e: except OgError as e:
logging.critical(e) logging.critical(e)
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -35,6 +35,7 @@ from src.utils.uefi import *
from src.utils.boot import * from src.utils.boot import *
from src.utils.sw_inventory import get_package_set from src.utils.sw_inventory import get_package_set
from src.utils.hw_inventory import get_hardware_inventory, legacy_list_hardware_inventory from src.utils.hw_inventory import get_hardware_inventory, legacy_list_hardware_inventory
from src.log import OgError
OG_SHELL = '/bin/bash' OG_SHELL = '/bin/bash'
@ -52,7 +53,7 @@ class OgLiveOperations:
proc = subprocess.call(["pkill", "-9", "browser"]) proc = subprocess.call(["pkill", "-9", "browser"])
proc = subprocess.Popen(["browser", "-qws", url]) proc = subprocess.Popen(["browser", "-qws", url])
except Exception as e: except Exception as e:
raise RuntimeError('Cannot restart browser') from e raise OgError('Cannot restart browser') from e
def _refresh_payload_disk(self, cxt, part_setup, num_disk): def _refresh_payload_disk(self, cxt, part_setup, num_disk):
part_setup['disk'] = str(num_disk) part_setup['disk'] = str(num_disk)
@ -119,7 +120,7 @@ class OgLiveOperations:
def _write_md5_file(self, path): def _write_md5_file(self, path):
if not os.path.exists(path): if not os.path.exists(path):
raise ValueError(f'Invalid image path {path} when computing md5 checksum') raise OgError(f'Invalid image path {path} when computing md5 checksum')
filename = path + ".full.sum" filename = path + ".full.sum"
dig = self._compute_md5(path) dig = self._compute_md5(path)
try: try:
@ -144,12 +145,12 @@ class OgLiveOperations:
r = shutil.copy(src, dst) r = shutil.copy(src, dst)
tip_write_csum(image_name) tip_write_csum(image_name)
except Exception as e: except Exception as e:
raise RuntimeError(f'Error copying image {image_name} to cache. Reported: {e}') from e raise OgError(f'Error copying image {image_name} to cache. Reported: {e}') from e
def _restore_image_unicast(self, repo, name, devpath, cache=False): def _restore_image_unicast(self, repo, name, devpath, cache=False):
if ogChangeRepo(repo, smb_user=self._smb_user, smb_pass=self._smb_pass) != 0: if ogChangeRepo(repo, smb_user=self._smb_user, smb_pass=self._smb_pass) != 0:
self._restartBrowser(self._url) self._restartBrowser(self._url)
raise ValueError(f'Cannot change repository to {repo}') raise OgError(f'Cannot change repository to {repo}')
logging.debug(f'restore_image_unicast: name => {name}') logging.debug(f'restore_image_unicast: name => {name}')
if cache: if cache:
image_path = f'/opt/opengnsys/cache/opt/opengnsys/images/{name}.img' image_path = f'/opt/opengnsys/cache/opt/opengnsys/images/{name}.img'
@ -162,7 +163,7 @@ class OgLiveOperations:
def _restore_image_tiptorrent(self, repo, name, devpath): def _restore_image_tiptorrent(self, repo, name, devpath):
if not os.path.exists(OG_CACHE_PATH): if not os.path.exists(OG_CACHE_PATH):
raise RuntimeError('No cache partition is mounted') raise OgError('No cache partition is mounted')
image_path = f'/opt/opengnsys/cache/opt/opengnsys/images/{name}.img' image_path = f'/opt/opengnsys/cache/opt/opengnsys/images/{name}.img'
try: try:
@ -172,11 +173,11 @@ class OgLiveOperations:
except: except:
self._restartBrowser(self._url) self._restartBrowser(self._url)
if (not os.path.exists(image_path)): if (not os.path.exists(image_path)):
raise RuntimeError(f'Image file {image_path} does not exist') raise OgError(f'Image file {image_path} does not exist')
if (not tip_check_csum(repo, name)): if (not tip_check_csum(repo, name)):
raise RuntimeError(f'checksum file {name}.full.sum is missing in repository {repo}') raise OgError(f'checksum file {name}.full.sum is missing in repository {repo}')
raise RuntimeError(f'Unexpected error when restoring image file {image_path}') raise OgError(f'Unexpected error when restoring image file {image_path}')
self._restore_image(image_path, devpath) self._restore_image(image_path, devpath)
@ -188,7 +189,7 @@ class OgLiveOperations:
cmd_pc = shlex.split(f'partclone.restore -d0 -C -I -o {devpath}') cmd_pc = shlex.split(f'partclone.restore -d0 -C -I -o {devpath}')
if not os.path.exists(image_path): if not os.path.exists(image_path):
raise RuntimeError(f'Image not found at {image_path} during image restore') raise OgError(f'Image not found at {image_path} during image restore')
with open('/tmp/command.log', 'wb', 0) as logfile: with open('/tmp/command.log', 'wb', 0) as logfile:
proc_lzop = subprocess.Popen(cmd_lzop, proc_lzop = subprocess.Popen(cmd_lzop,
@ -245,7 +246,7 @@ class OgLiveOperations:
executable=OG_SHELL) executable=OG_SHELL)
(output, error) = ogRest.proc.communicate() (output, error) = ogRest.proc.communicate()
except Exception as e: except Exception as e:
raise RuntimeError(f'Error when running "shell run" subprocess: {e}') from e raise OgError(f'Error when running "shell run" subprocess: {e}') from e
if ogRest.proc.returncode != 0: if ogRest.proc.returncode != 0:
logging.warn('Non zero exit code when running: %s', ' '.join(cmds)) logging.warn('Non zero exit code when running: %s', ' '.join(cmds))
@ -269,9 +270,9 @@ class OgLiveOperations:
partdev = get_partition_device(int(disk), int(partition)) partdev = get_partition_device(int(disk), int(partition))
mountpoint = partdev.replace('dev', 'mnt') mountpoint = partdev.replace('dev', 'mnt')
if not mount_mkdir(partdev, mountpoint): if not mount_mkdir(partdev, mountpoint):
raise RuntimeError(f'Error mounting {partdev} at {mountpoint}') raise OgError(f'Error mounting {partdev} at {mountpoint}')
if not os.path.ismount(mountpoint): if not os.path.ismount(mountpoint):
raise RuntimeError(f'Invalid mountpoint {mountpoint} for software inventory') raise OgError(f'Invalid mountpoint {mountpoint} for software inventory')
self._restartBrowser(self._url_log) self._restartBrowser(self._url_log)
pkgset = get_package_set(mountpoint) pkgset = get_package_set(mountpoint)
@ -294,7 +295,7 @@ class OgLiveOperations:
try: try:
inventory = get_hardware_inventory() inventory = get_hardware_inventory()
except Exception as e: except Exception as e:
raise RuntimeError(f'Error while running hardware inventory. {e}') from e raise OgError(f'Error while running hardware inventory. {e}') from e
finally: finally:
self._restartBrowser(self._url) self._restartBrowser(self._url)
@ -321,7 +322,7 @@ class OgLiveOperations:
elif table_type == 'GPT': elif table_type == 'GPT':
cxt.create_disklabel('gpt') cxt.create_disklabel('gpt')
else: else:
raise ValueError(f'Unsupported partition scheme {table_type}, only MSDOS and GPT are supported') raise OgError(f'Unsupported partition scheme {table_type}, only MSDOS and GPT are supported')
logging.info(f'Setting up partition layout to {table_type}') logging.info(f'Setting up partition layout to {table_type}')
@ -415,7 +416,7 @@ class OgLiveOperations:
if ogChangeRepo(repo, smb_user=self._smb_user, smb_pass=self._smb_pass) != 0: if ogChangeRepo(repo, smb_user=self._smb_user, smb_pass=self._smb_pass) != 0:
self._restartBrowser(self._url) self._restartBrowser(self._url)
raise RuntimeError(f'Cannot change image repository to {repo}') raise OgError(f'Cannot change image repository to {repo}')
if ogRest.terminated: if ogRest.terminated:
return return
@ -431,25 +432,25 @@ class OgLiveOperations:
if pa is None: if pa is None:
self._restartBrowser(self._url) self._restartBrowser(self._url)
raise RuntimeError(f'Target partition /dev/{diskname} not found') raise OgError(f'Target partition /dev/{diskname} not found')
padev = cxt.partition_to_string(pa, fdisk.FDISK_FIELD_DEVICE) padev = cxt.partition_to_string(pa, fdisk.FDISK_FIELD_DEVICE)
fstype = cxt.partition_to_string(pa, fdisk.FDISK_FIELD_FSTYPE) fstype = cxt.partition_to_string(pa, fdisk.FDISK_FIELD_FSTYPE)
if not fstype: if not fstype:
raise RuntimeError(f'No filesystem detected in {padev}. Aborting image creation') raise OgError(f'No filesystem detected in {padev}. Aborting image creation')
if change_access(user=self._smb_user, pwd=self._smb_pass) == -1: if change_access(user=self._smb_user, pwd=self._smb_pass) == -1:
raise RuntimeError('remount of /opt/opengnsys/images has failed') raise OgError('remount of /opt/opengnsys/images has failed')
if os.access(f'/opt/opengnsys/images', os.R_OK | os.W_OK) == False: if os.access(f'/opt/opengnsys/images', os.R_OK | os.W_OK) == False:
raise RuntimeError('Cannot access /opt/opengnsys/images in read and write mode, check permissions') raise OgError('Cannot access /opt/opengnsys/images in read and write mode, check permissions')
if os.access(f'{image_path}', os.R_OK) == True: if os.access(f'{image_path}', os.R_OK) == True:
logging.info(f'image file {image_path} already exists, updating.') logging.info(f'image file {image_path} already exists, updating.')
copy_windows_efi_bootloader(disk, partition) copy_windows_efi_bootloader(disk, partition)
if ogReduceFs(disk, partition) == -1: if ogReduceFs(disk, partition) == -1:
raise ValueError(f'Failed to shrink {fstype} filesystem in {padev}') raise OgError(f'Failed to shrink {fstype} filesystem in {padev}')
cmd1 = shlex.split(f'partclone.{fstype} -I -C --clone -s {padev} -O -') cmd1 = shlex.split(f'partclone.{fstype} -I -C --clone -s {padev} -O -')
cmd2 = shlex.split(f'lzop -1 -fo {image_path}') cmd2 = shlex.split(f'lzop -1 -fo {image_path}')
@ -469,7 +470,7 @@ class OgLiveOperations:
try: try:
retdata = p2.communicate() retdata = p2.communicate()
except OSError as e: except OSError as e:
raise OSError(f'Unexpected error when running partclone and lzop commands: {e}') from e raise OgError(f'Unexpected error when running partclone and lzop commands: {e}') from e
finally: finally:
logfile.close() logfile.close()
p2.terminate() p2.terminate()
@ -481,7 +482,7 @@ class OgLiveOperations:
ogExtendFs(disk, partition) ogExtendFs(disk, partition)
if os.access(f'{image_path}', os.R_OK) == False: if os.access(f'{image_path}', os.R_OK) == False:
raise RuntimeError(f'Cannot access partclone image file {image_path}') raise OgError(f'Cannot access partclone image file {image_path}')
image_info = ogGetImageInfo(image_path) image_info = ogGetImageInfo(image_path)
except Exception as e: except Exception as e:
@ -492,7 +493,7 @@ class OgLiveOperations:
shutil.move(f'{image_path}.ant', image_path) shutil.move(f'{image_path}.ant', image_path)
self._restartBrowser(self._url) self._restartBrowser(self._url)
raise RuntimeError(f'Failed to create image for {fstype} filesystem in device {padev}: {e}') from e raise OgError(f'Failed to create image for {fstype} filesystem in device {padev}: {e}') from e
try: try:
st = os.stat(image_path) st = os.stat(image_path)
@ -510,7 +511,7 @@ class OgLiveOperations:
image_info.mtime = mtime image_info.mtime = mtime
if self._write_md5_file(f'/opt/opengnsys/images/{name}.img') == -1: if self._write_md5_file(f'/opt/opengnsys/images/{name}.img') == -1:
raise ValueError(f'Cannot write {name}.full.sum file') raise OgError(f'Cannot write {name}.full.sum file')
self._restartBrowser(self._url) self._restartBrowser(self._url)

View File

@ -7,6 +7,7 @@
# (at your option) any later version. # (at your option) any later version.
import fdisk import fdisk
from src.log import OgError
GPT_PARTTYPES = { GPT_PARTTYPES = {
'LINUX-SWAP': '0657FD6D-A4AB-43C4-84E5-0933C84B4F4F', 'LINUX-SWAP': '0657FD6D-A4AB-43C4-84E5-0933C84B4F4F',
@ -43,15 +44,15 @@ def get_gpt_parttype(cxt, ptype_str):
def get_parttype(cxt, ptype_str): def get_parttype(cxt, ptype_str):
if not cxt: if not cxt:
raise RuntimeError('No libfdisk context') raise OgError('No libfdisk context')
if not cxt.label or cxt.label.name not in ['dos', 'gpt']: if not cxt.label or cxt.label.name not in ['dos', 'gpt']:
raise RuntimeError('Unknown libfdisk label') raise OgError('Unknown libfdisk label')
if type(ptype_str) != str: if type(ptype_str) != str:
raise RuntimeError('Invalid partition type') raise OgError('Invalid partition type')
if cxt.label.name == 'dos': if cxt.label.name == 'dos':
return get_dos_parttype(cxt, ptype_str) return get_dos_parttype(cxt, ptype_str)
elif cxt.label.name == 'gpt': elif cxt.label.name == 'gpt':
return get_gpt_parttype(cxt, ptype_str) return get_gpt_parttype(cxt, ptype_str)
else: else:
raise RuntimeError(f'Invalid partition label \'{cxt.label.name}\'') raise OgError(f'Invalid partition label \'{cxt.label.name}\'')

View File

@ -11,6 +11,10 @@ import logging.config
import os import os
class OgError(Exception):
pass
def _default_logging_linux(): def _default_logging_linux():
from src.utils.net import getifaddr from src.utils.net import getifaddr
logconfig = { logconfig = {
@ -125,7 +129,7 @@ def configure_logging(mode, level):
elif mode == 'live': elif mode == 'live':
logconfig = _default_logging_live() logconfig = _default_logging_live()
else: else:
raise ValueError(f'Logging mode {mode} not supported') raise OgError(f'Logging mode {mode} not supported')
logconfig['loggers']['']['level'] = level logconfig['loggers']['']['level'] = level

View File

@ -16,6 +16,7 @@ from io import StringIO
from src.restRequest import * from src.restRequest import *
from src.ogRest import * from src.ogRest import *
from src.log import OgError
from enum import Enum from enum import Enum
class State(Enum): class State(Enum):
@ -31,7 +32,7 @@ class ogClient:
self.mode = self.CONFIG['opengnsys']['mode'] self.mode = self.CONFIG['opengnsys']['mode']
if self.mode not in {'virtual', 'live', 'linux', 'windows'}: if self.mode not in {'virtual', 'live', 'linux', 'windows'}:
raise ValueError(f'Invalid ogClient mode: {self.mode}.') raise OgError(f'Invalid ogClient mode: {self.mode}.')
if self.mode in {'linux', 'windows'}: if self.mode in {'linux', 'windows'}:
self.event_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.event_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.event_sock.setblocking(0) self.event_sock.setblocking(0)
@ -190,4 +191,4 @@ class ogClient:
message = event_sock.recv(4096).decode('utf-8').rstrip() message = event_sock.recv(4096).decode('utf-8').rstrip()
self.send_event_hint(message) self.send_event_hint(message)
else: else:
raise ValueError(f'Invalid ogClient run state: {str(state)}.') raise OgError(f'Invalid ogClient run state: {str(state)}.')

View File

@ -19,6 +19,7 @@ import logging
from logging.handlers import SysLogHandler from logging.handlers import SysLogHandler
from src.restRequest import * from src.restRequest import *
from src.log import OgError
class ThreadState(Enum): class ThreadState(Enum):
@ -263,13 +264,13 @@ class ogRest():
from src.windows.ogOperations import OgWindowsOperations from src.windows.ogOperations import OgWindowsOperations
self.operations = OgWindowsOperations() self.operations = OgWindowsOperations()
else: else:
raise ValueError(f'Ogrest mode \'{self.mode}\'not supported') raise OgError(f'Ogrest mode \'{self.mode}\'not supported')
def send_internal_server_error(self, client, exc=None): def send_internal_server_error(self, client, exc=None):
if isinstance(exc, AssertionError): if isinstance(exc, OgError):
logging.exception(exc)
else:
logging.error(exc) logging.error(exc)
else:
logging.exception(exc)
response = restResponse(ogResponses.INTERNAL_ERR, seq=client.seq) response = restResponse(ogResponses.INTERNAL_ERR, seq=client.seq)
client.send(response.get()) client.send(response.get())
self.state = ThreadState.IDLE self.state = ThreadState.IDLE

View File

@ -8,6 +8,7 @@
import logging import logging
import os import os
from src.log import OgError
def get_grub_boot_params(mountpoint, device): def get_grub_boot_params(mountpoint, device):
@ -35,7 +36,7 @@ def get_vmlinuz_path(mountpoint):
target_file = file target_file = file
if not target_file: if not target_file:
raise FileNotFoundError(f'vmlinuz not found in {initrd_dir}') raise OgError(f'vmlinuz not found in {initrd_dir}')
return os.path.join(linuz_dir, target_file) return os.path.join(linuz_dir, target_file)
@ -48,6 +49,6 @@ def get_initrd_path(mountpoint):
target_file = file target_file = file
if not target_file: if not target_file:
raise FileNotFoundError(f'initrd not found in {initrd_dir}') raise OgError(f'initrd not found in {initrd_dir}')
return os.path.join(initrd_dir, target_file) return os.path.join(initrd_dir, target_file)

View File

@ -17,13 +17,14 @@ from src.utils.disk import get_partition_device, get_efi_partition
from src.utils.bios import * from src.utils.bios import *
from src.utils.uefi import * from src.utils.uefi import *
from src.utils.fs import * from src.utils.fs import *
from src.log import OgError
def _boot_bios_linux(disk, part, mountpoint): def _boot_bios_linux(disk, part, mountpoint):
logging.info(f'Booting Linux system') logging.info(f'Booting Linux system')
if not get_linux_distro_id(mountpoint) == 'ubuntu': if not get_linux_distro_id(mountpoint) == 'ubuntu':
raise NotImplementedError(f'{os_probe(mountpoint)} detected, only Ubuntu is supported for legacy BIOS boot') raise OgError(f'{os_probe(mountpoint)} detected, only Ubuntu is supported for legacy BIOS boot')
kernel_path = get_vmlinuz_path(mountpoint) kernel_path = get_vmlinuz_path(mountpoint)
initrd_path = get_initrd_path(mountpoint) initrd_path = get_initrd_path(mountpoint)
@ -39,7 +40,7 @@ def _boot_bios_linux(disk, part, mountpoint):
subprocess.run(shlex.split(kexec_cmd), check=True, text=True) subprocess.run(shlex.split(kexec_cmd), check=True, text=True)
subprocess.run(shlex.split(kexec_reboot_cmd), check=True, text=True) subprocess.run(shlex.split(kexec_reboot_cmd), check=True, text=True)
except OSError as e: except OSError as e:
raise OSError(f'Error processing kexec: {e}') from e raise OgError(f'Error processing kexec: {e}') from e
def _boot_bios_windows(disk, part, mountpoint): def _boot_bios_windows(disk, part, mountpoint):
logging.info(f'Booting Windows system') logging.info(f'Booting Windows system')
@ -52,7 +53,7 @@ def _boot_bios_windows(disk, part, mountpoint):
with open(f'{mountpoint}/ogboot.secondboot', 'w') as f: with open(f'{mountpoint}/ogboot.secondboot', 'w') as f:
f.write('\0' * (3072)) f.write('\0' * (3072))
except OSError as e: except OSError as e:
raise OSError(f'Could not create ogboot files in Windows partition: {e}') from e raise OgError(f'Could not create ogboot files in Windows partition: {e}') from e
def _boot_uefi_windows(disk, part, mountpoint): def _boot_uefi_windows(disk, part, mountpoint):
logging.info(f'Booting windows system') logging.info(f'Booting windows system')
@ -60,7 +61,7 @@ def _boot_uefi_windows(disk, part, mountpoint):
esp, esp_disk, esp_part_number = get_efi_partition(disk, enforce_gpt=True) esp, esp_disk, esp_part_number = get_efi_partition(disk, enforce_gpt=True)
esp_mountpoint = esp.replace('dev', 'mnt') esp_mountpoint = esp.replace('dev', 'mnt')
if not mount_mkdir(esp, esp_mountpoint): if not mount_mkdir(esp, esp_mountpoint):
raise RuntimeError(f'Unable to mount detected EFI System Partition at {esp} into {esp_mountpoint}') raise OgError(f'Unable to mount detected EFI System Partition at {esp} into {esp_mountpoint}')
loader_paths = [f'{esp_mountpoint}/EFI/{bootlabel}/Boot/bootmgfw.efi', loader_paths = [f'{esp_mountpoint}/EFI/{bootlabel}/Boot/bootmgfw.efi',
f'{esp_mountpoint}/EFI/Microsoft/Boot/bootmgfw.efi'] f'{esp_mountpoint}/EFI/Microsoft/Boot/bootmgfw.efi']
@ -71,7 +72,7 @@ def _boot_uefi_windows(disk, part, mountpoint):
logging.info(f'Found bootloader at ESP partition: {loader}') logging.info(f'Found bootloader at ESP partition: {loader}')
break break
else: else:
raise RuntimeError(f'Unable to locate Windows EFI bootloader bootmgfw.efi') raise OgError(f'Unable to locate Windows EFI bootloader bootmgfw.efi')
efibootmgr_delete_bootentry(bootlabel) efibootmgr_delete_bootentry(bootlabel)
efibootmgr_create_bootentry(esp_disk, esp_part_number, loader, bootlabel) efibootmgr_create_bootentry(esp_disk, esp_part_number, loader, bootlabel)
@ -85,7 +86,7 @@ def _boot_uefi_linux(disk, part, mountpoint):
esp, esp_disk, esp_part_number = get_efi_partition(disk, enforce_gpt=False) esp, esp_disk, esp_part_number = get_efi_partition(disk, enforce_gpt=False)
esp_mountpoint = esp.replace('dev', 'mnt') esp_mountpoint = esp.replace('dev', 'mnt')
if not mount_mkdir(esp, esp_mountpoint): if not mount_mkdir(esp, esp_mountpoint):
raise RuntimeError(f'Unable to mount detected EFI System Partition at {esp} into {esp_mountpoint}') raise OgError(f'Unable to mount detected EFI System Partition at {esp} into {esp_mountpoint}')
loader_paths = [f'{esp_mountpoint}/EFI/{bootlabel}/Boot/shimx64.efi', loader_paths = [f'{esp_mountpoint}/EFI/{bootlabel}/Boot/shimx64.efi',
f'{esp_mountpoint}/EFI/ubuntu/shimx64.efi'] f'{esp_mountpoint}/EFI/ubuntu/shimx64.efi']
@ -96,7 +97,7 @@ def _boot_uefi_linux(disk, part, mountpoint):
logging.info(f'Found bootloader at ESP partition: {loader}') logging.info(f'Found bootloader at ESP partition: {loader}')
break break
else: else:
raise RuntimeError(f'Unable to locate Linux EFI bootloader shimx64.efi') raise OgError(f'Unable to locate Linux EFI bootloader shimx64.efi')
efibootmgr_delete_bootentry(bootlabel) efibootmgr_delete_bootentry(bootlabel)
efibootmgr_create_bootentry(esp_disk, esp_part_number, loader, bootlabel) efibootmgr_create_bootentry(esp_disk, esp_part_number, loader, bootlabel)
@ -109,7 +110,7 @@ def boot_os_at(disk, part):
device = get_partition_device(disk, part) device = get_partition_device(disk, part)
mountpoint = device.replace('dev', 'mnt') mountpoint = device.replace('dev', 'mnt')
if not mount_mkdir(device, mountpoint): if not mount_mkdir(device, mountpoint):
raise RuntimeError(f'Cannot probe OS family. Unable to mount {device} into {mountpoint}') raise OgError(f'Cannot probe OS family. Unable to mount {device} into {mountpoint}')
is_uefi = is_uefi_supported() is_uefi = is_uefi_supported()
if is_uefi: if is_uefi:
@ -130,6 +131,6 @@ def boot_os_at(disk, part):
elif not is_uefi and os_family == OSFamily.LINUX: elif not is_uefi and os_family == OSFamily.LINUX:
_boot_bios_linux(disk, part, mountpoint) _boot_bios_linux(disk, part, mountpoint)
else: else:
raise RuntimeError(f'Unknown OS family {os_family}') raise OgError(f'Unknown OS family {os_family}')
finally: finally:
umount(mountpoint) umount(mountpoint)

View File

@ -8,6 +8,7 @@
import os import os
import logging import logging
from src.log import OgError
import fdisk import fdisk
@ -29,7 +30,7 @@ def get_partition_device(disknum, partnum):
""" """
disk_index = disknum - 1 disk_index = disknum - 1
if disk_index < 0 or disk_index >= len(get_disks()): if disk_index < 0 or disk_index >= len(get_disks()):
raise ValueError(f'Invalid disk number {disknum}, {len(get_disks())} disks available.') raise OgError(f'Invalid disk number {disknum}, {len(get_disks())} disks available.')
disk = get_disks()[disk_index] disk = get_disks()[disk_index]
cxt = fdisk.Context(f'/dev/{disk}') cxt = fdisk.Context(f'/dev/{disk}')
@ -38,7 +39,7 @@ def get_partition_device(disknum, partnum):
if pa.partno == partnum - 1: if pa.partno == partnum - 1:
return cxt.partition_to_string(pa, fdisk.FDISK_FIELD_DEVICE) return cxt.partition_to_string(pa, fdisk.FDISK_FIELD_DEVICE)
raise ValueError(f'No such partition with disk index {disknum} and partition index {partnum}') raise OgError(f'No such partition with disk index {disknum} and partition index {partnum}')
def get_efi_partition(disknum, enforce_gpt): def get_efi_partition(disknum, enforce_gpt):
@ -55,16 +56,16 @@ def get_efi_partition(disknum, enforce_gpt):
""" """
disk_index = disknum - 1 disk_index = disknum - 1
if disk_index < 0 or disk_index >= len(get_disks()): if disk_index < 0 or disk_index >= len(get_disks()):
raise ValueError(f'Invalid disk number {disknum} when trying to find ESP, {len(get_disks())} disks available.') raise OgError(f'Invalid disk number {disknum} when trying to find ESP, {len(get_disks())} disks available.')
disk = get_disks()[disk_index] disk = get_disks()[disk_index]
cxt = fdisk.Context(f'/dev/{disk}') cxt = fdisk.Context(f'/dev/{disk}')
if enforce_gpt and cxt.label == fdisk.FDISK_DISKLABEL_DOS: if enforce_gpt and cxt.label == fdisk.FDISK_DISKLABEL_DOS:
raise RuntimeError(f'Windows EFI System requires GPT partition scheme, but /dev/{disk} has DOS partition scheme') raise OgError(f'Windows EFI System requires GPT partition scheme, but /dev/{disk} has DOS partition scheme')
for pa in cxt.partitions: for pa in cxt.partitions:
logging.info(f'Checking partition "{pa.type.name}"...') logging.info(f'Checking partition "{pa.type.name}"...')
if pa.type.name == 'EFI System': if pa.type.name == 'EFI System':
return cxt.partition_to_string(pa, fdisk.FDISK_FIELD_DEVICE), f'/dev/{disk}', pa.partno + 1 return cxt.partition_to_string(pa, fdisk.FDISK_FIELD_DEVICE), f'/dev/{disk}', pa.partno + 1
raise RuntimeError(f'Cannot find "EFI System" partition at /dev/{disk}') raise OgError(f'Cannot find "EFI System" partition at /dev/{disk}')

View File

@ -10,6 +10,7 @@ import logging
import os import os
import subprocess import subprocess
import shlex import shlex
from src.log import OgError
from subprocess import DEVNULL, PIPE, STDOUT from subprocess import DEVNULL, PIPE, STDOUT
@ -148,12 +149,12 @@ def mkfs(fs, disk, partition, label=None):
} }
if fs not in fsdict: if fs not in fsdict:
raise ValueError(f'mkfs failed, unsupported target filesystem {fs}') raise OgError(f'mkfs failed, unsupported target filesystem {fs}')
try: try:
partdev = get_partition_device(disk, partition) partdev = get_partition_device(disk, partition)
except ValueError as e: except ValueError as e:
raise ValueError(f'mkfs aborted: {e}') from e raise OgError(f'mkfs aborted: {e}') from e
fsdict[fs](partdev, label) fsdict[fs](partdev, label)
@ -249,11 +250,11 @@ def _reduce_ntfsresize(partdev):
data_split = output_data.split(pattern) data_split = output_data.split(pattern)
# If we fail to match pattern in the split then data_split will contain [output_data] # If we fail to match pattern in the split then data_split will contain [output_data]
if len(data_split) == 1: if len(data_split) == 1:
raise ValueError(f'nfsresize: failed to find: {pattern}') raise OgError(f'nfsresize: failed to find: {pattern}')
value_str = data_split[1].split(' ')[0] value_str = data_split[1].split(' ')[0]
if not value_str.isdigit() or value_str.startswith('-'): if not value_str.isdigit() or value_str.startswith('-'):
raise ValueError(f'nfsresize: failed to parse numeric value at {pattern}') raise OgError(f'nfsresize: failed to parse numeric value at {pattern}')
return int(value_str) return int(value_str)
try: try:
@ -307,11 +308,11 @@ def _extend_resize2fs(partdev):
cmd = shlex.split(f'resize2fs -f {partdev}') cmd = shlex.split(f'resize2fs -f {partdev}')
proc = subprocess.run(cmd) proc = subprocess.run(cmd)
if proc.returncode != 0: if proc.returncode != 0:
raise RuntimeError(f'Error growing ext4 filesystem at {partdev}') raise OgError(f'Error growing ext4 filesystem at {partdev}')
def _extend_ntfsresize(partdev): def _extend_ntfsresize(partdev):
cmd = shlex.split(f'ntfsresize -f {partdev}') cmd = shlex.split(f'ntfsresize -f {partdev}')
proc = subprocess.run(cmd, input=b'y') proc = subprocess.run(cmd, input=b'y')
if proc.returncode != 0: if proc.returncode != 0:
raise RuntimeError(f'Error growing ntfs filesystem at {partdev}') raise OgError(f'Error growing ntfs filesystem at {partdev}')

View File

@ -10,6 +10,7 @@ import json
import os.path import os.path
import shlex import shlex
import subprocess import subprocess
from src.log import OgError
from collections import namedtuple from collections import namedtuple
from enum import Enum, auto from enum import Enum, auto
@ -55,16 +56,16 @@ class HardwareInventory():
def add_element(self, elem): def add_element(self, elem):
if elem.type not in HardwareType: if elem.type not in HardwareType:
raise ValueError(f'Unsupported hardware type, received {elem.type}') raise OgError(f'Unsupported hardware type, received {elem.type}')
if not elem.name: if not elem.name:
raise ValueError('Empty hardware element name') raise OgError('Empty hardware element name')
self.elements.append(elem) self.elements.append(elem)
def _bytes_to_human(size): def _bytes_to_human(size):
suffixes = ['B', 'MiB', 'GiB', 'TiB'] suffixes = ['B', 'MiB', 'GiB', 'TiB']
if type(size) is not int: if type(size) is not int:
raise TypeError(f'Invalid type in _bytes_to_human, got: {size} {type(size)}') raise OgError(f'Invalid type in _bytes_to_human, got: {size} {type(size)}')
for exponent, suffix in enumerate(suffixes, start=1): for exponent, suffix in enumerate(suffixes, start=1):
conv = size / (1024**exponent) conv = size / (1024**exponent)
if conv < 1024: if conv < 1024:
@ -250,7 +251,7 @@ def legacy_hardware_element(element):
represented as "vga=Foo" represented as "vga=Foo"
""" """
if type(element) is not HardwareElement: if type(element) is not HardwareElement:
raise TypeError('Invalid hardware element type') raise OgError('Invalid hardware element type')
elif element.type is HardwareType.MULTIMEDIA: elif element.type is HardwareType.MULTIMEDIA:
nemonic = 'mul' nemonic = 'mul'
elif element.type is HardwareType.BOOTMODE: elif element.type is HardwareType.BOOTMODE:
@ -297,7 +298,7 @@ def get_hardware_inventory():
if type(j) is list: if type(j) is list:
root = j[0] root = j[0]
if type(root) is not dict: if type(root) is not dict:
raise ValueError('Invalid lshw json output') raise OgError('Invalid lshw json output')
inventory = HardwareInventory() inventory = HardwareInventory()
_fill_computer_model(inventory, root) _fill_computer_model(inventory, root)

View File

@ -16,6 +16,7 @@ import shutil
from subprocess import PIPE, DEVNULL, STDOUT, CalledProcessError from subprocess import PIPE, DEVNULL, STDOUT, CalledProcessError
from src.utils.fs import umount from src.utils.fs import umount
from src.log import OgError
class ImageInfo: class ImageInfo:
@ -75,9 +76,9 @@ def image_info_from_partclone(partclone_output):
fill_imageinfo(line, image_info) fill_imageinfo(line, image_info)
if not image_info.datasize: if not image_info.datasize:
raise ValueError("Missing device size from partclone.info output") raise OgError("Missing device size from partclone.info output")
elif not image_info.filesystem: elif not image_info.filesystem:
raise ValueError("Missing filesystem from partclone.info output") raise OgError("Missing filesystem from partclone.info output")
return image_info return image_info
@ -100,7 +101,7 @@ def run_lzop_partcloneinfo(image_path):
p2_out, p2_err = p2.communicate() p2_out, p2_err = p2.communicate()
if p2.returncode != 0: if p2.returncode != 0:
raise ValueError(f'Unable to process image {image_path}') raise OgError(f'Unable to process image {image_path}')
return p2_out return p2_out
@ -174,7 +175,7 @@ def ogChangeRepo(ip, smb_user='opengnsys', smb_pass='og'):
try: try:
ipaddr = ipaddress.ip_address(ip) ipaddr = ipaddress.ip_address(ip)
except ValueError as e: except ValueError as e:
raise ValueError(f'Invalid IP address {ip} received') raise OgError(f'Invalid IP address {ip} received') from e
mounted = False mounted = False
with open('/etc/mtab') as f: with open('/etc/mtab') as f:
@ -209,7 +210,7 @@ def restoreImageCustom(repo_ip, image_name, disk, partition, method):
""" """
""" """
if not shutil.which('restoreImageCustom'): if not shutil.which('restoreImageCustom'):
raise OSError('restoreImageCustom not found') raise OgError('restoreImageCustom not found')
cmd = f'restoreImageCustom {repo_ip} {image_name} {disk} {partition} {method}' cmd = f'restoreImageCustom {repo_ip} {image_name} {disk} {partition} {method}'
with open('/tmp/command.log', 'wb', 0) as logfile: with open('/tmp/command.log', 'wb', 0) as logfile:
@ -220,7 +221,7 @@ def restoreImageCustom(repo_ip, image_name, disk, partition, method):
shell=True, shell=True,
check=True) check=True)
except OSError as e: except OSError as e:
raise OSError(f'Error processing restoreImageCustom: {e}') from e raise OgError(f'Error processing restoreImageCustom: {e}') from e
return proc.returncode return proc.returncode
@ -240,6 +241,6 @@ def configureOs(disk, partition):
check=True) check=True)
out = proc.stdout out = proc.stdout
except OSError as e: except OSError as e:
raise OSError(f'Error processing configureOsCustom: {e}') from e raise OgError(f'Error processing configureOsCustom: {e}') from e
return out return out

View File

@ -13,6 +13,7 @@ import shlex
import shutil import shutil
import subprocess import subprocess
import urllib.request import urllib.request
from src.log import OgError
def _compute_md5(path, bs=2**20): def _compute_md5(path, bs=2**20):
m = hashlib.md5() m = hashlib.md5()
@ -33,9 +34,9 @@ def tip_fetch_csum(tip_addr, image_name):
with urllib.request.urlopen(f'{url}') as resp: with urllib.request.urlopen(f'{url}') as resp:
r = resp.readline().rstrip().decode('utf-8') r = resp.readline().rstrip().decode('utf-8')
except urllib.error.URLError as e: except urllib.error.URLError as e:
raise urllib.error.URLError(f'URL error when fetching checksum: {e.reason}') from e raise OgError(f'URL error when fetching checksum: {e.reason}') from e
except urllib.error.HTTPError as e: except urllib.error.HTTPError as e:
raise urllib.error.URLError(f'HTTP Error when fetching checksum: {e.reason}') from e raise OgError(f'HTTP Error when fetching checksum: {e.reason}') from e
return r return r
@ -46,7 +47,7 @@ def tip_write_csum(image_name):
image_path = f'/opt/opengnsys/cache/opt/opengnsys/images/{image_name}.img' image_path = f'/opt/opengnsys/cache/opt/opengnsys/images/{image_name}.img'
if not os.path.exists(image_path): if not os.path.exists(image_path):
raise RuntimeError(f'Invalid image path {image_path} for tiptorrent checksum writing') raise OgError(f'Invalid image path {image_path} for tiptorrent checksum writing')
filename = image_path + ".full.sum" filename = image_path + ".full.sum"
csum = _compute_md5(image_path) csum = _compute_md5(image_path)
@ -62,7 +63,7 @@ def tip_check_csum(tip_addr, image_name):
logging.info(f'Verifying checksum for {image_name}.img, please wait...') logging.info(f'Verifying checksum for {image_name}.img, please wait...')
image_path = f'/opt/opengnsys/cache/opt/opengnsys/images/{image_name}.img' image_path = f'/opt/opengnsys/cache/opt/opengnsys/images/{image_name}.img'
if not os.path.exists(image_path): if not os.path.exists(image_path):
raise RuntimeError(f'Invalid image path {image_path} for tiptorrent image csum comparison') raise OgError(f'Invalid image path {image_path} for tiptorrent image csum comparison')
cache_csum = _compute_md5(image_path) cache_csum = _compute_md5(image_path)
remote_csum = tip_fetch_csum(tip_addr, image_name) remote_csum = tip_fetch_csum(tip_addr, image_name)
@ -85,12 +86,12 @@ def tip_client_get(tip_addr, image_name):
cwd='/opt/opengnsys/cache/opt/opengnsys/images/') cwd='/opt/opengnsys/cache/opt/opengnsys/images/')
proc.communicate() proc.communicate()
except OSError as e: except OSError as e:
raise OSError('Unexpected error running tiptorrent subprocess: {e}') from e raise OgError('Unexpected error running tiptorrent subprocess: {e}') from e
finally: finally:
logfile.close() logfile.close()
if proc.returncode != 0: if proc.returncode != 0:
raise RuntimeError(f'Error fetching image {image_name} via tiptorrent') raise OgError(f'Error fetching image {image_name} via tiptorrent')
else: else:
logging.info('Calculating checksum...') logging.info('Calculating checksum...')
logging.info('*DO NOT REBOOT OR POWEROFF* the client during this time') logging.info('*DO NOT REBOOT OR POWEROFF* the client during this time')

View File

@ -15,6 +15,7 @@ import shutil
from src.utils.disk import * from src.utils.disk import *
from src.utils.fs import * from src.utils.fs import *
from src.utils.probe import * from src.utils.probe import *
from src.log import OgError
import fdisk import fdisk
@ -25,14 +26,14 @@ def _find_bootentry(entries, label):
if entry['description'] == label: if entry['description'] == label:
return entry return entry
else: else:
raise NameError('Boot entry {label} not found') raise OgError('Boot entry {label} not found')
def _strip_boot_prefix(entry): def _strip_boot_prefix(entry):
try: try:
num = entry['name'][4:] num = entry['name'][4:]
except: except:
raise KeyError('Unable to strip "Boot" prefix from boot entry') raise OgError('Unable to strip "Boot" prefix from boot entry')
return num return num
@ -56,7 +57,7 @@ def is_uefi_supported():
def run_efibootmgr_json(): def run_efibootmgr_json():
if _check_efibootmgr_json() is False: if _check_efibootmgr_json() is False:
raise RuntimeError(f'{EFIBOOTMGR_BIN} not available') raise OgError(f'{EFIBOOTMGR_BIN} not available')
proc = subprocess.run([EFIBOOTMGR_BIN, '--json'], capture_output=True, text=True) proc = subprocess.run([EFIBOOTMGR_BIN, '--json'], capture_output=True, text=True)
dict_json = json.loads(proc.stdout) dict_json = json.loads(proc.stdout)
@ -99,14 +100,14 @@ def efibootmgr_create_bootentry(disk, part, loader, label, add_to_bootorder=True
try: try:
proc = subprocess.run(shlex.split(efibootmgr_cmd), check=True, text=True) proc = subprocess.run(shlex.split(efibootmgr_cmd), check=True, text=True)
except OSError as e: except OSError as e:
raise OSError(f'Unexpected error adding boot entry to nvram. UEFI firmware might be buggy') from e raise OgError(f'Unexpected error adding boot entry to nvram. UEFI firmware might be buggy') from e
def copy_windows_efi_bootloader(disk, partition): def copy_windows_efi_bootloader(disk, partition):
device = get_partition_device(disk, partition) device = get_partition_device(disk, partition)
mountpoint = device.replace('dev', 'mnt') mountpoint = device.replace('dev', 'mnt')
if not mount_mkdir(device, mountpoint): if not mount_mkdir(device, mountpoint):
raise RuntimeError(f'Cannot probe OS family. Unable to mount {device} into {mountpoint}') raise OgError(f'Cannot probe OS family. Unable to mount {device} into {mountpoint}')
os_family = get_os_family(mountpoint) os_family = get_os_family(mountpoint)
is_uefi = is_uefi_supported() is_uefi = is_uefi_supported()
@ -119,7 +120,7 @@ def copy_windows_efi_bootloader(disk, partition):
esp_mountpoint = esp.replace('dev', 'mnt') esp_mountpoint = esp.replace('dev', 'mnt')
if not mount_mkdir(esp, esp_mountpoint): if not mount_mkdir(esp, esp_mountpoint):
umount(mountpoint) umount(mountpoint)
raise RuntimeError(f'Unable to mount detected EFI System Partition at {esp} into {esp_mountpoint}') raise OgError(f'Unable to mount detected EFI System Partition at {esp} into {esp_mountpoint}')
loader_paths = [f'{esp_mountpoint}/EFI/{bootlabel}/Boot/bootmgfw.efi', loader_paths = [f'{esp_mountpoint}/EFI/{bootlabel}/Boot/bootmgfw.efi',
f'{esp_mountpoint}/EFI/Microsoft/Boot/bootmgfw.efi'] f'{esp_mountpoint}/EFI/Microsoft/Boot/bootmgfw.efi']
@ -131,7 +132,7 @@ def copy_windows_efi_bootloader(disk, partition):
logging.info(f'Found bootloader at ESP partition: {loader}') logging.info(f'Found bootloader at ESP partition: {loader}')
break break
else: else:
raise RuntimeError(f'Unable to locate Windows EFI bootloader bootmgfw.efi') raise OgError(f'Unable to locate Windows EFI bootloader bootmgfw.efi')
loader_dir = os.path.dirname(loader) loader_dir = os.path.dirname(loader)
destination_dir = f'{mountpoint}/ogBoot' destination_dir = f'{mountpoint}/ogBoot'
@ -139,12 +140,12 @@ def copy_windows_efi_bootloader(disk, partition):
try: try:
shutil.rmtree(destination_dir) shutil.rmtree(destination_dir)
except Exception as e: except Exception as e:
raise OSError(f'Failed to delete {destination_dir}: {e}') from e raise OgError(f'Failed to delete {destination_dir}: {e}') from e
logging.info(f'Copying {loader_dir} into {destination_dir}') logging.info(f'Copying {loader_dir} into {destination_dir}')
try: try:
shutil.copytree(loader_dir, destination_dir) shutil.copytree(loader_dir, destination_dir)
except Exception as e: except Exception as e:
raise OSError(f'Failed to copy {loader_dir} into {destination_dir}: {e}') from e raise OgError(f'Failed to copy {loader_dir} into {destination_dir}: {e}') from e
finally: finally:
umount(mountpoint) umount(mountpoint)
umount(esp_mountpoint) umount(esp_mountpoint)

View File

@ -23,6 +23,7 @@ import math
import sys import sys
import enum import enum
import time import time
from src.log import OgError
class OgVM: class OgVM:
DEFAULT_CPU = 'host' DEFAULT_CPU = 'host'
@ -97,7 +98,7 @@ class OgQMP:
self.sock.connect((self.ip, self.port)) self.sock.connect((self.ip, self.port))
except socket.error as err: except socket.error as err:
if err.errno == errno.ECONNREFUSED: if err.errno == errno.ECONNREFUSED:
raise RuntimeError('Cannot connect to QEMU') raise OgError('Cannot connect to QEMU')
elif err.errno == errno.EINPROGRESS: elif err.errno == errno.EINPROGRESS:
pass pass
@ -114,11 +115,11 @@ class OgQMP:
pass pass
if 'QMP' not in out: if 'QMP' not in out:
raise RuntimeError('Cannot handshake QEMU') raise OgError('Cannot handshake QEMU')
out = self.talk(str({"execute": "qmp_capabilities"})) out = self.talk(str({"execute": "qmp_capabilities"}))
if 'return' not in out: if 'return' not in out:
raise RuntimeError('Cannot handshake QEMU') raise OgError('Cannot handshake QEMU')
def disconnect(self): def disconnect(self):
try: try:
@ -143,9 +144,9 @@ class OgQMP:
try: try:
self.sock.send(bytes(data, 'utf-8')) self.sock.send(bytes(data, 'utf-8'))
except Exception as e: except Exception as e:
raise RuntimeError('Cannot talk to QEMU') from e raise OgError('Cannot talk to QEMU') from e
else: else:
raise RuntimeError('Timeout when talking to QEMU') raise OgError('Timeout when talking to QEMU')
return self.recv(timeout=timeout) return self.recv(timeout=timeout)
@ -158,9 +159,9 @@ class OgQMP:
out = self.sock.recv(4096).decode('utf-8') out = self.sock.recv(4096).decode('utf-8')
out = json.loads(out) out = json.loads(out)
except socket.error as err: except socket.error as err:
raise RuntimeError('Cannot talk to QEMU') from err raise OgError('Cannot talk to QEMU') from err
else: else:
raise RuntimeError('Timeout when talking to QEMU') raise OgError('Timeout when talking to QEMU')
return out return out
class OgVirtualOperations: class OgVirtualOperations: