]> git.ipfire.org Git - people/ms/pakfire.git/commitdiff
jail: Implement PTY forwarding
authorMichael Tremer <michael.tremer@ipfire.org>
Sat, 16 Dec 2023 13:07:50 +0000 (13:07 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Sat, 16 Dec 2023 13:07:50 +0000 (13:07 +0000)
This replaces the previous pipe forwarding with a new controlling PTY
that is set up for each jail.

Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
src/libpakfire/jail.c

index c26b3d09bc3a7e073c7b488f9ab528225664e863..5f3884445c04117f8e3365b15983f6d536e393ed 100644 (file)
@@ -144,10 +144,6 @@ struct pakfire_jail_exec {
 
        // Log pipes
        struct pakfire_jail_pipes {
-               int stdin[2];
-               int stdout[2];
-               int stderr[2];
-
                // Logging
                int log_INFO[2];
                int log_ERROR[2];
@@ -197,7 +193,7 @@ struct pakfire_jail_exec {
                // Standard Input
                struct pakfire_jail_pty_stdio {
                        int fd;
-                       char buffer[LINE_MAX];
+                       struct pakfire_log_buffer buffer;
                        struct termios attrs;
                        int fdflags;
                        enum pakfire_jail_pty_flags flags;
@@ -669,6 +665,7 @@ 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;
@@ -706,6 +703,7 @@ static int pakfire_jail_stream_stdin(struct pakfire_jail* jail,
 
        return r;
 }
+#endif
 
 static int pakfire_jail_recv_fd(struct pakfire_jail* jail, int socket, int* fd) {
        const size_t payload_length = sizeof(fd);
@@ -993,6 +991,132 @@ static int pakfire_jail_setup_pty_forwarding(struct pakfire_jail* jail,
        return 0;
 }
 
+static int pakfire_jail_fill_buffer(struct pakfire_jail* jail, int fd, struct pakfire_log_buffer* buffer) {
+       int r;
+
+       // Skip this if there is not space left in the buffer
+       if (buffer->used >= sizeof(buffer->data))
+               return 0;
+
+       // Fill the buffer
+       r = read(fd, buffer->data + buffer->used, sizeof(buffer->data) - buffer->used);
+
+       // Handle errors
+       if (r < 0) {
+               switch (errno) {
+                       case EAGAIN:
+                       case EIO:
+                               break;
+
+                       default:
+                               return -errno;
+               }
+
+       // EOF
+       } else if (r == 0) {
+               // XXX What to do here?
+
+       // Successful read
+       } else {
+               buffer->used += r;
+       }
+
+       return 0;
+}
+
+static int pakfire_jail_drain_buffer(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;
+}
+
+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];
@@ -1000,9 +1124,6 @@ static int pakfire_jail_wait(struct pakfire_jail* jail, struct pakfire_jail_exec
        int r = 0;
 
        // 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);
        const int pidfd  = ctx->pidfd;
 
        // Fetch the UNIX domain socket
@@ -1023,11 +1144,6 @@ static int pakfire_jail_wait(struct pakfire_jail* jail, struct pakfire_jail_exec
                const int fd;
                const int events;
        } fds[] = {
-               // Standard input/output
-               { stdin,  EPOLLOUT },
-               { stdout, EPOLLIN },
-               { stderr, EPOLLIN },
-
                // Timer
                { timerfd, EPOLLIN },
 
@@ -1093,6 +1209,46 @@ static int pakfire_jail_wait(struct pakfire_jail* jail, struct pakfire_jail_exec
                        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
@@ -1157,33 +1313,6 @@ static int pakfire_jail_wait(struct pakfire_jail* jail, struct pakfire_jail_exec
                                        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;
-                                       }
-
                                // Handle socket messages
                                } else if (fd == socket_recv) {
                                        // Receive the passed FD
@@ -1214,24 +1343,6 @@ static int pakfire_jail_wait(struct pakfire_jail* jail, struct pakfire_jail_exec
                                        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;
-                                               }
-                                       }
-                               }
-                       }
-
                        // Check if any file descriptors have been closed
                        if (e & EPOLLHUP) {
                                // Remove the file descriptor
@@ -1876,16 +1987,58 @@ static int pakfire_jail_open_pty(struct pakfire_jail* jail, struct pakfire_jail_
 
        CTX_DEBUG(jail->ctx, "Allocated console at %s (%d)\n", ctx->pty.console, ctx->pty.master.fd);
 
-#if 0
+       // 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, "/dev/console", ctx->pty.console);
+       r = pakfire_symlink(jail->ctx, ctx->pty.console, "/dev/console");
        if (r)
                return r;
-#endif
 
        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;
@@ -2016,14 +2169,12 @@ static int pakfire_jail_child(struct pakfire_jail* jail, struct pakfire_jail_exe
                }
        }
 
-#if 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;
        }
-#endif
 
        // Allocate a new PTY
        r = pakfire_jail_open_pty(jail, ctx);
@@ -2039,6 +2190,11 @@ static int pakfire_jail_child(struct pakfire_jail* jail, struct pakfire_jail_exe
                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;
@@ -2053,41 +2209,6 @@ static int pakfire_jail_child(struct pakfire_jail* jail, struct pakfire_jail_exe
        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]);
-
-                       return 1;
-               }
-       }
-
-       // 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]);
-
-                       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.stdin);
-               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)
@@ -2162,9 +2283,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
@@ -2218,26 +2336,6 @@ static int __pakfire_jail_exec(struct pakfire_jail* jail, const char* argv[],
                goto ERROR;
        }
 
-       // 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;
-       }
-
        // Setup pipes for logging
        // INFO
        r = pakfire_jail_setup_pipe(jail, &ctx.pipes.log_INFO, O_CLOEXEC);
@@ -2354,9 +2452,6 @@ 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)