From de63ea5341d3a7b8fd6466d08badc67c30adff72 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 | 4 ++ proton | 27 ++++++++++- steampipe_fixups.py | 116 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+), 2 deletions(-) create mode 100755 steampipe_fixups.py diff --git a/Makefile.in b/Makefile.in index c58d936f..31836e2f 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1084,10 +1084,14 @@ all-dist: ## make deploy ## +STEAMPIPE_FIXUPS_PY := $(SRCDIR)/steampipe_fixups.py + .PHONY: deploy deploy: all mkdir -p $(DEPLOY_DIR) && \ rsync --delete --exclude compatibilitytool.vdf -arx $(DST_BASE)/ $(DEPLOY_DIR) + cp -a $(STEAMPIPE_FIXUPS_PY) $(DEPLOY_DIR) + python3 $(STEAMPIPE_FIXUPS_PY) process $(DEPLOY_DIR) ## diff --git a/proton b/proton index 616c41b0..8c270bf7 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) @@ -769,10 +791,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 @@ -1548,6 +1570,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)