From dba632ea14bb1231e1d024e1824b6c41ab4408a6 Mon Sep 17 00:00:00 2001 From: The412Banner <205237651+The412Banner@users.noreply.github.com> Date: Tue, 6 Oct 2026 13:48:01 -0400 Subject: [PATCH] Fast path: hold every bind, so added games start in their own folder (#343) * Fast path: hold every bind, so added games start in their own folder libblfastpath kept the first 64 binds and dropped the rest without a word. A session has 87 (two Games folders are the last two), so /root/Games//... was mapped into the rootfs - the empty mount point - instead of the bound storage. Wine resolves a new process's working directory by those paths, found no drive for it and started every added game in C:\windows ("could not find DOS drive for current working directory"): DiRT 3 did not mind, FEZ looks for Content\Essentials.pak there and quits. The library now holds 256, and turns itself off for the process when a bind does not fit or the list runs past that, leaving the call to proot, which never maps it wrong. The app keeps the fast path off for a session with more binds than that. Verified on an AYANEO Pocket FIT: FEZ (added game) reaches its title screen; the warning is gone. * Fast path: say in the session log when it stays off --------- Co-authored-by: The412Banner --- .../launcher/runtime/ProotFastPath.kt | 4 +++- .../launcher/session/SessionService.kt | 3 +++ .../launcher/runtime/ProotFastPathTest.kt | 21 +++++++++++++++++++ tools/proot/fastpath/fastpath.c | 15 ++++++++++--- 4 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 app/src/test/java/com/droiddeck/launcher/runtime/ProotFastPathTest.kt diff --git a/app/src/main/java/com/droiddeck/launcher/runtime/ProotFastPath.kt b/app/src/main/java/com/droiddeck/launcher/runtime/ProotFastPath.kt index 104e73113..9b7818046 100644 --- a/app/src/main/java/com/droiddeck/launcher/runtime/ProotFastPath.kt +++ b/app/src/main/java/com/droiddeck/launcher/runtime/ProotFastPath.kt @@ -21,13 +21,15 @@ import java.security.MessageDigest */ object ProotFastPath { const val LIBRARY = "/usr/local/lib/libblfastpath.so" + /** MAX_BINDS in fastpath.c: past it the library turns itself off, so the session keeps it off. */ + const val MAX_BINDS = 256 fun enabled(context: Context): Boolean = SessionPrefs.prootFastPath(context) && !SessionPrefs.prootNoSeccomp(context) /** The key for this rootfs and these binds, or null when the library cannot be told them. */ fun key(root: File, binds: List): String? { - if (binds.any { it.contains('|') } || root.path.contains('|')) return null + if (binds.size > MAX_BINDS || binds.any { it.contains('|') } || root.path.contains('|')) return null val digest = MessageDigest.getInstance("SHA-256") .digest((root.path + "\n" + binds.joinToString("\n")).toByteArray()) return digest.take(12).joinToString("") { "%02x".format(it) } diff --git a/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt b/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt index 4c5014375..e2e08badf 100644 --- a/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt +++ b/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt @@ -416,6 +416,9 @@ class SessionService : Service() { guest.addAll(fastPathAt, env) shellGuest.addAll(fastPathAt, env) Log.i(TAG, "proot: fast path on (${prootBinds.size} binds)") + } ?: run { + Log.w(TAG, "proot: fast path off - ${prootBinds.size} binds (it holds ${ProotFastPath.MAX_BINDS}) or a path it cannot be told") + null } } else null diff --git a/app/src/test/java/com/droiddeck/launcher/runtime/ProotFastPathTest.kt b/app/src/test/java/com/droiddeck/launcher/runtime/ProotFastPathTest.kt new file mode 100644 index 000000000..ce129f7c0 --- /dev/null +++ b/app/src/test/java/com/droiddeck/launcher/runtime/ProotFastPathTest.kt @@ -0,0 +1,21 @@ +package com.droiddeck.launcher.runtime + +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.junit.Test +import java.io.File + +class ProotFastPathTest { + private val root = File("/data/data/com.droiddeck.launcher/files/linuxfs") + + @Test fun aSessionsUsualBindsGetAKey() { + // 87 on a device with two Games folders: past the old limit of 64, which dropped them. + val binds = (1..87).map { "/host/$it:/guest/$it" } + assertNotNull(ProotFastPath.key(root, binds)) + } + + @Test fun moreBindsThanTheLibraryHoldsLeaveItOff() { + val binds = (1..ProotFastPath.MAX_BINDS + 1).map { "/host/$it:/guest/$it" } + assertNull(ProotFastPath.key(root, binds)) + } +} diff --git a/tools/proot/fastpath/fastpath.c b/tools/proot/fastpath/fastpath.c index c7fb1f0b5..a48b201f8 100644 --- a/tools/proot/fastpath/fastpath.c +++ b/tools/proot/fastpath/fastpath.c @@ -48,7 +48,10 @@ #define FP_STUB_ADDR 0xffff00000UL /* must match FASTPATH_STUB_ADDR in proot's seccomp.c */ #define FP_STUB_SIZE 4096UL -#define MAX_BINDS 64 +/* A session binds 80-odd paths (devices, the Steam libraries, each Games folder...). More than fit, + * or one that cannot be held, turns the fast path off: a bind it did not know would map that + * guest path into the rootfs instead (an empty mount point), which proot never does. */ +#define MAX_BINDS 256 #define FP_SLOW (-100000L) typedef long (*stub_fn)(long nr, long a, long b, long c, long d, long e, long f); @@ -162,7 +165,9 @@ static void fp_init(void) { snprintf(root, sizeof root, "%s", r); canonical(root, sizeof root); rootlen = strlen(root); - for (const char *s = b; s && *s && nbinds < MAX_BINDS;) { + const char *s = b; + int unheld = 0; + for (; s && *s && nbinds < MAX_BINDS;) { const char *end = strchr(s, '|'); size_t len = end ? (size_t)(end - s) : strlen(s); char spec[2 * PATH_MAX]; @@ -176,7 +181,7 @@ static void fp_init(void) { * Static storage: malloc here would grow the heap with brk(2), which proot traps. */ static char hosts[MAX_BINDS][PATH_MAX], guests[MAX_BINDS][512]; const char *g = colon ? colon + 1 : spec; - if (strlen(g) >= sizeof guests[0]) { s = end ? end + 1 : NULL; continue; } + if (strlen(g) >= sizeof guests[0]) { unheld = 1; break; } bd->host = hosts[nbinds]; bd->guest = guests[nbinds]; snprintf(bd->guest, sizeof guests[0], "%s", g); @@ -184,9 +189,13 @@ static void fp_init(void) { bd->hlen = strlen(bd->host); bd->glen = strlen(bd->guest); if (bd->guest[0] == '/' && bd->glen > 1 && bd->hlen > 0) nbinds++; + } else if (len > 0) { + unheld = 1; + break; } s = end ? end + 1 : NULL; } + if (unheld || (s && *s)) { fp_sys = NULL; return; } pthread_atfork(NULL, NULL, after_fork); /* registered once per exec */ fp_on = 1; if (getenv("PROOT_FP_STATS")) atexit(stats);