Add a dummy python-level transaction set class, use it always
[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     rpmprobFilterFlags ignoreSet;
145 };
146
147 struct rpmtsCallbackType_s {
148     PyObject * cb;
149     PyObject * data;
150     rpmtsObject * tso;
151     PyThreadState *_save;
152 };
153
154 RPM_GNUC_NORETURN
155 static void die(PyObject *cb)
156 {
157     char *pyfn = NULL;
158     PyObject *r;
159
160     if (PyErr_Occurred()) {
161         PyErr_Print();
162     }
163     if ((r = PyObject_Repr(cb)) != NULL) { 
164         pyfn = PyString_AsString(r);
165     }
166     fprintf(stderr, _("error: python callback %s failed, aborting!\n"), 
167                       pyfn ? pyfn : "???");
168     rpmdbCheckTerminate(1);
169     exit(EXIT_FAILURE);
170 }
171
172 static PyObject *
173 rpmts_AddInstall(rpmtsObject * s, PyObject * args, PyObject * kwds)
174 {
175     Header h = NULL;
176     PyObject * key;
177     char * how = "u";   /* XXX default to upgrade element if missing */
178     int isUpgrade = 0;
179     char * kwlist[] = {"header", "key", "how", NULL};
180     int rc = 0;
181
182     if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&O|s:AddInstall", kwlist,
183             hdrFromPyObject, &h, &key, &how))
184         return NULL;
185
186     if (how && !rstreq(how, "u") && !rstreq(how, "i")) {
187         PyErr_SetString(PyExc_TypeError, "how argument must be \"u\" or \"i\"");
188         return NULL;
189     } else if (how && rstreq(how, "u"))
190         isUpgrade = 1;
191
192     rc = rpmtsAddInstallElement(s->ts, h, key, isUpgrade, NULL);
193     if (rc) {
194         PyErr_SetString(pyrpmError, "adding package to transaction failed");
195         return NULL;
196     }
197         
198
199     /* This should increment the usage count for me */
200     if (key)
201         PyList_Append(s->keyList, key);
202
203     Py_RETURN_NONE;
204 }
205
206 /* TODO Permit finer control (i.e. not just --allmatches) of deleted elments.*/
207 static PyObject *
208 rpmts_AddErase(rpmtsObject * s, PyObject * args, PyObject * kwds)
209 {
210     PyObject * o;
211     int installed = 0;
212     rpmdbMatchIterator mi = NULL;
213     Header h;
214     char * kwlist[] = {"name", NULL};
215
216     if (!PyArg_ParseTupleAndKeywords(args, kwds, "O:AddErase", kwlist, &o))
217         return NULL;
218
219     if (PyString_Check(o)) {
220         char * name = PyString_AsString(o);
221         mi = rpmtsInitIterator(s->ts, RPMDBI_LABEL, name, 0);
222     } else if (PyInt_Check(o)) {
223         uint32_t recno = PyInt_AsLong(o);
224         mi = rpmtsInitIterator(s->ts, RPMDBI_PACKAGES, &recno, sizeof(recno));
225     } else {
226         PyErr_SetString(PyExc_TypeError, "string or integer expected");
227         return NULL;
228     }
229
230     while ((h = rpmdbNextIterator(mi)) != NULL) {
231         installed++;
232         rpmtsAddEraseElement(s->ts, h, -1);
233     }
234     rpmdbFreeIterator(mi);
235     
236     if (installed) {
237         Py_RETURN_NONE;
238     } else {
239         PyErr_SetString(pyrpmError, "package not installed");
240         return NULL;
241     }
242 }
243
244 static int
245 rpmts_SolveCallback(rpmts ts, rpmds ds, const void * data)
246 {
247     struct rpmtsCallbackType_s * cbInfo = (struct rpmtsCallbackType_s *) data;
248     PyObject * args, * result;
249     int res = 1;
250
251     if (cbInfo->tso == NULL) return res;
252     if (cbInfo->cb == Py_None) return res;
253
254     PyEval_RestoreThread(cbInfo->_save);
255
256     args = Py_BuildValue("(Oissi)", cbInfo->tso,
257                 rpmdsTagN(ds), rpmdsN(ds), rpmdsEVR(ds), rpmdsFlags(ds));
258     result = PyEval_CallObject(cbInfo->cb, args);
259     Py_DECREF(args);
260
261     if (!result) {
262         die(cbInfo->cb);
263     } else {
264         if (PyInt_Check(result))
265             res = PyInt_AsLong(result);
266         Py_DECREF(result);
267     }
268
269     cbInfo->_save = PyEval_SaveThread();
270
271     return res;
272 }
273
274 static PyObject *
275 rpmts_Check(rpmtsObject * s, PyObject * args, PyObject * kwds)
276 {
277     rpmps ps;
278     rpmProblem p;
279     PyObject * list, * cf;
280     struct rpmtsCallbackType_s cbInfo;
281     int xx;
282     char * kwlist[] = {"callback", NULL};
283
284     memset(&cbInfo, 0, sizeof(cbInfo));
285     if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O:Check", kwlist,
286             &cbInfo.cb))
287         return NULL;
288
289     if (cbInfo.cb != NULL) {
290         if (!PyCallable_Check(cbInfo.cb)) {
291             PyErr_SetString(PyExc_TypeError, "expected a callable");
292             return NULL;
293         }
294         xx = rpmtsSetSolveCallback(s->ts, rpmts_SolveCallback, (void *)&cbInfo);
295     }
296
297     cbInfo.tso = s;
298     cbInfo._save = PyEval_SaveThread();
299
300     xx = rpmtsCheck(s->ts);
301     ps = rpmtsProblems(s->ts);
302
303     PyEval_RestoreThread(cbInfo._save);
304
305     if (ps != NULL) {
306         list = PyList_New(0);
307         rpmpsi psi = rpmpsInitIterator(ps);
308
309         /* XXX TODO: rpmlib >= 4.0.3 can return multiple suggested keys. */
310         while (rpmpsNextIterator(psi) >= 0) {
311             char * altNEVR, * needsName;
312             char * byName, * byVersion, * byRelease, *byArch;
313             char * needsOP, * needsVersion;
314             rpmsenseFlags needsFlags, sense;
315             fnpyKey key;
316
317             p = rpmpsGetProblem(psi);
318
319             byName = xstrdup(rpmProblemGetPkgNEVR(p));
320             if ((byArch= strrchr(byName, '.')) != NULL)
321                 *byArch++ = '\0';
322             if ((byRelease = strrchr(byName, '-')) != NULL)
323                 *byRelease++ = '\0';
324             if ((byVersion = strrchr(byName, '-')) != NULL)
325                 *byVersion++ = '\0';
326
327             key = rpmProblemGetKey(p);
328
329             altNEVR = needsName = xstrdup(rpmProblemGetAltNEVR(p));
330             if (needsName[1] == ' ') {
331                 sense = (needsName[0] == 'C')
332                         ? RPMDEP_SENSE_CONFLICTS : RPMDEP_SENSE_REQUIRES;
333                 needsName += 2;
334             } else
335                 sense = RPMDEP_SENSE_REQUIRES;
336             if ((needsVersion = strrchr(needsName, ' ')) != NULL)
337                 *needsVersion++ = '\0';
338
339             needsFlags = 0;
340             if ((needsOP = strrchr(needsName, ' ')) != NULL) {
341                 for (*needsOP++ = '\0'; *needsOP != '\0'; needsOP++) {
342                     if (*needsOP == '<')        needsFlags |= RPMSENSE_LESS;
343                     else if (*needsOP == '>')   needsFlags |= RPMSENSE_GREATER;
344                     else if (*needsOP == '=')   needsFlags |= RPMSENSE_EQUAL;
345                 }
346             }
347
348             cf = Py_BuildValue("((sss)(ss)iOi)", byName, byVersion, byRelease,
349                                needsName, needsVersion, needsFlags,
350                                (key != NULL ? key : Py_None),
351                                sense);
352             PyList_Append(list, (PyObject *) cf);
353             Py_DECREF(cf);
354             free(byName);
355             free(altNEVR);
356         }
357
358         psi = rpmpsFreeIterator(psi);
359         ps = rpmpsFree(ps);
360
361         return list;
362     }
363
364     Py_RETURN_NONE;
365 }
366
367 static PyObject *
368 rpmts_Order(rpmtsObject * s)
369 {
370     int rc;
371
372     Py_BEGIN_ALLOW_THREADS
373     rc = rpmtsOrder(s->ts);
374     Py_END_ALLOW_THREADS
375
376     return Py_BuildValue("i", rc);
377 }
378
379 static PyObject *
380 rpmts_Clean(rpmtsObject * s)
381 {
382     rpmtsClean(s->ts);
383
384     Py_RETURN_NONE;
385 }
386
387 static PyObject *
388 rpmts_OpenDB(rpmtsObject * s)
389 {
390     int dbmode;
391
392     dbmode = rpmtsGetDBMode(s->ts);
393     if (dbmode == -1)
394         dbmode = O_RDONLY;
395
396     return Py_BuildValue("i", rpmtsOpenDB(s->ts, dbmode));
397 }
398
399 static PyObject *
400 rpmts_CloseDB(rpmtsObject * s)
401 {
402     int rc;
403
404     rc = rpmtsCloseDB(s->ts);
405     rpmtsSetDBMode(s->ts, -1);  /* XXX disable lazy opens */
406
407     return Py_BuildValue("i", rc);
408 }
409
410 static PyObject *
411 rpmts_InitDB(rpmtsObject * s)
412 {
413     int rc;
414
415     rc = rpmtsInitDB(s->ts, O_RDONLY);
416     if (rc == 0)
417         rc = rpmtsCloseDB(s->ts);
418
419     return Py_BuildValue("i", rc);
420 }
421
422 static PyObject *
423 rpmts_RebuildDB(rpmtsObject * s)
424 {
425     int rc;
426
427     Py_BEGIN_ALLOW_THREADS
428     rc = rpmtsRebuildDB(s->ts);
429     Py_END_ALLOW_THREADS
430
431     return Py_BuildValue("i", rc);
432 }
433
434 static PyObject *
435 rpmts_VerifyDB(rpmtsObject * s)
436 {
437     int rc;
438
439     Py_BEGIN_ALLOW_THREADS
440     rc = rpmtsVerifyDB(s->ts);
441     Py_END_ALLOW_THREADS
442
443     return Py_BuildValue("i", rc);
444 }
445
446 static PyObject *
447 rpmts_HdrFromFdno(rpmtsObject * s, PyObject * args, PyObject * kwds)
448 {
449     PyObject * result = NULL;
450     Header h;
451     FD_t fd;
452     rpmRC rpmrc;
453     char * kwlist[] = {"fd", NULL};
454
455     if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&:HdrFromFdno", kwlist,
456                                      rpmFdFromPyObject, &fd))
457         return NULL;
458
459     rpmrc = rpmReadPackageFile(s->ts, fd, "rpmts_HdrFromFdno", &h);
460     Fclose(fd);
461
462     switch (rpmrc) {
463     case RPMRC_OK:
464         if (h)
465             result = Py_BuildValue("N", hdr_Wrap(&hdr_Type, h));
466         h = headerFree(h);      /* XXX ref held by result */
467         break;
468
469     case RPMRC_NOKEY:
470         PyErr_SetString(pyrpmError, "public key not available");
471         break;
472
473     case RPMRC_NOTTRUSTED:
474         PyErr_SetString(pyrpmError, "public key not trusted");
475         break;
476
477     case RPMRC_NOTFOUND:
478     case RPMRC_FAIL:
479     default:
480         PyErr_SetString(pyrpmError, "error reading package header");
481         break;
482     }
483
484     return result;
485 }
486
487 static PyObject *
488 rpmts_HdrCheck(rpmtsObject * s, PyObject * args, PyObject * kwds)
489 {
490     PyObject * blob;
491     PyObject * result = NULL;
492     char * msg = NULL;
493     const void * uh;
494     int uc;
495     rpmRC rpmrc;
496     char * kwlist[] = {"headers", NULL};
497
498     if (!PyArg_ParseTupleAndKeywords(args, kwds, "S:HdrCheck", kwlist, &blob))
499         return NULL;
500
501     uh = PyString_AsString(blob);
502     uc = PyString_Size(blob);
503
504     rpmrc = headerCheck(s->ts, uh, uc, &msg);
505
506     switch (rpmrc) {
507     case RPMRC_OK:
508         Py_INCREF(Py_None);
509         result = Py_None;
510         break;
511
512     case RPMRC_NOKEY:
513         PyErr_SetString(pyrpmError, "public key not availaiable");
514         break;
515
516     case RPMRC_NOTTRUSTED:
517         PyErr_SetString(pyrpmError, "public key not trusted");
518         break;
519
520     case RPMRC_FAIL:
521     default:
522         PyErr_SetString(pyrpmError, msg);
523         break;
524     }
525     msg = _free(msg);
526
527     return result;
528 }
529
530 static PyObject * wrapSetGetOld(rpmtsObject *s, const char *name, PyObject *val)
531 {
532     PyObject *oval = PyObject_GetAttrString((PyObject *)s, name);
533     if (PyObject_SetAttrString((PyObject *)s, name, val) == -1) {
534         Py_XDECREF(oval);
535         oval = NULL;
536     }
537     return oval;
538 }
539
540 static PyObject * rpmts_SetVSFlags(rpmtsObject * s, PyObject * arg)
541 {
542     return wrapSetGetOld(s, "_vsflags", arg);
543 }
544
545 static PyObject * rpmts_GetVSFlags(rpmtsObject * s)
546 {
547     return PyObject_GetAttrString((PyObject *)s, "_vsflags");
548 }
549
550 static PyObject * rpmts_SetColor(rpmtsObject * s, PyObject * arg)
551 {
552     return wrapSetGetOld(s, "_color", arg);
553 }
554
555 static PyObject *
556 rpmts_PgpPrtPkts(rpmtsObject * s, PyObject * args, PyObject * kwds)
557 {
558     PyObject * blob;
559     unsigned char * pkt;
560     unsigned int pktlen;
561     int rc;
562     char * kwlist[] = {"octets", NULL};
563
564     if (!PyArg_ParseTupleAndKeywords(args, kwds, "S:PgpPrtPkts", kwlist, &blob))
565         return NULL;
566
567     pkt = (unsigned char *)PyString_AsString(blob);
568     pktlen = PyString_Size(blob);
569
570     rc = pgpPrtPkts(pkt, pktlen, NULL, 1);
571
572     return Py_BuildValue("i", rc);
573 }
574
575 static PyObject *
576 rpmts_PgpImportPubkey(rpmtsObject * s, PyObject * args, PyObject * kwds)
577 {
578     PyObject * blob;
579     unsigned char * pkt;
580     unsigned int pktlen;
581     int rc;
582     char * kwlist[] = {"pubkey", NULL};
583
584     if (!PyArg_ParseTupleAndKeywords(args, kwds, "S:PgpImportPubkey",
585             kwlist, &blob))
586         return NULL;
587
588     pkt = (unsigned char *)PyString_AsString(blob);
589     pktlen = PyString_Size(blob);
590
591     rc = rpmtsImportPubkey(s->ts, pkt, pktlen);
592
593     return Py_BuildValue("i", rc);
594 }
595
596 static PyObject *
597 rpmts_GetKeys(rpmtsObject * s)
598 {
599     const void **data = NULL;
600     int num, i;
601     PyObject *tuple;
602
603     rpmtsGetKeys(s->ts, &data, &num);
604     if (data == NULL || num <= 0) {
605         data = _free(data);
606         Py_RETURN_NONE;
607     }
608
609     tuple = PyTuple_New(num);
610
611     for (i = 0; i < num; i++) {
612         PyObject *obj;
613         obj = (data[i] ? (PyObject *) data[i] : Py_None);
614         Py_INCREF(obj);
615         PyTuple_SetItem(tuple, i, obj);
616     }
617
618     data = _free(data);
619
620     return tuple;
621 }
622
623 static void *
624 rpmtsCallback(const void * hd, const rpmCallbackType what,
625                          const rpm_loff_t amount, const rpm_loff_t total,
626                          const void * pkgKey, rpmCallbackData data)
627 {
628     Header h = (Header) hd;
629     struct rpmtsCallbackType_s * cbInfo = data;
630     PyObject * pkgObj = (PyObject *) pkgKey;
631     PyObject * args, * result;
632     static FD_t fd;
633
634     if (cbInfo->cb == Py_None) return NULL;
635
636     /* Synthesize a python object for callback (if necessary). */
637     if (pkgObj == NULL) {
638         if (h) {
639             pkgObj = Py_BuildValue("s", headerGetString(h, RPMTAG_NAME));
640         } else {
641             pkgObj = Py_None;
642             Py_INCREF(pkgObj);
643         }
644     } else
645         Py_INCREF(pkgObj);
646
647     PyEval_RestoreThread(cbInfo->_save);
648
649     args = Py_BuildValue("(iLLOO)", what, amount, total, pkgObj, cbInfo->data);
650     result = PyEval_CallObject(cbInfo->cb, args);
651     Py_DECREF(args);
652     Py_DECREF(pkgObj);
653
654     if (!result) {
655         die(cbInfo->cb);
656     }
657
658     if (what == RPMCALLBACK_INST_OPEN_FILE) {
659         int fdno;
660
661         if (!PyArg_Parse(result, "i", &fdno)) {
662             die(cbInfo->cb);
663         }
664         Py_DECREF(result);
665         cbInfo->_save = PyEval_SaveThread();
666
667         fd = fdDup(fdno);
668         fcntl(Fileno(fd), F_SETFD, FD_CLOEXEC);
669
670         return fd;
671     } else
672     if (what == RPMCALLBACK_INST_CLOSE_FILE) {
673         Fclose (fd);
674     }
675
676     Py_DECREF(result);
677     cbInfo->_save = PyEval_SaveThread();
678
679     return NULL;
680 }
681
682 static PyObject * rpmts_SetFlags(rpmtsObject * s, PyObject * arg)
683 {
684     return wrapSetGetOld(s, "_flags", arg);
685 }
686
687 static PyObject * rpmts_SetProbFilter(rpmtsObject * s, PyObject * arg)
688 {
689     return wrapSetGetOld(s, "_probFilter", arg);
690 }
691
692 static PyObject *
693 rpmts_Problems(rpmtsObject * s)
694 {
695     return rpmps_Wrap(&rpmps_Type, rpmtsProblems(s->ts));
696 }
697
698 static PyObject *
699 rpmts_Run(rpmtsObject * s, PyObject * args, PyObject * kwds)
700 {
701     int rc;
702     PyObject * list;
703     rpmps ps;
704     rpmpsi psi;
705     struct rpmtsCallbackType_s cbInfo;
706     char * kwlist[] = {"callback", "data", NULL};
707
708     if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO:Run", kwlist,
709             &cbInfo.cb, &cbInfo.data))
710         return NULL;
711
712     cbInfo.tso = s;
713     cbInfo._save = PyEval_SaveThread();
714
715     if (cbInfo.cb != NULL) {
716         if (!PyCallable_Check(cbInfo.cb)) {
717             PyErr_SetString(PyExc_TypeError, "expected a callable");
718             return NULL;
719         }
720         (void) rpmtsSetNotifyCallback(s->ts, rpmtsCallback, (void *) &cbInfo);
721     }
722
723     rc = rpmtsRun(s->ts, NULL, s->ignoreSet);
724     ps = rpmtsProblems(s->ts);
725
726     if (cbInfo.cb)
727         (void) rpmtsSetNotifyCallback(s->ts, NULL, NULL);
728
729     PyEval_RestoreThread(cbInfo._save);
730
731     if (rc < 0) {
732         list = PyList_New(0);
733         return list;
734     } else if (!rc) {
735         Py_RETURN_NONE;
736     }
737
738     list = PyList_New(0);
739     psi = rpmpsInitIterator(ps);
740     while (rpmpsNextIterator(psi) >= 0) {
741         rpmProblem p = rpmpsGetProblem(psi);
742         char * ps = rpmProblemString(p);
743         PyObject * prob = Py_BuildValue("s(isN)", ps,
744                              rpmProblemGetType(p),
745                              rpmProblemGetStr(p),
746                              PyLong_FromLongLong(rpmProblemGetDiskNeed(p)));
747         PyList_Append(list, prob);
748         free(ps);
749         Py_DECREF(prob);
750     }
751
752     psi = rpmpsFreeIterator(psi);
753     ps = rpmpsFree(ps);
754
755     return list;
756 }
757
758 static PyObject *
759 rpmts_iternext(rpmtsObject * s)
760 {
761     PyObject * result = NULL;
762     rpmte te;
763
764     /* Reset iterator on 1st entry. */
765     if (s->tsi == NULL) {
766         s->tsi = rpmtsiInit(s->ts);
767         if (s->tsi == NULL)
768             return NULL;
769     }
770
771     te = rpmtsiNext(s->tsi, 0);
772     if (te != NULL) {
773         result = rpmte_Wrap(&rpmte_Type, te);
774     } else {
775         s->tsi = rpmtsiFree(s->tsi);
776     }
777
778     return result;
779 }
780
781 static PyObject *
782 spec_Parse(rpmtsObject * s, PyObject * args, PyObject * kwds)
783 {
784     DEPRECATED_METHOD("use 'spec = rpm.spec(<specfile>)' instead");
785     /* we could pass in the ts from here but hardly worth the trouble */
786     return PyObject_Call((PyObject *) &spec_Type, args, kwds);
787 }
788
789 static PyObject *
790 rpmts_Match(rpmtsObject * s, PyObject * args, PyObject * kwds)
791 {
792     PyObject *Key = NULL;
793     char *key = NULL;
794 /* XXX lkey *must* be a 32 bit integer, int "works" on all known platforms. */
795     int lkey = 0;
796     int len = 0;
797     rpmTag tag = RPMDBI_PACKAGES;
798     char * kwlist[] = {"tagNumber", "key", NULL};
799
800     if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O&O:Match", kwlist,
801             tagNumFromPyObject, &tag, &Key))
802         return NULL;
803
804     if (Key) {
805         if (PyString_Check(Key)) {
806             key = PyString_AsString(Key);
807             len = PyString_Size(Key);
808         } else if (PyInt_Check(Key)) {
809             lkey = PyInt_AsLong(Key);
810             key = (char *)&lkey;
811             len = sizeof(lkey);
812         } else {
813             PyErr_SetString(PyExc_TypeError, "unknown key type");
814             return NULL;
815         }
816         /* One of the conversions above failed, exception is set already */
817         if (PyErr_Occurred()) {
818             return NULL;
819         }
820     }
821
822     /* XXX If not already opened, open the database O_RDONLY now. */
823     /* XXX FIXME: lazy default rdonly open also done by rpmtsInitIterator(). */
824     if (rpmtsGetRdb(s->ts) == NULL) {
825         int rc = rpmtsOpenDB(s->ts, O_RDONLY);
826         if (rc || rpmtsGetRdb(s->ts) == NULL) {
827             PyErr_SetString(PyExc_TypeError, "rpmdb open failed");
828             return NULL;
829         }
830     }
831
832     return rpmmi_Wrap(&rpmmi_Type, rpmtsInitIterator(s->ts, tag, key, len), (PyObject*)s);
833 }
834
835 static struct PyMethodDef rpmts_methods[] = {
836  {"addInstall", (PyCFunction) rpmts_AddInstall, METH_VARARGS|METH_KEYWORDS,
837         NULL },
838  {"addErase",   (PyCFunction) rpmts_AddErase,   METH_VARARGS|METH_KEYWORDS,
839         NULL },
840  {"check",      (PyCFunction) rpmts_Check,      METH_VARARGS|METH_KEYWORDS,
841         NULL },
842  {"order",      (PyCFunction) rpmts_Order,      METH_NOARGS,
843         NULL },
844  {"setFlags",   (PyCFunction) rpmts_SetFlags,   METH_O,
845 "ts.setFlags(transFlags) -> previous transFlags\n\
846 - Set control bit(s) for executing ts.run().\n\
847   Note: This method replaces the 1st argument to the old ts.run()\n" },
848  {"setProbFilter",      (PyCFunction) rpmts_SetProbFilter,      METH_O,
849 "ts.setProbFilter(ignoreSet) -> previous ignoreSet\n\
850 - Set control bit(s) for ignoring problems found by ts.run().\n\
851   Note: This method replaces the 2nd argument to the old ts.run()\n" },
852  {"problems",   (PyCFunction) rpmts_Problems,   METH_NOARGS,
853 "ts.problems() -> ps\n\
854 - Return current problem set.\n" },
855  {"run",        (PyCFunction) rpmts_Run,        METH_VARARGS|METH_KEYWORDS,
856 "ts.run(callback, data) -> (problems)\n\
857 - Run a transaction set, returning list of problems found.\n\
858   Note: The callback may not be None.\n" },
859  {"clean",      (PyCFunction) rpmts_Clean,      METH_NOARGS,
860         NULL },
861  {"openDB",     (PyCFunction) rpmts_OpenDB,     METH_NOARGS,
862 "ts.openDB() -> None\n\
863 - Open the default transaction rpmdb.\n\
864   Note: The transaction rpmdb is lazily opened, so ts.openDB() is seldom needed.\n" },
865  {"closeDB",    (PyCFunction) rpmts_CloseDB,    METH_NOARGS,
866 "ts.closeDB() -> None\n\
867 - Close the default transaction rpmdb.\n\
868   Note: ts.closeDB() disables lazy opens, and should hardly ever be used.\n" },
869  {"initDB",     (PyCFunction) rpmts_InitDB,     METH_NOARGS,
870 "ts.initDB() -> None\n\
871 - Initialize the default transaction rpmdb.\n\
872  Note: ts.initDB() is seldom needed anymore.\n" },
873  {"rebuildDB",  (PyCFunction) rpmts_RebuildDB,  METH_NOARGS,
874 "ts.rebuildDB() -> None\n\
875 - Rebuild the default transaction rpmdb.\n" },
876  {"verifyDB",   (PyCFunction) rpmts_VerifyDB,   METH_NOARGS,
877 "ts.verifyDB() -> None\n\
878 - Verify the default transaction rpmdb.\n" },
879  {"hdrFromFdno",(PyCFunction) rpmts_HdrFromFdno,METH_VARARGS|METH_KEYWORDS,
880 "ts.hdrFromFdno(fdno) -> hdr\n\
881 - Read a package header from a file descriptor.\n" },
882  {"hdrCheck",   (PyCFunction) rpmts_HdrCheck,   METH_VARARGS|METH_KEYWORDS,
883         NULL },
884  {"setVSFlags",(PyCFunction) rpmts_SetVSFlags,  METH_O,
885 "ts.setVSFlags(vsflags) -> ovsflags\n\
886 - Set signature verification flags. Values for vsflags are:\n\
887     rpm.RPMVSF_NOHDRCHK      if set, don't check rpmdb headers\n\
888     rpm.RPMVSF_NEEDPAYLOAD   if not set, check header+payload (if possible)\n\
889     rpm.RPMVSF_NOSHA1HEADER  if set, don't check header SHA1 digest\n\
890     rpm.RPMVSF_NODSAHEADER   if set, don't check header DSA signature\n\
891     rpm.RPMVSF_NOMD5         if set, don't check header+payload MD5 digest\n\
892     rpm.RPMVSF_NODSA         if set, don't check header+payload DSA signature\n\
893     rpm.RPMVSF_NORSA         if set, don't check header+payload RSA signature\n\
894     rpm._RPMVSF_NODIGESTS    if set, don't check digest(s)\n\
895     rpm._RPMVSF_NOSIGNATURES if set, don't check signature(s)\n" },
896  {"getVSFlags",(PyCFunction) rpmts_GetVSFlags,  METH_NOARGS,
897 "ts.getVSFlags() -> vsflags\n\
898 - Retrieve current signature verification flags from transaction\n" },
899  {"setColor",(PyCFunction) rpmts_SetColor,      METH_O,
900         NULL },
901  {"pgpPrtPkts", (PyCFunction) rpmts_PgpPrtPkts, METH_VARARGS|METH_KEYWORDS,
902         NULL },
903  {"pgpImportPubkey",    (PyCFunction) rpmts_PgpImportPubkey,    METH_VARARGS|METH_KEYWORDS,
904         NULL },
905  {"getKeys",    (PyCFunction) rpmts_GetKeys,    METH_NOARGS,
906         NULL },
907  {"parseSpec",  (PyCFunction) spec_Parse,       METH_VARARGS|METH_KEYWORDS,
908 "ts.parseSpec(\"/path/to/foo.spec\") -> spec\n\
909 - Parse a spec file.\n" },
910  {"dbMatch",    (PyCFunction) rpmts_Match,      METH_VARARGS|METH_KEYWORDS,
911 "ts.dbMatch([TagN, [key, [len]]]) -> mi\n\
912 - Create a match iterator for the default transaction rpmdb.\n" },
913     {NULL,              NULL}           /* sentinel */
914 };
915
916 static void rpmts_dealloc(rpmtsObject * s)
917 {
918
919     s->ts = rpmtsFree(s->ts);
920
921     if (s->scriptFd) Fclose(s->scriptFd);
922     /* this will free the keyList, and decrement the ref count of all
923        the items on the list as well :-) */
924     Py_DECREF(s->keyList);
925     s->ob_type->tp_free((PyObject *)s);
926 }
927
928 static PyObject * rpmts_new(PyTypeObject * subtype, PyObject *args, PyObject *kwds)
929 {
930     char * rootDir = "/";
931     rpmVSFlags vsflags = rpmExpandNumeric("%{?__vsflags}");
932     char * kwlist[] = {"rootdir", "vsflags", 0};
933     rpmts ts = NULL;
934
935     if (!PyArg_ParseTupleAndKeywords(args, kwds, "|si:rpmts_new", kwlist,
936             &rootDir, &vsflags))
937         return NULL;
938
939     ts = rpmtsCreate();
940     (void) rpmtsSetRootDir(ts, rootDir);
941     /* XXX: make this use common code with rpmts_SetVSFlags() to check the
942      *      python objects */
943     (void) rpmtsSetVSFlags(ts, vsflags);
944
945     return rpmts_Wrap(subtype, ts);
946 }
947
948 static PyObject *rpmts_get_tid(rpmtsObject *s, void *closure)
949 {
950     return Py_BuildValue("i", rpmtsGetTid(s->ts));
951 }
952
953 static PyObject *rpmts_get_rootDir(rpmtsObject *s, void *closure)
954 {
955     return Py_BuildValue("s", rpmtsRootDir(s->ts));
956 }
957
958 static int rpmts_set_scriptFd(rpmtsObject *s, PyObject *value, void *closure)
959 {
960     int rc = 0;
961     if (PyArg_Parse(value, "O&", rpmFdFromPyObject, &s->scriptFd)) {
962         rpmtsSetScriptFd(s->ts, s->scriptFd);
963     } else if (value == Py_None) {
964         Fclose(s->scriptFd);
965         s->scriptFd = NULL;
966         rpmtsSetScriptFd(s->ts, NULL);
967     } else {
968         rc = -1;
969     }
970     return rc;
971 }
972
973 static PyObject *rpmts_get_color(rpmtsObject *s, void *closure)
974 {
975     return Py_BuildValue("i", rpmtsColor(s->ts));
976 }
977
978 static PyObject *rpmts_get_prefcolor(rpmtsObject *s, void *closure)
979 {
980     return Py_BuildValue("i", rpmtsPrefColor(s->ts));
981 }
982
983 static int rpmts_set_color(rpmtsObject *s, PyObject *value, void *closure)
984 {
985     rpm_color_t color;
986     if (!PyArg_Parse(value, "i", &color)) return -1;
987
988     /* TODO: validate the bits */
989     rpmtsSetColor(s->ts, color);
990     return 0;
991 }
992
993 static int rpmts_set_prefcolor(rpmtsObject *s, PyObject *value, void *closure)
994 {
995     rpm_color_t color;
996     if (!PyArg_Parse(value, "i", &color)) return -1;
997
998     /* TODO: validate the bits */
999     rpmtsSetPrefColor(s->ts, color);
1000     return 0;
1001 }
1002
1003 static int rpmts_set_flags(rpmtsObject *s, PyObject *value, void *closure)
1004 {
1005     rpmtransFlags flags;
1006     if (!PyArg_Parse(value, "i", &flags)) return -1;
1007
1008     /* TODO: validate the bits */
1009     rpmtsSetFlags(s->ts, flags);
1010     return 0;
1011 }
1012
1013 static int rpmts_set_vsflags(rpmtsObject *s, PyObject *value, void *closure)
1014 {
1015     rpmVSFlags flags;
1016     if (!PyArg_Parse(value, "i", &flags)) return -1;
1017
1018     /* TODO: validate the bits */
1019     rpmtsSetVSFlags(s->ts, flags);
1020     return 0;
1021 }
1022
1023 static PyObject *rpmts_get_flags(rpmtsObject *s, void *closure)
1024 {
1025     return Py_BuildValue("i", rpmtsFlags(s->ts));
1026 }
1027
1028 static PyObject *rpmts_get_vsflags(rpmtsObject *s, void *closure)
1029 {
1030     return Py_BuildValue("i", rpmtsVSFlags(s->ts));
1031 }
1032
1033 static char rpmts_doc[] =
1034 "";
1035
1036 static PyMemberDef rpmts_members[] = {
1037         {"_probFilter", T_INT, offsetof(rpmtsObject, ignoreSet), 0, NULL},
1038         {NULL}
1039 };
1040
1041 static PyGetSetDef rpmts_getseters[] = {
1042         /* only provide a setter until we have rpmfd wrappings */
1043         {"scriptFd",    NULL,   (setter)rpmts_set_scriptFd, NULL },
1044         {"tid",         (getter)rpmts_get_tid, NULL, NULL },
1045         {"rootDir",     (getter)rpmts_get_rootDir, NULL, NULL },
1046         {"_color",      (getter)rpmts_get_color, (setter)rpmts_set_color, NULL},
1047         {"_prefcolor",  (getter)rpmts_get_prefcolor, (setter)rpmts_set_prefcolor, NULL},
1048         {"_flags",      (getter)rpmts_get_flags, (setter)rpmts_set_flags, NULL},
1049         {"_vsflags",    (getter)rpmts_get_vsflags, (setter)rpmts_set_vsflags, NULL},
1050         { NULL }
1051 };
1052
1053 PyTypeObject rpmts_Type = {
1054         PyObject_HEAD_INIT(&PyType_Type)
1055         0,                              /* ob_size */
1056         "rpm.ts",                       /* tp_name */
1057         sizeof(rpmtsObject),            /* tp_size */
1058         0,                              /* tp_itemsize */
1059         (destructor) rpmts_dealloc,     /* tp_dealloc */
1060         0,                              /* tp_print */
1061         (getattrfunc)0,                 /* tp_getattr */
1062         (setattrfunc)0,                 /* tp_setattr */
1063         0,                              /* tp_compare */
1064         0,                              /* tp_repr */
1065         0,                              /* tp_as_number */
1066         0,                              /* tp_as_sequence */
1067         0,                              /* tp_as_mapping */
1068         0,                              /* tp_hash */
1069         0,                              /* tp_call */
1070         0,                              /* tp_str */
1071         PyObject_GenericGetAttr,        /* tp_getattro */
1072         PyObject_GenericSetAttr,        /* tp_setattro */
1073         0,                              /* tp_as_buffer */
1074         Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /* tp_flags */
1075         rpmts_doc,                      /* tp_doc */
1076         0,                              /* tp_traverse */
1077         0,                              /* tp_clear */
1078         0,                              /* tp_richcompare */
1079         0,                              /* tp_weaklistoffset */
1080         PyObject_SelfIter,              /* tp_iter */
1081         (iternextfunc) rpmts_iternext,  /* tp_iternext */
1082         rpmts_methods,                  /* tp_methods */
1083         rpmts_members,                  /* tp_members */
1084         rpmts_getseters,                /* tp_getset */
1085         0,                              /* tp_base */
1086         0,                              /* tp_dict */
1087         0,                              /* tp_descr_get */
1088         0,                              /* tp_descr_set */
1089         0,                              /* tp_dictoffset */
1090         0,                              /* tp_init */
1091         0,                              /* tp_alloc */
1092         (newfunc) rpmts_new,            /* tp_new */
1093         0,                              /* tp_free */
1094         0,                              /* tp_is_gc */
1095 };
1096
1097 PyObject * rpmts_Wrap(PyTypeObject *subtype, rpmts ts)
1098 {
1099     rpmtsObject * s = (rpmtsObject *)subtype->tp_alloc(subtype, 0);
1100     if (s == NULL) return PyErr_NoMemory();
1101
1102     s->ts = ts;
1103     s->keyList = PyList_New(0);
1104     s->scriptFd = NULL;
1105     s->tsi = NULL;
1106     return (PyObject *) s;
1107 }