#include <sys/timerfd.h>
#include <sys/types.h>
#include <sys/wait.h>
+#include <termios.h>
// libnl3
#include <net/if.h>
#include <pakfire/jail.h>
#include <pakfire/logging.h>
#include <pakfire/mount.h>
-#include <pakfire/os.h>
#include <pakfire/pakfire.h>
#include <pakfire/path.h>
#include <pakfire/private.h>
struct pakfire_jail_exec {
int flags;
- // PIDs (of the children)
- int pidfd1;
- int pidfd2;
+ // PID (of the child)
+ pid_t pid;
+ int pidfd;
// Socket to pass FDs
int socket[2];
+ // Process status (from waitid)
+ siginfo_t status;
+
// FD to notify the client that the parent has finished initialization
int completed_fd;
// Log pipes
struct pakfire_jail_pipes {
- int stdin[2];
- int stdout[2];
- int stderr[2];
-
// Logging
int log_INFO[2];
int log_ERROR[2];
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 (sizeof(buffer->data) == buffer->used);
}
-/*
- This function reads as much data as it can from the file descriptor.
- If it finds a whole line in it, it will send it to the logger and repeat the process.
- If not newline character is found, it will try to read more data until it finds one.
-*/
-static int pakfire_jail_handle_log(struct pakfire_jail* jail,
- struct pakfire_jail_exec* ctx, int priority, int fd,
- struct pakfire_log_buffer* buffer, pakfire_jail_communicate_out callback, void* data) {
- char line[BUFFER_SIZE + 1];
+static int pakfire_jail_fill_buffer(struct pakfire_jail* jail, int fd, struct pakfire_log_buffer* buffer) {
+ int r;
- // Fill up buffer from fd
- if (buffer->used < sizeof(buffer->data)) {
- ssize_t bytes_read = read(fd, buffer->data + buffer->used,
- sizeof(buffer->data) - buffer->used);
-
- // Handle errors
- if (bytes_read < 0) {
- ERROR(jail->pakfire, "Could not read from fd %d: %m\n", fd);
- return -1;
+ // Skip this if there is not space left in the buffer
+ if (buffer->used >= sizeof(buffer->data))
+ return 0;
+
+ // Fill the buffer
+ r = read(fd, buffer->data + buffer->used, sizeof(buffer->data) - buffer->used);
+
+ // Handle errors
+ if (r < 0) {
+ switch (errno) {
+ case EAGAIN:
+ case EIO:
+ break;
+
+ default:
+ return -errno;
}
- // Update buffer size
- buffer->used += bytes_read;
+ // EOF
+ } else if (r == 0) {
+ // XXX What to do here?
+
+ // Successful read
+ } else {
+ buffer->used += r;
}
- // See if we have any lines that we can write
+ return 0;
+}
+
+static int pakfire_jail_drain_buffer_with_callback(struct pakfire_jail* jail,
+ struct pakfire_log_buffer* buffer, int priority, pakfire_jail_communicate_out callback, void* data) {
+ const char* eol = NULL;
+ int r;
+
while (buffer->used) {
// Search for the end of the first line
- char* eol = memchr(buffer->data, '\n', buffer->used);
+ eol = memchr(buffer->data, '\n', buffer->used);
// No newline found
if (!eol) {
- // If the buffer is full, we send the content to the logger and try again
- // This should not happen in practise
+ // If the buffer is full, we send the entire content to make space.
if (pakfire_jail_log_buffer_is_full(buffer)) {
- DEBUG(jail->pakfire, "Logging buffer is full. Sending all content\n");
+ CTX_DEBUG(jail->ctx, "Buffer is full. Sending all content\n");
- eol = buffer->data + sizeof(buffer->data) - 1;
+ eol = buffer->data + buffer->used - 1;
- // Otherwise we might have only read parts of the output
- } else
+ // Otherwise we might have only read parts of the output...
+ } else {
break;
+ }
}
// Find the length of the string
- size_t length = eol - buffer->data + 1;
-
- // Copy the line into the buffer
- memcpy(line, buffer->data, length);
-
- // Terminate the string
- line[length] = '\0';
+ const size_t length = eol - buffer->data + 1;
- // Log the line
- if (callback) {
- int r = callback(jail->pakfire, data, priority, line, length);
- if (r) {
- ERROR(jail->pakfire, "The logging callback returned an error: %d\n", r);
- return r;
- }
+ // Call the callback
+ r = callback(jail->pakfire, data, priority, buffer->data, length);
+ if (r) {
+ CTX_ERROR(jail->ctx, "The logging callback returned an error: %d\n", r);
+ return r;
}
// Remove line from buffer
return 0;
}
+static int pakfire_jail_drain_buffer(struct pakfire_jail* jail, int fd, struct pakfire_log_buffer* buffer) {
+ int r;
+
+ // Nothing to do if the buffer is empty
+ if (!buffer->used)
+ return 0;
+
+ // Drain the buffer
+ r = write(fd, buffer->data, buffer->used);
+
+ // Handle errors
+ if (r < 0) {
+ switch (errno) {
+ case EAGAIN:
+ case EIO:
+ break;
+
+ default:
+ return -errno;
+ }
+
+ // Successful write
+ } else {
+ memmove(buffer->data, buffer->data + r, buffer->used - r);
+
+ buffer->used -= r;
+ }
+
+ return 0;
+}
+
+/*
+ This function reads as much data as it can from the file descriptor.
+ If it finds a whole line in it, it will send it to the logger and repeat the process.
+ If not newline character is found, it will try to read more data until it finds one.
+*/
+static int pakfire_jail_handle_log(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx,
+ int priority, int fd, struct pakfire_log_buffer* buffer,
+ pakfire_jail_communicate_out callback, void* data) {
+ int r;
+
+ // Fill up buffer from fd
+ r = pakfire_jail_fill_buffer(jail, fd, buffer);
+ if (r)
+ return r;
+
+ // Drain the buffer
+ r = pakfire_jail_drain_buffer_with_callback(jail, buffer, priority, callback, data);
+ if (r)
+ return r;
+
+ return 0;
+}
+
+#if 0
static int pakfire_jail_stream_stdin(struct pakfire_jail* jail,
struct pakfire_jail_exec* ctx, const int fd) {
int r;
}
// Skip if the writing pipe has already been closed
- if (!ctx->pipes.stdin[1])
+ if (ctx->pipes.stdin[1] < 0)
return 0;
DEBUG(jail->pakfire, "Streaming standard input...\n");
return r;
}
-
-static int pakfire_jail_setup_pipe(struct pakfire_jail* jail, int (*fds)[2], const int flags) {
- int r = pipe2(*fds, flags);
- if (r < 0) {
- ERROR(jail->pakfire, "Could not setup pipe: %m\n");
- return 1;
- }
-
- return 0;
-}
-
-static void pakfire_jail_close_pipe(struct pakfire_jail* jail, int fds[2]) {
- for (unsigned int i = 0; i < 2; i++)
- if (fds[i] >= 0)
- close(fds[i]);
-}
-
-/*
- This is a convenience function to fetch the reading end of a pipe and
- closes the write end.
-*/
-static int pakfire_jail_get_pipe_to_read(struct pakfire_jail* jail, int (*fds)[2]) {
- // Give the variables easier names to avoid confusion
- int* fd_read = &(*fds)[0];
- int* fd_write = &(*fds)[1];
-
- // Close the write end of the pipe
- if (*fd_write >= 0) {
- close(*fd_write);
- *fd_write = -1;
- }
-
- // Return the read end
- if (*fd_read >= 0)
- return *fd_read;
-
- return -1;
-}
-
-static int pakfire_jail_get_pipe_to_write(struct pakfire_jail* jail, int (*fds)[2]) {
- // Give the variables easier names to avoid confusion
- int* fd_read = &(*fds)[0];
- int* fd_write = &(*fds)[1];
-
- // Close the read end of the pipe
- if (*fd_read >= 0) {
- close(*fd_read);
- *fd_read = -1;
- }
-
- // Return the write end
- if (*fd_write >= 0)
- return *fd_write;
-
- return -1;
-}
+#endif
static int pakfire_jail_recv_fd(struct pakfire_jail* jail, int socket, int* fd) {
const size_t payload_length = sizeof(fd);
return 0;
}
+static int pakfire_jail_setup_pipe(struct pakfire_jail* jail, int (*fds)[2], const int flags) {
+ int r = pipe2(*fds, flags);
+ if (r < 0) {
+ ERROR(jail->pakfire, "Could not setup pipe: %m\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+static void pakfire_jail_close_pipe(struct pakfire_jail* jail, int fds[2]) {
+ for (unsigned int i = 0; i < 2; i++)
+ if (fds[i] >= 0)
+ close(fds[i]);
+}
+
+/*
+ This is a convenience function to fetch the reading end of a pipe and
+ closes the write end.
+*/
+static int pakfire_jail_get_pipe_to_read(struct pakfire_jail* jail, int (*fds)[2]) {
+ // Give the variables easier names to avoid confusion
+ int* fd_read = &(*fds)[0];
+ int* fd_write = &(*fds)[1];
+
+ // Close the write end of the pipe
+ if (*fd_write >= 0) {
+ close(*fd_write);
+ *fd_write = -1;
+ }
+
+ // Return the read end
+ if (*fd_read >= 0)
+ return *fd_read;
+
+ return -1;
+}
+
+static int pakfire_jail_get_pipe_to_write(struct pakfire_jail* jail, int (*fds)[2]) {
+ // Give the variables easier names to avoid confusion
+ int* fd_read = &(*fds)[0];
+ int* fd_write = &(*fds)[1];
+
+ // Close the read end of the pipe
+ if (*fd_read >= 0) {
+ close(*fd_read);
+ *fd_read = -1;
+ }
+
+ // Return the write end
+ if (*fd_write >= 0)
+ return *fd_write;
+
+ return -1;
+}
+
static int pakfire_jail_log(struct pakfire* pakfire, void* data, int priority,
const char* line, const size_t length) {
// Pass everything to the parent logger
return 0;
}
-static int pakfire_jail_setup_child2(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx);
+// PTY Forwarding
-static int pakfire_jail_wait_on_child(struct pakfire_jail* jail, int pidfd) {
- siginfo_t status = {};
+static int pakfire_jail_enable_raw_mode(struct pakfire_jail* jail,
+ struct pakfire_jail_pty_stdio* stdio) {
+ struct termios raw_attrs;
int r;
- // Call waitid() and store the result
- r = waitid(P_PIDFD, pidfd, &status, WEXITED);
+ // Store flags
+ stdio->fdflags = fcntl(stdio->fd, F_GETFL);
+ if (stdio->fdflags < 0) {
+ CTX_ERROR(jail->ctx, "Could not fetch flags from fd %d: %s\n",
+ stdio->fd, strerror(errno));
+ return -errno;
+ }
+
+ // Fetch all attributes
+ r = tcgetattr(stdio->fd, &stdio->attrs);
if (r) {
- CTX_ERROR(jail->ctx, "waitid() failed: %s\n", strerror(errno));
+ CTX_ERROR(jail->ctx, "Could not fetch terminal attributes from fd %d: %s\n",
+ stdio->fd, strerror(errno));
return -errno;
}
- switch (status.si_code) {
- // If the process exited normally, we return the exit code
- case CLD_EXITED:
- CTX_DEBUG(jail->ctx, "The child process exited with code %d\n", status.si_status);
- return status.si_status;
+ // Copy all attributes
+ raw_attrs = stdio->attrs;
- case CLD_KILLED:
- CTX_ERROR(jail->ctx, "The child process was killed\n");
- return 139;
+ // Make it RAW
+ cfmakeraw(&raw_attrs);
- case CLD_DUMPED:
- CTX_ERROR(jail->ctx, "The child process terminated abnormally\n");
- return 139;
+ switch (stdio->fd) {
+ case STDIN_FILENO:
+ raw_attrs.c_oflag = stdio->attrs.c_oflag;
+ break;
- // Log anything else
- default:
- CTX_ERROR(jail->ctx, "Unknown child exit code: %d\n", status.si_code);
+ case STDOUT_FILENO:
+ raw_attrs.c_iflag = stdio->attrs.c_iflag;
+ raw_attrs.c_lflag = stdio->attrs.c_lflag;
break;
}
- return -EBADMSG;
+ // Restore the attributes
+ r = tcsetattr(stdio->fd, TCSANOW, &raw_attrs);
+ if (r) {
+ CTX_ERROR(jail->ctx, "Could not restore terminal attributes for fd %d: %s\n",
+ stdio->fd, strerror(errno));
+ return -errno;
+ }
+
+ return 0;
}
-static int pakfire_jail_wait(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx) {
- int epollfd = -1;
- struct epoll_event events[EPOLL_MAX_EVENTS];
- char garbage[8];
- int r = 0;
+static int pakfire_jail_restore_attrs(struct pakfire_jail* jail,
+ const struct pakfire_jail_pty_stdio* stdio) {
+ int r;
- // Fetch the UNIX domain socket
- const int socket_recv = pakfire_jail_get_pipe_to_read(jail, &ctx->socket);
+ // Restore the flags
+ r = fcntl(stdio->fd, F_SETFL, stdio->fdflags);
+ if (r < 0) {
+ CTX_ERROR(jail->ctx, "Could not set flags for file descriptor %d: %s\n",
+ stdio->fd, strerror(errno));
+ return -errno;
+ }
- // Fetch file descriptors from context
- const int stdin = pakfire_jail_get_pipe_to_write(jail, &ctx->pipes.stdin);
- const int stdout = pakfire_jail_get_pipe_to_read(jail, &ctx->pipes.stdout);
- const int stderr = pakfire_jail_get_pipe_to_read(jail, &ctx->pipes.stderr);
+ // Restore the attributes
+ r = tcsetattr(stdio->fd, TCSANOW, &stdio->attrs);
+ if (r) {
+ CTX_ERROR(jail->ctx, "Could not restore terminal attributes for %d, ignoring: %s\n",
+ stdio->fd, strerror(errno));
+ return -errno;
+ }
- // Timer
- const int timerfd = pakfire_jail_create_timer(jail);
+ return 0;
+}
- // Logging
- const int log_INFO = pakfire_jail_get_pipe_to_read(jail, &ctx->pipes.log_INFO);
- const int log_ERROR = pakfire_jail_get_pipe_to_read(jail, &ctx->pipes.log_ERROR);
-#ifdef ENABLE_DEBUG
- const int log_DEBUG = pakfire_jail_get_pipe_to_read(jail, &ctx->pipes.log_DEBUG);
-#endif /* ENABLE_DEBUG */
+static int pakfire_jail_setup_pty_forwarding(struct pakfire_jail* jail,
+ struct pakfire_jail_exec* ctx, const int epollfd, const int fd) {
+ struct winsize size;
+ int r;
- // Make a list of all file descriptors we are interested in
- const struct pakfire_wait_fds {
- const int fd;
- const int events;
- } fds[] = {
- { socket_recv, EPOLLIN },
+ CTX_DEBUG(jail->ctx, "Setting up PTY forwarding on fd %d\n", fd);
- // Standard input/output
- { stdin, EPOLLOUT },
- { stdout, EPOLLIN },
- { stderr, EPOLLIN },
+ // Store the file descriptor
+ ctx->pty.master.fd = fd;
- // Timer
- { timerfd, EPOLLIN },
+ // Configure stdin/stdout
+ ctx->pty.stdin.fd = STDIN_FILENO;
+ ctx->pty.stdout.fd = STDOUT_FILENO;
- // Child Processes
- { ctx->pidfd1, EPOLLIN },
+ // Fetch dimensions
+ r = ioctl(ctx->pty.stdout.fd, TIOCGWINSZ, &size);
+ if (r) {
+ CTX_ERROR(jail->ctx, "Failed to determine terminal dimensions: %s\n", strerror(errno));
+ return -errno;
+ }
- // Log Pipes
- { log_INFO, EPOLLIN },
- { log_ERROR, EPOLLIN },
-#ifdef ENABLE_DEBUG
- { log_DEBUG, EPOLLIN },
-#endif /* ENABLE_DEBUG */
+ // Set dimensions
+ r = ioctl(ctx->pty.master.fd, TIOCSWINSZ, &size);
+ if (r) {
+ CTX_ERROR(jail->ctx, "Failed setting dimensions: %s\n", strerror(errno));
+ return -errno;
+ }
- // Sentinel
- { -1, 0 },
- };
+ // Enable RAW mode on standard input
+ r = pakfire_jail_enable_raw_mode(jail, &ctx->pty.stdin);
+ if (r)
+ return r;
- // Setup epoll
- epollfd = epoll_create1(0);
- if (epollfd < 0) {
- ERROR(jail->pakfire, "Could not initialize epoll(): %m\n");
- r = 1;
- goto ERROR;
- }
+ // Enable RAW mode on standard output
+ r = pakfire_jail_enable_raw_mode(jail, &ctx->pty.stdout);
+ if (r)
+ return r;
- // Turn file descriptors into non-blocking mode and add them to epoll()
- for (const struct pakfire_wait_fds* fd = fds; fd->events; fd++) {
+ // Add the master to the event loop
+ r = pakfire_jail_epoll_add_fd(jail, epollfd, ctx->pty.master.fd, EPOLLIN|EPOLLOUT|EPOLLET);
+ if (r)
+ return r;
+
+ // Add standard input to the event loop
+ r = pakfire_jail_epoll_add_fd(jail, epollfd, ctx->pty.stdin.fd, EPOLLIN|EPOLLET);
+ if (r)
+ return r;
+
+ // Add standard output to the event loop
+ r = pakfire_jail_epoll_add_fd(jail, epollfd, ctx->pty.stdout.fd, EPOLLOUT|EPOLLET);
+ if (r)
+ return r;
+
+ return 0;
+}
+
+static int pakfire_jail_forward_pty(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx) {
+ int r;
+
+ // Read from standard input
+ if (ctx->pty.stdin.flags & PAKFIRE_JAIL_PTY_READY_TO_READ) {
+ r = pakfire_jail_fill_buffer(jail, ctx->pty.stdin.fd, &ctx->pty.stdin.buffer);
+ if (r) {
+ CTX_ERROR(jail->ctx, "Failed reading from standard input: %s\n", strerror(-r));
+ return r;
+ }
+
+ // We are done reading for now
+ ctx->pty.stdin.flags &= ~PAKFIRE_JAIL_PTY_READY_TO_READ;
+
+ // But we may have data to write
+ if (ctx->pty.stdin.buffer.used)
+ ctx->pty.master.flags |= PAKFIRE_JAIL_PTY_READY_TO_WRITE;
+ }
+
+ // Write to the master
+ if (ctx->pty.master.flags & PAKFIRE_JAIL_PTY_READY_TO_WRITE) {
+ r = pakfire_jail_drain_buffer(jail, ctx->pty.master.fd, &ctx->pty.stdin.buffer);
+ if (r) {
+ CTX_ERROR(jail->ctx, "Failed writing to the PTY: %s\n", strerror(-r));
+ return r;
+ }
+
+ // We are done writing for now
+ ctx->pty.master.flags &= ~PAKFIRE_JAIL_PTY_READY_TO_WRITE;
+ }
+
+ // Read from the master
+ if (ctx->pty.master.flags & PAKFIRE_JAIL_PTY_READY_TO_READ) {
+ r = pakfire_jail_fill_buffer(jail, ctx->pty.master.fd, &ctx->pty.stdout.buffer);
+ if (r) {
+ CTX_ERROR(jail->ctx, "Failed reading from the PTY: %s\n", strerror(-r));
+ return r;
+ }
+
+ // We are done reading for now
+ ctx->pty.master.flags &= ~PAKFIRE_JAIL_PTY_READY_TO_READ;
+
+ // But we may have data to write
+ if (ctx->pty.stdout.buffer.used)
+ ctx->pty.stdout.flags |= PAKFIRE_JAIL_PTY_READY_TO_WRITE;
+ }
+
+ // Write to standard output
+ if (ctx->pty.stdout.flags & PAKFIRE_JAIL_PTY_READY_TO_WRITE) {
+ r = pakfire_jail_drain_buffer(jail, ctx->pty.stdout.fd, &ctx->pty.stdout.buffer);
+ if (r) {
+ CTX_ERROR(jail->ctx, "Failed writing to standard output: %s\n", strerror(-r));
+ return r;
+ }
+
+ // We are done writing for now
+ ctx->pty.stdout.flags &= ~PAKFIRE_JAIL_PTY_READY_TO_WRITE;
+ }
+
+ return 0;
+}
+
+static int pakfire_jail_wait(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx) {
+ int epollfd = -1;
+ struct epoll_event events[EPOLL_MAX_EVENTS];
+ char garbage[8];
+ int r = 0;
+
+ // Fetch file descriptors from context
+ const int pidfd = ctx->pidfd;
+
+ // Fetch the UNIX domain socket
+ const int socket_recv = pakfire_jail_get_pipe_to_read(jail, &ctx->socket);
+
+ // Timer
+ const int timerfd = pakfire_jail_create_timer(jail);
+
+ // Logging
+ const int log_INFO = pakfire_jail_get_pipe_to_read(jail, &ctx->pipes.log_INFO);
+ const int log_ERROR = pakfire_jail_get_pipe_to_read(jail, &ctx->pipes.log_ERROR);
+#ifdef ENABLE_DEBUG
+ const int log_DEBUG = pakfire_jail_get_pipe_to_read(jail, &ctx->pipes.log_DEBUG);
+#endif /* ENABLE_DEBUG */
+
+ // Make a list of all file descriptors we are interested in
+ const struct pakfire_wait_fds {
+ const int fd;
+ const int events;
+ } fds[] = {
+ // Timer
+ { timerfd, EPOLLIN },
+
+ // Child Process
+ { ctx->pidfd, EPOLLIN },
+
+ // Log Pipes
+ { log_INFO, EPOLLIN },
+ { log_ERROR, EPOLLIN },
+#ifdef ENABLE_DEBUG
+ { log_DEBUG, EPOLLIN },
+#endif /* ENABLE_DEBUG */
+
+ // UNIX Domain Socket
+ { socket_recv, EPOLLIN },
+
+ // Sentinel
+ { -1, 0 },
+ };
+
+ // Setup epoll
+ epollfd = epoll_create1(0);
+ if (epollfd < 0) {
+ ERROR(jail->pakfire, "Could not initialize epoll(): %m\n");
+ r = 1;
+ goto ERROR;
+ }
+
+ // Turn file descriptors into non-blocking mode and add them to epoll()
+ for (const struct pakfire_wait_fds* fd = fds; fd->events; fd++) {
// Skip fds which were not initialized
if (fd->fd < 0)
continue;
}
int ended = 0;
- int exit = 0;
-
- CTX_DEBUG(jail->ctx, "Launching main loop...\n");
// Loop for as long as the process is alive
while (!ended) {
int e = events[i].events;
int fd = events[i].data.fd;
- struct pakfire_log_buffer* buffer = NULL;
- pakfire_jail_communicate_out callback = NULL;
- void* data = NULL;
- int priority;
+ // Handle PTY forwarding events
+ if (ctx->pty.master.fd == fd) {
+ if (e & (EPOLLIN|EPOLLHUP))
+ ctx->pty.master.flags |= PAKFIRE_JAIL_PTY_READY_TO_READ;
- // Check if there is any data to be read
- if (e & EPOLLIN) {
- // Monitor the first child process
- if (fd == ctx->pidfd1) {
- r = pakfire_jail_wait_on_child(jail, ctx->pidfd1);
- if (r) {
- CTX_ERROR(jail->ctx, "The first child exited with an error\n");
- goto ERROR;
- }
+ if (e & (EPOLLOUT|EPOLLHUP))
+ ctx->pty.master.flags |= PAKFIRE_JAIL_PTY_READY_TO_WRITE;
- close(ctx->pidfd1);
- ctx->pidfd1 = -1;
+ // Perform the work
+ r = pakfire_jail_forward_pty(jail, ctx);
+ if (r) {
+ CTX_ERROR(jail->ctx, "Failed forwarding the PTY: %s\n", strerror(-r));
+ goto ERROR;
+ }
- continue;
+ // Handle standard input
+ } else if (ctx->pty.stdin.fd == fd) {
+ if (e & (EPOLLIN|EPOLLHUP))
+ ctx->pty.stdin.flags |= PAKFIRE_JAIL_PTY_READY_TO_READ;
+
+ // Perform the work
+ r = pakfire_jail_forward_pty(jail, ctx);
+ if (r) {
+ CTX_ERROR(jail->ctx, "Failed forwarding the PTY: %s\n", strerror(-r));
+ goto ERROR;
+ }
+
+ // Handle standard output
+ } else if (ctx->pty.stdout.fd == fd) {
+ if (e & (EPOLLOUT|EPOLLHUP))
+ ctx->pty.stdout.flags |= PAKFIRE_JAIL_PTY_READY_TO_WRITE;
+
+ // Perform the work
+ r = pakfire_jail_forward_pty(jail, ctx);
+ if (r) {
+ CTX_ERROR(jail->ctx, "Failed forwarding the PTY: %s\n", strerror(-r));
+ goto ERROR;
+ }
- // Monitor the second child process
- } else if (fd == ctx->pidfd2) {
- exit = pakfire_jail_wait_on_child(jail, ctx->pidfd2);
- if (exit < 0) {
- CTX_ERROR(jail->ctx, "The second child exited with an error\n");
+ // Handle any changes to the PIDFD
+ } else if (pidfd == fd) {
+ if (e & EPOLLIN) {
+ // Call waidid() and store the result
+ r = waitid(P_PIDFD, ctx->pidfd, &ctx->status, WEXITED);
+ if (r) {
+ ERROR(jail->pakfire, "waitid() failed: %m\n");
goto ERROR;
}
- close(ctx->pidfd2);
- ctx->pidfd2 = -1;
-
// Mark that we have ended so that we will process the remaining
// events from epoll() now, but won't restart the outer loop.
ended = 1;
+ }
- continue;
-
- // Handle timer events
- } else if (fd == timerfd) {
+ // Handle timer events
+ } else if (timerfd == fd) {
+ if (e & EPOLLIN) {
DEBUG(jail->pakfire, "Timer event received\n");
// Disarm the timer
DEBUG(jail->pakfire, "Terminating process...\n");
// Send SIGTERM to the process
- r = pidfd_send_signal(ctx->pidfd2, SIGKILL, NULL, 0);
+ r = pidfd_send_signal(pidfd, SIGKILL, NULL, 0);
if (r) {
ERROR(jail->pakfire, "Could not kill process: %m\n");
goto ERROR;
}
}
+ }
- // There is nothing else to do
- continue;
-
- // Handle socket messages
- } else if (fd == socket_recv) {
- // Receive the FD of the second child process
- r = pakfire_jail_recv_fd(jail, socket_recv, &ctx->pidfd2);
+ // Handle socket messages
+ } else if (socket_recv == fd) {
+ if (e & EPOLLIN) {
+ // Receive the passed FD
+ r = pakfire_jail_recv_fd(jail, socket_recv, &fd);
if (r)
goto ERROR;
- // Add it to the event loop
- r = pakfire_jail_epoll_add_fd(jail, epollfd, ctx->pidfd2, EPOLLIN);
+ // Setup PTY forwarding
+ if (ctx->pty.master.fd < 0) {
+ r = pakfire_jail_setup_pty_forwarding(jail, ctx, epollfd, fd);
+ if (r) {
+ CTX_ERROR(jail->ctx, "Failed setting up PTY forwarding: %s\n", strerror(-r));
+ goto ERROR;
+ }
+ }
+ }
+
+ // Handle log INFO messages
+ } else if (log_INFO == fd) {
+ if (e & EPOLLIN) {
+ r = pakfire_jail_handle_log(jail, ctx, LOG_INFO, fd,
+ &ctx->buffers.log_INFO, pakfire_jail_log, NULL);
if (r)
goto ERROR;
+ }
- // Setup the child process
- r = pakfire_jail_setup_child2(jail, ctx);
+ // Handle log ERROR messages
+ } else if (log_ERROR == fd) {
+ if (e & EPOLLIN) {
+ r = pakfire_jail_handle_log(jail, ctx, LOG_ERR, fd,
+ &ctx->buffers.log_ERROR, pakfire_jail_log, NULL);
if (r)
goto ERROR;
-
- // Don't fall through to log processing
- continue;
-
- // Handle logging messages
- } else if (fd == log_INFO) {
- buffer = &ctx->buffers.log_INFO;
- priority = LOG_INFO;
-
- callback = pakfire_jail_log;
-
- } else if (fd == log_ERROR) {
- buffer = &ctx->buffers.log_ERROR;
- priority = LOG_ERR;
-
- callback = pakfire_jail_log;
+ }
#ifdef ENABLE_DEBUG
- } else if (fd == log_DEBUG) {
- buffer = &ctx->buffers.log_DEBUG;
- priority = LOG_DEBUG;
-
- callback = pakfire_jail_log;
-#endif /* ENABLE_DEBUG */
-
- // Handle anything from the log pipes
- } else if (fd == stdout) {
- buffer = &ctx->buffers.stdout;
- priority = LOG_INFO;
-
- // Send any output to the default logger if no callback is set
- if (ctx->communicate.out) {
- callback = ctx->communicate.out;
- data = ctx->communicate.data;
- } else {
- callback = jail->callbacks.log;
- data = jail->callbacks.log_data;
- }
-
- } else if (fd == stderr) {
- buffer = &ctx->buffers.stderr;
- priority = LOG_ERR;
-
- // Send any output to the default logger if no callback is set
- if (ctx->communicate.out) {
- callback = ctx->communicate.out;
- data = ctx->communicate.data;
- } else {
- callback = jail->callbacks.log;
- data = jail->callbacks.log_data;
- }
-
- } else {
- DEBUG(jail->pakfire, "Received invalid file descriptor %d\n", fd);
- continue;
+ // Handle log DEBUG messages
+ } else if (log_DEBUG == fd) {
+ if (e & EPOLLIN) {
+ r = pakfire_jail_handle_log(jail, ctx, LOG_DEBUG, fd,
+ &ctx->buffers.log_DEBUG, pakfire_jail_log, NULL);
+ if (r)
+ goto ERROR;
}
+#endif /* ENABLE_DEBUG */
- // Handle log event
- r = pakfire_jail_handle_log(jail, ctx, priority, fd, buffer, callback, data);
- if (r)
- goto ERROR;
- }
-
- if (e & EPOLLOUT) {
- // Handle standard input
- if (fd == stdin) {
- r = pakfire_jail_stream_stdin(jail, ctx, fd);
- if (r) {
- switch (errno) {
- // Ignore if we filled up the buffer
- case EAGAIN:
- break;
-
- default:
- ERROR(jail->pakfire, "Could not write to stdin: %m\n");
- goto ERROR;
- }
- }
- }
+ // Log a message for anything else
+ } else {
+ DEBUG(jail->pakfire, "Received invalid file descriptor %d\n", fd);
+ continue;
}
// Check if any file descriptors have been closed
}
}
- // Return the exit code
- r = exit;
-
ERROR:
- CTX_DEBUG(jail->ctx, "Main loop terminated\n");
-
if (epollfd >= 0)
close(epollfd);
if (timerfd >= 0)
close(timerfd);
+ // Restore any changed terminal attributes
+ if (ctx->pty.stdin.fd >= 0)
+ pakfire_jail_restore_attrs(jail, &ctx->pty.stdin);
+ if (ctx->pty.stdout.fd >= 0)
+ pakfire_jail_restore_attrs(jail, &ctx->pty.stdout);
+
return r;
}
return r;
}
+/*
+ Performs the initialisation that needs to happen in the parent part
+*/
+static int pakfire_jail_parent(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx) {
+ int r;
+
+ // Setup UID mapping
+ r = pakfire_jail_setup_uid_mapping(jail, ctx->pid);
+ if (r)
+ return r;
+
+ // Write "deny" to /proc/PID/setgroups
+ r = pakfire_jail_setgroups(jail, ctx->pid);
+ if (r)
+ return r;
+
+ // Setup GID mapping
+ r = pakfire_jail_setup_gid_mapping(jail, ctx->pid);
+ if (r)
+ return r;
+
+ // Parent has finished initialisation
+ DEBUG(jail->pakfire, "Parent has finished initialization\n");
+
+ // Send signal to client
+ r = pakfire_jail_send_signal(jail, ctx->completed_fd);
+ if (r)
+ return r;
+
+ return 0;
+}
+
static int pakfire_jail_switch_root(struct pakfire_jail* jail, const char* root) {
int r;
return 0;
}
-/*
- Called by the parent that sets up the second child process...
-*/
-static int pakfire_jail_setup_child2(
- struct pakfire_jail* jail, struct pakfire_jail_exec* ctx) {
- pid_t pid = -1;
+static int pakfire_jail_open_pty(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx) {
int r;
- // Fetch the PID
- r = pidfd_get_pid(ctx->pidfd2, &pid);
+ // Allocate a new PTY
+ ctx->pty.master.fd = posix_openpt(O_RDWR|O_NONBLOCK|O_NOCTTY|O_CLOEXEC);
+ if (ctx->pty.master.fd < 0)
+ return -errno;
+
+ // Fetch the path
+ r = ptsname_r(ctx->pty.master.fd, ctx->pty.console, sizeof(ctx->pty.console));
+ if (r)
+ return -r;
+
+ CTX_DEBUG(jail->ctx, "Allocated console at %s (%d)\n", ctx->pty.console, ctx->pty.master.fd);
+
+ // Unlock the master device
+ r = unlockpt(ctx->pty.master.fd);
if (r) {
- CTX_ERROR(jail->ctx, "Could not fetch PID: %s\n", strerror(-r));
- return r;
+ CTX_ERROR(jail->ctx, "Could not unlock the PTY: %s\n", strerror(errno));
+ return -errno;
}
- // Setup UID mapping
- r = pakfire_jail_setup_uid_mapping(jail, pid);
+ // Create a symlink
+ r = pakfire_symlink(jail->ctx, ctx->pty.console, "/dev/console");
if (r)
return r;
- // Write "deny" to /proc/PID/setgroups
- r = pakfire_jail_setgroups(jail, pid);
- if (r)
- return r;
+ return r;
+}
- // Setup GID mapping
- r = pakfire_jail_setup_gid_mapping(jail, pid);
- if (r)
- return r;
+static int pakfire_jail_setup_terminal(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx) {
+ int fd;
+ int r;
- // Parent has finished initialisation
- DEBUG(jail->pakfire, "Parent has finished initialization\n");
+ // Open a new terminal
+ fd = open("/dev/console", O_RDWR|O_NOCTTY);
+ if (fd < 0) {
+ CTX_ERROR(jail->ctx, "Failed to open a new terminal: %s\n", strerror(errno));
+ return -errno;
+ }
- // Send signal to client
- r = pakfire_jail_send_signal(jail, ctx->completed_fd);
- if (r)
- return r;
+ CTX_DEBUG(jail->ctx, "Opened a new terminal %d\n", fd);
+
+ // Connect the new terminal to standard input
+ r = dup2(fd, STDIN_FILENO);
+ if (r < 0) {
+ CTX_ERROR(jail->ctx, "Failed to open standard input: %s\n", strerror(errno));
+ return -errno;
+ }
+
+ // Connect the new terminal to standard output
+ r = dup2(fd, STDOUT_FILENO);
+ if (r < 0) {
+ CTX_ERROR(jail->ctx, "Failed to open standard output: %s\n", strerror(errno));
+ return -errno;
+ }
+
+ // Connect the new terminal to standard error
+ r = dup2(fd, STDERR_FILENO);
+ if (r < 0) {
+ CTX_ERROR(jail->ctx, "Failed to open standard error: %s\n", strerror(errno));
+ return -errno;
+ }
return 0;
}
-/*
- Child 2 is launched in their own user/mount/etc. namespace.
-*/
-static int pakfire_jail_child2(struct pakfire_jail* jail,
- struct pakfire_jail_exec* ctx, const char* argv[]) {
+static int pakfire_jail_child(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx,
+ const char* argv[]) {
int r;
+ // Redirect any logging to our log pipe
+ pakfire_ctx_set_log_callback(jail->ctx, pakfire_jail_log_redirect, &ctx->pipes);
+
// Fetch my own PID
pid_t pid = getpid();
- CTX_DEBUG(jail->ctx, "Launched child process in jail with PID %d\n", pid);
+ DEBUG(jail->pakfire, "Launched child process in jail with PID %d\n", pid);
+
+ // Wait for the parent to finish initialization
+ r = pakfire_jail_wait_for_signal(jail, ctx->completed_fd);
+ if (r)
+ return r;
+
+ // Die with parent
+ r = prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
+ if (r) {
+ ERROR(jail->pakfire, "Could not configure to die with parent: %m\n");
+ return 126;
+ }
// Make this process dumpable
r = prctl (PR_SET_DUMPABLE, 1, 0, 0, 0);
if (r) {
- CTX_ERROR(jail->ctx, "Could not make the process dumpable: %m\n");
+ ERROR(jail->pakfire, "Could not make the process dumpable: %m\n");
return 126;
}
// Don't drop any capabilities on setuid()
r = prctl(PR_SET_KEEPCAPS, 1);
if (r) {
- CTX_ERROR(jail->ctx, "Could not set PR_SET_KEEPCAPS: %m\n");
+ ERROR(jail->pakfire, "Could not set PR_SET_KEEPCAPS: %m\n");
return 126;
}
- // Wait for the parent to finish initialization
- r = pakfire_jail_wait_for_signal(jail, ctx->completed_fd);
- if (r)
- return r;
-
// Fetch UID/GID
uid_t uid = getuid();
gid_t gid = getgid();
return 126;
}
+ const int socket_send = pakfire_jail_get_pipe_to_write(jail, &ctx->socket);
+
// Mount all default stuff
r = pakfire_mount_all(jail->pakfire, PAKFIRE_MNTNS_INNER, 0);
if (r)
return 126;
+ const char* root = pakfire_get_path(jail->pakfire);
const char* arch = pakfire_get_effective_arch(jail->pakfire);
+ // Change mount propagation to slave to receive anything from the parent namespace
+ r = pakfire_mount_change_propagation(jail->ctx, "/", MS_SLAVE);
+ if (r)
+ return r;
+
+ // Make root a mountpoint in the new mount namespace
+ r = pakfire_mount_make_mounpoint(jail->pakfire, root);
+ if (r)
+ return r;
+
+ // Change mount propagation to private
+ r = pakfire_mount_change_propagation(jail->ctx, root, MS_PRIVATE);
+ if (r)
+ return r;
+
+ // Change root (unless root is /)
+ if (!pakfire_on_root(jail->pakfire)) {
+ // Mount everything
+ r = pakfire_jail_mount(jail, ctx);
+ if (r)
+ return r;
+
+ // chroot()
+ r = pakfire_jail_switch_root(jail, root);
+ if (r)
+ return r;
+ }
+
// Set personality
unsigned long persona = pakfire_arch_personality(arch);
if (persona) {
r = personality(persona);
if (r < 0) {
ERROR(jail->pakfire, "Could not set personality (%x)\n", (unsigned int)persona);
- return 126;
+ return 1;
}
}
}
}
- // Close other end of log pipes
- close(ctx->pipes.log_INFO[0]);
- close(ctx->pipes.log_ERROR[0]);
-#ifdef ENABLE_DEBUG
- close(ctx->pipes.log_DEBUG[0]);
-#endif /* ENABLE_DEBUG */
-
- // Connect standard input
- if (ctx->pipes.stdin[0] >= 0) {
- r = dup2(ctx->pipes.stdin[0], STDIN_FILENO);
- if (r < 0) {
- ERROR(jail->pakfire, "Could not connect fd %d to stdin: %m\n",
- ctx->pipes.stdin[0]);
+ // Create a new session
+ r = setsid();
+ if (r < 0) {
+ CTX_ERROR(jail->ctx, "Could not create a new session: %s\n", strerror(errno));
+ return r;
+ }
- return 1;
- }
+ // Allocate a new PTY
+ r = pakfire_jail_open_pty(jail, ctx);
+ if (r) {
+ CTX_ERROR(jail->ctx, "Could not allocate a new PTY: %s\n", strerror(-r));
+ return r;
}
- // Connect standard output and error
- if (ctx->pipes.stdout[1] >= 0 && ctx->pipes.stderr[1] >= 0) {
- r = dup2(ctx->pipes.stdout[1], STDOUT_FILENO);
- if (r < 0) {
- ERROR(jail->pakfire, "Could not connect fd %d to stdout: %m\n",
- ctx->pipes.stdout[1]);
+ // Send the PTY master to the parent process
+ r = pakfire_jail_send_fd(jail, socket_send, ctx->pty.master.fd);
+ if (r) {
+ CTX_ERROR(jail->ctx, "Failed sending the PTY master to the parent: %s\n", strerror(-r));
+ return r;
+ }
- return 1;
- }
+ // Setup the terminal
+ r = pakfire_jail_setup_terminal(jail, ctx);
+ if (r)
+ return r;
- r = dup2(ctx->pipes.stderr[1], STDERR_FILENO);
- if (r < 0) {
- ERROR(jail->pakfire, "Could not connect fd %d to stderr: %m\n",
- ctx->pipes.stderr[1]);
+ // Close the master of the PTY
+ close(ctx->pty.master.fd);
+ ctx->pty.master.fd = -1;
- return 1;
- }
+ // Close the socket
+ close(socket_send);
- // Close the pipe (as we have moved the original file descriptors)
- pakfire_jail_close_pipe(jail, ctx->pipes.stdin);
- pakfire_jail_close_pipe(jail, ctx->pipes.stdout);
- pakfire_jail_close_pipe(jail, ctx->pipes.stderr);
- }
+ // Close other end of log pipes
+ close(ctx->pipes.log_INFO[0]);
+ close(ctx->pipes.log_ERROR[0]);
+#ifdef ENABLE_DEBUG
+ close(ctx->pipes.log_DEBUG[0]);
+#endif /* ENABLE_DEBUG */
// Reset open file limit (http://0pointer.net/blog/file-descriptor-limits.html)
r = pakfire_rlimit_reset_nofile(jail->pakfire);
if (r)
return r;
- CTX_DEBUG(jail->ctx, "Child process initialization done\n");
- CTX_DEBUG(jail->ctx, "Launching command:\n");
+ DEBUG(jail->pakfire, "Child process initialization done\n");
+ DEBUG(jail->pakfire, "Launching command:\n");
// Log argv
for (unsigned int i = 0; argv[i]; i++)
- CTX_DEBUG(jail->ctx, " argv[%u] = %s\n", i, argv[i]);
+ DEBUG(jail->pakfire, " argv[%u] = %s\n", i, argv[i]);
// exec() command
r = execvpe(argv[0], (char**)argv, jail->env);
r = 1;
}
- CTX_ERROR(jail->ctx, "Could not execve(%s): %m\n", argv[0]);
+ ERROR(jail->pakfire, "Could not execve(%s): %m\n", argv[0]);
}
// We should not get here
return r;
}
-/*
- Child 1 is launched in a new mount namespace...
-*/
-static int pakfire_jail_child1(struct pakfire_jail* jail,
- struct pakfire_jail_exec* ctx, const char* argv[]) {
- int r;
-
- // Redirect any logging to our log pipe
- pakfire_ctx_set_log_callback(jail->ctx, pakfire_jail_log_redirect, &ctx->pipes);
-
- CTX_DEBUG(jail->ctx, "First child process launched\n");
-
- const int socket_send = pakfire_jail_get_pipe_to_write(jail, &ctx->socket);
-
- const char* root = pakfire_get_path(jail->pakfire);
-
- // Die with parent
- r = prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
- if (r) {
- CTX_ERROR(jail->ctx, "Could not configure to die with parent: %s\n", strerror(errno));
- goto ERROR;
- }
-
- // Change mount propagation so that we will receive, but don't propagate back
- r = pakfire_mount_change_propagation(jail->ctx, "/", MS_SLAVE);
- if (r) {
- CTX_ERROR(jail->ctx, "Could not change mount propagation to SLAVE: %s\n", strerror(r));
- goto ERROR;
- }
-
- // Make root a mountpoint in the new mount namespace
- r = pakfire_mount_make_mounpoint(jail->pakfire, root);
- if (r)
- goto ERROR;
-
- // Make everything private
- r = pakfire_mount_change_propagation(jail->ctx, root, MS_PRIVATE);
- if (r) {
- CTX_ERROR(jail->ctx, "Could not change mount propagation to PRIVATE: %s\n", strerror(r));
- goto ERROR;
- }
-
- // Mount everything
- r = pakfire_jail_mount(jail, ctx);
- if (r)
- goto ERROR;
-
- // chroot()
- r = pakfire_jail_switch_root(jail, root);
- if (r)
- goto ERROR;
-
- // Change mount propagation so that we will propagate everything down
- r = pakfire_mount_change_propagation(jail->ctx, "/", MS_SHARED);
- if (r) {
- CTX_ERROR(jail->ctx, "Could not change mount propagation to SHARED: %s\n", strerror(r));
- goto ERROR;
- }
-
- // Configure child process
- struct clone_args args = {
- .flags =
- CLONE_NEWCGROUP |
- CLONE_NEWIPC |
- CLONE_NEWNS |
- CLONE_NEWPID |
- CLONE_NEWTIME |
- CLONE_NEWUSER |
- CLONE_NEWUTS |
- CLONE_PIDFD,
- .exit_signal = SIGCHLD,
- .pidfd = (long long unsigned int)&ctx->pidfd2,
- };
-
- // Launch the process into the configured cgroup
- if (ctx->cgroup) {
- args.flags |= CLONE_INTO_CGROUP;
-
- // Clone into this cgroup
- args.cgroup = pakfire_cgroup_fd(ctx->cgroup);
- }
-
- // Setup networking
- if (!pakfire_jail_exec_has_flag(ctx, PAKFIRE_JAIL_HAS_NETWORKING))
- args.flags |= CLONE_NEWNET;
-
- // Fork the second child process
- pid_t pid = clone3(&args, sizeof(args));
- if (pid < 0) {
- CTX_ERROR(jail->ctx, "Could not fork the first child process: %s\n", strerror(errno));
- r = -errno;
- goto ERROR;
-
- // Child process
- } else if (pid == 0) {
- r = pakfire_jail_child2(jail, ctx, argv);
- _exit(r);
- }
-
- // Send the pidfd of the child to the first parent
- r = pakfire_jail_send_fd(jail, socket_send, ctx->pidfd2);
- if (r)
- goto ERROR;
-
-ERROR:
- return r;
-}
-
// Run a command in the jail
static int __pakfire_jail_exec(struct pakfire_jail* jail, const char* argv[],
- const int interactive,
pakfire_jail_communicate_in communicate_in,
pakfire_jail_communicate_out communicate_out,
void* data, int flags) {
+ int exit = -1;
int r;
// Check if argv is valid
.socket = { -1, -1 },
.pipes = {
- .stdin = { -1, -1 },
- .stdout = { -1, -1 },
- .stderr = { -1, -1 },
.log_INFO = { -1, -1 },
.log_ERROR = { -1, -1 },
#ifdef ENABLE_DEBUG
.data = data,
},
- // PIDs
- .pidfd1 = -1,
- .pidfd2 = -1,
+ .pidfd = -1,
+
+ // PTY
+ .pty = {
+ .master = {
+ .fd = -1,
+ },
+ .stdin = {
+ .fd = -1,
+ },
+ .stdout = {
+ .fd = -1,
+ },
+ },
};
DEBUG(jail->pakfire, "Executing jail...\n");
- // Become the subreaper
- r = prctl(PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0);
- if (r < 0) {
- CTX_ERROR(jail->ctx, "Failed to become the sub-reaper: %s\n", strerror(errno));
- r = -errno;
- goto ERROR;
- }
-
// Enable networking in interactive mode
- if (interactive)
+ if (ctx.flags & PAKFIRE_JAIL_PTY_FORWARDING)
ctx.flags |= PAKFIRE_JAIL_HAS_NETWORKING;
- // Create a UNIX domain socket
- r = socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, ctx.socket);
- if (r < 0) {
- CTX_ERROR(jail->ctx, "Could not create UNIX socket: %s\n", strerror(errno));
- r = -errno;
- goto ERROR;
- }
-
/*
Setup a file descriptor which can be used to notify the client that the parent
has completed configuration.
return -1;
}
- // Create pipes to communicate with child process if we are not running interactively
- if (!interactive) {
- // stdin (only if callback is set)
- if (ctx.communicate.in) {
- r = pakfire_jail_setup_pipe(jail, &ctx.pipes.stdin, 0);
- if (r)
- goto ERROR;
- }
-
- // stdout
- r = pakfire_jail_setup_pipe(jail, &ctx.pipes.stdout, 0);
- if (r)
- goto ERROR;
-
- // stderr
- r = pakfire_jail_setup_pipe(jail, &ctx.pipes.stderr, 0);
- if (r)
- goto ERROR;
+ // Create a UNIX domain socket
+ r = socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, ctx.socket);
+ if (r < 0) {
+ CTX_ERROR(jail->ctx, "Could not create UNIX socket: %s\n", strerror(errno));
+ r = -errno;
+ goto ERROR;
}
// Setup pipes for logging
goto ERROR;
#endif /* ENABLE_DEBUG */
+ // Configure child process
+ struct clone_args args = {
+ .flags =
+ CLONE_NEWCGROUP |
+ CLONE_NEWIPC |
+ CLONE_NEWNS |
+ CLONE_NEWPID |
+ CLONE_NEWTIME |
+ CLONE_NEWUSER |
+ CLONE_NEWUTS |
+ CLONE_PIDFD,
+ .exit_signal = SIGCHLD,
+ .pidfd = (long long unsigned int)&ctx.pidfd,
+ };
+
// Launch the process in a cgroup that is a leaf of the configured cgroup
if (jail->cgroup) {
+ args.flags |= CLONE_INTO_CGROUP;
+
// Fetch our UUID
const char* uuid = pakfire_jail_uuid(jail);
ERROR(jail->pakfire, "Could not create cgroup for jail: %m\n");
goto ERROR;
}
- }
- /*
- Initially, we will set up a new mount namespace and launch a child process in it.
-
- This process remains in the user/ipc/time/etc. namespace and will set up
- the mount namespace.
- */
+ // Clone into this cgroup
+ args.cgroup = pakfire_cgroup_fd(ctx.cgroup);
+ }
- // Configure child process
- struct clone_args args = {
- .flags =
- CLONE_NEWNS |
- CLONE_PIDFD |
- CLONE_CLEAR_SIGHAND,
- .exit_signal = SIGCHLD,
- .pidfd = (long long unsigned int)&ctx.pidfd1,
- };
+ // Setup networking
+ if (!pakfire_jail_exec_has_flag(&ctx, PAKFIRE_JAIL_HAS_NETWORKING)) {
+ args.flags |= CLONE_NEWNET;
+ }
- // Fork the first child process
- pid_t pid = clone3(&args, sizeof(args));
- if (pid < 0) {
- CTX_ERROR(jail->ctx, "Could not fork the first child process: %s\n", strerror(errno));
- r = -errno;
- goto ERROR;
+ // Fork this process
+ ctx.pid = clone3(&args, sizeof(args));
+ if (ctx.pid < 0) {
+ ERROR(jail->pakfire, "Could not clone: %m\n");
+ return -1;
// Child process
- } else if (pid == 0) {
- r = pakfire_jail_child1(jail, &ctx, argv);
+ } else if (ctx.pid == 0) {
+ r = pakfire_jail_child(jail, &ctx, argv);
_exit(r);
}
// Parent process
+ r = pakfire_jail_parent(jail, &ctx);
+ if (r)
+ goto ERROR;
+
+ DEBUG(jail->pakfire, "Waiting for PID %d to finish its work\n", ctx.pid);
+
+ // Read output of the child process
r = pakfire_jail_wait(jail, &ctx);
if (r)
goto ERROR;
+ // Handle exit status
+ switch (ctx.status.si_code) {
+ case CLD_EXITED:
+ DEBUG(jail->pakfire, "The child process exited with code %d\n",
+ ctx.status.si_status);
+
+ // Pass exit code
+ exit = ctx.status.si_status;
+ break;
+
+ case CLD_KILLED:
+ ERROR(jail->pakfire, "The child process was killed\n");
+ exit = 139;
+ break;
+
+ case CLD_DUMPED:
+ ERROR(jail->pakfire, "The child process terminated abnormally\n");
+ break;
+
+ // Log anything else
+ default:
+ ERROR(jail->pakfire, "Unknown child exit code: %d\n", ctx.status.si_code);
+ break;
+ }
+
ERROR:
// Destroy the temporary cgroup (if any)
if (ctx.cgroup) {
}
// Close any file descriptors
- pakfire_jail_close_pipe(jail, ctx.pipes.stdin);
- pakfire_jail_close_pipe(jail, ctx.pipes.stdout);
- pakfire_jail_close_pipe(jail, ctx.pipes.stderr);
+ if (ctx.pidfd >= 0)
+ close(ctx.pidfd);
+ if (ctx.pty.master.fd >= 0)
+ close(ctx.pty.master.fd);
pakfire_jail_close_pipe(jail, ctx.pipes.log_INFO);
pakfire_jail_close_pipe(jail, ctx.pipes.log_ERROR);
#ifdef ENABLE_DEBUG
pakfire_jail_close_pipe(jail, ctx.pipes.log_DEBUG);
#endif /* ENABLE_DEBUG */
- if (ctx.pidfd1 >= 0)
- close(ctx.pidfd1);
- if (ctx.pidfd2 >= 0)
- close(ctx.pidfd2);
-
- // Close sockets
pakfire_jail_close_pipe(jail, ctx.socket);
- return r;
+ return exit;
}
PAKFIRE_EXPORT int pakfire_jail_exec(
pakfire_jail_communicate_in callback_in,
pakfire_jail_communicate_out callback_out,
void* data, int flags) {
- return __pakfire_jail_exec(jail, argv, 0, callback_in, callback_out, data, flags);
+ return __pakfire_jail_exec(jail, argv, callback_in, callback_out, data, flags);
}
static int pakfire_jail_exec_interactive(
struct pakfire_jail* jail, const char* argv[], int flags) {
int r;
+ flags |= PAKFIRE_JAIL_PTY_FORWARDING;
+
// Setup interactive stuff
r = pakfire_jail_setup_interactive_env(jail);
if (r)
return r;
- return __pakfire_jail_exec(jail, argv, 1, NULL, NULL, NULL, flags);
+ return __pakfire_jail_exec(jail, argv, NULL, NULL, NULL, flags);
}
int pakfire_jail_exec_script(struct pakfire_jail* jail,