"""Independent OS-state custody; never repair or take ownership of app data.""" import os import pathlib import weakref import json from d09_store import atomic, identity, read, secure from os_update_plan import canonical LEGACY_ROOT = pathlib.Path('/var/lib/archipelago/os-update') def refuse_legacy(): """Inspect only the old entry, never follow it or read/trust its contents.""" try: parent = os.open(LEGACY_ROOT.parent, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW) except FileNotFoundError: return except OSError as exc: raise PermissionError('legacy OS ancestry unresolved; explicit root reconciliation required') from exc try: try: os.stat(LEGACY_ROOT.name, dir_fd=parent, follow_symlinks=False) except FileNotFoundError: return raise PermissionError('legacy OS state exists; explicit root migration/reconciliation required') finally: os.close(parent) def directory_identity(fd): value = identity(fd) # Creating a child directory changes nlink, not custody of this directory. return value[:5] + value[6:] class Pin: def __init__(self, path): self.path = pathlib.Path(path) self.fd = secure(self.path, True) self.close = weakref.finalize(self, os.close, self.fd) self.identity = directory_identity(self.fd) def check(self): fd = secure(self.path, True) try: if directory_identity(fd) != self.identity or directory_identity(self.fd) != self.identity: raise PermissionError('OS state directory replaced; reconciliation required') finally: os.close(fd) def mkdir(path): """Validate/open protected parent BEFORE creating any child; no parents=True.""" path = pathlib.Path(path) parent = Pin(path.parent) try: try: os.mkdir(path.name, mode=0o700, dir_fd=parent.fd) created = True os.fsync(parent.fd) except FileExistsError: created = False parent.check() pin = Pin(path) return pin, created finally: parent.close() def transaction(path, initial, create): """Storage only: caller authenticates admission first. Absence is not reset.""" path = pathlib.Path(path) created = False pin = None if create: pin, created = mkdir(path) elif path.exists() or path.is_symlink(): pin = Pin(path) if pin is None: return None, initial try: status = path / 'status.json' if created: atomic(status, canonical(initial)) elif not (status.exists() or status.is_symlink()): raise PermissionError('existing OS transaction has no journal; explicit reconciliation required') pin.check() return pin, json.loads(read(status)) except BaseException: pin.close() raise