/* RPM - Copyright (C) 1995 Red Hat Software
 *
 * header.c - routines for managing rpm headers
 */

/* 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 "config.h"
#include "miscfn.h"

#include <zlib.h>

#if HAVE_ALLOCA_H
# include <alloca.h>
#endif

#include <stdlib.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <netinet/in.h>

#include "header.h"
#include "intl.h"
#include "tread.h"

#define INDEX_MALLOC_SIZE 8

#define PARSER_BEGIN 	0
#define PARSER_IN_ARRAY 1
#define PARSER_IN_EXPR  2

static unsigned char header_magic[4] = { 0x8e, 0xad, 0xe8, 0x01 };

/* handy -- this tells us alignments for defined elements as well */
static int typeSizes[] =  { 
	/* RPM_NULL_TYPE */		-1,
	/* RPM_CHAR_TYPE */		1,
	/* RPM_INT8_TYPE */		1,
	/* RPM_INT16_TYPE */		2,
	/* RPM_INT32_TYPE */		4,
	/* RPM_INT64_TYPE */		-1,
	/* RPM_STRING_TYPE */		-1,
	/* RPM_BIN_TYPE */		1,
	/* RPM_STRING_ARRAY_TYPE */	-1,
	/* RPM_I18NSTRING_TYPE */	-1 };

struct headerToken {
    struct indexEntry *index;
    int indexUsed;
    int indexAlloced;

    int sorted;  
    int langNum;
};

struct entryInfo {
    int_32 tag;
    int_32 type;
    int_32 offset;		/* Offset from beginning of data segment,
				   only defined on disk */
    int_32 count;
};

struct indexEntry {
    struct entryInfo info;
    void * data; 
    int length;			/* Computable, but why bother? */
};

struct sprintfTag {
    /* if NULL tag element is invalid */
    headerTagTagFunction ext;   
    int extNum;
    int_32 tag;
    int justOne;
    int arrayCount;
    char * format;
    char * type;
    int pad;
};

struct extensionCache {
    int_32 type, count;
    int avail, freeit;
    void * data;
};

struct sprintfToken {
    enum { PTOK_NONE = 0, PTOK_TAG, PTOK_ARRAY, PTOK_STRING, PTOK_COND } type;
    union {
	struct {
	    struct sprintfToken * format;
	    int numTokens;
	} array;
	struct sprintfTag tag;
	struct {
	    char * string;
	    int len;
	} string;
	struct {
	    struct sprintfToken * ifFormat;
	    int numIfTokens;
	    struct sprintfToken * elseFormat;
	    int numElseTokens;
	    struct sprintfTag tag;
	} cond;
    } u;
};

static int indexCmp(const void *ap, const void *bp);
static void *doHeaderUnload(Header h, int * lengthPtr);
static struct indexEntry *findEntry(Header h, int_32 tag, int_32 type);
static void * grabData(int_32 type, void * p, int_32 c, int * lengthPtr);
static int dataLength(int_32 type, void * p, int_32 count, int onDisk);
static void copyEntry(struct indexEntry * entry, 
			int_32 *type, void **p, int_32 *c);
static void freeFormat(struct sprintfToken * format, int num);
static void findTag(char * name, const struct headerTagTableEntry * tags, 
		    const struct headerSprintfExtension * extensions,
		    const struct headerTagTableEntry ** tagMatch,
		    const struct headerSprintfExtension ** extMatch);
static int parseExpression(struct sprintfToken * token, char * str, 
		           const struct headerTagTableEntry * tags, 
		           const struct headerSprintfExtension * extensions,
		           char ** endPtr, char ** error);
static int parseFormat(char * format, const struct headerTagTableEntry * tags,
		       const struct headerSprintfExtension * extentions,
		       struct sprintfToken ** formatPtr, int * numTokensPtr,
		       char ** endPtr, int state, char ** error);
static char * singleSprintf(Header h, struct sprintfToken * token,
			    const struct headerSprintfExtension * extensions,
			    struct extensionCache * extCache, int element);
static char escapedChar(const char ch);
static char * formatValue(struct sprintfTag * tag, Header h, 
			  const struct headerSprintfExtension * extensions,
			  struct extensionCache * extcache, int element);
static char * octalFormat(int_32 type, const void * data, 
		          char * formatPrefix, int padding, int element);
static char * dateFormat(int_32 type, const void * data, 
		          char * formatPrefix, int padding, int element);
static char * dayFormat(int_32 type, const void * data, 
		          char * formatPrefix, int padding, int element);
static char * shescapeFormat(int_32 type, const void * data, 
		          char * formatPrefix, int padding, int element);
static int getExtension(Header h, headerTagTagFunction fn, int_32 * typeptr,
			void ** data, int_32 * countptr, 
			struct extensionCache * ext);

const struct headerSprintfExtension headerDefaultFormats[] = {
    { HEADER_EXT_FORMAT, "octal", { octalFormat } },
    { HEADER_EXT_FORMAT, "date", { dateFormat } },
    { HEADER_EXT_FORMAT, "day", { dayFormat } },
    { HEADER_EXT_FORMAT, "shescape", { shescapeFormat } },
    { HEADER_EXT_LAST, NULL, { NULL } }
};


/********************************************************************/
/*                                                                  */
/* Header iteration and copying                                     */
/*                                                                  */
/********************************************************************/

struct headerIteratorS {
    Header h;
    int next_index;
};

HeaderIterator headerInitIterator(Header h)
{
    HeaderIterator hi = malloc(sizeof(struct headerIteratorS));

    headerSort(h);

    hi->h = h;
    hi->next_index = 0;
    return hi;
}

void headerFreeIterator(HeaderIterator iter)
{
    free(iter);
}

int headerNextIterator(HeaderIterator iter,
		 int_32 *tag, int_32 *type, void **p, int_32 *c)
{
    Header h = iter->h;
    int slot = iter->next_index;

    if (slot == h->indexUsed) {
	return 0;
    }
    iter->next_index++;

    if (tag) {
	*tag = h->index[slot].info.tag;
    }
    copyEntry(h->index + slot, type, p, c);

    return 1;
}

static int indexCmp(const void *ap, const void *bp)
{
    int_32 a, b;

    a = ((struct indexEntry *)ap)->info.tag;
    b = ((struct indexEntry *)bp)->info.tag;
    
    if (a > b) {
	return 1;
    } else if (a < b) {
	return -1;
    } else {
	return 0;
    }
}

void headerSort(Header h) {
    if (!h->sorted) {
	qsort(h->index, h->indexUsed, sizeof(struct indexEntry), indexCmp);
	h->sorted = 1;
    }
}

Header headerCopy(Header h)
{
    int_32 tag, type, count;
    void *ptr;
    HeaderIterator headerIter;
    Header res = headerNew();
   
    /* Sort the index -- not really necessary but some old apps may depend
       on this and it certainly won't hurt anything */
    headerSort(h);
    headerIter = headerInitIterator(h);

    while (headerNextIterator(headerIter, &tag, &type, &ptr, &count)) {
	headerAddEntry(res, tag, type, ptr, count);
	if (type == RPM_STRING_ARRAY_TYPE || 
	    type == RPM_I18NSTRING_TYPE) free(ptr);
    }

    res->sorted = 1;

    headerFreeIterator(headerIter);

    return res;
}

/********************************************************************/
/*                                                                  */
/* Reading and writing headers                                      */
/*                                                                  */
/********************************************************************/

void headerWrite(int fd, Header h, int magicp)
{
    void * p;
    int length;
    int_32 l;

    p = doHeaderUnload(h, &length);

    if (magicp) {
	write(fd, header_magic, sizeof(header_magic));
	l = htonl(0);
	write(fd, &l, sizeof(l));
    }
    
    write(fd, p, length);

    free(p);
}

Header headerRead(int fd, int magicp)
{
    int_32 reserved;
    int_32 * p;
    int_32 il, dl;
    int_32 magic;
    Header h;
    void * block;
    int totalSize;

    if (magicp == HEADER_MAGIC_YES) {
	if (timedRead(fd, &magic, sizeof(magic)) != sizeof(magic))
	    return NULL;
	if (memcmp(&magic, header_magic, sizeof(magic))) {
	    return NULL;
	}

	if (timedRead(fd, &reserved, sizeof(reserved)) != sizeof(reserved))
	    return NULL;
    }
    
    /* First read the index length (count of index entries) */
    if (timedRead(fd, &il, sizeof(il)) != sizeof(il)) 
	return NULL;

    il = ntohl(il);

    /* Then read the data length (number of bytes) */
    if (timedRead(fd, &dl, sizeof(dl)) != sizeof(dl)) 
	return NULL;

    dl = ntohl(dl);

    totalSize = sizeof(int_32) + sizeof(int_32) + 
		(il * sizeof(struct entryInfo)) + dl;

    block = p = malloc(totalSize);
    *p++ = htonl(il);
    *p++ = htonl(dl);

    totalSize -= sizeof(int_32) + sizeof(int_32);
    if (timedRead(fd, p, totalSize) != totalSize)
	return NULL;
    
    h = headerLoad(block);

    free(block);

    return h;
}

void headerGzWrite(gzFile fd, Header h, int magicp)
{
    void * p;
    int length;
    int_32 l;

    p = doHeaderUnload(h, &length);

    if (magicp) {
	gzwrite(fd, header_magic, sizeof(header_magic));
	l = htonl(0);
	gzwrite(fd, &l, sizeof(l));
    }
    
    gzwrite(fd, p, length);

    free(p);
}

Header headerGzRead(gzFile fd, int magicp)
{
    int_32 reserved;
    int_32 * p;
    int_32 il, dl;
    int_32 magic;
    Header h;
    void * block;
    int totalSize;

    if (magicp == HEADER_MAGIC_YES) {
	if (gzread(fd, &magic, sizeof(magic)) != sizeof(magic))
	    return NULL;
	if (memcmp(&magic, header_magic, sizeof(magic))) {
	    return NULL;
	}

	if (gzread(fd, &reserved, sizeof(reserved)) != sizeof(reserved))
	    return NULL;
    }
    
    /* First read the index length (count of index entries) */
    if (gzread(fd, &il, sizeof(il)) != sizeof(il)) 
	return NULL;

    il = ntohl(il);

    /* Then read the data length (number of bytes) */
    if (gzread(fd, &dl, sizeof(dl)) != sizeof(dl)) 
	return NULL;

    dl = ntohl(dl);

    totalSize = sizeof(int_32) + sizeof(int_32) + 
		(il * sizeof(struct entryInfo)) + dl;

    block = p = malloc(totalSize);
    *p++ = htonl(il);
    *p++ = htonl(dl);

    totalSize -= sizeof(int_32) + sizeof(int_32);
    if (gzread(fd, p, totalSize) != totalSize)
	return NULL;
    
    h = headerLoad(block);

    free(block);

    return h;
}

/********************************************************************/
/*                                                                  */
/* Header loading and unloading                                     */
/*                                                                  */
/********************************************************************/

Header headerLoad(void *pv)
{
    int_32 il;			/* index length, data length */
    char *p = pv;
    char * dataStart;
    struct entryInfo * pe;
    struct indexEntry * entry; 
    struct headerToken *h = malloc(sizeof(struct headerToken));
    char * src, * dst;
    int i;
    int count;

    il = ntohl(*((int_32 *) p));
    p += sizeof(int_32);

    /* we can skip the data length -- we only store this to allow reading
       from disk */
    p += sizeof(int_32);

    h->indexAlloced = il;
    h->indexUsed = il;
    h->index = malloc(il * sizeof(struct indexEntry));

    /* This assumes you only headerLoad() something you headerUnload()-ed */
    h->sorted = 1;
    h->langNum = -1;

    pe = (struct entryInfo *) p;
    dataStart = (char *) (pe + h->indexUsed);

    for (i = 0, entry = h->index; i < h->indexUsed; i++, entry++, pe++) {
	entry->info.type = htonl(pe->type);
	entry->info.tag = htonl(pe->tag);
	entry->info.count = htonl(pe->count);
	entry->info.offset = -1;

	src = dataStart + htonl(pe->offset);
	entry->length = dataLength(entry->info.type, src, 
				   entry->info.count, 1);
	entry->data = dst = malloc(entry->length);

	/* copy data w/ endian conversions */
	switch (entry->info.type) {
	  case RPM_INT32_TYPE:
	    count = entry->info.count;
	    while (count--) {
		*((int_32 *)dst) = htonl(*((int_32 *)src));
		src += sizeof(int_32);
		dst += sizeof(int_32);
	    }
	    break;

	  case RPM_INT16_TYPE:
	    count = entry->info.count;
	    while (count--) {
		*((int_16 *)dst) = htons(*((int_16 *)src));
		src += sizeof(int_16);
		dst += sizeof(int_16);
	    }
	    break;

	  default:
	    memcpy(dst, src, entry->length);
	}
    }

    return h;
}

static void *doHeaderUnload(Header h, int * lengthPtr)
{
    int i;
    int type, diff;
    void *p;
    int_32 *pi;
    struct entryInfo * pe;
    struct indexEntry * entry; 
    char * chptr, * src, * dataStart;
    int count;

    headerSort(h);

    *lengthPtr = headerSizeof(h, 0);
    pi = p = malloc(*lengthPtr);

    *pi++ = htonl(h->indexUsed);

    /* data length */
    *pi++ = htonl(*lengthPtr - sizeof(int_32) - sizeof(int_32) -
		(sizeof(struct entryInfo) * h->indexUsed));

    pe = (struct entryInfo *) pi;
    dataStart = chptr = (char *) (pe + h->indexUsed);

    for (i = 0, entry = h->index; i < h->indexUsed; i++, entry++, pe++) {
	pe->type = htonl(entry->info.type);
	pe->tag = htonl(entry->info.tag);
	pe->count = htonl(entry->info.count);

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

	pe->offset = htonl(chptr - dataStart);

	/* copy data w/ endian conversions */
	switch (entry->info.type) {
	  case RPM_INT32_TYPE:
	    count = entry->info.count;
	    src = entry->data;
	    while (count--) {
		*((int_32 *)chptr) = htonl(*((int_32 *)src));
		chptr += sizeof(int_32);
		src += sizeof(int_32);
	    }
	    break;

	  case RPM_INT16_TYPE:
	    count = entry->info.count;
	    src = entry->data;
	    while (count--) {
		*((int_16 *)chptr) = htons(*((int_16 *)src));
		chptr += sizeof(int_16);
		src += sizeof(int_16);
	    }
	    break;

	  default:
	    memcpy(chptr, entry->data, entry->length);
	    chptr += entry->length;
	}
    }
   
    return p;
}

void *headerUnload(Header h) {
    int length;

    return doHeaderUnload(h, &length);
}

/********************************************************************/
/*                                                                  */
/* Header dumping                                                   */
/*                                                                  */
/********************************************************************/

void headerDump(Header h, FILE *f, int flags, 
		const struct headerTagTableEntry * tags) {
    int i, c, ct;
    struct indexEntry *p;
    const struct headerTagTableEntry * tage;
    char *dp;
    char ch;
    char *type, *tag;

    /* First write out the length of the index (count of index entries) */
    fprintf(f, "Entry count: %d\n", h->indexUsed);

    /* Now write the index */
    p = h->index;
    fprintf(f, "\n             CT  TAG                  TYPE         "
		"      OFSET      COUNT\n");
    for (i = 0; i < h->indexUsed; i++) {
	switch (p->info.type) {
	    case RPM_NULL_TYPE:   		type = "NULL_TYPE"; 	break;
	    case RPM_CHAR_TYPE:   		type = "CHAR_TYPE"; 	break;
	    case RPM_BIN_TYPE:   		type = "BIN_TYPE"; 	break;
	    case RPM_INT8_TYPE:   		type = "INT8_TYPE"; 	break;
	    case RPM_INT16_TYPE:  		type = "INT16_TYPE"; 	break;
	    case RPM_INT32_TYPE:  		type = "INT32_TYPE"; 	break;
	    /*case RPM_INT64_TYPE:  		type = "INT64_TYPE"; 	break;*/
	    case RPM_STRING_TYPE: 	    	type = "STRING_TYPE"; 	break;
	    case RPM_STRING_ARRAY_TYPE: 	type = "STRING_ARRAY_TYPE"; break;
	    case RPM_I18NSTRING_TYPE:	 	type = "I18N_STRING_TYPE"; break;
	    default:		    	type = "(unknown)";	break;
	}

	tage = tags;
	while (tage->name && tage->val != p->info.tag) tage++;

	if (!tage->name)
	    tag = "(unknown)";
	else
	    tag = tage->name;

	fprintf(f, "Entry      : %.3d (%d)%-14s %-18s 0x%.8x %.8d\n", i,
		p->info.tag, tag, type, (uint_32) p->info.offset, (uint_32) 
		p->info.count);

	if (flags & HEADER_DUMP_INLINE) {
	    /* Print the data inline */
	    dp = p->data;
	    c = p->info.count;
	    ct = 0;
	    switch (p->info.type) {
	    case RPM_INT32_TYPE:
		while (c--) {
		    fprintf(f, "       Data: %.3d 0x%08x (%d)\n", ct++,
			    (uint_32) *((int_32 *) dp),
			    (uint_32) *((int_32 *) dp));
		    dp += sizeof(int_32);
		}
		break;

	    case RPM_INT16_TYPE:
		while (c--) {
		    fprintf(f, "       Data: %.3d 0x%04x (%d)\n", ct++,
			    (short int) *((int_16 *) dp),
			    (short int) *((int_16 *) dp));
		    dp += sizeof(int_16);
		}
		break;
	    case RPM_INT8_TYPE:
		while (c--) {
		    fprintf(f, "       Data: %.3d 0x%02x (%d)\n", ct++,
			    (char) *((int_8 *) dp),
			    (char) *((int_8 *) dp));
		    dp += sizeof(int_8);
		}
		break;
	    case RPM_BIN_TYPE:
	      while (c > 0) {
		  fprintf(f, "       Data: %.3d ", ct);
		  while (c--) {
		      fprintf(f, "%02x ", (unsigned char) *(int_8 *)dp);
		      ct++;
		      dp += sizeof(int_8);
		      if (! (ct % 8)) {
			  break;
		      }
		  }
		  fprintf(f, "\n");
	      }
		break;
	    case RPM_CHAR_TYPE:
		while (c--) {
		    ch = (char) *((char *) dp);
		    fprintf(f, "       Data: %.3d 0x%2x %c (%d)\n", ct++,
			    ch,
			    (isprint(ch) ? ch : ' '),
			    (char) *((char *) dp));
		    dp += sizeof(char);
		}
		break;
	    case RPM_STRING_TYPE:
	    case RPM_STRING_ARRAY_TYPE:
	    case RPM_I18NSTRING_TYPE:
		while (c--) {
		    fprintf(f, "       Data: %.3d %s\n", ct++, (char *) dp);
		    dp = strchr(dp, 0);
		    dp++;
		}
		break;
	    default:
		fprintf(stderr, "Data type %d not supprted\n", 
			(int) p->info.type);
		exit(1);
	    }
	}
	p++;
    }
}

/********************************************************************/
/*                                                                  */
/* Entry lookup                                                     */
/*                                                                  */
/********************************************************************/

static struct indexEntry *findEntry(Header h, int_32 tag, int_32 type)
{
    struct indexEntry * entry, * entry2, * last;
    struct indexEntry key;

    if (!h->sorted) headerSort(h);

    key.info.tag = tag;

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

    if (type == RPM_NULL_TYPE) 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;
    while (entry2->info.tag == tag && entry2->info.type != type &&
	   entry2 < last) entry2++;

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

    return NULL;
}

int headerIsEntry(Header h, int_32 tag)
{
    return (findEntry(h, tag, RPM_NULL_TYPE) ? 1 : 0);
}

static void copyEntry(struct indexEntry * entry, 
			int_32 *type, void **p, int_32 *c) {
    int i, tableSize;
    char ** ptrEntry;
    char * chptr;

    if (type) 
	*type = entry->info.type;
    if (c) 
	*c = entry->info.count;
    if (!p)
	return;

    /* Now look it up */
    switch (entry->info.type) {
      case RPM_STRING_TYPE:
	if (entry->info.count == 1) {
	    *p = entry->data;
	    break;
	}
	/* fallthrough */
      case RPM_STRING_ARRAY_TYPE:
      case RPM_I18NSTRING_TYPE:
	i = entry->info.count;
	tableSize = i * sizeof(char *);
	ptrEntry = *p = malloc(tableSize + entry->length);
	chptr = ((char *) *p) + tableSize;
	memcpy(chptr, entry->data, entry->length);
	while (i--) {
	    *ptrEntry++ = chptr;
	    chptr = strchr(chptr, 0);
	    chptr++;
	}
	break;

      default:
	*p = entry->data;
    }
}

int headerGetRawEntry(Header h, int_32 tag, int_32 *type, void **p, int_32 *c) {
    struct indexEntry * entry;

    if (!p) return headerIsEntry(h, tag);

    /* First find the tag */
    entry = findEntry(h, tag, RPM_NULL_TYPE);
    if (!entry) {
	*p = NULL;
	return 0;
    }

    copyEntry(entry, type, p, c);

    return 1;
}

int headerGetEntry(Header h, int_32 tag, int_32 * type, void **p, int_32 * c)
{
    struct indexEntry * entry;
    char * chptr;
    int i;

    if (!p) return headerIsEntry(h, tag);

    /* First find the tag */
    entry = findEntry(h, tag, RPM_NULL_TYPE);
    if (!entry) {
	*p = NULL;
	return 0;
    }

    if (entry->info.type == RPM_I18NSTRING_TYPE) {
	if (h->langNum == -1) headerResetLang(h);

	if (type) *type = RPM_STRING_TYPE;
	if (c) *c = 1;

	chptr = entry->data;
	for (i = 0; i < h->langNum; i++) 
	    chptr += strlen(chptr) + 1;

	*p = chptr;
    } else {
	copyEntry(entry, type, p, c);
    }

    return 1;
}

/********************************************************************/
/*                                                                  */
/* Header creation and deletion                                     */
/*                                                                  */
/********************************************************************/

Header headerNew()
{
    Header h = malloc(sizeof(struct headerToken));

    h->index = malloc(INDEX_MALLOC_SIZE * sizeof(struct indexEntry));
    h->indexAlloced = INDEX_MALLOC_SIZE;
    h->indexUsed = 0;

    h->sorted = 0;
    h->langNum = -1;

    return (Header) h;
}

void headerFree(Header h)
{
    int i;

    for (i = 0; i < h->indexUsed; i++)
	free(h->index[i].data);

    free(h->index);
    free(h);
}

unsigned int headerSizeof(Header h, int magicp)
{
    unsigned int size;
    int i, diff;
    int_32 type;

    headerSort(h);

    size = sizeof(int_32);	/* count of index entries */
    size += sizeof(int_32);	/* length of data */
    size += sizeof(struct entryInfo) * h->indexUsed;
    if (magicp) {
	size += 8;
    }

    for (i = 0; i < h->indexUsed; i++) {
	/* Alignment */
	type = h->index[i].info.type;
	if (typeSizes[type] > 1) {
	    diff = typeSizes[type] - (size % typeSizes[type]);
	    if (diff != typeSizes[type]) {
		size += diff;
	    }
	}

	size += h->index[i].length;
    }
   
    return size;
}

static int dataLength(int_32 type, void * p, int_32 count, int onDisk) {
    int thisLen, length, i;
    char ** src, * chptr;

    switch (type) {
      case RPM_STRING_TYPE:
	if (count == 1) {
	    /* Special case -- p is just the string */
	    length = strlen(p) + 1;
	    break;
	}
        /* This should not be allowed */
	fprintf(stderr, "grabData() RPM_STRING_TYPE count must be 1.\n");
	exit(1);

      case RPM_STRING_ARRAY_TYPE:
      case RPM_I18NSTRING_TYPE:
	/* This is like RPM_STRING_TYPE, except it's *always* an array */
	/* Compute sum of length of all strings, including null terminators */
	i = count;
	length = 0;

	if (onDisk) {
	    chptr = (char *) p;
	    while (i--) {
		thisLen = strlen(chptr) + 1;
		length += thisLen;
		chptr += thisLen;
	    }
	} else {
	    src = (char **) p;
	    while (i--) {
		/* add one for null termination */
		length += strlen(*src++) + 1;
	    }
	}
	break;

      default:
	if (typeSizes[type] != -1)
	    length = typeSizes[type] * count;
	else {
	    fprintf(stderr, "Data type %d not supported\n", (int) type);
	    exit(1);
	}
    }

    return length;
}

static void copyData(int_32 type, void * dstPtr, void * srcPtr, int_32 c, 
			int dataLength) {
    char ** src, * dst;
    int i, len;

    switch (type) {
      case RPM_STRING_ARRAY_TYPE:
      case RPM_I18NSTRING_TYPE:
	/* Otherwise, p is char** */
	i = c;
	src = (char **) srcPtr;
	dst = dstPtr;
	while (i--) {
	    len = *src ? strlen(*src) + 1 : 0;
	    memcpy(dst, *src, len);
	    dst += len;
	    src++;
	}
	break;

      default:
	memcpy(dstPtr, srcPtr, dataLength);
    }
}

static void * grabData(int_32 type, void * p, int_32 c, int * lengthPtr) {
    int length;
    void * data;

    length = dataLength(type, p, c, 0);
    data = malloc(length);

    copyData(type, data, p, c, length);

    *lengthPtr = length;
    return data;
}

/********************************************************************/
/*                                                                  */
/* Adding and modifying entries                                     */
/*                                                                  */
/********************************************************************/

int headerAddEntry(Header h, int_32 tag, int_32 type, void *p, int_32 c)
{
    struct indexEntry *entry;

    h->sorted = 0;

    if (c <= 0) {
	fprintf(stderr, "Bad count for headerAddEntry(): %d\n", (int) c);
	exit(1);
    }

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

    /* Fill in the index */
    entry = h->index + h->indexUsed++;
    entry->info.tag = tag;
    entry->info.type = type;
    entry->info.count = c;
    entry->info.offset = -1;

    entry->data = grabData(type, p, c, &entry->length);

    h->sorted = 0;

    return 1;
}

int headerAddI18NString(Header h, int_32 tag, char * string, char * lang) {
    struct indexEntry * table, * entry;
    char * charArray[2];
    char * chptr;
    char ** strArray;
    int length;
    int ghosts;
    int i, langNum;
    char * buf;

    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) {
	if (!lang || (lang[0] == 'C' && lang[1] == '\0')) {
	    charArray[0] = "C";
	    if (!headerAddEntry(h, HEADER_I18NTABLE, RPM_STRING_ARRAY_TYPE, 
				&charArray, 1)) return 0;
	} else {
	    charArray[0] = "C";
	    charArray[1] = lang;
	    if (!headerAddEntry(h, HEADER_I18NTABLE, RPM_STRING_ARRAY_TYPE, 
				&charArray, 2)) return 0;
	}
	table = findEntry(h, HEADER_I18NTABLE, RPM_STRING_ARRAY_TYPE);
    }

    if (!lang) lang = "C";

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

    if (langNum >= table->info.count) {
	length = strlen(lang) + 1;
	table->data = realloc(table->data, table->length + length);
	memcpy(((char *)table->data) + table->length, lang, length);
	table->length += length;
	table->info.count++;
    }

    if (!entry) {
	strArray = alloca(sizeof(*strArray) * (langNum + 1));
	for (i = 0; i < langNum; i++)
	    strArray[i] = "";
	strArray[langNum] = string;
	return headerAddEntry(h, tag, RPM_I18NSTRING_TYPE, strArray, 
				langNum + 1);
    } else if (langNum >= entry->info.count) {
	ghosts = langNum - entry->info.count;
	
	length = strlen(string) + 1 + ghosts;
	entry->data = realloc(entry->data, entry->length + length);

	memset(((char *)entry->data) + entry->length, '\0', ghosts);
	strcpy(((char *)entry->data) + entry->length + ghosts, string);

	entry->length += length;
	entry->info.count = langNum + 1;
    } else {
	/* tricky... we need to add one to the middle (or beginning). This
	   will replace an existing entry as well */
	buf = malloc(strlen(string) + entry->length);

	chptr = entry->data, length = 0;
	for (i = 0; i < langNum; i++) 
	    length += strlen(chptr + length) + 1;

	memcpy(buf, entry->data, length);
	strcpy(buf + length, string);
	i = strlen(chptr + length) + 1;
	memcpy(buf + strlen(string) + 1, ((char *) entry->data) + length + i, 
		entry->length - length - i);

	free(entry->data);
	entry->data = buf;
	entry->length += length;
    }

    return 0;
}

/* if there are multiple entries with this tag, the first one gets replaced */
int headerModifyEntry(Header h, int_32 tag, int_32 type, void *p, int_32 c)
{
    struct indexEntry *entry;
    void * oldData;

    /* First find the tag */
    entry = findEntry(h, tag, type);
    if (!entry) {
	return 0;
    }

    /* make sure entry points to the first occurence of this tag */
    while (entry > h->index && (entry - 1)->info.tag == 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 = entry->data;

    entry->info.count = c;
    entry->info.type = type;
    entry->data = grabData(type, p, c, &entry->length);

    free(oldData);
    
    return 1;
}

int headerAddOrAppendEntry(Header h, int_32 tag, int_32 type,
			   void * p, int_32 c)
{
    if (findEntry(h, tag, type)) {
	return headerAppendEntry(h, tag, type, p, c);
    } else {
	return headerAddEntry(h, tag, type, p, c);
    }
}

int headerAppendEntry(Header h, int_32 tag, int_32 type, void * p, int_32 c) {
    struct indexEntry *entry;
    int length;

    /* First find the tag */
    entry = findEntry(h, tag, type);
    if (!entry) {
	return 0;
    }

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

    length = dataLength(type, p, c, 0);

    entry->data = realloc(entry->data, entry->length + length);
    copyData(type, ((char *) entry->data) + entry->length, p, c, length);

    entry->length += length;

    entry->info.count += c;

    return 0;
}

int headerRemoveEntry(Header h, int_32 tag) {
    struct indexEntry * entry, * last;

    entry = findEntry(h, tag, RPM_NULL_TYPE);
    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--;

    /* We might be better off just counting the number of items off the
       end and issuing one big memcpy, but memcpy() doesn't have to work
       on overlapping regions thanks to ANSI <sigh>. A alloca() and two
       memcpy() would probably still be a win (as our moving from the
       end to the middle isn't very nice to the qsort() we'll have to
       do to make up for this!), but I'm too lazy to implement it. Just
       remember that this repeating this is basically nlogn thanks to this
       dumb implementation (but n is the best we'd do anyway) */

    last = h->index + h->indexUsed;
    while (entry->info.tag == tag && entry < last) {
	free(entry->data);
	*(entry++) = *(--last);
    }
    h->indexUsed = last - h->index;

    h->sorted = 0;

    return 0;
}

static char escapedChar(const char ch) {
    switch (ch) {
      case 'a': 	return '\a';
      case 'b': 	return '\b';
      case 'f': 	return '\f';
      case 'n': 	return '\n';
      case 'r': 	return '\r';
      case 't': 	return '\t';
      case 'v': 	return '\v';

      default:		return ch;
    }
}

static void freeFormat(struct sprintfToken * format, int num) {
    int i;

    for (i = 0; i < num; i++) {
	if (format[i].type == PTOK_ARRAY) 
	    freeFormat(format[i].u.array.format, format[i].u.array.numTokens);
	if (format[i].type == PTOK_COND) { 
	    freeFormat(format[i].u.cond.ifFormat, 
			format[i].u.cond.numIfTokens);
	    freeFormat(format[i].u.cond.elseFormat, 
			format[i].u.cond.numElseTokens);
	}
    }
    free(format);
}

static void findTag(char * name, const struct headerTagTableEntry * tags, 
		    const struct headerSprintfExtension * extensions,
		    const struct headerTagTableEntry ** tagMatch,
		    const struct headerSprintfExtension ** extMatch) {
    const struct headerTagTableEntry * entry;
    const struct headerSprintfExtension * ext;
    char * tagname;
    int i;

    *tagMatch = NULL;
    *extMatch = NULL;

    if (strncmp("RPMTAG_", name, 7)) {
	tagname = alloca(strlen(name) + 10);
	strcpy(tagname, "RPMTAG_");
	strcat(tagname, name);
    } else {
	tagname = name;
    }

    for (entry = tags; entry->name; entry++)
	if (!strcasecmp(entry->name, tagname)) break;

    if (entry->name) {
	*tagMatch = entry;
    } else {
	ext = extensions, i =0;
	while (ext->type != HEADER_EXT_LAST) {
	    if (ext->type == HEADER_EXT_TAG && 
		!strcasecmp(ext->name, tagname)) {
		break;
	    }

	    if (ext->type == HEADER_EXT_MORE)
		ext = ext->u.more;
	    else
		ext++;
	    i++;
	}

	if (ext->type == HEADER_EXT_TAG) {
	    *extMatch = ext;
	}
    }
}

static int parseExpression(struct sprintfToken * token, char * str, 
		           const struct headerTagTableEntry * tags, 
		           const struct headerSprintfExtension * extensions,
		           char ** endPtr, char ** error) {
    char * chptr, * end;
    const struct headerTagTableEntry * tag;
    const struct headerSprintfExtension * ext;

    chptr = str;
    while (*chptr && *chptr != '?') chptr++;

    if (*chptr != '?') {
	*error = _("? expected in expression");
	return 1;
    }

    *chptr++ = '\0';;

    if (*chptr != '{') {
	*error = _("{ exected after ? in expression");
	return 1;
    }

    chptr++;

    if (parseFormat(chptr, tags, extensions, &token->u.cond.ifFormat, 
		    &token->u.cond.numIfTokens, &end, PARSER_IN_EXPR, error)) 
	return 1;
    if (!*end) {
	*error = _("} expected in expression");
	freeFormat(token->u.cond.ifFormat, token->u.cond.numIfTokens);
	return 1;
    }

    chptr = end;
    if (*chptr != ':' && *chptr != '|') {
	*error = _(": expected following ? subexpression");
	freeFormat(token->u.cond.ifFormat, token->u.cond.numIfTokens);
	return 1;
    }

    if (*chptr == '|') {
	parseFormat(strdup(""), tags, extensions, &token->u.cond.elseFormat, 
			&token->u.cond.numElseTokens, &end, PARSER_IN_EXPR, 
			error);
    } else {
	chptr++;

	if (*chptr != '{') {
	    *error = _("{ exected after : in expression");
	    freeFormat(token->u.cond.ifFormat, token->u.cond.numIfTokens);
	    return 1;
	}

	chptr++;

	if (parseFormat(chptr, tags, extensions, &token->u.cond.elseFormat, 
			&token->u.cond.numElseTokens, &end, PARSER_IN_EXPR, 
			error)) 
	    return 1;
	if (!*end) {
	    *error = _("} expected in expression");
	    freeFormat(token->u.cond.ifFormat, token->u.cond.numIfTokens);
	    return 1;
	}

	chptr = end;
	if (*chptr != '|') {
	    *error = _("| expected at end of expression");
	    freeFormat(token->u.cond.ifFormat, token->u.cond.numIfTokens);
	    freeFormat(token->u.cond.elseFormat, token->u.cond.numElseTokens);
	    return 1;
	}
    }
	
    chptr++;

    *endPtr = chptr;

    findTag(str, tags, extensions, &tag, &ext);

    if (tag) {
	token->u.cond.tag.ext = NULL;
	token->u.cond.tag.tag = tag->val;
    } else if (ext) {
	token->u.cond.tag.ext = ext->u.tagFunction;
	token->u.cond.tag.extNum = ext - extensions;
    } else {
	token->u.cond.tag.ext = NULL;
	token->u.cond.tag.tag = -1;
    }
	
    token->type = PTOK_COND;

    return 0;
}

static int parseFormat(char * str, const struct headerTagTableEntry * tags,
		       const struct headerSprintfExtension * extensions,
		       struct sprintfToken ** formatPtr, int * numTokensPtr,
		       char ** endPtr, int state, char ** error) {
    char * chptr, * start, * next, * dst;
    struct sprintfToken * format;
    int numTokens;
    int currToken;
    const struct headerTagTableEntry * entry;
    const struct headerSprintfExtension * ext;
    int i;
    int done = 0;

    /* upper limit on number of individual formats */
    numTokens = 0;
    for (chptr = str; *chptr; chptr++)
	if (*chptr == '%') numTokens++;
    numTokens = numTokens * 2 + 1;

    format = calloc(sizeof(*format), numTokens);
    if (endPtr) *endPtr = NULL;

    dst = start = str;
    currToken = -1;
    while (*start && !done) {
	switch (*start) {
	  case '%':
	    /* handle %% */
	    if (*(start + 1) == '%') {
		if (currToken < 0 || format[currToken].type != PTOK_STRING) {
		    currToken++;
		    format[currToken].type = PTOK_STRING;
		    dst = format[currToken].u.string.string = start;
		}

		start++;

		*dst++ = *start++;

		break; /* out of switch */
	    } 

	    currToken++;
	    *dst++ = '\0';
	    start++;

	    if (*start == '|') {
		char * newEnd;

		start++;
		if (parseExpression(format + currToken, start, tags, 
				    extensions, &newEnd, error)) {
		    freeFormat(format, numTokens);
		    return 1;
		}
		start = newEnd;
	    } else {
		format[currToken].u.tag.format = start;
		format[currToken].u.tag.pad = 0;
		format[currToken].u.tag.justOne = 0;
		format[currToken].u.tag.arrayCount = 0;

		chptr = start;
		while (*chptr && *chptr != '{') chptr++;
		if (!*chptr) {
		    *error = _("missing { after %");
		    freeFormat(format, numTokens);
		    return 1;
		}

		*chptr++ = '\0';

		while (start < chptr) {
		    if (isdigit(*start)) {
			i = strtoul(start, &start, 10);
			format[currToken].u.tag.pad += i;
		    } else {
			start++;
		    }
		}

		if (*start == '=') {
		    format[currToken].u.tag.justOne = 1;
		    start++;
		} else if (*start == '#') {
		    format[currToken].u.tag.justOne = 1;
		    format[currToken].u.tag.arrayCount = 1;
		    start++;
		}

		next = start;
		while (*next && *next != '}') next++;
		if (!*next) {
		    *error = _("missing } after %{");
		    freeFormat(format, numTokens);
		    return 1;
		}
		*next++ = '\0';

		chptr = start;
		while (*chptr && *chptr != ':') chptr++;

		if (*chptr) {
		    *chptr++ = '\0';
		    if (!*chptr) {
			*error = _("empty tag format");
			freeFormat(format, numTokens);
			return 1;
		    }
		    format[currToken].u.tag.type = chptr;
		} else {
		    format[currToken].u.tag.type = NULL;
		}
	    
		if (!*start) {
		    *error = _("empty tag name");
		    freeFormat(format, numTokens);
		    return 1;
		}

		i = 0;
		findTag(start, tags, extensions, &entry, &ext);

		if (entry) {
		    format[currToken].u.tag.ext = NULL;
		    format[currToken].u.tag.tag = entry->val;
		} else if (ext) {
		    format[currToken].u.tag.ext = ext->u.tagFunction;
		    format[currToken].u.tag.extNum = ext - extensions;
		} else {
		    *error = _("unknown tag");
		    freeFormat(format, numTokens);
		    return 1;
		}

		format[currToken].type = PTOK_TAG;

		start = next;
	    }

	    break;

	  case '[':
	    *dst++ = '\0';
	    *start++ = '\0';
	    currToken++;

	    if (parseFormat(start, tags, extensions, 
			    &format[currToken].u.array.format,
			    &format[currToken].u.array.numTokens,
			    &start, PARSER_IN_ARRAY, error)) {
		freeFormat(format, numTokens);
		return 1;
	    }

	    if (!start) {
		*error = _("] expected at end of array");
		freeFormat(format, numTokens);
		return 1;
	    }

	    dst = start;

	    format[currToken].type = PTOK_ARRAY;

	    break;

	  case ']':
	  case '}':
	    if ((*start == ']' && state != PARSER_IN_ARRAY) ||
	        (*start == '}' && state != PARSER_IN_EXPR)) {
		if (*start == ']')
		    *error = _("unexpected ]");
		else
		    *error = _("unexpected }");
		freeFormat(format, numTokens);
		return 1;
	    }
	    *start++ = '\0';
	    *endPtr = start;
	    done = 1;
	    break;

	  default:
	    if (currToken < 0 || format[currToken].type != PTOK_STRING) {
		currToken++;
		format[currToken].type = PTOK_STRING;
		dst = format[currToken].u.string.string = start;
	    }

	    if (*start == '\\') {
		start++;
		*dst++ = escapedChar(*start++);
	    } else {
		*dst++ = *start++;
	    }
	}
    }

    *dst = '\0';

    currToken++;
    for (i = 0; i < currToken; i++) {
	if (format[i].type == PTOK_STRING)
	    format[i].u.string.len = strlen(format[i].u.string.string);
    }

    *numTokensPtr = currToken;
    *formatPtr = format;

    return 0;
}

static int getExtension(Header h, headerTagTagFunction fn, int_32 * typeptr,
			void ** data, int_32 * countptr, 
			struct extensionCache * ext) {
    if (!ext->avail) {
	if (fn(h, &ext->type, &ext->data, &ext->count, &ext->freeit))
	    return 1;
	ext->avail = 1;
    }

    *typeptr = ext->type;
    *data = ext->data;
    *countptr = ext->count;

    return 0;
}

static char * formatValue(struct sprintfTag * tag, Header h, 
			  const struct headerSprintfExtension * extensions,
			  struct extensionCache * extCache, int element) {
    int len;
    char buf[20];
    int_32 count, type;
    void * data;
    unsigned int intVal;
    char * val = NULL;
    char ** strarray;
    int mayfree = 0;
    int countBuf;
    headerTagFormatFunction tagtype = NULL;
    const struct headerSprintfExtension * ext;

    if (tag->ext) {
	if (getExtension(h, tag->ext, &type, &data, &count, 
			 extCache + tag->extNum)) {
	    count = 1;
	    type = RPM_STRING_TYPE;	
	    data = _("(none)");
	}
    } else {
	if (!headerGetEntry(h, tag->tag, &type, &data, &count)){
	    count = 1;
	    type = RPM_STRING_TYPE;	
	    data = _("(none)");
	}

	mayfree = 1;
    }

    if (tag->arrayCount) {
	if (type == RPM_STRING_ARRAY_TYPE) free(data);

	countBuf = count;
	data = &countBuf;
	count = 1;
	type = RPM_INT32_TYPE;
    }

    strcpy(buf, "%");
    strcat(buf, tag->format);

    if (tag->type) {
	ext = extensions;
	while (ext->type != HEADER_EXT_LAST) {
	    if (ext->type == HEADER_EXT_FORMAT && 
		!strcmp(ext->name, tag->type)) {
		tagtype = ext->u.formatFunction;
		break;
	    }

	    if (ext->type == HEADER_EXT_MORE)
		ext = ext->u.more;
	    else
		ext++;
	}
    }
    
    switch (type) {
      case RPM_STRING_ARRAY_TYPE:
	strarray = data;

	if (tagtype) {
	    val = tagtype(RPM_STRING_TYPE, strarray[element], buf, tag->pad, 0);
	}

	if (!val) {
	    strcat(buf, "s");

	    len = strlen(strarray[element]) + tag->pad + 20;
	    val = malloc(len);
	    sprintf(val, buf, strarray[element]);
	}

	if (mayfree) free(data);

	break;

      case RPM_STRING_TYPE:
	if (tagtype) {
	    val = tagtype(RPM_STRING_ARRAY_TYPE, data, buf, tag->pad,  0);
	}

	if (!val) {
	    strcat(buf, "s");

	    len = strlen(data) + tag->pad + 20;
	    val = malloc(len);
	    sprintf(val, buf, data);
	}
	break;

      case RPM_CHAR_TYPE:
      case RPM_INT8_TYPE:
      case RPM_INT16_TYPE:
      case RPM_INT32_TYPE:
	switch (type) {
	  case RPM_CHAR_TYPE:	
	  case RPM_INT8_TYPE:	intVal = *(((int_8 *) data) + element); break;
	  case RPM_INT16_TYPE:	intVal = *(((uint_16 *) data) + element); break;
	  default:		/* keep -Wall quiet */
	  case RPM_INT32_TYPE:	intVal = *(((int_32 *) data) + element);
	}

	if (tagtype) {
	    val = tagtype(RPM_INT32_TYPE, &intVal, buf, tag->pad,  element);
	}

	if (!val) {
	    strcat(buf, "d");
	    len = 10 + tag->pad + 20;
	    val = malloc(len);
	    sprintf(val, buf, intVal);
	}
	break;

      default:
	val = malloc(20);
	strcpy(val, _("(unknown type)"));
    }

    return val;
}

static char * singleSprintf(Header h, struct sprintfToken * token,
			    const struct headerSprintfExtension * extensions,
			    struct extensionCache * extCache, int element) {
    char * val, * thisItem;
    int thisItemLen;
    int len, alloced;
    int i, j;
    int numElements;
    int type;
    void * data;
    struct sprintfToken * condFormat;
    int condNumFormats;

    /* we assume the token and header have been validated already! */

    switch (token->type) {
      case PTOK_NONE:
	break;

      case PTOK_STRING:
	val = malloc(token->u.string.len + 1);
	strcpy(val, token->u.string.string);
	break;

      case PTOK_TAG:
	val = formatValue(&token->u.tag, h, extensions, extCache,
			  token->u.tag.justOne ? 0 : element);
	break;

      case PTOK_COND:
	if (token->u.cond.tag.ext ||
	    headerIsEntry(h, token->u.cond.tag.tag)) {
	    condFormat = token->u.cond.ifFormat;
	    condNumFormats = token->u.cond.numIfTokens;
	} else {
	    condFormat = token->u.cond.elseFormat;
	    condNumFormats = token->u.cond.numElseTokens;
	}

	alloced = condNumFormats * 20;
	val = malloc(alloced ? alloced : 1);
	*val = '\0';
	len = 0;

	for (i = 0; i < condNumFormats; i++) {
	    thisItem = singleSprintf(h, condFormat + i, 
				     extensions, extCache, element);
	    thisItemLen = strlen(thisItem);
	    if ((thisItemLen + len) >= alloced) {
		alloced = (thisItemLen + len) + 200;
		val = realloc(val, alloced);	
	    }
	    strcat(val, thisItem);
	    len += thisItemLen;
	    free(thisItem);
	}

	break;

      case PTOK_ARRAY:
	numElements = -1;
	for (i = 0; i < token->u.array.numTokens; i++) {
	    if (token->u.array.format[i].type != PTOK_TAG ||
		token->u.array.format[i].u.tag.arrayCount ||
		token->u.array.format[i].u.tag.justOne) continue;

	    if (token->u.array.format[i].u.tag.ext) {
		if (getExtension(h, token->u.array.format[i].u.tag.ext,
				 &type, &data, &numElements, 
				 extCache + 
				   token->u.array.format[i].u.tag.extNum))
		     continue;
	    } else {
		if (!headerGetEntry(h, token->u.array.format[i].u.tag.tag, 
				    &type, (void **) &val, &numElements))
		    continue;
		if (type == RPM_STRING_ARRAY_TYPE) free(val);
	    } 
	    break;
	}

	if (numElements == -1) {
	    val = malloc(20);
	    strcpy(val, _("(none)"));
	} else {
	    alloced = numElements * token->u.array.numTokens * 20;
	    val = malloc(alloced);
	    *val = '\0';
	    len = 0;

	    for (j = 0; j < numElements; j++) {
		for (i = 0; i < token->u.array.numTokens; i++) {
		    thisItem = singleSprintf(h, token->u.array.format + i, 
					     extensions, extCache, j);
		    thisItemLen = strlen(thisItem);
		    if ((thisItemLen + len) >= alloced) {
			alloced = (thisItemLen + len) + 200;
			val = realloc(val, alloced);	
		    }
		    strcat(val, thisItem);
		    len += thisItemLen;
		    free(thisItem);
		}
	    }
	}
	   
	break;
    }

    return val;
}

static struct extensionCache * allocateExtensionCache(
		     const struct headerSprintfExtension * extensions) {
    const struct headerSprintfExtension * ext = extensions;
    int i = 0;

    while (ext->type != HEADER_EXT_LAST) {
	i++;
	if (ext->type == HEADER_EXT_MORE)
	    ext = ext->u.more;
	else
	    ext++;
    }

    return calloc(i, sizeof(struct extensionCache));
}

void freeExtensionCache(const struct headerSprintfExtension * extensions,
		        struct extensionCache * cache) {
    const struct headerSprintfExtension * ext = extensions;
    int i = 0;

    while (ext->type != HEADER_EXT_LAST) {
	if (cache[i].freeit) free(cache[i].data);

	i++;
	if (ext->type == HEADER_EXT_MORE)
	    ext = ext->u.more;
	else
	    ext++;
    }

    free(cache);
}

char * headerSprintf(Header h, const char * origFmt, 
		     const struct headerTagTableEntry * tags,
		     const struct headerSprintfExtension * extensions,
		     char ** error) {
    char * fmtString;
    struct sprintfToken * format;
    int numTokens;
    char * answer, * piece;
    int answerLength;
    int answerAlloced;
    int pieceLength;
    int i;
    struct extensionCache * extCache;
 
    /*fmtString = escapeString(origFmt);*/
    fmtString = strdup(origFmt);
   
    if (parseFormat(fmtString, tags, extensions, &format, &numTokens, 
		    NULL, PARSER_BEGIN, error)) {
	free(fmtString);
	return NULL;
    }

    extCache = allocateExtensionCache(extensions);

    answerAlloced = 1024;
    answerLength = 0;
    answer = malloc(answerAlloced);
    *answer = '\0';

    for (i = 0; i < numTokens; i++) {
	piece = singleSprintf(h, format + i, extensions, extCache, 0);
	if (piece) {
	    pieceLength = strlen(piece);
	    if ((answerLength + pieceLength) >= answerAlloced) {
		while ((answerLength + pieceLength) >= answerAlloced) 
		    answerAlloced += 1024;
		answer = realloc(answer, answerAlloced);
	    }

	    strcat(answer, piece);
	    answerLength += pieceLength;
	}
    }

    free(fmtString);
    freeExtensionCache(extensions, extCache);
    free(format);

    return answer;
}

static char * octalFormat(int_32 type, const void * data, 
		          char * formatPrefix, int padding, int element) {
    char * val;

    if (type != RPM_INT32_TYPE) {
	val = malloc(20);
	strcpy(val, _("(not a number)"));
    } else {
	val = malloc(20 + padding);
	strcat(formatPrefix, "o");
	sprintf(val, formatPrefix, *((int_32 *) data));
    }

    return val;
}

static char * realDateFormat(int_32 type, const void * data, 
			     char * formatPrefix, int padding, int element,
			     char * strftimeFormat) {
    char * val;
    time_t dateint;
    struct tm * tstruct;
    char buf[50];

    if (type != RPM_INT32_TYPE) {
	val = malloc(20);
	strcpy(val, _("(not a number)"));
    } else {
	val = malloc(50 + padding);
	strcat(formatPrefix, "s");

	/* this is important if sizeof(int_32) ! sizeof(time_t) */
	dateint = *((int_32 *) data);
	tstruct = localtime(&dateint);
	strftime(buf, sizeof(buf) - 1, strftimeFormat, tstruct);
	sprintf(val, formatPrefix, buf);
    }

    return val;
}

static char * dateFormat(int_32 type, const void * data, 
		         char * formatPrefix, int padding, int element) {
    return realDateFormat(type, data, formatPrefix, padding, element, "%c");
}

static char * dayFormat(int_32 type, const void * data, 
		         char * formatPrefix, int padding, int element) {
    return realDateFormat(type, data, formatPrefix, padding, element, 
			  "%a %b %d %Y");
}

static char * shescapeFormat(int_32 type, const void * data, 
		         char * formatPrefix, int padding, int element) {
    char * result, * dst, * src, * buf;

    if (type == RPM_INT32_TYPE) {
	result = malloc(padding + 20);
	strcat(formatPrefix, "d");
	sprintf(result, formatPrefix, *((int_32 *) data));
    } else {
	buf = alloca(strlen(data) + padding + 2);
	strcat(formatPrefix, "s");
	sprintf(buf, formatPrefix, data);

	result = dst = malloc(strlen(buf) * 4 + 3);
	*dst++ = '\'';
	for (src = buf; *src; src++) {
	    if (*src == '\'') {
		*dst++ = '\'';
		*dst++ = '\\';
		*dst++ = '\'';
		*dst++ = '\'';
	    } else {
		*dst++ = *src;
	    }
	}
	*dst++ = '\'';
	*dst = '\0';

    }

    return result;
}

void headerSetLangPath(Header h, char * lang) {
    char * buf, * chptr, * start, * next;
    struct indexEntry * table;
    int langNum;

    table = findEntry(h, HEADER_I18NTABLE, RPM_STRING_ARRAY_TYPE);

    if (!lang || !table) {
	h->langNum = -1;
	return;
    }

    buf = alloca(strlen(lang) + 1);
    strcpy(buf, lang);

    start = buf;
    while (start) {
	chptr = strchr(start, ':');
	if (chptr) *chptr = '\0';
	
	next = table->data;
	for (langNum = 0; langNum < table->info.count; langNum++) {
	    if (!strcmp(next, start)) break;
	    next += strlen(next) + 1;
	}
	
	if (langNum < table->info.count) {
	    h->langNum = langNum;
	    break;
	}
	
	if (chptr)
	    start = chptr + 1;
	else
	    start = NULL;
    }

    if (!start)
	h->langNum = -1;
}

void headerResetLang(Header h) {
    char * str;

    if ((str = getenv("LANGUAGE"))) {
	headerSetLangPath(h, str);
	return;
    }
   
    headerSetLangPath(h, getenv("LANG"));
}
