Merge "microdroid: pass idsig in payload.img"
diff --git a/apex/Android.bp b/apex/Android.bp
index 5b6f0a9..146e4eb 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -13,12 +13,13 @@
     key: "com.android.virt.key",
     certificate: ":com.android.virt.certificate",
 
-    // crosvm is enabled for only 64-bit targets on device
+    // crosvm and virtualizationservice are only enabled for 64-bit targets on device
     arch: {
         arm64: {
             binaries: [
                 "authfs", // TODO(victorhsieh): move to microdroid once we can run the test in VM.
                 "crosvm",
+                "virtualizationservice",
             ],
             filesystems: [
                 "microdroid_super",
@@ -32,6 +33,7 @@
             binaries: [
                 "authfs", // TODO(victorhsieh): move to microdroid once we can run the test in VM.
                 "crosvm",
+                "virtualizationservice",
             ],
             filesystems: [
                 "microdroid_super",
@@ -44,7 +46,6 @@
     },
     binaries: [
         "fd_server",
-        "virtualizationservice",
         "vm",
 
         // tools to create composite images
diff --git a/compos/Android.bp b/compos/Android.bp
index fc0517f..1611b68 100644
--- a/compos/Android.bp
+++ b/compos/Android.bp
@@ -55,3 +55,10 @@
         "com.android.compos",
     ],
 }
+
+// TODO(b/190503456) Remove this when vm/virtualizationservice generates payload.img from vm_config
+prebuilt_etc {
+    name: "compos_payload_config",
+    src: "payload_config.json",
+    filename: "payload_config.json",
+}
diff --git a/compos/apex/Android.bp b/compos/apex/Android.bp
index 7ced384..3a8f601 100644
--- a/compos/apex/Android.bp
+++ b/compos/apex/Android.bp
@@ -40,4 +40,12 @@
         "compsvc_worker",
         "pvm_exec",
     ],
+
+    apps: [
+        "CompOSPayloadApp",
+    ],
+
+    prebuilts: [
+        "compos_payload_config",
+    ],
 }
diff --git a/compos/apk/Android.bp b/compos/apk/Android.bp
new file mode 100644
index 0000000..c6192b9
--- /dev/null
+++ b/compos/apk/Android.bp
@@ -0,0 +1,9 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_app {
+    name: "CompOSPayloadApp",
+    sdk_version: "current",
+    apex_available: ["com.android.compos"],
+}
diff --git a/compos/apk/AndroidManifest.xml b/compos/apk/AndroidManifest.xml
new file mode 100644
index 0000000..1e9352b
--- /dev/null
+++ b/compos/apk/AndroidManifest.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+      package="com.android.compos.payload">
+    <application android:label="CompOS" />
+</manifest>
diff --git a/compos/apk/assets/vm_config.json b/compos/apk/assets/vm_config.json
new file mode 100644
index 0000000..a8dca71
--- /dev/null
+++ b/compos/apk/assets/vm_config.json
@@ -0,0 +1,34 @@
+{
+  "version": 1,
+  "os": {
+    "name": "microdroid"
+  },
+  "task": {
+    "type": "executable",
+    "command": "/apex/com.android.compos/bin/compsvc",
+    "args": [
+      "--rpc-binder",
+      "/apex/com.android.art/bin/dex2oat64"
+    ]
+  },
+  "apexes": [
+    {
+      "name": "com.android.adbd"
+    },
+    {
+      "name": "com.android.art"
+    },
+    {
+      "name": "com.android.compos"
+    },
+    {
+      "name": "com.android.i18n"
+    },
+    {
+      "name": "com.android.os.statsd"
+    },
+    {
+      "name": "com.android.sdkext"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/compos/payload_config.json b/compos/payload_config.json
new file mode 100644
index 0000000..588ccca
--- /dev/null
+++ b/compos/payload_config.json
@@ -0,0 +1,15 @@
+{
+  "apk": {
+    "path": "/apex/com.android.compos/app/CompOSPayloadApp/CompOSPayloadApp.apk",
+    "name": "com.android.compos.payload"
+  },
+  "system_apexes": [
+    "com.android.adbd",
+    "com.android.art",
+    "com.android.compos",
+    "com.android.i18n",
+    "com.android.os.statsd",
+    "com.android.sdkext"
+  ],
+  "payload_config_path": "/mnt/apk/assets/vm_config.json"
+}
\ No newline at end of file
diff --git a/microdroid/README.md b/microdroid/README.md
index c8ad8f4..aae6e66 100644
--- a/microdroid/README.md
+++ b/microdroid/README.md
@@ -53,42 +53,22 @@
     {
       "partitions": [
         {
-          "label": "misc",
-          "path": "/data/local/tmp/microdroid/misc.img"
-        },
-        {
           "label": "boot_a",
           "path": "/apex/com.android.virt/etc/fs/microdroid_boot-5.10.img"
         },
         {
-          "label": "boot_b",
-          "path": "/apex/com.android.virt/etc/fs/microdroid_boot-5.10.img"
-        },
-        {
           "label": "vendor_boot_a",
           "path": "/apex/com.android.virt/etc/fs/microdroid_vendor_boot-5.10.img"
         },
         {
-          "label": "vendor_boot_b",
-          "path": "/apex/com.android.virt/etc/fs/microdroid_vendor_boot-5.10.img"
-        },
-        {
           "label": "vbmeta_a",
           "path": "/apex/com.android.virt/etc/fs/microdroid_vbmeta.img"
         },
         {
-          "label": "vbmeta_b",
-          "path": "/apex/com.android.virt/etc/fs/microdroid_vbmeta.img"
-        },
-        {
           "label": "vbmeta_system_a",
           "path": "/apex/com.android.virt/etc/fs/microdroid_vbmeta_system.img"
         },
         {
-          "label": "vbmeta_system_b",
-          "path": "/apex/com.android.virt/etc/fs/microdroid_vbmeta_system.img"
-        },
-        {
           "label": "super",
           "path": "/apex/com.android.virt/etc/fs/microdroid_super.img"
         }
@@ -131,7 +111,6 @@
 ```sh
 $ adb root
 $ adb shell 'mkdir /data/local/tmp/microdroid'
-$ adb shell 'dd if=/dev/zero of=/data/local/tmp/microdroid/misc.img bs=4k count=256'
 $ adb push payload.json /data/local/tmp/microdroid/payload.json
 $ adb shell 'cd /data/local/tmp/microdroid; /apex/com.android.virt/bin/mk_payload payload.json payload.img'
 $ adb shell 'chmod go+r /data/local/tmp/microdroid/payload*'
diff --git a/microdroid/microdroid_cdisk.json b/microdroid/microdroid_cdisk.json
index e1ca826..d0a5025 100644
--- a/microdroid/microdroid_cdisk.json
+++ b/microdroid/microdroid_cdisk.json
@@ -1,42 +1,22 @@
 {
   "partitions": [
     {
-      "label": "misc",
-      "path": "/data/local/tmp/virt/misc.img"
-    },
-    {
       "label": "boot_a",
       "path": "/apex/com.android.virt/etc/fs/microdroid_boot-5.10.img"
     },
     {
-      "label": "boot_b",
-      "path": "/apex/com.android.virt/etc/fs/microdroid_boot-5.10.img"
-    },
-    {
       "label": "vendor_boot_a",
       "path": "/apex/com.android.virt/etc/fs/microdroid_vendor_boot-5.10.img"
     },
     {
-      "label": "vendor_boot_b",
-      "path": "/apex/com.android.virt/etc/fs/microdroid_vendor_boot-5.10.img"
-    },
-    {
       "label": "vbmeta_a",
       "path": "/apex/com.android.virt/etc/fs/microdroid_vbmeta.img"
     },
     {
-      "label": "vbmeta_b",
-      "path": "/apex/com.android.virt/etc/fs/microdroid_vbmeta.img"
-    },
-    {
       "label": "vbmeta_system_a",
       "path": "/apex/com.android.virt/etc/fs/microdroid_vbmeta_system.img"
     },
     {
-      "label": "vbmeta_system_b",
-      "path": "/apex/com.android.virt/etc/fs/microdroid_vbmeta_system.img"
-    },
-    {
       "label": "super",
       "path": "/apex/com.android.virt/etc/fs/microdroid_super.img"
     }
diff --git a/microdroid/uboot-env-x86_64.txt b/microdroid/uboot-env-x86_64.txt
index ffc0462..1abafa6 100644
--- a/microdroid/uboot-env-x86_64.txt
+++ b/microdroid/uboot-env-x86_64.txt
@@ -1,7 +1,9 @@
 # Static u-boot environment variables for microdroid. See b/180481192
 
 # Boot the device following the Android boot procedure
-bootcmd=avb init virtio 0 && avb verify _a && boot_android virtio 0#misc
+# `0` is the disk number of os_composite.img
+# `a` and `_a` are the slot index for A/B
+bootcmd=avb init virtio 0 && avb verify _a && boot_android virtio 0 a
 
 bootdelay=0
 
diff --git a/microdroid/uboot-env.txt b/microdroid/uboot-env.txt
index 0bdc591..585702e 100644
--- a/microdroid/uboot-env.txt
+++ b/microdroid/uboot-env.txt
@@ -1,7 +1,9 @@
 # Static u-boot environment variables for microdroid. See b/180481192
 
 # Boot the device following the Android boot procedure
-bootcmd=avb init virtio 0 && avb verify _a && boot_android virtio 0#misc
+# `0` is the disk number of os_composite.img
+# `a` and `_a` are the slot index for A/B
+bootcmd=avb init virtio 0 && avb verify _a && boot_android virtio 0 a
 
 bootdelay=0
 fdtaddr=0x80000000
diff --git a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
index 87a9822..7f9b8de 100644
--- a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
+++ b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
@@ -138,7 +138,7 @@
 
     // Run a shell command on Microdroid
     private String runOnMicrodroid(String... cmd) {
-        final long timeout = 3000; // 3 sec. Microdroid is extremely slow on GCE-on-CF.
+        final long timeout = 30000; // 30 sec. Microdroid is extremely slow on GCE-on-CF.
         CommandResult result =
                 RunUtil.getDefault()
                         .runTimedCmd(timeout, "adb", "-s", MICRODROID_SERIAL, "shell", join(cmd));
@@ -150,14 +150,6 @@
         return String.join(" ", Arrays.asList(strs));
     }
 
-    // TODO(b/191131043) remove this step
-    private String createMiscImage() throws Exception {
-        final String output = TEST_ROOT + "misc.img";
-        runOnAndroid("dd", "if=/dev/zero", "of=" + output, "bs=4k", "count=256");
-        assertThat(runOnAndroid("du", "-b", output), is(not("")));
-        return output;
-    }
-
     private String createPayloadImage(String apkName, String packageName, String configPath)
             throws Exception {
         File apkFile = findTestFile(apkName);
@@ -223,9 +215,8 @@
 
     private void startMicrodroid(String apkName, String packageName, String configPath)
             throws Exception {
-        // Create misc.img and payload.img
+        // Create payload.img
         final String payloadImg = createPayloadImage(apkName, packageName, configPath);
-        final String miscImg = createMiscImage();
 
         // Tools and executables
         final String mkCdisk = VIRT_APEX + "bin/mk_cdisk";
diff --git a/virtualizationservice/Android.bp b/virtualizationservice/Android.bp
index bad7f46..f5ad1f8 100644
--- a/virtualizationservice/Android.bp
+++ b/virtualizationservice/Android.bp
@@ -7,6 +7,17 @@
     crate_name: "virtualizationservice",
     srcs: ["src/main.rs"],
     edition: "2018",
+    // Only build on targets which crosvm builds on.
+    enabled: false,
+    target: {
+        android64: {
+            compile_multilib: "64",
+            enabled: true,
+        },
+        linux_bionic_arm64: {
+            enabled: true,
+        },
+    },
     prefer_rlib: true,
     rustlibs: [
         "android.system.virtualizationservice-rust",
@@ -14,6 +25,7 @@
         "libanyhow",
         "libcommand_fds",
         "libcompositediskconfig",
+        "libdisk",
         "liblog_rust",
         "libserde_json",
         "libserde",
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
index 311c2e0..8affaad 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
@@ -27,7 +27,11 @@
     IVirtualMachine startVm(
             in VirtualMachineConfig config, in @nullable ParcelFileDescriptor logFd);
 
-    /** Initialise an empty partition image of the given size to be used as a writable partition. */
+    /**
+     * Initialise an empty partition image of the given size to be used as a writable partition.
+     *
+     * The file must be open with both read and write permissions, and should be a new empty file.
+     */
     void initializeWritablePartition(in ParcelFileDescriptor imageFd, long size);
 
     /**
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 7b63917..6d3f737 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -26,13 +26,14 @@
 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,
+    self, BinderFeatures, ExceptionCode, Interface, ParcelFileDescriptor, Status, Strong, ThreadState,
 };
 use command_fds::FdMapping;
+use disk::QcowFile;
 use log::{debug, error, warn};
 use std::convert::TryInto;
+use std::ffi::CString;
 use std::fs::{File, create_dir};
-use std::io::{Seek, SeekFrom, Write};
 use std::os::unix::io::AsRawFd;
 use std::path::{Path, PathBuf};
 use std::sync::{Arc, Mutex, Weak};
@@ -80,10 +81,16 @@
         let temporary_directory: PathBuf = format!("{}/{}", TEMPORARY_DIRECTORY, cid).into();
         create_dir(&temporary_directory).map_err(|e| {
             error!(
-                "Failed to create temporary directory {:?} for VM files: {:?}",
+                "Failed to create temporary directory {:?} for VM files: {}",
                 temporary_directory, e
             );
-            StatusCode::UNKNOWN_ERROR
+            new_binder_exception(
+                ExceptionCode::SERVICE_SPECIFIC,
+                format!(
+                    "Failed to create temporary directory {:?} for VM files: {}",
+                    temporary_directory, e
+                ),
+            )
         })?;
 
         // Assemble disk images if needed.
@@ -126,8 +133,11 @@
             requester_debug_pid,
         )
         .map_err(|e| {
-            error!("Failed to start VM with config {:?}: {:?}", config, e);
-            StatusCode::UNKNOWN_ERROR
+            error!("Failed to start VM with config {:?}: {}", config, e);
+            new_binder_exception(
+                ExceptionCode::SERVICE_SPECIFIC,
+                format!("Failed to start VM: {}", e),
+            )
         })?;
         state.add_vm(Arc::downgrade(&instance));
         Ok(VirtualMachine::create(instance))
@@ -139,26 +149,20 @@
         image_fd: &ParcelFileDescriptor,
         size: i64,
     ) -> binder::Result<()> {
-        let size: u64 = size.try_into().map_err(|e| {
-            error!("Invalid size {}: {}", size, e);
-            StatusCode::BAD_VALUE
+        let size = size.try_into().map_err(|e| {
+            new_binder_exception(
+                ExceptionCode::ILLEGAL_ARGUMENT,
+                format!("Invalid size {}: {}", size, e),
+            )
         })?;
-        let mut image = clone_file(image_fd)?;
+        let image = clone_file(image_fd)?;
 
-        // TODO: create a QCOW2 image instead, like `crosvm create_qcow2`, once `mk_cdisk` supports
-        // it (b/189211641).
-        if size > 0 {
-            // Extend the file to the given size by seeking to the size we want and writing a single
-            // 0 byte there.
-            image.seek(SeekFrom::Start(size - 1)).map_err(|e| {
-                error!("Failed to seek to desired size of image file ({}): {}.", size, e);
-                StatusCode::UNKNOWN_ERROR
-            })?;
-            image.write_all(&[0]).map_err(|e| {
-                error!("Failed to write 0 to image file: {}.", e);
-                StatusCode::UNKNOWN_ERROR
-            })?;
-        }
+        QcowFile::new(image, size).map_err(|e| {
+            new_binder_exception(
+                ExceptionCode::SERVICE_SPECIFIC,
+                format!("Failed to create QCOW2 image: {}", e),
+            )
+        })?;
 
         Ok(())
     }
@@ -166,9 +170,7 @@
     /// 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());
-        }
+        check_debug_access()?;
 
         let state = &mut *self.state.lock().unwrap();
         let vms = state.vms();
@@ -189,9 +191,7 @@
     /// 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());
-        }
+        check_debug_access()?;
 
         let state = &mut *self.state.lock().unwrap();
         state.debug_hold_vm(vmref.clone());
@@ -202,9 +202,7 @@
     /// 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());
-        }
+        check_debug_access()?;
 
         let state = &mut *self.state.lock().unwrap();
         Ok(state.debug_drop_vm(cid))
@@ -219,19 +217,25 @@
     temporary_directory: &Path,
     next_temporary_image_id: &mut u64,
     indirect_files: &mut Vec<File>,
-) -> Result<DiskFile, StatusCode> {
+) -> Result<DiskFile, Status> {
     let image = if !disk.partitions.is_empty() {
         if disk.image.is_some() {
             warn!("DiskImage {:?} contains both image and partitions.", disk);
-            return Err(StatusCode::BAD_VALUE);
+            return Err(new_binder_exception(
+                ExceptionCode::ILLEGAL_ARGUMENT,
+                "DiskImage contains both image and partitions.",
+            ));
         }
 
         let composite_image_filename =
             make_composite_image_filename(temporary_directory, next_temporary_image_id);
         let (image, partition_files) =
             make_composite_image(&disk.partitions, &composite_image_filename).map_err(|e| {
-                error!("Failed to make composite image with config {:?}: {:?}", disk, e);
-                StatusCode::UNKNOWN_ERROR
+                error!("Failed to make composite image with config {:?}: {}", disk, e);
+                new_binder_exception(
+                    ExceptionCode::SERVICE_SPECIFIC,
+                    format!("Failed to make composite image: {}", e),
+                )
             })?;
 
         // Pass the file descriptors for the various partition files to crosvm when it
@@ -243,7 +247,10 @@
         clone_file(image)?
     } else {
         warn!("DiskImage {:?} didn't contain image or partitions.", disk);
-        return Err(StatusCode::BAD_VALUE);
+        return Err(new_binder_exception(
+            ExceptionCode::ILLEGAL_ARGUMENT,
+            "DiskImage didn't contain image or partitions.",
+        ));
     };
 
     Ok(DiskFile { image, writable: disk.writable })
@@ -260,28 +267,35 @@
 }
 
 /// Gets the calling SID of the current Binder thread.
-fn get_calling_sid() -> Result<String, StatusCode> {
+fn get_calling_sid() -> Result<String, Status> {
     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)
+                    error!("SID was not valid UTF-8: {}", e);
+                    Err(new_binder_exception(
+                        ExceptionCode::ILLEGAL_ARGUMENT,
+                        format!("SID was not valid UTF-8: {}", e),
+                    ))
                 }
             }
         } else {
             error!("Missing SID on startVm");
-            Err(StatusCode::UNKNOWN_ERROR)
+            Err(new_binder_exception(ExceptionCode::SECURITY, "Missing SID on startVm"))
         }
     })
 }
 
 /// Check whether the caller of the current Binder method is allowed to call debug methods.
-fn debug_access_allowed() -> bool {
+fn check_debug_access() -> binder::Result<()> {
     let uid = ThreadState::get_calling_uid();
     log::trace!("Debug method call from UID {}.", uid);
-    DEBUG_ALLOWED_UIDS.contains(&uid)
+    if DEBUG_ALLOWED_UIDS.contains(&uid) {
+        Ok(())
+    } else {
+        Err(new_binder_exception(ExceptionCode::SECURITY, "Debug access denied"))
+    }
 }
 
 /// Implementation of the AIDL `IVirtualMachine` interface. Used as a handle to a VM.
@@ -396,7 +410,7 @@
     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)?;
+        self.next_cid = self.next_cid.checked_add(1).ok_or(ExceptionCode::ILLEGAL_STATE)?;
         Ok(cid)
     }
 }
@@ -413,6 +427,16 @@
 }
 
 /// Converts a `&ParcelFileDescriptor` to a `File` by cloning the file.
-fn clone_file(file: &ParcelFileDescriptor) -> Result<File, StatusCode> {
-    file.as_ref().try_clone().map_err(|_| StatusCode::UNKNOWN_ERROR)
+fn clone_file(file: &ParcelFileDescriptor) -> Result<File, Status> {
+    file.as_ref().try_clone().map_err(|e| {
+        new_binder_exception(
+            ExceptionCode::BAD_PARCELABLE,
+            format!("Failed to clone File from ParcelFileDescriptor: {}", e),
+        )
+    })
+}
+
+/// Constructs a new Binder error `Status` with the given `ExceptionCode` and message.
+fn new_binder_exception<T: AsRef<str>>(exception: ExceptionCode, message: T) -> Status {
+    Status::new_exception(exception, CString::new(message.as_ref()).ok().as_deref())
 }
diff --git a/virtualizationservice/src/crosvm.rs b/virtualizationservice/src/crosvm.rs
index 138236c..797011c 100644
--- a/virtualizationservice/src/crosvm.rs
+++ b/virtualizationservice/src/crosvm.rs
@@ -133,7 +133,7 @@
 
         // Delete temporary files.
         if let Err(e) = remove_dir_all(&self.temporary_directory) {
-            error!("Error removing temporary directory {:?}: {:?}", self.temporary_directory, e);
+            error!("Error removing temporary directory {:?}: {}", self.temporary_directory, e);
         }
     }
 
diff --git a/vm/src/main.rs b/vm/src/main.rs
index e2c11a8..2c93ec4 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -104,6 +104,7 @@
 ) -> Result<(), Error> {
     let image = OpenOptions::new()
         .create_new(true)
+        .read(true)
         .write(true)
         .open(image_path)
         .with_context(|| format!("Failed to create {:?}", image_path))?;