]> git.ipfire.org Git - pakfire.git/commitdiff
libpakfire: execute: Read content from stdout/stderr and sent it to the logger
authorMichael Tremer <michael.tremer@ipfire.org>
Fri, 15 Jan 2021 22:33:55 +0000 (22:33 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Fri, 15 Jan 2021 22:33:55 +0000 (22:33 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
configure.ac
src/_pakfire/pakfire.c
src/libpakfire/execute.c
src/pakfire/builder.py
tests/python/execute.py

index c2c5e30e5784450b1f6798759068a07668f0cc1c..32cc047fcda7c481521206d42206df4c329b3070 100644 (file)
@@ -120,6 +120,7 @@ AC_CHECK_LIB([m], [round])
 AC_CHECK_HEADERS([ \
        assert.h \
        ctypes.h \
+       fcntl.h \
        math.h \
        regex.h \
        sched.h \
@@ -127,6 +128,7 @@ AC_CHECK_HEADERS([ \
        stdlib.h \
        string.h \
        syslog.h \
+       sys/epoll.h \
        sys/personality.h \
        sys/stat.h \
        unistd.h
@@ -134,6 +136,10 @@ AC_CHECK_HEADERS([ \
 
 AC_CHECK_FUNCS([ \
        assert \
+       epoll_ctl \
+       epoll_create1 \
+       epoll_wait \
+       fnctl \
        personality \
        secure_getenv \
        strcmp \
index 253f9972476d0ff5ea21906c9ec6906dc667e361..f5dea94a51b8040266150d9c64dc1956ab3ff390 100644 (file)
@@ -351,13 +351,15 @@ static Py_ssize_t Pakfire_len(PakfireObject* self) {
 }
 
 static PyObject* Pakfire_execute(PakfireObject* self, PyObject* args, PyObject* kwds) {
-       char* kwlist[] = {"command", "environ", "enable_network", NULL};
+       char* kwlist[] = {"command", "environ", "enable_network", "log_output", NULL};
 
        PyObject* command = NULL;
        PyObject* environ = NULL;
        int enable_network = 0;
+       int log_output = 1;
 
-       if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|Op", kwlist, &command, &environ, &enable_network))
+       if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|Opp", kwlist, &command, &environ,
+                       &enable_network, &log_output))
                return NULL;
 
        // Check if command is a list
@@ -447,6 +449,10 @@ static PyObject* Pakfire_execute(PakfireObject* self, PyObject* args, PyObject*
        if (enable_network)
                flags |= PAKFIRE_EXECUTE_ENABLE_NETWORK;
 
+       // Log output?
+       if (log_output)
+               flags |= PAKFIRE_EXECUTE_LOG_OUTPUT;
+
        // Execute command
        int r = pakfire_execute(self->pakfire, argv, envp, flags);
 
index 35a5434a989d0c156a2a09522ae90dfa0b3791a8..3fdd167e6df39dce2face78cd7a5641536e524d0 100644 (file)
 #############################################################################*/
 
 #include <errno.h>
+#include <fcntl.h>
 #include <sched.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/epoll.h>
 #include <sys/personality.h>
 #include <sys/types.h>
+#include <sys/user.h>
 #include <sys/wait.h>
 #include <unistd.h>
 
@@ -33,6 +36,8 @@
 #include <pakfire/private.h>
 #include <pakfire/types.h>
 
+#define EPOLL_MAX_EVENTS 2
+
 static char* envp_empty[1] = { NULL };
 
 struct pakfire_execute {
@@ -45,6 +50,103 @@ struct pakfire_execute {
        int stderr[2];
 };
 
+struct pakfire_execute_line_buffer {
+       char data[PAGE_SIZE];
+       size_t used;
+};
+
+static int pakfire_execute_logger(Pakfire pakfire, pid_t pid, int stdout, int stderr, int* status) {
+       struct epoll_event ev;
+       struct epoll_event events[EPOLL_MAX_EVENTS];
+       int r = 0;
+
+       int fds[2] = {
+               stdout, stderr,
+       };
+
+       struct buffers {
+               struct pakfire_execute_line_buffer stdout;
+               struct pakfire_execute_line_buffer stderr;
+       } buffers;
+
+       // Setup epoll
+       int epollfd = epoll_create1(0);
+       if (epollfd < 0) {
+               ERROR(pakfire, "Could not initialize epoll(): %s\n", strerror(errno));
+               r = -errno;
+               goto OUT;
+       }
+
+       ev.events = EPOLLIN;
+
+       // Turn file descriptors into non-blocking mode and add them to epoll()
+       for (unsigned int i = 0; i < 2; i++) {
+               int fd = fds[i];
+
+               // Read flags
+               int flags = fcntl(fd, F_GETFL, 0);
+
+               // Set modified flags
+               if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
+                       ERROR(pakfire, "Could not set file descriptor %d into non-blocking mode: %s\n",
+                               fd, strerror(errno));
+                       r = -errno;
+                       goto OUT;
+               }
+
+               ev.data.fd = fd;
+
+               if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) < 0) {
+                       ERROR(pakfire, "Could not add file descriptor %d to epoll(): %s\n",
+                               fd, strerror(errno));
+                       r = -errno;
+                       goto OUT;
+               }
+       }
+
+       // Loop for as long as the process is alive
+       while (waitpid(pid, status, WNOHANG) == 0) {
+               int fds = epoll_wait(epollfd, events, EPOLL_MAX_EVENTS, -1);
+               if (fds < 1) {
+                       ERROR(pakfire, "epoll_wait() failed: %s\n", strerror(errno));
+                       r = -errno;
+
+                       goto OUT;
+               }
+
+               struct pakfire_execute_line_buffer* buffer;
+
+               for (int i = 0; i < fds; i++) {
+                       int fd = events[i].data.fd;
+
+                       if (fd == stdout)
+                               buffer = &buffers.stdout;
+
+                       else if (fd == stderr)
+                               buffer = &buffers.stderr;
+
+                       else {
+                               DEBUG(pakfire, "Received invalid file descriptor %d\n", fd);
+                               continue;
+                       }
+
+                       // Simply read everything into the buffer and send it to the logger
+                       size_t bytes_read = read(fd, buffer->data, PAGE_SIZE);
+                       if (bytes_read) {
+                               // Terminate the buffer
+                               buffer->data[bytes_read] = '\0';
+
+                               INFO(pakfire, "%s\n", buffer->data);
+                       }
+               }
+       }
+
+OUT:
+       close(epollfd);
+
+       return r;
+}
+
 static int pakfire_execute_fork(void* data) {
        struct pakfire_execute* env = (struct pakfire_execute*)data;
 
@@ -166,19 +268,26 @@ PAKFIRE_EXPORT int pakfire_execute(Pakfire pakfire, const char* argv[], char* en
                return -errno;
        }
 
-       // Close any unused file descriptors
-       if (env.stdout[1])
-               close(env.stdout[1]);
-       if (env.stderr[1])
-               close(env.stderr[1]);
+       // Set some useful error code
+       int r = -ESRCH;
+       int status;
 
        DEBUG(pakfire, "Waiting for PID %d to finish its work\n", pid);
 
-       int status;
-       waitpid(pid, &status, 0);
+       if (flags & PAKFIRE_EXECUTE_LOG_OUTPUT) {
+               // Close any unused file descriptors
+               if (env.stdout[1])
+                       close(env.stdout[1]);
+               if (env.stderr[1])
+                       close(env.stderr[1]);
+
+               if (pakfire_execute_logger(pakfire, pid, env.stdout[0], env.stderr[0], &status) < 0) {
+                       ERROR(pakfire, "Log reading aborted\n");
+               }
+       }
 
-       // Set some useful error code
-       int r = -ESRCH;
+       if (!status)
+               waitpid(pid, &status, 0);
 
        if (WIFEXITED(status)) {
                r = WEXITSTATUS(status);
index f83ac943a69ab2751b1dc0b63d015a0848b60416..1b5c3e1573e55be1df891e1b85eac63540194a26 100644 (file)
@@ -513,4 +513,4 @@ class BuilderContext(object):
 
                # Enter the shell
                self.pakfire.execute(["/usr/bin/bash", "--login"],
-                       environ=self.environ, enable_network=True)
+                       environ=self.environ, enable_network=True, log_output=False)
index eb25534bd1c8ab2380e46c16e29ff1c694b5bf70..5bdd033ea9a93716b36f430ca18391edf2b595ad 100755 (executable)
@@ -58,6 +58,9 @@ class Test(unittest.TestCase):
                with self.assertRaises(pakfire.CommandExecutionError):
                        self.pakfire.execute(["/usr/bin/does-not-exist"])
 
+       def test_execute_output(self):
+               self.pakfire.execute(["/bin/bash", "--help"], log_output=True)
+
        # This is an interactive test which cannot be performed automatically
        #def test_shell(self):
        #       self.pakfire.execute(["/bin/bash", "-i"])