From 585e2fdcd55ac2d522fb30cf2e9c9257eed17e5f Mon Sep 17 00:00:00 2001 From: cfig Date: Sun, 12 May 2024 23:43:44 +0800 Subject: [PATCH] Issue #145: staging boot.img V3 unpack java -jar bbootimg/build/libs/bbootimg.jar unpackInternal ../boot.img out pack java -jar bbootimg/build/libs/bbootimg.jar packInternal out ../x/boot.img.modified --- bbootimg/src/main/kotlin/avb/Avb.kt | 3 +- bbootimg/src/main/kotlin/bootimg/Common.kt | 1 + bbootimg/src/main/kotlin/bootimg/v2/BootV2.kt | 18 +-- bbootimg/src/main/kotlin/bootimg/v3/BootV3.kt | 144 ++++++++++++------ .../src/main/kotlin/bootimg/v3/VendorBoot.kt | 10 +- bbootimg/src/main/kotlin/ota/Payload.kt | 6 +- .../src/main/kotlin/packable/BootImgParser.kt | 65 ++++++-- .../main/kotlin/packable/PackableLauncher.kt | 62 ++++++-- .../main/kotlin/rom/sparse/ErofsGenerator.kt | 2 +- .../main/kotlin/rom/sparse/Ext4Generator.kt | 2 +- .../src/main/kotlin/rom/sparse/SparseImage.kt | 24 +-- .../src/main/kotlin/utils/KernelExtractor.kt | 4 +- bbootimg/src/main/resources/general.cfg | 2 + doc/feature_list.md | 5 +- helper/src/main/kotlin/cfig/helper/Helper.kt | 10 ++ lazybox/src/main/kotlin/cfig/lazybox/App.kt | 2 + 16 files changed, 255 insertions(+), 105 deletions(-) diff --git a/bbootimg/src/main/kotlin/avb/Avb.kt b/bbootimg/src/main/kotlin/avb/Avb.kt index 59a7ed6..5964ef8 100644 --- a/bbootimg/src/main/kotlin/avb/Avb.kt +++ b/bbootimg/src/main/kotlin/avb/Avb.kt @@ -174,7 +174,8 @@ class Avb { fun getJsonFileName(image_file: String): String { val jsonFile = File(image_file).name.removeSuffix(".img") + ".avb.json" - return Helper.prop("workDir") + jsonFile + log.warn("XXXX: json file = " + Helper.joinPath(Helper.prop("workDir")!!, jsonFile)) + return Helper.joinPath(Helper.prop("workDir")!!, jsonFile) } fun hasAvbFooter(fileName: String): Boolean { diff --git a/bbootimg/src/main/kotlin/bootimg/Common.kt b/bbootimg/src/main/kotlin/bootimg/Common.kt index 9c86f44..1797036 100644 --- a/bbootimg/src/main/kotlin/bootimg/Common.kt +++ b/bbootimg/src/main/kotlin/bootimg/Common.kt @@ -120,6 +120,7 @@ class Common { fun dumpKernel(s: Helper.Slice) { Helper.extractFile(s.srcFile, s.dumpFile, s.offset.toLong(), s.length) + log.warn("s.srcFile: ${s.srcFile}, s.dumpFile: ${s.dumpFile}, s.offset: ${s.offset}, s.length: ${s.length}") parseKernelInfo(s.dumpFile) } diff --git a/bbootimg/src/main/kotlin/bootimg/v2/BootV2.kt b/bbootimg/src/main/kotlin/bootimg/v2/BootV2.kt index ed5e8e3..4d9934a 100644 --- a/bbootimg/src/main/kotlin/bootimg/v2/BootV2.kt +++ b/bbootimg/src/main/kotlin/bootimg/v2/BootV2.kt @@ -128,7 +128,7 @@ data class BootV2( } } ret.kernel.let { theKernel -> - theKernel.file = "${workDir}kernel" + theKernel.file = File(workDir, "kernel").path theKernel.size = bh2.kernelLength theKernel.loadOffset = bh2.kernelOffset theKernel.position = ret.getKernelPosition() @@ -138,28 +138,28 @@ data class BootV2( theRamdisk.loadOffset = bh2.ramdiskOffset theRamdisk.position = ret.getRamdiskPosition() if (bh2.ramdiskLength > 0) { - theRamdisk.file = "${workDir}ramdisk.img" + 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 = "${workDir}second" + 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 = "${workDir}recoveryDtbo" + 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 = "${workDir}dtb" + ret.dtb!!.file = File(workDir, "dtb").path ret.dtb!!.position = ret.getDtbPosition() } } @@ -220,7 +220,7 @@ data class BootV2( //ramdisk if (this.ramdisk.size > 0) { val fmt = C.dumpRamdisk( - Helper.Slice(info.output, ramdisk.position.toInt(), ramdisk.size, ramdisk.file!!), "${workDir}root" + 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") { @@ -333,8 +333,8 @@ data class BootV2( it.addRule() it.addRow("ramdisk", this.ramdisk.file) prints.add(Pair("ramdisk", this.ramdisk.file.toString())) - it.addRow("\\-- extracted ramdisk rootfs", "${workDir}root") - prints.add(Pair("\\-- extracted ramdisk rootfs", "${workDir}root")) + it.addRow("\\-- extracted ramdisk rootfs", File(workDir, "root").path) + prints.add(Pair("\\-- extracted ramdisk rootfs", File(workDir, "root").path)) } //second this.secondBootloader?.let { theSecondBootloader -> @@ -440,7 +440,7 @@ data class BootV2( 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("${workDir}/root", 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() } diff --git a/bbootimg/src/main/kotlin/bootimg/v3/BootV3.kt b/bbootimg/src/main/kotlin/bootimg/v3/BootV3.kt index f9d4d26..e757f71 100644 --- a/bbootimg/src/main/kotlin/bootimg/v3/BootV3.kt +++ b/bbootimg/src/main/kotlin/bootimg/v3/BootV3.kt @@ -49,14 +49,15 @@ data class BootV3( private val log = LoggerFactory.getLogger(BootV3::class.java) private val errLog = LoggerFactory.getLogger("uiderrors") private val mapper = ObjectMapper() - private val workDir = Helper.prop("workDir") + private val workDir = Helper.prop("workDir")!! fun parse(fileName: String): BootV3 { val ret = BootV3() FileInputStream(fileName).use { fis -> val header = BootHeaderV3(fis) //info - ret.info.output = File(fileName).name + ret.info.input = File(fileName).canonicalPath + ret.info.role = File(fileName).name ret.info.json = File(fileName).name.removeSuffix(".img") + ".json" ret.info.cmdline = header.cmdline.trim() ret.info.headerSize = header.headerSize @@ -66,17 +67,17 @@ data class BootV3( ret.info.pageSize = BootHeaderV3.pageSize ret.info.signatureSize = header.signatureSize //kernel - ret.kernel.file = workDir + "kernel" + ret.kernel.file = Helper.joinPath(workDir, "kernel") ret.kernel.size = header.kernelSize ret.kernel.position = BootHeaderV3.pageSize //ramdisk - ret.ramdisk.file = workDir + "ramdisk.img" + ret.ramdisk.file = Helper.joinPath(workDir, "ramdisk.img") ret.ramdisk.size = header.ramdiskSize ret.ramdisk.position = ret.kernel.position + header.kernelSize + getPaddingSize(header.kernelSize, BootHeaderV3.pageSize) //boot signature if (header.signatureSize > 0) { - ret.bootSignature.file = workDir + "bootsig" + ret.bootSignature.file = Helper.joinPath(workDir, "bootsig") ret.bootSignature.size = header.signatureSize ret.bootSignature.position = ret.ramdisk.position + ret.ramdisk.size + getPaddingSize(header.ramdiskSize, BootHeaderV3.pageSize) @@ -88,7 +89,8 @@ data class BootV3( } data class MiscInfo( - var output: String = "", + var input: String = "", + var role: String = "", var json: String = "", var headerVersion: Int = 0, var headerSize: Int = 0, @@ -106,7 +108,7 @@ data class BootV3( var size: Int = 0, ) - data class RamdiskArgs ( + data class RamdiskArgs( var file: String = "", var position: Int = 0, var size: Int = 0, @@ -118,22 +120,30 @@ data class BootV3( this.kernel.size = File(this.kernel.file).length().toInt() } if (this.ramdisk.size > 0) { - 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.replaceFirst("[.][^.]+$", "")).deleleIfExists() //TODO: remove cpio in C/C++ - //C.packRootfs("$workDir/root", this.ramdisk.file, C.parseOsMajor(info.osVersion)) + //C.packRootfs(Helper.joinPath($workDir, "root"), this.ramdisk.file, C.parseOsMajor(info.osVersion)) // enable advance JAVA cpio - C.packRootfs("$workDir/root", this.ramdisk.file, this.ramdisk.xzFlags) + C.packRootfs(Helper.joinPath(workDir, "root"), this.ramdisk.file, this.ramdisk.xzFlags) } this.ramdisk.size = File(this.ramdisk.file).length().toInt() } //header - FileOutputStream(this.info.output + ".clear", false).use { fos -> + val intermediateDir = Helper.joinPath(workDir, "intermediate") + File(intermediateDir).let { + if (!it.exists()) { + it.mkdir() + } + } + Helper.setProp("intermediateDir", intermediateDir) + val clearFile = Helper.joinPath(intermediateDir, this.info.role + ".clear") + FileOutputStream(clearFile, false).use { fos -> //trim bootSig if it's not parsable //https://github.com/cfig/Android_boot_image_editor/issues/88 File(Avb.getJsonFileName(this.bootSignature.file)).let { bootSigJson -> @@ -163,7 +173,7 @@ data class BootV3( C.writePaddedFile(bf, this.ramdisk.file, this.info.pageSize) } //write V3 data - FileOutputStream("${this.info.output}.clear", true).use { fos -> + FileOutputStream(clearFile, true).use { fos -> fos.write(bf.array(), 0, bf.position()) } @@ -178,12 +188,12 @@ data class BootV3( //replace new pub key readBackBootSig.auxBlob!!.pubkey!!.pubkey = AuxBlob.encodePubKey(alg) //update hash and sig - readBackBootSig.auxBlob!!.hashDescriptors.get(0).update(this.info.output + ".clear") + readBackBootSig.auxBlob!!.hashDescriptors.get(0).update(this.info.role + ".clear") bootSigBytes = readBackBootSig.encodePadded() } if (this.info.signatureSize > 0) { //write V4 data - FileOutputStream("${this.info.output}.clear", true).use { fos -> + FileOutputStream(clearFile, true).use { fos -> fos.write(bootSigBytes) } } else { @@ -192,22 +202,40 @@ data class BootV3( } //google way - this.toCommandLine().addArgument(this.info.output + ".google").let { + val googleClearFile = Helper.joinPath(intermediateDir, this.info.role + ".google") + this.toCommandLine().addArgument(googleClearFile).let { log.info(it.toString()) DefaultExecutor().execute(it) } - Helper.assertFileEquals(this.info.output + ".clear", this.info.output + ".google") - File(this.info.output + ".google").delete() + Helper.assertFileEquals(clearFile, googleClearFile) + File(googleClearFile).delete() return this } fun sign(fileName: String): BootV3 { - if (File(Avb.getJsonFileName(info.output)).exists()) { - Signer.signAVB(fileName, this.info.imageSize, String.format(Helper.prop("avbtool")!!, "v1.2")) + log.warn("XXXX: sign $fileName") + if (File(Avb.getJsonFileName(info.role)).exists()) { + Signer.signAVB( + Helper.joinPath(Helper.prop("intermediateDir")!!, info.role), + this.info.imageSize, + String.format(Helper.prop("avbtool")!!, "v1.2") + ) } else { log.warn("no AVB info found, assume it's clear image") } + if (fileName != info.role) { + File(Helper.joinPath(Helper.prop("intermediateDir")!!, info.role + ".signed")).copyTo(File(fileName), true) + log.info("Signed image saved as $fileName") + } else { + File( + Helper.joinPath( + Helper.prop("intermediateDir")!!, + info.role + ".signed" + ) + ).copyTo(File(info.role + ".signed"), true) + log.info("Signed image saved as ${info.role}.signed") + } return this } @@ -227,35 +255,37 @@ data class BootV3( fun extractImages(): BootV3 { val workDir = Helper.prop("workDir") //info - mapper.writerWithDefaultPrettyPrinter().writeValue(File(workDir + this.info.json), this) + mapper.writerWithDefaultPrettyPrinter().writeValue(File(workDir, this.info.json), this) //kernel if (kernel.size > 0) { - C.dumpKernel(Helper.Slice(info.output, kernel.position, kernel.size, kernel.file)) + C.dumpKernel(Helper.Slice(info.input, kernel.position, kernel.size, kernel.file)) } else { - log.warn("${this.info.output} has no kernel") + log.warn("${this.info.role} has no kernel") } //ramdisk if (ramdisk.size > 0) { val fmt = C.dumpRamdisk( - Helper.Slice(info.output, ramdisk.position, ramdisk.size, ramdisk.file), "${workDir}root" + Helper.Slice(info.role, ramdisk.position, ramdisk.size, ramdisk.file), File(workDir, "root").toString() ) 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 } } //bootsig //dump info again - mapper.writerWithDefaultPrettyPrinter().writeValue(File(workDir + this.info.json), this) + mapper.writerWithDefaultPrettyPrinter().writeValue(File(workDir, this.info.json), this) return this } fun extractVBMeta(): BootV3 { // vbmeta in image try { - val ai = AVBInfo.parseFrom(Dumpling(info.output)).dumpDefault(info.output) + log.warn("XXXX: info.output ${info.input}") + val ai = AVBInfo.parseFrom(Dumpling(info.input)).dumpDefault(info.role) if (File("vbmeta.img").exists()) { log.warn("Found vbmeta.img, parsing ...") VBMetaParser().unpack("vbmeta.img") @@ -268,7 +298,7 @@ data class BootV3( //GKI 1.0 bootsig if (info.signatureSize > 0) { log.info("GKI 1.0 signature") - Dumpling(info.output).readFully(Pair(this.bootSignature.position.toLong(), this.bootSignature.size)) + Dumpling(info.role).readFully(Pair(this.bootSignature.position.toLong(), this.bootSignature.size)) .let { bootsigData -> File(this.bootSignature.file).writeBytes(bootsigData) if (bootsigData.any { it.toInt() != 0 }) { @@ -286,17 +316,17 @@ data class BootV3( } //GKI 2.0 bootsig - if (!File(Avb.getJsonFileName(info.output)).exists()) { - log.info("no AVB info found in ${info.output}") + if (!File(Avb.getJsonFileName(info.role)).exists()) { + log.info("no AVB info found in ${info.role}") return this } log.info("probing 16KB boot signature ...") val mainBlob = ObjectMapper().readValue( - File(Avb.getJsonFileName(info.output)), + File(Avb.getJsonFileName(info.role)), AVBInfo::class.java ) val bootSig16kData = - Dumpling(Dumpling(info.output).readFully(Pair(mainBlob.footer!!.originalImageSize - 16 * 1024, 16 * 1024))) + Dumpling(Dumpling(info.input).readFully(Pair(mainBlob.footer!!.originalImageSize - 16 * 1024, 16 * 1024))) try { val blob1 = AVBInfo.parseFrom(bootSig16kData) .also { check(it.auxBlob!!.hashDescriptors[0].partition_name == "boot") } @@ -306,8 +336,8 @@ data class BootV3( .also { check(it.auxBlob!!.hashDescriptors[0].partition_name == "generic_kernel") } .also { it.dumpDefault("sig.kernel") } val gkiAvbData = bootSig16kData.readFully(blob1.encode().size until bootSig16kData.getLength()) - File("${workDir}kernel.img").let { gki -> - File("${workDir}kernel").copyTo(gki) + File(workDir, "kernel.img").let { gki -> + File(workDir, "kernel").copyTo(gki) System.setProperty("more", workDir) Avb.verify(blob2, Dumpling(gkiAvbData)) gki.delete() @@ -331,19 +361,26 @@ data class BootV3( } 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", Helper.joinPath(workDir!!, info.role.removeSuffix(".img") + ".json")) + prints.add(Pair("image info", 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)) - File(Helper.prop("kernelVersionFile")).let { kernelVersionFile -> + File(Helper.joinPath(workDir, Helper.prop("kernelVersionStem")!!)).let { kernelVersionFile -> + log.warn("XXXX: kernelVersionFile ${kernelVersionFile.path}") if (kernelVersionFile.exists()) { it.addRow("\\-- version " + kernelVersionFile.readLines().toString(), kernelVersionFile.path) - prints.add(Pair("\\-- version " + kernelVersionFile.readLines().toString(), kernelVersionFile.path)) + prints.add( + Pair( + "\\-- version " + kernelVersionFile.readLines().toString(), + kernelVersionFile.path + ) + ) } } - File(Helper.prop("kernelConfigFile")).let { kernelConfigFile -> + File(Helper.joinPath(workDir, Helper.prop("kernelConfigStem")!!)).let { kernelConfigFile -> + log.warn("XXXX: kernelConfigFile ${kernelConfigFile.path}") if (kernelConfigFile.exists()) { it.addRow("\\-- config", kernelConfigFile.path) prints.add(Pair("\\-- config", kernelConfigFile.path)) @@ -354,11 +391,11 @@ data class BootV3( if (this.ramdisk.size > 0) { //fancy it.addRow("ramdisk", this.ramdisk.file) - it.addRow("\\-- extracted ramdisk rootfs", "${workDir}root") + it.addRow("\\-- extracted ramdisk rootfs", Helper.joinPath(workDir, "root")) it.addRule() //basic prints.add(Pair("ramdisk", this.ramdisk.file)) - prints.add(Pair("\\-- extracted ramdisk rootfs", "${workDir}root")) + prints.add(Pair("\\-- extracted ramdisk rootfs", Helper.joinPath(workDir, "root"))) } if (this.info.signatureSize > 0) { it.addRow("GKI signature 1.0", this.bootSignature.file) @@ -368,7 +405,12 @@ data class BootV3( prints.add(Pair("\\-- decoded boot signature", if (jsFile.exists()) jsFile.path else "N/A")) if (jsFile.exists()) { it.addRow("\\------ signing key", Avb.inspectKey(mapper.readValue(jsFile, AVBInfo::class.java))) - prints.add(Pair("\\------ signing key", Avb.inspectKey(mapper.readValue(jsFile, AVBInfo::class.java)))) + prints.add( + Pair( + "\\------ signing key", + Avb.inspectKey(mapper.readValue(jsFile, AVBInfo::class.java)) + ) + ) } } it.addRule() @@ -383,7 +425,12 @@ data class BootV3( //basic prints.add(Pair("GKI signature 2.0", this.bootSignature.file)) prints.add(Pair("\\-- boot", jsonFile.path)) - prints.add(Pair("\\------ signing key", Avb.inspectKey(mapper.readValue(jsonFile, AVBInfo::class.java)))) + prints.add( + Pair( + "\\------ signing key", + Avb.inspectKey(mapper.readValue(jsonFile, AVBInfo::class.java)) + ) + ) } } File(Avb.getJsonFileName("sig.kernel")).let { jsonFile -> @@ -399,7 +446,7 @@ data class BootV3( } //AVB info - Avb.getJsonFileName(info.output).let { jsonFile -> + 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")) if (File(jsonFile).exists()) { @@ -428,20 +475,20 @@ data class BootV3( log.info("\n" + Common.table2String(prints)) } else { log.info( - "\n\t\t\tUnpack Summary of ${info.output}\n{}\n{}{}", + "\n\t\t\tUnpack Summary of ${info.role}\n{}\n{}{}", tableHeader.render(), tab.render(), tabVBMeta ) } return this } - fun printPackSummary(): BootV3 { - Common.printPackSummary(info.output) + fun printPackSummary(fileName: String): BootV3 { + Common.printPackSummary(fileName) return this } fun updateVbmeta(): BootV3 { - Avb.updateVbmeta(info.output) + Avb.updateVbmeta(info.role) return this } @@ -475,7 +522,8 @@ data class BootV3( val alg = Algorithms.get(origSig.header!!.algorithm_type)!! ret.addArgument("--gki_signing_algorithm").addArgument(alg.name) ret.addArgument("--gki_signing_key").addArgument(alg.defaultKey) - ret.addArgument("--gki_signing_avbtool_path").addArgument(String.format(Helper.prop("avbtool")!!, "v1.2")) + ret.addArgument("--gki_signing_avbtool_path") + .addArgument(String.format(Helper.prop("avbtool")!!, "v1.2")) } ret.addArgument(" --id ") ret.addArgument(" --output ") diff --git a/bbootimg/src/main/kotlin/bootimg/v3/VendorBoot.kt b/bbootimg/src/main/kotlin/bootimg/v3/VendorBoot.kt index 81521a2..f015740 100644 --- a/bbootimg/src/main/kotlin/bootimg/v3/VendorBoot.kt +++ b/bbootimg/src/main/kotlin/bootimg/v3/VendorBoot.kt @@ -358,7 +358,7 @@ data class VendorBoot( //ramdisk //@formatter:off val fmt = C.dumpRamdisk( - Helper.Slice(info.output, ramdisk.position.toInt(), ramdisk.size, ramdisk.file), "${workDir}root", + Helper.Slice(info.output, ramdisk.position.toInt(), ramdisk.size, ramdisk.file), File(workDir, "root").path, this.ramdisk_table.ramdidks.isEmpty()) //@formatter:on this.ramdisk.file = this.ramdisk.file + ".$fmt" @@ -423,16 +423,16 @@ data class VendorBoot( 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", "${workDir}root.${index + 1}") + it.addRow("------- extracted rootfs", File(workDir, "root.${index + 1}").path) //basic ascii //@formatter:off prints.add(Pair(" -- ${entry.type} ramdisk[${index + 1}/${this.ramdisk_table.ramdidks.size}]", entry.file)) //@formatter:on - prints.add(Pair(" ------- extracted rootfs", "${workDir}root.${index + 1}")) + prints.add(Pair(" ------- extracted rootfs", File(workDir, "root.${index + 1}").path)) } } else { - it.addRow("\\-- extracted ramdisk rootfs", "${workDir}root") - prints.add(Pair("\\-- extracted ramdisk rootfs", "${workDir}root")) + it.addRow("\\-- extracted ramdisk rootfs", File(workDir, "root").path) + prints.add(Pair("\\-- extracted ramdisk rootfs", File(workDir, "root").path)) } it.addRule() if (this.dtb.size > 0) { diff --git a/bbootimg/src/main/kotlin/ota/Payload.kt b/bbootimg/src/main/kotlin/ota/Payload.kt index 0301b34..e7cea30 100644 --- a/bbootimg/src/main/kotlin/ota/Payload.kt +++ b/bbootimg/src/main/kotlin/ota/Payload.kt @@ -157,11 +157,11 @@ class Payload { ManifestInfo.DynamicPartGroup(name = it.name, size = it.size, partName = it.partitionNamesList) }) ObjectMapper().writerWithDefaultPrettyPrinter().writeValue(File("$workDir/header.json"), this.header) - log.info(" header info dumped to ${workDir}header.json") + log.info(" header info dumped to " + File(workDir, "header.json").path) ObjectMapper().writerWithDefaultPrettyPrinter().writeValue(File("$workDir/manifest.json"), mi) - log.info(" manifest info dumped to ${workDir}manifest.json") + log.info(" manifest info dumped to " + File(workDir, "manifest.json").path) - val signatureFile = "${workDir}signatures.txt" + val signatureFile = File(workDir, "signatures.txt").path FileOutputStream(signatureFile, false).use { fos -> fos.writer().use { fWriter -> fWriter.write(" signatures: offset=" + this.header.manifestLen + ", size=" + this.header.metaSigLen + "\n") diff --git a/bbootimg/src/main/kotlin/packable/BootImgParser.kt b/bbootimg/src/main/kotlin/packable/BootImgParser.kt index af71f50..7379bb3 100644 --- a/bbootimg/src/main/kotlin/packable/BootImgParser.kt +++ b/bbootimg/src/main/kotlin/packable/BootImgParser.kt @@ -27,6 +27,7 @@ import java.io.File import java.io.FileInputStream import kotlin.io.path.Path import kotlin.io.path.deleteIfExists +import kotlin.system.exitProcess class BootImgParser : IPackable { override val loopNo: Int @@ -34,11 +35,25 @@ class BootImgParser : IPackable { override fun capabilities(): List { //ramdisk.img : Issue #122 - return listOf("^boot(-debug)?\\.img$", "^recovery\\.img$", "^recovery-two-step\\.img$", "^init_boot\\.img$", "^ramdisk\\.img$") + return listOf( + "^boot(-debug)?\\.img$", + "^recovery\\.img$", + "^recovery-two-step\\.img$", + "^init_boot\\.img$", + "^ramdisk\\.img$" + ) } override fun unpack(fileName: String) { + unpackInternal(fileName, fileName, outDir) + } + + fun unpackInternal(targetFile: String, fileName: String, unpackDir: String) { + log.warn("Unpacking $fileName") + log.warn("fileName: $fileName, unpackDir: $unpackDir") + Helper.setProp("workDir", unpackDir) clear() + File("$outDir/role").writeText(File(File(fileName).canonicalPath).name) val hv = probeHeaderVersion(fileName) log.info("header version $hv") when (hv) { @@ -50,6 +65,7 @@ class BootImgParser : IPackable { .printUnpackSummary() log.debug(b2.toString()) } + in 3..4 -> { val b3 = BootV3 .parse(fileName) @@ -58,6 +74,7 @@ class BootImgParser : IPackable { .printUnpackSummary() log.debug(b3.toString()) } + else -> { val b2 = BootV2Dialects .parse(fileName) @@ -69,8 +86,10 @@ class BootImgParser : IPackable { } } - override fun pack(fileName: String) { - val cfgFile = outDir + fileName.removeSuffix(".img") + ".json" + fun packInternal(targetFile: String, workspace: String, fileName: String) { + log.warn("XXXX: targetFile: $targetFile, fileName: $fileName, workspace: $workspace") + Helper.setProp("workDir", workspace) + val cfgFile = Helper.joinPath(outDir, targetFile.removeSuffix(".img") + ".json") log.info("Loading config from $cfgFile") if (!File(cfgFile).exists()) { val tab = AsciiTable().let { @@ -82,23 +101,49 @@ class BootImgParser : IPackable { log.info("\n{}", tab.render()) return } - when (val hv = probeHeaderVersion(fileName)) { - 0, 1, 2 -> + + 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.warn("XXXX: worker is null") + exitProcess(2) + } + when (worker) { + is BootV2 -> { + worker .pack() .sign() .updateVbmeta() .printPackSummary() - 3, 4 -> - ObjectMapper().readValue(File(cfgFile), BootV3::class.java) + } + + is BootV3 -> { + worker .pack() .sign(fileName) .updateVbmeta() - .printPackSummary() - else -> throw IllegalArgumentException("do not support header version $hv") + .printPackSummary(fileName) + } + + else -> { + log.error("unsupported boot image format") + exitProcess(2) + } } } + override fun pack(fileName: String) { + packInternal(fileName, outDir, fileName) + } + override fun flash(fileName: String, deviceName: String) { val stem = fileName.substring(0, fileName.indexOf(".")) super.flash("$fileName.signed", stem) @@ -122,7 +167,7 @@ class BootImgParser : IPackable { } override fun `@verify`(fileName: String) { - File(Helper.prop("workDir")).let { + File(Helper.prop("workDir")!!).let { if (!it.exists()) { it.mkdirs() } diff --git a/bbootimg/src/main/kotlin/packable/PackableLauncher.kt b/bbootimg/src/main/kotlin/packable/PackableLauncher.kt index 4c97ec3..ad576e7 100644 --- a/bbootimg/src/main/kotlin/packable/PackableLauncher.kt +++ b/bbootimg/src/main/kotlin/packable/PackableLauncher.kt @@ -18,6 +18,7 @@ import rom.sparse.SparseImgParser import org.slf4j.LoggerFactory import packable.DeviceTreeParser import java.io.File +import java.util.* import java.util.regex.Pattern import kotlin.reflect.KClass import kotlin.reflect.full.createInstance @@ -48,22 +49,48 @@ fun main(args: Array) { } var targetFile: String? = null var targetHandler: KClass? = null + + log.info("XXXX: args: " + args.asList().toString()) + run found@{ for (currentLoopNo in 0..1) { //currently we have only 2 loops - File(".").listFiles()!!.forEach { file -> + if (args.size > 1) { // manual mode + targetFile = if (File(args[1]).isFile) { + 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()) + exitProcess(1) + } + log.warn("manual mode: args= ${args[1]}, $targetFile") 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 + if (Pattern.compile(item).matcher(File(targetFile).name).matches()) { + log.info("Found: $targetFile, $item") targetHandler = p.value return@found } } } - }//end-of-file-traversing + } else { // lazy mode + 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 + targetHandler = p.value + return@found + } + } + } + }//end-of-file-traversing + } }//end-of-range-loop }//end-of-found@ @@ -71,8 +98,9 @@ fun main(args: Array) { // /* 2 */ no-args & handler : help for Handler // /* 3 */ args & no-handler: do nothing // /* 4 */ args & handler : work - when (listOf(args.isEmpty(), targetHandler == null)) { - listOf(true, true) -> { /* 1 */ + when (listOf(args.isNotEmpty(), targetHandler != null)) { + listOf(false, false) -> { /* 1 */ + log.warn("args: ${args.size}, targetHandler: $targetHandler") log.info("help:") log.info("available IPackable subcommands are:") IPackable::class.declaredFunctions.forEach { @@ -81,7 +109,8 @@ fun main(args: Array) { exitProcess(1) } - listOf(true, false) -> {/* 2 */ + listOf(false, true) -> {/* 2 */ + log.warn("args: ${args.size}, targetHandler: $targetHandler") log.info("available ${targetHandler!!.simpleName} subcommands are:") targetHandler!!.declaredFunctions.forEach { log.info("\t" + it.name) @@ -89,13 +118,15 @@ fun main(args: Array) { exitProcess(1) } - listOf(false, true) -> {/* 3 */ + listOf(true, false) -> {/* 3 */ + log.warn("args: ${args.size}, targetHandler: $targetHandler") log.warn("No handler is activated, DO NOTHING!") exitProcess(2) } - listOf(false, false) -> {/* 4 */ - log.debug("continue ...") + listOf(true, true) -> {/* 4 */ + log.warn("args: ${args.size}, targetHandler: $targetHandler") + log.info("continue ...") } } @@ -111,12 +142,20 @@ fun main(args: Array) { exitProcess(3) } 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!!) } @@ -141,6 +180,7 @@ fun main(args: Array) { if (functions[0].returnType.toString() != Unit.toString()) { log.info("ret: $reflectRet") } +*/ log.warn("'${args[0]}' sequence completed") } } diff --git a/bbootimg/src/main/kotlin/rom/sparse/ErofsGenerator.kt b/bbootimg/src/main/kotlin/rom/sparse/ErofsGenerator.kt index 0608745..1644b85 100644 --- a/bbootimg/src/main/kotlin/rom/sparse/ErofsGenerator.kt +++ b/bbootimg/src/main/kotlin/rom/sparse/ErofsGenerator.kt @@ -30,7 +30,7 @@ class ErofsGenerator(inPartitionName: String) : BaseGenerator(inPartitionName, A signingArgs = newArgs.toString() val mkfsBin = "aosp/plugged/bin/mkfs.erofs" - val fc = "${workDir}file_contexts" + val fc = File(workDir, "file_contexts").path val cmd = CommandLine.parse(mkfsBin).apply { addArguments("-z lz4hc,9") addArguments("--mount-point $mount_point") diff --git a/bbootimg/src/main/kotlin/rom/sparse/Ext4Generator.kt b/bbootimg/src/main/kotlin/rom/sparse/Ext4Generator.kt index 57b3813..1baad37 100644 --- a/bbootimg/src/main/kotlin/rom/sparse/Ext4Generator.kt +++ b/bbootimg/src/main/kotlin/rom/sparse/Ext4Generator.kt @@ -123,7 +123,7 @@ class Ext4Generator(inPartitionName: String = "NA") : BaseGenerator(partitionNam addArguments("-e") addArguments("-p out/target/product/shiba/system") addArgument("-s") - addArguments("-S ${workDir}file_contexts.bin") + addArguments("-S " + File(workDir, "file_contexts.bin").path) addArguments("-f " + Helper.prop("workDir") + "/$mount_point") addArguments("-a /$mount_point") addArgument(outFile) diff --git a/bbootimg/src/main/kotlin/rom/sparse/SparseImage.kt b/bbootimg/src/main/kotlin/rom/sparse/SparseImage.kt index 0a1a1b0..b0917ee 100644 --- a/bbootimg/src/main/kotlin/rom/sparse/SparseImage.kt +++ b/bbootimg/src/main/kotlin/rom/sparse/SparseImage.kt @@ -33,8 +33,8 @@ data class SparseImage(var info: SparseInfo = SparseInfo()) { readBackAi, partName, workDir, - workDir + File(info.output).nameWithoutExtension, - workDir + File(info.pulp).name + ".signed" + File(workDir, File(info.output).nameWithoutExtension).path, + File(workDir, File(info.pulp).name + ".signed").path ) } @@ -43,8 +43,8 @@ data class SparseImage(var info: SparseInfo = SparseInfo()) { readBackAi, partName, workDir, - workDir + File(info.output).nameWithoutExtension, - workDir + "${info.output}.signed" + File(workDir, File(info.output).nameWithoutExtension).path, + File(workDir, "${info.output}.signed").path ) } @@ -66,26 +66,26 @@ data class SparseImage(var info: SparseInfo = SparseInfo()) { addRow("What", "Where") addRule() addRow("image (${info.outerFsType})", fileName) - ("${workDir}$stem.ext4").let { ext4 -> + File(workDir, "$stem.ext4").path.let { ext4 -> if (File(ext4).exists()) { addRule() addRow("converted image (ext4)", ext4) } } - ("${workDir}$stem.erofs").let { + File(workDir, "$stem.erofs").path.let { if (File(it).exists()) { addRule() addRow("converted image (erofs)", it) tail.addRule() - tail.addRow("sudo mount $it -o loop -t erofs ${workDir}mount") + tail.addRow("sudo mount $it -o loop -t erofs " + File(workDir, "mount").path) tail.addRule() } else if (info.innerFsType == "erofs") { tail.addRule() - tail.addRow("sudo mount $fileName -o loop -t erofs ${workDir}mount") + tail.addRow("sudo mount $fileName -o loop -t erofs " + File(workDir, "mount").path) tail.addRule() } } - ("${workDir}$stem").let { + File(workDir, "$stem").path.let { if (File(it).exists()) { addRule() if (File(it).isFile) { @@ -95,7 +95,7 @@ data class SparseImage(var info: SparseInfo = SparseInfo()) { } } } - ("${workDir}$stem.log").let { + File(workDir, "$stem.log").path.let { if (File(it).exists()) { addRule() addRow("log", it) @@ -103,7 +103,7 @@ data class SparseImage(var info: SparseInfo = SparseInfo()) { } if (info.innerFsType == "erofs") { addRule() - addRow("mount point", "${workDir}mount") + addRow("mount point", File(workDir, "mount").path) } addRule() } @@ -117,7 +117,7 @@ data class SparseImage(var info: SparseInfo = SparseInfo()) { fun unwrap(): SparseImage { if (info.outerFsType == "sparse") { - img2simg(workDir + File(info.output).name + ".signed", File(info.output).name + ".signed") + img2simg(File(workDir, (File(info.output).name + ".signed")).path, File(info.output).name + ".signed") } else { val s = info.pulp + ".signed" val t = info.output + ".signed" diff --git a/bbootimg/src/main/kotlin/utils/KernelExtractor.kt b/bbootimg/src/main/kotlin/utils/KernelExtractor.kt index 6feb3eb..2b82677 100644 --- a/bbootimg/src/main/kotlin/utils/KernelExtractor.kt +++ b/bbootimg/src/main/kotlin/utils/KernelExtractor.kt @@ -32,8 +32,8 @@ class KernelExtractor { fun run(fileName: String, workDir: File? = null): List { val ret: MutableList = mutableListOf() - val kernelVersionFile = Helper.prop("kernelVersionFile")!! - val kernelConfigFile = Helper.prop("kernelConfigFile")!! + val kernelVersionFile = File(Helper.prop("workDir")!! , Helper.prop("kernelVersionStem")!!).toString() + val kernelConfigFile = File(Helper.prop("workDir")!! , Helper.prop("kernelConfigStem")!!).toString() val cmdPrefix = if (EnvironmentVerifier().isWindows) "python " else "" val cmd = CommandLine.parse(cmdPrefix + Helper.prop("kernelExtracter")).let { it.addArgument("--input") diff --git a/bbootimg/src/main/resources/general.cfg b/bbootimg/src/main/resources/general.cfg index 38a75e8..554bc40 100644 --- a/bbootimg/src/main/resources/general.cfg +++ b/bbootimg/src/main/resources/general.cfg @@ -6,6 +6,8 @@ verity_pk8 = aosp/security/verity.pk8 verity_pem = aosp/security/verity.x509.pem kernelVersionFile = build/unzip_boot/kernel_version.txt kernelConfigFile = build/unzip_boot/kernel_configs.txt +kernelVersionStem = kernel_version.txt +kernelConfigStem = kernel_configs.txt kernelExtracter = aosp/make/tools/extract_kernel.py mkbootimg = aosp/system/tools/mkbootimg/mkbootimg.py dtboMaker = aosp/system/libufdt/utils/src/mkdtboimg.py diff --git a/doc/feature_list.md b/doc/feature_list.md index f509d1d..33deeb8 100644 --- a/doc/feature_list.md +++ b/doc/feature_list.md @@ -21,9 +21,10 @@ should be compatible with "/usr/bin/env sh" ## TODO: command line usage unpack ``` -abe unpack boot.img +abe unpack boot.img out ``` pack ``` -abe pack +abe pack out boot.img ``` + diff --git a/helper/src/main/kotlin/cfig/helper/Helper.kt b/helper/src/main/kotlin/cfig/helper/Helper.kt index 0a444be..4cbe087 100644 --- a/helper/src/main/kotlin/cfig/helper/Helper.kt +++ b/helper/src/main/kotlin/cfig/helper/Helper.kt @@ -23,6 +23,7 @@ import org.slf4j.LoggerFactory import java.io.* import java.nio.ByteBuffer import java.nio.ByteOrder +import java.nio.file.Paths import java.nio.file.attribute.PosixFilePermission import java.security.MessageDigest import java.util.* @@ -47,6 +48,15 @@ class Helper { return gcfg.getProperty(k) } + fun setProp(k: String, v: String) { + gcfg.setProperty(k, v) + } + + fun joinPath(vararg args: String): String { + val joinedPath = Paths.get("", *args) + return joinedPath.normalize().toString() + } + fun joinWithNulls(vararg source: ByteArray?): ByteArray { val baos = ByteArrayOutputStream() for (src in source) { diff --git a/lazybox/src/main/kotlin/cfig/lazybox/App.kt b/lazybox/src/main/kotlin/cfig/lazybox/App.kt index 75e3e4f..97d8076 100644 --- a/lazybox/src/main/kotlin/cfig/lazybox/App.kt +++ b/lazybox/src/main/kotlin/cfig/lazybox/App.kt @@ -22,6 +22,8 @@ fun main(args: Array) { println("bootchart: generate Android bootchart") println("pidstat : given a pid, profile its CPU usage") println("tracecmd : analyze trace-cmd report") + println("cpuinfo : get cpu info from /sys/devices/system/cpu/") + println("sysinfo : get overall system info from Android") exitProcess(0) } if (args[0] == "cpuinfo") {