FBDeviceControl/Commands/FBDeviceFileCommands.m (566 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 "FBDeviceFileCommands.h"
#import "FBAFCConnection.h"
#import "FBDevice+Private.h"
#import "FBDevice.h"
#import "FBDeviceControlError.h"
#import "FBDeviceDebugSymbolsCommands.h"
#import "FBDeviceProvisioningProfileCommands.h"
#import "FBManagedConfigClient.h"
#import "FBSpringboardServicesClient.h"
@interface FBDeviceFileContainer ()
@property (nonatomic, strong, readonly) dispatch_queue_t queue;
@property (nonatomic, strong, readonly) FBAFCConnection *connection;
@end
@implementation FBDeviceFileContainer
- (instancetype)initWithAFCConnection:(FBAFCConnection *)connection queue:(dispatch_queue_t)queue
{
self = [super init];
if (!self) {
return nil;
}
_connection = connection;
_queue = queue;
return self;
}
- (FBFuture<NSNull *> *)copyFromHost:(NSString *)sourcePath toContainer:(NSString *)destinationPath
{
return [self handleAFCOperation:^ NSNull * (FBAFCConnection *afc, NSError **error) {
BOOL success = [afc copyFromHost:sourcePath toContainerPath:destinationPath error:error];
if (!success) {
return nil;
}
return NSNull.null;
}];
}
- (FBFuture<NSString *> *)copyFromContainer:(NSString *)sourcePath toHost:(NSString *)destinationPath
{
NSString *destination = destinationPath;
if ([FBDeviceFileContainer isDirectory:destinationPath]){
destination = [destinationPath stringByAppendingPathComponent:sourcePath.lastPathComponent];
}
return [[self
readFileFromPathInContainer:sourcePath]
onQueue:self.queue fmap:^FBFuture<NSString *> *(NSData *fileData) {
NSError *error;
if (![fileData writeToFile:destination options:0 error:&error]) {
return [[[FBDeviceControlError
describeFormat:@"Failed to write data to file at path %@", destination]
causedBy:error]
failFuture];
}
return [FBFuture futureWithResult:destination];
}];
}
- (FBFuture<FBFuture<NSNull *> *> *)tail:(NSString *)path toConsumer:(id<FBDataConsumer>)consumer
{
return [[FBControlCoreError
describeFormat:@"-[%@ %@] is not implemented", NSStringFromClass(self.class), NSStringFromSelector(_cmd)]
failFuture];
}
- (FBFuture<NSNull *> *)createDirectory:(NSString *)directoryPath
{
return [self handleAFCOperation:^ NSNull * (FBAFCConnection *afc, NSError **error) {
BOOL success = [afc createDirectory:directoryPath error:error];
if (!success) {
return nil;
}
return NSNull.null;
}];
}
- (FBFuture<NSNull *> *)moveFrom:(NSString *)sourcePath to:(NSString *)destinationPath
{
return [self handleAFCOperation:^ NSNull * (FBAFCConnection *afc, NSError **error) {
BOOL success = [afc renamePath:sourcePath destination:destinationPath error:error];
if (!success) {
return nil;
}
return NSNull.null;
}];
}
- (FBFuture<NSNull *> *)remove:(NSString *)path
{
return [self handleAFCOperation:^ NSNull * (FBAFCConnection *afc, NSError **error) {
BOOL success = [afc removePath:path recursively:YES error:error];
if (!success) {
return nil;
}
return NSNull.null;
}];
}
- (FBFuture<NSArray<NSString *> *> *)contentsOfDirectory:(NSString *)path
{
return [self handleAFCOperation:^ NSArray<NSString *> * (FBAFCConnection *afc, NSError **error) {
return [afc contentsOfDirectory:path error:error];
}];
}
#pragma mark Private
- (FBFuture<NSData *> *)readFileFromPathInContainer:(NSString *)path
{
return [self handleAFCOperation:^ NSData * (FBAFCConnection *afc, NSError **error) {
return [afc contentsOfPath:path error:error];
}];
}
- (FBFuture *)handleAFCOperation:(id(^)(FBAFCConnection *, NSError **))operationBlock
{
return [FBFuture
onQueue:self.queue resolveValue:^(NSError **error) {
return operationBlock(self.connection, error);
}];
}
+ (BOOL)isDirectory:(NSString *)path
{
BOOL isDir = NO;
return ([NSFileManager.defaultManager fileExistsAtPath:path isDirectory:&isDir] && isDir);
}
@end
@interface FBDeviceFileContainer_Wallpaper : NSObject <FBFileContainer>
@property (nonatomic, strong, readonly) dispatch_queue_t queue;
@property (nonatomic, strong, readonly) FBSpringboardServicesClient *springboard;
@property (nonatomic, strong, readonly) FBManagedConfigClient *managedConfig;
@end
@implementation FBDeviceFileContainer_Wallpaper
- (instancetype)initWithSpringboard:(FBSpringboardServicesClient *)springboard managedConfig:(FBManagedConfigClient *)managedConfig queue:(dispatch_queue_t)queue
{
self = [super init];
if (!self) {
return nil;
}
_springboard = springboard;
_managedConfig = managedConfig;
_queue = queue;
return self;
}
#pragma mark FBFileContainer Implementation
- (FBFuture<NSArray<NSString *> *> *)contentsOfDirectory:(NSString *)path
{
return [FBFuture futureWithResult:@[FBWallpaperNameHomescreen, FBWallpaperNameLockscreen]];
}
- (FBFuture<NSString *> *)copyFromContainer:(NSString *)sourcePath toHost:(NSString *)destinationPath
{
return [[self.springboard
wallpaperImageDataForKind:sourcePath.lastPathComponent]
onQueue:self.queue fmap:^ FBFuture<NSString *> * (NSData *data) {
NSError *error = nil;
if (![data writeToFile:destinationPath options:NSDataWritingAtomic error:&error]) {
return [FBFuture futureWithError:error];
}
return [FBFuture futureWithResult:destinationPath];
}];
}
- (FBFuture<NSNull *> *)copyFromHost:(NSString *)sourcePath toContainer:(NSString *)destinationPath
{
return [FBFuture
onQueue:self.queue resolve:^ FBFuture<NSNull *> * {
NSError *error = nil;
NSData *data = [NSData dataWithContentsOfFile:sourcePath options:0 error:&error];
if (!data) {
return [FBFuture futureWithError:error];
}
return [self.managedConfig changeWallpaperWithName:destinationPath.lastPathComponent data:data];
}];
}
- (FBFuture<FBFuture<NSNull *> *> *)tail:(NSString *)path toConsumer:(id<FBDataConsumer>)consumer
{
return [[FBControlCoreError
describeFormat:@"-[%@ %@] is not implemented", NSStringFromClass(self.class), NSStringFromSelector(_cmd)]
failFuture];
}
- (FBFuture<NSNull *> *)createDirectory:(NSString *)directoryPath
{
return [[FBControlCoreError
describeFormat:@"%@ does not make sense for Wallpaper File Containers", NSStringFromSelector(_cmd)]
failFuture];
}
- (FBFuture<NSNull *> *)moveFrom:(NSString *)sourcePath to:(NSString *)destinationPath
{
return [[FBControlCoreError
describeFormat:@"%@ does not make sense for Wallpaper File Containers", NSStringFromSelector(_cmd)]
failFuture];
}
- (FBFuture<NSNull *> *)remove:(NSString *)path
{
return [[FBControlCoreError
describeFormat:@"%@ does not make sense for Wallpaper File Containers", NSStringFromSelector(_cmd)]
failFuture];
}
@end
@interface FBDeviceFileContainer_MDMProfiles : NSObject <FBFileContainer>
@property (nonatomic, strong, readonly) dispatch_queue_t queue;
@property (nonatomic, strong, readonly) FBManagedConfigClient *managedConfig;
@end
@implementation FBDeviceFileContainer_MDMProfiles
- (instancetype)initWithManagedConfig:(FBManagedConfigClient *)managedConfig queue:(dispatch_queue_t)queue
{
self = [super init];
if (!self) {
return nil;
}
_managedConfig = managedConfig;
_queue = queue;
return self;
}
#pragma mark FBFileContainer Implementation
- (FBFuture<NSArray<NSString *> *> *)contentsOfDirectory:(NSString *)path
{
return [self.managedConfig getProfileList];
}
- (FBFuture<NSString *> *)copyFromContainer:(NSString *)sourcePath toHost:(NSString *)destinationPath
{
return [[FBControlCoreError
describeFormat:@"%@ does not make sense for MDM Profile File Containers", NSStringFromSelector(_cmd)]
failFuture];
}
- (FBFuture<NSNull *> *)copyFromHost:(NSString *)sourcePath toContainer:(NSString *)destinationPath
{
return [FBFuture
onQueue:self.queue resolve:^ FBFuture<NSNull *> * {
NSError *error = nil;
NSData *data = [NSData dataWithContentsOfFile:sourcePath options:0 error:&error];
if (!data) {
return [FBFuture futureWithError:error];
}
return [self.managedConfig installProfile:data];
}];
}
- (FBFuture<FBFuture<NSNull *> *> *)tail:(NSString *)path toConsumer:(id<FBDataConsumer>)consumer
{
return [[FBControlCoreError
describeFormat:@"-[%@ %@] is not implemented", NSStringFromClass(self.class), NSStringFromSelector(_cmd)]
failFuture];
}
- (FBFuture<NSNull *> *)createDirectory:(NSString *)directoryPath
{
return [[FBControlCoreError
describeFormat:@"%@ does not make sense for MDM Profile File Containers", NSStringFromSelector(_cmd)]
failFuture];
}
- (FBFuture<NSNull *> *)moveFrom:(NSString *)sourcePath to:(NSString *)destinationPath
{
return [[FBControlCoreError
describeFormat:@"%@ does not make sense for MDM Profile File Containers", NSStringFromSelector(_cmd)]
failFuture];
}
- (FBFuture<NSNull *> *)remove:(NSString *)path
{
return [self.managedConfig removeProfile:path];
}
@end
static NSString *const MountRootPath = @"mounted";
@interface FBDeviceFileCommands_DiskImages : NSObject <FBFileContainer>
@property (nonatomic, strong, readonly) id<FBDeveloperDiskImageCommands> commands;
@property (nonatomic, strong, readonly) dispatch_queue_t queue;
@end
@implementation FBDeviceFileCommands_DiskImages
- (instancetype)initWithCommands:(id<FBDeveloperDiskImageCommands>)commands queue:(dispatch_queue_t)queue
{
self = [super init];
if (!self) {
return nil;
}
_commands = commands;
_queue = queue;
return self;
}
- (FBFuture<NSNull *> *)copyFromHost:(NSString *)sourcePath toContainer:(NSString *)destinationPath
{
return [[FBControlCoreError
describeFormat:@"%@ does not make sense for Disk Images", NSStringFromSelector(_cmd)]
failFuture];
}
- (FBFuture<NSString *> *)copyFromContainer:(NSString *)sourcePath toHost:(NSString *)destinationPath
{
return [[FBControlCoreError
describeFormat:@"%@ does not make sense for Disk Images", NSStringFromSelector(_cmd)]
failFuture];
}
- (FBFuture<FBFuture<NSNull *> *> *)tail:(NSString *)path toConsumer:(id<FBDataConsumer>)consumer
{
return [[FBControlCoreError
describeFormat:@"-[%@ %@] is not implemented", NSStringFromClass(self.class), NSStringFromSelector(_cmd)]
failFuture];
}
- (FBFuture<NSNull *> *)createDirectory:(NSString *)directoryPath
{
return [[FBControlCoreError
describeFormat:@"%@ does not make sense for Disk Images", NSStringFromSelector(_cmd)]
failFuture];
}
- (FBFuture<NSNull *> *)moveFrom:(NSString *)sourcePath to:(NSString *)destinationPath
{
if (![destinationPath hasPrefix:MountRootPath]) {
return [[FBDeviceControlError
describeFormat:@"%@ only moving into mounts is supported.", destinationPath]
failFuture];
}
NSDictionary<NSString *, FBDeveloperDiskImage *> *mountableImagesByPath = self.mountableDiskImagesByPath;
FBDeveloperDiskImage *image = mountableImagesByPath[sourcePath];
if (!image) {
return [[FBControlCoreError
describeFormat:@"%@ is not one of %@", sourcePath, [FBCollectionInformation oneLineDescriptionFromArray:mountableImagesByPath.allKeys]]
failFuture];
}
return [[self.commands
mountDiskImage:image]
mapReplace:NSNull.null];
}
- (FBFuture<NSNull *> *)remove:(NSString *)path
{
if (![path hasPrefix:MountRootPath]) {
return [[FBDeviceControlError
describeFormat:@"%@ cannot be removed, only mounts can be removed", path]
failFuture];
}
return [[self
mountedDiskImages]
onQueue:self.queue fmap:^ FBFuture<NSNull *> * (NSDictionary<NSString *, FBDeveloperDiskImage *> *mountedImages) {
FBDeveloperDiskImage *image = mountedImages[path];
if (!image) {
return [[FBDeviceControlError
describeFormat:@"%@ is not one of the available mounts %@", path, [FBCollectionInformation oneLineDescriptionFromArray:mountedImages.allKeys]]
failFuture];
}
return [self.commands unmountDiskImage:image];
}];
}
- (FBFuture<NSArray<NSString *> *> *)contentsOfDirectory:(NSString *)path
{
return [[self
allDiskImagePaths]
onQueue:self.queue fmap:^(NSArray<NSString *> *diskImagePaths) {
NSError *error = nil;
NSArray<NSString *> *traversedPaths = [FBDeviceFileCommands_DiskImages traverseAndDescendPaths:diskImagePaths path:path error:&error];
if (!traversedPaths) {
return [FBFuture futureWithError:error];
}
return [FBFuture futureWithResult:traversedPaths];
}];
}
#pragma mark Private
- (NSDictionary<NSString *, FBDeveloperDiskImage *> *)mountableDiskImagesByPath
{
NSArray<FBDeveloperDiskImage *> *images = self.commands.mountableDiskImages;
NSMutableDictionary<NSString *, FBDeveloperDiskImage *> *mapping = NSMutableDictionary.dictionary;
for (FBDeveloperDiskImage *image in images) {
NSString *mapped = [FBDeviceFileCommands_DiskImages filePathForImage:image];
mapping[mapped] = image;
}
return mapping;
}
- (FBFuture<NSDictionary<NSString *, FBDeveloperDiskImage *> *> *)mountedDiskImages
{
return [[self.commands
mountedDiskImages]
onQueue:self.queue map:^(NSArray<FBDeveloperDiskImage *> *mountedImages) {
NSMutableDictionary<NSString *, FBDeveloperDiskImage *> *imagesByPath = NSMutableDictionary.dictionary;
for (FBDeveloperDiskImage *image in mountedImages) {
NSString *mountedFilePath = [MountRootPath stringByAppendingPathComponent:[FBDeviceFileCommands_DiskImages filePathForImage:image]];
imagesByPath[mountedFilePath] = image;
}
return [imagesByPath copy];
}];
}
- (FBFuture<NSArray<NSString *> *> *)allDiskImagePaths
{
return [[self
mountedDiskImages]
onQueue:self.queue map:^(NSDictionary<NSString *, FBDeveloperDiskImage *> *mountedDiskImages) {
// Construct the full list of all paths, including the mounted & available images.
NSMutableArray<NSString *> *paths = NSMutableArray.array;
[paths addObjectsFromArray:[self.mountableDiskImagesByPath keysSortedByValueUsingSelector:@selector(compare:)]];
[paths addObject:MountRootPath];
[paths addObjectsFromArray:mountedDiskImages.allKeys];
return [paths copy];
}];
}
+ (NSArray<NSString *> *)traverseAndDescendPaths:(NSArray<NSString *> *)paths path:(NSString *)path error:(NSError **)error
{
NSArray<NSString *> *pathComponents = [path pathComponents];
NSString *firstPath = [pathComponents firstObject];
if (pathComponents.count == 1 && ([firstPath isEqualToString:@"."] || [firstPath isEqualToString:@"/"])) {
return paths;
}
NSMutableArray<NSString *> *traversedPaths = NSMutableArray.array;
for (NSString *candidatePath in paths) {
if (![candidatePath hasPrefix:path]) {
continue;
}
NSString *relativePath = [candidatePath substringFromIndex:path.length];
if ([relativePath hasPrefix:@"/"]) {
relativePath = [relativePath substringFromIndex:1];
}
[traversedPaths addObject:relativePath];
}
return [traversedPaths copy];
}
+ (NSString *)filePathForImage:(FBDeveloperDiskImage *)image
{
return [NSString stringWithFormat:@"%ld.%ld/%@", image.version.majorVersion, image.version.minorVersion, image.diskImagePath.lastPathComponent];
}
@end
@interface FBDeviceFileCommands_Symbols : NSObject <FBFileContainer>
@property (nonatomic, strong, readonly) id<FBDeviceDebugSymbolsCommands> commands;
@property (nonatomic, strong, readonly) dispatch_queue_t queue;
@end
@implementation FBDeviceFileCommands_Symbols
static NSString *const ExtractedSymbolsDirectory = @"Symbols";
- (instancetype)initWithCommands:(id<FBDeviceDebugSymbolsCommands>)commands queue:(dispatch_queue_t)queue
{
self = [super init];
if (!self) {
return nil;
}
_commands = commands;
_queue = queue;
return self;
}
- (FBFuture<NSNull *> *)copyFromHost:(NSURL *)sourcePath toContainer:(NSString *)destinationPath
{
return [[FBControlCoreError
describeFormat:@"%@ does not make sense for Symbols", NSStringFromSelector(_cmd)]
failFuture];
}
- (FBFuture<NSString *> *)copyFromContainer:(NSString *)sourcePath toHost:(NSString *)destinationPath
{
if ([sourcePath isEqualToString:ExtractedSymbolsDirectory]) {
return [self.commands pullAndExtractSymbolsToDestinationDirectory:destinationPath];
}
return [self.commands pullSymbolFile:sourcePath toDestinationPath:destinationPath];
}
- (FBFuture<FBFuture<NSNull *> *> *)tail:(NSString *)path toConsumer:(id<FBDataConsumer>)consumer
{
return [[FBControlCoreError
describeFormat:@"%@ does not make sense for Symbols", NSStringFromSelector(_cmd)]
failFuture];
}
- (FBFuture<NSNull *> *)createDirectory:(NSString *)directoryPath
{
return [[FBControlCoreError
describeFormat:@"%@ does not make sense for Symbols", NSStringFromSelector(_cmd)]
failFuture];
}
- (FBFuture<NSNull *> *)moveFrom:(NSString *)sourcePath to:(NSString *)destinationPath
{
return [[FBControlCoreError
describeFormat:@"%@ does not make sense for Symbols", NSStringFromSelector(_cmd)]
failFuture];
}
- (FBFuture<NSNull *> *)remove:(NSString *)path
{
return [[FBControlCoreError
describeFormat:@"%@ does not make sense for Symbols", NSStringFromSelector(_cmd)]
failFuture];
}
- (FBFuture<NSArray<NSString *> *> *)contentsOfDirectory:(NSString *)path
{
return [[self.commands
listSymbols]
onQueue:self.queue map:^(NSArray<NSString *> *listedSymbols) {
return [listedSymbols arrayByAddingObject:ExtractedSymbolsDirectory];
}];
}
@end
@interface FBDeviceFileCommands ()
@property (nonatomic, strong, readonly) FBDevice *device;
@property (nonatomic, assign, readonly) AFCCalls afcCalls;
@end
@implementation FBDeviceFileCommands
#pragma mark Initializers
+ (instancetype)commandsWithTarget:(FBDevice *)target afcCalls:(AFCCalls)afcCalls
{
return [[self alloc] initWithDevice:target afcCalls:afcCalls];
}
+ (instancetype)commandsWithTarget:(FBDevice *)target
{
return [self commandsWithTarget:target afcCalls:FBAFCConnection.defaultCalls];
}
- (instancetype)initWithDevice:(FBDevice *)device afcCalls:(AFCCalls)afcCalls
{
self = [super init];
if (!self) {
return nil;
}
_device = device;
_afcCalls = afcCalls;
return self;
}
#pragma mark FBFileCommands
- (FBFutureContext<id<FBFileContainer>> *)fileCommandsForContainerApplication:(NSString *)bundleID
{
return [[self.device
houseArrestAFCConnectionForBundleID:bundleID afcCalls:self.afcCalls]
onQueue:self.device.asyncQueue pend:^ FBFuture<id<FBFileContainer>> * (FBAFCConnection *connection) {
return [FBFuture futureWithResult:[[FBDeviceFileContainer alloc] initWithAFCConnection:connection queue:self.device.asyncQueue]];
}];
}
- (FBFutureContext<id<FBFileContainer>> *)fileCommandsForAuxillary
{
return [FBFutureContext futureContextWithResult:[FBFileContainer fileContainerForBasePath:self.device.auxillaryDirectory]];
}
- (FBFutureContext<id<FBFileContainer>> *)fileCommandsForApplicationContainers
{
return [[FBControlCoreError
describeFormat:@"%@ not supported on devices, requires a rooted device", NSStringFromSelector(_cmd)]
failFutureContext];
}
- (FBFutureContext<id<FBFileContainer>> *)fileCommandsForGroupContainers
{
return [[FBControlCoreError
describeFormat:@"%@ not supported on devices, requires a rooted device", NSStringFromSelector(_cmd)]
failFutureContext];
}
- (FBFutureContext<id<FBFileContainer>> *)fileCommandsForRootFilesystem
{
return [[FBControlCoreError
describeFormat:@"%@ not supported on devices, requires a rooted device", NSStringFromSelector(_cmd)]
failFutureContext];
}
- (FBFutureContext<id<FBFileContainer>> *)fileCommandsForMediaDirectory
{
return [[self.device
startAFCService:@"com.apple.afc"]
onQueue:self.device.asyncQueue pend:^ FBFuture<id<FBFileContainer>> * (FBAFCConnection *connection) {
return [FBFuture futureWithResult:[[FBDeviceFileContainer alloc] initWithAFCConnection:connection queue:self.device.asyncQueue]];
}];
}
- (FBFutureContext<id<FBFileContainer>> *)fileCommandsForProvisioningProfiles
{
return [FBFutureContext futureContextWithResult:[FBFileContainer fileContainerForProvisioningProfileCommands:[FBDeviceProvisioningProfileCommands commandsWithTarget:self.device] queue:self.device.workQueue]];
}
- (FBFutureContext<id<FBFileContainer>> *)fileCommandsForMDMProfiles
{
return [[self.device
startService:FBManagedConfigService]
onQueue:self.device.asyncQueue pend:^ FBFuture<id<FBFileContainer>> * (FBAMDServiceConnection *connection) {
FBManagedConfigClient *managedConfig = [FBManagedConfigClient managedConfigClientWithConnection:connection logger:self.device.logger];
return [FBFuture futureWithResult:[[FBDeviceFileContainer_MDMProfiles alloc] initWithManagedConfig:managedConfig queue:self.device.workQueue]];
}];
}
- (FBFutureContext<id<FBFileContainer>> *)fileCommandsForSpringboardIconLayout
{
return [[self.device
startService:FBSpringboardServiceName]
onQueue:self.device.asyncQueue pend:^ FBFuture<id<FBFileContainer>> * (FBAMDServiceConnection *connection) {
return [FBFuture futureWithResult:[[FBSpringboardServicesClient springboardServicesClientWithConnection:connection logger:self.device.logger] iconContainer]];
}];
}
- (FBFutureContext<id<FBFileContainer>> *)fileCommandsForWallpaper
{
return [[FBFutureContext
futureContextWithFutureContexts:@[
[self.device startService:FBSpringboardServiceName],
[self.device startService:FBManagedConfigService],
]]
onQueue:self.device.asyncQueue pend:^ FBFuture<id<FBFileContainer>> * (NSArray<FBAMDServiceConnection *> *connections) {
FBSpringboardServicesClient *springboard = [FBSpringboardServicesClient springboardServicesClientWithConnection:connections[0] logger:self.device.logger];
FBManagedConfigClient *managedConfig = [FBManagedConfigClient managedConfigClientWithConnection:connections[1] logger:self.device.logger];
return [FBFuture futureWithResult:[[FBDeviceFileContainer_Wallpaper alloc] initWithSpringboard:springboard managedConfig:managedConfig queue:self.device.workQueue]];
}];
}
- (FBFutureContext<id<FBFileContainer>> *)fileCommandsForDiskImages
{
return [FBFutureContext futureContextWithResult:[[FBDeviceFileCommands_DiskImages alloc] initWithCommands:self.device queue:self.device.asyncQueue]];
}
- (FBFutureContext<id<FBFileContainer>> *)fileCommandsForSymbols
{
return [FBFutureContext futureContextWithResult:[[FBDeviceFileCommands_Symbols alloc] initWithCommands:self.device queue:self.device.asyncQueue]];
}
@end