@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.
Files changed (72) hide show
  1. package/LogBrewReactNative.podspec +25 -3
  2. package/README.md +174 -4
  3. package/android/src/main/java/co/logbrew/reactnative/EventRecordStore.java +611 -0
  4. package/android/src/main/java/co/logbrew/reactnative/FatalStoreModuleImpl.java +129 -0
  5. package/android/src/newarch/java/co/logbrew/reactnative/FatalStoreModule.java +26 -0
  6. package/android/src/oldarch/java/co/logbrew/reactnative/FatalStoreModule.java +26 -0
  7. package/apple-native-diagnostics.d.ts +52 -0
  8. package/apple-native-diagnostics.js +286 -0
  9. package/expo.cjs +46 -0
  10. package/expo.d.ts +16 -0
  11. package/expo.js +5 -0
  12. package/fatal-replay.cjs +12 -1
  13. package/global-errors.cjs +3 -1
  14. package/index.cjs +86 -2
  15. package/index.d.cts +7 -0
  16. package/index.d.ts +7 -0
  17. package/index.js +86 -2
  18. package/index.native.d.ts +47 -1
  19. package/index.native.js +81 -1
  20. package/ios/AppleDiagnostics/LBRNAppleDiagnosticsModule.h +4 -0
  21. package/ios/AppleDiagnostics/LBRNAppleDiagnosticsModule.mm +56 -0
  22. package/ios/AppleDiagnostics/LBRNAppleNativeDiagnostics.swift +293 -0
  23. package/ios/GeneratedAppleDiagnostics/LogBrew/AutomaticTelemetryContext.swift +72 -0
  24. package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngine.swift +270 -0
  25. package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngineAutomatic.swift +331 -0
  26. package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngineDurable.swift +148 -0
  27. package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryEngineQueue.swift +343 -0
  28. package/ios/GeneratedAppleDiagnostics/LogBrew/DeliveryLifecycle.swift +70 -0
  29. package/ios/GeneratedAppleDiagnostics/LogBrew/DurableDeliveryStore.swift +258 -0
  30. package/ios/GeneratedAppleDiagnostics/LogBrew/DurableDeliveryStoreRecovery.swift +330 -0
  31. package/ios/GeneratedAppleDiagnostics/LogBrew/EventEncoding.swift +114 -0
  32. package/ios/GeneratedAppleDiagnostics/LogBrew/IssueDiagnostics.swift +385 -0
  33. package/ios/GeneratedAppleDiagnostics/LogBrew/LifecycleTrace.swift +138 -0
  34. package/ios/GeneratedAppleDiagnostics/LogBrew/LogBrewClient.swift +358 -0
  35. package/ios/GeneratedAppleDiagnostics/LogBrew/LogBrewLogger.swift +262 -0
  36. package/ios/GeneratedAppleDiagnostics/LogBrew/LogBrewTrace.swift +357 -0
  37. package/ios/GeneratedAppleDiagnostics/LogBrew/Metadata.swift +86 -0
  38. package/ios/GeneratedAppleDiagnostics/LogBrew/NativeStackFrame.swift +27 -0
  39. package/ios/GeneratedAppleDiagnostics/LogBrew/ProductTimeline.swift +228 -0
  40. package/ios/GeneratedAppleDiagnostics/LogBrew/PublicTypes.swift +334 -0
  41. package/ios/GeneratedAppleDiagnostics/LogBrew/TelemetryContext.swift +197 -0
  42. package/ios/GeneratedAppleDiagnostics/LogBrew/TelemetryContextValidation.swift +337 -0
  43. package/ios/GeneratedAppleDiagnostics/LogBrew/TraceEvidence.swift +79 -0
  44. package/ios/GeneratedAppleDiagnostics/LogBrew/Transport.swift +204 -0
  45. package/ios/GeneratedAppleDiagnostics/LogBrew/URLSessionTrace.swift +277 -0
  46. package/ios/GeneratedAppleDiagnostics/LogBrew/URLSessionTracer.swift +151 -0
  47. package/ios/GeneratedAppleDiagnostics/LogBrew/Validation.swift +165 -0
  48. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/CrashEngine.swift +125 -0
  49. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/CrashReportSanitizer.swift +139 -0
  50. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/CrashStorageDirectory.swift +124 -0
  51. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeArtifactIdentity.swift +137 -0
  52. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashCapture.swift +229 -0
  53. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashPublic.swift +386 -0
  54. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashReplay.swift +317 -0
  55. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangIncidentStore.swift +321 -0
  56. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangWatchdog.swift +386 -0
  57. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangWatchdogContracts.swift +84 -0
  58. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeHangWatchdogSystem.swift +139 -0
  59. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeMainThreadStackCapture.swift +84 -0
  60. package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeStackFrameSanitizer.swift +170 -0
  61. package/ios/GeneratedAppleDiagnostics/SOURCE-MANIFEST.json +43 -0
  62. package/ios/LBRNEventRecordStore.h +29 -0
  63. package/ios/LBRNEventRecordStore.m +746 -0
  64. package/ios/LBRNFatalStoreModule.mm +159 -14
  65. package/ios/LBRNPrivateStorage.h +7 -0
  66. package/ios/LBRNPrivateStorage.m +38 -0
  67. package/package.json +47 -4
  68. package/persistent-delivery.native.js +282 -0
  69. package/promise-rejections.cjs +3 -1
  70. package/scripts/sync-apple-native-sources.mjs +93 -0
  71. package/src/NativeLogBrewAppleDiagnostics.ts +12 -0
  72. package/src/NativeLogBrewFatalStore.ts +9 -0
@@ -0,0 +1,746 @@
1
+ #import "LBRNEventRecordStore.h"
2
+
3
+ #import <dirent.h>
4
+ #import <errno.h>
5
+ #import <fcntl.h>
6
+ #import <limits.h>
7
+ #import <math.h>
8
+ #import <stdint.h>
9
+ #import <stdlib.h>
10
+ #import <string.h>
11
+ #import <sys/stat.h>
12
+ #import <unistd.h>
13
+
14
+ NSString *const LBRNEventRecordPrefix = @"event-";
15
+ NSString *const LBRNEventRecordSuffix = @".record";
16
+ NSString *const LBRNEventMarkerPrefix = @"accepted-";
17
+ NSString *const LBRNEventMarkerSuffix = @".marker";
18
+
19
+ static const NSUInteger LBRNEventMaximumBytes = 4 * 1024 * 1024;
20
+ static const NSUInteger LBRNEventMaximumQueueBytes = 4 * 1024 * 1024;
21
+ static const NSUInteger LBRNEventMaximumQueueRecords = 1000;
22
+ static const NSInteger LBRNEventFileSchemaVersion = 1;
23
+
24
+ @interface LBRNEventRecordStore ()
25
+ @property (nonatomic, readonly) NSURL *directoryURL;
26
+ @property (nonatomic, readonly, copy)
27
+ LBRNEventDirectoryPreparation directoryPreparation;
28
+ @property (nonatomic) BOOL poisoned;
29
+ @end
30
+
31
+ @implementation LBRNEventRecordStore
32
+
33
+ - (instancetype)initWithDirectoryURL:(NSURL *)directoryURL
34
+ {
35
+ return [self initWithDirectoryURL:directoryURL
36
+ directoryPreparation:^BOOL(__unused NSURL *preparedURL) {
37
+ return NO;
38
+ }];
39
+ }
40
+
41
+ - (instancetype)initWithDirectoryURL:(NSURL *)directoryURL
42
+ directoryPreparation:(LBRNEventDirectoryPreparation)directoryPreparation
43
+ {
44
+ self = [super init];
45
+ if (self != nil) {
46
+ _directoryURL = directoryURL;
47
+ _directoryPreparation = [directoryPreparation copy];
48
+ _poisoned = NO;
49
+ }
50
+ return self;
51
+ }
52
+
53
+ - (NSDictionary *)loadRecords
54
+ {
55
+ @synchronized(self) {
56
+ int directoryFD = [self openPreparedDirectory];
57
+ if (directoryFD < 0) {
58
+ return [self storageError];
59
+ }
60
+ @try {
61
+ NSDictionary *snapshot = [self snapshotFromDirectory:directoryFD];
62
+ return snapshot == nil
63
+ ? [self storageError]
64
+ : @{ @"status" : @"loaded", @"records" : snapshot[@"records"] };
65
+ } @finally {
66
+ close(directoryFD);
67
+ }
68
+ }
69
+ }
70
+
71
+ - (NSDictionary *)appendSerializedEvent:(NSString *)serializedEvent
72
+ eventBytes:(NSNumber *)eventBytes
73
+ {
74
+ @synchronized(self) {
75
+ NSData *eventData = [self validatedEventData:serializedEvent eventBytes:eventBytes];
76
+ if (self.poisoned || eventData == nil) {
77
+ return [self storageError];
78
+ }
79
+ int directoryFD = [self openPreparedDirectory];
80
+ if (directoryFD < 0) {
81
+ return [self storageError];
82
+ }
83
+ @try {
84
+ NSDictionary *snapshot = [self snapshotFromDirectory:directoryFD];
85
+ if (snapshot == nil
86
+ || [snapshot[@"records"] count] >= LBRNEventMaximumQueueRecords
87
+ || [snapshot[@"totalEventBytes"] unsignedLongLongValue] + eventData.length
88
+ > LBRNEventMaximumQueueBytes) {
89
+ return [self storageError];
90
+ }
91
+ unsigned long long sequence = MAX(
92
+ [snapshot[@"markerSequence"] unsignedLongLongValue],
93
+ [snapshot[@"maximumRecordSequence"] unsignedLongLongValue]);
94
+ if (sequence >= LLONG_MAX) {
95
+ return [self storageError];
96
+ }
97
+ sequence += 1;
98
+ NSDictionary *record = @{
99
+ @"schemaVersion" : @(LBRNEventFileSchemaVersion),
100
+ @"sequence" : @(sequence),
101
+ @"serializedEvent" : serializedEvent,
102
+ @"eventBytes" : eventBytes,
103
+ };
104
+ NSData *data = [self propertyListData:record];
105
+ NSString *filename = [self recordFilename:sequence];
106
+ if (data == nil
107
+ || data.length > LBRNEventMaximumBytes + 4096
108
+ || ![self atomicallyWriteData:data filename:filename directoryFD:directoryFD]) {
109
+ return [self storageError];
110
+ }
111
+ return @{ @"status" : @"appended" };
112
+ } @finally {
113
+ close(directoryFD);
114
+ }
115
+ }
116
+ }
117
+
118
+ - (NSDictionary *)acknowledgeRecordCount:(NSNumber *)count
119
+ {
120
+ @synchronized(self) {
121
+ NSUInteger recordCount = 0;
122
+ if (self.poisoned || ![self nonnegativeInteger:count output:&recordCount]) {
123
+ return [self storageError];
124
+ }
125
+ int directoryFD = [self openPreparedDirectory];
126
+ if (directoryFD < 0) {
127
+ return [self storageError];
128
+ }
129
+ @try {
130
+ NSDictionary *snapshot = [self snapshotFromDirectory:directoryFD];
131
+ NSArray *records = snapshot[@"records"];
132
+ if (snapshot == nil || recordCount > records.count) {
133
+ return [self storageError];
134
+ }
135
+ if (recordCount == 0) {
136
+ return @{ @"status" : @"acknowledged" };
137
+ }
138
+ unsigned long long acceptedSequence =
139
+ [records[recordCount - 1][@"sequence"] unsignedLongLongValue];
140
+ if (![self commitMarker:acceptedSequence directoryFD:directoryFD]) {
141
+ return [self storageError];
142
+ }
143
+ [self removeAcceptedRecords:acceptedSequence directoryFD:directoryFD];
144
+ return @{ @"status" : @"acknowledged" };
145
+ } @finally {
146
+ close(directoryFD);
147
+ }
148
+ }
149
+ }
150
+
151
+ - (NSDictionary *)purgeRecords
152
+ {
153
+ @synchronized(self) {
154
+ if (self.poisoned) {
155
+ return [self storageError];
156
+ }
157
+ int directoryFD = [self openPreparedDirectory];
158
+ if (directoryFD < 0) {
159
+ return [self storageError];
160
+ }
161
+ @try {
162
+ if (![self removeStaleTemporaryFiles:directoryFD]) {
163
+ return [self storageError];
164
+ }
165
+ NSArray *entries = [self queueEntries:directoryFD];
166
+ if (entries == nil) {
167
+ return [self storageError];
168
+ }
169
+ unsigned long long maximumSequence = 0;
170
+ for (NSDictionary *entry in entries) {
171
+ if (![self safeRegularFilename:entry[@"name"] directoryFD:directoryFD]) {
172
+ return [self storageError];
173
+ }
174
+ maximumSequence = MAX(
175
+ maximumSequence, [entry[@"sequence"] unsignedLongLongValue]);
176
+ }
177
+ if (maximumSequence == 0) {
178
+ return @{ @"status" : @"purged" };
179
+ }
180
+ if (maximumSequence >= LLONG_MAX) {
181
+ return [self storageError];
182
+ }
183
+ unsigned long long acceptedSequence = maximumSequence + 1;
184
+ if (![self commitMarker:acceptedSequence directoryFD:directoryFD]) {
185
+ return [self storageError];
186
+ }
187
+ [self removeAcceptedRecords:acceptedSequence directoryFD:directoryFD];
188
+ [self removeMarkersBefore:acceptedSequence directoryFD:directoryFD];
189
+ return @{ @"status" : @"purged" };
190
+ } @finally {
191
+ close(directoryFD);
192
+ }
193
+ }
194
+ }
195
+
196
+ - (NSDictionary *)closeStore
197
+ {
198
+ @synchronized(self) {
199
+ return @{ @"status" : self.poisoned ? @"storage_error" : @"closed" };
200
+ }
201
+ }
202
+
203
+ - (NSDictionary *)snapshotFromDirectory:(int)directoryFD
204
+ {
205
+ if (self.poisoned || ![self removeStaleTemporaryFiles:directoryFD]) {
206
+ return nil;
207
+ }
208
+ NSArray<NSDictionary *> *entries = [self queueEntries:directoryFD];
209
+ if (entries == nil) {
210
+ return nil;
211
+ }
212
+ NSMutableArray<NSDictionary *> *records = [NSMutableArray array];
213
+ NSMutableArray<NSDictionary *> *markers = [NSMutableArray array];
214
+ unsigned long long markerSequence = 0;
215
+ unsigned long long maximumRecordSequence = 0;
216
+ for (NSDictionary *entry in entries) {
217
+ NSString *name = entry[@"name"];
218
+ if (![self safeRegularFilename:name directoryFD:directoryFD]) {
219
+ return nil;
220
+ }
221
+ unsigned long long sequence = [entry[@"sequence"] unsignedLongLongValue];
222
+ if ([entry[@"kind"] isEqualToString:@"marker"]) {
223
+ markerSequence = MAX(markerSequence, sequence);
224
+ [markers addObject:entry];
225
+ } else {
226
+ maximumRecordSequence = MAX(maximumRecordSequence, sequence);
227
+ }
228
+ }
229
+ if (markerSequence > 0
230
+ && ![self validMarkerFilename:[self markerFilename:markerSequence]
231
+ sequence:markerSequence
232
+ directoryFD:directoryFD]) {
233
+ return nil;
234
+ }
235
+
236
+ unsigned long long totalEventBytes = 0;
237
+ for (NSDictionary *entry in entries) {
238
+ if (![entry[@"kind"] isEqualToString:@"record"]) {
239
+ continue;
240
+ }
241
+ unsigned long long sequence = [entry[@"sequence"] unsignedLongLongValue];
242
+ if (sequence <= markerSequence) {
243
+ continue;
244
+ }
245
+ NSDictionary *record = [self readRecordFilename:entry[@"name"]
246
+ sequence:sequence
247
+ directoryFD:directoryFD];
248
+ if (record == nil) {
249
+ return nil;
250
+ }
251
+ totalEventBytes += [record[@"eventBytes"] unsignedLongLongValue];
252
+ if (records.count >= LBRNEventMaximumQueueRecords
253
+ || totalEventBytes > LBRNEventMaximumQueueBytes) {
254
+ return nil;
255
+ }
256
+ [records addObject:record];
257
+ }
258
+ [records sortUsingComparator:^NSComparisonResult(NSDictionary *left, NSDictionary *right) {
259
+ return [left[@"sequence"] compare:right[@"sequence"]];
260
+ }];
261
+ [self removeAcceptedRecords:markerSequence directoryFD:directoryFD];
262
+ [self removeMarkerEntries:markers newestSequence:markerSequence directoryFD:directoryFD];
263
+ return @{
264
+ @"markerSequence" : @(markerSequence),
265
+ @"maximumRecordSequence" : @(maximumRecordSequence),
266
+ @"totalEventBytes" : @(totalEventBytes),
267
+ @"records" : records,
268
+ };
269
+ }
270
+
271
+ - (NSArray<NSDictionary *> *)queueEntries:(int)directoryFD
272
+ {
273
+ int duplicateFD = openat(
274
+ directoryFD, ".", O_RDONLY | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW);
275
+ if (duplicateFD < 0) {
276
+ return nil;
277
+ }
278
+ DIR *stream = fdopendir(duplicateFD);
279
+ if (stream == NULL) {
280
+ close(duplicateFD);
281
+ return nil;
282
+ }
283
+ NSMutableArray<NSDictionary *> *entries = [NSMutableArray array];
284
+ BOOL valid = YES;
285
+ errno = 0;
286
+ struct dirent *entry;
287
+ while ((entry = readdir(stream)) != NULL) {
288
+ if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
289
+ continue;
290
+ }
291
+ NSString *name = [NSString stringWithUTF8String:entry->d_name];
292
+ NSString *kind = nil;
293
+ unsigned long long sequence = 0;
294
+ if (name == nil
295
+ || ![self parseQueueFilename:name kind:&kind sequence:&sequence]) {
296
+ valid = NO;
297
+ break;
298
+ }
299
+ [entries addObject:@{ @"name" : name, @"kind" : kind, @"sequence" : @(sequence) }];
300
+ }
301
+ if (entry == NULL && errno != 0) {
302
+ valid = NO;
303
+ }
304
+ closedir(stream);
305
+ if (!valid) {
306
+ return nil;
307
+ }
308
+ [entries sortUsingComparator:^NSComparisonResult(NSDictionary *left, NSDictionary *right) {
309
+ NSComparisonResult sequenceOrder = [left[@"sequence"] compare:right[@"sequence"]];
310
+ return sequenceOrder == NSOrderedSame
311
+ ? [left[@"kind"] compare:right[@"kind"]]
312
+ : sequenceOrder;
313
+ }];
314
+ return entries;
315
+ }
316
+
317
+ - (BOOL)parseQueueFilename:(NSString *)name
318
+ kind:(NSString *_Nullable *_Nonnull)kind
319
+ sequence:(unsigned long long *)sequence
320
+ {
321
+ NSString *prefix = nil;
322
+ NSString *suffix = nil;
323
+ NSString *resolvedKind = nil;
324
+ if ([name hasPrefix:LBRNEventRecordPrefix] && [name hasSuffix:LBRNEventRecordSuffix]) {
325
+ prefix = LBRNEventRecordPrefix;
326
+ suffix = LBRNEventRecordSuffix;
327
+ resolvedKind = @"record";
328
+ } else if ([name hasPrefix:LBRNEventMarkerPrefix]
329
+ && [name hasSuffix:LBRNEventMarkerSuffix]) {
330
+ prefix = LBRNEventMarkerPrefix;
331
+ suffix = LBRNEventMarkerSuffix;
332
+ resolvedKind = @"marker";
333
+ } else {
334
+ return NO;
335
+ }
336
+ NSUInteger digitsLength = name.length - prefix.length - suffix.length;
337
+ if (digitsLength != 20) {
338
+ return NO;
339
+ }
340
+ NSString *digits = [name substringWithRange:NSMakeRange(prefix.length, digitsLength)];
341
+ for (NSUInteger index = 0; index < digits.length; index += 1) {
342
+ unichar value = [digits characterAtIndex:index];
343
+ if (value < '0' || value > '9') {
344
+ return NO;
345
+ }
346
+ }
347
+ errno = 0;
348
+ char *end = NULL;
349
+ unsigned long long value = strtoull(digits.UTF8String, &end, 10);
350
+ if (errno != 0 || end == NULL || *end != '\0' || value == 0 || value > LLONG_MAX) {
351
+ return NO;
352
+ }
353
+ *kind = resolvedKind;
354
+ *sequence = value;
355
+ return YES;
356
+ }
357
+
358
+ - (NSDictionary *)readRecordFilename:(NSString *)filename
359
+ sequence:(unsigned long long)sequence
360
+ directoryFD:(int)directoryFD
361
+ {
362
+ NSData *data = [self readFilename:filename
363
+ maximumBytes:LBRNEventMaximumBytes + 4096
364
+ directoryFD:directoryFD];
365
+ NSDictionary *record = [self propertyListDictionary:data];
366
+ if (record == nil || record.count != 4
367
+ || [record[@"schemaVersion"] integerValue] != LBRNEventFileSchemaVersion
368
+ || [record[@"sequence"] unsignedLongLongValue] != sequence
369
+ || ![record[@"serializedEvent"] isKindOfClass:[NSString class]]) {
370
+ return nil;
371
+ }
372
+ NSData *eventData = [self validatedEventData:record[@"serializedEvent"]
373
+ eventBytes:record[@"eventBytes"]];
374
+ if (eventData == nil) {
375
+ return nil;
376
+ }
377
+ return @{
378
+ @"sequence" : @(sequence),
379
+ @"serializedEvent" : record[@"serializedEvent"],
380
+ @"eventBytes" : record[@"eventBytes"],
381
+ };
382
+ }
383
+
384
+ - (BOOL)validMarkerFilename:(NSString *)filename
385
+ sequence:(unsigned long long)sequence
386
+ directoryFD:(int)directoryFD
387
+ {
388
+ NSDictionary *marker = [self propertyListDictionary:[self readFilename:filename
389
+ maximumBytes:1024
390
+ directoryFD:directoryFD]];
391
+ return marker != nil && marker.count == 2
392
+ && [marker[@"schemaVersion"] integerValue] == LBRNEventFileSchemaVersion
393
+ && [marker[@"sequence"] unsignedLongLongValue] == sequence;
394
+ }
395
+
396
+ - (BOOL)commitMarker:(unsigned long long)sequence directoryFD:(int)directoryFD
397
+ {
398
+ NSString *filename = [self markerFilename:sequence];
399
+ struct stat info;
400
+ if (fstatat(directoryFD, filename.UTF8String, &info, AT_SYMLINK_NOFOLLOW) == 0) {
401
+ return S_ISREG(info.st_mode)
402
+ && [self validMarkerFilename:filename sequence:sequence directoryFD:directoryFD];
403
+ }
404
+ if (errno != ENOENT) {
405
+ return NO;
406
+ }
407
+ NSData *data = [self propertyListData:@{
408
+ @"schemaVersion" : @(LBRNEventFileSchemaVersion),
409
+ @"sequence" : @(sequence),
410
+ }];
411
+ return data != nil
412
+ && [self atomicallyWriteData:data filename:filename directoryFD:directoryFD];
413
+ }
414
+
415
+ - (void)removeAcceptedRecords:(unsigned long long)markerSequence
416
+ directoryFD:(int)directoryFD
417
+ {
418
+ if (markerSequence == 0) {
419
+ return;
420
+ }
421
+ NSArray<NSDictionary *> *entries = [self queueEntries:directoryFD];
422
+ BOOL deleted = NO;
423
+ for (NSDictionary *entry in entries ?: @[]) {
424
+ if ([entry[@"kind"] isEqualToString:@"record"]
425
+ && [entry[@"sequence"] unsignedLongLongValue] <= markerSequence
426
+ && [self safeRegularFilename:entry[@"name"] directoryFD:directoryFD]
427
+ && unlinkat(directoryFD, [entry[@"name"] UTF8String], 0) == 0) {
428
+ deleted = YES;
429
+ }
430
+ }
431
+ if (deleted) {
432
+ [self synchronizeDirectory:directoryFD];
433
+ }
434
+ }
435
+
436
+ - (void)removeMarkersBefore:(unsigned long long)markerSequence
437
+ directoryFD:(int)directoryFD
438
+ {
439
+ NSArray<NSDictionary *> *entries = [self queueEntries:directoryFD];
440
+ NSMutableArray<NSDictionary *> *markers = [NSMutableArray array];
441
+ for (NSDictionary *entry in entries ?: @[]) {
442
+ if ([entry[@"kind"] isEqualToString:@"marker"]) {
443
+ [markers addObject:entry];
444
+ }
445
+ }
446
+ [self removeMarkerEntries:markers
447
+ newestSequence:markerSequence
448
+ directoryFD:directoryFD];
449
+ }
450
+
451
+ - (void)removeMarkerEntries:(NSArray<NSDictionary *> *)markers
452
+ newestSequence:(unsigned long long)newestSequence
453
+ directoryFD:(int)directoryFD
454
+ {
455
+ BOOL deleted = NO;
456
+ for (NSDictionary *entry in markers) {
457
+ if ([entry[@"sequence"] unsignedLongLongValue] < newestSequence
458
+ && [self safeRegularFilename:entry[@"name"] directoryFD:directoryFD]
459
+ && unlinkat(directoryFD, [entry[@"name"] UTF8String], 0) == 0) {
460
+ deleted = YES;
461
+ }
462
+ }
463
+ if (deleted) {
464
+ [self synchronizeDirectory:directoryFD];
465
+ }
466
+ }
467
+
468
+ - (int)openPreparedDirectory
469
+ {
470
+ if (self.poisoned || !self.directoryURL.isFileURL) {
471
+ return -1;
472
+ }
473
+ const char *path = self.directoryURL.fileSystemRepresentation;
474
+ struct stat info;
475
+ if (lstat(path, &info) != 0) {
476
+ if (errno != ENOENT || mkdir(path, 0700) != 0) {
477
+ return -1;
478
+ }
479
+ } else if (!S_ISDIR(info.st_mode) || S_ISLNK(info.st_mode)) {
480
+ return -1;
481
+ }
482
+ int directoryFD = open(path, O_RDONLY | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW);
483
+ if (directoryFD < 0) {
484
+ return -1;
485
+ }
486
+ BOOL prepared = fstat(directoryFD, &info) == 0
487
+ && S_ISDIR(info.st_mode)
488
+ && fchmod(directoryFD, 0700) == 0;
489
+ @try {
490
+ prepared = prepared && self.directoryPreparation(self.directoryURL);
491
+ } @catch (__unused NSException *exception) {
492
+ prepared = NO;
493
+ }
494
+ if (!prepared || ![self directoryFDMatchesURL:directoryFD]) {
495
+ close(directoryFD);
496
+ return -1;
497
+ }
498
+ return directoryFD;
499
+ }
500
+
501
+ - (BOOL)directoryFDMatchesURL:(int)directoryFD
502
+ {
503
+ struct stat openedInfo;
504
+ struct stat pathInfo;
505
+ if (fstat(directoryFD, &openedInfo) != 0
506
+ || lstat(self.directoryURL.fileSystemRepresentation, &pathInfo) != 0) {
507
+ return NO;
508
+ }
509
+ return S_ISDIR(openedInfo.st_mode) && S_ISDIR(pathInfo.st_mode)
510
+ && !S_ISLNK(pathInfo.st_mode) && openedInfo.st_dev == pathInfo.st_dev
511
+ && openedInfo.st_ino == pathInfo.st_ino;
512
+ }
513
+
514
+ - (BOOL)removeStaleTemporaryFiles:(int)directoryFD
515
+ {
516
+ int duplicateFD = openat(
517
+ directoryFD, ".", O_RDONLY | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW);
518
+ if (duplicateFD < 0) {
519
+ return NO;
520
+ }
521
+ DIR *stream = fdopendir(duplicateFD);
522
+ if (stream == NULL) {
523
+ close(duplicateFD);
524
+ return NO;
525
+ }
526
+ BOOL succeeded = YES;
527
+ errno = 0;
528
+ struct dirent *entry;
529
+ while ((entry = readdir(stream)) != NULL) {
530
+ NSString *name = [NSString stringWithUTF8String:entry->d_name];
531
+ if (name == nil || ![name hasSuffix:@".tmp"]) {
532
+ continue;
533
+ }
534
+ if (![self safeRegularFilename:name directoryFD:directoryFD]
535
+ || unlinkat(directoryFD, name.UTF8String, 0) != 0) {
536
+ succeeded = NO;
537
+ break;
538
+ }
539
+ }
540
+ if (entry == NULL && errno != 0) {
541
+ succeeded = NO;
542
+ }
543
+ closedir(stream);
544
+ return succeeded;
545
+ }
546
+
547
+ - (BOOL)atomicallyWriteData:(NSData *)data
548
+ filename:(NSString *)filename
549
+ directoryFD:(int)directoryFD
550
+ {
551
+ NSString *temporary = [filename stringByAppendingString:@".tmp"];
552
+ struct stat info;
553
+ if (fstatat(directoryFD, filename.UTF8String, &info, AT_SYMLINK_NOFOLLOW) == 0
554
+ || errno != ENOENT) {
555
+ return NO;
556
+ }
557
+ if (fstatat(directoryFD, temporary.UTF8String, &info, AT_SYMLINK_NOFOLLOW) == 0) {
558
+ if (!S_ISREG(info.st_mode)
559
+ || unlinkat(directoryFD, temporary.UTF8String, 0) != 0) {
560
+ return NO;
561
+ }
562
+ } else if (errno != ENOENT) {
563
+ return NO;
564
+ }
565
+
566
+ int fileFD = openat(
567
+ directoryFD,
568
+ temporary.UTF8String,
569
+ O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC | O_NOFOLLOW,
570
+ 0600);
571
+ if (fileFD < 0) {
572
+ return NO;
573
+ }
574
+ BOOL succeeded = fchmod(fileFD, 0600) == 0;
575
+ NSUInteger offset = 0;
576
+ while (succeeded && offset < data.length) {
577
+ ssize_t count = write(
578
+ fileFD, (const uint8_t *)data.bytes + offset, data.length - offset);
579
+ if (count <= 0) {
580
+ succeeded = NO;
581
+ } else {
582
+ offset += (NSUInteger)count;
583
+ }
584
+ }
585
+ if (succeeded && fsync(fileFD) != 0) {
586
+ succeeded = NO;
587
+ }
588
+ if (close(fileFD) != 0) {
589
+ succeeded = NO;
590
+ }
591
+ if (succeeded
592
+ && renameat(
593
+ directoryFD,
594
+ temporary.UTF8String,
595
+ directoryFD,
596
+ filename.UTF8String)
597
+ != 0) {
598
+ succeeded = NO;
599
+ }
600
+ if (succeeded && ![self synchronizeDirectory:directoryFD]) {
601
+ self.poisoned = YES;
602
+ succeeded = NO;
603
+ }
604
+ if (!succeeded) {
605
+ unlinkat(directoryFD, temporary.UTF8String, 0);
606
+ }
607
+ return succeeded;
608
+ }
609
+
610
+ - (NSData *)readFilename:(NSString *)filename
611
+ maximumBytes:(NSUInteger)maximumBytes
612
+ directoryFD:(int)directoryFD
613
+ {
614
+ struct stat pathInfo;
615
+ if (fstatat(directoryFD, filename.UTF8String, &pathInfo, AT_SYMLINK_NOFOLLOW) != 0
616
+ || !S_ISREG(pathInfo.st_mode) || S_ISLNK(pathInfo.st_mode)
617
+ || pathInfo.st_size <= 0 || (uint64_t)pathInfo.st_size > maximumBytes) {
618
+ return nil;
619
+ }
620
+ int fileFD = openat(directoryFD, filename.UTF8String, O_RDONLY | O_CLOEXEC | O_NOFOLLOW);
621
+ if (fileFD < 0) {
622
+ return nil;
623
+ }
624
+ NSMutableData *data = [NSMutableData dataWithLength:(NSUInteger)pathInfo.st_size];
625
+ BOOL succeeded = YES;
626
+ NSUInteger offset = 0;
627
+ while (offset < data.length) {
628
+ ssize_t count = read(fileFD, (uint8_t *)data.mutableBytes + offset, data.length - offset);
629
+ if (count <= 0) {
630
+ succeeded = NO;
631
+ break;
632
+ }
633
+ offset += (NSUInteger)count;
634
+ }
635
+ struct stat openedInfo;
636
+ if (fstat(fileFD, &openedInfo) != 0 || openedInfo.st_dev != pathInfo.st_dev
637
+ || openedInfo.st_ino != pathInfo.st_ino || !S_ISREG(openedInfo.st_mode)
638
+ || openedInfo.st_size != pathInfo.st_size) {
639
+ succeeded = NO;
640
+ }
641
+ close(fileFD);
642
+ return succeeded ? data : nil;
643
+ }
644
+
645
+ - (BOOL)safeRegularFilename:(NSString *)filename directoryFD:(int)directoryFD
646
+ {
647
+ struct stat info;
648
+ return fstatat(directoryFD, filename.UTF8String, &info, AT_SYMLINK_NOFOLLOW) == 0
649
+ && S_ISREG(info.st_mode) && !S_ISLNK(info.st_mode);
650
+ }
651
+
652
+ - (BOOL)synchronizeDirectory:(int)directoryFD
653
+ {
654
+ if (fsync(directoryFD) == 0) {
655
+ return YES;
656
+ }
657
+ return errno == EINVAL || errno == ENOTSUP;
658
+ }
659
+
660
+ - (NSData *)validatedEventData:(id)serializedEvent eventBytes:(id)eventBytes
661
+ {
662
+ if (![serializedEvent isKindOfClass:[NSString class]]
663
+ || ![eventBytes isKindOfClass:[NSNumber class]]) {
664
+ return nil;
665
+ }
666
+ NSUInteger byteCount = 0;
667
+ if (![self positiveInteger:eventBytes output:&byteCount]
668
+ || byteCount > LBRNEventMaximumBytes) {
669
+ return nil;
670
+ }
671
+ NSData *data = [serializedEvent dataUsingEncoding:NSUTF8StringEncoding];
672
+ return data.length == byteCount ? data : nil;
673
+ }
674
+
675
+ - (BOOL)positiveInteger:(NSNumber *)value output:(NSUInteger *)output
676
+ {
677
+ if (strcmp(value.objCType, @encode(BOOL)) == 0) {
678
+ return NO;
679
+ }
680
+ double number = value.doubleValue;
681
+ if (!isfinite(number) || number <= 0 || number > INT_MAX || floor(number) != number) {
682
+ return NO;
683
+ }
684
+ *output = (NSUInteger)number;
685
+ return YES;
686
+ }
687
+
688
+ - (BOOL)nonnegativeInteger:(NSNumber *)value output:(NSUInteger *)output
689
+ {
690
+ if (![value isKindOfClass:[NSNumber class]]
691
+ || strcmp(value.objCType, @encode(BOOL)) == 0) {
692
+ return NO;
693
+ }
694
+ double number = value.doubleValue;
695
+ if (!isfinite(number) || number < 0 || number > INT_MAX || floor(number) != number) {
696
+ return NO;
697
+ }
698
+ *output = (NSUInteger)number;
699
+ return YES;
700
+ }
701
+
702
+ - (NSData *)propertyListData:(NSDictionary *)value
703
+ {
704
+ NSError *error = nil;
705
+ NSData *data = [NSPropertyListSerialization dataWithPropertyList:value
706
+ format:NSPropertyListBinaryFormat_v1_0
707
+ options:0
708
+ error:&error];
709
+ return error == nil ? data : nil;
710
+ }
711
+
712
+ - (NSDictionary *)propertyListDictionary:(NSData *)data
713
+ {
714
+ if (data == nil) {
715
+ return nil;
716
+ }
717
+ NSError *error = nil;
718
+ id value = [NSPropertyListSerialization propertyListWithData:data
719
+ options:NSPropertyListImmutable
720
+ format:nil
721
+ error:&error];
722
+ return error == nil && [value isKindOfClass:[NSDictionary class]] ? value : nil;
723
+ }
724
+
725
+ - (NSString *)recordFilename:(unsigned long long)sequence
726
+ {
727
+ return [NSString stringWithFormat:@"%@%020llu%@",
728
+ LBRNEventRecordPrefix,
729
+ sequence,
730
+ LBRNEventRecordSuffix];
731
+ }
732
+
733
+ - (NSString *)markerFilename:(unsigned long long)sequence
734
+ {
735
+ return [NSString stringWithFormat:@"%@%020llu%@",
736
+ LBRNEventMarkerPrefix,
737
+ sequence,
738
+ LBRNEventMarkerSuffix];
739
+ }
740
+
741
+ - (NSDictionary *)storageError
742
+ {
743
+ return @{ @"status" : @"storage_error" };
744
+ }
745
+
746
+ @end