#############################################################################*/
#include <errno.h>
+#include <fcntl.h>
#include <linux/capability.h>
#include <linux/sched.h>
#include <sched.h>
#include <stdlib.h>
#include <syscall.h>
#include <sys/capability.h>
+#include <sys/epoll.h>
#include <sys/eventfd.h>
#include <sys/personality.h>
#include <sys/prctl.h>
#include <pakfire/pakfire.h>
#include <pakfire/util.h>
-#define ENVIRON_SIZE 128
+#define BUFFER_SIZE 1024 * 64
+#define ENVIRON_SIZE 128
+#define EPOLL_MAX_EVENTS 2
// The default environment that will be set for every command
static const struct environ {
// Environment
char* env[ENVIRON_SIZE];
+
+ // Logging
+ pakfire_jail_log_callback log_callback;
+ void* log_data;
+};
+
+struct pakfire_log_buffer {
+ char data[BUFFER_SIZE];
+ size_t used;
+};
+
+struct pakfire_jail_exec {
+ // PID (of the child)
+ pid_t pid;
+
+ // Process status (from waitpid)
+ int status;
+
+ // Log pipes
+ union {
+ int stdout[2];
+ int stderr[2];
+ } pipes;
+
+ // Log buffers
+ union {
+ struct pakfire_log_buffer stdout;
+ struct pakfire_log_buffer stderr;
+ } buffers;
};
static int clone3(struct clone_args* args, size_t size) {
return NULL;
}
+static int pakfire_jail_has_flag(struct pakfire_jail* jail, int flag) {
+ return jail->flags & flag;
+}
+
// Environment
// Returns the length of the environment
return 0;
}
+// Logging
+
+static int pakfire_jail_log_buffer_is_full(const struct pakfire_log_buffer* buffer) {
+ 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) {
+ char line[BUFFER_SIZE + 1];
+
+ // 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;
+ }
+
+ // Update buffer size
+ buffer->used += bytes_read;
+ }
+
+ // See if we have any lines that we can write
+ while (buffer->used) {
+ // Search for the end of the first line
+ char* 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 (pakfire_jail_log_buffer_is_full(buffer)) {
+ DEBUG(jail->pakfire, "Logging buffer is full. Sending all content\n");
+
+ eol = buffer->data + sizeof(buffer->data) - 1;
+
+ // 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';
+
+ // Log the line
+ if (jail->log_callback) {
+ int r = jail->log_callback(jail->pakfire, jail->log_data, priority, line, length);
+ if (r) {
+ ERROR(jail->pakfire, "The logging callback returned an error: %d\n", r);
+ return r;
+ }
+ }
+
+ // Remove line from buffer
+ memmove(buffer->data, buffer->data + length, buffer->used - length);
+ buffer->used -= length;
+ }
+
+ return 0;
+}
+
+static int pakfire_jail_logger(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;
+
+ // Fetch file descriptors from context
+ const int stdout = ctx->pipes.stdout[1];
+ const int stderr = ctx->pipes.stderr[1];
+
+ int fds[2] = {
+ stdout, stderr,
+ };
+
+ // Setup epoll
+ epollfd = epoll_create1(0);
+ if (epollfd < 0) {
+ ERROR(jail->pakfire, "Could not initialize epoll(): %m\n");
+ r = 1;
+ goto OUT;
+ }
+
+ ev.events = EPOLLIN;
+
+ // Turn file descriptors into non-blocking mode and add them to epoll()
+ for (unsigned int i = 0; i < 2; i++) {
+ int fd = fds[i];
+
+ // Read flags
+ int flags = fcntl(fd, F_GETFL, 0);
+
+ // Set modified flags
+ if (fcntl(fd, F_SETFL, flags|O_NONBLOCK) < 0) {
+ ERROR(jail->pakfire,
+ "Could not set file descriptor %d into non-blocking mode: %m\n", fd);
+ r = 1;
+ goto OUT;
+ }
+
+ ev.data.fd = fd;
+
+ 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 OUT;
+ }
+ }
+
+ int ended = 0;
+
+ // Loop for as long as the process is alive
+ while (!ended) {
+ // If waitpid() returns non-zero, the process has ended, but we want to perform
+ // one last iteration over the loop to read any remaining content from the file
+ // descriptor buffers.
+ r = waitpid(ctx->pid, &ctx->status, WNOHANG);
+ if (r)
+ ended = 1;
+
+ 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 OUT;
+ }
+
+ struct pakfire_log_buffer* buffer;
+ int priority;
+
+ for (int i = 0; i < num; i++) {
+ int fd = events[i].data.fd;
+
+ if (fd == stdout) {
+ buffer = &ctx->buffers.stdout;
+ priority = LOG_INFO;
+
+ } else if (fd == stderr) {
+ buffer = &ctx->buffers.stderr;
+ priority = LOG_ERR;
+
+ } 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);
+ if (r)
+ goto OUT;
+ }
+ }
+
+OUT:
+ if (epollfd > 0)
+ close(epollfd);
+
+ return r;
+}
+
// Capabilities
static int pakfire_jail_drop_capabilities(struct pakfire_jail* jail) {
/*
Performs the initialisation that needs to happen in the parent part
*/
-static int pakfire_jail_parent(struct pakfire_jail* jail, pid_t pid, int completed_fd) {
+static int pakfire_jail_parent(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx,
+ int completed_fd) {
int r;
// Setup UID mapping
- r = pakfire_jail_setup_uid_mapping(jail, pid);
+ r = pakfire_jail_setup_uid_mapping(jail, ctx->pid);
if (r)
return r;
// Write "deny" to /proc/PID/setgroups
- r = pakfire_jail_setgroups(jail, pid);
+ r = pakfire_jail_setgroups(jail, ctx->pid);
if (r)
return r;
// Setup GID mapping
- r = pakfire_jail_setup_gid_mapping(jail, pid);
+ r = pakfire_jail_setup_gid_mapping(jail, ctx->pid);
if (r)
return r;
return 0;
}
-static int pakfire_jail_child(struct pakfire_jail* jail, const char* argv[], int completed_fd) {
+static int pakfire_jail_child(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx,
+ const char* argv[], int completed_fd) {
int r;
// XXX do we have to reconfigure logging here?
// Run a command in the jail
int pakfire_jail_exec(struct pakfire_jail* jail, const char* argv[]) {
int exit = -1;
- int status = 0;
int r;
// Check if argv is valid
return -1;
}
+ // Initialize context for this call
+ struct pakfire_jail_exec ctx = {
+ .pipes = {
+ .stdout = { 0, 0, },
+ .stderr = { 0, 0, },
+ },
+ .status = 0,
+ };
+
DEBUG(jail->pakfire, "Executing jail...\n");
/*
return -1;
}
+ // Create pipes to communicate with child process if we are not running interactively
+ if (!pakfire_jail_has_flag(jail, PAKFIRE_JAIL_INTERACTIVE)) {
+ // stdout
+ r = pipe(ctx.pipes.stdout);
+ if (r < 0) {
+ ERROR(jail->pakfire, "Could not create file descriptors for stdout: %m\n");
+ goto ERROR;
+ }
+
+ // stderr
+ r = pipe(ctx.pipes.stderr);
+ if (r < 0) {
+ ERROR(jail->pakfire, "Could not create file descriptors for stderr: %m\n");
+ goto ERROR;
+ }
+ }
+
// Configure child process
struct clone_args args = {
.flags =
};
// Fork this process
- pid_t pid = clone3(&args, sizeof(args));
- if (pid < 0) {
+ 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_child(jail, argv, completed_fd);
+ } else if (ctx.pid == 0) {
+ r = pakfire_jail_child(jail, &ctx, argv, completed_fd);
_exit(r);
}
// Parent process
- r = pakfire_jail_parent(jail, pid, completed_fd);
+ r = pakfire_jail_parent(jail, &ctx, completed_fd);
if (r)
goto ERROR;
- DEBUG(jail->pakfire, "Waiting for PID %d to finish its work\n", pid);
+ DEBUG(jail->pakfire, "Waiting for PID %d to finish its work\n", ctx.pid);
- if (!status)
- waitpid(pid, &status, 0);
+ // Read output of the child process
+ if (!pakfire_jail_has_flag(jail, PAKFIRE_JAIL_INTERACTIVE)) {
+ r = pakfire_jail_logger(jail, &ctx);
+ if (r)
+ ERROR(jail->pakfire, "Log reading aborted: %m\n");
+ }
- if (WIFEXITED(status)) {
- exit = WEXITSTATUS(status);
+ if (!ctx.status)
+ waitpid(ctx.pid, &ctx.status, 0);
+
+ if (WIFEXITED(ctx.status)) {
+ exit = WEXITSTATUS(ctx.status);
DEBUG(jail->pakfire, "Child process exited with code: %d\n", exit);
} else {
- ERROR(jail->pakfire, "Could not determine the exit status of process %d\n", pid);
+ ERROR(jail->pakfire, "Could not determine the exit status of process %d\n", ctx.pid);
errno = ESRCH;
exit = -1;
}
ERROR:
+ // Close any file descriptors
+ if (ctx.pipes.stdout[0])
+ close(ctx.pipes.stdout[0]);
+ if (ctx.pipes.stdout[1])
+ close(ctx.pipes.stdout[1]);
+ if (ctx.pipes.stderr[0])
+ close(ctx.pipes.stderr[0]);
+ if (ctx.pipes.stderr[1])
+ close(ctx.pipes.stderr[1]);
+
// Umount everything
if (!pakfire_on_root(jail->pakfire))
pakfire_umount_all(jail->pakfire);