Skip to main content

guest_cli/
attach.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::platform::{GuestConsole, PlatformServices, Stdio};
6use anyhow::{Error, anyhow};
7use fidl::endpoints::create_proxy;
8use fidl_fuchsia_virtualization::{GuestMarker, GuestProxy, GuestStatus};
9use fuchsia_async as fasync;
10use guest_cli_args as arguments;
11use std::fmt;
12
13#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
14pub enum AttachResult {
15    Attached,
16    NotRunning,
17    AttachFailure,
18}
19
20impl fmt::Display for AttachResult {
21    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22        match self {
23            AttachResult::Attached => {
24                write!(f, "Disconnected from guest after a successful attach")
25            }
26            AttachResult::NotRunning => write!(f, "Can't attach to a non-running guest"),
27            AttachResult::AttachFailure => write!(f, "Failed to attach to guest"),
28        }
29    }
30}
31
32pub async fn handle_attach<P: PlatformServices>(
33    services: &P,
34    args: &arguments::attach_args::AttachArgs,
35) -> Result<AttachResult, Error> {
36    let manager = services.connect_to_manager(args.guest_type).await?;
37    let status = manager.get_info().await?.guest_status.expect("guest status should always be set");
38    if status != GuestStatus::Starting && status != GuestStatus::Running {
39        return Ok(AttachResult::NotRunning);
40    }
41
42    let (guest_endpoint, guest_server_end) = create_proxy::<GuestMarker>();
43    manager
44        .connect(guest_server_end)
45        .await
46        .map_err(|err| anyhow!("failed to get a connect response: {}", err))?
47        .map_err(|err| anyhow!("connect failed with: {:?}", err))?;
48
49    Ok(match attach(guest_endpoint, args.serial).await {
50        Ok(()) => AttachResult::Attached,
51        Err(_) => AttachResult::AttachFailure,
52    })
53}
54
55pub async fn attach(guest: GuestProxy, serial_only: bool) -> Result<(), Error> {
56    if serial_only { attach_serial(guest).await } else { attach_console_and_serial(guest).await }
57}
58
59// Attach to a running guest, using the guest's virtio-console and serial output for stdout, and
60// the guest's virtio-console for stdin.
61async fn attach_console_and_serial(guest: GuestProxy) -> Result<(), Error> {
62    // Tie serial output to stdout.
63    let guest_serial_response = guest.get_serial().await?;
64    let guest_serial = fasync::Socket::from_socket(guest_serial_response);
65    let serial_output = async {
66        futures::io::copy(guest_serial, &mut GuestConsole::get_unblocked_stdio(Stdio::Stdout))
67            .await
68            .map(|_| ())
69            .map_err(anyhow::Error::from)
70    };
71
72    // Host doesn't currently support duplicating Fuchsia handles, so just call get console twice
73    // and let the VMM duplicate the socket for reading and writing.
74    let console_input = guest.get_console().await?.map_err(|err| anyhow!(format!("{:?}", err)))?;
75    let console_output = guest.get_console().await?.map_err(|err| anyhow!(format!("{:?}", err)))?;
76    let guest_console = GuestConsole::new(console_input, console_output)?;
77
78    futures::future::try_join(serial_output, guest_console.run_with_stdio())
79        .await
80        .map(|_| ())
81        .map_err(anyhow::Error::from)
82}
83
84// Attach to a running guest using serial for stdout and stdin.
85async fn attach_serial(guest: GuestProxy) -> Result<(), Error> {
86    // Host doesn't currently support duplicating Fuchsia handles, so just call get serial twice
87    // and let the VMM duplicate the socket for reading and writing.
88    let serial_input = guest.get_serial().await?;
89    let serial_output = guest.get_serial().await?;
90
91    let guest_console = GuestConsole::new(serial_input, serial_output)?;
92    guest_console.run_with_stdio().await
93}
94
95#[cfg(test)]
96mod test {
97    use super::*;
98    use fidl::Socket;
99    use fidl::endpoints::create_proxy_and_stream;
100    use fidl_fuchsia_virtualization::GuestError;
101    use futures::StreamExt;
102    use futures::future::join;
103
104    #[fuchsia::test(allow_stalls = false)]
105    async fn launch_invalid_console_returns_error() {
106        let (guest_proxy, mut guest_stream) = create_proxy_and_stream::<GuestMarker>();
107        let (serial_launch_sock, _serial_server_sock) = Socket::create_stream();
108
109        let server = async move {
110            let serial_responder = guest_stream
111                .next()
112                .await
113                .expect("Failed to read from stream")
114                .expect("Failed to parse request")
115                .into_get_serial()
116                .expect("Unexpected call to Guest Proxy");
117            serial_responder.send(serial_launch_sock).expect("Failed to send response to proxy");
118
119            let console_responder = guest_stream
120                .next()
121                .await
122                .expect("Failed to read from stream")
123                .expect("Failed to parse request")
124                .into_get_console()
125                .expect("Unexpected call to Guest Proxy");
126            console_responder
127                .send(Err(GuestError::DeviceNotPresent))
128                .expect("Failed to send response to proxy");
129        };
130
131        let client = attach(guest_proxy, false);
132        let (_, client_res) = join(server, client).await;
133        assert!(client_res.is_err());
134    }
135}