FBControlCore/Crashes/FBCrashLogNotifier.m (168 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "FBCrashLogNotifier.h" #import "FBCrashLog.h" #import "FBCrashLogStore.h" #import "FBControlCoreGlobalConfiguration.h" #import "FBControlCoreLogger.h" #import "FBControlCoreError.h" #if defined(__apple_build_version__) #import <CoreServices/CoreServices.h> #include <sys/stat.h> @interface FBCrashLogNotifier_FSEvents : NSObject @property (nonatomic, copy, readonly) NSArray<NSString *> *directories; @property (nonatomic, strong, readonly) FBCrashLogStore *store; @property (nonatomic, strong, readonly) id<FBControlCoreLogger> logger; @property (nonatomic, strong, readonly) dispatch_queue_t queue; @property (nonatomic, assign, readwrite) FSEventStreamRef eventStream; @end typedef NS_ENUM(NSUInteger, FBCrashLogNotifierFileEvent) { FBCrashLogNotifierFileEventUnknown = 0, FBCrashLogNotifierFileEventAdded = 1, FBCrashLogNotifierFileEventRemoved = 2, }; static FBCrashLogNotifierFileEvent GetEventType(FSEventStreamEventFlags flag, NSString *filePath) { if (flag & kFSEventStreamEventFlagItemRemoved) { return FBCrashLogNotifierFileEventRemoved; } else if (flag & kFSEventStreamEventFlagItemCreated) { return FBCrashLogNotifierFileEventAdded; } else if (flag & kFSEventStreamEventFlagItemRenamed) { struct stat buffer; int value = stat(filePath.UTF8String, &buffer); return value == 0 ? FBCrashLogNotifierFileEventAdded : FBCrashLogNotifierFileEventRemoved; } return FBCrashLogNotifierFileEventUnknown; } static void EventStreamCallback( ConstFSEventStreamRef streamRef, FBCrashLogNotifier_FSEvents *notifier, size_t numEvents, NSArray<NSString *> *eventPaths, const FSEventStreamEventFlags *eventFlags, const FSEventStreamEventId *eventIds ){ for (size_t index = 0; index < numEvents; index++) { NSString *path = eventPaths[index]; FSEventStreamEventFlags flag = eventFlags[index]; switch (GetEventType(flag, path)) { case FBCrashLogNotifierFileEventAdded: [notifier.store ingestCrashLogAtPath:path]; continue; case FBCrashLogNotifierFileEventRemoved: [notifier.store removeCrashLogAtPath:path]; continue; default: continue; } } } @implementation FBCrashLogNotifier_FSEvents - (instancetype)initWithDirectories:(NSArray<NSString *> *)directories store:(FBCrashLogStore *)store logger:(id<FBControlCoreLogger>)logger { self = [super init]; if (!self) { return nil; } _directories = directories; _store = store; _logger = logger; _queue = dispatch_queue_create("com.facebook.fbcontrolcore.crash_logs.fsevents", DISPATCH_QUEUE_SERIAL); return self; } - (void)startListening:(BOOL)onlyNew { if (self.eventStream) { return; } FSEventStreamContext context = { .version = 0, .info = (void *) CFBridgingRetain(self), .retain = CFRetain, .release = CFRelease, .copyDescription = NULL, }; NSMutableArray<NSString *> *pathsToWatch = NSMutableArray.array; for (NSString *reportPath in self.directories) { if ([[NSFileManager defaultManager] fileExistsAtPath:reportPath]) { [pathsToWatch addObject:reportPath]; } } FSEventStreamRef eventStream = FSEventStreamCreate( NULL, // Allocator (FSEventStreamCallback) EventStreamCallback, // Callback &context, // Context CFBridgingRetain(pathsToWatch), // Paths to watch onlyNew ? kFSEventStreamEventIdSinceNow : 0, // Since When 0, // Latency kFSEventStreamCreateFlagUseCFTypes | kFSEventStreamCreateFlagFileEvents | kFSEventStreamCreateFlagNoDefer ); FSEventStreamSetDispatchQueue(eventStream, self.queue); Boolean started = FSEventStreamStart(eventStream); NSAssert(started, @"Event Stream could not be started"); self.eventStream = eventStream; } @end #endif @interface FBCrashLogNotifier () #if defined(__apple_build_version__) @property (nonatomic, strong, readonly) FBCrashLogNotifier_FSEvents *fsEvents; #else @property (nonatomic, copy, readwrite) NSDate *sinceDate; #endif @end @implementation FBCrashLogNotifier #pragma mark Initializers - (instancetype)initWithLogger:(id<FBControlCoreLogger>)logger { self = [super init]; if (!self) { return nil; } _store = [FBCrashLogStore storeForDirectories:FBCrashLogInfo.diagnosticReportsPaths logger:logger]; #if defined(__apple_build_version__) _fsEvents = [[FBCrashLogNotifier_FSEvents alloc] initWithDirectories:FBCrashLogInfo.diagnosticReportsPaths store:_store logger:logger]; #else _sinceDate = NSDate.date; #endif return self; } + (instancetype)sharedInstance { static dispatch_once_t onceToken; static FBCrashLogNotifier *notifier; dispatch_once(&onceToken, ^{ notifier = [[FBCrashLogNotifier alloc] initWithLogger:FBControlCoreGlobalConfiguration.defaultLogger]; }); return notifier; } #pragma mark Public Methods - (instancetype)startListening:(BOOL)onlyNew { #if defined(__apple_build_version__) [self.fsEvents startListening:onlyNew]; #else self.sinceDate = NSDate.date; #endif return self; } - (FBFuture<FBCrashLogInfo *> *)nextCrashLogForPredicate:(NSPredicate *)predicate { [self startListening:YES]; #if defined(__apple_build_version__) return [FBCrashLogNotifier.sharedInstance.fsEvents.store nextCrashLogForMatchingPredicate:predicate]; #else dispatch_queue_t queue = dispatch_queue_create("com.facebook.fbcontrolcore.crashlogfetch", DISPATCH_QUEUE_SERIAL); return [FBFuture onQueue:queue resolveUntil:^{ FBCrashLogInfo *crashInfo = [[[FBCrashLogInfo crashInfoAfterDate:FBCrashLogNotifier.sharedInstance.sinceDate logger:nil] filteredArrayUsingPredicate:predicate] firstObject]; if (!crashInfo) { return [[FBControlCoreError describeFormat:@"Crash Log Info for %@ could not be obtained", predicate] failFuture]; } return [FBFuture futureWithResult:crashInfo]; }]; #endif } @end