@logbrew/react-native 0.1.10 → 0.1.12
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/LogBrewReactNative.podspec +25 -3
- package/README.md +174 -4
- package/android/src/main/java/co/logbrew/reactnative/EventRecordStore.java +611 -0
- package/android/src/main/java/co/logbrew/reactnative/FatalStoreModuleImpl.java +129 -0
- package/android/src/newarch/java/co/logbrew/reactnative/FatalStoreModule.java +26 -0
- package/android/src/oldarch/java/co/logbrew/reactnative/FatalStoreModule.java +26 -0
- package/apple-native-diagnostics.d.ts +52 -0
- package/apple-native-diagnostics.js +286 -0
- package/expo.cjs +46 -0
- package/expo.d.ts +16 -0
- package/expo.js +5 -0
- package/fatal-replay.cjs +12 -1
- package/global-errors.cjs +3 -1
- package/index.cjs +86 -2
- package/index.d.cts +7 -0
- package/index.d.ts +7 -0
- package/index.js +86 -2
- package/index.native.d.ts +47 -1
- package/index.native.js +81 -1
- package/ios/AppleDiagnostics/LBRNAppleDiagnosticsModule.h +4 -0
- package/ios/AppleDiagnostics/LBRNAppleDiagnosticsModule.mm +56 -0
- package/ios/AppleDiagnostics/LBRNAppleNativeDiagnostics.swift +293 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/AutomaticTelemetryContext.swift +72 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngine.swift +270 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngineAutomatic.swift +331 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngineDurable.swift +148 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngineQueue.swift +343 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryLifecycle.swift +70 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/DurableDeliveryStore.swift +258 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/DurableDeliveryStoreRecovery.swift +330 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/EventEncoding.swift +114 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/IssueDiagnostics.swift +385 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/LifecycleTrace.swift +138 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/LogBrewClient.swift +358 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/LogBrewLogger.swift +262 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/LogBrewTrace.swift +357 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/Metadata.swift +86 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/NativeStackFrame.swift +27 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/ProductTimeline.swift +228 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/PublicTypes.swift +334 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/TelemetryContext.swift +197 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/TelemetryContextValidation.swift +337 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/TraceEvidence.swift +79 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/Transport.swift +204 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/URLSessionTrace.swift +277 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/URLSessionTracer.swift +151 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/Validation.swift +165 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/CrashEngine.swift +125 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/CrashReportSanitizer.swift +139 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/CrashStorageDirectory.swift +124 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeArtifactIdentity.swift +137 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashCapture.swift +229 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashPublic.swift +386 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashReplay.swift +317 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangIncidentStore.swift +321 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangWatchdog.swift +386 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangWatchdogContracts.swift +84 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangWatchdogSystem.swift +139 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeMainThreadStackCapture.swift +84 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeStackFrameSanitizer.swift +170 -0
- package/ios/GeneratedAppleDiagnostics/SOURCE-MANIFEST.json +43 -0
- package/ios/LBRNEventRecordStore.h +29 -0
- package/ios/LBRNEventRecordStore.m +746 -0
- package/ios/LBRNFatalStoreModule.mm +159 -14
- package/ios/LBRNPrivateStorage.h +7 -0
- package/ios/LBRNPrivateStorage.m +38 -0
- package/package.json +47 -4
- package/persistent-delivery.native.js +282 -0
- package/promise-rejections.cjs +3 -1
- package/scripts/sync-apple-native-sources.mjs +93 -0
- package/src/NativeLogBrewAppleDiagnostics.ts +12 -0
- package/src/NativeLogBrewFatalStore.ts +9 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// Generated by scripts/sync-apple-native-sources.mjs.
|
|
2
|
+
// Edit the canonical Swift source, then regenerate this package boundary.
|
|
3
|
+
import Foundation
|
|
4
|
+
@preconcurrency import KSCrash
|
|
5
|
+
|
|
6
|
+
enum CrashMonitor: Equatable, Hashable {
|
|
7
|
+
case machException
|
|
8
|
+
case signal
|
|
9
|
+
case cppException
|
|
10
|
+
case objectiveCException
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
struct CrashEngineConfiguration: Equatable {
|
|
14
|
+
let storageDirectory: URL
|
|
15
|
+
let maxStoredReports: Int
|
|
16
|
+
let monitors: Set<CrashMonitor>
|
|
17
|
+
let includesMemory: Bool
|
|
18
|
+
let includesQueueNames: Bool
|
|
19
|
+
let includesConsoleLog: Bool
|
|
20
|
+
let artifactIdentity: NativeArtifactIdentityValue?
|
|
21
|
+
let deletionIsExplicit: Bool
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
protocol CrashEngineDriving: AnyObject {
|
|
25
|
+
func install(configuration: CrashEngineConfiguration) throws -> any CrashReportStoring
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
protocol CrashReportStoring: AnyObject {
|
|
29
|
+
var reportIDs: [Int64] { get }
|
|
30
|
+
func report(for id: Int64) -> [String: Any]?
|
|
31
|
+
func deleteReport(with id: Int64)
|
|
32
|
+
func deleteAllReports()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
final class ProcessCrashCaptureOwnership: @unchecked Sendable {
|
|
36
|
+
static let shared = ProcessCrashCaptureOwnership()
|
|
37
|
+
|
|
38
|
+
private let lock = NSLock()
|
|
39
|
+
private weak var owner: AnyObject?
|
|
40
|
+
private var claimed = false
|
|
41
|
+
|
|
42
|
+
func claim(_ candidate: AnyObject) throws {
|
|
43
|
+
lock.lock()
|
|
44
|
+
defer { lock.unlock() }
|
|
45
|
+
if let owner {
|
|
46
|
+
guard owner === candidate else {
|
|
47
|
+
throw NativeCrashError(.ownershipConflict)
|
|
48
|
+
}
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
guard !claimed else {
|
|
52
|
+
throw NativeCrashError(.ownershipConflict)
|
|
53
|
+
}
|
|
54
|
+
owner = candidate
|
|
55
|
+
claimed = true
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
final class KSCrashEngineDriver: CrashEngineDriving {
|
|
60
|
+
func install(configuration: CrashEngineConfiguration) throws -> any CrashReportStoring {
|
|
61
|
+
let storeConfiguration = CrashReportStoreConfiguration()
|
|
62
|
+
storeConfiguration.appName = "logbrew-native-crash"
|
|
63
|
+
storeConfiguration.maxReportCount = configuration.maxStoredReports
|
|
64
|
+
storeConfiguration.reportCleanupPolicy = configuration.deletionIsExplicit ? .never : .always
|
|
65
|
+
|
|
66
|
+
let engineConfiguration = KSCrashConfiguration()
|
|
67
|
+
engineConfiguration.installPath = configuration.storageDirectory.path
|
|
68
|
+
engineConfiguration.reportStoreConfiguration = storeConfiguration
|
|
69
|
+
var monitors: MonitorType = []
|
|
70
|
+
if configuration.monitors.contains(.machException) {
|
|
71
|
+
monitors.insert(.machException)
|
|
72
|
+
}
|
|
73
|
+
if configuration.monitors.contains(.signal) {
|
|
74
|
+
monitors.insert(.signal)
|
|
75
|
+
}
|
|
76
|
+
if configuration.monitors.contains(.cppException) {
|
|
77
|
+
monitors.insert(.cppException)
|
|
78
|
+
}
|
|
79
|
+
if configuration.monitors.contains(.objectiveCException) {
|
|
80
|
+
monitors.insert(.nsException)
|
|
81
|
+
}
|
|
82
|
+
engineConfiguration.monitors = monitors
|
|
83
|
+
engineConfiguration.userInfoJSON = configuration.artifactIdentity?.crashUserInfoJSON
|
|
84
|
+
engineConfiguration.deadlockWatchdogInterval = 0
|
|
85
|
+
engineConfiguration.enableQueueNameSearch = configuration.includesQueueNames
|
|
86
|
+
engineConfiguration.enableMemoryIntrospection = configuration.includesMemory
|
|
87
|
+
engineConfiguration.addConsoleLogToReport = configuration.includesConsoleLog
|
|
88
|
+
engineConfiguration.printPreviousLogOnStartup = false
|
|
89
|
+
engineConfiguration.enableSigTermMonitoring = false
|
|
90
|
+
|
|
91
|
+
do {
|
|
92
|
+
try KSCrash.shared.install(with: engineConfiguration)
|
|
93
|
+
} catch {
|
|
94
|
+
throw NativeCrashError(.engineInstallFailed)
|
|
95
|
+
}
|
|
96
|
+
guard let reportStore = KSCrash.shared.reportStore else {
|
|
97
|
+
throw NativeCrashError(.engineInstallFailed)
|
|
98
|
+
}
|
|
99
|
+
return KSCrashReportStoreAdapter(store: reportStore)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private final class KSCrashReportStoreAdapter: CrashReportStoring {
|
|
104
|
+
private let store: CrashReportStore
|
|
105
|
+
|
|
106
|
+
init(store: CrashReportStore) {
|
|
107
|
+
self.store = store
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
var reportIDs: [Int64] {
|
|
111
|
+
store.reportIDs.map(\.int64Value)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
func report(for id: Int64) -> [String: Any]? {
|
|
115
|
+
store.report(for: id)?.value
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
func deleteReport(with id: Int64) {
|
|
119
|
+
store.deleteReport(with: id)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
func deleteAllReports() {
|
|
123
|
+
store.deleteAllReports()
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// Generated by scripts/sync-apple-native-sources.mjs.
|
|
2
|
+
// Edit the canonical Swift source, then regenerate this package boundary.
|
|
3
|
+
import CryptoKit
|
|
4
|
+
import Foundation
|
|
5
|
+
|
|
6
|
+
struct CrashReportSanitizer {
|
|
7
|
+
let maxReplayBytes: Int
|
|
8
|
+
let ownerNonce: UUID
|
|
9
|
+
|
|
10
|
+
func makeRecord(reportID: Int64, rawReport: [String: Any]) throws -> NativeCrashRecord {
|
|
11
|
+
let data = try canonicalData(rawReport)
|
|
12
|
+
guard let report = rawReport["report"] as? [String: Any],
|
|
13
|
+
let rawID = report["id"] as? String,
|
|
14
|
+
let uuid = UUID(uuidString: rawID),
|
|
15
|
+
let rawTimestamp = report["timestamp"] as? String,
|
|
16
|
+
let timestamp = NativeCrashTimestamp(rawTimestamp)
|
|
17
|
+
else {
|
|
18
|
+
throw NativeCrashError(.reportCorrupt)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let crash = rawReport["crash"] as? [String: Any]
|
|
22
|
+
let error = crash?["error"] as? [String: Any]
|
|
23
|
+
let nativeStackFrames = NativeStackFrameSanitizer().frames(from: rawReport)
|
|
24
|
+
let artifactIdentity = try NativeArtifactIdentityValue.persistedIdentity(in: rawReport)
|
|
25
|
+
return NativeCrashRecord(
|
|
26
|
+
eventID: uuid.uuidString.lowercased(),
|
|
27
|
+
timestamp: timestamp.normalized,
|
|
28
|
+
mechanism: mechanism(error?["type"] as? String),
|
|
29
|
+
nativeStackFrames: nativeStackFrames,
|
|
30
|
+
artifactIdentity: artifactIdentity,
|
|
31
|
+
hangState: nil,
|
|
32
|
+
hangDurationMs: nil,
|
|
33
|
+
source: .engine(reportID: reportID),
|
|
34
|
+
digest: Data(SHA256.hash(data: data)),
|
|
35
|
+
ownerNonce: ownerNonce,
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
func digest(_ rawReport: [String: Any]) throws -> Data {
|
|
40
|
+
try Data(SHA256.hash(data: canonicalData(rawReport)))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private func canonicalData(_ rawReport: [String: Any]) throws -> Data {
|
|
44
|
+
guard JSONSerialization.isValidJSONObject(rawReport) else {
|
|
45
|
+
throw NativeCrashError(.reportCorrupt)
|
|
46
|
+
}
|
|
47
|
+
do {
|
|
48
|
+
let data = try JSONSerialization.data(withJSONObject: rawReport, options: [.sortedKeys])
|
|
49
|
+
guard data.count <= maxReplayBytes else {
|
|
50
|
+
throw NativeCrashError(.reportCorrupt)
|
|
51
|
+
}
|
|
52
|
+
return data
|
|
53
|
+
} catch let error as NativeCrashError {
|
|
54
|
+
throw error
|
|
55
|
+
} catch {
|
|
56
|
+
throw NativeCrashError(.reportCorrupt)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private func mechanism(_ rawValue: String?) -> NativeCrashMechanism {
|
|
61
|
+
switch rawValue {
|
|
62
|
+
case "signal": .signal
|
|
63
|
+
case "mach": .machException
|
|
64
|
+
case "cpp_exception": .cppException
|
|
65
|
+
case "nsexception": .objectiveCException
|
|
66
|
+
case "memory_termination": .memoryTermination
|
|
67
|
+
case "deadlock": .deadlock
|
|
68
|
+
default: .unknown
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
struct NativeCrashTimestamp: Comparable {
|
|
74
|
+
let normalized: String
|
|
75
|
+
private let wholeSecond: String
|
|
76
|
+
private let fractionalNanoseconds: String
|
|
77
|
+
|
|
78
|
+
init?(_ raw: String) {
|
|
79
|
+
guard raw.utf8.count <= 64,
|
|
80
|
+
!raw.unicodeScalars.contains(where: CharacterSet.controlCharacters.contains),
|
|
81
|
+
let components = Self.components(in: raw)
|
|
82
|
+
else {
|
|
83
|
+
return nil
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let formatter = ISO8601DateFormatter()
|
|
87
|
+
formatter.formatOptions = [.withInternetDateTime]
|
|
88
|
+
guard let date = formatter.date(from: components.wholeSecond) else {
|
|
89
|
+
return nil
|
|
90
|
+
}
|
|
91
|
+
wholeSecond = formatter.string(from: date)
|
|
92
|
+
fractionalNanoseconds = components.fractional
|
|
93
|
+
.padding(toLength: 9, withPad: "0", startingAt: 0)
|
|
94
|
+
if components.fractional.isEmpty {
|
|
95
|
+
normalized = wholeSecond
|
|
96
|
+
} else {
|
|
97
|
+
normalized = "\(wholeSecond.dropLast()).\(components.fractional)Z"
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
static func < (lhs: NativeCrashTimestamp, rhs: NativeCrashTimestamp) -> Bool {
|
|
102
|
+
if lhs.wholeSecond != rhs.wholeSecond {
|
|
103
|
+
return lhs.wholeSecond < rhs.wholeSecond
|
|
104
|
+
}
|
|
105
|
+
return lhs.fractionalNanoseconds < rhs.fractionalNanoseconds
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
static func == (lhs: NativeCrashTimestamp, rhs: NativeCrashTimestamp) -> Bool {
|
|
109
|
+
lhs.wholeSecond == rhs.wholeSecond
|
|
110
|
+
&& lhs.fractionalNanoseconds == rhs.fractionalNanoseconds
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private static func components(in timestamp: String) -> (wholeSecond: String, fractional: String)? {
|
|
114
|
+
guard let timeStart = timestamp.firstIndex(of: "T") else {
|
|
115
|
+
return nil
|
|
116
|
+
}
|
|
117
|
+
guard let separator = timestamp[timeStart...].firstIndex(of: ".") else {
|
|
118
|
+
return (timestamp, "")
|
|
119
|
+
}
|
|
120
|
+
let fractionalStart = timestamp.index(after: separator)
|
|
121
|
+
let suffix = timestamp[fractionalStart...]
|
|
122
|
+
let digits = suffix.prefix {
|
|
123
|
+
$0 >= "0" && $0 <= "9"
|
|
124
|
+
}
|
|
125
|
+
guard (1 ... 9).contains(digits.count),
|
|
126
|
+
let timezoneStart = timestamp.index(
|
|
127
|
+
fractionalStart,
|
|
128
|
+
offsetBy: digits.count,
|
|
129
|
+
limitedBy: timestamp.endIndex,
|
|
130
|
+
)
|
|
131
|
+
else {
|
|
132
|
+
return nil
|
|
133
|
+
}
|
|
134
|
+
return (
|
|
135
|
+
String(timestamp[..<separator] + timestamp[timezoneStart...]),
|
|
136
|
+
String(digits),
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// Generated by scripts/sync-apple-native-sources.mjs.
|
|
2
|
+
// Edit the canonical Swift source, then regenerate this package boundary.
|
|
3
|
+
import Darwin
|
|
4
|
+
import Foundation
|
|
5
|
+
|
|
6
|
+
enum CrashStorageDirectory {
|
|
7
|
+
static func normalized(_ url: URL) -> URL {
|
|
8
|
+
let parent = url.deletingLastPathComponent().resolvingSymlinksInPath().standardizedFileURL
|
|
9
|
+
return parent.appendingPathComponent(url.lastPathComponent, isDirectory: true).standardizedFileURL
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
static func prepare(_ url: URL) throws -> CrashStorageLease {
|
|
13
|
+
let path = url.path
|
|
14
|
+
try ensurePrivateDirectory(at: url, path: path)
|
|
15
|
+
try protectDirectory(at: url, path: path)
|
|
16
|
+
return try openLease(path: path)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
private static func ensurePrivateDirectory(at url: URL, path: String) throws {
|
|
20
|
+
var info = stat()
|
|
21
|
+
if lstat(path, &info) == 0 {
|
|
22
|
+
guard info.st_mode & S_IFMT == S_IFDIR,
|
|
23
|
+
info.st_mode & S_IFMT != S_IFLNK
|
|
24
|
+
else {
|
|
25
|
+
throw NativeCrashError(.storageUnsupported)
|
|
26
|
+
}
|
|
27
|
+
} else if errno == ENOENT {
|
|
28
|
+
do {
|
|
29
|
+
try FileManager.default.createDirectory(
|
|
30
|
+
at: url,
|
|
31
|
+
withIntermediateDirectories: false,
|
|
32
|
+
attributes: [.posixPermissions: NSNumber(value: 0o700)],
|
|
33
|
+
)
|
|
34
|
+
} catch {
|
|
35
|
+
throw NativeCrashError(.storageUnsupported)
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
throw NativeCrashError(.storageUnsupported)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private static func protectDirectory(at url: URL, path: String) throws {
|
|
43
|
+
do {
|
|
44
|
+
var attributes: [FileAttributeKey: Any] = [.posixPermissions: NSNumber(value: 0o700)]
|
|
45
|
+
#if os(iOS) || os(tvOS) || os(watchOS)
|
|
46
|
+
attributes[.protectionKey] = FileProtectionType.completeUntilFirstUserAuthentication
|
|
47
|
+
#endif
|
|
48
|
+
try FileManager.default.setAttributes(attributes, ofItemAtPath: path)
|
|
49
|
+
var values = URLResourceValues()
|
|
50
|
+
values.isExcludedFromBackup = true
|
|
51
|
+
var mutableURL = url
|
|
52
|
+
try mutableURL.setResourceValues(values)
|
|
53
|
+
} catch {
|
|
54
|
+
throw NativeCrashError(.storageUnsupported)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private static func openLease(path: String) throws -> CrashStorageLease {
|
|
59
|
+
let descriptor = open(path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC)
|
|
60
|
+
guard descriptor >= 0 else {
|
|
61
|
+
throw NativeCrashError(.storageUnsupported)
|
|
62
|
+
}
|
|
63
|
+
var opened = stat()
|
|
64
|
+
var info = stat()
|
|
65
|
+
guard fchmod(descriptor, S_IRWXU) == 0,
|
|
66
|
+
fstat(descriptor, &opened) == 0,
|
|
67
|
+
lstat(path, &info) == 0,
|
|
68
|
+
opened.st_mode & S_IFMT == S_IFDIR,
|
|
69
|
+
opened.st_mode & 0o077 == 0,
|
|
70
|
+
opened.st_dev == info.st_dev,
|
|
71
|
+
opened.st_ino == info.st_ino
|
|
72
|
+
else {
|
|
73
|
+
close(descriptor)
|
|
74
|
+
throw NativeCrashError(.storageUnsupported)
|
|
75
|
+
}
|
|
76
|
+
return CrashStorageLease(
|
|
77
|
+
descriptor: descriptor,
|
|
78
|
+
path: path,
|
|
79
|
+
device: opened.st_dev,
|
|
80
|
+
inode: opened.st_ino,
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
final class CrashStorageLease: @unchecked Sendable {
|
|
86
|
+
private let descriptor: Int32
|
|
87
|
+
private let path: String
|
|
88
|
+
private let device: dev_t
|
|
89
|
+
private let inode: ino_t
|
|
90
|
+
|
|
91
|
+
init(descriptor: Int32, path: String, device: dev_t, inode: ino_t) {
|
|
92
|
+
self.descriptor = descriptor
|
|
93
|
+
self.path = path
|
|
94
|
+
self.device = device
|
|
95
|
+
self.inode = inode
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
deinit {
|
|
99
|
+
close(descriptor)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
func verify() throws {
|
|
103
|
+
var opened = stat()
|
|
104
|
+
var current = stat()
|
|
105
|
+
guard fstat(descriptor, &opened) == 0,
|
|
106
|
+
lstat(path, ¤t) == 0,
|
|
107
|
+
opened.st_mode & S_IFMT == S_IFDIR,
|
|
108
|
+
opened.st_mode & 0o077 == 0,
|
|
109
|
+
opened.st_dev == device,
|
|
110
|
+
opened.st_ino == inode,
|
|
111
|
+
current.st_dev == device,
|
|
112
|
+
current.st_ino == inode
|
|
113
|
+
else {
|
|
114
|
+
throw NativeCrashError(.storageUnsupported)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
func synchronize() throws {
|
|
119
|
+
try verify()
|
|
120
|
+
guard fsync(descriptor) == 0 else {
|
|
121
|
+
throw NativeCrashError(.storageUnsupported)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// Generated by scripts/sync-apple-native-sources.mjs.
|
|
2
|
+
// Edit the canonical Swift source, then regenerate this package boundary.
|
|
3
|
+
import Foundation
|
|
4
|
+
|
|
5
|
+
@objc(LBRNAppleNativeArtifactIdentity)
|
|
6
|
+
@objcMembers
|
|
7
|
+
public final class NativeArtifactIdentity: NSObject, @unchecked Sendable {
|
|
8
|
+
public let projectId: String
|
|
9
|
+
@nonobjc public let release: String
|
|
10
|
+
public let environment: String
|
|
11
|
+
public let service: String
|
|
12
|
+
|
|
13
|
+
public init(
|
|
14
|
+
projectId: String,
|
|
15
|
+
release: String,
|
|
16
|
+
environment: String,
|
|
17
|
+
service: String,
|
|
18
|
+
) throws {
|
|
19
|
+
guard UUID(uuidString: projectId)?.uuidString.lowercased() == projectId,
|
|
20
|
+
Self.isExactContext(release),
|
|
21
|
+
Self.isExactContext(environment),
|
|
22
|
+
Self.isExactContext(service)
|
|
23
|
+
else {
|
|
24
|
+
throw NativeCrashError(.invalidConfiguration)
|
|
25
|
+
}
|
|
26
|
+
self.projectId = projectId
|
|
27
|
+
self.release = release
|
|
28
|
+
self.environment = environment
|
|
29
|
+
self.service = service
|
|
30
|
+
super.init()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
override public func isEqual(_ object: Any?) -> Bool {
|
|
34
|
+
guard let other = object as? NativeArtifactIdentity else {
|
|
35
|
+
return false
|
|
36
|
+
}
|
|
37
|
+
return projectId == other.projectId
|
|
38
|
+
&& release == other.release
|
|
39
|
+
&& environment == other.environment
|
|
40
|
+
&& service == other.service
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
override public var hash: Int {
|
|
44
|
+
var hasher = Hasher()
|
|
45
|
+
hasher.combine(projectId)
|
|
46
|
+
hasher.combine(release)
|
|
47
|
+
hasher.combine(environment)
|
|
48
|
+
hasher.combine(service)
|
|
49
|
+
return hasher.finalize()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
override public var description: String {
|
|
53
|
+
"NativeArtifactIdentity(configured: true)"
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private static func isExactContext(_ value: String) -> Bool {
|
|
57
|
+
guard (1 ... 256).contains(value.utf8.count),
|
|
58
|
+
value == value.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
59
|
+
!value.unicodeScalars.contains(where: CharacterSet.controlCharacters.contains)
|
|
60
|
+
else {
|
|
61
|
+
return false
|
|
62
|
+
}
|
|
63
|
+
return true
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
struct NativeArtifactIdentityValue: Codable, Equatable {
|
|
68
|
+
private static let reportKey = "logbrew_native_artifact_identity"
|
|
69
|
+
private static let reportFields = [
|
|
70
|
+
"environment",
|
|
71
|
+
"project_id",
|
|
72
|
+
"release",
|
|
73
|
+
"service",
|
|
74
|
+
"version",
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
let projectId: String
|
|
78
|
+
let release: String
|
|
79
|
+
let environment: String
|
|
80
|
+
let service: String
|
|
81
|
+
|
|
82
|
+
init(_ identity: NativeArtifactIdentity) {
|
|
83
|
+
projectId = identity.projectId
|
|
84
|
+
release = identity.release
|
|
85
|
+
environment = identity.environment
|
|
86
|
+
service = identity.service
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
func validatedIdentity() throws -> NativeArtifactIdentity {
|
|
90
|
+
try NativeArtifactIdentity(
|
|
91
|
+
projectId: projectId,
|
|
92
|
+
release: release,
|
|
93
|
+
environment: environment,
|
|
94
|
+
service: service,
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
var crashUserInfoJSON: [String: Any] {
|
|
99
|
+
[
|
|
100
|
+
Self.reportKey: [
|
|
101
|
+
"version": 1,
|
|
102
|
+
"project_id": projectId,
|
|
103
|
+
"release": release,
|
|
104
|
+
"environment": environment,
|
|
105
|
+
"service": service,
|
|
106
|
+
],
|
|
107
|
+
]
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
static func persistedIdentity(in rawReport: [String: Any]) throws -> NativeArtifactIdentity? {
|
|
111
|
+
guard let user = rawReport["user"] as? [String: Any],
|
|
112
|
+
let persisted = user[reportKey]
|
|
113
|
+
else {
|
|
114
|
+
return nil
|
|
115
|
+
}
|
|
116
|
+
guard let values = persisted as? [String: Any],
|
|
117
|
+
values.keys.sorted() == reportFields,
|
|
118
|
+
values["version"] as? Int == 1,
|
|
119
|
+
let projectId = values["project_id"] as? String,
|
|
120
|
+
let release = values["release"] as? String,
|
|
121
|
+
let environment = values["environment"] as? String,
|
|
122
|
+
let service = values["service"] as? String
|
|
123
|
+
else {
|
|
124
|
+
throw NativeCrashError(.reportCorrupt)
|
|
125
|
+
}
|
|
126
|
+
do {
|
|
127
|
+
return try NativeArtifactIdentity(
|
|
128
|
+
projectId: projectId,
|
|
129
|
+
release: release,
|
|
130
|
+
environment: environment,
|
|
131
|
+
service: service,
|
|
132
|
+
)
|
|
133
|
+
} catch {
|
|
134
|
+
throw NativeCrashError(.reportCorrupt)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|