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