mirror of
https://github.com/lucannez64/darling-lol.git
synced 2026-10-26 19:47:42 +00:00
Report hardware and software overviews from IOKit and sysctl in system_profiler
system_profiler now answers SPHardwareDataType and SPSoftwareDataType (and lists them in -listDataTypes). Model, serial number, platform UUID and provisioning UDID come from IOKit's IOPlatformExpertDevice via ioreg; CPU, cache and memory from sysctl; the OS version from sw_vers and uname. A field the guest does not provide is left out. #61 instead read a separate darling-smbios.conf written by a new generator (gen-darling-smbios.py), with hardcoded fallbacks (an Intel Core i9, macOS 14.7, SIP enabled). iokitd reads darling-platform.plist, so system_profiler and IOKit could report different serials and models on the same prefix, and the generator overwrote identities imported with configure-darling-platform.py. Reading the guest's own APIs keeps a single source of truth: configure-darling-platform.py remains the only way to set or provision an identity, and it is still never run by install or launch. tests/test_system_profiler.py runs the script under sh with stub guest commands and runs in CI. Split from #61. Co-authored-by: DevWebeloper <[email protected]> Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
603b5a168e
commit
34c3e4ae9f
4 files changed
+193
-3
No files matched your search
@@ -17,3 +17,4 @@ jobs:
|
||||
- run: python -m unittest discover -s tests -p 'test_hidden_riot_recovery.py'
|
||||
- run: python -m unittest discover -s tests -p 'test_desktop_run.py'
|
||||
- run: python -m unittest discover -s tests -p 'test_urlsession_runner.py'
|
||||
- run: python -m unittest discover -s tests -p 'test_system_profiler.py'
|
||||
@@ -134,3 +134,15 @@ a per-prefix lock. Repeated writes preserve identifiers and unchanged files.
|
||||
Previewing a fresh identity generates a sample; only `--write` makes it stable.
|
||||
To restore host-derived defaults after provisioning, remove both identity files
|
||||
while stopped, so a later provisioning run cannot reimport the legacy copy.
|
||||
|
||||
## system_profiler
|
||||
|
||||
The prefix's `/usr/sbin/system_profiler` (installed by
|
||||
`tools/install-darling-system-profiler.py`) answers `SPHardwareDataType` and
|
||||
`SPSoftwareDataType` from the running guest: model, serial number, platform UUID
|
||||
and provisioning UDID from IOKit's `IOPlatformExpertDevice` (via `ioreg`), CPU,
|
||||
cache and memory from `sysctl`, and the OS version from `sw_vers` and `uname`. It
|
||||
keeps no identity of its own, so it reports the same values as IOKit, including any
|
||||
override saved by this tool. A field the guest does not provide is left out.
|
||||
`python3 -m unittest tests.test_system_profiler` checks the output with stub
|
||||
commands.
|
||||
@@ -0,0 +1,97 @@
|
||||
"""system_profiler hardware/software overviews, with stub guest commands; never starts Darling."""
|
||||
import os
|
||||
from pathlib import Path
|
||||
import plistlib
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SCRIPT = ROOT / 'tools/darling-system_profiler.sh'
|
||||
|
||||
IOREG = '''+-o MacBookPro16,1 <class IOPlatformExpertDevice, id 0x100000110, registered, matched, active, busy 0 (0 ms), retain 30>
|
||||
{
|
||||
"IOPlatformSerialNumber" = "USER-SUPPLIED"
|
||||
"model" = <"MacBookPro16,1">
|
||||
"manufacturer" = <"Apple Inc.">
|
||||
"IOPlatformUUID" = "8E1F2A3B-4C5D-4E6F-8A7B-9C0D1E2F3A4B"
|
||||
"provisioning_UDID" = "8E1F2A3B-4C5D-4E6F-8A7B-9C0D1E2F3A4B"
|
||||
"ROM" = <0a1b2c3d4e5f>
|
||||
}
|
||||
'''
|
||||
SYSCTL = {
|
||||
'hw.physicalcpu': '8', 'hw.packages': '1', 'hw.memsize': str(32 << 30),
|
||||
'machdep.cpu.brand_string': 'AMD Ryzen 7 5800X 8-Core Processor & more',
|
||||
'hw.l2cachesize': str(512 << 10), 'hw.l3cachesize': str(32 << 20), 'hw.cpufrequency': '3800000000',
|
||||
}
|
||||
|
||||
|
||||
class SystemProfilerTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.folder = tempfile.TemporaryDirectory()
|
||||
self.bin = Path(self.folder.name)
|
||||
self.stub('sysctl', 'import sys\nvalues = %r\nname = sys.argv[-1]\n'
|
||||
'if name not in values: sys.exit(1)\nprint(values[name])' % SYSCTL)
|
||||
self.stub('sw_vers', 'import sys\nprint({"-productName": "macOS", "-productVersion": "11.7.4", '
|
||||
'"-buildVersion": "20G1120"}[sys.argv[1]])')
|
||||
self.stub('uname', 'print("Darwin 20.6.0")')
|
||||
self.stub('hostname', 'print("darling")')
|
||||
self.stub('ioreg', 'import sys\nsys.stdout.write(%r)' % IOREG)
|
||||
|
||||
def tearDown(self):
|
||||
self.folder.cleanup()
|
||||
|
||||
def stub(self, name, body):
|
||||
path = self.bin / name
|
||||
path.write_text('#!%s\n%s\n' % (sys.executable, body))
|
||||
path.chmod(0o755)
|
||||
|
||||
def run_profiler(self, *args):
|
||||
env = {'PATH': '%s:%s' % (self.bin, os.environ['PATH']), 'USER': 'player'}
|
||||
return subprocess.run(['sh', str(SCRIPT), *args], env=env, check=True, capture_output=True, text=True).stdout
|
||||
|
||||
def xml_items(self, data_type):
|
||||
report = plistlib.loads(self.run_profiler('-xml', data_type).encode())
|
||||
self.assertEqual(report[0]['_dataType'], data_type)
|
||||
return report[0]['_items'][0]
|
||||
|
||||
def test_hardware_reports_iokit_identity_and_sysctl_values(self):
|
||||
item = self.xml_items('SPHardwareDataType')
|
||||
self.assertEqual(item, {
|
||||
'_name': 'hardware_overview', 'machine_name': 'MacBook Pro', 'machine_model': 'MacBookPro16,1',
|
||||
'cpu_type': 'AMD Ryzen 7 5800X 8-Core Processor & more', 'current_processor_speed': '3.8 GHz',
|
||||
'packages': 1, 'number_processors': 8, 'l2_cache_core': '512 KB', 'l3_cache': '32 MB',
|
||||
'physical_memory': '32 GB', 'serial_number': 'USER-SUPPLIED',
|
||||
'platform_UUID': '8E1F2A3B-4C5D-4E6F-8A7B-9C0D1E2F3A4B',
|
||||
'provisioning_UDID': '8E1F2A3B-4C5D-4E6F-8A7B-9C0D1E2F3A4B',
|
||||
})
|
||||
text = self.run_profiler('SPHardwareDataType')
|
||||
self.assertIn(' Serial Number (system): USER-SUPPLIED\n', text)
|
||||
self.assertIn(' Model Name: MacBook Pro\n', text)
|
||||
|
||||
def test_missing_iokit_and_sysctl_values_are_omitted(self):
|
||||
self.stub('ioreg', 'import sys\nsys.exit(1)')
|
||||
self.stub('sysctl', 'import sys\nsys.exit(1)')
|
||||
self.assertEqual(self.xml_items('SPHardwareDataType'), {'_name': 'hardware_overview'})
|
||||
self.assertNotIn('Serial Number', self.run_profiler('SPHardwareDataType'))
|
||||
|
||||
def test_software_overview(self):
|
||||
item = self.xml_items('SPSoftwareDataType')
|
||||
self.assertEqual(item, {
|
||||
'_name': 'os_overview', 'os_version': 'macOS 11.7.4 (20G1120)', 'kernel_version': 'Darwin 20.6.0',
|
||||
'boot_volume': 'Macintosh HD', 'boot_mode': 'normal_boot', 'local_host_name': 'darling',
|
||||
'user_name': 'player',
|
||||
})
|
||||
self.assertIn(' Boot Mode: Normal\n', self.run_profiler('SPSoftwareDataType'))
|
||||
|
||||
def test_listed_and_default_types(self):
|
||||
listed = self.run_profiler('-listDataTypes')
|
||||
self.assertIn('SPHardwareDataType', listed)
|
||||
self.assertIn('SPSoftwareDataType', listed)
|
||||
types = [entry['_dataType'] for entry in plistlib.loads(self.run_profiler('-xml').encode())]
|
||||
self.assertEqual(types[:2], ['SPHardwareDataType', 'SPSoftwareDataType'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -1,6 +1,8 @@
|
||||
#!/bin/sh
|
||||
# system_profiler for the Darling prefix: reports the host's real display and
|
||||
# network hardware for the data types applications query at startup.
|
||||
# network hardware for the data types applications query at startup. The hardware
|
||||
# and software overviews come from the guest's own IOKit, sysctl and sw_vers, so they
|
||||
# match what other APIs report (including a configure-darling-platform.py override).
|
||||
# Installed as /usr/sbin/system_profiler by tools/install-darling-system-profiler.py.
|
||||
host=/Volumes/SystemRoot
|
||||
xml=0
|
||||
@@ -9,12 +11,12 @@ while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
-xml) xml=1;;
|
||||
-detailLevel|-timeout) shift;;
|
||||
-listDataTypes) printf 'Available Datatypes:\nSPAirPortDataType\nSPDisplaysDataType\nSPEthernetDataType\n'; exit 0;;
|
||||
-listDataTypes) printf 'Available Datatypes:\nSPAirPortDataType\nSPDisplaysDataType\nSPEthernetDataType\nSPHardwareDataType\nSPSoftwareDataType\n'; exit 0;;
|
||||
SP*DataType) types="$types $1";;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
[ -n "$types" ] || types=" SPDisplaysDataType SPEthernetDataType SPAirPortDataType"
|
||||
[ -n "$types" ] || types=" SPHardwareDataType SPSoftwareDataType SPDisplaysDataType SPEthernetDataType SPAirPortDataType"
|
||||
|
||||
escape() { sed -e 's/&/\&/g' -e 's/</\</g' -e 's/>/\>/g'; }
|
||||
|
||||
@@ -46,6 +48,80 @@ displays() { # one line per GPU: vendor|device|vram MB or empty|name
|
||||
done
|
||||
}
|
||||
|
||||
load_platform() { # IOPlatformExpertDevice string properties, "key|value" per line
|
||||
# String values print as "value", NUL-terminated data as <"value">; other data is skipped.
|
||||
platform_properties=$(ioreg -rd1 -c IOPlatformExpertDevice 2>/dev/null |
|
||||
sed -n 's/^[ |]*"\([^"]*\)" = <\{0,1\}"\(.*\)">\{0,1\}$/\1|\2/p')
|
||||
}
|
||||
platform() {
|
||||
printf '%s\n' "$platform_properties" | awk -v key="$1" 'index($0, key "|") == 1 { print substr($0, length(key) + 2); exit }'
|
||||
}
|
||||
|
||||
machine_name() { # model identifier -> marketing family, as in About This Mac
|
||||
case "$1" in
|
||||
MacBookPro*) echo 'MacBook Pro';; MacBookAir*) echo 'MacBook Air';; MacBook*) echo 'MacBook';;
|
||||
iMacPro*) echo 'iMac Pro';; iMac*) echo 'iMac';; Macmini*) echo 'Mac mini';; MacPro*) echo 'Mac Pro';;
|
||||
*) echo 'Mac';;
|
||||
esac
|
||||
}
|
||||
|
||||
hardware() { # label|xml key|xml type|value for each known field
|
||||
load_platform
|
||||
model=$(platform model)
|
||||
cores=$(sysctl -n hw.physicalcpu 2>/dev/null)
|
||||
memory=$(sysctl -n hw.memsize 2>/dev/null | awk '$1 > 0 { printf "%d GB", $1 / 1073741824 + 0.5 }')
|
||||
speed=$(sysctl -n hw.cpufrequency 2>/dev/null | awk '$1 > 0 { printf "%.1f GHz", $1 / 1e9 }')
|
||||
l2=$(sysctl -n hw.l2cachesize 2>/dev/null | awk '$1 > 0 { printf "%d KB", $1 / 1024 }')
|
||||
l3=$(sysctl -n hw.l3cachesize 2>/dev/null | awk '$1 > 0 { printf "%d MB", $1 / 1048576 }')
|
||||
echo "Model Name|machine_name|string|${model:+$(machine_name "$model")}"
|
||||
echo "Model Identifier|machine_model|string|$model"
|
||||
echo "Processor Name|cpu_type|string|$(sysctl -n machdep.cpu.brand_string 2>/dev/null)"
|
||||
echo "Processor Speed|current_processor_speed|string|$speed"
|
||||
echo "Number of Processors|packages|integer|$(sysctl -n hw.packages 2>/dev/null)"
|
||||
echo "Total Number of Cores|number_processors|integer|$cores"
|
||||
echo "L2 Cache (per Core)|l2_cache_core|string|$l2"
|
||||
echo "L3 Cache|l3_cache|string|$l3"
|
||||
echo "Memory|physical_memory|string|$memory"
|
||||
echo "Serial Number (system)|serial_number|string|$(platform IOPlatformSerialNumber)"
|
||||
echo "Hardware UUID|platform_UUID|string|$(platform IOPlatformUUID)"
|
||||
echo "Provisioning UDID|provisioning_UDID|string|$(platform provisioning_UDID)"
|
||||
}
|
||||
|
||||
software() {
|
||||
name=$(sw_vers -productName 2>/dev/null); version=$(sw_vers -productVersion 2>/dev/null)
|
||||
build=$(sw_vers -buildVersion 2>/dev/null)
|
||||
echo "System Version|os_version|string|${version:+${name:-macOS} $version${build:+ ($build)}}"
|
||||
echo "Kernel Version|kernel_version|string|$(uname -sr 2>/dev/null)"
|
||||
echo "Boot Volume|boot_volume|string|Macintosh HD"
|
||||
echo "Boot Mode|boot_mode|string|normal_boot"
|
||||
echo "Computer Name|local_host_name|string|$(hostname -s 2>/dev/null)"
|
||||
echo "User Name|user_name|string|${USER:-$(id -un 2>/dev/null)}"
|
||||
}
|
||||
|
||||
overview_xml() { # $1 = _name; fields on stdin; empty values are omitted
|
||||
echo ' <dict>'
|
||||
echo " <key>_name</key><string>$1</string>"
|
||||
while IFS='|' read -r label key kind value; do
|
||||
[ -n "$value" ] || continue
|
||||
if [ "$kind" = integer ]; then
|
||||
case "$value" in *[!0-9]*) continue;; esac
|
||||
echo " <key>$key</key><integer>$value</integer>"
|
||||
else
|
||||
echo " <key>$key</key><string>$(printf '%s' "$value" | escape)</string>"
|
||||
fi
|
||||
done
|
||||
echo ' </dict>'
|
||||
}
|
||||
|
||||
overview_text() { # $1 = section title, $2 = overview title; fields on stdin
|
||||
printf '%s:\n\n %s:\n\n' "$1" "$2"
|
||||
while IFS='|' read -r label key kind value; do
|
||||
[ "$key" != boot_mode ] || value=Normal
|
||||
[ -z "$value" ] || printf ' %s: %s\n' "$label" "$value"
|
||||
done
|
||||
echo
|
||||
}
|
||||
|
||||
interfaces() { # $1 = wired|wireless; physical host interfaces only
|
||||
for path in "$host"/sys/class/net/*; do
|
||||
[ -e "$path/device" ] || continue
|
||||
@@ -65,6 +141,8 @@ if [ "$xml" = 1 ]; then
|
||||
echo ' <key>_items</key>'
|
||||
echo ' <array>'
|
||||
case "$type" in
|
||||
SPHardwareDataType) hardware | overview_xml hardware_overview;;
|
||||
SPSoftwareDataType) software | overview_xml os_overview;;
|
||||
SPDisplaysDataType)
|
||||
displays | while IFS='|' read -r vendor device vram name; do
|
||||
name=$(printf '%s' "$name" | escape)
|
||||
@@ -108,6 +186,8 @@ fi
|
||||
|
||||
for type in $types; do
|
||||
case "$type" in
|
||||
SPHardwareDataType) hardware | overview_text Hardware 'Hardware Overview';;
|
||||
SPSoftwareDataType) software | overview_text Software 'System Software Overview';;
|
||||
SPDisplaysDataType)
|
||||
printf 'Graphics/Displays:\n\n'
|
||||
displays | while IFS='|' read -r vendor device vram name; do
|
||||
|
||||
Reference in new issue
Block a user