1 /* <lambda>null2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.virtualization.terminal 17 18 import android.os.Build 19 import android.os.Environment 20 import android.util.Log 21 import com.android.virtualization.terminal.MainActivity.Companion.TAG 22 import java.io.BufferedInputStream 23 import java.io.FileInputStream 24 import java.io.IOException 25 import java.io.InputStream 26 import java.lang.RuntimeException 27 import java.net.HttpURLConnection 28 import java.net.MalformedURLException 29 import java.net.URL 30 import java.nio.file.Files 31 import java.nio.file.Path 32 import java.nio.file.StandardCopyOption 33 import java.util.function.Function 34 import org.apache.commons.compress.archivers.ArchiveEntry 35 import org.apache.commons.compress.archivers.tar.TarArchiveInputStream 36 import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream 37 38 /** 39 * ImageArchive models the archive file (images.tar.gz) where VM payload files are in. This class 40 * provides methods for handling the archive file, most importantly installing it. 41 */ 42 internal class ImageArchive { 43 // Only one can be non-null 44 private sealed class Source<out A, out B> 45 46 private data class UrlSource<out Url>(val value: Url) : Source<Url, Nothing>() 47 48 private data class PathSource<out Path>(val value: Path) : Source<Nothing, Path>() 49 50 private val source: Source<URL, Path> 51 52 private constructor(url: URL) { 53 source = UrlSource(url) 54 } 55 56 private constructor(path: Path) { 57 source = PathSource(path) 58 } 59 60 /** Tests if ImageArchive exists on the medium. */ 61 fun exists(): Boolean { 62 return when (source) { 63 is UrlSource -> true 64 is PathSource -> Files.exists(source.value) 65 } 66 } 67 68 /** Returns path to the archive. */ 69 fun getPath(): String { 70 return when (source) { 71 is UrlSource -> source.value.toString() 72 is PathSource -> source.value.toString() 73 } 74 } 75 76 /** Returns size of the archive in bytes */ 77 @Throws(IOException::class) 78 fun getSize(): Long { 79 check(exists()) { "Cannot get size of non existing archive" } 80 return when (source) { 81 is UrlSource -> { 82 val conn = source.value.openConnection() as HttpURLConnection 83 try { 84 conn.requestMethod = "HEAD" 85 conn.getInputStream() 86 return conn.contentLength.toLong() 87 } finally { 88 conn.disconnect() 89 } 90 } 91 is PathSource -> Files.size(source.value) 92 } 93 } 94 95 @Throws(IOException::class) 96 private fun getInputStream(filter: Function<InputStream, InputStream>?): InputStream? { 97 val bufStream = 98 BufferedInputStream( 99 when (source) { 100 is UrlSource -> source.value.openStream() 101 is PathSource -> FileInputStream(source.value.toFile()) 102 } 103 ) 104 return filter?.apply(bufStream) ?: bufStream 105 } 106 107 /** 108 * Installs this ImageArchive to a directory pointed by path. filter can be supplied to provide 109 * an additional input stream which will be used during the installation. 110 */ 111 @Throws(IOException::class) 112 fun installTo(dir: Path, filter: Function<InputStream, InputStream>?) { 113 val source = 114 when (source) { 115 is PathSource -> source.value.toString() 116 is UrlSource -> source.value.toString() 117 } 118 Log.d(TAG, "Installing. source: $source, destination: $dir") 119 TarArchiveInputStream(GzipCompressorInputStream(getInputStream(filter))).use { tarStream -> 120 Files.createDirectories(dir) 121 var entry: ArchiveEntry? 122 while ((tarStream.nextEntry.also { entry = it }) != null) { 123 val to = dir.resolve(entry!!.getName()) 124 if (Files.isDirectory(to)) { 125 Files.createDirectories(to) 126 continue 127 } 128 Files.copy(tarStream, to, StandardCopyOption.REPLACE_EXISTING) 129 } 130 } 131 commitInstallationAt(dir) 132 } 133 134 @Throws(IOException::class) 135 private fun commitInstallationAt(dir: Path) { 136 // To save storage, delete the source archive on the disk. 137 if (source is PathSource) { 138 Files.deleteIfExists(source.value) 139 } 140 141 // Mark the completion 142 val marker = dir.resolve(InstalledImage.MARKER_FILENAME) 143 Files.createFile(marker) 144 } 145 146 companion object { 147 private const val DIR_IN_SDCARD = "linux" 148 private const val ARCHIVE_NAME = "images.tar.gz" 149 private val BUILD_TAG = Integer.toString(Build.VERSION.SDK_INT_FULL) 150 private val HOST_URL = "https://dl.google.com/android/ferrochrome/$BUILD_TAG" 151 152 fun getSdcardPathForTesting(): Path { 153 return Environment.getExternalStoragePublicDirectory(DIR_IN_SDCARD).toPath() 154 } 155 156 /** 157 * Creates ImageArchive which is located in the sdcard. This archive is for testing only. 158 */ 159 fun fromSdCard(): ImageArchive { 160 return ImageArchive(getSdcardPathForTesting().resolve(ARCHIVE_NAME)) 161 } 162 163 /** 164 * Creates ImageArchive which is hosted in the Google server. This is the official archive. 165 */ 166 fun fromInternet(): ImageArchive { 167 val arch = 168 if (listOf<String?>(*Build.SUPPORTED_ABIS).contains("x86_64")) "x86_64" 169 else "aarch64" 170 try { 171 return ImageArchive(URL("$HOST_URL/$arch/$ARCHIVE_NAME")) 172 } catch (e: MalformedURLException) { 173 // cannot happen 174 throw RuntimeException(e) 175 } 176 } 177 178 /** 179 * Creates ImageArchive from either SdCard or Internet. SdCard is used only when the build 180 * is debuggable and the file actually exists. 181 */ 182 fun getDefault(): ImageArchive { 183 val archive = fromSdCard() 184 return if (Build.isDebuggable() && archive.exists()) { 185 archive 186 } else { 187 fromInternet() 188 } 189 } 190 } 191 } 192