aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xbin/udt-accent35
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")