From abb18a5ad26b2f46af59334af34880aee47a1c38 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Fri, 10 May 2024 16:06:16 -0600 Subject: [PATCH] subprocess_wrapper.py: Add unified error handling Additionally adds backend support for Privileged Helper Tool --- opencore_legacy_patcher/support/arguments.py | 4 +- .../support/global_settings.py | 10 +- opencore_legacy_patcher/support/install.py | 8 +- .../support/kdk_handler.py | 35 ++-- .../support/logging_handler.py | 14 +- .../support/reroute_payloads.py | 5 +- .../support/subprocess_wrapper.py | 179 ++++++++++++++++++ opencore_legacy_patcher/support/utilities.py | 15 -- opencore_legacy_patcher/support/validation.py | 14 +- .../sys_patch/sys_patch.py | 95 +++++----- .../sys_patch/sys_patch_auto.py | 23 +-- .../sys_patch/sys_patch_helpers.py | 14 +- .../wx_gui/gui_macos_installer_flash.py | 5 +- .../wx_gui/gui_settings.py | 7 +- opencore_legacy_patcher/wx_gui/gui_support.py | 6 +- .../wx_gui/gui_sys_patch_display.py | 3 +- opencore_legacy_patcher/wx_gui/gui_update.py | 9 +- 17 files changed, 304 insertions(+), 142 deletions(-) create mode 100644 opencore_legacy_patcher/support/subprocess_wrapper.py diff --git a/opencore_legacy_patcher/support/arguments.py b/opencore_legacy_patcher/support/arguments.py index e95cb729e..809d4528d 100644 --- a/opencore_legacy_patcher/support/arguments.py +++ b/opencore_legacy_patcher/support/arguments.py @@ -11,6 +11,8 @@ import subprocess from pathlib import Path +from . import subprocess_wrapper + from .. import constants from ..wx_gui import gui_entry @@ -172,7 +174,7 @@ class arguments: if "GPUCompanionBundles" not in kext_plist: continue logging.info(f" - Removing {kext.name}") - subprocess.run(["/bin/rm", "-rf", kext]) + subprocess_wrapper.run_as_root(["/bin/rm", "-rf", kext]) def _build_handler(self) -> None: diff --git a/opencore_legacy_patcher/support/global_settings.py b/opencore_legacy_patcher/support/global_settings.py index a384891c7..b4ac0acb2 100644 --- a/opencore_legacy_patcher/support/global_settings.py +++ b/opencore_legacy_patcher/support/global_settings.py @@ -10,7 +10,8 @@ ie. during automated patching import os import logging import plistlib -import subprocess + +from . import subprocess_wrapper from pathlib import Path @@ -115,12 +116,11 @@ class GlobalEnviromentSettings: This in turn breaks normal OCLP execution to write to settings file """ - if os.geteuid() != 0: + if os.geteuid() != 0 and subprocess_wrapper.supports_privileged_helper() is False: return # Set file permission to allow any user to write to log file - result = subprocess.run(["/bin/chmod", "777", self.global_settings_plist], capture_output=True) + result = subprocess_wrapper.run_as_root(["/bin/chmod", "777", self.global_settings_plist], capture_output=True) if result.returncode != 0: logging.warning("Failed to fix settings file permissions:") - if result.stderr: - logging.warning(result.stderr.decode("utf-8")) \ No newline at end of file + subprocess_wrapper.log(result) \ No newline at end of file diff --git a/opencore_legacy_patcher/support/install.py b/opencore_legacy_patcher/support/install.py index 9bb45c3f8..b95c81934 100644 --- a/opencore_legacy_patcher/support/install.py +++ b/opencore_legacy_patcher/support/install.py @@ -9,7 +9,7 @@ import applescript from pathlib import Path -from . import utilities +from . import utilities, subprocess_wrapper from .. import constants @@ -92,7 +92,7 @@ class tui_disk_installation: def install_opencore(self, full_disk_identifier: str): # TODO: Apple Script fails in Yosemite(?) and older logging.info(f"Mounting partition: {full_disk_identifier}") - if self.constants.detected_os >= os_data.os_data.el_capitan and not self.constants.recovery_status: + if self.constants.detected_os >= os_data.os_data.el_capitan and not self.constants.recovery_status and subprocess_wrapper.supports_privileged_helper() is False: try: applescript.AppleScript(f'''do shell script "diskutil mount {full_disk_identifier}" with prompt "OpenCore Legacy Patcher needs administrator privileges to mount this volume." with administrator privileges without altering line endings''').run() except applescript.ScriptError as e: @@ -105,10 +105,10 @@ class tui_disk_installation: logging.info("Please disable Safe Mode and try again.") return else: - result = subprocess.run(["/usr/sbin/diskutil", "mount", full_disk_identifier], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + result = subprocess_wrapper.run_as_root(["/usr/sbin/diskutil", "mount", full_disk_identifier], stdout=subprocess.PIPE, stderr=subprocess.PIPE) if result.returncode != 0: logging.info("Mount failed") - logging.info(result.stderr.decode()) + subprocess_wrapper.log(result) return partition_info = plistlib.loads(subprocess.run(["/usr/sbin/diskutil", "info", "-plist", full_disk_identifier], stdout=subprocess.PIPE).stdout.decode().strip().encode()) diff --git a/opencore_legacy_patcher/support/kdk_handler.py b/opencore_legacy_patcher/support/kdk_handler.py index abbb7a36f..ea46ee264 100644 --- a/opencore_legacy_patcher/support/kdk_handler.py +++ b/opencore_legacy_patcher/support/kdk_handler.py @@ -18,8 +18,8 @@ from .. import constants from ..datasets import os_data from . import ( - utilities, - network_handler + network_handler, + subprocess_wrapper ) KDK_INSTALL_PATH: str = "/Library/Developer/KDKs" @@ -464,7 +464,7 @@ class KernelDebugKitObject: if self.passive is True: return - if os.getuid() != 0: + if os.getuid() != 0 and subprocess_wrapper.supports_privileged_helper() is False: logging.warning("Cannot remove KDK, not running as root") return @@ -474,10 +474,10 @@ class KernelDebugKitObject: rm_args = ["/bin/rm", "-rf" if Path(kdk_path).is_dir() else "-f", kdk_path] - result = utilities.elevated(rm_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + result = subprocess_wrapper.run_as_root(rm_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if result.returncode != 0: logging.warning(f"Failed to remove KDK: {kdk_path}") - logging.warning(f"{result.stdout.decode('utf-8')}") + subprocess_wrapper.log(result) return logging.info(f"Successfully removed KDK: {kdk_path}") @@ -545,7 +545,7 @@ class KernelDebugKitObject: result = subprocess.run(["/usr/bin/hdiutil", "verify", self.constants.kdk_download_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE) if result.returncode != 0: logging.info("Error: Kernel Debug Kit checksum verification failed!") - logging.info(f"Output: {result.stderr.decode('utf-8')}") + subprocess_wrapper.log(result) msg = "Kernel Debug Kit checksum verification failed, please try again.\n\nIf this continues to fail, ensure you're downloading on a stable network connection (ie. Ethernet)" logging.info(f"{msg}") @@ -579,7 +579,7 @@ class KernelDebugKitUtilities: bool: True if successful, False if not """ - if os.getuid() != 0: + if os.getuid() != 0 and subprocess_wrapper.supports_privileged_helper() is False: logging.warning("Cannot install KDK, not running as root") return False @@ -588,12 +588,10 @@ class KernelDebugKitUtilities: # TODO: Check whether enough disk space is available - result = utilities.elevated(["/usr/sbin/installer", "-pkg", kdk_path, "-target", "/"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + result = subprocess_wrapper.run_as_root(["/usr/sbin/installer", "-pkg", kdk_path, "-target", "/"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if result.returncode != 0: logging.info("Failed to install KDK:") - logging.info(result.stdout.decode('utf-8')) - if result.stderr: - logging.info(result.stderr.decode('utf-8')) + subprocess_wrapper.log(result) return False return True @@ -609,20 +607,19 @@ class KernelDebugKitUtilities: bool: True if successful, False if not """ - if os.getuid() != 0: + if os.getuid() != 0 and subprocess_wrapper.supports_privileged_helper() is False: logging.warning("Cannot install KDK, not running as root") return False logging.info(f"Extracting downloaded KDK disk image") with tempfile.TemporaryDirectory() as mount_point: - result = subprocess.run(["/usr/bin/hdiutil", "attach", kdk_path, "-mountpoint", mount_point, "-nobrowse"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + result = subprocess_wrapper.run_as_root(["/usr/bin/hdiutil", "attach", kdk_path, "-mountpoint", mount_point, "-nobrowse"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if result.returncode != 0: logging.info("Failed to mount KDK:") - logging.info(result.stdout.decode('utf-8')) + subprocess_wrapper.log(result) return False kdk_pkg_path = Path(f"{mount_point}/KernelDebugKit.pkg") - if not kdk_pkg_path.exists(): logging.warning("Failed to find KDK package in DMG, likely corrupted!!!") self._unmount_disk_image(mount_point) @@ -672,12 +669,12 @@ class KernelDebugKitUtilities: logging.warning("Malformed KDK Info.plist provided, cannot create backup") return - if os.getuid() != 0: + if os.getuid() != 0 and subprocess_wrapper.supports_privileged_helper() is False: logging.warning("Cannot create KDK backup, not running as root") return if not Path(KDK_INSTALL_PATH).exists(): - subprocess.run(["/bin/mkdir", "-p", KDK_INSTALL_PATH], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + subprocess_wrapper.run_as_root(["/bin/mkdir", "-p", KDK_INSTALL_PATH], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) kdk_dst_name = f"KDK_{kdk_info_dict['version']}_{kdk_info_dict['build']}.pkg" kdk_dst_path = Path(f"{KDK_INSTALL_PATH}/{kdk_dst_name}") @@ -687,7 +684,7 @@ class KernelDebugKitUtilities: logging.info("Backup already exists, skipping") return - result = utilities.elevated(["/bin/cp", "-R", kdk_path, kdk_dst_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + result = subprocess_wrapper.run_as_root(["/bin/cp", "-R", kdk_path, kdk_dst_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if result.returncode != 0: logging.info("Failed to create KDK backup:") - logging.info(result.stdout.decode('utf-8')) \ No newline at end of file + subprocess_wrapper.log(result) \ No newline at end of file diff --git a/opencore_legacy_patcher/support/logging_handler.py b/opencore_legacy_patcher/support/logging_handler.py index 65ad16131..8aff1efd9 100644 --- a/opencore_legacy_patcher/support/logging_handler.py +++ b/opencore_legacy_patcher/support/logging_handler.py @@ -18,7 +18,8 @@ from .. import constants from . import ( analytics_handler, - global_settings + global_settings, + subprocess_wrapper ) @@ -130,7 +131,7 @@ class InitializeLoggingSupport: This in turn breaks normal OCLP execution to write to log file """ - if os.geteuid() != 0: + if os.geteuid() != 0 and subprocess_wrapper.supports_privileged_helper() is False: return paths = [ @@ -139,15 +140,10 @@ class InitializeLoggingSupport: ] for path in paths: - result = subprocess.run(["/bin/chmod", "777", path], capture_output=True) + result = subprocess_wrapper.run_as_root(["/bin/chmod", "777", path], capture_output=True) if result.returncode != 0: logging.error(f"Failed to fix log file permissions") - if result.stdout: - logging.error("STDOUT:") - logging.error(result.stdout.decode("utf-8")) - if result.stderr: - logging.error("STDERR:") - logging.error(result.stderr.decode("utf-8")) + subprocess_wrapper.log(result) def _initialize_logging_configuration(self, log_to_file: bool = True) -> None: diff --git a/opencore_legacy_patcher/support/reroute_payloads.py b/opencore_legacy_patcher/support/reroute_payloads.py index 263352906..90b1815bb 100644 --- a/opencore_legacy_patcher/support/reroute_payloads.py +++ b/opencore_legacy_patcher/support/reroute_payloads.py @@ -12,6 +12,8 @@ import logging from pathlib import Path +from . import subprocess_wrapper + from .. import constants @@ -55,8 +57,7 @@ class RoutePayloadDiskImage: atexit.register(self._unmount_active_dmgs, unmount_all_active=False) else: logging.info("Failed to mount payloads.dmg") - logging.info(f"Output: {output.stdout.decode()}") - logging.info(f"Return Code: {output.returncode}") + subprocess_wrapper.log(output) def _unmount_active_dmgs(self, unmount_all_active: bool = True) -> None: diff --git a/opencore_legacy_patcher/support/subprocess_wrapper.py b/opencore_legacy_patcher/support/subprocess_wrapper.py new file mode 100644 index 000000000..a846f234b --- /dev/null +++ b/opencore_legacy_patcher/support/subprocess_wrapper.py @@ -0,0 +1,179 @@ +""" +subprocess_wrapper.py: Wrapper for subprocess module to better handle errors and output + Additionally handles our Privileged Helper Tool +""" + +import os +import enum +import logging +import subprocess + +from pathlib import Path +from functools import cache + +from . import utilities + + +OCLP_PRIVILEGED_HELPER = "/Library/PrivilegedHelperTools/com.dortania.opencore-legacy-patcher.privileged-helper" + + +class PrivilegedHelperErrorCodes(enum.IntEnum): + """ + Error codes for Privileged Helper Tool. + + Reference: + payloads/Tools/PrivilegedHelperTool/main.m + """ + OCLP_PHT_ERROR_MISSING_ARGUMENTS = 160 + OCLP_PHT_ERROR_SET_UID_MISSING = 161 + OCLP_PHT_ERROR_SET_UID_FAILED = 162 + OCLP_PHT_ERROR_SELF_PATH_MISSING = 163 + OCLP_PHT_ERROR_PARENT_PATH_MISSING = 164 + OCLP_PHT_ERROR_SIGNING_INFORMATION_MISSING = 165 + OCLP_PHT_ERROR_INVALID_TEAM_ID = 166 + OCLP_PHT_ERROR_INVALID_CERTIFICATES = 167 + OCLP_PHT_ERROR_COMMAND_MISSING = 168 + OCLP_PHT_ERROR_COMMAND_FAILED = 169 + OCLP_PHT_ERROR_CATCH_ALL = 170 + + +@cache +def supports_privileged_helper() -> bool: + """ + Check if Privileged Helper Tool is supported. + + When privileged helper is officially shipped, this function should always return True. + Something would have gone very wrong if it doesn't exist past that point. + """ + return Path(OCLP_PRIVILEGED_HELPER).exists() + + +def run(*args, **kwargs) -> subprocess.CompletedProcess: + """ + Basic subprocess.run wrapper. + """ + return subprocess.run(*args, **kwargs) + + +def run_as_root(*args, **kwargs) -> subprocess.CompletedProcess: + """ + Run subprocess as root. + + Note: Full path to first argument is required. + Helper tool does not resolve PATH. + """ + # Check if first argument exists + if not Path(args[0][0]).exists(): + raise FileNotFoundError(f"File not found: {args[0][0]}") + + if supports_privileged_helper() is False: + # Fall back to old logic + # This should be removed when we start shipping the helper tool officially + if os.getuid() == 0 or utilities.check_cli_args() is not None: + return subprocess.run(*args, **kwargs) + else: + return subprocess.run(["/usr/bin/sudo"] + [args[0][0]] + args[0][1:], **kwargs) + + return subprocess.run([OCLP_PRIVILEGED_HELPER] + [args[0][0]] + args[0][1:], **kwargs) + + +def verify(process_result: subprocess.CompletedProcess) -> None: + """ + Verify process result and raise exception if failed. + """ + if process_result.returncode == 0: + return + + log(process_result) + + raise Exception(f"Process failed with exit code {process_result.returncode}") + + +def run_and_verify(*args, **kwargs) -> None: + """ + Run subprocess and verify result. + + Asserts on failure. + """ + verify(run(*args, **kwargs)) + + +def run_as_root_and_verify(*args, **kwargs) -> None: + """ + Run subprocess as root and verify result. + + Asserts on failure. + """ + verify(run_as_root(*args, **kwargs)) + + +def log(process: subprocess.CompletedProcess) -> None: + """ + Display subprocess error output in formatted string. + """ + for line in generate_log(process).split("\n"): + logging.error(line) + + +def generate_log(process: subprocess.CompletedProcess) -> str: + """ + Display subprocess error output in formatted string. + Note this function is still used for zero return code errors, since + some software don't ever return non-zero regardless of success. + + Format: + + Command: + Return Code: + Standard Output: + + + ... + Standard Error: + + + ... + """ + output = "Subprocess failed.\n" + output += f" Command: {process.args}\n" + output += f" Return Code: {process.returncode}\n" + _returned_error = __resolve_privileged_helper_errors(process.returncode) + if _returned_error: + output += f" Likely Enum: {_returned_error}\n" + output += f" Standard Output:\n" + if process.stdout: + output += __format_output(process.stdout.decode("utf-8")) + else: + output += " None\n" + output += f" Standard Error:\n" + if process.stderr: + output += __format_output(process.stderr.decode("utf-8")) + else: + output += " None\n" + + return output + + +def __resolve_privileged_helper_errors(return_code: int) -> str: + """ + Attempt to resolve Privileged Helper Tool error codes. + """ + if return_code not in [error_code.value for error_code in PrivilegedHelperErrorCodes]: + return None + + return PrivilegedHelperErrorCodes(return_code).name + + +def __format_output(output: str) -> str: + """ + Format output. + """ + if not output: + # Shouldn't happen, but just in case + return " None\n" + + _result = "\n".join([f" {line}" for line in output.split("\n") if line not in ["", "\n"]]) + if not _result.endswith("\n"): + _result += "\n" + + return _result \ No newline at end of file diff --git a/opencore_legacy_patcher/support/utilities.py b/opencore_legacy_patcher/support/utilities.py index addf1493e..060ec17f3 100644 --- a/opencore_legacy_patcher/support/utilities.py +++ b/opencore_legacy_patcher/support/utilities.py @@ -41,13 +41,6 @@ def string_to_hex(input_string): return input_string -def process_status(process_result): - if process_result.returncode != 0: - logging.info(f"Process failed with exit code {process_result.returncode}") - logging.info(f"Please report the issue on the Discord server") - raise Exception(f"Process result: \n{process_result.stdout.decode()}") - - def human_fmt(num): for unit in ["B", "KB", "MB", "GB", "TB", "PB"]: if abs(num) < 1000.0: @@ -553,14 +546,6 @@ def check_boot_mode(): except (KeyError, TypeError, plistlib.InvalidFileException): return None -def elevated(*args, **kwargs) -> subprocess.CompletedProcess: - # When running through our GUI, we run as root, however we do not get uid 0 - # Best to assume CLI is running as root - if os.getuid() == 0 or check_cli_args() is not None: - return subprocess.run(*args, **kwargs) - else: - return subprocess.run(["/usr/bin/sudo"] + [args[0][0]] + args[0][1:], **kwargs) - def fetch_staged_update(variant: str = "Update") -> tuple[str, str]: """ diff --git a/opencore_legacy_patcher/support/validation.py b/opencore_legacy_patcher/support/validation.py index d4fb8ef44..ead7b0807 100644 --- a/opencore_legacy_patcher/support/validation.py +++ b/opencore_legacy_patcher/support/validation.py @@ -13,6 +13,7 @@ from .. import constants from ..sys_patch import sys_patch_helpers from ..efi_builder import build +from ..support import subprocess_wrapper from ..datasets import ( example_data, @@ -83,7 +84,7 @@ class PatcherValidation: result = subprocess.run([self.constants.ocvalidate_path, f"{self.constants.opencore_release_folder}/EFI/OC/config.plist"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if result.returncode != 0: logging.info("Error on build!") - logging.info(result.stdout.decode()) + subprocess_wrapper.log(result) raise Exception(f"Validation failed for predefined model: {model}") else: logging.info(f"Validation succeeded for predefined model: {model}") @@ -103,7 +104,7 @@ class PatcherValidation: result = subprocess.run([self.constants.ocvalidate_path, f"{self.constants.opencore_release_folder}/EFI/OC/config.plist"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if result.returncode != 0: logging.info("Error on build!") - logging.info(result.stdout.decode()) + subprocess_wrapper.log(result) raise Exception(f"Validation failed for predefined model: {self.constants.computer.real_model}") else: logging.info(f"Validation succeeded for predefined model: {self.constants.computer.real_model}") @@ -178,8 +179,7 @@ class PatcherValidation: if output.returncode != 0: logging.info("Failed to unmount Universal-Binaries.dmg") - logging.info(f"Output: {output.stdout.decode()}") - logging.info(f"Return Code: {output.returncode}") + subprocess_wrapper.log(output) raise Exception("Failed to unmount Universal-Binaries.dmg") @@ -196,8 +196,7 @@ class PatcherValidation: if output.returncode != 0: logging.info("Failed to mount Universal-Binaries.dmg") - logging.info(f"Output: {output.stdout.decode()}") - logging.info(f"Return Code: {output.returncode}") + subprocess_wrapper.log(output) raise Exception("Failed to mount Universal-Binaries.dmg") @@ -226,8 +225,7 @@ class PatcherValidation: if output.returncode != 0: logging.info("Failed to unmount Universal-Binaries.dmg") - logging.info(f"Output: {output.stdout.decode()}") - logging.info(f"Return Code: {output.returncode}") + subprocess_wrapper.log(output) raise Exception("Failed to unmount Universal-Binaries.dmg") diff --git a/opencore_legacy_patcher/sys_patch/sys_patch.py b/opencore_legacy_patcher/sys_patch/sys_patch.py index 1e3455c62..625b5192c 100644 --- a/opencore_legacy_patcher/sys_patch/sys_patch.py +++ b/opencore_legacy_patcher/sys_patch/sys_patch.py @@ -49,7 +49,8 @@ from ..datasets import os_data from ..support import ( utilities, - kdk_handler + kdk_handler, + subprocess_wrapper ) from . import ( sys_patch_detect, @@ -126,7 +127,7 @@ class PatchSysVolume: else: if self.root_supports_snapshot is True: logging.info("- Mounting APFS Snapshot as writable") - result = utilities.elevated(["/sbin/mount", "-o", "nobrowse", "-t", "apfs", f"/dev/{self.root_mount_path}", self.mount_location], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + result = subprocess_wrapper.run_as_root(["/sbin/mount", "-o", "nobrowse", "-t", "apfs", f"/dev/{self.root_mount_path}", self.mount_location], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if result.returncode == 0: logging.info(f"- Mounted APFS Snapshot as writable at: {self.mount_location}") if Path(self.mount_extensions).exists(): @@ -136,8 +137,7 @@ class PatchSysVolume: logging.info("- Root Volume appears to have unmounted unexpectedly") else: logging.info("- Unable to mount APFS Snapshot as writable") - logging.info("Reason for mount failure:") - logging.info(result.stdout.decode().strip()) + subprocess_wrapper.log(result) return False @@ -194,7 +194,7 @@ class PatchSysVolume: if kdk_obj.kdk_already_installed is False: # We shouldn't get here, but just in case logging.warning(f"KDK was not installed, but should have been: {kdk_obj.error_msg}") - raise Exception("KDK was not installed, but should have been: {kdk_obj.error_msg}") + raise Exception(f"KDK was not installed, but should have been: {kdk_obj.error_msg}") kdk_path = Path(kdk_obj.kdk_installed_path) if kdk_obj.kdk_installed_path != "" else None @@ -222,10 +222,10 @@ class PatchSysVolume: if save_hid_cs is True and cs_path.exists(): logging.info("- Backing up IOHIDEventDriver CodeSignature") # Note it's a folder, not a file - utilities.elevated(["/bin/cp", "-r", cs_path, f"{self.constants.payload_path}/IOHIDEventDriver_CodeSignature.bak"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + subprocess_wrapper.run_as_root(["/bin/cp", "-r", cs_path, f"{self.constants.payload_path}/IOHIDEventDriver_CodeSignature.bak"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) logging.info(f"- Merging KDK with Root Volume: {kdk_path.name}") - utilities.elevated( + subprocess_wrapper.run_as_root( # Only merge '/System/Library/Extensions' # 'Kernels' and 'KernelSupport' is wasted space for root patching (we don't care above dev kernels) ["/usr/bin/rsync", "-r", "-i", "-a", f"{kdk_path}/System/Library/Extensions/", f"{self.mount_location}/System/Library/Extensions"], @@ -243,9 +243,9 @@ class PatchSysVolume: logging.info("- Restoring IOHIDEventDriver CodeSignature") if not cs_path.exists(): logging.info(" - CodeSignature folder missing, creating") - utilities.elevated(["/bin/mkdir", "-p", cs_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - utilities.elevated(["/bin/cp", "-r", f"{self.constants.payload_path}/IOHIDEventDriver_CodeSignature.bak", cs_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - utilities.elevated(["/bin/rm", "-rf", f"{self.constants.payload_path}/IOHIDEventDriver_CodeSignature.bak"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + subprocess_wrapper.run_as_root(["/bin/mkdir", "-p", cs_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + subprocess_wrapper.run_as_root(["/bin/cp", "-r", f"{self.constants.payload_path}/IOHIDEventDriver_CodeSignature.bak", cs_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + subprocess_wrapper.run_as_root(["/bin/rm", "-rf", f"{self.constants.payload_path}/IOHIDEventDriver_CodeSignature.bak"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) def _unpatch_root_vol(self): @@ -257,11 +257,10 @@ class PatchSysVolume: logging.info("- OS version does not support snapshotting, skipping revert") logging.info("- Reverting to last signed APFS snapshot") - result = utilities.elevated(["/usr/sbin/bless", "--mount", self.mount_location, "--bootefi", "--last-sealed-snapshot"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + result = subprocess_wrapper.run_as_root(["/usr/sbin/bless", "--mount", self.mount_location, "--bootefi", "--last-sealed-snapshot"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if result.returncode != 0: logging.info("- Unable to revert root volume patches") - logging.info("Reason for unpatch Failure:") - logging.info(result.stdout.decode()) + subprocess_wrapper.log(result) logging.info("- Failed to revert snapshot via Apple's 'bless' command") else: self._clean_skylight_plugins() @@ -363,7 +362,7 @@ class PatchSysVolume: else: args = ["/usr/sbin/kextcache", "-i", f"{self.mount_location}/"] - result = utilities.elevated(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + result = subprocess_wrapper.run_as_root(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) # kextcache notes: # - kextcache always returns 0, even if it fails @@ -374,19 +373,17 @@ class PatchSysVolume: # - will return -10 if the volume is missing (ie. unmounted by another process) if result.returncode != 0 or (self.constants.detected_os < os_data.os_data.catalina and "KernelCache ID" not in result.stdout.decode()): logging.info("- Unable to build new kernel cache") - logging.info(f"\nReason for Patch Failure ({result.returncode}):") - logging.info(result.stdout.decode()) + subprocess_wrapper.log(result) logging.info("") logging.info("\nPlease reboot the machine to avoid potential issues rerunning the patcher") return False if self.skip_root_kmutil_requirement is True: # Force rebuild the Auxiliary KC - result = utilities.elevated(["/usr/bin/killall", "syspolicyd", "kernelmanagerd"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + result = subprocess_wrapper.run_as_root(["/usr/bin/killall", "syspolicyd", "kernelmanagerd"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if result.returncode != 0: logging.info("- Unable to remove kernel extension policy files") - logging.info(f"\nReason for Patch Failure ({result.returncode}):") - logging.info(result.stdout.decode()) + subprocess_wrapper.log(result) logging.info("") logging.info("\nPlease reboot the machine to avoid potential issues rerunning the patcher") return False @@ -411,7 +408,7 @@ class PatchSysVolume: if self.root_supports_snapshot is True: logging.info("- Creating new APFS snapshot") - bless = utilities.elevated( + bless = subprocess_wrapper.run_as_root( [ "/usr/sbin/bless", "--folder", f"{self.mount_location}/System/Library/CoreServices", @@ -420,8 +417,7 @@ class PatchSysVolume: ) if bless.returncode != 0: logging.info("- Unable to create new snapshot") - logging.info("Reason for snapshot failure:") - logging.info(bless.stdout.decode()) + subprocess_wrapper.log(bless) if "Can't use last-sealed-snapshot or create-snapshot on non system volume" in bless.stdout.decode(): logging.info("- This is an APFS bug with Monterey and newer! Perform a clean installation to ensure your APFS volume is built correctly") return False @@ -435,7 +431,7 @@ class PatchSysVolume: """ if self.root_mount_path: logging.info("- Unmounting Root Volume (Don't worry if this fails)") - utilities.elevated(["/usr/sbin/diskutil", "unmount", self.root_mount_path], stdout=subprocess.PIPE).stdout.decode().strip().encode() + subprocess_wrapper.run_as_root(["/usr/sbin/diskutil", "unmount", self.root_mount_path], stdout=subprocess.PIPE) else: logging.info("- Skipping Root Volume unmount") @@ -449,7 +445,7 @@ class PatchSysVolume: if self.constants.detected_os > os_data.os_data.catalina: return logging.info("- Rebuilding dyld shared cache") - utilities.process_status(utilities.elevated(["/usr/bin/update_dyld_shared_cache", "-root", f"{self.mount_location}/"])) + subprocess_wrapper.run_as_root_and_verify(["/usr/bin/update_dyld_shared_cache", "-root", f"{self.mount_location}/"]) def _update_preboot_kernel_cache(self) -> None: @@ -460,7 +456,7 @@ class PatchSysVolume: if self.constants.detected_os == os_data.os_data.catalina: logging.info("- Rebuilding preboot kernel cache") - utilities.process_status(utilities.elevated(["/usr/sbin/kcditto"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) + subprocess_wrapper.run_as_root_and_verify(["/usr/sbin/kcditto"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) def _clean_skylight_plugins(self) -> None: @@ -470,11 +466,11 @@ class PatchSysVolume: if (Path(self.mount_application_support) / Path("SkyLightPlugins/")).exists(): logging.info("- Found SkylightPlugins folder, removing old plugins") - utilities.process_status(utilities.elevated(["/bin/rm", "-Rf", f"{self.mount_application_support}/SkyLightPlugins"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) - utilities.process_status(utilities.elevated(["/bin/mkdir", f"{self.mount_application_support}/SkyLightPlugins"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) + subprocess_wrapper.run_as_root_and_verify(["/bin/rm", "-Rf", f"{self.mount_application_support}/SkyLightPlugins"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + subprocess_wrapper.run_as_root_and_verify(["/bin/mkdir", f"{self.mount_application_support}/SkyLightPlugins"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) else: logging.info("- Creating SkylightPlugins folder") - utilities.process_status(utilities.elevated(["/bin/mkdir", "-p", f"{self.mount_application_support}/SkyLightPlugins/"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) + subprocess_wrapper.run_as_root_and_verify(["/bin/mkdir", "-p", f"{self.mount_application_support}/SkyLightPlugins/"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) def _delete_nonmetal_enforcement(self) -> None: @@ -487,7 +483,7 @@ class PatchSysVolume: result = subprocess.run(["/usr/bin/defaults", "read", "/Library/Preferences/com.apple.CoreDisplay", arg], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).stdout.decode("utf-8").strip() if result in ["0", "false", "1", "true"]: logging.info(f"- Removing non-Metal Enforcement Preference: {arg}") - utilities.elevated(["/usr/bin/defaults", "delete", "/Library/Preferences/com.apple.CoreDisplay", arg]) + subprocess_wrapper.run_as_root(["/usr/bin/defaults", "delete", "/Library/Preferences/com.apple.CoreDisplay", arg]) def _clean_auxiliary_kc(self) -> None: @@ -530,15 +526,15 @@ class PatchSysVolume: relocation_path = "/Library/Relocated Extensions" if not Path(relocation_path).exists(): - utilities.elevated(["/bin/mkdir", relocation_path]) + subprocess_wrapper.run_as_root(["/bin/mkdir", relocation_path]) for file in Path("/Library/Extensions").glob("*.kext"): try: if datetime.fromtimestamp(file.stat().st_mtime) < datetime(2021, 10, 1): logging.info(f" - Relocating {file.name} kext to {relocation_path}") if Path(relocation_path) / Path(file.name).exists(): - utilities.elevated(["/bin/rm", "-Rf", relocation_path / Path(file.name)]) - utilities.elevated(["/bin/mv", file, relocation_path]) + subprocess_wrapper.run_as_root(["/bin/rm", "-Rf", relocation_path / Path(file.name)]) + subprocess_wrapper.run_as_root(["/bin/mv", file, relocation_path]) except: # Some users have the most cursed /L*/E* folders # ex. Symlinks pointing to symlinks pointing to dead files @@ -559,8 +555,8 @@ class PatchSysVolume: if sys_patch_helpers.SysPatchHelpers(self.constants).generate_patchset_plist(patchset, file_name, self.kdk_path): logging.info("- Writing patchset information to Root Volume") if Path(destination_path_file).exists(): - utilities.process_status(utilities.elevated(["/bin/rm", destination_path_file], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) - utilities.process_status(utilities.elevated(["/bin/cp", f"{self.constants.payload_path}/{file_name}", destination_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) + subprocess_wrapper.run_as_root_and_verify(["/bin/rm", destination_path_file], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + subprocess_wrapper.run_as_root_and_verify(["/bin/cp", f"{self.constants.payload_path}/{file_name}", destination_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) def _add_auxkc_support(self, install_file: str, source_folder_path: str, install_patch_directory: str, destination_folder_path: str) -> str: @@ -727,10 +723,10 @@ class PatchSysVolume: # Instead, call elevated funtion if string's boolean is True if required_patches[patch]["Processes"][process] is True: logging.info(f"- Running Process as Root:\n{process}") - utilities.process_status(utilities.elevated(process.split(" "), stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) + subprocess_wrapper.run_as_root_and_verify(process.split(" "), stdout=subprocess.PIPE, stderr=subprocess.STDOUT) else: logging.info(f"- Running Process:\n{process}") - utilities.process_status(subprocess.run(process, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)) + subprocess_wrapper.run_and_verify(process, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True) if any(x in required_patches for x in ["AMD Legacy GCN", "AMD Legacy Polaris", "AMD Legacy Vega"]): sys_patch_helpers.SysPatchHelpers(self.constants).disable_window_server_caching() if "Metal 3802 Common Extended" in required_patches: @@ -800,25 +796,25 @@ class PatchSysVolume: if file_name_str.endswith(".framework"): # merge with rsync logging.info(f" - Installing: {file_name}") - utilities.elevated(["/usr/bin/rsync", "-r", "-i", "-a", f"{source_folder}/{file_name}", f"{destination_folder}/"], stdout=subprocess.PIPE) + subprocess_wrapper.run_as_root(["/usr/bin/rsync", "-r", "-i", "-a", f"{source_folder}/{file_name}", f"{destination_folder}/"], stdout=subprocess.PIPE) self._fix_permissions(destination_folder + "/" + file_name) elif Path(source_folder + "/" + file_name_str).is_dir(): # Applicable for .kext, .app, .plugin, .bundle, all of which are directories if Path(destination_folder + "/" + file_name).exists(): logging.info(f" - Found existing {file_name}, overwriting...") - utilities.process_status(utilities.elevated(["/bin/rm", "-R", f"{destination_folder}/{file_name}"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) + subprocess_wrapper.run_as_root_and_verify(["/bin/rm", "-R", f"{destination_folder}/{file_name}"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) else: logging.info(f" - Installing: {file_name}") - utilities.process_status(utilities.elevated(["/bin/cp", "-R", f"{source_folder}/{file_name}", destination_folder], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) + subprocess_wrapper.run_as_root_and_verify(["/bin/cp", "-R", f"{source_folder}/{file_name}", destination_folder], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) self._fix_permissions(destination_folder + "/" + file_name) else: # Assume it's an individual file, replace as normal if Path(destination_folder + "/" + file_name).exists(): logging.info(f" - Found existing {file_name}, overwriting...") - utilities.process_status(utilities.elevated(["/bin/rm", f"{destination_folder}/{file_name}"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) + subprocess_wrapper.run_as_root_and_verify(["/bin/rm", f"{destination_folder}/{file_name}"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) else: logging.info(f" - Installing: {file_name}") - utilities.process_status(utilities.elevated(["/bin/cp", f"{source_folder}/{file_name}", destination_folder], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) + subprocess_wrapper.run_as_root_and_verify(["/bin/cp", f"{source_folder}/{file_name}", destination_folder], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) self._fix_permissions(destination_folder + "/" + file_name) @@ -834,9 +830,9 @@ class PatchSysVolume: if Path(destination_folder + "/" + file_name).exists(): logging.info(f" - Removing: {file_name}") if Path(destination_folder + "/" + file_name).is_dir(): - utilities.process_status(utilities.elevated(["/bin/rm", "-R", f"{destination_folder}/{file_name}"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) + subprocess_wrapper.run_as_root_and_verify(["/bin/rm", "-R", f"{destination_folder}/{file_name}"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) else: - utilities.process_status(utilities.elevated(["/bin/rm", f"{destination_folder}/{file_name}"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) + subprocess_wrapper.run_as_root_and_verify(["/bin/rm", f"{destination_folder}/{file_name}"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) def _fix_permissions(self, destination_file: Path) -> None: @@ -850,8 +846,8 @@ class PatchSysVolume: # Strip recursive arguments chmod_args.pop(1) chown_args.pop(1) - utilities.process_status(utilities.elevated(chmod_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) - utilities.process_status(utilities.elevated(chown_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) + subprocess_wrapper.run_as_root_and_verify(chmod_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + subprocess_wrapper.run_as_root_and_verify(chown_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) def _check_files(self) -> bool: @@ -882,8 +878,7 @@ class PatchSysVolume: if output.returncode != 0: logging.info("- Failed to mount Universal-Binaries.dmg") - logging.info(f"Output: {output.stdout.decode()}") - logging.info(f"Return Code: {output.returncode}") + subprocess_wrapper.log(output) return False logging.info("- Mounted Universal-Binaries.dmg") @@ -923,13 +918,11 @@ class PatchSysVolume: return True logging.info("- Failed to merge DortaniaInternal resources") - logging.info(f"Output: {result.stdout.decode()}") - logging.info(f"Return Code: {result.returncode}") + subprocess_wrapper.log(result) return False logging.info("- Failed to mount DortaniaInternal resources") - logging.info(f"Output: {result.stdout.decode()}") - logging.info(f"Return Code: {result.returncode}") + subprocess_wrapper.log(result) if "Authentication error" not in result.stdout.decode(): try: diff --git a/opencore_legacy_patcher/sys_patch/sys_patch_auto.py b/opencore_legacy_patcher/sys_patch/sys_patch_auto.py index ff263afd3..ee3d0724d 100644 --- a/opencore_legacy_patcher/sys_patch/sys_patch_auto.py +++ b/opencore_legacy_patcher/sys_patch/sys_patch_auto.py @@ -29,7 +29,8 @@ from ..support import ( utilities, updates, global_settings, - network_handler + network_handler, + subprocess_wrapper ) @@ -363,16 +364,16 @@ Please check the Github page for more information about this release.""" logging.info(f" - {name} checksums match, skipping") continue logging.info(f" - Existing service found, removing") - utilities.process_status(utilities.elevated(["/bin/rm", services[service]], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) + subprocess_wrapper.run_as_root_and_verify(["/bin/rm", services[service]], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) # Create parent directories if not Path(services[service]).parent.exists(): logging.info(f" - Creating {Path(services[service]).parent} directory") - utilities.process_status(utilities.elevated(["/bin/mkdir", "-p", Path(services[service]).parent], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) - utilities.process_status(utilities.elevated(["/bin/cp", service, services[service]], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) + subprocess_wrapper.run_as_root_and_verify(["/bin/mkdir", "-p", Path(services[service]).parent], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + subprocess_wrapper.run_as_root_and_verify(["/bin/cp", service, services[service]], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) # Set the permissions on the service - utilities.process_status(utilities.elevated(["/bin/chmod", "644", services[service]], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) - utilities.process_status(utilities.elevated(["/usr/sbin/chown", "root:wheel", services[service]], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) + subprocess_wrapper.run_as_root_and_verify(["/bin/chmod", "644", services[service]], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + subprocess_wrapper.run_as_root_and_verify(["/usr/sbin/chown", "root:wheel", services[service]], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if self.constants.launcher_binary.startswith("/Library/Application Support/Dortania/"): logging.info("- Skipping Patcher Install, already installed") @@ -384,24 +385,24 @@ Please check the Github page for more information about this release.""" if not Path("Library/Application Support/Dortania").exists(): logging.info("- Creating /Library/Application Support/Dortania/") - utilities.process_status(utilities.elevated(["/bin/mkdir", "-p", "/Library/Application Support/Dortania"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) + subprocess_wrapper.run_as_root_and_verify(["/bin/mkdir", "-p", "/Library/Application Support/Dortania"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) logging.info("- Copying OpenCore Patcher to /Library/Application Support/Dortania/") if Path("/Library/Application Support/Dortania/OpenCore-Patcher.app").exists(): logging.info("- Deleting existing OpenCore-Patcher") - utilities.process_status(utilities.elevated(["/bin/rm", "-R", "/Library/Application Support/Dortania/OpenCore-Patcher.app"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) + subprocess_wrapper.run_as_root_and_verify(["/bin/rm", "-R", "/Library/Application Support/Dortania/OpenCore-Patcher.app"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) # Strip everything after OpenCore-Patcher.app path = str(self.constants.launcher_binary).split("/Contents/MacOS/OpenCore-Patcher")[0] logging.info(f"- Copying {path} to /Library/Application Support/Dortania/") - utilities.process_status(utilities.elevated(["/usr/bin/ditto", path, "/Library/Application Support/Dortania/OpenCore-Patcher.app"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) + subprocess_wrapper.run_as_root_and_verify(["/usr/bin/ditto", path, "/Library/Application Support/Dortania/OpenCore-Patcher.app"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if not Path("/Library/Application Support/Dortania/OpenCore-Patcher.app").exists(): # Sometimes the binary the user launches may have a suffix (ie. OpenCore-Patcher 3.app) # We'll want to rename it to OpenCore-Patcher.app path = path.split("/")[-1] logging.info(f"- Renaming {path} to OpenCore-Patcher.app") - utilities.process_status(utilities.elevated(["/bin/mv", f"/Library/Application Support/Dortania/{path}", "/Library/Application Support/Dortania/OpenCore-Patcher.app"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) + subprocess_wrapper.run_as_root_and_verify(["/bin/mv", f"/Library/Application Support/Dortania/{path}", "/Library/Application Support/Dortania/OpenCore-Patcher.app"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) subprocess.run(["/usr/bin/xattr", "-cr", "/Library/Application Support/Dortania/OpenCore-Patcher.app"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) @@ -410,7 +411,7 @@ Please check the Github page for more information about this release.""" # If there's already an alias or exiting app, skip if not Path("/Applications/OpenCore-Patcher.app").exists(): logging.info("- Making app alias") - utilities.process_status(utilities.elevated(["/bin/ln", "-s", "/Library/Application Support/Dortania/OpenCore-Patcher.app", "/Applications/OpenCore-Patcher.app"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) + subprocess_wrapper.run_as_root_and_verify(["/bin/ln", "-s", "/Library/Application Support/Dortania/OpenCore-Patcher.app", "/Applications/OpenCore-Patcher.app"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) def _create_rsr_monitor_daemon(self) -> bool: diff --git a/opencore_legacy_patcher/sys_patch/sys_patch_helpers.py b/opencore_legacy_patcher/sys_patch/sys_patch_helpers.py index 047f0b0d4..2684af9a7 100644 --- a/opencore_legacy_patcher/sys_patch/sys_patch_helpers.py +++ b/opencore_legacy_patcher/sys_patch/sys_patch_helpers.py @@ -18,7 +18,8 @@ from ..datasets import os_data from ..support import ( bplist, generate_smbios, - utilities + utilities, + subprocess_wrapper ) @@ -138,9 +139,9 @@ class SysPatchHelpers: logging.info("Disabling WindowServer Caching") # Invoke via 'bash -c' to resolve pathing - utilities.elevated(["bash", "-c", "rm -rf /private/var/folders/*/*/*/WindowServer/com.apple.WindowServer"]) + subprocess_wrapper.run_as_root(["/bin/bash", "-c", "rm -rf /private/var/folders/*/*/*/WindowServer/com.apple.WindowServer"]) # Disable writing to WindowServer folder - utilities.elevated(["bash", "-c", "chflags uchg /private/var/folders/*/*/*/WindowServer"]) + subprocess_wrapper.run_as_root(["/bin/bash", "-c", "chflags uchg /private/var/folders/*/*/*/WindowServer"]) # Reference: # To reverse write lock: # 'chflags nouchg /private/var/folders/*/*/*/WindowServer' @@ -219,9 +220,10 @@ class SysPatchHelpers: return logging.info("Installing Kernel Collection syncing utility") - result = utilities.elevated([self.constants.rsrrepair_userspace_path, "--install"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + result = subprocess_wrapper.run_as_root([self.constants.rsrrepair_userspace_path, "--install"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if result.returncode != 0: - logging.info(f"- Failed to install RSRRepair: {result.stdout.decode()}") + logging.info("- Failed to install RSRRepair") + subprocess_wrapper.log(result) def patch_gpu_compiler_libraries(self, mount_point: Union[str, Path]): @@ -282,6 +284,6 @@ class SysPatchHelpers: src_dir = f"{LIBRARY_DIR}/{file.name}" if not Path(f"{DEST_DIR}/lib").exists(): - utilities.process_status(utilities.elevated(["/bin/cp", "-cR", f"{src_dir}/lib", f"{DEST_DIR}/"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) + subprocess_wrapper.run_as_root_and_verify(["/bin/cp", "-cR", f"{src_dir}/lib", f"{DEST_DIR}/"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) break \ No newline at end of file diff --git a/opencore_legacy_patcher/wx_gui/gui_macos_installer_flash.py b/opencore_legacy_patcher/wx_gui/gui_macos_installer_flash.py index 689ad7828..0f01017d3 100644 --- a/opencore_legacy_patcher/wx_gui/gui_macos_installer_flash.py +++ b/opencore_legacy_patcher/wx_gui/gui_macos_installer_flash.py @@ -26,6 +26,7 @@ from ..support import ( utilities, network_handler, kdk_handler, + subprocess_wrapper ) @@ -525,7 +526,7 @@ class macOSInstallerFlashFrame(wx.Frame): result = subprocess.run(["/usr/bin/hdiutil", "attach", kdk_dmg_path, "-mountpoint", mount_point, "-nobrowse"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if result.returncode != 0: logging.info("Failed to mount KDK") - logging.info(result.stdout.decode("utf-8")) + subprocess_wrapper.log(result) return logging.info("Copying KDK") @@ -535,7 +536,7 @@ class macOSInstallerFlashFrame(wx.Frame): result = subprocess.run(["/usr/bin/hdiutil", "detach", mount_point], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if result.returncode != 0: logging.info("Failed to unmount KDK") - logging.info(result.stdout.decode("utf-8")) + subprocess_wrapper.log(result) return logging.info("Removing KDK Disk Image") diff --git a/opencore_legacy_patcher/wx_gui/gui_settings.py b/opencore_legacy_patcher/wx_gui/gui_settings.py index 8b86a0ac8..8dfe48a6d 100644 --- a/opencore_legacy_patcher/wx_gui/gui_settings.py +++ b/opencore_legacy_patcher/wx_gui/gui_settings.py @@ -24,7 +24,8 @@ from ..support import ( global_settings, defaults, generate_smbios, - network_handler + network_handler, + subprocess_wrapper ) from ..datasets import ( model_array, @@ -1324,7 +1325,7 @@ Hardware Information: raise Exception("Test Exception") def on_mount_root_vol(self, event: wx.Event) -> None: - if os.geteuid() != 0: + if os.geteuid() != 0 and subprocess_wrapper.supports_privileged_helper() is False: wx.MessageDialog(self.parent, "Please relaunch as Root to mount the Root Volume", "Error", wx.OK | wx.ICON_ERROR).ShowModal() else: #Don't need to pass model as we're bypassing all logic @@ -1334,7 +1335,7 @@ Hardware Information: wx.MessageDialog(self.parent, "Root Volume Mount Failed, check terminal output", "Error", wx.OK | wx.ICON_ERROR).ShowModal() def on_bless_root_vol(self, event: wx.Event) -> None: - if os.geteuid() != 0: + if os.geteuid() != 0 and subprocess_wrapper.supports_privileged_helper() is False: wx.MessageDialog(self.parent, "Please relaunch as Root to save changes", "Error", wx.OK | wx.ICON_ERROR).ShowModal() else: #Don't need to pass model as we're bypassing all logic diff --git a/opencore_legacy_patcher/wx_gui/gui_support.py b/opencore_legacy_patcher/wx_gui/gui_support.py index 6fc3cbf75..dc3b83115 100644 --- a/opencore_legacy_patcher/wx_gui/gui_support.py +++ b/opencore_legacy_patcher/wx_gui/gui_support.py @@ -15,10 +15,12 @@ import packaging.version from pathlib import Path +from . import gui_about + from .. import constants -from ..wx_gui import gui_about from ..detections import device_probe +from ..support import subprocess_wrapper from ..datasets import ( model_array, @@ -75,7 +77,7 @@ class GenerateMenubar: self.frame.Bind(wx.EVT_MENU, lambda event: RelaunchApplicationAsRoot(self.frame, self.constants).relaunch(None), relaunchItem) self.frame.Bind(wx.EVT_MENU, lambda event: subprocess.run(["/usr/bin/open", "--reveal", self.constants.log_filepath]), revealLogItem) - if os.geteuid() == 0: + if os.geteuid() == 0 or subprocess_wrapper.supports_privileged_helper() is True: relaunchItem.Enable(False) diff --git a/opencore_legacy_patcher/wx_gui/gui_sys_patch_display.py b/opencore_legacy_patcher/wx_gui/gui_sys_patch_display.py index 898d04645..ca74ea71b 100644 --- a/opencore_legacy_patcher/wx_gui/gui_sys_patch_display.py +++ b/opencore_legacy_patcher/wx_gui/gui_sys_patch_display.py @@ -13,6 +13,7 @@ from pathlib import Path from .. import constants from ..sys_patch import sys_patch_detect +from ..support import subprocess_wrapper from ..wx_gui import ( gui_main_menu, @@ -242,7 +243,7 @@ class SysPatchDisplayFrame(wx.Frame): revert_button.Disable() # Relaunch as root if not root - if os.geteuid() != 0: + if os.geteuid() != 0 and subprocess_wrapper.supports_privileged_helper() is False: start_button.Bind(wx.EVT_BUTTON, gui_support.RelaunchApplicationAsRoot(frame, self.constants).relaunch) revert_button.Bind(wx.EVT_BUTTON, gui_support.RelaunchApplicationAsRoot(frame, self.constants).relaunch) diff --git a/opencore_legacy_patcher/wx_gui/gui_update.py b/opencore_legacy_patcher/wx_gui/gui_update.py index 81db2eab5..d08a49ca0 100644 --- a/opencore_legacy_patcher/wx_gui/gui_update.py +++ b/opencore_legacy_patcher/wx_gui/gui_update.py @@ -20,7 +20,8 @@ from ..wx_gui import ( ) from ..support import ( network_handler, - updates + updates, + subprocess_wrapper ) @@ -200,7 +201,8 @@ class UpdateFrame(wx.Frame): ["/usr/bin/ditto", "-xk", str(self.constants.payload_path / "OpenCore-Patcher-GUI.app.zip"), str(self.constants.payload_path)], capture_output=True ) if result.returncode != 0: - logging.error(f"Failed to extract update. Error: {result.stderr.decode('utf-8')}") + logging.error(f"Failed to extract update.") + subprocess_wrapper.log(result) wx.CallAfter(self.progress_bar_animation.stop_pulse) wx.CallAfter(self.progress_bar.SetValue, 0) wx.CallAfter(wx.MessageBox, f"Failed to extract update. Error: {result.stderr.decode('utf-8')}", "Critical Error!", wx.OK | wx.ICON_ERROR) @@ -278,7 +280,8 @@ EOF logging.info("User cancelled update") wx.CallAfter(wx.MessageBox, "User cancelled update", "Update Cancelled", wx.OK | wx.ICON_INFORMATION) else: - logging.critical(f"Failed to install update. Error: {result.stderr.decode('utf-8')}") + logging.critical("Failed to install update.") + subprocess_wrapper.log(result) wx.CallAfter(wx.MessageBox, f"Failed to install update. Error: {result.stderr.decode('utf-8')}", "Critical Error!", wx.OK | wx.ICON_ERROR) wx.CallAfter(sys.exit, 1)