squashed commit

gradle 8.14
kotlin 2.1.21
updated support of be

Signed-off-by: cfig <yuyezhong@gmail.com>
master
cfig
parent 763427af01
commit 7451a29f45
No known key found for this signature in database
GPG Key ID: B104C307F0FDABB7

@ -15,7 +15,7 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins { plugins {
kotlin("jvm") version "2.0.20" kotlin("jvm") version "2.1.21"
application application
} }
@ -62,11 +62,13 @@ java {
targetCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11
} }
tasks.withType<KotlinCompile>().all { tasks.withType<KotlinCompile>().configureEach {
kotlinOptions { compilerOptions {
freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn" freeCompilerArgs.addAll(
freeCompilerArgs += "-opt-in=kotlin.ExperimentalUnsignedTypes" "-opt-in=kotlin.RequiresOptIn",
jvmTarget = "11" "-opt-in=kotlin.ExperimentalUnsignedTypes"
)
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11)
} }
} }

@ -206,7 +206,7 @@ class Avb {
val readBackInfo = ObjectMapper().readValue(File(getJsonFileName(fileName)), AVBInfo::class.java) val readBackInfo = ObjectMapper().readValue(File(getJsonFileName(fileName)), AVBInfo::class.java)
val intermediateDir = Helper.joinPath(Helper.prop("workDir")!!, "intermediate") val intermediateDir = Helper.joinPath(Helper.prop("workDir")!!, "intermediate")
val newHashDesc = if (File(intermediateDir).exists()) { 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 { } else {
//FIXME: before BootV2 supports abe mode //FIXME: before BootV2 supports abe mode
AVBInfo.parseFrom(Dumpling("$fileName.signed")) AVBInfo.parseFrom(Dumpling("$fileName.signed"))

@ -20,6 +20,7 @@ import cfig.bootimg.cpio.AndroidCpio
import rom.fdt.DTC import rom.fdt.DTC
import cfig.helper.Helper import cfig.helper.Helper
import cfig.helper.ZipHelper import cfig.helper.ZipHelper
import cfig.packable.BootImgParser
import cfig.utils.KernelExtractor import cfig.utils.KernelExtractor
import com.github.freva.asciitable.HorizontalAlign import com.github.freva.asciitable.HorizontalAlign
import org.apache.commons.exec.CommandLine import org.apache.commons.exec.CommandLine
@ -32,6 +33,8 @@ import java.io.File
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Paths import java.nio.file.Paths
import java.io.FileInputStream import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
import java.lang.NumberFormatException import java.lang.NumberFormatException
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.nio.ByteOrder import java.nio.ByteOrder
@ -52,6 +55,12 @@ class Common {
private val log = LoggerFactory.getLogger(Common::class.java) private val log = LoggerFactory.getLogger(Common::class.java)
private const val MAX_ANDROID_VER = 11 private const val MAX_ANDROID_VER = 11
val loadProperties: (String) -> Properties = { fileName ->
Properties().apply {
File(fileName).inputStream().use { load(it) }
}
}
@Throws(IllegalArgumentException::class) @Throws(IllegalArgumentException::class)
fun packOsVersion(x: String?): Int { fun packOsVersion(x: String?): Int {
if (x.isNullOrBlank()) return 0 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<Pair<String, String>> = mutableListOf() val prints: MutableList<Pair<String, String>> = mutableListOf()
val tableHeader = de.vandermeer.asciitable.AsciiTable().apply { val tableHeader = de.vandermeer.asciitable.AsciiTable().apply {
addRule(); addRow("What", "Where"); addRule() addRule(); addRow("What", "Where"); addRule()
} }
val tab = de.vandermeer.asciitable.AsciiTable().let { val tab = de.vandermeer.asciitable.AsciiTable().let {
it.addRule() it.addRule()
if (File("$imageName.signed").exists()) { if (outFile != null) {
it.addRow("re-packed $imageName", "$imageName.signed") it.addRow("re-packed $imageName", outFile)
prints.add(Pair("re-packed $imageName", "$imageName.signed")) prints.add(Pair("re-packed $imageName", outFile))
} else { } else {
it.addRow("re-packed $imageName", "$imageName.clear") if (File("$imageName.signed").exists()) {
prints.add(Pair("re-packed $imageName", "$imageName.clear")) 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.addRule()
it 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) { fun printPackSummaryInternal(imageName: String) {
val prints: MutableList<Pair<String, String>> = mutableListOf() val prints: MutableList<Pair<String, String>> = mutableListOf()
val tableHeader = de.vandermeer.asciitable.AsciiTable().apply { 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()) 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")
}
} }
} }

@ -30,8 +30,58 @@ class Signer {
companion object { companion object {
private val log = LoggerFactory.getLogger(Signer::class.java) 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) { 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 ai = ObjectMapper().readValue(File(getJsonFileName(output)), AVBInfo::class.java)
val alg = Algorithms.get(ai.header!!.algorithm_type) val alg = Algorithms.get(ai.header!!.algorithm_type)
val bootDesc = ai.auxBlob!!.hashDescriptors[0] val bootDesc = ai.auxBlob!!.hashDescriptors[0]

@ -194,6 +194,7 @@ class AndroidCpio {
val rounded = Helper.round_to_multiple(len, 256) //file in page 256 val rounded = Helper.round_to_multiple(len, 256) //file in page 256
if (len != rounded) { if (len != rounded) {
FileOutputStream(outFile, true).use { fos -> FileOutputStream(outFile, true).use { fos ->
log.info("cpio padding size: " + (rounded - len) + " bytes")
fos.write(ByteArray((rounded - len).toInt())) fos.write(ByteArray((rounded - len).toInt()))
} }
} }

@ -16,15 +16,14 @@ package cfig.bootimg.v2
import avb.AVBInfo import avb.AVBInfo
import cfig.Avb import cfig.Avb
import cfig.bootimg.Common as C
import cfig.bootimg.Common import cfig.bootimg.Common
import cfig.bootimg.Common.Companion.deleleIfExists import cfig.bootimg.Common.Companion.deleleIfExists
import cfig.bootimg.Common.Companion.shortenPath
import cfig.bootimg.Signer import cfig.bootimg.Signer
import cfig.helper.Dumpling import cfig.helper.Dumpling
import cfig.helper.Helper import cfig.helper.Helper
import cfig.helper.ZipHelper import cfig.helper.ZipHelper
import cfig.packable.VBMetaParser import cfig.packable.VBMetaParser
import rom.fdt.DTC
import cfig.utils.EnvironmentVerifier import cfig.utils.EnvironmentVerifier
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import com.github.freva.asciitable.HorizontalAlign 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.CommandLine
import org.apache.commons.exec.DefaultExecutor import org.apache.commons.exec.DefaultExecutor
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import rom.fdt.DTC
import java.io.File import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
import java.io.FileOutputStream import java.io.FileOutputStream
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.nio.ByteOrder import java.nio.ByteOrder
import cfig.bootimg.Common as C
data class BootV2( data class BootV2(
var info: MiscInfo = MiscInfo(), var info: MiscInfo = MiscInfo(),
@ -95,7 +96,9 @@ data class BootV2(
companion object { companion object {
private val log = LoggerFactory.getLogger(BootV2::class.java) 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 mapper = ObjectMapper()
private val dtsSuffix = Helper.prop("config.dts_suffix") private val dtsSuffix = Helper.prop("config.dts_suffix")
@ -128,7 +131,7 @@ data class BootV2(
} }
} }
ret.kernel.let { theKernel -> ret.kernel.let { theKernel ->
theKernel.file = File(workDir, "kernel").path theKernel.file = File(workDir(), "kernel").path
theKernel.size = bh2.kernelLength theKernel.size = bh2.kernelLength
theKernel.loadOffset = bh2.kernelOffset theKernel.loadOffset = bh2.kernelOffset
theKernel.position = ret.getKernelPosition() theKernel.position = ret.getKernelPosition()
@ -138,28 +141,28 @@ data class BootV2(
theRamdisk.loadOffset = bh2.ramdiskOffset theRamdisk.loadOffset = bh2.ramdiskOffset
theRamdisk.position = ret.getRamdiskPosition() theRamdisk.position = ret.getRamdiskPosition()
if (bh2.ramdiskLength > 0) { if (bh2.ramdiskLength > 0) {
theRamdisk.file = File(workDir, "ramdisk.img").path theRamdisk.file = File(workDir(), "ramdisk.img").path
} }
} }
if (bh2.secondBootloaderLength > 0) { if (bh2.secondBootloaderLength > 0) {
ret.secondBootloader = CommArgs() ret.secondBootloader = CommArgs()
ret.secondBootloader!!.size = bh2.secondBootloaderLength ret.secondBootloader!!.size = bh2.secondBootloaderLength
ret.secondBootloader!!.loadOffset = bh2.secondBootloaderOffset ret.secondBootloader!!.loadOffset = bh2.secondBootloaderOffset
ret.secondBootloader!!.file = File(workDir, "second").path ret.secondBootloader!!.file = File(workDir(), "second").path
ret.secondBootloader!!.position = ret.getSecondBootloaderPosition() ret.secondBootloader!!.position = ret.getSecondBootloaderPosition()
} }
if (bh2.recoveryDtboLength > 0) { if (bh2.recoveryDtboLength > 0) {
ret.recoveryDtbo = CommArgsLong() ret.recoveryDtbo = CommArgsLong()
ret.recoveryDtbo!!.size = bh2.recoveryDtboLength ret.recoveryDtbo!!.size = bh2.recoveryDtboLength
ret.recoveryDtbo!!.loadOffset = bh2.recoveryDtboOffset //Q 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() ret.recoveryDtbo!!.position = ret.getRecoveryDtboPosition()
} }
if (bh2.dtbLength > 0) { if (bh2.dtbLength > 0) {
ret.dtb = DtbArgsLong() ret.dtb = DtbArgsLong()
ret.dtb!!.size = bh2.dtbLength ret.dtb!!.size = bh2.dtbLength
ret.dtb!!.loadOffset = bh2.dtbOffset //Q 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() ret.dtb!!.position = ret.getDtbPosition()
} }
} }
@ -206,7 +209,7 @@ data class BootV2(
fun extractImages(): BootV2 { fun extractImages(): BootV2 {
//info //info
mapper.writerWithDefaultPrettyPrinter().writeValue(File(workDir + info.json), this) mapper.writerWithDefaultPrettyPrinter().writeValue(File(workDir(), info.json), this)
//kernel //kernel
if (kernel.size > 0) { if (kernel.size > 0) {
Common.dumpKernel(Helper.Slice(info.output, kernel.position.toInt(), kernel.size, kernel.file!!)) Common.dumpKernel(Helper.Slice(info.output, kernel.position.toInt(), kernel.size, kernel.file!!))
@ -220,15 +223,17 @@ data class BootV2(
//ramdisk //ramdisk
if (this.ramdisk.size > 0) { if (this.ramdisk.size > 0) {
val fmt = C.dumpRamdisk( 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" this.ramdisk.file = this.ramdisk.file!! + ".$fmt"
if (fmt == "xz") { 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 this.ramdisk.xzFlags = checkType
} }
//dump info again //dump info again
mapper.writerWithDefaultPrettyPrinter().writeValue(File(workDir + this.info.json), this) mapper.writerWithDefaultPrettyPrinter().writeValue(File(workDir(), this.info.json), this)
} }
//second bootloader //second bootloader
secondBootloader?.let { secondBootloader?.let {
@ -254,7 +259,7 @@ data class BootV2(
this.dtb!!.dtbList = DTC.parseMultiple(dtb!!.file!!) this.dtb!!.dtbList = DTC.parseMultiple(dtb!!.file!!)
log.info("dtb sz = " + this.dtb!!.dtbList.size) log.info("dtb sz = " + this.dtb!!.dtbList.size)
//dump info again //dump info again
mapper.writerWithDefaultPrettyPrinter().writeValue(File(workDir + info.json), this) mapper.writerWithDefaultPrettyPrinter().writeValue(File(workDir(), info.json), this)
//dump dtb items //dump dtb items
DTC.extractMultiple(dtb!!.file!!, this.dtb!!.dtbList) DTC.extractMultiple(dtb!!.file!!, this.dtb!!.dtbList)
@ -285,8 +290,8 @@ data class BootV2(
} }
val tab = AsciiTable().let { val tab = AsciiTable().let {
it.addRule() it.addRule()
it.addRow("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", workDir + info.output.removeSuffix(".img") + ".json")) prints.add(Pair("image info", shortenPath(File(workDir(), info.output.removeSuffix(".img") + ".json").path)))
if (this.info.verify.startsWith("VB2.0")) { if (this.info.verify.startsWith("VB2.0")) {
it.addRule() it.addRule()
val verifyStatus = if (this.info.verify.contains("PASS")) { val verifyStatus = if (this.info.verify.contains("PASS")) {
@ -295,8 +300,8 @@ data class BootV2(
"verify fail" "verify fail"
} }
Avb.getJsonFileName(info.output).let { jsonFile -> Avb.getJsonFileName(info.output).let { jsonFile ->
it.addRow("AVB info [$verifyStatus]", jsonFile) it.addRow("AVB info [$verifyStatus]", shortenPath(jsonFile))
prints.add(Pair("AVB info [$verifyStatus]", jsonFile)) prints.add(Pair("AVB info [$verifyStatus]", shortenPath(jsonFile)))
if (File(jsonFile).exists()) { if (File(jsonFile).exists()) {
mapper.readValue(File(jsonFile), AVBInfo::class.java).let { ai -> mapper.readValue(File(jsonFile), AVBInfo::class.java).let { ai ->
val inspectRet = Avb.inspectKey(ai) val inspectRet = Avb.inspectKey(ai)
@ -311,11 +316,11 @@ data class BootV2(
//kernel //kernel
it.addRule() it.addRule()
if (this.kernel.size > 0) { 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 } else { //only for ramdisk.img, Issue #122
it.addRow("kernel", "NONE") 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 -> File(Helper.prop("kernelVersionFile")).let { kernelVersionFile ->
if (kernelVersionFile.exists()) { if (kernelVersionFile.exists()) {
it.addRow("\\-- version " + kernelVersionFile.readLines().toString(), kernelVersionFile.path) it.addRow("\\-- version " + kernelVersionFile.readLines().toString(), kernelVersionFile.path)
@ -331,10 +336,10 @@ data class BootV2(
//ramdisk //ramdisk
if (this.ramdisk.size > 0) { if (this.ramdisk.size > 0) {
it.addRule() it.addRule()
it.addRow("ramdisk", this.ramdisk.file) it.addRow("ramdisk", shortenPath(this.ramdisk.file!!))
prints.add(Pair("ramdisk", this.ramdisk.file.toString())) prints.add(Pair("ramdisk", shortenPath(this.ramdisk.file.toString())))
it.addRow("\\-- extracted ramdisk rootfs", File(workDir, "root").path) it.addRow("\\-- extracted ramdisk rootfs", shortenPath(File(workDir(), "root").path))
prints.add(Pair("\\-- extracted ramdisk rootfs", File(workDir, "root").path)) prints.add(Pair("\\-- extracted ramdisk rootfs", shortenPath(File(workDir(), "root").path)))
} }
//second //second
this.secondBootloader?.let { theSecondBootloader -> this.secondBootloader?.let { theSecondBootloader ->
@ -357,11 +362,11 @@ data class BootV2(
if (theDtb.size > 0) { if (theDtb.size > 0) {
val dtbCount = this.dtb!!.dtbList.size val dtbCount = this.dtb!!.dtbList.size
it.addRule() it.addRule()
it.addRow("dtb", theDtb.file) it.addRow("dtb", theDtb.file?.let { fullPath -> shortenPath(fullPath) })
prints.add(Pair("dtb", theDtb.file.toString())) prints.add(Pair("dtb", shortenPath(theDtb.file.toString())))
if (File(theDtb.file + ".0.${dtsSuffix}").exists()) { if (File(theDtb.file + ".0.${dtsSuffix}").exists()) {
it.addRow("\\-- decompiled dts [$dtbCount]", theDtb.file + ".*.${dtsSuffix}") it.addRow("\\-- decompiled dts [$dtbCount]", shortenPath(theDtb.file!!) + ".*.${dtsSuffix}")
prints.add(Pair("\\-- decompiled dts [$dtbCount]", theDtb.file + ".*.${dtsSuffix}")) prints.add(Pair("\\-- decompiled dts [$dtbCount]", shortenPath(theDtb.file!!) + ".*.${dtsSuffix}"))
} }
} }
} }
@ -372,8 +377,8 @@ data class BootV2(
val tabVBMeta = AsciiTable().let { val tabVBMeta = AsciiTable().let {
if (File("vbmeta.img").exists()) { if (File("vbmeta.img").exists()) {
it.addRule() it.addRule()
it.addRow("vbmeta.img", Avb.getJsonFileName("vbmeta.img")) it.addRow("vbmeta.img", shortenPath(Avb.getJsonFileName("vbmeta.img")))
prints.add(Pair("vbmeta.img", Avb.getJsonFileName("vbmeta.img"))) prints.add(Pair("vbmeta.img", shortenPath(Avb.getJsonFileName("vbmeta.img"))))
it.addRule() it.addRule()
"\n" + it.render() "\n" + it.render()
} else { } else {
@ -382,15 +387,17 @@ data class BootV2(
} }
if (EnvironmentVerifier().isWindows) { if (EnvironmentVerifier().isWindows) {
log.info("\n" + log.info(
com.github.freva.asciitable.AsciiTable.getTable( "\n" +
com.github.freva.asciitable.AsciiTable.BASIC_ASCII, com.github.freva.asciitable.AsciiTable.getTable(
prints, mutableListOf( com.github.freva.asciitable.AsciiTable.BASIC_ASCII,
com.github.freva.asciitable.Column().header("What") prints, mutableListOf(
.dataAlign(HorizontalAlign.LEFT) com.github.freva.asciitable.Column().header("What")
.with { it.first }, .dataAlign(HorizontalAlign.LEFT)
com.github.freva.asciitable.Column().header("Where").with { it.second }) .with { it.first },
)) com.github.freva.asciitable.Column().header("Where").with { it.second })
)
)
} else { } else {
log.info( log.info(
"\n\t\t\tUnpack Summary of ${info.output}\n{}\n{}{}", "\n\t\t\tUnpack Summary of ${info.output}\n{}\n{}{}",
@ -433,14 +440,14 @@ data class BootV2(
ramdisk.file = null ramdisk.file = null
ramdisk.loadOffset = 0 ramdisk.loadOffset = 0
} else { } 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 //do nothing if we have ramdisk.img.gz but no /root
log.warn("Use prebuilt ramdisk file: ${this.ramdisk.file}") log.warn("Use prebuilt ramdisk file: ${this.ramdisk.file}")
} else { } else {
File(this.ramdisk.file!!).deleleIfExists() File(this.ramdisk.file!!).deleleIfExists()
File(this.ramdisk.file!!.removeSuffix(".gz")).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!!, 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() this.ramdisk.size = File(this.ramdisk.file!!).length().toInt()
} }
@ -466,18 +473,21 @@ data class BootV2(
0 -> { 0 -> {
Common.hashFileAndSize(kernel.file, ramdisk.file, secondBootloader?.file) Common.hashFileAndSize(kernel.file, ramdisk.file, secondBootloader?.file)
} }
1 -> { 1 -> {
Common.hashFileAndSize( Common.hashFileAndSize(
kernel.file, ramdisk.file, kernel.file, ramdisk.file,
secondBootloader?.file, recoveryDtbo?.file secondBootloader?.file, recoveryDtbo?.file
) )
} }
2 -> { 2 -> {
Common.hashFileAndSize( Common.hashFileAndSize(
kernel.file, ramdisk.file, kernel.file, ramdisk.file,
secondBootloader?.file, recoveryDtbo?.file, dtb?.file secondBootloader?.file, recoveryDtbo?.file, dtb?.file
) )
} }
else -> { else -> {
throw IllegalArgumentException("headerVersion ${info.headerVersion} illegal") throw IllegalArgumentException("headerVersion ${info.headerVersion} illegal")
} }

@ -22,6 +22,7 @@ import cfig.bootimg.Common
import cfig.utils.EnvironmentVerifier import cfig.utils.EnvironmentVerifier
import cfig.bootimg.Common.Companion.deleleIfExists import cfig.bootimg.Common.Companion.deleleIfExists
import cfig.bootimg.Common.Companion.getPaddingSize import cfig.bootimg.Common.Companion.getPaddingSize
import cfig.bootimg.Common.Companion.shortenPath
import cfig.bootimg.Signer import cfig.bootimg.Signer
import cfig.helper.Helper import cfig.helper.Helper
import cfig.helper.Dumpling import cfig.helper.Dumpling
@ -228,22 +229,26 @@ data class BootV3(
} }
if (fileName != info.role) { if (fileName != info.role) {
Helper.setProp("out.file", fileName)
if (bSigningNeeded) { if (bSigningNeeded) {
log.info("x1")
Helper.setProp("out.file", "$fileName.signed")
//@formatter:off //@formatter:off
File(Helper.joinPath(Helper.prop("intermediateDir")!!, info.role + ".signed")) File(Helper.joinPath(Helper.prop("intermediateDir")!!, info.role + ".signed"))
.copyTo(File(fileName), true) .copyTo(File(Helper.prop("out.file")!!), true)
//@formatter:on //@formatter:on
log.info("Signed image saved as $fileName") log.info("Signed image saved as " + Helper.prop("out.file"))
} else { } else {
log.info("x2")
Helper.setProp("out.file", fileName)
//@formatter:off //@formatter:off
File(Helper.joinPath(Helper.prop("intermediateDir")!!, info.role + ".clear")) File(Helper.joinPath(Helper.prop("intermediateDir")!!, info.role + ".clear"))
.copyTo(File(fileName), true) .copyTo(File(Helper.prop("out.file")!!), true)
//@formatter:on //@formatter:on
log.info("Unsigned image saved as $fileName") log.info("Unsigned image saved as " + Helper.prop("out.file"))
} }
} else { } else {
if (bSigningNeeded) { if (bSigningNeeded) {
log.info("x3")
Helper.setProp("out.file", info.role + ".signed") Helper.setProp("out.file", info.role + ".signed")
//@formatter:off //@formatter:off
File(Helper.joinPath(Helper.prop("intermediateDir")!!, info.role + ".signed")) File(Helper.joinPath(Helper.prop("intermediateDir")!!, info.role + ".signed"))
@ -251,6 +256,7 @@ data class BootV3(
//@formatter:on //@formatter:on
log.info("Signed image saved as ${info.role}.signed") log.info("Signed image saved as ${info.role}.signed")
} else { } else {
log.info("x4")
Helper.setProp("out.file", info.role + ".clear") Helper.setProp("out.file", info.role + ".clear")
//@formatter:off //@formatter:off
File(Helper.joinPath(Helper.prop("intermediateDir")!!, info.role + ".clear")) File(Helper.joinPath(Helper.prop("intermediateDir")!!, info.role + ".clear"))
@ -385,27 +391,27 @@ data class BootV3(
} }
val tab = AsciiTable().let { val tab = AsciiTable().let {
it.addRule() it.addRule()
it.addRow("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", Helper.joinPath(workDir, info.role.removeSuffix(".img") + ".json"))) prints.add(Pair("image info", shortenPath(Helper.joinPath(workDir, info.role.removeSuffix(".img") + ".json"))))
it.addRule() it.addRule()
if (this.kernel.size > 0) { if (this.kernel.size > 0) {
it.addRow("kernel", this.kernel.file) it.addRow("kernel", shortenPath(this.kernel.file))
prints.add(Pair("kernel", this.kernel.file)) prints.add(Pair("kernel", shortenPath(this.kernel.file)))
File(Helper.joinPath(workDir, Helper.prop("kernelVersionStem")!!)).let { kernelVersionFile -> File(Helper.joinPath(workDir, Helper.prop("kernelVersionStem")!!)).let { kernelVersionFile ->
if (kernelVersionFile.exists()) { if (kernelVersionFile.exists()) {
it.addRow("\\-- version " + kernelVersionFile.readLines().toString(), kernelVersionFile.path) it.addRow("\\-- version " + kernelVersionFile.readLines().toString(), shortenPath( kernelVersionFile.path))
prints.add( prints.add(
Pair( Pair(
"\\-- version " + kernelVersionFile.readLines().toString(), "\\-- version " + kernelVersionFile.readLines().toString(),
kernelVersionFile.path shortenPath(kernelVersionFile.path)
) )
) )
} }
} }
File(Helper.joinPath(workDir, Helper.prop("kernelConfigStem")!!)).let { kernelConfigFile -> File(Helper.joinPath(workDir, Helper.prop("kernelConfigStem")!!)).let { kernelConfigFile ->
if (kernelConfigFile.exists()) { if (kernelConfigFile.exists()) {
it.addRow("\\-- config", kernelConfigFile.path) it.addRow("\\-- config", shortenPath(kernelConfigFile.path))
prints.add(Pair("\\-- config", kernelConfigFile.path)) prints.add(Pair("\\-- config", shortenPath(kernelConfigFile.path)))
} }
} }
it.addRule() it.addRule()
@ -442,11 +448,11 @@ data class BootV3(
File(Avb.getJsonFileName("sig.boot")).let { jsonFile -> File(Avb.getJsonFileName("sig.boot")).let { jsonFile ->
if (jsonFile.exists()) { if (jsonFile.exists()) {
it.addRow("GKI signature 2.0", this.bootSignature.file) 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))) it.addRow("\\------ signing key", Avb.inspectKey(mapper.readValue(jsonFile, AVBInfo::class.java)))
//basic //basic
prints.add(Pair("GKI signature 2.0", this.bootSignature.file)) 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( prints.add(
Pair( Pair(
"\\------ signing key", "\\------ signing key",
@ -458,19 +464,19 @@ data class BootV3(
File(Avb.getJsonFileName("sig.kernel")).let { jsonFile -> File(Avb.getJsonFileName("sig.kernel")).let { jsonFile ->
if (jsonFile.exists()) { if (jsonFile.exists()) {
val readBackAvb = mapper.readValue(jsonFile, AVBInfo::class.java) 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.addRow("\\------ signing key", Avb.inspectKey(readBackAvb))
it.addRule() it.addRule()
//basic //basic
prints.add(Pair("\\-- kernel", jsonFile.path)) prints.add(Pair("\\-- kernel", shortenPath(jsonFile.path)))
prints.add(Pair("\\------ signing key", Avb.inspectKey(readBackAvb))) prints.add(Pair("\\------ signing key", Avb.inspectKey(readBackAvb)))
} }
} }
//AVB info //AVB info
Avb.getJsonFileName(info.role).let { jsonFile -> Avb.getJsonFileName(info.role).let { jsonFile ->
it.addRow("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()) jsonFile else "NONE")) prints.add(Pair("AVB info", if (File(jsonFile).exists()) shortenPath(jsonFile) else "NONE"))
if (File(jsonFile).exists()) { if (File(jsonFile).exists()) {
mapper.readValue(File(jsonFile), AVBInfo::class.java).let { ai -> mapper.readValue(File(jsonFile), AVBInfo::class.java).let { ai ->
it.addRow("\\------ signing key", Avb.inspectKey(ai)) it.addRow("\\------ signing key", Avb.inspectKey(ai))
@ -487,10 +493,10 @@ data class BootV3(
if (File(vbmetaCompanion).exists()) { if (File(vbmetaCompanion).exists()) {
log.warn("XXXX: Found vbmeta.img, parsing ...") log.warn("XXXX: Found vbmeta.img, parsing ...")
//basic //basic
prints.add(Pair("vbmeta.img", Avb.getJsonFileName("vbmeta.img"))) prints.add(Pair("vbmeta.img", shortenPath(Avb.getJsonFileName("vbmeta.img"))))
//table //table
it.addRule() it.addRule()
it.addRow("vbmeta.img", Avb.getJsonFileName("vbmeta.img")) it.addRow("vbmeta.img", shortenPath(Avb.getJsonFileName("vbmeta.img")))
it.addRule() it.addRule()
"\n" + it.render() "\n" + it.render()
} else { } else {

@ -19,18 +19,19 @@ import cc.cfig.io.Struct
import cfig.Avb import cfig.Avb
import cfig.bootimg.Common import cfig.bootimg.Common
import cfig.bootimg.Common.Companion.deleleIfExists import cfig.bootimg.Common.Companion.deleleIfExists
import cfig.bootimg.Common.Companion.toShortenPath
import cfig.bootimg.Signer import cfig.bootimg.Signer
import cfig.helper.Dumpling import cfig.helper.Dumpling
import cfig.helper.Helper import cfig.helper.Helper
import cfig.helper.ZipHelper import cfig.helper.ZipHelper
import cfig.packable.VBMetaParser import cfig.packable.VBMetaParser
import rom.fdt.DTC
import cfig.utils.EnvironmentVerifier import cfig.utils.EnvironmentVerifier
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import de.vandermeer.asciitable.AsciiTable import de.vandermeer.asciitable.AsciiTable
import org.apache.commons.exec.CommandLine import org.apache.commons.exec.CommandLine
import org.apache.commons.exec.DefaultExecutor import org.apache.commons.exec.DefaultExecutor
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import rom.fdt.DTC
import java.io.* import java.io.*
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.nio.ByteOrder import java.nio.ByteOrder
@ -265,7 +266,9 @@ data class VendorBoot(
} }
this.dtb.size = File(this.dtb.file).length().toInt() this.dtb.size = File(this.dtb.file).length().toInt()
//header //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() val encodedHeader = this.toHeader().encode()
fos.write(encodedHeader) fos.write(encodedHeader)
fos.write(ByteArray(Helper.round_to_multiple(encodedHeader.size, this.info.pageSize) - encodedHeader.size)) fos.write(ByteArray(Helper.round_to_multiple(encodedHeader.size, this.info.pageSize) - encodedHeader.size))
@ -303,17 +306,17 @@ data class VendorBoot(
} }
} }
//write //write
FileOutputStream("${this.info.role}.clear", true).use { fos -> FileOutputStream(clearFile, true).use { fos ->
fos.write(bf.array(), 0, bf.position()) fos.write(bf.array(), 0, bf.position())
} }
//google way //google way
this.toCommandLine().addArgument(this.info.role + ".google").let { this.toCommandLine().addArgument(googleFile).let {
log.info(it.toString()) log.info(it.toString())
DefaultExecutor().execute(it) DefaultExecutor().execute(it)
} }
Helper.assertFileEquals(this.info.role + ".clear", this.info.role + ".google") Helper.assertFileEquals(clearFile, googleFile)
return this return this
} }
@ -321,7 +324,15 @@ data class VendorBoot(
val avbtool = String.format(Helper.prop("avbtool")!!, "v1.2") val avbtool = String.format(Helper.prop("avbtool")!!, "v1.2")
File(Avb.getJsonFileName(info.role)).let { File(Avb.getJsonFileName(info.role)).let {
if (it.exists()) { 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 { } else {
log.warn("skip signing of ${info.role}") log.warn("skip signing of ${info.role}")
} }
@ -329,6 +340,15 @@ data class VendorBoot(
return this 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 { fun updateVbmeta(): VendorBoot {
Avb.updateVbmeta(info.role) Avb.updateVbmeta(info.role)
return this return this
@ -416,31 +436,31 @@ data class VendorBoot(
} }
val tab = AsciiTable().let { val tab = AsciiTable().let {
it.addRule() 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) it.addRow("image info", imageInfoJsonFile)
prints.add(Pair("image info", imageInfoJsonFile)) prints.add(Pair("image info", imageInfoJsonFile))
it.addRule() it.addRule()
it.addRow("ramdisk", this.ramdisk.file) it.addRow("ramdisk", this.ramdisk.file.toShortenPath())
prints.add(Pair("ramdisk", this.ramdisk.file)) prints.add(Pair("ramdisk", this.ramdisk.file.toShortenPath()))
if (this.ramdisk_table.size > 0) { if (this.ramdisk_table.size > 0) {
this.ramdisk_table.ramdidks.forEachIndexed { index, entry -> this.ramdisk_table.ramdidks.forEachIndexed { index, entry ->
//fancy ascii //fancy ascii
it.addRow("-- ${entry.type} ramdisk[${index + 1}/${this.ramdisk_table.ramdidks.size}]", entry.file) 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) it.addRow("------- extracted rootfs", File(workDir, "root.${index + 1}").path.toShortenPath())
//basic ascii //basic ascii
//@formatter:off //@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 //@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 { } else {
it.addRow("\\-- 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)) prints.add(Pair("\\-- extracted ramdisk rootfs", File(workDir, "root").path.toShortenPath()))
} }
it.addRule() it.addRule()
if (this.dtb.size > 0) { if (this.dtb.size > 0) {
it.addRow("dtb", this.dtb.file) it.addRow("dtb", this.dtb.file.toShortenPath())
prints.add(Pair("dtb", this.dtb.file)) prints.add(Pair("dtb", this.dtb.file.toShortenPath()))
if (File(this.dtb.file + ".0.${dtsSuffix}").exists()) { if (File(this.dtb.file + ".0.${dtsSuffix}").exists()) {
it.addRow("\\-- decompiled dts [${dtb.dtbList.size}]", dtb.file + "*.${dtsSuffix}") it.addRow("\\-- decompiled dts [${dtb.dtbList.size}]", dtb.file + "*.${dtsSuffix}")
prints.add(Pair("\\-- 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) { if (this.bootconfig.size > 0) {
it.addRule() it.addRule()
it.addRow("bootconfig", this.bootconfig.file) it.addRow("bootconfig", this.bootconfig.file.toShortenPath())
prints.add(Pair("bootconfig", this.bootconfig.file)) prints.add(Pair("bootconfig", this.bootconfig.file.toShortenPath()))
} }
it.addRule() it.addRule()
Avb.getJsonFileName(info.role).let { jsonFile -> Avb.getJsonFileName(info.role).let { jsonFile ->
if (File(jsonFile).exists()) { if (File(jsonFile).exists()) {
it.addRow("AVB info", jsonFile) it.addRow("AVB info", jsonFile.toShortenPath())
prints.add(Pair("AVB info", jsonFile)) prints.add(Pair("AVB info", jsonFile.toShortenPath()))
mapper.readValue(File(jsonFile), AVBInfo::class.java).let { ai -> mapper.readValue(File(jsonFile), AVBInfo::class.java).let { ai ->
it.addRow("\\-- signing key", Avb.inspectKey(ai)) it.addRow("\\-- signing key", Avb.inspectKey(ai))
prints.add(Pair(" \\-- signing key", Avb.inspectKey(ai))) prints.add(Pair(" \\-- signing key", Avb.inspectKey(ai)))
@ -474,8 +494,8 @@ data class VendorBoot(
val tabVBMeta = AsciiTable().let { val tabVBMeta = AsciiTable().let {
if (File("vbmeta.img").exists()) { if (File("vbmeta.img").exists()) {
it.addRule() it.addRule()
it.addRow("vbmeta.img", Avb.getJsonFileName("vbmeta.img")) it.addRow("vbmeta.img", Avb.getJsonFileName("vbmeta.img").toShortenPath())
prints.add(Pair("vbmeta.img", Avb.getJsonFileName("vbmeta.img"))) prints.add(Pair("vbmeta.img", Avb.getJsonFileName("vbmeta.img").toShortenPath()))
it.addRule() it.addRule()
"\n" + it.render() "\n" + it.render()
} else { } else {
@ -492,8 +512,8 @@ data class VendorBoot(
return this return this
} }
fun printPackSummary(): VendorBoot { fun printPackSummary(outFileName: String): VendorBoot {
Common.printPackSummary(info.role) Common.printPackSummary(info.role, outFileName)
return this return this
} }

@ -15,6 +15,7 @@
package cfig.packable package cfig.packable
import avb.blob.Footer import avb.blob.Footer
import cfig.bootimg.Common
import cfig.bootimg.Common.Companion.probeHeaderVersion import cfig.bootimg.Common.Companion.probeHeaderVersion
import cfig.bootimg.v2.BootV2 import cfig.bootimg.v2.BootV2
import cfig.bootimg.v2.BootV2Dialects import cfig.bootimg.v2.BootV2Dialects
@ -45,14 +46,20 @@ class BootImgParser : IPackable {
} }
override fun unpack(fileName: String) { override fun unpack(fileName: String) {
unpackInternal(fileName, fileName, outDir) unpackInternal(fileName, outDir)
} }
fun unpackInternal(targetFile: String, fileName: String, unpackDir: String) { // called via reflection
log.info("unpackInternal(fileName: $fileName, unpackDir: $unpackDir)") fun unpackInternal(inFileName: String, unpackDir: String) {
Helper.setProp("workDir", unpackDir) 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() clear()
File("$outDir/role").writeText(File(File(targetFile).canonicalPath).name) //create workspace file
Common.createWorkspaceIni(fileName)
//create workspace file done
val hv = probeHeaderVersion(fileName) val hv = probeHeaderVersion(fileName)
log.info("header version $hv") log.info("header version $hv")
when (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)") log.info("packInternal(targetFile: $targetFile, fileName: $fileName, workspace: $workspace)")
Helper.setProp("workDir", workspace) Helper.setProp("workDir", workspace)
val cfgFile = Helper.joinPath(outDir, targetFile.removeSuffix(".img") + ".json") val cfgFile = Helper.joinPath(outDir, targetFile.removeSuffix(".img") + ".json")
@ -140,7 +204,8 @@ class BootImgParser : IPackable {
} }
override fun pack(fileName: String) { 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) { fun flash(fileName: String) {

@ -1,6 +1,7 @@
package packable package packable
import cfig.bootimg.Common import cfig.bootimg.Common
import cfig.bootimg.v3.VendorBoot
import cfig.helper.Helper import cfig.helper.Helper
import cfig.helper.Helper.Companion.check_call import cfig.helper.Helper.Companion.check_call
import cfig.helper.Helper.Companion.check_output import cfig.helper.Helper.Companion.check_output
@ -15,13 +16,24 @@ class DeviceTreeParser : IPackable {
override fun capabilities(): List<String> { override fun capabilities(): List<String> {
return listOf("^.*\\.dtb$") return listOf("^.*\\.dtb$")
} }
override val loopNo: Int override val loopNo: Int
get() = 1 get() = 1
override fun unpack(fileName: String) { override fun unpack(fileName: String) {
super.clear() unpackInternal(fileName, Helper.prop("workDir")!!)
log.info("unpacking $fileName") }
val outFile = workDir + fileName.removeSuffix(".dtb") + "." + Helper.prop("config.dts_suffix")
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) DTC().decompile(fileName, outFile)
//print summary //print summary
@ -32,15 +44,26 @@ class DeviceTreeParser : IPackable {
} }
override fun pack(fileName: String) { override fun pack(fileName: String) {
log.info("packing $fileName") packInternal(Helper.prop("workDir")!!, fileName)
val outFile = workDir + fileName.removeSuffix(".dtb") + "." + Helper.prop("config.dts_suffix") }
check(DTC().compile(outFile, "$fileName.new")) { "fail to compile dts" }
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 //print summary
val prints: MutableList<Pair<String, String>> = mutableListOf() val prints: MutableList<Pair<String, String>> = mutableListOf()
prints.add(Pair("DTS", outFile)) prints.add(Pair("DTS", dtsSrc))
prints.add(Pair("updated DTB", "$fileName.new")) prints.add(Pair("updated DTB", outFileName))
log.info("\n\t\t\tPack Summary of {}\n{}\n", fileName, Common.table2String(prints)) log.info("\n\t\t\tPack Summary of {}\n{}\n", outFileName, Common.table2String(prints))
} }
fun pull(fileName: String) { fun pull(fileName: String) {

@ -14,10 +14,12 @@
package cfig.packable package cfig.packable
import cfig.helper.Helper
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import packable.DeviceTreeParser import packable.DeviceTreeParser
import rom.sparse.SparseImgParser import rom.sparse.SparseImgParser
import java.io.File import java.io.File
import java.util.Properties
import java.util.regex.Pattern import java.util.regex.Pattern
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.reflect.full.createInstance import kotlin.reflect.full.createInstance
@ -28,6 +30,12 @@ class PackableLauncher
fun main(args: Array<String>) { fun main(args: Array<String>) {
val log = LoggerFactory.getLogger(PackableLauncher::class.java) 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<List<String>, KClass<IPackable>>() val packablePool = mutableMapOf<List<String>, KClass<IPackable>>()
listOf( listOf(
BootImgParser(), BootImgParser(),
@ -44,25 +52,43 @@ fun main(args: Array<String>) {
packablePool.put(it.capabilities(), it::class as KClass<IPackable>) packablePool.put(it.capabilities(), it::class as KClass<IPackable>)
} }
packablePool.forEach { packablePool.forEach {
log.debug("" + it.key + "/" + it.value) log.info("" + it.key + "/" + it.value)
} }
var targetFile: String? = null var targetFile: String? = null
var targetHandler: KClass<IPackable>? = null var targetHandler: KClass<IPackable>? = 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@{ run found@{
for (currentLoopNo in 0..1) { //currently we have only 2 loops for (currentLoopNo in 0..1) { //currently we have only 2 loops
devLog.info("loop #" + currentLoopNo)
if (args.size > 1) { // manual mode 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 File(args[1]).canonicalPath
} else if (File(args[1] + "/role").isFile) { } else if (File(args[1]).isDirectory and File(args[1], "workspace.ini").isFile) { //pack
File(args[1] + "/role").readText().trim() loadProperties(File(args[1], "workspace.ini").canonicalPath).getProperty("role")
} else { } else { //wrong
log.error("Not sure of what to do: " + args.asList().toString()) log.error("Not a file: " + args[1])
exitProcess(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 packablePool
.filter { it.value.createInstance().loopNo == currentLoopNo } .filter { it.value.createInstance().loopNo == currentLoopNo }
.forEach { p -> .forEach { p ->
@ -75,14 +101,15 @@ fun main(args: Array<String>) {
} }
} }
} else { // lazy mode } else { // lazy mode
devLog.info("lazy mode (in current dir)")
File(".").listFiles()!!.forEach { file -> File(".").listFiles()!!.forEach { file ->
packablePool packablePool
.filter { it.value.createInstance().loopNo == currentLoopNo } .filter { it.value.createInstance().loopNo == currentLoopNo }
.forEach { p -> .forEach { p ->
for (item in p.key) { for (item in p.key) {
if (Pattern.compile(item).matcher(file.name).matches()) { if (Pattern.compile(item).matcher(file.name).matches()) {
log.debug("Found: " + file.name + ", " + item) log.info("Found: " + file.name + ", " + item)
targetFile = file.name targetFile = File(file.name).canonicalPath
targetHandler = p.value targetHandler = p.value
return@found return@found
} }
@ -140,43 +167,26 @@ fun main(args: Array<String>) {
log.warn("'${args[0]}' sequence initialized") log.warn("'${args[0]}' sequence initialized")
log.warn("XXXX: args.size: ${args.size}") log.warn("XXXX: args.size: ${args.size}")
val convertedArgs = args.copyOf().apply { set(0, targetFile!!) } val c = (if (args.size == 1) { //lazy mode
functions[0].call(it.createInstance(), *convertedArgs) args.drop(1).toMutableList().apply {
add(targetFile!!)
/* //add(System.getProperty("user.dir"))
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)
} }
} 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()) { println("Converted args:")
log.info("ret: $reflectRet") convertedArgs.forEachIndexed { index, s ->
println("$index: $s")
} }
*/ functions[0].call(it.createInstance(), *convertedArgs)
log.warn("'${args[0]}' sequence completed") log.warn("'${args[0]}' sequence completed")
} }
} }

@ -16,6 +16,7 @@ package cfig.packable
import avb.AVBInfo import avb.AVBInfo
import cfig.Avb import cfig.Avb
import cfig.bootimg.Common
import cfig.helper.Dumpling import cfig.helper.Dumpling
import cfig.helper.Helper import cfig.helper.Helper
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
@ -35,20 +36,43 @@ class VBMetaParser : IPackable {
return listOf("^vbmeta\\.img$", "^vbmeta\\_[a-z]+.img$") return listOf("^vbmeta\\.img$", "^vbmeta\\_[a-z]+.img$")
} }
// lazy mode
override fun unpack(fileName: String) { 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()) { if (!it.exists()) {
it.mkdirs() it.mkdirs()
} }
} }
//workspace.ini
log.info("workspace set to $unpackDir")
Common.createWorkspaceIni(fileName, prefix = "vbmeta")
val ai = AVBInfo.parseFrom(Dumpling(fileName)).dumpDefault(fileName) val ai = AVBInfo.parseFrom(Dumpling(fileName)).dumpDefault(fileName)
log.info("Signing Key: " + Avb.inspectKey(ai)) log.info("Signing Key: " + Avb.inspectKey(ai))
} }
override fun pack(fileName: String) { override fun pack(fileName: String) {
val blob = ObjectMapper().readValue(File(Avb.getJsonFileName(fileName)), AVBInfo::class.java).encodePadded() packInternal(outDir, fileName)
log.info("Writing padded vbmeta to file: $fileName.signed") }
Files.write(Paths.get("$fileName.signed"), blob, StandardOpenOption.CREATE)
// 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) { override fun flash(fileName: String, deviceName: String) {

@ -14,6 +14,7 @@
package cfig.packable package cfig.packable
import cfig.bootimg.Common
import cfig.bootimg.v3.VendorBoot import cfig.bootimg.v3.VendorBoot
import cfig.helper.Helper import cfig.helper.Helper
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
@ -30,19 +31,18 @@ class VendorBootParser : IPackable {
} }
override fun unpack(fileName: String) { override fun unpack(fileName: String) {
clear() log.info("unpack(fileName: $fileName)")
val vb = VendorBoot unpackInternal(fileName, Helper.prop("workDir")!!)
.parse(fileName)
.extractImages()
.extractVBMeta()
.printUnpackSummary()
log.debug(vb.toString())
} }
fun unpackInternal(targetFile: String, fileName: String, unpackDir: String) { fun unpackInternal(fileName: String, unpackDir: String) {
//set workdir
log.info("unpackInternal(fileName: $fileName, unpackDir: $unpackDir)") log.info("unpackInternal(fileName: $fileName, unpackDir: $unpackDir)")
Helper.setProp("workDir", unpackDir) Helper.setProp("workDir", unpackDir)
clear() clear()
//create workspace file
Common.createWorkspaceIni(fileName)
//create workspace file done
val vb = VendorBoot val vb = VendorBoot
.parse(fileName) .parse(fileName)
.extractImages() .extractImages()
@ -52,13 +52,32 @@ class VendorBootParser : IPackable {
} }
override fun pack(fileName: String) { 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") log.info("Loading config from $cfgFile")
ObjectMapper().readValue(File(cfgFile), VendorBoot::class.java) ObjectMapper().readValue(File(cfgFile), VendorBoot::class.java)
.pack() .pack()
.sign() .sign()
.postCopy(outFileName)
.updateVbmeta() .updateVbmeta()
.printPackSummary() .printPackSummary(outFileName)
} }
override fun `@verify`(fileName: String) { override fun `@verify`(fileName: String) {

@ -34,7 +34,7 @@ data class SparseImage(var info: SparseInfo = SparseInfo()) {
partName, partName,
workDir, workDir,
File(workDir, File(info.output).nameWithoutExtension).path, 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, partName,
workDir, workDir,
File(workDir, File(info.output).nameWithoutExtension).path, 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") { if (info.outerFsType == "sparse") {
img2simg(File(workDir, (File(info.output).name + ".signed")).path, File(info.output).name + ".signed") img2simg(File(workDir, (File(info.output).name + ".signed")).path, File(info.output).name + ".signed")
} else { } else {
val s = info.pulp + ".signed" val s = Helper.joinPath(Helper.prop("intermediateDir")!!, File(info.pulp + ".signed").name)
val t = info.output + ".signed" val t = info.output + ".signed"
log.info("Moving $s -> $t") log.info("Moving $s -> $t")
Files.move(Path(s), Path(t), java.nio.file.StandardCopyOption.REPLACE_EXISTING) 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() val ret = SparseImage()
ret.info.json = File(fileName).name.removeSuffix(".img") + ".json" ret.info.json = File(fileName).name.removeSuffix(".img") + ".json"
ret.info.output = fileName 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)) { if (isSparse(fileName)) {
val tempFile = UUID.randomUUID().toString() val tempFile = UUID.randomUUID().toString()
ret.info.outerFsType = "sparse" ret.info.outerFsType = "sparse"
val rawFile = "${workDir}${File(fileName).nameWithoutExtension}"
simg2img(fileName, tempFile) simg2img(fileName, tempFile)
ret.info.pulp = if (isExt4(tempFile)) { ret.info.pulp = if (isExt4(tempFile)) {
ret.info.innerFsType = "ext4" ret.info.innerFsType = "ext4"
"$rawFile.ext4" ret.info.pulp + ".ext4"
} else if (isErofs(tempFile)) { } else if (isErofs(tempFile)) {
ret.info.innerFsType = "erofs" ret.info.innerFsType = "erofs"
"$rawFile.erofs" ret.info.pulp + ".erofs"
} else { } else {
"$rawFile.raw" ret.info.pulp + ".raw"
} }
Files.move(Path(tempFile), Path(ret.info.pulp)) Files.move(Path(tempFile), Path(ret.info.pulp))
} else if (isExt4(fileName)) { } else if (isExt4(fileName)) {
ret.info.outerFsType = "ext4" ret.info.outerFsType = "ext4"
ret.info.innerFsType = "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)) File(fileName).copyTo(File(ret.info.pulp))
} else if (isErofs(fileName)) { } else if (isErofs(fileName)) {
ret.info.outerFsType = "erofs" ret.info.outerFsType = "erofs"
ret.info.innerFsType = "erofs" ret.info.innerFsType = "erofs"
ret.info.pulp = ret.info.pulp + ".erofs"
File(fileName).copyTo(File(ret.info.pulp)) File(fileName).copyTo(File(ret.info.pulp))
} }
when (ret.info.innerFsType) { when (ret.info.innerFsType) {
@ -258,7 +261,7 @@ data class SparseImage(var info: SparseInfo = SparseInfo()) {
DefaultExecutor().apply { DefaultExecutor().apply {
streamHandler = PumpStreamHandler(System.out, System.err) streamHandler = PumpStreamHandler(System.out, System.err)
}.execute(CommandLine.parse("aosp/plugged/bin/sefcontext_compile").apply { }.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") addArgument("aosp/plugged/res/file_contexts.concat")
}.also { log.warn(it.toString()) }, env) }.also { log.warn(it.toString()) }, env)
} }

@ -15,6 +15,7 @@
package rom.sparse package rom.sparse
import avb.blob.Footer import avb.blob.Footer
import cfig.bootimg.Common
import cfig.helper.Helper import cfig.helper.Helper
import cfig.packable.IPackable import cfig.packable.IPackable
import cfig.packable.VBMetaParser import cfig.packable.VBMetaParser
@ -38,15 +39,45 @@ class SparseImgParser : IPackable {
} }
override fun unpack(fileName: String) { 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() clear()
//create workspace file
Common.createWorkspaceIni(fileName)
//create workspace file done
SparseImage SparseImage
.parse(fileName) .parse(fileName)
.printSummary(fileName) .printSummary(fileName)
} }
override fun pack(fileName: String) { override fun pack(fileName: String) {
//TODO("not implemented: refer to https://github.com/cfig/Android_boot_image_editor/issues/133") //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) val readBackSi = ObjectMapper().readValue(File(cfgFile), SparseImage::class.java)
readBackSi readBackSi
.pack() .pack()

@ -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

@ -21,16 +21,16 @@ should be compatible with "/usr/bin/env sh"
## TODO: command line usage ## TODO: command line usage
unpack unpack
``` ```
abe unpack boot.img out be unpack file
be unpack file dir
``` ```
pack pack
``` ```
abe pack out boot.img be pack dir
``` ```
properties: "out.file": the final output file 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, 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: "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"'. if args are "pack dir file", the shell script will print 'gradle pack --args="packInternal dir file"'.

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists 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 zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

@ -15,7 +15,7 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins { plugins {
kotlin("jvm") version "2.0.0" kotlin("jvm") version "2.1.21"
`java-library` `java-library`
application application
} }
@ -58,11 +58,13 @@ java {
targetCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11
} }
tasks.withType<KotlinCompile>().all { tasks.withType<KotlinCompile>().configureEach {
kotlinOptions { compilerOptions {
freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn" freeCompilerArgs.addAll(
freeCompilerArgs += "-opt-in=kotlin.ExperimentalUnsignedTypes" "-opt-in=kotlin.RequiresOptIn",
jvmTarget = "11" "-opt-in=kotlin.ExperimentalUnsignedTypes"
)
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11)
} }
} }

@ -8,7 +8,7 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins { plugins {
kotlin("jvm") version "2.0.20" kotlin("jvm") version "2.1.21"
application application
} }
@ -30,6 +30,7 @@ dependencies {
// Use the JUnit 5 integration. // Use the JUnit 5 integration.
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.9.3") testImplementation("org.junit.jupiter:junit-jupiter-engine:5.9.3")
testRuntimeOnly("org.junit.platform:junit-platform-launcher") testRuntimeOnly("org.junit.platform:junit-platform-launcher")
implementation(kotlin("stdlib"))
} }
java { java {
@ -37,11 +38,13 @@ java {
targetCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11
} }
tasks.withType<KotlinCompile>().all { tasks.withType<KotlinCompile>().configureEach {
kotlinOptions { compilerOptions {
freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn" freeCompilerArgs.addAll(
freeCompilerArgs += "-opt-in=kotlin.ExperimentalUnsignedTypes" "-opt-in=kotlin.RequiresOptIn",
jvmTarget = "11" "-opt-in=kotlin.ExperimentalUnsignedTypes"
)
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11)
} }
} }

@ -70,7 +70,17 @@ fun main(args: Array<String>) {
if (args[0] == "apps") { if (args[0] == "apps") {
//AppList.retrieveList() //AppList.retrieveList()
} }
if (args[0] == "rel") {
ImageRelease.run()
}
if (args[0] == "x") { if (args[0] == "x") {
AMS.computeRankAndBucket(AMS.getProcRank(), AMS.getStandbyBucket2()) AMS.computeRankAndBucket(AMS.getProcRank(), AMS.getStandbyBucket2())
} }
if (args[0] == "mount") {
MountAnalyzer().run()
}
if (args[0] == "booting") {
//BootingParser.run()
BootingParser.run2()
}
} }

@ -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
}

@ -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)
}
}
}

@ -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<MountInfo> {
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<MountInfo>()
val dmApex = mutableListOf<MountInfo>()
val tmpApex = mutableListOf<MountInfo>()
val bootApex = mutableListOf<MountInfo>()
val fuseInfo = mutableListOf<MountInfo>()
val sysInfo = mutableListOf<MountInfo>()
val androidRo = mutableListOf<MountInfo>()
val androidRw = mutableListOf<MountInfo>()
val otherRw = mutableListOf<MountInfo>()
val unknownMi = mutableListOf<MountInfo>()
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)
}
}

@ -1,3 +1,8 @@
pluginManagement {
plugins {
kotlin("jvm") version "2.1.21"
}
}
rootProject.name = "boot" rootProject.name = "boot"
include("bbootimg") include("bbootimg")
include("aosp:apksigner") include("aosp:apksigner")

@ -1 +1 @@
Subproject commit 9b783be45a23379d024157a6740fb06ba535e4d4 Subproject commit 012bda615f1c02f16064c30b828f2307f08a6730

@ -1 +1 @@
Subproject commit 15c3878486abe3f2e42e6e0f4f0d82682546afaf Subproject commit b1bff460f66fb916e30a0f7ca0b93bc922412b53

@ -73,6 +73,12 @@ unknown_list = [
"super_empty_all.img", #ADT-3 "super_empty_all.img", #ADT-3
"bootloader.img", #many "bootloader.img", #many
"dt.img", "dt.img",
"fastboot.img", #anon
"fastlogo.img", #anon
"bl.img", #anon
"tzl.img", #anon
"preboot.img", #anon
"tzk.img", #anon
] ]
tmp2 = "tmp2" tmp2 = "tmp2"

@ -2,10 +2,12 @@
baseDir=${0:a:h} baseDir=${0:a:h}
export baseDir export baseDir
be_caller_dir=${PWD}
export be_caller_dir
set -e set -e
# Parse command line arguments # Parse command line arguments
if [[ $# -eq 0 ]]; then if [[ $# -lt 2 ]]; then
echo "Usage: $0 <operation> [<file> <dir>]" echo "Usage: $0 <operation> [<file> <dir>]"
exit 1 exit 1
fi fi
@ -13,34 +15,48 @@ fi
operation=$1 operation=$1
echo arg num: $# echo arg num: $#
echo "args: $0 $1 $2" 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 # Determine which operation to perform
set -x
case $operation in case $operation in
"unpack") "unpack")
if [[ $# -eq 2 ]]; then if [[ $# -eq 2 ]]; then
file=`realpath $2` file=`realpath $2`
dir=`realpath out` dir=`realpath out`
cd ${baseDir}/../../../ cd ${source_code_dir}
pwd
gradle unpack --args="unpackInternal $file $dir" gradle unpack --args="unpackInternal $file $dir"
elif [[ $# -eq 3 ]]; then elif [[ $# -eq 3 ]]; then
file=`realpath $2` file=`realpath $2`
dir=`realpath $3` dir=`realpath $3`
cd ${baseDir}/../../../ cd ${source_code_dir}
gradle unpack --args="unpackInternal $file $dir" gradle unpack --args="unpackInternal $file $dir"
else else
echo "Invalid args"
cd ${baseDir}/../../../ cd ${baseDir}/../../../
gradle unpack gradle unpack
fi fi
;; ;;
"pack") "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` dir=`realpath $2`
file=`realpath $3` file=`realpath $3`
cd ${baseDir}/../../../ cd ${source_code_dir}
gradle pack --args="packInternal $dir $file" gradle pack --args="packInternal $dir $file"
else else
cd ${baseDir}/../../../ ##cd ${baseDir}/../../../
gradle pack #cd ${source_code_dir}
#gradle pack
echo "Invalid args"
fi fi
;; ;;
*) *)
Loading…
Cancel
Save