arq_restore/DiskPackIndex.m
2011-08-16 15:19:44 -04:00

358 lines
15 KiB
Objective-C

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