Files

265 lines
11 KiB
Python

"""Canonical, private Issue runtime allocation (not cache/worktree admission)."""
from __future__ import annotations
import argparse
import grp
import json
import os
import pwd
import re
import stat
import subprocess
import sys
from dataclasses import dataclass
from pathlib import Path
if __package__ in (None, ''):
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from harness.lib.managed_lock import ManagedLockSet, validate_held_flock
_IDENTIFIER = re.compile(r"[A-Za-z0-9][A-Za-z0-9_.-]{0,254}")
_RUN = re.compile(r"[A-Za-z0-9][A-Za-z0-9_.-]{0,127}")
_DIR_FLAGS = os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW | os.O_CLOEXEC
def _identity(root, owner, repository, issue, run_id=None):
raw = os.fspath(root)
if not isinstance(raw, str) or not raw.startswith('/') or '\x00' in raw:
raise ValueError('invalid harness root')
if any(part in {'', '.', '..'} for part in raw.split('/')[1:]):
raise ValueError('invalid harness root')
for value in (owner, repository):
if not isinstance(value, str) or not _IDENTIFIER.fullmatch(value):
raise ValueError('invalid repository identity')
if isinstance(issue, bool) or not isinstance(issue, int) or issue <= 0:
raise ValueError('invalid Issue')
if run_id is not None and (not isinstance(run_id, str) or not _RUN.fullmatch(run_id)):
raise ValueError('invalid workflow run id')
return Path(raw)
def issue_runtime_path(root, owner, repository, issue, run_id):
root = _identity(root, owner, repository, issue, run_id)
return root / 'repositories' / owner / repository / 'worktrees' / f'issue-{issue}' / 'runtime' / run_id
def _open_absolute(path):
fd = os.open('/', _DIR_FLAGS)
try:
for part in path.parts[1:]:
child = os.open(part, _DIR_FLAGS, dir_fd=fd)
os.close(fd)
fd = child
return fd
except BaseException:
os.close(fd)
raise
def _private(st):
if not stat.S_ISDIR(st.st_mode) or st.st_uid != os.getuid() or stat.S_IMODE(st.st_mode) != 0o700:
raise ValueError('unsafe private directory')
def _repository_container(st):
mode = stat.S_IMODE(st.st_mode)
if not stat.S_ISDIR(st.st_mode) or st.st_uid != os.getuid() or mode & 0o002:
raise ValueError('unsafe repositories directory')
if mode & 0o020:
try:
group = grp.getgrgid(st.st_gid)
members = {pwd.getpwnam(name).pw_uid for name in group.gr_mem}
members.update(user.pw_uid for user in pwd.getpwall() if user.pw_gid == st.st_gid)
except KeyError as exc:
raise ValueError('unresolved repositories group') from exc
if members != {os.getuid()}:
raise ValueError('unsafe repositories group')
def _existing(parent, name, check=_private):
child = os.open(name, _DIR_FLAGS, dir_fd=parent)
try:
check(os.fstat(child))
named = os.stat(name, dir_fd=parent, follow_symlinks=False)
opened = os.fstat(child)
if (named.st_dev, named.st_ino) != (opened.st_dev, opened.st_ino):
raise ValueError('directory identity changed')
return child
except BaseException:
os.close(child)
raise
def _cache_fd(root, owner, repository):
fd = _open_absolute(root)
try:
if os.fstat(fd).st_mode & 0o002:
raise ValueError('world-writable harness root')
for name, check in [('repositories', _repository_container), (owner, _private), (repository, _private)]:
child = _existing(fd, name, check)
os.close(fd)
fd = child
return fd
except BaseException:
os.close(fd)
raise
def _new_child(parent, name, created):
os.mkdir(name, 0o700, dir_fd=parent)
initial = os.stat(name, dir_fd=parent, follow_symlinks=False)
created.append((os.dup(parent), name, (initial.st_dev, initial.st_ino)))
# Correct only a directory just created by this call, including restrictive umask.
if not stat.S_ISDIR(initial.st_mode) or initial.st_uid != os.getuid():
raise ValueError('new directory identity changed')
os.chmod(name, 0o700, dir_fd=parent, follow_symlinks=False)
fd = _existing(parent, name)
opened = os.fstat(fd)
if (opened.st_dev, opened.st_ino) != (initial.st_dev, initial.st_ino):
os.close(fd)
raise ValueError('new directory identity changed')
return fd
@dataclass(frozen=True)
class RuntimeDirectory:
path: Path
fd: int # Caller owns this descriptor and must close it.
def prepare_issue_runtime(root, owner, repository, issue, run_id, *, lock_set):
path = issue_runtime_path(root, owner, repository, issue, run_id)
expected = ManagedLockSet.paths_for_issue(root, owner, repository, issue)
if (not isinstance(lock_set, ManagedLockSet) or not lock_set.issue.held or not lock_set.repository.held
or (lock_set.issue.path, lock_set.repository.path) != expected):
raise ValueError('matching Issue and repository leases required')
for lock in (lock_set.issue, lock_set.repository):
lock_fd = lock.fd
if lock_fd is None or not validate_held_flock(lock_fd, lock.path):
raise ValueError('kernel lease unavailable')
parent = _open_absolute(lock.path.parent)
try:
_private(os.fstat(parent))
named = os.stat(lock.path.name, dir_fd=parent, follow_symlinks=False)
opened = os.fstat(lock_fd)
if (named.st_dev, named.st_ino) != (opened.st_dev, opened.st_ino):
raise ValueError('kernel lease path identity mismatch')
finally:
os.close(parent)
fd = _cache_fd(Path(root), owner, repository)
created = []
success = False
try:
for name in ('worktrees', f'issue-{issue}', 'runtime'):
try:
child = _existing(fd, name)
except FileNotFoundError:
child = _new_child(fd, name, created)
os.close(fd)
fd = child
# Runs are immutable identities; even a safe, empty stale run is a collision.
try:
fd_run = _new_child(fd, run_id, created)
except FileExistsError as exc:
raise ValueError('runtime run already exists') from exc
os.close(fd)
fd = fd_run
success = True
return RuntimeDirectory(path, fd)
finally:
if not success:
os.close(fd)
for parent, name, identity in reversed(created):
try:
current = os.stat(name, dir_fd=parent, follow_symlinks=False)
if (current.st_dev, current.st_ino) == identity and stat.S_ISDIR(current.st_mode):
os.rmdir(name, dir_fd=parent) # Empty, owned inode only; never recursive.
except OSError:
pass
for parent, _, _ in created:
os.close(parent)
def inspect_issue_worktree_admission(root, owner, repository, issue):
"""Classify container shape; caller still authenticates state/base/registration.
This read-only check is used under Issue + repository leases. It does not
authorize Git writes or declare an existing checkout valid.
"""
root = _identity(root, owner, repository, issue)
fd = _cache_fd(root, owner, repository)
issue_fd = None
try:
bare = root / 'repositories' / owner / repository / 'repository.git'
bare_fd = os.open('repository.git', _DIR_FLAGS, dir_fd=fd)
try:
bare_st = os.fstat(bare_fd)
if bare_st.st_uid != os.getuid() or bare_st.st_mode & 0o022:
raise ValueError('unsafe bare repository')
result = subprocess.run(['git', '--git-dir', f'/proc/self/fd/{bare_fd}', 'worktree', 'list', '--porcelain', '-z'],
pass_fds=(bare_fd,), capture_output=True, text=True, timeout=30, check=False)
finally:
os.close(bare_fd)
if result.returncode:
raise ValueError('Git registration unavailable')
checkout = bare.parent / 'worktrees' / f'issue-{issue}' / 'checkout'
registrations = [field[9:] for field in result.stdout.split('\x00') if field.startswith('worktree ')]
registered = registrations.count(str(checkout))
try:
worktrees = _existing(fd, 'worktrees')
except FileNotFoundError:
return 'invalid' if registered else 'absent'
try:
try:
issue_fd = _existing(worktrees, f'issue-{issue}')
except FileNotFoundError:
return 'invalid' if registered else 'absent'
finally:
os.close(worktrees)
entries = set(os.listdir(issue_fd))
for name in entries & {'runtime', 'verification'}:
child = _existing(issue_fd, name)
os.close(child)
if entries <= {'runtime', 'verification'}:
return 'invalid' if registered else 'runtime-only'
if entries != (entries & {'runtime', 'verification'}) | {'checkout', 'state.json'} or registered != 1:
return 'invalid'
source = os.stat('checkout', dir_fd=issue_fd, follow_symlinks=False)
state = os.stat('state.json', dir_fd=issue_fd, follow_symlinks=False)
if (not stat.S_ISDIR(source.st_mode) or source.st_uid != os.getuid() or source.st_mode & 0o022
or not stat.S_ISREG(state.st_mode) or state.st_uid != os.getuid()
or stat.S_IMODE(state.st_mode) != 0o600 or state.st_nlink != 1):
return 'invalid'
return 'checkout-candidate'
finally:
if issue_fd is not None:
os.close(issue_fd)
os.close(fd)
def main():
# Direct-script bootstrap is below; imports are delayed to avoid broker cycles.
parser = argparse.ArgumentParser()
parser.add_argument('command', choices=['inspect-worktree'])
for name in ('root', 'owner', 'repository', 'handle'):
parser.add_argument('--' + name, required=True)
parser.add_argument('--issue', required=True, type=int)
args = parser.parse_args()
try:
from harness.lib import managed_lock_broker as broker, verification_root
root = _identity(args.root, args.owner, args.repository, args.issue)
verification_root._lease(Path(args.handle), root, args.owner, args.repository, args.issue)
if broker.request_handle(args.handle, 'status').get('status') != 'repository-held':
raise ValueError('repository lease required')
shape = inspect_issue_worktree_admission(root, args.owner, args.repository, args.issue)
print(json.dumps({'container': shape}))
return 1 if shape == 'invalid' else 0
except (ValueError, OSError, subprocess.SubprocessError):
print(json.dumps({'error': 'worktree-container-unavailable'}))
return 2
if __name__ == '__main__':
raise SystemExit(main())