blob: 711e07fd285d292b5b2474a00e349c590409a22d [file] [log] [blame]
// 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(())
}