From 34007857f6b96eca02c510a8a4d8fce6d37fa8df Mon Sep 17 00:00:00 2001 From: Alejandro Sirgo Rica Date: Mon, 27 May 2024 16:08:22 +0200 Subject: src: rename legacy.py into image.py legacy.py contais mostly functions related to system images. Rename the file to better represent the contents in it. --- src/live/ogOperations.py | 2 +- src/utils/image.py | 246 +++++++++++++++++++++++++++++++++++++++++++++++ src/utils/legacy.py | 246 ----------------------------------------------- 3 files changed, 247 insertions(+), 247 deletions(-) create mode 100644 src/utils/image.py delete mode 100644 src/utils/legacy.py diff --git a/src/live/ogOperations.py b/src/live/ogOperations.py index 485b4d7..04b4433 100644 --- a/src/live/ogOperations.py +++ b/src/live/ogOperations.py @@ -23,7 +23,7 @@ from src.ogRest import ThreadState from src.live.partcodes import GUID_MAP from src.live.parttypes import get_parttype -from src.utils.legacy import * +from src.utils.image import * from src.utils.net import ethtool from src.utils.menu import generate_menu from src.utils.fs import * diff --git a/src/utils/image.py b/src/utils/image.py new file mode 100644 index 0000000..cdc79c9 --- /dev/null +++ b/src/utils/image.py @@ -0,0 +1,246 @@ +# +# Copyright (C) 2023 Soleta Networks +# +# This program is free software: you can redistribute it and/or modify it under +# the terms of the GNU Affero General Public License as published by the +# Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. + +import ipaddress +import logging +import os +import subprocess +import shlex +import shutil + +from subprocess import PIPE, DEVNULL, STDOUT, CalledProcessError + +from src.utils.fs import umount +from src.log import OgError + + +class ImageInfo: + """ + Class that stores the OpenGnsys partition image information. + """ + def __init__(self, filesystem=None, datasize=None): + self.filesystem = filesystem + self.datasize = datasize + self.size = 0 + self.mtime = 0 + self.perms = 0 + self.clonator = 'PARTCLONE' + self.compressor = 'LZOP' + + +def human_to_kb(size, unit): + """ + Returns the KB conversion of a human readable string size and unit. + """ + size = float(size.replace(',', '.')) + if unit == 'GB': + return size * 1000000 + if unit == 'MB': + return size * 1000 + return 0 + + +def fill_imageinfo(line, image_info): + """ + Updates ImageInfo object with information processed from + a single partclone.info output line. + + ImageInfo object may not be updated if the line is invalid or does not + contain filesystem or device size information. + """ + if 'File system' in line: + filesystem = line.rstrip().split(' ')[-1] + image_info.filesystem = filesystem + elif 'Device size' in line: + l = [word for word in line.rstrip().split(' ') if word][2:4] + device_size = human_to_kb(*l) + image_info.datasize = device_size + + +def image_info_from_partclone(partclone_output): + """ + Return an ImageInfo object from partclone.info output. + """ + image_info = ImageInfo() + for n, line in enumerate(partclone_output.split('\n')): + # Ignore first two lines of partclone.info output + if n < 2: + continue + if image_info.datasize and image_info.filesystem: + break + fill_imageinfo(line, image_info) + + if not image_info.datasize: + raise OgError("Missing device size from partclone.info output") + elif not image_info.filesystem: + raise OgError("Missing filesystem from partclone.info output") + + return image_info + + +def run_lzop_partcloneinfo(image_path): + """ + Run lzop to decompress an OpenGnsys partition image, feed + lzop output to a partclone.info subprocess. + + Return the partclone.info subprocess output. + """ + cmd1 = f'{shutil.which("lzop")} -dc {image_path}' + cmd2 = f'{shutil.which("partclone.info")} -s -' + args1 = shlex.split(cmd1) + args2 = shlex.split(cmd2) + p1 = subprocess.Popen(args1, stdout=PIPE, stderr=DEVNULL) + p2 = subprocess.Popen(args2, stdout=PIPE, stdin=p1.stdout, + stderr=STDOUT, encoding='utf-8') + p1.stdout.close() + p2_out, p2_err = p2.communicate() + + if p2.returncode != 0: + raise OgError(f'Unable to process image {image_path}') + + return p2_out + + +def ogGetImageInfo(image_path): + """ + Obtain filesystem and device size information of an OpenGnsys partition + image. + + This method supports compressed images with lzop that have been created + with partclone. + + Returns an ImageInfo object. + + >>> image_info = ogGetImageInfo('/opt/opengnsys/images/foobar.img') + >>> image_info.filesystem + >>> 'NTFS' + >>> image_info.datasize + >>> 140000000 + >>> image_info.compressor + >>> 'LZOP' + >>> image_info.clonator + >>> 'PARTCLONE' + """ + partclone_output = run_lzop_partcloneinfo(image_path) + image_info = image_info_from_partclone(partclone_output) + return image_info + + +def change_access(mode='rw', user='opengnsys', pwd='og'): + """ + 'CambiarAcceso' (admin/Interface/CambiarAcceso) rewrite into native Python. + + Remount the (remote) samba directory that contains the OpenGnsys images. + Specify access mode ('rw', or 'ro') with mode parameter (default 'rw'). + Specify samba credentials with user and pwd parameter. + + Return 0 if exit-code was 0. Return -1 otherwise. + """ + assert mode in ['rw', 'ro'], 'Invalid remount mode option' + + cmd = shlex.split(f'mount -o remount,{mode},username={user},password={pwd} /opt/opengnsys/images') + p = subprocess.run(cmd, stdout=DEVNULL, stderr=DEVNULL) + + return 0 if p.returncode == 0 else -1 + +def ogChangeRepo(ip, smb_user='opengnsys', smb_pass='og'): + """ + Umount current Samba directory of OpenGnsys images and mount new Samba + directory (preserving previous access mode). + + If the mount fails, fallback to the image directory that was mounted before + calling this function. + + If there is no previous image directory mount, then simply try mounting the + new Samba directory with readonly mode option. + + Any CalledProcessError raised by the fallback mount subprocess should be + handled by the caller of this function. + """ + def fsopts_mode(fsopts): + for opt in fsopts.split(','): + if opt in ['rw', 'ro']: + return opt + + def process_mntent(line): + name, mntdir, fstype, opts, freq, passno = line.split(' ') + mode = fsopts_mode(opts) + return name, mntdir, fsopts_mode(opts) + + try: + ipaddr = ipaddress.ip_address(ip) + except ValueError as e: + raise OgError(f'Invalid IP address {ip} received') from e + + mounted = False + with open('/etc/mtab') as f: + for line in f: + if 'ogimages' in line: + orig_name, mntdir, mode = process_mntent(line) + mounted = True + break + + new_name = f'//{ip}/ogimages' + if not mounted: + orig_name = new_name + mntdir = '/opt/opengnsys/images' + mode = 'ro' + else: + umount(mntdir) + + err = 0 + cmd = f'mount.cifs -o {mode},username={smb_user},password={smb_pass} {new_name} /opt/opengnsys/images' + result = subprocess.run(shlex.split(cmd)) + if result.returncode != 0: + err = -1 + logging.error(f'Error mounting {new_name} in /opt/opengnsys/images with error {result.returncode}') + + cmd = f'mount.cifs -o {mode},username={smb_user},password={smb_pass} {orig_name} /opt/opengnsys/images' + subprocess.run(shlex.split(cmd)) + + return err + + +def restoreImageCustom(repo_ip, image_name, disk, partition, method): + """ + """ + if not shutil.which('restoreImageCustom'): + raise OgError('restoreImageCustom not found') + + cmd = f'restoreImageCustom {repo_ip} {image_name} {disk} {partition} {method}' + with open('/tmp/command.log', 'wb', 0) as logfile: + try: + proc = subprocess.run(cmd, + stdout=logfile, + encoding='utf-8', + shell=True, + check=True) + except OSError as e: + raise OgError(f'Error processing restoreImageCustom: {e}') from e + return proc.returncode + + +def configureOs(disk, partition): + """ + """ + if shutil.which('configureOsCustom'): + cmd_configure = f"configureOsCustom {disk} {partition}" + else: + cmd_configure = f"configureOs {disk} {partition}" + + try: + proc = subprocess.run(cmd_configure, + stdout=PIPE, + encoding='utf-8', + shell=True, + check=True) + out = proc.stdout + except OSError as e: + raise OgError(f'Error processing configureOsCustom: {e}') from e + + return out diff --git a/src/utils/legacy.py b/src/utils/legacy.py deleted file mode 100644 index cdc79c9..0000000 --- a/src/utils/legacy.py +++ /dev/null @@ -1,246 +0,0 @@ -# -# Copyright (C) 2023 Soleta Networks -# -# This program is free software: you can redistribute it and/or modify it under -# the terms of the GNU Affero General Public License as published by the -# Free Software Foundation; either version 3 of the License, or -# (at your option) any later version. - -import ipaddress -import logging -import os -import subprocess -import shlex -import shutil - -from subprocess import PIPE, DEVNULL, STDOUT, CalledProcessError - -from src.utils.fs import umount -from src.log import OgError - - -class ImageInfo: - """ - Class that stores the OpenGnsys partition image information. - """ - def __init__(self, filesystem=None, datasize=None): - self.filesystem = filesystem - self.datasize = datasize - self.size = 0 - self.mtime = 0 - self.perms = 0 - self.clonator = 'PARTCLONE' - self.compressor = 'LZOP' - - -def human_to_kb(size, unit): - """ - Returns the KB conversion of a human readable string size and unit. - """ - size = float(size.replace(',', '.')) - if unit == 'GB': - return size * 1000000 - if unit == 'MB': - return size * 1000 - return 0 - - -def fill_imageinfo(line, image_info): - """ - Updates ImageInfo object with information processed from - a single partclone.info output line. - - ImageInfo object may not be updated if the line is invalid or does not - contain filesystem or device size information. - """ - if 'File system' in line: - filesystem = line.rstrip().split(' ')[-1] - image_info.filesystem = filesystem - elif 'Device size' in line: - l = [word for word in line.rstrip().split(' ') if word][2:4] - device_size = human_to_kb(*l) - image_info.datasize = device_size - - -def image_info_from_partclone(partclone_output): - """ - Return an ImageInfo object from partclone.info output. - """ - image_info = ImageInfo() - for n, line in enumerate(partclone_output.split('\n')): - # Ignore first two lines of partclone.info output - if n < 2: - continue - if image_info.datasize and image_info.filesystem: - break - fill_imageinfo(line, image_info) - - if not image_info.datasize: - raise OgError("Missing device size from partclone.info output") - elif not image_info.filesystem: - raise OgError("Missing filesystem from partclone.info output") - - return image_info - - -def run_lzop_partcloneinfo(image_path): - """ - Run lzop to decompress an OpenGnsys partition image, feed - lzop output to a partclone.info subprocess. - - Return the partclone.info subprocess output. - """ - cmd1 = f'{shutil.which("lzop")} -dc {image_path}' - cmd2 = f'{shutil.which("partclone.info")} -s -' - args1 = shlex.split(cmd1) - args2 = shlex.split(cmd2) - p1 = subprocess.Popen(args1, stdout=PIPE, stderr=DEVNULL) - p2 = subprocess.Popen(args2, stdout=PIPE, stdin=p1.stdout, - stderr=STDOUT, encoding='utf-8') - p1.stdout.close() - p2_out, p2_err = p2.communicate() - - if p2.returncode != 0: - raise OgError(f'Unable to process image {image_path}') - - return p2_out - - -def ogGetImageInfo(image_path): - """ - Obtain filesystem and device size information of an OpenGnsys partition - image. - - This method supports compressed images with lzop that have been created - with partclone. - - Returns an ImageInfo object. - - >>> image_info = ogGetImageInfo('/opt/opengnsys/images/foobar.img') - >>> image_info.filesystem - >>> 'NTFS' - >>> image_info.datasize - >>> 140000000 - >>> image_info.compressor - >>> 'LZOP' - >>> image_info.clonator - >>> 'PARTCLONE' - """ - partclone_output = run_lzop_partcloneinfo(image_path) - image_info = image_info_from_partclone(partclone_output) - return image_info - - -def change_access(mode='rw', user='opengnsys', pwd='og'): - """ - 'CambiarAcceso' (admin/Interface/CambiarAcceso) rewrite into native Python. - - Remount the (remote) samba directory that contains the OpenGnsys images. - Specify access mode ('rw', or 'ro') with mode parameter (default 'rw'). - Specify samba credentials with user and pwd parameter. - - Return 0 if exit-code was 0. Return -1 otherwise. - """ - assert mode in ['rw', 'ro'], 'Invalid remount mode option' - - cmd = shlex.split(f'mount -o remount,{mode},username={user},password={pwd} /opt/opengnsys/images') - p = subprocess.run(cmd, stdout=DEVNULL, stderr=DEVNULL) - - return 0 if p.returncode == 0 else -1 - -def ogChangeRepo(ip, smb_user='opengnsys', smb_pass='og'): - """ - Umount current Samba directory of OpenGnsys images and mount new Samba - directory (preserving previous access mode). - - If the mount fails, fallback to the image directory that was mounted before - calling this function. - - If there is no previous image directory mount, then simply try mounting the - new Samba directory with readonly mode option. - - Any CalledProcessError raised by the fallback mount subprocess should be - handled by the caller of this function. - """ - def fsopts_mode(fsopts): - for opt in fsopts.split(','): - if opt in ['rw', 'ro']: - return opt - - def process_mntent(line): - name, mntdir, fstype, opts, freq, passno = line.split(' ') - mode = fsopts_mode(opts) - return name, mntdir, fsopts_mode(opts) - - try: - ipaddr = ipaddress.ip_address(ip) - except ValueError as e: - raise OgError(f'Invalid IP address {ip} received') from e - - mounted = False - with open('/etc/mtab') as f: - for line in f: - if 'ogimages' in line: - orig_name, mntdir, mode = process_mntent(line) - mounted = True - break - - new_name = f'//{ip}/ogimages' - if not mounted: - orig_name = new_name - mntdir = '/opt/opengnsys/images' - mode = 'ro' - else: - umount(mntdir) - - err = 0 - cmd = f'mount.cifs -o {mode},username={smb_user},password={smb_pass} {new_name} /opt/opengnsys/images' - result = subprocess.run(shlex.split(cmd)) - if result.returncode != 0: - err = -1 - logging.error(f'Error mounting {new_name} in /opt/opengnsys/images with error {result.returncode}') - - cmd = f'mount.cifs -o {mode},username={smb_user},password={smb_pass} {orig_name} /opt/opengnsys/images' - subprocess.run(shlex.split(cmd)) - - return err - - -def restoreImageCustom(repo_ip, image_name, disk, partition, method): - """ - """ - if not shutil.which('restoreImageCustom'): - raise OgError('restoreImageCustom not found') - - cmd = f'restoreImageCustom {repo_ip} {image_name} {disk} {partition} {method}' - with open('/tmp/command.log', 'wb', 0) as logfile: - try: - proc = subprocess.run(cmd, - stdout=logfile, - encoding='utf-8', - shell=True, - check=True) - except OSError as e: - raise OgError(f'Error processing restoreImageCustom: {e}') from e - return proc.returncode - - -def configureOs(disk, partition): - """ - """ - if shutil.which('configureOsCustom'): - cmd_configure = f"configureOsCustom {disk} {partition}" - else: - cmd_configure = f"configureOs {disk} {partition}" - - try: - proc = subprocess.run(cmd_configure, - stdout=PIPE, - encoding='utf-8', - shell=True, - check=True) - out = proc.stdout - except OSError as e: - raise OgError(f'Error processing configureOsCustom: {e}') from e - - return out -- cgit v1.2.3-18-g5258