]> git.ipfire.org Git - pakfire.git/commitdiff
jail: Import logging stuff from execute.c
authorMichael Tremer <michael.tremer@ipfire.org>
Tue, 2 Aug 2022 17:27:28 +0000 (17:27 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Tue, 2 Aug 2022 17:27:28 +0000 (17:27 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
src/libpakfire/include/pakfire/jail.h
src/libpakfire/jail.c

index 6cda85a16d31712d6157651bb1f04726b039b1e5..da1030ee5e6c75a8b4296565cd522c365abef6ac 100644 (file)
@@ -32,6 +32,9 @@ enum {
        PAKFIRE_JAIL_INTERACTIVE        = (1 << 0),
 };
 
+typedef int (*pakfire_jail_log_callback)(struct pakfire* pakfire, void* data,
+       int priority, const char* line, size_t length);
+
 int pakfire_jail_create(struct pakfire_jail** jail, struct pakfire* pakfire, int flags);
 
 struct pakfire_jail* pakfire_jail_ref(struct pakfire_jail* jail);
index 2f41728fe5e34fc008996e90107dc4b6fc9b0a74..76cdd104a43f63df0c9a06eef847c720cae98e3b 100644 (file)
@@ -19,6 +19,7 @@
 #############################################################################*/
 
 #include <errno.h>
+#include <fcntl.h>
 #include <linux/capability.h>
 #include <linux/sched.h>
 #include <sched.h>
@@ -26,6 +27,7 @@
 #include <stdlib.h>
 #include <syscall.h>
 #include <sys/capability.h>
+#include <sys/epoll.h>
 #include <sys/eventfd.h>
 #include <sys/personality.h>
 #include <sys/prctl.h>
@@ -42,7 +44,9 @@
 #include <pakfire/pakfire.h>
 #include <pakfire/util.h>
 
-#define ENVIRON_SIZE 128
+#define BUFFER_SIZE      1024 * 64
+#define ENVIRON_SIZE     128
+#define EPOLL_MAX_EVENTS 2
 
 // The default environment that will be set for every command
 static const struct environ {
@@ -63,6 +67,35 @@ struct pakfire_jail {
 
        // Environment
        char* env[ENVIRON_SIZE];
+
+       // Logging
+       pakfire_jail_log_callback log_callback;
+       void* log_data;
+};
+
+struct pakfire_log_buffer {
+       char data[BUFFER_SIZE];
+       size_t used;
+};
+
+struct pakfire_jail_exec {
+       // PID (of the child)
+       pid_t pid;
+
+       // Process status (from waitpid)
+       int status;
+
+       // Log pipes
+       union {
+               int stdout[2];
+               int stderr[2];
+       } pipes;
+
+       // Log buffers
+       union {
+               struct pakfire_log_buffer stdout;
+               struct pakfire_log_buffer stderr;
+       } buffers;
 };
 
 static int clone3(struct clone_args* args, size_t size) {
@@ -162,6 +195,10 @@ struct pakfire_jail* pakfire_jail_unref(struct pakfire_jail* jail) {
        return NULL;
 }
 
+static int pakfire_jail_has_flag(struct pakfire_jail* jail, int flag) {
+       return jail->flags & flag;
+}
+
 // Environment
 
 // Returns the length of the environment
@@ -228,6 +265,185 @@ int pakfire_jail_set_env(struct pakfire_jail* jail, const char* key, const char*
        return 0;
 }
 
+// Logging
+
+static int pakfire_jail_log_buffer_is_full(const struct pakfire_log_buffer* buffer) {
+       return (sizeof(buffer->data) == buffer->used);
+}
+
+/*
+       This function reads as much data as it can from the file descriptor.
+       If it finds a whole line in it, it will send it to the logger and repeat the process.
+       If not newline character is found, it will try to read more data until it finds one.
+*/
+static int pakfire_jail_handle_log(struct pakfire_jail* jail,
+                       struct pakfire_jail_exec* ctx, int priority, int fd, struct pakfire_log_buffer* buffer) {
+       char line[BUFFER_SIZE + 1];
+
+       // Fill up buffer from fd
+       if (buffer->used < sizeof(buffer->data)) {
+               ssize_t bytes_read = read(fd, buffer->data + buffer->used,
+                               sizeof(buffer->data) - buffer->used);
+
+               // Handle errors
+               if (bytes_read < 0) {
+                       ERROR(jail->pakfire, "Could not read from fd %d: %m\n", fd);
+                       return -1;
+               }
+
+               // Update buffer size
+               buffer->used += bytes_read;
+       }
+
+       // See if we have any lines that we can write
+       while (buffer->used) {
+               // Search for the end of the first line
+               char* eol = memchr(buffer->data, '\n', buffer->used);
+
+               // No newline found
+               if (!eol) {
+                       // If the buffer is full, we send the content to the logger and try again
+                       // This should not happen in practise
+                       if (pakfire_jail_log_buffer_is_full(buffer)) {
+                               DEBUG(jail->pakfire, "Logging buffer is full. Sending all content\n");
+
+                               eol = buffer->data + sizeof(buffer->data) - 1;
+
+                       // Otherwise we might have only read parts of the output
+                       } else
+                               break;
+               }
+
+               // Find the length of the string
+               size_t length = eol - buffer->data + 1;
+
+               // Copy the line into the buffer
+               memcpy(line, buffer->data, length);
+
+               // Terminate the string
+               line[length] = '\0';
+
+               // Log the line
+               if (jail->log_callback) {
+                       int r = jail->log_callback(jail->pakfire, jail->log_data, priority, line, length);
+                       if (r) {
+                               ERROR(jail->pakfire, "The logging callback returned an error: %d\n", r);
+                               return r;
+                       }
+               }
+
+               // Remove line from buffer
+               memmove(buffer->data, buffer->data + length, buffer->used - length);
+               buffer->used -= length;
+       }
+
+       return 0;
+}
+
+static int pakfire_jail_logger(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx) {
+       int epollfd = -1;
+       struct epoll_event ev;
+       struct epoll_event events[EPOLL_MAX_EVENTS];
+       int r = 0;
+
+       // Fetch file descriptors from context
+       const int stdout = ctx->pipes.stdout[1];
+       const int stderr = ctx->pipes.stderr[1];
+
+       int fds[2] = {
+               stdout, stderr,
+       };
+
+       // Setup epoll
+       epollfd = epoll_create1(0);
+       if (epollfd < 0) {
+               ERROR(jail->pakfire, "Could not initialize epoll(): %m\n");
+               r = 1;
+               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(jail->pakfire,
+                               "Could not set file descriptor %d into non-blocking mode: %m\n", fd);
+                       r = 1;
+                       goto OUT;
+               }
+
+               ev.data.fd = fd;
+
+               if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) < 0) {
+                       ERROR(jail->pakfire, "Could not add file descriptor %d to epoll(): %m\n", fd);
+                       r = 1;
+                       goto OUT;
+               }
+       }
+
+       int ended = 0;
+
+       // Loop for as long as the process is alive
+       while (!ended) {
+               // If waitpid() returns non-zero, the process has ended, but we want to perform
+               // one last iteration over the loop to read any remaining content from the file
+               // descriptor buffers.
+               r = waitpid(ctx->pid, &ctx->status, WNOHANG);
+               if (r)
+                       ended = 1;
+
+               int num = epoll_wait(epollfd, events, EPOLL_MAX_EVENTS, -1);
+               if (num < 1) {
+                       // Ignore if epoll_wait() has been interrupted
+                       if (errno == EINTR)
+                               continue;
+
+                       ERROR(jail->pakfire, "epoll_wait() failed: %m\n");
+                       r = 1;
+
+                       goto OUT;
+               }
+
+               struct pakfire_log_buffer* buffer;
+               int priority;
+
+               for (int i = 0; i < num; i++) {
+                       int fd = events[i].data.fd;
+
+                       if (fd == stdout) {
+                               buffer = &ctx->buffers.stdout;
+                               priority = LOG_INFO;
+
+                       } else if (fd == stderr) {
+                               buffer = &ctx->buffers.stderr;
+                               priority = LOG_ERR;
+
+                       } else {
+                               DEBUG(jail->pakfire, "Received invalid file descriptor %d\n", fd);
+                               continue;
+                       }
+
+                       // Handle log event
+                       r = pakfire_jail_handle_log(jail, ctx, priority, fd, buffer);
+                       if (r)
+                               goto OUT;
+               }
+       }
+
+OUT:
+       if (epollfd > 0)
+               close(epollfd);
+
+       return r;
+}
+
 // Capabilities
 
 static int pakfire_jail_drop_capabilities(struct pakfire_jail* jail) {
@@ -547,21 +763,22 @@ static int pakfire_jail_wait_for_signal(struct pakfire_jail* jail, int fd) {
 /*
        Performs the initialisation that needs to happen in the parent part
 */
-static int pakfire_jail_parent(struct pakfire_jail* jail, pid_t pid, int completed_fd) {
+static int pakfire_jail_parent(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx,
+               int completed_fd) {
        int r;
 
        // Setup UID mapping
-       r = pakfire_jail_setup_uid_mapping(jail, pid);
+       r = pakfire_jail_setup_uid_mapping(jail, ctx->pid);
        if (r)
                return r;
 
        // Write "deny" to /proc/PID/setgroups
-       r = pakfire_jail_setgroups(jail, pid);
+       r = pakfire_jail_setgroups(jail, ctx->pid);
        if (r)
                return r;
 
        // Setup GID mapping
-       r = pakfire_jail_setup_gid_mapping(jail, pid);
+       r = pakfire_jail_setup_gid_mapping(jail, ctx->pid);
        if (r)
                return r;
 
@@ -576,7 +793,8 @@ static int pakfire_jail_parent(struct pakfire_jail* jail, pid_t pid, int complet
        return 0;
 }
 
-static int pakfire_jail_child(struct pakfire_jail* jail, const char* argv[], int completed_fd) {
+static int pakfire_jail_child(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx,
+               const char* argv[], int completed_fd) {
        int r;
 
        // XXX do we have to reconfigure logging here?
@@ -682,7 +900,6 @@ static int pakfire_jail_child(struct pakfire_jail* jail, const char* argv[], int
 // Run a command in the jail
 int pakfire_jail_exec(struct pakfire_jail* jail, const char* argv[]) {
        int exit = -1;
-       int status = 0;
        int r;
 
        // Check if argv is valid
@@ -691,6 +908,15 @@ int pakfire_jail_exec(struct pakfire_jail* jail, const char* argv[]) {
                return -1;
        }
 
+       // Initialize context for this call
+       struct pakfire_jail_exec ctx = {
+               .pipes = {
+                       .stdout = { 0, 0, },
+                       .stderr = { 0, 0, },
+               },
+               .status = 0,
+       };
+
        DEBUG(jail->pakfire, "Executing jail...\n");
 
        /*
@@ -703,6 +929,23 @@ int pakfire_jail_exec(struct pakfire_jail* jail, const char* argv[]) {
                return -1;
        }
 
+       // Create pipes to communicate with child process if we are not running interactively
+       if (!pakfire_jail_has_flag(jail, PAKFIRE_JAIL_INTERACTIVE)) {
+               // stdout
+               r = pipe(ctx.pipes.stdout);
+               if (r < 0) {
+                       ERROR(jail->pakfire, "Could not create file descriptors for stdout: %m\n");
+                       goto ERROR;
+               }
+
+               // stderr
+               r = pipe(ctx.pipes.stderr);
+               if (r < 0) {
+                       ERROR(jail->pakfire, "Could not create file descriptors for stderr: %m\n");
+                       goto ERROR;
+               }
+       }
+
        // Configure child process
        struct clone_args args = {
                .flags =
@@ -716,39 +959,56 @@ int pakfire_jail_exec(struct pakfire_jail* jail, const char* argv[]) {
        };
 
        // Fork this process
-       pid_t pid = clone3(&args, sizeof(args));
-       if (pid < 0) {
+       ctx.pid = clone3(&args, sizeof(args));
+       if (ctx.pid < 0) {
                ERROR(jail->pakfire, "Could not clone: %m\n");
                return -1;
 
        // Child process
-       } else if (pid == 0) {
-               r = pakfire_jail_child(jail, argv, completed_fd);
+       } else if (ctx.pid == 0) {
+               r = pakfire_jail_child(jail, &ctx, argv, completed_fd);
                _exit(r);
        }
 
        // Parent process
-       r = pakfire_jail_parent(jail, pid, completed_fd);
+       r = pakfire_jail_parent(jail, &ctx, completed_fd);
        if (r)
                goto ERROR;
 
-       DEBUG(jail->pakfire, "Waiting for PID %d to finish its work\n", pid);
+       DEBUG(jail->pakfire, "Waiting for PID %d to finish its work\n", ctx.pid);
 
-       if (!status)
-               waitpid(pid, &status, 0);
+       // Read output of the child process
+       if (!pakfire_jail_has_flag(jail, PAKFIRE_JAIL_INTERACTIVE)) {
+               r = pakfire_jail_logger(jail, &ctx);
+               if (r)
+                       ERROR(jail->pakfire, "Log reading aborted: %m\n");
+       }
 
-       if (WIFEXITED(status)) {
-               exit = WEXITSTATUS(status);
+       if (!ctx.status)
+               waitpid(ctx.pid, &ctx.status, 0);
+
+       if (WIFEXITED(ctx.status)) {
+               exit = WEXITSTATUS(ctx.status);
 
                DEBUG(jail->pakfire, "Child process exited with code: %d\n", exit);
        } else {
-               ERROR(jail->pakfire, "Could not determine the exit status of process %d\n", pid);
+               ERROR(jail->pakfire, "Could not determine the exit status of process %d\n", ctx.pid);
 
                errno = ESRCH;
                exit = -1;
        }
 
 ERROR:
+       // Close any file descriptors
+       if (ctx.pipes.stdout[0])
+               close(ctx.pipes.stdout[0]);
+       if (ctx.pipes.stdout[1])
+               close(ctx.pipes.stdout[1]);
+       if (ctx.pipes.stderr[0])
+               close(ctx.pipes.stderr[0]);
+       if (ctx.pipes.stderr[1])
+               close(ctx.pipes.stderr[1]);
+
        // Umount everything
        if (!pakfire_on_root(jail->pakfire))
                pakfire_umount_all(jail->pakfire);