Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
222 changes: 144 additions & 78 deletions examples/consoles.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@
#include <string.h>
#include <unistd.h>
#include <stdarg.h>
#include <pthread.h>
#include <poll.h>
#include <sys/wait.h>
#include <sys/stat.h>

#include <libkrun.h>

#define NUM_RESERVED_PORTS 64

static int cmd_output(char *output, size_t output_size, const char *prog, ...)
{
va_list args;
Expand Down Expand Up @@ -63,12 +67,11 @@ 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);
Expand All @@ -90,23 +93,120 @@ static int mkfifo_if_needed(const char *path)
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;
*input_fd = open(fifo_in, O_RDWR | O_NONBLOCK);
if (*input_fd < 0) return -1;

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

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

struct console_state {
uint32_t ctx_id;
uint32_t console_id;
int ready_fd;
};

static void *dynamic_console_thread(void *arg)
{
struct console_state *state = arg;
int ready_fd = state->ready_fd;

struct pollfd pfd = { .fd = ready_fd, .events = POLLIN };
fprintf(stderr, "Waiting for console device...\n");
if (poll(&pfd, 1, -1) < 0) {
perror("poll");
return NULL;
}

uint64_t val;
if (read(ready_fd, &val, sizeof(val)) != sizeof(val)) {
perror("read eventfd");
return NULL;
}

fprintf(stderr, "\n");
fprintf(stderr, "=== VM Started ===\n");
fprintf(stderr, "\n");
fprintf(stderr, "*** To interact with the VM (hvc0), run in another terminal: ***\n");
fprintf(stderr, " tmux attach -t krun-console-1\n");
fprintf(stderr, "\n");
fprintf(stderr, "Commands: 'c' = add console\n");
fprintf(stderr, " 'p' = add pipe\n");
fprintf(stderr, "\n");

int console_count = 1; /* console-1 already exists (hvc0) */
int pipe_count = 0;
char line[16];
while (1) {
fprintf(stderr, "> ");
if (fgets(line, sizeof(line), stdin) == NULL) break;

if (line[0] == 'c' || line[0] == 'C') {
console_count++;
char sess[64], port[64];
snprintf(sess, sizeof(sess), "krun-console-%d", console_count);
snprintf(port, sizeof(port), "console-%d", console_count);

int fd = create_tmux_tty(sess);
if (fd < 0) { fprintf(stderr, "tmux: failed to create session '%s'\n", sess); continue; }

int err = krun_add_console_port_tty(state->ctx_id, state->console_id, port, fd);
if (err) { fprintf(stderr, "add port: %s\n", strerror(-err)); close(fd); continue; }

fprintf(stderr, "Created console '%s' (port %d, /dev/hvc%d):\n", port, console_count + pipe_count - 1, console_count - 1);
fprintf(stderr, " On host: tmux attach -t %s\n", sess);
fprintf(stderr, " In guest: setsid /sbin/agetty -a $(whoami) -L hvc%d xterm-256color\n", console_count - 1);
if (console_count + pipe_count > NUM_RESERVED_PORTS) {
fprintf(stderr, "Reached max reserved ports (%d)\n", NUM_RESERVED_PORTS);
break;
}
}

if (line[0] == 'p' || line[0] == 'P') {
pipe_count++;
char port[64], fifo_in[128], fifo_out[128];
snprintf(port, sizeof(port), "pipe-%d", pipe_count);
snprintf(fifo_in, sizeof(fifo_in), "/tmp/krun_pipe%d_in", pipe_count);
snprintf(fifo_out, sizeof(fifo_out), "/tmp/krun_pipe%d_out", pipe_count);

int in_fd, out_fd;
if (create_fifo_inout(fifo_in, fifo_out, &in_fd, &out_fd) < 0) {
perror("create_fifo_inout"); continue;
}

int err = krun_add_console_port_inout(state->ctx_id, state->console_id, port, in_fd, out_fd);
if (err) {
fprintf(stderr, "add port: %s\n", strerror(-err));
close(in_fd);
close(out_fd);
continue;
}

fprintf(stderr, "Created pipe '%s' (port %d):\n", port, console_count + pipe_count - 1);
fprintf(stderr, " In guest: DEV=/dev/$(grep -l %s /sys/class/virtio-ports/*/name | cut -d/ -f5)\n", port);
fprintf(stderr, " cat $DEV OR echo data > $DEV\n");
fprintf(stderr, " On host: echo 'data' > %s # send to guest\n", fifo_in);
fprintf(stderr, " cat %s # receive from guest\n", fifo_out);
if (console_count + pipe_count > NUM_RESERVED_PORTS) {
fprintf(stderr, "Reached max reserved ports (%d)\n", NUM_RESERVED_PORTS);
break;
}
}
}

return NULL;
}

int main(int argc, char *const argv[])
{
if (argc < 3) {
Expand All @@ -119,100 +219,66 @@ int main(int argc, char *const argv[])
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);
krun_set_log_level(KRUN_LOG_LEVEL_DEBUG);

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;
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;
errno = -console_id; perror("krun_add_virtio_console_multiport"); return 1;
}

/* Configure console ports - edit this section to add/remove ports */
/* Create 1 initial console BEFORE VM starts - this will run the command */
{

// 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;
int fd = create_tmux_tty("krun-console-1");
if (fd < 0) { fprintf(stderr, "create_tmux_tty failed (session already exists?)\n"); return 1; }
if ((err = krun_add_console_port_tty(ctx_id, console_id, "console-1", fd))) {
errno = -err; perror("krun_add_console_port_tty"); 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");
/* Reserve ports for dynamic addition */
if ((err = krun_console_reserve_ports(ctx_id, console_id, NUM_RESERVED_PORTS))) {
errno = -err; perror("krun_console_reserve_ports"); return 1;
}

if ((err = krun_set_vm_config(ctx_id, 4, 4096))) {
errno = -err;
perror("krun_set_vm_config");
return 1;
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;
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;
errno = -err; perror("krun_set_exec"); return 1;
}

fprintf(stderr, "\nStarting VM...\n");

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

struct console_state state = {
.ctx_id = ctx_id,
.console_id = console_id,
.ready_fd = ready_fd,
};

pthread_t dyn_thread;
pthread_create(&dyn_thread, NULL, dynamic_console_thread, &state);
pthread_detach(dyn_thread);

/* Run VM in main thread - this blocks until VM exits, then calls _exit() */
if ((err = krun_start_enter(ctx_id))) {
errno = -err;
perror("krun_start_enter");
return 1;
errno = -err; perror("krun_start_enter"); return 1;
}

return 0;
}


36 changes: 36 additions & 0 deletions include/libkrun.h
Original file line number Diff line number Diff line change
Expand Up @@ -1109,6 +1109,42 @@ int32_t krun_add_serial_console_default(uint32_t ctx_id,
*/
int32_t krun_add_virtio_console_multiport(uint32_t ctx_id);

/*
* Reserves additional port slots on a multi-port virtio-console device for dynamic addition
* after the VM has started.
*
* This function must be called before krun_start_enter(). The reserved ports can be populated
* later by calling krun_add_console_port_tty() or krun_add_console_port_inout() on a running VM.
*
* Arguments:
* "ctx_id" - the configuration context ID.
* "console_id" - the console ID returned by krun_add_virtio_console_multiport().
* "num_ports" - the number of additional port slots to reserve.
*
* Returns:
* Zero on success or a negative error number on failure.
*/
int32_t krun_console_reserve_ports(uint32_t ctx_id, uint32_t console_id, uint32_t num_ports);

/*
* Returns an eventfd that becomes readable when the virtio-console device is ready
* to accept dynamically added ports.
*
* This function must be called after krun_start_enter() has been invoked (typically from
* another thread). The returned fd can be polled; when readable, read an 8-byte value
* from it to consume the event, then call krun_add_console_port_tty() or
* krun_add_console_port_inout() to add ports dynamically.
*
* Arguments:
* "ctx_id" - the configuration context ID.
* "console_id" - the console ID returned by krun_add_virtio_console_multiport().
*
* Returns:
* The eventfd file descriptor (>= 0) on success, or a negative error number on failure.
* Returns -EAGAIN if the VM has not been started yet.
*/
int32_t krun_get_console_ready_fd(uint32_t ctx_id, uint32_t console_id);

/*
* Adds a TTY port to a multi-port virtio-console device.
*
Expand Down
6 changes: 3 additions & 3 deletions src/devices/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ tdx = ["blk", "tee"]
net = []
blk = []
efi = ["blk", "net"]
gpu = ["rutabaga_gfx", "thiserror", "zerocopy", "krun_display"]
snd = ["pw", "thiserror"]
gpu = ["rutabaga_gfx", "zerocopy", "krun_display"]
snd = ["pw"]
input = ["zerocopy", "krun_input"]
virgl_resource_map2 = []
nitro = []
Expand All @@ -27,7 +27,7 @@ log = "0.4.0"
nix = { version = "0.30.1", features = ["ioctl", "net", "poll", "socket", "fs"] }
pw = { package = "pipewire", version = "0.8.0", optional = true }
rand = "0.9.2"
thiserror = { version = "2.0", optional = true }
thiserror = { version = "2.0" }
virtio-bindings = "0.2.0"
vm-memory = { version = ">=0.13", features = ["backend-mmap"] }
zerocopy = { version = "0.8.26", optional = true, features = ["derive"] }
Expand Down
Loading
Loading