Skip to content

Commit

Permalink
harden shell commands, especially in case when exceptions should not …
Browse files Browse the repository at this point in the history
…be thrown (callers do not expect exceptions)
  • Loading branch information
hg42 committed Aug 25, 2024
1 parent 52095f9 commit c5d5a5b
Showing 1 changed file with 83 additions and 51 deletions.
134 changes: 83 additions & 51 deletions src/main/java/com/machiav3lli/backup/handler/ShellHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,11 @@ import androidx.compose.runtime.setValue
import androidx.core.text.isDigitsOnly
import com.machiav3lli.backup.OABX
import com.machiav3lli.backup.OABX.Companion.addErrorCommand
import com.machiav3lli.backup.OABX.Companion.assets
import com.machiav3lli.backup.OABX.Companion.isDebug
import com.machiav3lli.backup.handler.LogsHandler.Companion.logException
import com.machiav3lli.backup.handler.ShellHandler.Companion.splitCommand
import com.machiav3lli.backup.handler.ShellHandler.FileInfo.Companion.utilBoxInfo
import com.machiav3lli.backup.plugins.InternalShellScriptPlugin
import com.machiav3lli.backup.plugins.Plugin
import com.machiav3lli.backup.preferences.baseInfo
import com.machiav3lli.backup.preferences.pref_libsuTimeout
import com.machiav3lli.backup.preferences.pref_libsuUseRootShell
Expand All @@ -41,7 +39,6 @@ import com.machiav3lli.backup.utils.BUFFER_SIZE
import com.machiav3lli.backup.utils.FileUtils.translatePosixPermissionToMode
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.ShellUtils
import com.topjohnwu.superuser.ShellUtils.fastCmd
import com.topjohnwu.superuser.io.SuRandomAccessFile
import de.voize.semver4k.Semver
import kotlinx.coroutines.Dispatchers
Expand All @@ -63,6 +60,24 @@ const val verKnown = "0.8.0 - 0.8.7"
const val verBugDotDotDirHang = "0.8.3 - 0.8.6"
const val verBugDotDotDirExtract = "0.8.0 - 0.8.0" //TODO hg42 more versions?

class FakeShellResult(
var aCode: Int,
val aOut: MutableList<String> = mutableListOf(),
val aErr: MutableList<String> = mutableListOf(),
) : Shell.Result() {
override fun getOut(): MutableList<String> {
return aOut
}

override fun getErr(): MutableList<String> {
return aErr
}

override fun getCode(): Int {
return aCode
}
}

class ShellHandler {

class UtilBox(
Expand Down Expand Up @@ -308,7 +323,8 @@ class ShellHandler {
class ShellCommandFailedException(
@field:Transient val shellResult: Shell.Result,
val command: String,
) : Exception()
cause: Throwable? = null,
) : Exception(cause)

class UnexpectedCommandResult(message: String, val shellResult: Shell.Result?) :
Exception(message)
Expand Down Expand Up @@ -902,15 +918,22 @@ class ShellHandler {
// Shell.Config.setFlags(Shell.FLAG_REDIRECT_STDERR);
// stderr is used for logging, so it's better not to call an application that does that
// and keeps quiet
Timber.d("Running Command: $command")
val stdout: List<String> = arrayListOf()
val stderr: List<String> = arrayListOf()
val result = Shell.cmd(command).to(stdout, stderr).exec()
Timber.d("Command(s) $command ended with ${result.code}")
if (!result.isSuccess) {
var result: Shell.Result = FakeShellResult(-1)
try {
Timber.d("Running Command: $command")
val stdout: List<String> = arrayListOf()
val stderr: List<String> = arrayListOf()
result = Shell.cmd(command).to(stdout, stderr).exec()
Timber.d("Command(s) $command ended with ${result.code}")
if (!result.isSuccess) {
addErrorCommand(command)
if (throwFail)
throw ShellCommandFailedException(result, command)
}
} catch (e: Throwable) {
addErrorCommand(command)
if (throwFail)
throw ShellCommandFailedException(result, command)
throw ShellCommandFailedException(result, command = command, cause = e)
}
return result
}
Expand All @@ -928,31 +951,36 @@ class ShellHandler {

return runBlocking(Dispatchers.IO) {

val process = Runtime.getRuntime().exec(splitCommand(suCommand).toTypedArray())
runCatching {

val shellIn = process.outputStream
//val shellOut = process.inputStream
val shellErr = process.errorStream
val process = Runtime.getRuntime().exec(splitCommand(suCommand).toTypedArray())

val errAsync = async(Dispatchers.IO) {
shellErr.readBytes().decodeToString()
}
val shellIn = process.outputStream
//val shellOut = process.inputStream
val shellErr = process.errorStream

shellIn.write("$command\n".encodeToByteArray())
val errAsync = async(Dispatchers.IO) {
shellErr.readBytes().decodeToString()
}

inStream.copyTo(shellIn, 65536)
shellIn.close()
shellIn.write("$command\n".encodeToByteArray())

val err = errAsync.await()
withContext(Dispatchers.IO) { process.waitFor(10, TimeUnit.SECONDS) }
if (process.isAlive)
process.destroyForcibly()
val code = process.exitValue()
inStream.copyTo(shellIn, 65536)
shellIn.close()

if (code != 0)
addErrorCommand(command)
val err = errAsync.await()
withContext(Dispatchers.IO) { process.waitFor(10, TimeUnit.SECONDS) }
if (process.isAlive)
process.destroyForcibly()
val code = process.exitValue()

if (code != 0)
addErrorCommand(command)

(code to err)
(code to err)
}.getOrElse {
(-1 to (it.message ?: "unknown error"))
}
}
}

Expand All @@ -964,34 +992,38 @@ class ShellHandler {

return runBlocking(Dispatchers.IO) {

val process = Runtime.getRuntime().exec(splitCommand(suCommand).toTypedArray())

val shellIn = process.outputStream
val shellOut = process.inputStream
val shellErr = process.errorStream
runCatching {

val errAsync = async(Dispatchers.IO) {
shellErr.readBytes().decodeToString()
}
val process = Runtime.getRuntime().exec(splitCommand(suCommand).toTypedArray())

shellIn.write(command.encodeToByteArray())
shellIn.close()
val shellIn = process.outputStream
val shellOut = process.inputStream
val shellErr = process.errorStream

shellOut.copyTo(outStream, 65536)
outStream.flush()
val errAsync = async(Dispatchers.IO) {
shellErr.readBytes().decodeToString()
}

val err = errAsync.await()
withContext(Dispatchers.IO) {
process.waitFor(10, TimeUnit.SECONDS)
}
if (process.isAlive)
process.destroyForcibly()
val code = process.exitValue()
shellIn.write(command.encodeToByteArray())
shellIn.close()

if (code != 0)
addErrorCommand(command)
shellOut.copyTo(outStream, 65536)
outStream.flush()

(code to err)
val err = errAsync.await()
withContext(Dispatchers.IO) {
process.waitFor(10, TimeUnit.SECONDS)
}
if (process.isAlive)
process.destroyForcibly()
val code = process.exitValue()
if (code != 0)
addErrorCommand(command)

(code to err)
}.getOrElse {
(-1 to (it.message ?: "unknown error"))
}
}
}

Expand Down

0 comments on commit c5d5a5b

Please sign in to comment.