GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/transaction.c Lines: 110 239 46.0 %
Date: 2020-02-19 21:21:49 Branches: 28 102 27.5 %

Line Branch Exec Source
1
/**
2
 * transaction.c : wrapper class around libalpm transactions
3
 *
4
 *  Copyright (c) 2011 Rémy Oudompheng <remy@archlinux.org>
5
 *
6
 *  This file is part of pyalpm.
7
 *
8
 *  pyalpm is free software: you can redistribute it and/or modify
9
 *  it under the terms of the GNU General Public License as published by
10
 *  the Free Software Foundation, either version 3 of the License, or
11
 *  (at your option) any later version.
12
 *
13
 *  pyalpm is distributed in the hope that it will be useful,
14
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 *  GNU General Public License for more details.
17
 *
18
 *  You should have received a copy of the GNU General Public License
19
 *  along with pyalpm.  If not, see <http://www.gnu.org/licenses/>.
20
 *
21
 */
22
23
#include <pyconfig.h>
24
#include <string.h>
25
#include <alpm.h>
26
#include <Python.h>
27
#include "package.h"
28
#include "handle.h"
29
#include "util.h"
30
31
/** Transaction callbacks */
32
extern PyObject *global_py_callbacks[N_CALLBACKS];
33
34
void pyalpm_eventcb(alpm_event_t *event) {
35
  const char *eventstr;
36
  switch(event->type) {
37
    case ALPM_EVENT_CHECKDEPS_START:
38
      eventstr = "Checking dependencies";
39
      break;
40
    case ALPM_EVENT_CHECKDEPS_DONE:
41
      eventstr = "Done checking dependencies";
42
      break;
43
    case ALPM_EVENT_FILECONFLICTS_START:
44
      eventstr = "Checking file conflicts";
45
      break;
46
    case ALPM_EVENT_FILECONFLICTS_DONE:
47
      eventstr = "Done checking file conflicts";
48
      break;
49
    case ALPM_EVENT_RESOLVEDEPS_START:
50
      eventstr = "Resolving dependencies";
51
      break;
52
    case ALPM_EVENT_RESOLVEDEPS_DONE:
53
      eventstr = "Done resolving dependencies";
54
      break;
55
    case ALPM_EVENT_INTERCONFLICTS_START:
56
      eventstr = "Checking inter conflicts";
57
      break;
58
    case ALPM_EVENT_INTERCONFLICTS_DONE:
59
      eventstr = "Done checking inter conflicts";
60
      break;
61
    case ALPM_EVENT_PACKAGE_OPERATION_START:
62
      eventstr = "Operating on a package";
63
      switch(((alpm_event_package_operation_t*)event)->operation) {
64
      case ALPM_PACKAGE_INSTALL:
65
        eventstr = "Adding a package";
66
        break;
67
      case ALPM_PACKAGE_UPGRADE:
68
        eventstr = "Upgrading a package";
69
        break;
70
      case ALPM_PACKAGE_REINSTALL:
71
        eventstr = "Reinstalling a package";
72
        break;
73
      case ALPM_PACKAGE_DOWNGRADE:
74
        eventstr = "Downgrading a package";
75
        break;
76
      case ALPM_PACKAGE_REMOVE:
77
        eventstr = "Removing a package";
78
        break;
79
      }
80
      break;
81
    case ALPM_EVENT_PACKAGE_OPERATION_DONE:
82
      eventstr = "Done operating on a package";
83
      switch(((alpm_event_package_operation_t*)event)->operation) {
84
      case ALPM_PACKAGE_INSTALL:
85
        eventstr = "Done adding a package";
86
        break;
87
      case ALPM_PACKAGE_UPGRADE:
88
        eventstr = "Done upgrading a package";
89
        break;
90
      case ALPM_PACKAGE_REINSTALL:
91
        eventstr = "Done reinstalling a package";
92
        break;
93
      case ALPM_PACKAGE_DOWNGRADE:
94
        eventstr = "Done downgrading a package";
95
        break;
96
      case ALPM_PACKAGE_REMOVE:
97
        eventstr = "Done removing a package";
98
        break;
99
      }
100
      break;
101
    case ALPM_EVENT_INTEGRITY_START:
102
      eventstr = "Checking integrity";
103
      break;
104
    case ALPM_EVENT_INTEGRITY_DONE:
105
      eventstr = "Done checking integrity";
106
      break;
107
    case ALPM_EVENT_LOAD_START:
108
    case ALPM_EVENT_LOAD_DONE:
109
    case ALPM_EVENT_SCRIPTLET_INFO:
110
      /* info here */
111
    case ALPM_EVENT_RETRIEVE_START:
112
    case ALPM_EVENT_RETRIEVE_DONE:
113
    case ALPM_EVENT_RETRIEVE_FAILED:
114
      /* info here */
115
      eventstr = "event not implemented";
116
      break;
117
    case ALPM_EVENT_DISKSPACE_START:
118
      eventstr = "Checking disk space";
119
      break;
120
    case ALPM_EVENT_DISKSPACE_DONE:
121
      eventstr = "Done checking disk space";
122
      break;
123
    case ALPM_EVENT_OPTDEP_REMOVAL:
124
    case ALPM_EVENT_DATABASE_MISSING:
125
      eventstr = "event not implemented";
126
      break;
127
    case ALPM_EVENT_KEYRING_START:
128
      eventstr = "Checking keys in keyring";
129
      break;
130
    case ALPM_EVENT_KEYRING_DONE:
131
      eventstr = "Done checking keys in keyring";
132
      break;
133
    case ALPM_EVENT_KEY_DOWNLOAD_START:
134
    case ALPM_EVENT_KEY_DOWNLOAD_DONE:
135
    case ALPM_EVENT_PACNEW_CREATED:
136
    case ALPM_EVENT_PACSAVE_CREATED:
137
    case ALPM_EVENT_HOOK_START:
138
    case ALPM_EVENT_HOOK_DONE:
139
    case ALPM_EVENT_HOOK_RUN_START:
140
    case ALPM_EVENT_HOOK_RUN_DONE:
141
    default:
142
      eventstr = "unknown event";
143
  }
144
  {
145
    PyObject *result = NULL;
146
    if (global_py_callbacks[CB_PROGRESS]) {
147
      result = PyObject_CallFunction(global_py_callbacks[CB_EVENT], "is", event->type, eventstr);
148
    } else {
149
      PyErr_SetString(PyExc_RuntimeError, "event callback was called but it's not set!");
150
    }
151
    if (PyErr_Occurred()) PyErr_Print();
152
    Py_CLEAR(result);
153
  }
154
}
155
156
void pyalpm_questioncb(alpm_question_t question,
157
        void* data1, void *data2, void* data3, int* retcode) {
158
}
159
160
void pyalpm_progresscb(alpm_progress_t op,
161
        const char* target_name, int percentage, size_t n_targets, size_t cur_target) {
162
  PyObject *result = NULL;
163
  if (global_py_callbacks[CB_PROGRESS]) {
164
    result = PyObject_CallFunction(global_py_callbacks[CB_PROGRESS], "sinn",
165
      target_name, percentage, n_targets, cur_target);
166
  } else {
167
    PyErr_SetString(PyExc_RuntimeError, "progress callback was called but it's not set!");
168
  }
169
  if (PyErr_Occurred()) {
170
    PyErr_Print();
171
    /* alpm_trans_interrupt(handle); */
172
  }
173
  Py_CLEAR(result);
174
}
175
176
/** Transaction info translation */
177
3
static PyObject* pyobject_from_pmdepmissing(void *item) {
178
3
  alpm_depmissing_t* miss = (alpm_depmissing_t*)item;
179
3
  char* needed = alpm_dep_compute_string(miss->depend);
180
3
  PyObject *result = Py_BuildValue("(sss)",
181
      miss->target,
182
      needed,
183
      miss->causingpkg);
184
3
  free(needed);
185
3
  return result;
186
}
187
188
static PyObject* pyobject_from_pmfileconflict(void *item) {
189
  alpm_fileconflict_t* conflict = (alpm_fileconflict_t*)item;
190
  const char *target = conflict->target;
191
  const char *filename = conflict->file;
192
  switch(conflict->type) {
193
  case ALPM_FILECONFLICT_TARGET:
194
    return Py_BuildValue("(sss)", target, filename, conflict->ctarget);
195
  case ALPM_FILECONFLICT_FILESYSTEM:
196
    return Py_BuildValue("(ssO)", target, filename, Py_None);
197
  default:
198
    PyErr_Format(PyExc_RuntimeError, "invalid type %d for alpm_fileconflict_t object", conflict->type);
199
    return NULL;
200
  }
201
}
202
203
/* Standard methods */
204
const char* flagnames[19] = {
205
  "nodeps",
206
  "force",
207
  "nosave",
208
  "nodepversion",
209
  "cascade",
210
  "recurse",
211
  "dbonly",
212
  NULL,
213
  "alldeps",
214
  "downloadonly",
215
  "noscriptlet",
216
  "noconflicts",
217
  NULL,
218
  "needed",
219
  "allexplicit",
220
  "unneeded",
221
  "recurseall",
222
  "nolock",
223
  NULL
224
};
225
226
3
static PyObject *pyalpm_trans_get_flags(PyObject *self, void *closure)
227
{
228
3
  PyObject *result;
229
3
  alpm_handle_t *handle = ALPM_HANDLE(self);
230
3
  int flags = alpm_trans_get_flags(handle);
231
3
  int i;
232
3
  if (flags == -1) RET_ERR("no transaction defined", alpm_errno(handle), NULL);
233
3
  result = PyDict_New();
234
60
  for (i = 0; i < 18; i++) {
235
54
    if(flagnames[i])
236
96
      PyDict_SetItemString(result, flagnames[i], flags & (1 << i) ? Py_True : Py_False);
237
  }
238
  return result;
239
}
240
241
6
static PyObject *pyalpm_trans_get_add(PyObject *self, void *closure)
242
{
243
6
  alpm_handle_t *handle = ALPM_HANDLE(self);
244
6
  alpm_list_t *to_add;
245
  /* sanity check */
246
6
  int flags = alpm_trans_get_flags(handle);
247
6
  if (flags == -1) RET_ERR("no transaction defined", alpm_errno(handle), NULL);
248
249
6
  to_add = alpm_trans_get_add(handle);
250
6
  return alpmlist_to_pylist(to_add, pyalpm_package_from_pmpkg);
251
}
252
253
6
static PyObject *pyalpm_trans_get_remove(PyObject *self, void *closure)
254
{
255
6
  alpm_handle_t *handle = ALPM_HANDLE(self);
256
6
  alpm_list_t *to_remove;
257
  /* sanity check */
258
6
  int flags = alpm_trans_get_flags(handle);
259
6
  if (flags == -1) RET_ERR("no transaction defined", alpm_errno(handle), NULL);
260
261
6
  to_remove = alpm_trans_get_remove(handle);
262
6
  return alpmlist_to_pylist(to_remove, pyalpm_package_from_pmpkg);
263
}
264
265
/** Transaction flow */
266
#define INDEX_FLAGS(array) \
267
  array[0], array[1], array[2], \
268
  array[3], array[4], array[5], \
269
  array[6], array[8], array[9], \
270
  array[10], array[11], array[13], \
271
  array[14], array[15], array[16], \
272
  array[17]
273
274
/** Initializes a transaction
275
 * @param self a Handle object
276
 * ...
277
 * @return a Transaction object with the same underlying object
278
 */
279
27
PyObject* pyalpm_trans_init(PyObject *self, PyObject *args, PyObject *kwargs) {
280
27
  alpm_handle_t *handle = ALPM_HANDLE(self);
281
27
  PyObject *result;
282
27
  const char* keywords[] = { INDEX_FLAGS(flagnames), NULL };
283
27
  char flags[18] = "\0\0\0\0\0" /* 5 */ "\0\0\0\0\0" /* 10 */ "\0\0\0\0\0" /* 15 */ "\0\0\0";
284
285
  /* check all arguments */
286
27
  if (!PyArg_ParseTupleAndKeywords(args, kwargs,
287
        "|bbbbbbbbbbbbbbbbOOO", (char**)keywords,
288
        INDEX_FLAGS(&flags))) {
289
    return NULL;
290
  }
291
292
  /* run alpm_trans_init() */
293
  {
294
    alpm_transflag_t flag_int = 0;
295
    int i, ret;
296
513
    for (i = 0; i < 18; i++) {
297
486
      if (flags[i]) flag_int |= 1U << i;
298
    }
299
27
    ret = alpm_trans_init(handle, flag_int);
300
27
    if (ret == -1) {
301
      RET_ERR("transaction could not be initialized", alpm_errno(handle), NULL);
302
    }
303
  }
304
27
  result = pyalpm_transaction_from_pmhandle(handle);
305
27
  return result;
306
}
307
308
3
static PyObject* pyalpm_trans_prepare(PyObject *self, PyObject *args) {
309
3
  alpm_handle_t *handle = ALPM_HANDLE(self);
310
3
  alpm_list_t *data;
311
312
3
  int ret = alpm_trans_prepare(handle, &data);
313
3
  if (ret == -1) {
314
    /* return the list of package conflicts in the exception */
315
3
    PyObject *info = alpmlist_to_pylist(data, pyobject_from_pmdepmissing);
316
3
    if (!info) return NULL;
317
3
    RET_ERR_DATA("transaction preparation failed", alpm_errno(handle), info, NULL);
318
  }
319
320
  Py_RETURN_NONE;
321
}
322
323
3
static PyObject* pyalpm_trans_commit(PyObject *self, PyObject *args) {
324
3
  alpm_handle_t *handle = ALPM_HANDLE(self);
325
3
  alpm_list_t *data = NULL;
326
3
  int ret;
327
3
  enum _alpm_errno_t err;
328
3
  PyObject *err_info = NULL;
329
330
3
  ret = alpm_trans_commit(handle, &data);
331
3
  if (ret == 0) Py_RETURN_NONE;
332
3
  if (ret != -1) {
333
    PyErr_Format(PyExc_RuntimeError,
334
        "unexpected return value %d from alpm_trans_commit()", ret);
335
    return NULL;
336
  }
337
338
3
  err = alpm_errno(handle);
339
3
  switch(err) {
340
    case ALPM_ERR_FILE_CONFLICTS:
341
      /* return the list of file conflicts in the exception */
342
      err_info = alpmlist_to_pylist(data, pyobject_from_pmfileconflict);
343
      break;
344
    case ALPM_ERR_PKG_INVALID:
345
    case ALPM_ERR_PKG_INVALID_CHECKSUM:
346
    case ALPM_ERR_PKG_INVALID_SIG:
347
    default:
348
      break;
349
  }
350
  if (err_info)
351
    RET_ERR_DATA("transaction failed", err, err_info, NULL);
352
  else
353
3
    RET_ERR("transaction failed", err, NULL);
354
}
355
356
3
static PyObject* pyalpm_trans_interrupt(PyObject *self, PyObject *args) {
357
3
  alpm_handle_t *handle = ALPM_HANDLE(self);
358
3
  int ret = alpm_trans_interrupt(handle);
359
3
  if (ret == -1) RET_ERR("unable to interrupt transaction", alpm_errno(handle), NULL);
360
  Py_RETURN_NONE;
361
}
362
363
27
PyObject* pyalpm_trans_release(PyObject *self, PyObject *args) {
364
27
  alpm_handle_t *handle = ALPM_HANDLE(self);
365
27
  int ret = alpm_trans_release(handle);
366
27
  if (ret == -1) RET_ERR("unable to release transaction", alpm_errno(handle), NULL);
367
27
  Py_RETURN_NONE;
368
}
369
370
/** Transaction contents */
371
6
static PyObject* pyalpm_trans_add_pkg(PyObject *self, PyObject *args) {
372
6
  alpm_handle_t *handle = ALPM_HANDLE(self);
373
6
  alpm_pkg_t *pmpkg;
374
6
  PyObject *pkg;
375
6
  int ret;
376
377
6
  if (!PyArg_ParseTuple(args, "O!", &AlpmPackageType, &pkg)) {
378
    return NULL;
379
  }
380
381
3
  pmpkg = pmpkg_from_pyalpm_pkg(pkg);
382
3
  ret = alpm_add_pkg(handle, pmpkg);
383
3
  if (ret == -1) RET_ERR("unable to update transaction", alpm_errno(handle), NULL);
384
  /* alpm_add_pkg eats the reference to pkg */
385
3
  pyalpm_pkg_unref(pkg);
386
3
  Py_RETURN_NONE;
387
}
388
389
3
static PyObject* pyalpm_trans_remove_pkg(PyObject *self, PyObject *args) {
390
3
  alpm_handle_t *handle = ALPM_HANDLE(self);
391
3
  PyObject *pkg;
392
3
  alpm_pkg_t *pmpkg;
393
3
  int ret;
394
395
3
  if (!PyArg_ParseTuple(args, "O!", &AlpmPackageType, &pkg)) {
396
    return NULL;
397
  }
398
399
  pmpkg = pmpkg_from_pyalpm_pkg(pkg);
400
  ret = alpm_remove_pkg(handle, pmpkg);
401
  if (ret == -1) RET_ERR("unable to update transaction", alpm_errno(handle), NULL);
402
  Py_RETURN_NONE;
403
}
404
405
3
static PyObject* pyalpm_trans_sysupgrade(PyObject *self, PyObject *args, PyObject *kwargs) {
406
3
  alpm_handle_t *handle = ALPM_HANDLE(self);
407
3
  char* keyword[] = {"downgrade", NULL};
408
3
  PyObject *downgrade;
409
3
  int do_downgrade, ret;
410
411
3
  if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!", keyword, &PyBool_Type, &downgrade))
412
    return NULL;
413
414
3
  do_downgrade = (downgrade == Py_True) ? 1 : 0;
415
3
  ret = alpm_sync_sysupgrade(handle, do_downgrade);
416
3
  if (ret == -1) RET_ERR("unable to update transaction", alpm_errno(handle), NULL);
417
3
  Py_RETURN_NONE;
418
}
419
420
/** Properties and methods */
421
422
static struct PyGetSetDef pyalpm_trans_getset[] = {
423
  /** filepaths */
424
  { "flags", (getter)pyalpm_trans_get_flags, NULL, "Transaction flags", NULL } ,
425
  { "to_add", (getter)pyalpm_trans_get_add, NULL, "Packages added by the transaction", NULL },
426
  { "to_remove", (getter)pyalpm_trans_get_remove, NULL, "Packages added by the transaction", NULL },
427
  { NULL }
428
};
429
430
static struct PyMethodDef pyalpm_trans_methods[] = {
431
  /* Execution flow */
432
  {"prepare", pyalpm_trans_prepare,    METH_NOARGS, "prepare" },
433
  {"commit",  pyalpm_trans_commit,     METH_NOARGS, "commit" },
434
  {"interrupt", pyalpm_trans_interrupt,METH_NOARGS,  "Interrupt the transaction." },
435
  {"release", pyalpm_trans_release,    METH_NOARGS,  "Release the transaction." },
436
437
  /* Transaction contents */
438
  {"add_pkg",    pyalpm_trans_add_pkg,    METH_VARARGS,
439
    "append a package addition to transaction"},
440
  {"remove_pkg", pyalpm_trans_remove_pkg, METH_VARARGS,
441
    "append a package removal to transaction"},
442
  {"sysupgrade", (PyCFunction)pyalpm_trans_sysupgrade, METH_VARARGS | METH_KEYWORDS,
443
    "set the transaction to perform a system upgrade\n"
444
    "args:\n"
445
    "  transaction (boolean) : whether to enable downgrades\n" },
446
  { NULL }
447
};
448
449
/* The Transaction object have the same underlying C structure
450
 * as the Handle objects. Only the method table changes.
451
 */
452
static PyTypeObject AlpmTransactionType = {
453
  PyVarObject_HEAD_INIT(NULL, 0)
454
  "alpm.Transaction",    /*tp_name*/
455
  sizeof(AlpmHandle),  /*tp_basicsize*/
456
  .tp_flags = Py_TPFLAGS_DEFAULT,
457
  .tp_doc = "This class is the main interface to get/set libalpm options",
458
  .tp_methods = pyalpm_trans_methods,
459
  .tp_getset = pyalpm_trans_getset,
460
};
461
462
27
PyObject *pyalpm_transaction_from_pmhandle(void* data) {
463
27
  alpm_handle_t *handle = (alpm_handle_t*)data;
464
27
  AlpmHandle *self;
465
27
  self = (AlpmHandle*)AlpmTransactionType.tp_alloc(&AlpmTransactionType, 0);
466
27
  if (self == NULL) {
467
    PyErr_SetString(PyExc_RuntimeError, "unable to create pyalpm.Transaction object");
468
    return NULL;
469
  }
470
471
27
  self->c_data = handle;
472
27
  return (PyObject *)self;
473
}
474
475
/* Initialization */
476
3
int init_pyalpm_transaction(PyObject *module) {
477
3
  if (PyType_Ready(&AlpmTransactionType) < 0)
478
    return -1;
479
3
  Py_INCREF(&AlpmTransactionType);
480
3
  PyModule_AddObject(module, "Transaction", (PyObject*)(&AlpmTransactionType));
481
3
  return 0;
482
}
483
484
/* vim: set ts=2 sw=2 tw=0 et: */
485