fix: atomic save keeps the store's file permissions
T1-9: _save wrote a fresh temp file with umask permissions and os.replace'd it over the target, so a chmod 600 store became 0644 after the first set(). copy the target's mode onto the temp file before the payload is written. Signed-off-by: disqualifier <dev@disqualifier.me>
This commit is contained in:
1 parent
4ba768a3bf
commit
8607046fe9
1 file changed
+6
-1
+6
-1
@@ -18,6 +18,7 @@ cross-process storage.
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import shutil
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
import asyncio
|
import asyncio
|
||||||
@@ -117,7 +118,9 @@ class AioKV:
|
|||||||
"""write atomically: temp file in the same dir, then os.replace over the realpath'd
|
"""write atomically: temp file in the same dir, then os.replace over the realpath'd
|
||||||
target (symlink-safe). process-crash safe (a crash mid-write leaves the prior good
|
target (symlink-safe). process-crash safe (a crash mid-write leaves the prior good
|
||||||
file intact), NOT power-loss safe - no fsync, so an OS/power failure can still lose
|
file intact), NOT power-loss safe - no fsync, so an OS/power failure can still lose
|
||||||
the last write (acceptable: reconstructible single-process state, not a db)"""
|
the last write (acceptable: reconstructible single-process state, not a db). an
|
||||||
|
existing target's permission bits are copied onto the temp file before the payload
|
||||||
|
is written, so a chmod 600 store stays 600 instead of taking the umask default"""
|
||||||
target = await asyncio.to_thread(os.path.realpath, self.file)
|
target = await asyncio.to_thread(os.path.realpath, self.file)
|
||||||
directory = os.path.dirname(target) or "."
|
directory = os.path.dirname(target) or "."
|
||||||
await asyncio.to_thread(os.makedirs, directory, exist_ok=True)
|
await asyncio.to_thread(os.makedirs, directory, exist_ok=True)
|
||||||
@@ -127,6 +130,8 @@ class AioKV:
|
|||||||
tmp = f"{target}.{os.getpid()}.tmp"
|
tmp = f"{target}.{os.getpid()}.tmp"
|
||||||
try:
|
try:
|
||||||
async with aiofiles.open(tmp, mode="w", encoding="utf-8") as f:
|
async with aiofiles.open(tmp, mode="w", encoding="utf-8") as f:
|
||||||
|
if await asyncio.to_thread(os.path.exists, target):
|
||||||
|
await asyncio.to_thread(shutil.copymode, target, tmp)
|
||||||
await f.write(payload)
|
await f.write(payload)
|
||||||
await asyncio.to_thread(os.replace, tmp, target)
|
await asyncio.to_thread(os.replace, tmp, target)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
Reference in new issue
Block a user