From: Thomas Walker Lynch Date: Wed, 29 Oct 2025 09:04:53 +0000 (+0000) Subject: adds usage: scratchpad {ls|clear|help|make|write|size|find|lock|unlock} [ARGS] X-Git-Url: https://git.reasoningtechnology.com/style/static/gitweb.js?a=commitdiff_plain;h=afbf5eff61bad0fb4cc2f6c8aa51753814f2327c;p=Rabbit%2F.git adds usage: scratchpad {ls|clear|help|make|write|size|find|lock|unlock} [ARGS] ls List scratchpad in an indented tree with perms and owner (quiet if missing). clear Remove all contents of scratchpad/ except top-level .gitignore. clear NAME Remove scratchpad/NAME only. make [NAME] Ensure scratchpad/ exists with .gitignore; with NAME, mkdir scratchpad/NAME. write SRC [DST] Copy file/dir SRC into scratchpad (to DST if given; parents created). size Print 'empty' if only .gitignore; else total bytes and item count. find [OPTS...] Run system 'find' rooted at scratchpad/ with OPTS (omit literal 'scratchpad'). lock PATH... Attempt 'chattr +i' on given paths under scratchpad/ (no state kept). unlock PATH... Attempt 'chattr -i' on given paths under scratchpad/. Examples: scratchpad make scratchpad write ~/Downloads/test.tar.gz scratchpad find -type f -mtime +30 -print # files older than 30 days scratchpad lock some/dir important.txt scratchpad unlock some/dir important.txt command --- diff --git a/developer/cc/Rabbit_2017_to_WG.kmod.c b/developer/cc/Rabbit_2017_to_WG.kmod.c index 17ab69e..8a29aa3 100644 --- a/developer/cc/Rabbit_2017_to_WG.kmod.c +++ b/developer/cc/Rabbit_2017_to_WG.kmod.c @@ -12,7 +12,7 @@ #include #include -#define RABBIT_UID 2017 +#define US_UID 2017 #define DEV_NAME "US" /* WireGuard iface to force */ static atomic64_t cnt_v4_local_out = ATOMIC64_INIT(0); @@ -35,7 +35,7 @@ static int us_ifindex; /* cached ifindex for DEV_NAME */ static inline bool from_uid_2017(const struct nf_hook_state *st, struct sk_buff *skb){ struct sock *sk = st->sk ? st->sk : skb_to_full_sk(skb); if (!sk) return false; - return __kuid_val(sock_i_uid(sk)) == RABBIT_UID; + return __kuid_val(sock_i_uid(sk)) == US_UID; } /* Bind the socket to DEV_NAME once we see its first packet */ @@ -85,7 +85,7 @@ static int __init rabbit_init(void){ #else { int ret = nf_register_hooks(rabbit_ops, ARRAY_SIZE(rabbit_ops)); if (ret) return ret; } #endif - pr_info("rabbit_uid2017_bind: loaded; UID=%d bound to dev %s(ifindex=%d)\n", RABBIT_UID, DEV_NAME, us_ifindex); + pr_info("rabbit_uid2017_bind: loaded; UID=%d bound to dev %s(ifindex=%d)\n", US_UID, DEV_NAME, us_ifindex); return 0; } diff --git a/developer/tool/release b/developer/tool/release index 0eba83f..63f0378 100755 --- a/developer/tool/release +++ b/developer/tool/release @@ -28,16 +28,13 @@ machine_executable_list="scratchpad/hello" shopt -s nullglob # zero or more machine executables -for fglob in $machine_executable_list; do - # nesting the loop allows that $fglob is a file glob, and expands it - for f in $fglob; do - if [[ -x "$f" ]]; then - echo "+ install -m 0500 '$f' '$release_dir/machine/'" - install -m 0550 "$f" "$release_dir/machine/" - else - echo "(info) did not find '$f'" - fi - done +for f in $machine_executable_list; do + if [[ -x "$f" ]]; then + echo "+ install -m 0500 '$f' '$release_dir/machine/'" + install -m 0550 "$f" "$release_dir/machine/" + else + echo "(info) did not find '$f'" + fi done # zero or more Kernel modules diff --git a/release/kmod/Rabbit_2017_count.ko b/release/kmod/Rabbit_2017_count.ko index 6edb934..f998c53 100644 Binary files a/release/kmod/Rabbit_2017_count.ko and b/release/kmod/Rabbit_2017_count.ko differ diff --git a/release/kmod/Rabbit_2017_to_WG.ko b/release/kmod/Rabbit_2017_to_WG.ko new file mode 100644 index 0000000..c60a70f Binary files /dev/null and b/release/kmod/Rabbit_2017_to_WG.ko differ diff --git a/release/kmod/Rabbit_no-op.ko b/release/kmod/Rabbit_no-op.ko index 625d157..9565f97 100644 Binary files a/release/kmod/Rabbit_no-op.ko and b/release/kmod/Rabbit_no-op.ko differ diff --git a/tool_shared/bespoke/env b/tool_shared/bespoke/env index 37ba2dc..0d47fca 100644 --- a/tool_shared/bespoke/env +++ b/tool_shared/bespoke/env @@ -9,7 +9,7 @@ fi shopt -s nullglob # does not presume sharing or world permissions -umask 0027 +umask 0077 # -------------------------------------------------------------------------------- # project definition diff --git a/tool_shared/bespoke/scratchpad b/tool_shared/bespoke/scratchpad new file mode 100755 index 0000000..f14f140 --- /dev/null +++ b/tool_shared/bespoke/scratchpad @@ -0,0 +1,225 @@ +#!/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, subprocess + +HELP = """usage: scratchpad {ls|clear|help|make|write|size|find|lock|unlock} [ARGS] + ls List scratchpad in an indented tree with perms and owner (quiet if missing). + clear Remove all contents of scratchpad/ except top-level .gitignore. + clear NAME Remove scratchpad/NAME only. + make [NAME] Ensure scratchpad/ exists with .gitignore; with NAME, mkdir scratchpad/NAME. + write SRC [DST] Copy file/dir SRC into scratchpad (to DST if given; parents created). + size Print 'empty' if only .gitignore; else total bytes and item count. + find [OPTS...] Run system 'find' rooted at scratchpad/ with OPTS (omit literal 'scratchpad'). + lock PATH... Attempt 'chattr +i' on given paths under scratchpad/ (no state kept). + unlock PATH... Attempt 'chattr -i' on given paths under scratchpad/. + +Examples: + scratchpad make + scratchpad write ~/Downloads/test.tar.gz + scratchpad find -type f -mtime +30 -print # files older than 30 days + scratchpad lock some/dir important.txt + scratchpad unlock some/dir important.txt +""" + +CWD = os.getcwd() +SP = os.path.join(CWD, "scratchpad") +GITIGNORE = os.path.join(SP, ".gitignore") + +def have_sp() -> bool: + return os.path.isdir(SP) + +def ensure_sp(): + os.makedirs(SP, exist_ok=True) + ensure_gitignore() + +def ensure_gitignore(): + os.makedirs(SP, exist_ok=True) + if not os.path.isfile(GITIGNORE): + with open(GITIGNORE, "w", encoding="utf-8") as f: + f.write("*\n!.gitignore\n") + +def filemode(mode: int) -> str: + try: + return stat.filemode(mode) + except Exception: + return oct(mode & 0o777) + +def owner_group(st) -> str: + 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}" + +def rel_depth(base: str, root: str) -> int: + rel = os.path.relpath(base, root) + return 0 if rel == "." else rel.count(os.sep) + 1 + +def ls_tree(root: str) -> None: + if not have_sp(): + return + print("scratchpad/") + + def walk(path: str, indent: str, is_root: bool) -> None: + 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: + # 1) root-level hidden files first + for f in (e for e in files if e.name.startswith(".")): + st = os.lstat(f.path) + print(f"{filemode(st.st_mode)} {owner_group(st)} {indent}{f.name}") + # 2) then directories (and recurse so children sit under the parent) + for d in dirs: + st = os.lstat(d.path) + print(f"{filemode(st.st_mode)} {owner_group(st)} {indent}{d.name}/") + walk(d.path, indent + ' ', False) + # 3) then non-hidden files + for f in (e for e in files if not e.name.startswith(".")): + st = os.lstat(f.path) + print(f"{filemode(st.st_mode)} {owner_group(st)} {indent}{f.name}") + else: + # subdirs: keep previous order (dirs first, then files; dotfiles naturally sort first) + for d in dirs: + st = os.lstat(d.path) + print(f"{filemode(st.st_mode)} {owner_group(st)} {indent}{d.name}/") + walk(d.path, indent + ' ', False) + for f in files: + st = os.lstat(f.path) + print(f"{filemode(st.st_mode)} {owner_group(st)} {indent}{f.name}") + + walk(root, " ", True) + + +def clear_all() -> None: + if not have_sp(): + return + for name in os.listdir(SP): + p = os.path.join(SP, name) + if name == ".gitignore" and os.path.isfile(p): + continue # preserve only top-level .gitignore + 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 clear_subdir(sub: str) -> None: + if not have_sp(): + return + target = os.path.normpath(os.path.join(SP, sub)) + try: + if os.path.commonpath([SP]) != os.path.commonpath([SP, target]): + return + except Exception: + return + if os.path.isdir(target) and not os.path.islink(target): + shutil.rmtree(target, ignore_errors=True) + +def cmd_make(args): + ensure_sp() + if args: + os.makedirs(os.path.join(SP, args[0]), exist_ok=True) + +def cmd_write(args): + if len(args) < 1: + print(HELP); return + if not have_sp(): + ensure_sp() + src = args[0] + dst = args[1] if len(args) >= 2 else (os.path.basename(src.rstrip(os.sep)) or "untitled") + dst_path = os.path.normpath(os.path.join(SP, dst)) + try: + if os.path.commonpath([SP]) != os.path.commonpath([SP, dst_path]): + print("refusing to write outside scratchpad", file=sys.stderr); return + except Exception: + print("invalid destination", file=sys.stderr); return + os.makedirs(os.path.dirname(dst_path), exist_ok=True) + if os.path.isdir(src): + if os.path.exists(dst_path): + shutil.rmtree(dst_path, ignore_errors=True) + shutil.copytree(src, dst_path, dirs_exist_ok=False) + else: + shutil.copy2(src, dst_path) + +def cmd_size(): + if not have_sp(): + return + names = os.listdir(SP) + if [n for n in names if n != ".gitignore"] == []: + print("empty"); return + total = 0; count = 0 + for base, dirs, files in os.walk(SP): + for fn in files: + if fn == ".gitignore": + continue + p = os.path.join(base, fn) + try: + total += os.path.getsize(p); count += 1 + except OSError: + pass + print(f"bytes={total} items={count}") + +def cmd_find(args): + if not have_sp(): + return + try: + subprocess.run(["find", SP] + args, check=False) + except FileNotFoundError: + print("find not available", file=sys.stderr) + +def cmd_chattr(flag: str, paths): + if not have_sp() or not paths: + return + try: + subprocess.run(["chattr", "-V"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False) + except FileNotFoundError: + print("chattr not available; lock/unlock skipped", file=sys.stderr); return + for rel in paths: + target = os.path.normpath(os.path.join(SP, rel)) + try: + if os.path.commonpath([SP]) != os.path.commonpath([SP, target]): + continue + except Exception: + continue + try: + subprocess.run(["chattr", flag, target], check=False) + except Exception: + pass + +def CLI(): + if len(sys.argv) < 2: + print(HELP); return + cmd, *args = sys.argv[1:] + if cmd == "ls": + if have_sp(): ls_tree(SP) + else: return + elif cmd == "clear": + if len(args) >= 1: clear_subdir(args[0]) + else: clear_all() + elif cmd == "help": + print(HELP) + elif cmd == "make": + cmd_make(args) + elif cmd == "write": + cmd_write(args) + elif cmd == "size": + cmd_size() + elif cmd == "find": + cmd_find(args) + elif cmd == "lock": + cmd_chattr("+i", args) + elif cmd == "unlock": + cmd_chattr("-i", args) + else: + print(HELP) + +if __name__ == "__main__": + CLI() diff --git a/tool_shared/bespoke/version b/tool_shared/bespoke/version index 9d91a98..829308d 100755 --- a/tool_shared/bespoke/version +++ b/tool_shared/bespoke/version @@ -1,5 +1,5 @@ #!/bin/env bash script_afp=$(realpath "${BASH_SOURCE[0]}") -echo "Harmony v0.1 2025-05-19" +echo "Rabbit v0.1 2025-10-20"