92 lines
3.0 KiB
Kotlin
92 lines
3.0 KiB
Kotlin
package be.mygod.vpnhotspot.util
|
|
|
|
import be.mygod.librootkotlinx.RootServer
|
|
import be.mygod.vpnhotspot.root.RootManager
|
|
import be.mygod.vpnhotspot.root.RoutingCommands
|
|
import kotlinx.coroutines.runBlocking
|
|
import timber.log.Timber
|
|
import java.util.*
|
|
import java.util.concurrent.locks.ReentrantLock
|
|
import kotlin.concurrent.withLock
|
|
|
|
class RootSession : AutoCloseable {
|
|
companion object {
|
|
private val monitor = ReentrantLock()
|
|
|
|
fun <T> use(operation: (RootSession) -> T) = monitor.withLock { operation(RootSession()) }
|
|
fun beginTransaction(): Transaction {
|
|
monitor.lock()
|
|
val instance = try {
|
|
RootSession()
|
|
} catch (e: RuntimeException) {
|
|
monitor.unlock()
|
|
throw e
|
|
}
|
|
return instance.Transaction()
|
|
}
|
|
}
|
|
|
|
private var server: RootServer? = runBlocking { RootManager.acquire() }
|
|
override fun close() {
|
|
server = null
|
|
server?.let { runBlocking { RootManager.release(it) } }
|
|
}
|
|
|
|
/**
|
|
* Don't care about the results, but still sync.
|
|
*/
|
|
fun submit(command: String) = execQuiet(command).message(listOf(command))?.let { Timber.v(it) }
|
|
|
|
fun execQuiet(command: String, redirect: Boolean = false) = runBlocking {
|
|
server!!.execute(RoutingCommands.Process(listOf("sh", "-c", command), redirect))
|
|
}
|
|
fun exec(command: String) = execQuiet(command).check(listOf(command))
|
|
fun execOut(command: String): String {
|
|
val result = execQuiet(command)
|
|
result.check(listOf(command), false)
|
|
return result.out
|
|
}
|
|
|
|
/**
|
|
* 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) = execQuiet(command, revert).check(listOf(command))
|
|
fun execQuiet(command: String, revert: String? = null): RoutingCommands.ProcessResult {
|
|
if (revert != null) revertCommands.addFirst(revert) // add first just in case exec fails
|
|
return this@RootSession.execQuiet(command)
|
|
}
|
|
|
|
fun commit() = monitor.unlock()
|
|
|
|
fun revert() {
|
|
if (revertCommands.isEmpty()) return
|
|
var locked = monitor.isHeldByCurrentThread
|
|
try {
|
|
val shell = if (locked) this@RootSession else {
|
|
monitor.lock()
|
|
locked = true
|
|
RootSession()
|
|
}
|
|
revertCommands.forEach { shell.submit(it) }
|
|
} catch (e: RuntimeException) { // if revert fails, it should fail silently
|
|
Timber.d(e)
|
|
} finally {
|
|
revertCommands.clear()
|
|
if (locked) monitor.unlock() // commit
|
|
}
|
|
}
|
|
|
|
fun safeguard(work: Transaction.() -> Unit) = try {
|
|
work()
|
|
commit()
|
|
this
|
|
} catch (e: Exception) {
|
|
revert()
|
|
throw e
|
|
}
|
|
}
|
|
}
|