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