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+
1317static 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-
9496static 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+
110210int 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