"""Filesystem-safe names and atomic writes shared by storage and pipeline settings.""" from __future__ import annotations import json import os import ctypes from contextlib import suppress from pathlib import Path import stat import re from uuid import uuid4 _renameat2 = ctypes.CDLL(None, use_errno=True).renameat2 _renameat2.argtypes = [ctypes.c_int, ctypes.c_char_p, ctypes.c_int, ctypes.c_char_p, ctypes.c_uint] _renameat2.restype = ctypes.c_int def rename_no_replace(source: Path, destination: Path) -> None: """Publish a file or directory atomically, refusing to replace an existing name.""" # Linux AT_FDCWD=-100 and RENAME_NOREPLACE=1; also supported by FAT filesystems. if _renameat2(-100, os.fsencode(source), -100, os.fsencode(destination), 1): error = ctypes.get_errno() raise OSError(error, os.strerror(error), str(destination)) def temporary_file(destination: Path) -> Path: """Create a sibling file using the destination's permissions or the current umask.""" path = destination.with_name(f".{destination.name}.{uuid4().hex}.tmp") descriptor = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o666) try: try: mode = stat.S_IMODE(destination.stat().st_mode) except FileNotFoundError: pass else: os.fchmod(descriptor, mode) except BaseException: path.unlink() raise finally: os.close(descriptor) return path def sync_directory(path: Path) -> None: """Make directory entry changes durable after replacing or removing files.""" descriptor = os.open(path, os.O_RDONLY | os.O_DIRECTORY) try: os.fsync(descriptor) finally: os.close(descriptor) def make_directory(path: Path, *, durable: bool = False) -> None: """Create missing parents, optionally syncing each newly created directory entry.""" if not durable: path.mkdir(parents=True, exist_ok=True) elif not path.is_dir(): make_directory(path.parent, durable=True) path.mkdir(exist_ok=True) sync_directory(path.parent) def atomic_write_json(path: Path, payload: object, *, durable: bool = False) -> Path: """Serialize strict JSON before opening any files, then atomically commit it.""" return atomic_write_text(path, json.dumps(payload, indent=2, allow_nan=False), durable=durable) def atomic_write_text(path: Path, text: str, *, durable: bool = False) -> Path: """Replace a text file only after a complete write, cleaning up failed writes. Each call owns its temporary file, so concurrent writers cannot truncate or rename one another's work. Durable writes also sync the file and directory; frequent live settings can omit those syncs to avoid unnecessary disk I/O. """ make_directory(path.parent, durable=durable) temporary = None try: temporary = temporary_file(path) with temporary.open("w", encoding="utf-8") as handle: handle.write(text) if durable: handle.flush() os.fsync(handle.fileno()) os.replace(temporary, path) if durable: sync_directory(path.parent) finally: if temporary is not None: with suppress(OSError): temporary.unlink(missing_ok=True) return path def sanitize_path_component(name: str) -> str: """Convert arbitrary string into filesystem-safe component.""" cleaned = re.sub(r"[^A-Za-z0-9._-]+", "_", name.strip()) cleaned = cleaned.strip("._") return cleaned or "snapshot"