diff options
| author | Danilo M. <danix@danix.xyz> | 2026-09-17 13:26:10 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-09-17 13:26:10 +0200 |
| commit | c474d7bff63496750c88039c4bd55f81dd1d3541 (patch) | |
| tree | c5c25b3ef4ad6b66ecffff67a600c2b7798a8700 /bin | |
| parent | b668857ba6103e539a35c970d79c9a31dca33fee (diff) | |
| download | unified-desktop-theme-c474d7bff63496750c88039c4bd55f81dd1d3541.tar.gz unified-desktop-theme-c474d7bff63496750c88039c4bd55f81dd1d3541.zip | |
fix: make the SDDM palette file readable without a chmod race
Diffstat (limited to 'bin')
| -rwxr-xr-x | bin/udt-accent | 35 |
1 files changed, 30 insertions, 5 deletions
diff --git a/bin/udt-accent b/bin/udt-accent index 8d6a229..4dc7db3 100755 --- a/bin/udt-accent +++ b/bin/udt-accent @@ -60,18 +60,28 @@ SDDM_KEYS = ["base", "mantle", "crust", "surface0", "surface1", "surface2", "red", "yellow", "green", "teal", "blue", "lavender"] -def write_atomic(path, content): +def write_atomic(path, content, mode=None, dir_mode=None): """Write a file atomically, so no reader ever sees it half-written. Every generated file is watched by something: rofi rereads on launch, quickshell watches with a FileView. A torn read shows up as a theme that briefly loses its colours. + + `mode` is applied to the temp file before the rename, so a reader never + opens a file that is briefly unreadable. `dir_mode` does the same for the + parent directory, which umask would otherwise mask. Both default to the + process defaults (mkstemp 0600, umask-derived directory). """ path.parent.mkdir(parents=True, exist_ok=True) + if dir_mode is not None: + # mkdir masks its mode through umask, so set it explicitly. + os.chmod(path.parent, dir_mode) fd, tmp = tempfile.mkstemp(dir=str(path.parent), suffix=".tmp") try: with os.fdopen(fd, "w") as handle: handle.write(content) + if mode is not None: + os.chmod(tmp, mode) os.replace(tmp, path) except BaseException: if os.path.exists(tmp): @@ -319,16 +329,17 @@ def sddm_conf(palette, name, image): def write_sddm(name, image): """Write the live SDDM palette, atomically, world-readable. - World-readable because the greeter runs as the sddm user. The atomic writer - creates the file 0600, so the mode is fixed up here. + World-readable because the greeter runs as the sddm user, and traversable + for the same reason. Both modes are set before the rename, so the greeter + never sees an unreadable file or directory. """ palette = read_palette() if not palette: print("udt-accent: palette.rasi unreadable, skipping SDDM theme", file=sys.stderr) return - write_atomic(SDDM_OUTPUT, sddm_conf(palette, name, image)) - os.chmod(SDDM_OUTPUT, 0o644) + write_atomic(SDDM_OUTPUT, sddm_conf(palette, name, image), + mode=0o644, dir_mode=0o755) def main(image): @@ -424,6 +435,20 @@ def selftest(): assert target.read_text() == text, "second write changed the output" assert target.read_text().count("[General]") == 1, "second write appended" + # The greeter runs as another user, so file and directory modes must be + # set on the temp file before the rename. Fixing the mode up after the + # rename leaves a moment where the greeter can read a 0600 file. + readable = Path(tmpdir) / "readable.conf" + write_atomic(readable, text, mode=0o644) + assert readable.stat().st_mode & 0o777 == 0o644, \ + oct(readable.stat().st_mode & 0o777) + + outdir = Path(tmpdir) / "sub" / "udt" + writable = outdir / "f.conf" + write_atomic(writable, text, mode=0o644, dir_mode=0o755) + assert outdir.stat().st_mode & 0o777 == 0o755, \ + oct(outdir.stat().st_mode & 0o777) + print("selftest OK") |
