BIOSUtilities v24.10.09

Added Apple EFI EFI Image Identifier new argument -q
Improved Apple EFI EFI Image Identifier detection flow
Improved Apple EFI Package Extractor file parsing flow
Improved file vs link detection and read access control
Improved common switches handling of 7-Zip decompressor
Improved non-PATH external executable dependencies names
Fixed requirement instruction misalignments at the README
This commit is contained in:
Plato Mavropoulos 2024-10-10 02:18:17 +03:00
parent bbf0008384
commit b174ce40e6
18 changed files with 322 additions and 288 deletions

View file

@ -8,17 +8,44 @@ Copyright (C) 2022-2024 Plato Mavropoulos
import os
import subprocess
from typing import Final
from biosutilities.common.externals import szip_path, tiano_path
from biosutilities.common.paths import is_dir, is_empty_dir
from biosutilities.common.system import printer
# 7-Zip switches to auto rename, ignore passwords, ignore prompts, ignore wildcards,
# eliminate root duplication, set UTF-8 charset, suppress stdout, suppress stderr,
# suppress progress, disable headers, disable progress, disable output logs
SZIP_COMMON: Final[list[str]] = ['-aou', '-p', '-y', '-spd', '-spe', '-sccUTF-8',
'-bso0', '-bse0', '-bsp0', '-ba', '-bd', '-bb0']
# Success exit codes (0 = OK, 1 = Warnings)
SZIP_SUCCESS: Final[list[int]] = [0, 1]
def szip_code_assert(exit_code: int) -> None:
""" Check 7-Zip bad exit codes (0 OK, 1 Warning) """
if exit_code not in (0, 1):
if exit_code not in SZIP_SUCCESS:
raise ValueError(f'Bad exit code: {exit_code}')
def szip_switches(in_switches: list[str]) -> list[str]:
""" Generate 7-Zip command line switches """
common_switches: list[str] = SZIP_COMMON
for in_switch in in_switches:
for sw_pattern in ('-p', '-ao', '-bs', '-bb', '-scc'):
if in_switch.startswith(sw_pattern):
common_switches = [sw for sw in common_switches if not sw.startswith(sw_pattern)]
break
return [*set(common_switches + in_switches), '--']
def is_szip_supported(in_path: str, padding: int = 0, args: list | None = None, silent: bool = True) -> bool:
""" Check if file is 7-Zip supported """
@ -26,7 +53,7 @@ def is_szip_supported(in_path: str, padding: int = 0, args: list | None = None,
if args is None:
args = []
szip_c: list[str] = [szip_path(), 't', in_path, *args, '-bso0', '-bse0', '-bsp0']
szip_c: list[str] = [szip_path(), 't', *szip_switches(in_switches=[*args]), in_path]
szip_t: subprocess.CompletedProcess[bytes] = subprocess.run(args=szip_c, check=False)
@ -40,26 +67,23 @@ def is_szip_supported(in_path: str, padding: int = 0, args: list | None = None,
return True
def szip_decompress(in_path: str, out_path: str, in_name: str | None, padding: int = 0, args: list | None = None,
def szip_decompress(in_path: str, out_path: str, in_name: str = 'archive', padding: int = 0, args: list | None = None,
check: bool = False, silent: bool = False) -> bool:
""" Archive decompression via 7-Zip """
if not in_name:
in_name = 'archive'
try:
if args is None:
args = []
szip_c: list[str] = [szip_path(), 'x', *args, '-aou', '-bso0', '-bse0', '-bsp0', f'-o{out_path}', in_path]
szip_c: list[str] = [szip_path(), 'x', *szip_switches(in_switches=[*args, f'-o{out_path}']), in_path]
szip_x: subprocess.CompletedProcess[bytes] = subprocess.run(args=szip_c, check=False)
if check:
szip_code_assert(exit_code=szip_x.returncode)
if not os.path.isdir(out_path):
raise OSError(f'Extraction directory not found: {out_path}')
if not (is_dir(in_path=out_path) and not is_empty_dir(in_path=out_path)):
raise OSError(f'Extraction directory is empty or missing: {out_path}')
except Exception as error: # pylint: disable=broad-except
if not silent:
printer(message=f'Error: 7-Zip could not extract {in_name} file {in_path}: {error}!', padding=padding)

View file

@ -16,7 +16,7 @@ from importlib.util import module_from_spec, spec_from_file_location
from types import ModuleType
from typing import Type
from biosutilities.common.paths import project_root
from biosutilities.common.paths import is_dir, is_file, project_root
from biosutilities.common.texts import to_string
@ -25,12 +25,12 @@ def get_external_path(cmd: str | list | tuple) -> str:
external_root: str = os.path.join(project_root(), 'external')
external_path: str | None = external_root if os.path.isdir(external_root) else None
external_path: str | None = external_root if is_dir(in_path=external_root) else None
for command in cmd if isinstance(cmd, (list, tuple)) else [to_string(in_object=cmd)]:
command_path: str | None = shutil.which(cmd=command, path=external_path)
if command_path and os.path.isfile(path=command_path):
if command_path and is_file(in_path=command_path):
return command_path
raise OSError(f'{to_string(in_object=cmd, sep_char=", ")} could not be found!')
@ -63,7 +63,7 @@ def big_script_tool() -> Type | None:
def comextract_path() -> str:
""" Get ToshibaComExtractor path """
return get_external_path(cmd='comextract')
return get_external_path(cmd=['comextract', 'ComExtract'])
def szip_path() -> str:
@ -75,16 +75,16 @@ def szip_path() -> str:
def tiano_path() -> str:
""" Get TianoCompress path """
return get_external_path(cmd='TianoCompress')
return get_external_path(cmd=['TianoCompress', 'tianocompress'])
def uefifind_path() -> str:
""" Get UEFIFind path """
return get_external_path(cmd='UEFIFind')
return get_external_path(cmd=['uefifind', 'UEFIFind'])
def uefiextract_path() -> str:
""" Get UEFIExtract path """
return get_external_path(cmd='UEFIExtract')
return get_external_path(cmd=['uefiextract', 'UEFIExtract'])

View file

@ -131,7 +131,7 @@ def make_dirs(in_path: str, parents: bool = True, exist_ok: bool = False, delete
def delete_dirs(in_path: str) -> None:
""" Delete folder(s), if present """
if Path(in_path).is_dir():
if is_dir(in_path=in_path):
shutil.rmtree(path=in_path, onerror=clear_readonly_callback) # pylint: disable=deprecated-argument
@ -167,18 +167,54 @@ def clear_readonly_callback(in_func: Callable, in_path: str, _) -> None:
in_func(path=in_path)
def path_files(in_path: str, follow_links: bool = False) -> list[str]:
def path_files(in_path: str, follow_links: bool = False, root_only: bool = False) -> list[str]:
""" Walk path to get all files """
file_paths: list[str] = []
for root, _, filenames in os.walk(top=in_path, followlinks=follow_links):
for filename in filenames:
file_paths.append(os.path.abspath(path=os.path.join(root, filename)))
for root_path, _, file_names in os.walk(top=in_path, followlinks=follow_links):
for file_name in file_names:
file_path: str = os.path.abspath(path=os.path.join(root_path, file_name))
if is_file(in_path=file_path):
file_paths.append(file_path)
if root_only:
break
return file_paths
def is_dir(in_path: str) -> bool:
""" Check if path is a directory """
return Path(in_path).is_dir()
def is_file(in_path: str, allow_broken_links: bool = False) -> bool:
""" Check if path is a regural file or symlink (valid or broken) """
in_path_abs: str = os.path.abspath(path=in_path)
if os.path.lexists(path=in_path_abs):
if not is_dir(in_path=in_path_abs):
if allow_broken_links:
return os.path.isfile(path=in_path_abs) or os.path.islink(path=in_path_abs)
return os.path.isfile(path=in_path_abs)
return False
def is_access(in_path: str, access_mode: int = os.R_OK, follow_links: bool = False) -> bool:
""" Check if path is accessible """
if not follow_links and os.access not in os.supports_follow_symlinks:
follow_links = True
return os.access(path=in_path, mode=access_mode, follow_symlinks=follow_links)
def is_empty_dir(in_path: str, follow_links: bool = False) -> bool:
""" Check if directory is empty (file-wise) """

View file

@ -31,18 +31,6 @@ PAT_APPLE_PBZX: Final[re.Pattern[bytes]] = re.compile(
pattern=br'pbzx'
)
PAT_APPLE_PKG_DMG: Final[re.Pattern[bytes]] = re.compile(
pattern=br'EFI PART'
)
PAT_APPLE_PKG_TAR: Final[re.Pattern[bytes]] = re.compile(
pattern=br'<key>IFPkgDescriptionDescription</key>'
)
PAT_APPLE_PKG_XAR: Final[re.Pattern[bytes]] = re.compile(
pattern=br'xar!'
)
PAT_AWARD_LZH: Final[re.Pattern[bytes]] = re.compile(
pattern=br'-lh[04567]-'
)

View file

@ -12,8 +12,8 @@ from argparse import ArgumentParser, Namespace
from typing import Final
from biosutilities import __version__
from biosutilities.common.paths import (delete_dirs, extract_folder, is_empty_dir, path_files,
path_name, path_parent, real_path, runtime_root)
from biosutilities.common.paths import (delete_dirs, extract_folder, is_access, is_dir, is_file, is_empty_dir,
path_files, path_name, path_parent, real_path, runtime_root)
from biosutilities.common.system import system_platform, python_version, printer
from biosutilities.common.texts import remove_quotes, to_boxed, to_ordinal
@ -76,11 +76,11 @@ class BIOSUtility:
extract_path: str = os.path.join(self._output_path, extract_folder(in_path=input_name))
if os.path.isdir(extract_path):
if is_dir(in_path=extract_path):
for suffix in range(2, self.MAX_FAT32_ITEMS):
renamed_path: str = f'{os.path.normpath(path=extract_path)}_{to_ordinal(in_number=suffix)}'
if not os.path.isdir(renamed_path):
if not is_dir(in_path=renamed_path):
extract_path = renamed_path
break
@ -121,9 +121,11 @@ class BIOSUtility:
for input_path in [input_path for input_path in input_paths if input_path]:
input_path_real: str = real_path(in_path=input_path)
if os.path.isdir(input_path_real):
self._input_files.extend(path_files(input_path_real))
else:
if is_dir(in_path=input_path_real):
for input_file in path_files(in_path=input_path_real):
if is_file(in_path=input_file) and is_access(in_path=input_file):
self._input_files.append(input_file)
elif is_file(in_path=input_path_real) and is_access(in_path=input_path_real):
self._input_files.append(input_path_real)
def _setup_output_dir(self, padding: int = 0) -> None:
@ -137,7 +139,10 @@ class BIOSUtility:
if not output_path and self._input_files:
output_path = str(path_parent(in_path=self._input_files[0]))
self._output_path = output_path or runtime_root()
if output_path and is_dir(in_path=output_path) and is_access(in_path=output_path):
self._output_path = output_path
else:
self._output_path = runtime_root()
def _check_sys_py(self) -> None:
""" Check Python Version """