Implemented restore from S3.

This commit is contained in:
Stefan Reitshamer 2014-07-28 15:24:30 -04:00
parent 8b416e0e22
commit 85a5b9976b
23 changed files with 2415 additions and 7 deletions

View file

@ -6,10 +6,11 @@
//
//
#import "S3RestorerDelegate.h"
@class Target;
@interface ArqRestoreCommand : NSObject {
@interface ArqRestoreCommand : NSObject <S3RestorerDelegate> {
Target *target;
}

View file

@ -14,6 +14,11 @@
#import "UserAndComputer.h"
#import "Bucket.h"
#import "Repo.h"
#import "S3RestorerParamSet.h"
#import "Tree.h"
#import "Commit.h"
#import "BlobKey.h"
#import "S3Restorer.h"
@implementation ArqRestoreCommand
@ -54,17 +59,17 @@
// Valid command, but no additional args.
} else if ([cmd isEqualToString:@"listfolders"]) {
if ([args count] < 4) {
if ((argc - targetParamsIndex) < 2) {
SETNSERROR([self errorDomain], ERROR_USAGE, @"missing arguments for listfolders command");
return NO;
}
targetParamsIndex = 4;
targetParamsIndex += 2;
} else if ([cmd isEqualToString:@"restore"]) {
if ([args count] < 5) {
if ((argc - targetParamsIndex) < 3) {
SETNSERROR([self errorDomain], ERROR_USAGE, @"missing arguments");
return NO;
}
targetParamsIndex = 5;
targetParamsIndex += 3;
} else {
SETNSERROR([self errorDomain], ERROR_USAGE, @"unknown command: %@", cmd);
return NO;
@ -84,11 +89,11 @@
return NO;
}
} else if ([cmd isEqualToString:@"listfolders"]) {
if (![self listBucketsForComputerUUID:[args objectAtIndex:2] encryptionPassword:[args objectAtIndex:3] error:error]) {
if (![self listBucketsForComputerUUID:[args objectAtIndex:index+1] encryptionPassword:[args objectAtIndex:index+2] 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]) {
if (![self restoreComputerUUID:[args objectAtIndex:index+1] bucketUUID:[args objectAtIndex:index+3] encryptionPassword:[args objectAtIndex:index+2] error:error]) {
return NO;
}
} else {
@ -339,12 +344,89 @@
if (commitBlobKey == nil) {
return NO;
}
Commit *commit = [repo commitForBlobKey:commitBlobKey dataSize:NULL error:error];
if (commit == nil) {
return NO;
}
NSString *destinationPath = [[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingPathComponent:[[myBucket localPath] lastPathComponent]];
if ([[NSFileManager defaultManager] fileExistsAtPath:destinationPath]) {
SETNSERROR([self errorDomain], -1, @"%@ already exists", destinationPath);
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]);
AWSRegion *region = [AWSRegion regionWithS3Endpoint:[target endpoint]];
BOOL isGlacierDestination = [region supportsGlacier];
if ([myBucket storageType] == StorageTypeGlacier && isGlacierDestination) {
// [[[GlacierRestoreController alloc] initWithAppConfig:appConfig
// doChownsAbove499:NO
// destinationPath:destination
// displayBucket:sourceOutlineController.selectedDisplayBucket
// displayCommit:sourceOutlineController.selectedDisplayCommit
// displayNode:selectedNode
// mainWindow:mainWindow] autorelease];
} else if ([myBucket storageType] == StorageTypeS3Glacier && isGlacierDestination) {
// [[[S3GlacierRestoreSetupController alloc] initWithLocalComputerUUID:[appConfig computerUUIDForTargetUUID:[target targetUUID]]
// doChownsAbove499:NO
// destinationPath:destination
// displayBucket:sourceOutlineController.selectedDisplayBucket
// displayCommit:sourceOutlineController.selectedDisplayCommit
// displayNode:selectedNode
// mainWindow:mainWindow] autorelease];
} else {
// [[[S3RestoreController alloc] initWithAppConfig:appConfig
// doChownsAbove499:doChowns
// destinationPath:destination
// displayBucket:sourceOutlineController.selectedDisplayBucket
// displayCommit:sourceOutlineController.selectedDisplayCommit
// displayNode:selectedNode] autorelease];
S3RestorerParamSet *paramSet = [[[S3RestorerParamSet alloc] initWithBucket:myBucket
encryptionPassword:theEncryptionPassword
commitBlobKey:commitBlobKey
rootItemName:[[myBucket localPath] lastPathComponent]
treeVersion:CURRENT_TREE_VERSION
treeIsCompressed:[[commit treeBlobKey] compressed]
treeBlobKey:[commit treeBlobKey]
nodeName:nil
targetUID:getuid()
targetGID:getgid()
useTargetUIDAndGID:YES
destinationPath:destinationPath
logLevel:global_hslog_level] autorelease];
[[[S3Restorer alloc] initWithParamSet:paramSet delegate:self] autorelease];
}
return YES;
}
#pragma mark S3RestorerDelegate
// Methods return YES if cancel is requested.
- (BOOL)s3RestorerMessageDidChange:(NSString *)message {
printf("status: %s\n", [message UTF8String]);
return NO;
}
- (BOOL)s3RestorerBytesTransferredDidChange:(NSNumber *)theTransferred {
return NO;
}
- (BOOL)s3RestorerTotalBytesToTransferDidChange:(NSNumber *)theTotal {
return NO;
}
- (BOOL)s3RestorerErrorMessage:(NSString *)theErrorMessage didOccurForPath:(NSString *)thePath {
printf("%s error: %s\n", [thePath UTF8String], [theErrorMessage UTF8String]);
return NO;
}
- (BOOL)s3RestorerDidSucceed {
return NO;
}
- (BOOL)s3RestorerDidFail:(NSError *)error {
printf("failed: %s\n", [[error localizedDescription] UTF8String]);
return NO;
}
@end

View file

@ -181,6 +181,14 @@
F8F2D96B1986BF5100997A15 /* GunzipInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D96A1986BF5100997A15 /* GunzipInputStream.m */; };
F8F2D96E1986BF6800997A15 /* PackSet.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D96D1986BF6800997A15 /* PackSet.m */; };
F8F2D9701986C09300997A15 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8F2D96F1986C09300997A15 /* IOKit.framework */; };
F8F2D9741986D02D00997A15 /* S3RestorerParamSet.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D9731986D02D00997A15 /* S3RestorerParamSet.m */; };
F8F2D9771986D32B00997A15 /* S3Restorer.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D9761986D32B00997A15 /* S3Restorer.m */; };
F8F2D97C1986D38900997A15 /* OSStatusDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D97B1986D38900997A15 /* OSStatusDescription.m */; };
F8F2D97F1986D3A100997A15 /* FileAttributes.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D97E1986D3A100997A15 /* FileAttributes.m */; };
F8F2D9821986D3B100997A15 /* FileACL.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D9811986D3B100997A15 /* FileACL.m */; };
F8F2D9851986D3C400997A15 /* XAttrSet.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D9841986D3C400997A15 /* XAttrSet.m */; };
F8F2D9921986D4C700997A15 /* CalculateItem.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D98E1986D4C700997A15 /* CalculateItem.m */; };
F8F2D9931986D4C700997A15 /* RestoreItem.m in Sources */ = {isa = PBXBuildFile; fileRef = F8F2D9901986D4C700997A15 /* RestoreItem.m */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
@ -553,6 +561,24 @@
F8F2D96C1986BF6800997A15 /* PackSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PackSet.h; sourceTree = "<group>"; };
F8F2D96D1986BF6800997A15 /* PackSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PackSet.m; sourceTree = "<group>"; };
F8F2D96F1986C09300997A15 /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; };
F8F2D9721986D02D00997A15 /* S3RestorerParamSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3RestorerParamSet.h; sourceTree = "<group>"; };
F8F2D9731986D02D00997A15 /* S3RestorerParamSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = S3RestorerParamSet.m; sourceTree = "<group>"; };
F8F2D9751986D32B00997A15 /* S3Restorer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = S3Restorer.h; sourceTree = "<group>"; };
F8F2D9761986D32B00997A15 /* S3Restorer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = S3Restorer.m; sourceTree = "<group>"; };
F8F2D9791986D36D00997A15 /* S3RestorerDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = S3RestorerDelegate.h; sourceTree = "<group>"; };
F8F2D97A1986D38900997A15 /* OSStatusDescription.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OSStatusDescription.h; sourceTree = "<group>"; };
F8F2D97B1986D38900997A15 /* OSStatusDescription.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OSStatusDescription.m; sourceTree = "<group>"; };
F8F2D97D1986D3A100997A15 /* FileAttributes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FileAttributes.h; sourceTree = "<group>"; };
F8F2D97E1986D3A100997A15 /* FileAttributes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FileAttributes.m; sourceTree = "<group>"; };
F8F2D9801986D3B100997A15 /* FileACL.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FileACL.h; sourceTree = "<group>"; };
F8F2D9811986D3B100997A15 /* FileACL.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FileACL.m; sourceTree = "<group>"; };
F8F2D9831986D3C400997A15 /* XAttrSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XAttrSet.h; sourceTree = "<group>"; };
F8F2D9841986D3C400997A15 /* XAttrSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XAttrSet.m; sourceTree = "<group>"; };
F8F2D98D1986D4C700997A15 /* CalculateItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CalculateItem.h; sourceTree = "<group>"; };
F8F2D98E1986D4C700997A15 /* CalculateItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CalculateItem.m; sourceTree = "<group>"; };
F8F2D98F1986D4C700997A15 /* RestoreItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RestoreItem.h; sourceTree = "<group>"; };
F8F2D9901986D4C700997A15 /* RestoreItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RestoreItem.m; sourceTree = "<group>"; };
F8F2D9911986D4C700997A15 /* Restorer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Restorer.h; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -584,6 +610,8 @@
F829512019868345001DC91B /* cocoastack */,
F8F2D92B1986BA0700997A15 /* repo */,
08FB7795FE84155DC02AAC07 /* arq_restore */,
F8F2D98C1986D49600997A15 /* commonrestore */,
F8F2D9711986D00C00997A15 /* s3restore */,
08FB779DFE84155DC02AAC07 /* External Frameworks and Libraries */,
1AB674ADFE9D54B511CA2CBB /* Products */,
F89A204513FAE29E0071D321 /* libz.dylib */,
@ -690,6 +718,8 @@
F829523919868E94001DC91B /* CWLSynthesizeSingleton.h */,
F8F2D8E11986B75C00997A15 /* Computer.h */,
F8F2D8E21986B75C00997A15 /* Computer.m */,
F8F2D9801986D3B100997A15 /* FileACL.h */,
F8F2D9811986D3B100997A15 /* FileACL.m */,
F8F2D9631986BE7600997A15 /* NSData-GZip.h */,
F8F2D9641986BE7600997A15 /* NSData-GZip.m */,
F829512719868394001DC91B /* NSError_extra.h */,
@ -703,6 +733,8 @@
F829512319868345001DC91B /* HSLog.m */,
F829523E19868EC7001DC91B /* NSString_extra.h */,
F829523F19868EC7001DC91B /* NSString_extra.m */,
F8F2D97A1986D38900997A15 /* OSStatusDescription.h */,
F8F2D97B1986D38900997A15 /* OSStatusDescription.m */,
F829DBFA19868FCA00D637E0 /* RFC822.h */,
F829DBFB19868FCA00D637E0 /* RFC822.m */,
F829521419868DEA001DC91B /* RegexKitLite.h */,
@ -1114,6 +1146,8 @@
F8F2D9501986BE2A00997A15 /* Fark.h */,
F8F2D9511986BE2A00997A15 /* FarkImpl.h */,
F8F2D9521986BE2A00997A15 /* FarkImpl.m */,
F8F2D97D1986D3A100997A15 /* FileAttributes.h */,
F8F2D97E1986D3A100997A15 /* FileAttributes.m */,
F8F2D95D1986BE6100997A15 /* Node.h */,
F8F2D95E1986BE6100997A15 /* Node.m */,
F8F2D9541986BE3C00997A15 /* PackId.h */,
@ -1128,10 +1162,36 @@
F8F2D9481986BAD200997A15 /* Repo.m */,
F8F2D95F1986BE6100997A15 /* Tree.h */,
F8F2D9601986BE6100997A15 /* Tree.m */,
F8F2D9831986D3C400997A15 /* XAttrSet.h */,
F8F2D9841986D3C400997A15 /* XAttrSet.m */,
);
path = repo;
sourceTree = "<group>";
};
F8F2D9711986D00C00997A15 /* s3restore */ = {
isa = PBXGroup;
children = (
F8F2D9751986D32B00997A15 /* S3Restorer.h */,
F8F2D9761986D32B00997A15 /* S3Restorer.m */,
F8F2D9791986D36D00997A15 /* S3RestorerDelegate.h */,
F8F2D9721986D02D00997A15 /* S3RestorerParamSet.h */,
F8F2D9731986D02D00997A15 /* S3RestorerParamSet.m */,
);
path = s3restore;
sourceTree = "<group>";
};
F8F2D98C1986D49600997A15 /* commonrestore */ = {
isa = PBXGroup;
children = (
F8F2D98D1986D4C700997A15 /* CalculateItem.h */,
F8F2D98E1986D4C700997A15 /* CalculateItem.m */,
F8F2D98F1986D4C700997A15 /* RestoreItem.h */,
F8F2D9901986D4C700997A15 /* RestoreItem.m */,
F8F2D9911986D4C700997A15 /* Restorer.h */,
);
path = commonrestore;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -1188,6 +1248,7 @@
F8F2D8711986B5D900997A15 /* GoogleDriveTargetConnection.m in Sources */,
F8F2D9681986BEA300997A15 /* BlobKeyIO.m in Sources */,
F8F2D8821986B63400997A15 /* BaseTargetConnection.m in Sources */,
F8F2D9931986D4C700997A15 /* RestoreItem.m in Sources */,
F8F2D93D1986BA7900997A15 /* UserLibrary.m in Sources */,
F8F2D86E1986B5CC00997A15 /* SFTPTargetConnection.m in Sources */,
F829522019868E26001DC91B /* GoogleDrive.m in Sources */,
@ -1248,15 +1309,18 @@
F829524319868ED6001DC91B /* NSData-InputStream.m in Sources */,
F8F2D8E01986B74400997A15 /* UserAndComputer.m in Sources */,
F829DC12198691CB00D637E0 /* DateIO.m in Sources */,
F8F2D9771986D32B00997A15 /* S3Restorer.m in Sources */,
F8F2D8931986B66000997A15 /* GlacierJobLister.m in Sources */,
F8F2D9031986B78600997A15 /* RealNode.m in Sources */,
F8F2D87C1986B61800997A15 /* GoogleDriveRemoteFS.m in Sources */,
F829521319868DE6001DC91B /* RegexKitLite.m in Sources */,
F8F2D8FD1986B78600997A15 /* ArrayNode.m in Sources */,
F8F2D9741986D02D00997A15 /* S3RestorerParamSet.m in Sources */,
F82951ED19868D90001DC91B /* HTTPConnectionFactory.m in Sources */,
F8F2D9251986B98600997A15 /* BlobKey.m in Sources */,
F829523319868E59001DC91B /* SBJsonParser.m in Sources */,
F8F2D8A51986B67500997A15 /* Vault.m in Sources */,
F8F2D97F1986D3A100997A15 /* FileAttributes.m in Sources */,
F829DC15198691CB00D637E0 /* NSErrorIO.m in Sources */,
F82951EE19868D90001DC91B /* HTTPInputStream.m in Sources */,
F829523419868E59001DC91B /* SBJsonWriter.m in Sources */,
@ -1275,6 +1339,7 @@
F8F2D9061986B78600997A15 /* XMLPListReader.m in Sources */,
F8295164198683D5001DC91B /* packet.c in Sources */,
F829516B198683D5001DC91B /* userauth.c in Sources */,
F8F2D9921986D4C700997A15 /* CalculateItem.m in Sources */,
F829515E198683D5001DC91B /* kex.c in Sources */,
F8F2D8A61986B67500997A15 /* VaultDeleter.m in Sources */,
F8F2D8BB1986B6E000997A15 /* SubscribeResponse.m in Sources */,
@ -1322,6 +1387,7 @@
F8F2D87B1986B61800997A15 /* SFTPRemoteFS.m in Sources */,
F829523C19868EA4001DC91B /* NetMonitor.m in Sources */,
F8295191198683F9001DC91B /* RemoteS3Signer.m in Sources */,
F8F2D9851986D3C400997A15 /* XAttrSet.m in Sources */,
F8F2D9051986B78600997A15 /* XMLPlistParser.m in Sources */,
F8F2D9441986BA9B00997A15 /* Bucket.m in Sources */,
F829DC2E1986927000D637E0 /* FileOutputStream.m in Sources */,
@ -1330,11 +1396,13 @@
F82951EC19868D90001DC91B /* NSData-Base64Extensions.m in Sources */,
F8295193198683F9001DC91B /* S3DeleteReceiver.m in Sources */,
F8F2D9311986BA2200997A15 /* CommitFailedFile.m in Sources */,
F8F2D97C1986D38900997A15 /* OSStatusDescription.m in Sources */,
F8F2D8851986B64D00997A15 /* GlacierAuthorizationProvider.m in Sources */,
F83C1AE311CA7C7C0001958F /* arq_restore.m in Sources */,
F8295190198683F9001DC91B /* PathReceiver.m in Sources */,
F829DC281986924E00D637E0 /* DataOutputStream.m in Sources */,
F8295196198683F9001DC91B /* S3MultiDeleteResponse.m in Sources */,
F8F2D9821986D3B100997A15 /* FileACL.m in Sources */,
F8F2D9021986B78600997A15 /* IntegerNode.m in Sources */,
F8F2D8921986B66000997A15 /* GlacierJob.m in Sources */,
F8F2D8AD1986B6A300997A15 /* SHA256Hash.m in Sources */,

View file

@ -0,0 +1,40 @@
/*
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.
*/
@interface FileACL : NSObject {
}
+ (BOOL)aclText:(NSString **)aclText forFile:(NSString *)path error:(NSError **)error;
+ (BOOL)writeACLText:(NSString *)aclText toFile:(NSString *)path error:(NSError **)error;
@end

101
cocoastack/shared/FileACL.m Normal file
View file

@ -0,0 +1,101 @@
/*
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.
*/
#include <sys/stat.h>
#include <stdio.h>
#import "FileACL.h"
#import "NSError_extra.h"
@implementation FileACL
+ (BOOL)aclText:(NSString **)aclText forFile:(NSString *)path error:(NSError **)error {
*aclText = nil;
const char *pathChars = [path fileSystemRepresentation];
acl_t acl = acl_get_link_np(pathChars, ACL_TYPE_EXTENDED);
if (!acl) {
if (errno != ENOENT) {
int errnum = errno;
HSLogError(@"acl_get_link_np(%@) error %d: %s", path, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"failed to get ACL of %@: %s", path, strerror(errnum));
return NO;
}
} else {
char *aclTextChars = acl_to_text(acl, NULL);
if (!aclTextChars) {
acl_free(acl);
int errnum = errno;
HSLogError(@"acl_to_text from %@ error %d: %s", path, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"failed to convert ACL of %@ to text: %s", path, strerror(errnum));
return NO;
}
*aclText = [NSString stringWithUTF8String:aclTextChars];
acl_free(aclTextChars);
acl_free(acl);
}
return YES;
}
+ (BOOL)writeACLText:(NSString *)aclText toFile:(NSString *)path error:(NSError **)error {
HSLogTrace(@"applying ACL %@ to %@", aclText, path);
const char *pathChars = [path fileSystemRepresentation];
acl_t acl = acl_from_text([aclText UTF8String]);
if (!acl) {
int errnum = errno;
HSLogError(@"acl_from_text(%@) error %d: %s", aclText, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"failed to convert ACL text '%@' to ACL: %s", aclText, strerror(errnum));
return NO;
}
BOOL ret = NO;
struct stat st;
if (lstat(pathChars, &st) == -1) {
int errnum = errno;
HSLogError(@"lstat(%@) error %d: %s", path, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"%@: %s", path, strerror(errnum));
goto writeACLText_error;
}
if (S_ISLNK(st.st_mode)) {
ret = acl_set_link_np(pathChars, ACL_TYPE_EXTENDED, acl) != -1;
} else {
ret = acl_set_file(pathChars, ACL_TYPE_EXTENDED, acl) != -1;
}
if (!ret) {
int errnum = errno;
HSLogError(@"acl_set(%@) error %d: %s", path, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"failed to set ACL '%@' on %@: %s", aclText, path, strerror(errnum));
goto writeACLText_error;
}
ret = YES;
writeACLText_error:
acl_free(acl);
return ret;
}
@end

View file

@ -0,0 +1,40 @@
/*
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.
*/
@interface OSStatusDescription : NSObject {
}
+ (NSString *)descriptionForOSStatus:(OSStatus)status;
@end

View file

@ -0,0 +1,82 @@
/*
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 "OSStatusDescription.h"
@implementation OSStatusDescription
+ (NSString *)descriptionForOSStatus:(OSStatus)status {
NSString *msg = [(NSString *)SecCopyErrorMessageString(status, NULL) autorelease];
if (msg == nil) {
switch (status) {
case ioErr:
return @"I/O error"; // GetMacOSStatusCommentString() returns "I/O error (bummers)", which isn't appropriate!
case nsvErr:
return @"No such volume";
case bdNamErr:
return @"Bad file name";
case fnfErr:
return @"File not found";
case errAuthorizationSuccess:
return @"The operation completed successfully.";
case errAuthorizationInvalidSet:
return @"The set parameter is invalid.";
case errAuthorizationInvalidRef:
return @"The authorization parameter is invalid.";
case errAuthorizationInvalidPointer:
return @"The authorizedRights parameter is invalid.";
case errAuthorizationDenied:
return @"The Security Server denied authorization for one or more requested rights. This error is also returned if there was no definition found in the policy database, or a definition could not be created.";
case errAuthorizationCanceled:
return @"The user canceled the operation";
case errAuthorizationInteractionNotAllowed:
return @"The Security Server denied authorization because no user interaction is allowed.";
case errAuthorizationInternal:
return @"An unrecognized internal error occurred.";
case errAuthorizationExternalizeNotAllowed:
return @"The Security Server denied externalization of the authorization reference.";
case errAuthorizationInternalizeNotAllowed:
return @"The Security Server denied internalization of the authorization reference.";
case errAuthorizationInvalidFlags:
return @"The flags parameter is invalid.";
case errAuthorizationToolExecuteFailure:
return @"The tool failed to execute.";
case errAuthorizationToolEnvironmentError:
return @"The attempt to execute the tool failed to return a success or an error code.";
}
}
if ([msg length] == 0) {
msg = [NSString stringWithFormat:@"error %ld", (long)status];
}
return msg;
}
@end

View file

@ -0,0 +1,31 @@
//
// CalculateItem.h
// Arq
//
// Created by Stefan Reitshamer on 6/10/13.
// Copyright (c) 2013 Stefan Reitshamer. All rights reserved.
//
#include <sys/stat.h>
@class Tree;
@class Node;
@class Repo;
@protocol Restorer;
@interface CalculateItem : NSObject {
NSString *path;
Tree *tree;
Node *node;
struct stat st;
unsigned long long bytesToTransfer;
NSMutableSet *filesToSkip;
NSMutableArray *nextItems;
}
- (id)initWithPath:(NSString *)thePath tree:(Tree *)theTree;
- (id)initWithPath:(NSString *)thePath node:(Node *)theNode;
- (BOOL)calculateWithRepo:(Repo *)theRepo restorer:(id <Restorer>)theRestorer error:(NSError **)error;
- (unsigned long long)bytesToTransfer;
- (NSSet *)filesToSkip;
- (NSArray *)nextItems;
@end

View file

@ -0,0 +1,115 @@
//
// CalculateItem.m
// Arq
//
// Created by Stefan Reitshamer on 6/10/13.
// Copyright (c) 2013 Stefan Reitshamer. All rights reserved.
//
#import "CalculateItem.h"
#import "Tree.h"
#import "Node.h"
#import "Repo.h"
#import "BlobKey.h"
#import "FileInputStream.h"
#import "BufferedInputStream.h"
#import "NSData-GZip.h"
#import "SHA1Hash.h"
#import "Restorer.h"
@implementation CalculateItem
- (id)initWithPath:(NSString *)thePath tree:(Tree *)theTree {
if (self = [super init]) {
path = [thePath retain];
tree = [theTree retain];
filesToSkip = [[NSMutableSet alloc] init];
nextItems = [[NSMutableArray alloc] init];
}
return self;
}
- (id)initWithPath:(NSString *)thePath node:(Node *)theNode {
if (self = [super init]) {
path = [thePath retain];
node = [theNode retain];
filesToSkip = [[NSMutableSet alloc] init];
nextItems = [[NSMutableArray alloc] init];
}
return self;
}
- (void)dealloc {
[path release];
[node release];
[filesToSkip release];
[nextItems release];
[super dealloc];
}
- (BOOL)calculateWithRepo:(Repo *)theRepo restorer:(id <Restorer>)theRestorer error:(NSError **)error {
if (tree != nil) {
if (![self addBlobKeyToBytesToTransfer:[tree xattrsBlobKey] restorer:theRestorer error:error]) {
return NO;
}
if (![self addBlobKeyToBytesToTransfer:[tree aclBlobKey] restorer:theRestorer error:error]) {
return NO;
}
for (NSString *childNodeName in [tree childNodeNames]) {
Node *childNode = [tree childNodeWithName:childNodeName];
NSString *childPath = [path stringByAppendingPathComponent:childNodeName];
[nextItems addObject:[[[CalculateItem alloc] initWithPath:childPath node:childNode] autorelease]];
}
} else {
NSAssert(node != nil, @"node can't be nil if tree is nil");
if ([node isTree]) {
Tree *childTree = [theRepo treeForBlobKey:[node treeBlobKey] error:error];
if (childTree == nil) {
return NO;
}
[nextItems addObject:[[[CalculateItem alloc] initWithPath:path tree:childTree] autorelease]];
} else {
if (![self addBlobKeyToBytesToTransfer:[node xattrsBlobKey] restorer:theRestorer error:error]) {
return NO;
}
if (![self addBlobKeyToBytesToTransfer:[node aclBlobKey] restorer:theRestorer error:error]) {
return NO;
}
for (BlobKey *dataBlobKey in [node dataBlobKeys]) {
if (![self addBlobKeyToBytesToTransfer:dataBlobKey restorer:theRestorer error:error]) {
return NO;
}
}
}
}
return YES;
}
- (unsigned long long)bytesToTransfer {
return bytesToTransfer;
}
- (NSSet *)filesToSkip {
return filesToSkip;
}
- (NSArray *)nextItems {
return nextItems;
}
#pragma mark internal
- (BOOL)addBlobKeyToBytesToTransfer:(BlobKey *)theBlobKey restorer:(id <Restorer>)theRestorer error:(NSError **)error {
if (theBlobKey == nil) {
return YES;
}
NSError *myError = nil;
NSNumber *size = [theRestorer sizeOfBlob:theBlobKey error:&myError];
if (size == nil) {
SETERRORFROMMYERROR;
if ([myError isErrorWithDomain:[theRestorer errorDomain] code:ERROR_NOT_FOUND]) {
HSLogError(@"%@", [myError localizedDescription]);
size = [NSNumber numberWithUnsignedLongLong:0];
} else {
return NO;
}
}
bytesToTransfer += [size unsignedLongLongValue];
return YES;
}
@end

View file

@ -0,0 +1,34 @@
//
// RestoreItem.h
// Arq
//
// Created by Stefan Reitshamer on 5/30/13.
// Copyright (c) 2013 Stefan Reitshamer. All rights reserved.
//
@class Tree;
@class Node;
@class Repo;
@protocol Restorer;
@class FileOutputStream;
@interface RestoreItem : NSObject {
Tree *tree;
Node *node;
NSString *path;
int restoreAction;
FileOutputStream *fileOutputStream;
NSUInteger dataBlobKeyIndex;
BOOL errorOccurred;
}
- (id)initWithPath:(NSString *)thePath tree:(Tree *)theTree;
- (id)initWithPath:(NSString *)thePath tree:(Tree *)theTree node:(Node *)theNode;
- (id)initWithPath:(NSString *)thePath tree:(Tree *)theTree node:(Node *)theNode fileOutputStream:(FileOutputStream *)theFileOutputStream dataBlobKeyIndex:(NSUInteger)theDataBlobKeyIndex;
- (NSString *)errorDomain;
- (NSString *)path;
- (BOOL)restoreWithHardlinks:(NSMutableDictionary *)theHardlinks restorer:(id <Restorer>)theRestorer error:(NSError **)error;
- (NSArray *)nextItemsWithRepo:(Repo *)theRepo error:(NSError **)error;
@end

644
commonrestore/RestoreItem.m Normal file
View file

@ -0,0 +1,644 @@
//
// RestoreItem.m
// Arq
//
// Created by Stefan Reitshamer on 5/30/13.
// Copyright (c) 2013 Stefan Reitshamer. All rights reserved.
//
#import "RestoreItem.h"
#import "Tree.h"
#import "Node.h"
#import "Repo.h"
#import "OSStatusDescription.h"
#import "FileAttributes.h"
#import "FileACL.h"
#import "DataInputStream.h"
#import "BufferedInputStream.h"
#import "BlobKey.h"
#import "NSData-GZip.h"
#import "XAttrSet.h"
#import "Restorer.h"
#import "NSFileManager_extra.h"
#import "FileOutputStream.h"
#import "BufferedOutputStream.h"
enum {
kRestoreActionRestoreTree=1,
kRestoreActionRestoreNode=2,
kRestoreActionApplyTree=3,
kRestoreActionRestoreFileData=4
} RestoreAction;
@implementation RestoreItem
- (id)initWithPath:(NSString *)thePath tree:(Tree *)theTree {
if (self = [super init]) {
path = [thePath retain];
tree = [theTree retain];
restoreAction = kRestoreActionRestoreTree;
}
return self;
}
- (id)initWithPath:(NSString *)thePath tree:(Tree *)theTree node:(Node *)theNode {
if (self = [super init]) {
path = [thePath retain];
tree = [theTree retain];
node = [theNode retain];
restoreAction = kRestoreActionRestoreNode;
}
return self;
}
- (id)initWithPath:(NSString *)thePath tree:(Tree *)theTree node:(Node *)theNode fileOutputStream:(FileOutputStream *)theFileOutputStream dataBlobKeyIndex:(NSUInteger)theDataBlobKeyIndex {
if (self = [super init]) {
path = [thePath retain];
tree = [theTree retain];
node = [theNode retain];
fileOutputStream = [theFileOutputStream retain];
dataBlobKeyIndex = theDataBlobKeyIndex;
restoreAction = kRestoreActionRestoreFileData;
}
return self;
}
- (void)dealloc {
[tree release];
[node release];
[path release];
[fileOutputStream release];
[super dealloc];
}
- (NSString *)errorDomain {
return @"RestoreItemErrorDomain";
}
- (NSString *)path {
return path;
}
- (BOOL)restoreWithHardlinks:(NSMutableDictionary *)theHardlinks restorer:(id <Restorer>)theRestorer error:(NSError **)error {
BOOL ret = NO;
switch (restoreAction) {
case kRestoreActionRestoreTree:
ret = [self restoreTreeWithHardlinks:theHardlinks restorer:theRestorer error:error];
break;
case kRestoreActionRestoreNode:
ret = [self restoreNodeWithHardlinks:theHardlinks restorer:theRestorer error:error];
break;
case kRestoreActionApplyTree:
ret = [self applyTreeWithHardlinks:theHardlinks restorer:theRestorer error:error];
break;
case kRestoreActionRestoreFileData:
ret = [self restoreFileDataWithRestorer:theRestorer error:error];
break;
default:
NSAssert(0==1, @"unknown restore action");
break;
}
return ret;
}
- (NSArray *)nextItemsWithRepo:(Repo *)theRepo error:(NSError **)error {
NSArray *ret = nil;
switch (restoreAction) {
case kRestoreActionRestoreTree:
ret = [self nextItemsForTreeWithRepo:theRepo error:error];
break;
case kRestoreActionRestoreFileData:
case kRestoreActionRestoreNode:
if (!errorOccurred && fileOutputStream != nil && [[node dataBlobKeys] count] > dataBlobKeyIndex) {
RestoreItem *nextItem = [[[RestoreItem alloc] initWithPath:path tree:tree node:node fileOutputStream:fileOutputStream dataBlobKeyIndex:dataBlobKeyIndex] autorelease];
ret = [NSArray arrayWithObject:nextItem];
} else {
ret = [NSArray array];
}
break;
case kRestoreActionApplyTree:
ret = [NSArray array];
break;
break;
default:
NSAssert(0==1, @"unknown restore action");
break;
}
return ret;
}
#pragma mark internal
- (id)initApplyItemWithTree:(Tree *)theTree path:(NSString *)thePath {
if (self = [super init]) {
tree = [theTree retain];
restoreAction = kRestoreActionApplyTree;
path = [thePath retain];
}
return self;
}
- (BOOL)restoreTreeWithHardlinks:(NSMutableDictionary *)theHardlinks restorer:(id <Restorer>)theRestorer error:(NSError **)error {
NSNumber *inode = [NSNumber numberWithInt:[tree st_ino]];
NSString *existing = nil;
if ([tree st_nlink] > 1) {
existing = [theHardlinks objectForKey:inode];
}
if (existing != nil) {
// Link.
if (link([existing fileSystemRepresentation], [path fileSystemRepresentation]) == -1) {
int errnum = errno;
SETNSERROR([self errorDomain], -1, @"link(%@,%@): %s", existing, path, strerror(errnum));
HSLogError(@"link() failed");
return NO;
}
} else {
BOOL isDir = NO;
if ([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir]) {
if (!isDir) {
if (![[NSFileManager defaultManager] removeItemAtPath:path error:error]) {
return NO;
}
}
} else {
if (![[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:error]) {
return NO;
}
}
[theHardlinks setObject:path forKey:inode];
}
if ([theRestorer useTargetUIDAndGID]) {
HSLogDebug(@"use restorer %@ target UID %d and GID %d", theRestorer, [theRestorer targetUID], [theRestorer targetGID]);
if (![FileAttributes applyUID:[theRestorer targetUID] gid:[theRestorer targetGID] toPath:path error:error]) {
return NO;
}
} else {
HSLogDebug(@"use tree %@ UID %d and GID %d", tree, [tree uid], [tree gid]);
if (![FileAttributes applyUID:[tree uid] gid:[tree gid] toPath:path error:error]) {
return NO;
}
}
return YES;
}
- (BOOL)restoreNodeWithHardlinks:(NSMutableDictionary *)theHardlinks restorer:(id <Restorer>)theRestorer error:(NSError **)error {
NSNumber *inode = [NSNumber numberWithInt:[node st_ino]];
NSString *existing = nil;
if ([node st_nlink] > 1) {
existing = [theHardlinks objectForKey:inode];
}
if (existing != nil) {
// Link.
if (link([existing fileSystemRepresentation], [path fileSystemRepresentation]) == -1) {
int errnum = errno;
SETNSERROR([self errorDomain], -1, @"link(%@,%@): %s", existing, path, strerror(errnum));
HSLogError(@"link() failed");
return NO;
}
} else {
struct stat st;
if (lstat([path fileSystemRepresentation], &st) == -1) {
int errnum = errno;
if (errnum != ENOENT) {
SETNSERROR(@"UnixErrorDomain", errnum, @"lstat(%@): %s", path, strerror(errnum));
return NO;
}
} else {
BOOL shouldSkip = [theRestorer shouldSkipFile:path];
if (!S_ISREG(st.st_mode) || !shouldSkip) {
HSLogDetail(@"removing %@ because it's in the way", path);
if (![[NSFileManager defaultManager] removeItemAtPath:path error:error]) {
HSLogError(@"error removing %@", path);
return NO;
}
}
}
int mode = [node mode];
if (S_ISFIFO(mode)) {
if (mkfifo([path fileSystemRepresentation], mode) == -1) {
int errnum = errno;
SETNSERROR([self errorDomain], errnum, @"mkfifo(%@): %s", path, strerror(errnum));
return NO;
}
if (![self applyNodeWithRestorer:theRestorer error:error]) {
HSLogError(@"applyNode error");
return NO;
}
} else if (S_ISSOCK(mode)) {
// Skip socket -- restoring it doesn't make any sense.
} else {
if (![self createFile:node restorer:theRestorer error:error]) {
return NO;
}
// We call this once here in case it's not a regular file. We also call it after the last blob in restoreFileDataWithRestorer so the metadata are set after the last file blob.
if (![self applyNodeWithRestorer:theRestorer error:error]) {
return NO;
}
}
[theHardlinks setObject:path forKey:inode];
}
return YES;
}
- (BOOL)applyTreeWithHardlinks:(NSMutableDictionary *)theHardlinks restorer:(id <Restorer>)theRestorer error:(NSError **)error {
// Make sure all items are available for download.
if ([tree xattrsBlobKey] != nil) {
NSNumber *available = [theRestorer isObjectAvailableForBlobKey:[tree xattrsBlobKey] error:error];
if (available == nil) {
return NO;
}
if (![available boolValue]) {
SETNSERROR([self errorDomain], ERROR_GLACIER_OBJECT_NOT_AVAILABLE, @"%@ not available", [tree xattrsBlobKey]);
return NO;
}
}
if ([tree aclBlobKey] != nil) {
NSNumber *available = [theRestorer isObjectAvailableForBlobKey:[tree aclBlobKey] error:error];
if (available == nil) {
return NO;
}
if (![available boolValue]) {
SETNSERROR([self errorDomain], ERROR_GLACIER_OBJECT_NOT_AVAILABLE, @"%@ not available", [tree aclBlobKey]);
return NO;
}
}
if ([tree xattrsBlobKey] != nil && ![self applyXAttrsBlobKey:[tree xattrsBlobKey] restorer:theRestorer error:error]) {
return NO;
}
FSRef fsRef;
Boolean isDirectory;
OSStatus oss = FSPathMakeRef((UInt8*)[path fileSystemRepresentation], &fsRef, &isDirectory);
if (oss != noErr) {
if (oss == bdNamErr) {
HSLogInfo(@"not applying some metadata to %@: bad name", path);
return YES;
} else {
SETNSERROR([self errorDomain], -1, @"%@", [OSStatusDescription descriptionForOSStatus:oss]);
return NO;
}
}
if (![FileAttributes applyFinderFlags:[tree finderFlags] to:&fsRef isDirectory:YES error:error]
|| ![FileAttributes applyExtendedFinderFlags:[tree extendedFinderFlags] to:&fsRef isDirectory:YES error:error]) {
return NO;
}
if (![FileAttributes applyMode:[tree mode] toPath:path isDirectory:YES error:error]) {
return NO;
}
if (!S_ISLNK([tree mode]) && [tree treeVersion] >= 7 && ![FileAttributes applyMTimeSec:tree.mtime_sec mTimeNSec:tree.mtime_nsec toPath:path error:error]) {
return NO;
}
if (([tree treeVersion] >= 7) && ![FileAttributes applyCreateTimeSec:tree.createTime_sec createTimeNSec:tree.createTime_nsec to:&fsRef error:error]) {
return NO;
}
if (![FileAttributes applyFlags:(unsigned long)[tree flags] toPath:path error:error]) {
return NO;
}
if (([tree uid] < 500 && [tree gid] < 500) || ![theRestorer useTargetUIDAndGID]) {
if ([tree aclBlobKey] != nil && ![self applyACLBlobKey:[tree aclBlobKey] restorer:theRestorer error:error]) {
return NO;
}
}
return YES;
}
- (BOOL)applyXAttrsBlobKey:(BlobKey *)xattrsBlobKey restorer:(id <Restorer>)theRestorer error:(NSError **)error {
NSAssert(xattrsBlobKey != nil, @"xattrsBlobKey may not be nil");
NSData *xattrsData = [theRestorer dataForBlobKey:xattrsBlobKey error:error];
if (xattrsData == nil) {
return NO;
}
if ([xattrsBlobKey compressed]) {
xattrsData = [xattrsData gzipInflate:error];
if (xattrsData == nil) {
return NO;
}
}
DataInputStream *dis = [[[DataInputStream alloc] initWithData:xattrsData description:[NSString stringWithFormat:@"xattrs %@", xattrsBlobKey]] autorelease];
BufferedInputStream *bis = [[[BufferedInputStream alloc] initWithUnderlyingStream:dis] autorelease];
XAttrSet *set = [[[XAttrSet alloc] initWithBufferedInputStream:bis error:error] autorelease];
if (!set) {
return NO;
}
if (![set applyToFile:path error:error]) {
return NO;
}
return YES;
}
- (BOOL)applyACLBlobKey:(BlobKey *)theACLBlobKey restorer:(id <Restorer>)theRestorer error:(NSError **)error {
NSAssert(theACLBlobKey != nil, @"theACLBlobKey may not be nil");
NSData *data = [theRestorer dataForBlobKey:theACLBlobKey error:error];
if (data == nil) {
return NO;
}
if ([theACLBlobKey compressed]) {
data = [data gzipInflate:error];
if (data == nil) {
return NO;
}
}
NSString *aclString = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
NSString *currentAclString = nil;
if (![FileACL aclText:&currentAclString forFile:path error:error]) {
return NO;
}
if (![currentAclString isEqualToString:aclString] && [aclString length] > 0) {
if (![FileACL writeACLText:aclString toFile:path error:error]) {
return NO;
}
}
return YES;
}
- (BOOL)createFile:(Node *)theNode restorer:(id <Restorer>)theRestorer error:(NSError **)error {
// Make sure all items are available for download.
if ([theNode xattrsBlobKey] != nil) {
NSNumber *available = [theRestorer isObjectAvailableForBlobKey:[theNode xattrsBlobKey] error:error];
if (available == nil) {
return NO;
}
if (![available boolValue]) {
SETNSERROR([self errorDomain], ERROR_GLACIER_OBJECT_NOT_AVAILABLE, @"xattrs blob %@ not available", [theNode xattrsBlobKey]);
return NO;
}
}
if ([theNode aclBlobKey] != nil) {
NSNumber *available = [theRestorer isObjectAvailableForBlobKey:[theNode aclBlobKey] error:error];
if (available == nil) {
return NO;
}
if (![available boolValue]) {
SETNSERROR([self errorDomain], ERROR_GLACIER_OBJECT_NOT_AVAILABLE, @"acl blob %@ not available", [theNode aclBlobKey]);
return NO;
}
}
BOOL shouldSkipFile = [theRestorer shouldSkipFile:path];
BOOL shouldRestoreUIDAndGID = YES;
if (![[NSFileManager defaultManager] ensureParentPathExistsForPath:path targetUID:[theRestorer targetUID] targetGID:[theRestorer targetGID] error:error]) {
HSLogError(@"error ensuring path %@ exists", path);
return NO;
}
if (!shouldSkipFile) {
if ([[NSFileManager defaultManager] fileExistsAtPath:path] && ![[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 = [theRestorer dataForBlobKey:dataBlobKey error:error];
if (blobData == nil) {
HSLogError(@"error getting data for %@", dataBlobKey);
return NO;
}
if ([dataBlobKey compressed]) {
blobData = [blobData gzipInflate:error];
if (blobData == nil) {
return NO;
}
}
[data appendData:blobData];
}
NSString *target = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
if (symlink([target fileSystemRepresentation], [path fileSystemRepresentation]) == -1) {
int errnum = errno;
HSLogError(@"symlink(%@, %@) error %d: %s", target, path, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"failed to create symlink %@ to %@: %s", path, target, strerror(errnum));
return NO;
}
HSLogDetail(@"restored %@", path);
} else if ([node uncompressedDataSize] > 0) {
fileOutputStream = [[FileOutputStream alloc] initWithPath:path append:NO];
if ([[node dataBlobKeys] count] > 0) {
if (![self restoreFileDataWithRestorer:theRestorer error:error]) {
return NO;
}
shouldRestoreUIDAndGID = 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);
}
} else {
HSLogDetail(@"skipped restoring %@", path);
}
if (shouldRestoreUIDAndGID) {
if ([theRestorer useTargetUIDAndGID]) {
HSLogDebug(@"use restorer %@ target UID %d and GID %d", theRestorer, [theRestorer targetUID], [theRestorer targetGID]);
if (![FileAttributes applyUID:[theRestorer targetUID] gid:[theRestorer targetGID] toPath:path error:error]) {
return NO;
}
} else {
HSLogDebug(@"use node %@ UID %d and GID %d", node, [node uid], [node gid]);
if (![FileAttributes applyUID:[node uid] gid:[node gid] toPath:path error:error]) {
return NO;
}
}
}
return YES;
}
- (BOOL)restoreFileDataWithRestorer:(id <Restorer>)theRestorer error:(NSError **)error {
NSError *myError = nil;
if (![self doRestoreFileDataWithRestorer:theRestorer error:&myError]) {
SETERRORFROMMYERROR;
if ([myError isErrorWithDomain:[self errorDomain] code:ERROR_GLACIER_OBJECT_NOT_AVAILABLE]) {
HSLogDebug(@"object #%ld not available yet", (unsigned long)dataBlobKeyIndex);
} else {
// An error occurred. Stop continuing to restore this file.
dataBlobKeyIndex = [[node dataBlobKeys] count];
errorOccurred = YES;
}
return NO;
}
return YES;
}
- (BOOL)doRestoreFileDataWithRestorer:(id <Restorer>)theRestorer error:(NSError **)error {
BlobKey *theBlobKey = [[node dataBlobKeys] objectAtIndex:dataBlobKeyIndex];
NSNumber *available = [theRestorer isObjectAvailableForBlobKey:theBlobKey error:error];
if (available == nil) {
return NO;
}
if (![available boolValue]) {
SETNSERROR([self errorDomain], ERROR_GLACIER_OBJECT_NOT_AVAILABLE, @"acl blob %@ not available", theBlobKey);
return NO;
}
NSData *blobData = [theRestorer dataForBlobKey:theBlobKey error:error];
if (blobData == nil) {
NSError *myError = nil;
if (![[NSFileManager defaultManager] removeItemAtPath:path error:&myError]) {
HSLogError(@"error deleting incomplete file %@: %@", path, [myError localizedDescription]);
} else {
HSLogError(@"deleted incomplete file %@", path);
}
return NO;
}
if ([theBlobKey compressed]) {
blobData = [blobData gzipInflate:error];
if (blobData == nil) {
return NO;
NSError *myError = nil;
if (![[NSFileManager defaultManager] removeItemAtPath:path error:&myError]) {
HSLogError(@"error deleting incomplete file %@: %@", path, [myError localizedDescription]);
} else {
HSLogError(@"deleted incomplete file %@", path);
}
}
}
BufferedOutputStream *bos = [[BufferedOutputStream alloc] initWithUnderlyingOutputStream:fileOutputStream];
NSError *myError = nil;
BOOL ret = [bos writeFully:[blobData bytes] length:[blobData length] error:&myError] && [bos flush:&myError];
[bos release];
if (!ret) {
HSLogError(@"error appending data to %@: %@", path, myError);
SETERRORFROMMYERROR;
if (![[NSFileManager defaultManager] removeItemAtPath:path error:&myError]) {
HSLogError(@"error deleting incomplete file %@: %@", path, [myError localizedDescription]);
} else {
HSLogError(@"deleted incomplete file %@", path);
}
return NO;
}
dataBlobKeyIndex++;
HSLogDebug(@"appended blob %ld of %ld to %@", (unsigned long)dataBlobKeyIndex, (unsigned long)[[node dataBlobKeys] count], path);
if (dataBlobKeyIndex >= [[node dataBlobKeys] count]) {
if ([theRestorer useTargetUIDAndGID]) {
HSLogDebug(@"use restorer %@ target UID %d and GID %d", theRestorer, [theRestorer targetUID], [theRestorer targetGID]);
if (![FileAttributes applyUID:[theRestorer targetUID] gid:[theRestorer targetGID] toPath:path error:error]) {
return NO;
}
} else {
HSLogDebug(@"use node %@ UID %d and GID %d", node, [node uid], [node gid]);
if (![FileAttributes applyUID:[node uid] gid:[node gid] toPath:path error:error]) {
return NO;
}
}
if (![self applyNodeWithRestorer:theRestorer error:error]) {
return NO;
}
}
return YES;
}
- (BOOL)applyNodeWithRestorer:(id <Restorer>)theRestorer error:(NSError **)error {
NSError *xattrsError = nil;
if ([node xattrsBlobKey] != nil && ![self applyXAttrsBlobKey:[node xattrsBlobKey] restorer:theRestorer error:&xattrsError]) {
HSLogError(@"failed to apply xattrs %@ to %@: %@", [node xattrsBlobKey], path, xattrsError);
}
if (([node uid] < 500 && [node gid] < 500) || ![theRestorer useTargetUIDAndGID]) {
NSError *aclError = nil;
if ([node aclBlobKey] != nil && ![self applyACLBlobKey:[node aclBlobKey] restorer:theRestorer error:&aclError]) {
HSLogError(@"failed to apply acl %@ to %@: %@", [node aclBlobKey], path, aclError);
}
}
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;
}
FSRef fsRef;
Boolean isDirectory;
OSStatus oss = 0;
if (S_ISLNK(st.st_mode)) {
oss = SymlinkPathMakeRef((UInt8*)[path fileSystemRepresentation], &fsRef, &isDirectory);
} else {
oss = FSPathMakeRef((UInt8*)[path fileSystemRepresentation], &fsRef, &isDirectory);
}
if (oss == bdNamErr) {
HSLogInfo(@"skipping applying some metadata for %@: %@", path, [OSStatusDescription descriptionForOSStatus:oss]);
} else if (oss != noErr) {
SETNSERROR(@"MacFilesErrorDomain", oss, @"error making FSRef for %@: %@", path, [OSStatusDescription descriptionForOSStatus:oss]);
return NO;
} else {
if (!S_ISFIFO([node mode])) {
FileAttributes *fa = [[[FileAttributes alloc] initWithPath:path stat:&st error:error] autorelease];
if (fa == nil) {
return NO;
}
if ([fa finderFlags] != [node finderFlags] && ![FileAttributes applyFinderFlags:[node finderFlags] to:&fsRef isDirectory:NO error:error]) {
return NO;
}
if ([fa extendedFinderFlags] != [node extendedFinderFlags] && ![FileAttributes applyExtendedFinderFlags:[node extendedFinderFlags] to:&fsRef isDirectory:NO error:error]) {
return NO;
}
if ([node finderFileType] != nil || [node finderFileCreator] != nil) {
if (![[fa finderFileType] isEqualToString:[node finderFileType]] || ![[fa finderFileCreator] isEqualToString:[node finderFileCreator]]) {
if (![FileAttributes applyFinderFileType:[node finderFileType] finderFileCreator:[node finderFileCreator] to:&fsRef error:error]) {
return NO;
}
}
}
}
if (([node treeVersion] >= 7) && ![FileAttributes applyCreateTimeSec:node.createTime_sec createTimeNSec:node.createTime_nsec to:&fsRef error:error]) {
return NO;
}
}
if (st.st_mode != [node mode] && ![FileAttributes applyMode:[node mode] toPath:path isDirectory:NO error:error]) {
return NO;
}
if (!S_ISLNK([node mode]) && [node treeVersion] >= 7 && ![FileAttributes applyMTimeSec:node.mtime_sec mTimeNSec:node.mtime_nsec toPath:path error:error]) {
return NO;
}
if (!S_ISFIFO([node mode])) {
if (st.st_flags != [node flags] && ![FileAttributes applyFlags:(unsigned long)[node flags] toPath:path error:error]) {
return NO;
}
}
return YES;
}
- (NSArray *)nextItemsForTreeWithRepo:(Repo *)theRepo error:(NSError **)error {
NSMutableArray *nextItems = [NSMutableArray array];
NSAutoreleasePool *pool = nil;
for (NSString *childNodeName in [tree childNodeNames]) {
[pool drain];
pool = [[NSAutoreleasePool alloc] init];
Node *childNode = [tree childNodeWithName:childNodeName];
NSString *childPath = [path stringByAppendingPathComponent:childNodeName];
if ([childNode isTree]) {
Tree *childTree = [theRepo treeForBlobKey:[childNode treeBlobKey] error:error];
if (childTree == nil) {
nextItems = nil;
break;
}
RestoreItem *childRestoreItem = [[[RestoreItem alloc] initWithPath:childPath tree:childTree] autorelease];
[nextItems addObject:childRestoreItem];
} else {
RestoreItem *childRestoreItem = [[[RestoreItem alloc] initWithPath:childPath tree:tree node:childNode] autorelease];
[nextItems addObject:childRestoreItem];
}
}
if (nextItems == nil && error != NULL) {
[*error retain];
}
[pool drain];
if (nextItems == nil && error != NULL) {
[*error autorelease];
}
if (nextItems == nil) {
return nil;
}
RestoreItem *treeRestoreItem = [[[RestoreItem alloc] initApplyItemWithTree:tree path:path] autorelease];
[nextItems addObject:treeRestoreItem];
return nextItems;
}
#pragma mark NSObject
- (NSString *)description {
return [NSString stringWithFormat:@"<RestoreItem %@>", path];
}
@end

22
commonrestore/Restorer.h Normal file
View file

@ -0,0 +1,22 @@
//
// Restorer.h
// Arq
//
// Created by Stefan Reitshamer on 6/12/13.
// Copyright (c) 2013 Stefan Reitshamer. All rights reserved.
//
@class BlobKey;
@protocol Restorer <NSObject>
- (NSString *)errorDomain;
- (BOOL)requestBlobKey:(BlobKey *)theBlobKey error:(NSError **)error;
- (NSNumber *)isObjectAvailableForBlobKey:(BlobKey *)theBlobKey error:(NSError **)error;
- (NSNumber *)sizeOfBlob:(BlobKey *)theBlobKey error:(NSError **)error;
- (NSData *)dataForBlobKey:(BlobKey *)theBlobKey error:(NSError **)error;
- (BOOL)shouldSkipFile:(NSString *)thePath;
- (BOOL)useTargetUIDAndGID;
- (uid_t)targetUID;
- (gid_t)targetGID;
@end

45
repo/FileAttributes.h Normal file
View file

@ -0,0 +1,45 @@
//
// FileAttributes.h
// Backup
//
// Created by Stefan Reitshamer on 4/22/09.
// Copyright 2009 PhotoMinds LLC. All rights reserved.
//
#include <sys/stat.h>
OSStatus SymlinkPathMakeRef(const UInt8 *path, FSRef *ref, Boolean *isDirectory);
@interface FileAttributes : NSObject {
BOOL targetExists;
struct timespec createTime;
int finderFlags;
int extendedFinderFlags;
NSString *finderFileType;
NSString *finderFileCreator;
BOOL isFileExtensionHidden;
}
+ (NSString *)errorDomain;
- (id)initWithPath:(NSString *)thePath stat:(struct stat *)theStat error:(NSError **)error;
- (int)finderFlags;
- (int)extendedFinderFlags;
- (NSString *)finderFileType;
- (NSString *)finderFileCreator;
- (int64_t)createTime_sec;
- (int64_t)createTime_nsec;
- (BOOL)isFileExtensionHidden;
+ (BOOL)applyFinderFileType:(NSString *)fft finderFileCreator:(NSString *)ffc to:(FSRef *)fsRef error:(NSError **)error;
+ (BOOL)applyFlags:(unsigned long)flags toPath:(NSString *)thePath error:(NSError **)error;
+ (BOOL)applyFinderFlags:(int)ff to:(FSRef *)fsRef isDirectory:(BOOL)isDirectory error:(NSError **)error;
+ (BOOL)applyExtendedFinderFlags:(int)eff to:(FSRef *)fsRef isDirectory:(BOOL)isDirectory error:(NSError **)error;
+ (BOOL)applyFileExtensionHidden:(BOOL)hidden toPath:(NSString *)thePath error:(NSError **)error;
+ (BOOL)applyUID:(int)uid gid:(int)gid toPath:(NSString *)thePath error:(NSError **)error;
+ (BOOL)applyMode:(int)mode toPath:(NSString *)thePath isDirectory:(BOOL)isDirectory error:(NSError **)error;
+ (BOOL)applyMTimeSec:(int64_t)mtime_sec mTimeNSec:(int64_t)mtime_nsec toPath:(NSString *)thePath error:(NSError **)error;
+ (BOOL)applyCreateTimeSec:(int64_t)theCreateTime_sec createTimeNSec:(int64_t)theCreateTime_nsec to:(FSRef *)fsRef error:(NSError **)error;
@end

377
repo/FileAttributes.m Normal file
View file

@ -0,0 +1,377 @@
//
// 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 "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, kCFStringEncodingUTF8);
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;
}
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
+ (NSString *)errorDomain {
return @"FileAttributesErrorDomain";
}
- (id)initWithPath:(NSString *)thePath stat:(struct stat *)theStat error:(NSError **)error {
if (self = [super init]) {
NSError *myError = nil;
NSDictionary *attribs = [[NSFileManager defaultManager] attributesOfItemAtPath:thePath error:&myError];
if (attribs == nil) {
myError = [[[NSError alloc] initWithDomain:[FileAttributes errorDomain]
code:-1
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
[NSString stringWithFormat:@"[FileAttributes attributesOfItemAtPath:%@]: %@", thePath, [myError localizedDescription]], NSLocalizedDescriptionKey,
myError, NSUnderlyingErrorKey,
nil]] autorelease];
HSLogError(@"%@", myError);
SETERRORFROMMYERROR;
[self release];
return nil;
}
isFileExtensionHidden = [[attribs objectForKey:NSFileExtensionHidden] boolValue];
targetExists = YES;
if (S_ISLNK(theStat->st_mode)) {
struct stat targetSt;
int ret = stat([thePath fileSystemRepresentation], &targetSt);
if (ret == -1 && errno == ENOENT) {
targetExists = NO;
}
}
if (targetExists) {
FSRef fsRef;
Boolean isDirectory = false;
OSStatus oss = 0;
if (S_ISLNK(theStat->st_mode)) {
oss = SymlinkPathMakeRef((UInt8*)[thePath fileSystemRepresentation], &fsRef, &isDirectory);
} else {
oss = FSPathMakeRef((UInt8*)[thePath fileSystemRepresentation], &fsRef, &isDirectory);
}
if (oss == bdNamErr) {
HSLogInfo(@"skipping finder flags for %@: %@", thePath, [OSStatusDescription descriptionForOSStatus:oss]);
} else if (oss == ioErr) {
HSLogInfo(@"skipping finder flags for %@: %@", thePath, [OSStatusDescription descriptionForOSStatus:oss]);
} else if (oss != noErr) {
HSLogError(@"error making FSRef for %@: %@", thePath, [OSStatusDescription descriptionForOSStatus:oss]);
SETNSERROR([FileAttributes errorDomain], oss, @"error making FSRef for %@: %@", thePath, [OSStatusDescription descriptionForOSStatus:oss]);
[self release];
self = nil;
return self;
} else {
FSCatalogInfo catalogInfo;
OSErr oserr = FSGetCatalogInfo(&fsRef, kFSCatInfoCreateDate | kFSCatInfoFinderInfo | kFSCatInfoFinderXInfo, &catalogInfo, NULL, NULL, NULL);
if (oserr) {
HSLogError(@"FSGetCatalogInfo(%@): %@", thePath, [OSStatusDescription descriptionForOSStatus:(OSStatus)oserr]);
SETNSERROR([FileAttributes errorDomain], oss, @"FSGetCatalogInfo(%@): %@", thePath, [OSStatusDescription descriptionForOSStatus:(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 = (__darwin_time_t)(theCreateTime + NSTimeIntervalSince1970);
CFTimeInterval subsecond = theCreateTime - (double)((int64_t)theCreateTime);
createTime.tv_nsec = (__darwin_time_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 {
[finderFileType release];
[finderFileCreator release];
[super dealloc];
}
- (int)finderFlags {
return finderFlags;
}
- (int)extendedFinderFlags {
return extendedFinderFlags;
}
- (NSString *)finderFileType {
return finderFileType;
}
- (NSString *)finderFileCreator {
return finderFileCreator;
}
- (int64_t)createTime_sec {
return createTime.tv_sec;
}
- (int64_t)createTime_nsec {
return createTime.tv_nsec;
}
- (BOOL)isFileExtensionHidden {
return isFileExtensionHidden;
}
+ (BOOL)applyFinderFileType:(NSString *)fft finderFileCreator:(NSString *)ffc to:(FSRef *)fsRef error:(NSError **)error {
if ([fft length] != 4) {
HSLogTrace(@"not applying finder file type '%@': invalid length (must be 4 characters)", fft);
} else if ([ffc length] != 4) {
HSLogTrace(@"not applying finder file type '%@': invalid length (must be 4 characters)", ffc);
} else {
FSCatalogInfo catalogInfo;
OSErr oserr = FSGetCatalogInfo(fsRef, kFSCatInfoFinderInfo, &catalogInfo, NULL, NULL, NULL);
if (oserr) {
SETNSERROR(@"FileManagerErrorDomain", -1, @"%@", [OSStatusDescription descriptionForOSStatus:(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 descriptionForOSStatus:(OSStatus)oserr]);
return NO;
}
}
return YES;
}
+ (BOOL)applyFlags:(unsigned long)flags toPath:(NSString *)thePath error:(NSError **)error {
HSLogTrace(@"chflags(%@, %ld)", thePath, flags);
if (chflags([thePath fileSystemRepresentation], (unsigned int)flags) == -1) {
int errnum = errno;
HSLogError(@"chflags(%@, %ld) error %d: %s", thePath, flags, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"error changing flags of %@: %s", thePath, strerror(errnum));
return NO;
}
return YES;
}
+ (BOOL)applyFinderFlags:(int)ff to:(FSRef *)fsRef isDirectory:(BOOL)isDirectory error:(NSError **)error {
FSCatalogInfo catalogInfo;
OSErr oserr = FSGetCatalogInfo(fsRef, kFSCatInfoFinderInfo, &catalogInfo, NULL, NULL, NULL);
if (oserr) {
SETNSERROR(@"FileManagerErrorDomain", -1, @"%@", [OSStatusDescription descriptionForOSStatus:(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 descriptionForOSStatus:(OSStatus)oserr]);
return NO;
}
return YES;
}
+ (BOOL)applyExtendedFinderFlags:(int)eff to:(FSRef *)fsRef isDirectory:(BOOL)isDirectory error:(NSError **)error {
FSCatalogInfo catalogInfo;
OSErr oserr = FSGetCatalogInfo(fsRef, kFSCatInfoFinderXInfo, &catalogInfo, NULL, NULL, NULL);
if (oserr) {
SETNSERROR(@"FileManagerErrorDomain", -1, @"%@", [OSStatusDescription descriptionForOSStatus:(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 descriptionForOSStatus:(OSStatus)oserr]);
return NO;
}
return YES;
}
+ (BOOL)applyFileExtensionHidden:(BOOL)hidden toPath:(NSString *)thePath error:(NSError **)error {
NSDictionary *attribs = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:hidden], NSFileExtensionHidden, nil];
return [[NSFileManager defaultManager] setAttributes:attribs ofItemAtPath:thePath error:error];
}
+ (BOOL)applyUID:(int)uid gid:(int)gid toPath:(NSString *)thePath error:(NSError **)error {
HSLogDebug(@"chown(%@, %d, %d)", thePath, uid, gid);
if (lchown([thePath fileSystemRepresentation], uid, gid) == -1) {
int errnum = errno;
HSLogError(@"lchown(%@) error %d: %s", thePath, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"error changing ownership of %@: %s", thePath, strerror(errnum));
return NO;
}
HSLogDebug(@"lchown(%@, %d, %d); euid=%d", thePath, uid, gid, geteuid());
return YES;
}
+ (BOOL)applyMode:(int)mode toPath:(NSString *)thePath isDirectory:(BOOL)isDirectory error:(NSError **)error {
if (isDirectory) {
int ret = chmod([thePath fileSystemRepresentation], mode);
if (ret == -1) {
int errnum = errno;
HSLogError(@"chmod(%@, %d) error %d: %s", thePath, mode, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"failed to set permissions on %@: %s", thePath, strerror(errnum));
return NO;
}
HSLogDebug(@"chmod(%@, 0%6o)", thePath, mode);
} else {
int fd = open([thePath fileSystemRepresentation], O_RDWR|O_SYMLINK);
if (fd == -1) {
int errnum = errno;
HSLogError(@"open(%@) error %d: %s", thePath, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"failed to open %@: %s", thePath, strerror(errnum));
return NO;
}
int ret = fchmod(fd, mode);
close(fd);
if (ret == -1) {
int errnum = errno;
HSLogError(@"fchmod(%@) error %d: %s", thePath, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"failed to set permissions on %@: %s", thePath, strerror(errnum));
return NO;
}
HSLogDebug(@"fchmod(%@, 0%6o)", thePath, mode);
}
return YES;
}
+ (BOOL)applyMTimeSec:(int64_t)mtime_sec mTimeNSec:(int64_t)mtime_nsec toPath:(NSString *)thePath error:(NSError **)error {
struct timespec mtimeSpec = { (__darwin_time_t)mtime_sec, (__darwin_time_t)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([thePath fileSystemRepresentation], timevals) == -1) {
int errnum = errno;
HSLogError(@"utimes(%@) error %d: %s", thePath, errnum, strerror(errnum));
SETNSERROR(@"UnixErrorDomain", errnum, @"failed to set timestamps on %@: %s", thePath, strerror(errnum));
return NO;
}
return YES;
}
+ (BOOL)applyCreateTimeSec:(int64_t)theCreateTime_sec createTimeNSec:(int64_t)theCreateTime_nsec to:(FSRef *)fsRef error:(NSError **)error {
FSCatalogInfo catalogInfo;
OSErr oserr = FSGetCatalogInfo(fsRef, kFSCatInfoCreateDate, &catalogInfo, NULL, NULL, NULL);
if (oserr) {
SETNSERROR(@"FileManagerErrorDomain", -1, @"%@", [OSStatusDescription descriptionForOSStatus:(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 descriptionForOSStatus:(OSStatus)oserr]);
return NO;
}
return YES;
}
@end

View file

@ -173,6 +173,8 @@ loadExistingMutablePackFiles:(BOOL)theLoadExistingMutablePackFiles {
PackIndexEntry *pie = [packIndexEntriesByObjectSHA1 objectForKey:sha1];
if (pie != nil) {
ret = [fark dataForPackIndexEntry:pie storageType:storageType error:error];
} else {
SETNSERROR([self errorDomain], ERROR_NOT_FOUND, @"%@ not found in pack set", sha1);
}
return ret;
}

View file

@ -376,6 +376,7 @@ targetConnectionDelegate:(id<TargetConnectionDelegate>)theTCD
SETERRORFROMMYERROR;
if (![myError isErrorWithDomain:[blobsPackSet errorDomain] code:ERROR_NOT_FOUND]) {
// Return nil if not a not-found error.
SETERRORFROMMYERROR;
return nil;
}
data = [fark dataForSHA1:[theBlobKey sha1] storageType:[theBlobKey storageType] error:&myError];

23
repo/XAttrSet.h Normal file
View file

@ -0,0 +1,23 @@
//
// XAttrSet.h
// Backup
//
// Created by Stefan Reitshamer on 4/27/09.
// Copyright 2009 PhotoMinds LLC. All rights reserved.
//
#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

204
repo/XAttrSet.m Normal file
View file

@ -0,0 +1,204 @@
//
// 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 "DataInputStream.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, @"xattr lstat(%@): %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);
if (valuesize == -1) {
int errnum = errno;
HSLogError(@"skipping xattrs %s of %s: error %d: %s", name, cpath, errnum, strerror(errnum));
} else if (valuesize > 0) {
void *value = malloc(valuesize);
if (getxattr(cpath, name, value, valuesize, 0, XATTR_NOFOLLOW) == -1) {
int errnum = errno;
HSLogError(@"skipping xattrs %s of %s: error %d: %s", name, cpath, errnum, strerror(errnum));
} else {
[xattrs setObject:[NSData dataWithBytes:value length:valuesize] forKey:theName];
}
free(value);
} else {
[xattrs setObject:[NSData data] 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

43
s3restore/S3Restorer.h Normal file
View file

@ -0,0 +1,43 @@
//
// S3Restorer.h
// Arq
//
// Created by Stefan Reitshamer on 5/28/13.
// Copyright (c) 2013 Stefan Reitshamer. All rights reserved.
//
#import "StorageType.h"
#import "Restorer.h"
#import "TargetConnection.h"
@protocol S3RestorerDelegate;
@class S3RestorerParamSet;
@class Repo;
@class Commit;
@class Tree;
@class BlobKey;
@interface S3Restorer : NSObject <Restorer, TargetConnectionDelegate> {
S3RestorerParamSet *paramSet;
id <S3RestorerDelegate> delegate;
NSString *skipFilesRoot;
NSMutableArray *calculateItems;
NSMutableArray *restoreItems;
NSMutableDictionary *hardlinks;
Repo *repo;
Commit *commit;
NSString *commitDescription;
Tree *rootTree;
unsigned long long bytesTransferred;
unsigned long long totalBytesToTransfer;
unsigned long long writtenToCurrentFile;
}
- (id)initWithParamSet:(S3RestorerParamSet *)theParamSet
delegate:(id <S3RestorerDelegate>)theDelegate;
@end

297
s3restore/S3Restorer.m Normal file
View file

@ -0,0 +1,297 @@
//
// S3Restorer.m
// Arq
//
// Created by Stefan Reitshamer on 5/28/13.
// Copyright (c) 2013 Stefan Reitshamer. All rights reserved.
//
#import "S3Restorer.h"
#import "S3RestorerDelegate.h"
#import "ArqSalt.h"
#import "Repo.h"
#import "S3AuthorizationProvider.h"
#import "S3Service.h"
#import "Tree.h"
#import "Node.h"
#import "FileOutputStream.h"
#import "Commit.h"
#import "BlobKey.h"
#import "NSFileManager_extra.h"
#import "NSData-GZip.h"
#import "BufferedOutputStream.h"
#import "OSStatusDescription.h"
#import "FileAttributes.h"
#import "FileACL.h"
#import "DataInputStream.h"
#import "XAttrSet.h"
#import "FileInputStream.h"
#import "SHA1Hash.h"
#import "S3RestorerParamSet.h"
#import "RestoreItem.h"
#import "UserLibrary_Arq.h"
#import "CalculateItem.h"
@implementation S3Restorer
- (id)initWithParamSet:(S3RestorerParamSet *)theParamSet
delegate:(id <S3RestorerDelegate>)theDelegate {
if (self = [super init]) {
paramSet = [theParamSet retain];
delegate = theDelegate; // Don't retain it.
skipFilesRoot = [[[UserLibrary arqUserLibraryPath] stringByAppendingFormat:@"/RestoreJobSkipFiles/%f", [NSDate timeIntervalSinceReferenceDate]] retain];
calculateItems = [[NSMutableArray alloc] init];
restoreItems = [[NSMutableArray alloc] init];
hardlinks = [[NSMutableDictionary alloc] init];
[self run];
}
return self;
}
- (void)dealloc {
[paramSet release];
[skipFilesRoot release];
[calculateItems release];
[restoreItems release];
[hardlinks release];
[repo release];
[super dealloc];
}
- (void)run {
NSError *myError = nil;
if (![self run:&myError]) {
[delegate s3RestorerDidFail:myError];
} else {
[delegate s3RestorerDidSucceed];
}
}
- (BOOL)requestBlobKey:(BlobKey *)theBlobKey error:(NSError **)error {
// Not relevant for S3.
return YES;
}
- (NSNumber *)isObjectAvailableForBlobKey:(BlobKey *)theBlobKey error:(NSError **)error {
return [NSNumber numberWithBool:YES];
}
- (NSNumber *)sizeOfBlob:(BlobKey *)theBlobKey error:(NSError **)error {
unsigned long long size = 0;
NSNumber *contains = [repo containsBlobForBlobKey:theBlobKey dataSize:&size error:error];
if (contains == nil) {
return nil;
}
if (![contains boolValue]) {
SETNSERROR([self errorDomain], ERROR_NOT_FOUND, @"size of blob %@ not found because blob not found", theBlobKey);
return NO;
}
return [NSNumber numberWithUnsignedLongLong:size];
}
- (NSData *)dataForBlobKey:(BlobKey *)theBlobKey error:(NSError **)error {
// Because of a bug in Arq pre-4.4, Arq might have created Buckets for non-AWS S3-compatible destinations with a storage type of StorageTypeS3Glacier.
// So, we could be here and theBlobKey's storage type could be StorageTypeS3Glacier, which is OK because the Repo will just put "glacier/" in the path
// and restoring will work fine.
NSData *ret = [repo dataForBlobKey:theBlobKey error:error];
if (ret == nil) {
return nil;
}
if (![self addToBytesTransferred:[ret length] error:error]) {
return nil;
}
return ret;
}
- (BOOL)shouldSkipFile:(NSString *)thePath {
NSString *skipFilePath = [skipFilesRoot stringByAppendingString:thePath];
return [[NSFileManager defaultManager] fileExistsAtPath:skipFilePath];
}
- (BOOL)useTargetUIDAndGID {
return paramSet.useTargetUIDAndGID;
}
- (uid_t)targetUID {
return paramSet.targetUID;
}
- (gid_t)targetGID {
return paramSet.targetGID;
}
#pragma mark TargetConnectionDelegate
- (BOOL)targetConnectionShouldRetryOnTransientError:(NSError **)error {
return YES;
}
#pragma mark internal
- (NSString *)errorDomain {
return @"S3RestorerErrorDomain";
}
- (BOOL)run:(NSError **)error {
if (![self setUp:error]) {
return NO;
}
if ([delegate s3RestorerMessageDidChange:@"Calculating sizes"]) {
SETNSERROR([self errorDomain], ERROR_ABORT_REQUESTED, @"cancel requested");
return NO;
}
if (![self calculateSizes:error]) {
return NO;
}
if ([delegate s3RestorerMessageDidChange:[NSString stringWithFormat:@"Restoring %@ from %@ to %@", paramSet.rootItemName, commitDescription, paramSet.destinationPath]]) {
SETNSERROR([self errorDomain], ERROR_ABORT_REQUESTED, @"cancel requested");
return NO;
}
NSAutoreleasePool *pool = nil;
BOOL ret = YES;
while ([restoreItems count] > 0) {
[pool drain];
pool = [[NSAutoreleasePool alloc] init];
RestoreItem *restoreItem = [restoreItems objectAtIndex:0];
NSError *restoreError = nil;
if (![restoreItem restoreWithHardlinks:hardlinks restorer:self error:&restoreError]) {
if ([restoreError isErrorWithDomain:[self errorDomain] code:ERROR_ABORT_REQUESTED]) {
if (error != NULL) {
*error = restoreError;
}
ret = NO;
break;
} else {
[delegate s3RestorerErrorMessage:[restoreError localizedDescription] didOccurForPath:[restoreItem path]];
}
}
NSArray *nextItems = [restoreItem nextItemsWithRepo:repo error:error];
if (nextItems == nil) {
ret = NO;
break;
}
[restoreItems removeObjectAtIndex:0];
if ([nextItems count] > 0) {
[restoreItems insertObjects:nextItems atIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [nextItems count])]];
}
}
if (!ret && error != NULL) {
[*error retain];
}
[pool drain];
if (!ret && error != NULL) {
[*error autorelease];
}
return ret;
}
- (BOOL)setUp:(NSError **)error {
repo = [[Repo alloc] initWithBucket:[paramSet bucket]
encryptionPassword:[paramSet encryptionPassword]
targetUID:[paramSet targetUID]
targetGID:[paramSet targetGID]
loadExistingMutablePackFiles:NO
targetConnectionDelegate:self
repoDelegate:nil
error:error];
if (repo == nil) {
return NO;
}
commit = [[repo commitForBlobKey:paramSet.commitBlobKey error:error] retain];
if (commit == nil) {
return NO;
}
NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
[dateFormatter setDateStyle:NSDateFormatterMediumStyle];
[dateFormatter setTimeStyle:NSDateFormatterShortStyle];
commitDescription = [[dateFormatter stringFromDate:[commit creationDate]] retain];
rootTree = [[repo treeForBlobKey:paramSet.treeBlobKey error:error] retain];
if (rootTree == nil) {
return NO;
}
unsigned long long total = 0;
if (paramSet.nodeName != nil) {
// Individual file.
Node *node = [rootTree childNodeWithName:paramSet.nodeName];
if ([[rootTree childNodeNames] isEqualToArray:[NSArray arrayWithObject:@"."]]) {
// The single-file case.
node = [rootTree childNodeWithName:@"."];
}
NSAssert(node != nil, @"node may not be nil");
total = [node uncompressedDataSize];
[calculateItems addObject:[[[CalculateItem alloc] initWithPath:paramSet.destinationPath node:node] autorelease]];
[restoreItems addObject:[[[RestoreItem alloc] initWithPath:paramSet.destinationPath tree:rootTree node:node] autorelease]];
} else {
// Tree.
total = [rootTree aggregateUncompressedDataSize];
[calculateItems addObject:[[[CalculateItem alloc] initWithPath:paramSet.destinationPath tree:rootTree] autorelease]];
[restoreItems addObject:[[[RestoreItem alloc] initWithPath:paramSet.destinationPath tree:rootTree] autorelease]];
}
if (![self addToTotalBytesToTransfer:total error:error]) {
return NO;
}
return YES;
}
- (BOOL)calculateSizes:(NSError **)error {
BOOL ret = YES;
NSAutoreleasePool *pool = nil;
while ([calculateItems count] > 0) {
[pool drain];
pool = [[NSAutoreleasePool alloc] init];
CalculateItem *item = [calculateItems objectAtIndex:0];
if (![item calculateWithRepo:repo restorer:self error:error]) {
ret = NO;
break;
}
for (NSString *path in [item filesToSkip]) {
[self skipFile:path];
}
[calculateItems removeObjectAtIndex:0];
NSArray *nextItems = [item nextItems];
if ([nextItems count] > 0) {
[calculateItems insertObjects:nextItems atIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [nextItems count])]];
}
}
if (!ret && error != NULL) {
[*error retain];
}
[pool drain];
if (!ret && error != NULL) {
[*error autorelease];
}
return ret;
}
- (void)skipFile:(NSString *)thePath {
NSString *skipFilePath = [skipFilesRoot stringByAppendingString:thePath];
NSError *myError = nil;
if (![[NSFileManager defaultManager] ensureParentPathExistsForPath:skipFilePath targetUID:paramSet.targetUID targetGID:paramSet.targetGID error:&myError]) {
HSLogError(@"error creating parent dir for %@: %@", skipFilePath, myError);
return;
}
if (![[NSFileManager defaultManager] touchFileAtPath:skipFilePath targetUID:paramSet.targetUID targetGID:paramSet.targetGID error:&myError]) {
HSLogError(@"error touching %@: %@", skipFilePath, myError);
}
HSLogDebug(@"skip file %@", thePath);
}
- (BOOL)addToBytesTransferred:(unsigned long long)length error:(NSError **)error {
bytesTransferred += length;
if ([delegate s3RestorerBytesTransferredDidChange:[NSNumber numberWithUnsignedLongLong:bytesTransferred]]) {
SETNSERROR([self errorDomain], ERROR_ABORT_REQUESTED, @"cancel requested");
return NO;
}
return YES;
}
- (BOOL)addToTotalBytesToTransfer:(unsigned long long)length error:(NSError **)error {
totalBytesToTransfer += length;
if ([delegate s3RestorerTotalBytesToTransferDidChange:[NSNumber numberWithUnsignedLongLong:totalBytesToTransfer]]) {
SETNSERROR([self errorDomain], ERROR_ABORT_REQUESTED, @"cancel requested");
return NO;
}
return YES;
}
@end

View file

@ -0,0 +1,22 @@
//
// S3RestorerDelegate.h
// Arq
//
// Created by Stefan Reitshamer on 5/28/13.
// Copyright (c) 2013 Stefan Reitshamer. All rights reserved.
//
@protocol S3RestorerDelegate <NSObject>
// Methods return YES if cancel is requested.
- (BOOL)s3RestorerMessageDidChange:(NSString *)message;
- (BOOL)s3RestorerBytesTransferredDidChange:(NSNumber *)theTransferred;
- (BOOL)s3RestorerTotalBytesToTransferDidChange:(NSNumber *)theTotal;
- (BOOL)s3RestorerErrorMessage:(NSString *)theErrorMessage didOccurForPath:(NSString *)thePath;
- (BOOL)s3RestorerDidSucceed;
- (BOOL)s3RestorerDidFail:(NSError *)error;
@end

View file

@ -0,0 +1,59 @@
//
// S3RestorerParamSet.h
// Arq
//
// Created by Stefan Reitshamer on 5/28/13.
// Copyright (c) 2013 Stefan Reitshamer. All rights reserved.
//
#import "StorageType.h"
@class Bucket;
@class BufferedInputStream;
@class BufferedOutputStream;
@class AWSRegion;
@class BlobKey;
@interface S3RestorerParamSet : NSObject {
Bucket *bucket;
NSString *encryptionPassword;
BlobKey *commitBlobKey;
NSString *rootItemName;
int treeVersion;
BOOL treeIsCompressed;
BlobKey *treeBlobKey;
NSString *nodeName;
uid_t targetUID;
gid_t targetGID;
BOOL useTargetUIDAndGID;
NSString *destinationPath;
int logLevel;
}
- (id)initWithBucket:(Bucket *)theBucket
encryptionPassword:(NSString *)theEncryptionPassword
commitBlobKey:(BlobKey *)theCommitBlobKey
rootItemName:(NSString *)theRootItemName
treeVersion:(int32_t)theTreeVersion
treeIsCompressed:(BOOL)theTreeIsCompressed
treeBlobKey:(BlobKey *)theTreeBlobKey
nodeName:(NSString *)theNodeName
targetUID:(uid_t)theTargetUID
targetGID:(gid_t)theTargetGID
useTargetUIDAndGID:(BOOL)theUseTargetUIDAndGID
destinationPath:(NSString *)theDestination
logLevel:(int)theLogLevel;
@property (readonly, retain) NSString *arqs3restorerPath;
@property (readonly, retain) Bucket *bucket;
@property (readonly, retain) NSString *encryptionPassword;
@property (readonly, retain) BlobKey *commitBlobKey;
@property (readonly, retain) NSString *rootItemName;
@property (readonly, retain) BlobKey *treeBlobKey;
@property (readonly, retain) NSString *nodeName;
@property (readonly) uid_t targetUID;
@property (readonly) gid_t targetGID;
@property (readonly) BOOL useTargetUIDAndGID;
@property (readonly, retain) NSString *destinationPath;
@property (readonly) int logLevel;
@end

View file

@ -0,0 +1,75 @@
//
// S3RestorerParamSet.m
// Arq
//
// Created by Stefan Reitshamer on 5/28/13.
// Copyright (c) 2013 Stefan Reitshamer. All rights reserved.
//
#import "S3RestorerParamSet.h"
#import "AWSRegion.h"
#import "BlobKey.h"
#import "StringIO.h"
#import "IntegerIO.h"
#import "BlobKeyIO.h"
#import "BooleanIO.h"
#import "DataIO.h"
#import "Tree.h"
#import "Bucket.h"
@implementation S3RestorerParamSet
@synthesize bucket;
@synthesize encryptionPassword;
@synthesize commitBlobKey;
@synthesize rootItemName;
@synthesize treeBlobKey;
@synthesize nodeName;
@synthesize targetUID;
@synthesize targetGID;
@synthesize useTargetUIDAndGID;
@synthesize destinationPath;
@synthesize logLevel;
- (id)initWithBucket:(Bucket *)theBucket
encryptionPassword:(NSString *)theEncryptionPassword
commitBlobKey:(BlobKey *)theCommitBlobKey
rootItemName:(NSString *)theRootItemName
treeVersion:(int32_t)theTreeVersion
treeIsCompressed:(BOOL)theTreeIsCompressed
treeBlobKey:(BlobKey *)theTreeBlobKey
nodeName:(NSString *)theNodeName
targetUID:(uid_t)theTargetUID
targetGID:(gid_t)theTargetGID
useTargetUIDAndGID:(BOOL)theUseTargetUIDAndGID
destinationPath:(NSString *)theDestination
logLevel:(int)theLogLevel {
if (self = [super init]) {
bucket = [theBucket retain];
encryptionPassword = [theEncryptionPassword retain];
commitBlobKey = [theCommitBlobKey retain];
rootItemName = [theRootItemName retain];
treeVersion = theTreeVersion;
treeIsCompressed = theTreeIsCompressed;
treeBlobKey = [theTreeBlobKey retain];
nodeName = [theNodeName retain];
targetUID = theTargetUID;
targetGID = theTargetGID;
useTargetUIDAndGID = theUseTargetUIDAndGID;
destinationPath = [theDestination retain];
logLevel = theLogLevel;
}
return self;
}
- (void)dealloc {
[bucket release];
[encryptionPassword release];
[commitBlobKey release];
[rootItemName release];
[treeBlobKey release];
[nodeName release];
[destinationPath release];
[super dealloc];
}
@end