Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import java.io.FilterInputStream
import java.io.InputStream
import java.io.OutputStream
import java.util.concurrent.TimeUnit
import kotlin.concurrent.thread
import kotlin.system.measureTimeMillis

class IdeArchiveServiceImpl(
Expand Down Expand Up @@ -255,6 +254,21 @@ class IdeArchiveServiceImpl(
}
}

/**
* Extracts a `.tar.xz` archive by invoking Termux `tar`.
*
* The child process's combined stdout/stderr is redirected to a temporary log file
* ([ProcessBuilder.redirectOutput]) and read back after the process exits, rather than being
* drained by a separate reader thread. This removes the concurrency hazard behind ADFA-3945
* (Sentry APPDEVFORALL-V0): when a timeout triggers [Process.destroyForcibly] the pipe is
* closed, and a thread blocked in `read()` on it would throw an `InterruptedIOException` on a
* bare thread with no handler, crashing the app. With OS-side redirection there is no in-flight
* read to interrupt, and the process also cannot deadlock on a full pipe buffer.
*
* @param archiveFile the `.tar.xz` to extract.
* @param outputDir the directory to extract into (must exist and be writable).
* @return `true` only if `tar` completed within the timeout with exit code 0.
*/
private fun extractTarXzViaTermux(archiveFile: File, outputDir: File): Boolean {
if (!archiveFile.exists()) {
logger.debug("Archive not found: ${archiveFile.absolutePath}")
Expand All @@ -263,28 +277,25 @@ class IdeArchiveServiceImpl(

logger.debug("Starting Termux tar extraction: ${archiveFile.absolutePath}")

val logFile = File.createTempFile("tarxz-extract", ".log", outputDir)
return runCatching {
val output = StringBuilder()
var exitCode = -1

val elapsed = measureTimeMillis {
val process = ProcessBuilder(
"$TERMUX_BIN_PATH/tar", "-xJf", archiveFile.absolutePath,
"-C", outputDir.canonicalPath, "--no-same-owner"
).redirectErrorStream(true).apply {
environment()["PATH"] = TERMUX_BIN_PATH
}.start()

val reader = thread(name = "tar-xz-extract-output") {
process.inputStream.bufferedReader().useLines { it.forEach(output::appendLine) }
}
).redirectErrorStream(true)
.redirectOutput(logFile)
.apply { environment()["PATH"] = TERMUX_BIN_PATH }
.start()

val completed = process.waitFor(2, TimeUnit.MINUTES)
if (!completed) process.destroyForcibly()
reader.join()
exitCode = if (completed) process.exitValue() else -1
}

val output = runCatching { logFile.readText() }.getOrDefault("")
when (exitCode) {
0 -> {
logger.debug("Extraction succeeded in ${elapsed}ms: $output")
Expand All @@ -298,6 +309,8 @@ class IdeArchiveServiceImpl(
}.getOrElse { e ->
logger.error("Termux process error: ${e.message}")
false
}.also {
logFile.delete()
}
}

Expand Down
Loading