|
@@ -2,25 +2,15 @@
|
|
|
|
|
|
import errno
|
|
|
import os
|
|
|
-import re
|
|
|
-import subprocess
|
|
|
-import sys
|
|
|
import tempfile
|
|
|
-from ctypes import CDLL, create_string_buffer, c_ssize_t, c_size_t, c_char_p, c_int, c_uint32, get_errno
|
|
|
-from ctypes.util import find_library
|
|
|
-from distutils.version import LooseVersion
|
|
|
|
|
|
-from .helpers import Buffer, prepare_subprocess_env
|
|
|
+from .logger import create_logger
|
|
|
|
|
|
+logger = create_logger()
|
|
|
|
|
|
-try:
|
|
|
- ENOATTR = errno.ENOATTR
|
|
|
-except AttributeError:
|
|
|
- # on some platforms, ENOATTR is missing, use ENODATA there
|
|
|
- ENOATTR = errno.ENODATA
|
|
|
+from .platform import listxattr, getxattr, setxattr, ENOATTR
|
|
|
|
|
|
-
|
|
|
-buffer = Buffer(create_string_buffer, limit=2**24)
|
|
|
+XATTR_FAKEROOT = True # fakeroot with xattr support required (>= 1.20.2?)
|
|
|
|
|
|
|
|
|
def is_enabled(path=None):
|
|
@@ -28,13 +18,13 @@ def is_enabled(path=None):
|
|
|
"""
|
|
|
with tempfile.NamedTemporaryFile(dir=path, prefix='borg-tmp') as fd:
|
|
|
try:
|
|
|
- setxattr(fd.fileno(), 'user.name', b'value')
|
|
|
+ setxattr(fd.fileno(), b'user.name', b'value')
|
|
|
except OSError:
|
|
|
return False
|
|
|
- return getxattr(fd.fileno(), 'user.name') == b'value'
|
|
|
+ return getxattr(fd.fileno(), b'user.name') == b'value'
|
|
|
|
|
|
|
|
|
-def get_all(path, follow_symlinks=True):
|
|
|
+def get_all(path, follow_symlinks=False):
|
|
|
"""
|
|
|
Return all extended attributes on *path* as a mapping.
|
|
|
|
|
@@ -42,349 +32,86 @@ def get_all(path, follow_symlinks=True):
|
|
|
*follow_symlinks* indicates whether symlinks should be followed
|
|
|
and only applies when *path* is not an open file descriptor.
|
|
|
|
|
|
- The returned mapping maps xattr names (str) to values (bytes or None).
|
|
|
+ The returned mapping maps xattr names (bytes) to values (bytes or None).
|
|
|
None indicates, as a xattr value, an empty value, i.e. a value of length zero.
|
|
|
"""
|
|
|
+ if isinstance(path, str):
|
|
|
+ path = os.fsencode(path)
|
|
|
+ result = {}
|
|
|
try:
|
|
|
- result = {}
|
|
|
names = listxattr(path, follow_symlinks=follow_symlinks)
|
|
|
for name in names:
|
|
|
try:
|
|
|
- result[name] = getxattr(path, name, follow_symlinks=follow_symlinks)
|
|
|
+ # xattr name is a bytes object, we directly use it.
|
|
|
+ # if we get an empty xattr value (b''), we store None into the result dict -
|
|
|
+ # borg always did it like that...
|
|
|
+ result[name] = getxattr(path, name, follow_symlinks=follow_symlinks) or None
|
|
|
except OSError as e:
|
|
|
- # if we get ENOATTR, a race has happened: xattr names were deleted after list.
|
|
|
- # we just ignore the now missing ones. if you want consistency, do snapshots.
|
|
|
- if e.errno != ENOATTR:
|
|
|
+ name_str = name.decode()
|
|
|
+ if isinstance(path, int):
|
|
|
+ path_str = '<FD %d>' % path
|
|
|
+ else:
|
|
|
+ path_str = os.fsdecode(path)
|
|
|
+ if e.errno == ENOATTR:
|
|
|
+ # if we get ENOATTR, a race has happened: xattr names were deleted after list.
|
|
|
+ # we just ignore the now missing ones. if you want consistency, do snapshots.
|
|
|
+ pass
|
|
|
+ elif e.errno == errno.EPERM:
|
|
|
+ # we were not permitted to read this attribute, still can continue trying to read others
|
|
|
+ logger.warning('%s: Operation not permitted when reading extended attribute %s' % (
|
|
|
+ path_str, name_str))
|
|
|
+ else:
|
|
|
raise
|
|
|
- return result
|
|
|
except OSError as e:
|
|
|
if e.errno in (errno.ENOTSUP, errno.EPERM):
|
|
|
- return {}
|
|
|
-
|
|
|
-
|
|
|
-libc_name = find_library('c')
|
|
|
-if libc_name is None:
|
|
|
- # find_library didn't work, maybe we are on some minimal system that misses essential
|
|
|
- # tools used by find_library, like ldconfig, gcc/cc, objdump.
|
|
|
- # so we can only try some "usual" names for the C library:
|
|
|
- if sys.platform.startswith('linux'):
|
|
|
- libc_name = 'libc.so.6'
|
|
|
- elif sys.platform.startswith(('freebsd', 'netbsd')):
|
|
|
- libc_name = 'libc.so'
|
|
|
- elif sys.platform == 'darwin':
|
|
|
- libc_name = 'libc.dylib'
|
|
|
- else:
|
|
|
- msg = "Can't find C library. No fallback known. Try installing ldconfig, gcc/cc or objdump."
|
|
|
- print(msg, file=sys.stderr) # logger isn't initialized at this stage
|
|
|
- raise Exception(msg)
|
|
|
-
|
|
|
-# If we are running with fakeroot on Linux, then use the xattr functions of fakeroot. This is needed by
|
|
|
-# the 'test_extract_capabilities' test, but also allows xattrs to work with fakeroot on Linux in normal use.
|
|
|
-# TODO: Check whether fakeroot supports xattrs on all platforms supported below.
|
|
|
-# TODO: If that's the case then we can make Borg fakeroot-xattr-compatible on these as well.
|
|
|
-XATTR_FAKEROOT = False
|
|
|
-if sys.platform.startswith('linux'):
|
|
|
- LD_PRELOAD = os.environ.get('LD_PRELOAD', '')
|
|
|
- preloads = re.split("[ :]", LD_PRELOAD)
|
|
|
- for preload in preloads:
|
|
|
- if preload.startswith("libfakeroot"):
|
|
|
- env = prepare_subprocess_env(system=True)
|
|
|
- fakeroot_output = subprocess.check_output(['fakeroot', '-v'], env=env)
|
|
|
- fakeroot_version = LooseVersion(fakeroot_output.decode('ascii').split()[-1])
|
|
|
- if fakeroot_version >= LooseVersion("1.20.2"):
|
|
|
- # 1.20.2 has been confirmed to have xattr support
|
|
|
- # 1.18.2 has been confirmed not to have xattr support
|
|
|
- # Versions in-between are unknown
|
|
|
- libc_name = preload
|
|
|
- XATTR_FAKEROOT = True
|
|
|
- break
|
|
|
-
|
|
|
-try:
|
|
|
- libc = CDLL(libc_name, use_errno=True)
|
|
|
-except OSError as e:
|
|
|
- msg = "Can't find C library [%s]. Try installing ldconfig, gcc/cc or objdump." % e
|
|
|
- raise Exception(msg)
|
|
|
-
|
|
|
-
|
|
|
-def split_string0(buf):
|
|
|
- """split a list of zero-terminated strings into python not-zero-terminated bytes"""
|
|
|
- return buf.split(b'\0')[:-1]
|
|
|
-
|
|
|
-
|
|
|
-def split_lstring(buf):
|
|
|
- """split a list of length-prefixed strings into python not-length-prefixed bytes"""
|
|
|
- result = []
|
|
|
- mv = memoryview(buf)
|
|
|
- while mv:
|
|
|
- length = mv[0]
|
|
|
- result.append(bytes(mv[1:1 + length]))
|
|
|
- mv = mv[1 + length:]
|
|
|
- return result
|
|
|
-
|
|
|
-
|
|
|
-class BufferTooSmallError(Exception):
|
|
|
- """the buffer given to a xattr function was too small for the result."""
|
|
|
-
|
|
|
-
|
|
|
-def _check(rv, path=None, detect_buffer_too_small=False):
|
|
|
- if rv < 0:
|
|
|
- e = get_errno()
|
|
|
- if detect_buffer_too_small and e == errno.ERANGE:
|
|
|
- # listxattr and getxattr signal with ERANGE that they need a bigger result buffer.
|
|
|
- # setxattr signals this way that e.g. a xattr key name is too long / inacceptable.
|
|
|
- raise BufferTooSmallError
|
|
|
+ # if xattrs are not supported on the filesystem, we give up.
|
|
|
+ # EPERM might be raised by listxattr.
|
|
|
+ pass
|
|
|
else:
|
|
|
- try:
|
|
|
- msg = os.strerror(e)
|
|
|
- except ValueError:
|
|
|
- msg = ''
|
|
|
- if isinstance(path, int):
|
|
|
- path = '<FD %d>' % path
|
|
|
- raise OSError(e, msg, path)
|
|
|
- if detect_buffer_too_small and rv >= len(buffer):
|
|
|
- # freebsd does not error with ERANGE if the buffer is too small,
|
|
|
- # it just fills the buffer, truncates and returns.
|
|
|
- # so, we play sure and just assume that result is truncated if
|
|
|
- # it happens to be a full buffer.
|
|
|
- raise BufferTooSmallError
|
|
|
- return rv
|
|
|
+ raise
|
|
|
+ return result
|
|
|
|
|
|
|
|
|
-def _listxattr_inner(func, path):
|
|
|
- if isinstance(path, str):
|
|
|
- path = os.fsencode(path)
|
|
|
- size = len(buffer)
|
|
|
- while True:
|
|
|
- buf = buffer.get(size)
|
|
|
- try:
|
|
|
- n = _check(func(path, buf, size), path, detect_buffer_too_small=True)
|
|
|
- except BufferTooSmallError:
|
|
|
- size *= 2
|
|
|
- else:
|
|
|
- return n, buf.raw
|
|
|
+def set_all(path, xattrs, follow_symlinks=False):
|
|
|
+ """
|
|
|
+ Set all extended attributes on *path* from a mapping.
|
|
|
|
|
|
+ *path* can either be a path (str or bytes) or an open file descriptor (int).
|
|
|
+ *follow_symlinks* indicates whether symlinks should be followed
|
|
|
+ and only applies when *path* is not an open file descriptor.
|
|
|
+ *xattrs* is mapping maps xattr names (bytes) to values (bytes or None).
|
|
|
+ None indicates, as a xattr value, an empty value, i.e. a value of length zero.
|
|
|
|
|
|
-def _getxattr_inner(func, path, name):
|
|
|
+ Return warning status (True means a non-fatal exception has happened and was dealt with).
|
|
|
+ """
|
|
|
if isinstance(path, str):
|
|
|
path = os.fsencode(path)
|
|
|
- name = os.fsencode(name)
|
|
|
- size = len(buffer)
|
|
|
- while True:
|
|
|
- buf = buffer.get(size)
|
|
|
+ warning = False
|
|
|
+ for k, v in xattrs.items():
|
|
|
try:
|
|
|
- n = _check(func(path, name, buf, size), path, detect_buffer_too_small=True)
|
|
|
- except BufferTooSmallError:
|
|
|
- size *= 2
|
|
|
- else:
|
|
|
- return n, buf.raw
|
|
|
-
|
|
|
-
|
|
|
-def _setxattr_inner(func, path, name, value):
|
|
|
- if isinstance(path, str):
|
|
|
- path = os.fsencode(path)
|
|
|
- name = os.fsencode(name)
|
|
|
- value = value and os.fsencode(value)
|
|
|
- size = len(value) if value else 0
|
|
|
- _check(func(path, name, value, size), path, detect_buffer_too_small=False)
|
|
|
-
|
|
|
-
|
|
|
-if sys.platform.startswith('linux'): # pragma: linux only
|
|
|
- libc.llistxattr.argtypes = (c_char_p, c_char_p, c_size_t)
|
|
|
- libc.llistxattr.restype = c_ssize_t
|
|
|
- libc.flistxattr.argtypes = (c_int, c_char_p, c_size_t)
|
|
|
- libc.flistxattr.restype = c_ssize_t
|
|
|
- libc.lsetxattr.argtypes = (c_char_p, c_char_p, c_char_p, c_size_t, c_int)
|
|
|
- libc.lsetxattr.restype = c_int
|
|
|
- libc.fsetxattr.argtypes = (c_int, c_char_p, c_char_p, c_size_t, c_int)
|
|
|
- libc.fsetxattr.restype = c_int
|
|
|
- libc.lgetxattr.argtypes = (c_char_p, c_char_p, c_char_p, c_size_t)
|
|
|
- libc.lgetxattr.restype = c_ssize_t
|
|
|
- libc.fgetxattr.argtypes = (c_int, c_char_p, c_char_p, c_size_t)
|
|
|
- libc.fgetxattr.restype = c_ssize_t
|
|
|
-
|
|
|
- def listxattr(path, *, follow_symlinks=True):
|
|
|
- def func(path, buf, size):
|
|
|
+ # the key k is a bytes object due to msgpack unpacking it as such.
|
|
|
+ # if we have a None value, it means "empty", so give b'' to setxattr in that case:
|
|
|
+ setxattr(path, k, v or b'', follow_symlinks=follow_symlinks)
|
|
|
+ except OSError as e:
|
|
|
+ warning = True
|
|
|
+ k_str = k.decode()
|
|
|
if isinstance(path, int):
|
|
|
- return libc.flistxattr(path, buf, size)
|
|
|
+ path_str = '<FD %d>' % path
|
|
|
else:
|
|
|
- if follow_symlinks:
|
|
|
- return libc.listxattr(path, buf, size)
|
|
|
- else:
|
|
|
- return libc.llistxattr(path, buf, size)
|
|
|
-
|
|
|
- n, buf = _listxattr_inner(func, path)
|
|
|
- return [os.fsdecode(name) for name in split_string0(buf[:n])
|
|
|
- if name and not name.startswith(b'system.posix_acl_')]
|
|
|
-
|
|
|
- def getxattr(path, name, *, follow_symlinks=True):
|
|
|
- def func(path, name, buf, size):
|
|
|
- if isinstance(path, int):
|
|
|
- return libc.fgetxattr(path, name, buf, size)
|
|
|
+ path_str = os.fsdecode(path)
|
|
|
+ if e.errno == errno.E2BIG:
|
|
|
+ logger.warning('%s: Value or key of extended attribute %s is too big for this filesystem' % (
|
|
|
+ path_str, k_str))
|
|
|
+ elif e.errno == errno.ENOTSUP:
|
|
|
+ logger.warning('%s: Extended attributes are not supported on this filesystem' % path_str)
|
|
|
+ elif e.errno == errno.EACCES:
|
|
|
+ # permission denied to set this specific xattr (this may happen related to security.* keys)
|
|
|
+ logger.warning('%s: Permission denied when setting extended attribute %s' % (path_str, k_str))
|
|
|
+ elif e.errno == errno.ENOSPC:
|
|
|
+ # ext4 reports ENOSPC when trying to set an xattr with >4kiB while ext4 can only support 4kiB xattrs
|
|
|
+ # (in this case, this is NOT a "disk full" error, just a ext4 limitation).
|
|
|
+ logger.warning('%s: No space left on device while setting extended attribute %s (len = %d)' % (
|
|
|
+ path_str, k_str, len(v)))
|
|
|
else:
|
|
|
- if follow_symlinks:
|
|
|
- return libc.getxattr(path, name, buf, size)
|
|
|
- else:
|
|
|
- return libc.lgetxattr(path, name, buf, size)
|
|
|
-
|
|
|
- n, buf = _getxattr_inner(func, path, name)
|
|
|
- return buf[:n] or None
|
|
|
-
|
|
|
- def setxattr(path, name, value, *, follow_symlinks=True):
|
|
|
- def func(path, name, value, size):
|
|
|
- flags = 0
|
|
|
- if isinstance(path, int):
|
|
|
- return libc.fsetxattr(path, name, value, size, flags)
|
|
|
- else:
|
|
|
- if follow_symlinks:
|
|
|
- return libc.setxattr(path, name, value, size, flags)
|
|
|
- else:
|
|
|
- return libc.lsetxattr(path, name, value, size, flags)
|
|
|
-
|
|
|
- _setxattr_inner(func, path, name, value)
|
|
|
-
|
|
|
-elif sys.platform == 'darwin': # pragma: darwin only
|
|
|
- libc.listxattr.argtypes = (c_char_p, c_char_p, c_size_t, c_int)
|
|
|
- libc.listxattr.restype = c_ssize_t
|
|
|
- libc.flistxattr.argtypes = (c_int, c_char_p, c_size_t)
|
|
|
- libc.flistxattr.restype = c_ssize_t
|
|
|
- libc.setxattr.argtypes = (c_char_p, c_char_p, c_char_p, c_size_t, c_uint32, c_int)
|
|
|
- libc.setxattr.restype = c_int
|
|
|
- libc.fsetxattr.argtypes = (c_int, c_char_p, c_char_p, c_size_t, c_uint32, c_int)
|
|
|
- libc.fsetxattr.restype = c_int
|
|
|
- libc.getxattr.argtypes = (c_char_p, c_char_p, c_char_p, c_size_t, c_uint32, c_int)
|
|
|
- libc.getxattr.restype = c_ssize_t
|
|
|
- libc.fgetxattr.argtypes = (c_int, c_char_p, c_char_p, c_size_t, c_uint32, c_int)
|
|
|
- libc.fgetxattr.restype = c_ssize_t
|
|
|
-
|
|
|
- XATTR_NOFLAGS = 0x0000
|
|
|
- XATTR_NOFOLLOW = 0x0001
|
|
|
-
|
|
|
- def listxattr(path, *, follow_symlinks=True):
|
|
|
- def func(path, buf, size):
|
|
|
- if isinstance(path, int):
|
|
|
- return libc.flistxattr(path, buf, size, XATTR_NOFLAGS)
|
|
|
- else:
|
|
|
- if follow_symlinks:
|
|
|
- return libc.listxattr(path, buf, size, XATTR_NOFLAGS)
|
|
|
- else:
|
|
|
- return libc.listxattr(path, buf, size, XATTR_NOFOLLOW)
|
|
|
-
|
|
|
- n, buf = _listxattr_inner(func, path)
|
|
|
- return [os.fsdecode(name) for name in split_string0(buf[:n]) if name]
|
|
|
-
|
|
|
- def getxattr(path, name, *, follow_symlinks=True):
|
|
|
- def func(path, name, buf, size):
|
|
|
- if isinstance(path, int):
|
|
|
- return libc.fgetxattr(path, name, buf, size, 0, XATTR_NOFLAGS)
|
|
|
- else:
|
|
|
- if follow_symlinks:
|
|
|
- return libc.getxattr(path, name, buf, size, 0, XATTR_NOFLAGS)
|
|
|
- else:
|
|
|
- return libc.getxattr(path, name, buf, size, 0, XATTR_NOFOLLOW)
|
|
|
-
|
|
|
- n, buf = _getxattr_inner(func, path, name)
|
|
|
- return buf[:n] or None
|
|
|
-
|
|
|
- def setxattr(path, name, value, *, follow_symlinks=True):
|
|
|
- def func(path, name, value, size):
|
|
|
- if isinstance(path, int):
|
|
|
- return libc.fsetxattr(path, name, value, size, 0, XATTR_NOFLAGS)
|
|
|
- else:
|
|
|
- if follow_symlinks:
|
|
|
- return libc.setxattr(path, name, value, size, 0, XATTR_NOFLAGS)
|
|
|
- else:
|
|
|
- return libc.setxattr(path, name, value, size, 0, XATTR_NOFOLLOW)
|
|
|
-
|
|
|
- _setxattr_inner(func, path, name, value)
|
|
|
-
|
|
|
-elif sys.platform.startswith('freebsd'): # pragma: freebsd only
|
|
|
- libc.extattr_list_fd.argtypes = (c_int, c_int, c_char_p, c_size_t)
|
|
|
- libc.extattr_list_fd.restype = c_ssize_t
|
|
|
- libc.extattr_list_link.argtypes = (c_char_p, c_int, c_char_p, c_size_t)
|
|
|
- libc.extattr_list_link.restype = c_ssize_t
|
|
|
- libc.extattr_list_file.argtypes = (c_char_p, c_int, c_char_p, c_size_t)
|
|
|
- libc.extattr_list_file.restype = c_ssize_t
|
|
|
- libc.extattr_get_fd.argtypes = (c_int, c_int, c_char_p, c_char_p, c_size_t)
|
|
|
- libc.extattr_get_fd.restype = c_ssize_t
|
|
|
- libc.extattr_get_link.argtypes = (c_char_p, c_int, c_char_p, c_char_p, c_size_t)
|
|
|
- libc.extattr_get_link.restype = c_ssize_t
|
|
|
- libc.extattr_get_file.argtypes = (c_char_p, c_int, c_char_p, c_char_p, c_size_t)
|
|
|
- libc.extattr_get_file.restype = c_ssize_t
|
|
|
- libc.extattr_set_fd.argtypes = (c_int, c_int, c_char_p, c_char_p, c_size_t)
|
|
|
- libc.extattr_set_fd.restype = c_int
|
|
|
- libc.extattr_set_link.argtypes = (c_char_p, c_int, c_char_p, c_char_p, c_size_t)
|
|
|
- libc.extattr_set_link.restype = c_int
|
|
|
- libc.extattr_set_file.argtypes = (c_char_p, c_int, c_char_p, c_char_p, c_size_t)
|
|
|
- libc.extattr_set_file.restype = c_int
|
|
|
- ns = EXTATTR_NAMESPACE_USER = 0x0001
|
|
|
-
|
|
|
- def listxattr(path, *, follow_symlinks=True):
|
|
|
- def func(path, buf, size):
|
|
|
- if isinstance(path, int):
|
|
|
- return libc.extattr_list_fd(path, ns, buf, size)
|
|
|
- else:
|
|
|
- if follow_symlinks:
|
|
|
- return libc.extattr_list_file(path, ns, buf, size)
|
|
|
- else:
|
|
|
- return libc.extattr_list_link(path, ns, buf, size)
|
|
|
-
|
|
|
- n, buf = _listxattr_inner(func, path)
|
|
|
- return [os.fsdecode(name) for name in split_lstring(buf[:n]) if name]
|
|
|
-
|
|
|
- def getxattr(path, name, *, follow_symlinks=True):
|
|
|
- def func(path, name, buf, size):
|
|
|
- if isinstance(path, int):
|
|
|
- return libc.extattr_get_fd(path, ns, name, buf, size)
|
|
|
- else:
|
|
|
- if follow_symlinks:
|
|
|
- return libc.extattr_get_file(path, ns, name, buf, size)
|
|
|
- else:
|
|
|
- return libc.extattr_get_link(path, ns, name, buf, size)
|
|
|
-
|
|
|
- n, buf = _getxattr_inner(func, path, name)
|
|
|
- return buf[:n] or None
|
|
|
-
|
|
|
- def setxattr(path, name, value, *, follow_symlinks=True):
|
|
|
- def func(path, name, value, size):
|
|
|
- if isinstance(path, int):
|
|
|
- return libc.extattr_set_fd(path, ns, name, value, size)
|
|
|
- else:
|
|
|
- if follow_symlinks:
|
|
|
- return libc.extattr_set_file(path, ns, name, value, size)
|
|
|
- else:
|
|
|
- return libc.extattr_set_link(path, ns, name, value, size)
|
|
|
-
|
|
|
- _setxattr_inner(func, path, name, value)
|
|
|
-
|
|
|
-else: # pragma: unknown platform only
|
|
|
- def listxattr(path, *, follow_symlinks=True):
|
|
|
- """
|
|
|
- Return list of xattr names on a file.
|
|
|
-
|
|
|
- *path* can either be a path (str or bytes) or an open file descriptor (int).
|
|
|
- *follow_symlinks* indicates whether symlinks should be followed
|
|
|
- and only applies when *path* is not an open file descriptor.
|
|
|
- """
|
|
|
- return []
|
|
|
-
|
|
|
- def getxattr(path, name, *, follow_symlinks=True):
|
|
|
- """
|
|
|
- Read xattr and return its value (as bytes) or None if its empty.
|
|
|
-
|
|
|
- *path* can either be a path (str or bytes) or an open file descriptor (int).
|
|
|
- *name* is the name of the xattr to read (str).
|
|
|
- *follow_symlinks* indicates whether symlinks should be followed
|
|
|
- and only applies when *path* is not an open file descriptor.
|
|
|
- """
|
|
|
-
|
|
|
- def setxattr(path, name, value, *, follow_symlinks=True):
|
|
|
- """
|
|
|
- Write xattr on *path*.
|
|
|
-
|
|
|
- *path* can either be a path (str or bytes) or an open file descriptor (int).
|
|
|
- *name* is the name of the xattr to read (str).
|
|
|
- *value* is the value to write. It is either bytes or None. The latter
|
|
|
- signals that the value shall be empty (size equals zero).
|
|
|
- *follow_symlinks* indicates whether symlinks should be followed
|
|
|
- and only applies when *path* is not an open file descriptor.
|
|
|
- """
|
|
|
+ raise
|
|
|
+ return warning
|