Skip to content

Commit 2341a33

Browse files
committed
[cas] Add ValidateCAS action to ensure data coherence
Use llvm-cas's new validate-if-needed action to ensure the correctness of CAS data in the case of power failure or similar situations. This action is added to prepareForBuilding to ensure that it is run before any other other CAS accesses. Note that validate-if-needed internally avoids performing unnecessary work - in particular, it only validates data once for every machine boot. rdar://150295950
1 parent 8258eb5 commit 2341a33

File tree

12 files changed

+343
-13
lines changed

12 files changed

+343
-13
lines changed

Sources/SWBBuildSystem/BuildOperation.swift

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,7 @@ package final class BuildOperation: BuildSystemOperation {
419419
}
420420

421421
// Perform any needed steps before we kick off the build.
422-
if let (warnings, errors) = prepareForBuilding() {
422+
if let (warnings, errors) = await prepareForBuilding() {
423423
// Emit any warnings and errors. If there were any errors, then bail out.
424424
for message in warnings { buildOutputDelegate.warning(message) }
425425
for message in errors { buildOutputDelegate.error(message) }
@@ -809,7 +809,7 @@ package final class BuildOperation: BuildSystemOperation {
809809
return delegate.buildComplete(self, status: effectiveStatus, delegate: buildOutputDelegate, metrics: .init(counters: aggregatedCounters))
810810
}
811811

812-
func prepareForBuilding() -> ([String], [String])? {
812+
func prepareForBuilding() async -> ([String], [String])? {
813813
let warnings = [String]() // Not presently used
814814
var errors = [String]()
815815

@@ -829,9 +829,59 @@ package final class BuildOperation: BuildSystemOperation {
829829
}
830830
}
831831

832+
if UserDefaults.enableCASValidation {
833+
for info in buildDescription.casValidationInfos {
834+
do {
835+
try await validateCAS(info)
836+
} catch {
837+
errors.append("cas validation failed for \(info.options.casPath.str)")
838+
}
839+
}
840+
}
841+
832842
return (warnings.count > 0 || errors.count > 0) ? (warnings, errors) : nil
833843
}
834844

845+
func validateCAS(_ info: BuildDescription.CASValidationInfo) async throws {
846+
assert(UserDefaults.enableCASValidation)
847+
848+
let casPath = info.options.casPath
849+
850+
let signatureCtx = InsecureHashContext()
851+
signatureCtx.add(string: "ValidateCAS")
852+
signatureCtx.add(string: casPath.str)
853+
let signature = signatureCtx.signature
854+
855+
let activityId = delegate.beginActivity(self, ruleInfo: "ValidateCAS \(casPath.str)", executionDescription: "Validate CAS contents at \(casPath.str)", signature: signature, target: nil, parentActivity: nil)
856+
var status: BuildOperationTaskEnded.Status = .failed
857+
defer {
858+
delegate.endActivity(self, id: activityId, signature: signature, status: status)
859+
}
860+
861+
var commandLine = [
862+
info.llvmCasExec.str,
863+
"-cas", casPath.str,
864+
"-validate-if-needed",
865+
"-check-hash",
866+
"-allow-recovery",
867+
]
868+
if let pluginPath = info.options.pluginPath {
869+
commandLine.append(contentsOf: [
870+
"-fcas-plugin-path", pluginPath.str
871+
])
872+
}
873+
let result: Processes.ExecutionResult = try await clientDelegate.executeExternalTool(commandLine: commandLine)
874+
// In a task we might use a discovered tool info to detect if the tool supports validation, but without that scaffolding, just check the specific error.
875+
if result.exitStatus == .exit(1) && result.stderr.contains(ByteString("Unknown command line argument '-validate-if-needed'")) {
876+
delegate.emit(data: ByteString("validation not supported").bytes, for: activityId, signature: signature)
877+
status = .succeeded
878+
} else {
879+
delegate.emit(data: ByteString(result.stderr).bytes, for: activityId, signature: signature)
880+
delegate.emit(data: ByteString(result.stdout).bytes, for: activityId, signature: signature)
881+
status = result.exitStatus.isSuccess ? .succeeded : result.exitStatus.wasCanceled ? .cancelled : .failed
882+
}
883+
}
884+
835885
/// Cancel the executing build operation.
836886
package func cancel() {
837887
queue.blocking_sync() {

Sources/SWBCore/Settings/BuiltinMacros.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1126,6 +1126,7 @@ public final class BuiltinMacros {
11261126
public static let TAPI_HEADER_SEARCH_PATHS = BuiltinMacros.declarePathListMacro("TAPI_HEADER_SEARCH_PATHS")
11271127
public static let USE_HEADER_SYMLINKS = BuiltinMacros.declareBooleanMacro("USE_HEADER_SYMLINKS")
11281128
public static let USE_HIERARCHICAL_LAYOUT_FOR_COPIED_ASIDE_PRODUCTS = BuiltinMacros.declareBooleanMacro("USE_HIERARCHICAL_LAYOUT_FOR_COPIED_ASIDE_PRODUCTS")
1129+
public static let VALIDATE_CAS_EXEC = BuiltinMacros.declareStringMacro("VALIDATE_CAS_EXEC")
11291130
public static let VALIDATE_PLIST_FILES_WHILE_COPYING = BuiltinMacros.declareBooleanMacro("VALIDATE_PLIST_FILES_WHILE_COPYING")
11301131
public static let VALIDATE_PRODUCT = BuiltinMacros.declareBooleanMacro("VALIDATE_PRODUCT")
11311132
public static let VALIDATE_DEPENDENCIES = BuiltinMacros.declareEnumMacro("VALIDATE_DEPENDENCIES") as EnumMacroDeclaration<BooleanWarningLevel>
@@ -2327,6 +2328,7 @@ public final class BuiltinMacros {
23272328
USE_HEADER_SYMLINKS,
23282329
USE_HIERARCHICAL_LAYOUT_FOR_COPIED_ASIDE_PRODUCTS,
23292330
VALIDATE_PLIST_FILES_WHILE_COPYING,
2331+
VALIDATE_CAS_EXEC,
23302332
VALIDATE_PRODUCT,
23312333
VALIDATE_DEPENDENCIES,
23322334
VALIDATE_DEVELOPMENT_ASSET_PATHS,

Sources/SWBCore/TaskGeneration.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -768,7 +768,7 @@ extension CoreClientTargetDiagnosticProducingDelegate {
768768
private let externalToolExecutionQueue = AsyncOperationQueue(concurrentTasks: ProcessInfo.processInfo.activeProcessorCount)
769769

770770
extension CoreClientDelegate {
771-
func executeExternalTool(commandLine: [String], workingDirectory: String? = nil, environment: [String: String] = [:]) async throws -> Processes.ExecutionResult {
771+
package func executeExternalTool(commandLine: [String], workingDirectory: String? = nil, environment: [String: String] = [:]) async throws -> Processes.ExecutionResult {
772772
switch try await executeExternalTool(commandLine: commandLine, workingDirectory: workingDirectory, environment: environment) {
773773
case .deferred:
774774
guard let url = commandLine.first.map(URL.init(fileURLWithPath:)) else {

0 commit comments

Comments
 (0)