Run x86_64 Flatpak apps through FEX (#1875)

* Run x86_64 Flatpak apps through FEX

* proot: run an execveat with an absolute path like execve

* Find x86_64 Flatpak installs and resolve sandbox paths like the sandbox does
This commit is contained in:
MaxsTechReview authored and GitHub committed 2026-10-30 21:21:22 -04:00
1 parent 8088e47d14
commit 28067b3bbe
45 files changed
+494 -61

No files matched your search

@@ -75,7 +75,10 @@ object BwrapSpawner {
}
}
class Plan(val root: File, val binds: List<Bind>, val cwd: String, val argv: List<String>, val env: Map<String, String>)
class Plan(
val root: File, val binds: List<Bind>, val cwd: String, val argv: List<String>, val env: Map<String, String>,
val envTool: String = "/usr/bin/env",
)
data class Bind(val host: String, val guest: String) {
val spec: String get() = if (host == guest) host else "$host:$guest"
@@ -179,12 +182,13 @@ object BwrapSpawner {
val argv = request.getJSONArray("argv").let { a -> (0 until a.length()).map { a.getString(it) } }
val env = LinkedHashMap<String, String>()
request.optJSONObject("env")?.let { e -> e.keys().forEach { k -> env[k] = e.getString(k) } }
if (binds.any { it.guest == "/dev" }) gpu(rootfs, root, env)?.forEach { addBind(it) }
val envTool = request.optString("envTool").takeIf { it.startsWith("/") }
if (envTool == null && binds.any { it.guest == "/dev" }) gpu(rootfs, root, env)?.forEach { addBind(it) }
val libc = listOf("/usr/lib/aarch64-linux-gnu/libc.so.6", "/lib/aarch64-linux-gnu/libc.so.6").map { hostPath(binds, root.path, it) }
if (preloadFits(rootfs, libc.firstOrNull { File(it).isFile } ?: libc.first())) preload(session, rootfs, env).forEach { addBind(it) }
if (envTool == null && preloadFits(rootfs, libc.firstOrNull { File(it).isFile } ?: libc.first())) preload(session, rootfs, env).forEach { addBind(it) }
zypakShims(binds, root, dir).forEach { addBind(it) }
browserSandboxes(env)
return Plan(root, binds, request.optString("cwd", "/").ifEmpty { "/" }, argv, env)
return Plan(root, binds, request.optString("cwd", "/").ifEmpty { "/" }, argv, env, envTool ?: "/usr/bin/env")
}
private const val GPU_DIR = "/run/droiddeck-gpu"
@@ -325,7 +329,7 @@ object BwrapSpawner {
val verbose = plan.env["DROIDDECK_PROOT_VERBOSE"]?.toIntOrNull()
if (verbose != null) { cmd.add(1, "-v"); cmd.add(2, verbose.toString()) }
plan.binds.forEach { cmd.add("-b"); cmd.add(it.spec) }
cmd.add("/usr/bin/env")
cmd.add(plan.envTool)
cmd.add("-i")
plan.env.forEach { (k, v) -> cmd.add("$k=$v") }
cmd.addAll(plan.argv)
@@ -19,7 +19,7 @@ object FlatpakManager {
@Volatile var busy: String? = null
private set
class App(val id: String, val name: String, val icon: File?, val branch: String, val summary: String?)
class App(val id: String, val name: String, val icon: File?, val branch: String, val summary: String?, val arch: String = "aarch64")
fun ready(context: Context): Boolean {
val root = LinuxRuntime.rootDir(context)
@@ -33,24 +33,33 @@ object FlatpakManager {
else -> "aarch64"
}
internal fun arches(native: String = arch()): List<String> = if (native == "aarch64") listOf("aarch64", "x86_64") else listOf(native)
internal fun installedBranch(appDir: File, native: String = arch()): Pair<String, File>? =
arches(native).firstNotNullOfOrNull { arch ->
File(appDir, arch).listFiles()?.firstOrNull { File(it, "active").exists() }?.let { arch to it }
}
fun foreign(app: App, native: String = arch()): Boolean = app.arch != native
fun launchArgs(app: App, native: String = arch()): List<String> = if (foreign(app, native)) listOf("--arch=${app.arch}", app.id) else listOf(app.id)
fun installedApps(context: Context): List<App> {
val apps = File(LinuxRuntime.rootDir(context), "$USER_DIR/app")
val arch = arch()
return apps.listFiles()?.mapNotNull { dir ->
val id = dir.name
val archDir = File(dir, arch)
val branch = archDir.listFiles()?.firstOrNull { File(it, "active").exists() } ?: return@mapNotNull null
val (arch, branch) = installedBranch(dir) ?: return@mapNotNull null
val active = File(branch, "active")
val export = File(active, "export/share")
val desktop = FileUtils.readString(File(export, "applications/$id.desktop"))
val name = desktop?.let { entry(it, "Name") } ?: id.substringAfterLast('.')
val summary = desktop?.let { entry(it, "Comment") }
App(id, name, icon(export, id), branch.name, summary)
App(id, name, icon(export, id), branch.name, summary, arch)
}?.sortedBy { it.name.lowercase() } ?: emptyList()
}
fun isInstalled(context: Context, id: String): Boolean =
File(LinuxRuntime.rootDir(context), "$USER_DIR/app/$id/${arch()}").listFiles()?.any { File(it, "active").exists() } == true
installedBranch(File(LinuxRuntime.rootDir(context), "$USER_DIR/app/$id")) != null
private fun entry(desktop: String, key: String): String? {
var inMain = false
@@ -97,7 +97,8 @@ object UserApps {
App(
"flatpak:${app.id}", Kind.FLATPAK, readName(custom) ?: app.name,
File(custom, "icon.png").takeIf { it.isFile } ?: app.icon ?: FlathubApi.iconUrl(app.id),
app.summary, FlatpakManager.LAUNCHER, listOf(app.id),
app.summary, FlatpakManager.LAUNCHER, FlatpakManager.launchArgs(app),
arch = app.arch.takeIf { FlatpakManager.foreign(app) },
)
}
return (scripts + images + flatpaks).sortedBy { it.name.lowercase() }
@@ -465,7 +465,8 @@ private fun UserAppPage(app: UserApps.App, s: FrontEndState, a: FrontEndActions,
val ctx = LocalContext.current
val narrow = LocalNarrowPane.current
var editing by rememberSaveable(app.key) { mutableStateOf(false) }
val x86 = (app.arch == "x86_64" || app.arch == "i386") && app.fex != com.droiddeck.launcher.runtime.LinuxFex.OFF
val x86 = (app.arch == "x86_64" || app.arch == "i386") && app.fex != com.droiddeck.launcher.runtime.LinuxFex.OFF &&
app.kind != UserApps.Kind.FLATPAK
val fexReady by produceState<Boolean?>(null, app.key, x86, s.sessionRunning) {
value = if (x86) kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.IO) { com.droiddeck.launcher.runtime.LinuxFex.ready(ctx) } else true
}
+1 -1
View File
@@ -1892,7 +1892,7 @@
<string name="flatpakmgr_not_ready">أعِدّ Flatpak أولاً</string>
<string name="flatpakmgr_starting">جارٍ البدء</string>
<string name="flatpakmgr_exit_status">خرج Flatpak بالحالة %1$d</string>
<string name="flatpakmgr_wrong_arch">تم بناء %1$s لمعمارية %2$s، لكن تطبيقات Flatpak هنا يجب أن تكون لمعمارية ARM64. استخدم ملف AppImage للتطبيق بدلًا من ذلك؛ يشغّل DroidDeck ملفات AppImage لمعمارية x86 عبر FEX.</string>
<string name="flatpakmgr_wrong_arch">تم بناء %1$s لمعمارية %2$s، ولا يستطيع DroidDeck تشغيلها. تحتاج تطبيقات Flatpak هنا إلى إصدار لمعمارية ARM64 أو x86_64.</string>
<string name="flatpakmgr_installing">جارٍ تثبيت %1$s</string>
<string name="flatpakmgr_updating">جارٍ تحديث %1$s</string>
<string name="flatpakmgr_removing">جارٍ إزالة %1$s</string>
+1 -1
View File
@@ -1884,7 +1884,7 @@
<string name="flatpakmgr_not_ready">Configura Flatpak primero</string>
<string name="flatpakmgr_starting">Iniciando</string>
<string name="flatpakmgr_exit_status">Flatpak terminó con el código %1$d</string>
<string name="flatpakmgr_wrong_arch">%1$s está compilado para %2$s, pero aquí las apps Flatpak deben ser para ARM64. Usa el AppImage de la app; DroidDeck ejecuta los AppImage x86 con FEX.</string>
<string name="flatpakmgr_wrong_arch">%1$s está compilado para %2$s, que DroidDeck no puede ejecutar. Aquí las apps Flatpak necesitan una versión para ARM64 o x86_64.</string>
<string name="flatpakmgr_installing">Instalando %1$s</string>
<string name="flatpakmgr_updating">Actualizando %1$s</string>
<string name="flatpakmgr_removing">Quitando %1$s</string>
+1 -1
View File
@@ -1800,7 +1800,7 @@
<string name="flatpakmgr_not_ready">Настрой Flatpak първо</string>
<string name="flatpakmgr_starting">Стартиране</string>
<string name="flatpakmgr_exit_status">Flatpak приключи със код %1$d</string>
<string name="flatpakmgr_wrong_arch">%1$s е създадено за %2$s, но приложенията Flatpak тук трябва да са за ARM64. Използвайте AppImage на приложението; DroidDeck изпълнява x86 AppImage чрез FEX.</string>
<string name="flatpakmgr_wrong_arch">%1$s е създадено за %2$s, което DroidDeck не може да изпълнява. Приложенията Flatpak тук трябва да са за ARM64 или x86_64.</string>
<string name="flatpakmgr_installing">Инсталиране на %1$s</string>
<string name="flatpakmgr_updating">Актуализиране на %1$s</string>
<string name="flatpakmgr_removing">Премахване на %1$s</string>
+1 -1
View File
@@ -1846,7 +1846,7 @@
<string name="flatpakmgr_not_ready">Nejprve nastavte Flatpak</string>
<string name="flatpakmgr_starting">Spouštění</string>
<string name="flatpakmgr_exit_status">Flatpak skončil s návratovým kódem %1$d</string>
<string name="flatpakmgr_wrong_arch">%1$s je sestaveno pro %2$s, ale aplikace Flatpak zde musí být pro ARM64. Použijte AppImage aplikace; DroidDeck spouští x86 AppImage přes FEX.</string>
<string name="flatpakmgr_wrong_arch">%1$s je sestaveno pro %2$s, které DroidDeck neumí spustit. Aplikace Flatpak zde potřebují verzi pro ARM64 nebo x86_64.</string>
<string name="flatpakmgr_installing">Instalování %1$s</string>
<string name="flatpakmgr_updating">Aktualizování %1$s</string>
<string name="flatpakmgr_removing">Odstraňování %1$s</string>
+1 -1
View File
@@ -1800,7 +1800,7 @@
<string name="flatpakmgr_not_ready">Opsæt Flatpak først</string>
<string name="flatpakmgr_starting">Starter</string>
<string name="flatpakmgr_exit_status">Flatpak afsluttede med status %1$d</string>
<string name="flatpakmgr_wrong_arch">%1$s er bygget til %2$s, men Flatpak-apps her skal være bygget til ARM64. Brug appens AppImage i stedet; DroidDeck kører x86-AppImages gennem FEX.</string>
<string name="flatpakmgr_wrong_arch">%1$s er bygget til %2$s, som DroidDeck ikke kan køre. Flatpak-apps her skal være bygget til ARM64 eller x86_64.</string>
<string name="flatpakmgr_installing">Installerer %1$s</string>
<string name="flatpakmgr_updating">Opdaterer %1$s</string>
<string name="flatpakmgr_removing">Fjerner %1$s</string>
+1 -1
View File
@@ -1792,7 +1792,7 @@
<string name="flatpakmgr_not_ready">Richte zuerst Flatpak ein</string>
<string name="flatpakmgr_starting">Wird gestartet</string>
<string name="flatpakmgr_exit_status">Flatpak wurde mit Status %1$d beendet</string>
<string name="flatpakmgr_wrong_arch">%1$s wurde für %2$s gebaut, Flatpak-Apps müssen hier aber für ARM64 gebaut sein. Nutze stattdessen das AppImage der App; DroidDeck führt x86-AppImages über FEX aus.</string>
<string name="flatpakmgr_wrong_arch">%1$s wurde für %2$s gebaut, das DroidDeck nicht ausführen kann. Flatpak-Apps brauchen hier eine ARM64- oder x86_64-Version.</string>
<string name="flatpakmgr_installing">%1$s wird installiert</string>
<string name="flatpakmgr_updating">%1$s wird aktualisiert</string>
<string name="flatpakmgr_removing">%1$s wird entfernt</string>
+1 -1
View File
@@ -1800,7 +1800,7 @@
<string name="flatpakmgr_not_ready">Ρυθμίστε πρώτα το Flatpak</string>
<string name="flatpakmgr_starting">Γίνεται εκκίνηση</string>
<string name="flatpakmgr_exit_status">Το Flatpak έκλεισε με κατάσταση %1$d</string>
<string name="flatpakmgr_wrong_arch">Το %1$s είναι φτιαγμένο για %2$s, αλλά οι εφαρμογές Flatpak εδώ πρέπει να είναι για ARM64. Χρησιμοποιήστε το AppImage της εφαρμογής· το DroidDeck εκτελεί x86 AppImage μέσω FEX.</string>
<string name="flatpakmgr_wrong_arch">Το %1$s είναι φτιαγμένο για %2$s, που το DroidDeck δεν μπορεί να εκτελέσει. Οι εφαρμογές Flatpak εδώ χρειάζονται έκδοση για ARM64 ή x86_64.</string>
<string name="flatpakmgr_installing">Εγκατάσταση %1$s</string>
<string name="flatpakmgr_updating">Ενημέρωση %1$s</string>
<string name="flatpakmgr_removing">Κατάργηση %1$s</string>
+1 -1
View File
@@ -1823,7 +1823,7 @@
<string name="flatpakmgr_not_ready">Configura Flatpak primero</string>
<string name="flatpakmgr_starting">Iniciando</string>
<string name="flatpakmgr_exit_status">Flatpak terminó con el código %1$d</string>
<string name="flatpakmgr_wrong_arch">%1$s está compilado para %2$s, pero aquí las apps Flatpak deben ser para ARM64. Usa el AppImage de la app; DroidDeck ejecuta los AppImage x86 con FEX.</string>
<string name="flatpakmgr_wrong_arch">%1$s está compilado para %2$s, que DroidDeck no puede ejecutar. Aquí las apps Flatpak necesitan una versión para ARM64 o x86_64.</string>
<string name="flatpakmgr_installing">Instalando %1$s</string>
<string name="flatpakmgr_updating">Actualizando %1$s</string>
<string name="flatpakmgr_removing">Quitando %1$s</string>
+1 -1
View File
@@ -1800,7 +1800,7 @@
<string name="flatpakmgr_not_ready">Asenna Flatpak ensin</string>
<string name="flatpakmgr_starting">Käynnistetään</string>
<string name="flatpakmgr_exit_status">Flatpak sulkeutui tilakoodilla %1$d</string>
<string name="flatpakmgr_wrong_arch">%1$s on tehty alustalle %2$s, mutta Flatpak-sovellusten täytyy täällä olla ARM64-sovelluksia. Käytä sovelluksen AppImagea; DroidDeck ajaa x86-AppImaget FEXin kautta.</string>
<string name="flatpakmgr_wrong_arch">%1$s on tehty alustalle %2$s, jota DroidDeck ei pysty ajamaan. Flatpak-sovelluksista tarvitaan täällä ARM64- tai x86_64-versio.</string>
<string name="flatpakmgr_installing">Asennetaan %1$s</string>
<string name="flatpakmgr_updating">Päivitetään %1$s</string>
<string name="flatpakmgr_removing">Poistetaan %1$s</string>
+1 -1
View File
@@ -1823,7 +1823,7 @@
<string name="flatpakmgr_not_ready">Configurez d\'abord Flatpak</string>
<string name="flatpakmgr_starting">Démarrage</string>
<string name="flatpakmgr_exit_status">Flatpak s\'est arrêté avec le code %1$d</string>
<string name="flatpakmgr_wrong_arch">%1$s est compilée pour %2$s, mais ici les applis Flatpak doivent l\'être pour ARM64. Utilisez plutôt l\'AppImage de l\'appli ; DroidDeck exécute les AppImage x86 avec FEX.</string>
<string name="flatpakmgr_wrong_arch">%1$s est compilée pour %2$s, que DroidDeck ne peut pas exécuter. Ici, les applis Flatpak doivent exister en version ARM64 ou x86_64.</string>
<string name="flatpakmgr_installing">Installation : %1$s</string>
<string name="flatpakmgr_updating">Mise à jour : %1$s</string>
<string name="flatpakmgr_removing">Suppression : %1$s</string>
+1 -1
View File
@@ -1800,7 +1800,7 @@
<string name="flatpakmgr_not_ready">पहले Flatpak सेट करें</string>
<string name="flatpakmgr_starting">शुरू हो रहा है</string>
<string name="flatpakmgr_exit_status">Flatpak status %1$d के साथ बंद हुआ</string>
<string name="flatpakmgr_wrong_arch">%1$s %2$s के लिए बना है, पर यहाँ Flatpak ऐप ARM64 के लिए बने होने चाहिए। इसके बजाय ऐप का AppImage इस्तेमाल करें; DroidDeck x86 AppImage को FEX से चलाता है।</string>
<string name="flatpakmgr_wrong_arch">%1$s %2$s के लिए बना है, जिसे DroidDeck नहीं चला सकता। यहाँ Flatpak ऐप ARM64 या x86_64 के लिए बने होने चाहिए।</string>
<string name="flatpakmgr_installing">%1$s इंस्टॉल हो रहा है</string>
<string name="flatpakmgr_updating">%1$s अपडेट हो रहा है</string>
<string name="flatpakmgr_removing">%1$s हटाया जा रहा है</string>
+1 -1
View File
@@ -1800,7 +1800,7 @@
<string name="flatpakmgr_not_ready">Előbb állítsa be a Flatpakot</string>
<string name="flatpakmgr_starting">Indítás</string>
<string name="flatpakmgr_exit_status">A Flatpak %1$d állapotkóddal lépett ki</string>
<string name="flatpakmgr_wrong_arch">A(z) %1$s %2$s rendszerre készült, de az itteni Flatpak-alkalmazásoknak ARM64-re kell készülniük. Használd inkább az alkalmazás AppImage-ét; a DroidDeck az x86 AppImage-eket FEX-en keresztül futtatja.</string>
<string name="flatpakmgr_wrong_arch">A(z) %1$s %2$s rendszerre készült, ezt a DroidDeck nem tudja futtatni. Az itteni Flatpak-alkalmazásoknak ARM64- vagy x86_64-változat kell.</string>
<string name="flatpakmgr_installing">%1$s telepítése</string>
<string name="flatpakmgr_updating">%1$s frissítése</string>
<string name="flatpakmgr_removing">%1$s eltávolítása</string>
+1 -1
View File
@@ -1777,7 +1777,7 @@
<string name="flatpakmgr_not_ready">Siapkan Flatpak terlebih dahulu</string>
<string name="flatpakmgr_starting">Memulai</string>
<string name="flatpakmgr_exit_status">Flatpak keluar dengan status %1$d</string>
<string name="flatpakmgr_wrong_arch">%1$s dibuat untuk %2$s, tetapi aplikasi Flatpak di sini harus dibuat untuk ARM64. Gunakan AppImage aplikasi tersebut; DroidDeck menjalankan AppImage x86 melalui FEX.</string>
<string name="flatpakmgr_wrong_arch">%1$s dibuat untuk %2$s, yang tidak dapat dijalankan DroidDeck. Aplikasi Flatpak di sini memerlukan versi ARM64 atau x86_64.</string>
<string name="flatpakmgr_installing">Menginstal %1$s</string>
<string name="flatpakmgr_updating">Memperbarui %1$s</string>
<string name="flatpakmgr_removing">Menghapus %1$s</string>
+1 -1
View File
@@ -1815,7 +1815,7 @@
<string name="flatpakmgr_not_ready">Configura prima Flatpak</string>
<string name="flatpakmgr_starting">Avvio</string>
<string name="flatpakmgr_exit_status">Flatpak è terminato con stato %1$d</string>
<string name="flatpakmgr_wrong_arch">%1$s è compilata per %2$s, ma qui le app Flatpak devono essere per ARM64. Usa invece l\'AppImage dell\'app; DroidDeck esegue le AppImage x86 tramite FEX.</string>
<string name="flatpakmgr_wrong_arch">%1$s è compilata per %2$s, che DroidDeck non può eseguire. Qui le app Flatpak richiedono una versione per ARM64 o x86_64.</string>
<string name="flatpakmgr_installing">Installazione di %1$s</string>
<string name="flatpakmgr_updating">Aggiornamento di %1$s</string>
<string name="flatpakmgr_removing">Rimozione di %1$s</string>
+1 -1
View File
@@ -1748,7 +1748,7 @@
<string name="flatpakmgr_not_ready">先にFlatpakをセットアップしてください</string>
<string name="flatpakmgr_starting">開始中</string>
<string name="flatpakmgr_exit_status">Flatpakがステータス%1$dで終了しました</string>
<string name="flatpakmgr_wrong_arch">%1$s は %2$s 向けにビルドされていますが、ここでは Flatpak アプリは ARM64 向けである必要があります。代わりにアプリの AppImage を使ってください。DroidDeck は x86 の AppImage を FEX で実行します。</string>
<string name="flatpakmgr_wrong_arch">%1$s は DroidDeck で実行できない %2$s 向けにビルドされています。ここでは Flatpak アプリに ARM64 または x86_64 版が必要です。</string>
<string name="flatpakmgr_installing">%1$sをインストール中</string>
<string name="flatpakmgr_updating">%1$sを更新中</string>
<string name="flatpakmgr_removing">%1$sを削除中</string>
+1 -1
View File
@@ -1777,7 +1777,7 @@
<string name="flatpakmgr_not_ready">먼저 Flatpak을 설정하세요</string>
<string name="flatpakmgr_starting">시작 중</string>
<string name="flatpakmgr_exit_status">Flatpak이 종료되었습니다(상태 %1$d)</string>
<string name="flatpakmgr_wrong_arch">%1$s은(는) %2$s용으로 빌드되었지만 여기서는 Flatpak 앱이 ARM64용이어야 합니다. 대신 앱의 AppImage를 사용하세요. DroidDeck은 x86 AppImage를 FEX로 실행합니다.</string>
<string name="flatpakmgr_wrong_arch">%1$s은(는) DroidDeck이 실행할 수 없는 %2$s용으로 빌드되었습니다. 여기서 Flatpak 앱은 ARM64 또는 x86_64 빌드여야 합니다.</string>
<string name="flatpakmgr_installing">%1$s 설치 중</string>
<string name="flatpakmgr_updating">%1$s 업데이트 중</string>
<string name="flatpakmgr_removing">%1$s 제거 중</string>
+1 -1
View File
@@ -1777,7 +1777,7 @@
<string name="flatpakmgr_not_ready">Sediakan Flatpak dahulu</string>
<string name="flatpakmgr_starting">Memulakan</string>
<string name="flatpakmgr_exit_status">Flatpak keluar dengan status %1$d</string>
<string name="flatpakmgr_wrong_arch">%1$s dibina untuk %2$s, tetapi apl Flatpak di sini mesti dibina untuk ARM64. Gunakan AppImage apl itu; DroidDeck menjalankan AppImage x86 melalui FEX.</string>
<string name="flatpakmgr_wrong_arch">%1$s dibina untuk %2$s, yang tidak dapat dijalankan oleh DroidDeck. Apl Flatpak di sini memerlukan binaan ARM64 atau x86_64.</string>
<string name="flatpakmgr_installing">Memasang %1$s</string>
<string name="flatpakmgr_updating">Mengemas kini %1$s</string>
<string name="flatpakmgr_removing">Mengalih keluar %1$s</string>
+1 -1
View File
@@ -1800,7 +1800,7 @@
<string name="flatpakmgr_not_ready">Sett opp Flatpak først</string>
<string name="flatpakmgr_starting">Starter</string>
<string name="flatpakmgr_exit_status">Flatpak avsluttet med status %1$d</string>
<string name="flatpakmgr_wrong_arch">%1$s er bygget for %2$s, men Flatpak-apper her må være bygget for ARM64. Bruk appens AppImage i stedet; DroidDeck kjører x86-AppImages gjennom FEX.</string>
<string name="flatpakmgr_wrong_arch">%1$s er bygget for %2$s, som DroidDeck ikke kan kjøre. Flatpak-apper her trenger en ARM64- eller x86_64-versjon.</string>
<string name="flatpakmgr_installing">Installerer %1$s</string>
<string name="flatpakmgr_updating">Oppdaterer %1$s</string>
<string name="flatpakmgr_removing">Fjerner %1$s</string>
+1 -1
View File
@@ -1800,7 +1800,7 @@
<string name="flatpakmgr_not_ready">Stel eerst Flatpak in</string>
<string name="flatpakmgr_starting">Starten</string>
<string name="flatpakmgr_exit_status">Flatpak is gestopt met status %1$d</string>
<string name="flatpakmgr_wrong_arch">%1$s is gebouwd voor %2$s, maar Flatpak-apps moeten hier voor ARM64 zijn gebouwd. Gebruik de AppImage van de app; DroidDeck draait x86-AppImages via FEX.</string>
<string name="flatpakmgr_wrong_arch">%1$s is gebouwd voor %2$s, wat DroidDeck niet kan draaien. Flatpak-apps hebben hier een ARM64- of x86_64-versie nodig.</string>
<string name="flatpakmgr_installing">%1$s installeren</string>
<string name="flatpakmgr_updating">%1$s updaten</string>
<string name="flatpakmgr_removing">%1$s verwijderen</string>
+1 -1
View File
@@ -1846,7 +1846,7 @@
<string name="flatpakmgr_not_ready">Najpierw skonfiguruj Flatpak</string>
<string name="flatpakmgr_starting">Rozpoczynanie</string>
<string name="flatpakmgr_exit_status">Flatpak zakończył działanie z kodem %1$d</string>
<string name="flatpakmgr_wrong_arch">%1$s zbudowano dla %2$s, a aplikacje Flatpak muszą tu być zbudowane dla ARM64. Użyj AppImage tej aplikacji; DroidDeck uruchamia AppImage x86 przez FEX.</string>
<string name="flatpakmgr_wrong_arch">%1$s zbudowano dla %2$s, czego DroidDeck nie potrafi uruchomić. Aplikacje Flatpak potrzebują tu wersji dla ARM64 lub x86_64.</string>
<string name="flatpakmgr_installing">Instalowanie %1$s</string>
<string name="flatpakmgr_updating">Aktualizowanie %1$s</string>
<string name="flatpakmgr_removing">Usuwanie %1$s</string>
+1 -1
View File
@@ -1815,7 +1815,7 @@
<string name="flatpakmgr_not_ready">Configure primeiro o Flatpak</string>
<string name="flatpakmgr_starting">A iniciar</string>
<string name="flatpakmgr_exit_status">O Flatpak terminou com o código %1$d</string>
<string name="flatpakmgr_wrong_arch">%1$s foi compilado para %2$s, mas aqui as apps Flatpak têm de ser para ARM64. Use antes o AppImage da app; o DroidDeck executa AppImages x86 através do FEX.</string>
<string name="flatpakmgr_wrong_arch">%1$s foi compilado para %2$s, que o DroidDeck não consegue executar. Aqui as apps Flatpak precisam de uma versão para ARM64 ou x86_64.</string>
<string name="flatpakmgr_installing">A instalar %1$s</string>
<string name="flatpakmgr_updating">A atualizar %1$s</string>
<string name="flatpakmgr_removing">A remover %1$s</string>
+1 -1
View File
@@ -1815,7 +1815,7 @@
<string name="flatpakmgr_not_ready">Configure o Flatpak primeiro</string>
<string name="flatpakmgr_starting">Iniciando</string>
<string name="flatpakmgr_exit_status">O Flatpak terminou com o código %1$d</string>
<string name="flatpakmgr_wrong_arch">%1$s foi compilado para %2$s, mas aqui os apps Flatpak precisam ser para ARM64. Use o AppImage do app; o DroidDeck executa AppImages x86 pelo FEX.</string>
<string name="flatpakmgr_wrong_arch">%1$s foi compilado para %2$s, que o DroidDeck não consegue executar. Aqui os apps Flatpak precisam de uma versão para ARM64 ou x86_64.</string>
<string name="flatpakmgr_installing">Instalando %1$s</string>
<string name="flatpakmgr_updating">Atualizando %1$s</string>
<string name="flatpakmgr_removing">Removendo %1$s</string>
+1 -1
View File
@@ -1823,7 +1823,7 @@
<string name="flatpakmgr_not_ready">Configurează mai întâi Flatpak</string>
<string name="flatpakmgr_starting">Se pornește</string>
<string name="flatpakmgr_exit_status">Flatpak s-a închis cu starea %1$d</string>
<string name="flatpakmgr_wrong_arch">%1$s este construită pentru %2$s, dar aici aplicațiile Flatpak trebuie să fie pentru ARM64. Folosește în schimb AppImage-ul aplicației; DroidDeck rulează AppImage-urile x86 prin FEX.</string>
<string name="flatpakmgr_wrong_arch">%1$s este construită pentru %2$s, pe care DroidDeck nu o poate rula. Aici aplicațiile Flatpak au nevoie de o versiune pentru ARM64 sau x86_64.</string>
<string name="flatpakmgr_installing">Se instalează %1$s</string>
<string name="flatpakmgr_updating">Se actualizează %1$s</string>
<string name="flatpakmgr_removing">Se elimină %1$s</string>
+1 -1
View File
@@ -1846,7 +1846,7 @@
<string name="flatpakmgr_not_ready">Сначала настройте Flatpak</string>
<string name="flatpakmgr_starting">Запуск</string>
<string name="flatpakmgr_exit_status">Flatpak завершился с кодом %1$d</string>
<string name="flatpakmgr_wrong_arch">%1$s собрано для %2$s, а приложения Flatpak здесь должны быть собраны для ARM64. Используйте AppImage этого приложения: DroidDeck запускает x86 AppImage через FEX.</string>
<string name="flatpakmgr_wrong_arch">%1$s собрано для %2$s, а DroidDeck не может это запустить. Приложениям Flatpak здесь нужна сборка для ARM64 или x86_64.</string>
<string name="flatpakmgr_installing">Установка %1$s</string>
<string name="flatpakmgr_updating">Обновление %1$s</string>
<string name="flatpakmgr_removing">Удаление %1$s</string>
+1 -1
View File
@@ -1800,7 +1800,7 @@
<string name="flatpakmgr_not_ready">Konfigurera Flatpak först</string>
<string name="flatpakmgr_starting">Startar</string>
<string name="flatpakmgr_exit_status">Flatpak avslutades med status %1$d</string>
<string name="flatpakmgr_wrong_arch">%1$s är byggd för %2$s, men Flatpak-appar här måste vara byggda för ARM64. Använd appens AppImage i stället; DroidDeck kör x86-AppImages via FEX.</string>
<string name="flatpakmgr_wrong_arch">%1$s är byggd för %2$s, som DroidDeck inte kan köra. Flatpak-appar här behöver en ARM64- eller x86_64-version.</string>
<string name="flatpakmgr_installing">Installerar %1$s</string>
<string name="flatpakmgr_updating">Uppdaterar %1$s</string>
<string name="flatpakmgr_removing">Tar bort %1$s</string>
+1 -1
View File
@@ -1777,7 +1777,7 @@
<string name="flatpakmgr_not_ready">ตั้งค่า Flatpak ก่อน</string>
<string name="flatpakmgr_starting">กำลังเริ่ม</string>
<string name="flatpakmgr_exit_status">Flatpak ออกด้วยสถานะ %1$d</string>
<string name="flatpakmgr_wrong_arch">%1$s สร้างมาสำหรับ %2$s แต่แอป Flatpak ที่นี่ต้องสร้างสำหรับ ARM64 ให้ใช้ AppImage ของแอปแทน DroidDeck จะเรียกใช้ AppImage x86 ผ่าน FEX</string>
<string name="flatpakmgr_wrong_arch">%1$s สร้างมาสำหรับ %2$s ซึ่ง DroidDeck เรียกใช้ไม่ได้ แอป Flatpak ที่นี่ต้องเป็นเวอร์ชัน ARM64 หรือ x86_64</string>
<string name="flatpakmgr_installing">กำลังติดตั้ง %1$s</string>
<string name="flatpakmgr_updating">กำลังอัปเดต %1$s</string>
<string name="flatpakmgr_removing">กำลังลบ %1$s</string>
+1 -1
View File
@@ -1792,7 +1792,7 @@
<string name="flatpakmgr_not_ready">Önce Flatpak\'i kurun</string>
<string name="flatpakmgr_starting">Başlatılıyor</string>
<string name="flatpakmgr_exit_status">Flatpak %1$d durum koduyla çıktı</string>
<string name="flatpakmgr_wrong_arch">%1$s %2$s için derlenmiş, ancak buradaki Flatpak uygulamaları ARM64 için derlenmiş olmalı. Bunun yerine uygulamanın AppImage dosyasını kullanın; DroidDeck x86 AppImage dosyalarını FEX ile çalıştırır.</string>
<string name="flatpakmgr_wrong_arch">%1$s, DroidDeck\'in çalıştıramadığı %2$s için derlenmiş. Buradaki Flatpak uygulamalarının ARM64 veya x86_64 sürümü olmalı.</string>
<string name="flatpakmgr_installing">%1$s yükleniyor</string>
<string name="flatpakmgr_updating">%1$s güncelleniyor</string>
<string name="flatpakmgr_removing">%1$s kaldırılıyor</string>
+1 -1
View File
@@ -1846,7 +1846,7 @@
<string name="flatpakmgr_not_ready">Спершу налаштуйте Flatpak</string>
<string name="flatpakmgr_starting">Запуск</string>
<string name="flatpakmgr_exit_status">Flatpak завершився з кодом %1$d</string>
<string name="flatpakmgr_wrong_arch">%1$s зібрано для %2$s, а застосунки Flatpak тут мають бути зібрані для ARM64. Використайте AppImage цього застосунку: DroidDeck запускає x86 AppImage через FEX.</string>
<string name="flatpakmgr_wrong_arch">%1$s зібрано для %2$s, а DroidDeck не може це запустити. Застосункам Flatpak тут потрібна збірка для ARM64 або x86_64.</string>
<string name="flatpakmgr_installing">Встановлення %1$s</string>
<string name="flatpakmgr_updating">Оновлення %1$s</string>
<string name="flatpakmgr_removing">Видалення %1$s</string>
+1 -1
View File
@@ -1777,7 +1777,7 @@
<string name="flatpakmgr_not_ready">Thiết lập Flatpak trước</string>
<string name="flatpakmgr_starting">Đang khởi động</string>
<string name="flatpakmgr_exit_status">Flatpak đã thoát với mã %1$d</string>
<string name="flatpakmgr_wrong_arch">%1$s được dựng cho %2$s, nhưng ứng dụng Flatpak ở đây phải dựng cho ARM64. Hãy dùng bản AppImage của ứng dụng; DroidDeck chạy AppImage x86 qua FEX.</string>
<string name="flatpakmgr_wrong_arch">%1$s được dựng cho %2$s, DroidDeck không chạy được. Ứng dụng Flatpak ở đây cần bản ARM64 hoặc x86_64.</string>
<string name="flatpakmgr_installing">Đang cài %1$s</string>
<string name="flatpakmgr_updating">Đang cập nhật %1$s</string>
<string name="flatpakmgr_removing">Đang gỡ %1$s</string>
+1 -1
View File
@@ -1825,7 +1825,7 @@
<string name="flatpakmgr_not_ready">请先配置 Flatpak</string>
<string name="flatpakmgr_starting">启动中</string>
<string name="flatpakmgr_exit_status">Flatpak 已退出,状态码 %1$d</string>
<string name="flatpakmgr_wrong_arch">%1$s 是为 %2$s 构建的,但这里的 Flatpak 应用必须为 ARM64 构建。请改用该应用的 AppImage;DroidDeck 会通过 FEX 运行 x86 AppImage。</string>
<string name="flatpakmgr_wrong_arch">%1$s 是为 %2$s 构建的,DroidDeck 无法运行。这里的 Flatpak 应用需要 ARM64 或 x86_64 版本。</string>
<string name="flatpakmgr_installing">正在安装 %1$s</string>
<string name="flatpakmgr_updating">正在更新 %1$s</string>
<string name="flatpakmgr_removing">正在移除 %1$s</string>
+1 -1
View File
@@ -1748,7 +1748,7 @@
<string name="flatpakmgr_not_ready">請先設定 Flatpak</string>
<string name="flatpakmgr_starting">正在開始</string>
<string name="flatpakmgr_exit_status">Flatpak 已結束,狀態為 %1$d</string>
<string name="flatpakmgr_wrong_arch">%1$s 是為 %2$s 建置的,但這裡的 Flatpak 應用程式必須為 ARM64 建置。請改用該應用程式的 AppImage;DroidDeck 會透過 FEX 執行 x86 AppImage。</string>
<string name="flatpakmgr_wrong_arch">%1$s 是為 %2$s 建置的,DroidDeck 無法執行。這裡的 Flatpak 應用程式需要 ARM64 或 x86_64 版本。</string>
<string name="flatpakmgr_installing">正在安裝 %1$s</string>
<string name="flatpakmgr_updating">正在更新 %1$s</string>
<string name="flatpakmgr_removing">正在移除 %1$s</string>
+1 -1
View File
@@ -1748,7 +1748,7 @@
<string name="flatpakmgr_not_ready">請先設定 Flatpak</string>
<string name="flatpakmgr_starting">正在啟動</string>
<string name="flatpakmgr_exit_status">Flatpak 已結束,狀態碼 %1$d</string>
<string name="flatpakmgr_wrong_arch">%1$s 是為 %2$s 建置的,但這裡的 Flatpak 應用程式必須為 ARM64 建置。請改用該應用程式的 AppImage;DroidDeck 會透過 FEX 執行 x86 AppImage。</string>
<string name="flatpakmgr_wrong_arch">%1$s 是為 %2$s 建置的,DroidDeck 無法執行。這裡的 Flatpak 應用程式需要 ARM64 或 x86_64 版本。</string>
<string name="flatpakmgr_installing">正在安裝 %1$s</string>
<string name="flatpakmgr_updating">正在更新 %1$s</string>
<string name="flatpakmgr_removing">正在移除 %1$s</string>
+1 -1
View File
@@ -1904,7 +1904,7 @@
<string name="flatpakmgr_not_ready">Set up Flatpak first</string>
<string name="flatpakmgr_starting">Starting</string>
<string name="flatpakmgr_exit_status">Flatpak exited with status %1$d</string>
<string name="flatpakmgr_wrong_arch">%1$s is built for %2$s, but Flatpak apps here must be built for ARM64. Use the app\'s AppImage instead; DroidDeck runs x86 AppImages through FEX.</string>
<string name="flatpakmgr_wrong_arch">%1$s is built for %2$s, which DroidDeck can\'t run. Flatpak apps here need an ARM64 or x86_64 build.</string>
<string name="flatpakmgr_installing">Installing %1$s</string>
<string name="flatpakmgr_updating">Updating %1$s</string>
<string name="flatpakmgr_removing">Removing %1$s</string>
@@ -119,6 +119,27 @@ class BwrapSpawnerPlanTest {
assertEquals("org.example.Game", plan.env["FLATPAK_ID"])
}
@Test fun sandboxesStartThroughTheirOwnEnvByDefault() {
val plan = BwrapSpawner.plan(RuntimeEnvironment.getApplication(), request(op("t" to "dir", "dest" to "/tmp")), dir)
assertEquals("/usr/bin/env", plan.envTool)
}
@Test fun fexSandboxesStartThroughTheEnvTheyBringAndSkipArmPreloads() {
val context = RuntimeEnvironment.getApplication()
val rootfs = LinuxRuntime.rootDir(context)
val lib = File(rootfs, "usr/local/lib/libblsession.so").apply { parentFile?.mkdirs(); writeText("") }
val preload = File(rootfs, "etc/ld.so.preload").apply { parentFile?.mkdirs(); writeText("/usr/local/lib/libblsession.so\n") }
try {
val plan = BwrapSpawner.plan(context, request(op("t" to "dir", "dest" to "/tmp"))
.put("envTool", "/run/droiddeck-fex/bin/env"), dir)
assertEquals("/run/droiddeck-fex/bin/env", plan.envTool)
assertFalse(plan.env.containsKey("LD_PRELOAD"))
assertFalse(plan.binds.any { it.guest.startsWith("/run/droiddeck-preload") })
} finally {
listOf(lib, preload).forEach { it.delete() }
}
}
@Test fun sandboxedAppsSeeTheSessionsNetworkLink() {
val context = RuntimeEnvironment.getApplication()
val rootfs = LinuxRuntime.rootDir(context)
@@ -0,0 +1,55 @@
package com.droiddeck.launcher.runtime
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Before
import org.junit.Test
import java.io.File
import java.nio.file.Files
class FlatpakManagerArchTest {
private lateinit var dir: File
@Before fun setUp() {
dir = Files.createTempDirectory("flatpak-arch").toFile()
}
@After fun tearDown() {
dir.deleteRecursively()
}
private fun install(arch: String, branch: String) {
File(dir, "$arch/$branch/active").mkdirs()
}
@Test fun armDevicesAlsoListX86Builds() {
assertEquals(listOf("aarch64", "x86_64"), FlatpakManager.arches("aarch64"))
assertEquals(listOf("x86_64"), FlatpakManager.arches("x86_64"))
}
@Test fun anX86OnlyInstallIsFound() {
install("x86_64", "master")
val (arch, branch) = FlatpakManager.installedBranch(dir, "aarch64")!!
assertEquals("x86_64", arch)
assertEquals("master", branch.name)
}
@Test fun theNativeBuildWinsWhenBothAreInstalled() {
install("x86_64", "master")
install("aarch64", "stable")
assertEquals("aarch64", FlatpakManager.installedBranch(dir, "aarch64")!!.first)
}
@Test fun aBranchWithoutAnActiveDeployIsNotInstalled() {
File(dir, "x86_64/master").mkdirs()
assertNull(FlatpakManager.installedBranch(dir, "aarch64"))
}
@Test fun x86BuildsOnArmLaunchWithTheirArch() {
val x86 = FlatpakManager.App("io.github.ebkr.r2modman", "r2modman", null, "master", null, "x86_64")
val arm = FlatpakManager.App("org.example.App", "App", null, "stable", null, "aarch64")
assertEquals(listOf("--arch=x86_64", "io.github.ebkr.r2modman"), FlatpakManager.launchArgs(x86, "aarch64"))
assertEquals(listOf("org.example.App"), FlatpakManager.launchArgs(arm, "aarch64"))
}
}
@@ -6,6 +6,7 @@ import os
import signal
import socket
import struct
import subprocess
import sys
SOCKET_FILE = "/etc/droiddeck/bwrap-socket"
@@ -23,6 +24,11 @@ def socket_name():
SOCKET = socket_name()
FEX_DIR = "/run/droiddeck-fex"
OWN_FEX = "/usr/local"
X86_MACHINES = (3, 62)
ARM_LIBS = "/usr/lib/aarch64-linux-gnu"
ARM_LOADER = "/usr/lib/ld-linux-aarch64.so.1"
VERSION = "bubblewrap 0.11.0 (droiddeck)"
NO_ARG = {
@@ -91,6 +97,7 @@ class Spec:
self.lock_files = []
self.command = []
self.argv0 = None
self.env_tool = None
def parse(argv):
@@ -217,6 +224,134 @@ def host_path(s, path):
return path
def sandbox_link(s, path):
for op in reversed(s.ops):
if op.get("dest", "").rstrip("/") != path:
continue
if op["t"] == "symlink":
return op["target"]
if op["t"] == "bind":
return None
break
here = sandbox_source(s, path)
return os.readlink(here) if os.path.islink(here) else None
def sandbox_source(s, path):
for op in reversed(s.ops):
if op["t"] != "bind":
continue
dest = op["dest"].rstrip("/")
if path == dest or path.startswith(dest + "/"):
return real(op["src"]).rstrip("/") + path[len(dest):]
return path
def sandbox_file(s, path):
parts = [p for p in path.split("/") if p]
done = "/"
hops = 0
i = 0
while i < len(parts):
step = os.path.join(done, parts[i])
link = sandbox_link(s, step)
if link is None:
done = step
i += 1
continue
hops += 1
if hops > 40:
return None
base = "/" if link.startswith("/") else done
parts = [p for p in os.path.normpath(os.path.join(base, link)).split("/") if p] + parts[i + 1:]
done = "/"
i = 0
here = sandbox_source(s, done)
return here if os.path.isfile(here) else None
def program_path(s, program):
if "/" in program:
return os.path.normpath(os.path.join(s.cwd or "/", program))
for folder in s.env.get("PATH", "/usr/bin:/bin").split(":"):
if not folder:
continue
candidate = os.path.normpath(os.path.join(s.cwd or "/", folder, program))
if sandbox_file(s, candidate):
return candidate
return None
def machine(s, path, depth=0):
here = sandbox_file(s, path)
if here is None:
return None
try:
with open(here, "rb") as f:
head = f.read(256)
except OSError:
return None
if head[:4] == b"\x7fELF" and len(head) >= 20:
return struct.unpack("<H" if head[5] == 1 else ">H", head[18:20])[0]
if head[:2] == b"#!" and depth < 4:
words = head[2:].split(b"\n")[0].split()
if words:
return machine(s, words[0].decode("utf-8", "replace"), depth + 1)
return None
def own_fex():
return all(os.access(os.path.join(OWN_FEX, "bin", name), os.X_OK) for name in ("FEX", "FEXServer")) \
and os.path.isfile(os.path.join(OWN_FEX, "lib", "libxxhash.so.0"))
def fex_ready():
if own_fex():
return True
try:
subprocess.call([os.path.join(OWN_FEX, "bin", "droiddeck-fex-update")], stdout=sys.stderr)
except OSError:
return False
return own_fex()
def start_fex_server():
try:
subprocess.Popen([os.path.join(OWN_FEX, "bin", "FEXServer"), "-p", "30"], stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, start_new_session=True)
except OSError as e:
sys.stderr.write("bwrap: could not start FEXServer: %s\n" % e)
def through_fex(s):
program = program_path(s, s.command[0])
if program is None or machine(s, program) not in X86_MACHINES:
return False
if not fex_ready():
sys.stderr.write("bwrap: %s is an x86 program and FEX is not installed\n" % program)
return False
for src, dest in ((os.path.join(OWN_FEX, "bin", "FEX"), FEX_DIR + "/bin/FEX"),
(os.path.join(OWN_FEX, "bin", "FEXServer"), FEX_DIR + "/bin/FEXServer"),
(os.path.join(OWN_FEX, "lib", "libxxhash.so.0"), FEX_DIR + "/lib/libxxhash.so.0"),
("/usr/bin/env", FEX_DIR + "/bin/env"),
(ARM_LIBS, ARM_LIBS),
(real(ARM_LOADER), ARM_LOADER)):
s.ops.append({"t": "bind", "src": src, "dest": dest, "try": False, "ro": True})
data = s.env.get("XDG_CACHE_HOME") or "/tmp"
s.env["FEX_APP_CONFIG_LOCATION"] = FEX_DIR + "/config/"
s.env["FEX_APP_DATA_LOCATION"] = data + "/droiddeck-fex/"
s.env["FEX_APP_CACHE_LOCATION"] = data + "/droiddeck-fex/cache/"
for name in ("FEX_ROOTFS", "FEX_THUNKHOSTLIBS", "FEX_THUNKGUESTLIBS", "FEX_THUNKCONFIG", "FEX_PORTABLE",
"GLIBC_TUNABLES", "MESA_LOADER_DRIVER_OVERRIDE", "GALLIUM_DRIVER", "VK_ICD_FILENAMES",
"VK_DRIVER_FILES"):
s.env.pop(name, None)
s.command = [FEX_DIR + "/bin/FEX", program] + s.command[1:]
s.argv0 = None
s.env_tool = FEX_DIR + "/bin/env"
start_fex_server()
return True
def hold_locks(s):
for path in s.lock_files:
try:
@@ -290,6 +425,8 @@ def run_by_app(s):
if s.argv0:
argv[0] = s.argv0
request = {"argv": argv, "env": s.env, "cwd": s.cwd or "/", "ops": ops}
if s.env_tool:
request["envTool"] = s.env_tool
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
try:
sock.connect(SOCKET)
@@ -333,8 +470,9 @@ def main():
hold_locks(s)
if remaps_nothing(s):
run_here(s)
else:
run_by_app(s)
return
through_fex(s)
run_by_app(s)
if __name__ == "__main__":
@@ -109,21 +109,31 @@ def run(t, state, report_done=True):
return 0
def arches():
native = Flatpak.get_default_arch()
return [native, "x86_64"] if native == "aarch64" else [native]
def install(app_id):
inst = installation()
arch = Flatpak.get_default_arch()
if app_ref(inst, app_id) is not None:
emit(e="done", note="already installed")
return 0
branch, last = None, None
for candidate in ("stable", "master"):
try:
branch = inst.fetch_remote_ref_sync(REMOTE, Flatpak.RefKind.APP, app_id, arch, candidate, None).get_branch()
arch, branch, last = None, None, None
for candidate_arch in arches():
for candidate in ("stable", "master"):
try:
branch = inst.fetch_remote_ref_sync(REMOTE, Flatpak.RefKind.APP, app_id, candidate_arch, candidate,
None).get_branch()
arch = candidate_arch
break
except GLib.Error as err:
last = err
if branch is not None:
break
except GLib.Error as err:
last = err
if branch is None:
emit(e="error", message="%s is not on Flathub for %s (%s)" % (app_id, arch, last.message if last else "no branch"))
emit(e="error", message="%s is not on Flathub for %s (%s)" % (app_id, " or ".join(arches()),
last.message if last else "no branch"))
return 1
t, state = transaction(inst)
t.add_install(REMOTE, "app/%s/%s/%s" % (app_id, arch, branch), None)
@@ -146,9 +156,9 @@ def install_bundle(path):
except GLib.Error as err:
emit(e="error", message="%s is not a Flatpak bundle (%s)" % (os.path.basename(path), err.message))
return 1
if ref.get_arch() != Flatpak.get_default_arch():
if ref.get_arch() not in arches():
emit(e="error", code="arch", id=app_id, arch=ref.get_arch(),
message="%s is built for %s, not %s" % (app_id, ref.get_arch(), Flatpak.get_default_arch()))
message="%s is built for %s, not %s" % (app_id, ref.get_arch(), " or ".join(arches())))
return 1
if app_ref(inst, app_id) is not None:
emit(e="done", id=app_id, note="already installed")
+5
View File
@@ -127,6 +127,11 @@ Prototype (inert unless `PROOT_FASTPATH` is set; see `docs/development/proot-per
IDs wrap (32768 on many Android kernels) a new child can get that ID, and PRoot aborted on the
assertion that a new child has no state, ending the whole session. Chromium-based programs such as
Steam's client do this. PRoot now retires the stale entry and sets the new child up from scratch.
- `0024-execveat-absolute-path.patch` - `execveat` with an absolute path and neither `AT_EMPTY_PATH`
nor `AT_SYMLINK_NOFOLLOW` runs as `execve`. The kernel ignores the directory descriptor for such a
path, and PRoot refused the call unless the descriptor was `AT_FDCWD`. FEX starts every child of an
emulated program with `execveat(fd, "/proc/self/exe")`, so x86 programs in a Flatpak sandbox, which
does not carry the session's preloads, could not start other programs.
Old kernel fixes:
@@ -0,0 +1,44 @@
From 968f450754f97907d72a756add5010f6be746b6a Mon Sep 17 00:00:00 2001
From: maxjivi05 <maxjividen@gmail.com>
Date: Fri, 30 Oct 2026 20:21:28 -0400
Subject: [PATCH] syscall: run an execveat with an absolute path like execve
The kernel ignores the directory descriptor of execveat when the path is
absolute, so such a call is an execve with flags. FEX runs every child
of an emulated program through execveat(fd, "/proc/self/exe"), which
proot refused unless the descriptor was AT_FDCWD.
---
src/syscall/enter.c | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/src/syscall/enter.c b/src/syscall/enter.c
index f8ce2e7..98241bd 100644
--- a/src/syscall/enter.c
+++ b/src/syscall/enter.c
@@ -129,8 +129,13 @@ int translate_syscall_enter(Tracee *tracee)
status = translate_execve_enter(tracee);
break;
- case PR_execveat:
- if ((int) peek_reg(tracee, CURRENT, SYSARG_1) == AT_FDCWD) {
+ case PR_execveat: {
+ word_t flags = peek_reg(tracee, CURRENT, SYSARG_5);
+ char first = '\0';
+ bool absolute = (flags & (AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW)) == 0
+ && read_data(tracee, &first, peek_reg(tracee, CURRENT, SYSARG_2), 1) == 0
+ && first == '/';
+ if ((int) peek_reg(tracee, CURRENT, SYSARG_1) == AT_FDCWD || absolute) {
set_sysnum(tracee, PR_execve);
poke_reg(tracee, SYSARG_1, peek_reg(tracee, CURRENT, SYSARG_2));
poke_reg(tracee, SYSARG_2, peek_reg(tracee, CURRENT, SYSARG_3));
@@ -142,6 +147,7 @@ int translate_syscall_enter(Tracee *tracee)
}
status = translate_execve_enter(tracee);
break;
+ }
case PR_ptrace:
status = translate_ptrace_enter(tracee);
--
2.43.0
+98
View File
@@ -0,0 +1,98 @@
import os
import runpy
import shutil
import struct
import tempfile
import unittest
from pathlib import Path
BWRAP = Path(__file__).resolve().parents[1] / "linuxfs/overlay/usr/local/bin/droiddeck-bwrap"
def elf(path, machine):
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(b"\x7fELF\x02\x01\x01" + bytes(11) + struct.pack("<H", machine) + bytes(64))
path.chmod(0o755)
class BwrapFexTest(unittest.TestCase):
def setUp(self):
self.dir = Path(tempfile.mkdtemp())
self.addCleanup(shutil.rmtree, self.dir, True)
self.shim = runpy.run_path(str(BWRAP))
self.fex = self.dir / "fex"
for name in ("bin/FEX", "bin/FEXServer", "lib/libxxhash.so.0"):
elf(self.fex / name, 183)
self.shim["through_fex"].__globals__["OWN_FEX"] = str(self.fex)
self.runtime = self.dir / "runtime"
self.app = self.dir / "app"
elf(self.runtime / "bin/ldconfig", 62)
elf(self.runtime / "bin/bash", 62)
os.symlink("bash", self.runtime / "bin/sh")
elf(self.runtime / "bin/arm-tool", 183)
(self.app / "bin").mkdir(parents=True)
(self.app / "bin/r2modman").write_text("#!/bin/sh\nexec /app/r2modman \"$@\"\n")
def spec(self, command, path="/app/bin:/usr/bin"):
s = self.shim["Spec"]()
s.env = {"PATH": path, "XDG_CACHE_HOME": "/home/user/.var/app/x/cache", "FEX_ROOTFS": "/sniper"}
s.ops = [
{"t": "bind", "src": str(self.runtime), "dest": "/usr", "try": False, "ro": True},
{"t": "bind", "src": str(self.app), "dest": "/app", "try": False, "ro": True},
{"t": "symlink", "target": "usr/bin", "dest": "/bin"},
]
s.command = command
return s
def test_an_x86_64_program_found_on_path_runs_through_fex(self):
s = self.spec(["ldconfig", "-X", "-C", "/run/cache"])
self.assertTrue(self.shim["through_fex"](s))
self.assertEqual(["/run/droiddeck-fex/bin/FEX", "/usr/bin/ldconfig", "-X", "-C", "/run/cache"], s.command)
dests = {op["dest"]: op["src"] for op in s.ops if op["t"] == "bind"}
self.assertEqual(str(self.fex / "bin/FEX"), dests["/run/droiddeck-fex/bin/FEX"])
self.assertEqual(str(self.fex / "lib/libxxhash.so.0"), dests["/run/droiddeck-fex/lib/libxxhash.so.0"])
self.assertIn("/usr/lib/aarch64-linux-gnu", dests)
self.assertIn("/usr/lib/ld-linux-aarch64.so.1", dests)
self.assertNotIn("FEX_ROOTFS", s.env)
self.assertEqual("/home/user/.var/app/x/cache/droiddeck-fex/", s.env["FEX_APP_DATA_LOCATION"])
def test_a_script_whose_interpreter_is_x86_64_runs_through_fex(self):
s = self.spec(["r2modman", "--flag"])
self.assertTrue(self.shim["through_fex"](s))
self.assertEqual(["/run/droiddeck-fex/bin/FEX", "/app/bin/r2modman", "--flag"], s.command)
def test_a_program_reached_through_a_sandbox_symlink_is_checked(self):
s = self.spec(["/bin/sh", "-c", "true"])
self.assertTrue(self.shim["through_fex"](s))
self.assertEqual("/bin/sh", s.command[1])
def test_a_bind_whose_source_is_a_symlink_resolves_from_the_source(self):
real = self.dir / "store" / "tool-real"
elf(real, 62)
os.symlink("tool-real", self.dir / "store" / "tool-link")
s = self.spec(["/opt/tool"])
s.ops.append({"t": "bind", "src": str(self.dir / "store" / "tool-link"), "dest": "/opt/tool", "try": False,
"ro": True})
self.assertTrue(self.shim["through_fex"](s))
self.assertEqual("/opt/tool", s.command[1])
def test_a_relative_path_entry_is_searched_from_the_working_directory(self):
s = self.spec(["r2modman"], path="bin")
s.cwd = "/app"
self.assertTrue(self.shim["through_fex"](s))
self.assertEqual("/app/bin/r2modman", s.command[1])
def test_an_arm64_program_runs_as_it_is(self):
s = self.spec(["arm-tool"])
self.assertFalse(self.shim["through_fex"](s))
self.assertEqual(["arm-tool"], s.command)
self.assertEqual(3, len(s.ops))
def test_a_missing_program_is_left_alone(self):
s = self.spec(["nothing-here"])
self.assertFalse(self.shim["through_fex"](s))
self.assertEqual(["nothing-here"], s.command)
if __name__ == "__main__":
unittest.main()
+50 -3
View File
@@ -70,13 +70,13 @@ class FlatpakBundleArchTest(unittest.TestCase):
rc = None
return rc, [json.loads(line) for line in out.getvalue().splitlines()], installs
def test_a_bundle_for_another_architecture_is_refused_before_installing(self):
rc, events, installs = self.run_install("x86_64")
def test_a_bundle_for_an_architecture_fex_cannot_run_is_refused_before_installing(self):
rc, events, installs = self.run_install("riscv64")
self.assertEqual(rc, 1)
self.assertEqual(installs, [])
self.assertEqual(events[0]["e"], "error")
self.assertEqual(events[0]["code"], "arch")
self.assertEqual(events[0]["arch"], "x86_64")
self.assertEqual(events[0]["arch"], "riscv64")
self.assertEqual(events[0]["id"], "io.github.ebkr.r2modman")
def test_a_bundle_for_this_device_goes_on_to_install(self):
@@ -84,6 +84,53 @@ class FlatpakBundleArchTest(unittest.TestCase):
self.assertEqual(installs, ["bundle"])
self.assertEqual(events, [])
def test_an_x86_64_bundle_goes_on_to_install_for_fex(self):
rc, events, installs = self.run_install("x86_64")
self.assertEqual(installs, ["bundle"])
self.assertEqual(events, [])
def test_flathub_falls_back_to_the_x86_64_build(self):
module = load("aarch64", [])
asked = []
class Remote:
def __init__(self, branch):
self.branch = branch
def get_branch(self):
return self.branch
class Installation:
def fetch_remote_ref_sync(self, remote, kind, app_id, arch, branch, cancellable):
asked.append((arch, branch))
if arch == "x86_64" and branch == "stable":
return Remote("stable")
raise RuntimeError("not found")
added = []
class Transaction:
def add_install(self, remote, ref, subpaths):
added.append(ref)
module.Flatpak.RefKind = types.SimpleNamespace(APP=0)
module.installation = lambda: Installation()
installed = []
module.app_ref = lambda inst, app_id: (installed[0] if installed else None)
module.transaction = lambda inst: (Transaction(), {})
def run(t, state, report_done=True):
installed.append(object())
return 0
module.run = run
out = io.StringIO()
with redirect_stdout(out):
rc = module.install("io.github.ebkr.r2modman")
self.assertEqual(rc, 0)
self.assertEqual(asked, [("aarch64", "stable"), ("aarch64", "master"), ("x86_64", "stable")])
self.assertEqual(added, ["app/io.github.ebkr.r2modman/x86_64/stable"])
if __name__ == "__main__":
unittest.main()