FBDeviceControl/Commands/FBDeviceDeveloperDiskImageCommands.m (196 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 "FBDeviceDeveloperDiskImageCommands.h"
#import "FBAMDServiceConnection.h"
#import "FBDevice.h"
#import "FBDeviceControlError.h"
static NSString *const MountPathKey = @"MountPath";
static NSString *const ImageTypeKey = @"ImageType";
static NSString *const ImageSignatureKey = @"ImageSignature";
static NSString *const CommandKey = @"Command";
static NSString *const DiskImageTypeDeveloper = @"Developer";
static NSString *const ImageMounterService = @"com.apple.mobile.mobile_image_mounter";
static void MountCallback(NSDictionary<NSString *, id> *callbackDictionary, id<FBDeviceCommands> device)
{
[device.logger logFormat:@"Mount Progress: %@", [FBCollectionInformation oneLineDescriptionFromDictionary:callbackDictionary]];
}
@interface FBDeviceDeveloperDiskImageCommands ()
@property (nonatomic, weak, readonly) FBDevice *device;
@end
@implementation FBDeviceDeveloperDiskImageCommands
#pragma mark Initializers
+ (instancetype)commandsWithTarget:(FBDevice *)target
{
return [[self alloc] initWithDevice:target];
}
- (instancetype)initWithDevice:(FBDevice *)device
{
self = [super init];
if (!self) {
return nil;
}
_device = device;
return self;
}
#pragma mark FBDeveloperDiskImageCommands Implementation
static const int DiskImageMountingError = -402653066; // 0xe8000076 in hex
- (FBFuture<NSArray<FBDeveloperDiskImage *> *> *)mountedDiskImages
{
return [[self
mountInfoToDiskImage]
onQueue:self.device.asyncQueue map:^(NSDictionary<NSDictionary<NSString *, id> *, FBDeveloperDiskImage *> *mountInfoToDiskImage) {
return [mountInfoToDiskImage allValues];
}];
}
- (FBFuture<FBDeveloperDiskImage *> *)mountDiskImage:(FBDeveloperDiskImage *)diskImage
{
return [self mountDeveloperDiskImage:diskImage imageType:DiskImageTypeDeveloper];
}
- (FBFuture<NSNull *> *)unmountDiskImage:(FBDeveloperDiskImage *)diskImage
{
return [[self
mountedImageEntries]
onQueue:self.device.workQueue fmap:^ FBFuture<NSNull *> * (NSArray<NSDictionary<NSString *, id> *> *mountEntries) {
for (NSDictionary<NSString *, id> *mountEntry in mountEntries) {
NSData *mountSingature = mountEntry[ImageSignatureKey];
if (![mountSingature isEqualToData:diskImage.signature]) {
continue;
}
NSString *mountPath = mountEntry[MountPathKey];
return [self unmountDiskImageAtPath:mountPath];
}
return [[FBDeviceControlError
describeFormat:@"%@ does not appear to be mounted", diskImage]
failFuture];
}];
}
- (NSArray<FBDeveloperDiskImage *> *)mountableDiskImages
{
return FBDeveloperDiskImage.allDiskImages;
}
- (FBFuture<FBDeveloperDiskImage *> *)ensureDeveloperDiskImageIsMounted
{
NSError *error = nil;
NSOperatingSystemVersion targetVersion = [FBOSVersion operatingSystemVersionFromName:self.device.productVersion];
FBDeveloperDiskImage *diskImage = [FBDeveloperDiskImage developerDiskImage:targetVersion logger:self.device.logger error:&error];
if (!diskImage) {
return [FBFuture futureWithError:error];
}
return [self mountDeveloperDiskImage:diskImage imageType:DiskImageTypeDeveloper];
}
#pragma mark Private
- (FBFuture<NSDictionary<NSDictionary<NSString *, id> *, FBDeveloperDiskImage *> *> *)mountInfoToDiskImage
{
id<FBControlCoreLogger> logger = self.device.logger;
return [[self
mountedImageEntries]
onQueue:self.device.asyncQueue map:^(NSArray<NSDictionary<NSString *,id> *> *mountEntries) {
NSArray<FBDeveloperDiskImage *> *images = FBDeveloperDiskImage.allDiskImages;
NSDictionary<NSData *, FBDeveloperDiskImage *> *imagesBySignature = [NSDictionary dictionaryWithObjects:images forKeys:[images valueForKey:@"signature"]];
NSMutableDictionary<NSDictionary<NSString *, id> *, FBDeveloperDiskImage *> *mountEntryToDiskImage = NSMutableDictionary.dictionary;
for (NSDictionary<NSString *, id> *mountEntry in mountEntries) {
NSData *signature = mountEntry[ImageSignatureKey];
FBDeveloperDiskImage *image = imagesBySignature[signature];
if (!image) {
[logger logFormat:@"Could not find the location of the image mounted on the device %@", mountEntryToDiskImage];
continue;
}
mountEntryToDiskImage[mountEntry] = image;
}
return [mountEntryToDiskImage copy];
}];
}
- (FBFuture<NSArray<NSDictionary<NSString *, id> *> *> *)mountedImageEntries
{
return [[self.device
startService:ImageMounterService]
onQueue:self.device.asyncQueue pop:^(FBAMDServiceConnection *connection) {
NSDictionary<NSString *, id> *request = @{
CommandKey: @"CopyDevices",
};
NSError *error = nil;
NSDictionary<NSString *, id> *response = [connection sendAndReceiveMessage:request error:&error];
if (!response) {
return [FBFuture futureWithError:error];
}
NSString *errorString = response[@"Error"];
if (errorString) {
return [[FBDeviceControlError
describeFormat:@"Could not get mounted image info: %@", errorString]
failFuture];
}
NSArray<NSDictionary<NSString *, id> *> *entries = response[@"EntryList"];
return [FBFuture futureWithResult:entries];
}];
}
- (FBFuture<NSDictionary<NSData *, FBDeveloperDiskImage *> *> *)signatureToDiskImageOfMountedDisks
{
return [[self
mountInfoToDiskImage]
onQueue:self.device.asyncQueue map:^(NSDictionary<NSDictionary<NSString *, id> *, FBDeveloperDiskImage *> *mountInfoToDiskImage) {
NSMutableDictionary<NSData *, FBDeveloperDiskImage *> *signatureToDiskImage = NSMutableDictionary.dictionary;
for (FBDeveloperDiskImage *image in mountInfoToDiskImage.allValues) {
signatureToDiskImage[image.signature] = image;
}
return signatureToDiskImage;
}];
}
- (FBFuture<FBDeveloperDiskImage *> *)mountDeveloperDiskImage:(FBDeveloperDiskImage *)diskImage imageType:(NSString *)imageType
{
id<FBControlCoreLogger> logger = self.device.logger;
return [[self
signatureToDiskImageOfMountedDisks]
onQueue:self.device.asyncQueue fmap:^ FBFuture<FBDeveloperDiskImage *> * (NSDictionary<NSData *, FBDeveloperDiskImage *> *signatureToDiskImage) {
if (signatureToDiskImage[diskImage.signature]) {
[logger logFormat:@"Disk Image %@ is already mounted, avoiding re-mounting it", diskImage];
return [FBFuture futureWithResult:diskImage];
}
return [self performDiskImageMount:diskImage imageType:imageType];
}];
}
- (FBFuture<FBDeveloperDiskImage *> *)performDiskImageMount:(FBDeveloperDiskImage *)diskImage imageType:(NSString *)imageType
{
return [[self.device
connectToDeviceWithPurpose:@"mount_disk_image"]
onQueue:self.device.asyncQueue pop:^ FBFuture<NSDictionary<NSString *, NSDictionary<NSString *, id> *> *> * (id<FBDeviceCommands> device) {
NSDictionary<NSString *, id> *options = @{
@"ImageSignature": diskImage.signature,
@"ImageType": imageType,
};
int status = device.calls.MountImage(
device.amDeviceRef,
(__bridge CFStringRef)(diskImage.diskImagePath),
(__bridge CFDictionaryRef)(options),
(AMDeviceProgressCallback) MountCallback,
(__bridge void *) (device)
);
if (status == DiskImageMountingError) {
return [[FBDeviceControlError
describeFormat:@"Failed to mount image '%@', this can occur when the wrong disk image is mounted for the target OS, or a disk image of the same type is already mounted.", diskImage]
failFuture];
}
else if (status != 0) {
NSString *internalMessage = CFBridgingRelease(device.calls.CopyErrorText(status));
return [[FBDeviceControlError
describeFormat:@"Failed to mount image '%@' with error 0x%x (%@)", diskImage.diskImagePath, status, internalMessage]
failFuture];
}
return [FBFuture futureWithResult:diskImage];
}];
}
- (FBFuture<NSNull *> *)unmountDiskImageAtPath:(NSString *)mountPath
{
return [[self.device
startService:ImageMounterService]
onQueue:self.device.asyncQueue pop:^ FBFuture<NSNull *> * (FBAMDServiceConnection *connection) {
NSDictionary<NSString *, id> *request = @{
CommandKey: @"UnmountImage",
MountPathKey: mountPath,
};
NSError *error = nil;
NSDictionary<NSString *, id> *response = [connection sendAndReceiveMessage:request error:&error];
if (!response) {
return [FBFuture futureWithError:error];
}
return FBFuture.empty;
}];
}
@end