diff --git a/ArqRestoreCommand.m b/ArqRestoreCommand.m index e847393..6c731dd 100644 --- a/ArqRestoreCommand.m +++ b/ArqRestoreCommand.m @@ -43,6 +43,7 @@ #import "UserAndComputer.h" #import "ArqSalt.h" #import "ArqRepo.h" +#import "BackupSet.h" @interface ArqRestoreCommand (internal) - (BOOL)printArqFolders:(NSError **)error; @@ -119,56 +120,43 @@ if (![self validateS3Keys:error]) { return NO; } - for (NSString *s3BucketName in [S3Service s3BucketNamesForAccessKeyID:accessKey]) { + + NSArray *backupSets = [BackupSet allBackupSetsForAccessKeyID:accessKey secretAccessKey:secretKey error:error]; + if (backupSets == nil) { + return NO; + } + for (BackupSet *backupSet in backupSets) { + NSString *s3BucketName = [backupSet s3BucketName]; + NSString *computerUUID = [backupSet computerUUID]; + UserAndComputer *uac = [backupSet userAndComputer]; printf("S3 bucket: %s", [s3BucketName UTF8String]); - NSString *computerUUIDPrefix = [NSString stringWithFormat:@"/%@/", s3BucketName]; - NSError *myError = nil; - NSArray *computerUUIDs = [s3 commonPrefixesForPathPrefix:computerUUIDPrefix delimiter:@"/" error:&myError]; - if (computerUUIDs == nil && ![myError isErrorWithDomain:[S3Service errorDomain] code:ERROR_NOT_FOUND]) { - if (error != NULL) { - *error = myError; - } + if (uac != nil) { + printf(" %s (%s)", [[uac computerName] UTF8String], [[uac userName] UTF8String]); + } else { + printf(" (unknown computer)"); + } + printf(" UUID %s", [computerUUID UTF8String]); + NSString *computerBucketsPrefix = [NSString stringWithFormat:@"/%@/%@/buckets", s3BucketName, computerUUID]; + NSArray *s3BucketUUIDPaths = [s3 pathsWithPrefix:computerBucketsPrefix error:error]; + if (s3BucketUUIDPaths == nil) { return NO; } - if ([computerUUIDs count] == 0) { - printf(" (no computers found)"); + if ([s3BucketUUIDPaths count] == 0) { + printf(" (no folders found)"); } printf("\n"); - for (NSString *computerUUID in computerUUIDs) { - NSString *computerInfoPath = [NSString stringWithFormat:@"/%@/%@/computerinfo", s3BucketName, computerUUID]; - NSError *uacError = nil; - NSData *uacData = [s3 dataAtPath:computerInfoPath error:&uacError]; - UserAndComputer *uac = nil; - if (uacData != nil) { - uac = [[[UserAndComputer alloc] initWithXMLData:uacData error:&uacError] autorelease]; - printf(" %s (%s)", [[uac computerName] UTF8String], [[uac userName] UTF8String]); - } else { - printf(" (unknown computer)"); - } - printf(" UUID %s", [computerUUID UTF8String]); - NSString *computerUUIDPath = [computerUUIDPrefix stringByAppendingPathComponent:computerUUID]; - NSString *computerBucketsPrefix = [computerUUIDPath stringByAppendingPathComponent:@"buckets"]; - NSArray *s3BucketUUIDPaths = [s3 pathsWithPrefix:computerBucketsPrefix error:error]; - if (s3BucketUUIDPaths == nil) { + for (NSString *uuidPath in s3BucketUUIDPaths) { + NSData *data = [s3 dataAtPath:uuidPath error:error]; + if (data == nil) { return NO; } - if ([s3BucketUUIDPaths count] == 0) { - printf(" (no folders found)"); - } - printf("\n"); - for (NSString *uuidPath in s3BucketUUIDPaths) { - NSData *data = [s3 dataAtPath:uuidPath error:error]; - if (data == nil) { - return NO; - } - DictNode *plist = [DictNode dictNodeWithXMLData:data error:error]; - if (plist == nil) { - return NO; - } - printf(" %s\n", [[[plist stringNodeForKey:@"LocalPath"] stringValue] UTF8String]); - printf(" UUID: %s\n", [[uuidPath lastPathComponent] UTF8String]); - printf(" restore command: arq_restore %s\n", [uuidPath UTF8String]); + DictNode *plist = [DictNode dictNodeWithXMLData:data error:error]; + if (plist == nil) { + return NO; } + printf(" %s\n", [[[plist stringNodeForKey:@"LocalPath"] stringValue] UTF8String]); + printf(" UUID: %s\n", [[uuidPath lastPathComponent] UTF8String]); + printf(" restore command: arq_restore %s\n", [uuidPath UTF8String]); } } return YES; diff --git a/BackupSet.h b/BackupSet.h new file mode 100644 index 0000000..56ba406 --- /dev/null +++ b/BackupSet.h @@ -0,0 +1,53 @@ +/* + Copyright (c) 2010, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import +@class UserAndComputer; + +@interface BackupSet : NSObject { + NSString *accessKeyID; + NSString *secretAccessKey; + NSString *s3BucketName; + NSString *computerUUID; + UserAndComputer *uac; +} ++ (NSArray *)allBackupSetsForAccessKeyID:(NSString *)theAccessKeyID secretAccessKey:(NSString *)theSecretAccessKey error:(NSError **)error; + +- (id)initWithAccessKeyID:(NSString *)theAccessKeyID + secretAccessKey:(NSString *)theSecretAccessKey + s3BucketName:(NSString *)theS3BucketName + computerUUID:(NSString *)theComputerUUID + userAndComputer:(UserAndComputer *)theUAC; +- (NSString *)s3BucketName; +- (NSString *)computerUUID; +- (UserAndComputer *)userAndComputer; +@end diff --git a/BackupSet.m b/BackupSet.m new file mode 100644 index 0000000..cf1a32d --- /dev/null +++ b/BackupSet.m @@ -0,0 +1,134 @@ +/* + Copyright (c) 2010, Stefan Reitshamer http://www.haystacksoftware.com + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the names of PhotoMinds LLC or Haystack Software, nor the names of + their contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "BackupSet.h" +#import "S3AuthorizationProvider.h" +#import "S3Service.h" +#import "UserAndComputer.h" +#import "NSErrorCodes.h" +#import "NSData-Encrypt.h" +#import "CryptoKey.h" +#import "RegexKitLite.h" +#import "ArqRepo.h" +#import "BlobKey.h" +#import "SetNSError.h" +#import "Commit.h" +#import "S3ObjectMetadata.h" +#import "ArqSalt.h" + +@implementation BackupSet ++ (NSArray *)allBackupSetsForAccessKeyID:(NSString *)theAccessKeyID secretAccessKey:(NSString *)theSecretAccessKey error:(NSError **)error { + S3AuthorizationProvider *sap = [[[S3AuthorizationProvider alloc] initWithAccessKey:theAccessKeyID secretKey:theSecretAccessKey] autorelease]; + S3Service *s3 = [[[S3Service alloc] initWithS3AuthorizationProvider:sap useSSL:YES retryOnTransientError:NO] autorelease]; + NSArray *s3BucketNames = [s3 s3BucketNames:error]; + if (s3BucketNames == nil) { + return nil; + } + NSMutableArray *ret = [NSMutableArray array]; + for (NSString *theS3BucketName in s3BucketNames) { + if ([theS3BucketName rangeOfString:@"-com-haystacksoftware-arq"].location == NSNotFound + && [theS3BucketName rangeOfString:@".com.haystacksoftware.arq"].location == NSNotFound) { + HSLogDebug(@"skipping bucket %@", theS3BucketName); + } else { + NSString *queryPrefix = [NSString stringWithFormat:@"/%@/", [theS3BucketName lowercaseString]]; + NSArray *theS3ComputerUUIDs = [s3 commonPrefixesForPathPrefix:queryPrefix delimiter:@"/" error:error]; + if (theS3ComputerUUIDs == nil) { + return nil; + } + for (NSString *s3ComputerUUID in theS3ComputerUUIDs) { + NSString *computerInfoPath = [NSString stringWithFormat:@"/%@/%@/computerinfo", theS3BucketName, s3ComputerUUID]; + NSError *uacError = nil; + NSData *uacData = [s3 dataAtPath:computerInfoPath error:&uacError]; + UserAndComputer *uac = nil; + if (uacData != nil) { + uac = [[[UserAndComputer alloc] initWithXMLData:uacData error:&uacError] autorelease]; + if (uac == nil) { + HSLogError(@"error parsing UserAndComputer data: %@", uacError); + } + } + BackupSet *backupSet = [[[BackupSet alloc] initWithAccessKeyID:theAccessKeyID + secretAccessKey:theSecretAccessKey + s3BucketName:theS3BucketName + computerUUID:s3ComputerUUID + userAndComputer:uac] autorelease]; + [ret addObject:backupSet]; + } + } + } + NSSortDescriptor *descriptor = [[[NSSortDescriptor alloc] initWithKey:@"description" ascending:YES] autorelease]; + [ret sortUsingDescriptors:[NSArray arrayWithObject:descriptor]]; + return ret; +} + +- (id)initWithAccessKeyID:(NSString *)theAccessKeyID + secretAccessKey:(NSString *)theSecretAccessKey + s3BucketName:(NSString *)theS3BucketName + computerUUID:(NSString *)theComputerUUID + userAndComputer:(UserAndComputer *)theUAC { + if (self = [super init]) { + accessKeyID = [theAccessKeyID retain]; + secretAccessKey = [theSecretAccessKey retain]; + s3BucketName = [theS3BucketName retain]; + computerUUID = [theComputerUUID retain]; + uac = [theUAC retain]; + } + return self; +} +- (void)dealloc { + [accessKeyID release]; + [secretAccessKey release]; + [s3BucketName release]; + [computerUUID release]; + [uac release]; + [super dealloc]; +} +- (NSString *)s3BucketName { + return s3BucketName; +} +- (NSString *)computerUUID { + return computerUUID; +} +- (UserAndComputer *)userAndComputer { + return uac; +} + + +#pragma mark NSObject +- (NSString *)description { + NSString *bucketRegion = [S3Service displayNameForBucketRegion:[S3Service s3BucketRegionForS3BucketName:s3BucketName]]; + if (uac != nil) { + return [NSString stringWithFormat:@"%@ (%@) : %@ (%@)", [uac computerName], [uac userName], bucketRegion, computerUUID]; + } + return [NSString stringWithFormat:@"unknown computer : %@ (%@)", bucketRegion, computerUUID]; + +} +@end diff --git a/arq_restore.xcodeproj/project.pbxproj b/arq_restore.xcodeproj/project.pbxproj index 7032a47..9f1b88e 100644 --- a/arq_restore.xcodeproj/project.pbxproj +++ b/arq_restore.xcodeproj/project.pbxproj @@ -50,6 +50,7 @@ F805B8A11160EBAA007EC01E /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F805B8A01160EBAA007EC01E /* CoreFoundation.framework */; }; F805B8C21160EC41007EC01E /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F805B8C11160EC41007EC01E /* Security.framework */; }; F805B8CE1160ECD7007EC01E /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F805B8CD1160ECD7007EC01E /* CoreServices.framework */; }; + F81426D714541A6C00D7E50A /* BackupSet.m in Sources */ = {isa = PBXBuildFile; fileRef = F81426D614541A6C00D7E50A /* BackupSet.m */; }; F83C1A7411CA7C170001958F /* ArqRestoreCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B54C1160D3E6007EC01E /* ArqRestoreCommand.m */; }; F83C1A7711CA7C170001958F /* ArrayNode.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7421160DCFE007EC01E /* ArrayNode.m */; }; F83C1A7811CA7C170001958F /* BooleanNode.m in Sources */ = {isa = PBXBuildFile; fileRef = F805B7441160DCFE007EC01E /* BooleanNode.m */; }; @@ -336,6 +337,8 @@ F805B8C11160EC41007EC01E /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; F805B8C51160EC4E007EC01E /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; F805B8CD1160ECD7007EC01E /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; }; + F81426D514541A6C00D7E50A /* BackupSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BackupSet.h; sourceTree = ""; }; + F81426D614541A6C00D7E50A /* BackupSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BackupSet.m; sourceTree = ""; }; F83C1A5F11CA7A6B0001958F /* arq_verify.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = arq_verify.m; sourceTree = ""; }; F83C1A6111CA7BD20001958F /* ArqVerifyCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ArqVerifyCommand.h; sourceTree = ""; }; F83C1A6211CA7BD20001958F /* ArqVerifyCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ArqVerifyCommand.m; sourceTree = ""; }; @@ -544,6 +547,8 @@ F8F4D67D121DA542002D09C1 /* UserAndComputer.m */, F89A1F3C13FAC6820071D321 /* UserLibrary_Arq.h */, F89A1F3D13FAC6820071D321 /* UserLibrary_Arq.m */, + F81426D514541A6C00D7E50A /* BackupSet.h */, + F81426D614541A6C00D7E50A /* BackupSet.m */, ); name = Source; sourceTree = ""; @@ -974,6 +979,7 @@ F89A200B13FADAD70071D321 /* ArqSalt.m in Sources */, F89A205413FAE2DA0071D321 /* LocalS3Signer.m in Sources */, F89A205A13FAE3010071D321 /* DataOutputStream.m in Sources */, + F81426D714541A6C00D7E50A /* BackupSet.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1088,7 +1094,11 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + ARCHS = ( + x86_64, + i386, + ppc, + ); COPY_PHASE_STRIP = NO; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_FIX_AND_CONTINUE = YES; @@ -1108,7 +1118,11 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + ARCHS = ( + x86_64, + i386, + ppc, + ); DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_MODEL_TUNING = G5; GCC_PRECOMPILE_PREFIX_HEADER = YES;