265 lines
11 KiB
Python
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())
|