summary refs log tree commit diff
path: root/9s
diff options
context:
space:
mode:
authorChirantan Ekbote <chirantan@chromium.org>2018-06-22 19:00:30 -0700
committerchrome-bot <chrome-bot@chromium.org>2018-08-08 13:45:41 -0700
commita79073ad7d244b45bca5ba0c5ddf92b04827fc24 (patch)
treef1cea7b698de43de0718a7d9116ff66b496ebb5b /9s
parentf226e28632d95c8b3c8d90fbaaef1eda8509fc74 (diff)
downloadcrosvm-a79073ad7d244b45bca5ba0c5ddf92b04827fc24.tar
crosvm-a79073ad7d244b45bca5ba0c5ddf92b04827fc24.tar.gz
crosvm-a79073ad7d244b45bca5ba0c5ddf92b04827fc24.tar.bz2
crosvm-a79073ad7d244b45bca5ba0c5ddf92b04827fc24.tar.lz
crosvm-a79073ad7d244b45bca5ba0c5ddf92b04827fc24.tar.xz
crosvm-a79073ad7d244b45bca5ba0c5ddf92b04827fc24.tar.zst
crosvm-a79073ad7d244b45bca5ba0c5ddf92b04827fc24.zip
9s: Server binary for the 9p file system
Add the 9s crate, which provides an executable that can serve the 9p
file system protocol.  It initially only supports connections over vsock
but can easily be extended to support network and unix domain socket
based connections.

BUG=chromium:703939
TEST=Run the server, have maitred connect to it over vsock, mount the
     9p file system in the guest kernel, share it with the penguin
     container, and run `bonnie++ -r 256 -s 512`
CQ-DEPEND=CL:1121550, CL:1166446

Change-Id: Ia0c72bcf29188bba4c07b6c0a2dd5a83d02339b5
Signed-off-by: Chirantan Ekbote <chirantan@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/1112870
Reviewed-by: Dylan Reid <dgreid@chromium.org>
Diffstat (limited to '9s')
-rw-r--r--9s/Cargo.toml10
-rw-r--r--9s/src/main.rs208
-rw-r--r--9s/src/vsock.rs197
3 files changed, 415 insertions, 0 deletions
diff --git a/9s/Cargo.toml b/9s/Cargo.toml
new file mode 100644
index 0000000..151dbb6
--- /dev/null
+++ b/9s/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "9s"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+
+[dependencies]
+getopts = "=0.2.17"
+libc = "=0.2.40"
+p9 = { path = "../p9" }
+sys_util = { path = "../sys_util" }
diff --git a/9s/src/main.rs b/9s/src/main.rs
new file mode 100644
index 0000000..711e07f
--- /dev/null
+++ b/9s/src/main.rs
@@ -0,0 +1,208 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/// Runs a [9P] server.
+///
+/// [9P]: http://man.cat-v.org/plan_9/5/0intro
+extern crate getopts;
+extern crate libc;
+extern crate p9;
+#[macro_use]
+extern crate sys_util;
+
+mod vsock;
+
+use std::fmt;
+use std::io::{self, BufReader, BufWriter};
+use std::net;
+use std::num::ParseIntError;
+use std::os::raw::c_uint;
+use std::result;
+use std::str::FromStr;
+use std::string;
+use std::sync::Arc;
+use std::thread;
+
+use sys_util::syslog;
+
+use vsock::*;
+
+const DEFAULT_BUFFER_SIZE: usize = 8192;
+
+// Address family identifiers.
+const VSOCK: &'static str = "vsock:";
+const UNIX: &'static str = "unix:";
+
+// Usage for this program.
+const USAGE: &'static str = "9s [options] {vsock:<port>|unix:<path>|<ip>:<port>}";
+
+enum ListenAddress {
+    Net(net::SocketAddr),
+    Unix(String),
+    Vsock(c_uint),
+}
+
+#[derive(Debug)]
+enum ParseAddressError {
+    MissingUnixPath,
+    MissingVsockPort,
+    Net(net::AddrParseError),
+    Unix(string::ParseError),
+    Vsock(ParseIntError),
+}
+
+impl fmt::Display for ParseAddressError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            &ParseAddressError::MissingUnixPath => write!(f, "missing unix path"),
+            &ParseAddressError::MissingVsockPort => write!(f, "missing vsock port number"),
+            &ParseAddressError::Net(ref e) => e.fmt(f),
+            &ParseAddressError::Unix(ref e) => write!(f, "invalid unix path: {}", e),
+            &ParseAddressError::Vsock(ref e) => write!(f, "invalid vsock port number: {}", e),
+        }
+    }
+}
+
+impl FromStr for ListenAddress {
+    type Err = ParseAddressError;
+
+    fn from_str(s: &str) -> result::Result<Self, Self::Err> {
+        if s.starts_with(VSOCK) {
+            if s.len() > VSOCK.len() {
+                Ok(ListenAddress::Vsock(s[VSOCK.len()..]
+                    .parse()
+                    .map_err(ParseAddressError::Vsock)?))
+            } else {
+                Err(ParseAddressError::MissingVsockPort)
+            }
+        } else if s.starts_with(UNIX) {
+            if s.len() > UNIX.len() {
+                Ok(ListenAddress::Unix(s[UNIX.len()..]
+                    .parse()
+                    .map_err(ParseAddressError::Unix)?))
+            } else {
+                Err(ParseAddressError::MissingUnixPath)
+            }
+        } else {
+            Ok(ListenAddress::Net(
+                s.parse().map_err(ParseAddressError::Net)?
+            ))
+        }
+    }
+}
+
+#[derive(Debug)]
+enum Error {
+    Address(ParseAddressError),
+    Argument(getopts::Fail),
+    Cid(ParseIntError),
+    IO(io::Error),
+    MissingAcceptCid,
+    Syslog(syslog::Error),
+}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            &Error::Address(ref e) => e.fmt(f),
+            &Error::Argument(ref e) => e.fmt(f),
+            &Error::Cid(ref e) => write!(f, "invalid cid value: {}", e),
+            &Error::IO(ref e) => e.fmt(f),
+            &Error::MissingAcceptCid => write!(f, "`accept_cid` is required for vsock servers"),
+            &Error::Syslog(ref e) => write!(f, "failed to initialize syslog: {:?}", e),
+        }
+    }
+}
+
+type Result<T> = result::Result<T, Error>;
+
+fn handle_client<R: io::Read, W: io::Write>(
+    root: Arc<str>,
+    mut reader: R,
+    mut writer: W,
+) -> io::Result<()> {
+    let mut server = p9::Server::new(&*root);
+
+    loop {
+        server.handle_message(&mut reader, &mut writer)?;
+    }
+}
+
+fn run_vsock_server(root: Arc<str>, port: c_uint, accept_cid: c_uint) -> io::Result<()> {
+    let listener = VsockListener::bind(port)?;
+
+    loop {
+        let (stream, peer) = listener.accept()?;
+
+        if accept_cid != peer.cid {
+            warn!("ignoring connection from {}:{}", peer.cid, peer.port);
+            continue;
+        }
+
+        info!("accepted connection from {}:{}", peer.cid, peer.port);
+        let reader = BufReader::with_capacity(DEFAULT_BUFFER_SIZE, stream.try_clone()?);
+        let writer = BufWriter::with_capacity(DEFAULT_BUFFER_SIZE, stream);
+        let server_root = root.clone();
+        thread::spawn(move || {
+            if let Err(e) = handle_client(server_root, reader, writer) {
+                error!(
+                    "error while handling client {}:{}: {}",
+                    peer.cid, peer.port, e
+                );
+            }
+        });
+    }
+}
+
+fn main() -> Result<()> {
+    let mut opts = getopts::Options::new();
+    opts.optopt(
+        "",
+        "accept_cid",
+        "only accept connections from this vsock context id",
+        "CID",
+    );
+    opts.optopt(
+        "r",
+        "root",
+        "root directory for clients (default is \"/\")",
+        "PATH",
+    );
+    opts.optflag("h", "help", "print this help menu");
+
+    let matches = opts.parse(std::env::args_os().skip(1))
+        .map_err(Error::Argument)?;
+
+    if matches.opt_present("h") || matches.free.len() == 0 {
+        print!("{}", opts.usage(USAGE));
+        return Ok(());
+    }
+
+    syslog::init().map_err(Error::Syslog)?;
+
+    let root: Arc<str> = Arc::from(matches.opt_str("r").unwrap_or_else(|| "/".into()));
+
+    // We already checked that |matches.free| has at least one item.
+    match matches.free[0]
+        .parse::<ListenAddress>()
+        .map_err(Error::Address)?
+    {
+        ListenAddress::Vsock(port) => {
+            let accept_cid = if let Some(cid) = matches.opt_str("accept_cid") {
+                cid.parse::<c_uint>().map_err(Error::Cid)
+            } else {
+                Err(Error::MissingAcceptCid)
+            }?;
+            run_vsock_server(root, port, accept_cid).map_err(Error::IO)?;
+        }
+        ListenAddress::Net(_) => {
+            error!("Network server unimplemented");
+        }
+        ListenAddress::Unix(_) => {
+            error!("Unix server unimplemented");
+        }
+    }
+
+    Ok(())
+}
diff --git a/9s/src/vsock.rs b/9s/src/vsock.rs
new file mode 100644
index 0000000..7957860
--- /dev/null
+++ b/9s/src/vsock.rs
@@ -0,0 +1,197 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/// Support for virtual sockets.
+use std::io;
+use std::mem::{self, size_of};
+use std::os::raw::{c_int, c_uchar, c_uint, c_ushort};
+use std::os::unix::io::RawFd;
+
+use libc::{self, c_void, sa_family_t, size_t, sockaddr, socklen_t};
+
+// The domain for vsock sockets.
+const AF_VSOCK: sa_family_t = 40;
+
+// Vsock equivalent of INADDR_ANY.  Indicates the context id of the current endpoint.
+const VMADDR_CID_ANY: c_uint = c_uint::max_value();
+
+// The number of bytes of padding to be added to the sockaddr_vm struct.  Taken directly
+// from linux/vm_sockets.h.
+const PADDING: usize = size_of::<sockaddr>()
+    - size_of::<sa_family_t>()
+    - size_of::<c_ushort>()
+    - (2 * size_of::<c_uint>());
+
+#[repr(C)]
+struct sockaddr_vm {
+    svm_family: sa_family_t,
+    svm_reserved1: c_ushort,
+    svm_port: c_uint,
+    svm_cid: c_uint,
+    svm_zero: [c_uchar; PADDING],
+}
+
+/// An address associated with a virtual socket.
+pub struct SocketAddr {
+    pub cid: c_uint,
+    pub port: c_uint,
+}
+
+/// A virtual stream socket.
+pub struct VsockStream {
+    fd: RawFd,
+}
+
+impl VsockStream {
+    pub fn try_clone(&self) -> io::Result<VsockStream> {
+        // Safe because this doesn't modify any memory and we check the return value.
+        let dup_fd = unsafe { libc::fcntl(self.fd, libc::F_DUPFD_CLOEXEC, 0) };
+        if dup_fd < 0 {
+            return Err(io::Error::last_os_error());
+        }
+
+        Ok(VsockStream { fd: dup_fd })
+    }
+}
+
+impl io::Read for VsockStream {
+    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+        // Safe because this will only modify the contents of |buf| and we check the return value.
+        let ret = unsafe {
+            handle_eintr_errno!(libc::read(
+                self.fd,
+                buf as *mut [u8] as *mut c_void,
+                buf.len() as size_t
+            ))
+        };
+        if ret < 0 {
+            return Err(io::Error::last_os_error());
+        }
+
+        Ok(ret as usize)
+    }
+}
+
+impl io::Write for VsockStream {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        // Safe because this doesn't modify any memory and we check the return value.
+        let ret = unsafe {
+            handle_eintr_errno!(libc::write(
+                self.fd,
+                buf as *const [u8] as *const c_void,
+                buf.len() as size_t,
+            ))
+        };
+        if ret < 0 {
+            return Err(io::Error::last_os_error());
+        }
+
+        Ok(ret as usize)
+    }
+
+    fn flush(&mut self) -> io::Result<()> {
+        // No buffered data so nothing to do.
+        Ok(())
+    }
+}
+
+impl Drop for VsockStream {
+    fn drop(&mut self) {
+        // Safe because this doesn't modify any memory and we are the only
+        // owner of the file descriptor.
+        unsafe { libc::close(self.fd) };
+    }
+}
+
+/// Represents a virtual socket server.
+pub struct VsockListener {
+    fd: RawFd,
+}
+
+impl VsockListener {
+    /// Creates a new `VsockListener` bound to the specified port on the current virtual socket
+    /// endpoint.
+    pub fn bind(port: c_uint) -> io::Result<VsockListener> {
+        // The compiler should optimize this out since these are both compile-time constants.
+        assert_eq!(size_of::<sockaddr_vm>(), size_of::<sockaddr>());
+
+        // Safe because this doesn't modify any memory and we check the return value.
+        let fd: RawFd =
+            unsafe { libc::socket(AF_VSOCK as c_int, libc::SOCK_STREAM | libc::SOCK_CLOEXEC, 0) };
+        if fd < 0 {
+            return Err(io::Error::last_os_error());
+        }
+
+        // Safe because we are zero-initializing a struct with only integer fields.
+        let mut svm: sockaddr_vm = unsafe { mem::zeroed() };
+        svm.svm_family = AF_VSOCK;
+        svm.svm_cid = VMADDR_CID_ANY;
+        svm.svm_port = port;
+
+        // Safe because this doesn't modify any memory and we check the return value.
+        let ret = unsafe {
+            libc::bind(
+                fd,
+                &svm as *const sockaddr_vm as *const sockaddr,
+                size_of::<sockaddr_vm>() as socklen_t,
+            )
+        };
+        if ret < 0 {
+            return Err(io::Error::last_os_error());
+        }
+
+        // Safe because this doesn't modify any memory and we check the return value.
+        let ret = unsafe { libc::listen(fd, 1) };
+        if ret < 0 {
+            return Err(io::Error::last_os_error());
+        }
+
+        Ok(VsockListener { fd: fd })
+    }
+
+    /// Accepts a new incoming connection on this listener.  Blocks the calling thread until a
+    /// new connection is established.  When established, returns the corresponding `VsockStream`
+    /// and the remote peer's address.
+    pub fn accept(&self) -> io::Result<(VsockStream, SocketAddr)> {
+        // Safe because we are zero-initializing a struct with only integer fields.
+        let mut svm: sockaddr_vm = unsafe { mem::zeroed() };
+
+        // Safe because this will only modify |svm| and we check the return value.
+        let mut socklen: socklen_t = size_of::<sockaddr_vm>() as socklen_t;
+        let fd = unsafe {
+            libc::accept4(
+                self.fd,
+                &mut svm as *mut sockaddr_vm as *mut sockaddr,
+                &mut socklen as *mut socklen_t,
+                libc::SOCK_CLOEXEC,
+            )
+        };
+        if fd < 0 {
+            return Err(io::Error::last_os_error());
+        }
+
+        if svm.svm_family != AF_VSOCK {
+            return Err(io::Error::new(
+                io::ErrorKind::InvalidData,
+                format!("unexpected address family: {}", svm.svm_family),
+            ));
+        }
+
+        Ok((
+            VsockStream { fd: fd },
+            SocketAddr {
+                cid: svm.svm_cid,
+                port: svm.svm_port,
+            },
+        ))
+    }
+}
+
+impl Drop for VsockListener {
+    fn drop(&mut self) {
+        // Safe because this doesn't modify any memory and we are the only
+        // owner of the file descriptor.
+        unsafe { libc::close(self.fd) };
+    }
+}