Skip to content

Commit 93986b9

Browse files
committed
Platform: Add cross-platform removexattr bindings
Signed-off-by: alighazi288 <51366992+alighazi288@users.noreply.github.com>
1 parent b8034fc commit 93986b9

8 files changed

Lines changed: 141 additions & 11 deletions

File tree

src/borg/platform/__init__.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
platform_ug: ModuleType | None = None # make mypy happy
1919

2020
if is_linux: # pragma: linux only
21-
from .linux import listxattr, getxattr, setxattr
21+
from .linux import listxattr, getxattr, setxattr, removexattr
2222
from .linux import acl_get, acl_set
2323
from .linux import set_flags, get_flags
2424
from .linux import SyncFile
@@ -27,7 +27,7 @@
2727
from .posix import getosusername
2828
from . import posix_ug as platform_ug
2929
elif is_freebsd: # pragma: freebsd only
30-
from .freebsd import listxattr, getxattr, setxattr
30+
from .freebsd import listxattr, getxattr, setxattr, removexattr
3131
from .freebsd import acl_get, acl_set
3232
from .freebsd import set_flags
3333
from .base import get_flags
@@ -37,7 +37,7 @@
3737
from .posix import getosusername
3838
from . import posix_ug as platform_ug
3939
elif is_netbsd: # pragma: netbsd only
40-
from .netbsd import listxattr, getxattr, setxattr
40+
from .netbsd import listxattr, getxattr, setxattr, removexattr
4141
from .base import acl_get, acl_set
4242
from .base import set_flags, get_flags
4343
from .base import SyncFile
@@ -46,7 +46,7 @@
4646
from .posix import getosusername
4747
from . import posix_ug as platform_ug
4848
elif is_darwin: # pragma: darwin only
49-
from .darwin import listxattr, getxattr, setxattr
49+
from .darwin import listxattr, getxattr, setxattr, removexattr
5050
from .darwin import acl_get, acl_set
5151
from .darwin import is_darwin_feature_64_bit_inode, _get_birthtime_ns
5252
from .darwin import set_flags
@@ -59,7 +59,7 @@
5959
from . import posix_ug as platform_ug
6060
elif not is_win32: # pragma: posix only
6161
# Generic code for all other POSIX OSes
62-
from .base import listxattr, getxattr, setxattr
62+
from .base import listxattr, getxattr, setxattr, removexattr
6363
from .base import acl_get, acl_set
6464
from .base import set_flags, get_flags
6565
from .base import SyncFile
@@ -69,7 +69,7 @@
6969
from . import posix_ug as platform_ug
7070
else: # pragma: win32 only
7171
# Win32-specific stuff
72-
from .base import listxattr, getxattr, setxattr
72+
from .base import listxattr, getxattr, setxattr, removexattr
7373
from .base import acl_get, acl_set
7474
from .base import set_flags, get_flags
7575
from .windows import SyncFile

src/borg/platform/base.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,17 @@ def setxattr(path, name, value, *, follow_symlinks=False):
6969
"""
7070

7171

72+
def removexattr(path, name, *, follow_symlinks=False):
73+
"""
74+
Remove an xattr from *path*.
75+
76+
*path* can either be a path (bytes) or an open file descriptor (int).
77+
*name* is the name of the xattr to remove (bytes).
78+
*follow_symlinks* indicates whether symlinks should be followed
79+
and only applies when *path* is not an open file descriptor.
80+
"""
81+
82+
7283
def acl_get(path, item, st, numeric_ids=False, fd=None):
7384
"""
7485
Save ACL entries.

src/borg/platform/darwin.pyx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ from posix.time cimport timespec
66

77
from . import posix_ug
88
from ..helpers import safe_decode, safe_encode
9-
from .xattr import _listxattr_inner, _getxattr_inner, _setxattr_inner, split_string0
9+
from .xattr import _listxattr_inner, _getxattr_inner, _setxattr_inner, _removexattr_inner, split_string0
1010

1111

1212

@@ -32,6 +32,9 @@ cdef extern from "sys/xattr.h":
3232
int c_setxattr "setxattr" (const char *path, const char *name, const void *value, size_t size, uint32_t pos, int flags)
3333
int c_fsetxattr "fsetxattr" (int filedes, const char *name, const void *value, size_t size, uint32_t pos, int flags)
3434

35+
int c_removexattr "removexattr" (const char *path, const char *name, int flags)
36+
int c_fremovexattr "fremovexattr" (int filedes, const char *name, int flags)
37+
3538
int XATTR_NOFOLLOW
3639

3740
cdef int XATTR_NOFLAGS = 0x0000
@@ -100,6 +103,19 @@ def setxattr(path, name, value, *, follow_symlinks=False):
100103
_setxattr_inner(func, path, name, value)
101104

102105

106+
def removexattr(path, name, *, follow_symlinks=False):
107+
def func(path, name):
108+
if isinstance(path, int):
109+
return c_fremovexattr(path, name, XATTR_NOFLAGS)
110+
else:
111+
if follow_symlinks:
112+
return c_removexattr(path, name, XATTR_NOFLAGS)
113+
else:
114+
return c_removexattr(path, name, XATTR_NOFOLLOW)
115+
116+
_removexattr_inner(func, path, name)
117+
118+
103119
def _remove_numeric_id_if_possible(acl):
104120
"""Replace the user/group field with the local uid/gid, if possible."""
105121
assert isinstance(acl, bytes)

src/borg/platform/freebsd.pyx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ from libc cimport errno
55

66
from .posix import posix_acl_use_stored_uid_gid
77
from ..helpers import safe_encode, safe_decode
8-
from .xattr import _listxattr_inner, _getxattr_inner, _setxattr_inner, split_lstring
8+
from .xattr import _listxattr_inner, _getxattr_inner, _setxattr_inner, _removexattr_inner, split_lstring
99

1010

1111

@@ -22,6 +22,10 @@ cdef extern from "sys/extattr.h":
2222
int c_extattr_set_link "extattr_set_link" (const char *path, int attrnamespace, const char *attrname, const void *data, size_t nbytes)
2323
int c_extattr_set_fd "extattr_set_fd" (int fd, int attrnamespace, const char *attrname, const void *data, size_t nbytes)
2424

25+
int c_extattr_delete_file "extattr_delete_file" (const char *path, int attrnamespace, const char *attrname)
26+
int c_extattr_delete_link "extattr_delete_link" (const char *path, int attrnamespace, const char *attrname)
27+
int c_extattr_delete_fd "extattr_delete_fd" (int fd, int attrnamespace, const char *attrname)
28+
2529
int EXTATTR_NAMESPACE_USER
2630

2731
cdef extern from "sys/types.h":
@@ -124,6 +128,25 @@ def setxattr(path, name, value, *, follow_symlinks=False):
124128
_setxattr_inner(func, path, name, value)
125129

126130

131+
def removexattr(path, name, *, follow_symlinks=False):
132+
def func(path, name):
133+
if isinstance(path, int):
134+
return c_extattr_delete_fd(path, ns_id, name)
135+
else:
136+
if follow_symlinks:
137+
return c_extattr_delete_file(path, ns_id, name)
138+
else:
139+
return c_extattr_delete_link(path, ns_id, name)
140+
141+
ns, name = split_ns(name, b"user")
142+
try:
143+
ns_id = NS_ID_MAP[ns] # this will raise a KeyError it the namespace is unsupported
144+
except KeyError:
145+
pass
146+
else:
147+
_removexattr_inner(func, path, name)
148+
149+
127150
cdef _get_acl(p, type, item, attribute, flags, fd=None):
128151
cdef acl_t acl
129152
cdef char *text

src/borg/platform/linux.pyx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ from ..helpers import workarounds
88
from ..helpers import safe_decode, safe_encode
99
from .base import SyncFile as BaseSyncFile
1010
from .base import safe_fadvise
11-
from .xattr import _listxattr_inner, _getxattr_inner, _setxattr_inner, split_string0
11+
from .xattr import _listxattr_inner, _getxattr_inner, _setxattr_inner, _removexattr_inner, split_string0
1212
try:
1313
from .syncfilerange import sync_file_range, SYNC_FILE_RANGE_WRITE, SYNC_FILE_RANGE_WAIT_BEFORE, SYNC_FILE_RANGE_WAIT_AFTER
1414
SYNC_FILE_RANGE_LOADED = True
@@ -32,6 +32,10 @@ cdef extern from "sys/xattr.h":
3232
int c_lsetxattr "lsetxattr" (const char *path, const char *name, const void *value, size_t size, int flags)
3333
int c_fsetxattr "fsetxattr" (int filedes, const char *name, const void *value, size_t size, int flags)
3434

35+
int c_removexattr "removexattr" (const char *path, const char *name)
36+
int c_lremovexattr "lremovexattr" (const char *path, const char *name)
37+
int c_fremovexattr "fremovexattr" (int filedes, const char *name)
38+
3539
cdef extern from "sys/types.h":
3640
int ACL_TYPE_ACCESS
3741
int ACL_TYPE_DEFAULT
@@ -121,6 +125,19 @@ def setxattr(path, name, value, *, follow_symlinks=False):
121125
_setxattr_inner(func, path, name, value)
122126

123127

128+
def removexattr(path, name, *, follow_symlinks=False):
129+
def func(path, name):
130+
if isinstance(path, int):
131+
return c_fremovexattr(path, name)
132+
else:
133+
if follow_symlinks:
134+
return c_removexattr(path, name)
135+
else:
136+
return c_lremovexattr(path, name)
137+
138+
_removexattr_inner(func, path, name)
139+
140+
124141
BSD_TO_LINUX_FLAGS = {
125142
stat.UF_NODUMP: FS_NODUMP_FL,
126143
stat.UF_IMMUTABLE: FS_IMMUTABLE_FL,

src/borg/platform/netbsd.pyx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from .xattr import _listxattr_inner, _getxattr_inner, _setxattr_inner, split_lstring
1+
from .xattr import _listxattr_inner, _getxattr_inner, _setxattr_inner, _removexattr_inner, split_lstring
22

33

44

@@ -15,6 +15,10 @@ cdef extern from "sys/extattr.h":
1515
int c_extattr_set_link "extattr_set_link" (const char *path, int attrnamespace, const char *attrname, const void *data, size_t nbytes)
1616
int c_extattr_set_fd "extattr_set_fd" (int fd, int attrnamespace, const char *attrname, const void *data, size_t nbytes)
1717

18+
int c_extattr_delete_file "extattr_delete_file" (const char *path, int attrnamespace, const char *attrname)
19+
int c_extattr_delete_link "extattr_delete_link" (const char *path, int attrnamespace, const char *attrname)
20+
int c_extattr_delete_fd "extattr_delete_fd" (int fd, int attrnamespace, const char *attrname)
21+
1822
int EXTATTR_NAMESPACE_USER
1923

2024

@@ -87,3 +91,22 @@ def setxattr(path, name, value, *, follow_symlinks=False):
8791
pass
8892
else:
8993
_setxattr_inner(func, path, name, value)
94+
95+
96+
def removexattr(path, name, *, follow_symlinks=False):
97+
def func(path, name):
98+
if isinstance(path, int):
99+
return c_extattr_delete_fd(path, ns_id, name)
100+
else:
101+
if follow_symlinks:
102+
return c_extattr_delete_file(path, ns_id, name)
103+
else:
104+
return c_extattr_delete_link(path, ns_id, name)
105+
106+
ns, name = split_ns(name, b"user")
107+
try:
108+
ns_id = NS_ID_MAP[ns] # this will raise a KeyError it the namespace is unsupported
109+
except KeyError:
110+
pass
111+
else:
112+
_removexattr_inner(func, path, name)

src/borg/platform/xattr.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,9 @@ def _setxattr_inner(func, path, name, value):
8787
assert isinstance(name, bytes)
8888
assert isinstance(value, bytes)
8989
_check(func(path, name, value, len(value)), path, detect_buffer_too_small=False)
90+
91+
92+
def _removexattr_inner(func, path, name):
93+
assert isinstance(path, (bytes, int))
94+
assert isinstance(name, bytes)
95+
_check(func(path, name), path, detect_buffer_too_small=False)

src/borg/xattr.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
logger = create_logger()
1717

18-
from .platform import listxattr, getxattr, setxattr, ENOATTR
18+
from .platform import listxattr, getxattr, setxattr, removexattr, ENOATTR
1919

2020
# If we are running with fakeroot on Linux, then use the xattr functions of fakeroot. This is needed by
2121
# the 'test_extract_capabilities' test, but also allows xattrs to work with fakeroot on Linux in normal use.
@@ -130,3 +130,37 @@ def set_all(path, xattrs, follow_symlinks=False):
130130
err_str = str(e)
131131
logger.warning("When setting extended attribute %s: %s", k.decode(errors="replace"), err_str)
132132
return warning
133+
134+
135+
def clear_all(path, follow_symlinks=False):
136+
"""
137+
Remove all (removable) extended attributes from *path*.
138+
139+
*path* can either be a path (str or bytes) or an open file descriptor (int).
140+
*follow_symlinks* indicates whether symlinks should be followed
141+
and only applies when *path* is not an open file descriptor.
142+
143+
This is best-effort: xattrs that cannot be removed (e.g. attributes in a
144+
protected namespace such as "security.selinux", or on filesystems without
145+
xattr support) are silently skipped rather than raising. It is used to bring
146+
an existing file to a "fresh" state before re-applying archived metadata.
147+
"""
148+
if isinstance(path, str):
149+
path = os.fsencode(path)
150+
try:
151+
names = listxattr(path, follow_symlinks=follow_symlinks)
152+
except OSError as e:
153+
if e.errno in (errno.ENOTSUP, errno.EOPNOTSUPP, errno.EPERM):
154+
# xattrs not supported on this filesystem (or not permitted to list): nothing to clear.
155+
return
156+
raise
157+
for name in names:
158+
try:
159+
removexattr(path, name, follow_symlinks=follow_symlinks)
160+
except OSError as e:
161+
# ENOATTR: race, the xattr was already removed between list and remove.
162+
# ENOTSUP/EOPNOTSUPP: filesystem does not support xattrs.
163+
# EPERM/EACCES: protected namespace (e.g. "security.*") we are not allowed to drop.
164+
if e.errno in (ENOATTR, errno.ENOTSUP, errno.EOPNOTSUPP, errno.EPERM, errno.EACCES):
165+
continue
166+
raise

0 commit comments

Comments
 (0)