/** \ingroup header
 * \file rpmdb/header.c
 */

/* RPM - Copyright (C) 1995-2002 Red Hat Software */

/* Data written to file descriptors is in network byte order.    */
/* Data read from file descriptors is expected to be in          */
/* network byte order and is converted on the fly to host order. */

#include "system.h"

#include <rpmio.h>
#define	_RPMTAG_INTERNAL
#include <header_internal.h>

#include "debug.h"

/*@unchecked@*/
int _hdr_debug = 0;

/*@access Header @*/
/*@access HeaderIterator @*/
/*@access headerSprintfExtension @*/
/*@access headerTagTableEntry @*/

/*@access entryInfo @*/
/*@access indexEntry @*/

/** \ingroup header
 */
/*@-type@*/
/*@observer@*/ /*@unchecked@*/
static unsigned char header_magic[8] = {
	0x8e, 0xad, 0xe8, 0x01, 0x00, 0x00, 0x00, 0x00
};
/*@=type@*/

/** \ingroup header
 * Size of header data types.
 */
/*@observer@*/ /*@unchecked@*/
static int typeSizes[16] =  { 
    0,	/*!< (unused) RPM_NULL_TYPE */
    1,	/*!< (unused) RPM_CHAR_TYPE */
    1,	/*!< RPM_UINT8_TYPE */
    2,	/*!< RPM_UINT16_TYPE */
    4,	/*!< RPM_UINT32_TYPE */
    8,	/*!< RPM_UINT64_TYPE */
    -1,	/*!< RPM_STRING_TYPE */
    1,	/*!< RPM_BIN_TYPE */
    -1,	/*!< RPM_STRING_ARRAY_TYPE */
    -1,	/*!< RPM_I18NSTRING_TYPE */
    0,  /*!< (unused) RPM_ASN1_TYPE */
    0,  /*!< (unused) RPM_OPENPGP_TYPE */
    0,
    0,
    0,
    0
};

/** \ingroup header
 * Maximum no. of bytes permitted in a header.
 */
/*@unchecked@*/
static size_t headerMaxbytes = (1024*1024*1024);

/**
 * Global header stats enabler.
 */
/*@unchecked@*/
int _hdr_stats = 0;

/*@-compmempass@*/
/*@unchecked@*/
static struct rpmop_s hdr_loadops;
/*@unchecked@*/ /*@relnull@*/
rpmop _hdr_loadops = &hdr_loadops;
/*@unchecked@*/
static struct rpmop_s hdr_getops;
/*@unchecked@*/ /*@relnull@*/
rpmop _hdr_getops = &hdr_getops;
/*@=compmempass@*/

void * headerGetStats(Header h, int opx)
{
    rpmop op = NULL;
    if (_hdr_stats)
    switch (opx) {
    case 18:	op = &h->h_loadops;	break;	/* RPMTS_OP_HDRLOAD */
    case 19:	op = &h->h_getops;	break;	/* RPMTS_OP_HDRGET */
    }
    return op;
}

Header headerLink(Header h)
{
/*@-nullret@*/
    if (h == NULL) return NULL;
/*@=nullret@*/

    h->nrefs++;
/*@-modfilesys@*/
if (_hdr_debug)
fprintf(stderr, "--> h  %p ++ %d at %s:%u\n", h, h->nrefs, __FILE__, __LINE__);
/*@=modfilesys@*/

    /*@-refcounttrans @*/
    return h;
    /*@=refcounttrans @*/
}

Header headerUnlink(Header h)
{
    if (h == NULL) return NULL;
/*@-modfilesys@*/
if (_hdr_debug)
fprintf(stderr, "--> h  %p -- %d at %s:%u\n", h, h->nrefs, __FILE__, __LINE__);
/*@=modfilesys@*/
    h->nrefs--;
    return NULL;
}

Header headerFree(Header h)
{
    (void) headerUnlink(h);

    /*@-usereleased@*/
    if (h == NULL || h->nrefs > 0)
	return NULL;	/* XXX return previous header? */

    if (h->index) {
	indexEntry entry = h->index;
	size_t i;
	for (i = 0; i < h->indexUsed; i++, entry++) {
	    if ((h->flags & HEADERFLAG_ALLOCATED) && ENTRY_IS_REGION(entry)) {
		if (entry->length > 0) {
		    uint32_t * ei = entry->data;
		    if ((ei - 2) == h->blob)
			h->blob = _free(h->blob);
		    entry->data = NULL;
		}
	    } else if (!ENTRY_IN_REGION(entry)) {
		entry->data = _free(entry->data);
	    }
	    entry->data = NULL;
	}
	h->index = _free(h->index);
    }
    h->origin = _free(h->origin);
    h->baseurl = _free(h->baseurl);
    h->digest = _free(h->digest);

/*@-nullstate@*/
    if (_hdr_stats) {
	if (_hdr_loadops)	/* RPMTS_OP_HDRLOAD */
	    (void) rpmswAdd(_hdr_loadops, headerGetStats(h, 18));
	if (_hdr_getops)	/* RPMTS_OP_HDRGET */
	    (void) rpmswAdd(_hdr_getops, headerGetStats(h, 19));
    }
/*@=nullstate@*/

    /*@-refcounttrans@*/ h = _free(h); /*@=refcounttrans@*/
    return h;
    /*@=usereleased@*/
}

Header headerNew(void)
{
    Header h = xcalloc(1, sizeof(*h));

    (void) memcpy(h->magic, header_magic, sizeof(h->magic));
    h->blob = NULL;
    h->origin = NULL;
    h->baseurl = NULL;
    h->digest = NULL;
    h->instance = 0;
    h->indexAlloced = INDEX_MALLOC_SIZE;
    h->indexUsed = 0;
    h->flags |= HEADERFLAG_SORTED;

    h->index = (h->indexAlloced
	? xcalloc(h->indexAlloced, sizeof(*h->index))
	: NULL);

    h->nrefs = 0;
    /*@-globstate -observertrans @*/
    return headerLink(h);
    /*@=globstate =observertrans @*/
}

/**
 */
static int indexCmp(const void * avp, const void * bvp)
	/*@*/
{
    /*@-castexpose@*/
    indexEntry ap = (indexEntry) avp, bp = (indexEntry) bvp;
    /*@=castexpose@*/
    return ((int)ap->info.tag - (int)bp->info.tag);
}

/** \ingroup header
 * Sort tags in header.
 * @param h		header
 */
static
void headerSort(Header h)
	/*@modifies h @*/
{
    if (!(h->flags & HEADERFLAG_SORTED)) {
	qsort(h->index, h->indexUsed, sizeof(*h->index), indexCmp);
	h->flags |= HEADERFLAG_SORTED;
    }
}

/**
 */
static int offsetCmp(const void * avp, const void * bvp) /*@*/
{
    /*@-castexpose@*/
    indexEntry ap = (indexEntry) avp, bp = (indexEntry) bvp;
    /*@=castexpose@*/
    int rc = ((int)ap->info.offset - (int)bp->info.offset);

    if (rc == 0) {
	/* Within a region, entries sort by address. Added drips sort by tag. */
	if (ap->info.offset < 0)
	    rc = (((char *)ap->data) - ((char *)bp->data));
	else
	    rc = ((int)ap->info.tag - (int)bp->info.tag);
    }
    return rc;
}

/** \ingroup header
 * Restore tags in header to original ordering.
 * @param h		header
 */
static
void headerUnsort(Header h)
	/*@modifies h @*/
{
    qsort(h->index, h->indexUsed, sizeof(*h->index), offsetCmp);
}

size_t headerSizeof(Header h)
{
    indexEntry entry;
    size_t size = 0;
    size_t pad = 0;
    size_t i;

    if (h == NULL)
	return size;

    headerSort(h);

    size += sizeof(header_magic);	/* XXX HEADER_MAGIC_YES */

    /*@-sizeoftype@*/
    size += 2 * sizeof(uint32_t);	/* count of index entries */
    /*@=sizeoftype@*/

    for (i = 0, entry = h->index; i < h->indexUsed; i++, entry++) {
	size_t diff;
	rpmTagType type;

	/* Regions go in as is ... */
        if (ENTRY_IS_REGION(entry)) {
	    size += entry->length;
	    /* XXX Legacy regions do not include the region tag and data. */
	    /*@-sizeoftype@*/
	    if (i == 0 && (h->flags & HEADERFLAG_LEGACY))
		size += sizeof(struct entryInfo_s) + entry->info.count;
	    /*@=sizeoftype@*/
	    continue;
        }

	/* ... and region elements are skipped. */
	if (entry->info.offset < 0)
	    continue;

	/* Alignment */
	type = entry->info.type;
	if (typeSizes[type] > 1) {
	    diff = typeSizes[type] - (size % typeSizes[type]);
	    if ((int)diff != typeSizes[type]) {
		size += diff;
		pad += diff;
	    }
	}

	/*@-sizeoftype@*/
	size += sizeof(struct entryInfo_s) + entry->length;
	/*@=sizeoftype@*/
    }

    return size;
}

/**
 * Return length of entry data.
 * @param type		entry data type
 * @param *p		tag container data
 * @param count		entry item count
 * @param onDisk	data is concatenated strings (with NUL's))?
 * @param *pend		pointer to end of tag container data (or NULL)
 * @return		no. bytes in data, 0 on failure
 */
static size_t dataLength(rpmTagType type, rpmTagData * p, rpmTagCount count,
		int onDisk, /*@null@*/ rpmTagData * pend)
	/*@*/
{
    const unsigned char * s = (unsigned char *) (*p).ui8p;
    const unsigned char * se = (unsigned char *) (pend ? (*pend).ui8p : NULL);
    size_t length = 0;

    switch (type) {
    case RPM_STRING_TYPE:
	if (count != 1)
	    return 0;
	while (*s++ != '\0') {
	    if (se && s > se)
		return 0;
	    length++;
	}
	length++;	/* count nul terminator too. */
	break;
	/* These are like RPM_STRING_TYPE, except they're *always* an array */
	/* Compute sum of length of all strings, including nul terminators */
    case RPM_I18NSTRING_TYPE:
    case RPM_STRING_ARRAY_TYPE:
	if (onDisk) {
	    while (count--) {
		length++;       /* count nul terminator too */
		while (*s++ != '\0') {
		    if (se && s > se)
			return 0;
		    length++;
		}
	    }
	} else {
	    const char ** av = (*p).argv;
	    while (count--) {
		/* add one for null termination */
		length += strlen(*av++) + 1;
	    }
	}
	break;
    default:
	if (typeSizes[type] == -1)
	    return 0;
	length = typeSizes[(type & 0xf)] * count;
	if ((se && (s + length) > se))
	    return 0;
	break;
    }

    return length;
}

#ifdef	NOTYET
/** \ingroup header
 * Swab uint64_t/uint32_t/uint16_t arrays within header region.
 *
 */
static unsigned char * tagSwab(unsigned char * t, const HE_t he, size_t nb)
	/*@modifies *t @*/
{
    uint32_t i;

    switch (he->t) {
    case RPM_UINT64_TYPE:
    {	uint32_t * tt = (uint32_t *)t;
	for (i = 0; i < he->c; i++) {
	    uint32_t j = 2 * i;
	    uint32_t b = (uint32_t) htonl(he->p.ui32p[j]);
	    tt[j  ] = (uint32_t) htonl(he->p.ui32p[j+1]);
	    tt[j+1] = b;
	}
    }   break;
    case RPM_UINT32_TYPE:
    {	uint32_t * tt = (uint32_t *)t;
	for (i = 0; i < he->c; i++)
	    tt[i] = (uint32_t) htonl(he->p.ui32p[i]);
    }   break;
    case RPM_UINT16_TYPE:
    {	uint16_t * tt = (uint16_t *)t;
	for (i = 0; i < he->c; i++)
	    tt[i] = (uint16_t) htons(he->p.ui16p[i]);
    }   break;
    default:
	if ((void *)t != he->p.ptr && nb)
	    memcpy(t, he->p.ptr, nb);
	t += nb;
	break;
    }
    return t;
}
#endif

/**
 * Always realloc HE_t memory.
 * @param he		tag container
 * @return		1 on success, 0 on failure
 */
static int rpmheRealloc(HE_t he)
	/*@modifies he @*/
{
    size_t nb = 0;
    int rc = 1;		/* assume success */

    switch (he->t) {
    default:
assert(0);	/* XXX stop unimplemented oversights. */
	break;
    case RPM_BIN_TYPE:
	he->freeData = 1;	/* XXX RPM_BIN_TYPE is malloc'd */
	/*@fallthrough@*/
    case RPM_UINT8_TYPE:
	nb = he->c * sizeof(*he->p.ui8p);
	break;
    case RPM_UINT16_TYPE:
	nb = he->c * sizeof(*he->p.ui16p);
	break;
    case RPM_UINT32_TYPE:
	nb = he->c * sizeof(*he->p.ui32p);
	break;
    case RPM_UINT64_TYPE:
	nb = he->c * sizeof(*he->p.ui64p);
	break;
    case RPM_STRING_TYPE:
	if (he->p.str)
	    nb = strlen(he->p.str) + 1;
	else
	    rc = 0;
	break;
    case RPM_I18NSTRING_TYPE:
    case RPM_STRING_ARRAY_TYPE:
	break;
    }

    /* Allocate all returned storage (if not already). */
    if (he->p.ptr && nb && !he->freeData) {
	void * ptr = memcpy(xmalloc(nb), he->p.ptr, nb);
	he->p.ptr = ptr;
    }

    if (rc)
	he->freeData = 1;

    return rc;
}

/** \ingroup header
 * Swap uint32_t and uint16_t arrays within header region.
 *
 * This code is way more twisty than I would like.
 *
 * A bug with RPM_I18NSTRING_TYPE in rpm-2.5.x (fixed in August 1998)
 * causes the offset and length of elements in a header region to disagree
 * regarding the total length of the region data.
 *
 * The "fix" is to compute the size using both offset and length and
 * return the larger of the two numbers as the size of the region.
 * Kinda like computing left and right Riemann sums of the data elements
 * to determine the size of a data structure, go figger :-).
 *
 * There's one other twist if a header region tag is in the set to be swabbed,
 * as the data for a header region is located after all other tag data.
 *
 * @param entry		header entry
 * @param il		no. of entries
 * @param dl		start no. bytes of data
 * @param pe		header physical entry pointer (swapped)
 * @param dataStart	header data start
 * @param dataEnd	header data end
 * @param regionid	region offset
 * @return		no. bytes of data in region, 0 on error
 */
/*@-globs@*/	/* XXX rpm_typeAlign usage */
static uint32_t regionSwab(/*@null@*/ indexEntry entry, uint32_t il, uint32_t dl,
		entryInfo pe,
		unsigned char * dataStart,
		/*@null@*/ const unsigned char * dataEnd,
		int32_t regionid)
	/*@modifies *entry, *dataStart @*/
{
    rpmTagData p;
    rpmTagData pend;
    unsigned char * tprev = NULL;
    unsigned char * t = NULL;
    size_t tdel = 0;
    size_t tl = dl;
    struct indexEntry_s ieprev;

    memset(&ieprev, 0, sizeof(ieprev));
    for (; il > 0; il--, pe++) {
	struct indexEntry_s ie;
	rpmTagType type;

	ie.info.tag = (uint32_t) ntohl(pe->tag);
	ie.info.type = (uint32_t) ntohl(pe->type);
	ie.info.count = (uint32_t) ntohl(pe->count);
	ie.info.offset = (int32_t) ntohl(pe->offset);
assert(ie.info.offset >= 0);	/* XXX insurance */

	if (hdrchkType(ie.info.type))
	    return 0;
	if (hdrchkData(ie.info.count))
	    return 0;
	if (hdrchkData(ie.info.offset))
	    return 0;
	if (hdrchkAlign(ie.info.type, ie.info.offset))
	    return 0;

	ie.data = t = dataStart + ie.info.offset;
	if (dataEnd && t >= dataEnd)
	    return 0;

	p.ptr = ie.data;
	pend.ui8p = (uint8_t *) dataEnd;
/*@-nullstate@*/	/* pend.ui8p derived from dataLength may be null */
	ie.length = dataLength(ie.info.type, &p, ie.info.count, 1, &pend);
/*@=nullstate@*/
	if (ie.length == 0 || hdrchkData(ie.length))
	    return 0;

	ie.rdlen = 0;

	if (entry) {
	    ie.info.offset = regionid;
/*@-kepttrans@*/	/* entry->data is kept */
	    *entry = ie;	/* structure assignment */
/*@=kepttrans@*/
	    entry++;
	}

	/* Alignment */
	type = ie.info.type;
	if (typeSizes[type] > 1) {
	    size_t diff = typeSizes[type] - (dl % typeSizes[type]);
	    if ((int)diff != typeSizes[type]) {
		dl += diff;
		if (ieprev.info.type == RPM_I18NSTRING_TYPE)
		    ieprev.length += diff;
	    }
	}
	tdel = (tprev ? (t - tprev) : 0);
	if (ieprev.info.type == RPM_I18NSTRING_TYPE)
	    tdel = ieprev.length;

	if (ie.info.tag >= HEADER_I18NTABLE) {
	    tprev = t;
	} else {
	    tprev = dataStart;
	    /* XXX HEADER_IMAGE tags don't include region sub-tag. */
	    /*@-sizeoftype@*/
	    if (ie.info.tag == HEADER_IMAGE)
		tprev -= REGION_TAG_COUNT;
	    /*@=sizeoftype@*/
	}

	/* Perform endian conversions */
	switch (ntohl(pe->type)) {
	case RPM_UINT64_TYPE:
	{   uint64_t * it = (uint64_t *)t;
	    uint32_t b[2];
	    for (; ie.info.count > 0; ie.info.count--, it += 1) {
		if (dataEnd && ((unsigned char *)it) >= dataEnd)
		    return 0;
		b[1] = (uint32_t) htonl(((uint32_t *)it)[0]);
		b[0] = (uint32_t) htonl(((uint32_t *)it)[1]);
		if (b[1] != ((uint32_t *)it)[0])
		    memcpy(it, b, sizeof(b));
	    }
	    t = (unsigned char *) it;
	}   /*@switchbreak@*/ break;
	case RPM_UINT32_TYPE:
	{   uint32_t * it = (uint32_t *)t;
	    for (; ie.info.count > 0; ie.info.count--, it += 1) {
		if (dataEnd && ((unsigned char *)it) >= dataEnd)
		    return 0;
		*it = (uint32_t) htonl(*it);
	    }
	    t = (unsigned char *) it;
	}   /*@switchbreak@*/ break;
	case RPM_UINT16_TYPE:
	{   uint16_t * it = (uint16_t *) t;
	    for (; ie.info.count > 0; ie.info.count--, it += 1) {
		if (dataEnd && ((unsigned char *)it) >= dataEnd)
		    return 0;
		*it = (uint16_t) htons(*it);
	    }
	    t = (unsigned char *) it;
	}   /*@switchbreak@*/ break;
	default:
	    t += ie.length;
	    /*@switchbreak@*/ break;
	}

	dl += ie.length;
	if (dataEnd && (dataStart + dl) > dataEnd) return 0;
	tl += tdel;
	ieprev = ie;	/* structure assignment */

    }
    tdel = (tprev ? (t - tprev) : 0);
    tl += tdel;

    /* XXX
     * There are two hacks here:
     *	1) tl is 16b (i.e. REGION_TAG_COUNT) short while doing headerReload().
     *	2) the 8/98 rpm bug with inserting i18n tags needs to use tl, not dl.
     */
    /*@-sizeoftype@*/
    if (tl+REGION_TAG_COUNT == dl)
	tl += REGION_TAG_COUNT;
    /*@=sizeoftype@*/

    return dl;
}
/*@=globs@*/

void * headerUnload(Header h, size_t * lenp)
{
    void * sw;
    uint32_t * ei = NULL;
    entryInfo pe;
    unsigned char * dataStart;
    unsigned char * te;
    unsigned pad;
    size_t len = 0;
    uint32_t il = 0;
    uint32_t dl = 0;
    indexEntry entry; 
    rpmTagType type;
    size_t i;
    size_t drlen;
    size_t ndribbles;
    size_t driplen;
    size_t ndrips;
    int legacy = 0;

    if ((sw = headerGetStats(h, 18)) != NULL)	/* RPMTS_OP_HDRLOAD */
	(void) rpmswEnter(sw, 0);

    /* Sort entries by (offset,tag). */
    headerUnsort(h);

    /* Compute (il,dl) for all tags, including those deleted in region. */
    pad = 0;
    drlen = ndribbles = driplen = ndrips = 0;
    for (i = 0, entry = h->index; i < h->indexUsed; i++, entry++) {
	if (ENTRY_IS_REGION(entry)) {
	    uint32_t rdl;
	    uint32_t ril;
	    int32_t rid;

assert(entry->info.offset <= 0);	/* XXX insurance */
	    rdl = -entry->info.offset;	/* negative offset */
	    ril = rdl/sizeof(*pe);
	    rid = entry->info.offset;

	    il += ril;
	    dl += entry->rdlen + entry->info.count;
	    /* XXX Legacy regions do not include the region tag and data. */
	    if (i == 0 && (h->flags & HEADERFLAG_LEGACY))
		il += 1;

	    /* Skip rest of entries in region, but account for dribbles. */
	    for (; i < h->indexUsed && entry->info.offset <= rid+1; i++, entry++) {
		if (entry->info.offset <= rid)
		    /*@innercontinue@*/ continue;

		/* Alignment */
		type = entry->info.type;
		if (typeSizes[type] > 1) {
		    size_t diff = typeSizes[type] - (dl % typeSizes[type]);
		    if ((int)diff != typeSizes[type]) {
			drlen += diff;
			pad += diff;
			dl += diff;
		    }
		}

		ndribbles++;
		il++;
		drlen += entry->length;
		dl += entry->length;
	    }
	    i--;
	    entry--;
	    continue;
	}

	/* Ignore deleted drips. */
	if (entry->data == NULL || entry->length == 0)
	    continue;

	/* Alignment */
	type = entry->info.type;
	if (typeSizes[type] > 1) {
	    size_t diff = typeSizes[type] - (dl % typeSizes[type]);
	    if ((int)diff != typeSizes[type]) {
		driplen += diff;
		pad += diff;
		dl += diff;
	    } else
		diff = 0;
	}

	ndrips++;
	il++;
	driplen += entry->length;
	dl += entry->length;
    }

    /* Sanity checks on header intro. */
    if (hdrchkTags(il) || hdrchkData(dl))
	goto errxit;

    len = sizeof(il) + sizeof(dl) + (il * sizeof(*pe)) + dl;

    ei = xmalloc(len);
    ei[0] = (uint32_t) htonl(il);
    ei[1] = (uint32_t) htonl(dl);

    pe = (entryInfo) &ei[2];
    dataStart = te = (unsigned char *) (pe + il);

    pad = 0;
    for (i = 0, entry = h->index; i < h->indexUsed; i++, entry++) {
	const char * src;
	unsigned char *t;
	uint32_t count;
	uint32_t rdlen;

	if (entry->data == NULL || entry->length == 0)
	    continue;

	t = te;
	pe->tag = (uint32_t) htonl(entry->info.tag);
	pe->type = (uint32_t) htonl(entry->info.type);
	pe->count = (uint32_t) htonl(entry->info.count);

	if (ENTRY_IS_REGION(entry)) {
	    uint32_t rdl;
	    uint32_t ril;
	    int32_t rid;

assert(entry->info.offset <= 0);	/* XXX insurance */

	    rdl = -entry->info.offset;	/* negative offset */
	    ril = rdl/sizeof(*pe) + ndribbles;
	    rid = entry->info.offset;

	    src = (char *)entry->data;
	    rdlen = entry->rdlen;

	    /* XXX Legacy regions do not include the region tag and data. */
	    if (i == 0 && (h->flags & HEADERFLAG_LEGACY)) {
		uint32_t stei[4];

		legacy = 1;
		memcpy(pe+1, src, rdl);
		memcpy(te, src + rdl, rdlen);
		te += rdlen;

		pe->offset = (int32_t) htonl(te - dataStart);
		stei[0] = (uint32_t) pe->tag;
		stei[1] = (uint32_t) pe->type;
		stei[2] = (uint32_t) htonl(-rdl-entry->info.count);
		stei[3] = (uint32_t) pe->count;
		memcpy(te, stei, entry->info.count);
		te += entry->info.count;
		ril++;
		rdlen += entry->info.count;

		count = regionSwab(NULL, ril, 0, pe, t, te, 0);
		if (count != rdlen)
		    goto errxit;

	    } else {

		memcpy(pe+1, src + sizeof(*pe), ((ril-1) * sizeof(*pe)));
		memcpy(te, src + (ril * sizeof(*pe)), rdlen+entry->info.count+drlen);
		te += rdlen;
		{   /*@-castexpose@*/
		    entryInfo se = (entryInfo)src;
		    /*@=castexpose@*/
		    int32_t off = (int32_t) ntohl(se->offset);
		    pe->offset = (int32_t)((off)
			? htonl(te - dataStart) : htonl(off));
		}
		te += entry->info.count + drlen;

		count = regionSwab(NULL, ril, 0, pe, t, te, 0);
		if (count != (rdlen + entry->info.count + drlen))
		    goto errxit;
	    }

	    /* Skip rest of entries in region. */
	    while (i < h->indexUsed && entry->info.offset <= rid+1) {
		i++;
		entry++;
	    }
	    i--;
	    entry--;
	    pe += ril;
	    continue;
	}

	/* Ignore deleted drips. */
	if (entry->data == NULL || entry->length == 0)
	    continue;

	/* Alignment */
	type = entry->info.type;
	if (typeSizes[type] > 1) {
	    size_t diff;
	    diff = typeSizes[type] - ((te - dataStart) % typeSizes[type]);
	    if ((int)diff != typeSizes[type]) {
		memset(te, 0, diff);
		te += diff;
		pad += diff;
	    }
	}

	pe->offset = (int32_t) htonl(te - dataStart);

	/* copy data w/ endian conversions */
	switch (entry->info.type) {
	case RPM_UINT64_TYPE:
	{   uint32_t b[2];
	    count = entry->info.count;
	    src = entry->data;
	    while (count--) {
		b[1] = (uint32_t) htonl(((uint32_t *)src)[0]);
		b[0] = (uint32_t) htonl(((uint32_t *)src)[1]);
		if (b[1] == ((uint32_t *)src)[0])
		    memcpy(te, src, sizeof(b));
		else
		    memcpy(te, b, sizeof(b));
		te += sizeof(b);
		src += sizeof(b);
	    }
	}   /*@switchbreak@*/ break;

	case RPM_UINT32_TYPE:
	    count = entry->info.count;
	    src = entry->data;
	    while (count--) {
		*((uint32_t *)te) = (uint32_t) htonl(*((uint32_t *)src));
		/*@-sizeoftype@*/
		te += sizeof(uint32_t);
		src += sizeof(uint32_t);
		/*@=sizeoftype@*/
	    }
	    /*@switchbreak@*/ break;

	case RPM_UINT16_TYPE:
	    count = entry->info.count;
	    src = entry->data;
	    while (count--) {
		*((uint16_t *)te) = (uint16_t) htons(*((uint16_t *)src));
		/*@-sizeoftype@*/
		te += sizeof(uint16_t);
		src += sizeof(uint16_t);
		/*@=sizeoftype@*/
	    }
	    /*@switchbreak@*/ break;

	default:
	    memcpy(te, entry->data, entry->length);
	    te += entry->length;
	    /*@switchbreak@*/ break;
	}
	pe++;
    }
   
    /* Insure that there are no memcpy underruns/overruns. */
    if (((unsigned char *)pe) != dataStart)
	goto errxit;
    if ((((unsigned char *)ei)+len) != te)
	goto errxit;

    if (lenp)
	*lenp = len;

    h->flags &= ~HEADERFLAG_SORTED;
    headerSort(h);

    if (sw != NULL)	(void) rpmswExit(sw, len);

    return (void *) ei;

errxit:
    if (sw != NULL)	(void) rpmswExit(sw, len);
    /*@-usereleased@*/
    ei = _free(ei);
    /*@=usereleased@*/
    return (void *) ei;
}

/**
 * Find matching (tag,type) entry in header.
 * @param h		header
 * @param tag		entry tag
 * @param type		entry type
 * @return 		header entry
 */
static /*@null@*/
indexEntry findEntry(/*@null@*/ Header h, rpmTag tag, rpmTagType type)
	/*@modifies h @*/
{
    indexEntry entry, entry2, last;
    struct indexEntry_s key;

    if (h == NULL) return NULL;
    if (!(h->flags & HEADERFLAG_SORTED)) headerSort(h);

    key.info.tag = tag;

    entry2 = entry = 
	bsearch(&key, h->index, h->indexUsed, sizeof(*h->index), indexCmp);
    if (entry == NULL)
	return NULL;

    if (type == 0)
	return entry;

    /* look backwards */
    while (entry->info.tag == tag && entry->info.type != type &&
	   entry > h->index) entry--;

    if (entry->info.tag == tag && entry->info.type == type)
	return entry;

    last = h->index + h->indexUsed;
    /*@-usereleased@*/ /* FIX: entry2 = entry. Code looks bogus as well. */
    while (entry2->info.tag == tag && entry2->info.type != type &&
	   entry2 < last) entry2++;
    /*@=usereleased@*/

    if (entry->info.tag == tag && entry->info.type == type)
	return entry;

    return NULL;
}

/** \ingroup header
 * Delete tag in header.
 * Removes all entries of type tag from the header, returns 1 if none were
 * found.
 *
 * @param h		header
 * @param tag		tag
 * @return		0 on success, 1 on failure (INCONSISTENT)
 */
static
int headerRemoveEntry(Header h, rpmTag tag)
	/*@modifies h @*/
{
    indexEntry last = h->index + h->indexUsed;
    indexEntry entry, first;
    int ne;

    entry = findEntry(h, tag, 0);
    if (!entry) return 1;

    /* Make sure entry points to the first occurence of this tag. */
    while (entry > h->index && (entry - 1)->info.tag == tag)  
	entry--;

    /* Free data for tags being removed. */
    for (first = entry; first < last; first++) {
	void * data;
	if (first->info.tag != tag)
	    break;
	data = first->data;
	first->data = NULL;
	first->length = 0;
	if (ENTRY_IN_REGION(first))
	    continue;
	data = _free(data);
    }

    ne = (first - entry);
    if (ne > 0) {
	h->indexUsed -= ne;
	ne = last - first;
	if (ne > 0)
	    memmove(entry, first, (ne * sizeof(*entry)));
    }

    return 0;
}

Header headerLoad(void * uh)
{
    void * sw = NULL;
    uint32_t * ei = (uint32_t *) uh;
    uint32_t il = (uint32_t) ntohl(ei[0]);		/* index length */
    uint32_t dl = (uint32_t) ntohl(ei[1]);		/* data length */
    /*@-sizeoftype@*/
    size_t pvlen = sizeof(il) + sizeof(dl) +
               (il * sizeof(struct entryInfo_s)) + dl;
    /*@=sizeoftype@*/
    void * pv = uh;
    Header h = NULL;
    entryInfo pe;
    unsigned char * dataStart;
    unsigned char * dataEnd;
    indexEntry entry; 
    uint32_t rdlen;
    int i;

    /* Sanity checks on header intro. */
    if (hdrchkTags(il) || hdrchkData(dl))
	goto errxit;

    ei = (uint32_t *) pv;
    /*@-castexpose@*/
    pe = (entryInfo) &ei[2];
    /*@=castexpose@*/
    dataStart = (unsigned char *) (pe + il);
    dataEnd = dataStart + dl;

    h = xcalloc(1, sizeof(*h));
    if ((sw = headerGetStats(h, 18)) != NULL)	/* RPMTS_OP_HDRLOAD */
	(void) rpmswEnter(sw, 0);
    {	unsigned char * hmagic = header_magic;
	(void) memcpy(h->magic, hmagic, sizeof(h->magic));
    }
    /*@-assignexpose -kepttrans@*/
    h->blob = uh;
    /*@=assignexpose =kepttrans@*/
    h->indexAlloced = il + 1;
    h->indexUsed = il;
    h->index = xcalloc(h->indexAlloced, sizeof(*h->index));
    h->flags |= HEADERFLAG_SORTED;
    h->nrefs = 0;
    h->startoff = 0;
    h->endoff = (uint32_t) pvlen;
    h = headerLink(h);

    entry = h->index;
    i = 0;
    if (!(htonl(pe->tag) < HEADER_I18NTABLE)) {
	h->flags |= HEADERFLAG_LEGACY;
	entry->info.type = REGION_TAG_TYPE;
	entry->info.tag = HEADER_IMAGE;
	/*@-sizeoftype@*/
	entry->info.count = REGION_TAG_COUNT;
	/*@=sizeoftype@*/
	entry->info.offset = ((unsigned char *)pe - dataStart); /* negative offset */

	/*@-assignexpose@*/
	entry->data = pe;
	/*@=assignexpose@*/
	entry->length = pvlen - sizeof(il) - sizeof(dl);
	rdlen = regionSwab(entry+1, il, 0, pe, dataStart, dataEnd, entry->info.offset);
#if 0	/* XXX don't check, the 8/98 i18n bug fails here. */
	if (rdlen != dl)
	    goto errxit;
#endif
	entry->rdlen = rdlen;
	entry++;
	h->indexUsed++;
    } else {
	uint32_t rdl;
	uint32_t ril;

	h->flags &= ~HEADERFLAG_LEGACY;

	entry->info.type = (uint32_t) htonl(pe->type);
	entry->info.count = (uint32_t) htonl(pe->count);

	if (hdrchkType(entry->info.type))
	    goto errxit;
	if (hdrchkTags(entry->info.count))
	    goto errxit;

	{   int32_t off = (int32_t) ntohl(pe->offset);

	    if (hdrchkData(off))
		goto errxit;
	    if (off) {
/*@-sizeoftype@*/
		size_t nb = REGION_TAG_COUNT;
/*@=sizeoftype@*/
		uint32_t * stei = memcpy(alloca(nb), dataStart + off, nb);
		rdl = -ntohl(stei[2]);	/* negative offset */
assert((int32_t)rdl >= 0);	/* XXX insurance */
		ril = rdl/sizeof(*pe);
		if (hdrchkTags(ril) || hdrchkData(rdl))
		    goto errxit;
		entry->info.tag = (uint32_t) htonl(pe->tag);
	    } else {
		ril = il;
		/*@-sizeoftype@*/
		rdl = (ril * sizeof(struct entryInfo_s));
		/*@=sizeoftype@*/
		entry->info.tag = HEADER_IMAGE;
	    }
	}
	entry->info.offset = (int32_t) -rdl;	/* negative offset */

	/*@-assignexpose@*/
	entry->data = pe;
	/*@=assignexpose@*/
	entry->length = pvlen - sizeof(il) - sizeof(dl);
	rdlen = regionSwab(entry+1, (ril-1), 0, pe+1, dataStart, dataEnd, entry->info.offset);
	if (rdlen == 0)
	    goto errxit;
	entry->rdlen = rdlen;

	if (ril < h->indexUsed) {
	    indexEntry newEntry = entry + ril;
	    size_t ne = (h->indexUsed - ril);
	    int32_t rid = entry->info.offset+1;
	    uint32_t rc;

	    /* Load dribble entries from region. */
	    rc = regionSwab(newEntry, ne, 0, pe+ril, dataStart, dataEnd, rid);
	    if (rc == 0)
		goto errxit;
	    rdlen += rc;

	  { indexEntry firstEntry = newEntry;
	    size_t save = h->indexUsed;
	    size_t j;

	    /* Dribble entries replace duplicate region entries. */
	    h->indexUsed -= ne;
	    for (j = 0; j < ne; j++, newEntry++) {
		(void) headerRemoveEntry(h, newEntry->info.tag);
		if (newEntry->info.tag == HEADER_BASENAMES)
		    (void) headerRemoveEntry(h, HEADER_OLDFILENAMES);
	    }

	    /* If any duplicate entries were replaced, move new entries down. */
	    if (h->indexUsed < (save - ne)) {
		memmove(h->index + h->indexUsed, firstEntry,
			(ne * sizeof(*entry)));
	    }
	    h->indexUsed += ne;
	  }
	}
    }

    h->flags &= ~HEADERFLAG_SORTED;
    headerSort(h);

    if (sw != NULL)	(void) rpmswExit(sw, pvlen);

    /*@-globstate -observertrans @*/
    return h;
    /*@=globstate =observertrans @*/

errxit:
    if (sw != NULL)	(void) rpmswExit(sw, pvlen);
    /*@-usereleased@*/
    if (h) {
	h->index = _free(h->index);
	/*@-refcounttrans@*/
	h = _free(h);
	/*@=refcounttrans@*/
    }
    /*@=usereleased@*/
    /*@-refcounttrans -globstate@*/
    return h;
    /*@=refcounttrans =globstate@*/
}

int headerGetMagic(Header h, unsigned char ** magicp, size_t * nmagicp)
{
    unsigned char * hmagic = header_magic;
    if (magicp)
	*magicp = (h ? h->magic : hmagic);
    if (nmagicp)
	*nmagicp = (h ? sizeof(h->magic) : sizeof(header_magic));
    return 0;
}

int headerSetMagic(Header h, unsigned char * magic, size_t nmagic)
{
    if (nmagic > sizeof(h->magic))
	nmagic = sizeof(h->magic);
    if (h) {
	memset(h->magic, 0, sizeof(h->magic));
	if (nmagic > 0)
	    memmove(h->magic, magic, nmagic);
    }
    return 0;
}

const char * headerGetOrigin(Header h)
{
    return (h != NULL ? h->origin : NULL);
}

int headerSetOrigin(Header h, const char * origin)
{
    if (h != NULL) {
	h->origin = _free(h->origin);
	h->origin = xstrdup(origin);
    }
    return 0;
}

const char * headerGetBaseURL(Header h)
{
    return (h != NULL ? h->baseurl : NULL);
}

int headerSetBaseURL(Header h, const char * baseurl)
{
    if (h != NULL) {
	h->baseurl = _free(h->baseurl);
	h->baseurl = xstrdup(baseurl);
    }
    return 0;
}

struct stat * headerGetStatbuf(Header h)
{
    return &h->sb;
}

int headerSetStatbuf(Header h, struct stat * st)
{
    if (h != NULL && st != NULL)
	memcpy(&h->sb, st, sizeof(h->sb));
    return 0;
}

const char * headerGetDigest(Header h)
{
    return (h != NULL ? h->digest : NULL);
}

int headerSetDigest(Header h, const char * digest)
{
    if (h != NULL) {
	h->digest = _free(h->digest);
	h->digest = xstrdup(digest);
    }
    return 0;
}

uint32_t headerGetInstance(Header h)
{
    return (h != NULL ? h->instance : 0);
}

uint32_t headerSetInstance(Header h, uint32_t instance)
{
    if (h != NULL)
	h->instance = instance;
    return 0;
}

uint32_t headerGetStartOff(Header h)
{
    return (h != NULL ? h->startoff : 0);
}

uint32_t headerSetStartOff(Header h, uint32_t startoff)
{
    if (h != NULL)
	h->startoff = startoff;
    return 0;
}

uint32_t headerGetEndOff(Header h)
{
    return (h != NULL ? h->endoff : 0);
}

uint32_t headerSetEndOff(Header h, uint32_t endoff)
{
    if (h != NULL)
	h->endoff = endoff;
    return 0;
}

Header headerReload(Header h, int tag)
{
    Header nh;
    void * uh;
    const char * origin = (h->origin != NULL ? xstrdup(h->origin) : NULL);
    const char * baseurl = (h->baseurl != NULL ? xstrdup(h->baseurl) : NULL);
    const char * digest = (h->digest != NULL ? xstrdup(h->digest) : NULL);
    struct stat sb = h->sb;	/* structure assignment */
    uint32_t instance = h->instance;
    int xx;

/*@-onlytrans@*/
    uh = headerUnload(h, NULL);
    h = headerFree(h);
/*@=onlytrans@*/
    if (uh == NULL)
	return NULL;
    nh = headerLoad(uh);
    if (nh == NULL) {
	uh = _free(uh);
	return NULL;
    }
    if (nh->flags & HEADERFLAG_ALLOCATED)
	uh = _free(uh);
    nh->flags |= HEADERFLAG_ALLOCATED;
    if (ENTRY_IS_REGION(nh->index)) {
	if (tag == HEADER_SIGNATURES || tag == HEADER_IMMUTABLE)
	    nh->index[0].info.tag = tag;
    }
    if (origin != NULL) {
	xx = headerSetOrigin(nh, origin);
	origin = _free(origin);
    }
    if (baseurl != NULL) {
	xx = headerSetBaseURL(nh, baseurl);
	baseurl = _free(baseurl);
    }
    if (digest != NULL) {
	xx = headerSetDigest(nh, digest);
	digest = _free(digest);
    }
    nh->sb = sb;	/* structure assignment */
    xx = (int) headerSetInstance(nh, instance);
    return nh;
}

Header headerCopyLoad(const void * uh)
{
    uint32_t * ei = (uint32_t *) uh;
    uint32_t il = (uint32_t) ntohl(ei[0]);		/* index length */
    uint32_t dl = (uint32_t) ntohl(ei[1]);		/* data length */
    /*@-sizeoftype@*/
    size_t pvlen = sizeof(il) + sizeof(dl) +
			(il * sizeof(struct entryInfo_s)) + dl;
    /*@=sizeoftype@*/
    void * nuh = NULL;
    Header h = NULL;

    /* Sanity checks on header intro. */
    if (!(hdrchkTags(il) || hdrchkData(dl)) && pvlen < headerMaxbytes) {
	nuh = memcpy(xmalloc(pvlen), uh, pvlen);
	if ((h = headerLoad(nuh)) != NULL)
	    h->flags |= HEADERFLAG_ALLOCATED;
    }
    if (h == NULL)
	nuh = _free(nuh);
    return h;
}

int headerIsEntry(Header h, rpmTag tag)
{
    /*@-mods@*/		/*@ FIX: h modified by sort. */
    return (findEntry(h, tag, 0) ? 1 : 0);
    /*@=mods@*/	
}

/** \ingroup header
 * Retrieve data from header entry.
 * @todo Permit retrieval of regions other than HEADER_IMUTABLE.
 * @param entry		header entry
 * @retval *he		tag container
 * @param minMem	string pointers refer to header memory?
 * @return		1 on success, otherwise error.
 */
static int copyEntry(const indexEntry entry, HE_t he, int minMem)
	/*@modifies he @*/
{
    rpmTagCount count = entry->info.count;
    uint32_t rdlen;
    int rc = 1;		/* XXX 1 on success. */

    switch (entry->info.type) {
    case RPM_BIN_TYPE:
	/*
	 * XXX This only works for
	 * XXX 	"sealed" HEADER_IMMUTABLE/HEADER_SIGNATURES/HEADER_IMAGE.
	 * XXX This will *not* work for unsealed legacy HEADER_IMAGE (i.e.
	 * XXX a legacy header freshly read, but not yet unloaded to the rpmdb).
	 */
	if (ENTRY_IS_REGION(entry)) {
	    uint32_t * ei = ((uint32_t *)entry->data) - 2;
	    /*@-castexpose@*/
	    entryInfo pe = (entryInfo) (ei + 2);
	    /*@=castexpose@*/
	    unsigned char * dataStart = (unsigned char *) (pe + ntohl(ei[0]));
	    unsigned char * dataEnd;
	    uint32_t rdl;
	    uint32_t ril;

assert(entry->info.offset <= 0);		/* XXX insurance */
	    rdl = -entry->info.offset;	/* negative offset */
	    ril = rdl/sizeof(*pe);
	    /*@-sizeoftype@*/
	    rdl = entry->rdlen;
	    count = 2 * sizeof(*ei) + (ril * sizeof(*pe)) + rdl;
	    if (entry->info.tag == HEADER_IMAGE) {
		ril -= 1;
		pe += 1;
	    } else {
		count += REGION_TAG_COUNT;
		rdl += REGION_TAG_COUNT;
	    }

	    he->p.ui32p = ei = xmalloc(count);
	    ei[0] = htonl(ril);
	    ei[1] = htonl(rdl);

	    /*@-castexpose@*/
	    pe = (entryInfo) memcpy(ei + 2, pe, (ril * sizeof(*pe)));
	    /*@=castexpose@*/

	    dataStart = (unsigned char *) memcpy(pe + ril, dataStart, rdl);
	    dataEnd = dataStart + rdl;
	    /*@=sizeoftype@*/

	    rdlen = regionSwab(NULL, ril, 0, pe, dataStart, dataEnd, 0);
	    /* XXX 1 on success. */
	    rc = (rdlen == 0) ? 0 : 1;
	    if (rc == 0)
		he->p.ptr = _free(he->p.ptr);
	} else {
	    count = entry->length;
	    he->p.ptr = (!minMem
		? memcpy(xmalloc(count), entry->data, count)
		: entry->data);
	}
	break;
    case RPM_STRING_TYPE:
	if (count == 1) {
	    he->p.str = entry->data;
	    break;
	}
	/*@fallthrough@*/
    case RPM_I18NSTRING_TYPE:
    case RPM_STRING_ARRAY_TYPE:
    {	const char ** argv;
	size_t nb = count * sizeof(*argv);
	char * t;
	unsigned i;

	/*@-mods@*/
	if (minMem) {
	    he->p.argv = argv = xmalloc(nb);
	    t = entry->data;
	} else {
	    he->p.argv = argv = xmalloc(nb + entry->length);
	    t = (char *) &argv[count];
	    memcpy(t, entry->data, entry->length);
	}
	/*@=mods@*/
	for (i = 0; i < (unsigned) count; i++) {
	    argv[i] = t;
	    t = strchr(t, 0);
	    t++;
	}
    }	break;

    default:
	he->p.ptr = entry->data;
	break;
    }
    he->t = entry->info.type;
    he->c = count;
    return rc;
}

/**
 * Does locale match entry in header i18n table?
 * 
 * \verbatim
 * The range [l,le) contains the next locale to match:
 *    ll[_CC][.EEEEE][@dddd]
 * where
 *    ll	ISO language code (in lowercase).
 *    CC	(optional) ISO coutnry code (in uppercase).
 *    EEEEE	(optional) encoding (not really standardized).
 *    dddd	(optional) dialect.
 * \endverbatim
 *
 * @param td		header i18n table data, NUL terminated
 * @param l		start of locale	to match
 * @param le		end of locale to match
 * @return		1 on match, 0 on no match
 */
static int headerMatchLocale(const char *td, const char *l, const char *le)
	/*@*/
{
    const char *fe;


#if 0
  { const char *s, *ll, *CC, *EE, *dd;
    char *lbuf, *t.

    /* Copy the buffer and parse out components on the fly. */
    lbuf = alloca(le - l + 1);
    for (s = l, ll = t = lbuf; *s; s++, t++) {
	switch (*s) {
	case '_':
	    *t = '\0';
	    CC = t + 1;
	    break;
	case '.':
	    *t = '\0';
	    EE = t + 1;
	    break;
	case '@':
	    *t = '\0';
	    dd = t + 1;
	    break;
	default:
	    *t = *s;
	    break;
	}
    }

    if (ll)	/* ISO language should be lower case */
	for (t = ll; *t; t++)	*t = tolower(*t);
    if (CC)	/* ISO country code should be upper case */
	for (t = CC; *t; t++)	*t = toupper(*t);

    /* There are a total of 16 cases to attempt to match. */
  }
#endif

    /* First try a complete match. */
    if (strlen(td) == (size_t)(le - l) && !strncmp(td, l, (size_t)(le - l)))
	return 1;

    /* Next, try stripping optional dialect and matching.  */
    for (fe = l; fe < le && *fe != '@'; fe++)
	{};
    if (fe < le && !strncmp(td, l, (fe - l)))
	return 1;

    /* Next, try stripping optional codeset and matching.  */
    for (fe = l; fe < le && *fe != '.'; fe++)
	{};
    if (fe < le && !strncmp(td, l, (fe - l)))
	return 1;

    /* Finally, try stripping optional country code and matching. */
    for (fe = l; fe < le && *fe != '_'; fe++)
	{};
    if (fe < le && !strncmp(td, l, (fe - l)))
	return 2;

    return 0;
}

/**
 * Return i18n string from header that matches locale.
 * @param h		header
 * @param entry		i18n string data
 * @return		matching i18n string (or 1st string if no match)
 */
/*@dependent@*/ /*@exposed@*/ static char *
headerFindI18NString(Header h, indexEntry entry)
	/*@*/
{
    const char *lang, *l, *le;
    indexEntry table;

    /* XXX Drepper sez' this is the order. */
    if ((lang = getenv("LANGUAGE")) == NULL &&
	(lang = getenv("LC_ALL")) == NULL &&
	(lang = getenv("LC_MESSAGES")) == NULL &&
	(lang = getenv("LANG")) == NULL)
	    return entry->data;
    
    /*@-mods@*/
    if ((table = findEntry(h, HEADER_I18NTABLE, RPM_STRING_ARRAY_TYPE)) == NULL)
	return entry->data;
    /*@=mods@*/

    for (l = lang; *l != '\0'; l = le) {
	const char *td;
	char *ed, *ed_weak = NULL;
	uint32_t langNum;

	while (*l && *l == ':')			/* skip leading colons */
	    l++;
	if (*l == '\0')
	    break;
	for (le = l; *le && *le != ':'; le++)	/* find end of this locale */
	    {};

	/* For each entry in the header ... */
	for (langNum = 0, td = table->data, ed = entry->data;
	     langNum < entry->info.count;
	     langNum++, td += strlen(td) + 1, ed += strlen(ed) + 1)
	{
		int match = headerMatchLocale(td, l, le);
		if (match == 1) return ed;
		else if (match == 2) ed_weak = ed;
	}
	if (ed_weak) return ed_weak;
    }

    return entry->data;
}

/**
 * Retrieve tag data from header.
 * @param h		header
 * @param he		tag container
 * @param flags		headerGet flags
 * @return		1 on success, 0 on not found
 */
static int intGetEntry(Header h, HE_t he, int flags)
	/*@modifies he @*/
{
    int minMem = 0;
    indexEntry entry;
    int rc;

    /* First find the tag */
/*@-mods@*/		/*@ FIX: h modified by sort. */
    entry = findEntry(h, he->tag, 0);
/*@=mods@*/
    if (entry == NULL) {
	he->t = 0;
	he->p.ptr = NULL;
	he->c = 0;
	return 0;
    }

    switch (entry->info.type) {
    case RPM_I18NSTRING_TYPE:
	if (!(flags & HEADERGET_NOI18NSTRING)) {
	    rc = 1;
	    he->t = RPM_STRING_TYPE;
	    he->c = 1;
/*@-dependenttrans@*/
	    he->p.str = headerFindI18NString(h, entry);
/*@=dependenttrans@*/
	    break;
	}
	/*@fallthrough@*/
    default:
	rc = copyEntry(entry, he, minMem);
	break;
    }

    /* XXX 1 on success */
    return ((rc == 1) ? 1 : 0);
}

/**
 */
static void copyData(rpmTagType type, rpmTagData * dest, rpmTagData * src,
		rpmTagCount cnt, size_t len)
	/*@modifies *dest @*/
{
    switch (type) {
    case RPM_I18NSTRING_TYPE:
    case RPM_STRING_ARRAY_TYPE:
    {	const char ** av = (*src).argv;
	char * t = (char *) (*dest).str;

	while (cnt-- > 0 && len > 0) {
	    const char * s;
	    if ((s = *av++) == NULL)
		continue;
	    do {
		*t++ = *s++;
	    } while (s[-1] && --len > 0);
	}
    }	break;
    default:
	memmove((*dest).ptr, (*src).ptr, len);
	break;
    }
}

/**
 * Return (malloc'ed) copy of entry data.
 * @param type		entry data type
 * @param *p		tag container data
 * @param c		entry item count
 * @retval *lenp	no. bytes in returned data
 * @return 		(malloc'ed) copy of entry data, NULL on error
 */
/*@null@*/
static void *
grabData(rpmTagType type, rpmTagData * p, rpmTagCount c, /*@out@*/size_t * lenp)
	/*@modifies *lenp @*/
	/*@requires maxSet(lenp) >= 0 @*/
{
    rpmTagData data = { .ptr = NULL };
    size_t length;

    length = dataLength(type, p, c, 0, NULL);
    if (length > 0) {
	data.ptr = xmalloc(length);
	copyData(type, &data, p, c, length);
    }

    if (lenp)
	*lenp = length;
    return data.ptr;
}

/** \ingroup header
 * Add tag to header.
 * Duplicate tags are okay, but only defined for iteration (with the
 * exceptions noted below). While you are allowed to add i18n string
 * arrays through this function, you probably don't mean to. See
 * headerAddI18NString() instead.
 *
 * @param h		header
 * @param he		tag container
 * @return		1 on success, 0 on failure
 */
static
int headerAddEntry(Header h, HE_t he)
	/*@modifies h @*/
{
    indexEntry entry;
    rpmTagData q = { .ptr = he->p.ptr };
    rpmTagData data;
    size_t length;

    /* Count must always be >= 1 for headerAddEntry. */
    if (he->c == 0)
	return 0;

    if (hdrchkType(he->t))
	return 0;
    if (hdrchkData(he->c))
	return 0;

    length = 0;
    data.ptr = grabData(he->t, &q, he->c, &length);
    if (data.ptr == NULL || length == 0)
	return 0;

    /* Allocate more index space if necessary */
    if (h->indexUsed == h->indexAlloced) {
	h->indexAlloced += INDEX_MALLOC_SIZE;
	h->index = xrealloc(h->index, h->indexAlloced * sizeof(*h->index));
    }

    /* Fill in the index */
    entry = h->index + h->indexUsed;
    entry->info.tag = he->tag;
    entry->info.type = he->t;
    entry->info.count = he->c;
    entry->info.offset = 0;
    entry->data = data.ptr;
    entry->length = length;

    if (h->indexUsed > 0 && he->tag < h->index[h->indexUsed-1].info.tag)
	h->flags &= ~HEADERFLAG_SORTED;
    h->indexUsed++;

    return 1;
}

/** \ingroup header
 * Append element to tag array in header.
 * Appends item p to entry w/ tag and type as passed. Won't work on
 * RPM_STRING_TYPE.
 *
 * @param h		header
 * @param he		tag container
 * @return		1 on success, 0 on failure
 */
static
int headerAppendEntry(Header h, HE_t he)
	/*@modifies h @*/
{
    rpmTagData src = { .ptr = he->p.ptr };
    rpmTagData dest = { .ptr = NULL };
    indexEntry entry;
    size_t length;

    if (he->t == RPM_STRING_TYPE || he->t == RPM_I18NSTRING_TYPE) {
	/* we can't do this */
	return 0;
    }

    /* Find the tag entry in the header. */
    entry = findEntry(h, he->tag, he->t);
    if (!entry)
	return 0;

    length = dataLength(he->t, &src, he->c, 0, NULL);
    if (length == 0)
	return 0;

    if (ENTRY_IN_REGION(entry)) {
	char * t = xmalloc(entry->length + length);
	memcpy(t, entry->data, entry->length);
	entry->data = t;
	entry->info.offset = 0;
    } else
	entry->data = xrealloc(entry->data, entry->length + length);

    dest.ptr = ((char *) entry->data) + entry->length;
    copyData(he->t, &dest, &src, he->c, length);

    entry->length += length;

    entry->info.count += he->c;

    return 1;
}

/** \ingroup header
 * Add or append element to tag array in header.
 * @param h		header
 * @param he		tag container
 * @return		1 on success, 0 on failure
 */
static
int headerAddOrAppendEntry(Header h, HE_t he)
	/*@modifies h @*/
{
    return (findEntry(h, he->tag, he->t)
	? headerAppendEntry(h, he)
	: headerAddEntry(h, he));
}

int headerAddI18NString(Header h, rpmTag tag, const char * string,
		const char * lang)
{
    HE_t he = memset(alloca(sizeof(*he)), 0, sizeof(*he));
    indexEntry table, entry;
    rpmTagData p;
    size_t length;
    size_t ghosts;
    uint32_t i;
    uint32_t langNum;
    char * buf;
    int xx;

    table = findEntry(h, HEADER_I18NTABLE, RPM_STRING_ARRAY_TYPE);
    entry = findEntry(h, tag, RPM_I18NSTRING_TYPE);

    if (!table && entry)
	return 0;		/* this shouldn't ever happen!! */

    if (!table && !entry) {
	const char * argv[2];
	int count = 0;
	p.argv = argv;
	if (!lang || (lang[0] == 'C' && lang[1] == '\0')) {
	    /*@-observertrans -readonlytrans@*/
	    p.argv[count++] = "C";
	    /*@=observertrans =readonlytrans@*/
	} else {
	    /*@-observertrans -readonlytrans@*/
	    p.argv[count++] = "C";
	    /*@=observertrans =readonlytrans@*/
	    p.argv[count++] = lang;
	}
	he->tag = HEADER_I18NTABLE;
	he->t = RPM_STRING_ARRAY_TYPE;
	he->p.ptr = p.ptr;
	he->c = count;
	xx = headerAddEntry(h, he);
	if (!xx)
	    return 0;
	table = findEntry(h, he->tag, he->t);
    }

    if (!table)
	return 0;
    if (!lang) lang = "C";

    {	const char * l = table->data;
	for (langNum = 0; langNum < table->info.count; langNum++) {
	    if (!strcmp(l, lang)) break;
	    l += strlen(l) + 1;
	}
    }

    if (langNum >= table->info.count) {
	length = strlen(lang) + 1;
	if (ENTRY_IN_REGION(table)) {
	    char * t = xmalloc(table->length + length);
	    memcpy(t, table->data, table->length);
	    table->data = t;
	    table->info.offset = 0;
	} else
	    table->data = xrealloc(table->data, table->length + length);
	memmove(((char *)table->data) + table->length, lang, length);
	table->length += length;
	table->info.count++;
    }

    if (!entry) {
	p.argv = alloca(sizeof(*p.argv) * (langNum + 1));
/*@-observertrans -readonlytrans@*/
	for (i = 0; i < langNum; i++)
	    p.argv[i] = "";
/*@=observertrans =readonlytrans@*/
	p.argv[langNum] = string;
	he->tag = tag;
	he->t = RPM_I18NSTRING_TYPE;
	he->p.ptr = p.ptr;
	he->c = langNum + 1;
/*@-compmempass@*/
	xx = headerAddEntry(h, he);
/*@=compmempass@*/
	return xx;
    } else if (langNum >= entry->info.count) {
	ghosts = langNum - entry->info.count;
	
	length = strlen(string) + 1 + ghosts;
	if (ENTRY_IN_REGION(entry)) {
	    char * t = xmalloc(entry->length + length);
	    memcpy(t, entry->data, entry->length);
	    entry->data = t;
	    entry->info.offset = 0;
	} else
	    entry->data = xrealloc(entry->data, entry->length + length);

	memset(((char *)entry->data) + entry->length, 0, ghosts);
	memmove(((char *)entry->data) + entry->length + ghosts, string, strlen(string)+1);

	entry->length += length;
	entry->info.count = langNum + 1;
    } else {
	char *b, *be, *e, *ee, *t;
	size_t bn, sn, en;

	/* Set beginning/end pointers to previous data */
	b = be = e = ee = entry->data;
	for (i = 0; i < table->info.count; i++) {
	    if (i == langNum)
		be = ee;
	    ee += strlen(ee) + 1;
	    if (i == langNum)
		e  = ee;
	}

	/* Get storage for new buffer */
	bn = (be-b);
	sn = strlen(string) + 1;
	en = (ee-e);
	length = bn + sn + en;
	t = buf = xmalloc(length);

	/* Copy values into new storage */
	memcpy(t, b, bn);
	t += bn;
/*@-mayaliasunique@*/
	memcpy(t, string, sn);
	t += sn;
	memcpy(t, e, en);
	t += en;
/*@=mayaliasunique@*/

	/* Replace i18N string array */
	entry->length -= strlen(be) + 1;
	entry->length += sn;
	
	if (ENTRY_IN_REGION(entry)) {
	    entry->info.offset = 0;
	} else
	    entry->data = _free(entry->data);
	/*@-dependenttrans@*/
	entry->data = buf;
	/*@=dependenttrans@*/
    }

    return 0;
}

/** \ingroup header
 * Modify tag in header.
 * If there are multiple entries with this tag, the first one gets replaced.
 * @param h		header
 * @param he		tag container
 * @return		1 on success, 0 on failure
 */
static
int headerModifyEntry(Header h, HE_t he)
	/*@modifies h @*/
{
    indexEntry entry;
    rpmTagData q = { .ptr = he->p.ptr };
    rpmTagData oldData;
    rpmTagData newData;
    size_t length;

    /* First find the tag */
    entry = findEntry(h, he->tag, he->t);
    if (!entry)
	return 0;

    length = 0;
    newData.ptr = grabData(he->t, &q, he->c, &length);
    if (newData.ptr == NULL || length == 0)
	return 0;

    /* make sure entry points to the first occurence of this tag */
    while (entry > h->index && (entry - 1)->info.tag == he->tag)  
	entry--;

    /* free after we've grabbed the new data in case the two are intertwined;
       that's a bad idea but at least we won't break */
    oldData.ptr = entry->data;

    entry->info.count = he->c;
    entry->info.type = he->t;
    entry->data = newData.ptr;
    entry->length = length;

    if (ENTRY_IN_REGION(entry)) {
	entry->info.offset = 0;
    } else
	oldData.ptr = _free(oldData.ptr);

    return 1;
}

/**
 * Header tag iterator data structure.
 */
struct headerIterator_s {
    Header h;		/*!< Header being iterated. */
    size_t next_index;	/*!< Next tag index. */
};

HeaderIterator headerFini(/*@only@*/ HeaderIterator hi)
{
    if (hi != NULL) {
	hi->h = headerFree(hi->h);
	hi = _free(hi);
    }
    return hi;
}

HeaderIterator headerInit(Header h)
{
    HeaderIterator hi = xmalloc(sizeof(*hi));

    headerSort(h);

    hi->h = headerLink(h);
    hi->next_index = 0;
    return hi;
}

int headerNext(HeaderIterator hi, HE_t he, /*@unused@*/ unsigned int flags)
{
    void * sw;
    Header h = hi->h;
    size_t slot = hi->next_index;
    indexEntry entry = NULL;
    int rc;

    /* Insure that *he is reliably initialized. */
    memset(he, 0, sizeof(*he));

    for (slot = hi->next_index; slot < h->indexUsed; slot++) {
	entry = h->index + slot;
	if (!ENTRY_IS_REGION(entry))
	    break;
    }
    hi->next_index = slot;
    if (entry == NULL || slot >= h->indexUsed)
	return 0;

    hi->next_index++;

    if ((sw = headerGetStats(h, 19)) != NULL)	/* RPMTS_OP_HDRGET */
	(void) rpmswEnter(sw, 0);

    he->tag = entry->info.tag;
    rc = copyEntry(entry, he, 0);
    if (rc)
	rc = rpmheRealloc(he);

    if (sw != NULL)	(void) rpmswExit(sw, 0);

    /* XXX 1 on success */
    return ((rc == 1) ? 1 : 0);
}

Header headerCopy(Header h)
{
    HE_t he = memset(alloca(sizeof(*he)), 0, sizeof(*he));
    Header nh = headerNew();
    HeaderIterator hi;
   
    for (hi = headerInit(h);
	headerNext(hi, he, 0);
	he->p.ptr = _free(he->p.ptr))
    {
	if (he->p.ptr) (void) headerAddEntry(nh, he);
    }
    hi = headerFini(hi);

    return headerReload(nh, HEADER_IMAGE);
}

void headerCopyTags(Header headerFrom, Header headerTo, rpmTag * tagstocopy)
{
    HE_t he = memset(alloca(sizeof(*he)), 0, sizeof(*he));
    rpmTag * tagno;
    int xx;

    if (headerFrom == headerTo)
	return;

    for (tagno = tagstocopy; *tagno != 0; tagno++) {
	if (headerIsEntry(headerTo, *tagno))
	    continue;
	he->tag = *tagno;
	if (!headerGet(headerFrom, he, 0))
	    continue;
	xx = headerPut(headerTo, he, 0);
	he->p.ptr = _free(he->p.ptr);
    }
}

int headerGet(Header h, HE_t he, unsigned int flags)
{
    void * sw;
    const char * name;
    headerSprintfExtension exts = headerCompoundFormats;
    headerSprintfExtension ext = NULL;
    int extNum;
    int rc;

    if (h == NULL || he == NULL)	return 0;	/* XXX this is nutty. */

    /* Insure that *he is reliably initialized. */
    {	rpmTag tag = he->tag;
	memset(he, 0, sizeof(*he));
	he->tag = tag;
    }
    name = tagName(he->tag);

    if ((sw = headerGetStats(h, 19)) != NULL)	/* RPMTS_OP_HDRGET */
	(void) rpmswEnter(sw, 0);

    /* Search extensions for specific tag override. */
    if (!(flags & HEADERGET_NOEXTENSION))
    for (ext = exts, extNum = 0; ext != NULL && ext->type != HEADER_EXT_LAST;
	ext = (ext->type == HEADER_EXT_MORE ? *ext->u.more : ext+1), extNum++)
    {
	if (ext->name == NULL || ext->type != HEADER_EXT_TAG)
	    continue;
	if (!xstrcasecmp(ext->name + (sizeof("RPMTAG_")-1), name))
	    break;
    }

    if (ext && ext->name != NULL && ext->type == HEADER_EXT_TAG) {
	rc = ext->u.tagFunction(h, he);
	rc = (rc == 0);		/* XXX invert extension return. */
    } else
	rc = intGetEntry(h, he, flags);

    if (rc)
	rc = rpmheRealloc(he);

    if (sw != NULL)	(void) rpmswExit(sw, 0);

#if defined(SUPPORT_IMPLICIT_TAG_DATA_TYPES)
/*@-modfilesys@*/
    /* XXX verify that explicit and implicit types are identical. */
    if (rc)
	tagTypeValidate(he);
/*@=modfilesys@*/
#endif

/*@-modfilesys@*/
    if (!((rc == 0 && he->freeData == 0 && he->p.ptr == NULL) ||
	  (rc == 1 && he->freeData == 1 && he->p.ptr != NULL)))
    {
if (_hdr_debug)
fprintf(stderr, "==> %s(%u) %u %p[%u] free %u rc %d\n", name, (unsigned) he->tag, (unsigned) he->t, he->p.ptr, (unsigned) he->c, he->freeData, rc);
    }
/*@=modfilesys@*/

    return rc;
}

int headerPut(Header h, HE_t he, /*@unused@*/ unsigned int flags)
{
    int rc;

#if defined(SUPPORT_IMPLICIT_TAG_DATA_TYPES)
/*@-modfilesys@*/
    /* XXX verify that explicit and implicit types are identical. */
    tagTypeValidate(he);
/*@=modfilesys@*/
#endif

    if (he->append)
	rc = headerAddOrAppendEntry(h, he);
    else
	rc = headerAddEntry(h, he);

    return rc;
}

int headerDel(Header h, HE_t he, /*@unused@*/ unsigned int flags)
	/*@modifies h @*/
{
    return headerRemoveEntry(h, he->tag);
}

int headerMod(Header h, HE_t he, /*@unused@*/ unsigned int flags)
	/*@modifies h @*/
{

#if defined(SUPPORT_IMPLICIT_TAG_DATA_TYPES)
/*@-modfilesys@*/
    /* XXX verify that explicit and implicit types are identical. */
    tagTypeValidate(he);
/*@=modfilesys@*/
#endif

    return headerModifyEntry(h, he);
}
