diff --git a/Squirrel/SQRLUpdater.m b/Squirrel/SQRLUpdater.m index 2c860bb1..ac1c3aa0 100644 --- a/Squirrel/SQRLUpdater.m +++ b/Squirrel/SQRLUpdater.m @@ -20,6 +20,7 @@ #import "SQRLShipItRequest.h" #import #import +#import NSString * const SQRLUpdaterErrorDomain = @"SQRLUpdaterErrorDomain"; NSString * const SQRLUpdaterServerDataErrorKey = @"SQRLUpdaterServerDataErrorKey"; @@ -32,6 +33,9 @@ const NSInteger SQRLUpdaterErrorInvalidJSON = 6; const NSInteger SQRLUpdaterErrorInvalidServerBody = 7; +/// The application's being run on a read-only volume. +const NSInteger SQRLUpdaterErrorReadOnlyVolume = 8; + // The prefix used when creating temporary directories for updates. This will be // followed by a random string of characters. static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update."; @@ -194,6 +198,16 @@ - (id)initWithUpdateRequest:(NSURLRequest *)updateRequest { if (httpResponse.statusCode == 204 /* No Content */) { return [RACSignal empty]; } + + BOOL readOnlyVolume = [self isRunningOnReadOnlyVolume]; + if (readOnlyVolume) { + NSDictionary *errorInfo = @{ + NSLocalizedDescriptionKey: NSLocalizedString(@"Cannot update while running on a read-only volume", nil), + NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"The application is on a read-only volume. Please move the application and try again. If you're on macOS Sierra or later, you'll need to move the application out of the Downloads directory. See https://github.com/Squirrel/Squirrel.Mac/issues/182 for more information.", nil), + }; + NSError *error = [NSError errorWithDomain:SQRLUpdaterErrorDomain code:SQRLUpdaterErrorReadOnlyVolume userInfo:errorInfo]; + return [RACSignal error:error]; + } } return [RACSignal return:bodyData]; @@ -492,6 +506,19 @@ - (RACSignal *)shipItStateURL { setNameWithFormat:@"%@ -shipItStateURL", self]; } +/// Is the host app running on a read-only volume? +- (BOOL)isRunningOnReadOnlyVolume { + struct statfs statfsInfo; + NSURL *bundleURL = NSRunningApplication.currentApplication.bundleURL; + int result = statfs(bundleURL.fileSystemRepresentation, &statfsInfo); + if (result == 0) { + return (statfsInfo.f_flags & MNT_RDONLY) != 0; + } else { + // If we can't even check if the volume is read-only, assume it is. + return true; + } +} + - (RACSignal *)performHousekeeping { return [[RACSignal merge:@[ [self pruneUpdateDirectories], [self truncateLogs] ]]