mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 02:51:10 +00:00
Boot Linux games under the FEX compatibility tool again (#1100)
Valve's FEX opens rootfs paths with openat2(RESOLVE_IN_ROOT), which the preload emulates under proot. droiddeck-fex's rootfs links to the Steam runtimes with absolute paths outside the rootfs, so every lookup missed and FEX fell back to the arm64 host files: x86 bash, and with it Stardew Valley's launcher, failed on libdl.so.2. An absolute link whose target is missing in the root but present on the host now finishes there.
This commit is contained in:
1 parent
1873a9aee7
commit
2016a363f2
2 files changed
+47
-10
No files matched your search
@@ -208,6 +208,19 @@ static long openat_in_root(int dirfd, const char *path, int flags, long mode) {
|
||||
}
|
||||
memmove(pending + target_len, tail, rest_len + 1);
|
||||
if (pending[0] == '/') {
|
||||
char saved = pending[target_len];
|
||||
pending[target_len] = 0;
|
||||
const char *inside = pending;
|
||||
while (*inside == '/') {
|
||||
inside++;
|
||||
}
|
||||
bl_stat probe;
|
||||
int host = *inside && real_syscall(BL_SYS_FSTATAT, (long)dirfd, (long)inside, (long)&probe, (long)AT_SYMLINK_NOFOLLOW) != 0 &&
|
||||
errno == ENOENT && real_syscall(BL_SYS_FSTATAT, (long)AT_FDCWD, (long)pending, (long)&probe, 0L) == 0;
|
||||
pending[target_len] = saved;
|
||||
if (host) {
|
||||
return real_syscall(SYS_openat, (long)AT_FDCWD, (long)pending, (long)flags, mode);
|
||||
}
|
||||
resolved_len = 0;
|
||||
}
|
||||
rest = pending;
|
||||
|
||||
@@ -117,8 +117,17 @@ CASES = [
|
||||
("o", os.O_RDONLY, "proc/self/status", "proc"),
|
||||
]
|
||||
|
||||
# Only the preload: an absolute link to a path missing in the tree but present outside it, as droiddeck-fex's rootfs has.
|
||||
HOST_CASES = [
|
||||
("r", os.O_RDONLY, "lib/x86_64-linux-gnu/libbar.so.2", "host:libbar.so.2"),
|
||||
("r", os.O_RDONLY, "hostdir/sub/data", "host:sub/data"),
|
||||
("r", os.O_RDONLY | os.O_DIRECTORY, "hostdir/sub", "host:sub"),
|
||||
("r", os.O_RDONLY, "etc/alternatives/hn", "etc/hostname"),
|
||||
("r", os.O_RDONLY, "hostdir/missing", "ENOENT"),
|
||||
]
|
||||
|
||||
def make_tree(root):
|
||||
|
||||
def make_tree(root, outside):
|
||||
for folder in ("usr/lib/x86_64-linux-gnu", "etc/alternatives", "loop", "chain"):
|
||||
(root / folder).mkdir(parents=True)
|
||||
(root / "usr/lib/x86_64-linux-gnu/libfoo.so.1.2").write_text("x86")
|
||||
@@ -129,16 +138,24 @@ def make_tree(root):
|
||||
"etc/alternatives/hn": "/etc/hostname", "etc/escape": "../../../../../../etc/hostname", "loop/a": "b",
|
||||
"loop/b": "a", "dirlink": "/usr", "rootlink": "/", "dangling": "/usr/newfile"}
|
||||
links.update({"chain/l%d" % i: ("l%d" % (i + 1) if i < 41 else "end") for i in range(1, 42)})
|
||||
links.update({"usr/lib/x86_64-linux-gnu/libbar.so.2": str(outside / "libbar.so.2"), "hostdir": str(outside)})
|
||||
for link, target in links.items():
|
||||
os.symlink(target, root / link)
|
||||
|
||||
|
||||
def labels(lines, root):
|
||||
def make_outside(outside):
|
||||
(outside / "sub").mkdir(parents=True)
|
||||
(outside / "libbar.so.2").write_text("host")
|
||||
(outside / "sub/data").write_text("host")
|
||||
|
||||
|
||||
def labels(lines, root, outside):
|
||||
inodes = {(os.lstat(root).st_dev, os.lstat(root).st_ino): "."}
|
||||
for folder, dirs, files in os.walk(root):
|
||||
for name in dirs + files:
|
||||
st = os.lstat(os.path.join(folder, name))
|
||||
inodes[(st.st_dev, st.st_ino)] = os.path.relpath(os.path.join(folder, name), root)
|
||||
for base, prefix in ((root, ""), (outside, "host:")):
|
||||
for folder, dirs, files in os.walk(base):
|
||||
for name in dirs + files:
|
||||
st = os.lstat(os.path.join(folder, name))
|
||||
inodes[(st.st_dev, st.st_ino)] = prefix + os.path.relpath(os.path.join(folder, name), base)
|
||||
out = []
|
||||
for line in lines:
|
||||
kind, *values = line.split()
|
||||
@@ -161,16 +178,19 @@ class Openat2PreloadTest(unittest.TestCase):
|
||||
source.write_text(PROGRAM)
|
||||
subprocess.run(["cc", "-O2", "-Wall", "-Werror", "-o", str(cls.tmp / "openat2"), str(source), "-ldl"], check=True)
|
||||
cls.trees = (cls.tmp / "kernel", cls.tmp / "preload")
|
||||
cls.outside = cls.tmp / "outside"
|
||||
make_outside(cls.outside)
|
||||
for tree in cls.trees:
|
||||
make_tree(tree)
|
||||
cases = "".join("%s\t%d\t%s\n" % case[:3] for case in CASES)
|
||||
make_tree(tree, cls.outside)
|
||||
every = CASES + HOST_CASES
|
||||
cases = "".join("%s\t%d\t%s\n" % case[:3] for case in every)
|
||||
result = subprocess.run([str(cls.tmp / "openat2"), *map(str, cls.trees)], input=cases, capture_output=True, text=True,
|
||||
env=dict(os.environ, LD_PRELOAD=str(cls.lib)), timeout=60)
|
||||
if result.returncode != 0:
|
||||
raise AssertionError(result.stdout + result.stderr)
|
||||
lines = result.stdout.splitlines()
|
||||
cls.kernel = labels(lines[1:1 + len(CASES)], cls.trees[0]) if lines[0] == "kernel" else None
|
||||
cls.preload = labels(lines[-len(CASES):], cls.trees[1])
|
||||
cls.kernel = labels(lines[1:1 + len(CASES)], cls.trees[0], cls.outside) if lines[0] == "kernel" else None
|
||||
cls.preload = labels(lines[-len(every):], cls.trees[1], cls.outside)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
@@ -181,6 +201,10 @@ class Openat2PreloadTest(unittest.TestCase):
|
||||
self.assertEqual(got, case[3], case)
|
||||
self.assertTrue((self.trees[1] / "usr/newfile").exists())
|
||||
|
||||
def test_preload_finishes_a_link_to_a_path_only_outside_the_root_there(self):
|
||||
for case, got in zip(HOST_CASES, self.preload[len(CASES):]):
|
||||
self.assertEqual(got, case[3], case)
|
||||
|
||||
def test_the_kernel_agrees(self):
|
||||
if self.kernel is None:
|
||||
self.skipTest("this kernel or sandbox has no openat2")
|
||||
|
||||
Reference in new issue
Block a user