GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/handle.c Lines: 67 112 59.8 %
Date: 2019-02-09 19:11:25 Branches: 12 44 27.3 %

Line Branch Exec Source
1
/**
2
 * handle.c : wrapper class around alpm_handle_t
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 <alpm.h>
25
#include <Python.h>
26
27
#include "handle.h"
28
#include "package.h"
29
#include "db.h"
30
#include "options.h"
31
#include "util.h"
32
33
PyTypeObject AlpmHandleType;
34
35
30
static PyObject *pyalpm_handle_from_pmhandle(void* data) {
36
30
  alpm_handle_t *handle = (alpm_handle_t*)data;
37
30
  AlpmHandle *self;
38
30
  self = (AlpmHandle*)AlpmHandleType.tp_alloc(&AlpmHandleType, 0);
39
30
  if (self == NULL) {
40
    PyErr_SetString(PyExc_RuntimeError, "unable to create pyalpm.Handle object");
41
    return NULL;
42
  }
43
44
30
  self->c_data = handle;
45
  /* memset(self->py_callbacks, 0, N_CALLBACKS * sizeof(PyObject*)); */
46
30
  return (PyObject *)self;
47
}
48
49
/*pyalpm functions*/
50
30
PyObject* pyalpm_initialize(PyTypeObject *subtype, PyObject *args, PyObject *kwargs)
51
{
52
30
  const char *root;
53
30
  const char *dbpath;
54
30
  alpm_handle_t *h;
55
30
  enum _alpm_errno_t errcode = 0;
56
30
  if(!PyArg_ParseTuple(args, "ss", &root, &dbpath)) {
57
    return NULL;
58
  }
59
60
30
  h = alpm_initialize(root, dbpath, &errcode);
61
30
  if (h) {
62
30
    return pyalpm_handle_from_pmhandle((void*)h);
63
  } else {
64
    RET_ERR("could not create a libalpm handle", errcode, NULL);
65
  }
66
}
67
68
PyObject* pyalpm_release(PyObject *self, PyObject *args)
69
{
70
  AlpmHandle *pyhandle;
71
  if(!PyArg_ParseTuple(args, "O!", &AlpmHandleType, &pyhandle))
72
    return NULL;
73
74
  alpm_release(pyhandle->c_data);
75
  pyhandle->c_data = NULL;
76
  Py_RETURN_NONE;
77
}
78
79
/* Database getters/setters */
80
81
9
static PyObject* pyalpm_get_localdb(PyObject *self, PyObject *dummy) {
82
9
  alpm_handle_t *handle = ALPM_HANDLE(self);
83
9
  return pyalpm_db_from_pmdb(alpm_get_localdb(handle));
84
}
85
86
7
static PyObject* pyalpm_get_syncdbs(PyObject *self, PyObject *dummy) {
87
7
  alpm_handle_t *handle = ALPM_HANDLE(self);
88
7
  return alpmlist_to_pylist(alpm_get_syncdbs(handle),
89
			    pyalpm_db_from_pmdb);
90
}
91
92
5
static PyObject* pyalpm_register_syncdb(PyObject *self, PyObject *args) {
93
5
  alpm_handle_t *handle = ALPM_HANDLE(self);
94
5
  const char *dbname;
95
5
  alpm_db_t *result;
96
5
  int pgp_level;
97
98
5
  if (!PyArg_ParseTuple(args, "si", &dbname, &pgp_level)) {
99
    PyErr_Format(PyExc_TypeError, "%s() takes a string and an integer", __func__);
100
    return NULL;
101
  }
102
103
5
  result = alpm_register_syncdb(handle, dbname, pgp_level);
104
5
  if (! result) {
105
    PyErr_Format(alpm_error, "unable to register sync database %s", dbname);
106
    return NULL;
107
  }
108
109
5
  return pyalpm_db_from_pmdb(result);
110
}
111
112
static PyObject* pyalpm_set_pkgreason(PyObject* self, PyObject* args) {
113
  alpm_handle_t *handle = ALPM_HANDLE(self);
114
  alpm_pkg_t *pmpkg = NULL;
115
  PyObject *pkg = NULL;
116
  alpm_pkgreason_t reason;
117
  int ret;
118
  if (!PyArg_ParseTuple(args, "O!i:set_pkgreason", &AlpmPackageType, &pkg, &reason)) {
119
    return NULL;
120
  }
121
  pmpkg = ALPM_PACKAGE(pkg);
122
  ret = alpm_pkg_set_reason(pmpkg, reason);
123
124
  if (ret == -1) RET_ERR("failed setting install reason", alpm_errno(handle), NULL);
125
  Py_RETURN_NONE;
126
}
127
128
/* String attributes get/setters */
129
struct _alpm_str_getset {
130
  const char *(*getter)(alpm_handle_t *);
131
  int (*setter)(alpm_handle_t *, const char *);
132
};
133
134
1
static PyObject *_get_string_attr(PyObject *self, const struct _alpm_str_getset *closure) {
135
1
  alpm_handle_t *handle = ALPM_HANDLE(self);
136
1
  const char *str = closure->getter(handle);
137
1
  if(str == NULL)
138
    RET_ERR("failed getting option value", alpm_errno(handle), NULL);
139
1
  return Py_BuildValue("s", str);
140
}
141
142
static int _set_string_attr(PyObject *self, PyObject *value, const struct _alpm_str_getset *closure) {
143
  alpm_handle_t *handle = ALPM_HANDLE(self);
144
  char *path = NULL;
145
  int ret;
146
  if (PyBytes_Check(value)) {
147
    path = strdup(PyBytes_AS_STRING(value));
148
  } else if (PyUnicode_Check(value)) {
149
    PyObject* utf8 = PyUnicode_AsUTF8String(value);
150
    path = strdup(PyBytes_AS_STRING(utf8));
151
    Py_DECREF(utf8);
152
  } else {
153
    PyErr_SetString(PyExc_TypeError, "logfile path must be a string");
154
    return -1;
155
  }
156
157
  ret = closure->setter(handle, path);
158
  free(path);
159
  if (ret == -1) RET_ERR("failed setting option value", alpm_errno(handle), -1);
160
  return 0;
161
}
162
163
static struct _alpm_str_getset root_getset    = { alpm_option_get_root, NULL };
164
static struct _alpm_str_getset dbpath_getset  = { alpm_option_get_dbpath, NULL };
165
static struct _alpm_str_getset lockfile_getset  = { alpm_option_get_lockfile, NULL };
166
static struct _alpm_str_getset logfile_getset = { alpm_option_get_logfile, alpm_option_set_logfile };
167
static struct _alpm_str_getset gpgdir_getset = { alpm_option_get_gpgdir, alpm_option_set_gpgdir };
168
static struct _alpm_str_getset arch_getset = { alpm_option_get_arch, alpm_option_set_arch };
169
170
/* Callback attributes get/setters */
171
typedef int (*alpm_cb_setter)(alpm_handle_t*, void*);
172
struct _alpm_cb_getset {
173
  alpm_cb_setter setter;
174
  void *cb_wrapper;
175
  pyalpm_callback_id id;
176
};
177
178
void pyalpm_eventcb(alpm_event_t event, void* data1, void *data2);
179
void pyalpm_questioncb(alpm_question_t question,
180
    void* data1, void *data2, void* data3, int* retcode);
181
void pyalpm_progresscb(alpm_progress_t op,
182
    const char* target_name, int percentage, size_t n_targets, size_t cur_target);
183
184
static struct _alpm_cb_getset cb_getsets[N_CALLBACKS] = {
185
  { (alpm_cb_setter)alpm_option_set_logcb, pyalpm_logcb, CB_LOG },
186
  { (alpm_cb_setter)alpm_option_set_dlcb, pyalpm_dlcb, CB_DOWNLOAD },
187
  { (alpm_cb_setter)alpm_option_set_fetchcb, pyalpm_fetchcb, CB_FETCH },
188
  { (alpm_cb_setter)alpm_option_set_totaldlcb, pyalpm_totaldlcb, CB_TOTALDL },
189
  { (alpm_cb_setter)alpm_option_set_eventcb, pyalpm_eventcb, CB_EVENT },
190
  { (alpm_cb_setter)alpm_option_set_questioncb, pyalpm_questioncb, CB_QUESTION },
191
  { (alpm_cb_setter)alpm_option_set_progresscb, pyalpm_progresscb, CB_PROGRESS },
192
};
193
194
/** Callback options
195
 * We use Python callable objects as callbacks: they are
196
 * stored in static variables, and the reference count is
197
 * increased accordingly.
198
 *
199
 * These Python functions are wrapped into C functions
200
 * that are passed to libalpm.
201
 */
202
PyObject *global_py_callbacks[N_CALLBACKS];
203
204
1
static PyObject* _get_cb_attr(PyObject *self, const struct _alpm_cb_getset *closure) {
205
  /* AlpmHandle *it = self; */
206
1
  PyObject *pycb = global_py_callbacks[closure->id];
207
1
  if (pycb == NULL) Py_RETURN_NONE;
208
1
  Py_INCREF(pycb);
209
1
  return pycb;
210
}
211
212
2
static int _set_cb_attr(PyObject *self, PyObject *value, const struct _alpm_cb_getset *closure) {
213
2
  AlpmHandle *it = (AlpmHandle *)self;
214
2
  if (value == Py_None) {
215
    Py_CLEAR(global_py_callbacks[closure->id]);
216
    closure->setter(it->c_data, NULL);
217
2
  } else if (PyCallable_Check(value)) {
218

2
    Py_CLEAR(global_py_callbacks[closure->id]);
219
2
    Py_INCREF(value);
220
2
    global_py_callbacks[closure->id] = value;
221
2
    closure->setter(it->c_data, closure->cb_wrapper);
222
  } else {
223
    PyErr_SetString(PyExc_TypeError, "value must be None or a function");
224
    return -1;
225
  }
226
227
  return 0;
228
}
229
230
struct PyGetSetDef pyalpm_handle_getset[] = {
231
  /** filepaths */
232
  { "root",
233
    (getter)_get_string_attr,
234
    NULL,
235
    "system root directory", &root_getset } ,
236
  { "dbpath",
237
    (getter)_get_string_attr,
238
    NULL,
239
    "alpm database directory", &dbpath_getset } ,
240
  { "logfile",
241
    (getter)_get_string_attr,
242
    (setter)_set_string_attr,
243
    "alpm logfile path", &logfile_getset } ,
244
  { "lockfile",
245
    (getter)_get_string_attr,
246
    NULL,
247
    "alpm lockfile path", &lockfile_getset } ,
248
  { "gpgdir",
249
    (getter)_get_string_attr,
250
    (setter)_set_string_attr,
251
    "alpm GnuPG home directory", &gpgdir_getset } ,
252
253
  /** strings */
254
  { "arch",
255
    (getter)_get_string_attr,
256
    (setter)_set_string_attr,
257
    "Target archichecture", &arch_getset } ,
258
259
  /** booleans */
260
  { "usesyslog",
261
    (getter)option_get_usesyslog_alpm,
262
    (setter)option_set_usesyslog_alpm,
263
    "use syslog (an integer, 0 = False, 1 = True)", NULL } ,
264
  { "deltaratio",
265
    (getter)option_get_deltaratio_alpm,
266
    (setter)option_set_deltaratio_alpm,
267
    "set deltaratio (a float). Deltas are enabled if this is nonzero.", NULL } ,
268
  { "checkspace",
269
    (getter)option_get_checkspace_alpm,
270
    (setter)option_set_checkspace_alpm,
271
    "check disk space before transactions (an integer, 0 = False, 1 = True)", NULL } ,
272
273
  /** lists */
274
  { "cachedirs",
275
    (getter)option_get_cachedirs_alpm,
276
    (setter)option_set_cachedirs_alpm,
277
    "list of package cache directories", NULL },
278
  { "noupgrades",
279
    (getter)option_get_noupgrades_alpm,
280
    (setter)option_set_noupgrades_alpm,
281
    "list of ...", NULL },
282
  { "noextracts",
283
    (getter)option_get_noextracts_alpm,
284
    (setter)option_set_noextracts_alpm,
285
    "list of ...", NULL },
286
  { "ignorepkgs",
287
    (getter)option_get_ignorepkgs_alpm,
288
    (setter)option_set_ignorepkgs_alpm,
289
    "list of ignored packages", NULL },
290
  { "ignoregrps",
291
    (getter)option_get_ignoregrps_alpm,
292
    (setter)option_set_ignoregrps_alpm,
293
    "list of ignored groups", NULL },
294
295
  /** callbacks */
296
  { "logcb",
297
    (getter)_get_cb_attr, (setter)_set_cb_attr,
298
    "logging callback, with arguments (loglevel, format string, tuple)",
299
    &cb_getsets[CB_LOG] },
300
  { "dlcb",
301
    (getter)_get_cb_attr, (setter)_set_cb_attr,
302
    "download status callback (a function)\n"
303
    "args: filename    :: str\n"
304
    "      transferred :: int\n"
305
    "      total       :: int\n",
306
    &cb_getsets[CB_DOWNLOAD] },
307
  { "totaldlcb",
308
    (getter)_get_cb_attr, (setter)_set_cb_attr,
309
    "total download size callback: totaldlcb(total_size)",
310
    &cb_getsets[CB_TOTALDL] },
311
  { "fetchcb",
312
    (getter)_get_cb_attr, (setter)_set_cb_attr,
313
    "download function\n"
314
    "args: url              :: string\n"
315
    "      destination path :: string\n"
316
    "      overwrite        :: bool\n"
317
    "returns: 0 on success, 1 if file exists, -1 on error",
318
    &cb_getsets[CB_FETCH] },
319
  { "eventcb",
320
    (getter)_get_cb_attr, (setter)_set_cb_attr,
321
    "  a function called when an event occurs\n"
322
    "    -- args: (event ID, event string, (object 1, object 2))\n",
323
    &cb_getsets[CB_EVENT] },
324
  { "questioncb",
325
    (getter)_get_cb_attr, (setter)_set_cb_attr,
326
    "  a function called to get user input\n",
327
    &cb_getsets[CB_QUESTION] },
328
  { "progresscb",
329
    (getter)_get_cb_attr, (setter)_set_cb_attr,
330
    "  -- a function called to indicate progress\n"
331
    "    -- args: (target name, percentage, number of targets, target number)\n",
332
    &cb_getsets[CB_PROGRESS] },
333
334
  /** terminator */
335
  { NULL }
336
};
337
338
static PyMethodDef pyalpm_handle_methods[] = {
339
  /* Transaction initialization */
340
  {"init_transaction",    (PyCFunction)pyalpm_trans_init, METH_VARARGS | METH_KEYWORDS,
341
    "Initializes a transaction.\n"
342
    "Arguments:\n"
343
    "  nodeps, force, nosave, nodepversion, cascade, recurse,\n"
344
    "  dbonly, alldeps, downloadonly, noscriptlet, noconflicts,\n"
345
    "  needed, allexplicit, inneeded, recurseall, nolock\n"
346
    "    -- the transaction options (booleans)\n"
347
  },
348
349
  /* Package load */
350
  {"load_pkg", (PyCFunction)pyalpm_package_load, METH_VARARGS | METH_KEYWORDS,
351
    "loads package information from a tarball"},
352
353
  /* Database members */
354
  {"register_syncdb", pyalpm_register_syncdb, METH_VARARGS,
355
   "registers the database with the given name\n"
356
   "returns the new database on success"},
357
  {"get_localdb", pyalpm_get_localdb, METH_NOARGS, "returns an object representing the local DB"},
358
  {"get_syncdbs", pyalpm_get_syncdbs, METH_NOARGS, "returns a list of sync DBs"},
359
  {"set_pkgreason", pyalpm_set_pkgreason, METH_VARARGS,
360
    "set install reason for a package (PKG_REASON_DEPEND, PKG_REASON_EXPLICIT)\n"},
361
362
  /* Option modifiers */
363
  {"add_noupgrade", option_add_noupgrade_alpm, METH_VARARGS, "add a noupgrade package."},
364
  {"remove_noupgrade", option_remove_noupgrade_alpm, METH_VARARGS, "removes a noupgrade package."},
365
366
  {"add_cachedir", option_add_cachedir_alpm, METH_VARARGS, "adds a cachedir."},
367
  {"remove_cachedir", option_remove_cachedir_alpm, METH_VARARGS, "removes a cachedir."},
368
369
  {"add_noextract", option_add_noextract_alpm, METH_VARARGS, "add a noextract package."},
370
  {"remove_noextract", option_remove_noextract_alpm, METH_VARARGS, "remove a noextract package."},
371
372
  {"add_ignorepkg", option_add_ignorepkg_alpm, METH_VARARGS, "add an ignorepkg."},
373
  {"remove_ignorepkg", option_remove_ignorepkg_alpm, METH_VARARGS, "remove an ignorepkg."},
374
375
  {"add_ignoregrp", option_add_ignoregrp_alpm, METH_VARARGS, "add an ignoregrp."},
376
  {"remove_ignoregrp", option_remove_ignoregrp_alpm, METH_VARARGS, "remove an ignoregrp."},
377
  {NULL, NULL, 0, NULL},
378
};
379
380
30
static void pyalpm_dealloc(PyObject* self) {
381
30
  alpm_handle_t *handle = ALPM_HANDLE(self);
382
30
  int ret = alpm_release(handle);
383
30
  if (ret == -1) {
384
    PyErr_Format(alpm_error, "unable to release alpm handle");
385
  }
386
30
  handle = NULL;
387
30
  Py_TYPE(self)->tp_free((PyObject *)self);
388
30
}
389
390
PyTypeObject AlpmHandleType = {
391
  PyVarObject_HEAD_INIT(NULL, 0)
392
  "alpm.Handle",       /*tp_name*/
393
  sizeof(AlpmHandle),  /*tp_basicsize*/
394
  0,                   /*tp_itemsize*/
395
  .tp_flags = Py_TPFLAGS_DEFAULT,
396
  .tp_doc = "An object wrapping a libalpm handle. Arguments: root path, DB path.",
397
  .tp_methods = pyalpm_handle_methods,
398
  .tp_getset = pyalpm_handle_getset,
399
  .tp_new = pyalpm_initialize,
400
  .tp_dealloc = (destructor) pyalpm_dealloc,
401
};
402
403
/** Initializes Handle class in module */
404
1
int init_pyalpm_handle(PyObject *module) {
405
1
  PyObject *type;
406
1
  if (PyType_Ready(&AlpmHandleType) < 0)
407
    return -1;
408
1
  type = (PyObject*)&AlpmHandleType;
409
1
  Py_INCREF(type);
410
1
  PyModule_AddObject(module, "Handle", type);
411
412
1
  PyModule_AddIntConstant(module, "LOG_ERROR", ALPM_LOG_ERROR);
413
1
  PyModule_AddIntConstant(module, "LOG_WARNING", ALPM_LOG_WARNING);
414
1
  PyModule_AddIntConstant(module, "LOG_DEBUG", ALPM_LOG_DEBUG);
415
1
  PyModule_AddIntConstant(module, "LOG_FUNCTION", ALPM_LOG_FUNCTION);
416
417
1
  return 0;
418
}
419
420
/* vim: set ts=2 sw=2 et: */