Use dmg2img to parse DMG file

This commit is contained in:
Davide Depau 2018-08-17 00:27:03 +02:00
parent 34d8f52e1d
commit 8cba765dff
Signed by: depau
GPG key ID: C7D999B6A55EFE86
12 changed files with 278 additions and 7 deletions

View file

@ -7,6 +7,7 @@ import eu.depau.etchdroid.fragments.WizardFragment
import eu.depau.etchdroid.enums.FlashMethod
import eu.depau.etchdroid.enums.ImageLocation
import eu.depau.etchdroid.enums.WizardStep
import eu.depau.etchdroid.img_types.Image
object StateKeeper {
var wizardStep: WizardStep = WizardStep.SELECT_FLASH_METHOD
@ -15,6 +16,7 @@ object StateKeeper {
var imageLocation: ImageLocation? = null
var streamingWrite: Boolean = false
var imageFile: Uri? = null
var imageRepr: Image? = null
var usbDevice: UsbDevice? = null
var usbMassStorageDevice: UsbMassStorageDevice? = null

View file

@ -12,7 +12,7 @@ abstract class WizardActivity : AppCompatActivity(), SimpleFilePickerDialog.Inte
override fun onResult(dialogTag: String, which: Int, extras: Bundle): Boolean {
if (StateKeeper.currentFragment is SimpleFilePickerDialog.InteractionListenerString)
return (StateKeeper.currentFragment as SimpleFilePickerDialog.InteractionListenerString).onResult(dialogTag, which, extras)
throw RuntimeException("Wrong fragment type")
throw RuntimeException("Wrong fragment fsType")
}
override fun showListItemDialog(title: String?, folderPath: String?, mode: SimpleFilePickerDialog.CompositeMode?, dialogTag: String?) {

View file

@ -0,0 +1,39 @@
package eu.depau.etchdroid.enums
enum class FilesystemType {
// Microsoft
FAT12,
FAT16,
FAT32,
EXFAT,
NTFS,
REFS,
// Apple
HFS,
HFSPLUS,
APFS,
APT_DATA, // Apple Partition Table stuff
// ISO 9660
ISO9660,
// Linux
EXT2,
EXT3,
EXT4,
BTRFS,
F2FS,
LUKS,
LINUX_SWAP,
LINUX_LVM_PV,
// BSD
UFS,
XFS,
ZFS,
FREE,
UNFORMATTED,
UNKNOWN
}

View file

@ -0,0 +1,14 @@
package eu.depau.etchdroid.enums
enum class PartitionTableType {
AIX,
AMIGA,
BSD,
DVH,
GPT,
LOOP,
MAC,
MSDOS,
PC98,
SUN
}

View file

@ -0,0 +1,3 @@
package eu.depau.etchdroid.enums
enum class PartitionType {}

View file

@ -22,6 +22,7 @@ import eu.depau.etchdroid.kotlin_exts.snackbar
import eu.depau.etchdroid.enums.FlashMethod
import eu.depau.etchdroid.enums.ImageLocation
import eu.depau.etchdroid.enums.WizardStep
import eu.depau.etchdroid.img_types.DMGImage
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.fragment_select_location.*
import java.io.File
@ -80,7 +81,7 @@ class ImageLocationFragment : WizardFragment(), SimpleFilePickerDialog.Interacti
img_url_textview?.isEnabled = StateKeeper.imageLocation == ImageLocation.REMOTE
setStreamingCheckBoxAvailability(activity as WizardActivity)
updateFileButtonLabel(activity as WizardActivity)
loadImageChanges(activity as WizardActivity)
}
override fun onButtonClicked(view: View) {
@ -200,16 +201,21 @@ class ImageLocationFragment : WizardFragment(), SimpleFilePickerDialog.Interacti
return Uri.parse(text)
}
fun updateFileButtonLabel(context: WizardActivity) {
fun loadImageChanges(context: WizardActivity) {
val button = pick_file_btn
val uri = StateKeeper.imageFile
val uri = StateKeeper.imageFile ?: return
val text = uri?.getFileName(context)
val text = uri.getFileName(context)
if (text != null)
button.text = text
else
button.text = getString(R.string.pick_a_file)
if (StateKeeper.flashMethod == FlashMethod.FLASH_DMG_API) {
StateKeeper.imageRepr = DMGImage(uri, context)
Log.d(TAG, (StateKeeper.imageRepr as DMGImage).partitionTable.toString())
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
@ -223,7 +229,7 @@ class ImageLocationFragment : WizardFragment(), SimpleFilePickerDialog.Interacti
uri = data.getData()
Log.d(TAG, "Uri: " + uri!!.toString())
StateKeeper.imageFile = uri
updateFileButtonLabel(activity as WizardActivity)
loadImageChanges(activity as WizardActivity)
}
}
}
@ -235,7 +241,7 @@ class ImageLocationFragment : WizardFragment(), SimpleFilePickerDialog.Interacti
if (extras.containsKey(SimpleFilePickerDialog.SELECTED_SINGLE_PATH)) {
val path = extras.getString(SimpleFilePickerDialog.SELECTED_SINGLE_PATH)
StateKeeper.imageFile = Uri.fromFile(File(path))
updateFileButtonLabel(activity as WizardActivity)
loadImageChanges(activity as WizardActivity)
}
}
}

View file

@ -0,0 +1,93 @@
package eu.depau.etchdroid.img_types
import android.content.Context
import android.net.Uri
import eu.depau.etchdroid.enums.FilesystemType
import eu.depau.etchdroid.enums.PartitionTableType
import eu.depau.etchdroid.kotlin_exts.getBinary
import eu.depau.etchdroid.kotlin_exts.readString
import eu.depau.etchdroid.utils.Partition
import eu.depau.etchdroid.utils.PartitionBuilder
import java.io.File
private val partRegex = Regex("partition (\\d+): (.*) \\((.+) : \\d+\\)")
private fun readPartitionTable(dmg2img: File, libDir: String, file: File): Pair<PartitionTableType?, List<Partition>?> {
val pt = ArrayList<Partition>()
var ptType: PartitionTableType? = null
val pb = ProcessBuilder(dmg2img.path, "-l", file.path)
pb.environment()["LD_LIBRARY_PATH"] = libDir
pb.redirectErrorStream(true)
val p = pb.start()
val out = p.inputStream.readString()
p.waitFor()
val matches = partRegex.findAll(out)
matchloop@ for (m in matches) {
val (number, label, type) = m.destructured
val pb = PartitionBuilder()
pb.number = number.toInt()
if (label.isNotEmpty())
pb.fsLabel = label
pb.partLabel = type
when (type) {
"Apple_partition_map" -> {
ptType = PartitionTableType.MAC
}
"MBR" -> {
ptType = PartitionTableType.MSDOS
continue@matchloop
}
}
pb.fsType = when {
type == "Apple_Empty" || type == "Apple_Scratch" || type == "Apple_Free" -> FilesystemType.FREE
type == "Apple_HFS" -> FilesystemType.HFSPLUS
type == "Apple_HFSX" -> FilesystemType.HFSPLUS
type == "Windows_FAT_32" -> FilesystemType.FAT32
type.startsWith("Apple_") || type == "DDM" -> FilesystemType.APT_DATA
else -> FilesystemType.UNKNOWN
}
pt.add(pb.build())
}
return Pair(ptType, pt)
}
class DMGImage(private val uri: Uri, private val context: Context) : Image {
private val dmg2img: File = context.getBinary("dmg2img")
private val libDir: String = context.applicationInfo.nativeLibraryDir
private var loaded: Boolean = false
private var partTable: List<Partition>? = null
private var partTableType: PartitionTableType? = null
private fun readInfo() {
if (loaded)
return
val pair = readPartitionTable(dmg2img, libDir, File(uri.path))
loaded = true
partTableType = pair.first
partTable = pair.second
}
override val partitionTable: List<Partition>?
get() {
readInfo()
return partTable
}
override val tableType: PartitionTableType?
get() {
readInfo()
return partTableType
}
}

View file

@ -0,0 +1,9 @@
package eu.depau.etchdroid.img_types
import eu.depau.etchdroid.enums.PartitionTableType
import eu.depau.etchdroid.utils.Partition
interface Image {
val partitionTable: List<Partition>?
val tableType: PartitionTableType?
}

View file

@ -0,0 +1,57 @@
package eu.depau.etchdroid.kotlin_exts
import android.content.Context
import android.content.Context.MODE_PRIVATE
import android.os.Build
import java.io.File
import java.io.FileOutputStream
private val copiedBinaries: MutableMap<String, File> = HashMap()
fun Context.getBinary(name: String): File {
if (name in copiedBinaries.keys)
return copiedBinaries[name]!!
val abi = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
Build.SUPPORTED_ABIS[0]
else
Build.CPU_ABI
val arch = when {
abi.contains("armeabi-v7a") -> "armeabi-v7a"
abi.contains("x86_64") -> "x86_64"
abi.contains("x86") -> "x86"
abi.contains("arm64-v8a") -> "arm64-v8a"
else -> null!!
}
val assetManager = assets
val assetIn = assetManager.open("bin/$arch/$name")
val bin = File("$filesDir/bin/$arch/$name")
bin.parentFile?.mkdirs()
if (!bin.exists())
bin.createNewFile()
val binOut = FileOutputStream(bin)
// Copy executable
var size: Long = 0
val buff = ByteArray(1024)
var nRead = assetIn.read(buff)
while (nRead != -1) {
binOut.write(buff, 0, nRead)
size += nRead.toLong()
nRead = assetIn.read(buff)
}
assetIn.close()
binOut.close()
bin.setExecutable(true)
copiedBinaries[name] = bin
return bin
}

View file

@ -0,0 +1,18 @@
package eu.depau.etchdroid.kotlin_exts
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.InputStream
@Throws(IOException::class)
fun InputStream.readString(): String {
val baos = ByteArrayOutputStream()
val buffer = ByteArray(1024)
var length = this.read(buffer)
while (length != -1) {
baos.write(buffer, 0, length)
length = this.read(buffer)
}
return baos.toString("UTF-8")
}

View file

@ -0,0 +1,14 @@
package eu.depau.etchdroid.utils
import eu.depau.etchdroid.enums.FilesystemType
import eu.depau.etchdroid.enums.PartitionType
data class Partition(
val number: Int?,
val offset: Int?,
val size: Int?,
val partType: PartitionType?,
val partLabel: String?,
val fsType: FilesystemType?,
val fsLabel: String?
)

View file

@ -0,0 +1,16 @@
package eu.depau.etchdroid.utils
import eu.depau.etchdroid.enums.FilesystemType
import eu.depau.etchdroid.enums.PartitionType
class PartitionBuilder {
var number: Int? = null
var offset: Int? = null
var size: Int? = null
var partType: PartitionType? = null
var partLabel: String? = null
var fsType: FilesystemType? = null
var fsLabel: String? = null
fun build(): Partition = Partition(number, offset, size, partType, partLabel, fsType, fsLabel)
}