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;
+}