1cf6115a7SPraveen K Paladugu // Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2cf6115a7SPraveen K Paladugu //
3cf6115a7SPraveen K Paladugu // Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
4cf6115a7SPraveen K Paladugu // Use of this source code is governed by a BSD-style license that can be
5cf6115a7SPraveen K Paladugu // found in the LICENSE-BSD-3-Clause file.
6cf6115a7SPraveen K Paladugu //
7cf6115a7SPraveen K Paladugu // Copyright © 2019 Intel Corporation
8cf6115a7SPraveen K Paladugu // Copyright © 2024 Microsoft Corporation
9cf6115a7SPraveen K Paladugu //
10cf6115a7SPraveen K Paladugu // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
11cf6115a7SPraveen K Paladugu //
12cf6115a7SPraveen K Paladugu
1361e57e1cSRuoqing He use std::fs::{read_link, File, OpenOptions};
14cf6115a7SPraveen K Paladugu use std::mem::zeroed;
1561e57e1cSRuoqing He use std::os::fd::{AsRawFd, FromRawFd, RawFd};
16cf6115a7SPraveen K Paladugu use std::os::unix::fs::OpenOptionsExt;
17cf6115a7SPraveen K Paladugu use std::os::unix::net::UnixListener;
18cf6115a7SPraveen K Paladugu use std::path::PathBuf;
1961e57e1cSRuoqing He use std::sync::{Arc, Mutex};
2061e57e1cSRuoqing He use std::{io, result};
2188a9f799SRob Bradford
2261e57e1cSRuoqing He use libc::{cfmakeraw, isatty, tcgetattr, tcsetattr, termios, TCSANOW};
23cf6115a7SPraveen K Paladugu use thiserror::Error;
24cf6115a7SPraveen K Paladugu
2588a9f799SRob Bradford use crate::sigwinch_listener::listen_for_sigwinch_on_tty;
2688a9f799SRob Bradford use crate::vm_config::ConsoleOutputMode;
2788a9f799SRob Bradford use crate::Vmm;
2888a9f799SRob Bradford
29cf6115a7SPraveen K Paladugu const TIOCSPTLCK: libc::c_int = 0x4004_5431;
3011d98fccSPraveen K Paladugu const TIOCGPTPEER: libc::c_int = 0x5441;
31cf6115a7SPraveen K Paladugu
32cf6115a7SPraveen K Paladugu /// Errors associated with console devices
33cf6115a7SPraveen K Paladugu #[derive(Debug, Error)]
34cf6115a7SPraveen K Paladugu pub enum ConsoleDeviceError {
35cf6115a7SPraveen K Paladugu /// Error creating console device
36*d7edd9d5SPhilipp Schuster #[error("Error creating console device")]
37cf6115a7SPraveen K Paladugu CreateConsoleDevice(#[source] io::Error),
38cf6115a7SPraveen K Paladugu
39287887c9SAlyssa Ross /// No socket option support for console device
40287887c9SAlyssa Ross #[error("No socket option support for console device")]
41287887c9SAlyssa Ross NoSocketOptionSupportForConsoleDevice,
42287887c9SAlyssa Ross
43cf6115a7SPraveen K Paladugu /// Error setting pty raw mode
44*d7edd9d5SPhilipp Schuster #[error("Error setting pty raw mode")]
45cf6115a7SPraveen K Paladugu SetPtyRaw(#[source] vmm_sys_util::errno::Error),
46cf6115a7SPraveen K Paladugu
47cf6115a7SPraveen K Paladugu /// Cannot duplicate file descriptor
48*d7edd9d5SPhilipp Schuster #[error("Cannot duplicate file descriptor")]
49cf6115a7SPraveen K Paladugu DupFd(#[source] vmm_sys_util::errno::Error),
50385f9a9aSPraveen K Paladugu
51385f9a9aSPraveen K Paladugu /// Error starting sigwinch listener
52*d7edd9d5SPhilipp Schuster #[error("Error starting sigwinch listener")]
53385f9a9aSPraveen K Paladugu StartSigwinchListener(#[source] std::io::Error),
54cf6115a7SPraveen K Paladugu }
55cf6115a7SPraveen K Paladugu
56cf6115a7SPraveen K Paladugu type ConsoleDeviceResult<T> = result::Result<T, ConsoleDeviceError>;
57cf6115a7SPraveen K Paladugu
58287887c9SAlyssa Ross #[derive(Clone)]
59287887c9SAlyssa Ross pub enum ConsoleOutput {
60287887c9SAlyssa Ross File(Arc<File>),
61287887c9SAlyssa Ross Pty(Arc<File>),
62287887c9SAlyssa Ross Tty(Arc<File>),
63287887c9SAlyssa Ross Null,
64287887c9SAlyssa Ross Socket(Arc<UnixListener>),
65287887c9SAlyssa Ross Off,
66287887c9SAlyssa Ross }
67287887c9SAlyssa Ross
68287887c9SAlyssa Ross #[derive(Clone)]
69cf6115a7SPraveen K Paladugu pub struct ConsoleInfo {
70287887c9SAlyssa Ross pub console_main_fd: ConsoleOutput,
71287887c9SAlyssa Ross pub serial_main_fd: ConsoleOutput,
72cf6115a7SPraveen K Paladugu #[cfg(target_arch = "x86_64")]
73287887c9SAlyssa Ross pub debug_main_fd: ConsoleOutput,
74cf6115a7SPraveen K Paladugu }
75cf6115a7SPraveen K Paladugu
modify_mode<F: FnOnce(&mut termios)>( fd: RawFd, f: F, original_termios_opt: Arc<Mutex<Option<termios>>>, ) -> vmm_sys_util::errno::Result<()>76cf6115a7SPraveen K Paladugu fn modify_mode<F: FnOnce(&mut termios)>(
77cf6115a7SPraveen K Paladugu fd: RawFd,
78cf6115a7SPraveen K Paladugu f: F,
79cf6115a7SPraveen K Paladugu original_termios_opt: Arc<Mutex<Option<termios>>>,
80cf6115a7SPraveen K Paladugu ) -> vmm_sys_util::errno::Result<()> {
81cf6115a7SPraveen K Paladugu // SAFETY: safe because we check the return value of isatty.
82cf6115a7SPraveen K Paladugu if unsafe { isatty(fd) } != 1 {
83cf6115a7SPraveen K Paladugu return Ok(());
84cf6115a7SPraveen K Paladugu }
85cf6115a7SPraveen K Paladugu
86cf6115a7SPraveen K Paladugu // SAFETY: The following pair are safe because termios gets totally overwritten by tcgetattr
87cf6115a7SPraveen K Paladugu // and we check the return result.
88cf6115a7SPraveen K Paladugu let mut termios: termios = unsafe { zeroed() };
89cf6115a7SPraveen K Paladugu // SAFETY: see above
90cf6115a7SPraveen K Paladugu let ret = unsafe { tcgetattr(fd, &mut termios as *mut _) };
91cf6115a7SPraveen K Paladugu if ret < 0 {
92cf6115a7SPraveen K Paladugu return vmm_sys_util::errno::errno_result();
93cf6115a7SPraveen K Paladugu }
94cf6115a7SPraveen K Paladugu let mut original_termios_opt = original_termios_opt.lock().unwrap();
95cf6115a7SPraveen K Paladugu if original_termios_opt.is_none() {
96cf6115a7SPraveen K Paladugu original_termios_opt.replace(termios);
97cf6115a7SPraveen K Paladugu }
98cf6115a7SPraveen K Paladugu
99cf6115a7SPraveen K Paladugu f(&mut termios);
100cf6115a7SPraveen K Paladugu // SAFETY: Safe because the syscall will only read the extent of termios and we check
101cf6115a7SPraveen K Paladugu // the return result.
102cf6115a7SPraveen K Paladugu let ret = unsafe { tcsetattr(fd, TCSANOW, &termios as *const _) };
103cf6115a7SPraveen K Paladugu if ret < 0 {
104cf6115a7SPraveen K Paladugu return vmm_sys_util::errno::errno_result();
105cf6115a7SPraveen K Paladugu }
106cf6115a7SPraveen K Paladugu
107cf6115a7SPraveen K Paladugu Ok(())
108cf6115a7SPraveen K Paladugu }
109cf6115a7SPraveen K Paladugu
set_raw_mode( f: &dyn AsRawFd, original_termios_opt: Arc<Mutex<Option<termios>>>, ) -> ConsoleDeviceResult<()>11052eebaf6SPraveen K Paladugu fn set_raw_mode(
111cf6115a7SPraveen K Paladugu f: &dyn AsRawFd,
112cf6115a7SPraveen K Paladugu original_termios_opt: Arc<Mutex<Option<termios>>>,
113cf6115a7SPraveen K Paladugu ) -> ConsoleDeviceResult<()> {
114cf6115a7SPraveen K Paladugu modify_mode(
115cf6115a7SPraveen K Paladugu f.as_raw_fd(),
116cf6115a7SPraveen K Paladugu // SAFETY: FFI call. Variable t is guaranteed to be a valid termios from modify_mode.
117cf6115a7SPraveen K Paladugu |t| unsafe { cfmakeraw(t) },
118cf6115a7SPraveen K Paladugu original_termios_opt,
119cf6115a7SPraveen K Paladugu )
120cf6115a7SPraveen K Paladugu .map_err(ConsoleDeviceError::SetPtyRaw)
121cf6115a7SPraveen K Paladugu }
122cf6115a7SPraveen K Paladugu
create_pty() -> io::Result<(File, File, PathBuf)>12352eebaf6SPraveen K Paladugu fn create_pty() -> io::Result<(File, File, PathBuf)> {
124cf6115a7SPraveen K Paladugu // Try to use /dev/pts/ptmx first then fall back to /dev/ptmx
125cf6115a7SPraveen K Paladugu // This is done to try and use the devpts filesystem that
126cf6115a7SPraveen K Paladugu // could be available for use in the process's namespace first.
127cf6115a7SPraveen K Paladugu // Ideally these are all the same file though but different
128cf6115a7SPraveen K Paladugu // kernels could have things setup differently.
129cf6115a7SPraveen K Paladugu // See https://www.kernel.org/doc/Documentation/filesystems/devpts.txt
130cf6115a7SPraveen K Paladugu // for further details.
131cf6115a7SPraveen K Paladugu
132cf6115a7SPraveen K Paladugu let custom_flags = libc::O_NONBLOCK;
133cf6115a7SPraveen K Paladugu let main = match OpenOptions::new()
134cf6115a7SPraveen K Paladugu .read(true)
135cf6115a7SPraveen K Paladugu .write(true)
136cf6115a7SPraveen K Paladugu .custom_flags(custom_flags)
137cf6115a7SPraveen K Paladugu .open("/dev/pts/ptmx")
138cf6115a7SPraveen K Paladugu {
139cf6115a7SPraveen K Paladugu Ok(f) => f,
140cf6115a7SPraveen K Paladugu _ => OpenOptions::new()
141cf6115a7SPraveen K Paladugu .read(true)
142cf6115a7SPraveen K Paladugu .write(true)
143cf6115a7SPraveen K Paladugu .custom_flags(custom_flags)
144cf6115a7SPraveen K Paladugu .open("/dev/ptmx")?,
145cf6115a7SPraveen K Paladugu };
146cf6115a7SPraveen K Paladugu let mut unlock: libc::c_ulong = 0;
147cf6115a7SPraveen K Paladugu // SAFETY: FFI call into libc, trivially safe
148cf6115a7SPraveen K Paladugu unsafe { libc::ioctl(main.as_raw_fd(), TIOCSPTLCK as _, &mut unlock) };
149cf6115a7SPraveen K Paladugu
150cf6115a7SPraveen K Paladugu // SAFETY: FFI call into libc, trivially safe
151cf6115a7SPraveen K Paladugu let sub_fd = unsafe {
152cf6115a7SPraveen K Paladugu libc::ioctl(
153cf6115a7SPraveen K Paladugu main.as_raw_fd(),
15411d98fccSPraveen K Paladugu TIOCGPTPEER as _,
155cf6115a7SPraveen K Paladugu libc::O_NOCTTY | libc::O_RDWR,
156cf6115a7SPraveen K Paladugu )
157cf6115a7SPraveen K Paladugu };
158cf6115a7SPraveen K Paladugu if sub_fd == -1 {
159cf6115a7SPraveen K Paladugu return vmm_sys_util::errno::errno_result().map_err(|e| e.into());
160cf6115a7SPraveen K Paladugu }
161cf6115a7SPraveen K Paladugu
162cf6115a7SPraveen K Paladugu let proc_path = PathBuf::from(format!("/proc/self/fd/{sub_fd}"));
163cf6115a7SPraveen K Paladugu let path = read_link(proc_path)?;
164cf6115a7SPraveen K Paladugu
165cf6115a7SPraveen K Paladugu // SAFETY: sub_fd is checked to be valid before being wrapped in File
166cf6115a7SPraveen K Paladugu Ok((main, unsafe { File::from_raw_fd(sub_fd) }, path))
167cf6115a7SPraveen K Paladugu }
168cf6115a7SPraveen K Paladugu
dup_stdout() -> vmm_sys_util::errno::Result<File>169a5df8669SAlyssa Ross fn dup_stdout() -> vmm_sys_util::errno::Result<File> {
170a5df8669SAlyssa Ross // SAFETY: FFI call to dup. Trivially safe.
171a5df8669SAlyssa Ross let stdout = unsafe { libc::dup(libc::STDOUT_FILENO) };
172a5df8669SAlyssa Ross if stdout == -1 {
173a5df8669SAlyssa Ross return vmm_sys_util::errno::errno_result();
174a5df8669SAlyssa Ross }
175a5df8669SAlyssa Ross // SAFETY: stdout is valid and owned solely by us.
176a5df8669SAlyssa Ross Ok(unsafe { File::from_raw_fd(stdout) })
177a5df8669SAlyssa Ross }
178a5df8669SAlyssa Ross
pre_create_console_devices(vmm: &mut Vmm) -> ConsoleDeviceResult<ConsoleInfo>179cf6115a7SPraveen K Paladugu pub(crate) fn pre_create_console_devices(vmm: &mut Vmm) -> ConsoleDeviceResult<ConsoleInfo> {
180cf6115a7SPraveen K Paladugu let vm_config = vmm.vm_config.as_mut().unwrap().clone();
181cf6115a7SPraveen K Paladugu let mut vmconfig = vm_config.lock().unwrap();
182cf6115a7SPraveen K Paladugu
183287887c9SAlyssa Ross let console_info = ConsoleInfo {
184287887c9SAlyssa Ross console_main_fd: match vmconfig.console.mode {
185cf6115a7SPraveen K Paladugu ConsoleOutputMode::File => {
186cf6115a7SPraveen K Paladugu let file = File::create(vmconfig.console.file.as_ref().unwrap())
187cf6115a7SPraveen K Paladugu .map_err(ConsoleDeviceError::CreateConsoleDevice)?;
188287887c9SAlyssa Ross ConsoleOutput::File(Arc::new(file))
189cf6115a7SPraveen K Paladugu }
190cf6115a7SPraveen K Paladugu ConsoleOutputMode::Pty => {
191cf6115a7SPraveen K Paladugu let (main_fd, sub_fd, path) =
192cf6115a7SPraveen K Paladugu create_pty().map_err(ConsoleDeviceError::CreateConsoleDevice)?;
193cf6115a7SPraveen K Paladugu set_raw_mode(&sub_fd.as_raw_fd(), vmm.original_termios_opt.clone())?;
194cf6115a7SPraveen K Paladugu vmconfig.console.file = Some(path.clone());
1954bfeba96SAlyssa Ross vmm.console_resize_pipe = Some(Arc::new(
196385f9a9aSPraveen K Paladugu listen_for_sigwinch_on_tty(
197385f9a9aSPraveen K Paladugu sub_fd,
198385f9a9aSPraveen K Paladugu &vmm.seccomp_action,
199385f9a9aSPraveen K Paladugu vmm.hypervisor.hypervisor_type(),
200385f9a9aSPraveen K Paladugu )
201385f9a9aSPraveen K Paladugu .map_err(ConsoleDeviceError::StartSigwinchListener)?,
2024bfeba96SAlyssa Ross ));
203287887c9SAlyssa Ross ConsoleOutput::Pty(Arc::new(main_fd))
204cf6115a7SPraveen K Paladugu }
205cf6115a7SPraveen K Paladugu ConsoleOutputMode::Tty => {
206cf6115a7SPraveen K Paladugu // Duplicating the file descriptors like this is needed as otherwise
207cf6115a7SPraveen K Paladugu // they will be closed on a reboot and the numbers reused
208cf6115a7SPraveen K Paladugu
209a5df8669SAlyssa Ross let stdout = dup_stdout().map_err(ConsoleDeviceError::DupFd)?;
210cf6115a7SPraveen K Paladugu
211385f9a9aSPraveen K Paladugu // SAFETY: FFI call. Trivially safe.
2129f969ee1SPraveen K Paladugu if unsafe { libc::isatty(stdout.as_raw_fd()) } == 1 {
2134bfeba96SAlyssa Ross vmm.console_resize_pipe = Some(Arc::new(
214385f9a9aSPraveen K Paladugu listen_for_sigwinch_on_tty(
215385f9a9aSPraveen K Paladugu stdout.try_clone().unwrap(),
216385f9a9aSPraveen K Paladugu &vmm.seccomp_action,
217385f9a9aSPraveen K Paladugu vmm.hypervisor.hypervisor_type(),
218385f9a9aSPraveen K Paladugu )
219385f9a9aSPraveen K Paladugu .map_err(ConsoleDeviceError::StartSigwinchListener)?,
2204bfeba96SAlyssa Ross ));
221385f9a9aSPraveen K Paladugu }
222385f9a9aSPraveen K Paladugu
223cf6115a7SPraveen K Paladugu // Make sure stdout is in raw mode, if it's a terminal.
224cf6115a7SPraveen K Paladugu set_raw_mode(&stdout, vmm.original_termios_opt.clone())?;
225287887c9SAlyssa Ross ConsoleOutput::Tty(Arc::new(stdout))
226cf6115a7SPraveen K Paladugu }
227287887c9SAlyssa Ross ConsoleOutputMode::Socket => {
228287887c9SAlyssa Ross return Err(ConsoleDeviceError::NoSocketOptionSupportForConsoleDevice)
229cf6115a7SPraveen K Paladugu }
230287887c9SAlyssa Ross ConsoleOutputMode::Null => ConsoleOutput::Null,
231287887c9SAlyssa Ross ConsoleOutputMode::Off => ConsoleOutput::Off,
232287887c9SAlyssa Ross },
233287887c9SAlyssa Ross serial_main_fd: match vmconfig.serial.mode {
234cf6115a7SPraveen K Paladugu ConsoleOutputMode::File => {
235cf6115a7SPraveen K Paladugu let file = File::create(vmconfig.serial.file.as_ref().unwrap())
236cf6115a7SPraveen K Paladugu .map_err(ConsoleDeviceError::CreateConsoleDevice)?;
237287887c9SAlyssa Ross ConsoleOutput::File(Arc::new(file))
238cf6115a7SPraveen K Paladugu }
239cf6115a7SPraveen K Paladugu ConsoleOutputMode::Pty => {
240cf6115a7SPraveen K Paladugu let (main_fd, sub_fd, path) =
241cf6115a7SPraveen K Paladugu create_pty().map_err(ConsoleDeviceError::CreateConsoleDevice)?;
242cf6115a7SPraveen K Paladugu set_raw_mode(&sub_fd.as_raw_fd(), vmm.original_termios_opt.clone())?;
243cf6115a7SPraveen K Paladugu vmconfig.serial.file = Some(path.clone());
244287887c9SAlyssa Ross ConsoleOutput::Pty(Arc::new(main_fd))
245cf6115a7SPraveen K Paladugu }
246cf6115a7SPraveen K Paladugu ConsoleOutputMode::Tty => {
247a8fa2af6SPraveen K Paladugu // During vm_shutdown, when serial device is closed, FD#2(STDOUT)
248a8fa2af6SPraveen K Paladugu // will be closed and FD#2 could be reused in a future boot of the
249a8fa2af6SPraveen K Paladugu // guest by a different file.
250a8fa2af6SPraveen K Paladugu //
251a8fa2af6SPraveen K Paladugu // To ensure FD#2 always points to STANDARD OUT, a `dup` of STDOUT
252a8fa2af6SPraveen K Paladugu // is passed to serial device. Doing so, even if the serial device
253a8fa2af6SPraveen K Paladugu // were to be closed, FD#2 will continue to point to STANDARD OUT.
254a8fa2af6SPraveen K Paladugu
255a5df8669SAlyssa Ross let stdout = dup_stdout().map_err(ConsoleDeviceError::DupFd)?;
256a8fa2af6SPraveen K Paladugu
257a8fa2af6SPraveen K Paladugu // Make sure stdout is in raw mode, if it's a terminal.
258a8fa2af6SPraveen K Paladugu set_raw_mode(&stdout, vmm.original_termios_opt.clone())?;
259287887c9SAlyssa Ross
260287887c9SAlyssa Ross ConsoleOutput::Tty(Arc::new(stdout))
261cf6115a7SPraveen K Paladugu }
262cf6115a7SPraveen K Paladugu ConsoleOutputMode::Socket => {
263cf6115a7SPraveen K Paladugu let listener = UnixListener::bind(vmconfig.serial.socket.as_ref().unwrap())
264cf6115a7SPraveen K Paladugu .map_err(ConsoleDeviceError::CreateConsoleDevice)?;
265287887c9SAlyssa Ross ConsoleOutput::Socket(Arc::new(listener))
266cf6115a7SPraveen K Paladugu }
267287887c9SAlyssa Ross ConsoleOutputMode::Null => ConsoleOutput::Null,
268287887c9SAlyssa Ross ConsoleOutputMode::Off => ConsoleOutput::Off,
269287887c9SAlyssa Ross },
270cf6115a7SPraveen K Paladugu #[cfg(target_arch = "x86_64")]
271287887c9SAlyssa Ross debug_main_fd: match vmconfig.debug_console.mode {
272cf6115a7SPraveen K Paladugu ConsoleOutputMode::File => {
273cf6115a7SPraveen K Paladugu let file = File::create(vmconfig.debug_console.file.as_ref().unwrap())
274cf6115a7SPraveen K Paladugu .map_err(ConsoleDeviceError::CreateConsoleDevice)?;
275287887c9SAlyssa Ross ConsoleOutput::File(Arc::new(file))
276cf6115a7SPraveen K Paladugu }
277cf6115a7SPraveen K Paladugu ConsoleOutputMode::Pty => {
278cf6115a7SPraveen K Paladugu let (main_fd, sub_fd, path) =
279cf6115a7SPraveen K Paladugu create_pty().map_err(ConsoleDeviceError::CreateConsoleDevice)?;
280cf6115a7SPraveen K Paladugu set_raw_mode(&sub_fd.as_raw_fd(), vmm.original_termios_opt.clone())?;
281cf6115a7SPraveen K Paladugu vmconfig.debug_console.file = Some(path.clone());
282287887c9SAlyssa Ross ConsoleOutput::Pty(Arc::new(main_fd))
283cf6115a7SPraveen K Paladugu }
284cf6115a7SPraveen K Paladugu ConsoleOutputMode::Tty => {
285287887c9SAlyssa Ross let out =
286287887c9SAlyssa Ross dup_stdout().map_err(|e| ConsoleDeviceError::CreateConsoleDevice(e.into()))?;
287cf6115a7SPraveen K Paladugu set_raw_mode(&out, vmm.original_termios_opt.clone())?;
288287887c9SAlyssa Ross ConsoleOutput::Tty(Arc::new(out))
289cf6115a7SPraveen K Paladugu }
290287887c9SAlyssa Ross ConsoleOutputMode::Socket => {
291287887c9SAlyssa Ross return Err(ConsoleDeviceError::NoSocketOptionSupportForConsoleDevice)
292cf6115a7SPraveen K Paladugu }
293287887c9SAlyssa Ross ConsoleOutputMode::Null => ConsoleOutput::Null,
294287887c9SAlyssa Ross ConsoleOutputMode::Off => ConsoleOutput::Off,
295287887c9SAlyssa Ross },
296287887c9SAlyssa Ross };
297cf6115a7SPraveen K Paladugu
298cf6115a7SPraveen K Paladugu Ok(console_info)
299cf6115a7SPraveen K Paladugu }
300