/*
 * Copyright (c) 2004,2005 Michael Schroeder (mls@suse.de)
 *
 * This program is licensed under the BSD license, read LICENSE.BSD
 * for further information
 */

#define _XOPEN_SOURCE 500
#ifdef DELTARPM_64BIT
# define _LARGEFILE64_SOURCE
#endif

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/wait.h>

#include <bzlib.h>
#include <zlib.h>
#include <lzma.h>

#include "util.h"
#include "md5.h"
#include "sha256.h"
#include "rpmhead.h"
#include "cpio.h"
#include "cfile.h"
#include "deltarpm.h"
#include "prelink.h"

#define BLKSHIFT 13
#define BLKSIZE  (1 << BLKSHIFT)
#define BLKMASK  ((1 << BLKSHIFT) - 1)

#define SEQCHECK_MD5   (1<<0)
#define SEQCHECK_SIZE  (1<<1)

#ifndef RPMDUMPHEADER
# define RPMDUMPHEADER "rpmdumpheader"
#endif


/*****************************************************************
 * openfile, maintain a set of opened files, close descriptors if
 * limit is reached.
 */

struct openfile {
  struct openfile *prev;
  struct openfile *next;
  char *name;
  int fd;
  unsigned int off;
  struct seqdescr *sd;
};

struct openfile *openfiles;
struct openfile *openfilestail;
int nopenfile;
int maxopenfile = 50;



struct openfile *
newopen(struct seqdescr *sd, struct fileblock *fb)
{
  int fd;
  char *name;
  struct openfile *of;
  struct stat stb;

  name = fb->filenames[sd->i];
  if ((fd = open(name, O_RDONLY)) == -1)
    {
      perror(name);
      fprintf(stderr, "cannot reconstruct rpm from disk files\n");
      exit(1);
    }
  if (fstat(fd, &stb) == 0 && stb.st_size != fb->filesizes[sd->i])
    {
      unsigned char buf[128];
      if (is_prelinked(fd, buf, pread(fd, buf, 128, (off_t)0)))
	{
	  close(fd);
	  return 0;
	}
    }
  if (nopenfile < maxopenfile)
    {
      of = xmalloc(sizeof(*of));
      nopenfile++;
    }
  else
    {
      of = openfiles;
      openfiles = of->next;
      if (openfiles)
	openfiles->prev = 0;
      else
	openfilestail = 0;
      of->sd->f = 0;
      // printf("closing %s\n", of->name);
      close(of->fd);
    }
  // printf("opening %s\n", name);
  of->fd = fd;
  of->name = name;
  of->off = 0;
  of->sd = sd;
  of->prev = of->next = 0;
  if (openfilestail)
    {
      openfilestail->next = of;
      of->prev = openfilestail;
      openfilestail = of;
    }
  else
    openfiles = openfilestail = of;
  sd->f = of;
  return of;
}

/*****************************************************************
 * blk stuff, block contents creation and paging
 */

#define BLK_FREE     0
#define BLK_CORE_REC 1
#define BLK_CORE_ONE 2
#define BLK_PAGE     3

struct blk {
  struct blk *next;
  int type;
  int id;
  union {
    unsigned int off;
    unsigned char *buf;
  } e;
};

struct blk *coreblks;
struct blk *freecoreblks;
struct blk *pageblks;
int ncoreblk = 0;
int npageblk = 0;
int ndropblk = 0;

int maxcoreblk = 5000;

unsigned int *maxblockuse;	/* last time the block will be used */
struct blk **vmem;

unsigned char *cpiodata;
int csdesc = -1;
char *symdata;

char *fromrpm;
int fromrpm_raw;
struct cfile *outfp;
unsigned int outfpleft;
drpmuint outfpleft_raw;
int outfpid;


int pagefd = -1;


void (*fillblock_method)(struct blk *b, int id, struct seqdescr *sdesc, int nsdesc, struct fileblock *fb, int idx);

void
pageoutblock(struct blk *cb, int idx)
{
  struct blk *b;

  // printf("pageoutblock %d\n", cb->id);
  for (b = pageblks; b; b = b->next)
    if (b->id == cb->id)
      {
        vmem[b->id] = b;
        return;
      }
  for (b = pageblks; b; b = b->next)
    if (maxblockuse[b->id] < idx)
      break;
  if (!b)
    {
      b = xmalloc(sizeof(*b));
      b->next = pageblks;
      b->type = BLK_PAGE;
      b->e.off = npageblk;
      pageblks = b;
      npageblk++;
      if (pagefd < 0)
	{
	  char tmpname[80];
	  sprintf(tmpname, "/tmp/deltarpmpageXXXXXX");
#ifdef DELTARPM_64BIT
	  pagefd = mkstemp64(tmpname);
#else
	  pagefd = mkstemp(tmpname);
#endif
	  if (pagefd < 0)
	    {
	      fprintf(stderr, "could not create page area\n");
	      exit(1);
	    }
	  unlink(tmpname);
	}
    }
  b->id = cb->id;
#ifdef DELTARPM_64BIT
  if (pwrite64(pagefd, cb->e.buf, BLKSIZE, (off64_t)b->e.off * BLKSIZE) != BLKSIZE)
    {
      perror("page area write");
      exit(1);
    }
#else
  if (pwrite(pagefd, cb->e.buf, BLKSIZE, (off_t)b->e.off * BLKSIZE) != BLKSIZE)
    {
      perror("page area write");
      exit(1);
    }
#endif
  vmem[b->id] = b;
}

void
pageinblock(struct blk *cb, struct blk *b)
{
  if (b->type != BLK_PAGE)
    abort();
#ifdef DELTARPM_64BIT
  if (pread64(pagefd, cb->e.buf, BLKSIZE, (off64_t)b->e.off * BLKSIZE) != BLKSIZE)
    {
      perror("page area read");
      exit(1);
    }
#else
  if (pread(pagefd, cb->e.buf, BLKSIZE, (off_t)b->e.off * BLKSIZE) != BLKSIZE)
    {
      perror("page area read");
      exit(1);
    }
#endif
  cb->id = b->id;
  cb->type = BLK_CORE_ONE;
  vmem[cb->id] = cb;
}

struct blk *
newcoreblk(void)
{
  struct blk *b;
  b = xmalloc(sizeof(*b) + BLKSIZE);
  b->next = coreblks;
  b->type = BLK_FREE;
  b->e.buf = (unsigned char *)(b + 1);
  coreblks = b;
  ncoreblk++;
  // printf("created new coreblk, have now %d\n", ncoreblk);
  return b;
}

void
pushblock(struct blk *nb, int idx)
{
  struct blk *b;

  b = freecoreblks;
  if (b)
    {
      freecoreblks = b->next;
      b->next = coreblks;
      coreblks = b;
    }
  if (!b && ncoreblk < maxcoreblk)
    b = newcoreblk();
  if (!b)
    {
      /* could not find in-core place */
      if (nb->type == BLK_CORE_ONE)
        pageoutblock(nb, idx);
      else
	vmem[nb->id] = 0;
      return;
    }
  b->type = nb->type;
  b->id = nb->id;
  memcpy(b->e.buf, nb->e.buf, BLKSIZE);
  vmem[b->id] = b;
}

void
createcpiohead(struct seqdescr *sd, struct fileblock *fb)
{
  int i = sd->i;
  unsigned int lsize, rdev;
  char *np;

  if (i == -1)
    {
      sprintf((char *)cpiodata, "%s%c%c%c%c", "07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000b00000000TRAILER!!!", 0, 0, 0, 0);
      return;
    }
  lsize = rdev = 0;
  np = fb->filenames[i];
  if (*np == '/')
    np++;
  if (S_ISREG(fb->filemodes[i]))
    lsize = fb->filesizes[i];
  else if (S_ISLNK(fb->filemodes[i]))
    {
      symdata = fb->filelinktos[i];
      lsize = strlen(fb->filelinktos[i]);
    }
  if (S_ISBLK(fb->filemodes[i]) || S_ISCHR(fb->filemodes[i]))
    rdev = fb->filerdevs[i];
  sprintf((char *)cpiodata, "07070100000000%08x00000000000000000000000100000000%08x0000000000000000%08x%08x%08x00000000./%s%c%c%c%c", fb->filemodes[i], lsize, devmajor(rdev), devminor(rdev), (int)strlen(np) + 3, np, 0, 0, 0, 0);
}

int nprelink = 0;

void
fillblock_prelink(struct blk *b, int id, struct seqdescr *sd, struct fileblock *fb, int idx)
{
  int xid = id;
  drpmuint off;
  struct stat stb;
  char *name;
  int fd = -1;
  int isp = 0;
  int l;
  unsigned char buf[128];
  unsigned char *bp, saveblk[BLKSIZE];

  /* go to first block that doesn't start in the middle of a
   * prelinked file */
  off = id << BLKSHIFT;
  for (;;)
    {
      while (sd->off > off)
        sd--;
      /* now sd contains off */
      if (sd->i == -1 || sd->datalen == 0 || sd->off + sd->cpiolen >= off)
	break;
      if (S_ISLNK(fb->filemodes[sd->i]))
	break;
      /* off in regular file, check if prelinked */
      name = fb->filenames[sd->i];
      if ((fd = open(name, O_RDONLY)) == -1)
	{
	  perror(name);
	  fprintf(stderr, "cannot reconstruct rpm from disk files\n");
	  exit(1);
	}
      if (fstat(fd, &stb) != 0 || stb.st_size == fb->filesizes[sd->i])
	break;
      if (!is_prelinked(fd, buf, pread(fd, buf, 128, (off_t)0)))
	break;
      close(fd);
      fd = -1;
      /* rewind blocks until we leave the file */
      do
	{
	  id--;
	  off = id << BLKSHIFT;
	}
      while (sd->off + sd->cpiolen < off);
    }
  /* ok, got id, sd and maybe fd. create blocks. */
  l = BLKSIZE;
  bp = b->e.buf;
  if (fd != -1)
    {
      unsigned int u = off - (sd->off + sd->cpiolen);
      if (u && u < fb->filesizes[sd->i])
	{
	  if (lseek(fd, (off_t)u, SEEK_SET) == (off_t)-1)
	    {
	      fprintf(stderr, "%s: seek error\n", fb->filenames[sd->i]);
	      fprintf(stderr, "cannot reconstruct rpm from disk files\n");
	      exit(1);
	    }
	}
    }
  for (;;)
    {
      while (l)
	{
	  while (off >= sd->off + sd->cpiolen + sd->datalen)
	    {
	      if (fd != -1)
		{
		  close(fd);
		  fd = -1;
		}
	      sd++;
	    }
	  if (off < sd->off + sd->cpiolen)
	    {
	      int o = off - sd->off;
	      int l2 = l > sd->cpiolen - o ? sd->cpiolen - o : l;
	      createcpiohead(sd, fb);
	      memcpy(bp, cpiodata + o, l2);
	      bp += l2;
	      off += l2;
	      l -= l2;
	    }
	  if (!l)
	    break;
	  if (sd->i == -1)
	    {
	      memset(bp, 0, l);
	      bp += l;
	      off += l;
	      l -= l;
	    }
	  else if (S_ISLNK(fb->filemodes[sd->i]))
	    {
	      int o = off - (sd->off + sd->cpiolen);
	      char *ln = fb->filelinktos[sd->i];
	      int l2 = strlen(ln) - o;
	      if (l2 < 0)
		l2 = 0;
	      if (l2 > l)
		l2 = l;
	      if (l2)
		memcpy(bp, ln + o, l2);
	      bp += l2;
	      off += l2;
	      l -= l2;
	      o += l2;
	      l2 = l > sd->datalen - o ? sd->datalen - o : l;
	      if (l2 > 0)
		{
		  memset(bp, 0, l2);
		  bp += l2;
		  off += l2;
		  l -= l2;
		}
	    }
	  else if (sd->datalen)
	    {
	      int o = off - (sd->off + sd->cpiolen);
	      int l2;
	      if (o < fb->filesizes[sd->i])
		{
		  l2 = l > fb->filesizes[sd->i] - o ? fb->filesizes[sd->i] - o : l;
		  if (fd == -1)
		    {
		      name = fb->filenames[sd->i];
		      isp = 0;
		      if ((fd = open(name, O_RDONLY)) == -1)
			perror(name);
		      else if (fstat(fd, &stb) == 0 && stb.st_size != fb->filesizes[sd->i] && is_prelinked(fd, buf, pread(fd, buf, 128, (off_t)0)))
			{
			  close(fd);
			  fd = prelinked_open(name);
			  nprelink++;
			  isp = 1;
			}
		      if (fd == -1)
			{
			  fprintf(stderr, "cannot reconstruct rpm from disk files\n");
			  exit(1);
			}
		    }
		  if (read(fd, bp, l2) != l2)
		    {
		      fprintf(stderr, "%s: read error\n", fb->filenames[sd->i]);
		      fprintf(stderr, "(tried to read %d bytes from offset %d\n", l2, o);
		      exit(1);
		    }
		  bp += l2;
		  off += l2;
		  l -= l2;
		  o += l2;
		}
	      if (o >= fb->filesizes[sd->i])
		{
		  if (fd)
		    {
		      close(fd);
		      fd = -1;
		    }
		  l2 = l > sd->datalen - o ? sd->datalen - o : l;
		  if (l2)
		    memset(bp, 0, l2);
		  bp += l2;
		  off += l2;
		  l -= l2;
		}
	    }
	}

      b->type = BLK_CORE_ONE;
      b->id = id;
      if (id == xid)
	memcpy(saveblk, b->e.buf, BLKSIZE);
      else if (maxblockuse[b->id] > idx || (maxblockuse[b->id] == idx && id > xid))
	pushblock(b, idx);
      /* finished block */
      if (fd == -1 || !isp)
	break;
      l = BLKSIZE;
      bp = b->e.buf;
      id++;
      off = id << BLKSHIFT;
    }
  if (id < xid)
    {
      fprintf(stderr, "internal error, could not reach block %d (%d)\n", xid, id);
      exit(1);
    }
  if (fd != -1)
    close(fd);		/* never prelinked */
  memcpy(b->e.buf, saveblk, BLKSIZE);
  b->type = BLK_CORE_ONE;
  b->id = xid;
}

void
fillblock_disk(struct blk *b, int id, struct seqdescr *sdesc, int nsdesc, struct fileblock *fb, int idx)
{
  drpmuint off;
  unsigned int u;
  struct seqdescr *sd;
  int i;
  unsigned int l, l2;
  unsigned char *bp;

  l = BLKSIZE;
  bp = b->e.buf;
  off = id << BLKSHIFT;
  i = csdesc >= 0 ? csdesc : 0;
  for (sd = sdesc + i; i > 0 && sd->off > off; i--, sd--)
    ;
  for (; i < nsdesc; i++, sd++)
    if (sd->off <= off && sd->off + sd->cpiolen + sd->datalen > off)
      break;
  if (i == nsdesc)
    {
      fprintf(stderr, "fillblock_disk: block %d out of range\n", id);
      exit(1);
    }
  if (i != csdesc)
    {
      csdesc = i;
      createcpiohead(sd, fb);
    }
  i = sd->i;
  while (l > 0)
    {
      if (off < sd->off + sd->cpiolen)
	{
	  u = off - sd->off;
	  l2 = sd->cpiolen - u;
	  if (l2 > l)
	    l2 = l;
	  memcpy(bp, cpiodata + u, l2);
	  bp += l2;
	  off += l2;
	  l -= l2;
	  continue;
	}
      if (i == -1)
	{
	  memset(bp, 0, l);
	  l = 0;
	  break;
	}
      if (off < sd->off + sd->cpiolen + sd->datalen)
	{
	  u = off - (sd->off + sd->cpiolen);
	  if (S_ISLNK(fb->filemodes[i]))
	    {
	      l2 = sd->datalen - u;
	      if (l2 > l)
		l2 = l;
	      if (u > strlen(symdata))
		memset(bp, 0, l2);
	      else
		strncpy((char *)bp, symdata + u, l2);
	    }
	  else if (u < fb->filesizes[i])
	    {
	      struct openfile *of;
	      l2 = fb->filesizes[i] - u;
	      if (l2 > l)
		l2 = l;
	      if (!(of = sd->f))
		of = newopen(sd, fb);
	      if (!of)
		{
		  fillblock_prelink(b, id, sd, fb, idx);
		  csdesc = -1;
		  return;
		}
	      if (of->next)
		{
		  of->next->prev = of->prev;
		  if (of->prev)
		    of->prev->next = of->next;
		  else
		    openfiles = of->next;
		  of->next = 0;
		  of->prev = openfilestail;
		  openfilestail->next = of;
		  openfilestail = of;
		}
	      if (of->off != u)
		{
		  if (lseek(of->fd, (off_t)u, SEEK_SET) == (off_t)-1)
		    {
		      fprintf(stderr, "%s: seek error\n", of->name);
		      fprintf(stderr, "cannot reconstruct rpm from disk files\n");
		      exit(1);
		    }
		}
	      if (read(of->fd, bp, l2) != l2)
		{
		  fprintf(stderr, "%s: read error\n", of->name);
		  fprintf(stderr, "(tried to read %d bytes from offset %d)\n", l2, u);
		  fprintf(stderr, "cannot reconstruct rpm from disk files\n");
		  exit(1);
		}
	      of->off = u + l2;
	    }
	  else
	    {
	      l2 = sd->datalen - u;
	      if (l2 > l)
		l2 = l;
	      memset(bp, 0, l2);
	    }
	  bp += l2;
	  off += l2;
	  l -= l2;
	  continue;
        }
      csdesc++;
      sd++;
      createcpiohead(sd, fb);
      i = sd->i;
    }
  b->id = id;
  b->type = BLK_CORE_REC;
}

void
fillblock_rawrpm(struct blk *b, int id, struct seqdescr *sdesc, int nsdesc, struct fileblock *fb, int idx)
{
  unsigned char *bp;
  unsigned int l2;

  for (;;)
    {
      bp = b->e.buf;
      l2 = outfpleft_raw > BLKSIZE ? BLKSIZE : outfpleft_raw;
      if (outfp->read(outfp, bp, l2) != l2)
	{
	  fprintf(stderr, "read error");
	  exit(1);
	}
      outfpleft_raw -= l2;
      if (l2 < BLKSIZE)
	memset(bp + l2, 0, BLKSIZE - l2);
      b->type = BLK_CORE_ONE;
      b->id = outfpid++;
      if (b->id == id)
	 return;
      if (b->id > id)
	{
	  fprintf(stderr, "internal error, cannot rewind blocks (%d %d)\n", b->id, id);
	  exit(1);
	}
      if (maxblockuse[b->id] > idx)
	pushblock(b, idx);
    }
}

void
fillblock_rpm(struct blk *b, int id, struct seqdescr *sdesc, int nsdesc, struct fileblock *fb, int idx)
{
  unsigned int size, nsize;
  unsigned char *bp;
  char *np;
  int i;
  unsigned int l, l2, u;
  struct seqdescr *sd;
  struct cpiophys cph;
  static char *namebuf;
  static int namebufl;
  char skipbuf[4096];

  l = BLKSIZE;
  bp = b->e.buf;
  for (;;)
    {
      if (outfpleft)
	{
	  sd = sdesc + csdesc;
	  if (outfpleft > sd->datalen)
	    {
	      u = sd->cpiolen + sd->datalen - outfpleft;
	      l2 = sd->cpiolen - u;
	      if (l2 > l)
		l2 = l;
	      memcpy(bp, cpiodata + u, l2);
	      bp += l2;
	      outfpleft -= l2;
	      l -= l2;
	    }
	  if (l && outfpleft)
	    {
	      l2 = outfpleft;
	      if (l2 > l)
		l2 = l;
	      if (S_ISLNK(fb->filemodes[sd->i]))
		{
		  strncpy((char *)bp, symdata, l2);
		  if (strlen(symdata) < l2)
		    symdata += strlen(symdata);
		  else
		    symdata += l2;
		}
	      else
		{
		  if (outfp->read(outfp, bp, l2) != l2)
		    {
		      fprintf(stderr, "read error");
		      exit(1);
		    }
		}
	      bp += l2;
	      outfpleft -= l2;
	      l -= l2;
	    }
	}
      if (l && csdesc >= 0 && sdesc[csdesc].i == -1)
	{
	  memset(bp, 0, l); /* blocks are empty after trailer */
	  l = 0;
	}
      if (l == 0)
	{
	  b->type = BLK_CORE_ONE;
	  b->id = outfpid++;
	  if (b->id == id)
	     return;
	  if (b->id > id)
	    {
	      fprintf(stderr, "internal error, cannot rewind blocks (%d %d)\n", b->id, id);
	      exit(1);
	    }
	  if (maxblockuse[b->id] > idx)
	    pushblock(b, idx);
	  l = BLKSIZE;
	  bp = b->e.buf;
	  continue;
	}
      csdesc++;
      sd = sdesc + csdesc;
      i = sd->i;
      if (i == -1)
	{
	  createcpiohead(sd, fb);
	  outfpleft = sd->cpiolen + sd->datalen;
	  continue;
	}
      for (;;)
	{
	  if (outfp->read(outfp, &cph, sizeof(cph)) != sizeof(cph))
	    {
	      fprintf(stderr, "read error");
	      exit(1);
	    }
	  if (memcmp(cph.magic, "070701", 6))
	    {
	      fprintf(stderr, "read error: bad cpio archive\n");
	      exit(1);
	    }
	  size = cpion(cph.filesize);
	  nsize = cpion(cph.namesize);
	  nsize += (4 - ((nsize + 2) & 3)) & 3;
	  if (nsize > namebufl)
	    {
	      namebuf = xrealloc(namebuf, nsize);
	      namebufl = nsize;
	    }
	  if (outfp->read(outfp, namebuf, nsize) != nsize)
	    {
	      fprintf(stderr, "read failed (name)\n");
	      exit(1);
	    }
	  namebuf[nsize - 1] = 0;
	  if (!strcmp(namebuf, "TRAILER!!!"))
	    {
	      fprintf(stderr, "cpio end reached, bad rpm\n");
	      exit(1);
	    }
	  np = namebuf;
	  if (*np == '.' && np[1] == '/')
	    np += 2;
	  if (!strcmp(fb->filenames[i][0] == '/' ? fb->filenames[i] + 1 : fb->filenames[i], np))
	    break;
	  if (size & 3)
	    size += 4 - (size & 3);
	  while (size > 0)
	    {
	      l2 = size > sizeof(skipbuf) ? sizeof(skipbuf) : size;
	      if (outfp->read(outfp, skipbuf, l2) != l2)
		{
		  fprintf(stderr, "read failed (name)\n");
		  exit(1);
		}
	      size -= l2;
	    }
	}
      createcpiohead(sd, fb);
      if (size & 3)
	size += 4 - (size & 3);
      if (!S_ISREG(fb->filemodes[i]))
	{
	  while (size > 0)
	    {
	      l2 = size > sizeof(skipbuf) ? sizeof(skipbuf) : size;
	      if (outfp->read(outfp, skipbuf, l2) != l2)
		{
		  fprintf(stderr, "read failed (data skip)\n");
		  exit(1);
		}
	      size -= l2;
	    }
	}
      else if (size != sd->datalen)
	{
	  fprintf(stderr, "cpio data size mismatch, bad rpm\n");
	  exit(1);
	}
      outfpleft = sd->cpiolen + sd->datalen;
    }
}


/* construct the block "id". Note that the tupel (idx, id) will
 * only get bigger, so we use this to recycly no longer needed
 * blocks */

struct blk *
getblock(int id, struct seqdescr *sdesc, int nsdesc, struct fileblock *fb, int idx)
{
  struct blk *b, **bb;
  struct blk *pb;
  static int cleanup_cnt;

// printf("%d %d %d\n", idx, id, maxblockuse[id]);
  b = vmem[id];
  if (b && (b->type == BLK_CORE_REC || b->type == BLK_CORE_ONE))
    return b;

  b = freecoreblks;
  if (b)
    {
      freecoreblks = b->next;
      b->next = coreblks;
      coreblks = b;
    }

  if (!b && ncoreblk < maxcoreblk && (++cleanup_cnt & 7) != 0)
    b = newcoreblk();

  if (!b)
    {
      for (bb = &coreblks; (b = *bb) != 0; bb = &b->next)
	{
	  if (maxblockuse[b->id] < idx || (maxblockuse[b->id] == idx && b->id < id))
	    {
	      *bb = b->next;
	      vmem[b->id] = 0;
	      b->type = BLK_FREE;
	      b->next = freecoreblks;
	      freecoreblks = b;
	    }
	  else
	    bb = &b->next;
	}
      b = freecoreblks;
      if (b)
	{
	  freecoreblks = b->next;
	  b->next = coreblks;
	  coreblks = b;
	}
    }

  if (!b && ncoreblk < maxcoreblk)
    b = newcoreblk();
  if (!b)
    {
      /* use first created block */
      for (bb = &coreblks; (b = *bb); bb = &b->next)
	if (b->next == 0)
	  break;
      *bb = 0;
      b->next = coreblks;
      coreblks = b;
      if (b->type == BLK_CORE_ONE)
	pageoutblock(b, idx);
      else
	{
	  vmem[b->id] = 0;
	  ndropblk++;
	}
      b->type = BLK_FREE;
    }

  /* got destination block, now fill it with data */
  pb = vmem[id];
  if (pb && pb->type == BLK_PAGE)
    {
      pageinblock(b, pb);
      return b;
    }
  fillblock_method(b, id, sdesc, nsdesc, fb, idx);
  vmem[id] = b;
  return b;
}

int
cfile_write_uncomp(struct cfile *f, void *buf, int len)
{
  int l2;
  if (!len)
    return 0;
  l2 = len > f->len ? f->len : len;
  if (fwrite(buf, l2, 1, (FILE *)f->fp) != 1)
    return -1;
  if (l2 && f->ctxup)
    f->ctxup(f->ctx, buf, l2);
  f->len -= l2;
  if (f->len)
    return l2;
  f = cfile_open(CFILE_OPEN_WR, CFILE_IO_REOPEN, f, f->comp, CFILE_LEN_UNLIMITED, f->ctxup, f->ctx);
  if (!f)
    {
      fprintf(stderr, "payload re-open error\n");
      exit(1);
    }
  if (f->write(f, buf + l2, len - l2) != len - l2)
    return -1;
  return len;
}

typedef struct {
  union {
    MD5_CTX md5ctx;
    SHA256_ctx sha256ctx;
  } ctx;
} DIG_CTX;


static inline void
DIG_Init(DIG_CTX *ctx, int digestalgo)
{
  if (digestalgo == 1)
    rpmMD5Init(&ctx->ctx.md5ctx);
  else if (digestalgo == 8)
    SHA256_init(&ctx->ctx.sha256ctx);
}

static inline void
DIG_Update(DIG_CTX *ctx, int digestalgo, unsigned char const *buf, unsigned len)
{
  if (digestalgo == 1)
    rpmMD5Update(&ctx->ctx.md5ctx, buf, len);
  else if (digestalgo == 8)
    SHA256_update(&ctx->ctx.sha256ctx, buf, len);
}

static inline void
DIG_Final(DIG_CTX *ctx, int digestalgo, unsigned char *digest)
{
  if (digestalgo == 1)
    rpmMD5Final(digest, &ctx->ctx.md5ctx);
  else if (digestalgo == 8)
    {
      SHA256_final(&ctx->ctx.sha256ctx);
      SHA256_digest(&ctx->ctx.sha256ctx, digest);
    }
  else
    *digest = 0;
}

static inline int
DIG_Len(int digestalgo)
{
  if (digestalgo == 1)
    return 16;
  if (digestalgo == 8)
    return 32;
  return 0;
}

int
checkprelinked(char *name, int digestalgo, unsigned char *hmd5, unsigned int size)
{
  int fd, l;
  unsigned char buf[4096];
  DIG_CTX ctx;
  unsigned char md5[32];

  nprelink++;
  if ((fd = prelinked_open(name)) < 0)
    {
      perror(name);
      return -1;
    }
  DIG_Init(&ctx, digestalgo);
  while (size && (l = read(fd, buf, sizeof(buf))) > 0)
    {
      if (l > size)
	l = size;
      DIG_Update(&ctx, digestalgo, buf, l);
      size -= l;
    }
  close(fd);
  DIG_Final(&ctx, digestalgo, md5);
  if (memcmp(md5, hmd5, DIG_Len(digestalgo)))
    {
      fprintf(stderr, "%s: contents have been changed\n", name);
      return -1;
    }
  return 0;
}

int
checkfilemd5(char *name, int digestalgo, unsigned char *hmd5, unsigned int size)
{
  int fd, l;
  unsigned char buf[4096];
  DIG_CTX ctx;
  unsigned char md5[32];
  struct stat stb;

  if ((fd = open(name, O_RDONLY)) < 0 || fstat(fd, &stb))
    {
      perror(name);
      return -1;
    }
  DIG_Init(&ctx, digestalgo);
  if (stb.st_size > size && (l = read(fd, buf, sizeof(buf))) > 0)
    {
      if (is_prelinked(fd, buf, l))
	{
	  close(fd);
	  return checkprelinked(name, digestalgo, hmd5, size);
	}
      if (l > size)
	l = size;
      DIG_Update(&ctx, digestalgo, buf, l);
      size -= l;
    }
  while (size && (l = read(fd, buf, sizeof(buf))) > 0)
    {
      if (l > size)
	l = size;
      DIG_Update(&ctx, digestalgo, buf, l);
      size -= l;
    }
  close(fd);
  DIG_Final(&ctx, digestalgo, md5);
  if (memcmp(md5, hmd5, DIG_Len(digestalgo)))
    {
      fprintf(stderr, "%s: contents have been changed\n", name);
      return -1;
    }
  return 0;
}

int
checkfilesize(char *name, int digestalgo, unsigned char *hmd5, unsigned int size)
{
  struct stat stb;
  unsigned char buf[128];
  int l;

  if (stat(name, &stb) == -1)
    {
      perror(name);
      return -1;
    }
  if (stb.st_size == size)
    return 0;
  if (stb.st_size > size)
    {
      int fd;
      fd = open(name, O_RDONLY);
      if (fd != -1 && (l = read(fd, buf, sizeof(buf))) > 0 && is_prelinked(fd, buf, l))
	{
	  close(fd);
	  return checkprelinked(name, digestalgo, hmd5, size);
	}
      if (fd != -1)
        close(fd);
    }
  fprintf(stderr, "%s: contents have been changed\n", name);
  return -1;
}

/*****************************************************************
 * main program
 */

int
main(int argc, char **argv)
{
  int c, i;
  char *deltarpm;
  struct rpmhead *h;
  int fd;
  struct cfile *bfp = 0;
  struct cfile *obfp;
  char *fnevr;
  unsigned int inn;
  unsigned int *in;
  unsigned int outn;
  unsigned int *out;
  drpmuint off, paywritten;
  struct fileblock fb;
  struct seqdescr *sdesc;
  int nsdesc;
  struct blk *lastblk = 0;
  int idx;
  int on;
  unsigned int len, l;
  int bs, be;
  unsigned char buf[4096];
  MD5_CTX wrmd5;
  unsigned char wrmd5res[16];
  int nofullmd5 = 0;
  FILE *ofp;
  int numblks;
  int percent = 0;
  int curpercent;
  int lastpercent = -1;
  int verbose = 0;
  int seqcheck = 0;
  int check = 0;
  int checkflags = 0;
  int info = 0;
  bz_stream addbz2strm;
  z_stream addgzstrm;
  int addblkcomp;
  unsigned char *addblkbuf = 0;
  unsigned char *b;
  int seqmatches = 1;
  FILE *vfp;
  struct deltarpm d;
  char *arch = 0;

  while ((c = getopt(argc, argv, "cCisvpr:a:")) != -1)
    {
      switch(c)
	{
	case 'v':
          verbose++;
	  break;
	case 'p':
          percent++;
	  break;
	case 'r':
	  fromrpm = optarg;
	  break;
	case 's':
	  check = 1;
	  seqcheck = 1;
	  break;
	case 'i':
	  info = 1;
	  break;
	case 'c':
	  checkflags = SEQCHECK_MD5;
	  check = 1;
	  break;
	case 'C':
	  checkflags = SEQCHECK_SIZE;
	  check = 1;
	  break;
	case 'a':
	  arch = optarg;
	  break;
	default:
	  fprintf(stderr, "usage: applydeltarpm [-r <rpm>] deltarpm rpm\n");
          exit(1);
	}
    }

  if (optind + (check || info ? 1 : 2) != argc)
    {
      fprintf(stderr, "usage: applydeltarpm [-r <rpm>] deltarpm rpm\n");
      exit(1);
    }
  if (checkflags && fromrpm)
    {
      fprintf(stderr, "on-disk checking does not work with the -r option.\n");
      exit(1);
    }

  vfp = !(check || info) && !strcmp(argv[argc - 1], "-") ? stderr : stdout;

  deltarpm = argv[optind];

  bfp = 0;
  if (seqcheck)
    {
      char *hex;
      int nevrl;

      if (info)
	{
	  fprintf(stderr, "need real delta-rpm for info\n");
	  exit(1);
	}
      memset(&d, 0, sizeof(d));
      d.name = deltarpm;
      d.seql = strlen(deltarpm);
      if (d.seql < 34 || (hex = strrchr(deltarpm, '-')) == 0)
	{
	  fprintf(stderr, "%s: bad sequence\n", deltarpm);
	  exit(1);
	}
      d.nevr = deltarpm;
      nevrl = hex - deltarpm;
      d.seql -= nevrl + 1;
      *hex++ = 0;
      d.seql = (d.seql + 1) / 2;
      d.seq = xmalloc(d.seql);
      if (parsehex(hex, d.seq, d.seql) != d.seql)
	{
	  fprintf(stderr, "bad sequence\n");
	  exit(1);
	}
    }
  else
    {
      if (verbose)
	fprintf(vfp, "reading deltarpm\n");
      readdeltarpm(deltarpm, &d, &bfp);
      nofullmd5 = !memcmp("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", d.targetmd5, 16);
#ifdef DELTARPM_64BIT
      if (d.outlen >= 0xffffffffULL << BLKSHIFT)
	{
	  fprintf(stderr, "cpio size too big\n");
	  exit(1);
	}
#endif
      numblks = (unsigned int)(d.outlen >> BLKSHIFT);
      if ((d.outlen & (BLKSIZE - 1)) != 0)
	numblks++;

      maxblockuse = xcalloc(numblks, sizeof(unsigned int));
      vmem = xcalloc(numblks, sizeof(struct blk *));

      if (verbose > 1)
	{
	  fprintf(vfp, "%llu bytes source payload size\n", (unsigned long long)d.outlen);
	  fprintf(vfp, "%llu bytes target payload size\n", (unsigned long long)d.paylen);
	  fprintf(vfp, "%llu bytes internal data size\n", (unsigned long long)d.inlen);
	  fprintf(vfp, "%u bytes add data size\n", d.addblklen);
	  fprintf(vfp, "%d blocks\n", numblks);
	  fprintf(vfp, "%d copy instructions\n", d.inn + d.outn);
	}
      off = 0;
      for (i = 0; i < d.outn; i++)
        {
	  off += (int)d.out[2 * i];
	  bs = off >> BLKSHIFT;
	  off += d.out[2 * i + 1];
	  be = (off - 1) >> BLKSHIFT;
	  for (; bs <= be; bs++)
	    maxblockuse[bs] = i;
	}
    }

  addblkcomp = CFILE_COMP_BZ;
  if (d.addblklen > 9 && d.addblk[0] == 0x1f && d.addblk[1] == 0x8b)
    addblkcomp = CFILE_COMP_GZ;
  else if (d.addblklen > 3 && (d.addblk[0] == 255 && d.addblk[1] == 'L' && d.addblk[2] == 'Z'))
    addblkcomp = CFILE_COMP_LZMA;
  else if (d.addblklen > 6 && (d.addblk[0] == 0xfd && d.addblk[1] == '7' && d.addblk[2] == 'z' && d.addblk[3] == 'X' && d.addblk[4] == 'Z'))
    addblkcomp = CFILE_COMP_XZ;
  else if (d.addblklen > 4 && (d.addblk[0] & 0xf0) == 0x20 &&d.addblk[1] == 0xb5 && d.addblk[2] == 0x2f && d.addblk[3] == 0xfd)
    addblkcomp = CFILE_COMP_ZSTD;
  if (info)
    {
      unsigned int *size;
      if (d.version)
	printf("deltarpm version: %c\n", d.version & 0xff);
      printf("deltarpm type: %s\n", d.h ? "standard" : d.targetcomp != CFILE_COMP_UN || d.inn != 0 || d.outn != 0 ? "rpm-only" : "rpm-only, no diff");
      printf("deltarpm compression: %s\n", cfile_comp2str(d.deltacomp));
      printf("sequence: %s-", d.nevr);
      for (i = 0; i < d.seql; i++)
	printf("%02x", d.seq[i]);
      putchar('\n');
      printf("source rpm: %s\n", d.nevr);
      if (d.h || d.targetcomp != CFILE_COMP_UN || d.inn != 0 || d.outn != 0)
        printf("source payload size: %llu\n", (unsigned long long)d.outlen);
      printf("target rpm: %s\n", d.targetnevr);
      if (d.h || d.targetcomp != CFILE_COMP_UN || d.inn != 0 || d.outn != 0)
	{
	  printf("target payload size: %llu\n", (unsigned long long)d.paylen);
	  if (d.targetcomp != CFILE_COMP_XX)
	    printf("target payload compression: %s\n", cfile_comp2str(d.targetcomp));
	}
      if (d.targetsize == 0)
	{
          struct rpmhead *dsigh = readhead_buf(d.lead + 96, d.leadl - 96, 0);
	  if (dsigh && (size = headint32(dsigh, 1000, (int *)0)) != 0)
	    {
	      d.targetsize = d.leadl + *size;
	      free(size);
	    }
	  xfree(dsigh);
	}
      if (d.targetsize)
        printf("target size: %u\n", d.targetsize);
      printf("target md5: ");
      for (i = 0; i < 16; i++)
	printf("%02x", d.targetmd5[i]);
      putchar('\n');
      if (d.h || d.targetcomp != CFILE_COMP_UN || d.inn != 0 || d.outn != 0)
	{
	  printf("internal data size: %llu\n", (unsigned long long)d.inlen);
	  printf("compressed add data size: %d\n", d.addblklen);
	  if (d.addblklen)
	    printf("compressed add data compression: %s\n", cfile_comp2str(addblkcomp));
	  printf("instructions: %d\n", d.inn + d.outn);
	}
      if (bfp)
        bfp->close(bfp);
      exit(0);
    }

  if (d.targetcompparalen)
    {
      fprintf(stderr, "deltarpm contains unknown compression parameters\n");
      exit(1);
    }

  if (!fromrpm)
    {
      pid_t pid;
      int pi[2];

      if (!seqcheck && !d.h)
	{
	  fprintf(stderr, "this deltarpm does not work from filesystem, use '-r <oldrpm>'.\n");
	  exit(1);
	}
      if (!seqcheck && !headstring(d.h, TAG_SOURCERPM))
	{
	  fprintf(stderr, "cannot reconstruct source rpms from filesystem\n");
	  exit(1);
	}
      if (pipe(pi))
	{
	  perror("pipe");
	  exit(1);
	}
      if ((pid = fork()) == (pid_t)-1)
	{
	  perror("fork");
	  exit(1);
	}
      if (pid == 0)
	{
	  close(pi[0]);
	  if (pi[1] != 1)
	    {
	      dup2(pi[1], 1);
	      close(pi[1]);
	    }
	  if (arch)
	    execlp(RPMDUMPHEADER, RPMDUMPHEADER, "-a", arch, d.nevr, (char *)0);
	  else
	    execlp(RPMDUMPHEADER, RPMDUMPHEADER, d.nevr, (char *)0);
	  perror(RPMDUMPHEADER);
	  _exit(1);
	}
      close(pi[1]);
      fd = pi[0];
    }
  else
    {
      unsigned char rpmlead[96];

      if ((fd = open(fromrpm, O_RDONLY)) < 0)
	{
	  perror(fromrpm);
	  exit(1);
	}
      if (read(fd, rpmlead, 96) != 96 || rpmlead[0] != 0xed || rpmlead[1] != 0xab || rpmlead[2] != 0xee || rpmlead[3] != 0xdb)
	{
	  fprintf(stderr, "%s: not a rpm\n", fromrpm);
	  exit(1);
	}
      if (rpmlead[4] != 0x03 || rpmlead[0x4e] != 0 || rpmlead[0x4f] != 5)
	{
	  fprintf(stderr, "%s: not a v3 rpm or not new header styles\n", fromrpm);
	  exit(1);
	}
      h = readhead(fd, 1);
      if (!h)
	{
	  fprintf(stderr, "could not read signature header\n");
	  exit(1);
	}
      if (!d.h)
	{
	  unsigned char *hmd5 = headbin(h, 1004, 16);
	  if (!hmd5 || memcmp(hmd5, d.seq, 16) != 0)
	    seqmatches = 0;
	  if (seqcheck)
	    {
	      /* we don't know if this is a rpm-only deltarpm or not,
               * so we assume yes if seqmatches is true */
	      if (seqmatches && d.seql == 16)
		seqcheck = 0;	/* assume rpm-only, no expandseq */
	      else
	        seqmatches = 1;	/* assume normal, run expandseq */
	    }
	}
      free(h);
    }
  h = readhead(fd, 0);
  if (!h)
    {
      if (fromrpm)
        fprintf(stderr, "could not read header\n");
      exit(1);
    }
  fnevr = headtonevr(h);
  if (strcmp(fnevr, (char *)d.nevr) != 0)
    {
      fprintf(stderr, "delta rpm made for %s, not %s\n", d.nevr, fnevr);
      exit(1);
    }
  if (!seqmatches)
    {
      fprintf(stderr, "rpm does not match the one used for creating the deltarpm\n");
      exit(1);
    }
  if (d.h || seqcheck)
    {
      int (*checkfunc)(char *, int, unsigned char *, unsigned int);
      if (headtofb(h, &fb))
	{
	  fprintf(stderr, "bad header\n");
	  exit(1);
	}
      checkfunc = 0;
      if ((checkflags & SEQCHECK_MD5) != 0)
	checkfunc = checkfilemd5;
      else if ((checkflags & SEQCHECK_SIZE) != 0)
	checkfunc = checkfilesize;
      sdesc = expandseq(d.seq, d.seql, &nsdesc, &fb, checkfunc);
      if (!sdesc)
	{
	  fprintf(stderr, "could not expand sequence data\n");
	  exit(1);
	}
    }
  else
    {
      nsdesc = 0;
      sdesc = 0;
    }
  if (!fromrpm)
    {
      int status;
      close(fd);
      wait(&status);
    }
  if (check)
    exit(0);

  l = 0;
  for (i = 0; i < nsdesc; i++)
    if (sdesc[i].cpiolen > l)
      l = sdesc[i].cpiolen;
  if (l < 124)
    l = 124;			/* room for tailer */
  cpiodata = xmalloc(l + 4);	/* extra room for padding */


  rpmMD5Init(&wrmd5);
  if (!strcmp(argv[optind + 1], "-"))
    ofp = stdout;
  else if ((ofp = fopen(argv[optind + 1], "w")) == 0)
    {
      perror(argv[optind + 1]);
      exit(1);
    }
  if (fwrite(d.lead, d.leadl, 1, ofp) != 1)
    {
      fprintf(stderr, "write error\n");
      exit(1);
    }
  if (!nofullmd5)
    rpmMD5Update(&wrmd5, d.lead, d.leadl);
  if (!d.h)
    fromrpm_raw = 1;

  if (fromrpm_raw && d.targetcomp == CFILE_COMP_UN && d.inn == 0 && d.outn == 0)
    {
      /* no diff, copy-through mode */
      if (fwrite(h->intro, 16, 1, ofp) != 1 || fwrite(h->data, 16 * h->cnt + h->dcnt, 1, ofp) != 1)
	{
	  fprintf(stderr, "write error\n");
	  exit(1);
	}
      rpmMD5Update(&wrmd5, h->intro, 16);
      rpmMD5Update(&wrmd5, h->data, 16 * h->cnt + h->dcnt);
      while ((l = read(fd, buf, sizeof(buf))) > 0)
	{
	  if (fwrite(buf, l, 1, ofp) != 1)
	    {
	      fprintf(stderr, "write error\n");
	      exit(1);
	    }
	  rpmMD5Update(&wrmd5, buf, l);
	}
      if (fflush(ofp) || (ofp != stdout && fclose(ofp) != 0))
	{
	  fprintf(stderr, "write error\n");
	  exit(1);
	}
      rpmMD5Final(wrmd5res, &wrmd5);
      if (nofullmd5)
        {
          struct rpmhead *dsigh = readhead_buf(d.lead + 96, d.leadl - 96, 0);
          if (dsigh)
            {
              unsigned char *hmd5 = headbin(dsigh, SIGTAG_MD5, 16);
              if (hmd5)
                {
                  if (memcmp(wrmd5res, hmd5, 16) != 0)
                    {
                      fprintf(stderr, "%s: md5 mismatch of result\n", deltarpm);
                      exit(1);
                    }
                }
              xfree(dsigh);
            }
        }
      else if (memcmp(wrmd5res, d.targetmd5, 16) != 0)
	{
	  fprintf(stderr, "%s: md5 mismatch of result\n", deltarpm);
	  exit(1);
	}
      exit(0);
    }

  if (!fromrpm_raw)
    {
      if (fwrite(d.h->intro, 16, 1, ofp) != 1)
	{
	  fprintf(stderr, "write error\n");
	  exit(1);
	}
      rpmMD5Update(&wrmd5, d.h->intro, 16);
      strncpy((char *)d.h->dp + d.payformatoff, "cpio", 4);
      if (fwrite(d.h->data, 16 * d.h->cnt + d.h->dcnt, 1, ofp) != 1)
	{
	  fprintf(stderr, "write error\n");
	  exit(1);
	}
      rpmMD5Update(&wrmd5, d.h->data, 16 * d.h->cnt + d.h->dcnt);
    }

  if (fromrpm)
    {
      if ((outfp = cfile_open(CFILE_OPEN_RD, fd, 0, CFILE_COMP_XX, CFILE_LEN_UNLIMITED, 0, 0)) == 0)
	{
	  fprintf(stderr, "%s: payload open failed\n", deltarpm);
	  exit(1);
	}
    }

  if (d.addblklen)
    {
      switch (addblkcomp)
	{
	case CFILE_COMP_GZ:
	  if (memcmp(d.addblk, "\037\213\010\0\0\0\0\0\0\003", 10) != 0)
	    {
	      fprintf(stderr, "addblk: unsupported gz stream\n");
	      exit(1);
	    }
	  addgzstrm.zalloc = NULL;
	  addgzstrm.zfree = NULL;
	  addgzstrm.opaque = NULL;
	  if (inflateInit2(&addgzstrm, -MAX_WBITS) != Z_OK)
	    {
	      fprintf(stderr, "addblk: inflateInit2 error\n");
	      exit(1);
	    }
	  addgzstrm.next_in = d.addblk + 10;
	  addgzstrm.avail_in = d.addblklen - 10;
	  break;
	default:
	  addbz2strm.bzalloc = NULL;
	  addbz2strm.bzfree = NULL;
	  addbz2strm.opaque = NULL;
	  if (BZ2_bzDecompressInit(&addbz2strm, 0, 0) != BZ_OK)
	    {
	      fprintf(stderr, "addblk: BZ2_bzDecompressInit error\n");
	      exit(1);
	    }
	  addbz2strm.next_in = (char *)d.addblk;
	  addbz2strm.avail_in = d.addblklen;
	  break;
	}
      addblkbuf = xmalloc(BLKSIZE);
    }

  obfp = cfile_open(CFILE_OPEN_WR, CFILE_IO_FILE, ofp, d.compheadlen ? CFILE_COMP_UN : d.targetcomp, CFILE_LEN_UNLIMITED, (cfile_ctxup)rpmMD5Update, &wrmd5);
  if (!obfp)
    {
      fprintf(stderr, "payload write error\n");
      exit(1);
    }
  if (d.compheadlen)
    {
      obfp->comp = d.targetcomp;
      obfp->len = d.compheadlen;
      obfp->write = cfile_write_uncomp;
    }
  if (fromrpm)
    fillblock_method = fillblock_rpm;
  else
    fillblock_method = fillblock_disk;
  if (fromrpm_raw)
    {
      fillblock_method = fillblock_rawrpm;
      if (outfp->unread(outfp, h->data, 16 * h->cnt + h->dcnt) || outfp->unread(outfp, h->intro, 16))
	{
	  fprintf(stderr, "could not unread header\n");
	  exit(1);
	}
      outfpleft_raw = d.outlen;
    }
  if (verbose)
    fprintf(vfp, "applying delta\n");
  idx = 0;
  paywritten = 0;
  inn = d.inn;
  outn = d.outn;
  in = d.in;
  out = d.out;
  off = 0;
  while (inn > 0)
    {
      on = *in++;
      if (on > outn)
	{
	  fprintf(stderr, "corrupt delta instructions\n");
	  exit(1);
	}
      while (on > 0)
	{
	  off += (int)*out++;
	  len = *out++;
	  paywritten += len;
	  outn--;
	  on--;
	  bs = off >> BLKSHIFT;
	  while (len > 0)
	    {
	      if (!lastblk || bs != lastblk->id)
		{
		  lastblk = vmem[bs];
		  if (!lastblk || lastblk->type == BLK_PAGE)
		    lastblk = getblock(bs, sdesc, nsdesc, &fb, idx);
		}
	      l = off & BLKMASK;
	      if (l + len > BLKSIZE)
		l = BLKSIZE - l;
	      else
		l = len;
	      b = lastblk->e.buf + (off & BLKMASK);
	      if (d.addblklen)
	        {
		  if (addblkcomp == CFILE_COMP_GZ)
		    {
		      addgzstrm.next_out = addblkbuf;
		      addgzstrm.avail_out = l;
		      inflate(&addgzstrm, Z_NO_FLUSH);
		      if (addgzstrm.avail_out != 0)
			{
			  fprintf(stderr, "addblk: inflate error\n");
			  exit(1);
			}
		    }
		  else
		    {
		      addbz2strm.next_out = (char *)addblkbuf;
		      addbz2strm.avail_out = l;
		      BZ2_bzDecompress(&addbz2strm);
		      if (addbz2strm.avail_out != 0)
			{
			  fprintf(stderr, "addblk: BZ2_bzDecompress error\n");
			  exit(1);
			}
		    }
		  for (i = 0; i < l; i++)
		    addblkbuf[i] += b[i];
		  b = addblkbuf;
	        }
	      if (obfp->write(obfp, b, l) != l)
		{
		  fprintf(stderr, "write error\n");
		  exit(1);
		}
	      len -= l;
	      off += l;
	      bs++;
	    }
	  idx++;
          if (percent)
	    {
	      if (d.paylen >= 0x1000000)
	        curpercent = (paywritten >> 8) * 100 / (d.paylen >> 8);
	      else if (d.paylen)
	        curpercent = paywritten * 100 / d.paylen;
	      else
	        curpercent = 0;
	      if (curpercent != lastpercent)
		{
		  if (percent > 1)
		    fprintf(vfp, "%d percent finished.\n", curpercent);
		  else
		    fprintf(vfp, "\r%d percent finished.", curpercent);
		  fflush(vfp);
		  lastpercent = curpercent;
		}
	    }
	}
      len = *in++;
      paywritten += len;
      while (len > 0)
	{
	  l = len > sizeof(buf) ? sizeof(buf) : len;
	  if (bfp->read(bfp, buf, l) != l)
	    {
	      fprintf(stderr, "%s: read error data area\n", deltarpm);
	      exit(1);
	    }
	  if (obfp->write(obfp, buf, l) != l)
	    {
	      fprintf(stderr, "write error\n");
	      exit(1);
	    }
	  len -= l;
	}
      inn--;
      if (percent)
	{
	  curpercent = paywritten * 100. / (d.paylen ? d.paylen : 1);
	  if (curpercent != lastpercent)
	    {
	      if (percent > 1)
		fprintf(vfp, "%d percent finished.\n", curpercent);
	      else
		fprintf(vfp, "\r%d percent finished.", curpercent);
	      fflush(vfp);
	      lastpercent = curpercent;
	    }
	}
    }
  if (percent > 1)
    fprintf(vfp, "100 percent finished.\n");
  else if (percent)
    fprintf(vfp, "\r100 percent finished.\n");
  if (obfp->close(obfp) == -1)
    {
      fprintf(stderr, "write error\n");
      exit(1);
    }
  if (fflush(ofp) || (ofp != stdout && fclose(ofp) != 0))
    {
      fprintf(stderr, "write error\n");
      exit(1);
    }
  if (outfp)
    outfp->close(outfp);
  if (bfp)
    bfp->close(bfp);
  if (d.addblklen)
    {
      if (addblkcomp == CFILE_COMP_GZ)
        inflateEnd(&addgzstrm);
      else
        BZ2_bzDecompressEnd(&addbz2strm);
    }
  if (verbose > 1)
    {
      fprintf(vfp, "used %d core pages\n", ncoreblk);
      fprintf(vfp, "used %d swap pages\n", npageblk);
      fprintf(vfp, "had to recreate %d core pages\n", ndropblk);
      if (nprelink)
        fprintf(vfp, "had to call prelink %d times\n", nprelink);
    }
  rpmMD5Final(wrmd5res, &wrmd5);
  if (nofullmd5)
    {
      struct rpmhead *dsigh = readhead_buf(d.lead + 96, d.leadl - 96, 0);
      if (dsigh)
        {
          unsigned char *hmd5 = headbin(dsigh, SIGTAG_MD5, 16);
          if (hmd5)
            {
              if (memcmp(wrmd5res, hmd5, 16) != 0)
                {
                  fprintf(stderr, "%s: md5 mismatch of result\n", deltarpm);
                  exit(1);
                }
            }
          xfree(dsigh);
        }
    }
  else if (memcmp(wrmd5res, d.targetmd5, 16) != 0)
    {
      fprintf(stderr, "%s: md5 mismatch of result\n", deltarpm);
      exit(1);
    }
  exit(0);
}
