Skip to content

Commit b2d4617

Browse files
committed
examples/consoles: Support attaching consoles and pipes at runtime
Add dynamic console management that waits for the console device to become ready, then allows interactive addition of new console ports and pipe ports while the VM is running. Signed-off-by: Matej Hrica <[email protected]>
1 parent 7b08b68 commit b2d4617

File tree

1 file changed

+144
-78
lines changed

1 file changed

+144
-78
lines changed

examples/consoles.c

Lines changed: 144 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,15 @@
55
#include <string.h>
66
#include <unistd.h>
77
#include <stdarg.h>
8+
#include <pthread.h>
9+
#include <poll.h>
810
#include <sys/wait.h>
911
#include <sys/stat.h>
1012

1113
#include <libkrun.h>
1214

15+
#define NUM_RESERVED_PORTS 64
16+
1317
static int cmd_output(char *output, size_t output_size, const char *prog, ...)
1418
{
1519
va_list args;
@@ -63,12 +67,11 @@ static int create_tmux_tty(const char *session_name)
6367
{
6468
char tty_path[256];
6569
char wait_cmd[128];
66-
70+
6771
snprintf(wait_cmd, sizeof(wait_cmd), "waitpid %d", (int)getpid());
6872
if (cmd("tmux", "new-session", "-d", "-s", session_name, "sh", "-c", wait_cmd, NULL) != 0)
6973
return -1;
7074

71-
// Hook up tmux to send us SIGWINCH signal on resize
7275
char hook_cmd[128];
7376
snprintf(hook_cmd, sizeof(hook_cmd), "run-shell 'kill -WINCH %d'", (int)getpid());
7477
cmd("tmux", "set-hook", "-g", "client-resized", hook_cmd, NULL);
@@ -90,23 +93,120 @@ static int mkfifo_if_needed(const char *path)
9093
return 0;
9194
}
9295

93-
9496
static int create_fifo_inout(const char *fifo_in, const char *fifo_out, int *input_fd, int *output_fd)
9597
{
9698
if (mkfifo_if_needed(fifo_in) < 0) return -1;
9799
if (mkfifo_if_needed(fifo_out) < 0) return -1;
98100

99-
int in_fd = open(fifo_in, O_RDONLY | O_NONBLOCK);
100-
if (in_fd < 0) return -1;
101+
*input_fd = open(fifo_in, O_RDWR | O_NONBLOCK);
102+
if (*input_fd < 0) return -1;
101103

102-
int out_fd = open(fifo_out, O_RDWR | O_NONBLOCK);
103-
if (out_fd < 0) { close(in_fd); return -1; }
104+
*output_fd = open(fifo_out, O_RDWR | O_NONBLOCK);
105+
if (*output_fd < 0) {
106+
close(*input_fd);
107+
return -1;
108+
}
104109

105-
*input_fd = in_fd;
106-
*output_fd = out_fd;
107110
return 0;
108111
}
109112

113+
struct console_state {
114+
uint32_t ctx_id;
115+
uint32_t console_id;
116+
int ready_fd;
117+
};
118+
119+
static void *dynamic_console_thread(void *arg)
120+
{
121+
struct console_state *state = arg;
122+
int ready_fd = state->ready_fd;
123+
124+
struct pollfd pfd = { .fd = ready_fd, .events = POLLIN };
125+
fprintf(stderr, "Waiting for console device...\n");
126+
if (poll(&pfd, 1, -1) < 0) {
127+
perror("poll");
128+
return NULL;
129+
}
130+
131+
uint64_t val;
132+
if (read(ready_fd, &val, sizeof(val)) != sizeof(val)) {
133+
perror("read eventfd");
134+
return NULL;
135+
}
136+
137+
fprintf(stderr, "\n");
138+
fprintf(stderr, "=== VM Started ===\n");
139+
fprintf(stderr, "\n");
140+
fprintf(stderr, "*** To interact with the VM (hvc0), run in another terminal: ***\n");
141+
fprintf(stderr, " tmux attach -t krun-console-1\n");
142+
fprintf(stderr, "\n");
143+
fprintf(stderr, "Commands: 'c' = add console\n");
144+
fprintf(stderr, " 'p' = add pipe\n");
145+
fprintf(stderr, "\n");
146+
147+
int console_count = 1; /* console-1 already exists (hvc0) */
148+
int pipe_count = 0;
149+
char line[16];
150+
while (1) {
151+
fprintf(stderr, "> ");
152+
if (fgets(line, sizeof(line), stdin) == NULL) break;
153+
154+
if (line[0] == 'c' || line[0] == 'C') {
155+
console_count++;
156+
char sess[64], port[64];
157+
snprintf(sess, sizeof(sess), "krun-console-%d", console_count);
158+
snprintf(port, sizeof(port), "console-%d", console_count);
159+
160+
int fd = create_tmux_tty(sess);
161+
if (fd < 0) { fprintf(stderr, "tmux: failed to create session '%s'\n", sess); continue; }
162+
163+
int err = krun_add_console_port_tty(state->ctx_id, state->console_id, port, fd);
164+
if (err) { fprintf(stderr, "add port: %s\n", strerror(-err)); close(fd); continue; }
165+
166+
fprintf(stderr, "Created console '%s' (port %d, /dev/hvc%d):\n", port, console_count + pipe_count - 1, console_count - 1);
167+
fprintf(stderr, " On host: tmux attach -t %s\n", sess);
168+
fprintf(stderr, " In guest: setsid /sbin/agetty -a $(whoami) -L hvc%d xterm-256color\n", console_count - 1);
169+
if (console_count + pipe_count > NUM_RESERVED_PORTS) {
170+
fprintf(stderr, "Reached max reserved ports (%d)\n", NUM_RESERVED_PORTS);
171+
break;
172+
}
173+
}
174+
175+
if (line[0] == 'p' || line[0] == 'P') {
176+
pipe_count++;
177+
char port[64], fifo_in[128], fifo_out[128];
178+
snprintf(port, sizeof(port), "pipe-%d", pipe_count);
179+
snprintf(fifo_in, sizeof(fifo_in), "/tmp/krun_pipe%d_in", pipe_count);
180+
snprintf(fifo_out, sizeof(fifo_out), "/tmp/krun_pipe%d_out", pipe_count);
181+
182+
int in_fd, out_fd;
183+
if (create_fifo_inout(fifo_in, fifo_out, &in_fd, &out_fd) < 0) {
184+
perror("create_fifo_inout"); continue;
185+
}
186+
187+
int err = krun_add_console_port_inout(state->ctx_id, state->console_id, port, in_fd, out_fd);
188+
if (err) {
189+
fprintf(stderr, "add port: %s\n", strerror(-err));
190+
close(in_fd);
191+
close(out_fd);
192+
continue;
193+
}
194+
195+
fprintf(stderr, "Created pipe '%s' (port %d):\n", port, console_count + pipe_count - 1);
196+
fprintf(stderr, " In guest: DEV=/dev/$(grep -l %s /sys/class/virtio-ports/*/name | cut -d/ -f5)\n", port);
197+
fprintf(stderr, " cat $DEV OR echo data > $DEV\n");
198+
fprintf(stderr, " On host: echo 'data' > %s # send to guest\n", fifo_in);
199+
fprintf(stderr, " cat %s # receive from guest\n", fifo_out);
200+
if (console_count + pipe_count > NUM_RESERVED_PORTS) {
201+
fprintf(stderr, "Reached max reserved ports (%d)\n", NUM_RESERVED_PORTS);
202+
break;
203+
}
204+
}
205+
}
206+
207+
return NULL;
208+
}
209+
110210
int main(int argc, char *const argv[])
111211
{
112212
if (argc < 3) {
@@ -119,100 +219,66 @@ int main(int argc, char *const argv[])
119219
const char *const *command_args = (argc > 3) ? (const char *const *)&argv[3] : NULL;
120220
const char *const envp[] = { 0 };
121221

122-
krun_set_log_level(KRUN_LOG_LEVEL_WARN);
222+
krun_set_log_level(KRUN_LOG_LEVEL_DEBUG);
123223

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

128228
if ((err = krun_disable_implicit_console(ctx_id))) {
129-
errno = -err;
130-
perror("krun_disable_implicit_console");
131-
return 1;
229+
errno = -err; perror("krun_disable_implicit_console"); return 1;
132230
}
133231

134232
int console_id = krun_add_virtio_console_multiport(ctx_id);
135233
if (console_id < 0) {
136-
errno = -console_id;
137-
perror("krun_add_virtio_console_multiport");
138-
return 1;
234+
errno = -console_id; perror("krun_add_virtio_console_multiport"); return 1;
139235
}
140236

141-
/* Configure console ports - edit this section to add/remove ports */
237+
/* Create 1 initial console BEFORE VM starts - this will run the command */
142238
{
143-
144-
// You could also use the controlling terminal of this process in the guest:
145-
/*
146-
if ((err = krun_add_console_port_tty(ctx_id, console_id, "host_tty", open("/dev/tty", O_RDWR)))) {
147-
errno = -err;
148-
perror("port host_tty");
149-
return 1;
150-
}
151-
*/
152-
153-
int num_consoles = 3;
154-
for (int i = 0; i < num_consoles; i++) {
155-
char session_name[64];
156-
char port_name[64];
157-
snprintf(session_name, sizeof(session_name), "krun-console-%d", i + 1);
158-
snprintf(port_name, sizeof(port_name), "console-%d", i + 1);
159-
160-
int tmux_fd = create_tmux_tty(session_name);
161-
if (tmux_fd < 0) {
162-
perror("create_tmux_tty");
163-
return 1;
164-
}
165-
if ((err = krun_add_console_port_tty(ctx_id, console_id, port_name, tmux_fd))) {
166-
errno = -err;
167-
perror("krun_add_console_port_tty");
168-
return 1;
169-
}
170-
}
171-
172-
int in_fd, out_fd;
173-
if (create_fifo_inout("/tmp/consoles_example_in", "/tmp/consoles_example_out", &in_fd, &out_fd) < 0) {
174-
perror("create_fifo_inout");
175-
return 1;
176-
}
177-
if ((err = krun_add_console_port_inout(ctx_id, console_id, "fifo_inout", in_fd, out_fd))) {
178-
errno = -err;
179-
perror("krun_add_console_port_inout");
180-
return 1;
239+
int fd = create_tmux_tty("krun-console-1");
240+
if (fd < 0) { fprintf(stderr, "create_tmux_tty failed (session already exists?)\n"); return 1; }
241+
if ((err = krun_add_console_port_tty(ctx_id, console_id, "console-1", fd))) {
242+
errno = -err; perror("krun_add_console_port_tty"); return 1;
181243
}
244+
}
182245

183-
fprintf(stderr, "\n=== Console ports configured ===\n");
184-
for (int i = 0; i < num_consoles; i++) {
185-
fprintf(stderr, " console-%d: tmux attach -t krun-console-%d\n", i + 1, i + 1);
186-
}
187-
fprintf(stderr, " fifo_inout: /tmp/consoles_example_in (host->guest)\n");
188-
fprintf(stderr, " fifo_inout: /tmp/consoles_example_out (guest->host)\n");
189-
fprintf(stderr, "================================\n\n");
246+
/* Reserve ports for dynamic addition */
247+
if ((err = krun_console_reserve_ports(ctx_id, console_id, NUM_RESERVED_PORTS))) {
248+
errno = -err; perror("krun_console_reserve_ports"); return 1;
190249
}
191250

192251
if ((err = krun_set_vm_config(ctx_id, 4, 4096))) {
193-
errno = -err;
194-
perror("krun_set_vm_config");
195-
return 1;
252+
errno = -err; perror("krun_set_vm_config"); return 1;
196253
}
197-
198254
if ((err = krun_set_root(ctx_id, root_dir))) {
199-
errno = -err;
200-
perror("krun_set_root");
201-
return 1;
255+
errno = -err; perror("krun_set_root"); return 1;
202256
}
203-
204257
if ((err = krun_set_exec(ctx_id, command, command_args, envp))) {
205-
errno = -err;
206-
perror("krun_set_exec");
207-
return 1;
258+
errno = -err; perror("krun_set_exec"); return 1;
259+
}
260+
261+
fprintf(stderr, "\nStarting VM...\n");
262+
263+
int ready_fd = krun_get_console_ready_fd(ctx_id, console_id);
264+
if (ready_fd < 0) {
265+
errno = -ready_fd; perror("krun_get_console_ready_fd"); return 1;
208266
}
209267

268+
struct console_state state = {
269+
.ctx_id = ctx_id,
270+
.console_id = console_id,
271+
.ready_fd = ready_fd,
272+
};
273+
274+
pthread_t dyn_thread;
275+
pthread_create(&dyn_thread, NULL, dynamic_console_thread, &state);
276+
pthread_detach(dyn_thread);
277+
278+
/* Run VM in main thread - this blocks until VM exits, then calls _exit() */
210279
if ((err = krun_start_enter(ctx_id))) {
211-
errno = -err;
212-
perror("krun_start_enter");
213-
return 1;
280+
errno = -err; perror("krun_start_enter"); return 1;
214281
}
282+
215283
return 0;
216284
}
217-
218-

0 commit comments

Comments
 (0)