From: Michael Tremer Date: Tue, 2 Aug 2022 17:27:28 +0000 (+0000) Subject: jail: Import logging stuff from execute.c X-Git-Tag: 0.9.28~622 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=616f1fca47cef1d7b2607cc4c82af5c9121732bb;p=pakfire.git jail: Import logging stuff from execute.c Signed-off-by: Michael Tremer --- diff --git a/src/libpakfire/include/pakfire/jail.h b/src/libpakfire/include/pakfire/jail.h index 6cda85a16..da1030ee5 100644 --- a/src/libpakfire/include/pakfire/jail.h +++ b/src/libpakfire/include/pakfire/jail.h @@ -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); diff --git a/src/libpakfire/jail.c b/src/libpakfire/jail.c index 2f41728fe..76cdd104a 100644 --- a/src/libpakfire/jail.c +++ b/src/libpakfire/jail.c @@ -19,6 +19,7 @@ #############################################################################*/ #include +#include #include #include #include @@ -26,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -42,7 +44,9 @@ #include #include -#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);