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