This should be the most efficient version, yet.
We create a memfd buffer which we write to and later copy the output to
the heap. This also reduces the amount of callbacks that we have to pass
around.
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
pakfire_jail_set_stdout_callback(jail, Pakfire_execute_output_callback, callback);
// Execute command
- r = pakfire_jail_exec(jail, argv, 0);
+ r = pakfire_jail_exec(jail, argv, 0, NULL);
Py_END_ALLOW_THREADS
pakfire_jail_set_stdin_callback(jail, pakfire_archive_stream_payload, a);
// Run!
- r = pakfire_jail_exec(jail, argv, PAKFIRE_JAIL_NOENT_OK);
+ r = pakfire_jail_exec(jail, argv, PAKFIRE_JAIL_NOENT_OK, NULL);
ERROR:
if (jail)
// Run the command (if given)
if (argv)
- return pakfire_jail_exec(build->jail, argv, 0);
+ return pakfire_jail_exec(build->jail, argv, 0, NULL);
// Otherwise run the shell
return pakfire_jail_shell(build->jail);
PAKFIRE_JAIL_HAS_LOOP_DEVICES = (1 << 3),
};
-int pakfire_jail_exec(
- struct pakfire_jail* jail,
- const char* argv[],
- int flags);
+int pakfire_jail_exec(struct pakfire_jail* jail, const char* argv[], int flags, char** output);
#ifdef PAKFIRE_PRIVATE
struct pakfire_pty;
enum pakfire_pty_flags {
- PAKFIRE_PTY_READ_ONLY = (1 << 0),
+ PAKFIRE_PTY_READ_ONLY = (1 << 0),
+ PAKFIRE_PTY_CAPTURE_OUTPUT = (1 << 1),
};
int pakfire_pty_create(struct pakfire_pty** pty,
int pakfire_pty_drain(struct pakfire_pty* pty);
+char* pakfire_pty_output(struct pakfire_pty* pty, size_t* length);
+
#endif /* PAKFIRE_PRIVATE */
#endif /* PAKFIRE_PTY_H */
}
// Run a command in the jail
-PAKFIRE_EXPORT int pakfire_jail_exec(struct pakfire_jail* jail, const char* argv[], int flags) {
+PAKFIRE_EXPORT int pakfire_jail_exec(struct pakfire_jail* jail,
+ const char* argv[], int flags, char** output) {
int pty_flags = 0;
int r;
// Enable networking
ctx.flags |= PAKFIRE_JAIL_HAS_NETWORKING;
+ // We cannot capture the output in interactive mode
+ if (output) {
+ CTX_ERROR(jail->ctx, "Cannot capture output in interactive mode\n");
+ r = -ENOTSUP;
+ goto ERROR;
+ }
+
} else {
// Make the PTY read-only
pty_flags |= PAKFIRE_PTY_READ_ONLY;
+
+ // Capture Output?
+ if (output)
+ pty_flags |= PAKFIRE_PTY_CAPTURE_OUTPUT;
}
/*
goto ERROR;
}
+ // Return the output
+ if (output) {
+ *output = pakfire_pty_output(ctx.pty, NULL);
+ }
+
ERROR:
// Reset all callbacks
pakfire_jail_set_stdin_callback(jail, NULL, NULL);
if (r)
return r;
- return pakfire_jail_exec(jail, argv, flags);
+ return pakfire_jail_exec(jail, argv, flags, NULL);
}
int pakfire_jail_exec_script(struct pakfire_jail* jail,
argv[i] = args[i-1];
// Run the script
- r = pakfire_jail_exec(jail, argv, 0);
+ r = pakfire_jail_exec(jail, argv, 0, NULL);
ERROR:
if (argv)
if (r)
goto ERROR;
- // Set the callback that captures the output
- pakfire_jail_set_stdout_callback(jail, pakfire_jail_capture_stdout, output);
-
// Execute the command
- r = pakfire_jail_exec(jail, argv, 0);
+ r = pakfire_jail_exec(jail, argv, flags, output);
ERROR:
if (jail)
#include <fcntl.h>
#include <stdlib.h>
#include <sys/ioctl.h>
+#include <sys/mman.h>
#include <sys/stat.h>
#include <termios.h>
#include <unistd.h>
PAKFIRE_PTY_STATE_DRAINING,
PAKFIRE_PTY_STATE_DONE,
} state;
+
+ // Captured Output
+ struct iovec output;
+ int outputfd;
};
static int pakfire_pty_has_flag(struct pakfire_pty* pty, int flag) {
return pty->flags & flag;
}
+static int pakfire_pty_store_output(struct pakfire_pty* pty) {
+ struct stat buffer;
+ int r;
+
+ // Stat the buffer
+ r = fstat(pty->stdout.fd, &buffer);
+ if (r < 0) {
+ CTX_ERROR(pty->ctx, "Could not stat the output buffer: %m\n");
+ return -errno;
+ }
+
+ CTX_DEBUG(pty->ctx, "Read %jd byte(s) of output\n", buffer.st_size);
+
+ // Map the output into memory
+ pty->output.iov_base = mmap(NULL, buffer.st_size, PROT_READ, MAP_SHARED, pty->stdout.fd, 0);
+ if (pty->output.iov_base == MAP_FAILED) {
+ CTX_ERROR(pty->ctx, "Could not map the output into memory: %m\n");
+ return -errno;
+ }
+
+ // Store the size of the buffer
+ pty->output.iov_len = buffer.st_size;
+
+ return 0;
+}
+
static int pakfire_pty_same_inode(struct pakfire_pty* pty, int fd1, int fd2) {
struct stat stat1;
struct stat stat2;
static int pakfire_pty_done(struct pakfire_pty* pty, int code) {
sd_event* loop = NULL;
+ int r;
// Don't run this more than once
if (pty->state == PAKFIRE_PTY_STATE_DONE)
return 0;
+ // Store the output
+ if (pakfire_pty_has_flag(pty, PAKFIRE_PTY_CAPTURE_OUTPUT)) {
+ r = pakfire_pty_store_output(pty);
+ if (r < 0) {
+ CTX_ERROR(pty->ctx, "Could not store output: %s\n", strerror(-r));
+ return r;
+ }
+ }
+
// Disconnect
pakfire_pty_disconnect(pty);
}
}
- // Connect to standard output
- pty->stdout.fd = pakfire_pty_reopen(pty, STDOUT_FILENO, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
- if (pty->stdout.fd < 0) {
- CTX_DEBUG(pty->ctx, "Could not re-open standard output: %s. Ignoring.\n", strerror(-pty->stdout.fd));
+ // Create a buffer to capture the output in
+ if (pakfire_pty_has_flag(pty, PAKFIRE_PTY_CAPTURE_OUTPUT)) {
+ pty->stdout.fd = memfd_create("pty-output", MFD_CLOEXEC);
+ if (pty->stdout.fd < 0) {
+ CTX_ERROR(pty->ctx, "Could not create the output buffer: %m\n");
+ return -errno;
+ }
- // Use the original file descriptor
- pty->stdout.fd = STDOUT_FILENO;
+ // Close the buffer in the end
+ pty->stdout.close_fd = 1;
- // Request to close the file descriptor afterwards
+ // Connect to standard output
} else {
- pty->stdout.close_fd = 1;
+ pty->stdout.fd = pakfire_pty_reopen(pty, STDOUT_FILENO, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
+ if (pty->stdout.fd < 0) {
+ CTX_DEBUG(pty->ctx, "Could not re-open standard output: %s. Ignoring.\n", strerror(-pty->stdout.fd));
+
+ // Use the original file descriptor
+ pty->stdout.fd = STDOUT_FILENO;
+
+ // Request to close the file descriptor afterwards
+ } else {
+ pty->stdout.close_fd = 1;
+ }
}
// Copy the terminal dimensions to the PTY
// Add standard output to the event loop
r = sd_event_add_io(pty->loop, &pty->stdout.event,
pty->stdout.fd, EPOLLOUT|EPOLLET, pakfire_pty_stdout, pty);
- if (r)
- return r;
+ if (r < 0) {
+ switch (-r) {
+ case EPERM:
+ pty->stdout.io |= PAKFIRE_PTY_READY_TO_WRITE;
+ break;
+
+ default:
+ CTX_ERROR(pty->ctx,
+ "Could not add standard output to the event loop: %s\n", strerror(-r));
+ return r;
+ }
+ }
// Set description
sd_event_source_set_description(pty->stdout.event, "pty-stdout");
if (pty->socket[1] >= 0)
close(pty->socket[1]);
+ // Output
+ if (pty->output.iov_base)
+ munmap(pty->output.iov_base, pty->output.iov_len);
+ if (pty->outputfd)
+ close(pty->outputfd);
+
if (pty->loop)
sd_event_unref(pty->loop);
if (pty->ctx)
return pakfire_pty_drained(pty);
}
+
+/*
+ Creates an independent copy of the output buffer
+*/
+char* pakfire_pty_output(struct pakfire_pty* pty, size_t* length) {
+ char* buffer = NULL;
+
+ // Is not operation supported?
+ if (!pakfire_pty_has_flag(pty, PAKFIRE_PTY_CAPTURE_OUTPUT)) {
+ errno = -ENOTSUP;
+ return NULL;
+ }
+
+ if (!pty->output.iov_base)
+ return NULL;
+
+ // Allocate a new buffer
+ buffer = calloc(pty->output.iov_len + 1, sizeof(*buffer));
+ if (!buffer)
+ return NULL;
+
+ // Copy the output
+ memcpy(buffer, pty->output.iov_base, pty->output.iov_len);
+
+ // Return the length
+ if (length)
+ *length = pty->output.iov_len;
+
+ return buffer;
+}
// Run a few things
for (unsigned int i = 0; i < 3; i++) {
- ASSERT_SUCCESS(pakfire_jail_exec(jail, argv, 0));
+ ASSERT_SUCCESS(pakfire_jail_exec(jail, argv, 0, NULL));
}
// Try reading the stats into some invalid space
ASSERT_SUCCESS(pakfire_jail_create(&jail, t->pakfire));
// Check if we receive the correct exit code
- ASSERT(pakfire_jail_exec(jail, argv, 0) == 123);
+ ASSERT(pakfire_jail_exec(jail, argv, 0, NULL) == 123);
// Success
r = EXIT_SUCCESS;
ASSERT_SUCCESS(pakfire_jail_create(&jail, t->pakfire));
// Check if we receive the correct exit code
- ASSERT(pakfire_jail_exec(jail, argv, 0) == 139);
+ ASSERT(pakfire_jail_exec(jail, argv, 0, NULL) == 139);
// Success
r = EXIT_SUCCESS;
static int test_exec(const struct test* t) {
struct pakfire_jail* jail = NULL;
+ char* output = NULL;
// Create a new jail
ASSERT_SUCCESS(pakfire_jail_create(&jail, t->pakfire));
// Try to execute something
- ASSERT_SUCCESS(pakfire_jail_exec(jail, cmd_hello_world, 0));
+ ASSERT_SUCCESS(pakfire_jail_exec(jail, cmd_hello_world, 0, &output));
+
+ // We should have some output
+ ASSERT_STRING_EQUALS(output, "Hello World!\r\n");
// Destroy it
ASSERT_NULL(pakfire_jail_unref(jail));
ASSERT_SUCCESS(pakfire_jail_set_cgroup(jail, cgroup));
// Run command
- ASSERT(pakfire_jail_exec(jail, cmd_hello_world, 0) == 0);
+ ASSERT(pakfire_jail_exec(jail, cmd_hello_world, 0, NULL) == 0);
r = EXIT_SUCCESS;
// Set something sane
ASSERT_SUCCESS(pakfire_jail_nice(jail, 5));
- // Capture the output of the next execution
- pakfire_jail_set_stdout_callback(jail, pakfire_jail_capture_stdout, &output);
-
// Check if the nice level has been set
- ASSERT_SUCCESS(pakfire_jail_exec(jail, argv, 0));
- ASSERT_STRING_EQUALS(output, "5\n");
+ ASSERT_SUCCESS(pakfire_jail_exec(jail, argv, 0, &output));
+ ASSERT_STRING_EQUALS(output, "5\r\n");
// Success
r = EXIT_SUCCESS;
ASSERT_SUCCESS(pakfire_cgroup_set_memory_limit(cgroup, 100 * 1024 * 1024));
// Try to exhaust all memory
- ASSERT_FAILURE(pakfire_jail_exec(jail, cmd_exhaust_memory, 0));
+ ASSERT_FAILURE(pakfire_jail_exec(jail, cmd_exhaust_memory, 0, NULL));
// A fork bomb should also exhaust all memory
- ASSERT_FAILURE(pakfire_jail_exec(jail, cmd_fork_bomb, 0));
+ ASSERT_FAILURE(pakfire_jail_exec(jail, cmd_fork_bomb, 0, NULL));
// Success
r = EXIT_SUCCESS;
ASSERT_SUCCESS(pakfire_cgroup_set_pid_limit(cgroup, 100));
// Try to fork as many processes as possible
- ASSERT_FAILURE(pakfire_jail_exec(jail, cmd_fork_bomb, 0));
+ ASSERT_FAILURE(pakfire_jail_exec(jail, cmd_fork_bomb, 0, NULL));
// Success
r = EXIT_SUCCESS;
ASSERT_SUCCESS(pakfire_jail_bind(jail, source, target, MS_RDONLY));
// Check if the mount actually works
- ASSERT_SUCCESS(pakfire_jail_exec(jail, argv, 0));
+ ASSERT_SUCCESS(pakfire_jail_exec(jail, argv, 0, NULL));
// Success
r = EXIT_SUCCESS;
pakfire_jail_set_stdin_callback(jail, callback_stdin, &lines);
// Check if the mount actually works
- ASSERT_SUCCESS(pakfire_jail_exec(jail, argv, 0));
+ ASSERT_SUCCESS(pakfire_jail_exec(jail, argv, 0, NULL));
// Success
r = EXIT_SUCCESS;
};
// Perform the command
- return pakfire_jail_exec(jail, argv, 0);
+ return pakfire_jail_exec(jail, argv, 0, NULL);
}
static int test_send_signal(const struct test* t) {
ASSERT_SUCCESS(pakfire_jail_set_timeout(jail, 1));
// Check if we receive the correct exit code
- ASSERT(pakfire_jail_exec(jail, argv, 0) == 139);
+ ASSERT(pakfire_jail_exec(jail, argv, 0, NULL) == 139);
// Success
r = EXIT_SUCCESS;