summary refs log tree commit diff
path: root/host/start-vm/start-vm.rs
blob: ab856ea7d26e8f8f28e63f9becc2b4965154fb87 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
// SPDX-License-Identifier: EUPL-1.2
// SPDX-FileCopyrightText: 2022 Alyssa Ross <hi@alyssa.is>

mod ch;
mod net;

use std::env::{args, current_dir};
use std::ffi::{CString, OsString};
use std::io::{self, ErrorKind};
use std::os::unix::prelude::*;
use std::path::PathBuf;
use std::process::{exit, Command};

use net::{format_mac, net_setup, NetConfig};

fn vm_command(dir: PathBuf) -> Result<Command, String> {
    let dir = dir.into_os_string().into_vec();
    let dir = PathBuf::from(OsString::from_vec(dir));

    let vm_name = dir
        .file_name()
        .ok_or_else(|| "directory has no name".to_string())?;

    if vm_name.as_bytes().contains(&b',') {
        return Err(format!("VM name may not contain a comma: {:?}", vm_name));
    }

    let mut command = Command::new("s6-notifyoncheck");
    command.args(&["-dc", "test -S env/cloud-hypervisor.sock"]);
    command.arg("cloud-hypervisor");
    command.args(&["--api-socket", "env/cloud-hypervisor.sock"]);
    command.args(&["--cmdline", "console=ttyS0 root=/dev/vda"]);
    command.args(&["--memory", "size=128M"]);
    command.args(&["--console", "pty"]);

    let mut net_providers_dir = PathBuf::new();
    net_providers_dir.push("/ext/svc/data");
    net_providers_dir.push(vm_name);
    net_providers_dir.push("providers/net");

    match net_providers_dir.read_dir() {
        Ok(entries) => {
            for r in entries {
                let entry = r
                    .map_err(|e| format!("examining directory entry: {}", e))?
                    .file_name();

                // Safe because prov is the name of a directory entry, so
                // con't contain a null byte.
                let provider_name = unsafe { CString::from_vec_unchecked(entry.into_vec()) };

                // Safe because we pass a valid pointer and check the result.
                let NetConfig { fd, mac } = unsafe { net_setup(provider_name.as_ptr()) };
                if fd == -1 {
                    let e = io::Error::last_os_error();
                    return Err(format!("setting up networking failed: {}", e));
                }

                command
                    .arg("--net")
                    .arg(format!("fd={},mac={}", fd, format_mac(&mac)));

                // TODO: to support multiple net providers, we'll need
                // a better naming scheme for tap and bridge devices.
                break;
            }
        }
        Err(e) if e.kind() == ErrorKind::NotFound => {}
        Err(e) => return Err(format!("reading directory {:?}: {}", net_providers_dir, e)),
    }

    command.arg("--kernel").arg({
        let mut kernel = OsString::from("/ext/svc/data/");
        kernel.push(&vm_name);
        kernel.push("/vmlinux");
        kernel
    });

    command.arg("--disk").arg({
        let mut disk = OsString::from("path=/ext/svc/data/");
        disk.push(&vm_name);
        disk.push("/rootfs.ext4,readonly=on");
        disk
    });

    command.arg("--serial").arg({
        let mut serial = OsString::from("file=/run/");
        serial.push(&vm_name);
        serial.push(".log");
        serial
    });

    Ok(command)
}

fn run() -> String {
    let dir = match current_dir().map_err(|e| format!("getting current directory: {}", e)) {
        Ok(dir) => dir,
        Err(e) => return e,
    };

    match vm_command(dir) {
        Ok(mut command) => format!("failed to exec: {}", command.exec()),
        Err(e) => e,
    }
}

fn main() {
    let argv0_option = args().next();
    let argv0 = argv0_option
        .as_ref()
        .map(String::as_str)
        .unwrap_or("start-vm");
    eprintln!("{}: {}", argv0, run());
    exit(1);
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_vm_name_comma() {
        assert!(vm_command("/v,m".into()).unwrap_err().contains("comma"));
    }
}