mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-21 22:34:05 +00:00
Rollup merge of #39400 - alexcrichton:arm-cross-test, r=brson
Add support for test suites emulated in QEMU This commit adds support to the build system to execute test suites that cannot run natively but can instead run inside of a QEMU emulator. A proof-of-concept builder was added for the `arm-unknown-linux-gnueabihf` target to show off how this might work. In general the architecture is to have a server running inside of the emulator which a local client connects to. The protocol between the server/client supports compiling tests on the host and running them on the target inside the emulator. Closes #33114
This commit is contained in:
commit
370b63f386
@ -13,6 +13,7 @@ matrix:
|
||||
include:
|
||||
# Linux builders, all docker images
|
||||
- env: IMAGE=android DEPLOY=1
|
||||
- env: IMAGE=armhf-gnu
|
||||
- env: IMAGE=cross DEPLOY=1
|
||||
- env: IMAGE=linux-tested-targets DEPLOY=1
|
||||
- env: IMAGE=dist-arm-linux DEPLOY=1
|
||||
|
1
configure
vendored
1
configure
vendored
@ -684,6 +684,7 @@ valopt musl-root-arm "" "arm-unknown-linux-musleabi install directory"
|
||||
valopt musl-root-armhf "" "arm-unknown-linux-musleabihf install directory"
|
||||
valopt musl-root-armv7 "" "armv7-unknown-linux-musleabihf install directory"
|
||||
valopt extra-filename "" "Additional data that is hashed and passed to the -C extra-filename flag"
|
||||
valopt qemu-armhf-rootfs "" "rootfs in qemu testing, you probably don't want to use this"
|
||||
|
||||
if [ -e ${CFG_SRC_DIR}.git ]
|
||||
then
|
||||
|
8
src/Cargo.lock
generated
8
src/Cargo.lock
generated
@ -224,6 +224,14 @@ dependencies = [
|
||||
"syntax_pos 0.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "qemu-test-client"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "qemu-test-server"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.0.0"
|
||||
|
@ -11,6 +11,8 @@ members = [
|
||||
"tools/rustbook",
|
||||
"tools/tidy",
|
||||
"tools/build-manifest",
|
||||
"tools/qemu-test-client",
|
||||
"tools/qemu-test-server",
|
||||
]
|
||||
|
||||
# Curiously, compiletest will segfault if compiled with opt-level=3 on 64-bit
|
||||
|
@ -26,7 +26,7 @@ use build_helper::output;
|
||||
|
||||
use {Build, Compiler, Mode};
|
||||
use dist;
|
||||
use util::{self, dylib_path, dylib_path_var};
|
||||
use util::{self, dylib_path, dylib_path_var, exe};
|
||||
|
||||
const ADB_TEST_DIR: &'static str = "/data/tmp";
|
||||
|
||||
@ -221,6 +221,12 @@ pub fn compiletest(build: &Build,
|
||||
.arg("--llvm-cxxflags").arg("");
|
||||
}
|
||||
|
||||
if build.qemu_rootfs(target).is_some() {
|
||||
cmd.arg("--qemu-test-client")
|
||||
.arg(build.tool(&Compiler::new(0, &build.config.build),
|
||||
"qemu-test-client"));
|
||||
}
|
||||
|
||||
// Running a C compiler on MSVC requires a few env vars to be set, to be
|
||||
// sure to set them here.
|
||||
//
|
||||
@ -403,9 +409,9 @@ pub fn krate(build: &Build,
|
||||
dylib_path.insert(0, build.sysroot_libdir(&compiler, target));
|
||||
cargo.env(dylib_path_var(), env::join_paths(&dylib_path).unwrap());
|
||||
|
||||
if target.contains("android") {
|
||||
cargo.arg("--no-run");
|
||||
} else if target.contains("emscripten") {
|
||||
if target.contains("android") ||
|
||||
target.contains("emscripten") ||
|
||||
build.qemu_rootfs(target).is_some() {
|
||||
cargo.arg("--no-run");
|
||||
}
|
||||
|
||||
@ -423,6 +429,9 @@ pub fn krate(build: &Build,
|
||||
} else if target.contains("emscripten") {
|
||||
build.run(&mut cargo);
|
||||
krate_emscripten(build, &compiler, target, mode);
|
||||
} else if build.qemu_rootfs(target).is_some() {
|
||||
build.run(&mut cargo);
|
||||
krate_qemu(build, &compiler, target, mode);
|
||||
} else {
|
||||
cargo.args(&build.flags.cmd.test_args());
|
||||
build.run(&mut cargo);
|
||||
@ -480,23 +489,46 @@ fn krate_emscripten(build: &Build,
|
||||
compiler: &Compiler,
|
||||
target: &str,
|
||||
mode: Mode) {
|
||||
let mut tests = Vec::new();
|
||||
let out_dir = build.cargo_out(compiler, mode, target);
|
||||
find_tests(&out_dir, target, &mut tests);
|
||||
find_tests(&out_dir.join("deps"), target, &mut tests);
|
||||
let mut tests = Vec::new();
|
||||
let out_dir = build.cargo_out(compiler, mode, target);
|
||||
find_tests(&out_dir, target, &mut tests);
|
||||
find_tests(&out_dir.join("deps"), target, &mut tests);
|
||||
|
||||
for test in tests {
|
||||
let test_file_name = test.to_string_lossy().into_owned();
|
||||
println!("running {}", test_file_name);
|
||||
let nodejs = build.config.nodejs.as_ref().expect("nodejs not configured");
|
||||
let mut cmd = Command::new(nodejs);
|
||||
cmd.arg(&test_file_name);
|
||||
if build.config.quiet_tests {
|
||||
cmd.arg("--quiet");
|
||||
}
|
||||
build.run(&mut cmd);
|
||||
}
|
||||
}
|
||||
for test in tests {
|
||||
let test_file_name = test.to_string_lossy().into_owned();
|
||||
println!("running {}", test_file_name);
|
||||
let nodejs = build.config.nodejs.as_ref().expect("nodejs not configured");
|
||||
let mut cmd = Command::new(nodejs);
|
||||
cmd.arg(&test_file_name);
|
||||
if build.config.quiet_tests {
|
||||
cmd.arg("--quiet");
|
||||
}
|
||||
build.run(&mut cmd);
|
||||
}
|
||||
}
|
||||
|
||||
fn krate_qemu(build: &Build,
|
||||
compiler: &Compiler,
|
||||
target: &str,
|
||||
mode: Mode) {
|
||||
let mut tests = Vec::new();
|
||||
let out_dir = build.cargo_out(compiler, mode, target);
|
||||
find_tests(&out_dir, target, &mut tests);
|
||||
find_tests(&out_dir.join("deps"), target, &mut tests);
|
||||
|
||||
let tool = build.tool(&Compiler::new(0, &build.config.build),
|
||||
"qemu-test-client");
|
||||
for test in tests {
|
||||
let mut cmd = Command::new(&tool);
|
||||
cmd.arg("run")
|
||||
.arg(&test);
|
||||
if build.config.quiet_tests {
|
||||
cmd.arg("--quiet");
|
||||
}
|
||||
cmd.args(&build.flags.cmd.test_args());
|
||||
build.run(&mut cmd);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn find_tests(dir: &Path,
|
||||
@ -516,13 +548,15 @@ fn find_tests(dir: &Path,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn android_copy_libs(build: &Build,
|
||||
compiler: &Compiler,
|
||||
target: &str) {
|
||||
if !target.contains("android") {
|
||||
return
|
||||
pub fn emulator_copy_libs(build: &Build, compiler: &Compiler, target: &str) {
|
||||
if target.contains("android") {
|
||||
android_copy_libs(build, compiler, target)
|
||||
} else if let Some(s) = build.qemu_rootfs(target) {
|
||||
qemu_copy_libs(build, compiler, target, s)
|
||||
}
|
||||
}
|
||||
|
||||
fn android_copy_libs(build: &Build, compiler: &Compiler, target: &str) {
|
||||
println!("Android copy libs to emulator ({})", target);
|
||||
build.run(Command::new("adb").arg("wait-for-device"));
|
||||
build.run(Command::new("adb").arg("remount"));
|
||||
@ -548,6 +582,39 @@ pub fn android_copy_libs(build: &Build,
|
||||
}
|
||||
}
|
||||
|
||||
fn qemu_copy_libs(build: &Build,
|
||||
compiler: &Compiler,
|
||||
target: &str,
|
||||
rootfs: &Path) {
|
||||
println!("QEMU copy libs to emulator ({})", target);
|
||||
assert!(target.starts_with("arm"), "only works with arm for now");
|
||||
t!(fs::create_dir_all(build.out.join("tmp")));
|
||||
|
||||
// Copy our freshly compiled test server over to the rootfs
|
||||
let server = build.cargo_out(compiler, Mode::Tool, target)
|
||||
.join(exe("qemu-test-server", target));
|
||||
t!(fs::copy(&server, rootfs.join("testd")));
|
||||
|
||||
// Spawn the emulator and wait for it to come online
|
||||
let tool = build.tool(&Compiler::new(0, &build.config.build),
|
||||
"qemu-test-client");
|
||||
build.run(Command::new(&tool)
|
||||
.arg("spawn-emulator")
|
||||
.arg(rootfs)
|
||||
.arg(build.out.join("tmp")));
|
||||
|
||||
// Push all our dylibs to the emulator
|
||||
for f in t!(build.sysroot_libdir(compiler, target).read_dir()) {
|
||||
let f = t!(f);
|
||||
let name = f.file_name().into_string().unwrap();
|
||||
if util::is_dylib(&name) {
|
||||
build.run(Command::new(&tool)
|
||||
.arg("push")
|
||||
.arg(f.path()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Run "distcheck", a 'make check' from a tarball
|
||||
pub fn distcheck(build: &Build) {
|
||||
if build.config.build != "x86_64-unknown-linux-gnu" {
|
||||
|
@ -382,10 +382,10 @@ fn add_to_sysroot(out_dir: &Path, sysroot_dst: &Path) {
|
||||
///
|
||||
/// This will build the specified tool with the specified `host` compiler in
|
||||
/// `stage` into the normal cargo output directory.
|
||||
pub fn tool(build: &Build, stage: u32, host: &str, tool: &str) {
|
||||
println!("Building stage{} tool {} ({})", stage, tool, host);
|
||||
pub fn tool(build: &Build, stage: u32, target: &str, tool: &str) {
|
||||
println!("Building stage{} tool {} ({})", stage, tool, target);
|
||||
|
||||
let compiler = Compiler::new(stage, host);
|
||||
let compiler = Compiler::new(stage, &build.config.build);
|
||||
|
||||
// FIXME: need to clear out previous tool and ideally deps, may require
|
||||
// isolating output directories or require a pseudo shim step to
|
||||
@ -396,7 +396,7 @@ pub fn tool(build: &Build, stage: u32, host: &str, tool: &str) {
|
||||
// let out_dir = build.cargo_out(stage, &host, Mode::Librustc, target);
|
||||
// build.clear_if_dirty(&out_dir, &libstd_stamp(build, stage, &host, target));
|
||||
|
||||
let mut cargo = build.cargo(&compiler, Mode::Tool, host, "build");
|
||||
let mut cargo = build.cargo(&compiler, Mode::Tool, target, "build");
|
||||
cargo.arg("--manifest-path")
|
||||
.arg(build.src.join(format!("src/tools/{}/Cargo.toml", tool)));
|
||||
|
||||
|
@ -114,6 +114,7 @@ pub struct Target {
|
||||
pub cxx: Option<PathBuf>,
|
||||
pub ndk: Option<PathBuf>,
|
||||
pub musl_root: Option<PathBuf>,
|
||||
pub qemu_rootfs: Option<PathBuf>,
|
||||
}
|
||||
|
||||
/// Structure of the `config.toml` file that configuration is read from.
|
||||
@ -222,6 +223,7 @@ struct TomlTarget {
|
||||
cxx: Option<String>,
|
||||
android_ndk: Option<String>,
|
||||
musl_root: Option<String>,
|
||||
qemu_rootfs: Option<String>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
@ -360,6 +362,7 @@ impl Config {
|
||||
target.cxx = cfg.cxx.clone().map(PathBuf::from);
|
||||
target.cc = cfg.cc.clone().map(PathBuf::from);
|
||||
target.musl_root = cfg.musl_root.clone().map(PathBuf::from);
|
||||
target.qemu_rootfs = cfg.qemu_rootfs.clone().map(PathBuf::from);
|
||||
|
||||
config.target_config.insert(triple.clone(), target);
|
||||
}
|
||||
@ -562,6 +565,12 @@ impl Config {
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
}
|
||||
"CFG_QEMU_ARMHF_ROOTFS" if value.len() > 0 => {
|
||||
let target = "arm-unknown-linux-gnueabihf".to_string();
|
||||
let target = self.target_config.entry(target)
|
||||
.or_insert(Target::default());
|
||||
target.qemu_rootfs = Some(parse_configure_path(value));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
@ -878,6 +878,17 @@ impl Build {
|
||||
.map(|p| &**p)
|
||||
}
|
||||
|
||||
/// Returns the root of the "rootfs" image that this target will be using,
|
||||
/// if one was configured.
|
||||
///
|
||||
/// If `Some` is returned then that means that tests for this target are
|
||||
/// emulated with QEMU and binaries will need to be shipped to the emulator.
|
||||
fn qemu_rootfs(&self, target: &str) -> Option<&Path> {
|
||||
self.config.target_config.get(target)
|
||||
.and_then(|t| t.qemu_rootfs.as_ref())
|
||||
.map(|p| &**p)
|
||||
}
|
||||
|
||||
/// Path to the python interpreter to use
|
||||
fn python(&self) -> &Path {
|
||||
self.config.python.as_ref().unwrap()
|
||||
|
@ -295,7 +295,7 @@ pub fn build_rules<'a>(build: &'a Build) -> Rules {
|
||||
.dep(|s| s.name("libtest"))
|
||||
.dep(|s| s.name("tool-compiletest").target(s.host).stage(0))
|
||||
.dep(|s| s.name("test-helpers"))
|
||||
.dep(|s| s.name("android-copy-libs"))
|
||||
.dep(|s| s.name("emulator-copy-libs"))
|
||||
.default(mode != "pretty") // pretty tests don't run everywhere
|
||||
.run(move |s| {
|
||||
check::compiletest(build, &s.compiler(), s.target, mode, dir)
|
||||
@ -333,7 +333,7 @@ pub fn build_rules<'a>(build: &'a Build) -> Rules {
|
||||
.dep(|s| s.name("tool-compiletest").target(s.host).stage(0))
|
||||
.dep(|s| s.name("test-helpers"))
|
||||
.dep(|s| s.name("debugger-scripts"))
|
||||
.dep(|s| s.name("android-copy-libs"))
|
||||
.dep(|s| s.name("emulator-copy-libs"))
|
||||
.run(move |s| check::compiletest(build, &s.compiler(), s.target,
|
||||
"debuginfo-gdb", "debuginfo"));
|
||||
let mut rule = rules.test("check-debuginfo", "src/test/debuginfo");
|
||||
@ -387,14 +387,14 @@ pub fn build_rules<'a>(build: &'a Build) -> Rules {
|
||||
for (krate, path, _default) in krates("std_shim") {
|
||||
rules.test(&krate.test_step, path)
|
||||
.dep(|s| s.name("libtest"))
|
||||
.dep(|s| s.name("android-copy-libs"))
|
||||
.dep(|s| s.name("emulator-copy-libs"))
|
||||
.run(move |s| check::krate(build, &s.compiler(), s.target,
|
||||
Mode::Libstd, TestKind::Test,
|
||||
Some(&krate.name)));
|
||||
}
|
||||
rules.test("check-std-all", "path/to/nowhere")
|
||||
.dep(|s| s.name("libtest"))
|
||||
.dep(|s| s.name("android-copy-libs"))
|
||||
.dep(|s| s.name("emulator-copy-libs"))
|
||||
.default(true)
|
||||
.run(move |s| check::krate(build, &s.compiler(), s.target,
|
||||
Mode::Libstd, TestKind::Test, None));
|
||||
@ -403,14 +403,14 @@ pub fn build_rules<'a>(build: &'a Build) -> Rules {
|
||||
for (krate, path, _default) in krates("std_shim") {
|
||||
rules.bench(&krate.bench_step, path)
|
||||
.dep(|s| s.name("libtest"))
|
||||
.dep(|s| s.name("android-copy-libs"))
|
||||
.dep(|s| s.name("emulator-copy-libs"))
|
||||
.run(move |s| check::krate(build, &s.compiler(), s.target,
|
||||
Mode::Libstd, TestKind::Bench,
|
||||
Some(&krate.name)));
|
||||
}
|
||||
rules.bench("bench-std-all", "path/to/nowhere")
|
||||
.dep(|s| s.name("libtest"))
|
||||
.dep(|s| s.name("android-copy-libs"))
|
||||
.dep(|s| s.name("emulator-copy-libs"))
|
||||
.default(true)
|
||||
.run(move |s| check::krate(build, &s.compiler(), s.target,
|
||||
Mode::Libstd, TestKind::Bench, None));
|
||||
@ -418,21 +418,21 @@ pub fn build_rules<'a>(build: &'a Build) -> Rules {
|
||||
for (krate, path, _default) in krates("test_shim") {
|
||||
rules.test(&krate.test_step, path)
|
||||
.dep(|s| s.name("libtest"))
|
||||
.dep(|s| s.name("android-copy-libs"))
|
||||
.dep(|s| s.name("emulator-copy-libs"))
|
||||
.run(move |s| check::krate(build, &s.compiler(), s.target,
|
||||
Mode::Libtest, TestKind::Test,
|
||||
Some(&krate.name)));
|
||||
}
|
||||
rules.test("check-test-all", "path/to/nowhere")
|
||||
.dep(|s| s.name("libtest"))
|
||||
.dep(|s| s.name("android-copy-libs"))
|
||||
.dep(|s| s.name("emulator-copy-libs"))
|
||||
.default(true)
|
||||
.run(move |s| check::krate(build, &s.compiler(), s.target,
|
||||
Mode::Libtest, TestKind::Test, None));
|
||||
for (krate, path, _default) in krates("rustc-main") {
|
||||
rules.test(&krate.test_step, path)
|
||||
.dep(|s| s.name("librustc"))
|
||||
.dep(|s| s.name("android-copy-libs"))
|
||||
.dep(|s| s.name("emulator-copy-libs"))
|
||||
.host(true)
|
||||
.run(move |s| check::krate(build, &s.compiler(), s.target,
|
||||
Mode::Librustc, TestKind::Test,
|
||||
@ -440,7 +440,7 @@ pub fn build_rules<'a>(build: &'a Build) -> Rules {
|
||||
}
|
||||
rules.test("check-rustc-all", "path/to/nowhere")
|
||||
.dep(|s| s.name("librustc"))
|
||||
.dep(|s| s.name("android-copy-libs"))
|
||||
.dep(|s| s.name("emulator-copy-libs"))
|
||||
.default(true)
|
||||
.host(true)
|
||||
.run(move |s| check::krate(build, &s.compiler(), s.target,
|
||||
@ -481,9 +481,34 @@ pub fn build_rules<'a>(build: &'a Build) -> Rules {
|
||||
|
||||
rules.build("test-helpers", "src/rt/rust_test_helpers.c")
|
||||
.run(move |s| native::test_helpers(build, s.target));
|
||||
rules.test("android-copy-libs", "path/to/nowhere")
|
||||
|
||||
// Some test suites are run inside emulators, and most of our test binaries
|
||||
// are linked dynamically which means we need to ship the standard library
|
||||
// and such to the emulator ahead of time. This step represents this and is
|
||||
// a dependency of all test suites.
|
||||
//
|
||||
// Most of the time this step is a noop (the `check::emulator_copy_libs`
|
||||
// only does work if necessary). For some steps such as shipping data to
|
||||
// QEMU we have to build our own tools so we've got conditional dependencies
|
||||
// on those programs as well. Note that the QEMU client is built for the
|
||||
// build target (us) and the server is built for the target.
|
||||
rules.test("emulator-copy-libs", "path/to/nowhere")
|
||||
.dep(|s| s.name("libtest"))
|
||||
.run(move |s| check::android_copy_libs(build, &s.compiler(), s.target));
|
||||
.dep(move |s| {
|
||||
if build.qemu_rootfs(s.target).is_some() {
|
||||
s.name("tool-qemu-test-client").target(s.host).stage(0)
|
||||
} else {
|
||||
Step::noop()
|
||||
}
|
||||
})
|
||||
.dep(move |s| {
|
||||
if build.qemu_rootfs(s.target).is_some() {
|
||||
s.name("tool-qemu-test-server")
|
||||
} else {
|
||||
Step::noop()
|
||||
}
|
||||
})
|
||||
.run(move |s| check::emulator_copy_libs(build, &s.compiler(), s.target));
|
||||
|
||||
rules.test("check-bootstrap", "src/bootstrap")
|
||||
.default(true)
|
||||
@ -516,6 +541,12 @@ pub fn build_rules<'a>(build: &'a Build) -> Rules {
|
||||
rules.build("tool-build-manifest", "src/tools/build-manifest")
|
||||
.dep(|s| s.name("libstd"))
|
||||
.run(move |s| compile::tool(build, s.stage, s.target, "build-manifest"));
|
||||
rules.build("tool-qemu-test-server", "src/tools/qemu-test-server")
|
||||
.dep(|s| s.name("libstd"))
|
||||
.run(move |s| compile::tool(build, s.stage, s.target, "qemu-test-server"));
|
||||
rules.build("tool-qemu-test-client", "src/tools/qemu-test-client")
|
||||
.dep(|s| s.name("libstd"))
|
||||
.run(move |s| compile::tool(build, s.stage, s.target, "qemu-test-client"));
|
||||
|
||||
// ========================================================================
|
||||
// Documentation targets
|
||||
|
90
src/ci/docker/armhf-gnu/Dockerfile
Normal file
90
src/ci/docker/armhf-gnu/Dockerfile
Normal file
@ -0,0 +1,90 @@
|
||||
FROM ubuntu:16.04
|
||||
|
||||
RUN apt-get update -y && apt-get install -y --no-install-recommends \
|
||||
bc \
|
||||
bzip2 \
|
||||
ca-certificates \
|
||||
cmake \
|
||||
cpio \
|
||||
curl \
|
||||
file \
|
||||
g++ \
|
||||
gcc-arm-linux-gnueabihf \
|
||||
git \
|
||||
libc6-dev \
|
||||
libc6-dev-armhf-cross \
|
||||
make \
|
||||
python2.7 \
|
||||
qemu-system-arm \
|
||||
xz-utils
|
||||
|
||||
ENV ARCH=arm \
|
||||
CROSS_COMPILE=arm-linux-gnueabihf-
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
# Compile the kernel that we're going to run and be emulating with. This is
|
||||
# basically just done to be compatible with the QEMU target that we're going
|
||||
# to be using when running tests. If any other kernel works or if any
|
||||
# other QEMU target works with some other stock kernel, we can use that too!
|
||||
#
|
||||
# The `vexpress_config` config file was a previously generated config file for
|
||||
# the kernel. This file was generated by running `make vexpress_defconfig`
|
||||
# followed by `make menuconfig` and then enabling the IPv6 protocol page.
|
||||
COPY vexpress_config /build/.config
|
||||
RUN curl https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.4.42.tar.xz | \
|
||||
tar xJf - && \
|
||||
cd /build/linux-4.4.42 && \
|
||||
cp /build/.config . && \
|
||||
make -j$(nproc) all && \
|
||||
cp arch/arm/boot/zImage /tmp && \
|
||||
cd /build && \
|
||||
rm -rf linux-4.4.42
|
||||
|
||||
# Compile an instance of busybox as this provides a lightweight system and init
|
||||
# binary which we will boot into. Only trick here is configuring busybox to
|
||||
# build static binaries.
|
||||
RUN curl https://www.busybox.net/downloads/busybox-1.21.1.tar.bz2 | tar xjf - && \
|
||||
cd busybox-1.21.1 && \
|
||||
make defconfig && \
|
||||
sed -i 's/.*CONFIG_STATIC.*/CONFIG_STATIC=y/' .config && \
|
||||
make -j$(nproc) && \
|
||||
make install && \
|
||||
mv _install /tmp/rootfs && \
|
||||
cd /build && \
|
||||
rm -rf busybox-1.12.1
|
||||
|
||||
# Download the ubuntu rootfs, which we'll use as a chroot for all our tests.
|
||||
WORKDIR /tmp
|
||||
RUN mkdir rootfs/ubuntu
|
||||
RUN curl http://cdimage.ubuntu.com/ubuntu-base/releases/16.04/release/ubuntu-base-16.04-core-armhf.tar.gz | \
|
||||
tar xzf - -C rootfs/ubuntu && \
|
||||
cd rootfs && mkdir proc sys dev etc etc/init.d
|
||||
|
||||
# Copy over our init script, which starts up our test server and also a few
|
||||
# other misc tasks.
|
||||
COPY rcS rootfs/etc/init.d/rcS
|
||||
RUN chmod +x rootfs/etc/init.d/rcS
|
||||
|
||||
# Helper to quickly fill the entropy pool in the kernel.
|
||||
COPY addentropy.c /tmp/
|
||||
RUN arm-linux-gnueabihf-gcc addentropy.c -o rootfs/addentropy -static
|
||||
|
||||
# TODO: What is this?!
|
||||
RUN curl -O http://ftp.nl.debian.org/debian/dists/jessie/main/installer-armhf/current/images/device-tree/vexpress-v2p-ca15-tc1.dtb
|
||||
|
||||
ENV SCCACHE_DIGEST=7237e38e029342fa27b7ac25412cb9d52554008b12389727320bd533fd7f05b6a96d55485f305caf95e5c8f5f97c3313e10012ccad3e752aba2518f3522ba783
|
||||
RUN curl -L https://api.pub.build.mozilla.org/tooltool/sha512/$SCCACHE_DIGEST | \
|
||||
tar xJf - -C /usr/local/bin --strip-components=1
|
||||
|
||||
RUN curl -OL https://github.com/Yelp/dumb-init/releases/download/v1.2.0/dumb-init_1.2.0_amd64.deb && \
|
||||
dpkg -i dumb-init_*.deb && \
|
||||
rm dumb-init_*.deb
|
||||
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
|
||||
|
||||
ENV RUST_CONFIGURE_ARGS \
|
||||
--target=arm-unknown-linux-gnueabihf \
|
||||
--qemu-armhf-rootfs=/tmp/rootfs
|
||||
ENV SCRIPT python2.7 ../x.py test --target arm-unknown-linux-gnueabihf
|
||||
|
||||
ENV NO_CHANGE_USER=1
|
43
src/ci/docker/armhf-gnu/addentropy.c
Normal file
43
src/ci/docker/armhf-gnu/addentropy.c
Normal file
@ -0,0 +1,43 @@
|
||||
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/random.h>
|
||||
|
||||
#define N 2048
|
||||
|
||||
struct entropy {
|
||||
int ent_count;
|
||||
int size;
|
||||
unsigned char data[N];
|
||||
};
|
||||
|
||||
int main() {
|
||||
struct entropy buf;
|
||||
ssize_t n;
|
||||
|
||||
int random_fd = open("/dev/random", O_RDWR);
|
||||
assert(random_fd >= 0);
|
||||
|
||||
while ((n = read(0, &buf.data, N)) > 0) {
|
||||
buf.ent_count = n * 8;
|
||||
buf.size = n;
|
||||
if (ioctl(random_fd, RNDADDENTROPY, &buf) != 0) {
|
||||
perror("failed to add entropy");
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
28
src/ci/docker/armhf-gnu/rcS
Normal file
28
src/ci/docker/armhf-gnu/rcS
Normal file
@ -0,0 +1,28 @@
|
||||
#!/bin/sh
|
||||
mount -t proc none /proc
|
||||
mount -t sysfs none /sys
|
||||
/sbin/mdev -s
|
||||
|
||||
# fill up our entropy pool, if we don't do this then anything with a hash map
|
||||
# will likely block forever as the kernel is pretty unlikely to have enough
|
||||
# entropy.
|
||||
/addentropy < /addentropy
|
||||
cat /dev/urandom | head -n 2048 | /addentropy
|
||||
|
||||
# Set up IP that qemu expects. This confgures eth0 with the public IP that QEMU
|
||||
# will communicate to as well as the loopback 127.0.0.1 address.
|
||||
ifconfig eth0 10.0.2.15
|
||||
ifconfig lo up
|
||||
|
||||
# Configure DNS resolution of 'localhost' to work
|
||||
echo 'hosts: files dns' >> /ubuntu/etc/nsswitch.conf
|
||||
echo '127.0.0.1 localhost' >> /ubuntu/etc/hosts
|
||||
|
||||
# prepare the chroot
|
||||
mount -t proc proc /ubuntu/proc/
|
||||
mount --rbind /sys /ubuntu/sys/
|
||||
mount --rbind /dev /ubuntu/dev/
|
||||
|
||||
# Execute our `testd` inside the ubuntu chroot
|
||||
cp /testd /ubuntu/testd
|
||||
chroot /ubuntu /testd &
|
2910
src/ci/docker/armhf-gnu/vexpress_config
Normal file
2910
src/ci/docker/armhf-gnu/vexpress_config
Normal file
File diff suppressed because it is too large
Load Diff
@ -11,11 +11,13 @@
|
||||
|
||||
set -e
|
||||
|
||||
if [ "$LOCAL_USER_ID" != "" ]; then
|
||||
useradd --shell /bin/bash -u $LOCAL_USER_ID -o -c "" -m user
|
||||
export HOME=/home/user
|
||||
unset LOCAL_USER_ID
|
||||
exec su --preserve-environment -c "env PATH=$PATH \"$0\"" user
|
||||
if [ "$NO_CHANGE_USER" = "" ]; then
|
||||
if [ "$LOCAL_USER_ID" != "" ]; then
|
||||
useradd --shell /bin/bash -u $LOCAL_USER_ID -o -c "" -m user
|
||||
export HOME=/home/user
|
||||
unset LOCAL_USER_ID
|
||||
exec su --preserve-environment -c "env PATH=$PATH \"$0\"" user
|
||||
fi
|
||||
fi
|
||||
|
||||
RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --enable-sccache"
|
||||
|
@ -439,6 +439,10 @@ mod tests {
|
||||
#[test]
|
||||
#[cfg_attr(target_os = "macos", ignore)]
|
||||
#[cfg_attr(target_os = "nacl", ignore)] // no signals on NaCl.
|
||||
// When run under our current QEMU emulation test suite this test fails,
|
||||
// although the reason isn't very clear as to why. For now this test is
|
||||
// ignored there.
|
||||
#[cfg_attr(target_arch = "arm", ignore)]
|
||||
fn test_process_mask() {
|
||||
unsafe {
|
||||
// Test to make sure that a signal mask does not get inherited.
|
||||
@ -471,7 +475,7 @@ mod tests {
|
||||
// Either EOF or failure (EPIPE) is okay.
|
||||
let mut buf = [0; 5];
|
||||
if let Ok(ret) = stdout_read.read(&mut buf) {
|
||||
assert!(ret == 0);
|
||||
assert_eq!(ret, 0);
|
||||
}
|
||||
|
||||
t!(cat.wait());
|
||||
|
@ -35,7 +35,7 @@ fn main() {
|
||||
}
|
||||
|
||||
fn parent() {
|
||||
let file = File::open(file!()).unwrap();
|
||||
let file = File::open(env::current_exe().unwrap()).unwrap();
|
||||
let tcp1 = TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
let tcp2 = tcp1.try_clone().unwrap();
|
||||
let addr = tcp1.local_addr().unwrap();
|
||||
|
@ -185,6 +185,9 @@ pub struct Config {
|
||||
// Print one character per test instead of one line
|
||||
pub quiet: bool,
|
||||
|
||||
// where to find the qemu test client process, if we're using it
|
||||
pub qemu_test_client: Option<PathBuf>,
|
||||
|
||||
// Configuration for various run-make tests frobbing things like C compilers
|
||||
// or querying about various LLVM component information.
|
||||
pub cc: String,
|
||||
|
@ -116,6 +116,7 @@ pub fn parse_config(args: Vec<String> ) -> Config {
|
||||
reqopt("", "llvm-components", "list of LLVM components built in", "LIST"),
|
||||
reqopt("", "llvm-cxxflags", "C++ flags for LLVM", "FLAGS"),
|
||||
optopt("", "nodejs", "the name of nodejs", "PATH"),
|
||||
optopt("", "qemu-test-client", "path to the qemu test client", "PATH"),
|
||||
optflag("h", "help", "show this message")];
|
||||
|
||||
let (argv0, args_) = args.split_first().unwrap();
|
||||
@ -196,6 +197,7 @@ pub fn parse_config(args: Vec<String> ) -> Config {
|
||||
lldb_python_dir: matches.opt_str("lldb-python-dir"),
|
||||
verbose: matches.opt_present("verbose"),
|
||||
quiet: matches.opt_present("quiet"),
|
||||
qemu_test_client: matches.opt_str("qemu-test-client").map(PathBuf::from),
|
||||
|
||||
cc: matches.opt_str("cc").unwrap(),
|
||||
cxx: matches.opt_str("cxx").unwrap(),
|
||||
@ -302,6 +304,14 @@ pub fn run_tests(config: &Config) {
|
||||
// time.
|
||||
env::set_var("RUST_TEST_THREADS", "1");
|
||||
}
|
||||
|
||||
DebugInfoGdb => {
|
||||
if config.qemu_test_client.is_some() {
|
||||
println!("WARNING: debuginfo tests are not available when \
|
||||
testing with QEMU");
|
||||
return
|
||||
}
|
||||
}
|
||||
_ => { /* proceed */ }
|
||||
}
|
||||
|
||||
|
@ -1190,7 +1190,45 @@ actual:\n\
|
||||
"arm-linux-androideabi" | "armv7-linux-androideabi" | "aarch64-linux-android" => {
|
||||
self._arm_exec_compiled_test(env)
|
||||
}
|
||||
_=> {
|
||||
|
||||
// This is pretty similar to below, we're transforming:
|
||||
//
|
||||
// program arg1 arg2
|
||||
//
|
||||
// into
|
||||
//
|
||||
// qemu-test-client run program:support-lib.so arg1 arg2
|
||||
//
|
||||
// The test-client program will upload `program` to the emulator
|
||||
// along with all other support libraries listed (in this case
|
||||
// `support-lib.so`. It will then execute the program on the
|
||||
// emulator with the arguments specified (in the environment we give
|
||||
// the process) and then report back the same result.
|
||||
_ if self.config.qemu_test_client.is_some() => {
|
||||
let aux_dir = self.aux_output_dir_name();
|
||||
let mut args = self.make_run_args();
|
||||
let mut program = args.prog.clone();
|
||||
if let Ok(entries) = aux_dir.read_dir() {
|
||||
for entry in entries {
|
||||
let entry = entry.unwrap();
|
||||
if !entry.path().is_file() {
|
||||
continue
|
||||
}
|
||||
program.push_str(":");
|
||||
program.push_str(entry.path().to_str().unwrap());
|
||||
}
|
||||
}
|
||||
args.args.insert(0, program);
|
||||
args.args.insert(0, "run".to_string());
|
||||
args.prog = self.config.qemu_test_client.clone().unwrap()
|
||||
.into_os_string().into_string().unwrap();
|
||||
self.compose_and_run(args,
|
||||
env,
|
||||
self.config.run_lib_path.to_str().unwrap(),
|
||||
Some(aux_dir.to_str().unwrap()),
|
||||
None)
|
||||
}
|
||||
_ => {
|
||||
let aux_dir = self.aux_output_dir_name();
|
||||
self.compose_and_run(self.make_run_args(),
|
||||
env,
|
||||
|
6
src/tools/qemu-test-client/Cargo.toml
Normal file
6
src/tools/qemu-test-client/Cargo.toml
Normal file
@ -0,0 +1,6 @@
|
||||
[package]
|
||||
name = "qemu-test-client"
|
||||
version = "0.1.0"
|
||||
authors = ["The Rust Project Developers"]
|
||||
|
||||
[dependencies]
|
221
src/tools/qemu-test-client/src/main.rs
Normal file
221
src/tools/qemu-test-client/src/main.rs
Normal file
@ -0,0 +1,221 @@
|
||||
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
/// This is a small client program intended to pair with `qemu-test-server` in
|
||||
/// this repository. This client connects to the server over TCP and is used to
|
||||
/// push artifacts and run tests on the server instead of locally.
|
||||
///
|
||||
/// Here is also where we bake in the support to spawn the QEMU emulator as
|
||||
/// well.
|
||||
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::io::{self, BufWriter};
|
||||
use std::net::TcpStream;
|
||||
use std::path::Path;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
macro_rules! t {
|
||||
($e:expr) => (match $e {
|
||||
Ok(e) => e,
|
||||
Err(e) => panic!("{} failed with {}", stringify!($e), e),
|
||||
})
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut args = env::args().skip(1);
|
||||
|
||||
match &args.next().unwrap()[..] {
|
||||
"spawn-emulator" => {
|
||||
spawn_emulator(Path::new(&args.next().unwrap()),
|
||||
Path::new(&args.next().unwrap()))
|
||||
}
|
||||
"push" => {
|
||||
push(Path::new(&args.next().unwrap()))
|
||||
}
|
||||
"run" => {
|
||||
run(args.next().unwrap(), args.collect())
|
||||
}
|
||||
cmd => panic!("unknown command: {}", cmd),
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_emulator(rootfs: &Path, tmpdir: &Path) {
|
||||
// Generate a new rootfs image now that we've updated the test server
|
||||
// executable. This is the equivalent of:
|
||||
//
|
||||
// find $rootfs -print 0 | cpio --null -o --format=newc > rootfs.img
|
||||
let rootfs_img = tmpdir.join("rootfs.img");
|
||||
let mut cmd = Command::new("cpio");
|
||||
cmd.arg("--null")
|
||||
.arg("-o")
|
||||
.arg("--format=newc")
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.current_dir(rootfs);
|
||||
let mut child = t!(cmd.spawn());
|
||||
let mut stdin = child.stdin.take().unwrap();
|
||||
let rootfs = rootfs.to_path_buf();
|
||||
thread::spawn(move || add_files(&mut stdin, &rootfs, &rootfs));
|
||||
t!(io::copy(&mut child.stdout.take().unwrap(),
|
||||
&mut t!(File::create(&rootfs_img))));
|
||||
assert!(t!(child.wait()).success());
|
||||
|
||||
// Start up the emulator, in the background
|
||||
let mut cmd = Command::new("qemu-system-arm");
|
||||
cmd.arg("-M").arg("vexpress-a15")
|
||||
.arg("-m").arg("1024")
|
||||
.arg("-kernel").arg("/tmp/zImage")
|
||||
.arg("-initrd").arg(&rootfs_img)
|
||||
.arg("-dtb").arg("/tmp/vexpress-v2p-ca15-tc1.dtb")
|
||||
.arg("-append").arg("console=ttyAMA0 root=/dev/ram rdinit=/sbin/init init=/sbin/init")
|
||||
.arg("-nographic")
|
||||
.arg("-redir").arg("tcp:12345::12345");
|
||||
t!(cmd.spawn());
|
||||
|
||||
// Wait for the emulator to come online
|
||||
loop {
|
||||
let dur = Duration::from_millis(100);
|
||||
if let Ok(mut client) = TcpStream::connect("127.0.0.1:12345") {
|
||||
t!(client.set_read_timeout(Some(dur)));
|
||||
t!(client.set_write_timeout(Some(dur)));
|
||||
if client.write_all(b"ping").is_ok() {
|
||||
let mut b = [0; 4];
|
||||
if client.read_exact(&mut b).is_ok() {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
thread::sleep(dur);
|
||||
}
|
||||
|
||||
fn add_files(w: &mut Write, root: &Path, cur: &Path) {
|
||||
for entry in t!(cur.read_dir()) {
|
||||
let entry = t!(entry);
|
||||
let path = entry.path();
|
||||
let to_print = path.strip_prefix(root).unwrap();
|
||||
t!(write!(w, "{}\u{0}", to_print.to_str().unwrap()));
|
||||
if t!(entry.file_type()).is_dir() {
|
||||
add_files(w, root, &path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn push(path: &Path) {
|
||||
let client = t!(TcpStream::connect("127.0.0.1:12345"));
|
||||
let mut client = BufWriter::new(client);
|
||||
t!(client.write_all(b"push"));
|
||||
t!(client.write_all(path.file_name().unwrap().to_str().unwrap().as_bytes()));
|
||||
t!(client.write_all(&[0]));
|
||||
let mut file = t!(File::open(path));
|
||||
t!(io::copy(&mut file, &mut client));
|
||||
t!(client.flush());
|
||||
println!("done pushing {:?}", path);
|
||||
}
|
||||
|
||||
fn run(files: String, args: Vec<String>) {
|
||||
let client = t!(TcpStream::connect("127.0.0.1:12345"));
|
||||
let mut client = BufWriter::new(client);
|
||||
t!(client.write_all(b"run "));
|
||||
|
||||
// Send over the args
|
||||
for arg in args {
|
||||
t!(client.write_all(arg.as_bytes()));
|
||||
t!(client.write_all(&[0]));
|
||||
}
|
||||
t!(client.write_all(&[0]));
|
||||
|
||||
// Send over env vars
|
||||
for (k, v) in env::vars() {
|
||||
if k != "PATH" && k != "LD_LIBRARY_PATH" {
|
||||
t!(client.write_all(k.as_bytes()));
|
||||
t!(client.write_all(&[0]));
|
||||
t!(client.write_all(v.as_bytes()));
|
||||
t!(client.write_all(&[0]));
|
||||
}
|
||||
}
|
||||
t!(client.write_all(&[0]));
|
||||
|
||||
// Send over support libraries
|
||||
let mut files = files.split(':');
|
||||
let exe = files.next().unwrap();
|
||||
for file in files.map(Path::new) {
|
||||
t!(client.write_all(file.file_name().unwrap().to_str().unwrap().as_bytes()));
|
||||
t!(client.write_all(&[0]));
|
||||
send(&file, &mut client);
|
||||
}
|
||||
t!(client.write_all(&[0]));
|
||||
|
||||
// Send over the client executable as the last piece
|
||||
send(exe.as_ref(), &mut client);
|
||||
|
||||
println!("uploaded {:?}, waiting for result", exe);
|
||||
|
||||
// Ok now it's time to read all the output. We're receiving "frames"
|
||||
// representing stdout/stderr, so we decode all that here.
|
||||
let mut header = [0; 5];
|
||||
let mut stderr_done = false;
|
||||
let mut stdout_done = false;
|
||||
let mut client = t!(client.into_inner());
|
||||
let mut stdout = io::stdout();
|
||||
let mut stderr = io::stderr();
|
||||
while !stdout_done || !stderr_done {
|
||||
t!(client.read_exact(&mut header));
|
||||
let amt = ((header[1] as u64) << 24) |
|
||||
((header[2] as u64) << 16) |
|
||||
((header[3] as u64) << 8) |
|
||||
((header[4] as u64) << 0);
|
||||
if header[0] == 0 {
|
||||
if amt == 0 {
|
||||
stdout_done = true;
|
||||
} else {
|
||||
t!(io::copy(&mut (&mut client).take(amt), &mut stdout));
|
||||
t!(stdout.flush());
|
||||
}
|
||||
} else {
|
||||
if amt == 0 {
|
||||
stderr_done = true;
|
||||
} else {
|
||||
t!(io::copy(&mut (&mut client).take(amt), &mut stderr));
|
||||
t!(stderr.flush());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, read out the exit status
|
||||
let mut status = [0; 5];
|
||||
t!(client.read_exact(&mut status));
|
||||
let code = ((status[1] as i32) << 24) |
|
||||
((status[2] as i32) << 16) |
|
||||
((status[3] as i32) << 8) |
|
||||
((status[4] as i32) << 0);
|
||||
if status[0] == 0 {
|
||||
std::process::exit(code);
|
||||
} else {
|
||||
println!("died due to signal {}", code);
|
||||
std::process::exit(3);
|
||||
}
|
||||
}
|
||||
|
||||
fn send(path: &Path, dst: &mut Write) {
|
||||
let mut file = t!(File::open(&path));
|
||||
let amt = t!(file.metadata()).len();
|
||||
t!(dst.write_all(&[
|
||||
(amt >> 24) as u8,
|
||||
(amt >> 16) as u8,
|
||||
(amt >> 8) as u8,
|
||||
(amt >> 0) as u8,
|
||||
]));
|
||||
t!(io::copy(&mut file, dst));
|
||||
}
|
6
src/tools/qemu-test-server/Cargo.toml
Normal file
6
src/tools/qemu-test-server/Cargo.toml
Normal file
@ -0,0 +1,6 @@
|
||||
[package]
|
||||
name = "qemu-test-server"
|
||||
version = "0.1.0"
|
||||
authors = ["The Rust Project Developers"]
|
||||
|
||||
[dependencies]
|
232
src/tools/qemu-test-server/src/main.rs
Normal file
232
src/tools/qemu-test-server/src/main.rs
Normal file
@ -0,0 +1,232 @@
|
||||
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
/// This is a small server which is intended to run inside of an emulator. This
|
||||
/// server pairs with the `qemu-test-client` program in this repository. The
|
||||
/// `qemu-test-client` connects to this server over a TCP socket and performs
|
||||
/// work such as:
|
||||
///
|
||||
/// 1. Pushing shared libraries to the server
|
||||
/// 2. Running tests through the server
|
||||
///
|
||||
/// The server supports running tests concurrently and also supports tests
|
||||
/// themselves having support libraries. All data over the TCP sockets is in a
|
||||
/// basically custom format suiting our needs.
|
||||
|
||||
use std::fs::{self, File, Permissions};
|
||||
use std::io::prelude::*;
|
||||
use std::io::{self, BufReader};
|
||||
use std::net::{TcpListener, TcpStream};
|
||||
use std::os::unix::prelude::*;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::path::Path;
|
||||
use std::str;
|
||||
use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering};
|
||||
use std::thread;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
macro_rules! t {
|
||||
($e:expr) => (match $e {
|
||||
Ok(e) => e,
|
||||
Err(e) => panic!("{} failed with {}", stringify!($e), e),
|
||||
})
|
||||
}
|
||||
|
||||
static TEST: AtomicUsize = ATOMIC_USIZE_INIT;
|
||||
|
||||
fn main() {
|
||||
println!("starting test server");
|
||||
let listener = t!(TcpListener::bind("10.0.2.15:12345"));
|
||||
println!("listening!");
|
||||
|
||||
let work = Path::new("/tmp/work");
|
||||
t!(fs::create_dir_all(work));
|
||||
|
||||
let lock = Arc::new(Mutex::new(()));
|
||||
|
||||
for socket in listener.incoming() {
|
||||
let mut socket = t!(socket);
|
||||
let mut buf = [0; 4];
|
||||
t!(socket.read_exact(&mut buf));
|
||||
if &buf[..] == b"ping" {
|
||||
t!(socket.write_all(b"pong"));
|
||||
} else if &buf[..] == b"push" {
|
||||
handle_push(socket, work);
|
||||
} else if &buf[..] == b"run " {
|
||||
let lock = lock.clone();
|
||||
thread::spawn(move || handle_run(socket, work, &lock));
|
||||
} else {
|
||||
panic!("unknown command {:?}", buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_push(socket: TcpStream, work: &Path) {
|
||||
let mut reader = BufReader::new(socket);
|
||||
let mut filename = Vec::new();
|
||||
t!(reader.read_until(0, &mut filename));
|
||||
filename.pop(); // chop off the 0
|
||||
let filename = t!(str::from_utf8(&filename));
|
||||
|
||||
let path = work.join(filename);
|
||||
t!(io::copy(&mut reader, &mut t!(File::create(&path))));
|
||||
t!(fs::set_permissions(&path, Permissions::from_mode(0o755)));
|
||||
}
|
||||
|
||||
struct RemoveOnDrop<'a> {
|
||||
inner: &'a Path,
|
||||
}
|
||||
|
||||
impl<'a> Drop for RemoveOnDrop<'a> {
|
||||
fn drop(&mut self) {
|
||||
t!(fs::remove_dir_all(self.inner));
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_run(socket: TcpStream, work: &Path, lock: &Mutex<()>) {
|
||||
let mut arg = Vec::new();
|
||||
let mut reader = BufReader::new(socket);
|
||||
|
||||
// Allocate ourselves a directory that we'll delete when we're done to save
|
||||
// space.
|
||||
let n = TEST.fetch_add(1, Ordering::SeqCst);
|
||||
let path = work.join(format!("test{}", n));
|
||||
let exe = path.join("exe");
|
||||
t!(fs::create_dir(&path));
|
||||
let _a = RemoveOnDrop { inner: &path };
|
||||
|
||||
// First up we'll get a list of arguments delimited with 0 bytes. An empty
|
||||
// argument means that we're done.
|
||||
let mut cmd = Command::new(&exe);
|
||||
while t!(reader.read_until(0, &mut arg)) > 1 {
|
||||
cmd.arg(t!(str::from_utf8(&arg[..arg.len() - 1])));
|
||||
arg.truncate(0);
|
||||
}
|
||||
|
||||
// Next we'll get a bunch of env vars in pairs delimited by 0s as well
|
||||
arg.truncate(0);
|
||||
while t!(reader.read_until(0, &mut arg)) > 1 {
|
||||
let key_len = arg.len() - 1;
|
||||
let val_len = t!(reader.read_until(0, &mut arg)) - 1;
|
||||
{
|
||||
let key = &arg[..key_len];
|
||||
let val = &arg[key_len + 1..][..val_len];
|
||||
let key = t!(str::from_utf8(key));
|
||||
let val = t!(str::from_utf8(val));
|
||||
cmd.env(key, val);
|
||||
}
|
||||
arg.truncate(0);
|
||||
}
|
||||
|
||||
// The section of code from here down to where we drop the lock is going to
|
||||
// be a critical section for us. On Linux you can't execute a file which is
|
||||
// open somewhere for writing, as you'll receive the error "text file busy".
|
||||
// Now here we never have the text file open for writing when we spawn it,
|
||||
// so why do we still need a critical section?
|
||||
//
|
||||
// Process spawning first involves a `fork` on Unix, which clones all file
|
||||
// descriptors into the child process. This means that it's possible for us
|
||||
// to open the file for writing (as we're downloading it), then some other
|
||||
// thread forks, then we close the file and try to exec. At that point the
|
||||
// other thread created a child process with the file open for writing, and
|
||||
// we attempt to execute it, so we get an error.
|
||||
//
|
||||
// This race is resolve by ensuring that only one thread can writ ethe file
|
||||
// and spawn a child process at once. Kinda an unfortunate solution, but we
|
||||
// don't have many other choices with this sort of setup!
|
||||
//
|
||||
// In any case the lock is acquired here, before we start writing any files.
|
||||
// It's then dropped just after we spawn the child. That way we don't lock
|
||||
// the execution of the child, just the creation of its files.
|
||||
let lock = lock.lock();
|
||||
|
||||
// Next there's a list of dynamic libraries preceded by their filenames.
|
||||
arg.truncate(0);
|
||||
while t!(reader.read_until(0, &mut arg)) > 1 {
|
||||
let dst = path.join(t!(str::from_utf8(&arg[..arg.len() - 1])));
|
||||
let amt = read_u32(&mut reader) as u64;
|
||||
t!(io::copy(&mut reader.by_ref().take(amt),
|
||||
&mut t!(File::create(&dst))));
|
||||
t!(fs::set_permissions(&dst, Permissions::from_mode(0o755)));
|
||||
arg.truncate(0);
|
||||
}
|
||||
|
||||
// Finally we'll get the binary. The other end will tell us how big the
|
||||
// binary is and then we'll download it all to the exe path we calculated
|
||||
// earlier.
|
||||
let amt = read_u32(&mut reader) as u64;
|
||||
t!(io::copy(&mut reader.by_ref().take(amt),
|
||||
&mut t!(File::create(&exe))));
|
||||
t!(fs::set_permissions(&exe, Permissions::from_mode(0o755)));
|
||||
|
||||
// Support libraries were uploaded to `work` earlier, so make sure that's
|
||||
// in `LD_LIBRARY_PATH`. Also include our own current dir which may have
|
||||
// had some libs uploaded.
|
||||
cmd.env("LD_LIBRARY_PATH",
|
||||
format!("{}:{}", work.display(), path.display()));
|
||||
|
||||
// Spawn the child and ferry over stdout/stderr to the socket in a framed
|
||||
// fashion (poor man's style)
|
||||
let mut child = t!(cmd.stdin(Stdio::null())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn());
|
||||
drop(lock);
|
||||
let mut stdout = child.stdout.take().unwrap();
|
||||
let mut stderr = child.stderr.take().unwrap();
|
||||
let socket = Arc::new(Mutex::new(reader.into_inner()));
|
||||
let socket2 = socket.clone();
|
||||
let thread = thread::spawn(move || my_copy(&mut stdout, 0, &*socket2));
|
||||
my_copy(&mut stderr, 1, &*socket);
|
||||
thread.join().unwrap();
|
||||
|
||||
// Finally send over the exit status.
|
||||
let status = t!(child.wait());
|
||||
let (which, code) = match status.code() {
|
||||
Some(n) => (0, n),
|
||||
None => (1, status.signal().unwrap()),
|
||||
};
|
||||
t!(socket.lock().unwrap().write_all(&[
|
||||
which,
|
||||
(code >> 24) as u8,
|
||||
(code >> 16) as u8,
|
||||
(code >> 8) as u8,
|
||||
(code >> 0) as u8,
|
||||
]));
|
||||
}
|
||||
|
||||
fn my_copy(src: &mut Read, which: u8, dst: &Mutex<Write>) {
|
||||
let mut b = [0; 1024];
|
||||
loop {
|
||||
let n = t!(src.read(&mut b));
|
||||
let mut dst = dst.lock().unwrap();
|
||||
t!(dst.write_all(&[
|
||||
which,
|
||||
(n >> 24) as u8,
|
||||
(n >> 16) as u8,
|
||||
(n >> 8) as u8,
|
||||
(n >> 0) as u8,
|
||||
]));
|
||||
if n > 0 {
|
||||
t!(dst.write_all(&b[..n]));
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_u32(r: &mut Read) -> u32 {
|
||||
let mut len = [0; 4];
|
||||
t!(r.read_exact(&mut len));
|
||||
((len[0] as u32) << 24) |
|
||||
((len[1] as u32) << 16) |
|
||||
((len[2] as u32) << 8) |
|
||||
((len[3] as u32) << 0)
|
||||
}
|
Loading…
Reference in New Issue
Block a user