From: Michael Tremer Date: Wed, 3 Aug 2022 14:22:43 +0000 (+0000) Subject: python: Rework Pakfire.execute() using jail X-Git-Tag: 0.9.28~598 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=deeb4e91865ffc35c8f6445454d0378f23c0e32c;p=pakfire.git python: Rework Pakfire.execute() using jail Signed-off-by: Michael Tremer --- diff --git a/src/_pakfire/pakfire.c b/src/_pakfire/pakfire.c index 4ed49ffe2..d3323527d 100644 --- a/src/_pakfire/pakfire.c +++ b/src/_pakfire/pakfire.c @@ -26,7 +26,7 @@ #include #include #include -#include +#include #include #include #include @@ -775,14 +775,13 @@ static PyObject* Pakfire_version_compare(PakfireObject* self, PyObject* args) { return PyLong_FromLong(cmp); } -static PyObject* Pakfire_execute_logging_callback = NULL; - -static int __Pakfire_execute_logging_callback(struct pakfire* pakfire, void* data, +static int __Pakfire_logging_callback(struct pakfire* pakfire, void* data, int priority, const char* line, size_t length) { + PyObject* callback = (PyObject*)data; int r = 0; // Do nothing if callback isn't set - if (!Pakfire_execute_logging_callback) + if (!callback) return 0; // Translate priority to Python logging priorities @@ -805,7 +804,7 @@ static int __Pakfire_execute_logging_callback(struct pakfire* pakfire, void* dat if (!args) return 1; - PyObject* result = PyObject_CallObject(Pakfire_execute_logging_callback, args); + PyObject* result = PyObject_CallObject(callback, args); if (result && PyLong_Check(result)) { r = PyLong_AsLong(result); } @@ -817,8 +816,20 @@ static int __Pakfire_execute_logging_callback(struct pakfire* pakfire, void* dat } static PyObject* Pakfire_execute(PakfireObject* self, PyObject* args, PyObject* kwds) { - char* kwlist[] = {"command", "environ", "enable_network", "interactive", - "logging_callback", NULL}; + char* kwlist[] = { + "command", + "environ", + "enable_network", + "interactive", + "logging_callback", + NULL + }; + + struct pakfire_jail* jail = NULL; + const char** argv = NULL; + int flags = 0; + int r; + PyObject* ret = NULL; PyObject* command = NULL; PyObject* environ = NULL; @@ -833,17 +844,22 @@ static PyObject* Pakfire_execute(PakfireObject* self, PyObject* args, PyObject* // Check if command is a list if (!PyList_Check(command)) { PyErr_SetString(PyExc_TypeError, "command must be a list"); - return NULL; + goto ERROR; } - ssize_t command_length = PyList_Size(command); + const ssize_t command_length = PyList_Size(command); // Check if command is not empty if (command_length == 0) { PyErr_SetString(PyExc_ValueError, "command is empty"); - return NULL; + goto ERROR; } + // Allocate argv + argv = calloc(command_length + 1, sizeof(*argv)); + if (!argv) + goto ERROR; + // All arguments in command must be strings for (unsigned int i = 0; i < command_length; i++) { PyObject* item = PyList_GET_ITEM(command, i); @@ -852,111 +868,104 @@ static PyObject* Pakfire_execute(PakfireObject* self, PyObject* args, PyObject* PyErr_Format(PyExc_TypeError, "Item %u in command is not a string", i); return NULL; } + + // Copy to argv + argv[i] = PyUnicode_AsUTF8(item); } - // Check if logging_callback is - if (logging_callback && !PyCallable_Check(logging_callback)) { - PyErr_SetString(PyExc_TypeError, "logging_callback must be callable\n"); - return NULL; +#if 0 + // Enable network? + if (enable_network) + flags |= PAKFIRE_JAIL_ENABLE_NETWORK; +#endif + + // Interactive? + if (interactive) + flags |= PAKFIRE_JAIL_INTERACTIVE; + + // Create jail + r = pakfire_jail_create(&jail, self->pakfire, flags); + if (r) { + PyErr_SetFromErrno(PyExc_OSError); + goto ERROR; + } + + // Set logging callback + if (logging_callback) { + if (!PyCallable_Check(logging_callback)) { + PyErr_SetString(PyExc_TypeError, "logging_callback must be callable\n"); + goto ERROR; + } + + pakfire_jail_set_log_callback(jail, __Pakfire_logging_callback, logging_callback); } - ssize_t environ_length = 0; - PyObject* key; - PyObject* value; + PyObject* key = NULL; + PyObject* value = NULL; Py_ssize_t p = 0; + // Parse the environment if (environ) { // Check if environ is a dictionary if (!PyDict_Check(environ)) { PyErr_SetString(PyExc_TypeError, "environ must be a dictionary"); - return NULL; + goto ERROR; } // All keys and values must be strings while (PyDict_Next(environ, &p, &key, &value)) { if (!PyUnicode_Check(key) || !PyUnicode_Check(value)) { PyErr_SetString(PyExc_TypeError, "Environment contains a non-string object"); - return NULL; + goto ERROR; } - } - - environ_length = PyDict_Size(environ); - } - - // All inputs look fine - const char* argv[command_length + 1]; - char* envp[environ_length + 1]; - int flags = 0; - - // Parse arguments - for (unsigned int i = 0; i < command_length; i++) { - PyObject* item = PyList_GET_ITEM(command, i); - argv[i] = PyUnicode_AsUTF8(item); - } - - // Parse environ - if (environ) { - unsigned int i = 0; - p = 0; - - while (PyDict_Next(environ, &p, &key, &value)) { - int r = asprintf(&envp[i++], "%s=%s", - PyUnicode_AsUTF8(key), PyUnicode_AsUTF8(value)); - - // Handle errors - if (r < 0) { - // Cleanup - for (i = 0; envp[i]; i++) - free(envp[i]); - - return PyErr_NoMemory(); + // Set environment value + r = pakfire_jail_set_env(jail, PyUnicode_AsUTF8(key), PyUnicode_AsUTF8(value)); + if (r) { + PyErr_SetFromErrno(PyExc_OSError); + goto ERROR; } } } - // Terminate argv and envp - argv[command_length] = NULL; - envp[environ_length] = NULL; - - // Enable network? - if (enable_network) - flags |= PAKFIRE_EXECUTE_ENABLE_NETWORK; - - // Interactive? - if (interactive) - flags |= PAKFIRE_EXECUTE_INTERACTIVE; - - // Set logging callback - Pakfire_execute_logging_callback = logging_callback; - // Execute command - int r = pakfire_execute(self->pakfire, argv, envp, flags, - (logging_callback) ? __Pakfire_execute_logging_callback : NULL, NULL); - - // Cleanup - for (unsigned int i = 0; envp[i]; i++) - free(envp[i]); + r = pakfire_jail_exec(jail, argv, NULL); - // Raise an OS error if r < 0 + // If the return code was negative, we had some internal error if (r < 0) { - errno = -r; - PyErr_SetFromErrno(PyExc_OSError); - return NULL; + goto ERROR; - // Raise exception when the command failed + // Otherwise the executed command returned some error code } else if (r > 0) { PyObject* code = PyLong_FromLong(r); + // Raise CommandExecutionError PyErr_SetObject(PyExc_CommandExecutionError, code); Py_DECREF(code); - return NULL; + goto ERROR; } - // Return nothing - Py_RETURN_NONE; + // Return None if everything was successful + ret = Py_None; + Py_INCREF(ret); + +ERROR: + if (argv) + free(argv); + + if (jail) { + // Dereference the logging callback + // It might happen that the jail is not being freed because something else is + // holding a reference to it. We will however lose the reference to the logging + // function here which is why we reset it. + pakfire_jail_set_log_callback(jail, NULL, NULL); + + pakfire_jail_unref(jail); + } + + return ret; } static PyObject* Pakfire_dist(PakfireObject* self, PyObject* args) {