Replaced Arq 2 code with Arq 4 code.

This commit is contained in:
Stefan Reitshamer 2014-07-28 14:20:07 -04:00
parent 20bf814d17
commit 8b416e0e22
437 changed files with 43188 additions and 11390 deletions

View file

@ -1,26 +0,0 @@
//
// AppKeychain.h
// Backup
//
// Created by Stefan Reitshamer on 8/26/09.
// Copyright 2009 PhotoMinds LLC. All rights reserved.
//
@interface AppKeychain : NSObject {
NSString *backupAppPath;
NSString *agentAppPath;
SecAccessRef access;
}
+ (BOOL)accessKeyID:(NSString **)accessKeyID secretAccessKey:(NSString **)secret error:(NSError **)error;
+ (BOOL)containsEncryptionPasswordForS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID;
+ (BOOL)encryptionPassword:(NSString **)encryptionPassword forS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID error:(NSError **)error;
+ (BOOL)twitterAccessKey:(NSString **)theAccessKey secret:(NSString **)secret error:(NSError **)error;
- (id)initWithBackupAppPath:(NSString *)backupAppPath agentAppPath:(NSString *)agentAppPath;
- (BOOL)setAccessKeyID:(NSString *)theAccessKeyID secretAccessKey:(NSString *)theSecretAccessKey error:(NSError **)error;
- (BOOL)setEncryptionKey:(NSString *)theEncryptionPassword forS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID error:(NSError **)error;
- (BOOL)setTwitterAccessKey:(NSString *)theKey secret:(NSString *)theSecret error:(NSError **)error;
- (BOOL)deleteTwitterAccessKey:(NSError **)error;
@end

View file

@ -1,271 +0,0 @@
//
// AppKeychain.m
// Backup
//
// Created by Stefan Reitshamer on 8/26/09.
// Copyright 2009 PhotoMinds LLC. All rights reserved.
//
#import "AppKeychain.h"
#import "SetNSError.h"
#import "UserLibrary.h"
#define ARQ_S3_LABEL @"Arq S3"
#define ARQ_TWITTER_LABEL @"Arq Twitter Access Token"
#define ARQ_ENCRYPTION_ACCOUNT_LABEL @"EncryptionKey"
@interface AppKeychain (internal)
+ (NSString *)encryptionPasswordLabelForS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID;
+ (BOOL)account:(NSString **)account password:(NSString **)password forLabel:(NSString *)label error:(NSError **)error;
+ (BOOL)findItem:(NSString *)theLabel item:(SecKeychainItemRef *)item error:(NSError **)error;
- (BOOL)loadSecAccess:(NSError **)error;
- (BOOL)deleteLabel:(NSString *)theLabel error:(NSError **)error;
- (BOOL)addLabel:(NSString *)theLabel account:(NSString *)theAccount password:(NSString *)thePassword error:(NSError **)error;
@end
@implementation AppKeychain
+ (BOOL)accessKeyID:(NSString **)accessKey secretAccessKey:(NSString **)secret error:(NSError **)error {
NSString *account = nil;
NSString *password = nil;
if (![AppKeychain account:&account password:&password forLabel:ARQ_S3_LABEL error:error]) {
return NO;
}
if (accessKey != nil) {
*accessKey = account;
}
if (secret != nil) {
*secret = password;
}
return YES;
}
+ (BOOL)containsEncryptionPasswordForS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID {
NSString *encryptionPassword = nil;
NSError *myError = nil;
return [AppKeychain encryptionPassword:&encryptionPassword forS3BucketName:theS3BucketName computerUUID:theComputerUUID error:&myError];
}
+ (BOOL)encryptionPassword:(NSString **)encryptionPassword forS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID error:(NSError **)error {
NSString *label = [AppKeychain encryptionPasswordLabelForS3BucketName:theS3BucketName computerUUID:theComputerUUID];
NSString *account = nil;
NSString *password = nil;
if (![AppKeychain account:&account password:&password forLabel:label error:error]) {
return NO;
}
if (encryptionPassword != nil) {
*encryptionPassword = password;
}
return YES;
}
+ (BOOL)twitterAccessKey:(NSString **)theAccessKey secret:(NSString **)secret error:(NSError **)error {
return [AppKeychain account:theAccessKey password:secret forLabel:ARQ_TWITTER_LABEL error:error];
}
- (id)initWithBackupAppPath:(NSString *)theBackupAppPath
agentAppPath:(NSString *)theAgentAppPath {
if (self = [super init]) {
backupAppPath = [theBackupAppPath copy];
agentAppPath = [theAgentAppPath copy];
}
return self;
}
- (void)dealloc {
if (access != NULL) {
CFRelease(access);
}
[backupAppPath release];
[agentAppPath release];
[super dealloc];
}
- (BOOL)setAccessKeyID:(NSString *)theAccessKeyID
secretAccessKey:(NSString *)theSecretAccessKey
error:(NSError **)error {
return [self addLabel:ARQ_S3_LABEL account:theAccessKeyID password:theSecretAccessKey error:error];
}
- (BOOL)setEncryptionKey:(NSString *)theEncryptionPassword
forS3BucketName:(NSString *)theS3BucketName
computerUUID:(NSString *)theComputerUUID
error:(NSError **)error {
NSString *label = [AppKeychain encryptionPasswordLabelForS3BucketName:theS3BucketName computerUUID:theComputerUUID];
return [self addLabel:label account:ARQ_ENCRYPTION_ACCOUNT_LABEL password:theEncryptionPassword error:error];
}
- (BOOL)setTwitterAccessKey:(NSString *)theKey secret:(NSString *)theSecret error:(NSError **)error {
return [self addLabel:ARQ_TWITTER_LABEL account:theKey password:theSecret error:error];
}
- (BOOL)deleteTwitterAccessKey:(NSError **)error {
return [self deleteLabel:ARQ_TWITTER_LABEL error:error];
}
@end
@implementation AppKeychain (internal)
+ (NSString *)encryptionPasswordLabelForS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID {
return [NSString stringWithFormat:@"Arq Encryption:%@:%@", theS3BucketName, theComputerUUID];
}
+ (BOOL)account:(NSString **)account password:(NSString **)password forLabel:(NSString *)theLabel error:(NSError **)error {
SecKeychainItemRef item = NULL;
if (![AppKeychain findItem:theLabel item:&item error:error]) {
return NO;
}
if (item == NULL) {
SETNSERROR(@"AppKeychainErrorDomain", -1, @"Keychain item %@ not found", theLabel);
return NO;
}
SecKeychainAttributeList *outAttrList = NULL;
UInt32 length;
void *data;
UInt32 tags[] = {
kSecAccountItemAttr
};
UInt32 formats[] = {
CSSM_DB_ATTRIBUTE_FORMAT_STRING
};
SecKeychainAttributeInfo info = {
1,
tags,
formats
};
OSStatus oss = SecKeychainItemCopyAttributesAndData(item, &info, NULL, &outAttrList, &length, &data);
if (oss != noErr) {
NSString *errorMessage = (NSString *)SecCopyErrorMessageString(oss, NULL);
SETNSERROR(@"AppKeychainErrorDomain", -1, @"Error reading data from Keychain item %@: %@ (code %d)", theLabel, errorMessage, oss);
[errorMessage release];
return NO;
}
if (account != nil) {
*account = nil;
for (UInt32 index = 0; index < outAttrList->count; index++) {
SecKeychainAttribute *attr = outAttrList->attr + index;
if (attr->tag == kSecAccountItemAttr) {
*account = [[[NSString alloc] initWithBytes:attr->data length:attr->length encoding:NSUTF8StringEncoding] autorelease];
break;
}
}
}
if (password != nil) {
*password = [[[NSString alloc] initWithBytes:data length:length encoding:NSUTF8StringEncoding] autorelease];
}
SecKeychainItemFreeAttributesAndData(outAttrList, data);
CFRelease(item);
return YES;
}
+ (BOOL)findItem:(NSString *)theLabel item:(SecKeychainItemRef *)item error:(NSError **)error {
*item = NULL;
const char *label = [theLabel UTF8String];
SecKeychainAttribute attrs[] = {
{
kSecLabelItemAttr,
strlen(label),
(char *)label
}
};
SecKeychainAttributeList attributes = {
sizeof(attrs) / sizeof(attrs[0]),
attrs
};
SecKeychainSearchRef searchRef;
OSStatus oss;
oss = SecKeychainSearchCreateFromAttributes(NULL, kSecGenericPasswordItemClass, &attributes, &searchRef);
if (oss != noErr) {
SETNSERROR(@"AppKeychainErrorDomain", -1, @"error creating keychain search");
return NO;
}
SecKeychainSearchCopyNext(searchRef, item);
CFRelease(searchRef);
return YES;
}
- (BOOL)loadSecAccess:(NSError **)error {
if (access == NULL) {
NSMutableArray *trustedApplications = [NSMutableArray array];
OSStatus oss;
SecTrustedApplicationRef agentApp;
oss = SecTrustedApplicationCreateFromPath([agentAppPath fileSystemRepresentation], &agentApp);
if (oss != noErr) {
CFStringRef msg = SecCopyErrorMessageString(oss, NULL);
SETNSERROR(@"AppKeychainErrorDomain", -1, @"Error creating Agent trusted application %@: %@ (code %d)", agentAppPath, (NSString *)msg, oss);
CFRelease(msg);
return NO;
}
[trustedApplications addObject:(id)agentApp];
CFRelease(agentApp);
SecTrustedApplicationRef backupApp;
oss = SecTrustedApplicationCreateFromPath([backupAppPath fileSystemRepresentation], &backupApp);
if (oss != noErr) {
CFStringRef msg = SecCopyErrorMessageString(oss, NULL);
SETNSERROR(@"AppKeychainErrorDomain", -1, @"Error creating trusted application: %@ (code %d)", (NSString *)msg, oss);
CFRelease(agentApp);
CFRelease(msg);
return NO;
}
[trustedApplications addObject:(id)backupApp];
CFRelease(backupApp);
oss = SecAccessCreate((CFStringRef)@"Arq", (CFArrayRef)trustedApplications, &access);
if (oss != noErr) {
CFStringRef msg = SecCopyErrorMessageString(oss, NULL);
SETNSERROR(@"AppKeychainErrorDomain", -1, @"Error creating SecAccessRef: %@ (code %d)", (NSString *)msg, oss);
CFRelease(msg);
return NO;
}
}
return YES;
}
- (BOOL)deleteLabel:(NSString *)theLabel error:(NSError **)error {
SecKeychainItemRef item = NULL;
if (![AppKeychain findItem:theLabel item:&item error:error]) {
return NO;
}
if (item != NULL) {
OSStatus oss = SecKeychainItemDelete(item);
CFRelease(item);
if (oss != noErr) {
NSString *errorMessage = (NSString *)SecCopyErrorMessageString(oss, NULL);
SETNSERROR(@"AppKeychainErrorDomain", -1, @"error deleting item for label %@: %@ (code %d)", theLabel, errorMessage, oss);
[errorMessage release];
return NO;
}
}
return YES;
}
- (BOOL)addLabel:(NSString *)theLabel account:(NSString *)theAccount password:(NSString *)thePassword error:(NSError **)error {
if (![self loadSecAccess:error]) {
return NO;
}
if (![self deleteLabel:theLabel error:error]) {
return NO;
}
const char *label = [theLabel UTF8String];
const char *account = [theAccount UTF8String];
const char *password = [thePassword UTF8String];
SecKeychainAttribute attrs[] = {
{
kSecLabelItemAttr,
strlen(label),
(char *)label
},
{
kSecAccountItemAttr,
strlen(account),
(char *)account
},
{
kSecServiceItemAttr,
strlen(label),
(char *)label
}
};
SecKeychainAttributeList attributes = {
sizeof(attrs) / sizeof(attrs[0]),
attrs
};
OSStatus oss = SecKeychainItemCreateFromContent(kSecGenericPasswordItemClass, &attributes, strlen(password), password, NULL, access, NULL);
if (oss != noErr) {
NSString *errorMessage = (NSString *)SecCopyErrorMessageString(oss, NULL);
SETNSERROR(@"AppKeychainErrorDomain", -1, @"Error creating keychain item: %@ (code %d)", errorMessage, oss);
[errorMessage release];
return NO;
}
return YES;
}
@end

View file

@ -1,26 +0,0 @@
//
// ArqFark.h
// Arq
//
// Created by Stefan Reitshamer on 6/22/10.
// Copyright 2010 __MyCompanyName__. All rights reserved.
//
@class S3Service;
@class ServerBlob;
@interface ArqFark : NSObject {
S3Service *s3;
NSString *s3BucketName;
NSString *computerUUID;
NSThread *creatorThread;
}
+ (NSString *)errorDomain;
- (id)initWithS3Service:(S3Service *)theS3
s3BucketName:(NSString *)theS3BucketName
computerUUID:(NSString *)theComputerUUID;
- (NSData *)bucketDataForRelativePath:(NSString *)bucketDataRelativePath error:(NSError **)error;
- (NSData *)dataForSHA1:(NSString *)sha1 error:(NSError **)error;
- (ServerBlob *)newServerBlobForSHA1:(NSString *)sha1 error:(NSError **)error;
@end

View file

@ -1,74 +0,0 @@
//
// ArqFark.m
// Arq
//
// Created by Stefan Reitshamer on 6/22/10.
// Copyright 2010 __MyCompanyName__. All rights reserved.
//
#import "ArqFark.h"
#import "ArqPackSet.h"
#import "ServerBlob.h"
#import "NSErrorCodes.h"
#import "S3Service.h"
#import "RegexKitLite.h"
#import "DiskPackIndex.h"
#import "FarkPath.h"
#import "SetNSError.h"
#import "NSError_extra.h"
#define MAX_RETRIES 10
@implementation ArqFark
+ (NSString *)errorDomain {
return @"ArqFarkErrorDomain";
}
- (id)initWithS3Service:(S3Service *)theS3
s3BucketName:(NSString *)theS3BucketName
computerUUID:(NSString *)theComputerUUID {
if (self = [super init]) {
s3 = [theS3 retain];
s3BucketName = [theS3BucketName retain];
computerUUID = [theComputerUUID retain];
creatorThread = [[NSThread currentThread] retain];
}
return self;
}
- (void)dealloc {
[s3 release];
[s3BucketName release];
[computerUUID release];
[creatorThread release];
[super dealloc];
}
- (NSData *)bucketDataForRelativePath:(NSString *)bucketDataRelativePath error:(NSError **)error {
NSAssert([NSThread currentThread] == creatorThread, @"must be on same thread!");
NSError *myError = nil;
NSData *data = [s3 dataAtPath:[FarkPath s3PathForBucketDataRelativePath:bucketDataRelativePath s3BucketName:s3BucketName computerUUID:computerUUID] error:&myError];
if (data == nil) {
if ([myError isErrorWithDomain:[S3Service errorDomain] code:ERROR_NOT_FOUND]) {
SETNSERROR([ArqFark errorDomain], ERROR_NOT_FOUND, @"bucket data not found for path %@", bucketDataRelativePath);
} else {
if (error != NULL) {
*error = myError;
}
}
}
return data;
}
- (NSData *)dataForSHA1:(NSString *)sha1 error:(NSError **)error {
ServerBlob *sb = [self newServerBlobForSHA1:sha1 error:error];
if (sb == nil) {
return nil;
}
NSData *data = [sb slurp:error];
[sb release];
return data;
}
- (ServerBlob *)newServerBlobForSHA1:(NSString *)sha1 error:(NSError **)error {
// NSAssert([NSThread currentThread] == creatorThread, @"must be on same thread!");
NSString *s3Path = [NSString stringWithFormat:@"/%@/%@/objects/%@", s3BucketName, computerUUID, sha1];
return [s3 newServerBlobAtPath:s3Path error:error];
}
@end

View file

@ -1,28 +0,0 @@
//
// ArqPackSet.h
// Arq
//
// Created by Stefan Reitshamer on 6/22/10.
// Copyright 2010 __MyCompanyName__. All rights reserved.
//
@class S3Service;
@class ServerBlob;
@interface ArqPackSet : NSObject {
S3Service *s3;
NSString *s3BucketName;
NSString *computerUUID;
NSString *packSetName;
NSDictionary *packIndexEntries;
}
+ (NSString *)errorDomain;
- (id)initWithS3Service:(S3Service *)theS3
s3BucketName:(NSString *)theS3BucketName
computerUUID:(NSString *)theComputerUUID
packSetName:(NSString *)thePackSetName;
- (NSString *)packSetName;
- (ServerBlob *)newServerBlobForSHA1:(NSString *)sha1 error:(NSError **)error;
- (BOOL)containsBlob:(BOOL *)contains forSHA1:(NSString *)sha1 packSHA1:(NSString **)packSHA1 error:(NSError **)error;
@end

View file

@ -1,212 +0,0 @@
//
// ArqPackSet.m
// Arq
//
// Created by Stefan Reitshamer on 6/22/10.
// Copyright 2010 __MyCompanyName__. All rights reserved.
//
#import "ArqPackSet.h"
#import "S3Service.h"
#import "RegexKitLite.h"
#import "DiskPackIndex.h"
#import "PackIndexEntry.h"
#import "SetNSError.h"
#import "NSErrorCodes.h"
#import "DiskPack.h"
#import "AppKeychain.h"
#import "S3AuthorizationProvider.h"
#import "NSError_extra.h"
#define MAX_RETRIES (10)
@interface ArqPackSet (internal)
- (ServerBlob *)newInternalServerBlobForSHA1:(NSString *)sha1 error:(NSError **)error;
- (BOOL)loadPackIndexEntries:(NSError **)error;
- (NSDictionary *)doLoadPackIndexEntries:(NSError **)error;
@end
@implementation ArqPackSet
+ (NSString *)errorDomain {
return @"ArqPackSetErrorDomain";
}
- (id)initWithS3Service:(S3Service *)theS3
s3BucketName:(NSString *)theS3BucketName
computerUUID:(NSString *)theComputerUUID
packSetName:(NSString *)thePackSetName {
if (self = [super init]) {
s3 = [theS3 retain];
s3BucketName = [theS3BucketName retain];
computerUUID = [theComputerUUID retain];
packSetName = [thePackSetName retain];
}
return self;
}
- (void)dealloc {
[s3 release];
[s3BucketName release];
[computerUUID release];
[packSetName release];
[packIndexEntries release];
[super dealloc];
}
- (NSString *)packSetName {
return packSetName;
}
- (ServerBlob *)newServerBlobForSHA1:(NSString *)sha1 error:(NSError **)error {
ServerBlob *sb = nil;
NSError *myError = nil;
NSUInteger i = 0;
NSAutoreleasePool *pool = nil;
for (i = 0; i < MAX_RETRIES; i++) {
[pool drain];
pool = [[NSAutoreleasePool alloc] init];
myError = nil;
sb = [self newInternalServerBlobForSHA1:sha1 error:&myError];
if (sb == nil) {
if ([myError isErrorWithDomain:[ArqPackSet errorDomain] code:ERROR_PACK_INDEX_ENTRY_NOT_RESOLVABLE]) {
HSLogInfo(@"pack index entry not resolvable; reloading pack index entries from disk cache");
[packIndexEntries release];
packIndexEntries = nil;
} else {
break;
}
} else {
break;
}
}
[myError retain];
[pool drain];
[myError autorelease];
if (sb == nil) {
if ([myError isErrorWithDomain:[ArqPackSet errorDomain] code:ERROR_PACK_INDEX_ENTRY_NOT_RESOLVABLE]) {
SETNSERROR([ArqPackSet errorDomain], ERROR_NOT_FOUND, @"failed %lu times to load blob for sha1 %@ from pack set %@", i, sha1, packSetName);
} else if (error != NULL) {
*error = myError;
}
}
return sb;
}
- (BOOL)containsBlob:(BOOL *)contains forSHA1:(NSString *)sha1 packSHA1:(NSString **)packSHA1 error:(NSError **)error {
if (packIndexEntries == nil && ![self loadPackIndexEntries:error]) {
return NO;
}
PackIndexEntry *pie = [packIndexEntries objectForKey:sha1];
*contains = (pie != nil);
if (pie != nil) {
*packSHA1 = [pie packSHA1];
}
return YES;
}
@end
@implementation ArqPackSet (internal)
- (ServerBlob *)newInternalServerBlobForSHA1:(NSString *)sha1 error:(NSError **)error {
if (packIndexEntries == nil && ![self loadPackIndexEntries:error]) {
return nil;
}
PackIndexEntry *pie = [packIndexEntries objectForKey:sha1];
if (pie == nil) {
SETNSERROR([ArqPackSet errorDomain], ERROR_NOT_FOUND, @"sha1 %@ not found in pack set %@", sha1, packSetName);
return NO;
}
DiskPack *diskPack = [[DiskPack alloc] initWithS3Service:s3
s3BucketName:s3BucketName
computerUUID:computerUUID
packSetName:packSetName
packSHA1:[pie packSHA1]
targetUID:getuid()
targetGID:getgid()];
ServerBlob *sb = nil;
do {
NSError *myError = nil;
if (![diskPack makeLocal:&myError]) {
NSString *msg = [NSString stringWithFormat:@"error making disk pack %@ (pack set %@, computerUUID %@, s3BucketName %@) containing sha1 %@ local: %@", [pie packSHA1], packSetName, computerUUID, s3BucketName, sha1, [myError localizedDescription]];
HSLogError(@"%@", msg);
SETNSERROR([ArqPackSet errorDomain], ERROR_PACK_INDEX_ENTRY_NOT_RESOLVABLE, @"%@", msg);
break;
}
sb = [diskPack newServerBlobForObjectAtOffset:[pie offset] error:&myError];
if (sb == nil) {
SETNSERROR([ArqPackSet errorDomain], ERROR_PACK_INDEX_ENTRY_NOT_RESOLVABLE, @"error reading sha1 %@ from disk pack %@ (pack set %@, computerUUID %@, s3BucketName %@): %@", sha1, [pie packSHA1], packSetName, computerUUID, s3BucketName, [myError localizedDescription]);
break;
}
} while(0);
[diskPack release];
return sb;
}
- (BOOL)loadPackIndexEntries:(NSError **)error {
BOOL ret = YES;
NSAutoreleasePool *pool = nil;
for (;;) {
[pool drain];
pool = [[NSAutoreleasePool alloc] init];
NSError *myError = nil;
NSDictionary *entries = [self doLoadPackIndexEntries:&myError];
if (entries != nil) {
packIndexEntries = [entries retain];
break;
}
if ([myError code] != ERROR_NOT_FOUND) {
if (error != NULL) {
*error = myError;
}
ret = NO;
break;
}
// If it's a not-found error, it can be because Arq Agent replaced a pack with another one between when we got
// the S3 list and when we tried to make them local.
HSLogDebug(@"error loading pack index entries (retrying): %@", myError);
}
if (!ret && error != NULL) {
[*error retain];
}
[pool drain];
if (!ret && error != NULL) {
[*error autorelease];
}
return ret;
}
- (NSDictionary *)doLoadPackIndexEntries:(NSError **)error {
NSMutableDictionary *entries = [NSMutableDictionary dictionary];
NSString *packSHA1Prefix = [NSString stringWithFormat:@"/%@/%@/packsets/%@/", s3BucketName, computerUUID, packSetName];
NSArray *packSHA1Paths = [s3 pathsWithPrefix:packSHA1Prefix error:error];
if (packSHA1Paths == nil) {
return nil;
}
for (NSString *packSHA1Path in packSHA1Paths) {
NSRange sha1Range = [packSHA1Path rangeOfRegex:@"/(\\w+)\\.pack$" capture:1];
if (sha1Range.location != NSNotFound) {
NSString *packSHA1 = [packSHA1Path substringWithRange:sha1Range];
BOOL ret = NO;
DiskPackIndex *index = [[DiskPackIndex alloc] initWithS3Service:s3
s3BucketName:s3BucketName
computerUUID:computerUUID
packSetName:packSetName
packSHA1:packSHA1
targetUID:getuid()
targetGID:getgid()];
do {
if (![index makeLocal:error]) {
break;
}
NSArray *pies = [index allPackIndexEntries:error];
if (pies == nil) {
break;
}
HSLogTrace(@"found %lu entries in s3 pack sha1 %@ packset %@ computer %@ s3bucket %@", [pies count], packSHA1, packSetName, computerUUID, s3BucketName);
for (PackIndexEntry *pie in pies) {
[entries setObject:pie forKey:[pie objectSHA1]];
}
ret = YES;
} while (0);
[index release];
if (!ret) {
return nil;
}
}
}
return entries;
}
@end

View file

@ -1,43 +0,0 @@
//
// ArqRepo.h
// Arq
//
// Created by Stefan Reitshamer on 6/23/10.
// Copyright 2010 __MyCompanyName__. All rights reserved.
//
@class S3Service;
@class ArqFark;
@class ArqPackSet;
@class Commit;
@class Tree;
@class ServerBlob;
@class CryptoKey;
@class BlobKey;
@interface ArqRepo : NSObject {
NSString *bucketUUID;
ArqFark *arqFark;
CryptoKey *cryptoKey;
CryptoKey *stretchedCryptoKey;
ArqPackSet *treesPackSet;
ArqPackSet *blobsPackSet;
}
+ (NSString *)errorDomain;
- (id)initWithS3Service:(S3Service *)theS3
s3BucketName:(NSString *)theS3BucketName
computerUUID:(NSString *)theComputerUUID
bucketUUID:(NSString *)theBucketUUID
encryptionPassword:(NSString *)theEncryptionPassword
salt:(NSData *)theEncryptionSalt
error:(NSError **)error;
- (NSString *)bucketUUID;
- (BlobKey *)headBlobKey:(NSError **)error;
- (Commit *)commitForBlobKey:(BlobKey *)treeBlobKey error:(NSError **)error;
- (Tree *)treeForBlobKey:(BlobKey *)treeBlobKey error:(NSError **)error;
- (NSData *)blobDataForBlobKey:(BlobKey *)treeBlobKey error:(NSError **)error;
- (ServerBlob *)newServerBlobForBlobKey:(BlobKey *)treeBlobKey error:(NSError **)error;
- (BOOL)containsPackedBlob:(BOOL *)contains forBlobKey:(BlobKey *)theBlobKey packSetName:(NSString **)packSetName packSHA1:(NSString **)packSHA1 error:(NSError **)error;
@end

243
ArqRepo.m
View file

@ -1,243 +0,0 @@
//
// ArqRepo.m
// Arq
//
// Created by Stefan Reitshamer on 6/23/10.
// Copyright 2010 __MyCompanyName__. All rights reserved.
//
#import "ArqRepo.h"
#import "ArqFark.h"
#import "ArqPackSet.h"
#import "Commit.h"
#import "Tree.h"
#import "NSErrorCodes.h"
#import "ServerBlob.h"
#import "DataInputStream.h"
#import "DecryptedInputStream.h"
#import "NSData-Encrypt.h"
#import "SetNSError.h"
#import "NSError_extra.h"
#import "GunzipInputStream.h"
#import "CryptoKey.h"
#import "BlobKey.h"
#import "Encryption.h"
#import "NSData-GZip.h"
@implementation ArqRepo
+ (NSString *)errorDomain {
return @"ArqRepoErrorDomain";
}
- (id)initWithS3Service:(S3Service *)theS3
s3BucketName:(NSString *)theS3BucketName
computerUUID:(NSString *)theComputerUUID
bucketUUID:(NSString *)theBucketUUID
encryptionPassword:(NSString *)theEncryptionPassword
salt:(NSData *)theEncryptionSalt
error:(NSError **)error {
if (self = [super init]) {
bucketUUID = [theBucketUUID retain];
if (theEncryptionPassword == nil) {
SETNSERROR([Encryption errorDomain], -1, @"missing encryption password");
[self release];
return nil;
}
cryptoKey = [[CryptoKey alloc] initLegacyWithPassword:theEncryptionPassword error:error];
if (cryptoKey == nil) {
[self release];
return nil;
}
stretchedCryptoKey = [[CryptoKey alloc] initWithPassword:theEncryptionPassword salt:theEncryptionSalt error:error];
if (stretchedCryptoKey == nil) {
[self release];
return nil;
}
arqFark = [[ArqFark alloc] initWithS3Service:theS3 s3BucketName:theS3BucketName computerUUID:theComputerUUID];
treesPackSet = [[ArqPackSet alloc] initWithS3Service:theS3 s3BucketName:theS3BucketName computerUUID:theComputerUUID packSetName:[theBucketUUID stringByAppendingString:@"-trees"]];
blobsPackSet = [[ArqPackSet alloc] initWithS3Service:theS3 s3BucketName:theS3BucketName computerUUID:theComputerUUID packSetName:[theBucketUUID stringByAppendingString:@"-blobs"]];
}
return self;
}
- (void)dealloc {
[bucketUUID release];
[cryptoKey release];
[stretchedCryptoKey release];
[arqFark release];
[treesPackSet release];
[blobsPackSet release];
[super dealloc];
}
- (NSString *)bucketUUID {
return bucketUUID;
}
- (BlobKey *)headBlobKey:(NSError **)error {
NSString *bucketDataRelativePath = [NSString stringWithFormat:@"/%@/refs/heads/master", bucketUUID];
NSError *myError = nil;
NSData *data = [arqFark bucketDataForRelativePath:bucketDataRelativePath error:&myError];
if (data == nil) {
if ([myError isErrorWithDomain:[ArqFark errorDomain] code:ERROR_NOT_FOUND]) {
SETNSERROR([ArqRepo errorDomain], ERROR_NOT_FOUND, @"no head for bucketUUID %@", bucketUUID);
} else {
if (error != NULL) {
*error = myError;
}
}
return nil;
}
NSString *sha1 = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
BOOL stretch = NO;
if ([sha1 length] > 40) {
stretch = [sha1 characterAtIndex:40] == 'Y';
sha1 = [sha1 substringToIndex:40];
}
return [[[BlobKey alloc] initWithSHA1:sha1 storageType:StorageTypeS3 stretchEncryptionKey:stretch compressed:NO] autorelease];
}
- (Commit *)commitForBlobKey:(BlobKey *)commitBlobKey error:(NSError **)error {
NSError *myError = nil;
ServerBlob *sb = [treesPackSet newServerBlobForSHA1:[commitBlobKey sha1] error:&myError];
if (sb == nil) {
if ([myError isErrorWithDomain:[ArqPackSet errorDomain] code:ERROR_NOT_FOUND]) {
HSLogDebug(@"commit %@ not found in pack set %@", commitBlobKey, [treesPackSet packSetName]);
SETNSERROR([ArqRepo errorDomain], ERROR_NOT_FOUND, @"commit %@ not found", commitBlobKey);
} else {
HSLogError(@"commit %@ not found for: %@", commitBlobKey, [myError localizedDescription]);
if (error != NULL) {
*error = myError;
}
}
return nil;
}
NSData *encrypted = [sb slurp:error];
[sb release];
if (encrypted == nil) {
return nil;
}
NSData *data = [encrypted decryptWithCryptoKey:([commitBlobKey stretchEncryptionKey] ? stretchedCryptoKey : cryptoKey) error:error];
if (data == nil) {
return nil;
}
DataInputStream *dis = [[DataInputStream alloc] initWithData:data];
BufferedInputStream *bis = [[BufferedInputStream alloc] initWithUnderlyingStream:dis];
Commit *commit = [[[Commit alloc] initWithBufferedInputStream:bis error:error] autorelease];
[bis release];
[dis release];
return commit;
}
// Returns NO if commit not found:
- (Tree *)treeForBlobKey:(BlobKey *)blobKey error:(NSError **)error {
NSError *myError = nil;
ServerBlob *sb = [treesPackSet newServerBlobForSHA1:[blobKey sha1] error:&myError];
if (sb == nil) {
if ([myError isErrorWithDomain:[ArqPackSet errorDomain] code:ERROR_NOT_FOUND]) {
HSLogDebug(@"tree %@ not found in pack set %@", blobKey, [treesPackSet packSetName]);
SETNSERROR([ArqRepo errorDomain], ERROR_NOT_FOUND, @"tree %@ not found", blobKey);
} else {
HSLogError(@"error reading tree %@: %@", blobKey, [myError localizedDescription]);
if (error != NULL) {
*error = myError;
}
}
return nil;
}
NSData *encrypted = [sb slurp:error];
[sb release];
if (encrypted == nil) {
return nil;
}
NSData *data = [encrypted decryptWithCryptoKey:([blobKey stretchEncryptionKey] ? stretchedCryptoKey : cryptoKey) error:error];
if (data == nil) {
return nil;
}
if ([blobKey compressed]) {
data = [data gzipInflate];
}
DataInputStream *dis = [[DataInputStream alloc] initWithData:data];
BufferedInputStream *bis = [[BufferedInputStream alloc] initWithUnderlyingStream:dis];
Tree *tree = [[[Tree alloc] initWithBufferedInputStream:bis error:error] autorelease];
[bis release];
[dis release];
return tree;
}
- (NSData *)blobDataForBlobKey:(BlobKey *)treeBlobKey error:(NSError **)error {
ServerBlob *sb = [self newServerBlobForBlobKey:treeBlobKey error:error];
if (sb == nil) {
return nil;
}
NSData *data = [sb slurp:error];
[sb release];
return data;
}
- (ServerBlob *)newServerBlobForBlobKey:(BlobKey *)theBlobKey error:(NSError **)error {
NSError *myError = nil;
ServerBlob *sb = [blobsPackSet newServerBlobForSHA1:[theBlobKey sha1] error:&myError];
if (sb == nil) {
if ([myError isErrorWithDomain:[ArqPackSet errorDomain] code:ERROR_NOT_FOUND]) {
HSLogTrace(@"%@ not found in pack set %@; looking in S3", theBlobKey, [blobsPackSet packSetName]);
sb = [arqFark newServerBlobForSHA1:[theBlobKey sha1] error:&myError];
if (sb == nil) {
if ([myError isErrorWithDomain:[ArqFark errorDomain] code:ERROR_NOT_FOUND]) {
SETNSERROR([ArqRepo errorDomain], ERROR_NOT_FOUND, @"object %@ not found", theBlobKey);
} else {
if (error != NULL) {
*error = myError;
}
}
}
} else {
HSLogError(@"error trying to read from pack set: %@", [myError localizedDescription]);
if (error != NULL) {
*error = myError;
}
}
}
if (sb == nil) {
return nil;
}
NSString *mimeType = [sb mimeType];
NSString *downloadName = [sb downloadName];
NSData *encrypted = [sb slurp:error];
[sb release];
sb = nil;
if (encrypted == nil) {
return nil;
}
NSData *data = [encrypted decryptWithCryptoKey:([theBlobKey stretchEncryptionKey] ? stretchedCryptoKey : cryptoKey) error:error];
if (data == nil) {
return nil;
}
id <InputStream> blobIS = [[DataInputStream alloc] initWithData:data];
sb = [[ServerBlob alloc] initWithInputStream:blobIS mimeType:mimeType downloadName:downloadName];
[blobIS release];
return sb;
}
- (BOOL)containsPackedBlob:(BOOL *)contains forBlobKey:(BlobKey *)theBlobKey packSetName:(NSString **)packSetName packSHA1:(NSString **)packSHA1 error:(NSError **)error {
if (![blobsPackSet containsBlob:contains forSHA1:[theBlobKey sha1] packSHA1:packSHA1 error:error]) {
return NO;
}
if (*contains) {
*packSetName = [blobsPackSet packSetName];
return YES;
}
if (![treesPackSet containsBlob:contains forSHA1:[theBlobKey sha1] packSHA1:packSHA1 error:error]) {
return NO;
}
if (*contains) {
*packSetName = [treesPackSet packSetName];
}
return YES;
}
#pragma mark NSObject
- (NSString *)description {
return [NSString stringWithFormat:@"<ArqRepo: bucketUUID=%@>", bucketUUID];
}
@end

View file

@ -1,47 +1,18 @@
/*
Copyright (c) 2009, 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.
*/
//
// ArqRestoreCommand.h
// arq_restore
//
// Created by Stefan Reitshamer on 7/25/14.
//
//
@class Target;
@class S3Service;
@interface ArqRestoreCommand : NSObject {
NSString *accessKey;
NSString *secretKey;
NSString *encryptionPassword;
S3Service *s3;
NSString *path;
NSString *commitSHA1;
BOOL vFlag;
Target *target;
}
- (BOOL)readArgc:(int)argc argv:(const char **)argv;
- (BOOL)execute:(NSError **)error;
- (NSString *)errorDomain;
- (BOOL)executeWithArgc:(int)argc argv:(const char **)argv error:(NSError **)error;
@end

View file

@ -1,305 +1,350 @@
/*
Copyright (c) 2009, 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.
*/
//
// ArqRestoreCommand.m
// arq_restore
//
// Created by Stefan Reitshamer on 7/25/14.
//
//
#import "ArqRestoreCommand.h"
#import "SetNSError.h"
#import "S3AuthorizationProvider.h"
#import "S3Service.h"
#import "RegexKitLite.h"
#import "DictNode.h"
#import "HTTP.h"
#import "Restorer.h"
#import "NSErrorCodes.h"
#import "NSError_extra.h"
#import "UserAndComputer.h"
#import "ArqSalt.h"
#import "ArqRepo.h"
#import "Target.h"
#import "AWSRegion.h"
#import "BackupSet.h"
#import "ReflogPrinter.h"
#import "NSData-Encrypt.h"
#import "CryptoKey.h"
#import "S3Service.h"
#import "UserAndComputer.h"
#import "Bucket.h"
#import "Repo.h"
#define BUCKET_PLIST_SALT "BucketPL"
@interface ArqRestoreCommand (internal)
- (BOOL)printArqFolders:(NSError **)error;
- (BOOL)processPath:(NSError **)error;
- (BOOL)validateS3Keys:(NSError **)error;
@end
@implementation ArqRestoreCommand
- (id)init {
if (self = [super init]) {
char *theAccessKey = getenv("ARQ_ACCESS_KEY");
if (theAccessKey != NULL) {
accessKey = [[NSString alloc] initWithUTF8String:theAccessKey];
}
char *theSecretKey = getenv("ARQ_SECRET_KEY");
if (theSecretKey != NULL) {
secretKey = [[NSString alloc] initWithUTF8String:theSecretKey];
}
char *theEncryptionPassword = getenv("ARQ_ENCRYPTION_PASSWORD");
if (theEncryptionPassword != NULL) {
encryptionPassword = [[NSString alloc] initWithUTF8String:theEncryptionPassword];
}
if (accessKey != nil && secretKey != nil) {
S3AuthorizationProvider *sap = [[S3AuthorizationProvider alloc] initWithAccessKey:accessKey secretKey:secretKey];
s3 = [[S3Service alloc] initWithS3AuthorizationProvider:sap useSSL:NO retryOnTransientError:YES];
[sap release];
}
}
return self;
}
- (void)dealloc {
[accessKey release];
[secretKey release];
[encryptionPassword release];
[s3 release];
[path release];
[commitSHA1 release];
[target release];
[super dealloc];
}
- (BOOL)readArgc:(int)argc argv:(const char **)argv {
for (int i = 1; i < argc; i++) {
if (*argv[i] == '-') {
if (!strcmp(argv[i], "-l")) {
if (argc <= i+1) {
fprintf(stderr, "missing log_level argument (error,warn,info,debug or trace)\n");
return NO;
}
i++;
NSString *level = [NSString stringWithUTF8String:argv[i]];
setHSLogLevel(hsLogLevelForName(level));
} else if (!strcmp(argv[i], "-v")) {
printf("%s version 20-Aug-2012\n", argv[0]);
exit(0);
} else {
fprintf(stderr, "unknown option %s\n", argv[i]);
return NO;
}
} else if (path == nil) {
path = [[NSString alloc] initWithUTF8String:argv[i]];
} else if (commitSHA1 == nil) {
commitSHA1 = [[NSString alloc] initWithUTF8String:argv[i]];
} else {
fprintf(stderr, "warning: ignoring argument '%s'\n", argv[i]);
}
- (NSString *)errorDomain {
return @"ArqRestoreCommandErrorDomain";
}
- (BOOL)executeWithArgc:(int)argc argv:(const char **)argv error:(NSError **)error {
NSMutableArray *args = [NSMutableArray array];
for (int i = 0; i < argc; i++) {
[args addObject:[[[NSString alloc] initWithBytes:argv[i] length:strlen(argv[i]) encoding:NSUTF8StringEncoding] autorelease]];
}
if ([args count] < 2) {
SETNSERROR([self errorDomain], ERROR_USAGE, @"missing arguments");
return NO;
}
int index = 1;
if ([[args objectAtIndex:1] isEqualToString:@"-l"]) {
if ([args count] < 4) {
SETNSERROR([self errorDomain], ERROR_USAGE, @"missing arguments");
return NO;
}
setHSLogLevel(hsLogLevelForName([args objectAtIndex:2]));
index += 2;
}
NSString *cmd = [args objectAtIndex:index];
int targetParamsIndex = index + 1;
if ([cmd isEqualToString:@"listcomputers"]) {
// Valid command, but no additional args.
} else if ([cmd isEqualToString:@"listfolders"]) {
if ([args count] < 4) {
SETNSERROR([self errorDomain], ERROR_USAGE, @"missing arguments for listfolders command");
return NO;
}
targetParamsIndex = 4;
} else if ([cmd isEqualToString:@"restore"]) {
if ([args count] < 5) {
SETNSERROR([self errorDomain], ERROR_USAGE, @"missing arguments");
return NO;
}
targetParamsIndex = 5;
} else {
SETNSERROR([self errorDomain], ERROR_USAGE, @"unknown command: %@", cmd);
return NO;
}
if (targetParamsIndex >= argc) {
SETNSERROR([self errorDomain], ERROR_USAGE, @"missing target type params");
return NO;
}
target = [[self targetForParams:[args subarrayWithRange:NSMakeRange(targetParamsIndex, argc - targetParamsIndex)] error:error] retain];
if (target == nil) {
return NO;
}
if ([cmd isEqualToString:@"listcomputers"]) {
if (![self listComputers:error]) {
return NO;
}
} else if ([cmd isEqualToString:@"listfolders"]) {
if (![self listBucketsForComputerUUID:[args objectAtIndex:2] encryptionPassword:[args objectAtIndex:3] error:error]) {
return NO;
}
} else if ([cmd isEqualToString:@"restore"]) {
if (![self restoreComputerUUID:[args objectAtIndex:2] bucketUUID:[args objectAtIndex:4] encryptionPassword:[args objectAtIndex:3] error:error]) {
return NO;
}
} else {
SETNSERROR([self errorDomain], ERROR_USAGE, @"unknown command: %@", cmd);
return NO;
}
return YES;
}
- (BOOL)execute:(NSError **)error {
BOOL ret = YES;
if (path == nil) {
ret = [self printArqFolders:error];
#pragma mark internal
- (Target *)targetForParams:(NSArray *)theParams error:(NSError **)error {
NSString *theTargetType = [theParams objectAtIndex:0];
Target *ret = nil;
if ([theTargetType isEqualToString:@"aws"]) {
if ([theParams count] != 4) {
SETNSERROR([self errorDomain], ERROR_USAGE, @"invalid aws parameters");
return nil;
}
NSString *theAccessKey = [theParams objectAtIndex:1];
NSString *theSecretKey = [theParams objectAtIndex:2];
NSString *theBucketName = [theParams objectAtIndex:3];
AWSRegion *awsRegion = [self awsRegionForAccessKey:theAccessKey secretKey:theSecretKey bucketName:theBucketName error:error];
if (awsRegion == nil) {
return nil;
}
NSURL *s3Endpoint = [awsRegion s3EndpointWithSSL:YES];
int port = [[s3Endpoint port] intValue];
NSString *portString = @"";
if (port != 0) {
portString = [NSString stringWithFormat:@":%d", port];
}
NSURL *targetEndpoint = [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@@%@%@/%@", [s3Endpoint scheme], theAccessKey, [s3Endpoint host], portString, theBucketName]];
ret = [[[Target alloc] initWithEndpoint:targetEndpoint secret:theSecretKey passphrase:nil] autorelease];
} else if ([theTargetType isEqualToString:@"sftp"]) {
if ([theParams count] != 6 && [theParams count] != 7) {
SETNSERROR([self errorDomain], ERROR_USAGE, @"invalid sftp parameters");
return nil;
}
NSString *hostname = [theParams objectAtIndex:1];
int port = [[theParams objectAtIndex:2] intValue];
NSString *path = [theParams objectAtIndex:3];
NSString *username = [theParams objectAtIndex:4];
NSString *secret = [theParams objectAtIndex:5];
NSString *keyfilePassphrase = [theParams count] > 6 ? [theParams objectAtIndex:6] : nil;
if (![path hasPrefix:@"/"]) {
path = [@"/~/" stringByAppendingString:path];
}
NSString *escapedPath = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)path, NULL, (CFStringRef)@"!*'();:@&=+$,?%#[]", kCFStringEncodingUTF8);
NSString *escapedUsername = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)username, NULL, (CFStringRef)@"!*'();:@&=+$,?%#[]", kCFStringEncodingUTF8);
NSURL *endpoint = [NSURL URLWithString:[NSString stringWithFormat:@"sftp://%@@%@:%d%@", escapedUsername, hostname, port, escapedPath]];
ret = [[[Target alloc] initWithEndpoint:endpoint secret:secret passphrase:keyfilePassphrase] autorelease];
} else if ([theTargetType isEqualToString:@"greenqloud"]
|| [theTargetType isEqualToString:@"dreamobjects"]
|| [theTargetType isEqualToString:@"googlecloudstorage"]
|| [theTargetType isEqualToString:@"s3compatible"]) {
if ([theParams count] != 4) {
SETNSERROR([self errorDomain], ERROR_USAGE, @"invalid %@ parameters", theTargetType);
return nil;
}
NSString *theAccessKey = [theParams objectAtIndex:1];
NSString *theSecretKey = [theParams objectAtIndex:2];
NSString *theBucketName = [theParams objectAtIndex:3];
NSString *theHostname = nil;
if ([theTargetType isEqualToString:@"greenqloud"]) {
theHostname = @"s.greenqloud.com";
} else if ([theTargetType isEqualToString:@"dreamobjects"]) {
theHostname = @"objects.dreamhost.com";
} else if ([theTargetType isEqualToString:@"googlecloudstorage"]) {
theHostname = @"storage.googleapis.com";
} else {
SETNSERROR([self errorDomain], ERROR_USAGE, @"no hostname for target type: %@", theTargetType);
return nil;
}
NSURL *endpoint = [NSURL URLWithString:[NSString stringWithFormat:@"https://%@@%@/%@", theAccessKey, theHostname, theBucketName]];
ret = [[[Target alloc] initWithEndpoint:endpoint secret:theSecretKey passphrase:nil] autorelease];
} else if ([theTargetType isEqualToString:@"googledrive"]) {
if ([theParams count] != 3) {
SETNSERROR([self errorDomain], ERROR_USAGE, @"invalid googledrive parameters");
return nil;
}
NSString *theRefreshToken = [theParams objectAtIndex:1];
NSString *thePath = [theParams objectAtIndex:2];
NSString *escapedPath = (NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)thePath, CFSTR("/"), CFSTR("@?=&+"), kCFStringEncodingUTF8);
[escapedPath autorelease];
NSURL *endpoint = [NSURL URLWithString:[NSString stringWithFormat:@"googledrive://unknown_email_address@www.googleapis.com%@", escapedPath]];
ret = [[[Target alloc] initWithEndpoint:endpoint secret:theRefreshToken passphrase:nil] autorelease];
} else {
ret = [self processPath:error];
}
SETNSERROR([self errorDomain], ERROR_USAGE, @"unknown target type: %@", theTargetType);
return nil;
}
return ret;
}
@end
@implementation ArqRestoreCommand (internal)
- (BOOL)printArqFolders:(NSError **)error {
if (![self validateS3Keys:error]) {
- (AWSRegion *)awsRegionForAccessKey:(NSString *)theAccessKey secretKey:(NSString *)theSecretKey bucketName:(NSString *)theBucketName error:(NSError **)error {
return nil;
}
- (BOOL)listComputers:(NSError **)error {
NSArray *expandedTargetList = [self expandedTargetList:error];
if (expandedTargetList == nil) {
return NO;
}
NSArray *backupSets = [BackupSet allBackupSetsForAccessKeyID:accessKey secretAccessKey:secretKey error:error];
if (backupSets == nil) {
NSMutableArray *ret = [NSMutableArray array];
for (Target *theTarget in expandedTargetList) {
NSError *myError = nil;
HSLogDebug(@"getting backup sets for %@", theTarget);
NSArray *backupSets = [BackupSet allBackupSetsForTarget:theTarget targetConnectionDelegate:nil error:&myError];
if (backupSets == nil) {
if ([myError isErrorWithDomain:[S3Service errorDomain] code:S3SERVICE_ERROR_AMAZON_ERROR] && [[[myError userInfo] objectForKey:@"HTTPStatusCode"] intValue] == 403) {
HSLogError(@"access denied getting backup sets for %@", theTarget);
} else {
HSLogError(@"error getting backup sets for %@: %@", theTarget, myError);
SETERRORFROMMYERROR;
return nil;
}
} else {
printf("target: %s\n", [[theTarget endpointDisplayName] UTF8String]);
for (BackupSet *backupSet in backupSets) {
printf("\tcomputer %s\n", [[backupSet computerUUID] UTF8String]);
printf("\t\t%s (%s)\n", [[[backupSet userAndComputer] computerName] UTF8String], [[[backupSet userAndComputer] userName] UTF8String]);
}
}
}
return ret;
}
- (NSArray *)expandedTargetList:(NSError **)error {
NSMutableArray *expandedTargetList = [NSMutableArray arrayWithObject:target];
if ([target targetType] == kTargetAWS
|| [target targetType] == kTargetDreamObjects
|| [target targetType] == kTargetGoogleCloudStorage
|| [target targetType] == kTargetGreenQloud
|| [target targetType] == kTargetS3Compatible) {
NSError *myError = nil;
NSArray *targets = [self expandedTargetsForS3Target:target error:&myError];
if (targets == nil) {
HSLogError(@"failed to expand target list for %@: %@", target, myError);
} else {
[expandedTargetList setArray:targets];
HSLogDebug(@"expandedTargetList is now: %@", expandedTargetList);
}
}
return expandedTargetList;
}
- (NSArray *)expandedTargetsForS3Target:(Target *)theTarget error:(NSError **)error {
S3Service *s3 = [theTarget s3:error];
if (s3 == nil) {
return nil;
}
NSArray *s3BucketNames = [s3 s3BucketNamesWithTargetConnectionDelegate:nil error:error];
if (s3BucketNames == nil) {
return nil;
}
HSLogDebug(@"s3BucketNames for %@: %@", theTarget, s3BucketNames);
NSURL *originalEndpoint = [theTarget endpoint];
NSMutableArray *ret = [NSMutableArray array];
for (NSString *s3BucketName in s3BucketNames) {
NSURL *endpoint = nil;
if ([theTarget targetType] == kTargetAWS) {
NSString *location = [s3 locationOfS3Bucket:s3BucketName targetConnectionDelegate:nil error:error];
if (location == nil) {
return nil;
}
AWSRegion *awsRegion = [AWSRegion regionWithLocation:location];
HSLogDebug(@"awsRegion for s3BucketName %@: %@", s3BucketName, location);
NSURL *s3Endpoint = [awsRegion s3EndpointWithSSL:YES];
HSLogDebug(@"s3Endpoint: %@", s3Endpoint);
endpoint = [NSURL URLWithString:[NSString stringWithFormat:@"https://%@@%@/%@", [originalEndpoint user], [s3Endpoint host], s3BucketName]];
} else {
endpoint = [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@@%@/%@", [originalEndpoint scheme], [originalEndpoint user], [originalEndpoint host], s3BucketName]];
}
HSLogDebug(@"endpoint: %@", endpoint);
Target *theTarget = [[[Target alloc] initWithEndpoint:endpoint secret:[theTarget secret:NULL] passphrase:[theTarget passphrase:NULL]] autorelease];
[ret addObject:theTarget];
}
return ret;
}
- (BOOL)listBucketsForComputerUUID:(NSString *)theComputerUUID encryptionPassword:(NSString *)theEncryptionPassword error:(NSError **)error {
NSArray *buckets = [Bucket bucketsWithTarget:target computerUUID:theComputerUUID encryptionPassword:theEncryptionPassword targetConnectionDelegate:nil error:error];
if (buckets == 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]);
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 ([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;
}
// Decrypt the plist if necessary:
unsigned long length = 9;
if ([data length] < length) {
length = [data length];
}
if (length >= 9 && !strncmp([data bytes], "encrypted", length)) {
NSData *encryptedData = [data subdataWithRange:NSMakeRange(9, [data length] - 9)];
CryptoKey *cryptoKey = [[[CryptoKey alloc] initWithPassword:encryptionPassword salt:[NSData dataWithBytes:BUCKET_PLIST_SALT length:8] error:error] autorelease];
if (cryptoKey == nil) {
return NO;
}
data = [encryptedData decryptWithCryptoKey:cryptoKey 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(" reflog command: arq_restore %s reflog\n", [uuidPath UTF8String]);
printf(" restore command: arq_restore %s\n", [uuidPath UTF8String]);
printf("\n");
}
printf("target %s\n", [[target endpointDisplayName] UTF8String]);
printf("computer %s\n", [theComputerUUID UTF8String]);
for (Bucket *bucket in buckets) {
printf("\tfolder %s\n", [[bucket localPath] UTF8String]);
printf("\t\tuuid %s\n", [[bucket bucketUUID] UTF8String]);
}
return YES;
}
- (BOOL)processPath:(NSError **)error {
if (![self validateS3Keys:error]) {
- (BOOL)restoreComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID encryptionPassword:(NSString *)theEncryptionPassword error:(NSError **)error {
Bucket *myBucket = nil;
NSArray *expandedTargetList = [self expandedTargetList:error];
if (expandedTargetList == nil) {
return NO;
}
if (encryptionPassword == nil) {
SETNSERROR(@"ArqErrorDomain", -1, @"missing ARQ_ENCRYPTION_PASSWORD environment variable");
return NO;
}
NSString *pattern = @"^/([^/]+)/([^/]+)/buckets/([^/]+)";
NSRange s3BucketNameRange = [path rangeOfRegex:pattern capture:1];
NSRange computerUUIDRange = [path rangeOfRegex:pattern capture:2];
NSRange bucketUUIDRange = [path rangeOfRegex:pattern capture:3];
if (s3BucketNameRange.location == NSNotFound || computerUUIDRange.location == NSNotFound || bucketUUIDRange.location == NSNotFound) {
SETNSERROR(@"ArqErrorDomain", -1, @"invalid S3 path");
return NO;
}
NSString *s3BucketName = [path substringWithRange:s3BucketNameRange];
NSString *computerUUID = [path substringWithRange:computerUUIDRange];
NSString *bucketUUID = [path substringWithRange:bucketUUIDRange];
NSString *bucketName = @"(unknown)";
NSData *data = [s3 dataAtPath:path error:NULL];
if (data != nil) {
if (!strncmp([data bytes], "encrypted", 9)) {
data = [data subdataWithRange:NSMakeRange(9, [data length] - 9)];
CryptoKey *cryptoKey = [[[CryptoKey alloc] initWithPassword:encryptionPassword salt:[NSData dataWithBytes:BUCKET_PLIST_SALT length:8] error:error] autorelease];
if (cryptoKey == nil) {
return NO;
}
data = [data decryptWithCryptoKey:cryptoKey error:error];
if (data == nil) {
HSLogError(@"failed to decrypt %@", path);
return NO;
}
}
DictNode *plist = [DictNode dictNodeWithXMLData:data error:NULL];
if (plist != nil) {
bucketName = [[plist stringNodeForKey:@"BucketName"] stringValue];
}
}
NSError *uacError = nil;
NSData *uacData = [s3 dataAtPath:[NSString stringWithFormat:@"/%@/%@/computerinfo", s3BucketName, computerUUID] error:&uacError];
UserAndComputer *uac = nil;
if (uacData != nil) {
uac = [[[UserAndComputer alloc] initWithXMLData:uacData error:&uacError] autorelease];
}
NSError *saltError = nil;
ArqSalt *arqSalt = [[[ArqSalt alloc] initWithAccessKeyID:accessKey secretAccessKey:secretKey s3BucketName:s3BucketName computerUUID:computerUUID] autorelease];
NSData *salt = [arqSalt salt:&saltError];
if (salt == nil) {
if ([saltError code] != ERROR_NOT_FOUND) {
if (error != NULL) {
*error = saltError;
}
for (Target *theTarget in expandedTargetList) {
NSArray *buckets = [Bucket bucketsWithTarget:theTarget computerUUID:theComputerUUID encryptionPassword:theEncryptionPassword targetConnectionDelegate:nil error:error];
if (buckets == nil) {
return NO;
}
for (Bucket *bucket in buckets) {
if ([[bucket bucketUUID] isEqualToString:theBucketUUID]) {
myBucket = bucket;
break;
}
}
if (myBucket != nil) {
break;
}
}
if (myBucket == nil) {
SETNSERROR([self errorDomain], ERROR_NOT_FOUND, @"folder %@ not found", theBucketUUID);
return NO;
}
ArqRepo *repo = [[[ArqRepo alloc] initWithS3Service:s3 s3BucketName:s3BucketName computerUUID:computerUUID bucketUUID:bucketUUID encryptionPassword:encryptionPassword salt:salt error:error] autorelease];
Repo *repo = [[[Repo alloc] initWithBucket:myBucket encryptionPassword:theEncryptionPassword targetUID:getuid() targetGID:getgid() loadExistingMutablePackFiles:NO targetConnectionDelegate:nil repoDelegate:nil error:error] autorelease];
if (repo == nil) {
return NO;
}
if ([commitSHA1 isEqualToString:@"reflog"]) {
printf("printing reflog for %s\n", [bucketName UTF8String]);
ReflogPrinter *printer = [[[ReflogPrinter alloc] initWithS3BucketName:s3BucketName computerUUID:computerUUID bucketUUID:bucketUUID s3:s3 repo:repo] autorelease];
if (![printer printReflog:error]) {
return NO;
}
} else {
printf("restoring %s from ", [bucketName UTF8String]);
if (uac != nil) {
printf("%s (%s)", [[uac computerName] UTF8String], [[uac userName] UTF8String]);
} else {
printf("(unknown computer)");
}
printf(" to %s/%s\n", [[[NSFileManager defaultManager] currentDirectoryPath] UTF8String], [bucketName UTF8String]);
Restorer *restorer = [[[Restorer alloc] initWithRepo:repo bucketName:bucketName commitSHA1:commitSHA1] autorelease];
if (![restorer restore:error]) {
return NO;
}
printf("restored files are in %s\n", [bucketName fileSystemRepresentation]);
NSDictionary *errorsByPath = [restorer errorsByPath];
if ([errorsByPath count] > 0) {
printf("Errors occurred:\n");
NSArray *sortedKeys = [[errorsByPath allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
for (NSString *key in sortedKeys) {
printf("%s\t\t%s\n", [key UTF8String], [[[errorsByPath objectForKey:key] localizedDescription] UTF8String]);
}
}
}
return YES;
}
- (BOOL)validateS3Keys:(NSError **)error {
if (accessKey == nil) {
SETNSERROR(@"ArqErrorDomain", -1, @"missing ARQ_ACCESS_KEY environment variable");
return NO;
}
if (secretKey == nil) {
SETNSERROR(@"ArqErrorDomain", -1, @"missing ARQ_SECRET_KEY environment variable");
BlobKey *commitBlobKey = [repo headBlobKey:error];
if (commitBlobKey == nil) {
return NO;
}
printf("target %s\n", [[[myBucket target] endpointDisplayName] UTF8String]);
printf("computer %s\n", [[myBucket computerUUID] UTF8String]);
printf("\nrestoring folder %s\n\n", [[myBucket localPath] UTF8String]);
return YES;
}
@end

View file

@ -1,25 +1,24 @@
//
// ArqSalt.h
// Arq
//
// Created by Stefan Reitshamer on 7/16/11.
// Copyright 2011 __MyCompanyName__. All rights reserved.
// Copyright 2011 Haystack Software. All rights reserved.
//
#import "TargetConnection.h"
@class AWSRegion;
@class Target;
@interface ArqSalt : NSObject {
NSString *accessKeyID;
NSString *secretAccessKey;
NSString *s3BucketName;
Target *target;
uid_t uid;
gid_t gid;
NSString *computerUUID;
NSString *localPath;
NSString *s3Path;
}
- (id)initWithAccessKeyID:(NSString *)theAccessKeyID
secretAccessKey:(NSString *)theSecretAccessKey
s3BucketName:(NSString *)theS3BucketName
computerUUID:(NSString *)theComputerUUID;
- (NSData *)salt:(NSError **)error;
- (id)initWithTarget:(Target *)theTarget
targetUID:(uid_t)theTargetUID
targetGID:(gid_t)theTargetGID
computerUUID:(NSString *)theComputerUUID;
- (NSData *)saltWithTargetConnectionDelegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (BOOL)saveSalt:(NSData *)theSalt targetConnectionDelegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (NSData *)createSaltWithTargetConnectionDelegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
@end

View file

@ -1,70 +1,77 @@
//
// ArqSalt.m
// Arq
//
// Created by Stefan Reitshamer on 7/16/11.
// Copyright 2011 __MyCompanyName__. All rights reserved.
// Copyright 2011 Haystack Software. All rights reserved.
//
#import "ArqSalt.h"
#import "S3AuthorizationProvider.h"
#import "S3Service.h"
#import "Blob.h"
#import "BlobACL.h"
#import "NSFileManager_extra.h"
#import "UserLibrary_Arq.h"
#import "Target.h"
#import "TargetConnection.h"
#import "Streams.h"
#define SALT_LENGTH (8)
@interface ArqSalt (internal)
- (NSData *)createRandomSalt;
@end
@implementation ArqSalt
- (id)initWithAccessKeyID:(NSString *)theAccessKeyID
secretAccessKey:(NSString *)theSecretAccessKey
s3BucketName:(NSString *)theS3BucketName
computerUUID:(NSString *)theComputerUUID {
- (id)initWithTarget:(Target *)theTarget
targetUID:(uid_t)theTargetUID
targetGID:(gid_t)theTargetGID
computerUUID:(NSString *)theComputerUUID {
if (self = [super init]) {
accessKeyID = [theAccessKeyID retain];
secretAccessKey = [theSecretAccessKey retain];
s3BucketName = [theS3BucketName retain];
target = [theTarget retain];
uid = theTargetUID;
gid = theTargetGID;
computerUUID = [theComputerUUID retain];
localPath = [[NSString alloc] initWithFormat:@"%@/Cache.noindex/%@/%@/salt.dat", [UserLibrary arqUserLibraryPath], s3BucketName, computerUUID];
s3Path = [[NSString alloc] initWithFormat:@"/%@/%@/salt", s3BucketName, computerUUID];
}
return self;
}
- (void)dealloc {
[accessKeyID release];
[secretAccessKey release];
[s3BucketName release];
[target release];
[computerUUID release];
[localPath release];
[s3Path release];
[super dealloc];
}
- (NSData *)salt:(NSError **)error {
NSData *ret = [NSData dataWithContentsOfFile:localPath options:NSUncachedRead error:error];
- (NSData *)saltWithTargetConnectionDelegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
NSData *ret = [NSData dataWithContentsOfFile:[self localPath] options:NSUncachedRead error:error];
if (ret == nil) {
S3AuthorizationProvider *sap = [[[S3AuthorizationProvider alloc] initWithAccessKey:accessKeyID secretKey:secretAccessKey] autorelease];
S3Service *s3 = [[[S3Service alloc] initWithS3AuthorizationProvider:sap useSSL:YES retryOnTransientError:NO] autorelease];
ret = [s3 dataAtPath:s3Path error:error];
if (ret == nil) {
return nil;
}
NSError *myError = nil;
if (![[NSFileManager defaultManager] ensureParentPathExistsForPath:localPath error:&myError]
|| ![ret writeToFile:localPath options:NSAtomicWrite error:&myError]) {
HSLogError(@"error caching salt data to %@: %@", localPath, myError);
}
id <TargetConnection> targetConnection = [target newConnection];
do {
ret = [targetConnection saltDataForComputerUUID:computerUUID delegate:theDelegate error:error];
if (ret != nil) {
NSError *myError = nil;
if (![[NSFileManager defaultManager] ensureParentPathExistsForPath:[self localPath] targetUID:uid targetGID:gid error:&myError]
|| ![Streams writeData:ret atomicallyToFile:[self localPath] targetUID:uid targetGID:gid bytesWritten:NULL error:&myError]) {
HSLogError(@"error caching salt data to %@: %@", [self localPath], myError);
}
}
} while(0);
[targetConnection release];
}
return ret;
}
@end
- (BOOL)saveSalt:(NSData *)theSalt targetConnectionDelegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
id <TargetConnection> targetConnection = [target newConnection];
BOOL ret = YES;
do {
ret = [targetConnection setSaltData:theSalt forComputerUUID:computerUUID delegate:theDelegate error:error]
&& [[NSFileManager defaultManager] ensureParentPathExistsForPath:[self localPath] targetUID:uid targetGID:gid error:error]
&& [Streams writeData:theSalt atomicallyToFile:[self localPath] targetUID:uid targetGID:gid bytesWritten:NULL error:error];
} while (0);
[targetConnection release];
return ret;
}
- (NSData *)createSaltWithTargetConnectionDelegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
NSData *theSalt = [self createRandomSalt];
if (![self saveSalt:theSalt targetConnectionDelegate:theDelegate error:error]) {
return nil;
}
return theSalt;
}
@implementation ArqSalt (internal)
#pragma mark internal
- (NSData *)createRandomSalt {
unsigned char buf[SALT_LENGTH];
for (NSUInteger i = 0; i < SALT_LENGTH; i++) {
@ -72,4 +79,7 @@
}
return [[[NSData alloc] initWithBytes:buf length:SALT_LENGTH] autorelease];
}
- (NSString *)localPath {
return [NSString stringWithFormat:@"%@/Cache.noindex/%@/%@/salt.dat", [UserLibrary arqUserLibraryPath], [target targetUUID], computerUUID];
}
@end

View file

@ -1,26 +0,0 @@
//
// ArqVerifyCommand.h
// arq_restore
//
// Created by Stefan Reitshamer on 6/17/10.
// Copyright 2010 __MyCompanyName__. All rights reserved.
//
@class S3Service;
@interface ArqVerifyCommand : NSObject {
NSString *accessKey;
NSString *secretKey;
NSString *encryptionPassword;
S3Service *s3;
NSSet *objectSHA1s;
BOOL verbose;
}
- (id)initWithAccessKey:(NSString *)theAccessKey secretKey:(NSString *)theSecretKey encryptionPassword:(NSString *)theEncryptionPassword;
- (void)setVerbose:(BOOL)isVerbose;
- (BOOL)verifyAll:(NSError **)error;
- (BOOL)verifyS3BucketName:(NSString *)s3BucketName error:(NSError **)error;
- (BOOL)verifyS3BucketName:(NSString *)s3BucketName computerUUID:(NSString *)computerUUID error:(NSError **)error;
- (BOOL)verifyS3BucketName:(NSString *)s3BucketName computerUUID:(NSString *)computerUUID bucketUUID:(NSString *)bucketUUID error:(NSError **)error;
@end

View file

@ -1,194 +0,0 @@
//
// ArqVerifyCommand.m
// arq_restore
//
// Created by Stefan Reitshamer on 6/17/10.
// Copyright 2010 __MyCompanyName__. All rights reserved.
//
#import "ArqVerifyCommand.h"
#import "S3AuthorizationProvider.h"
#import "S3Service.h"
#import "HTTP.h"
#import "RegexKitLite.h"
#import "BucketVerifier.h"
#import "NSError_extra.h"
#import "NSErrorCodes.h"
#import "ArqSalt.h"
#import "ArqRepo.h"
@interface ArqVerifyCommand (internal)
- (BOOL)loadObjectSHA1sForS3BucketName:(NSString *)s3BucketName computerUUID:(NSString *)computerUUID error:(NSError **)error;
@end
@implementation ArqVerifyCommand
- (id)initWithAccessKey:(NSString *)theAccessKey secretKey:(NSString *)theSecretKey encryptionPassword:(NSString *)theEncryptionPassword {
if (self = [super init]) {
accessKey = [theAccessKey retain];
secretKey = [theSecretKey retain];
encryptionPassword = [theEncryptionPassword retain];
S3AuthorizationProvider *sap = [[S3AuthorizationProvider alloc] initWithAccessKey:accessKey secretKey:secretKey];
s3 = [[S3Service alloc] initWithS3AuthorizationProvider:sap useSSL:NO retryOnTransientError:YES];
[sap release];
}
return self;
}
- (void)dealloc {
[accessKey release];
[secretKey release];
[encryptionPassword release];
[s3 release];
[super dealloc];
}
- (void)setVerbose:(BOOL)isVerbose {
verbose = isVerbose;
}
- (BOOL)verifyAll:(NSError **)error {
NSArray *s3BucketNames = [S3Service s3BucketNamesForAccessKeyID:accessKey];
for (NSString *s3BucketName in s3BucketNames) {
printf("s3bucket name: %s\n", [s3BucketName UTF8String]);
}
for (NSString *s3BucketName in s3BucketNames) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
BOOL ret = [self verifyS3BucketName:s3BucketName error:error];
if (error != NULL) {
[*error retain];
}
[pool drain];
if (error != NULL) {
[*error autorelease];
}
if (!ret) {
return NO;
}
}
return YES;
}
- (BOOL)verifyS3BucketName:(NSString *)s3BucketName error:(NSError **)error {
printf("verifying s3Bucket %s\n", [s3BucketName UTF8String]);
NSString *computerUUIDPrefix = [NSString stringWithFormat:@"/%@/", s3BucketName];
NSError *myError = nil;
NSArray *computerUUIDs = [s3 commonPrefixesForPathPrefix:computerUUIDPrefix delimiter:@"/" error:&myError];
if (computerUUIDs == nil) {
if ([myError isErrorWithDomain:[S3Service errorDomain] code:ERROR_NOT_FOUND]) {
// Skip.
printf("no computer UUIDs found in bucket %s\n", [s3BucketName UTF8String]);
return YES;
} else {
if (error != NULL) {
*error = myError;
}
return NO;
}
}
for (NSString *computerUUID in computerUUIDs) {
printf("found computer UUID %s\n", [computerUUID UTF8String]);
}
for (NSString *computerUUID in computerUUIDs) {
if (![self verifyS3BucketName:s3BucketName computerUUID:computerUUID error:error]) {
return NO;
}
}
return YES;
}
- (BOOL)verifyS3BucketName:(NSString *)s3BucketName computerUUID:(NSString *)computerUUID error:(NSError **)error {
printf("\nverifying computerUUID %s s3Bucket %s\n", [computerUUID UTF8String], [s3BucketName UTF8String]);
NSString *computerBucketsPrefix = [NSString stringWithFormat:@"/%@/%@/buckets", s3BucketName, computerUUID];
NSArray *s3BucketUUIDPaths = [s3 pathsWithPrefix:computerBucketsPrefix error:error];
if (s3BucketUUIDPaths == nil) {
return NO;
}
NSMutableArray *bucketUUIDs = [NSMutableArray array];
for (NSString *s3BucketUUIDPath in s3BucketUUIDPaths) {
NSString *bucketUUID = [s3BucketUUIDPath lastPathComponent];
printf("found bucket UUID %s\n", [bucketUUID UTF8String]);
[bucketUUIDs addObject:bucketUUID];
}
[objectSHA1s release];
objectSHA1s = nil;
if (![self loadObjectSHA1sForS3BucketName:s3BucketName computerUUID:computerUUID error:error]) {
return NO;
}
BOOL ret = YES;
NSAutoreleasePool *pool = nil;
for (NSString *bucketUUID in bucketUUIDs) {
[pool release];
pool = [[NSAutoreleasePool alloc] init];
if (![self verifyS3BucketName:s3BucketName computerUUID:computerUUID bucketUUID:bucketUUID error:error]) {
ret = NO;
break;
}
}
if (!ret && error != NULL) {
[*error retain];
}
[pool drain];
if (!ret && error != NULL) {
[*error autorelease];
}
return ret;
}
- (BOOL)verifyS3BucketName:(NSString *)s3BucketName computerUUID:(NSString *)computerUUID bucketUUID:(NSString *)bucketUUID error:(NSError **)error {
if (objectSHA1s == nil) {
if (![self loadObjectSHA1sForS3BucketName:s3BucketName computerUUID:computerUUID error:error]) {
return NO;
}
}
NSError *saltError = nil;
ArqSalt *arqSalt = [[[ArqSalt alloc] initWithAccessKeyID:accessKey secretAccessKey:secretKey s3BucketName:s3BucketName computerUUID:computerUUID] autorelease];
NSData *salt = [arqSalt salt:&saltError];
if (salt == nil) {
if ([saltError code] != ERROR_NOT_FOUND) {
if (error != NULL) {
*error = saltError;
}
return NO;
}
}
ArqRepo *repo = [[[ArqRepo alloc] initWithS3Service:s3
s3BucketName:s3BucketName
computerUUID:computerUUID
bucketUUID:bucketUUID
encryptionPassword:encryptionPassword
salt:salt
error:error] autorelease];
if (repo == nil) {
return NO;
}
printf("\nverifying bucketUUID %s computerUUID %s s3Bucket %s\n", [bucketUUID UTF8String], [computerUUID UTF8String], [s3BucketName UTF8String]);
BucketVerifier *bucketVerifier = [[[BucketVerifier alloc] initWithS3Service:s3
s3BucketName:s3BucketName
computerUUID:computerUUID
bucketUUID:bucketUUID
s3ObjectSHA1s:objectSHA1s
verbose:verbose
repo:repo] autorelease];
if (![bucketVerifier verify:error]) {
return NO;
}
return YES;
}
@end
@implementation ArqVerifyCommand (internal)
- (BOOL)loadObjectSHA1sForS3BucketName:(NSString *)s3BucketName computerUUID:(NSString *)computerUUID error:(NSError **)error {
NSMutableSet *theObjectSHA1s = [NSMutableSet set];
NSString *objectsPrefix = [NSString stringWithFormat:@"/%@/%@/objects", s3BucketName, computerUUID];
printf("loading S3 object SHA1s with prefix %s\n", [objectsPrefix UTF8String]);
NSArray *objectPaths = [s3 pathsWithPrefix:objectsPrefix error:error];
if (objectPaths == nil) {
return NO;
}
for (NSString *objectPath in objectPaths) {
[theObjectSHA1s addObject:[objectPath lastPathComponent]];
}
objectSHA1s = [theObjectSHA1s retain];
printf("loaded %u object SHA1s with prefix %s\n", [objectSHA1s count], [objectsPrefix UTF8String]);
return YES;
}
@end

View file

@ -1,53 +1,30 @@
/*
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.
*/
//
// BackupSet.h
// Arq
//
// Created by Stefan Reitshamer on 4/11/11.
// Copyright 2011 Haystack Software. All rights reserved.
//
@class UserAndComputer;
@class AppConfig;
@class Target;
@protocol TargetConnectionDelegate;
@interface BackupSet : NSObject {
NSString *accessKeyID;
NSString *secretAccessKey;
NSString *s3BucketName;
Target *target;
NSString *computerUUID;
UserAndComputer *uac;
}
+ (NSArray *)allBackupSetsForAccessKeyID:(NSString *)theAccessKeyID secretAccessKey:(NSString *)theSecretAccessKey error:(NSError **)error;
+ (NSArray *)allBackupSetsForTarget:(Target *)theTarget targetConnectionDelegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (id)initWithAccessKeyID:(NSString *)theAccessKeyID
secretAccessKey:(NSString *)theSecretAccessKey
s3BucketName:(NSString *)theS3BucketName
computerUUID:(NSString *)theComputerUUID
userAndComputer:(UserAndComputer *)theUAC;
- (NSString *)s3BucketName;
- (id)initWithTarget:(Target *)theTarget
computerUUID:(NSString *)theComputerUUID
userAndComputer:(UserAndComputer *)theUAC;
- (NSString *)errorDomain;
- (Target *)target;
- (NSString *)computerUUID;
- (UserAndComputer *)userAndComputer;
@end

View file

@ -1,87 +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.
*/
//
// BackupSet.m
// Arq
//
// Created by Stefan Reitshamer on 4/11/11.
// Copyright 2011 Haystack Software. All rights reserved.
//
#import "BackupSet.h"
#import "S3AuthorizationProvider.h"
#import "S3Service.h"
#import "GlacierAuthorizationProvider.h"
#import "GlacierService.h"
#import "UserAndComputer.h"
#import "NSErrorCodes.h"
#import "NSData-Encrypt.h"
#import "S3DeleteReceiver.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"
#import "S3Region.h"
#import "AWSRegion.h"
#import "Bucket.h"
#import "Target.h"
#import "TargetConnection.h"
#import "Repo.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) {
+ (NSArray *)allBackupSetsForTarget:(Target *)theTarget targetConnectionDelegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error {
id <TargetConnection> targetConnection = [[theTarget newConnection] autorelease];
NSArray *theComputerUUIDs = [targetConnection computerUUIDsWithDelegate:theDelegate error:error];
if (theComputerUUIDs == 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
&& [theS3BucketName rangeOfString:@"comhaystacksoftwarearq"].location == NSNotFound) {
HSLogDebug(@"skipping bucket %@", theS3BucketName);
for (NSString *theComputerUUID in theComputerUUIDs) {
NSError *uacError = nil;
NSData *uacData = [targetConnection computerInfoForComputerUUID:theComputerUUID delegate:theDelegate error:&uacError];
if (uacData == nil) {
HSLogWarn(@"unable to read %@ (skipping): %@", theComputerUUID, [uacError localizedDescription]);
} 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];
UserAndComputer *uac = [[[UserAndComputer alloc] initWithXMLData:uacData error:&uacError] autorelease];
if (uac == nil) {
HSLogError(@"error parsing UserAndComputer data %@: %@", theComputerUUID, uacError);
} else {
BackupSet *backupSet = [[[BackupSet alloc] initWithTarget:theTarget
computerUUID:theComputerUUID
userAndComputer:uac] autorelease];
[ret addObject:backupSet];
}
}
@ -91,30 +57,27 @@
return ret;
}
- (id)initWithAccessKeyID:(NSString *)theAccessKeyID
secretAccessKey:(NSString *)theSecretAccessKey
s3BucketName:(NSString *)theS3BucketName
computerUUID:(NSString *)theComputerUUID
userAndComputer:(UserAndComputer *)theUAC {
- (id)initWithTarget:(Target *)theTarget
computerUUID:(NSString *)theComputerUUID
userAndComputer:(UserAndComputer *)theUAC {
if (self = [super init]) {
accessKeyID = [theAccessKeyID retain];
secretAccessKey = [theSecretAccessKey retain];
s3BucketName = [theS3BucketName retain];
target = [theTarget retain];
computerUUID = [theComputerUUID retain];
uac = [theUAC retain];
}
return self;
}
- (void)dealloc {
[accessKeyID release];
[secretAccessKey release];
[s3BucketName release];
[target release];
[computerUUID release];
[uac release];
[super dealloc];
}
- (NSString *)s3BucketName {
return s3BucketName;
- (NSString *)errorDomain {
return @"BackupSetErrorDomain";
}
- (Target *)target {
return target;
}
- (NSString *)computerUUID {
return computerUUID;
@ -126,11 +89,10 @@
#pragma mark NSObject
- (NSString *)description {
NSString *bucketRegion = [[S3Region s3RegionForBucketName:s3BucketName] displayName];
if (uac != nil) {
return [NSString stringWithFormat:@"%@ (%@) : %@ (%@)", [uac computerName], [uac userName], bucketRegion, computerUUID];
return [NSString stringWithFormat:@"%@ : %@ (%@)", [uac computerName], [uac userName], computerUUID];
}
return [NSString stringWithFormat:@"unknown computer : %@ (%@)", bucketRegion, computerUUID];
return [NSString stringWithFormat:@"unknown computer : %@", computerUUID];
}
@end

48
BaseTargetConnection.h Normal file
View file

@ -0,0 +1,48 @@
//
// BaseTargetConnection.h
// Arq
//
// Created by Stefan Reitshamer on 4/12/14.
// Copyright (c) 2014 Stefan Reitshamer. All rights reserved.
//
@class Target;
@protocol RemoteFS;
@protocol TargetConnectionDelegate;
@interface BaseTargetConnection : NSObject {
Target *target;
id <RemoteFS> remoteFS;
NSString *pathPrefix;
}
- (id)initWithTarget:(Target *)theTarget remoteFS:(id <RemoteFS>)theRemoteFS;
- (NSArray *)computerUUIDsWithDelegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (NSArray *)bucketUUIDsForComputerUUID:(NSString *)theComputerUUID deleted:(BOOL)deleted delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (NSData *)bucketPlistDataForComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (BOOL)saveBucketPlistData:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (BOOL)deleteBucketPlistDataForComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (NSData *)computerInfoForComputerUUID:(NSString *)theComputerUUID delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (BOOL)saveComputerInfo:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (NSArray *)objectsWithPrefix:(NSString *)thePrefix delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (NSArray *)pathsWithPrefix:(NSString *)thePrefix delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (BOOL)deleteObjectsForComputerUUID:(NSString *)theComputerUUID delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (BOOL)deletePaths:(NSArray *)thePaths delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (NSNumber *)fileExistsAtPath:(NSString *)thePath dataSize:(unsigned long long *)theDataSize delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (NSData *)contentsOfFileAtPath:(NSString *)thePath delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (BOOL)writeData:(NSData *)theData toFileAtPath:(NSString *)thePath dataTransferDelegate:(id <DataTransferDelegate>)theDataTransferDelegate targetConnectionDelegate:(id <TargetConnectionDelegate>)theTargetConnectionDelegate error:(NSError **)error;
- (BOOL)removeItemAtPath:(NSString *)thePath delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (NSNumber *)sizeOfItemAtPath:(NSString *)thePath delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (NSNumber *)isObjectRestoredAtPath:(NSString *)thePath delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (BOOL)restoreObjectAtPath:(NSString *)thePath forDays:(NSUInteger)theDays alreadyRestoredOrRestoring:(BOOL *)alreadyRestoredOrRestoring delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (NSData *)saltDataForComputerUUID:(NSString *)theComputerUUID delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (BOOL)setSaltData:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
@end

142
BaseTargetConnection.m Normal file
View file

@ -0,0 +1,142 @@
//
// TargetConnection.m
// Arq
//
// Created by Stefan Reitshamer on 4/12/14.
// Copyright (c) 2014 Stefan Reitshamer. All rights reserved.
//
#import "Target.h"
#import "RemoteFS.h"
#import "BaseTargetConnection.h"
#import "RegexKitLite.h"
#import "TargetConnection.h"
@implementation BaseTargetConnection
- (id)initWithTarget:(Target *)theTarget remoteFS:(id<RemoteFS>)theRemoteFS {
if (self = [super init]) {
target = [theTarget retain];
remoteFS = [theRemoteFS retain];
if ([[[theTarget endpoint] path] isEqualToString:@"/"]) {
pathPrefix = [@"" retain];
} else {
pathPrefix = [[[theTarget endpoint] path] retain];
}
}
return self;
}
- (void)dealloc {
[target release];
[remoteFS release];
[pathPrefix release];
[super dealloc];
}
- (NSArray *)computerUUIDsWithDelegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error {
NSArray *computerUUIDs = [remoteFS contentsOfDirectoryAtPath:[[target endpoint] path] targetConnectionDelegate:theDelegate error:error];
if (computerUUIDs == nil) {
return nil;
}
NSMutableArray *ret = [NSMutableArray array];
for (NSString *computerUUID in computerUUIDs) {
if ([computerUUID rangeOfRegex:@"^(\\S{8}-\\S{4}-\\S{4}-\\S{4}-\\S{12})$"].location != NSNotFound) {
[ret addObject:computerUUID];
}
}
return ret;
}
- (NSArray *)bucketUUIDsForComputerUUID:(NSString *)theComputerUUID deleted:(BOOL)deleted delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error {
NSString *subdir = deleted ? @"deletedbuckets" : @"buckets";
NSString *bucketsPrefix = [NSString stringWithFormat:@"%@/%@/%@/", pathPrefix, theComputerUUID, subdir];
NSArray *bucketUUIDs = [remoteFS contentsOfDirectoryAtPath:bucketsPrefix targetConnectionDelegate:theDelegate error:error];
if (bucketUUIDs == nil) {
return nil;
}
NSMutableArray *ret = [NSMutableArray array];
for (NSString *bucketUUID in bucketUUIDs) {
if ([bucketUUID rangeOfRegex:@"^(\\S{8}-\\S{4}-\\S{4}-\\S{4}-\\S{12})$"].location != NSNotFound) {
[ret addObject:bucketUUID];
}
}
return ret;
}
- (NSData *)bucketPlistDataForComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error {
NSString *subdir = deleted ? @"deletedbuckets" : @"buckets";
NSString *path = [NSString stringWithFormat:@"%@/%@/%@/%@", pathPrefix, theComputerUUID, subdir, theBucketUUID];
return [remoteFS contentsOfFileAtPath:path dataTransferDelegate:nil targetConnectionDelegate:theDelegate error:error];
}
- (BOOL)saveBucketPlistData:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error {
NSString *subdir = deleted ? @"deletedbuckets" : @"buckets";
NSString *path = [NSString stringWithFormat:@"%@/%@/%@/%@", pathPrefix, theComputerUUID, subdir, theBucketUUID];
return [remoteFS writeData:theData atomicallyToFileAtPath:path dataTransferDelegate:nil targetConnectionDelegate:theDelegate error:error];
}
- (BOOL)deleteBucketPlistDataForComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error {
NSString *subdir = deleted ? @"deletedbuckets" : @"buckets";
NSString *path = [NSString stringWithFormat:@"%@/%@/%@/%@", pathPrefix, theComputerUUID, subdir, theBucketUUID];
return [remoteFS removeItemAtPath:path targetConnectionDelegate:theDelegate error:error];
}
- (NSData *)computerInfoForComputerUUID:(NSString *)theComputerUUID delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error {
NSString *path = [NSString stringWithFormat:@"%@/%@/computerinfo", pathPrefix, theComputerUUID];
return [remoteFS contentsOfFileAtPath:path dataTransferDelegate:nil targetConnectionDelegate:theDelegate error:error];
}
- (BOOL)saveComputerInfo:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error {
NSString *path = [NSString stringWithFormat:@"%@/%@/computerinfo", pathPrefix, theComputerUUID];
return [remoteFS writeData:theData atomicallyToFileAtPath:path dataTransferDelegate:nil targetConnectionDelegate:theDelegate error:error];
}
- (NSArray *)objectsWithPrefix:(NSString *)thePrefix delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [remoteFS objectsAtPath:thePrefix targetConnectionDelegate:theDelegate error:error];
}
- (NSArray *)pathsWithPrefix:(NSString *)thePrefix delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [remoteFS pathsOfObjectsAtPath:thePrefix targetConnectionDelegate:theDelegate error:error];
}
- (BOOL)deleteObjectsForComputerUUID:(NSString *)theComputerUUID delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [remoteFS removeItemAtPath:[NSString stringWithFormat:@"%@/%@", pathPrefix, theComputerUUID] targetConnectionDelegate:theDelegate error:error];
}
- (BOOL)deletePaths:(NSArray *)thePaths delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error {
for (NSString *path in thePaths) {
if (![remoteFS removeItemAtPath:path targetConnectionDelegate:theDelegate error:error]) {
return NO;
}
}
return YES;
}
- (NSNumber *)fileExistsAtPath:(NSString *)thePath dataSize:(unsigned long long *)theDataSize delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [remoteFS fileExistsAtPath:thePath dataSize:theDataSize targetConnectionDelegate:theDelegate error:error];
}
- (NSData *)contentsOfFileAtPath:(NSString *)thePath delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [remoteFS contentsOfFileAtPath:thePath dataTransferDelegate:nil targetConnectionDelegate:theDelegate error:error];
}
- (BOOL)writeData:(NSData *)theData toFileAtPath:(NSString *)thePath dataTransferDelegate:(id <DataTransferDelegate>)theDataTransferDelegate targetConnectionDelegate:(id <TargetConnectionDelegate>)theTargetConnectionDelegate error:(NSError **)error {
return [remoteFS writeData:theData atomicallyToFileAtPath:thePath dataTransferDelegate:theDataTransferDelegate targetConnectionDelegate:theTargetConnectionDelegate error:error];
}
- (BOOL)removeItemAtPath:(NSString *)thePath delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [remoteFS removeItemAtPath:thePath targetConnectionDelegate:theDelegate error:error];
}
- (NSNumber *)sizeOfItemAtPath:(NSString *)thePath delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [remoteFS sizeOfItemAtPath:thePath targetConnectionDelegate:theDelegate error:error];
}
- (NSNumber *)isObjectRestoredAtPath:(NSString *)thePath delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [remoteFS isObjectRestoredAtPath:thePath targetConnectionDelegate:theDelegate error:error];
}
- (BOOL)restoreObjectAtPath:(NSString *)thePath forDays:(NSUInteger)theDays alreadyRestoredOrRestoring:(BOOL *)alreadyRestoredOrRestoring delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [remoteFS restoreObjectAtPath:thePath forDays:theDays alreadyRestoredOrRestoring:alreadyRestoredOrRestoring targetConnectionDelegate:theDelegate error:error];
}
- (NSData *)saltDataForComputerUUID:(NSString *)theComputerUUID delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error {
NSString *s3Path = [NSString stringWithFormat:@"%@/%@/salt", pathPrefix, theComputerUUID];
return [remoteFS contentsOfFileAtPath:s3Path dataTransferDelegate:nil targetConnectionDelegate:theDelegate error:error];
}
- (BOOL)setSaltData:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error {
NSString *s3Path = [NSString stringWithFormat:@"%@/%@/salt", pathPrefix, theComputerUUID];
return [remoteFS writeData:theData atomicallyToFileAtPath:s3Path dataTransferDelegate:nil targetConnectionDelegate:theDelegate error:error];
}
@end

View file

@ -14,19 +14,21 @@
NSString *archiveId;
uint64_t archiveSize;
NSDate *archiveUploadedDate;
NSString *sha1;
unsigned char *sha1Bytes;
BOOL stretchEncryptionKey;
BOOL compressed;
}
- (id)initWithSHA1:(NSString *)theSHA1 archiveId:(NSString *)theArchiveId archiveSize:(uint64_t)theArchiveSize archiveUploadedDate:(NSDate *)theArchiveUploadedDate compressed:(BOOL)isCompressed;
- (id)initWithSHA1:(NSString *)theSHA1 storageType:(StorageType)theStorageType stretchEncryptionKey:(BOOL)isStretchedKey compressed:(BOOL)isCompressed;
- (id)initWithStorageType:(StorageType)theStorageType archiveId:(NSString *)theArchiveId archiveSize:(uint64_t)theArchiveSize archiveUploadedDate:(NSDate *)theArchiveUploadedDate sha1:(NSString *)theSHA1 stretchEncryptionKey:(BOOL)isStretchedKey compressed:(BOOL)isCompressed;
- (id)initWithSHA1:(NSString *)theSHA1 archiveId:(NSString *)theArchiveId archiveSize:(uint64_t)theArchiveSize archiveUploadedDate:(NSDate *)theArchiveUploadedDate compressed:(BOOL)isCompressed error:(NSError **)error;
- (id)initWithSHA1:(NSString *)theSHA1 storageType:(StorageType)theStorageType stretchEncryptionKey:(BOOL)isStretchedKey compressed:(BOOL)isCompressed error:(NSError **)error;
- (id)initWithStorageType:(StorageType)theStorageType archiveId:(NSString *)theArchiveId archiveSize:(uint64_t)theArchiveSize archiveUploadedDate:(NSDate *)theArchiveUploadedDate sha1:(NSString *)theSHA1 stretchEncryptionKey:(BOOL)isStretchedKey compressed:(BOOL)isCompressed error:(NSError **)error;
- (id)initCopyOfBlobKey:(BlobKey *)theBlobKey withStorageType:(StorageType)theStorageType;
- (StorageType)storageType;
- (NSString *)archiveId;
- (uint64_t)archiveSize;
- (NSDate *)archiveUploadedDate;
- (NSString *)sha1;
- (unsigned char *)sha1Bytes;
- (BOOL)stretchEncryptionKey;
- (BOOL)compressed;
- (BOOL)isEqualToBlobKey:(BlobKey *)other;

103
BlobKey.m
View file

@ -11,13 +11,28 @@
#import "BooleanIO.h"
#import "IntegerIO.h"
#import "NSObject_extra.h"
#import "NSString_extra.h"
#import "SHA1Hash.h"
@implementation BlobKey
- (id)initWithSHA1:(NSString *)theSHA1 archiveId:(NSString *)theArchiveId archiveSize:(uint64_t)theArchiveSize archiveUploadedDate:(NSDate *)theArchiveUploadedDate compressed:(BOOL)isCompressed {
- (id)initWithSHA1:(NSString *)theSHA1 archiveId:(NSString *)theArchiveId archiveSize:(uint64_t)theArchiveSize archiveUploadedDate:(NSDate *)theArchiveUploadedDate compressed:(BOOL)isCompressed error:(NSError **)error {
if (self = [super init]) {
storageType = StorageTypeGlacier;
sha1 = [theSHA1 retain];
NSData *sha1Data = [theSHA1 hexStringToData:error];
if (sha1Data == nil) {
[self release];
return nil;
}
if ([sha1Data length] != 20) {
SETNSERROR(@"BlobKeyErrorDomain", -1, @"invalid sha1 %@ for BlobKey (must be 20 bytes)", theSHA1);
[self release];
return nil;
}
sha1Bytes = (unsigned char *)malloc(20);
memcpy(sha1Bytes, [sha1Data bytes], 20);
archiveId = [theArchiveId retain];
archiveSize = theArchiveSize;
archiveUploadedDate = [theArchiveUploadedDate retain];
@ -25,31 +40,66 @@
}
return self;
}
- (id)initWithSHA1:(NSString *)theSHA1 storageType:(StorageType)theStorageType stretchEncryptionKey:(BOOL)isStretchedKey compressed:(BOOL)isCompressed {
- (id)initWithSHA1:(NSString *)theSHA1 storageType:(StorageType)theStorageType stretchEncryptionKey:(BOOL)isStretchedKey compressed:(BOOL)isCompressed error:(NSError **)error {
if (self = [super init]) {
storageType = theStorageType;
sha1 = [theSHA1 retain];
NSData *sha1Data = [theSHA1 hexStringToData:error];
if (sha1Data == nil) {
[self release];
return nil;
}
if ([sha1Data length] != 20) {
SETNSERROR(@"BlobKeyErrorDomain", -1, @"invalid sha1 %@ for BlobKey (must be 20 bytes)", theSHA1);
[self release];
return nil;
}
sha1Bytes = (unsigned char *)malloc(20);
memcpy(sha1Bytes, [sha1Data bytes], 20);
stretchEncryptionKey = isStretchedKey;
compressed = isCompressed;
}
return self;
}
- (id)initWithStorageType:(StorageType)theStorageType archiveId:(NSString *)theArchiveId archiveSize:(uint64_t)theArchiveSize archiveUploadedDate:(NSDate *)theArchiveUploadedDate sha1:(NSString *)theSHA1 stretchEncryptionKey:(BOOL)isStretchedKey compressed:(BOOL)isCompressed {
- (id)initWithStorageType:(StorageType)theStorageType archiveId:(NSString *)theArchiveId archiveSize:(uint64_t)theArchiveSize archiveUploadedDate:(NSDate *)theArchiveUploadedDate sha1:(NSString *)theSHA1 stretchEncryptionKey:(BOOL)isStretchedKey compressed:(BOOL)isCompressed error:(NSError **)error {
if (self = [super init]) {
storageType = theStorageType;
archiveId = [theArchiveId retain];
archiveSize = theArchiveSize;
archiveUploadedDate = [theArchiveUploadedDate retain];
sha1 = [theSHA1 retain];
NSData *sha1Data = [theSHA1 hexStringToData:error];
if (sha1Data == nil) {
[self release];
return nil;
}
if ([sha1Data length] != 20) {
SETNSERROR(@"BlobKeyErrorDomain", -1, @"invalid sha1 %@ for BlobKey (must be 20 bytes)", theSHA1);
[self release];
return nil;
}
sha1Bytes = (unsigned char *)malloc(20);
memcpy(sha1Bytes, [sha1Data bytes], 20);
stretchEncryptionKey = isStretchedKey;
compressed = isCompressed;
}
return self;
}
- (id)initCopyOfBlobKey:(BlobKey *)theBlobKey withStorageType:(StorageType)theStorageType {
return [[BlobKey alloc] initWithStorageType:theStorageType
archiveId:[theBlobKey archiveId]
archiveSize:[theBlobKey archiveSize]
archiveUploadedDate:[theBlobKey archiveUploadedDate]
sha1Bytes:[theBlobKey sha1Bytes]
stretchEncryptionKey:[theBlobKey stretchEncryptionKey]
compressed:[theBlobKey compressed]];
}
- (void)dealloc {
[archiveId release];
[archiveUploadedDate release];
[sha1 release];
free(sha1Bytes);
[super dealloc];
}
@ -66,7 +116,10 @@
return archiveUploadedDate;
}
- (NSString *)sha1 {
return sha1;
return [NSString hexStringWithBytes:sha1Bytes length:20];
}
- (unsigned char *)sha1Bytes {
return sha1Bytes;
}
- (BOOL)stretchEncryptionKey {
return stretchEncryptionKey;
@ -75,7 +128,7 @@
return compressed;
}
- (BOOL)isEqualToBlobKey:(BlobKey *)other {
if (![[other sha1] isEqualToString:sha1]) {
if (memcmp(sha1Bytes, [other sha1Bytes], 20) != 0) {
return NO;
}
if (stretchEncryptionKey != [other stretchEncryptionKey]) {
@ -87,16 +140,17 @@
#pragma mark NSCopying
- (id)copyWithZone:(NSZone *)zone {
return [[BlobKey alloc] initWithStorageType:storageType archiveId:archiveId archiveSize:archiveSize archiveUploadedDate:archiveUploadedDate sha1:sha1 stretchEncryptionKey:stretchEncryptionKey compressed:compressed];
return [[BlobKey alloc] initWithStorageType:storageType archiveId:archiveId archiveSize:archiveSize archiveUploadedDate:archiveUploadedDate sha1Bytes:sha1Bytes stretchEncryptionKey:stretchEncryptionKey compressed:compressed];
}
#pragma mark NSObject
- (NSString *)description {
if (storageType == StorageTypeS3) {
return [NSString stringWithFormat:@"<BlobKey sha1=%@,stretchedkey=%@,compressed=%@>", sha1, (stretchEncryptionKey ? @"YES" : @"NO"), (compressed ? @"YES" : @"NO")];
if (storageType == StorageTypeS3 || storageType == StorageTypeS3Glacier) {
NSString *type = storageType == StorageTypeS3 ? @"S3" : @"S3Glacier";
return [NSString stringWithFormat:@"<BlobKey sha1=%@,type=%@,stretchedkey=%@,compressed=%@>", [self sha1], type, (stretchEncryptionKey ? @"YES" : @"NO"), (compressed ? @"YES" : @"NO")];
}
return [NSString stringWithFormat:@"<BlobKey archiveId=%@,archiveUploadedDate=%@,stretchedkey=%@,compressed=%@>", archiveId, archiveUploadedDate, (stretchEncryptionKey ? @"YES" : @"NO"), (compressed ? @"YES" : @"NO")];
return [NSString stringWithFormat:@"<BlobKey sha1=%@,type=Glacier,archiveId=%@,archiveSize=%qu,archiveUploadedDate=%@,stretchedkey=%@,compressed=%@>", [self sha1], archiveId, archiveSize, [self archiveUploadedDate], (stretchEncryptionKey ? @"YES" : @"NO"), (compressed ? @"YES" : @"NO")];
}
- (BOOL)isEqual:(id)anObject {
if (![anObject isKindOfClass:[BlobKey class]]) {
@ -104,7 +158,7 @@
}
BlobKey *other = (BlobKey *)anObject;
return [NSObject equalObjects:sha1 and:[other sha1]]
return memcmp(sha1Bytes, [other sha1Bytes], 20) == 0
&& stretchEncryptionKey == [other stretchEncryptionKey]
&& storageType == [other storageType]
&& [NSObject equalObjects:archiveId and:[other archiveId]]
@ -113,6 +167,25 @@
&& compressed == [other compressed];
}
- (NSUInteger)hash {
return [sha1 hash] + (stretchEncryptionKey ? 1 : 0) + (compressed ? 1 : 0);
return (NSUInteger)(*sha1Bytes);
}
#pragma mark internal
- (id)initWithStorageType:(StorageType)theStorageType archiveId:(NSString *)theArchiveId archiveSize:(uint64_t)theArchiveSize archiveUploadedDate:(NSDate *)theArchiveUploadedDate sha1Bytes:(unsigned char *)theSHA1Bytes stretchEncryptionKey:(BOOL)isStretchedKey compressed:(BOOL)isCompressed {
if (self = [super init]) {
storageType = theStorageType;
archiveId = [theArchiveId retain];
archiveSize = theArchiveSize;
archiveUploadedDate = [theArchiveUploadedDate retain];
NSAssert(theSHA1Bytes != NULL, @"theSHA1Bytes may not be null");
sha1Bytes = (unsigned char *)malloc(20);
memcpy(sha1Bytes, theSHA1Bytes, 20);
stretchEncryptionKey = isStretchedKey;
compressed = isCompressed;
}
return self;
}
@end

View file

@ -6,6 +6,7 @@
//
@class BufferedInputStream;
@class BufferedOutputStream;
@class BlobKey;
@ -13,5 +14,6 @@
}
+ (void)write:(BlobKey *)theBlobKey to:(NSMutableData *)data;
+ (BOOL)write:(BlobKey *)theBlobKey to:(BufferedOutputStream *)os error:(NSError **)error;
+ (BOOL)read:(BlobKey **)theBlobKey from:(BufferedInputStream *)is treeVersion:(int)theTreeVersion compressed:(BOOL)isCompressed error:(NSError **)error;
@end

View file

@ -23,6 +23,14 @@
[IntegerIO writeUInt64:[theBlobKey archiveSize] to:data];
[DateIO write:[theBlobKey archiveUploadedDate] to:data];
}
+ (BOOL)write:(BlobKey *)theBlobKey to:(BufferedOutputStream *)os error:(NSError **)error {
return [StringIO write:[theBlobKey sha1] to:os error:error]
&& [BooleanIO write:[theBlobKey stretchEncryptionKey] to:os error:error]
&& [IntegerIO writeUInt32:(uint32_t)[theBlobKey storageType] to:os error:error]
&& [StringIO write:[theBlobKey archiveId] to:os error:error]
&& [IntegerIO writeUInt64:[theBlobKey archiveSize] to:os error:error]
&& [DateIO write:[theBlobKey archiveUploadedDate] to:os error:error];
}
+ (BOOL)read:(BlobKey **)theBlobKey from:(BufferedInputStream *)is treeVersion:(int)theTreeVersion compressed:(BOOL)isCompressed error:(NSError **)error {
NSString *dataSHA1;
BOOL stretchEncryptionKey = NO;
@ -32,11 +40,9 @@
NSDate *archiveUploadedDate = nil;
if (![StringIO read:&dataSHA1 from:is error:error]) {
[self release];
return NO;
}
if (theTreeVersion >= 14 && ![BooleanIO read:&stretchEncryptionKey from:is error:error]) {
[self release];
return NO;
}
if (theTreeVersion >= 17) {
@ -48,7 +54,17 @@
return NO;
}
}
*theBlobKey = [[[BlobKey alloc] initWithStorageType:storageType archiveId:archiveId archiveSize:archiveSize archiveUploadedDate:archiveUploadedDate sha1:dataSHA1 stretchEncryptionKey:stretchEncryptionKey compressed:isCompressed] autorelease];
if (dataSHA1 == nil) {
// This BlobKeyIO class has been writing nil BlobKeys as if they weren't nil,
// and then reading the values in and creating bogus BlobKeys.
// If the sha1 is nil, it must have been a nil BlobKey, so we return nil here.
*theBlobKey = nil;
} else {
*theBlobKey = [[[BlobKey alloc] initWithStorageType:storageType archiveId:archiveId archiveSize:archiveSize archiveUploadedDate:archiveUploadedDate sha1:dataSHA1 stretchEncryptionKey:stretchEncryptionKey compressed:isCompressed error:error] autorelease];
if (*theBlobKey == nil) {
return NO;
}
}
return YES;
}
@end

90
Bucket.h Normal file
View file

@ -0,0 +1,90 @@
//
// BackupVolume.h
//
// Created by Stefan Reitshamer on 3/25/11.
// Copyright 2011 Haystack Software. All rights reserved.
//
#import "StorageType.h"
@class AWSRegion;
@class DictNode;
@class BucketExcludeSet;
@class BackupParamSet;
@class Target;
@class BufferedInputStream;
@class BufferedOutputStream;
@protocol TargetConnectionDelegate;
enum {
BucketPathMixedState = -1,
BucketPathOffState = 0,
BucketPathOnState = 1
};
typedef NSInteger BucketPathState;
@interface Bucket : NSObject <NSCopying> {
Target *target;
NSString *bucketUUID;
NSString *bucketName;
NSString *computerUUID;
NSString *localPath;
NSString *localMountPoint;
StorageType storageType;
NSMutableArray *ignoredRelativePaths;
BucketExcludeSet *excludeSet;
NSMutableArray *stringArrayPairs;
NSString *vaultName;
NSDate *vaultCreatedDate;
NSDate *plistDeletedDate;
}
+ (NSArray *)bucketsWithTarget:(Target *)theTarget
computerUUID:(NSString *)theComputerUUID
encryptionPassword:(NSString *)theEncryptionPassword
targetConnectionDelegate:(id <TargetConnectionDelegate>)theTCD
error:(NSError **)error;
+ (NSArray *)bucketUUIDsWithTarget:(Target *)theTarget
computerUUID:(NSString *)theComputerUUID
encryptionPassword:(NSString *)theEncryptionPassword
targetConnectionDelegate:(id <TargetConnectionDelegate>)theTCD
error:(NSError **)error;
+ (NSArray *)deletedBucketsWithTarget:(Target *)theTarget
computerUUID:(NSString *)theComputerUUID
encryptionPassword:(NSString *)theEncryptionPassword
targetConnectionDelegate:(id <TargetConnectionDelegate>)theTCD
error:(NSError **)error;
+ (NSString *)errorDomain;
- (id)initWithTarget:(Target *)theTarget
bucketUUID:(NSString *)theBucketUUID
bucketName:(NSString *)theBucketName
computerUUID:(NSString *)theComputerUUID
localPath:(NSString *)theLocalPath
localMountPoint:(NSString *)theLocalMountPoint
storageType:(int)theStorageType;
- (id)initWithBufferedInputStream:(BufferedInputStream *)theBIS error:(NSError **)error;
- (Target *)target;
- (NSString *)computerUUID;
- (NSString *)bucketUUID;
- (NSString *)bucketName;
- (NSString *)localPath;
- (StorageType)storageType;
- (NSString *)localMountPoint;
- (BucketExcludeSet *)bucketExcludeSet;
- (NSString *)vaultName;
- (NSDate *)vaultCreatedDate;
- (NSDate *)plistDeletedDate;
- (BucketPathState)stateForPath:(NSString *)thePath ignoreExcludes:(BOOL)ignoreExcludes;
- (void)setIgnoredRelativePaths:(NSSet *)theSet;
- (NSSet *)ignoredRelativePaths;
- (void)enteredPath:(NSString *)thePath;
- (void)leftPath:(NSString *)thePath;
- (NSData *)toXMLData;
@end

482
Bucket.m Normal file
View file

@ -0,0 +1,482 @@
//
// Bucket.m
//
// Created by Stefan Reitshamer on 3/25/11.
// Copyright 2011 Haystack Software. All rights reserved.
//
#import "Bucket.h"
#import "DictNode.h"
#import "ArrayNode.h"
#import "StringNode.h"
#import "NSString_slashed.h"
#import "BucketExcludeSet.h"
#import "S3AuthorizationProvider.h"
#import "S3Service.h"
#import "UserLibrary_Arq.h"
#import "S3Service.h"
#import "StorageType.h"
#import "GlacierService.h"
#import "AWSRegion.h"
#import "RegexKitLite.h"
#import "AWSRegion.h"
#import "CryptoKey.h"
#import "Target.h"
#import "TargetConnection.h"
#import "GlacierAuthorizationProvider.h"
#import "GlacierService.h"
#import "ArqSalt.h"
#import "DataIO.h"
#import "StringIO.h"
#define BUCKET_PLIST_SALT "BucketPL"
@interface StringArrayPair : NSObject {
NSString *left;
NSArray *right;
}
- (id)initWithLeft:(NSString *)theLeft right:(NSArray *)theRight;
- (NSString *)left;
- (NSArray *)right;
@end
@implementation StringArrayPair
- (id)initWithLeft:(NSString *)theLeft right:(NSArray *)theRight {
if (self = [super init]) {
left = [theLeft retain];
right = [theRight retain];
}
return self;
}
- (void)dealloc {
[left release];
[right release];
[super dealloc];
}
- (NSString *)left {
return left;
}
- (NSArray *)right {
return right;
}
@end
@implementation Bucket
+ (NSArray *)bucketsWithTarget:(Target *)theTarget
computerUUID:(NSString *)theComputerUUID
encryptionPassword:(NSString *)theEncryptionPassword
targetConnectionDelegate:(id <TargetConnectionDelegate>)theTCD
error:(NSError **)error {
id <TargetConnection> targetConnection = [theTarget newConnection];
NSMutableArray *ret = [NSMutableArray array];
do {
NSArray *bucketUUIDs = [targetConnection bucketUUIDsForComputerUUID:theComputerUUID deleted:NO delegate:theTCD error:error];
if (bucketUUIDs == nil) {
if (error != NULL) {
HSLogDebug(@"failed to load bucketUUIDs for target %@ computer %@: %@", theTarget, theComputerUUID, *error);
}
ret = nil;
break;
}
for (NSString *bucketUUID in bucketUUIDs) {
NSError *myError = nil;
Bucket *bucket = [Bucket bucketWithTarget:theTarget targetConnection:targetConnection computerUUID:theComputerUUID bucketUUID:bucketUUID encryptionPassword:theEncryptionPassword error:&myError];
if (bucket == nil) {
HSLogError(@"failed to load bucket plist for %@/%@: %@", theComputerUUID, bucketUUID, myError);
if ([myError code] != ERROR_INVALID_PLIST_XML) {
SETERRORFROMMYERROR;
ret = nil;
break;
}
} else {
[ret addObject:bucket];
}
}
NSSortDescriptor *descriptor = [[[NSSortDescriptor alloc] initWithKey:@"bucketName" ascending:YES selector:@selector(caseInsensitiveCompare:)] autorelease];
[ret sortUsingDescriptors:[NSArray arrayWithObject:descriptor]];
} while(0);
[targetConnection release];
return ret;
}
+ (NSArray *)bucketUUIDsWithTarget:(Target *)theTarget
computerUUID:(NSString *)theComputerUUID
encryptionPassword:(NSString *)theEncryptionPassword
targetConnectionDelegate:(id <TargetConnectionDelegate>)theTCD
error:(NSError **)error {
id <TargetConnection> targetConnection = [[theTarget newConnection] autorelease];
return [targetConnection bucketUUIDsForComputerUUID:theComputerUUID deleted:NO delegate:theTCD error:error];
}
+ (NSArray *)deletedBucketsWithTarget:(Target *)theTarget
computerUUID:(NSString *)theComputerUUID
encryptionPassword:(NSString *)theEncryptionPassword
targetConnectionDelegate:(id <TargetConnectionDelegate>)theTCD
error:(NSError **)error {
HSLogDebug(@"deletedBucketsWithTarget: theTarget=%@ endpoint=%@ path=%@", theTarget, [theTarget endpoint], [[theTarget endpoint] path]);
NSData *salt = [NSData dataWithBytes:BUCKET_PLIST_SALT length:8];
CryptoKey *cryptoKey = [[[CryptoKey alloc] initWithPassword:theEncryptionPassword salt:salt error:error] autorelease];
if (cryptoKey == nil) {
return nil;
}
NSMutableArray *ret = [NSMutableArray array];
id <TargetConnection> targetConnection = [[theTarget newConnection] autorelease];
NSArray *deletedBucketUUIDs = [targetConnection bucketUUIDsForComputerUUID:theComputerUUID deleted:YES delegate:theTCD error:error];
if (deletedBucketUUIDs == nil) {
return nil;
}
for (NSString *bucketUUID in deletedBucketUUIDs) {
NSData *data = [targetConnection bucketPlistDataForComputerUUID:theComputerUUID bucketUUID:bucketUUID deleted:YES delegate:theTCD error:error];
if (data == nil) {
return nil;
}
if (!strncmp([data bytes], "encrypted", 9)) {
NSData *encryptedData = [data subdataWithRange:NSMakeRange(9, [data length] - 9)];
data = [cryptoKey decrypt:encryptedData error:error];
if (data == nil) {
return nil;
}
}
NSError *myError = nil;
DictNode *plist = [DictNode dictNodeWithXMLData:data error:&myError];
if (plist == nil) {
SETNSERROR(@"BucketErrorDomain", -1, @"error parsing bucket plist %@ %@: %@", theComputerUUID, bucketUUID, [myError localizedDescription]);
return nil;
}
Bucket *bucket = [[[Bucket alloc] initWithTarget:theTarget plist:plist] autorelease];
[ret addObject:bucket];
}
NSSortDescriptor *descriptor = [[[NSSortDescriptor alloc] initWithKey:@"bucketName" ascending:YES selector:@selector(caseInsensitiveCompare:)] autorelease];
[ret sortUsingDescriptors:[NSArray arrayWithObject:descriptor]];
return ret;
}
+ (NSString *)errorDomain {
return @"BucketErrorDomain";
}
- (id)initWithTarget:(Target *)theTarget
bucketUUID:(NSString *)theBucketUUID
bucketName:(NSString *)theBucketName
computerUUID:(NSString *)theComputerUUID
localPath:(NSString *)theLocalPath
localMountPoint:(NSString *)theLocalMountPoint
storageType:(int)theStorageType {
if (self = [super init]) {
target = [theTarget retain];
bucketUUID = [theBucketUUID retain];
bucketName = [theBucketName retain];
computerUUID = [theComputerUUID retain];
localPath = [theLocalPath retain];
localMountPoint = [theLocalMountPoint retain];
storageType = theStorageType;
ignoredRelativePaths = [[NSMutableArray alloc] init];
excludeSet = [[BucketExcludeSet alloc] init];
stringArrayPairs = [[NSMutableArray alloc] init];
}
return self;
}
- (id)initWithBufferedInputStream:(BufferedInputStream *)theBIS error:(NSError **)error {
Target *theTarget = [[[Target alloc] initWithBufferedInputStream:theBIS error:error] autorelease];
if (theTarget == nil) {
[self release];
return nil;
}
NSData *xmlData = nil;
if (![DataIO read:&xmlData from:theBIS error:error]) {
[self release];
return nil;
}
if (xmlData == nil) {
SETNSERROR([Bucket errorDomain], -1, @"nil xmlData!");
[self release];
return nil;
}
DictNode *plist = [DictNode dictNodeWithXMLData:xmlData error:error];
if (plist == nil) {
[self release];
return nil;
}
return [self initWithTarget:theTarget plist:plist];
}
- (void)dealloc {
[target release];
[bucketUUID release];
[bucketName release];
[computerUUID release];
[localPath release];
[localMountPoint release];
[ignoredRelativePaths release];
[excludeSet release];
[stringArrayPairs release];
[vaultName release];
[vaultCreatedDate release];
[plistDeletedDate release];
[super dealloc];
}
- (Target *)target {
return target;
}
- (NSString *)computerUUID {
return computerUUID;
}
- (NSString *)bucketUUID {
return bucketUUID;
}
- (NSString *)bucketName {
return bucketName;
}
- (NSString *)localPath {
return localPath;
}
- (NSString *)localMountPoint {
return localMountPoint;
}
- (StorageType)storageType {
return storageType;
}
- (BucketExcludeSet *)bucketExcludeSet {
return excludeSet;
}
- (NSString *)vaultName {
return vaultName;
}
- (NSDate *)vaultCreatedDate {
return vaultCreatedDate;
}
- (NSDate *)plistDeletedDate {
return plistDeletedDate;
}
- (BucketPathState)stateForPath:(NSString *)thePath ignoreExcludes:(BOOL)ignoreExcludes {
if ([ignoredRelativePaths containsObject:@""]) {
return BucketPathOffState;
}
NSInteger ret = BucketPathOnState;
NSString *relativePath = [thePath substringFromIndex:[localPath length]];
for (NSString *ignoredRelativePath in ignoredRelativePaths) {
if ([relativePath isEqualToString:ignoredRelativePath]
|| ([relativePath hasPrefix:ignoredRelativePath] && ([relativePath length] > [ignoredRelativePath length]) && ([relativePath characterAtIndex:[ignoredRelativePath length]] == '/'))
) {
ret = BucketPathOffState;
break;
} else if (([ignoredRelativePath hasPrefix:relativePath] || [relativePath length] == 0)
&& ([ignoredRelativePath length] > [relativePath length])
&& ([relativePath isEqualToString:@""] || [relativePath isEqualToString:@"/"] || [ignoredRelativePath characterAtIndex:[relativePath length]] == '/')) {
ret = BucketPathMixedState;
break;
}
}
if (!ignoreExcludes && [excludeSet matchesFullPath:thePath filename:[thePath lastPathComponent]]) {
return BucketPathOffState;
}
return ret;
}
- (void)setIgnoredRelativePaths:(NSSet *)theSet {
[ignoredRelativePaths setArray:[theSet allObjects]];
}
- (NSSet *)ignoredRelativePaths {
return [NSSet setWithArray:ignoredRelativePaths];
}
- (void)enteredPath:(NSString *)thePath {
NSMutableArray *relativePathsToSkip = [NSMutableArray array];
if (![thePath isEqualToString:localPath]) {
NSString *relativePath = [thePath substringFromIndex:[localPath length]];
for (NSString *ignored in ignoredRelativePaths) {
BOOL applicable = NO;
if ([ignored hasPrefix:relativePath]) {
if ([ignored isEqualToString:relativePath] || ([ignored characterAtIndex:[relativePath length]] == '/')) {
applicable = YES;
}
}
if (!applicable) {
[relativePathsToSkip addObject:ignored];
}
}
}
StringArrayPair *sap = [[StringArrayPair alloc] initWithLeft:thePath right:relativePathsToSkip];
[stringArrayPairs addObject:sap];
[sap release];
[ignoredRelativePaths removeObjectsInArray:relativePathsToSkip];
}
- (void)leftPath:(NSString *)thePath {
StringArrayPair *sap = [stringArrayPairs lastObject];
NSAssert([[sap left] isEqualToString:thePath], @"must leave last path on the stack!");
[ignoredRelativePaths addObjectsFromArray:[sap right]];
[stringArrayPairs removeLastObject];
}
- (NSData *)toXMLData {
DictNode *plist = [[[DictNode alloc] init] autorelease];
[plist putString:[[target endpoint] description] forKey:@"Endpoint"];
[plist putString:bucketUUID forKey:@"BucketUUID"];
[plist putString:bucketName forKey:@"BucketName"];
[plist putString:computerUUID forKey:@"ComputerUUID"];
[plist putString:localPath forKey:@"LocalPath"];
[plist putString:localMountPoint forKey:@"LocalMountPoint"];
[plist putInt:storageType forKey:@"StorageType"];
[plist putString:vaultName forKey:@"VaultName"];
if (vaultCreatedDate != nil) {
[plist putDouble:[vaultCreatedDate timeIntervalSinceReferenceDate] forKey:@"VaultCreatedTime"];
}
if (plistDeletedDate != nil) {
[plist putDouble:[plistDeletedDate timeIntervalSinceReferenceDate] forKey:@"PlistDeletedTime"];
}
ArrayNode *ignoredRelativePathsNode = [[[ArrayNode alloc] init] autorelease];
[plist put:ignoredRelativePathsNode forKey:@"IgnoredRelativePaths"];
NSArray *sortedRelativePaths = [ignoredRelativePaths sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
for (NSString *ignoredRelativePath in sortedRelativePaths) {
[ignoredRelativePathsNode add:[[[StringNode alloc] initWithString:ignoredRelativePath] autorelease]];
}
[plist put:[excludeSet toPlist] forKey:@"Excludes"];
return [plist XMLData];
}
#pragma mark NSCopying
- (id)copyWithZone:(NSZone *)zone {
BucketExcludeSet *excludeSetCopy = [excludeSet copyWithZone:zone];
NSMutableArray *stringArrayPairsCopy = [stringArrayPairs copyWithZone:zone];
NSMutableArray *ignoredRelativePathsCopy = [[NSMutableArray alloc] initWithArray:ignoredRelativePaths copyItems:YES];
Bucket *ret = [[Bucket alloc] initWithTarget:target
bucketUUID:bucketUUID
bucketName:bucketName
computerUUID:computerUUID
localPath:localPath
localMountPoint:localMountPoint
storageType:storageType
ignoredRelativePaths:ignoredRelativePathsCopy
excludeSet:excludeSetCopy
stringArrayPairs:stringArrayPairsCopy
vaultName:vaultName
vaultCreatedDate:vaultCreatedDate
plistDeletedDate:plistDeletedDate];
[excludeSetCopy release];
[stringArrayPairsCopy release];
[ignoredRelativePathsCopy release];
return ret;
}
#pragma mark NSObject
- (NSString *)description {
return [NSString stringWithFormat:@"<Bucket %@: %@ (%lu ignored paths)>", bucketUUID, localPath, (unsigned long)[ignoredRelativePaths count]];
}
#pragma mark internal
+ (Bucket *)bucketWithTarget:(Target *)theTarget
targetConnection:(id <TargetConnection>)theTargetConnection
computerUUID:(NSString *)theComputerUUID
bucketUUID:(NSString *)theBucketUUID
encryptionPassword:(NSString *)theEncryptionPassword
error:(NSError **)error {
NSData *salt = [NSData dataWithBytes:BUCKET_PLIST_SALT length:8];
CryptoKey *cryptoKey = [[[CryptoKey alloc] initWithPassword:theEncryptionPassword salt:salt error:error] autorelease];
if (cryptoKey == nil) {
return nil;
}
BOOL encrypted = NO;
NSData *data = [theTargetConnection bucketPlistDataForComputerUUID:theComputerUUID bucketUUID:theBucketUUID deleted:NO delegate:nil error:error];
if (data == nil) {
return nil;
}
unsigned long length = 9;
if ([data length] < length) {
length = [data length];
}
if (length >= 9 && !strncmp([data bytes], "encrypted", length)) {
encrypted = YES;
NSData *encryptedData = [data subdataWithRange:NSMakeRange(9, [data length] - 9)];
data = [cryptoKey decrypt:encryptedData error:error];
if (data == nil) {
return nil;
}
}
NSError *myError = nil;
DictNode *plist = [DictNode dictNodeWithXMLData:data error:&myError];
if (plist == nil) {
HSLogDebug(@"error parsing XML data into DictNode: %@", myError);
NSString *str = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
HSLogDebug(@"invalid XML: %@", str);
SETNSERROR(@"BucketErrorDomain", ERROR_INVALID_PLIST_XML, @"%@ %@ not valid XML", theComputerUUID, theBucketUUID);
return nil;
}
Bucket *bucket = [[[Bucket alloc] initWithTarget:theTarget plist:plist] autorelease];
return bucket;
}
- (id)initWithTarget:(Target *)theTarget plist:(DictNode *)thePlist {
if (self = [super init]) {
target = [theTarget retain];
bucketUUID = [[[thePlist stringNodeForKey:@"BucketUUID"] stringValue] retain];
bucketName = [[[thePlist stringNodeForKey:@"BucketName"] stringValue] retain];
computerUUID = [[[thePlist stringNodeForKey:@"ComputerUUID"] stringValue] retain];
localPath = [[[thePlist stringNodeForKey:@"LocalPath"] stringValue] retain];
localMountPoint = [[[thePlist stringNodeForKey:@"LocalMountPoint"] stringValue] retain];
storageType = StorageTypeS3;
if ([thePlist containsKey:@"StorageType"]) {
storageType = [[thePlist integerNodeForKey:@"StorageType"] intValue];
}
vaultName = [[[thePlist stringNodeForKey:@"VaultName"] stringValue] retain];
if ([thePlist containsKey:@"VaultCreatedTime"]) {
vaultCreatedDate = [[NSDate alloc] initWithTimeIntervalSinceReferenceDate:[[thePlist realNodeForKey:@"VaultCreatedTime"] doubleValue]];
}
if ([thePlist containsKey:@"PlistDeletedTime"]) {
plistDeletedDate = [[NSDate alloc] initWithTimeIntervalSinceReferenceDate:[[thePlist realNodeForKey:@"PlistDeletedTime"] doubleValue]];
}
ignoredRelativePaths = [[NSMutableArray alloc] init];
ArrayNode *ignoredPathsNode = [thePlist arrayNodeForKey:@"IgnoredRelativePaths"];
for (NSUInteger index = 0; index < [ignoredPathsNode size]; index++) {
[ignoredRelativePaths addObject:[[ignoredPathsNode stringNodeAtIndex:(int)index] stringValue]];
}
[self sortIgnoredRelativePaths];
excludeSet = [[BucketExcludeSet alloc] init];
[excludeSet loadFromPlist:[thePlist dictNodeForKey:@"Excludes"] localPath:localPath];
stringArrayPairs = [[NSMutableArray alloc] init];
}
return self;
}
- (void)sortIgnoredRelativePaths {
// Filter out duplicates.
NSSet *set = [NSSet setWithArray:ignoredRelativePaths];
[ignoredRelativePaths setArray:[set allObjects]];
[ignoredRelativePaths sortUsingSelector:@selector(compareByLength:)];
}
- (id)initWithTarget:(Target *)theTarget
bucketUUID:(NSString *)theBucketUUID
bucketName:(NSString *)theBucketName
computerUUID:(NSString *)theComputerUUID
localPath:(NSString *)theLocalPath
localMountPoint:(NSString *)theLocalMountPoint
storageType:(int)theStorageType
ignoredRelativePaths:(NSMutableArray *)theIgnoredRelativePaths
excludeSet:(BucketExcludeSet *)theExcludeSet
stringArrayPairs:(NSMutableArray *)theStringArrayPairs
vaultName:(NSString *)theVaultName
vaultCreatedDate:(NSDate *)theVaultCreatedDate
plistDeletedDate:(NSDate *)thePlistDeletedDate {
if (self = [super init]) {
target = [theTarget retain];
bucketUUID = [theBucketUUID retain];
bucketName = [theBucketName retain];
computerUUID = [theComputerUUID retain];
localPath = [theLocalPath retain];
localMountPoint = [theLocalMountPoint retain];
storageType = theStorageType;
ignoredRelativePaths = [theIgnoredRelativePaths retain];
excludeSet = [theExcludeSet retain];
stringArrayPairs = [theStringArrayPairs retain];
vaultName = [theVaultName retain];
vaultCreatedDate = [theVaultCreatedDate retain];
plistDeletedDate = [thePlistDeletedDate retain];
}
return self;
}
@end

31
BucketExclude.h Normal file
View file

@ -0,0 +1,31 @@
//
// BucketExclude.h
//
// Created by Stefan Reitshamer on 4/26/11.
// Copyright 2011 Haystack Software. All rights reserved.
//
@class DictNode;
enum {
kBucketExcludeTypeFileNameIs = 1,
kBucketExcludeTypeFileNameContains = 2,
kBucketExcludeTypeFileNameStartsWith = 3,
kBucketExcludeTypeFileNameEndsWith = 4,
kBucketExcludeTypePathMatchesRegex = 5
};
typedef UInt32 BucketExcludeType;
@interface BucketExclude : NSObject <NSCopying> {
BucketExcludeType type;
NSString *text;
}
- (id)initWithType:(BucketExcludeType)theType text:(NSString *)theText;
- (id)initWithPlist:(DictNode *)thePlist;
- (DictNode *)toPlist;
- (BOOL)matchesFullPath:(NSString *)thePath filename:(NSString *)theFilename;
@property (readonly) BucketExcludeType type;
@property (readonly, copy) NSString *text;
@end

87
BucketExclude.m Normal file
View file

@ -0,0 +1,87 @@
//
// BucketExclude.m
//
// Created by Stefan Reitshamer on 4/26/11.
// Copyright 2011 Haystack Software. All rights reserved.
//
#import "BucketExclude.h"
#import "DictNode.h"
#import "IntegerNode.h"
#import "StringNode.h"
#import "RegexKitLite.h"
@implementation BucketExclude
@synthesize type, text;
- (id)initWithType:(BucketExcludeType)theType text:(NSString *)theText {
if (self = [super init]) {
type = theType;
text = [theText retain];
}
return self;
}
- (id)initWithPlist:(DictNode *)thePlist {
if (self = [super init]) {
type = [[thePlist integerNodeForKey:@"type"] intValue];
text = [[[thePlist stringNodeForKey:@"text"] stringValue] retain];
}
return self;
}
- (void)dealloc {
[text release];
[super dealloc];
}
- (DictNode *)toPlist {
DictNode *ret = [[[DictNode alloc] init] autorelease];
[ret put:[[[IntegerNode alloc] initWithInt:type] autorelease] forKey:@"type"];
[ret put:[[[StringNode alloc] initWithString:text] autorelease] forKey:@"text"];
return ret;
}
- (BOOL)matchesFullPath:(NSString *)thePath filename:(NSString *)theFilename {
switch(type) {
case kBucketExcludeTypeFileNameIs:
return [theFilename compare:text options:NSCaseInsensitiveSearch] == NSOrderedSame;
case kBucketExcludeTypeFileNameContains:
return [theFilename rangeOfString:text options:NSCaseInsensitiveSearch].location != NSNotFound;
case kBucketExcludeTypeFileNameStartsWith:
return [[theFilename lowercaseString] hasPrefix:[text lowercaseString]];
case kBucketExcludeTypeFileNameEndsWith:
return [[theFilename lowercaseString] hasSuffix:[text lowercaseString]];
case kBucketExcludeTypePathMatchesRegex:
return [thePath isMatchedByRegex:text options:RKLCaseless inRange:NSMakeRange(0, [thePath length]) error:NULL];
}
return NO;
}
#pragma mark NSObject
- (NSString *)description {
NSString *typeDesc = @"";
switch( type) {
case kBucketExcludeTypeFileNameIs:
typeDesc = @"kBucketExcludeTypeFileNameIs";
break;
case kBucketExcludeTypeFileNameContains:
typeDesc = @"kBucketExcludeTypeFileNameContains";
break;
case kBucketExcludeTypeFileNameStartsWith:
typeDesc = @"kBucketExcludeTypeFileNameStartsWith";
break;
case kBucketExcludeTypeFileNameEndsWith:
typeDesc = @"kBucketExcludeTypeFileNameEndsWith";
break;
case kBucketExcludeTypePathMatchesRegex:
typeDesc = @"kBucketExcludeTypePathMatchesRegex";
break;
}
return [NSString stringWithFormat:@"<BucketExclude: type=%@ text=%@>", typeDesc, text];
}
#pragma mark NSCopying
- (id)copyWithZone:(NSZone *)theZone {
return [[BucketExclude alloc] initWithType:type text:text];
}
@end

25
BucketExcludeSet.h Normal file
View file

@ -0,0 +1,25 @@
//
// BucketExcludeSet.h
//
// Created by Stefan Reitshamer on 4/27/11.
// Copyright 2011 Haystack Software. All rights reserved.
//
#import "BucketExclude.h"
@class DictNode;
@interface BucketExcludeSet : NSObject <NSCopying> {
NSMutableArray *bucketExcludes;
}
- (void)loadFromPlist:(DictNode *)thePlist localPath:(NSString *)theLocalPath;
- (DictNode *)toPlist;
- (NSArray *)bucketExcludes;
- (BOOL)containsExcludeWithType:(BucketExcludeType)theType text:(NSString *)theText;
- (void)addExcludeWithType:(BucketExcludeType)theType text:(NSString *)theText;
- (void)removeExcludeWithType:(BucketExcludeType)theType text:(NSString *)theText;
- (void)setExcludes:(NSArray *)theBucketExcludes;
- (BOOL)removeEmptyExcludes;
- (BOOL)matchesFullPath:(NSString *)thePath filename:(NSString *)theFilename;
- (void)clearExcludes;
@end

173
BucketExcludeSet.m Normal file
View file

@ -0,0 +1,173 @@
//
// BucketExcludeSet.m
//
// Created by Stefan Reitshamer on 4/27/11.
// Copyright 2011 Haystack Software. All rights reserved.
//
#import "BucketExcludeSet.h"
#import "BucketExclude.h"
#import "DictNode.h"
#import "ArrayNode.h"
@interface BucketExcludeSet (internal)
- (id)initWithBucketExcludes:(NSMutableArray *)theBucketExcludes;
@end
@implementation BucketExcludeSet
- (id)init {
if (self = [super init]) {
bucketExcludes = [[NSMutableArray alloc] init];
}
return self;
}
- (void)dealloc {
[bucketExcludes release];
[super dealloc];
}
- (void)loadFromPlist:(DictNode *)thePlist localPath:(NSString *)theLocalPath {
[bucketExcludes removeAllObjects];
if ([thePlist containsKey:@"Conditions"]) {
ArrayNode *conditions = [thePlist arrayNodeForKey:@"Conditions"];
for (NSUInteger index = 0; index < [conditions size]; index++) {
DictNode *condition = [conditions dictNodeAtIndex:(int)index];
NSString *matchText = [[condition stringNodeForKey:@"MatchText"] stringValue];
NSString *escapedLocalPath = [theLocalPath stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"];
escapedLocalPath = [escapedLocalPath stringByReplacingOccurrencesOfString:@"^" withString:@"\\^"];
escapedLocalPath = [escapedLocalPath stringByReplacingOccurrencesOfString:@"$" withString:@"\\$"];
escapedLocalPath = [escapedLocalPath stringByReplacingOccurrencesOfString:@"." withString:@"\\."];
escapedLocalPath = [escapedLocalPath stringByReplacingOccurrencesOfString:@"*" withString:@"\\*"];
escapedLocalPath = [escapedLocalPath stringByReplacingOccurrencesOfString:@"|" withString:@"\\|"];
escapedLocalPath = [escapedLocalPath stringByReplacingOccurrencesOfString:@"+" withString:@"\\+"];
escapedLocalPath = [escapedLocalPath stringByReplacingOccurrencesOfString:@"?" withString:@"\\?"];
escapedLocalPath = [escapedLocalPath stringByReplacingOccurrencesOfString:@"{" withString:@"\\{"];
escapedLocalPath = [escapedLocalPath stringByReplacingOccurrencesOfString:@"(" withString:@"\\("];
NSString *escapedMatchText = [matchText stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"];
escapedMatchText = [escapedMatchText stringByReplacingOccurrencesOfString:@"^" withString:@"\\^"];
escapedMatchText = [escapedMatchText stringByReplacingOccurrencesOfString:@"$" withString:@"\\$"];
escapedMatchText = [escapedMatchText stringByReplacingOccurrencesOfString:@"." withString:@"\\."];
escapedMatchText = [escapedMatchText stringByReplacingOccurrencesOfString:@"*" withString:@"\\*"];
escapedMatchText = [escapedMatchText stringByReplacingOccurrencesOfString:@"|" withString:@"\\|"];
escapedMatchText = [escapedMatchText stringByReplacingOccurrencesOfString:@"+" withString:@"\\+"];
escapedMatchText = [escapedMatchText stringByReplacingOccurrencesOfString:@"?" withString:@"\\?"];
escapedMatchText = [escapedMatchText stringByReplacingOccurrencesOfString:@"{" withString:@"\\{"];
escapedMatchText = [escapedMatchText stringByReplacingOccurrencesOfString:@"(" withString:@"\\("];
NSString *regex = nil;
int matchTypeId = [[condition integerNodeForKey:@"MatchTypeId"] intValue];
int objectTypeId = [[condition integerNodeForKey:@"ObjectTypeId"] intValue];
BucketExcludeType excludeType = 0;
switch (matchTypeId) {
case 2:
excludeType = kBucketExcludeTypeFileNameContains;
regex = [NSString stringWithFormat:@"^%@.*/[^/]*%@[^/]*$", escapedLocalPath, escapedMatchText];
break;
case 4:
excludeType = kBucketExcludeTypeFileNameIs;
regex = [NSString stringWithFormat:@"^%@.*/%@$", escapedLocalPath, escapedMatchText];
break;
case 6:
excludeType = kBucketExcludeTypeFileNameStartsWith;
regex = [NSString stringWithFormat:@"^%@.*/%@[^/]*$", escapedLocalPath, escapedMatchText];
break;
case 7:
excludeType = kBucketExcludeTypeFileNameEndsWith;
regex = [NSString stringWithFormat:@"^%@/.*%@$", escapedLocalPath, escapedMatchText];
break;
}
if (objectTypeId == 2) {
excludeType = kBucketExcludeTypePathMatchesRegex;
matchText = regex;
}
BucketExclude *be = [[[BucketExclude alloc] initWithType:excludeType text:matchText] autorelease];
[bucketExcludes addObject:be];
}
} else {
ArrayNode *excludes = [thePlist arrayNodeForKey:@"excludes"];
for (NSUInteger index = 0; index < [excludes size]; index++) {
BucketExclude *bce = [[[BucketExclude alloc] initWithPlist:[excludes dictNodeAtIndex:(int)index]] autorelease];
[bucketExcludes addObject:bce];
}
}
}
- (DictNode *)toPlist {
DictNode *plist = [[[DictNode alloc] init] autorelease];
ArrayNode *array = [[[ArrayNode alloc] init] autorelease];
[plist put:array forKey:@"excludes"];
for (BucketExclude *bce in bucketExcludes) {
[array add:[bce toPlist]];
}
return plist;
}
- (NSArray *)bucketExcludes {
return bucketExcludes;
}
- (BOOL)containsExcludeWithType:(BucketExcludeType)theType text:(NSString *)theText {
for (BucketExclude *bce in bucketExcludes) {
if (bce.type == theType && [bce.text isEqualToString:theText]) {
return YES;
}
}
return NO;
}
- (void)addExcludeWithType:(BucketExcludeType)theType text:(NSString *)theText {
if (![self containsExcludeWithType:theType text:theText]) {
BucketExclude *bce = [[[BucketExclude alloc] initWithType:theType text:theText] autorelease];
[bucketExcludes addObject:bce];
}
}
- (void)removeExcludeWithType:(BucketExcludeType)theType text:(NSString *)theText {
BucketExclude *found = nil;
for (BucketExclude *bce in bucketExcludes) {
if (bce.type == theType && [bce.text isEqualToString:theText]) {
found = bce;
break;
}
}
if (found != nil) {
[bucketExcludes removeObject:found];
}
}
- (void)setExcludes:(NSArray *)theBucketExcludes {
[bucketExcludes setArray:theBucketExcludes];
}
- (BOOL)removeEmptyExcludes {
NSMutableSet *itemsToRemove = [NSMutableSet set];
for (BucketExclude *bce in bucketExcludes) {
if ([bce.text length] == 0) {
[itemsToRemove addObject:bce];
}
}
[bucketExcludes removeObjectsInArray:[itemsToRemove allObjects]];
return ([itemsToRemove count] > 0);
}
- (BOOL)matchesFullPath:(NSString *)thePath filename:(NSString *)theFilename {
for (BucketExclude *bce in bucketExcludes) {
if ([bce matchesFullPath:thePath filename:theFilename]) {
return YES;
}
}
return NO;
}
- (void)clearExcludes {
[bucketExcludes removeAllObjects];
}
#pragma mark NSCopying
- (id)copyWithZone:(NSZone *)zone {
NSMutableArray *excludesCopy = [[NSMutableArray alloc] initWithArray:bucketExcludes copyItems:YES];
BucketExcludeSet *ret = [[BucketExcludeSet alloc] initWithBucketExcludes:excludesCopy];
[excludesCopy release];
return ret;
}
@end
@implementation BucketExcludeSet (internal)
- (id)initWithBucketExcludes:(NSMutableArray *)theBucketExcludes {
if (self = [super init]) {
bucketExcludes = [theBucketExcludes retain];
}
return self;
}
@end

View file

@ -1,39 +0,0 @@
//
// DiskPack.h
// Arq
//
// Created by Stefan Reitshamer on 12/30/09.
// Copyright 2009 __MyCompanyName__. All rights reserved.
//
@class S3Service;
@class ServerBlob;
@interface DiskPack : NSObject {
S3Service *s3;
NSString *s3BucketName;
NSString *computerUUID;
NSString *packSetName;
NSString *packSHA1;
NSString *s3Path;
NSString *localPath;
uid_t targetUID;
gid_t targetGID;
}
+ (NSString *)s3PathWithS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID packSetName:(NSString *)thePackSetName packSHA1:(NSString *)thePackSHA1;
+ (NSString *)localPathWithS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID packSetName:(NSString *)thePackSetName packSHA1:(NSString *)thePackSHA1;
- (id)initWithS3Service:(S3Service *)theS3
s3BucketName:(NSString *)theS3BucketName
computerUUID:(NSString *)theComputerUUID
packSetName:(NSString *)thePackSetName
packSHA1:(NSString *)thePackSHA1
targetUID:(uid_t)theTargetUID
targetGID:(gid_t)theTargetGID;
- (BOOL)makeLocal:(NSError **)error;
- (BOOL)makeNotLocal:(NSError **)error;
- (ServerBlob *)newServerBlobForObjectAtOffset:(unsigned long long)offset error:(NSError **)error;
- (BOOL)fileLength:(unsigned long long *)length error:(NSError **)error;
- (BOOL)copyToPath:(NSString *)dest error:(NSError **)error;
- (NSArray *)sortedPackIndexEntries:(NSError **)error;
@end

View file

@ -1,262 +0,0 @@
//
// DiskPack.m
// Arq
//
// Created by Stefan Reitshamer on 12/30/09.
// Copyright 2009 __MyCompanyName__. All rights reserved.
//
#include <sys/stat.h>
#import "DiskPack.h"
#import "SetNSError.h"
#import "FDInputStream.h"
#import "StringIO.h"
#import "IntegerIO.h"
#import "ServerBlob.h"
#import "NSFileManager_extra.h"
#import "S3Service.h"
#import "ServerBlob.h"
#import "FileInputStream.h"
#import "FileOutputStream.h"
#import "Streams.h"
#import "S3ObjectReceiver.h"
#import "S3ObjectMetadata.h"
#import "PackIndexEntry.h"
#import "SHA1Hash.h"
#import "UserLibrary_Arq.h"
#import "BufferedInputStream.h"
#import "NSError_extra.h"
@interface DiskPack (internal)
- (BOOL)savePack:(ServerBlob *)sb error:(NSError **)error;
- (NSArray *)sortedPackIndexEntriesFromStream:(BufferedInputStream *)fis error:(NSError **)error;
@end
@implementation DiskPack
+ (NSString *)s3PathWithS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID packSetName:(NSString *)thePackSetName packSHA1:(NSString *)thePackSHA1 {
return [NSString stringWithFormat:@"/%@/%@/packsets/%@/%@.pack", theS3BucketName, theComputerUUID, thePackSetName, thePackSHA1];
}
+ (NSString *)localPathWithS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID packSetName:(NSString *)thePackSetName packSHA1:(NSString *)thePackSHA1 {
return [NSString stringWithFormat:@"%@/%@/%@/packsets/%@/%@/%@.pack", [UserLibrary arqCachePath], theS3BucketName, theComputerUUID, thePackSetName, [thePackSHA1 substringToIndex:2], [thePackSHA1 substringFromIndex:2]];
}
- (id)initWithS3Service:(S3Service *)theS3
s3BucketName:(NSString *)theS3BucketName
computerUUID:(NSString *)theComputerUUID
packSetName:(NSString *)thePackSetName
packSHA1:(NSString *)thePackSHA1
targetUID:(uid_t)theTargetUID
targetGID:(gid_t)theTargetGID {
if (self = [super init]) {
s3 = [theS3 retain];
s3BucketName = [theS3BucketName retain];
computerUUID = [theComputerUUID retain];
packSetName = [thePackSetName retain];
packSHA1 = [thePackSHA1 retain];
s3Path = [[DiskPack s3PathWithS3BucketName:s3BucketName computerUUID:computerUUID packSetName:packSetName packSHA1:packSHA1] retain];
localPath = [[DiskPack localPathWithS3BucketName:s3BucketName computerUUID:computerUUID packSetName:packSetName packSHA1:packSHA1] retain];
targetUID = theTargetUID;
targetGID = theTargetGID;
}
return self;
}
- (void)dealloc {
[s3 release];
[s3BucketName release];
[computerUUID release];
[packSetName release];
[packSHA1 release];
[s3Path release];
[localPath release];
[super dealloc];
}
- (BOOL)makeLocal:(NSError **)error {
NSFileManager *fm = [NSFileManager defaultManager];
BOOL ret = YES;
if (![fm fileExistsAtPath:localPath]) {
for (;;) {
HSLogDebug(@"packset %@: making pack %@ local", packSetName, packSHA1);
NSError *myError = nil;
ServerBlob *sb = [s3 newServerBlobAtPath:s3Path error:&myError];
if (sb != nil) {
ret = [self savePack:sb error:error];
[sb release];
break;
}
if (![myError isTransientError]) {
HSLogError(@"error getting S3 pack %@: %@", s3Path, myError);
if (error != NULL) {
*error = myError;
}
ret = NO;
break;
}
HSLogWarn(@"network error making pack %@ local (retrying): %@", s3Path, myError);
NSError *rmError = nil;
if (![[NSFileManager defaultManager] removeItemAtPath:localPath error:&rmError]) {
HSLogError(@"error deleting incomplete downloaded pack file %@: %@", localPath, rmError);
}
}
}
return ret;
}
- (BOOL)makeNotLocal:(NSError **)error {
NSFileManager *fm = [NSFileManager defaultManager];
HSLogDebug(@"removing disk pack %@", localPath);
BOOL ret = YES;
if ([fm fileExistsAtPath:localPath] && ![fm removeItemAtPath:localPath error:error]) {
ret = NO;
}
return ret;
}
- (ServerBlob *)newServerBlobForObjectAtOffset:(unsigned long long)offset error:(NSError **)error {
int fd = open([localPath fileSystemRepresentation], O_RDONLY);
if (fd == -1) {
int errnum = errno;
HSLogError(@"open(%@) error %d: %s", localPath, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"failed to open %@: %s", localPath, strerror(errnum));
return nil;
}
ServerBlob *ret = nil;
FDInputStream *fdis = [[FDInputStream alloc] initWithFD:fd label:localPath];
BufferedInputStream *bis = [[BufferedInputStream alloc] initWithUnderlyingStream:fdis];
do {
if (lseek(fd, offset, SEEK_SET) == -1) {
int errnum = errno;
HSLogError(@"lseek(%@, %qu) error %d: %s", localPath, offset, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"failed to seek to %qu in %@: %s", offset, localPath, strerror(errnum));
break;
}
NSString *mimeType;
NSString *downloadName;
if (![StringIO read:&mimeType from:bis error:error] || ![StringIO read:&downloadName from:bis error:error]) {
break;
}
uint64_t dataLen = 0;
if (![IntegerIO readUInt64:&dataLen from:bis error:error]) {
break;
}
NSData *data = nil;
if (dataLen > 0) {
unsigned char *buf = (unsigned char *)malloc(dataLen);
if (![bis readExactly:dataLen into:buf error:error]) {
free(buf);
break;
}
data = [NSData dataWithBytesNoCopy:buf length:dataLen];
} else {
data = [NSData data];
}
ret = [[ServerBlob alloc] initWithData:data mimeType:mimeType downloadName:downloadName];
} while (0);
close(fd);
[bis release];
[fdis release];
return ret;
}
- (BOOL)fileLength:(unsigned long long *)length error:(NSError **)error {
struct stat st;
if (lstat([localPath fileSystemRepresentation], &st) == -1) {
int errnum = errno;
HSLogError(@"lstat(%@) error %d: %s", localPath, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"%@: %s", localPath, strerror(errnum));
return NO;
}
*length = st.st_size;
return YES;
}
- (BOOL)copyToPath:(NSString *)dest error:(NSError **)error {
NSFileManager *fm = [NSFileManager defaultManager];
if ([fm fileExistsAtPath:dest] && ![fm removeItemAtPath:dest error:error]) {
HSLogError(@"error removing old mutable pack at %@", dest);
return NO;
}
if (![fm ensureParentPathExistsForPath:dest targetUID:targetUID targetGID:targetGID error:error] || ![fm copyItemAtPath:localPath toPath:dest error:error]) {
HSLogError(@"error copying pack %@ to %@", localPath, dest);
return NO;
}
if (chown([localPath fileSystemRepresentation], targetUID, targetGID) == -1) {
int errnum = errno;
SETNSERROR(@"UnixErrorDomain", errnum, @"chown(%@): %s", localPath, strerror(errnum));
return NO;
}
HSLogDebug(@"copied %@ to %@", localPath, dest);
return YES;
}
- (NSArray *)sortedPackIndexEntries:(NSError **)error {
unsigned long long length;
if (![self fileLength:&length error:error]) {
return NO;
}
FileInputStream *fis = [[FileInputStream alloc] initWithPath:localPath offset:0 length:length];
BufferedInputStream *bis = [[BufferedInputStream alloc] initWithUnderlyingStream:fis];
NSArray *ret = [self sortedPackIndexEntriesFromStream:bis error:error];
[bis release];
[fis release];
return ret;
}
@end
@implementation DiskPack (internal)
- (BOOL)savePack:(ServerBlob *)sb error:(NSError **)error {
if (![[NSFileManager defaultManager] ensureParentPathExistsForPath:localPath targetUID:targetUID targetGID:targetGID error:error]) {
return NO;
}
id <InputStream> is = [sb newInputStream];
NSError *myError;
unsigned long long written;
BOOL ret = [Streams transferFrom:is atomicallyToFile:localPath targetUID:targetUID targetGID:targetGID bytesWritten:&written error:&myError];
if (ret) {
HSLogDebug(@"wrote %qu bytes to %@", written, localPath);
} else {
if (error != NULL) {
*error = myError;
}
HSLogError(@"error making pack %@ local at %@: %@", packSHA1, localPath, [myError localizedDescription]);
}
[is release];
return ret;
}
- (NSArray *)sortedPackIndexEntriesFromStream:(BufferedInputStream *)is error:(NSError **)error {
uint32_t packSig;
uint32_t packVersion;
if (![IntegerIO readUInt32:&packSig from:is error:error] || ![IntegerIO readUInt32:&packVersion from:is error:error]) {
return nil;
}
if (packSig != 0x5041434b) { // "PACK"
SETNSERROR(@"PackErrorDomain", -1, @"invalid pack signature");
return nil;
}
if (packVersion != 2) {
SETNSERROR(@"PackErrorDomain", -1, @"invalid pack version");
}
uint32_t objectCount;
if (![IntegerIO readUInt32:&objectCount from:is error:error]) {
return nil;
}
NSMutableArray *ret = [NSMutableArray array];
for (uint32_t index = 0; index < objectCount; index++) {
uint64_t offset = [is bytesReceived];
NSString *mimeType;
NSString *name;
uint64_t length;
if (![StringIO read:&mimeType from:is error:error] || ![StringIO read:&name from:is error:error] || ![IntegerIO readUInt64:&length from:is error:error]) {
return NO;
}
NSString *objectSHA1 = [SHA1Hash hashStream:is withLength:length error:error];
if (objectSHA1 == nil) {
return NO;
}
PackIndexEntry *pie = [[PackIndexEntry alloc] initWithPackSHA1:packSHA1 offset:offset dataLength:length objectSHA1:objectSHA1];
[ret addObject:pie];
[pie release];
}
return ret;
}
#pragma mark NSObject
- (NSString *)description {
return [NSString stringWithFormat:@"<DiskPack s3Bucket=%@ computerUUID=%@ packset=%@ sha1=%@ localPath=%@>", s3BucketName, computerUUID, packSetName, packSHA1, localPath];
}
@end

View file

@ -1,46 +0,0 @@
//
// DiskPackIndex.h
// Arq
//
// Created by Stefan Reitshamer on 12/30/09.
// Copyright 2009 __MyCompanyName__. All rights reserved.
//
@class PackIndexEntry;
@class S3Service;
@interface DiskPackIndex : NSObject {
S3Service *s3;
NSString *s3BucketName;
NSString *computerUUID;
NSString *packSetName;
NSString *packSHA1;
NSString *s3Path;
NSString *localPath;
uid_t targetUID;
gid_t targetGID;
}
+ (NSString *)s3PathWithS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID packSetName:(NSString *)thePackSetName packSHA1:(NSString *)thePackSHA1;
+ (NSString *)localPathWithS3BucketName:theS3BucketName computerUUID:(NSString *)theComputerUUID packSetName:(NSString *)thePackSetName packSHA1:(NSString *)thePackSHA1;
+ (NSArray *)diskPackIndexesForS3Service:(S3Service *)theS3
s3BucketName:theS3BucketName
computerUUID:(NSString *)theComputerUUID
packSetName:(NSString *)thePackSetName
targetUID:(uid_t)theTargetUID
targetGID:(gid_t)theTargetGID
error:(NSError **)error;
- (id)initWithS3Service:(S3Service *)theS3
s3BucketName:(NSString *)theS3BucketName
computerUUID:(NSString *)theComputerUUID
packSetName:(NSString *)thePackSetName
packSHA1:(NSString *)thePackSHA1
targetUID:(uid_t)theTargetUID
targetGID:(gid_t)theTargetGID;
- (BOOL)makeLocal:(NSError **)error;
- (NSArray *)allPackIndexEntries:(NSError **)error;
- (PackIndexEntry *)entryForSHA1:(NSString *)sha1 error:(NSError **)error;
- (NSString *)packSetName;
- (NSString *)packSHA1;
@end

View file

@ -1,358 +0,0 @@
//
// DiskPackIndex.m
// Arq
//
// Created by Stefan Reitshamer on 12/30/09.
// Copyright 2009 __MyCompanyName__. All rights reserved.
//
#include <sys/stat.h>
#include <sys/mman.h>
#include <libkern/OSByteOrder.h>
#import "DiskPackIndex.h"
#import "NSString_extra.h"
#import "SetNSError.h"
#import "BinarySHA1.h"
#import "PackIndexEntry.h"
#import "NSErrorCodes.h"
#import "S3Service.h"
#import "FileOutputStream.h"
#import "Streams.h"
#import "NSFileManager_extra.h"
#import "ServerBlob.h"
#import "S3ObjectReceiver.h"
#import "DiskPack.h"
#import "BlobACL.h"
#import "FileInputStreamFactory.h"
#import "PackIndexWriter.h"
#import "UserLibrary_Arq.h"
#import "NSError_extra.h"
#import "RegexKitLite.h"
typedef struct index_object {
uint64_t nbo_offset;
uint64_t nbo_datalength;
unsigned char sha1[20];
unsigned char filler[4];
} index_object;
typedef struct pack_index {
uint32_t magic_number;
uint32_t nbo_version;
uint32_t nbo_fanout[256];
index_object first_index_object;
} pack_index;
@interface DiskPackIndex (internal)
- (BOOL)savePackIndex:(ServerBlob *)sb error:(NSError **)error;
- (PackIndexEntry *)doEntryForSHA1:(NSString *)sha1 error:(NSError **)error;
- (PackIndexEntry *)findEntryForSHA1:(NSString *)sha1 fd:(int)fd betweenStartIndex:(uint32_t)startIndex andEndIndex:(uint32_t)endIndex error:(NSError **)error;
- (BOOL)readFanoutStartIndex:(uint32_t *)start fanoutEndIndex:(uint32_t *)end fromFD:(int)fd forSHA1FirstByte:(unsigned int)firstByte error:(NSError **)error;
@end
@implementation DiskPackIndex
+ (NSString *)s3PathWithS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID packSetName:(NSString *)thePackSetName packSHA1:(NSString *)thePackSHA1 {
return [NSString stringWithFormat:@"/%@/%@/packsets/%@/%@.index", theS3BucketName, theComputerUUID, thePackSetName, thePackSHA1];
}
+ (NSString *)localPathWithS3BucketName:theS3BucketName computerUUID:(NSString *)theComputerUUID packSetName:(NSString *)thePackSetName packSHA1:(NSString *)thePackSHA1 {
return [NSString stringWithFormat:@"%@/%@/%@/packsets/%@/%@/%@.index", [UserLibrary arqCachePath], theS3BucketName, theComputerUUID, thePackSetName, [thePackSHA1 substringToIndex:2], [thePackSHA1 substringFromIndex:2]];
}
+ (NSArray *)diskPackIndexesForS3Service:(S3Service *)theS3
s3BucketName:theS3BucketName
computerUUID:(NSString *)theComputerUUID
packSetName:(NSString *)thePackSetName
targetUID:(uid_t)theTargetUID
targetGID:(gid_t)theTargetGID
error:(NSError **)error {
NSMutableArray *diskPackIndexes = [NSMutableArray array];
NSString *packSetsPrefix = [NSString stringWithFormat:@"/%@/%@/packsets/%@/", theS3BucketName, theComputerUUID, thePackSetName];
NSArray *paths = [theS3 pathsWithPrefix:packSetsPrefix error:error];
if (paths == nil) {
return nil;
}
for (NSString *thePath in paths) {
NSRange sha1Range = [thePath rangeOfRegex:@"/(\\w+)\\.pack$" capture:1];
if (sha1Range.location != NSNotFound) {
NSString *thePackSHA1 = [thePath substringWithRange:sha1Range];
DiskPackIndex *index = [[DiskPackIndex alloc] initWithS3Service:theS3
s3BucketName:theS3BucketName
computerUUID:theComputerUUID
packSetName:thePackSetName
packSHA1:thePackSHA1
targetUID:theTargetUID
targetGID:theTargetGID];
[diskPackIndexes addObject:index];
[index release];
}
}
return diskPackIndexes;
}
- (id)initWithS3Service:(S3Service *)theS3
s3BucketName:(NSString *)theS3BucketName
computerUUID:(NSString *)theComputerUUID
packSetName:(NSString *)thePackSetName
packSHA1:(NSString *)thePackSHA1
targetUID:(uid_t)theTargetUID
targetGID:(gid_t)theTargetGID {
if (self = [super init]) {
s3 = [theS3 retain];
s3BucketName = [theS3BucketName retain];
computerUUID = [theComputerUUID retain];
packSetName = [thePackSetName retain];
packSHA1 = [thePackSHA1 retain];
s3Path = [[DiskPackIndex s3PathWithS3BucketName:s3BucketName computerUUID:computerUUID packSetName:packSetName packSHA1:packSHA1] retain];
localPath = [[DiskPackIndex localPathWithS3BucketName:s3BucketName computerUUID:computerUUID packSetName:packSetName packSHA1:packSHA1] retain];
targetUID = theTargetUID;
targetGID = theTargetGID;
}
return self;
}
- (void)dealloc {
[s3 release];
[s3BucketName release];
[computerUUID release];
[packSetName release];
[packSHA1 release];
[s3Path release];
[localPath release];
[super dealloc];
}
- (BOOL)makeLocal:(NSError **)error {
NSFileManager *fm = [NSFileManager defaultManager];
BOOL ret = YES;
if (![fm fileExistsAtPath:localPath]) {
for (;;) {
HSLogDebug(@"packset %@: making pack index %@ local", packSetName, packSHA1);
NSError *myError = nil;
ServerBlob *sb = [s3 newServerBlobAtPath:s3Path error:&myError];
if (sb != nil) {
ret = [self savePackIndex:sb error:error];
[sb release];
break;
}
if (![myError isTransientError]) {
HSLogError(@"error getting S3 pack index %@: %@", s3Path, myError);
if (error != NULL) {
*error = myError;
}
ret = NO;
break;
}
HSLogWarn(@"network error making pack index %@ local (retrying): %@", s3Path, myError);
NSError *rmError = nil;
if (![[NSFileManager defaultManager] removeItemAtPath:localPath error:&rmError]) {
HSLogError(@"error deleting incomplete downloaded pack index %@: %@", localPath, rmError);
}
}
}
return ret;
}
- (NSArray *)allPackIndexEntries:(NSError **)error {
int fd = open([localPath fileSystemRepresentation], O_RDONLY);
if (fd == -1) {
int errnum = errno;
HSLogError(@"open(%@) error %d: %s", localPath, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"failed to open %@: %s", localPath, strerror(errnum));
return nil;
}
struct stat st;
if (fstat(fd, &st) == -1) {
int errnum = errno;
HSLogError(@"fstat(%@) error %d: %s", localPath, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"%@: %s", localPath, strerror(errnum));
close(fd);
return nil;
}
pack_index *the_pack_index = mmap(0, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
if (the_pack_index == MAP_FAILED) {
int errnum = errno;
HSLogError(@"mmap(%@) error %d: %s", localPath, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"error mapping %@ to memory: %s", localPath, strerror(errnum));
close(fd);
return NO;
}
NSMutableArray *ret = [NSMutableArray array];
uint32_t count = OSSwapBigToHostInt32(the_pack_index->nbo_fanout[255]);
index_object *indexObjects = &(the_pack_index->first_index_object);
for (uint32_t i = 0; i < count; i++) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
uint64_t offset = OSSwapBigToHostInt64(indexObjects[i].nbo_offset);
uint64_t dataLength = OSSwapBigToHostInt64(indexObjects[i].nbo_datalength);
NSString *objectSHA1 = [NSString hexStringWithBytes:indexObjects[i].sha1 length:20];
PackIndexEntry *pie = [[[PackIndexEntry alloc] initWithPackSHA1:packSHA1 offset:offset dataLength:dataLength objectSHA1:objectSHA1] autorelease];
[ret addObject:pie];
[pool drain];
}
if (munmap(the_pack_index, st.st_size) == -1) {
int errnum = errno;
HSLogError(@"munmap: %s", strerror(errnum));
}
close(fd);
return ret;
}
- (PackIndexEntry *)entryForSHA1:(NSString *)sha1 error:(NSError **)error {
if (error != NULL) {
*error = nil;
}
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
PackIndexEntry *ret = [self doEntryForSHA1:sha1 error:error];
[ret retain];
if (ret == nil && error != NULL) {
[*error retain];
}
[pool drain];
[ret autorelease];
if (ret == nil && error != NULL) {
[*error autorelease];
}
return ret;
}
- (NSString *)packSetName {
return packSetName;
}
- (NSString *)packSHA1 {
return packSHA1;
}
#pragma mark NSObject
- (NSString *)description {
return [NSString stringWithFormat:@"<DiskPackIndex: computerUUID=%@ packset=%@ packSHA1=%@>", computerUUID, packSetName, packSHA1];
}
@end
@implementation DiskPackIndex (internal)
- (BOOL)savePackIndex:(ServerBlob *)sb error:(NSError **)error {
if (![[NSFileManager defaultManager] ensureParentPathExistsForPath:localPath targetUID:targetUID targetGID:targetGID error:error]) {
return NO;
}
id <InputStream> is = [sb newInputStream];
NSError *myError = nil;
unsigned long long written = 0;
BOOL ret = [Streams transferFrom:is atomicallyToFile:localPath targetUID:targetUID targetGID:targetGID bytesWritten:&written error:&myError];
if (ret) {
HSLogDebug(@"wrote %qu bytes to %@", written, localPath);
} else {
if (error != NULL) {
*error = myError;
}
HSLogError(@"error making pack %@ local at %@: %@", packSHA1, localPath, [myError localizedDescription]);
}
[is release];
return ret;
}
- (PackIndexEntry *)doEntryForSHA1:(NSString *)sha1 error:(NSError **)error {
NSData *sha1Hex = [sha1 hexStringToData];
unsigned char *sha1Bytes = (unsigned char *)[sha1Hex bytes];
HSLogTrace(@"looking for sha1 %@ in packindex %@", sha1, packSHA1);
int fd = open([localPath fileSystemRepresentation], O_RDONLY);
if (fd == -1) {
int errnum = errno;
HSLogError(@"open(%@) error %d: %s", localPath, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"failed to open %@: %s", localPath, strerror(errnum));
return nil;
}
uint32_t startIndex;
uint32_t endIndex;
if (![self readFanoutStartIndex:&startIndex fanoutEndIndex:&endIndex fromFD:fd forSHA1FirstByte:(unsigned int)sha1Bytes[0] error:error]) {
close(fd);
return nil;
}
close(fd);
if (endIndex == 0) {
SETNSERROR(@"PacksErrorDomain", ERROR_NOT_FOUND, @"sha1 %@ not found in pack", sha1);
return NO;
}
fd = open([localPath fileSystemRepresentation], O_RDONLY);
if (fd == -1) {
int errnum = errno;
HSLogError(@"open(%@) error %d: %s", localPath, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"failed to open %@: %s", localPath, strerror(errnum));
return nil;
}
PackIndexEntry *ret = [self findEntryForSHA1:sha1 fd:fd betweenStartIndex:startIndex andEndIndex:endIndex error:error];
close(fd);
if (ret != nil) {
HSLogTrace(@"found sha1 %@ in packindex %@", sha1, packSHA1);
}
return ret;
}
- (PackIndexEntry *)findEntryForSHA1:(NSString *)sha1 fd:(int)fd betweenStartIndex:(uint32_t)startIndex andEndIndex:(uint32_t)endIndex error:(NSError **)error {
NSData *sha1Data = [sha1 hexStringToData];
const void *sha1Bytes = [sha1Data bytes];
uint32_t lengthToMap = 4 + 4 + 256*4 + endIndex * sizeof(index_object);
pack_index *the_pack_index = mmap(0, lengthToMap, PROT_READ, MAP_SHARED, fd, 0);
if (the_pack_index == MAP_FAILED) {
int errnum = errno;
HSLogError(@"mmap(%@) error %d: %s", localPath, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"error mapping %@ to memory: %s", localPath, strerror(errnum));
return NO;
}
int64_t left = startIndex;
int64_t right = endIndex - 1;
int64_t middle;
int64_t offset;
int64_t dataLength;
PackIndexEntry *pie = nil;
while (left <= right) {
middle = (left + right)/2;
index_object *firstIndexObject = &(the_pack_index->first_index_object);
index_object *middleIndexObject = &firstIndexObject[middle];
void *middleSHA1 = middleIndexObject->sha1;
NSComparisonResult result = [BinarySHA1 compare:middleSHA1 to:sha1Bytes];
switch (result) {
case NSOrderedAscending:
left = middle + 1;
break;
case NSOrderedDescending:
right = middle - 1;
break;
default:
offset = OSSwapBigToHostInt64(middleIndexObject->nbo_offset);
dataLength = OSSwapBigToHostInt64(middleIndexObject->nbo_datalength);
pie = [[[PackIndexEntry alloc] initWithPackSHA1:packSHA1 offset:offset dataLength:dataLength objectSHA1:sha1] autorelease];
}
if (pie != nil) {
break;
}
}
if (munmap(the_pack_index, lengthToMap) == -1) {
int errnum = errno;
HSLogError(@"munmap: %s", strerror(errnum));
}
if (pie == nil) {
SETNSERROR(@"PackErrorDomain", ERROR_NOT_FOUND, @"sha1 %@ not found in pack %@", sha1, packSHA1);
}
return pie;
}
- (BOOL)readFanoutStartIndex:(uint32_t *)start fanoutEndIndex:(uint32_t *)end fromFD:(int)fd forSHA1FirstByte:(unsigned int)firstByte error:(NSError **)error {
size_t len = 4 + 4 + 4*256;
uint32_t *map = mmap(0, len, PROT_READ, MAP_SHARED, fd, 0);
if (map == MAP_FAILED) {
int errnum = errno;
HSLogError(@"mmap(%@) error %d: %s", localPath, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"error mapping %@ to memory: %s", localPath, strerror(errnum));
return NO;
}
BOOL ret = YES;
uint32_t magicNumber = OSSwapBigToHostInt32(map[0]);
uint32_t version = OSSwapBigToHostInt32(map[1]);
if (magicNumber != 0xff744f63 || version != 2) {
SETNSERROR(@"PackErrorDomain", -1, @"invalid pack index header");
ret = NO;
} else {
uint32_t *fanoutTable = map + 2;
*start = 0;
if (firstByte > 0) {
*start = OSSwapBigToHostInt32(fanoutTable[firstByte - 1]);
}
*end = OSSwapBigToHostInt32(fanoutTable[firstByte]);
}
if (munmap(map, len) == -1) {
int errnum = errno;
HSLogError(@"munmap: %s", strerror(errnum));
}
return ret;
}
@end

View file

@ -1,17 +0,0 @@
//
// FarkPath.h
// Arq
//
// Created by Stefan Reitshamer on 6/29/10.
// Copyright 2010 __MyCompanyName__. All rights reserved.
//
@interface FarkPath : NSObject {
}
+ (NSString *)s3PathForS3BucketName:(NSString *)s3BucketName computerUUID:(NSString *)computerUUID sha1:(NSString *)sha1;
+ (NSString *)s3PathForBucketDataRelativePath:(NSString *)bucketDataRelativePath s3BucketName:(NSString *)s3BucketName computerUUID:(NSString *)computerUUID;
@end

View file

@ -1,19 +0,0 @@
//
// FarkPath.m
// Arq
//
// Created by Stefan Reitshamer on 6/29/10.
// Copyright 2010 __MyCompanyName__. All rights reserved.
//
#import "FarkPath.h"
@implementation FarkPath
+ (NSString *)s3PathForS3BucketName:(NSString *)s3BucketName computerUUID:(NSString *)computerUUID sha1:(NSString *)sha1 {
return [NSString stringWithFormat:@"/%@/%@/objects/%@", s3BucketName, computerUUID, sha1];
}
+ (NSString *)s3PathForBucketDataRelativePath:(NSString *)bucketDataRelativePath s3BucketName:(NSString *)s3BucketName computerUUID:(NSString *)computerUUID {
return [[NSString stringWithFormat:@"/%@/%@/bucketdata", s3BucketName, computerUUID] stringByAppendingPathComponent:bucketDataRelativePath];
}
@end

View file

@ -1,63 +0,0 @@
//
// FileAttributes.h
// Backup
//
// Created by Stefan Reitshamer on 4/22/09.
// Copyright 2009 PhotoMinds LLC. All rights reserved.
//
#include <sys/stat.h>
@interface FileAttributes : NSObject {
BOOL targetExists;
NSString *path;
const char *cPath;
struct stat st;
struct timespec createTime;
int finderFlags;
int extendedFinderFlags;
NSString *finderFileType;
NSString *finderFileCreator;
}
- (id)initWithPath:(NSString *)thePath error:(NSError **)error;
- (id)initWithPath:(NSString *)thePath stat:(struct stat *)st error:(NSError **)error;
- (unsigned long long)fileSize;
- (int)uid;
- (int)gid;
- (int)mode;
- (long)mtime_sec;
- (long)mtime_nsec;
- (long)flags;
- (int)finderFlags;
- (int)extendedFinderFlags;
- (NSString *)finderFileType;
- (NSString *)finderFileCreator;
- (BOOL)isExtensionHidden;
- (BOOL)isFifo;
- (BOOL)isDevice;
- (BOOL)isSymbolicLink;
- (BOOL)isRegularFile;
- (BOOL)isSocket;
- (int)st_dev;
- (int)st_ino;
- (uint32_t)st_nlink;
- (int)st_rdev;
- (int64_t)ctime_sec;
- (int64_t)ctime_nsec;
- (int64_t)createTime_sec;
- (int64_t)createTime_nsec;
- (int64_t)st_blocks;
- (uint32_t)st_blksize;
- (BOOL)applyFinderFileType:(NSString *)finderFileType finderFileCreator:(NSString *)finderFileCreator error:(NSError **)error;
- (BOOL)applyFlags:(int)flags error:(NSError **)error;
- (BOOL)applyFinderFlags:(int)finderFlags error:(NSError **)error;
- (BOOL)applyExtendedFinderFlags:(int)extendedFinderFlags error:(NSError **)error;
- (BOOL)applyExtensionHidden:(BOOL)isExtensionHidden error:(NSError **)error;
- (BOOL)applyUID:(int)uid gid:(int)gid error:(NSError **)error;
- (BOOL)applyMode:(int)mode error:(NSError **)error;
- (BOOL)applyMTimeSec:(int64_t)mtime_sec mTimeNSec:(int64_t)mtime_nsec error:(NSError **)error;
- (BOOL)applyCreateTimeSec:(int64_t)createTime_sec createTimeNSec:(int64_t)createTime_sec error:(NSError **)error;
@end

View file

@ -1,541 +0,0 @@
//
// FileAttributes.m
// Backup
//
// Created by Stefan Reitshamer on 4/22/09.
// Copyright 2009 PhotoMinds LLC. All rights reserved.
//
#include <CoreServices/CoreServices.h>
#include <sys/attr.h>
#include <unistd.h>
#include <sys/time.h>
#include <stdio.h>
#import "FileAttributes.h"
#import "SHA1Hash.h"
#import "FileACL.h"
#import "SetNSError.h"
#import "OSStatusDescription.h"
#import "NSError_extra.h"
#define kCouldNotCreateCFString 4
#define kCouldNotGetStringData 5
#define MAX_PATH 1024
struct createDateBuf {
u_int32_t length;
struct timespec createTime;
};
static OSStatus ConvertCStringToHFSUniStr(const char* cStr, HFSUniStr255 *uniStr) {
OSStatus oss = noErr;
CFStringRef tmpStringRef = CFStringCreateWithCString(kCFAllocatorDefault, cStr, kCFStringEncodingMacRoman);
if (tmpStringRef != NULL) {
if (CFStringGetCString(tmpStringRef, (char*)uniStr->unicode, sizeof(uniStr->unicode), kCFStringEncodingUnicode)) {
uniStr->length = CFStringGetLength(tmpStringRef);
} else {
oss = kCouldNotGetStringData;
}
CFRelease(tmpStringRef);
} else {
oss = kCouldNotCreateCFString;
}
return oss;
}
static OSStatus SymlinkPathMakeRef(const UInt8 *path, FSRef *ref, Boolean *isDirectory) {
FSRef tmpFSRef;
char tmpPath[MAX_PATH];
char *tmpNamePtr;
OSStatus oss;
strcpy(tmpPath, (char *)path);
tmpNamePtr = strrchr(tmpPath, '/');
if (*(tmpNamePtr + 1) == '\0') {
// Last character in the path is a '/'.
*tmpNamePtr = '\0';
tmpNamePtr = strrchr(tmpPath, '/');
}
*tmpNamePtr = '\0';
tmpNamePtr++;
// Get FSRef for parent directory.
oss = FSPathMakeRef((const UInt8 *)tmpPath, &tmpFSRef, NULL);
if (oss == noErr) {
HFSUniStr255 uniName;
oss = ConvertCStringToHFSUniStr(tmpNamePtr, &uniName);
if (oss == noErr) {
FSRef newFSRef;
oss = FSMakeFSRefUnicode(&tmpFSRef, uniName.length, uniName.unicode, kTextEncodingUnknown, &newFSRef);
tmpFSRef = newFSRef;
}
}
if (oss == noErr) {
*ref = tmpFSRef;
}
return oss;
}
@implementation FileAttributes
- (id)initWithPath:(NSString *)thePath error:(NSError **)error {
struct stat theStat;
int ret = lstat([thePath fileSystemRepresentation], &theStat);
if (ret == -1) {
int errnum = errno;
HSLogError(@"lstat(%@) error %d: %s", thePath, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"%@: %s", thePath, strerror(errnum));
return nil;
}
return [self initWithPath:thePath stat:&theStat error:error];
}
- (id)initWithPath:(NSString *)thePath stat:(struct stat *)theStat error:(NSError **)error {
if (self = [super init]) {
path = [thePath copy];
cPath = [path fileSystemRepresentation];
memcpy(&st, theStat, sizeof(st));
targetExists = YES;
if (S_ISLNK(st.st_mode)) {
struct stat targetSt;
int ret = stat(cPath, &targetSt);
if (ret == -1 && errno == ENOENT) {
targetExists = NO;
}
}
if (targetExists) {
FSRef fsRef;
Boolean isDirectory;
OSStatus oss = 0;
if (S_ISLNK(st.st_mode)) {
oss = SymlinkPathMakeRef((UInt8*)cPath, &fsRef, &isDirectory);
} else {
oss = FSPathMakeRef((UInt8*)cPath, &fsRef, &isDirectory);
}
if (oss == bdNamErr) {
HSLogInfo(@"skipping finder flags for %s: %@", cPath, [OSStatusDescription descriptionForMacOSStatus:oss]);
}else if (oss != noErr) {
SETNSERROR(@"MacFilesErrorDomain", oss, @"error making FSRef for %@: %@", thePath, [OSStatusDescription descriptionForMacOSStatus:oss]);
[self release];
self = nil;
return self;
} else {
FSCatalogInfo catalogInfo;
OSErr oserr = FSGetCatalogInfo(&fsRef, kFSCatInfoCreateDate | kFSCatInfoFinderInfo | kFSCatInfoFinderXInfo, &catalogInfo, NULL, NULL, NULL);
if (oserr) {
SETNSERROR(@"MacFilesErrorDomain", oss, @"FSGetCatalogInfo(%@): %@", thePath, [OSStatusDescription descriptionForMacOSStatus:(OSStatus)oserr]);
[self release];
self = nil;
return self;
}
CFTimeInterval theCreateTime; // double: seconds since reference date
if (UCConvertUTCDateTimeToCFAbsoluteTime(&catalogInfo.createDate, &theCreateTime) != noErr) {
HSLogError(@"error converting create time to CFAbsoluteTime");
} else {
createTime.tv_sec = (int64_t)(theCreateTime + NSTimeIntervalSince1970);
CFTimeInterval subsecond = theCreateTime - (double)((int64_t)theCreateTime);
createTime.tv_nsec = (int64_t)(subsecond * 1000000000.0);
}
finderFlags = 0;
extendedFinderFlags = 0;
if (isDirectory) {
FolderInfo *folderInfo = (FolderInfo *)&catalogInfo.finderInfo;
finderFlags = folderInfo->finderFlags;
ExtendedFolderInfo *extFolderInfo = (ExtendedFolderInfo *)&catalogInfo.extFinderInfo;
extendedFinderFlags = extFolderInfo->extendedFinderFlags;
finderFileType = [[NSString alloc] initWithString:@""];
finderFileCreator = [[NSString alloc] initWithString:@""];
} else {
FileInfo *fileInfo = (FileInfo *)&catalogInfo.finderInfo;
finderFlags = fileInfo->finderFlags;
ExtendedFileInfo *extFileInfo = (ExtendedFileInfo *)&catalogInfo.extFinderInfo;
extendedFinderFlags = extFileInfo->extendedFinderFlags;
char fileType[5];
fileType[0] = *((const char *)&fileInfo->fileType + 3);
fileType[1] = *((const char *)&fileInfo->fileType + 2);
fileType[2] = *((const char *)&fileInfo->fileType + 1);
fileType[3] = *((const char *)&fileInfo->fileType);
fileType[4] = 0;
finderFileType = [[NSString alloc] initWithCString:fileType encoding:NSUTF8StringEncoding];
char fileCreator[5];
fileCreator[0] = *((const char *)&fileInfo->fileCreator + 3);
fileCreator[1] = *((const char *)&fileInfo->fileCreator + 2);
fileCreator[2] = *((const char *)&fileInfo->fileCreator + 1);
fileCreator[3] = *((const char *)&fileInfo->fileCreator);
fileCreator[4] = 0;
finderFileCreator = [[NSString alloc] initWithCString:fileCreator encoding:NSUTF8StringEncoding];
}
}
}
}
return self;
}
- (void)dealloc {
[path release];
[finderFileType release];
[finderFileCreator release];
[super dealloc];
}
- (unsigned long long)fileSize {
return (unsigned long long)st.st_size;
}
- (int)uid {
return st.st_uid;
}
- (int)gid {
return st.st_gid;
}
- (int)mode {
return st.st_mode;
}
- (long)mtime_sec {
return st.st_mtimespec.tv_sec;
}
- (long)mtime_nsec {
return st.st_mtimespec.tv_nsec;
}
- (long)flags {
return st.st_flags;
}
- (int)finderFlags {
return finderFlags;
}
- (int)extendedFinderFlags {
return extendedFinderFlags;
}
- (NSString *)finderFileType {
return finderFileType;
}
- (NSString *)finderFileCreator {
return finderFileCreator;
}
- (BOOL)isExtensionHidden {
return st.st_flags & UF_HIDDEN;
}
- (BOOL)isFifo {
return S_ISFIFO(st.st_mode);
}
- (BOOL)isDevice {
return S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode);
}
- (BOOL)isSymbolicLink {
return S_ISLNK(st.st_mode);
}
- (BOOL)isRegularFile {
return S_ISREG(st.st_mode);
}
- (BOOL)isSocket {
return S_ISSOCK(st.st_mode);
}
- (int)st_dev {
return st.st_dev;
}
- (int)st_ino {
return st.st_ino;
}
- (uint32_t)st_nlink {
return st.st_nlink;
}
- (int)st_rdev {
return st.st_rdev;
}
- (int64_t)ctime_sec {
return st.st_ctimespec.tv_sec;
}
- (int64_t)ctime_nsec {
return st.st_ctimespec.tv_nsec;
}
- (int64_t)createTime_sec {
return createTime.tv_sec;
}
- (int64_t)createTime_nsec {
return createTime.tv_nsec;
}
- (int64_t)st_blocks {
return st.st_blocks;
}
- (uint32_t)st_blksize {
return st.st_blksize;
}
- (BOOL)applyFinderFileType:(NSString *)fft finderFileCreator:(NSString *)ffc error:(NSError **)error {
if (targetExists && (![fft isEqualToString:finderFileType] || ![ffc isEqualToString:finderFileCreator])) {
if ([fft length] != 4) {
HSLogTrace(@"not applying finder file type '%@' to %@: invalid length (must be 4 characters)", fft, path);
} else if ([ffc length] != 4) {
HSLogTrace(@"not applying finder file type '%@' to %@: invalid length (must be 4 characters)", ffc, path);
} else {
FSRef fsRef;
Boolean isDirectory;
OSStatus oss = 0;
if (S_ISLNK(st.st_mode)) {
oss = SymlinkPathMakeRef((UInt8*)cPath, &fsRef, &isDirectory);
} else {
oss = FSPathMakeRef((UInt8*)cPath, &fsRef, &isDirectory);
}
if (oss != noErr) {
if (oss == bdNamErr) {
HSLogInfo(@"not setting finder file type/creator on %s: bad name", cPath);
return YES;
} else {
SETNSERROR(@"FileManagerErrorDomain", -1, @"%@", [OSStatusDescription descriptionForMacOSStatus:oss]);
return NO;
}
}
if (isDirectory) {
SETNSERROR(@"FileManagerErrorDomain", -1, @"cannot apply finderFileType to a directory");
return NO;
}
FSCatalogInfo catalogInfo;
OSErr oserr = FSGetCatalogInfo(&fsRef, kFSCatInfoFinderInfo, &catalogInfo, NULL, NULL, NULL);
if (oserr) {
SETNSERROR(@"FileManagerErrorDomain", -1, @"%@", [OSStatusDescription descriptionForMacOSStatus:(OSStatus)oserr]);
return NO;
}
FileInfo *fileInfo = (FileInfo *)&catalogInfo.finderInfo;
const char *fileType = [fft UTF8String];
char *destFileType = (char *)&fileInfo->fileType;
destFileType[3] = fileType[0];
destFileType[2] = fileType[1];
destFileType[1] = fileType[2];
destFileType[0] = fileType[3];
const char *fileCreator = [ffc UTF8String];
char *destFileCreator = (char *)&fileInfo->fileCreator;
destFileCreator[3] = fileCreator[0];
destFileCreator[2] = fileCreator[1];
destFileCreator[1] = fileCreator[2];
destFileCreator[0] = fileCreator[3];
oserr = FSSetCatalogInfo(&fsRef, kFSCatInfoFinderInfo, &catalogInfo);
if (oserr) {
SETNSERROR(@"FileManagerErrorDomain", -1, @"%@", [OSStatusDescription descriptionForMacOSStatus:(OSStatus)oserr]);
return NO;
}
[finderFileType release];
finderFileType = [fft copy];
[finderFileCreator release];
finderFileCreator = [ffc copy];
}
}
return YES;
}
- (BOOL)applyFlags:(int)flags error:(NSError **)error {
if (targetExists && flags != st.st_flags) {
HSLogTrace(@"chflags(%s, %d)", cPath, flags);
if (chflags(cPath, flags) == -1) {
int errnum = errno;
HSLogError(@"chflags(%s, %d) error %d: %s", cPath, flags, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"error changing flags of %s: %s", cPath, strerror(errnum));
return NO;
}
st.st_flags = flags;
}
return YES;
}
- (BOOL)applyFinderFlags:(int)ff error:(NSError **)error {
if (targetExists && ff != finderFlags) {
FSRef fsRef;
Boolean isDirectory;
OSStatus oss = 0;
if (S_ISLNK(st.st_mode)) {
oss = SymlinkPathMakeRef((UInt8*)cPath, &fsRef, &isDirectory);
} else {
oss = FSPathMakeRef((UInt8*)cPath, &fsRef, &isDirectory);
}
if (oss != noErr) {
if (oss == bdNamErr) {
HSLogInfo(@"not setting finder file type/creator on %s: bad name", cPath);
return YES;
} else {
SETNSERROR(@"FileManagerErrorDomain", -1, @"%@", [OSStatusDescription descriptionForMacOSStatus:oss]);
return NO;
}
}
FSCatalogInfo catalogInfo;
OSErr oserr = FSGetCatalogInfo(&fsRef, kFSCatInfoFinderInfo, &catalogInfo, NULL, NULL, NULL);
if (oserr) {
SETNSERROR(@"FileManagerErrorDomain", -1, @"%@", [OSStatusDescription descriptionForMacOSStatus:(OSStatus)oserr]);
return NO;
}
if (isDirectory) {
FolderInfo *folderInfo = (FolderInfo *)&catalogInfo.finderInfo;
folderInfo->finderFlags = ff;
} else {
FileInfo *fileInfo = (FileInfo *)&catalogInfo.finderInfo;
fileInfo->finderFlags = ff;
}
oserr = FSSetCatalogInfo(&fsRef, kFSCatInfoFinderInfo, &catalogInfo);
if (oserr) {
SETNSERROR(@"FileManagerErrorDomain", -1, @"%@", [OSStatusDescription descriptionForMacOSStatus:(OSStatus)oserr]);
return NO;
}
finderFlags = ff;
}
return YES;
}
- (BOOL)applyExtendedFinderFlags:(int)eff error:(NSError **)error {
if (targetExists && extendedFinderFlags != eff) {
FSRef fsRef;
Boolean isDirectory;
OSStatus oss = 0;
if (S_ISLNK(st.st_mode)) {
oss = SymlinkPathMakeRef((UInt8*)cPath, &fsRef, &isDirectory);
} else {
oss = FSPathMakeRef((UInt8*)cPath, &fsRef, &isDirectory);
}
if (oss != noErr) {
if (oss == bdNamErr) {
HSLogInfo(@"not setting finder file type/creator on %s: bad name", cPath);
return YES;
} else {
SETNSERROR(@"FileManagerErrorDomain", -1, @"%@", [OSStatusDescription descriptionForMacOSStatus:oss]);
return NO;
}
}
FSCatalogInfo catalogInfo;
OSErr oserr = FSGetCatalogInfo(&fsRef, kFSCatInfoFinderXInfo, &catalogInfo, NULL, NULL, NULL);
if (oserr) {
SETNSERROR(@"FileManagerErrorDomain", -1, @"%@", [OSStatusDescription descriptionForMacOSStatus:(OSStatus)oserr]);
return NO;
}
if (isDirectory) {
ExtendedFolderInfo *extFolderInfo = (ExtendedFolderInfo *)&catalogInfo.extFinderInfo;
extFolderInfo->extendedFinderFlags = eff;
} else {
ExtendedFileInfo *extFileInfo = (ExtendedFileInfo *)&catalogInfo.extFinderInfo;
extFileInfo->extendedFinderFlags = eff;
}
oserr = FSSetCatalogInfo(&fsRef, kFSCatInfoFinderXInfo, &catalogInfo);
if (oserr) {
SETNSERROR(@"FileManagerErrorDomain", -1, @"%@", [OSStatusDescription descriptionForMacOSStatus:(OSStatus)oserr]);
return NO;
}
extendedFinderFlags = eff;
}
return YES;
}
- (BOOL)applyExtensionHidden:(BOOL)hidden error:(NSError **)error {
BOOL ret = YES;
if (hidden != [self isExtensionHidden]) {
NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:hidden], NSFileExtensionHidden, nil];
ret = [[NSFileManager defaultManager] setAttributes:attribs ofItemAtPath:path error:error];
if (ret) {
if (hidden) {
st.st_flags = st.st_flags & UF_HIDDEN;
} else {
st.st_flags = st.st_flags & (0xffffffff ^ UF_HIDDEN);
}
}
}
return ret;
}
- (BOOL)applyUID:(int)uid gid:(int)gid error:(NSError **)error {
if (uid != st.st_uid || gid != st.st_gid) {
if (lchown(cPath, uid, gid) == -1) {
int errnum = errno;
HSLogError(@"lchown(%s) error %d: %s", cPath, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"error changing ownership of %s: %s", cPath, strerror(errnum));
return NO;
}
HSLogDebug(@"lchown(%s, %d, %d); euid=%d", cPath, uid, gid, geteuid());
st.st_uid = uid;
st.st_gid = gid;
}
return YES;
}
- (BOOL)applyMode:(int)mode error:(NSError **)error {
if (mode != st.st_mode) {
if (S_ISDIR(st.st_mode)) {
int ret = chmod(cPath, mode);
if (ret == -1) {
int errnum = errno;
HSLogError(@"chmod(%s, %d) error %d: %s", cPath, mode, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"failed to set permissions on %@: %s", path, strerror(errnum));
return NO;
}
HSLogDebug(@"chmod(%s, 0%6o)", cPath, mode);
} else {
int fd = open(cPath, O_RDWR|O_SYMLINK);
if (fd == -1) {
int errnum = errno;
HSLogError(@"open(%s) error %d: %s", cPath, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"failed to open %@: %s", path, strerror(errnum));
return NO;
}
int ret = fchmod(fd, mode);
close(fd);
if (ret == -1) {
int errnum = errno;
HSLogError(@"fchmod(%@) error %d: %s", path, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"failed to set permissions on %@: %s", path, strerror(errnum));
return NO;
}
HSLogDebug(@"fchmod(%s, 0%6o)", cPath, mode);
}
st.st_mode = mode;
}
return YES;
}
- (BOOL)applyMTimeSec:(int64_t)mtime_sec mTimeNSec:(int64_t)mtime_nsec error:(NSError **)error {
if (st.st_mtimespec.tv_sec != mtime_sec
|| st.st_mtimespec.tv_nsec != mtime_nsec) {
struct timespec mtimeSpec = { mtime_sec, mtime_nsec };
struct timeval atimeVal;
struct timeval mtimeVal;
TIMESPEC_TO_TIMEVAL(&atimeVal, &mtimeSpec); // Just use mtime because we don't have atime, nor do we care about atime.
TIMESPEC_TO_TIMEVAL(&mtimeVal, &mtimeSpec);
struct timeval timevals[2];
timevals[0] = atimeVal;
timevals[1] = mtimeVal;
if (utimes(cPath, timevals) == -1) {
int errnum = errno;
HSLogError(@"utimes(%@) error %d: %s", path, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"failed to set timestamps on %@: %s", path, strerror(errnum));
return NO;
}
}
return YES;
}
- (BOOL)applyCreateTimeSec:(int64_t)theCreateTime_sec createTimeNSec:(int64_t)theCreateTime_nsec error:(NSError **)error {
if (createTime.tv_sec != theCreateTime_sec || createTime.tv_nsec != theCreateTime_nsec) {
FSRef fsRef;
Boolean isDirectory;
OSStatus oss = 0;
if (S_ISLNK(st.st_mode)) {
oss = SymlinkPathMakeRef((UInt8*)cPath, &fsRef, &isDirectory);
} else {
oss = FSPathMakeRef((UInt8*)cPath, &fsRef, &isDirectory);
}
if (oss != noErr) {
if (oss == bdNamErr) {
HSLogInfo(@"not setting create time on %s: bad name", cPath);
return YES;
} else {
SETNSERROR(@"FileManagerErrorDomain", -1, @"%@", [OSStatusDescription descriptionForMacOSStatus:oss]);
return NO;
}
}
FSCatalogInfo catalogInfo;
OSErr oserr = FSGetCatalogInfo(&fsRef, kFSCatInfoCreateDate, &catalogInfo, NULL, NULL, NULL);
if (oserr) {
SETNSERROR(@"FileManagerErrorDomain", -1, @"%@", [OSStatusDescription descriptionForMacOSStatus:(OSStatus)oserr]);
return NO;
}
CFTimeInterval theCreateTime = (double)theCreateTime_sec - NSTimeIntervalSince1970 + (double)theCreateTime_nsec / 1000000000.0;
if (UCConvertCFAbsoluteTimeToUTCDateTime(theCreateTime, &catalogInfo.createDate) != noErr) {
SETNSERROR(@"FileManagerErrorDomain", -1, @"unable to convert CFAbsoluteTime %f to UTCDateTime", theCreateTime);
return NO;
}
oserr = FSSetCatalogInfo(&fsRef, kFSCatInfoCreateDate, &catalogInfo);
if (oserr) {
SETNSERROR(@"FileManagerErrorDomain", -1, @"%@", [OSStatusDescription descriptionForMacOSStatus:(OSStatus)oserr]);
return NO;
}
createTime.tv_sec = theCreateTime_sec;
createTime.tv_nsec = theCreateTime_nsec;
}
return YES;
}
@end

View file

@ -0,0 +1,22 @@
//
// GoogleDriveTargetConnection.h
// Arq
//
// Created by Stefan Reitshamer on 7/18/14.
// Copyright (c) 2014 Stefan Reitshamer. All rights reserved.
//
#import "TargetConnection.h"
@class BaseTargetConnection;
@class Target;
@class GoogleDriveRemoteFS;
@interface GoogleDriveTargetConnection : NSObject <TargetConnection> {
GoogleDriveRemoteFS *googleDriveRemoteFS;
BaseTargetConnection *base;
}
- (id)initWithTarget:(Target *)theTarget;
@end

View file

@ -0,0 +1,102 @@
//
// GoogleDriveTargetConnection.m
// Arq
//
// Created by Stefan Reitshamer on 7/18/14.
// Copyright (c) 2014 Stefan Reitshamer. All rights reserved.
//
#import "GoogleDriveTargetConnection.h"
#import "GoogleDriveRemoteFS.h"
#import "BaseTargetConnection.h"
#import "S3ObjectMetadata.h"
@implementation GoogleDriveTargetConnection
- (id)initWithTarget:(Target *)theTarget {
if (self = [super init]) {
googleDriveRemoteFS = [[GoogleDriveRemoteFS alloc] initWithTarget:theTarget];
base = [[BaseTargetConnection alloc] initWithTarget:theTarget remoteFS:googleDriveRemoteFS];
}
return self;
}
- (void)dealloc {
[googleDriveRemoteFS release];
[base release];
[super dealloc];
}
#pragma mark TargetConnection
- (NSArray *)computerUUIDsWithDelegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base computerUUIDsWithDelegate:theDelegate error:error];
}
- (NSArray *)bucketUUIDsForComputerUUID:(NSString *)theComputerUUID deleted:(BOOL)deleted delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base bucketUUIDsForComputerUUID:theComputerUUID deleted:deleted delegate:theDelegate error:error];
}
- (NSData *)bucketPlistDataForComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base bucketPlistDataForComputerUUID:theComputerUUID bucketUUID:theBucketUUID deleted:deleted delegate:theDelegate error:error];
}
- (BOOL)saveBucketPlistData:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base saveBucketPlistData:theData forComputerUUID:theComputerUUID bucketUUID:theBucketUUID deleted:deleted delegate:theDelegate error:error];
}
- (BOOL)deleteBucketPlistDataForComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base deleteBucketPlistDataForComputerUUID:theComputerUUID bucketUUID:theBucketUUID deleted:deleted delegate:theDelegate error:error];
}
- (NSData *)computerInfoForComputerUUID:(NSString *)theComputerUUID delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base computerInfoForComputerUUID:theComputerUUID delegate:theDelegate error:error];
}
- (BOOL)saveComputerInfo:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base saveComputerInfo:theData forComputerUUID:theComputerUUID delegate:theDelegate error:error];
}
- (NSDictionary *)objectsBySHA1ForTargetEndpoint:(NSURL *)theEndpoint isGlacier:(BOOL)theIsGlacier computerUUID:(NSString *)theComputerUUID delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
NSString *prefix = [NSString stringWithFormat:@"%@/%@/objects/", [theEndpoint path], theComputerUUID];
NSArray *objects = [base objectsWithPrefix:prefix delegate:theDelegate error:error];
if (objects == nil) {
return nil;
}
NSMutableDictionary *ret = [NSMutableDictionary dictionary];
for (S3ObjectMetadata *md in objects) {
[ret setObject:md forKey:[[md path] lastPathComponent]];
}
return ret;
}
- (NSArray *)pathsWithPrefix:(NSString *)thePrefix delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base pathsWithPrefix:thePrefix delegate:theDelegate error:error];
}
- (BOOL)deleteObjectsForComputerUUID:(NSString *)theComputerUUID delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base deleteObjectsForComputerUUID:theComputerUUID delegate:theDelegate error:error];
}
- (BOOL)deletePaths:(NSArray *)thePaths delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base deletePaths:thePaths delegate:theDelegate error:error];
}
- (NSNumber *)fileExistsAtPath:(NSString *)thePath dataSize:(unsigned long long *)theDataSize delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base fileExistsAtPath:thePath dataSize:theDataSize delegate:theDelegate error:error];
}
- (NSData *)contentsOfFileAtPath:(NSString *)thePath delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base contentsOfFileAtPath:thePath delegate:theDelegate error:error];
}
- (BOOL)writeData:(NSData *)theData toFileAtPath:(NSString *)thePath dataTransferDelegate:(id <DataTransferDelegate>)theDataTransferDelegate targetConnectionDelegate:(id<TargetConnectionDelegate>)theTargetConnectionDelegate error:(NSError **)error {
return [base writeData:theData toFileAtPath:thePath dataTransferDelegate:theDataTransferDelegate targetConnectionDelegate:theTargetConnectionDelegate error:error];
}
- (BOOL)removeItemAtPath:(NSString *)thePath delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base removeItemAtPath:thePath delegate:theDelegate error:error];
}
- (NSNumber *)sizeOfItemAtPath:(NSString *)thePath delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base sizeOfItemAtPath:thePath delegate:theDelegate error:error];
}
- (NSNumber *)isObjectRestoredAtPath:(NSString *)thePath delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base isObjectRestoredAtPath:thePath delegate:theDelegate error:error];
}
- (BOOL)restoreObjectAtPath:(NSString *)thePath forDays:(NSUInteger)theDays alreadyRestoredOrRestoring:(BOOL *)alreadyRestoredOrRestoring delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base restoreObjectAtPath:thePath forDays:theDays alreadyRestoredOrRestoring:alreadyRestoredOrRestoring delegate:theDelegate error:error];
}
- (NSData *)saltDataForComputerUUID:(NSString *)theComputerUUID delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base saltDataForComputerUUID:theComputerUUID delegate:theDelegate error:error];
}
- (BOOL)setSaltData:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base setSaltData:theData forComputerUUID:theComputerUUID delegate:theDelegate error:error];
}
@end

13
NSString_slashed.h Normal file
View file

@ -0,0 +1,13 @@
//
// NSString_slashed.h
// Arq
//
// Created by Stefan Reitshamer on 4/22/11.
// Copyright 2011 Haystack Software. All rights reserved.
//
@interface NSString (slashed)
- (NSString *)slashed;
@end

18
NSString_slashed.m Normal file
View file

@ -0,0 +1,18 @@
//
// NSString_slashed.m
// Arq
//
// Created by Stefan Reitshamer on 4/22/11.
// Copyright 2011 Haystack Software. All rights reserved.
//
#import "NSString_slashed.h"
@implementation NSString (slashed)
- (NSString *)slashed {
if ([self isEqualToString:@"/"]) {
return self;
}
return [self stringByAppendingString:@"/"];
}
@end

View file

@ -1,23 +0,0 @@
//
// PackIndexEntry.h
// Arq
//
// Created by Stefan Reitshamer on 12/30/09.
// Copyright 2009 __MyCompanyName__. All rights reserved.
//
@interface PackIndexEntry : NSObject {
NSString *packSHA1;
unsigned long long offset;
unsigned long long dataLength;
NSString *objectSHA1;
}
- (id)initWithPackSHA1:(NSString *)thePackSHA1 offset:(unsigned long long)theOffset dataLength:(unsigned long long)theDataLength objectSHA1:(NSString *)theObjectSHA1;
- (NSString *)packSHA1;
- (unsigned long long)offset;
- (unsigned long long)dataLength;
- (NSString *)objectSHA1;
@end

View file

@ -1,44 +0,0 @@
//
// PackIndexEntry.m
// Arq
//
// Created by Stefan Reitshamer on 12/30/09.
// Copyright 2009 __MyCompanyName__. All rights reserved.
//
#import "PackIndexEntry.h"
@implementation PackIndexEntry
- (id)initWithPackSHA1:(NSString *)thePackSHA1 offset:(unsigned long long)theOffset dataLength:(unsigned long long)theDataLength objectSHA1:(NSString *)theObjectSHA1 {
if (self = [super init]) {
packSHA1 = [thePackSHA1 copy];
offset = theOffset;
dataLength = theDataLength;
objectSHA1 = [theObjectSHA1 copy];
}
return self;
}
- (void)dealloc {
[packSHA1 release];
[objectSHA1 release];
[super dealloc];
}
- (NSString *)packSHA1 {
return packSHA1;
}
- (unsigned long long)offset {
return offset;
}
- (unsigned long long)dataLength {
return dataLength;
}
- (NSString *)objectSHA1 {
return objectSHA1;
}
#pragma mark NSObject
- (NSString *)description {
return [NSString stringWithFormat:@"<PackIndexEntry: packSHA1=%@ offset=%qu dataLength=%qu objectSHA1=%@>", packSHA1, offset, dataLength, objectSHA1];
}
@end

View file

@ -1,22 +0,0 @@
//
// PackIndexWriter.h
// Arq
//
// Created by Stefan Reitshamer on 3/3/10.
// Copyright 2010 __MyCompanyName__. All rights reserved.
//
@class DiskPack;
@interface PackIndexWriter : NSObject {
DiskPack *diskPack;
NSString *destination;
uid_t targetUID;
gid_t targetGID;
}
- (id)initWithPack:(DiskPack *)theDiskPack destination:(NSString *)theDestination
targetUID:(uid_t)theTargetUID
targetGID:(gid_t)theTargetGID;
- (BOOL)writeIndex:(NSError **)error;
@end

View file

@ -1,121 +0,0 @@
//
// PackIndexWriter.m
// Arq
//
// Created by Stefan Reitshamer on 3/3/10.
// Copyright 2010 __MyCompanyName__. All rights reserved.
//
#import "PackIndexWriter.h"
#import "DiskPack.h"
#import "FileInputStream.h"
#import "FileOutputStream.h"
#import "IntegerIO.h"
#import "StringIO.h"
#import "SetNSError.h"
#import "SHA1Hash.h"
#import "NSString_extra.h"
#import "PackIndexEntry.h"
#import "BufferedOutputStream.h"
@interface PackIndexWriter (internal)
- (BOOL)appendSHA1:(NSString *)theSHA1 error:(NSError **)error;
- (BOOL)writeEntries:(NSArray *)entries toStream:(BufferedOutputStream *)bos error:(NSError **)error;
@end
@implementation PackIndexWriter
- (id)initWithPack:(DiskPack *)theDiskPack destination:(NSString *)theDestination
targetUID:(uid_t)theTargetUID
targetGID:(gid_t)theTargetGID {
if (self = [super init]) {
diskPack = [theDiskPack retain];
destination = [theDestination copy];
targetUID = theTargetUID;
targetGID = theTargetGID;
}
return self;
}
- (void)dealloc {
[diskPack release];
[destination release];
[super dealloc];
}
- (BOOL)writeIndex:(NSError **)error {
NSArray *entries = [diskPack sortedPackIndexEntries:error];
if (entries == nil) {
return NO;
}
BufferedOutputStream *bos = [[BufferedOutputStream alloc] initWithPath:destination targetUID:targetUID targetGID:targetGID append:NO];
BOOL ret = [self writeEntries:entries toStream:bos error:error];
if (![bos flush:error]) {
ret = NO;
}
[bos release];
if (!ret) {
return NO;
}
NSString *indexSHA1 = [SHA1Hash hashFile:destination error:error];
if (![self appendSHA1:indexSHA1 error:error]) {
return NO;
}
return ret;
}
@end
@implementation PackIndexWriter (internal)
- (BOOL)appendSHA1:(NSString *)theSHA1 error:(NSError **)error {
NSData *sha1Data = [theSHA1 hexStringToData];
BufferedOutputStream *bos = [[BufferedOutputStream alloc] initWithPath:destination targetUID:targetUID targetGID:targetGID append:YES];
BOOL ret = [bos writeFully:[sha1Data bytes] length:[sha1Data length] error:error];
if (![bos flush:error]) {
ret = NO;
}
[bos release];
return ret;
}
- (BOOL)writeEntries:(NSArray *)entries toStream:(BufferedOutputStream *)bos error:(NSError **)error {
// Write header to index.
if (![IntegerIO writeUInt32:0xff744f63 to:bos error:error]) { // Magic number.
return NO;
}
if (![IntegerIO writeUInt32:0x00000002 to:bos error:error]) { // Version 2.
return NO;
}
unsigned int firstByte = 0;
NSUInteger index = 0;
for (index = 0; index < [entries count]; index++) {
PackIndexEntry *pie = [entries objectAtIndex:index];
NSData *sha1Hex = [[pie objectSHA1] hexStringToData];
unsigned char myFirstByte = ((unsigned char *)[sha1Hex bytes])[0];
while ((unsigned int)myFirstByte > firstByte) {
if (![IntegerIO writeUInt32:index to:bos error:error]) {
return NO;
}
firstByte++;
}
}
while (firstByte <= 0xff) {
if (![IntegerIO writeUInt32:index to:bos error:error]) {
return NO;
}
firstByte++;
}
for (index = 0; index < [entries count]; index++) {
PackIndexEntry *pie = [entries objectAtIndex:index];
if (![IntegerIO writeUInt64:[pie offset] to:bos error:error]
|| ![IntegerIO writeUInt64:[pie dataLength] to:bos error:error]) {
return NO;
}
// Write sha1 to index.
NSData *sha1Data = [[pie objectSHA1] hexStringToData];
if (![bos writeFully:[sha1Data bytes] length:[sha1Data length] error:error]) {
break;
}
// Write 4 bytes (for alignment) to index.
if (![IntegerIO writeUInt32:0 to:bos error:error]) {
return NO;
}
}
return YES;
}
@end

View file

@ -1,20 +0,0 @@
//
// ReflogEntry.h
// arq_restore
//
// Created by Stefan Reitshamer on 11/20/11.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
@class BlobKey;
@interface ReflogEntry : NSObject {
BlobKey *oldHeadBlobKey;
BlobKey *newHeadBlobKey;
}
- (id)initWithData:(NSData *)theData error:(NSError **)error;
- (BlobKey *)oldHeadBlobKey;
- (BlobKey *)newHeadBlobKey;
@end

View file

@ -1,55 +0,0 @@
//
// ReflogEntry.m
// arq_restore
//
// Created by Stefan Reitshamer on 11/20/11.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
#import "ReflogEntry.h"
#import "BlobKey.h"
#import "DictNode.h"
#import "SetNSError.h"
@implementation ReflogEntry
- (id)initWithData:(NSData *)theData error:(NSError **)error {
if (self = [super init]) {
DictNode *dictNode = [DictNode dictNodeWithXMLData:theData error:error];
if (dictNode == nil) {
[self release];
return nil;
}
if (![dictNode containsKey:@"oldHeadSHA1"]
|| ![dictNode containsKey:@"oldHeadStretchKey"]
|| ![dictNode containsKey:@"newHeadSHA1"]
|| ![dictNode containsKey:@"newHeadStretchKey"]) {
SETNSERROR(@"ReflogEntryErrorDomain", -1, @"missing values in reflog entry");
[self release];
return nil;
}
oldHeadBlobKey = [[BlobKey alloc] initWithSHA1:[[dictNode stringNodeForKey:@"oldHeadSHA1"] stringValue]
storageType:StorageTypeS3
stretchEncryptionKey:[[dictNode booleanNodeForKey:@"oldHeadStretchKey"] booleanValue]
compressed:NO];
newHeadBlobKey = [[BlobKey alloc] initWithSHA1:[[dictNode stringNodeForKey:@"newHeadSHA1"] stringValue]
storageType:StorageTypeS3
stretchEncryptionKey:[[dictNode booleanNodeForKey:@"newHeadStretchKey"] booleanValue]
compressed:NO];
}
return self;
}
- (void)dealloc {
[oldHeadBlobKey release];
[newHeadBlobKey release];
[super dealloc];
}
- (BlobKey *)oldHeadBlobKey {
return oldHeadBlobKey;
}
- (BlobKey *)newHeadBlobKey {
return newHeadBlobKey;
}
@end

View file

@ -1,22 +0,0 @@
//
// ReflogPrinter.h
// arq_restore
//
// Created by Stefan Reitshamer on 11/21/11.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
@class S3Service;
@class ArqRepo;
@interface ReflogPrinter : NSObject {
NSString *s3BucketName;
NSString *computerUUID;
NSString *bucketUUID;
S3Service *s3;
ArqRepo *repo;
}
- (id)initWithS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID s3:(S3Service *)theS3 repo:(ArqRepo *)theRepo;
- (BOOL)printReflog:(NSError **)error;
@end

View file

@ -1,99 +0,0 @@
//
// ReflogPrinter.m
// arq_restore
//
// Created by Stefan Reitshamer on 11/21/11.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
#import "ReflogPrinter.h"
#import "S3Service.h"
#import "ArqRepo.h"
#import "ReflogEntry.h"
#import "Commit.h"
#import "BlobKey.h"
@interface ReflogPrinter (internal)
- (BOOL)printEntry:(NSString *)path error:(NSError **)error;
@end
@implementation ReflogPrinter
- (id)initWithS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID s3:(S3Service *)theS3 repo:(ArqRepo *)theRepo {
if (self = [super init]) {
s3BucketName = [theS3BucketName retain];
computerUUID = [theComputerUUID retain];
bucketUUID = [theBucketUUID retain];
s3 = [theS3 retain];
repo = [theRepo retain];
}
return self;
}
- (void)dealloc {
[s3BucketName release];
[computerUUID release];
[bucketUUID release];
[s3 release];
[repo release];
[super dealloc];
}
- (BOOL)printReflog:(NSError **)error {
NSString *prefix = [NSString stringWithFormat:@"/%@/%@/bucketdata/%@/refs/logs/master/", s3BucketName, computerUUID, bucketUUID];
NSArray *paths = [s3 pathsWithPrefix:prefix error:error];
if (paths == nil) {
return NO;
}
NSSortDescriptor *descriptor = [[[NSSortDescriptor alloc] initWithKey:@"description" ascending:NO] autorelease];
NSArray *sortedPaths = [paths sortedArrayUsingDescriptors:[NSArray arrayWithObject:descriptor]];
BOOL ret = YES;
NSAutoreleasePool *pool = nil;
for (NSString *path in sortedPaths) {
[pool drain];
pool = [[NSAutoreleasePool alloc] init];
if (![self printEntry:path error:error]) {
ret = NO;
break;
}
}
if (!ret && error != NULL) {
[*error retain];
}
[pool drain];
if (!ret && error != NULL) {
[*error autorelease];
}
return ret;
}
@end
@implementation ReflogPrinter (internal)
- (BOOL)printEntry:(NSString *)path error:(NSError **)error {
printf("reflog %s\n", [path UTF8String]);
NSData *data = [s3 dataAtPath:path error:error];
if (data == nil) {
return NO;
}
NSError *myError = nil;
ReflogEntry *entry = [[[ReflogEntry alloc] initWithData:data error:&myError] autorelease];
if (entry == nil) {
printf("\terror reading reflog entry: %s\n\n", [[myError description] UTF8String]);
} else {
Commit *commit = [repo commitForBlobKey:[entry newHeadBlobKey] error:&myError];
if (commit == nil) {
printf("\t%s\n\n", [[myError localizedDescription] UTF8String]);
} else {
printf("\tblobkey: %s\n", [[[entry newHeadBlobKey] description] UTF8String]);
printf("\tauthor: %s\n", [[commit author] UTF8String]);
printf("\tdate: %s\n", [[[commit creationDate] description] UTF8String]);
printf("\tlocation: %s\n", [[commit location] UTF8String]);
printf("\trestore command: arq_restore /%s/%s/buckets/%s %s\n\n", [s3BucketName UTF8String], [computerUUID UTF8String], [bucketUUID UTF8String],
[[[entry newHeadBlobKey] sha1] UTF8String]);
}
}
return YES;
}
@end

View file

@ -1,48 +0,0 @@
/*
Copyright (c) 2009, 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 "InputStream.h"
#import "OutputStream.h"
@class Tree;
@class Node;
@interface RestoreNode : NSObject {
Tree *tree;
NSString *nodeName;
NSString *relativePath;
}
- (id)initWithTree:(Tree *)theTree nodeName:(NSString *)theNodeName relativePath:(NSString *)theRelativePath;
- (Tree *)tree;
- (Node *)node;
- (NSString *)relativePath;
@end

View file

@ -1,63 +0,0 @@
/*
Copyright (c) 2009, 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 "RestoreNode.h"
#import "Tree.h"
#import "Node.h"
#import "StringIO.h"
@implementation RestoreNode
- (id)initWithTree:(Tree *)theTree nodeName:(NSString *)theNodeName relativePath:(NSString *)theRelativePath {
if (self = [super init]) {
tree = [theTree retain];
nodeName = [theNodeName copy];
NSAssert((nodeName == nil) || ([tree childNodeWithName:nodeName] != nil), @"node doesn't exist in Tree!");
relativePath = [theRelativePath copy];
}
return self;
}
- (void)dealloc {
[tree release];
[nodeName release];
[relativePath release];
[super dealloc];
}
- (Tree *)tree {
return tree;
}
- (Node *)node {
return [tree childNodeWithName:nodeName];
}
- (NSString *)relativePath {
return relativePath;
}
@end

View file

@ -1,60 +0,0 @@
/*
Copyright (c) 2009, 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.
*/
@class S3Service;
@class ArqRepo;
@class BlobKey;
@class Commit;
@class Tree;
@interface Restorer : NSObject {
ArqRepo *repo;
NSString *bucketName;
NSString *commitSHA1;
NSString *rootPath;
NSUInteger superUserNodeCount;
NSMutableArray *restoreNodes;
NSMutableDictionary *hardlinks;
unsigned long long writtenToCurrentFile;
NSMutableDictionary *errorsByPath;
int myUID;
int myGID;
unsigned long long transferred;
unsigned long long total;
Tree *rootTree;
}
- (id)initWithRepo:(ArqRepo *)theRepo bucketName:(NSString *)theBucketName commitSHA1:(NSString *)theCommitSHA1;
- (BOOL)restore:(NSError **)error;
- (NSDictionary *)errorsByPath;
@end

View file

@ -1,700 +0,0 @@
/*
Copyright (c) 2009, 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.
*/
#include <sys/types.h>
#include <sys/stat.h>
#import "Restorer.h"
#import "ArqRepo.h"
#import "SetNSError.h"
#import "Tree.h"
#import "Node.h"
#import "RestoreNode.h"
#import "FileAttributes.h"
#import "NSData-InputStream.h"
#import "DataInputStream.h"
#import "XAttrSet.h"
#import "FileOutputStream.h"
#import "NSFileManager_extra.h"
#import "NSErrorCodes.h"
#import "BufferedInputStream.h"
#import "BufferedOutputStream.h"
#import "NSData-Gzip.h"
#import "GunzipInputStream.h"
#import "FileACL.h"
#import "BlobKey.h"
#define MAX_RETRIES (10)
#define MY_BUF_SIZE (8192)
@interface Restorer (internal)
+ (NSString *)errorDomain;
- (BOOL)restoreTree:(Tree *)theTree toPath:(NSString *)thePath error:(NSError **)error;
- (BOOL)restoreNode:(Node *)theNode ofTree:(Tree *)theTree toPath:(NSString *)thePath error:(NSError **)error;
- (BOOL)needSuperUserForTree:(Tree *)theTree;
- (BOOL)needSuperUserForTree:(Tree *)theTree node:(Node *)theNode;
- (BOOL)chownNode:(Node *)theNode ofTree:(Tree *)theTree atPath:(NSString *)thePath error:(NSError **)error;
- (BOOL)chownTree:(Tree *)theTree atPath:(NSString *)thePath error:(NSError **)error;
- (BOOL)applyUID:(int)theUID gid:(int)theGID mode:(int)theMode rdev:(int)theRdev toPath:(NSString *)thePath error:(NSError **)error;
- (BOOL)applyTree:(Tree *)tree toPath:(NSString *)restorePath error:(NSError **)error;
- (BOOL)applyNode:(Node *)node toPath:(NSString *)restorePath error:(NSError **)error;
- (BOOL)createFile:(Node *)node atPath:(NSString *)path error:(NSError **)error;
- (BOOL)createFileAtPath:(NSString *)path fromBlobKeys:(NSArray *)dataBlobKeys error:(NSError **)error;
- (BOOL)appendBlobForBlobKey:(BlobKey *)theBlobKey to:(FileOutputStream *)fos error:(NSError **)error;
- (BOOL)doAppendBlobForBlobKey:(BlobKey *)theBlobKey to:(BufferedOutputStream *)bos error:(NSError **)error;
- (BOOL)createSymLink:(Node *)node path:(NSString *)symLinkFile target:(NSString *)target error:(NSError **)error;
- (BOOL)applyACLBlobKey:(BlobKey *)aclBlobKey uncompress:(BOOL)uncompress toPath:(NSString *)path error:(NSError **)error;
- (BOOL)applyXAttrsBlobKey:(BlobKey *)xattrsBlobKey uncompress:(BOOL)uncompress toFile:(NSString *)path error:(NSError **)error;
- (void)addError:(NSError *)theError forPath:(NSString *)thePath;
@end
@implementation Restorer
- (id)initWithRepo:(ArqRepo *)theArqRepo bucketName:(NSString *)theBucketName commitSHA1:(NSString *)theCommitSHA1 {
if (self = [super init]) {
repo = [theArqRepo retain];
bucketName = [theBucketName copy];
commitSHA1 = [theCommitSHA1 copy];
rootPath = [[[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingPathComponent:theBucketName] copy];
restoreNodes = [[NSMutableArray alloc] init];
hardlinks = [[NSMutableDictionary alloc] init];
errorsByPath = [[NSMutableDictionary alloc] init];
myUID = geteuid();
myGID = getgid();
}
return self;
}
- (void)dealloc {
[repo release];
[bucketName release];
[commitSHA1 release];
[rootPath release];
[restoreNodes release];
[hardlinks release];
[errorsByPath release];
[rootTree release];
[super dealloc];
}
- (BOOL)restore:(NSError **)error {
if ([[NSFileManager defaultManager] fileExistsAtPath:rootPath]) {
SETNSERROR([Restorer errorDomain], -1, @"%@ already exists", rootPath);
return NO;
}
if (![[NSFileManager defaultManager] createDirectoryAtPath:rootPath withIntermediateDirectories:YES attributes:nil error:error]) {
HSLogError(@"failed to create directory %@", rootPath);
return NO;
}
BlobKey *commitBlobKey = nil;
Commit *commit = nil;
NSError *myError = nil;
if (commitSHA1 != nil) {
commitBlobKey = [[[BlobKey alloc] initWithSHA1:commitSHA1 storageType:StorageTypeS3 stretchEncryptionKey:YES compressed:NO] autorelease];
commit = [repo commitForBlobKey:commitBlobKey error:&myError];
if (commit == nil) {
HSLogError(@"error attempting to read commit for %@", commitBlobKey);
// Try without stretched encryption key.
commitBlobKey = [[[BlobKey alloc] initWithSHA1:commitSHA1 storageType:StorageTypeS3 stretchEncryptionKey:NO compressed:NO] autorelease];
commit = [repo commitForBlobKey:commitBlobKey error:&myError];
if (commit == nil) {
HSLogError(@"error attempting to read commit for %@", commitBlobKey);
if (error != NULL) {
*error = myError;
}
return NO;
}
}
} else {
commitBlobKey = [[repo headBlobKey:error] retain];
if (commitBlobKey == nil) {
SETNSERROR([Restorer errorDomain], -1, @"no backup found");
return NO;
}
commit = [repo commitForBlobKey:commitBlobKey error:error];
if (commit == nil) {
return NO;
}
}
printf("restoring %scommit %s\n", (commitSHA1 == nil ? "head " : ""), [[commitBlobKey description] UTF8String]);
rootTree = [[repo treeForBlobKey:[commit treeBlobKey] error:error] retain];
if (rootTree == nil) {
return NO;
}
if (![self restoreTree:rootTree toPath:rootPath error:error]) {
return NO;
}
return YES;
}
- (NSDictionary *)errorsByPath {
return errorsByPath;
}
@end
@implementation Restorer (internal)
+ (NSString *)errorDomain {
return @"RestorerErrorDomain";
}
- (BOOL)restoreTree:(Tree *)theTree toPath:(NSString *)thePath error:(NSError **)error {
NSNumber *inode = [NSNumber numberWithInt:[theTree st_ino]];
NSString *existing = nil;
if ([theTree st_nlink] > 1) {
existing = [hardlinks objectForKey:inode];
}
if (existing != nil) {
// Link.
if (link([existing fileSystemRepresentation], [thePath fileSystemRepresentation]) == -1) {
int errnum = errno;
SETNSERROR([Restorer errorDomain], -1, @"link(%@,%@): %s", existing, thePath, strerror(errnum));
HSLogError(@"link() failed");
return NO;
}
} else {
if (![[NSFileManager defaultManager] fileExistsAtPath:thePath]
&& ![[NSFileManager defaultManager] createDirectoryAtPath:thePath withIntermediateDirectories:YES attributes:nil error:error]) {
return NO;
}
NSAutoreleasePool *pool = nil;
BOOL ret = YES;
for (NSString *childNodeName in [theTree childNodeNames]) {
[pool drain];
pool = [[NSAutoreleasePool alloc] init];
Node *childNode = [theTree childNodeWithName:childNodeName];
NSString *childPath = [thePath stringByAppendingPathComponent:childNodeName];
if ([childNode isTree]) {
Tree *childTree = [repo treeForBlobKey:[childNode treeBlobKey] error:error];
if (childTree == nil) {
ret = NO;
break;
}
NSError *restoreError = nil;
if (![self restoreTree:childTree toPath:childPath error:&restoreError]) {
HSLogDebug(@"error restoring %@: %@", childPath, restoreError);
if ([restoreError isErrorWithDomain:[Restorer errorDomain] code:ERROR_ABORT_REQUESTED]) {
ret = NO;
if (error != NULL) {
*error = restoreError;
}
break;
}
[self addError:restoreError forPath:childPath];
}
} else {
NSError *restoreError = nil;
if (![self restoreNode:childNode ofTree:theTree toPath:childPath error:&restoreError]) {
if ([restoreError isErrorWithDomain:[Restorer errorDomain] code:ERROR_ABORT_REQUESTED]) {
ret = NO;
if (error != NULL) {
*error = restoreError;
}
break;
}
HSLogDebug(@"error restoring %@: %@", childPath, restoreError);
[self addError:restoreError forPath:childPath];
}
}
}
if (error != NULL) { [*error retain]; }
[pool drain];
if (error != NULL) { [*error autorelease]; }
if (!ret) {
return NO;
}
if (![self applyTree:theTree toPath:thePath error:error]) {
return NO;
}
[hardlinks setObject:thePath forKey:inode];
if ([self needSuperUserForTree:theTree]) {
superUserNodeCount++;
}
}
return YES;
}
- (BOOL)restoreNode:(Node *)theNode ofTree:(Tree *)theTree toPath:(NSString *)thePath error:(NSError **)error {
NSAssert(theNode != nil, @"theNode can't be nil");
NSAssert(theTree != nil, @"theTree can't be nil");
NSNumber *inode = [NSNumber numberWithInt:[theNode st_ino]];
NSString *existing = nil;
if ([theNode st_nlink] > 1) {
existing = [hardlinks objectForKey:inode];
}
if (existing != nil) {
// Link.
if (link([existing fileSystemRepresentation], [thePath fileSystemRepresentation]) == -1) {
int errnum = errno;
SETNSERROR([Restorer errorDomain], -1, @"link(%@,%@): %s", existing, thePath, strerror(errnum));
HSLogError(@"link() failed");
return NO;
}
} else {
int mode = [theNode mode];
if (S_ISFIFO(mode)) {
if (mkfifo([thePath fileSystemRepresentation], mode) == -1) {
int errnum = errno;
SETNSERROR([Restorer errorDomain], errnum, @"mkfifo(%@): %s", thePath, strerror(errnum));
return NO;
}
if (![self applyNode:theNode toPath:thePath error:error]) {
HSLogError(@"applyNode error");
return NO;
}
} else if (S_ISSOCK(mode)) {
// Skip socket -- restoring it doesn't make any sense.
} else if (S_ISCHR(mode)) {
// character device: needs to be done as super-user.
} else if (S_ISBLK(mode)) {
// block device: needs to be done as super-user.
} else {
if (![self createFile:theNode atPath:thePath error:error]) {
HSLogError(@"createFile error");
return NO;
}
if (![self applyNode:theNode toPath:thePath error:error]) {
HSLogError(@"applyNode error");
return NO;
}
}
[hardlinks setObject:thePath forKey:inode];
if ([self needSuperUserForTree:theTree node:theNode]) {
superUserNodeCount++;
}
}
return YES;
}
- (BOOL)needSuperUserForTree:(Tree *)theTree {
NSAssert(theTree != nil, @"theTree can't be nil");
int uid = [theTree uid];
int gid = [theTree gid];
int mode = [theTree mode];
if ((uid != myUID) || (gid != myGID)) {
return YES;
}
if (mode & (S_ISUID|S_ISGID|S_ISVTX)) {
return YES;
}
return NO;
}
- (BOOL)needSuperUserForTree:(Tree *)theTree node:(Node *)theNode {
NSAssert(theNode != nil, @"theNode can't be nil");
NSAssert(theTree != nil, @"theTree can't be nil");
int uid = [theNode uid];
int gid = [theNode gid];
int mode = [theNode mode];
if ([theTree treeVersion] >= 7 && (S_ISCHR(mode) || S_ISBLK(mode))) {
return YES;
}
if ((uid != myUID) || (gid != myGID)) {
return YES;
}
if (mode & (S_ISUID|S_ISGID|S_ISVTX)) {
return YES;
}
return NO;
}
- (BOOL)chownNode:(Node *)theNode ofTree:(Tree *)theTree atPath:(NSString *)thePath error:(NSError **)error {
if ([[errorsByPath allKeys] containsObject:thePath]) {
HSLogDebug(@"error restoring %@; skipping chownNode", thePath);
return YES;
}
if ([self needSuperUserForTree:theTree node:theNode]) {
if (![self applyUID:[theNode uid] gid:[theNode gid] mode:[theNode mode] rdev:[theNode st_rdev] toPath:thePath error:error]) {
return NO;
}
}
return YES;
}
- (BOOL)chownTree:(Tree *)theTree atPath:(NSString *)thePath error:(NSError **)error {
if ([[errorsByPath allKeys] containsObject:thePath]) {
HSLogDebug(@"error restoring %@; skipping chownTree", thePath);
return YES;
}
NSAutoreleasePool *pool = nil;
BOOL ret = YES;
for (NSString *childNodeName in [theTree childNodeNames]) {
[pool drain];
pool = [[NSAutoreleasePool alloc] init];
Node *childNode = [theTree childNodeWithName:childNodeName];
NSString *childPath = [thePath stringByAppendingPathComponent:childNodeName];
if ([childNode isTree]) {
Tree *childTree = [repo treeForBlobKey:[childNode treeBlobKey] error:error];
if (childTree == nil) {
ret = NO;
break;
}
if (![self chownTree:childTree atPath:childPath error:error]) {
ret = NO;
break;
}
} else {
if (![self chownNode:childNode ofTree:theTree atPath:childPath error:error]) {
ret = NO;
break;
}
}
}
if (error != NULL) { [*error retain]; }
[pool drain];
if (error != NULL) { [*error autorelease]; }
if (!ret) {
return NO;
}
if ([self needSuperUserForTree:theTree]) {
if (![self applyUID:[theTree uid] gid:[theTree gid] mode:[theTree mode] rdev:[theTree st_rdev] toPath:thePath error:error]) {
return NO;
}
}
return YES;
}
- (BOOL)applyUID:(int)theUID gid:(int)theGID mode:(int)theMode rdev:(int)theRdev toPath:(NSString *)thePath error:(NSError **)error {
if (S_ISCHR(theMode) || S_ISBLK(theMode)) {
if (![[NSFileManager defaultManager] ensureParentPathExistsForPath:thePath error:error]) {
return NO;
}
HSLogDebug(@"mknod(%@, %d, %d)", thePath, theMode, theRdev);
if (mknod([thePath fileSystemRepresentation], theMode, theRdev) == -1) {
int errnum = errno;
HSLogError(@"mknod(%@) error %d: %s", thePath, errnum, strerror(errnum));
SETNSERROR([Restorer errorDomain], -1, @"failed to make device node %@: %s", thePath, strerror(errnum));
return NO;
}
}
FileAttributes *fa = [[[FileAttributes alloc] initWithPath:thePath error:error] autorelease];
if (fa == nil) {
return NO;
}
int flags = [fa flags];
if (flags) {
// Clear the flags temporarily so we can change ownership of the file.
if (![fa applyFlags:0 error:error]) {
return NO;
}
}
if (![fa applyMode:theMode error:error]) {
return NO;
}
if (![fa applyUID:theUID gid:theGID error:error]) {
return NO;
}
if (flags) {
if (![fa applyFlags:flags error:error]) {
return NO;
}
}
return YES;
}
- (BOOL)applyTree:(Tree *)tree toPath:(NSString *)path error:(NSError **)error {
FileAttributes *fa = [[[FileAttributes alloc] initWithPath:path error:error] autorelease];
if (!fa) {
return NO;
}
if (![self applyXAttrsBlobKey:[tree xattrsBlobKey] uncompress:[tree xattrsAreCompressed] toFile:path error:error]) {
return NO;
}
if (![fa applyFinderFlags:[tree finderFlags] error:error]
|| ![fa applyExtendedFinderFlags:[tree extendedFinderFlags] error:error]) {
return NO;
}
if (([tree mode] & (S_ISUID|S_ISGID|S_ISVTX)) && ![fa applyMode:[tree mode] error:error]) {
return NO;
}
if (!S_ISLNK([tree mode]) && [tree treeVersion] >= 7 && ![fa applyMTimeSec:tree.mtime_sec mTimeNSec:tree.mtime_nsec error:error]) {
return NO;
}
if (([tree treeVersion] >= 7) && ![fa applyCreateTimeSec:tree.createTime_sec createTimeNSec:tree.createTime_nsec error:error]) {
return NO;
}
if (![fa applyFlags:[tree flags] error:error]) {
return NO;
}
if (![self applyACLBlobKey:[tree aclBlobKey] uncompress:[tree aclIsCompressed] toPath:path error:error]) {
return NO;
}
return YES;
}
- (BOOL)applyNode:(Node *)node toPath:(NSString *)path error:(NSError **)error {
FileAttributes *fa = [[[FileAttributes alloc] initWithPath:path error:error] autorelease];
if (!fa) {
return NO;
}
if (![self applyXAttrsBlobKey:[node xattrsBlobKey] uncompress:[[node xattrsBlobKey] compressed] toFile:path error:error]) {
return NO;
}
if (![self applyACLBlobKey:[node aclBlobKey] uncompress:[[node aclBlobKey] compressed] toPath:path error:error]) {
return NO;
}
if (!S_ISFIFO([node mode])) {
if (![fa applyFinderFlags:[node finderFlags] error:error]
|| ![fa applyExtendedFinderFlags:[node extendedFinderFlags] error:error]
|| ![fa applyFinderFileType:[node finderFileType] finderFileCreator:[node finderFileCreator] error:error]) {
return NO;
}
}
if (!([node mode] & (S_ISUID|S_ISGID|S_ISVTX))) {
if (![fa applyMode:[node mode] error:error]) {
return NO;
}
}
if (!S_ISLNK([node mode]) && [node treeVersion] >= 7 && ![fa applyMTimeSec:node.mtime_sec mTimeNSec:node.mtime_nsec error:error]) {
return NO;
}
if (([node treeVersion] >= 7) && ![fa applyCreateTimeSec:node.createTime_sec createTimeNSec:node.createTime_nsec error:error]) {
return NO;
}
if (!S_ISFIFO([node mode])) {
if (![fa applyFlags:[node flags] error:error]) {
return NO;
}
}
return YES;
}
- (BOOL)createFile:(Node *)node atPath:(NSString *)path error:(NSError **)error {
if (![[NSFileManager defaultManager] ensureParentPathExistsForPath:path error:error]) {
HSLogError(@"error ensuring path %@ exists", path);
return NO;
}
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
if (![[NSFileManager defaultManager] removeItemAtPath:path error:error]) {
HSLogError(@"error removing existing file %@", path);
return NO;
}
}
HSLogTrace(@"%qu bytes -> %@", [node uncompressedDataSize], path);
if (S_ISLNK([node mode])) {
NSMutableData *data = [NSMutableData data];
for (BlobKey *dataBlobKey in [node dataBlobKeys]) {
NSData *blobData = [repo blobDataForBlobKey:dataBlobKey error:error];
if (blobData == nil) {
HSLogError(@"error getting data for %@", dataBlobKey);
return NO;
}
if ([dataBlobKey compressed]) {
blobData = [blobData gzipInflate];
}
[data appendData:blobData];
}
NSString *target = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
if (![self createSymLink:node path:path target:target error:error]) {
HSLogError(@"error creating sym link %@", path);
return NO;
}
} else if ([node uncompressedDataSize] > 0) {
if (![self createFileAtPath:path fromBlobKeys:[node dataBlobKeys] error:error]) {
NSError *myError = nil;
if ([[NSFileManager defaultManager] fileExistsAtPath:path] && ![[NSFileManager defaultManager] removeItemAtPath:path error:&myError]) {
HSLogError(@"error deleting incorrectly-restored file %@: %@", path, myError);
}
return NO;
}
} else {
// It's a zero-byte file.
int fd = open([path fileSystemRepresentation], O_CREAT|O_EXCL, S_IRWXU);
if (fd == -1) {
int errnum = errno;
HSLogError(@"open(%@) error %d: %s", path, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"failed to open %@: %s", path, strerror(errnum));
return NO;
}
close(fd);
}
HSLogDetail(@"restored %@", path);
return YES;
}
- (BOOL)createFileAtPath:(NSString *)path fromBlobKeys:(NSArray *)dataBlobKeys error:(NSError **)error {
FileOutputStream *fos = [[FileOutputStream alloc] initWithPath:path append:NO];
BOOL ret = YES;
writtenToCurrentFile = 0;
for (BlobKey *dataBlobKey in dataBlobKeys) {
if (![self appendBlobForBlobKey:dataBlobKey to:fos error:error]) {
ret = NO;
break;
}
}
[fos release];
return ret;
}
- (BOOL)appendBlobForBlobKey:(BlobKey *)theBlobKey to:(FileOutputStream *)fos error:(NSError **)error {
BOOL ret = NO;
NSError *myError = nil;
NSAutoreleasePool *pool = nil;
unsigned long long transferredSoFar = transferred;
unsigned long long writtenToCurrentFileSoFar = writtenToCurrentFile;
for (;;) {
[pool drain];
pool = [[NSAutoreleasePool alloc] init];
BufferedOutputStream *bos = [[[BufferedOutputStream alloc] initWithUnderlyingOutputStream:fos] autorelease];
if ([self doAppendBlobForBlobKey:theBlobKey to:bos error:&myError] && [bos flush:&myError]) {
ret = YES;
break;
}
if ([myError isErrorWithDomain:[Restorer errorDomain] code:ERROR_ABORT_REQUESTED]) {
HSLogInfo(@"restore canceled");
break;
}
if (![myError isTransientError]) {
HSLogError(@"error getting appending blob %@ to %@: %@", theBlobKey, bos, myError);
ret = NO;
break;
}
HSLogWarn(@"error appending blob %@ to %@ (retrying): %@", theBlobKey, bos, [myError localizedDescription]);
// Reset transferred:
transferred = transferredSoFar;
writtenToCurrentFile = writtenToCurrentFileSoFar;
// Seek back to the starting offset for this blob:
if (![fos seekTo:writtenToCurrentFile error:&myError]) {
ret = NO;
break;
}
}
[myError retain];
[pool drain];
[myError autorelease];
if (error != NULL) {
*error = myError;
}
return ret;
}
- (BOOL)doAppendBlobForBlobKey:(BlobKey *)theBlobKey to:(BufferedOutputStream *)bos error:(NSError **)error {
ServerBlob *sb = [[repo newServerBlobForBlobKey:theBlobKey error:error] autorelease];
if (sb == nil) {
return NO;
}
id <InputStream> is = [[sb newInputStream] autorelease];
if ([theBlobKey compressed]) {
is = [[[GunzipInputStream alloc] initWithUnderlyingStream:is] autorelease];
}
HSLogDebug(@"writing %@ to %@", is, bos);
BOOL ret = YES;
NSError *myError = nil;
NSAutoreleasePool *pool = nil;
unsigned char *buf = (unsigned char *)malloc(MY_BUF_SIZE);
for (;;) {
[pool drain];
pool = [[NSAutoreleasePool alloc] init];
NSInteger received = [is read:buf bufferLength:MY_BUF_SIZE error:&myError];
if (received < 0) {
ret = NO;
break;
}
if (received == 0) {
break;
}
if (![bos writeFully:buf length:received error:error]) {
ret = NO;
break;
}
transferred += received;
writtenToCurrentFile += received;
}
free(buf);
[myError retain];
[pool drain];
[myError autorelease];
if (error != NULL) {
*error = myError;
}
return ret;
}
- (BOOL)createSymLink:(Node *)node path:(NSString *)symLinkFile target:(NSString *)target error:(NSError **)error {
struct stat st;
if (lstat([symLinkFile fileSystemRepresentation], &st) == 0) {
if (![[NSFileManager defaultManager] removeItemAtPath:symLinkFile error:error]) {
return NO;
}
}
if (symlink([target fileSystemRepresentation], [symLinkFile fileSystemRepresentation]) == -1) {
int errnum = errno;
HSLogError(@"symlink(%@, %@) error %d: %s", target, symLinkFile, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"failed to create symlink %@ to %@: %s", symLinkFile, target, strerror(errnum));
return NO;
}
return YES;
}
- (BOOL)applyACLBlobKey:(BlobKey *)aclBlobKey uncompress:(BOOL)uncompress toPath:(NSString *)path error:(NSError **)error {
if (aclBlobKey != nil) {
NSData *data = [repo blobDataForBlobKey:aclBlobKey error:error];
if (data == nil) {
return NO;
}
if (uncompress) {
data = [data gzipInflate];
}
NSString *aclString = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
NSString *currentAclString = nil;
if (![FileACL aclText:&currentAclString forFile:path error:error]) {
return NO;
}
if (![currentAclString isEqualToString:aclString] && [aclString length] > 0) {
if (![FileACL writeACLText:aclString toFile:path error:error]) {
return NO;
}
}
}
return YES;
}
- (BOOL)applyXAttrsBlobKey:(BlobKey *)xattrsBlobKey uncompress:(BOOL)uncompress toFile:(NSString *)path error:(NSError **)error {
if (xattrsBlobKey != nil) {
NSData *xattrsData = [repo blobDataForBlobKey:xattrsBlobKey error:error];
if (xattrsData == nil) {
return NO;
}
id <InputStream> is = [xattrsData newInputStream];
if (uncompress) {
id <InputStream> uncompressed = [[GunzipInputStream alloc] initWithUnderlyingStream:is];
[is release];
is = uncompressed;
}
BufferedInputStream *bis = [[BufferedInputStream alloc] initWithUnderlyingStream:is];
[is release];
XAttrSet *set = [[[XAttrSet alloc] initWithBufferedInputStream:bis error:error] autorelease];
[bis release];
if (!set) {
return NO;
}
if (![set applyToFile:path error:error]) {
return NO;
}
}
return YES;
}
- (void)addError:(NSError *)theError forPath:(NSString *)thePath {
[errorsByPath setObject:theError forKey:thePath];
}
@end

21
S3TargetConnection.h Normal file
View file

@ -0,0 +1,21 @@
//
// S3TargetConnection.h
// Arq
//
// Created by Stefan Reitshamer on 4/21/14.
// Copyright (c) 2014 Stefan Reitshamer. All rights reserved.
//
#import "TargetConnection.h"
@class BaseTargetConnection;
@class Target;
@class S3RemoteFS;
@interface S3TargetConnection : NSObject <TargetConnection> {
S3RemoteFS *s3RemoteFS;
BaseTargetConnection *base;
}
- (id)initWithTarget:(Target *)theTarget;
@end

101
S3TargetConnection.m Normal file
View file

@ -0,0 +1,101 @@
//
// S3TargetConnection.m
// Arq
//
// Created by Stefan Reitshamer on 4/21/14.
// Copyright (c) 2014 Stefan Reitshamer. All rights reserved.
//
#import "S3TargetConnection.h"
#import "BaseTargetConnection.h"
#import "S3RemoteFS.h"
#import "S3ObjectMetadata.h"
@implementation S3TargetConnection
- (id)initWithTarget:(Target *)theTarget {
if (self = [super init]) {
s3RemoteFS = [[S3RemoteFS alloc] initWithTarget:theTarget];
base = [[BaseTargetConnection alloc] initWithTarget:theTarget remoteFS:s3RemoteFS];
}
return self;
}
- (void)dealloc {
[s3RemoteFS release];
[base release];
[super dealloc];
}
#pragma mark TargetConnection
- (NSArray *)computerUUIDsWithDelegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base computerUUIDsWithDelegate:theDelegate error:error];
}
- (NSArray *)bucketUUIDsForComputerUUID:(NSString *)theComputerUUID deleted:(BOOL)deleted delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base bucketUUIDsForComputerUUID:theComputerUUID deleted:deleted delegate:theDelegate error:error];
}
- (NSData *)bucketPlistDataForComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base bucketPlistDataForComputerUUID:theComputerUUID bucketUUID:theBucketUUID deleted:deleted delegate:theDelegate error:error];
}
- (BOOL)saveBucketPlistData:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base saveBucketPlistData:theData forComputerUUID:theComputerUUID bucketUUID:theBucketUUID deleted:deleted delegate:theDelegate error:error];
}
- (BOOL)deleteBucketPlistDataForComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base deleteBucketPlistDataForComputerUUID:theComputerUUID bucketUUID:theBucketUUID deleted:deleted delegate:theDelegate error:error];
}
- (NSData *)computerInfoForComputerUUID:(NSString *)theComputerUUID delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base computerInfoForComputerUUID:theComputerUUID delegate:theDelegate error:error];
}
- (BOOL)saveComputerInfo:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base saveComputerInfo:theData forComputerUUID:theComputerUUID delegate:theDelegate error:error];
}
- (NSDictionary *)objectsBySHA1ForTargetEndpoint:(NSURL *)theEndpoint isGlacier:(BOOL)theIsGlacier computerUUID:(NSString *)theComputerUUID delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
NSString *prefix = [NSString stringWithFormat:@"%@/%@%@/objects/", [theEndpoint path], (theIsGlacier ? @"glacier/" : @""), theComputerUUID];
NSArray *objects = [base objectsWithPrefix:prefix delegate:theDelegate error:error];
if (objects == nil) {
return nil;
}
NSMutableDictionary *ret = [NSMutableDictionary dictionary];
for (S3ObjectMetadata *md in objects) {
[ret setObject:md forKey:[[md path] lastPathComponent]];
}
return ret;
}
- (NSArray *)pathsWithPrefix:(NSString *)thePrefix delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base pathsWithPrefix:thePrefix delegate:theDelegate error:error];
}
- (BOOL)deleteObjectsForComputerUUID:(NSString *)theComputerUUID delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base deleteObjectsForComputerUUID:theComputerUUID delegate:theDelegate error:error];
}
- (BOOL)deletePaths:(NSArray *)thePaths delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base deletePaths:thePaths delegate:theDelegate error:error];
}
- (NSNumber *)fileExistsAtPath:(NSString *)thePath dataSize:(unsigned long long *)theDataSize delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base fileExistsAtPath:thePath dataSize:theDataSize delegate:theDelegate error:error];
}
- (NSData *)contentsOfFileAtPath:(NSString *)thePath delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base contentsOfFileAtPath:thePath delegate:theDelegate error:error];
}
- (BOOL)writeData:(NSData *)theData toFileAtPath:(NSString *)thePath dataTransferDelegate:(id <DataTransferDelegate>)theDataTransferDelegate targetConnectionDelegate:(id<TargetConnectionDelegate>)theTargetConnectionDelegate error:(NSError **)error {
return [base writeData:theData toFileAtPath:thePath dataTransferDelegate:theDataTransferDelegate targetConnectionDelegate:theTargetConnectionDelegate error:error];
}
- (BOOL)removeItemAtPath:(NSString *)thePath delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base removeItemAtPath:thePath delegate:theDelegate error:error];
}
- (NSNumber *)sizeOfItemAtPath:(NSString *)thePath delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base sizeOfItemAtPath:thePath delegate:theDelegate error:error];
}
- (NSNumber *)isObjectRestoredAtPath:(NSString *)thePath delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base isObjectRestoredAtPath:thePath delegate:theDelegate error:error];
}
- (BOOL)restoreObjectAtPath:(NSString *)thePath forDays:(NSUInteger)theDays alreadyRestoredOrRestoring:(BOOL *)alreadyRestoredOrRestoring delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base restoreObjectAtPath:thePath forDays:theDays alreadyRestoredOrRestoring:alreadyRestoredOrRestoring delegate:theDelegate error:error];
}
- (NSData *)saltDataForComputerUUID:(NSString *)theComputerUUID delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base saltDataForComputerUUID:theComputerUUID delegate:theDelegate error:error];
}
- (BOOL)setSaltData:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base setSaltData:theData forComputerUUID:theComputerUUID delegate:theDelegate error:error];
}
@end

21
SFTPTargetConnection.h Normal file
View file

@ -0,0 +1,21 @@
//
// SFTPTargetConnection.h
// Arq
//
// Created by Stefan Reitshamer on 4/21/14.
// Copyright (c) 2014 Stefan Reitshamer. All rights reserved.
//
#import "TargetConnection.h"
@class SFTPRemoteFS;
@class BaseTargetConnection;
@class Target;
@interface SFTPTargetConnection : NSObject <TargetConnection> {
SFTPRemoteFS *sftpRemoteFS;
BaseTargetConnection *base;
}
- (id)initWithTarget:(Target *)theTarget;
@end

130
SFTPTargetConnection.m Normal file
View file

@ -0,0 +1,130 @@
//
// SFTPTargetConnection.m
// Arq
//
// Created by Stefan Reitshamer on 4/21/14.
// Copyright (c) 2014 Stefan Reitshamer. All rights reserved.
//
#import "SFTPTargetConnection.h"
#import "SFTPRemoteFS.h"
#import "Target.h"
#import "BaseTargetConnection.h"
#import "S3ObjectMetadata.h"
@implementation SFTPTargetConnection
- (id)initWithTarget:(Target *)theTarget {
if (self = [super init]) {
NSString *pathPrefix = [[theTarget endpoint] path];
if ([pathPrefix isEqualToString:@"/"]) {
pathPrefix = @"";
}
sftpRemoteFS = [[SFTPRemoteFS alloc] initWithTarget:theTarget tempDir:[pathPrefix stringByAppendingString:@"/temp"]];
base = [[BaseTargetConnection alloc] initWithTarget:theTarget remoteFS:sftpRemoteFS];
}
return self;
}
- (void)dealloc {
[sftpRemoteFS release];
[base release];
[super dealloc];
}
#pragma mark TargetConnection
- (NSArray *)computerUUIDsWithDelegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base computerUUIDsWithDelegate:theDelegate error:error];
}
- (NSArray *)bucketUUIDsForComputerUUID:(NSString *)theComputerUUID deleted:(BOOL)deleted delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base bucketUUIDsForComputerUUID:theComputerUUID deleted:deleted delegate:theDelegate error:error];
}
- (NSData *)bucketPlistDataForComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base bucketPlistDataForComputerUUID:theComputerUUID bucketUUID:theBucketUUID deleted:deleted delegate:theDelegate error:error];
}
- (BOOL)saveBucketPlistData:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base saveBucketPlistData:theData forComputerUUID:theComputerUUID bucketUUID:theBucketUUID deleted:deleted delegate:theDelegate error:error];
}
- (BOOL)deleteBucketPlistDataForComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base deleteBucketPlistDataForComputerUUID:theComputerUUID bucketUUID:theBucketUUID deleted:deleted delegate:theDelegate error:error];
}
- (NSData *)computerInfoForComputerUUID:(NSString *)theComputerUUID delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base computerInfoForComputerUUID:theComputerUUID delegate:theDelegate error:error];
}
- (BOOL)saveComputerInfo:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base saveComputerInfo:theData forComputerUUID:theComputerUUID delegate:theDelegate error:error];
}
- (NSDictionary *)objectsBySHA1ForTargetEndpoint:(NSURL *)theEndpoint isGlacier:(BOOL)theIsGlacier computerUUID:(NSString *)theComputerUUID delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
NSString *endpointPath = [theEndpoint path];
if ([endpointPath isEqualToString:@"/"]) {
endpointPath = @"";
}
NSString *prefix = [NSString stringWithFormat:@"%@/%@/objects/", endpointPath, theComputerUUID];
NSArray *objects = [base objectsWithPrefix:prefix delegate:theDelegate error:error];
if (objects == nil) {
return nil;
}
NSMutableDictionary *ret = [NSMutableDictionary dictionary];
for (S3ObjectMetadata *md in objects) {
NSArray *pathComponents = [[md path] pathComponents];
NSString *lastPathComponent = [pathComponents lastObject];
if ([lastPathComponent length] == 40) {
[ret setObject:md forKey:lastPathComponent];
} else if ([lastPathComponent length] == 38) {
NSString *fragment1 = [pathComponents objectAtIndex:([pathComponents count] - 2)];
NSString *fragment2 = [pathComponents objectAtIndex:([pathComponents count] - 1)];
NSString *theSHA1 = [fragment1 stringByAppendingString:fragment2];
[ret setObject:md forKey:theSHA1];
} else if ([lastPathComponent length] == 36) {
NSString *fragment1 = [pathComponents objectAtIndex:([pathComponents count] - 3)];
NSString *fragment2 = [pathComponents objectAtIndex:([pathComponents count] - 2)];
NSString *fragment3 = [pathComponents objectAtIndex:([pathComponents count] - 1)];
NSString *theSHA1 = [[fragment1 stringByAppendingString:fragment2] stringByAppendingString:fragment3];
[ret setObject:md forKey:theSHA1];
} else {
HSLogWarn(@"unexpected lastpathcomponent %@ of path %@", lastPathComponent, [md path]);
}
}
return ret;
}
- (NSArray *)pathsWithPrefix:(NSString *)thePrefix delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base pathsWithPrefix:thePrefix delegate:theDelegate error:error];
}
- (BOOL)deleteObjectsForComputerUUID:(NSString *)theComputerUUID delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base deleteObjectsForComputerUUID:theComputerUUID delegate:theDelegate error:error];
}
- (BOOL)deletePaths:(NSArray *)thePaths delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base deletePaths:thePaths delegate:theDelegate error:error];
}
- (NSNumber *)fileExistsAtPath:(NSString *)thePath dataSize:(unsigned long long *)theDataSize delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base fileExistsAtPath:thePath dataSize:theDataSize delegate:theDelegate error:error];
}
- (NSData *)contentsOfFileAtPath:(NSString *)thePath delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base contentsOfFileAtPath:thePath delegate:theDelegate error:error];
}
- (BOOL)writeData:(NSData *)theData toFileAtPath:(NSString *)thePath dataTransferDelegate:(id<DataTransferDelegate>)theDTD targetConnectionDelegate:(id<TargetConnectionDelegate>)theTCD error:(NSError **)error {
return [base writeData:theData toFileAtPath:thePath dataTransferDelegate:theDTD targetConnectionDelegate:theTCD error:error];
}
- (BOOL)removeItemAtPath:(NSString *)thePath delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base removeItemAtPath:thePath delegate:theDelegate error:error];
}
- (NSNumber *)sizeOfItemAtPath:(NSString *)thePath delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base sizeOfItemAtPath:thePath delegate:theDelegate error:error];
}
- (NSNumber *)isObjectRestoredAtPath:(NSString *)thePath delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base isObjectRestoredAtPath:thePath delegate:theDelegate error:error];
}
- (BOOL)restoreObjectAtPath:(NSString *)thePath forDays:(NSUInteger)theDays alreadyRestoredOrRestoring:(BOOL *)alreadyRestoredOrRestoring delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base restoreObjectAtPath:thePath forDays:theDays alreadyRestoredOrRestoring:alreadyRestoredOrRestoring delegate:theDelegate error:error];
}
- (NSData *)saltDataForComputerUUID:(NSString *)theComputerUUID delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base saltDataForComputerUUID:theComputerUUID delegate:theDelegate error:error];
}
- (BOOL)setSaltData:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID delegate:(id<TargetConnectionDelegate>)theDelegate error:(NSError **)error {
return [base setSaltData:theData forComputerUUID:theComputerUUID delegate:theDelegate error:error];
}
@end

50
Target.h Normal file
View file

@ -0,0 +1,50 @@
//
// Target.h
// arq_restore
//
// Created by Stefan Reitshamer on 7/28/14.
//
//
@protocol TargetConnection;
@class S3Service;
@class BufferedInputStream;
enum TargetType {
kTargetAWS = 0,
kTargetSFTP = 1,
kTargetGreenQloud = 2,
kTargetDreamObjects = 3,
kTargetGoogleCloudStorage = 4,
kTargetS3Compatible = 5,
kTargetGoogleDrive = 6
};
typedef int TargetType;
@interface Target : NSObject {
NSString *uuid;
NSURL *endpoint;
TargetType targetType;
NSString *secret;
NSString *passphrase;
BOOL budgetEnabled;
double budgetDollars;
uint32_t budgetGB;
BOOL useRRS;
}
- (id)initWithEndpoint:(NSURL *)theEndpoint secret:(NSString *)theSecret passphrase:(NSString *)thePassphrase;
- (id)initWithBufferedInputStream:(BufferedInputStream *)theBIS error:(NSError **)error;
- (NSString *)targetUUID;
- (NSURL *)endpoint;
- (NSString *)endpointDisplayName;
- (NSString *)secret:(NSError **)error;
- (NSString *)passphrase:(NSError **)error;
- (TargetType)targetType;
- (id <TargetConnection>)newConnection;
- (S3Service *)s3:(NSError **)error;
@end

169
Target.m Normal file
View file

@ -0,0 +1,169 @@
//
// Target.m
// arq_restore
//
// Created by Stefan Reitshamer on 7/28/14.
//
//
#import "Target.h"
#import "NSString_extra.h"
#import "AWSRegion.h"
#import "SFTPTargetConnection.h"
#import "GoogleDriveTargetConnection.h"
#import "S3TargetConnection.h"
#import "S3Service.h"
#import "S3AuthorizationProvider.h"
#import "TargetSchedule.h"
#import "DoubleIO.h"
#import "BooleanIO.h"
#import "IntegerIO.h"
#import "StringIO.h"
@implementation Target
- (id)initWithEndpoint:(NSURL *)theEndpoint secret:(NSString *)theSecret passphrase:(NSString *)thePassphrase {
if (self = [super init]) {
uuid = [[NSString stringWithRandomUUID] retain];
endpoint = [theEndpoint retain];
secret = [theSecret retain];
targetType = [self targetTypeForEndpoint];
passphrase = [thePassphrase retain];
}
return self;
}
- (id)initWithBufferedInputStream:(BufferedInputStream *)theBIS error:(NSError **)error {
if (self = [super init]) {
if (![StringIO read:&uuid from:theBIS error:error]) {
[self release];
return nil;
}
[uuid retain];
NSString *theEndpointDescription = nil;
if (![StringIO read:&theEndpointDescription from:theBIS error:error]) {
[self release];
return nil;
}
TargetSchedule *targetSchedule = [[TargetSchedule alloc] initWithBufferedInputStream:theBIS error:error];
if (targetSchedule == nil) {
[self release];
return nil;
}
if (![BooleanIO read:&budgetEnabled from:theBIS error:error]
|| ![DoubleIO read:&budgetDollars from:theBIS error:error]
|| ![BooleanIO read:&useRRS from:theBIS error:error]
|| ![IntegerIO readUInt32:&budgetGB from:theBIS error:error]) {
[self release];
return nil;
}
endpoint = [[NSURL URLWithString:theEndpointDescription] copy];
targetType = [self targetTypeForEndpoint];
NSAssert(endpoint != nil, @"endpoint may not be nil");
}
return self;
}
- (void)dealloc {
[uuid release];
[endpoint release];
[secret release];
[passphrase release];
[super dealloc];
}
- (NSString *)errorDomain {
return @"TargetErrorDomain";
}
- (NSString *)targetUUID {
return uuid;
}
- (NSURL *)endpoint {
return endpoint;
}
- (NSString *)endpointDisplayName {
TargetType theTargetType = [self targetType];
switch (theTargetType) {
case kTargetAWS:
return @"Amazon";
case kTargetGreenQloud:
return @"GreenQloud";
case kTargetDreamObjects:
return @"DreamObjects";
case kTargetGoogleCloudStorage:
return @"Google Cloud Storage";
case kTargetGoogleDrive:
return @"Google Drive Storage";
}
return [endpoint host];
}
- (NSString *)secret:(NSError **)error {
return secret;
}
- (NSString *)passphrase:(NSError **)error {
if (passphrase == nil) {
SETNSERROR([self errorDomain], ERROR_NOT_FOUND, @"passphrase not given");
return nil;
}
return passphrase;
}
- (TargetType)targetType {
return targetType;
}
- (id <TargetConnection>)newConnection {
id <TargetConnection> ret = nil;
if (targetType == kTargetSFTP) {
ret = [[SFTPTargetConnection alloc] initWithTarget:self];
} else if (targetType == kTargetGoogleDrive) {
ret = [[GoogleDriveTargetConnection alloc] initWithTarget:self];
} else {
ret = [[S3TargetConnection alloc] initWithTarget:self];
}
return ret;
}
- (S3Service *)s3:(NSError **)error {
if ([self targetType] == kTargetSFTP || [self targetType] == kTargetGoogleDrive) {
SETNSERROR([self errorDomain], -1, @"cannot create S3Service for endpoint %@", endpoint);
return nil;
}
S3AuthorizationProvider *sap = [[[S3AuthorizationProvider alloc] initWithAccessKey:[endpoint user] secretKey:secret] autorelease];
NSString *portString = @"";
if ([[endpoint port] intValue] != 0) {
portString = [NSString stringWithFormat:@":%d", [[endpoint port] intValue]];
}
NSURL *s3Endpoint = [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@%@", [endpoint scheme], [endpoint host], portString]];
return [[[S3Service alloc] initWithS3AuthorizationProvider:sap endpoint:s3Endpoint useAmazonRRS:NO] autorelease];
}
#pragma mark NSObject
- (NSString *)description {
return [NSString stringWithFormat:@"%@:%@", [endpoint host], [endpoint path]];
}
#pragma mark internal
- (TargetType)targetTypeForEndpoint {
if ([[[self endpoint] scheme] isEqualToString:@"sftp"]) {
return kTargetSFTP;
}
if ([[[self endpoint] scheme] isEqualToString:@"googledrive"]) {
return kTargetGoogleDrive;
}
AWSRegion *awsRegion = [AWSRegion regionWithS3Endpoint:[self endpoint]];
if (awsRegion != nil) {
return kTargetAWS;
}
if ([[[self endpoint] host] isEqualToString:@"w.greenqloud.com"]) {
return kTargetGreenQloud;
}
if ([[[self endpoint] host] isEqualToString:@"objects.dreamhost.com"]) {
return kTargetDreamObjects;
}
if ([[[self endpoint] host] isEqualToString:@"storage.googleapis.com"]) {
return kTargetGoogleCloudStorage;
}
return kTargetS3Compatible;
}
@end

43
TargetConnection.h Normal file
View file

@ -0,0 +1,43 @@
//
// TargetConnection.h
// Arq
//
// Created by Stefan Reitshamer on 4/21/14.
// Copyright (c) 2014 Stefan Reitshamer. All rights reserved.
//
@protocol DataTransferDelegate;
@protocol TargetConnectionDelegate <NSObject>
- (BOOL)targetConnectionShouldRetryOnTransientError:(NSError **)error;
@end
@protocol TargetConnection <NSObject>
- (NSArray *)computerUUIDsWithDelegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (NSArray *)bucketUUIDsForComputerUUID:(NSString *)theComputerUUID deleted:(BOOL)deleted delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (NSData *)bucketPlistDataForComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (BOOL)saveBucketPlistData:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (BOOL)deleteBucketPlistDataForComputerUUID:(NSString *)theComputerUUID bucketUUID:(NSString *)theBucketUUID deleted:(BOOL)deleted delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (NSData *)computerInfoForComputerUUID:(NSString *)theComputerUUID delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (BOOL)saveComputerInfo:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (NSDictionary *)objectsBySHA1ForTargetEndpoint:(NSURL *)theEndpoint isGlacier:(BOOL)theIsGlacier computerUUID:(NSString *)theComputerUUID delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (NSArray *)pathsWithPrefix:(NSString *)thePrefix delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (BOOL)deleteObjectsForComputerUUID:(NSString *)theComputerUUID delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (BOOL)deletePaths:(NSArray *)thePaths delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (NSNumber *)fileExistsAtPath:(NSString *)thePath dataSize:(unsigned long long *)theDataSize delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (NSData *)contentsOfFileAtPath:(NSString *)thePath delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (BOOL)writeData:(NSData *)theData toFileAtPath:(NSString *)thePath dataTransferDelegate:(id <DataTransferDelegate>)theDelegate targetConnectionDelegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (BOOL)removeItemAtPath:(NSString *)thePath delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (NSNumber *)sizeOfItemAtPath:(NSString *)thePath delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (NSNumber *)isObjectRestoredAtPath:(NSString *)thePath delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (BOOL)restoreObjectAtPath:(NSString *)thePath forDays:(NSUInteger)theDays alreadyRestoredOrRestoring:(BOOL *)alreadyRestoredOrRestoring delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (NSData *)saltDataForComputerUUID:(NSString *)theComputerUUID delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
- (BOOL)setSaltData:(NSData *)theData forComputerUUID:(NSString *)theComputerUUID delegate:(id <TargetConnectionDelegate>)theDelegate error:(NSError **)error;
@end

53
TargetSchedule.h Normal file
View file

@ -0,0 +1,53 @@
//
// TargetSchedule.h
// Arq
//
// Created by Stefan Reitshamer on 12/10/13.
// Copyright (c) 2013 Stefan Reitshamer. All rights reserved.
//
enum {
TargetScheduleTypeHourly = 0,
TargetScheduleTypeDaily = 1,
TargetScheduleTypeManual = 2
};
typedef uint32_t TargetScheduleType;
@class DictNode;
@class BufferedInputStream;
@class BufferedOutputStream;
@interface TargetSchedule : NSObject {
TargetScheduleType type;
uint32_t numberOfHours;
uint32_t minutesAfterHour;
uint32_t backupHour;
uint32_t budgetEnforcementIntervalHours;
BOOL pauseDuringWindow;
uint32_t pauseFromHour;
uint32_t pauseToHour;
}
- (id)initWithScheduleType:(TargetScheduleType)theType
numberOfHours:(int)theNumberOfHours
minutesAfterHour:(int)theMinutesAfterHour
backupHour:(int)theBackupHour
budgetEnforcementIntervalHours:(int)theBudgetEnforcementIntervalHours
pauseDuringWindow:(BOOL)thePauseDuringWindow
pauseFromHour:(NSUInteger)thePauseFromHour
pauseToHour:(NSUInteger)thePauseToHour;
- (id)initWithPlist:(DictNode *)thePlist;
- (id)initWithBufferedInputStream:(BufferedInputStream *)theBIS error:(NSError **)error;
- (TargetScheduleType)type;
- (uint32_t)numberOfHours;
- (uint32_t)minutesAfterHour;
- (uint32_t)backupHour;
- (uint32_t)budgetEnforcementIntervalHours;
- (BOOL)pauseDuringWindow;
- (uint32_t)pauseFromHour;
- (uint32_t)pauseToHour;
- (DictNode *)toPlist;
- (BOOL)writeTo:(BufferedOutputStream *)theBOS error:(NSError **)error;
@end

122
TargetSchedule.m Normal file
View file

@ -0,0 +1,122 @@
//
// TargetSchedule.m
// Arq
//
// Created by Stefan Reitshamer on 12/10/13.
// Copyright (c) 2013 Stefan Reitshamer. All rights reserved.
//
#import "TargetSchedule.h"
#import "DictNode.h"
#import "IntegerNode.h"
#import "IntegerIO.h"
#import "BufferedOutputStream.h"
#import "BooleanNode.h"
#import "BooleanIO.h"
#define TARGET_DATA_VERSION (1)
@implementation TargetSchedule
- (id)initWithScheduleType:(TargetScheduleType)theType
numberOfHours:(int)theNumberOfHours
minutesAfterHour:(int)theMinutesAfterHour
backupHour:(int)theBackupHour
budgetEnforcementIntervalHours:(int)theBudgetEnforcementIntervalHours
pauseDuringWindow:(BOOL)thePauseDuringWindow
pauseFromHour:(NSUInteger)thePauseFromHour
pauseToHour:(NSUInteger)thePauseToHour {
if (self = [super init]) {
type = theType;
numberOfHours = theNumberOfHours;
minutesAfterHour = theMinutesAfterHour;
backupHour = theBackupHour;
budgetEnforcementIntervalHours = theBudgetEnforcementIntervalHours;
pauseDuringWindow = thePauseDuringWindow;
pauseFromHour = (uint32_t)thePauseFromHour;
pauseToHour = (uint32_t)thePauseToHour;
}
return self;
}
- (id)initWithPlist:(DictNode *)thePlist {
if (self = [super init]) {
type = [[thePlist integerNodeForKey:@"type"] intValue];
numberOfHours = [[thePlist integerNodeForKey:@"numberOfHours"] intValue];
minutesAfterHour = [[thePlist integerNodeForKey:@"minutesAfterHour"] intValue];
backupHour = [[thePlist integerNodeForKey:@"backupHour"] intValue];
budgetEnforcementIntervalHours = [[thePlist integerNodeForKey:@"budgetEnforcementIntervalHours"] intValue];
pauseDuringWindow = [[thePlist booleanNodeForKey:@"pauseDuringWindow"] booleanValue];
pauseFromHour = [[thePlist integerNodeForKey:@"pauseFromHour"] intValue];
pauseToHour = [[thePlist integerNodeForKey:@"pauseToHour"] intValue];
}
return self;
}
- (id)initWithBufferedInputStream:(BufferedInputStream *)theBIS error:(NSError **)error {
if (self = [super init]) {
uint32_t version = 0;
if (![IntegerIO readUInt32:&version from:theBIS error:error]
|| ![IntegerIO readUInt32:&type from:theBIS error:error]
|| ![IntegerIO readUInt32:&numberOfHours from:theBIS error:error]
|| ![IntegerIO readUInt32:&minutesAfterHour from:theBIS error:error]
|| ![IntegerIO readUInt32:&backupHour from:theBIS error:error]
|| ![IntegerIO readUInt32:&budgetEnforcementIntervalHours from:theBIS error:error]
|| ![BooleanIO read:&pauseDuringWindow from:theBIS error:error]
|| ![IntegerIO readUInt32:&pauseFromHour from:theBIS error:error]
|| ![IntegerIO readUInt32:&pauseToHour from:theBIS error:error]) {
[self release];
return nil;
}
}
return self;
}
- (TargetScheduleType)type {
return type;
}
- (uint32_t)numberOfHours {
return numberOfHours;
}
- (uint32_t)minutesAfterHour {
return minutesAfterHour;
}
- (uint32_t)backupHour {
return backupHour;
}
- (uint32_t)budgetEnforcementIntervalHours {
return budgetEnforcementIntervalHours;
}
- (BOOL)pauseDuringWindow {
return pauseDuringWindow;
}
- (uint32_t)pauseFromHour {
return pauseFromHour;
}
- (uint32_t)pauseToHour {
return pauseToHour;
}
- (DictNode *)toPlist {
DictNode *ret = [[[DictNode alloc] init] autorelease];
[ret putInt:TARGET_DATA_VERSION forKey:@"dataVersion"];
[ret putInt:type forKey:@"type"];
[ret putInt:numberOfHours forKey:@"numberOfHours"];
[ret putInt:minutesAfterHour forKey:@"minutesAfterHour"];
[ret putInt:backupHour forKey:@"backupHour"];
[ret putInt:budgetEnforcementIntervalHours forKey:@"budgetEnforcementIntervalHours"];
[ret putBoolean:pauseDuringWindow forKey:@"pauseDuringWindow"];
[ret putInt:pauseFromHour forKey:@"pauseFromHour"];
[ret putInt:pauseToHour forKey:@"pauseToHour"];
return ret;
}
- (BOOL)writeTo:(BufferedOutputStream *)theBOS error:(NSError **)error {
return [IntegerIO writeUInt32:TARGET_DATA_VERSION to:theBOS error:error]
&& [IntegerIO writeUInt32:type to:theBOS error:error]
&& [IntegerIO writeUInt32:numberOfHours to:theBOS error:error]
&& [IntegerIO writeUInt32:minutesAfterHour to:theBOS error:error]
&& [IntegerIO writeUInt32:backupHour to:theBOS error:error]
&& [IntegerIO writeUInt32:budgetEnforcementIntervalHours to:theBOS error:error]
&& [BooleanIO write:pauseDuringWindow to:theBOS error:error]
&& [IntegerIO writeUInt32:pauseFromHour to:theBOS error:error]
&& [IntegerIO writeUInt32:pauseToHour to:theBOS error:error];
}
@end

View file

@ -3,7 +3,7 @@
// Arq
//
// Created by Stefan Reitshamer on 7/9/10.
// Copyright 2010 __MyCompanyName__. All rights reserved.
// Copyright 2010 Haystack Software. All rights reserved.
//

View file

@ -3,7 +3,7 @@
// Arq
//
// Created by Stefan Reitshamer on 7/9/10.
// Copyright 2010 __MyCompanyName__. All rights reserved.
// Copyright 2010 Haystack Software. All rights reserved.
//
#import "UserAndComputer.h"

View file

@ -11,7 +11,7 @@
@implementation UserLibrary (Arq)
+ (NSString *)arqUserLibraryPath {
return [NSHomeDirectory() stringByAppendingString:@"/Library/Arq"];
return [[NSHomeDirectory() stringByAppendingPathComponent:@"Library"] stringByAppendingPathComponent:@"Arq"];
}
+ (NSString *)arqCachePath {
return [NSString stringWithFormat:@"%@/Cache.noindex", [UserLibrary arqUserLibraryPath]];

View file

@ -1,24 +0,0 @@
//
// XAttrSet.h
// Backup
//
// Created by Stefan Reitshamer on 4/27/09.
// Copyright 2009 PhotoMinds LLC. All rights reserved.
//
#import "Blob.h"
#import "BufferedInputStream.h"
@interface XAttrSet : NSObject {
NSMutableDictionary *xattrs;
NSString *path;
}
- (id)initWithPath:(NSString *)thePath error:(NSError **)error;
- (id)initWithBufferedInputStream:(BufferedInputStream *)is error:(NSError **)error;
- (NSData *)toData;
- (NSUInteger)count;
- (unsigned long long)dataLength;
- (NSArray *)names;
- (BOOL)applyToFile:(NSString *)path error:(NSError **)error;
@end

View file

@ -1,214 +0,0 @@
//
// XAttrSet.m
// Backup
//
// Created by Stefan Reitshamer on 4/27/09.
// Copyright 2009 PhotoMinds LLC. All rights reserved.
//
#include <sys/stat.h>
#include <sys/xattr.h>
#import "XAttrSet.h"
#import "StringIO.h"
#import "DataIO.h"
#import "IntegerIO.h"
#import "Blob.h"
#import "DataInputStream.h"
#import "SetNSError.h"
#import "NSErrorCodes.h"
#import "Streams.h"
#import "NSError_extra.h"
#import "BufferedInputStream.h"
#import "NSData-Gzip.h"
#define HEADER_LENGTH (12)
@interface XAttrSet (internal)
- (BOOL)loadFromPath:(NSString *)thePath error:(NSError **)error;
- (BOOL)loadFromInputStream:(BufferedInputStream *)is error:(NSError **)error;
@end
@implementation XAttrSet
- (id)initWithPath:(NSString *)thePath error:(NSError **)error {
if (self = [super init]) {
xattrs = [[NSMutableDictionary alloc] init];
NSError *myError = nil;
if (![self loadFromPath:thePath error:&myError]) {
if ([myError isErrorWithDomain:@"UnixErrorDomain" code:EPERM]) {
HSLogDebug(@"%@ doesn't support extended attributes; skipping", thePath);
} else {
if (error != NULL) {
*error = myError;
}
[self release];
return nil;
}
}
path = [thePath retain];
}
return self;
}
- (id)initWithBufferedInputStream:(BufferedInputStream *)is error:(NSError **)error {
if (self = [super init]) {
xattrs = [[NSMutableDictionary alloc] init];
if (![self loadFromInputStream:is error:error]) {
[self release];
self = nil;
}
}
return self;
}
- (void)dealloc {
[xattrs release];
[path release];
[super dealloc];
}
- (NSData *)toData {
NSMutableData *mutableData = [[[NSMutableData alloc] init] autorelease];
[mutableData appendBytes:"XAttrSetV002" length:HEADER_LENGTH];
uint64_t count = (uint64_t)[xattrs count];
[IntegerIO writeUInt64:count to:mutableData];
for (NSString *name in [xattrs allKeys]) {
[StringIO write:name to:mutableData];
[DataIO write:[xattrs objectForKey:name] to:mutableData];
}
return mutableData;
}
- (NSUInteger)count {
return [xattrs count];
}
- (unsigned long long)dataLength {
unsigned long long total = 0;
for (NSString *key in [xattrs allKeys]) {
NSData *value = [xattrs objectForKey:key];
total += [value length];
}
return total;
}
- (NSArray *)names {
return [xattrs allKeys];
}
- (BOOL)applyToFile:(NSString *)thePath error:(NSError **)error {
XAttrSet *current = [[[XAttrSet alloc] initWithPath:thePath error:error] autorelease];
if (!current) {
return NO;
}
const char *pathChars = [thePath fileSystemRepresentation];
for (NSString *name in [current names]) {
if (removexattr(pathChars, [name UTF8String], XATTR_NOFOLLOW) == -1) {
int errnum = errno;
HSLogError(@"removexattr(%@, %@) error %d: %s", thePath, name, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"failed to remove extended attribute %@ from %@: %s", name, thePath, strerror(errnum));
return NO;
}
}
for (NSString *key in [xattrs allKeys]) {
NSData *value = [xattrs objectForKey:key];
if (setxattr(pathChars,
[key UTF8String],
[value bytes],
[value length],
0,
XATTR_NOFOLLOW) == -1) {
int errnum = errno;
HSLogError(@"setxattr(%@, %@) error %d: %s", thePath, key, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"failed to set extended attribute %@ on %@: %s", key, thePath, strerror(errnum));
return NO;
}
}
return YES;
}
@end
@implementation XAttrSet (internal)
- (BOOL)loadFromPath:(NSString *)thePath error:(NSError **)error {
struct stat st;
if (lstat([thePath fileSystemRepresentation], &st) == -1) {
int errnum = errno;
HSLogError(@"lstat(%@) error %d: %s", thePath, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"%@: %s", thePath, strerror(errnum));
return NO;
}
if (S_ISREG(st.st_mode) || S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode)) {
const char *cpath = [thePath fileSystemRepresentation];
ssize_t xattrsize = listxattr(cpath, NULL, 0, XATTR_NOFOLLOW);
if (xattrsize == -1) {
int errnum = errno;
HSLogError(@"listxattr(%@) error %d: %s", thePath, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"failed to list extended attributes of %@: %s", thePath, strerror(errnum));
return NO;
}
if (xattrsize > 0) {
char *xattrbuf = (char *)malloc(xattrsize);
xattrsize = listxattr(cpath, xattrbuf, xattrsize, XATTR_NOFOLLOW);
if (xattrsize == -1) {
int errnum = errno;
HSLogError(@"listxattr(%@) error %d: %s", thePath, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"failed to list extended attributes of %@: %s", thePath, strerror(errnum));
free(xattrbuf);
return NO;
}
for (char *name = xattrbuf; name < (xattrbuf + xattrsize); name += strlen(name) + 1) {
NSString *theName = [NSString stringWithUTF8String:name];
ssize_t valuesize = getxattr(cpath, name, NULL, 0, 0, XATTR_NOFOLLOW);
NSData *xattrData = nil;
if (valuesize == -1) {
int errnum = errno;
HSLogError(@"getxattr(%s, %s) error %d: %s", cpath, name, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"failed to read extended attribute %s of %@: %s", name, thePath, strerror(errnum));
free(xattrbuf);
return NO;
}
if (valuesize > 0) {
void *value = malloc(valuesize);
if (getxattr(cpath, name, value, valuesize, 0, XATTR_NOFOLLOW) == -1) {
int errnum = errno;
HSLogError(@"getxattr(%s, %s) error %d: %s", cpath, name, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"failed to read extended attribute %s of %@: %s", name, thePath, strerror(errnum));
free(value);
free(xattrbuf);
return NO;
}
xattrData = [NSData dataWithBytes:value length:valuesize];
free(value);
} else {
xattrData = [NSData data];
}
[xattrs setObject:xattrData forKey:theName];
}
free(xattrbuf);
}
}
return YES;
}
- (BOOL)loadFromInputStream:(BufferedInputStream *)is error:(NSError **)error {
BOOL ret = NO;
unsigned char *buf = (unsigned char *)malloc(HEADER_LENGTH);
if (![is readExactly:HEADER_LENGTH into:buf error:error]) {
goto load_error;
}
if (strncmp((const char *)buf, "XAttrSetV002", HEADER_LENGTH)) {
SETNSERROR(@"XAttrSetErrorDomain", ERROR_INVALID_OBJECT_VERSION, @"invalid XAttrSet header");
goto load_error;
}
uint64_t count;
if (![IntegerIO readUInt64:&count from:is error:error]) {
goto load_error;
}
for (uint64_t i = 0; i < count; i++) {
NSString *name;
if (![StringIO read:&name from:is error:error]) {
goto load_error;
}
NSData *value;
if (![DataIO read:&value from:is error:error]) {
goto load_error;
}
[xattrs setObject:value forKey:name];
}
ret = YES;
load_error:
free(buf);
return ret;
}
@end

View file

@ -37,26 +37,34 @@
static void printUsage(const char *exeName) {
fprintf(stderr, "Usage:\n");
fprintf(stderr, "\t%s -v\n", exeName);
fprintf(stderr, "\t%s [-l log_level]\n", exeName);
fprintf(stderr, "\t%s [-l log_level] /s3bucket/computerUUID/folderUUID\n", exeName);
fprintf(stderr, "\t%s [-l log_level] /s3bucket/computerUUID/folderUUID reflog\n", exeName);
fprintf(stderr, "\t%s [-l log_level] /s3bucket/computerUUID/folderUUID <commitSHA1>\n", exeName);
fprintf(stderr, "\t%s [-l log_level] listcomputers <target_type> <target_params>\n", exeName);
fprintf(stderr, "\t%s [-l log_level] listfolders <computer_uuid> <encryption_password> <target_type> <target_params>\n", exeName);
fprintf(stderr, "\t%s [-l log_level] restore <computer_uuid> <encryption_password> <folder_uuid> <target_type> <target_params>\n", exeName);
fprintf(stderr, "\t\ntarget_params by target type:\n");
fprintf(stderr, "\taws: access_key secret_key bucket_name\n");
fprintf(stderr, "\tsftp: hostname port path username password_or_keyfile [keyfile_passphrase]\n");
fprintf(stderr, "\tgreenqloud: access_key secret_key bucket_name\n");
fprintf(stderr, "\tdreamobjects: public_key secret_key bucket_name\n");
fprintf(stderr, "\tgooglecloudstorage: public_key secret_key bucket_name\n");
fprintf(stderr, "\ts3compatible: service_url access_key secret_key bucket_name\n");
fprintf(stderr, "\tgoogledrive: refresh_token path\n");
}
int main (int argc, const char **argv) {
setHSLogLevel(HSLOG_LEVEL_WARN);
char *exePath = strdup(argv[0]);
char *exeName = basename(exePath);
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
ArqRestoreCommand *cmd = [[[ArqRestoreCommand alloc] init] autorelease];
int ret = 0;
if (![cmd readArgc:argc argv:argv]) {
if (argc == 2 && !strcmp(argv[1], "-h")) {
printUsage(exeName);
ret = 1;
} else {
NSError *myError = nil;
if (![cmd execute:&myError]) {
fprintf(stderr, "restore error: %s\n", [[myError localizedDescription] UTF8String]);
if (![cmd executeWithArgc:argc argv:argv error:&myError]) {
fprintf(stderr, "%s: %s\n", exeName, [[myError localizedDescription] UTF8String]);
if ([myError isErrorWithDomain:[cmd errorDomain] code:ERROR_USAGE]) {
printUsage(exeName);
}
ret = 1;
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
#ifdef __OBJC__
#import <Foundation/Foundation.h>
#import "HSLog.h"
#import <Foundation/Foundation.h>
#import "HSLog.h"
#import "SetNSError.h"
#import "NSErrorCodes.h"
#endif

View file

@ -1,110 +0,0 @@
/*
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.
*/
#include <libgen.h>
#import <Foundation/Foundation.h>
#import "ArqVerifyCommand.h"
static void printUsage(const char *exeName) {
fprintf(stderr, "usage: %s [-v] all | s3_bucket_name [computer_uuid [folder_uuid]]]\n", exeName);
}
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
setHSLogLevel(HSLOG_LEVEL_ERROR);
char *exePath = strdup(argv[0]);
char *exeName = basename(exePath);
char *cAccessKey = getenv("ARQ_ACCESS_KEY");
if (cAccessKey == NULL) {
fprintf(stderr, "%s: missing ARQ_ACCESS_KEY environment variable\n", exeName);
}
char *cSecretKey = getenv("ARQ_SECRET_KEY");
if (cSecretKey == NULL) {
fprintf(stderr, "%s: missing ARQ_SECRET_KEY environment variable\n", exeName);
}
char *cEncryptionPassword = getenv("ARQ_ENCRYPTION_PASSWORD");
if (cEncryptionPassword == NULL) {
fprintf(stderr, "%s: missing ARQ_ENCRYPTION_PASSWORD environment variable\n", exeName);
}
if (cAccessKey == NULL || cSecretKey == NULL || cEncryptionPassword == NULL) {
goto main_error;
}
NSString *accessKey = [NSString stringWithUTF8String:cAccessKey];
NSString *secretKey = [NSString stringWithUTF8String:cSecretKey];
NSString *encryptionPassword = [[[NSString alloc] initWithUTF8String:cEncryptionPassword] autorelease];
ArqVerifyCommand *cmd = [[[ArqVerifyCommand alloc] initWithAccessKey:accessKey secretKey:secretKey encryptionPassword:encryptionPassword] autorelease];
NSError *error = nil;
BOOL verbose = NO;
int index = 1;
if (argc > 1 && !strcmp(argv[1], "-v")) {
verbose = YES;
index++;
}
[cmd setVerbose:verbose];
BOOL ret = NO;
if ((argc - index) == 0) {
printUsage(exeName);
goto main_error;
} else if ((argc - index) == 1) {
if (!strcmp(argv[index], "-?") || !strcmp(argv[index], "-h")) {
printUsage(exeName);
goto main_error;
} else if (!strcmp(argv[index], "all")) {
if (![cmd verifyAll:&error]) {
NSLog(@"%@", [error localizedDescription]);
goto main_error;
}
} else if (![cmd verifyS3BucketName:[NSString stringWithUTF8String:argv[index]] error:&error]) {
NSLog(@"%@", [error localizedDescription]);
goto main_error;
}
} else if ((argc - index) == 2) {
if (![cmd verifyS3BucketName:[NSString stringWithUTF8String:argv[index]] computerUUID:[NSString stringWithUTF8String:argv[index+1]] error:&error]) {
NSLog(@"%@", [error localizedDescription]);
goto main_error;
}
} else if ((argc - index) == 3) {
if (![cmd verifyS3BucketName:[NSString stringWithUTF8String:argv[index]] computerUUID:[NSString stringWithUTF8String:argv[index+1]] bucketUUID:[NSString stringWithUTF8String:argv[index+2]] error:&error]) {
NSLog(@"%@", [error localizedDescription]);
goto main_error;
}
} else {
printUsage(exeName);
goto main_error;
}
ret = YES;
main_error:
[pool drain];
free(exePath);
return ret ? 0 : 1;
}

View file

@ -0,0 +1,15 @@
//
// Created by Stefan Reitshamer on 9/16/12.
//
//
@interface AWSQueryError : NSObject <NSXMLParserDelegate> {
NSMutableDictionary *values;
NSMutableString *currentStringBuffer;
BOOL parseErrorOccurred;
NSError *nsError;
}
- (id)initWithDomain:(NSString *)theDomain httpStatusCode:(int)theCode responseBody:(NSData *)theBody;
- (NSError *)nsError;
@end

View file

@ -0,0 +1,77 @@
//
// Created by Stefan Reitshamer on 9/16/12.
//
//
#import "AWSQueryError.h"
@implementation AWSQueryError
- (id)initWithDomain:(NSString *)theDomain httpStatusCode:(int)theCode responseBody:(NSData *)theBody {
if (self = [super init]) {
values = [[NSMutableDictionary alloc] init];
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:theBody];
[parser setDelegate:self];
[parser parse];
[parser release];
if (parseErrorOccurred) {
nsError = [[NSError errorWithDomain:theDomain code:theCode description:@"SNS error"] retain];
} else {
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
[userInfo setObject:[NSNumber numberWithInt:theCode] forKey:@"HTTPStatusCode"];
for (NSString *key in [values allKeys]) {
[userInfo setObject:[values objectForKey:key] forKey:[@"Amazon" stringByAppendingString:key]];
}
NSString *msg = [values objectForKey:@"Message"];
if (msg == nil) {
msg = @"unknown AWS error";
}
if ([[userInfo objectForKey:@"AmazonCode"] isEqualToString:@"SubscriptionRequiredException"]) {
msg = @"Your AWS account is not signed up all services. Please visit http://aws.amazon.com and sign up for S3, Glacier, SNS and SQS.";
}
[userInfo setObject:msg forKey:NSLocalizedDescriptionKey];
nsError = [[NSError errorWithDomain:theDomain code:theCode userInfo:userInfo] retain];
}
}
return self;
}
- (void)dealloc {
[values release];
[currentStringBuffer release];
[nsError release];
[super dealloc];
}
- (NSError *)nsError {
return nsError;
}
#pragma mark - NSXMLParserDelegate
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qualifiedName
attributes:(NSDictionary *)attributeDict {
[currentStringBuffer release];
currentStringBuffer = nil;
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
if (currentStringBuffer == nil) {
currentStringBuffer = [[NSMutableString alloc] init];
}
[currentStringBuffer appendString:string];
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
if (currentStringBuffer != nil) {
[values setObject:[NSString stringWithString:currentStringBuffer] forKey:elementName];
}
}
- (void)parser:(NSXMLParser *)theParser parseErrorOccurred:(NSError *)parseError {
parseErrorOccurred = YES;
HSLogError(@"error parsing amazon error response: %@", parseError);
}
- (void)parserDidEndDocument:(NSXMLParser *)parser {
}
@end

View file

@ -0,0 +1,18 @@
//
// Created by Stefan Reitshamer on 9/16/12.
//
//
@class AWSQueryResponse;
@interface AWSQueryRequest : NSObject {
NSString *method;
NSURL *url;
BOOL retryOnTransientError;
}
- (id)initWithMethod:(NSString *)theMethod url:(NSURL *)theURL retryOnTransientError:(BOOL)theRetryOnTransientError;
- (NSString *)errorDomain;
- (AWSQueryResponse *)execute:(NSError **)error;
@end

View file

@ -0,0 +1,133 @@
//
// Created by Stefan Reitshamer on 9/16/12.
//
//
#import "AWSQueryRequest.h"
#import "AWSQueryResponse.h"
#import "HTTPConnectionFactory.h"
#import "HTTPConnection.h"
#import "InputStream.h"
#import "HTTP.h"
#import "AWSQueryError.h"
#define INITIAL_RETRY_SLEEP (0.5)
#define RETRY_SLEEP_GROWTH_FACTOR (1.5)
#define MAX_RETRY_SLEEP (5.0)
@interface AWSQueryRequest ()
- (AWSQueryResponse *)executeOnce:(NSError **)error;
@end
@implementation AWSQueryRequest
- (id)initWithMethod:(NSString *)theMethod url:(NSURL *)theURL retryOnTransientError:(BOOL)theRetryOnTransientError {
if (self = [super init]) {
method = [theMethod retain];
url = [theURL retain];
retryOnTransientError = theRetryOnTransientError;
}
return self;
}
- (void)dealloc {
[method release];
[url release];
[super dealloc];
}
- (NSString *)errorDomain {
return @"AWSQueryRequestErrorDomain";
}
- (AWSQueryResponse *)execute:(NSError **)error {
NSAutoreleasePool *pool = nil;
NSTimeInterval sleepTime = INITIAL_RETRY_SLEEP;
AWSQueryResponse *theResponse = nil;
NSError *myError = nil;
for (;;) {
[pool drain];
pool = [[NSAutoreleasePool alloc] init];
BOOL transientError = NO;
BOOL needSleep = NO;
myError = nil;
theResponse = [self executeOnce:&myError];
if (theResponse != nil) {
break;
}
if ([myError isErrorWithDomain:[self errorDomain] code:ERROR_NOT_FOUND]) {
break;
} else if ([[[myError userInfo] objectForKey:@"HTTPStatusCode"] intValue] == HTTP_INTERNAL_SERVER_ERROR) {
transientError = YES;
} else if ([myError isConnectionResetError]) {
transientError = YES;
} else if (retryOnTransientError && [myError isErrorWithDomain:[self errorDomain] code:HTTP_SERVICE_NOT_AVAILABLE]) {
// Sometimes SQS returns a 503 error, and we're supposed to retry in that case.
transientError = YES;
needSleep = YES;
} else if (retryOnTransientError && [myError isTransientError]) {
transientError = YES;
needSleep = YES;
} else {
HSLogError(@"%@ %@: %@", method, url, myError);
break;
}
if (transientError) {
HSLogDetail(@"retrying %@ %@: %@", method, url, myError);
}
if (needSleep) {
[NSThread sleepForTimeInterval:sleepTime];
sleepTime *= RETRY_SLEEP_GROWTH_FACTOR;
if (sleepTime > MAX_RETRY_SLEEP) {
sleepTime = MAX_RETRY_SLEEP;
}
}
}
[theResponse retain];
[myError retain];
[pool drain];
[theResponse autorelease];
[myError autorelease];
if (error != NULL) { *error = myError; }
return theResponse;
}
#pragma mark internal
- (AWSQueryResponse *)executeOnce:(NSError **)error {
id <HTTPConnection> conn = [[[HTTPConnectionFactory theFactory] newHTTPConnectionToURL:url method:method dataTransferDelegate:nil] autorelease];
if (conn == nil) {
return nil;
}
[conn setRequestHostHeader];
HSLogDebug(@"%@ %@", method, url);
NSData *responseData = [conn executeRequest:error];
if (responseData == nil) {
return nil;
}
int code = [conn responseCode];
if (code >= 200 && code <= 299) {
HSLogDebug(@"HTTP %d; returning response length=%lu", code, (unsigned long)[responseData length]);
AWSQueryResponse *response = [[[AWSQueryResponse alloc] initWithCode:code headers:[conn responseHeaders] body:responseData] autorelease];
return response;
}
if (code == HTTP_NOT_FOUND) {
SETNSERROR([self errorDomain], ERROR_NOT_FOUND, @"%@ not found", url);
HSLogDebug(@"returning not-found error");
return nil;
}
if (code == HTTP_METHOD_NOT_ALLOWED) {
HSLogError(@"%@ 405 error", url);
SETNSERROR([self errorDomain], ERROR_RRS_NOT_FOUND, @"%@ 405 error", url);
}
AWSQueryError *queryError = [[[AWSQueryError alloc] initWithDomain:[self errorDomain] httpStatusCode:code responseBody:responseData] autorelease];
NSError *myError = [queryError nsError];
HSLogDebug(@"%@ %@ error: %@", method, conn, myError);
if (error != NULL) {
*error = myError;
}
return nil;
}
@end

View file

@ -0,0 +1,17 @@
//
// Created by Stefan Reitshamer on 9/16/12.
//
//
@interface AWSQueryResponse : NSObject {
int code;
NSDictionary *headers;
NSData *body;
}
- (id)initWithCode:(int)theCode headers:(NSDictionary *)theHeaders body:(NSData *)theBody;
- (int)code;
- (NSString *)headerForKey:(NSString *)theKey;
- (NSData *)body;
@end

View file

@ -0,0 +1,34 @@
//
// Created by Stefan Reitshamer on 9/16/12.
//
//
#import "AWSQueryResponse.h"
@implementation AWSQueryResponse
- (id)initWithCode:(int)theCode headers:(NSDictionary *)theHeaders body:(NSData *)theBody {
if (self = [super init]) {
code = theCode;
headers = [theHeaders copy];
body = [theBody retain];
}
return self;
}
- (void)dealloc {
[headers release];
[body release];
[super dealloc];
}
- (int)code {
return code;
}
- (NSString *)headerForKey:(NSString *)theKey {
return [headers objectForKey:theKey];
}
- (NSData *)body {
return body;
}
@end

View file

@ -0,0 +1,56 @@
//
// Created by Stefan Reitshamer on 9/23/12.
//
//
@interface AWSRegion : NSObject {
NSString *regionName;
NSArray *s3LocationConstraints;
NSString *s3Hostname;
NSString *displayName;
NSString *shortDisplayName;
double s3StorageDollarsPerGBMonthStandard;
double s3StorageDollarsPerGBMonthRRS;
double s3UploadDollarsPerGB;
double s3DataTransferOutDollarsPerGB;
double glacierStorageDollarsPerGBMonth;
double glacierUploadDollarsPerGB;
double glacierDataTransferOutDollarsPerGB;
BOOL supportsGlacier;
}
+ (NSArray *)allRegions;
+ (NSArray *)s3Regions;
+ (NSArray *)glacierRegions;
+ (AWSRegion *)regionWithName:(NSString *)theRegionName;
+ (AWSRegion *)regionWithLocation:(NSString *)theLocation;
+ (AWSRegion *)regionWithS3Endpoint:(NSURL *)theEndpoint;
+ (AWSRegion *)usEast1;
+ (AWSRegion *)usWest1;
+ (AWSRegion *)usWest2;
+ (AWSRegion *)euWest1;
+ (AWSRegion *)apSoutheast1;
+ (AWSRegion *)apSoutheast2;
+ (AWSRegion *)apNortheast1;
+ (AWSRegion *)saEast1;
- (NSString *)regionName;
- (NSString *)displayName;
- (NSString *)shortDisplayName;
- (NSString *)defaultS3LocationConstraint;
- (NSArray *)s3LocationConstraints;
- (double)s3StorageDollarsPerGBMonthStandard;
- (double)s3StorageDollarsPerGBMonthRRS;
- (double)s3UploadDollarsPerGB;
- (double)s3DataTransferOutDollarsPerGB;
- (double)glacierStorageDollarsPerGBMonth;
- (double)glacierUploadDollarsPerGB;
- (double)glacierDataTransferOutDollarsPerGB;
- (NSURL *)s3EndpointWithSSL:(BOOL)useSSL;
- (BOOL)supportsGlacier;
- (NSURL *)glacierEndpointWithSSL:(BOOL)useSSL;
- (NSURL *)snsEndpointWithSSL:(BOOL)useSSL;
- (NSURL *)sqsEndpointWithSSL:(BOOL)useSSL;
@end

340
cocoastack/aws/AWSRegion.m Normal file
View file

@ -0,0 +1,340 @@
//
// Created by Stefan Reitshamer on 9/23/12.
//
//
#import "AWSRegion.h"
#import "RegexKitLite.h"
@implementation AWSRegion
+ (NSArray *)allRegions {
return [NSArray arrayWithObjects:
[AWSRegion usEast1],
[AWSRegion usWest1],
[AWSRegion usWest2],
[AWSRegion euWest1],
[AWSRegion apSoutheast1],
[AWSRegion apSoutheast2],
[AWSRegion apNortheast1],
[AWSRegion saEast1],
nil];
}
+ (NSArray *)s3Regions {
return [AWSRegion allRegions];
}
+ (NSArray *)glacierRegions {
NSMutableArray *ret = [NSMutableArray array];
for (AWSRegion *region in [AWSRegion allRegions]) {
if ([region supportsGlacier]) {
[ret addObject:region];
}
}
return ret;
}
+ (AWSRegion *)regionWithName:(NSString *)theRegionName {
for (AWSRegion *region in [AWSRegion allRegions]) {
if ([[region regionName] isEqualToString:theRegionName]) {
return region;
}
}
return nil;
}
+ (AWSRegion *)regionWithLocation:(NSString *)theLocation {
if ([theLocation length] == 0) {
return [AWSRegion usEast1];
}
for (AWSRegion *region in [AWSRegion allRegions]) {
for (NSString *constraint in [region s3LocationConstraints]) {
if ([constraint caseInsensitiveCompare:theLocation] == NSOrderedSame) {
return region;
}
}
}
HSLogDebug(@"no AWS region found for location %@", theLocation);
return nil;
}
+ (AWSRegion *)regionWithS3Endpoint:(NSURL *)theEndpoint {
if (![[theEndpoint scheme] isEqualToString:@"https"] && ![[theEndpoint scheme] isEqualToString:@"http"]) {
// HSLogDebug(@"unknown AWSRegion endpoint scheme: %@", theEndpoint);
return nil;
}
for (AWSRegion *region in [AWSRegion allRegions]) {
NSURL *endpoint = [region s3EndpointWithSSL:YES];
if ([[theEndpoint host] isEqualToString:[endpoint host]]) {
return region;
}
}
// HSLogDebug(@"AWSRegion not found for S3 endpoint %@", theEndpoint);
return nil;
}
+ (AWSRegion *)usEast1 {
return [[[AWSRegion alloc] initWithRegionName:@"us-east-1"
s3LocationConstraints:nil
s3Hostname:@"s3.amazonaws.com"
displayName:@"US East (Northern Virginia)"
shortDisplayName:@"N. Virginia"
s3StorageDollarsPerGBMonthStandard:.030
s3StorageDollarsPerGBMonthRRS:.024
s3UploadDollarsPerGB:.005
s3DataTransferOutDollarsPerGB:.12
glacierStorageDollarsPerGBMonth:.01
glacierUploadDollarsPerGB:.05
glacierDataTransferOutDollarsPerGB:.12
supportsGlacier:YES] autorelease];
}
+ (AWSRegion *)usWest1 {
return [[[AWSRegion alloc] initWithRegionName:@"us-west-1"
s3LocationConstraints:[NSArray arrayWithObject:@"us-west-1"]
s3Hostname:@"s3-us-west-1.amazonaws.com"
displayName:@"US West (Northern California)"
shortDisplayName:@"N. California"
s3StorageDollarsPerGBMonthStandard:.033
s3StorageDollarsPerGBMonthRRS:.0264
s3UploadDollarsPerGB:.0055
s3DataTransferOutDollarsPerGB:.12
glacierStorageDollarsPerGBMonth:.011
glacierUploadDollarsPerGB:.055
glacierDataTransferOutDollarsPerGB:.12
supportsGlacier:YES] autorelease];
}
+ (AWSRegion *)usWest2 {
return [[[AWSRegion alloc] initWithRegionName:@"us-west-2"
s3LocationConstraints:[NSArray arrayWithObject:@"us-west-2"]
s3Hostname:@"s3-us-west-2.amazonaws.com"
displayName:@"US West (Oregon)"
shortDisplayName:@"Oregon"
s3StorageDollarsPerGBMonthStandard:.030
s3StorageDollarsPerGBMonthRRS:.024
s3UploadDollarsPerGB:.005
s3DataTransferOutDollarsPerGB:.12
glacierStorageDollarsPerGBMonth:.01
glacierUploadDollarsPerGB:.05
glacierDataTransferOutDollarsPerGB:.12
supportsGlacier:YES] autorelease];
}
+ (AWSRegion *)euWest1 {
return [[[AWSRegion alloc] initWithRegionName:@"eu-west-1"
s3LocationConstraints:[NSArray arrayWithObjects:@"EU", @"eu-west-1", nil]
s3Hostname:@"s3-eu-west-1.amazonaws.com"
displayName:@"EU (Ireland)"
shortDisplayName:@"Ireland"
s3StorageDollarsPerGBMonthStandard:.030
s3StorageDollarsPerGBMonthRRS:.024
s3UploadDollarsPerGB:.005
s3DataTransferOutDollarsPerGB:.12
glacierStorageDollarsPerGBMonth:.011
glacierUploadDollarsPerGB:.055
glacierDataTransferOutDollarsPerGB:.12
supportsGlacier:YES] autorelease];
}
+ (AWSRegion *)apSoutheast1 {
return [[[AWSRegion alloc] initWithRegionName:@"ap-southeast-1"
s3LocationConstraints:[NSArray arrayWithObject:@"ap-southeast-1"]
s3Hostname:@"s3-ap-southeast-1.amazonaws.com"
displayName:@"Asia Pacific (Singapore)"
shortDisplayName:@"Singapore"
s3StorageDollarsPerGBMonthStandard:.030
s3StorageDollarsPerGBMonthRRS:.024
s3UploadDollarsPerGB:.005
s3DataTransferOutDollarsPerGB:.19
glacierStorageDollarsPerGBMonth:0
glacierUploadDollarsPerGB:0
glacierDataTransferOutDollarsPerGB:0
supportsGlacier:NO] autorelease];
}
+ (AWSRegion *)apSoutheast2 {
return [[[AWSRegion alloc] initWithRegionName:@"ap-southeast-2"
s3LocationConstraints:[NSArray arrayWithObject:@"ap-southeast-2"]
s3Hostname:@"s3-ap-southeast-2.amazonaws.com"
displayName:@"Asia Pacific (Sydney)"
shortDisplayName:@"Sydney"
s3StorageDollarsPerGBMonthStandard:.033
s3StorageDollarsPerGBMonthRRS:.0264
s3UploadDollarsPerGB:.0055
s3DataTransferOutDollarsPerGB:.19
glacierStorageDollarsPerGBMonth:.012
glacierUploadDollarsPerGB:.06
glacierDataTransferOutDollarsPerGB:.19
supportsGlacier:YES] autorelease];
}
+ (AWSRegion *)apNortheast1 {
return [[[AWSRegion alloc] initWithRegionName:@"ap-northeast-1"
s3LocationConstraints:[NSArray arrayWithObject:@"ap-northeast-1"]
s3Hostname:@"s3-ap-northeast-1.amazonaws.com"
displayName:@"Asia Pacific (Tokyo)"
shortDisplayName:@"Tokyo"
s3StorageDollarsPerGBMonthStandard:.033
s3StorageDollarsPerGBMonthRRS:.0264
s3UploadDollarsPerGB:.005
s3DataTransferOutDollarsPerGB:.201
glacierStorageDollarsPerGBMonth:.012
glacierUploadDollarsPerGB:.06
glacierDataTransferOutDollarsPerGB:.201
supportsGlacier:YES] autorelease];
}
+ (AWSRegion *)saEast1 {
return [[[AWSRegion alloc] initWithRegionName:@"sa-east-1"
s3LocationConstraints:[NSArray arrayWithObject:@"sa-east-1"]
s3Hostname:@"s3-sa-east-1.amazonaws.com"
displayName:@"South America (Sao Paulo)"
shortDisplayName:@"Sao Paulo"
s3StorageDollarsPerGBMonthStandard:.0408
s3StorageDollarsPerGBMonthRRS:.0326
s3UploadDollarsPerGB:.007
s3DataTransferOutDollarsPerGB:.25
glacierStorageDollarsPerGBMonth:0
glacierUploadDollarsPerGB:0
glacierDataTransferOutDollarsPerGB:0
supportsGlacier:NO] autorelease];
}
- (void)dealloc {
[regionName release];
[s3LocationConstraints release];
[s3Hostname release];
[displayName release];
[shortDisplayName release];
[super dealloc];
}
- (NSString *)regionName {
return regionName;
}
- (NSString *)displayName {
return displayName;
}
- (NSString *)shortDisplayName {
return shortDisplayName;
}
- (NSString *)defaultS3LocationConstraint {
return [s3LocationConstraints lastObject];
}
- (NSArray *)s3LocationConstraints {
return s3LocationConstraints;
}
- (double)s3StorageDollarsPerGBMonthStandard {
return s3StorageDollarsPerGBMonthStandard;
}
- (double)s3StorageDollarsPerGBMonthRRS {
return s3StorageDollarsPerGBMonthRRS;
}
- (double)s3UploadDollarsPerGB {
return s3UploadDollarsPerGB;
}
- (double)s3DataTransferOutDollarsPerGB {
return s3DataTransferOutDollarsPerGB;
}
- (double)glacierStorageDollarsPerGBMonth {
return glacierStorageDollarsPerGBMonth;
}
- (double)glacierUploadDollarsPerGB {
return glacierUploadDollarsPerGB;
}
- (double)glacierDataTransferOutDollarsPerGB {
return glacierDataTransferOutDollarsPerGB;
}
- (NSURL *)s3EndpointWithSSL:(BOOL)useSSL {
return [NSURL URLWithString:[NSString stringWithFormat:@"http%@://%@", (useSSL ? @"s" : @""), s3Hostname]];
}
- (BOOL)supportsGlacier {
return supportsGlacier;
}
- (NSString *)glacierEndpointWithSSL:(BOOL)useSSL {
if (!supportsGlacier) {
return nil;
}
if (getenv("AWS_HOST")) {
return [NSString stringWithFormat:@"%s/aws/glacier", getenv("AWS_HOST")];
}
return [self endpointWithService:@"glacier" useSSL:useSSL];
}
- (NSString *)snsEndpointWithSSL:(BOOL)useSSL {
if (getenv("AWS_HOST")) {
return [NSString stringWithFormat:@"%s/aws/sns", getenv("AWS_HOST")];
}
return [self endpointWithService:@"sns" useSSL:useSSL];
}
- (NSString *)sqsEndpointWithSSL:(BOOL)useSSL {
if (getenv("AWS_HOST")) {
return [NSString stringWithFormat:@"%s/aws/sqs", getenv("AWS_HOST")];
}
return [self endpointWithService:@"sqs" useSSL:useSSL];
}
#pragma mark NSObject
- (NSString *)description {
return [NSString stringWithFormat:@"<AWSRegion %@>", regionName];
}
- (BOOL)isEqual:(id)other {
if (other == self) {
return YES;
}
if (other == nil || ![other isKindOfClass:[self class]]) {
return NO;
}
return [regionName isEqualToString:[(AWSRegion *)other regionName]];
}
- (NSUInteger)hash {
return [regionName hash];
}
#pragma mark NSCopying
- (id)copyWithZone:(NSZone *)zone {
return [[AWSRegion alloc] initWithRegionName:regionName
s3LocationConstraints:s3LocationConstraints
s3Hostname:s3Hostname
displayName:displayName
shortDisplayName:shortDisplayName
s3StorageDollarsPerGBMonthStandard:s3StorageDollarsPerGBMonthStandard
s3StorageDollarsPerGBMonthRRS:s3StorageDollarsPerGBMonthRRS
s3UploadDollarsPerGB:s3UploadDollarsPerGB
s3DataTransferOutDollarsPerGB:s3DataTransferOutDollarsPerGB
glacierStorageDollarsPerGBMonth:glacierStorageDollarsPerGBMonth
glacierUploadDollarsPerGB:glacierUploadDollarsPerGB
glacierDataTransferOutDollarsPerGB:glacierDataTransferOutDollarsPerGB
supportsGlacier:supportsGlacier];
}
#pragma mark internal
- (id)initWithRegionName:(NSString *)theRegionName
s3LocationConstraints:(NSArray *)theS3LocationConstraints
s3Hostname:(NSString *)theS3Hostname
displayName:(NSString *)theDisplayName
shortDisplayName:(NSString *)theShortDisplayName
s3StorageDollarsPerGBMonthStandard:(double)theS3StorageDollarsPerGBMonthStandard
s3StorageDollarsPerGBMonthRRS:(double)theS3StorageDollarsPerGBMonthRRS
s3UploadDollarsPerGB:(double)theS3UploadDollarsPerGB
s3DataTransferOutDollarsPerGB:(double)theS3DataTransferOutDollarsPerGB
glacierStorageDollarsPerGBMonth:(double)theGlacierStorageDollarsPerGBMonth
glacierUploadDollarsPerGB:(double)theGlacierUploadDollarsPerGB
glacierDataTransferOutDollarsPerGB:(double)theGlacierDataTransferOutDollarsPerGB
supportsGlacier:(BOOL)theSupportsGlacier {
if (self = [super init]) {
regionName = [theRegionName retain];
s3LocationConstraints = [theS3LocationConstraints retain];
s3Hostname = [theS3Hostname retain];
displayName = [theDisplayName retain];
shortDisplayName = [theShortDisplayName retain];
s3StorageDollarsPerGBMonthStandard = theS3StorageDollarsPerGBMonthStandard;
s3StorageDollarsPerGBMonthRRS = theS3StorageDollarsPerGBMonthRRS;
s3UploadDollarsPerGB = theS3UploadDollarsPerGB;
s3DataTransferOutDollarsPerGB = theS3DataTransferOutDollarsPerGB;
glacierStorageDollarsPerGBMonth = theGlacierStorageDollarsPerGBMonth;
glacierUploadDollarsPerGB = theGlacierUploadDollarsPerGB;
glacierDataTransferOutDollarsPerGB = theGlacierDataTransferOutDollarsPerGB;
supportsGlacier = theSupportsGlacier;
}
return self;
}
- (NSString *)endpointWithService:(NSString *)theServiceName useSSL:(BOOL)useSSL {
return [NSString stringWithFormat:@"http%@://%@.%@.amazonaws.com", (useSSL ? @"s" : @""), theServiceName, regionName];
}
@end

View file

@ -0,0 +1,15 @@
//
// SignatureV2Provider.h
// Arq
//
// Created by Stefan Reitshamer on 9/16/12.
//
//
@interface SignatureV2Provider : NSObject {
NSData *secretKeyData;
}
- (id)initWithSecretKey:(NSString *)secret;
- (NSString *)signatureForHTTPMethod:(NSString *)theMethod url:(NSURL *)theURL;
@end

View file

@ -0,0 +1,44 @@
//
// SignatureV2Provider.m
// Arq
//
// Created by Stefan Reitshamer on 9/16/12.
//
//
#include <CommonCrypto/CommonHMAC.h>
#import "SignatureV2Provider.h"
#import "NSData-Base64Extensions.h"
@implementation SignatureV2Provider
- (id)initWithSecretKey:(NSString *)theSecretKey {
if (self = [super init]) {
secretKeyData = [[theSecretKey dataUsingEncoding:NSUTF8StringEncoding] retain];
}
return self;
}
- (void)dealloc {
[secretKeyData release];
[super dealloc];
}
- (NSString *)signatureForHTTPMethod:(NSString *)theMethod url:(NSURL *)theURL {
NSMutableString *stringToSign = [NSMutableString string];
[stringToSign appendFormat:@"%@\n", theMethod];
[stringToSign appendFormat:@"%@\n", [[theURL host] lowercaseString]];
NSString *thePath = [theURL path];
if ([thePath length] == 0) {
thePath = @"/";
}
[stringToSign appendFormat:@"%@\n", thePath];
[stringToSign appendString:[theURL query]];
NSData *data = [stringToSign dataUsingEncoding:NSUTF8StringEncoding];
unsigned char digest[CC_SHA256_DIGEST_LENGTH];
CCHmac(kCCHmacAlgSHA256, [secretKeyData bytes], [secretKeyData length], [data bytes], [data length], digest);
NSData *sig = [NSData dataWithBytes:digest length:CC_SHA256_DIGEST_LENGTH];
return [sig encodeBase64];
}
@end

View file

@ -0,0 +1,28 @@
//
// CryptoKey.h
//
// Created by Stefan Reitshamer on 6/9/11.
// Copyright 2011 Haystack Software. All rights reserved.
//
#ifdef USE_OPENSSL
#import "OpenSSLCryptoKey.h"
#else
#import "CCCryptoKey.h"
#endif
@interface CryptoKey : NSObject {
#ifdef USE_OPENSSL
OpenSSLCryptoKey *cryptoKey;
#else
CCCryptoKey *cryptoKey;
#endif
}
+ (NSString *)errorDomain;
- (id)initWithPassword:(NSString *)thePassword salt:(NSData *)theSalt error:(NSError **)error;
- (id)initLegacyWithPassword:(NSString *)thePassword error:(NSError **)error;
- (NSData *)encrypt:(NSData *)plainData error:(NSError **)error;
- (NSData *)decrypt:(NSData *)encrypted error:(NSError **)error;
@end

View file

@ -0,0 +1,78 @@
//
// CryptoKey.m
// Arq
//
// Created by Stefan Reitshamer on 6/9/11.
// Copyright 2011 Haystack Software. All rights reserved.
//
#import "CryptoKey.h"
#ifdef USE_OPENSSL
#import "OpenSSLCryptoKey.h"
#else
#import "CCCryptoKey.h"
#endif
@implementation CryptoKey
+ (NSString *)errorDomain {
return @"CryptoKeyErrorDomain";
}
- (id)init {
@throw [NSException exceptionWithName:@"InvalidInitializerException" reason:@"can't call CryptoKey init" userInfo:nil];
}
- (id)initWithPassword:(NSString *)thePassword salt:(NSData *)theSalt error:(NSError **)error {
if (self = [super init]) {
if ([thePassword length] == 0) {
SETNSERROR([CryptoKey errorDomain], ERROR_NOT_FOUND, @"missing encryption password");
[self release];
return nil;
}
#ifdef USE_OPENSSL
cryptoKey = [[OpenSSLCryptoKey alloc] initWithPassword:thePassword salt:theSalt error:error];
HSLogDebug(@"using OpenSSL");
#else
cryptoKey = [[CCCryptoKey alloc] initWithPassword:thePassword salt:theSalt error:error];
HSLogDebug(@"using CommonCrypto");
#endif
if (cryptoKey == nil) {
[self release];
return nil;
}
}
return self;
}
- (id)initLegacyWithPassword:(NSString *)thePassword error:(NSError **)error {
if (self = [super init]) {
if ([thePassword length] == 0) {
SETNSERROR([CryptoKey errorDomain], ERROR_NOT_FOUND, @"missing encryption password");
[self release];
return nil;
}
#ifdef USE_OPENSSL
cryptoKey = [[OpenSSLCryptoKey alloc] initLegacyWithPassword:thePassword error:error];
#else
cryptoKey = [[CCCryptoKey alloc] initLegacyWithPassword:thePassword error:error];
#endif
if (cryptoKey == nil) {
[self release];
return nil;
}
}
return self;
}
- (void)dealloc {
[cryptoKey release];
[super dealloc];
}
- (NSData *)encrypt:(NSData *)plainData error:(NSError **)error {
return [cryptoKey encrypt:plainData error:error];
}
- (NSData *)decrypt:(NSData *)encrypted error:(NSError **)error {
return [cryptoKey decrypt:encrypted error:error];
}
@end

View file

@ -0,0 +1,12 @@
//
// MD5Hash.h
// Arq
//
// Created by Stefan Reitshamer on 1/1/14.
// Copyright (c) 2014 Stefan Reitshamer. All rights reserved.
//
@interface MD5Hash : NSObject
+ (NSString *)hashDataBase64Encode:(NSData *)theData;
@end

View file

@ -0,0 +1,25 @@
//
// MD5Hash.m
// Arq
//
// Created by Stefan Reitshamer on 1/1/14.
// Copyright (c) 2014 Stefan Reitshamer. All rights reserved.
//
#import <CommonCrypto/CommonDigest.h>
#import "MD5Hash.h"
#import "NSString_extra.h"
#import "NSData-Base64Extensions.h"
@implementation MD5Hash
+ (NSString *)hashDataBase64Encode:(NSData *)data {
unsigned char digest[CC_MD5_DIGEST_LENGTH];
memset(digest, 0, CC_MD5_DIGEST_LENGTH);
if (CC_MD5([data bytes], (CC_LONG)[data length], digest) == NULL) {
HSLogError(@"CC_MD5 failed!");
}
NSData *digestData = [NSData dataWithBytes:digest length:CC_MD5_DIGEST_LENGTH];
return [digestData encodeBase64];
}
@end

View file

@ -18,10 +18,9 @@
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import <Foundation/Foundation.h>
@interface NSData (Base64)
- (NSString *) encodeBase64;
- (NSString *) encodeBase64WithNewlines: (BOOL) encodeWithNewlines;
@end

View file

@ -0,0 +1,93 @@
// Copyright (c) 2006 Dave Dribin (http://www.dribin.org/dave/)
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "NSData-Base64Extensions.h"
#include <openssl/bio.h>
#include <openssl/evp.h>
@implementation NSData (Base64)
- (NSString *) encodeBase64 {
//Point to start of the data and set buffer sizes
int inLength = (int)[self length];
int outLength = ((((inLength * 4)/3)/4)*4) + (((inLength * 4)/3)%4 ? 4 : 0);
const char *inputBuffer = [self bytes];
char *outputBuffer = malloc(outLength+1);
outputBuffer[outLength] = 0;
//64 digit code
static char Encode[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
//start the count
int cycle = 0;
int inpos = 0;
int outpos = 0;
char temp;
//Pad the last to bytes, the outbuffer must always be a multiple of 4
outputBuffer[outLength-1] = '=';
outputBuffer[outLength-2] = '=';
/* http://en.wikipedia.org/wiki/Base64
Text content M a n
ASCII 77 97 110
8 Bit pattern 01001101 01100001 01101110
6 Bit pattern 010011 010110 000101 101110
Index 19 22 5 46
Base64-encoded T W F u
*/
while (inpos < inLength){
switch (cycle) {
case 0:
outputBuffer[outpos++] = Encode[(inputBuffer[inpos]&0xFC)>>2];
cycle = 1;
break;
case 1:
temp = (inputBuffer[inpos++]&0x03)<<4;
outputBuffer[outpos] = Encode[temp];
cycle = 2;
break;
case 2:
outputBuffer[outpos++] = Encode[temp|(inputBuffer[inpos]&0xF0)>> 4];
temp = (inputBuffer[inpos++]&0x0F)<<2;
outputBuffer[outpos] = Encode[temp];
cycle = 3;
break;
case 3:
outputBuffer[outpos++] = Encode[temp|(inputBuffer[inpos]&0xC0)>>6];
cycle = 4;
break;
case 4:
outputBuffer[outpos++] = Encode[inputBuffer[inpos++]&0x3f];
cycle = 0;
break;
default:
cycle = 0;
break;
}
}
NSString *pictemp = [NSString stringWithUTF8String:outputBuffer];
free(outputBuffer);
return pictemp;
}
@end

View file

@ -0,0 +1,18 @@
//
// OpenSSL.h
// Arq
//
// Created by Stefan Reitshamer on 10/8/12.
// Copyright (c) 2012 Stefan Reitshamer. All rights reserved.
//
#import <openssl/ssl.h>
@interface OpenSSL : NSObject {
}
+ (BOOL)initializeSSL:(NSError **)error;
+ (SSL_CTX *)context;
+ (NSString *)errorMessage;
@end

View file

@ -0,0 +1,66 @@
//
// OpenSSL.m
// Arq
//
// Created by Stefan Reitshamer on 10/8/12.
// Copyright (c) 2012 Stefan Reitshamer. All rights reserved.
//
#ifdef USE_OPENSSL
#import "OpenSSL.h"
#import <openssl/err.h>
#import <openssl/ssl.h>
static BOOL initialized = NO;
static SSL_CTX *ctx;
@implementation OpenSSL
+ (BOOL)initializeSSL:(NSError **)error {
if (!initialized) {
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
ERR_load_crypto_strings();
ctx = SSL_CTX_new(SSLv23_method());
if (ctx == NULL) {
SETNSERROR(@"SSLErrorDomain", -1, @"SSL_CTX_new: %@", [OpenSSL errorMessage]);
return NO;
}
initialized = YES;
#pragma GCC diagnostic warning "-Wdeprecated-declarations"
}
return YES;
}
+ (SSL_CTX *)context {
return ctx;
}
+ (NSString *)errorMessage {
NSMutableString *msg = [NSMutableString string];
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
for (;;) {
unsigned long err = ERR_get_error();
if (err == 0) {
break;
}
if ([msg length] > 0) {
[msg appendString:@"; "];
}
HSLogTrace(@"%s", ERR_error_string(err, NULL));
[msg appendFormat:@"%s", ERR_reason_error_string(err)];
}
if ([msg length] == 0) {
[msg appendString:@"(no error)"];
}
#pragma GCC diagnostic warning "-Wdeprecated-declarations"
return msg;
}
@end
#endif /* USE_OPENSSL */

View file

@ -1,25 +1,27 @@
//
// CryptoKey.h
// OpenSSLCryptoKey.h
// Arq
//
// Created by Stefan Reitshamer on 6/9/11.
// Copyright 2011 __MyCompanyName__. All rights reserved.
// Created by Stefan Reitshamer on 10/8/12.
// Copyright (c) 2012 Stefan Reitshamer. All rights reserved.
//
#ifdef USE_OPENSSL
#include <openssl/evp.h>
@interface CryptoKey : NSObject {
@interface OpenSSLCryptoKey : NSObject {
const EVP_CIPHER *cipher;
unsigned char evpKey[EVP_MAX_KEY_LENGTH];
unsigned char iv[EVP_MAX_IV_LENGTH];
}
+ (NSString *)errorDomain;
- (id)initWithPassword:(NSString *)thePassword salt:(NSData *)theSalt error:(NSError **)error;
- (id)initLegacyWithPassword:(NSString *)thePassword error:(NSError **)error;
- (const EVP_CIPHER *)cipher;
- (unsigned char *)evpKey;
- (unsigned char *)iv;
- (NSData *)encrypt:(NSData *)plainData error:(NSError **)error;
- (NSData *)decrypt:(NSData *)encrypted error:(NSError **)error;
@end
#endif

View file

@ -0,0 +1,184 @@
//
// OpenSSLCryptoKey.m
// Arq
//
// Created by Stefan Reitshamer on 10/8/12.
// Copyright (c) 2012 Stefan Reitshamer. All rights reserved.
//
#ifdef USE_OPENSSL
#import "OpenSSLCryptoKey.h"
#import "OpenSSL.h"
#import "CryptoKey.h"
#define ITERATIONS (1000)
#define KEYLEN (48)
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
@implementation OpenSSLCryptoKey
- (id)initWithPassword:(NSString *)thePassword salt:(NSData *)theSalt error:(NSError **)error {
if (self = [super init]) {
if (![OpenSSL initializeSSL:error]) {
[self release];
return nil;
}
if (theSalt != nil && [theSalt length] != 8) {
SETNSERROR([CryptoKey errorDomain], -1, @"salt must be 8 bytes or nil");
[self release];
return nil;
}
cipher = EVP_aes_256_cbc();
if (cipher == NULL) {
SETNSERROR([CryptoKey errorDomain], -1, @"cipher not found!");
[self release];
return nil;
}
const char *cPassword = [thePassword UTF8String];
unsigned char *cSaltCopy = NULL;
if (theSalt != nil) {
cSaltCopy = (unsigned char *)malloc([theSalt length]);
memcpy(cSaltCopy, [theSalt bytes], [theSalt length]);
} else {
HSLogWarn(@"NULL salt value for CryptoKey");
}
unsigned char buf[KEYLEN];
memset(buf, 0, KEYLEN);
if (PKCS5_PBKDF2_HMAC_SHA1(cPassword, (int)strlen(cPassword), cSaltCopy, (int)[theSalt length], ITERATIONS, KEYLEN, buf) == 0) {
SETNSERROR([CryptoKey errorDomain], -1, @"PKCS5_PBKDF2_HMAC_SHA1 failed");
if (cSaltCopy != NULL) {
free(cSaltCopy);
}
[self release];
return nil;
}
evpKey[0] = 0;
int keySize = EVP_BytesToKey(cipher, EVP_sha1(), cSaltCopy, buf, KEYLEN, ITERATIONS, evpKey, iv);
if (cSaltCopy != NULL) {
free(cSaltCopy);
}
if (keySize == 0) {
SETNSERROR([CryptoKey errorDomain], -1, @"EVP_BytesToKey: %@", [OpenSSL errorMessage]);
[self release];
return nil;
}
if (keySize != 32) {
SETNSERROR([CryptoKey errorDomain], -1, @"invalid key length -- should be 32 bytes");
[self release];
return nil;
}
}
return self;
}
- (id)initLegacyWithPassword:(NSString *)thePassword error:(NSError **)error {
if (self = [super init]) {
if (![OpenSSL initializeSSL:error]) {
[self release];
return nil;
}
cipher = EVP_aes_256_cbc();
evpKey[0] = 0;
NSData *passwordData = [thePassword dataUsingEncoding:NSUTF8StringEncoding];
int keySize = EVP_BytesToKey(cipher, EVP_md5(), NULL, [passwordData bytes], (int)[passwordData length], 1, evpKey, iv);
if (keySize == 0) {
SETNSERROR([CryptoKey errorDomain], -1, @"EVP_BytesToKey: %@", [OpenSSL errorMessage]);
[self release];
return nil;
}
if (keySize != 32) {
SETNSERROR([CryptoKey errorDomain], -1, @"invalid key length -- should be 32 bytes");
[self release];
return nil;
}
}
return self;
}
- (void)dealloc {
[super dealloc];
}
- (NSData *)encrypt:(NSData *)plainData error:(NSError **)error {
if ([plainData length] == 0) {
return [NSData data];
}
EVP_CIPHER_CTX cipherContext;
EVP_CIPHER_CTX_init(&cipherContext);
if (!EVP_EncryptInit(&cipherContext, cipher, evpKey, iv)) {
SETNSERROR([CryptoKey errorDomain], -1, @"EVP_EncryptInit: %@", [OpenSSL errorMessage]);
EVP_CIPHER_CTX_cleanup(&cipherContext);
return nil;
}
// Need room for data + cipher block size - 1.
unsigned char *outbuf = (unsigned char *)malloc([plainData length] + EVP_CIPHER_CTX_block_size(&cipherContext) - 1);
int outlen = 0;
if (!EVP_EncryptUpdate(&cipherContext, outbuf, &outlen, [plainData bytes], (int)[plainData length])) {
SETNSERROR([CryptoKey errorDomain], -1, @"EVP_EncryptUpdate: %@", [OpenSSL errorMessage]);
free(outbuf);
EVP_CIPHER_CTX_cleanup(&cipherContext);
return nil;
}
int extralen = 0;
if (!EVP_EncryptFinal(&cipherContext, outbuf + outlen, &extralen)) {
SETNSERROR([CryptoKey errorDomain], -1, @"EVP_EncryptFinal: %@", [OpenSSL errorMessage]);
free(outbuf);
EVP_CIPHER_CTX_cleanup(&cipherContext);
return nil;
}
EVP_CIPHER_CTX_cleanup(&cipherContext);
NSData *ret = [[[NSData alloc] initWithBytesNoCopy:outbuf length:(outlen + extralen)] autorelease];
return ret;
}
- (NSData *)decrypt:(NSData *)encrypted error:(NSError **)error {
if (encrypted == nil) {
SETNSERROR([CryptoKey errorDomain], -1, @"decrypt: nil input NSData");
return nil;
}
if ([encrypted length] == 0) {
return [NSData data];
}
int inlen = (int)[encrypted length];
unsigned char *input = (unsigned char *)[encrypted bytes];
EVP_CIPHER_CTX cipherContext;
EVP_CIPHER_CTX_init(&cipherContext);
if (!EVP_DecryptInit(&cipherContext, cipher, evpKey, iv)) {
SETNSERROR([CryptoKey errorDomain], -1, @"EVP_DecryptInit: %@", [OpenSSL errorMessage]);
EVP_CIPHER_CTX_cleanup(&cipherContext);
return nil;
}
unsigned char *outbuf = (unsigned char *)malloc(inlen + EVP_CIPHER_CTX_block_size(&cipherContext));
int outlen = 0;
if (!EVP_DecryptUpdate(&cipherContext, outbuf, &outlen, input, inlen)) {
SETNSERROR([CryptoKey errorDomain], -1, @"EVP_DecryptUpdate: %@", [OpenSSL errorMessage]);
free(outbuf);
EVP_CIPHER_CTX_cleanup(&cipherContext);
return nil;
}
int extralen = 0;
if (!EVP_DecryptFinal(&cipherContext, outbuf + outlen, &extralen)) {
SETNSERROR([CryptoKey errorDomain], -1, @"EVP_DecryptFinal: %@", [OpenSSL errorMessage]);
free(outbuf);
EVP_CIPHER_CTX_cleanup(&cipherContext);
return nil;
}
EVP_CIPHER_CTX_cleanup(&cipherContext);
NSData *ret = [[[NSData alloc] initWithBytesNoCopy:outbuf length:(outlen + extralen)] autorelease];
return ret;
}
@end
#endif

View file

@ -32,14 +32,13 @@
#import "InputStream.h"
@class Blob;
@interface SHA1Hash : NSObject {
}
+ (NSString *)errorDomain;
+ (NSString *)hashData:(NSData *)data;
+ (NSString *)hashBlob:(Blob *)blob blobLength:(unsigned long long *)blobLength error:(NSError **)error;
+ (NSString *)hashStream:(id <InputStream>)is withLength:(uint64_t)length error:(NSError **)error;
+ (NSString *)hashFile:(NSString *)path error:(NSError **)error;
+ (NSString *)hashPhoto:(NSString *)path error:(NSError **)error;
@end

View file

@ -0,0 +1,246 @@
/*
Copyright (c) 2009-2011, 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 <CommonCrypto/CommonDigest.h>
#include <sys/stat.h>
#import "SHA1Hash.h"
#import "NSString_extra.h"
#import "FileInputStream.h"
#import "BufferedInputStream.h"
#define MY_BUF_SIZE (4096)
@interface SHA1Hash (internal)
+ (NSString *)hashStream:(id <InputStream>)is error:(NSError **)error;
+ (NSString *)hashStream:(id <InputStream>)is streamLength:(unsigned long long *)streamLength error:(NSError **)error;
+ (NSString *)hashPhotoStream:(BufferedInputStream *)bis totalLength:(unsigned long long)totalLength error:(NSError **)error;
+ (BOOL)updateSHA1:(CC_SHA1_CTX *)ctx fromStream:(id <InputStream>)is length:(unsigned long long)theLength error:(NSError **)error;
@end
@implementation SHA1Hash
+ (NSString *)errorDomain {
return @"SHA1HashErrorDomain";
}
+ (NSString *)hashData:(NSData *)data {
unsigned char digest[CC_SHA1_DIGEST_LENGTH];
memset(digest, 0, CC_SHA1_DIGEST_LENGTH);
if (CC_SHA1([data bytes], (CC_LONG)[data length], digest) == NULL) {
HSLogError(@"CC_SHA1 failed!");
}
return [NSString hexStringWithBytes:digest length:CC_SHA1_DIGEST_LENGTH];
}
+ (NSString *)hashFile:(NSString *)path error:(NSError **)error {
struct stat st;
if (lstat([path fileSystemRepresentation], &st) == -1) {
int errnum = errno;
HSLogError(@"lstat(%@) error %d: %s", path, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"%@: %s", path, strerror(errnum));
return NO;
}
unsigned long long length = (unsigned long long)st.st_size;
FileInputStream *fis = [[FileInputStream alloc] initWithPath:path offset:0 length:length];
NSString *sha1 = [SHA1Hash hashStream:fis error:error];
[fis release];
return sha1;
}
+ (NSString *)hashStream:(id <InputStream>)is withLength:(uint64_t)length error:(NSError **)error {
CC_SHA1_CTX ctx;
CC_SHA1_Init(&ctx);
uint64_t received = 0;
unsigned char *buf = (unsigned char *)malloc(MY_BUF_SIZE);
NSInteger ret = 0;
while (received < length) {
uint64_t toRead = length - received;
uint64_t toReadThisTime = toRead > MY_BUF_SIZE ? MY_BUF_SIZE : toRead;
ret = [is read:buf bufferLength:(NSUInteger)toReadThisTime error:error];
if (ret < 0) {
break;
}
if (ret == 0) {
SETNSERROR([SHA1Hash errorDomain], ERROR_EOF, @"unexpected EOF in %@ after %qu of %qu bytes", is, received, length);
break;
}
CC_SHA1_Update(&ctx, buf, (CC_LONG)ret);
received += (uint64_t)ret;
}
free(buf);
if (ret < 0) {
return nil;
}
unsigned char digest[CC_SHA1_DIGEST_LENGTH];
CC_SHA1_Final(digest, &ctx);
return [NSString hexStringWithBytes:digest length:CC_SHA1_DIGEST_LENGTH];
}
+ (NSString *)hashPhoto:(NSString *)path error:(NSError **)error {
struct stat st;
if (lstat([path fileSystemRepresentation], &st) == -1) {
int errnum = errno;
HSLogError(@"lstat(%@) error %d: %s", path, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"%@: %s", path, strerror(errnum));
return nil;
}
unsigned long long totalLength = (unsigned long long)st.st_size;
FileInputStream *fis = [[FileInputStream alloc] initWithPath:path offset:0 length:totalLength];
BufferedInputStream *bis = [[BufferedInputStream alloc] initWithUnderlyingStream:fis];
[fis release];
NSString *sha1 = [SHA1Hash hashPhotoStream:bis totalLength:totalLength error:error];
[bis release];
if (sha1 == nil) {
sha1 = [SHA1Hash hashFile:path error:error];
}
return sha1;
}
@end
@implementation SHA1Hash (internal)
+ (NSString *)hashStream:(id <InputStream>)is error:(NSError **)error {
unsigned long long length;
return [SHA1Hash hashStream:is streamLength:&length error:error];
}
+ (NSString *)hashStream:(id <InputStream>)is streamLength:(unsigned long long *)streamLength error:(NSError **)error {
CC_SHA1_CTX ctx;
CC_SHA1_Init(&ctx);
*streamLength = 0;
unsigned char *buf = (unsigned char *)malloc(MY_BUF_SIZE);
NSInteger ret = 0;
for (;;) {
ret = [is read:buf bufferLength:MY_BUF_SIZE error:error];
if (ret <= 0) {
break;
}
CC_SHA1_Update(&ctx, buf, (CC_LONG)ret);
*streamLength += (unsigned long long)ret;
}
free(buf);
if (ret < 0) {
return nil;
}
unsigned char md[CC_SHA1_DIGEST_LENGTH];
CC_SHA1_Final(md, &ctx);
return [NSString hexStringWithBytes:md length:CC_SHA1_DIGEST_LENGTH];
}
+ (NSString *)hashPhotoStream:(BufferedInputStream *)bis totalLength:(unsigned long long)totalLength error:(NSError **)error {
CC_SHA1_CTX ctx;
CC_SHA1_Init(&ctx);
uint64_t received = 0;
if (totalLength > 4) {
unsigned char buf[2];
if (![bis readExactly:2 into:buf error:error]) {
return nil;
}
received += 2;
if (buf[0] == 0xff && buf[1] == 0xd8) {
// It's a JPEG. Skip the metadata.
for (;;) {
if (![bis readExactly:2 into:buf error:error]) {
return nil;
}
received += 2;
unsigned int markerID = ((unsigned int)buf[0] << 8) + (unsigned int)buf[1];
NSAssert(received <= totalLength, @"received can't be greater than totalLength");
if (received == totalLength) {
if (markerID != 0xffd9) {
HSLogWarn(@"unexpected end marker in JPEG: 0x%04x", markerID);
}
break;
}
if (![bis readExactly:2 into:buf error:error]) {
return nil;
}
received += 2;
uint32_t segmentLength = ((uint32_t)buf[0] << 8) + (uint32_t)buf[1];
if (markerID == 0xffda) {
// Read in the rest of the file minus the last 2 bytes.
if (![self updateSHA1:&ctx fromStream:bis length:(totalLength - 2 - received) error:error]) {
return nil;
}
received = totalLength - 2;
} else {
if (segmentLength < 3) {
SETNSERROR([SHA1Hash errorDomain], -1, @"%@: JPEG segment %04x length can't be fewer than 3 bytes long", bis, (unsigned int)markerID);
return nil;
}
NSData *data = [bis readExactly:(segmentLength - 2) error:error];
if (data == nil) {
return nil;
}
received += segmentLength - 2;
}
}
} else {
CC_SHA1_Update(&ctx, buf, 2);
}
}
unsigned char *buf = (unsigned char *)malloc(MY_BUF_SIZE);
NSInteger ret = 0;
while (received < totalLength) {
uint64_t needed = totalLength - received;
int64_t toRead = needed > MY_BUF_SIZE ? MY_BUF_SIZE : needed;
ret = [bis read:buf bufferLength:(NSUInteger)toRead error:error];
if (ret <= 0) {
break;
}
CC_SHA1_Update(&ctx, buf, (CC_LONG)ret);
received += (uint64_t)ret;
}
free(buf);
if (ret < 0) {
return nil;
}
unsigned char md[CC_SHA1_DIGEST_LENGTH];
CC_SHA1_Final(md, &ctx);
return [NSString hexStringWithBytes:md length:CC_SHA1_DIGEST_LENGTH];
}
+ (BOOL)updateSHA1:(CC_SHA1_CTX *)ctx fromStream:(id <InputStream>)is length:(unsigned long long)theLength error:(NSError **)error {
unsigned char *imageBuf = (unsigned char *)malloc(MY_BUF_SIZE);
uint64_t recvd = 0;
NSInteger ret = 0;
while (recvd < theLength) {
uint64_t needed = theLength - recvd;
uint64_t toRead = needed > MY_BUF_SIZE ? MY_BUF_SIZE : needed;
ret = [is read:imageBuf bufferLength:(NSUInteger)toRead error:error];
if (ret < 0) {
break;
}
if (ret == 0) {
SETNSERROR([SHA1Hash errorDomain], -1, @"unexpected EOF reading image data from %@", is);
break;
}
CC_SHA1_Update(ctx, imageBuf, (CC_LONG)ret);
recvd += (uint64_t)ret;
}
free(imageBuf);
return ret > 0;
}
@end

View file

@ -0,0 +1,15 @@
//
// SHA256Hash.h
// Arq
//
// Created by Stefan Reitshamer on 9/8/12.
//
//
@interface SHA256Hash : NSObject {
}
+ (NSData *)hashData:(NSData *)data;
+ (NSData *)hashBytes:(const unsigned char *)bytes length:(NSUInteger)length;
@end

View file

@ -0,0 +1,26 @@
//
// SHA256Hash.m
// Arq
//
// Created by Stefan Reitshamer on 9/8/12.
//
//
#import "SHA256Hash.h"
#import <CommonCrypto/CommonDigest.h>
@implementation SHA256Hash
+ (NSData *)hashData:(NSData *)data {
NSAssert(data != nil, @"data may not be nil!");
return [SHA256Hash hashBytes:(const unsigned char *)[data bytes] length:[data length]];
}
+ (NSData *)hashBytes:(const unsigned char *)bytes length:(NSUInteger)length {
unsigned char *digest = (unsigned char *)malloc(CC_SHA256_DIGEST_LENGTH);
memset(digest, 0, CC_SHA256_DIGEST_LENGTH);
if (CC_SHA256(bytes, (CC_LONG)length, digest) == NULL) {
HSLogError(@"CC_SHA256 failed!");
}
return [NSData dataWithBytesNoCopy:digest length:CC_SHA256_DIGEST_LENGTH];
}
@end

View file

@ -0,0 +1,21 @@
//
// GlacierAuthorization.h
//
// Created by Stefan Reitshamer on 9/8/12.
//
//
@class AWSRegion;
@protocol HTTPConnection;
@protocol GlacierSigner;
@interface GlacierAuthorization : NSObject {
AWSRegion *awsRegion;
id <HTTPConnection> conn;
NSData *requestBody;
NSString *accessKey;
id <GlacierSigner> signer;
}
- (id)initWithAWSRegion:(AWSRegion *)theAWSRegion connection:(id <HTTPConnection>)theConn requestBody:(NSData *)theRequestBody accessKey:(NSString *)theAccessKey signer:(id <GlacierSigner>)theSigner;
@end

View file

@ -0,0 +1,120 @@
//
// GlacierAuthorization.m
//
// Created by Stefan Reitshamer on 9/8/12.
//
//
#import "GlacierAuthorization.h"
#import "HTTPConnection.h"
#import "GlacierSigner.h"
#import "AWSRegion.h"
#import "ISO8601Date.h"
#import "SHA256Hash.h"
#import "NSString_extra.h"
@implementation GlacierAuthorization
- (id)initWithAWSRegion:(AWSRegion *)theAWSRegion connection:(id <HTTPConnection>)theConn requestBody:(NSData *)theRequestBody accessKey:(NSString *)theAccessKey signer:(id <GlacierSigner>)theSigner {
if (self = [super init]) {
awsRegion = [theAWSRegion retain];
conn = [theConn retain];
requestBody = [theRequestBody retain];
accessKey = [theAccessKey retain];
signer = [theSigner retain];
}
return self;
}
- (void)dealloc {
[awsRegion release];
[conn release];
[requestBody release];
[accessKey release];
[signer release];
[super dealloc];
}
#pragma mark NSObject
- (NSString *)description {
NSMutableString *canonicalRequest = [[[NSMutableString alloc] init] autorelease];
[canonicalRequest appendString:[conn requestMethod]];
[canonicalRequest appendString:@"\n"];
[canonicalRequest appendString:[conn requestPathInfo]];
[canonicalRequest appendString:@"\n"];
if ([conn requestQueryString] != nil) {
NSString *query = [(NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
(CFStringRef)[conn requestQueryString],
(CFStringRef)@"=",
(CFStringRef)@"!*'();:@&+$,/?%#[]",
kCFStringEncodingUTF8) autorelease];
[canonicalRequest appendString:query];
}
[canonicalRequest appendString:@"\n"];
// Add sorted canonical headers:
NSMutableArray *theHeaders = [NSMutableArray array];
for (NSString *headerName in [conn requestHeaderKeys]) {
NSString *lower = [headerName lowercaseString];
[theHeaders addObject:[NSString stringWithFormat:@"%@:%@\n", lower, [conn requestHeaderForKey:headerName]]];
}
[theHeaders sortUsingSelector:@selector(compare:)];
for (NSString *hdr in theHeaders) {
[canonicalRequest appendString:hdr];
}
// Add a newline:
[canonicalRequest appendString:@"\n"];
NSMutableArray *theSortedLowercaseHeaderNames = [NSMutableArray array];
for (NSString *headerName in [conn requestHeaderKeys]) {
[theSortedLowercaseHeaderNames addObject:[headerName lowercaseString]];
}
[theSortedLowercaseHeaderNames sortUsingSelector:@selector(compare:)];
// Create list of names of the signed headers:
NSMutableString *namesOfSignedHeaders = [NSMutableString string];
NSString *separator = @"";
for (NSString *name in theSortedLowercaseHeaderNames) {
[namesOfSignedHeaders appendString:separator];
separator = @";";
[namesOfSignedHeaders appendString:name];
}
// Add list of signed headers (header names):
[canonicalRequest appendString:namesOfSignedHeaders];
[canonicalRequest appendString:@"\n"];
// Add hash of payload:
NSData *payload = requestBody;
if (payload == nil) {
payload = [NSData data];
}
[canonicalRequest appendString:[NSString hexStringWithData:[SHA256Hash hashData:payload]]];
NSString *dateStamp = [ISO8601Date basicDateStringFromDate:[conn date]];
NSString *dateTime = [ISO8601Date basicDateTimeStringFromDate:[conn date]];
NSString *scope = [NSString stringWithFormat:@"%@/%@/glacier/aws4_request", dateStamp, [awsRegion regionName]];
NSString *signingCredentials = [NSString stringWithFormat:@"%@/%@", accessKey, scope];
HSLogTrace(@"canonical string = %@", canonicalRequest);
NSData *canonicalRequestData = [canonicalRequest dataUsingEncoding:NSUTF8StringEncoding];
NSString *canonicalRequestHashHex = [NSString hexStringWithData:[SHA256Hash hashData:canonicalRequestData]];
NSString *stringToSign = [NSString stringWithFormat:@"AWS4-HMAC-SHA256\n%@\n%@\n%@", dateTime, scope, canonicalRequestHashHex];
HSLogTrace(@"stringToSign = %@", stringToSign);
//FIXME: Extract the service name from the hostname (see AwsHostNameUtils.parseServiceName() method in Java AWS SDK).
NSString *signature = [signer signString:stringToSign withDateStamp:dateStamp regionName:[awsRegion regionName] serviceName:@"glacier"];
NSString *ret = [[[NSString alloc] initWithFormat:@"AWS4-HMAC-SHA256 Credential=%@, SignedHeaders=%@, Signature=%@", signingCredentials, namesOfSignedHeaders, signature] autorelease];
return ret;
}
@end

View file

@ -0,0 +1,19 @@
//
// GlacierAuthorizationProvider.h
//
// Created by Stefan Reitshamer on 9/7/12.
//
//
@protocol GlacierSigner;
@protocol HTTPConnection;
@class AWSRegion;
@interface GlacierAuthorizationProvider : NSObject {
NSString *accessKey;
id <GlacierSigner> signer;
}
- (id)initWithAccessKey:(NSString *)access secretKey:(NSString *)secret;
- (NSString *)authorizationForAWSRegion:(AWSRegion *)theAWSRegion connection:(id <HTTPConnection>)theConn requestBody:(NSData *)theRequestBody;
@end

View file

@ -0,0 +1,34 @@
//
// GlacierAuthorizationProvider.m
//
// Created by Stefan Reitshamer on 9/7/12.
//
//
#import "GlacierAuthorizationProvider.h"
#import "LocalGlacierSigner.h"
#import "HTTPConnection.h"
#import "SHA256Hash.h"
#import "ISO8601Date.h"
#import "GlacierAuthorization.h"
@implementation GlacierAuthorizationProvider
- (id)initWithAccessKey:(NSString *)access secretKey:(NSString *)secret {
if (self = [super init]) {
accessKey = [access retain];
signer = [[LocalGlacierSigner alloc] initWithSecretKey:secret];
}
return self;
}
- (void)dealloc {
[accessKey release];
[signer release];
[super dealloc];
}
- (NSString *)authorizationForAWSRegion:(AWSRegion *)theAWSRegion connection:(id <HTTPConnection>)theConn requestBody:(NSData *)theRequestBody {
GlacierAuthorization *authorization = [[[GlacierAuthorization alloc] initWithAWSRegion:theAWSRegion connection:theConn requestBody:theRequestBody accessKey:accessKey signer:signer] autorelease];
return [authorization description];
}
@end

View file

@ -0,0 +1,24 @@
//
// GlacierJob.h
//
// Created by Stefan Reitshamer on 9/17/12.
//
//
@class AWSRegion;
@interface GlacierJob : NSObject {
AWSRegion *awsRegion;
NSString *vaultName;
NSDictionary *json;
}
- (id)initWithAWSRegion:(AWSRegion *)theAWSRegion vaultName:(NSString *)theVaultName json:(NSDictionary *)theJSON;
- (AWSRegion *)awsRegion;
- (NSString *)vaultName;
- (NSString *)jobId;
- (NSString *)action;
- (NSString *)archiveId;
- (NSString *)snsTopicArn;
- (NSDictionary *)json;
@end

View file

@ -0,0 +1,47 @@
//
// GlacierJob.m
//
// Created by Stefan Reitshamer on 9/17/12.
//
//
#import "GlacierJob.h"
@implementation GlacierJob
- (id)initWithAWSRegion:(AWSRegion *)theAWSRegion vaultName:(NSString *)theVaultName json:(NSDictionary *)theJSON {
if (self = [super init]) {
awsRegion = [theAWSRegion retain];
vaultName = [theVaultName retain];
json = [theJSON retain];
}
return self;
}
- (void)dealloc {
[awsRegion release];
[vaultName release];
[json release];
[super dealloc];
}
- (AWSRegion *)awsRegion {
return awsRegion;
}
- (NSString *)vaultName {
return vaultName;
}
- (NSString *)jobId {
return [json objectForKey:@"JobId"];
}
- (NSString *)action {
return [json objectForKey:@"Action"];
}
- (NSString *)archiveId {
return [json objectForKey:@"ArchiveId"];
}
- (NSString *)snsTopicArn {
return [json objectForKey:@"SNSTopic"];
}
- (NSDictionary *)json {
return json;
}
@end

Some files were not shown because too many files have changed in this diff Show more