]> git.ipfire.org Git - pakfire.git/commitdiff
pty: Implement capturing the output
authorMichael Tremer <michael.tremer@ipfire.org>
Thu, 10 Oct 2024 09:51:52 +0000 (09:51 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Thu, 10 Oct 2024 09:51:52 +0000 (09:51 +0000)
This should be the most efficient version, yet.

We create a memfd buffer which we write to and later copy the output to
the heap. This also reduces the amount of callbacks that we have to pass
around.

Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
src/_pakfire/pakfire.c
src/libpakfire/archive.c
src/libpakfire/build.c
src/libpakfire/include/pakfire/jail.h
src/libpakfire/include/pakfire/pty.h
src/libpakfire/jail.c
src/libpakfire/pty.c
tests/libpakfire/cgroup.c
tests/libpakfire/jail.c

index f6e6f8f1717c7ec2f7d5f054bae58767bb4ccdf2..5b2fcddc800daab7a8a97eed16a82dcea90ce096 100644 (file)
@@ -528,7 +528,7 @@ static PyObject* Pakfire_execute(PakfireObject* self, PyObject* args, PyObject*
        pakfire_jail_set_stdout_callback(jail, Pakfire_execute_output_callback, callback);
 
        // Execute command
-       r = pakfire_jail_exec(jail, argv, 0);
+       r = pakfire_jail_exec(jail, argv, 0, NULL);
 
        Py_END_ALLOW_THREADS
 
index a263cd84884cd7ca396133d90ec613b23188d870..7808e0a691779db7b268645d6706ee2be34e32cd 100644 (file)
@@ -1452,7 +1452,7 @@ static int __pakfire_archive_handle_systemd_sysusers(struct pakfire_ctx* ctx,
        pakfire_jail_set_stdin_callback(jail, pakfire_archive_stream_payload, a);
 
        // Run!
-       r = pakfire_jail_exec(jail, argv, PAKFIRE_JAIL_NOENT_OK);
+       r = pakfire_jail_exec(jail, argv, PAKFIRE_JAIL_NOENT_OK, NULL);
 
 ERROR:
        if (jail)
index a2dac5a8194f331f569744b74c333f6199c5c1ef..c3ed131d7122c736f8ff633f07f0d8fff3948e05 100644 (file)
@@ -2471,7 +2471,7 @@ PAKFIRE_EXPORT int pakfire_build_shell(struct pakfire_build* build, const char*
 
        // Run the command (if given)
        if (argv)
-               return pakfire_jail_exec(build->jail, argv, 0);
+               return pakfire_jail_exec(build->jail, argv, 0, NULL);
 
        // Otherwise run the shell
        return pakfire_jail_shell(build->jail);
index 55fbfe9989198b85f78246cdccbd67d770ca92c5..55006fa9a7a1b303923170eee8951bc42cf9ce9d 100644 (file)
@@ -64,10 +64,7 @@ enum pakfire_jail_exec_flags {
        PAKFIRE_JAIL_HAS_LOOP_DEVICES = (1 << 3),
 };
 
-int pakfire_jail_exec(
-       struct pakfire_jail* jail,
-       const char* argv[],
-       int flags);
+int pakfire_jail_exec(struct pakfire_jail* jail, const char* argv[], int flags, char** output);
 
 #ifdef PAKFIRE_PRIVATE
 
index 3efa97a9481e18545fb5358031b4b6bf92a4c1e7..32febcf13e1ac4eb66440ad2ebed34f6e4be71ee 100644 (file)
@@ -30,7 +30,8 @@
 struct pakfire_pty;
 
 enum pakfire_pty_flags {
-       PAKFIRE_PTY_READ_ONLY = (1 << 0),
+       PAKFIRE_PTY_READ_ONLY      = (1 << 0),
+       PAKFIRE_PTY_CAPTURE_OUTPUT = (1 << 1),
 };
 
 int pakfire_pty_create(struct pakfire_pty** pty,
@@ -43,5 +44,7 @@ int pakfire_pty_open(struct pakfire_pty* pty);
 
 int pakfire_pty_drain(struct pakfire_pty* pty);
 
+char* pakfire_pty_output(struct pakfire_pty* pty, size_t* length);
+
 #endif /* PAKFIRE_PRIVATE */
 #endif /* PAKFIRE_PTY_H */
index 4d6626cea492baddf11e4090b59b17e06d9e251b..1eae57e34388945b16a4458d4f6dcedac08acade 100644 (file)
@@ -1483,7 +1483,8 @@ static int pakfire_jail_child(struct pakfire_jail* jail, struct pakfire_jail_exe
 }
 
 // Run a command in the jail
-PAKFIRE_EXPORT int pakfire_jail_exec(struct pakfire_jail* jail, const char* argv[], int flags) {
+PAKFIRE_EXPORT int pakfire_jail_exec(struct pakfire_jail* jail,
+               const char* argv[], int flags, char** output) {
        int pty_flags = 0;
        int r;
 
@@ -1525,9 +1526,20 @@ PAKFIRE_EXPORT int pakfire_jail_exec(struct pakfire_jail* jail, const char* argv
                // Enable networking
                ctx.flags |= PAKFIRE_JAIL_HAS_NETWORKING;
 
+               // We cannot capture the output in interactive mode
+               if (output) {
+                       CTX_ERROR(jail->ctx, "Cannot capture output in interactive mode\n");
+                       r = -ENOTSUP;
+                       goto ERROR;
+               }
+
        } else {
                // Make the PTY read-only
                pty_flags |= PAKFIRE_PTY_READ_ONLY;
+
+               // Capture Output?
+               if (output)
+                       pty_flags |= PAKFIRE_PTY_CAPTURE_OUTPUT;
        }
 
        /*
@@ -1637,6 +1649,11 @@ PAKFIRE_EXPORT int pakfire_jail_exec(struct pakfire_jail* jail, const char* argv
                goto ERROR;
        }
 
+       // Return the output
+       if (output) {
+               *output = pakfire_pty_output(ctx.pty, NULL);
+       }
+
 ERROR:
        // Reset all callbacks
        pakfire_jail_set_stdin_callback(jail, NULL, NULL);
@@ -1686,7 +1703,7 @@ static int pakfire_jail_exec_interactive(
        if (r)
                return r;
 
-       return pakfire_jail_exec(jail, argv, flags);
+       return pakfire_jail_exec(jail, argv, flags, NULL);
 }
 
 int pakfire_jail_exec_script(struct pakfire_jail* jail,
@@ -1751,7 +1768,7 @@ int pakfire_jail_exec_script(struct pakfire_jail* jail,
                argv[i] = args[i-1];
 
        // Run the script
-       r = pakfire_jail_exec(jail, argv, 0);
+       r = pakfire_jail_exec(jail, argv, 0, NULL);
 
 ERROR:
        if (argv)
@@ -1779,11 +1796,8 @@ int pakfire_jail_run(struct pakfire* pakfire, const char* argv[], int flags, cha
        if (r)
                goto ERROR;
 
-       // Set the callback that captures the output
-       pakfire_jail_set_stdout_callback(jail, pakfire_jail_capture_stdout, output);
-
        // Execute the command
-       r = pakfire_jail_exec(jail, argv, 0);
+       r = pakfire_jail_exec(jail, argv, flags, output);
 
 ERROR:
        if (jail)
index c4c190ecb6262b92f5e5b8bc9b637528a06dda1b..4288ffadc1c94830beafe82ae2e8d788cee4f8c2 100644 (file)
@@ -22,6 +22,7 @@
 #include <fcntl.h>
 #include <stdlib.h>
 #include <sys/ioctl.h>
+#include <sys/mman.h>
 #include <sys/stat.h>
 #include <termios.h>
 #include <unistd.h>
@@ -90,12 +91,42 @@ struct pakfire_pty {
                PAKFIRE_PTY_STATE_DRAINING,
                PAKFIRE_PTY_STATE_DONE,
        } state;
+
+       // Captured Output
+       struct iovec output;
+       int outputfd;
 };
 
 static int pakfire_pty_has_flag(struct pakfire_pty* pty, int flag) {
        return pty->flags & flag;
 }
 
+static int pakfire_pty_store_output(struct pakfire_pty* pty) {
+       struct stat buffer;
+       int r;
+
+       // Stat the buffer
+       r = fstat(pty->stdout.fd, &buffer);
+       if (r < 0) {
+               CTX_ERROR(pty->ctx, "Could not stat the output buffer: %m\n");
+               return -errno;
+       }
+
+       CTX_DEBUG(pty->ctx, "Read %jd byte(s) of output\n", buffer.st_size);
+
+       // Map the output into memory
+       pty->output.iov_base = mmap(NULL, buffer.st_size, PROT_READ, MAP_SHARED, pty->stdout.fd, 0);
+       if (pty->output.iov_base == MAP_FAILED) {
+               CTX_ERROR(pty->ctx, "Could not map the output into memory: %m\n");
+               return -errno;
+       }
+
+       // Store the size of the buffer
+       pty->output.iov_len = buffer.st_size;
+
+       return 0;
+}
+
 static int pakfire_pty_same_inode(struct pakfire_pty* pty, int fd1, int fd2) {
        struct stat stat1;
        struct stat stat2;
@@ -253,11 +284,21 @@ static int pakfire_pty_drained(struct pakfire_pty* pty) {
 
 static int pakfire_pty_done(struct pakfire_pty* pty, int code) {
        sd_event* loop = NULL;
+       int r;
 
        // Don't run this more than once
        if (pty->state == PAKFIRE_PTY_STATE_DONE)
                return 0;
 
+       // Store the output
+       if (pakfire_pty_has_flag(pty, PAKFIRE_PTY_CAPTURE_OUTPUT)) {
+               r = pakfire_pty_store_output(pty);
+               if (r < 0) {
+                       CTX_ERROR(pty->ctx, "Could not store output: %s\n", strerror(-r));
+                       return r;
+               }
+       }
+
        // Disconnect
        pakfire_pty_disconnect(pty);
 
@@ -654,17 +695,30 @@ static int pakfire_pty_setup_forwarding(struct pakfire_pty* pty) {
                }
        }
 
-       // Connect to standard output
-       pty->stdout.fd = pakfire_pty_reopen(pty, STDOUT_FILENO, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
-       if (pty->stdout.fd < 0) {
-               CTX_DEBUG(pty->ctx, "Could not re-open standard output: %s. Ignoring.\n", strerror(-pty->stdout.fd));
+       // Create a buffer to capture the output in
+       if (pakfire_pty_has_flag(pty, PAKFIRE_PTY_CAPTURE_OUTPUT)) {
+               pty->stdout.fd = memfd_create("pty-output", MFD_CLOEXEC);
+               if (pty->stdout.fd < 0) {
+                       CTX_ERROR(pty->ctx, "Could not create the output buffer: %m\n");
+                       return -errno;
+               }
 
-               // Use the original file descriptor
-               pty->stdout.fd = STDOUT_FILENO;
+               // Close the buffer in the end
+               pty->stdout.close_fd = 1;
 
-       // Request to close the file descriptor afterwards
+       // Connect to standard output
        } else {
-               pty->stdout.close_fd = 1;
+               pty->stdout.fd = pakfire_pty_reopen(pty, STDOUT_FILENO, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
+               if (pty->stdout.fd < 0) {
+                       CTX_DEBUG(pty->ctx, "Could not re-open standard output: %s. Ignoring.\n", strerror(-pty->stdout.fd));
+
+                       // Use the original file descriptor
+                       pty->stdout.fd = STDOUT_FILENO;
+
+               // Request to close the file descriptor afterwards
+               } else {
+                       pty->stdout.close_fd = 1;
+               }
        }
 
        // Copy the terminal dimensions to the PTY
@@ -704,8 +758,18 @@ static int pakfire_pty_setup_forwarding(struct pakfire_pty* pty) {
        // Add standard output to the event loop
        r = sd_event_add_io(pty->loop, &pty->stdout.event,
                        pty->stdout.fd, EPOLLOUT|EPOLLET, pakfire_pty_stdout, pty);
-       if (r)
-               return r;
+       if (r < 0) {
+               switch (-r) {
+                       case EPERM:
+                               pty->stdout.io |= PAKFIRE_PTY_READY_TO_WRITE;
+                               break;
+
+                       default:
+                               CTX_ERROR(pty->ctx,
+                                       "Could not add standard output to the event loop: %s\n", strerror(-r));
+                               return r;
+               }
+       }
 
        // Set description
        sd_event_source_set_description(pty->stdout.event, "pty-stdout");
@@ -828,6 +892,12 @@ static void pakfire_pty_free(struct pakfire_pty* pty) {
        if (pty->socket[1] >= 0)
                close(pty->socket[1]);
 
+       // Output
+       if (pty->output.iov_base)
+               munmap(pty->output.iov_base, pty->output.iov_len);
+       if (pty->outputfd)
+               close(pty->outputfd);
+
        if (pty->loop)
                sd_event_unref(pty->loop);
        if (pty->ctx)
@@ -1007,3 +1077,33 @@ int pakfire_pty_drain(struct pakfire_pty* pty) {
 
        return pakfire_pty_drained(pty);
 }
+
+/*
+       Creates an independent copy of the output buffer
+*/
+char* pakfire_pty_output(struct pakfire_pty* pty, size_t* length) {
+       char* buffer = NULL;
+
+       // Is not operation supported?
+       if (!pakfire_pty_has_flag(pty, PAKFIRE_PTY_CAPTURE_OUTPUT)) {
+               errno = -ENOTSUP;
+               return NULL;
+       }
+
+       if (!pty->output.iov_base)
+               return NULL;
+
+       // Allocate a new buffer
+       buffer = calloc(pty->output.iov_len + 1, sizeof(*buffer));
+       if (!buffer)
+               return NULL;
+
+       // Copy the output
+       memcpy(buffer, pty->output.iov_base, pty->output.iov_len);
+
+       // Return the length
+       if (length)
+               *length = pty->output.iov_len;
+
+       return buffer;
+}
index 9cc41133464153d613db03b0f297d0445244a6df..dd00a460b3e8d18130bc80a413a34b167644d43d 100644 (file)
@@ -74,7 +74,7 @@ static int test_stats(const struct test* t) {
 
        // Run a few things
        for (unsigned int i = 0; i < 3; i++) {
-               ASSERT_SUCCESS(pakfire_jail_exec(jail, argv, 0));
+               ASSERT_SUCCESS(pakfire_jail_exec(jail, argv, 0, NULL));
        }
 
        // Try reading the stats into some invalid space
index 0848f02f8e543f641b4d2362d1dd87445b9cc100..533aa3317f1e33cfc365ccce6ea10dc866b67249 100644 (file)
@@ -68,7 +68,7 @@ static int test_exit_code(const struct test* t) {
        ASSERT_SUCCESS(pakfire_jail_create(&jail, t->pakfire));
 
        // Check if we receive the correct exit code
-       ASSERT(pakfire_jail_exec(jail, argv, 0) == 123);
+       ASSERT(pakfire_jail_exec(jail, argv, 0, NULL) == 123);
 
        // Success
        r = EXIT_SUCCESS;
@@ -92,7 +92,7 @@ static int test_segv(const struct test* t) {
        ASSERT_SUCCESS(pakfire_jail_create(&jail, t->pakfire));
 
        // Check if we receive the correct exit code
-       ASSERT(pakfire_jail_exec(jail, argv, 0) == 139);
+       ASSERT(pakfire_jail_exec(jail, argv, 0, NULL) == 139);
 
        // Success
        r = EXIT_SUCCESS;
@@ -134,12 +134,16 @@ FAIL:
 
 static int test_exec(const struct test* t) {
        struct pakfire_jail* jail = NULL;
+       char* output = NULL;
 
        // Create a new jail
        ASSERT_SUCCESS(pakfire_jail_create(&jail, t->pakfire));
 
        // Try to execute something
-       ASSERT_SUCCESS(pakfire_jail_exec(jail, cmd_hello_world, 0));
+       ASSERT_SUCCESS(pakfire_jail_exec(jail, cmd_hello_world, 0, &output));
+
+       // We should have some output
+       ASSERT_STRING_EQUALS(output, "Hello World!\r\n");
 
        // Destroy it
        ASSERT_NULL(pakfire_jail_unref(jail));
@@ -165,7 +169,7 @@ static int test_launch_into_cgroup(const struct test* t) {
        ASSERT_SUCCESS(pakfire_jail_set_cgroup(jail, cgroup));
 
        // Run command
-       ASSERT(pakfire_jail_exec(jail, cmd_hello_world, 0) == 0);
+       ASSERT(pakfire_jail_exec(jail, cmd_hello_world, 0, NULL) == 0);
 
        r = EXIT_SUCCESS;
 
@@ -199,12 +203,9 @@ static int test_nice(const struct test* t) {
        // Set something sane
        ASSERT_SUCCESS(pakfire_jail_nice(jail, 5));
 
-       // Capture the output of the next execution
-       pakfire_jail_set_stdout_callback(jail, pakfire_jail_capture_stdout, &output);
-
        // Check if the nice level has been set
-       ASSERT_SUCCESS(pakfire_jail_exec(jail, argv, 0));
-       ASSERT_STRING_EQUALS(output, "5\n");
+       ASSERT_SUCCESS(pakfire_jail_exec(jail, argv, 0, &output));
+       ASSERT_STRING_EQUALS(output, "5\r\n");
 
        // Success
        r = EXIT_SUCCESS;
@@ -237,10 +238,10 @@ static int test_memory_limit(const struct test* t) {
        ASSERT_SUCCESS(pakfire_cgroup_set_memory_limit(cgroup, 100 * 1024 * 1024));
 
        // Try to exhaust all memory
-       ASSERT_FAILURE(pakfire_jail_exec(jail, cmd_exhaust_memory, 0));
+       ASSERT_FAILURE(pakfire_jail_exec(jail, cmd_exhaust_memory, 0, NULL));
 
        // A fork bomb should also exhaust all memory
-       ASSERT_FAILURE(pakfire_jail_exec(jail, cmd_fork_bomb, 0));
+       ASSERT_FAILURE(pakfire_jail_exec(jail, cmd_fork_bomb, 0, NULL));
 
        // Success
        r = EXIT_SUCCESS;
@@ -274,7 +275,7 @@ static int test_pid_limit(const struct test* t) {
        ASSERT_SUCCESS(pakfire_cgroup_set_pid_limit(cgroup, 100));
 
        // Try to fork as many processes as possible
-       ASSERT_FAILURE(pakfire_jail_exec(jail, cmd_fork_bomb, 0));
+       ASSERT_FAILURE(pakfire_jail_exec(jail, cmd_fork_bomb, 0, NULL));
 
        // Success
        r = EXIT_SUCCESS;
@@ -333,7 +334,7 @@ static int test_bind(const struct test* t) {
        ASSERT_SUCCESS(pakfire_jail_bind(jail, source, target, MS_RDONLY));
 
        // Check if the mount actually works
-       ASSERT_SUCCESS(pakfire_jail_exec(jail, argv, 0));
+       ASSERT_SUCCESS(pakfire_jail_exec(jail, argv, 0, NULL));
 
        // Success
        r = EXIT_SUCCESS;
@@ -386,7 +387,7 @@ static int test_communicate(const struct test* t) {
        pakfire_jail_set_stdin_callback(jail, callback_stdin, &lines);
 
        // Check if the mount actually works
-       ASSERT_SUCCESS(pakfire_jail_exec(jail, argv, 0));
+       ASSERT_SUCCESS(pakfire_jail_exec(jail, argv, 0, NULL));
 
        // Success
        r = EXIT_SUCCESS;
@@ -405,7 +406,7 @@ static int test_send_one_signal(const struct test* t,
        };
 
        // Perform the command
-       return pakfire_jail_exec(jail, argv, 0);
+       return pakfire_jail_exec(jail, argv, 0, NULL);
 }
 
 static int test_send_signal(const struct test* t) {
@@ -449,7 +450,7 @@ static int test_timeout(const struct test* t) {
        ASSERT_SUCCESS(pakfire_jail_set_timeout(jail, 1));
 
        // Check if we receive the correct exit code
-       ASSERT(pakfire_jail_exec(jail, argv, 0) == 139);
+       ASSERT(pakfire_jail_exec(jail, argv, 0, NULL) == 139);
 
        // Success
        r = EXIT_SUCCESS;