Skip to content
Open
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ __pycache__
/libkrun.pc
init/init
examples/chroot_vm
examples/consoles
test-prefix
7 changes: 5 additions & 2 deletions examples/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ ROOTFS_DIR = rootfs_$(ROOTFS_DISTRO)

.PHONY: clean rootfs

EXAMPLES := chroot_vm external_kernel
EXAMPLES := chroot_vm external_kernel consoles
ifeq ($(SEV),1)
EXAMPLES := launch-tee
endif
Expand Down Expand Up @@ -53,6 +53,9 @@ ifeq ($(OS),Darwin)
codesign --entitlements chroot_vm.entitlements --force -s - $@
endif

consoles: consoles.c
gcc -o $@ $< $(CFLAGS) $(LDFLAGS_$(ARCH)_$(OS))

nitro: nitro.c
gcc -o $@ $< $(CFLAGS) $(LDFLAGS_nitro)

Expand All @@ -64,4 +67,4 @@ rootfs:
podman rm libkrun_chroot_vm

clean:
rm -rf chroot_vm $(ROOTFS_DIR) launch-tee boot_efi external_kernel nitro
rm -rf chroot_vm $(ROOTFS_DIR) launch-tee boot_efi external_kernel nitro consoles
218 changes: 218 additions & 0 deletions examples/consoles.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdarg.h>
#include <sys/wait.h>
#include <sys/stat.h>

#include <libkrun.h>

static int cmd_output(char *output, size_t output_size, const char *prog, ...)
{
va_list args;
const char *argv[32];
int argc = 0;
int pipe_fds[2] = { -1, -1 };

argv[argc++] = prog;
va_start(args, prog);
while (argc < 31) {
const char *arg = va_arg(args, const char *);
argv[argc++] = arg;
if (arg == NULL) break;
}
va_end(args);
argv[argc] = NULL;

if (output && output_size > 0) {
if (pipe(pipe_fds) < 0) return -1;
}

pid_t pid = fork();
if (pid < 0) return -1;
if (pid == 0) {
if (pipe_fds[0] >= 0) {
close(pipe_fds[0]);
dup2(pipe_fds[1], STDOUT_FILENO);
close(pipe_fds[1]);
}
execvp(prog, (char *const *)argv);
abort();
}

if (pipe_fds[0] >= 0) {
close(pipe_fds[1]);
ssize_t n = read(pipe_fds[0], output, output_size - 1);
close(pipe_fds[0]);
if (n < 0) n = 0;
output[n] = '\0';
}

int status;
if (waitpid(pid, &status, 0) < 0) return -1;
if (!WIFEXITED(status)) return -1;
return WEXITSTATUS(status);
}

#define cmd(...) ({ char _d[1]; cmd_output(_d, 0, __VA_ARGS__); })

static int create_tmux_tty(const char *session_name)
{
char tty_path[256];
char wait_cmd[128];

snprintf(wait_cmd, sizeof(wait_cmd), "waitpid %d", (int)getpid());
if (cmd("tmux", "new-session", "-d", "-s", session_name, "sh", "-c", wait_cmd, NULL) != 0)
return -1;

// Hook up tmux to send us SIGWINCH signal on resize
char hook_cmd[128];
snprintf(hook_cmd, sizeof(hook_cmd), "run-shell 'kill -WINCH %d'", (int)getpid());
cmd("tmux", "set-hook", "-g", "client-resized", hook_cmd, NULL);

if (cmd_output(tty_path, sizeof(tty_path), "tmux", "display-message", "-p", "-t", session_name, "#{pane_tty}", NULL) != 0)
return -1;
tty_path[strcspn(tty_path, "\n")] = '\0';

int fd = open(tty_path, O_RDWR);
if (fd < 0) return -1;
return fd;
}

static int mkfifo_if_needed(const char *path)
{
if (mkfifo(path, 0666) < 0) {
if (errno != EEXIST) return -1;
}
return 0;
}


static int create_fifo_inout(const char *fifo_in, const char *fifo_out, int *input_fd, int *output_fd)
{
if (mkfifo_if_needed(fifo_in) < 0) return -1;
if (mkfifo_if_needed(fifo_out) < 0) return -1;

int in_fd = open(fifo_in, O_RDONLY | O_NONBLOCK);
if (in_fd < 0) return -1;

int out_fd = open(fifo_out, O_RDWR | O_NONBLOCK);
if (out_fd < 0) { close(in_fd); return -1; }

*input_fd = in_fd;
*output_fd = out_fd;
return 0;
}

int main(int argc, char *const argv[])
{
if (argc < 3) {
fprintf(stderr, "Usage: %s ROOT_DIR COMMAND [ARGS...]\n", argv[0]);
return 1;
}

const char *root_dir = argv[1];
const char *command = argv[2];
const char *const *command_args = (argc > 3) ? (const char *const *)&argv[3] : NULL;
const char *const envp[] = { 0 };

krun_set_log_level(KRUN_LOG_LEVEL_WARN);

int err;
int ctx_id = krun_create_ctx();
if (ctx_id < 0) { errno = -ctx_id; perror("krun_create_ctx"); return 1; }

if ((err = krun_disable_implicit_console(ctx_id))) {
errno = -err;
perror("krun_disable_implicit_console");
return 1;
}

int console_id = krun_add_virtio_console_multiport(ctx_id);
if (console_id < 0) {
errno = -console_id;
perror("krun_add_virtio_console_multiport");
return 1;
}

/* Configure console ports - edit this section to add/remove ports */
{

// You could also use the controlling terminal of this process in the guest:
/*
if ((err = krun_add_console_port_tty(ctx_id, console_id, "host_tty", open("/dev/tty", O_RDWR)))) {
errno = -err;
perror("port host_tty");
return 1;
}
*/

int num_consoles = 3;
for (int i = 0; i < num_consoles; i++) {
char session_name[64];
char port_name[64];
snprintf(session_name, sizeof(session_name), "krun-console-%d", i + 1);
snprintf(port_name, sizeof(port_name), "console-%d", i + 1);

int tmux_fd = create_tmux_tty(session_name);
if (tmux_fd < 0) {
perror("create_tmux_tty");
return 1;
}
if ((err = krun_add_console_port_tty(ctx_id, console_id, port_name, tmux_fd))) {
errno = -err;
perror("krun_add_console_port_tty");
return 1;
}
}

int in_fd, out_fd;
if (create_fifo_inout("/tmp/consoles_example_in", "/tmp/consoles_example_out", &in_fd, &out_fd) < 0) {
perror("create_fifo_inout");
return 1;
}
if ((err = krun_add_console_port_inout(ctx_id, console_id, "fifo_inout", in_fd, out_fd))) {
errno = -err;
perror("krun_add_console_port_inout");
return 1;
}

fprintf(stderr, "\n=== Console ports configured ===\n");
for (int i = 0; i < num_consoles; i++) {
fprintf(stderr, " console-%d: tmux attach -t krun-console-%d\n", i + 1, i + 1);
}
fprintf(stderr, " fifo_inout: /tmp/consoles_example_in (host->guest)\n");
fprintf(stderr, " fifo_inout: /tmp/consoles_example_out (guest->host)\n");
fprintf(stderr, "================================\n\n");
}

if ((err = krun_set_vm_config(ctx_id, 4, 4096))) {
errno = -err;
perror("krun_set_vm_config");
return 1;
}

if ((err = krun_set_root(ctx_id, root_dir))) {
errno = -err;
perror("krun_set_root");
return 1;
}

if ((err = krun_set_exec(ctx_id, command, command_args, envp))) {
errno = -err;
perror("krun_set_exec");
return 1;
}

if ((err = krun_start_enter(ctx_id))) {
errno = -err;
perror("krun_start_enter");
return 1;
}
return 0;
}


Binary file added examples/external_kernel
Binary file not shown.
64 changes: 64 additions & 0 deletions include/libkrun.h
Original file line number Diff line number Diff line change
Expand Up @@ -985,6 +985,70 @@ int32_t krun_add_serial_console_default(uint32_t ctx_id,
int input_fd,
int output_fd);

/*
* Adds a multi-port virtio-console device to the guest with explicitly configured ports.
*
* This function creates a new virtio-console device that can have multiple ports added to it
* via krun_add_console_port_tty() and krun_add_console_port_inout(). Unlike krun_add_virtio_console_default(),
* this does not do any automatic detections to configure ports based on the file descriptors.
*
* The function can be called multiple times for adding multiple virtio-console devices.
* Each device appears in the guest with port 0 accessible as /dev/hvcN (hvc0, hvc1, etc.) in the order
* devices are added. If the implicit console is not disabled via `krun_disable_implicit_console`,
* the first explicitly added device will occupy the "hvc1" ID. Additional ports within each device
* (port 1, 2, ...) appear as /dev/vportNpM character devices.
*
* Arguments:
* "ctx_id" - the configuration context ID.
*
* Returns:
* The console_id (>= 0) on success or a negative error number on failure.
*/
int32_t krun_add_virtio_console_multiport(uint32_t ctx_id);

/*
* Adds a TTY port to a multi-port virtio-console device.
*
* The TTY file descriptor is used for both input and output. This port will be marked with the
* VIRTIO_CONSOLE_CONSOLE_PORT flag, enabling console-specific features notably window resize.
*
* Arguments:
* "ctx_id" - the configuration context ID
* "console_id" - the console ID returned by krun_add_virtio_console_multiport()
* "name" - the name of the port for identifying the port in the guest, can be empty ("")
* "tty_fd" - file descriptor for the TTY to use for both input, output, and determining terminal size
*
* Returns:
* Zero on success or a negative error number on failure.
*/
int32_t krun_add_console_port_tty(uint32_t ctx_id,
uint32_t console_id,
const char *name,
int tty_fd);

/*
* Adds a generic I/O port to a multi-port virtio-console device, suitable for arbitrary bidirectional
* data streams that don't require terminal functionality.
*
* This port will NOT be marked with the VIRTIO_CONSOLE_CONSOLE_PORT flag, meaning it won't support
* console-specific features like window resize signals.
*
* Arguments:
* "ctx_id" - the configuration context ID
* "console_id" - the console ID returned by krun_add_virtio_console_multiport()
* "name" - the name of the port for identifying the port in the guest, can be empty ("")
* "input_fd" - file descriptor to use for input (host writes, guest reads)
* "output_fd" - file descriptor to use for output (guest writes, host reads)
*
* Returns:
* Zero on success or a negative error number on failure.
*/
int32_t krun_add_console_port_inout(uint32_t ctx_id,
uint32_t console_id,
const char *name,
int input_fd,
int output_fd);

/**
* Configure block device to be used as root filesystem.
*
Expand Down
Loading
Loading