Skip to content

Commit

Permalink
Extract archives in a separate directory from the input archive (2.2.…
Browse files Browse the repository at this point in the history
…x) (#2553)
  • Loading branch information
zorgiepoo authored May 6, 2024
1 parent 8e27997 commit 80c8c1f
Show file tree
Hide file tree
Showing 15 changed files with 109 additions and 76 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: Build & Tests

on:
push:
branches: [ 2.x, 2.2.2-dev, master ]
branches: [ 2.x, 2.2.x, master ]
pull_request:
branches: [ 2.x, 2.2.2-dev, master ]
branches: [ 2.x, 2.2.x, master ]

jobs:
build:
Expand Down
17 changes: 14 additions & 3 deletions Autoupdate/AppInstaller.m
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ @interface AppInstaller () <NSXPCListenerDelegate, SUInstallerCommunicationProto
@property (nonatomic, readonly) NSString *userName;
@property (nonatomic) SUHost *host;
@property (nonatomic, copy) NSString *updateDirectoryPath;
@property (nonatomic, copy) NSString *extractionDirectory;
@property (nonatomic, copy) NSString *downloadName;
@property (nonatomic, copy) NSString *decryptionPassword;
@property (nonatomic, strong) SUSignatures *signatures;
Expand Down Expand Up @@ -94,6 +95,7 @@ @implementation AppInstaller
@synthesize updateValidator = _updateValidator;
@synthesize host = _host;
@synthesize updateDirectoryPath = _updateDirectoryPath;
@synthesize extractionDirectory = _extractionDirectory;
@synthesize downloadName = _downloadName;
@synthesize decryptionPassword = _decryptionPassword;
@synthesize signatures = _signatures;
Expand Down Expand Up @@ -193,7 +195,7 @@ - (void)extractAndInstallUpdate
[self.communicator handleMessageWithIdentifier:SPUExtractionStarted data:[NSData data]];

NSString *archivePath = [self.updateDirectoryPath stringByAppendingPathComponent:self.downloadName];
id<SUUnarchiverProtocol> unarchiver = [SUUnarchiver unarchiverForPath:archivePath updatingHostBundlePath:self.host.bundlePath decryptionPassword:self.decryptionPassword expectingInstallationType:self.installationType];
id<SUUnarchiverProtocol> unarchiver = [SUUnarchiver unarchiverForPath:archivePath extractionDirectory:self.extractionDirectory updatingHostBundlePath:self.host.bundlePath decryptionPassword:self.decryptionPassword expectingInstallationType:self.installationType];

NSError *unarchiverError = nil;
BOOL success = NO;
Expand Down Expand Up @@ -226,7 +228,7 @@ - (void)extractAndInstallUpdate
[self.communicator handleMessageWithIdentifier:SPUValidationStarted data:[NSData data]];

NSError *validationError = nil;
BOOL validationSuccess = [self.updateValidator validateWithUpdateDirectory:self.updateDirectoryPath error:&validationError];
BOOL validationSuccess = [self.updateValidator validateWithUpdateDirectory:self.extractionDirectory error:&validationError];

if (!validationSuccess) {
[self cleanupAndExitWithStatus:EXIT_FAILURE error:[NSError errorWithDomain:SUSparkleErrorDomain code:SPUInstallerError userInfo:@{ NSLocalizedDescriptionKey: @"Update validation was a failure", NSUnderlyingErrorKey: validationError }]];
Expand Down Expand Up @@ -276,6 +278,7 @@ - (void)unarchiverDidFailWithError:(NSError *)error
// but may as well set other fields to nil too
[self clearUpdateDirectory];
self.downloadName = nil;
self.extractionDirectory = nil;
self.decryptionPassword = nil;
self.signatures = nil;
self.relaunchPath = nil;
Expand Down Expand Up @@ -407,13 +410,21 @@ - (void)handleMessageWithIdentifier:(int32_t)identifier data:(NSData *)data
return;
}

NSString *extractionDirectory = [SPULocalCacheDirectory createUniqueDirectoryInDirectory:cacheInstallationPath];
if (extractionDirectory == nil) {
[self cleanupAndExitWithStatus:EXIT_FAILURE error:[NSError errorWithDomain:SUSparkleErrorDomain code:SPUInstallerError userInfo:@{ NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Error: Failed to create installation extraction directory in %@", cacheInstallationPath] }]];

return;
}

// Carry these properities separately rather than using the SUInstallationInputData object
// Some of our properties may slightly differ than our input and we don't want to make the mistake of using one of those
self.installationType = installationType;
self.relaunchPath = installationData.relaunchPath;
self.downloadName = installationData.downloadName;
self.signatures = installationData.signatures;
self.updateDirectoryPath = cacheInstallationPath;
self.extractionDirectory = extractionDirectory;
self.host = [[SUHost alloc] initWithBundle:hostBundle];

[self extractAndInstallUpdate];
Expand Down Expand Up @@ -463,7 +474,7 @@ - (void)startInstallation

dispatch_async(self.installerQueue, ^{
NSError *installerError = nil;
id <SUInstallerProtocol> installer = [SUInstaller installerForHost:self.host expectedInstallationType:self.installationType updateDirectory:self.updateDirectoryPath homeDirectory:self.homeDirectory userName:self.userName error:&installerError];
id <SUInstallerProtocol> installer = [SUInstaller installerForHost:self.host expectedInstallationType:self.installationType updateDirectory:self.extractionDirectory homeDirectory:self.homeDirectory userName:self.userName error:&installerError];

if (installer == nil) {
dispatch_async(dispatch_get_main_queue(), ^{
Expand Down
2 changes: 1 addition & 1 deletion Autoupdate/SUBinaryDeltaUnarchiver.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ NS_ASSUME_NONNULL_BEGIN

@interface SUBinaryDeltaUnarchiver : NSObject <SUUnarchiverProtocol>

- (instancetype)initWithArchivePath:(NSString *)archivePath updateHostBundlePath:(NSString *)updateHostBundlePath;
- (instancetype)initWithArchivePath:(NSString *)archivePath extractionDirectory:(NSString *)extractionDirectory updateHostBundlePath:(NSString *)updateHostBundlePath;

@end

Expand Down
7 changes: 5 additions & 2 deletions Autoupdate/SUBinaryDeltaUnarchiver.m
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ @interface SUBinaryDeltaUnarchiver ()

@property (nonatomic, copy, readonly) NSString *archivePath;
@property (nonatomic, copy, readonly) NSString *updateHostBundlePath;
@property (nonatomic, copy, readonly) NSString *extractionDirectory;

@end

@implementation SUBinaryDeltaUnarchiver

@synthesize archivePath = _archivePath;
@synthesize updateHostBundlePath = _updateHostBundlePath;
@synthesize extractionDirectory = _extractionDirectory;

+ (BOOL)canUnarchivePath:(NSString *)path
{
Expand Down Expand Up @@ -73,12 +75,13 @@ + (void)updateSpotlightImportersAtBundlePath:(NSString *)targetPath
}
}

- (instancetype)initWithArchivePath:(NSString *)archivePath updateHostBundlePath:(NSString *)updateHostBundlePath
- (instancetype)initWithArchivePath:(NSString *)archivePath extractionDirectory:(NSString *)extractionDirectory updateHostBundlePath:(NSString *)updateHostBundlePath
{
self = [super init];
if (self != nil) {
_archivePath = [archivePath copy];
_updateHostBundlePath = [updateHostBundlePath copy];
_extractionDirectory = [extractionDirectory copy];
}
return self;
}
Expand All @@ -96,7 +99,7 @@ - (void)unarchiveWithCompletionBlock:(void (^)(NSError * _Nullable))completionBl
- (void)extractDeltaWithNotifier:(SUUnarchiverNotifier *)notifier
{
NSString *sourcePath = self.updateHostBundlePath;
NSString *targetPath = [[self.archivePath stringByDeletingLastPathComponent] stringByAppendingPathComponent:[sourcePath lastPathComponent]];
NSString *targetPath = [self.extractionDirectory stringByAppendingPathComponent:[sourcePath lastPathComponent]];

NSError *applyDiffError = nil;
BOOL success = applyBinaryDelta(sourcePath, targetPath, self.archivePath, NO, ^(double progress){
Expand Down
2 changes: 1 addition & 1 deletion Autoupdate/SUDiskImageUnarchiver.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ NS_ASSUME_NONNULL_BEGIN

@interface SUDiskImageUnarchiver : NSObject <SUUnarchiverProtocol>

- (instancetype)initWithArchivePath:(NSString *)archivePath decryptionPassword:(nullable NSString *)decryptionPassword;
- (instancetype)initWithArchivePath:(NSString *)archivePath extractionDirectory:(NSString *)extractionDirectory decryptionPassword:(nullable NSString *)decryptionPassword;

@end

Expand Down
7 changes: 5 additions & 2 deletions Autoupdate/SUDiskImageUnarchiver.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ @interface SUDiskImageUnarchiver ()

@property (nonatomic, copy, readonly) NSString *archivePath;
@property (nullable, nonatomic, copy, readonly) NSString *decryptionPassword;
@property (nonatomic, copy, readonly) NSString *extractionDirectory;

@end

@implementation SUDiskImageUnarchiver

@synthesize archivePath = _archivePath;
@synthesize decryptionPassword = _decryptionPassword;
@synthesize extractionDirectory = _extractionDirectory;

+ (BOOL)canUnarchivePath:(NSString *)path
{
Expand All @@ -35,12 +37,13 @@ + (BOOL)mustValidateBeforeExtraction
return NO;
}

- (instancetype)initWithArchivePath:(NSString *)archivePath decryptionPassword:(nullable NSString *)decryptionPassword
- (instancetype)initWithArchivePath:(NSString *)archivePath extractionDirectory:(NSString *)extractionDirectory decryptionPassword:(nullable NSString *)decryptionPassword
{
self = [super init];
if (self != nil) {
_archivePath = [archivePath copy];
_decryptionPassword = [decryptionPassword copy];
_extractionDirectory = [extractionDirectory copy];
}
return self;
}
Expand Down Expand Up @@ -189,7 +192,7 @@ - (void)extractDMGWithNotifier:(SUUnarchiverNotifier *)notifier
for (NSString *item in contents)
{
NSString *fromPath = [mountPoint stringByAppendingPathComponent:item];
NSString *toPath = [[self.archivePath stringByDeletingLastPathComponent] stringByAppendingPathComponent:item];
NSString *toPath = [self.extractionDirectory stringByAppendingPathComponent:item];

itemsCopied += 1.0;
[notifier notifyProgress:0.5 + itemsCopied/(totalItems*2.0)];
Expand Down
2 changes: 1 addition & 1 deletion Autoupdate/SUFlatPackageUnarchiver.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ NS_ASSUME_NONNULL_BEGIN
// An unarchiver for flat packages that doesn't really do any unarchiving
@interface SUFlatPackageUnarchiver : NSObject <SUUnarchiverProtocol>

- (instancetype)initWithFlatPackagePath:(NSString *)flatPackagePath expectingInstallationType:(NSString *)installationType;
- (instancetype)initWithFlatPackagePath:(NSString *)flatPackagePath extractionDirectory:(NSString *)extractionDirectory expectingInstallationType:(NSString *)installationType;

@end

Expand Down
21 changes: 18 additions & 3 deletions Autoupdate/SUFlatPackageUnarchiver.m
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ @interface SUFlatPackageUnarchiver ()

@property (nonatomic, readonly) NSString *flatPackagePath;
@property (nonatomic, readonly) NSString *expectedInstallationType;
@property (nonatomic, readonly) NSString *extractionDirectory;

@end

@implementation SUFlatPackageUnarchiver

@synthesize flatPackagePath = _flatPackagePath;
@synthesize expectedInstallationType = _expectedInstallationType;
@synthesize extractionDirectory = _extractionDirectory;

+ (BOOL)canUnarchivePath:(NSString *)path
{
Expand All @@ -36,12 +38,13 @@ + (BOOL)mustValidateBeforeExtraction
return YES;
}

- (instancetype)initWithFlatPackagePath:(NSString *)flatPackagePath expectingInstallationType:(NSString *)installationType
- (instancetype)initWithFlatPackagePath:(NSString *)flatPackagePath extractionDirectory:(NSString *)extractionDirectory expectingInstallationType:(NSString *)installationType
{
self = [super init];
if (self != nil) {
_flatPackagePath = [flatPackagePath copy];
_expectedInstallationType = [installationType copy];
_extractionDirectory = [extractionDirectory copy];
}
return self;
}
Expand All @@ -57,8 +60,20 @@ - (void)unarchiveWithCompletionBlock:(void (^)(NSError * _Nullable))completionBl
} else if (![[NSFileManager defaultManager] fileExistsAtPath:self.flatPackagePath isDirectory:&isDirectory] || isDirectory) {
[notifier notifyFailureWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SUUnarchivingError userInfo:@{ NSLocalizedDescriptionKey:[NSString stringWithFormat:@"Flat package does not exist at %@", self.flatPackagePath]}]];
} else {
[notifier notifyProgress:1.0];
[notifier notifySuccess];
// Copying the flat package should be very fast, especially on APFS
NSError *copyError = nil;
if (![[NSFileManager defaultManager] copyItemAtPath:self.flatPackagePath toPath:[self.extractionDirectory stringByAppendingPathComponent:self.flatPackagePath.lastPathComponent] error:&copyError]) {
NSMutableDictionary *userInfoDictionary = [NSMutableDictionary dictionaryWithDictionary:@{ NSLocalizedDescriptionKey:[NSString stringWithFormat:@"Flat package (%@) cannot be copied to extraction directory (%@)", self.flatPackagePath, self.extractionDirectory]}];

if (copyError != nil) {
userInfoDictionary[NSUnderlyingErrorKey] = copyError;
}

[notifier notifyFailureWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SUUnarchivingError userInfo:userInfoDictionary]];
} else {
[notifier notifyProgress:1.0];
[notifier notifySuccess];
}
}
}

Expand Down
41 changes: 28 additions & 13 deletions Autoupdate/SUInstaller.m
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,6 @@

@implementation SUInstaller

+ (BOOL)isAliasFolderAtPath:(NSString *)path
{
NSNumber *aliasFlag = nil;
[[NSURL fileURLWithPath:path] getResourceValue:&aliasFlag forKey:NSURLIsAliasFileKey error:nil];
NSNumber *directoryFlag = nil;
[[NSURL fileURLWithPath:path] getResourceValue:&directoryFlag forKey:NSURLIsDirectoryKey error:nil];
return aliasFlag.boolValue && directoryFlag.boolValue;
}

+ (nullable NSString *)installSourcePathInUpdateFolder:(NSString *)inUpdateFolder forHost:(SUHost *)host isPackage:(BOOL *)isPackagePtr isGuided:(BOOL *)isGuidedPtr
{
NSParameterAssert(inUpdateFolder);
Expand All @@ -49,6 +40,34 @@ + (nullable NSString *)installSourcePathInUpdateFolder:(NSString *)inUpdateFolde

while ((currentFile = [dirEnum nextObject])) {
NSString *currentPath = [inUpdateFolder stringByAppendingPathComponent:currentFile];

// Ignore all symbolic links and aliases
{
NSURL *currentPathURL = [NSURL fileURLWithPath:currentPath];

NSNumber *symbolicLinkFlag = nil;
[currentPathURL getResourceValue:&symbolicLinkFlag forKey:NSURLIsSymbolicLinkKey error:NULL];
if (symbolicLinkFlag.boolValue) {
// NSDirectoryEnumerator won't recurse into symlinked directories
continue;
}

NSNumber *aliasFlag = nil;
[currentPathURL getResourceValue:&aliasFlag forKey:NSURLIsAliasFileKey error:NULL];

if (aliasFlag.boolValue) {
NSNumber *directoryFlag = nil;
[currentPathURL getResourceValue:&directoryFlag forKey:NSURLIsDirectoryKey error:NULL];

// Some DMGs have symlinks into /Applications! That's no good!
if (directoryFlag.boolValue) {
[dirEnum skipDescendents];
}

continue;
}
}

NSString *currentFilename = [currentFile lastPathComponent];
NSString *currentExtension = [currentFile pathExtension];
NSString *currentFilenameNoExtension = [currentFilename stringByDeletingPathExtension];
Expand Down Expand Up @@ -78,10 +97,6 @@ + (nullable NSString *)installSourcePathInUpdateFolder:(NSString *)inUpdateFolde
break;
}
}

// Some DMGs have symlinks into /Applications! That's no good!
if ([self isAliasFolderAtPath:currentPath])
[dirEnum skipDescendents];
}

// We don't have a valid path. Try to use the fallback package.
Expand Down
2 changes: 1 addition & 1 deletion Autoupdate/SUPipedUnarchiver.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ NS_ASSUME_NONNULL_BEGIN

@interface SUPipedUnarchiver : NSObject <SUUnarchiverProtocol>

- (instancetype)initWithArchivePath:(NSString *)archivePath;
- (instancetype)initWithArchivePath:(NSString *)archivePath extractionDirectory:(NSString *)extractionDirectory;

@end

Expand Down
7 changes: 5 additions & 2 deletions Autoupdate/SUPipedUnarchiver.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@
@interface SUPipedUnarchiver ()

@property (nonatomic, copy, readonly) NSString *archivePath;
@property (nonatomic, copy, readonly) NSString *extractionDirectory;

@end

@implementation SUPipedUnarchiver

@synthesize archivePath = _archivePath;
@synthesize extractionDirectory = _extractionDirectory;

+ (nullable NSArray <NSString *> *)commandAndArgumentsConformingToTypeOfPath:(NSString *)path
{
Expand Down Expand Up @@ -63,11 +65,12 @@ + (BOOL)mustValidateBeforeExtraction
return NO;
}

- (instancetype)initWithArchivePath:(NSString *)archivePath
- (instancetype)initWithArchivePath:(NSString *)archivePath extractionDirectory:(NSString *)extractionDirectory
{
self = [super init];
if (self != nil) {
_archivePath = [archivePath copy];
_extractionDirectory = [extractionDirectory copy];
}
return self;
}
Expand All @@ -93,7 +96,7 @@ - (void)extractArchivePipingDataToCommand:(NSString *)command arguments:(NSArray
{
// *** GETS CALLED ON NON-MAIN THREAD!!!
@autoreleasepool {
NSString *destination = [self.archivePath stringByDeletingLastPathComponent];
NSString *destination = self.extractionDirectory;

SULog(SULogLevelDefault, @"Extracting using '%@' '%@' < '%@' '%@'", command, [args componentsJoinedByString:@"' '"], self.archivePath, destination);

Expand Down
2 changes: 1 addition & 1 deletion Autoupdate/SUUnarchiver.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ NS_ASSUME_NONNULL_BEGIN

@interface SUUnarchiver : NSObject

+ (nullable id <SUUnarchiverProtocol>)unarchiverForPath:(NSString *)path updatingHostBundlePath:(nullable NSString *)hostPath decryptionPassword:(nullable NSString *)decryptionPassword expectingInstallationType:(NSString *)installationType;
+ (nullable id <SUUnarchiverProtocol>)unarchiverForPath:(NSString *)path extractionDirectory:(NSString *)extractionDirectory updatingHostBundlePath:(nullable NSString *)hostPath decryptionPassword:(nullable NSString *)decryptionPassword expectingInstallationType:(NSString *)installationType;

@end

Expand Down
10 changes: 5 additions & 5 deletions Autoupdate/SUUnarchiver.m
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,21 @@

@implementation SUUnarchiver

+ (nullable id <SUUnarchiverProtocol>)unarchiverForPath:(NSString *)path updatingHostBundlePath:(nullable NSString *)hostPath decryptionPassword:(nullable NSString *)decryptionPassword expectingInstallationType:(NSString *)installationType
+ (nullable id <SUUnarchiverProtocol>)unarchiverForPath:(NSString *)path extractionDirectory:(NSString *)extractionDirectory updatingHostBundlePath:(nullable NSString *)hostPath decryptionPassword:(nullable NSString *)decryptionPassword expectingInstallationType:(NSString *)installationType
{
if ([SUPipedUnarchiver canUnarchivePath:path]) {
return [[SUPipedUnarchiver alloc] initWithArchivePath:path];
return [[SUPipedUnarchiver alloc] initWithArchivePath:path extractionDirectory:extractionDirectory];

} else if ([SUDiskImageUnarchiver canUnarchivePath:path]) {
return [[SUDiskImageUnarchiver alloc] initWithArchivePath:path decryptionPassword:decryptionPassword];
return [[SUDiskImageUnarchiver alloc] initWithArchivePath:path extractionDirectory:extractionDirectory decryptionPassword:decryptionPassword];

} else if ([SUBinaryDeltaUnarchiver canUnarchivePath:path]) {
assert(hostPath != nil);
NSString *nonNullHostPath = hostPath;
return [[SUBinaryDeltaUnarchiver alloc] initWithArchivePath:path updateHostBundlePath:nonNullHostPath];
return [[SUBinaryDeltaUnarchiver alloc] initWithArchivePath:path extractionDirectory:extractionDirectory updateHostBundlePath:nonNullHostPath];
} else if ([SUFlatPackageUnarchiver canUnarchivePath:path]) {
// Flat packages are only supported for guided packaage installs
return [[SUFlatPackageUnarchiver alloc] initWithFlatPackagePath:path expectingInstallationType:installationType];
return [[SUFlatPackageUnarchiver alloc] initWithFlatPackagePath:path extractionDirectory:extractionDirectory expectingInstallationType:installationType];
}
return nil;
}
Expand Down
Loading

0 comments on commit 80c8c1f

Please sign in to comment.