Skip to content

Commit cf56ee9

Browse files
committed
virtio-console, builder: Attach terminal information to each port
Previosly we were assuming stdin is terminal using it to determine the terminal size. Attach a PortTerminalProperties trait object to each port for querying the correct terminal size. Note that this still only supports dynamically resizing the first port (upon SIGWINCH). Signed-off-by: Matej Hrica <[email protected]>
1 parent bb8e49d commit cf56ee9

File tree

6 files changed

+121
-54
lines changed

6 files changed

+121
-54
lines changed

src/devices/src/virtio/console/device.rs

Lines changed: 10 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -29,34 +29,6 @@ pub(crate) const AVAIL_FEATURES: u64 = (1 << uapi::VIRTIO_CONSOLE_F_SIZE as u64)
2929
| (1 << uapi::VIRTIO_CONSOLE_F_MULTIPORT as u64)
3030
| (1 << uapi::VIRTIO_F_VERSION_1 as u64);
3131

32-
#[repr(C)]
33-
#[derive(Default)]
34-
struct WS {
35-
rows: u16,
36-
cols: u16,
37-
xpixel: u16,
38-
ypixel: u16,
39-
}
40-
ioctl_read_bad!(tiocgwinsz, TIOCGWINSZ, WS);
41-
42-
pub(crate) fn get_win_size() -> (u16, u16) {
43-
let mut ws: WS = WS::default();
44-
45-
let ret = unsafe { tiocgwinsz(0, &mut ws) };
46-
47-
if let Err(err) = ret {
48-
match err {
49-
// If the port isn't a TTY, this is expected to fail. Avoid logging
50-
// an error in that case.
51-
nix::errno::Errno::ENOTTY => {}
52-
_ => error!("Couldn't get terminal dimensions: {err}"),
53-
}
54-
(0, 0)
55-
} else {
56-
(ws.cols, ws.rows)
57-
}
58-
}
59-
6032
#[derive(Copy, Clone, Debug, Default)]
6133
#[repr(C, packed)]
6234
pub struct VirtioConsoleConfig {
@@ -100,7 +72,7 @@ pub struct Console {
10072
impl Console {
10173
pub fn new(ports: Vec<PortDescription>) -> super::Result<Console> {
10274
assert!(!ports.is_empty(), "Expected at least 1 port");
103-
assert!(ports[0].represents_console, "First port must be a console");
75+
assert!(ports[0].terminal.is_some(), "First port must be a console");
10476

10577
let num_queues = num_queues(ports.len());
10678
let queues = vec![VirtQueue::new(QUEUE_SIZE); num_queues];
@@ -111,12 +83,16 @@ impl Console {
11183
.push(EventFd::new(utils::eventfd::EFD_NONBLOCK).map_err(ConsoleError::EventFd)?);
11284
}
11385

114-
let (cols, rows) = get_win_size();
115-
let config = VirtioConsoleConfig::new(cols, rows, ports.len() as u32);
116-
let ports = zip(0u32.., ports)
86+
let ports: Vec<Port> = zip(0u32.., ports)
11787
.map(|(port_id, description)| Port::new(port_id, description))
11888
.collect();
11989

90+
let (cols, rows) = ports[0]
91+
.terminal()
92+
.expect("Port 0 should always be a terminal")
93+
.get_win_size();
94+
let config = VirtioConsoleConfig::new(cols, rows, ports.len() as u32);
95+
12096
Ok(Console {
12197
control: ConsoleControl::new(),
12298
ports,
@@ -228,10 +204,10 @@ impl Console {
228204
continue;
229205
}
230206

231-
if self.ports[cmd.id as usize].is_console() {
207+
if let Some(term) = self.ports[cmd.id as usize].terminal() {
232208
self.control.mark_console_port(mem, cmd.id);
233209
self.control.port_open(cmd.id, true);
234-
let (cols, rows) = get_win_size();
210+
let (cols, rows) = term.get_win_size();
235211
self.control
236212
.console_resize(cmd.id, VirtioConsoleResize { cols, rows });
237213
} else {

src/devices/src/virtio/console/event_handler.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::os::unix::io::AsRawFd;
33
use polly::event_manager::{EventManager, Subscriber};
44
use utils::epoll::{EpollEvent, EventSet};
55

6-
use super::device::{get_win_size, Console};
6+
use super::device::Console;
77
use crate::virtio::console::device::{CONTROL_RXQ_INDEX, CONTROL_TXQ_INDEX};
88
use crate::virtio::console::port_queue_mapping::{queue_idx_to_port_id, QueueDirection};
99
use crate::virtio::device::VirtioDevice;
@@ -88,8 +88,10 @@ impl Console {
8888
error!("Failed to read the sigwinch event: {e:?}");
8989
}
9090

91-
let (cols, rows) = get_win_size();
92-
self.update_console_size(cols, rows);
91+
if let Some(term) = self.ports[0].terminal() {
92+
let (cols, rows) = term.get_win_size();
93+
self.update_console_size(cols, rows);
94+
}
9395
}
9496

9597
fn read_control_queue_event(&mut self, event: &EpollEvent) {

src/devices/src/virtio/console/port.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,27 @@ use crate::virtio::console::console_control::ConsoleControl;
1010
use crate::virtio::console::port_io::{PortInput, PortOutput};
1111
use crate::virtio::console::process_rx::process_rx;
1212
use crate::virtio::console::process_tx::process_tx;
13+
use crate::virtio::port_io::PortTerminalProperties;
1314
use crate::virtio::{InterruptTransport, Queue};
1415

1516
pub struct PortDescription {
1617
pub name: Cow<'static, str>,
17-
pub represents_console: bool,
1818
pub input: Option<Box<dyn PortInput + Send>>,
1919
pub output: Option<Box<dyn PortOutput + Send>>,
20+
pub terminal: Option<Box<dyn PortTerminalProperties>>,
2021
}
2122

2223
impl PortDescription {
2324
pub fn console(
2425
input: Option<Box<dyn PortInput + Send>>,
2526
output: Option<Box<dyn PortOutput + Send>>,
27+
terminal: Box<dyn PortTerminalProperties>,
2628
) -> Self {
2729
Self {
2830
name: "".into(),
29-
represents_console: true,
3031
input,
3132
output,
33+
terminal: Some(terminal),
3234
}
3335
}
3436

@@ -38,9 +40,9 @@ impl PortDescription {
3840
) -> Self {
3941
Self {
4042
name: name.into(),
41-
represents_console: false,
4243
input: None,
4344
output: Some(output),
45+
terminal: None,
4446
}
4547
}
4648

@@ -50,9 +52,9 @@ impl PortDescription {
5052
) -> Self {
5153
Self {
5254
name: name.into(),
53-
represents_console: false,
5455
input: Some(input),
5556
output: None,
57+
terminal: None,
5658
}
5759
}
5860
}
@@ -71,32 +73,32 @@ pub(crate) struct Port {
7173
port_id: u32,
7274
/// Empty if no name given
7375
name: Cow<'static, str>,
74-
represents_console: bool,
7576
state: PortState,
7677
input: Option<Arc<Mutex<Box<dyn PortInput + Send>>>>,
7778
output: Option<Arc<Mutex<Box<dyn PortOutput + Send>>>>,
79+
terminal: Option<Box<dyn PortTerminalProperties>>,
7880
}
7981

8082
impl Port {
8183
pub(crate) fn new(port_id: u32, description: PortDescription) -> Self {
8284
Self {
8385
port_id,
8486
name: description.name,
85-
represents_console: description.represents_console,
8687
state: PortState::Inactive,
8788
input: description.input.map(|input| Arc::new(Mutex::new(input))),
8889
output: description
8990
.output
9091
.map(|output| Arc::new(Mutex::new(output))),
92+
terminal: description.terminal,
9193
}
9294
}
9395

9496
pub fn name(&self) -> &str {
9597
&self.name
9698
}
9799

98-
pub fn is_console(&self) -> bool {
99-
self.represents_console
100+
pub fn terminal(&self) -> Option<&dyn PortTerminalProperties> {
101+
self.terminal.as_deref()
100102
}
101103

102104
pub fn notify_rx(&self) {

src/devices/src/virtio/console/port_io.rs

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
use std::fs::File;
2-
use std::io::{self, ErrorKind};
3-
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd, RawFd};
4-
5-
use libc::{fcntl, F_GETFL, F_SETFL, O_NONBLOCK, STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO};
1+
use libc::{
2+
fcntl, F_GETFL, F_SETFL, O_NONBLOCK, STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO, TIOCGWINSZ,
3+
};
64
use log::Level;
75
use nix::errno::Errno;
6+
use nix::ioctl_read_bad;
87
use nix::poll::{poll, PollFd, PollFlags, PollTimeout};
9-
use nix::unistd::dup;
8+
use nix::unistd::{dup, isatty};
9+
use std::fs::File;
10+
use std::io::{self, ErrorKind};
11+
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd, RawFd};
1012
use utils::eventfd::EventFd;
1113
use utils::eventfd::EFD_NONBLOCK;
1214
use vm_memory::bitmap::Bitmap;
@@ -24,6 +26,11 @@ pub trait PortOutput {
2426
fn wait_until_writable(&self);
2527
}
2628

29+
/// Terminal properties associated with this port
30+
pub trait PortTerminalProperties: Send + Sync {
31+
fn get_win_size(&self) -> (u16, u16);
32+
}
33+
2734
pub fn stdin() -> Result<Box<dyn PortInput + Send>, nix::Error> {
2835
let fd = dup_raw_fd_into_owned(STDIN_FILENO)?;
2936
make_non_blocking(&fd)?;
@@ -44,6 +51,21 @@ pub fn stderr() -> Result<Box<dyn PortOutput + Send>, nix::Error> {
4451
output_to_raw_fd_dup(STDERR_FILENO)
4552
}
4653

54+
pub fn term_fd(
55+
term_fd: RawFd,
56+
) -> Result<Box<dyn PortTerminalProperties + Send + Sync>, nix::Error> {
57+
let fd = dup_raw_fd_into_owned(term_fd)?;
58+
assert!(
59+
isatty(&fd).is_ok_and(|v| v),
60+
"Expected fd {fd:?}, to be a tty, to query the window size!"
61+
);
62+
Ok(Box::new(PortTerminalPropertiesFd(fd)))
63+
}
64+
65+
pub fn term_fixed_size(width: u16, height: u16) -> Box<dyn PortTerminalProperties + Send + Sync> {
66+
Box::new(PortTerminalPropertiesFixed((width, height)))
67+
}
68+
4769
pub fn input_empty() -> Result<Box<dyn PortInput + Send>, nix::Error> {
4870
Ok(Box::new(PortInputEmpty {}))
4971
}
@@ -278,3 +300,35 @@ impl PortInput for PortInputEmpty {
278300
}
279301
}
280302
}
303+
304+
struct PortTerminalPropertiesFixed((u16, u16));
305+
306+
impl PortTerminalProperties for PortTerminalPropertiesFixed {
307+
fn get_win_size(&self) -> (u16, u16) {
308+
self.0
309+
}
310+
}
311+
312+
struct PortTerminalPropertiesFd(OwnedFd);
313+
314+
impl PortTerminalProperties for PortTerminalPropertiesFd {
315+
fn get_win_size(&self) -> (u16, u16) {
316+
let mut ws: WS = WS::default();
317+
318+
if let Err(err) = unsafe { tiocgwinsz(self.0.as_raw_fd(), &mut ws) } {
319+
error!("Couldn't get terminal dimensions: {err}");
320+
return (0, 0);
321+
}
322+
(ws.cols, ws.rows)
323+
}
324+
}
325+
326+
#[repr(C)]
327+
#[derive(Default)]
328+
struct WS {
329+
rows: u16,
330+
cols: u16,
331+
xpixel: u16,
332+
ypixel: u16,
333+
}
334+
ioctl_read_bad!(tiocgwinsz, TIOCGWINSZ, WS);

src/vmm/src/builder.rs

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1870,22 +1870,32 @@ fn attach_console_devices(
18701870
) -> std::result::Result<(), StartMicrovmError> {
18711871
use self::StartMicrovmError::*;
18721872

1873+
let creating_implicit_console = cfg.is_none();
18731874
let mut console_output_path: Option<PathBuf> = None;
18741875

18751876
// we don't care about the console output that was set for the vm_resources
18761877
// if the implicit console is disabled
18771878
if let Some(path) = vm_resources.console_output.clone() {
18781879
// only set the console output for the implicit console
1879-
if !vm_resources.disable_implicit_console && cfg.is_none() {
1880+
if !vm_resources.disable_implicit_console && creating_implicit_console {
18801881
console_output_path = Some(path)
18811882
}
18821883
}
18831884

18841885
let ports = if console_output_path.is_some() {
18851886
let file = File::create(console_output_path.unwrap()).map_err(OpenConsoleFile)?;
1887+
// Manually emulate our Legacy behavior: In the case of output_path we have always used the
1888+
// stdin to determine the console size
1889+
let stdin_fd = unsafe { BorrowedFd::borrow_raw(STDIN_FILENO) };
1890+
let term_fd = if isatty(stdin_fd).is_ok_and(|v| v) {
1891+
port_io::term_fd(stdin_fd.as_raw_fd()).unwrap()
1892+
} else {
1893+
port_io::term_fixed_size(0, 0)
1894+
};
18861895
vec![PortDescription::console(
18871896
Some(port_io::input_empty().unwrap()),
18881897
Some(port_io::output_file(file).unwrap()),
1898+
term_fd,
18891899
)]
18901900
} else {
18911901
let (input_fd, output_fd, err_fd) = match cfg {
@@ -1935,7 +1945,30 @@ fn attach_console_devices(
19351945
Some(port_io::output_to_log_as_err())
19361946
};
19371947

1938-
let mut ports = vec![PortDescription::console(console_input, console_output)];
1948+
let terminal_properties = term_fd
1949+
.map(|fd| port_io::term_fd(fd.as_raw_fd()).unwrap())
1950+
.unwrap_or_else(|| port_io::term_fixed_size(0, 0));
1951+
1952+
if let Some(term_fd) = term_fd {
1953+
match term_set_raw_mode(term_fd, forwarding_sigint) {
1954+
Ok(old_mode) => {
1955+
vmm.exit_observers.push(Arc::new(Mutex::new(move || {
1956+
if let Err(e) = term_restore_mode(term_fd, &old_mode) {
1957+
log::error!("Failed to restore terminal mode: {e}")
1958+
}
1959+
})));
1960+
}
1961+
Err(e) => {
1962+
log::error!("Failed to set terminal to raw mode: {e}")
1963+
}
1964+
};
1965+
}
1966+
1967+
let mut ports = vec![PortDescription::console(
1968+
console_input,
1969+
console_output,
1970+
terminal_properties,
1971+
)];
19391972

19401973
if input_fd >= 0 && !input_is_terminal {
19411974
ports.push(PortDescription::input_pipe(

src/vmm/src/terminal.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ pub fn term_set_raw_mode(
1313
let old_state = termios.clone();
1414

1515
let mut mask = LocalFlags::ECHO | LocalFlags::ICANON;
16-
if !handle_signals_by_terminal {
16+
if handle_signals_by_terminal {
1717
mask |= LocalFlags::ISIG
1818
}
1919

0 commit comments

Comments
 (0)