From 44142e33e77cbcb44b048b2f6c11e052cc1fa5c4 Mon Sep 17 00:00:00 2001 From: Michael Tremer Date: Mon, 24 Mar 2025 15:58:33 +0000 Subject: [PATCH] jail: Bring back pipes for non-interactive sessions Signed-off-by: Michael Tremer --- src/pakfire/jail.c | 437 ++++++++++++++++++++++++++++++++++----- src/pakfire/log_stream.c | 55 +++++ src/pakfire/log_stream.h | 2 + tests/libpakfire/jail.c | 2 +- 4 files changed, 449 insertions(+), 47 deletions(-) diff --git a/src/pakfire/jail.c b/src/pakfire/jail.c index 339dcc67..85bbbf70 100644 --- a/src/pakfire/jail.c +++ b/src/pakfire/jail.c @@ -130,8 +130,19 @@ struct pakfire_jail_exec { // PTY struct pakfire_pty* pty; - // Log pipes + // Pipes struct pakfire_jail_pipes { + int stdin[2]; + int stdout[2]; + int stderr[2]; + } pipes; + + // Streams + struct pakfire_jail_streams { + // Output + FILE* stdout; + FILE* stderr; + // Logging struct pakfire_log_stream* INFO; struct pakfire_log_stream* WARN; @@ -139,7 +150,7 @@ struct pakfire_jail_exec { #ifdef ENABLE_DEBUG struct pakfire_log_stream* DEBUG; #endif /* ENABLE_DEBUG */ - } log; + } streams; // Timeout sd_event_source* timeout; @@ -150,6 +161,17 @@ struct pakfire_jail_exec { // The function to be called once the jail has been set up pakfire_jail_callback callback; void* data; + + // Callbacks + struct pakfire_jail_exec_callbacks { + // Input + pakfire_jail_input_callback input; + void* input_data; + + // Output + pakfire_jail_output_callback output; + void* output_data; + } callbacks; }; static int pivot_root(const char* new_root, const char* old_root) { @@ -312,6 +334,143 @@ int pakfire_jail_set_timeout( return 0; } +/* + Pipes +*/ + +static int pakfire_jail_setup_pipe(struct pakfire_jail* self, int (*fds)[2], const int flags) { + int r; + + // Setup the pipe + r = pipe2(*fds, flags); + if (r < 0) { + ERROR(self->ctx, "Could not setup pipe: %m\n"); + return -errno; + } + + return 0; +} + +static void pakfire_jail_close_pipe(struct pakfire_jail* self, int fds[2]) { + for (unsigned int i = 0; i < 2; i++) + if (fds[i] >= 0) + close(fds[i]); +} + +/* + This is a convenience function to fetch the reading end of a pipe and + closes the write end. +*/ +static int pakfire_jail_get_pipe_to_read(struct pakfire_jail* self, int (*fds)[2]) { + // Give the variables easier names to avoid confusion + int* fd_read = &(*fds)[0]; + int* fd_write = &(*fds)[1]; + + // Close the write end of the pipe + if (*fd_write >= 0) { + close(*fd_write); + *fd_write = -EBADF; + } + + // Return the read end + if (*fd_read >= 0) + return *fd_read; + + return -EBADF; +} + +static int pakfire_jail_get_pipe_to_write(struct pakfire_jail* self, int (*fds)[2]) { + // Give the variables easier names to avoid confusion + int* fd_read = &(*fds)[0]; + int* fd_write = &(*fds)[1]; + + // Close the read end of the pipe + if (*fd_read >= 0) { + close(*fd_read); + *fd_read = -EBADF; + } + + // Return the write end + if (*fd_write >= 0) + return *fd_write; + + return -EBADF; +} + +static int pakfire_jail_stdin(sd_event_source* source, int fd, unsigned int events, void* data) { + struct pakfire_jail_exec* exec = data; + struct pakfire_jail* self = exec->jail; + ssize_t bytes_written = 0; + + // Fail if no callback is set + if (unlikely(!exec->callbacks.input)) { + ERROR(self->ctx, "Tried to call the standard input callback, but it is not set\n"); + goto CLOSE; + } + + // Call the callback + bytes_written = exec->callbacks.input(self->ctx, fd, exec->callbacks.input_data); + if (bytes_written < 0) { + ERROR(self->ctx, "The input callback failed: %s\n", strerror(-bytes_written)); + return bytes_written; + + // Close the file descriptor if we are done reading + } else if (bytes_written == 0) { + goto CLOSE; + } + + return 0; + +CLOSE: + pakfire_jail_close_pipe(self, exec->pipes.stdin); + + return 0; +} + +static int pakfire_jail_output(struct pakfire_jail* self, int fd, unsigned int events, FILE* f) { + ssize_t bytes_written = 0; + ssize_t bytes_read = 0; + char buffer[4096]; + + for (;;) { + bytes_read = read(fd, buffer, sizeof(buffer)); + if (bytes_read < 0) { + switch (errno) { + case EAGAIN: + return 0; + + default: + return -errno; + } + + // XXX Should we close the stream here? + } else if (bytes_read == 0) { + return 0; + } + + // Write the buffer to the output + if (f) { + bytes_written = fwrite(buffer, 1, bytes_read, f); + if (bytes_written < bytes_read) { + ERROR(self->ctx, "Failed to write output: %m\n"); + return -errno; + } + } + } +} + +static int pakfire_jail_stdout(sd_event_source* source, int fd, unsigned int events, void* data) { + struct pakfire_jail_exec* exec = data; + + return pakfire_jail_output(exec->jail, fd, events, exec->streams.stdout); +} + +static int pakfire_jail_stderr(sd_event_source* source, int fd, unsigned int events, void* data) { + struct pakfire_jail_exec* exec = data; + + return pakfire_jail_output(exec->jail, fd, events, exec->streams.stderr); +} + /* This function replaces any logging in the child process. @@ -327,20 +486,20 @@ static void pakfire_jail_log_redirect(void* data, int priority, const char* file switch (priority) { case LOG_INFO: - pakfire_log_stream_vprintf(ctx->log.INFO, format, args); + pakfire_log_stream_vprintf(ctx->streams.INFO, format, args); break; case LOG_WARNING: - pakfire_log_stream_vprintf(ctx->log.WARN, format, args); + pakfire_log_stream_vprintf(ctx->streams.WARN, format, args); break; case LOG_ERR: - pakfire_log_stream_vprintf(ctx->log.ERROR, format, args); + pakfire_log_stream_vprintf(ctx->streams.ERROR, format, args); break; #ifdef ENABLE_DEBUG case LOG_DEBUG: - pakfire_log_stream_vprintf(ctx->log.DEBUG, format, args); + pakfire_log_stream_vprintf(ctx->streams.DEBUG, format, args); break; #endif /* ENABLE_DEBUG */ @@ -1061,6 +1220,7 @@ ERROR: Performs the initialisation that needs to happen in the parent part */ static int pakfire_jail_parent(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx) { + int fd = -EBADF; int r; // Register the PID file descriptor @@ -1070,17 +1230,47 @@ static int pakfire_jail_parent(struct pakfire_jail* jail, struct pakfire_jail_ex return r; } + // Prepare standard input for writing + fd = pakfire_jail_get_pipe_to_write(jail, &ctx->pipes.stdin); + if (fd >= 0) { + r = sd_event_add_io(ctx->loop, NULL, fd, EPOLLOUT|EPOLLHUP|EPOLLET, pakfire_jail_stdin, ctx); + if (r < 0) { + ERROR(jail->ctx, "Failed to register standard input for writing: %s\n", strerror(-r)); + return r; + } + } + + // Prepare standard output for reading + fd = pakfire_jail_get_pipe_to_read(jail, &ctx->pipes.stdout); + if (fd >= 0) { + r = sd_event_add_io(ctx->loop, NULL, fd, EPOLLIN|EPOLLHUP|EPOLLET, pakfire_jail_stdout, ctx); + if (r < 0) { + ERROR(jail->ctx, "Failed to register standard output for reading: %s\n", strerror(-r)); + return r; + } + } + + // Prepare standard error for reading + fd = pakfire_jail_get_pipe_to_read(jail, &ctx->pipes.stderr); + if (fd >= 0) { + r = sd_event_add_io(ctx->loop, NULL, fd, EPOLLIN|EPOLLHUP|EPOLLET, pakfire_jail_stderr, ctx); + if (r < 0) { + ERROR(jail->ctx, "Failed to register standard error for reading: %s\n", strerror(-r)); + return r; + } + } + // Setup logging - r = pakfire_log_stream_in_parent(ctx->log.INFO, ctx->loop); + r = pakfire_log_stream_in_parent(ctx->streams.INFO, ctx->loop); if (r) return r; - r = pakfire_log_stream_in_parent(ctx->log.ERROR, ctx->loop); + r = pakfire_log_stream_in_parent(ctx->streams.ERROR, ctx->loop); if (r) return r; #ifdef ENABLE_DEBUG - r = pakfire_log_stream_in_parent(ctx->log.DEBUG, ctx->loop); + r = pakfire_log_stream_in_parent(ctx->streams.DEBUG, ctx->loop); if (r) return r; #endif /* ENABLE_DEBUG */ @@ -1138,6 +1328,49 @@ static int pakfire_jail_switch_root(struct pakfire_jail* jail, const char* root) return 0; } +static int pakfire_jail_connect_null(struct pakfire_jail* self, int fileno) { + int fd = -EBADF; + int flags = 0; + int r; + + switch (fileno) { + case STDIN_FILENO: + flags |= O_RDONLY; + break; + + case STDOUT_FILENO: + case STDERR_FILENO: + flags |= O_WRONLY; + break; + + default: + return -EINVAL; + } + + + // Open /dev/null + fd = open("/dev/null", flags); + if (fd < 0) { + ERROR(self->ctx, "Failed to open /dev/null: %m\n"); + r = -errno; + goto ERROR; + } + + // Copy to the desired file descriptor + r = dup2(fd, fileno); + if (r < 0) { + ERROR(self->ctx, "Failed to duplicate the file descriptor: %m\n"); + r = -errno; + goto ERROR; + } + +ERROR: + if (fd >= 0) + close(fd); + + return r; +} + static int pakfire_jail_child(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx) { int r; @@ -1279,27 +1512,76 @@ static int pakfire_jail_child(struct pakfire_jail* jail, struct pakfire_jail_exe } // Open a new PTY - r = pakfire_pty_open(ctx->pty); - if (r < 0) { - ERROR(jail->ctx, "Could not open a new PTY: %s\n", strerror(-r)); - return r; + if (ctx->pty) { + r = pakfire_pty_open(ctx->pty); + if (r < 0) { + ERROR(jail->ctx, "Could not open a new PTY: %s\n", strerror(-r)); + return r; + } + + // If we don't have a PTY, we will connect the logging pipes + } else { + // Standard input + if (ctx->pipes.stdin[0] >= 0) { + r = dup2(ctx->pipes.stdin[0], STDIN_FILENO); + if (r < 0) { + ERROR(jail->ctx, "Failed to connect %d to stdin: %m\n", ctx->pipes.stdin[0]); + return -errno; + } + } else { + r = pakfire_jail_connect_null(jail, STDIN_FILENO); + if (r < 0) + return r; + } + + // Standard output + if (ctx->pipes.stdout[1] >= 1) { + r = dup2(ctx->pipes.stdout[1], STDOUT_FILENO); + if (r < 0) { + ERROR(jail->ctx, "Failed to connect %d to stdout: %m\n", ctx->pipes.stdout[1]); + return -errno; + } + } else { + r = pakfire_jail_connect_null(jail, STDOUT_FILENO); + if (r < 0) + return r; + } + + // Standard error + if (ctx->pipes.stderr[1] >= 1) { + r = dup2(ctx->pipes.stderr[1], STDERR_FILENO); + if (r < 0) { + ERROR(jail->ctx, "Failed to connect %d to stderr: %m\n", ctx->pipes.stderr[1]); + return -errno; + } + } else { + r = pakfire_jail_connect_null(jail, STDERR_FILENO); + if (r < 0) + return r; + } + + // Close the pipes entirely as the reading/writing ends + // that we need have been moved to stdin/stdout/stderr. + pakfire_jail_close_pipe(jail, ctx->pipes.stdin); + pakfire_jail_close_pipe(jail, ctx->pipes.stdout); + pakfire_jail_close_pipe(jail, ctx->pipes.stderr); } // Setup logging - r = pakfire_log_stream_in_child(ctx->log.INFO); + r = pakfire_log_stream_in_child(ctx->streams.INFO); if (r) return r; - r = pakfire_log_stream_in_child(ctx->log.WARN); + r = pakfire_log_stream_in_child(ctx->streams.WARN); if (r) return r; - r = pakfire_log_stream_in_child(ctx->log.ERROR); + r = pakfire_log_stream_in_child(ctx->streams.ERROR); if (r) return r; #ifdef ENABLE_DEBUG - r = pakfire_log_stream_in_child(ctx->log.DEBUG); + r = pakfire_log_stream_in_child(ctx->streams.DEBUG); if (r) return r; #endif /* ENABLE_DEBUG */ @@ -1342,6 +1624,8 @@ static int __pakfire_jail_exec(struct pakfire_jail* jail, pakfire_pty_stdin_callback stdin_callback, void* stdin_data, pakfire_pty_stdout_callback stdout_callback, void* stdout_data, char** output, size_t* output_length) { + struct pakfire_log_stream* stdout = NULL; + struct pakfire_log_stream* stderr = NULL; int r; // We cannot have both, an output callback and buffer @@ -1352,11 +1636,18 @@ static int __pakfire_jail_exec(struct pakfire_jail* jail, struct pakfire_jail_exec ctx = { .jail = jail, .flags = flags, - .pidfd = -1, + .pidfd = -EBADF, // Callback & Data .callback = callback, .data = data, + + // Pipes + .pipes = { + .stdin = { -EBADF, -EBADF }, + .stdout = { -EBADF, -EBADF }, + .stderr = { -EBADF, -EBADF }, + }, }; DEBUG(jail->ctx, "Executing jail...\n"); @@ -1406,29 +1697,66 @@ static int __pakfire_jail_exec(struct pakfire_jail* jail, goto ERROR; } - // Do nothing if we have a callback set - } else if (stdout_callback) { - // Perform line-buffering on the callback - r = pakfire_pty_log_stream(ctx.pty, stdout_callback, stdout_data); - if (r < 0) { - ERROR(jail->ctx, "Failed to setup log stream: %s\n", strerror(-r)); + // Otherwise we are running a non-interactive session + } else { + // Create a pipe for the standard input only when a callback is set + if (stdin_callback) { + r = pakfire_jail_setup_pipe(jail, &ctx.pipes.stdin, O_NONBLOCK); + if (r < 0) + goto ERROR; + } + + // Create a pipe for standard output + r = pakfire_jail_setup_pipe(jail, &ctx.pipes.stdout, O_NONBLOCK); + if (r < 0) + goto ERROR; + + // Create a pipe for standard error + r = pakfire_jail_setup_pipe(jail, &ctx.pipes.stderr, O_NONBLOCK); + if (r < 0) goto ERROR; + + // Setup some buffer if we want to capture the output + if (output) { + ctx.streams.stdout = open_memstream(output, output_length); + if (!ctx.streams.stdout) { + ERROR(jail->ctx, "Failed to open memory stream for output: %m\n"); + r = -errno; + goto ERROR; + } + + // Otherwise send the output to the log streamer + } else { + // Create a new stream + r = pakfire_log_stream_create(&stdout, jail->ctx, stdout_callback, stdout_data); + if (r < 0) { + ERROR(jail->ctx, "Failed to create standard output stream: %s\n", strerror(-r)); + goto ERROR; + } + + // Ask to stream our output into this + ctx.streams.stdout = pakfire_log_stream_as_file(stdout); + if (!ctx.streams.stdout) { + ERROR(jail->ctx, "Failed to open file stream for standard output: %m\n"); + r = -errno; + goto ERROR; + } } - // Capture Output? - } else if (output) { -#warning This needs to be moved back internally again -#if 0 - r = pakfire_pty_capture_output(ctx.pty, output, output_length); + // Send the standard error output to a log streamer, too + r = pakfire_log_stream_create(&stderr, jail->ctx, stdout_callback, stdout_data); if (r < 0) { - ERROR(jail->ctx, "Failed to set up output capture: %s\n", strerror(-r)); + ERROR(jail->ctx, "Failed to create standard error stream: %s\n", strerror(-r)); goto ERROR; } -#endif - // Otherwise we dump everything to the console - } else { - // XXX Need to find a solution about what to do here... + // Ask to stream our output into this + ctx.streams.stderr = pakfire_log_stream_as_file(stderr); + if (!ctx.streams.stderr) { + ERROR(jail->ctx, "Failed to open file stream for standard output: %m\n"); + r = -errno; + goto ERROR; + } } /* @@ -1441,29 +1769,31 @@ static int __pakfire_jail_exec(struct pakfire_jail* jail, goto ERROR; } +#if 0 // Configure the callbacks if (stdin_callback) pakfire_pty_set_stdin_callback(ctx.pty, stdin_callback, stdin_data); +#endif // Setup pipes for logging // INFO - r = pakfire_log_stream_create(&ctx.log.INFO, jail->ctx, pakfire_jail_INFO, jail); + r = pakfire_log_stream_create(&ctx.streams.INFO, jail->ctx, pakfire_jail_INFO, jail); if (r) goto ERROR; // WARN - r = pakfire_log_stream_create(&ctx.log.WARN, jail->ctx, pakfire_jail_WARN, jail); + r = pakfire_log_stream_create(&ctx.streams.WARN, jail->ctx, pakfire_jail_WARN, jail); if (r) goto ERROR; // ERROR - r = pakfire_log_stream_create(&ctx.log.ERROR, jail->ctx, pakfire_jail_ERROR, jail); + r = pakfire_log_stream_create(&ctx.streams.ERROR, jail->ctx, pakfire_jail_ERROR, jail); if (r) goto ERROR; #ifdef ENABLE_DEBUG // DEBUG - r = pakfire_log_stream_create(&ctx.log.DEBUG, jail->ctx, pakfire_jail_DEBUG, jail); + r = pakfire_log_stream_create(&ctx.streams.DEBUG, jail->ctx, pakfire_jail_DEBUG, jail); if (r) goto ERROR; #endif /* ENABLE_DEBUG */ @@ -1557,6 +1887,11 @@ ERROR: pakfire_cgroup_unref(ctx.cgroup); } + // Close any pipes + pakfire_jail_close_pipe(jail, ctx.pipes.stdin); + pakfire_jail_close_pipe(jail, ctx.pipes.stdout); + pakfire_jail_close_pipe(jail, ctx.pipes.stderr); + // Close any file descriptors if (ctx.pidfd >= 0) close(ctx.pidfd); @@ -1565,16 +1900,26 @@ ERROR: if (ctx.pty) pakfire_pty_unref(ctx.pty); + // Streams + if (ctx.streams.stdout) + fclose(ctx.streams.stdout); + if (ctx.streams.stderr) + fclose(ctx.streams.stderr); + if (stdout) + pakfire_log_stream_unref(stdout); + if (stderr) + pakfire_log_stream_unref(stderr); + // Logging - if (ctx.log.INFO) - pakfire_log_stream_unref(ctx.log.INFO); - if (ctx.log.WARN) - pakfire_log_stream_unref(ctx.log.WARN); - if (ctx.log.ERROR) - pakfire_log_stream_unref(ctx.log.ERROR); + if (ctx.streams.INFO) + pakfire_log_stream_unref(ctx.streams.INFO); + if (ctx.streams.WARN) + pakfire_log_stream_unref(ctx.streams.WARN); + if (ctx.streams.ERROR) + pakfire_log_stream_unref(ctx.streams.ERROR); #ifdef ENABLE_DEBUG - if (ctx.log.DEBUG) - pakfire_log_stream_unref(ctx.log.DEBUG); + if (ctx.streams.DEBUG) + pakfire_log_stream_unref(ctx.streams.DEBUG); #endif /* ENABLE_DEBUG */ if (ctx.timeout) diff --git a/src/pakfire/log_stream.c b/src/pakfire/log_stream.c index b7f6faff..f113949b 100644 --- a/src/pakfire/log_stream.c +++ b/src/pakfire/log_stream.c @@ -405,3 +405,58 @@ int pakfire_log_stream_pty(struct pakfire_ctx* ctx, void* data, const char* buff // Return how many bytes we have consumed return length; } + +static ssize_t __pakfire_log_stream_write(void* cookie, const char* buffer, size_t length) { + struct pakfire_log_stream* self = cookie; + int r; + + // Push the data into the buffer + r = pakfire_buffer_push(&self->buffer, buffer, length); + if (r < 0) + return r; + + // Drain the buffer + r = pakfire_log_stream_drain_buffer(self); + if (r < 0) + return r; + + // Return how many bytes we have consumed + return length; +} + +static int __pakfire_log_stream_close(void* cookie) { + struct pakfire_log_stream* self = cookie; + + // Release the reference to the stream + if (self) + pakfire_log_stream_unref(self); + + return 0; +} + +FILE* pakfire_log_stream_as_file(struct pakfire_log_stream* self) { + struct pakfire_log_stream* stream = NULL; + static cookie_io_functions_t functions = { + .write = __pakfire_log_stream_write, + .close = __pakfire_log_stream_close, + }; + FILE* f = NULL; + + // Create a new reference to the stream + stream = pakfire_log_stream_ref(self); + + // Create the file handle + f = fopencookie(stream, "w+", functions); + if (!f) { + ERROR(self->ctx, "fopencookie failed: %m\n"); + goto ERROR; + } + + return f; + +ERROR: + if (stream) + pakfire_log_stream_unref(stream); + + return NULL; +} diff --git a/src/pakfire/log_stream.h b/src/pakfire/log_stream.h index 91b4ce21..cf3022a3 100644 --- a/src/pakfire/log_stream.h +++ b/src/pakfire/log_stream.h @@ -51,4 +51,6 @@ int pakfire_log_stream_close(struct pakfire_log_stream* stream); int pakfire_log_stream_pty(struct pakfire_ctx* ctx, void* data, const char* buffer, size_t length); +FILE* pakfire_log_stream_as_file(struct pakfire_log_stream* self); + #endif /* PAKFIRE_LOG_STREAM_H */ diff --git a/tests/libpakfire/jail.c b/tests/libpakfire/jail.c index 8f541032..2196fbe3 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_command(jail, argv, NULL, 0) == 123); + ASSERT_EQUALS(pakfire_jail_exec_command(jail, argv, NULL, 0), 123); // Success r = EXIT_SUCCESS; -- 2.39.5