#include "rpmsystem-py.h"

#include <rpm/rpmlib.h>		/* rpmvercmp */
#include <rpm/rpmtag.h>
#include <rpm/rpmstring.h>
#include <rpm/rpmts.h>	/* XXX rpmtsCreate/rpmtsFree */

#include "header-py.h"
#include "rpmds-py.h"
#include "rpmfd-py.h"
#include "rpmfi-py.h"
#include "rpmtd-py.h"

/** \ingroup python
 * \class Rpm
 * \brief START HERE / RPM base module for the Python API
 *
 * The rpm base module provides the main starting point for
 * accessing RPM from Python. For most usage, call
 * the TransactionSet method to get a transaction set (rpmts).
 *
 * For example:
 * \code
 *	import rpm
 *
 *	ts = rpm.TransactionSet()
 * \endcode
 *
 * The transaction set will open the RPM database as needed, so
 * in most cases, you do not need to explicitly open the
 * database. The transaction set is the workhorse of RPM.
 *
 * You can open another RPM database, such as one that holds
 * all packages for a given Linux distribution, to provide
 * packages used to solve dependencies. To do this, use
 * the following code:
 *
 * \code
 * rpm.addMacro('_dbpath', '/path/to/alternate/database')
 * solvets = rpm.TransactionSet()
 * solvets.openDB()
 * rpm.delMacro('_dbpath')
 *
 * # Open default database
 * ts = rpm.TransactionSet()
 * \endcode
 *
 * This code gives you access to two RPM databases through
 * two transaction sets (rpmts): ts is a transaction set
 * associated with the default RPM database and solvets
 * is a transaction set tied to an alternate database, which
 * is very useful for resolving dependencies.
 *
 * The rpm methods used here are:
 *
 * - addMacro(macro, value)
 * @param macro   Name of macro to add
 * @param value   Value for the macro
 *
 * - delMacro(macro)
 * @param macro   Name of macro to delete
 *
 */

/** \ingroup python
 * \class Rpmhdr
 * \brief A python header object represents an RPM package header.
 *
 * All RPM packages have headers that provide metadata for the package.
 * Header objects can be returned by database queries or loaded from a
 * binary package on disk.
 *
 * The ts.hdrFromFdno() function returns the package header from a
 * package on disk, verifying package signatures and digests of the
 * package while reading.
 *
 * Note: The older method rpm.headerFromPackage() which has been replaced
 * by ts.hdrFromFdno() used to return a (hdr, isSource) tuple.
 *
 * If you need to distinguish source/binary headers, do:
 * \code
 * 	import os, rpm
 *
 *	ts = rpm.TransactionSet()
 * 	fdno = os.open("/tmp/foo-1.0-1.i386.rpm", os.O_RDONLY)
 * 	hdr = ts.hdrFromFdno(fdno)
 *	os.close(fdno)
 *	if hdr[rpm.RPMTAG_SOURCEPACKAGE]:
 *	   print "header is from a source package"
 *	else:
 *	   print "header is from a binary package"
 * \endcode
 *
 * The Python interface to the header data is quite elegant.  It
 * presents the data in a dictionary form.  We'll take the header we
 * just loaded and access the data within it:
 * \code
 * 	print hdr[rpm.RPMTAG_NAME]
 * 	print hdr[rpm.RPMTAG_VERSION]
 * 	print hdr[rpm.RPMTAG_RELEASE]
 * \endcode
 * in the case of our "foo-1.0-1.i386.rpm" package, this code would
 * output:
\verbatim
  	foo
  	1.0
  	1
\endverbatim
 *
 * You make also access the header data by string name:
 * \code
 * 	print hdr['name']
 * 	print hdr['version']
 * 	print hdr['release']
 * \endcode
 *
 * This method of access is a teensy bit slower because the name must be
 * translated into the tag number dynamically. You also must make sure
 * the strings in header lookups don't get translated, or the lookups
 * will fail.
 */

/** \ingroup python
 * \name Class: rpm.hdr
 */

struct hdrObject_s {
    PyObject_HEAD
    Header h;
    HeaderIterator hi;
};

static PyObject * hdrKeyList(hdrObject * s)
{
    PyObject * keys;
    HeaderIterator hi;
    rpmTagVal tag;

    keys = PyList_New(0);
    if (!keys) {
        return NULL;
    }

    hi = headerInitIterator(s->h);
    while ((tag = headerNextTag(hi)) != RPMTAG_NOT_FOUND) {
	PyObject *to = PyInt_FromLong(tag);
        if (!to) {
            headerFreeIterator(hi);
            Py_DECREF(keys);
            return NULL;
        }
	PyList_Append(keys, to);
	Py_DECREF(to);
    }
    headerFreeIterator(hi);

    return keys;
}

static PyObject * hdrAsBytes(hdrObject * s, int legacy)
{
    PyObject *res = NULL;
    char *buf = NULL;
    unsigned int len;
    Header h = headerLink(s->h);
   
    /* XXX this legacy switch is a hack, needs to be removed. */
    if (legacy) {
	h = headerCopy(s->h);	/* XXX strip region tags, etc */
	headerFree(s->h);
    }
    buf = headerExport(h, &len);
    h = headerFree(h);

    if (buf == NULL || len == 0) {
	PyErr_SetString(pyrpmError, "can't unload bad header\n");
    } else {
	res = PyBytes_FromStringAndSize(buf, len);
    }
    free(buf);
    return res;
}

static PyObject * hdrUnload(hdrObject * s, PyObject * args, PyObject *keywords)
{
    int legacy = 0;
    static char *kwlist[] = { "legacyHeader", NULL};

    if (!PyArg_ParseTupleAndKeywords(args, keywords, "|i", kwlist, &legacy))
	return NULL;

    return hdrAsBytes(s, legacy);
}

static PyObject * hdrExpandFilelist(hdrObject * s)
{
    DEPRECATED_METHOD("use hdr.convert() instead");
    headerConvert(s->h, HEADERCONV_EXPANDFILELIST);

    Py_RETURN_NONE;
}

static PyObject * hdrCompressFilelist(hdrObject * s)
{
    DEPRECATED_METHOD("use hdr.convert() instead");
    headerConvert(s->h, HEADERCONV_COMPRESSFILELIST);

    Py_RETURN_NONE;
}

/* make a header with _all_ the tags we need */
static PyObject * hdrFullFilelist(hdrObject * s)
{
    rpmtd fileNames = rpmtdNew();
    Header h = s->h;

    DEPRECATED_METHOD("obsolete method");
    if (!headerIsEntry (h, RPMTAG_BASENAMES)
	|| !headerIsEntry (h, RPMTAG_DIRNAMES)
	|| !headerIsEntry (h, RPMTAG_DIRINDEXES))
	headerConvert(h, HEADERCONV_COMPRESSFILELIST);

    if (headerGet(h, RPMTAG_FILENAMES, fileNames, HEADERGET_EXT)) {
	rpmtdSetTag(fileNames, RPMTAG_OLDFILENAMES);
	headerPut(h, fileNames, HEADERPUT_DEFAULT);
	rpmtdFreeData(fileNames);
    }
    rpmtdFree(fileNames);

    Py_RETURN_NONE;
}

static PyObject * hdrFormat(hdrObject * s, PyObject * args, PyObject * kwds)
{
    const char * fmt;
    char * r;
    errmsg_t err;
    PyObject * result;
    char * kwlist[] = {"format", NULL};

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, &fmt))
	return NULL;

    r = headerFormat(s->h, fmt, &err);
    if (!r) {
	PyErr_SetString(pyrpmError, err);
	return NULL;
    }

    result = Py_BuildValue("s", r);
    free(r);

    return result;
}

static PyObject *hdrIsSource(hdrObject *s)
{
    return PyBool_FromLong(headerIsSource(s->h));
}

static int hdrContains(hdrObject *s, PyObject *pytag)
{
    rpmTagVal tag;
    if (!tagNumFromPyObject(pytag, &tag)) return -1;

    return headerIsEntry(s->h, tag);
}

static PyObject *hdrConvert(hdrObject *self, PyObject *args, PyObject *kwds)
{
    char *kwlist[] = {"op", NULL};
    int op = -1;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "i", kwlist, &op)) {
        return NULL;
    }
    return PyBool_FromLong(headerConvert(self->h, op));
}

static PyObject * hdrWrite(hdrObject *s, PyObject *args, PyObject *kwds)
{
    char *kwlist[] = { "file", "magic", NULL };
    int magic = HEADER_MAGIC_YES;
    rpmfdObject *fdo = NULL;
    int rc;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&|i", kwlist,
				     rpmfdFromPyObject, &fdo, &magic))
	return NULL;

    Py_BEGIN_ALLOW_THREADS;
    rc = headerWrite(rpmfdGetFd(fdo), s->h,
		     magic ? HEADER_MAGIC_YES : HEADER_MAGIC_NO);
    Py_END_ALLOW_THREADS;

    if (rc) PyErr_SetFromErrno(PyExc_IOError);
    Py_XDECREF(fdo); /* avoid messing up errno with file close  */
    if (rc) return NULL;

    Py_RETURN_NONE;
}

/*
 * Just a backwards-compatibility dummy, the arguments are not looked at
 * or used. TODO: push this over to python side...
 */
static PyObject * hdr_fiFromHeader(PyObject * s, PyObject * args, PyObject * kwds)
{
    return PyObject_CallFunctionObjArgs((PyObject *) &rpmfi_Type, s, NULL);
}

/* Backwards compatibility. Flags argument is just a dummy and discarded. */
static PyObject * hdr_dsFromHeader(PyObject * s, PyObject * args, PyObject * kwds)
{
    rpmTagVal tag = RPMTAG_REQUIRENAME;
    rpmsenseFlags flags = 0;
    char * kwlist[] = {"to", "flags", NULL};
    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O&i:dsFromHeader", kwlist,
            tagNumFromPyObject, &tag, &flags))
        return NULL;

    return PyObject_CallFunction((PyObject *) &rpmds_Type,
                                 "(Oi)", s, tag);
}

static PyObject * hdr_dsOfHeader(PyObject * s)
{
    return PyObject_CallFunction((PyObject *) &rpmds_Type,
                                 "(Oi)", s, RPMTAG_NEVR);
}

static long hdr_hash(PyObject * h)
{
    return (long) h;
}

static PyObject * hdr_reduce(hdrObject *s)
{
    PyObject *res = NULL;
    PyObject *blob = hdrAsBytes(s, 0);
    if (blob) {
	res = Py_BuildValue("O(O)", Py_TYPE(s), blob);
	Py_DECREF(blob);
    }
    return res;
}

static struct PyMethodDef hdr_methods[] = {
    {"keys",		(PyCFunction) hdrKeyList,	METH_NOARGS,
	NULL },
    {"unload",		(PyCFunction) hdrUnload,	METH_VARARGS|METH_KEYWORDS,
	NULL },
    {"expandFilelist",	(PyCFunction) hdrExpandFilelist,METH_NOARGS,
	NULL },
    {"compressFilelist",(PyCFunction) hdrCompressFilelist,METH_NOARGS,
	NULL },
    {"fullFilelist",	(PyCFunction) hdrFullFilelist,	METH_NOARGS,
	NULL },
    {"convert",		(PyCFunction) hdrConvert,	METH_VARARGS|METH_KEYWORDS,
	NULL },
    {"format",		(PyCFunction) hdrFormat,	METH_VARARGS|METH_KEYWORDS,
	NULL },
    {"sprintf",		(PyCFunction) hdrFormat,	METH_VARARGS|METH_KEYWORDS,
	NULL },
    {"isSource",	(PyCFunction)hdrIsSource,	METH_NOARGS, 
	NULL },
    {"write",		(PyCFunction)hdrWrite,		METH_VARARGS|METH_KEYWORDS,
	NULL },
    {"dsOfHeader",	(PyCFunction)hdr_dsOfHeader,	METH_NOARGS,
	NULL},
    {"dsFromHeader",	(PyCFunction)hdr_dsFromHeader,	METH_VARARGS|METH_KEYWORDS,
	NULL},
    {"fiFromHeader",	(PyCFunction)hdr_fiFromHeader,	METH_VARARGS|METH_KEYWORDS,
	NULL},
    {"__reduce__",	(PyCFunction)hdr_reduce,	METH_NOARGS,
	NULL},

    {NULL,		NULL}		/* sentinel */
};

/* TODO: permit keyring check + retrofits on copy/load */
static PyObject *hdr_new(PyTypeObject *subtype, PyObject *args, PyObject *kwds)
{
    PyObject *obj = NULL;
    rpmfdObject *fdo = NULL;
    Header h = NULL;
    char *kwlist[] = { "obj", NULL };

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O", kwlist, &obj)) {
	return NULL;
    }

    if (obj == NULL) {
	h = headerNew();
    } else if (CAPSULE_CHECK(obj)) {
	h = CAPSULE_EXTRACT(obj, "rpm._C_Header");
    } else if (hdrObject_Check(obj)) {
	h = headerCopy(((hdrObject*) obj)->h);
    } else if (PyBytes_Check(obj)) {
	h = headerCopyLoad(PyBytes_AsString(obj));
    } else if (rpmfdFromPyObject(obj, &fdo)) {
	Py_BEGIN_ALLOW_THREADS;
	h = headerRead(rpmfdGetFd(fdo), HEADER_MAGIC_YES);
	Py_END_ALLOW_THREADS;
	Py_XDECREF(fdo);
    } else {
    	PyErr_SetString(PyExc_TypeError, "header, blob or file expected");
	return NULL;
    }

    if (h == NULL) {
	PyErr_SetString(pyrpmError, "bad header");
	return NULL;
    }
    
    return hdr_Wrap(subtype, h);
}

static void hdr_dealloc(hdrObject * s)
{
    if (s->h) headerFree(s->h);
    Py_TYPE(s)->tp_free((PyObject *)s);
}

static PyObject * hdr_iternext(hdrObject *s)
{
    PyObject *res = NULL;
    rpmTagVal tag;

    if (s->hi == NULL) s->hi = headerInitIterator(s->h);

    if ((tag = headerNextTag(s->hi)) != RPMTAG_NOT_FOUND) {
	res = PyInt_FromLong(tag);
    } else {
	s->hi = headerFreeIterator(s->hi);
    }
    return res;
}

int utf8FromPyObject(PyObject *item, PyObject **str)
{
    PyObject *res = NULL;
    if (PyBytes_Check(item)) {
	Py_XINCREF(item);
	res = item;
    } else if (PyUnicode_Check(item)) {
	res = PyUnicode_AsUTF8String(item);
    }
    if (res == NULL) return 0;

    *str = res;
    return 1;
}

int tagNumFromPyObject (PyObject *item, rpmTagVal *tagp)
{
    rpmTagVal tag = RPMTAG_NOT_FOUND;
    PyObject *str = NULL;

    if (PyInt_Check(item)) {
	/* XXX we should probably validate tag numbers too */
	tag = PyInt_AsLong(item);
    } else if (utf8FromPyObject(item, &str)) {
	tag = rpmTagGetValue(PyBytes_AsString(str));
	Py_DECREF(str);
    } else {
	PyErr_SetString(PyExc_TypeError, "expected a string or integer");
	return 0;
    }
    if (tag == RPMTAG_NOT_FOUND) {
	PyErr_SetString(PyExc_ValueError, "unknown header tag");
	return 0;
    }

    *tagp = tag;
    return 1;
}

static PyObject * hdrGetTag(Header h, rpmTagVal tag)
{
    PyObject *res = NULL;
    struct rpmtd_s td;

    (void) headerGet(h, tag, &td, HEADERGET_EXT);
    if (rpmtdGetFlags(&td) & RPMTD_INVALID) {
	PyErr_SetString(pyrpmError, "invalid header data");
    } else {
	/* rpmtd_AsPyobj() knows how to handle empty containers and all */
	res = rpmtd_AsPyobj(&td);
    }
    rpmtdFreeData(&td);
    return res;
}

static int validItem(rpmTagClass tclass, PyObject *item)
{
    int rc;

    switch (tclass) {
    case RPM_NUMERIC_CLASS:
	rc = (PyLong_Check(item) || PyInt_Check(item));
	break;
    case RPM_STRING_CLASS:
	rc = (PyBytes_Check(item) || PyUnicode_Check(item));
	break;
    case RPM_BINARY_CLASS:
	rc = PyBytes_Check(item);
	break;
    default:
	rc = 0;
	break;
    }
    return rc;
}

static int validData(rpmTagVal tag, rpmTagType type, rpmTagReturnType retype, PyObject *value)
{
    rpmTagClass tclass = rpmTagGetClass(tag);
    int valid = 1;
    
    if (retype == RPM_SCALAR_RETURN_TYPE) {
	valid = validItem(tclass, value);
    } else if (retype == RPM_ARRAY_RETURN_TYPE && PyList_Check(value)) {
	/* python lists can contain arbitrary objects, validate each item */
	Py_ssize_t len = PyList_Size(value);
	if (len == 0)
	    valid = 0;
	for (Py_ssize_t i = 0; i < len; i++) {
	    PyObject *item = PyList_GetItem(value, i);
	    if (!validItem(tclass, item)) {
		valid = 0;
		break;
	    }
	}
    } else {
	valid = 0;
    }
    return valid;
}

static int hdrAppendItem(Header h, rpmTagVal tag, rpmTagType type, PyObject *item)
{
    int rc = 0;

    switch (type) {
    case RPM_I18NSTRING_TYPE: /* XXX this needs to be handled separately */
    case RPM_STRING_TYPE:
    case RPM_STRING_ARRAY_TYPE: {
	PyObject *str = NULL;
	if (utf8FromPyObject(item, &str)) 
	    rc = headerPutString(h, tag, PyBytes_AsString(str));
	Py_XDECREF(str);
	} break;
    case RPM_BIN_TYPE: {
	uint8_t *val = (uint8_t *) PyBytes_AsString(item);
	rpm_count_t len = PyBytes_Size(item);
	rc = headerPutBin(h, tag, val, len);
	} break;
    case RPM_INT64_TYPE: {
	uint64_t val = PyInt_AsUnsignedLongLongMask(item);
	rc = headerPutUint64(h, tag, &val, 1);
	} break;
    case RPM_INT32_TYPE: {
	uint32_t val = PyInt_AsUnsignedLongMask(item);
	rc = headerPutUint32(h, tag, &val, 1);
	} break;
    case RPM_INT16_TYPE: {
	uint16_t val = PyInt_AsUnsignedLongMask(item);
	rc = headerPutUint16(h, tag, &val, 1);
	} break;
    case RPM_INT8_TYPE:
    case RPM_CHAR_TYPE: {
	uint8_t val = PyInt_AsUnsignedLongMask(item);
	rc = headerPutUint8(h, tag, &val, 1);
	} break;
    default:
	PyErr_SetString(PyExc_TypeError, "unhandled datatype");
    }
    return rc;
}

static int hdrPutTag(Header h, rpmTagVal tag, PyObject *value)
{
    rpmTagType type = rpmTagGetTagType(tag);
    rpmTagReturnType retype = rpmTagGetReturnType(tag);
    int rc = 0;

    /* XXX this isn't really right (i18n strings etc) but for now ... */
    if (headerIsEntry(h, tag)) {
	PyErr_SetString(PyExc_TypeError, "tag already exists");
	return rc;
    }

    /* validate all data before trying to insert */
    if (!validData(tag, type, retype, value)) { 
	PyErr_SetString(PyExc_TypeError, "invalid type for tag");
	return 0;
    }

    if (retype == RPM_SCALAR_RETURN_TYPE) {
	rc = hdrAppendItem(h, tag, type, value);
    } else if (retype == RPM_ARRAY_RETURN_TYPE && PyList_Check(value)) {
	Py_ssize_t len = PyList_Size(value);
	for (Py_ssize_t i = 0; i < len; i++) {
	    PyObject *item = PyList_GetItem(value, i);
	    rc = hdrAppendItem(h, tag, type, item);
	}
    } else {
	PyErr_SetString(PyExc_RuntimeError, "can't happen, right?");
    }

    return rc;
}

static PyObject * hdr_subscript(hdrObject * s, PyObject * item)
{
    rpmTagVal tag;

    if (!tagNumFromPyObject(item, &tag)) return NULL;
    return hdrGetTag(s->h, tag);
}

static int hdr_ass_subscript(hdrObject *s, PyObject *key, PyObject *value)
{
    rpmTagVal tag;
    if (!tagNumFromPyObject(key, &tag)) return -1;

    if (value == NULL) {
	/* XXX should failure raise key error? */
	headerDel(s->h, tag);
    } else if (!hdrPutTag(s->h, tag, value)) {
	return -1;
    }
    return 0;
}

static PyObject * hdr_getattro(hdrObject * s, PyObject * n)
{
    PyObject *res = PyObject_GenericGetAttr((PyObject *) s, n);
    if (res == NULL) {
	PyObject *type, *value, *traceback;
	rpmTagVal tag;

	/* Save and restore original exception if it's not a valid tag either */
	PyErr_Fetch(&type, &value, &traceback);
	if (tagNumFromPyObject(n, &tag)) {
	    Py_XDECREF(type);
	    Py_XDECREF(value);
	    Py_XDECREF(traceback);
	    res = hdrGetTag(s->h, tag);
	} else {
	    PyErr_Restore(type, value, traceback);
	}
    }
    return res;
}

static int hdr_setattro(PyObject * o, PyObject * n, PyObject * v)
{
    return PyObject_GenericSetAttr(o, n, v);
}

static PyMappingMethods hdr_as_mapping = {
	(lenfunc) 0,			/* mp_length */
	(binaryfunc) hdr_subscript,	/* mp_subscript */
	(objobjargproc)hdr_ass_subscript,/* mp_ass_subscript */
};

static PySequenceMethods hdr_as_sequence = {
    0,				/* sq_length */
    0,				/* sq_concat */
    0,				/* sq_repeat */
    0,				/* sq_item */
    0,				/* sq_slice */
    0,				/* sq_ass_item */
    0,				/* sq_ass_slice */
    (objobjproc)hdrContains,	/* sq_contains */
    0,				/* sq_inplace_concat */
    0,				/* sq_inplace_repeat */
};

static char hdr_doc[] =
"";

PyTypeObject hdr_Type = {
	PyVarObject_HEAD_INIT(&PyType_Type, 0)
	"rpm.hdr",			/* tp_name */
	sizeof(hdrObject),		/* tp_size */
	0,				/* tp_itemsize */
	(destructor) hdr_dealloc, 	/* tp_dealloc */
	0,				/* tp_print */
	(getattrfunc) 0, 		/* tp_getattr */
	0,				/* tp_setattr */
	0,				/* tp_compare */
	0,				/* tp_repr */
	0,				/* tp_as_number */
	&hdr_as_sequence,		/* tp_as_sequence */
	&hdr_as_mapping,		/* tp_as_mapping */
	hdr_hash,			/* tp_hash */
	0,				/* tp_call */
	0,				/* tp_str */
	(getattrofunc) hdr_getattro,	/* tp_getattro */
	(setattrofunc) hdr_setattro,	/* tp_setattro */
	0,				/* tp_as_buffer */
	Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,	/* tp_flags */
	hdr_doc,			/* tp_doc */
	0,				/* tp_traverse */
	0,				/* tp_clear */
	0,				/* tp_richcompare */
	0,				/* tp_weaklistoffset */
	PyObject_SelfIter,		/* tp_iter */
	(iternextfunc)hdr_iternext,	/* tp_iternext */
	hdr_methods,			/* tp_methods */
	0,				/* tp_members */
	0,				/* tp_getset */
	0,				/* tp_base */
	0,				/* tp_dict */
	0,				/* tp_descr_get */
	0,				/* tp_descr_set */
	0,				/* tp_dictoffset */
	0,				/* tp_init */
	0,				/* tp_alloc */
	hdr_new,			/* tp_new */
	0,				/* tp_free */
	0,				/* tp_is_gc */
};

PyObject * hdr_Wrap(PyTypeObject *subtype, Header h)
{
    hdrObject * hdr = (hdrObject *)subtype->tp_alloc(subtype, 0);
    if (hdr == NULL) return NULL;

    hdr->h = headerLink(h);
    return (PyObject *) hdr;
}

int hdrFromPyObject(PyObject *item, Header *hptr)
{
    if (hdrObject_Check(item)) {
	*hptr = ((hdrObject *) item)->h;
	return 1;
    } else {
	PyErr_SetString(PyExc_TypeError, "header object expected");
	return 0;
    }
}

/**
 * This assumes the order of list matches the order of the new headers, and
 * throws an exception if that isn't true.
 */
static int rpmMergeHeaders(PyObject * list, FD_t fd, int matchTag)
{
    Header h;
    HeaderIterator hi;
    rpmTagVal newMatch, oldMatch;
    hdrObject * hdr;
    rpm_count_t count = 0;
    int rc = 1; /* assume failure */
    rpmtd td = rpmtdNew();

    Py_BEGIN_ALLOW_THREADS
    h = headerRead(fd, HEADER_MAGIC_YES);
    Py_END_ALLOW_THREADS

    while (h) {
	if (!headerGet(h, matchTag, td, HEADERGET_MINMEM)) {
	    PyErr_SetString(pyrpmError, "match tag missing in new header");
	    goto exit;
	}
	newMatch = rpmtdTag(td);
	rpmtdFreeData(td);

	hdr = (hdrObject *) PyList_GetItem(list, count++);
	if (!hdr) goto exit;

	if (!headerGet(hdr->h, matchTag, td, HEADERGET_MINMEM)) {
	    PyErr_SetString(pyrpmError, "match tag missing in new header");
	    goto exit;
	}
	oldMatch = rpmtdTag(td);
	rpmtdFreeData(td);

	if (newMatch != oldMatch) {
	    PyErr_SetString(pyrpmError, "match tag mismatch");
	    goto exit;
	}

	for (hi = headerInitIterator(h); headerNext(hi, td); rpmtdFreeData(td))
	{
	    /* could be dupes */
	    headerDel(hdr->h, rpmtdTag(td));
	    headerPut(hdr->h, td, HEADERPUT_DEFAULT);
	}

	headerFreeIterator(hi);
	h = headerFree(h);

	Py_BEGIN_ALLOW_THREADS
	h = headerRead(fd, HEADER_MAGIC_YES);
	Py_END_ALLOW_THREADS
    }
    rc = 0;

exit:
    rpmtdFree(td);
    return rc;
}

PyObject *
rpmMergeHeadersFromFD(PyObject * self, PyObject * args, PyObject * kwds)
{
    FD_t fd;
    int fileno;
    PyObject * list;
    int rc;
    int matchTag;
    char * kwlist[] = {"list", "fd", "matchTag", NULL};

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "Oii", kwlist, &list,
	    &fileno, &matchTag))
	return NULL;

    if (!PyList_Check(list)) {
	PyErr_SetString(PyExc_TypeError, "first parameter must be a list");
	return NULL;
    }

    fd = fdDup(fileno);

    rc = rpmMergeHeaders (list, fd, matchTag);
    Fclose(fd);

    if (rc) {
	return NULL;
    }

    Py_RETURN_NONE;
}

PyObject * versionCompare (PyObject * self, PyObject * args, PyObject * kwds)
{
    hdrObject * h1, * h2;
    char * kwlist[] = {"version0", "version1", NULL};

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!O!", kwlist, &hdr_Type,
	    &h1, &hdr_Type, &h2))
	return NULL;

    return Py_BuildValue("i", rpmVersionCompare(h1->h, h2->h));
}

static int compare_values(const char *str1, const char *str2)
{
    if (!str1 && !str2)
	return 0;
    else if (str1 && !str2)
	return 1;
    else if (!str1 && str2)
	return -1;
    return rpmvercmp(str1, str2);
}

PyObject * labelCompare (PyObject * self, PyObject * args)
{
    const char *v1, *r1, *v2, *r2;
    const char *e1, *e2;
    int rc;

    if (!PyArg_ParseTuple(args, "(zzz)(zzz)",
			&e1, &v1, &r1, &e2, &v2, &r2))
	return NULL;

    if (e1 == NULL)	e1 = "0";
    if (e2 == NULL)	e2 = "0";

    rc = compare_values(e1, e2);
    if (!rc) {
	rc = compare_values(v1, v2);
	if (!rc)
	    rc = compare_values(r1, r2);
    }
    return Py_BuildValue("i", rc);
}

