forked from Manuel/meeting-assistant
Observe owned diskutil stack and live StorageKit in Recovery
This commit is contained in:
@@ -29,6 +29,7 @@ static class NativeDiagnostic
|
||||
const string FullState = "/storage/14/ci-state";
|
||||
const string NativeVersionSource = "/System/Library/CoreServices/SystemVersion.plist";
|
||||
const string NativeVersionMethod = "guest-file/host-xml";
|
||||
const int MaximumDiskStackBytes = 512 * 1024;
|
||||
static readonly JsonSerializerOptions JsonOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = true };
|
||||
const string OriginalBootstrap = "[ ! -e /tmp/m ]&&{ /sbin/mount_9p installstate >/dev/null 2>&1;exec /Volumes/installstate/launch.sh;};: >/tmp/m\n";
|
||||
const string MountOnlyBootstrap = "[ ! -e /tmp/m ]&& /sbin/mount_9p installstate >/dev/null 2>&1; : >/tmp/m\n";
|
||||
@@ -73,6 +74,7 @@ static class NativeDiagnostic
|
||||
{
|
||||
ValidateContracts();
|
||||
ValidateBootProgress();
|
||||
ValidateDiskStackCapture(output);
|
||||
await ValidateNativeSystemVersion(output);
|
||||
if (full) ValidateFullContracts();
|
||||
if (Option(args, "--source") is { } source)
|
||||
@@ -94,7 +96,11 @@ static class NativeDiagnostic
|
||||
nativeVersionMaximumBytes = 4096, nativeVersionResponseRequired = false,
|
||||
nativeVersionCandidateIsQualifiedReadiness = false, nativeVersionBindingRequiredBeforePermit = true,
|
||||
nativeVersionFixtures = "native-system-version-fixtures.json", nativeProductVersionCommandRemoved = true,
|
||||
diskReadinessAttemptLimit = 1, diskCommandLimitSeconds = 120, diskThreadObservationLimitSeconds = 60, stackSamplingUsed = false,
|
||||
diskReadinessAttemptLimit = 1, diskCommandLimitSeconds = 600, diskCommandExtendedForDiagnosticObservation = true,
|
||||
diskObservationCommandLimitSeconds = 60, stackSamplingRequested = true, stackSamplingDurationSeconds = 1,
|
||||
stackSamplingIntervalMilliseconds = 100, stackSamplingMayDie = true, stackSamplingRuntimeSucceeded = false,
|
||||
stackObservationIsQualifiedReadiness = false, diskStackMaximumCapturedBytes = MaximumDiskStackBytes,
|
||||
diskManagementDiagnosticLabel = "com.apple.storagekitd", diskStackCaptureFixtureCases = 4,
|
||||
resultNegativeCases = 6, containerNegativeCases = 11, recoveryPositiveCases = 4, recoveryNegativeCases = 12,
|
||||
independentFixtureCrc32Readback = true, resourceSnapshotRetention = true, cpuProfileSourceContractsVerified = true,
|
||||
preflightGateFixtureCases = 8, qemuRuntimePreflightExecuted = false, templateIsoDownloaded = false,
|
||||
@@ -279,6 +285,9 @@ static class NativeDiagnostic
|
||||
baseline = ReplaceOnce(baseline, "while (( attempt < 1 && SECONDS - readiness_start < 600 )); do", "while (( SECONDS - readiness_start < 600 )); do");
|
||||
if (Hash(Encoding.UTF8.GetBytes(baseline)) != "4d428f594dac14eff64ed87b172c81ecf85ac91da8c5460cd6ec4b1d310800c3")
|
||||
throw new InvalidOperationException("Outside seven explicit diagnostic blocks, two explicit native SystemVersion getter blocks and one-attempt limit, baseline identity/service/disk gates and watchdogs must remain identical.");
|
||||
foreach (var required in new[] { "command_limit=600; fi", "run_command management_before /bin/launchctl print system/com.apple.storagekitd", "observe_live_command storagekit-live /bin/launchctl print system/com.apple.storagekitd", "observe_command diskutil-stack /usr/bin/sample \"$disk_process\" 1 100 -mayDie -file \"$stack_output\"", "stack_output=\"$STATE_DIR/diskutil-stack.txt\"", "read -r -t 60 -u 9", "\"$BASH_VERSION\"" })
|
||||
if (!readiness.Contains(required, StringComparison.Ordinal)) throw new InvalidOperationException("Owned optional disk observation contract changed: " + required);
|
||||
if (readiness.Contains("PENDING_OUTPUTS+=(\"$stack_output\")", StringComparison.Ordinal)) throw new InvalidOperationException("Stack reports must be bounded directly by the Linux host, without another guest copy.");
|
||||
if (Hash(File.ReadAllBytes(Path.Combine("tools", "ci", "macos-native-bootstrap.sh"))) != "94f069e116fdc7685a4d233cab6fa50df9f39274386bb82157674061e74fadb5")
|
||||
throw new InvalidOperationException("Compatibility profile must preserve the baseline Apple recoveryosd wrapper.");
|
||||
if (Hash(Encoding.UTF8.GetBytes(OriginalDaemon13)) != "af9d7f6c1948079bd4384d27b6882678d6fb4e338fcf6a8be8f84fceef174ad6") throw new InvalidOperationException("macOS 13 allowlist bytes differ from the independently read comparison plist.");
|
||||
@@ -1158,6 +1167,7 @@ static class NativeDiagnostic
|
||||
ReportCpuPreflight(output, stage.ExitCode == 0 ? stage.Output : logs.Output);
|
||||
await Command("docker", ["exec", id, "head", "-c", "4096", "/run/shm/kernel-handoffs.log"], output, "capture-kernel-handoffs", cancellation, requireSuccess: false, retainSuccessful: true);
|
||||
if (token is not null) await CaptureNativeSystemVersion(id, output, token, full, cancellation);
|
||||
if (token is not null) await CaptureDiskStack(id, output, token, full, final, cancellation);
|
||||
var files = new List<(string, string)> { ("proof.log", "guest-proof.log"), ("result.json", "guest-result.json") };
|
||||
if (full) files.AddRange([("guest-phase.json", "guest-phase.json"), ("full-result.json", "full-result.json"), ("firstboot.log", "firstboot.log"), ("unattended-firstboot.log", "unattended-firstboot.log"), ("install.log", "install.log"), ("apple.log", "apple.log"), ("disk-ownership-ioreg.log", "disk-ownership-ioreg.log"), ("installed-root.plist", "installed-root.plist"), ("apfs-containers.plist", "apfs-containers.plist"), ("physical-store.plist", "physical-store.plist"), ("clt-catalog.log", "clt-catalog.log"), ("clt-install.log", "clt-install.log"), ("clt-sdk.log", "clt-sdk.log")]);
|
||||
foreach (var file in files)
|
||||
@@ -1182,6 +1192,55 @@ static class NativeDiagnostic
|
||||
await Command("docker", ["exec", id, "sh", "-c", "printf '[qemu]\n'; qemu-system-x86_64 --version | head -n 1; printf '[Recovery hash]\n'; test -f /storage/14/setup.dmg && sha256sum /storage/14/setup.dmg || exit 1; printf '[resources]\n'; df -Pk /storage; cat /sys/fs/cgroup/memory.max /sys/fs/cgroup/cpu.max 2>/dev/null || true"], output, "guest-container-resources", cancellation, requireSuccess: false, retainSuccessful: true);
|
||||
}
|
||||
|
||||
static async Task CaptureDiskStack(string id, string output, string token, bool full, bool final, CancellationToken cancellation)
|
||||
{
|
||||
var sourcePath = (full ? FullState : "/dev/shm/installstate") + "/diskutil-stack.txt";
|
||||
// The sample writes directly through 9p. Capture changing bytes with Linux
|
||||
// coreutils; absence/empty output must never suppress a later or final read.
|
||||
var capture = await Command("docker", ["exec", id, "bash", "-o", "pipefail", "-c", "head -c 524289 '" + sourcePath + "' | base64 -w 0"], output, "capture-diskutil-stack", cancellation, requireSuccess: false);
|
||||
if (capture.ExitCode != 0 || string.IsNullOrEmpty(capture.Output)) return;
|
||||
SaveDiskStackObservation(output, token, sourcePath, Convert.FromBase64String(capture.Output), final);
|
||||
}
|
||||
|
||||
static void SaveDiskStackObservation(string output, string token, string sourcePath, byte[] raw, bool final)
|
||||
{
|
||||
if (raw.Length == 0) return;
|
||||
if (raw.Length > MaximumDiskStackBytes + 1) throw new InvalidOperationException("Disk stack transport exceeded its one-byte sentinel.");
|
||||
var truncated = raw.Length > MaximumDiskStackBytes;
|
||||
var bounded = truncated ? raw[..MaximumDiskStackBytes] : raw;
|
||||
File.WriteAllBytes(Path.Combine(output, "diskutil-stack.txt"), bounded);
|
||||
Save(Path.Combine(output, "diskutil-stack-capture.json"), new { token, sourcePath, capturedBytes = bounded.Length, sha256 = Hash(bounded), maximumCapturedBytes = MaximumDiskStackBytes, truncated, finalCapture = final, snapshotOnly = true, sampleCompletionVerified = false, qualifiedReadiness = false, sampleExitAndTimeoutEvidence = "guest-proof.log", capturedUtc = DateTimeOffset.UtcNow });
|
||||
}
|
||||
|
||||
static void ValidateDiskStackCapture(string output)
|
||||
{
|
||||
var fixture = Path.Combine(output, "validation-disk-stack-capture");
|
||||
Directory.CreateDirectory(fixture);
|
||||
var cases = new List<object>();
|
||||
foreach (var size in new[] { 0, 64, MaximumDiskStackBytes, MaximumDiskStackBytes + 1 })
|
||||
{
|
||||
var folder = Path.Combine(fixture, size.ToString());
|
||||
Directory.CreateDirectory(folder);
|
||||
var raw = Enumerable.Repeat((byte)'s', size).ToArray();
|
||||
SaveDiskStackObservation(folder, "fixture", FullState + "/diskutil-stack.txt", raw, false);
|
||||
if (size == 0)
|
||||
{
|
||||
if (Directory.EnumerateFiles(folder).Any()) throw new InvalidOperationException("Empty stack capture was finalized.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var retained = File.ReadAllBytes(Path.Combine(folder, "diskutil-stack.txt"));
|
||||
using var receipt = JsonDocument.Parse(File.ReadAllText(Path.Combine(folder, "diskutil-stack-capture.json")));
|
||||
if (retained.Length != Math.Min(size, MaximumDiskStackBytes) || !retained.AsSpan().SequenceEqual(raw.AsSpan(0, retained.Length))
|
||||
|| receipt.RootElement.GetProperty("truncated").GetBoolean() != (size > MaximumDiskStackBytes)
|
||||
|| receipt.RootElement.GetProperty("sampleCompletionVerified").GetBoolean() || receipt.RootElement.GetProperty("qualifiedReadiness").GetBoolean())
|
||||
throw new InvalidOperationException("Stack capture changed bytes/bounds or qualified an observation.");
|
||||
}
|
||||
cases.Add(new { size, success = true });
|
||||
}
|
||||
Save(Path.Combine(fixture, "validation.json"), new { success = true, cases, emptyCaptureFinalized = false, maximumCapturedBytes = MaximumDiskStackBytes, qualifiedReadiness = false, guestExecuted = false });
|
||||
}
|
||||
|
||||
static int NativeVersionRequestLength(string request, string token)
|
||||
{
|
||||
var fields = request.Split('\n');
|
||||
|
||||
@@ -142,10 +142,11 @@ read_native_system_version() {
|
||||
}
|
||||
# END native SystemVersion plist request helpers
|
||||
# BEGIN disk IPC diagnostic
|
||||
# Optional observations have their own child/timer ownership. Thread state/time
|
||||
# targets only this probe's diskutil and does not request stack symbolication.
|
||||
# Optional observations have their own child/timer ownership. Stack/thread
|
||||
# observations target only this probe's live diskutil; none qualifies readiness.
|
||||
observe_disk_query() {
|
||||
local disk_process="$1" output="$2" observation_child="" observation_timer=""
|
||||
local stack_output="$STATE_DIR/diskutil-stack.txt"
|
||||
cancel_observation() {
|
||||
trap '' TERM INT
|
||||
if [ -n "$observation_child" ]; then
|
||||
@@ -179,19 +180,38 @@ observe_disk_query() {
|
||||
kill -TERM "$observation_timer" 2>/dev/null || :
|
||||
wait "$observation_timer" 2>/dev/null || :
|
||||
printf '[disk-observation-exit] %s status=%s elapsed=%ss\n' "$name" "$status" "$((SECONDS - started))" >> "$output"
|
||||
OBSERVATION_EXIT="$status"
|
||||
observation_child=""; observation_timer=""
|
||||
}
|
||||
observe_live_command() {
|
||||
local name="$1"
|
||||
shift
|
||||
if ! kill -0 "$disk_process" 2>/dev/null; then
|
||||
printf '[disk-observation-unavailable] %s: owned diskutil already exited\n' "$name" >> "$output"
|
||||
elif [ ! -x "$1" ]; then
|
||||
printf '[disk-observation-unavailable] %s: %s is unavailable\n' "$name" "$1" >> "$output"
|
||||
else
|
||||
observe_command "$name" "$@"
|
||||
fi
|
||||
}
|
||||
trap cancel_observation TERM INT
|
||||
printf '[disk-observation] owned-diskutil-child=%s parent-shell=%s\n' "$disk_process" "$$" >> "$output"
|
||||
if [ -x /bin/ps ]; then
|
||||
if kill -0 "$disk_process" 2>/dev/null; then
|
||||
observe_command diskutil-threads /bin/ps -M -p "$disk_process"
|
||||
else
|
||||
printf '[disk-observation-unavailable] diskutil already exited before thread observation\n' >> "$output"
|
||||
fi
|
||||
observe_live_command diskutil-threads-before /bin/ps -M -p "$disk_process"
|
||||
if [ ! -x /usr/bin/sample ]; then
|
||||
printf '[disk-observation-unavailable] diskutil-stack: /usr/bin/sample is unavailable\n' >> "$output"
|
||||
elif ! kill -0 "$disk_process" 2>/dev/null; then
|
||||
printf '[disk-observation-unavailable] diskutil-stack: owned diskutil already exited\n' >> "$output"
|
||||
elif [ -e "$stack_output" ] || [ -L "$stack_output" ]; then
|
||||
printf '[disk-observation-unavailable] diskutil-stack: output already exists\n' >> "$output"
|
||||
elif : > "$stack_output"; then
|
||||
printf '[disk-stack-attempt] owned-diskutil-child=%s duration=1s interval=100ms limit=60s output=%s observation-only=true\n' "$disk_process" "$stack_output" >> "$output"
|
||||
observe_command diskutil-stack /usr/bin/sample "$disk_process" 1 100 -mayDie -file "$stack_output"
|
||||
printf '[disk-stack-result] status=%s observation-only=true; raw report is captured by the Linux host\n' "$OBSERVATION_EXIT" >> "$output"
|
||||
else
|
||||
printf '[disk-observation-unavailable] /bin/ps is unavailable\n' >> "$output"
|
||||
printf '[disk-observation-unavailable] diskutil-stack: output cannot be created\n' >> "$output"
|
||||
fi
|
||||
observe_live_command storagekit-live /bin/launchctl print system/com.apple.storagekitd
|
||||
observe_live_command diskutil-threads-after /bin/ps -M -p "$disk_process"
|
||||
}
|
||||
|
||||
stop_disk_observation() {
|
||||
@@ -226,7 +246,7 @@ run_command() {
|
||||
# Isolate only the failed UID gate; every other watchdog remains unchanged.
|
||||
[[ "$name" != uid ]] || command_limit=180
|
||||
# BEGIN disk IPC diagnostic
|
||||
if [[ "$name" == disks && "${attempt:-0}" == 1 ]]; then command_limit=120; fi
|
||||
if [[ "$name" == disks && "${attempt:-0}" == 1 ]]; then command_limit=600; fi
|
||||
# END disk IPC diagnostic
|
||||
LAST_OUTPUT="/tmp/native-diagnostic-$name.out"
|
||||
printf '\n[proof-command] %s:' "$name" >&3
|
||||
@@ -314,8 +334,9 @@ os_version="$SCALAR"
|
||||
flush_outputs || finish false diagnostic_log_budget_exceeded
|
||||
|
||||
# BEGIN disk IPC diagnostic
|
||||
printf '[disk-diagnostic-runtime] bash=%s stack-observation-only=true\n' "$BASH_VERSION" >&3
|
||||
run_command arbitration_before /bin/launchctl print system/com.apple.diskarbitrationd
|
||||
run_command management_before /bin/launchctl print system/com.apple.diskmanagementd
|
||||
run_command management_before /bin/launchctl print system/com.apple.storagekitd
|
||||
run_command media_before /usr/sbin/ioreg -r -c IOMedia -l -w 0
|
||||
flush_outputs || finish false diagnostic_log_budget_exceeded
|
||||
|
||||
|
||||
Reference in New Issue
Block a user