From 51b0acd2d00b449c661333f73ceaa61dc65eb255 Mon Sep 17 00:00:00 2001 From: Tom Burgin Date: Wed, 1 Aug 2018 15:58:02 -0400 Subject: [PATCH] support t2 macs (#41) * image t2 macs - update error handling * only process whole disk disappear * spelling --- Common/Disk.h | 1 - Common/Disk.m | 3 - Common/RestorProtocol.h | 9 +- Podfile.lock | 9 +- Restor.xcodeproj/project.pbxproj | 48 ----- .../CollectionViewItemImaging.m | 3 + .../ViewControllers/MainViewController.m | 29 ++- Restor/Info.plist | 4 +- Restor/Model/ImagingSession.h | 1 + Restor/Model/ImagingSession.m | 12 +- restord/ImageSessionServer.m | 204 +++++++----------- restord/Info.plist | 4 +- 12 files changed, 116 insertions(+), 211 deletions(-) diff --git a/Common/Disk.h b/Common/Disk.h index bbe1d25..83bd9b7 100644 --- a/Common/Disk.h +++ b/Common/Disk.h @@ -27,7 +27,6 @@ @property(readonly, nonatomic) NSString *volName; @property(readonly, nonatomic) NSString *volKind; @property(readonly, nonatomic) NSString *mediaName; -@property(readonly, nonatomic) NSString *mediaContent; @property(readonly, nonatomic) BOOL isInternal; @property(readonly, nonatomic) BOOL isWhole; @property(readonly, nonatomic) BOOL isNetwork; diff --git a/Common/Disk.m b/Common/Disk.m index 8cf6c01..4c17363 100644 --- a/Common/Disk.m +++ b/Common/Disk.m @@ -27,7 +27,6 @@ - (instancetype)initWithDictionary:(NSDictionary *)dict { _volName = dict[(__bridge NSString *)kDADiskDescriptionVolumeNameKey]; _volKind = dict[(__bridge NSString *)kDADiskDescriptionVolumeKindKey]; _mediaName = dict[(__bridge NSString *)kDADiskDescriptionMediaNameKey]; - _mediaContent = dict[(__bridge NSString *)kDADiskDescriptionMediaContentKey]; _diskSize = dict[(__bridge NSString *)kDADiskDescriptionMediaSizeKey]; _protocol = dict[(__bridge NSString *)kDADiskDescriptionDeviceProtocolKey]; _isWhole = [dict[(__bridge NSString *)kDADiskDescriptionMediaWholeKey] boolValue]; @@ -61,7 +60,6 @@ - (void)encodeWithCoder:(NSCoder *)coder { [coder encodeObject:self.volName forKey:@"volName"]; [coder encodeObject:self.volKind forKey:@"volKind"]; [coder encodeObject:self.mediaName forKey:@"mediaName"]; - [coder encodeObject:self.mediaContent forKey:@"mediaContent"]; [coder encodeObject:self.diskSize forKey:@"diskSize"]; [coder encodeObject:self.protocol forKey:@"protocol"]; [coder encodeObject:@(self.isWhole) forKey:@"isWhole"]; @@ -77,7 +75,6 @@ - (instancetype)initWithCoder:(NSCoder *)decoder { _volName = [decoder decodeObjectOfClass:[NSString class] forKey:@"volName"]; _volKind = [decoder decodeObjectOfClass:[NSString class] forKey:@"volKind"]; _mediaName = [decoder decodeObjectOfClass:[NSString class] forKey:@"mediaName"]; - _mediaContent = [decoder decodeObjectOfClass:[NSString class] forKey:@"mediaContent"]; _diskSize = [decoder decodeObjectOfClass:[NSNumber class] forKey:@"diskSize"]; _protocol = [decoder decodeObjectOfClass:[NSString class] forKey:@"protocol"]; _isWhole = [[decoder decodeObjectOfClass:[NSNumber class] forKey:@"isWhole"] boolValue]; diff --git a/Common/RestorProtocol.h b/Common/RestorProtocol.h index e976d9e..7b44c10 100644 --- a/Common/RestorProtocol.h +++ b/Common/RestorProtocol.h @@ -69,17 +69,16 @@ - (void)verifyingPercentage:(NSUInteger)percent; /* - Notifies the UI that an error has occurred and imaging has stopped. - - @param error An error object containing details of the error that occurred. + Notifies the UI that inverting has started. */ -- (void)errorOccurred:(NSError *)error; +- (void)invertingStarted; /* Notifies the UI that imaging has completed and ASR has exited. @param success YES if ASR exited with 0. + @param error An error object containing details of the error that occurred. */ -- (void)imageAppliedSuccess:(BOOL)success; +- (void)imageAppliedSuccess:(BOOL)success error:(NSError *)error; @end diff --git a/Podfile.lock b/Podfile.lock index 1a10ee9..49f7784 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -11,6 +11,13 @@ DEPENDENCIES: - MOLAuthenticatingURLSession - MOLXPCConnection +SPEC REPOS: + https://github.com/cocoapods/specs.git: + - MOLAuthenticatingURLSession + - MOLCertificate + - MOLCodesignChecker + - MOLXPCConnection + SPEC CHECKSUMS: MOLAuthenticatingURLSession: c238aa1c9a7b1077eb39a6f40204bfe76a7d204e MOLCertificate: c999513316d511c69f290fbf313dfe8dca4ad592 @@ -19,4 +26,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 5cf4b07f7d175476d2cc6f41208074b1933af454 -COCOAPODS: 1.4.0 +COCOAPODS: 1.5.3 diff --git a/Restor.xcodeproj/project.pbxproj b/Restor.xcodeproj/project.pbxproj index b763439..283ad51 100644 --- a/Restor.xcodeproj/project.pbxproj +++ b/Restor.xcodeproj/project.pbxproj @@ -318,8 +318,6 @@ 0DE120391DF5D13300ABAC7E /* Frameworks */, 0DE1203A1DF5D13300ABAC7E /* Resources */, 0DE120861DF5D68000ABAC7E /* CopyFiles */, - 4EABD2DDB9043AF4D6CBCC84 /* [CP] Embed Pods Frameworks */, - D9C29F6DD958B285BAAADCE2 /* [CP] Copy Pods Resources */, 0DE1208F1DF6088700ABAC7E /* Reset Info.plist */, ); buildRules = ( @@ -341,7 +339,6 @@ 0DE1206F1DF5D49100ABAC7E /* Sources */, 0DE120701DF5D49100ABAC7E /* Frameworks */, 0DE120711DF5D49100ABAC7E /* CopyFiles */, - 37E4C1CE4524B834E53D48F5 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -447,21 +444,6 @@ shellPath = /bin/sh; shellScript = "sh ./Common/requirement_builder.sh reset-app\nsh ./Common/requirement_builder.sh reset-helper"; }; - 37E4C1CE4524B834E53D48F5 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-com.google.corp.restord/Pods-com.google.corp.restord-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; 4568AE51EB71AE55CEB0C12C /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -480,21 +462,6 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 4EABD2DDB9043AF4D6CBCC84 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Restor/Pods-Restor-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; A371B301FC3DB0BDEC89BDE7 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -513,21 +480,6 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - D9C29F6DD958B285BAAADCE2 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Restor/Pods-Restor-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ diff --git a/Restor/Controllers/ViewControllers/CollectionViewItemImaging.m b/Restor/Controllers/ViewControllers/CollectionViewItemImaging.m index 0af8fdd..3ed2455 100644 --- a/Restor/Controllers/ViewControllers/CollectionViewItemImaging.m +++ b/Restor/Controllers/ViewControllers/CollectionViewItemImaging.m @@ -41,6 +41,8 @@ - (NSString *)imagingStatusString { return NSLocalizedString(@"Starting...", nil); case ImagingStageImaging: return NSLocalizedString(@"Imaging...", nil); + case ImagingStageInverting: + return NSLocalizedString(@"Inverting...", nil); case ImagingStageVerifying: return NSLocalizedString(@"Verifying...", nil); case ImagingStageComplete: @@ -56,6 +58,7 @@ - (float)progressPercent { case ImagingStageError: return 0.0; case ImagingStageImaging: + case ImagingStageInverting: return self.imagingSession.percentComplete * kImagingRatioOfTotal; case ImagingStageVerifying: return (kImagingRatioOfTotal * 100) + diff --git a/Restor/Controllers/ViewControllers/MainViewController.m b/Restor/Controllers/ViewControllers/MainViewController.m index 3679d22..863ec90 100644 --- a/Restor/Controllers/ViewControllers/MainViewController.m +++ b/Restor/Controllers/ViewControllers/MainViewController.m @@ -72,6 +72,7 @@ - (void)createDiskWatcher { dispatch_async(dispatch_get_main_queue(), ^{ STRONGIFY(self); + if ([self.connectedDisks containsObject:disk]) return; [[self mutableArrayValueForKey:@"connectedDisks"] addObject:disk]; [self.collectionView reloadData]; @@ -88,17 +89,27 @@ - (void)createDiskWatcher { }; self.diskWatcher.disappearCallback = ^(Disk *disk) { STRONGIFY(self); - ImagingSession *is = self.imagingSessions[disk.bsdName]; - self.imagingSessions[disk.bsdName] = nil; - dispatch_sync(dispatch_get_main_queue(), ^{ - STRONGIFY(self); - [[self mutableArrayValueForKey:@"connectedDisks"] removeObject:disk]; - [self.collectionView reloadData]; - }); + // Only proccess whole disks + if (!disk.isWhole) return; + + // Remove all leaf disks + for (Disk *connectedDisk in self.connectedDisks) { + if (![connectedDisk.bsdName hasPrefix:disk.bsdName]) continue; + + ImagingSession *is = self.imagingSessions[connectedDisk.bsdName]; + self.imagingSessions[connectedDisk.bsdName] = nil; + + dispatch_sync(dispatch_get_main_queue(), ^{ + STRONGIFY(self); + [[self mutableArrayValueForKey:@"connectedDisks"] removeObject:connectedDisk]; + [self.collectionView reloadData]; + }); + + // Do this after the UI updates to avoid breaking KVO. + [is cancel]; + } - // Do this after the UI updates to avoid breaking KVO. - [is cancel]; }; [self.diskWatcher beginWatching]; } diff --git a/Restor/Info.plist b/Restor/Info.plist index 0dac919..fbf0386 100644 --- a/Restor/Info.plist +++ b/Restor/Info.plist @@ -15,9 +15,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.6 + 1.7 CFBundleVersion - 1.6 + 1.7 LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright diff --git a/Restor/Model/ImagingSession.h b/Restor/Model/ImagingSession.h index 71640a7..bfb6365 100644 --- a/Restor/Model/ImagingSession.h +++ b/Restor/Model/ImagingSession.h @@ -32,6 +32,7 @@ typedef NS_ENUM(NSInteger, ImagingStage) { ImagingStageNotStarted, ImagingStageImaging, + ImagingStageInverting, ImagingStageVerifying, ImagingStageComplete, ImagingStageError, diff --git a/Restor/Model/ImagingSession.m b/Restor/Model/ImagingSession.m index e417a1f..5425e2c 100644 --- a/Restor/Model/ImagingSession.m +++ b/Restor/Model/ImagingSession.m @@ -29,7 +29,6 @@ @interface ImagingSession () @property(readwrite) NSUInteger percentComplete; @property(readwrite) ImagingStage imagingStage; @property(readwrite) NSError *lastError; -@property NSError *error; @end @implementation ImagingSession @@ -70,7 +69,6 @@ - (void)imagingPercentage:(NSUInteger)percent { dispatch_async(dispatch_get_main_queue(), ^{ self.imagingStage = ImagingStageImaging; self.percentComplete = percent; - self.error = nil; }); } @@ -78,23 +76,21 @@ - (void)verifyingPercentage:(NSUInteger)percent { dispatch_async(dispatch_get_main_queue(), ^{ self.imagingStage = ImagingStageVerifying; self.percentComplete = percent; - self.error = nil; }); } -- (void)errorOccurred:(NSError *)error { +- (void)invertingStarted { dispatch_async(dispatch_get_main_queue(), ^{ - self.imagingStage = ImagingStageError; - self.error = error; + self.imagingStage = ImagingStageInverting; }); } -- (void)imageAppliedSuccess:(BOOL)success { +- (void)imageAppliedSuccess:(BOOL)success error:(NSError *)error { dispatch_async(dispatch_get_main_queue(), ^{ if (!success) { self.imagingStage = ImagingStageError; self.percentComplete = 0; - self.lastError = self.error; + self.lastError = error; } else { self.imagingStage = ImagingStageComplete; self.percentComplete = 100; diff --git a/restord/ImageSessionServer.m b/restord/ImageSessionServer.m index 2bfccde..f36a750 100644 --- a/restord/ImageSessionServer.m +++ b/restord/ImageSessionServer.m @@ -36,8 +36,6 @@ @interface ImageSessionServer () /// The source URL of the image @property(copy) Image *image; -@property BOOL isImageAPFS; - /// The target disk/partition. @property(copy) Disk *targetDisk; @@ -60,6 +58,9 @@ @interface ImageSessionServer () @property DASessionRef diskArbSession; @property DADiskRef diskRef; +// Unknown ASR lines +@property NSMutableArray *asrUnknownLines; + @end @implementation ImageSessionServer @@ -74,7 +75,7 @@ - (instancetype)initWithImage:(nonnull Image *)image _destination = [NSURL fileURLWithPathComponents:@[@"/dev", targetDisk.bsdName]]; _client = conn; _startDate = [NSDate date]; - _isImageAPFS = [self isImageAPFSContainer]; + _asrUnknownLines = [NSMutableArray array]; NSLog(@"%@ New imaging client: s: '%@', d: %@", self, _image.name, _destination.path); @@ -103,147 +104,105 @@ - (void)dealloc { - (void)beginImaging { [self unmountDisk:self.diskRef]; - int diskUtilReturnCode = [self eraseDisk]; - NSLog(@"%@ DiskUtil exit code: %d", self, diskUtilReturnCode); + // Remove any top level recovery partitions. + [self removeRecovery]; int asrReturnCode = [self applyImage]; NSLog(@"%@ ASR exit code: %d", self, asrReturnCode); + NSError *err; if (asrReturnCode == 0) { DADiskRef disk = NULL; - if (self.isImageAPFS) { - NSString *apfsBootDisk = [NSString stringWithFormat:@"/dev/%@", [self apfsBootDisk]]; + + // Get the synthesized "Macintosh HD" apfs disk, if any. + NSString *apfsOSDisk = [self apfsOSDisk]; + if (apfsOSDisk) { + NSString *apfsBootDisk = [NSString stringWithFormat:@"/dev/%@", apfsOSDisk]; disk = DADiskCreateFromBSDName(NULL, self.diskArbSession, apfsBootDisk.UTF8String); } + NSURL *mountURL = [self mountDisk:disk ?: self.diskRef]; - if (!mountURL) { - NSLog(@"%@ Unable to remount target, skipping imaginfo.plist application", self); - } else { + if (mountURL) { NSLog(@"%@ Remounted target %@ as %@", self, self.destination.path, mountURL.path); - - // Set imageinfo.plist keys [self applyImageInfo:mountURL]; - [self blessMountURL:mountURL]; [self unmountDisk:disk ?: self.diskRef]; + } else { + NSLog(@"%@ Unable to remount target, skipping imaginfo.plist application", self); } + if (disk) CFRelease(disk); + } else { + err = [self constructErrorFromReturnCode:asrReturnCode]; } // Send final reply with task status. - [[self.client remoteObjectProxy] imageAppliedSuccess:(asrReturnCode == 0)]; + [[self.client remoteObjectProxy] imageAppliedSuccess:(asrReturnCode == 0) error:err]; } -- (void)cancelImaging { - NSLog(@"%@ Cancelling imaging!", self); - if (self.asr.isRunning) [self.asr terminate]; - self.asr = nil; -} +- (NSError *)constructErrorFromReturnCode:(int)code { + NSMutableString *s = [NSMutableString stringWithFormat:@"ASR failed with exit code: %d.", code]; -- (int)eraseDisk { - // Check the target disk's GPT to erase with the appropriate tools - if ([self.targetDisk.mediaContent isEqualToString:kGPTAPFSUUID]) { - NSLog(@"Delete APFS Container"); - return [self deleteAPFSContainer]; - } else if ([self.targetDisk.mediaContent isEqualToString:kGPTCoreStorageUUID]) { - NSLog(@"Delete CoreStorage LogicalVolumeGroup"); - NSString *lvg = [self coreStorageLogicalVolumeGroup]; - return [self deleteCoreStorageLogicalVolumeGroup:lvg]; - } else { - NSLog(@"Erase HFS Volume"); - NSTask *diskUtil = [[NSTask alloc] init]; - diskUtil.standardOutput = [NSPipe pipe]; - diskUtil.standardError = [NSPipe pipe]; - diskUtil.launchPath = @"/usr/sbin/diskutil"; - diskUtil.arguments = @[ @"eraseVolume", @"JHFS+", @"%noformat", self.destination.path ]; - [diskUtil launch]; - [diskUtil waitUntilExit]; - return diskUtil.terminationStatus; + if (self.asrUnknownLines.count) { + [s appendString:@"\nPotential Errors:"]; + for (NSString *line in self.asrUnknownLines) { + [s appendFormat:@"\n%@", line]; + } + } + + NSOperatingSystemVersion version = {10, 13, 4}; + if (![[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:version]) { + [s appendString:@"\nHave you tried Restor on macOS 10.13.4 or above?"]; } + + NSDictionary *info = @{ NSLocalizedDescriptionKey: s }; + return [NSError errorWithDomain:@"com.google.corp.restord" code:555 userInfo:info]; } -- (int)deleteCoreStorageLogicalVolumeGroup:(NSString *)lvg { - if (!lvg) return -1; - NSTask *diskUtil = [[NSTask alloc] init]; - diskUtil.launchPath = @"/usr/sbin/diskutil"; - diskUtil.arguments = @[ @"cs", @"delete", lvg ]; - [diskUtil launch]; - [diskUtil waitUntilExit]; - return diskUtil.terminationStatus; +- (void)cancelImaging { + NSLog(@"%@ Cancelling imaging!", self); + if (self.asr.isRunning) [self.asr terminate]; + self.asr = nil; } -- (NSString *)coreStorageLogicalVolumeGroup { +- (NSString *)recoveryDevice { NSTask *diskUtil = [[NSTask alloc] init]; diskUtil.standardOutput = [NSPipe pipe]; - diskUtil.standardError = [NSPipe pipe]; diskUtil.launchPath = @"/usr/sbin/diskutil"; - diskUtil.arguments = @[ @"cs", @"info", @"-plist", self.destination.path ]; + diskUtil.arguments = @[ @"info", @"-plist", self.destination.path ]; [diskUtil launch]; [diskUtil waitUntilExit]; NSData *sout = [[diskUtil.standardOutput fileHandleForReading] readDataToEndOfFile]; if (sout) { - NSDictionary *csDict = [NSPropertyListSerialization propertyListWithData:sout - options:0 - format:NULL - error:NULL]; - return csDict[@"MemberOfCoreStorageLogicalVolumeGroup"]; + NSDictionary *info = [NSPropertyListSerialization propertyListWithData:sout + options:0 + format:NULL + error:NULL]; + if ([info isKindOfClass:[NSDictionary class]]) return info[@"RecoveryDeviceIdentifier"]; } return nil; } -- (int)deleteAPFSContainer { - NSTask *diskUtil = [[NSTask alloc] init]; - diskUtil.launchPath = @"/usr/sbin/diskutil"; - diskUtil.arguments = @[ @"apfs", @"deleteContainer", self.destination.path ]; - [diskUtil launch]; - [diskUtil waitUntilExit]; - return diskUtil.terminationStatus; -} - -- (int)createAPFSContainer { +- (void)removeRecovery { + NSString *recoveryDevice = [self recoveryDevice]; + if (!recoveryDevice) return; + NSLog(@"%@ Removing recovery device: %@", self, recoveryDevice); NSTask *diskUtil = [[NSTask alloc] init]; diskUtil.launchPath = @"/usr/sbin/diskutil"; - diskUtil.arguments = @[ @"apfs", @"createContainer", self.destination.path ]; + diskUtil.arguments = @[ @"eraseVolume", @"Free", @"Space", recoveryDevice ]; [diskUtil launch]; [diskUtil waitUntilExit]; - return diskUtil.terminationStatus; -} - -- (NSArray *)partitionsForImageDict:(NSDictionary *)imageDict { - if (![imageDict isKindOfClass:[NSDictionary class]]) return nil; - if (![imageDict[@"partitions"] isKindOfClass:[NSDictionary class]]) return nil; - if (![imageDict[@"partitions"][@"partitions"] isKindOfClass:[NSArray class]]) return nil; - return imageDict[@"partitions"][@"partitions"]; -} - -- (BOOL)isImageAPFSContainer { - NSTask *hdiUtil = [[NSTask alloc] init]; - hdiUtil.standardOutput = [NSPipe pipe]; - hdiUtil.standardError = [NSPipe pipe]; - hdiUtil.launchPath = @"/usr/bin/hdiutil"; - hdiUtil.arguments = @[ @"imageinfo", @"-plist", self.image.localURL.path ]; - [hdiUtil launch]; - [hdiUtil waitUntilExit]; - - NSData *sout = [[hdiUtil.standardOutput fileHandleForReading] readDataToEndOfFile]; - if (sout) { - NSDictionary *imageDict = [NSPropertyListSerialization propertyListWithData:sout - options:0 - format:NULL - error:NULL]; - for (NSDictionary *partition in [self partitionsForImageDict:imageDict]) { - if (![partition isKindOfClass:[NSDictionary class]]) return NO; - if ([partition[@"partition-hint-UUID"] isEqualToString:kGPTAPFSUUID]) return YES; - } + if (diskUtil.terminationStatus == 0) { + NSLog(@"%@ Recovery device: %@ removed", self, recoveryDevice); + } else { + NSLog(@"%@ Failed to remove recovery device: %@", self, recoveryDevice); } - return NO; } -- (NSString *)apfsBootDisk { +- (NSString *)apfsOSDisk { NSTask *diskUtil = [[NSTask alloc] init]; diskUtil.standardOutput = [NSPipe pipe]; - diskUtil.standardError = [NSPipe pipe]; diskUtil.launchPath = @"/usr/sbin/diskutil"; diskUtil.arguments = @[ @"apfs", @"list", @"-plist" ]; [diskUtil launch]; @@ -255,11 +214,11 @@ - (NSString *)apfsBootDisk { options:0 format:NULL error:NULL]; - // Find the boot APFS volume and return it's BSD disk name. + // Find the APFS OS volume (Macintosh HD) and return its BSD disk name. for (NSDictionary *c in apfsDict[@"Containers"]) { if (![c isKindOfClass:[NSDictionary class]]) return nil; if (![c[@"DesignatedPhysicalStore"] isEqualToString:self.targetDisk.bsdName]) continue; - // Find the first volume that does not have a role. This should be the boot volume. + // Find the first volume that does not have a role. This should be the OS volume. for (NSDictionary *volume in c[@"Volumes"]) { if (![volume isKindOfClass:[NSDictionary class]]) return nil; if (![volume[@"Roles"] isKindOfClass:[NSArray class]]) return nil; @@ -273,22 +232,6 @@ - (NSString *)apfsBootDisk { - (int)applyImage { NSString *path = self.image.localURL.path; - // asr chokes if the destination is not already an APFS container when applying an APFS image. - if (self.isImageAPFS) { - NSLog(@"Create APFS Container"); - int ret = [self createAPFSContainer]; - if (ret != 0) { - NSError *e = [NSError errorWithDomain:@"com.google.corp.restord" - code:555 - userInfo:@{ - NSLocalizedDescriptionKey:@"You can only apply a 10.13 or later image " - @"from a Mac running 10.13 or later." - }]; - [[self.client remoteObjectProxy] errorOccurred:e]; - return ret; - } - } - self.asr = [[NSTask alloc] init]; self.asr.launchPath = @"/usr/sbin/asr"; self.asr.arguments = @[ @"restore", @@ -322,8 +265,7 @@ - (int)applyImage { // Clear readability handler or the file handle is never released. outputFh.readabilityHandler = nil; - - return self.asr.terminationStatus; + return self.asr ? self.asr.terminationStatus : -1; } - (void)processOutput:(NSData *)output { @@ -357,18 +299,20 @@ - (void)processOutput:(NSData *)output { } } else if ([line isEqualToString:@"Personalization over TDM succeeded"]) { NSLog(@"%@ %@", self, line); + } else if ([line isEqualToString:@"Inverting target volume..."]) { + [[self.client remoteObjectProxy] invertingStarted]; } else if (line.length >= dirtyLine.length && ![line isEqualToString:@"done"] && ![line isEqualToString:@"Validating target..."] && + ![line isEqualToString:@"Validating source..."] && ![line isEqualToString:@"Validating sizes..."] && + ![line isEqualToString:@"Repartitioning target device..."] && + ![line isEqualToString:@"Retrieving scan information..."] && ![line hasPrefix:@"nx_kernel_mount"]) { // If line doesn't begin with a tab (an INFO message) and isn't a known info message, - // treat it as an error :-( - NSLog(@"%@ ASR Error: %@", self, line); - NSError *e = [NSError errorWithDomain:@"com.google.corp.restord" - code:666 - userInfo:@{ NSLocalizedDescriptionKey: line }]; - [[self.client remoteObjectProxy] errorOccurred:e]; + // save the line and surface it as an error if ASR fails. + NSLog(@"%@ ASR unknown line: %@", self, line); + [self.asrUnknownLines addObject:line]; } } } @@ -395,7 +339,7 @@ - (NSURL *)mountDisk:(DADiskRef)disk { DADiskMount(disk, (__bridge CFURLRef)directoryURL, 0, &MountUnmountCallback, (__bridge void *)sema); if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) { - NSLog(@"%@ timed out while mounting disk", self); + NSLog(@"%@ Timed out while mounting disk", self); return nil; } @@ -408,7 +352,7 @@ - (void)unmountDisk:(DADiskRef)disk { dispatch_semaphore_t sema = dispatch_semaphore_create(0); DADiskUnmount(disk, kDADiskUnmountOptionForce, &MountUnmountCallback, (__bridge void *)sema); if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) { - NSLog(@"%@ timed out while unmounting disk", self); + NSLog(@"%@ Timed out while unmounting disk", self); } } @@ -439,18 +383,14 @@ - (void)applyImageInfo:(NSURL *)mountURL { format:NSPropertyListXMLFormat_v1_0 options:0 error:&error]; - if (!error) { - [plistData writeToURL:imageInfoURL options:NSDataWritingAtomic error:&error]; - } - - if (error) { + if ([plistData writeToURL:imageInfoURL options:NSDataWritingAtomic error:&error]) { + NSLog(@"%@ Successfully written: %@", self, imageInfoURL.path); + } else { NSLog(@"%@ Failed to write imageinfo.plist: %@", self, error); } } - (void)blessMountURL:(NSURL *)mountURL { - NSOperatingSystemVersion version = {10, 13, 4}; - if (![[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:version]) return; NSTask *bless = [[NSTask alloc] init]; bless.launchPath = @"/usr/sbin/bless"; bless.arguments = @[ @"--folder", mountURL.path ]; diff --git a/restord/Info.plist b/restord/Info.plist index 37bf6f1..f21317f 100644 --- a/restord/Info.plist +++ b/restord/Info.plist @@ -15,9 +15,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.6 + 1.7 CFBundleVersion - 7 + 8 LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright