Rename VirtManager to VirtualizationService.

Bug: 188042280
Test: atest VirtualizationTestCases
Change-Id: I15f3f91e464f52d1b1fd47b1290846b1d21fa665
diff --git a/virtualizationservice/.gitignore b/virtualizationservice/.gitignore
new file mode 100644
index 0000000..96ef6c0
--- /dev/null
+++ b/virtualizationservice/.gitignore
@@ -0,0 +1,2 @@
+/target
+Cargo.lock
diff --git a/virtualizationservice/Android.bp b/virtualizationservice/Android.bp
new file mode 100644
index 0000000..2c44200
--- /dev/null
+++ b/virtualizationservice/Android.bp
@@ -0,0 +1,20 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_binary {
+    name: "virtualizationservice",
+    crate_name: "virtualizationservice",
+    srcs: ["src/main.rs"],
+    edition: "2018",
+    prefer_rlib: true,
+    rustlibs: [
+        "android.system.virtualizationservice-rust",
+        "libandroid_logger",
+        "libanyhow",
+        "libcommand_fds",
+        "liblog_rust",
+        "libshared_child",
+    ],
+    apex_available: ["com.android.virt"],
+}
diff --git a/virtualizationservice/aidl/Android.bp b/virtualizationservice/aidl/Android.bp
new file mode 100644
index 0000000..89cfdef
--- /dev/null
+++ b/virtualizationservice/aidl/Android.bp
@@ -0,0 +1,22 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+aidl_interface {
+    name: "android.system.virtualizationservice",
+    srcs: ["**/*.aidl"],
+    // TODO(qwandor): Consider changing this to false, unless we have a Java wrapper.
+    unstable: true,
+    backend: {
+        java: {
+            sdk_version: "module_current",
+        },
+        cpp: {
+            enabled: true,
+        },
+        rust: {
+            enabled: true,
+            apex_available: ["com.android.virt"],
+        },
+    },
+}
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/DiskImage.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/DiskImage.aidl
new file mode 100644
index 0000000..6bc747e
--- /dev/null
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/DiskImage.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2021 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.system.virtualizationservice;
+
+/** A disk image to be made available to the VM. */
+parcelable DiskImage {
+    /** The disk image. */
+    ParcelFileDescriptor image;
+
+    /** Whether this disk should be writable by the VM. */
+    boolean writable;
+}
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachine.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachine.aidl
new file mode 100644
index 0000000..e864414
--- /dev/null
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachine.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2021 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.system.virtualizationservice;
+
+import android.system.virtualizationservice.IVirtualMachineCallback;
+
+interface IVirtualMachine {
+    /** Get the CID allocated to the VM. */
+    int getCid();
+
+    /** Returns true if the VM is still running, or false if it has exited for any reason. */
+    boolean isRunning();
+
+    /**
+     * Register a Binder object to get callbacks when the state of the VM changes, such as if it
+     * dies.
+     */
+    void registerCallback(IVirtualMachineCallback callback);
+}
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl
new file mode 100644
index 0000000..10ef31b
--- /dev/null
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2021 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.system.virtualizationservice;
+
+import android.system.virtualizationservice.IVirtualMachine;
+
+/**
+ * An object which a client may register with the VirtualizationService to get callbacks about the
+ * state of a particular VM.
+ */
+oneway interface IVirtualMachineCallback {
+    /**
+     * Called when the VM dies.
+     *
+     * Note that this will not be called if the VirtualizationService itself dies, so you should
+     * also use `link_to_death` to handle that.
+     */
+    void onDied(int cid);
+}
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
new file mode 100644
index 0000000..7974e71
--- /dev/null
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2021 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.system.virtualizationservice;
+
+import android.system.virtualizationservice.IVirtualMachine;
+import android.system.virtualizationservice.VirtualMachineConfig;
+import android.system.virtualizationservice.VirtualMachineDebugInfo;
+
+interface IVirtualizationService {
+    /**
+     * Start the VM with the given config file, and return a handle to it. If `logFd` is provided
+     * then console logs from the VM will be sent to it.
+     */
+    IVirtualMachine startVm(
+            in VirtualMachineConfig config, in @nullable ParcelFileDescriptor logFd);
+
+    /**
+     * Get a list of all currently running VMs. This method is only intended for debug purposes,
+     * and as such is only permitted from the shell user.
+     */
+    VirtualMachineDebugInfo[] debugListVms();
+
+    /**
+     * Hold a strong reference to a VM in VirtualizationService. This method is only intended for
+     * debug purposes, and as such is only permitted from the shell user.
+     */
+    void debugHoldVmRef(IVirtualMachine vm);
+
+    /**
+     * Drop reference to a VM that is being held by VirtualizationService. Returns the reference if
+     * VM was found and null otherwise. This method is only intended for debug purposes, and as such
+     * is only permitted from the shell user.
+     */
+    @nullable IVirtualMachine debugDropVmRef(int cid);
+}
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineConfig.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineConfig.aidl
new file mode 100644
index 0000000..6ca9cc7
--- /dev/null
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineConfig.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2021 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.system.virtualizationservice;
+
+import android.system.virtualizationservice.DiskImage;
+
+/** Configuration for running a VM. */
+parcelable VirtualMachineConfig {
+    /** The kernel image, if any. */
+    @nullable ParcelFileDescriptor kernel;
+
+    /** The initial ramdisk for the kernel, if any. */
+    @nullable ParcelFileDescriptor initrd;
+
+    /**
+     * Parameters to pass to the kernel. As far as the VMM and boot protocol are concerned this is
+     * just a string, but typically it will contain multiple parameters separated by spaces.
+     */
+    @nullable String params;
+
+    /**
+     * The bootloader to use. If this is supplied then the kernel and initrd must not be supplied;
+     * the bootloader is instead responsibly for loading the kernel from one of the disks.
+     */
+    @nullable ParcelFileDescriptor bootloader;
+
+    /** Disk images to be made available to the VM. */
+    DiskImage[] disks;
+}
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineDebugInfo.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineDebugInfo.aidl
new file mode 100644
index 0000000..d20d91d
--- /dev/null
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineDebugInfo.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2021 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.system.virtualizationservice;
+
+/** Information about a running VM, for debug purposes only. */
+parcelable VirtualMachineDebugInfo {
+    /** The CID assigned to the VM. */
+    int cid;
+
+    /** The UID of the process which requested the VM. */
+    int requesterUid;
+
+    /** The SID of the process which requested the VM. */
+    String requesterSid;
+
+    /**
+     * The PID of the process which requested the VM. Note that this process may no longer exist and
+     * the PID may have been reused for a different process, so this should not be trusted.
+     */
+    int requesterPid;
+
+    /** Whether the VM is still running. */
+    boolean running;
+}
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
new file mode 100644
index 0000000..ef973d1
--- /dev/null
+++ b/virtualizationservice/src/aidl.rs
@@ -0,0 +1,267 @@
+// Copyright 2021, 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.
+
+//! Implementation of the AIDL interface of the VirtualizationService.
+
+use crate::crosvm::VmInstance;
+use crate::{Cid, FIRST_GUEST_CID};
+use android_system_virtualizationservice::aidl::android::system::virtualizationservice::IVirtualizationService::IVirtualizationService;
+use android_system_virtualizationservice::aidl::android::system::virtualizationservice::IVirtualMachine::{
+    BnVirtualMachine, IVirtualMachine,
+};
+use android_system_virtualizationservice::aidl::android::system::virtualizationservice::IVirtualMachineCallback::IVirtualMachineCallback;
+use android_system_virtualizationservice::aidl::android::system::virtualizationservice::VirtualMachineConfig::VirtualMachineConfig;
+use android_system_virtualizationservice::aidl::android::system::virtualizationservice::VirtualMachineDebugInfo::VirtualMachineDebugInfo;
+use android_system_virtualizationservice::binder::{
+    self, BinderFeatures, Interface, ParcelFileDescriptor, StatusCode, Strong, ThreadState,
+};
+use log::{debug, error};
+use std::sync::{Arc, Mutex, Weak};
+
+pub const BINDER_SERVICE_IDENTIFIER: &str = "android.system.virtualizationservice";
+
+// TODO(qwandor): Use PermissionController once it is available to Rust.
+/// Only processes running with one of these UIDs are allowed to call debug methods.
+const DEBUG_ALLOWED_UIDS: [u32; 2] = [0, 2000];
+
+/// Implementation of `IVirtualizationService`, the entry point of the AIDL service.
+#[derive(Debug, Default)]
+pub struct VirtualizationService {
+    state: Mutex<State>,
+}
+
+impl Interface for VirtualizationService {}
+
+impl IVirtualizationService for VirtualizationService {
+    /// Create and start a new VM with the given configuration, assigning it the next available CID.
+    ///
+    /// Returns a binder `IVirtualMachine` object referring to it, as a handle for the client.
+    fn startVm(
+        &self,
+        config: &VirtualMachineConfig,
+        log_fd: Option<&ParcelFileDescriptor>,
+    ) -> binder::Result<Strong<dyn IVirtualMachine>> {
+        let state = &mut *self.state.lock().unwrap();
+        let log_fd = log_fd
+            .map(|fd| fd.as_ref().try_clone().map_err(|_| StatusCode::UNKNOWN_ERROR))
+            .transpose()?;
+        let requester_uid = ThreadState::get_calling_uid();
+        let requester_sid = ThreadState::with_calling_sid(|sid| {
+            if let Some(sid) = sid {
+                match sid.to_str() {
+                    Ok(sid) => Ok(sid.to_owned()),
+                    Err(e) => {
+                        error!("SID was not valid UTF-8: {:?}", e);
+                        Err(StatusCode::BAD_VALUE)
+                    }
+                }
+            } else {
+                error!("Missing SID on startVm");
+                Err(StatusCode::UNKNOWN_ERROR)
+            }
+        })?;
+        let requester_debug_pid = ThreadState::get_calling_pid();
+        let cid = state.allocate_cid()?;
+        let instance = VmInstance::start(
+            config,
+            cid,
+            log_fd,
+            requester_uid,
+            requester_sid,
+            requester_debug_pid,
+        )
+        .map_err(|e| {
+            error!("Failed to start VM with config {:?}: {:?}", config, e);
+            StatusCode::UNKNOWN_ERROR
+        })?;
+        state.add_vm(Arc::downgrade(&instance));
+        Ok(VirtualMachine::create(instance))
+    }
+
+    /// Get a list of all currently running VMs. This method is only intended for debug purposes,
+    /// and as such is only permitted from the shell user.
+    fn debugListVms(&self) -> binder::Result<Vec<VirtualMachineDebugInfo>> {
+        if !debug_access_allowed() {
+            return Err(StatusCode::PERMISSION_DENIED.into());
+        }
+
+        let state = &mut *self.state.lock().unwrap();
+        let vms = state.vms();
+        let cids = vms
+            .into_iter()
+            .map(|vm| VirtualMachineDebugInfo {
+                cid: vm.cid as i32,
+                requesterUid: vm.requester_uid as i32,
+                requesterSid: vm.requester_sid.clone(),
+                requesterPid: vm.requester_debug_pid,
+                running: vm.running(),
+            })
+            .collect();
+        Ok(cids)
+    }
+
+    /// Hold a strong reference to a VM in VirtualizationService. This method is only intended for
+    /// debug purposes, and as such is only permitted from the shell user.
+    fn debugHoldVmRef(&self, vmref: &Strong<dyn IVirtualMachine>) -> binder::Result<()> {
+        if !debug_access_allowed() {
+            return Err(StatusCode::PERMISSION_DENIED.into());
+        }
+
+        let state = &mut *self.state.lock().unwrap();
+        state.debug_hold_vm(vmref.clone());
+        Ok(())
+    }
+
+    /// Drop reference to a VM that is being held by VirtualizationService. Returns the reference if
+    /// the VM was found and None otherwise. This method is only intended for debug purposes, and as
+    /// such is only permitted from the shell user.
+    fn debugDropVmRef(&self, cid: i32) -> binder::Result<Option<Strong<dyn IVirtualMachine>>> {
+        if !debug_access_allowed() {
+            return Err(StatusCode::PERMISSION_DENIED.into());
+        }
+
+        let state = &mut *self.state.lock().unwrap();
+        Ok(state.debug_drop_vm(cid))
+    }
+}
+
+/// Check whether the caller of the current Binder method is allowed to call debug methods.
+fn debug_access_allowed() -> bool {
+    let uid = ThreadState::get_calling_uid();
+    log::trace!("Debug method call from UID {}.", uid);
+    DEBUG_ALLOWED_UIDS.contains(&uid)
+}
+
+/// Implementation of the AIDL `IVirtualMachine` interface. Used as a handle to a VM.
+#[derive(Debug)]
+struct VirtualMachine {
+    instance: Arc<VmInstance>,
+}
+
+impl VirtualMachine {
+    fn create(instance: Arc<VmInstance>) -> Strong<dyn IVirtualMachine> {
+        let binder = VirtualMachine { instance };
+        BnVirtualMachine::new_binder(binder, BinderFeatures::default())
+    }
+}
+
+impl Interface for VirtualMachine {}
+
+impl IVirtualMachine for VirtualMachine {
+    fn getCid(&self) -> binder::Result<i32> {
+        Ok(self.instance.cid as i32)
+    }
+
+    fn isRunning(&self) -> binder::Result<bool> {
+        Ok(self.instance.running())
+    }
+
+    fn registerCallback(
+        &self,
+        callback: &Strong<dyn IVirtualMachineCallback>,
+    ) -> binder::Result<()> {
+        // TODO: Should this give an error if the VM is already dead?
+        self.instance.callbacks.add(callback.clone());
+        Ok(())
+    }
+}
+
+impl Drop for VirtualMachine {
+    fn drop(&mut self) {
+        debug!("Dropping {:?}", self);
+        self.instance.kill();
+    }
+}
+
+/// A set of Binders to be called back in response to various events on the VM, such as when it
+/// dies.
+#[derive(Debug, Default)]
+pub struct VirtualMachineCallbacks(Mutex<Vec<Strong<dyn IVirtualMachineCallback>>>);
+
+impl VirtualMachineCallbacks {
+    /// Call all registered callbacks to say that the VM has died.
+    pub fn callback_on_died(&self, cid: Cid) {
+        let callbacks = &*self.0.lock().unwrap();
+        for callback in callbacks {
+            if let Err(e) = callback.onDied(cid as i32) {
+                error!("Error calling callback: {}", e);
+            }
+        }
+    }
+
+    /// Add a new callback to the set.
+    fn add(&self, callback: Strong<dyn IVirtualMachineCallback>) {
+        self.0.lock().unwrap().push(callback);
+    }
+}
+
+/// The mutable state of the VirtualizationService. There should only be one instance of this
+/// struct.
+#[derive(Debug)]
+struct State {
+    /// The next available unused CID.
+    next_cid: Cid,
+
+    /// The VMs which have been started. When VMs are started a weak reference is added to this list
+    /// while a strong reference is returned to the caller over Binder. Once all copies of the
+    /// Binder client are dropped the weak reference here will become invalid, and will be removed
+    /// from the list opportunistically the next time `add_vm` is called.
+    vms: Vec<Weak<VmInstance>>,
+
+    /// Vector of strong VM references held on behalf of users that cannot hold them themselves.
+    /// This is only used for debugging purposes.
+    debug_held_vms: Vec<Strong<dyn IVirtualMachine>>,
+}
+
+impl State {
+    /// Get a list of VMs which still have Binder references to them.
+    fn vms(&self) -> Vec<Arc<VmInstance>> {
+        // Attempt to upgrade the weak pointers to strong pointers.
+        self.vms.iter().filter_map(Weak::upgrade).collect()
+    }
+
+    /// Add a new VM to the list.
+    fn add_vm(&mut self, vm: Weak<VmInstance>) {
+        // Garbage collect any entries from the stored list which no longer exist.
+        self.vms.retain(|vm| vm.strong_count() > 0);
+
+        // Actually add the new VM.
+        self.vms.push(vm);
+    }
+
+    /// Store a strong VM reference.
+    fn debug_hold_vm(&mut self, vm: Strong<dyn IVirtualMachine>) {
+        self.debug_held_vms.push(vm);
+    }
+
+    /// Retrieve and remove a strong VM reference.
+    fn debug_drop_vm(&mut self, cid: i32) -> Option<Strong<dyn IVirtualMachine>> {
+        let pos = self.debug_held_vms.iter().position(|vm| vm.getCid() == Ok(cid))?;
+        Some(self.debug_held_vms.swap_remove(pos))
+    }
+
+    /// Get the next available CID, or an error if we have run out.
+    fn allocate_cid(&mut self) -> binder::Result<Cid> {
+        // TODO(qwandor): keep track of which CIDs are currently in use so that we can reuse them.
+        let cid = self.next_cid;
+        self.next_cid = self.next_cid.checked_add(1).ok_or(StatusCode::UNKNOWN_ERROR)?;
+        Ok(cid)
+    }
+}
+
+impl Default for State {
+    fn default() -> Self {
+        State { next_cid: FIRST_GUEST_CID, vms: vec![], debug_held_vms: vec![] }
+    }
+}
diff --git a/virtualizationservice/src/crosvm.rs b/virtualizationservice/src/crosvm.rs
new file mode 100644
index 0000000..552941d
--- /dev/null
+++ b/virtualizationservice/src/crosvm.rs
@@ -0,0 +1,196 @@
+// Copyright 2021, 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.
+
+//! Functions for running instances of `crosvm`.
+
+use crate::aidl::VirtualMachineCallbacks;
+use crate::Cid;
+use android_system_virtualizationservice::aidl::android::system::virtualizationservice::VirtualMachineConfig::VirtualMachineConfig;
+use anyhow::{bail, Context, Error};
+use command_fds::{CommandFdExt, FdMapping};
+use log::{debug, error, info};
+use shared_child::SharedChild;
+use std::fs::File;
+use std::os::unix::io::AsRawFd;
+use std::process::Command;
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::Arc;
+use std::thread;
+
+const CROSVM_PATH: &str = "/apex/com.android.virt/bin/crosvm";
+
+/// Information about a particular instance of a VM which is running.
+#[derive(Debug)]
+pub struct VmInstance {
+    /// The crosvm child process.
+    child: SharedChild,
+    /// The CID assigned to the VM for vsock communication.
+    pub cid: Cid,
+    /// The UID of the process which requested the VM.
+    pub requester_uid: u32,
+    /// The SID of the process which requested the VM.
+    pub requester_sid: String,
+    /// The PID of the process which requested the VM. Note that this process may no longer exist
+    /// and the PID may have been reused for a different process, so this should not be trusted.
+    pub requester_debug_pid: i32,
+    /// Whether the VM is still running.
+    running: AtomicBool,
+    /// Callbacks to clients of the VM.
+    pub callbacks: VirtualMachineCallbacks,
+}
+
+impl VmInstance {
+    /// Create a new `VmInstance` for the given process.
+    fn new(
+        child: SharedChild,
+        cid: Cid,
+        requester_uid: u32,
+        requester_sid: String,
+        requester_debug_pid: i32,
+    ) -> VmInstance {
+        VmInstance {
+            child,
+            cid,
+            requester_uid,
+            requester_sid,
+            requester_debug_pid,
+            running: AtomicBool::new(true),
+            callbacks: Default::default(),
+        }
+    }
+
+    /// Start an instance of `crosvm` to manage a new VM. The `crosvm` instance will be killed when
+    /// the `VmInstance` is dropped.
+    pub fn start(
+        config: &VirtualMachineConfig,
+        cid: Cid,
+        log_fd: Option<File>,
+        requester_uid: u32,
+        requester_sid: String,
+        requester_debug_pid: i32,
+    ) -> Result<Arc<VmInstance>, Error> {
+        let child = run_vm(config, cid, log_fd)?;
+        let instance = Arc::new(VmInstance::new(
+            child,
+            cid,
+            requester_uid,
+            requester_sid,
+            requester_debug_pid,
+        ));
+
+        let instance_clone = instance.clone();
+        thread::spawn(move || {
+            instance_clone.monitor();
+        });
+
+        Ok(instance)
+    }
+
+    /// Wait for the crosvm child process to finish, then mark the VM as no longer running and call
+    /// any callbacks.
+    fn monitor(&self) {
+        match self.child.wait() {
+            Err(e) => error!("Error waiting for crosvm instance to die: {}", e),
+            Ok(status) => info!("crosvm exited with status {}", status),
+        }
+        self.running.store(false, Ordering::Release);
+        self.callbacks.callback_on_died(self.cid);
+    }
+
+    /// Return whether `crosvm` is still running the VM.
+    pub fn running(&self) -> bool {
+        self.running.load(Ordering::Acquire)
+    }
+
+    /// Kill the crosvm instance.
+    pub fn kill(&self) {
+        // TODO: Talk to crosvm to shutdown cleanly.
+        if let Err(e) = self.child.kill() {
+            error!("Error killing crosvm instance: {}", e);
+        }
+    }
+}
+
+/// Start an instance of `crosvm` to manage a new VM.
+fn run_vm(
+    config: &VirtualMachineConfig,
+    cid: Cid,
+    log_fd: Option<File>,
+) -> Result<SharedChild, Error> {
+    validate_config(config)?;
+
+    let mut command = Command::new(CROSVM_PATH);
+    // TODO(qwandor): Remove --disable-sandbox.
+    command.arg("run").arg("--disable-sandbox").arg("--cid").arg(cid.to_string());
+
+    if let Some(log_fd) = log_fd {
+        command.stdout(log_fd);
+    } else {
+        // Ignore console output.
+        command.arg("--serial=type=sink");
+    }
+
+    // Keep track of what file descriptors should be mapped to the crosvm process.
+    let mut fd_mappings = vec![];
+
+    if let Some(bootloader) = &config.bootloader {
+        command.arg("--bios").arg(add_fd_mapping(&mut fd_mappings, bootloader.as_ref()));
+    }
+
+    if let Some(initrd) = &config.initrd {
+        command.arg("--initrd").arg(add_fd_mapping(&mut fd_mappings, initrd.as_ref()));
+    }
+
+    if let Some(params) = &config.params {
+        command.arg("--params").arg(params);
+    }
+
+    for disk in &config.disks {
+        command.arg(if disk.writable { "--rwdisk" } else { "--disk" }).arg(add_fd_mapping(
+            &mut fd_mappings,
+            // TODO(b/187187765): This shouldn't be an Option.
+            disk.image.as_ref().context("Invalid disk image file descriptor")?.as_ref(),
+        ));
+    }
+
+    if let Some(kernel) = &config.kernel {
+        command.arg(add_fd_mapping(&mut fd_mappings, kernel.as_ref()));
+    }
+
+    debug!("Setting mappings {:?}", fd_mappings);
+    command.fd_mappings(fd_mappings)?;
+
+    info!("Running {:?}", command);
+    let result = SharedChild::spawn(&mut command)?;
+    Ok(result)
+}
+
+/// Ensure that the configuration has a valid combination of fields set, or return an error if not.
+fn validate_config(config: &VirtualMachineConfig) -> Result<(), Error> {
+    if config.bootloader.is_none() && config.kernel.is_none() {
+        bail!("VM must have either a bootloader or a kernel image.");
+    }
+    if config.bootloader.is_some() && (config.kernel.is_some() || config.initrd.is_some()) {
+        bail!("Can't have both bootloader and kernel/initrd image.");
+    }
+    Ok(())
+}
+
+/// Adds a mapping for `file` to `fd_mappings`, and returns a string of the form "/proc/self/fd/N"
+/// where N is the file descriptor for the child process.
+fn add_fd_mapping(fd_mappings: &mut Vec<FdMapping>, file: &File) -> String {
+    let fd = file.as_raw_fd();
+    fd_mappings.push(FdMapping { parent_fd: fd, child_fd: fd });
+    format!("/proc/self/fd/{}", fd)
+}
diff --git a/virtualizationservice/src/main.rs b/virtualizationservice/src/main.rs
new file mode 100644
index 0000000..5453146
--- /dev/null
+++ b/virtualizationservice/src/main.rs
@@ -0,0 +1,47 @@
+// Copyright 2021, 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.
+
+//! Android VirtualizationService
+
+mod aidl;
+mod crosvm;
+
+use crate::aidl::{VirtualizationService, BINDER_SERVICE_IDENTIFIER};
+use android_system_virtualizationservice::aidl::android::system::virtualizationservice::IVirtualizationService::BnVirtualizationService;
+use android_system_virtualizationservice::binder::{add_service, BinderFeatures, ProcessState};
+use log::{info, Level};
+
+/// The first CID to assign to a guest VM managed by the VirtualizationService. CIDs lower than this
+/// are reserved for the host or other usage.
+const FIRST_GUEST_CID: Cid = 10;
+
+const LOG_TAG: &str = "VirtualizationService";
+
+/// The unique ID of a VM used (together with a port number) for vsock communication.
+type Cid = u32;
+
+fn main() {
+    android_logger::init_once(
+        android_logger::Config::default().with_tag(LOG_TAG).with_min_level(Level::Trace),
+    );
+
+    let virt_manager = VirtualizationService::default();
+    let virt_manager = BnVirtualizationService::new_binder(
+        virt_manager,
+        BinderFeatures { set_requesting_sid: true, ..BinderFeatures::default() },
+    );
+    add_service(BINDER_SERVICE_IDENTIFIER, virt_manager.as_binder()).unwrap();
+    info!("Registered Binder service, joining threadpool.");
+    ProcessState::join_thread_pool();
+}