microsoft/openvmm

Public

mirrored fromhttps://github.com/microsoft/openvmmAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
e6c778cbebacf3a70be1d39295b4f090134c4091

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

petri/src/linux_direct_serial_agent.rs

81lines · modecode

1// Copyright (C) Microsoft Corporation. All rights reserved.
2
3use futures::AsyncReadExt;
4use futures::AsyncWriteExt;
5use pal_async::socket::ReadHalf;
6use pal_async::socket::WriteHalf;
7use unix_socket::UnixStream;
8
9const BUSYBOX_INIT: &str =
10 "/bin/busybox --install /bin && mount none /dev -t devtmpfs && mount none /proc -t proc && mount none /sys -t sysfs";
11
12pub(crate) struct LinuxDirectSerialAgent {
13 /// Writer to serial 0, the console we define in our kernel commandline
14 write: WriteHalf<UnixStream>,
15 /// Reader on serial 1, not serial 0, to avoid reading the commands we just sent
16 read: ReadHalf<UnixStream>,
17 /// Delayed initialization so new can be synchronous
18 init: bool,
19}
20
21impl LinuxDirectSerialAgent {
22 pub(crate) fn new(
23 serial1_read: ReadHalf<UnixStream>,
24 serial0_write: WriteHalf<UnixStream>,
25 ) -> Self {
26 Self {
27 read: serial1_read,
28 write: serial0_write,
29 init: false,
30 }
31 }
32}
33
34impl LinuxDirectSerialAgent {
35 // Inform the agent that it should reinitialize itself on the next command.
36 pub(crate) fn reset(&mut self) {
37 self.init = false;
38 }
39
40 pub(crate) async fn run_command(&mut self, command: &str) -> anyhow::Result<String> {
41 self.init_busybox_if_necessary().await?;
42 let bytes = self.run_command_core(command).await?;
43 Ok(String::from_utf8_lossy(&bytes).into_owned())
44 }
45
46 async fn run_command_core(&mut self, command: &str) -> anyhow::Result<Vec<u8>> {
47 // We need a signal that the current command has finished executing so that we can stop reading
48 // and return to the caller. The pipe will remain open, so we can't just read until we get 0 bytes.
49 // Instead we send this special text sequence to signal the end of the command, since it's unlikely
50 // that a normal command will ever output it.
51 const COMMAND_END_SIGNAL: &str = "== Petri Command Complete ==";
52 let command = format!("({command}) > /dev/ttyS1\necho {COMMAND_END_SIGNAL} > /dev/ttyS1\n");
53
54 // When reading the output there will be a trailing newline.
55 const COMMAND_END_SIGNAL_READ: &str = "== Petri Command Complete ==\r\n";
56
57 self.write.write_all(command.as_bytes()).await?;
58
59 let mut output = Vec::new();
60 let mut buf = [0u8; 1024];
61 loop {
62 let n = self.read.read(&mut buf).await?;
63 tracing::debug!(buf = ?&buf[..n], "read serial bytes from guest");
64 output.extend_from_slice(&buf[..n]);
65 if output.ends_with(COMMAND_END_SIGNAL_READ.as_bytes()) {
66 output.truncate(output.len() - COMMAND_END_SIGNAL_READ.len());
67 break;
68 }
69 }
70
71 Ok(output)
72 }
73
74 async fn init_busybox_if_necessary(&mut self) -> anyhow::Result<()> {
75 if !self.init {
76 self.run_command_core(BUSYBOX_INIT).await?;
77 self.init = true;
78 };
79 Ok(())
80 }
81}
82