From: Michael Tremer Date: Thu, 10 Oct 2024 09:51:52 +0000 (+0000) Subject: pty: Implement capturing the output X-Git-Tag: 0.9.30~1099 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ff20f0107e31c06f8a3aa89e3d5647a4c7b0f84d;p=pakfire.git pty: Implement capturing the output 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 --- diff --git a/src/_pakfire/pakfire.c b/src/_pakfire/pakfire.c index f6e6f8f17..5b2fcddc8 100644 --- a/src/_pakfire/pakfire.c +++ b/src/_pakfire/pakfire.c @@ -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 diff --git a/src/libpakfire/archive.c b/src/libpakfire/archive.c index a263cd848..7808e0a69 100644 --- a/src/libpakfire/archive.c +++ b/src/libpakfire/archive.c @@ -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) diff --git a/src/libpakfire/build.c b/src/libpakfire/build.c index a2dac5a81..c3ed131d7 100644 --- a/src/libpakfire/build.c +++ b/src/libpakfire/build.c @@ -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); diff --git a/src/libpakfire/include/pakfire/jail.h b/src/libpakfire/include/pakfire/jail.h index 55fbfe998..55006fa9a 100644 --- a/src/libpakfire/include/pakfire/jail.h +++ b/src/libpakfire/include/pakfire/jail.h @@ -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 diff --git a/src/libpakfire/include/pakfire/pty.h b/src/libpakfire/include/pakfire/pty.h index 3efa97a94..32febcf13 100644 --- a/src/libpakfire/include/pakfire/pty.h +++ b/src/libpakfire/include/pakfire/pty.h @@ -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 */ diff --git a/src/libpakfire/jail.c b/src/libpakfire/jail.c index 4d6626cea..1eae57e34 100644 --- a/src/libpakfire/jail.c +++ b/src/libpakfire/jail.c @@ -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) diff --git a/src/libpakfire/pty.c b/src/libpakfire/pty.c index c4c190ecb..4288ffadc 100644 --- a/src/libpakfire/pty.c +++ b/src/libpakfire/pty.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -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; +} diff --git a/tests/libpakfire/cgroup.c b/tests/libpakfire/cgroup.c index 9cc411334..dd00a460b 100644 --- a/tests/libpakfire/cgroup.c +++ b/tests/libpakfire/cgroup.c @@ -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 diff --git a/tests/libpakfire/jail.c b/tests/libpakfire/jail.c index 0848f02f8..533aa3317 100644 --- a/tests/libpakfire/jail.c +++ b/tests/libpakfire/jail.c @@ -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;