Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/devices/src/sys/linux/serial_device.rs
5394 views
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 {
39
pub fn new() -> Self {
40
Self(std::io::stdin())
41
}
42
}
43
44
impl 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
50
impl ReadNotifier for ConsoleInput {
51
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 {
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.
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 {
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
107
impl 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
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.
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
max_queue_sizes: param.max_queue_sizes.clone(),
261
},
262
keep_rds.to_vec(),
263
))
264
}
265
266