From f0cf4ffb6e63e88c396256644172ce3e655e068f Mon Sep 17 00:00:00 2001 From: Michael Tremer Date: Sun, 6 Oct 2024 12:14:12 +0000 Subject: [PATCH] jail: Move the PTY into an extra file The code for this is rather large and makes jail.c almost unreadable. This patch does not move the entire feature over just yet, but it is a start. Signed-off-by: Michael Tremer --- Makefile.am | 2 + src/libpakfire/include/pakfire/pty.h | 45 ++ src/libpakfire/jail.c | 779 +-------------------------- src/libpakfire/pty.c | 673 +++++++++++++++++++++++ 4 files changed, 741 insertions(+), 758 deletions(-) create mode 100644 src/libpakfire/include/pakfire/pty.h create mode 100644 src/libpakfire/pty.c diff --git a/Makefile.am b/Makefile.am index 9c4205681..c4d54570f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -235,6 +235,7 @@ libpakfire_la_SOURCES = \ src/libpakfire/path.c \ src/libpakfire/problem.c \ src/libpakfire/progress.c \ + src/libpakfire/pty.c \ src/libpakfire/pwd.c \ src/libpakfire/repo.c \ src/libpakfire/repolist.c \ @@ -287,6 +288,7 @@ pkginclude_HEADERS += \ src/libpakfire/include/pakfire/private.h \ src/libpakfire/include/pakfire/problem.h \ src/libpakfire/include/pakfire/progress.h \ + src/libpakfire/include/pakfire/pty.h \ src/libpakfire/include/pakfire/pwd.h \ src/libpakfire/include/pakfire/repo.h \ src/libpakfire/include/pakfire/repolist.h \ diff --git a/src/libpakfire/include/pakfire/pty.h b/src/libpakfire/include/pakfire/pty.h new file mode 100644 index 000000000..f3f394227 --- /dev/null +++ b/src/libpakfire/include/pakfire/pty.h @@ -0,0 +1,45 @@ +/*############################################################################# +# # +# Pakfire - The IPFire package management system # +# Copyright (C) 2024 Pakfire development team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +# # +#############################################################################*/ + +#ifndef PAKFIRE_PTY_H +#define PAKFIRE_PTY_H + +#ifdef PAKFIRE_PRIVATE + +#include + +#include + +struct pakfire_pty; + +enum pakfire_pty_flags { + PAKFIRE_PTY_FORWARD = (1 << 0), +}; + +int pakfire_pty_create(struct pakfire_pty** pty, + struct pakfire_ctx* ctx, sd_event* loop, int flags); + +struct pakfire_pty* pakfire_pty_ref(struct pakfire_pty* pty); +struct pakfire_pty* pakfire_pty_unref(struct pakfire_pty* pty); + +int pakfire_pty_open(struct pakfire_pty* pty); + +#endif /* PAKFIRE_PRIVATE */ +#endif /* PAKFIRE_PTY_H */ diff --git a/src/libpakfire/jail.c b/src/libpakfire/jail.c index 7be231bda..99838e12c 100644 --- a/src/libpakfire/jail.c +++ b/src/libpakfire/jail.c @@ -22,22 +22,17 @@ #include #include #include -#include -#include #include #include #include #include #include -#include #include #include #include #include #include #include -#include -#include // libnl3 #include @@ -61,13 +56,13 @@ #include #include #include +#include #include #include #include #define BUFFER_SIZE 1024 * 64 #define ENVIRON_SIZE 128 -#define EPOLL_MAX_EVENTS 2 #define MAX_MOUNTPOINTS 8 // The default environment that will be set for every command @@ -88,29 +83,6 @@ static const struct environ { struct pakfire_log_buffer { char data[BUFFER_SIZE]; size_t used; - int priority; -}; - -enum pakfire_jail_pty_io { - PAKFIRE_JAIL_PTY_READY_TO_READ = (1 << 0), - PAKFIRE_JAIL_PTY_READY_TO_WRITE = (1 << 1), -}; - -struct pakfire_jail_pty_stdio { - // File Descriptor - int fd; - - // Buffer - struct pakfire_log_buffer buffer; - - // Terminal Attributes - struct termios attrs; - - // File Descriptor Flags - int flags; - - // IO Flags - enum pakfire_jail_pty_io io; }; struct pakfire_jail_mountpoint { @@ -172,12 +144,12 @@ struct pakfire_jail_exec { pid_t pid; int pidfd; - // Socket to pass FDs - int socket[2]; - // FD to notify the client that the parent has finished initialization int completed_fd; + // PTY + struct pakfire_pty* pty; + // Log pipes struct pakfire_jail_pipes { // Logging @@ -193,22 +165,6 @@ struct pakfire_jail_exec { struct pakfire_cgroup* cgroup; struct pakfire_cgroup_stats cgroup_stats; - - // PTY - struct pakfire_jail_pty { - // The path to the console - char console[PATH_MAX]; - - // The master device - struct pakfire_jail_pty_master { - int fd; - enum pakfire_jail_pty_io io; - } master; - - // Standard Input/Output - struct pakfire_jail_pty_stdio stdin; - struct pakfire_jail_pty_stdio stdout; - } pty; }; static int pivot_root(const char* new_root, const char* old_root) { @@ -538,39 +494,6 @@ static void pakfire_jail_log_redirect(void* data, int priority, const char* file } } -static int pakfire_jail_fill_buffer(struct pakfire_jail* jail, int fd, struct pakfire_log_buffer* buffer) { - int r; - - // Skip this if there is not space left in the buffer - if (buffer->used >= sizeof(buffer->data)) - return 0; - - // Fill the buffer - r = read(fd, buffer->data + buffer->used, sizeof(buffer->data) - buffer->used); - - // Handle errors - if (r < 0) { - switch (errno) { - case EAGAIN: - case EIO: - break; - - default: - return -errno; - } - - // EOF - } else if (r == 0) { - // XXX What to do here? - - // Successful read - } else { - buffer->used += r; - } - - return 0; -} - static int pakfire_jail_drain_buffer_with_callback(struct pakfire_jail* jail, struct pakfire_log_buffer* buffer, pakfire_jail_stdout_callback callback, void* data) { const char* eol = NULL; @@ -612,41 +535,6 @@ static int pakfire_jail_drain_buffer_with_callback(struct pakfire_jail* jail, return 0; } -static int pakfire_jail_drain_buffer(struct pakfire_jail* jail, int fd, struct pakfire_log_buffer* buffer) { - int r; - - // Nothing to do if the buffer is empty - if (!buffer->used) - return 0; - - // Do not try to write to an invalid file descriptor - if (fd < 0) - return 0; - - // Drain the buffer - r = write(fd, buffer->data, buffer->used); - - // Handle errors - if (r < 0) { - switch (errno) { - case EAGAIN: - case EIO: - break; - - default: - return -errno; - } - - // Successful write - } else { - memmove(buffer->data, buffer->data + r, buffer->used - r); - - buffer->used -= r; - } - - return 0; -} - /* Passes any log messages on to the context logger */ @@ -711,294 +599,6 @@ static int pakfire_jail_stream_stdin(struct pakfire_jail* jail, } } -static int pakfire_jail_recv_fd(struct pakfire_jail* jail, int socket, int* fd) { - const size_t payload_length = sizeof(fd); - char buffer[CMSG_SPACE(payload_length)]; - int r; - - struct msghdr msg = { - .msg_control = buffer, - .msg_controllen = sizeof(buffer), - }; - - // Receive the message - r = recvmsg(socket, &msg, 0); - if (r) { - CTX_ERROR(jail->ctx, "Could not receive file descriptor: %s\n", strerror(errno)); - return -errno; - } - - // Fetch the payload - struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); - if (!cmsg) - return -EBADMSG; - - *fd = *((int*)CMSG_DATA(cmsg)); - - CTX_DEBUG(jail->ctx, "Received fd %d from socket %d\n", *fd, socket); - - return 0; -} - -static int pakfire_jail_send_fd(struct pakfire_jail* jail, int socket, int fd) { - const size_t payload_length = sizeof(fd); - char buffer[CMSG_SPACE(payload_length)]; - int r; - - CTX_DEBUG(jail->ctx, "Sending fd %d to socket %d\n", fd, socket); - - // Header - struct msghdr msg = { - .msg_control = buffer, - .msg_controllen = sizeof(buffer), - }; - - // Payload - struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); - cmsg->cmsg_level = SOL_SOCKET; - cmsg->cmsg_type = SCM_RIGHTS; - cmsg->cmsg_len = CMSG_LEN(payload_length); - - // Set payload - *((int*)CMSG_DATA(cmsg)) = fd; - - // Send the message - r = sendmsg(socket, &msg, 0); - if (r) { - CTX_ERROR(jail->ctx, "Could not send file descriptor: %s\n", strerror(errno)); - return -errno; - } - - return 0; -} - -static void pakfire_jail_close_pipe(struct pakfire_jail* jail, 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* jail, 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 = -1; - } - - // Return the read end - if (*fd_read >= 0) - return *fd_read; - - return -1; -} - -static int pakfire_jail_get_pipe_to_write(struct pakfire_jail* jail, 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 = -1; - } - - // Return the write end - if (*fd_write >= 0) - return *fd_write; - - return -1; -} - -static int pakfire_jail_epoll_add_fd(struct pakfire_jail* jail, int epollfd, int fd, int events) { - struct epoll_event event = { - .events = events|EPOLLHUP, - .data = { - .fd = fd, - }, - }; - int r; - - // Read flags - int flags = fcntl(fd, F_GETFL, 0); - - // Set modified flags - r = fcntl(fd, F_SETFL, flags|O_NONBLOCK); - if (r < 0) { - CTX_ERROR(jail->ctx, "Could not set file descriptor %d into non-blocking mode: %s\n", - fd, strerror(errno)); - return -errno; - } - - // Add the file descriptor to the loop - r = epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event); - if (r < 0) { - CTX_ERROR(jail->ctx, "Could not add file descriptor %d to epoll(): %s\n", - fd, strerror(errno)); - return -errno; - } - - return 0; -} - -// PTY Forwarding - -static int pakfire_jail_enable_raw_mode(struct pakfire_jail* jail, - struct pakfire_jail_pty_stdio* stdio) { - struct termios raw_attrs; - int r; - - // Skip if we don't know the file descriptor - if (stdio->fd < 0) - return 0; - - // Skip everything if fd is not a TTY - if (!isatty(stdio->fd)) - return 0; - - // Store flags - stdio->flags = fcntl(stdio->fd, F_GETFL); - if (stdio->flags < 0) { - CTX_ERROR(jail->ctx, "Could not fetch flags from fd %d: %s\n", - stdio->fd, strerror(errno)); - return -errno; - } - - // Fetch all attributes - r = tcgetattr(stdio->fd, &stdio->attrs); - if (r) { - CTX_ERROR(jail->ctx, "Could not fetch terminal attributes from fd %d: %s\n", - stdio->fd, strerror(errno)); - return -errno; - } - - // Copy all attributes - raw_attrs = stdio->attrs; - - // Make it RAW - cfmakeraw(&raw_attrs); - - switch (stdio->fd) { - case STDIN_FILENO: - raw_attrs.c_oflag = stdio->attrs.c_oflag; - break; - - case STDOUT_FILENO: - raw_attrs.c_iflag = stdio->attrs.c_iflag; - raw_attrs.c_lflag = stdio->attrs.c_lflag; - break; - } - - // Restore the attributes - r = tcsetattr(stdio->fd, TCSANOW, &raw_attrs); - if (r) { - CTX_ERROR(jail->ctx, "Could not restore terminal attributes for fd %d: %s\n", - stdio->fd, strerror(errno)); - return -errno; - } - - return 0; -} - -static int pakfire_jail_restore_attrs(struct pakfire_jail* jail, - const struct pakfire_jail_pty_stdio* stdio) { - int r; - - // Skip if we don't know the file descriptor - if (stdio->fd < 0) - return 0; - - // Skip everything if fd is not a TTY - if (!isatty(stdio->fd)) - return 0; - - // Restore the flags - r = fcntl(stdio->fd, F_SETFL, stdio->flags); - if (r < 0) { - CTX_ERROR(jail->ctx, "Could not set flags for file descriptor %d: %s\n", - stdio->fd, strerror(errno)); - return -errno; - } - - // Restore the attributes - r = tcsetattr(stdio->fd, TCSANOW, &stdio->attrs); - if (r) { - CTX_ERROR(jail->ctx, "Could not restore terminal attributes for %d, ignoring: %s\n", - stdio->fd, strerror(errno)); - return -errno; - } - - return 0; -} - -static int pakfire_jail_setup_pty_forwarding(struct pakfire_jail* jail, - struct pakfire_jail_exec* ctx, const int epollfd, const int fd) { - struct winsize size; - int r; - - CTX_DEBUG(jail->ctx, "Setting up PTY forwarding on fd %d\n", fd); - - // Store the file descriptor - ctx->pty.master.fd = fd; - - // Add the master to the event loop - r = pakfire_jail_epoll_add_fd(jail, epollfd, ctx->pty.master.fd, EPOLLIN|EPOLLOUT|EPOLLET); - if (r) - return r; - - if (ctx->flags & PAKFIRE_JAIL_PTY_FORWARDING) { - // Configure stdin/stdout - ctx->pty.stdin.fd = STDIN_FILENO; - ctx->pty.stdout.fd = STDOUT_FILENO; - - // Fetch dimensions - if (isatty(ctx->pty.stdout.fd)) { - r = ioctl(ctx->pty.stdout.fd, TIOCGWINSZ, &size); - if (r) { - CTX_ERROR(jail->ctx, "Failed to determine terminal dimensions: %s\n", strerror(errno)); - return -errno; - } - - // Set dimensions - r = ioctl(ctx->pty.master.fd, TIOCSWINSZ, &size); - if (r) { - CTX_ERROR(jail->ctx, "Failed setting dimensions: %s\n", strerror(errno)); - return -errno; - } - } - - // Enable RAW mode on standard input - r = pakfire_jail_enable_raw_mode(jail, &ctx->pty.stdin); - if (r) - return r; - - // Enable RAW mode on standard output - r = pakfire_jail_enable_raw_mode(jail, &ctx->pty.stdout); - if (r) - return r; - - // Add standard input to the event loop - r = pakfire_jail_epoll_add_fd(jail, epollfd, ctx->pty.stdin.fd, EPOLLIN|EPOLLET); - if (r) - return r; - - // Add standard output to the event loop - r = pakfire_jail_epoll_add_fd(jail, epollfd, ctx->pty.stdout.fd, EPOLLOUT|EPOLLET); - if (r) - return r; - } - - return 0; -} - static int pakfire_jail_command_output(struct pakfire_ctx* ctx, struct pakfire_jail* jail, void* data, const char* line, const size_t length) { CTX_INFO(ctx, "Command Output: %.*s", (int)length, line); @@ -1006,242 +606,6 @@ static int pakfire_jail_command_output(struct pakfire_ctx* ctx, struct pakfire_j return 0; } -static int pakfire_jail_forward_pty(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx) { - int r; - - while (ctx->pty.master.io || ctx->pty.stdin.io || ctx->pty.stdout.io) { - // Read from standard input - if (ctx->pty.stdin.io & PAKFIRE_JAIL_PTY_READY_TO_READ) { - r = pakfire_jail_fill_buffer(jail, ctx->pty.stdin.fd, &ctx->pty.stdin.buffer); - if (r) { - CTX_ERROR(jail->ctx, "Failed reading from standard input: %s\n", strerror(-r)); - return r; - } - - // We are done reading for now - ctx->pty.stdin.io &= ~PAKFIRE_JAIL_PTY_READY_TO_READ; - - // But we may have data to write - if (ctx->pty.stdin.buffer.used) - ctx->pty.master.io |= PAKFIRE_JAIL_PTY_READY_TO_WRITE; - } - - // Write to the master - if (ctx->pty.master.io & PAKFIRE_JAIL_PTY_READY_TO_WRITE) { - if (jail->callbacks.stdin.callback) { - r = pakfire_jail_stream_stdin(jail, ctx, ctx->pty.master.fd); - if (r) - return r; - - } else { - r = pakfire_jail_drain_buffer(jail, ctx->pty.master.fd, &ctx->pty.stdin.buffer); - if (r) { - CTX_ERROR(jail->ctx, "Failed writing to the PTY: %s\n", strerror(-r)); - return r; - } - } - - // We are done writing for now - ctx->pty.master.io &= ~PAKFIRE_JAIL_PTY_READY_TO_WRITE; - } - - // Read from the master - if (ctx->pty.master.io & PAKFIRE_JAIL_PTY_READY_TO_READ) { - r = pakfire_jail_fill_buffer(jail, ctx->pty.master.fd, &ctx->pty.stdout.buffer); - if (r) { - CTX_ERROR(jail->ctx, "Failed reading from the PTY: %s\n", strerror(-r)); - return r; - } - - // We are done reading for now - ctx->pty.master.io &= ~PAKFIRE_JAIL_PTY_READY_TO_READ; - - // But we may have data to write - if (ctx->pty.stdout.buffer.used) - ctx->pty.stdout.io |= PAKFIRE_JAIL_PTY_READY_TO_WRITE; - } - - // Write to standard output - if (ctx->pty.stdout.io & PAKFIRE_JAIL_PTY_READY_TO_WRITE) { - // If we have a callback, we will send any output to the callback - if (jail->callbacks.stdout.callback) { - r = pakfire_jail_drain_buffer_with_callback(jail, &ctx->pty.stdout.buffer, - jail->callbacks.stdout.callback, jail->callbacks.stdout.data); - if (r) - return r; - - // If we have a file descriptor, we will forward any output - } else if (ctx->pty.stdout.fd >= 0) { - r = pakfire_jail_drain_buffer(jail, ctx->pty.stdout.fd, &ctx->pty.stdout.buffer); - if (r) { - CTX_ERROR(jail->ctx, "Failed writing to standard output: %s\n", strerror(-r)); - return r; - } - - // Otherwise send the output to the default logger - } else { - r = pakfire_jail_drain_buffer_with_callback(jail, &ctx->pty.stdout.buffer, - pakfire_jail_command_output, NULL); - if (r) - return r; - } - - // We are done writing for now - ctx->pty.stdout.io &= ~PAKFIRE_JAIL_PTY_READY_TO_WRITE; - } - } - - return 0; -} - -static int pakfire_jail_wait(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx) { - int epollfd = -1; - struct epoll_event events[EPOLL_MAX_EVENTS]; - int r = 0; - - // Fetch the UNIX domain socket - const int socket_recv = pakfire_jail_get_pipe_to_read(jail, &ctx->socket); - - // Make a list of all file descriptors we are interested in - const struct pakfire_wait_fds { - const int fd; - const int events; - } fds[] = { - // UNIX Domain Socket - { socket_recv, EPOLLIN }, - - // Sentinel - { -1, 0 }, - }; - - // Setup epoll - epollfd = epoll_create1(0); - if (epollfd < 0) { - CTX_ERROR(jail->ctx, "Could not initialize epoll(): %m\n"); - r = 1; - goto ERROR; - } - - // Turn file descriptors into non-blocking mode and add them to epoll() - for (const struct pakfire_wait_fds* fd = fds; fd->events; fd++) { - // Skip fds which were not initialized - if (fd->fd < 0) - continue; - - // Add the FD to the event loop - r = pakfire_jail_epoll_add_fd(jail, epollfd, fd->fd, fd->events); - if (r) - goto ERROR; - } - - int ended = 0; - - // Loop for as long as the process is alive - while (!ended) { - int num = epoll_wait(epollfd, events, EPOLL_MAX_EVENTS, -1); - if (num < 1) { - // Ignore if epoll_wait() has been interrupted - if (errno == EINTR) - continue; - - CTX_ERROR(jail->ctx, "epoll_wait() failed: %m\n"); - r = 1; - - goto ERROR; - } - - for (int i = 0; i < num; i++) { - int e = events[i].events; - int fd = events[i].data.fd; - - // Handle PTY forwarding events - if (ctx->pty.master.fd == fd) { - if (e & (EPOLLIN|EPOLLHUP)) - ctx->pty.master.io |= PAKFIRE_JAIL_PTY_READY_TO_READ; - - if (e & (EPOLLOUT|EPOLLHUP)) - ctx->pty.master.io |= PAKFIRE_JAIL_PTY_READY_TO_WRITE; - - // Perform the work - r = pakfire_jail_forward_pty(jail, ctx); - if (r) { - CTX_ERROR(jail->ctx, "Failed forwarding the PTY: %s\n", strerror(-r)); - goto ERROR; - } - - // Handle standard input - } else if (ctx->pty.stdin.fd == fd) { - if (e & (EPOLLIN|EPOLLHUP)) - ctx->pty.stdin.io |= PAKFIRE_JAIL_PTY_READY_TO_READ; - - // Perform the work - r = pakfire_jail_forward_pty(jail, ctx); - if (r) { - CTX_ERROR(jail->ctx, "Failed forwarding the PTY: %s\n", strerror(-r)); - goto ERROR; - } - - // Handle standard output - } else if (ctx->pty.stdout.fd == fd) { - if (e & (EPOLLOUT|EPOLLHUP)) - ctx->pty.stdout.io |= PAKFIRE_JAIL_PTY_READY_TO_WRITE; - - // Perform the work - r = pakfire_jail_forward_pty(jail, ctx); - if (r) { - CTX_ERROR(jail->ctx, "Failed forwarding the PTY: %s\n", strerror(-r)); - goto ERROR; - } - - // Handle socket messages - } else if (socket_recv == fd) { - if (e & EPOLLIN) { - // Receive the passed FD - r = pakfire_jail_recv_fd(jail, socket_recv, &fd); - if (r) - goto ERROR; - - // Setup PTY forwarding - if (ctx->pty.master.fd < 0) { - r = pakfire_jail_setup_pty_forwarding(jail, ctx, epollfd, fd); - if (r) { - CTX_ERROR(jail->ctx, "Failed setting up PTY forwarding: %s\n", strerror(-r)); - goto ERROR; - } - } - } - - // Log a message for anything else - } else { - CTX_DEBUG(jail->ctx, "Received invalid file descriptor %d\n", fd); - continue; - } - - // Check if any file descriptors have been closed - if (e & EPOLLHUP) { - // Remove the file descriptor - r = epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, NULL); - if (r) { - CTX_ERROR(jail->ctx, "Could not remove closed file-descriptor %d: %m\n", fd); - goto ERROR; - } - } - } - } - -ERROR: - if (epollfd >= 0) - close(epollfd); - - // Restore any changed terminal attributes - if (ctx->pty.stdin.fd >= 0) - pakfire_jail_restore_attrs(jail, &ctx->pty.stdin); - if (ctx->pty.stdout.fd >= 0) - pakfire_jail_restore_attrs(jail, &ctx->pty.stdout); - - return r; -} - int pakfire_jail_capture_stdout(struct pakfire_ctx* ctx, struct pakfire_jail* jail, void* data, const char* line, size_t length) { char** output = (char**)data; @@ -1902,73 +1266,6 @@ static int pakfire_jail_switch_root(struct pakfire_jail* jail, const char* root) return 0; } -static int pakfire_jail_open_pty(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx) { - int r; - - // Allocate a new PTY - ctx->pty.master.fd = posix_openpt(O_RDWR|O_NONBLOCK|O_NOCTTY|O_CLOEXEC); - if (ctx->pty.master.fd < 0) - return -errno; - - // Fetch the path - r = ptsname_r(ctx->pty.master.fd, ctx->pty.console, sizeof(ctx->pty.console)); - if (r) - return -r; - - CTX_DEBUG(jail->ctx, "Allocated console at %s (%d)\n", ctx->pty.console, ctx->pty.master.fd); - - // Unlock the master device - r = unlockpt(ctx->pty.master.fd); - if (r) { - CTX_ERROR(jail->ctx, "Could not unlock the PTY: %s\n", strerror(errno)); - return -errno; - } - - // Create a symlink - r = pakfire_symlink(jail->ctx, ctx->pty.console, "/dev/console"); - if (r) - return r; - - return r; -} - -static int pakfire_jail_setup_terminal(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx) { - int fd; - int r; - - // Open a new terminal - fd = open("/dev/console", O_RDWR|O_NOCTTY); - if (fd < 0) { - CTX_ERROR(jail->ctx, "Failed to open a new terminal: %s\n", strerror(errno)); - return -errno; - } - - CTX_DEBUG(jail->ctx, "Opened a new terminal %d\n", fd); - - // Connect the new terminal to standard input - r = dup2(fd, STDIN_FILENO); - if (r < 0) { - CTX_ERROR(jail->ctx, "Failed to open standard input: %s\n", strerror(errno)); - return -errno; - } - - // Connect the new terminal to standard output - r = dup2(fd, STDOUT_FILENO); - if (r < 0) { - CTX_ERROR(jail->ctx, "Failed to open standard output: %s\n", strerror(errno)); - return -errno; - } - - // Connect the new terminal to standard error - r = dup2(fd, STDERR_FILENO); - if (r < 0) { - CTX_ERROR(jail->ctx, "Failed to open standard error: %s\n", strerror(errno)); - return -errno; - } - - return 0; -} - static int pakfire_jail_child(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx, const char* argv[]) { int r; @@ -2033,8 +1330,6 @@ static int pakfire_jail_child(struct pakfire_jail* jail, struct pakfire_jail_exe return 126; } - const int socket_send = pakfire_jail_get_pipe_to_write(jail, &ctx->socket); - // Mount all default stuff r = pakfire_mount_all(jail->pakfire, PAKFIRE_MNTNS_INNER, 0); if (r) @@ -2106,32 +1401,13 @@ static int pakfire_jail_child(struct pakfire_jail* jail, struct pakfire_jail_exe return r; } - // Allocate a new PTY - r = pakfire_jail_open_pty(jail, ctx); + // Open a new PTY + r = pakfire_pty_open(ctx->pty); if (r) { - CTX_ERROR(jail->ctx, "Could not allocate a new PTY: %s\n", strerror(-r)); + CTX_ERROR(jail->ctx, "Could not open a new PTY: %s\n", strerror(-r)); return r; } - // Send the PTY master to the parent process - r = pakfire_jail_send_fd(jail, socket_send, ctx->pty.master.fd); - if (r) { - CTX_ERROR(jail->ctx, "Failed sending the PTY master to the parent: %s\n", strerror(-r)); - return r; - } - - // Setup the terminal - r = pakfire_jail_setup_terminal(jail, ctx); - if (r) - return r; - - // Close the master of the PTY - close(ctx->pty.master.fd); - ctx->pty.master.fd = -1; - - // Close the socket - close(socket_send); - // Setup logging r = pakfire_log_stream_in_child(ctx->log.INFO); if (r) @@ -2201,6 +1477,7 @@ 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) { + int pty_flags = 0; int r; // Check if argv is valid @@ -2213,26 +1490,10 @@ PAKFIRE_EXPORT int pakfire_jail_exec(struct pakfire_jail* jail, const char* argv struct pakfire_jail_exec ctx = { .jail = jail, .flags = flags, - - // Exit Code - .exit = -1, - - .socket = { -1, -1 }, - .pidfd = -1, - // PTY - .pty = { - .master = { - .fd = -1, - }, - .stdin = { - .fd = -1, - }, - .stdout = { - .fd = -1, - }, - }, + // Exit Code + .exit = -1, }; CTX_DEBUG(jail->ctx, "Executing jail...\n"); @@ -2256,6 +1517,10 @@ PAKFIRE_EXPORT int pakfire_jail_exec(struct pakfire_jail* jail, const char* argv if (ctx.flags & PAKFIRE_JAIL_PTY_FORWARDING) ctx.flags |= PAKFIRE_JAIL_HAS_NETWORKING; + // Enable PTY forwarding + if (ctx.flags & PAKFIRE_JAIL_PTY_FORWARDING) + pty_flags |= PAKFIRE_PTY_FORWARD; + /* Setup a file descriptor which can be used to notify the client that the parent has completed configuration. @@ -2266,13 +1531,10 @@ PAKFIRE_EXPORT int pakfire_jail_exec(struct pakfire_jail* jail, const char* argv return -1; } - // Create a UNIX domain socket - r = socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, ctx.socket); - if (r < 0) { - CTX_ERROR(jail->ctx, "Could not create UNIX socket: %s\n", strerror(errno)); - r = -errno; + // Setup the PTY + r = pakfire_pty_create(&ctx.pty, jail->ctx, ctx.loop, pty_flags); + if (r) goto ERROR; - } // Setup pipes for logging // INFO @@ -2383,8 +1645,10 @@ ERROR: // Close any file descriptors if (ctx.pidfd >= 0) close(ctx.pidfd); - if (ctx.pty.master.fd >= 0) - close(ctx.pty.master.fd); + + // PTY + if (ctx.pty) + pakfire_pty_unref(ctx.pty); // Logging if (ctx.log.INFO) @@ -2395,7 +1659,6 @@ ERROR: if (ctx.log.DEBUG) pakfire_log_stream_unref(ctx.log.DEBUG); #endif /* ENABLE_DEBUG */ - pakfire_jail_close_pipe(jail, ctx.socket); if (ctx.loop) sd_event_unref(ctx.loop); diff --git a/src/libpakfire/pty.c b/src/libpakfire/pty.c new file mode 100644 index 000000000..098b45cd1 --- /dev/null +++ b/src/libpakfire/pty.c @@ -0,0 +1,673 @@ +/*############################################################################# +# # +# Pakfire - The IPFire package management system # +# Copyright (C) 2024 Pakfire development team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +# # +#############################################################################*/ + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +struct pakfire_pty_stdio { + // File Descriptor + int fd; + + // Buffer + char buffer[64 * 1024]; + size_t buffered; + + // Terminal Attributes + struct termios attrs; + + // File Descriptor Flags + int flags; + + // IO Flags + enum pakfire_pty_io { + PAKFIRE_PTY_READY_TO_READ = (1 << 0), + PAKFIRE_PTY_READY_TO_WRITE = (1 << 1), + } io; +}; + +struct pakfire_pty { + struct pakfire_ctx* ctx; + int nrefs; + + // Flags + int flags; + + // Event Loop + sd_event* loop; + + // A UNIX socket to pass the file descriptor + int socket[2]; + + // The master PTY + struct pakfire_pty_stdio master; + + // The path to the PTY + char path[PATH_MAX]; + + // Standard Input + struct pakfire_pty_stdio stdin; + struct pakfire_pty_stdio stdout; +}; + +/* + Reads as much data as possible into the buffer +*/ +static int pakfire_pty_fill_buffer(struct pakfire_pty* pty, struct pakfire_pty_stdio* stdio) { + int r; + + for (;;) { + // Skip this if there is not space left in the buffer + if (stdio->buffered >= sizeof(stdio->buffer)) + return 0; + + // Fill the buffer + r = read(stdio->fd, stdio->buffer + stdio->buffered, sizeof(stdio->buffer) - stdio->buffered); + if (r < 0) { + switch (errno) { + case EAGAIN: + case EIO: + return 0; + + default: + return -errno; + } + + // EOF + } else if (r == 0) { + return 0; + + // Successful read + } else { + stdio->buffered += r; + } + } +} + +/* + Writes as much data as possible from the buffer +*/ +static int pakfire_pty_drain_buffer(struct pakfire_pty* pty, struct pakfire_pty_stdio* stdio) { + int r; + + // Do not try to write to an invalid file descriptor + if (stdio->fd < 0) + return 0; + + for (;;) { + // Nothing to do if the buffer is empty + if (!stdio->buffered) + return 0; + + // Drain the buffer + r = write(stdio->fd, stdio->buffer, stdio->buffered); + if (r < 0) { + switch (errno) { + case EAGAIN: + case EIO: + return 0; + + default: + return -errno; + } + + // Successful write + } else { + memmove(stdio->buffer, stdio->buffer + r, stdio->buffered - r); + + stdio->buffered -= r; + } + } + + return 0; +} + +/* + This function handles the forwarding between the different pipes... +*/ +static int pakfire_pty_forward(struct pakfire_pty* pty) { + int r; + + while (pty->master.io || pty->stdin.io || pty->stdout.io) { + // Read from standard input + if (pty->stdin.io & PAKFIRE_PTY_READY_TO_READ) { + r = pakfire_pty_fill_buffer(pty, &pty->stdin); + if (r < 0) { + CTX_ERROR(pty->ctx, "Failed reading from standard input: %s\n", strerror(-r)); + return r; + } + + // We are done reading for now + pty->stdin.io &= ~PAKFIRE_PTY_READY_TO_READ; + + // But we may have data to write + if (pty->stdin.buffered) + pty->master.io |= PAKFIRE_PTY_READY_TO_WRITE; + } + + // Write to the master + if (pty->master.io & PAKFIRE_PTY_READY_TO_WRITE) { + r = pakfire_pty_drain_buffer(pty, &pty->master); + if (r) { + CTX_ERROR(pty->ctx, "Failed writing to the PTY: %s\n", strerror(-r)); + return r; + } + + // We are done writing for now + pty->master.io &= ~PAKFIRE_PTY_READY_TO_WRITE; + } + + // Read from the master + if (pty->master.io & PAKFIRE_PTY_READY_TO_READ) { + r = pakfire_pty_fill_buffer(pty, &pty->master); + if (r) { + CTX_ERROR(pty->ctx, "Failed reading from the PTY: %s\n", strerror(-r)); + return r; + } + + // We are done reading for now + pty->master.io &= ~PAKFIRE_PTY_READY_TO_READ; + + // But we may have data to write + if (pty->stdout.buffered) + pty->stdout.io |= PAKFIRE_PTY_READY_TO_WRITE; + } + + // Write to standard output + if (pty->stdout.io & PAKFIRE_PTY_READY_TO_WRITE) { + r = pakfire_pty_drain_buffer(pty, &pty->stdout); + if (r) { + CTX_ERROR(pty->ctx, "Failed writing to standard output: %s\n", strerror(-r)); + return r; + } + + // We are done writing for now + pty->stdout.io &= ~PAKFIRE_PTY_READY_TO_WRITE; + } + } + + return 0; +} + +static int pakfire_pty_restore_attrs(struct pakfire_pty* pty, + const struct pakfire_pty_stdio* stdio) { + int r; + + // Skip if we don't know the file descriptor + if (stdio->fd < 0) + return 0; + + // Skip everything if fd is not a TTY + if (!isatty(stdio->fd)) + return 0; + + // Restore the flags + r = fcntl(stdio->fd, F_SETFL, stdio->flags); + if (r < 0) { + CTX_ERROR(pty->ctx, "Could not set flags for file descriptor %d: %s\n", + stdio->fd, strerror(errno)); + return -errno; + } + + // Restore the attributes + r = tcsetattr(stdio->fd, TCSANOW, &stdio->attrs); + if (r) { + CTX_ERROR(pty->ctx, "Could not restore terminal attributes for %d, ignoring: %s\n", + stdio->fd, strerror(errno)); + return -errno; + } + + return 0; +} + +static int pakfire_pty_activity(struct pakfire_pty* pty, struct pakfire_pty_stdio* stdio, uint32_t events) { + // Do we have data to read? + if (events & (EPOLLIN|EPOLLHUP)) + stdio->io |= PAKFIRE_PTY_READY_TO_READ; + + // Do we have data to write? + if (events & (EPOLLOUT|EPOLLHUP)) + stdio->io |= PAKFIRE_PTY_READY_TO_WRITE; + + return pakfire_pty_forward(pty); +} + +static int pakfire_pty_master(sd_event_source* source, int fd, uint32_t events, void* data) { + struct pakfire_pty* pty = data; + + return pakfire_pty_activity(pty, &pty->master, events); +} + +static int pakfire_pty_stdin(sd_event_source* source, int fd, uint32_t events, void* data) { + struct pakfire_pty* pty = data; + + return pakfire_pty_activity(pty, &pty->stdin, events); +} + +static int pakfire_pty_stdout(sd_event_source* source, int fd, uint32_t events, void* data) { + struct pakfire_pty* pty = data; + + return pakfire_pty_activity(pty, &pty->stdout, events); +} + +static int pakfire_pty_enable_raw_mode(struct pakfire_pty* pty, struct pakfire_pty_stdio* stdio) { + struct termios raw_attrs; + int r; + + // Skip if we don't know the file descriptor + if (stdio->fd < 0) + return 0; + + // Skip everything if fd is not a TTY + if (!isatty(stdio->fd)) + return 0; + + // Store flags + stdio->flags = fcntl(stdio->fd, F_GETFL); + if (stdio->flags < 0) { + CTX_ERROR(pty->ctx, "Could not fetch flags from fd %d: %s\n", + stdio->fd, strerror(errno)); + return -errno; + } + + // Fetch all attributes + r = tcgetattr(stdio->fd, &stdio->attrs); + if (r) { + CTX_ERROR(pty->ctx, "Could not fetch terminal attributes from fd %d: %s\n", + stdio->fd, strerror(errno)); + return -errno; + } + + // Copy all attributes + raw_attrs = stdio->attrs; + + // Make it RAW + cfmakeraw(&raw_attrs); + + switch (stdio->fd) { + case STDIN_FILENO: + raw_attrs.c_oflag = stdio->attrs.c_oflag; + break; + + case STDOUT_FILENO: + raw_attrs.c_iflag = stdio->attrs.c_iflag; + raw_attrs.c_lflag = stdio->attrs.c_lflag; + break; + } + + // Restore the attributes + r = tcsetattr(stdio->fd, TCSANOW, &raw_attrs); + if (r) { + CTX_ERROR(pty->ctx, "Could not restore terminal attributes for fd %d: %s\n", + stdio->fd, strerror(errno)); + return -errno; + } + + return 0; +} + +/* + Sets up PTY forwarding... +*/ +static int pakfire_pty_setup_forwarding(struct pakfire_pty* pty) { + struct winsize size; + int r; + + CTX_DEBUG(pty->ctx, "Setting up PTY Forwarding...\n"); + + // Connect to standard input/output + pty->stdin.fd = STDIN_FILENO; + pty->stdout.fd = STDOUT_FILENO; + + // Copy the terminal dimensions to the PTY + if (isatty(pty->stdout.fd)) { + // Fetch dimensions + r = ioctl(pty->stdout.fd, TIOCGWINSZ, &size); + if (r) { + CTX_ERROR(pty->ctx, "Failed to determine terminal dimensions: %s\n", strerror(errno)); + return -errno; + } + + // Set dimensions + r = ioctl(pty->master.fd, TIOCSWINSZ, &size); + if (r) { + CTX_ERROR(pty->ctx, "Failed setting dimensions: %s\n", strerror(errno)); + return -errno; + } + } + + // Enable RAW mode on standard input + r = pakfire_pty_enable_raw_mode(pty, &pty->stdin); + if (r) + return r; + + // Enable RAW mode on standard output + r = pakfire_pty_enable_raw_mode(pty, &pty->stdout); + if (r) + return r; + + // Add standard input to the event loop + r = sd_event_add_io(pty->loop, NULL, + pty->stdin.fd, EPOLLIN|EPOLLET, pakfire_pty_stdin, pty); + if (r) + return r; + + // Add standard output to the event loop + r = sd_event_add_io(pty->loop, NULL, + pty->stdout.fd, EPOLLOUT|EPOLLET, pakfire_pty_stdout, pty); + if (r) + return r; + + return 0; +} + +/* + Sends the master file descriptor to the parent process... +*/ +static int pakfire_pty_send_master(struct pakfire_pty* pty) { + const size_t payload_length = sizeof(pty->master.fd); + char buffer[CMSG_SPACE(payload_length)]; + int r; + + CTX_DEBUG(pty->ctx, "Sending fd %d to parent\n", pty->master.fd); + + // Header + struct msghdr msg = { + .msg_control = buffer, + .msg_controllen = sizeof(buffer), + }; + + // Payload + struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(payload_length); + + // Set payload + *((int*)CMSG_DATA(cmsg)) = pty->master.fd; + + // Send the message + r = sendmsg(pty->socket[1], &msg, 0); + if (r) { + CTX_ERROR(pty->ctx, "Could not send file descriptor: %s\n", strerror(errno)); + return -errno; + } + + // We can close the socket now + close(pty->socket[1]); + pty->socket[1] = -1; + + return 0; +} + +/* + Received the master file descriptor from the child process... +*/ +static int pakfire_pty_recv_master(struct pakfire_pty* pty) { + const size_t payload_length = sizeof(pty->master.fd); + char buffer[CMSG_SPACE(payload_length)]; + int r; + + struct msghdr msg = { + .msg_control = buffer, + .msg_controllen = sizeof(buffer), + }; + + // Receive the message + r = recvmsg(pty->socket[0], &msg, 0); + if (r) { + CTX_ERROR(pty->ctx, "Could not receive file descriptor: %s\n", strerror(errno)); + return -errno; + } + + // Fetch the payload + struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); + if (!cmsg) + return -EBADMSG; + + // Store the file descriptor + pty->master.fd = *((int*)CMSG_DATA(cmsg)); + + CTX_DEBUG(pty->ctx, "Received fd %d from socket %d\n", pty->master.fd, pty->socket[0]); + + // We can close the socket now + close(pty->socket[0]); + pty->socket[0] = -1; + + return 0; +} + +/* + Called when the master socket is being received from the child process. +*/ +static int pakfire_pty_setup(sd_event_source* source, int fd, uint32_t events, void* data) { + struct pakfire_pty* pty = data; + int r; + + // Receive the master file descriptor + r = pakfire_pty_recv_master(pty); + if (r) + return r; + + // Add the master file descriptor to the event loop + r = sd_event_add_io(pty->loop, NULL, pty->master.fd, + EPOLLIN|EPOLLOUT|EPOLLET, pakfire_pty_master, pty); + if (r < 0) { + CTX_ERROR(pty->ctx, "Could not add the master file descriptor: %s\n", strerror(-r)); + return r; + } + + // Do we need to set up PTY forwarding? + if (pty->flags & PAKFIRE_PTY_FORWARD) { + r = pakfire_pty_setup_forwarding(pty); + if (r) + return r; + } + + return r; +} + +static void pakfire_pty_free(struct pakfire_pty* pty) { + // Restore any changed terminal attributes + if (pty->stdin.fd >= 0) + pakfire_pty_restore_attrs(pty, &pty->stdin); + if (pty->stdout.fd >= 0) + pakfire_pty_restore_attrs(pty, &pty->stdout); + + if (pty->socket[0] >= 0) + close(pty->socket[0]); + if (pty->socket[1] >= 0) + close(pty->socket[1]); + if (pty->master.fd >= 0) + close(pty->master.fd); + if (pty->loop) + sd_event_unref(pty->loop); + if (pty->ctx) + pakfire_ctx_unref(pty->ctx); + free(pty); +} + +int pakfire_pty_create(struct pakfire_pty** pty, struct pakfire_ctx* ctx, + sd_event* loop, int flags) { + struct pakfire_pty* p = NULL; + int r; + + // Allocate a new object + p = calloc(1, sizeof(*p)); + if (!p) + return -errno; + + // Initialize the reference counter + p->nrefs = 1; + + // Store a reference to the context + p->ctx = pakfire_ctx_ref(ctx); + + // Store a reference to the event loop + p->loop = sd_event_ref(loop); + + // Store the flags + p->flags = flags; + + // Initialize the master file descriptor + p->master.fd = -1; + + // Initialize standard input/output + p->stdin.fd = -1; + p->stdout.fd = -1; + + // Create a UNIX domain socket + r = socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, p->socket); + if (r < 0) { + CTX_ERROR(p->ctx, "Could not create a UNIX socket: %m\n"); + r = -errno; + goto ERROR; + } + + // Register the socket to receive the master socket + r = sd_event_add_io(p->loop, NULL, p->socket[0], EPOLLIN|EPOLLHUP, + pakfire_pty_setup, p); + if (r < 0) { + CTX_ERROR(p->ctx, "Could not listen to socket: %s\n", strerror(-r)); + r = -errno; + goto ERROR; + } + + // Return the pointer + *pty = p; + + return 0; + +ERROR: + if (p) + pakfire_pty_free(p); + + return r; +} + +struct pakfire_pty* pakfire_pty_ref(struct pakfire_pty* pty) { + ++pty->nrefs; + + return pty; +} + +struct pakfire_pty* pakfire_pty_unref(struct pakfire_pty* pty) { + if (--pty->nrefs > 0) + return pty; + + pakfire_pty_free(pty); + return NULL; +} + +/* + Sets up the terminal in the child process... +*/ +static int pakfire_pty_setup_terminal(struct pakfire_pty* pty) { + int fd = -1; + int r; + + // Open a new terminal + fd = open("/dev/console", O_RDWR|O_NOCTTY); + if (fd < 0) { + CTX_ERROR(pty->ctx, "Failed to open a new terminal: %s\n", strerror(errno)); + return -errno; + } + + CTX_DEBUG(pty->ctx, "Opened a new terminal %d\n", fd); + + // Connect the new terminal to standard input + r = dup2(fd, STDIN_FILENO); + if (r < 0) { + CTX_ERROR(pty->ctx, "Failed to open standard input: %s\n", strerror(errno)); + return -errno; + } + + // Connect the new terminal to standard output + r = dup2(fd, STDOUT_FILENO); + if (r < 0) { + CTX_ERROR(pty->ctx, "Failed to open standard output: %s\n", strerror(errno)); + return -errno; + } + + // Connect the new terminal to standard error + r = dup2(fd, STDERR_FILENO); + if (r < 0) { + CTX_ERROR(pty->ctx, "Failed to open standard error: %s\n", strerror(errno)); + return -errno; + } + + return 0; +} + +/* + Allocates a new PTY and must be called from the child process... +*/ +int pakfire_pty_open(struct pakfire_pty* pty) { + int r; + + // Allocate a new PTY + pty->master.fd = posix_openpt(O_RDWR|O_NONBLOCK|O_NOCTTY|O_CLOEXEC); + if (pty->master.fd < 0) + return -errno; + + // Fetch the path + r = ptsname_r(pty->master.fd, pty->path, sizeof(pty->path)); + if (r) + return -r; + + CTX_DEBUG(pty->ctx, "Allocated console at %s (%d)\n", pty->path, pty->master.fd); + + // Unlock the master device + r = unlockpt(pty->master.fd); + if (r) { + CTX_ERROR(pty->ctx, "Could not unlock the PTY: %s\n", strerror(errno)); + return -errno; + } + + // Create a symlink + r = pakfire_symlink(pty->ctx, pty->path, "/dev/console"); + if (r) + return r; + + // Send the master to the parent process + r = pakfire_pty_send_master(pty); + if (r) + return r; + + // Setup the terminal + r = pakfire_pty_setup_terminal(pty); + if (r) + return r; + + // We are done with the master and close it now + close(pty->master.fd); + pty->master.fd = -1; + + return 0; +} -- 2.47.2