Decouple python spec objects from transaction objects
[platform/upstream/rpm.git] / python / rpmts-py.c
1 #include "rpmsystem-py.h"
2
3 #include <rpm/rpmlib.h> /* rpmReadPackageFile, headerCheck */
4 #include <rpm/rpmtag.h>
5 #include <rpm/rpmpgp.h>
6 #include <rpm/rpmdb.h>
7 #include <rpm/rpmbuild.h>
8
9 #include "header-py.h"
10 #include "rpmds-py.h"   /* XXX for rpmdsNew */
11 #include "rpmfd-py.h"
12 #include "rpmfi-py.h"   /* XXX for rpmfiNew */
13 #include "rpmmi-py.h"
14 #include "rpmps-py.h"
15 #include "rpmte-py.h"
16 #include "spec-py.h"
17
18 #include "rpmts-py.h"
19
20 #include "debug.h"
21
22 /** \ingroup python
23  * \name Class: Rpmts
24  * \class Rpmts
25  * \brief A python rpm.ts object represents an RPM transaction set.
26  *
27  * The transaction set is the workhorse of RPM.  It performs the
28  * installation and upgrade of packages.  The rpm.ts object is
29  * instantiated by the TransactionSet function in the rpm module.
30  *
31  * The TransactionSet function takes two optional arguments. The first
32  * argument is the root path. The second is the verify signature disable flags,
33  * a set of the following bits:
34  *
35  * -    rpm.RPMVSF_NOHDRCHK     if set, don't check rpmdb headers
36  * -    rpm.RPMVSF_NEEDPAYLOAD  if not set, check header+payload (if possible)
37  * -    rpm.RPMVSF_NOSHA1HEADER if set, don't check header SHA1 digest
38  * -    rpm.RPMVSF_NODSAHEADER  if set, don't check header DSA signature
39  * -    rpm.RPMVSF_NOMD5        if set, don't check header+payload MD5 digest
40  * -    rpm.RPMVSF_NODSA        if set, don't check header+payload DSA signature
41  * -    rpm.RPMVSF_NORSA        if set, don't check header+payload RSA signature
42  *
43  * For convenience, there are the following masks:
44  * -    rpm._RPMVSF_NODIGESTS           if set, don't check digest(s).
45  * -    rpm._RPMVSF_NOSIGNATURES        if set, don't check signature(s).
46  *
47  * A rpm.ts object has the following methods:
48  *
49  * - addInstall(hdr,data,mode)  Add an install element to a transaction set.
50  * @param hdr   the header to be added
51  * @param data  user data that will be passed to the transaction callback
52  *              during transaction execution
53  * @param mode  optional argument that specifies if this package should
54  *              be installed ('i'), upgraded ('u').
55  *
56  * - addErase(name) Add an erase element to a transaction set.
57  * @param name  the package name to be erased
58  *
59  * - check()    Perform a dependency check on the transaction set. After
60  *              headers have been added to a transaction set, a dependency
61  *              check can be performed to make sure that all package
62  *              dependencies are satisfied.
63  * @return      None If there are no unresolved dependencies
64  *              Otherwise a list of complex tuples is returned, one tuple per
65  *              unresolved dependency, with
66  * The format of the dependency tuple is:
67  *     ((packageName, packageVersion, packageRelease),
68  *      (reqName, reqVersion),
69  *      needsFlags,
70  *      suggestedPackage,
71  *      sense)
72  *     packageName, packageVersion, packageRelease are the name,
73  *     version, and release of the package that has the unresolved
74  *     dependency or conflict.
75  *     The reqName and reqVersion are the name and version of the
76  *     requirement or conflict.
77  *     The needsFlags is a bitfield that describes the versioned
78  *     nature of a requirement or conflict.  The constants
79  *     rpm.RPMSENSE_LESS, rpm.RPMSENSE_GREATER, and
80  *     rpm.RPMSENSE_EQUAL can be logical ANDed with the needsFlags
81  *     to get versioned dependency information.
82  *     suggestedPackage is a tuple if the dependency check was aware
83  *     of a package that solves this dependency problem when the
84  *     dependency check was run.  Packages that are added to the
85  *     transaction set as "available" are examined during the
86  *     dependency check as possible dependency solvers. The tuple
87  *     contains two values, (header, suggestedName).  These are set to
88  *     the header of the suggested package and its name, respectively.
89  *     If there is no known package to solve the dependency problem,
90  *     suggestedPackage is None.
91  *     The constants rpm.RPMDEP_SENSE_CONFLICTS and
92  *     rpm.RPMDEP_SENSE_REQUIRES are set to show a dependency as a
93  *     requirement or a conflict.
94  *
95  * - ts.order() Do a topological sort of added element relations.
96  * @return      None
97  *
98  * - ts.setFlags(transFlags) Set transaction set flags.
99  * @param transFlags - bit(s) to controll transaction operations. The
100  *              following values can be logically OR'ed together:
101  *      - rpm.RPMTRANS_FLAG_TEST - test mode, do not modify the RPM
102  *              database, change any files, or run any package scripts
103  *      - rpm.RPMTRANS_FLAG_BUILD_PROBS - only build a list of
104  *              problems encountered when attempting to run this transaction
105  *              set
106  *      - rpm.RPMTRANS_FLAG_NOSCRIPTS - do not execute package scripts
107  *      - rpm.RPMTRANS_FLAG_JUSTDB - only make changes to the rpm
108  *              database, do not modify files.
109  *      - rpm.RPMTRANS_FLAG_NOTRIGGERS - do not run trigger scripts
110  *      - rpm.RPMTRANS_FLAG_NODOCS - do not install files marked as %doc
111  *      - rpm.RPMTRANS_FLAG_ALLFILES - create all files, even if a
112  *              file is marked %config(missingok) and an upgrade is
113  *              being performed.
114  *      - rpm.RPMTRANS_FLAG_KEEPOBSOLETE - do not remove obsoleted
115  *              packages.
116  * @return      previous transFlags
117  *
118  * - ts.setProbFilter(ignoreSet) Set transaction set problem filter.
119  * @param problemSetFilter - control bit(s) to ignore classes of problems,
120  *              a logical or of one or more of the following bit(s):
121  *      - rpm.RPMPROB_FILTER_IGNOREOS -
122  *      - rpm.RPMPROB_FILTER_IGNOREARCH -
123  *      - rpm.RPMPROB_FILTER_REPLACEPKG -
124  *      - rpm.RPMPROB_FILTER_FORCERELOCATE -
125  *      - rpm.RPMPROB_FILTER_REPLACENEWFILES -
126  *      - rpm.RPMPROB_FILTER_REPLACEOLDFILES -
127  *      - rpm.RPMPROB_FILTER_OLDPACKAGE -
128  *      - rpm.RPMPROB_FILTER_DISKSPACE -
129  * @return      previous ignoreSet
130  *
131  * - ts.run(callback,data) Attempt to execute a transaction set.
132  *      After the transaction set has been populated with install/upgrade or
133  *      erase actions, the transaction set can be executed by invoking
134  *      the ts.run() method.
135  */
136
137 struct rpmtsObject_s {
138     PyObject_HEAD
139     PyObject *md_dict;          /*!< to look like PyModuleObject */
140     rpmts       ts;
141     PyObject * keyList;         /* keeps reference counts correct */
142     FD_t scriptFd;
143     rpmtsi tsi;
144     rpmElementType tsiFilter;
145     rpmprobFilterFlags ignoreSet;
146 };
147
148 struct rpmtsCallbackType_s {
149     PyObject * cb;
150     PyObject * data;
151     rpmtsObject * tso;
152     PyThreadState *_save;
153 };
154
155 RPM_GNUC_NORETURN
156 static void die(PyObject *cb)
157 {
158     char *pyfn = NULL;
159     PyObject *r;
160
161     if (PyErr_Occurred()) {
162         PyErr_Print();
163     }
164     if ((r = PyObject_Repr(cb)) != NULL) { 
165         pyfn = PyString_AsString(r);
166     }
167     fprintf(stderr, _("error: python callback %s failed, aborting!\n"), 
168                       pyfn ? pyfn : "???");
169     rpmdbCheckTerminate(1);
170     exit(EXIT_FAILURE);
171 }
172
173 static PyObject *
174 rpmts_AddInstall(rpmtsObject * s, PyObject * args, PyObject * kwds)
175 {
176     hdrObject * h;
177     PyObject * key;
178     char * how = "u";   /* XXX default to upgrade element if missing */
179     int isUpgrade = 0;
180     char * kwlist[] = {"header", "key", "how", NULL};
181     int rc = 0;
182
183     if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!O|s:AddInstall", kwlist,
184             &hdr_Type, &h, &key, &how))
185         return NULL;
186
187     {   PyObject * hObj = (PyObject *) h;
188         if (hObj->ob_type != &hdr_Type) {
189             PyErr_SetString(PyExc_TypeError, "bad type for header argument");
190             return NULL;
191         }
192     }
193
194     if (how && !rstreq(how, "u") && !rstreq(how, "i")) {
195         PyErr_SetString(PyExc_TypeError, "how argument must be \"u\" or \"i\"");
196         return NULL;
197     } else if (how && rstreq(how, "u"))
198         isUpgrade = 1;
199
200     rc = rpmtsAddInstallElement(s->ts, hdrGetHeader(h), key, isUpgrade, NULL);
201     if (rc) {
202         PyErr_SetString(pyrpmError, "adding package to transaction failed");
203         return NULL;
204     }
205         
206
207     /* This should increment the usage count for me */
208     if (key)
209         PyList_Append(s->keyList, key);
210
211     Py_RETURN_NONE;
212 }
213
214 /* TODO Permit finer control (i.e. not just --allmatches) of deleted elments.*/
215 static PyObject *
216 rpmts_AddErase(rpmtsObject * s, PyObject * args, PyObject * kwds)
217 {
218     PyObject * o;
219     int installed = 0;
220     rpmdbMatchIterator mi = NULL;
221     Header h;
222     char * kwlist[] = {"name", NULL};
223
224     if (!PyArg_ParseTupleAndKeywords(args, kwds, "O:AddErase", kwlist, &o))
225         return NULL;
226
227     if (PyString_Check(o)) {
228         char * name = PyString_AsString(o);
229         mi = rpmtsInitIterator(s->ts, RPMDBI_LABEL, name, 0);
230     } else if (PyInt_Check(o)) {
231         uint32_t recno = PyInt_AsLong(o);
232         mi = rpmtsInitIterator(s->ts, RPMDBI_PACKAGES, &recno, sizeof(recno));
233     } else {
234         PyErr_SetString(PyExc_TypeError, "string or integer expected");
235         return NULL;
236     }
237
238     while ((h = rpmdbNextIterator(mi)) != NULL) {
239         installed++;
240         rpmtsAddEraseElement(s->ts, h, -1);
241     }
242     rpmdbFreeIterator(mi);
243     
244     if (installed) {
245         Py_RETURN_NONE;
246     } else {
247         PyErr_SetString(pyrpmError, "package not installed");
248         return NULL;
249     }
250 }
251
252 static int
253 rpmts_SolveCallback(rpmts ts, rpmds ds, const void * data)
254 {
255     struct rpmtsCallbackType_s * cbInfo = (struct rpmtsCallbackType_s *) data;
256     PyObject * args, * result;
257     int res = 1;
258
259     if (cbInfo->tso == NULL) return res;
260     if (cbInfo->cb == Py_None) return res;
261
262     PyEval_RestoreThread(cbInfo->_save);
263
264     args = Py_BuildValue("(Oissi)", cbInfo->tso,
265                 rpmdsTagN(ds), rpmdsN(ds), rpmdsEVR(ds), rpmdsFlags(ds));
266     result = PyEval_CallObject(cbInfo->cb, args);
267     Py_DECREF(args);
268
269     if (!result) {
270         die(cbInfo->cb);
271     } else {
272         if (PyInt_Check(result))
273             res = PyInt_AsLong(result);
274         Py_DECREF(result);
275     }
276
277     cbInfo->_save = PyEval_SaveThread();
278
279     return res;
280 }
281
282 static PyObject *
283 rpmts_Check(rpmtsObject * s, PyObject * args, PyObject * kwds)
284 {
285     rpmps ps;
286     rpmProblem p;
287     PyObject * list, * cf;
288     struct rpmtsCallbackType_s cbInfo;
289     int xx;
290     char * kwlist[] = {"callback", NULL};
291
292     memset(&cbInfo, 0, sizeof(cbInfo));
293     if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O:Check", kwlist,
294             &cbInfo.cb))
295         return NULL;
296
297     if (cbInfo.cb != NULL) {
298         if (!PyCallable_Check(cbInfo.cb)) {
299             PyErr_SetString(PyExc_TypeError, "expected a callable");
300             return NULL;
301         }
302         xx = rpmtsSetSolveCallback(s->ts, rpmts_SolveCallback, (void *)&cbInfo);
303     }
304
305     cbInfo.tso = s;
306     cbInfo._save = PyEval_SaveThread();
307
308     xx = rpmtsCheck(s->ts);
309     ps = rpmtsProblems(s->ts);
310
311     PyEval_RestoreThread(cbInfo._save);
312
313     if (ps != NULL) {
314         list = PyList_New(0);
315         rpmpsi psi = rpmpsInitIterator(ps);
316
317         /* XXX TODO: rpmlib >= 4.0.3 can return multiple suggested keys. */
318         while (rpmpsNextIterator(psi) >= 0) {
319             char * altNEVR, * needsName;
320             char * byName, * byVersion, * byRelease, *byArch;
321             char * needsOP, * needsVersion;
322             rpmsenseFlags needsFlags, sense;
323             fnpyKey key;
324
325             p = rpmpsGetProblem(psi);
326
327             byName = xstrdup(rpmProblemGetPkgNEVR(p));
328             if ((byArch= strrchr(byName, '.')) != NULL)
329                 *byArch++ = '\0';
330             if ((byRelease = strrchr(byName, '-')) != NULL)
331                 *byRelease++ = '\0';
332             if ((byVersion = strrchr(byName, '-')) != NULL)
333                 *byVersion++ = '\0';
334
335             key = rpmProblemGetKey(p);
336
337             altNEVR = needsName = xstrdup(rpmProblemGetAltNEVR(p));
338             if (needsName[1] == ' ') {
339                 sense = (needsName[0] == 'C')
340                         ? RPMDEP_SENSE_CONFLICTS : RPMDEP_SENSE_REQUIRES;
341                 needsName += 2;
342             } else
343                 sense = RPMDEP_SENSE_REQUIRES;
344             if ((needsVersion = strrchr(needsName, ' ')) != NULL)
345                 *needsVersion++ = '\0';
346
347             needsFlags = 0;
348             if ((needsOP = strrchr(needsName, ' ')) != NULL) {
349                 for (*needsOP++ = '\0'; *needsOP != '\0'; needsOP++) {
350                     if (*needsOP == '<')        needsFlags |= RPMSENSE_LESS;
351                     else if (*needsOP == '>')   needsFlags |= RPMSENSE_GREATER;
352                     else if (*needsOP == '=')   needsFlags |= RPMSENSE_EQUAL;
353                 }
354             }
355
356             cf = Py_BuildValue("((sss)(ss)iOi)", byName, byVersion, byRelease,
357                                needsName, needsVersion, needsFlags,
358                                (key != NULL ? key : Py_None),
359                                sense);
360             PyList_Append(list, (PyObject *) cf);
361             Py_DECREF(cf);
362             free(byName);
363             free(altNEVR);
364         }
365
366         psi = rpmpsFreeIterator(psi);
367         ps = rpmpsFree(ps);
368
369         return list;
370     }
371
372     Py_RETURN_NONE;
373 }
374
375 static PyObject *
376 rpmts_Order(rpmtsObject * s)
377 {
378     int rc;
379
380     Py_BEGIN_ALLOW_THREADS
381     rc = rpmtsOrder(s->ts);
382     Py_END_ALLOW_THREADS
383
384     return Py_BuildValue("i", rc);
385 }
386
387 static PyObject *
388 rpmts_Clean(rpmtsObject * s)
389 {
390     rpmtsClean(s->ts);
391
392     Py_RETURN_NONE;
393 }
394
395 static PyObject *
396 rpmts_OpenDB(rpmtsObject * s)
397 {
398     int dbmode;
399
400     dbmode = rpmtsGetDBMode(s->ts);
401     if (dbmode == -1)
402         dbmode = O_RDONLY;
403
404     return Py_BuildValue("i", rpmtsOpenDB(s->ts, dbmode));
405 }
406
407 static PyObject *
408 rpmts_CloseDB(rpmtsObject * s)
409 {
410     int rc;
411
412     rc = rpmtsCloseDB(s->ts);
413     rpmtsSetDBMode(s->ts, -1);  /* XXX disable lazy opens */
414
415     return Py_BuildValue("i", rc);
416 }
417
418 static PyObject *
419 rpmts_InitDB(rpmtsObject * s)
420 {
421     int rc;
422
423     rc = rpmtsInitDB(s->ts, O_RDONLY);
424     if (rc == 0)
425         rc = rpmtsCloseDB(s->ts);
426
427     return Py_BuildValue("i", rc);
428 }
429
430 static PyObject *
431 rpmts_RebuildDB(rpmtsObject * s)
432 {
433     int rc;
434
435     Py_BEGIN_ALLOW_THREADS
436     rc = rpmtsRebuildDB(s->ts);
437     Py_END_ALLOW_THREADS
438
439     return Py_BuildValue("i", rc);
440 }
441
442 static PyObject *
443 rpmts_VerifyDB(rpmtsObject * s)
444 {
445     int rc;
446
447     Py_BEGIN_ALLOW_THREADS
448     rc = rpmtsVerifyDB(s->ts);
449     Py_END_ALLOW_THREADS
450
451     return Py_BuildValue("i", rc);
452 }
453
454 static PyObject *
455 rpmts_HdrFromFdno(rpmtsObject * s, PyObject * args, PyObject * kwds)
456 {
457     PyObject * result = NULL;
458     Header h;
459     FD_t fd;
460     rpmRC rpmrc;
461     char * kwlist[] = {"fd", NULL};
462
463     if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&:HdrFromFdno", kwlist,
464                                      rpmFdFromPyObject, &fd))
465         return NULL;
466
467     rpmrc = rpmReadPackageFile(s->ts, fd, "rpmts_HdrFromFdno", &h);
468     Fclose(fd);
469
470     switch (rpmrc) {
471     case RPMRC_OK:
472         if (h)
473             result = Py_BuildValue("N", hdr_Wrap(h));
474         h = headerFree(h);      /* XXX ref held by result */
475         break;
476
477     case RPMRC_NOKEY:
478         PyErr_SetString(pyrpmError, "public key not available");
479         break;
480
481     case RPMRC_NOTTRUSTED:
482         PyErr_SetString(pyrpmError, "public key not trusted");
483         break;
484
485     case RPMRC_NOTFOUND:
486     case RPMRC_FAIL:
487     default:
488         PyErr_SetString(pyrpmError, "error reading package header");
489         break;
490     }
491
492     return result;
493 }
494
495 static PyObject *
496 rpmts_HdrCheck(rpmtsObject * s, PyObject * args, PyObject * kwds)
497 {
498     PyObject * blob;
499     PyObject * result = NULL;
500     char * msg = NULL;
501     const void * uh;
502     int uc;
503     rpmRC rpmrc;
504     char * kwlist[] = {"headers", NULL};
505
506     if (!PyArg_ParseTupleAndKeywords(args, kwds, "O:HdrCheck", kwlist, &blob))
507         return NULL;
508
509     if (blob == Py_None) {
510         Py_RETURN_NONE;
511     }
512     if (!PyString_Check(blob)) {
513         PyErr_SetString(pyrpmError, "hdrCheck takes a string of octets");
514         return result;
515     }
516     uh = PyString_AsString(blob);
517     uc = PyString_Size(blob);
518
519     rpmrc = headerCheck(s->ts, uh, uc, &msg);
520
521     switch (rpmrc) {
522     case RPMRC_OK:
523         Py_INCREF(Py_None);
524         result = Py_None;
525         break;
526
527     case RPMRC_NOKEY:
528         PyErr_SetString(pyrpmError, "public key not availaiable");
529         break;
530
531     case RPMRC_NOTTRUSTED:
532         PyErr_SetString(pyrpmError, "public key not trusted");
533         break;
534
535     case RPMRC_FAIL:
536     default:
537         PyErr_SetString(pyrpmError, msg);
538         break;
539     }
540     msg = _free(msg);
541
542     return result;
543 }
544
545 static PyObject *
546 rpmts_SetVSFlags(rpmtsObject * s, PyObject * args, PyObject * kwds)
547 {
548     rpmVSFlags vsflags;
549     char * kwlist[] = {"flags", NULL};
550
551     if (!PyArg_ParseTupleAndKeywords(args, kwds, "i:SetVSFlags", kwlist,
552             &vsflags))
553         return NULL;
554
555     /* XXX FIXME: value check on vsflags, or build pure python object 
556      * for it, and require an object of that type */
557
558     return Py_BuildValue("i", rpmtsSetVSFlags(s->ts, vsflags));
559 }
560
561 static PyObject *
562 rpmts_GetVSFlags(rpmtsObject * s)
563 {
564     return Py_BuildValue("i", rpmtsVSFlags(s->ts));
565 }
566
567 static PyObject *
568 rpmts_SetColor(rpmtsObject * s, PyObject * args, PyObject * kwds)
569 {
570     rpm_color_t tscolor;
571     char * kwlist[] = {"color", NULL};
572
573     if (!PyArg_ParseTupleAndKeywords(args, kwds, "i:Color", kwlist, &tscolor))
574         return NULL;
575
576     /* XXX FIXME: value check on tscolor, or build pure python object
577      * for it, and require an object of that type */
578
579     return Py_BuildValue("i", rpmtsSetColor(s->ts, tscolor));
580 }
581
582 static PyObject *
583 rpmts_PgpPrtPkts(rpmtsObject * s, PyObject * args, PyObject * kwds)
584 {
585     PyObject * blob;
586     unsigned char * pkt;
587     unsigned int pktlen;
588     int rc;
589     char * kwlist[] = {"octets", NULL};
590
591     if (!PyArg_ParseTupleAndKeywords(args, kwds, "O:PgpPrtPkts", kwlist, &blob))
592         return NULL;
593
594     if (blob == Py_None) {
595         Py_RETURN_NONE;
596     }
597     if (!PyString_Check(blob)) {
598         PyErr_SetString(pyrpmError, "pgpPrtPkts takes a string of octets");
599         return NULL;
600     }
601     pkt = (unsigned char *)PyString_AsString(blob);
602     pktlen = PyString_Size(blob);
603
604     rc = pgpPrtPkts(pkt, pktlen, NULL, 1);
605
606     return Py_BuildValue("i", rc);
607 }
608
609 static PyObject *
610 rpmts_PgpImportPubkey(rpmtsObject * s, PyObject * args, PyObject * kwds)
611 {
612     PyObject * blob;
613     unsigned char * pkt;
614     unsigned int pktlen;
615     int rc;
616     char * kwlist[] = {"pubkey", NULL};
617
618     if (!PyArg_ParseTupleAndKeywords(args, kwds, "O:PgpImportPubkey",
619             kwlist, &blob))
620         return NULL;
621
622     if (blob == Py_None) {
623         Py_RETURN_NONE;
624     }
625     if (!PyString_Check(blob)) {
626         PyErr_SetString(pyrpmError, "PgpImportPubkey takes a string of octets");
627         return NULL;
628     }
629     pkt = (unsigned char *)PyString_AsString(blob);
630     pktlen = PyString_Size(blob);
631
632     rc = rpmtsImportPubkey(s->ts, pkt, pktlen);
633
634     return Py_BuildValue("i", rc);
635 }
636
637 static PyObject *
638 rpmts_GetKeys(rpmtsObject * s)
639 {
640     const void **data = NULL;
641     int num, i;
642     PyObject *tuple;
643
644     rpmtsGetKeys(s->ts, &data, &num);
645     if (data == NULL || num <= 0) {
646         data = _free(data);
647         Py_RETURN_NONE;
648     }
649
650     tuple = PyTuple_New(num);
651
652     for (i = 0; i < num; i++) {
653         PyObject *obj;
654         obj = (data[i] ? (PyObject *) data[i] : Py_None);
655         Py_INCREF(obj);
656         PyTuple_SetItem(tuple, i, obj);
657     }
658
659     data = _free(data);
660
661     return tuple;
662 }
663
664 static void *
665 rpmtsCallback(const void * hd, const rpmCallbackType what,
666                          const rpm_loff_t amount, const rpm_loff_t total,
667                          const void * pkgKey, rpmCallbackData data)
668 {
669     Header h = (Header) hd;
670     struct rpmtsCallbackType_s * cbInfo = data;
671     PyObject * pkgObj = (PyObject *) pkgKey;
672     PyObject * args, * result;
673     static FD_t fd;
674
675     if (cbInfo->cb == Py_None) return NULL;
676
677     /* Synthesize a python object for callback (if necessary). */
678     if (pkgObj == NULL) {
679         if (h) {
680             pkgObj = Py_BuildValue("s", headerGetString(h, RPMTAG_NAME));
681         } else {
682             pkgObj = Py_None;
683             Py_INCREF(pkgObj);
684         }
685     } else
686         Py_INCREF(pkgObj);
687
688     PyEval_RestoreThread(cbInfo->_save);
689
690     args = Py_BuildValue("(iLLOO)", what, amount, total, pkgObj, cbInfo->data);
691     result = PyEval_CallObject(cbInfo->cb, args);
692     Py_DECREF(args);
693     Py_DECREF(pkgObj);
694
695     if (!result) {
696         die(cbInfo->cb);
697     }
698
699     if (what == RPMCALLBACK_INST_OPEN_FILE) {
700         int fdno;
701
702         if (!PyArg_Parse(result, "i", &fdno)) {
703             die(cbInfo->cb);
704         }
705         Py_DECREF(result);
706         cbInfo->_save = PyEval_SaveThread();
707
708         fd = fdDup(fdno);
709         fcntl(Fileno(fd), F_SETFD, FD_CLOEXEC);
710
711         return fd;
712     } else
713     if (what == RPMCALLBACK_INST_CLOSE_FILE) {
714         Fclose (fd);
715     }
716
717     Py_DECREF(result);
718     cbInfo->_save = PyEval_SaveThread();
719
720     return NULL;
721 }
722
723 static PyObject *
724 rpmts_SetFlags(rpmtsObject * s, PyObject * args, PyObject * kwds)
725 {
726     rpmtransFlags transFlags = 0;
727     char * kwlist[] = {"flags", NULL};
728
729     if (!PyArg_ParseTupleAndKeywords(args, kwds, "i:SetFlags", kwlist,
730             &transFlags))
731         return NULL;
732
733     /* XXX FIXME: value check on flags, or build pure python object 
734      * for it, and require an object of that type */
735
736     return Py_BuildValue("i", rpmtsSetFlags(s->ts, transFlags));
737 }
738
739 static PyObject *
740 rpmts_SetProbFilter(rpmtsObject * s, PyObject * args, PyObject * kwds)
741 {
742     rpmprobFilterFlags ignoreSet = 0;
743     rpmprobFilterFlags oignoreSet;
744     char * kwlist[] = {"ignoreSet", NULL};
745
746     if (!PyArg_ParseTupleAndKeywords(args, kwds, "i:ProbFilter", kwlist,
747             &ignoreSet))
748         return NULL;
749
750     oignoreSet = s->ignoreSet;
751     s->ignoreSet = ignoreSet;
752
753     return Py_BuildValue("i", oignoreSet);
754 }
755
756 static PyObject *
757 rpmts_Problems(rpmtsObject * s)
758 {
759     return rpmps_Wrap( rpmtsProblems(s->ts) );
760 }
761
762 static PyObject *
763 rpmts_Run(rpmtsObject * s, PyObject * args, PyObject * kwds)
764 {
765     int rc;
766     PyObject * list;
767     rpmps ps;
768     rpmpsi psi;
769     struct rpmtsCallbackType_s cbInfo;
770     char * kwlist[] = {"callback", "data", NULL};
771
772     if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO:Run", kwlist,
773             &cbInfo.cb, &cbInfo.data))
774         return NULL;
775
776     cbInfo.tso = s;
777     cbInfo._save = PyEval_SaveThread();
778
779     if (cbInfo.cb != NULL) {
780         if (!PyCallable_Check(cbInfo.cb)) {
781             PyErr_SetString(PyExc_TypeError, "expected a callable");
782             return NULL;
783         }
784         (void) rpmtsSetNotifyCallback(s->ts, rpmtsCallback, (void *) &cbInfo);
785     }
786
787     rc = rpmtsRun(s->ts, NULL, s->ignoreSet);
788     ps = rpmtsProblems(s->ts);
789
790     if (cbInfo.cb)
791         (void) rpmtsSetNotifyCallback(s->ts, NULL, NULL);
792
793     PyEval_RestoreThread(cbInfo._save);
794
795     if (rc < 0) {
796         list = PyList_New(0);
797         return list;
798     } else if (!rc) {
799         Py_RETURN_NONE;
800     }
801
802     list = PyList_New(0);
803     psi = rpmpsInitIterator(ps);
804     while (rpmpsNextIterator(psi) >= 0) {
805         rpmProblem p = rpmpsGetProblem(psi);
806         char * ps = rpmProblemString(p);
807         PyObject * prob = Py_BuildValue("s(isN)", ps,
808                              rpmProblemGetType(p),
809                              rpmProblemGetStr(p),
810                              PyLong_FromLongLong(rpmProblemGetDiskNeed(p)));
811         PyList_Append(list, prob);
812         free(ps);
813         Py_DECREF(prob);
814     }
815
816     psi = rpmpsFreeIterator(psi);
817     ps = rpmpsFree(ps);
818
819     return list;
820 }
821
822 /* TODO Add TR_ADDED filter to iterator. */
823 static PyObject *
824 rpmts_iternext(rpmtsObject * s)
825 {
826     PyObject * result = NULL;
827     rpmte te;
828
829     /* Reset iterator on 1st entry. */
830     if (s->tsi == NULL) {
831         s->tsi = rpmtsiInit(s->ts);
832         if (s->tsi == NULL)
833             return NULL;
834         s->tsiFilter = 0;
835     }
836
837     te = rpmtsiNext(s->tsi, s->tsiFilter);
838     if (te != NULL) {
839         result = rpmte_Wrap(te);
840     } else {
841         s->tsi = rpmtsiFree(s->tsi);
842         s->tsiFilter = 0;
843     }
844
845     return result;
846 }
847
848 static PyObject *
849 spec_Parse(rpmtsObject * s, PyObject * args, PyObject * kwds)
850 {
851     DEPRECATED_METHOD;
852     /* we could pass in the ts from here but hardly worth the trouble */
853     return PyObject_Call((PyObject *) &spec_Type, args, kwds);
854 }
855
856 static PyObject *
857 rpmts_Match(rpmtsObject * s, PyObject * args, PyObject * kwds)
858 {
859     PyObject *Key = NULL;
860     char *key = NULL;
861 /* XXX lkey *must* be a 32 bit integer, int "works" on all known platforms. */
862     int lkey = 0;
863     int len = 0;
864     rpmTag tag = RPMDBI_PACKAGES;
865     char * kwlist[] = {"tagNumber", "key", NULL};
866
867     if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O&O:Match", kwlist,
868             tagNumFromPyObject, &tag, &Key))
869         return NULL;
870
871     if (Key) {
872         if (PyString_Check(Key)) {
873             key = PyString_AsString(Key);
874             len = PyString_Size(Key);
875         } else if (PyInt_Check(Key)) {
876             lkey = PyInt_AsLong(Key);
877             key = (char *)&lkey;
878             len = sizeof(lkey);
879         } else {
880             PyErr_SetString(PyExc_TypeError, "unknown key type");
881             return NULL;
882         }
883         /* One of the conversions above failed, exception is set already */
884         if (PyErr_Occurred()) {
885             return NULL;
886         }
887     }
888
889     /* XXX If not already opened, open the database O_RDONLY now. */
890     /* XXX FIXME: lazy default rdonly open also done by rpmtsInitIterator(). */
891     if (rpmtsGetRdb(s->ts) == NULL) {
892         int rc = rpmtsOpenDB(s->ts, O_RDONLY);
893         if (rc || rpmtsGetRdb(s->ts) == NULL) {
894             PyErr_SetString(PyExc_TypeError, "rpmdb open failed");
895             return NULL;
896         }
897     }
898
899     return rpmmi_Wrap( rpmtsInitIterator(s->ts, tag, key, len), (PyObject*)s);
900 }
901
902 static struct PyMethodDef rpmts_methods[] = {
903  {"addInstall", (PyCFunction) rpmts_AddInstall, METH_VARARGS|METH_KEYWORDS,
904         NULL },
905  {"addErase",   (PyCFunction) rpmts_AddErase,   METH_VARARGS|METH_KEYWORDS,
906         NULL },
907  {"check",      (PyCFunction) rpmts_Check,      METH_VARARGS|METH_KEYWORDS,
908         NULL },
909  {"order",      (PyCFunction) rpmts_Order,      METH_NOARGS,
910         NULL },
911  {"setFlags",   (PyCFunction) rpmts_SetFlags,   METH_VARARGS|METH_KEYWORDS,
912 "ts.setFlags(transFlags) -> previous transFlags\n\
913 - Set control bit(s) for executing ts.run().\n\
914   Note: This method replaces the 1st argument to the old ts.run()\n" },
915  {"setProbFilter",      (PyCFunction) rpmts_SetProbFilter,      METH_VARARGS|METH_KEYWORDS,
916 "ts.setProbFilter(ignoreSet) -> previous ignoreSet\n\
917 - Set control bit(s) for ignoring problems found by ts.run().\n\
918   Note: This method replaces the 2nd argument to the old ts.run()\n" },
919  {"problems",   (PyCFunction) rpmts_Problems,   METH_NOARGS,
920 "ts.problems() -> ps\n\
921 - Return current problem set.\n" },
922  {"run",        (PyCFunction) rpmts_Run,        METH_VARARGS|METH_KEYWORDS,
923 "ts.run(callback, data) -> (problems)\n\
924 - Run a transaction set, returning list of problems found.\n\
925   Note: The callback may not be None.\n" },
926  {"clean",      (PyCFunction) rpmts_Clean,      METH_NOARGS,
927         NULL },
928  {"openDB",     (PyCFunction) rpmts_OpenDB,     METH_NOARGS,
929 "ts.openDB() -> None\n\
930 - Open the default transaction rpmdb.\n\
931   Note: The transaction rpmdb is lazily opened, so ts.openDB() is seldom needed.\n" },
932  {"closeDB",    (PyCFunction) rpmts_CloseDB,    METH_NOARGS,
933 "ts.closeDB() -> None\n\
934 - Close the default transaction rpmdb.\n\
935   Note: ts.closeDB() disables lazy opens, and should hardly ever be used.\n" },
936  {"initDB",     (PyCFunction) rpmts_InitDB,     METH_NOARGS,
937 "ts.initDB() -> None\n\
938 - Initialize the default transaction rpmdb.\n\
939  Note: ts.initDB() is seldom needed anymore.\n" },
940  {"rebuildDB",  (PyCFunction) rpmts_RebuildDB,  METH_NOARGS,
941 "ts.rebuildDB() -> None\n\
942 - Rebuild the default transaction rpmdb.\n" },
943  {"verifyDB",   (PyCFunction) rpmts_VerifyDB,   METH_NOARGS,
944 "ts.verifyDB() -> None\n\
945 - Verify the default transaction rpmdb.\n" },
946  {"hdrFromFdno",(PyCFunction) rpmts_HdrFromFdno,METH_VARARGS|METH_KEYWORDS,
947 "ts.hdrFromFdno(fdno) -> hdr\n\
948 - Read a package header from a file descriptor.\n" },
949  {"hdrCheck",   (PyCFunction) rpmts_HdrCheck,   METH_VARARGS|METH_KEYWORDS,
950         NULL },
951  {"setVSFlags",(PyCFunction) rpmts_SetVSFlags,  METH_VARARGS|METH_KEYWORDS,
952 "ts.setVSFlags(vsflags) -> ovsflags\n\
953 - Set signature verification flags. Values for vsflags are:\n\
954     rpm.RPMVSF_NOHDRCHK      if set, don't check rpmdb headers\n\
955     rpm.RPMVSF_NEEDPAYLOAD   if not set, check header+payload (if possible)\n\
956     rpm.RPMVSF_NOSHA1HEADER  if set, don't check header SHA1 digest\n\
957     rpm.RPMVSF_NODSAHEADER   if set, don't check header DSA signature\n\
958     rpm.RPMVSF_NOMD5         if set, don't check header+payload MD5 digest\n\
959     rpm.RPMVSF_NODSA         if set, don't check header+payload DSA signature\n\
960     rpm.RPMVSF_NORSA         if set, don't check header+payload RSA signature\n\
961     rpm._RPMVSF_NODIGESTS    if set, don't check digest(s)\n\
962     rpm._RPMVSF_NOSIGNATURES if set, don't check signature(s)\n" },
963  {"getVSFlags",(PyCFunction) rpmts_GetVSFlags,  METH_NOARGS,
964 "ts.getVSFlags() -> vsflags\n\
965 - Retrieve current signature verification flags from transaction\n" },
966  {"setColor",(PyCFunction) rpmts_SetColor,      METH_VARARGS|METH_KEYWORDS,
967         NULL },
968  {"pgpPrtPkts", (PyCFunction) rpmts_PgpPrtPkts, METH_VARARGS|METH_KEYWORDS,
969         NULL },
970  {"pgpImportPubkey",    (PyCFunction) rpmts_PgpImportPubkey,    METH_VARARGS|METH_KEYWORDS,
971         NULL },
972  {"getKeys",    (PyCFunction) rpmts_GetKeys,    METH_NOARGS,
973         NULL },
974  {"parseSpec",  (PyCFunction) spec_Parse,       METH_VARARGS|METH_KEYWORDS,
975 "ts.parseSpec(\"/path/to/foo.spec\") -> spec\n\
976 - Parse a spec file.\n" },
977  {"dbMatch",    (PyCFunction) rpmts_Match,      METH_VARARGS|METH_KEYWORDS,
978 "ts.dbMatch([TagN, [key, [len]]]) -> mi\n\
979 - Create a match iterator for the default transaction rpmdb.\n" },
980     {NULL,              NULL}           /* sentinel */
981 };
982
983 static void rpmts_dealloc(rpmtsObject * s)
984 {
985
986     s->ts = rpmtsFree(s->ts);
987
988     if (s->scriptFd) Fclose(s->scriptFd);
989     /* this will free the keyList, and decrement the ref count of all
990        the items on the list as well :-) */
991     Py_DECREF(s->keyList);
992     PyObject_Del((PyObject *)s);
993 }
994
995 static PyObject * rpmts_getattro(PyObject * o, PyObject * n)
996 {
997     return PyObject_GenericGetAttr(o, n);
998 }
999
1000 static int rpmts_setattro(PyObject * o, PyObject * n, PyObject * v)
1001 {
1002     rpmtsObject *s = (rpmtsObject *)o;
1003     char * name = PyString_AsString(n);
1004     int fdno;
1005
1006     if (rstreq(name, "scriptFd")) {
1007         if (!PyArg_Parse(v, "i", &fdno)) return 0;
1008         if (fdno < 0) {
1009             PyErr_SetString(PyExc_TypeError, "bad file descriptor");
1010             return -1;
1011         } else {
1012             s->scriptFd = fdDup(fdno);
1013             rpmtsSetScriptFd(s->ts, s->scriptFd);
1014         }
1015     } else {
1016         PyErr_SetString(PyExc_AttributeError, name);
1017         return -1;
1018     }
1019
1020     return 0;
1021 }
1022
1023 static void rpmts_free(rpmtsObject * s)
1024 {
1025     s->ts = rpmtsFree(s->ts);
1026
1027     if (s->scriptFd)
1028         Fclose(s->scriptFd);
1029
1030     /* this will free the keyList, and decrement the ref count of all
1031        the items on the list as well :-) */
1032     Py_DECREF(s->keyList);
1033
1034     PyObject_Del((PyObject *)s);
1035 }
1036
1037 static PyObject * rpmts_new(PyTypeObject * subtype, PyObject *args, PyObject *kwds)
1038 {
1039     char * rootDir = "/";
1040     rpmVSFlags vsflags = rpmExpandNumeric("%{?__vsflags}");
1041     char * kwlist[] = {"rootdir", "vsflags", 0};
1042     rpmts ts = NULL;
1043
1044     if (!PyArg_ParseTupleAndKeywords(args, kwds, "|si:rpmts_new", kwlist,
1045             &rootDir, &vsflags))
1046         return NULL;
1047
1048     ts = rpmtsCreate();
1049     /* XXX: Why is there no rpmts_SetRootDir() ? */
1050     (void) rpmtsSetRootDir(ts, rootDir);
1051     /* XXX: make this use common code with rpmts_SetVSFlags() to check the
1052      *      python objects */
1053     (void) rpmtsSetVSFlags(ts, vsflags);
1054
1055     return rpmts_Wrap(ts);
1056 }
1057
1058 static char rpmts_doc[] =
1059 "";
1060
1061 PyTypeObject rpmts_Type = {
1062         PyObject_HEAD_INIT(&PyType_Type)
1063         0,                              /* ob_size */
1064         "rpm.ts",                       /* tp_name */
1065         sizeof(rpmtsObject),            /* tp_size */
1066         0,                              /* tp_itemsize */
1067         (destructor) rpmts_dealloc,     /* tp_dealloc */
1068         0,                              /* tp_print */
1069         (getattrfunc)0,                 /* tp_getattr */
1070         (setattrfunc)0,                 /* tp_setattr */
1071         0,                              /* tp_compare */
1072         0,                              /* tp_repr */
1073         0,                              /* tp_as_number */
1074         0,                              /* tp_as_sequence */
1075         0,                              /* tp_as_mapping */
1076         0,                              /* tp_hash */
1077         0,                              /* tp_call */
1078         0,                              /* tp_str */
1079         (getattrofunc) rpmts_getattro,  /* tp_getattro */
1080         (setattrofunc) rpmts_setattro,  /* tp_setattro */
1081         0,                              /* tp_as_buffer */
1082         Py_TPFLAGS_DEFAULT,             /* tp_flags */
1083         rpmts_doc,                      /* tp_doc */
1084         0,                              /* tp_traverse */
1085         0,                              /* tp_clear */
1086         0,                              /* tp_richcompare */
1087         0,                              /* tp_weaklistoffset */
1088         PyObject_SelfIter,              /* tp_iter */
1089         (iternextfunc) rpmts_iternext,  /* tp_iternext */
1090         rpmts_methods,                  /* tp_methods */
1091         0,                              /* tp_members */
1092         0,                              /* tp_getset */
1093         0,                              /* tp_base */
1094         0,                              /* tp_dict */
1095         0,                              /* tp_descr_get */
1096         0,                              /* tp_descr_set */
1097         0,                              /* tp_dictoffset */
1098         0,                              /* tp_init */
1099         0,                              /* tp_alloc */
1100         (newfunc) rpmts_new,            /* tp_new */
1101         (freefunc) rpmts_free,          /* tp_free */
1102         0,                              /* tp_is_gc */
1103 };
1104
1105 PyObject *
1106 rpmts_Create(PyObject * self, PyObject * args, PyObject * kwds)
1107 {
1108     return PyObject_Call((PyObject *) &rpmts_Type, args, kwds);
1109 }
1110
1111 PyObject * rpmts_Wrap(rpmts ts)
1112 {
1113     rpmtsObject * s = PyObject_New(rpmtsObject, &rpmts_Type);
1114     if (s == NULL) return PyErr_NoMemory();
1115
1116     s->ts = ts;
1117     s->keyList = PyList_New(0);
1118     s->scriptFd = NULL;
1119     s->tsi = NULL;
1120     s->tsiFilter = 0;
1121     return (PyObject *) s;
1122 }