From 076825cc5aef9e2f9b15e1775cc7c2be973d6965 Mon Sep 17 00:00:00 2001 From: Andrew Eikum Date: Fri, 26 Mar 2021 16:04:41 -0500 Subject: [PATCH] Handle steampipe quirks in deploy builds --- Makefile.in | 9 ++-- proton | 27 ++++++++++- steampipe_fixups.py | 116 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 147 insertions(+), 5 deletions(-) create mode 100755 steampipe_fixups.py diff --git a/Makefile.in b/Makefile.in index cc1b53b4..3400a2f8 100644 --- a/Makefile.in +++ b/Makefile.in @@ -190,6 +190,7 @@ COMPAT_MANIFEST_TEMPLATE := $(SRCDIR)/compatibilitytool.vdf.template LICENSE := $(SRCDIR)/dist.LICENSE OFL_LICENSE := $(SRCDIR)/fonts/liberation-fonts/LICENSE AV1_PATENTS := $(SRCDIR)/dav1d/doc/PATENTS +STEAMPIPE_FIXUPS_PY := $(SRCDIR)/steampipe_fixups.py GECKO_VER := 2.47.3 GECKO32_TARBALL := wine-gecko-$(GECKO_VER)-x86.tar.xz @@ -278,9 +279,10 @@ DIST_TARGETS := $(DIST_COPY_TARGETS) $(DIST_OVR32) $(DIST_OVR64) \ $(DIST_COMPAT_MANIFEST) $(DIST_LICENSE) $(DIST_TOOLMANIFEST) \ $(DIST_OFL_LICENSE) $(DIST_AV1_PATENTS) $(DIST_FONTS) -DEPLOY_COPY_TARGETS := $(DIST_COPY_TARGETS) $(DIST_VERSION) $(DIST_LICENSE) \ - $(DIST_TOOLMANIFEST) $(DIST_OFL_LICENSE) $(DIST_AV1_PATENTS) $(DST_DIR) -REDIST_COPY_TARGETS := $(DEPLOY_COPY_TARGETS) $(DIST_COMPAT_MANIFEST) +BASE_COPY_TARGETS := $(DIST_COPY_TARGETS) $(DIST_VERSION) $(DIST_LICENSE) \ + $(DIST_TOOLMANIFEST) $(DIST_OFL_LICENSE) $(DIST_AV1_PATENTS) $(DST_DIR) +DEPLOY_COPY_TARGETS := $(BASE_COPY_TARGETS) $(STEAMPIPE_FIXUPS_PY) +REDIST_COPY_TARGETS := $(BASE_COPY_TARGETS) $(DIST_COMPAT_MANIFEST) $(DIST_LICENSE): $(LICENSE) cp -a $< $@ @@ -377,6 +379,7 @@ dist: $(DIST_TARGETS) all-dist dist_wineopenxr | $(DST_DIR) deploy: dist | $(filter-out dist deploy install redist,$(MAKECMDGOALS)) mkdir -p $(DEPLOY_DIR) cp -af --no-dereference --preserve=mode,links $(DEPLOY_COPY_TARGETS) $(DEPLOY_DIR) + python3 $(STEAMPIPE_FIXUPS_PY) process $(DEPLOY_DIR) install: dist | $(filter-out dist deploy install redist,$(MAKECMDGOALS)) if [ ! -d $(STEAM_DIR) ]; then echo >&2 "!! "$(STEAM_DIR)" does not exist, cannot install"; return 1; fi diff --git a/proton b/proton index b586ed0f..170cb260 100755 --- a/proton +++ b/proton @@ -435,6 +435,28 @@ class Proton: if file_exists(old_dist_dir, follow_symlinks=True): shutil.rmtree(old_dist_dir) + def do_steampipe_fixups(self): + fixups_json = self.path("steampipe_fixups.json") + fixups_mtime = self.path("files/steampipe_fixups_mtime") + + if file_exists(fixups_json, follow_symlinks=True): + with self.dist_lock: + import steampipe_fixups + + current_fixup_mtime = None + if file_exists(fixups_mtime, follow_symlinks=True): + with open(fixups_mtime, "r") as f: + current_fixup_mtime = f.readline().strip() + + new_fixup_mtime = getmtimestr(fixups_json) + + if current_fixup_mtime != new_fixup_mtime: + result_code = steampipe_fixups.do_restore(self.base_dir, fixups_json) + + if result_code == 0: + with open(fixups_mtime, "w") as f: + f.write(new_fixup_mtime + "\n") + def missing_default_prefix(self): '''Check if the default prefix dir is missing. Returns true if missing, false if present''' return not os.path.isdir(self.default_pfx_dir) @@ -770,10 +792,10 @@ class CompatData: self.migrate_user_paths() - if not os.path.lexists(self.prefix_dir + "/dosdevices/c:"): + if not file_exists(self.prefix_dir + "/dosdevices/c:", follow_symlinks=False): os.symlink("../drive_c", self.prefix_dir + "/dosdevices/c:") - if not os.path.lexists(self.prefix_dir + "/dosdevices/z:"): + if not file_exists(self.prefix_dir + "/dosdevices/z:", follow_symlinks=False): os.symlink("/", self.prefix_dir + "/dosdevices/z:") # collect configuration info @@ -1509,6 +1531,7 @@ if __name__ == "__main__": g_proton = Proton(os.path.dirname(sys.argv[0])) g_proton.cleanup_legacy_dist() + g_proton.do_steampipe_fixups() g_compatdata = CompatData(os.environ["STEAM_COMPAT_DATA_PATH"]) diff --git a/steampipe_fixups.py b/steampipe_fixups.py new file mode 100755 index 00000000..e5646ebe --- /dev/null +++ b/steampipe_fixups.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 + +#Steampipe doesn't support certain unix-y things which may be required by +#native Linux applications. This file will process a directory of Linux files +#and store the file properties into a manifest file. After a round trip through +#Steampipe, the original file properties can be restored using this same +#script. + +import json +import os +import secrets +import stat + +DEFAULT_MANIFEST_NAME = "steampipe_fixups.json" + +def usage(): + print("Usage:") + print("\t" + sys.argv[0] + "\tprepare\t\t[manifest output file]") + print("\t\tProcess the given path and output the manifest file to the given path, or if unspecified.") + print("") + print("\t" + sys.argv[0] + "\trestore\t\t[manifest file]") + print("\t\tRestore the given path using the manifest file, or if unspecified.") + +empty_dirs = [] +no_write_paths = [] + +def canonicalize(path, prefix): + return path.replace(prefix, "", 1).lstrip('/') + +def process_dir(path): + for root, subdirs, files in os.walk(path): + if len(subdirs) == 0 and len(files) == 0: + empty_dirs.append(canonicalize(root, path)) + + for file_ in files: + this_file = os.path.join(root, file_) + stat_result = os.lstat(this_file) + if (stat_result.st_mode & stat.S_IWUSR) == 0: + no_write_paths.append(canonicalize(this_file, path)) + + return 0 + +def write_manifest(manifest): + out = open(manifest, "w") + json.dump( + { + "id": str(secrets.randbits(32)), #we need steampipe to update this file for every build + "empty_dirs": empty_dirs, + "no_write_paths": no_write_paths, + }, + out, + indent = 4, + sort_keys = True + ) + return 0 + +def do_process(path, manifest): + if os.path.exists(manifest): + os.remove(manifest) + + ret = process_dir(path) + if ret != 0: + return ret + + #output should be deterministic + empty_dirs.sort() + no_write_paths.sort() + + ret = write_manifest(manifest) + if ret != 0: + return ret + + return 0 + +def do_restore(path, manifest): + loaded = json.load(open(manifest, "r")) + + empty_dirs = loaded["empty_dirs"] + no_write_paths = loaded["no_write_paths"] + + for empty_dir in empty_dirs: + try: + os.makedirs(os.path.join(path, empty_dir)) + except OSError: + #already exists + pass + + for file_ in no_write_paths: + this_file = os.path.join(path, file_) + stat_result = os.lstat(this_file) + os.chmod(this_file, + stat_result.st_mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)) + + return 0 + +if __name__ == '__main__': + import sys + if len(sys.argv) < 3 or len(sys.argv) > 4: + usage() + sys.exit(1) + + verb = sys.argv[1] + path = sys.argv[2] + if len(sys.argv) >= 4: + manifest = sys.argv[3] + else: + manifest = os.path.join(path, DEFAULT_MANIFEST_NAME) + + if verb == "process": + sys.exit(do_process(path, manifest)) + + if verb == "restore": + sys.exit(do_restore(path, manifest)) + + usage() + sys.exit(1)