diff --git a/app/src/main/java/io/github/dylanneve1/iosonpixel/QemuBackend.kt b/app/src/main/java/io/github/dylanneve1/iosonpixel/QemuBackend.kt index f94a02a..7cf26fb 100644 --- a/app/src/main/java/io/github/dylanneve1/iosonpixel/QemuBackend.kt +++ b/app/src/main/java/io/github/dylanneve1/iosonpixel/QemuBackend.kt @@ -62,11 +62,8 @@ class QemuBackend(ctx: Context, private val install: Install) : VmBackend { val cmd = QemuCommand.build(paths, vm, settings) + settings.extraArgList + extra log("$ " + cmd.joinToString(" ")) + val env = QemuCommand.env(paths.tmpDir, settings) if (settings.inProcess) { - val env = buildMap { - put("TMPDIR", paths.tmpDir.absolutePath) - if (settings.effectiveVcpuAffinity.isNotBlank()) put("QEMU_VCPU_AFFINITY", settings.effectiveVcpuAffinity) - } val vmProcess = QemuInProcess(app, paths, cmd, vm.dir.absolutePath, env) if (vmProcess.bind()) return vmProcess log("error: failed to start QEMU: cannot bind the :vm service") @@ -74,10 +71,7 @@ class QemuBackend(ctx: Context, private val install: Install) : VmBackend { } val pb = ProcessBuilder(cmd).directory(vm.dir).redirectErrorStream(true) - pb.environment()["TMPDIR"] = paths.tmpDir.absolutePath - if (settings.effectiveVcpuAffinity.isNotBlank()) { - pb.environment()["QEMU_VCPU_AFFINITY"] = settings.effectiveVcpuAffinity - } + pb.environment().putAll(env) return try { Instance(pb.start()) @@ -171,6 +165,17 @@ object QemuCommand { return runCatching { f.readText().split(Regex("\\s+")).filter { it.isNotEmpty() } }.getOrDefault(emptyList()) } + /** + * Environment for QEMU, the same for the child process and the in-process :vm path. + * TMPDIR is where -snapshot puts its overlays (vl.XXXXXX): the app's cache dir, which + * only the app's uid can write. QEMU run by hand as another uid (adb shell) needs a + * TMPDIR of its own, or it fails with "Could not open temporary file" (#68). + */ + fun env(tmpDir: File, s: VmSettings): Map = buildMap { + put("TMPDIR", tmpDir.absolutePath) + s.effectiveVcpuAffinity.takeIf { it.isNotBlank() }?.let { put("QEMU_VCPU_AFFINITY", it) } + } + /** Mirrors the sample command from the Inferno guide, minus host-only display flags. */ fun build(paths: AppPaths, vm: VmLayout, s: VmSettings): List { fun path(key: String) = vm.file(key)!!.absolutePath diff --git a/app/src/test/java/io/github/dylanneve1/iosonpixel/QemuEnvTest.kt b/app/src/test/java/io/github/dylanneve1/iosonpixel/QemuEnvTest.kt new file mode 100644 index 0000000..ba9d32a --- /dev/null +++ b/app/src/test/java/io/github/dylanneve1/iosonpixel/QemuEnvTest.kt @@ -0,0 +1,27 @@ +package io.github.dylanneve1.iosonpixel + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Test +import java.io.File + +class QemuEnvTest { + private val cache = File("/data/user/0/io.github.dylanneve1.iosonpixel/cache") + + @Test fun tmpDirIsTheCacheDirOnBothPaths() { + // -snapshot puts its overlays in TMPDIR (#68); the child process and the :vm path share one env. + for (inProcess in listOf(false, true)) { + val env = QemuCommand.env(cache, VmSettings(inProcess = inProcess)) + assertEquals(cache.absolutePath, env["TMPDIR"]) + } + } + + @Test fun autoAffinityFollowsSmp() { + val env = QemuCommand.env(cache, VmSettings(smp = 4)) + assertEquals("2-7;2-7;2-7;0-1", env["QEMU_VCPU_AFFINITY"]) + } + + @Test fun emptyAffinityLeavesThePinningOut() { + assertFalse("QEMU_VCPU_AFFINITY" in QemuCommand.env(cache, VmSettings(vcpuAffinity = ""))) + } +} diff --git a/docs/PHONE-SMOKE.md b/docs/PHONE-SMOKE.md index c251621..596cd6a 100644 --- a/docs/PHONE-SMOKE.md +++ b/docs/PHONE-SMOKE.md @@ -87,6 +87,16 @@ qemu -M t8030,trustcache=restore.trustcache.im4p,sep-fw=placeholder.sepfw,sep-ro , as in QemuBackend.kt> ``` +**`-snapshot` and TMPDIR.** `-snapshot` writes one overlay per drive (`vl.XXXXXX`) to +`$TMPDIR`. Under `adb shell` that is `/data/local/tmp`, which works. The app sets TMPDIR +to its cache dir for both the child process and the in-process :vm path, and only the +app's uid can write there. So a QEMU started by hand as the shell user with the app's +TMPDIR fails at the first drive with `Could not open temporary file +'.../cache/vl.XXXXXX': Permission denied` (#68). Run it with `run-as ` to use the +app's cache dir. The SELinux log also shows a denied `ioctl` (`0x581f`, +FS_IOC_FSGETXATTR) on each overlay. That one is harmless: QEMU only tries to read the +extent size hint and goes on without it. + **SEP placeholders.** Since Inferno #11, t8030 refuses SEP images that can't be real: empty files, files larger than their region (ROM 8 MiB, firmware 16 MiB), and files that are a single repeated byte. So zero-filled placeholders stop at load with a clear error. Fill the