diff options
author | Alyssa Ross <hi@alyssa.is> | 2020-10-23 16:11:37 +0000 |
---|---|---|
committer | Alyssa Ross <hi@alyssa.is> | 2020-10-23 16:11:37 +0000 |
commit | 227fc065bd9eb0750a7f0f92321d505ddc113a2e (patch) | |
tree | 80f4c6ede501bf845b7a69810c5fa28ef03976a1 | |
parent | 1def890e17c9a416a5728e4c13e60b79c01bbdd2 (diff) | |
download | spectrum-run-227fc065bd9eb0750a7f0f92321d505ddc113a2e.tar spectrum-run-227fc065bd9eb0750a7f0f92321d505ddc113a2e.tar.gz spectrum-run-227fc065bd9eb0750a7f0f92321d505ddc113a2e.tar.bz2 spectrum-run-227fc065bd9eb0750a7f0f92321d505ddc113a2e.tar.lz spectrum-run-227fc065bd9eb0750a7f0f92321d505ddc113a2e.tar.xz spectrum-run-227fc065bd9eb0750a7f0f92321d505ddc113a2e.tar.zst spectrum-run-227fc065bd9eb0750a7f0f92321d505ddc113a2e.zip |
-rw-r--r-- | getopt/lib.rs | 2 | ||||
-rw-r--r-- | getopt_sys/lib.rs | 2 | ||||
-rw-r--r-- | libmnl_sys/lib.rs | 2 | ||||
-rw-r--r-- | linux_sys/build.rs | 7 | ||||
-rw-r--r-- | linux_sys/lib.rs | 51 | ||||
-rw-r--r-- | linux_sys/wrapper.h | 3 | ||||
-rw-r--r-- | shell.nix | 14 | ||||
-rw-r--r-- | src/ip/v4/cidr.rs | 2 | ||||
-rw-r--r-- | src/mac_address.rs | 2 | ||||
-rw-r--r-- | src/main.rs | 111 | ||||
-rw-r--r-- | src/usb.rs | 5 | ||||
-rw-r--r-- | src/vm_config.rs | 2 | ||||
-rw-r--r-- | src/vsock.rs | 206 |
13 files changed, 365 insertions, 44 deletions
diff --git a/getopt/lib.rs b/getopt/lib.rs index 2ffe57d..09ec0a3 100644 --- a/getopt/lib.rs +++ b/getopt/lib.rs @@ -11,7 +11,7 @@ use std::os::raw::{c_char, c_int}; use std::os::unix::ffi::{OsStrExt, OsStringExt}; use std::ptr::null_mut; -use getopt_sys::{optind, optarg, getopt_long, option}; +use getopt_sys::{getopt_long, optarg, optind, option}; fn c_to_os_string(s: CString) -> OsString { OsString::from_vec(s.into_bytes()) diff --git a/getopt_sys/lib.rs b/getopt_sys/lib.rs index 7393693..a820df6 100644 --- a/getopt_sys/lib.rs +++ b/getopt_sys/lib.rs @@ -38,6 +38,6 @@ extern "C" { argv: *mut *const c_char, optstring: *const c_char, longopts: *const option, - longindex: *mut c_int, + longindex: *mut c_int, ); } diff --git a/libmnl_sys/lib.rs b/libmnl_sys/lib.rs index b80da54..ddfb761 100644 --- a/libmnl_sys/lib.rs +++ b/libmnl_sys/lib.rs @@ -13,8 +13,8 @@ mod generated { pub use generated::*; -use std::os::raw::c_long; use std::cmp::min; +use std::os::raw::c_long; // MNL_SOCKET_BUFFER_SIZE is a preprocessor macro that calls sysconf, // and so can't be translated into a Rust constant. diff --git a/linux_sys/build.rs b/linux_sys/build.rs index 76af123..d5ae44c 100644 --- a/linux_sys/build.rs +++ b/linux_sys/build.rs @@ -1,7 +1,7 @@ extern crate bindgen; -use std::path::PathBuf; use std::env; +use std::path::PathBuf; fn main() { println!("cargo:rerun-if-changed=wrapper.h"); @@ -9,6 +9,11 @@ fn main() { let bindings = bindgen::Builder::default() .header("wrapper.h") .parse_callbacks(Box::new(bindgen::CargoCallbacks)) + // These functions generate bindings that use u128, which are not valid for + // FFI. We don't need them anyway, so just disable them to prevent the warnings. + // See <https://github.com/rust-lang/rust-bindgen/issues/1560>. + .blacklist_function("q[efg]cvt(_r)?") + .blacklist_function("strtold") .prepend_enum_name(false) .generate() .expect("Unable to generate bindings"); diff --git a/linux_sys/lib.rs b/linux_sys/lib.rs index 7f7fa1a..ed0e390 100644 --- a/linux_sys/lib.rs +++ b/linux_sys/lib.rs @@ -14,33 +14,40 @@ mod generated { pub use generated::*; -macro_rules! _IOC { - ($dir: expr, $type_: expr, $nr: expr, $size: expr) => { - (($dir as u32) << _IOC_DIRSHIFT) - | (($type_ as u32) << _IOC_TYPESHIFT) - | (($nr as u32) << _IOC_NRSHIFT) - | (($size as u32) << _IOC_SIZESHIFT) - }; +// Function-like macros bindgen can't bind to. + +pub const fn _IOC_TYPECHECK<size>() -> usize { + std::mem::size_of::<size>() +} + +pub const fn _IOC(dir: u32, type_: u8, nr: u32, size: usize) -> u32 { + (dir << _IOC_DIRSHIFT) + | ((type_ as u32) << _IOC_TYPESHIFT) + | (nr << _IOC_NRSHIFT) + | ((size as u32) << _IOC_SIZESHIFT) +} + +pub const fn _IOW<size>(type_: u8, nr: u32) -> u32 { + _IOC(_IOC_WRITE, type_, nr, _IOC_TYPECHECK::<size>()) +} + +pub const fn _IOR<size>(type_: u8, nr: u32) -> u32 { + _IOC(_IOC_READ, type_, nr, _IOC_TYPECHECK::<size>()) } -macro_rules! _IOC_TYPECHECK { - ($t: ty) => { - ::std::mem::size_of::<$t>() - }; +pub const fn WEXITSTATUS(status: i32) -> i8 { + ((status & 0xff00) >> 8) as _ } -macro_rules! _IOW { - ($type_: expr, $nr: expr, $size: ty) => { - _IOC!(_IOC_WRITE, $type_, $nr, _IOC_TYPECHECK!($size)) - }; +pub const fn WTERMSIG(status: i32) -> i8 { + (status & 0x7f) as _ } -macro_rules! _IOR { - ($type_: expr, $nr: expr, $size: ty) => { - _IOC!(_IOC_READ, $type_, $nr, _IOC_TYPECHECK!($size)) - }; +pub const fn WIFEXITED(status: i32) -> bool { + WTERMSIG(status) == 0 } -// Extras missed by bindgen: -pub const TUNSETIFF: u32 = _IOW!(b'T', 202, std::os::raw::c_int); -pub const TUNGETIFF: u32 = _IOR!(b'T', 210, std::os::raw::c_uint); +// Extras values bindgen can't translate. + +pub const TUNSETIFF: u32 = _IOW::<std::os::raw::c_int>(b'T', 202); +pub const TUNGETIFF: u32 = _IOR::<std::os::raw::c_uint>(b'T', 210); diff --git a/linux_sys/wrapper.h b/linux_sys/wrapper.h index 99ff6a9..447137f 100644 --- a/linux_sys/wrapper.h +++ b/linux_sys/wrapper.h @@ -1,7 +1,9 @@ #include <arpa/inet.h> #include <fcntl.h> #include <net/if.h> +#include <stdlib.h> #include <sys/ioctl.h> +#include <sys/wait.h> #include <sysexits.h> #include <time.h> #include <unistd.h> @@ -9,3 +11,4 @@ #include <linux/if.h> #include <linux/if_tun.h> #include <linux/rtnetlink.h> +#include <linux/vm_sockets.h> diff --git a/shell.nix b/shell.nix index 5969246..1327441 100644 --- a/shell.nix +++ b/shell.nix @@ -1,7 +1,13 @@ { pkgs ? import <nixpkgs> {} } @ args: with pkgs; -(import /home/src/nixlib/shells/rust.nix args).overrideAttrs ({ buildInputs ? [], ... }: { - name = "crosvm-shell"; - buildInputs = buildInputs ++ [ linuxHeaders libmnl ]; -}) +mkShell { + buildInputs = [ cargo libmnl clang llvm rustfmt ]; + + LIBCLANG_PATH = "${llvmPackages.libclang}/lib"; + } + +# (import /home/src/nixlib/shells/rust.nix args).overrideAttrs ({ buildInputs ? [], ... }: { +# name = "crosvm-shell"; +# buildInputs = buildInputs ++ [ linuxHeaders libmnl ]; +# }) diff --git a/src/ip/v4/cidr.rs b/src/ip/v4/cidr.rs index 83ad45e..6e3aecd 100644 --- a/src/ip/v4/cidr.rs +++ b/src/ip/v4/cidr.rs @@ -78,7 +78,7 @@ impl CidrBlock { let prefix_len = prefix_str.parse().map_err(|_| prefix_err(prefix_bytes))?; if prefix_len > 32 { - return Err(prefix_err(prefix_bytes)) + return Err(prefix_err(prefix_bytes)); } Ok(Self { addr, prefix_len }) diff --git a/src/mac_address.rs b/src/mac_address.rs index f35939a..0c923cb 100644 --- a/src/mac_address.rs +++ b/src/mac_address.rs @@ -1,4 +1,4 @@ -use std::fmt::{self, Display, Formatter, Debug}; +use std::fmt::{self, Debug, Display, Formatter}; #[derive(Copy, Clone, Eq, PartialEq)] pub struct MacAddress([u8; 6]); diff --git a/src/main.rs b/src/main.rs index 5da628d..dd8a76a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,12 +5,14 @@ mod tap; mod usb; mod vm_config; mod vm_name; +mod vsock; use std::env::args_os; use std::ffi::{OsStr, OsString}; use std::fmt::{self, Display, Formatter}; use std::fs::File; -use std::io::{self, stderr, Read, Write}; +use std::num::NonZeroI32; +use std::io::{self, stderr, stdin, ErrorKind, Read, Write}; use std::mem::forget; use std::os::unix::prelude::*; use std::path::{Path, PathBuf}; @@ -18,7 +20,10 @@ use std::process::{exit, Command}; use getopt::{GetoptArgs, GetoptLong, LongOptDef, OptDef, ShortOptDef}; -use linux_sys::{EX_OSERR, EX_UNAVAILABLE, EX_USAGE}; +use linux_sys::{ + fcntl, waitpid, EXIT_SUCCESS, EX_OSERR, EX_UNAVAILABLE, EX_USAGE, F_GETFD, WEXITED, + WEXITSTATUS, WIFEXITED, +}; use err::{eprint_argv0, OrExit}; use mac_address::MacAddress; @@ -30,16 +35,65 @@ use vm_name::VmName; #[derive(Clone)] enum Opt { Live(OsString), + NotifyFd(OsString), Usb(OsString), } -const OPT_DEFS: [OptDef<Opt>; 4] = [ +const OPT_DEFS: [OptDef<Opt>; 6] = [ OptDef::Short(ShortOptDef::RequiredArgument('l', &Opt::Live)), OptDef::Long(LongOptDef::RequiredArgument("runtime-dir", &Opt::Live)), + OptDef::Short(ShortOptDef::RequiredArgument('d', &Opt::NotifyFd)), + OptDef::Long(LongOptDef::RequiredArgument("notify-fd", &Opt::NotifyFd)), OptDef::Short(ShortOptDef::RequiredArgument('u', &Opt::Usb)), OptDef::Long(LongOptDef::RequiredArgument("usb", &Opt::Usb)), ]; +enum ForkResult { + Child, + Parent(NonZeroI32), +} + +unsafe fn fork() -> io::Result<ForkResult> { + match linux_sys::fork() { + -1 => Err(io::Error::last_os_error()), + 0 => Ok(ForkResult::Child), + pid => Ok(ForkResult::Parent(NonZeroI32::new_unchecked(pid))) + } +} + +enum DoubleForkResult { + Child, + Parent, +} + +unsafe fn double_fork() -> io::Result<DoubleForkResult> { + match fork()? { + ForkResult::Child => match fork() { + Err(e) => exit(e.raw_os_error().unwrap()), + Ok(ForkResult::Child) => Ok(DoubleForkResult::Child), + Ok(ForkResult::Parent(_)) => exit(EXIT_SUCCESS as _), + }, + ForkResult::Parent(pid) => { + let mut wstatus = 0; + if waitpid(pid.get(), &mut wstatus, WEXITED as _) == -1 { + return Err(io::Error::last_os_error()); + } + + if !WIFEXITED(wstatus) { + return Err(io::Error::new( + ErrorKind::Interrupted, + "forked process died from signal", + )); + } + + match WEXITSTATUS(wstatus) { + 0 => Ok(DoubleForkResult::Parent), + errno => Err(io::Error::from_raw_os_error(errno as _)), + } + } + } +} + fn mac_for_ip(addr: ip::v4::Address) -> MacAddress { let bytes = addr.bytes(); // OR with 1 because the guest's address will be the second address in the /31, and @@ -64,12 +118,22 @@ impl Display for SubprocessFailed { impl std::error::Error for SubprocessFailed {} -fn start_service(runtime_dir: &Path, name: &OsStr) -> Result<(), Box<dyn std::error::Error>> { - let s6_rc_dir = runtime_dir.join("s6-rc"); +fn s6_rc_dir(runtime_dir: &Path) -> PathBuf { + let mut dir = runtime_dir.join("s6-rc"); + dir.push("live"); + dir +} +fn service_dir(runtime_dir: &Path) -> PathBuf { + let mut dir = s6_rc_dir(runtime_dir); + dir.push("servicedirs"); + dir +} + +fn start_service(runtime_dir: &Path, name: &OsStr) -> Result<(), Box<dyn std::error::Error>> { let status = Command::new("s6-rc") .arg("-l") - .arg(s6_rc_dir) + .arg(s6_rc_dir(runtime_dir)) .arg("-u") .arg("change") .arg(name) @@ -92,7 +156,8 @@ fn ip_for_vm( runtime_dir: &Path, name: &VmName, ) -> Result<ip::v4::Address, Box<dyn std::error::Error>> { - let mut path = runtime_dir.join(&vm_service_name(name)); + let mut path = service_dir(runtime_dir); + path.push(&vm_service_name(name)); path.push("data"); path.push("hostip"); @@ -119,8 +184,9 @@ pub fn main() { // Our option definitions are known not to contain null bytes. let mut gl = GetoptLong::new(OPT_DEFS.to_vec(), args).unwrap(); - let mut usb_resources = vec![]; + let mut notify_fd: Option<File> = None; let mut runtime_dir: Option<PathBuf> = None; + let mut usb_resources = vec![]; while let Some(item) = unsafe { gl.next() }.unwrap_or_exit(EX_USAGE as _) { match item { @@ -129,6 +195,25 @@ pub fn main() { UsbResource::parse(s).expect_or_exit(EX_USAGE as _, "parsing USB resource"); usb_resources.push(resource); } + Opt::NotifyFd(fd) => { + let fd: RawFd = fd + .into_string() + .map_err(|x| x.to_string_lossy().into_owned()) + .expect_or_exit(EX_USAGE as _, "parsing notify fd") + .parse() + .expect_or_exit(EX_USAGE as _, "parsing notify fd"); + + // Check the fd is valid. + // Safe because the fd was given to us by the user. + if unsafe { fcntl(fd, F_GETFD as _) } == -1 { + eprint_argv0(); + eprint!("notify fd: {}", io::Error::last_os_error()); + exit(EX_USAGE as _); + } + + // Safe because fd was given to us by the user, and we're taking ownership of it. + notify_fd = Some(unsafe { File::from_raw_fd(fd) }); + } Opt::Live(dir) => { runtime_dir = Some(dir.into()); } @@ -169,7 +254,7 @@ pub fn main() { dbg!(&usb_resources); - let mut args = vec![OsString::from("crosvm"), "run".into()]; + let mut args = vec![OsString::from("run")]; let config = VmConfig::new(path); @@ -190,8 +275,10 @@ pub fn main() { forget(tap); } + args.push("-p--".into()); + if !usb_resources.is_empty() { - let mut param = OsString::from("spectrum.usb="); + let mut param = OsString::from("usb="); for (i, res) in usb_resources.into_iter().enumerate() { start_service(&runtime_dir, &vm_service_name(&res.vm)) @@ -218,4 +305,8 @@ pub fn main() { args.push(config.kernel_path().into()); println!("{:?}", args); + + dbg!(stdin().read_line(&mut String::new())); + + dbg!(Command::new("crosvm").args(args).exec()); } diff --git a/src/usb.rs b/src/usb.rs index 35293e1..91d5fa4 100644 --- a/src/usb.rs +++ b/src/usb.rs @@ -76,7 +76,10 @@ mod tests { #[test] fn resource_parse_missing_vm() { let result = UsbResource::parse(":b".into()); - assert!(matches!(result, Err(UsbResourceParseError::Vm(vm_name::Error::Empty)))); + assert!(matches!( + result, + Err(UsbResourceParseError::Vm(vm_name::Error::Empty)) + )); } #[test] diff --git a/src/vm_config.rs b/src/vm_config.rs index 339e880..8f1333c 100644 --- a/src/vm_config.rs +++ b/src/vm_config.rs @@ -79,6 +79,6 @@ impl<'a> VmConfig<'a> { } pub fn kernel_path(&self) -> PathBuf { - self.path.join("bzImage") + self.path.join("kernel") } } diff --git a/src/vsock.rs b/src/vsock.rs new file mode 100644 index 0000000..d39675c --- /dev/null +++ b/src/vsock.rs @@ -0,0 +1,206 @@ +use std::io; +use std::io::prelude::*; +use std::mem::{size_of, zeroed}; +use std::os::unix::prelude::*; + +use linux_sys::{ + accept, bind, close, connect, listen, read, sockaddr, sockaddr_vm, socket, socklen_t, write, + AF_VSOCK, SOCK_STREAM, +}; + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct VsockAddr { + pub cid: u32, + pub port: u32, +} + +impl From<VsockAddr> for sockaddr_vm { + fn from(addr: VsockAddr) -> Self { + // Safe because a zeroed sockaddr_vm is valid. + let mut sa: sockaddr_vm = unsafe { zeroed() }; + sa.svm_family = AF_VSOCK as _; + sa.svm_port = addr.port; + sa.svm_cid = addr.cid; + sa + } +} + +impl From<sockaddr_vm> for VsockAddr { + fn from(addr: sockaddr_vm) -> Self { + Self { + cid: addr.svm_cid, + port: addr.svm_port, + } + } +} + +#[derive(Debug)] +pub struct VsockStream(RawFd); + +impl VsockStream { + fn new() -> io::Result<Self> { + // Safe because arguments are static and we check the return value. + let fd = unsafe { socket(AF_VSOCK as _, SOCK_STREAM as _, 0) }; + if fd == -1 { + return Err(io::Error::last_os_error()); + } + + Ok(Self(fd)) + } + + pub fn connect(addr: VsockAddr) -> io::Result<Self> { + let sock = Self::new()?; + + // Safe because fd is valid and owned by us, the address is owned by us, and we + // pass the correct length for the address. + if unsafe { + connect( + sock.as_raw_fd(), + &sockaddr_vm::from(addr) as *const _ as *const sockaddr, + size_of::<sockaddr_vm>() as _, + ) + } == -1 + { + return Err(io::Error::last_os_error()); + } + + return Ok(sock); + } +} + +impl Read for VsockStream { + fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { + // Safe because fd is valid and owned by us, buf is borrowed by us, and we pass the + // correct length for buf. + let len = unsafe { read(self.0, buf as *mut [u8] as *mut _, buf.len() as _) }; + if len == -1 { + return Err(io::Error::last_os_error()); + } + + Ok(len as _) + } +} + +impl Write for VsockStream { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + // Safe because fd is valid and owned by us, buf is borrowed by us, and we pass the + // correct length for buf. + let len = unsafe { write(self.0, buf as *const [u8] as *const _, buf.len() as _) }; + if len == -1 { + return Err(io::Error::last_os_error()); + } + + Ok(len as _) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl AsRawFd for VsockStream { + fn as_raw_fd(&self) -> RawFd { + self.0 + } +} + +impl FromRawFd for VsockStream { + unsafe fn from_raw_fd(fd: RawFd) -> Self { + Self(fd) + } +} + +impl IntoRawFd for VsockStream { + fn into_raw_fd(self) -> RawFd { + self.0 + } +} + +impl Drop for VsockStream { + fn drop(&mut self) { + // Safe because we pass a valid FD, owned by us. + let _ = unsafe { close(self.0) }; + } +} + +#[derive(Debug)] +pub struct VsockListener(RawFd); + +impl VsockListener { + fn new() -> io::Result<Self> { + // Safe because arguments are static and we check the return value. + let fd = unsafe { socket(AF_VSOCK as _, SOCK_STREAM as _, 0) }; + if fd == -1 { + return Err(io::Error::last_os_error()); + } + + Ok(Self(fd)) + } + + pub fn bind(addr: VsockAddr) -> io::Result<Self> { + let sock = VsockListener::new()?; + + let sa = sockaddr_vm::from(addr); + + // Safe because fd is valid and owned by us, addr is owned by us, and we pass the + // correct size. + if unsafe { + bind( + sock.as_raw_fd(), + &sa as *const _ as *const sockaddr, + size_of::<sockaddr_vm>() as _, + ) + } == -1 + { + return Err(io::Error::last_os_error()); + } + + // Safe because fd is valid and owned by us. + if unsafe { listen(sock.as_raw_fd(), 128) } == -1 { + return Err(io::Error::last_os_error()); + } + + return Ok(sock); + } + + pub fn accept(&self) -> io::Result<(VsockStream, VsockAddr)> { + // Safe because this is an out parameter. + let mut addr: sockaddr_vm = unsafe { zeroed() }; + let mut addr_len = size_of::<sockaddr_vm>() as socklen_t; + + // Safe because fd is valid and owned by us, addr is valid and owned by us, and we + // pass the correct length, and we check the return value. + let fd = unsafe { accept(self.0, &mut addr as *mut _ as *mut sockaddr, &mut addr_len) }; + if fd == -1 { + return Err(io::Error::last_os_error()); + } + + // Safe because fd is valid and owned by us, and we know it's a vsock stream. + Ok((unsafe { VsockStream::from_raw_fd(fd) }, addr.into())) + } +} + +impl AsRawFd for VsockListener { + fn as_raw_fd(&self) -> RawFd { + self.0 + } +} + +impl FromRawFd for VsockListener { + unsafe fn from_raw_fd(fd: RawFd) -> Self { + Self(fd) + } +} + +impl IntoRawFd for VsockListener { + fn into_raw_fd(self) -> RawFd { + self.0 + } +} + +impl Drop for VsockListener { + fn drop(&mut self) { + // Safe because we pass a valid FD, owned by us. + let _ = unsafe { close(self.0) }; + } +} |