diff options
Diffstat (limited to 'xdmain.c')
-rwxr-xr-x | xdmain.c | 1941 |
1 files changed, 1941 insertions, 0 deletions
diff --git a/xdmain.c b/xdmain.c new file mode 100755 index 0000000..b1abc74 --- /dev/null +++ b/xdmain.c @@ -0,0 +1,1941 @@ +/* -*- Mode: C;-*- + * + * This file is part of XDelta - A binary delta generator. + * + * Copyright (C) 1997, 1998, 1999, 2001 Josh MacDonald + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Author: Josh MacDonald <jmacd@CS.Berkeley.EDU> + * + * $Id: xdmain.c 1.22.1.7.1.1 Sat, 27 Jan 2007 17:53:47 -0800 jmacd $ + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> + +#include <fcntl.h> +#include <sys/stat.h> + +#if defined(_WIN32) || defined(__DJGPP__) +#define WINHACK +#endif + +#ifndef WINHACK +#include <unistd.h> +#include <sys/mman.h> +#define O_BINARY 0 + +#else /* WINHACK */ + +#include <io.h> +#include <process.h> +#ifdef __DJGPP__ +#include <unistd.h> +#include <dpmi.h> +#endif + +#define STDOUT_FILENO 1 +#define lstat stat + +#ifndef __DJGPP__ +#define S_IFMT _S_IFMT +#define S_IFREG _S_IFREG +#endif /* !__DJGPP__ */ + +#endif /* !WINHACK_ */ + +#include <zlib.h> + +#include "xdelta.h" + +static HandleFuncTable xd_handle_table; + +#define XD_PAGE_SIZE (1<<20) + +#define XDELTA_110_PREFIX "%XDZ004%" +#define XDELTA_104_PREFIX "%XDZ003%" +#define XDELTA_100_PREFIX "%XDZ002%" +#define XDELTA_020_PREFIX "%XDZ001%" +#define XDELTA_018_PREFIX "%XDZ000%" +#define XDELTA_014_PREFIX "%XDELTA%" +#define XDELTA_PREFIX XDELTA_110_PREFIX +#define XDELTA_PREFIX_LEN 8 + +#define HEADER_WORDS (6) +#define HEADER_SPACE (HEADER_WORDS*4) +/* The header is composed of 4-byte words (network byte order) as follows: + * word 1: flags + * word 2: (from name length) << 16 | (to name length) + * word 3: (reserved) + * word 4: (reserved) + * word 5: (reserved) + * word 6: (reserved) + * flags are: + */ +#define FLAG_NO_VERIFY 1 +#define FLAG_FROM_COMPRESSED 2 +#define FLAG_TO_COMPRESSED 4 +#define FLAG_PATCH_COMPRESSED 8 +/* and, the header is follwed by the from file name, then the to file + * name, then the data. */ + +#ifdef WINHACK +#define FOPEN_READ_ARG "rb" +#define FOPEN_WRITE_ARG "wb" +#define FILE_SEPARATOR '\\' +#else +#define FOPEN_READ_ARG "r" +#define FOPEN_WRITE_ARG "w" +#define FILE_SEPARATOR '/' +#endif /* WINHACK */ + +#include "getopt.h" + +typedef struct _LRU LRU; + +struct _LRU +{ + LRU *next; + LRU *prev; + + gint refs; + guint page; + guint8* buffer; +}; + +typedef struct _XdFileHandle XdFileHandle; + +typedef struct { + gboolean patch_is_compressed; + const gchar* patch_name; + guint patch_flags; + const gchar* patch_version; + gboolean has_trailer; + + XdeltaSourceInfo* data_source; + XdeltaSourceInfo* from_source; + + gchar *from_name; + gchar *to_name; + + guint control_offset; + guint header_offset; + + gint16 from_name_len; + gint16 to_name_len; + + guint32 header_space[HEADER_WORDS]; + guint8 magic_buf[XDELTA_PREFIX_LEN]; + + XdFileHandle *patch_in; + + XdeltaControl *cont; +} XdeltaPatch; + +struct _XdFileHandle +{ + FileHandle fh; + + guint length; + guint real_length; + gint type; + const char* name; + const char* cleanup; + + guint8 md5[16]; + EdsioMD5Ctx ctx; + + /* for write */ + int out_fd; + void* out; + gboolean (* out_write) (XdFileHandle* handle, const void* buf, gint nbyte); + gboolean (* out_close) (XdFileHandle* handle); + + /* for read */ + GPtrArray *lru_table; + LRU *lru_head; /* most recently used. */ + LRU *lru_tail; /* least recently used. */ + GMemChunk *lru_chunk; + guint lru_count; + guint lru_outstanding_refs; + + guint narrow_low; + guint narrow_high; + guint current_pos; + FILE* in; + gboolean (* in_read) (XdFileHandle* handle, void* buf, gint nbyte); + gboolean (* in_close) (XdFileHandle* handle); + gboolean in_compressed; + + const guint8* copy_page; + guint copy_pgno; + gboolean md5_good; + gboolean reset_length_next_write; + + gint md5_page; + gint fd; +}; + +/* $Format: "static const char xdelta_version[] = \"$ReleaseVersion$\"; " $ */ +static const char xdelta_version[] = "1.1.4"; + +typedef struct _Command Command; + +struct _Command { + gchar* name; + gint (* func) (gint argc, gchar** argv); + gint nargs; +}; + +static gint delta_command (gint argc, gchar** argv); +static gint patch_command (gint argc, gchar** argv); +static gint info_command (gint argc, gchar** argv); + +static const Command commands[] = +{ + { "delta", delta_command, -1 }, + { "patch", patch_command, -1 }, + { "info", info_command, 1 }, + { NULL, NULL, 0 } +}; + +static struct option const long_options[] = +{ + {"help", no_argument, 0, 'h'}, + {"version", no_argument, 0, 'v'}, + {"verbose", no_argument, 0, 'V'}, + {"noverify", no_argument, 0, 'n'}, + {"pristine", no_argument, 0, 'p'}, + {"quiet", no_argument, 0, 'q'}, + {"maxmem", required_argument, 0, 'm'}, + {"blocksize", required_argument, 0, 's'}, + {0,0,0,0} +}; + +static const gchar* program_name; +static gint compress_level = Z_DEFAULT_COMPRESSION; +static gint no_verify = FALSE; +static gint pristine = FALSE; +static gint verbose = FALSE; +static gint max_mapped_pages = G_MAXINT; +static gint quiet = FALSE; + +#define xd_error g_warning + +static void +usage () +{ + xd_error ("usage: %s COMMAND [OPTIONS] [ARG1 ...]\n", program_name); + xd_error ("use --help for more help\n"); + exit (2); +} + +static void +help () +{ + xd_error ("usage: %s COMMAND [OPTIONS] [ARG1 ...]\n", program_name); + xd_error ("COMMAND is one of:\n"); + xd_error (" delta Produce a delta from ARG1 to ARG2 producing ARG3\n"); + xd_error (" info List details about delta ARG1\n"); + xd_error (" patch Apply patch ARG1 using file ARG2 producing ARG3\n"); + xd_error ("OPTIONS are:\n"); + xd_error (" -v, --version Print version information\n"); + xd_error (" -V, --verbose Print verbose error messages\n"); + xd_error (" -h, --help Print this summary\n"); + xd_error (" -n, --noverify Disable automatic MD5 verification\n"); + xd_error (" -p, --pristine Disable automatic GZIP decompression\n"); + xd_error (" -m, --maxmem=SIZE Set the buffer size limit, e.g. 640K, 16M\n"); + xd_error (" -[0-9] ZLIB compress level: 0=none, 1=fast, 6=default, 9=best\n"); + xd_error (" -s=BLOCK_SIZE Sets block size (power of 2), minimum match length\n"); + xd_error (" In-core memory requirement is (FROM_LEN * 8) / BLOCK_SIZE\n"); + exit (2); +} + +static void +version () +{ + xd_error ("version %s\n", xdelta_version); + exit (2); +} + +static FILE* xd_error_file = NULL; + +static void +xd_error_func (const gchar *log_domain, + GLogLevelFlags log_level, + const gchar *message, + gpointer user_data) +{ + if (! xd_error_file) + xd_error_file = stderr; + + fprintf (xd_error_file, "%s: %s", program_name, message); +} + +static gboolean +event_devel (void) +{ + static gboolean once = FALSE; + static gboolean devel = FALSE; + + if (! once) + { + devel = g_getenv ("EDSIO_DEVEL") != NULL; + once = TRUE; + } + + return devel; +} + +static gboolean +event_watch (GenericEvent* ev, GenericEventDef* def, const char* message) +{ + if (quiet && def->level <= EL_Warning) + return TRUE; + + if (event_devel ()) + fprintf (stderr, "%s:%d: %s\n", ev->srcfile, ev->srcline, message); + else + fprintf (stderr, "%s: %s\n", program_name, message); + + return TRUE; +} + +gint +main (gint argc, gchar** argv) +{ + const Command *cmd = NULL; + gint c; + gint longind; + + eventdelivery_event_watch_all (event_watch); + + if (! xd_edsio_init ()) + return 2; + +#ifdef __DJGPP__ + /* + * (richdawe@bigfoot.com): Limit maximum memory usage to 87.5% of available + * physical memory for DJGPP. Otherwise the replacement for mmap() will + * exhaust memory and XDelta will fail. + */ + { + unsigned long phys_free = _go32_dpmi_remaining_physical_memory(); + + max_mapped_pages = phys_free / XD_PAGE_SIZE; + max_mapped_pages %= 8; + max_mapped_pages *= 7; + } + + /* + * (richdawe@bigfoot.com): Strip off the file extension 'exe' from + * program_name, since it makes the help messages look ugly. + */ + { + char strip_ext[PATH_MAX + 1]; + char *p; + + strcpy (strip_ext, argv[0]); + p = strrchr (strip_ext, '.'); + if ((p != NULL) && (strncasecmp (p + 1, "exe", 3) == 0)) + *p = '\0'; + program_name = g_basename (strip_ext); + } +#else /* !__DJGPP__ */ + program_name = g_basename (argv[0]); +#endif /* __DJGPP__ */ + + g_log_set_handler (G_LOG_DOMAIN, + G_LOG_LEVEL_WARNING, + xd_error_func, + NULL); + + if (argc < 2) + usage (); + + for (cmd = commands; cmd->name; cmd += 1) + if (strcmp (cmd->name, argv[1]) == 0) + break; + + if (strcmp (argv[1], "-h") == 0 || + strcmp (argv[1], "--help") == 0) + help (); + + if (strcmp (argv[1], "-v") == 0 || + strcmp (argv[1], "--version") == 0) + version (); + + if (!cmd->name) + { + xd_error ("unrecognized command\n"); + help (); + } + + argc -= 1; + argv += 1; + + while ((c = getopt_long(argc, + argv, + "+nqphvVs:m:0123456789", + long_options, + &longind)) != EOF) + { + switch (c) + { + case 'q': quiet = TRUE; break; + case 'n': no_verify = TRUE; break; + case 'p': pristine = TRUE; break; + case 'V': verbose = TRUE; break; + case 's': + { + int ret; + int s = atoi (optarg); + + if ((ret = xdp_set_query_size_pow (s)) && ! quiet) + { + xd_error ("illegal query size: %s\n", xdp_errno (ret)); + } + } + break; + case 'm': + { + gchar* end = NULL; + glong l = strtol (optarg, &end, 0); + + if (end && g_strcasecmp (end, "M") == 0) + l <<= 20; + else if (end && g_strcasecmp (end, "K") == 0) + l <<= 10; + else if (end || l < 0) + { + xd_error ("illegal maxmem argument %s\n", optarg); + return 2; + } + +#ifdef __DJGPP__ + /* + * (richdawe@bigfoot.com): Old MS-DOS systems may have a maximum + * of 8MB memory == XD_PAGE_SIZE * 8. Therefore, do what the user + * asks for on MS-DOS. + */ +#else /* !__DJGPP__ */ + l = MAX (l, XD_PAGE_SIZE * 8); +#endif /* __DJGPP__ */ + + max_mapped_pages = l / XD_PAGE_SIZE; + } + break; + case 'h': help (); break; + case 'v': version (); break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + compress_level = c - '0'; + break; + case '?': + default: + xd_error ("illegal argument, use --help for help\n"); + return 2; + } + } + + if (verbose && max_mapped_pages < G_MAXINT) + xd_error ("using %d kilobytes of buffer space\n", (max_mapped_pages * XD_PAGE_SIZE) >> 10); + + argc -= optind; + argv += optind; + + if (cmd->nargs >= 0 && argc != cmd->nargs) + { + xd_error ("wrong number of arguments\n"); + help (); + return 2; + } + + return (* cmd->func) (argc, argv); +} + +/* Commands */ + +#define READ_TYPE 1 +#define READ_NOSEEK_TYPE 1 +#define READ_SEEK_TYPE 3 +#define WRITE_TYPE 4 + +/* Note to the casual reader: the filehandle implemented here is + * highly aware of the calling patterns of the Xdelta library. It + * also plays games with narrowing to implement several files in the + * same handle. So, if you try to modify or use this code, BEWARE. + * See the repository package for a complete implementation. + * In fact, its really quite a hack. */ + +static gssize xd_handle_map_page (XdFileHandle *fh, guint pgno, const guint8** mem); +static gboolean xd_handle_unmap_page (XdFileHandle *fh, guint pgno, const guint8** mem); + +static gboolean +xd_fwrite (XdFileHandle* fh, const void* buf, gint nbyte) +{ + return fwrite (buf, nbyte, 1, fh->out) == 1; +} + +static gboolean +xd_fread (XdFileHandle* fh, void* buf, gint nbyte) +{ + return fread (buf, nbyte, 1, fh->in) == 1; +} + +static gboolean +xd_fclose (XdFileHandle* fh) +{ + return fclose (fh->out) == 0; +} + +static gboolean +xd_frclose (XdFileHandle* fh) +{ + return fclose (fh->in) == 0; +} + +static gboolean +xd_gzwrite (XdFileHandle* fh, const void* buf, gint nbyte) +{ + return gzwrite (fh->out, (void*) buf, nbyte) == nbyte; +} + +static gboolean +xd_gzread (XdFileHandle* fh, void* buf, gint nbyte) +{ + return gzread (fh->in, buf, nbyte) == nbyte; +} + +static gboolean +xd_gzclose (XdFileHandle* fh) +{ + return gzclose (fh->out) == Z_OK; +} + +static gboolean +xd_gzrclose (XdFileHandle* fh) +{ + return gzclose (fh->in) == Z_OK; +} + +static void +init_table (XdFileHandle* fh) +{ + fh->lru_table = g_ptr_array_new (); + fh->lru_chunk = g_mem_chunk_create(LRU, 1<<9, G_ALLOC_ONLY); + fh->lru_head = NULL; + fh->lru_tail = NULL; +} + +static XdFileHandle* +open_common (const char* name, const char* real_name) +{ + XdFileHandle* fh; + + gint fd; + struct stat buf; + + if ((fd = open (name, O_RDONLY | O_BINARY, 0)) < 0) + { + xd_error ("open %s failed: %s\n", name, g_strerror (errno)); + return NULL; + } + + if (stat (name, &buf) < 0) + { + xd_error ("stat %s failed: %s\n", name, g_strerror (errno)); + return NULL; + } + + /* S_ISREG() is not on Windows */ + if ((buf.st_mode & S_IFMT) != S_IFREG) + { + xd_error ("%s is not a regular file\n", name); + return NULL; + } + + fh = g_new0 (XdFileHandle, 1); + + fh->fh.table = & xd_handle_table; + fh->name = real_name; + fh->fd = fd; + fh->length = buf.st_size; + fh->narrow_high = buf.st_size; + + return fh; +} + +static gboolean +file_gzipped (const char* name, gboolean *is_compressed) +{ + FILE* f = fopen (name, FOPEN_READ_ARG); + guint8 buf[2]; + + (*is_compressed) = FALSE; + + if (! f) + { + xd_error ("open %s failed: %s\n", name, g_strerror (errno)); + return FALSE; + } + + if (fread (buf, 2, 1, f) != 1) + return TRUE; + +#define GZIP_MAGIC1 037 +#define GZIP_MAGIC2 0213 + + if (buf[0] == GZIP_MAGIC1 && buf[1] == GZIP_MAGIC2) + (* is_compressed) = TRUE; + + return TRUE; +} + +static const char* +xd_tmpname (void) +{ + const char* tmpdir = g_get_tmp_dir (); + GString* s; + gint x = getpid (); + static gint seq = 0; + struct stat buf; + + s = g_string_new (NULL); + + do + { + /* + * (richdawe@bigfoot.com): Limit temporary filenames to + * the MS-DOS 8+3 convention for DJGPP. + */ + g_string_sprintf (s, "%s/xd-%05d.%03d", tmpdir, x, seq++); + } + while (lstat (s->str, &buf) == 0); + + return s->str; +} + +static const char* +file_gunzip (const char* name) +{ + const char* new_name = xd_tmpname (); + FILE* out = fopen (new_name, FOPEN_WRITE_ARG); + gzFile in = gzopen (name, "rb"); + guint8 buf[1024]; + int nread; + + while ((nread = gzread (in, buf, 1024)) > 0) + { + if (fwrite (buf, nread, 1, out) != 1) + { + xd_error ("write %s failed (during uncompression): %s\n", new_name, g_strerror (errno)); + return NULL; + } + } + + if (nread < 0) + { + xd_error ("gzread %s failed\n", name); + return NULL; + } + + gzclose (in); + + if (fclose (out)) + { + xd_error ("close %s failed (during uncompression): %s\n", new_name, g_strerror (errno)); + return NULL; + } + + return new_name; +} + +static XdFileHandle* +open_read_noseek_handle (const char* name, gboolean* is_compressed, gboolean will_read, gboolean honor_pristine) +{ + XdFileHandle* fh; + const char* name0 = name; + + /* we _could_ stream-read this file if compressed, but it adds a + * lot of complexity. the library can handle it, just set the + * length to (XDELTA_MAX_FILE_LEN-1) and make sure that the end + * of file condition is set when on the last page. However, I + * don't feel like it. */ + if (honor_pristine && pristine) + *is_compressed = FALSE; + else + { + if (! file_gzipped (name, is_compressed)) + return NULL; + } + + if ((* is_compressed) && ! (name = file_gunzip (name))) + return NULL; + + if (! (fh = open_common (name, name0))) + return NULL; + + fh->type = READ_NOSEEK_TYPE; + + edsio_md5_init (&fh->ctx); + + if (*is_compressed) + fh->cleanup = name; + + if (will_read) + { + g_assert (fh->fd >= 0); + if (! (fh->in = fdopen (dup (fh->fd), FOPEN_READ_ARG))) + { + xd_error ("fdopen: %s\n", g_strerror (errno)); + return NULL; + } + fh->in_read = &xd_fread; + fh->in_close = &xd_frclose; + } + else + { + init_table (fh); + } + + return fh; +} + +static void +xd_read_close (XdFileHandle* fh) +{ + /* + * (richdawe@bigfoot.com): On Unix you can unlink a file while it is + * still open. On MS-DOS this can lead to filesystem corruption. Close + * the file before unlinking it. + */ + close (fh->fd); + + if (fh->cleanup) + unlink (fh->cleanup); + + if (fh->in) + (*fh->in_close) (fh); +} + +static XdFileHandle* +open_read_seek_handle (const char* name, gboolean* is_compressed, gboolean honor_pristine) +{ + XdFileHandle* fh; + const char* name0 = name; + + if (honor_pristine && pristine) + *is_compressed = FALSE; + else + { + if (! file_gzipped (name, is_compressed)) + return NULL; + } + + if ((* is_compressed) && ! (name = file_gunzip (name))) + return NULL; + + if (! (fh = open_common (name, name0))) + return NULL; + + fh->type = READ_SEEK_TYPE; + + if (*is_compressed) + fh->cleanup = name; + + init_table (fh); + + edsio_md5_init (&fh->ctx); + + return fh; +} + +static XdFileHandle* +open_write_handle (int fd, const char* name) +{ + XdFileHandle* fh = g_new0 (XdFileHandle, 1); + int nfd; + + fh->fh.table = & xd_handle_table; + fh->out_fd = fd; + fh->out_write = &xd_fwrite; + fh->out_close = &xd_fclose; + + g_assert (fh->out_fd >= 0); + + nfd = dup (fh->out_fd); + + if (! (fh->out = fdopen (nfd, FOPEN_WRITE_ARG))) + { + xd_error ("fdopen %s failed: %s\n", name, g_strerror (errno)); + return NULL; + } + + fh->type = WRITE_TYPE; + fh->name = name; + + edsio_md5_init (&fh->ctx); + + return fh; +} + +static gint +xd_begin_compression (XdFileHandle* fh) +{ + gint filepos, nfd; + + if (compress_level == 0) + return fh->real_length; + + if (! (fh->out_close) (fh)) + { + xd_error ("fclose failed: %s\n", g_strerror (errno)); + return -1; + } + + filepos = lseek (fh->out_fd, 0, SEEK_END); + + if (filepos < 0) + { + xd_error ("lseek failed: %s\n", g_strerror (errno)); + return -1; + } + + g_assert (fh->out_fd >= 0); + + nfd = dup (fh->out_fd); + + fh->out = gzdopen (nfd, "wb"); + fh->out_write = &xd_gzwrite; + fh->out_close = &xd_gzclose; + + if (! fh->out) + { + xd_error ("gzdopen failed: %s\n", g_strerror (errno)); + return -1; + } + + if (gzsetparams(fh->out, compress_level, Z_DEFAULT_STRATEGY) != Z_OK) + { + int foo; + xd_error ("gzsetparams failed: %s\n", gzerror (fh->out, &foo)); + return -1; + } + + return filepos; +} + +static gboolean +xd_end_compression (XdFileHandle* fh) +{ + if (compress_level == 0) + return TRUE; + + if (! (fh->out_close) (fh)) + { + xd_error ("fdclose failed: %s\n", g_strerror (errno)); + return FALSE; + } + + if (lseek (fh->out_fd, 0, SEEK_END) < 0) + { + xd_error ("lseek failed: %s\n", g_strerror (errno)); + return FALSE; + } + + g_assert (fh->out_fd >= 0); + fh->out = fdopen (dup (fh->out_fd), FOPEN_WRITE_ARG); + fh->out_write = &xd_fwrite; + fh->out_close = &xd_fclose; + + if (! fh->out) + { + xd_error ("fdopen failed: %s\n", g_strerror (errno)); + return FALSE; + } + + return TRUE; +} + +static gssize +xd_handle_length (XdFileHandle *fh) +{ + if (fh->in_compressed) + return fh->current_pos; + else + return fh->narrow_high - fh->narrow_low; +} + +static gssize +xd_handle_pages (XdFileHandle *fh) +{ + g_assert (fh->type & READ_TYPE); + return xd_handle_length (fh) / XD_PAGE_SIZE; +} + +static gssize +xd_handle_pagesize (XdFileHandle *fh) +{ + g_assert (fh->type & READ_TYPE); + return XD_PAGE_SIZE; +} + +static gint +on_page (XdFileHandle* fh, guint pgno) +{ + if (pgno > xd_handle_pages (fh)) + return -1; + + if (pgno == xd_handle_pages (fh)) + return xd_handle_length (fh) % XD_PAGE_SIZE; + + return XD_PAGE_SIZE; +} + +static gboolean +xd_handle_close (XdFileHandle *fh, gint ignore) +{ + /* this is really a reset for writable files */ + + if (fh->type == WRITE_TYPE) + { + if (fh->reset_length_next_write) + { + fh->reset_length_next_write = FALSE; + fh->length = 0; + fh->narrow_high = 0; + } + + fh->reset_length_next_write = TRUE; + edsio_md5_final (fh->md5, &fh->ctx); + edsio_md5_init (&fh->ctx); + } + else if (fh->in) + { + edsio_md5_final (fh->md5, &fh->ctx); + edsio_md5_init (&fh->ctx); + fh->md5_good = FALSE; + } + + return TRUE; +} + +static const guint8* +xd_handle_checksum_md5 (XdFileHandle *fh) +{ + if (fh->in && ! fh->md5_good) + { + edsio_md5_final (fh->md5, &fh->ctx); + fh->md5_good = TRUE; + } + else if (fh->type != WRITE_TYPE && !fh->in) + { + const guint8* page; + + while (fh->md5_page <= xd_handle_pages (fh)) + { + gint pgno = fh->md5_page; + gint onpage; + + if ((onpage = xd_handle_map_page (fh, pgno, &page)) < 0) + return NULL; + + if (pgno == fh->md5_page) + { + fh->md5_page += 1; + edsio_md5_update (&fh->ctx, page, onpage); + + if (fh->md5_page > xd_handle_pages (fh)) + edsio_md5_final (fh->md5, &fh->ctx); + } + + if (! xd_handle_unmap_page (fh, pgno, &page)) + return NULL; + } + } + + return g_memdup (fh->md5, 16); +} + +static gboolean +xd_handle_set_pos (XdFileHandle *fh, guint pos) +{ + if (fh->current_pos == pos + fh->narrow_low) + return TRUE; + + if (pos + fh->narrow_low > fh->narrow_high) + { + xd_error ("unexpected EOF in %s\n", fh->name); + return FALSE; + } + + fh->current_pos = pos + fh->narrow_low; + + if (fseek (fh->in, fh->current_pos, SEEK_SET)) + { + xd_error ("fseek failed: %s\n", g_strerror (errno)); + return FALSE; + } + + return TRUE; +} + +static gboolean +xd_handle_narrow (XdFileHandle* fh, guint low, guint high, gboolean compressed) +{ + if (high > fh->length) + { + xd_error ("%s: corrupt or truncated delta\n", fh->name); + return FALSE; + } + + fh->narrow_low = low; + fh->narrow_high = high; + + edsio_md5_init (&fh->ctx); + + if (compressed) + { + (* fh->in_close) (fh); + + if (lseek (fh->fd, low, SEEK_SET) < 0) + { + xd_error ("%s: corrupt or truncated delta: cannot seek to %d: %s\n", fh->name, low, g_strerror (errno)); + return FALSE; + } + + g_assert (fh->fd >= 0); + fh->in = gzdopen (dup (fh->fd), "rb"); + fh->in_read = &xd_gzread; + fh->in_close = &xd_gzrclose; + fh->in_compressed = TRUE; + fh->current_pos = 0; + + if (! fh->in) + { + xd_error ("gzdopen failed: %s\n", g_strerror (errno)); + return -1; + } + } + else + { + if (! xd_handle_set_pos (fh, 0)) + return FALSE; + } + + return TRUE; +} + +static guint +xd_handle_get_pos (XdFileHandle* fh) +{ + return fh->current_pos - fh->narrow_low; +} + +static const gchar* +xd_handle_name (XdFileHandle *fh) +{ + return g_strdup (fh->name); +} + +static gssize +xd_handle_read (XdFileHandle *fh, guint8 *buf, gsize nbyte) +{ + if (nbyte == 0) + return 0; + + if (! (fh->in_read) (fh, buf, nbyte)) /* This is suspicious */ + { + xd_error ("read failed: %s\n", g_strerror (errno)); + return -1; + } + + if (!no_verify) + edsio_md5_update (&fh->ctx, buf, nbyte); + + fh->current_pos += nbyte; + + return nbyte; +} + +static gboolean +xd_handle_write (XdFileHandle *fh, const guint8 *buf, gsize nbyte) +{ + g_assert (fh->type == WRITE_TYPE); + + if (fh->reset_length_next_write) + { + fh->reset_length_next_write = FALSE; + fh->length = 0; + fh->narrow_high = 0; + } + + if (! no_verify) + edsio_md5_update (&fh->ctx, buf, nbyte); + + if (! (*fh->out_write) (fh, buf, nbyte)) + { + xd_error ("write failed: %s\n", g_strerror (errno)); + return FALSE; + } + + fh->length += nbyte; + fh->real_length += nbyte; + fh->narrow_high += nbyte; + + return TRUE; +} + +static gboolean +xd_handle_really_close (XdFileHandle *fh) +{ + g_assert (fh->type == WRITE_TYPE); + + if (! (* fh->out_close) (fh) || close (fh->out_fd) < 0) + { + xd_error ("write failed: %s\n", g_strerror (errno)); + return FALSE; + } + + return TRUE; +} + +static LRU* +pull_lru (XdFileHandle* fh, LRU* lru) +{ + if (lru->next && lru->prev) + { + lru->next->prev = lru->prev; + lru->prev->next = lru->next; + } + else if (lru->next) + { + fh->lru_tail = lru->next; + lru->next->prev = NULL; + } + else if (lru->prev) + { + fh->lru_head = lru->prev; + lru->prev->next = NULL; + } + else + { + fh->lru_head = NULL; + fh->lru_tail = NULL; + } + + lru->next = NULL; + lru->prev = NULL; + + return lru; +} + +static gboolean +really_free_one_page (XdFileHandle* fh) +{ + LRU *lru = fh->lru_tail; + + for (; lru; lru = lru->prev) + { + gint to_unmap; + LRU *lru_dead; + + if (lru->refs > 0) + continue; + + lru_dead = pull_lru (fh, lru); + + g_assert (lru_dead->buffer); + + to_unmap = on_page (fh, lru_dead->page); + + fh->lru_count -= 1; + + if (to_unmap > 0) + { +#ifdef WINHACK + g_free (lru_dead->buffer); +#else + if (munmap (lru_dead->buffer, to_unmap)) + { + xd_error ("munmap failed: %s\n", g_strerror (errno)); + return FALSE; + } +#endif /* WINHACK */ + } + + lru_dead->buffer = NULL; + + return TRUE; + } + + return TRUE; +} + +#if 0 +static void +print_lru (XdFileHandle* fh) +{ + LRU* lru = fh->lru_head; + + for (; lru; lru = lru->prev) + { + g_print ("page %d buffer %p\n", lru->page, lru->buffer); + + if (! lru->prev && lru != fh->lru_tail) + g_print ("incorrect lru_tail\n"); + } +} +#endif + +static gboolean +make_lru_room (XdFileHandle* fh) +{ + if (fh->lru_count == max_mapped_pages) + { + if (! really_free_one_page (fh)) + return FALSE; + } + + g_assert (fh->lru_count < max_mapped_pages); + + return TRUE; +} + +/*#define DEBUG_MAP*/ + +static gssize +xd_handle_map_page (XdFileHandle *fh, guint pgno, const guint8** mem) +{ + LRU* lru; + guint to_map; + +#ifdef DEBUG_MAP + g_print ("map %p:%d\n", fh, pgno); +#endif + + g_assert (fh->type & READ_TYPE); + + if (fh->lru_table->len < (pgno + 1)) + { + gint olen = fh->lru_table->len; + + g_ptr_array_set_size (fh->lru_table, pgno + 1); + + while (olen <= pgno) + fh->lru_table->pdata[olen++] = NULL; + } + + lru = fh->lru_table->pdata[pgno]; + + if (! lru) + { + lru = g_chunk_new0 (LRU, fh->lru_chunk); + fh->lru_table->pdata[pgno] = lru; + lru->page = pgno; + } + else if (lru->buffer) + { + pull_lru (fh, lru); + } + + lru->prev = fh->lru_head; + lru->next = NULL; + + fh->lru_head = lru; + + if (lru->prev) + lru->prev->next = lru; + + if (! fh->lru_tail) + fh->lru_tail = lru; + + to_map = on_page (fh, pgno); + + if (to_map < 0) + { + xd_error ("unexpected EOF in %s\n", fh->name); + return -1; + } + + if (! lru->buffer) + { + if (! make_lru_room (fh)) + return -1; + + fh->lru_count += 1; + + if (to_map > 0) + { +#ifdef WINHACK + lru->buffer = g_malloc (to_map); + + if (lseek (fh->fd, pgno * XD_PAGE_SIZE, SEEK_SET) < 0) + { + xd_error ("lseek failed: %s\n", g_strerror (errno)); + return -1; + } + + if (read (fh->fd, lru->buffer, to_map) != to_map) + { + xd_error ("read failed: %s\n", g_strerror (errno)); + return -1; + } +#else + if (! (lru->buffer = mmap (NULL, to_map, PROT_READ, MAP_PRIVATE, fh->fd, pgno * XD_PAGE_SIZE))) + { + xd_error ("mmap failed: %s\n", g_strerror (errno)); + return -1; + } +#endif + } + else + { + lru->buffer = (void*) -1; + } + + if (pgno == fh->md5_page) + { + if (! no_verify) + edsio_md5_update (&fh->ctx, lru->buffer, to_map); + fh->md5_page += 1; + + if (fh->md5_page > xd_handle_pages (fh)) + edsio_md5_final (fh->md5, &fh->ctx); + } + } + + (*mem) = lru->buffer; + + lru->refs += 1; + fh->lru_outstanding_refs += 1; + + return to_map; +} + +static gboolean +xd_handle_unmap_page (XdFileHandle *fh, guint pgno, const guint8** mem) +{ + LRU* lru; + +#ifdef DEBUG_MAP + g_print ("unmap %p:%d\n", fh, pgno); +#endif + + g_assert (fh->type & READ_TYPE); + + g_assert (pgno < fh->lru_table->len); + + lru = fh->lru_table->pdata[pgno]; + + g_assert (lru && lru->refs > 0); + + g_assert (lru->buffer == (*mem)); + + (*mem) = NULL; + + lru->refs -= 1; + fh->lru_outstanding_refs += 1; + + if (lru->refs == 0 && fh->type == READ_NOSEEK_TYPE) + { + pull_lru (fh, lru); + + lru->next = fh->lru_tail; + if (lru->next) lru->next->prev = lru; + lru->prev = NULL; + fh->lru_tail = lru; + + if (! really_free_one_page (fh)) + return FALSE; + } + + return TRUE; +} + +static gboolean +xd_handle_copy (XdFileHandle *from, XdFileHandle *to, guint off, guint len) +{ + if (from->in) + { + guint8 buf[1024]; + + /*if (! xd_handle_set_pos (from, off)) + return FALSE;*/ + + while (len > 0) + { + guint r = MIN (1024, len); + + if (xd_handle_read (from, buf, r) != r) + return FALSE; + + if (! xd_handle_write (to, buf, r)) + return FALSE; + + len -= r; + } + } + else + { + while (len > 0) + { + guint off_page = off / XD_PAGE_SIZE; + guint off_off = off % XD_PAGE_SIZE; + + gint on = on_page (from, off_page); + guint rem; + guint copy; + + if (on <= 0) + { + xd_error ("unexpected EOF in %s\n", from->name); + return FALSE; + } + + rem = on - off_off; + copy = MIN (len, rem); + + if (from->copy_pgno != off_page && + from->copy_page && + ! xd_handle_unmap_page (from, from->copy_pgno, &from->copy_page)) + return FALSE; + + from->copy_pgno = off_page; + + if (xd_handle_map_page (from, off_page, &from->copy_page) < 0) + return FALSE; + + if (! xd_handle_write (to, from->copy_page + off_off, copy)) + return FALSE; + + if (! xd_handle_unmap_page (from, off_page, &from->copy_page)) + return FALSE; + + len -= copy; + off += copy; + } + } + + return TRUE; +} + +static gboolean +xd_handle_putui (XdFileHandle *fh, guint32 i) +{ + guint32 hi = g_htonl (i); + + return xd_handle_write (fh, (guint8*)&hi, 4); +} + +static gboolean +xd_handle_getui (XdFileHandle *fh, guint32* i) +{ + if (xd_handle_read (fh, (guint8*)i, 4) != 4) + return FALSE; + + *i = g_ntohl (*i); + + return TRUE; +} + +static HandleFuncTable xd_handle_table = +{ + (gssize (*) (FileHandle *fh)) xd_handle_length, + (gssize (*) (FileHandle *fh)) xd_handle_pages, + (gssize (*) (FileHandle *fh)) xd_handle_pagesize, + (gssize (*) (FileHandle *fh, guint pgno, const guint8** mem)) xd_handle_map_page, + (gboolean (*) (FileHandle *fh, guint pgno, const guint8** mem)) xd_handle_unmap_page, + (const guint8* (*) (FileHandle *fh)) xd_handle_checksum_md5, + + (gboolean (*) (FileHandle *fh, gint flags)) xd_handle_close, + + (gboolean (*) (FileHandle *fh, const guint8 *buf, gsize nbyte)) xd_handle_write, + (gboolean (*) (FileHandle *from, FileHandle *to, guint off, guint len)) xd_handle_copy, + + (gboolean (*) (FileHandle *fh, guint32* i)) xd_handle_getui, + (gboolean (*) (FileHandle *fh, guint32 i)) xd_handle_putui, + (gssize (*) (FileHandle *fh, guint8 *buf, gsize nbyte)) xd_handle_read, + (const gchar* (*) (FileHandle *fh)) xd_handle_name, +}; + +static void +htonl_array (guint32* array, gint len) +{ + gint i; + + for (i = 0; i < len; i += 1) + array[i] = g_htonl(array[i]); +} + +static void +ntohl_array (guint32* array, gint len) +{ + gint i; + + for (i = 0; i < len; i += 1) + array[i] = g_ntohl(array[i]); +} + +static gint +delta_command (gint argc, gchar** argv) +{ + gint patch_out_fd; + const char* patch_out_name; + XdFileHandle *from, *to, *out; + XdeltaGenerator* gen; + XdeltaSource* src; + XdeltaControl* cont; + gboolean from_is_compressed = FALSE, to_is_compressed = FALSE; + guint32 control_offset, header_offset; + const char* from_name, *to_name; + guint32 header_space[HEADER_WORDS]; + int fd; + + memset (header_space, 0, sizeof (header_space)); + + if (argc != 3) + { + xd_error ("usage: %s delta fromfile tofile patchfile\n", program_name); + return 2; + } + + if (verbose) + { + xd_error ("using block size: %d bytes\n", xdp_blocksize ()); + } + + + if (! (from = open_read_seek_handle (argv[0], &from_is_compressed, TRUE))) + return 2; + + if (! (to = open_read_noseek_handle (argv[1], &to_is_compressed, FALSE, TRUE))) + return 2; + + // Note: I tried support to patches to stdout, but it broke when + // compression was added. Sigh + fd = open (argv[2], O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666); + + if (fd < 0) + { + xd_error ("open %s failed: %s\n", argv[2], g_strerror (errno)); + return 2; + } + + patch_out_fd = fd; + patch_out_name = argv[2]; + + from_name = g_basename (argv[0]); + to_name = g_basename (argv[1]); + + if (! (out = open_write_handle (patch_out_fd, patch_out_name))) + return 2; + + if (! (gen = xdp_generator_new ())) + return 2; + + if (! (src = xdp_source_new (from_name, (FileHandle*) from, NULL, NULL))) + return 2; + + xdp_source_add (gen, src); + + if (! xd_handle_write (out, XDELTA_PREFIX, XDELTA_PREFIX_LEN)) + return 2; + + /* compute the header */ + header_space[0] = 0; + + if (no_verify) header_space[0] |= FLAG_NO_VERIFY; + if (from_is_compressed) header_space[0] |= FLAG_FROM_COMPRESSED; + if (to_is_compressed) header_space[0] |= FLAG_TO_COMPRESSED; + if (compress_level != 0) header_space[0] |= FLAG_PATCH_COMPRESSED; + + header_space[1] = strlen (from_name) << 16 | strlen (to_name); + /* end compute the header */ + + htonl_array (header_space, HEADER_WORDS); + + if (! xd_handle_write (out, (guint8*) header_space, HEADER_SPACE)) + return 2; + + if (! xd_handle_write (out, from_name, strlen (from_name))) + return 2; + + if (! xd_handle_write (out, to_name, strlen (to_name))) + return 2; + + if (! xd_handle_close (out, 0)) + return 2; + + if ((header_offset = xd_begin_compression (out)) < 0) + return 2; + + if (! (cont = xdp_generate_delta (gen, (FileHandle*) to, NULL, (FileHandle*) out))) + return 2; + +#if 0 + serializeio_print_xdeltacontrol_obj (cont, 0); +#endif + + if (cont->has_data && cont->has_data == cont->source_info_len) + { + if (! quiet) + xd_error ("warning: no matches found in from file, patch will apply without it\n"); + } + + if (! xd_handle_close (out, 0)) + return 2; + + if ((control_offset = xd_begin_compression (out)) < 0) + return 2; + + if (! xdp_control_write (cont, (FileHandle*) out)) + return 2; + + if (! xd_end_compression (out)) + return 2; + + if (! xd_handle_putui (out, control_offset)) + return 2; + + if (! xd_handle_write (out, XDELTA_PREFIX, XDELTA_PREFIX_LEN)) + return 2; + + xd_read_close (from); + xd_read_close (to); + + if (! xd_handle_really_close (out)) + return 2; + + xdp_generator_free (gen); + + return control_offset != header_offset; +} + +static XdeltaPatch* +process_patch (const char* name) +{ + XdeltaPatch* patch; + guint total_trailer; + + patch = g_new0 (XdeltaPatch, 1); + + patch->patch_name = name; + + /* Strictly speaking, I'm violating the intended semantics of noseek here. + * It will seek the file, which is not in fact checked in the map/unmap + * logic above. This only means that it will not cache pages of this file + * since it will be read piecewise sequentially. */ + if (! (patch->patch_in = open_read_noseek_handle (name, &patch->patch_is_compressed, TRUE, TRUE))) + return NULL; + + if (xd_handle_read (patch->patch_in, patch->magic_buf, XDELTA_PREFIX_LEN) != XDELTA_PREFIX_LEN) + return NULL; + + if (xd_handle_read (patch->patch_in, (guint8*) patch->header_space, HEADER_SPACE) != HEADER_SPACE) + return NULL; + + ntohl_array (patch->header_space, HEADER_WORDS); + + if (strncmp (patch->magic_buf, XDELTA_110_PREFIX, XDELTA_PREFIX_LEN) == 0) + { + patch->has_trailer = TRUE; + patch->patch_version = "1.1"; + } + else if (strncmp (patch->magic_buf, XDELTA_104_PREFIX, XDELTA_PREFIX_LEN) == 0) + { + patch->has_trailer = TRUE; + patch->patch_version = "1.0.4"; + } + else if (strncmp (patch->magic_buf, XDELTA_100_PREFIX, XDELTA_PREFIX_LEN) == 0) + { + patch->patch_version = "1.0"; + } + else if (strncmp (patch->magic_buf, XDELTA_020_PREFIX, XDELTA_PREFIX_LEN) == 0) + goto nosupport; + else if (strncmp (patch->magic_buf, XDELTA_018_PREFIX, XDELTA_PREFIX_LEN) == 0) + goto nosupport; + else if (strncmp (patch->magic_buf, XDELTA_014_PREFIX, XDELTA_PREFIX_LEN) == 0) + goto nosupport; + else + { + xd_error ("%s: bad magic number: not a valid delta\n", name); + return NULL; + } + + patch->patch_flags = patch->header_space[0]; + + if (no_verify) + xd_error ("--noverify is only accepted when creating a delta\n"); + + if (patch->patch_flags & FLAG_NO_VERIFY) + no_verify = TRUE; + else + no_verify = FALSE; + + patch->from_name_len = patch->header_space[1] >> 16; + patch->to_name_len = patch->header_space[1] & 0xffff; + + patch->from_name = g_malloc (patch->from_name_len+1); + patch->to_name = g_malloc (patch->to_name_len+1); + + patch->from_name[patch->from_name_len] = 0; + patch->to_name[patch->to_name_len] = 0; + + if (xd_handle_read (patch->patch_in, patch->from_name, patch->from_name_len) != patch->from_name_len) + return NULL; + + if (xd_handle_read (patch->patch_in, patch->to_name, patch->to_name_len) != patch->to_name_len) + return NULL; + + patch->header_offset = xd_handle_get_pos (patch->patch_in); + + total_trailer = 4 + (patch->has_trailer ? XDELTA_PREFIX_LEN : 0); + + if (! xd_handle_set_pos (patch->patch_in, xd_handle_length (patch->patch_in) - total_trailer)) + return NULL; + + if (! xd_handle_getui (patch->patch_in, &patch->control_offset)) + return NULL; + + if (patch->has_trailer) + { + guint8 trailer_buf[XDELTA_PREFIX_LEN]; + + if (xd_handle_read (patch->patch_in, trailer_buf, XDELTA_PREFIX_LEN) != XDELTA_PREFIX_LEN) + return NULL; + + if (strncmp (trailer_buf, patch->magic_buf, XDELTA_PREFIX_LEN) != 0) + { + xd_error ("%s: bad trailing magic number, delta is corrupt\n", name); + return NULL; + } + } + + if (! xd_handle_narrow (patch->patch_in, patch->control_offset, + xd_handle_length (patch->patch_in) - total_trailer, + patch->patch_flags & FLAG_PATCH_COMPRESSED)) + return NULL; + + if (! (patch->cont = xdp_control_read ((FileHandle*) patch->patch_in))) + return NULL; + + if (patch->cont->source_info_len > 0) + { + XdeltaSourceInfo* info = patch->cont->source_info[0]; + + if (info->isdata) + patch->data_source = info; + else + { + patch->from_source = info; + + if (patch->cont->source_info_len > 1) + { + xd_generate_void_event (EC_XdIncompatibleDelta); + return NULL; + } + } + } + + if (patch->cont->source_info_len > 1) + { + patch->from_source = patch->cont->source_info[1]; + } + + if (patch->cont->source_info_len > 2) + { + xd_generate_void_event (EC_XdIncompatibleDelta); + return NULL; + } + + if (! xd_handle_narrow (patch->patch_in, + patch->header_offset, + patch->control_offset, + patch->patch_flags & FLAG_PATCH_COMPRESSED)) + return NULL; + + return patch; + + nosupport: + + xd_error ("delta format is unsupported (too old)\n"); + return NULL; +} + +static gint +info_command (gint argc, gchar** argv) +{ + XdeltaPatch* patch; + char buf[33]; + int i; + XdeltaSourceInfo* si; + + if (! (patch = process_patch (argv[0]))) + return 2; + + xd_error_file = stdout; + + xd_error ("version %s found patch version %s in %s%s\n", + xdelta_version, + patch->patch_version, + patch->patch_name, + patch->patch_flags & FLAG_PATCH_COMPRESSED ? " (compressed)" : ""); + + if (patch->patch_flags & FLAG_NO_VERIFY) + xd_error ("generated with --noverify\n"); + + if (patch->patch_flags & FLAG_FROM_COMPRESSED) + xd_error ("generated with a gzipped FROM file\n"); + + if (patch->patch_flags & FLAG_TO_COMPRESSED) + xd_error ("generated with a gzipped TO file\n"); + + edsio_md5_to_string (patch->cont->to_md5, buf); + + xd_error ("output name: %s\n", patch->to_name); + xd_error ("output length: %d\n", patch->cont->to_len); + xd_error ("output md5: %s\n", buf); + + xd_error ("patch from segments: %d\n", patch->cont->source_info_len); + + xd_error ("MD5\t\t\t\t\tLength\tCopies\tUsed\tSeq?\tName\n"); + + for (i = 0; i < patch->cont->source_info_len; i += 1) + { + si = patch->cont->source_info[i]; + + edsio_md5_to_string (si->md5, buf); + + xd_error ("%s\t%d\t%d\t%d\t%s\t%s\n", + buf, + si->len, + si->copies, + si->copy_length, + si->sequential ? "yes" : "no", + si->name); + } + + return 0; +} + +static gint +patch_command (gint argc, gchar** argv) +{ + XdFileHandle* to_out; + XdeltaPatch* patch; + gint to_out_fd; + int count = 1; + int ret; + + if (argc < 1 || argc > 3) + { + xd_error ("usage: %s patch patchfile [fromfile [tofile]]\n", program_name); + return 2; + } + + if (! (patch = process_patch (argv[0]))) + return 2; + + if (argc > 1) + patch->from_name = argv[1]; + else if (verbose) + xd_error ("using from file name: %s\n", patch->from_name); + + if (argc > 2) + patch->to_name = argv[2]; + else + { + struct stat sbuf; + gchar *defname = g_strdup (patch->to_name); + + while ((ret = stat (patch->to_name, & sbuf)) == 0) + { + if (verbose) + xd_error ("to file exists: %s\n", patch->to_name); + patch->to_name = g_strdup_printf ("%s.xdp%d", defname, count++); + } + + if (verbose || strcmp (defname, patch->to_name) != 0) + xd_error ("using to file name: %s\n", patch->to_name); + } + + if (strcmp (patch->to_name, "-") == 0) + { + to_out_fd = STDOUT_FILENO; + patch->to_name = "standard output"; + } + else + { + to_out_fd = open (patch->to_name, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666); + + if (to_out_fd < 0) + { + xd_error ("open %s failed: %s\n", patch->to_name, g_strerror (errno)); + return 2; + } + } + + to_out = open_write_handle (to_out_fd, patch->to_name); + + if ((patch->patch_flags & FLAG_TO_COMPRESSED) && (xd_begin_compression (to_out) < 0)) + return 2; + + if (patch->from_source) + { + XdFileHandle* from_in; + gboolean from_is_compressed = FALSE; + + if (! (from_in = open_read_seek_handle (patch->from_name, &from_is_compressed, TRUE))) + return 2; + + if (from_is_compressed != ((patch->patch_flags & FLAG_FROM_COMPRESSED) && 1)) + xd_error ("warning: expected %scompressed from file (%s)\n", + (patch->patch_flags & FLAG_FROM_COMPRESSED) ? "" : "un", + patch->from_name); + + if (xd_handle_length (from_in) != patch->from_source->len) + { + xd_error ("expected from file (%s) of %slength %d bytes\n", + patch->from_name, + from_is_compressed ? "uncompressed " : "", + patch->from_source->len); + return 2; + } + + patch->from_source->in = (XdeltaStream*) from_in; + } + + if (patch->data_source) + patch->data_source->in = (XdeltaStream*) patch->patch_in; + + if (! xdp_apply_delta (patch->cont, (FileHandle*) to_out)) + return 2; + + if (patch->from_source) + xd_read_close ((XdFileHandle*) patch->from_source->in); + + xd_read_close (patch->patch_in); + + if (! xd_handle_really_close (to_out)) + return 2; + + return 0; +} |