ensure renameat2 checks only run when required

This commit is contained in:
Gregory Becker 2023-03-28 14:36:08 -07:00
parent 9baea936f2
commit 6bad473e4e

View File

@ -13,18 +13,32 @@
RENAME_EXCHANGE = 2
AT_FDCWD = -100
libc: Optional[ctypes.CDLL] = None
try:
# CDLL(None) returns the python process
# python links against libc, so we can treat this as a libc handle
# we could also use CDLL("libc.so.6") but this is (irrelevantly) more future proof
libc = ctypes.CDLL(None)
except (OSError, TypeError):
# OSError if the call fails,
# TypeError on Windows
pass
# object for renameat2 not set
# We use None for not found and notset for not set so that boolean checks work
# properly in client code
notset = object()
_renameat2 = notset
renameat2 = getattr(libc, "renameat2", None)
def set_renameat2():
libc: Optional[ctypes.CDLL] = None
try:
# CDLL(None) returns the python process
# python links against libc, so we can treat this as a libc handle
# we could also use CDLL("libc.so.6") but this is (irrelevantly) more future proof
libc = ctypes.CDLL(None)
except (OSError, TypeError):
# OSError if the call fails,
# TypeError on Windows
return None
return getattr(libc, "renameat2", None)
def renameat2():
if _renameat2 is notset:
_renameat2 = set_renameat2()
return _renameat2
def atomic_update(oldpath, newpath):
@ -35,7 +49,7 @@ def atomic_update(oldpath, newpath):
on other systems, oldpath is not affected but all paths are abstracted
by a symlink to allow for atomic updates.
"""
if renameat2:
if renameat2():
return atomic_update_renameat2(oldpath, newpath)
else:
return atomic_update_symlink(oldpath, newpath)
@ -50,7 +64,7 @@ def atomic_update_renameat2(src, dest):
if not dest_exists:
fs.touch(dest)
try:
rc = renameat2(AT_FDCWD, src.encode(), AT_FDCWD, dest.encode(), RENAME_EXCHANGE)
rc = renameat2()(AT_FDCWD, src.encode(), AT_FDCWD, dest.encode(), RENAME_EXCHANGE)
if rc:
raise OSError(f"renameat2 failed to exchange {src} and {dest}")
if not dest_exists: