diff --git a/Configurations/ConfigBinaryDelta.xcconfig b/Configurations/ConfigBinaryDelta.xcconfig new file mode 100644 index 000000000..1f8fea116 --- /dev/null +++ b/Configurations/ConfigBinaryDelta.xcconfig @@ -0,0 +1,5 @@ +// BinaryDelta tool only + +PRODUCT_NAME = BinaryDelta +GCC_PREFIX_HEADER = +SDKROOT = macosx10.6 diff --git a/Configurations/ConfigBinaryDeltaDebug.xcconfig b/Configurations/ConfigBinaryDeltaDebug.xcconfig new file mode 100644 index 000000000..170c9756d --- /dev/null +++ b/Configurations/ConfigBinaryDeltaDebug.xcconfig @@ -0,0 +1,5 @@ +#include "ConfigCommon.xcconfig" +#include "ConfigCommonDebug.xcconfig" +#include "ConfigBinaryDelta.xcconfig" + +OTHER_CFLAGS = -fsingle-precision-constant -DDEBUG diff --git a/Configurations/ConfigBinaryDeltaRelease.xcconfig b/Configurations/ConfigBinaryDeltaRelease.xcconfig new file mode 100644 index 000000000..cb4622597 --- /dev/null +++ b/Configurations/ConfigBinaryDeltaRelease.xcconfig @@ -0,0 +1,3 @@ +#include "ConfigCommon.xcconfig" +#include "ConfigCommonRelease.xcconfig" +#include "ConfigBinaryDelta.xcconfig" diff --git a/License.txt b/License.txt index f81c9d4f8..978193f9b 100644 --- a/License.txt +++ b/License.txt @@ -123,4 +123,31 @@ Original SSLeay License * derivative of this code cannot be changed. i.e. this code cannot simply be * copied and put under another distribution licence * [including the GNU Public Licence.] -*/ \ No newline at end of file +*/ + +License for bspatch.c and bsdiff.c, from bsdiff 4.3 (: +/*- + * Copyright 2003-2005 Colin Percival + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted providing that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + */ diff --git a/SUBinaryDeltaApply.h b/SUBinaryDeltaApply.h new file mode 100644 index 000000000..d44501093 --- /dev/null +++ b/SUBinaryDeltaApply.h @@ -0,0 +1,15 @@ +// +// SUBinaryDeltaApply.h +// Sparkle +// +// Created by Mark Rowe on 2009-06-01. +// Copyright 2009 Mark Rowe. All rights reserved. +// + +#ifndef SUBINARYDELTAAPPLY_H +#define SUBINARYDELTAAPPLY_H + +@class NSString; +int applyBinaryDelta(NSString *source, NSString *destination, NSString *patchFile); + +#endif diff --git a/SUBinaryDeltaApply.m b/SUBinaryDeltaApply.m new file mode 100644 index 000000000..285b38b57 --- /dev/null +++ b/SUBinaryDeltaApply.m @@ -0,0 +1,101 @@ +// +// SUBinaryDeltaApply.m +// Sparkle +// +// Created by Mark Rowe on 2009-06-01. +// Copyright 2009 Mark Rowe. All rights reserved. +// + +#include "SUBinaryDeltaCommon.h" +#include +#include +#include +#include +#include + +extern int bspatch(int argc, const char **argv); + +static void applyBinaryDeltaToFile(xar_t x, xar_file_t file, NSString *sourceFilePath, NSString *destinationFilePath) +{ + NSString *patchFile = temporaryFilename(@"apply-binary-delta"); + xar_extract_tofile(x, file, [patchFile fileSystemRepresentation]); + const char *argv[] = {"/usr/bin/bspatch", [sourceFilePath fileSystemRepresentation], [destinationFilePath fileSystemRepresentation], [patchFile fileSystemRepresentation]}; + bspatch(4, argv); + unlink([patchFile fileSystemRepresentation]); +} + +int applyBinaryDelta(NSString *source, NSString *destination, NSString *patchFile) +{ + xar_t x = xar_open([patchFile UTF8String], READ); + if (!x) { + fprintf(stderr, "Unable to open %s. Giving up.\n", [patchFile UTF8String]); + return 1; + } + + NSString *expectedBeforeHash = nil; + NSString *expectedAfterHash = nil; + xar_subdoc_t subdoc; + for (subdoc = xar_subdoc_first(x); subdoc; subdoc = xar_subdoc_next(subdoc)) { + if (!strcmp(xar_subdoc_name(subdoc), "binary-delta-attributes")) { + const char *value = 0; + xar_subdoc_prop_get(subdoc, "before-sha1", &value); + if (value) + expectedBeforeHash = [NSString stringWithUTF8String:value]; + + xar_subdoc_prop_get(subdoc, "after-sha1", &value); + if (value) + expectedAfterHash = [NSString stringWithUTF8String:value]; + } + } + + if (!expectedBeforeHash || !expectedAfterHash) { + fprintf(stderr, "Unable to find before-sha1 or after-sha1 metadata in delta. Giving up.\n"); + return 1; + } + + fprintf(stderr, "Verifying source... "); + NSString *beforeHash = hashOfTree(source); + + if (![beforeHash isEqualToString:expectedBeforeHash]) { + fprintf(stderr, "Source doesn't have expected hash (%s != %s). Giving up.\n", [expectedBeforeHash UTF8String], [beforeHash UTF8String]); + return 1; + } + + fprintf(stderr, "\nCopying files... "); + removeTree(destination); + copyTree(source, destination); + + fprintf(stderr, "\nPatching... "); + xar_file_t file; + xar_iter_t iter = xar_iter_new(); + for (file = xar_file_first(x, iter); file; file = xar_file_next(iter)) { + NSString *path = [NSString stringWithUTF8String:xar_get_path(file)]; + NSString *sourceFilePath = [source stringByAppendingPathComponent:path]; + NSString *destinationFilePath = [destination stringByAppendingPathComponent:path]; + + const char *value; + if (!xar_prop_get(file, "delete", &value) || !xar_prop_get(file, "delete-then-extract", &value)) { + removeTree(destinationFilePath); + if (!xar_prop_get(file, "delete", &value)) + continue; + } + + if (!xar_prop_get(file, "binary-delta", &value)) + applyBinaryDeltaToFile(x, file, sourceFilePath, destinationFilePath); + else + xar_extract_tofile(x, file, [destinationFilePath fileSystemRepresentation]); + } + xar_close(x); + + fprintf(stderr, "\nVerifying destination... "); + NSString *afterHash = hashOfTree(destination); + + if (![afterHash isEqualToString:expectedAfterHash]) { + fprintf(stderr, "Destination doesn't have expected hash (%s != %s). Giving up.\n", [expectedAfterHash UTF8String], [afterHash UTF8String]); + removeTree(destination); + return 1; + } + + fprintf(stderr, "\nDone!\n"); + return 0; +} diff --git a/SUBinaryDeltaCommon.h b/SUBinaryDeltaCommon.h new file mode 100644 index 000000000..9eb81f386 --- /dev/null +++ b/SUBinaryDeltaCommon.h @@ -0,0 +1,25 @@ +// +// SUBinaryDeltaCommon.h +// Sparkle +// +// Created by Mark Rowe on 2009-06-01. +// Copyright 2009 Mark Rowe. All rights reserved. +// + +#ifndef SUBINARYDELTACOMMON_H +#define SUBINARYDELTACOMMON_H + +#include + +@class NSString; +@class NSData; + +extern int compareFiles(const FTSENT **a, const FTSENT **b); +extern NSData *hashOfFile(FTSENT *ent); +extern NSString *hashOfTree(NSString *path); +extern void removeTree(NSString *path); +extern void copyTree(NSString *source, NSString *dest); +extern NSString *pathRelativeToDirectory(NSString *directory, NSString *path); +NSString *temporaryFilename(NSString *base); + +#endif diff --git a/SUBinaryDeltaCommon.m b/SUBinaryDeltaCommon.m new file mode 100644 index 000000000..210ecaeba --- /dev/null +++ b/SUBinaryDeltaCommon.m @@ -0,0 +1,150 @@ +// +// SUBinaryDeltaCommon.m +// Sparkle +// +// Created by Mark Rowe on 2009-06-01. +// Copyright 2009 Mark Rowe. All rights reserved. +// + +#include "SUBinaryDeltaCommon.h" +#include +#include +#include +#include +#include +#include +#include +#include + +int compareFiles(const FTSENT **a, const FTSENT **b) +{ + return strcoll((*a)->fts_name, (*b)->fts_name); +} + +NSString *pathRelativeToDirectory(NSString *directory, NSString *path) +{ + NSUInteger directoryLength = [directory length]; + if ([path hasPrefix:directory]) + return [path substringFromIndex:directoryLength]; + + return path; +} + +NSString *temporaryFilename(NSString *base) +{ + NSString *template = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.XXXXXXXXXX", base]]; + char buffer[MAXPATHLEN]; + strcpy(buffer, [template fileSystemRepresentation]); + return [NSString stringWithUTF8String:mktemp(buffer)]; +} + +static void _hashOfBuffer(unsigned char *hash, const char* buffer, size_t bufferLength) +{ + assert(bufferLength <= UINT32_MAX); + CC_SHA1_CTX hashContext; + CC_SHA1_Init(&hashContext); + CC_SHA1_Update(&hashContext, buffer, (CC_LONG)bufferLength); + CC_SHA1_Final(hash, &hashContext); +} + +static void _hashOfFile(unsigned char* hash, FTSENT *ent) +{ + if (ent->fts_info == FTS_SL) { + char linkDestination[MAXPATHLEN + 1]; + size_t linkDestinationLength = readlink(ent->fts_path, linkDestination, MAXPATHLEN); + if (linkDestinationLength < 0) { + perror("readlink"); + return; + } + + _hashOfBuffer(hash, linkDestination, linkDestinationLength); + return; + } + + if (ent->fts_info == FTS_F) { + int fileDescriptor = open(ent->fts_path, O_RDONLY); + if (fileDescriptor == -1) { + perror("open"); + return; + } + + size_t fileSize = ent->fts_statp->st_size; + void *buffer = mmap(0, fileSize, PROT_READ, MAP_FILE | MAP_PRIVATE, fileDescriptor, 0); + if (buffer == (void*)-1) { + close(fileDescriptor); + perror("mmap"); + return; + } + + _hashOfBuffer(hash, buffer, fileSize); + munmap(buffer, fileSize); + close(fileDescriptor); + return; + } + + if (ent->fts_info == FTS_D) + memset(hash, 0xdd, CC_SHA1_DIGEST_LENGTH); +} + +NSData *hashOfFile(FTSENT *ent) +{ + unsigned char fileHash[CC_SHA1_DIGEST_LENGTH]; + _hashOfFile(fileHash, ent); + return [NSData dataWithBytes:fileHash length:CC_SHA1_DIGEST_LENGTH]; +} + +NSString *hashOfTree(NSString *path) +{ + const char *sourcePaths[] = {[path UTF8String], 0}; + FTS *fts = fts_open((char* const*)sourcePaths, FTS_PHYSICAL | FTS_NOCHDIR, compareFiles); + if (!fts) { + perror("fts_open"); + return nil; + } + + CC_SHA1_CTX hashContext; + CC_SHA1_Init(&hashContext); + + FTSENT *ent = 0; + while ((ent = fts_read(fts))) { + if (ent->fts_info != FTS_F && ent->fts_info != FTS_SL) + continue; + + unsigned char fileHash[CC_SHA1_DIGEST_LENGTH]; + _hashOfFile(fileHash, ent); + CC_SHA1_Update(&hashContext, fileHash, sizeof(fileHash)); + + NSString *relativePath = pathRelativeToDirectory(path, [NSString stringWithUTF8String:ent->fts_path]); + NSData *relativePathBytes = [relativePath dataUsingEncoding:NSUTF8StringEncoding]; + CC_SHA1_Update(&hashContext, [relativePathBytes bytes], (uint32_t)[relativePathBytes length]); + } + fts_close(fts); + + unsigned char hash[CC_SHA1_DIGEST_LENGTH]; + CC_SHA1_Final(hash, &hashContext); + + char hexHash[CC_SHA1_DIGEST_LENGTH * 2 + 1]; + size_t i; + for (i = 0; i < CC_SHA1_DIGEST_LENGTH; i++) + sprintf(hexHash + i * 2, "%02x", hash[i]); + + return [NSString stringWithUTF8String:hexHash]; +} + +void removeTree(NSString *path) +{ +#if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_4 + [[NSFileManager defaultManager] removeItemAtPath:path error:0]; +#else + [[NSFileManager defaultManager] removeFileAtPath:path handler:nil]; +#endif +} + +void copyTree(NSString *source, NSString *dest) +{ +#if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_4 + [[NSFileManager defaultManager] copyItemAtPath:source toPath:dest error:0]; +#else + [[NSFileManager defaultManager] copyPath:source toPath:dest handler:nil]; +#endif +} diff --git a/SUBinaryDeltaTool.m b/SUBinaryDeltaTool.m new file mode 100644 index 000000000..20be9b5af --- /dev/null +++ b/SUBinaryDeltaTool.m @@ -0,0 +1,236 @@ +// +// SUBinaryDeltaTool.m +// Sparkle +// +// Created by Mark Rowe on 2009-06-01. +// Copyright 2009 Mark Rowe. All rights reserved. +// + +#include "SUBinaryDeltaCommon.h" +#include "SUBinaryDeltaApply.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern int bsdiff(int argc, const char **argv); + +static NSDictionary *infoForFile(FTSENT *ent) +{ + NSData *hash = hashOfFile(ent); + NSNumber *size = nil; + if (ent->fts_info != FTS_D) + size = [NSNumber numberWithUnsignedLongLong:ent->fts_statp->st_size]; + return [NSDictionary dictionaryWithObjectsAndKeys:hash, @"hash", [NSNumber numberWithUnsignedShort:ent->fts_info], @"type", size, @"size", nil]; +} + +static void addBinaryDelta(dispatch_group_t deltaGroup, dispatch_queue_t xarQueue, xar_t x, NSString *relativePath, NSString *oldBasePath, NSString *newBasePath) +{ + NSString *oldPath = [oldBasePath stringByAppendingPathComponent:relativePath]; + NSString *newPath = [newBasePath stringByAppendingPathComponent:relativePath]; + NSString *temporaryFile = temporaryFilename(@"create-binary-delta"); + + dispatch_queue_t bsdiffQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + dispatch_retain(xarQueue); + dispatch_retain(deltaGroup); + + dispatch_group_async(deltaGroup, bsdiffQueue, ^{ + const char *argv[] = {"/usr/bin/bsdiff", [oldPath fileSystemRepresentation], [newPath fileSystemRepresentation], [temporaryFile fileSystemRepresentation]}; + int result = bsdiff(4, argv); + + if (!result) { + dispatch_group_async(deltaGroup, xarQueue, ^{ + xar_file_t newFile = xar_add_frompath(x, 0, [relativePath fileSystemRepresentation], [temporaryFile fileSystemRepresentation]); + assert(newFile); + xar_prop_set(newFile, "binary-delta", "true"); + unlink([temporaryFile fileSystemRepresentation]); + }); + } + dispatch_release(xarQueue); + dispatch_release(deltaGroup); + }); +} + +static NSString *absolutePath(NSString *path) +{ + NSURL *url = [[[NSURL alloc] initFileURLWithPath:path] autorelease]; + return [[url absoluteURL] path]; +} + +static NSString *temporaryPatchFile(NSString *patchFile) +{ + NSString *path = absolutePath(patchFile); + NSString *directory = [path stringByDeletingLastPathComponent]; + NSString *file = [path lastPathComponent]; + return [NSString stringWithFormat:@"%@/.%@.tmp", directory, file]; +} + +static BOOL shouldSkipDeltaCompression(NSString *key, NSDictionary* originalInfo, NSDictionary *newInfo) +{ + size_t fileSize = [[newInfo objectForKey:@"size"] unsignedLongLongValue]; + if (fileSize < 4096) + return YES; + + if (!originalInfo) + return YES; + + if ([[originalInfo objectForKey:@"type"] unsignedShortValue] != [[newInfo objectForKey:@"type"] unsignedShortValue]) + return YES; + + return NO; +} + +static BOOL shouldDeleteThenExtract(NSString *key, NSDictionary* originalInfo, NSDictionary *newInfo) +{ + if (!originalInfo) + return NO; + + if ([[originalInfo objectForKey:@"type"] unsignedShortValue] != [[newInfo objectForKey:@"type"] unsignedShortValue]) + return YES; + + return NO; +} + +int main(int argc, char **argv) +{ + if (argc != 5) { +usage: + fprintf(stderr, "Usage: BinaryDelta [create | apply] before-tree after-tree patch-file\n"); + exit(1); + } + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + NSString *command = [NSString stringWithUTF8String:argv[1]]; + NSString *oldPath = [NSString stringWithUTF8String:argv[2]]; + NSString *newPath = [NSString stringWithUTF8String:argv[3]]; + NSString *patchFile = [NSString stringWithUTF8String:argv[4]]; + + if ([command isEqualToString:@"apply"]) + return applyBinaryDelta(oldPath, newPath, patchFile); + if (![command isEqualToString:@"create"]) + goto usage; + + NSMutableDictionary *originalTreeState = [NSMutableDictionary new]; + + const char *sourcePaths[] = {[oldPath fileSystemRepresentation], 0}; + FTS *fts = fts_open((char* const*)sourcePaths, FTS_PHYSICAL | FTS_NOCHDIR, compareFiles); + if (!fts) { + perror("fts_open"); + return 1; + } + + fprintf(stderr, "Processing %s...", [oldPath UTF8String]); + FTSENT *ent = 0; + while ((ent = fts_read(fts))) { + if (ent->fts_info != FTS_F && ent->fts_info != FTS_SL && ent->fts_info != FTS_D) + continue; + + NSString *key = pathRelativeToDirectory(oldPath, [NSString stringWithUTF8String:ent->fts_path]); + if (![key length]) + continue; + + NSDictionary *info = infoForFile(ent); + [originalTreeState setObject:info forKey:key]; + } + fts_close(fts); + + NSString *beforeHash = hashOfTree(oldPath); + + NSMutableDictionary *newTreeState = [NSMutableDictionary new]; + for (NSString *key in originalTreeState) + { + [newTreeState setObject:[NSNull null] forKey:key]; + } + + fprintf(stderr, "\nProcessing %s... ", [newPath UTF8String]); + sourcePaths[0] = [newPath fileSystemRepresentation]; + fts = fts_open((char* const*)sourcePaths, FTS_PHYSICAL | FTS_NOCHDIR, compareFiles); + if (!fts) { + perror("fts_open"); + return 1; + } + + + while ((ent = fts_read(fts))) { + if (ent->fts_info != FTS_F && ent->fts_info != FTS_SL && ent->fts_info != FTS_D) + continue; + + NSString *key = pathRelativeToDirectory(newPath, [NSString stringWithUTF8String:ent->fts_path]); + if (![key length]) + continue; + + NSDictionary *info = infoForFile(ent); + NSDictionary *oldInfo = [originalTreeState objectForKey:key]; + + if ([info isEqual:oldInfo]) + [newTreeState removeObjectForKey:key]; + else + [newTreeState setObject:info forKey:key]; + } + fts_close(fts); + + NSString *afterHash = hashOfTree(newPath); + + fprintf(stderr, "\nGenerating delta... "); + + dispatch_group_t deltaGroup = dispatch_group_create(); + dispatch_queue_t xarQueue = dispatch_queue_create("xar", 0); + + NSString *temporaryFile = temporaryPatchFile(patchFile); + __block xar_t x; + dispatch_sync(xarQueue, ^{ + x = xar_open([temporaryFile fileSystemRepresentation], WRITE); + xar_opt_set(x, XAR_OPT_COMPRESSION, "bzip2"); + xar_subdoc_t attributes = xar_subdoc_new(x, "binary-delta-attributes"); + xar_subdoc_prop_set(attributes, "before-sha1", [beforeHash UTF8String]); + xar_subdoc_prop_set(attributes, "after-sha1", [afterHash UTF8String]); + }); + + NSArray *keys = [[newTreeState allKeys] sortedArrayUsingSelector:@selector(compare:)]; + for (NSString* key in keys) { + id value = [newTreeState valueForKey:key]; + + if ([value isEqual:[NSNull null]]) { + dispatch_group_async(deltaGroup, xarQueue, ^{ + xar_file_t newFile = xar_add_frombuffer(x, 0, [key fileSystemRepresentation], "", 1); + assert(newFile); + xar_prop_set(newFile, "delete", "true"); + }); + continue; + } + + NSDictionary *originalInfo = [originalTreeState objectForKey:key]; + NSDictionary *newInfo = [newTreeState objectForKey:key]; + if (shouldSkipDeltaCompression(key, originalInfo, newInfo)) { + NSString *path = [newPath stringByAppendingPathComponent:key]; + __block BOOL deleteFirst = shouldDeleteThenExtract(key, originalInfo, newInfo); + dispatch_group_async(deltaGroup, xarQueue, ^{ + xar_file_t newFile = xar_add_frompath(x, 0, [key fileSystemRepresentation], [path fileSystemRepresentation]); + assert(newFile); + if (deleteFirst) + xar_prop_set(newFile, "delete-then-extract", "true"); + }); + } else + addBinaryDelta(deltaGroup, xarQueue, x, key, oldPath, newPath); + } + + dispatch_group_wait(deltaGroup, UINT64_MAX); + dispatch_sync(xarQueue, ^{ xar_close(x); }); + + unlink([patchFile fileSystemRepresentation]); + link([temporaryFile fileSystemRepresentation], [patchFile fileSystemRepresentation]); + unlink([temporaryFile fileSystemRepresentation]); + fprintf(stderr, "Done!\n"); + + [pool drain]; + return 0; +} diff --git a/Sparkle.xcodeproj/project.pbxproj b/Sparkle.xcodeproj/project.pbxproj index 0be4f470c..d0c0ddb1c 100644 --- a/Sparkle.xcodeproj/project.pbxproj +++ b/Sparkle.xcodeproj/project.pbxproj @@ -7,6 +7,17 @@ objects = { /* Begin PBXBuildFile section */ + 5D06E8D80FD68C8E005AE3F6 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8DC2EF5B0486A6940098B216 /* Sparkle.framework */; }; + 5D06E8E90FD68CDB005AE3F6 /* bsdiff.c in Sources */ = {isa = PBXBuildFile; fileRef = 5D06E8DB0FD68CB9005AE3F6 /* bsdiff.c */; settings = {COMPILER_FLAGS = "-Wno-shorten-64-to-32"; }; }; + 5D06E8EA0FD68CDB005AE3F6 /* SUBinaryDeltaTool.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D06E8E30FD68CC7005AE3F6 /* SUBinaryDeltaTool.m */; }; + 5D06E8EB0FD68CE4005AE3F6 /* bspatch.c in Sources */ = {isa = PBXBuildFile; fileRef = 5D06E8DC0FD68CB9005AE3F6 /* bspatch.c */; settings = {COMPILER_FLAGS = "-Wno-shorten-64-to-32"; }; }; + 5D06E8EC0FD68CE4005AE3F6 /* SUBinaryDeltaApply.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D06E8E00FD68CC7005AE3F6 /* SUBinaryDeltaApply.m */; }; + 5D06E8ED0FD68CE4005AE3F6 /* SUBinaryDeltaCommon.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D06E8E20FD68CC7005AE3F6 /* SUBinaryDeltaCommon.m */; }; + 5D06E8FD0FD68D6B005AE3F6 /* libbz2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D06E8FB0FD68D61005AE3F6 /* libbz2.dylib */; }; + 5D06E8FE0FD68D6B005AE3F6 /* libxar.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D06E8F90FD68D53005AE3F6 /* libxar.dylib */; }; + 5D06E8FF0FD68D6D005AE3F6 /* libbz2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D06E8FB0FD68D61005AE3F6 /* libbz2.dylib */; }; + 5D06E9000FD68D6D005AE3F6 /* libxar.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D06E8F90FD68D53005AE3F6 /* libxar.dylib */; }; + 5D06E9050FD68D7D005AE3F6 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0867D69BFE84028FC02AAC07 /* Foundation.framework */; }; 610134730DD250470049ACDF /* SUUpdateDriver.h in Headers */ = {isa = PBXBuildFile; fileRef = 610134710DD250470049ACDF /* SUUpdateDriver.h */; settings = {ATTRIBUTES = (); }; }; 610134740DD250470049ACDF /* SUUpdateDriver.m in Sources */ = {isa = PBXBuildFile; fileRef = 610134720DD250470049ACDF /* SUUpdateDriver.m */; }; 6101347B0DD2541A0049ACDF /* SUProbingUpdateDriver.h in Headers */ = {isa = PBXBuildFile; fileRef = 610134790DD2541A0049ACDF /* SUProbingUpdateDriver.h */; settings = {ATTRIBUTES = (); }; }; @@ -99,6 +110,13 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 5D06E8D50FD68C86005AE3F6 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8DC2EF4F0486A6940098B216 /* Sparkle */; + remoteInfo = Sparkle; + }; 61227AB90DB5C4BB00AB99EA /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; @@ -145,6 +163,19 @@ /* Begin PBXFileReference section */ 0867D69BFE84028FC02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; 0867D6A5FE840307C02AAC07 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; }; + 5D06E8D00FD68C7C005AE3F6 /* BinaryDelta */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = BinaryDelta; sourceTree = BUILT_PRODUCTS_DIR; }; + 5D06E8DB0FD68CB9005AE3F6 /* bsdiff.c */ = {isa = PBXFileReference; comments = "-Wno-shorten-64-to-32"; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = bsdiff.c; sourceTree = ""; }; + 5D06E8DC0FD68CB9005AE3F6 /* bspatch.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = bspatch.c; sourceTree = ""; }; + 5D06E8DF0FD68CC7005AE3F6 /* SUBinaryDeltaApply.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SUBinaryDeltaApply.h; sourceTree = ""; }; + 5D06E8E00FD68CC7005AE3F6 /* SUBinaryDeltaApply.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SUBinaryDeltaApply.m; sourceTree = ""; }; + 5D06E8E10FD68CC7005AE3F6 /* SUBinaryDeltaCommon.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SUBinaryDeltaCommon.h; sourceTree = ""; }; + 5D06E8E20FD68CC7005AE3F6 /* SUBinaryDeltaCommon.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SUBinaryDeltaCommon.m; sourceTree = ""; }; + 5D06E8E30FD68CC7005AE3F6 /* SUBinaryDeltaTool.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SUBinaryDeltaTool.m; sourceTree = ""; }; + 5D06E8F10FD68D21005AE3F6 /* ConfigBinaryDelta.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = ConfigBinaryDelta.xcconfig; sourceTree = ""; }; + 5D06E8F20FD68D21005AE3F6 /* ConfigBinaryDeltaDebug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = ConfigBinaryDeltaDebug.xcconfig; sourceTree = ""; }; + 5D06E8F30FD68D21005AE3F6 /* ConfigBinaryDeltaRelease.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = ConfigBinaryDeltaRelease.xcconfig; sourceTree = ""; }; + 5D06E8F90FD68D53005AE3F6 /* libxar.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libxar.dylib; path = /usr/lib/libxar.dylib; sourceTree = ""; }; + 5D06E8FB0FD68D61005AE3F6 /* libbz2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libbz2.dylib; path = /usr/lib/libbz2.dylib; sourceTree = ""; }; 610134710DD250470049ACDF /* SUUpdateDriver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SUUpdateDriver.h; sourceTree = ""; }; 610134720DD250470049ACDF /* SUUpdateDriver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SUUpdateDriver.m; sourceTree = ""; }; 610134790DD2541A0049ACDF /* SUProbingUpdateDriver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SUProbingUpdateDriver.h; sourceTree = ""; }; @@ -291,6 +322,17 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 5D06E8CE0FD68C7C005AE3F6 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5D06E8D80FD68C8E005AE3F6 /* Sparkle.framework in Frameworks */, + 5D06E8FF0FD68D6D005AE3F6 /* libbz2.dylib in Frameworks */, + 5D06E9000FD68D6D005AE3F6 /* libxar.dylib in Frameworks */, + 5D06E9050FD68D7D005AE3F6 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 612279D60DB5470200AB99EA /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -319,6 +361,8 @@ 61177A1F0D1112E900749C97 /* IOKit.framework in Frameworks */, FAEFA2F70D94AA7500472538 /* Foundation.framework in Frameworks */, FAEFA2F80D94AA7900472538 /* AppKit.framework in Frameworks */, + 5D06E8FD0FD68D6B005AE3F6 /* libbz2.dylib in Frameworks */, + 5D06E8FE0FD68D6B005AE3F6 /* libxar.dylib in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -340,6 +384,7 @@ 61B5F90209C4CEE200B25A18 /* Sparkle Test App.app */, DAAEFC960DA571DF0051E0D0 /* relaunch */, 612279D90DB5470200AB99EA /* Sparkle Unit Tests.octest */, + 5D06E8D00FD68C7C005AE3F6 /* BinaryDelta */, ); name = Products; sourceTree = ""; @@ -371,6 +416,8 @@ 61B5F8F609C4CEB300B25A18 /* Security.framework */, 0867D6A5FE840307C02AAC07 /* AppKit.framework */, 0867D69BFE84028FC02AAC07 /* Foundation.framework */, + 5D06E8FB0FD68D61005AE3F6 /* libbz2.dylib */, + 5D06E8F90FD68D53005AE3F6 /* libxar.dylib */, ); name = "Apple Frameworks and Libraries"; sourceTree = ""; @@ -390,9 +437,24 @@ name = "Framework Resources"; sourceTree = ""; }; + 5D06E8D90FD68C95005AE3F6 /* Binary Delta */ = { + isa = PBXGroup; + children = ( + 5D06E8DB0FD68CB9005AE3F6 /* bsdiff.c */, + 5D06E8DC0FD68CB9005AE3F6 /* bspatch.c */, + 5D06E8DF0FD68CC7005AE3F6 /* SUBinaryDeltaApply.h */, + 5D06E8E00FD68CC7005AE3F6 /* SUBinaryDeltaApply.m */, + 5D06E8E10FD68CC7005AE3F6 /* SUBinaryDeltaCommon.h */, + 5D06E8E20FD68CC7005AE3F6 /* SUBinaryDeltaCommon.m */, + 5D06E8E30FD68CC7005AE3F6 /* SUBinaryDeltaTool.m */, + ); + name = "Binary Delta"; + sourceTree = ""; + }; 6101354A0DD25B7F0049ACDF /* Unarchiving */ = { isa = PBXGroup; children = ( + 5D06E8D90FD68C95005AE3F6 /* Binary Delta */, 61299A8B09CA790200B7442F /* SUUnarchiver.h */, 61299A8C09CA790200B7442F /* SUUnarchiver.m */, 6102FE590E08C7EC00F85D09 /* SUUnarchiver_Private.h */, @@ -545,6 +607,9 @@ FA1941C40D94A6EA00DD942E /* Configurations */ = { isa = PBXGroup; children = ( + 5D06E8F10FD68D21005AE3F6 /* ConfigBinaryDelta.xcconfig */, + 5D06E8F20FD68D21005AE3F6 /* ConfigBinaryDeltaDebug.xcconfig */, + 5D06E8F30FD68D21005AE3F6 /* ConfigBinaryDeltaRelease.xcconfig */, FA1941D00D94A70100DD942E /* ConfigCommon.xcconfig */, FA1941CF0D94A70100DD942E /* ConfigCommonDebug.xcconfig */, FA1941CC0D94A70100DD942E /* ConfigCommonRelease.xcconfig */, @@ -608,6 +673,24 @@ /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ + 5D06E8CF0FD68C7C005AE3F6 /* BinaryDelta */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5D06E8DA0FD68C95005AE3F6 /* Build configuration list for PBXNativeTarget "BinaryDelta" */; + buildPhases = ( + 5D06E8CD0FD68C7C005AE3F6 /* Sources */, + 5D06E8CE0FD68C7C005AE3F6 /* Frameworks */, + 5D06E90D0FD68DA3005AE3F6 /* Fix Install Name */, + ); + buildRules = ( + ); + dependencies = ( + 5D06E8D60FD68C86005AE3F6 /* PBXTargetDependency */, + ); + name = BinaryDelta; + productName = BinaryDelta; + productReference = 5D06E8D00FD68C7C005AE3F6 /* BinaryDelta */; + productType = "com.apple.product-type.tool"; + }; 612279D80DB5470200AB99EA /* Sparkle Unit Tests */ = { isa = PBXNativeTarget; buildConfigurationList = 612279DD0DB5470300AB99EA /* Build configuration list for PBXNativeTarget "Sparkle Unit Tests" */; @@ -736,6 +819,7 @@ 61B5F90109C4CEE200B25A18 /* Sparkle Test App */, DAAEFC950DA571DF0051E0D0 /* relaunch tool */, 612279D80DB5470200AB99EA /* Sparkle Unit Tests */, + 5D06E8CF0FD68C7C005AE3F6 /* BinaryDelta */, ); }; /* End PBXProject section */ @@ -777,6 +861,21 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 5D06E90D0FD68DA3005AE3F6 /* Fix Install Name */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Fix Install Name"; + outputPaths = ( + "$(CONFIGURATION_BUILD_DIR)/BinaryDelta", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "install_name_tool -change \"@loader_path/../Frameworks/Sparkle.framework/Versions/A/Sparkle\" \"@loader_path/Sparkle.framework/Versions/A/Sparkle\" \"${CONFIGURATION_BUILD_DIR}/BinaryDelta\""; + }; 612279D70DB5470200AB99EA /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -823,6 +922,15 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 5D06E8CD0FD68C7C005AE3F6 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5D06E8E90FD68CDB005AE3F6 /* bsdiff.c in Sources */, + 5D06E8EA0FD68CDB005AE3F6 /* SUBinaryDeltaTool.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 612279D50DB5470200AB99EA /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -872,6 +980,9 @@ 6102FE5C0E08C7EC00F85D09 /* SUUnarchiver_Private.m in Sources */, 61D85D6D0E10B2ED00F9B4A9 /* SUPipedUnarchiver.m in Sources */, 61EF67560E25B58D00F754E0 /* SUHost.m in Sources */, + 5D06E8EB0FD68CE4005AE3F6 /* bspatch.c in Sources */, + 5D06E8EC0FD68CE4005AE3F6 /* SUBinaryDeltaApply.m in Sources */, + 5D06E8ED0FD68CE4005AE3F6 /* SUBinaryDeltaCommon.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -886,6 +997,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 5D06E8D60FD68C86005AE3F6 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 8DC2EF4F0486A6940098B216 /* Sparkle */; + targetProxy = 5D06E8D50FD68C86005AE3F6 /* PBXContainerItemProxy */; + }; 61227ABA0DB5C4BB00AB99EA /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 612279D80DB5470200AB99EA /* Sparkle Unit Tests */; @@ -1033,6 +1149,27 @@ }; name = Release; }; + 5D06E8D20FD68C7D005AE3F6 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5D06E8F20FD68D21005AE3F6 /* ConfigBinaryDeltaDebug.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + 5D06E8D30FD68C7D005AE3F6 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5D06E8F30FD68D21005AE3F6 /* ConfigBinaryDeltaRelease.xcconfig */; + buildSettings = { + }; + name = Release; + }; + 5D06E8D40FD68C7D005AE3F6 /* Release (GC dual-mode; 10.5-only) */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5D06E8F30FD68D21005AE3F6 /* ConfigBinaryDeltaRelease.xcconfig */; + buildSettings = { + }; + name = "Release (GC dual-mode; 10.5-only)"; + }; 61072EAD0DF263BD008FE88B /* Release (GC dual-mode; 10.5-only) */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1199,6 +1336,16 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 5D06E8DA0FD68C95005AE3F6 /* Build configuration list for PBXNativeTarget "BinaryDelta" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5D06E8D20FD68C7D005AE3F6 /* Debug */, + 5D06E8D30FD68C7D005AE3F6 /* Release */, + 5D06E8D40FD68C7D005AE3F6 /* Release (GC dual-mode; 10.5-only) */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 612279DD0DB5470300AB99EA /* Build configuration list for PBXNativeTarget "Sparkle Unit Tests" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/bsdiff.c b/bsdiff.c new file mode 100644 index 000000000..408182007 --- /dev/null +++ b/bsdiff.c @@ -0,0 +1,522 @@ +/*- + * Copyright 2003 - 2005 Colin Percival + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted providing that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + */ + +#if 0 +__FBSDID("$FreeBSD: src/usr.bin/bsdiff/bsdiff/bsdiff.c, v 1.1 2005/08/06 01:59:05 cperciva Exp $"); +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include + +#define MIN(x, y) (((x)<(y)) ? (x) : (y)) + +static void split(off_t *I, off_t *V, off_t start, off_t len, off_t h) +{ + off_t i, j, k, x, tmp, jj, kk; + + if (len < 16) { + for (k = start; k < start + len; k += j) { + j = 1; x = V[I[k] + h]; + for (i = 1; k + i < start + len; i++) { + if (V[I[k + i] + h] < x) { + x = V[I[k + i] + h]; + j = 0; + }; + if (V[I[k + i] + h] == x) { + tmp = I[k + j]; I[k + j] = I[k + i]; I[k + i] = tmp; + j++; + }; + }; + for (i = 0; i < j; i++) + V[I[k + i]] = k + j - 1; + if (j == 1) + I[k] = -1; + }; + return; + }; + + x = V[I[start + len/2] + h]; + jj = 0; kk = 0; + for (i = start; i < start + len; i++) { + if (V[I[i] + h] < x) + jj++; + if (V[I[i] + h] == x) + kk++; + }; + jj += start; kk += jj; + + i = start; j = 0; k = 0; + while (i < jj) { + if (V[I[i] + h] < x) { + i++; + } else if (V[I[i] + h] == x) { + tmp = I[i]; I[i] = I[jj + j]; I[jj + j] = tmp; + j++; + } else { + tmp = I[i]; I[i] = I[kk + k]; I[kk + k] = tmp; + k++; + }; + }; + + while (jj + j < kk) { + if (V[I[jj + j] + h] == x) { + j++; + } else { + tmp = I[jj + j]; I[jj + j] = I[kk + k]; I[kk + k] = tmp; + k++; + }; + }; + + if (jj > start) + split(I, V, start, jj - start, h); + + for (i = 0; i < kk - jj; i++) + V[I[jj + i]] = kk - 1; + if (jj == kk - 1) + I[jj] = -1; + + if (start + len > kk) + split(I, V, kk, start + len - kk, h); +} + +/* qsufsort(I, V, old, oldsize) + * + * Computes the suffix sort of the string at 'old' and stores the resulting + * indices in 'I', using 'V' as a temporary array for the computation. */ +static void qsufsort(off_t *I, off_t *V, u_char *old, off_t oldsize) +{ + off_t buckets[256]; + off_t i, h, len; + + /* count number of each byte */ + for (i = 0; i < 256; i++) + buckets[i] = 0; + for (i = 0; i < oldsize; i++) + buckets[old[i]]++; + /* make buckets cumulative */ + for (i = 1; i < 256; i++) + buckets[i] += buckets[i - 1]; + /* shift right by one */ + for (i = 255; i > 0; i--) + buckets[i] = buckets[i - 1]; + buckets[0] = 0; + /* at this point, buckets[c] is the number of bytes in the old file with + * value less than c. */ + + /* set up the sort order of the suffixes based solely on the first + * character */ + for (i = 0; i < oldsize; i++) + I[++buckets[old[i]]] = i; + I[0] = oldsize; + /* ? */ + for (i = 0; i < oldsize; i++) + V[i] = buckets[old[i]]; + V[oldsize] = 0; + /* forward any entries in the ordering which have the same initial + * character */ + for (i = 1; i < 256; i++) { + if (buckets[i] == buckets[i - 1] + 1) + I[buckets[i]] = -1; + } + I[0] = -1; + + for (h = 1; I[0] != -(oldsize + 1); h += h) { + len = 0; + for (i = 0; i < oldsize + 1;) { + if (I[i] < 0) { + len -= I[i]; + i -= I[i]; + } else { + if (len) + I[i - len] = -len; + len = V[I[i]] + 1 - i; + split(I, V, i, len, h); + i += len; + len = 0; + } + } + if (len) + I[i - len] = -len; + }; + + for (i = 0; i < oldsize + 1; i++) I[V[i]] = i; +} + +/* matchlen(old, oldsize, new, newsize) + * + * Returns the length of the longest common prefix between 'old' and 'new'. */ +static off_t matchlen(u_char *old, off_t oldsize, u_char *new, off_t newsize) +{ + off_t i; + + for (i = 0; (i < oldsize) && (i < newsize); i++) + { + if (old[i] != new[i]) + break; + } + + return i; +} + +/* search(I, old, oldsize, new, newsize, st, en, pos) + * + * Searches for the longest prefix of 'new' that occurs in 'old', stores its + * offset in '*pos', and returns its length. 'I' should be the suffix sort of + * 'old', and 'st' and 'en' are the lowest and highest indices in the suffix + * sort to consider. If you're searching all suffixes, 'st = 0' and 'en = + * oldsize - 1'. */ +static off_t search(off_t *I, u_char *old, off_t oldsize, + u_char *new, off_t newsize, off_t st, off_t en, off_t *pos) +{ + off_t x, y; + + if (en - st < 2) { + x = matchlen(old + I[st], oldsize - I[st], new, newsize); + y = matchlen(old + I[en], oldsize - I[en], new, newsize); + + if (x > y) { + *pos = I[st]; + return x; + } else { + *pos = I[en]; + return y; + } + } + + x = st + (en - st)/2; + if (memcmp(old + I[x], new, MIN(oldsize - I[x], newsize)) < 0) { + return search(I, old, oldsize, new, newsize, x, en, pos); + } else { + return search(I, old, oldsize, new, newsize, st, x, pos); + }; +} + +/* offtout(x, buf) + * + * Writes the off_t 'x' portably to the array 'buf'. */ +static void offtout(off_t x, u_char *buf) +{ + off_t y; + + if (x < 0) + y = -x; + else + y = x; + + buf[0] = y % 256; + y -= buf[0]; + y = y/256; buf[1] = y%256; y -= buf[1]; + y = y/256; buf[2] = y%256; y -= buf[2]; + y = y/256; buf[3] = y%256; y -= buf[3]; + y = y/256; buf[4] = y%256; y -= buf[4]; + y = y/256; buf[5] = y%256; y -= buf[5]; + y = y/256; buf[6] = y%256; y -= buf[6]; + y = y/256; buf[7] = y%256; + + if (x < 0) + buf[7] |= 0x80; +} + +int bsdiff(int argc, char *argv[]) +{ + int fd; + u_char *old,*new; /* contents of old, new files */ + off_t oldsize, newsize; /* length of old, new files */ + off_t *I,*V; /* arrays used for suffix sort; I is ordering */ + off_t scan; /* position of current match in old file */ + off_t pos; /* position of current match in new file */ + off_t len; /* length of current match */ + off_t lastscan; /* position of previous match in old file */ + off_t lastpos; /* position of previous match in new file */ + off_t lastoffset; /* lastpos - lastscan */ + off_t oldscore, scsc; /* temp variables in match search */ + off_t s, Sf, lenf, Sb, lenb; /* temp vars in match extension */ + off_t overlap, Ss, lens; + off_t i; + off_t dblen, eblen; /* length of diff, extra sections */ + u_char *db,*eb; /* contents of diff, extra sections */ + u_char buf[8]; + u_char header[32]; + FILE * pf; + BZFILE * pfbz2; + int bz2err; + + if (argc != 4) + errx(1,"usage: %s oldfile newfile patchfile\n", argv[0]); + + /* Allocate oldsize + 1 bytes instead of oldsize bytes to ensure + that we never try to malloc(0) and get a NULL pointer */ + if (((fd = open(argv[1], O_RDONLY, 0)) < 0) || + ((oldsize = lseek(fd, 0, SEEK_END)) == -1) || + ((old = malloc(oldsize + 1)) == NULL) || + (lseek(fd, 0, SEEK_SET) != 0) || + (read(fd, old, oldsize) != oldsize) || + (close(fd) == -1)) + err(1,"%s", argv[1]); + + if (((I = malloc((oldsize + 1) * sizeof(off_t))) == NULL) || + ((V = malloc((oldsize + 1) * sizeof(off_t))) == NULL)) + err(1, NULL); + + /* Do a suffix sort on the old file. */ + qsufsort(I, V, old, oldsize); + + free(V); + + /* Allocate newsize + 1 bytes instead of newsize bytes to ensure + that we never try to malloc(0) and get a NULL pointer */ + if (((fd = open(argv[2], O_RDONLY, 0)) < 0) || + ((newsize = lseek(fd, 0, SEEK_END)) == -1) || + ((new = malloc(newsize + 1)) == NULL) || + (lseek(fd, 0, SEEK_SET) != 0) || + (read(fd, new, newsize) != newsize) || + (close(fd) == -1)) + err(1,"%s", argv[2]); + + if (((db = malloc(newsize + 1)) == NULL) || + ((eb = malloc(newsize + 1)) == NULL)) + err(1, NULL); + dblen = 0; + eblen = 0; + + /* Create the patch file */ + if ((pf = fopen(argv[3], "w")) == NULL) + err(1, "%s", argv[3]); + + /* Header is + 0 8 "BSDIFF40" + 8 8 length of bzip2ed ctrl block + 16 8 length of bzip2ed diff block + 24 8 length of new file */ + /* File is + 0 32 Header + 32 ?? Bzip2ed ctrl block + ?? ?? Bzip2ed diff block + ?? ?? Bzip2ed extra block */ + memcpy(header, "BSDIFF40", 8); + offtout(0, header + 8); + offtout(0, header + 16); + offtout(newsize, header + 24); + if (fwrite(header, 32, 1, pf) != 1) + err(1, "fwrite(%s)", argv[3]); + + /* Compute the differences, writing ctrl as we go */ + if ((pfbz2 = BZ2_bzWriteOpen(&bz2err, pf, 9, 0, 0)) == NULL) + errx(1, "BZ2_bzWriteOpen, bz2err = %d", bz2err); + scan = 0; + len = 0; + lastscan = 0; + lastpos = 0; + lastoffset = 0; + while (scan < newsize) { + oldscore = 0; + + for (scsc = scan += len; scan < newsize; scan++) { + /* 'oldscore' is the number of characters that match between the + * substrings 'old[lastoffset + scan:lastoffset + scsc]' and + * 'new[scan:scsc]'. */ + len = search(I, old, oldsize, new + scan, newsize - scan, + 0, oldsize, &pos); + + /* If this match extends further than the last one, add any new + * matching characters to 'oldscore'. */ + for (; scsc < scan + len; scsc++) { + if ((scsc + lastoffset < oldsize) && + (old[scsc + lastoffset] == new[scsc])) + oldscore++; + } + + /* Choose this as our match if it contains more than eight + * characters that would be wrong if matched with a forward + * extension of the previous match instead. */ + if (((len == oldscore) && (len != 0)) || + (len > oldscore + 8)) + break; + + /* Since we're advancing 'scan' by 1, remove the character under it + * from 'oldscore' if it matches. */ + if ((scan + lastoffset < oldsize) && + (old[scan + lastoffset] == new[scan])) + oldscore--; + } + + /* Skip this section if we found an exact match that would be + * better serviced by a forward extension of the previous match. */ + if ((len != oldscore) || (scan == newsize)) { + /* Figure out how far forward the previous match should be + * extended... */ + s = 0; + Sf = 0; + lenf = 0; + for (i = 0; (lastscan + i < scan) && (lastpos + i < oldsize);) { + if (old[lastpos + i] == new[lastscan + i]) + s++; + i++; + if (s * 2 - i > Sf * 2 - lenf) { + Sf = s; + lenf = i; + } + } + + /* ... and how far backwards the next match should be extended. */ + lenb = 0; + if (scan < newsize) { + s = 0; + Sb = 0; + for (i = 1; (scan >= lastscan + i) && (pos >= i); i++) { + if (old[pos - i] == new[scan - i]) + s++; + if (s * 2 - i > Sb * 2 - lenb) { + Sb = s; + lenb = i; + } + } + } + + /* If there is an overlap between the extensions, find the best + * dividing point in the middle and reset 'lenf' and 'lenb' + * accordingly. */ + if (lastscan + lenf > scan - lenb) { + overlap = (lastscan + lenf) - (scan - lenb); + s = 0; + Ss = 0; + lens = 0; + for (i = 0; i < overlap; i++) { + if (new[lastscan + lenf - overlap + i] == + old[lastpos + lenf - overlap + i]) + s++; + if (new[scan - lenb + i] == old[pos - lenb + i]) + s--; + if (s > Ss) { + Ss = s; + lens = i + 1; + } + } + + lenf += lens - overlap; + lenb -= lens; + } + + /* Write the diff data for the last match to the diff section... */ + for (i = 0; i < lenf; i++) + db[dblen + i] = new[lastscan + i] - old[lastpos + i]; + /* ... and, if there's a gap between the extensions just + * calculated, write the data in that gap to the extra section. */ + for (i = 0; i< (scan - lenb) - (lastscan + lenf); i++) + eb[eblen + i] = new[lastscan + lenf + i]; + + /* Update the diff and extra section lengths accordingly. */ + dblen += lenf; + eblen += (scan - lenb) - (lastscan + lenf); + + /* Write the following triple of integers to the control section: + * - length of the diff + * - length of the extra section + * - offset between the end of the diff and the start of the next + * diff, in the old file + */ + offtout(lenf, buf); + BZ2_bzWrite(&bz2err, pfbz2, buf, 8); + if (bz2err != BZ_OK) + errx(1, "BZ2_bzWrite, bz2err = %d", bz2err); + + offtout((scan - lenb) - (lastscan + lenf), buf); + BZ2_bzWrite(&bz2err, pfbz2, buf, 8); + if (bz2err != BZ_OK) + errx(1, "BZ2_bzWrite, bz2err = %d", bz2err); + + offtout((pos - lenb) - (lastpos + lenf), buf); + BZ2_bzWrite(&bz2err, pfbz2, buf, 8); + if (bz2err != BZ_OK) + errx(1, "BZ2_bzWrite, bz2err = %d", bz2err); + + /* Update the variables describing the last match. Note that + * 'lastscan' is set to the start of the current match _after_ the + * backwards extension; the data in that extension will be written + * in the next pass. */ + lastscan = scan - lenb; + lastpos = pos - lenb; + lastoffset = pos - scan; + } + } + BZ2_bzWriteClose(&bz2err, pfbz2, 0, NULL, NULL); + if (bz2err != BZ_OK) + errx(1, "BZ2_bzWriteClose, bz2err = %d", bz2err); + + /* Compute size of compressed ctrl data */ + if ((len = ftello(pf)) == -1) + err(1, "ftello"); + offtout(len - 32, header + 8); + + /* Write compressed diff data */ + if ((pfbz2 = BZ2_bzWriteOpen(&bz2err, pf, 9, 0, 0)) == NULL) + errx(1, "BZ2_bzWriteOpen, bz2err = %d", bz2err); + BZ2_bzWrite(&bz2err, pfbz2, db, dblen); + if (bz2err != BZ_OK) + errx(1, "BZ2_bzWrite, bz2err = %d", bz2err); + BZ2_bzWriteClose(&bz2err, pfbz2, 0, NULL, NULL); + if (bz2err != BZ_OK) + errx(1, "BZ2_bzWriteClose, bz2err = %d", bz2err); + + /* Compute size of compressed diff data */ + if ((newsize = ftello(pf)) == -1) + err(1, "ftello"); + offtout(newsize - len, header + 16); + + /* Write compressed extra data */ + if ((pfbz2 = BZ2_bzWriteOpen(&bz2err, pf, 9, 0, 0)) == NULL) + errx(1, "BZ2_bzWriteOpen, bz2err = %d", bz2err); + BZ2_bzWrite(&bz2err, pfbz2, eb, eblen); + if (bz2err != BZ_OK) + errx(1, "BZ2_bzWrite, bz2err = %d", bz2err); + BZ2_bzWriteClose(&bz2err, pfbz2, 0, NULL, NULL); + if (bz2err != BZ_OK) + errx(1, "BZ2_bzWriteClose, bz2err = %d", bz2err); + + /* Seek to the beginning, write the header, and close the file */ + if (fseeko(pf, 0, SEEK_SET)) + err(1, "fseeko"); + if (fwrite(header, 32, 1, pf) != 1) + err(1, "fwrite(%s)", argv[3]); + if (fclose(pf)) + err(1, "fclose"); + + /* Free the memory we used */ + free(db); + free(eb); + free(I); + free(old); + free(new); + + return 0; +} diff --git a/bspatch.c b/bspatch.c new file mode 100644 index 000000000..643817b2b --- /dev/null +++ b/bspatch.c @@ -0,0 +1,208 @@ +/*- + * Copyright 2003-2005 Colin Percival + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted providing that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + */ + +#if 0 +__FBSDID("$FreeBSD: src/usr.bin/bsdiff/bspatch/bspatch.c,v 1.1 2005/08/06 01:59:06 cperciva Exp $"); +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifndef u_char +typedef unsigned char u_char; +#endif + +static off_t offtin(u_char *buf) +{ + off_t y; + + y=buf[7]&0x7F; + y=y*256;y+=buf[6]; + y=y*256;y+=buf[5]; + y=y*256;y+=buf[4]; + y=y*256;y+=buf[3]; + y=y*256;y+=buf[2]; + y=y*256;y+=buf[1]; + y=y*256;y+=buf[0]; + + if(buf[7]&0x80) y=-y; + + return y; +} + +int bspatch(int argc,char * argv[]) +{ + FILE * f, * cpf, * dpf, * epf; + BZFILE * cpfbz2, * dpfbz2, * epfbz2; + int cbz2err, dbz2err, ebz2err; + int fd; + ssize_t oldsize,newsize; + ssize_t bzctrllen,bzdatalen; + u_char header[32],buf[8]; + u_char *old, *new; + off_t oldpos,newpos; + off_t ctrl[3]; + off_t lenread; + off_t i; + + if(argc!=4) errx(1,"usage: %s oldfile newfile patchfile\n",argv[0]); + + /* Open patch file */ + if ((f = fopen(argv[3], "r")) == NULL) + err(1, "fopen(%s)", argv[3]); + + /* + File format: + 0 8 "BSDIFF40" + 8 8 X + 16 8 Y + 24 8 sizeof(newfile) + 32 X bzip2(control block) + 32+X Y bzip2(diff block) + 32+X+Y ??? bzip2(extra block) + with control block a set of triples (x,y,z) meaning "add x bytes + from oldfile to x bytes from the diff block; copy y bytes from the + extra block; seek forwards in oldfile by z bytes". + */ + + /* Read header */ + if (fread(header, 1, 32, f) < 32) { + if (feof(f)) + errx(1, "Corrupt patch\n"); + err(1, "fread(%s)", argv[3]); + } + + /* Check for appropriate magic */ + if (memcmp(header, "BSDIFF40", 8) != 0) + errx(1, "Corrupt patch\n"); + + /* Read lengths from header */ + bzctrllen=offtin(header+8); + bzdatalen=offtin(header+16); + newsize=offtin(header+24); + if((bzctrllen<0) || (bzdatalen<0) || (newsize<0)) + errx(1,"Corrupt patch\n"); + + /* Close patch file and re-open it via libbzip2 at the right places */ + if (fclose(f)) + err(1, "fclose(%s)", argv[3]); + if ((cpf = fopen(argv[3], "r")) == NULL) + err(1, "fopen(%s)", argv[3]); + if (fseeko(cpf, 32, SEEK_SET)) + err(1, "fseeko(%s, %lld)", argv[3], + (long long)32); + if ((cpfbz2 = BZ2_bzReadOpen(&cbz2err, cpf, 0, 0, NULL, 0)) == NULL) + errx(1, "BZ2_bzReadOpen, bz2err = %d", cbz2err); + if ((dpf = fopen(argv[3], "r")) == NULL) + err(1, "fopen(%s)", argv[3]); + if (fseeko(dpf, 32 + bzctrllen, SEEK_SET)) + err(1, "fseeko(%s, %lld)", argv[3], + (long long)(32 + bzctrllen)); + if ((dpfbz2 = BZ2_bzReadOpen(&dbz2err, dpf, 0, 0, NULL, 0)) == NULL) + errx(1, "BZ2_bzReadOpen, bz2err = %d", dbz2err); + if ((epf = fopen(argv[3], "r")) == NULL) + err(1, "fopen(%s)", argv[3]); + if (fseeko(epf, 32 + bzctrllen + bzdatalen, SEEK_SET)) + err(1, "fseeko(%s, %lld)", argv[3], + (long long)(32 + bzctrllen + bzdatalen)); + if ((epfbz2 = BZ2_bzReadOpen(&ebz2err, epf, 0, 0, NULL, 0)) == NULL) + errx(1, "BZ2_bzReadOpen, bz2err = %d", ebz2err); + + if(((fd=open(argv[1],O_RDONLY,0))<0) || + ((oldsize=lseek(fd,0,SEEK_END))==-1) || + ((old=malloc(oldsize+1))==NULL) || + (lseek(fd,0,SEEK_SET)!=0) || + (read(fd,old,oldsize)!=oldsize) || + (close(fd)==-1)) err(1,"%s",argv[1]); + if((new=malloc(newsize+1))==NULL) err(1,NULL); + + oldpos=0;newpos=0; + while(newposnewsize) + errx(1,"Corrupt patch\n"); + + /* Read diff string */ + lenread = BZ2_bzRead(&dbz2err, dpfbz2, new + newpos, ctrl[0]); + if ((lenread < ctrl[0]) || + ((dbz2err != BZ_OK) && (dbz2err != BZ_STREAM_END))) + errx(1, "Corrupt patch\n"); + + /* Add old data to diff string */ + for(i=0;i=0) && (oldpos+inewsize) + errx(1,"Corrupt patch\n"); + + /* Read extra string */ + lenread = BZ2_bzRead(&ebz2err, epfbz2, new + newpos, ctrl[1]); + if ((lenread < ctrl[1]) || + ((ebz2err != BZ_OK) && (ebz2err != BZ_STREAM_END))) + errx(1, "Corrupt patch\n"); + + /* Adjust pointers */ + newpos+=ctrl[1]; + oldpos+=ctrl[2]; + }; + + /* Clean up the bzip2 reads */ + BZ2_bzReadClose(&cbz2err, cpfbz2); + BZ2_bzReadClose(&dbz2err, dpfbz2); + BZ2_bzReadClose(&ebz2err, epfbz2); + if (fclose(cpf) || fclose(dpf) || fclose(epf)) + err(1, "fclose(%s)", argv[3]); + + /* Write the new file */ + if(((fd=open(argv[2],O_CREAT|O_TRUNC|O_WRONLY,0666))<0) || + (write(fd,new,newsize)!=newsize) || (close(fd)==-1)) + err(1,"%s",argv[2]); + + free(new); + free(old); + + return 0; +}