xref: /aosp_15_r20/external/crosvm/devices/src/sys/linux/serial_device.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1 // Copyright 2022 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 use std::borrow::Cow;
6 use std::fs::OpenOptions;
7 use std::io;
8 use std::io::ErrorKind;
9 use std::io::Write;
10 use std::os::unix::net::UnixDatagram;
11 use std::os::unix::net::UnixStream;
12 use std::path::Path;
13 use std::path::PathBuf;
14 use std::thread;
15 use std::time::Duration;
16 
17 use base::error;
18 use base::info;
19 use base::read_raw_stdin;
20 use base::AsRawDescriptor;
21 use base::Event;
22 use base::FileSync;
23 use base::RawDescriptor;
24 use base::ReadNotifier;
25 use hypervisor::ProtectionType;
26 
27 use crate::serial_device::Error;
28 use crate::serial_device::SerialInput;
29 use crate::serial_device::SerialOptions;
30 use crate::serial_device::SerialParameters;
31 
32 pub const SYSTEM_SERIAL_TYPE_NAME: &str = "UnixSocket";
33 
34 // This wrapper is used in place of the libstd native version because we don't want
35 // buffering for stdin.
36 pub struct ConsoleInput(std::io::Stdin);
37 
38 impl ConsoleInput {
new() -> Self39     pub fn new() -> Self {
40         Self(std::io::stdin())
41     }
42 }
43 
44 impl io::Read for ConsoleInput {
read(&mut self, out: &mut [u8]) -> io::Result<usize>45     fn read(&mut self, out: &mut [u8]) -> io::Result<usize> {
46         read_raw_stdin(out).map_err(|e| e.into())
47     }
48 }
49 
50 impl ReadNotifier for ConsoleInput {
get_read_notifier(&self) -> &dyn AsRawDescriptor51     fn get_read_notifier(&self) -> &dyn AsRawDescriptor {
52         &self.0
53     }
54 }
55 
56 impl SerialInput for ConsoleInput {}
57 
58 /// Abstraction over serial-like devices that can be created given an event and optional input and
59 /// output streams.
60 pub trait SerialDevice {
new( protection_type: ProtectionType, interrupt_evt: Event, input: Option<Box<dyn SerialInput>>, output: Option<Box<dyn io::Write + Send>>, sync: Option<Box<dyn FileSync + Send>>, options: SerialOptions, keep_rds: Vec<RawDescriptor>, ) -> Self61     fn new(
62         protection_type: ProtectionType,
63         interrupt_evt: Event,
64         input: Option<Box<dyn SerialInput>>,
65         output: Option<Box<dyn io::Write + Send>>,
66         sync: Option<Box<dyn FileSync + Send>>,
67         options: SerialOptions,
68         keep_rds: Vec<RawDescriptor>,
69     ) -> Self;
70 }
71 
72 // The maximum length of a path that can be used as the address of a
73 // unix socket. Note that this includes the null-terminator.
74 pub const MAX_SOCKET_PATH_LENGTH: usize = 108;
75 
76 struct WriteSocket {
77     sock: UnixDatagram,
78     buf: Vec<u8>,
79 }
80 
81 const BUF_CAPACITY: usize = 1024;
82 
83 impl WriteSocket {
new(s: UnixDatagram) -> WriteSocket84     pub fn new(s: UnixDatagram) -> WriteSocket {
85         WriteSocket {
86             sock: s,
87             buf: Vec::with_capacity(BUF_CAPACITY),
88         }
89     }
90 
send_buf(&self, buf: &[u8]) -> io::Result<usize>91     pub fn send_buf(&self, buf: &[u8]) -> io::Result<usize> {
92         const SEND_RETRY: usize = 2;
93         let mut sent = 0;
94         for _ in 0..SEND_RETRY {
95             match self.sock.send(buf) {
96                 Ok(bytes_sent) => {
97                     sent = bytes_sent;
98                     break;
99                 }
100                 Err(e) => info!("Send error: {:?}", e),
101             }
102         }
103         Ok(sent)
104     }
105 }
106 
107 impl io::Write for WriteSocket {
write(&mut self, buf: &[u8]) -> io::Result<usize>108     fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
109         let last_newline_idx = match buf.iter().rposition(|&x| x == b'\n') {
110             Some(newline_idx) => Some(self.buf.len() + newline_idx),
111             None => None,
112         };
113         self.buf.extend_from_slice(buf);
114 
115         match last_newline_idx {
116             Some(last_newline_idx) => {
117                 for line in (self.buf[..last_newline_idx]).split(|&x| x == b'\n') {
118                     // Also drop CR+LF line endings.
119                     let send_line = match line.split_last() {
120                         Some((b'\r', trimmed)) => trimmed,
121                         _ => line,
122                     };
123                     if self.send_buf(send_line).is_err() {
124                         break;
125                     }
126                 }
127                 self.buf.drain(..=last_newline_idx);
128             }
129             None => {
130                 if self.buf.len() >= BUF_CAPACITY {
131                     if let Err(e) = self.send_buf(&self.buf) {
132                         info!("Couldn't send full buffer. {:?}", e);
133                     }
134                     self.buf.clear();
135                 }
136             }
137         }
138         Ok(buf.len())
139     }
140 
flush(&mut self) -> io::Result<()>141     fn flush(&mut self) -> io::Result<()> {
142         Ok(())
143     }
144 }
145 
create_system_type_serial_device<T: SerialDevice>( param: &SerialParameters, protection_type: ProtectionType, evt: Event, input: Option<Box<dyn SerialInput>>, keep_rds: &mut Vec<RawDescriptor>, ) -> std::result::Result<T, Error>146 pub(crate) fn create_system_type_serial_device<T: SerialDevice>(
147     param: &SerialParameters,
148     protection_type: ProtectionType,
149     evt: Event,
150     input: Option<Box<dyn SerialInput>>,
151     keep_rds: &mut Vec<RawDescriptor>,
152 ) -> std::result::Result<T, Error> {
153     match &param.path {
154         Some(path) => {
155             // If the path is longer than 107 characters,
156             // then we won't be able to connect directly
157             // to it. Instead we can shorten the path by
158             // opening the containing directory and using
159             // /proc/self/fd/*/ to access it via a shorter
160             // path.
161             let mut path_cow = Cow::<Path>::Borrowed(path);
162             let mut _dir_fd = None;
163             if path.as_os_str().len() >= MAX_SOCKET_PATH_LENGTH {
164                 let mut short_path = PathBuf::with_capacity(MAX_SOCKET_PATH_LENGTH);
165                 short_path.push("/proc/self/fd/");
166 
167                 let parent_path = path
168                     .parent()
169                     .ok_or_else(|| Error::InvalidPath(path.clone()))?;
170                 let file_name = path
171                     .file_name()
172                     .ok_or_else(|| Error::InvalidPath(path.clone()))?;
173 
174                 // We don't actually want to open this
175                 // directory for reading, but the stdlib
176                 // requires all files be opened as at
177                 // least one of readable, writeable, or
178                 // appeandable.
179                 let dir = OpenOptions::new()
180                     .read(true)
181                     .open(parent_path)
182                     .map_err(|e| Error::FileOpen(e, parent_path.into()))?;
183 
184                 short_path.push(dir.as_raw_descriptor().to_string());
185                 short_path.push(file_name);
186                 path_cow = Cow::Owned(short_path);
187                 _dir_fd = Some(dir);
188             }
189 
190             // The shortened path may still be too long,
191             // in which case we must give up here.
192             if path_cow.as_os_str().len() >= MAX_SOCKET_PATH_LENGTH {
193                 return Err(Error::InvalidPath(path_cow.into()));
194             }
195 
196             // There's a race condition between
197             // vmlog_forwarder making the logging socket and
198             // crosvm starting up, so we loop here until it's
199             // available.
200             let sock = UnixDatagram::unbound().map_err(Error::SocketCreate)?;
201             loop {
202                 match sock.connect(&path_cow) {
203                     Ok(_) => break,
204                     Err(e) => {
205                         match e.kind() {
206                             ErrorKind::NotFound | ErrorKind::ConnectionRefused => {
207                                 // logging socket doesn't
208                                 // exist yet, sleep for 10 ms
209                                 // and try again.
210                                 thread::sleep(Duration::from_millis(10))
211                             }
212                             _ => {
213                                 error!("Unexpected error connecting to logging socket: {:?}", e);
214                                 return Err(Error::SocketConnect(e));
215                             }
216                         }
217                     }
218                 };
219             }
220             keep_rds.push(sock.as_raw_descriptor());
221             let output: Option<Box<dyn Write + Send>> = Some(Box::new(WriteSocket::new(sock)));
222             Ok(T::new(
223                 protection_type,
224                 evt,
225                 input,
226                 output,
227                 None,
228                 Default::default(),
229                 keep_rds.to_vec(),
230             ))
231         }
232         None => Err(Error::PathRequired),
233     }
234 }
235 
236 /// Creates a serial device that use the given UnixStream path for both input and output.
create_unix_stream_serial_device<T: SerialDevice>( param: &SerialParameters, protection_type: ProtectionType, evt: Event, keep_rds: &mut Vec<RawDescriptor>, ) -> std::result::Result<T, Error>237 pub(crate) fn create_unix_stream_serial_device<T: SerialDevice>(
238     param: &SerialParameters,
239     protection_type: ProtectionType,
240     evt: Event,
241     keep_rds: &mut Vec<RawDescriptor>,
242 ) -> std::result::Result<T, Error> {
243     let path = param.path.as_ref().ok_or(Error::PathRequired)?;
244     let input = UnixStream::connect(path).map_err(Error::SocketConnect)?;
245     let output = input.try_clone().map_err(Error::CloneUnixStream)?;
246     keep_rds.push(input.as_raw_descriptor());
247     keep_rds.push(output.as_raw_descriptor());
248 
249     Ok(T::new(
250         protection_type,
251         evt,
252         Some(Box::new(input)),
253         Some(Box::new(output)),
254         None,
255         SerialOptions {
256             name: param.name.clone(),
257             out_timestamp: param.out_timestamp,
258             console: param.console,
259             pci_address: param.pci_address,
260         },
261         keep_rds.to_vec(),
262     ))
263 }
264