diff --git a/bbootimg/build.gradle.kts b/bbootimg/build.gradle.kts index 9e7c3e7..8243c89 100644 --- a/bbootimg/build.gradle.kts +++ b/bbootimg/build.gradle.kts @@ -15,7 +15,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - kotlin("jvm") version "2.0.20" + kotlin("jvm") version "2.1.21" application } @@ -62,11 +62,13 @@ java { targetCompatibility = JavaVersion.VERSION_11 } -tasks.withType().all { - kotlinOptions { - freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn" - freeCompilerArgs += "-opt-in=kotlin.ExperimentalUnsignedTypes" - jvmTarget = "11" +tasks.withType().configureEach { + compilerOptions { + freeCompilerArgs.addAll( + "-opt-in=kotlin.RequiresOptIn", + "-opt-in=kotlin.ExperimentalUnsignedTypes" + ) + jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11) } } diff --git a/bbootimg/src/main/kotlin/avb/Avb.kt b/bbootimg/src/main/kotlin/avb/Avb.kt index a8e01a1..2185a08 100644 --- a/bbootimg/src/main/kotlin/avb/Avb.kt +++ b/bbootimg/src/main/kotlin/avb/Avb.kt @@ -206,7 +206,7 @@ class Avb { val readBackInfo = ObjectMapper().readValue(File(getJsonFileName(fileName)), AVBInfo::class.java) val intermediateDir = Helper.joinPath(Helper.prop("workDir")!!, "intermediate") val newHashDesc = if (File(intermediateDir).exists()) { - AVBInfo.parseFrom(Dumpling(Helper.joinPath(intermediateDir, "$fileName.signed"))) + AVBInfo.parseFrom(Dumpling(Helper.joinPath(intermediateDir, File("$fileName.signed").name))) } else { //FIXME: before BootV2 supports abe mode AVBInfo.parseFrom(Dumpling("$fileName.signed")) diff --git a/bbootimg/src/main/kotlin/bootimg/Common.kt b/bbootimg/src/main/kotlin/bootimg/Common.kt index ed99534..6a58cea 100644 --- a/bbootimg/src/main/kotlin/bootimg/Common.kt +++ b/bbootimg/src/main/kotlin/bootimg/Common.kt @@ -20,6 +20,7 @@ import cfig.bootimg.cpio.AndroidCpio import rom.fdt.DTC import cfig.helper.Helper import cfig.helper.ZipHelper +import cfig.packable.BootImgParser import cfig.utils.KernelExtractor import com.github.freva.asciitable.HorizontalAlign import org.apache.commons.exec.CommandLine @@ -32,6 +33,8 @@ import java.io.File import java.nio.file.Files import java.nio.file.Paths import java.io.FileInputStream +import java.io.FileOutputStream +import java.io.IOException import java.lang.NumberFormatException import java.nio.ByteBuffer import java.nio.ByteOrder @@ -52,6 +55,12 @@ class Common { private val log = LoggerFactory.getLogger(Common::class.java) private const val MAX_ANDROID_VER = 11 + val loadProperties: (String) -> Properties = { fileName -> + Properties().apply { + File(fileName).inputStream().use { load(it) } + } + } + @Throws(IllegalArgumentException::class) fun packOsVersion(x: String?): Int { if (x.isNullOrBlank()) return 0 @@ -423,19 +432,24 @@ class Common { ) } - fun printPackSummary(imageName: String) { + fun printPackSummary(imageName: String, outFile: String? = null) { val prints: MutableList> = mutableListOf() val tableHeader = de.vandermeer.asciitable.AsciiTable().apply { addRule(); addRow("What", "Where"); addRule() } val tab = de.vandermeer.asciitable.AsciiTable().let { it.addRule() - if (File("$imageName.signed").exists()) { - it.addRow("re-packed $imageName", "$imageName.signed") - prints.add(Pair("re-packed $imageName", "$imageName.signed")) + if (outFile != null) { + it.addRow("re-packed $imageName", outFile) + prints.add(Pair("re-packed $imageName", outFile)) } else { - it.addRow("re-packed $imageName", "$imageName.clear") - prints.add(Pair("re-packed $imageName", "$imageName.clear")) + if (File("$imageName.signed").exists()) { + it.addRow("re-packed $imageName", "$imageName.signed") + prints.add(Pair("re-packed $imageName", "$imageName.signed")) + } else { + it.addRow("re-packed $imageName", "$imageName.clear") + prints.add(Pair("re-packed $imageName", "$imageName.clear")) + } } it.addRule() it @@ -457,6 +471,31 @@ class Common { } } + /* + be_caller_dir: set in "be" script, to support out of tree invocation + */ + fun shortenPath(fullPath: String, inCurrentPath: String = System.getProperty("user.dir")): String { + val currentPath = System.getenv("be_caller_dir") ?: inCurrentPath + val full = Paths.get(fullPath).normalize().toAbsolutePath() + val base = Paths.get(currentPath).normalize().toAbsolutePath() + return try { + base.relativize(full).toString() + } catch (e: IllegalArgumentException) { + full.toString() + } + } + + fun String.toShortenPath(inCurrentPath: String = System.getProperty("user.dir")): String { + val currentPath = System.getenv("be_caller_dir") ?: inCurrentPath + val full = Paths.get(this).normalize().toAbsolutePath() + val base = Paths.get(currentPath).normalize().toAbsolutePath() + return try { + base.relativize(full).toString() + } catch (e: IllegalArgumentException) { + full.toString() + } + } + fun printPackSummaryInternal(imageName: String) { val prints: MutableList> = mutableListOf() val tableHeader = de.vandermeer.asciitable.AsciiTable().apply { @@ -485,5 +524,47 @@ class Common { log.info("\n\t\t\tPack Summary of ${imageName}\n{}\n{}", tableHeader.render(), tab.render()) } } + + fun createWorkspaceIni(fileName: String, iniFileName: String = "workspace.ini", prefix: String? = null) { + log.trace("create workspace file") + val workDir = Helper.prop("workDir") + val workspaceFile = File(workDir, iniFileName) + + try { + if (prefix.isNullOrBlank()) { + // override existing file entirely when prefix is null or empty + val props = Properties().apply { + setProperty("file", fileName) + setProperty("workDir", workDir) + setProperty("role", File(fileName).name) + } + FileOutputStream(workspaceFile).use { out -> + props.store(out, "unpackInternal configuration (overridden)") + } + log.info("workspace file overridden: ${workspaceFile.canonicalPath}") + } else { + // merge into existing (or create new) with prefixed keys + val props = Properties().apply { + if (workspaceFile.exists()) { + FileInputStream(workspaceFile).use { load(it) } + } + } + + fun key(name: String) = "${prefix.trim()}.$name" + props.setProperty(key("file"), fileName) + props.setProperty(key("workDir"), workDir) + props.setProperty(key("role"), File(fileName).name) + + FileOutputStream(workspaceFile).use { out -> + props.store(out, "unpackInternal configuration (with prefix='$prefix')") + } + log.info("workspace file created/updated with prefix '$prefix': ${workspaceFile.canonicalPath}") + } + } catch (e: IOException) { + log.error("error writing workspace file: ${e.message}") + } + + log.trace("create workspace file done") + } } } diff --git a/bbootimg/src/main/kotlin/bootimg/Signer.kt b/bbootimg/src/main/kotlin/bootimg/Signer.kt index 7609967..600a29a 100644 --- a/bbootimg/src/main/kotlin/bootimg/Signer.kt +++ b/bbootimg/src/main/kotlin/bootimg/Signer.kt @@ -30,8 +30,58 @@ class Signer { companion object { private val log = LoggerFactory.getLogger(Signer::class.java) + fun signAVB2(inFile: String, //"$output.clear" + outFile: String, //"$output.signed" + aiFile: String, //AVBInfo + imageSize: Long, + avbtool: String) { + log.info("Adding hash_footer with verified-boot 2.0 style: $inFile -> $outFile") + val ai = ObjectMapper().readValue(File(aiFile), AVBInfo::class.java) + val alg = Algorithms.get(ai.header!!.algorithm_type) + val bootDesc = ai.auxBlob!!.hashDescriptors[0] + val newAvbInfo = ObjectMapper().readValue(File(aiFile), AVBInfo::class.java) + + //our signer + File(inFile).copyTo(File(outFile), overwrite = true) + Avb().addHashFooter(outFile, + imageSize, + partition_name = bootDesc.partition_name, + newAvbInfo = newAvbInfo) + //original signer + val cmdPrefix = if (EnvironmentVerifier().isWindows) "python " else "" + CommandLine.parse("$cmdPrefix$avbtool add_hash_footer").apply { + addArguments("--image ${outFile}2") //boot.img.signed2 + addArguments("--flags ${ai.header!!.flags}") + addArguments("--partition_size ${imageSize}") + addArguments("--salt ${Helper.toHexString(bootDesc.salt)}") + addArguments("--partition_name ${bootDesc.partition_name}") + addArguments("--hash_algorithm ${bootDesc.hash_algorithm}") + addArguments("--algorithm ${alg!!.name}") + addArguments("--rollback_index ${ai.header!!.rollback_index}") + if (alg.defaultKey.isNotBlank()) { + addArguments("--key ${alg.defaultKey}") + } + newAvbInfo.auxBlob?.let { newAuxblob -> + newAuxblob.propertyDescriptors.forEach { newProp -> + addArguments(arrayOf("--prop", "${newProp.key}:${newProp.value}")) + } + } + addArgument("--internal_release_string") + addArgument(ai.header!!.release_string, false) + log.info(this.toString()) + + File(inFile).copyTo(File("${outFile}2"), overwrite = true) + DefaultExecutor().execute(this) + } + Helper.assertFileEquals(outFile, "${outFile}2") + File("${outFile}2").delete() + //TODO: decide what to verify + //Parser.verifyAVBIntegrity(cfg.info.output + ".signed", avbtool) + //Parser.verifyAVBIntegrity(cfg.info.output + ".signed2", avbtool) + } + fun signAVB(output: String, imageSize: Long, avbtool: String) { - log.info("Adding hash_footer with verified-boot 2.0 style") + log.info("Adding hash_footer with verified-boot 2.0 style: $output") val ai = ObjectMapper().readValue(File(getJsonFileName(output)), AVBInfo::class.java) val alg = Algorithms.get(ai.header!!.algorithm_type) val bootDesc = ai.auxBlob!!.hashDescriptors[0] diff --git a/bbootimg/src/main/kotlin/bootimg/cpio/AndroidCpio.kt b/bbootimg/src/main/kotlin/bootimg/cpio/AndroidCpio.kt index dd40d46..59e5925 100644 --- a/bbootimg/src/main/kotlin/bootimg/cpio/AndroidCpio.kt +++ b/bbootimg/src/main/kotlin/bootimg/cpio/AndroidCpio.kt @@ -194,6 +194,7 @@ class AndroidCpio { val rounded = Helper.round_to_multiple(len, 256) //file in page 256 if (len != rounded) { FileOutputStream(outFile, true).use { fos -> + log.info("cpio padding size: " + (rounded - len) + " bytes") fos.write(ByteArray((rounded - len).toInt())) } } diff --git a/bbootimg/src/main/kotlin/bootimg/v2/BootV2.kt b/bbootimg/src/main/kotlin/bootimg/v2/BootV2.kt index 4d9934a..9530d06 100644 --- a/bbootimg/src/main/kotlin/bootimg/v2/BootV2.kt +++ b/bbootimg/src/main/kotlin/bootimg/v2/BootV2.kt @@ -16,15 +16,14 @@ package cfig.bootimg.v2 import avb.AVBInfo import cfig.Avb -import cfig.bootimg.Common as C import cfig.bootimg.Common import cfig.bootimg.Common.Companion.deleleIfExists +import cfig.bootimg.Common.Companion.shortenPath import cfig.bootimg.Signer import cfig.helper.Dumpling import cfig.helper.Helper import cfig.helper.ZipHelper import cfig.packable.VBMetaParser -import rom.fdt.DTC import cfig.utils.EnvironmentVerifier import com.fasterxml.jackson.databind.ObjectMapper import com.github.freva.asciitable.HorizontalAlign @@ -32,11 +31,13 @@ import de.vandermeer.asciitable.AsciiTable import org.apache.commons.exec.CommandLine import org.apache.commons.exec.DefaultExecutor import org.slf4j.LoggerFactory +import rom.fdt.DTC import java.io.File import java.io.FileInputStream import java.io.FileOutputStream import java.nio.ByteBuffer import java.nio.ByteOrder +import cfig.bootimg.Common as C data class BootV2( var info: MiscInfo = MiscInfo(), @@ -95,7 +96,9 @@ data class BootV2( companion object { private val log = LoggerFactory.getLogger(BootV2::class.java) - private val workDir = Helper.prop("workDir") + private val workDir: () -> String = { + Helper.prop("workDir")!! + } private val mapper = ObjectMapper() private val dtsSuffix = Helper.prop("config.dts_suffix") @@ -128,7 +131,7 @@ data class BootV2( } } ret.kernel.let { theKernel -> - theKernel.file = File(workDir, "kernel").path + theKernel.file = File(workDir(), "kernel").path theKernel.size = bh2.kernelLength theKernel.loadOffset = bh2.kernelOffset theKernel.position = ret.getKernelPosition() @@ -138,28 +141,28 @@ data class BootV2( theRamdisk.loadOffset = bh2.ramdiskOffset theRamdisk.position = ret.getRamdiskPosition() if (bh2.ramdiskLength > 0) { - theRamdisk.file = File(workDir, "ramdisk.img").path + theRamdisk.file = File(workDir(), "ramdisk.img").path } } if (bh2.secondBootloaderLength > 0) { ret.secondBootloader = CommArgs() ret.secondBootloader!!.size = bh2.secondBootloaderLength ret.secondBootloader!!.loadOffset = bh2.secondBootloaderOffset - ret.secondBootloader!!.file = File(workDir, "second").path + ret.secondBootloader!!.file = File(workDir(), "second").path ret.secondBootloader!!.position = ret.getSecondBootloaderPosition() } if (bh2.recoveryDtboLength > 0) { ret.recoveryDtbo = CommArgsLong() ret.recoveryDtbo!!.size = bh2.recoveryDtboLength ret.recoveryDtbo!!.loadOffset = bh2.recoveryDtboOffset //Q - ret.recoveryDtbo!!.file = File(workDir, "recoveryDtbo").path + ret.recoveryDtbo!!.file = File(workDir(), "recoveryDtbo").path ret.recoveryDtbo!!.position = ret.getRecoveryDtboPosition() } if (bh2.dtbLength > 0) { ret.dtb = DtbArgsLong() ret.dtb!!.size = bh2.dtbLength ret.dtb!!.loadOffset = bh2.dtbOffset //Q - ret.dtb!!.file = File(workDir, "dtb").path + ret.dtb!!.file = File(workDir(), "dtb").path ret.dtb!!.position = ret.getDtbPosition() } } @@ -206,7 +209,7 @@ data class BootV2( fun extractImages(): BootV2 { //info - mapper.writerWithDefaultPrettyPrinter().writeValue(File(workDir + info.json), this) + mapper.writerWithDefaultPrettyPrinter().writeValue(File(workDir(), info.json), this) //kernel if (kernel.size > 0) { Common.dumpKernel(Helper.Slice(info.output, kernel.position.toInt(), kernel.size, kernel.file!!)) @@ -220,15 +223,17 @@ data class BootV2( //ramdisk if (this.ramdisk.size > 0) { val fmt = C.dumpRamdisk( - Helper.Slice(info.output, ramdisk.position.toInt(), ramdisk.size, ramdisk.file!!), File(workDir, "root").path + Helper.Slice(info.output, ramdisk.position.toInt(), ramdisk.size, ramdisk.file!!), + File(workDir(), "root").path ) this.ramdisk.file = this.ramdisk.file!! + ".$fmt" if (fmt == "xz") { - val checkType = ZipHelper.xzStreamFlagCheckTypeToString(ZipHelper.parseStreamFlagCheckType(this.ramdisk.file!!)) + val checkType = + ZipHelper.xzStreamFlagCheckTypeToString(ZipHelper.parseStreamFlagCheckType(this.ramdisk.file!!)) this.ramdisk.xzFlags = checkType } //dump info again - mapper.writerWithDefaultPrettyPrinter().writeValue(File(workDir + this.info.json), this) + mapper.writerWithDefaultPrettyPrinter().writeValue(File(workDir(), this.info.json), this) } //second bootloader secondBootloader?.let { @@ -254,7 +259,7 @@ data class BootV2( this.dtb!!.dtbList = DTC.parseMultiple(dtb!!.file!!) log.info("dtb sz = " + this.dtb!!.dtbList.size) //dump info again - mapper.writerWithDefaultPrettyPrinter().writeValue(File(workDir + info.json), this) + mapper.writerWithDefaultPrettyPrinter().writeValue(File(workDir(), info.json), this) //dump dtb items DTC.extractMultiple(dtb!!.file!!, this.dtb!!.dtbList) @@ -285,8 +290,8 @@ data class BootV2( } val tab = AsciiTable().let { it.addRule() - it.addRow("image info", workDir + info.output.removeSuffix(".img") + ".json") - prints.add(Pair("image info", workDir + info.output.removeSuffix(".img") + ".json")) + it.addRow("image info", shortenPath( File(Helper.prop("workDir"), info.output.removeSuffix(".img") + ".json").path)) + prints.add(Pair("image info", shortenPath(File(workDir(), info.output.removeSuffix(".img") + ".json").path))) if (this.info.verify.startsWith("VB2.0")) { it.addRule() val verifyStatus = if (this.info.verify.contains("PASS")) { @@ -295,8 +300,8 @@ data class BootV2( "verify fail" } Avb.getJsonFileName(info.output).let { jsonFile -> - it.addRow("AVB info [$verifyStatus]", jsonFile) - prints.add(Pair("AVB info [$verifyStatus]", jsonFile)) + it.addRow("AVB info [$verifyStatus]", shortenPath(jsonFile)) + prints.add(Pair("AVB info [$verifyStatus]", shortenPath(jsonFile))) if (File(jsonFile).exists()) { mapper.readValue(File(jsonFile), AVBInfo::class.java).let { ai -> val inspectRet = Avb.inspectKey(ai) @@ -311,11 +316,11 @@ data class BootV2( //kernel it.addRule() if (this.kernel.size > 0) { - it.addRow("kernel", this.kernel.file) + it.addRow("kernel", shortenPath(this.kernel.file!!)) } else { //only for ramdisk.img, Issue #122 it.addRow("kernel", "NONE") } - prints.add(Pair("kernel", this.kernel.file.toString())) + prints.add(Pair("kernel", shortenPath(this.kernel.file.toString()))) File(Helper.prop("kernelVersionFile")).let { kernelVersionFile -> if (kernelVersionFile.exists()) { it.addRow("\\-- version " + kernelVersionFile.readLines().toString(), kernelVersionFile.path) @@ -331,10 +336,10 @@ data class BootV2( //ramdisk if (this.ramdisk.size > 0) { it.addRule() - it.addRow("ramdisk", this.ramdisk.file) - prints.add(Pair("ramdisk", this.ramdisk.file.toString())) - it.addRow("\\-- extracted ramdisk rootfs", File(workDir, "root").path) - prints.add(Pair("\\-- extracted ramdisk rootfs", File(workDir, "root").path)) + it.addRow("ramdisk", shortenPath(this.ramdisk.file!!)) + prints.add(Pair("ramdisk", shortenPath(this.ramdisk.file.toString()))) + it.addRow("\\-- extracted ramdisk rootfs", shortenPath(File(workDir(), "root").path)) + prints.add(Pair("\\-- extracted ramdisk rootfs", shortenPath(File(workDir(), "root").path))) } //second this.secondBootloader?.let { theSecondBootloader -> @@ -357,11 +362,11 @@ data class BootV2( if (theDtb.size > 0) { val dtbCount = this.dtb!!.dtbList.size it.addRule() - it.addRow("dtb", theDtb.file) - prints.add(Pair("dtb", theDtb.file.toString())) + it.addRow("dtb", theDtb.file?.let { fullPath -> shortenPath(fullPath) }) + prints.add(Pair("dtb", shortenPath(theDtb.file.toString()))) if (File(theDtb.file + ".0.${dtsSuffix}").exists()) { - it.addRow("\\-- decompiled dts [$dtbCount]", theDtb.file + ".*.${dtsSuffix}") - prints.add(Pair("\\-- decompiled dts [$dtbCount]", theDtb.file + ".*.${dtsSuffix}")) + it.addRow("\\-- decompiled dts [$dtbCount]", shortenPath(theDtb.file!!) + ".*.${dtsSuffix}") + prints.add(Pair("\\-- decompiled dts [$dtbCount]", shortenPath(theDtb.file!!) + ".*.${dtsSuffix}")) } } } @@ -372,8 +377,8 @@ data class BootV2( val tabVBMeta = AsciiTable().let { if (File("vbmeta.img").exists()) { it.addRule() - it.addRow("vbmeta.img", Avb.getJsonFileName("vbmeta.img")) - prints.add(Pair("vbmeta.img", Avb.getJsonFileName("vbmeta.img"))) + it.addRow("vbmeta.img", shortenPath(Avb.getJsonFileName("vbmeta.img"))) + prints.add(Pair("vbmeta.img", shortenPath(Avb.getJsonFileName("vbmeta.img")))) it.addRule() "\n" + it.render() } else { @@ -382,15 +387,17 @@ data class BootV2( } if (EnvironmentVerifier().isWindows) { - log.info("\n" + - com.github.freva.asciitable.AsciiTable.getTable( - com.github.freva.asciitable.AsciiTable.BASIC_ASCII, - prints, mutableListOf( - com.github.freva.asciitable.Column().header("What") - .dataAlign(HorizontalAlign.LEFT) - .with { it.first }, - com.github.freva.asciitable.Column().header("Where").with { it.second }) - )) + log.info( + "\n" + + com.github.freva.asciitable.AsciiTable.getTable( + com.github.freva.asciitable.AsciiTable.BASIC_ASCII, + prints, mutableListOf( + com.github.freva.asciitable.Column().header("What") + .dataAlign(HorizontalAlign.LEFT) + .with { it.first }, + com.github.freva.asciitable.Column().header("Where").with { it.second }) + ) + ) } else { log.info( "\n\t\t\tUnpack Summary of ${info.output}\n{}\n{}{}", @@ -433,14 +440,14 @@ data class BootV2( ramdisk.file = null ramdisk.loadOffset = 0 } else { - if (File(this.ramdisk.file!!).exists() && !File(workDir + "root").exists()) { + if (File(this.ramdisk.file!!).exists() && !File(workDir(), "root").exists()) { //do nothing if we have ramdisk.img.gz but no /root log.warn("Use prebuilt ramdisk file: ${this.ramdisk.file}") } else { File(this.ramdisk.file!!).deleleIfExists() File(this.ramdisk.file!!.removeSuffix(".gz")).deleleIfExists() //Common.packRootfs("${workDir}/root", this.ramdisk.file!!, Common.parseOsMajor(info.osVersion.toString())) - Common.packRootfs(File(workDir, "root").path, this.ramdisk.file!!, this.ramdisk.xzFlags) + Common.packRootfs(File(workDir(), "root").path, this.ramdisk.file!!, this.ramdisk.xzFlags) } this.ramdisk.size = File(this.ramdisk.file!!).length().toInt() } @@ -466,18 +473,21 @@ data class BootV2( 0 -> { Common.hashFileAndSize(kernel.file, ramdisk.file, secondBootloader?.file) } + 1 -> { Common.hashFileAndSize( kernel.file, ramdisk.file, secondBootloader?.file, recoveryDtbo?.file ) } + 2 -> { Common.hashFileAndSize( kernel.file, ramdisk.file, secondBootloader?.file, recoveryDtbo?.file, dtb?.file ) } + else -> { throw IllegalArgumentException("headerVersion ${info.headerVersion} illegal") } diff --git a/bbootimg/src/main/kotlin/bootimg/v3/BootV3.kt b/bbootimg/src/main/kotlin/bootimg/v3/BootV3.kt index 3506d93..0f2dfd1 100644 --- a/bbootimg/src/main/kotlin/bootimg/v3/BootV3.kt +++ b/bbootimg/src/main/kotlin/bootimg/v3/BootV3.kt @@ -22,6 +22,7 @@ import cfig.bootimg.Common import cfig.utils.EnvironmentVerifier import cfig.bootimg.Common.Companion.deleleIfExists import cfig.bootimg.Common.Companion.getPaddingSize +import cfig.bootimg.Common.Companion.shortenPath import cfig.bootimg.Signer import cfig.helper.Helper import cfig.helper.Dumpling @@ -228,22 +229,26 @@ data class BootV3( } if (fileName != info.role) { - Helper.setProp("out.file", fileName) if (bSigningNeeded) { + log.info("x1") + Helper.setProp("out.file", "$fileName.signed") //@formatter:off File(Helper.joinPath(Helper.prop("intermediateDir")!!, info.role + ".signed")) - .copyTo(File(fileName), true) + .copyTo(File(Helper.prop("out.file")!!), true) //@formatter:on - log.info("Signed image saved as $fileName") + log.info("Signed image saved as " + Helper.prop("out.file")) } else { + log.info("x2") + Helper.setProp("out.file", fileName) //@formatter:off File(Helper.joinPath(Helper.prop("intermediateDir")!!, info.role + ".clear")) - .copyTo(File(fileName), true) + .copyTo(File(Helper.prop("out.file")!!), true) //@formatter:on - log.info("Unsigned image saved as $fileName") + log.info("Unsigned image saved as " + Helper.prop("out.file")) } } else { if (bSigningNeeded) { + log.info("x3") Helper.setProp("out.file", info.role + ".signed") //@formatter:off File(Helper.joinPath(Helper.prop("intermediateDir")!!, info.role + ".signed")) @@ -251,6 +256,7 @@ data class BootV3( //@formatter:on log.info("Signed image saved as ${info.role}.signed") } else { + log.info("x4") Helper.setProp("out.file", info.role + ".clear") //@formatter:off File(Helper.joinPath(Helper.prop("intermediateDir")!!, info.role + ".clear")) @@ -385,27 +391,27 @@ data class BootV3( } val tab = AsciiTable().let { it.addRule() - it.addRow("image info", Helper.joinPath(workDir!!, info.role.removeSuffix(".img") + ".json")) - prints.add(Pair("image info", Helper.joinPath(workDir, info.role.removeSuffix(".img") + ".json"))) + it.addRow("image info", shortenPath(Helper.joinPath(workDir!!, info.role.removeSuffix(".img") + ".json"))) + prints.add(Pair("image info", shortenPath(Helper.joinPath(workDir, info.role.removeSuffix(".img") + ".json")))) it.addRule() if (this.kernel.size > 0) { - it.addRow("kernel", this.kernel.file) - prints.add(Pair("kernel", this.kernel.file)) + it.addRow("kernel", shortenPath(this.kernel.file)) + prints.add(Pair("kernel", shortenPath(this.kernel.file))) File(Helper.joinPath(workDir, Helper.prop("kernelVersionStem")!!)).let { kernelVersionFile -> if (kernelVersionFile.exists()) { - it.addRow("\\-- version " + kernelVersionFile.readLines().toString(), kernelVersionFile.path) + it.addRow("\\-- version " + kernelVersionFile.readLines().toString(), shortenPath( kernelVersionFile.path)) prints.add( Pair( "\\-- version " + kernelVersionFile.readLines().toString(), - kernelVersionFile.path + shortenPath(kernelVersionFile.path) ) ) } } File(Helper.joinPath(workDir, Helper.prop("kernelConfigStem")!!)).let { kernelConfigFile -> if (kernelConfigFile.exists()) { - it.addRow("\\-- config", kernelConfigFile.path) - prints.add(Pair("\\-- config", kernelConfigFile.path)) + it.addRow("\\-- config", shortenPath(kernelConfigFile.path)) + prints.add(Pair("\\-- config", shortenPath(kernelConfigFile.path))) } } it.addRule() @@ -442,11 +448,11 @@ data class BootV3( File(Avb.getJsonFileName("sig.boot")).let { jsonFile -> if (jsonFile.exists()) { it.addRow("GKI signature 2.0", this.bootSignature.file) - it.addRow("\\-- boot", jsonFile.path) + it.addRow("\\-- boot", shortenPath(jsonFile.path)) it.addRow("\\------ signing key", Avb.inspectKey(mapper.readValue(jsonFile, AVBInfo::class.java))) //basic prints.add(Pair("GKI signature 2.0", this.bootSignature.file)) - prints.add(Pair("\\-- boot", jsonFile.path)) + prints.add(Pair("\\-- boot", shortenPath(jsonFile.path))) prints.add( Pair( "\\------ signing key", @@ -458,19 +464,19 @@ data class BootV3( File(Avb.getJsonFileName("sig.kernel")).let { jsonFile -> if (jsonFile.exists()) { val readBackAvb = mapper.readValue(jsonFile, AVBInfo::class.java) - it.addRow("\\-- kernel", jsonFile.path) + it.addRow("\\-- kernel", shortenPath(jsonFile.path)) it.addRow("\\------ signing key", Avb.inspectKey(readBackAvb)) it.addRule() //basic - prints.add(Pair("\\-- kernel", jsonFile.path)) + prints.add(Pair("\\-- kernel", shortenPath(jsonFile.path))) prints.add(Pair("\\------ signing key", Avb.inspectKey(readBackAvb))) } } //AVB info Avb.getJsonFileName(info.role).let { jsonFile -> - it.addRow("AVB info", if (File(jsonFile).exists()) jsonFile else "NONE") - prints.add(Pair("AVB info", if (File(jsonFile).exists()) jsonFile else "NONE")) + it.addRow("AVB info", if (File(jsonFile).exists()) shortenPath(jsonFile) else "NONE") + prints.add(Pair("AVB info", if (File(jsonFile).exists()) shortenPath(jsonFile) else "NONE")) if (File(jsonFile).exists()) { mapper.readValue(File(jsonFile), AVBInfo::class.java).let { ai -> it.addRow("\\------ signing key", Avb.inspectKey(ai)) @@ -487,10 +493,10 @@ data class BootV3( if (File(vbmetaCompanion).exists()) { log.warn("XXXX: Found vbmeta.img, parsing ...") //basic - prints.add(Pair("vbmeta.img", Avb.getJsonFileName("vbmeta.img"))) + prints.add(Pair("vbmeta.img", shortenPath(Avb.getJsonFileName("vbmeta.img")))) //table it.addRule() - it.addRow("vbmeta.img", Avb.getJsonFileName("vbmeta.img")) + it.addRow("vbmeta.img", shortenPath(Avb.getJsonFileName("vbmeta.img"))) it.addRule() "\n" + it.render() } else { diff --git a/bbootimg/src/main/kotlin/bootimg/v3/VendorBoot.kt b/bbootimg/src/main/kotlin/bootimg/v3/VendorBoot.kt index 6a515e4..8d8c957 100644 --- a/bbootimg/src/main/kotlin/bootimg/v3/VendorBoot.kt +++ b/bbootimg/src/main/kotlin/bootimg/v3/VendorBoot.kt @@ -19,18 +19,19 @@ import cc.cfig.io.Struct import cfig.Avb import cfig.bootimg.Common import cfig.bootimg.Common.Companion.deleleIfExists +import cfig.bootimg.Common.Companion.toShortenPath import cfig.bootimg.Signer import cfig.helper.Dumpling import cfig.helper.Helper import cfig.helper.ZipHelper import cfig.packable.VBMetaParser -import rom.fdt.DTC import cfig.utils.EnvironmentVerifier import com.fasterxml.jackson.databind.ObjectMapper import de.vandermeer.asciitable.AsciiTable import org.apache.commons.exec.CommandLine import org.apache.commons.exec.DefaultExecutor import org.slf4j.LoggerFactory +import rom.fdt.DTC import java.io.* import java.nio.ByteBuffer import java.nio.ByteOrder @@ -265,7 +266,9 @@ data class VendorBoot( } this.dtb.size = File(this.dtb.file).length().toInt() //header - FileOutputStream(this.info.role + ".clear", false).use { fos -> + val clearFile = Helper.joinPath(Helper.prop("intermediateDir")!!, this.info.role + ".clear") + val googleFile = Helper.joinPath(Helper.prop("intermediateDir")!!, this.info.role + ".google") + FileOutputStream(clearFile, false).use { fos -> val encodedHeader = this.toHeader().encode() fos.write(encodedHeader) fos.write(ByteArray(Helper.round_to_multiple(encodedHeader.size, this.info.pageSize) - encodedHeader.size)) @@ -303,17 +306,17 @@ data class VendorBoot( } } //write - FileOutputStream("${this.info.role}.clear", true).use { fos -> + FileOutputStream(clearFile, true).use { fos -> fos.write(bf.array(), 0, bf.position()) } //google way - this.toCommandLine().addArgument(this.info.role + ".google").let { + this.toCommandLine().addArgument(googleFile).let { log.info(it.toString()) DefaultExecutor().execute(it) } - Helper.assertFileEquals(this.info.role + ".clear", this.info.role + ".google") + Helper.assertFileEquals(clearFile, googleFile) return this } @@ -321,7 +324,15 @@ data class VendorBoot( val avbtool = String.format(Helper.prop("avbtool")!!, "v1.2") File(Avb.getJsonFileName(info.role)).let { if (it.exists()) { - Signer.signAVB(info.role, this.info.imageSize, avbtool) + //Signer.signAVB(info.role, this.info.imageSize, avbtool) + val clearFile = Helper.joinPath(Helper.prop("intermediateDir")!!, this.info.role + ".clear") + val signedFile = Helper.joinPath(Helper.prop("intermediateDir")!!, this.info.role + ".signed") + Signer.signAVB2(clearFile, + signedFile, + Avb.getJsonFileName(info.role), + this.info.imageSize, + avbtool + ) } else { log.warn("skip signing of ${info.role}") } @@ -329,6 +340,15 @@ data class VendorBoot( return this } + fun postCopy(outFile: String): VendorBoot { + val dir = Helper.prop("intermediateDir")!! + val signedFile = Helper.joinPath(dir, "${info.role}.signed").takeIf { File(it).exists() } + ?: Helper.joinPath(dir, "${info.role}.clear") + log.info("COPY $signedFile -> $outFile") + File(signedFile).copyTo(File(outFile), overwrite = true) + return this + } + fun updateVbmeta(): VendorBoot { Avb.updateVbmeta(info.role) return this @@ -416,31 +436,31 @@ data class VendorBoot( } val tab = AsciiTable().let { it.addRule() - val imageInfoJsonFile = Helper.joinPath(workDir!!, info.role.removeSuffix(".img") + ".json") + val imageInfoJsonFile = Helper.joinPath(workDir!!, info.role.removeSuffix(".img") + ".json").toShortenPath() it.addRow("image info", imageInfoJsonFile) prints.add(Pair("image info", imageInfoJsonFile)) it.addRule() - it.addRow("ramdisk", this.ramdisk.file) - prints.add(Pair("ramdisk", this.ramdisk.file)) + it.addRow("ramdisk", this.ramdisk.file.toShortenPath()) + prints.add(Pair("ramdisk", this.ramdisk.file.toShortenPath())) if (this.ramdisk_table.size > 0) { this.ramdisk_table.ramdidks.forEachIndexed { index, entry -> //fancy ascii - it.addRow("-- ${entry.type} ramdisk[${index + 1}/${this.ramdisk_table.ramdidks.size}]", entry.file) - it.addRow("------- extracted rootfs", File(workDir, "root.${index + 1}").path) + it.addRow("-- ${entry.type} ramdisk[${index + 1}/${this.ramdisk_table.ramdidks.size}]", entry.file.toShortenPath()) + it.addRow("------- extracted rootfs", File(workDir, "root.${index + 1}").path.toShortenPath()) //basic ascii //@formatter:off - prints.add(Pair(" -- ${entry.type} ramdisk[${index + 1}/${this.ramdisk_table.ramdidks.size}]", entry.file)) + prints.add(Pair(" -- ${entry.type} ramdisk[${index + 1}/${this.ramdisk_table.ramdidks.size}]", entry.file.toShortenPath())) //@formatter:on - prints.add(Pair(" ------- extracted rootfs", File(workDir, "root.${index + 1}").path)) + prints.add(Pair(" ------- extracted rootfs", File(workDir, "root.${index + 1}").path.toShortenPath())) } } else { - it.addRow("\\-- extracted ramdisk rootfs", File(workDir, "root").path) - prints.add(Pair("\\-- extracted ramdisk rootfs", File(workDir, "root").path)) + it.addRow("\\-- extracted ramdisk rootfs", File(workDir, "root").path.toShortenPath()) + prints.add(Pair("\\-- extracted ramdisk rootfs", File(workDir, "root").path.toShortenPath())) } it.addRule() if (this.dtb.size > 0) { - it.addRow("dtb", this.dtb.file) - prints.add(Pair("dtb", this.dtb.file)) + it.addRow("dtb", this.dtb.file.toShortenPath()) + prints.add(Pair("dtb", this.dtb.file.toShortenPath())) if (File(this.dtb.file + ".0.${dtsSuffix}").exists()) { it.addRow("\\-- decompiled dts [${dtb.dtbList.size}]", dtb.file + "*.${dtsSuffix}") prints.add(Pair("\\-- decompiled dts [${dtb.dtbList.size}]", dtb.file + "*.${dtsSuffix}")) @@ -451,14 +471,14 @@ data class VendorBoot( } if (this.bootconfig.size > 0) { it.addRule() - it.addRow("bootconfig", this.bootconfig.file) - prints.add(Pair("bootconfig", this.bootconfig.file)) + it.addRow("bootconfig", this.bootconfig.file.toShortenPath()) + prints.add(Pair("bootconfig", this.bootconfig.file.toShortenPath())) } it.addRule() Avb.getJsonFileName(info.role).let { jsonFile -> if (File(jsonFile).exists()) { - it.addRow("AVB info", jsonFile) - prints.add(Pair("AVB info", jsonFile)) + it.addRow("AVB info", jsonFile.toShortenPath()) + prints.add(Pair("AVB info", jsonFile.toShortenPath())) mapper.readValue(File(jsonFile), AVBInfo::class.java).let { ai -> it.addRow("\\-- signing key", Avb.inspectKey(ai)) prints.add(Pair(" \\-- signing key", Avb.inspectKey(ai))) @@ -474,8 +494,8 @@ data class VendorBoot( val tabVBMeta = AsciiTable().let { if (File("vbmeta.img").exists()) { it.addRule() - it.addRow("vbmeta.img", Avb.getJsonFileName("vbmeta.img")) - prints.add(Pair("vbmeta.img", Avb.getJsonFileName("vbmeta.img"))) + it.addRow("vbmeta.img", Avb.getJsonFileName("vbmeta.img").toShortenPath()) + prints.add(Pair("vbmeta.img", Avb.getJsonFileName("vbmeta.img").toShortenPath())) it.addRule() "\n" + it.render() } else { @@ -492,8 +512,8 @@ data class VendorBoot( return this } - fun printPackSummary(): VendorBoot { - Common.printPackSummary(info.role) + fun printPackSummary(outFileName: String): VendorBoot { + Common.printPackSummary(info.role, outFileName) return this } diff --git a/bbootimg/src/main/kotlin/packable/BootImgParser.kt b/bbootimg/src/main/kotlin/packable/BootImgParser.kt index bedda87..4123292 100644 --- a/bbootimg/src/main/kotlin/packable/BootImgParser.kt +++ b/bbootimg/src/main/kotlin/packable/BootImgParser.kt @@ -15,6 +15,7 @@ package cfig.packable import avb.blob.Footer +import cfig.bootimg.Common import cfig.bootimg.Common.Companion.probeHeaderVersion import cfig.bootimg.v2.BootV2 import cfig.bootimg.v2.BootV2Dialects @@ -45,14 +46,20 @@ class BootImgParser : IPackable { } override fun unpack(fileName: String) { - unpackInternal(fileName, fileName, outDir) + unpackInternal(fileName, outDir) } - fun unpackInternal(targetFile: String, fileName: String, unpackDir: String) { - log.info("unpackInternal(fileName: $fileName, unpackDir: $unpackDir)") - Helper.setProp("workDir", unpackDir) + // called via reflection + fun unpackInternal(inFileName: String, unpackDir: String) { + log.info("unpackInternal(fileName: $inFileName, unpackDir: $unpackDir)") + val fileName = File(inFileName).canonicalPath + Helper.setProp("workDir", File(unpackDir).canonicalPath) + log.info("workspace set to $unpackDir") clear() - File("$outDir/role").writeText(File(File(targetFile).canonicalPath).name) + //create workspace file + Common.createWorkspaceIni(fileName) + //create workspace file done + val hv = probeHeaderVersion(fileName) log.info("header version $hv") when (hv) { @@ -85,7 +92,64 @@ class BootImgParser : IPackable { } } - fun packInternal(targetFile: String, workspace: String, fileName: String) { + // called via reflection + fun packInternal(workspace: String, outFileName: String) { + log.info("packInternal($workspace, $outFileName)") + Helper.setProp("workDir", workspace) + val targetFile = outFileName + val iniRole = Common.loadProperties(File(workspace, "workspace.ini").canonicalPath).getProperty("role") + val cfgFile = File(workspace, iniRole.removeSuffix(".img") + ".json").canonicalPath + log.info("Loading config from $cfgFile") + if (!File(cfgFile).exists()) { + val tab = AsciiTable().let { + it.addRule() + it.addRow("'$cfgFile' doesn't exist, did you forget to 'unpack' ?") + it.addRule() + it + } + log.info("\n{}", tab.render()) + return + } + + val worker = + try { + ObjectMapper().readValue(File(cfgFile), BootV2::class.java) + } catch (e: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException) { + try { + ObjectMapper().readValue(File(cfgFile), BootV3::class.java) + } catch (e: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException) { + null + } + } + if (worker == null) { + log.error("no worker available") + exitProcess(2) + } + when (worker) { + is BootV2 -> { + worker + .pack() + .sign() + .updateVbmeta() + .printPackSummary() + } + + is BootV3 -> { + worker + .pack() + .sign(targetFile) + .updateVbmeta() + .printPackSummary(worker.info.role) + } + + else -> { + log.error("unsupported boot image format") + exitProcess(2) + } + } + } + + fun packInternalLegacy(targetFile: String, workspace: String, fileName: String) { log.info("packInternal(targetFile: $targetFile, fileName: $fileName, workspace: $workspace)") Helper.setProp("workDir", workspace) val cfgFile = Helper.joinPath(outDir, targetFile.removeSuffix(".img") + ".json") @@ -140,7 +204,8 @@ class BootImgParser : IPackable { } override fun pack(fileName: String) { - packInternal(fileName, outDir, fileName) + val targetFile = Common.loadProperties(File(outDir, "workspace.ini").canonicalPath).getProperty("role") + packInternal(outDir, targetFile) } fun flash(fileName: String) { diff --git a/bbootimg/src/main/kotlin/packable/DeviceTreeParser.kt b/bbootimg/src/main/kotlin/packable/DeviceTreeParser.kt index a72419f..db14255 100644 --- a/bbootimg/src/main/kotlin/packable/DeviceTreeParser.kt +++ b/bbootimg/src/main/kotlin/packable/DeviceTreeParser.kt @@ -1,6 +1,7 @@ package packable import cfig.bootimg.Common +import cfig.bootimg.v3.VendorBoot import cfig.helper.Helper import cfig.helper.Helper.Companion.check_call import cfig.helper.Helper.Companion.check_output @@ -15,13 +16,24 @@ class DeviceTreeParser : IPackable { override fun capabilities(): List { return listOf("^.*\\.dtb$") } + override val loopNo: Int get() = 1 override fun unpack(fileName: String) { - super.clear() - log.info("unpacking $fileName") - val outFile = workDir + fileName.removeSuffix(".dtb") + "." + Helper.prop("config.dts_suffix") + unpackInternal(fileName, Helper.prop("workDir")!!) + } + + fun unpackInternal(fileName: String, unpackDir: String) { + //set workdir + log.info("unpackInternal(fileName: $fileName, unpackDir: $unpackDir)") + Helper.setProp("workDir", unpackDir) + clear() + //create workspace file + Common.createWorkspaceIni(fileName) + //create workspace file done + + val outFile = File(workDir, File(fileName).nameWithoutExtension + "." + Helper.prop("config.dts_suffix")).path DTC().decompile(fileName, outFile) //print summary @@ -32,15 +44,26 @@ class DeviceTreeParser : IPackable { } override fun pack(fileName: String) { - log.info("packing $fileName") - val outFile = workDir + fileName.removeSuffix(".dtb") + "." + Helper.prop("config.dts_suffix") - check(DTC().compile(outFile, "$fileName.new")) { "fail to compile dts" } + packInternal(Helper.prop("workDir")!!, fileName) + } + + fun packInternal(workspace: String, outFileName: String) { + log.info("packInternal($workspace, $outFileName)") + Helper.setProp("workDir", workspace) + //workspace+cfg + val iniRole = Common.loadProperties(File(workspace, "workspace.ini").canonicalPath).getProperty("role") + val dtsSrc = File(workDir, File(iniRole).nameWithoutExtension + "." + Helper.prop("config.dts_suffix")).path + + val origFile = File(workDir, File(outFileName).name + ".orig").path + log.info("COPY $outFileName -> $origFile") + File(outFileName).copyTo(File(origFile), overwrite = true) + check(DTC().compile(dtsSrc, outFileName)) { "fail to compile dts" } //print summary val prints: MutableList> = mutableListOf() - prints.add(Pair("DTS", outFile)) - prints.add(Pair("updated DTB", "$fileName.new")) - log.info("\n\t\t\tPack Summary of {}\n{}\n", fileName, Common.table2String(prints)) + prints.add(Pair("DTS", dtsSrc)) + prints.add(Pair("updated DTB", outFileName)) + log.info("\n\t\t\tPack Summary of {}\n{}\n", outFileName, Common.table2String(prints)) } fun pull(fileName: String) { diff --git a/bbootimg/src/main/kotlin/packable/PackableLauncher.kt b/bbootimg/src/main/kotlin/packable/PackableLauncher.kt index 0fd68d9..1d3b734 100644 --- a/bbootimg/src/main/kotlin/packable/PackableLauncher.kt +++ b/bbootimg/src/main/kotlin/packable/PackableLauncher.kt @@ -14,10 +14,12 @@ package cfig.packable +import cfig.helper.Helper import org.slf4j.LoggerFactory import packable.DeviceTreeParser import rom.sparse.SparseImgParser import java.io.File +import java.util.Properties import java.util.regex.Pattern import kotlin.reflect.KClass import kotlin.reflect.full.createInstance @@ -28,6 +30,12 @@ class PackableLauncher fun main(args: Array) { val log = LoggerFactory.getLogger(PackableLauncher::class.java) + val devLog = LoggerFactory.getLogger("XXXX") + val loadProperties: (String) -> Properties = { fileName -> + Properties().apply { + File(fileName).inputStream().use { load(it) } + } + } val packablePool = mutableMapOf, KClass>() listOf( BootImgParser(), @@ -44,25 +52,43 @@ fun main(args: Array) { packablePool.put(it.capabilities(), it::class as KClass) } packablePool.forEach { - log.debug("" + it.key + "/" + it.value) + log.info("" + it.key + "/" + it.value) } var targetFile: String? = null var targetHandler: KClass? = null + var targetDir: String? = null - log.info("XXXX: args: " + args.asList().toString()) + devLog.info("args: " + args.asList().toString()) + args.forEachIndexed { index, s -> + devLog.info(" arg: #$index - $s") + } run found@{ for (currentLoopNo in 0..1) { //currently we have only 2 loops + devLog.info("loop #" + currentLoopNo) if (args.size > 1) { // manual mode - targetFile = if (File(args[1]).isFile) { + devLog.info("manual mode") + targetFile = if (File(args[1]).isFile) { //unpack File(args[1]).canonicalPath - } else if (File(args[1] + "/role").isFile) { - File(args[1] + "/role").readText().trim() - } else { - log.error("Not sure of what to do: " + args.asList().toString()) + } else if (File(args[1]).isDirectory and File(args[1], "workspace.ini").isFile) { //pack + loadProperties(File(args[1], "workspace.ini").canonicalPath).getProperty("role") + } else { //wrong + log.error("Not a file: " + args[1]) exitProcess(1) } - log.warn("manual mode: args= ${args[1]}, $targetFile") + devLog.warn("manual mode: file=$targetFile") + targetDir = when (args.size) { + 2 -> if (File(args[1]).isDirectory and File(args[1], "workspace.ini").isFile) { //arg = outDir + File(args[1]).canonicalPath + } else { + Helper.prop("workDir") // arg = outDir + } + 3 -> File(args[2]).canonicalPath // arg = file + else -> { + throw IllegalArgumentException("too many args") + } + } + devLog.warn("manual mode: file=$targetFile, dir=$targetDir") packablePool .filter { it.value.createInstance().loopNo == currentLoopNo } .forEach { p -> @@ -75,14 +101,15 @@ fun main(args: Array) { } } } else { // lazy mode + devLog.info("lazy mode (in current dir)") File(".").listFiles()!!.forEach { file -> packablePool .filter { it.value.createInstance().loopNo == currentLoopNo } .forEach { p -> for (item in p.key) { if (Pattern.compile(item).matcher(file.name).matches()) { - log.debug("Found: " + file.name + ", " + item) - targetFile = file.name + log.info("Found: " + file.name + ", " + item) + targetFile = File(file.name).canonicalPath targetHandler = p.value return@found } @@ -140,43 +167,26 @@ fun main(args: Array) { log.warn("'${args[0]}' sequence initialized") log.warn("XXXX: args.size: ${args.size}") - val convertedArgs = args.copyOf().apply { set(0, targetFile!!) } - functions[0].call(it.createInstance(), *convertedArgs) - -/* - val reflectRet = when (functions[0].parameters.size) { - 1 -> { - log.warn("1: call null") - functions[0].call(it.createInstance()) - } - - 2 -> { - log.warn("2: call $targetFile") - functions[0].call(it.createInstance(), targetFile!!) - } - - 3 -> { - if (args.size != 2) { - log.info("invoke: ${it.qualifiedName}, $targetFile, " + targetFile!!.removeSuffix(".img")) - functions[0].call(it.createInstance(), targetFile!!, targetFile!!.removeSuffix(".img")) - } else { - log.info("invoke: ${it.qualifiedName}, $targetFile, " + args[1]) - functions[0].call(it.createInstance(), targetFile!!, args[1]) - } - } - - else -> { - functions[0].parameters.forEach { kp -> - println("Param: " + kp.index + " " + kp.type + " " + kp.name) - } - log.error("I am confused by so many parameters") - exitProcess(4) + val c = (if (args.size == 1) { //lazy mode + args.drop(1).toMutableList().apply { + add(targetFile!!) + //add(System.getProperty("user.dir")) } + } else { + args.drop(1) + }).toTypedArray() + val convertedArgs = c + println("clazz = " + it.simpleName) + println("func = " + functions[0]) + println("orig args:") + args.forEachIndexed { index, s -> + println("$index: $s") } - if (functions[0].returnType.toString() != Unit.toString()) { - log.info("ret: $reflectRet") + println("Converted args:") + convertedArgs.forEachIndexed { index, s -> + println("$index: $s") } -*/ + functions[0].call(it.createInstance(), *convertedArgs) log.warn("'${args[0]}' sequence completed") } } diff --git a/bbootimg/src/main/kotlin/packable/VBMetaParser.kt b/bbootimg/src/main/kotlin/packable/VBMetaParser.kt index 0f8059c..92ce4da 100644 --- a/bbootimg/src/main/kotlin/packable/VBMetaParser.kt +++ b/bbootimg/src/main/kotlin/packable/VBMetaParser.kt @@ -16,6 +16,7 @@ package cfig.packable import avb.AVBInfo import cfig.Avb +import cfig.bootimg.Common import cfig.helper.Dumpling import cfig.helper.Helper import com.fasterxml.jackson.databind.ObjectMapper @@ -35,20 +36,43 @@ class VBMetaParser : IPackable { return listOf("^vbmeta\\.img$", "^vbmeta\\_[a-z]+.img$") } + // lazy mode override fun unpack(fileName: String) { - File(Helper.prop("workDir")).let { + unpackInternal(fileName, Helper.prop("workDir")!!) + } + + // manual mode + fun unpackInternal(inFileName: String, unpackDir: String) { + //common + log.info("unpackInternal(fileName: $inFileName, unpackDir: $unpackDir)") + val fileName = File(inFileName).canonicalPath + Helper.setProp("workDir", File(unpackDir).canonicalPath) + //prepare workdir + File(Helper.prop("workDir")!!).let { if (!it.exists()) { it.mkdirs() } } + //workspace.ini + log.info("workspace set to $unpackDir") + Common.createWorkspaceIni(fileName, prefix = "vbmeta") + val ai = AVBInfo.parseFrom(Dumpling(fileName)).dumpDefault(fileName) log.info("Signing Key: " + Avb.inspectKey(ai)) } override fun pack(fileName: String) { - val blob = ObjectMapper().readValue(File(Avb.getJsonFileName(fileName)), AVBInfo::class.java).encodePadded() - log.info("Writing padded vbmeta to file: $fileName.signed") - Files.write(Paths.get("$fileName.signed"), blob, StandardOpenOption.CREATE) + packInternal(outDir, fileName) + } + + // called via reflection + fun packInternal(workspace: String, outFileName: String) { + log.info("packInternal(workspace: $workspace, $outFileName)") + Helper.setProp("workDir", workspace) + val iniRole = Common.loadProperties(File(workspace, "workspace.ini").canonicalPath).getProperty("vbmeta.role") + val blob = ObjectMapper().readValue(File(Avb.getJsonFileName(iniRole)), AVBInfo::class.java).encodePadded() + log.info("Writing padded vbmeta to file: $outFileName.signed") + Files.write(Paths.get("$outFileName.signed"), blob, StandardOpenOption.CREATE) } override fun flash(fileName: String, deviceName: String) { diff --git a/bbootimg/src/main/kotlin/packable/VendorBootParser.kt b/bbootimg/src/main/kotlin/packable/VendorBootParser.kt index 6b70601..5779686 100644 --- a/bbootimg/src/main/kotlin/packable/VendorBootParser.kt +++ b/bbootimg/src/main/kotlin/packable/VendorBootParser.kt @@ -14,6 +14,7 @@ package cfig.packable +import cfig.bootimg.Common import cfig.bootimg.v3.VendorBoot import cfig.helper.Helper import com.fasterxml.jackson.databind.ObjectMapper @@ -30,19 +31,18 @@ class VendorBootParser : IPackable { } override fun unpack(fileName: String) { - clear() - val vb = VendorBoot - .parse(fileName) - .extractImages() - .extractVBMeta() - .printUnpackSummary() - log.debug(vb.toString()) + log.info("unpack(fileName: $fileName)") + unpackInternal(fileName, Helper.prop("workDir")!!) } - fun unpackInternal(targetFile: String, fileName: String, unpackDir: String) { + fun unpackInternal(fileName: String, unpackDir: String) { + //set workdir log.info("unpackInternal(fileName: $fileName, unpackDir: $unpackDir)") Helper.setProp("workDir", unpackDir) clear() + //create workspace file + Common.createWorkspaceIni(fileName) + //create workspace file done val vb = VendorBoot .parse(fileName) .extractImages() @@ -52,13 +52,32 @@ class VendorBootParser : IPackable { } override fun pack(fileName: String) { - val cfgFile = "$outDir/${fileName.removeSuffix(".img")}.json" + log.info("packInternal($fileName)") + packInternal(Helper.prop("workDir")!!, fileName) + } + + fun packInternal(workspace: String, outFileName: String) { + log.info("packInternal($workspace, $outFileName)") + Helper.setProp("workDir", workspace) + //intermediate + Helper.joinPath(workspace, "intermediate").also { intermediateDir -> + File(intermediateDir).let { + if (!it.exists()) { + it.mkdir() + } + } + Helper.setProp("intermediateDir", intermediateDir) + } + //workspace+cfg + val iniRole = Common.loadProperties(File(workspace, "workspace.ini").canonicalPath).getProperty("role") + val cfgFile = File(workspace, iniRole.removeSuffix(".img") + ".json").canonicalPath log.info("Loading config from $cfgFile") ObjectMapper().readValue(File(cfgFile), VendorBoot::class.java) .pack() .sign() + .postCopy(outFileName) .updateVbmeta() - .printPackSummary() + .printPackSummary(outFileName) } override fun `@verify`(fileName: String) { diff --git a/bbootimg/src/main/kotlin/rom/sparse/SparseImage.kt b/bbootimg/src/main/kotlin/rom/sparse/SparseImage.kt index b0917ee..9bdbbe5 100644 --- a/bbootimg/src/main/kotlin/rom/sparse/SparseImage.kt +++ b/bbootimg/src/main/kotlin/rom/sparse/SparseImage.kt @@ -34,7 +34,7 @@ data class SparseImage(var info: SparseInfo = SparseInfo()) { partName, workDir, File(workDir, File(info.output).nameWithoutExtension).path, - File(workDir, File(info.pulp).name + ".signed").path + File(Helper.prop("intermediateDir"), File(info.pulp).name + ".signed").path ) } @@ -44,7 +44,7 @@ data class SparseImage(var info: SparseInfo = SparseInfo()) { partName, workDir, File(workDir, File(info.output).nameWithoutExtension).path, - File(workDir, "${info.output}.signed").path + File(Helper.prop("intermediateDir"), "${info.output}.signed").path ) } @@ -119,7 +119,7 @@ data class SparseImage(var info: SparseInfo = SparseInfo()) { if (info.outerFsType == "sparse") { img2simg(File(workDir, (File(info.output).name + ".signed")).path, File(info.output).name + ".signed") } else { - val s = info.pulp + ".signed" + val s = Helper.joinPath(Helper.prop("intermediateDir")!!, File(info.pulp + ".signed").name) val t = info.output + ".signed" log.info("Moving $s -> $t") Files.move(Path(s), Path(t), java.nio.file.StandardCopyOption.REPLACE_EXISTING) @@ -147,29 +147,32 @@ data class SparseImage(var info: SparseInfo = SparseInfo()) { val ret = SparseImage() ret.info.json = File(fileName).name.removeSuffix(".img") + ".json" ret.info.output = fileName - ret.info.pulp = workDir + fileName + ret.info.pulp = File(Helper.prop("workDir")!!, File(fileName).nameWithoutExtension).path + log.info(ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(ret.info)) if (isSparse(fileName)) { val tempFile = UUID.randomUUID().toString() ret.info.outerFsType = "sparse" - val rawFile = "${workDir}${File(fileName).nameWithoutExtension}" simg2img(fileName, tempFile) ret.info.pulp = if (isExt4(tempFile)) { ret.info.innerFsType = "ext4" - "$rawFile.ext4" + ret.info.pulp + ".ext4" } else if (isErofs(tempFile)) { ret.info.innerFsType = "erofs" - "$rawFile.erofs" + ret.info.pulp + ".erofs" } else { - "$rawFile.raw" + ret.info.pulp + ".raw" } Files.move(Path(tempFile), Path(ret.info.pulp)) } else if (isExt4(fileName)) { ret.info.outerFsType = "ext4" ret.info.innerFsType = "ext4" + ret.info.pulp = ret.info.pulp + ".ext4" + log.info("COPY $fileName -> ${ret.info.pulp}") File(fileName).copyTo(File(ret.info.pulp)) } else if (isErofs(fileName)) { ret.info.outerFsType = "erofs" ret.info.innerFsType = "erofs" + ret.info.pulp = ret.info.pulp + ".erofs" File(fileName).copyTo(File(ret.info.pulp)) } when (ret.info.innerFsType) { @@ -258,7 +261,7 @@ data class SparseImage(var info: SparseInfo = SparseInfo()) { DefaultExecutor().apply { streamHandler = PumpStreamHandler(System.out, System.err) }.execute(CommandLine.parse("aosp/plugged/bin/sefcontext_compile").apply { - addArguments("-o " + Helper.prop("workDir") + "file_contexts.bin") + addArguments("-o " + File(Helper.prop("workDir"), "file_contexts.bin").path) addArgument("aosp/plugged/res/file_contexts.concat") }.also { log.warn(it.toString()) }, env) } diff --git a/bbootimg/src/main/kotlin/rom/sparse/SparseImgParser.kt b/bbootimg/src/main/kotlin/rom/sparse/SparseImgParser.kt index 492f4d1..0848e7c 100644 --- a/bbootimg/src/main/kotlin/rom/sparse/SparseImgParser.kt +++ b/bbootimg/src/main/kotlin/rom/sparse/SparseImgParser.kt @@ -15,6 +15,7 @@ package rom.sparse import avb.blob.Footer +import cfig.bootimg.Common import cfig.helper.Helper import cfig.packable.IPackable import cfig.packable.VBMetaParser @@ -38,15 +39,45 @@ class SparseImgParser : IPackable { } override fun unpack(fileName: String) { + log.info("unpack(fileName: $fileName)") + unpackInternal(fileName, Helper.prop("workDir")!!) + } + + fun unpackInternal(fileName: String, unpackDir: String) { + //set workdir + log.info("unpackInternal(fileName: $fileName, unpackDir: $unpackDir)") + Helper.setProp("workDir", unpackDir) clear() + //create workspace file + Common.createWorkspaceIni(fileName) + //create workspace file done SparseImage .parse(fileName) .printSummary(fileName) } + override fun pack(fileName: String) { //TODO("not implemented: refer to https://github.com/cfig/Android_boot_image_editor/issues/133") - val cfgFile = outDir + fileName.removeSuffix(".img") + ".json" + packInternal(Helper.prop("workDir")!!, fileName) + } + + fun packInternal(workspace: String, outFileName: String) { + log.info("packInternal($workspace, $outFileName)") + Helper.setProp("workDir", workspace) + //intermediate + Helper.joinPath(workspace, "intermediate").also { intermediateDir -> + File(intermediateDir).let { + if (!it.exists()) { + it.mkdir() + } + } + Helper.setProp("intermediateDir", intermediateDir) + } + //workspace+cfg + val iniRole = Common.loadProperties(File(workspace, "workspace.ini").canonicalPath).getProperty("role") + val cfgFile = File(workspace, iniRole.removeSuffix(".img") + ".json").canonicalPath + log.info("Loading config from $cfgFile") val readBackSi = ObjectMapper().readValue(File(cfgFile), SparseImage::class.java) readBackSi .pack() diff --git a/doc/booting.puml b/doc/booting.puml new file mode 100644 index 0000000..14bb492 --- /dev/null +++ b/doc/booting.puml @@ -0,0 +1,15 @@ +@startuml +participant init +participant Service +participant "Service Started" as C +participant "Command" as D + +init -> Service: +ueventd +Service -> C: ueventd started + +init -> Service: +apexd-bootstrap +Service -> C: apexd-bootstrap started + +init -> D !!: 'mkdir /acct/uid' +init -> D: update_linker_config +@enduml diff --git a/doc/feature_list.md b/doc/feature_list.md index 70fd6b9..b20a005 100644 --- a/doc/feature_list.md +++ b/doc/feature_list.md @@ -21,16 +21,16 @@ should be compatible with "/usr/bin/env sh" ## TODO: command line usage unpack ``` -abe unpack boot.img out +be unpack file +be unpack file dir ``` - pack ``` -abe pack out boot.img +be pack dir ``` properties: "out.file": the final output file -### something interesting in abe +### something interesting in be a zsh script, parse the input command line parameters, for example, if args are: "unpack file dir", the shell script will print 'gradle unpack --args="unpackInternal file dir"'; if args are "pack dir file", the shell script will print 'gradle pack --args="packInternal dir file"'. diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0d18421..5c82cb0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/helper/build.gradle.kts b/helper/build.gradle.kts index 15f80b1..288b4d6 100644 --- a/helper/build.gradle.kts +++ b/helper/build.gradle.kts @@ -15,7 +15,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - kotlin("jvm") version "2.0.0" + kotlin("jvm") version "2.1.21" `java-library` application } @@ -58,11 +58,13 @@ java { targetCompatibility = JavaVersion.VERSION_11 } -tasks.withType().all { - kotlinOptions { - freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn" - freeCompilerArgs += "-opt-in=kotlin.ExperimentalUnsignedTypes" - jvmTarget = "11" +tasks.withType().configureEach { + compilerOptions { + freeCompilerArgs.addAll( + "-opt-in=kotlin.RequiresOptIn", + "-opt-in=kotlin.ExperimentalUnsignedTypes" + ) + jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11) } } diff --git a/lazybox/build.gradle.kts b/lazybox/build.gradle.kts index 684d10d..ea9467e 100644 --- a/lazybox/build.gradle.kts +++ b/lazybox/build.gradle.kts @@ -8,7 +8,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - kotlin("jvm") version "2.0.20" + kotlin("jvm") version "2.1.21" application } @@ -30,6 +30,7 @@ dependencies { // Use the JUnit 5 integration. testImplementation("org.junit.jupiter:junit-jupiter-engine:5.9.3") testRuntimeOnly("org.junit.platform:junit-platform-launcher") + implementation(kotlin("stdlib")) } java { @@ -37,11 +38,13 @@ java { targetCompatibility = JavaVersion.VERSION_11 } -tasks.withType().all { - kotlinOptions { - freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn" - freeCompilerArgs += "-opt-in=kotlin.ExperimentalUnsignedTypes" - jvmTarget = "11" +tasks.withType().configureEach { + compilerOptions { + freeCompilerArgs.addAll( + "-opt-in=kotlin.RequiresOptIn", + "-opt-in=kotlin.ExperimentalUnsignedTypes" + ) + jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11) } } diff --git a/lazybox/src/main/kotlin/cfig/lazybox/App.kt b/lazybox/src/main/kotlin/cfig/lazybox/App.kt index 6e33454..7bdc38d 100644 --- a/lazybox/src/main/kotlin/cfig/lazybox/App.kt +++ b/lazybox/src/main/kotlin/cfig/lazybox/App.kt @@ -70,7 +70,17 @@ fun main(args: Array) { if (args[0] == "apps") { //AppList.retrieveList() } + if (args[0] == "rel") { + ImageRelease.run() + } if (args[0] == "x") { AMS.computeRankAndBucket(AMS.getProcRank(), AMS.getStandbyBucket2()) } + if (args[0] == "mount") { + MountAnalyzer().run() + } + if (args[0] == "booting") { + //BootingParser.run() + BootingParser.run2() + } } diff --git a/lazybox/src/main/kotlin/cfig/lazybox/BootingParser.kt b/lazybox/src/main/kotlin/cfig/lazybox/BootingParser.kt new file mode 100644 index 0000000..94c66b3 --- /dev/null +++ b/lazybox/src/main/kotlin/cfig/lazybox/BootingParser.kt @@ -0,0 +1,90 @@ +package cfig.lazybox + +import org.slf4j.LoggerFactory +import java.io.File +import java.util.regex.Pattern + +class BootingParser { + companion object { + private val log = LoggerFactory.getLogger(BootingParser::class.java) + fun run() { + val logLines = File("booting.log").readLines() + val regex = Pattern.compile("""\[([^]]+)] \[\s*([0-9.]+)]\[\s*(T\d+)] init: starting service '([^']+)'.*""") + for (line in logLines) { + val matcher = regex.matcher(line) + if (matcher.find()) { + val timestamp = matcher.group(1) + val kernelTime = matcher.group(2) + val tLevel = matcher.group(3) + val serviceName = matcher.group(4) + + println("Timestamp: $timestamp, Kernel Time: $kernelTime, T-Level: $tLevel, Service Name: $serviceName") + } + } + } + + fun run2() { + val logLines = File("booting.log2").readLines() + + + val actionRegex = Pattern.compile("""\[([^]]+)] \[\s*([0-9.]+)]\[\s*(T\d+)] init: processing action \(([^)]+)\) from \(([^)]+)\).*""") + val commandRegex = Pattern.compile("""\[([^]]+)] \[\s*([0-9.]+)]\[\s*(T\d+)] init: Command '([^']+)' action=([^\(]+) \(([^)]+)\) took (\d+)ms and (succeeded|failed)(.*)?""") + val svcExecRegex = Pattern.compile("""\[([^]]+)] \[\s*([0-9.]+)]\[\s*(T\d+)] init: SVC_EXEC service '([^']+)' pid (\d+) \(([^)]+)\) started; waiting\.""") + val serviceStartRegex = Pattern.compile("""\[([^]]+)] \[\s*([0-9.]+)]\[\s*(T\d+)] init: starting service '([^']+)'.*""") + + for (line in logLines) { + val actionMatcher = actionRegex.matcher(line) + if (actionMatcher.find()) { + val timestamp = actionMatcher.group(1) + val kernelTime = actionMatcher.group(2) + val tLevel = actionMatcher.group(3) + val actionName = actionMatcher.group(4) + val fromComponent = actionMatcher.group(5) + + println("Timestamp: $timestamp, Kernel Time: $kernelTime, T-Level: $tLevel, Action Name: $actionName, From: $fromComponent") + } + + val commandMatcher = commandRegex.matcher(line) + if (commandMatcher.find()) { + val timestamp = commandMatcher.group(1) + val kernelTime = commandMatcher.group(2) + val tLevel = commandMatcher.group(3) + val command = commandMatcher.group(4) + val action = commandMatcher.group(5).trim() + val fromComponent = commandMatcher.group(6) + val duration = commandMatcher.group(7) + val status = commandMatcher.group(8) + val failReason = commandMatcher.group(9)?.trim() + + println("Timestamp: $timestamp, Kernel Time: $kernelTime, T-Level: $tLevel, Command: $command, Action: $action, From: $fromComponent, Duration: ${duration}ms, Status: $status${if (failReason != null) ", Reason: $failReason" else ""}") + } + + val svcExecMatcher = svcExecRegex.matcher(line) + if (svcExecMatcher.find()) { + val timestamp = svcExecMatcher.group(1) + val kernelTime = svcExecMatcher.group(2) + val tLevel = svcExecMatcher.group(3) + val serviceName = svcExecMatcher.group(4) + val pid = svcExecMatcher.group(5) + val context = svcExecMatcher.group(6) + + println("Timestamp: $timestamp, Kernel Time: $kernelTime, T-Level: $tLevel, Service Name: $serviceName, PID: $pid, Context: $context") + } + + val serviceStartMatcher = serviceStartRegex.matcher(line) + if (serviceStartMatcher.find()) { + val timestamp = serviceStartMatcher.group(1) + val kernelTime = serviceStartMatcher.group(2) + val tLevel = serviceStartMatcher.group(3) + val serviceName = serviceStartMatcher.group(4) + + println("Timestamp: $timestamp, Kernel Time: $kernelTime, T-Level: $tLevel, Service Name: $serviceName") + } + } + + + + + } // end-of-fun + } // end-of-companion +} \ No newline at end of file diff --git a/lazybox/src/main/kotlin/cfig/lazybox/ImageRelease.kt b/lazybox/src/main/kotlin/cfig/lazybox/ImageRelease.kt new file mode 100644 index 0000000..3a9ce0e --- /dev/null +++ b/lazybox/src/main/kotlin/cfig/lazybox/ImageRelease.kt @@ -0,0 +1,67 @@ +package cfig.lazybox + +import cfig.helper.Helper +import cfig.helper.Helper.Companion.check_call +import org.slf4j.LoggerFactory +import java.io.File +import java.nio.file.Files +import java.nio.file.Paths + +class ImageRelease { + companion object { + private val log = LoggerFactory.getLogger(ImageRelease::class.java) + fun run() { + val buildId = Helper.adbCmd("getprop ro.build.id").lowercase() + val variant = Helper.adbCmd("getprop ro.build.type") + val product = Helper.adbCmd("getprop ro.build.product") + val rel = Helper.adbCmd("getprop ro.build.version.release") + val increment = Helper.adbCmd("getprop ro.build.version.incremental") + val fp = Helper.adbCmd("getprop ro.build.fingerprint") + + val computFacDir = "$product-factory-$buildId" + val computFacZip = "$product-factory-$buildId-$increment.zip" + val computOtaZip = "$product-ota-$buildId-$increment.zip" + val computEmmcZip = "$product-eMMCimg-$buildId-$increment.zip" + + log.info("fingerprint: $fp") + log.info("$product-factory-$buildId-$increment -> $product-factory-$buildId") + log.info("$product-ota-$buildId-$increment") + log.info("$product-eMMCimg-$buildId-$increment") + + //factory + if (File("factory.zip").exists()) { + "rm -fr factory_image factory_img $computFacDir $computFacZip".check_call() + "unzip factory.zip".check_call() + val facDir = if (File("factory_img").exists()) { + //user + "factory_img" + } else if (File("factory_image").exists()) { + //userdebug + "factory_image" + } else { + throw IllegalStateException("can not find factory image folder") + } + File(facDir).listFiles()?.filter { it.name.endsWith(".sh") }?.forEach { it.delete() } + "cp -v /tftp/flash_platypus.sh $facDir/flash.sh".check_call() + "mv -v $facDir $computFacDir".check_call() + "zip $computFacZip -r $computFacDir".check_call() + "rm -fr $computFacDir".check_call() + } + + File("factory.zip").delete() + if (File("ota.zip").exists()) { + Files.move(Paths.get("ota.zip"), Paths.get(computOtaZip)) + } + if (File("emmc.zip").exists()) { + Files.move(Paths.get("emmc.zip"), Paths.get(computEmmcZip)) + } + + log.info("fingerprint: $fp") + log.info("$product-factory-$buildId-$increment -> $product-factory-$buildId") + log.info(computFacZip) + log.info(computOtaZip) + log.info(computEmmcZip) + } + } + +} \ No newline at end of file diff --git a/lazybox/src/main/kotlin/cfig/lazybox/MountAnalyzer.kt b/lazybox/src/main/kotlin/cfig/lazybox/MountAnalyzer.kt new file mode 100644 index 0000000..7199ef2 --- /dev/null +++ b/lazybox/src/main/kotlin/cfig/lazybox/MountAnalyzer.kt @@ -0,0 +1,148 @@ +package cfig.lazybox + +import org.slf4j.LoggerFactory +import java.io.BufferedWriter +import java.io.File +import java.io.FileOutputStream +import java.io.FileWriter + +class MountAnalyzer { + data class MountInfo( + var dev: String = "", + var mountPoint: String = "", + var fsType: String = "", + var flags: String? = null, + ) + + class MiComparator : Comparator { + override fun compare(p1: MountInfo, p2: MountInfo): Int { + var ret = p1.fsType.compareTo(p2.fsType) * 100 + ret += p1.dev.compareTo(p2.dev) * 10 + ret += p1.mountPoint.compareTo(p2.mountPoint) * 1 + return ret + } + } + + fun run() { + val loopApex = mutableListOf() + val dmApex = mutableListOf() + val tmpApex = mutableListOf() + val bootApex = mutableListOf() + val fuseInfo = mutableListOf() + val sysInfo = mutableListOf() + val androidRo = mutableListOf() + val androidRw = mutableListOf() + val otherRw = mutableListOf() + val unknownMi = mutableListOf() + val lines = File("mount.log").readLines() + lines.forEachIndexed { n, line -> + val regex = Regex("(\\S+)\\s+on\\s+(\\S+)\\s+type\\s+(\\w+)\\s+\\(([^)]*)\\)") // Capture flags + val matchResult = regex.find(line) + if (matchResult != null) { + val dev = matchResult.groupValues[1] + val mountPoint = matchResult.groupValues[2] + val fsType = matchResult.groupValues[3] + val flags = + if (matchResult.groupValues.size > 4) matchResult.groupValues[4] else null // Handle no flags + val mi = MountInfo(dev, mountPoint, fsType, flags) + if (mi.mountPoint.startsWith("/apex") || mi.mountPoint.startsWith("/bootstrap-apex")) { + if (mi.mountPoint.startsWith("/bootstrap-apex")) { + bootApex.add(mi) + } else if (mi.dev.startsWith("/dev/block/loop")) { + loopApex.add(mi) + } else if (mi.dev.startsWith("/dev/block/dm")) { + dmApex.add(mi) + } else if (mi.dev.startsWith("tmpfs")) { + tmpApex.add(mi) + } else { + log.info("$fsType: $dev -> $mountPoint") + throw IllegalStateException("X1") + } + } else if (mi.mountPoint.startsWith("/sys/") || mi.mountPoint == "/sys") { + sysInfo.add(mi) + } else if (mi.fsType == "fuse") { + fuseInfo.add(mi) + } else { + log.info("$fsType: $dev -> $mountPoint") + if (mi.flags!!.contains("ro,") or mi.flags!!.contains("ro)")) { + androidRo.add(mi) + } else if (mi.flags!!.contains("rw,") or mi.flags!!.contains("rw)")) { + if (mi.dev.startsWith("/dev/")) { + androidRw.add(mi) + } else { + otherRw.add(mi) + } + } else { + throw IllegalStateException("X2") + } + } + } else { //For lines without flags + val regexNoFlags = Regex("(\\S+)\\s+on\\s+(\\S+)\\s+type\\s+(\\w+)") + val matchResultNoFlags = regexNoFlags.find(line) + if (matchResultNoFlags != null) { + val dev = matchResultNoFlags.groupValues[1] + val mountPoint = matchResultNoFlags.groupValues[2] + val fsType = matchResultNoFlags.groupValues[3] + val mi = MountInfo(dev, mountPoint, fsType, null) + unknownMi.add(mi) + } else { + throw IllegalStateException("X3") + } + } + } // end-of-lines + //sanity check, make sure consistent + check( + listOf( + loopApex, + dmApex, + tmpApex, + bootApex, + fuseInfo, + sysInfo, + androidRo, + androidRw, + otherRw, + unknownMi + ).sumOf { it.size } == lines.size) + //dump + val infoNames = listOf( + "fusefs", + "sysfs", + "Android RO", + "Android RW", + "other Rw", + "loop apex", + "dm apex", + "tmp apex", + "boot apex", + "unknown" + ) + BufferedWriter(FileWriter(File("sorted_mount.log"))).use { fos -> + listOf( + fuseInfo, + sysInfo, + androidRo, + androidRw, + otherRw, + loopApex, + dmApex, + tmpApex, + bootApex, + unknownMi + ).forEachIndexed { n, mis -> + mis.sortWith(MiComparator()) + log.info(infoNames.get(n)) + fos.write(infoNames.get(n) + "\n") + mis.forEachIndexed { index, it -> + log.info("[$index] ${it.fsType} : ${it.dev} -> ${it.mountPoint} (${it.flags})") + fos.write("#$index | ${it.fsType} | ${it.dev} | ${it.mountPoint} | (${it.flags})\n") + } + fos.write("\n") + } + } + } + + companion object { + private val log = LoggerFactory.getLogger(MountAnalyzer::class.java) + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index ee1e2d1..9a7b5fc 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,3 +1,8 @@ +pluginManagement { + plugins { + kotlin("jvm") version "2.1.21" + } +} rootProject.name = "boot" include("bbootimg") include("aosp:apksigner") diff --git a/src/integrationTest/resources_2 b/src/integrationTest/resources_2 index 9b783be..012bda6 160000 --- a/src/integrationTest/resources_2 +++ b/src/integrationTest/resources_2 @@ -1 +1 @@ -Subproject commit 9b783be45a23379d024157a6740fb06ba535e4d4 +Subproject commit 012bda615f1c02f16064c30b828f2307f08a6730 diff --git a/src/integrationTest/resources_3 b/src/integrationTest/resources_3 index 15c3878..b1bff46 160000 --- a/src/integrationTest/resources_3 +++ b/src/integrationTest/resources_3 @@ -1 +1 @@ -Subproject commit 15c3878486abe3f2e42e6e0f4f0d82682546afaf +Subproject commit b1bff460f66fb916e30a0f7ca0b93bc922412b53 diff --git a/tools/factory_image_parser.py b/tools/factory_image_parser.py index 9e89200..a068c65 100755 --- a/tools/factory_image_parser.py +++ b/tools/factory_image_parser.py @@ -73,6 +73,12 @@ unknown_list = [ "super_empty_all.img", #ADT-3 "bootloader.img", #many "dt.img", + "fastboot.img", #anon + "fastlogo.img", #anon + "bl.img", #anon + "tzl.img", #anon + "preboot.img", #anon + "tzk.img", #anon ] tmp2 = "tmp2" diff --git a/tools/local/bin/abe b/tools/local/bin/be similarity index 56% rename from tools/local/bin/abe rename to tools/local/bin/be index ed14df1..ee81660 100755 --- a/tools/local/bin/abe +++ b/tools/local/bin/be @@ -2,10 +2,12 @@ baseDir=${0:a:h} export baseDir +be_caller_dir=${PWD} +export be_caller_dir set -e # Parse command line arguments -if [[ $# -eq 0 ]]; then +if [[ $# -lt 2 ]]; then echo "Usage: $0 [ ]" exit 1 fi @@ -13,34 +15,48 @@ fi operation=$1 echo arg num: $# echo "args: $0 $1 $2" +echo "baseDir: $baseDir" +echo "callerDir: $be_caller_dir" +source_code_dir=/home/yu/work/boot # Determine which operation to perform +set -x case $operation in "unpack") if [[ $# -eq 2 ]]; then file=`realpath $2` dir=`realpath out` - cd ${baseDir}/../../../ + cd ${source_code_dir} + pwd gradle unpack --args="unpackInternal $file $dir" elif [[ $# -eq 3 ]]; then file=`realpath $2` dir=`realpath $3` - cd ${baseDir}/../../../ + cd ${source_code_dir} gradle unpack --args="unpackInternal $file $dir" else + echo "Invalid args" cd ${baseDir}/../../../ gradle unpack fi ;; "pack") - if [[ $# -eq 3 ]]; then + pwd + if [[ $# -eq 2 ]]; then + dir=`realpath $2` + cd ${source_code_dir} + file=${be_caller_dir}/$(grep -m1 '^role=' $dir/workspace.ini | cut -d'=' -f2-) + gradle pack --args="packInternal $dir $file" + elif [[ $# -eq 3 ]]; then dir=`realpath $2` file=`realpath $3` - cd ${baseDir}/../../../ + cd ${source_code_dir} gradle pack --args="packInternal $dir $file" else - cd ${baseDir}/../../../ - gradle pack + ##cd ${baseDir}/../../../ + #cd ${source_code_dir} + #gradle pack + echo "Invalid args" fi ;; *)