File _service:obs_scm:BSSolv.xs of Package perl-BSSolv

/*
 * Copyright (c) 2009 - 2017 SUSE Linux Products GmbH
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the same terms as Perl itself.
 *
 */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#define MULTI_SEMANTICS

#include "solvversion.h"
#if LIBSOLV_VERSION < 623
#define LIBSOLVEXT_FEATURE_DEBIAN
#define LIBSOLVEXT_FEATURE_ARCHREPO
#endif

#include "pool.h"
#include "repo.h"
#include "util.h"
#include "evr.h"
#include "hash.h"
#include "chksum.h"
#include "testcase.h"
#include "repo_solv.h"
#include "repo_write.h"
#include "repo_rpmdb.h"
#if defined(LIBSOLVEXT_FEATURE_DEBIAN)
#include "repo_deb.h"
#endif
#if defined(LIBSOLVEXT_FEATURE_ARCHREPO)
#include "repo_arch.h"
#endif
#if defined(LIBSOLV_FEATURE_COMPLEX_DEPS)
#include "pool_parserpmrichdep.h"
#endif

#ifndef REL_ERROR
# define REL_ERROR 27		/* for old libsolv versions */
#endif
#ifndef REL_UNLESS
# define REL_UNLESS 29		/* for old libsolv versions */
#endif

#define EXPANDER_DEBUG_ALL		(1 << 0)
#define EXPANDER_DEBUG_STDOUT		(1 << 1)
#define EXPANDER_DEBUG_STR		(1 << 2)

#define EXPANDER_OPTION_IGNOREIGNORE			(1 << 0)
#define EXPANDER_OPTION_IGNORECONFLICTS			(1 << 1)
#define EXPANDER_OPTION_DORECOMMENDS			(1 << 2)
#define EXPANDER_OPTION_DOSUPPLEMENTS			(1 << 3)
#define EXPANDER_OPTION_USERECOMMENDSFORCHOICES		(1 << 4)
#define EXPANDER_OPTION_USESUPPLEMENTSFORCHOICES	(1 << 5)

typedef struct _Expander {
  Pool *pool;

  Map ignored;
  Map ignoredx;

  Queue preferposq;
  Map preferpos;
  Map preferposx;

  Map preferneg;
  Map prefernegx;

  Queue conflictsq;
  Map conflicts;

  int havefileprovides;

  /* debug support */
  int debug;
  char *debugstr;
  int debugstrl;
  int debugstrf;

  /* options */
  int ignoreconflicts;
  int ignoreignore;
  int userecommendsforchoices;
  int usesupplementsforchoices;
  int dorecommends;
  int dosupplements;
} Expander;

typedef struct _ExpanderCtx {
  Pool *pool;
  Expander *xp;
  Queue *out;			/* the result */
  Map installed;		/* installed packages */
  Map conflicts;		/* conflicts from installed packages */
  Queue conflictsinfo;		/* source info for the above */
  int cidone;			/* conflictsinfo done position */
  Queue todo;			/* requires todo list */
  Queue errors;			/* expansion errors */
  Queue cplxq;			/* complex dep work queue */
  Queue cplxblks;		/* complex dep block data, add only */
  Queue todo_cond;		/* delayed requires/conflicts */
  Queue pruneq;			/* multi purpose queue for pruning packages */
  Map todo_condmap;		/* all neg packages in todo_cond blocks */
  Map recommended;		/* recommended packages */
  int recdone;			/* recommended done position */

  /* options */
  int ignoreconflicts;
  int ignoreignore;
  int userecommendsforchoices;
  int usesupplementsforchoices;
  int dorecommends;
  int dosupplements;

  /* hacks */
  Solvable *ignore_s;		/* small hack: ignore requires of this solvable */
} ExpanderCtx;


typedef Pool *BSSolv__pool;
typedef Repo *BSSolv__repo;
typedef Expander *BSSolv__expander;

static Id buildservice_id;
static Id buildservice_repocookie;
static Id buildservice_external;
static Id buildservice_dodurl;
static Id buildservice_dodcookie;
static Id buildservice_dodresources;
static Id buildservice_annotation;
static Id buildservice_modules;
static Id expander_directdepsend;

static int genmetaalgo;
static int depsortsccs;

/* make sure bit n is usable */
#define MAPEXP(m, n) ((m)->size < (((n) + 8) >> 3) ? map_grow(m, n + 256) : 0)

#define REPOCOOKIE "buildservice repo 1.1"

static int
myrepowritefilter(Repo *repo, Repokey *key, void *kfdata)
{
  int i;
  if (key->name == SOLVABLE_URL)
    return KEY_STORAGE_DROPPED;
  if (key->name == SOLVABLE_HEADEREND)
    return KEY_STORAGE_DROPPED;
  if (key->name == SOLVABLE_PACKAGER)
    return KEY_STORAGE_DROPPED;
  if (key->name == SOLVABLE_GROUP)
    return KEY_STORAGE_DROPPED;
  if (key->name == SOLVABLE_LICENSE)
    return KEY_STORAGE_DROPPED;
  if (key->name == SOLVABLE_PKGID)
    return KEY_STORAGE_INCORE;
  if (key->name == SOLVABLE_CHECKSUM)
    return KEY_STORAGE_INCORE;
  i = repo_write_stdkeyfilter(repo, key, kfdata);
  if (i == KEY_STORAGE_VERTICAL_OFFSET)
    return KEY_STORAGE_DROPPED;
  return i;
}

static inline char *
hvlookupstr(HV *hv, const char *key, int keyl)
{
  SV **svp = hv_fetch(hv, key, keyl, 0);
  if (!svp)
    return 0;
  return SvPV_nolen(*svp);
}

static inline AV *
hvlookupav(HV *hv, const char *key, int keyl)
{
  SV *sv, **svp = hv_fetch(hv, key, keyl, 0);
  if (!svp)
    return 0;
  sv = *svp;
  if (!sv || !SvROK(sv) || SvTYPE(SvRV(sv)) != SVt_PVAV)
    return 0;
  return (AV *)SvRV(sv);
}

static Id
makeevr(Pool *pool, char *e, char *v, char *r)
{
  char *s;

  if (!v)
    return 0;
  if (e && !strcmp(e, "0"))
    e = 0;
  if (e)
    s = pool_tmpjoin(pool, e, ":", v);
  else
    s = v;
  if (r)
    s = pool_tmpjoin(pool, s, "-", r);
  return pool_str2id(pool, s, 1);
}

static inline char *
avlookupstr(AV *av, int n)
{
  SV **svp = av_fetch(av, n, 0);
  if (!svp)
    return 0;
  return SvPV_nolen(*svp);
}

static inline Id
id2name(Pool *pool, Id id)
{
  while (ISRELDEP(id))
    {
      Reldep *rd = GETRELDEP(pool, id);
      id = rd->name;
    }
  return id;
}

static Id
dep2id_rec(Pool *pool, char *s)
{
  char *n;
  Id id;
  int flags;

  if ((n = strchr(s, '|')) != 0)
    {
      id = dep2id_rec(pool, n + 1);
      *n = 0;
      id = pool_rel2id(pool, dep2id_rec(pool, s), id, REL_OR, 1);
      *n = '|';
      return id;
    }
  while (*s == ' ' || *s == '\t')
    s++;
  n = s;
  if (pool->disttype == DISTTYPE_RPM)
    {
      /* rpm delimits the name by whitespace only */
      while (*s && *s != ' ' && *s != '\t')
        s++;
    }
  else
    {
      while (*s && *s != ' ' && *s != '\t' && *s != '<' && *s != '=' && *s != '>')
        s++;
    }
#ifdef REL_MULTIARCH
  if (s - n > 4 && s[-4] == ':' && !strncmp(s - 4, ":any", 4))
    {
      id = pool_strn2id(pool, n, s - n - 4, 1);
      id = pool_rel2id(pool, id, ARCH_ANY, REL_MULTIARCH, 1);
    }
  else
#endif
    id = pool_strn2id(pool, n, s - n, 1);
  if (!*s)
    return id;
  while (*s == ' ' || *s == '\t')
    s++;
  flags = 0;
  for (;;s++)
    {
      if (*s == '<')
       flags |= REL_LT;
      else if (*s == '=')
       flags |= REL_EQ;
      else if (*s == '>')
       flags |= REL_GT;
      else
       break;
    }
  if (!flags)
    return id;
  while (*s == ' ' || *s == '\t')
    s++;
  n = s;
  while (*s && *s != ' ' && *s != '\t')
    s++;
  return pool_rel2id(pool, id, pool_strn2id(pool, n, s - n, 1), flags, 1);
}

static Id
parsedep_error(Pool *pool, const char *s)
{
  Id id;
  id = pool_str2id(pool, s, 1);
  return pool_rel2id(pool, pool_str2id(pool, "dependency parse error", 1), id, REL_ERROR, 1);
}

static Id
dep2id(Pool *pool, char *s)
{
  Id id;
  if (pool->disttype == DISTTYPE_RPM && *s == '(')
    {
#if defined(LIBSOLV_FEATURE_COMPLEX_DEPS)
      id = pool_parserpmrichdep(pool, s);
#else
      id = 0;
#endif
    }
  else
    id = dep2id_rec(pool, s);
  if (!id)
    id = parsedep_error(pool, s);
  return id;
}

static Offset
importdeps(HV *hv, const char *key, int keyl, Repo *repo)
{
  Pool *pool = repo->pool;
  SSize_t i;
  AV *av = hvlookupav(hv, key, keyl);
  Offset off = 0;
  if (av)
    {
      for (i = 0; i <= av_len(av); i++)
	{
	  char *str = avlookupstr(av, i);
	  if (str)
	    {
	      Id id = testcase_str2dep(pool, str);
	      if (!id)
		id = parsedep_error(pool, str);
	      off = repo_addid_dep(repo, off, id, 0);
	    }
	}
    }
  return off;
}

static void
exportdeps(HV *hv, const char *key, int keyl, Repo *repo, Offset off, Id skey)
{
  Pool *pool = repo->pool;
  AV *av;
  Id id, *pp;
  const char *str;

  if (!off || !repo->idarraydata[off])
    return;
  pp = repo->idarraydata + off;
  av = 0;
  while ((id = *pp++))
    {
      if (id == SOLVABLE_FILEMARKER)
	break;
      str = testcase_dep2str(pool, id);
      if (skey == SOLVABLE_REQUIRES)
	{
	  if (id == SOLVABLE_PREREQMARKER)
	    continue;
	  if (*str == 'r' && !strncmp(str, "rpmlib(", 7))
	    continue;
	}
      if (!av)
        av = newAV();
      av_push(av, newSVpv(str, 0));
    }
  if (av)
    (void)hv_store(hv, key, keyl, newRV_noinc((SV*)av), 0);
}

static int
data2pkg(Repo *repo, Repodata *data, HV *hv, int isdod)
{
  Pool *pool = repo->pool;
  char *str;
  Id p;
  Solvable *s;
  AV *av;

  str = hvlookupstr(hv, "name", 4);
  if (!str)
    return 0;	/* need to have a name */
  p = repo_add_solvable(repo);
  s = pool_id2solvable(pool, p);
  s->name = pool_str2id(pool, str, 1);
  str = hvlookupstr(hv, "arch", 4);
  if (!str)
    str = "";	/* dummy, need to have arch */
  s->arch = pool_str2id(pool, str, 1);
  s->evr = makeevr(pool, hvlookupstr(hv, "epoch", 5), hvlookupstr(hv, "version", 7), hvlookupstr(hv, "release", 7));
  str = hvlookupstr(hv, "path", 4);
  if (str)
    {
      char *ss = strrchr(str, '/');
      if (ss)
	{
	  *ss = 0;
	  repodata_set_str(data, p, SOLVABLE_MEDIADIR, str);
	  *ss++ = '/';
	}
      else
	ss = str;
      repodata_set_str(data, p, SOLVABLE_MEDIAFILE, ss);
    }
  if (isdod)
    repodata_set_str(data, p, buildservice_id, "dod");
  else
    {
      str = hvlookupstr(hv, "id", 2);
      if (str)
	repodata_set_str(data, p, buildservice_id, str);
    }
  str = hvlookupstr(hv, "source", 6);
  if (str)
    repodata_set_poolstr(data, p, SOLVABLE_SOURCENAME, str);
  if (isdod)
    {
      static unsigned char dod_pkgid[16] = { 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0 };
      repodata_set_bin_checksum(data, p, SOLVABLE_PKGID, REPOKEY_TYPE_MD5, dod_pkgid);
    }
  else
    {
      str = hvlookupstr(hv, "hdrmd5", 6);
      if (str && strlen(str) == 32)
	repodata_set_checksum(data, p, SOLVABLE_PKGID, REPOKEY_TYPE_MD5, str);
    }
  s->provides    = importdeps(hv, "provides", 8, repo);
  s->obsoletes   = importdeps(hv, "obsoletes", 9, repo);
  s->conflicts   = importdeps(hv, "conflicts", 9, repo);
  s->requires    = importdeps(hv, "requires", 8, repo);
  s->recommends  = importdeps(hv, "recommends", 10, repo);
  s->suggests    = importdeps(hv, "suggests", 8, repo);
  s->supplements = importdeps(hv, "supplements", 11, repo);
  s->enhances    = importdeps(hv, "enhances", 8, repo);
  if (!s->evr && s->provides)
    {
      /* look for self provides */
      Id pro, *prop = s->repo->idarraydata + s->provides;
      while ((pro = *prop++) != 0)
	{
	  Reldep *rd;
	  if (!ISRELDEP(pro))
	    continue;
	  rd = GETRELDEP(pool, pro);
	  if (rd->name == s->name && rd->flags == REL_EQ)
	    s->evr = rd->evr;
	}
    }
  if (s->evr && s->arch != ARCH_SRC && s->arch != ARCH_NOSRC)
    s->provides = repo_addid_dep(repo, s->provides, pool_rel2id(pool, s->name, s->evr, REL_EQ, 1), 0);
  str = hvlookupstr(hv, "checksum", 8);
  if (str)
    {
      char *cp, typebuf[8];
      Id ctype;
      if (*str != ':' && (cp = strchr(str, ':')) != 0 && cp - str < sizeof(typebuf))
	{
	  strncpy(typebuf, str, cp - str);
	  typebuf[cp - str] = 0;
	  ctype = solv_chksum_str2type(typebuf);
	  if (ctype)
	    repodata_set_checksum(data, p, SOLVABLE_CHECKSUM, ctype, cp + 1);
	}
    }
  str = hvlookupstr(hv, "annotation", 10);
  if (str && strlen(str) < 100000)
    repodata_set_str(data, p, buildservice_annotation, str);
  av = hvlookupav(hv, "modules", 7);
  if (av)
    {
      SSize_t i;
      for (i = 0; i <= av_len(av); i++)
	{
	  char *str = avlookupstr(av, i);
	  repodata_add_idarray(data, p, buildservice_modules, pool_str2id(pool, str, 1));
	}
    }
  return p;
}

static void
data2solvables(Repo *repo, Repodata *data, SV *rsv, int isdod)
{
  AV *rav = 0;
  SSize_t ravi = 0;
  HV *rhv = 0;
  SV *sv;
  char *key;
  I32 keyl;

  if (SvTYPE(rsv) == SVt_PVAV)
    rav = (AV *)rsv;
  else
    rhv = (HV *)rsv;

  if (rhv)
    hv_iterinit(rhv);
  for (;;)
    {
      if (rhv)
	{
	  sv = hv_iternextsv(rhv, &key, &keyl);
	  if (!sv)
	    break;
	}
      else
	{
	  SV **svp;
	  if (ravi > av_len(rav))
	    break;
	  svp = av_fetch(rav, ravi++, 0);
	  if (!svp || !*svp)
	    continue;
	  sv = *svp;
	}
      if (!SvROK(sv) || SvTYPE(SvRV(sv)) != SVt_PVHV)
	continue;
      data2pkg(repo, data, (HV *)SvRV(sv), isdod);
    }

  /* set meta information */
  repodata_set_str(data, SOLVID_META, buildservice_repocookie, REPOCOOKIE);
  if (rhv)
    {
      char *str;
      AV *av;
      str = hvlookupstr(rhv, "/url", 4);
      if (str)
	repodata_set_str(data, SOLVID_META, buildservice_dodurl, str);
      str = hvlookupstr(rhv, "/dodcookie", 10);
      if (str)
	repodata_set_str(data, SOLVID_META, buildservice_dodcookie, str);
      av = hvlookupav(rhv, "/dodresources", 13);
      if (av)
	{
	  SSize_t i;
	  for (i = 0; i <= av_len(av); i++)
	    {
	      Id id = pool_str2id(repo->pool, avlookupstr(av, i), 1);
	      repodata_add_idarray(data, SOLVID_META, buildservice_dodresources, id);
	    }
	}
    }
}

static SV *
retrieve(unsigned char **srcp, STRLEN *srclp, int depth)
{
  SV *sv, *rv;
  AV *av;
  HV *hv;
  unsigned char *src = *srcp;
  STRLEN srcl = *srclp;
  int type;
  unsigned int i, len;
  STRLEN size;

  if (depth > 10)
    return 0;
  if (srcl-- == 0)
    return 0;
  type = *src++;
  switch (type)
    {
    case 1:
      if (srcl < 4)
	return 0;
      size = src[0] << 24 | src[1] << 16 | src[2] << 8 | src[3];
      srcl -= 4;
      src += 4;
      if (srcl < size)
	return 0;
      sv = NEWSV(10002, size);
      sv_setpvn(sv, (char *)src, size);
      srcl -= size;
      src += size;
      break;
    case 2:
      if (srcl < 4)
	return 0;
      len = src[0] << 24 | src[1] << 16 | src[2] << 8 | src[3];
      srcl -= 4;
      src += 4;
      if (len > srcl)
	return 0;
      av = newAV();
      if (len)
	av_extend(av, len);
      for (i = 0; i < len; i++)
	{
	  sv = retrieve(&src, &srcl, depth + 1);
	  if (!sv)
	    return 0;
	  if (av_store(av, i, sv) == 0)
	    return 0;
	}
      sv = (SV *)av;
      break;
    case 3:
      if (srcl < 4)
	return 0;
      len = src[0] << 24 | src[1] << 16 | src[2] << 8 | src[3];
      srcl -= 4;
      src += 4;
      if (len > srcl)
	return 0;
      hv = newHV();
      if (len)
	hv_ksplit(hv, len + 1);
      for (i = 0; i < len; i++)
	{
	  sv = retrieve(&src, &srcl, depth + 1);
	  if (!sv) 
	    return 0;
	  if (srcl < 4)
	    return 0;
	  size = src[0] << 24 | src[1] << 16 | src[2] << 8 | src[3];
	  srcl -= 4;
	  src += 4;
	  if (srcl < size)
	    return 0;
          if (hv_store(hv, (char *)src, (U32)size, sv, 0) == 0)
	    return 0;
	  srcl -= size;
	  src += size;
	}
      sv = (SV *)hv;
      break;
    case 4:
      rv = NEWSV(10002, 0);
      sv = retrieve(&src, &srcl, depth + 1);
      if (!sv) 
	return 0;
      sv_upgrade(rv, SVt_RV);
      SvRV_set(rv, sv);
      SvROK_on(rv);
      sv = rv;
      break;
    case 10:
      if (srcl-- == 0)
	return 0;
      size = *src++;
      if (srcl < size)
	return 0;
      sv = NEWSV(10002, size);
      sv_setpvn(sv, (char *)src, size);
      srcl -= size;
      src += size;
      break;
    default:
      /* fprintf(stderr, "unknown tag %d\n", type); */
      return 0;
    }
  *srcp = src;
  *srclp = srcl;
  return sv;
}

#define CPLXDEPS_TODNF (1 << 0)

static int
invert_depblocks(ExpanderCtx *xpctx, Queue *bq, int start, int r)
{
  int i, j, end;
  if (r == 0 || r == 1)
    return r ? 0 : 1;
  end = bq->count;
  for (i = j = start; i < end; i++)
    {
      if (bq->elements[i])
        {
          bq->elements[i] = -bq->elements[i];
          continue;
        }
      /* end of block reached, reverse */
      if (i - 1 > j)
        {
          int k;
          for (k = i - 1; j < k; j++, k--)
            {
              Id t = bq->elements[j];
              bq->elements[j] = bq->elements[k];
              bq->elements[k] = t;
            }
        }
      j = i + 1;
    }
  return -1;
}

static int
distribute_depblocks(ExpanderCtx *xpctx, Queue *bq, int start, int start2, int flags)
{
  int i, j, end2 = bq->count;
  for (i = start; i < start2; i++)
    {
      for (j = start2; j < end2; j++)
	{
	  int a, b;
	  int bqcnt4 = bq->count;
	  int k = i;

	  /* distribute i block with j block, both blocks are sorted */
	  while (bq->elements[k] && bq->elements[j])
	    {
	      if (bq->elements[k] < bq->elements[j])
		queue_push(bq, bq->elements[k++]);
	      else
		{
		  if (bq->elements[k] == bq->elements[j])
		    k++;
		  queue_push(bq, bq->elements[j++]);
		}
	    }
	  while (bq->elements[j])
	    queue_push(bq, bq->elements[j++]);
	  while (bq->elements[k])
	    queue_push(bq, bq->elements[k++]);

	  /* block is finished, check for A + -A */
	  for (a = bqcnt4, b = bq->count - 1; a < b; )
	    {
	      if (-bq->elements[a] == bq->elements[b])
		break;
	      if (-bq->elements[a] > bq->elements[b])
		a++;
	      else
		b--;
	    }
	  if (a < b)
	    queue_truncate(bq, bqcnt4);     /* ignore this block */
	  else
	    queue_push(bq, 0);      /* finish block */
	}
      /* advance to next block */
      while (bq->elements[i])
	i++;
    }
  queue_deleten(bq, start, end2 - start);
  if (start == bq->count)
    return flags & CPLXDEPS_TODNF ? 0 : 1;
  return -1;
}

#if 0
static void
print_depblocks(ExpanderCtx *xpctx, Queue *bq, int start, int r)
{
  Pool *pool = xpctx->pool;
  int i;

  if (r == 0)
    {
      printf("[NONE]\n");
      return;
    }
  if (r == 1)
    {
      printf("[ALL]\n");
      return;
    }
  for (i = start; i < bq->count; i++)
    {
      if (bq->elements[i] > 0)
        printf(" %s", pool_solvid2str(pool, bq->elements[i]));
      else if (bq->elements[i] < 0)
        printf(" -%s", pool_solvid2str(pool, -bq->elements[i]));
      else
        printf(" ||");
    }
  printf("\n");
}
#endif


static int
pool_is_complex_dep_rd(Pool *pool, Reldep *rd)
{
  for (;;)
    {
      if (rd->flags == REL_AND || rd->flags == REL_COND || rd->flags == REL_UNLESS)        /* those are the complex ones */
        return 1;
      if (rd->flags != REL_OR)
        return 0;
      if (ISRELDEP(rd->name) && pool_is_complex_dep_rd(pool, GETRELDEP(pool, rd->name)))
        return 1;
      if (!ISRELDEP(rd->evr))
        return 0;
      rd = GETRELDEP(pool, rd->evr);
    }
}

static inline int
pool_is_complex_dep(Pool *pool, Id dep)
{
  if (ISRELDEP(dep))
    {
      Reldep *rd = GETRELDEP(pool, dep);
      if (rd->flags >= 8 && pool_is_complex_dep_rd(pool, rd))
        return 1;
    }
  return 0;
}

static int normalize_dep(ExpanderCtx *xpctx, Id dep, Queue *bq, int flags);

static int
normalize_dep_or(ExpanderCtx *xpctx, Id dep1, Id dep2, Queue *bq, int flags, int invflags)
{
  int r1, r2, bqcnt2, bqcnt = bq->count;
  r1 = normalize_dep(xpctx, dep1, bq, flags);
  if (r1 == 1)
    return 1;		/* early exit */
  bqcnt2 = bq->count;
  r2 = normalize_dep(xpctx, dep2, bq, flags ^ invflags);
  if (invflags)
    r2 = invert_depblocks(xpctx, bq, bqcnt2, r2);
  if (r1 == 1 || r2 == 1)
    {
      queue_truncate(bq, bqcnt);
      return 1;
    }
  if (r1 == 0)
    return r2;
  if (r2 == 0)
    return r1;
  if ((flags & CPLXDEPS_TODNF) == 0)
    return distribute_depblocks(xpctx, bq, bqcnt, bqcnt2, flags);
  return -1;
}

static int
normalize_dep_and(ExpanderCtx *xpctx, Id dep1, Id dep2, Queue *bq, int flags, int invflags)
{
  int r1, r2, bqcnt2, bqcnt = bq->count;
  r1 = normalize_dep(xpctx, dep1, bq, flags);
  if (r1 == 0)
    return 0;		/* early exit */
  bqcnt2 = bq->count;
  r2 = normalize_dep(xpctx, dep2, bq, flags ^ invflags);
  if (invflags)
    r2 = invert_depblocks(xpctx, bq, bqcnt2, r2);
  if (r1 == 0 || r2 == 0)
    {
      queue_truncate(bq, bqcnt);
      return 0;
    }
  if (r1 == 1)
    return r2;
  if (r2 == 1)
    return r1;
  if ((flags & CPLXDEPS_TODNF) != 0)
    return distribute_depblocks(xpctx, bq, bqcnt, bqcnt2, flags);
  return -1;
}

static int
normalize_dep_if_else(ExpanderCtx *xpctx, Id dep1, Id dep2, Id dep3, Queue *bq, int flags)
{
  /* A IF (B ELSE C) -> (A OR ~B) AND (C OR B) */
  int r1, r2, bqcnt2, bqcnt = bq->count;
  r1 = normalize_dep_or(xpctx, dep1, dep2, bq, flags, CPLXDEPS_TODNF);
  if (r1 == 0)
    return 0;		/* early exit */
  bqcnt2 = bq->count;
  r2 = normalize_dep_or(xpctx, dep2, dep3, bq, flags, 0);
  if (r1 == 0 || r2 == 0)
    {
      queue_truncate(bq, bqcnt);
      return 0;
    }
  if (r1 == 1)
    return r2;
  if (r2 == 1)
    return r1;
  if ((flags & CPLXDEPS_TODNF) != 0)
    return distribute_depblocks(xpctx, bq, bqcnt, bqcnt2, flags);
  return -1;
}

static int
normalize_dep_unless_else(ExpanderCtx *xpctx, Id dep1, Id dep2, Id dep3, Queue *bq, int flags)
{
  /* A UNLESS (B ELSE C) -> (A AND ~B) OR (C AND B) */
  int r1, r2, bqcnt2, bqcnt = bq->count;
  r1 = normalize_dep_and(xpctx, dep1, dep2, bq, flags, CPLXDEPS_TODNF);
  if (r1 == 1)
    return 1;		/* early exit */
  bqcnt2 = bq->count;
  r2 = normalize_dep_and(xpctx, dep2, dep3, bq, flags, 0);
  if (r1 == 1 || r2 == 1)
    {
      queue_truncate(bq, bqcnt);
      return 1;
    }
  if (r1 == 0)
    return r2;
  if (r2 == 0)
    return r1;
  if ((flags & CPLXDEPS_TODNF) == 0)
    return distribute_depblocks(xpctx, bq, bqcnt, bqcnt2, flags);
  return -1;
}

static int expander_isignored(Expander *xp, Solvable *s, Id req);

static int
normalize_dep(ExpanderCtx *xpctx, Id dep, Queue *bq, int flags)
{
  Pool *pool = xpctx->pool;
  Id p, dp;
  
  if (pool_is_complex_dep(pool, dep))
    {
      Reldep *rd = GETRELDEP(pool, dep);
      if (rd->flags == REL_COND)
	{
	  Id evr = rd->evr;
	  if (ISRELDEP(evr))
	    {
	      Reldep *rd2 = GETRELDEP(pool, evr);
	      if (rd2->flags == REL_ELSE)
		return normalize_dep_if_else(xpctx, rd->name, rd2->name, rd2->evr, bq, flags);
	    }
	  return normalize_dep_or(xpctx, rd->name, rd->evr, bq, flags, CPLXDEPS_TODNF);
	}
      if (rd->flags == REL_UNLESS)
	{
	  Id evr = rd->evr;
	  if (ISRELDEP(evr))
	    {
	      Reldep *rd2 = GETRELDEP(pool, evr);
	      if (rd2->flags == REL_ELSE)
		return normalize_dep_unless_else(xpctx, rd->name, rd2->name, rd2->evr, bq, flags);
	    }
	  return normalize_dep_and(xpctx, rd->name, rd->evr, bq, flags, CPLXDEPS_TODNF);
	}
      if (rd->flags == REL_OR)
	return normalize_dep_or(xpctx, rd->name, rd->evr, bq, flags, 0);
      if (rd->flags == REL_AND)
	return normalize_dep_and(xpctx, rd->name, rd->evr, bq, flags, 0);
    }

  if (xpctx->ignore_s && (flags & CPLXDEPS_TODNF) == 0)
    {
      if (expander_isignored(xpctx->xp, xpctx->ignore_s, dep))
	return 1;
    }

  dp = pool_whatprovides(pool, dep);
  if (dp == 2)
    return 1;
  if (dp < 2 || !pool->whatprovidesdata[dp])
    return 0;
  if (pool->whatprovidesdata[dp] == SYSTEMSOLVABLE)
    return 1;
  if ((flags & CPLXDEPS_TODNF) != 0)
    {
      while ((p = pool->whatprovidesdata[dp++]) != 0)
        queue_push2(bq, p, 0);
    }
  else
    {
      while ((p = pool->whatprovidesdata[dp++]) != 0)
        queue_push(bq, p);
      queue_push(bq, 0);
    }
  return -1;
}

#define ISCPLX(pool, d) (ISRELDEP(d) && GETRELID(d) >= pool->nrels)
#define GETCPLX(pool, d) (GETRELID(d) - pool->nrels)
#define MAKECPLX(pool, d) (MAKERELDEP(pool->nrels + d))

#define DEPTYPE_REQUIRES		0
#define DEPTYPE_CONFLICTS		1
#define DEPTYPE_OBSOLETES		2
#define DEPTYPE_RECOMMENDS		3
#define DEPTYPE_PROVIDES		4

#define ERROR_NOPROVIDER		1
#define ERROR_CHOICE			2
#define ERROR_CONFLICTINGPROVIDERS	3
#define ERROR_PROVIDERINFO		4
#define ERROR_PROVIDERINFO2		5
#define ERROR_BADDEPENDENCY		6
#define ERROR_CONFLICT			7
#define ERROR_CONFLICT2			8
#define ERROR_ALLCONFLICT		9
#define ERROR_NOPROVIDERINFO		10

static void
expander_dbg(Expander *xp, const char *format, ...)
{
  va_list args;
  char buf[1024];
  int l;

  if (!xp->debug)
    return;
  va_start(args, format);
  vsnprintf(buf, sizeof(buf), format, args);
  va_end(args);
  l = strlen(buf);
  if ((xp->debug & (EXPANDER_DEBUG_ALL | EXPANDER_DEBUG_STDOUT)) != 0)
    {
      printf("%s", buf);
      if (buf[0] != ' ' || (l && buf[l - 1] == '\n'))
        fflush(stdout);
    }
  if ((xp->debug & (EXPANDER_DEBUG_ALL | EXPANDER_DEBUG_STR)) != 0)
    {
      if (l >= xp->debugstrf)	/* >= because of trailing \0 */
	{
	  xp->debugstr = solv_realloc(xp->debugstr, xp->debugstrl + l + 1024);
	  xp->debugstrf = l + 1024;
	}
      strcpy(xp->debugstr + xp->debugstrl, buf);
      xp->debugstrl += l;
      xp->debugstrf -= l;
    }
}

static void
expander_clrdbg(Expander *xp)
{
  if (xp->debugstr)
    free(xp->debugstr);
  xp->debugstr = 0;
  xp->debugstrl = xp->debugstrf = 0;
}

static const char *
expander_solvid2name(Expander *xp, Id p)
{
  const char *n = pool_id2str(xp->pool, xp->pool->solvables[p].name);
  Repo *r; 
  if (!xp->debug)
    return n;
  r = xp->pool->solvables[p].repo;
  if (!r) 
    return n;
  return pool_tmpjoin(xp->pool, n, "@", r->name);
}

static const char *
expander_solvid2str(Expander *xp, Id p)
{
  const char *n = pool_solvid2str(xp->pool, p);
  Repo *r; 
  if (!xp->debug)
    return n;
  r = xp->pool->solvables[p].repo;
  if (!r) 
    return n;
  return pool_tmpjoin(xp->pool, n, "@", r->name);
}

static int
pkgname_sort_cmp(const void *ap, const void *bp, void *dp)
{
  Pool *pool = (Pool *)dp;
  Id a = *(Id *)ap;
  Id b = *(Id *)bp;
  return strcmp(pool_id2str(pool, pool->solvables[a].name), pool_id2str(pool, pool->solvables[b].name));
}

static int
expander_isignored(Expander *xp, Solvable *s, Id req)
{
  Pool *pool = xp->pool;
  Id id = id2name(pool, req);
  const char *n;

  if (!xp->ignoreignore)
    {
      if (MAPTST(&xp->ignored, id))
	return 1;
      if (MAPTST(&xp->ignoredx, id))
	{
	  Id xid = pool_str2id(pool, pool_tmpjoin(pool, pool_id2str(pool, s->name), ":", pool_id2str(pool, id)), 0);
	  if (xid && MAPTST(&xp->ignored, xid))
	    return 1;
	}
    }
  n = pool_id2str(pool, id);
  if (!strncmp(n, "rpmlib(", 7))
    {
      MAPEXP(&xp->ignored, id);
      MAPSET(&xp->ignored, id);
      return 1;
    }
  if (*n == '/')
    {
      if (!xp->havefileprovides || pool->whatprovides[id] <= 1)
	{
	  MAPEXP(&xp->ignored, id);
	  MAPSET(&xp->ignored, id);
	  return 1;
	}
    }
  return 0;
}

static int
expander_to_cplxblks(ExpanderCtx *xpctx, Id p, Id dep, int deptype, Id *ptr)
{
  int blkoff = xpctx->cplxblks.count;
  queue_push(&xpctx->cplxblks, p);
  queue_push(&xpctx->cplxblks, dep);
  queue_push(&xpctx->cplxblks, deptype);
  for (;;)
    {
      Id pp = *ptr++;
      queue_push(&xpctx->cplxblks, pp);
      if (!pp)
	break;
    }
  return blkoff;
}

static int
expander_check_cplxblock(ExpanderCtx *xpctx, Id p, Id dep, int deptype, Id *ptr, int blkoff)
{
  Pool *pool = xpctx->pool;
  int posn = 0, posi = 0, negn = 0, negi = 0;
  Id pp, *ptr2 = ptr;
  Id lastcon = 0;

  while ((pp = *ptr2++) != 0)
    {
      if (pp > 0)
	{
	  posn++;
	  if (MAPTST(&xpctx->installed, pp))
	    posi++;
	}
      else
	{
	  if (p == -pp)
	    continue;	/* ignore redundant self-entry */
	  negn++;
	  if (MAPTST(&xpctx->installed, -pp))
	    negi++;
	  else
	    lastcon = -pp;
	}
    }
#if 0
  printf("expander_check_cplxblock pos: %d,%d neg: %d,%d\n", posn, posi, negn, negi);
#endif
  if (posi)
    return -1;
  if (!posn && deptype == DEPTYPE_RECOMMENDS)
    return -1;
  if (negi == negn)
    {
      /* all neg installed */
      if (posn)
	{
	  /* posn > 0 and all neg installed, add to todo */
	  if (blkoff < 0)
	    blkoff = expander_to_cplxblks(xpctx, p, dep, deptype, ptr);
#if 0
	  printf("put on todo, blkoff = %d\n", blkoff);
#endif
	  queue_push2(&xpctx->todo, MAKECPLX(pool, blkoff), p);
	}
      else
	{
	  /* no posn, conflict */
	  for (ptr2 = ptr; (pp = *ptr2++) != 0; )
	    {
	      if (p == -pp)
		continue;	/* ignore redundant self-entry */
	      if (deptype == DEPTYPE_REQUIRES)
		{
		  /* do not report a requires as conflicts */
		  queue_push(&xpctx->errors, ERROR_NOPROVIDER);
		  queue_push2(&xpctx->errors, dep, p);
		  break;
		}
	      queue_push(&xpctx->errors, ERROR_CONFLICT);
	      queue_push2(&xpctx->errors, p, -pp);
	    }
	}
      return -1;
    }
  else if (!posn && negn && negi == negn - 1)
    {
      /* add conflict */
#if 0
      printf("add conflict %d %d\n", lastcon, p);
#endif
      MAPEXP(&xpctx->conflicts, pool->nsolvables);
      MAPSET(&xpctx->conflicts, lastcon);
      if (p)
        queue_push2(&xpctx->conflictsinfo, lastcon, p);	/* always do this for rich deps */
      return -1;
    }
  else
    {
#ifdef DEBUG_COND
      printf("put/stay on cond queue, blkoff = %d\n", blkoff);
#endif
      /* either posn > 0 and 1 neg not installed or more than 1 neg not installed */
      if (blkoff < 0)
	blkoff = expander_to_cplxblks(xpctx, p, dep, deptype, ptr);
      return blkoff;
    }
}

static void
expander_installed_complexdep(ExpanderCtx *xpctx, Id p, Id dep, int deptype)
{
  Queue *cplxq = &xpctx->cplxq;
  int r, i, start = cplxq->count, blkoff;

#if 0
  printf("expander_installed_complexdep %s type %d\n", pool_dep2str(xpctx->pool, dep), deptype);
#endif
  if (deptype == DEPTYPE_CONFLICTS)
    {
      r = normalize_dep(xpctx, dep, cplxq, CPLXDEPS_TODNF);
      r = invert_depblocks(xpctx, cplxq, start, r);
    }
  else
    r = normalize_dep(xpctx, dep, cplxq, 0);
#if 0
  print_depblocks(xpctx, cplxq, start, r);
#endif
  if (r == 1)
    return;
  if (r == 0)
    {
      if (deptype == DEPTYPE_CONFLICTS)
	{
	  queue_push(&xpctx->errors, ERROR_ALLCONFLICT);
	  queue_push2(&xpctx->errors, dep, p);
	}
      else if (deptype != DEPTYPE_RECOMMENDS)
	{
	  queue_push(&xpctx->errors, ERROR_NOPROVIDER);
	  queue_push2(&xpctx->errors, dep, p);
	}
      return;
    }
  while (start < cplxq->count)
    {
      /* find end */
      for (i = start; cplxq->elements[i] != 0; i++)
	;
      blkoff = expander_check_cplxblock(xpctx, p, dep, deptype, cplxq->elements + start, -1);
      if (blkoff >= 0)
	{
	  Pool *pool = xpctx->pool;
	  Id p;
	  MAPEXP(&xpctx->todo_condmap, pool->nsolvables);
	  for (i = start; (p = cplxq->elements[i]) != 0; i++)
	    if (p < 0)
	      MAPSET(&xpctx->todo_condmap, -p);
	  queue_push(&xpctx->todo_cond, blkoff);
	}
      start = i + 1;
    }
  queue_truncate(cplxq, start);
}

static int
expander_checkconflicts_complexdep(ExpanderCtx *xpctx, Id p, Id dep, int deptype, int recorderrors)
{
  Queue *cplxq = &xpctx->cplxq;
  int r, i, start = cplxq->count;
  Id pp;
  int ret = 0;

#if 0
  printf("expander_checkconflicts_complexdep %s type %d\n", pool_dep2str(xpctx->pool, dep), deptype);
#endif
  if (deptype == DEPTYPE_CONFLICTS)
    {
      r = normalize_dep(xpctx, dep, cplxq, CPLXDEPS_TODNF);
      r = invert_depblocks(xpctx, cplxq, start, r);
    }
  else
    r = normalize_dep(xpctx, dep, cplxq, 0);
#if 0
  print_depblocks(xpctx, cplxq, start, r);
#endif
  /* r == 0: conflict with everything. Ignore here, pick error up when package gets installed */
  if (r == 0 || r == 1)
    return 0;
  while (start < cplxq->count)
    {
      for (i = start; (pp = cplxq->elements[i]) != 0; i++)
        if (pp > 0 || (pp < 0 && !MAPTST(&xpctx->installed, -pp)))
	  break;
      if (pp == 0)
	{
	  /* no pos and all neg installed -> conflict */
	  for (i = start; (pp = cplxq->elements[i]) != 0; i++)
	    {
	      pp = -cplxq->elements[i];
	      if (recorderrors)
		{
		  queue_push(&xpctx->errors, recorderrors == 2 ? ERROR_CONFLICT : ERROR_PROVIDERINFO);
		  queue_push2(&xpctx->errors, p, pp);
		}
	      else if (xpctx->xp->debug)
		{
		  Pool *pool = xpctx->pool;
		  expander_dbg(xpctx->xp, "ignoring provider %s because it conflicts with installed %s\n", pool_solvid2str(pool, p), pool_solvid2str(pool, pp));
		}
	      ret = ret ? 1 : pp;
	    }
	}
      for (; cplxq->elements[i] != 0; i++)
	;
      start = i + 1;
    }
  queue_truncate(cplxq, start);
  return ret;
}

static void
updateconflictsinfo(ExpanderCtx *xpctx)
{
  int i;
  Pool *pool = xpctx->pool;
  Queue *out = xpctx->out;
  Queue *conflictsinfo = &xpctx->conflictsinfo;

  if (xpctx->ignoreconflicts)
    return;
  for (i = xpctx->cidone; i < out->count; i++)
    {
      Id p, p2, pp2;
      Id con, *conp;
      Solvable *s;
      p = out->elements[i];
      s = pool->solvables + p;
      /* keep in sync with expander_installed! */
      if (s->conflicts)
	{
	  conp = s->repo->idarraydata + s->conflicts;
	  while ((con = *conp++) != 0)
	    {
	      if (pool_is_complex_dep(pool, con))
		continue;	/* already pushed */
	      FOR_PROVIDES(p2, pp2, con)
		{
		  if (p2 == p)
		    continue;
		  queue_push2(conflictsinfo, p2, p);
		}
	    }
	}
      if (s->obsoletes)
	{
	  conp = s->repo->idarraydata + s->obsoletes;
	  while ((con = *conp++) != 0)
	    {
	      FOR_PROVIDES(p2, pp2, con)
		{
		  if (p2 == p || !pool_match_nevr(pool, pool->solvables + p2, con))
		    continue;
		  queue_push2(conflictsinfo, p2, -p);
		}
	    }
	}
    }
  xpctx->cidone = out->count;
}

static int
findconflictsinfo(ExpanderCtx *xpctx, Id p, int recorderrors)
{
  Queue *conflictsinfo = &xpctx->conflictsinfo;
  int i, ret = 0;

  if (xpctx->cidone < xpctx->out->count)
    updateconflictsinfo(xpctx);

  for (i = 0; i < conflictsinfo->count; i++)
    if (conflictsinfo->elements[i] == p)
      {
	ret = conflictsinfo->elements[i + 1];
	if (recorderrors)
	  {
	    queue_push(&xpctx->errors, recorderrors == 2 ? ERROR_CONFLICT2 : ERROR_PROVIDERINFO2);
	    queue_push2(&xpctx->errors, p, ret);
	  }
	else if (xpctx->xp->debug)
	  {
	    Pool *pool = xpctx->pool;
	    expander_dbg(xpctx->xp, "ignoring provider %s because installed %s %s it\n", pool_solvid2str(pool, p), pool_solvid2str(pool, ret > 0 ? ret : -ret), ret > 0 ? "conflicts with" : "obsoletes");
	  }
      }
  if (!ret)
    {
      /* conflict from our job, i.e. a !xxx dep */
      if (recorderrors)
	{
	  queue_push(&xpctx->errors, recorderrors == 2 ? ERROR_CONFLICT2 : ERROR_PROVIDERINFO2);
	  queue_push2(&xpctx->errors, p, 0);
	}
      else if (xpctx->xp->debug)
	{
	  Pool *pool = xpctx->pool;
	  expander_dbg(xpctx->xp, "ignoring conflicted provider %s\n", pool_solvid2str(pool, p));
	}
    }
  return ret;
}


static void
recheck_conddeps(ExpanderCtx *xpctx)
{
  int i;
  for (i = 0; i < xpctx->todo_cond.count; i++)
    {
      int blkoff = xpctx->todo_cond.elements[i];
#ifdef DEBUG_COND
      printf("todo_cond %d\n", blkoff);
#endif
      Id *ptr = xpctx->cplxblks.elements + blkoff;
      if (expander_check_cplxblock(xpctx, ptr[0], ptr[1], ptr[2], ptr + 3, blkoff) < 0)
	{
#ifdef DEBUG_COND
	  printf("remove no longer needed cond entry\n");
#endif
	  queue_delete(&xpctx->todo_cond, i);
	  i--;
	}
    }
}

/* install a single package */
static void
expander_installed(ExpanderCtx *xpctx, Id p)
{
  Pool *pool = xpctx->pool;
  Expander *xp = xpctx->xp;
  Solvable *s = pool->solvables + p;
  Id req, *reqp, con, *conp;

#if 0
printf("expander_installed %s\n", pool_solvid2str(pool, p));
#endif
  MAPSET(&xpctx->installed, p);
  queue_push(xpctx->out, p);

  if (xpctx->conflicts.size && MAPTST(&xpctx->conflicts, p))
    findconflictsinfo(xpctx, p, 2);

  /* add synthetic conflicts from the project config */
  if (MAPTST(&xp->conflicts, s->name))
    {
      int i;
      for (i = 0; i < xp->conflictsq.count; i++)
	{
	  Id p2, pp2;
	  Id id = xp->conflictsq.elements[i];
	  if (id != s->name)
	    continue;
	  id = xp->conflictsq.elements[i ^ 1];
	  FOR_PROVIDES(p2, pp2, id)
	    {
	      if (pool->solvables[p2].name != id)
		continue;
	      if (MAPTST(&xpctx->installed, p2))
		{
		  queue_push(&xpctx->errors, ERROR_CONFLICT);
		  queue_push2(&xpctx->errors, p, p2);
		  continue;
		}
	      MAPEXP(&xpctx->conflicts, pool->nsolvables);
	      MAPSET(&xpctx->conflicts, p2);
	      queue_push2(&xpctx->conflictsinfo, p2, p);
	    }
	}
    }

  if (s->requires)
    {
      reqp = s->repo->idarraydata + s->requires;
      while ((req = *reqp++) != 0)
	{
	  if (req == SOLVABLE_PREREQMARKER)
	    continue;
	  if (ISRELDEP(req) && GETRELDEP(pool, req)->flags == REL_ERROR)
	    {
	      queue_push(&xpctx->errors, ERROR_BADDEPENDENCY);
	      queue_push2(&xpctx->errors, GETRELDEP(pool, req)->evr, p);
	      continue;
	    }
	  if (pool_is_complex_dep(pool, req))
	    {
	      xpctx->ignore_s = s;
	      expander_installed_complexdep(xpctx, p, req, DEPTYPE_REQUIRES);
	      xpctx->ignore_s = 0;
	      continue;
	    }
	  if (expander_isignored(xp, s, req))
	    continue;
	  queue_push2(&xpctx->todo, req, p);
	}
    }
  if (!xpctx->ignoreconflicts)
    {
      if (s->conflicts)
	{
	  conp = s->repo->idarraydata + s->conflicts;
	  while ((con = *conp++) != 0)
	    {
	      Id p2, pp2;
	      if (ISRELDEP(con) && GETRELDEP(pool, con)->flags == REL_ERROR)
		{
		  queue_push(&xpctx->errors, ERROR_BADDEPENDENCY);
		  queue_push2(&xpctx->errors, GETRELDEP(pool, con)->evr, p);
		  continue;
		}
	      if (pool_is_complex_dep(pool, con))
		{
		  expander_installed_complexdep(xpctx, p, con, DEPTYPE_CONFLICTS);
		  continue;
		}
	      FOR_PROVIDES(p2, pp2, con)
		{
		  if (p2 == p)
		    continue;
		  if (MAPTST(&xpctx->installed, p2))
		    {
		      queue_push(&xpctx->errors, ERROR_CONFLICT);
		      queue_push2(&xpctx->errors, p, p2);
		      continue;
		    }
		  MAPEXP(&xpctx->conflicts, pool->nsolvables);
		  MAPSET(&xpctx->conflicts, p2);
		  if (xp->debug)
		    queue_push2(&xpctx->conflictsinfo, p2, p);
		}
	    }
	}
      if (s->obsoletes)
	{
	  conp = s->repo->idarraydata + s->obsoletes;
	  while ((con = *conp++) != 0)
	    {
	      Id p2, pp2;
	      FOR_PROVIDES(p2, pp2, con)
		{
		  if (p2 == p || !pool_match_nevr(pool, pool->solvables + p2, con))
		    continue;
		  if (MAPTST(&xpctx->installed, p2))
		    {
		      queue_push(&xpctx->errors, ERROR_CONFLICT);
		      queue_push2(&xpctx->errors, p, -p2);
		      continue;
		    }
		  MAPEXP(&xpctx->conflicts, pool->nsolvables);
		  MAPSET(&xpctx->conflicts, p2);
		  if (xp->debug)
		    queue_push2(&xpctx->conflictsinfo, p2, -p);
		}
	    }
	}
      if (xp->debug)
	xpctx->cidone = xpctx->out->count;
    }
  if (xpctx->todo_condmap.size && MAPTST(&xpctx->todo_condmap, p))
    recheck_conddeps(xpctx);
}

/* same as expander_installed, but install multiple packages
 * in one block */
static void
expander_installed_multiple(ExpanderCtx *xpctx, Queue *toinstall)
{
  int i, j, havecond = 0;

  /* unify */
  for (i = j = 0; i < toinstall->count; i++)
    {
      Id p = toinstall->elements[i];
      if (MAPTST(&xpctx->installed, p))
	continue;	/* already seen */
      MAPSET(&xpctx->installed, p);
      toinstall->elements[j++] = p;
      if (xpctx->todo_condmap.size && MAPTST(&xpctx->todo_condmap, p))
	{
	  havecond = 1;
	  MAPCLR(&xpctx->todo_condmap, p);	/* no longer needed */
	}
    }
  queue_truncate(toinstall, j);
  
  /* run conditionals first */
  if (havecond)
    recheck_conddeps(xpctx);

  if (!xpctx->errors.count)
    for (i = 0; i < toinstall->count; i++)
      expander_installed(xpctx, toinstall->elements[i]);
  queue_empty(toinstall);
}

static int
expander_checkconflicts(ExpanderCtx *xpctx, Id p, Id *conflicts, int isobsoletes, int recorderrors)
{
  Map *installed = &xpctx->installed;
  Pool *pool = xpctx->pool;
  Id con, p2, pp2;
  int ret = 0;

  if (xpctx->ignoreconflicts)
    return 0;
  while ((con = *conflicts++) != 0)
    {
      if (!isobsoletes && pool_is_complex_dep(pool, con))
	{
	  p2 = expander_checkconflicts_complexdep(xpctx, p, con, DEPTYPE_CONFLICTS, recorderrors);
	  ret = ret ? 1 : p2;
	  continue;
	}
      FOR_PROVIDES(p2, pp2, con)
	{
	  if (p == p2)
	    continue;
	  if (isobsoletes && !pool_match_nevr(pool, pool->solvables + p2, con))
	    continue;
	  if (MAPTST(installed, p2))
	    {
	      if (recorderrors)
		{
		  queue_push(&xpctx->errors, recorderrors == 2 ? ERROR_CONFLICT : ERROR_PROVIDERINFO);
		  queue_push2(&xpctx->errors, p, isobsoletes ? -p2 : p2);
		}
	      else if (xpctx->xp->debug)
		{
		  if (isobsoletes)
		    expander_dbg(xpctx->xp, "ignoring provider %s because it obsoletes installed %s\n", pool_solvid2str(pool, p), pool_solvid2str(pool, p2));
		  else
		    expander_dbg(xpctx->xp, "ignoring provider %s because it conflicts with installed %s\n", pool_solvid2str(pool, p), pool_solvid2str(pool, p2));
		}
	      ret = ret ? 1 : p2;
	    }
	}
    }
  return ret;
}

static void
expander_updaterecommendedmap(ExpanderCtx *xpctx)
{
  Pool *pool = xpctx->pool;
  Queue *out = xpctx->out;
  Map *recommended = &xpctx->recommended;

  int i;
  Id p, pp, rec, *recp;
  for (i = xpctx->recdone; i < out->count; i++)
    {
      Solvable *s;
      s = pool->solvables + out->elements[i];
      if (s->recommends)
	{
          MAPEXP(recommended, pool->nsolvables);
          for (recp = s->repo->idarraydata + s->recommends; (rec = *recp++) != 0; )
	    FOR_PROVIDES(p, pp, rec)
	      MAPSET(recommended, p);
	}
    }
  xpctx->recdone = out->count;
}

static int
expander_dep_fulfilled(ExpanderCtx *xpctx, Id dep)
{
  Pool *pool = xpctx->pool;
  Id p, pp; 

  if (ISRELDEP(dep))
    {   
      Reldep *rd = GETRELDEP(pool, dep);
      if (rd->flags == REL_COND)
	{
	  if (ISRELDEP(rd->evr))
	    {
	      Reldep *rd2 = GETRELDEP(pool, rd->evr);
	      if (rd2->flags == REL_ELSE)
		{
		  if (expander_dep_fulfilled(xpctx, rd2->name))
		    return expander_dep_fulfilled(xpctx, rd->name);
		  return expander_dep_fulfilled(xpctx, rd2->evr);
		}
	    }
	  if (expander_dep_fulfilled(xpctx, rd->name))	/* A OR ~B */
	    return 1;
	  return !expander_dep_fulfilled(xpctx, rd->evr);
	}
      if (rd->flags == REL_UNLESS)
	{
	  if (ISRELDEP(rd->evr))
	    {
	      Reldep *rd2 = GETRELDEP(pool, rd->evr);
	      if (rd2->flags == REL_ELSE)
		{
		  if (!expander_dep_fulfilled(xpctx, rd2->name))
		    return expander_dep_fulfilled(xpctx, rd->name);
		  return expander_dep_fulfilled(xpctx, rd2->evr);
		}
	    }
	  if (!expander_dep_fulfilled(xpctx, rd->name))	/* A AND ~B */
	    return 0;
	  return !expander_dep_fulfilled(xpctx, rd->evr);
	}
      if (rd->flags == REL_AND)
	{
	  if (!expander_dep_fulfilled(xpctx, rd->name))
	    return 0;
	  return expander_dep_fulfilled(xpctx, rd->evr);
	}
      if (rd->flags == REL_OR)
	{
	  if (expander_dep_fulfilled(xpctx, rd->name))
	    return 1;
	  return expander_dep_fulfilled(xpctx, rd->evr);
	}
    }
  FOR_PROVIDES(p, pp, dep)
    {
      if (MAPTST(&xpctx->installed, p))
	return 1;
    }
  return 0;
}

static int
prune_neg_prefers(ExpanderCtx *xpctx, Id who, Id *e, int n)
{
  Expander *xp = xpctx->xp;
  Pool *pool = xpctx->pool;
  Id whon = who ? pool->solvables[who].name : 0;
  int i, j;
  for (i = j = 0; i < n; i++)
    {
      Id p = e[i];
      Id pn = pool->solvables[p].name;
      if (MAPTST(&xp->preferneg, pn))
	continue;
      if (who && MAPTST(&xp->prefernegx, pn))
	{
	  Id xid = pool_str2id(pool, pool_tmpjoin(pool, pool_id2str(pool, whon), ":", pool_id2str(pool, pn)), 0);
	  if (xid && MAPTST(&xp->preferneg, xid))
	    continue;
	}
      e[j++] = p;
    }
  return j ? j : n;
}

static int
prune_pos_prefers(ExpanderCtx *xpctx, Id who, Id *e, int n, int domulti)
{
  Expander *xp = xpctx->xp;
  Queue *pruneq = &xpctx->pruneq;
  Pool *pool = xpctx->pool;
  Id whon = who ? pool->solvables[who].name : 0;
  int i, j;

  if (pruneq->count)
    queue_empty(pruneq);
  for (i = j = 0; i < n; i++)
    {
      Id p = e[i];
      Id pn = pool->solvables[p].name;
      if (MAPTST(&xp->preferpos, pn))
	queue_push2(pruneq, pn, p);
      else if (who && MAPTST(&xp->preferposx, pn))
	{
	  Id xid = pool_str2id(pool, pool_tmpjoin(pool, pool_id2str(pool, whon), ":", pool_id2str(pool, pn)), 0);
	  if (xid && MAPTST(&xp->preferpos, xid))
	    queue_push2(pruneq, xid, p);
	}
    }
  if (!pruneq->count)
    return n;
  if (pruneq->count > 2)
    {
      if (!domulti)
	return n;
      /* pos prefers are ordered, the first one wins */
      for (i = 0; i < xp->preferposq.count; i++)
	{
	  Id xid = xp->preferposq.elements[i];
	  for (j = 0; j < pruneq->count; j += 2)
	    if (pruneq->elements[j] == xid)
	      {
		e[0] = pruneq->elements[j + 1];
		return 1;
	      }
	}
    }
  e[0] = pruneq->elements[1];	/* simple case, just one prefer */
  return 1;
}

static int
prune_or_dep(ExpanderCtx *xpctx, Id dep, Id *e, int n)
{
  Pool *pool = xpctx->pool;
  int i, j;
  Id p, pp;

  for (;;)
    {
      Reldep *rd = 0;
      if (ISRELDEP(dep))
	{
	  rd = GETRELDEP(pool, dep);
	  if (rd->flags != REL_OR)
	    rd = 0;
	}
      if (rd)
	dep = rd->name;
      i = j = 0;
      /* both sets are ordered */
      FOR_PROVIDES(p, pp, dep)
	{
	  if (p < e[i])
	    continue;
	  while (i < n && p > e[i])
	    i++;
	  if (i == n)
	    break;
	  if (p == e[i])
	    e[j++] = p;
	}
      if (j)
	return j;
      if (rd)
	dep = rd->evr;
      else
	break;
    }
  return n;
}

static int
prune_supplemented(ExpanderCtx *xpctx, Id *e, int n)
{
  Pool *pool = xpctx->pool;
  int i, j;
  Id sup, *supp;

  for (i = j = 0; i < n; i++)
    {
      Id p = e[i];
      Solvable *s = pool->solvables + p;
      if (!s->supplements)
	continue;
      supp = s->repo->idarraydata + s->supplements;
      while ((sup = *supp++) != 0)
	if (expander_dep_fulfilled(xpctx, sup))
	  break;
      if (sup)
        e[j++] = p;
    }
  return j ? j : n;
}

static void
add_recommended_packages(ExpanderCtx *xpctx, Solvable *s)
{
  Pool *pool = xpctx->pool;
  Id p, pp, rec, *recp;
  for (recp = s->repo->idarraydata + s->recommends; (rec = *recp++) != 0; )
    {
      int haveone = 0;
      if (pool_is_complex_dep(pool, rec))
	{
	  expander_installed_complexdep(xpctx, s - pool->solvables, rec, DEPTYPE_RECOMMENDS);
	  continue;
	}
      FOR_PROVIDES(p, pp, rec)
	{
	  if (MAPTST(&xpctx->installed, p))
	    break;
	  if (haveone)
	    continue;
	  if (xpctx->conflicts.size && MAPTST(&xpctx->conflicts, p))
	    continue;
	  if (pool->solvables[p].conflicts && expander_checkconflicts(xpctx, p, pool->solvables[p].repo->idarraydata + pool->solvables[p].conflicts, 0, 0) != 0)
	    continue;
	  if (pool->solvables[p].obsoletes && expander_checkconflicts(xpctx, p, pool->solvables[p].repo->idarraydata + pool->solvables[p].obsoletes, 1, 0) != 0)
	    continue;
	  haveone = 1;
	}
      if (p)
	continue;	/* already fulfilled */
      if (haveone)
	queue_push2(&xpctx->todo, rec, s - pool->solvables);
    }
}

static void
expander_growmaps(Expander *xp)
{
  Pool *pool = xp->pool;
  MAPEXP(&xp->ignored, pool->ss.nstrings);
  MAPEXP(&xp->ignoredx, pool->ss.nstrings);
  MAPEXP(&xp->preferpos, pool->ss.nstrings);
  MAPEXP(&xp->preferposx, pool->ss.nstrings);
  MAPEXP(&xp->preferneg, pool->ss.nstrings);
  MAPEXP(&xp->prefernegx, pool->ss.nstrings);
  MAPEXP(&xp->conflicts, pool->ss.nstrings);
}

static Id
str2id_dup(Pool *pool, const char *str)
{
  char buf[256];
  size_t l = strlen(str);
  if (l < 256) {
    memcpy(buf, str, l + 1);
    return pool_str2id(pool, buf, 1);
  } else {
    return pool_str2id(pool, pool_tmpjoin(pool, str, 0, 0), 1);
  }
}

static void
add_noproviderinfo(ExpanderCtx *xpctx, Id dep, Id who)
{
  Pool *pool = xpctx->pool;
  Reldep *rd, *prd;
  Id p, pp, prov, *provp;
  int nprovinfo;

  if (xpctx->xp->debug)
    {
      if (who)
        expander_dbg(xpctx->xp, "nothing provides %s needed by %s\n", pool_dep2str(pool, dep), expander_solvid2str(xpctx->xp, who));
      else
        expander_dbg(xpctx->xp, "nothing provides %s\n", pool_dep2str(pool, dep));
    }
  if (!ISRELDEP(dep))
    return;
  rd = GETRELDEP(pool, dep);
  if (rd->flags >= 8 || ISRELDEP(rd->name) || ISRELDEP(rd->evr))
    return;
  nprovinfo = 0;
  FOR_PROVIDES(p, pp, rd->name)
    {
      Solvable *s = pool->solvables + p;
      if (!s->repo || !s->provides)
	continue;
      for (provp = s->repo->idarraydata + s->provides; (prov = *provp++) != 0; )
	{
	  if (!ISRELDEP(prov))
	    continue;
	  prd = GETRELDEP(pool, prov);
	  if (prd->name != rd->name || ISRELDEP(prd->evr))
	    continue;
	  queue_push(&xpctx->errors, ERROR_NOPROVIDERINFO);
	  if (prd->name == s->name && prd->evr == s->evr)
	    {
	      if (xpctx->xp->debug)
		expander_dbg(xpctx->xp, "%s has version %s\n", expander_solvid2str(xpctx->xp, p), pool_id2str(pool, prd->evr));
	      queue_push2(&xpctx->errors, prd->evr, 0);
	    }
	  else
	    {
	      if (xpctx->xp->debug)
		expander_dbg(xpctx->xp, "%s provides version %s\n", expander_solvid2str(xpctx->xp, p), pool_id2str(pool, prd->evr));
	      queue_push2(&xpctx->errors, prd->evr, p);
	    }
	  if (++nprovinfo >= 4)
	    return;		/* only show the first 4 providers */
	}
    }
}

static int
expander_expand(Expander *xp, Queue *in, Queue *indep, Queue *out, Queue *ignoreq, int options)
{
  ExpanderCtx xpctx;
  Pool *pool = xp->pool;
  Queue toinstall;
  Queue qq, choices;
  Solvable *s;
  Id q, p, pp;
  int i, j, nerrors;
  int ti, tj, tc;
  Id todoid, id, who, whon;
  Id conflprovpc;
  int pass;
  Queue revertignore;
  int oldignoreignore = xp->ignoreignore;
  Map oldignored, oldignoredx;
  int ignoremapssaved = 0;
  int dorecstart = 0;

  memset(&xpctx, 0, sizeof(xpctx));
  xpctx.xp = xp;
  xpctx.pool = pool;
  xpctx.out = out;
  xpctx.ignoreignore = options & EXPANDER_OPTION_IGNOREIGNORE ? 1 : xp->ignoreignore;
  xpctx.ignoreconflicts = options & EXPANDER_OPTION_IGNORECONFLICTS ? 1 : xp->ignoreconflicts;
  xpctx.userecommendsforchoices = options & EXPANDER_OPTION_USERECOMMENDSFORCHOICES ? 1 : xp->userecommendsforchoices;
  xpctx.usesupplementsforchoices = options & EXPANDER_OPTION_USESUPPLEMENTSFORCHOICES ? 1 : xp->usesupplementsforchoices;
  xpctx.dorecommends = options & EXPANDER_OPTION_DORECOMMENDS ? 1 : xp->dorecommends;
  xpctx.dosupplements = options & EXPANDER_OPTION_DOSUPPLEMENTS ? 1 : xp->dosupplements;
  map_init(&xpctx.installed, pool->nsolvables);
  map_init(&xpctx.conflicts, 0);
  map_init(&xpctx.recommended, 0);
  queue_init(&xpctx.conflictsinfo);
  queue_init(&xpctx.todo);
  queue_init(&xpctx.todo_cond);
  map_init(&xpctx.todo_condmap, 0);
  queue_init(&xpctx.errors);
  queue_init(&xpctx.cplxq);
  queue_init(&xpctx.cplxblks);
  queue_init(&xpctx.pruneq);

  queue_init(&toinstall);
  queue_init(&qq);
  queue_init(&choices);
  queue_init(&revertignore);

  queue_empty(out);

  /* process ignored. hack: we mess with the ignore config in xp */
  xp->ignoreignore = 0;
  if (xpctx.ignoreignore && ignoreq->count)
    {
      /* bad: have direct ignores and we need to zero the project config ignores */
      oldignored = xp->ignored;
      oldignoredx = xp->ignoredx;
      ignoremapssaved = 1;
      /* clear project config maps */
      memset(&xp->ignored, 0, sizeof(xp->ignored));
      memset(&xp->ignoredx, 0, sizeof(xp->ignoredx));
    }
  if (ignoreq->count)
    {
      /* mix direct ignores with ignores from project config */
      for (i = 0; i < ignoreq->count; i++)
	{
	  const char *ss;
	  id = ignoreq->elements[i];
	  MAPEXP(&xp->ignored, id);
	  if (MAPTST(&xp->ignored, id))
	    continue;
	  MAPSET(&xp->ignored, id);
	  queue_push(&revertignore, id);
	  if ((ss = strchr(pool_id2str(pool, id), ':')) != 0)
	    {
	      id = str2id_dup(pool, ss + 1);
	      MAPEXP(&xp->ignoredx, id);
	      if (MAPTST(&xp->ignoredx, id))
		continue;
	      MAPSET(&xp->ignoredx, id);
	      queue_push(&revertignore, -id);
	    }
	}
    }
  else if (xpctx.ignoreignore)
    {
      /* no direct ignores, ignore project config ignores.
       * easy: just disable ignore processing */
      xp->ignoreignore = 1;
    }

  /* grow maps to make bit tests cheaper */
  expander_growmaps(xp);

  /* process standard dependencies */
  if (indep)
    {
      for (i = 0; i < indep->count; i += 2)
	{
	  int deptype = indep->elements[i];
	  Id dep = indep->elements[i + 1];
	  if (ISRELDEP(dep) && GETRELDEP(pool, dep)->flags == REL_ERROR)
	    {
	      queue_push(&xpctx.errors, ERROR_BADDEPENDENCY);
	      queue_push2(&xpctx.errors, GETRELDEP(pool, dep)->evr, 0);
	      continue;
	    }
	  if ((deptype == DEPTYPE_REQUIRES || deptype == DEPTYPE_CONFLICTS) && pool_is_complex_dep(pool, dep))
	    {
	      expander_installed_complexdep(&xpctx, 0, dep, deptype);
	      continue;
	    }
	  if (deptype == DEPTYPE_REQUIRES)
	    {
	      queue_push2(&xpctx.todo, dep, 0);
	    }
	  else if (deptype == DEPTYPE_CONFLICTS || deptype == DEPTYPE_OBSOLETES)
	    {
	      FOR_PROVIDES(p, pp, dep)
		{
		  if (deptype == DEPTYPE_OBSOLETES && !pool_match_nevr(pool, pool->solvables + p, dep))
		    continue;
		  MAPEXP(&xpctx.conflicts, pool->nsolvables);
		  MAPSET(&xpctx.conflicts, p);
		}
	    }
	}
    }
  /* process direct dependencies */
  for (i = 0; i < in->count; i++)
    {
      id = in->elements[i];
      if (ISRELDEP(id) && GETRELDEP(pool, id)->flags == REL_ERROR)
	{
	  queue_push(&xpctx.errors, ERROR_BADDEPENDENCY);
	  queue_push2(&xpctx.errors, GETRELDEP(pool, id)->evr, 0);
	  continue;
	}
      if (pool_is_complex_dep(pool, id))
	{
	  expander_installed_complexdep(&xpctx, 0, id, DEPTYPE_REQUIRES);
	  continue;
	}
      q = 0;
      FOR_PROVIDES(p, pp, id)
	{
	  s = pool->solvables + p;
	  if (!pool_match_nevr(pool, s, id))
	    continue;
	  if (q)
	    {
	      q = 0;
	      break;
	    }
	  q = p;
	}
      if (!q)
	{
	  queue_push2(&xpctx.todo, id, 0);	/* unclear, resolve later */
	  continue;
	}
      if (xp->debug)
	expander_dbg(xp, "added %s because of %s (direct dep)\n", expander_solvid2name(xp, q), pool_dep2str(pool, id));
      queue_push(&toinstall, q);
    }

  /* unify toinstall, check against conflicts */
  for (i = 0; i < toinstall.count; i++)
    {
      p = toinstall.elements[i];
      MAPSET(&xpctx.installed, p);
    }
  for (i = j = 0; i < toinstall.count; i++)
    {
      p = toinstall.elements[i];
      if (!MAPTST(&xpctx.installed, p))
	continue;
      MAPCLR(&xpctx.installed, p);
      toinstall.elements[j++] = p;
    }
  queue_truncate(&toinstall, j);
  if (xpctx.conflicts.size)
    {
      for (i = 0; i < toinstall.count; i++)
	{
	  p = toinstall.elements[i];
	  if (MAPTST(&xpctx.conflicts, p))
	    findconflictsinfo(&xpctx, p, 2);
	}
    }

  /* here is the big expansion loop */
  pass = 0;
  while (!xpctx.errors.count)
    {
      if (toinstall.count)
	{
	  expander_installed_multiple(&xpctx, &toinstall);
	  pass = 0;
	  continue;
	}

      if (!xpctx.todo.count)
	{
	  /* almost finished. now do weak deps if requested */
	  pass = 0;
	  if (xpctx.dorecommends)
	    {
	      expander_dbg(xp, "--- now doing recommended packages\n");
	      for (; dorecstart < out->count; dorecstart++)
		{
		  s = pool->solvables + out->elements[dorecstart];
		  if (s->recommends)
		    add_recommended_packages(&xpctx, s);
		}
	      if (xpctx.todo.count)
	        continue;
	    }
	  if (xpctx.dosupplements)
	    {
	      Id sup, *supp;
	      expander_dbg(xp, "--- now doing supplemented packages\n");
	      for (p = 1; p < pool->nsolvables; p++)
		{
		  s = pool->solvables + p;
		  if (!s->supplements || !s->repo)
		    continue;
		  if (MAPTST(&xpctx.installed, p))
		    continue;
		  if (!pool_installable(pool, s))
		    continue;
		  if (xpctx.conflicts.size && MAPTST(&xpctx.conflicts, p))
		    continue;
		  if (s->conflicts && expander_checkconflicts(&xpctx, p, s->repo->idarraydata + s->conflicts, 0, 0) != 0)
		    continue;
		  if (s->obsoletes && expander_checkconflicts(&xpctx, p, s->repo->idarraydata + s->obsoletes, 1, 0) != 0)
		    continue;
		  supp = s->repo->idarraydata + s->supplements;
		  while ((sup = *supp++) != 0)
		    if (expander_dep_fulfilled(&xpctx, sup))
		      break;
		  if (!sup)
		    continue;
		  expander_dbg(xp, "added %s because it supplements %s\n", expander_solvid2name(xp, p), pool_dep2str(pool, sup));
		  queue_push(&toinstall, p);
		}
	      if (toinstall.count)
		continue;
	    }
	  /* no new stuff to do, we're finished! */
	  break;
	}

      expander_dbg(xp, "--- now doing normal dependencies\n");

      if (pass == 1)
	queue_empty(&choices);
	
      for (ti = tj = 0; ti < xpctx.todo.count; ti += 2)
	{
	  int deptype = DEPTYPE_REQUIRES;
	  todoid = id = xpctx.todo.elements[ti];
	  who = xpctx.todo.elements[ti + 1];
	  if (!id)			/* deleted entry? */
	    continue;
	  queue_empty(&qq);
	  if (ISCPLX(pool, id))
	    {
	      pp = GETCPLX(pool, id);	/* p, dep, deptype, ids... */
	      id = xpctx.cplxblks.elements[pp + 1];
	      deptype = xpctx.cplxblks.elements[pp + 2];
	      pp += 3;
	      while ((p = xpctx.cplxblks.elements[pp++]))
		if (p > 0)
		  queue_push(&qq, p);
	    }
	  else
	    {
	      FOR_PROVIDES(p, pp, id)
		queue_push(&qq, p);
	    }

	  if (qq.count == 0)
	    {
	      if (deptype == DEPTYPE_RECOMMENDS)
		continue;
	      queue_push(&xpctx.errors, ERROR_NOPROVIDER);
	      queue_push2(&xpctx.errors, id, who);
	      add_noproviderinfo(&xpctx, id, who);
	      continue;
	    }

	  /* check installed and ignores */
	  whon = who ? pool->solvables[who].name : 0;
	  for (i = 0; i < qq.count; i++)
	    {
	      p = qq.elements[i];
	      if (MAPTST(&xpctx.installed, p))
		break;
	      if (who && deptype == DEPTYPE_REQUIRES && !xp->ignoreignore)
		{
		  Id pn = pool->solvables[p].name;
		  if (MAPTST(&xp->ignored, pn))
		    break;
		  if (MAPTST(&xp->ignoredx, pn))
		    {
		      Id xid = pool_str2id(pool, pool_tmpjoin(pool, pool_id2str(pool, whon), ":", pool_id2str(pool, pn)), 0);
		      if (xid && MAPTST(&xp->ignored, xid))
			break;
		    }
		}
	    }
	  if (i < qq.count)
	    continue;		/* ignored dependency or fulfilled */

	  if (pass == 0 && qq.count > 1)
	    {
	      xpctx.todo.elements[tj++] = todoid;
	      xpctx.todo.elements[tj++] = who;
	      continue;
	    }

	  /* do conflict pruning */
	  conflprovpc = 0;
	  for (i = j = 0; i < qq.count; i++)
	    {
	      Id pc;
	      p = qq.elements[i];
	      if (xpctx.conflicts.size && MAPTST(&xpctx.conflicts, p))
		{
		  if (xp->debug)
		    findconflictsinfo(&xpctx, p, 0);
		  conflprovpc = 0;
		  continue;
		}
	      if (pool->solvables[p].conflicts && (pc = expander_checkconflicts(&xpctx, p, pool->solvables[p].repo->idarraydata + pool->solvables[p].conflicts, 0, 0)) != 0)
		{
		  conflprovpc = pc;
		  continue;
		}
	      if (pool->solvables[p].obsoletes && (pc = expander_checkconflicts(&xpctx, p, pool->solvables[p].repo->idarraydata + pool->solvables[p].obsoletes, 1, 0)) != 0)
		{
		  conflprovpc = -pc;
		  continue;
		}
	      qq.elements[j++] = p;
	    }
	  if (j == 0)
	    {
	      if (deptype == DEPTYPE_RECOMMENDS)
		continue;
	      queue_push(&xpctx.errors, ERROR_CONFLICTINGPROVIDERS);
	      queue_push2(&xpctx.errors, id, who);
	      if (qq.count == 1 && conflprovpc != 1 && conflprovpc != -1)
		{
		  p = qq.elements[0];
		  if (conflprovpc)
		    {
		      queue_push(&xpctx.errors, ERROR_PROVIDERINFO);
		      queue_push2(&xpctx.errors, p, conflprovpc);
		      continue;
		    }
		  findconflictsinfo(&xpctx, p, 1);
		  continue;
		}
	      /* even more work if all providers conflict */
	      for (j = 0; j < qq.count; j++)
		{
		  p = qq.elements[j];
		  if (xpctx.conflicts.size && MAPTST(&xpctx.conflicts, p))
		    findconflictsinfo(&xpctx, p, 1);
		  if (pool->solvables[p].conflicts)
		    expander_checkconflicts(&xpctx, p, pool->solvables[p].repo->idarraydata + pool->solvables[p].conflicts, 0, 1);
		  if (pool->solvables[p].obsoletes)
		    expander_checkconflicts(&xpctx, p, pool->solvables[p].repo->idarraydata + pool->solvables[p].obsoletes, 1, 1);
		}
	      continue;
	    }
	  queue_truncate(&qq, j);
          if (qq.count == 1)
	    {
	      p = qq.elements[0];
	      if (xp->debug)
		expander_dbg(xp, "added %s because of %s:%s\n", expander_solvid2name(xp, p), whon ? pool_id2str(pool, whon) : "(direct)", pool_dep2str(pool, id));
	      queue_push(&toinstall, p);
	      continue;
	    }
	  /* pass is == 1 and we have multiple choices */
	  if (xp->debug)
	    {
	      expander_dbg(xp, "undecided about %s:%s:", whon ? pool_id2str(pool, whon) : "(direct)", pool_dep2str(pool, id));
	      for (i = 0; i < qq.count; i++)
		expander_dbg(xp, " %s", expander_solvid2name(xp, qq.elements[i]));
              expander_dbg(xp, "\n");
	    }
	  queue_push2(&choices, qq.count + 3, id);
	  queue_push(&choices, qq.count);
	  queue_insertn(&choices, choices.count, qq.count, qq.elements);
	  xpctx.todo.elements[tj++] = todoid;
	  xpctx.todo.elements[tj++] = who;
	}
      queue_truncate(&xpctx.todo, tj);

      if (toinstall.count)
	continue;

      if (!xpctx.todo.count)
	continue;

      /* did not find a package to install, only choices left on todo list */
      if (pass == 0)
	{
	  pass = 1;	/* now do conflict pruning */
	  continue;
	}

      expander_dbg(xp, "--- now doing undecided dependencies\n");

      /* prune prefers */
      for (ti = tc = 0; ti < xpctx.todo.count; ti += 2)
	{
	  Id who = xpctx.todo.elements[ti + 1];
	  Id *qe = choices.elements + tc + 3;
	  Id id = choices.elements[tc + 1];
	  int qn = choices.elements[tc + 2];
	  whon = who ? pool->solvables[who].name : 0;
	  if (qn > 1)
	    qn = prune_neg_prefers(&xpctx, who, qe, qn);
	  if (qn > 1)
	    qn = prune_pos_prefers(&xpctx, who, qe, qn, 0);
	  if (qn == 1)
	    {
	      p = qe[0];
	      if (xp->debug)
		expander_dbg(xp, "added %s because of %s:%s\n", expander_solvid2name(xp, p), whon ? pool_id2str(pool, whon) : "(direct)", pool_dep2str(pool, id));
	      queue_push(&toinstall, p);
	      xpctx.todo.elements[ti] = 0;	/* kill entry */
	    }
	  choices.elements[tc + 2] = qn;
	  tc += choices.elements[tc];
	}
      if (toinstall.count)
	continue;

      /* prune pos prefers with domulti and debian or */
      for (ti = tc = 0; ti < xpctx.todo.count; ti += 2)
	{
	  Id who = xpctx.todo.elements[ti + 1];
	  Id *qe = choices.elements + tc + 3;
	  Id id = choices.elements[tc + 1];
	  int qn = choices.elements[tc + 2];
	  whon = who ? pool->solvables[who].name : 0;
	  if (qn > 1)
	    qn = prune_pos_prefers(&xpctx, who, qe, qn, 1);
	  if (qn > 1 && pool->disttype != DISTTYPE_RPM)
	    {
	      if (ISRELDEP(id) && GETRELDEP(pool, id)->flags == REL_OR)
	        qn = prune_or_dep(&xpctx, id, qe, qn);
	    }
	  if (qn == 1)
	    {
	      p = qe[0];
	      if (xp->debug)
		expander_dbg(xp, "added %s because of %s:%s\n", expander_solvid2name(xp, p), whon ? pool_id2str(pool, whon) : "(direct)", pool_dep2str(pool, id));
	      queue_push(&toinstall, p);
	      xpctx.todo.elements[ti] = 0;	/* kill entry */
	    }
	  choices.elements[tc + 2] = qn;
	  tc += choices.elements[tc];
	}
      if (toinstall.count)
	continue;

      /* prune recommended packages */
      if (xpctx.userecommendsforchoices)
        expander_updaterecommendedmap(&xpctx);
      if (xpctx.recommended.size)
	{
	  expander_dbg(xp, "now doing undecided dependencies with recommends\n");
	  for (ti = tc = 0; ti < xpctx.todo.count; ti += 2)
	    {
	      Id who = xpctx.todo.elements[ti + 1];
	      Id *qe = choices.elements + tc + 3;
	      Id id = choices.elements[tc + 1];
	      int qn = choices.elements[tc + 2];
	      whon = who ? pool->solvables[who].name : 0;
	      for (i = j = 0; i < qn; i++)
		if (MAPTST(&xpctx.recommended, qe[i]))
		  qe[j++] = qe[i];
	      if (j)
		qn = j;
	      if (qn == 1)
		{
		  p = qe[0];
		  if (xp->debug)
		    expander_dbg(xp, "added %s because of %s:%s\n", expander_solvid2name(xp, p), whon ? pool_id2str(pool, whon) : "(direct)", pool_dep2str(pool, id));
		  queue_push(&toinstall, p);
		  xpctx.todo.elements[ti] = 0;	/* kill entry */
		}
	      choices.elements[tc + 2] = qn;
	      tc += choices.elements[tc];
	    }
	  if (toinstall.count)
	    continue;
	}
      if (xpctx.usesupplementsforchoices)
	{
	  expander_dbg(xp, "now doing undecided dependencies with supplements\n");
	  for (ti = tc = 0; ti < xpctx.todo.count; ti += 2)
	    {
	      Id who = xpctx.todo.elements[ti + 1];
	      Id *qe = choices.elements + tc + 3;
	      Id id = choices.elements[tc + 1];
	      int qn = choices.elements[tc + 2];
	      whon = who ? pool->solvables[who].name : 0;
	      qn = prune_supplemented(&xpctx, qe, qn);
	      if (qn == 1)
		{
		  p = qe[0];
		  if (xp->debug)
		    expander_dbg(xp, "added %s because of %s:%s\n", expander_solvid2name(xp, p), whon ? pool_id2str(pool, whon) : "(direct)", pool_dep2str(pool, id));
		  queue_push(&toinstall, p);
		  xpctx.todo.elements[ti] = 0;	/* kill entry */
		}
	      choices.elements[tc + 2] = qn;
	      tc += choices.elements[tc];
	    }
	  if (toinstall.count)
	    continue;
	}

      /* nothing more to prune. record errors. */
      for (ti = tc = 0; ti < xpctx.todo.count; ti += 2)
	{
	  Id who = xpctx.todo.elements[ti + 1];
	  Id *qe = choices.elements + tc + 3;
	  Id id = choices.elements[tc + 1];
	  int qn = choices.elements[tc + 2];
	  queue_push(&xpctx.errors, ERROR_CHOICE);
	  queue_push2(&xpctx.errors, id, who);
	  for (i = 0; i < qn; i++)
	    queue_push(&xpctx.errors, qe[i]);
	  queue_push(&xpctx.errors, 0);
	  tc += choices.elements[tc];
	}
    }

  /* free data */
  map_free(&xpctx.installed);
  map_free(&xpctx.conflicts);
  map_free(&xpctx.recommended);
  map_free(&xpctx.todo_condmap);
  queue_free(&xpctx.conflictsinfo);
  queue_free(&xpctx.todo_cond);
  queue_free(&xpctx.todo);
  queue_free(&toinstall);
  queue_free(&qq);
  queue_free(&choices);
  queue_free(&xpctx.pruneq);
  queue_free(&xpctx.cplxq);
  queue_free(&xpctx.cplxblks);

  /* revert ignores */
  xp->ignoreignore = oldignoreignore;
  if (ignoremapssaved)
    {
      map_free(&xp->ignored);
      map_free(&xp->ignoredx);
      xp->ignored = oldignored;
      xp->ignoredx = oldignoredx;
    }
  else
    {
      for (i = 0; i < revertignore.count; i++)
	{
	  id = revertignore.elements[i];
	  if (id > 0)
	    MAPCLR(&xp->ignored, id);
	  else
	    MAPCLR(&xp->ignoredx, -id);
	}
    }
  queue_free(&revertignore);

  /* finish return queue, count errors */
  nerrors = 0;
  if (xpctx.errors.count)
    {
      queue_empty(out);
      queue_insertn(out, 0, xpctx.errors.count, xpctx.errors.elements);
      for (i = 0; i < out->count; i += 3)
	{
	  nerrors++;
	  if (out->elements[i] == ERROR_CHOICE)
	    while (out->elements[i + 3])
	      i++;
	}
    }
  queue_free(&xpctx.errors);
  return nerrors;
}

static Expander *
expander_create(Pool *pool, Queue *preferpos, Queue *preferneg, Queue *ignore, Queue *conflict, Queue *fileprovides, int debug, int options)
{
  Expander *xp;
  int i, j;
  Id id, id2;
  const char *str;
  Queue q;

  xp = calloc(sizeof(Expander), 1);
  xp->pool = pool;
  xp->debug = debug;
  xp->ignoreignore = options & EXPANDER_OPTION_IGNOREIGNORE ? 1 : 0;
  xp->ignoreconflicts = options & EXPANDER_OPTION_IGNORECONFLICTS ? 1 : 0;
  xp->userecommendsforchoices = options & EXPANDER_OPTION_USERECOMMENDSFORCHOICES ? 1 : 0;
  xp->usesupplementsforchoices = options & EXPANDER_OPTION_USESUPPLEMENTSFORCHOICES ? 1 : 0;
  xp->dorecommends = options & EXPANDER_OPTION_DORECOMMENDS ? 1 : 0;
  xp->dosupplements = options & EXPANDER_OPTION_DOSUPPLEMENTS ? 1 : 0;

  queue_init(&xp->preferposq);
  for (i = 0; i < preferpos->count; i++)
    {
      id = preferpos->elements[i];
      queue_push(&xp->preferposq, id);
      MAPEXP(&xp->preferpos, id);
      MAPSET(&xp->preferpos, id);
      if ((str = strchr(pool_id2str(pool, id), ':')) != 0)
        {
          id = str2id_dup(pool, str + 1);
	  MAPEXP(&xp->preferposx, id);
	  MAPSET(&xp->preferposx, id);
        }
    }
  for (i = 0; i < preferneg->count; i++)
    {
      id = preferneg->elements[i];
      MAPEXP(&xp->preferneg, id);
      MAPSET(&xp->preferneg, id);
      if ((str = strchr(pool_id2str(pool, id), ':')) != 0)
        {
          id = str2id_dup(pool, str + 1);
	  MAPEXP(&xp->prefernegx, id);
	  MAPSET(&xp->prefernegx, id);
        }
    }

  for (i = 0; i < ignore->count; i++)
    {
      id = ignore->elements[i];
      MAPEXP(&xp->ignored, id);
      MAPSET(&xp->ignored, id);
      if ((str = strchr(pool_id2str(pool, id), ':')) != 0)
        {
          id = str2id_dup(pool, str + 1);
	  MAPEXP(&xp->ignoredx, id);
	  MAPSET(&xp->ignoredx, id);
        }
    }

  queue_init(&xp->conflictsq);
  for (i = 0; i < conflict->count; i += 2)
    {
      id = conflict->elements[i];
      id2 = conflict->elements[i + 1];
      queue_push2(&xp->conflictsq, id, id2);
      MAPEXP(&xp->conflicts, id);
      MAPSET(&xp->conflicts, id);
      MAPEXP(&xp->conflicts, id2);
      MAPSET(&xp->conflicts, id2);
    }

  if (fileprovides->count)
    xp->havefileprovides = 1;
  queue_init(&q);
  for (i = 0; i < fileprovides->count; i++)
    {
      Id p, pp;
      id = fileprovides->elements[i];
      int havenew = 0;

      /* XXX: this modifies the pool, which is somewhat unclean! */
      /* get old providers */
      queue_empty(&q);
      FOR_PROVIDES(p, pp, id)
        queue_push(&q, p);
      for (j = i + 1; j < fileprovides->count && (id2 = fileprovides->elements[j]) != 0; j++)
	{
	  FOR_PROVIDES(p, pp, id2)
	    {
	      int k;
	      if (pool->solvables[p].name != id2)
	        continue;		/* match name only */
	      /* insert sorted */
	      for (k = 0; ; k++)
	        {
		  if (k == q.count || q.elements[k] > p)
		    {
		      queue_insert(&q, k, p);
		      havenew = 1;
		      break;
		    }
		  if (q.elements[k] == p)
		    break;
	        }
	    }
	}
      if (havenew)
        pool->whatprovides[id] = pool_queuetowhatprovides(pool, &q);
      i = j;
    }
  queue_free(&q);
  return xp;
}

static void
expander_free(Expander *xp)
{
  map_free(&xp->ignored);
  map_free(&xp->ignoredx);
  queue_free(&xp->preferposq);
  map_free(&xp->preferpos);
  map_free(&xp->preferposx);
  map_free(&xp->preferneg);
  map_free(&xp->prefernegx);
  queue_free(&xp->conflictsq);
  map_free(&xp->conflicts);
  solv_free(xp->debugstr);
  solv_free(xp);
}



static void
set_disttype(Pool *pool, int disttype)
{
  pool_setdisttype(pool, disttype);
#ifdef POOL_FLAG_HAVEDISTEPOCH
  /* make newer mandriva work, hopefully there are no ill effects... */
  pool_set_flag(pool, POOL_FLAG_HAVEDISTEPOCH, disttype == DISTTYPE_RPM ? 1 : 0);
#endif
}

static void
set_disttype_from_location(Pool *pool, Solvable *so)
{
  unsigned int medianr;
  const char *s = solvable_get_location(so, &medianr);
  int disttype = -1;
  int sl;
  if (!s)
    return;
  sl = strlen(s);
  if (disttype < 0 && sl >= 4 && !strcmp(s + sl - 4, ".rpm"))
    disttype = DISTTYPE_RPM;
#ifdef DISTTYPE_DEB
  if (disttype < 0 && sl >= 4 && !strcmp(s + sl - 4, ".deb"))
    disttype = DISTTYPE_DEB;
#endif
#ifdef DISTTYPE_ARCH
  if (disttype < 0 && sl >= 12 && (!strcmp(s + sl - 11, ".pkg.tar.gz") || !strcmp(s + sl - 11, ".pkg.tar.xz") || !strcmp(s + sl - 12, ".pkg.tar.zst")))
    disttype = DISTTYPE_ARCH;
#endif
  if (disttype >= 0 && pool->disttype != disttype)
    set_disttype(pool, disttype);
}

static inline const char *
solvid2name(Pool *pool, Id p)
{
  return pool_id2str(pool, pool->solvables[p].name);
}

#define ISNOARCH(arch) (arch == ARCH_NOARCH || arch == ARCH_ALL || arch == ARCH_ANY)

static int
has_keyname(Repo *repo, Id keyname)
{
  Repodata *data;
  int rdid;
  FOR_REPODATAS(repo, rdid, data)
    if (repodata_has_keyname(data, keyname))
      return 1;
  return 0;
}

static inline int
match_modules_req(Pool *pool, Id id)
{
  const char *dep = pool_id2str(pool, id);
  Id *modules;
  if (strncmp(dep, "platform", 8) == 0 && (dep[8] == 0 || dep[8] == '-'))
    return 1;
  for (modules = pool->appdata; *modules; modules++)
    {
      const char *name, *rname;
      if (*modules == id)
	return 1;
      name = pool_id2str(pool, *modules);
      if ((rname = strrchr(name, '-')) == 0 || rname == name)
	continue;
      if (!strncmp(dep, rname, rname - name) && dep[rname - name] == 0)
	return 1;
    }
  return 0;
}

static void
create_module_map(Repo *repo, Map *modulemap, Queue *modulemapq)
{
  Pool *pool = repo->pool;
  Id *modules = pool->appdata;
  int i, have_moduleinfo = 0;
  Id id, p, *pp;
  Solvable *s;

  if (!modulemap->size)
    map_grow(modulemap, pool->ss.nstrings);
  if (!modules)
    return;
  if (!*modules)
    {
      map_setall(modulemap);
      return;
    }
  /* clear old bits */
  if (modulemapq->count)
    {
      for (i = 0; i < modulemapq->count; i++)
	MAPCLR(modulemap, modulemapq->elements[i]);
      queue_empty(modulemapq);
    }
  for (modules = pool->appdata; *modules; modules++)
    MAPSET(modulemap, *modules);
  /* look for module information stored in "buildservice:modules" solvables */
  FOR_REPO_SOLVABLES(repo, p, s)
    {
      if (s->name != buildservice_modules || s->arch != ARCH_SRC)
	continue;
      have_moduleinfo = 1;
      if (s->evr >= 1 && s->evr < pool->ss.nstrings && MAPTST(modulemap, s->evr))
	{
	  queue_push(modulemapq, s->evr);	/* directly addressed */
	  continue;
	}
      id = s->repo->idarraydata[s->provides];
      if (id < 1 || id >= pool->ss.nstrings || !MAPTST(modulemap, id))
	continue;	/* not what we're looking for */
      for (pp = s->repo->idarraydata + s->requires; (id = *pp) != 0; pp++)
        {
          /* check if the dep is fulfilled by any module in the list */
          if (id < 1 || id >= pool->ss.nstrings)
	    break;	/* hey! */
          if (!MAPTST(modulemap, id) && !match_modules_req(pool, id))
	    break;	/* could not fulfil requires */
        }
      if (id)
	continue;	/* could not fulfil one of the requires, ignore module */
      queue_push(modulemapq, s->evr);
    }
  if (!have_moduleinfo)
    {
      /* old style repo with no moduleinfo at all. simple use the unexpanded ids */
      for (modules = pool->appdata; *modules; modules++)
        queue_push(modulemapq, *modules);
      return;
    }
  for (modules = pool->appdata; *modules; modules++)
    MAPCLR(modulemap, *modules);
  for (i = 0; i < modulemapq->count; i++)
    MAPSET(modulemap, modulemapq->elements[i]);
}

static int
in_module_map(Pool *pool, Map *modulemap, Queue *modules)
{
  int i;
  for (i = 0; i < modules->count; i++)
    { 
      Id id = modules->elements[i];
      if (id > 1 && id < pool->ss.nstrings && MAPTST(modulemap, id))
	return 1;
    }
  return 0;
}


static void
create_considered(Pool *pool, Repo *repoonly, Map *considered, int unorderedrepos)
{
  Id p, pb,*best;
  Solvable *s, *sb;
  int ridx;
  Repo *repo;
  int olddisttype = -1;
  int dodrepo;
  int mayhave_modules;
  Queue modules;
  Map modulemap;
  Queue modulemapq;
  int modulemap_uptodate;

  map_init(considered, pool->nsolvables);
  best = solv_calloc(sizeof(Id), pool->ss.nstrings);
  
  queue_init(&modules);
  map_init(&modulemap, 0);
  queue_init(&modulemapq);
  FOR_REPOS(ridx, repo)
    {
      if (repoonly && repo != repoonly)
	continue;
      dodrepo = repo_lookup_str(repo, SOLVID_META, buildservice_dodurl) != 0;
      mayhave_modules = has_keyname(repo, buildservice_modules) ? 1 : 0;
      modulemap_uptodate = 0;
      FOR_REPO_SOLVABLES(repo, p, s)
	{
	  int inmodule = 0;
	  if (s->arch == ARCH_SRC || s->arch == ARCH_NOSRC)
	    continue;
	  pb = best[s->name];
	  sb = pb ? pool->solvables + pb : 0;
	  if (mayhave_modules)
	    {
	      solvable_lookup_idarray(s, buildservice_modules, &modules);
	      inmodule = modules.count ? 1 : 0;
	      if (inmodule)
		{
		  if (!modulemap_uptodate)
		    {
		      create_module_map(repo, &modulemap, &modulemapq);
		      modulemap_uptodate = 1;
		    }
		  if (!in_module_map(pool, &modulemap, &modules))
		    continue;		/* nope, ignore package */
		}
	    }
	  if (unorderedrepos && sb && s->repo->priority != sb->repo->priority)
	    {
	      if (s->repo->priority < sb->repo->priority)
		continue;	/* lower prio, ignore */
	    }
	  else if (sb)
	    {
	      int sbinmodule = 0;
	      /* we already have that name. decide which one to take */
	      if (!unorderedrepos && s->repo != sb->repo)
		continue;	/* first repo wins */

	      if (s->repo == sb->repo && mayhave_modules)
		sbinmodule = solvable_lookup_type(sb, buildservice_modules) ? 1 : 0;

	      if (inmodule != sbinmodule)
		{
		  if (inmodule < sbinmodule)
		    continue;
		}
	      else if (s->evr != sb->evr)
		{
		  /* check versions */
		  int r;
		  if (olddisttype < 0)
		    {
		      olddisttype = pool->disttype;
		      set_disttype_from_location(pool, s);
		    }
		  r = pool_evrcmp(pool, sb->evr, s->evr, EVRCMP_COMPARE);
		  if (r == 0)
		    r = strcmp(pool_id2str(pool, sb->evr), pool_id2str(pool, s->evr));
		  if (r >= 0)
		    continue;
		}
	      else if (s->arch != sb->arch)
		{
		  /* same versions, check arch */
		  if (ISNOARCH(sb->arch) && !ISNOARCH(s->arch))
		    continue;
		  if (ISNOARCH(sb->arch) || !ISNOARCH(s->arch))
		    {
		      int r;
		      /* the strcmp is kind of silly, but works for most archs */
		      r = strcmp(pool_id2str(pool, sb->arch), pool_id2str(pool, s->arch));
		      if (r >= 0)
			continue;
		    }
		}
	    }

	   if (dodrepo)
	    {
	      /* we only consider dod packages */
	      const char *bsid = solvable_lookup_str(s, buildservice_id);
	      if (!bsid || strcmp(bsid, "dod") != 0)
		continue;
	    }
	  if (pb)
	    MAPCLR(considered, pb);
	  best[s->name] = p;
	  MAPSET(considered, p);
	}
      /* dodrepos have a second pass: replace dod entries with identical downloaded ones */
      if (dodrepo)
	{
	  const char *bsid;
	  FOR_REPO_SOLVABLES(repo, p, s)
	    {
	      if (s->arch == ARCH_SRC || s->arch == ARCH_NOSRC)
		continue;
	      pb = best[s->name];
	      if (!pb || pb == p)
		continue;
	      sb = pool->solvables + pb;
	      if (sb->repo != s->repo || sb->name != s->name || sb->arch != s->arch || sb->evr != s->evr)
		continue;
	      bsid = solvable_lookup_str(s, buildservice_id);
	      if (bsid && strcmp(bsid, "dod") == 0)
		continue;	/* not downloaded */
	      MAPCLR(considered, pb);
	      best[s->name] = p;
	      MAPSET(considered, p);
	    }
	}
    }
  solv_free(best);
  queue_free(&modules);
  map_free(&modulemap);
  queue_free(&modulemapq);
  if (olddisttype >= 0 && pool->disttype != olddisttype)
    set_disttype(pool, olddisttype);
}

struct metaline {
  char *l;		/* pointer to line */
  int lastoff;		/* line offset of last path element */
  int nslash;		/* number of slashes */
  int killed;		/* 1: line has been killed. 2: because of a cycle package */
};

static int metacmp(const void *ap, const void *bp)
{
  const struct metaline *a, *b;
  int r;

  a = ap;
  b = bp;
  r = a->nslash - b->nslash;
  if (r)
    return r;
  r = strcmp(a->l + 34, b->l + 34);
  if (r)
    return r;
  r = strcmp(a->l, b->l);
  if (r)
    return r;
  return a - b;
}

static char *
slurp(FILE *fp, int *lenp)
{
  int l, ll;
  char *buf = 0; 
  int bufl = 0; 

  for (l = 0; ; l += ll)
    {    
      if (bufl - l < 4096)
        {
          bufl += 4096;
	  if (bufl < 0)
	    {
	      buf = solv_free(buf);
	      l = 0;
	      break;
	    }
          buf = solv_realloc(buf, bufl);
        }
      ll = fread(buf + l, 1, bufl - l, fp); 
      if (ll < 0) 
        {
          buf = solv_free(buf);
          l = 0; 
          break;
        }
      if (ll == 0)
        {
          buf[l] = 0;   /* always zero-terminate */
          break;
        }
    }    
  if (lenp)
    *lenp = l; 
  return buf; 
}


Id
repo_add_obsbinlnk(Repo *repo, const char *path, int flags)
{
  Repodata *data;
  FILE *fp;
  char *buf;
  int len;
  SV *sv;
  unsigned char *src;
  STRLEN srcl;
  Id p;

  if ((fp = fopen(path, "r")) == 0)
    return 0;
  buf = slurp(fp, &len);
  fclose(fp);
  if (!buf || len <= 0)
    return 0;
  src = (unsigned char *)buf;
  srcl = len;
  sv = 0;
  if (srcl >= 7 && src[0] == 'p' && src[1] == 's' && src[2] == 't' && src[3] == '0' && (src[4] & 1) == 1 && src[4] >= 5) {
    src += 6;
    srcl -= 6;
    sv = retrieve(&src, &srcl, 0);
  }
  free(buf);
  if (!sv)
    return 0;
  if (SvTYPE(sv) != SVt_PVHV)
    {
      SvREFCNT_dec(sv);
      return 0;
    }
  data = repo_add_repodata(repo, flags);
  p = data2pkg(repo, data, (HV *)sv, 0);
  SvREFCNT_dec(sv);
  if (!(flags & REPO_NO_INTERNALIZE))
    repodata_internalize(data);
  return p;
}

#ifndef REPO_NO_LOCATION
# define REPO_NO_LOCATION 0
#endif

Id
repodata_addbin(Repodata *data, char *prefix, char *s, int sl, char *sid)
{
  Id p = 0;
  char *path;
#if REPO_NO_LOCATION == 0
  char *sp;
#endif

  path = solv_dupjoin(prefix, "/", s);
  if (sl >= 4 && !strcmp(s + sl - 4, ".rpm"))
    p = repo_add_rpm(data->repo, (const char *)path, REPO_REUSE_REPODATA|REPO_NO_INTERNALIZE|REPO_NO_LOCATION|RPM_ADD_WITH_PKGID|RPM_ADD_NO_FILELIST|RPM_ADD_NO_RPMLIBREQS);
#if defined(LIBSOLVEXT_FEATURE_DEBIAN)
  else if (sl >= 4 && !strcmp(s + sl - 4, ".deb"))
    p = repo_add_deb(data->repo, (const char *)path, REPO_REUSE_REPODATA|REPO_NO_INTERNALIZE|REPO_NO_LOCATION|DEBS_ADD_WITH_PKGID);
#endif
  else if (sl >= 10 && !strcmp(s + sl - 10, ".obsbinlnk"))
    {
      p = repo_add_obsbinlnk(data->repo, (const char *)path, REPO_REUSE_REPODATA|REPO_NO_INTERNALIZE|REPO_NO_LOCATION);
      /* do not overwrite location from obsbinlnk file */
      solv_free(path);
      if (p)
        repodata_set_str(data, p, buildservice_id, sid);
      return p;
    }
#if defined(LIBSOLVEXT_FEATURE_ARCHREPO) && defined(ARCH_ADD_WITH_PKGID)
  else if (sl >= 12 && (!strcmp(s + sl - 11, ".pkg.tar.gz") || !strcmp(s + sl - 11, ".pkg.tar.xz") || !strcmp(s + sl - 12, ".pkg.tar.zst")))
    p = repo_add_arch_pkg(data->repo, (const char *)path, REPO_REUSE_REPODATA|REPO_NO_INTERNALIZE|REPO_NO_LOCATION|ARCH_ADD_WITH_PKGID);
#endif
  solv_free(path);
  if (!p)
    return 0;
#if REPO_NO_LOCATION != 0
  repodata_set_location(data, p, 0, 0, s);
#else
  if ((sp = strrchr(s, '/')) != 0)
    {
      *sp = 0;
      repodata_set_str(data, p, SOLVABLE_MEDIADIR, s);
      *sp = '/';
    }
  else
    repodata_delete_uninternalized(data, p, SOLVABLE_MEDIADIR);
#endif
  repodata_set_str(data, p, buildservice_id, sid);
  return p;
}

static int
subpack_sort_cmp(const void *ap, const void *bp, void *dp)
{
  Pool *pool = (Pool *)dp;
  const Id *a = ap;
  const Id *b = bp;
  int r = a[1] - b[1];
  if (r)
    return r;
  r = strcmp(pool_id2str(pool, a[0]), pool_id2str(pool, b[0]));
  return r ? r : a[0] - b[0];
}

/* This is an OpenSSL-compatible implementation of the RSA Data Security,
 * Inc. MD5 Message-Digest Algorithm.
 *
 * Written by Solar Designer <solar@openwall.com> in 2001, and placed in
 * the public domain. */

#define F(x, y, z)                      ((z) ^ ((x) & ((y) ^ (z))))
#define G(x, y, z)                      ((y) ^ ((z) & ((x) ^ (y))))
#define H(x, y, z)                      ((x) ^ (y) ^ (z))
#define I(x, y, z)                      ((y) ^ ((x) | ~(z)))

#define STEP(f, a, b, c, d, x, t, s) \
        (a) += f((b), (c), (d)) + (x) + (t); \
        (a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s)))); \
        (a) += (b);

#if defined(__i386__) || defined(__vax__)
#define SET(n) \
        (*(MD5_u32plus *)&ptr[(n) * 4])
#define GET(n) \
        SET(n)
#else
#define SET(n) \
        (ctx->block[(n)] = \
        (MD5_u32plus)ptr[(n) * 4] | \
        ((MD5_u32plus)ptr[(n) * 4 + 1] << 8) | \
        ((MD5_u32plus)ptr[(n) * 4 + 2] << 16) | \
        ((MD5_u32plus)ptr[(n) * 4 + 3] << 24))
#define GET(n) \
        (ctx->block[(n)])
#endif

typedef unsigned long MD5_u32plus;

typedef struct {
        MD5_u32plus lo, hi;
        MD5_u32plus a, b, c, d;
        unsigned char buffer[64];
        MD5_u32plus block[16];
} MD5_CTX;

/*
 * This processes one or more 64-byte data blocks, but does NOT update
 * the bit counters.  There're no alignment requirements.
 */
static void *md5_body(MD5_CTX *ctx, void *data, unsigned long size)
{
        unsigned char *ptr;
        MD5_u32plus a, b, c, d;
        MD5_u32plus saved_a, saved_b, saved_c, saved_d;

        ptr = data;

        a = ctx->a;
        b = ctx->b;
        c = ctx->c;
        d = ctx->d;

        do {
                saved_a = a;
                saved_b = b;
                saved_c = c;
                saved_d = d;

/* Round 1 */
                STEP(F, a, b, c, d, SET(0), 0xd76aa478, 7)
                STEP(F, d, a, b, c, SET(1), 0xe8c7b756, 12)
                STEP(F, c, d, a, b, SET(2), 0x242070db, 17)
                STEP(F, b, c, d, a, SET(3), 0xc1bdceee, 22)
                STEP(F, a, b, c, d, SET(4), 0xf57c0faf, 7)
                STEP(F, d, a, b, c, SET(5), 0x4787c62a, 12)
                STEP(F, c, d, a, b, SET(6), 0xa8304613, 17)
                STEP(F, b, c, d, a, SET(7), 0xfd469501, 22)
                STEP(F, a, b, c, d, SET(8), 0x698098d8, 7)
                STEP(F, d, a, b, c, SET(9), 0x8b44f7af, 12)
                STEP(F, c, d, a, b, SET(10), 0xffff5bb1, 17)
                STEP(F, b, c, d, a, SET(11), 0x895cd7be, 22)
                STEP(F, a, b, c, d, SET(12), 0x6b901122, 7)
                STEP(F, d, a, b, c, SET(13), 0xfd987193, 12)
                STEP(F, c, d, a, b, SET(14), 0xa679438e, 17)
                STEP(F, b, c, d, a, SET(15), 0x49b40821, 22)

/* Round 2 */
                STEP(G, a, b, c, d, GET(1), 0xf61e2562, 5)
                STEP(G, d, a, b, c, GET(6), 0xc040b340, 9)
                STEP(G, c, d, a, b, GET(11), 0x265e5a51, 14)
                STEP(G, b, c, d, a, GET(0), 0xe9b6c7aa, 20)
                STEP(G, a, b, c, d, GET(5), 0xd62f105d, 5)
                STEP(G, d, a, b, c, GET(10), 0x02441453, 9)
                STEP(G, c, d, a, b, GET(15), 0xd8a1e681, 14)
                STEP(G, b, c, d, a, GET(4), 0xe7d3fbc8, 20)
                STEP(G, a, b, c, d, GET(9), 0x21e1cde6, 5)
                STEP(G, d, a, b, c, GET(14), 0xc33707d6, 9)
                STEP(G, c, d, a, b, GET(3), 0xf4d50d87, 14)
                STEP(G, b, c, d, a, GET(8), 0x455a14ed, 20)
                STEP(G, a, b, c, d, GET(13), 0xa9e3e905, 5)
                STEP(G, d, a, b, c, GET(2), 0xfcefa3f8, 9)
                STEP(G, c, d, a, b, GET(7), 0x676f02d9, 14)
                STEP(G, b, c, d, a, GET(12), 0x8d2a4c8a, 20)

/* Round 3 */
                STEP(H, a, b, c, d, GET(5), 0xfffa3942, 4)
                STEP(H, d, a, b, c, GET(8), 0x8771f681, 11)
                STEP(H, c, d, a, b, GET(11), 0x6d9d6122, 16)
                STEP(H, b, c, d, a, GET(14), 0xfde5380c, 23)
                STEP(H, a, b, c, d, GET(1), 0xa4beea44, 4)
                STEP(H, d, a, b, c, GET(4), 0x4bdecfa9, 11)
                STEP(H, c, d, a, b, GET(7), 0xf6bb4b60, 16)
                STEP(H, b, c, d, a, GET(10), 0xbebfbc70, 23)
                STEP(H, a, b, c, d, GET(13), 0x289b7ec6, 4)
                STEP(H, d, a, b, c, GET(0), 0xeaa127fa, 11)
                STEP(H, c, d, a, b, GET(3), 0xd4ef3085, 16)
                STEP(H, b, c, d, a, GET(6), 0x04881d05, 23)
                STEP(H, a, b, c, d, GET(9), 0xd9d4d039, 4)
                STEP(H, d, a, b, c, GET(12), 0xe6db99e5, 11)
                STEP(H, c, d, a, b, GET(15), 0x1fa27cf8, 16)
                STEP(H, b, c, d, a, GET(2), 0xc4ac5665, 23)

/* Round 4 */
                STEP(I, a, b, c, d, GET(0), 0xf4292244, 6)
                STEP(I, d, a, b, c, GET(7), 0x432aff97, 10)
                STEP(I, c, d, a, b, GET(14), 0xab9423a7, 15)
                STEP(I, b, c, d, a, GET(5), 0xfc93a039, 21)
                STEP(I, a, b, c, d, GET(12), 0x655b59c3, 6)
                STEP(I, d, a, b, c, GET(3), 0x8f0ccc92, 10)
                STEP(I, c, d, a, b, GET(10), 0xffeff47d, 15)
                STEP(I, b, c, d, a, GET(1), 0x85845dd1, 21)
                STEP(I, a, b, c, d, GET(8), 0x6fa87e4f, 6)
                STEP(I, d, a, b, c, GET(15), 0xfe2ce6e0, 10)
                STEP(I, c, d, a, b, GET(6), 0xa3014314, 15)
                STEP(I, b, c, d, a, GET(13), 0x4e0811a1, 21)
                STEP(I, a, b, c, d, GET(4), 0xf7537e82, 6)
                STEP(I, d, a, b, c, GET(11), 0xbd3af235, 10)
                STEP(I, c, d, a, b, GET(2), 0x2ad7d2bb, 15)
                STEP(I, b, c, d, a, GET(9), 0xeb86d391, 21)

                a += saved_a;
                b += saved_b;
                c += saved_c;
                d += saved_d;

                ptr += 64;
        } while (size -= 64);

        ctx->a = a;
        ctx->b = b;
        ctx->c = c;
        ctx->d = d;

        return ptr;
}

static void md5_init(MD5_CTX *ctx)
{
        ctx->a = 0x67452301;
        ctx->b = 0xefcdab89;
        ctx->c = 0x98badcfe;
        ctx->d = 0x10325476;
        ctx->lo = 0;
        ctx->hi = 0;
}

static void md5_update(MD5_CTX *ctx, void *data, unsigned long size)
{
        MD5_u32plus saved_lo;
        unsigned long used, free;

        saved_lo = ctx->lo;
        if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo)
                ctx->hi++;
        ctx->hi += size >> 29;

        used = saved_lo & 0x3f;

        if (used) {
                free = 64 - used;

                if (size < free) {
                        memcpy(&ctx->buffer[used], data, size);
                        return;
                }

                memcpy(&ctx->buffer[used], data, free);
                data = (unsigned char *)data + free;
                size -= free;
                md5_body(ctx, ctx->buffer, 64);
        }

        if (size >= 64) {
                data = md5_body(ctx, data, size & ~(unsigned long)0x3f);
                size &= 0x3f;
        }

        memcpy(ctx->buffer, data, size);
}

static void md5_final(MD5_CTX *ctx, unsigned char *result)
{
        unsigned long used, free;
        used = ctx->lo & 0x3f;
        ctx->buffer[used++] = 0x80;
        free = 64 - used;
        if (free < 8) {
                memset(&ctx->buffer[used], 0, free);
                md5_body(ctx, ctx->buffer, 64);
                used = 0;
                free = 64;
        }
        memset(&ctx->buffer[used], 0, free - 8);
        ctx->lo <<= 3;
        ctx->buffer[56] = ctx->lo;
        ctx->buffer[57] = ctx->lo >> 8;
        ctx->buffer[58] = ctx->lo >> 16;
        ctx->buffer[59] = ctx->lo >> 24;
        ctx->buffer[60] = ctx->hi;
        ctx->buffer[61] = ctx->hi >> 8;
        ctx->buffer[62] = ctx->hi >> 16;
        ctx->buffer[63] = ctx->hi >> 24;
        md5_body(ctx, ctx->buffer, 64);
        result[0] = ctx->a;
        result[1] = ctx->a >> 8;
        result[2] = ctx->a >> 16;
        result[3] = ctx->a >> 24;
        result[4] = ctx->b;
        result[5] = ctx->b >> 8;
        result[6] = ctx->b >> 16;
        result[7] = ctx->b >> 24;
        result[8] = ctx->c;
        result[9] = ctx->c >> 8;
        result[10] = ctx->c >> 16;
        result[11] = ctx->c >> 24;
        result[12] = ctx->d;
        result[13] = ctx->d >> 8;
        result[14] = ctx->d >> 16;
        result[15] = ctx->d >> 24;
        memset(ctx, 0, sizeof(*ctx));
}

static unsigned int buz_noise[256] =
{
  0x9be502a4U, 0xba7180eaU, 0x324e474fU, 0x0aab8451U, 0x0ced3810U,
  0x2158a968U, 0x6bbd3771U, 0x75a02529U, 0x41f05c14U, 0xc2264b87U,
  0x1f67b359U, 0xcd2d031dU, 0x49dc0c04U, 0xa04ae45cU, 0x6ade28a7U,
  0x2d0254ffU, 0xdec60c7cU, 0xdef5c084U, 0x0f77ffc8U, 0x112021f6U,
  0x5f6d581eU, 0xe35ea3dfU, 0x3216bfb4U, 0xd5a3083dU, 0x7e63e9cdU,
  0xaa9208f6U, 0xda3f3978U, 0xfe0e2547U, 0x09dfb020U, 0xd97472c5U,
  0xbbce2edeU, 0x121aebd2U, 0x0e9fdbebU, 0x7b6f5d9cU, 0x84938e43U,
  0x30694f2dU, 0x86b7a7f8U, 0xefaf5876U, 0x263812e6U, 0xb6e48ddfU,
  0xce8ed980U, 0x4df591e1U, 0x75257b35U, 0x2f88dcffU, 0xa461fe44U,
  0xca613b4dU, 0xd9803f73U, 0xea056205U, 0xccca7a89U, 0x0f2dbb07U,
  0xc53e359eU, 0xe80d0137U, 0x2b2d2a5dU, 0xcfc1391aU, 0x2bb3b6c5U,
  0xb66aea3cU, 0x00ea419eU, 0xce5ada84U, 0xae1d6712U, 0x12f576baU,
  0x117fcbc4U, 0xa9d4c775U, 0x25b3d616U, 0xefda65a8U, 0xaff3ef5bU,
  0x00627e68U, 0x668d1e99U, 0x088d0eefU, 0xf8fac24dU, 0xe77457c7U,
  0x68d3beb4U, 0x921d2acbU, 0x9410eac9U, 0xd7f24399U, 0xcbdec497U,
  0x98c99ae1U, 0x65802b2cU, 0x81e1c3c4U, 0xa130bb09U, 0x17a87badU,
  0xa70367d6U, 0x148658d4U, 0x02f33377U, 0x8620d8b6U, 0xbdac25bdU,
  0xb0a6de51U, 0xd64c4571U, 0xa4185ba0U, 0xa342d70fU, 0x3f1dc4c1U,
  0x042dc3ceU, 0x0de89f43U, 0xa69b1867U, 0x3c064e11U, 0xad1e2c3eU,
  0x9660e8cdU, 0xd36b09caU, 0x4888f228U, 0x61a9ac3cU, 0xd9561118U,
  0x3532797eU, 0x71a35c22U, 0xecc1376cU, 0xab31e656U, 0x88bd0d35U,
  0x423b20ddU, 0x38e4651cU, 0x3c6397a4U, 0x4a7b12d9U, 0x08b1cf33U,
  0xd0604137U, 0xb035fdb8U, 0x4916da23U, 0xa9349493U, 0xd83daa9bU,
  0x145f7d95U, 0x868531d6U, 0xacb18f17U, 0x9cd33b6fU, 0x193e42b9U,
  0x26dfdc42U, 0x5069d8faU, 0x5bee24eeU, 0x5475d4c6U, 0x315b2c0cU,
  0xf764ef45U, 0x01b6f4ebU, 0x60ba3225U, 0x8a16777cU, 0x4c05cd28U,
  0x53e8c1d2U, 0xc8a76ce5U, 0x8045c1e6U, 0x61328752U, 0x2ebad322U,
  0x3444f3e2U, 0x91b8af11U, 0xb0cee675U, 0x55dbff5aU, 0xf7061ee0U,
  0x27d7d639U, 0xa4aef8c9U, 0x42ff0e4fU, 0x62755468U, 0x1c6ca3f3U,
  0xe4f522d1U, 0x2765fcb3U, 0xe20c8a95U, 0x3a69aea7U, 0x56ab2c4fU,
  0x8551e688U, 0xe0bc14c2U, 0x278676bfU, 0x893b6102U, 0xb4f0ab3bU,
  0xb55ddda9U, 0xa04c521fU, 0xc980088eU, 0x912aeac1U, 0x08519badU,
  0x991302d3U, 0x5b91a25bU, 0x696d9854U, 0x9ad8b4bfU, 0x41cb7e21U,
  0xa65d1e03U, 0x85791d29U, 0x89478aa7U, 0x4581e337U, 0x59bae0b1U,
  0xe0fc9df3U, 0x45d9002cU, 0x7837464fU, 0xda22de3aU, 0x1dc544bdU,
  0x601d8badU, 0x668b0abcU, 0x7a5ebfb1U, 0x3ac0b624U, 0x5ee16d7dU,
  0x9bfac387U, 0xbe8ef20cU, 0x8d2ae384U, 0x819dc7d5U, 0x7c4951e7U,
  0xe60da716U, 0x0c5b0073U, 0xb43b3d97U, 0xce9974edU, 0x0f691da9U,
  0x4b616d60U, 0x8fa9e819U, 0x3f390333U, 0x6f62fad6U, 0x5a32b67cU,
  0x3be6f1c3U, 0x05851103U, 0xff28828dU, 0xaa43a56aU, 0x075d7dd5U,
  0x248c4b7eU, 0x52fde3ebU, 0xf72e2edaU, 0x5da6f75fU, 0x2f5148d9U,
  0xcae2aeaeU, 0xfda6f3e5U, 0xff60d8ffU, 0x2adc02d2U, 0x1dbdbd4cU,
  0xd410ad7cU, 0x8c284aaeU, 0x392ef8e0U, 0x37d48b3aU, 0x6792fe9dU,
  0xad32ddfaU, 0x1545f24eU, 0x3a260f73U, 0xb724ca36U, 0xc510d751U,
  0x4f8df992U, 0x000b8b37U, 0x292e9b3dU, 0xa32f250fU, 0x8263d144U,
  0xfcae0516U, 0x1eae2183U, 0xd4af2027U, 0xc64afae3U, 0xe7b34fe4U,
  0xdf864aeaU, 0x80cc71c5U, 0x0e814df3U, 0x66cc5f41U, 0x853a497aU,
  0xa2886213U, 0x5e34a2eaU, 0x0f53ba47U, 0x718c484aU, 0xfa0f0b12U,
  0x33cc59ffU, 0x72b48e07U, 0x8b6f57bcU, 0x29cf886dU, 0x1950955bU,
  0xcd52910cU, 0x4cecef65U, 0x05c2cbfeU, 0x49df4f6aU, 0x1f4c3f34U,
  0xfadc1a09U, 0xf2d65a24U, 0x117f5594U, 0xde3a84e6U, 0x48db3024U,
  0xd10ca9b5U
};


/* 
 * our delta search blocksize
 *
 * smaller gives more hits, but increases the hash size
 *
 * must be a multiple of 256
 * must be in range [256,32767]
 */
#define DELTA_BSIZE 1024

/* min store block len, smaller blocks are encoded as direct data */
#define MIN_BSIZE 32

/* min meta block len, must be >= 10 */
#define MIN_BSIZE_META 32

/* max meta block len, must be <= DELTA_BSIZE */
#define MAX_BSIZE_META DELTA_BSIZE

/* number of slots per data area */
#define SLOTS_PER_AREA	4095


/* buzhash by Robert C. Uzgalis */
/* General hash functions. Technical Report TR-92-01, The University
   of Hong Kong, 1993 */

static unsigned int buzhash(unsigned char *buf)
{
  unsigned int x = 0x83d31df4U;
  int i;
  for (i = DELTA_BSIZE ; i != 0; i--)
    x = (x << 1) ^ (x & (1 << 31) ? 1 : 0) ^ buz_noise[*buf++];
  return x;
}

static void md5block(unsigned char *buf, int len, unsigned char *out)
{
  MD5_CTX ctx;
  md5_init(&ctx);
  md5_update(&ctx, buf, (unsigned long)len);
  md5_final(&ctx, out);
}

#define HASHCHAIN_START 7
#define HASHCHAIN_NEXT(h, hh, mask) (((h) + (hh)++) & (mask))


struct deltastore {
  int fd;				/* file descriptor */
  int rdonly;				/* store is read only */

  unsigned long long end;		/* store file size */

  unsigned long long *offsets;		/* the data areas we know about */
  int noffsets;

  unsigned char *hash;			/* our hash */
  unsigned int hm;			/* size of hash */
  unsigned int hf;			/* hash fill */
  unsigned int hd;			/* entries not in hash */

  int freecnt;				/* free slots in last slot area */
  int usedcnt;				/* used slots in last slot area */
  unsigned long long slotsoffset;	/* offset of last slot area */
};

struct deltaout {
  FILE *fp;
  struct deltastore *store;

  /* for block coalescence */
  unsigned long long oldoffset;
  unsigned long long oldsize;

  /* for offset encoding */
  unsigned long long lastoffset;

  /* for meta block creation */
  int outbuf_do_meta;				/* create meta blocks */
  unsigned char outbuf[MAX_BSIZE_META + 16];	/* 16 extra bytes for one encoded block */
  int outbuf_len;
  /* offset patching */
  unsigned long long outbuf_startoffset;
  int outbuf_startoffset_set;
  int outbuf_set_len1;
  int outbuf_set_len2;
  unsigned long long outbuf_set_offset;		/* offset we need to patch in, already encoded */
};

static inline unsigned long long getu48(unsigned char *d)
{
  unsigned long long x = d[0] << 8 | d[1];
  return (x << 32) | (d[2] << 24 | d[3] << 16 | d[4] << 8 | d[5]);
}

static inline void putu48(unsigned char *d, unsigned long long x)
{
  d[0] = x >> 40;
  d[1] = x >> 32;
  d[2] = x >> 24;
  d[3] = x >> 16;
  d[4] = x >> 8;
  d[5] = x;
}

static inline unsigned int getu32(unsigned char *d)
{
  return d[0] << 24 | d[1] << 16 | d[2] << 8 | d[3];
}

static inline void putu32(unsigned char *d, unsigned int x)
{
  d[0] = x >> 24;
  d[1] = x >> 16;
  d[2] = x >> 8;
  d[3] = x;
}

/**
 **  store handling
 **/

static int
finddataarea(struct deltastore *store, unsigned long long offset)
{
  int i;
  for (i = 0; i < store->noffsets; i += 2)
    if (offset >= store->offsets[i] && offset < store->offsets[i + 1])
      return i;
  return -1;
}

static void
adddataarea(struct deltastore *store, unsigned long long start, unsigned long long end)
{
  unsigned long long *newoffsets;
  if (store->noffsets && store->offsets[store->noffsets - 1] == start)
    {
      store->offsets[store->noffsets - 1] = end;
      return;
    }
  if (store->offsets)
    newoffsets = realloc(store->offsets, (store->noffsets + 2) * sizeof(unsigned long long));
  else
    newoffsets = malloc((store->noffsets + 2) * sizeof(unsigned long long));
  if (!newoffsets)
    return;
  newoffsets[store->noffsets++] = start;
  newoffsets[store->noffsets++] = end;
  store->offsets = newoffsets;
}

static int
addslotarea(struct deltastore *store, int cnt)
{
  unsigned char *slots;
  if (!cnt || cnt > 65535)
    return 0;
  if (store->rdonly)
    return 0;
  if ((store->end & 4095) != 0)		/* pad to multiple of 4096 */
    {
      char pad[4096];
      int l = 4096 - (store->end & 4095);
      memset(pad, 0, l);
      if (pwrite(store->fd, pad, l, store->end) != l)
	{
	  perror("pwrite pad next slotsarea");
	  return 0;
	}
      store->end += l;
    }
  if (store->end + (cnt + 1) * 16 >= (1LL << 48))
    return 0;				/* store too big! */
  slots = calloc(cnt + 1, 16);
  if (!slots)
    return 0;
  memcpy(slots, "OBSDELT", 8);
  slots[8] = cnt >> 8;
  slots[9] = cnt;
  /* write pointer to next slot area */
  if (store->end)
    {
      putu48(slots + 10, store->end);
      if (pwrite(store->fd, slots, 16, store->slotsoffset) != 16)
	{
	  perror("pwrite update next slotsarea");
	  free(slots);
	  return 0;
	}
      memset(slots + 10, 0, 6);
    }
  if (pwrite(store->fd, slots, (cnt + 1) * 16, store->end) != (cnt + 1) * 16)
    {
      perror("pwrite new slotarea");
      free(slots);
      return 0;
    }
  free(slots);
  store->slotsoffset = store->end;
  store->end += (cnt + 1) * 16;
  store->freecnt = cnt;
  store->usedcnt = 0;
  return 1;
}

/* 
 * add a new block to the store.
 * returns the store offset, 0 on error
 */
static unsigned long long
putinstore(struct deltastore *store, unsigned char *buf, int size, unsigned char *md5, unsigned int hx)
{
  unsigned char md5buf[16];
  unsigned char hp[16];
  unsigned long long offset;

  unsigned char *hash;
  unsigned int h, hh, hm;

  if (!size || size > DELTA_BSIZE)
    return 0;

  if (store->rdonly)
    return 0;
  if (store->freecnt == 0 && !addslotarea(store, SLOTS_PER_AREA))
    return 0;

  /* write data */
  offset = store->end;
  if (offset + size >= (1LL << 48))
    return 0;			/* store too big! */
  if (pwrite(store->fd, buf, size, store->end) != size)
    {
      perror("pwrite data");
      return 0;
    }
  adddataarea(store, store->end, store->end + size);
  store->end += size;

  /* write slot */
  if (!md5)
    {
      md5block(buf, size, md5buf);
      md5 = md5buf;
    }
  hp[0] = size >> 8;
  hp[1] = size;
  putu48(hp + 2, offset);
  if (size == DELTA_BSIZE)
    {
      if (!hx)
        hx = buzhash(buf);
      putu32(hp + 8, hx);
      memcpy(hp + 12, md5, 4);
    }
  else
    {
      hp[0] |= 0x80;		/* small block marker */
      memcpy(hp + 8, md5, 8);
      hx = getu32(hp + 8);	/* needed later */
    }
#if 0
{
  int j;
  printf("NEW SLOT");
  for (j = 0; j < 16; j++)
    printf(" %02x", hp[j]);
  printf("\n");
}
#endif
  if (pwrite(store->fd, hp, 16, store->slotsoffset + (store->usedcnt + 1) * 16) != 16)
    {
      perror("pwrite slot");
      return 0;
    }
  store->freecnt--;
  store->usedcnt++;

  /* update hash */
  hm = store->hm;
  hash = store->hash;
  h = hx & hm;
  hh = HASHCHAIN_START;
  while (hash[16 * h] != 0)
    h = HASHCHAIN_NEXT(h, hh, hm);
  memcpy(hash + 16 * h, hp, 16);
  store->hf++;
  return offset;
}

/* make sure that we found the correct block */
static int
checkstore(struct deltastore *store, unsigned long long offset, unsigned char *buf, int size)
{
  unsigned char buf2[4096];
  while (size)
    {
      int l = size > 4096 ? 4096 : size;
      if (pread(store->fd, buf2, l, offset) != l)
	return 0;
      if (memcmp(buf2, buf, l) != 0)
	return 0;
      size -= l;
      buf += l;
      offset += l;
    }
  return 1;
}

/* 
 * try to find a (non-rolling) block in the store. If not found, add it.
 * returns the store offset, 0 on error
 */
static unsigned long long
reuse_or_add_block(struct deltastore *store, unsigned char *buf, int size)
{
  unsigned char md5[16];
  unsigned int h, hh, hm;
  unsigned char *hash;
  unsigned long long offset;

  if (!size || size >= DELTA_BSIZE)
    return 0;		/* only small non-rolling blocks for now */
  md5block(buf, size, md5);
  hm = store->hm;
  hash = store->hash;
  h = (md5[0] << 24 | md5[1] << 16 | md5[2] << 8 | md5[3]) & hm;
  hh = HASHCHAIN_START;
  while (hash[16 * h] != 0)
    {
      unsigned char *hp = hash + 16 * h;
      if (((hp[0] & 0x7f) << 8 | hp[1]) == size && !memcmp(hp + 8, md5, 8))
	{
	  offset = getu48(hp + 2);
	  if (checkstore(store, offset, buf, size))
	    return offset;
	}
      h = HASHCHAIN_NEXT(h, hh, hm);
    }
  return putinstore(store, buf, size, md5, 0);
}

/**
 **  block encoding
 **/

static int
encodelonglong(FILE *ofp, unsigned long long x)
{
  unsigned long long z = 1;
  int c;
  do
    {
      z = z << 7 | (x & 127);
      x >>= 7;
    }
  while (x);
  for (;;)
    {
      c = (z & 127) | 128;
      z >>= 7;
      if (z == 1)
	break;
      if (putc(c, ofp) == EOF)
	return 0;
    }
  if (putc(c ^ 128, ofp) == EOF)
    return 0;
  return 1;
}

static int
encodelonglong_mem(unsigned char *bp, unsigned long long x)
{
  unsigned long long z = 1;
  int c;
  int l = 0;
  do
    {
      z = z << 7 | (x & 127);
      x >>= 7;
    }
  while (x);
  for (;;)
    {
      c = (z & 127) | 128;
      z >>= 7;
      if (z == 1)
	break;
      *bp++ = c;
      l++;
    }
  *bp = c ^ 128;;
  return l + 1;
}


#if 1
/* fancy delta conversion, seems to work better than the simple xor */
static inline unsigned long long
encodeoffset(unsigned long long oldoffset, unsigned long long offset)
{
  if (oldoffset & (1LL << 47))
    {
      offset ^= ((1LL << 48) - 1);
      oldoffset ^= ((1LL << 48) - 1);
    }
  if (offset < oldoffset * 2)
    {
      if (offset < oldoffset)
	offset = (oldoffset - offset - 1) << 1 | 1;
      else
	offset = (offset - oldoffset) << 1;
    }
  return offset;
}

static inline unsigned long long
decodeoffset(unsigned long long oldoffset, unsigned long long offset)
{
  int neg = oldoffset & (1LL << 47) ? ((1LL << 48) - 1) : 0;
  oldoffset ^= neg;
  if (offset < oldoffset * 2)
    {
      if (offset & 1)
	offset = oldoffset - ((offset >> 1) + 1);
      else
	offset = oldoffset + (offset >> 1);
    }
  return offset ^ neg;
}

#else
static inline unsigned long long
encodeoffset(unsigned long long oldoffset, unsigned long long offset)
{
  return oldoffset ^ offset;
}

static inline unsigned long long
decodeoffset(unsigned long long oldoffset, unsigned long long offset)
{
  return oldoffset ^ offset;
}
#endif

static int
flushoutbuf(struct deltaout *out)
{
  if (!out->outbuf_len)
    return 1;
  if (out->outbuf_len >= MAX_BSIZE_META)
    return 0;

  if (out->outbuf_len >= MIN_BSIZE_META)
    {
      /* put as meta block into store! */
      int size = out->outbuf_len;
      unsigned long long offset;
#if 0
      printf("META size %d\n", out->outbuf_len);
#endif
      offset = reuse_or_add_block(out->store, out->outbuf, size);
      if (!offset)
	return 0;
      /* encode meta block into outbuf */
      if (out->outbuf_startoffset_set)
	out->lastoffset = out->outbuf_startoffset;
      out->outbuf[0] = 15;			/* meta */
      out->outbuf_len = 1;
      out->outbuf_len += encodelonglong_mem(out->outbuf + out->outbuf_len, size);
      out->outbuf_len += encodelonglong_mem(out->outbuf + out->outbuf_len, encodeoffset(out->lastoffset, offset));
      out->lastoffset = offset + size;
      out->outbuf_startoffset_set = 0;
    }

  if (out->outbuf_startoffset_set)
    {
      /* tricky: fix up first offset! */
      unsigned char buf[9];
      int l = encodelonglong_mem(buf, out->outbuf_set_offset);
      if (fwrite(out->outbuf, out->outbuf_set_len1, 1, out->fp) != 1)
	return 0;
      if (fwrite(buf, l, 1, out->fp) != 1)
	return 0;
      if (out->outbuf_set_len2 < out->outbuf_len && fwrite(out->outbuf + out->outbuf_set_len2, out->outbuf_len - out->outbuf_set_len2, 1, out->fp) != 1)
	return 0;
    }
  else if (fwrite(out->outbuf, out->outbuf_len, 1, out->fp) != 1)
    return 0;
  out->outbuf_len = 0;
  out->outbuf_startoffset_set = 0;
  return 1;
}

static int
encodestoreblock_real(struct deltaout *out, unsigned long long offset, unsigned long long size)
{
#if 0
  printf("BLOCK %#llx %llu\n", offset, size);
#endif
  if (out->outbuf_do_meta)
    {
      int lastlen = out->outbuf_len;
      /* the following code is needed as we want to use a lastoffset of
       * zero if this ends up in a meta instruction. So we encode with
       * lastoffset zero but also store the real lastoffset and byte offsets,
       * in order to fix up the offset in flushbuf */
      int set = out->outbuf_startoffset_set;
      if (!set)
	{
	  out->outbuf_startoffset_set = 1;
	  out->outbuf_startoffset = out->lastoffset;
	  out->outbuf_set_offset = encodeoffset(out->lastoffset, offset);
	  out->lastoffset = 0;
	}
      out->outbuf_len += encodelonglong_mem(out->outbuf + out->outbuf_len, out->oldsize + 256);
      if (!set)
        out->outbuf_set_len1 = out->outbuf_len;
      out->outbuf_len += encodelonglong_mem(out->outbuf + out->outbuf_len, encodeoffset(out->lastoffset, offset));
      if (!set)
        out->outbuf_set_len2 = out->outbuf_len;

      if (out->outbuf_len >= DELTA_BSIZE)
	{
	  /* buffer too full. revert changes. flush outbuf. retry */
	  out->outbuf_len = lastlen;
	  if (!set)
	    {
	      out->outbuf_startoffset_set = 0;
	      out->lastoffset = out->outbuf_startoffset;
	    }
	  if (!flushoutbuf(out))
	    return 0;
	  return encodestoreblock_real(out, offset, size);
	}
    }
  else
    {
      if (!encodelonglong(out->fp, size + 256))
	return 0;
      if (!encodelonglong(out->fp, encodeoffset(out->lastoffset, offset)))
	return 0;
    }
  out->lastoffset = offset + size;
  return 1;
}

/* encode a store block instruction
 * we delay the real encoding so that we can join adjacent blocks
 */
static int
encodestoreblock(struct deltaout *out, unsigned long long offset, unsigned long long size)
{
  if (out->oldoffset)
    {
      if (out->oldoffset + out->oldsize == offset)
	{
	  out->oldsize += size;
	  return 1;
	}
      if (!encodestoreblock_real(out, out->oldoffset, out->oldsize))
        return 0;
    }
  out->oldoffset = offset;	/* block not yet written */
  out->oldsize = size;
  return 1;
}

/* encode a direct data instruction
 */
static int
encodedirect(struct deltaout *out, unsigned char *buf, int size)
{
  if (!size)
    return 1;
  if (size >= 256 - 16)
    return 0;
  if (out->oldoffset)
    {
      if (!encodestoreblock(out, 0, 0))	/* flush */
	return 0;
    }
#if 0
  printf("DIRECT %u\n", size);
#endif
  if (out->outbuf_do_meta)
    {
      if (out->outbuf_len + 1 + size >= DELTA_BSIZE)
	{
	  /* buffer too full. flush outbuf */
	  if (!flushoutbuf(out))
	    return 0;
	}
      out->outbuf[out->outbuf_len++] = 16 + size;
      memcpy(out->outbuf + out->outbuf_len, buf, size);
      out->outbuf_len += size;
    }
  else
    {
      if (putc(16 + size, out->fp) == EOF)
	return 0;
      if (fwrite(buf, size, 1, out->fp) != 1)
	return 0;
    }
  return 1;
}

/**
 **  the delta algorithm
 **/

static unsigned long long
extendblock(struct deltastore *store, FILE *fp, unsigned long long offset, unsigned long long areaend, unsigned long long maxextend)
{
  unsigned char buf[1024];
  int c, i, bufl;
  unsigned long long extend = 0;

  if (offset >= areaend)
    return 0;
  if (areaend - offset < maxextend)
    maxextend = areaend - offset;
  if (!maxextend)
    return 0;
  i = bufl = 0;
  for (;;)
    {
      if (i == bufl)
	{
	  bufl = maxextend > 1024 ? 1024 : maxextend;
	  if (bufl == 0)
	    return extend;
	  if (pread(store->fd, buf, bufl, (off_t)offset) != bufl)
	    return extend;
	  offset += bufl;
	  maxextend -= bufl;
	  i = 0;
	}
      c = getc(fp);
      if (c == EOF)
	return extend;
      if (buf[i++] != c)
	{
	  ungetc(c, fp);
	  return extend;
	}
      extend++;
    }
}

static unsigned long long
extendblock_back(struct deltastore *store, unsigned char *data, unsigned long long offset, unsigned long long areastart, unsigned long long maxextend)
{
  unsigned char buf[1024];
  unsigned long long extend = 0;
  int bufl;

  if (offset <= areastart)
    return 0;
  if (offset - areastart < maxextend)
    maxextend = offset - areastart;
  if (!maxextend)
    return 0;
  bufl = 0;
  for (;;)
    {
      if (bufl == 0)
	{
	  bufl = maxextend > 1024 ? 1024 : maxextend;
	  if (bufl == 0)
	    return extend;
	  offset -= bufl;
	  if (pread(store->fd, buf, bufl, (off_t)offset) != bufl)
	    return extend;
	  maxextend -= bufl;
	}
      if (*--data != buf[--bufl])
	return extend;
      extend++;
    }
}

static int
dosimple(struct deltastore *store, struct deltaout *out, unsigned char *buf, int size)
{
  unsigned long long offset;

  while (size >= DELTA_BSIZE)
    {
      offset = putinstore(store, buf, DELTA_BSIZE, 0, 0);
      if (!offset || !encodestoreblock(out, offset, DELTA_BSIZE))
	return 0;
      size -= DELTA_BSIZE;
      buf += DELTA_BSIZE;
    }
  if (size < MIN_BSIZE)
    return encodedirect(out, buf, size);
  offset = reuse_or_add_block(store, buf, size);
  if (!offset)
    return 0;
  return encodestoreblock(out, offset, size);
}

static int
dodelta(struct deltastore *store, FILE *fp, struct deltaout *out, unsigned long long size)
{
  unsigned char buf[DELTA_BSIZE * 16];
  unsigned char md5[16];
  unsigned long long offset, extendf, extendb;
  unsigned int h, hh, hm, hx;
  unsigned char *hash;
  int c, foundit, bi;

#if 0
  printf("DODELTA\n");
#endif
  hm = store->hm;
  hash = store->hash;
  while (size)
    {
      if (size < DELTA_BSIZE)
	{
	  if (fread(buf, (int)size, 1, fp) != 1)
	    return 0;
	  if (!dosimple(store, out, buf, (int)size))
	    return 0;
	  break;
	}
      /* read a block */
      bi = 0;
      if (fread(buf, DELTA_BSIZE, 1, fp) != 1)
	return 0;
      size -= DELTA_BSIZE;

      hx = buzhash(buf);
      foundit = 0;

      /* start rolling */
      for (;;)
	{
	  int md5set = 0;
	  /* check if the block at (buf + bi) is in the hash */
#if 0
	  if (hx != buzhash(buf + bi))
	    abort();
#endif
	  hh = HASHCHAIN_START;
	  for (h = hx & hm; hash[16 * h] != 0; h = HASHCHAIN_NEXT(h, hh, hm))
	    {
	      unsigned char *hp = hash + 16 * h;
	      /* first check block len */
	      if (hp[0] != (DELTA_BSIZE >> 8) || hp[1] != (DELTA_BSIZE & 0xff))
		continue;
	      /* then check complete hash value */
	      if (hp[8] != (hx >> 24))
		continue;
	      if ((hp[8] << 24 | hp[9] << 16 | hp[10] << 8 | hp[11]) != hx)
		continue;
	      /* then check strong hash */
	      if (!md5set)
		{
		  md5block(buf + bi, DELTA_BSIZE, md5);
		  md5set = 1;
		}
	      if (memcmp(hp + 12, md5, 4) != 0)
		continue;
	      /* looks good. check data */
	      offset = getu48(hp + 2);
	      if (!checkstore(store, offset, buf + bi, DELTA_BSIZE))
		continue;
	      /* yes! found block in store! */
	      /* try to extend found block */
	      c = finddataarea(store, offset);
	      extendf = extendb = 0;
	      if (c >= 0)
		{
		  extendf = extendblock(store, fp, offset + DELTA_BSIZE, store->offsets[c + 1], size);
		  size -= extendf;	/* extended bytes */
		  extendb = extendblock_back(store, buf + bi, offset, store->offsets[c], bi);
		  offset -= extendb;
		  bi -= extendb;
		}
	      /* encode data before block */
	      if (bi)
		{
		  if (!dosimple(store, out, buf, bi))
		    return 0;
		  bi = 0;
		}
	      /* encode */
	      if (!encodestoreblock(out, offset, DELTA_BSIZE + extendf + extendb))
		return 0;
	      foundit = 1;
	      break;
	    }
	  if (foundit)
	    break;

          /* not found. move block one byte */
	  if (!size)
	    {
	      if (!dosimple(store, out, buf, bi + DELTA_BSIZE))
		return 0;
	      break;
	    }
	  c = fgetc(fp);
	  if (c == EOF)
	    return 0;
	  size--;
	  buf[DELTA_BSIZE + bi] = c;
	  hx = (hx << 1) ^ (hx & (1 << 31) ? 1 : 0) ^ buz_noise[c];
	  c = buf[bi++];
	  hx ^= buz_noise[c] ^ (0x83d31df4U ^ 0x07a63be9U);
	  if (bi == sizeof(buf) - DELTA_BSIZE)
	    {
	      /* trim down, but leave one block for backward extension */
	      if (!dosimple(store, out, buf, bi - DELTA_BSIZE))
		return 0;
	      /* no overlap as the buffer is >= 4 * DELTA_BSIZE */
	      memcpy(buf, buf + bi - DELTA_BSIZE, 2 * DELTA_BSIZE);
	      bi = DELTA_BSIZE;
	    }
	}
    }
  if (!encodestoreblock(out, 0, 0))	/* flush */
    return 0;
  if (!flushoutbuf(out))
    return 0;
  return 1;
}

static int
readdeltastore(struct deltastore *store, int fd, int rdonly, unsigned long long xsize)
{
  unsigned char *slots;
  unsigned char oneslot[16];
  unsigned long long offset, nextoffset, lastgoodoffset;
  struct stat st;
  unsigned long long fsize;
  unsigned int nslots = 0, hslots;
  unsigned char *hash;
  unsigned int hm, h, hh, hf, hd;
  int isbad = 0;
  int i, lasti, cnt, maxcnt = 0;
  unsigned int drop = 0;

  memset(store, 0, sizeof(*store));
  store->fd = fd;
  store->rdonly = rdonly;
  if (fstat(fd, &st))
    {
      perror("fstat");
      return 0;
    }
  fsize = (unsigned long long)st.st_size;
  store->end = fsize;

  /* first pass: find number of used entries */
  offset = 0;
  lastgoodoffset = -1;
  for (;;)
    {
      if (offset == fsize)
	break;
      if (offset + 16 > fsize)
	{
	  fprintf(stderr, "WARNING: slot area exceeds file size!\n");
	  isbad = 1;
	  break;
	}
      if (pread(fd, oneslot, 16, offset) != 16)
	return 0;
      if (memcmp(oneslot, "OBSDELT", 8) != 0)
	{
	  fprintf(stderr, "WARNING: slot magic error!\n");
	  isbad = 1;
	  break;
	}
      cnt = oneslot[8] << 8 | oneslot[9];
      nextoffset = getu48(oneslot + 10);
      if (!nextoffset)
	nextoffset = fsize;
      offset += (cnt + 1) * 16;
      if (offset > fsize)
	{
	  fprintf(stderr, "WARNING: slot area exceeds file size!\n");
	  isbad = 1;
	  break;
	}
      nslots += cnt;
      lastgoodoffset = offset - (cnt + 1) * 16;
      if (cnt > maxcnt)
	maxcnt = cnt;
      if (offset > nextoffset)
	{
	  fprintf(stderr, "WARNING: end of slots bigger than nextoffset!\n");
	  isbad = 1;
	  break;
	}
      offset = nextoffset;
    }

  if (isbad && !store->rdonly)
    {
      fprintf(stderr, "WARNING: fixing up bad slots!\n");
      if (lastgoodoffset == -1)
	{
	  /* worst case: first slots area is damaged */
	  memset(oneslot, 0, 16);
	  memcpy(oneslot, "OBSDELT", 8);
	  putu48(oneslot + 10, fsize);
	  if (pwrite(store->fd, oneslot, 16, 0) != 16)
	    {
	      perror("pwrite repair first slots area");
	      return 0;
	    }
	}
      else
	{
	  putu48(oneslot + 10, fsize);
	  if (pwrite(store->fd, oneslot + 10, 6, lastgoodoffset + 10) != 6)
	    {
	      perror("pwrite repair bad slots area nextoffset");
	      return 0;
	    }
	}
      isbad = 0;
    }

  slots = calloc(maxcnt + 1, 16);
  if (!slots)
    return 0;

  /* find good hash size and allocate hash */
  hslots = nslots + xsize / 512;
  while (hslots & (hslots - 1))
    hslots = hslots & (hslots - 1);
  if (hslots < 16384)
    hslots = 16384;		/* 1 MByte min mem */
  while (hslots > 128 * 1024 * 1024)	/* 8 GByte max mem */
    {
      /* oh no. max size reached. drop half of slots */
      hslots >>= 1;
      drop += (drop ? drop : nslots) / 2;
    }
  hslots *= 4;
  store->hm = hm = hslots - 1;
  store->hash = hash = calloc(hm + 1, 16);
  if (!hash)
    {
      fprintf(stderr, "could not allocate hash (%u MB)\n", (hm + 1) / (1024 * 1024 / 16));
      free(slots);
      return 0;
    }

  /* second pass: fill the hash */
  offset = 0;
  hf = hd = 0;
  for (;;)
    {
      int toread = 16 * (maxcnt + 1);
      if (isbad && lastgoodoffset == -1)
	break;
      if (offset >= fsize)
	break;
      if (offset + toread > fsize)
	toread = fsize - offset;
      if (pread(fd, slots, toread, offset) != toread)
	{
	  free(slots);
	  return 0;
	}
      if (memcmp(slots, "OBSDELT", 8) != 0)
	break;
      cnt = oneslot[8] << 8 | oneslot[9];
      offset += 16 * (cnt + 1);
      nextoffset = getu48(slots + 10);
      if (!nextoffset)
	nextoffset = fsize;
      if (offset > nextoffset)
	break;
      if (offset != nextoffset)
	adddataarea(store, offset, nextoffset);
      lasti = 0;
      for (i = 1; i < cnt + 1; i++)
	if (slots[16 * i])
	  {
	    unsigned char *hp = slots + 16 * i;
	    int len = (hp[0] & 127) << 8 | hp[1];
	    unsigned long long o = getu48(hp + 2);
	    lasti = i;
	    if (drop)
	      {
		drop--;
		hd++;
	      }
	    else if (o >= offset && o + len <= nextoffset)
	      {
		/* a good one. add to hash. */
	        h = getu32(hp + 8) & hm;
		hh = HASHCHAIN_START;
		while (hash[16 * h] != 0)
		  h = HASHCHAIN_NEXT(h, hh, hm);
		memcpy(hash + 16 * h, hp, 16);
		hf++;
	      }
	  }
      store->slotsoffset = offset - 16 * (cnt + 1);
      store->freecnt = cnt - lasti;
      store->usedcnt = lasti;
      if (isbad && lastgoodoffset == store->slotsoffset)
	break;
      offset = nextoffset;
    }
  store->hf = hf;
  store->hd = hd;
#if 0
  printf("readdeltastore: have %d entries, %d dropped, hash size %d\n", hf, hd, hm + 1);
#endif
  free(slots);
  return 1;
}

static void
printdeltastorestats(struct deltastore *store)
{
  unsigned int buckets[2048];
  unsigned int hm, hf, hd;
  unsigned char *hp;
  int i, j, bc = 16;

  memset(buckets, 0, sizeof(buckets));
  hm = store->hm;
  hf = store->hf;
  hd = store->hd;

  printf("store size: %llu (%u MB)\n", store->end, (unsigned int)(store->end / (1024 * 1024)));
  printf("hash mask: 0x%x (%u MB hash mem)\n", hm, (unsigned int)(hm / 65536) + 1);
  printf("hash entries set: %u (%.2f %%)\n", hf, ((double)hf * 100) / ((double)hm + 1));
  printf("hash entries dropped: %u (%.2f %%)\n", hd, hd ? ((double)hd * 100) / ((double)hf + (double)hd) : 0.);
  for (hp = store->hash;; hp += 16)
    {
      if (hp[0])
        buckets[((hp[0] & 0x7f) << 8 | hp[1]) / 16]++;
      if (!hm--)
	break;
    }
  for (i = 2047; i >= 1; i--)
    if (buckets[i])
      break;
  i++;
  while (i > 16)
    {
      for (j = 0; j < i; j += 2)
	buckets[j / 2] = buckets[j] + buckets[j + 1];
      i = (i + 1) / 2;
      bc *= 2;
    }
  printf("block stats:\n");
  for (j = 0; j < i; j++)
    printf("  size %#6x - %#6x: %10u\n", j * bc, j * bc + bc - 1, buckets[j]);
  printf("data areas: %d\n", store->noffsets / 2);
}

static void
freedeltastore(struct deltastore *store)
{
  if (store->hash)
    free(store->hash);
  if (store->offsets)
    free(store->offsets);
}

static void
settimes(int fd, struct stat *st)
{
  struct timeval tv[2];

  tv[0].tv_sec = st->st_atime;
  tv[0].tv_usec = 0;
  tv[1].tv_sec = st->st_mtime;
  tv[1].tv_usec = 0;
  futimes(fd, tv);
}

static int
checkhexcomp(unsigned char *buf)
{
  int i, hexcomp = 0;
  for (i = 0; i < 110; i++)
    {
      int c = *buf++;
      if (c >= '0' && c <= '9')
	continue;
      else if (c >= 'A' && c <= 'F')
	{
	  if (!hexcomp)
	    hexcomp = 1;
	  if (hexcomp != 1)
	    break;
	}
      else if (c >= 'a' && c <= 'f')
	{
	  if (!hexcomp)
	    hexcomp = 2;
	  if (hexcomp != 2)
	    break;
	}
      else
	break;
    }
  if (i < 110)
    return 0;
  return hexcomp ? hexcomp : 1;
}

static unsigned int fromhex(unsigned char *hex)
{
  int i;
  unsigned int x = 0;
  for (i = 0; i < 8; i++, hex++)
    {
      if (*hex >= '0' && *hex <= '9')
	x = x << 4 | (*hex - '0');
      else if (*hex >= 'a' && *hex <= 'f')
	x = x << 4 | (*hex - ('a' - 10));
      else if (*hex >= 'A' && *hex <= 'F')
	x = x << 4 | (*hex - ('A' - 10));
    }
  return x;
}

static void
magic_inode_increment(unsigned char *cpio)
{
  unsigned int inode = getu32(cpio + 3);
  if (inode)
    putu32(cpio + 3, inode + 1);
}

static int
makedelta(struct deltastore *store, FILE *fp, FILE *ofp, unsigned long long fpsize)
{
  unsigned char cpiohead[1024 + 16384];
  unsigned char oldcpio[1024];
  int trailerseen = 0;
  int i, j;
  struct deltaout out;

  if (fpsize >= (1LL << 40))
    return 0;

  /* init deltaout struct */
  memset(&out, 0, sizeof(out));
  out.fp = ofp;
  out.store = store;
  out.outbuf_do_meta = 1;		/* create meta blocks */

  /* write our header */
  memset(cpiohead, 0, 32);
  memcpy(cpiohead, "OBScpio", 8);
  putu48(cpiohead + 10, fpsize);
  if (fwrite(cpiohead, 16, 1, ofp) != 1)
    return 0;

  memset(oldcpio, 0, 1024);
  while (!trailerseen)
    {
      unsigned long long fsize;
      unsigned int hsize, nsize;
      int run;
      int hexcomp;
      int noff = 110;
      int zero;

      /* read the header */
      if (fread(cpiohead, 110, 1, fp) != 1)
	{
	  fprintf(stderr, "cpio header read error\n");
	  return 0;
	}
      if (memcmp(cpiohead, "070701", 6) != 0)
	{
	  fprintf(stderr, "not a newc cpio archive\n");
	  return 0;
	}
      fsize = fromhex(cpiohead + 54);
      nsize = fromhex(cpiohead + 94);
      if (nsize > 16384)
	{
	  fprintf(stderr, "filename size too big\n");
	  return 0;
	}
      hsize = noff + nsize;
      if (hsize & 3)
	hsize += 4 - (hsize & 3);
      /* check if we can do hex compression */
      hexcomp = checkhexcomp(cpiohead);
      if (hexcomp)
	{
	  /* do fancy hex compression */
	  cpiohead[0] = 0x07;
	  cpiohead[1] = 0x07;
	  cpiohead[2] = 0x01;
	  for (i = 3; i < 55; i += 4)
	    {
	      unsigned int x = fromhex(cpiohead + i * 2);
	      putu32(cpiohead + i, x);
	    }
	   noff -= 55;
	   hsize -= 55;
	}

#if 0
      printf("fsize = %d, nsize = %d, hsize = %d\n", fsize, nsize, hsize);
#endif
      if (fread(cpiohead + noff, hsize - noff, 1, fp) != 1)
	{
	  fprintf(stderr, "cpio header read error\n");
	  return 0;
	}
      if (fsize == 0 && nsize == 11 && !memcmp(cpiohead + noff, "TRAILER!!!", 11))
	{
	  trailerseen = 1;
	  while (hsize < sizeof(cpiohead))
	    {
	      int c = getc(fp);
	      if (c == EOF)
		break;
	      cpiohead[hsize++] = c;
	    }
	  if (hsize == sizeof(cpiohead))
	    {
	      fprintf(stderr, "excess trailer data\n");
	      return 0;
	    }
	}
      /* check trailing zero */
      for (zero = 0; zero < 4; zero++)
	if (cpiohead[hsize - 1 - zero] != 0)
	  break;
      /* write the header */
      if (putc(2 + hexcomp, ofp) == EOF)
	return 0;
      for (i = 0; i < 1024 && i < hsize; i++)
	{
	  cpiohead[i] ^= oldcpio[i];
	  oldcpio[i] ^= cpiohead[i];
	}
      if (hexcomp)
	magic_inode_increment(oldcpio);
      run = 0;
      hsize -= zero;
      for (i = 0; i < hsize; i++)
	{
	  if (cpiohead[i] == 0)
	    {
	      run++;
	      if (i + 1 < hsize)
	        continue;
	    }
	  while (run)
	    {
	      int z = run > 127 ? 127 : run;
	      if (putc(z, ofp) == EOF)
		return 0;
	      run -= z;
	    }
	  if (cpiohead[i] == 0)
	    break;		/* ended in zero */
	  for (j = i; j < hsize - 1; j++)
	    if (cpiohead[j] == 0 && cpiohead[j + 1] == 0)
	      break;
	  if (j == hsize - 1)
	    j = hsize;
	  j -= i;
	  if (j > 123)
	    j = 123;
	  if (putc(j + 128, ofp) == EOF)
	    return 0;
	  while (j-- > 0)
	    {
	      int z = cpiohead[i++];
	      if (putc(z, ofp) == EOF)
		return 0;
	    }
	  i--;
	}
      if (putc(zero ? zero + 251 : 0, ofp) == EOF)
	return 0;
      if (fsize)
	{
	  if (!dodelta(store, fp, &out, fsize))
	    return 0;
	  if ((fsize & 3) != 0)
	    {
	      i = 4 - (fsize & 3);
	      if (putc(4 + i, ofp) == EOF)
		return 0;
	      while (i--)
		{
		  if (getc(fp) != 0)
		    {
		      fprintf(stderr, "non-zero padding\n");
		      return 0;
		    }
		}
	    }
	}
    }
  if (putc(1, ofp) == EOF)
    return 0;
  if (fflush(ofp) != 0)
    return 0;
  return 1;
}

static unsigned long long
expandobscpio_next(FILE *fp)
{
  unsigned long long x = 0;
  int i;
  for (;;)
    {
      i = getc(fp);
      if (i == EOF)
	return (unsigned long long)(-1);
      if ((i & 128) == 0)
	return x << 7 | i;
      x = x << 7 | (i ^ 128);
    }
}

static unsigned long long
expandobscpio_next_mem(unsigned char **bp, unsigned int *lp)
{
  unsigned long long x = 0;
  unsigned char *b = *bp;
  unsigned int l = *lp;
  int i;
  for (;;)
    {
      if (l == 0)
	return (unsigned long long)(-1);
      i = *b++;
      l--;
      if ((i & 128) == 0)
        {
	  *bp = b;
	  *lp = l;
	  return x << 7 | i;
        }
      x = x << 7 | (i ^ 128);
    }
}

static int
expandobscpio_direct_mem(unsigned char **bp, unsigned int *lp, void *out, unsigned int outlen)
{
  if (*lp < outlen)
    return 0;
  if (outlen)
    memcpy(out, *bp, outlen);
  *bp += outlen;
  *lp -= outlen;
  return 1;
}

static int
expandcpiohead(FILE *fp, FILE *ofp, unsigned char *cpio, int hexcomp)
{
  int l = 0;
  int zero;
  for (;;)
    {
      int c = getc(fp);
      if (c == EOF)
	return 0;
      if (c == 0)
	break;
      if (c < 128)
	zero = 1;
      else if (c >= 252)
	{
	  zero = -1;
	  c -= 251;
	}
      else
	{
	  zero = 0;
	  c -= 128;
	}
      while (c-- > 0)
	{
	  int x = zero ? 0 : getc(fp);
	  if (x == EOF)
	    return 0;
	  if (l < 1024)
	    {
	      if (zero >= 0)
	        x ^= cpio[l];
	      cpio[l++] = x;
	    }
	  if (hexcomp && l <= 55)
	    {
	      int lettershift = (hexcomp == 1 ? 'A' : 'a') - ('0' + 10);
	      int x1 = (x >> 4) + '0';
	      int x2 = (x & 15) + '0';
	      if (x1 > '9')
		x1 += lettershift;
	      if (x2 > '9')
		x2 += lettershift;
	      if (putc(x1, ofp) == EOF || putc(x2, ofp) == EOF)
	        return 0;
	    }
	  else if (putc(x, ofp) == EOF)
	    return 0;
	}
      if (zero < 0)
	break;
    }
  if (hexcomp)
    magic_inode_increment(cpio);
  return 1;
}

static int
expandobscpio(FILE *fp, int fdstore, FILE *ofp)
{
  unsigned char magic[16];
  unsigned char metabuf[16384];
  unsigned char cpio[1024];
  unsigned long long o, l;
  struct stat st;
  unsigned long long oldoffset = 0;
  unsigned char *meta = 0;
  unsigned int metal = 0;
  unsigned long long oldoffset_meta = 0;

  if (!fp || !ofp || fdstore == -1)
    return 0;
  if (fstat(fileno(fp), &st))
    return 0;
  if (fread(magic, 16, 1, fp) != 1 || memcmp(magic, "OBScpio", 7) != 0)
    return 0;
  memset(cpio, 0, sizeof(cpio));
  for (;;)
    {
      if (meta && !metal)
	{
          meta = 0;
	  oldoffset = oldoffset_meta;
	}
      if (meta)
        l = expandobscpio_next_mem(&meta, &metal);
      else
        l = expandobscpio_next(fp);
      if (l == (unsigned long long)(-1))
	return 0;
#if 0
printf("NEXT %d\n", l);
#endif
      if (l < 16)
	{
	  /* first 16 reserved as instructions */
	  if (meta)
	    return 0;
	  if (l == 1)					/* EOF */
	    break;
	  if (l == 2 || l == 3 || l == 4)		/* CPIO */
	    {
	      if (!expandcpiohead(fp, ofp, cpio, l - 2))
		return 0;
	    }
	  else if (l == 5 || l == 6 || l == 7)		/* ZERO */
	    {
	      l -= 4;
	      while (l--)
	        if (putc(0, ofp) == EOF)
		  return 0;
	    }
	  else if (l == 15)				/* META */
	    {
	      l = expandobscpio_next(fp);
	      if (l == (unsigned long long)(-1))
		return 0;
	      if (l < 16 || l > sizeof(metabuf))
		return 0;
	      o = expandobscpio_next(fp);
	      if (o == (unsigned long long)(-1))
		return 0;
	      o = decodeoffset(oldoffset, o);
	      oldoffset_meta = o + l;
	      oldoffset = 0;
	      if (pread(fdstore, metabuf, (size_t)l, (off_t)o) != (size_t)l)
		return 0;
	      metal = (unsigned int)l;
	      meta = metabuf;
	    }
	  else
	    return 0;
	}
      else if (l < 256)
	{
	  /* direct bytes */
	  l -= 16;
	  if (l)
	    {
	      char buf[256];
	      if (meta)
		{
		  if (expandobscpio_direct_mem(&meta, &metal, buf, l) != 1)
		    return 0;
		}
	      else if (fread(buf, (int)l, 1, fp) != 1)
	        return 0;
	      if (fwrite(buf, (int)l, 1, ofp) != 1)
	        return 0;
	    }
	}
      else
	{
	  /* bytes from the store */
	  l -= 256;
	  if (meta)
	    o = expandobscpio_next_mem(&meta, &metal);
	  else
	    o = expandobscpio_next(fp);
	  if (o == (unsigned long long)(-1))
	    return 0;
	  o = decodeoffset(oldoffset, o);
	  oldoffset = o + l;
	  while (l)
	    {
	      char buf[8192];
	      size_t count = l > 8192 ? 8192 : l;
	      if (pread(fdstore, buf, count, (off_t)o) != count)
	        return 0;
	      if (fwrite(buf, count, 1, ofp) != 1)
	        return 0;
	      o += count;
	      l -= count;
	    }
	}
    }
  if (fflush(ofp) != 0)
    return 0;
  settimes(fileno(ofp), &st);
  return 1;
}

static void
printobscpioinstr(FILE *fp, int fdstore, int withmeta)
{
  unsigned char magic[16];
  unsigned long long oldoffset = 0, o, l;
  unsigned char metabuf[16384];
  unsigned char *meta = 0;
  unsigned int metal = 0;
  unsigned long long oldoffset_meta = 0;

  unsigned int stats_cpio = 0;
  unsigned long long stats_cpio_len = 0;
  unsigned int stats_direct = 0;
  unsigned long long stats_direct_len = 0;
  unsigned int stats_store = 0;
  unsigned long long stats_store_len = 0;
  unsigned int stats_zero = 0;
  unsigned long long stats_zero_len = 0;
  unsigned int stats_meta = 0;
  unsigned long long stats_meta_len = 0;
  unsigned int stats_meta_store = 0;
  unsigned long long stats_meta_store_len = 0;
  unsigned int stats_meta_direct = 0;
  unsigned long long stats_meta_direct_len = 0;

  if (fread(magic, 16, 1, fp) != 1 || memcmp(magic, "OBScpio", 7) != 0)
    return;
  for (;;)
    {
      if (meta && !metal)
	{
          meta = 0;
	  oldoffset = oldoffset_meta;
	}
      if (meta)
        l = expandobscpio_next_mem(&meta, &metal);
      else
        l = expandobscpio_next(fp);
      if (l == (unsigned long long)(-1))
        return;
      if (l < 16)
	{
	  if (meta)
	    return;
	  if (l == 1)
	    {
	      printf("end\n");
	      break;
	    }
	  if (l == 2 || l == 3 || l == 4)	/* CPIO HEADER */
	    {
	      printf("cpio%d", (int)l);
	      stats_cpio++;
	      for (;;)
		{
		  int c = getc(fp);
		  if (c == EOF)
		    return;
		  stats_cpio_len++;
		  if (c == 0)
		    {
		      printf(" (0)");
		      break;
		    }
		  if (c < 128)
		    printf(" [%d]", c);
		  else if (c >= 252)
		    {
		      printf(" (%d)", c - 251);
		      break;
		    }
		  else
		    {
		      c -= 128;
		      printf(" %d", c);
		      stats_cpio_len += c;
		      while (c--)
		        if (getc(fp) == EOF)
			  return;
		    }
		}
	      printf("\n");
	    }
	  else if (l == 5 || l == 6 || l == 7)	/* ZERO */
	    {
	      printf("zero %d\n", (int)l - 4);
	      stats_zero++;
	      stats_zero_len += (int)l - 4;
	    }
	  else if (l == 15)				/* META */
	    {
	      l = expandobscpio_next(fp);
	      if (l == (unsigned long long)(-1))
		return;
	      if (l < 16 || l > sizeof(metabuf))
		return;
	      o = expandobscpio_next(fp);
	      if (o == (unsigned long long)(-1))
		return;
	      o = decodeoffset(oldoffset, o);
	      oldoffset = o + l;
	      printf("meta %#llx %llu\n", o, l);
	      stats_meta++;
	      stats_meta_len += l;
	      if (withmeta)
		{
		  oldoffset_meta = o + l;
		  oldoffset = 0;
		  if (pread(fdstore, metabuf, (size_t)l, (off_t)o) != (size_t)l)
		    return;
		  metal = (unsigned int)l;
		  meta = metabuf;
		}
	    }
	  else
	    return;
	  continue;
	}
      if (meta)
	printf("    ");
      if (l < 256)
	{
	  l -= 16;
	  printf("direct %d\n", (int)l);
	  if (meta)
	    {
	      stats_meta_direct++;
	      stats_meta_direct_len += l;
	    }
	  else
	    {
	      stats_direct++;
	      stats_direct_len += l;
	    }
	  if (meta)
	    {
	      if (l > metal)
		return;
	      metal -= l;
	      meta += l;
	    }
	  else
	    {
	      while (l--)
		if (getc(fp) == EOF)
		  return;
	    }
	  continue;
	}
      l -= 256;
      if (meta)
        o = expandobscpio_next_mem(&meta, &metal);
      else
        o = expandobscpio_next(fp);
      if (o == (unsigned long long)(-1))
	return;
      o = decodeoffset(oldoffset, o);
      oldoffset = o + l;
      printf("store %#llx %llu\n", o, l);
      if (meta)
	{
	  stats_meta_store++;
	  stats_meta_store_len += l;
	}
      else
	{
	  stats_store++;
	  stats_store_len += l;
	}
    }
  printf("stats cpio %u len %llu\n", stats_cpio, stats_cpio_len);
  printf("stats direct %u len %llu\n", stats_direct, stats_direct_len);
  if (withmeta)
    printf("stats meta_direct %u len %llu\n", stats_meta_direct, stats_meta_direct_len);
  printf("stats store %u len %llu\n", stats_store, stats_store_len);
  if (withmeta)
    printf("stats meta_store %u len %llu\n", stats_meta_store, stats_meta_store_len);
  printf("stats zero %u len %llu\n", stats_zero, stats_zero_len);
  printf("stats meta %u len %llu\n", stats_meta, stats_meta_len);
  if (withmeta)
    printf("stats instr %u\n", stats_cpio + stats_direct + stats_store + stats_zero + stats_meta + 1 + stats_meta_direct + stats_meta_store);
  printf("stats file_instr %u\n", stats_cpio + stats_direct + stats_store + stats_zero + stats_meta + 1);
  printf("stats file_data %lld\n", stats_cpio_len + stats_direct_len);
  printf("stats file_size %lld\n", (unsigned long long)ftell(fp));
}

static int
unifymodules_cmp(const void *ap, const void *bp, void *dp)
{
  return *(Id *)ap - *(Id *)bp;
}

static int
missingmodules_cmp(const void *ap, const void *bp, void *dp)
{
  const Id *a = ap;
  const Id *b = bp;
  if (a[0] != b[0])
    return a[0] - b[0];
  if (!a[1] && b[1])
    return -1;
  if (!b[1] && a[1])
    return 1;
  return a[1] - b[1];
}

static int
is_dod_package(Solvable *s)
{
  const char *str = solvable_lookup_str(s, buildservice_id);
  return str && !strcmp(str, "dod") ? 1 : 0;
}

static Solvable *
find_corresponding_dod(Solvable *s)
{
  Repo *repo = s->repo;
  Id p2;
  Solvable *s2;

  if (!repo)
    return 0;
  FOR_REPO_SOLVABLES(repo, p2, s2)
    {
      if (s->name == s2->name && s->evr == s2->evr && s->arch == s2->arch && s != s2 && is_dod_package(s2))
	return s2;
    }
  return 0;
}

struct scc_data {
  Id *edata;
  Id *vedge;
  Queue *sccs;
  int *stack;
  int nstack;
  int *low;
  int idx;
};

static void
scc_collect(struct scc_data *scc, int node)
{
  int *low = scc->low;
  Id *e;
  queue_push(scc->sccs, node);
  low[node] = -1;
  for (e = scc->edata + scc->vedge[node]; *e; e++)
    if (*e != -1 && low[*e] > 0)
      scc_collect(scc, *e);
}

/* Tarjan's SCC algorithm */
static int
scc_visit(struct scc_data *scc, int node)
{
  int l, myidx, *low = scc->low, nontrivial = 0;
  Id *e;
  low[node] = myidx = scc->idx++;
  for (e = scc->edata + scc->vedge[node]; *e; e++)
    {
      if (*e == -1 || *e == node)
	continue;
      if (!(l = low[*e]))
	l = scc_visit(scc, *e);
      if (l > 0)
	nontrivial = 1;
      if (l > 0 && l < low[node])
	low[node] = l;
    }
  if (low[node] != myidx)
    return low[node];
  low[node] = -1;
  if (nontrivial)
    {
      scc_collect(scc, node);
      queue_push(scc->sccs, 0);
    }
  return -1;
}

static void
find_sccs(Queue *edata, Queue *vedge, Queue *sccs)
{
  struct scc_data scc;
  int i;
  scc.edata = edata->elements;
  scc.vedge = vedge->elements;
  scc.sccs = sccs;
  scc.low = solv_calloc(vedge->count, sizeof(int));
  scc.idx = 1;
  for (i = 1; i < vedge->count; i++)
    if (!scc.edata[vedge->elements[i]])
      scc.low[i] = -1;
  for (i = 1; i < vedge->count; i++)
    if (!scc.low[i])
      scc_visit(&scc, i);
  solv_free(scc.low);
}


MODULE = BSSolv		PACKAGE = BSSolv

void
depsort(HV *deps, SV *mapp, SV *cycp, ...)
    ALIAS:
	depsort2 = 1
    PPCODE:
	{
	    int i, j, k, cy, cycstart, nv;
	    int pkgstart = 3;
	    SV *sv, **svp;
	    SV *pkg2srcp = 0;
	    Id id, *e;
	    Id *mark;
	    char **names;
	    char **depnames;
	    Hashtable ht;
	    Hashval h, hh, hm;
	    HV *mhv = 0;
	    HV *pkg2srchv = 0;

	    Queue edata;
	    Queue vedge;
	    Queue todo;
	    Queue cycles;
	    Map edgeunifymap;
	    int didsccs = 0;

	    if (ix)
	      {
		/* called as depsort2 */
		if (items < 4)
		  XSRETURN_EMPTY; /* nothing to sort */
		pkgstart = 4;
		pkg2srcp = cycp;
		cycp = ST(3);
	      }
	    if (items == pkgstart)
	      XSRETURN_EMPTY; /* nothing to sort */
	    if (items == pkgstart + 1)
	      {
		/* only one item */
		char *s = SvPV_nolen(ST(pkgstart));
		EXTEND(SP, 1);
		sv = newSVpv(s, 0);
		PUSHs(sv_2mortal(sv));
	        XSRETURN(1); /* nothing to sort */
	      }

	    if (pkg2srcp && SvROK(pkg2srcp) && SvTYPE(SvRV(pkg2srcp)) == SVt_PVHV)
	      pkg2srchv = (HV *)SvRV(pkg2srcp);

	    if (mapp && SvROK(mapp) && SvTYPE(SvRV(mapp)) == SVt_PVHV)
	      mhv = (HV *)SvRV(mapp);

	    queue_init(&edata);
	    queue_init(&vedge);
	    queue_init(&todo);
	    queue_init(&cycles);

	    hm = mkmask(items);
	    ht = solv_calloc(hm + 1, sizeof(*ht));
	    names = depnames = solv_calloc(items, sizeof(char *));

	    /* create pkgname -> edge hash, store edge -> pkgname data */
	    nv = 1;
	    for (i = pkgstart; i < items; i++)
	      {
		char *s = SvPV_nolen(ST(i));
		h = strhash(s) & hm;
		hh = HASHCHAIN_START;
		while ((id = ht[h]) != 0)
		  {
		    if (!strcmp(names[id], s))
		      break;
		    h = HASHCHAIN_NEXT(h, hh, hm);
		  }
		if (id)
		  continue;	/* had that one before, ignore */
		id = nv++;
		ht[h] = id;
		names[id] = s;
	      }

	    if (pkg2srchv)
	      {
		/* redo the hash with src names instead of pkg names */
		depnames = solv_calloc(nv, sizeof(char *));
		memset(ht, 0, (hm + 1) * sizeof(*ht));
		for (i = 1; i < nv; i++)
		  {
		    char *s = names[i];
		    svp = hv_fetch(pkg2srchv, s, strlen(s), 0);
		    if (svp)
		      {
			char *ns = SvPV_nolen(*svp);
			if (ns)
			  s = ns;
		      }
		    depnames[i] = s;
		    h = strhash(s) & hm;
		    hh = HASHCHAIN_START;
		    while ((id = ht[h]) != 0)
		      h = HASHCHAIN_NEXT(h, hh, hm);
		    ht[h] = i;
		  }
	      }

	    /* we now know all vertices, create edges */
	    queue_push(&vedge, 0);
	    queue_push(&edata, 0);
	    map_init(&edgeunifymap, nv);
	    for (i = 1; i < nv; i++)
	      {
		int edgestart = edata.count;
		svp = hv_fetch(deps, names[i], strlen(names[i]), 0);
		sv = svp ? *svp : 0;
		queue_push(&vedge, edgestart);
		if (sv && SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVAV)
		  {
		    AV *av = (AV *)SvRV(sv);
		    for (j = 0; j <= av_len(av); j++)
		      {
			char *s;
			STRLEN slen;

			svp = av_fetch(av, j, 0);
			if (!svp)
			  continue;
			sv = *svp;
			s = SvPV(sv, slen);
			if (!s)
			  continue;
			if (mhv)
			  {
			    /* look up in dep map */
			    svp = hv_fetch(mhv, s, slen, 0);
			    if (svp)
			      {
				s = SvPV(*svp, slen);
				if (!s)
				  continue;
			      }
			  }
			/* look up in hash */
			h = strhash(s) & hm;
			hh = HASHCHAIN_START;
			while ((id = ht[h]) != 0)
			  {
			    if (!strcmp(depnames[id], s))
			      {
				if (id != i && !MAPTST(&edgeunifymap, id))
				  {
				    MAPSET(&edgeunifymap, id);
				    queue_push(&edata, id);
				  }
				if (names == depnames)
				  break;	/* no other entry with same name */
			      }
			    h = HASHCHAIN_NEXT(h, hh, hm);
			  }
		      }
		  }
		for (j = edgestart; j < edata.count; j++)
		  {
#ifdef MAPCLR_AT
		    MAPCLR_AT(&edgeunifymap, edata.elements[j]);
#else
		    MAPCLR(&edgeunifymap, edata.elements[j]);
#endif
		  }
		queue_push(&edata, 0);	/* terminate edge array */
	      }
	    /* free no longer needed stuff */
	    map_free(&edgeunifymap);
	    solv_free(ht);
	    if (depnames != names)
	      depnames = solv_free(depnames);

	    if (0)
	      {
		printf("vertexes: %d\n", vedge.count - 1);
		for (i = 1; i < vedge.count; i++)
		  {
		    printf("%d %s:", i, names[i]);
		    Id *e = edata.elements + vedge.elements[i];
		    for (; *e; e++)
		      printf(" %d", *e);
		    printf("\n");
		  }
	      }
		

	    /* now everything is set up, sort em! */
	    mark = solv_calloc(vedge.count, sizeof(Id));
	    for (i = vedge.count - 1; i; i--)
	      queue_push(&todo, i);
	    EXTEND(SP, vedge.count - 1);
	    while (todo.count)
	      {
		i = queue_pop(&todo);
		// printf("check %d\n", i);
		if (i < 0)
		  {
		    i = -i;
		    mark[i] = 2;
		    sv = newSVpv(names[i], 0);
		    PUSHs(sv_2mortal(sv));
		    continue;
		  }
		if (mark[i] == 2)
		  continue;
		if (mark[i] == 0)
		  {
		    int edgestovisit = 0;
		    Id *e = edata.elements + vedge.elements[i];
		    for (; *e; e++)
		      {
			if (*e == -1)
			  continue;	/* broken */
			if (mark[*e] == 2)
			  continue;
			if (!edgestovisit++)
			  queue_push(&todo, -i);
		        queue_push(&todo, *e);
		      }
		    if (!edgestovisit)
		      {
			mark[i] = 2;
			sv = newSVpv(names[i], 0);
			PUSHs(sv_2mortal(sv));
		      }
		    else
		      mark[i] = 1;
		    continue;
		  }
		/* oh no, we found a cycle, record and break it */
		if (depsortsccs && !didsccs && cycp)
		  {
		    /* use Tarjan's SCC algorithm */
		    find_sccs(&edata, &vedge, &cycles);
		    queue_push(&cycles, 0);
		    didsccs = cycles.count;
		  }
		cy = cycles.count;
		for (j = todo.count - 1; j >= 0; j--)
		  if (todo.elements[j] == -i)
		    break;
		cycstart = j;
		// printf("cycle:\n");
		for (j = cycstart; j < todo.count; j++)
		  if (todo.elements[j] < 0)
		    {
		      k = -todo.elements[j];
		      mark[k] = 0;
		      queue_push(&cycles, k);
		      // printf("  %d\n", k);
		    }
	        queue_push(&cycles, 0);
		todo.elements[cycstart] = i;
		/* break it */
		for (k = cy; cycles.elements[k]; k++)
		  ;
		if (!cycles.elements[k])
		  k = cy;
		j = cycles.elements[k + 1] ? cycles.elements[k + 1] : cycles.elements[cy];
		k = cycles.elements[k];
		/* breaking edge from k -> j */
		// printf("break %d -> %d\n", k, j);
		e = edata.elements + vedge.elements[k];
		for (; *e; e++)
		  if (*e == j)
		    break;
		if (!*e)
		  abort();
		*e = -1;
		todo.count = cycstart + 1;
	      }

	    if (didsccs && depsortsccs != 2)
	      queue_truncate(&cycles, didsccs - 1);

	    /* record cycles */
	    if (cycles.count && cycp && SvROK(cycp) && SvTYPE(SvRV(cycp)) == SVt_PVAV)
	      {
		AV *av = (AV *)SvRV(cycp);
		for (i = 0; i < cycles.count;)
		  {
		    AV *av2 = newAV();
		    for (; cycles.elements[i]; i++)
		      {
			SV *sv = newSVpv(names[cycles.elements[i]], 0);
			av_push(av2, sv);
		      }
		    av_push(av, newRV_noinc((SV*)av2));
		    i++;
		  }
	      }
	    queue_free(&cycles);

	    queue_free(&edata);
	    queue_free(&vedge);
	    queue_free(&todo);
	    solv_free(mark);
	    solv_free(names);
	}

int
setdepsortsccs(int flag)
    CODE:
	depsortsccs = flag;
	RETVAL = flag;
    OUTPUT:
	RETVAL

int
setgenmetaalgo(int algo)
    CODE:
	if (algo < 0)
	    algo = 1;
	if (algo > 1)
	    croak("BSSolv::setgenmetaalgo: unsupported algo %d\n", algo);
	genmetaalgo = algo;
	RETVAL = algo;
    OUTPUT:
	RETVAL


void
gen_meta(AV *subp, ...)
    PPCODE:
	{
	    Hashtable ht;
	    Hashval h, hh, hm;
	    char **subpacks;
	    struct metaline *lines, *lp;
	    int nlines;
	    int i, j, cycle, ns;
	    char *s, *s2, *lo;
	    Id id;
	    Queue cycles;
	    Id cycles_buf[64];

	    if (items == 1)
	      XSRETURN_EMPTY; /* nothing to generate */

	    queue_init_buffer(&cycles, cycles_buf, sizeof(cycles_buf)/sizeof(*cycles_buf));
	    hm = mkmask(av_len(subp) + 2);
	    ht = solv_calloc(hm + 1, sizeof(*ht));
	    subpacks = solv_calloc(av_len(subp) + 2, sizeof(char *));
	    for (j = 0; j <= av_len(subp); j++)
	      {
		SV **svp = av_fetch(subp, j, 0);
		if (!svp)
		  continue;
		s = SvPV_nolen(*svp);
		h = strhash(s) & hm;
		hh = HASHCHAIN_START;
		while ((id = ht[h]) != 0)
		  h = HASHCHAIN_NEXT(h, hh, hm);
		ht[h] = j + 1;
		subpacks[j + 1] = s;
	      }

	    lines = solv_calloc(items - 1, sizeof(*lines));
	    nlines = items - 1;
	    /* lines are of the form "md5sum  pkg/pkg..." */
	    for (i = 0, lp = lines; i < nlines; i++, lp++)
	      {
		s = SvPV_nolen(ST(i + 1));
		if (strlen(s) < 35 || s[32] != ' ' || s[33] != ' ')
		  croak("gen_meta: bad line %s\n", s);
		/* count '/' */
		lp->l = s;
		ns = 0;
		cycle = 0;
		lo = s + 34;
		for (s2 = lo; *s2; s2++)
		  if (*s2 == '/')
		    {
		      if (!cycle)	
			{
			  *s2 = 0;
			  h = strhash(lo) & hm;
			  hh = HASHCHAIN_START;
			  while ((id = ht[h]) != 0)
			    {
			      if (!strcmp(lo, subpacks[id]))
				break;
			      h = HASHCHAIN_NEXT(h, hh, hm);
			    }
			  *s2 = '/';
			  if (id)
			    cycle = 1 + ns;
			}
		      ns++;
		      lo = s2 + 1;
		    }
		if (!cycle)
		  {
		    h = strhash(lo) & hm;
		    hh = HASHCHAIN_START;
		    while ((id = ht[h]) != 0)
		      {
		        if (!strcmp(lo, subpacks[id]))
			  break;
		        h = HASHCHAIN_NEXT(h, hh, hm);
		      }
		    if (id)
		      cycle = 1 + ns;
		  }
		if (cycle)
		  {
		    lp->killed = 1;	/* killed because line includes a subpackage */
		    if (cycle > 1)	/* ignore self cycles */
		      queue_push(&cycles, i);
		  }
		lp->nslash = ns;
		lp->lastoff = lo - s;
	      }
	    solv_free(ht);
	    solv_free(subpacks);

	    /* if we found cycles, prune em */
	    if (cycles.count)
	      {
		char *cycledata = 0;
		int cycledatalen = 0;

		/* create hash of cycle packages */
		cycledata = solv_extend(cycledata, cycledatalen, 1, 1, 255);
		cycledata[cycledatalen++] = 0;
		hm = mkmask(cycles.count);
		ht = solv_calloc(hm + 1, sizeof(*ht));
		for (i = 0; i < cycles.count; i++)
		  {
		    char *se;
		    s = lines[cycles.elements[i]].l + 34;
		    se = strchr(s, '/');
		    if (se)
		      *se = 0;
		    h = strhash(s) & hm;
		    hh = HASHCHAIN_START;
		    while ((id = ht[h]) != 0)
		      {
		        if (!strcmp(s, cycledata + id))
			  break;
		        h = HASHCHAIN_NEXT(h, hh, hm);
		      }
		    if (!id)
		      {
			int l = strlen(s);
			cycledata = solv_extend(cycledata, cycledatalen, l + 1, 1, 255);
			ht[h] = cycledatalen;	/* point to name */
			strcpy(cycledata + cycledatalen, s);
			cycledatalen += l + 1;
		      }
		    if (se)
		      *se = '/';
		  }

		for (i = 0, lp = lines; i < nlines; i++, lp++)
		  {
		    if (!lp->nslash)
		      continue;
		    if (lp->killed && genmetaalgo == 0)
		      continue;
		    lo = strchr(lp->l + 34, '/') + 1;
		    for (s2 = lo; *s2; s2++)
		      if (*s2 == '/')
			{
			  *s2 = 0;
			  h = strhash(lo) & hm;
			  hh = HASHCHAIN_START;
			  while ((id = ht[h]) != 0)
			    {
			      if (!strcmp(lo, cycledata + id))
				break;
			      h = HASHCHAIN_NEXT(h, hh, hm);
			    }
			  *s2 = '/';
			  if (id)
			    {
			      lp->killed = 2;	/* killed because it containes a cycle package */
			      break;
			    }
			  lo = s2 + 1;
			}
		    if (lp->killed == 2)
		      continue;
		    h = strhash(lo) & hm;
		    hh = HASHCHAIN_START;
		    while ((id = ht[h]) != 0)
		      {
		        if (!strcmp(lo, cycledata + id))
			  break;
		        h = HASHCHAIN_NEXT(h, hh, hm);
		      }
		    if (id)
		      lp->killed = 2;	/* killed because it containes a cycle package */
		  }
		solv_free(ht);
		cycledata = solv_free(cycledata);
	      }
	    queue_free(&cycles);

	    /* cycles are pruned, now sort array */
	    if (nlines > 1)
	      qsort(lines, nlines, sizeof(*lines), metacmp);

	    hm = mkmask(nlines);
	    ht = solv_calloc(hm + 1, sizeof(*ht));
	    for (i = 0, lp = lines; i < nlines; i++, lp++)
	      {
		if (lp->killed)
		  {
		    if (genmetaalgo == 0 || lp->killed != 2)
		      continue;
		  }
		s = lp->l;
		h = strnhash(s, 10);
		h = strhash_cont(s + lp->lastoff, h) & hm;
	        hh = HASHCHAIN_START;
		while ((id = ht[h]) != 0)
		  {
		    struct metaline *lp2 = lines + (id - 1);
		    if (!strncmp(lp->l, lp2->l, 32) && !strcmp(lp->l + lp->lastoff, lp2->l + lp2->lastoff))
		      break;
		    h = HASHCHAIN_NEXT(h, hh, hm);
		  }
		if (id && genmetaalgo == 1 && lp->killed == 2)
		  {
		    /* also kill old line of same level */
		    struct metaline *lp2 = lines + (id - 1);
		    if (!lp2->killed && lp2->nslash == lp->nslash)
		      lp2->killed = 1;
		  }
		if (id)
		  lp->killed = 1;
		else
		  ht[h] = i + 1;
	      }
	    solv_free(ht);
	    j = 0;
	    for (i = 0, lp = lines; i < nlines; i++, lp++)
	      if (!lp->killed)
		j++;
	    EXTEND(SP, j);
	    for (i = 0, lp = lines; i < nlines; i++, lp++)
	      {
		SV *sv;
		if (lp->killed)
		  continue;
		sv = newSVpv(lp->l, 0);
		PUSHs(sv_2mortal(sv));
	      }
	    solv_free(lines);
	}

void
add_meta(AV *new_meta, SV *sv, const char *bin, const char *packid = 0)
    PPCODE:
	{
	    const char *p, *np;
	    char *buf;
	    size_t l, bufl, binl, packidl;
	    int first = 1;
	    if (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVAV) {
		AV *av = (AV *)SvRV(sv);
		SV **svp = av_fetch(av, 0, 0);
		sv = svp ? *svp : 0;
	    }
	    if (!sv)
		XSRETURN_EMPTY;
	    p = SvPV_nolen(sv);
	    binl = strlen(bin);
	    bufl = binl + 256;
	    buf = malloc(bufl);
	    if (!buf) {
		croak("out of mem\n");
		XSRETURN_EMPTY;
	    }
	    packidl = packid ? strlen(packid) : 0;
	    for (;;) {
		np = strchr(p, '\n');
		l = np ? np - p : strlen(p);
		if (l > 34) {
		    if (l + binl + 1 + 1 > bufl) {
			bufl = l + binl + 256;
			buf = realloc(buf, bufl);
			if (!buf) {
			    croak("out of mem\n");
			    XSRETURN_EMPTY;
			}
		    }
		    strncpy(buf, p, 34);
		    strcpy(buf + 34, bin);
		    buf[34 + binl] = '/';
		    strncpy(buf + 34 + binl + 1, p + 34, l - 34);
		    l += binl + 1;
		    buf[l] = 0;
		    if (first) {
			if (packidl && l > packidl + 1 && buf[l - packidl - 1] == '/' && !strcmp(buf + l - packidl, packid)) {
			    free(buf);
			    XSRETURN_EMPTY;
			}
			l = 34 + binl;
			buf[l] = 0;
			first = 0;
		    }
		    av_push(new_meta, newSVpvn(buf, l));
		}
		if (!np)
		    break;
		p = np + 1;
	    }
	    free(buf);
	}

SV *
thawcache(SV *sv)
    CODE:
	unsigned char *src;
	STRLEN srcl;
	if (!SvPOK(sv)) {
	    croak("thaw: argument is not a string\n");
	    XSRETURN_UNDEF;
	}
	src = (unsigned char *)SvPV(sv, srcl);
	if (srcl < 7 || src[0] != 'p' || src[1] != 's' || src[2] != 't' || src[3] != '0') {
	    croak("thaw: argument is not a perl storable\n");
	    XSRETURN_UNDEF;
	}
	if ((src[4] & 1) != 1) {
	    croak("thaw: argument is not a perl storable in network order\n");
	    XSRETURN_UNDEF;
	}
	if (src[4] < 5) {
	    croak("thaw: argument is a perl storable with a too old version\n");
	    XSRETURN_UNDEF;
	}
        src += 6;
        srcl -= 6;
	sv = retrieve(&src, &srcl, 0);
	if (sv == 0 || srcl) {
	    croak("thaw: corrupt storable\n");
	    XSRETURN_UNDEF;
	}
	RETVAL = newRV_noinc(sv);
    OUTPUT:
	RETVAL

int
isobscpio(const char *file)
    CODE:
	int fd;
	RETVAL = 0;
	if ((fd = open(file, O_RDONLY)) != -1) {
	    unsigned char magic[16];
	    if (read(fd, magic, 16) == 16 && !memcmp(magic, "OBScpio", 7))
		RETVAL = 1;
	    close(fd);
	}
    OUTPUT:
	RETVAL


void
obscpiostat(const char *file)
    PPCODE:
	{
	    int fd;
	    struct stat st;
	    if ((fd = open(file, O_RDONLY)) != -1) {
		if (!fstat(fd, &st)) {
		    unsigned char magic[16];
		    if (read(fd, magic, 16) == 16 && !memcmp(magic, "OBScpio", 7)) {
			st.st_size = getu48(magic + 10);
		    }
		    EXTEND(SP, 10);
		    PUSHs(&PL_sv_undef);
		    PUSHs(&PL_sv_undef);
		    PUSHs(sv_2mortal(newSVuv((UV)st.st_mode)));
		    PUSHs(sv_2mortal(newSVuv((UV)st.st_nlink)));
		    PUSHs(&PL_sv_undef);
		    PUSHs(&PL_sv_undef);
		    PUSHs(&PL_sv_undef);
#if IVSIZE > 4
		    PUSHs(sv_2mortal(newSVuv((UV)st.st_size)));
#else
		    PUSHs(sv_2mortal(newSVnv((double)st.st_size)));
#endif
		    PUSHs(sv_2mortal(newSVuv((UV)st.st_atime)));
		    PUSHs(sv_2mortal(newSVuv((UV)st.st_mtime)));
		    PUSHs(sv_2mortal(newSVuv((UV)st.st_ctime)));
		}
		close(fd);
	    }
	}

int
obscpioopen(const char *file, const char *store, SV *gvrv, const char *tmpdir = 0)
    CODE:
	int fd;
	GV *gv;
	if (!SvROK(gvrv) || SvTYPE(SvRV(gvrv)) != SVt_PVGV) {
	    croak("obscpioopen needs a GV reference\n");
	}
	if (tmpdir && strlen(tmpdir) > 200) {
	    croak("tmpdir too long\n");
	}
	gv = (GV *)SvRV(gvrv);
	RETVAL = 0;
	if ((fd = open(file, O_RDONLY)) != -1) {
	    unsigned char magic[16];
	    if (read(fd, magic, 16) == 16 && !memcmp(magic, "OBScpio", 7)) {
		char template[256];
		int nfd = -1;
		int sfd;
		if ((sfd = open(store, O_RDONLY)) != -1) {
		    if (tmpdir) {
			strcpy(template, tmpdir);
			strcat(template, "/obscpioopen-XXXXXX");
		    } else {
			strcpy(template, "/var/tmp/obscpioopen-XXXXXX");
		    }
		    nfd = mkstemp(template);
		    if (nfd != -1) {
			FILE *fp = 0, *nfp = 0;
		        unlink(template);
			lseek(fd, 0, SEEK_SET);
			if ((fp = fdopen(fd, "r")) == 0)
			    close(fd);
			if ((nfp = fdopen(nfd, "w+")) == 0)
			    close(nfd);
		        if (fp && nfp && expandobscpio(fp, sfd, nfp)) {
			    nfd = dup(nfd);
			    if (fclose(nfp)) {
				close(nfd);
			        nfd = -1;
			    }
			    nfp = 0;
			} else {
			    nfd = -1;
			}
			if (fp)
			    fclose(fp);
			if (nfp)
			    fclose(nfp);
			fd = -1;
		    }
		    close(sfd);
		}
		if (fd != -1)
		    close(fd);
		fd = nfd;
	    }
	    if (fd != -1) {
		IO * io = GvIOn(gv);
		PerlIO *fp;

		lseek(fd, 0, SEEK_SET);
		fp = PerlIO_fdopen(fd, "rb");
		if (fp) {
		    IoIFP(io) = fp;
		    RETVAL = 1;
		}
	    }
	}
	
    OUTPUT:
	RETVAL

int
expandobscpio(const char *file, const char *store, const char *tmpfile)
    CODE:
	{
	    int fd, nfd, sfd;
	    RETVAL = 0;

	    unlink(tmpfile);
	    if ((fd = open(file, O_RDONLY)) != -1) {
		unsigned char magic[16];
		if (!(read(fd, magic, 16) == 16 && !memcmp(magic, "OBScpio", 7))) {
		    close(fd);
		    fd = -1;
		    if (link(file, tmpfile) == 0 && (fd = open(tmpfile, O_RDONLY)) != -1) {
			if (read(fd, magic, 16) == 16 && !memcmp(magic, "OBScpio", 7)) {
			    unlink(tmpfile);
			} else {
			    close(fd);
			    fd = -1;
			    RETVAL = 1;
			}
		    }
		}
		if (fd != -1) {
		    if ((sfd = open(store, O_RDONLY)) != -1) {
			FILE *fp;
			lseek(fd, 0, SEEK_SET);
			if ((fp = fdopen(fd, "r")) == 0)
			    close(fd);
			if (fp && (nfd = open(tmpfile, O_WRONLY|O_CREAT|O_TRUNC|O_EXCL, 0666)) != -1) {
			    FILE *nfp;
			    if ((nfp = fdopen(nfd, "w")) == 0)
				close(nfd);
			    if (nfp && expandobscpio(fp, sfd, nfp)) {
				if (!fclose(nfp))
				    RETVAL = 1;
				else
				    unlink(tmpfile);
				nfp = 0;
			    } else
				unlink(tmpfile);
			    if (nfp)
				fclose(nfp);
			}
		        if (fp)
			  fclose(fp);
			close(sfd);
		    } else {
		      close(fd);
		    }
		}
	    }
	}
    OUTPUT:
	RETVAL


int
makeobscpio(const char *in, const char *store, const char *out)
    CODE:
	{
	    FILE *fpin, *fpout;
	    struct stat st;
	    int fdstore;
	    RETVAL = 0;
	    if ((fpin = fopen(in, "r")) == 0) {	
		perror(in);
	    } else if (fstat(fileno(fpin), &st) != 0) {
		perror(in);
	        fclose(fpin);
	    } else if ((fpout = fopen(out, "w")) == 0) {
	        perror(out);
	        fclose(fpin);
	    } else if ((fdstore = open(store, O_RDWR|O_CREAT, 0666)) == -1) {
	        perror(store);
	        fclose(fpin);
	        fclose(fpout);
	    } else {
		int gotlock = 0;
	        while (!gotlock) {
		    if (flock(fdstore, LOCK_EX) == 0)
			gotlock = 1;
		    else if (errno != EINTR)
			break;
		}
		if (gotlock) {
		    struct deltastore store;
		    if (readdeltastore(&store, fdstore, 0, (unsigned long long)st.st_size)) {
			int r = makedelta(&store, fpin, fpout, (unsigned long long)st.st_size);
#if 0
  printf("after makedelta: have %d entries, hash size %d\n", store.hf, store.hm + 1);
#endif
			if (fsync(store.fd))
			  r = 0;
			freedeltastore(&store);
			if (r) {
			    settimes(fileno(fpout), &st);
			    RETVAL = 1;
			}
		    }
		}
		close(fdstore);
	        fclose(fpin);
	        fclose(fpout);
	    }
	}
    OUTPUT:
	RETVAL

void
obscpiostorestats(const char *store)
    CODE:
	{
	    int fdstore;

	    if ((fdstore = open(store, O_RDONLY)) == -1)
	        perror(store);
	    else {
		int gotlock = 0;
		while (!gotlock) {
		    if (flock(fdstore, LOCK_EX) == 0)
			gotlock = 1;
		    else if (errno != EINTR)
			break;
		}
		if (gotlock) {
		    struct deltastore store;
		    if (readdeltastore(&store, fdstore, 1, (unsigned long long)0)) {
			printdeltastorestats(&store);
			fsync(store.fd);
			freedeltastore(&store);
		    }
		}
		close(fdstore);
	    }
	}

void
obscpioinstr(const char *file, const char *store = 0)
    CODE:
	{
	    FILE *fp;
	    int fdstore = -1;
	    if ((fp = fopen(file, "r")) == 0)
		perror(file);
	    else {
		if (store) {
		    fdstore = open(store, O_RDONLY);
		    if (fdstore == -1)
			perror(store);
		}
		printobscpioinstr(fp, fdstore, fdstore == -1 ? 0 : 1);
		fclose(fp);
		if (fdstore != -1)
		    close(fdstore);
	    }
	}


MODULE = BSSolv		PACKAGE = BSSolv::pool		PREFIX = pool

PROTOTYPES: ENABLE

BSSolv::pool
new(char *packname = "BSSolv::pool")
    CODE:
	{
	    Pool *pool = pool_create();
	    set_disttype(pool, DISTTYPE_RPM);
	    buildservice_id = pool_str2id(pool, "buildservice:id", 1);
	    buildservice_repocookie= pool_str2id(pool, "buildservice:repocookie", 1);
	    buildservice_external = pool_str2id(pool, "buildservice:external", 1);
	    buildservice_dodurl = pool_str2id(pool, "buildservice:dodurl", 1);
	    expander_directdepsend = pool_str2id(pool, "-directdepsend--", 1);
	    buildservice_dodcookie = pool_str2id(pool, "buildservice:dodcookie", 1);
	    buildservice_dodresources = pool_str2id(pool, "buildservice:dodresources", 1);
	    buildservice_annotation = pool_str2id(pool, "buildservice:annotation", 1);
	    buildservice_modules = pool_str2id(pool, "buildservice:modules", 1);
	    pool_freeidhashes(pool);
	    RETVAL = pool;
	}
    OUTPUT:
	RETVAL

void
settype(BSSolv::pool pool, char *type)
    CODE:
	if (!strcmp(type, "rpm"))
	  set_disttype(pool, DISTTYPE_RPM);
#ifdef DISTTYPE_DEB
	else if (!strcmp(type, "deb"))
	  set_disttype(pool, DISTTYPE_DEB);
#endif
#ifdef DISTTYPE_ARCH
	else if (!strcmp(type, "arch"))
	  set_disttype(pool, DISTTYPE_ARCH);
#endif
	else
	  croak("settype: unknown type '%s'\n", type);


BSSolv::repo
repofromfile(BSSolv::pool pool, char *name, char *filename)
    CODE:
	FILE *fp;
	fp = fopen(filename, "r");
	if (!fp) {
	    croak("%s: %s\n", filename, Strerror(errno));
	    XSRETURN_UNDEF;
	}
	RETVAL = repo_create(pool, name);
	repo_add_solv(RETVAL, fp, 0);
	fclose(fp);
    OUTPUT:
	RETVAL

BSSolv::repo
repofromstr(BSSolv::pool pool, char *name, SV *sv)
    CODE:
	FILE *fp;
	STRLEN len;
	char *buf;
	buf = SvPV(sv, len);
	if (!buf)
	    croak("repofromstr: undef string\n");
	fp = fmemopen(buf, len, "r");
	if (!fp) {
	    croak("fmemopen failed\n");
	    XSRETURN_UNDEF;
	}
	RETVAL = repo_create(pool, name);
	repo_add_solv(RETVAL, fp, 0);
	fclose(fp);
    OUTPUT:
	RETVAL

BSSolv::repo
repofrombins(BSSolv::pool pool, char *name, char *dir, ...)
    CODE:
	{
	    int i;
	    Repo *repo;
	    Repodata *data;
	    repo = repo_create(pool, name);
	    data = repo_add_repodata(repo, 0);
	    for (i = 3; i + 1 < items; i += 2)
	      {
		STRLEN sl;
		char *s = SvPV(ST(i), sl);
		char *sid = SvPV_nolen(ST(i + 1));
		if (sl < 4)
		  continue;
		if (strcmp(s + sl - 4, ".rpm")
                    && strcmp(s + sl - 4, ".deb")
                    && (sl < 10 || strcmp(s + sl - 10, ".obsbinlnk"))
#ifdef ARCH_ADD_WITH_PKGID
                    && (sl < 11 || strcmp(s + sl - 11, ".pkg.tar.gz"))
                    && (sl < 11 || strcmp(s + sl - 11, ".pkg.tar.xz"))
                    && (sl < 12 || strcmp(s + sl - 12, ".pkg.tar.zst"))
#endif
		   )
		  continue;
		if (sl >= 10 && !strcmp(s + sl - 10, ".patch.rpm"))
		  continue;
		if (sl >= 10 && !strcmp(s + sl - 10, ".nosrc.rpm"))
		  continue;
		if (sl >= 8 && !strcmp(s + sl - 8, ".src.rpm"))
		  continue;
		repodata_addbin(data, dir, s, (int)sl, sid);
	      }
	    repo_set_str(repo, SOLVID_META, buildservice_repocookie, REPOCOOKIE);
	    repo_internalize(repo);
	    RETVAL = repo;
	}
    OUTPUT:
	RETVAL

BSSolv::repo
repofromdata(BSSolv::pool pool, char *name, SV *rv)
    CODE:
	{
	    Repo *repo;
	    Repodata *data;
	    if (!SvROK(rv) || (SvTYPE(SvRV(rv)) != SVt_PVHV && SvTYPE(SvRV(rv)) != SVt_PVAV))
		croak("BSSolv::pool::repofromdata: rv is not a HASH or ARRAY reference");
	    repo = repo_create(pool, name);
	    data = repo_add_repodata(repo, 0);
	    data2solvables(repo, data, SvRV(rv), 0);
	    if (name && !strcmp(name, "/external/"))
	      repodata_set_void(data, SOLVID_META, buildservice_external);
	    repo_internalize(repo);
	    RETVAL = repo;
	}
    OUTPUT:
	RETVAL

void
createwhatprovides(BSSolv::pool pool, int unorderedrepos = 0)
    CODE:
	if (pool->considered)
	  {
	    map_free(pool->considered);
	    solv_free(pool->considered);
	  }
	pool->considered = solv_calloc(sizeof(Map), 1);
	create_considered(pool, 0, pool->considered, unorderedrepos);
	pool_createwhatprovides(pool);

void
setdebuglevel(BSSolv::pool pool, int level)
    CODE:
	pool_setdebuglevel(pool, level);

void
whatprovides(BSSolv::pool pool, char *str)
    PPCODE:
	{
	    Id p, pp, id;
	    id = testcase_str2dep(pool, str);
	    if (id)
	      FOR_PROVIDES(p, pp, id)
		XPUSHs(sv_2mortal(newSViv((IV)p)));
	}

void
whatrequires(BSSolv::pool pool, char *str)
    PPCODE:
	{
	    Id p, id;
	    Id *pp;
	    Solvable *s;
	    id = testcase_str2dep(pool, str);
	    if (id)
	      {
		for (p = 2; p < pool->nsolvables; p++)
		  {
		    if (!MAPTST(pool->considered, p))
		      continue;
		    s = pool->solvables + p;
		    if (!s->requires)
		      continue;
		    for (pp = s->repo->idarraydata + s->requires; *pp; pp++)
		      if (pool_match_dep(pool, id, *pp))
			break;
		    if (*pp)
		      XPUSHs(sv_2mortal(newSViv((IV)p)));
		  }
	      }
	}

void
consideredpackages(BSSolv::pool pool)
    PPCODE:
	{
	    int p, nsolv = 0;
	    for (p = 2; p < pool->nsolvables; p++)
	      if (MAPTST(pool->considered, p))
		nsolv++;
	    EXTEND(SP, nsolv);
	    for (p = 2; p < pool->nsolvables; p++)
	      if (MAPTST(pool->considered, p))
		PUSHs(sv_2mortal(newSViv((IV)p)));
	}
	
void
allpackages(BSSolv::pool pool)
    PPCODE:
	{
	    int p, nsolv = 0;
	    for (p = 2; p < pool->nsolvables; p++)
	      if (pool->solvables[p].repo)
		nsolv++;
	    EXTEND(SP, nsolv);
	    for (p = 2; p < pool->nsolvables; p++)
	      if (pool->solvables[p].repo)
		PUSHs(sv_2mortal(newSViv((IV)p)));
	}

const char *
pkg2name(BSSolv::pool pool, int p)
    CODE:
	RETVAL = pool_id2str(pool, pool->solvables[p].name);
    OUTPUT:
	RETVAL

const char *
pkg2evr(BSSolv::pool pool, int p)
    CODE:
	RETVAL = pool_id2str(pool, pool->solvables[p].evr);
    OUTPUT:
	RETVAL

const char *
pkg2arch(BSSolv::pool pool, int p)
    CODE:
	RETVAL = pool_id2str(pool, pool->solvables[p].arch);
    OUTPUT:
	RETVAL

const char *
pkg2srcname(BSSolv::pool pool, int p)
    CODE:
	if (solvable_lookup_void(pool->solvables + p, SOLVABLE_SOURCENAME))
	  RETVAL = pool_id2str(pool, pool->solvables[p].name);
	else
	  RETVAL = solvable_lookup_str(pool->solvables + p, SOLVABLE_SOURCENAME);
    OUTPUT:
	RETVAL

const char *
pkg2pkgid(BSSolv::pool pool, int p)
    CODE:
	{
	    Id type;
	    const char *s = solvable_lookup_checksum(pool->solvables + p, SOLVABLE_PKGID, &type);
	    RETVAL = s;
	}
    OUTPUT:
	RETVAL

const char *
pkg2bsid(BSSolv::pool pool, int p)
    CODE:
	RETVAL = solvable_lookup_str(pool->solvables + p, buildservice_id);
    OUTPUT:
	RETVAL

const char *
pkg2reponame(BSSolv::pool pool, int p)
    CODE:
	{
	    Repo *repo = pool->solvables[p].repo;
	    RETVAL = repo ? repo->name : 0;
	}
    OUTPUT:
	RETVAL

const char *
pkg2path(BSSolv::pool pool, int p)
    CODE:
	{
	    unsigned int medianr;
	    RETVAL = solvable_get_location(pool->solvables + p, &medianr);
	}
    OUTPUT:
	RETVAL
	
const char *
pkg2fullpath(BSSolv::pool pool, int p, char *myarch)
    CODE:
	{
	    unsigned int medianr;
	    const char *s = solvable_get_location(pool->solvables + p, &medianr);
	    Repo *repo = pool->solvables[p].repo;
	    s = pool_tmpjoin(pool, myarch, "/:full/", s);
	    RETVAL = pool_tmpjoin(pool, repo->name, "/", s);
	}
    OUTPUT:
	RETVAL

int
pkg2sizek(BSSolv::pool pool, int p)
    CODE:
#ifdef SOLV_KV_NUM64
	RETVAL = solvable_lookup_sizek(pool->solvables + p, SOLVABLE_DOWNLOADSIZE, 0);
#else
	RETVAL = solvable_lookup_num(pool->solvables + p, SOLVABLE_DOWNLOADSIZE, 0);
#endif
    OUTPUT:
	RETVAL

const char *
pkg2checksum(BSSolv::pool pool, int p)
    CODE:
	{
	    Id type;
	    const char *s = solvable_lookup_checksum(pool->solvables + p, SOLVABLE_CHECKSUM, &type);
	    if (s)
		s = pool_tmpjoin(pool, solv_chksum_type2str(type), ":", s);
	    RETVAL = s;
	}
    OUTPUT:
	RETVAL

int
pkg2inmodule(BSSolv::pool pool, int p)
    CODE:
	RETVAL = solvable_lookup_type(pool->solvables + p, buildservice_modules) != 0;
    OUTPUT:
	RETVAL

void
pkg2modules(BSSolv::pool pool, int p)
    PPCODE:
	{
	  Solvable *s = pool->solvables + p;
	  Queue modules;
	  int i;
	  queue_init(&modules);
	  solvable_lookup_idarray(s, buildservice_modules, &modules);
	  if (!modules.count && !is_dod_package(s))
	    {
	      Solvable *s2 = find_corresponding_dod(s);
	      if (s2)
		solvable_lookup_idarray(s2, buildservice_modules, &modules);
	    }
	  for (i = 0; i < modules.count; i++)
	    XPUSHs(sv_2mortal(newSVpv(pool_id2str(pool, modules.elements[i]), 0)));
	  queue_free(&modules);
	}

int
verifypkgchecksum(BSSolv::pool pool, int p, char *path)
    CODE:
	{
	    Id type;
	    const unsigned char *cin, *cout;
	    FILE *fp;
	    void *cs;
	    int cslen;
	    char buf[4096];
	    size_t len;
	    int res = 0;

	    if ((cin = solvable_lookup_bin_checksum(pool->solvables + p, SOLVABLE_CHECKSUM, &type)) != 0) {
		if ((fp = fopen(path, "r")) != 0) {
		    if ((cs = solv_chksum_create(type)) != 0) {
			while ((len = fread(buf, 1, sizeof(buf), fp)) > 0)
			    solv_chksum_add(cs, buf, len);
			if ((cout = solv_chksum_get(cs, &cslen)) != 0 && cslen && !memcmp(cin, cout, cslen))
			    res = 1;
			solv_chksum_free(cs, 0);
		    }
		    fclose(fp);
		}
	    }
	    RETVAL = res;
	}
    OUTPUT:
	RETVAL

HV *
pkg2data(BSSolv::pool pool, int p)
    CODE:
	{
	    Solvable *s = pool->solvables + p;
	    Id id;
	    const char *ss, *se;
	    unsigned int medianr;

	    if (!s->repo)
		XSRETURN_EMPTY;
	    RETVAL = newHV();
	    sv_2mortal((SV*)RETVAL);
	    (void)hv_store(RETVAL, "name", 4, newSVpv(pool_id2str(pool, s->name), 0), 0);
	    ss = pool_id2str(pool, s->evr);
	    se = ss;
	    while (*se >= '0' && *se <= '9')
	      se++;
	    if (se != ss && *se == ':' && se[1])
	      {
		(void)hv_store(RETVAL, "epoch", 5, newSVpvn(ss, se - ss), 0);
		ss = se + 1;
	      }
	    se = strrchr(ss, '-');
	    if (se)
	      {
	        (void)hv_store(RETVAL, "version", 7, newSVpvn(ss, se - ss), 0);
	        (void)hv_store(RETVAL, "release", 7, newSVpv(se + 1, 0), 0);
	      }
	    else
	      (void)hv_store(RETVAL, "version", 7, newSVpv(ss, 0), 0);
	    (void)hv_store(RETVAL, "arch", 4, newSVpv(pool_id2str(pool, s->arch), 0), 0);
	    exportdeps(RETVAL, "provides", 8, s->repo, s->provides, SOLVABLE_PROVIDES);
	    exportdeps(RETVAL, "obsoletes", 9, s->repo, s->obsoletes, SOLVABLE_OBSOLETES);
	    exportdeps(RETVAL, "conflicts", 9, s->repo, s->conflicts, SOLVABLE_CONFLICTS);
	    exportdeps(RETVAL, "requires", 8, s->repo, s->requires, SOLVABLE_REQUIRES);
	    exportdeps(RETVAL, "recommends", 10, s->repo, s->recommends, SOLVABLE_RECOMMENDS);
	    exportdeps(RETVAL, "suggests", 8, s->repo, s->suggests, SOLVABLE_SUGGESTS);
	    exportdeps(RETVAL, "supplements", 11, s->repo, s->supplements, SOLVABLE_SUPPLEMENTS);
	    exportdeps(RETVAL, "enhances", 8, s->repo, s->enhances, SOLVABLE_ENHANCES);
	    if (solvable_lookup_void(s, SOLVABLE_SOURCENAME))
	      ss = pool_id2str(pool, s->name);
	    else
	      ss = solvable_lookup_str(s, SOLVABLE_SOURCENAME);
	    if (ss)
	      (void)hv_store(RETVAL, "source", 6, newSVpv(ss, 0), 0);
	    ss = solvable_get_location(s, &medianr);
	    if (ss)
	      (void)hv_store(RETVAL, "path", 4, newSVpv(ss, 0), 0);
	    ss = solvable_lookup_checksum(s, SOLVABLE_PKGID, &id);
	    if (ss && id == REPOKEY_TYPE_MD5)
	      (void)hv_store(RETVAL, "hdrmd5", 6, newSVpv(ss, 0), 0);
	    ss = solvable_lookup_str(s, buildservice_id);
	    if (ss)
	      (void)hv_store(RETVAL, "id", 2, newSVpv(ss, 0), 0);
	    ss = solvable_lookup_str(s, buildservice_annotation);
	    if (ss)
	      (void)hv_store(RETVAL, "annotation", 10, newSVpv(ss, 0), 0);
	    if (solvable_lookup_type(s, buildservice_modules))
	      {
		Queue modules;
		int i;
		queue_init(&modules);
		solvable_lookup_idarray(s, buildservice_modules, &modules);
		if (modules.count)
		  {
        	    AV *av = newAV();
		    for (i = 0; i < modules.count; i++)
		      av_push(av, newSVpv(pool_id2str(pool, modules.elements[i]), 0));
		    (void)hv_store(RETVAL, "modules", 7, newRV_noinc((SV*)av), 0);
		  }
	      }
	}
    OUTPUT:
	RETVAL

const char *
pkg2annotation(BSSolv::pool pool, int p)
    CODE:
	RETVAL = solvable_lookup_str(pool->solvables + p, buildservice_annotation);
    OUTPUT:
	RETVAL

void
repos(BSSolv::pool pool)
    PPCODE:
	{
	    int ridx;
	    Repo *repo;

	    EXTEND(SP, pool->nrepos);
	    FOR_REPOS(ridx, repo)
	      {
		SV *sv = sv_newmortal();
		sv_setref_pv(sv, "BSSolv::repo", (void *)repo);
		PUSHs(sv);
	      }
	}

void
preparehashes(BSSolv::pool pool, char *prp, SV *gctxprpnotreadysv = 0)
    PPCODE:
	{
	    HV *gctxprpnotready = 0;
	    int ridx;
	    Repo *repo;
	    /* generated: */
	    HV *depislocal = newHV();
	    HV *dep2pkg = newHV();
	    HV *dep2src = newHV();
	    HV *notready = newHV();
	    HV *subpacks = newHV();
	    const char *srcstr;
	    const char *str;
	    Queue subq;
	    Id lastsrc, srcname, srctype;
	    int i, j;
	    Id p;
	    Solvable *s;
	    SV *sv, **svp;

	    if (gctxprpnotreadysv && SvROK(gctxprpnotreadysv) && SvTYPE(SvRV(gctxprpnotreadysv)) == SVt_PVHV)
	      gctxprpnotready = (HV *)SvRV(gctxprpnotreadysv);
	    queue_init(&subq);
	    FOR_REPOS(ridx, repo)
	      {
		HV *prpnotready = 0;
		int islocal = repo->name && !strcmp(repo->name, prp);
		svp = 0;
		if (repo->name && !islocal && gctxprpnotready)
		  svp = hv_fetch(gctxprpnotready, repo->name, strlen(repo->name), 0);
		if (svp && *svp && SvROK(*svp) && SvTYPE(SvRV(*svp)) == SVt_PVHV)
		  prpnotready = (HV *)SvRV(*svp);
		FOR_REPO_SOLVABLES(repo, p, s)
		  {
		    if (!MAPTST(pool->considered, p))
		      continue;
		    srctype = solvable_lookup_type(pool->solvables + p, SOLVABLE_SOURCENAME);
		    if (srctype == REPOKEY_TYPE_VOID)
		      srcname = s->name;
		    else if (srctype == REPOKEY_TYPE_ID)
		      srcname = solvable_lookup_id(pool->solvables + p, SOLVABLE_SOURCENAME);
		    else
		      {
			srcstr = solvable_lookup_str(pool->solvables + p, SOLVABLE_SOURCENAME);
			srcname = srcstr ? pool_str2id(pool, srcstr, 1) : 0;
		      }
		    if (!srcname || srcname == 1)
		      srcname = s->name;
		    queue_push2(&subq, s->name, srcname);

		    str = pool_id2str(pool, s->name);
		    (void)hv_store(dep2pkg, str, strlen(str), newSViv((IV)p), 0);
		    if (islocal)
		      (void)hv_store(depislocal, str, strlen(str), newSViv((IV)1), 0);
		    srcstr = pool_id2str(pool, srcname);
		    (void)hv_store(dep2src, str, strlen(str), newSVpv(srcstr, 0), 0);
		    if (!islocal && prpnotready)
		      {
			svp = hv_fetch(prpnotready, srcstr, strlen(srcstr), 0);
			if (svp && *svp && SvTRUE(*svp))
			  (void)hv_store(notready, srcstr, strlen((char *)srcstr), newSViv((IV)2), 0);
		      }
		  }
	      }
	    solv_sort(subq.elements, subq.count / 2, sizeof(Id) * 2, subpack_sort_cmp, pool);
	    queue_push2(&subq, 0, 0);
	    lastsrc = 0;
	    for (i = j = 0; i < subq.count; i += 2)
	      {
		if (subq.elements[i + 1] != lastsrc)
		  {
		    if (j < i)
		      {
			AV *subs = newAV();
			for (; j < i; j += 2)
			  {
			    str = pool_id2str(pool,  subq.elements[j]);
			    av_push(subs, newSVpv(str, 0));
			  }
			str = pool_id2str(pool, lastsrc);
		        (void)hv_store(subpacks, str, strlen(str), newRV_noinc((SV *)subs), 0);
		      }
		    lastsrc = subq.elements[i + 1];
		  }
	      }
	    queue_free(&subq);
	    EXTEND(SP, 5);
	    sv = newRV_noinc((SV *)dep2pkg);
	    PUSHs(sv_2mortal(sv));
	    sv = newRV_noinc((SV *)dep2src);
	    PUSHs(sv_2mortal(sv));
	    sv = newRV_noinc((SV *)depislocal);
	    PUSHs(sv_2mortal(sv));
	    sv = newRV_noinc((SV *)notready);
	    PUSHs(sv_2mortal(sv));
	    sv = newRV_noinc((SV *)subpacks);
	    PUSHs(sv_2mortal(sv));
	}

void
setmodules(BSSolv::pool pool, AV *modulesav)
    CODE:
	{
	  SSize_t i, n = av_len(modulesav);
	  pool->appdata = solv_free(pool->appdata);
	  if (n >= 0 && n < 1000000)
	    {
	      Id *modules = pool->appdata = solv_calloc(n + 2, sizeof(Id));
	      for (i = 0; i <= n; i++)
		modules[i] = pool_str2id(pool, avlookupstr(modulesav, i), 1);
	      modules[i] = 0;
	    }
	}

void
getmodules(BSSolv::pool pool)
    PPCODE:
	if (pool->appdata)
	  {
	    Id *modules = pool->appdata;
	    int i;
	    for (i = 0; modules[i]; i++)
	      XPUSHs(sv_2mortal(newSVpv(pool_id2str(pool, modules[i]), 0)));
	  }

void
DESTROY(BSSolv::pool pool)
    CODE:
        if (pool->considered)
	  {
	    map_free(pool->considered);
	    pool->considered = solv_free(pool->considered);
	  }
	pool->appdata = solv_free(pool->appdata);
	pool_free(pool);




MODULE = BSSolv		PACKAGE = BSSolv::repo		PREFIX = repo

void
freerepo(BSSolv::repo repo)
    CODE:
	{
	  repo_free(repo, 1);
	}

void
allpackages(BSSolv::repo repo)
    PPCODE:
	{
	    Id p;
	    Solvable *s;
	    EXTEND(SP, repo->nsolvables);
	    FOR_REPO_SOLVABLES(repo, p, s)
	      PUSHs(sv_2mortal(newSViv(p)));
	}

void
pkgnames(BSSolv::repo repo)
    PPCODE:
	{
	    Pool *pool = repo->pool;
	    Id p;
	    Solvable *s;
	    Map c;

	    create_considered(pool, repo, &c, 0);
	    EXTEND(SP, 2 * repo->nsolvables);
	    FOR_REPO_SOLVABLES(repo, p, s)
	      {
		if (!MAPTST(&c, p))
		  continue;
		PUSHs(sv_2mortal(newSVpv(pool_id2str(pool, s->name), 0)));
		PUSHs(sv_2mortal(newSViv(p)));
	      }
	    map_free(&c);
	}

void
pkgpaths(BSSolv::repo repo)
    PPCODE:
	{
	    Pool *pool = repo->pool;
	    Id p;
	    Solvable *s;
	    Map c;
	    const char *str;
	    unsigned int medianr;
	
	    create_considered(pool, repo, &c, 0);
	    EXTEND(SP, 2 * repo->nsolvables);
	    FOR_REPO_SOLVABLES(repo, p, s)
	      {
		if (!MAPTST(&c, p))
		  continue;
		/* ignore dod packages */
		str = solvable_lookup_str(s, buildservice_id);
		if (str && !strcmp(str, "dod"))
		  continue;
		str = solvable_get_location(pool->solvables + p, &medianr);
		if (!str)
		  continue;
		PUSHs(sv_2mortal(newSVpv(str, 0)));
		PUSHs(sv_2mortal(newSViv(p)));
	      }
	    map_free(&c);
	}

void
tofile(BSSolv::repo repo, char *filename)
    CODE:
	{
	    FILE *fp;
	    fp = fopen(filename, "w");
	    if (fp == 0)
	      croak("%s: %s\n", filename, Strerror(errno));
	    repo_write_filtered(repo, fp, myrepowritefilter, 0, 0);
	    if (fclose(fp))
	      croak("fclose: %s\n",  Strerror(errno));
	}

void
tofile_fd(BSSolv::repo repo, int fd)
    CODE:
	{
	    FILE *fp;
	    int fd2;
	    fd2 = dup(fd);
	    if (fd2 == -1)
	      croak("dup: %s\n", Strerror(errno));
	    fp = fdopen(fd2, "w");
	    if (fp == 0)
	      {
		int e = errno;
		close(fd2);
		croak("fdopen: %s\n", Strerror(e));
	      }
	    repo_write_filtered(repo, fp, myrepowritefilter, 0, 0);
	    if (fclose(fp))
	      {
		int e = errno;
		close(fd2);
		croak("fclose: %s\n",  Strerror(e));
	      }
	}

SV *
tostr(BSSolv::repo repo)
    CODE:
	{
	    FILE *fp;
	    char *buf;
	    size_t len;
	    fp = open_memstream(&buf, &len);
	    if (fp == 0)
	      croak("open_memstream: %s\n", Strerror(errno));
	    repo_write_filtered(repo, fp, myrepowritefilter, 0, 0);
	    if (fclose(fp))
	      croak("fclose: %s\n",  Strerror(errno));
	    RETVAL = newSVpvn(buf, len);
	    free(buf);
	}
    OUTPUT:
	RETVAL

int
updatefrombins(BSSolv::repo repo, char *dir, ...)
    CODE:
	{
	    Pool *pool = repo->pool;
	    int i;
	    Repodata *data = 0;
	    Hashtable ht;
	    Hashval h, hh, hm;
	    int dirty = 0;
	    Map reused;
	    int oldend = 0;
	    Id p, id;
	    Solvable *s;
	    STRLEN sl;
	    const char *oldcookie;

	    map_init(&reused, repo->end - repo->start);
	    if (repo_lookup_str(repo, SOLVID_META, buildservice_dodurl))
	      {
	        /* this is a dod repo. keep all dod packages. */
		FOR_REPO_SOLVABLES(repo, p, s)
		  {
		    const char *str = solvable_lookup_str(s, buildservice_id);
		    if (str && !strcmp(str, "dod"))
		      MAPSET(&reused, p - repo->start);
		  }
	      }
	    hm = mkmask(2 * repo->nsolvables + 1);
	    ht = solv_calloc(hm + 1, sizeof(*ht));
	    oldcookie = repo_lookup_str(repo, SOLVID_META, buildservice_repocookie);
	    if (oldcookie && !strcmp(oldcookie, REPOCOOKIE))
	      {
		FOR_REPO_SOLVABLES(repo, p, s)
		  {
		    const char *str = solvable_lookup_str(s, buildservice_id);
		    if (!str || !strcmp(str, "dod"))
		      continue;
		    h = strhash(str) & hm;
		    hh = HASHCHAIN_START;
		    while (ht[h])
		      h = HASHCHAIN_NEXT(h, hh, hm);
		    ht[h] = p;
		  }
	      }
	    if (repo->end != repo->start)
	      oldend = repo->end;

	    for (i = 2; i + 1 < items; i += 2)
	      {
		char *s = SvPV(ST(i), sl);
		char *sid = SvPV_nolen(ST(i + 1));
		if (sl < 4)
		  continue;
		if (strcmp(s + sl - 4, ".rpm")
                    && strcmp(s + sl - 4, ".deb")
                    && (sl < 10 || strcmp(s + sl - 10, ".obsbinlnk"))
#ifdef ARCH_ADD_WITH_PKGID
                    && (sl < 11 || strcmp(s + sl - 11, ".pkg.tar.gz"))
                    && (sl < 11 || strcmp(s + sl - 11, ".pkg.tar.xz"))
                    && (sl < 12 || strcmp(s + sl - 12, ".pkg.tar.zst"))
#endif
		   )
		  continue;
		if (sl > 10 && !strcmp(s + sl - 10, ".patch.rpm"))
		  continue;
		if (sl > 10 && !strcmp(s + sl - 10, ".nosrc.rpm"))
		  continue;
		if (sl > 8 && !strcmp(s + sl - 8, ".src.rpm"))
		  continue;
		h = strhash(sid) & hm;
		hh = HASHCHAIN_START;
		while ((id = ht[h]) != 0)
		  {
		    const char *str = solvable_lookup_str(pool->solvables + id, buildservice_id);
		    if (!strcmp(str, sid))
		      {
			/* check location (unless it's a obsbinlnk where the location comes from the content) */
			unsigned int medianr;
			str = solvable_get_location(pool->solvables + id, &medianr);
			if (str[0] == '.' && str[1] == '/')
			  str += 2;
			if (!strcmp(str, s) || (sl >= 10 && !strcmp(s + sl - 10, ".obsbinlnk")))
		          break;
		      }
		    h = HASHCHAIN_NEXT(h, hh, hm);
		  }
		if (id)
		  {
		    /* same id and location, reuse old entry */
		    MAPSET(&reused, id - repo->start);
		  }
		else
		  {
		    /* add new entry */
		    dirty++;
		    if (!data)
		      data = repo_add_repodata(repo, 0);
		    repodata_addbin(data, dir, s, (int)sl, sid);
		  }
	      }
	    solv_free(ht);
	    if (oldcookie)
	      {
		if (strcmp(oldcookie, REPOCOOKIE) != 0)
		  {
		    Repodata *firstrepodata = repo_id2repodata(repo, 1);
		    if (data && data != firstrepodata)
		      repodata_internalize(data);
		    data = firstrepodata;
		    repodata_set_str(data, SOLVID_META, buildservice_repocookie, REPOCOOKIE);
		  }
	      }
	    else
	      {
	        if (!data)
	          data = repo_add_repodata(repo, 0);
	        repodata_set_str(data, SOLVID_META, buildservice_repocookie, REPOCOOKIE);
	      }
	    if (data)
	      repodata_internalize(data);
	    if (oldend)
	      {
		for (i = repo->start; i < oldend; i++)
		  {
		    if (pool->solvables[i].repo != repo)
		      continue;
		    if (MAPTST(&reused, i - repo->start))
		      continue;
		    if (dirty <= 0)
		      dirty--;
		    repo_free_solvable_block(repo, i, 1, 0);
		  }
	      }
	    map_free(&reused);
	    RETVAL = dirty;
	}
    OUTPUT:
	RETVAL

void
modulesfrombins(BSSolv::repo repo, ...)
    PPCODE:
	{
	    Pool *pool = repo->pool;
	    Hashtable ht;
	    Hashval h, hh, hm;
	    Queue modules;
	    Queue collectedmodules;
            Id p, lastid;
	    Solvable *s;
	    int i, j;

	    queue_init(&collectedmodules);
	    queue_init(&modules);
	    hm = mkmask(2 * repo->nsolvables + 1);
	    ht = solv_calloc(hm + 1, sizeof(*ht));
	    FOR_REPO_SOLVABLES(repo, p, s)
	      {
	        const char *bsid = solvable_lookup_str(s, buildservice_id);
		if (!bsid)
		  continue;
		if (!strcmp(bsid, "dod"))
		  h = s->name + s->evr * 37 + s->arch * 129;
		else
		  h = strhash(bsid);
		h &= hm;
		hh = HASHCHAIN_START;
		while (ht[h])
		  h = HASHCHAIN_NEXT(h, hh, hm);
		ht[h] = p;
	      }

	    for (i = 1; i + 1 < items; i += 2)
	      {
		const char *bsid = SvPV_nolen(ST(i + 1));
		h = strhash(bsid) & hm;
		hh = HASHCHAIN_START;
		while ((p = ht[h]) != 0)
		  {
		    const char *bsid2 = solvable_lookup_str(pool->solvables + p, buildservice_id);
		    if (!strcmp(bsid, bsid2))
		      break;
		    h = HASHCHAIN_NEXT(h, hh, hm);
		  }
		if (!p)
		  continue;
		s = pool->solvables + p;
	        h = (s->name + s->evr * 37 + s->arch * 129) & hm;
		hh = HASHCHAIN_START;
		while ((p = ht[h]) != 0)
		  {
		    Solvable *s2 = pool->solvables + p;
		    if (s->name == s2->name && s->evr == s2->evr && s->arch == s2->arch)
		      {
			lastid = collectedmodules.count ? collectedmodules.elements[collectedmodules.count - 1] : 0;
			solvable_lookup_idarray(s2, buildservice_modules, &modules);
			for (j = 0; j < modules.count; j++)
			  if (modules.elements[j] != lastid)
			    queue_push(&collectedmodules, modules.elements[j]);
		      }
		    h = HASHCHAIN_NEXT(h, hh, hm);
		  }
	      }
	    solv_free(ht);
	    queue_free(&modules);
	    /* sort and unify */
	    solv_sort(collectedmodules.elements, collectedmodules.count, sizeof(Id), unifymodules_cmp, 0);
	    lastid = -1;
	    for (i = 0; i < collectedmodules.count; i++)
	      {
		if (collectedmodules.elements[i] == lastid)
		  continue;
		lastid = collectedmodules.elements[i];
	        XPUSHs(sv_2mortal(newSVpv(pool_id2str(pool, lastid), 0)));
	      }
	    queue_free(&collectedmodules);
	}

void
missingmodules(BSSolv::repo repo, ...)
    PPCODE:
	{
	    Pool *pool = repo->pool;
            Id p, *pp, *modules, id, req, lastid1, lastid2;
	    Solvable *s;
	    Queue missingq;
	    int i, missing;

	    queue_init(&missingq);
	    if (pool->appdata && ((Id *)pool->appdata)[0] && has_keyname(repo, buildservice_modules))
	      {
		FOR_REPO_SOLVABLES(repo, p, s)
		  {
		    if (s->name != buildservice_modules || s->arch != ARCH_SRC || !s->requires)
		      continue;
		    id = s->repo->idarraydata[s->provides];
		    for (modules = pool->appdata; *modules; modules++)
		      if (id == *modules)
			break;
		    if (!*modules)
		      continue;
		    missing = 0;
		    for (pp = s->repo->idarraydata + s->requires; (req = *pp) != 0; pp++)
		      if (!match_modules_req(pool, req))
			{
			  missing = 1;
			  queue_push2(&missingq, id, req);
			}
		    if (!missing)	/* we're good */
		      queue_push2(&missingq, id, 0);
		  }
		/* sort and unify */
		solv_sort(missingq.elements, missingq.count / 2, sizeof(Id) * 2, missingmodules_cmp, 0);
		lastid1 = lastid2 = -1;
		for (i = 0; i < missingq.count; i += 2)
		  {
		    if (missingq.elements[i] == lastid1 && missingq.elements[i + 1] == lastid2)
		      continue;
		    if (missingq.elements[i] != lastid1)
		      {
			lastid1 = missingq.elements[i];
			lastid2 = missingq.elements[i + 1];
		      }
		    if (!lastid2)
		      continue;
		    lastid2 = missingq.elements[i + 1];
		    XPUSHs(sv_2mortal(newSVpv(pool_id2str(pool, lastid1), 0)));
		    XPUSHs(sv_2mortal(newSVpv(pool_id2str(pool, lastid2), 0)));
		  }
		queue_free(&missingq);
	      }
	}

void
getpathid(BSSolv::repo repo)
    PPCODE:
	{
	    Id p;
	    Solvable *s;
	    EXTEND(SP, repo->nsolvables * 2);
	    FOR_REPO_SOLVABLES(repo, p, s)
	      {
		unsigned int medianr;
		const char *str;
		str = solvable_get_location(s, &medianr);
		/* We need to special case .obsbinlink here where the location
		 * points back into the package. We currently assume that
		 * the name in the full tree is always <name>.obsbinlnk */
		if (!strncmp(str, "../", 3))
		  str = pool_tmpjoin(repo->pool, pool_id2str(repo->pool, s->name), ".obsbinlnk", 0);
		PUSHs(sv_2mortal(newSVpv(str, 0)));
		str = solvable_lookup_str(s, buildservice_id);
		PUSHs(sv_2mortal(newSVpv(str, 0)));
	      }
	}

const char *
name(BSSolv::repo repo)
    CODE:
	RETVAL = repo->name;
    OUTPUT:
	RETVAL

int
isexternal(BSSolv::repo repo)
    CODE:
	RETVAL = repo_lookup_void(repo, SOLVID_META, buildservice_external) ? 1 : 0;
    OUTPUT:
	RETVAL

const char *
dodurl(BSSolv::repo repo)
    CODE:
	RETVAL = repo_lookup_str(repo, SOLVID_META, buildservice_dodurl);
    OUTPUT:
	RETVAL

const char *
dodcookie(BSSolv::repo repo)
    CODE:
	RETVAL = repo_lookup_str(repo, SOLVID_META, buildservice_dodcookie);
    OUTPUT:
	RETVAL

void
dodresources(BSSolv::repo repo)
    PPCODE:
	{
	  Pool *pool = repo->pool;
	  Queue dodresources;
	  int i;

	  queue_init(&dodresources);
	  repo_lookup_idarray(repo, SOLVID_META, buildservice_dodresources, &dodresources);
	  for (i = 0; i < dodresources.count; i++)
	    XPUSHs(sv_2mortal(newSVpv(pool_id2str(pool, dodresources.elements[i]), 0)));
	  queue_free(&dodresources);
	}

void
updatedoddata(BSSolv::repo repo, HV *rhv = 0)
    CODE:
	{
	    Id p;
	    Solvable *s;
	    Repodata *data;
	    /* delete old dod data */
	    FOR_REPO_SOLVABLES(repo, p, s)
	      {
		const char *str = solvable_lookup_str(s, buildservice_id);
		if (!str || !strcmp(str, "dod"))
		    repo_free_solvable(repo, p, 1);
	      }
	    data = repo_add_repodata(repo, REPO_REUSE_REPODATA);
	    repodata_unset(data, SOLVID_META, buildservice_dodurl);
	    repodata_unset(data, SOLVID_META, buildservice_dodcookie);
	    repodata_unset(data, SOLVID_META, buildservice_dodresources);
	    /* add new data */
	    if (rhv)
		data2solvables(repo, data, (SV *)rhv, 1);
	    repo_internalize(repo);
	}

void
setpriority(BSSolv::repo repo, int priority)
    PPCODE:
	repo->priority = priority;

int
mayhavemodules(BSSolv::repo repo)
    CODE:
	RETVAL = has_keyname(repo, buildservice_modules);
    OUTPUT:
	RETVAL

void
getmodules(BSSolv::repo repo)
    PPCODE:
	if (has_keyname(repo, buildservice_modules))
	  {
	    Pool *pool = repo->pool;
	    Id p, lastid = -1;
	    Solvable *s;
	    Queue collectedmodules;
	    int i;

	    queue_init(&collectedmodules);
	    FOR_REPO_SOLVABLES(repo, p, s)
	      if (s->name == buildservice_modules && s->arch == ARCH_SRC && s->repo->idarraydata[s->provides])
	        queue_push(&collectedmodules, s->repo->idarraydata[s->provides]);
	    if (!collectedmodules.count)
	      {
	        Queue modules;
		queue_init(&modules);
		FOR_REPO_SOLVABLES(repo, p, s)
		  {
		    solvable_lookup_idarray(pool->solvables + p, buildservice_modules, &modules);
		    for (i = 0; i < modules.count; i++)
		      {
			if (modules.elements[i] == lastid)
			  continue;
			lastid = modules.elements[i];
			queue_push(&collectedmodules, modules.elements[i]);
		      }
		  }
		queue_free(&modules);
	      }
	    /* sort and unify */
	    solv_sort(collectedmodules.elements, collectedmodules.count, sizeof(Id), unifymodules_cmp, 0);
	    lastid = -1;
	    for (i = 0; i < collectedmodules.count; i++)
	      {
		if (collectedmodules.elements[i] == lastid)
		  continue;
		lastid = collectedmodules.elements[i];
	        XPUSHs(sv_2mortal(newSVpv(pool_id2str(pool, lastid), 0)));
	      }
	    queue_free(&collectedmodules);
	  }

void
getdodblobs(BSSolv::repo repo)
    PPCODE:
	{
	    Pool *pool = repo->pool;
	    int i;
	    Id p;
	    Solvable *s;
	    Stringpool ss;
	    stringpool_init_empty(&ss);
	    FOR_REPO_SOLVABLES(repo, p, s)
	      {
		const char *str = solvable_lookup_str(s, buildservice_id);
		unsigned int medianr;
		const char *s, *se;
		if (!str || strcmp(str, "dod") != 0)
		  continue;
		s = solvable_get_location(pool->solvables + p, &medianr);
		if ((s = strrchr(s, '?')) == 0)
		  continue;
		for (++s; s; s = se ? se + 1 : 0)
		  {
		    se = strchr(s, ',');
		    if (se)
		      stringpool_strn2id(&ss, s, se - s, 1);
		    else
		      stringpool_str2id(&ss, s, 1);
		  }
	      }
	    for (i = 2; i < ss.nstrings; i++)
	      {
	        XPUSHs(sv_2mortal(newSVpv(stringpool_id2str(&ss, i), 0)));
	      }
	    stringpool_free(&ss);
	}


MODULE = BSSolv		PACKAGE = BSSolv::expander	PREFIX = expander

BSSolv::expander
new(char *packname = "BSSolv::expander", BSSolv::pool pool, HV *config)
    CODE:
	{
	    SV *sv, **svp;
	    char *str, *p;
	    int i;
	    Id id;
	    Expander *xp;
	    Queue preferpos;
	    Queue preferneg;
	    Queue ignore;
	    Queue conflict;
	    Queue fileprovides;
	    int debug = 0;
	    int options = 0;

	    queue_init(&preferpos);
	    queue_init(&preferneg);
	    queue_init(&ignore);
	    queue_init(&conflict);
	    queue_init(&fileprovides);
	    svp = hv_fetch(config, "prefer", 6, 0);
	    sv = svp ? *svp : 0;
	    if (sv && SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVAV)
	      {
		AV *av = (AV *)SvRV(sv);
		for (i = 0; i <= av_len(av); i++)
		  {
		    svp = av_fetch(av, i, 0);
		    if (!svp)
		      continue;
		    sv = *svp;
		    str = SvPV_nolen(sv);
		    if (!str)
		      continue;
		    if (*str == '-')
		      queue_push(&preferneg, pool_str2id(pool, str + 1, 1));
		    else
		      queue_push(&preferpos, pool_str2id(pool, str, 1));
		  }
	      }
	    svp = hv_fetch(config, "ignoreh", 7, 0);
	    sv = svp ? *svp : 0;
	    if (sv && SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVHV)
	      {
		HV *hv = (HV *)SvRV(sv);
		HE *he;
		hv_iterinit(hv);
		while ((he = hv_iternext(hv)) != 0)
		  {
		    I32 strl;
		    str = hv_iterkey(he, &strl);
		    if (!str)
		      continue;
		    queue_push(&ignore, pool_str2id(pool, str, 1));
		  }
	      }
	    svp = hv_fetch(config, "conflict", 8, 0);
	    sv = svp ? *svp : 0;
	    if (sv && SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVAV)
	      {
		AV *av = (AV *)SvRV(sv);
		for (i = 0; i <= av_len(av); i++)
		  {
		    svp = av_fetch(av, i, 0);
		    if (!svp)
		      continue;
		    sv = *svp;
		    str = SvPV_nolen(sv);
		    if (!str)
		      continue;
		    p = strchr(str, ':');
		    if (!p)
		      continue;
		    id = pool_strn2id(pool, str, p - str, 1);
		    str = p + 1;
		    while ((p = strchr(str, ',')) != 0)
		      {
			queue_push2(&conflict, id, pool_strn2id(pool, str, p - str, 1));
			str = p + 1;
		      }
		    queue_push2(&conflict, id, pool_str2id(pool, str, 1));
		  }
	      }
	    svp = hv_fetch(config, "fileprovides", 12, 0);
	    sv = svp ? *svp : 0;
	    if (sv && SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVHV)
	      {
		HV *hv = (HV *)SvRV(sv);
		I32 strl;

		hv_iterinit(hv);
		while ((sv = hv_iternextsv(hv, &str, &strl)) != 0)
		  {
		    AV *av;
		    Id id2;

		    if (!SvROK(sv) || SvTYPE(SvRV(sv)) != SVt_PVAV)
		      continue;
		    id = pool_str2id(pool, str, 1);
		    av = (AV *)SvRV(sv);
		    for (i = 0; i <= av_len(av); i++)
		      {
			svp = av_fetch(av, i, 0);
			if (!svp)
			  continue;
			sv = *svp;
			str = SvPV_nolen(sv);
			if (!str)
			  continue;
			id2 = pool_str2id(pool, str, 0);
			if (!id2)
			  continue;
			if (id)
			  {
			    queue_push(&fileprovides, id);	/* start name block */
			    id = 0;
			  }
			queue_push(&fileprovides, id2);
		      }
		    if (id == 0)
		      queue_push(&fileprovides, 0);	/* had at least one entry, finish name block */
		  }
	      }
	    options |= EXPANDER_OPTION_USERECOMMENDSFORCHOICES;
	    svp = hv_fetch(config, "expandflags:ignoreconflicts", 27, 0);
	    sv = svp ? *svp : 0;
	    if (sv && SvTRUE(sv))
	      options |= EXPANDER_OPTION_IGNORECONFLICTS;
	    svp = hv_fetch(config, "expandflags:dorecommends", 24, 0);
	    sv = svp ? *svp : 0;
	    if (sv && SvTRUE(sv))
	      options |= EXPANDER_OPTION_DORECOMMENDS;
	    svp = hv_fetch(config, "expandflags:dosupplements", 25, 0);
	    sv = svp ? *svp : 0;
	    if (sv && SvTRUE(sv))
	      options |= EXPANDER_OPTION_DOSUPPLEMENTS | EXPANDER_OPTION_USESUPPLEMENTSFORCHOICES;
	    svp = hv_fetch(config, "expand_dbg", 10, 0);
	    sv = svp ? *svp : 0;
	    if (sv && SvOK(sv))
	      debug = SvIV(sv);
	    else
	      {
		sv = get_sv("Build::expand_dbg", FALSE);
		if (sv && SvOK(sv))
		  debug = SvIV(sv);
	      }
	    xp = expander_create(pool, &preferpos, &preferneg, &ignore, &conflict, &fileprovides, debug, options);
	    queue_free(&preferpos);
	    queue_free(&preferneg);
	    queue_free(&ignore);
	    queue_free(&conflict);
	    queue_free(&fileprovides);
	    RETVAL = xp;
	}
    OUTPUT:
	RETVAL


void
expand(BSSolv::expander xp, ...)
    PPCODE:
	{
	    Pool *pool;
	    int i, nerrors;
	    Id id, who, indepbuf[64];
	    Queue ignoreq, in, out, indep;
	    int directdepsend = 0;
	    int options = 0;

	    queue_init(&ignoreq);
	    queue_init(&in);
	    queue_init(&out);
	    queue_init_buffer(&indep, indepbuf, sizeof(indepbuf)/sizeof(*indepbuf));
	    pool = xp->pool;
	    if (xp->debug)
	      expander_dbg(xp, "expand args:");
	    for (i = 1; i < items; i++)
	      {
		char *s = SvPV_nolen(ST(i));
		int deptype = DEPTYPE_REQUIRES;

		if (xp->debug)
		  expander_dbg(xp, " %s", s);
		if (*s == '-' && s[1] == '-')
		  {
		    /* expand option */
		    if (!strcmp(s, "--ignoreignore--"))
		      options |= EXPANDER_OPTION_IGNOREIGNORE;
		    else if (!strcmp(s, "--directdepsend--"))
		      directdepsend = 1;
		    else if (!strcmp(s, "--dorecommends--"))
		      options |= EXPANDER_OPTION_DORECOMMENDS;
		    else if (!strcmp(s, "--dosupplements--"))
		      options |= EXPANDER_OPTION_DOSUPPLEMENTS | EXPANDER_OPTION_USESUPPLEMENTSFORCHOICES;
		    else if (!strcmp(s, "--ignoreconflicts--"))
		      options |= EXPANDER_OPTION_IGNORECONFLICTS;
		    continue;
		  }
		if (*s == '-')
		  {
		    /* ignore dependency */
		    id = pool_str2id(pool, s + 1, 1);
		    queue_push(&ignoreq, id);
		    continue;
		  }
		if (*s == '!')
		  {
		    deptype = DEPTYPE_CONFLICTS;
		    s++;
		    if (*s == '!')
		      {
			deptype = DEPTYPE_OBSOLETES;
			s++;
		      }
		  }
		id = dep2id(pool, s);
		if (deptype == DEPTYPE_REQUIRES && !directdepsend)
		  queue_push(&in, id);
		else
		  queue_push2(&indep, deptype, id);
	      }
	    if (xp->debug)
	      expander_dbg(xp, "\n");

	    nerrors = expander_expand(xp, &in, &indep, &out, &ignoreq, options);

	    queue_free(&in);
	    queue_free(&indep);
	    queue_free(&ignoreq);

	    if (nerrors)
	      {
		EXTEND(SP, nerrors + 1);
		PUSHs(sv_2mortal(newSV(0)));
		for (i = 0; i < out.count; )
		  {
		    SV *sv;
		    Id type = out.elements[i];
		    if (type == ERROR_NOPROVIDER)
		      {
			id = out.elements[i + 1];
			who = out.elements[i + 2];
			if (who)
		          sv = newSVpvf("nothing provides %s needed by %s", pool_dep2str(pool, id), solvid2name(pool, who));
			else
		          sv = newSVpvf("nothing provides %s", pool_dep2str(pool, id));
			i += 3;
		      }
		    else if (type == ERROR_ALLCONFLICT)
		      {
			id = out.elements[i + 1];
			who = out.elements[i + 2];
			if (who)
		          sv = newSVpvf("%s conflicts with always true %s", solvid2name(pool, who), pool_dep2str(pool, id));
			else
		          sv = newSVpvf("conflict with always true %s", pool_dep2str(pool, id));
			i += 3;
		      }
		    else if (type == ERROR_CONFLICT)
		      {
			Id who2 = out.elements[i + 2];
			who = out.elements[i + 1];
			if (!who && who2 >= 0)
		          sv = newSVpvf("conflicts with %s", solvid2name(pool, who2));
			else if (who2 < 0)
		          sv = newSVpvf("%s obsoletes %s", solvid2name(pool, who), solvid2name(pool, -who2));
			else
		          sv = newSVpvf("%s conflicts with %s", solvid2name(pool, who), solvid2name(pool, who2));
			i += 3;
		      }
		    else if (type == ERROR_CONFLICT2)
		      {
			Id who2 = out.elements[i + 2];
			who = out.elements[i + 1];
			if (who2 < 0)
		          sv = newSVpvf("%s is obsoleted by %s", solvid2name(pool, who), solvid2name(pool, -who2));
			else if (who2 > 0)
		          sv = newSVpvf("%s is in conflict with %s", solvid2name(pool, who), solvid2name(pool, who2));
			else
		          sv = newSVpvf("%s is in conflict", solvid2name(pool, who));
			i += 3;
		      }
		    else if (type == ERROR_CONFLICTINGPROVIDERS)
		      {
			id = out.elements[i + 1];
			who = out.elements[i + 2];
			if (who)
			  sv = newSVpvf("conflict for providers of %s needed by %s", pool_dep2str(pool, id), solvid2name(pool, who));
			else
			  sv = newSVpvf("conflict for providers of %s", pool_dep2str(pool, id));
			i += 3;
		      }
		    else if (type == ERROR_PROVIDERINFO)
		      {
			Id who2 = out.elements[i + 2];
			who = out.elements[i + 1];
			if (who2 < 0)
		          sv = newSVpvf("(provider %s obsoletes %s)", solvid2name(pool, who), solvid2name(pool, -who2));
			else
		          sv = newSVpvf("(provider %s conflicts with %s)", solvid2name(pool, who), solvid2name(pool, who2));
			i += 3;
		      }
		    else if (type == ERROR_PROVIDERINFO2)
		      {
			Id who2 = out.elements[i + 2];
			who = out.elements[i + 1];
			if (who2 < 0)
		          sv = newSVpvf("(provider %s is obsoleted by %s)", solvid2name(pool, who), solvid2name(pool, -who2));
			else if (who2 > 0)
		          sv = newSVpvf("(provider %s is in conflict with %s)", solvid2name(pool, who), solvid2name(pool, who2));
			else
		          sv = newSVpvf("(provider %s is in conflict)", solvid2name(pool, who));
			i += 3;
		      }
		    else if (type == ERROR_CHOICE)
		      {
			int j;
			char *str = "";
			for (j = i + 3; out.elements[j]; j++)
			  ;
			solv_sort(out.elements + i + 3, j - (i + 3), sizeof(Id), pkgname_sort_cmp, pool);
			for (j = i + 3; out.elements[j]; j++)
			  {
			    Solvable *s = pool->solvables + out.elements[j];
			    str = pool_tmpjoin(pool, str, " ", pool_id2str(pool, s->name));
			  }
			if (*str)
			  str++;	/* skip starting ' ' */
			id = out.elements[i + 1];
			who = out.elements[i + 2];
			if (who)
		          sv = newSVpvf("have choice for %s needed by %s: %s", pool_dep2str(pool, id), solvid2name(pool, who), str);
			else
		          sv = newSVpvf("have choice for %s: %s", pool_dep2str(pool, id), str);
			i = j + 1;
		      }
		    else if (type == ERROR_BADDEPENDENCY)
		      {
			id = out.elements[i + 1];
			who = out.elements[i + 2];
			if (who)
		          sv = newSVpvf("cannot parse dependency %s from %s", pool_dep2str(pool, id), solvid2name(pool, who));
			else
		          sv = newSVpvf("cannot parse dependency %s", pool_dep2str(pool, id));
			i += 3;
		      }
		    else if (type == ERROR_NOPROVIDERINFO)
		      {
			id = out.elements[i + 1];
			who = out.elements[i + 2];
			if (who)
		          sv = newSVpvf("(got version %s provided by %s)", pool_id2str(pool, id), solvid2name(pool, who));
			else
		          sv = newSVpvf("(got version %s)", pool_id2str(pool, id));
			i += 3;
		      }
		    else
		      croak("expander: bad error type\n");
		    PUSHs(sv_2mortal(sv));
		  }
	      }
	    else
	      {
		EXTEND(SP, out.count + 1);
		PUSHs(sv_2mortal(newSViv((IV)1)));
		for (i = 0; i < out.count; i++)
		  {
		    Solvable *s = pool->solvables + out.elements[i];
		    PUSHs(sv_2mortal(newSVpv(pool_id2str(pool, s->name), 0)));
		  }
	      }
	    queue_free(&out);
	}

void
debug(BSSolv::expander xp, const char *str)
    CODE:
	expander_dbg(xp, "%s", str);


const char *
debugstr(BSSolv::expander xp)
    CODE:
	RETVAL = xp->debugstr ? xp->debugstr : "";
    OUTPUT:
	RETVAL

const char *
debugstrclr(BSSolv::expander xp)
	
    CODE:
	RETVAL = xp->debugstr ? xp->debugstr : "";
    OUTPUT:
	RETVAL
    CLEANUP:
	expander_clrdbg(xp);

void
DESTROY(BSSolv::expander xp)
    CODE:
	expander_free(xp);

openSUSE Build Service is sponsored by