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/<folder>/... 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 <the412banner@users.noreply.github.com>
This commit is contained in:
The412BannerandThe412Banner authored and GitHub committed 2026-10-06 13:48:01 -04:00
1 parent 9fe5f29f64
commit dba632ea14
4 files changed
+39 -4

No files matched your search

@@ -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>): 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) }
@@ -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
@@ -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))
}
}
+12 -3
View File
@@ -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);