mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-23 20:29:06 -05:00
fix: give atomic writes the umask mode
mkstemp creates its scratch 0600 and os.replace kept that mode, so database.json, the manifests, the packs, the recipe snapshots and the large-file cache were readable by their owner only. set_plain_mode gives the scratch 0o666 less the umask, as open() would, before each rename. A test walks every function that renames a mkstemp file.
This commit is contained in:
1 parent
66937d21d7
commit
b8baf92f1d
9 files changed
+122
-2
No files matched your search
@@ -50,6 +50,25 @@ def write_if_changed(
|
||||
return True
|
||||
|
||||
|
||||
def plain_file_mode() -> int:
|
||||
"""The mode open() gives a new file: 0o666 less the process umask."""
|
||||
# Reading the umask sets it, so it is put back at once.
|
||||
umask = os.umask(0)
|
||||
os.umask(umask)
|
||||
return 0o666 & ~umask
|
||||
|
||||
|
||||
def set_plain_mode(path: str | os.PathLike) -> None:
|
||||
"""Give a scratch file the mode a plain open() would have.
|
||||
|
||||
mkstemp creates its file 0600 so no other user reads it while it is
|
||||
written. Renamed into place, that mode stayed: database.json, the
|
||||
manifests, the packs and the recipe snapshots came out unreadable to
|
||||
every other account on the machine and to a web server serving them.
|
||||
"""
|
||||
os.chmod(path, plain_file_mode())
|
||||
|
||||
|
||||
def write_text_atomic(path: str, content: str) -> None:
|
||||
"""Write a whole file or nothing.
|
||||
|
||||
@@ -65,6 +84,7 @@ def write_text_atomic(path: str, content: str) -> None:
|
||||
try:
|
||||
with os.fdopen(handle, "w", encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
set_plain_mode(scratch)
|
||||
os.replace(scratch, path)
|
||||
except BaseException:
|
||||
with contextlib.suppress(OSError):
|
||||
@@ -81,6 +101,7 @@ def write_bytes_atomic(path: str, content: bytes) -> None:
|
||||
try:
|
||||
with os.fdopen(handle, "wb") as f:
|
||||
f.write(content)
|
||||
set_plain_mode(scratch)
|
||||
os.replace(scratch, path)
|
||||
except BaseException:
|
||||
with contextlib.suppress(OSError):
|
||||
|
||||
@@ -2805,6 +2805,8 @@ from artifacts import ( # noqa: E402,F401
|
||||
write_if_changed,
|
||||
write_text_atomic,
|
||||
copy_file_atomic,
|
||||
plain_file_mode,
|
||||
set_plain_mode,
|
||||
ArtifactLockBusy,
|
||||
artifact_lock,
|
||||
hold_artifact_lock,
|
||||
|
||||
@@ -27,6 +27,7 @@ from common import (
|
||||
list_registered_platforms,
|
||||
load_provenance_snapshots,
|
||||
require_yaml,
|
||||
set_plain_mode,
|
||||
source_yaml_paths,
|
||||
variant_base_name,
|
||||
write_if_changed,
|
||||
@@ -309,6 +310,7 @@ def _restore(cached: str, path: str) -> None:
|
||||
os.close(fd)
|
||||
try:
|
||||
shutil.copy2(cached, scratch)
|
||||
set_plain_mode(scratch)
|
||||
os.replace(scratch, path)
|
||||
finally:
|
||||
if os.path.exists(scratch):
|
||||
|
||||
@@ -14,7 +14,7 @@ from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable, Iterator
|
||||
|
||||
from artifacts import _build_timestamp
|
||||
from artifacts import _build_timestamp, set_plain_mode
|
||||
import argparse
|
||||
import contextlib
|
||||
import functools
|
||||
@@ -221,6 +221,7 @@ def _writing_pack(zip_path: str) -> Iterator[zipfile.ZipFile]:
|
||||
try:
|
||||
with zipfile.ZipFile(scratch, "w", zipfile.ZIP_DEFLATED) as zf:
|
||||
yield zf
|
||||
set_plain_mode(scratch)
|
||||
os.replace(scratch, zip_path)
|
||||
except BaseException:
|
||||
Path(scratch).unlink(missing_ok=True)
|
||||
@@ -4573,6 +4574,7 @@ def inject_manifest(zip_path: str, manifest: dict) -> None:
|
||||
continue
|
||||
dst.writestr(item, src.read(item.filename))
|
||||
_write_generated_member(dst, "manifest.json", manifest_json)
|
||||
set_plain_mode(scratch)
|
||||
os.replace(scratch, zip_path)
|
||||
except BaseException:
|
||||
Path(scratch).unlink(missing_ok=True)
|
||||
|
||||
@@ -19,6 +19,7 @@ from collections import Counter
|
||||
from collections.abc import Iterable
|
||||
from pathlib import Path
|
||||
|
||||
from artifacts import set_plain_mode
|
||||
from hashing import compute_hashes
|
||||
|
||||
|
||||
@@ -219,6 +220,7 @@ def _fetch_asset(
|
||||
compute_hashes(tmp_path), expected_sha1, expected_md5
|
||||
):
|
||||
return None
|
||||
set_plain_mode(tmp_path)
|
||||
os.replace(tmp_path, cached)
|
||||
return cached
|
||||
finally:
|
||||
|
||||
@@ -25,7 +25,7 @@ import urllib.request
|
||||
import zipfile
|
||||
from collections.abc import Iterator
|
||||
from pathlib import Path
|
||||
from artifacts import file_lock as _file_lock
|
||||
from artifacts import file_lock as _file_lock, set_plain_mode
|
||||
from common import github_token, platform_arg, require_platform, yaml_load
|
||||
|
||||
try:
|
||||
@@ -133,6 +133,7 @@ def _save_versions(
|
||||
with os.fdopen(handle, "w") as f:
|
||||
json.dump(versions, f, indent=2, sort_keys=True)
|
||||
f.write("\n")
|
||||
set_plain_mode(scratch)
|
||||
os.replace(scratch, path)
|
||||
except BaseException:
|
||||
with contextlib.suppress(OSError):
|
||||
|
||||
@@ -21,6 +21,8 @@ from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from artifacts import set_plain_mode
|
||||
|
||||
USER_AGENT = "retrobios-profile-sync/1.0"
|
||||
# Where fetched upstream files live, shared by every reader of this cache.
|
||||
CACHE_DIR = ".cache/upstream"
|
||||
@@ -343,6 +345,7 @@ def write_cache(target: Path, text: str) -> None:
|
||||
try:
|
||||
with os.fdopen(fd, "w", encoding="utf-8") as fh:
|
||||
fh.write(text)
|
||||
set_plain_mode(scratch)
|
||||
os.replace(scratch, target)
|
||||
except OSError:
|
||||
Path(scratch).unlink(missing_ok=True)
|
||||
|
||||
@@ -14,6 +14,7 @@ import sys
|
||||
import tempfile
|
||||
import zipfile
|
||||
|
||||
from artifacts import set_plain_mode
|
||||
from hashing import md5sum
|
||||
|
||||
|
||||
@@ -141,6 +142,7 @@ def _write_member_cache(path: str, data: dict[str, list[list]]) -> None:
|
||||
try:
|
||||
with os.fdopen(fd, "w", encoding="utf-8") as fh:
|
||||
json.dump(data, fh, separators=(",", ":"))
|
||||
set_plain_mode(scratch)
|
||||
os.replace(scratch, path)
|
||||
except OSError as exc:
|
||||
print(f"warning: could not write {path}: {exc}", file=sys.stderr)
|
||||
|
||||
Reference in new issue
Block a user