mirror of
https://github.com/samsonjs/arq_restore.git
synced 2026-03-25 09:25:53 +00:00
Implemented restore from S3.
This commit is contained in:
parent
8b416e0e22
commit
85a5b9976b
23 changed files with 2415 additions and 7 deletions
|
|
@ -6,10 +6,11 @@
|
|||
//
|
||||
//
|
||||
|
||||
#import "S3RestorerDelegate.h"
|
||||
@class Target;
|
||||
|
||||
|
||||
@interface ArqRestoreCommand : NSObject {
|
||||
@interface ArqRestoreCommand : NSObject <S3RestorerDelegate> {
|
||||
Target *target;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 */,
|
||||
|
|
|
|||
40
cocoastack/shared/FileACL.h
Normal file
40
cocoastack/shared/FileACL.h
Normal 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
101
cocoastack/shared/FileACL.m
Normal 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
|
||||
40
cocoastack/shared/OSStatusDescription.h
Normal file
40
cocoastack/shared/OSStatusDescription.h
Normal 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
|
||||
82
cocoastack/shared/OSStatusDescription.m
Normal file
82
cocoastack/shared/OSStatusDescription.m
Normal 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
|
||||
31
commonrestore/CalculateItem.h
Normal file
31
commonrestore/CalculateItem.h
Normal 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
|
||||
115
commonrestore/CalculateItem.m
Normal file
115
commonrestore/CalculateItem.m
Normal 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
|
||||
34
commonrestore/RestoreItem.h
Normal file
34
commonrestore/RestoreItem.h
Normal 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
644
commonrestore/RestoreItem.m
Normal 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:¤tAclString forFile:path error:error]) {
|
||||
return NO;
|
||||
}
|
||||
if (![currentAclString isEqualToString:aclString] && [aclString length] > 0) {
|
||||
if (![FileACL writeACLText:aclString toFile:path error:error]) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
- (BOOL)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
22
commonrestore/Restorer.h
Normal 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
45
repo/FileAttributes.h
Normal 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
377
repo/FileAttributes.m
Normal 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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
23
repo/XAttrSet.h
Normal 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
204
repo/XAttrSet.m
Normal 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
43
s3restore/S3Restorer.h
Normal 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
297
s3restore/S3Restorer.m
Normal 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
|
||||
22
s3restore/S3RestorerDelegate.h
Normal file
22
s3restore/S3RestorerDelegate.h
Normal 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
|
||||
59
s3restore/S3RestorerParamSet.h
Normal file
59
s3restore/S3RestorerParamSet.h
Normal 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
|
||||
75
s3restore/S3RestorerParamSet.m
Normal file
75
s3restore/S3RestorerParamSet.m
Normal 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
|
||||
Loading…
Reference in a new issue