mirror of
https://github.com/borgbackup/borg.git
synced 2026-04-29 10:01:06 -04:00
refactor id <-> name lookup for monkeypatching
we can't monkeypatch stuff in Cython/C code, so we go over python module attribute lookup. that way, we can more easily test some functions that internally do id<->name lookups.
This commit is contained in:
parent
6808809818
commit
798bd9ed0d
9 changed files with 128 additions and 92 deletions
|
|
@ -4,12 +4,16 @@ Platform-specific APIs.
|
|||
Public APIs are documented in platform.base.
|
||||
"""
|
||||
|
||||
from types import ModuleType
|
||||
|
||||
from ..platformflags import is_win32, is_linux, is_freebsd, is_netbsd, is_darwin, is_cygwin
|
||||
|
||||
from .base import ENOATTR, API_VERSION
|
||||
from .base import SaveFile, sync_dir, fdatasync, safe_fadvise
|
||||
from .base import get_process_id, fqdn, hostname, hostid
|
||||
|
||||
platform_ug: ModuleType | None = None # make mypy happy
|
||||
|
||||
if is_linux: # pragma: linux only
|
||||
from .linux import API_VERSION as OS_API_VERSION
|
||||
from .linux import listxattr, getxattr, setxattr
|
||||
|
|
@ -19,7 +23,8 @@ if is_linux: # pragma: linux only
|
|||
from .posix import process_alive, local_pid_alive
|
||||
from .posix import swidth
|
||||
from .posix import get_errno
|
||||
from .posix import uid2user, user2uid, gid2group, group2gid, getosusername
|
||||
from .posix import getosusername
|
||||
from . import posix_ug as platform_ug
|
||||
elif is_freebsd: # pragma: freebsd only
|
||||
from .freebsd import API_VERSION as OS_API_VERSION
|
||||
from .freebsd import listxattr, getxattr, setxattr
|
||||
|
|
@ -30,7 +35,8 @@ elif is_freebsd: # pragma: freebsd only
|
|||
from .posix import process_alive, local_pid_alive
|
||||
from .posix import swidth
|
||||
from .posix import get_errno
|
||||
from .posix import uid2user, user2uid, gid2group, group2gid, getosusername
|
||||
from .posix import getosusername
|
||||
from . import posix_ug as platform_ug
|
||||
elif is_netbsd: # pragma: netbsd only
|
||||
from .netbsd import API_VERSION as OS_API_VERSION
|
||||
from .netbsd import listxattr, getxattr, setxattr
|
||||
|
|
@ -40,7 +46,8 @@ elif is_netbsd: # pragma: netbsd only
|
|||
from .posix import process_alive, local_pid_alive
|
||||
from .posix import swidth
|
||||
from .posix import get_errno
|
||||
from .posix import uid2user, user2uid, gid2group, group2gid, getosusername
|
||||
from .posix import getosusername
|
||||
from . import posix_ug as platform_ug
|
||||
elif is_darwin: # pragma: darwin only
|
||||
from .darwin import API_VERSION as OS_API_VERSION
|
||||
from .darwin import listxattr, getxattr, setxattr
|
||||
|
|
@ -52,7 +59,8 @@ elif is_darwin: # pragma: darwin only
|
|||
from .posix import process_alive, local_pid_alive
|
||||
from .posix import swidth
|
||||
from .posix import get_errno
|
||||
from .posix import uid2user, user2uid, gid2group, group2gid, getosusername
|
||||
from .posix import getosusername
|
||||
from . import posix_ug as platform_ug
|
||||
elif not is_win32: # pragma: posix only
|
||||
# Generic code for all other POSIX OSes
|
||||
OS_API_VERSION = API_VERSION
|
||||
|
|
@ -63,7 +71,8 @@ elif not is_win32: # pragma: posix only
|
|||
from .posix import process_alive, local_pid_alive
|
||||
from .posix import swidth
|
||||
from .posix import get_errno
|
||||
from .posix import uid2user, user2uid, gid2group, group2gid, getosusername
|
||||
from .posix import getosusername
|
||||
from . import posix_ug as platform_ug
|
||||
else: # pragma: win32 only
|
||||
# Win32-specific stuff
|
||||
OS_API_VERSION = API_VERSION
|
||||
|
|
@ -73,7 +82,8 @@ else: # pragma: win32 only
|
|||
from .base import SyncFile
|
||||
from .windows import process_alive, local_pid_alive
|
||||
from .base import swidth
|
||||
from .windows import uid2user, user2uid, gid2group, group2gid, getosusername
|
||||
from .windows import getosusername
|
||||
from . import windows_ug as platform_ug
|
||||
|
||||
|
||||
def get_birthtime_ns(st, path, fd=None):
|
||||
|
|
@ -86,3 +96,21 @@ def get_birthtime_ns(st, path, fd=None):
|
|||
return int(st.st_birthtime * 10**9)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
# have some wrapper functions, so we can monkeypatch the functions in platform_ug.
|
||||
# for normal usage from outside the platform package, always import these:
|
||||
def uid2user(uid, default=None):
|
||||
return platform_ug._uid2user(uid, default)
|
||||
|
||||
|
||||
def gid2group(gid, default=None):
|
||||
return platform_ug._gid2group(gid, default)
|
||||
|
||||
|
||||
def user2uid(user, default=None):
|
||||
return platform_ug._user2uid(user, default)
|
||||
|
||||
|
||||
def group2gid(group, default=None):
|
||||
return platform_ug._group2gid(group, default)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from libc.stdint cimport uint32_t
|
|||
from libc cimport errno
|
||||
from posix.time cimport timespec
|
||||
|
||||
from .posix import user2uid, group2gid
|
||||
from . import posix_ug
|
||||
from ..helpers import safe_decode, safe_encode
|
||||
from .xattr import _listxattr_inner, _getxattr_inner, _setxattr_inner, split_string0
|
||||
|
||||
|
|
@ -108,10 +108,10 @@ def _remove_numeric_id_if_possible(acl):
|
|||
if entry:
|
||||
fields = entry.split(':')
|
||||
if fields[0] == 'user':
|
||||
if user2uid(fields[2]) is not None:
|
||||
if posix_ug._user2uid(fields[2]) is not None:
|
||||
fields[1] = fields[3] = ''
|
||||
elif fields[0] == 'group':
|
||||
if group2gid(fields[2]) is not None:
|
||||
if posix_ug._group2gid(fields[2]) is not None:
|
||||
fields[1] = fields[3] = ''
|
||||
entries.append(':'.join(fields))
|
||||
return safe_encode('\n'.join(entries))
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import re
|
|||
import stat
|
||||
|
||||
from .posix import posix_acl_use_stored_uid_gid
|
||||
from .posix import user2uid, group2gid, uid2user, gid2group
|
||||
from . import posix_ug
|
||||
from ..helpers import workarounds
|
||||
from ..helpers import safe_decode, safe_encode
|
||||
from .base import SyncFile as BaseSyncFile
|
||||
|
|
@ -99,14 +99,14 @@ def _acl_from_numeric_to_named_with_id(acl):
|
|||
uid = int(name)
|
||||
except ValueError:
|
||||
uid = None
|
||||
uname = uid2user(uid, name) if uid is not None else name
|
||||
uname = posix_ug._uid2user(uid, name) if uid is not None else name
|
||||
entries.append(':'.join([typ, uname, perm, str(uid if uid is not None else name)]))
|
||||
elif name and typ == 'group':
|
||||
try:
|
||||
gid = int(name)
|
||||
except ValueError:
|
||||
gid = None
|
||||
gname = gid2group(gid, name) if gid is not None else name
|
||||
gname = posix_ug._gid2group(gid, name) if gid is not None else name
|
||||
entries.append(':'.join([typ, gname, perm, str(gid if gid is not None else name)]))
|
||||
else:
|
||||
# owner, group_obj, mask, other (empty name field) stay as-is
|
||||
|
|
@ -261,9 +261,9 @@ def acl_use_local_uid_gid(acl):
|
|||
if entry:
|
||||
fields = entry.split(':')
|
||||
if fields[0] == 'user' and fields[1]:
|
||||
fields[1] = str(user2uid(fields[1], fields[3]))
|
||||
fields[1] = str(posix_ug._user2uid(fields[1], fields[3]))
|
||||
elif fields[0] == 'group' and fields[1]:
|
||||
fields[1] = str(group2gid(fields[1], fields[3]))
|
||||
fields[1] = str(posix_ug._group2gid(fields[1], fields[3]))
|
||||
entries.append(':'.join(fields[:3]))
|
||||
return safe_encode('\n'.join(entries))
|
||||
|
||||
|
|
@ -277,9 +277,9 @@ cdef acl_append_numeric_ids(acl):
|
|||
if entry:
|
||||
type, name, permission = entry.split(':')
|
||||
if name and type == 'user':
|
||||
entries.append(':'.join([type, name, permission, str(user2uid(name, name))]))
|
||||
entries.append(':'.join([type, name, permission, str(posix_ug._user2uid(name, name))]))
|
||||
elif name and type == 'group':
|
||||
entries.append(':'.join([type, name, permission, str(group2gid(name, name))]))
|
||||
entries.append(':'.join([type, name, permission, str(posix_ug._group2gid(name, name))]))
|
||||
else:
|
||||
entries.append(entry)
|
||||
return safe_encode('\n'.join(entries))
|
||||
|
|
@ -294,10 +294,10 @@ cdef acl_numeric_ids(acl):
|
|||
if entry:
|
||||
type, name, permission = entry.split(':')
|
||||
if name and type == 'user':
|
||||
uid = str(user2uid(name, name))
|
||||
uid = str(posix_ug._user2uid(name, name))
|
||||
entries.append(':'.join([type, uid, permission, uid]))
|
||||
elif name and type == 'group':
|
||||
gid = str(group2gid(name, name))
|
||||
gid = str(posix_ug._group2gid(name, name))
|
||||
entries.append(':'.join([type, gid, permission, gid]))
|
||||
else:
|
||||
entries.append(entry)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import errno
|
||||
import os
|
||||
import grp
|
||||
import pwd
|
||||
from functools import lru_cache
|
||||
|
||||
from . import posix_ug
|
||||
|
||||
from libc.errno cimport errno as c_errno
|
||||
|
||||
|
|
@ -77,42 +76,6 @@ def local_pid_alive(pid):
|
|||
return True
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def uid2user(uid, default=None):
|
||||
try:
|
||||
return pwd.getpwuid(uid).pw_name
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def user2uid(user, default=None):
|
||||
if not user:
|
||||
return default
|
||||
try:
|
||||
return pwd.getpwnam(user).pw_uid
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def gid2group(gid, default=None):
|
||||
try:
|
||||
return grp.getgrgid(gid).gr_name
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def group2gid(group, default=None):
|
||||
if not group:
|
||||
return default
|
||||
try:
|
||||
return grp.getgrnam(group).gr_gid
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
|
||||
def posix_acl_use_stored_uid_gid(acl):
|
||||
"""Replace the user/group field with the stored uid/gid."""
|
||||
assert isinstance(acl, bytes)
|
||||
|
|
@ -131,4 +94,4 @@ def posix_acl_use_stored_uid_gid(acl):
|
|||
def getosusername():
|
||||
"""Return the OS username."""
|
||||
uid = os.getuid()
|
||||
return uid2user(uid, uid)
|
||||
return posix_ug._uid2user(uid, uid)
|
||||
|
|
|
|||
39
src/borg/platform/posix_ug.py
Normal file
39
src/borg/platform/posix_ug.py
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import grp
|
||||
import pwd
|
||||
from functools import lru_cache
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def _uid2user(uid, default=None):
|
||||
try:
|
||||
return pwd.getpwuid(uid).pw_name
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def _user2uid(user, default=None):
|
||||
if not user:
|
||||
return default
|
||||
try:
|
||||
return pwd.getpwnam(user).pw_uid
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def _gid2group(gid, default=None):
|
||||
try:
|
||||
return grp.getgrgid(gid).gr_name
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def _group2gid(group, default=None):
|
||||
if not group:
|
||||
return default
|
||||
try:
|
||||
return grp.getgrnam(group).gr_gid
|
||||
except KeyError:
|
||||
return default
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
import os
|
||||
import platform
|
||||
from functools import lru_cache
|
||||
|
||||
|
||||
cdef extern from 'windows.h':
|
||||
|
|
@ -14,32 +13,6 @@ cdef extern from 'windows.h':
|
|||
cdef extern int PROCESS_QUERY_INFORMATION
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def uid2user(uid, default=None):
|
||||
return "root"
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def user2uid(user, default=None):
|
||||
if not user:
|
||||
# user is either None or the empty string
|
||||
return default
|
||||
return 0
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def gid2group(gid, default=None):
|
||||
return "root"
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def group2gid(group, default=None):
|
||||
if not group:
|
||||
# group is either None or the empty string
|
||||
return default
|
||||
return 0
|
||||
|
||||
|
||||
def getosusername():
|
||||
"""Return the OS username."""
|
||||
return os.getlogin()
|
||||
|
|
|
|||
33
src/borg/platform/windows_ug.py
Normal file
33
src/borg/platform/windows_ug.py
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
from functools import lru_cache
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def _uid2user(uid, default=None):
|
||||
# On Windows, Borg uses a simplified mapping for ownership fields.
|
||||
# Return a stable placeholder name.
|
||||
return "root"
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def _user2uid(user, default=None):
|
||||
if not user:
|
||||
# user is either None or the empty string
|
||||
return default
|
||||
# Use 0 as the canonical uid placeholder on Windows.
|
||||
return 0
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def _gid2group(gid, default=None):
|
||||
# On Windows, Borg uses a simplified mapping for ownership fields.
|
||||
# Return a stable placeholder name.
|
||||
return "root"
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def _group2gid(group, default=None):
|
||||
if not group:
|
||||
# group is either None or the empty string
|
||||
return default
|
||||
# Use 0 as the canonical gid placeholder on Windows.
|
||||
return 0
|
||||
|
|
@ -379,7 +379,7 @@ def test_get_item_uid_gid():
|
|||
assert gid == 8
|
||||
|
||||
if not is_win32:
|
||||
# Due to the hack in borg.platform.windows, user2uid/group2gid always return 0
|
||||
# Due to the hack in borg.platform.windows_ug, user2uid/group2gid always return 0
|
||||
# (no matter which username we ask for), and they never raise a KeyError (e.g., for
|
||||
# a non-existing user/group name). Thus, these tests can currently not succeed on win32.
|
||||
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ def test_numeric_to_named_with_id_simple(monkeypatch):
|
|||
from ...platform.linux import _acl_from_numeric_to_named_with_id
|
||||
|
||||
# Pretend uid 1000 -> 'alice', gid 100 -> 'staff'
|
||||
from ...platform import posix
|
||||
from ...platform import platform_ug
|
||||
|
||||
def _uid2user(uid, default=None):
|
||||
if uid == 1000:
|
||||
|
|
@ -141,8 +141,8 @@ def test_numeric_to_named_with_id_simple(monkeypatch):
|
|||
return "staff"
|
||||
return default
|
||||
|
||||
monkeypatch.setattr(posix, "uid2user", _uid2user)
|
||||
monkeypatch.setattr(posix, "gid2group", _gid2group)
|
||||
monkeypatch.setattr(platform_ug, "_uid2user", _uid2user)
|
||||
monkeypatch.setattr(platform_ug, "_gid2group", _gid2group)
|
||||
|
||||
src = b"\n".join([b"user::rwx", b"user:1000:r-x", b"group::r--", b"group:100:r--", b"mask::r-x", b"other::r--"])
|
||||
out = _acl_from_numeric_to_named_with_id(src)
|
||||
|
|
@ -159,7 +159,7 @@ def test_numeric_to_named_with_id_nonexistent_ids(monkeypatch):
|
|||
from ...platform.linux import _acl_from_numeric_to_named_with_id
|
||||
|
||||
# Map functions return default (the given fallback), so names stay numeric but still append the fourth field
|
||||
from ...platform import posix
|
||||
from ...platform import platform_ug
|
||||
|
||||
def _uid2user(uid, default=None):
|
||||
return default
|
||||
|
|
@ -167,8 +167,8 @@ def test_numeric_to_named_with_id_nonexistent_ids(monkeypatch):
|
|||
def _gid2group(gid, default=None):
|
||||
return default
|
||||
|
||||
monkeypatch.setattr(posix, "uid2user", _uid2user)
|
||||
monkeypatch.setattr(posix, "gid2group", _gid2group)
|
||||
monkeypatch.setattr(platform_ug, "_uid2user", _uid2user)
|
||||
monkeypatch.setattr(platform_ug, "_gid2group", _gid2group)
|
||||
|
||||
src = b"user:9999:r--\ngroup:8888:r--\n"
|
||||
out = _acl_from_numeric_to_named_with_id(src)
|
||||
|
|
|
|||
Loading…
Reference in a new issue