Files
DroidDeck/tools/linuxfs/preload/perms.c
T
The412Banner 65f5d0e45f Optional x86-64 Steam client under FEX, brought up to date with main
The x86-64 Steam client branch (VAC-secured servers) and its VAC diagnostics,
replayed onto current main as one commit. Originals, in order (kept at
refs/backup/20261007/x86-64-steam-client-before-rebase):
  c8f4441 Optional x86-64 Steam client under FEX (VAC-secured servers)
  43fcfe6 x86-64 client: pin the x86 rootfs image's sha256
  25ca971 x86-64 client: the arm64 client's Proton tools and one shared library
  020a0b7 x86-64 client: arm64 Proton wrappers named *a64*
  0e11894 x86-64 client: arm64 game overlay for arm Protons, GE-Proton by default
  a0441ce x86-64 client: the arm64 web helper, and the client on the GPU
  6811f5d x86-64 client: the arm64 client's session steps, startup movie, Decky
  faa5803 x86-64 client: the library beat and the shortcuts after exit
  95470a0 x86-64 client: Deck mode by default
  2fa4bf2 x86-64 client: install the arm64 client's parts first
  0e1cdcd x86-64 client: redirect the web helper's exec instead of editing Steam
  c3401cc x86-64 client: SteamRT3 opt-in marker, and the arm64 parts on its build
  99e92da bannerlator-steam-install: mark the arm64 client's programs executable
  209a0ff bannerlator-steam-install --sync: mark the programs executable when already current too
  d1074a5 x86-64 client: start signed in with the arm64 client's account

Adjusted for what main changed since the branch last synced:
- Scripts follow main's droiddeck-* names: droiddeck-steam-x64,
  droiddeck-x64-arm-proton and droiddeck-x64-arm-webhelper, and every call
  into the renamed session scripts.
- bannerlator-steam-compat is gone; its additions move to
  steam-compatibility: the VAC diagnostics hook (-condebug for Source titles)
  in both launcher templates, and an --x64 mode (x64_repoint) that points the
  x86-64 client's Valve Proton entries at its arm64 wrapper. The arm64
  client keeps main's mapping, including its handling of titles installed
  while the client runs.
- The x86 libblsession and libfakeinput come from main's
  build-x86-preloads.sh (staged under /usr/local/lib/droiddeck-fex); the
  branch's own host builds are dropped. PRELOAD_CACHE_VERSION v6.
- droiddeck-steam-install keeps main's repair and channel handling next to
  --sync, and the system bus config moves to /usr/local/share/droiddeck.
- The tracer-hiding caller check sits beside main's NTSync open in opens.c.
- tools/tests/test_steam_compat_x64.py covers the --x64 mapping.

Still opt-in (Steam settings > Client) and not device-tested on this base.
2026-10-07 06:37:58 -04:00

83 lines
3.3 KiB
C

/*
* Root's way past a read-only file, for the Steam client.
*
* The client runs as "root" inside the session - the rootfs names the app's own uid root, and
* proot (-i) shows it that uid - but the kernel treats it as the ordinary user it is. The client
* leaves many content files read-only (r-x------,
* ~290 files of Proton Experimental (ARM64) alone), and when an update later rewrites one of them
* it opens the file for writing expecting root's permission to override the mode. The kernel
* refuses (EACCES), the client reports "Missing file permissions" and cancels the update
* (2026-09-26: Proton Experimental (ARM64), "CFileWriter: errno: 13 ... wineopenxr.dll").
*
* So for the client's own processes, a refused write-open of a file this user owns is answered the
* way root's would be: the owner's write bit is added - to the file, or to the directory a new
* file is being created in - and the open is tried once more. chmod only succeeds on what the app
* owns, so nothing it could not already change is touched. Games, Wine and everything else keep
* ordinary permissions: only the processes started from the client's own directories take this.
*/
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
static int in_steam_dirs(const char *path) {
return path != NULL && (strstr(path, "/Steam/steamrtarm64/") != NULL || strstr(path, "/Steam/steamrt64/") != NULL
|| strstr(path, "/Steam/ubuntu12_64/") != NULL || strstr(path, "/Steam/linuxarm64/") != NULL);
}
/*
* The client and its helpers run from the Steam root's platform directories. The kernel's
* /proc/self/exe names proot's loader for every process in the session, so the program's own
* name - the path it was started by - is asked as well.
*/
static int is_steam_client(void) {
static int cached = -1;
char exe[PATH_MAX];
ssize_t n;
if (cached >= 0) return cached;
n = readlink("/proc/self/exe", exe, sizeof(exe) - 1);
if (n > 0) exe[n] = '\0';
cached = (n > 0 && in_steam_dirs(exe)) || in_steam_dirs(program_invocation_name);
return cached;
}
static int grant_owner_write(int dirfd, const char *path) {
struct stat st;
if (fstatat(dirfd, path, &st, 0) != 0 || (st.st_mode & S_IWUSR)) return 0;
return fchmodat(dirfd, path, (st.st_mode & 07777) | S_IWUSR, 0) == 0;
}
/*
* After an open refused with EACCES: returns 1 when a write bit was added and the open is worth
* one more try. errno is left as the open set it.
*/
__attribute__((visibility("hidden")))
int bl_writable_retry(int dirfd, const char *path, int flags) {
int saved = errno;
int granted = 0;
char parent[PATH_MAX];
const char *slash;
if (saved != EACCES || path == NULL || (flags & O_ACCMODE) == O_RDONLY || !is_steam_client()) return 0;
if (faccessat(dirfd, path, F_OK, 0) == 0) {
granted = grant_owner_write(dirfd, path);
} else if (flags & O_CREAT) {
/* A new file in a read-only directory. */
slash = strrchr(path, '/');
if (slash == NULL) {
granted = grant_owner_write(dirfd, ".");
} else if ((size_t) (slash - path) < sizeof(parent)) {
memcpy(parent, path, slash - path);
parent[slash - path] = '\0';
granted = grant_owner_write(dirfd, parent[0] ? parent : "/");
}
}
errno = saved;
return granted;
}