Stream ``src`` to ``dest`` through a temp file, then atomically replace. Streams (never buffers the whole file) so copying a large raw PDF does not spike peak memory. The temp-file + ``os.replace`` means a torn intermediate state can never be observed at ``dest``. Used by snapshot b
(src: Path, dest: Path)
| 68 | |
| 69 | |
| 70 | def _copy_file_atomic(src: Path, dest: Path) -> None: |
| 71 | """Stream ``src`` to ``dest`` through a temp file, then atomically replace. |
| 72 | |
| 73 | Streams (never buffers the whole file) so copying a large raw PDF does |
| 74 | not spike peak memory. The temp-file + ``os.replace`` means a torn |
| 75 | intermediate state can never be observed at ``dest``. Used by snapshot |
| 76 | backup creation, rollback restore, and the cross-filesystem fallback of |
| 77 | :func:`_publish_staged_file` — so every byte copy in this module shares |
| 78 | one atomic, streaming, durable semantic: the parent directory is fsynced |
| 79 | and the result carries the umask mode (not ``mkstemp``'s 0600). |
| 80 | """ |
| 81 | dest.parent.mkdir(parents=True, exist_ok=True) |
| 82 | # Capture the destination mode before the temp file shadows it: a brand- |
| 83 | # new file gets the process umask mode (0o666 & ~umask), an existing file |
| 84 | # keeps its current mode — the same rule ``atomic_write_bytes`` applies. |
| 85 | mode = _target_mode(dest) |
| 86 | fd, tmp_name = tempfile.mkstemp(prefix=f".{dest.name}.", suffix=".tmp", dir=dest.parent) |
| 87 | tmp_path = Path(tmp_name) |
| 88 | try: |
| 89 | with os.fdopen(fd, "wb") as out, src.open("rb") as inp: |
| 90 | shutil.copyfileobj(inp, out) |
| 91 | out.flush() |
| 92 | os.fsync(out.fileno()) |
| 93 | os.replace(tmp_path, dest) |
| 94 | _apply_mode(dest, mode) |
| 95 | _fsync_directory(dest.parent) |
| 96 | finally: |
| 97 | tmp_path.unlink(missing_ok=True) |
| 98 | |
| 99 | |
| 100 | def _publish_staged_file(src: Path, dest: Path) -> None: |
no test coverage detected