619 lines
24 KiB
Objective-C
619 lines
24 KiB
Objective-C
//
|
|
// SUPlainInstallerInternals.m
|
|
// Sparkle
|
|
//
|
|
// Created by Andy Matuschak on 3/9/06.
|
|
// Copyright 2006 Andy Matuschak. All rights reserved.
|
|
//
|
|
|
|
#import "SUUpdater.h"
|
|
|
|
#import "SUAppcast.h"
|
|
#import "SUAppcastItem.h"
|
|
#import "SUVersionComparisonProtocol.h"
|
|
#import "SUPlainInstallerInternals.h"
|
|
#import "SUConstants.h"
|
|
#import "SULog.h"
|
|
|
|
#import <CoreServices/CoreServices.h>
|
|
#import <Security/Security.h>
|
|
#import <sys/stat.h>
|
|
#import <sys/wait.h>
|
|
#import <dirent.h>
|
|
#import <unistd.h>
|
|
#import <sys/param.h>
|
|
|
|
|
|
@interface SUPlainInstaller (MMExtendedAttributes)
|
|
// Removes the directory tree rooted at |root| from the file quarantine.
|
|
// The quarantine was introduced on Mac OS X 10.5 and is described at:
|
|
//
|
|
// http://developer.apple.com/releasenotes/Carbon/RN-LaunchServices/index.html
|
|
//#apple_ref/doc/uid/TP40001369-DontLinkElementID_2
|
|
//
|
|
// If |root| is not a directory, then it alone is removed from the quarantine.
|
|
// Symbolic links, including |root| if it is a symbolic link, will not be
|
|
// traversed.
|
|
//
|
|
// Ordinarily, the quarantine is managed by calling LSSetItemAttribute
|
|
// to set the kLSItemQuarantineProperties attribute to a dictionary specifying
|
|
// the quarantine properties to be applied. However, it does not appear to be
|
|
// possible to remove an item from the quarantine directly through any public
|
|
// Launch Services calls. Instead, this method takes advantage of the fact
|
|
// that the quarantine is implemented in part by setting an extended attribute,
|
|
// "com.apple.quarantine", on affected files. Removing this attribute is
|
|
// sufficient to remove files from the quarantine.
|
|
+ (void)releaseFromQuarantine:(NSString*)root;
|
|
@end
|
|
|
|
// Authorization code based on generous contribution from Allan Odgaard. Thanks, Allan!
|
|
|
|
static BOOL AuthorizationExecuteWithPrivilegesAndWait(AuthorizationRef authorization, const char* executablePath, AuthorizationFlags options, const char* const* arguments)
|
|
{
|
|
// *** MUST BE SAFE TO CALL ON NON-MAIN THREAD!
|
|
|
|
sig_t oldSigChildHandler = signal(SIGCHLD, SIG_DFL);
|
|
BOOL returnValue = YES;
|
|
|
|
if (AuthorizationExecuteWithPrivileges(authorization, executablePath, options, (char* const*)arguments, NULL) == errAuthorizationSuccess)
|
|
{
|
|
int status;
|
|
pid_t pid = wait(&status);
|
|
if (pid == -1 || !WIFEXITED(status) || WEXITSTATUS(status) != 0)
|
|
returnValue = NO;
|
|
}
|
|
else
|
|
returnValue = NO;
|
|
|
|
signal(SIGCHLD, oldSigChildHandler);
|
|
return returnValue;
|
|
}
|
|
|
|
@implementation SUPlainInstaller (Internals)
|
|
|
|
+ (NSString *)temporaryNameForPath:(NSString *)path
|
|
{
|
|
// Let's try to read the version number so the filename will be more meaningful.
|
|
NSString *postFix;
|
|
NSString *version;
|
|
if ((version = [[NSBundle bundleWithPath:path] objectForInfoDictionaryKey:@"CFBundleVersion"]) && ![version isEqualToString:@""])
|
|
{
|
|
// We'll clean it up a little for safety.
|
|
// The cast is necessary because of a bug in the headers in pre-10.5 SDKs
|
|
NSMutableCharacterSet *validCharacters = (id)[NSMutableCharacterSet alphanumericCharacterSet];
|
|
[validCharacters formUnionWithCharacterSet:[NSCharacterSet characterSetWithCharactersInString:@".-()"]];
|
|
postFix = [version stringByTrimmingCharactersInSet:[validCharacters invertedSet]];
|
|
}
|
|
else
|
|
postFix = @"old";
|
|
NSString *prefix = [[path stringByDeletingPathExtension] stringByAppendingFormat:@" (%@)", postFix];
|
|
NSString *tempDir = [prefix stringByAppendingPathExtension:[path pathExtension]];
|
|
// Now let's make sure we get a unique path.
|
|
unsigned int cnt=2;
|
|
while ([[NSFileManager defaultManager] fileExistsAtPath:tempDir] && cnt <= 999)
|
|
tempDir = [NSString stringWithFormat:@"%@ %u.%@", prefix, cnt++, [path pathExtension]];
|
|
return [tempDir lastPathComponent];
|
|
}
|
|
|
|
+ (NSString *)_temporaryCopyNameForPath:(NSString *)path didFindTrash: (BOOL*)outDidFindTrash
|
|
{
|
|
// *** MUST BE SAFE TO CALL ON NON-MAIN THREAD!
|
|
NSString *tempDir = nil;
|
|
|
|
UInt8 trashPath[MAXPATHLEN +1] = { 0 };
|
|
FSRef trashRef, pathRef;
|
|
FSVolumeRefNum vSrcRefNum = kFSInvalidVolumeRefNum;
|
|
FSCatalogInfo catInfo;
|
|
memset( &catInfo, 0, sizeof(catInfo) );
|
|
OSStatus err = FSPathMakeRef( (UInt8*) [path fileSystemRepresentation], &pathRef, NULL );
|
|
if( err == noErr )
|
|
{
|
|
err = FSGetCatalogInfo( &pathRef, kFSCatInfoVolume, &catInfo, NULL, NULL, NULL );
|
|
vSrcRefNum = catInfo.volume;
|
|
}
|
|
if( err == noErr )
|
|
err = FSFindFolder( vSrcRefNum, kTrashFolderType, kCreateFolder, &trashRef );
|
|
if( err == noErr )
|
|
err = FSGetCatalogInfo( &trashRef, kFSCatInfoVolume, &catInfo, NULL, NULL, NULL );
|
|
if( err == noErr && vSrcRefNum != catInfo.volume )
|
|
err = nsvErr; // Couldn't find a trash folder on same volume as given path. Docs say this may happen in the future.
|
|
if( err == noErr )
|
|
err = FSRefMakePath( &trashRef, trashPath, MAXPATHLEN );
|
|
if( err == noErr )
|
|
tempDir = [[NSFileManager defaultManager] stringWithFileSystemRepresentation: (char*) trashPath length: strlen((char*) trashPath)];
|
|
if( outDidFindTrash )
|
|
*outDidFindTrash = (tempDir != nil);
|
|
if( !tempDir )
|
|
tempDir = [path stringByDeletingLastPathComponent];
|
|
|
|
// Let's try to read the version number so the filename will be more meaningful.
|
|
#if TRY_TO_APPEND_VERSION_NUMBER
|
|
NSString *postFix = nil;
|
|
NSString *version = nil;
|
|
if ((version = [[NSBundle bundleWithPath: path] objectForInfoDictionaryKey:@"CFBundleVersion"]) && ![version isEqualToString:@""])
|
|
{
|
|
// We'll clean it up a little for safety.
|
|
// The cast is necessary because of a bug in the headers in pre-10.5 SDKs
|
|
NSMutableCharacterSet *validCharacters = (id)[NSMutableCharacterSet alphanumericCharacterSet];
|
|
[validCharacters formUnionWithCharacterSet:[NSCharacterSet characterSetWithCharactersInString:@".-()"]];
|
|
postFix = [version stringByTrimmingCharactersInSet:[validCharacters invertedSet]];
|
|
}
|
|
else
|
|
postFix = @"old";
|
|
NSString *prefix = [NSString stringWithFormat: @"%@ (%@)", [[path lastPathComponent] stringByDeletingPathExtension], postFix];
|
|
#else
|
|
NSString *prefix = [[path lastPathComponent] stringByDeletingPathExtension];
|
|
#endif
|
|
NSString *tempName = [prefix stringByAppendingPathExtension: [path pathExtension]];
|
|
tempDir = [tempDir stringByAppendingPathComponent: tempName];
|
|
|
|
// Now let's make sure we get a unique path.
|
|
int cnt=2;
|
|
while ([[NSFileManager defaultManager] fileExistsAtPath:tempDir] && cnt <= 9999)
|
|
tempDir = [[tempDir stringByDeletingLastPathComponent] stringByAppendingPathComponent: [NSString stringWithFormat:@"%@ %d.%@", prefix, cnt++, [path pathExtension]]];
|
|
|
|
return tempDir;
|
|
}
|
|
|
|
+ (BOOL)_copyPathWithForcedAuthentication:(NSString *)src toPath:(NSString *)dst temporaryPath:(NSString *)tmp error:(NSError **)error
|
|
{
|
|
// *** MUST BE SAFE TO CALL ON NON-MAIN THREAD!
|
|
|
|
const char* srcPath = [src fileSystemRepresentation];
|
|
const char* tmpPath = [tmp fileSystemRepresentation];
|
|
const char* dstPath = [dst fileSystemRepresentation];
|
|
|
|
struct stat dstSB;
|
|
if( stat(dstPath, &dstSB) != 0 ) // Doesn't exist yet, try containing folder.
|
|
{
|
|
const char* dstDirPath = [[dst stringByDeletingLastPathComponent] fileSystemRepresentation];
|
|
if( stat(dstDirPath, &dstSB) != 0 )
|
|
{
|
|
NSString *errorMessage = [NSString stringWithFormat:@"Stat on %@ during authenticated file copy failed.", dst];
|
|
if (error != NULL)
|
|
*error = [NSError errorWithDomain:SUSparkleErrorDomain code:SUFileCopyFailure userInfo:[NSDictionary dictionaryWithObject:errorMessage forKey:NSLocalizedDescriptionKey]];
|
|
return NO;
|
|
}
|
|
}
|
|
|
|
AuthorizationRef auth = NULL;
|
|
OSStatus authStat = errAuthorizationDenied;
|
|
while (authStat == errAuthorizationDenied) {
|
|
authStat = AuthorizationCreate(NULL,
|
|
kAuthorizationEmptyEnvironment,
|
|
kAuthorizationFlagDefaults,
|
|
&auth);
|
|
}
|
|
|
|
BOOL res = NO;
|
|
if (authStat == errAuthorizationSuccess) {
|
|
res = YES;
|
|
|
|
char uidgid[42];
|
|
snprintf(uidgid, sizeof(uidgid), "%u:%u",
|
|
dstSB.st_uid, dstSB.st_gid);
|
|
|
|
// If the currently-running application is trusted, the new
|
|
// version should be trusted as well. Remove it from the
|
|
// quarantine to avoid a delay at launch, and to avoid
|
|
// presenting the user with a confusing trust dialog.
|
|
//
|
|
// This needs to be done before "chown" changes ownership,
|
|
// because the ownership change will fail if the file is quarantined.
|
|
if (res)
|
|
{
|
|
SULog(@"releaseFromQuarantine");
|
|
[self performSelectorOnMainThread:@selector(releaseFromQuarantine:) withObject:src waitUntilDone:YES];
|
|
}
|
|
|
|
if( res ) // Set permissions while it's still in source, so we have it with working and correct perms when it arrives at destination.
|
|
{
|
|
const char* coParams[] = { "-R", uidgid, srcPath, NULL };
|
|
res = AuthorizationExecuteWithPrivilegesAndWait( auth, "/usr/sbin/chown", kAuthorizationFlagDefaults, coParams );
|
|
if( !res )
|
|
SULog( @"chown -R %s %s failed.", uidgid, srcPath );
|
|
}
|
|
|
|
BOOL haveDst = [[NSFileManager defaultManager] fileExistsAtPath: dst];
|
|
if( res && haveDst ) // If there's something at our tmp path (previous failed update or whatever) delete that first.
|
|
{
|
|
const char* rmParams[] = { "-rf", tmpPath, NULL };
|
|
res = AuthorizationExecuteWithPrivilegesAndWait( auth, "/bin/rm", kAuthorizationFlagDefaults, rmParams );
|
|
if( !res )
|
|
SULog( @"rm failed" );
|
|
}
|
|
|
|
if( res && haveDst ) // Move old exe to tmp path.
|
|
{
|
|
const char* mvParams[] = { "-f", dstPath, tmpPath, NULL };
|
|
res = AuthorizationExecuteWithPrivilegesAndWait( auth, "/bin/mv", kAuthorizationFlagDefaults, mvParams );
|
|
if( !res )
|
|
SULog( @"mv 1 failed" );
|
|
}
|
|
|
|
if( res ) // Move new exe to old exe's path.
|
|
{
|
|
const char* mvParams2[] = { "-f", srcPath, dstPath, NULL };
|
|
res = AuthorizationExecuteWithPrivilegesAndWait( auth, "/bin/mv", kAuthorizationFlagDefaults, mvParams2 );
|
|
if( !res )
|
|
SULog( @"mv 2 failed" );
|
|
}
|
|
|
|
// if( res && haveDst /*&& !foundTrash*/ ) // If we managed to put the old exe in the trash, leave it there for the user to delete or recover.
|
|
// { // ... Otherwise we better delete it, wouldn't want dozens of old versions lying around next to the new one.
|
|
// const char* rmParams2[] = { "-rf", tmpPath, NULL };
|
|
// res = AuthorizationExecuteWithPrivilegesAndWait( auth, "/bin/rm", kAuthorizationFlagDefaults, rmParams2 );
|
|
// }
|
|
|
|
AuthorizationFree(auth, 0);
|
|
|
|
// If the currently-running application is trusted, the new
|
|
// version should be trusted as well. Remove it from the
|
|
// quarantine to avoid a delay at launch, and to avoid
|
|
// presenting the user with a confusing trust dialog.
|
|
//
|
|
// This needs to be done after the application is moved to its
|
|
// new home with "mv" in case it's moved across filesystems: if
|
|
// that happens, "mv" actually performs a copy and may result
|
|
// in the application being quarantined.
|
|
if (res)
|
|
{
|
|
SULog(@"releaseFromQuarantine after installing");
|
|
[self performSelectorOnMainThread:@selector(releaseFromQuarantine:) withObject:dst waitUntilDone:YES];
|
|
}
|
|
|
|
if (!res)
|
|
{
|
|
// Something went wrong somewhere along the way, but we're not sure exactly where.
|
|
NSString *errorMessage = [NSString stringWithFormat:@"Authenticated file copy from %@ to %@ failed.", src, dst];
|
|
if (error != nil)
|
|
*error = [NSError errorWithDomain:SUSparkleErrorDomain code:SUAuthenticationFailure userInfo:[NSDictionary dictionaryWithObject:errorMessage forKey:NSLocalizedDescriptionKey]];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (error != nil)
|
|
*error = [NSError errorWithDomain:SUSparkleErrorDomain code:SUAuthenticationFailure userInfo:[NSDictionary dictionaryWithObject:@"Couldn't get permission to authenticate." forKey:NSLocalizedDescriptionKey]];
|
|
}
|
|
return res;
|
|
}
|
|
|
|
+ (BOOL)_movePathWithForcedAuthentication:(NSString *)src toPath:(NSString *)dst error:(NSError **)error
|
|
{
|
|
// *** MUST BE SAFE TO CALL ON NON-MAIN THREAD!
|
|
|
|
const char* srcPath = [src fileSystemRepresentation];
|
|
const char* dstPath = [dst fileSystemRepresentation];
|
|
const char* dstContainerPath = [[dst stringByDeletingLastPathComponent] fileSystemRepresentation];
|
|
|
|
struct stat dstSB;
|
|
stat(dstContainerPath, &dstSB);
|
|
|
|
AuthorizationRef auth = NULL;
|
|
OSStatus authStat = errAuthorizationDenied;
|
|
while( authStat == errAuthorizationDenied )
|
|
{
|
|
authStat = AuthorizationCreate(NULL,
|
|
kAuthorizationEmptyEnvironment,
|
|
kAuthorizationFlagDefaults,
|
|
&auth);
|
|
}
|
|
|
|
BOOL res = NO;
|
|
if (authStat == errAuthorizationSuccess)
|
|
{
|
|
res = YES;
|
|
|
|
char uidgid[42];
|
|
snprintf(uidgid, sizeof(uidgid), "%d:%d",
|
|
dstSB.st_uid, dstSB.st_gid);
|
|
|
|
if( res ) // Set permissions while it's still in source, so we have it with working and correct perms when it arrives at destination.
|
|
{
|
|
const char* coParams[] = { "-R", uidgid, srcPath, NULL };
|
|
res = AuthorizationExecuteWithPrivilegesAndWait( auth, "/usr/sbin/chown", kAuthorizationFlagDefaults, coParams );
|
|
if( !res )
|
|
SULog(@"Can't set permissions");
|
|
}
|
|
|
|
BOOL haveDst = [[NSFileManager defaultManager] fileExistsAtPath: dst];
|
|
if( res && haveDst ) // If there's something at our tmp path (previous failed update or whatever) delete that first.
|
|
{
|
|
const char* rmParams[] = { "-rf", dstPath, NULL };
|
|
res = AuthorizationExecuteWithPrivilegesAndWait( auth, "/bin/rm", kAuthorizationFlagDefaults, rmParams );
|
|
if( !res )
|
|
SULog(@"Can't remove destination file");
|
|
}
|
|
|
|
if( res ) // Move!.
|
|
{
|
|
const char* mvParams[] = { "-f", srcPath, dstPath, NULL };
|
|
res = AuthorizationExecuteWithPrivilegesAndWait( auth, "/bin/mv", kAuthorizationFlagDefaults, mvParams );
|
|
if( !res )
|
|
SULog(@"Can't move source file");
|
|
}
|
|
|
|
AuthorizationFree(auth, 0);
|
|
|
|
if (!res)
|
|
{
|
|
// Something went wrong somewhere along the way, but we're not sure exactly where.
|
|
NSString *errorMessage = [NSString stringWithFormat:@"Authenticated file move from %@ to %@ failed.", src, dst];
|
|
if (error != NULL)
|
|
*error = [NSError errorWithDomain:SUSparkleErrorDomain code:SUAuthenticationFailure userInfo:[NSDictionary dictionaryWithObject:errorMessage forKey:NSLocalizedDescriptionKey]];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (error != NULL)
|
|
*error = [NSError errorWithDomain:SUSparkleErrorDomain code:SUAuthenticationFailure userInfo:[NSDictionary dictionaryWithObject:@"Couldn't get permission to authenticate." forKey:NSLocalizedDescriptionKey]];
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
+ (BOOL)_removeFileAtPathWithForcedAuthentication:(NSString *)src error:(NSError **)error
|
|
{
|
|
// *** MUST BE SAFE TO CALL ON NON-MAIN THREAD!
|
|
|
|
const char* srcPath = [src fileSystemRepresentation];
|
|
|
|
AuthorizationRef auth = NULL;
|
|
OSStatus authStat = errAuthorizationDenied;
|
|
while( authStat == errAuthorizationDenied )
|
|
{
|
|
authStat = AuthorizationCreate(NULL,
|
|
kAuthorizationEmptyEnvironment,
|
|
kAuthorizationFlagDefaults,
|
|
&auth);
|
|
}
|
|
|
|
BOOL res = NO;
|
|
if (authStat == errAuthorizationSuccess)
|
|
{
|
|
res = YES;
|
|
|
|
if( res ) // If there's something at our tmp path (previous failed update or whatever) delete that first.
|
|
{
|
|
const char* rmParams[] = { "-rf", srcPath, NULL };
|
|
res = AuthorizationExecuteWithPrivilegesAndWait( auth, "/bin/rm", kAuthorizationFlagDefaults, rmParams );
|
|
if( !res )
|
|
SULog(@"Can't remove destination file");
|
|
}
|
|
|
|
AuthorizationFree(auth, 0);
|
|
|
|
if (!res)
|
|
{
|
|
// Something went wrong somewhere along the way, but we're not sure exactly where.
|
|
NSString *errorMessage = [NSString stringWithFormat:@"Authenticated file remove from %@ failed.", src];
|
|
if (error != NULL)
|
|
*error = [NSError errorWithDomain:SUSparkleErrorDomain code:SUAuthenticationFailure userInfo:[NSDictionary dictionaryWithObject:errorMessage forKey:NSLocalizedDescriptionKey]];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (error != NULL)
|
|
*error = [NSError errorWithDomain:SUSparkleErrorDomain code:SUAuthenticationFailure userInfo:[NSDictionary dictionaryWithObject:@"Couldn't get permission to authenticate." forKey:NSLocalizedDescriptionKey]];
|
|
}
|
|
return res;
|
|
}
|
|
|
|
+ (BOOL)_removeFileAtPath:(NSString *)path error: (NSError**)error
|
|
{
|
|
BOOL success = YES;
|
|
#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
|
|
if( ![[NSFileManager defaultManager] removeFileAtPath: path handler: nil] )
|
|
#else
|
|
if( ![[NSFileManager defaultManager] removeItemAtPath: path error: NULL] )
|
|
#endif
|
|
{
|
|
success = [self _removeFileAtPathWithForcedAuthentication: path error: error];
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
+ (void)_movePathToTrash:(NSString *)path
|
|
{
|
|
//SULog(@"Moving %@ to the trash.", path);
|
|
NSInteger tag = 0;
|
|
if (![[NSWorkspace sharedWorkspace] performFileOperation:NSWorkspaceRecycleOperation source:[path stringByDeletingLastPathComponent] destination:@"" files:[NSArray arrayWithObject:[path lastPathComponent]] tag:&tag])
|
|
{
|
|
BOOL didFindTrash = NO;
|
|
NSString* trashPath = [self _temporaryCopyNameForPath: path didFindTrash: &didFindTrash];
|
|
if( didFindTrash )
|
|
{
|
|
NSError *err = nil;
|
|
if( ![self _movePathWithForcedAuthentication: path toPath: trashPath error: &err] )
|
|
SULog(@"Sparkle error: couldn't move %@ to the trash (%@). %@", path, trashPath, err);
|
|
}
|
|
else
|
|
SULog(@"Sparkle error: couldn't move %@ to the trash. This is often a sign of a permissions error.", path);
|
|
}
|
|
else
|
|
;//SULog(@"Moved %@ to the trash.", path);
|
|
}
|
|
|
|
+ (BOOL)copyPathWithAuthentication:(NSString *)src overPath:(NSString *)dst temporaryName:(NSString *)tmp error:(NSError **)error
|
|
{
|
|
FSRef srcRef, dstRef, dstDirRef, movedRef, tmpDirRef;
|
|
OSStatus err;
|
|
BOOL hadFileAtDest = NO, didFindTrash = NO;
|
|
NSString *tmpPath = [self _temporaryCopyNameForPath: dst didFindTrash: &didFindTrash];
|
|
|
|
// Make FSRef for destination:
|
|
err = FSPathMakeRefWithOptions((UInt8 *)[dst fileSystemRepresentation], kFSPathMakeRefDoNotFollowLeafSymlink, &dstRef, NULL);
|
|
hadFileAtDest = (err == noErr); // There is a file at the destination, move it aside. If we normalized the name, we might not get here, so don't error.
|
|
if( hadFileAtDest )
|
|
{
|
|
if (0 != access([dst fileSystemRepresentation], W_OK) || 0 != access([[dst stringByDeletingLastPathComponent] fileSystemRepresentation], W_OK))
|
|
{
|
|
return [self _copyPathWithForcedAuthentication:src toPath:dst temporaryPath:tmpPath error:error];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (0 != access([[dst stringByDeletingLastPathComponent] fileSystemRepresentation], W_OK)
|
|
|| 0 != access([[[dst stringByDeletingLastPathComponent] stringByDeletingLastPathComponent] fileSystemRepresentation], W_OK))
|
|
{
|
|
return [self _copyPathWithForcedAuthentication:src toPath:dst temporaryPath:tmpPath error:error];
|
|
}
|
|
}
|
|
|
|
if( hadFileAtDest )
|
|
{
|
|
err = FSPathMakeRef((UInt8 *)[[tmpPath stringByDeletingLastPathComponent] fileSystemRepresentation], &tmpDirRef, NULL);
|
|
if (err != noErr)
|
|
err = FSPathMakeRef((UInt8 *)[[dst stringByDeletingLastPathComponent] fileSystemRepresentation], &tmpDirRef, NULL);
|
|
}
|
|
|
|
err = FSPathMakeRef((UInt8 *)[[dst stringByDeletingLastPathComponent] fileSystemRepresentation], &dstDirRef, NULL);
|
|
|
|
if (err == noErr && hadFileAtDest)
|
|
{
|
|
if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_5)
|
|
{
|
|
NSFileManager *manager = [[[NSFileManager alloc] init] autorelease];
|
|
BOOL success = [manager moveItemAtPath:dst toPath:tmpPath error:error];
|
|
if (!success && hadFileAtDest)
|
|
{
|
|
if (error != NULL)
|
|
*error = [NSError errorWithDomain:SUSparkleErrorDomain code:SUFileCopyFailure userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Couldn't move %@ to %@.", dst, tmpPath] forKey:NSLocalizedDescriptionKey]];
|
|
return NO;
|
|
}
|
|
|
|
} else {
|
|
err = FSMoveObjectSync(&dstRef, &tmpDirRef, (CFStringRef)[tmpPath lastPathComponent], &movedRef, 0);
|
|
if (err != noErr && hadFileAtDest)
|
|
{
|
|
if (error != NULL)
|
|
*error = [NSError errorWithDomain:SUSparkleErrorDomain code:SUFileCopyFailure userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Couldn't move %@ to %@.", dst, tmpPath] forKey:NSLocalizedDescriptionKey]];
|
|
return NO;
|
|
}
|
|
}
|
|
}
|
|
|
|
err = FSPathMakeRef((UInt8 *)[src fileSystemRepresentation], &srcRef, NULL);
|
|
if (err == noErr)
|
|
{
|
|
if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_5)
|
|
{
|
|
NSFileManager *manager = [[[NSFileManager alloc] init] autorelease];
|
|
BOOL success = [manager copyItemAtPath:src toPath:dst error:error];
|
|
if (!success)
|
|
{
|
|
// We better move the old version back to its old location
|
|
if( hadFileAtDest )
|
|
success = [manager moveItemAtPath:tmpPath toPath:dst error:error];
|
|
if (error != NULL)
|
|
*error = [NSError errorWithDomain:SUSparkleErrorDomain code:SUFileCopyFailure userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Couldn't move %@ to %@.", dst, tmpPath] forKey:NSLocalizedDescriptionKey]];
|
|
return NO;
|
|
|
|
}
|
|
} else {
|
|
err = FSCopyObjectSync(&srcRef, &dstDirRef, (CFStringRef)[dst lastPathComponent], NULL, 0);
|
|
if (err != noErr)
|
|
{
|
|
// We better move the old version back to its old location
|
|
if( hadFileAtDest )
|
|
FSMoveObjectSync(&movedRef, &dstDirRef, (CFStringRef)[dst lastPathComponent], &movedRef, 0);
|
|
if (error != NULL)
|
|
*error = [NSError errorWithDomain:SUSparkleErrorDomain code:SUFileCopyFailure userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Couldn't copy %@ to %@.", src, dst] forKey:NSLocalizedDescriptionKey]];
|
|
return NO;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the currently-running application is trusted, the new
|
|
// version should be trusted as well. Remove it from the
|
|
// quarantine to avoid a delay at launch, and to avoid
|
|
// presenting the user with a confusing trust dialog.
|
|
//
|
|
// This needs to be done after the application is moved to its
|
|
// new home in case it's moved across filesystems: if that
|
|
// happens, the move is actually a copy, and it may result
|
|
// in the application being quarantined.
|
|
[self performSelectorOnMainThread:@selector(releaseFromQuarantine:) withObject:dst waitUntilDone:YES];
|
|
|
|
return YES;
|
|
}
|
|
|
|
@end
|
|
|
|
#import <dlfcn.h>
|
|
#import <errno.h>
|
|
#import <sys/xattr.h>
|
|
|
|
@implementation SUPlainInstaller (MMExtendedAttributes)
|
|
|
|
+ (int)removeXAttr:(const char*)name
|
|
fromFile:(NSString*)file
|
|
options:(int)options
|
|
{
|
|
// *** MUST BE SAFE TO CALL ON NON-MAIN THREAD!
|
|
|
|
typedef int (*removexattr_type)(const char*, const char*, int);
|
|
// Reference removexattr directly, it's in the SDK.
|
|
static removexattr_type removexattr_func = removexattr;
|
|
|
|
// Make sure that the symbol is present. This checks the deployment
|
|
// target instead of the SDK so that it's able to catch dlsym failures
|
|
// as well as the null symbol that would result from building with the
|
|
// 10.4 SDK and a lower deployment target, and running on 10.3.
|
|
if (!removexattr_func) {
|
|
errno = ENOSYS;
|
|
return -1;
|
|
}
|
|
|
|
const char* path = NULL;
|
|
@try {
|
|
path = [file fileSystemRepresentation];
|
|
}
|
|
@catch (id exception) {
|
|
// -[NSString fileSystemRepresentation] throws an exception if it's
|
|
// unable to convert the string to something suitable. Map that to
|
|
// EDOM, "argument out of domain", which sort of conveys that there
|
|
// was a conversion failure.
|
|
errno = EDOM;
|
|
return -1;
|
|
}
|
|
|
|
return removexattr_func(path, name, options);
|
|
}
|
|
|
|
+ (void)releaseFromQuarantine:(NSString*)root
|
|
{
|
|
// *** MUST BE SAFE TO CALL ON NON-MAIN THREAD!
|
|
|
|
const char* quarantineAttribute = "com.apple.quarantine";
|
|
const int removeXAttrOptions = XATTR_NOFOLLOW;
|
|
|
|
[self removeXAttr:quarantineAttribute
|
|
fromFile:root
|
|
options:removeXAttrOptions];
|
|
|
|
// Only recurse if it's actually a directory. Don't recurse into a
|
|
// root-level symbolic link.
|
|
#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
|
|
NSDictionary* rootAttributes = [[NSFileManager defaultManager] fileAttributesAtPath:root traverseLink:NO];
|
|
#else
|
|
NSDictionary* rootAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:root error:nil];
|
|
#endif
|
|
NSString* rootType = [rootAttributes objectForKey:NSFileType];
|
|
|
|
if (rootType == NSFileTypeDirectory) {
|
|
// The NSDirectoryEnumerator will avoid recursing into any contained
|
|
// symbolic links, so no further type checks are needed.
|
|
NSDirectoryEnumerator* directoryEnumerator = [[NSFileManager defaultManager] enumeratorAtPath:root];
|
|
NSString* file = nil;
|
|
while ((file = [directoryEnumerator nextObject])) {
|
|
[self removeXAttr:quarantineAttribute
|
|
fromFile:[root stringByAppendingPathComponent:file]
|
|
options:removeXAttrOptions];
|
|
}
|
|
}
|
|
}
|
|
|
|
@end
|