Migrate to Apache commons-compress

Close #8121
This commit is contained in:
topjohnwu 2024-06-29 22:10:17 -07:00
parent 52063b3652
commit cacc60b1ac
3 changed files with 156 additions and 110 deletions

View File

@ -70,15 +70,14 @@ configurations.all {
dependencies { dependencies {
implementation(project(":app:shared")) implementation(project(":app:shared"))
implementation("com.github.topjohnwu:jtar:1.1.0")
implementation("com.github.topjohnwu:indeterminate-checkbox:1.0.7") implementation("com.github.topjohnwu:indeterminate-checkbox:1.0.7")
implementation("com.github.topjohnwu:lz4-java:1.7.1")
implementation("com.jakewharton.timber:timber:5.0.1") implementation("com.jakewharton.timber:timber:5.0.1")
implementation("org.bouncycastle:bcpkix-jdk18on:1.78.1") implementation("org.bouncycastle:bcpkix-jdk18on:1.78.1")
implementation("dev.rikka.rikkax.layoutinflater:layoutinflater:1.3.0") implementation("dev.rikka.rikkax.layoutinflater:layoutinflater:1.3.0")
implementation("dev.rikka.rikkax.insets:insets:1.3.0") implementation("dev.rikka.rikkax.insets:insets:1.3.0")
implementation("dev.rikka.rikkax.recyclerview:recyclerview-ktx:1.3.2") implementation("dev.rikka.rikkax.recyclerview:recyclerview-ktx:1.3.2")
implementation("io.noties.markwon:core:4.6.2") implementation("io.noties.markwon:core:4.6.2")
implementation("org.apache.commons:commons-compress:1.26.2")
val vLibsu = "6.0.0" val vLibsu = "6.0.0"
implementation("com.github.topjohnwu.libsu:core:${vLibsu}") implementation("com.github.topjohnwu.libsu:core:${vLibsu}")

View File

@ -35,37 +35,53 @@ import com.topjohnwu.superuser.nio.ExtendedFile
import com.topjohnwu.superuser.nio.FileSystemManager import com.topjohnwu.superuser.nio.FileSystemManager
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import net.jpountz.lz4.LZ4FrameInputStream import org.apache.commons.compress.archivers.tar.TarArchiveEntry
import org.kamranzafar.jtar.TarEntry import org.apache.commons.compress.archivers.tar.TarArchiveInputStream
import org.kamranzafar.jtar.TarHeader import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream
import org.kamranzafar.jtar.TarInputStream import org.apache.commons.compress.archivers.zip.ZipArchiveEntry
import org.kamranzafar.jtar.TarOutputStream import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream
import org.apache.commons.compress.archivers.zip.ZipFile
import org.apache.commons.compress.compressors.lz4.FramedLZ4CompressorInputStream
import timber.log.Timber import timber.log.Timber
import java.io.* import java.io.File
import java.io.FilterInputStream
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.io.PushbackInputStream
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.security.SecureRandom import java.security.SecureRandom
import java.util.* import java.util.Arrays
import java.util.Locale
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipInputStream
abstract class MagiskInstallImpl protected constructor( abstract class MagiskInstallImpl protected constructor(
protected val console: MutableList<String>, protected val console: MutableList<String>,
private val logs: MutableList<String> private val logs: MutableList<String>
) { ) {
protected lateinit var installDir: ExtendedFile private lateinit var installDir: ExtendedFile
private lateinit var srcBoot: ExtendedFile private lateinit var srcBoot: ExtendedFile
private val shell = Shell.getShell() private val shell = Shell.getShell()
private val service get() = ServiceLocator.networkService
protected val context get() = ServiceLocator.deContext
private val useRootDir = shell.isRoot && Info.noDataExec private val useRootDir = shell.isRoot && Info.noDataExec
protected val context get() = ServiceLocator.deContext
private val rootFS get() = RootUtils.fs private val rootFS get() = RootUtils.fs
private val localFS get() = FileSystemManager.getLocal() private val localFS get() = FileSystemManager.getLocal()
private val destName: String by lazy {
val alpha = "abcdefghijklmnopqrstuvwxyz"
val alphaNum = "$alpha${alpha.uppercase(Locale.ROOT)}0123456789"
val random = SecureRandom()
StringBuilder("magisk_patched-${BuildConfig.VERSION_CODE}_").run {
for (i in 1..5) {
append(alphaNum[random.nextInt(alphaNum.length)])
}
toString()
}
}
private fun findImage(): Boolean { private fun findImage(): Boolean {
val bootPath = "RECOVERYMODE=${Config.recovery} find_boot_image; echo \"\$BOOTIMAGE\"".fsh() val bootPath = "RECOVERYMODE=${Config.recovery} find_boot_image; echo \"\$BOOTIMAGE\"".fsh()
if (bootPath.isEmpty()) { if (bootPath.isEmpty()) {
@ -106,8 +122,8 @@ abstract class MagiskInstallImpl protected constructor(
try { try {
// Extract binaries // Extract binaries
if (isRunningAsStub) { if (isRunningAsStub) {
ZipFile(StubApk.current(context)).use { zf -> ZipFile.builder().setFile(StubApk.current(context)).get().use { zf ->
zf.entries().asSequence().filter { zf.entries.asSequence().filter {
!it.isDirectory && it.name.startsWith("/lib/${Const.CPU_ABI}/") !it.isDirectory && it.name.startsWith("/lib/${Const.CPU_ABI}/")
}.forEach { }.forEach {
val n = it.name.substring(it.name.lastIndexOf('/') + 1) val n = it.name.substring(it.name.lastIndexOf('/') + 1)
@ -120,13 +136,14 @@ abstract class MagiskInstallImpl protected constructor(
val abi32 = Const.CPU_ABI_32 val abi32 = Const.CPU_ABI_32
if (Process.is64Bit() && abi32 != null) { if (Process.is64Bit() && abi32 != null) {
val magisk32 = File(installDir, "magisk32") val magisk32 = File(installDir, "magisk32")
zf.getInputStream(ZipEntry("lib/$abi32/libmagisk.so")).writeTo(magisk32) val entry = zf.getEntry("lib/$abi32/libmagisk.so")
zf.getInputStream(entry).writeTo(magisk32)
magisk32.setExecutable(true) magisk32.setExecutable(true)
} }
} }
} else { } else {
val info = context.applicationInfo val info = context.applicationInfo
var libs = File(info.nativeLibraryDir).listFiles { _, name -> val libs = File(info.nativeLibraryDir).listFiles { _, name ->
name.startsWith("lib") && name.endsWith(".so") name.startsWith("lib") && name.endsWith(".so")
} ?: emptyArray() } ?: emptyArray()
@ -179,78 +196,107 @@ abstract class MagiskInstallImpl protected constructor(
private suspend fun InputStream.copyAndCloseOut(out: OutputStream) = out.use { copyAll(it) } private suspend fun InputStream.copyAndCloseOut(out: OutputStream) = out.use { copyAll(it) }
private fun newTarEntry(name: String, size: Long): TarEntry {
console.add("-- Writing: $name")
return TarEntry(TarHeader.createHeader(name, size, 0, false, 420 /* 0644 */))
}
private class NoAvailableStream(s: InputStream) : FilterInputStream(s) { private class NoAvailableStream(s: InputStream) : FilterInputStream(s) {
// Make sure available is never called on the actual stream and always return 0 // Make sure available is never called on the actual stream and always return 0
// 1. Workaround bug in LZ4FrameInputStream // to reduce max buffer size and avoid OOM
// 2. Reduce max buffer size to prevent OOM
override fun available() = 0 override fun available() = 0
} }
private class NoBootException : IOException() private class NoBootException : IOException()
inner class BootItem(private val entry: TarArchiveEntry) {
val name = entry.name.replace(".lz4", "")
var file = installDir.getChildFile(name)
suspend fun copyTo(tarOut: TarArchiveOutputStream) {
entry.name = name
entry.size = file.length()
file.newInputStream().use {
console.add("-- Writing : $name")
tarOut.putArchiveEntry(entry)
it.copyAll(tarOut)
tarOut.closeArchiveEntry()
}
}
}
@Throws(IOException::class) @Throws(IOException::class)
private suspend fun processTar(tarIn: TarInputStream, tarOut: TarOutputStream): ExtendedFile { private suspend fun processTar(
tarIn: TarArchiveInputStream,
tarOut: TarArchiveOutputStream
): BootItem {
console.add("- Processing tar file") console.add("- Processing tar file")
lateinit var entry: TarEntry lateinit var entry: TarArchiveEntry
fun decompressedStream(): InputStream { fun decompressedStream(): InputStream {
val stream = if (entry.name.endsWith(".lz4")) LZ4FrameInputStream(tarIn) else tarIn val stream = if (entry.name.endsWith(".lz4"))
FramedLZ4CompressorInputStream(tarIn, true) else tarIn
return NoAvailableStream(stream) return NoAvailableStream(stream)
} }
while (tarIn.nextEntry?.let { entry = it } != null) { var boot: BootItem? = null
if (entry.name.startsWith("boot.img") || var initBoot: BootItem? = null
entry.name.startsWith("init_boot.img") || var recovery: BootItem? = null
(Config.recovery && entry.name.contains("recovery.img"))) {
val name = entry.name.replace(".lz4", "")
console.add("-- Extracting: $name")
val extract = installDir.getChildFile(name) while (true) {
decompressedStream().copyAndCloseOut(extract.newOutputStream()) entry = tarIn.nextEntry ?: break
val bootItem: BootItem?
if (entry.name.startsWith("boot.img")) {
bootItem = BootItem(entry)
boot = bootItem
} else if (entry.name.startsWith("init_boot.img")) {
bootItem = BootItem(entry)
initBoot = bootItem
} else if (Config.recovery && entry.name.contains("recovery.img")) {
bootItem = BootItem(entry)
recovery = bootItem
} else {
bootItem = null
}
if (bootItem != null) {
console.add("-- Extracting: ${bootItem.name}")
decompressedStream().copyAndCloseOut(bootItem.file.newOutputStream())
} else if (entry.name.contains("vbmeta.img")) { } else if (entry.name.contains("vbmeta.img")) {
val rawData = decompressedStream().readBytes() val rawData = decompressedStream().readBytes()
// Valid vbmeta.img should be at least 256 bytes // Valid vbmeta.img should be at least 256 bytes
if (rawData.size < 256) if (rawData.size < 256)
continue continue
// Patch flags to AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED |
// AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED
console.add("-- Patching: vbmeta.img")
ByteBuffer.wrap(rawData).putInt(120, 3)
tarOut.putNextEntry(newTarEntry("vbmeta.img", rawData.size.toLong()))
tarOut.write(rawData)
// vbmeta partition exist, disable boot vbmeta patch // vbmeta partition exist, disable boot vbmeta patch
Info.patchBootVbmeta = false Info.patchBootVbmeta = false
val name = entry.name.replace(".lz4", "")
console.add("-- Patching : $name")
// Patch flags to AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED |
// AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED
ByteBuffer.wrap(rawData).putInt(120, 3)
// Update entry with new information
entry.name = name
entry.size = rawData.size.toLong()
// Write output
tarOut.putArchiveEntry(entry)
tarOut.write(rawData)
tarOut.closeArchiveEntry()
} else if (entry.name.contains("userdata.img")) { } else if (entry.name.contains("userdata.img")) {
console.add("-- Skipping : ${entry.name}")
continue continue
} else { } else {
console.add("-- Copying : ${entry.name}") console.add("-- Copying : ${entry.name}")
tarOut.putNextEntry(entry) tarOut.putArchiveEntry(entry)
tarIn.copyAll(tarOut, bufferSize = 1024 * 1024) tarIn.copyAll(tarOut, bufferSize = 1024 * 1024)
tarOut.closeArchiveEntry()
} }
} }
val boot = installDir.getChildFile("boot.img")
val initBoot = installDir.getChildFile("init_boot.img")
val recovery = installDir.getChildFile("recovery.img")
suspend fun ExtendedFile.copyToTar() {
newInputStream().use {
tarOut.putNextEntry(newTarEntry(name, length()))
it.copyAll(tarOut)
}
delete()
}
// Patch priority: recovery > init_boot > boot // Patch priority: recovery > init_boot > boot
return when { return when {
recovery.exists() -> { recovery != null -> {
if (boot.exists()) { if (boot != null) {
// Repack boot image to prevent auto restore // Repack boot image to prevent auto restore
arrayOf( arrayOf(
"cd $installDir", "cd $installDir",
@ -261,27 +307,27 @@ abstract class MagiskInstallImpl protected constructor(
"./magiskboot cleanup", "./magiskboot cleanup",
"rm -f new-boot.img", "rm -f new-boot.img",
"cd /").sh() "cd /").sh()
boot.copyToTar() boot.copyTo(tarOut)
} }
recovery recovery
} }
initBoot.exists() -> { initBoot != null -> {
if (boot.exists()) boot?.copyTo(tarOut)
boot.copyToTar()
initBoot initBoot
} }
boot.exists() -> boot boot != null -> boot
else -> throw NoBootException() else -> throw NoBootException()
} }
} }
@Throws(IOException::class) @Throws(IOException::class)
private suspend fun processZip(zipIn: ZipInputStream): ExtendedFile { private suspend fun processZip(zipIn: ZipArchiveInputStream): ExtendedFile {
console.add("- Processing zip file") console.add("- Processing zip file")
val boot = installDir.getChildFile("boot.img") val boot = installDir.getChildFile("boot.img")
val initBoot = installDir.getChildFile("init_boot.img") val initBoot = installDir.getChildFile("init_boot.img")
lateinit var entry: ZipEntry var entry: ZipArchiveEntry
while (zipIn.nextEntry?.also { entry = it } != null) { while (true) {
entry = zipIn.nextEntry ?: break
if (entry.isDirectory) continue if (entry.isDirectory) continue
when (entry.name.substringAfterLast('/')) { when (entry.name.substringAfterLast('/')) {
"payload.bin" -> { "payload.bin" -> {
@ -376,9 +422,10 @@ abstract class MagiskInstallImpl protected constructor(
} }
} }
private suspend fun handleFile(uri: Uri): Boolean { private suspend fun processFile(uri: Uri): Boolean {
val outStream: OutputStream val outStream: OutputStream
val outFile: MediaStoreUtils.UriFile val outFile: MediaStoreUtils.UriFile
var bootItem: BootItem? = null
// Process input file // Process input file
try { try {
@ -393,23 +440,17 @@ abstract class MagiskInstallImpl protected constructor(
val magic = head.copyOf(4) val magic = head.copyOf(4)
val tarMagic = Arrays.copyOfRange(head, 257, 262) val tarMagic = Arrays.copyOfRange(head, 257, 262)
val alpha = "abcdefghijklmnopqrstuvwxyz"
val alphaNum = "$alpha${alpha.uppercase(Locale.ROOT)}0123456789"
val random = SecureRandom()
val filename = StringBuilder("magisk_patched-${BuildConfig.VERSION_CODE}_").run {
for (i in 1..5) {
append(alphaNum[random.nextInt(alphaNum.length)])
}
toString()
}
srcBoot = if (tarMagic.contentEquals("ustar".toByteArray())) { srcBoot = if (tarMagic.contentEquals("ustar".toByteArray())) {
// tar file // tar file
outFile = MediaStoreUtils.getFile("$filename.tar", true) outFile = MediaStoreUtils.getFile("$destName.tar", true)
outStream = TarOutputStream(outFile.uri.outputStream()) outStream = TarArchiveOutputStream(outFile.uri.outputStream()).also {
it.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_STAR)
it.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU)
}
try { try {
processTar(TarInputStream(src), outStream) bootItem = processTar(TarArchiveInputStream(src), outStream)
bootItem.file
} catch (e: IOException) { } catch (e: IOException) {
outStream.close() outStream.close()
outFile.delete() outFile.delete()
@ -417,14 +458,14 @@ abstract class MagiskInstallImpl protected constructor(
} }
} else { } else {
// raw image // raw image
outFile = MediaStoreUtils.getFile("$filename.img", true) outFile = MediaStoreUtils.getFile("$destName.img", true)
outStream = outFile.uri.outputStream() outStream = outFile.uri.outputStream()
try { try {
if (magic.contentEquals("CrAU".toByteArray())) { if (magic.contentEquals("CrAU".toByteArray())) {
processPayload(src) processPayload(src)
} else if (magic.contentEquals("PK\u0003\u0004".toByteArray())) { } else if (magic.contentEquals("PK\u0003\u0004".toByteArray())) {
processZip(ZipInputStream(src)) processZip(ZipArchiveInputStream(src))
} else { } else {
console.add("- Copying image to cache") console.add("- Copying image to cache")
installDir.getChildFile("boot.img").also { installDir.getChildFile("boot.img").also {
@ -455,17 +496,12 @@ abstract class MagiskInstallImpl protected constructor(
// Output file // Output file
try { try {
val newBoot = installDir.getChildFile("new-boot.img") val newBoot = installDir.getChildFile("new-boot.img")
if (outStream is TarOutputStream) { if (bootItem != null) {
val name = with(srcBoot.path) { bootItem.file = newBoot
when { bootItem.copyTo(outStream as TarArchiveOutputStream)
contains("recovery") -> "recovery.img" } else {
contains("init_boot") -> "init_boot.img"
else -> "boot.img"
}
}
outStream.putNextEntry(newTarEntry(name, newBoot.length()))
}
newBoot.newInputStream().copyAndClose(outStream) newBoot.newInputStream().copyAndClose(outStream)
}
newBoot.delete() newBoot.delete()
console.add("") console.add("")
@ -536,7 +572,7 @@ abstract class MagiskInstallImpl protected constructor(
private fun String.fsh() = ShellUtils.fastCmd(shell, this) private fun String.fsh() = ShellUtils.fastCmd(shell, this)
private fun Array<String>.fsh() = ShellUtils.fastCmd(shell, *this) private fun Array<String>.fsh() = ShellUtils.fastCmd(shell, *this)
protected suspend fun patchFile(file: Uri) = extractFiles() && handleFile(file) protected suspend fun patchFile(file: Uri) = extractFiles() && processFile(file)
protected suspend fun direct() = findImage() && extractFiles() && patchBoot() && flashBoot() protected suspend fun direct() = findImage() && extractFiles() && patchBoot() && flashBoot()
@ -553,9 +589,14 @@ abstract class MagiskInstallImpl protected constructor(
open suspend fun exec(): Boolean { open suspend fun exec(): Boolean {
if (haveActiveSession.getAndSet(true)) if (haveActiveSession.getAndSet(true))
return false return false
val result = withContext(Dispatchers.IO) { operations() } val result = withContext(Dispatchers.IO) { operations() }
haveActiveSession.set(false) haveActiveSession.set(false)
return result if (result)
return true
Shell.cmd("rm -rf $installDir").submit()
return false
} }
companion object { companion object {
@ -573,7 +614,6 @@ abstract class MagiskInstaller(
if (success) { if (success) {
console.add("- All done!") console.add("- All done!")
} else { } else {
Shell.cmd("rm -rf $installDir").submit()
console.add("! Installation failed") console.add("! Installation failed")
} }
return success return success

View File

@ -1,24 +1,36 @@
package com.topjohnwu.magisk.core.utils package com.topjohnwu.magisk.core.utils
import com.topjohnwu.magisk.core.ktx.copyAll import com.topjohnwu.magisk.core.ktx.copyAll
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream
import org.apache.commons.compress.archivers.zip.ZipFile
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
@Throws(IOException::class) @Throws(IOException::class)
suspend fun File.unzip(folder: File, path: String = "", junkPath: Boolean = false) { suspend fun File.unzip(folder: File, path: String = "", junkPath: Boolean = false) {
inputStream().buffered().use { ZipFile.Builder().setFile(this).get().use { zip ->
it.unzip(folder, path, junkPath) for (entry in zip.entries) {
if (!entry.name.startsWith(path) || entry.isDirectory) {
// Ignore directories, only create files
continue
}
val name = if (junkPath)
entry.name.substring(entry.name.lastIndexOf('/') + 1)
else
entry.name
val dest = File(folder, name)
dest.parentFile?.mkdirs()
dest.outputStream().use { out -> zip.getInputStream(entry).copyAll(out) }
}
} }
} }
@Throws(IOException::class) @Throws(IOException::class)
suspend fun InputStream.unzip(folder: File, path: String, junkPath: Boolean) { suspend fun InputStream.unzip(folder: File, path: String = "", junkPath: Boolean = false) {
try { ZipArchiveInputStream(this).use { zin ->
val zin = ZipInputStream(this) var entry: ZipArchiveEntry
var entry: ZipEntry
while (true) { while (true) {
entry = zin.nextEntry ?: break entry = zin.nextEntry ?: break
if (!entry.name.startsWith(path) || entry.isDirectory) { if (!entry.name.startsWith(path) || entry.isDirectory) {
@ -31,13 +43,8 @@ suspend fun InputStream.unzip(folder: File, path: String, junkPath: Boolean) {
entry.name entry.name
val dest = File(folder, name) val dest = File(folder, name)
dest.parentFile!!.let { dest.parentFile?.mkdirs()
if (!it.exists())
it.mkdirs()
}
dest.outputStream().use { out -> zin.copyAll(out) } dest.outputStream().use { out -> zin.copyAll(out) }
} }
} catch (e: IllegalArgumentException) {
throw IOException(e)
} }
} }