@hasna/recordings 0.2.11 → 0.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +349 -69
- package/bun.lock +3 -0
- package/dist/__tests__/helpers/native-fs-guard.d.ts +2 -0
- package/dist/__tests__/helpers/native-fs-guard.d.ts.map +1 -0
- package/dist/cli/index.js +1728 -320
- package/dist/cli/macos-permissions.d.ts +13 -0
- package/dist/cli/macos-permissions.d.ts.map +1 -0
- package/dist/cli/options.d.ts +12 -0
- package/dist/cli/options.d.ts.map +1 -1
- package/dist/db/pg-migrations.d.ts.map +1 -1
- package/dist/db/recordings.d.ts +2 -1
- package/dist/db/recordings.d.ts.map +1 -1
- package/dist/db/remote-storage.d.ts +5 -1
- package/dist/db/remote-storage.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +628 -147
- package/dist/lib/bun-runtime.d.ts +11 -0
- package/dist/lib/bun-runtime.d.ts.map +1 -0
- package/dist/lib/config.d.ts +8 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/enhancer.d.ts +10 -2
- package/dist/lib/enhancer.d.ts.map +1 -1
- package/dist/lib/install-maintenance.d.ts +24 -0
- package/dist/lib/install-maintenance.d.ts.map +1 -0
- package/dist/lib/machine.d.ts +2 -0
- package/dist/lib/machine.d.ts.map +1 -0
- package/dist/lib/recorder.d.ts +2 -1
- package/dist/lib/recorder.d.ts.map +1 -1
- package/dist/lib/recording-create-identity.d.ts +13 -0
- package/dist/lib/recording-create-identity.d.ts.map +1 -0
- package/dist/lib/release-install-policy.d.ts +31 -0
- package/dist/lib/release-install-policy.d.ts.map +1 -0
- package/dist/lib/transcriber.d.ts.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +977 -194
- package/dist/sdk/index.js +5 -1
- package/dist/sdk/v1.generated.d.ts +4 -0
- package/dist/sdk/v1.generated.d.ts.map +1 -1
- package/dist/server/cloud-config.d.ts +9 -0
- package/dist/server/cloud-config.d.ts.map +1 -0
- package/dist/server/cloud-readiness.d.ts +10 -0
- package/dist/server/cloud-readiness.d.ts.map +1 -0
- package/dist/server/cloud.d.ts +9 -18
- package/dist/server/cloud.d.ts.map +1 -1
- package/dist/server/index.js +1654 -304
- package/dist/server/migrate-command.d.ts +11 -0
- package/dist/server/migrate-command.d.ts.map +1 -0
- package/dist/server/openapi.d.ts +22 -0
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/repo.d.ts +8 -1
- package/dist/server/repo.d.ts.map +1 -1
- package/dist/server/serve.d.ts +6 -1
- package/dist/server/serve.d.ts.map +1 -1
- package/dist/server/v1.d.ts.map +1 -1
- package/dist/storage.js +515 -80
- package/dist/store.d.ts +3 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +7 -4
- package/packaging/macos/Empty.entitlements +5 -0
- package/packaging/macos/Library/LaunchDaemons/com.hasna.recordings.updater.plist +34 -0
- package/packaging/macos/Verifier.entitlements +10 -0
- package/packaging/macos/artifact-verifier.sb +33 -0
- package/packaging/macos/build_release_pkg.sh +872 -0
- package/packaging/macos/managed_bootstrap.sh +623 -0
- package/packaging/macos/pkgutil_fingerprint.awk +37 -0
- package/packaging/macos/release_lifecycle.ts +463 -0
- package/packaging/macos/scripts/postinstall +565 -0
- package/packaging/macos/scripts/preinstall +297 -0
- package/scripts/build_companion_cli.sh +337 -0
- package/scripts/build_native_fs_guard.sh +59 -0
- package/scripts/generate-sdk.ts +19 -1
- package/scripts/install_macos_app.sh +1919 -98
- package/scripts/macos_artifact.ts +5316 -0
- package/scripts/migrate.ts +38 -10
- package/scripts/native/prebuilds/darwin-universal/recordings_fs_guard.node +0 -0
- package/scripts/native/recordings_fs_guard.c +1167 -0
- package/scripts/native_fs_guard.ts +158 -0
- package/scripts/release-guard.ts +20 -1
- package/scripts/resolve_tailscale_cli.sh +389 -0
- package/scripts/smoke_macos_app.sh +573 -0
- package/src/native/Recordings/App/ContentView.swift +83 -0
- package/src/native/Recordings/App/MenuBarStatusView.swift +102 -0
- package/src/native/Recordings/App/RecordWorkspaceView.swift +468 -0
- package/src/native/Recordings/App/RecordingDetailView.swift +138 -0
- package/src/native/Recordings/App/RecordingsApp.swift +367 -44
- package/src/native/Recordings/App/RecordingsListView.swift +130 -0
- package/src/native/Recordings/App/RecordingsStore.swift +206 -0
- package/src/native/Recordings/App/RuntimeSmoke.swift +158 -0
- package/src/native/Recordings/App/SidebarView.swift +212 -0
- package/src/native/Recordings/App/Theme.swift +87 -0
- package/src/native/Recordings/Package.resolved +5 -5
- package/src/native/Recordings/Package.swift +62 -2
- package/src/native/Recordings/RecordingsLib/AccessibilityPromptGate.swift +104 -0
- package/src/native/Recordings/RecordingsLib/BrandAssets.swift +0 -32
- package/src/native/Recordings/RecordingsLib/ChromeSurface.swift +17 -0
- package/src/native/Recordings/RecordingsLib/Info.plist +7 -3
- package/src/native/Recordings/RecordingsLib/MenuBarPresentation.swift +37 -0
- package/src/native/Recordings/RecordingsLib/NativeAppDiagnostics.swift +32 -2
- package/src/native/Recordings/RecordingsLib/NativeMachineIdentity.swift +24 -0
- package/src/native/Recordings/RecordingsLib/NativePCMRecorder.swift +262 -23
- package/src/native/Recordings/RecordingsLib/OpenAIAPIKeyStore.swift +56 -0
- package/src/native/Recordings/RecordingsLib/PermissionRequestLaunchPlan.swift +56 -0
- package/src/native/Recordings/RecordingsLib/ProjectStore.swift +321 -25
- package/src/native/Recordings/RecordingsLib/RealtimeTranscriptionClient.swift +605 -90
- package/src/native/Recordings/RecordingsLib/Recording.swift +141 -0
- package/src/native/Recordings/RecordingsLib/RecordingEngine.swift +3704 -376
- package/src/native/Recordings/RecordingsLib/RecordingStartControlPresentation.swift +29 -0
- package/src/native/Recordings/RecordingsLib/RecordingsCLI.entitlements +10 -0
- package/src/native/Recordings/RecordingsLib/RecordingsCLI.swift +222 -0
- package/src/native/Recordings/RecordingsLib/SettingsView.swift +45 -11
- package/src/native/Recordings/RecordingsLib/SpeechIntent.swift +331 -0
- package/src/native/Recordings/RecordingsLib/SpeechIntentClassifier.swift +232 -0
- package/src/native/Recordings/RecordingsLib/VoiceShortcuts.swift +17 -7
- package/src/native/Recordings/RecordingsTests/CLIRunnerTests.swift +1343 -0
- package/src/native/Recordings/RecordingsTests/MenuBarPresentationTests.swift +98 -0
- package/src/native/Recordings/RecordingsTests/NativeAppDiagnosticsTests.swift +20 -0
- package/src/native/Recordings/RecordingsTests/NativePCMRecorderTests.swift +370 -1
- package/src/native/Recordings/RecordingsTests/PasteTargetTests.swift +987 -1
- package/src/native/Recordings/RecordingsTests/ProjectStoreTests.swift +385 -0
- package/src/native/Recordings/RecordingsTests/RealtimeTranscriptionTests.swift +835 -8
- package/src/native/Recordings/RecordingsTests/RecordingBridgeTests.swift +180 -0
- package/src/native/Recordings/RecordingsTests/RecordingEngineDeliveryTests.swift +760 -0
- package/src/native/Recordings/RecordingsTests/RecordingStartControlPresentationTests.swift +42 -0
- package/src/native/Recordings/RecordingsTests/RecordingStartGateTests.swift +264 -0
- package/src/native/Recordings/RecordingsTests/RecordingStartTimingTests.swift +129 -0
- package/src/native/Recordings/RecordingsTests/SpeechIntentTests.swift +600 -0
- package/src/native/Recordings/RecordingsTests/TranscriptResolutionTests.swift +62 -1
- package/src/native/Recordings/RecordingsTests/TranscriptionResultIdentityTests.swift +21 -0
- package/src/native/Recordings/Updater/BootstrapPreflight/BootstrapPreflightMain.swift +739 -0
- package/src/native/Recordings/Updater/Broker/ActivationRecoveryPolicy.swift +254 -0
- package/src/native/Recordings/Updater/Broker/ApplicationNamespace.swift +463 -0
- package/src/native/Recordings/Updater/Broker/ApplicationProcessQuiescence.swift +55 -0
- package/src/native/Recordings/Updater/Broker/ArtifactIngest.swift +341 -0
- package/src/native/Recordings/Updater/Broker/AtomicActivation.swift +477 -0
- package/src/native/Recordings/Updater/Broker/BrokerMain.swift +624 -0
- package/src/native/Recordings/Updater/Broker/CanonicalTreeCopy.swift +63 -0
- package/src/native/Recordings/Updater/Broker/CodeValidation.swift +561 -0
- package/src/native/Recordings/Updater/Broker/DarwinACLValidator.swift +66 -0
- package/src/native/Recordings/Updater/Broker/HostOSProductVersion.swift +50 -0
- package/src/native/Recordings/Updater/Broker/InstallJournal.swift +362 -0
- package/src/native/Recordings/Updater/Broker/InstallRecovery.swift +662 -0
- package/src/native/Recordings/Updater/Broker/MonotonicState.swift +456 -0
- package/src/native/Recordings/Updater/Broker/PeerIdentity.swift +274 -0
- package/src/native/Recordings/Updater/Broker/VerifierRunner.swift +81 -0
- package/src/native/Recordings/Updater/BrokerTests/ActivationRecoveryPolicyTests.swift +259 -0
- package/src/native/Recordings/Updater/BrokerTests/CanonicalReleaseOrderTests.swift +21 -0
- package/src/native/Recordings/Updater/Client/ClientMain.swift +119 -0
- package/src/native/Recordings/Updater/Protocol/BoundedProcess.swift +159 -0
- package/src/native/Recordings/Updater/Protocol/CandidateMetadataPolicy.swift +214 -0
- package/src/native/Recordings/Updater/Protocol/HostOSVersionPolicy.swift +55 -0
- package/src/native/Recordings/Updater/Protocol/MonotonicReleasePolicy.swift +152 -0
- package/src/native/Recordings/Updater/Protocol/ReleaseEnvelope.swift +229 -0
- package/src/native/Recordings/Updater/Protocol/UpdateProtocol.swift +119 -0
- package/src/native/Recordings/Updater/ProtocolTests/BoundedProcessRunnerTests.swift +60 -0
- package/src/native/Recordings/Updater/ProtocolTests/CandidateMetadataPolicyTests.swift +168 -0
- package/src/native/Recordings/Updater/ProtocolTests/HostOSVersionPolicyTests.swift +62 -0
- package/src/native/Recordings/Updater/ProtocolTests/MonotonicReleasePolicyTests.swift +172 -0
- package/src/native/Recordings/Updater/ProtocolTests/ReleaseEnvelopeValidationTests.swift +65 -0
- package/src/native/Recordings/Updater/Signer/SignerMain.swift +210 -0
- package/src/native/Recordings/Updater/VerifierLauncher/RecordingsVerifierLauncher.c +725 -0
- package/src/native/Recordings/Updater/VerifierLauncher/include/RecordingsVerifierLauncher.h +33 -0
- package/src/native/Recordings/build.sh +1973 -236
- package/src/native/Recordings/RecordingsLib/MenuBarPopover.swift +0 -380
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
import Darwin
|
|
2
|
+
import Foundation
|
|
3
|
+
import RecordingsUpdateProtocol
|
|
4
|
+
|
|
5
|
+
/// Descriptor-bound namespace mutations for the live application and one protected
|
|
6
|
+
/// transaction. Existing installs are exchanged with the candidate in one kernel
|
|
7
|
+
/// operation; first installs use exclusive rename semantics and can never replace a
|
|
8
|
+
/// leaf created concurrently in `/Applications`.
|
|
9
|
+
final class ApplicationNamespace {
|
|
10
|
+
private static let liveLeaf = "Recordings.app"
|
|
11
|
+
private static let previousLeaf = "previous.app"
|
|
12
|
+
private static let failedCandidateLeaf = "failed-candidate.app"
|
|
13
|
+
|
|
14
|
+
private let applicationsDescriptor: Int32
|
|
15
|
+
private let candidateDescriptor: Int32
|
|
16
|
+
private let transactionDescriptor: Int32
|
|
17
|
+
|
|
18
|
+
init(journal: BrokerInstallJournal) throws {
|
|
19
|
+
let expectedCandidateDirectory = journal.candidateApplicationPath
|
|
20
|
+
.deletingLastPathComponent
|
|
21
|
+
let expectedTransactionDirectory = expectedCandidateDirectory
|
|
22
|
+
.deletingLastPathComponent
|
|
23
|
+
guard journal.applicationPath == RecordingsUpdateConstants.applicationPath,
|
|
24
|
+
journal.candidateApplicationPath == expectedTransactionDirectory
|
|
25
|
+
+ "/candidate/Recordings.app",
|
|
26
|
+
journal.previousApplicationPath == nil ||
|
|
27
|
+
journal.previousApplicationPath == expectedTransactionDirectory + "/previous.app"
|
|
28
|
+
else {
|
|
29
|
+
throw ApplicationNamespaceError.invalidBinding
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
applicationsDescriptor = try Self.openValidatedDirectory(
|
|
33
|
+
"/Applications",
|
|
34
|
+
exactApplicationsDirectory: true
|
|
35
|
+
)
|
|
36
|
+
do {
|
|
37
|
+
candidateDescriptor = try Self.openValidatedDirectory(
|
|
38
|
+
expectedCandidateDirectory,
|
|
39
|
+
exactApplicationsDirectory: false
|
|
40
|
+
)
|
|
41
|
+
} catch {
|
|
42
|
+
Darwin.close(applicationsDescriptor)
|
|
43
|
+
throw error
|
|
44
|
+
}
|
|
45
|
+
do {
|
|
46
|
+
transactionDescriptor = try Self.openValidatedDirectory(
|
|
47
|
+
expectedTransactionDirectory,
|
|
48
|
+
exactApplicationsDirectory: false
|
|
49
|
+
)
|
|
50
|
+
} catch {
|
|
51
|
+
Darwin.close(candidateDescriptor)
|
|
52
|
+
Darwin.close(applicationsDescriptor)
|
|
53
|
+
throw error
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
deinit {
|
|
58
|
+
Darwin.close(transactionDescriptor)
|
|
59
|
+
Darwin.close(candidateDescriptor)
|
|
60
|
+
Darwin.close(applicationsDescriptor)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
func exchangeCandidateAndLive() throws {
|
|
64
|
+
try renameAtX(
|
|
65
|
+
from: candidateDescriptor,
|
|
66
|
+
Self.liveLeaf,
|
|
67
|
+
to: applicationsDescriptor,
|
|
68
|
+
Self.liveLeaf,
|
|
69
|
+
flags: UInt32(RENAME_SWAP)
|
|
70
|
+
)
|
|
71
|
+
try synchronize([candidateDescriptor, applicationsDescriptor])
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
func installCandidateExclusively() throws {
|
|
75
|
+
try renameAtX(
|
|
76
|
+
from: candidateDescriptor,
|
|
77
|
+
Self.liveLeaf,
|
|
78
|
+
to: applicationsDescriptor,
|
|
79
|
+
Self.liveLeaf,
|
|
80
|
+
flags: UInt32(RENAME_EXCL)
|
|
81
|
+
)
|
|
82
|
+
try synchronize([candidateDescriptor, applicationsDescriptor])
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
func retainSwappedPreviousExclusively() throws {
|
|
86
|
+
try renameAtX(
|
|
87
|
+
from: candidateDescriptor,
|
|
88
|
+
Self.liveLeaf,
|
|
89
|
+
to: transactionDescriptor,
|
|
90
|
+
Self.previousLeaf,
|
|
91
|
+
flags: UInt32(RENAME_EXCL)
|
|
92
|
+
)
|
|
93
|
+
try synchronize([candidateDescriptor, transactionDescriptor])
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
func exchangeRetainedPreviousAndLive() throws {
|
|
97
|
+
try renameAtX(
|
|
98
|
+
from: transactionDescriptor,
|
|
99
|
+
Self.previousLeaf,
|
|
100
|
+
to: applicationsDescriptor,
|
|
101
|
+
Self.liveLeaf,
|
|
102
|
+
flags: UInt32(RENAME_SWAP)
|
|
103
|
+
)
|
|
104
|
+
try synchronize([transactionDescriptor, applicationsDescriptor])
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
func retainFailedCandidateFromPreviousSlotExclusively() throws {
|
|
108
|
+
try renameAtX(
|
|
109
|
+
from: transactionDescriptor,
|
|
110
|
+
Self.previousLeaf,
|
|
111
|
+
to: transactionDescriptor,
|
|
112
|
+
Self.failedCandidateLeaf,
|
|
113
|
+
flags: UInt32(RENAME_EXCL)
|
|
114
|
+
)
|
|
115
|
+
try synchronize([transactionDescriptor])
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
func retainFailedCandidateFromLiveExclusively() throws {
|
|
119
|
+
try renameAtX(
|
|
120
|
+
from: applicationsDescriptor,
|
|
121
|
+
Self.liveLeaf,
|
|
122
|
+
to: transactionDescriptor,
|
|
123
|
+
Self.failedCandidateLeaf,
|
|
124
|
+
flags: UInt32(RENAME_EXCL)
|
|
125
|
+
)
|
|
126
|
+
try synchronize([applicationsDescriptor, transactionDescriptor])
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
func engageLaunchBarrier(journal: BrokerInstallJournal) throws {
|
|
130
|
+
try setApplicationExecutableModes(
|
|
131
|
+
parent: candidateDescriptor,
|
|
132
|
+
applicationLeaf: Self.liveLeaf,
|
|
133
|
+
originalModes: journal.candidateExecutableModes,
|
|
134
|
+
engageBarrier: true
|
|
135
|
+
)
|
|
136
|
+
try setDirectoryMode(
|
|
137
|
+
parent: candidateDescriptor,
|
|
138
|
+
leaf: Self.liveLeaf,
|
|
139
|
+
allowedCurrentModes: [mode_t(journal.candidateApplicationMode), 0],
|
|
140
|
+
newMode: 0
|
|
141
|
+
)
|
|
142
|
+
if let previousMode = journal.previousApplicationMode {
|
|
143
|
+
guard let previousExecutableModes = journal.previousExecutableModes else {
|
|
144
|
+
throw ApplicationNamespaceError.invalidBinding
|
|
145
|
+
}
|
|
146
|
+
try setApplicationExecutableModes(
|
|
147
|
+
parent: applicationsDescriptor,
|
|
148
|
+
applicationLeaf: Self.liveLeaf,
|
|
149
|
+
originalModes: previousExecutableModes,
|
|
150
|
+
engageBarrier: true
|
|
151
|
+
)
|
|
152
|
+
try setDirectoryMode(
|
|
153
|
+
parent: applicationsDescriptor,
|
|
154
|
+
leaf: Self.liveLeaf,
|
|
155
|
+
allowedCurrentModes: [mode_t(previousMode), 0],
|
|
156
|
+
newMode: 0
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
func releaseCommittedLaunchBarrier(journal: BrokerInstallJournal) throws {
|
|
162
|
+
try setApplicationExecutableModes(
|
|
163
|
+
parent: applicationsDescriptor,
|
|
164
|
+
applicationLeaf: Self.liveLeaf,
|
|
165
|
+
originalModes: journal.candidateExecutableModes,
|
|
166
|
+
engageBarrier: false
|
|
167
|
+
)
|
|
168
|
+
try setDirectoryMode(
|
|
169
|
+
parent: applicationsDescriptor,
|
|
170
|
+
leaf: Self.liveLeaf,
|
|
171
|
+
allowedCurrentModes: [0, mode_t(journal.candidateApplicationMode)],
|
|
172
|
+
newMode: mode_t(journal.candidateApplicationMode)
|
|
173
|
+
)
|
|
174
|
+
if let previousMode = journal.previousApplicationMode {
|
|
175
|
+
guard let previousExecutableModes = journal.previousExecutableModes else {
|
|
176
|
+
throw ApplicationNamespaceError.invalidBinding
|
|
177
|
+
}
|
|
178
|
+
try setApplicationExecutableModes(
|
|
179
|
+
parent: transactionDescriptor,
|
|
180
|
+
applicationLeaf: Self.previousLeaf,
|
|
181
|
+
originalModes: previousExecutableModes,
|
|
182
|
+
engageBarrier: false
|
|
183
|
+
)
|
|
184
|
+
try setDirectoryMode(
|
|
185
|
+
parent: transactionDescriptor,
|
|
186
|
+
leaf: Self.previousLeaf,
|
|
187
|
+
allowedCurrentModes: [0, mode_t(previousMode)],
|
|
188
|
+
newMode: mode_t(previousMode)
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
func releaseRolledBackLaunchBarrier(journal: BrokerInstallJournal) throws {
|
|
194
|
+
if let previousMode = journal.previousApplicationMode {
|
|
195
|
+
guard let previousExecutableModes = journal.previousExecutableModes else {
|
|
196
|
+
throw ApplicationNamespaceError.invalidBinding
|
|
197
|
+
}
|
|
198
|
+
try setApplicationExecutableModes(
|
|
199
|
+
parent: applicationsDescriptor,
|
|
200
|
+
applicationLeaf: Self.liveLeaf,
|
|
201
|
+
originalModes: previousExecutableModes,
|
|
202
|
+
engageBarrier: false
|
|
203
|
+
)
|
|
204
|
+
try setDirectoryMode(
|
|
205
|
+
parent: applicationsDescriptor,
|
|
206
|
+
leaf: Self.liveLeaf,
|
|
207
|
+
allowedCurrentModes: [0, mode_t(previousMode)],
|
|
208
|
+
newMode: mode_t(previousMode)
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
let candidateAtOriginalSlot = try directoryExists(
|
|
212
|
+
parent: candidateDescriptor,
|
|
213
|
+
leaf: Self.liveLeaf
|
|
214
|
+
)
|
|
215
|
+
let candidateAtFailedSlot = try directoryExists(
|
|
216
|
+
parent: transactionDescriptor,
|
|
217
|
+
leaf: Self.failedCandidateLeaf
|
|
218
|
+
)
|
|
219
|
+
guard candidateAtOriginalSlot != candidateAtFailedSlot else {
|
|
220
|
+
throw ApplicationNamespaceError.invalidBinding
|
|
221
|
+
}
|
|
222
|
+
try setApplicationExecutableModes(
|
|
223
|
+
parent: candidateAtOriginalSlot ? candidateDescriptor : transactionDescriptor,
|
|
224
|
+
applicationLeaf: candidateAtOriginalSlot ? Self.liveLeaf : Self.failedCandidateLeaf,
|
|
225
|
+
originalModes: journal.candidateExecutableModes,
|
|
226
|
+
engageBarrier: false
|
|
227
|
+
)
|
|
228
|
+
try setDirectoryMode(
|
|
229
|
+
parent: candidateAtOriginalSlot ? candidateDescriptor : transactionDescriptor,
|
|
230
|
+
leaf: candidateAtOriginalSlot ? Self.liveLeaf : Self.failedCandidateLeaf,
|
|
231
|
+
allowedCurrentModes: [0, mode_t(journal.candidateApplicationMode)],
|
|
232
|
+
newMode: mode_t(journal.candidateApplicationMode)
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
private func renameAtX(
|
|
237
|
+
from sourceDescriptor: Int32,
|
|
238
|
+
_ sourceLeaf: String,
|
|
239
|
+
to destinationDescriptor: Int32,
|
|
240
|
+
_ destinationLeaf: String,
|
|
241
|
+
flags: UInt32
|
|
242
|
+
) throws {
|
|
243
|
+
let status = sourceLeaf.withCString { source in
|
|
244
|
+
destinationLeaf.withCString { destination in
|
|
245
|
+
renameatx_np(
|
|
246
|
+
sourceDescriptor,
|
|
247
|
+
source,
|
|
248
|
+
destinationDescriptor,
|
|
249
|
+
destination,
|
|
250
|
+
flags
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
guard status == 0 else { throw ApplicationNamespaceError.mutationFailed }
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private func synchronize(_ descriptors: [Int32]) throws {
|
|
258
|
+
for descriptor in descriptors {
|
|
259
|
+
guard DarwinACLValidator.descriptorHasNoExtendedACL(descriptor),
|
|
260
|
+
fsync(descriptor) == 0
|
|
261
|
+
else {
|
|
262
|
+
throw ApplicationNamespaceError.couldNotSync
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
private func setDirectoryMode(
|
|
268
|
+
parent: Int32,
|
|
269
|
+
leaf: String,
|
|
270
|
+
allowedCurrentModes: Set<mode_t>,
|
|
271
|
+
newMode: mode_t
|
|
272
|
+
) throws {
|
|
273
|
+
var namedBefore = stat()
|
|
274
|
+
let inspectStatus = leaf.withCString {
|
|
275
|
+
fstatat(parent, $0, &namedBefore, AT_SYMLINK_NOFOLLOW)
|
|
276
|
+
}
|
|
277
|
+
guard inspectStatus == 0,
|
|
278
|
+
(namedBefore.st_mode & S_IFMT) == S_IFDIR,
|
|
279
|
+
namedBefore.st_uid == 0,
|
|
280
|
+
allowedCurrentModes.contains(namedBefore.st_mode & 0o777)
|
|
281
|
+
else {
|
|
282
|
+
throw ApplicationNamespaceError.invalidBinding
|
|
283
|
+
}
|
|
284
|
+
let descriptor = leaf.withCString {
|
|
285
|
+
openat(parent, $0, O_RDONLY | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW)
|
|
286
|
+
}
|
|
287
|
+
guard descriptor >= 0 else { throw ApplicationNamespaceError.invalidBinding }
|
|
288
|
+
defer { Darwin.close(descriptor) }
|
|
289
|
+
var opened = stat()
|
|
290
|
+
guard fstat(descriptor, &opened) == 0,
|
|
291
|
+
opened.st_dev == namedBefore.st_dev,
|
|
292
|
+
opened.st_ino == namedBefore.st_ino,
|
|
293
|
+
DarwinACLValidator.descriptorHasNoExtendedACL(descriptor),
|
|
294
|
+
fchmod(descriptor, newMode) == 0,
|
|
295
|
+
fsync(descriptor) == 0
|
|
296
|
+
else {
|
|
297
|
+
throw ApplicationNamespaceError.mutationFailed
|
|
298
|
+
}
|
|
299
|
+
var namedAfter = stat()
|
|
300
|
+
let recheckStatus = leaf.withCString {
|
|
301
|
+
fstatat(parent, $0, &namedAfter, AT_SYMLINK_NOFOLLOW)
|
|
302
|
+
}
|
|
303
|
+
guard recheckStatus == 0,
|
|
304
|
+
namedAfter.st_dev == opened.st_dev,
|
|
305
|
+
namedAfter.st_ino == opened.st_ino,
|
|
306
|
+
(namedAfter.st_mode & 0o777) == newMode,
|
|
307
|
+
fsync(parent) == 0
|
|
308
|
+
else {
|
|
309
|
+
throw ApplicationNamespaceError.mutationFailed
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
private func setApplicationExecutableModes(
|
|
314
|
+
parent: Int32,
|
|
315
|
+
applicationLeaf: String,
|
|
316
|
+
originalModes: [String: UInt16],
|
|
317
|
+
engageBarrier: Bool
|
|
318
|
+
) throws {
|
|
319
|
+
let application = try openSafeDirectoryAt(parent: parent, leaf: applicationLeaf)
|
|
320
|
+
defer { Darwin.close(application) }
|
|
321
|
+
for relativePath in originalModes.keys.sorted() {
|
|
322
|
+
let components = relativePath.split(separator: "/").map(String.init)
|
|
323
|
+
guard components.count == 3, components[0] == "Contents",
|
|
324
|
+
let originalMode = originalModes[relativePath]
|
|
325
|
+
else {
|
|
326
|
+
throw ApplicationNamespaceError.invalidBinding
|
|
327
|
+
}
|
|
328
|
+
let contents = try openSafeDirectoryAt(parent: application, leaf: components[0])
|
|
329
|
+
defer { Darwin.close(contents) }
|
|
330
|
+
let executableDirectory = try openSafeDirectoryAt(
|
|
331
|
+
parent: contents,
|
|
332
|
+
leaf: components[1]
|
|
333
|
+
)
|
|
334
|
+
defer { Darwin.close(executableDirectory) }
|
|
335
|
+
try setRegularFileMode(
|
|
336
|
+
parent: executableDirectory,
|
|
337
|
+
leaf: components[2],
|
|
338
|
+
allowedCurrentModes: [0, mode_t(originalMode)],
|
|
339
|
+
newMode: engageBarrier ? 0 : mode_t(originalMode)
|
|
340
|
+
)
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
private func openSafeDirectoryAt(parent: Int32, leaf: String) throws -> Int32 {
|
|
345
|
+
let descriptor = leaf.withCString {
|
|
346
|
+
openat(parent, $0, O_RDONLY | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW)
|
|
347
|
+
}
|
|
348
|
+
guard descriptor >= 0 else { throw ApplicationNamespaceError.invalidBinding }
|
|
349
|
+
var metadata = stat()
|
|
350
|
+
guard fstat(descriptor, &metadata) == 0,
|
|
351
|
+
(metadata.st_mode & S_IFMT) == S_IFDIR,
|
|
352
|
+
metadata.st_uid == 0,
|
|
353
|
+
(metadata.st_mode & 0o022) == 0,
|
|
354
|
+
DarwinACLValidator.descriptorHasNoExtendedACL(descriptor)
|
|
355
|
+
else {
|
|
356
|
+
Darwin.close(descriptor)
|
|
357
|
+
throw ApplicationNamespaceError.invalidBinding
|
|
358
|
+
}
|
|
359
|
+
return descriptor
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
private func setRegularFileMode(
|
|
363
|
+
parent: Int32,
|
|
364
|
+
leaf: String,
|
|
365
|
+
allowedCurrentModes: Set<mode_t>,
|
|
366
|
+
newMode: mode_t
|
|
367
|
+
) throws {
|
|
368
|
+
var namedBefore = stat()
|
|
369
|
+
let inspectStatus = leaf.withCString {
|
|
370
|
+
fstatat(parent, $0, &namedBefore, AT_SYMLINK_NOFOLLOW)
|
|
371
|
+
}
|
|
372
|
+
guard inspectStatus == 0,
|
|
373
|
+
(namedBefore.st_mode & S_IFMT) == S_IFREG,
|
|
374
|
+
namedBefore.st_uid == 0,
|
|
375
|
+
namedBefore.st_nlink == 1,
|
|
376
|
+
allowedCurrentModes.contains(namedBefore.st_mode & 0o777)
|
|
377
|
+
else {
|
|
378
|
+
throw ApplicationNamespaceError.invalidBinding
|
|
379
|
+
}
|
|
380
|
+
let descriptor = leaf.withCString {
|
|
381
|
+
openat(parent, $0, O_RDONLY | O_CLOEXEC | O_NOFOLLOW)
|
|
382
|
+
}
|
|
383
|
+
guard descriptor >= 0 else { throw ApplicationNamespaceError.invalidBinding }
|
|
384
|
+
defer { Darwin.close(descriptor) }
|
|
385
|
+
var opened = stat()
|
|
386
|
+
guard fstat(descriptor, &opened) == 0,
|
|
387
|
+
opened.st_dev == namedBefore.st_dev,
|
|
388
|
+
opened.st_ino == namedBefore.st_ino,
|
|
389
|
+
DarwinACLValidator.descriptorHasNoExtendedACL(descriptor),
|
|
390
|
+
fchmod(descriptor, newMode) == 0,
|
|
391
|
+
fsync(descriptor) == 0
|
|
392
|
+
else {
|
|
393
|
+
throw ApplicationNamespaceError.mutationFailed
|
|
394
|
+
}
|
|
395
|
+
var namedAfter = stat()
|
|
396
|
+
let recheckStatus = leaf.withCString {
|
|
397
|
+
fstatat(parent, $0, &namedAfter, AT_SYMLINK_NOFOLLOW)
|
|
398
|
+
}
|
|
399
|
+
guard recheckStatus == 0,
|
|
400
|
+
namedAfter.st_dev == opened.st_dev,
|
|
401
|
+
namedAfter.st_ino == opened.st_ino,
|
|
402
|
+
(namedAfter.st_mode & 0o777) == newMode,
|
|
403
|
+
fsync(parent) == 0
|
|
404
|
+
else {
|
|
405
|
+
throw ApplicationNamespaceError.mutationFailed
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
private func directoryExists(parent: Int32, leaf: String) throws -> Bool {
|
|
410
|
+
var metadata = stat()
|
|
411
|
+
let status = leaf.withCString {
|
|
412
|
+
fstatat(parent, $0, &metadata, AT_SYMLINK_NOFOLLOW)
|
|
413
|
+
}
|
|
414
|
+
if status != 0, errno == ENOENT { return false }
|
|
415
|
+
guard status == 0, (metadata.st_mode & S_IFMT) == S_IFDIR else {
|
|
416
|
+
throw ApplicationNamespaceError.invalidBinding
|
|
417
|
+
}
|
|
418
|
+
return true
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
private static func openValidatedDirectory(
|
|
422
|
+
_ path: String,
|
|
423
|
+
exactApplicationsDirectory: Bool
|
|
424
|
+
) throws -> Int32 {
|
|
425
|
+
guard DarwinACLValidator.rootOwnedDirectoryAncestryHasNoExtendedACL(to: path) else {
|
|
426
|
+
throw ApplicationNamespaceError.unsafeDirectory
|
|
427
|
+
}
|
|
428
|
+
let descriptor = Darwin.open(path, O_RDONLY | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW)
|
|
429
|
+
guard descriptor >= 0 else { throw ApplicationNamespaceError.unsafeDirectory }
|
|
430
|
+
let valid: Bool
|
|
431
|
+
if exactApplicationsDirectory {
|
|
432
|
+
valid = DarwinACLValidator.descriptorIsSafeRootOwnedDirectory(
|
|
433
|
+
descriptor,
|
|
434
|
+
exactPath: "/Applications"
|
|
435
|
+
)
|
|
436
|
+
} else {
|
|
437
|
+
var metadata = stat()
|
|
438
|
+
valid = fstat(descriptor, &metadata) == 0 &&
|
|
439
|
+
(metadata.st_mode & S_IFMT) == S_IFDIR &&
|
|
440
|
+
metadata.st_uid == 0 &&
|
|
441
|
+
(metadata.st_mode & 0o077) == 0 &&
|
|
442
|
+
DarwinACLValidator.descriptorHasNoExtendedACL(descriptor)
|
|
443
|
+
}
|
|
444
|
+
guard valid else {
|
|
445
|
+
Darwin.close(descriptor)
|
|
446
|
+
throw ApplicationNamespaceError.unsafeDirectory
|
|
447
|
+
}
|
|
448
|
+
return descriptor
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
private extension String {
|
|
453
|
+
var deletingLastPathComponent: String {
|
|
454
|
+
URL(fileURLWithPath: self).deletingLastPathComponent().path
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
enum ApplicationNamespaceError: Error {
|
|
459
|
+
case invalidBinding
|
|
460
|
+
case unsafeDirectory
|
|
461
|
+
case mutationFailed
|
|
462
|
+
case couldNotSync
|
|
463
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import Darwin
|
|
2
|
+
import Foundation
|
|
3
|
+
import RecordingsUpdateProtocol
|
|
4
|
+
|
|
5
|
+
enum ApplicationProcessQuiescence {
|
|
6
|
+
/// Reject activation while any executable loaded from the live bundle is still
|
|
7
|
+
/// running. The one authenticated update client servicing this XPC request is
|
|
8
|
+
/// exempt only when its kernel-reported executable path is the fixed client path.
|
|
9
|
+
static func requireQuiescence(excludingAuthenticatedClientPID excludedPID: pid_t?) throws {
|
|
10
|
+
let capacity = 16_384
|
|
11
|
+
var processIdentifiers = [pid_t](repeating: 0, count: capacity)
|
|
12
|
+
let count = processIdentifiers.withUnsafeMutableBytes { bytes in
|
|
13
|
+
proc_listallpids(bytes.baseAddress, Int32(bytes.count))
|
|
14
|
+
}
|
|
15
|
+
guard count >= 0, Int(count) < capacity else {
|
|
16
|
+
throw ApplicationProcessQuiescenceError.enumerationFailed
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let liveContentsPrefix = RecordingsUpdateConstants.applicationPath + "/Contents/"
|
|
20
|
+
let authenticatedClientPath = RecordingsUpdateConstants.applicationPath + "/"
|
|
21
|
+
+ RecordingsUpdateConstants.updateClientRelativePath
|
|
22
|
+
for processIdentifier in processIdentifiers.prefix(Int(count)) where processIdentifier > 0 {
|
|
23
|
+
var pathBytes = [CChar](repeating: 0, count: Int(PROC_PIDPATHINFO_MAXSIZE))
|
|
24
|
+
let pathLength = pathBytes.withUnsafeMutableBufferPointer { buffer in
|
|
25
|
+
proc_pidpath(
|
|
26
|
+
processIdentifier,
|
|
27
|
+
buffer.baseAddress,
|
|
28
|
+
UInt32(buffer.count)
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
if pathLength <= 0 {
|
|
32
|
+
// PIDs can disappear between enumeration and inspection. A live PID
|
|
33
|
+
// that cannot be inspected remains observable with kill(0), so only
|
|
34
|
+
// fail closed for that case.
|
|
35
|
+
if kill(processIdentifier, 0) == 0 || errno == EPERM {
|
|
36
|
+
throw ApplicationProcessQuiescenceError.processInspectionFailed
|
|
37
|
+
}
|
|
38
|
+
continue
|
|
39
|
+
}
|
|
40
|
+
let executablePath = String(cString: pathBytes)
|
|
41
|
+
guard executablePath.hasPrefix(liveContentsPrefix) else { continue }
|
|
42
|
+
if processIdentifier == excludedPID,
|
|
43
|
+
executablePath == authenticatedClientPath {
|
|
44
|
+
continue
|
|
45
|
+
}
|
|
46
|
+
throw ApplicationProcessQuiescenceError.liveBundleProcessRunning
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
enum ApplicationProcessQuiescenceError: Error {
|
|
52
|
+
case enumerationFailed
|
|
53
|
+
case processInspectionFailed
|
|
54
|
+
case liveBundleProcessRunning
|
|
55
|
+
}
|