devices/sys/linux/
serial_device.rs

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
5use std::borrow::Cow;
6use std::fs::OpenOptions;
7use std::io;
8use std::io::ErrorKind;
9use std::io::Write;
10use std::os::unix::net::UnixDatagram;
11use std::os::unix::net::UnixStream;
12use std::path::Path;
13use std::path::PathBuf;
14use std::thread;
15use std::time::Duration;
16
17use base::error;
18use base::info;
19use base::read_raw_stdin;
20use base::AsRawDescriptor;
21use base::Event;
22use base::FileSync;
23use base::RawDescriptor;
24use base::ReadNotifier;
25use hypervisor::ProtectionType;
26
27use crate::serial_device::Error;
28use crate::serial_device::SerialInput;
29use crate::serial_device::SerialOptions;
30use crate::serial_device::SerialParameters;
31
32pub 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.
36pub struct ConsoleInput(std::io::Stdin);
37
38impl ConsoleInput {
39    pub fn new() -> Self {
40        Self(std::io::stdin())
41    }
42}
43
44impl io::Read for ConsoleInput {
45    fn read(&mut self, out: &mut [u8]) -> io::Result<usize> {
46        read_raw_stdin(out).map_err(|e| e.into())
47    }
48}
49
50impl ReadNotifier for ConsoleInput {
51    fn get_read_notifier(&self) -> &dyn AsRawDescriptor {
52        &self.0
53    }
54}
55
56impl SerialInput for ConsoleInput {}
57
58/// Abstraction over serial-like devices that can be created given an event and optional input and
59/// output streams.
60pub trait SerialDevice {
61    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.
74pub const MAX_SOCKET_PATH_LENGTH: usize = 108;
75
76struct WriteSocket {
77    sock: UnixDatagram,
78    buf: Vec<u8>,
79}
80
81const BUF_CAPACITY: usize = 1024;
82
83impl WriteSocket {
84    pub fn new(s: UnixDatagram) -> WriteSocket {
85        WriteSocket {
86            sock: s,
87            buf: Vec::with_capacity(BUF_CAPACITY),
88        }
89    }
90
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
107impl io::Write for WriteSocket {
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
141    fn flush(&mut self) -> io::Result<()> {
142        Ok(())
143    }
144}
145
146pub(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.
237pub(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            max_queue_sizes: param.max_queue_sizes.clone(),
261        },
262        keep_rds.to_vec(),
263    ))
264}