Skip to main content

virtual_console_lib/
session_manager.rs

1// Copyright 2021 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::terminal::Terminal;
6use anyhow::{Context as _, Error};
7use fidl::endpoints::{RequestStream, ServerEnd};
8use fidl_fuchsia_hardware_pty::DeviceMarker;
9use fidl_fuchsia_virtualconsole::{SessionManagerRequest, SessionManagerRequestStream};
10use fuchsia_async as fasync;
11use futures::io::AsyncReadExt;
12use futures::prelude::*;
13use pty::ServerPty;
14use std::cell::RefCell;
15use std::rc::Rc;
16
17const BYTE_BUFFER_MAX_SIZE: usize = 128;
18
19pub trait SessionManagerClient: 'static + Clone {
20    fn create_terminal(
21        &self,
22        id: u32,
23        title: String,
24        make_active: bool,
25        pty: ServerPty,
26    ) -> Result<Terminal, Error>;
27    fn request_update(&self, id: u32);
28}
29
30pub struct SessionManager {
31    keep_log_visible: bool,
32    first_session_id: u32,
33    next_session_id: Rc<RefCell<u32>>,
34    has_primary_connected: Rc<RefCell<bool>>,
35}
36
37impl SessionManager {
38    pub fn new(keep_log_visible: bool, first_session_id: u32) -> Self {
39        let next_session_id = Rc::new(RefCell::new(first_session_id));
40        let has_primary_connected = Rc::new(RefCell::new(false));
41
42        Self { keep_log_visible, first_session_id, next_session_id, has_primary_connected }
43    }
44
45    pub fn set_has_primary_connected(&mut self, has_primary_connected: bool) {
46        *self.has_primary_connected.borrow_mut() = has_primary_connected;
47    }
48
49    pub fn bind<T: SessionManagerClient>(&mut self, client: &T, channel: fasync::Channel) {
50        let keep_log_visible = self.keep_log_visible;
51        let first_session_id = self.first_session_id;
52        let next_session_id = Rc::clone(&self.next_session_id);
53        let has_primary_connected = Rc::clone(&self.has_primary_connected);
54        let client = client.clone();
55
56        fasync::Task::local(
57            async move {
58                let mut stream = SessionManagerRequestStream::from_channel(channel);
59                while let Some(request) = stream.try_next().await? {
60                    match request {
61                        SessionManagerRequest::CreateSession { session, control_handle: _ } => {
62                            let id = {
63                                let mut next_session_id = next_session_id.borrow_mut();
64                                let id = *next_session_id;
65                                *next_session_id += 1;
66                                id
67                            };
68                            let make_active = !keep_log_visible && id == first_session_id;
69                            let () = Self::create_session(session, &client, id, make_active).await;
70                        }
71                        SessionManagerRequest::HasPrimaryConnected { responder } => {
72                            responder
73                                .send(*has_primary_connected.borrow())
74                                .context("error sending response")?;
75                        }
76                    }
77                }
78                Ok(())
79            }
80            .unwrap_or_else(|e: anyhow::Error| eprintln!("{:?}", e)),
81        )
82        .detach();
83    }
84
85    async fn create_session<T: SessionManagerClient>(
86        session: ServerEnd<DeviceMarker>,
87        client: &T,
88        id: u32,
89        make_active: bool,
90    ) {
91        let client = client.clone();
92        let pty = ServerPty::new().expect("failed to create PTY");
93        let () = pty.open_client(session).await.expect("failed to connect session");
94        let read_fd = pty.try_clone_fd().expect("unable to clone PTY fd");
95        let terminal = client
96            .create_terminal(id, String::new(), make_active, pty)
97            .expect("failed to create terminal");
98        let term = terminal.clone_term();
99
100        fasync::Task::local(async move {
101            let mut evented_fd = unsafe {
102                // EventedFd::new() is unsafe because it can't guarantee the lifetime of
103                // the file descriptor passed to it exceeds the lifetime of the EventedFd.
104                // Since we're cloning the file when passing it in, the EventedFd
105                // effectively owns that file descriptor and thus controls it's lifetime.
106                fasync::net::EventedFd::new(read_fd).expect("failed to create evented_fd")
107            };
108
109            let mut read_buf = [0u8; BYTE_BUFFER_MAX_SIZE];
110            loop {
111                let result = evented_fd.read(&mut read_buf).await;
112                let read_count = result.unwrap_or_else(|e: std::io::Error| {
113                    println!("vc: failed to read bytes, dropping current message: {:?}", e);
114                    0
115                });
116                if read_count > 0 {
117                    let mut parser = term.borrow_mut();
118                    for byte in &read_buf[0..read_count] {
119                        parser.process(&[*byte]);
120                    }
121                    client.request_update(id);
122                }
123            }
124        })
125        .detach()
126    }
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132    use crate::colors::ColorScheme;
133    use fuchsia_async as fasync;
134
135    #[derive(Default, Clone)]
136    struct TestSessionManagerClient;
137
138    impl SessionManagerClient for TestSessionManagerClient {
139        fn create_terminal(
140            &self,
141            _id: u32,
142            title: String,
143            _make_active: bool,
144            pty: ServerPty,
145        ) -> Result<Terminal, Error> {
146            Ok(Terminal::new(title, ColorScheme::default(), 1024, Some(pty)))
147        }
148        fn request_update(&self, _id: u32) {}
149    }
150
151    #[fasync::run_singlethreaded(test)]
152    async fn can_create_session() -> Result<(), Error> {
153        let client = TestSessionManagerClient::default();
154        let (_, server_end) = fidl::endpoints::create_endpoints();
155        let () = SessionManager::create_session(server_end, &client, 0, false).await;
156        Ok(())
157    }
158}