Skip to content

Commit e566288

Browse files
committed
examples/consoles: Support attaching consoles and pipes at runtime
Signed-off-by: Matej Hrica <mhrica@redhat.com>
1 parent bd9fd48 commit e566288

1 file changed

Lines changed: 284 additions & 0 deletions

File tree

examples/consoles.c

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
#include <errno.h>
2+
#include <fcntl.h>
3+
#include <stdio.h>
4+
#include <stdlib.h>
5+
#include <string.h>
6+
#include <unistd.h>
7+
#include <stdarg.h>
8+
#include <pthread.h>
9+
#include <poll.h>
10+
#include <sys/wait.h>
11+
#include <sys/stat.h>
12+
13+
#include <libkrun.h>
14+
15+
#define NUM_RESERVED_PORTS 64
16+
17+
static int cmd_output(char *output, size_t output_size, const char *prog, ...)
18+
{
19+
va_list args;
20+
const char *argv[32];
21+
int argc = 0;
22+
int pipe_fds[2] = { -1, -1 };
23+
24+
argv[argc++] = prog;
25+
va_start(args, prog);
26+
while (argc < 31) {
27+
const char *arg = va_arg(args, const char *);
28+
argv[argc++] = arg;
29+
if (arg == NULL) break;
30+
}
31+
va_end(args);
32+
argv[argc] = NULL;
33+
34+
if (output && output_size > 0) {
35+
if (pipe(pipe_fds) < 0) return -1;
36+
}
37+
38+
pid_t pid = fork();
39+
if (pid < 0) return -1;
40+
if (pid == 0) {
41+
if (pipe_fds[0] >= 0) {
42+
close(pipe_fds[0]);
43+
dup2(pipe_fds[1], STDOUT_FILENO);
44+
close(pipe_fds[1]);
45+
}
46+
execvp(prog, (char *const *)argv);
47+
abort();
48+
}
49+
50+
if (pipe_fds[0] >= 0) {
51+
close(pipe_fds[1]);
52+
ssize_t n = read(pipe_fds[0], output, output_size - 1);
53+
close(pipe_fds[0]);
54+
if (n < 0) n = 0;
55+
output[n] = '\0';
56+
}
57+
58+
int status;
59+
if (waitpid(pid, &status, 0) < 0) return -1;
60+
if (!WIFEXITED(status)) return -1;
61+
return WEXITSTATUS(status);
62+
}
63+
64+
#define cmd(...) ({ char _d[1]; cmd_output(_d, 0, __VA_ARGS__); })
65+
66+
static int create_tmux_tty(const char *session_name)
67+
{
68+
char tty_path[256];
69+
char wait_cmd[128];
70+
71+
snprintf(wait_cmd, sizeof(wait_cmd), "waitpid %d", (int)getpid());
72+
if (cmd("tmux", "new-session", "-d", "-s", session_name, "sh", "-c", wait_cmd, NULL) != 0)
73+
return -1;
74+
75+
char hook_cmd[128];
76+
snprintf(hook_cmd, sizeof(hook_cmd), "run-shell 'kill -WINCH %d'", (int)getpid());
77+
cmd("tmux", "set-hook", "-g", "client-resized", hook_cmd, NULL);
78+
79+
if (cmd_output(tty_path, sizeof(tty_path), "tmux", "display-message", "-p", "-t", session_name, "#{pane_tty}", NULL) != 0)
80+
return -1;
81+
tty_path[strcspn(tty_path, "\n")] = '\0';
82+
83+
int fd = open(tty_path, O_RDWR);
84+
if (fd < 0) return -1;
85+
return fd;
86+
}
87+
88+
static int mkfifo_if_needed(const char *path)
89+
{
90+
if (mkfifo(path, 0666) < 0) {
91+
if (errno != EEXIST) return -1;
92+
}
93+
return 0;
94+
}
95+
96+
static int create_fifo_inout(const char *fifo_in, const char *fifo_out, int *input_fd, int *output_fd)
97+
{
98+
if (mkfifo_if_needed(fifo_in) < 0) return -1;
99+
if (mkfifo_if_needed(fifo_out) < 0) return -1;
100+
101+
*input_fd = open(fifo_in, O_RDWR | O_NONBLOCK);
102+
if (*input_fd < 0) return -1;
103+
104+
*output_fd = open(fifo_out, O_RDWR | O_NONBLOCK);
105+
if (*output_fd < 0) {
106+
close(*input_fd);
107+
return -1;
108+
}
109+
110+
return 0;
111+
}
112+
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+
210+
int main(int argc, char *const argv[])
211+
{
212+
if (argc < 3) {
213+
fprintf(stderr, "Usage: %s ROOT_DIR COMMAND [ARGS...]\n", argv[0]);
214+
return 1;
215+
}
216+
217+
const char *root_dir = argv[1];
218+
const char *command = argv[2];
219+
const char *const *command_args = (argc > 3) ? (const char *const *)&argv[3] : NULL;
220+
const char *const envp[] = { 0 };
221+
222+
krun_set_log_level(KRUN_LOG_LEVEL_DEBUG);
223+
224+
int err;
225+
int ctx_id = krun_create_ctx();
226+
if (ctx_id < 0) { errno = -ctx_id; perror("krun_create_ctx"); return 1; }
227+
228+
if ((err = krun_disable_implicit_console(ctx_id))) {
229+
errno = -err; perror("krun_disable_implicit_console"); return 1;
230+
}
231+
232+
int console_id = krun_add_virtio_console_multiport(ctx_id);
233+
if (console_id < 0) {
234+
errno = -console_id; perror("krun_add_virtio_console_multiport"); return 1;
235+
}
236+
237+
/* Create 1 initial console BEFORE VM starts - this will run the command */
238+
{
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;
243+
}
244+
}
245+
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;
249+
}
250+
251+
if ((err = krun_set_vm_config(ctx_id, 4, 4096))) {
252+
errno = -err; perror("krun_set_vm_config"); return 1;
253+
}
254+
if ((err = krun_set_root(ctx_id, root_dir))) {
255+
errno = -err; perror("krun_set_root"); return 1;
256+
}
257+
if ((err = krun_set_exec(ctx_id, command, command_args, envp))) {
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;
266+
}
267+
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() */
279+
if ((err = krun_start_enter(ctx_id))) {
280+
errno = -err; perror("krun_start_enter"); return 1;
281+
}
282+
283+
return 0;
284+
}

0 commit comments

Comments
 (0)