Merge "Add framework-virtualization to combined_apis"
diff --git a/apkdmverity/src/main.rs b/apkdmverity/src/main.rs
index a69b583..6e12e38 100644
--- a/apkdmverity/src/main.rs
+++ b/apkdmverity/src/main.rs
@@ -162,7 +162,7 @@
     }
 
     fn create_block_aligned_file(path: &Path, data: &[u8]) {
-        let mut f = File::create(&path).unwrap();
+        let mut f = File::create(path).unwrap();
         f.write_all(data).unwrap();
 
         // Add padding so that the size of the file is multiple of 4096.
diff --git a/compos/src/artifact_signer.rs b/compos/src/artifact_signer.rs
index e51b8dd..d3843fc 100644
--- a/compos/src/artifact_signer.rs
+++ b/compos/src/artifact_signer.rs
@@ -46,7 +46,7 @@
     pub fn add_artifact(&mut self, path: &Path) -> Result<()> {
         // The path we store is where the file will be when it is verified, not where it is now.
         let suffix = path
-            .strip_prefix(&self.base_directory)
+            .strip_prefix(self.base_directory)
             .context("Artifacts must be under base directory")?;
         let target_path = Path::new(TARGET_DIRECTORY).join(suffix);
         let target_path = target_path.to_str().ok_or_else(|| anyhow!("Invalid path"))?;
diff --git a/compos/src/compsvc.rs b/compos/src/compsvc.rs
index 4330bbf..0e8b9f5 100644
--- a/compos/src/compsvc.rs
+++ b/compos/src/compsvc.rs
@@ -144,7 +144,7 @@
 
 fn add_artifacts(target_dir: &Path, artifact_signer: &mut ArtifactSigner) -> Result<()> {
     for entry in
-        read_dir(&target_dir).with_context(|| format!("Traversing {}", target_dir.display()))?
+        read_dir(target_dir).with_context(|| format!("Traversing {}", target_dir.display()))?
     {
         let entry = entry?;
         let file_type = entry.file_type()?;
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index 6233e0a..c200d00 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -16,6 +16,7 @@
 
 package android.system.virtualmachine;
 
+import static android.os.ParcelFileDescriptor.AutoCloseInputStream;
 import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
 import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
 import static android.system.virtualmachine.VirtualMachineCallback.ERROR_PAYLOAD_CHANGED;
@@ -69,12 +70,14 @@
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
+import java.nio.channels.FileChannel;
 import java.nio.file.FileAlreadyExistsException;
 import java.nio.file.FileVisitResult;
 import java.nio.file.Files;
@@ -284,10 +287,45 @@
         return sInstances.computeIfAbsent(context, unused -> new HashMap<>());
     }
 
+    /**
+     * Builds a virtual machine from an {@link VirtualMachineDescriptor} object and associates it
+     * with the given name.
+     *
+     * <p>The new virtual machine will be in the same state as the descriptor indicates.
+     *
+     * <p>Once a virtual machine is imported it is persisted until it is deleted by calling {@link
+     * #delete}. The imported virtual machine is in {@link #STATUS_STOPPED} state. To run the VM,
+     * call {@link #run}.
+     */
+    @GuardedBy("sCreateLock")
     @NonNull
-    private static File getVmDir(Context context, String name) {
-        File vmRoot = new File(context.getDataDir(), VM_DIR);
-        return new File(vmRoot, name);
+    static VirtualMachine fromDescriptor(
+            @NonNull Context context,
+            @NonNull String name,
+            @NonNull VirtualMachineDescriptor vmDescriptor)
+            throws VirtualMachineException {
+        VirtualMachineConfig config = VirtualMachineConfig.from(vmDescriptor.getConfigFd());
+        File vmDir = createVmDir(context, name);
+        try {
+            VirtualMachine vm = new VirtualMachine(context, name, config);
+            config.serialize(vm.mConfigFilePath);
+            try {
+                vm.mInstanceFilePath.createNewFile();
+            } catch (IOException e) {
+                throw new VirtualMachineException("failed to create instance image", e);
+            }
+            vm.importInstanceFrom(vmDescriptor.getInstanceImgFd());
+            getInstancesMap(context).put(name, new WeakReference<>(vm));
+            return vm;
+        } catch (VirtualMachineException | RuntimeException e) {
+            // If anything goes wrong, delete any files created so far and the VM's directory
+            try {
+                deleteRecursively(vmDir);
+            } catch (IOException innerException) {
+                e.addSuppressed(innerException);
+            }
+            throw e;
+        }
     }
 
     /**
@@ -300,21 +338,7 @@
     static VirtualMachine create(
             @NonNull Context context, @NonNull String name, @NonNull VirtualMachineConfig config)
             throws VirtualMachineException {
-        File vmDir = getVmDir(context, name);
-
-        try {
-            // We don't need to undo this even if VM creation fails.
-            Files.createDirectories(vmDir.getParentFile().toPath());
-
-            // The checking of the existence of this directory and the creation of it is done
-            // atomically. If the directory already exists (i.e. the VM with the same name was
-            // already created), FileAlreadyExistsException is thrown.
-            Files.createDirectory(vmDir.toPath());
-        } catch (FileAlreadyExistsException e) {
-            throw new VirtualMachineException("virtual machine already exists", e);
-        } catch (IOException e) {
-            throw new VirtualMachineException("failed to create directory for VM", e);
-        }
+        File vmDir = createVmDir(context, name);
 
         try {
             VirtualMachine vm = new VirtualMachine(context, name, config);
@@ -412,6 +436,33 @@
         if (instancesMap != null) instancesMap.remove(name);
     }
 
+    @GuardedBy("sCreateLock")
+    @NonNull
+    private static File createVmDir(@NonNull Context context, @NonNull String name)
+            throws VirtualMachineException {
+        File vmDir = getVmDir(context, name);
+        try {
+            // We don't need to undo this even if VM creation fails.
+            Files.createDirectories(vmDir.getParentFile().toPath());
+
+            // The checking of the existence of this directory and the creation of it is done
+            // atomically. If the directory already exists (i.e. the VM with the same name was
+            // already created), FileAlreadyExistsException is thrown.
+            Files.createDirectory(vmDir.toPath());
+        } catch (FileAlreadyExistsException e) {
+            throw new VirtualMachineException("virtual machine already exists", e);
+        } catch (IOException e) {
+            throw new VirtualMachineException("failed to create directory for VM", e);
+        }
+        return vmDir;
+    }
+
+    @NonNull
+    private static File getVmDir(Context context, String name) {
+        File vmRoot = new File(context.getDataDir(), VM_DIR);
+        return new File(vmRoot, name);
+    }
+
     /**
      * Returns the name of this virtual machine. The name is unique in the package and can't be
      * changed.
@@ -889,6 +940,7 @@
      * @return a {@link VirtualMachineDescriptor} instance that represents the VM's state.
      * @throws VirtualMachineException if the virtual machine is not stopped, or the state could not
      *     be captured.
+     * @hide
      */
     @NonNull
     public VirtualMachineDescriptor toDescriptor() throws VirtualMachineException {
@@ -1053,4 +1105,14 @@
             throw new VirtualMachineException("Couldn't parse extra apks from the vm config", e);
         }
     }
+
+    private void importInstanceFrom(@NonNull ParcelFileDescriptor instanceFd)
+            throws VirtualMachineException {
+        try (FileChannel instance = new FileOutputStream(mInstanceFilePath).getChannel();
+                FileChannel instanceInput = new AutoCloseInputStream(instanceFd).getChannel()) {
+            instance.transferFrom(instanceInput, /*position=*/ 0, instanceInput.size());
+        } catch (IOException e) {
+            throw new VirtualMachineException("failed to transfer instance image", e);
+        }
+    }
 }
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
index 593a57d..a660306 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -16,6 +16,7 @@
 
 package android.system.virtualmachine;
 
+import static android.os.ParcelFileDescriptor.AutoCloseInputStream;
 import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
 
 import static java.util.Objects.requireNonNull;
@@ -193,6 +194,17 @@
         }
     }
 
+    /** Loads a config from a {@link ParcelFileDescriptor}. */
+    @NonNull
+    static VirtualMachineConfig from(@NonNull ParcelFileDescriptor fd)
+            throws VirtualMachineException {
+        try (AutoCloseInputStream input = new AutoCloseInputStream(fd)) {
+            return fromInputStream(input);
+        } catch (IOException e) {
+            throw new VirtualMachineException("failed to read VM config from file descriptor", e);
+        }
+    }
+
     /** Loads a config from a stream, for example a file. */
     @NonNull
     private static VirtualMachineConfig fromInputStream(@NonNull InputStream input)
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java b/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
index 70532fc..b51cbce 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
@@ -23,8 +23,6 @@
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
 
-import com.android.internal.annotations.VisibleForTesting;
-
 /**
  * A VM descriptor that captures the state of a Virtual Machine.
  *
@@ -35,8 +33,8 @@
  * @hide
  */
 public final class VirtualMachineDescriptor implements Parcelable {
-    private final @NonNull ParcelFileDescriptor mConfigFd;
-    private final @NonNull ParcelFileDescriptor mInstanceImgFd;
+    @NonNull private final ParcelFileDescriptor mConfigFd;
+    @NonNull private final ParcelFileDescriptor mInstanceImgFd;
     // TODO(b/243129654): Add trusted storage fd once it is available.
 
     @Override
@@ -45,13 +43,14 @@
     }
 
     @Override
-    public void writeToParcel(Parcel out, int flags) {
+    public void writeToParcel(@NonNull Parcel out, int flags) {
         mConfigFd.writeToParcel(out, flags);
         mInstanceImgFd.writeToParcel(out, flags);
     }
 
+    @NonNull
     public static final Parcelable.Creator<VirtualMachineDescriptor> CREATOR =
-            new Parcelable.Creator<VirtualMachineDescriptor>() {
+            new Parcelable.Creator<>() {
                 public VirtualMachineDescriptor createFromParcel(Parcel in) {
                     return new VirtualMachineDescriptor(in);
                 }
@@ -63,19 +62,17 @@
 
     /**
      * @return File descriptor of the VM configuration file config.xml.
-     * @hide
      */
-    @VisibleForTesting
-    public @NonNull ParcelFileDescriptor getConfigFd() {
+    @NonNull
+    ParcelFileDescriptor getConfigFd() {
         return mConfigFd;
     }
 
     /**
      * @return File descriptor of the instance.img of the VM.
-     * @hide
      */
-    @VisibleForTesting
-    public @NonNull ParcelFileDescriptor getInstanceImgFd() {
+    @NonNull
+    ParcelFileDescriptor getInstanceImgFd() {
         return mInstanceImgFd;
     }
 
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
index 34b9fd9..c357f50 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
@@ -142,6 +142,24 @@
     }
 
     /**
+     * Imports a virtual machine from an {@link VirtualMachineDescriptor} object and associates it
+     * with the given name.
+     *
+     * <p>The new virtual machine will be in the same state as the descriptor indicates.
+     *
+     * @throws VirtualMachineException if the VM cannot be imported.
+     * @hide
+     */
+    @NonNull
+    public VirtualMachine importFromDescriptor(
+            @NonNull String name, @NonNull VirtualMachineDescriptor vmDescriptor)
+            throws VirtualMachineException {
+        synchronized (VirtualMachine.sCreateLock) {
+            return VirtualMachine.fromDescriptor(mContext, name, vmDescriptor);
+        }
+    }
+
+    /**
      * Returns an existing {@link VirtualMachine} with the given name. Returns null if there is no
      * such virtual machine.
      *
diff --git a/libs/devicemapper/src/lib.rs b/libs/devicemapper/src/lib.rs
index b9fb5c3..ebe71e4 100644
--- a/libs/devicemapper/src/lib.rs
+++ b/libs/devicemapper/src/lib.rs
@@ -212,7 +212,7 @@
         dm_dev_suspend(self, &mut data).context("failed to activate")?;
 
         // Step 4: wait unti the device is created and return the device path
-        let path = Path::new(MAPPER_DEV_ROOT).join(&name);
+        let path = Path::new(MAPPER_DEV_ROOT).join(name);
         wait_for_path(&path)?;
         Ok(path)
     }
@@ -250,13 +250,13 @@
     }
 
     fn write_to_dev(path: &Path, data: &[u8]) {
-        let mut f = OpenOptions::new().read(true).write(true).open(&path).unwrap();
+        let mut f = OpenOptions::new().read(true).write(true).open(path).unwrap();
         f.write_all(data).unwrap();
     }
 
     fn delete_device(dm: &DeviceMapper, name: &str) -> Result<()> {
         dm.delete_device_deferred(name)?;
-        wait_for_path_disappears(Path::new(MAPPER_DEV_ROOT).join(&name))?;
+        wait_for_path_disappears(Path::new(MAPPER_DEV_ROOT).join(name))?;
         Ok(())
     }
 
diff --git a/tests/aidl/com/android/microdroid/testservice/IBenchmarkService.aidl b/tests/aidl/com/android/microdroid/testservice/IBenchmarkService.aidl
index 260f804..16e4893 100644
--- a/tests/aidl/com/android/microdroid/testservice/IBenchmarkService.aidl
+++ b/tests/aidl/com/android/microdroid/testservice/IBenchmarkService.aidl
@@ -25,7 +25,7 @@
      *
      * @return The read rate in MB/s.
      */
-    double measureReadRate(String filename, long fileSizeBytes, boolean isRand);
+    double measureReadRate(String filename, boolean isRand);
 
     /** Returns an entry from /proc/meminfo. */
     long getMemInfoEntry(String name);
diff --git a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
index db74358..28852e8 100644
--- a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
+++ b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
@@ -257,18 +257,10 @@
     private static class VirtioBlkListener implements BenchmarkVmListener.InnerListener {
         private static final String FILENAME = APEX_ETC_FS + "microdroid_super.img";
 
-        private final long mFileSizeBytes;
         private final List<Double> mReadRates;
         private final boolean mIsRand;
 
         VirtioBlkListener(List<Double> readRates, boolean isRand) {
-            File file = new File(FILENAME);
-            try {
-                mFileSizeBytes = Files.size(file.toPath());
-            } catch (IOException e) {
-                throw new RuntimeException(e);
-            }
-            assertThat(mFileSizeBytes).isGreaterThan((long) SIZE_MB);
             mReadRates = readRates;
             mIsRand = isRand;
         }
@@ -276,7 +268,7 @@
         @Override
         public void onPayloadReady(VirtualMachine vm, IBenchmarkService benchmarkService)
                 throws RemoteException {
-            double readRate = benchmarkService.measureReadRate(FILENAME, mFileSizeBytes, mIsRand);
+            double readRate = benchmarkService.measureReadRate(FILENAME, mIsRand);
             mReadRates.add(readRate);
         }
     }
diff --git a/tests/benchmark/src/native/benchmarkbinary.cpp b/tests/benchmark/src/native/benchmarkbinary.cpp
index 70c6884..e43025c 100644
--- a/tests/benchmark/src/native/benchmarkbinary.cpp
+++ b/tests/benchmark/src/native/benchmarkbinary.cpp
@@ -23,6 +23,8 @@
 #include <fcntl.h>
 #include <linux/vm_sockets.h>
 #include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
 #include <time.h>
 #include <unistd.h>
 #include <vm_main.h>
@@ -56,9 +58,9 @@
 
 class IOBenchmarkService : public aidl::com::android::microdroid::testservice::BnBenchmarkService {
 public:
-    ndk::ScopedAStatus measureReadRate(const std::string& filename, int64_t fileSizeBytes,
-                                       bool isRand, double* out) override {
-        auto res = measure_read_rate(filename, fileSizeBytes, isRand);
+    ndk::ScopedAStatus measureReadRate(const std::string& filename, bool isRand,
+                                       double* out) override {
+        auto res = measure_read_rate(filename, isRand);
         if (res.ok()) {
             *out = res.value();
         }
@@ -90,10 +92,17 @@
     }
 
 private:
-    /** Measures the read rate for reading the given file. */
-    Result<double> measure_read_rate(const std::string& filename, int64_t fileSizeBytes,
-                                     bool is_rand) {
-        const int64_t block_count = fileSizeBytes / kBlockSizeBytes;
+    /**
+     * Measures the read rate for reading the given file.
+     * @return The read rate in MB/s.
+     */
+    Result<double> measure_read_rate(const std::string& filename, bool is_rand) {
+        struct stat file_stats;
+        if (stat(filename.c_str(), &file_stats) == -1) {
+            return Error() << "failed to get file stats";
+        }
+        const int64_t file_size_bytes = file_stats.st_size;
+        const int64_t block_count = file_size_bytes / kBlockSizeBytes;
         std::vector<uint64_t> offsets(block_count);
         for (auto i = 0; i < block_count; ++i) {
             offsets[i] = i * kBlockSizeBytes;
@@ -118,8 +127,8 @@
             }
         }
         double elapsed_seconds = ((double)clock() - start) / CLOCKS_PER_SEC;
-        double read_rate = (double)fileSizeBytes / kNumBytesPerMB / elapsed_seconds;
-        return {read_rate};
+        double file_size_mb = (double)file_size_bytes / kNumBytesPerMB;
+        return {file_size_mb / elapsed_seconds};
     }
 
     Result<size_t> read_meminfo_entry(const std::string& stat) {
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index 492eb33..5e86798 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -61,6 +61,7 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.Arrays;
 import java.util.List;
 import java.util.OptionalLong;
 import java.util.UUID;
@@ -600,31 +601,46 @@
     }
 
     @Test
-    public void vmConvertsToValidDescriptor() throws Exception {
+    public void importedVmIsEqualToTheOriginalVm() throws Exception {
         // Arrange
         VirtualMachineConfig config =
                 mInner.newVmConfigBuilder()
                         .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
                         .setDebugLevel(DEBUG_LEVEL_NONE)
                         .build();
-        String vmName = "test_vm";
-        VirtualMachine vm = mInner.forceCreateNewVirtualMachine(vmName, config);
+        String vmNameOrig = "test_vm_orig", vmNameImport = "test_vm_import";
+        VirtualMachine vmOrig = mInner.forceCreateNewVirtualMachine(vmNameOrig, config);
+        // Run something to make the instance.img different with the initialized one.
+        TestResults origTestResults = runVmTestService(vmOrig);
+        assertThat(origTestResults.mException).isNull();
+        assertThat(origTestResults.mAddInteger).isEqualTo(123 + 456);
+        VirtualMachineDescriptor descriptor = vmOrig.toDescriptor();
+        VirtualMachineManager vmm = mInner.getVirtualMachineManager();
+        if (vmm.get(vmNameImport) != null) {
+            vmm.delete(vmNameImport);
+        }
 
         // Action
-        VirtualMachineDescriptor descriptor = vm.toDescriptor();
+        VirtualMachine vmImport = vmm.importFromDescriptor(vmNameImport, descriptor);
 
         // Asserts
-        assertFileContentsAreEqual(descriptor.getConfigFd(), vmName, "config.xml");
-        assertFileContentsAreEqual(descriptor.getInstanceImgFd(), vmName, "instance.img");
+        assertFileContentsAreEqualInTwoVms("config.xml", vmNameOrig, vmNameImport);
+        assertFileContentsAreEqualInTwoVms("instance.img", vmNameOrig, vmNameImport);
+        assertThat(vmImport).isNotEqualTo(vmOrig);
+        vmm.delete(vmNameOrig);
+        assertThat(vmImport).isEqualTo(vmm.get(vmNameImport));
+        TestResults testResults = runVmTestService(vmImport);
+        assertThat(testResults.mException).isNull();
+        assertThat(testResults.mAddInteger).isEqualTo(123 + 456);
     }
 
-    private void assertFileContentsAreEqual(
-            ParcelFileDescriptor parcelFd, String vmName, String fileName) throws IOException {
-        File file = getVmFile(vmName, fileName);
-        // Use try-with-resources to close the files automatically after assert.
-        try (FileInputStream input1 = new FileInputStream(parcelFd.getFileDescriptor());
-                FileInputStream input2 = new FileInputStream(file)) {
-            assertThat(input1.readAllBytes()).isEqualTo(input2.readAllBytes());
+    private void assertFileContentsAreEqualInTwoVms(String fileName, String vmName1, String vmName2)
+            throws IOException {
+        File file1 = getVmFile(vmName1, fileName);
+        File file2 = getVmFile(vmName2, fileName);
+        try (FileInputStream input1 = new FileInputStream(file1);
+                FileInputStream input2 = new FileInputStream(file2)) {
+            assertThat(Arrays.equals(input1.readAllBytes(), input2.readAllBytes())).isTrue();
         }
     }
 
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 340fc68..a8f68bc 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -500,8 +500,7 @@
 }
 
 fn prepare_ramdump_file(ramdump_path: &Path) -> Result<File> {
-    File::create(&ramdump_path)
-        .context(format!("Failed to create ramdump file {:?}", &ramdump_path))
+    File::create(ramdump_path).context(format!("Failed to create ramdump file {:?}", &ramdump_path))
 }
 
 /// Given the configuration for a disk image, assembles the `DiskFile` to pass to crosvm.
diff --git a/virtualizationservice/src/composite.rs b/virtualizationservice/src/composite.rs
index c9a68ac..fe17ff4 100644
--- a/virtualizationservice/src/composite.rs
+++ b/virtualizationservice/src/composite.rs
@@ -51,7 +51,7 @@
         OpenOptions::new().create_new(true).read(true).write(true).open(footer_path).with_context(
             || format!("Failed to create composite image header {:?}", footer_path),
         )?;
-    let zero_filler_file = File::open(&zero_filler_path).with_context(|| {
+    let zero_filler_file = File::open(zero_filler_path).with_context(|| {
         format!("Failed to open composite image zero filler {:?}", zero_filler_path)
     })?;
 
@@ -66,7 +66,7 @@
     )?;
 
     // Re-open the composite image as read-only.
-    let composite_image = File::open(&output_path)
+    let composite_image = File::open(output_path)
         .with_context(|| format!("Failed to open composite image {:?}", output_path))?;
 
     files.push(header_file);