#include <alloca.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>
#include <glob.h>       /* XXX rpmio.h */
#include <dirent.h>     /* XXX rpmio.h */

#include "Python.h"
#include "rpmlib.h"
#include "misc.h"
#include "rpmmacro.h"
#include "upgrade.h"

/* from lib/misc.c */
int rpmvercmp(const char * one, const char * two);

/* Forward types */

typedef struct rpmdbObject_s rpmdbObject;
typedef struct rpmtransObject_s rpmtransObject;
typedef struct hdrObject_s hdrObject;

/* Prototypes */

static void rpmdbDealloc(rpmdbObject * s);
static PyObject * rpmdbGetAttr(rpmdbObject * s, char * name);
static PyObject * rpmdbFirst(rpmdbObject * s, PyObject * args);
static PyObject * rpmdbNext(rpmdbObject * s, PyObject * args);
static PyObject * rpmdbByName(rpmdbObject * s, PyObject * args);
static PyObject * rpmdbByProvides(rpmdbObject * s, PyObject * args);
static PyObject * rpmdbByFile(rpmdbObject * s, PyObject * args);
static int rpmdbLength(rpmdbObject * s);
static hdrObject * rpmdbSubscript(rpmdbObject * s, PyObject * key);

static void hdrDealloc(hdrObject * s);
static PyObject * hdrGetAttr(hdrObject * s, char * name);
static PyObject * hdrSubscript(hdrObject * s, PyObject * item);
static PyObject * hdrKeyList(hdrObject * s, PyObject * args);
static PyObject * hdrUnload(hdrObject * s, PyObject * args);
static PyObject * hdrVerifyFile(hdrObject * s, PyObject * args);
static PyObject * hdrCompressFilelist(hdrObject * s, PyObject * args);
static PyObject * hdrExpandFilelist(hdrObject * s, PyObject * args);
static PyObject * hdrFullFilelist(hdrObject * s, PyObject * args);

void initrpm(void);
static PyObject * doAddMacro(PyObject * self, PyObject * args);
static PyObject * doDelMacro(PyObject * self, PyObject * args);
static rpmdbObject * rpmOpenDB(PyObject * self, PyObject * args);
static PyObject * hdrLoad(PyObject * self, PyObject * args);
static PyObject * rpmHeaderFromPackage(PyObject * self, PyObject * args);
static PyObject * rpmHeaderFromFile(PyObject * self, PyObject * args);
static PyObject * archScore(PyObject * self, PyObject * args);
static PyObject * rpmHeaderFromFD(PyObject * self, PyObject * args);
static PyObject * findUpgradeSet(PyObject * self, PyObject * args);
static PyObject * errorSetCallback (PyObject * self, PyObject * args);
static PyObject * errorString (PyObject * self, PyObject * args);
static PyObject * versionCompare (PyObject * self, PyObject * args);
static PyObject * labelCompare (PyObject * self, PyObject * args);
static PyObject * rebuildDB (PyObject * self, PyObject * args);

static PyObject * rpmtransCreate(PyObject * self, PyObject * args);
static PyObject * rpmtransAdd(rpmtransObject * s, PyObject * args);
static PyObject * rpmtransDepCheck(rpmtransObject * s, PyObject * args);
static PyObject * rpmtransRun(rpmtransObject * s, PyObject * args);
static PyObject * rpmtransOrder(rpmtransObject * s, PyObject * args);
static void rpmtransDealloc(PyObject * o);
static PyObject * rpmtransGetAttr(rpmtransObject * o, char * name);
static int rpmtransSetAttr(rpmtransObject * o, char * name,
                          PyObject * val);
static PyObject * doFopen(PyObject * self, PyObject * args);

/* Types */

static PyMethodDef rpmModuleMethods[] = {
   { "TransactionSet", (PyCFunction) rpmtransCreate, METH_VARARGS, NULL },
   { "addMacro", (PyCFunction) doAddMacro, METH_VARARGS, NULL },
   { "delMacro", (PyCFunction) doDelMacro, METH_VARARGS, NULL },
   { "archscore", (PyCFunction) archScore, METH_VARARGS, NULL },
   { "findUpgradeSet", (PyCFunction) findUpgradeSet, METH_VARARGS, NULL },
   { "headerFromPackage", (PyCFunction) rpmHeaderFromPackage, METH_VARARGS, NULL },
   { "headerLoad", (PyCFunction) hdrLoad, METH_VARARGS, NULL },
   { "opendb", (PyCFunction) rpmOpenDB, METH_VARARGS, NULL },
   { "rebuilddb", (PyCFunction) rebuildDB, METH_VARARGS, NULL },
   { "readHeaderListFromFD", (PyCFunction) rpmHeaderFromFD, METH_VARARGS, NULL },
   { "readHeaderListFromFile", (PyCFunction) rpmHeaderFromFile, METH_VARARGS, NULL },
   { "errorSetCallback", (PyCFunction) errorSetCallback, METH_VARARGS, NULL },
   { "errorString", (PyCFunction) errorString, METH_VARARGS, NULL },
   { "versionCompare", (PyCFunction) versionCompare, METH_VARARGS, NULL },
   { "labelCompare", (PyCFunction) labelCompare, METH_VARARGS, NULL },
   /* don't use me yet
   { "Fopen", (PyCFunction) doFopen, METH_VARARGS, NULL },
   */
   { NULL }
} ;

struct rpmdbObject_s {
   PyObject_HEAD;
   rpmdb db;
} ;

struct rpmtransObject_s {
   PyObject_HEAD;
   rpmdbObject * dbo;
   rpmTransactionSet ts;
   PyObject * keyList;                 /* keeps reference counts correct */
   FD_t scriptFd;
} ;

struct hdrObject_s {
   PyObject_HEAD;
   Header h;
   char ** md5list;
   char ** fileList;
   char ** linkList;
   int_32 * fileSizes;
   int_32 * mtimes;
   int_32 * uids, * gids;
   unsigned short * rdevs;
   unsigned short * modes;
} ;

/* Data */

static PyObject * pyrpmError;

static PyMappingMethods hdrAsMapping = {
       (inquiry) 0,                    /* mp_length */
       (binaryfunc) hdrSubscript,      /* mp_subscript */
       (objobjargproc)0,               /* mp_ass_subscript */
};

static PyTypeObject hdrType = {
       PyObject_HEAD_INIT(&PyType_Type)
       0,                              /* ob_size */
       "header",                       /* tp_name */
       sizeof(hdrObject),              /* tp_size */
       0,                              /* tp_itemsize */
       (destructor) hdrDealloc,        /* tp_dealloc */
       0,                              /* tp_print */
       (getattrfunc) hdrGetAttr,       /* tp_getattr */
       0,                              /* tp_setattr */
       0,                              /* tp_compare */
       0,                              /* tp_repr */
       0,                              /* tp_as_number */
       0,                              /* tp_as_sequence */
       &hdrAsMapping,                  /* tp_as_mapping */
};

static PyMappingMethods rpmdbAsMapping = {
       (inquiry) rpmdbLength,          /* mp_length */
       (binaryfunc) rpmdbSubscript,    /* mp_subscript */
       (objobjargproc)0,               /* mp_ass_subscript */
};

static PyTypeObject rpmdbType = {
       PyObject_HEAD_INIT(&PyType_Type)
       0,                              /* ob_size */
       "rpmdb",                        /* tp_name */
       sizeof(rpmdbObject),            /* tp_size */
       0,                              /* tp_itemsize */
       (destructor) rpmdbDealloc,      /* tp_dealloc */
       0,                              /* tp_print */
       (getattrfunc) rpmdbGetAttr,     /* tp_getattr */
       0,                              /* tp_setattr */
       0,                              /* tp_compare */
       0,                              /* tp_repr */
       0,                              /* tp_as_number */
       0,                              /* tp_as_sequence */
       &rpmdbAsMapping,                /* tp_as_mapping */
};

static PyTypeObject rpmtransType = {
       PyObject_HEAD_INIT(&PyType_Type)
       0,                              /* ob_size */
       "rpmtrans",                     /* tp_name */
       sizeof(rpmtransObject),         /* tp_size */
       0,                              /* tp_itemsize */
       (destructor) rpmtransDealloc,   /* tp_dealloc */
       0,                              /* tp_print */
       (getattrfunc) rpmtransGetAttr,  /* tp_getattr */
       (setattrfunc) rpmtransSetAttr,  /* tp_setattr */
       0,                              /* tp_compare */
       0,                              /* tp_repr */
       0,                              /* tp_as_number */
       0,                              /* tp_as_sequence */
       0,                              /* tp_as_mapping */
};

static struct PyMethodDef rpmdbMethods[] = {
       {"firstkey",        (PyCFunction) rpmdbFirst,   1 },
       {"nextkey",         (PyCFunction) rpmdbNext,    1 },
       {"findbyfile",      (PyCFunction) rpmdbByFile, 1 },
       {"findbyname",      (PyCFunction) rpmdbByName, 1 },
       {"findbyprovides",  (PyCFunction) rpmdbByProvides, 1 },
       {NULL,          NULL}           /* sentinel */
};

static struct PyMethodDef rpmtransMethods[] = {
       {"add",         (PyCFunction) rpmtransAdd,      1 },
       {"depcheck",    (PyCFunction) rpmtransDepCheck, 1 },
       {"order",       (PyCFunction) rpmtransOrder,    1 },
       {"run",         (PyCFunction) rpmtransRun, 1 },
       {NULL,          NULL}           /* sentinel */
};

static struct PyMethodDef hdrMethods[] = {
       {"keys",        (PyCFunction) hdrKeyList,       1 },
       {"unload",      (PyCFunction) hdrUnload,        1 },
       {"verifyFile",  (PyCFunction) hdrVerifyFile,    1 },
       {"expandFilelist",      (PyCFunction) hdrExpandFilelist,        1 },
       {"compressFilelist",    (PyCFunction) hdrCompressFilelist,      1 },
       {"fullFilelist",        (PyCFunction) hdrFullFilelist,  1 },
       {NULL,          NULL}           /* sentinel */
};

/* External functions */
int mdfile(const char *fn, unsigned char *digest);

/* Code */

extern int _rpmio_debug;

void initrpm(void) {
   PyObject * m, * d, * tag, * dict;
   int i;
   const struct headerSprintfExtension * extensions = rpmHeaderFormats;
   struct headerSprintfExtension * ext;

/*      i18ndomains = "redhat-dist"; */

/*      _rpmio_debug = -1;  */
   rpmReadConfigFiles(NULL, NULL);

   m = Py_InitModule("rpm", rpmModuleMethods);
   d = PyModule_GetDict(m);

   pyrpmError = PyString_FromString("rpm.error");
   PyDict_SetItemString(d, "error", pyrpmError);

   dict = PyDict_New();

   for (i = 0; i < rpmTagTableSize; i++) {
       tag = PyInt_FromLong(rpmTagTable[i].val);
       PyDict_SetItemString(d, (char *) rpmTagTable[i].name, tag);

       PyDict_SetItem(dict, tag, PyString_FromString(rpmTagTable[i].name + 7));
   }

   while (extensions->name) {
       if (extensions->type == HEADER_EXT_TAG) {
           (const struct headerSprintfExtension *) ext = extensions;
           PyDict_SetItemString(d, extensions->name, PyCObject_FromVoidPtr(ext, NULL));
           PyDict_SetItem(dict, tag, PyString_FromString(ext->name + 7));
       }
       extensions++;
   }

   PyDict_SetItemString(d, "tagnames", dict);

   PyDict_SetItemString(d, "RPMFILE_STATE_NORMAL",
                        PyInt_FromLong(RPMFILE_STATE_NORMAL));
   PyDict_SetItemString(d, "RPMFILE_STATE_REPLACED",
                        PyInt_FromLong(RPMFILE_STATE_REPLACED));
   PyDict_SetItemString(d, "RPMFILE_STATE_NOTINSTALLED",
                        PyInt_FromLong(RPMFILE_STATE_NOTINSTALLED));
   PyDict_SetItemString(d, "RPMFILE_CONFIG",
                        PyInt_FromLong(RPMFILE_CONFIG));
   PyDict_SetItemString(d, "RPMFILE_MISSINGOK",
                        PyInt_FromLong(RPMFILE_MISSINGOK));
   PyDict_SetItemString(d, "RPMFILE_DOC",
                        PyInt_FromLong(RPMFILE_DOC));

   PyDict_SetItemString(d, "RPMDEP_SENSE_REQUIRES",
                        PyInt_FromLong(RPMDEP_SENSE_REQUIRES));
   PyDict_SetItemString(d, "RPMDEP_SENSE_CONFLICTS",
                        PyInt_FromLong(RPMDEP_SENSE_CONFLICTS));

   PyDict_SetItemString(d, "RPMSENSE_SERIAL",
                        PyInt_FromLong(RPMSENSE_SERIAL));
   PyDict_SetItemString(d, "RPMSENSE_LESS",
                        PyInt_FromLong(RPMSENSE_LESS));
   PyDict_SetItemString(d, "RPMSENSE_GREATER",
                        PyInt_FromLong(RPMSENSE_GREATER));
   PyDict_SetItemString(d, "RPMSENSE_EQUAL",
                        PyInt_FromLong(RPMSENSE_EQUAL));
   PyDict_SetItemString(d, "RPMSENSE_PREREQ",
                        PyInt_FromLong(RPMSENSE_PREREQ));

   PyDict_SetItemString(d, "RPMTRANS_FLAG_TEST",
                        PyInt_FromLong(RPMTRANS_FLAG_TEST));
   PyDict_SetItemString(d, "RPMTRANS_FLAG_BUILD_PROBS",
                        PyInt_FromLong(RPMTRANS_FLAG_BUILD_PROBS));
   PyDict_SetItemString(d, "RPMTRANS_FLAG_NOSCRIPTS",
                        PyInt_FromLong(RPMTRANS_FLAG_NOSCRIPTS));
   PyDict_SetItemString(d, "RPMTRANS_FLAG_JUSTDB",
                        PyInt_FromLong(RPMTRANS_FLAG_JUSTDB));
   PyDict_SetItemString(d, "RPMTRANS_FLAG_NOTRIGGERS",
                        PyInt_FromLong(RPMTRANS_FLAG_NOTRIGGERS));
   PyDict_SetItemString(d, "RPMTRANS_FLAG_NODOCS",
                        PyInt_FromLong(RPMTRANS_FLAG_NODOCS));
   PyDict_SetItemString(d, "RPMTRANS_FLAG_ALLFILES",
                        PyInt_FromLong(RPMTRANS_FLAG_ALLFILES));
   PyDict_SetItemString(d, "RPMTRANS_FLAG_KEEPOBSOLETE",
                        PyInt_FromLong(RPMTRANS_FLAG_KEEPOBSOLETE));

   PyDict_SetItemString(d, "RPMPROB_FILTER_IGNOREOS",
                        PyInt_FromLong(RPMPROB_FILTER_IGNOREOS));
   PyDict_SetItemString(d, "RPMPROB_FILTER_IGNOREARCH",
                        PyInt_FromLong(RPMPROB_FILTER_IGNOREARCH));
   PyDict_SetItemString(d, "RPMPROB_FILTER_REPLACEPKG",
                        PyInt_FromLong(RPMPROB_FILTER_REPLACEPKG));
   PyDict_SetItemString(d, "RPMPROB_FILTER_FORCERELOCATE",
                        PyInt_FromLong(RPMPROB_FILTER_FORCERELOCATE));
   PyDict_SetItemString(d, "RPMPROB_FILTER_REPLACENEWFILES",
                        PyInt_FromLong(RPMPROB_FILTER_REPLACENEWFILES));
   PyDict_SetItemString(d, "RPMPROB_FILTER_REPLACEOLDFILES",
                        PyInt_FromLong(RPMPROB_FILTER_REPLACEOLDFILES));
   PyDict_SetItemString(d, "RPMPROB_FILTER_OLDPACKAGE",
                        PyInt_FromLong(RPMPROB_FILTER_OLDPACKAGE));
   PyDict_SetItemString(d, "RPMPROB_FILTER_DISKSPACE",
                        PyInt_FromLong(RPMPROB_FILTER_DISKSPACE));

   PyDict_SetItemString(d, "RPMCALLBACK_INST_PROGRESS",
                        PyInt_FromLong(RPMCALLBACK_INST_PROGRESS));
   PyDict_SetItemString(d, "RPMCALLBACK_INST_START",
                        PyInt_FromLong(RPMCALLBACK_INST_START));
   PyDict_SetItemString(d, "RPMCALLBACK_INST_OPEN_FILE",
                        PyInt_FromLong(RPMCALLBACK_INST_OPEN_FILE));
   PyDict_SetItemString(d, "RPMCALLBACK_INST_CLOSE_FILE",
                        PyInt_FromLong(RPMCALLBACK_INST_CLOSE_FILE));
   PyDict_SetItemString(d, "RPMCALLBACK_TRANS_PROGRESS",
                        PyInt_FromLong(RPMCALLBACK_TRANS_PROGRESS));
   PyDict_SetItemString(d, "RPMCALLBACK_TRANS_START",
                        PyInt_FromLong(RPMCALLBACK_TRANS_START));
   PyDict_SetItemString(d, "RPMCALLBACK_TRANS_STOP",
                        PyInt_FromLong(RPMCALLBACK_TRANS_STOP));
   PyDict_SetItemString(d, "RPMCALLBACK_UNINST_PROGRESS",
                        PyInt_FromLong(RPMCALLBACK_UNINST_PROGRESS));
   PyDict_SetItemString(d, "RPMCALLBACK_UNINST_START",
                        PyInt_FromLong(RPMCALLBACK_UNINST_START));
   PyDict_SetItemString(d, "RPMCALLBACK_UNINST_STOP",
                        PyInt_FromLong(RPMCALLBACK_UNINST_STOP));

   PyDict_SetItemString(d, "RPMPROB_BADARCH",
                        PyInt_FromLong(RPMPROB_BADARCH));
   PyDict_SetItemString(d, "RPMPROB_BADOS",
                        PyInt_FromLong(RPMPROB_BADOS));
   PyDict_SetItemString(d, "RPMPROB_PKG_INSTALLED",
                        PyInt_FromLong(RPMPROB_PKG_INSTALLED));
   PyDict_SetItemString(d, "RPMPROB_BADRELOCATE",
                        PyInt_FromLong(RPMPROB_BADRELOCATE));
   PyDict_SetItemString(d, "RPMPROB_REQUIRES",
                        PyInt_FromLong(RPMPROB_REQUIRES));
   PyDict_SetItemString(d, "RPMPROB_CONFLICT",
                        PyInt_FromLong(RPMPROB_CONFLICT));
   PyDict_SetItemString(d, "RPMPROB_NEW_FILE_CONFLICT",
                        PyInt_FromLong(RPMPROB_NEW_FILE_CONFLICT));
   PyDict_SetItemString(d, "RPMPROB_FILE_CONFLICT",
                        PyInt_FromLong(RPMPROB_FILE_CONFLICT));
   PyDict_SetItemString(d, "RPMPROB_OLDPACKAGE",
                        PyInt_FromLong(RPMPROB_OLDPACKAGE));
   PyDict_SetItemString(d, "RPMPROB_DISKSPACE",
                        PyInt_FromLong(RPMPROB_DISKSPACE));
}

/* make a header with _all_ the tags we need */
void mungeFilelist(Header h)
{
   const char ** fileNames = NULL;
   int count = 0;

   if (!headerIsEntry (h, RPMTAG_BASENAMES)
       || !headerIsEntry (h, RPMTAG_DIRNAMES)
       || !headerIsEntry (h, RPMTAG_DIRINDEXES))
       compressFilelist(h);

   rpmBuildFileList(h, &fileNames, &count);

   if (fileNames == NULL || count <= 0)
       return;

   headerAddEntry(h, RPMTAG_OLDFILENAMES, RPM_STRING_ARRAY_TYPE,
                       fileNames, count);

   xfree(fileNames);
}


static int psGetArchScore(Header h) {
   void * pkgArch;
   int type, count;

   if (!headerGetEntry(h, RPMTAG_ARCH, &type, (void **) &pkgArch, &count) ||
       type == RPM_INT8_TYPE)
      return 150;
   else
       return rpmMachineScore(RPM_MACHTABLE_INSTARCH, pkgArch);
}

static int pkgCompareVer(void * first, void * second) {
   struct packageInfo ** a = first;
   struct packageInfo ** b = second;
   int ret, score1, score2;

   /* put packages w/o names at the end */
   if (!(*a)->name) return 1;
   if (!(*b)->name) return -1;

   ret = strcasecmp((*a)->name, (*b)->name);
   if (ret) return ret;
   score1 = psGetArchScore((*a)->h);
   if (!score1) return 1;
   score2 = psGetArchScore((*b)->h);
   if (!score2) return -1;
   if (score1 < score2) return -1;
   if (score1 > score2) return 1;
   return rpmVersionCompare((*b)->h, (*a)->h);
}

static void pkgSort(struct pkgSet * psp) {
   int i;
   char *name;

   qsort(psp->packages, psp->numPackages, sizeof(*psp->packages),
        (void *) pkgCompareVer);

   name = psp->packages[0]->name;
   if (!name) {
      psp->numPackages = 0;
      return;
   }
   for (i = 1; i < psp->numPackages; i++) {
      if (!psp->packages[i]->name) break;
      if (!strcmp(psp->packages[i]->name, name))
          psp->packages[i]->name = NULL;
      else
          name = psp->packages[i]->name;
   }

   qsort(psp->packages, psp->numPackages, sizeof(*psp->packages),
        (void *) pkgCompareVer);

   for (i = 0; i < psp->numPackages; i++)
      if (!psp->packages[i]->name) break;
   psp->numPackages = i;
}

static PyObject * findUpgradeSet(PyObject * self, PyObject * args) {
   PyObject * hdrList, * result;
   char * root = "/";
   int i;
   struct pkgSet list;
   hdrObject * hdr;

   if (!PyArg_ParseTuple(args, "O|s", &hdrList, &root)) return NULL;

   if (!PyList_Check(hdrList)) {
       PyErr_SetString(PyExc_TypeError, "list of headers expected");
       return NULL;
   }

   list.numPackages = PyList_Size(hdrList);
   list.packages = alloca(sizeof(list.packages) * list.numPackages);
   for (i = 0; i < list.numPackages; i++) {
       hdr = (hdrObject *) PyList_GetItem(hdrList, i);
       if (hdr->ob_type != &hdrType) {
           PyErr_SetString(PyExc_TypeError, "list of headers expected");
           return NULL;
       }
       list.packages[i] = alloca(sizeof(struct packageInfo));
       list.packages[i]->h = hdr->h;
       list.packages[i]->selected = 0;
       list.packages[i]->data = hdr;

       headerGetEntry(hdr->h, RPMTAG_NAME, NULL,
                     (void **) &list.packages[i]->name, NULL);
   }

   pkgSort (&list);

   if (ugFindUpgradePackages(&list, root)) {
       PyErr_SetString(pyrpmError, "error during upgrade check");
       return NULL;
   }

   result = PyList_New(0);
   for (i = 0; i < list.numPackages; i++) {
       if (list.packages[i]->selected)
           PyList_Append(result, list.packages[i]->data);
   }

   return result;
}



static rpmdbObject * rpmOpenDB(PyObject * self, PyObject * args) {
   rpmdbObject * o;
   char * root = "";
   int forWrite = 0;

   if (!PyArg_ParseTuple(args, "|is", &forWrite, &root)) return NULL;

   o = PyObject_NEW(rpmdbObject, &rpmdbType);
   o->db = NULL;

   if (rpmdbOpen(root, &o->db, forWrite ? O_RDWR | O_CREAT: O_RDONLY, 0)) {
       char * errmsg = "cannot open database in %s";
       char * errstr = NULL;
       int errsize;

       Py_DECREF(o);
       /* PyErr_SetString should take varargs... */
       errsize = strlen(errmsg) + *root == '\0' ? 15 /* "/var/lib/rpm" */ : strlen(root);
       errstr = alloca(errsize);
       snprintf(errstr, errsize, errmsg, *root == '\0' ? "/var/lib/rpm" : root);
       PyErr_SetString(pyrpmError, errstr);
       return NULL;
   }

   return o;
}

static PyObject * rebuildDB (PyObject * self, PyObject * args) {
   char * root = "";

   if (!PyArg_ParseTuple(args, "s", &root)) return NULL;

   return Py_BuildValue("i", rpmdbRebuild(root));
}

static PyObject * rpmReadHeaders (FD_t fd) {
   PyObject * list;
   Header header;
   hdrObject * h;

   if (!fd) {
       PyErr_SetFromErrno(pyrpmError);
       return NULL;
   }

   list = PyList_New(0);
   Py_BEGIN_ALLOW_THREADS
   header = headerRead(fd, HEADER_MAGIC_YES);
   Py_END_ALLOW_THREADS
   while (header) {
       h = (hdrObject *) PyObject_NEW(PyObject, &hdrType);
       h->h = header;
       h->fileList = h->linkList = h->md5list = NULL;
       h->uids = h->gids = h->mtimes = h->fileSizes = NULL;
       h->modes = h->rdevs = NULL;
       if (PyList_Append(list, (PyObject *) h)) {
           Py_DECREF(list);
           Py_DECREF(h);
           return NULL;
       }

       Py_DECREF(h);

       Py_BEGIN_ALLOW_THREADS
       header = headerRead(fd, HEADER_MAGIC_YES);
       Py_END_ALLOW_THREADS
   }

   return list;
}

static PyObject * rpmHeaderFromFD(PyObject * self, PyObject * args) {
   FD_t fd;
   int fileno;
   PyObject * list;

   if (!PyArg_ParseTuple(args, "i", &fileno)) return NULL;
   fd = fdDup(fileno);

   list = rpmReadHeaders (fd);
   Fclose(fd);

   return list;
}


static PyObject * hdrLoad(PyObject * self, PyObject * args) {
   char * obj;
   Header hdr;
   hdrObject * h;
   int len;

   if (!PyArg_ParseTuple(args, "s#", &obj, &len)) return NULL;

   hdr = headerLoad(obj);
   if (!hdr) {
       PyErr_SetString(pyrpmError, "bad header");
       return NULL;
   }

   h = (hdrObject *) PyObject_NEW(PyObject, &hdrType);
   h->h = hdr;
   h->fileList = h->linkList = h->md5list = NULL;
   h->uids = h->gids = h->mtimes = h->fileSizes = NULL;
   h->modes = h->rdevs = NULL;

   return (PyObject *) h;
}

static PyObject * rpmHeaderFromFile(PyObject * self, PyObject * args) {
   char * filespec;
   FD_t fd;
   PyObject * list;

   if (!PyArg_ParseTuple(args, "s", &filespec)) return NULL;
   fd = Fopen(filespec, "r.fdio");

   if (!fd) {
       PyErr_SetFromErrno(pyrpmError);
       return NULL;
   }

   list = rpmReadHeaders (fd);
   Fclose(fd);

   return list;
}

static PyObject * errorCB = NULL, * errorData = NULL;

static void errorcb (void)
{
   PyObject * result, * args = NULL;

   if (errorData)
       args = Py_BuildValue("(O)", errorData);

   result = PyEval_CallObject(errorCB, args);
   Py_XDECREF(args);

   if (result == NULL) {
       PyErr_Print();
       PyErr_Clear();
   }
   Py_DECREF (result);
}

static PyObject * errorSetCallback (PyObject * self, PyObject * args) {
   if (errorCB != NULL) {
       Py_DECREF (errorCB);
       errorCB = NULL;
   }

   if (errorData != NULL) {
       Py_DECREF (errorData);
       errorData = NULL;
   }

   if (!PyArg_ParseTuple(args, "O|O", &errorCB, &errorData)) return NULL;

   /* if we're getting a void*, set the error callback to this. */
   /* also, we can possibly decref any python callbacks we had  */
   /* and set them to NULL.                                     */
   if (PyCObject_Check (errorCB)) {
       rpmErrorSetCallback (PyCObject_AsVoidPtr(errorCB));

       Py_XDECREF (errorCB);
       Py_XDECREF (errorData);

       errorCB   = NULL;
       errorData = NULL;

       Py_INCREF(Py_None);
       return Py_None;
   }

   if (!PyCallable_Check (errorCB)) {
       PyErr_SetString(PyExc_TypeError, "parameter must be callable");
       return NULL;
   }

   Py_INCREF (errorCB);
   Py_XINCREF (errorData);

   return PyCObject_FromVoidPtr(rpmErrorSetCallback (errorcb), NULL);
}

static PyObject * errorString (PyObject * self, PyObject * args) {
   return PyString_FromString(rpmErrorString ());
}

static PyObject * versionCompare (PyObject * self, PyObject * args) {
   hdrObject * h1, * h2;

   if (!PyArg_ParseTuple(args, "O!O!", &hdrType, &h1, &hdrType, &h2)) return NULL;

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

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

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

   if (e1 && !e2)
       return Py_BuildValue("i", 1);
   else if (!e1 && e2)
       return Py_BuildValue("i", -1);
   else if (e1 && e2) {
       int ep1, ep2;
       ep1 = atoi (e1);
       ep2 = atoi (e2);
       if (ep1 < ep2)
           return Py_BuildValue("i", -1);
       else if (ep1 > ep2)
           return Py_BuildValue("i", 1);
   }

   rc = rpmvercmp(v1, v2);
   if (rc)
       return Py_BuildValue("i", rc);

   return Py_BuildValue("i", rpmvercmp(r1, r2));
}

static PyObject * rpmHeaderFromPackage(PyObject * self, PyObject * args) {
   hdrObject * h;
   Header header;
   int rc;
   FD_t fd;
   int rawFd;
   int isSource;

   if (!PyArg_ParseTuple(args, "i", &rawFd)) return NULL;
   fd = fdDup(rawFd);

   rc = rpmReadPackageHeader(fd, &header, &isSource, NULL, NULL);
   Fclose(fd);

   switch (rc) {
     case 0:
       h = (hdrObject *) PyObject_NEW(PyObject, &hdrType);
       h->h = header;
       h->fileList = h->linkList = h->md5list = NULL;
       h->uids = h->gids = h->mtimes = h->fileSizes = NULL;
       h->modes = h->rdevs = NULL;
       break;

     case 1:
       Py_INCREF(Py_None);
       h = (hdrObject *) Py_None;
       break;

     default:
       PyErr_SetString(pyrpmError, "error reading package");
       return NULL;
   }

   return Py_BuildValue("(Oi)", h, isSource);
}

/* methods for rpmdb object */

static PyObject * rpmdbGetAttr(rpmdbObject * s, char * name) {
   return Py_FindMethod(rpmdbMethods, (PyObject * ) s, name);
}

static void rpmdbDealloc(rpmdbObject * s) {
   if (s->db) {
       rpmdbClose(s->db);
   }
}

static PyObject * rpmdbFirst(rpmdbObject * s, PyObject * args) {
   int first;

   if (!PyArg_ParseTuple (args, "")) return NULL;

   first = rpmdbFirstRecNum(s->db);

   if (!first) {
       PyErr_SetString(pyrpmError, "cannot find first entry in database\n");
       return NULL;
   }

   return Py_BuildValue("i", first);
}

static PyObject * rpmdbNext(rpmdbObject * s, PyObject * args) {
   int where;

   if (!PyArg_ParseTuple (args, "i", &where)) return NULL;

   where = rpmdbNextRecNum(s->db, where);

   if (!where) {
       Py_INCREF(Py_None);
       return Py_None;
   }

   return Py_BuildValue("i", where);
}

static PyObject * handleDbResult(int rc, dbiIndexSet matches) {
   PyObject * list;
   int i;

   if (rc == -1) {
       PyErr_SetString(pyrpmError, "error reading from database");
       return NULL;
   }

   list = PyList_New(0);

   if (!rc) {
       for (i = 0; i < matches.count; i++)
           PyList_Append(list, PyInt_FromLong(matches.recs[i].recOffset));

       dbiFreeIndexRecord(matches);
   }

   return list;
}

static PyObject * rpmdbByName(rpmdbObject * s, PyObject * args) {
   char * str;
   dbiIndexSet matches;
   int rc;

   if (!PyArg_ParseTuple(args, "s", &str)) return NULL;

   rc = rpmdbFindPackage(s->db, str, &matches);
   return handleDbResult(rc, matches);
}

static PyObject * rpmdbByFile(rpmdbObject * s, PyObject * args) {
   char * str;
   dbiIndexSet matches;
   int rc;

   if (!PyArg_ParseTuple(args, "s", &str)) return NULL;

   rc = rpmdbFindByFile(s->db, str, &matches);
   return handleDbResult(rc, matches);
}

static PyObject * rpmdbByProvides(rpmdbObject * s, PyObject * args) {
   char * str;
   dbiIndexSet matches;
   int rc;

   if (!PyArg_ParseTuple(args, "s", &str)) return NULL;

   rc = rpmdbFindByProvides(s->db, str, &matches);
   return handleDbResult(rc, matches);
}

static int rpmdbLength(rpmdbObject * s) {
   int first;
   int count = 0;

   first = rpmdbFirstRecNum(s->db);
   if (!first) return 0;

   count++;
   while ((first = rpmdbNextRecNum(s->db, first))) {
       count++;
   }

   return count;
}

static hdrObject * rpmdbSubscript(rpmdbObject * s, PyObject * key) {
   int offset;
   hdrObject * h;

   if (!PyInt_Check(key)) {
       PyErr_SetString(PyExc_TypeError, "integer expected");
       return NULL;
   }

   offset = (int) PyInt_AsLong(key);

   h = PyObject_NEW(hdrObject, &hdrType);
   h->h = NULL;
   h->h = rpmdbGetRecord(s->db, offset);
   h->fileList = h->linkList = h->md5list = NULL;
   h->uids = h->gids = h->mtimes = h->fileSizes = NULL;
   h->modes = h->rdevs = NULL;
   if (!h->h) {
       Py_DECREF(h);
       PyErr_SetString(pyrpmError, "cannot read rpmdb entry");
       return NULL;
   }

   return h;
}

/* methods for header object */

static void hdrDealloc(hdrObject * s) {
   if (s->h) headerFree(s->h);
   if (s->md5list) free(s->md5list);
   if (s->fileList) free(s->fileList);
   if (s->linkList) free(s->linkList);
}

static PyObject * hdrGetAttr(hdrObject * s, char * name) {
   return Py_FindMethod(hdrMethods, (PyObject * ) s, name);
}

static PyObject * hdrSubscript(hdrObject * s, PyObject * item) {
   int type, count, i, tag = -1;
   void * data;
   PyObject * o, * metao;
   char ** stringArray;
   int forceArray = 0;
   int freeData = 0;
   char * str;
   struct headerSprintfExtension * ext = NULL;
   const struct headerSprintfExtension * extensions = rpmHeaderFormats;

   if (PyCObject_Check (item)) {
       ext = PyCObject_AsVoidPtr(item);
   } else if (PyInt_Check(item)) {
       tag = PyInt_AsLong(item);
   } else if (PyString_Check(item)) {
       str = PyString_AsString(item);
       for (i = 0; i < rpmTagTableSize; i++)
           if (!strcasecmp(rpmTagTable[i].name + 7, str)) break;
       if (i < rpmTagTableSize) tag = rpmTagTable[i].val;
       if (tag == -1) {
           /* if we still don't have the tag, go looking for the header
              extensions */
           while (extensions->name) {
               if (extensions->type == HEADER_EXT_TAG
                   && !strcasecmp(extensions->name + 7, str)) {
                   (const struct headerSprintfExtension *) ext = extensions;
               }
               extensions++;
           }
       }
   }

   if (ext) {
       ext->u.tagFunction(s->h, &type, (const void **) &data, &count, &freeData);
   } else {
       if (tag == -1) {
           PyErr_SetString(PyExc_KeyError, "unknown header tag");
           return NULL;
       }

       if (!rpmHeaderGetEntry(s->h, tag, &type, &data, &count)) {
           Py_INCREF(Py_None);
           return Py_None;
       }
   }

   switch (tag) {
     case RPMTAG_OLDFILENAMES:
     case RPMTAG_FILESIZES:
     case RPMTAG_FILESTATES:
     case RPMTAG_FILEMODES:
     case RPMTAG_FILEUIDS:
     case RPMTAG_FILEGIDS:
     case RPMTAG_FILERDEVS:
     case RPMTAG_FILEMTIMES:
     case RPMTAG_FILEMD5S:
     case RPMTAG_FILELINKTOS:
     case RPMTAG_FILEFLAGS:
     case RPMTAG_ROOT:
     case RPMTAG_FILEUSERNAME:
     case RPMTAG_FILEGROUPNAME:
       forceArray = 1;
       break;
     case RPMTAG_SUMMARY:
     case RPMTAG_GROUP:
     case RPMTAG_DESCRIPTION:
       freeData = 1;
       break;
     default:
       break;
   }

   switch (type) {
     case RPM_BIN_TYPE:
       o = PyString_FromStringAndSize(data, count);
       break;

     case RPM_INT32_TYPE:
       if (count != 1 || forceArray) {
           metao = PyList_New(0);
           for (i = 0; i < count; i++) {
               o = PyInt_FromLong(((int *) data)[i]);
               PyList_Append(metao, o);
               Py_DECREF(o);
           }
           o = metao;
       } else {
           o = PyInt_FromLong(*((int *) data));
       }
       break;

     case RPM_CHAR_TYPE:
     case RPM_INT8_TYPE:
       if (count != 1 || forceArray) {
           metao = PyList_New(0);
           for (i = 0; i < count; i++) {
               o = PyInt_FromLong(((char *) data)[i]);
               PyList_Append(metao, o);
               Py_DECREF(o);
           }
           o = metao;
       } else {
           o = PyInt_FromLong(*((char *) data));
       }
       break;

     case RPM_INT16_TYPE:
       if (count != 1 || forceArray) {
           metao = PyList_New(0);
           for (i = 0; i < count; i++) {
               o = PyInt_FromLong(((short *) data)[i]);
               PyList_Append(metao, o);
               Py_DECREF(o);
           }
           o = metao;
       } else {
           o = PyInt_FromLong(*((short *) data));
       }
       break;

     case RPM_STRING_ARRAY_TYPE:
       stringArray = data;

       metao = PyList_New(0);
       for (i = 0; i < count; i++) {
           o = PyString_FromString(stringArray[i]);
           PyList_Append(metao, o);
           Py_DECREF(o);
       }
       o = metao;
       break;

     case RPM_STRING_TYPE:
       if (count != 1 || forceArray) {
           stringArray = data;

           metao = PyList_New(0);
           for (i=0; i < count; i++) {
               o = PyString_FromString(stringArray[i]);
               PyList_Append(metao, o);
               Py_DECREF(o);
           }
           o = metao;
       } else {
           o = PyString_FromString(data);
           if (freeData)
               free (data);
       }
       break;

     default:
       PyErr_SetString(PyExc_TypeError, "unsupported type in header");
       return NULL;
   }

   return o;
}

static PyObject * hdrKeyList(hdrObject * s, PyObject * args) {
   PyObject * list;
   HeaderIterator iter;
   int tag, type;

   if (!PyArg_ParseTuple(args, "")) return NULL;

   list = PyList_New(0);

   iter = headerInitIterator(s->h);
   while (headerNextIterator(iter, &tag, &type, NULL, NULL)) {
       if (tag == HEADER_I18NTABLE) continue;

       switch (type) {
         case RPM_BIN_TYPE:
         case RPM_INT32_TYPE:
         case RPM_CHAR_TYPE:
         case RPM_INT8_TYPE:
         case RPM_INT16_TYPE:
         case RPM_STRING_ARRAY_TYPE:
         case RPM_STRING_TYPE:
           PyList_Append(list, PyInt_FromLong(tag));
       }
   }

   headerFreeIterator(iter);

   return list;
}

static PyObject * hdrUnload(hdrObject * s, PyObject * args) {
   char * buf;
   int len;
   PyObject * rc;

   len = headerSizeof(s->h, 0);
   buf = headerUnload(s->h);

   rc = PyString_FromStringAndSize(buf, len);
   free(buf);

   return rc;
}

/* Returns a list of these tuple for each part which failed:

       (attr_name, correctValue, currentValue)

       It should be passwd the file number to verify.
*/
static PyObject * hdrVerifyFile(hdrObject * s, PyObject * args) {
   int fileNumber;
   int verifyResult;
   PyObject * list, * tuple, * attrName;
   int type, count;
   struct stat sb;
   char buf[2048];
   int i;
   time_t timeInt;
   struct tm * timeStruct;

   if (!PyInt_Check(args)) {
       PyErr_SetString(PyExc_TypeError, "integer expected");
       return NULL;
   }

   fileNumber = (int) PyInt_AsLong(args);

   if (rpmVerifyFile("", s->h, fileNumber, &verifyResult, 0)) {
       Py_INCREF(Py_None);
       return Py_None;
   }

   list = PyList_New(0);

   if (!verifyResult) return list;

   if (!s->fileList) {
       headerGetEntry(s->h, RPMTAG_OLDFILENAMES, &type, (void **) &s->fileList,
                &count);
   }

   lstat(s->fileList[fileNumber], &sb);

   if (verifyResult & RPMVERIFY_MD5) {
       if (!s->md5list) {
           headerGetEntry(s->h, RPMTAG_FILEMD5S, &type, (void **) &s->md5list,
                    &count);
       }

       if (mdfile(s->fileList[fileNumber], buf)) {
           strcpy(buf, "(unknown)");
       }

       tuple = PyTuple_New(3);
       attrName = PyString_FromString("checksum");
       PyTuple_SetItem(tuple, 0, attrName);
       PyTuple_SetItem(tuple, 1, PyString_FromString(s->md5list[fileNumber]));
       PyTuple_SetItem(tuple, 2, PyString_FromString(buf));
       PyList_Append(list, tuple);
   }

   if (verifyResult & RPMVERIFY_FILESIZE) {
       if (!s->fileSizes) {
           headerGetEntry(s->h, RPMTAG_FILESIZES, &type, (void **) &s->fileSizes,
                    &count);

       }

       tuple = PyTuple_New(3);
       attrName = PyString_FromString("size");
       PyTuple_SetItem(tuple, 0, attrName);

       sprintf(buf, "%d", 100);
       PyTuple_SetItem(tuple, 1, PyString_FromString(buf));
       sprintf(buf, "%ld", sb.st_size);
       PyTuple_SetItem(tuple, 2, PyString_FromString(buf));
       PyList_Append(list, tuple);
   }

   if (verifyResult & RPMVERIFY_LINKTO) {
       if (!s->linkList) {
           headerGetEntry(s->h, RPMTAG_FILELINKTOS, &type, (void **) &s->linkList,
                    &count);
       }

       i = readlink(s->fileList[fileNumber], buf, sizeof(buf));
       if (i <= 0)
           strcpy(buf, "(unknown)");
       else
           buf[i] = '\0';

       tuple = PyTuple_New(3);
       attrName = PyString_FromString("link");
       PyTuple_SetItem(tuple, 0, attrName);
       PyTuple_SetItem(tuple, 1, PyString_FromString(s->linkList[fileNumber]));
       PyTuple_SetItem(tuple, 2, PyString_FromString(buf));
       PyList_Append(list, tuple);
   }

   if (verifyResult & RPMVERIFY_MTIME) {
       if (!s->mtimes) {
           headerGetEntry(s->h, RPMTAG_FILEMTIMES, &type, (void **) &s->mtimes,
                    &count);
       }

       tuple = PyTuple_New(3);
       attrName = PyString_FromString("time");
       PyTuple_SetItem(tuple, 0, attrName);

       timeInt = sb.st_mtime;
       timeStruct = localtime(&timeInt);
       strftime(buf, sizeof(buf) - 1, "%c", timeStruct);
       PyTuple_SetItem(tuple, 1, PyString_FromString(buf));

       timeInt = s->mtimes[fileNumber];
       timeStruct = localtime(&timeInt);
       strftime(buf, sizeof(buf) - 1, "%c", timeStruct);

       PyTuple_SetItem(tuple, 2, PyString_FromString(buf));

       PyList_Append(list, tuple);
   }

   if (verifyResult & RPMVERIFY_RDEV) {
       if (!s->rdevs) {
           headerGetEntry(s->h, RPMTAG_FILERDEVS, &type, (void **) &s->rdevs,
                    &count);
       }

       tuple = PyTuple_New(3);
       attrName = PyString_FromString("device");

       PyTuple_SetItem(tuple, 0, attrName);
       sprintf(buf, "0x%-4x", s->rdevs[fileNumber]);
       PyTuple_SetItem(tuple, 1, PyString_FromString(buf));
       sprintf(buf, "0x%-4x", (unsigned int) sb.st_rdev);
       PyTuple_SetItem(tuple, 2, PyString_FromString(buf));
       PyList_Append(list, tuple);
   }

   /* RPMVERIFY_USER and RPM_VERIFY_GROUP are handled wrong here, but rpmlib.a
      doesn't do these correctly either. At least this is consisten */
   if (verifyResult & RPMVERIFY_USER) {
       if (!s->uids) {
           headerGetEntry(s->h, RPMTAG_FILEUIDS, &type, (void **) &s->uids,
                    &count);
       }

       tuple = PyTuple_New(3);
       attrName = PyString_FromString("uid");
       PyTuple_SetItem(tuple, 0, attrName);
       sprintf(buf, "%d", s->uids[fileNumber]);
       PyTuple_SetItem(tuple, 1, PyString_FromString(buf));
       sprintf(buf, "%d", sb.st_uid);
       PyTuple_SetItem(tuple, 2, PyString_FromString(buf));
       PyList_Append(list, tuple);
   }

   if (verifyResult & RPMVERIFY_GROUP) {
       if (!s->gids) {
           headerGetEntry(s->h, RPMTAG_FILEGIDS, &type, (void **) &s->gids,
                    &count);
       }

       tuple = PyTuple_New(3);
       attrName = PyString_FromString("gid");
       PyTuple_SetItem(tuple, 0, attrName);
       sprintf(buf, "%d", s->gids[fileNumber]);
       PyTuple_SetItem(tuple, 1, PyString_FromString(buf));
       sprintf(buf, "%d", sb.st_gid);
       PyTuple_SetItem(tuple, 2, PyString_FromString(buf));
       PyList_Append(list, tuple);
   }

   if (verifyResult & RPMVERIFY_MODE) {
       if (!s->modes) {
           headerGetEntry(s->h, RPMTAG_FILEMODES, &type, (void **) &s->modes,
                    &count);
       }

       tuple = PyTuple_New(3);
       attrName = PyString_FromString("permissions");
       PyTuple_SetItem(tuple, 0, attrName);
       sprintf(buf, "0%-4o", s->modes[fileNumber]);
       PyTuple_SetItem(tuple, 1, PyString_FromString(buf));
       sprintf(buf, "0%-4o", sb.st_mode);
       PyTuple_SetItem(tuple, 2, PyString_FromString(buf));
       PyList_Append(list, tuple);
   }

   return list;
}

static PyObject * hdrCompressFilelist(hdrObject * s, PyObject * args) {
   compressFilelist (s->h);

   Py_INCREF(Py_None);
   return Py_None;
}

static PyObject * hdrExpandFilelist(hdrObject * s, PyObject * args) {
   expandFilelist (s->h);

   Py_INCREF(Py_None);
   return Py_None;
}

static PyObject * hdrFullFilelist(hdrObject * s, PyObject * args) {
   mungeFilelist (s->h);

   Py_INCREF(Py_None);
   return Py_None;
}

static PyObject * rpmtransCreate(PyObject * self, PyObject * args) {
   rpmtransObject * o;
   rpmdbObject * db = NULL;
   char * rootPath = "/";

   if (!PyArg_ParseTuple(args, "|sO", &rootPath, &db)) return NULL;
   if (db && db->ob_type != &rpmdbType) {
       PyErr_SetString(PyExc_TypeError, "bad type for database argument");
       return NULL;
   }

   o = (void *) PyObject_NEW(rpmtransObject, &rpmtransType);

   Py_XINCREF(db);
   o->dbo = db;
   o->scriptFd = NULL;
   o->ts = rpmtransCreateSet(db ? db->db : NULL, rootPath);
   o->keyList = PyList_New(0);

   return (void *) o;
}

static void rpmtransDealloc(PyObject * o) {
   rpmtransObject * trans = (void *) o;

   rpmtransFree(trans->ts);
   if (trans->dbo) {
       Py_DECREF(trans->dbo);
   }
   if (trans->scriptFd) Fclose(trans->scriptFd);
   /* this will free the keyList, and decrement the ref count of all
      the items on the list as well :-) */
   Py_DECREF(trans->keyList);
}

static PyObject * rpmtransGetAttr(rpmtransObject * o, char * name) {
   return Py_FindMethod(rpmtransMethods, (PyObject *) o, name);
}

static int rpmtransSetAttr(rpmtransObject * o, char * name,
                          PyObject * val) {
   int i;

   if (!strcmp(name, "scriptFd")) {
       if (!PyArg_Parse(val, "i", &i)) return 0;
       if (i < 0) {
           PyErr_SetString(PyExc_TypeError, "bad file descriptor");
           return -1;
       } else {
           o->scriptFd = fdDup(i);
           rpmtransSetScriptFd(o->ts, o->scriptFd);
       }
   } else {
       PyErr_SetString(PyExc_AttributeError, name);
       return -1;
   }

   return 0;
}

static PyObject * rpmtransAdd(rpmtransObject * s, PyObject * args) {
   hdrObject * h;
   PyObject * key;
   char * how = NULL;
   int isUpgrade = 0;

   if (!PyArg_ParseTuple(args, "OO|s", &h, &key, &how)) return NULL;
   if (h->ob_type != &hdrType) {
       PyErr_SetString(PyExc_TypeError, "bad type for header argument");
       return NULL;
   }

   if (how && strcmp(how, "a") && strcmp(how, "u") && strcmp(how, "i")) {
       PyErr_SetString(PyExc_TypeError, "how argument must be \"u\", \"a\", or \"i\"");
       return NULL;
   } else if (how && !strcmp(how, "u"))
       isUpgrade = 1;

   if (how && !strcmp(how, "a"))
       rpmtransAvailablePackage(s->ts, h->h, key);
   else
       rpmtransAddPackage(s->ts, h->h, NULL, key, isUpgrade, NULL);

   /* This should increment the usage count for me */
   if (key) PyList_Append(s->keyList, key);

   Py_INCREF(Py_None);
   return Py_None;
}

static PyObject * rpmtransOrder(rpmtransObject * s, PyObject * args) {
   if (!PyArg_ParseTuple(args, "")) return NULL;

   rpmdepOrder(s->ts);

   Py_INCREF(Py_None);
   return Py_None;
}

static PyObject * rpmtransDepCheck(rpmtransObject * s, PyObject * args) {
   struct rpmDependencyConflict * conflicts;
   int numConflicts;
   PyObject * list, * cf;
   int i;

   if (!PyArg_ParseTuple(args, "")) return NULL;

   rpmdepCheck(s->ts, &conflicts, &numConflicts);
   if (numConflicts) {
       list = PyList_New(0);

       for (i = 0; i < numConflicts; i++) {
           cf = Py_BuildValue("((sss)(ss)iOi)", conflicts[i].byName,
                              conflicts[i].byVersion, conflicts[i].byRelease,

                              conflicts[i].needsName,
                              conflicts[i].needsVersion,

                              conflicts[i].needsFlags,
                              conflicts[i].suggestedPackage ?
                                  conflicts[i].suggestedPackage : Py_None,
                              conflicts[i].sense);
           PyList_Append(list, (PyObject *) cf);
           Py_DECREF(cf);
       }

       rpmdepFreeConflicts(conflicts, numConflicts);

       return list;
   }

   Py_INCREF(Py_None);
   return Py_None;
}

struct tsCallbackType {
   PyObject * cb;
   PyObject * data;
   int pythonError;
};

static void * tsCallback(const Header h, const rpmCallbackType what,
                        const unsigned long amount, const unsigned long total,
                        const void * pkgKey, void * data) {
   struct tsCallbackType * cbInfo = data;
   PyObject * args, * result;
   int fd;
   static FD_t fdt;

   if (cbInfo->pythonError) return NULL;

   if (!pkgKey) pkgKey = Py_None;

   args = Py_BuildValue("(illOO)", what, amount, total, pkgKey, cbInfo->data);
   result = PyEval_CallObject(cbInfo->cb, args);
   Py_DECREF(args);

   if (!result) {
       cbInfo->pythonError = 1;
       return NULL;
   }

   if (what == RPMCALLBACK_INST_OPEN_FILE) {
       if (!PyArg_Parse(result, "i", &fd)) {
           cbInfo->pythonError = 1;
           return NULL;
       }
       fdt = fdDup(fd);

       Py_DECREF(result);
       return fdt;
   }

   if (what == RPMCALLBACK_INST_CLOSE_FILE) {
       Fclose (fdt);
   }

   Py_DECREF(result);

   return NULL;
}

static PyObject * rpmtransRun(rpmtransObject * s, PyObject * args) {
   int flags, ignoreSet;
   int rc, i;
   PyObject * list, * prob;
   rpmProblemSet probs;
   struct tsCallbackType cbInfo;

   if (!PyArg_ParseTuple(args, "iiOO", &flags, &ignoreSet, &cbInfo.cb,
                         &cbInfo.data))
       return NULL;

   cbInfo.pythonError = 0;

   rc = rpmRunTransactions(s->ts, tsCallback, &cbInfo, NULL, &probs, flags,
                           ignoreSet);

   if (cbInfo.pythonError) {
       if (rc > 0)
           rpmProblemSetFree(probs);
       return NULL;
   }

   if (rc < 0) {
       list = PyList_New(0);
       return list;
   } else if (!rc) {
       Py_INCREF(Py_None);
       return Py_None;
   }

   list = PyList_New(0);
   for (i = 0; i < probs->numProblems; i++) {
       prob = Py_BuildValue("s(isi)", rpmProblemString(probs->probs[i]),
                            probs->probs[i].type,
                            probs->probs[i].str1,
                            probs->probs[i].ulong1);
       PyList_Append(list, prob);
       Py_DECREF(prob);
   }

   rpmProblemSetFree(probs);

   return list;
}

static PyObject * archScore(PyObject * self, PyObject * args) {
   char * arch;
   int score;

   if (!PyArg_ParseTuple(args, "s", &arch))
       return NULL;

   score = rpmMachineScore(RPM_MACHTABLE_INSTARCH, arch);

   return Py_BuildValue("i", score);
}

static PyObject * doAddMacro(PyObject * self, PyObject * args) {
   char * name, * val;

   if (!PyArg_ParseTuple(args, "ss", &name, &val))
       return NULL;

   addMacro(NULL, name, NULL, val, RMIL_DEFAULT);

   Py_INCREF(Py_None);
   return Py_None;
}

static PyObject * doDelMacro(PyObject * self, PyObject * args) {
   char * name;

   if (!PyArg_ParseTuple(args, "s", &name))
       return NULL;

   delMacro(NULL, name);

   Py_INCREF(Py_None);
   return Py_None;
}

typedef struct FDlist_t FDlist;

struct FDlist_t {
   FILE *f;
   FD_t fd;
   FDlist *next;
} ;

static FDlist *fdhead = NULL;
static FDlist *fdtail = NULL;

static int closeCallback(FILE * f) {
   FDlist *node, *last;

   node = fdhead;
   last = NULL;
   while (node) {
       if (node->f == f)
           break;
       last = node;
       node = node->next;
   }
   if (node) {
       if (last)
           last->next = node->next;
       else
           fdhead = node->next;
       printf ("closing\n");
       node->fd = fdLink(node->fd, "closeCallback");
       Fclose (node->fd);
       while (node->fd)
           node->fd = fdFree(node->fd, "closeCallback");
       free (node);
   }
   return 0;
}

static PyObject * doFopen(PyObject * self, PyObject * args) {
   char * path, * mode;
   FDlist *node;

   if (!PyArg_ParseTuple(args, "ss", &path, &mode))
       return NULL;

   node = malloc (sizeof(FDlist));

   node->fd = Fopen(path, mode);
   node->fd = fdLink(node->fd, "doFopen");

   if (!node->fd) {
       PyErr_SetFromErrno(pyrpmError);
       free (node);
       return NULL;
   }

   if (Ferror(node->fd)) {
       const char *err = Fstrerror(node->fd);
       free(node);
       if (err) {
           PyErr_SetString(pyrpmError, err);
           return NULL;
       }
   }
   node->f = fdGetFp(node->fd);
   if (!node->f) {
       PyErr_SetString(pyrpmError, "FD_t has no FILE*");
       free(node);
       return NULL;
   }

   node->next = NULL;
   if (fdtail) {
       fdtail->next = node;
   } else {
       fdhead = node;
   }
   fdtail = node;

   return PyFile_FromFile (node->f, path, mode, closeCallback);
}