summary refs log tree commit diff
path: root/tests/boot.rs
blob: 9c2da3c3bba2235d171e6a059b6b518777dfde29 (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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
// Copyright 2019 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.

use std::env;
use std::fs;
use std::io::{stdout, Write};
use std::mem;
use std::os::unix::fs::symlink;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::sync::Once;

use libc::{cpu_set_t, sched_getaffinity};

use arch::{set_default_serial_parameters, SerialHardware, SerialParameters, SerialType};
use crosvm::{linux, Config, Executable};
use sys_util::syslog;

const CHROOT_KERNEL_PATH: &str = "/mnt/host/source/src/third_party/kernel/v4.19/";
const CONTAINER_VM_DEFCONFIG: &str = "arch/x86/configs/chromiumos-container-vm-x86_64_defconfig";
const KERNEL_REPO: &str = "https://chromium.googlesource.com/chromiumos/third_party/kernel";
const KERNEL_REPO_BRANCH: &str = "chromeos-4.19";
// TODO(zachr): this URL is a placeholder until the automated builder is running and we've settled
// on a location.
const KERNEL_PREBUILT: &str = "http://storage.googleapis.com/crosvm-testing";

/// Returns the number of CPUs that this process and its children can use by querying our process's
/// CPU affinity.
fn get_cpu_count() -> usize {
    unsafe {
        let mut set: cpu_set_t = mem::zeroed();
        let ret = sched_getaffinity(0, mem::size_of::<cpu_set_t>(), &mut set);
        if ret != 0 {
            // A good guess.
            4
        } else {
            // The cpu_set_t is normally counted using the CPU_COUNT macro, but we don't have that
            // in Rust. Because it's always a bitset, we will treat it like one here.
            let set: [u8; mem::size_of::<cpu_set_t>()] = mem::transmute(set);
            set.iter().map(|b| b.count_ones() as usize).sum()
        }
    }
}

/// Clones a chrome os kernel into the given path.
fn clone_kernel_source(dir: &Path) {
    let status = Command::new("git")
        .args(&[
            "clone",
            "--depth",
            "1",
            "--branch",
            KERNEL_REPO_BRANCH,
            KERNEL_REPO,
        ])
        .arg(dir)
        .status()
        .expect("failed to execute git");
    if !status.success() {
        panic!("failed to clone kernel source: {}", status);
    }
}

// Kernel binary algorithm.
// 1: If CROSVM_CARGO_TEST_KERNEL_BINARY is in the env:
//        If CROSVM_CARGO_TEST_KERNEL_BINARY is empty, skip step 3.
//        If CROSVM_CARGO_TEST_KERNEL_BINARY does not exist, panic.
// 2: If "bzImage" exists in the target directory use that.
// 3: Download "bzImage" from the KERNEL_PREBUILT url and use that.
//    If the download does not work, go to the kernel source algorithm.
//
// Kernel source algorithm
// 1: If CROSVM_CARGO_TEST_KERNEL_SOURCE is in the env, use that.
//    If CROSVM_CARGO_TEST_KERNEL_SOURCE does not exist, panic
// 2: If CHROOT_KERNEL_PATH exists, use that.
// 3: Checkout and use the chromeos kernel.
//
// Kernel config algorithm
// 1: If the .config already exists in the kernel source, use that.
// 2: If the CONTAINER_VM_DEFCONFIG exists in the kernel source, use that.
// 3: Use `make defconfig`.
fn prepare_kernel_once(dir: &Path) {
    let kernel_binary = dir.join("bzImage");

    let mut download_prebuilt = true;
    if let Ok(env_kernel_binary) = env::var("CROSVM_CARGO_TEST_KERNEL_BINARY") {
        if env_kernel_binary.is_empty() {
            download_prebuilt = false;
        } else {
            println!(
                "using kernel binary from enviroment `{}`",
                env_kernel_binary
            );
            let env_kernel_binary = PathBuf::from(env_kernel_binary);
            if env_kernel_binary.exists() {
                symlink(env_kernel_binary, &kernel_binary)
                    .expect("failed to create symlink for kernel binary");
                return;
            } else {
                panic!(
                    "expected kernel binary at `{}`",
                    env_kernel_binary.display()
                )
            }
        }
    }

    println!("looking for kernel binary at `{}`", kernel_binary.display());
    if kernel_binary.exists() {
        println!("using kernel binary at `{}`", kernel_binary.display());
        return;
    }

    if download_prebuilt {
        // Resolve the base URL into a specific path for this architecture.
        let kernel_prebuilt = format!(
            "{}/{}/{}",
            KERNEL_PREBUILT,
            env::consts::ARCH,
            "latest-bzImage"
        );
        println!(
            "downloading prebuilt kernel binary from `{}`",
            kernel_prebuilt
        );
        let status = Command::new("curl")
            .args(&["--fail", "--location"])
            .arg("--output")
            .arg(&kernel_binary)
            .arg(kernel_prebuilt)
            .status();
        if let Ok(status) = status {
            if status.success() {
                println!("using prebuilt kernel binary");
                return;
            }
        }

        println!("failed to download prebuilt kernel binary");
    }

    let kernel_source = if let Ok(env_kernel_source) = env::var("CROSVM_CARGO_TEST_KERNEL_SOURCE") {
        if Path::new(&env_kernel_source).is_dir() {
            PathBuf::from(env_kernel_source)
        } else {
            panic!("expected kernel source at `{}`", env_kernel_source);
        }
    } else if Path::new(CHROOT_KERNEL_PATH).is_dir() {
        PathBuf::from(CHROOT_KERNEL_PATH)
    } else {
        let kernel_source = dir.join("kernel-source");
        // Check for kernel source
        if !kernel_source.is_dir() {
            clone_kernel_source(&kernel_source);
        }
        kernel_source
    };

    println!("building kernel from source `{}`", kernel_source.display());

    // Special provisions for using the ChromeOS kernel source and its config used in crostini.
    let current_config = kernel_source.join(".config");
    let container_vm_defconfig = kernel_source.join(CONTAINER_VM_DEFCONFIG);
    if current_config.exists() {
        fs::copy(current_config, dir.join(".config"))
            .expect("failed to copy existing kernel config");
    } else if container_vm_defconfig.exists() {
        fs::copy(container_vm_defconfig, dir.join(".config"))
            .expect("failed to copy  chromiumos container vm kernel config");
    } else {
        // TODO(zachr): the defconfig for vanilla kernels is probably inadequate. There should
        // probably be a step where additional options are added to the resulting .config.
        let status = Command::new("make")
            .current_dir(&kernel_source)
            .arg(format!("O={}", dir.display()))
            .arg("defconfig")
            .status()
            .expect("failed to execute make");
        if !status.success() {
            panic!("failed to default config kernel: {}", status);
        }
    }

    let output = Command::new("make")
        .current_dir(&kernel_source)
        .arg(format!("O={}", dir.display()))
        .args(&["bzImage", "-j"])
        .arg(format!("{}", get_cpu_count()))
        .output()
        .expect("failed to execute make");
    if !output.status.success() {
        let _ = stdout().lock().write(&output.stderr);
        panic!("failed to build kernel: {}", output.status);
    }

    fs::copy(dir.join("arch/x86/boot/bzImage"), &kernel_binary)
        .expect("failed to copy kernel binary");
}

/// Gets the target directory path for artifacts.
fn get_target_path() -> PathBuf {
    env::current_exe()
        .ok()
        .map(|mut path| {
            path.pop();
            path
        })
        .expect("failed to get target dir")
}

/// Thread-safe method for preparing a kernel and returning the path to its binary.
fn prepare_kernel() -> PathBuf {
    // Lots of unit tests need the kernel, but it should only get prepared once by any arbitrary
    // test. The rest of the tests should wait until the arbitrary one finishes.
    let default_linux_dir = get_target_path();
    static PREP_ONCE: Once = Once::new();
    PREP_ONCE.call_once(|| prepare_kernel_once(&default_linux_dir));
    default_linux_dir.join("bzImage")
}

#[test]
fn boot() {
    syslog::init().unwrap();

    let kernel_path = prepare_kernel();

    let mut c = Config::default();
    c.sandbox = false;
    c.serial_parameters.insert(
        (SerialHardware::Serial, 1),
        SerialParameters {
            type_: SerialType::Sink,
            hardware: SerialHardware::Serial,
            path: None,
            input: None,
            num: 1,
            console: false,
            earlycon: false,
            stdin: false,
        },
    );
    set_default_serial_parameters(&mut c.serial_parameters);
    c.executable_path = Some(Executable::Kernel(kernel_path));

    let r = linux::run_config(c);
    r.expect("failed to run linux");
}