]> git.ipfire.org Git - pakfire.git/blobdiff - src/libpakfire/jail.c
jail: Refactor how to drain logging buffers with callbacks
[pakfire.git] / src / libpakfire / jail.c
index d67686cd7f718fe4b539e9b5da387b3ed30136c8..29b0198a8a09c0bfc8846b1a2699d3f03c13709d 100644 (file)
@@ -38,6 +38,7 @@
 #include <sys/timerfd.h>
 #include <sys/types.h>
 #include <sys/wait.h>
+#include <termios.h>
 
 // libnl3
 #include <net/if.h>
@@ -54,7 +55,6 @@
 #include <pakfire/jail.h>
 #include <pakfire/logging.h>
 #include <pakfire/mount.h>
-#include <pakfire/os.h>
 #include <pakfire/pakfire.h>
 #include <pakfire/path.h>
 #include <pakfire/private.h>
@@ -129,22 +129,21 @@ struct pakfire_log_buffer {
 struct pakfire_jail_exec {
        int flags;
 
-       // PIDs (of the children)
-       int pidfd1;
-       int pidfd2;
+       // PID (of the child)
+       pid_t pid;
+       int pidfd;
 
        // Socket to pass FDs
        int socket[2];
 
+       // Process status (from waitid)
+       siginfo_t status;
+
        // FD to notify the client that the parent has finished initialization
        int completed_fd;
 
        // Log pipes
        struct pakfire_jail_pipes {
-               int stdin[2];
-               int stdout[2];
-               int stderr[2];
-
                // Logging
                int log_INFO[2];
                int log_ERROR[2];
@@ -175,6 +174,34 @@ 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 fd
+               struct pakfire_jail_pty_master {
+                       int fd;
+
+                       enum pakfire_jail_pty_flags {
+                               PAKFIRE_JAIL_PTY_READY_TO_READ  = (1 << 0),
+                               PAKFIRE_JAIL_PTY_READY_TO_WRITE = (1 << 1),
+                       } flags;
+               } master;
+
+               // Standard Input
+               struct pakfire_jail_pty_stdio {
+                       int fd;
+                       struct pakfire_log_buffer buffer;
+                       struct termios attrs;
+                       int fdflags;
+                       enum pakfire_jail_pty_flags flags;
+               } stdin;
+
+               // Standard Output
+               struct pakfire_jail_pty_stdio stdout;
+       } pty;
 };
 
 static int clone3(struct clone_args* args, size_t size) {
@@ -568,66 +595,70 @@ static int pakfire_jail_log_buffer_is_full(const struct pakfire_log_buffer* buff
        return (sizeof(buffer->data) == buffer->used);
 }
 
-/*
-       This function reads as much data as it can from the file descriptor.
-       If it finds a whole line in it, it will send it to the logger and repeat the process.
-       If not newline character is found, it will try to read more data until it finds one.
-*/
-static int pakfire_jail_handle_log(struct pakfire_jail* jail,
-               struct pakfire_jail_exec* ctx, int priority, int fd,
-               struct pakfire_log_buffer* buffer, pakfire_jail_communicate_out callback, void* data) {
-       char line[BUFFER_SIZE + 1];
+static int pakfire_jail_fill_buffer(struct pakfire_jail* jail, int fd, struct pakfire_log_buffer* buffer) {
+       int r;
 
-       // Fill up buffer from fd
-       if (buffer->used < sizeof(buffer->data)) {
-               ssize_t bytes_read = read(fd, buffer->data + buffer->used,
-                               sizeof(buffer->data) - buffer->used);
-
-               // Handle errors
-               if (bytes_read < 0) {
-                       ERROR(jail->pakfire, "Could not read from fd %d: %m\n", fd);
-                       return -1;
+       // 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;
                }
 
-               // Update buffer size
-               buffer->used += bytes_read;
+       // EOF
+       } else if (r == 0) {
+               // XXX What to do here?
+
+       // Successful read
+       } else {
+               buffer->used += r;
        }
 
-       // See if we have any lines that we can write
+       return 0;
+}
+
+static int pakfire_jail_drain_buffer_with_callback(struct pakfire_jail* jail,
+               struct pakfire_log_buffer* buffer, int priority, pakfire_jail_communicate_out callback, void* data) {
+       const char* eol = NULL;
+       int r;
+
        while (buffer->used) {
                // Search for the end of the first line
-               char* eol = memchr(buffer->data, '\n', buffer->used);
+               eol = memchr(buffer->data, '\n', buffer->used);
 
                // No newline found
                if (!eol) {
-                       // If the buffer is full, we send the content to the logger and try again
-                       // This should not happen in practise
+                       // If the buffer is full, we send the entire content to make space.
                        if (pakfire_jail_log_buffer_is_full(buffer)) {
-                               DEBUG(jail->pakfire, "Logging buffer is full. Sending all content\n");
+                               CTX_DEBUG(jail->ctx, "Buffer is full. Sending all content\n");
 
-                               eol = buffer->data + sizeof(buffer->data) - 1;
+                               eol = buffer->data + buffer->used - 1;
 
-                       // Otherwise we might have only read parts of the output
-                       } else
+                       // Otherwise we might have only read parts of the output...
+                       } else {
                                break;
+                       }
                }
 
                // Find the length of the string
-               size_t length = eol - buffer->data + 1;
-
-               // Copy the line into the buffer
-               memcpy(line, buffer->data, length);
-
-               // Terminate the string
-               line[length] = '\0';
+               const size_t length = eol - buffer->data + 1;
 
-               // Log the line
-               if (callback) {
-                       int r = callback(jail->pakfire, data, priority, line, length);
-                       if (r) {
-                               ERROR(jail->pakfire, "The logging callback returned an error: %d\n", r);
-                               return r;
-                       }
+               // Call the callback
+               r = callback(jail->pakfire, data, priority, buffer->data, length);
+               if (r) {
+                       CTX_ERROR(jail->ctx, "The logging callback returned an error: %d\n", r);
+                       return r;
                }
 
                // Remove line from buffer
@@ -638,6 +669,61 @@ static int pakfire_jail_handle_log(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;
+
+       // 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;
+}
+
+/*
+       This function reads as much data as it can from the file descriptor.
+       If it finds a whole line in it, it will send it to the logger and repeat the process.
+       If not newline character is found, it will try to read more data until it finds one.
+*/
+static int pakfire_jail_handle_log(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx,
+               int priority, int fd, struct pakfire_log_buffer* buffer,
+               pakfire_jail_communicate_out callback, void* data) {
+       int r;
+
+       // Fill up buffer from fd
+       r = pakfire_jail_fill_buffer(jail, fd, buffer);
+       if (r)
+               return r;
+
+       // Drain the buffer
+       r = pakfire_jail_drain_buffer_with_callback(jail, buffer, priority, callback, data);
+       if (r)
+               return r;
+
+       return 0;
+}
+
+#if 0
 static int pakfire_jail_stream_stdin(struct pakfire_jail* jail,
                struct pakfire_jail_exec* ctx, const int fd) {
        int r;
@@ -649,7 +735,7 @@ static int pakfire_jail_stream_stdin(struct pakfire_jail* jail,
        }
 
        // Skip if the writing pipe has already been closed
-       if (!ctx->pipes.stdin[1])
+       if (ctx->pipes.stdin[1] < 0)
                return 0;
 
        DEBUG(jail->pakfire, "Streaming standard input...\n");
@@ -675,62 +761,7 @@ static int pakfire_jail_stream_stdin(struct pakfire_jail* jail,
 
        return r;
 }
-
-static int pakfire_jail_setup_pipe(struct pakfire_jail* jail, int (*fds)[2], const int flags) {
-       int r = pipe2(*fds, flags);
-       if (r < 0) {
-               ERROR(jail->pakfire, "Could not setup pipe: %m\n");
-               return 1;
-       }
-
-       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;
-}
+#endif
 
 static int pakfire_jail_recv_fd(struct pakfire_jail* jail, int socket, int* fd) {
        const size_t payload_length = sizeof(fd);
@@ -793,6 +824,62 @@ static int pakfire_jail_send_fd(struct pakfire_jail* jail, int socket, int fd) {
        return 0;
 }
 
+static int pakfire_jail_setup_pipe(struct pakfire_jail* jail, int (*fds)[2], const int flags) {
+       int r = pipe2(*fds, flags);
+       if (r < 0) {
+               ERROR(jail->pakfire, "Could not setup pipe: %m\n");
+               return 1;
+       }
+
+       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_log(struct pakfire* pakfire, void* data, int priority,
                const char* line, const size_t length) {
        // Pass everything to the parent logger
@@ -832,105 +919,255 @@ static int pakfire_jail_epoll_add_fd(struct pakfire_jail* jail, int epollfd, int
        return 0;
 }
 
-static int pakfire_jail_setup_child2(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx);
+// PTY Forwarding
 
-static int pakfire_jail_wait_on_child(struct pakfire_jail* jail, int pidfd) {
-       siginfo_t status = {};
+static int pakfire_jail_enable_raw_mode(struct pakfire_jail* jail,
+               struct pakfire_jail_pty_stdio* stdio) {
+       struct termios raw_attrs;
        int r;
 
-       // Call waitid() and store the result
-       r = waitid(P_PIDFD, pidfd, &status, WEXITED);
+       // Store flags
+       stdio->fdflags = fcntl(stdio->fd, F_GETFL);
+       if (stdio->fdflags < 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, "waitid() failed: %s\n", strerror(errno));
+               CTX_ERROR(jail->ctx, "Could not fetch terminal attributes from fd %d: %s\n",
+                       stdio->fd, strerror(errno));
                return -errno;
        }
 
-       switch (status.si_code) {
-               // If the process exited normally, we return the exit code
-               case CLD_EXITED:
-                       CTX_DEBUG(jail->ctx, "The child process exited with code %d\n", status.si_status);
-                       return status.si_status;
+       // Copy all attributes
+       raw_attrs = stdio->attrs;
 
-               case CLD_KILLED:
-                       CTX_ERROR(jail->ctx, "The child process was killed\n");
-                       return 139;
+       // Make it RAW
+       cfmakeraw(&raw_attrs);
 
-               case CLD_DUMPED:
-                       CTX_ERROR(jail->ctx, "The child process terminated abnormally\n");
-                       return 139;
+       switch (stdio->fd) {
+               case STDIN_FILENO:
+                       raw_attrs.c_oflag = stdio->attrs.c_oflag;
+                       break;
 
-               // Log anything else
-               default:
-                       CTX_ERROR(jail->ctx, "Unknown child exit code: %d\n", status.si_code);
+               case STDOUT_FILENO:
+                       raw_attrs.c_iflag = stdio->attrs.c_iflag;
+                       raw_attrs.c_lflag = stdio->attrs.c_lflag;
                        break;
        }
 
-       return -EBADMSG;
+       // 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_wait(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx) {
-       int epollfd = -1;
-       struct epoll_event events[EPOLL_MAX_EVENTS];
-       char garbage[8];
-       int r = 0;
+static int pakfire_jail_restore_attrs(struct pakfire_jail* jail,
+               const struct pakfire_jail_pty_stdio* stdio) {
+       int r;
 
-       // Fetch the UNIX domain socket
-       const int socket_recv = pakfire_jail_get_pipe_to_read(jail, &ctx->socket);
+       // Restore the flags
+       r = fcntl(stdio->fd, F_SETFL, stdio->fdflags);
+       if (r < 0) {
+               CTX_ERROR(jail->ctx, "Could not set flags for file descriptor %d: %s\n",
+                       stdio->fd, strerror(errno));
+               return -errno;
+       }
 
-       // Fetch file descriptors from context
-       const int stdin = pakfire_jail_get_pipe_to_write(jail, &ctx->pipes.stdin);
-       const int stdout = pakfire_jail_get_pipe_to_read(jail, &ctx->pipes.stdout);
-       const int stderr = pakfire_jail_get_pipe_to_read(jail, &ctx->pipes.stderr);
+       // 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;
+       }
 
-       // Timer
-       const int timerfd = pakfire_jail_create_timer(jail);
+       return 0;
+}
 
-       // Logging
-       const int log_INFO  = pakfire_jail_get_pipe_to_read(jail, &ctx->pipes.log_INFO);
-       const int log_ERROR = pakfire_jail_get_pipe_to_read(jail, &ctx->pipes.log_ERROR);
-#ifdef ENABLE_DEBUG
-       const int log_DEBUG = pakfire_jail_get_pipe_to_read(jail, &ctx->pipes.log_DEBUG);
-#endif /* ENABLE_DEBUG */
+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;
 
-       // Make a list of all file descriptors we are interested in
-       const struct pakfire_wait_fds {
-               const int fd;
-               const int events;
-       } fds[] = {
-               { socket_recv, EPOLLIN },
+       CTX_DEBUG(jail->ctx, "Setting up PTY forwarding on fd %d\n", fd);
 
-               // Standard input/output
-               { stdin,  EPOLLOUT },
-               { stdout, EPOLLIN },
-               { stderr, EPOLLIN },
+       // Store the file descriptor
+       ctx->pty.master.fd = fd;
 
-               // Timer
-               { timerfd, EPOLLIN },
+       // Configure stdin/stdout
+       ctx->pty.stdin.fd  = STDIN_FILENO;
+       ctx->pty.stdout.fd = STDOUT_FILENO;
 
-               // Child Processes
-               { ctx->pidfd1, EPOLLIN },
+       // Fetch dimensions
+       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;
+       }
 
-               // Log Pipes
-               { log_INFO, EPOLLIN },
-               { log_ERROR, EPOLLIN },
-#ifdef ENABLE_DEBUG
-               { log_DEBUG, EPOLLIN },
-#endif /* ENABLE_DEBUG */
+       // 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;
+       }
 
-               // Sentinel
-               { -1, 0 },
-       };
+       // Enable RAW mode on standard input
+       r = pakfire_jail_enable_raw_mode(jail, &ctx->pty.stdin);
+       if (r)
+               return r;
 
-       // Setup epoll
-       epollfd = epoll_create1(0);
-       if (epollfd < 0) {
-               ERROR(jail->pakfire, "Could not initialize epoll(): %m\n");
-               r = 1;
-               goto ERROR;
-       }
+       // Enable RAW mode on standard output
+       r = pakfire_jail_enable_raw_mode(jail, &ctx->pty.stdout);
+       if (r)
+               return r;
 
-       // Turn file descriptors into non-blocking mode and add them to epoll()
-       for (const struct pakfire_wait_fds* fd = fds; fd->events; 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;
+
+       // 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_forward_pty(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx) {
+       int r;
+
+       // Read from standard input
+       if (ctx->pty.stdin.flags & 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.flags &= ~PAKFIRE_JAIL_PTY_READY_TO_READ;
+
+               // But we may have data to write
+               if (ctx->pty.stdin.buffer.used)
+                       ctx->pty.master.flags |= PAKFIRE_JAIL_PTY_READY_TO_WRITE;
+       }
+
+       // Write to the master
+       if (ctx->pty.master.flags & PAKFIRE_JAIL_PTY_READY_TO_WRITE) {
+               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.flags &= ~PAKFIRE_JAIL_PTY_READY_TO_WRITE;
+       }
+
+       // Read from the master
+       if (ctx->pty.master.flags & 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.flags &= ~PAKFIRE_JAIL_PTY_READY_TO_READ;
+
+               // But we may have data to write
+               if (ctx->pty.stdout.buffer.used)
+                       ctx->pty.stdout.flags |= PAKFIRE_JAIL_PTY_READY_TO_WRITE;
+       }
+
+       // Write to standard output
+       if (ctx->pty.stdout.flags & PAKFIRE_JAIL_PTY_READY_TO_WRITE) {
+               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;
+               }
+
+               // We are done writing for now
+               ctx->pty.stdout.flags &= ~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];
+       char garbage[8];
+       int r = 0;
+
+       // Fetch file descriptors from context
+       const int pidfd  = ctx->pidfd;
+
+       // Fetch the UNIX domain socket
+       const int socket_recv = pakfire_jail_get_pipe_to_read(jail, &ctx->socket);
+
+       // Timer
+       const int timerfd = pakfire_jail_create_timer(jail);
+
+       // Logging
+       const int log_INFO  = pakfire_jail_get_pipe_to_read(jail, &ctx->pipes.log_INFO);
+       const int log_ERROR = pakfire_jail_get_pipe_to_read(jail, &ctx->pipes.log_ERROR);
+#ifdef ENABLE_DEBUG
+       const int log_DEBUG = pakfire_jail_get_pipe_to_read(jail, &ctx->pipes.log_DEBUG);
+#endif /* ENABLE_DEBUG */
+
+       // Make a list of all file descriptors we are interested in
+       const struct pakfire_wait_fds {
+               const int fd;
+               const int events;
+       } fds[] = {
+               // Timer
+               { timerfd, EPOLLIN },
+
+               // Child Process
+               { ctx->pidfd, EPOLLIN },
+
+               // Log Pipes
+               { log_INFO, EPOLLIN },
+               { log_ERROR, EPOLLIN },
+#ifdef ENABLE_DEBUG
+               { log_DEBUG, EPOLLIN },
+#endif /* ENABLE_DEBUG */
+
+               // UNIX Domain Socket
+               { socket_recv, EPOLLIN },
+
+               // Sentinel
+               { -1, 0 },
+       };
+
+       // Setup epoll
+       epollfd = epoll_create1(0);
+       if (epollfd < 0) {
+               ERROR(jail->pakfire, "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;
@@ -942,9 +1179,6 @@ static int pakfire_jail_wait(struct pakfire_jail* jail, struct pakfire_jail_exec
        }
 
        int ended = 0;
-       int exit = 0;
-
-       CTX_DEBUG(jail->ctx, "Launching main loop...\n");
 
        // Loop for as long as the process is alive
        while (!ended) {
@@ -964,45 +1198,63 @@ static int pakfire_jail_wait(struct pakfire_jail* jail, struct pakfire_jail_exec
                        int e  = events[i].events;
                        int fd = events[i].data.fd;
 
-                       struct pakfire_log_buffer* buffer = NULL;
-                       pakfire_jail_communicate_out callback = NULL;
-                       void* data = NULL;
-                       int priority;
+                       // Handle PTY forwarding events
+                       if (ctx->pty.master.fd == fd) {
+                               if (e & (EPOLLIN|EPOLLHUP))
+                                       ctx->pty.master.flags |= PAKFIRE_JAIL_PTY_READY_TO_READ;
 
-                       // Check if there is any data to be read
-                       if (e & EPOLLIN) {
-                               // Monitor the first child process
-                               if (fd == ctx->pidfd1) {
-                                       r = pakfire_jail_wait_on_child(jail, ctx->pidfd1);
-                                       if (r) {
-                                               CTX_ERROR(jail->ctx, "The first child exited with an error\n");
-                                               goto ERROR;
-                                       }
+                               if (e & (EPOLLOUT|EPOLLHUP))
+                                       ctx->pty.master.flags |= PAKFIRE_JAIL_PTY_READY_TO_WRITE;
 
-                                       close(ctx->pidfd1);
-                                       ctx->pidfd1 = -1;
+                               // 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;
+                               }
 
-                                       continue;
+                       // Handle standard input
+                       } else if (ctx->pty.stdin.fd == fd) {
+                               if (e & (EPOLLIN|EPOLLHUP))
+                                       ctx->pty.stdin.flags |= 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.flags |= 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;
+                               }
 
-                               // Monitor the second child process
-                               } else if (fd == ctx->pidfd2) {
-                                       exit = pakfire_jail_wait_on_child(jail, ctx->pidfd2);
-                                       if (exit < 0) {
-                                               CTX_ERROR(jail->ctx, "The second child exited with an error\n");
+                       // Handle any changes to the PIDFD
+                       } else if (pidfd == fd) {
+                               if (e & EPOLLIN) {
+                                       // Call waidid() and store the result
+                                       r = waitid(P_PIDFD, ctx->pidfd, &ctx->status, WEXITED);
+                                       if (r) {
+                                               ERROR(jail->pakfire, "waitid() failed: %m\n");
                                                goto ERROR;
                                        }
 
-                                       close(ctx->pidfd2);
-                                       ctx->pidfd2 = -1;
-
                                        // Mark that we have ended so that we will process the remaining
                                        // events from epoll() now, but won't restart the outer loop.
                                        ended = 1;
+                               }
 
-                                       continue;
-
-                               // Handle timer events
-                               } else if (fd == timerfd) {
+                       // Handle timer events
+                       } else if (timerfd == fd) {
+                               if (e & EPOLLIN) {
                                        DEBUG(jail->pakfire, "Timer event received\n");
 
                                        // Disarm the timer
@@ -1018,111 +1270,65 @@ static int pakfire_jail_wait(struct pakfire_jail* jail, struct pakfire_jail_exec
                                                DEBUG(jail->pakfire, "Terminating process...\n");
 
                                                // Send SIGTERM to the process
-                                               r = pidfd_send_signal(ctx->pidfd2, SIGKILL, NULL, 0);
+                                               r = pidfd_send_signal(pidfd, SIGKILL, NULL, 0);
                                                if (r) {
                                                        ERROR(jail->pakfire, "Could not kill process: %m\n");
                                                        goto ERROR;
                                                }
                                        }
+                               }
 
-                                       // There is nothing else to do
-                                       continue;
-
-                               // Handle socket messages
-                               } else if (fd == socket_recv) {
-                                       // Receive the FD of the second child process
-                                       r = pakfire_jail_recv_fd(jail, socket_recv, &ctx->pidfd2);
+                       // 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;
 
-                                       // Add it to the event loop
-                                       r = pakfire_jail_epoll_add_fd(jail, epollfd, ctx->pidfd2, EPOLLIN);
+                                       // 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;
+                                               }
+                                       }
+                               }
+
+                       // Handle log INFO messages
+                       } else if (log_INFO == fd) {
+                               if (e & EPOLLIN) {
+                                       r = pakfire_jail_handle_log(jail, ctx, LOG_INFO, fd,
+                                               &ctx->buffers.log_INFO, pakfire_jail_log, NULL);
                                        if (r)
                                                goto ERROR;
+                               }
 
-                                       // Setup the child process
-                                       r = pakfire_jail_setup_child2(jail, ctx);
+                       // Handle log ERROR messages
+                       } else if (log_ERROR == fd) {
+                               if (e & EPOLLIN) {
+                                       r = pakfire_jail_handle_log(jail, ctx, LOG_ERR, fd,
+                                               &ctx->buffers.log_ERROR, pakfire_jail_log, NULL);
                                        if (r)
                                                goto ERROR;
-
-                                       // Don't fall through to log processing
-                                       continue;
-
-                               // Handle logging messages
-                               } else if (fd == log_INFO) {
-                                       buffer = &ctx->buffers.log_INFO;
-                                       priority = LOG_INFO;
-
-                                       callback = pakfire_jail_log;
-
-                               } else if (fd == log_ERROR) {
-                                       buffer = &ctx->buffers.log_ERROR;
-                                       priority = LOG_ERR;
-
-                                       callback = pakfire_jail_log;
+                               }
 
 #ifdef ENABLE_DEBUG
-                               } else if (fd == log_DEBUG) {
-                                       buffer = &ctx->buffers.log_DEBUG;
-                                       priority = LOG_DEBUG;
-
-                                       callback = pakfire_jail_log;
-#endif /* ENABLE_DEBUG */
-
-                               // Handle anything from the log pipes
-                               } else if (fd == stdout) {
-                                       buffer = &ctx->buffers.stdout;
-                                       priority = LOG_INFO;
-
-                                       // Send any output to the default logger if no callback is set
-                                       if (ctx->communicate.out) {
-                                               callback = ctx->communicate.out;
-                                               data     = ctx->communicate.data;
-                                       } else {
-                                               callback = jail->callbacks.log;
-                                               data     = jail->callbacks.log_data;
-                                       }
-
-                               } else if (fd == stderr) {
-                                       buffer = &ctx->buffers.stderr;
-                                       priority = LOG_ERR;
-
-                                       // Send any output to the default logger if no callback is set
-                                       if (ctx->communicate.out) {
-                                               callback = ctx->communicate.out;
-                                               data     = ctx->communicate.data;
-                                       } else {
-                                               callback = jail->callbacks.log;
-                                               data     = jail->callbacks.log_data;
-                                       }
-
-                               } else {
-                                       DEBUG(jail->pakfire, "Received invalid file descriptor %d\n", fd);
-                                       continue;
+                       // Handle log DEBUG messages
+                       } else if (log_DEBUG == fd) {
+                               if (e & EPOLLIN) {
+                                       r = pakfire_jail_handle_log(jail, ctx, LOG_DEBUG, fd,
+                                               &ctx->buffers.log_DEBUG, pakfire_jail_log, NULL);
+                                       if (r)
+                                               goto ERROR;
                                }
+#endif /* ENABLE_DEBUG */
 
-                               // Handle log event
-                               r = pakfire_jail_handle_log(jail, ctx, priority, fd, buffer, callback, data);
-                               if (r)
-                                       goto ERROR;
-                       }
-
-                       if (e & EPOLLOUT) {
-                               // Handle standard input
-                               if (fd == stdin) {
-                                       r = pakfire_jail_stream_stdin(jail, ctx, fd);
-                                       if (r) {
-                                               switch (errno) {
-                                                       // Ignore if we filled up the buffer
-                                                       case EAGAIN:
-                                                               break;
-
-                                                       default:
-                                                               ERROR(jail->pakfire, "Could not write to stdin: %m\n");
-                                                               goto ERROR;
-                                               }
-                                       }
-                               }
+                       // Log a message for anything else
+                       } else {
+                               DEBUG(jail->pakfire, "Received invalid file descriptor %d\n", fd);
+                               continue;
                        }
 
                        // Check if any file descriptors have been closed
@@ -1137,17 +1343,18 @@ static int pakfire_jail_wait(struct pakfire_jail* jail, struct pakfire_jail_exec
                }
        }
 
-       // Return the exit code
-       r = exit;
-
 ERROR:
-       CTX_DEBUG(jail->ctx, "Main loop terminated\n");
-
        if (epollfd >= 0)
                close(epollfd);
        if (timerfd >= 0)
                close(timerfd);
 
+       // 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;
 }
 
@@ -1694,6 +1901,38 @@ static int pakfire_jail_wait_for_signal(struct pakfire_jail* jail, int fd) {
        return r;
 }
 
+/*
+       Performs the initialisation that needs to happen in the parent part
+*/
+static int pakfire_jail_parent(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx) {
+       int r;
+
+       // Setup UID mapping
+       r = pakfire_jail_setup_uid_mapping(jail, ctx->pid);
+       if (r)
+               return r;
+
+       // Write "deny" to /proc/PID/setgroups
+       r = pakfire_jail_setgroups(jail, ctx->pid);
+       if (r)
+               return r;
+
+       // Setup GID mapping
+       r = pakfire_jail_setup_gid_mapping(jail, ctx->pid);
+       if (r)
+               return r;
+
+       // Parent has finished initialisation
+       DEBUG(jail->pakfire, "Parent has finished initialization\n");
+
+       // Send signal to client
+       r = pakfire_jail_send_signal(jail, ctx->completed_fd);
+       if (r)
+               return r;
+
+       return 0;
+}
+
 static int pakfire_jail_switch_root(struct pakfire_jail* jail, const char* root) {
        int r;
 
@@ -1721,78 +1960,111 @@ static int pakfire_jail_switch_root(struct pakfire_jail* jail, const char* root)
        return 0;
 }
 
-/*
-       Called by the parent that sets up the second child process...
-*/
-static int pakfire_jail_setup_child2(
-               struct pakfire_jail* jail, struct pakfire_jail_exec* ctx) {
-       pid_t pid = -1;
+static int pakfire_jail_open_pty(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx) {
        int r;
 
-       // Fetch the PID
-       r = pidfd_get_pid(ctx->pidfd2, &pid);
+       // 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 fetch PID: %s\n", strerror(-r));
-               return r;
+               CTX_ERROR(jail->ctx, "Could not unlock the PTY: %s\n", strerror(errno));
+               return -errno;
        }
 
-       // Setup UID mapping
-       r = pakfire_jail_setup_uid_mapping(jail, pid);
+       // Create a symlink
+       r = pakfire_symlink(jail->ctx, ctx->pty.console, "/dev/console");
        if (r)
                return r;
 
-       // Write "deny" to /proc/PID/setgroups
-       r = pakfire_jail_setgroups(jail, pid);
-       if (r)
-               return r;
+       return r;
+}
 
-       // Setup GID mapping
-       r = pakfire_jail_setup_gid_mapping(jail, pid);
-       if (r)
-               return r;
+static int pakfire_jail_setup_terminal(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx) {
+       int fd;
+       int r;
 
-       // Parent has finished initialisation
-       DEBUG(jail->pakfire, "Parent has finished initialization\n");
+       // 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;
+       }
 
-       // Send signal to client
-       r = pakfire_jail_send_signal(jail, ctx->completed_fd);
-       if (r)
-               return r;
+       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;
 }
 
-/*
-       Child 2 is launched in their own user/mount/etc. namespace.
-*/
-static int pakfire_jail_child2(struct pakfire_jail* jail,
-               struct pakfire_jail_exec* ctx, const char* argv[]) {
+static int pakfire_jail_child(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx,
+               const char* argv[]) {
        int r;
 
+       // Redirect any logging to our log pipe
+       pakfire_ctx_set_log_callback(jail->ctx, pakfire_jail_log_redirect, &ctx->pipes);
+
        // Fetch my own PID
        pid_t pid = getpid();
 
-       CTX_DEBUG(jail->ctx, "Launched child process in jail with PID %d\n", pid);
+       DEBUG(jail->pakfire, "Launched child process in jail with PID %d\n", pid);
+
+       // Wait for the parent to finish initialization
+       r = pakfire_jail_wait_for_signal(jail, ctx->completed_fd);
+       if (r)
+               return r;
+
+       // Die with parent
+       r = prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
+       if (r) {
+               ERROR(jail->pakfire, "Could not configure to die with parent: %m\n");
+               return 126;
+       }
 
        // Make this process dumpable
        r = prctl (PR_SET_DUMPABLE, 1, 0, 0, 0);
        if (r) {
-               CTX_ERROR(jail->ctx, "Could not make the process dumpable: %m\n");
+               ERROR(jail->pakfire, "Could not make the process dumpable: %m\n");
                return 126;
        }
 
        // Don't drop any capabilities on setuid()
        r = prctl(PR_SET_KEEPCAPS, 1);
        if (r) {
-               CTX_ERROR(jail->ctx, "Could not set PR_SET_KEEPCAPS: %m\n");
+               ERROR(jail->pakfire, "Could not set PR_SET_KEEPCAPS: %m\n");
                return 126;
        }
 
-       // Wait for the parent to finish initialization
-       r = pakfire_jail_wait_for_signal(jail, ctx->completed_fd);
-       if (r)
-               return r;
-
        // Fetch UID/GID
        uid_t uid = getuid();
        gid_t gid = getgid();
@@ -1819,20 +2091,51 @@ static int pakfire_jail_child2(struct pakfire_jail* jail,
                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)
                return 126;
 
+       const char* root = pakfire_get_path(jail->pakfire);
        const char* arch = pakfire_get_effective_arch(jail->pakfire);
 
+       // Change mount propagation to slave to receive anything from the parent namespace
+       r = pakfire_mount_change_propagation(jail->ctx, "/", MS_SLAVE);
+       if (r)
+               return r;
+
+       // Make root a mountpoint in the new mount namespace
+       r = pakfire_mount_make_mounpoint(jail->pakfire, root);
+       if (r)
+               return r;
+
+       // Change mount propagation to private
+       r = pakfire_mount_change_propagation(jail->ctx, root, MS_PRIVATE);
+       if (r)
+               return r;
+
+       // Change root (unless root is /)
+       if (!pakfire_on_root(jail->pakfire)) {
+               // Mount everything
+               r = pakfire_jail_mount(jail, ctx);
+               if (r)
+                       return r;
+
+               // chroot()
+               r = pakfire_jail_switch_root(jail, root);
+               if (r)
+                       return r;
+       }
+
        // Set personality
        unsigned long persona = pakfire_arch_personality(arch);
        if (persona) {
                r = personality(persona);
                if (r < 0) {
                        ERROR(jail->pakfire, "Could not set personality (%x)\n", (unsigned int)persona);
-                       return 126;
+                       return 1;
                }
        }
 
@@ -1854,47 +2157,45 @@ static int pakfire_jail_child2(struct pakfire_jail* jail,
                }
        }
 
-       // Close other end of log pipes
-       close(ctx->pipes.log_INFO[0]);
-       close(ctx->pipes.log_ERROR[0]);
-#ifdef ENABLE_DEBUG
-       close(ctx->pipes.log_DEBUG[0]);
-#endif /* ENABLE_DEBUG */
-
-       // Connect standard input
-       if (ctx->pipes.stdin[0] >= 0) {
-               r = dup2(ctx->pipes.stdin[0], STDIN_FILENO);
-               if (r < 0) {
-                       ERROR(jail->pakfire, "Could not connect fd %d to stdin: %m\n",
-                               ctx->pipes.stdin[0]);
+       // Create a new session
+       r = setsid();
+       if (r < 0) {
+               CTX_ERROR(jail->ctx, "Could not create a new session: %s\n", strerror(errno));
+               return r;
+       }
 
-                       return 1;
-               }
+       // Allocate a new PTY
+       r = pakfire_jail_open_pty(jail, ctx);
+       if (r) {
+               CTX_ERROR(jail->ctx, "Could not allocate a new PTY: %s\n", strerror(-r));
+               return r;
        }
 
-       // Connect standard output and error
-       if (ctx->pipes.stdout[1] >= 0 && ctx->pipes.stderr[1] >= 0) {
-               r = dup2(ctx->pipes.stdout[1], STDOUT_FILENO);
-               if (r < 0) {
-                       ERROR(jail->pakfire, "Could not connect fd %d to stdout: %m\n",
-                               ctx->pipes.stdout[1]);
+       // 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;
+       }
 
-                       return 1;
-               }
+       // Setup the terminal
+       r = pakfire_jail_setup_terminal(jail, ctx);
+       if (r)
+               return r;
 
-               r = dup2(ctx->pipes.stderr[1], STDERR_FILENO);
-               if (r < 0) {
-                       ERROR(jail->pakfire, "Could not connect fd %d to stderr: %m\n",
-                               ctx->pipes.stderr[1]);
+       // Close the master of the PTY
+       close(ctx->pty.master.fd);
+       ctx->pty.master.fd = -1;
 
-                       return 1;
-               }
+       // Close the socket
+       close(socket_send);
 
-               // Close the pipe (as we have moved the original file descriptors)
-               pakfire_jail_close_pipe(jail, ctx->pipes.stdin);
-               pakfire_jail_close_pipe(jail, ctx->pipes.stdout);
-               pakfire_jail_close_pipe(jail, ctx->pipes.stderr);
-       }
+       // Close other end of log pipes
+       close(ctx->pipes.log_INFO[0]);
+       close(ctx->pipes.log_ERROR[0]);
+#ifdef ENABLE_DEBUG
+       close(ctx->pipes.log_DEBUG[0]);
+#endif /* ENABLE_DEBUG */
 
        // Reset open file limit (http://0pointer.net/blog/file-descriptor-limits.html)
        r = pakfire_rlimit_reset_nofile(jail->pakfire);
@@ -1916,12 +2217,12 @@ static int pakfire_jail_child2(struct pakfire_jail* jail,
        if (r)
                return r;
 
-       CTX_DEBUG(jail->ctx, "Child process initialization done\n");
-       CTX_DEBUG(jail->ctx, "Launching command:\n");
+       DEBUG(jail->pakfire, "Child process initialization done\n");
+       DEBUG(jail->pakfire, "Launching command:\n");
 
        // Log argv
        for (unsigned int i = 0; argv[i]; i++)
-               CTX_DEBUG(jail->ctx, "  argv[%u] = %s\n", i, argv[i]);
+               DEBUG(jail->pakfire, "  argv[%u] = %s\n", i, argv[i]);
 
        // exec() command
        r = execvpe(argv[0], (char**)argv, jail->env);
@@ -1941,127 +2242,19 @@ static int pakfire_jail_child2(struct pakfire_jail* jail,
                                r = 1;
                }
 
-               CTX_ERROR(jail->ctx, "Could not execve(%s): %m\n", argv[0]);
+               ERROR(jail->pakfire, "Could not execve(%s): %m\n", argv[0]);
        }
 
        // We should not get here
        return r;
 }
 
-/*
-       Child 1 is launched in a new mount namespace...
-*/
-static int pakfire_jail_child1(struct pakfire_jail* jail,
-               struct pakfire_jail_exec* ctx, const char* argv[]) {
-       int r;
-
-       // Redirect any logging to our log pipe
-       pakfire_ctx_set_log_callback(jail->ctx, pakfire_jail_log_redirect, &ctx->pipes);
-
-       CTX_DEBUG(jail->ctx, "First child process launched\n");
-
-       const int socket_send = pakfire_jail_get_pipe_to_write(jail, &ctx->socket);
-
-       const char* root = pakfire_get_path(jail->pakfire);
-
-       // Die with parent
-       r = prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
-       if (r) {
-               CTX_ERROR(jail->ctx, "Could not configure to die with parent: %s\n", strerror(errno));
-               goto ERROR;
-       }
-
-       // Change mount propagation so that we will receive, but don't propagate back
-       r = pakfire_mount_change_propagation(jail->ctx, "/", MS_SLAVE);
-       if (r) {
-               CTX_ERROR(jail->ctx, "Could not change mount propagation to SLAVE: %s\n", strerror(r));
-               goto ERROR;
-       }
-
-       // Make root a mountpoint in the new mount namespace
-       r = pakfire_mount_make_mounpoint(jail->pakfire, root);
-       if (r)
-               goto ERROR;
-
-       // Make everything private
-       r = pakfire_mount_change_propagation(jail->ctx, root, MS_PRIVATE);
-       if (r) {
-               CTX_ERROR(jail->ctx, "Could not change mount propagation to PRIVATE: %s\n", strerror(r));
-               goto ERROR;
-       }
-
-       // Mount everything
-       r = pakfire_jail_mount(jail, ctx);
-       if (r)
-               goto ERROR;
-
-       // chroot()
-       r = pakfire_jail_switch_root(jail, root);
-       if (r)
-               goto ERROR;
-
-       // Change mount propagation so that we will propagate everything down
-       r = pakfire_mount_change_propagation(jail->ctx, "/", MS_SHARED);
-       if (r) {
-               CTX_ERROR(jail->ctx, "Could not change mount propagation to SHARED: %s\n", strerror(r));
-               goto ERROR;
-       }
-
-       // Configure child process
-       struct clone_args args = {
-               .flags =
-                       CLONE_NEWCGROUP |
-                       CLONE_NEWIPC |
-                       CLONE_NEWNS |
-                       CLONE_NEWPID |
-                       CLONE_NEWTIME |
-                       CLONE_NEWUSER |
-                       CLONE_NEWUTS |
-                       CLONE_PIDFD,
-               .exit_signal = SIGCHLD,
-               .pidfd = (long long unsigned int)&ctx->pidfd2,
-       };
-
-       // Launch the process into the configured cgroup
-       if (ctx->cgroup) {
-               args.flags |= CLONE_INTO_CGROUP;
-
-               // Clone into this cgroup
-               args.cgroup = pakfire_cgroup_fd(ctx->cgroup);
-       }
-
-       // Setup networking
-       if (!pakfire_jail_exec_has_flag(ctx, PAKFIRE_JAIL_HAS_NETWORKING))
-               args.flags |= CLONE_NEWNET;
-
-       // Fork the second child process
-       pid_t pid = clone3(&args, sizeof(args));
-       if (pid < 0) {
-               CTX_ERROR(jail->ctx, "Could not fork the first child process: %s\n", strerror(errno));
-               r = -errno;
-               goto ERROR;
-
-       // Child process
-       } else if (pid == 0) {
-               r = pakfire_jail_child2(jail, ctx, argv);
-               _exit(r);
-       }
-
-       // Send the pidfd of the child to the first parent
-       r = pakfire_jail_send_fd(jail, socket_send, ctx->pidfd2);
-       if (r)
-               goto ERROR;
-
-ERROR:
-       return r;
-}
-
 // Run a command in the jail
 static int __pakfire_jail_exec(struct pakfire_jail* jail, const char* argv[],
-               const int interactive,
                pakfire_jail_communicate_in  communicate_in,
                pakfire_jail_communicate_out communicate_out,
                void* data, int flags) {
+       int exit = -1;
        int r;
 
        // Check if argv is valid
@@ -2077,9 +2270,6 @@ static int __pakfire_jail_exec(struct pakfire_jail* jail, const char* argv[],
                .socket = { -1, -1 },
 
                .pipes = {
-                       .stdin     = { -1, -1 },
-                       .stdout    = { -1, -1 },
-                       .stderr    = { -1, -1 },
                        .log_INFO  = { -1, -1 },
                        .log_ERROR = { -1, -1 },
 #ifdef ENABLE_DEBUG
@@ -2093,33 +2283,28 @@ static int __pakfire_jail_exec(struct pakfire_jail* jail, const char* argv[],
                        .data = data,
                },
 
-               // PIDs
-               .pidfd1 = -1,
-               .pidfd2 = -1,
+               .pidfd = -1,
+
+               // PTY
+               .pty = {
+                       .master = {
+                               .fd = -1,
+                       },
+                       .stdin = {
+                               .fd = -1,
+                       },
+                       .stdout = {
+                               .fd = -1,
+                       },
+               },
        };
 
        DEBUG(jail->pakfire, "Executing jail...\n");
 
-       // Become the subreaper
-       r = prctl(PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0);
-       if (r < 0) {
-               CTX_ERROR(jail->ctx, "Failed to become the sub-reaper: %s\n", strerror(errno));
-               r = -errno;
-               goto ERROR;
-       }
-
        // Enable networking in interactive mode
-       if (interactive)
+       if (ctx.flags & PAKFIRE_JAIL_PTY_FORWARDING)
                ctx.flags |= PAKFIRE_JAIL_HAS_NETWORKING;
 
-       // 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;
-               goto ERROR;
-       }
-
        /*
                Setup a file descriptor which can be used to notify the client that the parent
                has completed configuration.
@@ -2130,24 +2315,12 @@ static int __pakfire_jail_exec(struct pakfire_jail* jail, const char* argv[],
                return -1;
        }
 
-       // Create pipes to communicate with child process if we are not running interactively
-       if (!interactive) {
-               // stdin (only if callback is set)
-               if (ctx.communicate.in) {
-                       r = pakfire_jail_setup_pipe(jail, &ctx.pipes.stdin, 0);
-                       if (r)
-                               goto ERROR;
-               }
-
-               // stdout
-               r = pakfire_jail_setup_pipe(jail, &ctx.pipes.stdout, 0);
-               if (r)
-                       goto ERROR;
-
-               // stderr
-               r = pakfire_jail_setup_pipe(jail, &ctx.pipes.stderr, 0);
-               if (r)
-                       goto ERROR;
+       // 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;
+               goto ERROR;
        }
 
        // Setup pipes for logging
@@ -2168,8 +2341,25 @@ static int __pakfire_jail_exec(struct pakfire_jail* jail, const char* argv[],
                goto ERROR;
 #endif /* ENABLE_DEBUG */
 
+       // Configure child process
+       struct clone_args args = {
+               .flags =
+                       CLONE_NEWCGROUP |
+                       CLONE_NEWIPC |
+                       CLONE_NEWNS |
+                       CLONE_NEWPID |
+                       CLONE_NEWTIME |
+                       CLONE_NEWUSER |
+                       CLONE_NEWUTS |
+                       CLONE_PIDFD,
+               .exit_signal = SIGCHLD,
+               .pidfd = (long long unsigned int)&ctx.pidfd,
+       };
+
        // Launch the process in a cgroup that is a leaf of the configured cgroup
        if (jail->cgroup) {
+               args.flags |= CLONE_INTO_CGROUP;
+
                // Fetch our UUID
                const char* uuid = pakfire_jail_uuid(jail);
 
@@ -2179,43 +2369,65 @@ static int __pakfire_jail_exec(struct pakfire_jail* jail, const char* argv[],
                        ERROR(jail->pakfire, "Could not create cgroup for jail: %m\n");
                        goto ERROR;
                }
-       }
 
-       /*
-               Initially, we will set up a new mount namespace and launch a child process in it.
-
-               This process remains in the user/ipc/time/etc. namespace and will set up
-               the mount namespace.
-       */
+               // Clone into this cgroup
+               args.cgroup = pakfire_cgroup_fd(ctx.cgroup);
+       }
 
-       // Configure child process
-       struct clone_args args = {
-               .flags =
-                       CLONE_NEWNS |
-                       CLONE_PIDFD |
-                       CLONE_CLEAR_SIGHAND,
-               .exit_signal = SIGCHLD,
-               .pidfd = (long long unsigned int)&ctx.pidfd1,
-       };
+       // Setup networking
+       if (!pakfire_jail_exec_has_flag(&ctx, PAKFIRE_JAIL_HAS_NETWORKING)) {
+               args.flags |= CLONE_NEWNET;
+       }
 
-       // Fork the first child process
-       pid_t pid = clone3(&args, sizeof(args));
-       if (pid < 0) {
-               CTX_ERROR(jail->ctx, "Could not fork the first child process: %s\n", strerror(errno));
-               r = -errno;
-               goto ERROR;
+       // Fork this process
+       ctx.pid = clone3(&args, sizeof(args));
+       if (ctx.pid < 0) {
+               ERROR(jail->pakfire, "Could not clone: %m\n");
+               return -1;
 
        // Child process
-       } else if (pid == 0) {
-               r = pakfire_jail_child1(jail, &ctx, argv);
+       } else if (ctx.pid == 0) {
+               r = pakfire_jail_child(jail, &ctx, argv);
                _exit(r);
        }
 
        // Parent process
+       r = pakfire_jail_parent(jail, &ctx);
+       if (r)
+               goto ERROR;
+
+       DEBUG(jail->pakfire, "Waiting for PID %d to finish its work\n", ctx.pid);
+
+       // Read output of the child process
        r = pakfire_jail_wait(jail, &ctx);
        if (r)
                goto ERROR;
 
+       // Handle exit status
+       switch (ctx.status.si_code) {
+               case CLD_EXITED:
+                       DEBUG(jail->pakfire, "The child process exited with code %d\n",
+                               ctx.status.si_status);
+
+                       // Pass exit code
+                       exit = ctx.status.si_status;
+                       break;
+
+               case CLD_KILLED:
+                       ERROR(jail->pakfire, "The child process was killed\n");
+                       exit = 139;
+                       break;
+
+               case CLD_DUMPED:
+                       ERROR(jail->pakfire, "The child process terminated abnormally\n");
+                       break;
+
+               // Log anything else
+               default:
+                       ERROR(jail->pakfire, "Unknown child exit code: %d\n", ctx.status.si_code);
+                       break;
+       }
+
 ERROR:
        // Destroy the temporary cgroup (if any)
        if (ctx.cgroup) {
@@ -2227,23 +2439,18 @@ ERROR:
        }
 
        // Close any file descriptors
-       pakfire_jail_close_pipe(jail, ctx.pipes.stdin);
-       pakfire_jail_close_pipe(jail, ctx.pipes.stdout);
-       pakfire_jail_close_pipe(jail, ctx.pipes.stderr);
+       if (ctx.pidfd >= 0)
+               close(ctx.pidfd);
+       if (ctx.pty.master.fd >= 0)
+               close(ctx.pty.master.fd);
        pakfire_jail_close_pipe(jail, ctx.pipes.log_INFO);
        pakfire_jail_close_pipe(jail, ctx.pipes.log_ERROR);
 #ifdef ENABLE_DEBUG
        pakfire_jail_close_pipe(jail, ctx.pipes.log_DEBUG);
 #endif /* ENABLE_DEBUG */
-       if (ctx.pidfd1 >= 0)
-               close(ctx.pidfd1);
-       if (ctx.pidfd2 >= 0)
-               close(ctx.pidfd2);
-
-       // Close sockets
        pakfire_jail_close_pipe(jail, ctx.socket);
 
-       return r;
+       return exit;
 }
 
 PAKFIRE_EXPORT int pakfire_jail_exec(
@@ -2252,19 +2459,21 @@ PAKFIRE_EXPORT int pakfire_jail_exec(
                pakfire_jail_communicate_in  callback_in,
                pakfire_jail_communicate_out callback_out,
                void* data, int flags) {
-       return __pakfire_jail_exec(jail, argv, 0, callback_in, callback_out, data, flags);
+       return __pakfire_jail_exec(jail, argv, callback_in, callback_out, data, flags);
 }
 
 static int pakfire_jail_exec_interactive(
                struct pakfire_jail* jail, const char* argv[], int flags) {
        int r;
 
+       flags |= PAKFIRE_JAIL_PTY_FORWARDING;
+
        // Setup interactive stuff
        r = pakfire_jail_setup_interactive_env(jail);
        if (r)
                return r;
 
-       return __pakfire_jail_exec(jail, argv, 1, NULL, NULL, NULL, flags);
+       return __pakfire_jail_exec(jail, argv, NULL, NULL, NULL, flags);
 }
 
 int pakfire_jail_exec_script(struct pakfire_jail* jail,