Migrate from NoisySu to RootSession
Fix #24. Note that just like before, IpMonitor doesn't use RootSession.
This commit is contained in:
107
mobile/src/main/java/be/mygod/vpnhotspot/util/RootSession.kt
Normal file
107
mobile/src/main/java/be/mygod/vpnhotspot/util/RootSession.kt
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user