Migrate from NoisySu to RootSession

Fix #24. Note that just like before, IpMonitor doesn't use RootSession.
This commit is contained in:
Mygod
2018-09-06 15:36:34 +08:00
parent aa624708bb
commit 823ae9633b
12 changed files with 273 additions and 205 deletions

View File

@@ -0,0 +1,107 @@
package be.mygod.vpnhotspot.util
import android.util.Log
import com.crashlytics.android.Crashlytics
import com.topjohnwu.superuser.Shell
import java.util.*
import java.util.concurrent.locks.ReentrantLock
import kotlin.collections.ArrayList
import kotlin.concurrent.withLock
class RootSession {
companion object {
const val TAG = "RootSession"
const val INIT_CHECKPOINT = "$TAG initialized successfully"
private val monitor = ReentrantLock()
private var instance: RootSession? = null
private fun ensureInstance(): RootSession {
var instance = instance
if (instance == null) instance = RootSession().also { RootSession.instance = it }
return instance
}
fun <T> use(operation: (RootSession) -> T) = monitor.withLock { operation(ensureInstance()) }
fun beginTransaction(): Transaction {
monitor.lock()
return try {
ensureInstance()
} catch (e: RuntimeException) {
monitor.unlock()
throw e
}.Transaction()
}
}
class UnexpectedOutputException(msg: String) : RuntimeException(msg)
private fun checkOutput(command: String, result: Shell.Result, out: Boolean = result.out.isNotEmpty(),
err: Boolean = stderr.isNotEmpty()) {
if (result.isSuccess && !out && !err) return
val msg = StringBuilder("$command exited with ${result.code}")
if (out) result.out.forEach { msg.append("\n$it") }
// TODO bug: https://github.com/topjohnwu/libsu/pull/23
if (err) stderr.forEach { msg.append("\nE $it") }
throw UnexpectedOutputException(msg.toString())
}
private val shell = Shell.newInstance("su")
private val stdout = ArrayList<String>()
private val stderr = ArrayList<String>()
init {
// check basic shell functionality very basically
val result = execQuiet("echo $INIT_CHECKPOINT")
checkOutput("echo", result, result.out.joinToString("\n").trim() != INIT_CHECKPOINT)
}
/**
* Don't care about the results, but still sync.
*/
fun submit(command: String) {
val result = execQuiet(command)
if (result.code != 0) Crashlytics.log(Log.VERBOSE, TAG, "$command exited with ${result.code}")
var msg = stderr.joinToString("\n").trim()
if (msg.isNotEmpty()) Crashlytics.log(Log.VERBOSE, TAG, msg)
msg = result.out.joinToString("\n").trim()
if (msg.isNotEmpty()) Crashlytics.log(Log.VERBOSE, TAG, msg)
}
fun execQuiet(command: String, redirect: Boolean = false): Shell.Result {
stdout.clear()
return shell.newJob().add(command).to(stdout, if (redirect) stdout else {
stderr.clear()
stderr
}).exec()
}
fun exec(command: String) = checkOutput(command, execQuiet(command))
fun execOut(command: String): String {
val result = execQuiet(command)
checkOutput(command, result, false)
return result.out.joinToString("\n")
}
/**
* This transaction is different from what you may have in mind since you can revert it after committing it.
*/
inner class Transaction {
private val revertCommands = LinkedList<String>()
fun exec(command: String, revert: String? = null) {
if (revert != null) revertCommands.addFirst(revert) // add first just in case exec fails
this@RootSession.exec(command)
}
fun execQuiet(command: String) = this@RootSession.execQuiet(command)
fun commit() = monitor.unlock()
fun revert() {
if (revertCommands.isEmpty()) return
val shell = if (monitor.isHeldByCurrentThread) this@RootSession else {
monitor.lock()
ensureInstance()
}
revertCommands.forEach { shell.submit(it) }
revertCommands.clear()
monitor.unlock() // commit
}
}
}