Upgrade OC 0.8.6 (*.efi, *.kext, *.plist)
This commit is contained in:
@@ -1,5 +1,18 @@
|
|||||||
OpenCore Changelog
|
OpenCore Changelog
|
||||||
==================
|
==================
|
||||||
|
#### v0.8.6
|
||||||
|
- Updated NVRAM save script for compatibilty with earlier macOS (Snow Leopard+ tested)
|
||||||
|
- Updated NVRAM save script to automatically install as launch daemon (Yosemite+) or logout hook (older macOS)
|
||||||
|
- Fixed maximum click duration and double click speed for non-standard poll frequencies
|
||||||
|
- Added support for pointer dwell-clicking
|
||||||
|
- Fixed recursive loop crash at first non-early log line on some systems
|
||||||
|
- Fixed early log preservation when using unsafe fast file logging
|
||||||
|
- Updated builtin firmware versions for SMBIOS and the rest
|
||||||
|
- Resolved wake-from-sleep failure on EFI 1.1 systems (including earlier Macs) with standalone emulated NVRAM driver
|
||||||
|
- Updated macrecovery commands with macOS 12 and 13, thx @Core-i99
|
||||||
|
- Updates SSDT-BRG0 with macOS-specific STA to avoid compatibility issues on Windows, thx @Lorys89
|
||||||
|
- Fixed memory issues in OpenLinuxBoot causing crashes on 32-bit UEFI firmware
|
||||||
|
|
||||||
#### v0.8.5
|
#### v0.8.5
|
||||||
- Updated builtin firmware versions for SMBIOS and the rest
|
- Updated builtin firmware versions for SMBIOS and the rest
|
||||||
- Moved CPU objects that exist only in Windows Server 2022 into `SSDT-HV-DEV-WS2022.dsl`
|
- Moved CPU objects that exist only in Windows Server 2022 into `SSDT-HV-DEV-WS2022.dsl`
|
||||||
|
|||||||
Binary file not shown.
@@ -1379,6 +1379,12 @@
|
|||||||
<integer>50</integer>
|
<integer>50</integer>
|
||||||
<key>KeySubsequentDelay</key>
|
<key>KeySubsequentDelay</key>
|
||||||
<integer>5</integer>
|
<integer>5</integer>
|
||||||
|
<key>PointerDwellClickTimeout</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PointerDwellDoubleClickTimeout</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PointerDwellRadius</key>
|
||||||
|
<integer>0</integer>
|
||||||
<key>PointerPollMask</key>
|
<key>PointerPollMask</key>
|
||||||
<integer>-1</integer>
|
<integer>-1</integer>
|
||||||
<key>PointerPollMax</key>
|
<key>PointerPollMax</key>
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -2,7 +2,7 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key># BASE EFI INTEL 11th ALDER LAKE (doest not support iGPU) - DEBUG Version</key>
|
<key># BASE EFI INTEL 12h ALDER LAKE (doest not support iGPU) - DEBUG Version</key>
|
||||||
<string></string>
|
<string></string>
|
||||||
<key># DISCORD: https://discord.universohackintosh.com</key>
|
<key># DISCORD: https://discord.universohackintosh.com</key>
|
||||||
<string></string>
|
<string></string>
|
||||||
@@ -555,6 +555,12 @@
|
|||||||
<integer>50</integer>
|
<integer>50</integer>
|
||||||
<key>KeySubsequentDelay</key>
|
<key>KeySubsequentDelay</key>
|
||||||
<integer>5</integer>
|
<integer>5</integer>
|
||||||
|
<key>PointerDwellClickTimeout</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PointerDwellDoubleClickTimeout</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PointerDwellRadius</key>
|
||||||
|
<integer>0</integer>
|
||||||
<key>PointerPollMask</key>
|
<key>PointerPollMask</key>
|
||||||
<integer>-1</integer>
|
<integer>-1</integer>
|
||||||
<key>PointerPollMax</key>
|
<key>PointerPollMax</key>
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -555,6 +555,12 @@
|
|||||||
<integer>50</integer>
|
<integer>50</integer>
|
||||||
<key>KeySubsequentDelay</key>
|
<key>KeySubsequentDelay</key>
|
||||||
<integer>5</integer>
|
<integer>5</integer>
|
||||||
|
<key>PointerDwellClickTimeout</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PointerDwellDoubleClickTimeout</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>PointerDwellRadius</key>
|
||||||
|
<integer>0</integer>
|
||||||
<key>PointerPollMask</key>
|
<key>PointerPollMask</key>
|
||||||
<integer>-1</integer>
|
<integer>-1</integer>
|
||||||
<key>PointerPollMax</key>
|
<key>PointerPollMax</key>
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ Note|Description
|
|||||||
:----|:----
|
:----|:----
|
||||||
Initial macOS Support|macOS 10.15, Catalina.
|
Initial macOS Support|macOS 10.15, Catalina.
|
||||||
|
|
||||||
- Opencore version: 0.8.5
|
- Opencore version: 0.8.6
|
||||||
- Release date: 04/10/2022 (late 1 day)
|
- Release date: 07/11/2022
|
||||||
|
|
||||||
# Basic Steps
|
# Basic Steps
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +1,31 @@
|
|||||||
{
|
{
|
||||||
"Mac-EE2EBD4B90B839A8": "latest",
|
"Mac-EE2EBD4B90B839A8": "latest",
|
||||||
"Mac-BE0E8AC46FE800CC": "11.7",
|
"Mac-BE0E8AC46FE800CC": "11.7.1",
|
||||||
"Mac-9AE82516C7C6B903": "12.6",
|
"Mac-9AE82516C7C6B903": "12.6.1",
|
||||||
"Mac-942452F5819B1C1B": "10.13.6",
|
"Mac-942452F5819B1C1B": "10.13.6",
|
||||||
"Mac-942C5DF58193131B": "10.13.6",
|
"Mac-942C5DF58193131B": "10.13.6",
|
||||||
"Mac-C08A6BB70A942AC2": "10.13.6",
|
"Mac-C08A6BB70A942AC2": "10.13.6",
|
||||||
"Mac-742912EFDBEE19B3": "10.13.6",
|
"Mac-742912EFDBEE19B3": "10.13.6",
|
||||||
"Mac-66F35F19FE2A0D05": "10.15.7",
|
"Mac-66F35F19FE2A0D05": "10.15.7",
|
||||||
"Mac-2E6FAB96566FE58C": "10.15.7",
|
"Mac-2E6FAB96566FE58C": "10.15.7",
|
||||||
"Mac-35C1E88140C3E6CF": "11.7",
|
"Mac-35C1E88140C3E6CF": "11.7.1",
|
||||||
"Mac-7DF21CB3ED6977E5": "11.7",
|
"Mac-7DF21CB3ED6977E5": "11.7.1",
|
||||||
"Mac-9F18E312C5C2BF0B": "12.6",
|
"Mac-9F18E312C5C2BF0B": "12.6.1",
|
||||||
"Mac-937CB26E2E02BB01": "12.6",
|
"Mac-937CB26E2E02BB01": "12.6.1",
|
||||||
"Mac-827FAC58A8FDFA22": "latest",
|
"Mac-827FAC58A8FDFA22": "latest",
|
||||||
"Mac-226CB3C6A851A671": "latest",
|
"Mac-226CB3C6A851A671": "latest",
|
||||||
"Mac-0CFF9C7C2B63DF8D": "latest",
|
"Mac-0CFF9C7C2B63DF8D": "latest",
|
||||||
"Mac-C3EC7CD22292981F": "10.15.7",
|
"Mac-C3EC7CD22292981F": "10.15.7",
|
||||||
"Mac-AFD8A9D944EA4843": "10.15.7",
|
"Mac-AFD8A9D944EA4843": "10.15.7",
|
||||||
"Mac-189A3D4F975D5FFC": "11.7",
|
"Mac-189A3D4F975D5FFC": "11.7.1",
|
||||||
"Mac-3CBD00234E554E41": "11.7",
|
"Mac-3CBD00234E554E41": "11.7.1",
|
||||||
"Mac-2BD1B31983FE1663": "11.7",
|
"Mac-2BD1B31983FE1663": "11.7.1",
|
||||||
"Mac-06F11FD93F0323C5": "12.6",
|
"Mac-06F11FD93F0323C5": "12.6.1",
|
||||||
"Mac-06F11F11946D27C5": "12.6",
|
"Mac-06F11F11946D27C5": "12.6.1",
|
||||||
"Mac-E43C1C25D4880AD6": "12.6",
|
"Mac-E43C1C25D4880AD6": "12.6.1",
|
||||||
"Mac-473D31EABEB93F9B": "12.6",
|
"Mac-473D31EABEB93F9B": "12.6.1",
|
||||||
"Mac-66E35819EE2D0D05": "12.6",
|
"Mac-66E35819EE2D0D05": "12.6.1",
|
||||||
"Mac-A5C67F76ED83108C": "12.6",
|
"Mac-A5C67F76ED83108C": "12.6.1",
|
||||||
"Mac-B4831CEBD52A0C4C": "latest",
|
"Mac-B4831CEBD52A0C4C": "latest",
|
||||||
"Mac-CAD6701F7CEA0921": "latest",
|
"Mac-CAD6701F7CEA0921": "latest",
|
||||||
"Mac-551B86E5744E2388": "latest",
|
"Mac-551B86E5744E2388": "latest",
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
"Mac-942459F5819B171B": "10.13.6",
|
"Mac-942459F5819B171B": "10.13.6",
|
||||||
"Mac-4B7AC7E43945597E": "10.15.7",
|
"Mac-4B7AC7E43945597E": "10.15.7",
|
||||||
"Mac-6F01561E16C75D06": "10.15.7",
|
"Mac-6F01561E16C75D06": "10.15.7",
|
||||||
"Mac-F60DEB81FF30ACF6": "12.6",
|
"Mac-F60DEB81FF30ACF6": "12.6.1",
|
||||||
"Mac-27AD2F918AE68F61": "latest",
|
"Mac-27AD2F918AE68F61": "latest",
|
||||||
"Mac-F2208EC8": "10.13.6",
|
"Mac-F2208EC8": "10.13.6",
|
||||||
"Mac-8ED6AF5B48C039E1": "10.13.6",
|
"Mac-8ED6AF5B48C039E1": "10.13.6",
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
"Mac-7BA5B2794B2CDB12": "10.13.6",
|
"Mac-7BA5B2794B2CDB12": "10.13.6",
|
||||||
"Mac-031AEE4D24BFF0B1": "10.15.7",
|
"Mac-031AEE4D24BFF0B1": "10.15.7",
|
||||||
"Mac-F65AE981FFA204ED": "10.15.7",
|
"Mac-F65AE981FFA204ED": "10.15.7",
|
||||||
"Mac-35C5E08120C7EEAF": "12.6",
|
"Mac-35C5E08120C7EEAF": "12.6.1",
|
||||||
"Mac-7BA5B2DFE22DDD8C": "latest",
|
"Mac-7BA5B2DFE22DDD8C": "latest",
|
||||||
"Mac-942B5BF58194151B": "10.13.6",
|
"Mac-942B5BF58194151B": "10.13.6",
|
||||||
"Mac-942B59F58194171B": "10.13.6",
|
"Mac-942B59F58194171B": "10.13.6",
|
||||||
@@ -61,13 +61,13 @@
|
|||||||
"Mac-031B6874CF7F642A": "10.15.7",
|
"Mac-031B6874CF7F642A": "10.15.7",
|
||||||
"Mac-27ADBB7B4CEE8E61": "10.15.7",
|
"Mac-27ADBB7B4CEE8E61": "10.15.7",
|
||||||
"Mac-77EB7D7DAF985301": "10.15.7",
|
"Mac-77EB7D7DAF985301": "10.15.7",
|
||||||
"Mac-81E3E92DD6088272": "11.7",
|
"Mac-81E3E92DD6088272": "11.7.1",
|
||||||
"Mac-42FD25EABCABB274": "11.7",
|
"Mac-42FD25EABCABB274": "11.7.1",
|
||||||
"Mac-A369DDC4E67F1C45": "12.6",
|
"Mac-A369DDC4E67F1C45": "12.6.1",
|
||||||
"Mac-FFE5EF870D7BA81A": "12.6",
|
"Mac-FFE5EF870D7BA81A": "12.6.1",
|
||||||
"Mac-DB15BD556843C820": "12.6",
|
"Mac-DB15BD556843C820": "12.6.1",
|
||||||
"Mac-65CE76090165799A": "12.6",
|
"Mac-65CE76090165799A": "12.6.1",
|
||||||
"Mac-B809C3757DA9BB8D": "12.6",
|
"Mac-B809C3757DA9BB8D": "12.6.1",
|
||||||
"Mac-4B682C642B45593E": "latest",
|
"Mac-4B682C642B45593E": "latest",
|
||||||
"Mac-77F17D7DA9285301": "latest",
|
"Mac-77F17D7DA9285301": "latest",
|
||||||
"Mac-BE088AF8C5EB4FA2": "latest",
|
"Mac-BE088AF8C5EB4FA2": "latest",
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ python macrecovery.py -b Mac-00BE6ED71E35EB86 -m 00000000000000000 download
|
|||||||
# Big Sur (11)
|
# Big Sur (11)
|
||||||
python macrecovery.py -b Mac-42FD25EABCABB274 -m 00000000000000000 download
|
python macrecovery.py -b Mac-42FD25EABCABB274 -m 00000000000000000 download
|
||||||
|
|
||||||
|
# Monterey (12)
|
||||||
|
python macrecovery.py -b Mac-E43C1C25D4880AD6 -m 00000000000000000 download
|
||||||
|
|
||||||
# Latest version
|
# Latest version
|
||||||
# ie. Monterey (12)
|
# ie. Ventura (13)
|
||||||
python ./macrecovery.py -b Mac-E43C1C25D4880AD6 -m 00000000000000000 download
|
python ./macrecovery.py -b Mac-B4831CEBD52A0C4C -m 00000000000000000 download
|
||||||
|
|||||||
+411
@@ -0,0 +1,411 @@
|
|||||||
|
import os, sys
|
||||||
|
sys.path.append(os.path.abspath(os.path.dirname(os.path.realpath(__file__))))
|
||||||
|
import run, plist
|
||||||
|
|
||||||
|
class Disk:
|
||||||
|
def __init__(self):
|
||||||
|
self.r = run.Run()
|
||||||
|
self.diskdump = self.check_diskdump()
|
||||||
|
self.full_os_version = self.r.run({"args":["sw_vers", "-productVersion"]})[0]
|
||||||
|
if len(self.full_os_version.split(".")) < 3:
|
||||||
|
# Ensure the format is XX.YY.ZZ
|
||||||
|
self.full_os_version += ".0"
|
||||||
|
self.os_version = ".".join(self.full_os_version.split(".")[:2])
|
||||||
|
self.sudo_mount_version = "10.13.6"
|
||||||
|
self.efi_guids = ["C12A7328-F81F-11D2-BA4B-00A0C93EC93B"]
|
||||||
|
self.disks = self.get_disks()
|
||||||
|
|
||||||
|
def check_diskdump(self):
|
||||||
|
ddpath = os.path.join(os.path.dirname(os.path.realpath(__file__)),"diskdump")
|
||||||
|
if not os.path.exists(ddpath):
|
||||||
|
raise FileNotFoundError("Could not locate diskdump")
|
||||||
|
if "com.apple.quarantine" in self.r.run({"args":["xattr",ddpath]})[0]:
|
||||||
|
self.r.run({"args":["xattr","-d","com.apple.quarantine",ddpath]})
|
||||||
|
return ddpath
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
# Refresh our disk list
|
||||||
|
self.disks = self.get_disks()
|
||||||
|
return self.disks
|
||||||
|
|
||||||
|
def get_disks(self):
|
||||||
|
# Check for our binary - and ensure it's setup to run
|
||||||
|
ddpath = os.path.join(os.path.dirname(os.path.realpath(__file__)),"diskdump")
|
||||||
|
if not os.path.exists(ddpath): return {}
|
||||||
|
# Get our "diskutil list" and diskdump info. Run diskutil list first
|
||||||
|
# as it takes longer - but will stall while waiting for disks to appear,
|
||||||
|
# meaning our diskdump output will be better reflected.
|
||||||
|
diskutil_list = self.r.run({"args":["diskutil","list"]})[0]
|
||||||
|
diskstring = self.r.run({"args":[ddpath]})[0]
|
||||||
|
if not diskstring: return {}
|
||||||
|
diskdump = plist.loads(diskstring)
|
||||||
|
last_disk = None
|
||||||
|
for line in diskutil_list.split("\n"):
|
||||||
|
if line.startswith("/dev/disk"):
|
||||||
|
last_disk = line.split()[0].split("/")[-1]
|
||||||
|
elif not last_disk:
|
||||||
|
continue
|
||||||
|
elif line.strip().startswith("Logical Volume on"):
|
||||||
|
# Core Storage
|
||||||
|
ps = line.split("Logical Volume on")[1].strip().split(", ")
|
||||||
|
disk = self.get_disk(last_disk,disk_dict=diskdump)
|
||||||
|
if disk: # Update parent disk
|
||||||
|
disk["container"] = True
|
||||||
|
disk["core_storage"] = True
|
||||||
|
disk["physical_stores"] = ps
|
||||||
|
# Save a reference to the physical stores
|
||||||
|
for s in ps:
|
||||||
|
store = self.get_disk(s,disk_dict=diskdump)
|
||||||
|
if store:
|
||||||
|
store["container_for"] = last_disk
|
||||||
|
store["core_storage_container_for"] = last_disk
|
||||||
|
elif line.strip().startswith("Physical Store"):
|
||||||
|
# APFS
|
||||||
|
ps = line.split("Physical Store")[1].strip().split(", ")
|
||||||
|
disk = self.get_disk(last_disk,disk_dict=diskdump)
|
||||||
|
if disk: # Update parent disk
|
||||||
|
disk["container"] = True
|
||||||
|
disk["apfs"] = True
|
||||||
|
disk["physical_stores"] = ps
|
||||||
|
# Save a reference to the physical stores
|
||||||
|
for s in ps:
|
||||||
|
store = self.get_disk(s,disk_dict=diskdump)
|
||||||
|
if store:
|
||||||
|
store["container_for"] = last_disk
|
||||||
|
store["apfs_container_for"] = last_disk
|
||||||
|
return diskdump
|
||||||
|
|
||||||
|
def get_identifier(self, disk = None, disk_dict = None):
|
||||||
|
# Should be able to take a mount point, disk name, or disk identifier,
|
||||||
|
# and return the disk's identifier
|
||||||
|
if isinstance(disk,dict): disk = disk.get("DAMediaBSDName")
|
||||||
|
if not disk: return
|
||||||
|
disk_dict = disk_dict or self.disks # Normalize the dict
|
||||||
|
disk = disk[6:] if disk.lower().startswith("/dev/rdisk") else disk[5:] if disk.lower().startswith("/dev/disk") else disk
|
||||||
|
if disk.lower() in disk_dict.get("AllDisks",[]): return disk
|
||||||
|
for d in disk_dict.get("AllDisksAndPartitions", []):
|
||||||
|
# Check the parent disk
|
||||||
|
if any((disk.lower()==d.get(x,"").lower() for x in ("DAMediaBSDName","DAVolumeName","DAVolumeUUID","DAMediaUUID","DAVolumePath"))):
|
||||||
|
return d.get("DAMediaBSDName")
|
||||||
|
# Check the partitions
|
||||||
|
for p in d.get("Partitions", []):
|
||||||
|
if any((disk.lower()==p.get(x,"").lower() for x in ("DAMediaBSDName","DAVolumeName","DAVolumeUUID","DAMediaUUID","DAVolumePath"))):
|
||||||
|
return p.get("DAMediaBSDName")
|
||||||
|
# At this point, we didn't find it
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_parent(self, disk = None, disk_dict = None):
|
||||||
|
# For backward compatibility with the old disk.py approach
|
||||||
|
return self.get_physical_parent_identifiers(disk,disk_dict=disk_dict)
|
||||||
|
|
||||||
|
def get_parent_identifier(self, disk = None, disk_dict = None):
|
||||||
|
# Resolves the passed disk value and returns the parent disk/container.
|
||||||
|
# i.e. Passing disk5s2s1 would return disk5
|
||||||
|
disk = self.get_identifier(disk,disk_dict=disk_dict)
|
||||||
|
if not disk: return
|
||||||
|
return "disk"+disk.lower().split("disk")[1].split("s")[0]
|
||||||
|
|
||||||
|
def get_physical_parent_identifier(self, disk = None, disk_dict = None):
|
||||||
|
# Returns the first hit from get_physical_parent_identifiers()
|
||||||
|
return next(iter(self.get_physical_parent_identifiers(disk, disk_dict=disk_dict) or []), None)
|
||||||
|
|
||||||
|
def get_physical_parent_identifiers(self, disk = None, disk_dict = None):
|
||||||
|
# Resolves the passed disk to the physical parent disk identifiers. Useful for APFS
|
||||||
|
# and Core Storage volumes which are logical - and can span multiple disks.
|
||||||
|
# If you have an APFS container on disk4 and its Physical Store lists
|
||||||
|
# disk2s2, disk3s2 - this would return [disk2, disk3]
|
||||||
|
return [self.get_identifier(x,disk_dict=disk_dict) for x in self.get_physical_parent_disks(disk,disk_dict=disk_dict)]
|
||||||
|
|
||||||
|
def get_physical_parent_disks(self, disk = None, disk_dict = None):
|
||||||
|
# Resolves the passed disk to the physical parent disk dicts. Useful for APFS
|
||||||
|
# and Core Storage volumes which are logcial and can span multiple physical
|
||||||
|
# disks. If you have an APFS container on disk4 and its Physical Store is on
|
||||||
|
# disk2s2 and disk3s2 - this would return the disk dicts for disk2 and disk3.
|
||||||
|
parent = self.get_parent_disk(disk, disk_dict=disk_dict)
|
||||||
|
if not parent: return []
|
||||||
|
if not "physical_stores" in parent: return [parent]
|
||||||
|
return [self.get_parent_disk(x,disk_dict=disk_dict) for x in parent.get("physical_stores",[])]
|
||||||
|
|
||||||
|
def get_parent_disk(self, disk = None, disk_dict = None):
|
||||||
|
# Returns the dict info for the parent of the passed mount point, name, identifier, etc
|
||||||
|
return self.get_disk(self.get_parent_identifier(disk,disk_dict=disk_dict),disk_dict=disk_dict)
|
||||||
|
|
||||||
|
def get_disk(self, disk = None, disk_dict = None):
|
||||||
|
# Returns the dict info for the passed mount point, name, identifier, etc
|
||||||
|
disk = self.get_identifier(disk,disk_dict=disk_dict)
|
||||||
|
if not disk: return
|
||||||
|
parent = self.get_parent_identifier(disk,disk_dict=disk_dict)
|
||||||
|
# Walk AllDisksAndPartitions, and return the first hit
|
||||||
|
for d in (disk_dict or self.disks).get("AllDisksAndPartitions",[]):
|
||||||
|
d_ident = d.get("DAMediaBSDName")
|
||||||
|
if d_ident == disk:
|
||||||
|
return d # Got the disk
|
||||||
|
elif d_ident == parent:
|
||||||
|
# Got the parent - iterate the partitions
|
||||||
|
return next((p for p in d.get("Partitions",[]) if p.get("DAMediaBSDName")==disk),None)
|
||||||
|
return None # Didn't find it
|
||||||
|
|
||||||
|
def get_efis(self, disk = None, disk_dict = None):
|
||||||
|
# Returns the identifiers for any EFI partitions attached to the
|
||||||
|
# parent disk(s) of the passed disk
|
||||||
|
efis = []
|
||||||
|
for parent in self.get_physical_parent_identifiers(disk,disk_dict=disk_dict):
|
||||||
|
parent_dict = self.get_disk(parent,disk_dict=disk_dict)
|
||||||
|
if not parent_dict: continue
|
||||||
|
for part in parent_dict.get("Partitions",[]):
|
||||||
|
# Use the GUID instead of media name - as that can vary
|
||||||
|
if part.get("DAMediaContent","").upper() in self.efi_guids:
|
||||||
|
efis.append(part["DAMediaBSDName"])
|
||||||
|
# Normalize case for the DAMediaName;
|
||||||
|
# macOS disks: "EFI System Partition", Windows disks: "EFI system partition"
|
||||||
|
# Maybe use this approach as a fallback at some point - but for now, just use the GUID
|
||||||
|
# if part.get("DAMediaName").lower() == "efi system partition":
|
||||||
|
# efis.append(part["DAMediaBSDName"])
|
||||||
|
return efis
|
||||||
|
|
||||||
|
def get_efi(self, disk = None, disk_dict = None):
|
||||||
|
# Returns the identifier for the first EFI partition found for
|
||||||
|
# the passed disk
|
||||||
|
return next(iter(self.get_efis(disk,disk_dict=disk_dict) or []), None)
|
||||||
|
|
||||||
|
def get_mounted_volumes(self, disk_dict = None):
|
||||||
|
# Returns a list of mounted volumes
|
||||||
|
return (disk_dict or self.disks).get("MountPointsFromDisks",[])
|
||||||
|
|
||||||
|
def get_mounted_volume_dicts(self, disk_dict = None):
|
||||||
|
# Returns a list of dicts of name, identifier, mount point dicts
|
||||||
|
vol_list = []
|
||||||
|
for v in (disk_dict or self.disks).get("MountPointsFromDisks"):
|
||||||
|
i = self.get_disk(v,disk_dict=disk_dict)
|
||||||
|
if not i: continue # Skip - as it didn't resolve
|
||||||
|
mount_point = self.get_mount_point(i,disk_dict=disk_dict)
|
||||||
|
# Check if we're either not mounted - or not mounted in /Volumes/
|
||||||
|
if not v or not (v == "/" or v.lower().startswith("/volumes/")):
|
||||||
|
continue
|
||||||
|
vol = {
|
||||||
|
"name": self.get_volume_name(i,disk_dict=disk_dict),
|
||||||
|
"identifier": self.get_identifier(i,disk_dict=disk_dict),
|
||||||
|
"mount_point": v,
|
||||||
|
"disk_uuid": self.get_disk_uuid(i,disk_dict=disk_dict),
|
||||||
|
"volume_uuid": self.get_volume_uuid(i,disk_dict=disk_dict)
|
||||||
|
}
|
||||||
|
if "container_for" in i: vol["container_for"] = i["container_for"]
|
||||||
|
vol_list.append(vol)
|
||||||
|
return sorted(vol_list,key=lambda x:x["identifier"])
|
||||||
|
|
||||||
|
def get_disks_and_partitions_dict(self, disk_dict = None):
|
||||||
|
# Returns a list of dictionaries like so:
|
||||||
|
# { "disk0" : {
|
||||||
|
# "container": true/false,
|
||||||
|
# "physical_stores": [
|
||||||
|
# "diskAsB",
|
||||||
|
# "diskXsY"
|
||||||
|
# ],
|
||||||
|
# "scheme": "Guid_partition_scheme",
|
||||||
|
# "partitions" : [
|
||||||
|
# {
|
||||||
|
# "identifier" : "disk0s1",
|
||||||
|
# "name" : "EFI",
|
||||||
|
# "mount_point" : "/Volumes/EFI",
|
||||||
|
# "container_for": "diskCsD"
|
||||||
|
# }
|
||||||
|
# ] } }
|
||||||
|
disks = {}
|
||||||
|
for d in sorted((disk_dict or self.disks).get("AllDisksAndPartitions"),key=lambda x:x.get("DAMediaBSDName")):
|
||||||
|
if not "DAMediaBSDName" in d: continue # Malformed
|
||||||
|
parent = d["DAMediaBSDName"]
|
||||||
|
disks[parent] = {"partitions":[]}
|
||||||
|
# Save if the disk is logical - and a l)ist of its physical stores
|
||||||
|
for x in ("container","physical_stores"):
|
||||||
|
if x in d: disks[parent][x] = d[x]
|
||||||
|
disks[parent]["scheme"] = self.get_readable_partition_scheme(d,disk_dict=disk_dict)
|
||||||
|
# Check if this disk is also a volume - i.e. also a leaf, and insert it in the partitions list
|
||||||
|
partitions = d.get("Partitions",[])
|
||||||
|
if d.get("DAMediaLeaf"):
|
||||||
|
partitions.insert(0,d)
|
||||||
|
for p in d.get("Partitions",[]):
|
||||||
|
part = {
|
||||||
|
"name": self.get_volume_name(p,disk_dict=disk_dict),
|
||||||
|
"identifier": self.get_identifier(p,disk_dict=disk_dict),
|
||||||
|
"mount_point": self.get_mount_point(p,disk_dict=disk_dict),
|
||||||
|
"disk_uuid": self.get_disk_uuid(p,disk_dict=disk_dict),
|
||||||
|
"volume_uuid": self.get_volume_uuid(p,disk_dict=disk_dict)
|
||||||
|
}
|
||||||
|
if "container_for" in p: part["container_for"] = p["container_for"]
|
||||||
|
disks[parent]["partitions"].append(part)
|
||||||
|
disks[parent]["partitions"].sort(key=lambda x:x["identifier"])
|
||||||
|
return disks
|
||||||
|
|
||||||
|
def _get_value(self, disk = None, value = None, disk_dict = None):
|
||||||
|
if not disk or not value: return # Missing info
|
||||||
|
if isinstance(disk,dict): return disk.get(value)
|
||||||
|
try: return self.get_disk(disk,disk_dict=disk_dict).get(value)
|
||||||
|
except: return
|
||||||
|
|
||||||
|
def _is_uuid(self, value):
|
||||||
|
# Helper to return whether a passed value is a UUID
|
||||||
|
# 7C3CFDDF-920A-4924-AED6-7CD4AF6E4512
|
||||||
|
if not isinstance(value,str): return False # Wrong type
|
||||||
|
value = value.lower()
|
||||||
|
# Check that all chars are hex or the separator
|
||||||
|
if not all((x in "-0123456789abcdef" for x in value)): return False
|
||||||
|
len_list = (8,4,4,4,12)
|
||||||
|
chunks = value.split("-")
|
||||||
|
# Make sure we have the right number of chunks - and
|
||||||
|
# each chunk is the right length.
|
||||||
|
if not len(chunks)==len(len_list): return False
|
||||||
|
for i,chunk in enumerate(chunks):
|
||||||
|
if not len(chunk)==len_list[i]: return False
|
||||||
|
# Passed all the checks
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_partition_scheme(self, disk, allow_logical = True, disk_dict = None):
|
||||||
|
# let's resolve the disk to its physical parents
|
||||||
|
comm = self.get_parent_disk if allow_logical else self.get_physical_parent_disks
|
||||||
|
p = comm(disk,disk_dict=disk_dict)
|
||||||
|
if p:
|
||||||
|
if isinstance(p,(list,tuple)): p = p[0] # Extract the first parent if need be
|
||||||
|
if p.get("apfs"): return "APFS_container_scheme"
|
||||||
|
elif p.get("core_storage"): return "Core_Storage_container_scheme"
|
||||||
|
content = self.get_content(p,disk_dict=disk_dict)
|
||||||
|
if content.lower().endswith("scheme"):
|
||||||
|
return content
|
||||||
|
|
||||||
|
def get_readable_partition_scheme(self, disk, allow_logical = True, disk_dict = None):
|
||||||
|
s = self.get_partition_scheme(disk,disk_dict=disk_dict)
|
||||||
|
if not s: return
|
||||||
|
# We want to convert GUID_partition_scheme to GUID
|
||||||
|
# We also want to translate FDisk to MBR
|
||||||
|
joined = " ".join(["MBR" if x.lower() == "fdisk" else x.capitalize() if x!=x.upper() else x for x in s.replace("_"," ").split() if x])
|
||||||
|
return joined
|
||||||
|
|
||||||
|
def get_content(self, disk, disk_dict = None):
|
||||||
|
return self._get_value(disk,"DAMediaContent",disk_dict=disk_dict)
|
||||||
|
|
||||||
|
def get_volume_name(self, disk, disk_dict = None):
|
||||||
|
return self._get_value(disk,"DAVolumeName",disk_dict=disk_dict)
|
||||||
|
|
||||||
|
def get_volume_uuid(self, disk, disk_dict = None):
|
||||||
|
return self._get_value(disk,"DAVolumeUUID",disk_dict=disk_dict)
|
||||||
|
|
||||||
|
def get_disk_uuid(self, disk, disk_dict = None):
|
||||||
|
return self._get_value(disk,"DAMediaUUID",disk_dict=disk_dict)
|
||||||
|
|
||||||
|
def get_mount_point(self, disk, disk_dict = None):
|
||||||
|
return self._get_value(disk,"DAVolumePath",disk_dict=disk_dict)
|
||||||
|
|
||||||
|
def open_mount_point(self, disk, new_window = False, disk_dict = None):
|
||||||
|
disk = self.get_identifier(disk,disk_dict=disk_dict)
|
||||||
|
if not disk: return
|
||||||
|
mount = self.get_mount_point(disk)
|
||||||
|
if not mount: return
|
||||||
|
return self.r.run({"args":["open", mount]})[2] == 0
|
||||||
|
|
||||||
|
def compare_version(self, v1, v2):
|
||||||
|
# Splits the version numbers by periods and compare each value
|
||||||
|
# Allows 0.0.10 > 0.0.9 where normal string comparison would return false
|
||||||
|
# Also strips out any non-numeric values from each segment to avoid conflicts
|
||||||
|
#
|
||||||
|
# Returns True if v1 > v2, None if v1 == v2, and False if v1 < v2
|
||||||
|
if not all((isinstance(x,str) for x in (v1,v2))):
|
||||||
|
# Wrong types
|
||||||
|
return False
|
||||||
|
v1_seg = v1.split(".")
|
||||||
|
v2_seg = v2.split(".")
|
||||||
|
# Pad with 0s to ensure common length
|
||||||
|
v1_seg += ["0"]*(len(v2_seg)-len(v1_seg))
|
||||||
|
v2_seg += ["0"]*(len(v1_seg)-len(v2_seg))
|
||||||
|
# Compare each segment - stripping non-numbers as needed
|
||||||
|
for i in range(len(v1_seg)):
|
||||||
|
a,b = v1_seg[i],v2_seg[i]
|
||||||
|
try: a = int("".join([x for x in a if x.isdigit()]))
|
||||||
|
except: a = 0
|
||||||
|
try: b = int("".join([x for x in b if x.isdigit()]))
|
||||||
|
except: b = 0
|
||||||
|
if a > b: return True
|
||||||
|
if a < b: return False
|
||||||
|
# If we're here, both versions are the same
|
||||||
|
return None
|
||||||
|
|
||||||
|
def needs_sudo(self, disk = None, disk_dict = None):
|
||||||
|
# Default to EFI if we didn't pass a disk
|
||||||
|
if not disk: return self.compare_version(self.full_os_version,self.sudo_mount_version) in (True,None)
|
||||||
|
return self.compare_version(self.full_os_version,self.sudo_mount_version) in (True,None) and self.get_content(disk,disk_dict=disk_dict).upper() in self.efi_guids
|
||||||
|
|
||||||
|
def mount_partition(self, disk, disk_dict = None):
|
||||||
|
disk = self.get_identifier(disk,disk_dict=disk_dict)
|
||||||
|
if not disk: return
|
||||||
|
sudo = self.needs_sudo(disk,disk_dict=disk_dict)
|
||||||
|
out = self.r.run({"args":["diskutil","mount",disk],"sudo":sudo})
|
||||||
|
self.update()
|
||||||
|
return out
|
||||||
|
|
||||||
|
def unmount_partition(self, disk, disk_dict = None):
|
||||||
|
disk = self.get_identifier(disk,disk_dict=disk_dict)
|
||||||
|
if not disk: return
|
||||||
|
out = self.r.run({"args":["diskutil","unmount",disk]})
|
||||||
|
self.update()
|
||||||
|
return out
|
||||||
|
|
||||||
|
def is_mounted(self, disk, disk_dict = None):
|
||||||
|
disk = self.get_identifier(disk,disk_dict=disk_dict)
|
||||||
|
if not disk: return
|
||||||
|
m = self.get_mount_point(disk,disk_dict=disk_dict)
|
||||||
|
return (m != None and len(m))
|
||||||
|
|
||||||
|
def get_volumes(self, disk_dict = None):
|
||||||
|
# Returns a list object with all volumes from disks
|
||||||
|
return sorted((disk_dict or self.disks).get("VolumesFromDisks",[]))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
d = Disk()
|
||||||
|
# Gather the args
|
||||||
|
errors = []
|
||||||
|
args = []
|
||||||
|
for x in sys.argv[1:]:
|
||||||
|
if x == "/":
|
||||||
|
args.append(x)
|
||||||
|
continue
|
||||||
|
if x.endswith("/"):
|
||||||
|
x = x[:-1]
|
||||||
|
if not x.lower().startswith("/volumes/") or len(x.split("/")) > 3:
|
||||||
|
errors.append("'{}' is not a volume.".format(x))
|
||||||
|
continue
|
||||||
|
if not os.path.exists(x):
|
||||||
|
# Doesn't exist, skip it
|
||||||
|
errors.append("'{}' does not exist.".format(x))
|
||||||
|
continue
|
||||||
|
args.append(x)
|
||||||
|
mount_list = []
|
||||||
|
needs_sudo = d.needs_sudo()
|
||||||
|
for x in args:
|
||||||
|
name = d.get_volume_name(x)
|
||||||
|
if not name: name = "Untitled"
|
||||||
|
name = name.replace('"','\\"') # Escape double quotes in names
|
||||||
|
efi = d.get_efi(x)
|
||||||
|
if efi: mount_list.append((efi,name,d.is_mounted(efi),"diskutil mount {}".format(efi)))
|
||||||
|
else: errors.append("'{}' has no ESP.".format(name))
|
||||||
|
if mount_list:
|
||||||
|
# We have something to mount
|
||||||
|
efis = [x[-1] for x in mount_list if not x[2]] # Only mount those that aren't mounted
|
||||||
|
names = [x[1] for x in mount_list if not x[2]]
|
||||||
|
if efis: # We have something to mount here
|
||||||
|
command = "do shell script \"{}\" with prompt \"MountEFI would like to mount the ESP{} on {}\"{}".format(
|
||||||
|
"; ".join(efis),
|
||||||
|
"s" if len(names) > 1 else "",
|
||||||
|
", ".join(names),
|
||||||
|
" with administrator privileges" if needs_sudo else "")
|
||||||
|
o,e,r = d.r.run({"args":["osascript","-e",command]})
|
||||||
|
if r > 0 and len(e.strip()) and e.strip().lower().endswith("(-128)"): exit() # User canceled, bail
|
||||||
|
# Update the disks
|
||||||
|
d.update()
|
||||||
|
# Walk the mounts and find out which aren't mounted
|
||||||
|
for efi,name,mounted,comm in mount_list:
|
||||||
|
mounted_at = d.get_mount_point(efi)
|
||||||
|
if mounted_at: d.open_mount_point(mounted_at)
|
||||||
|
else: errors.append("ESP for '{}' failed to mount.".format(name))
|
||||||
|
else:
|
||||||
|
errors.append("No disks with ESPs selected.")
|
||||||
|
if errors:
|
||||||
|
# Display our errors before we leave
|
||||||
|
d.r.run({"args":["osascript","-e","display dialog \"{}\" buttons {{\"OK\"}} default button \"OK\" with icon caution".format("\n".join(errors))]})
|
||||||
Binary file not shown.
+612
@@ -0,0 +1,612 @@
|
|||||||
|
### ###
|
||||||
|
# Imports #
|
||||||
|
### ###
|
||||||
|
|
||||||
|
import datetime, os, plistlib, struct, sys, itertools
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
if sys.version_info < (3,0):
|
||||||
|
# Force use of StringIO instead of cStringIO as the latter
|
||||||
|
# has issues with Unicode strings
|
||||||
|
from StringIO import StringIO
|
||||||
|
|
||||||
|
try:
|
||||||
|
basestring # Python 2
|
||||||
|
unicode
|
||||||
|
except NameError:
|
||||||
|
basestring = str # Python 3
|
||||||
|
unicode = str
|
||||||
|
|
||||||
|
try:
|
||||||
|
FMT_XML = plistlib.FMT_XML
|
||||||
|
FMT_BINARY = plistlib.FMT_BINARY
|
||||||
|
except AttributeError:
|
||||||
|
FMT_XML = "FMT_XML"
|
||||||
|
FMT_BINARY = "FMT_BINARY"
|
||||||
|
|
||||||
|
### ###
|
||||||
|
# Helper Methods #
|
||||||
|
### ###
|
||||||
|
|
||||||
|
def wrap_data(value):
|
||||||
|
if not _check_py3(): return plistlib.Data(value)
|
||||||
|
return value
|
||||||
|
|
||||||
|
def extract_data(value):
|
||||||
|
if not _check_py3() and isinstance(value,plistlib.Data): return value.data
|
||||||
|
return value
|
||||||
|
|
||||||
|
def _check_py3():
|
||||||
|
return sys.version_info >= (3, 0)
|
||||||
|
|
||||||
|
def _is_binary(fp):
|
||||||
|
if isinstance(fp, basestring):
|
||||||
|
return fp.startswith(b"bplist00")
|
||||||
|
header = fp.read(32)
|
||||||
|
fp.seek(0)
|
||||||
|
return header[:8] == b'bplist00'
|
||||||
|
|
||||||
|
### ###
|
||||||
|
# Deprecated Functions - Remapped #
|
||||||
|
### ###
|
||||||
|
|
||||||
|
def readPlist(pathOrFile):
|
||||||
|
if not isinstance(pathOrFile, basestring):
|
||||||
|
return load(pathOrFile)
|
||||||
|
with open(pathOrFile, "rb") as f:
|
||||||
|
return load(f)
|
||||||
|
|
||||||
|
def writePlist(value, pathOrFile):
|
||||||
|
if not isinstance(pathOrFile, basestring):
|
||||||
|
return dump(value, pathOrFile, fmt=FMT_XML, sort_keys=True, skipkeys=False)
|
||||||
|
with open(pathOrFile, "wb") as f:
|
||||||
|
return dump(value, f, fmt=FMT_XML, sort_keys=True, skipkeys=False)
|
||||||
|
|
||||||
|
### ###
|
||||||
|
# Remapped Functions #
|
||||||
|
### ###
|
||||||
|
|
||||||
|
def load(fp, fmt=None, use_builtin_types=None, dict_type=dict):
|
||||||
|
if _check_py3():
|
||||||
|
use_builtin_types = True if use_builtin_types == None else use_builtin_types
|
||||||
|
# We need to monkey patch this to allow for hex integers - code taken/modified from
|
||||||
|
# https://github.com/python/cpython/blob/3.8/Lib/plistlib.py
|
||||||
|
if fmt is None:
|
||||||
|
header = fp.read(32)
|
||||||
|
fp.seek(0)
|
||||||
|
for info in plistlib._FORMATS.values():
|
||||||
|
if info['detect'](header):
|
||||||
|
P = info['parser']
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise plistlib.InvalidFileException()
|
||||||
|
else:
|
||||||
|
P = plistlib._FORMATS[fmt]['parser']
|
||||||
|
try:
|
||||||
|
p = P(use_builtin_types=use_builtin_types, dict_type=dict_type)
|
||||||
|
except:
|
||||||
|
# Python 3.9 removed use_builtin_types
|
||||||
|
p = P(dict_type=dict_type)
|
||||||
|
if isinstance(p,plistlib._PlistParser):
|
||||||
|
# Monkey patch!
|
||||||
|
def end_integer():
|
||||||
|
d = p.get_data()
|
||||||
|
value = int(d,16) if d.lower().startswith("0x") else int(d)
|
||||||
|
if -1 << 63 <= value < 1 << 63:
|
||||||
|
p.add_object(value)
|
||||||
|
else:
|
||||||
|
raise OverflowError("Integer overflow at line {}".format(p.parser.CurrentLineNumber))
|
||||||
|
def end_data():
|
||||||
|
try:
|
||||||
|
p.add_object(plistlib._decode_base64(p.get_data()))
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception("Data error at line {}: {}".format(p.parser.CurrentLineNumber,e))
|
||||||
|
p.end_integer = end_integer
|
||||||
|
p.end_data = end_data
|
||||||
|
return p.parse(fp)
|
||||||
|
elif not _is_binary(fp):
|
||||||
|
# Is not binary - assume a string - and try to load
|
||||||
|
# We avoid using readPlistFromString() as that uses
|
||||||
|
# cStringIO and fails when Unicode strings are detected
|
||||||
|
# Don't subclass - keep the parser local
|
||||||
|
from xml.parsers.expat import ParserCreate
|
||||||
|
# Create a new PlistParser object - then we need to set up
|
||||||
|
# the values and parse.
|
||||||
|
p = plistlib.PlistParser()
|
||||||
|
parser = ParserCreate()
|
||||||
|
parser.StartElementHandler = p.handleBeginElement
|
||||||
|
parser.EndElementHandler = p.handleEndElement
|
||||||
|
parser.CharacterDataHandler = p.handleData
|
||||||
|
# We also need to monkey patch this to allow for other dict_types, hex int support
|
||||||
|
# proper line output for data errors, and for unicode string decoding
|
||||||
|
def begin_dict(attrs):
|
||||||
|
d = dict_type()
|
||||||
|
p.addObject(d)
|
||||||
|
p.stack.append(d)
|
||||||
|
def end_integer():
|
||||||
|
d = p.getData()
|
||||||
|
value = int(d,16) if d.lower().startswith("0x") else int(d)
|
||||||
|
if -1 << 63 <= value < 1 << 63:
|
||||||
|
p.addObject(value)
|
||||||
|
else:
|
||||||
|
raise OverflowError("Integer overflow at line {}".format(parser.CurrentLineNumber))
|
||||||
|
def end_data():
|
||||||
|
try:
|
||||||
|
p.addObject(plistlib.Data.fromBase64(p.getData()))
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception("Data error at line {}: {}".format(parser.CurrentLineNumber,e))
|
||||||
|
def end_string():
|
||||||
|
d = p.getData()
|
||||||
|
if isinstance(d,unicode):
|
||||||
|
d = d.encode("utf-8")
|
||||||
|
p.addObject(d)
|
||||||
|
p.begin_dict = begin_dict
|
||||||
|
p.end_integer = end_integer
|
||||||
|
p.end_data = end_data
|
||||||
|
p.end_string = end_string
|
||||||
|
if isinstance(fp, unicode):
|
||||||
|
# Encode unicode -> string; use utf-8 for safety
|
||||||
|
fp = fp.encode("utf-8")
|
||||||
|
if isinstance(fp, basestring):
|
||||||
|
# It's a string - let's wrap it up
|
||||||
|
fp = StringIO(fp)
|
||||||
|
# Parse it
|
||||||
|
parser.ParseFile(fp)
|
||||||
|
return p.root
|
||||||
|
else:
|
||||||
|
use_builtin_types = False if use_builtin_types == None else use_builtin_types
|
||||||
|
try:
|
||||||
|
p = _BinaryPlistParser(use_builtin_types=use_builtin_types, dict_type=dict_type)
|
||||||
|
except:
|
||||||
|
# Python 3.9 removed use_builtin_types
|
||||||
|
p = _BinaryPlistParser(dict_type=dict_type)
|
||||||
|
return p.parse(fp)
|
||||||
|
|
||||||
|
def loads(value, fmt=None, use_builtin_types=None, dict_type=dict):
|
||||||
|
if _check_py3() and isinstance(value, basestring):
|
||||||
|
# If it's a string - encode it
|
||||||
|
value = value.encode()
|
||||||
|
try:
|
||||||
|
return load(BytesIO(value),fmt=fmt,use_builtin_types=use_builtin_types,dict_type=dict_type)
|
||||||
|
except:
|
||||||
|
# Python 3.9 removed use_builtin_types
|
||||||
|
return load(BytesIO(value),fmt=fmt,dict_type=dict_type)
|
||||||
|
def dump(value, fp, fmt=FMT_XML, sort_keys=True, skipkeys=False):
|
||||||
|
if _check_py3():
|
||||||
|
plistlib.dump(value, fp, fmt=fmt, sort_keys=sort_keys, skipkeys=skipkeys)
|
||||||
|
else:
|
||||||
|
if fmt == FMT_XML:
|
||||||
|
# We need to monkey patch a bunch here too in order to avoid auto-sorting
|
||||||
|
# of keys
|
||||||
|
writer = plistlib.PlistWriter(fp)
|
||||||
|
def writeDict(d):
|
||||||
|
if d:
|
||||||
|
writer.beginElement("dict")
|
||||||
|
items = sorted(d.items()) if sort_keys else d.items()
|
||||||
|
for key, value in items:
|
||||||
|
if not isinstance(key, basestring):
|
||||||
|
if skipkeys:
|
||||||
|
continue
|
||||||
|
raise TypeError("keys must be strings")
|
||||||
|
writer.simpleElement("key", key)
|
||||||
|
writer.writeValue(value)
|
||||||
|
writer.endElement("dict")
|
||||||
|
else:
|
||||||
|
writer.simpleElement("dict")
|
||||||
|
writer.writeDict = writeDict
|
||||||
|
writer.writeln("<plist version=\"1.0\">")
|
||||||
|
writer.writeValue(value)
|
||||||
|
writer.writeln("</plist>")
|
||||||
|
elif fmt == FMT_BINARY:
|
||||||
|
# Assume binary at this point
|
||||||
|
writer = _BinaryPlistWriter(fp, sort_keys=sort_keys, skipkeys=skipkeys)
|
||||||
|
writer.write(value)
|
||||||
|
else:
|
||||||
|
# Not a proper format
|
||||||
|
raise ValueError("Unsupported format: {}".format(fmt))
|
||||||
|
|
||||||
|
def dumps(value, fmt=FMT_XML, skipkeys=False, sort_keys=True):
|
||||||
|
if _check_py3():
|
||||||
|
return plistlib.dumps(value, fmt=fmt, skipkeys=skipkeys, sort_keys=sort_keys).decode("utf-8")
|
||||||
|
else:
|
||||||
|
# We avoid using writePlistToString() as that uses
|
||||||
|
# cStringIO and fails when Unicode strings are detected
|
||||||
|
f = StringIO()
|
||||||
|
dump(value, f, fmt=fmt, skipkeys=skipkeys, sort_keys=sort_keys)
|
||||||
|
return f.getvalue()
|
||||||
|
|
||||||
|
### ###
|
||||||
|
# Binary Plist Stuff For Py2 #
|
||||||
|
### ###
|
||||||
|
|
||||||
|
# From the python 3 plistlib.py source: https://github.com/python/cpython/blob/3.7/Lib/plistlib.py
|
||||||
|
# Tweaked to function on Python 2
|
||||||
|
|
||||||
|
class InvalidFileException (ValueError):
|
||||||
|
def __init__(self, message="Invalid file"):
|
||||||
|
ValueError.__init__(self, message)
|
||||||
|
|
||||||
|
_BINARY_FORMAT = {1: 'B', 2: 'H', 4: 'L', 8: 'Q'}
|
||||||
|
|
||||||
|
_undefined = object()
|
||||||
|
|
||||||
|
class _BinaryPlistParser:
|
||||||
|
"""
|
||||||
|
Read or write a binary plist file, following the description of the binary
|
||||||
|
format. Raise InvalidFileException in case of error, otherwise return the
|
||||||
|
root object.
|
||||||
|
see also: http://opensource.apple.com/source/CF/CF-744.18/CFBinaryPList.c
|
||||||
|
"""
|
||||||
|
def __init__(self, use_builtin_types, dict_type):
|
||||||
|
self._use_builtin_types = use_builtin_types
|
||||||
|
self._dict_type = dict_type
|
||||||
|
|
||||||
|
def parse(self, fp):
|
||||||
|
try:
|
||||||
|
# The basic file format:
|
||||||
|
# HEADER
|
||||||
|
# object...
|
||||||
|
# refid->offset...
|
||||||
|
# TRAILER
|
||||||
|
self._fp = fp
|
||||||
|
self._fp.seek(-32, os.SEEK_END)
|
||||||
|
trailer = self._fp.read(32)
|
||||||
|
if len(trailer) != 32:
|
||||||
|
raise InvalidFileException()
|
||||||
|
(
|
||||||
|
offset_size, self._ref_size, num_objects, top_object,
|
||||||
|
offset_table_offset
|
||||||
|
) = struct.unpack('>6xBBQQQ', trailer)
|
||||||
|
self._fp.seek(offset_table_offset)
|
||||||
|
self._object_offsets = self._read_ints(num_objects, offset_size)
|
||||||
|
self._objects = [_undefined] * num_objects
|
||||||
|
return self._read_object(top_object)
|
||||||
|
|
||||||
|
except (OSError, IndexError, struct.error, OverflowError,
|
||||||
|
UnicodeDecodeError):
|
||||||
|
raise InvalidFileException()
|
||||||
|
|
||||||
|
def _get_size(self, tokenL):
|
||||||
|
""" return the size of the next object."""
|
||||||
|
if tokenL == 0xF:
|
||||||
|
m = ord(self._fp.read(1)[0]) & 0x3
|
||||||
|
s = 1 << m
|
||||||
|
f = '>' + _BINARY_FORMAT[s]
|
||||||
|
return struct.unpack(f, self._fp.read(s))[0]
|
||||||
|
|
||||||
|
return tokenL
|
||||||
|
|
||||||
|
def _read_ints(self, n, size):
|
||||||
|
data = self._fp.read(size * n)
|
||||||
|
if size in _BINARY_FORMAT:
|
||||||
|
return struct.unpack('>' + _BINARY_FORMAT[size] * n, data)
|
||||||
|
else:
|
||||||
|
if not size or len(data) != size * n:
|
||||||
|
raise InvalidFileException()
|
||||||
|
return tuple(int.from_bytes(data[i: i + size], 'big')
|
||||||
|
for i in range(0, size * n, size))
|
||||||
|
|
||||||
|
def _read_refs(self, n):
|
||||||
|
return self._read_ints(n, self._ref_size)
|
||||||
|
|
||||||
|
def _read_object(self, ref):
|
||||||
|
"""
|
||||||
|
read the object by reference.
|
||||||
|
May recursively read sub-objects (content of an array/dict/set)
|
||||||
|
"""
|
||||||
|
result = self._objects[ref]
|
||||||
|
if result is not _undefined:
|
||||||
|
return result
|
||||||
|
|
||||||
|
offset = self._object_offsets[ref]
|
||||||
|
self._fp.seek(offset)
|
||||||
|
token = ord(self._fp.read(1)[0])
|
||||||
|
tokenH, tokenL = token & 0xF0, token & 0x0F
|
||||||
|
|
||||||
|
if token == 0: # \x00 or 0x00
|
||||||
|
result = None
|
||||||
|
|
||||||
|
elif token == 8: # \x08 or 0x08
|
||||||
|
result = False
|
||||||
|
|
||||||
|
elif token == 9: # \x09 or 0x09
|
||||||
|
result = True
|
||||||
|
|
||||||
|
# The referenced source code also mentions URL (0x0c, 0x0d) and
|
||||||
|
# UUID (0x0e), but neither can be generated using the Cocoa libraries.
|
||||||
|
|
||||||
|
elif token == 15: # \x0f or 0x0f
|
||||||
|
result = b''
|
||||||
|
|
||||||
|
elif tokenH == 0x10: # int
|
||||||
|
result = 0
|
||||||
|
for k in range((2 << tokenL) - 1):
|
||||||
|
result = (result << 8) + ord(self._fp.read(1))
|
||||||
|
# result = int.from_bytes(self._fp.read(1 << tokenL),
|
||||||
|
# 'big', signed=tokenL >= 3)
|
||||||
|
|
||||||
|
elif token == 0x22: # real
|
||||||
|
result = struct.unpack('>f', self._fp.read(4))[0]
|
||||||
|
|
||||||
|
elif token == 0x23: # real
|
||||||
|
result = struct.unpack('>d', self._fp.read(8))[0]
|
||||||
|
|
||||||
|
elif token == 0x33: # date
|
||||||
|
f = struct.unpack('>d', self._fp.read(8))[0]
|
||||||
|
# timestamp 0 of binary plists corresponds to 1/1/2001
|
||||||
|
# (year of Mac OS X 10.0), instead of 1/1/1970.
|
||||||
|
result = (datetime.datetime(2001, 1, 1) +
|
||||||
|
datetime.timedelta(seconds=f))
|
||||||
|
|
||||||
|
elif tokenH == 0x40: # data
|
||||||
|
s = self._get_size(tokenL)
|
||||||
|
if self._use_builtin_types:
|
||||||
|
result = self._fp.read(s)
|
||||||
|
else:
|
||||||
|
result = plistlib.Data(self._fp.read(s))
|
||||||
|
|
||||||
|
elif tokenH == 0x50: # ascii string
|
||||||
|
s = self._get_size(tokenL)
|
||||||
|
result = self._fp.read(s).decode('ascii')
|
||||||
|
result = result
|
||||||
|
|
||||||
|
elif tokenH == 0x60: # unicode string
|
||||||
|
s = self._get_size(tokenL)
|
||||||
|
result = self._fp.read(s * 2).decode('utf-16be')
|
||||||
|
|
||||||
|
# tokenH == 0x80 is documented as 'UID' and appears to be used for
|
||||||
|
# keyed-archiving, not in plists.
|
||||||
|
|
||||||
|
elif tokenH == 0xA0: # array
|
||||||
|
s = self._get_size(tokenL)
|
||||||
|
obj_refs = self._read_refs(s)
|
||||||
|
result = []
|
||||||
|
self._objects[ref] = result
|
||||||
|
result.extend(self._read_object(x) for x in obj_refs)
|
||||||
|
|
||||||
|
# tokenH == 0xB0 is documented as 'ordset', but is not actually
|
||||||
|
# implemented in the Apple reference code.
|
||||||
|
|
||||||
|
# tokenH == 0xC0 is documented as 'set', but sets cannot be used in
|
||||||
|
# plists.
|
||||||
|
|
||||||
|
elif tokenH == 0xD0: # dict
|
||||||
|
s = self._get_size(tokenL)
|
||||||
|
key_refs = self._read_refs(s)
|
||||||
|
obj_refs = self._read_refs(s)
|
||||||
|
result = self._dict_type()
|
||||||
|
self._objects[ref] = result
|
||||||
|
for k, o in zip(key_refs, obj_refs):
|
||||||
|
key = self._read_object(k)
|
||||||
|
if isinstance(key, plistlib.Data):
|
||||||
|
key = key.data
|
||||||
|
result[key] = self._read_object(o)
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise InvalidFileException()
|
||||||
|
|
||||||
|
self._objects[ref] = result
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _count_to_size(count):
|
||||||
|
if count < 1 << 8:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
elif count < 1 << 16:
|
||||||
|
return 2
|
||||||
|
|
||||||
|
elif count << 1 << 32:
|
||||||
|
return 4
|
||||||
|
|
||||||
|
else:
|
||||||
|
return 8
|
||||||
|
|
||||||
|
_scalars = (str, int, float, datetime.datetime, bytes)
|
||||||
|
|
||||||
|
class _BinaryPlistWriter (object):
|
||||||
|
def __init__(self, fp, sort_keys, skipkeys):
|
||||||
|
self._fp = fp
|
||||||
|
self._sort_keys = sort_keys
|
||||||
|
self._skipkeys = skipkeys
|
||||||
|
|
||||||
|
def write(self, value):
|
||||||
|
|
||||||
|
# Flattened object list:
|
||||||
|
self._objlist = []
|
||||||
|
|
||||||
|
# Mappings from object->objectid
|
||||||
|
# First dict has (type(object), object) as the key,
|
||||||
|
# second dict is used when object is not hashable and
|
||||||
|
# has id(object) as the key.
|
||||||
|
self._objtable = {}
|
||||||
|
self._objidtable = {}
|
||||||
|
|
||||||
|
# Create list of all objects in the plist
|
||||||
|
self._flatten(value)
|
||||||
|
|
||||||
|
# Size of object references in serialized containers
|
||||||
|
# depends on the number of objects in the plist.
|
||||||
|
num_objects = len(self._objlist)
|
||||||
|
self._object_offsets = [0]*num_objects
|
||||||
|
self._ref_size = _count_to_size(num_objects)
|
||||||
|
|
||||||
|
self._ref_format = _BINARY_FORMAT[self._ref_size]
|
||||||
|
|
||||||
|
# Write file header
|
||||||
|
self._fp.write(b'bplist00')
|
||||||
|
|
||||||
|
# Write object list
|
||||||
|
for obj in self._objlist:
|
||||||
|
self._write_object(obj)
|
||||||
|
|
||||||
|
# Write refnum->object offset table
|
||||||
|
top_object = self._getrefnum(value)
|
||||||
|
offset_table_offset = self._fp.tell()
|
||||||
|
offset_size = _count_to_size(offset_table_offset)
|
||||||
|
offset_format = '>' + _BINARY_FORMAT[offset_size] * num_objects
|
||||||
|
self._fp.write(struct.pack(offset_format, *self._object_offsets))
|
||||||
|
|
||||||
|
# Write trailer
|
||||||
|
sort_version = 0
|
||||||
|
trailer = (
|
||||||
|
sort_version, offset_size, self._ref_size, num_objects,
|
||||||
|
top_object, offset_table_offset
|
||||||
|
)
|
||||||
|
self._fp.write(struct.pack('>5xBBBQQQ', *trailer))
|
||||||
|
|
||||||
|
def _flatten(self, value):
|
||||||
|
# First check if the object is in the object table, not used for
|
||||||
|
# containers to ensure that two subcontainers with the same contents
|
||||||
|
# will be serialized as distinct values.
|
||||||
|
if isinstance(value, _scalars):
|
||||||
|
if (type(value), value) in self._objtable:
|
||||||
|
return
|
||||||
|
|
||||||
|
elif isinstance(value, plistlib.Data):
|
||||||
|
if (type(value.data), value.data) in self._objtable:
|
||||||
|
return
|
||||||
|
|
||||||
|
elif id(value) in self._objidtable:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Add to objectreference map
|
||||||
|
refnum = len(self._objlist)
|
||||||
|
self._objlist.append(value)
|
||||||
|
if isinstance(value, _scalars):
|
||||||
|
self._objtable[(type(value), value)] = refnum
|
||||||
|
elif isinstance(value, plistlib.Data):
|
||||||
|
self._objtable[(type(value.data), value.data)] = refnum
|
||||||
|
else:
|
||||||
|
self._objidtable[id(value)] = refnum
|
||||||
|
|
||||||
|
# And finally recurse into containers
|
||||||
|
if isinstance(value, dict):
|
||||||
|
keys = []
|
||||||
|
values = []
|
||||||
|
items = value.items()
|
||||||
|
if self._sort_keys:
|
||||||
|
items = sorted(items)
|
||||||
|
|
||||||
|
for k, v in items:
|
||||||
|
if not isinstance(k, basestring):
|
||||||
|
if self._skipkeys:
|
||||||
|
continue
|
||||||
|
raise TypeError("keys must be strings")
|
||||||
|
keys.append(k)
|
||||||
|
values.append(v)
|
||||||
|
|
||||||
|
for o in itertools.chain(keys, values):
|
||||||
|
self._flatten(o)
|
||||||
|
|
||||||
|
elif isinstance(value, (list, tuple)):
|
||||||
|
for o in value:
|
||||||
|
self._flatten(o)
|
||||||
|
|
||||||
|
def _getrefnum(self, value):
|
||||||
|
if isinstance(value, _scalars):
|
||||||
|
return self._objtable[(type(value), value)]
|
||||||
|
elif isinstance(value, plistlib.Data):
|
||||||
|
return self._objtable[(type(value.data), value.data)]
|
||||||
|
else:
|
||||||
|
return self._objidtable[id(value)]
|
||||||
|
|
||||||
|
def _write_size(self, token, size):
|
||||||
|
if size < 15:
|
||||||
|
self._fp.write(struct.pack('>B', token | size))
|
||||||
|
|
||||||
|
elif size < 1 << 8:
|
||||||
|
self._fp.write(struct.pack('>BBB', token | 0xF, 0x10, size))
|
||||||
|
|
||||||
|
elif size < 1 << 16:
|
||||||
|
self._fp.write(struct.pack('>BBH', token | 0xF, 0x11, size))
|
||||||
|
|
||||||
|
elif size < 1 << 32:
|
||||||
|
self._fp.write(struct.pack('>BBL', token | 0xF, 0x12, size))
|
||||||
|
|
||||||
|
else:
|
||||||
|
self._fp.write(struct.pack('>BBQ', token | 0xF, 0x13, size))
|
||||||
|
|
||||||
|
def _write_object(self, value):
|
||||||
|
ref = self._getrefnum(value)
|
||||||
|
self._object_offsets[ref] = self._fp.tell()
|
||||||
|
if value is None:
|
||||||
|
self._fp.write(b'\x00')
|
||||||
|
|
||||||
|
elif value is False:
|
||||||
|
self._fp.write(b'\x08')
|
||||||
|
|
||||||
|
elif value is True:
|
||||||
|
self._fp.write(b'\x09')
|
||||||
|
|
||||||
|
elif isinstance(value, int):
|
||||||
|
if value < 0:
|
||||||
|
try:
|
||||||
|
self._fp.write(struct.pack('>Bq', 0x13, value))
|
||||||
|
except struct.error:
|
||||||
|
raise OverflowError(value) # from None
|
||||||
|
elif value < 1 << 8:
|
||||||
|
self._fp.write(struct.pack('>BB', 0x10, value))
|
||||||
|
elif value < 1 << 16:
|
||||||
|
self._fp.write(struct.pack('>BH', 0x11, value))
|
||||||
|
elif value < 1 << 32:
|
||||||
|
self._fp.write(struct.pack('>BL', 0x12, value))
|
||||||
|
elif value < 1 << 63:
|
||||||
|
self._fp.write(struct.pack('>BQ', 0x13, value))
|
||||||
|
elif value < 1 << 64:
|
||||||
|
self._fp.write(b'\x14' + value.to_bytes(16, 'big', signed=True))
|
||||||
|
else:
|
||||||
|
raise OverflowError(value)
|
||||||
|
|
||||||
|
elif isinstance(value, float):
|
||||||
|
self._fp.write(struct.pack('>Bd', 0x23, value))
|
||||||
|
|
||||||
|
elif isinstance(value, datetime.datetime):
|
||||||
|
f = (value - datetime.datetime(2001, 1, 1)).total_seconds()
|
||||||
|
self._fp.write(struct.pack('>Bd', 0x33, f))
|
||||||
|
|
||||||
|
elif isinstance(value, plistlib.Data):
|
||||||
|
self._write_size(0x40, len(value.data))
|
||||||
|
self._fp.write(value.data)
|
||||||
|
|
||||||
|
elif isinstance(value, basestring):
|
||||||
|
try:
|
||||||
|
t = value.encode('ascii')
|
||||||
|
self._write_size(0x50, len(value))
|
||||||
|
except UnicodeEncodeError:
|
||||||
|
t = value.encode('utf-16be')
|
||||||
|
self._write_size(0x60, len(t) // 2)
|
||||||
|
self._fp.write(t)
|
||||||
|
|
||||||
|
elif isinstance(value, (bytes, bytearray)):
|
||||||
|
self._write_size(0x40, len(value))
|
||||||
|
self._fp.write(value)
|
||||||
|
|
||||||
|
elif isinstance(value, (list, tuple)):
|
||||||
|
refs = [self._getrefnum(o) for o in value]
|
||||||
|
s = len(refs)
|
||||||
|
self._write_size(0xA0, s)
|
||||||
|
self._fp.write(struct.pack('>' + self._ref_format * s, *refs))
|
||||||
|
|
||||||
|
elif isinstance(value, dict):
|
||||||
|
keyRefs, valRefs = [], []
|
||||||
|
|
||||||
|
if self._sort_keys:
|
||||||
|
rootItems = sorted(value.items())
|
||||||
|
else:
|
||||||
|
rootItems = value.items()
|
||||||
|
|
||||||
|
for k, v in rootItems:
|
||||||
|
if not isinstance(k, basestring):
|
||||||
|
if self._skipkeys:
|
||||||
|
continue
|
||||||
|
raise TypeError("keys must be strings")
|
||||||
|
keyRefs.append(self._getrefnum(k))
|
||||||
|
valRefs.append(self._getrefnum(v))
|
||||||
|
|
||||||
|
s = len(keyRefs)
|
||||||
|
self._write_size(0xD0, s)
|
||||||
|
self._fp.write(struct.pack('>' + self._ref_format * s, *keyRefs))
|
||||||
|
self._fp.write(struct.pack('>' + self._ref_format * s, *valRefs))
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise TypeError(value)
|
||||||
+151
@@ -0,0 +1,151 @@
|
|||||||
|
import sys, subprocess, time, threading, shlex
|
||||||
|
try:
|
||||||
|
from Queue import Queue, Empty
|
||||||
|
except:
|
||||||
|
from queue import Queue, Empty
|
||||||
|
|
||||||
|
ON_POSIX = 'posix' in sys.builtin_module_names
|
||||||
|
|
||||||
|
class Run:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
return
|
||||||
|
|
||||||
|
def _read_output(self, pipe, q):
|
||||||
|
try:
|
||||||
|
for line in iter(lambda: pipe.read(1), b''):
|
||||||
|
q.put(line)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
pipe.close()
|
||||||
|
|
||||||
|
def _create_thread(self, output):
|
||||||
|
# Creates a new queue and thread object to watch based on the output pipe sent
|
||||||
|
q = Queue()
|
||||||
|
t = threading.Thread(target=self._read_output, args=(output, q))
|
||||||
|
t.daemon = True
|
||||||
|
return (q,t)
|
||||||
|
|
||||||
|
def _stream_output(self, comm, shell = False):
|
||||||
|
output = error = ""
|
||||||
|
p = None
|
||||||
|
try:
|
||||||
|
if shell and type(comm) is list:
|
||||||
|
comm = " ".join(shlex.quote(x) for x in comm)
|
||||||
|
if not shell and type(comm) is str:
|
||||||
|
comm = shlex.split(comm)
|
||||||
|
p = subprocess.Popen(comm, shell=shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=0, universal_newlines=True, close_fds=ON_POSIX)
|
||||||
|
# Setup the stdout thread/queue
|
||||||
|
q,t = self._create_thread(p.stdout)
|
||||||
|
qe,te = self._create_thread(p.stderr)
|
||||||
|
# Start both threads
|
||||||
|
t.start()
|
||||||
|
te.start()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
c = z = ""
|
||||||
|
try: c = q.get_nowait()
|
||||||
|
except Empty: pass
|
||||||
|
else:
|
||||||
|
sys.stdout.write(c)
|
||||||
|
output += c
|
||||||
|
sys.stdout.flush()
|
||||||
|
try: z = qe.get_nowait()
|
||||||
|
except Empty: pass
|
||||||
|
else:
|
||||||
|
sys.stderr.write(z)
|
||||||
|
error += z
|
||||||
|
sys.stderr.flush()
|
||||||
|
if not c==z=="": continue # Keep going until empty
|
||||||
|
# No output - see if still running
|
||||||
|
p.poll()
|
||||||
|
if p.returncode != None:
|
||||||
|
# Subprocess ended
|
||||||
|
break
|
||||||
|
# No output, but subprocess still running - stall for 20ms
|
||||||
|
time.sleep(0.02)
|
||||||
|
|
||||||
|
o, e = p.communicate()
|
||||||
|
return (output+o, error+e, p.returncode)
|
||||||
|
except:
|
||||||
|
if p:
|
||||||
|
try: o, e = p.communicate()
|
||||||
|
except: o = e = ""
|
||||||
|
return (output+o, error+e, p.returncode)
|
||||||
|
return ("", "Command not found!", 1)
|
||||||
|
|
||||||
|
def _decode(self, value, encoding="utf-8", errors="ignore"):
|
||||||
|
# Helper method to only decode if bytes type
|
||||||
|
if sys.version_info >= (3,0) and isinstance(value, bytes):
|
||||||
|
return value.decode(encoding,errors)
|
||||||
|
return value
|
||||||
|
|
||||||
|
def _run_command(self, comm, shell = False):
|
||||||
|
c = None
|
||||||
|
try:
|
||||||
|
if shell and type(comm) is list:
|
||||||
|
comm = " ".join(shlex.quote(x) for x in comm)
|
||||||
|
if not shell and type(comm) is str:
|
||||||
|
comm = shlex.split(comm)
|
||||||
|
p = subprocess.Popen(comm, shell=shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
|
c = p.communicate()
|
||||||
|
except:
|
||||||
|
if c == None:
|
||||||
|
return ("", "Command not found!", 1)
|
||||||
|
return (self._decode(c[0]), self._decode(c[1]), p.returncode)
|
||||||
|
|
||||||
|
def run(self, command_list, leave_on_fail = False):
|
||||||
|
# Command list should be an array of dicts
|
||||||
|
if type(command_list) is dict:
|
||||||
|
# We only have one command
|
||||||
|
command_list = [command_list]
|
||||||
|
output_list = []
|
||||||
|
for comm in command_list:
|
||||||
|
args = comm.get("args", [])
|
||||||
|
shell = comm.get("shell", False)
|
||||||
|
stream = comm.get("stream", False)
|
||||||
|
sudo = comm.get("sudo", False)
|
||||||
|
stdout = comm.get("stdout", False)
|
||||||
|
stderr = comm.get("stderr", False)
|
||||||
|
mess = comm.get("message", None)
|
||||||
|
show = comm.get("show", False)
|
||||||
|
|
||||||
|
if not mess == None:
|
||||||
|
print(mess)
|
||||||
|
|
||||||
|
if not len(args):
|
||||||
|
# nothing to process
|
||||||
|
continue
|
||||||
|
if sudo:
|
||||||
|
# Check if we have sudo
|
||||||
|
out = self._run_command(["which", "sudo"])
|
||||||
|
if "sudo" in out[0]:
|
||||||
|
# Can sudo
|
||||||
|
if type(args) is list:
|
||||||
|
args.insert(0, out[0].replace("\n", "")) # add to start of list
|
||||||
|
elif type(args) is str:
|
||||||
|
args = out[0].replace("\n", "") + " " + args # add to start of string
|
||||||
|
|
||||||
|
if show:
|
||||||
|
print(" ".join(args))
|
||||||
|
|
||||||
|
if stream:
|
||||||
|
# Stream it!
|
||||||
|
out = self._stream_output(args, shell)
|
||||||
|
else:
|
||||||
|
# Just run and gather output
|
||||||
|
out = self._run_command(args, shell)
|
||||||
|
if stdout and len(out[0]):
|
||||||
|
print(out[0])
|
||||||
|
if stderr and len(out[1]):
|
||||||
|
print(out[1])
|
||||||
|
# Append output
|
||||||
|
output_list.append(out)
|
||||||
|
# Check for errors
|
||||||
|
if leave_on_fail and out[2] != 0:
|
||||||
|
# Got an error - leave
|
||||||
|
break
|
||||||
|
if len(output_list) == 1:
|
||||||
|
# We only ran one command - just return that output
|
||||||
|
return output_list[0]
|
||||||
|
return output_list
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>AMApplicationBuild</key>
|
<key>AMApplicationBuild</key>
|
||||||
<string>509</string>
|
<string>512</string>
|
||||||
<key>AMApplicationVersion</key>
|
<key>AMApplicationVersion</key>
|
||||||
<string>2.10</string>
|
<string>2.10</string>
|
||||||
<key>AMDocumentVersion</key>
|
<key>AMDocumentVersion</key>
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
<key>ActionParameters</key>
|
<key>ActionParameters</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>source</key>
|
<key>source</key>
|
||||||
<string>on run {input, parameters}
set the_list to {}
repeat with the_path in input
set the_text to POSIX path of (the_path as text)
set end of the_list to the_text
end repeat
return the_list
end run</string>
|
<string>on run {input, parameters}
# Hacky nonsense to export paths that aren't normally available
set export to "export PATH=/usr/local/bin:/usr/local/sbin/:$PATH; "
# Try to get the system python versions - starting with py 3
set python to ""
repeat with py in {"python3", "python"}
try
set pypath to do shell script export & "which " & py
if pypath is "/usr/bin/python3" then
# Check if we're running 10.15 or newer - and verify "xcode-select -p"
# runs properly before allowing the stub
if (do shell script "sw_vers -productVersion") ≥ "10.15" then
do shell script "xcode-select -p"
end if
end if
do shell script export & "/usr/bin/env " & py & " -V"
set python to py
exit repeat
on error errorMsg
# Didn't resolve - onto the next
end try
end repeat
if python is "" then
my print_error("Could not locate python3 or python via /usr/bin/env!", "Please download and install the latest from python.org")
return
end if
# If we got here - we have a python version to run.
# Organize the input paths
set passed_paths to ""
repeat with the_path in input
set the_text to quoted form of POSIX path of (the_path as text) as string
set passed_paths to passed_paths & " " & the_text
end repeat
# Let's make sure we have disk.py file
set target to "disk.py"
# Get the contents of the ~/Library/Services folder, and look for disk.py to run
set services to POSIX path of (path to home folder) & "Library/Services/"
set quick_action_list to do shell script "ls -1 " & quoted form of services
set quick_actions to paragraphs of quick_action_list
repeat with qa in quick_actions
set test_path to services & qa & "/Contents/Scripts/" & target
if my check_exists(test_path) is "0" then
# We got a valid path - let's run it
do shell script export & "/usr/bin/env " & python & " " & quoted form of test_path & passed_paths
return
end if
end repeat
# If we got here - then we didn't find the quick action
my print_error("Required files were not found!", "Please reinstall the latest Mount EFI quick action from https://github.com/corpnewt/MountEFI")
return
end run
on check_exists(path)
# Quick one-liner to abuse bash and see if a file/folder exists
return do shell script "if [[ -e " & quoted form of path & " ]]; then echo 0; else echo 1; fi"
end check_exists
on print_error(header, footer)
beep
display alert header & "
" & footer
end print_error</string>
|
||||||
</dict>
|
</dict>
|
||||||
<key>BundleIdentifier</key>
|
<key>BundleIdentifier</key>
|
||||||
<string>com.apple.Automator.RunScript</string>
|
<string>com.apple.Automator.RunScript</string>
|
||||||
@@ -67,18 +67,16 @@
|
|||||||
</array>
|
</array>
|
||||||
<key>Class Name</key>
|
<key>Class Name</key>
|
||||||
<string>RunScriptAction</string>
|
<string>RunScriptAction</string>
|
||||||
<key>IgnoresInput</key>
|
|
||||||
<false/>
|
|
||||||
<key>InputUUID</key>
|
<key>InputUUID</key>
|
||||||
<string>75C1496C-F97C-463E-8C60-9352C5A4B9C5</string>
|
<string>34AED1F9-B31F-4927-A2F3-43392E378CAC</string>
|
||||||
<key>Keywords</key>
|
<key>Keywords</key>
|
||||||
<array>
|
<array>
|
||||||
<string>Run</string>
|
<string>Run</string>
|
||||||
</array>
|
</array>
|
||||||
<key>OutputUUID</key>
|
<key>OutputUUID</key>
|
||||||
<string>E8D5BFA7-19E8-444A-A035-17C400DBCEFA</string>
|
<string>EAB751F1-47E9-4256-9F92-ED4CD9CF95F5</string>
|
||||||
<key>UUID</key>
|
<key>UUID</key>
|
||||||
<string>D6C847AF-3800-48CD-B4BA-DBFB7C59F37A</string>
|
<string>FDE567E1-0DA9-43F5-B7DC-9D30FD0B8553</string>
|
||||||
<key>UnlocalizedApplications</key>
|
<key>UnlocalizedApplications</key>
|
||||||
<array>
|
<array>
|
||||||
<string>Automator</string>
|
<string>Automator</string>
|
||||||
@@ -105,529 +103,18 @@ end run</string>
|
|||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
<key>isViewVisible</key>
|
<key>isViewVisible</key>
|
||||||
<true/>
|
<integer>1</integer>
|
||||||
<key>location</key>
|
<key>location</key>
|
||||||
<string>784.000000:368.000000</string>
|
<string>301.500000:558.000000</string>
|
||||||
<key>nibPath</key>
|
<key>nibPath</key>
|
||||||
<string>/System/Library/Automator/Run AppleScript.action/Contents/Resources/Base.lproj/main.nib</string>
|
<string>/System/Library/Automator/Run AppleScript.action/Contents/Resources/Base.lproj/main.nib</string>
|
||||||
</dict>
|
</dict>
|
||||||
<key>isViewVisible</key>
|
<key>isViewVisible</key>
|
||||||
<true/>
|
<integer>1</integer>
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>action</key>
|
|
||||||
<dict>
|
|
||||||
<key>AMAccepts</key>
|
|
||||||
<dict>
|
|
||||||
<key>Container</key>
|
|
||||||
<string>List</string>
|
|
||||||
<key>Optional</key>
|
|
||||||
<true/>
|
|
||||||
<key>Types</key>
|
|
||||||
<array>
|
|
||||||
<string>com.apple.cocoa.string</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
<key>AMActionVersion</key>
|
|
||||||
<string>2.0.3</string>
|
|
||||||
<key>AMApplication</key>
|
|
||||||
<array>
|
|
||||||
<string>Automator</string>
|
|
||||||
</array>
|
|
||||||
<key>AMParameterProperties</key>
|
|
||||||
<dict>
|
|
||||||
<key>COMMAND_STRING</key>
|
|
||||||
<dict/>
|
|
||||||
<key>CheckedForUserDefaultShell</key>
|
|
||||||
<dict/>
|
|
||||||
<key>inputMethod</key>
|
|
||||||
<dict/>
|
|
||||||
<key>shell</key>
|
|
||||||
<dict/>
|
|
||||||
<key>source</key>
|
|
||||||
<dict/>
|
|
||||||
</dict>
|
|
||||||
<key>AMProvides</key>
|
|
||||||
<dict>
|
|
||||||
<key>Container</key>
|
|
||||||
<string>List</string>
|
|
||||||
<key>Types</key>
|
|
||||||
<array>
|
|
||||||
<string>com.apple.cocoa.string</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
<key>ActionBundlePath</key>
|
|
||||||
<string>/System/Library/Automator/Run Shell Script.action</string>
|
|
||||||
<key>ActionName</key>
|
|
||||||
<string>Run Shell Script</string>
|
|
||||||
<key>ActionParameters</key>
|
|
||||||
<dict>
|
|
||||||
<key>COMMAND_STRING</key>
|
|
||||||
<string>import subprocess, plistlib, sys, os, shlex
|
|
||||||
|
|
||||||
class Disk:
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.diskutil = "diskutil" # self.get_diskutil()
|
|
||||||
self.os_version = self.run(["sw_vers", "-productVersion"])[0].strip()
|
|
||||||
self.sudo_mount_version = "10.13.6"
|
|
||||||
self.sudo_mount_types = ["efi"]
|
|
||||||
self.apfs = {}
|
|
||||||
self._update_disks()
|
|
||||||
|
|
||||||
def run(self, comm, shell = False):
|
|
||||||
c = None
|
|
||||||
try:
|
|
||||||
if shell and type(comm) is list: comm = " ".join(shlex.quote(x) for x in comm)
|
|
||||||
if not shell and type(comm) is str: comm = shlex.split(comm)
|
|
||||||
p = subprocess.Popen(comm, shell=shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
||||||
c = p.communicate()
|
|
||||||
except:
|
|
||||||
if c == None: return ("", "Command not found!", 1)
|
|
||||||
return (self._get_str(c[0]), self._get_str(c[1]), p.returncode)
|
|
||||||
|
|
||||||
def _get_str(self, val):
|
|
||||||
# Helper method to return a string value based on input type
|
|
||||||
if sys.version_info >= (3,0) and isinstance(val, bytes): return val.encode("utf-8")
|
|
||||||
return val
|
|
||||||
|
|
||||||
def _get_plist(self, s):
|
|
||||||
p = {}
|
|
||||||
try:
|
|
||||||
if sys.version_info >= (3, 0):
|
|
||||||
p = plistlib.loads(s.encode("utf-8"))
|
|
||||||
else:
|
|
||||||
# We avoid using readPlistFromString() as that uses
|
|
||||||
# cStringIO and fails when Unicode strings are detected
|
|
||||||
# Don't subclass - keep the parser local
|
|
||||||
from xml.parsers.expat import ParserCreate
|
|
||||||
# Create a new PlistParser object - then we need to set up
|
|
||||||
# the values and parse.
|
|
||||||
pa = plistlib.PlistParser()
|
|
||||||
# We also monkey patch this to encode unicode as utf-8
|
|
||||||
def end_string():
|
|
||||||
d = pa.getData()
|
|
||||||
if isinstance(d,unicode):
|
|
||||||
d = d.encode("utf-8")
|
|
||||||
pa.addObject(d)
|
|
||||||
pa.end_string = end_string
|
|
||||||
parser = ParserCreate()
|
|
||||||
parser.StartElementHandler = pa.handleBeginElement
|
|
||||||
parser.EndElementHandler = pa.handleEndElement
|
|
||||||
parser.CharacterDataHandler = pa.handleData
|
|
||||||
if isinstance(s, unicode):
|
|
||||||
# Encode unicode -> string; use utf-8 for safety
|
|
||||||
s = s.encode("utf-8")
|
|
||||||
# Parse the string
|
|
||||||
parser.Parse(s, 1)
|
|
||||||
p = pa.root
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
return p
|
|
||||||
|
|
||||||
def update(self):
|
|
||||||
self._update_disks()
|
|
||||||
|
|
||||||
def _update_disks(self):
|
|
||||||
self.disks = self.get_disks()
|
|
||||||
self.disk_text = self.get_disk_text()
|
|
||||||
self.apfs = self.get_apfs() if self.os_version >= "10.12" else {}
|
|
||||||
|
|
||||||
def get_disks(self):
|
|
||||||
# Returns a dictionary object of connected disks
|
|
||||||
disk_list = self.run([self.diskutil, "list", "-plist"])[0]
|
|
||||||
return self._get_plist(disk_list)
|
|
||||||
|
|
||||||
def get_disk_text(self):
|
|
||||||
# Returns plain text listing connected disks
|
|
||||||
return self.run([self.diskutil, "list"])[0]
|
|
||||||
|
|
||||||
def get_apfs(self):
|
|
||||||
# Returns a dictionary object of apfs disks
|
|
||||||
output = self.run("echo y | " + self.diskutil + " apfs list -plist", True)
|
|
||||||
if not output[2] == 0: return {} # Error getting apfs info - return an empty dict
|
|
||||||
disk_list = output[0]
|
|
||||||
p_list = disk_list.split("<?xml")
|
|
||||||
if len(p_list) > 1: disk_list = "<?xml" + p_list[-1] # We had text before the start - get only the plist info
|
|
||||||
return self._get_plist(disk_list)
|
|
||||||
|
|
||||||
def is_apfs(self, disk):
|
|
||||||
disk_id = self.get_identifier(disk)
|
|
||||||
if not disk_id: return None
|
|
||||||
# Takes a disk identifier, and returns whether or not it's apfs
|
|
||||||
for d in self.disks.get("AllDisksAndPartitions", []):
|
|
||||||
if not "APFSVolumes" in d: continue
|
|
||||||
if d.get("DeviceIdentifier", "").lower() == disk_id.lower():
|
|
||||||
return True
|
|
||||||
for a in d.get("APFSVolumes", []):
|
|
||||||
if a.get("DeviceIdentifier", "").lower() == disk_id.lower():
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def is_apfs_container(self, disk):
|
|
||||||
disk_id = self.get_identifier(disk)
|
|
||||||
if not disk_id: return None
|
|
||||||
# Takes a disk identifier, and returns whether or not that specific
|
|
||||||
# disk/volume is an APFS Container
|
|
||||||
for d in self.disks.get("AllDisksAndPartitions", []):
|
|
||||||
# Only check partitions
|
|
||||||
for p in d.get("Partitions", []):
|
|
||||||
if disk_id.lower() == p.get("DeviceIdentifier", "").lower():
|
|
||||||
return p.get("Content", "").lower() == "apple_apfs"
|
|
||||||
return False
|
|
||||||
|
|
||||||
def is_cs_container(self, disk):
|
|
||||||
disk_id = self.get_identifier(disk)
|
|
||||||
if not disk_id: return None
|
|
||||||
# Takes a disk identifier, and returns whether or not that specific
|
|
||||||
# disk/volume is an CoreStorage Container
|
|
||||||
for d in self.disks.get("AllDisksAndPartitions", []):
|
|
||||||
# Only check partitions
|
|
||||||
for p in d.get("Partitions", []):
|
|
||||||
if disk_id.lower() == p.get("DeviceIdentifier", "").lower():
|
|
||||||
return p.get("Content", "").lower() == "apple_corestorage"
|
|
||||||
return False
|
|
||||||
|
|
||||||
def is_core_storage(self, disk):
|
|
||||||
disk_id = self.get_identifier(disk)
|
|
||||||
if not disk_id: return None
|
|
||||||
if self._get_physical_disk(disk_id, "Logical Volume on "): return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_identifier(self, disk):
|
|
||||||
# Should be able to take a mount point, disk name, or disk identifier,
|
|
||||||
# and return the disk's identifier
|
|
||||||
# Iterate!!
|
|
||||||
if not disk or not len(self._get_str(disk)): return None
|
|
||||||
disk = disk.lower()
|
|
||||||
if disk.startswith("/dev/r"): disk = disk[len("/dev/r"):]
|
|
||||||
elif disk.startswith("/dev/"): disk = disk[len("/dev/"):]
|
|
||||||
if disk in self.disks.get("AllDisks", []): return disk
|
|
||||||
for d in self.disks.get("AllDisksAndPartitions", []):
|
|
||||||
for a in d.get("APFSVolumes", []):
|
|
||||||
if disk in [ a.get(x, "").lower() for x in ["DeviceIdentifier", "VolumeName", "VolumeUUID", "DiskUUID", "MountPoint"] ]:
|
|
||||||
return a.get("DeviceIdentifier", None)
|
|
||||||
for a in d.get("Partitions", []):
|
|
||||||
if disk in [ a.get(x, "").lower() for x in ["DeviceIdentifier", "VolumeName", "VolumeUUID", "DiskUUID", "MountPoint"] ]:
|
|
||||||
return a.get("DeviceIdentifier", None)
|
|
||||||
# At this point, we didn't find it
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_top_identifier(self, disk):
|
|
||||||
disk_id = self.get_identifier(disk)
|
|
||||||
if not disk_id: return None
|
|
||||||
return disk_id.replace("disk", "didk").split("s")[0].replace("didk", "disk")
|
|
||||||
|
|
||||||
def _get_physical_disk(self, disk, search_term):
|
|
||||||
# Change disk0s1 to disk0
|
|
||||||
our_disk = self.get_top_identifier(disk)
|
|
||||||
our_term = "/dev/" + our_disk
|
|
||||||
found_disk = False
|
|
||||||
our_text = ""
|
|
||||||
for line in self.disk_text.split("\n"):
|
|
||||||
if line.lower().startswith(our_term):
|
|
||||||
found_disk = True
|
|
||||||
continue
|
|
||||||
if not found_disk: continue
|
|
||||||
if line.lower().startswith("/dev/disk"):
|
|
||||||
# At the next disk - bail
|
|
||||||
break
|
|
||||||
if search_term.lower() in line.lower():
|
|
||||||
our_text = line
|
|
||||||
break
|
|
||||||
if not len(our_text): return None # Nothing found
|
|
||||||
our_stores = "".join(our_text.strip().split(search_term)[1:]).split(" ,")
|
|
||||||
if not len(our_stores): return None
|
|
||||||
for store in our_stores:
|
|
||||||
efi = self.get_efi(store)
|
|
||||||
if efi: return store
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_physical_store(self, disk):
|
|
||||||
# Returns the physical store containing the EFI
|
|
||||||
disk_id = self.get_identifier(disk)
|
|
||||||
if not disk_id or not self.is_apfs(disk_id): return None
|
|
||||||
return self._get_physical_disk(disk_id, "Physical Store ")
|
|
||||||
|
|
||||||
def get_core_storage_pv(self, disk):
|
|
||||||
# Returns the core storage physical volume containing the EFI
|
|
||||||
disk_id = self.get_identifier(disk)
|
|
||||||
if not disk_id or not self.is_core_storage(disk_id): return None
|
|
||||||
return self._get_physical_disk(disk_id, "Logical Volume on ")
|
|
||||||
|
|
||||||
def get_parent(self, disk):
|
|
||||||
# Disk can be a mount point, disk name, or disk identifier
|
|
||||||
disk_id = self.get_identifier(disk)
|
|
||||||
if self.is_apfs(disk_id): disk_id = self.get_physical_store(disk_id)
|
|
||||||
elif self.is_core_storage(disk_id): disk_id = self.get_core_storage_pv(disk_id)
|
|
||||||
if not disk_id: return None
|
|
||||||
if self.is_apfs(disk_id):
|
|
||||||
# We have apfs - let's get the container ref
|
|
||||||
for a in self.apfs.get("Containers", []):
|
|
||||||
# Check if it's the whole container
|
|
||||||
if a.get("ContainerReference", "").lower() == disk_id.lower():
|
|
||||||
return a["ContainerReference"]
|
|
||||||
# Check through each volume and return the parent's container ref
|
|
||||||
for v in a.get("Volumes", []):
|
|
||||||
if v.get("DeviceIdentifier", "").lower() == disk_id.lower():
|
|
||||||
return a.get("ContainerReference", None)
|
|
||||||
else:
|
|
||||||
# Not apfs - go through all volumes and whole disks
|
|
||||||
for d in self.disks.get("AllDisksAndPartitions", []):
|
|
||||||
if d.get("DeviceIdentifier", "").lower() == disk_id.lower():
|
|
||||||
return d["DeviceIdentifier"]
|
|
||||||
for p in d.get("Partitions", []):
|
|
||||||
if p.get("DeviceIdentifier", "").lower() == disk_id.lower():
|
|
||||||
return d["DeviceIdentifier"]
|
|
||||||
# Didn't find anything
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_efi(self, disk):
|
|
||||||
disk_id = self.get_parent(self.get_identifier(disk))
|
|
||||||
if not disk_id: return None
|
|
||||||
# At this point - we should have the parent
|
|
||||||
for d in self.disks["AllDisksAndPartitions"]:
|
|
||||||
if d.get("DeviceIdentifier", "").lower() == disk_id.lower():
|
|
||||||
# Found our disk
|
|
||||||
for p in d.get("Partitions", []):
|
|
||||||
if p.get("Content", "").lower() == "efi":
|
|
||||||
return p.get("DeviceIdentifier", None)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def needs_sudo(self, disk_id = None):
|
|
||||||
content = "EFI" # Default to EFI content
|
|
||||||
if disk_id: content = self.get_content(disk_id)
|
|
||||||
return self.os_version >= self.sudo_mount_version and content.lower() in self.sudo_mount_types
|
|
||||||
|
|
||||||
def is_mounted(self, disk):
|
|
||||||
disk_id = self.get_identifier(disk)
|
|
||||||
if not disk_id: return None
|
|
||||||
m = self.get_mount_point(disk_id)
|
|
||||||
return (m != None and len(m)>0)
|
|
||||||
|
|
||||||
def _get_value(self, disk, field, default = None, apfs_only = False):
|
|
||||||
disk_id = self.get_identifier(disk)
|
|
||||||
if not disk_id:
|
|
||||||
return None
|
|
||||||
# Takes a disk identifier, and returns the requested value
|
|
||||||
for d in self.disks.get("AllDisksAndPartitions", []):
|
|
||||||
for a in d.get("APFSVolumes", []):
|
|
||||||
if a.get("DeviceIdentifier", "").lower() == disk_id.lower():
|
|
||||||
return a.get(field, default)
|
|
||||||
if apfs_only:
|
|
||||||
# Skip looking at regular partitions
|
|
||||||
continue
|
|
||||||
if d.get("DeviceIdentifier", "").lower() == disk_id.lower():
|
|
||||||
return d.get(field, default)
|
|
||||||
for a in d.get("Partitions", []):
|
|
||||||
if a.get("DeviceIdentifier", "").lower() == disk_id.lower():
|
|
||||||
return a.get(field, default)
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Getter methods
|
|
||||||
def get_content(self, disk):
|
|
||||||
return self._get_value(disk, "Content")
|
|
||||||
|
|
||||||
def get_mount_point(self, disk):
|
|
||||||
return self._get_value(disk, "MountPoint")
|
|
||||||
|
|
||||||
def get_volume_name(self, disk):
|
|
||||||
return self._get_value(disk, "VolumeName")
|
|
||||||
|
|
||||||
def open_mount_point(self, disk, new_window = False):
|
|
||||||
disk_id = self.get_identifier(disk)
|
|
||||||
if not disk_id: return None
|
|
||||||
mount = self.get_mount_point(disk_id)
|
|
||||||
if not mount: return None
|
|
||||||
out = self.run(["open", mount])
|
|
||||||
return out[2] == 0
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
d = Disk()
|
|
||||||
# Gather the args
|
|
||||||
errors = []
|
|
||||||
args = []
|
|
||||||
for x in sys.argv[1:]:
|
|
||||||
if x == "/":
|
|
||||||
args.append(x)
|
|
||||||
continue
|
|
||||||
if not x.lower().startswith("/volumes/"):
|
|
||||||
errors.append("'{}' is not a volume.".format(x))
|
|
||||||
continue
|
|
||||||
if x.endswith("/"):
|
|
||||||
x = x[:-1]
|
|
||||||
if len(x.split("/")) > 3:
|
|
||||||
# Too nested - not a volume
|
|
||||||
errors.append("'{}' is not a volume.".format(x))
|
|
||||||
continue
|
|
||||||
if not os.path.exists(x):
|
|
||||||
# Doesn't exist, skip it
|
|
||||||
errors.append("'{}' does not exist.".format(x))
|
|
||||||
continue
|
|
||||||
args.append(x)
|
|
||||||
mount_list = []
|
|
||||||
needs_sudo = d.needs_sudo()
|
|
||||||
for x in args:
|
|
||||||
name = d.get_volume_name(x)
|
|
||||||
if not name: name = "Untitled"
|
|
||||||
name = name.replace('"','\\"') # Escape double quotes in names
|
|
||||||
efi = d.get_efi(x)
|
|
||||||
if efi: mount_list.append((efi,name,d.is_mounted(efi),"diskutil mount {}".format(efi)))
|
|
||||||
else: errors.append("'{}' has no ESP.".format(name))
|
|
||||||
if len(mount_list):
|
|
||||||
# We have something to mount
|
|
||||||
efis = [x[-1] for x in mount_list if not x[2]] # Only mount those that aren't mounted
|
|
||||||
names = [x[1] for x in mount_list if not x[2]]
|
|
||||||
if len(efis): # We have something to mount here
|
|
||||||
command = "do shell script \"{}\" with prompt \"MountEFI would like to mount the ESP{} on {}\"{}".format(
|
|
||||||
"; ".join(efis),
|
|
||||||
"s" if len(names) > 1 else "",
|
|
||||||
", ".join(names),
|
|
||||||
" with administrator privileges" if needs_sudo else "")
|
|
||||||
o,e,r = d.run(["osascript","-e",command])
|
|
||||||
if r > 0 and len(e.strip()) and e.strip().lower().endswith("(-128)"): exit() # User canceled, bail
|
|
||||||
# Update the disks
|
|
||||||
d.update()
|
|
||||||
# Walk the mounts and find out which aren't mounted
|
|
||||||
for efi,name,mounted,comm in mount_list:
|
|
||||||
mounted_at = d.get_mount_point(efi)
|
|
||||||
if mounted_at: d.open_mount_point(mounted_at)
|
|
||||||
else: errors.append("ESP for '{}' failed to mount.".format(name))
|
|
||||||
else:
|
|
||||||
errors.append("No disks with ESPs selected.")
|
|
||||||
if len(errors):
|
|
||||||
# Display our errors before we leave
|
|
||||||
d.run(["osascript","-e","display dialog \"{}\" buttons {{\"OK\"}} default button \"OK\" with icon caution".format("\n".join(errors))])</string>
|
|
||||||
<key>CheckedForUserDefaultShell</key>
|
|
||||||
<true/>
|
|
||||||
<key>inputMethod</key>
|
|
||||||
<integer>1</integer>
|
|
||||||
<key>shell</key>
|
|
||||||
<string>/usr/bin/python</string>
|
|
||||||
<key>source</key>
|
|
||||||
<string></string>
|
|
||||||
</dict>
|
|
||||||
<key>BundleIdentifier</key>
|
|
||||||
<string>com.apple.RunShellScript</string>
|
|
||||||
<key>CFBundleVersion</key>
|
|
||||||
<string>2.0.3</string>
|
|
||||||
<key>CanShowSelectedItemsWhenRun</key>
|
|
||||||
<false/>
|
|
||||||
<key>CanShowWhenRun</key>
|
|
||||||
<true/>
|
|
||||||
<key>Category</key>
|
|
||||||
<array>
|
|
||||||
<string>AMCategoryUtilities</string>
|
|
||||||
</array>
|
|
||||||
<key>Class Name</key>
|
|
||||||
<string>RunShellScriptAction</string>
|
|
||||||
<key>InputUUID</key>
|
|
||||||
<string>1EF3F5D2-601A-45CB-A0F3-2073CDF75D6D</string>
|
|
||||||
<key>Keywords</key>
|
|
||||||
<array>
|
|
||||||
<string>Shell</string>
|
|
||||||
<string>Script</string>
|
|
||||||
<string>Command</string>
|
|
||||||
<string>Run</string>
|
|
||||||
<string>Unix</string>
|
|
||||||
</array>
|
|
||||||
<key>OutputUUID</key>
|
|
||||||
<string>A943503D-31E8-4E6D-91EB-046A35336942</string>
|
|
||||||
<key>UUID</key>
|
|
||||||
<string>9AE9FCB9-5186-4567-A3FB-08D7175D30F6</string>
|
|
||||||
<key>UnlocalizedApplications</key>
|
|
||||||
<array>
|
|
||||||
<string>Automator</string>
|
|
||||||
</array>
|
|
||||||
<key>arguments</key>
|
|
||||||
<dict>
|
|
||||||
<key>0</key>
|
|
||||||
<dict>
|
|
||||||
<key>default value</key>
|
|
||||||
<integer>0</integer>
|
|
||||||
<key>name</key>
|
|
||||||
<string>inputMethod</string>
|
|
||||||
<key>required</key>
|
|
||||||
<string>0</string>
|
|
||||||
<key>type</key>
|
|
||||||
<string>0</string>
|
|
||||||
<key>uuid</key>
|
|
||||||
<string>0</string>
|
|
||||||
</dict>
|
|
||||||
<key>1</key>
|
|
||||||
<dict>
|
|
||||||
<key>default value</key>
|
|
||||||
<false/>
|
|
||||||
<key>name</key>
|
|
||||||
<string>CheckedForUserDefaultShell</string>
|
|
||||||
<key>required</key>
|
|
||||||
<string>0</string>
|
|
||||||
<key>type</key>
|
|
||||||
<string>0</string>
|
|
||||||
<key>uuid</key>
|
|
||||||
<string>1</string>
|
|
||||||
</dict>
|
|
||||||
<key>2</key>
|
|
||||||
<dict>
|
|
||||||
<key>default value</key>
|
|
||||||
<string></string>
|
|
||||||
<key>name</key>
|
|
||||||
<string>source</string>
|
|
||||||
<key>required</key>
|
|
||||||
<string>0</string>
|
|
||||||
<key>type</key>
|
|
||||||
<string>0</string>
|
|
||||||
<key>uuid</key>
|
|
||||||
<string>2</string>
|
|
||||||
</dict>
|
|
||||||
<key>3</key>
|
|
||||||
<dict>
|
|
||||||
<key>default value</key>
|
|
||||||
<string></string>
|
|
||||||
<key>name</key>
|
|
||||||
<string>COMMAND_STRING</string>
|
|
||||||
<key>required</key>
|
|
||||||
<string>0</string>
|
|
||||||
<key>type</key>
|
|
||||||
<string>0</string>
|
|
||||||
<key>uuid</key>
|
|
||||||
<string>3</string>
|
|
||||||
</dict>
|
|
||||||
<key>4</key>
|
|
||||||
<dict>
|
|
||||||
<key>default value</key>
|
|
||||||
<string>/bin/sh</string>
|
|
||||||
<key>name</key>
|
|
||||||
<string>shell</string>
|
|
||||||
<key>required</key>
|
|
||||||
<string>0</string>
|
|
||||||
<key>type</key>
|
|
||||||
<string>0</string>
|
|
||||||
<key>uuid</key>
|
|
||||||
<string>4</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<key>isViewVisible</key>
|
|
||||||
<true/>
|
|
||||||
<key>location</key>
|
|
||||||
<string>784.000000:1486.000000</string>
|
|
||||||
<key>nibPath</key>
|
|
||||||
<string>/System/Library/Automator/Run Shell Script.action/Contents/Resources/Base.lproj/main.nib</string>
|
|
||||||
</dict>
|
|
||||||
<key>isViewVisible</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>connectors</key>
|
<key>connectors</key>
|
||||||
<dict>
|
<dict/>
|
||||||
<key>EFDA46C2-B5B5-4365-A046-D576516A3332</key>
|
|
||||||
<dict>
|
|
||||||
<key>from</key>
|
|
||||||
<string>D6C847AF-3800-48CD-B4BA-DBFB7C59F37A - D6C847AF-3800-48CD-B4BA-DBFB7C59F37A</string>
|
|
||||||
<key>to</key>
|
|
||||||
<string>9AE9FCB9-5186-4567-A3FB-08D7175D30F6 - 9AE9FCB9-5186-4567-A3FB-08D7175D30F6</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<key>workflowMetaData</key>
|
<key>workflowMetaData</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>applicationBundleIDsByPath</key>
|
<key>applicationBundleIDsByPath</key>
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ Utility to validate whether a `config.plist` matches requirements and convention
|
|||||||
- Entry[N]->BundlePath: Filename should have `.kext` suffix.
|
- Entry[N]->BundlePath: Filename should have `.kext` suffix.
|
||||||
- Entry[N]->PlistPath: Filename should have `.plist` suffix.
|
- Entry[N]->PlistPath: Filename should have `.plist` suffix.
|
||||||
- Entry[N]: If `Lilu.kext` is used, `DisableLinkeditJettison` should be enabled in `Kernel->Quirks`.
|
- Entry[N]: If `Lilu.kext` is used, `DisableLinkeditJettison` should be enabled in `Kernel->Quirks`.
|
||||||
|
- `BrcmFirmwareRepo.kext` must not be injected by OpenCore.
|
||||||
- For some known kexts, their `BundlePath`, `ExecutablePath`, and `PlistPath` must match against each other. Current list of rules can be found [here](https://github.com/acidanthera/OpenCorePkg/blob/master/Utilities/ocvalidate/KextInfo.c).
|
- For some known kexts, their `BundlePath`, `ExecutablePath`, and `PlistPath` must match against each other. Current list of rules can be found [here](https://github.com/acidanthera/OpenCorePkg/blob/master/Utilities/ocvalidate/KextInfo.c).
|
||||||
- Plugin kext must be placed after parent kext. For example, [plugins of Lilu](https://github.com/acidanthera/Lilu/blob/master/KnownPlugins.md) must be placed after `Lilu.kext`.
|
- Plugin kext must be placed after parent kext. For example, [plugins of Lilu](https://github.com/acidanthera/Lilu/blob/master/KnownPlugins.md) must be placed after `Lilu.kext`.
|
||||||
#### Delete
|
#### Delete
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user