mirror of
https://github.com/samsonjs/arq_restore.git
synced 2026-03-25 09:25:53 +00:00
662 lines
27 KiB
Objective-C
662 lines
27 KiB
Objective-C
/*
|
|
Copyright (c) 2009-2017, Haystack Software LLC https://www.arqbackup.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 "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 "XAttrSet.h"
|
|
#import "Restorer.h"
|
|
#import "NSFileManager_extra.h"
|
|
#import "FileOutputStream.h"
|
|
#import "BufferedOutputStream.h"
|
|
#import "NSData-Compress.h"
|
|
#import "CacheOwnership.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;
|
|
}
|
|
}
|
|
[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 ([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;
|
|
}
|
|
xattrsData = [xattrsData uncompress:[xattrsBlobKey compressionType] error: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;
|
|
}
|
|
data = [data uncompress:[theACLBlobKey compressionType] error: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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
blobData = [blobData uncompress:[dataBlobKey compressionType] error: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 targetUID:[[CacheOwnership sharedCacheOwnership] uid] targetGID:[[CacheOwnership sharedCacheOwnership] gid] 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;
|
|
}
|
|
}
|
|
if (![self applyNodeWithRestorer:theRestorer 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 {
|
|
NSError *myError = nil;
|
|
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:&myError];
|
|
if (blobData == nil) {
|
|
SETERRORFROMMYERROR;
|
|
HSLogError(@"failed to get data for %@: %@", [theBlobKey sha1], [myError localizedDescription]);
|
|
|
|
if (![[NSFileManager defaultManager] removeItemAtPath:path error:&myError]) {
|
|
HSLogError(@"error deleting incomplete file %@: %@", path, [myError localizedDescription]);
|
|
} else {
|
|
HSLogError(@"deleted incomplete file %@", path);
|
|
}
|
|
return NO;
|
|
}
|
|
blobData = [blobData uncompress:[theBlobKey compressionType] error:&myError];
|
|
if (blobData == nil) {
|
|
SETERRORFROMMYERROR;
|
|
HSLogError(@"failed to uncompress %@: %@", [theBlobKey sha1], [myError localizedDescription]);
|
|
|
|
if (![[NSFileManager defaultManager] removeItemAtPath:path error:&myError]) {
|
|
HSLogError(@"error deleting incomplete file %@: %@", path, [myError localizedDescription]);
|
|
} else {
|
|
HSLogError(@"deleted incomplete file %@", path);
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
BufferedOutputStream *bos = [[BufferedOutputStream alloc] initWithUnderlyingOutputStream:fileOutputStream];
|
|
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 (%ld bytes) to %@", (unsigned long)dataBlobKeyIndex, (unsigned long)[[node dataBlobKeys] count], (unsigned long)[blobData length], 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 != noErr) {
|
|
HSLogInfo(@"skipping applying some metadata for %@: %@", path, [OSStatusDescription descriptionForOSStatus:oss]);
|
|
} else {
|
|
if (!S_ISFIFO([node mode])) {
|
|
FileAttributes *fa = [[[FileAttributes alloc] initWithPath:path isSymLink:S_ISLNK([node mode]) 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 ([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
|