1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
|
// VIRTIO CONSOLE Emulation via vhost-user
//
// Copyright 2023 VIRTUAL OPEN SYSTEMS SAS. All Rights Reserved.
// Timos Ampelikiotis <t.ampelikiotis@virtualopensystems.com>
//
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
use log::{error, info, warn};
use std::process::exit;
use std::sync::{Arc, RwLock};
use std::thread::{spawn, JoinHandle};
use clap::Parser;
use thiserror::Error as ThisError;
use vhost::{vhost_user, vhost_user::Listener};
use vhost_user_backend::VhostUserDaemon;
use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap};
use crate::console::{ConsoleController};
use crate::vhu_console::VhostUserConsoleBackend;
pub(crate) type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, ThisError)]
/// Errors related to low level Console helpers
pub(crate) enum Error {
#[error("Invalid socket count: {0}")]
SocketCountInvalid(usize),
#[error("Failed to join threads")]
FailedJoiningThreads,
#[error("Could not create console controller: {0}")]
CouldNotCreateConsoleController(crate::console::Error),
#[error("Could not create console backend: {0}")]
CouldNotCreateBackend(crate::vhu_console::Error),
#[error("Could not create daemon: {0}")]
CouldNotCreateDaemon(vhost_user_backend::Error),
}
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
struct ConsoleArgs {
/// Location of vhost-user Unix domain socket. This is suffixed by 0,1,2..socket_count-1.
#[clap(short, long)]
socket_path: String,
/// A console device name to be used for reading (ex. vconsole, console0, console1, ... etc.)
#[clap(short = 'i', long)]
console_path: String,
/// Number of guests (sockets) to connect to.
#[clap(short = 'c', long, default_value_t = 1)]
socket_count: u32,
}
#[derive(PartialEq, Debug)]
struct ConsoleConfiguration {
socket_path: String,
socket_count: u32,
console_path: String,
}
impl TryFrom<ConsoleArgs> for ConsoleConfiguration {
type Error = Error;
fn try_from(args: ConsoleArgs) -> Result<Self> {
if args.socket_count == 0 {
return Err(Error::SocketCountInvalid(0));
}
let console_path = args.console_path.trim().to_string();
Ok(ConsoleConfiguration {
socket_path: args.socket_path,
socket_count: args.socket_count,
console_path,
})
}
}
fn start_backend(args: ConsoleArgs) -> Result<()> {
println!("start_backend function!\n");
let config = ConsoleConfiguration::try_from(args).unwrap();
let mut handles = Vec::new();
for _ in 0..config.socket_count {
let socket = config.socket_path.to_owned();
let console_path = config.console_path.to_owned();
let handle: JoinHandle<Result<()>> = spawn(move || loop {
// A separate thread is spawned for each socket and console connect to a separate guest.
// These are run in an infinite loop to not require the daemon to be restarted once a
// guest exits.
//
// There isn't much value in complicating code here to return an error from the
// threads, and so the code uses unwrap() instead. The panic on a thread won't cause
// trouble to other threads/guests or the main() function and should be safe for the
// daemon.
let controller =
ConsoleController::new(console_path.clone()).map_err(Error::CouldNotCreateConsoleController)?;
let arc_controller = Arc::new(RwLock::new(controller));
let vu_console_backend = Arc::new(RwLock::new(
VhostUserConsoleBackend::new(arc_controller).map_err(Error::CouldNotCreateBackend)?,
));
let mut daemon = VhostUserDaemon::new(
String::from("vhost-device-console-backend"),
vu_console_backend.clone(),
GuestMemoryAtomic::new(GuestMemoryMmap::new()),
)
.map_err(Error::CouldNotCreateDaemon)?;
/* Start the read thread -- need to handle it after termination */
let vring_workers = daemon.get_epoll_handlers();
vu_console_backend.read()
.unwrap()
.set_vring_worker(&vring_workers[0]);
VhostUserConsoleBackend::start_console_thread(&vu_console_backend);
let listener = Listener::new(socket.clone(), true).unwrap();
daemon.start(listener).unwrap();
match daemon.wait() {
Ok(()) => {
info!("Stopping cleanly.");
}
Err(vhost_user_backend::Error::HandleRequest(
vhost_user::Error::PartialMessage | vhost_user::Error::Disconnected,
)) => {
info!("vhost-user connection closed with partial message. If the VM is shutting down, this is expected behavior; otherwise, it might be a bug.");
}
Err(e) => {
warn!("Error running daemon: {:?}", e);
}
}
// No matter the result, we need to shut down the worker thread.
vu_console_backend.read().unwrap().exit_event.write(1).unwrap();
});
handles.push(handle);
}
for handle in handles {
handle.join().map_err(|_| Error::FailedJoiningThreads)??;
}
Ok(())
}
pub(crate) fn console_init() {
env_logger::init();
println!("Console_init function!");
if let Err(e) = start_backend(ConsoleArgs::parse()) {
error!("{e}");
exit(1);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_console_configuration_try_from_valid_args() {
let args = ConsoleArgs {
socket_path: String::from("/path/to/socket"),
console_path: String::from("vconsole"),
socket_count: 3,
};
let result = ConsoleConfiguration::try_from(args);
assert!(result.is_ok());
let config = result.unwrap();
assert_eq!(config.socket_path, "/path/to/socket");
assert_eq!(config.console_path, "vconsole");
assert_eq!(config.socket_count, 3);
}
#[test]
fn test_console_configuration_try_from_invalid_args() {
// Test with socket_count = 0
let args_invalid_count = ConsoleArgs {
socket_path: String::from("/path/to/socket"),
console_path: String::from("vconsole"),
socket_count: 0,
};
let result_invalid_count = ConsoleConfiguration::try_from(args_invalid_count);
assert!(result_invalid_count.is_err());
}
#[test]
fn test_start_backend_success() {
// Test start_backend with valid arguments
let args = ConsoleArgs {
socket_path: String::from("/path/to/socket"),
console_path: String::from("vconsole"),
socket_count: 2,
};
let result = start_backend(args);
assert!(result.is_ok());
}
}
|