subprocess_wrapper.py: Add unified error handling

Additionally adds backend support for Privileged Helper Tool
This commit is contained in:
Mykola Grymalyuk
2024-05-10 16:06:16 -06:00
parent 84e020f7ea
commit abb18a5ad2
17 changed files with 304 additions and 142 deletions
+3 -1
View File
@@ -11,6 +11,8 @@ import subprocess
from pathlib import Path from pathlib import Path
from . import subprocess_wrapper
from .. import constants from .. import constants
from ..wx_gui import gui_entry from ..wx_gui import gui_entry
@@ -172,7 +174,7 @@ class arguments:
if "GPUCompanionBundles" not in kext_plist: if "GPUCompanionBundles" not in kext_plist:
continue continue
logging.info(f" - Removing {kext.name}") 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: def _build_handler(self) -> None:
@@ -10,7 +10,8 @@ ie. during automated patching
import os import os
import logging import logging
import plistlib import plistlib
import subprocess
from . import subprocess_wrapper
from pathlib import Path from pathlib import Path
@@ -115,12 +116,11 @@ class GlobalEnviromentSettings:
This in turn breaks normal OCLP execution to write to settings file 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 return
# Set file permission to allow any user to write to log file # 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: if result.returncode != 0:
logging.warning("Failed to fix settings file permissions:") logging.warning("Failed to fix settings file permissions:")
if result.stderr: subprocess_wrapper.log(result)
logging.warning(result.stderr.decode("utf-8"))
+4 -4
View File
@@ -9,7 +9,7 @@ import applescript
from pathlib import Path from pathlib import Path
from . import utilities from . import utilities, subprocess_wrapper
from .. import constants from .. import constants
@@ -92,7 +92,7 @@ class tui_disk_installation:
def install_opencore(self, full_disk_identifier: str): def install_opencore(self, full_disk_identifier: str):
# TODO: Apple Script fails in Yosemite(?) and older # TODO: Apple Script fails in Yosemite(?) and older
logging.info(f"Mounting partition: {full_disk_identifier}") 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: 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() 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: except applescript.ScriptError as e:
@@ -105,10 +105,10 @@ class tui_disk_installation:
logging.info("Please disable Safe Mode and try again.") logging.info("Please disable Safe Mode and try again.")
return return
else: 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: if result.returncode != 0:
logging.info("Mount failed") logging.info("Mount failed")
logging.info(result.stderr.decode()) subprocess_wrapper.log(result)
return return
partition_info = plistlib.loads(subprocess.run(["/usr/sbin/diskutil", "info", "-plist", full_disk_identifier], stdout=subprocess.PIPE).stdout.decode().strip().encode()) partition_info = plistlib.loads(subprocess.run(["/usr/sbin/diskutil", "info", "-plist", full_disk_identifier], stdout=subprocess.PIPE).stdout.decode().strip().encode())
+16 -19
View File
@@ -18,8 +18,8 @@ from .. import constants
from ..datasets import os_data from ..datasets import os_data
from . import ( from . import (
utilities, network_handler,
network_handler subprocess_wrapper
) )
KDK_INSTALL_PATH: str = "/Library/Developer/KDKs" KDK_INSTALL_PATH: str = "/Library/Developer/KDKs"
@@ -464,7 +464,7 @@ class KernelDebugKitObject:
if self.passive is True: if self.passive is True:
return 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") logging.warning("Cannot remove KDK, not running as root")
return return
@@ -474,10 +474,10 @@ class KernelDebugKitObject:
rm_args = ["/bin/rm", "-rf" if Path(kdk_path).is_dir() else "-f", kdk_path] 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: if result.returncode != 0:
logging.warning(f"Failed to remove KDK: {kdk_path}") logging.warning(f"Failed to remove KDK: {kdk_path}")
logging.warning(f"{result.stdout.decode('utf-8')}") subprocess_wrapper.log(result)
return return
logging.info(f"Successfully removed KDK: {kdk_path}") 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) result = subprocess.run(["/usr/bin/hdiutil", "verify", self.constants.kdk_download_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if result.returncode != 0: if result.returncode != 0:
logging.info("Error: Kernel Debug Kit checksum verification failed!") 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)" 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}") logging.info(f"{msg}")
@@ -579,7 +579,7 @@ class KernelDebugKitUtilities:
bool: True if successful, False if not 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") logging.warning("Cannot install KDK, not running as root")
return False return False
@@ -588,12 +588,10 @@ class KernelDebugKitUtilities:
# TODO: Check whether enough disk space is available # 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: if result.returncode != 0:
logging.info("Failed to install KDK:") logging.info("Failed to install KDK:")
logging.info(result.stdout.decode('utf-8')) subprocess_wrapper.log(result)
if result.stderr:
logging.info(result.stderr.decode('utf-8'))
return False return False
return True return True
@@ -609,20 +607,19 @@ class KernelDebugKitUtilities:
bool: True if successful, False if not 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") logging.warning("Cannot install KDK, not running as root")
return False return False
logging.info(f"Extracting downloaded KDK disk image") logging.info(f"Extracting downloaded KDK disk image")
with tempfile.TemporaryDirectory() as mount_point: 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: if result.returncode != 0:
logging.info("Failed to mount KDK:") logging.info("Failed to mount KDK:")
logging.info(result.stdout.decode('utf-8')) subprocess_wrapper.log(result)
return False return False
kdk_pkg_path = Path(f"{mount_point}/KernelDebugKit.pkg") kdk_pkg_path = Path(f"{mount_point}/KernelDebugKit.pkg")
if not kdk_pkg_path.exists(): if not kdk_pkg_path.exists():
logging.warning("Failed to find KDK package in DMG, likely corrupted!!!") logging.warning("Failed to find KDK package in DMG, likely corrupted!!!")
self._unmount_disk_image(mount_point) self._unmount_disk_image(mount_point)
@@ -672,12 +669,12 @@ class KernelDebugKitUtilities:
logging.warning("Malformed KDK Info.plist provided, cannot create backup") logging.warning("Malformed KDK Info.plist provided, cannot create backup")
return 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") logging.warning("Cannot create KDK backup, not running as root")
return return
if not Path(KDK_INSTALL_PATH).exists(): 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_name = f"KDK_{kdk_info_dict['version']}_{kdk_info_dict['build']}.pkg"
kdk_dst_path = Path(f"{KDK_INSTALL_PATH}/{kdk_dst_name}") kdk_dst_path = Path(f"{KDK_INSTALL_PATH}/{kdk_dst_name}")
@@ -687,7 +684,7 @@ class KernelDebugKitUtilities:
logging.info("Backup already exists, skipping") logging.info("Backup already exists, skipping")
return 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: if result.returncode != 0:
logging.info("Failed to create KDK backup:") logging.info("Failed to create KDK backup:")
logging.info(result.stdout.decode('utf-8')) subprocess_wrapper.log(result)
@@ -18,7 +18,8 @@ from .. import constants
from . import ( from . import (
analytics_handler, 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 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 return
paths = [ paths = [
@@ -139,15 +140,10 @@ class InitializeLoggingSupport:
] ]
for path in paths: 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: if result.returncode != 0:
logging.error(f"Failed to fix log file permissions") logging.error(f"Failed to fix log file permissions")
if result.stdout: subprocess_wrapper.log(result)
logging.error("STDOUT:")
logging.error(result.stdout.decode("utf-8"))
if result.stderr:
logging.error("STDERR:")
logging.error(result.stderr.decode("utf-8"))
def _initialize_logging_configuration(self, log_to_file: bool = True) -> None: def _initialize_logging_configuration(self, log_to_file: bool = True) -> None:
@@ -12,6 +12,8 @@ import logging
from pathlib import Path from pathlib import Path
from . import subprocess_wrapper
from .. import constants from .. import constants
@@ -55,8 +57,7 @@ class RoutePayloadDiskImage:
atexit.register(self._unmount_active_dmgs, unmount_all_active=False) atexit.register(self._unmount_active_dmgs, unmount_all_active=False)
else: else:
logging.info("Failed to mount payloads.dmg") logging.info("Failed to mount payloads.dmg")
logging.info(f"Output: {output.stdout.decode()}") subprocess_wrapper.log(output)
logging.info(f"Return Code: {output.returncode}")
def _unmount_active_dmgs(self, unmount_all_active: bool = True) -> None: def _unmount_active_dmgs(self, unmount_all_active: bool = True) -> None:
@@ -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: <command>
Return Code: <return code>
Standard Output:
<standard output line 1>
<standard output line 2>
...
Standard Error:
<standard error line 1>
<standard error line 2>
...
"""
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
@@ -41,13 +41,6 @@ def string_to_hex(input_string):
return 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): def human_fmt(num):
for unit in ["B", "KB", "MB", "GB", "TB", "PB"]: for unit in ["B", "KB", "MB", "GB", "TB", "PB"]:
if abs(num) < 1000.0: if abs(num) < 1000.0:
@@ -553,14 +546,6 @@ def check_boot_mode():
except (KeyError, TypeError, plistlib.InvalidFileException): except (KeyError, TypeError, plistlib.InvalidFileException):
return None 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]: def fetch_staged_update(variant: str = "Update") -> tuple[str, str]:
""" """
@@ -13,6 +13,7 @@ from .. import constants
from ..sys_patch import sys_patch_helpers from ..sys_patch import sys_patch_helpers
from ..efi_builder import build from ..efi_builder import build
from ..support import subprocess_wrapper
from ..datasets import ( from ..datasets import (
example_data, 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) 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: if result.returncode != 0:
logging.info("Error on build!") logging.info("Error on build!")
logging.info(result.stdout.decode()) subprocess_wrapper.log(result)
raise Exception(f"Validation failed for predefined model: {model}") raise Exception(f"Validation failed for predefined model: {model}")
else: else:
logging.info(f"Validation succeeded for predefined model: {model}") 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) 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: if result.returncode != 0:
logging.info("Error on build!") 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}") raise Exception(f"Validation failed for predefined model: {self.constants.computer.real_model}")
else: else:
logging.info(f"Validation succeeded for predefined model: {self.constants.computer.real_model}") logging.info(f"Validation succeeded for predefined model: {self.constants.computer.real_model}")
@@ -178,8 +179,7 @@ class PatcherValidation:
if output.returncode != 0: if output.returncode != 0:
logging.info("Failed to unmount Universal-Binaries.dmg") logging.info("Failed to unmount Universal-Binaries.dmg")
logging.info(f"Output: {output.stdout.decode()}") subprocess_wrapper.log(output)
logging.info(f"Return Code: {output.returncode}")
raise Exception("Failed to unmount Universal-Binaries.dmg") raise Exception("Failed to unmount Universal-Binaries.dmg")
@@ -196,8 +196,7 @@ class PatcherValidation:
if output.returncode != 0: if output.returncode != 0:
logging.info("Failed to mount Universal-Binaries.dmg") logging.info("Failed to mount Universal-Binaries.dmg")
logging.info(f"Output: {output.stdout.decode()}") subprocess_wrapper.log(output)
logging.info(f"Return Code: {output.returncode}")
raise Exception("Failed to mount Universal-Binaries.dmg") raise Exception("Failed to mount Universal-Binaries.dmg")
@@ -226,8 +225,7 @@ class PatcherValidation:
if output.returncode != 0: if output.returncode != 0:
logging.info("Failed to unmount Universal-Binaries.dmg") logging.info("Failed to unmount Universal-Binaries.dmg")
logging.info(f"Output: {output.stdout.decode()}") subprocess_wrapper.log(output)
logging.info(f"Return Code: {output.returncode}")
raise Exception("Failed to unmount Universal-Binaries.dmg") raise Exception("Failed to unmount Universal-Binaries.dmg")
+44 -51
View File
@@ -49,7 +49,8 @@ from ..datasets import os_data
from ..support import ( from ..support import (
utilities, utilities,
kdk_handler kdk_handler,
subprocess_wrapper
) )
from . import ( from . import (
sys_patch_detect, sys_patch_detect,
@@ -126,7 +127,7 @@ class PatchSysVolume:
else: else:
if self.root_supports_snapshot is True: if self.root_supports_snapshot is True:
logging.info("- Mounting APFS Snapshot as writable") 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: if result.returncode == 0:
logging.info(f"- Mounted APFS Snapshot as writable at: {self.mount_location}") logging.info(f"- Mounted APFS Snapshot as writable at: {self.mount_location}")
if Path(self.mount_extensions).exists(): if Path(self.mount_extensions).exists():
@@ -136,8 +137,7 @@ class PatchSysVolume:
logging.info("- Root Volume appears to have unmounted unexpectedly") logging.info("- Root Volume appears to have unmounted unexpectedly")
else: else:
logging.info("- Unable to mount APFS Snapshot as writable") logging.info("- Unable to mount APFS Snapshot as writable")
logging.info("Reason for mount failure:") subprocess_wrapper.log(result)
logging.info(result.stdout.decode().strip())
return False return False
@@ -194,7 +194,7 @@ class PatchSysVolume:
if kdk_obj.kdk_already_installed is False: if kdk_obj.kdk_already_installed is False:
# We shouldn't get here, but just in case # We shouldn't get here, but just in case
logging.warning(f"KDK was not installed, but should have been: {kdk_obj.error_msg}") 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 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(): if save_hid_cs is True and cs_path.exists():
logging.info("- Backing up IOHIDEventDriver CodeSignature") logging.info("- Backing up IOHIDEventDriver CodeSignature")
# Note it's a folder, not a file # 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}") logging.info(f"- Merging KDK with Root Volume: {kdk_path.name}")
utilities.elevated( subprocess_wrapper.run_as_root(
# Only merge '/System/Library/Extensions' # Only merge '/System/Library/Extensions'
# 'Kernels' and 'KernelSupport' is wasted space for root patching (we don't care above dev kernels) # '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"], ["/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") logging.info("- Restoring IOHIDEventDriver CodeSignature")
if not cs_path.exists(): if not cs_path.exists():
logging.info(" - CodeSignature folder missing, creating") logging.info(" - CodeSignature folder missing, creating")
utilities.elevated(["/bin/mkdir", "-p", cs_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) subprocess_wrapper.run_as_root(["/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) subprocess_wrapper.run_as_root(["/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/rm", "-rf", f"{self.constants.payload_path}/IOHIDEventDriver_CodeSignature.bak"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
def _unpatch_root_vol(self): def _unpatch_root_vol(self):
@@ -257,11 +257,10 @@ class PatchSysVolume:
logging.info("- OS version does not support snapshotting, skipping revert") logging.info("- OS version does not support snapshotting, skipping revert")
logging.info("- Reverting to last signed APFS snapshot") 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: if result.returncode != 0:
logging.info("- Unable to revert root volume patches") logging.info("- Unable to revert root volume patches")
logging.info("Reason for unpatch Failure:") subprocess_wrapper.log(result)
logging.info(result.stdout.decode())
logging.info("- Failed to revert snapshot via Apple's 'bless' command") logging.info("- Failed to revert snapshot via Apple's 'bless' command")
else: else:
self._clean_skylight_plugins() self._clean_skylight_plugins()
@@ -363,7 +362,7 @@ class PatchSysVolume:
else: else:
args = ["/usr/sbin/kextcache", "-i", f"{self.mount_location}/"] 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 notes:
# - kextcache always returns 0, even if it fails # - 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) # - 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()): 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("- Unable to build new kernel cache")
logging.info(f"\nReason for Patch Failure ({result.returncode}):") subprocess_wrapper.log(result)
logging.info(result.stdout.decode())
logging.info("") logging.info("")
logging.info("\nPlease reboot the machine to avoid potential issues rerunning the patcher") logging.info("\nPlease reboot the machine to avoid potential issues rerunning the patcher")
return False return False
if self.skip_root_kmutil_requirement is True: if self.skip_root_kmutil_requirement is True:
# Force rebuild the Auxiliary KC # 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: if result.returncode != 0:
logging.info("- Unable to remove kernel extension policy files") logging.info("- Unable to remove kernel extension policy files")
logging.info(f"\nReason for Patch Failure ({result.returncode}):") subprocess_wrapper.log(result)
logging.info(result.stdout.decode())
logging.info("") logging.info("")
logging.info("\nPlease reboot the machine to avoid potential issues rerunning the patcher") logging.info("\nPlease reboot the machine to avoid potential issues rerunning the patcher")
return False return False
@@ -411,7 +408,7 @@ class PatchSysVolume:
if self.root_supports_snapshot is True: if self.root_supports_snapshot is True:
logging.info("- Creating new APFS snapshot") logging.info("- Creating new APFS snapshot")
bless = utilities.elevated( bless = subprocess_wrapper.run_as_root(
[ [
"/usr/sbin/bless", "/usr/sbin/bless",
"--folder", f"{self.mount_location}/System/Library/CoreServices", "--folder", f"{self.mount_location}/System/Library/CoreServices",
@@ -420,8 +417,7 @@ class PatchSysVolume:
) )
if bless.returncode != 0: if bless.returncode != 0:
logging.info("- Unable to create new snapshot") logging.info("- Unable to create new snapshot")
logging.info("Reason for snapshot failure:") subprocess_wrapper.log(bless)
logging.info(bless.stdout.decode())
if "Can't use last-sealed-snapshot or create-snapshot on non system volume" in bless.stdout.decode(): 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") 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 return False
@@ -435,7 +431,7 @@ class PatchSysVolume:
""" """
if self.root_mount_path: if self.root_mount_path:
logging.info("- Unmounting Root Volume (Don't worry if this fails)") 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: else:
logging.info("- Skipping Root Volume unmount") logging.info("- Skipping Root Volume unmount")
@@ -449,7 +445,7 @@ class PatchSysVolume:
if self.constants.detected_os > os_data.os_data.catalina: if self.constants.detected_os > os_data.os_data.catalina:
return return
logging.info("- Rebuilding dyld shared cache") 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: def _update_preboot_kernel_cache(self) -> None:
@@ -460,7 +456,7 @@ class PatchSysVolume:
if self.constants.detected_os == os_data.os_data.catalina: if self.constants.detected_os == os_data.os_data.catalina:
logging.info("- Rebuilding preboot kernel cache") 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: def _clean_skylight_plugins(self) -> None:
@@ -470,11 +466,11 @@ class PatchSysVolume:
if (Path(self.mount_application_support) / Path("SkyLightPlugins/")).exists(): if (Path(self.mount_application_support) / Path("SkyLightPlugins/")).exists():
logging.info("- Found SkylightPlugins folder, removing old plugins") 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)) subprocess_wrapper.run_as_root_and_verify(["/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/mkdir", f"{self.mount_application_support}/SkyLightPlugins"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
else: else:
logging.info("- Creating SkylightPlugins folder") 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: 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() 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"]: if result in ["0", "false", "1", "true"]:
logging.info(f"- Removing non-Metal Enforcement Preference: {arg}") 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: def _clean_auxiliary_kc(self) -> None:
@@ -530,15 +526,15 @@ class PatchSysVolume:
relocation_path = "/Library/Relocated Extensions" relocation_path = "/Library/Relocated Extensions"
if not Path(relocation_path).exists(): 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"): for file in Path("/Library/Extensions").glob("*.kext"):
try: try:
if datetime.fromtimestamp(file.stat().st_mtime) < datetime(2021, 10, 1): if datetime.fromtimestamp(file.stat().st_mtime) < datetime(2021, 10, 1):
logging.info(f" - Relocating {file.name} kext to {relocation_path}") logging.info(f" - Relocating {file.name} kext to {relocation_path}")
if Path(relocation_path) / Path(file.name).exists(): if Path(relocation_path) / Path(file.name).exists():
utilities.elevated(["/bin/rm", "-Rf", relocation_path / Path(file.name)]) subprocess_wrapper.run_as_root(["/bin/rm", "-Rf", relocation_path / Path(file.name)])
utilities.elevated(["/bin/mv", file, relocation_path]) subprocess_wrapper.run_as_root(["/bin/mv", file, relocation_path])
except: except:
# Some users have the most cursed /L*/E* folders # Some users have the most cursed /L*/E* folders
# ex. Symlinks pointing to symlinks pointing to dead files # 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): if sys_patch_helpers.SysPatchHelpers(self.constants).generate_patchset_plist(patchset, file_name, self.kdk_path):
logging.info("- Writing patchset information to Root Volume") logging.info("- Writing patchset information to Root Volume")
if Path(destination_path_file).exists(): if Path(destination_path_file).exists():
utilities.process_status(utilities.elevated(["/bin/rm", destination_path_file], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) subprocess_wrapper.run_as_root_and_verify(["/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/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: 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 # Instead, call elevated funtion if string's boolean is True
if required_patches[patch]["Processes"][process] is True: if required_patches[patch]["Processes"][process] is True:
logging.info(f"- Running Process as Root:\n{process}") 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: else:
logging.info(f"- Running Process:\n{process}") 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"]): 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() sys_patch_helpers.SysPatchHelpers(self.constants).disable_window_server_caching()
if "Metal 3802 Common Extended" in required_patches: if "Metal 3802 Common Extended" in required_patches:
@@ -800,25 +796,25 @@ class PatchSysVolume:
if file_name_str.endswith(".framework"): if file_name_str.endswith(".framework"):
# merge with rsync # merge with rsync
logging.info(f" - Installing: {file_name}") 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) self._fix_permissions(destination_folder + "/" + file_name)
elif Path(source_folder + "/" + file_name_str).is_dir(): elif Path(source_folder + "/" + file_name_str).is_dir():
# Applicable for .kext, .app, .plugin, .bundle, all of which are directories # Applicable for .kext, .app, .plugin, .bundle, all of which are directories
if Path(destination_folder + "/" + file_name).exists(): if Path(destination_folder + "/" + file_name).exists():
logging.info(f" - Found existing {file_name}, overwriting...") 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: else:
logging.info(f" - Installing: {file_name}") 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) self._fix_permissions(destination_folder + "/" + file_name)
else: else:
# Assume it's an individual file, replace as normal # Assume it's an individual file, replace as normal
if Path(destination_folder + "/" + file_name).exists(): if Path(destination_folder + "/" + file_name).exists():
logging.info(f" - Found existing {file_name}, overwriting...") 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: else:
logging.info(f" - Installing: {file_name}") 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) self._fix_permissions(destination_folder + "/" + file_name)
@@ -834,9 +830,9 @@ class PatchSysVolume:
if Path(destination_folder + "/" + file_name).exists(): if Path(destination_folder + "/" + file_name).exists():
logging.info(f" - Removing: {file_name}") logging.info(f" - Removing: {file_name}")
if Path(destination_folder + "/" + file_name).is_dir(): 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: 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: def _fix_permissions(self, destination_file: Path) -> None:
@@ -850,8 +846,8 @@ class PatchSysVolume:
# Strip recursive arguments # Strip recursive arguments
chmod_args.pop(1) chmod_args.pop(1)
chown_args.pop(1) chown_args.pop(1)
utilities.process_status(utilities.elevated(chmod_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) subprocess_wrapper.run_as_root_and_verify(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(chown_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
def _check_files(self) -> bool: def _check_files(self) -> bool:
@@ -882,8 +878,7 @@ class PatchSysVolume:
if output.returncode != 0: if output.returncode != 0:
logging.info("- Failed to mount Universal-Binaries.dmg") logging.info("- Failed to mount Universal-Binaries.dmg")
logging.info(f"Output: {output.stdout.decode()}") subprocess_wrapper.log(output)
logging.info(f"Return Code: {output.returncode}")
return False return False
logging.info("- Mounted Universal-Binaries.dmg") logging.info("- Mounted Universal-Binaries.dmg")
@@ -923,13 +918,11 @@ class PatchSysVolume:
return True return True
logging.info("- Failed to merge DortaniaInternal resources") logging.info("- Failed to merge DortaniaInternal resources")
logging.info(f"Output: {result.stdout.decode()}") subprocess_wrapper.log(result)
logging.info(f"Return Code: {result.returncode}")
return False return False
logging.info("- Failed to mount DortaniaInternal resources") logging.info("- Failed to mount DortaniaInternal resources")
logging.info(f"Output: {result.stdout.decode()}") subprocess_wrapper.log(result)
logging.info(f"Return Code: {result.returncode}")
if "Authentication error" not in result.stdout.decode(): if "Authentication error" not in result.stdout.decode():
try: try:
@@ -29,7 +29,8 @@ from ..support import (
utilities, utilities,
updates, updates,
global_settings, 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") logging.info(f" - {name} checksums match, skipping")
continue continue
logging.info(f" - Existing service found, removing") 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 # Create parent directories
if not Path(services[service]).parent.exists(): if not Path(services[service]).parent.exists():
logging.info(f" - Creating {Path(services[service]).parent} directory") 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)) subprocess_wrapper.run_as_root_and_verify(["/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/cp", service, services[service]], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
# Set the permissions on the service # Set the permissions on the service
utilities.process_status(utilities.elevated(["/bin/chmod", "644", 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)
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(["/usr/sbin/chown", "root:wheel", services[service]], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
if self.constants.launcher_binary.startswith("/Library/Application Support/Dortania/"): if self.constants.launcher_binary.startswith("/Library/Application Support/Dortania/"):
logging.info("- Skipping Patcher Install, already installed") 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(): if not Path("Library/Application Support/Dortania").exists():
logging.info("- Creating /Library/Application Support/Dortania/") 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/") logging.info("- Copying OpenCore Patcher to /Library/Application Support/Dortania/")
if Path("/Library/Application Support/Dortania/OpenCore-Patcher.app").exists(): if Path("/Library/Application Support/Dortania/OpenCore-Patcher.app").exists():
logging.info("- Deleting existing OpenCore-Patcher") 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 # Strip everything after OpenCore-Patcher.app
path = str(self.constants.launcher_binary).split("/Contents/MacOS/OpenCore-Patcher")[0] path = str(self.constants.launcher_binary).split("/Contents/MacOS/OpenCore-Patcher")[0]
logging.info(f"- Copying {path} to /Library/Application Support/Dortania/") 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(): 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) # 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 # We'll want to rename it to OpenCore-Patcher.app
path = path.split("/")[-1] path = path.split("/")[-1]
logging.info(f"- Renaming {path} to OpenCore-Patcher.app") 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) 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 there's already an alias or exiting app, skip
if not Path("/Applications/OpenCore-Patcher.app").exists(): if not Path("/Applications/OpenCore-Patcher.app").exists():
logging.info("- Making app alias") 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: def _create_rsr_monitor_daemon(self) -> bool:
@@ -18,7 +18,8 @@ from ..datasets import os_data
from ..support import ( from ..support import (
bplist, bplist,
generate_smbios, generate_smbios,
utilities utilities,
subprocess_wrapper
) )
@@ -138,9 +139,9 @@ class SysPatchHelpers:
logging.info("Disabling WindowServer Caching") logging.info("Disabling WindowServer Caching")
# Invoke via 'bash -c' to resolve pathing # 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 # 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: # Reference:
# To reverse write lock: # To reverse write lock:
# 'chflags nouchg /private/var/folders/*/*/*/WindowServer' # 'chflags nouchg /private/var/folders/*/*/*/WindowServer'
@@ -219,9 +220,10 @@ class SysPatchHelpers:
return return
logging.info("Installing Kernel Collection syncing utility") 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: 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]): def patch_gpu_compiler_libraries(self, mount_point: Union[str, Path]):
@@ -282,6 +284,6 @@ class SysPatchHelpers:
src_dir = f"{LIBRARY_DIR}/{file.name}" src_dir = f"{LIBRARY_DIR}/{file.name}"
if not Path(f"{DEST_DIR}/lib").exists(): 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 break
@@ -26,6 +26,7 @@ from ..support import (
utilities, utilities,
network_handler, network_handler,
kdk_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) result = subprocess.run(["/usr/bin/hdiutil", "attach", kdk_dmg_path, "-mountpoint", mount_point, "-nobrowse"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
if result.returncode != 0: if result.returncode != 0:
logging.info("Failed to mount KDK") logging.info("Failed to mount KDK")
logging.info(result.stdout.decode("utf-8")) subprocess_wrapper.log(result)
return return
logging.info("Copying KDK") 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) result = subprocess.run(["/usr/bin/hdiutil", "detach", mount_point], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
if result.returncode != 0: if result.returncode != 0:
logging.info("Failed to unmount KDK") logging.info("Failed to unmount KDK")
logging.info(result.stdout.decode("utf-8")) subprocess_wrapper.log(result)
return return
logging.info("Removing KDK Disk Image") logging.info("Removing KDK Disk Image")
@@ -24,7 +24,8 @@ from ..support import (
global_settings, global_settings,
defaults, defaults,
generate_smbios, generate_smbios,
network_handler network_handler,
subprocess_wrapper
) )
from ..datasets import ( from ..datasets import (
model_array, model_array,
@@ -1324,7 +1325,7 @@ Hardware Information:
raise Exception("Test Exception") raise Exception("Test Exception")
def on_mount_root_vol(self, event: wx.Event) -> None: 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() wx.MessageDialog(self.parent, "Please relaunch as Root to mount the Root Volume", "Error", wx.OK | wx.ICON_ERROR).ShowModal()
else: else:
#Don't need to pass model as we're bypassing all logic #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() 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: 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() wx.MessageDialog(self.parent, "Please relaunch as Root to save changes", "Error", wx.OK | wx.ICON_ERROR).ShowModal()
else: else:
#Don't need to pass model as we're bypassing all logic #Don't need to pass model as we're bypassing all logic
@@ -15,10 +15,12 @@ import packaging.version
from pathlib import Path from pathlib import Path
from . import gui_about
from .. import constants from .. import constants
from ..wx_gui import gui_about
from ..detections import device_probe from ..detections import device_probe
from ..support import subprocess_wrapper
from ..datasets import ( from ..datasets import (
model_array, 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: 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) 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) relaunchItem.Enable(False)
@@ -13,6 +13,7 @@ from pathlib import Path
from .. import constants from .. import constants
from ..sys_patch import sys_patch_detect from ..sys_patch import sys_patch_detect
from ..support import subprocess_wrapper
from ..wx_gui import ( from ..wx_gui import (
gui_main_menu, gui_main_menu,
@@ -242,7 +243,7 @@ class SysPatchDisplayFrame(wx.Frame):
revert_button.Disable() revert_button.Disable()
# Relaunch as root if not root # 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) 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) revert_button.Bind(wx.EVT_BUTTON, gui_support.RelaunchApplicationAsRoot(frame, self.constants).relaunch)
+6 -3
View File
@@ -20,7 +20,8 @@ from ..wx_gui import (
) )
from ..support import ( from ..support import (
network_handler, 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 ["/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: 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_animation.stop_pulse)
wx.CallAfter(self.progress_bar.SetValue, 0) 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) 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") logging.info("User cancelled update")
wx.CallAfter(wx.MessageBox, "User cancelled update", "Update Cancelled", wx.OK | wx.ICON_INFORMATION) wx.CallAfter(wx.MessageBox, "User cancelled update", "Update Cancelled", wx.OK | wx.ICON_INFORMATION)
else: 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(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) wx.CallAfter(sys.exit, 1)