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