]> git.ipfire.org Git - pakfire.git/blob - src/_pakfire/ctx.c
python: ctx: Cleanup code that dereferences the logging function
[pakfire.git] / src / _pakfire / ctx.c
1 /*#############################################################################
2 # #
3 # Pakfire - The IPFire package management system #
4 # Copyright (C) 2023 Pakfire development team #
5 # #
6 # This program is free software: you can redistribute it and/or modify #
7 # it under the terms of the GNU General Public License as published by #
8 # the Free Software Foundation, either version 3 of the License, or #
9 # (at your option) any later version. #
10 # #
11 # This program is distributed in the hope that it will be useful, #
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
14 # GNU General Public License for more details. #
15 # #
16 # You should have received a copy of the GNU General Public License #
17 # along with this program. If not, see <http://www.gnu.org/licenses/>. #
18 # #
19 #############################################################################*/
20
21 #define PY_SSIZE_T_CLEAN
22 #include <Python.h>
23 #include <syslog.h>
24
25 #include <pakfire/ctx.h>
26
27 #include "ctx.h"
28
29 static PyObject* Ctx_new(PyTypeObject* type, PyObject* args, PyObject* kwds) {
30 CtxObject* self = (CtxObject *)type->tp_alloc(type, 0);
31 if (self)
32 self->ctx = NULL;
33
34 return (PyObject *)self;
35 }
36
37 static void Ctx_log_callback(void* data, int level, const char* file, int line,
38 const char* fn, const char* format, va_list args) {
39 PyObject* logger = (PyObject*)data;
40 PyObject* result = NULL;
41 char* buffer = NULL;
42 int r;
43
44 PyObject* exception = NULL;
45 PyObject* type = NULL;
46 PyObject* value = NULL;
47 PyObject* traceback = NULL;
48
49 // Do nothing if the logger does not exist
50 if (!logger)
51 return;
52
53 // Translate priority to Python logging priorities
54 switch (level) {
55 case LOG_DEBUG:
56 level = 10;
57 break;
58
59 case LOG_INFO:
60 level = 20;
61 break;
62
63 case LOG_ERR:
64 level = 40;
65 break;
66
67 // Drop messages of an unknown level
68 default:
69 return;
70 }
71
72 PyGILState_STATE state = PyGILState_Ensure();
73
74 // Format the log line
75 r = vasprintf(&buffer, format, args);
76 if (r < 0)
77 goto ERROR;
78
79 // Remove trailing newline
80 if (buffer[r - 1] == '\n')
81 r--;
82
83 // Call the logger
84 result = PyObject_CallMethod(logger, "log", "is#", level, buffer, r);
85 if (!result)
86 goto ERROR;
87
88 ERROR:
89 /*
90 We cannot really catch any Python errors here, since we cannot send
91 any error codes up the chain.
92
93 So, in order to debug the problem, We will check if an exception has
94 occurred and if so, print it to the console.
95 */
96 exception = PyErr_Occurred();
97 if (exception) {
98 PyErr_Print();
99
100 // Fetch the exception and destroy it
101 PyErr_Fetch(&type, &value, &traceback);
102
103 Py_XDECREF(type);
104 Py_XDECREF(value);
105 Py_XDECREF(traceback);
106 }
107
108 if (buffer)
109 free(buffer);
110 Py_XDECREF(result);
111
112 // Release the GIL
113 PyGILState_Release(state);
114 }
115
116 static void Ctx___set_logger(CtxObject* self, PyObject* logger) {
117 // Dereference the old logger
118 Py_XDECREF(self->logger);
119
120 // Store the new logger
121 self->logger = logger;
122 Py_XINCREF(self->logger);
123
124 // Set the logger
125 pakfire_ctx_set_log_callback(self->ctx, Ctx_log_callback, self->logger);
126 }
127
128 static int Ctx_setup_logging(CtxObject* self) {
129 PyObject* logging = NULL;
130 PyObject* logger = NULL;
131 int r = -1;
132
133 // import logging
134 logging = PyImport_ImportModule("logging");
135 if (!logging)
136 goto ERROR;
137
138 // logging.getLogger("pakfire")
139 logger = PyObject_CallMethod(logging, "getLogger", "s", "pakfire");
140 if (!logger)
141 goto ERROR;
142
143 // Set default logger
144 Ctx___set_logger(self, logger);
145
146 // Success
147 r = 0;
148
149 ERROR:
150 Py_XDECREF(logging);
151 Py_XDECREF(logger);
152
153 return r;
154 }
155
156 static int Ctx_init(CtxObject* self, PyObject* args, PyObject* kwargs) {
157 char* kwlist[] = { (char*)"path", NULL };
158 const char* path = NULL;
159 int r;
160
161 // Parse arguments
162 if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|s", kwlist, &path))
163 return -1;
164
165 // Create context
166 r = pakfire_ctx_create(&self->ctx, path);
167 if (r) {
168 errno = -r;
169
170 PyErr_SetFromErrno(PyExc_OSError);
171 return -1;
172 }
173
174 // Set the log level to DEBUG
175 pakfire_ctx_set_log_level(self->ctx, LOG_DEBUG);
176
177 // Setup the default logger
178 r = Ctx_setup_logging(self);
179 if (r)
180 return r;
181
182 return 0;
183 }
184
185 static void Ctx_dealloc(CtxObject* self) {
186 if (self->ctx) {
187 // Reset the logger
188 pakfire_ctx_set_log_callback(self->ctx, NULL, NULL);
189
190 pakfire_ctx_unref(self->ctx);
191 }
192
193 Py_XDECREF(self->logger);
194
195 Py_TYPE(self)->tp_free((PyObject *)self);
196 }
197
198 // Logging
199
200 static PyObject* Ctx_set_logger(CtxObject* self, PyObject* args) {
201 PyObject* logger = NULL;
202
203 if (!PyArg_ParseTuple(args, "O", &logger))
204 return NULL;
205
206 // Set the logger
207 Ctx___set_logger(self, logger);
208
209 Py_RETURN_NONE;
210 }
211
212 static PyObject* Ctx_set_cache_path(CtxObject* self, PyObject* value) {
213 const char* path = NULL;
214 int r;
215
216 // Fetch the path
217 path = PyUnicode_AsUTF8(value);
218 if (!path)
219 return NULL;
220
221 // Set the cache path
222 r = pakfire_ctx_set_cache_path(self->ctx, path);
223 if (r) {
224 errno = -r;
225 PyErr_SetFromErrno(PyExc_OSError);
226
227 return NULL;
228 }
229
230 Py_RETURN_NONE;
231 }
232
233 static struct PyMethodDef Ctx_methods[] = {
234 {
235 "set_logger",
236 (PyCFunction)Ctx_set_logger,
237 METH_VARARGS,
238 NULL,
239 },
240 { NULL },
241 };
242
243 static struct PyGetSetDef Ctx_getsetters[] = {
244 {
245 "cache_path",
246 NULL,
247 (setter)Ctx_set_cache_path,
248 NULL,
249 NULL,
250 },
251 { NULL },
252 };
253
254 PyTypeObject CtxType = {
255 PyVarObject_HEAD_INIT(NULL, 0)
256 tp_name: "_pakfire.Ctx",
257 tp_basicsize: sizeof(CtxObject),
258 tp_flags: Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,
259 tp_new: Ctx_new,
260 tp_dealloc: (destructor)Ctx_dealloc,
261 tp_init: (initproc)Ctx_init,
262 tp_doc: "Ctx Object",
263 tp_methods: Ctx_methods,
264 tp_getset: Ctx_getsetters,
265 };