From 0286fb6dc42113f4774f4d226a831fe40f441396 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Wed, 12 Nov 2025 01:49:04 +0100 Subject: [PATCH] NetBSD: copy xattr implementation of FreeBSD, fixes #1332 --- setup.py | 9 +++- src/borg/platform/__init__.py | 12 ++++- src/borg/platform/netbsd.pyx | 89 +++++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 src/borg/platform/netbsd.pyx diff --git a/setup.py b/setup.py index 6217cac00..acb80596f 100644 --- a/setup.py +++ b/setup.py @@ -61,6 +61,7 @@ platform_linux_source = "src/borg/platform/linux.pyx" platform_syncfilerange_source = "src/borg/platform/syncfilerange.pyx" platform_darwin_source = "src/borg/platform/darwin.pyx" platform_freebsd_source = "src/borg/platform/freebsd.pyx" +platform_netbsd_source = "src/borg/platform/netbsd.pyx" platform_windows_source = "src/borg/platform/windows.pyx" cython_sources = [ @@ -76,6 +77,7 @@ cython_sources = [ platform_linux_source, platform_syncfilerange_source, platform_freebsd_source, + platform_netbsd_source, platform_darwin_source, platform_windows_source, ] @@ -197,6 +199,7 @@ if not on_rtd: "borg.platform.syncfilerange", [platform_syncfilerange_source], extra_compile_args=cflags ) freebsd_ext = Extension("borg.platform.freebsd", [platform_freebsd_source], extra_compile_args=cflags) + netbsd_ext = Extension("borg.platform.netbsd", [platform_netbsd_source], extra_compile_args=cflags) darwin_ext = Extension("borg.platform.darwin", [platform_darwin_source], extra_compile_args=cflags) windows_ext = Extension("borg.platform.windows", [platform_windows_source], extra_compile_args=cflags) @@ -209,6 +212,8 @@ if not on_rtd: ext_modules.append(syncfilerange_ext) elif sys.platform.startswith("freebsd"): ext_modules.append(freebsd_ext) + elif sys.platform.startswith("netbsd"): + ext_modules.append(netbsd_ext) elif sys.platform == "darwin": ext_modules.append(darwin_ext) @@ -230,7 +235,9 @@ if not on_rtd: # generate C code from Cython for ALL supported platforms, so we have them in the sdist. # the sdist does not require Cython at install time, so we need all as C. - cythonize([posix_ext, linux_ext, syncfilerange_ext, freebsd_ext, darwin_ext, windows_ext], **cython_opts) + cythonize( + [posix_ext, linux_ext, syncfilerange_ext, freebsd_ext, netbsd_ext, darwin_ext, windows_ext], **cython_opts + ) # generate C code from Cython for THIS platform (and for all platform-independent Cython parts). ext_modules = cythonize(ext_modules, **cython_opts) diff --git a/src/borg/platform/__init__.py b/src/borg/platform/__init__.py index 1f008cd41..f73f616d3 100644 --- a/src/borg/platform/__init__.py +++ b/src/borg/platform/__init__.py @@ -4,7 +4,7 @@ Platform-specific APIs. Public APIs are documented in platform.base. """ -from ..platformflags import is_win32, is_linux, is_freebsd, is_darwin, is_cygwin +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 @@ -31,6 +31,16 @@ elif is_freebsd: # pragma: freebsd only from .posix import swidth from .posix import get_errno from .posix import uid2user, user2uid, gid2group, group2gid, getosusername +elif is_netbsd: # pragma: netbsd only + from .netbsd import API_VERSION as OS_API_VERSION + from .netbsd import listxattr, getxattr, setxattr + from .base import acl_get, acl_set + from .base import set_flags, get_flags + from .base import SyncFile + 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 elif is_darwin: # pragma: darwin only from .darwin import API_VERSION as OS_API_VERSION from .darwin import listxattr, getxattr, setxattr diff --git a/src/borg/platform/netbsd.pyx b/src/borg/platform/netbsd.pyx new file mode 100644 index 000000000..b79c02abc --- /dev/null +++ b/src/borg/platform/netbsd.pyx @@ -0,0 +1,89 @@ +from .xattr import _listxattr_inner, _getxattr_inner, _setxattr_inner, split_lstring + +API_VERSION = '1.2_05' + +cdef extern from "sys/extattr.h": + ssize_t c_extattr_list_file "extattr_list_file" (const char *path, int attrnamespace, void *data, size_t nbytes) + ssize_t c_extattr_list_link "extattr_list_link" (const char *path, int attrnamespace, void *data, size_t nbytes) + ssize_t c_extattr_list_fd "extattr_list_fd" (int fd, int attrnamespace, void *data, size_t nbytes) + + ssize_t c_extattr_get_file "extattr_get_file" (const char *path, int attrnamespace, const char *attrname, void *data, size_t nbytes) + ssize_t c_extattr_get_link "extattr_get_link" (const char *path, int attrnamespace, const char *attrname, void *data, size_t nbytes) + ssize_t c_extattr_get_fd "extattr_get_fd" (int fd, int attrnamespace, const char *attrname, void *data, size_t nbytes) + + int c_extattr_set_file "extattr_set_file" (const char *path, int attrnamespace, const char *attrname, const void *data, size_t nbytes) + int c_extattr_set_link "extattr_set_link" (const char *path, int attrnamespace, const char *attrname, const void *data, size_t nbytes) + int c_extattr_set_fd "extattr_set_fd" (int fd, int attrnamespace, const char *attrname, const void *data, size_t nbytes) + + int EXTATTR_NAMESPACE_USER + + +# On NetBSD, Borg currently only deals with the USER namespace, as it is unclear +# whether (and, if so, how exactly) it should deal with the SYSTEM namespace. +NS_ID_MAP = {b"user": EXTATTR_NAMESPACE_USER, } + + +def split_ns(ns_name, default_ns): + # Split ns_name (which is in the form b"namespace.name") into namespace and name. + # If there is no namespace given in ns_name, default to default_ns. + # We also need to deal with "unexpected" namespaces here — they could come + # from Borg archives made on other operating systems. + ns_name_tuple = ns_name.split(b".", 1) + if len(ns_name_tuple) == 2: + # We have a namespace prefix in the given name. + ns, name = ns_name_tuple + else: + # No namespace given in ns_name (no dot found); maybe data from an old Borg archive. + ns, name = default_ns, ns_name + return ns, name + + +def listxattr(path, *, follow_symlinks=False): + def func(path, buf, size): + if isinstance(path, int): + return c_extattr_list_fd(path, ns_id, buf, size) + else: + if follow_symlinks: + return c_extattr_list_file(path, ns_id, buf, size) + else: + return c_extattr_list_link(path, ns_id, buf, size) + + ns = b"user" + ns_id = NS_ID_MAP[ns] + n, buf = _listxattr_inner(func, path) + return [ns + b"." + name for name in split_lstring(buf[:n]) if name] + + +def getxattr(path, name, *, follow_symlinks=False): + def func(path, name, buf, size): + if isinstance(path, int): + return c_extattr_get_fd(path, ns_id, name, buf, size) + else: + if follow_symlinks: + return c_extattr_get_file(path, ns_id, name, buf, size) + else: + return c_extattr_get_link(path, ns_id, name, buf, size) + + ns, name = split_ns(name, b"user") + ns_id = NS_ID_MAP[ns] # this will raise a KeyError it the namespace is unsupported + n, buf = _getxattr_inner(func, path, name) + return bytes(buf[:n]) + + +def setxattr(path, name, value, *, follow_symlinks=False): + def func(path, name, value, size): + if isinstance(path, int): + return c_extattr_set_fd(path, ns_id, name, value, size) + else: + if follow_symlinks: + return c_extattr_set_file(path, ns_id, name, value, size) + else: + return c_extattr_set_link(path, ns_id, name, value, size) + + ns, name = split_ns(name, b"user") + try: + ns_id = NS_ID_MAP[ns] # this will raise a KeyError it the namespace is unsupported + except KeyError: + pass + else: + _setxattr_inner(func, path, name, value)