]> git.ipfire.org Git - pakfire.git/commitdiff
jail: Move the PTY into an extra file
authorMichael Tremer <michael.tremer@ipfire.org>
Sun, 6 Oct 2024 12:14:12 +0000 (12:14 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Sun, 6 Oct 2024 12:14:12 +0000 (12:14 +0000)
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 <michael.tremer@ipfire.org>
Makefile.am
src/libpakfire/include/pakfire/pty.h [new file with mode: 0644]
src/libpakfire/jail.c
src/libpakfire/pty.c [new file with mode: 0644]

index 9c42056817aea16f55c7ad215d0f7820c81ac191..c4d54570fcbc1e4cc0e84d2abfb9791a77d8f100 100644 (file)
@@ -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 (file)
index 0000000..f3f3942
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+#############################################################################*/
+
+#ifndef PAKFIRE_PTY_H
+#define PAKFIRE_PTY_H
+
+#ifdef PAKFIRE_PRIVATE
+
+#include <systemd/sd-event.h>
+
+#include <pakfire/ctx.h>
+
+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 */
index 7be231bda30b89ee423f119a0692dd34f3dcf57f..99838e12c7670da3ddcae215258887eb50e25664 100644 (file)
 #include <fcntl.h>
 #include <linux/capability.h>
 #include <linux/sched.h>
-#include <sys/wait.h>
-#include <linux/wait.h>
 #include <sched.h>
 #include <signal.h>
 #include <stdlib.h>
 #include <syscall.h>
 #include <sys/capability.h>
-#include <sys/epoll.h>
 #include <sys/eventfd.h>
 #include <sys/mount.h>
 #include <sys/personality.h>
 #include <sys/prctl.h>
 #include <sys/resource.h>
 #include <sys/types.h>
-#include <sys/wait.h>
-#include <termios.h>
 
 // libnl3
 #include <net/if.h>
 #include <pakfire/pakfire.h>
 #include <pakfire/path.h>
 #include <pakfire/private.h>
+#include <pakfire/pty.h>
 #include <pakfire/pwd.h>
 #include <pakfire/string.h>
 #include <pakfire/util.h>
 
 #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 (file)
index 0000000..098b45c
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+#############################################################################*/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include <systemd/sd-event.h>
+
+#include <pakfire/ctx.h>
+#include <pakfire/pty.h>
+#include <pakfire/util.h>
+
+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;
+}