aboutsummaryrefslogtreecommitdiffstats
path: root/rename_exchange.py
blob: b1b02cb5a7a0604013eec156f30a34dadf6e9b99 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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))