Merge "Disable LLMNR in the guest VM" into main
diff --git a/android/TerminalApp/Android.bp b/android/TerminalApp/Android.bp
index 2711af0..733a72b 100644
--- a/android/TerminalApp/Android.bp
+++ b/android/TerminalApp/Android.bp
@@ -9,7 +9,6 @@
         "java/**/*.kt",
     ],
     resource_dirs: ["res"],
-    asset_dirs: ["assets"],
     static_libs: [
         "androidx-constraintlayout_constraintlayout",
         "androidx.window_window",
diff --git a/android/TerminalApp/assets/.gitkeep b/android/TerminalApp/assets/.gitkeep
deleted file mode 100644
index e69de29..0000000
--- a/android/TerminalApp/assets/.gitkeep
+++ /dev/null
diff --git a/android/TerminalApp/assets/client.p12 b/android/TerminalApp/assets/client.p12
deleted file mode 100644
index f1f5820..0000000
--- a/android/TerminalApp/assets/client.p12
+++ /dev/null
Binary files differ
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/ConfigJson.java b/android/TerminalApp/java/com/android/virtualization/terminal/ConfigJson.java
index e1342e9..cec1b7a 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/ConfigJson.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/ConfigJson.java
@@ -35,8 +35,16 @@
 import com.google.gson.Gson;
 import com.google.gson.annotations.SerializedName;
 
+import java.io.BufferedReader;
 import java.io.FileReader;
+import java.io.IOException;
+import java.io.PipedReader;
+import java.io.PipedWriter;
+import java.io.Reader;
+import java.nio.file.Path;
 import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Objects;
 
 /** This class and its inner classes model vm_config.json. */
@@ -69,14 +77,54 @@
     private GpuJson gpu;
 
     /** Parses JSON file at jsonPath */
-    static ConfigJson from(String jsonPath) {
-        try (FileReader r = new FileReader(jsonPath)) {
+    static ConfigJson from(Context context, Path jsonPath) {
+        try (FileReader fileReader = new FileReader(jsonPath.toFile());
+                Reader r = replaceKeywords(fileReader, context)) {
             return new Gson().fromJson(r, ConfigJson.class);
         } catch (Exception e) {
             throw new RuntimeException("Failed to parse " + jsonPath, e);
         }
     }
 
+    private static Reader replaceKeywords(Reader r, Context context) throws IOException {
+        PipedWriter pipeIn = new PipedWriter();
+        PipedReader pipeOut = new PipedReader();
+        pipeOut.connect(pipeIn);
+
+        Map<String, String> rules = new HashMap<>();
+        rules.put("\\$PAYLOAD_DIR", InstallUtils.getInternalStorageDir(context).toString());
+        rules.put("\\$USER_ID", String.valueOf(context.getUserId()));
+        rules.put("\\$PACKAGE_NAME", context.getPackageName());
+        String appDataDir = context.getDataDir().toString();
+        // TODO: remove this hack
+        if (context.getUserId() == 0) {
+            appDataDir = "/data/data/" + context.getPackageName();
+        }
+        rules.put("\\$APP_DATA_DIR", appDataDir);
+
+        try (BufferedReader br = new BufferedReader(r)) {
+            br.lines()
+                    .map(
+                            line -> {
+                                for (Map.Entry<String, String> rule : rules.entrySet()) {
+                                    line = line.replaceAll(rule.getKey(), rule.getValue());
+                                }
+                                return line;
+                            })
+                    .forEach(
+                            line -> {
+                                try {
+                                    pipeIn.write(line);
+                                    pipeIn.write('\n');
+                                } catch (IOException e) {
+                                    // this cannot happen as it is connected to a pipe.
+                                    throw new RuntimeException(e);
+                                }
+                            });
+        }
+        return pipeOut;
+    }
+
     private int getCpuTopology() {
         switch (cpu_topology) {
             case "one_cpu":
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/ImageArchive.java b/android/TerminalApp/java/com/android/virtualization/terminal/ImageArchive.java
new file mode 100644
index 0000000..12f485a
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/ImageArchive.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2024 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 com.android.virtualization.terminal;
+
+import android.os.Build;
+import android.os.Environment;
+
+import org.apache.commons.compress.archivers.ArchiveEntry;
+import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
+import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
+
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.Arrays;
+import java.util.function.Function;
+
+/**
+ * ImageArchive models the archive file (images.tar.gz) where VM payload files are in. This class
+ * provides methods for handling the archive file, most importantly installing it.
+ */
+class ImageArchive {
+    private static final String DIR_IN_SDCARD = "linux";
+    private static final String ARCHIVE_NAME = "images.tar.gz";
+    private static final String HOST_URL = "https://dl.google.com/android/ferrochrome/latest";
+
+    // Only one can be non-null
+    private final URL mUrl;
+    private final Path mPath;
+
+    private ImageArchive(URL url) {
+        mUrl = url;
+        mPath = null;
+    }
+
+    private ImageArchive(Path path) {
+        mUrl = null;
+        mPath = path;
+    }
+
+    /** Creates ImageArchive which is located in the sdcard. This archive is for testing only. */
+    public static ImageArchive fromSdCard() {
+        Path dir = Environment.getExternalStoragePublicDirectory(DIR_IN_SDCARD).toPath();
+        Path file = dir.resolve(ARCHIVE_NAME);
+        return new ImageArchive(file);
+    }
+
+    /** Creates ImageArchive which is hosted in the Google server. This is the official archive. */
+    public static ImageArchive fromInternet() {
+        String arch = Arrays.asList(Build.SUPPORTED_ABIS).contains("x86_64") ? "x86_64" : "aarch64";
+        try {
+            URL url = new URL(HOST_URL + "/" + arch + "/" + ARCHIVE_NAME);
+            return new ImageArchive(url);
+        } catch (MalformedURLException e) {
+            // cannot happen
+            throw new RuntimeException(e);
+        }
+    }
+
+    private InputStream getInputStream(Function<InputStream, InputStream> filter)
+            throws IOException {
+        InputStream is = mPath != null ? new FileInputStream(mPath.toFile()) : mUrl.openStream();
+        BufferedInputStream bufStream = new BufferedInputStream(is);
+        return filter == null ? bufStream : filter.apply(bufStream);
+    }
+
+    /**
+     * Installs this ImageArchive to a directory pointed by path. filter can be supplied to provide
+     * an additional input stream which will be used during the installation.
+     */
+    public void installTo(Path path, Function<InputStream, InputStream> filter) throws IOException {
+        try (InputStream stream = getInputStream(filter);
+                GzipCompressorInputStream gzStream = new GzipCompressorInputStream(stream);
+                TarArchiveInputStream tarStream = new TarArchiveInputStream(gzStream)) {
+
+            Files.createDirectories(path);
+            ArchiveEntry entry;
+            while ((entry = tarStream.getNextEntry()) != null) {
+                Path to = path.resolve(entry.getName());
+                if (Files.isDirectory(to)) {
+                    Files.createDirectories(to);
+                } else {
+                    Files.copy(tarStream, to, StandardCopyOption.REPLACE_EXISTING);
+                }
+            }
+        }
+        postInstall();
+    }
+
+    // To save storage, delete the source archive on the disk.
+    private void postInstall() throws IOException {
+        if (mPath != null) {
+            Files.deleteIfExists(mPath);
+        }
+    }
+}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/InstallUtils.java b/android/TerminalApp/java/com/android/virtualization/terminal/InstallUtils.java
index 71f2a2d..1b6da6c 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/InstallUtils.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/InstallUtils.java
@@ -35,9 +35,6 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.StandardCopyOption;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.function.Function;
 
 public class InstallUtils {
     private static final String VM_CONFIG_FILENAME = "vm_config.json";
@@ -47,8 +44,8 @@
     private static final String INSTALLATION_COMPLETED_FILENAME = "completed";
     private static final String PAYLOAD_DIR = "linux";
 
-    public static String getVmConfigPath(Context context) {
-        return getInternalStorageDir(context).toPath().resolve(VM_CONFIG_FILENAME).toString();
+    public static Path getVmConfigPath(Context context) {
+        return getInternalStorageDir(context).resolve(VM_CONFIG_FILENAME);
     }
 
     public static boolean isImageInstalled(Context context) {
@@ -57,15 +54,16 @@
 
     public static void backupRootFs(Context context) throws IOException {
         Files.move(
-                getRootfsFile(context).toPath(),
-                getBackupFile(context).toPath(),
+                getRootfsFile(context),
+                getBackupFile(context),
                 StandardCopyOption.REPLACE_EXISTING);
     }
 
     public static boolean createInstalledMarker(Context context) {
         try {
-            File file = new File(getInstallationCompletedPath(context).toString());
-            return file.createNewFile();
+            Path path = getInstallationCompletedPath(context);
+            Files.createFile(path);
+            return true;
         } catch (IOException e) {
             Log.e(TAG, "Failed to mark install completed", e);
             return false;
@@ -74,7 +72,7 @@
 
     @VisibleForTesting
     public static void deleteInstallation(Context context) {
-        FileUtils.deleteContentsAndDir(getInternalStorageDir(context));
+        FileUtils.deleteContentsAndDir(getInternalStorageDir(context).toFile());
     }
 
     private static Path getPayloadPath() {
@@ -91,16 +89,16 @@
         return Files.exists(getPayloadPath());
     }
 
-    public static File getInternalStorageDir(Context context) {
-        return new File(context.getFilesDir(), PAYLOAD_DIR);
+    public static Path getInternalStorageDir(Context context) {
+        return context.getFilesDir().toPath().resolve(PAYLOAD_DIR);
     }
 
-    public static File getBackupFile(Context context) {
-        return new File(context.getFilesDir(), BACKUP_FILENAME);
+    public static Path getBackupFile(Context context) {
+        return context.getFilesDir().toPath().resolve(BACKUP_FILENAME);
     }
 
     private static Path getInstallationCompletedPath(Context context) {
-        return getInternalStorageDir(context).toPath().resolve(INSTALLATION_COMPLETED_FILENAME);
+        return getInternalStorageDir(context).resolve(INSTALLATION_COMPLETED_FILENAME);
     }
 
     public static boolean installImageFromExternalStorage(Context context) {
@@ -128,15 +126,6 @@
             Log.e(TAG, "installation failed", e);
             return false;
         }
-        if (!resolvePathInVmConfig(context)) {
-            Log.d(TAG, "resolving path failed");
-            try {
-                Files.deleteIfExists(Path.of(getVmConfigPath(context)));
-            } catch (IOException e) {
-                return false;
-            }
-            return false;
-        }
 
         // remove payload if installation is done.
         try {
@@ -149,46 +138,12 @@
         return createInstalledMarker(context);
     }
 
-    private static Function<String, String> getReplacer(Context context) {
-        Map<String, String> rules = new HashMap<>();
-        rules.put("\\$PAYLOAD_DIR", new File(context.getFilesDir(), PAYLOAD_DIR).toString());
-        rules.put("\\$USER_ID", String.valueOf(context.getUserId()));
-        rules.put("\\$PACKAGE_NAME", context.getPackageName());
-        String appDataDir = context.getDataDir().toString();
-        // TODO: remove this hack
-        if (context.getUserId() == 0) {
-            appDataDir = "/data/data/" + context.getPackageName();
-        }
-        rules.put("\\$APP_DATA_DIR", appDataDir);
-        return (s) -> {
-            for (Map.Entry<String, String> rule : rules.entrySet()) {
-                s = s.replaceAll(rule.getKey(), rule.getValue());
-            }
-            return s;
-        };
-    }
-
-    public static boolean resolvePathInVmConfig(Context context) {
-        try {
-            String replacedVmConfig =
-                    String.join(
-                            "\n",
-                            Files.readAllLines(Path.of(getVmConfigPath(context))).stream()
-                                    .map(getReplacer(context))
-                                    .toList());
-            Files.write(Path.of(getVmConfigPath(context)), replacedVmConfig.getBytes());
-            return true;
-        } catch (IOException e) {
-            return false;
-        }
-    }
-
-    public static File getRootfsFile(Context context) throws FileNotFoundException {
-        File file = new File(getInternalStorageDir(context), ROOTFS_FILENAME);
-        if (!file.exists()) {
-            Log.d(TAG, file.getAbsolutePath() + " - file not found");
+    public static Path getRootfsFile(Context context) throws FileNotFoundException {
+        Path path = getInternalStorageDir(context).resolve(ROOTFS_FILENAME);
+        if (!Files.exists(path)) {
+            Log.d(TAG, path.toString() + " - file not found");
             throw new FileNotFoundException("File not found: " + ROOTFS_FILENAME);
         }
-        return file;
+        return path;
     }
 }
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/InstallerService.java b/android/TerminalApp/java/com/android/virtualization/terminal/InstallerService.java
index 6fd3b5c..4b2e640 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/InstallerService.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/InstallerService.java
@@ -177,7 +177,7 @@
     }
 
     private void reLabelImagesSELinuxContext() {
-        File payloadFolder = InstallUtils.getInternalStorageDir(this);
+        File payloadFolder = InstallUtils.getInternalStorageDir(this).toFile();
 
         // The context should be u:object_r:privapp_data_file:s0:c35,c257,c512,c768
         // and we want to get s0:c35,c257,c512,c768 part
@@ -236,7 +236,7 @@
                 TarArchiveInputStream tar =
                         new TarArchiveInputStream(new GzipCompressorInputStream(wifiInputStream))) {
             ArchiveEntry entry;
-            Path baseDir = InstallUtils.getInternalStorageDir(this).toPath();
+            Path baseDir = InstallUtils.getInternalStorageDir(this);
             Files.createDirectories(baseDir);
             while ((entry = tar.getNextEntry()) != null) {
                 Path extractTo = baseDir.resolve(entry.getName());
@@ -261,10 +261,6 @@
             return false;
         }
 
-        if (!InstallUtils.resolvePathInVmConfig(this)) {
-            notifyError(getString(R.string.installer_error_unknown));
-            return false;
-        }
         return InstallUtils.createInstalledMarker(this);
     }
 
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
index bc5d037..a3b0577 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
@@ -61,7 +61,6 @@
 
 import com.google.android.material.appbar.MaterialToolbar;
 
-import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -70,6 +69,8 @@
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.UnknownHostException;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.security.KeyStore;
 import java.security.PrivateKey;
 import java.security.cert.X509Certificate;
@@ -334,33 +335,29 @@
                 .start();
     }
 
-    private void diskResize(File file, long sizeInBytes) throws IOException {
+    private void diskResize(Path path, long sizeInBytes) throws IOException {
         try {
             if (sizeInBytes == 0) {
                 return;
             }
-            String filePath = file.getAbsolutePath();
-            Log.d(TAG, "Disk-resize in progress for partition: " + filePath);
+            Log.d(TAG, "Disk-resize in progress for partition: " + path);
 
-            long currentSize = Os.stat(filePath).st_size;
-            runE2fsck(filePath);
+            long currentSize = Files.size(path);
+            runE2fsck(path);
             if (sizeInBytes > currentSize) {
-                allocateSpace(file, sizeInBytes);
+                allocateSpace(path, sizeInBytes);
             }
 
-            resizeFilesystem(filePath, sizeInBytes);
-        } catch (ErrnoException e) {
-            Log.e(TAG, "ErrnoException during disk resize", e);
-            throw new IOException("ErrnoException during disk resize", e);
+            resizeFilesystem(path, sizeInBytes);
         } catch (IOException e) {
             Log.e(TAG, "Failed to resize disk", e);
             throw e;
         }
     }
 
-    private static void allocateSpace(File file, long sizeInBytes) throws IOException {
+    private static void allocateSpace(Path path, long sizeInBytes) throws IOException {
         try {
-            RandomAccessFile raf = new RandomAccessFile(file, "rw");
+            RandomAccessFile raf = new RandomAccessFile(path.toFile(), "rw");
             FileDescriptor fd = raf.getFD();
             Os.posix_fallocate(fd, 0, sizeInBytes);
             raf.close();
@@ -371,17 +368,18 @@
         }
     }
 
-    private static void runE2fsck(String filePath) throws IOException {
+    private static void runE2fsck(Path path) throws IOException {
         try {
-            runCommand("/system/bin/e2fsck", "-y", "-f", filePath);
-            Log.d(TAG, "e2fsck completed: " + filePath);
+            String p = path.toAbsolutePath().toString();
+            runCommand("/system/bin/e2fsck", "-y", "-f", p);
+            Log.d(TAG, "e2fsck completed: " + path);
         } catch (IOException e) {
             Log.e(TAG, "Failed to run e2fsck", e);
             throw e;
         }
     }
 
-    private static void resizeFilesystem(String filePath, long sizeInBytes) throws IOException {
+    private static void resizeFilesystem(Path path, long sizeInBytes) throws IOException {
         long sizeInMB = sizeInBytes / (1024 * 1024);
         if (sizeInMB == 0) {
             Log.e(TAG, "Invalid size: " + sizeInBytes + " bytes");
@@ -389,8 +387,9 @@
         }
         String sizeArg = sizeInMB + "M";
         try {
-            runCommand("/system/bin/resize2fs", filePath, sizeArg);
-            Log.d(TAG, "resize2fs completed: " + filePath + ", size: " + sizeArg);
+            String p = path.toAbsolutePath().toString();
+            runCommand("/system/bin/resize2fs", p, sizeArg);
+            Log.d(TAG, "resize2fs completed: " + path + ", size: " + sizeArg);
         } catch (IOException e) {
             Log.e(TAG, "Failed to run resize2fs", e);
             throw e;
@@ -591,10 +590,11 @@
         return (long) Math.ceil(((double) diskSize) / diskSizeStep) * diskSizeStep;
     }
 
-    public static long getMinFilesystemSize(File file) throws IOException, NumberFormatException {
+    public static long getMinFilesystemSize(Path path) throws IOException, NumberFormatException {
         try {
-            runE2fsck(file.getAbsolutePath());
-            String result = runCommand("/system/bin/resize2fs", "-P", file.getAbsolutePath());
+            runE2fsck(path);
+            String p = path.toAbsolutePath().toString();
+            String result = runCommand("/system/bin/resize2fs", "-P", p);
             // The return value is the number of 4k block
             long minSize = Long.parseLong(
                     result.lines().toArray(String[]::new)[1].substring(42)) * 4 * 1024;
@@ -605,13 +605,13 @@
         }
     }
 
-    private static long getFilesystemSize(File file) throws ErrnoException {
-        return Os.stat(file.getAbsolutePath()).st_size;
+    private static long getFilesystemSize(Path fsPath) throws ErrnoException {
+        return Os.stat(fsPath.toAbsolutePath().toString()).st_size;
     }
 
     private void resizeDiskIfNecessary() {
         try {
-            File file = InstallUtils.getRootfsFile(this);
+            Path file = InstallUtils.getRootfsFile(this);
             SharedPreferences sharedPref = this.getSharedPreferences(
                     getString(R.string.preference_file_key), Context.MODE_PRIVATE);
             SharedPreferences.Editor editor = sharedPref.edit();
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsRecoveryActivity.kt b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsRecoveryActivity.kt
index e291b57..4b6bf96 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsRecoveryActivity.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsRecoveryActivity.kt
@@ -27,6 +27,7 @@
 import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import com.google.android.material.snackbar.Snackbar
 import java.io.IOException
+import java.nio.file.Files
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
@@ -55,7 +56,7 @@
             dialog.show()
         }
         val resetBackupCard = findViewById<View>(R.id.settings_recovery_reset_backup_card)
-        resetBackupCard.isVisible = InstallUtils.getBackupFile(this).exists()
+        resetBackupCard.isVisible = Files.exists(InstallUtils.getBackupFile(this))
 
         resetBackupCard.setOnClickListener {
             val dialog = MaterialAlertDialogBuilder(this)
@@ -73,7 +74,10 @@
     }
 
     private fun removeBackup(): Unit {
-        if (!InstallUtils.getBackupFile(this@SettingsRecoveryActivity).delete()) {
+        val file = InstallUtils.getBackupFile(this@SettingsRecoveryActivity)
+        try {
+            Files.deleteIfExists(file)
+        } catch (e: IOException) {
             Snackbar.make(
                 findViewById(android.R.id.content),
                 R.string.settings_recovery_error_during_removing_backup,
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.java b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.java
index 652af8e..879b5e5 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.java
@@ -50,6 +50,7 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.net.InetSocketAddress;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Objects;
 import java.util.Set;
@@ -151,13 +152,13 @@
         }
         mExecutorService = Executors.newCachedThreadPool();
 
-        ConfigJson json = ConfigJson.from(InstallUtils.getVmConfigPath(this));
+        ConfigJson json = ConfigJson.from(this, InstallUtils.getVmConfigPath(this));
         VirtualMachineConfig.Builder configBuilder = json.toConfigBuilder(this);
         VirtualMachineCustomImageConfig.Builder customImageConfigBuilder =
                 json.toCustomImageConfigBuilder(this);
-        File backupFile = InstallUtils.getBackupFile(this);
-        if (backupFile.exists()) {
-            customImageConfigBuilder.addDisk(Disk.RWDisk(backupFile.getPath()));
+        Path backupFile = InstallUtils.getBackupFile(this);
+        if (Files.exists(backupFile)) {
+            customImageConfigBuilder.addDisk(Disk.RWDisk(backupFile.toString()));
             configBuilder.setCustomImageConfig(customImageConfigBuilder.build());
         }
         VirtualMachineConfig config = configBuilder.build();