Merge "Add crosvm to com.android.virt"
diff --git a/tests/hostside/Android.bp b/tests/hostside/Android.bp
new file mode 100644
index 0000000..3f04a78
--- /dev/null
+++ b/tests/hostside/Android.bp
@@ -0,0 +1,92 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+kernel_version = "5.4"
+
+kernel_target_stem = ":kernel_prebuilts-" + kernel_version
+vendor_ramdisk_target_stem = ":cf_prebuilts_initramfs-" + kernel_version
+
+// JAR containing all virtualization host-side tests.
+java_test_host {
+    name: "VirtualizationHostTestCases",
+    srcs: ["java/**/*.java"],
+    test_suites: ["device-tests"],
+    libs: ["tradefed"],
+    data: [
+        ":virt_hostside_tests_kernel",
+        ":virt_hostside_tests_initramfs-arm64",
+        ":virt_hostside_tests_initramfs-x86_64",
+    ],
+    required: [
+        "virt_hostside_tests_vsock_server",
+    ],
+}
+
+// Give kernel images unique file names.
+genrule {
+    name: "virt_hostside_tests_kernel",
+    srcs: [
+        kernel_target_stem + "-arm64",
+        kernel_target_stem + "-x86_64",
+    ],
+    out: [
+        "virt_hostside_tests_kernel-arm64",
+        "virt_hostside_tests_kernel-x86_64",
+    ],
+    tool_files: ["scripts/place_files.sh"],
+    cmd: "$(location scripts/place_files.sh) $(in) -- $(out)",
+}
+
+// Ramdisk containing /init and test binaries/resources needed inside guest.
+genrule {
+    name: "virt_hostside_tests_initramfs_base",
+    tools: [
+        "mkbootfs",
+        "lz4",
+    ],
+    tool_files: ["scripts/place_files.sh"],
+    out: ["initramfs.lz4"],
+    srcs: [
+        ":virt_hostside_tests_guest_init",
+        ":virt_hostside_tests_vsock_client",
+    ],
+    cmd: "$(location scripts/place_files.sh) $(in) -- " +
+        "$(genDir)/root/init " +
+        "$(genDir)/root/bin/vsock_client " +
+        "&& $(location mkbootfs) $(genDir)/root | $(location lz4) -fq - $(out)",
+}
+
+// Default rule for producing a combined base + vendor ramdisk.
+genrule_defaults {
+    name: "virt_hostside_tests_initramfs_concat",
+    srcs: [":virt_hostside_tests_initramfs_base"],
+    tools: ["lz4"],
+    cmd: "cat $(in) | $(location lz4) -dfq - $(out)",
+}
+
+// Combined base + vendor ramdisk for arm64.
+genrule {
+    name: "virt_hostside_tests_initramfs-arm64",
+    defaults: ["virt_hostside_tests_initramfs_concat"],
+    srcs: [vendor_ramdisk_target_stem + "-arm64"],
+    out: ["virt_hostside_tests_initramfs-arm64"],
+}
+
+// Combined base + vendor ramdisk for x86_64.
+genrule {
+    name: "virt_hostside_tests_initramfs-x86_64",
+    defaults: ["virt_hostside_tests_initramfs_concat"],
+    srcs: [vendor_ramdisk_target_stem + "-x86_64"],
+    out: ["virt_hostside_tests_initramfs-x86_64"],
+}
diff --git a/tests/hostside/AndroidTest.xml b/tests/hostside/AndroidTest.xml
new file mode 100644
index 0000000..1fd86ef
--- /dev/null
+++ b/tests/hostside/AndroidTest.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<configuration description="Config for Virtualization host tests">
+
+    <!-- Basic checks that the device has all the prerequisites. -->
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="throw-if-cmd-fail" value="true" />
+        <!-- Kernel has KVM enabled. -->
+        <option name="run-command" value="ls /dev/kvm" />
+        <!-- Kernel has vhost-vsock enabled. -->
+        <option name="run-command" value="ls /dev/vhost-vsock" />
+        <!-- CrosVM is installed. -->
+        <option name="run-command" value="which crosvm" />
+    </target_preparer>
+
+    <!-- Push test binaries to the device. -->
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="abort-on-push-failure" value="true" />
+        <option name="push-file" key="virt_hostside_tests_kernel-arm64" value="/data/local/tmp/virt-test/arm64/kernel" />
+        <option name="push-file" key="virt_hostside_tests_initramfs-arm64" value="/data/local/tmp/virt-test/arm64/initramfs" />
+        <option name="push-file" key="virt_hostside_tests_kernel-x86_64" value="/data/local/tmp/virt-test/x86_64/kernel" />
+        <option name="push-file" key="virt_hostside_tests_initramfs-x86_64" value="/data/local/tmp/virt-test/x86_64/initramfs" />
+        <option name="push-file" key="virt_hostside_tests_vsock_server" value="/data/local/tmp/virt-test" />
+    </target_preparer>
+
+    <!-- Root currently needed to run CrosVM.
+         TODO: Give sufficient permissions to the adb shell user (b/171240450). -->
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="VirtualizationHostTestCases.jar" />
+        <option name="runtime-hint" value="2m" />
+    </test>
+</configuration>
diff --git a/tests/hostside/java/android/virt/test/VirtTestCase.java b/tests/hostside/java/android/virt/test/VirtTestCase.java
new file mode 100644
index 0000000..7ba6409
--- /dev/null
+++ b/tests/hostside/java/android/virt/test/VirtTestCase.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.virt.test;
+
+import static org.junit.Assert.*;
+
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IAbi;
+import com.android.tradefed.testtype.IAbiReceiver;
+
+import org.junit.Before;
+
+import java.util.ArrayList;
+
+public abstract class VirtTestCase extends DeviceTestCase implements IAbiReceiver {
+
+    private static final String DEVICE_DIR = "/data/local/tmp/virt-test";
+
+    private static final int CID_RESERVED = 2;
+
+    private IAbi mAbi;
+
+    @Before
+    public void setUp() throws Exception {
+        getDevice().waitForDeviceAvailable();
+    }
+
+    private String getAbiName() {
+        String name = mAbi.getName();
+        if ("arm64-v8a".equals(name)) {
+            name = "arm64";
+        }
+        return name;
+    }
+
+    protected String getDevicePathForTestBinary(String targetName) throws Exception {
+        String path = String.format("%s/%s/%s", DEVICE_DIR, getAbiName(), targetName);
+        if (!getDevice().doesFileExist(path)) {
+            throw new IllegalArgumentException(String.format(
+                    "Binary for target %s not found on device at \"%s\"", targetName, path));
+        }
+        return path;
+    }
+
+    protected static String createCommand(String prog, Object... args) {
+        ArrayList<String> strings = new ArrayList<>();
+        strings.add(prog);
+        for (Object arg : args) {
+            strings.add(arg.toString());
+        }
+        for (String str : strings) {
+            if (str.indexOf(' ') != -1) {
+                throw new IllegalArgumentException("TODO: implement quotes around arguments");
+            } else if (str.indexOf('\'') != -1) {
+                throw new IllegalArgumentException("TODO: implement escaping arguments");
+            }
+        }
+        return String.join(" ", strings);
+    }
+
+    protected String getVmCommand(String guestCmd, Integer cid) throws Exception {
+        ArrayList<String> cmd = new ArrayList<>();
+
+        cmd.add("crosvm");
+        cmd.add("run");
+
+        cmd.add("--disable-sandbox");
+
+        if (cid != null) {
+            if (cid > CID_RESERVED) {
+                cmd.add("--cid");
+                cmd.add(cid.toString());
+            } else {
+                throw new IllegalArgumentException("Invalid CID " + cid);
+            }
+        }
+
+        cmd.add("--initrd");
+        cmd.add(getDevicePathForTestBinary("initramfs"));
+
+        cmd.add("--params");
+        cmd.add(String.format("'%s'", guestCmd));
+
+        cmd.add(getDevicePathForTestBinary("kernel"));
+
+        return String.join(" ", cmd);
+    }
+
+    @Override
+    public void setAbi(IAbi abi) {
+        mAbi = abi;
+    }
+}
diff --git a/tests/hostside/java/android/virt/test/VsockTest.java b/tests/hostside/java/android/virt/test/VsockTest.java
new file mode 100644
index 0000000..c82db77
--- /dev/null
+++ b/tests/hostside/java/android/virt/test/VsockTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.virt.test;
+
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+import org.junit.Test;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+public class VsockTest extends VirtTestCase {
+    private static final long     TIMEOUT = 2L;
+    private static final TimeUnit TIMEOUT_UNIT = TimeUnit.MINUTES;
+    private static final int      RETRIES = 0;
+
+    private static final Integer  HOST_CID = 2;
+    private static final Integer  GUEST_CID = 42;
+    private static final Integer  GUEST_PORT = 45678;
+    private static final String   TEST_MESSAGE = "HelloWorld";
+
+    private static final String   CLIENT_PATH = "bin/vsock_client";
+    private static final String   SERVER_TARGET = "virt_hostside_tests_vsock_server";
+
+    @Test
+    public void testVsockServer() throws Exception {
+        ExecutorService executor = Executors.newFixedThreadPool(2);
+
+        final String serverPath = getDevicePathForTestBinary(SERVER_TARGET);
+        final String serverCmd = createCommand(serverPath, GUEST_PORT);
+        final String clientCmd = createCommand(CLIENT_PATH, HOST_CID, GUEST_PORT, TEST_MESSAGE);
+        final String vmCmd = getVmCommand(clientCmd, GUEST_CID);
+
+        // Start server in Android that listens for vsock connections.
+        // It will receive a message from a client in the guest VM.
+        Future<?> serverTask = executor.submit(() -> {
+            CommandResult res = getDevice().executeShellV2Command(
+                    serverCmd, TIMEOUT, TIMEOUT_UNIT, RETRIES);
+            assertEquals(TEST_MESSAGE, res.getStdout().trim());
+            return null;
+        });
+
+        // Run VM that will connect to the server and send a message to it.
+        Future<?> vmTask = executor.submit(() -> {
+            CommandResult res = getDevice().executeShellV2Command(
+                    vmCmd, TIMEOUT, TIMEOUT_UNIT, RETRIES);
+            CLog.d(res.getStdout()); // print VMM output into host_log
+            assertEquals(CommandStatus.SUCCESS, res.getStatus());
+            return null;
+        });
+
+        // Wait for the VMM to finish sending the message.
+        try {
+            vmTask.get(TIMEOUT, TIMEOUT_UNIT);
+        } catch (Throwable ex) {
+            // The VMM either exited with a non-zero code or it timed out.
+            // Kill the server process, the test has failed.
+            // Note: executeShellV2Command cannot be interrupted. This will wait
+            // until `serverTask` times out.
+            executor.shutdownNow();
+            throw ex;
+        }
+
+        // Wait for the server to finish processing the message.
+        serverTask.get(TIMEOUT, TIMEOUT_UNIT);
+    }
+}
diff --git a/tests/hostside/native/init/Android.bp b/tests/hostside/native/init/Android.bp
new file mode 100644
index 0000000..a98948b
--- /dev/null
+++ b/tests/hostside/native/init/Android.bp
@@ -0,0 +1,25 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_binary {
+    name: "virt_hostside_tests_guest_init",
+    srcs: ["main.cc"],
+    static_executable: true,
+    installable: false,
+    static_libs: [
+        "libbase",
+        "liblog",
+        "libmodprobe",
+    ],
+}
diff --git a/tests/hostside/native/init/main.cc b/tests/hostside/native/init/main.cc
new file mode 100644
index 0000000..fe4cc80
--- /dev/null
+++ b/tests/hostside/native/init/main.cc
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <dirent.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/utsname.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <cstdlib>
+#include <filesystem>
+#include <fstream>
+#include <iostream>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <modprobe/modprobe.h>
+
+#include "android-base/logging.h"
+#include "android-base/strings.h"
+
+using namespace android::base;
+
+static constexpr const char MODULE_BASE_DIR[] = "/lib/modules";
+
+bool LoadKernelModules() {
+    struct utsname uts;
+    if (uname(&uts)) {
+        LOG(ERROR) << "Failed to get kernel version.";
+        return false;
+    }
+    int major, minor;
+    if (sscanf(uts.release, "%d.%d", &major, &minor) != 2) {
+        LOG(ERROR) << "Failed to parse kernel version " << uts.release;
+        return false;
+    }
+
+    std::unique_ptr<DIR, decltype(&closedir)> base_dir(opendir(MODULE_BASE_DIR), closedir);
+    if (!base_dir) {
+        LOG(ERROR) << "Unable to open /lib/modules, skipping module loading.";
+        return false;
+    }
+    dirent* entry;
+    std::vector<std::string> module_dirs;
+    while ((entry = readdir(base_dir.get()))) {
+        if (entry->d_type != DT_DIR) {
+            continue;
+        }
+        int dir_major, dir_minor;
+        if (sscanf(entry->d_name, "%d.%d", &dir_major, &dir_minor) != 2 || dir_major != major ||
+            dir_minor != minor) {
+            continue;
+        }
+        module_dirs.emplace_back(entry->d_name);
+    }
+
+    // Sort the directories so they are iterated over during module loading
+    // in a consistent order. Alphabetical sorting is fine here because the
+    // kernel version at the beginning of the directory name must match the
+    // current kernel version, so the sort only applies to a label that
+    // follows the kernel version, for example /lib/modules/5.4 vs.
+    // /lib/modules/5.4-gki.
+    std::sort(module_dirs.begin(), module_dirs.end());
+
+    for (const auto& module_dir : module_dirs) {
+        std::string dir_path(MODULE_BASE_DIR);
+        dir_path.append("/");
+        dir_path.append(module_dir);
+        Modprobe m({dir_path});
+        bool retval = m.LoadListedModules();
+        int modules_loaded = m.GetModuleCount();
+        if (modules_loaded > 0) {
+            return retval;
+        }
+    }
+
+    Modprobe m({MODULE_BASE_DIR});
+    bool retval = m.LoadListedModules();
+    int modules_loaded = m.GetModuleCount();
+    if (modules_loaded > 0) {
+        return retval;
+    }
+
+    return true;
+}
+
+int main(int argc, const char* argv[]) {
+    SetLogger(StderrLogger);
+
+    LOG(INFO) << "Guest VM init process";
+    LOG(INFO) << "Command line: " << Join(std::vector(argv, argv + argc), " ");
+
+    if (clearenv() != EXIT_SUCCESS) {
+        PLOG(ERROR) << "clearenv";
+        return EXIT_FAILURE;
+    }
+
+    LOG(INFO) << "Loading kernel modules...";
+    if (!LoadKernelModules()) {
+        LOG(ERROR) << "LoadKernelModules failed";
+        return EXIT_FAILURE;
+    }
+
+    LOG(INFO) << "Executing test binary " << argv[1] << "...";
+    execv(argv[1], (char**)(argv + 1));
+
+    PLOG(ERROR) << "execv";
+    return EXIT_FAILURE;
+}
diff --git a/tests/hostside/native/vsock/Android.bp b/tests/hostside/native/vsock/Android.bp
new file mode 100644
index 0000000..cbee98d
--- /dev/null
+++ b/tests/hostside/native/vsock/Android.bp
@@ -0,0 +1,35 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_test {
+    name: "virt_hostside_tests_vsock_server",
+    srcs: ["server.cc"],
+    static_libs: [
+        "libbase",
+        "liblog",
+    ],
+    static_executable: true,
+    test_suites: ["device-tests"],
+}
+
+cc_binary {
+    name: "virt_hostside_tests_vsock_client",
+    srcs: ["client.cc"],
+    static_libs: [
+        "libbase",
+        "liblog",
+    ],
+    static_executable: true,
+    installable: false,
+}
diff --git a/tests/hostside/native/vsock/client.cc b/tests/hostside/native/vsock/client.cc
new file mode 100644
index 0000000..7a72e11
--- /dev/null
+++ b/tests/hostside/native/vsock/client.cc
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <sys/socket.h>
+
+// Needs to be included after sys/socket.h
+#include <linux/vm_sockets.h>
+
+#include <iostream>
+
+#include "android-base/file.h"
+#include "android-base/logging.h"
+#include "android-base/parseint.h"
+#include "android-base/unique_fd.h"
+
+using namespace android::base;
+
+int main(int argc, const char *argv[]) {
+    SetLogger(StderrLogger);
+
+    unsigned int cid, port;
+    if (argc != 4 || !ParseUint(argv[1], &cid) || !ParseUint(argv[2], &port)) {
+        LOG(ERROR) << "Usage: " << argv[0] << " <cid> <port> <msg>";
+        return EXIT_FAILURE;
+    }
+    std::string msg(argv[3]);
+
+    unique_fd fd(TEMP_FAILURE_RETRY(socket(AF_VSOCK, SOCK_STREAM, 0)));
+    if (fd < 0) {
+        PLOG(ERROR) << "socket";
+        return EXIT_FAILURE;
+    }
+
+    struct sockaddr_vm sa = (struct sockaddr_vm){
+            .svm_family = AF_VSOCK,
+            .svm_port = port,
+            .svm_cid = cid,
+    };
+
+    LOG(INFO) << "Connecting to CID " << cid << " on port " << port << "...";
+    int ret = TEMP_FAILURE_RETRY(connect(fd, (struct sockaddr *)&sa, sizeof(sa)));
+    if (ret < 0) {
+        PLOG(ERROR) << "connect";
+        return EXIT_FAILURE;
+    }
+
+    LOG(INFO) << "Sending message to server...";
+    if (!WriteStringToFd(msg, fd)) {
+        PLOG(ERROR) << "WriteStringToFd";
+        return EXIT_FAILURE;
+    }
+
+    LOG(INFO) << "Exiting...";
+    return EXIT_SUCCESS;
+}
diff --git a/tests/hostside/native/vsock/server.cc b/tests/hostside/native/vsock/server.cc
new file mode 100644
index 0000000..d4a99d2
--- /dev/null
+++ b/tests/hostside/native/vsock/server.cc
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <sys/socket.h>
+#include <unistd.h>
+
+// Needs to be included after sys/socket.h
+#include <linux/vm_sockets.h>
+
+#include <iostream>
+
+#include "android-base/file.h"
+#include "android-base/logging.h"
+#include "android-base/parseint.h"
+#include "android-base/unique_fd.h"
+
+using namespace android::base;
+
+int main(int argc, const char *argv[]) {
+    unsigned int port;
+    if (argc != 2 || !ParseUint(argv[1], &port)) {
+        LOG(ERROR) << "Usage: " << argv[0] << " <port>";
+        return EXIT_FAILURE;
+    }
+
+    unique_fd server_fd(TEMP_FAILURE_RETRY(socket(AF_VSOCK, SOCK_STREAM, 0)));
+    if (server_fd < 0) {
+        PLOG(ERROR) << "socket";
+        return EXIT_FAILURE;
+    }
+
+    struct sockaddr_vm server_sa = (struct sockaddr_vm){
+            .svm_family = AF_VSOCK,
+            .svm_port = port,
+            .svm_cid = VMADDR_CID_ANY,
+    };
+
+    int ret = TEMP_FAILURE_RETRY(bind(server_fd, (struct sockaddr *)&server_sa, sizeof(server_sa)));
+    if (ret != 0) {
+        PLOG(ERROR) << "bind";
+        return EXIT_FAILURE;
+    }
+
+    LOG(INFO) << "Listening on port " << port << "...";
+    ret = TEMP_FAILURE_RETRY(listen(server_fd, 1));
+    if (ret != 0) {
+        PLOG(ERROR) << "listen";
+        return EXIT_FAILURE;
+    }
+
+    LOG(INFO) << "Accepting connection...";
+    struct sockaddr_vm client_sa;
+    socklen_t client_sa_len = sizeof(client_sa);
+    unique_fd client_fd(
+            TEMP_FAILURE_RETRY(accept(server_fd, (struct sockaddr *)&client_sa, &client_sa_len)));
+    if (client_fd < 0) {
+        PLOG(ERROR) << "accept";
+        return EXIT_FAILURE;
+    }
+    LOG(INFO) << "Connection from CID " << client_sa.svm_cid << " on port " << client_sa.svm_port;
+
+    LOG(INFO) << "Reading message from the client...";
+    std::string msg;
+    if (!ReadFdToString(client_fd, &msg)) {
+        PLOG(ERROR) << "ReadFdToString";
+        return EXIT_FAILURE;
+    }
+
+    // Print the received message to stdout.
+    std::cout << msg << std::endl;
+
+    LOG(INFO) << "Exiting...";
+    return EXIT_SUCCESS;
+}
diff --git a/tests/hostside/scripts/place_files.sh b/tests/hostside/scripts/place_files.sh
new file mode 100755
index 0000000..f0a9603
--- /dev/null
+++ b/tests/hostside/scripts/place_files.sh
@@ -0,0 +1,70 @@
+#!/usr/bin/env bash
+##
+## Copyright (C) 2020 The Android Open Source Project
+##
+## Licensed under the Apache License, Version 2.0 (the "License");
+## you may not use this file except in compliance with the License.
+## You may obtain a copy of the License at
+##
+##      http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+##
+
+set -euo pipefail
+
+# Wrapper around 'expr' that handles the fact that it returns code 1
+# if the result is zero/null. That messes with 'set -e'.
+function expr {
+	eval 'val=$($(which expr) $@); ret=$?'
+	if [ "$ret" != 0 -a "$ret" != 1 ]
+	then
+		return $ret
+	fi
+	echo "$val"
+}
+
+ARGS=( "$@" )
+NUM_ARGS=${#ARGS[@]}
+
+POS_DIVIDER=-1
+for i in $(seq 0 $(expr $NUM_ARGS - 1))
+do
+	if [ "${ARGS[$i]}" == "--" ]
+	then
+		if [ "$POS_DIVIDER" -eq -1 ]
+		then
+			POS_DIVIDER=$i
+		else
+			echo "Multiple dividers in command line inputs" 1>&2
+			exit 1
+		fi
+	fi
+done
+
+if [ "$POS_DIVIDER" -eq -1 ]
+then
+	echo "Divider expected among command line inputs" 1>&2
+	exit 1
+fi
+
+NUM_INPUT=${POS_DIVIDER}
+NUM_OUTPUT=$(expr $NUM_ARGS - $POS_DIVIDER - 1)
+
+if [ "$NUM_INPUT" -ne "$NUM_OUTPUT" ]
+then
+	echo "Number of inputs does not match number of outputs" 1>&2
+	exit 1
+fi
+
+for i in $(seq 0 $(expr $NUM_INPUT - 1))
+do
+	INPUT="${ARGS[$i]}"
+	OUTPUT="${ARGS[$NUM_INPUT + $i + 1]}"
+	mkdir -p "$(dirname "$OUTPUT")"
+	cp "$INPUT" "$OUTPUT"
+done