SumEn f4eaa51a73
fix(uploads): retain filesystem ownership across request cancellation (#7170)
* fix(uploads): retain filesystem ownership across cancellation

* fix(uploads): preserve cancellation across cleanup failures
2026-10-20 23:17:00 +08:00

73 lines
2.7 KiB
Python

"""Atomic byte publication with normal file-creation permissions."""
from __future__ import annotations
import logging
import os
import stat
from contextlib import suppress
from pathlib import Path
from uuid import uuid4
logger = logging.getLogger(__name__)
def cleanup_failed_atomic_write(temporary: Path, fd: int | None = None) -> None:
"""Release an owned temporary without replacing the caller's outcome.
Pass *fd* only while the raw descriptor is still owned by the caller;
successful ``fdopen`` transfers that ownership to the file object.
An explicitly transferred duplicate descriptor is also caller-owned.
Also use this to discard staging after a publication decision. Filesystem
and logging-handler errors remain independent best-effort steps.
"""
# Cleanup and diagnostics are best-effort: keep the original error
# and attempt unlink even if closing an untransferred descriptor fails.
if fd is not None:
try:
os.close(fd)
except OSError:
with suppress(Exception):
logger.warning("Failed to close temporary output descriptor for %s", temporary, exc_info=True)
try:
temporary.unlink(missing_ok=True)
except OSError:
with suppress(Exception):
logger.warning("Failed to remove temporary output file %s", temporary, exc_info=True)
def publish_bytes_atomically(path: Path, content: bytes) -> None:
"""Replace *path* with complete bytes, preserving its existing mode.
New files use ``0666 & ~umask``, like ``Path.write_bytes``, so captures
remain readable through sandbox mounts under the usual ``0022`` umask.
A hidden, exclusively created sibling keeps partial writes away from the
final path. Failure before replacement leaves the previous file intact.
"""
try:
mode = stat.S_IMODE(path.stat().st_mode)
except FileNotFoundError:
mode = None
temporary = path.with_name(f".{path.name}.{uuid4().hex}.tmp")
flags = os.O_CREAT | os.O_EXCL | os.O_WRONLY | getattr(os, "O_BINARY", 0)
# Let the kernel apply umask; reading it via os.umask would mutate
# process-global state while other threads may be creating files.
fd = os.open(temporary, flags, 0o666)
try:
handle = os.fdopen(fd, "wb")
fd = None # The file object now owns the descriptor.
with handle:
handle.write(content)
handle.flush()
if mode is not None:
if os.name == "nt":
os.chmod(temporary, mode)
else:
os.fchmod(handle.fileno(), mode)
os.fsync(handle.fileno())
os.replace(temporary, path)
except BaseException:
cleanup_failed_atomic_write(temporary, fd)
raise