diff options
Diffstat (limited to 'rename_exchange.py')
-rw-r--r-- | rename_exchange.py | 48 |
1 files changed, 48 insertions, 0 deletions
diff --git a/rename_exchange.py b/rename_exchange.py new file mode 100644 index 0000000..b1b02cb --- /dev/null +++ b/rename_exchange.py @@ -0,0 +1,48 @@ +# © 2022 Nicko van Someren +# License: MIT + +# pylint: disable=missing-module-docstring + +from ctypes import CDLL, get_errno +from os import strerror, PathLike, fsencode +from platform import machine +from typing import Optional + +SYSCALL_ARCH_MAP = { + 'x86_64': 316, + 'armv6l': 382, + 'armv7l': 382, + 'aarch64': 276, + 'i386': 353, +} + +libc = CDLL('libc.so.6', use_errno=True) +syscall_function = libc.syscall +ARCH = machine() +RENAME_EXCHANGE = 2 +AT_FDCWD = -100 + +if ARCH not in SYSCALL_ARCH_MAP: + raise NotImplementedError(f'Unsupported architecture: {ARCH}') + +SYSCALL_RENAMEAT2 = SYSCALL_ARCH_MAP[ARCH] + +def rename_exchange(oldpath : PathLike, + newpath : PathLike, + olddirfd : Optional[int] = None, + newdirfd : Optional[int] = None) -> None: + """Atomically exchange oldpath and newpath. Both pathnames must + exist but may be of different types (e.g., one could be a non-empty + directory and the other a symbolic link).""" + + result = syscall_function( + SYSCALL_RENAMEAT2, + olddirfd if olddirfd is not None else AT_FDCWD, + fsencode(oldpath), + newdirfd if newdirfd is not None else AT_FDCWD, + fsencode(newpath), + RENAME_EXCHANGE, + ) + if result != 0: + errno = get_errno() + raise OSError(errno, strerror(errno)) |