mirror of
https://github.com/samsonjs/arq_restore.git
synced 2026-03-25 09:25:53 +00:00
Replaced Arq 2 code with Arq 4 code.
This commit is contained in:
parent
20bf814d17
commit
8b416e0e22
437 changed files with 43188 additions and 11390 deletions
|
|
@ -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
|
||||
271
AppKeychain.m
271
AppKeychain.m
|
|
@ -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
|
||||
26
ArqFark.h
26
ArqFark.h
|
|
@ -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
|
||||
74
ArqFark.m
74
ArqFark.m
|
|
@ -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
|
||||
28
ArqPackSet.h
28
ArqPackSet.h
|
|
@ -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
|
||||
212
ArqPackSet.m
212
ArqPackSet.m
|
|
@ -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
|
||||
43
ArqRepo.h
43
ArqRepo.h
|
|
@ -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
243
ArqRepo.m
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
29
ArqSalt.h
29
ArqSalt.h
|
|
@ -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
|
||||
|
|
|
|||
90
ArqSalt.m
90
ArqSalt.m
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
59
BackupSet.h
59
BackupSet.h
|
|
@ -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
|
||||
|
|
|
|||
128
BackupSet.m
128
BackupSet.m
|
|
@ -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
48
BaseTargetConnection.h
Normal 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
142
BaseTargetConnection.m
Normal 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
|
||||
10
BlobKey.h
10
BlobKey.h
|
|
@ -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
103
BlobKey.m
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
22
BlobKeyIO.m
22
BlobKeyIO.m
|
|
@ -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
90
Bucket.h
Normal 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
482
Bucket.m
Normal 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
31
BucketExclude.h
Normal 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
87
BucketExclude.m
Normal 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
25
BucketExcludeSet.h
Normal 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
173
BucketExcludeSet.m
Normal 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
|
||||
39
DiskPack.h
39
DiskPack.h
|
|
@ -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
|
||||
262
DiskPack.m
262
DiskPack.m
|
|
@ -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
|
||||
|
|
@ -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
|
||||
358
DiskPackIndex.m
358
DiskPackIndex.m
|
|
@ -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
|
||||
17
FarkPath.h
17
FarkPath.h
|
|
@ -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
|
||||
19
FarkPath.m
19
FarkPath.m
|
|
@ -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
|
||||
|
|
@ -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
|
||||
541
FileAttributes.m
541
FileAttributes.m
|
|
@ -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
|
||||
22
GoogleDriveTargetConnection.h
Normal file
22
GoogleDriveTargetConnection.h
Normal 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
|
||||
102
GoogleDriveTargetConnection.m
Normal file
102
GoogleDriveTargetConnection.m
Normal 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
13
NSString_slashed.h
Normal 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
18
NSString_slashed.m
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
60
Restorer.h
60
Restorer.h
|
|
@ -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
|
||||
700
Restorer.m
700
Restorer.m
|
|
@ -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:¤tAclString 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
21
S3TargetConnection.h
Normal 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
101
S3TargetConnection.m
Normal 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
21
SFTPTargetConnection.h
Normal 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
130
SFTPTargetConnection.m
Normal 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
50
Target.h
Normal 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
169
Target.m
Normal 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
43
TargetConnection.h
Normal 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
53
TargetSchedule.h
Normal 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
122
TargetSchedule.m
Normal 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
|
||||
|
|
@ -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.
|
||||
//
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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]];
|
||||
|
|
|
|||
24
XAttrSet.h
24
XAttrSet.h
|
|
@ -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
|
||||
214
XAttrSet.m
214
XAttrSet.m
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
|||
110
arq_verify.m
110
arq_verify.m
|
|
@ -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;
|
||||
}
|
||||
15
cocoastack/aws/AWSQueryError.h
Normal file
15
cocoastack/aws/AWSQueryError.h
Normal 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
|
||||
77
cocoastack/aws/AWSQueryError.m
Normal file
77
cocoastack/aws/AWSQueryError.m
Normal 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
|
||||
18
cocoastack/aws/AWSQueryRequest.h
Normal file
18
cocoastack/aws/AWSQueryRequest.h
Normal 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
|
||||
133
cocoastack/aws/AWSQueryRequest.m
Normal file
133
cocoastack/aws/AWSQueryRequest.m
Normal 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
|
||||
17
cocoastack/aws/AWSQueryResponse.h
Normal file
17
cocoastack/aws/AWSQueryResponse.h
Normal 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
|
||||
34
cocoastack/aws/AWSQueryResponse.m
Normal file
34
cocoastack/aws/AWSQueryResponse.m
Normal 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
|
||||
56
cocoastack/aws/AWSRegion.h
Normal file
56
cocoastack/aws/AWSRegion.h
Normal 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
340
cocoastack/aws/AWSRegion.m
Normal 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
|
||||
15
cocoastack/aws/SignatureV2Provider.h
Normal file
15
cocoastack/aws/SignatureV2Provider.h
Normal 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
|
||||
44
cocoastack/aws/SignatureV2Provider.m
Normal file
44
cocoastack/aws/SignatureV2Provider.m
Normal 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
|
||||
28
cocoastack/crypto/CryptoKey.h
Normal file
28
cocoastack/crypto/CryptoKey.h
Normal 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
|
||||
78
cocoastack/crypto/CryptoKey.m
Normal file
78
cocoastack/crypto/CryptoKey.m
Normal 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
|
||||
12
cocoastack/crypto/MD5Hash.h
Normal file
12
cocoastack/crypto/MD5Hash.h
Normal 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
|
||||
25
cocoastack/crypto/MD5Hash.m
Normal file
25
cocoastack/crypto/MD5Hash.m
Normal 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
|
||||
|
|
@ -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
|
||||
93
cocoastack/crypto/NSData-Base64Extensions.m
Normal file
93
cocoastack/crypto/NSData-Base64Extensions.m
Normal 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
|
||||
18
cocoastack/crypto/OpenSSL.h
Normal file
18
cocoastack/crypto/OpenSSL.h
Normal 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
|
||||
66
cocoastack/crypto/OpenSSL.m
Normal file
66
cocoastack/crypto/OpenSSL.m
Normal 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 */
|
||||
|
|
@ -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
|
||||
184
cocoastack/crypto/OpenSSLCryptoKey.m
Normal file
184
cocoastack/crypto/OpenSSLCryptoKey.m
Normal 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
|
||||
|
|
@ -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
|
||||
246
cocoastack/crypto/SHA1Hash.m
Normal file
246
cocoastack/crypto/SHA1Hash.m
Normal 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
|
||||
15
cocoastack/crypto/SHA256Hash.h
Normal file
15
cocoastack/crypto/SHA256Hash.h
Normal 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
|
||||
26
cocoastack/crypto/SHA256Hash.m
Normal file
26
cocoastack/crypto/SHA256Hash.m
Normal 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
|
||||
21
cocoastack/glacier/GlacierAuthorization.h
Normal file
21
cocoastack/glacier/GlacierAuthorization.h
Normal 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
|
||||
120
cocoastack/glacier/GlacierAuthorization.m
Normal file
120
cocoastack/glacier/GlacierAuthorization.m
Normal 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
|
||||
19
cocoastack/glacier/GlacierAuthorizationProvider.h
Normal file
19
cocoastack/glacier/GlacierAuthorizationProvider.h
Normal 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
|
||||
34
cocoastack/glacier/GlacierAuthorizationProvider.m
Normal file
34
cocoastack/glacier/GlacierAuthorizationProvider.m
Normal 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
|
||||
24
cocoastack/glacier/GlacierJob.h
Normal file
24
cocoastack/glacier/GlacierJob.h
Normal 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
|
||||
47
cocoastack/glacier/GlacierJob.m
Normal file
47
cocoastack/glacier/GlacierJob.m
Normal 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
Loading…
Reference in a new issue