From: Thomas Walker Lynch Date: Wed, 29 Oct 2025 19:17:14 +0000 (+0000) Subject: backing off of read only release dir due to complexity X-Git-Url: https://git.reasoningtechnology.com/style/rt_dark_doc.css?a=commitdiff_plain;h=fe57deb550cc7d5ec934ad243e7f2ebb26da2262;p=Harmony.git backing off of read only release dir due to complexity --- diff --git a/env_developer b/env_developer index 892df91..59993f6 100644 --- a/env_developer +++ b/env_developer @@ -1,4 +1,6 @@ #!/usr/bin/env bash +# env_developer — enter the project developer environment +# (must be sourced) script_afp=$(realpath "${BASH_SOURCE[0]}") if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then diff --git a/env_toolsmith b/env_toolsmith index b121a82..2e27571 100644 --- a/env_toolsmith +++ b/env_toolsmith @@ -1,11 +1,45 @@ #!/usr/bin/env bash +# env_toolsmith — enter the project toolsmith environment +# (must be sourced) + script_afp=$(realpath "${BASH_SOURCE[0]}") if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then echo "$script_afp:: This script must be sourced, not executed." exit 1 fi -export ROLE=toolsmith -source tool_shared/bespoke/env -export ENV=$ROLE +# enter project environment +# + source tool_shared/bespoke/env + +# setup tools +# initially these will not exist, as the toolsmith installs them +# + export PYTHON_HOME="$REPO_HOME/tool_shared/third_party/python" + if [[ ":$PATH:" != *":$PYTHON_HOME/bin:"* ]]; then + export PATH="$PYTHON_HOME/bin:$PATH" + fi + + RT_gcc="$REPO_HOME/tool_shared/third_party/RT_gcc/release" + if [[ ":$PATH:" != *":$RT_gcc:"* ]]; then + export PATH="$RT_gcc:$PATH" + fi + +# enter the role environment +# + export ROLE=toolsmith + + TOOL_DIR="$REPO_HOME/tool" + if [[ ":$PATH:" != *":$TOOL_DIR:"* ]]; then + export PATH="$TOOL_DIR:$PATH" + fi + + export ENV="tool/env" + cd "$REPO_HOME" + if [[ -f "tool/env" ]]; then + source "tool/env" + echo "in environment: $ENV" + else + echo "not found: $ENV" + fi diff --git a/tool/.githolder b/tool/.githolder deleted file mode 100644 index e69de29..0000000 diff --git a/tool/env b/tool/env new file mode 100644 index 0000000..0b993ad --- /dev/null +++ b/tool/env @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +script_afp=$(realpath "${BASH_SOURCE[0]}") + diff --git a/tool/release b/tool/release new file mode 100755 index 0000000..e99629c --- /dev/null +++ b/tool/release @@ -0,0 +1,291 @@ +#!/usr/bin/env -S python3 -B +# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*- + +import os, sys, shutil, stat, pwd, grp, glob, tempfile + +HELP = """usage: release {write|clean|ls|help|dry write} [DIR] + write [DIR] Writes released files into $REPO_HOME/release. If [DIR] is specified, only writes files found in scratchpad/DIR. + clean [DIR] Remove the contents of the release directories. If [DIR] is specified, clean only the contents of that release directory. + ls List release/ as an indented tree: PERMS OWNER NAME (root-level dotfiles printed first). + help Show this message. + dry write [DIR] + Preview what write would do without modifying the filesystem. +""" + +ENV_MUST_BE = "developer/tool/env" +DEFAULT_DIR_MODE = 0o700 # 077-congruent dirs + +def exit_with_status(msg, code=1): + print(f"release: {msg}", file=sys.stderr) + sys.exit(code) + +def assert_env(): + env = os.environ.get("ENV", "") + if env != ENV_MUST_BE: + hint = ( + "ENV is not 'developer/tool/env'.\n" + "Enter the project with: source ./env_developer\n" + "That script exports: ROLE=developer; ENV=$ROLE/tool/env" + ) + exit_with_status(f"bad environment: ENV='{env}'. {hint}") + +def repo_home(): + rh = os.environ.get("REPO_HOME") + if not rh: + exit_with_status("REPO_HOME not set (did you 'source ./env_developer'?)") + return rh + +def dpath(*parts): + return os.path.join(repo_home(), "developer", *parts) + +def rpath(*parts): + return os.path.join(repo_home(), "release", *parts) + +def dev_root(): + return dpath() + +def rel_root(): + return rpath() + +def _display_src(p_abs: str) -> str: + try: + if os.path.commonpath([dev_root()]) == os.path.commonpath([dev_root(), p_abs]): + return os.path.relpath(p_abs, dev_root()) + except Exception: + pass + return p_abs + +def _display_dst(p_abs: str) -> str: + try: + rel = os.path.relpath(p_abs, rel_root()) + rel = "" if rel == "." else rel + return "$REPO_HOME/release" + ("/" + rel if rel else "") + except Exception: + return p_abs + +def ensure_mode(path, mode): + try: os.chmod(path, mode) + except Exception: pass + +def ensure_dir(path, mode=DEFAULT_DIR_MODE, dry=False): + if dry: + if not os.path.isdir(path): + shown = _display_dst(path) if path.startswith(rel_root()) else ( + os.path.relpath(path, dev_root()) if path.startswith(dev_root()) else path + ) + print(f"(dry) mkdir -m {oct(mode)[2:]} '{shown}'") + return + os.makedirs(path, exist_ok=True) + ensure_mode(path, mode) + +def filemode(m): + try: return stat.filemode(m) + except Exception: return oct(m & 0o777) + +def owner_group(st): + try: return f"{pwd.getpwuid(st.st_uid).pw_name}:{grp.getgrgid(st.st_gid).gr_name}" + except Exception: return f"{st.st_uid}:{st.st_gid}" + +# ---------- LS (two-pass owner:group width) ---------- +def list_tree(root): + if not os.path.isdir(root): + return + entries = [] + def gather(path: str, depth: int, is_root: bool): + try: + it = list(os.scandir(path)) + except FileNotFoundError: + return + dirs = [e for e in it if e.is_dir(follow_symlinks=False)] + files = [e for e in it if not e.is_dir(follow_symlinks=False)] + dirs.sort(key=lambda e: e.name); files.sort(key=lambda e: e.name) + + if is_root: + for f in (e for e in files if e.name.startswith(".")): + st = os.lstat(f.path); entries.append((False, depth, filemode(st.st_mode), owner_group(st), f.name)) + for d in dirs: + st = os.lstat(d.path); entries.append((True, depth, filemode(st.st_mode), owner_group(st), d.name + "/")) + gather(d.path, depth + 1, False) + for f in (e for e in files if not e.name.startswith(".")): + st = os.lstat(f.path); entries.append((False, depth, filemode(st.st_mode), owner_group(st), f.name)) + else: + for d in dirs: + st = os.lstat(d.path); entries.append((True, depth, filemode(st.st_mode), owner_group(st), d.name + "/")) + gather(d.path, depth + 1, False) + for f in files: + st = os.lstat(f.path); entries.append((False, depth, filemode(st.st_mode), owner_group(st), f.name)) + gather(root, depth=1, is_root=True) + + ogw = 0 + for (_isdir, _depth, _perms, ownergrp, _name) in entries: + if len(ownergrp) > ogw: ogw = len(ownergrp) + + print("release/") + for (_isdir, depth, perms, ownergrp, name) in entries: + indent = " " * depth + print(f"{perms} {ownergrp:<{ogw}} {indent}{name}") +# ---------- end LS ---------- + +def iter_src_files(topdir, src_root): + base = os.path.join(src_root, topdir) if topdir else src_root + if not os.path.isdir(base): + return + yield + if topdir == "kmod": + for p in sorted(glob.glob(os.path.join(base, "*.ko"))): + yield (p, os.path.basename(p)) + else: + for root, dirs, files in os.walk(base): + dirs.sort(); files.sort() + for fn in files: + src = os.path.join(root, fn) + rel = os.path.relpath(src, base) + yield (src, rel) + +def _target_mode_from_source(src_abs: str) -> int: + """077 policy: files 0600; if source has owner-exec, make 0700.""" + try: + sm = stat.S_IMODE(os.stat(src_abs).st_mode) + except FileNotFoundError: + return 0o600 + return 0o700 if (sm & stat.S_IXUSR) else 0o600 + +def copy_one(src_abs, dst_abs, dry=False): + src_show = _display_src(src_abs) + dst_show = _display_dst(dst_abs) + parent = os.path.dirname(dst_abs) + os.makedirs(parent, exist_ok=True) + target_mode = _target_mode_from_source(src_abs) + + def _is_writable_dir(p): return os.access(p, os.W_OK) + flip_needed = not _is_writable_dir(parent) + restore_mode = None + parent_show = _display_dst(parent) + + if dry: + if flip_needed: + print(f"(dry) chmod u+w '{parent_show}'") + if os.path.exists(dst_abs): + print(f"(dry) unlink '{dst_show}'") + # show final mode we will set + print(f"(dry) install -m {oct(target_mode)[2:]} -D '{src_show}' '{dst_show}'") + if flip_needed: + print(f"(dry) chmod u-w '{parent_show}'") + return + + try: + if flip_needed: + try: + st_parent = os.stat(parent) + restore_mode = stat.S_IMODE(st_parent.st_mode) + os.chmod(parent, restore_mode | stat.S_IWUSR) + except PermissionError: + exit_with_status(f"cannot write: parent dir not writable and chmod failed on {parent_show}") + + # Atomic replace with enforced 077-compliant mode + fd, tmp_path = tempfile.mkstemp(prefix='.tmp.', dir=parent) + try: + with os.fdopen(fd, "wb") as tmpf, open(src_abs, "rb") as sf: + shutil.copyfileobj(sf, tmpf) + tmpf.flush() + os.chmod(tmp_path, target_mode) + os.replace(tmp_path, dst_abs) + finally: + try: + if os.path.exists(tmp_path): + os.unlink(tmp_path) + except Exception: + pass + finally: + if restore_mode is not None: + try: os.chmod(parent, restore_mode) + except Exception: pass + + print(f"+ install -m {oct(target_mode)[2:]} '{src_show}' '{dst_show}'") + +def write_one_dir(topdir, dry): + rel_root_dir = rpath() + src_root = dpath("scratchpad") + src_dir = os.path.join(src_root, topdir) + dst_dir = os.path.join(rel_root_dir, topdir) + + if not os.path.isdir(src_dir): + exit_with_status( + f"cannot write: expected '{_display_src(src_dir)}' to exist. " + f"Create scratchpad/{topdir} (Makefiles may need to populate it)." + ) + + ensure_dir(dst_dir, DEFAULT_DIR_MODE, dry=dry) + + wrote = False + for src_abs, rel in iter_src_files(topdir, src_root): + dst_abs = os.path.join(dst_dir, rel) + copy_one(src_abs, dst_abs, dry=dry) + wrote = True + if not wrote: + msg = "no matching artifacts found" + if topdir == "kmod": msg += " (looking for *.ko)" + print(f"(info) {msg} in {_display_src(src_dir)}") + +def cmd_write(dir_arg, dry=False): + assert_env() + ensure_dir(rpath(), DEFAULT_DIR_MODE, dry=dry) + + src_root = dpath("scratchpad") + if not os.path.isdir(src_root): + exit_with_status(f"cannot find developer scratchpad at '{_display_src(src_root)}'") + + if dir_arg: + write_one_dir(dir_arg, dry=dry) + else: + subs = sorted([e.name for e in os.scandir(src_root) if e.is_dir(follow_symlinks=False)]) + if not subs: + print(f"(info) nothing to release; no subdirectories found under {_display_src(src_root)}") + return + for td in subs: + write_one_dir(td, dry=dry) + +def _clean_contents(dir_path): + if not os.path.isdir(dir_path): return + for name in os.listdir(dir_path): + p = os.path.join(dir_path, name) + if os.path.isdir(p) and not os.path.islink(p): + shutil.rmtree(p, ignore_errors=True) + else: + try: os.unlink(p) + except FileNotFoundError: pass + +def cmd_clean(dir_arg): + assert_env() + rel_root_dir = rpath() + if not os.path.isdir(rel_root_dir): + return + if dir_arg: + _clean_contents(os.path.join(rel_root_dir, dir_arg)) + else: + for e in os.scandir(rel_root_dir): + if e.is_dir(follow_symlinks=False): + _clean_contents(e.path) + +def CLI(): + if len(sys.argv) < 2: + print(HELP); return + cmd, *args = sys.argv[1:] + if cmd == "write": + cmd_write(args[0] if args else None, dry=False) + elif cmd == "clean": + cmd_clean(args[0] if args else None) + elif cmd == "ls": + list_tree(rpath()) + elif cmd == "help": + print(HELP) + elif cmd == "dry": + if args and args[0] == "write": + cmd_write(args[1] if len(args) >= 2 else None, dry=True) + else: + print(HELP) + else: + print(HELP) + +if __name__ == "__main__": + CLI() diff --git a/tool/set_project_permissions b/tool/set_project_permissions new file mode 100755 index 0000000..3511c89 --- /dev/null +++ b/tool/set_project_permissions @@ -0,0 +1,124 @@ +#!/usr/bin/env -S python3 -B +# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*- + +""" +set_project_permissions — normalize a freshly cloned project to Harmony policies. + +usage: + set_project_permissions [default] + set_project_permissions help | --help | -h + +notes: + • Must be run from the toolsmith environment (ENV=tool/env, ROLE=toolsmith). + • Starts at $REPO_HOME. + • Baseline is umask-077 congruence: + - directories → 0700 + - files → 0600, but preserve owner-exec (→ 0700 for executables) + applied to the entire repo, including release/, EXCEPT: + - release/kmod/*.ko → 0440 + • Skips .git/ and symlinks. +""" + +import os, sys, stat + +# Must match tool_shared/bespoke/env policy: +DEFAULT_UMASK = 0o077 # reminder only; effective modes below implement 077 congruence. + +DIR_MODE_077 = 0o700 + +def die(msg, code=1): + print(f"set_project_permissions: {msg}", file=sys.stderr) + sys.exit(code) + +def require_toolsmith_env(): + env = os.environ.get("ENV", "") + role = os.environ.get("ROLE", "") + if env != "tool/env" or role != "toolsmith": + hint = ( + "This script should be run from the toolsmith environment.\n" + "Try: source ./env_toolsmith (then re-run: set_project_permissions default)" + ) + die(f"bad environment: ENV='{env}' ROLE='{role}'.\n{hint}") + +def repo_home(): + rh = os.environ.get("REPO_HOME") + if not rh: + die("REPO_HOME is not set (did you source tool_shared/bespoke/env?)") + return os.path.realpath(rh) + +def show_path(p, rh): + return p.replace(rh, "$REPO_HOME", 1) if p.startswith(rh) else p + +def is_git_dir(path): + return os.path.basename(path.rstrip(os.sep)) == ".git" + +def file_target_mode_077_preserve_exec(current_mode: int) -> int: + # Base 0600, add owner exec if currently set; drop all group/other. + target = 0o600 + if current_mode & stat.S_IXUSR: + target |= stat.S_IXUSR + return target + +def set_mode_if_needed(path, target, rh): + try: + st = os.lstat(path) + except FileNotFoundError: + return 0 + cur = stat.S_IMODE(st.st_mode) + if cur == target: + return 0 + os.chmod(path, target) + print(f"+ chmod {oct(target)[2:]} '{show_path(path, rh)}'") + return 1 + +def apply_policy(rh): + changed = 0 + release_root = os.path.join(rh, "release") + for dirpath, dirnames, filenames in os.walk(rh, topdown=True, followlinks=False): + # prune .git + dirnames[:] = [d for d in dirnames if d != ".git"] + + # directories: 0700 everywhere (incl. release/) + changed += set_mode_if_needed(dirpath, DIR_MODE_077, rh) + + # files: 0600 (+owner exec) everywhere, except release/kmod/*.ko → 0440 + rel_from_repo = os.path.relpath(dirpath, rh) + under_release = rel_from_repo == "release" or rel_from_repo.startswith("release"+os.sep) + top_under_release = "" + if under_release: + rel_from_release = os.path.relpath(dirpath, release_root) + top_under_release = (rel_from_release.split(os.sep, 1)[0] if rel_from_release != "." else "") + + for fn in filenames: + p = os.path.join(dirpath, fn) + if os.path.islink(p): + continue + try: + st = os.lstat(p) + except FileNotFoundError: + continue + + if under_release and top_under_release == "kmod" and fn.endswith(".ko"): + target = 0o440 + else: + target = file_target_mode_077_preserve_exec(stat.S_IMODE(st.st_mode)) + + changed += set_mode_if_needed(p, target, rh) + return changed + +def cmd_default(): + require_toolsmith_env() + rh = repo_home() + total = apply_policy(rh) + print(f"changes: {total}") + +def main(): + if len(sys.argv) == 1 or sys.argv[1] in ("default",): + return cmd_default() + if sys.argv[1] in ("help", "--help", "-h"): + print(__doc__.strip()); return 0 + # unknown command → help + print(__doc__.strip()); return 1 + +if __name__ == "__main__": + sys.exit(main())