return ret;
}
+/*
+ * Execute
+ */
+
+static PyObject* Pakfire_execute(PakfireObject* self, PyObject* args, PyObject* kwargs) {
+ struct pakfire_jail* jail = NULL;
+ struct pakfire_env* env = NULL;
+ const char** argv = NULL;
+ PyObject* ret = NULL;
+ PyObject* k = NULL;
+ PyObject* v = NULL;
+ Py_ssize_t p = 0;
+ int r;
+
+ PyObject* command = NULL;
+ PyObject* environ = NULL;
+
+ const char* kwlist[] = {
+ "command",
+ "environ",
+ NULL,
+ };
+
+ // Parse arguments
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|O", (char**)kwlist, &command, &environ))
+ goto ERROR;
+
+ // Check if command is a list
+ if (!PyList_Check(command)) {
+ PyErr_SetString(PyExc_TypeError, "command must be a list");
+ goto ERROR;
+ }
+
+ // Fetch the length of the command
+ const ssize_t command_length = PyList_Size(command);
+
+ // Check if command is not empty
+ if (!command_length) {
+ PyErr_SetString(PyExc_ValueError, "command is empty");
+ 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);
+
+ if (!PyUnicode_Check(item)) {
+ PyErr_Format(PyExc_TypeError, "Item %u in command is not a string", i);
+ goto ERROR;
+ }
+
+ // Copy to argv
+ argv[i] = PyUnicode_AsUTF8(item);
+ }
+
+ // Parse the environment
+ if (environ) {
+ // Create a new environment
+ r = pakfire_env_create(&env, self->ctx);
+ if (r < 0) {
+ errno = -r;
+ PyErr_SetFromErrno(PyExc_OSError);
+ goto ERROR;
+ }
+
+ // Check if environ is a dictionary
+ if (!PyDict_Check(environ)) {
+ PyErr_SetString(PyExc_TypeError, "environ must be a dictionary");
+ goto ERROR;
+ }
+
+ // All keys and values must be strings
+ while (PyDict_Next(environ, &p, &k, &v)) {
+ if (!PyUnicode_Check(k) || !PyUnicode_Check(v)) {
+ PyErr_SetString(PyExc_TypeError, "Environment contains a non-string object");
+ goto ERROR;
+ }
+
+ // Set environment value
+ r = pakfire_env_set(env, PyUnicode_AsUTF8(k), "%s", PyUnicode_AsUTF8(v));
+ if (r < 0) {
+ errno = -r;
+ PyErr_SetFromErrno(PyExc_OSError);
+ goto ERROR;
+ }
+ }
+ }
+
+ // Create a new jail
+ r = pakfire_jail_create(&jail, self->pakfire);
+ if (r < 0) {
+ errno = -r;
+ PyErr_SetFromErrno(PyExc_OSError);
+ goto ERROR;
+ }
+
+ Py_BEGIN_ALLOW_THREADS
+
+ // Execute command
+ r = pakfire_jail_communicate(jail, argv, env, 0, NULL, NULL, NULL, NULL);
+
+ Py_END_ALLOW_THREADS
+
+ // If the return code was negative, we had some internal error
+ if (r < 0) {
+ errno = -r;
+ PyErr_SetFromErrno(PyExc_OSError);
+ goto ERROR;
+
+ // 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);
+
+ goto ERROR;
+ }
+
+ // Otherwise return None
+ ret = Py_NewRef(Py_None);
+
+ERROR:
+ if (jail)
+ pakfire_jail_unref(jail);
+ if (env)
+ pakfire_env_unref(env);
+ if (argv)
+ free(argv);
+
+ return ret;
+}
+
static struct PyMethodDef Pakfire_methods[] = {
{
"clean",
METH_VARARGS,
NULL
},
+ {
+ "execute",
+ (PyCFunction)Pakfire_execute,
+ METH_VARARGS|METH_KEYWORDS,
+ NULL,
+ },
{
"generate_key",
(PyCFunction)Pakfire_generate_key,
--- /dev/null
+#!/usr/bin/python3
+###############################################################################
+# #
+# Pakfire - The IPFire package management system #
+# Copyright (C) 2024 Pakfire development team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+###############################################################################
+
+import logging
+import pakfire
+
+import tests
+
+class ExecuteTests(tests.TestCase):
+ """
+ This tests the execute command
+ """
+ def setUp(self):
+ self.pakfire = self.setup_pakfire()
+
+ # XXX Temporarily disabled, because the jail messes up the console
+ def test_execute(self):
+ r = self.pakfire.execute(["/command", "exit-with-code", "0"])
+
+ self.assertIsNone(r)
+
+ def test_return_value(self):
+ with self.assertRaises(pakfire.CommandExecutionError) as e:
+ self.pakfire.execute(["/command", "exit-with-code", "123"])
+
+ # Extract return code
+ code, = e.exception.args
+
+ self.assertTrue(code == 123)
+
+ def test_environ(self):
+ r = self.pakfire.execute(["/command", "echo-environ", "VAR1"],
+ environ={"VAR1" : "VAL1"})
+
+ self.assertIsNone(r)
+
+ def test_invalid_inputs(self):
+ # Arguments
+ with self.assertRaises(TypeError):
+ self.pakfire.execute("/command")
+
+ with self.assertRaises(TypeError):
+ self.pakfire.execute(["/command", 1])
+
+ with self.assertRaises(TypeError):
+ self.pakfire.execute(("/command", "--help"))
+
+ # Environment
+ with self.assertRaises(TypeError):
+ self.pakfire.execute(["/command", "--help"], environ={"VAR1" : 1})
+
+ with self.assertRaises(TypeError):
+ self.pakfire.execute(["/command", "--help"], environ={1 : "VAL1"})
+
+ with self.assertRaises(TypeError):
+ self.pakfire.execute(["/command", "--help"], environ="VAR1=VAL1")
+
+ def test_execute_non_existant_command(self):
+ """
+ Executing non-existant commands should raise an error
+ """
+ with self.assertRaises(pakfire.CommandExecutionError):
+ self.pakfire.execute(["/command-does-not-exist"])
+
+ def test_execute_output(self):
+ self.pakfire.execute(["/command", "echo", "123"])
+
+ # Multiple newlines in one read
+ self.pakfire.execute(["/command", "echo", "1\n2\n3"])
+
+ # Run a command with a lot of output which exceeds the buffer size
+ self.pakfire.execute(["/command", "lines", "1", "65536"])
+
+ # Run a command that generates lots of lines
+ self.pakfire.execute(["/command", "lines", "100", "40"])
+
+ #def test_nice(self):
+ # self.pakfire.execute(["/command", "print-nice"], nice=5)
+
+ #def test_nice_invalid_input(self):
+ # """
+ # Tries using an invalid nice value
+ # """
+ # with self.assertRaises(OSError):
+ # self.pakfire.execute(["/command", "print-nice"], nice=100)
+
+ #def test_check_open_file_descriptors(self):
+ # """
+ # Since we are spawning child processes, it might happen that we leak file
+ # descriptors to the child process.
+ # """
+ # self.pakfire.execute(["/command", "check-open-file-descriptors"])
+
+ # Signals
+
+ def test_send_signal_DEFAULT(self):
+ """
+ Sends a stupid signal which doesn't do anything
+ """
+ self.pakfire.execute(["/command", "send-signal", "0"])
+
+ def test_send_signal_KILL(self):
+ """
+ Test the process killing itself
+ """
+ self.pakfire.execute(["/command", "send-signal", "9"])
+
+ def test_send_signal_TERM(self):
+ """
+ Test the process terminating itself
+ """
+ self.pakfire.execute(["/command", "send-signal", "15"])
+
+
+if __name__ == "__main__":
+ tests.main()