aboutsummaryrefslogtreecommitdiffstats
path: root/rename_exchange.py
diff options
context:
space:
mode:
Diffstat (limited to 'rename_exchange.py')
-rw-r--r--rename_exchange.py48
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))