]> git.ipfire.org Git - people/ms/pakfire.git/blobdiff - src/libpakfire/jail.c
jail: Implement PTY forwarding
[people/ms/pakfire.git] / src / libpakfire / jail.c
index f2aac5fd677fa8d218ff2854cfe154ef111e5a76..5f3884445c04117f8e3365b15983f6d536e393ed 100644 (file)
 #############################################################################*/
 
 #include <errno.h>
+#include <fcntl.h>
 #include <linux/capability.h>
-#include <linux/fcntl.h>
 #include <linux/sched.h>
+#include <sys/wait.h>
 #include <linux/wait.h>
 #include <sched.h>
 #include <signal.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/timerfd.h>
 #include <sys/types.h>
 #include <sys/wait.h>
+#include <termios.h>
+
+// libnl3
+#include <net/if.h>
+#include <netlink/route/link.h>
 
 // libseccomp
 #include <seccomp.h>
@@ -48,6 +56,7 @@
 #include <pakfire/logging.h>
 #include <pakfire/mount.h>
 #include <pakfire/pakfire.h>
+#include <pakfire/path.h>
 #include <pakfire/private.h>
 #include <pakfire/pwd.h>
 #include <pakfire/string.h>
@@ -63,8 +72,13 @@ static const struct environ {
        const char* key;
        const char* val;
 } ENV[] = {
-       { "LANG", "en_US.utf-8" },
+       { "HOME", "/root" },
+       { "LANG", "C.utf-8" },
+       { "PATH", "/usr/local/sbin:/usr/sbin:/sbin:/usr/local/bin:/usr/bin:/bin", },
        { "TERM", "vt100" },
+
+       // Tell everything that it is running inside a Pakfire container
+       { "container", "pakfire" },
        { NULL, NULL },
 };
 
@@ -75,6 +89,7 @@ struct pakfire_jail_mountpoint {
 };
 
 struct pakfire_jail {
+       struct pakfire_ctx* ctx;
        struct pakfire* pakfire;
        int nrefs;
 
@@ -82,25 +97,28 @@ struct pakfire_jail {
        uuid_t uuid;
        char __uuid[UUID_STR_LEN];
 
-       // Flags
-       int flags;
-
        // Resource Limits
        int nice;
 
+       // Timeout
+       struct itimerspec timeout;
+
        // CGroup
        struct pakfire_cgroup* cgroup;
 
        // Environment
        char* env[ENVIRON_SIZE];
 
-       // Logging
-       pakfire_jail_log_callback log_callback;
-       void* log_data;
-
        // Mountpoints
        struct pakfire_jail_mountpoint mountpoints[MAX_MOUNTPOINTS];
        unsigned int num_mountpoints;
+
+       // Callbacks
+       struct pakfire_jail_callbacks {
+               // Log
+               pakfire_jail_log_callback log;
+               void* log_data;
+       } callbacks;
 };
 
 struct pakfire_log_buffer {
@@ -109,10 +127,15 @@ struct pakfire_log_buffer {
 };
 
 struct pakfire_jail_exec {
+       int flags;
+
        // PID (of the child)
        pid_t pid;
        int pidfd;
 
+       // Socket to pass FDs
+       int socket[2];
+
        // Process status (from waitid)
        siginfo_t status;
 
@@ -121,15 +144,21 @@ struct pakfire_jail_exec {
 
        // Log pipes
        struct pakfire_jail_pipes {
-               int stdout[2];
-               int stderr[2];
-
                // Logging
                int log_INFO[2];
                int log_ERROR[2];
+#ifdef ENABLE_DEBUG
                int log_DEBUG[2];
+#endif /* ENABLE_DEBUG */
        } pipes;
 
+       // Communicate
+       struct pakfire_jail_communicate {
+               pakfire_jail_communicate_in  in;
+               pakfire_jail_communicate_out out;
+               void* data;
+       } communicate;
+
        // Log buffers
        struct pakfire_jail_buffers {
                struct pakfire_log_buffer stdout;
@@ -138,17 +167,60 @@ struct pakfire_jail_exec {
                // Logging
                struct pakfire_log_buffer log_INFO;
                struct pakfire_log_buffer log_ERROR;
+#ifdef ENABLE_DEBUG
                struct pakfire_log_buffer log_DEBUG;
+#endif /* ENABLE_DEBUG */
        } buffers;
 
        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) {
        return syscall(__NR_clone3, args, size);
 }
 
+static int pidfd_send_signal(int pidfd, int sig, siginfo_t* info, unsigned int flags) {
+       return syscall(SYS_pidfd_send_signal, pidfd, sig, info, flags);
+}
+
+static int pivot_root(const char* new_root, const char* old_root) {
+       return syscall(SYS_pivot_root, new_root, old_root);
+}
+
+static int pakfire_jail_exec_has_flag(
+               const struct pakfire_jail_exec* ctx, const enum pakfire_jail_exec_flags flag) {
+       return ctx->flags & flag;
+}
+
 static void pakfire_jail_free(struct pakfire_jail* jail) {
        DEBUG(jail->pakfire, "Freeing jail at %p\n", jail);
 
@@ -158,8 +230,10 @@ static void pakfire_jail_free(struct pakfire_jail* jail) {
 
        if (jail->cgroup)
                pakfire_cgroup_unref(jail->cgroup);
-
-       pakfire_unref(jail->pakfire);
+       if (jail->pakfire)
+               pakfire_unref(jail->pakfire);
+       if (jail->ctx)
+               pakfire_ctx_unref(jail->ctx);
        free(jail);
 }
 
@@ -187,6 +261,13 @@ static int pakfire_jail_default_log_callback(struct pakfire* pakfire, void* data
        return 0;
 }
 
+static const char* pakfire_jail_uuid(struct pakfire_jail* jail) {
+       if (!*jail->__uuid)
+               uuid_unparse_lower(jail->uuid, jail->__uuid);
+
+       return jail->__uuid;
+}
+
 static int pakfire_jail_setup_interactive_env(struct pakfire_jail* jail) {
        // Set PS1
        int r = pakfire_jail_set_env(jail, "PS1", "pakfire-jail \\w> ");
@@ -212,33 +293,32 @@ static int pakfire_jail_setup_interactive_env(struct pakfire_jail* jail) {
        return 0;
 }
 
-PAKFIRE_EXPORT int pakfire_jail_create(struct pakfire_jail** jail,
-               struct pakfire* pakfire, int flags) {
+PAKFIRE_EXPORT int pakfire_jail_create(struct pakfire_jail** jail, struct pakfire* pakfire) {
        int r;
 
+       const char* arch = pakfire_get_effective_arch(pakfire);
+
        // Allocate a new jail
        struct pakfire_jail* j = calloc(1, sizeof(*j));
        if (!j)
                return 1;
 
+       // Reference context
+       j->ctx = pakfire_ctx(pakfire);
+
        // Reference Pakfire
        j->pakfire = pakfire_ref(pakfire);
 
        // Initialize reference counter
        j->nrefs = 1;
 
-       // Store flags
-       j->flags = flags;
-
        // Generate a random UUID
        uuid_generate_random(j->uuid);
 
        DEBUG(j->pakfire, "Allocated new jail at %p\n", j);
 
-       // Set default log callback
-       r = pakfire_jail_set_log_callback(j, pakfire_jail_default_log_callback, NULL);
-       if (r)
-               goto ERROR;
+       // Set the default logging callback
+       pakfire_jail_set_log_callback(j, pakfire_jail_default_log_callback, NULL);
 
        // Set default environment
        for (const struct environ* e = ENV; e->key; e++) {
@@ -247,6 +327,25 @@ PAKFIRE_EXPORT int pakfire_jail_create(struct pakfire_jail** jail,
                        goto ERROR;
        }
 
+       // Enable all CPU features that CPU has to offer
+       if (!pakfire_arch_is_supported_by_host(arch)) {
+               r = pakfire_jail_set_env(j, "QEMU_CPU", "max");
+               if (r)
+                       goto ERROR;
+       }
+
+       // Set container UUID
+       r = pakfire_jail_set_env(j, "container_uuid", pakfire_jail_uuid(j));
+       if (r)
+               goto ERROR;
+
+       // Disable systemctl to talk to systemd
+       if (!pakfire_on_root(j->pakfire)) {
+               r = pakfire_jail_set_env(j, "SYSTEMD_OFFLINE", "1");
+               if (r)
+                       goto ERROR;
+       }
+
        // Done
        *jail = j;
        return 0;
@@ -271,11 +370,12 @@ PAKFIRE_EXPORT struct pakfire_jail* pakfire_jail_unref(struct pakfire_jail* jail
        return NULL;
 }
 
-static const char* pakfire_jail_uuid(struct pakfire_jail* jail) {
-       if (!*jail->__uuid)
-               uuid_unparse_lower(jail->uuid, jail->__uuid);
+// Logging Callback
 
-       return jail->__uuid;
+PAKFIRE_EXPORT void pakfire_jail_set_log_callback(struct pakfire_jail* jail,
+               pakfire_jail_log_callback callback, void* data) {
+       jail->callbacks.log = callback;
+       jail->callbacks.log_data = data;
 }
 
 // Resource Limits
@@ -331,12 +431,13 @@ static int pakfire_jail_find_env(struct pakfire_jail* jail, const char* key) {
                return -1;
        }
 
-       char buffer[strlen(key) + 2];
-       pakfire_string_format(buffer, "%s=", key);
+       const size_t length = strlen(key);
 
        for (unsigned int i = 0; jail->env[i]; i++) {
-               if (pakfire_string_startswith(jail->env[i], buffer))
+               if ((pakfire_string_startswith(jail->env[i], key)
+                               && *(jail->env[i] + length) == '=')) {
                        return i;
+               }
        }
 
        // Nothing found
@@ -410,22 +511,57 @@ PAKFIRE_EXPORT int pakfire_jail_import_env(struct pakfire_jail* jail, const char
        return 0;
 }
 
-// Logging
+// Timeout
 
-PAKFIRE_EXPORT int pakfire_jail_set_log_callback(struct pakfire_jail* jail,
-               pakfire_jail_log_callback callback, void* data) {
-       jail->log_callback = callback;
-       jail->log_data = data;
+PAKFIRE_EXPORT int pakfire_jail_set_timeout(
+               struct pakfire_jail* jail, unsigned int timeout) {
+       // Store value
+       jail->timeout.it_value.tv_sec = timeout;
+
+       if (timeout > 0)
+               DEBUG(jail->pakfire, "Timeout set to %u second(s)\n", timeout);
+       else
+               DEBUG(jail->pakfire, "Timeout disabled\n");
 
        return 0;
 }
 
+static int pakfire_jail_create_timer(struct pakfire_jail* jail) {
+       int r;
+
+       // Nothing to do if no timeout has been set
+       if (!jail->timeout.it_value.tv_sec)
+               return -1;
+
+       // Create a new timer
+       const int fd = timerfd_create(CLOCK_MONOTONIC, 0);
+       if (fd < 0) {
+               ERROR(jail->pakfire, "Could not create timer: %m\n");
+               goto ERROR;
+       }
+
+       // Arm timer
+       r = timerfd_settime(fd, 0, &jail->timeout, NULL);
+       if (r) {
+               ERROR(jail->pakfire, "Could not arm timer: %m\n");
+               goto ERROR;
+       }
+
+       return fd;
+
+ERROR:
+       if (fd >= 0)
+               close(fd);
+
+       return -1;
+}
+
 /*
        This function replaces any logging in the child process.
 
        All log messages will be sent to the parent process through their respective pipes.
 */
-static void pakfire_jail_log(void* data, int priority, const char* file,
+static void pakfire_jail_log_redirect(void* data, int priority, const char* file,
                int line, const char* fn, const char* format, va_list args) {
        struct pakfire_jail_pipes* pipes = (struct pakfire_jail_pipes*)data;
        int fd;
@@ -451,7 +587,7 @@ static void pakfire_jail_log(void* data, int priority, const char* file,
        }
 
        // Send the log message
-       if (fd)
+       if (fd >= 0)
                vdprintf(fd, format, args);
 }
 
@@ -466,7 +602,7 @@ static int pakfire_jail_log_buffer_is_full(const struct pakfire_log_buffer* buff
 */
 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_log_callback callback, void* data) {
+               struct pakfire_log_buffer* buffer, pakfire_jail_communicate_out callback, void* data) {
        char line[BUFFER_SIZE + 1];
 
        // Fill up buffer from fd
@@ -529,6 +665,107 @@ static int pakfire_jail_handle_log(struct pakfire_jail* jail,
        return 0;
 }
 
+#if 0
+static int pakfire_jail_stream_stdin(struct pakfire_jail* jail,
+               struct pakfire_jail_exec* ctx, const int fd) {
+       int r;
+
+       // Nothing to do if there is no stdin callback set
+       if (!ctx->communicate.in) {
+               DEBUG(jail->pakfire, "Callback for standard input is not set\n");
+               return 0;
+       }
+
+       // Skip if the writing pipe has already been closed
+       if (ctx->pipes.stdin[1] < 0)
+               return 0;
+
+       DEBUG(jail->pakfire, "Streaming standard input...\n");
+
+       // Calling the callback
+       r = ctx->communicate.in(jail->pakfire, ctx->communicate.data, fd);
+
+       DEBUG(jail->pakfire, "Standard input callback finished: %d\n", r);
+
+       // The callback signaled that it has written everything
+       if (r == EOF) {
+               DEBUG(jail->pakfire, "Closing standard input pipe\n");
+
+               // Close the file-descriptor
+               close(fd);
+
+               // Reset the file-descriptor so it won't be closed again later
+               ctx->pipes.stdin[1] = -1;
+
+               // Report success
+               r = 0;
+       }
+
+       return r;
+}
+#endif
+
+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 int pakfire_jail_setup_pipe(struct pakfire_jail* jail, int (*fds)[2], const int flags) {
        int r = pipe2(*fds, flags);
        if (r < 0) {
@@ -541,7 +778,7 @@ static int pakfire_jail_setup_pipe(struct pakfire_jail* jail, int (*fds)[2], con
 
 static void pakfire_jail_close_pipe(struct pakfire_jail* jail, int fds[2]) {
        for (unsigned int i = 0; i < 2; i++)
-               if (fds[i])
+               if (fds[i] >= 0)
                        close(fds[i]);
 }
 
@@ -549,301 +786,747 @@ static void pakfire_jail_close_pipe(struct pakfire_jail* jail, int fds[2]) {
        This is a convenience function to fetch the reading end of a pipe and
        closes the write end.
 */
-static int pakfire_jail_get_pipe(struct pakfire_jail* jail, int (*fds)[2]) {
+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) {
+       if (*fd_write >= 0) {
                close(*fd_write);
-               *fd_write = 0;
+               *fd_write = -1;
        }
 
        // Return the read end
-       return *fd_read;
+       if (*fd_read >= 0)
+               return *fd_read;
+
+       return -1;
 }
 
-static int pakfire_jail_wait(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx) {
-       int epollfd = -1;
-       struct epoll_event ev;
-       struct epoll_event events[EPOLL_MAX_EVENTS];
-       int r = 0;
+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];
 
-       // Fetch file descriptors from context
-       const int stdout = pakfire_jail_get_pipe(jail, &ctx->pipes.stdout);
-       const int stderr = pakfire_jail_get_pipe(jail, &ctx->pipes.stderr);
-       const int pidfd  = ctx->pidfd;
+       // Close the read end of the pipe
+       if (*fd_read >= 0) {
+               close(*fd_read);
+               *fd_read = -1;
+       }
 
-       // Logging
-       const int log_INFO  = pakfire_jail_get_pipe(jail, &ctx->pipes.log_INFO);
-       const int log_ERROR = pakfire_jail_get_pipe(jail, &ctx->pipes.log_ERROR);
-       const int log_DEBUG = pakfire_jail_get_pipe(jail, &ctx->pipes.log_DEBUG);
+       // Return the write end
+       if (*fd_write >= 0)
+               return *fd_write;
 
-       // Make a list of all file descriptors we are interested in
-       int fds[] = {
-               stdout, stderr, pidfd, log_INFO, log_ERROR, log_DEBUG,
-       };
+       return -1;
+}
 
-       // Setup epoll
-       epollfd = epoll_create1(0);
-       if (epollfd < 0) {
-               ERROR(jail->pakfire, "Could not initialize epoll(): %m\n");
-               r = 1;
-               goto ERROR;
-       }
+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
+       pakfire_log_condition(pakfire, priority, 0, "%.*s", (int)length, line);
 
-       ev.events = EPOLLIN|EPOLLHUP;
+       return 0;
+}
 
-       // Turn file descriptors into non-blocking mode and add them to epoll()
-       for (unsigned int i = 0; i < sizeof(fds) / sizeof(*fds); i++) {
-               int fd = fds[i];
+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;
 
-               // Skip fds which were not initialized
-               if (fd <= 0)
-                       continue;
+       // Read flags
+       int flags = fcntl(fd, F_GETFL, 0);
 
-               ev.data.fd = fd;
+       // 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;
+       }
 
-               if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) < 0) {
-                       ERROR(jail->pakfire, "Could not add file descriptor %d to epoll(): %m\n", fd);
-                       r = 1;
-                       goto ERROR;
-               }
+       // Add the file descriptor to the loop
+       r = epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
+       if (r < 0) {
+               ERROR(jail->pakfire, "Could not add file descriptor %d to epoll(): %s\n",
+                       fd, strerror(errno));
+               return -errno;
        }
 
-       int ended = 0;
+       return 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;
+// PTY Forwarding
 
-                       ERROR(jail->pakfire, "epoll_wait() failed: %m\n");
-                       r = 1;
+static int pakfire_jail_enable_raw_mode(struct pakfire_jail* jail,
+               struct pakfire_jail_pty_stdio* stdio) {
+       struct termios raw_attrs;
+       int r;
 
-                       goto ERROR;
-               }
+       // 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;
+       }
 
-               for (int i = 0; i < num; i++) {
-                       int e  = events[i].events;
-                       int fd = events[i].data.fd;
+       // 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;
+       }
 
-                       struct pakfire_log_buffer* buffer = NULL;
-                       pakfire_jail_log_callback callback = NULL;
-                       void* data = NULL;
-                       int priority;
+       // Copy all attributes
+       raw_attrs = stdio->attrs;
 
-                       // Check if there is any data to be read
-                       if (e & EPOLLIN) {
-                               // Handle any changes to the PIDFD
-                               if (fd == pidfd) {
-                                       // 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;
-                                       }
+       // Make it RAW
+       cfmakeraw(&raw_attrs);
 
-                                       // 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;
+       switch (stdio->fd) {
+               case STDIN_FILENO:
+                       raw_attrs.c_oflag = stdio->attrs.c_oflag;
+                       break;
 
-                               // Handle logging messages
-                               } else if (fd == log_INFO) {
-                                       buffer = &ctx->buffers.log_INFO;
-                                       priority = LOG_INFO;
+               case STDOUT_FILENO:
+                       raw_attrs.c_iflag = stdio->attrs.c_iflag;
+                       raw_attrs.c_lflag = stdio->attrs.c_lflag;
+                       break;
+       }
 
-                                       callback = pakfire_jail_default_log_callback;
+       // 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;
+       }
 
-                               } else if (fd == log_ERROR) {
-                                       buffer = &ctx->buffers.log_ERROR;
-                                       priority = LOG_ERR;
+       return 0;
+}
 
-                                       callback = pakfire_jail_default_log_callback;
+static int pakfire_jail_restore_attrs(struct pakfire_jail* jail,
+               const struct pakfire_jail_pty_stdio* stdio) {
+       int r;
 
-                               } else if (fd == log_DEBUG) {
-                                       buffer = &ctx->buffers.log_DEBUG;
-                                       priority = LOG_DEBUG;
+       // 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;
+       }
 
-                                       callback = pakfire_jail_default_log_callback;
+       // 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;
+       }
 
-                               // Handle anything from the log pipes
-                               } else if (fd == stdout) {
-                                       buffer = &ctx->buffers.stdout;
-                                       priority = LOG_INFO;
+       return 0;
+}
 
-                                       callback = jail->log_callback;
-                                       data = jail->log_data;
+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;
 
-                               } else if (fd == stderr) {
-                                       buffer = &ctx->buffers.stderr;
-                                       priority = LOG_ERR;
+       CTX_DEBUG(jail->ctx, "Setting up PTY forwarding on fd %d\n", fd);
 
-                                       callback = jail->log_callback;
-                                       data = jail->log_data;
+       // Store the file descriptor
+       ctx->pty.master.fd = fd;
 
-                               } else {
-                                       DEBUG(jail->pakfire, "Received invalid file descriptor %d\n", fd);
-                                       continue;
-                               }
+       // Configure stdin/stdout
+       ctx->pty.stdin.fd  = STDIN_FILENO;
+       ctx->pty.stdout.fd = STDOUT_FILENO;
 
-                               // Handle log event
-                               r = pakfire_jail_handle_log(jail, ctx, priority, fd, buffer, callback, data);
-                               if (r)
-                                       goto ERROR;
-                       }
+       // 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;
+       }
 
-                       // 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) {
-                                       ERROR(jail->pakfire, "Could not remove closed file-descriptor %d: %m\n", fd);
-                                       goto ERROR;
-                               }
-                       }
-               }
+       // 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;
        }
 
-ERROR:
-       if (epollfd > 0)
-               close(epollfd);
+       // Enable RAW mode on standard input
+       r = pakfire_jail_enable_raw_mode(jail, &ctx->pty.stdin);
+       if (r)
+               return r;
 
-       return r;
-}
+       // Enable RAW mode on standard output
+       r = pakfire_jail_enable_raw_mode(jail, &ctx->pty.stdout);
+       if (r)
+               return r;
 
-static int pakfire_jail_capture_stdout(struct pakfire* pakfire, void* data, int priority,
-               const char* line, size_t length) {
-       char** output = (char**)data;
+       // 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_fill_buffer(struct pakfire_jail* jail, int fd, struct pakfire_log_buffer* buffer) {
        int r;
 
-               // Append everything from stdout to a buffer
-       if (priority == LOG_INFO) {
-               r = asprintf(output, "%s%s", (output && *output) ? *output : "", line);
-               if (r < 0)
-                       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;
+               }
+
+       // EOF
+       } else if (r == 0) {
+               // XXX What to do here?
+
+       // Successful read
+       } else {
+               buffer->used += r;
        }
 
-       // Send everything else to the default logger
-       return pakfire_jail_default_log_callback(pakfire, NULL, priority, line, length);
+       return 0;
 }
 
-// Capabilities
+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;
+               }
 
-static int pakfire_jail_drop_capabilities(struct pakfire_jail* jail) {
-       const int capabilities[] = {
-               // Deny access to the kernel's audit system
-               CAP_AUDIT_CONTROL,
-               CAP_AUDIT_READ,
-               CAP_AUDIT_WRITE,
+       // Successful write
+       } else {
+               memmove(buffer->data, buffer->data + r, buffer->used - r);
 
-               // Deny suspending block devices
-               CAP_BLOCK_SUSPEND,
+               buffer->used -= r;
+       }
+
+       return 0;
+}
+
+static int pakfire_jail_forward_pty(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx) {
+       int r;
 
-               // Deny any stuff with BPF
-               CAP_BPF,
+       // 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;
+               }
 
-               // Deny checkpoint restore
-               CAP_CHECKPOINT_RESTORE,
+               // We are done reading for now
+               ctx->pty.stdin.flags &= ~PAKFIRE_JAIL_PTY_READY_TO_READ;
 
-               // Deny opening files by inode number (open_by_handle_at)
-               CAP_DAC_READ_SEARCH,
+               // 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;
+               }
 
-               // Deny setting SUID bits
-               CAP_FSETID,
+               // We are done writing for now
+               ctx->pty.master.flags &= ~PAKFIRE_JAIL_PTY_READY_TO_WRITE;
+       }
 
-               // Deny locking more memory
-               CAP_IPC_LOCK,
+       // 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;
+               }
 
-               // Deny modifying any Apparmor/SELinux/SMACK configuration
-               CAP_MAC_ADMIN,
-               CAP_MAC_OVERRIDE,
+               // We are done reading for now
+               ctx->pty.master.flags &= ~PAKFIRE_JAIL_PTY_READY_TO_READ;
 
-               // Deny creating any special devices
-               CAP_MKNOD,
+               // But we may have data to write
+               if (ctx->pty.stdout.buffer.used)
+                       ctx->pty.stdout.flags |= PAKFIRE_JAIL_PTY_READY_TO_WRITE;
+       }
 
-               // Deny setting any capabilities
-               CAP_SETFCAP,
+       // 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;
+               }
 
-               // Deny reading from syslog
-               CAP_SYSLOG,
+               // We are done writing for now
+               ctx->pty.stdout.flags &= ~PAKFIRE_JAIL_PTY_READY_TO_WRITE;
+       }
 
-               // Deny any admin actions (mount, sethostname, ...)
-               CAP_SYS_ADMIN,
+       return 0;
+}
 
-               // Deny rebooting the system
-               CAP_SYS_BOOT,
+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;
 
-               // Deny loading kernel modules
-               CAP_SYS_MODULE,
+       // Fetch file descriptors from context
+       const int pidfd  = ctx->pidfd;
 
-               // Deny setting nice level
-               CAP_SYS_NICE,
+       // Fetch the UNIX domain socket
+       const int socket_recv = pakfire_jail_get_pipe_to_read(jail, &ctx->socket);
 
-               // Deny access to /proc/kcore, /dev/mem, /dev/kmem
-               CAP_SYS_RAWIO,
+       // Timer
+       const int timerfd = pakfire_jail_create_timer(jail);
 
-               // Deny circumventing any resource limits
-               CAP_SYS_RESOURCE,
+       // 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 */
 
-               // Deny setting the system time
-               CAP_SYS_TIME,
+       // 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 */
 
-               // Deny playing with suspend
-               CAP_WAKE_ALARM,
+               // UNIX Domain Socket
+               { socket_recv, EPOLLIN },
 
-               0,
+               // Sentinel
+               { -1, 0 },
        };
 
-       DEBUG(jail->pakfire, "Dropping capabilities...\n");
+       // Setup epoll
+       epollfd = epoll_create1(0);
+       if (epollfd < 0) {
+               ERROR(jail->pakfire, "Could not initialize epoll(): %m\n");
+               r = 1;
+               goto ERROR;
+       }
 
-       size_t num_caps = 0;
-       int r;
+       // 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;
 
-       // Drop any capabilities
-       for (const int* cap = capabilities; *cap; cap++) {
-               r = prctl(PR_CAPBSET_DROP, *cap, 0, 0, 0);
-               if (r) {
-                       ERROR(jail->pakfire, "Could not drop capability %d: %m\n", *cap);
-                       return r;
+               // 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;
+
+                       ERROR(jail->pakfire, "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;
+
+                       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;
+
+                               if (e & (EPOLLOUT|EPOLLHUP))
+                                       ctx->pty.master.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;
+                               }
+
+                       // 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;
+                               }
+                       }
+
+                       // Check if there is any data to be read
+                       if (e & EPOLLIN) {
+                               // Handle any changes to the PIDFD
+                               if (fd == pidfd) {
+                                       // 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;
+                                       }
+
+                                       // 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) {
+                                       DEBUG(jail->pakfire, "Timer event received\n");
+
+                                       // Disarm the timer
+                                       r = read(timerfd, garbage, sizeof(garbage));
+                                       if (r < 1) {
+                                               ERROR(jail->pakfire, "Could not disarm timer: %m\n");
+                                               r = 1;
+                                               goto ERROR;
+                                       }
+
+                                       // Terminate the process if it hasn't already ended
+                                       if (!ended) {
+                                               DEBUG(jail->pakfire, "Terminating process...\n");
+
+                                               // Send SIGTERM to the process
+                                               r = pidfd_send_signal(pidfd, SIGKILL, NULL, 0);
+                                               if (r) {
+                                                       ERROR(jail->pakfire, "Could not kill process: %m\n");
+                                                       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 socket messages
+                               } else if (fd == socket_recv) {
+                                       // 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;
+                                               }
+                                       }
+
+                                       // Don't fall through to log processing
+                                       continue;
+
+                               } else {
+                                       DEBUG(jail->pakfire, "Received invalid file descriptor %d\n", fd);
+                                       continue;
+                               }
+
+                               // Handle log event
+                               r = pakfire_jail_handle_log(jail, ctx, priority, fd, buffer, callback, data);
+                               if (r)
+                                       goto ERROR;
+                       }
+
+                       // 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) {
+                                       ERROR(jail->pakfire, "Could not remove closed file-descriptor %d: %m\n", fd);
+                                       goto ERROR;
+                               }
+                       }
                }
+       }
+
+ERROR:
+       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;
+}
+
+int pakfire_jail_capture_stdout(struct pakfire* pakfire, void* data,
+               int priority, const char* line, size_t length) {
+       char** output = (char**)data;
+       int r;
 
-               num_caps++;
+       // Append everything from stdout to a buffer
+       if (output && priority == LOG_INFO) {
+               r = asprintf(output, "%s%s", (output && *output) ? *output : "", line);
+               if (r < 0)
+                       return 1;
+               return 0;
        }
 
-       // Fetch any capabilities
-       cap_t caps = cap_get_proc();
+       // Send everything else to the default logger
+       return pakfire_jail_default_log_callback(pakfire, NULL, priority, line, length);
+}
+
+// Capabilities
+
+// Logs all capabilities of the current process
+static int pakfire_jail_show_capabilities(struct pakfire_jail* jail) {
+       cap_t caps = NULL;
+       char* name = NULL;
+       cap_flag_value_t value_e;
+       cap_flag_value_t value_i;
+       cap_flag_value_t value_p;
+       int r;
+
+       // Fetch PID
+       pid_t pid = getpid();
+
+       // Fetch all capabilities
+       caps = cap_get_proc();
        if (!caps) {
-               ERROR(jail->pakfire, "Could not read capabilities: %m\n");
-               return 1;
+               ERROR(jail->pakfire, "Could not fetch capabilities: %m\n");
+               r = 1;
+               goto ERROR;
        }
 
-       /*
-               Set inheritable capabilities
+       DEBUG(jail->pakfire, "Capabilities of PID %d:\n", pid);
 
-               This ensures that no processes will be able to gain any of the listed
-               capabilities again.
-       */
-       r = cap_set_flag(caps, CAP_INHERITABLE, num_caps, capabilities, CAP_CLEAR);
-       if (r) {
-               ERROR(jail->pakfire, "cap_set_flag() failed: %m\n");
+       // Iterate over all capabilities
+       for (unsigned int cap = 0; cap_valid(cap); cap++) {
+               name = cap_to_name(cap);
+
+               // Fetch effective value
+               r = cap_get_flag(caps, cap, CAP_EFFECTIVE, &value_e);
+               if (r)
+                       goto ERROR;
+
+               // Fetch inheritable value
+               r = cap_get_flag(caps, cap, CAP_INHERITABLE, &value_i);
+               if (r)
+                       goto ERROR;
+
+               // Fetch permitted value
+               r = cap_get_flag(caps, cap, CAP_PERMITTED, &value_p);
+               if (r)
+                       goto ERROR;
+
+               DEBUG(jail->pakfire,
+                       "  %-24s : %c%c%c\n",
+                       name,
+                       (value_e == CAP_SET) ? 'e' : '-',
+                       (value_i == CAP_SET) ? 'i' : '-',
+                       (value_p == CAP_SET) ? 'p' : '-'
+               );
+
+               // Free name
+               cap_free(name);
+               name = NULL;
+       }
+
+       // Success
+       r = 0;
+
+ERROR:
+       if (name)
+               cap_free(name);
+       if (caps)
+               cap_free(caps);
+
+       return r;
+}
+
+static int pakfire_jail_set_capabilities(struct pakfire_jail* jail) {
+       cap_t caps = NULL;
+       char* name = NULL;
+       int r;
+
+       // Fetch capabilities
+       caps = cap_get_proc();
+       if (!caps) {
+               ERROR(jail->pakfire, "Could not read capabilities: %m\n");
+               r = 1;
                goto ERROR;
        }
 
-       // Restore capabilities
+       // Walk through all capabilities
+       for (cap_value_t cap = 0; cap_valid(cap); cap++) {
+               cap_value_t _caps[] = { cap };
+
+               // Fetch the name of the capability
+               name = cap_to_name(cap);
+
+               r = cap_set_flag(caps, CAP_EFFECTIVE, 1, _caps, CAP_SET);
+               if (r) {
+                       ERROR(jail->pakfire, "Could not set %s: %m\n", name);
+                       goto ERROR;
+               }
+
+               r = cap_set_flag(caps, CAP_INHERITABLE, 1, _caps, CAP_SET);
+               if (r) {
+                       ERROR(jail->pakfire, "Could not set %s: %m\n", name);
+                       goto ERROR;
+               }
+
+               r = cap_set_flag(caps, CAP_PERMITTED, 1, _caps, CAP_SET);
+               if (r) {
+                       ERROR(jail->pakfire, "Could not set %s: %m\n", name);
+                       goto ERROR;
+               }
+
+               // Free name
+               cap_free(name);
+               name = NULL;
+       }
+
+       // Restore all capabilities
        r = cap_set_proc(caps);
        if (r) {
-               ERROR(jail->pakfire, "Could not restore capabilities: %m\n");
+               ERROR(jail->pakfire, "Restoring capabilities failed: %m\n");
                goto ERROR;
        }
 
+       // Add all capabilities to the ambient set
+       for (unsigned int cap = 0; cap_valid(cap); cap++) {
+               name = cap_to_name(cap);
+
+               // Raise the capability
+               r = prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, cap, 0, 0);
+               if (r) {
+                       ERROR(jail->pakfire, "Could not set ambient capability %s: %m\n", name);
+                       goto ERROR;
+               }
+
+               // Free name
+               cap_free(name);
+               name = NULL;
+       }
+
+       // Success
+       r = 0;
+
 ERROR:
+       if (name)
+               cap_free(name);
        if (caps)
                cap_free(caps);
 
@@ -925,16 +1608,16 @@ PAKFIRE_EXPORT int pakfire_jail_bind(struct pakfire_jail* jail,
 
        // Copy source
        r = pakfire_string_set(mp->source, source);
-       if (r < 0) {
+       if (r) {
                ERROR(jail->pakfire, "Could not copy source: %m\n");
-               return 1;
+               return r;
        }
 
        // Copy target
        r = pakfire_string_set(mp->target, target);
-       if (r < 0) {
+       if (r) {
                ERROR(jail->pakfire, "Could not copy target: %m\n");
-               return 1;
+               return r;
        }
 
        // Copy flags
@@ -946,18 +1629,68 @@ PAKFIRE_EXPORT int pakfire_jail_bind(struct pakfire_jail* jail,
        return 0;
 }
 
+static int pakfire_jail_mount_networking(struct pakfire_jail* jail) {
+       int r;
+
+       const char* paths[] = {
+               "/etc/hosts",
+               "/etc/resolv.conf",
+               NULL,
+       };
+
+       // Bind-mount all paths read-only
+       for (const char** path = paths; *path; path++) {
+               r = pakfire_bind(jail->pakfire, *path, NULL, MS_RDONLY);
+               if (r) {
+                       switch (errno) {
+                               // Ignore if we don't have permission
+                               case EPERM:
+                                       continue;
+
+                               default:
+                                       break;
+                       }
+                       return r;
+               }
+       }
+
+       return 0;
+}
+
 /*
        Mounts everything that we require in the new namespace
 */
-static int pakfire_jail_mount(struct pakfire_jail* jail) {
+static int pakfire_jail_mount(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx) {
        struct pakfire_jail_mountpoint* mp = NULL;
+       int flags = 0;
        int r;
 
+       // Enable loop devices
+       if (pakfire_jail_exec_has_flag(ctx, PAKFIRE_JAIL_HAS_LOOP_DEVICES))
+               flags |= PAKFIRE_MOUNT_LOOP_DEVICES;
+
        // Mount all default stuff
-       r = pakfire_mount_all(jail->pakfire);
+       r = pakfire_mount_all(jail->pakfire, PAKFIRE_MNTNS_OUTER, flags);
+       if (r)
+               return r;
+
+       // Populate /dev
+       r = pakfire_populate_dev(jail->pakfire, flags);
+       if (r)
+               return r;
+
+       // Mount the interpreter (if needed)
+       r = pakfire_mount_interpreter(jail->pakfire);
        if (r)
                return r;
 
+       // Mount networking stuff
+       if (pakfire_jail_exec_has_flag(ctx, PAKFIRE_JAIL_HAS_NETWORKING)) {
+               r = pakfire_jail_mount_networking(jail);
+               if (r)
+                       return r;
+       }
+
        // Mount all custom stuff
        for (unsigned int i = 0; i < jail->num_mountpoints; i++) {
                // Fetch mountpoint
@@ -969,20 +1702,80 @@ static int pakfire_jail_mount(struct pakfire_jail* jail) {
                        return r;
        }
 
-       // Log all mountpoints
-       pakfire_mount_list(jail->pakfire);
-
        return 0;
 }
 
-// UID/GID Mapping
+// Networking
+
+static int pakfire_jail_setup_loopback(struct pakfire_jail* jail) {
+       struct nl_sock* nl = NULL;
+       struct nl_cache* cache = NULL;
+       struct rtnl_link* link = NULL;
+       struct rtnl_link* change = NULL;
+       int r;
+
+       DEBUG(jail->pakfire, "Setting up loopback...\n");
+
+       // Allocate a netlink socket
+       nl = nl_socket_alloc();
+       if (!nl) {
+               ERROR(jail->pakfire, "Could not allocate a netlink socket: %m\n");
+               r = 1;
+               goto ERROR;
+       }
+
+       // Connect the socket
+       r = nl_connect(nl, NETLINK_ROUTE);
+       if (r) {
+               ERROR(jail->pakfire, "Could not connect netlink socket: %s\n", nl_geterror(r));
+               goto ERROR;
+       }
+
+       // Allocate the netlink cache
+       r = rtnl_link_alloc_cache(nl, AF_UNSPEC, &cache);
+       if (r < 0) {
+               ERROR(jail->pakfire, "Unable to allocate netlink cache: %s\n", nl_geterror(r));
+               goto ERROR;
+       }
+
+       // Fetch loopback interface
+       link = rtnl_link_get_by_name(cache, "lo");
+       if (!link) {
+               ERROR(jail->pakfire, "Could not find lo interface. Ignoring.\n");
+               r = 0;
+               goto ERROR;
+       }
+
+       // Allocate a new link
+       change = rtnl_link_alloc();
+       if (!change) {
+               ERROR(jail->pakfire, "Could not allocate change link\n");
+               r = 1;
+               goto ERROR;
+       }
+
+       // Set the link to UP
+       rtnl_link_set_flags(change, IFF_UP);
+
+       // Apply any changes
+       r = rtnl_link_change(nl, link, change, 0);
+       if (r) {
+               ERROR(jail->pakfire, "Unable to activate loopback: %s\n", nl_geterror(r));
+               goto ERROR;
+       }
+
+       // Success
+       r = 0;
 
-static int pakfire_jail_write_uidgid_mapping(struct pakfire_jail* jail,
-               const char* path, const struct pakfire_subid* subid) {
-       return pakfire_file_write(jail->pakfire, path, 0, 0, 0,
-               "%d %u %lu\n", 0, subid->id, subid->length);
+ERROR:
+       if (nl)
+               nl_socket_free(nl);
+
+       return r;
 }
 
+// UID/GID Mapping
+
 static int pakfire_jail_setup_uid_mapping(struct pakfire_jail* jail, pid_t pid) {
        char path[PATH_MAX];
        int r;
@@ -991,20 +1784,40 @@ static int pakfire_jail_setup_uid_mapping(struct pakfire_jail* jail, pid_t pid)
        if (pakfire_on_root(jail->pakfire))
                return 0;
 
+       // Make path
+       r = pakfire_string_format(path, "/proc/%d/uid_map", pid);
+       if (r)
+               return r;
+
+       // Fetch UID
+       const uid_t uid = pakfire_uid(jail->pakfire);
+
        // Fetch SUBUID
        const struct pakfire_subid* subuid = pakfire_subuid(jail->pakfire);
        if (!subuid)
                return 1;
 
-       // Make path
-       r = pakfire_string_format(path, "/proc/%d/uid_map", pid);
-       if (r < 0)
-               return 1;
+       /* When running as root, we will map the entire range.
+
+          When running as a non-privileged user, we will map the root user inside the jail
+          to the user's UID outside of the jail, and we will map the rest starting from one.
+       */
+
+       // Running as root
+       if (uid == 0) {
+               r = pakfire_file_write(jail->pakfire, path, 0, 0, 0,
+                       "0 %lu %lu\n", subuid->id, subuid->length);
+       } else {
+               r = pakfire_file_write(jail->pakfire, path, 0, 0, 0,
+                       "0 %lu 1\n1 %lu %lu\n", uid, subuid->id, subuid->length);
+       }
 
-       DEBUG(jail->pakfire, "Mapping UID range (%u - %lu)\n",
-               subuid->id, subuid->id + subuid->length);
+       if (r) {
+               ERROR(jail->pakfire, "Could not map UIDs: %m\n");
+               return r;
+       }
 
-       return pakfire_jail_write_uidgid_mapping(jail, path, subuid);
+       return r;
 }
 
 static int pakfire_jail_setup_gid_mapping(struct pakfire_jail* jail, pid_t pid) {
@@ -1015,6 +1828,9 @@ static int pakfire_jail_setup_gid_mapping(struct pakfire_jail* jail, pid_t pid)
        if (pakfire_on_root(jail->pakfire))
                return 0;
 
+       // Fetch GID
+       const gid_t gid = pakfire_gid(jail->pakfire);
+
        // Fetch SUBGID
        const struct pakfire_subid* subgid = pakfire_subgid(jail->pakfire);
        if (!subgid)
@@ -1022,49 +1838,41 @@ static int pakfire_jail_setup_gid_mapping(struct pakfire_jail* jail, pid_t pid)
 
        // Make path
        r = pakfire_string_format(path, "/proc/%d/gid_map", pid);
-       if (r < 0)
-               return 1;
+       if (r)
+               return r;
+
+       // Running as root
+       if (gid == 0) {
+               r = pakfire_file_write(jail->pakfire, path, 0, 0, 0,
+                       "0 %lu %lu\n", subgid->id, subgid->length);
+       } else {
+               r = pakfire_file_write(jail->pakfire, path, 0, 0, 0,
+                       "0 %lu 1\n1 %lu %lu\n", gid, subgid->id, subgid->length);
+       }
 
-       DEBUG(jail->pakfire, "Mapping GID range (%u - %lu)\n",
-               subgid->id, subgid->id + subgid->length);
+       if (r) {
+               ERROR(jail->pakfire, "Could not map GIDs: %m\n");
+               return r;
+       }
 
-       return pakfire_jail_write_uidgid_mapping(jail, path, subgid);
+       return r;
 }
 
 static int pakfire_jail_setgroups(struct pakfire_jail* jail, pid_t pid) {
        char path[PATH_MAX];
-       int r = 1;
+       int r;
 
        // Make path
        r = pakfire_string_format(path, "/proc/%d/setgroups", pid);
-       if (r < 0)
-               return 1;
-
-       // Open file for writing
-       FILE* f = fopen(path, "w");
-       if (!f) {
-               ERROR(jail->pakfire, "Could not open %s for writing: %m\n", path);
-               goto ERROR;
-       }
-
-       // Write content
-       int bytes_written = fprintf(f, "deny\n");
-       if (bytes_written <= 0) {
-               ERROR(jail->pakfire, "Could not write to %s: %m\n", path);
-               goto ERROR;
-       }
+       if (r)
+               return r;
 
-       r = fclose(f);
-       f = NULL;
+       r = pakfire_file_write(jail->pakfire, path, 0, 0, 0, "deny\n");
        if (r) {
-               ERROR(jail->pakfire, "Could not close %s: %m\n", path);
-               goto ERROR;
+               CTX_ERROR(jail->ctx, "Could not set setgroups to deny: %s\n", strerror(errno));
+               r = -errno;
        }
 
-ERROR:
-       if (f)
-               fclose(f);
-
        return r;
 }
 
@@ -1075,10 +1883,10 @@ static int pakfire_jail_send_signal(struct pakfire_jail* jail, int fd) {
        DEBUG(jail->pakfire, "Sending signal...\n");
 
        // Write to the file descriptor
-       ssize_t bytes_written = write(fd, &val, sizeof(val));
-       if (bytes_written < 0 || (size_t)bytes_written < sizeof(val)) {
-               ERROR(jail->pakfire, "Could not send signal: %m\n");
-               r = 1;
+       r = eventfd_write(fd, val);
+       if (r < 0) {
+               ERROR(jail->pakfire, "Could not send signal: %s\n", strerror(errno));
+               r = -errno;
        }
 
        // Close the file descriptor
@@ -1093,10 +1901,10 @@ static int pakfire_jail_wait_for_signal(struct pakfire_jail* jail, int fd) {
 
        DEBUG(jail->pakfire, "Waiting for signal...\n");
 
-       ssize_t bytes_read = read(fd, &val, sizeof(val));
-       if (bytes_read < 0 || (size_t)bytes_read < sizeof(val)) {
-               ERROR(jail->pakfire, "Error waiting for signal: %m\n");
-               r = 1;
+       r = eventfd_read(fd, &val);
+       if (r < 0) {
+               ERROR(jail->pakfire, "Error waiting for signal: %s\n", strerror(errno));
+               r = -errno;
        }
 
        // Close the file descriptor
@@ -1111,13 +1919,13 @@ static int pakfire_jail_wait_for_signal(struct pakfire_jail* jail, int fd) {
 static int pakfire_jail_parent(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx) {
        int r;
 
-       // Write "deny" to /proc/PID/setgroups
-       r = pakfire_jail_setgroups(jail, ctx->pid);
+       // Setup UID mapping
+       r = pakfire_jail_setup_uid_mapping(jail, ctx->pid);
        if (r)
                return r;
 
-       // Setup UID mapping
-       r = pakfire_jail_setup_uid_mapping(jail, ctx->pid);
+       // Write "deny" to /proc/PID/setgroups
+       r = pakfire_jail_setgroups(jail, ctx->pid);
        if (r)
                return r;
 
@@ -1137,35 +1945,137 @@ static int pakfire_jail_parent(struct pakfire_jail* jail, struct pakfire_jail_ex
        return 0;
 }
 
-static int pakfire_jail_child(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx,
-               const char* argv[]) {
+static int pakfire_jail_switch_root(struct pakfire_jail* jail, const char* root) {
        int r;
 
-       // Redirect any logging to our log pipe
-       pakfire_set_log_callback(jail->pakfire, pakfire_jail_log, &ctx->pipes);
+       // Change to the new root
+       r = chdir(root);
+       if (r) {
+               ERROR(jail->pakfire, "chdir(%s) failed: %m\n", root);
+               return r;
+       }
 
-       // Die with parent
-       r = prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
+       // Switch Root!
+       r = pivot_root(".", ".");
        if (r) {
-               ERROR(jail->pakfire, "Could not configure to die with parent: %m\n");
-               return 126;
+               ERROR(jail->pakfire, "Failed changing into the new root directory %s: %m\n", root);
+               return r;
+       }
+
+       // Umount the old root
+       r = umount2(".", MNT_DETACH);
+       if (r) {
+               ERROR(jail->pakfire, "Could not umount the old root filesystem: %m\n");
+               return r;
+       }
+
+       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;
+
+       // 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();
 
        DEBUG(jail->pakfire, "Launched child process in jail with PID %d\n", pid);
 
-       // Log argv
-       for (unsigned int i = 0; argv[i]; i++)
-               DEBUG(jail->pakfire, "  argv[%d] = %s\n", i, argv[i]);
-
        // Wait for the parent to finish initialization
        r = pakfire_jail_wait_for_signal(jail, ctx->completed_fd);
        if (r)
                return r;
 
-       // Perform further initialization
+       // 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) {
+               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) {
+               ERROR(jail->pakfire, "Could not set PR_SET_KEEPCAPS: %m\n");
+               return 126;
+       }
 
        // Fetch UID/GID
        uid_t uid = getuid();
@@ -1175,38 +2085,60 @@ static int pakfire_jail_child(struct pakfire_jail* jail, struct pakfire_jail_exe
        uid_t euid = geteuid();
        gid_t egid = getegid();
 
-       DEBUG(jail->pakfire, "  UID: %d (effective %d)\n", uid, euid);
-       DEBUG(jail->pakfire, "  GID: %d (effective %d)\n", gid, egid);
+       DEBUG(jail->pakfire, "  UID: %u (effective %u)\n", uid, euid);
+       DEBUG(jail->pakfire, "  GID: %u (effective %u)\n", gid, egid);
+
+       // Log all mountpoints
+       pakfire_mount_list(jail->ctx);
+
+       // Fail if we are not PID 1
+       if (pid != 1) {
+               CTX_ERROR(jail->ctx, "Child process is not PID 1\n");
+               return 126;
+       }
 
-       // Check if we are (effectively running as root)
+       // Fail if we are not running as root
        if (uid || gid || euid || egid) {
                ERROR(jail->pakfire, "Child process is not running as root\n");
                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_arch(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);
+               r = pakfire_jail_mount(jail, ctx);
                if (r)
                        return r;
 
-               // Call chroot()
-               r = chroot(root);
-               if (r) {
-                       ERROR(jail->pakfire, "chroot() to %s failed: %m\n", root);
-                       return 1;
-               }
-
-               // Change directory to /
-               r = chdir("/");
-               if (r) {
-                       ERROR(jail->pakfire, "chdir() after chroot() failed: %m\n");
-                       return 1;
-               }
+               // chroot()
+               r = pakfire_jail_switch_root(jail, root);
+               if (r)
+                       return r;
        }
 
        // Set personality
@@ -1219,6 +2151,13 @@ static int pakfire_jail_child(struct pakfire_jail* jail, struct pakfire_jail_exe
                }
        }
 
+       // Setup networking
+       if (!pakfire_jail_exec_has_flag(ctx, PAKFIRE_JAIL_HAS_NETWORKING)) {
+               r = pakfire_jail_setup_loopback(jail);
+               if (r)
+                       return 1;
+       }
+
        // Set nice level
        if (jail->nice) {
                DEBUG(jail->pakfire, "Setting nice level to %d\n", jail->nice);
@@ -1230,6 +2169,39 @@ static int pakfire_jail_child(struct pakfire_jail* jail, struct pakfire_jail_exe
                }
        }
 
+       // 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;
+       }
+
+       // 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;
+       }
+
+       // 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);
+
        // Close other end of log pipes
        close(ctx->pipes.log_INFO[0]);
        close(ctx->pipes.log_ERROR[0]);
@@ -1237,36 +2209,18 @@ static int pakfire_jail_child(struct pakfire_jail* jail, struct pakfire_jail_exe
        close(ctx->pipes.log_DEBUG[0]);
 #endif /* ENABLE_DEBUG */
 
-       // Connect standard output and error
-       if (ctx->pipes.stdout[1] && ctx->pipes.stderr[1]) {
-               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]);
-
-                       return 1;
-               }
-
-               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]);
-
-                       return 1;
-               }
-
-               // Close the pipe (as we have moved the original file descriptors)
-               pakfire_jail_close_pipe(jail, ctx->pipes.stdout);
-               pakfire_jail_close_pipe(jail, ctx->pipes.stderr);
-       }
-
        // Reset open file limit (http://0pointer.net/blog/file-descriptor-limits.html)
        r = pakfire_rlimit_reset_nofile(jail->pakfire);
        if (r)
                return r;
 
-       // Drop capabilities
-       r = pakfire_jail_drop_capabilities(jail);
+       // Set capabilities
+       r = pakfire_jail_set_capabilities(jail);
+       if (r)
+               return r;
+
+       // Show capabilities
+       r = pakfire_jail_show_capabilities(jail);
        if (r)
                return r;
 
@@ -1275,19 +2229,32 @@ static int pakfire_jail_child(struct pakfire_jail* jail, struct pakfire_jail_exe
        if (r)
                return r;
 
+       DEBUG(jail->pakfire, "Child process initialization done\n");
+       DEBUG(jail->pakfire, "Launching command:\n");
+
+       // Log argv
+       for (unsigned int i = 0; argv[i]; i++)
+               DEBUG(jail->pakfire, "  argv[%u] = %s\n", i, argv[i]);
+
        // exec() command
        r = execvpe(argv[0], (char**)argv, jail->env);
-       if (r < 0)
-               ERROR(jail->pakfire, "Could not execve(): %m\n");
+       if (r < 0) {
+               // Translate errno into regular exit code
+               switch (errno) {
+                       case ENOENT:
+                               // Ignore if the command doesn't exist
+                               if (ctx->flags & PAKFIRE_JAIL_NOENT_OK)
+                                       r = 0;
+                               else
+                                       r = 127;
 
-       // Translate errno into regular exit code
-       switch (errno) {
-               case ENOENT:
-                       r = 127;
-                       break;
+                               break;
 
-               default:
-                       r = 1;
+                       default:
+                               r = 1;
+               }
+
+               ERROR(jail->pakfire, "Could not execve(%s): %m\n", argv[0]);
        }
 
        // We should not get here
@@ -1296,7 +2263,10 @@ static int pakfire_jail_child(struct pakfire_jail* jail, struct pakfire_jail_exe
 
 // Run a command in the jail
 static int __pakfire_jail_exec(struct pakfire_jail* jail, const char* argv[],
-               const int interactive) {
+               const int interactive,
+               pakfire_jail_communicate_in  communicate_in,
+               pakfire_jail_communicate_out communicate_out,
+               void* data, int flags) {
        int exit = -1;
        int r;
 
@@ -1308,14 +2278,46 @@ static int __pakfire_jail_exec(struct pakfire_jail* jail, const char* argv[],
 
        // Initialize context for this call
        struct pakfire_jail_exec ctx = {
+               .flags = flags,
+
+               .socket = { -1, -1 },
+
                .pipes = {
-                       .stdout   = { 0, 0 },
-                       .stderr   = { 0, 0 },
+                       .log_INFO  = { -1, -1 },
+                       .log_ERROR = { -1, -1 },
+#ifdef ENABLE_DEBUG
+                       .log_DEBUG = { -1, -1 },
+#endif /* ENABLE_DEBUG */
+               },
+
+               .communicate = {
+                       .in   = communicate_in,
+                       .out  = communicate_out,
+                       .data = data,
+               },
+
+               .pidfd = -1,
+
+               // PTY
+               .pty = {
+                       .master = {
+                               .fd = -1,
+                       },
+                       .stdin = {
+                               .fd = -1,
+                       },
+                       .stdout = {
+                               .fd = -1,
+                       },
                },
        };
 
        DEBUG(jail->pakfire, "Executing jail...\n");
 
+       // Enable networking in interactive mode
+       if (interactive)
+               ctx.flags |= PAKFIRE_JAIL_HAS_NETWORKING;
+
        /*
                Setup a file descriptor which can be used to notify the client that the parent
                has completed configuration.
@@ -1326,17 +2328,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) {
-               // 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
@@ -1364,6 +2361,7 @@ static int __pakfire_jail_exec(struct pakfire_jail* jail, const char* argv[],
                        CLONE_NEWIPC |
                        CLONE_NEWNS |
                        CLONE_NEWPID |
+                       CLONE_NEWTIME |
                        CLONE_NEWUSER |
                        CLONE_NEWUTS |
                        CLONE_PIDFD,
@@ -1389,6 +2387,11 @@ static int __pakfire_jail_exec(struct pakfire_jail* jail, const char* argv[],
                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 this process
        ctx.pid = clone3(&args, sizeof(args));
        if (ctx.pid < 0) {
@@ -1424,8 +2427,12 @@ static int __pakfire_jail_exec(struct pakfire_jail* jail, const char* argv[],
                        break;
 
                case CLD_KILLED:
-               case CLD_DUMPED:
                        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
@@ -1438,52 +2445,38 @@ ERROR:
        // Destroy the temporary cgroup (if any)
        if (ctx.cgroup) {
                // Read cgroup stats
-               r = pakfire_cgroup_stat(ctx.cgroup, &ctx.cgroup_stats);
-               if (r) {
-                       ERROR(jail->pakfire, "Could not read cgroup stats: %m\n");
-               } else {
-                       pakfire_cgroup_stat_dump(ctx.cgroup, &ctx.cgroup_stats);
-               }
-
+               pakfire_cgroup_stat(ctx.cgroup, &ctx.cgroup_stats);
+               pakfire_cgroup_stat_dump(ctx.cgroup, &ctx.cgroup_stats);
                pakfire_cgroup_destroy(ctx.cgroup);
                pakfire_cgroup_unref(ctx.cgroup);
        }
 
        // Close any file descriptors
-       pakfire_jail_close_pipe(jail, ctx.pipes.stdout);
-       pakfire_jail_close_pipe(jail, ctx.pipes.stderr);
-       if (ctx.pidfd)
+       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 */
+       pakfire_jail_close_pipe(jail, ctx.socket);
 
        return exit;
 }
 
-PAKFIRE_EXPORT int pakfire_jail_exec(struct pakfire_jail* jail,
-               const char* argv[], char** output) {
-       int r;
-
-       // Store logging callback
-       pakfire_jail_log_callback log_callback = jail->log_callback;
-       void* log_data = jail->log_data;
-
-       // Capture output if requested by user
-       if (output)
-               pakfire_jail_set_log_callback(jail, pakfire_jail_capture_stdout, output);
-
-       // Run exec()
-       r = __pakfire_jail_exec(jail, argv, 0);
-
-       // Restore log callback
-       pakfire_jail_set_log_callback(jail, log_callback, log_data);
-
-       return r;
+PAKFIRE_EXPORT int pakfire_jail_exec(
+               struct pakfire_jail* jail,
+               const char* argv[],
+               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);
 }
 
 static int pakfire_jail_exec_interactive(
-               struct pakfire_jail* jail, const char* argv[]) {
+               struct pakfire_jail* jail, const char* argv[], int flags) {
        int r;
 
        // Setup interactive stuff
@@ -1491,55 +2484,53 @@ static int pakfire_jail_exec_interactive(
        if (r)
                return r;
 
-       return __pakfire_jail_exec(jail, argv, 1);
+       return __pakfire_jail_exec(jail, argv, 1, NULL, NULL, NULL, flags);
 }
 
-PAKFIRE_EXPORT int pakfire_jail_exec_script(struct pakfire_jail* jail,
-               const char* script, const size_t size, const char* args[], char** output) {
+int pakfire_jail_exec_script(struct pakfire_jail* jail,
+               const char* script,
+               const size_t size,
+               const char* args[],
+               pakfire_jail_communicate_in  callback_in,
+               pakfire_jail_communicate_out callback_out,
+               void* data) {
        char path[PATH_MAX];
        const char** argv = NULL;
+       FILE* f = NULL;
        int r;
 
        const char* root = pakfire_get_path(jail->pakfire);
 
        // Write the scriptlet to disk
-       r = pakfire_path_join(path, root, "pakfire-script.XXXXXX");
-       if (r < 0)
+       r = pakfire_path_append(path, root, PAKFIRE_TMP_DIR "/pakfire-script.XXXXXX");
+       if (r)
                goto ERROR;
 
-       // Open a temporary file
-       int fd = mkstemp(path);
-       if (fd < 0) {
-               ERROR(jail->pakfire, "Could not open a temporary file: %m\n");
-               r = 1;
+       // Create a temporary file
+       f = pakfire_mktemp(path, 0700);
+       if (!f) {
+               ERROR(jail->pakfire, "Could not create temporary file: %m\n");
                goto ERROR;
        }
 
        DEBUG(jail->pakfire, "Writing script to %s:\n%.*s\n", path, (int)size, script);
 
        // Write data
-       ssize_t bytes_written = write(fd, script, size);
-       if (bytes_written < (ssize_t)size) {
+       r = fprintf(f, "%s", script);
+       if (r < 0) {
                ERROR(jail->pakfire, "Could not write script to file %s: %m\n", path);
-               r = 1;
-               goto ERROR;
-       }
-
-       // Make the script executable
-       r = fchmod(fd, S_IRUSR|S_IWUSR|S_IXUSR);
-       if (r) {
-               ERROR(jail->pakfire, "Could not set executable permissions on %s: %m\n", path);
                goto ERROR;
        }
 
        // Close file
-       r = close(fd);
+       r = fclose(f);
        if (r) {
                ERROR(jail->pakfire, "Could not close script file %s: %m\n", path);
-               r = 1;
                goto ERROR;
        }
 
+       f = NULL;
+
        // Count how many arguments were passed
        unsigned int argc = 1;
        if (args) {
@@ -1561,11 +2552,13 @@ PAKFIRE_EXPORT int pakfire_jail_exec_script(struct pakfire_jail* jail,
                argv[i] = args[i-1];
 
        // Run the script
-       r = pakfire_jail_exec(jail, argv, output);
+       r = pakfire_jail_exec(jail, argv, callback_in, callback_out, data, 0);
 
 ERROR:
        if (argv)
                free(argv);
+       if (f)
+               fclose(f);
 
        // Remove script from disk
        if (*path)
@@ -1583,12 +2576,12 @@ int pakfire_jail_run(struct pakfire* pakfire, const char* argv[], int flags, cha
        int r;
 
        // Create a new jail
-       r = pakfire_jail_create(&jail, pakfire, flags);
+       r = pakfire_jail_create(&jail, pakfire);
        if (r)
                goto ERROR;
 
        // Execute the command
-       r = pakfire_jail_exec(jail, argv, output);
+       r = pakfire_jail_exec(jail, argv, NULL, pakfire_jail_capture_stdout, output, 0);
 
 ERROR:
        if (jail)
@@ -1598,17 +2591,17 @@ ERROR:
 }
 
 int pakfire_jail_run_script(struct pakfire* pakfire,
-               const char* script, const size_t length, const char* argv[], int flags, char** output) {
+               const char* script, const size_t length, const char* argv[], int flags) {
        struct pakfire_jail* jail = NULL;
        int r;
 
        // Create a new jail
-       r = pakfire_jail_create(&jail, pakfire, flags);
+       r = pakfire_jail_create(&jail, pakfire);
        if (r)
                goto ERROR;
 
        // Execute the command
-       r = pakfire_jail_exec_script(jail, script, length, argv, output);
+       r = pakfire_jail_exec_script(jail, script, length, argv, NULL, NULL, NULL);
 
 ERROR:
        if (jail)
@@ -1618,35 +2611,56 @@ ERROR:
 }
 
 int pakfire_jail_shell(struct pakfire_jail* jail) {
+       int r;
+
        const char* argv[] = {
                "/bin/bash", "--login", NULL,
        };
 
        // Execute /bin/bash
-       return pakfire_jail_exec_interactive(jail, argv);
+       r = pakfire_jail_exec_interactive(jail, argv, 0);
+
+       // Raise any errors
+       if (r < 0)
+               return r;
+
+       // Ignore any return codes from the shell
+       return 0;
 }
 
-int pakfire_jail_ldconfig(struct pakfire* pakfire) {
+static int pakfire_jail_run_if_possible(struct pakfire* pakfire, const char** argv) {
        char path[PATH_MAX];
+       int r;
 
-       const char* ldconfig = "/sbin/ldconfig";
-
-       // Check if ldconfig exists before calling it to avoid overhead
-       int r = pakfire_make_path(pakfire, path, ldconfig);
-       if (r < 0)
-               return 1;
+       r = pakfire_path(pakfire, path, "%s", *argv);
+       if (r)
+               return r;
 
-       // Check if ldconfig is executable
+       // Check if the file is executable
        r = access(path, X_OK);
        if (r) {
-               DEBUG(pakfire, "%s is not executable. Skipping...\n", ldconfig);
+               DEBUG(pakfire, "%s is not executable. Skipping...\n", *argv);
                return 0;
        }
 
+       return pakfire_jail_run(pakfire, argv, 0, NULL);
+}
+
+int pakfire_jail_ldconfig(struct pakfire* pakfire) {
        const char* argv[] = {
-               ldconfig, NULL,
+               "/sbin/ldconfig",
+               NULL,
        };
 
-       // Run ldconfig
-       return pakfire_jail_run(pakfire, argv, 0, NULL);
+       return pakfire_jail_run_if_possible(pakfire, argv);
+}
+
+int pakfire_jail_run_systemd_tmpfiles(struct pakfire* pakfire) {
+       const char* argv[] = {
+               "/usr/bin/systemd-tmpfiles",
+               "--create",
+               NULL,
+       };
+
+       return pakfire_jail_run_if_possible(pakfire, argv);
 }