summary refs log tree commit diff
path: root/devices
diff options
context:
space:
mode:
Diffstat (limited to 'devices')
-rw-r--r--devices/src/lib.rs3
-rw-r--r--devices/src/proxy.rs43
-rw-r--r--devices/src/serial.rs104
3 files changed, 126 insertions, 24 deletions
diff --git a/devices/src/lib.rs b/devices/src/lib.rs
index 512d08b..d96ce19 100644
--- a/devices/src/lib.rs
+++ b/devices/src/lib.rs
@@ -38,7 +38,8 @@ pub use self::proxy::Error as ProxyError;
 pub use self::proxy::ProxyDevice;
 pub use self::serial::Error as SerialError;
 pub use self::serial::{
-    get_serial_tty_string, Serial, SerialParameters, SerialType, DEFAULT_SERIAL_PARAMS, SERIAL_ADDR,
+    get_serial_tty_string, Serial, SerialInput, SerialParameters, SerialType,
+    DEFAULT_SERIAL_PARAMS, SERIAL_ADDR,
 };
 pub use self::usb::host_backend::host_backend_device_provider::HostBackendDeviceProvider;
 pub use self::usb::xhci::xhci_controller::XhciController;
diff --git a/devices/src/proxy.rs b/devices/src/proxy.rs
index b97ccca..3770779 100644
--- a/devices/src/proxy.rs
+++ b/devices/src/proxy.rs
@@ -56,6 +56,7 @@ enum Command {
         data: [u8; 4],
     },
     Shutdown,
+    RunUserCommand,
 }
 
 #[derive(MsgOnSocket)]
@@ -65,7 +66,11 @@ enum CommandResult {
     ReadConfigResult(u32),
 }
 
-fn child_proc(sock: UnixSeqpacket, device: &mut dyn BusDevice) {
+fn child_proc<D: BusDevice, F: FnMut(&mut D)>(
+    sock: UnixSeqpacket,
+    device: &mut D,
+    mut user_command: F,
+) {
     let mut running = true;
     let sock = MsgSocket::<CommandResult, Command>::new(sock);
 
@@ -107,6 +112,10 @@ fn child_proc(sock: UnixSeqpacket, device: &mut dyn BusDevice) {
                 running = false;
                 sock.send(&CommandResult::Ok)
             }
+            Command::RunUserCommand => {
+                user_command(device);
+                sock.send(&CommandResult::Ok)
+            }
         };
         if let Err(e) = res {
             error!("child device process failed send: {}", e);
@@ -132,11 +141,34 @@ impl ProxyDevice {
     ///
     /// # Arguments
     /// * `device` - The device to isolate to another process.
-    /// * `keep_fds` - File descriptors that will be kept open in the child
+    /// * `jail` - The jail to use for isolating the given device.
+    /// * `keep_fds` - File descriptors that will be kept open in the child.
     pub fn new<D: BusDevice>(
+        device: D,
+        jail: &Minijail,
+        keep_fds: Vec<RawFd>,
+    ) -> Result<ProxyDevice> {
+        Self::new_with_user_command(device, jail, keep_fds, |_| {})
+    }
+
+    /// Similar to `ProxyDevice::new`, but adds an additional custom command to be run in the forked
+    /// process when `run_user_command` is called.
+    ///
+    /// Note that the custom command closure is run in the main thread of the child process, which
+    /// also services `BusDevice` requests. Therefore, do not run blocking calls in the closure
+    /// without a timeout, or you will block any VCPU which touches this device, and every other
+    /// thread which needs to lock this device's mutex.
+    ///
+    /// # Arguments
+    /// * `device` - The device to isolate to another process.
+    /// * `jail` - The jail to use for isolating the given device.
+    /// * `keep_fds` - File descriptors that will be kept open in the child.
+    /// * `user_command` - Closure to be run in the forked process.
+    pub fn new_with_user_command<D: BusDevice, F: FnMut(&mut D)>(
         mut device: D,
         jail: &Minijail,
         mut keep_fds: Vec<RawFd>,
+        user_command: F,
     ) -> Result<ProxyDevice> {
         let debug_label = device.debug_label();
         let (child_sock, parent_sock) = UnixSeqpacket::pair().map_err(Error::Io)?;
@@ -147,7 +179,7 @@ impl ProxyDevice {
             match jail.fork(Some(&keep_fds)).map_err(Error::ForkingJail)? {
                 0 => {
                     device.on_sandboxed();
-                    child_proc(child_sock, &mut device);
+                    child_proc(child_sock, &mut device, user_command);
 
                     // We're explicitly not using std::process::exit here to avoid the cleanup of
                     // stdout/stderr globals. This can cause cascading panics and SIGILL if a worker
@@ -180,6 +212,11 @@ impl ProxyDevice {
         self.pid
     }
 
+    /// Runs the callback given in `new_with_custom_command` in the child device process.
+    pub fn run_user_command(&self) {
+        self.sync_send(Command::RunUserCommand);
+    }
+
     fn sync_send(&self, cmd: Command) -> Option<CommandResult> {
         let res = self.sock.send(&cmd);
         if let Err(e) = res {
diff --git a/devices/src/serial.rs b/devices/src/serial.rs
index 5ecefd7..9485497 100644
--- a/devices/src/serial.rs
+++ b/devices/src/serial.rs
@@ -5,13 +5,17 @@
 use std::collections::VecDeque;
 use std::fmt::{self, Display};
 use std::fs::File;
-use std::io::{self, stdout};
+use std::io::{self, stdout, Write};
+use std::os::unix::io::{AsRawFd, RawFd};
 use std::path::PathBuf;
 use std::str::FromStr;
+use std::sync::Arc;
 
+use sync::Mutex;
 use sys_util::{error, syslog, EventFd, Result};
 
 use crate::BusDevice;
+use crate::ProxyDevice;
 
 const LOOP_SIZE: usize = 0x40;
 
@@ -135,28 +139,38 @@ impl SerialParameters {
     ///
     /// # Arguments
     /// * `evt_fd` - eventfd used for interrupt events
-    pub fn create_serial_device(&self, evt_fd: &EventFd) -> std::result::Result<Serial, Error> {
+    /// * `keep_fds` - Vector of FDs required by this device if it were sandboxed in a child
+    ///                process. `evt_fd` will always be added to this vector by this function.
+    pub fn create_serial_device(
+        &self,
+        evt_fd: &EventFd,
+        keep_fds: &mut Vec<RawFd>,
+    ) -> std::result::Result<Serial, Error> {
+        let evt_fd = evt_fd.try_clone().map_err(Error::CloneEventFd)?;
+        keep_fds.push(evt_fd.as_raw_fd());
         match self.type_ {
-            SerialType::Stdout => Ok(Serial::new_out(
-                evt_fd.try_clone().map_err(Error::CloneEventFd)?,
-                Box::new(stdout()),
-            )),
-            SerialType::Sink => Ok(Serial::new_sink(
-                evt_fd.try_clone().map_err(Error::CloneEventFd)?,
-            )),
-            SerialType::Syslog => Ok(Serial::new_out(
-                evt_fd.try_clone().map_err(Error::CloneEventFd)?,
-                Box::new(syslog::Syslogger::new(
-                    syslog::Priority::Info,
-                    syslog::Facility::Daemon,
-                )),
-            )),
+            SerialType::Stdout => {
+                keep_fds.push(stdout().as_raw_fd());
+                Ok(Serial::new_out(evt_fd, Box::new(stdout())))
+            }
+            SerialType::Sink => Ok(Serial::new_sink(evt_fd)),
+            SerialType::Syslog => {
+                syslog::push_fds(keep_fds);
+                Ok(Serial::new_out(
+                    evt_fd,
+                    Box::new(syslog::Syslogger::new(
+                        syslog::Priority::Info,
+                        syslog::Facility::Daemon,
+                    )),
+                ))
+            }
             SerialType::File => match &self.path {
                 None => Err(Error::PathRequired),
-                Some(path) => Ok(Serial::new_out(
-                    evt_fd.try_clone().map_err(Error::CloneEventFd)?,
-                    Box::new(File::create(path.as_path()).map_err(Error::FileError)?),
-                )),
+                Some(path) => {
+                    let file = File::create(path.as_path()).map_err(Error::FileError)?;
+                    keep_fds.push(file.as_raw_fd());
+                    Ok(Serial::new_out(evt_fd, Box::new(file)))
+                }
             },
             SerialType::UnixSocket => Err(Error::Unimplemented(SerialType::UnixSocket)),
         }
@@ -213,6 +227,50 @@ pub fn get_serial_tty_string(stdio_serial_num: u8) -> String {
     }
 }
 
+/// A type for queueing input bytes to a serial device that abstracts if the device is local or part
+/// of a sandboxed device process.
+pub enum SerialInput {
+    #[doc(hidden)]
+    Local(Arc<Mutex<Serial>>),
+    #[doc(hidden)]
+    Remote {
+        input: File,
+        proxy: Arc<Mutex<ProxyDevice>>,
+    },
+}
+
+impl SerialInput {
+    /// Creates a `SerialInput` that trivially forwards queued bytes to the device in the local
+    /// process.
+    pub fn new_local(serial: Arc<Mutex<Serial>>) -> SerialInput {
+        SerialInput::Local(serial)
+    }
+
+    /// Creates a `SerialInput` that will forward bytes via `input` and will wake up the child
+    /// device process which contains the serial device so that it may process the other end of the
+    /// `input` pipe. This will use the `ProxyDevice::run_user_command` method, so the user command
+    /// closure (registered with `ProxyDevice::new_with_user_command`) should own the other side of
+    /// `input` and read from the pipe whenever the closure is run.
+    pub fn new_remote(input: File, proxy: Arc<Mutex<ProxyDevice>>) -> SerialInput {
+        SerialInput::Remote { input, proxy }
+    }
+
+    /// Just like `Serial::queue_input_bytes`, but abstracted over local and sandboxed serial
+    /// devices. In the case that the serial device is sandboxed, this method will also trigger the
+    /// user command in the `ProxyDevice`'s child process.
+    pub fn queue_input_bytes(&self, bytes: &[u8]) -> Result<()> {
+        match self {
+            SerialInput::Local(device) => device.lock().queue_input_bytes(bytes),
+            SerialInput::Remote { input, proxy } => {
+                let mut input = input;
+                input.write_all(bytes)?;
+                proxy.lock().run_user_command();
+                Ok(())
+            }
+        }
+    }
+}
+
 /// Emulates serial COM ports commonly seen on x86 I/O ports 0x3f8/0x2f8/0x3e8/0x2e8.
 ///
 /// This can optionally write the guest's output to a Write trait object. To send input to the
@@ -268,6 +326,12 @@ impl Serial {
         Ok(())
     }
 
+    /// Gets the interrupt eventfd used to interrupt the driver when it needs to respond to this
+    /// device.
+    pub fn interrupt_eventfd(&self) -> &EventFd {
+        &self.interrupt_evt
+    }
+
     fn is_dlab_set(&self) -> bool {
         (self.line_control & 0x80) != 0
     }