summary refs log tree commit diff
path: root/host/start-vm/ch.rs
blob: 876a6ed4d034a68162c9fbe637135b0ee040bdcb (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>

use std::ffi::{CStr, OsStr, OsString};
use std::num::NonZeroI32;
use std::os::raw::{c_char, c_int};
use std::os::unix::prelude::*;
use std::process::{Command, Stdio};

use crate::format_mac;

// Trivially safe.
const EPERM: NonZeroI32 = unsafe { NonZeroI32::new_unchecked(1) };
const EPROTO: NonZeroI32 = unsafe { NonZeroI32::new_unchecked(71) };

fn command(vm_name: &OsStr, s: impl AsRef<OsStr>) -> Command {
    let mut api_socket_path = OsString::from("/run/service/ext-vm-");
    api_socket_path.push(vm_name);
    api_socket_path.push("/env/cloud-hypervisor.sock");

    let mut command = Command::new("ch-remote");
    command.stdin(Stdio::null());
    command.arg("--api-socket");
    command.arg(api_socket_path);
    command.arg(s);
    command
}

pub fn add_net(vm_name: &OsStr, tap: RawFd, mac: &str) -> Result<OsString, NonZeroI32> {
    let mut ch_remote = command(vm_name, "add-net")
        .arg(format!("fd={},mac={}", tap, mac))
        .stdout(Stdio::piped())
        .spawn()
        .or(Err(EPERM))?;

    let jq_out = match Command::new("jq")
        .args(["-j", ".id"])
        .stdin(ch_remote.stdout.take().unwrap())
        .stderr(Stdio::inherit())
        .output()
    {
        Ok(o) => o,
        Err(_) => {
            // Try not to leave a zombie.
            let _ = ch_remote.kill();
            let _ = ch_remote.wait();
            return Err(EPERM);
        }
    };

    if let Ok(ch_remote_status) = ch_remote.wait() {
        if ch_remote_status.success() && jq_out.status.success() {
            return Ok(OsString::from_vec(jq_out.stdout));
        }
    }

    Err(EPROTO)
}

pub fn remove_device(vm_name: &OsStr, device_id: &OsStr) -> Result<(), NonZeroI32> {
    let ch_remote = command(vm_name, "remove-device")
        .arg(device_id)
        .status()
        .or(Err(EPERM))?;

    if ch_remote.success() {
        Ok(())
    } else {
        Err(EPROTO)
    }
}

/// # Safety
///
/// - `vm_name` must point to a valid C string.
/// - `tap` must be a file descriptor describing an tap device.
/// - `mac` must be a valid pointer.
#[export_name = "ch_add_net"]
unsafe extern "C" fn add_net_c(
    vm_name: *const c_char,
    tap: RawFd,
    mac: *const [u8; 6],
    id: *mut *mut OsString,
) -> c_int {
    let vm_name = CStr::from_ptr(vm_name);
    let mac = format_mac(&*mac);

    match add_net(OsStr::from_bytes(vm_name.to_bytes()), tap, &mac) {
        Err(e) => e.get(),
        Ok(id_str) => {
            if !id.is_null() {
                let token = Box::into_raw(Box::new(id_str));
                *id = token;
            }
            0
        }
    }
}

/// # Safety
///
/// - `vm_name` must point to a valid C string.
/// - `id` must be a device ID obtained by calling `add_net_c`.  After
///   calling `remove_device_c`, the pointer is no longer valid.
#[export_name = "ch_remove_device"]
unsafe extern "C" fn remove_device_c(vm_name: *const c_char, device_id: *mut OsString) -> c_int {
    let vm_name = CStr::from_ptr(vm_name);
    let device_id = Box::from_raw(device_id);

    if let Err(e) = remove_device(OsStr::from_bytes(vm_name.to_bytes()), device_id.as_ref()) {
        e.get()
    } else {
        0
    }
}

/// # Safety
///
/// `id` must be a device ID obtained by calling `add_net_c`.  After
/// calling `device_free`, the pointer is no longer valid.
#[export_name = "ch_device_free"]
unsafe extern "C" fn device_free(id: *mut OsString) {
    if !id.is_null() {
        drop(Box::from_raw(id))
    }
}