Skip to content

gh-57187: add xattr support for FreeBSD and macOS #103994

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 15 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 74 additions & 4 deletions Doc/library/os.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3781,12 +3781,29 @@ features:
.. versionadded:: 3.10


Linux extended attributes
~~~~~~~~~~~~~~~~~~~~~~~~~
Extended attributes
~~~~~~~~~~~~~~~~~~~

.. versionadded:: 3.3
Added suppport for Linux.

These functions are all available on Linux only.
.. versionadded:: 3.12
Added support for macOS and FreeBSD.

These functions provide access to file system extended attributes on the Unix
platforms that support them.

.. note::

On some platforms, extended attribute names have a *namespace qualifier*
prefix such as ``'user.'`` or ``'system.'``.

On FreeBSD, Python requires that the caller of functions that expect an
attribute name specify the namespace qualifier, which is interpreted
according to ``extattr_string_to_namespace()``. The rest of the string is
interpreted as the attribute name. E.g. Python will interpret the
``'user.mime_type'`` string as the attribute name ``'mime_type'`` in the
``EXTATTR_NAMESPACE_USER`` namespace.

.. function:: getxattr(path, attribute, *, follow_symlinks=True)

Expand All @@ -3800,9 +3817,13 @@ These functions are all available on Linux only.

.. audit-event:: os.getxattr path,attribute os.getxattr

.. availability:: Linux, macOS, FreeBSD.

.. versionchanged:: 3.6
Accepts a :term:`path-like object` for *path* and *attribute*.

.. versionadded:: 3.12
Added support for macOS and FreeBSD.

.. function:: listxattr(path=None, *, follow_symlinks=True)

Expand All @@ -3814,11 +3835,21 @@ These functions are all available on Linux only.
This function can support :ref:`specifying a file descriptor <path_fd>` and
:ref:`not following symlinks <follow_symlinks>`.

.. note::

On FreeBSD, this function will only list attributes residing in the
``user`` and ``system`` (if permitted) namespaces, prefixed with a
namespace qualifier.

.. audit-event:: os.listxattr path os.listxattr

.. availability:: Linux, macOS, FreeBSD.

.. versionchanged:: 3.6
Accepts a :term:`path-like object`.

.. versionadded:: 3.12
Added support for macOS and FreeBSD.

.. function:: removexattr(path, attribute, *, follow_symlinks=True)

Expand All @@ -3832,9 +3863,13 @@ These functions are all available on Linux only.

.. audit-event:: os.removexattr path,attribute os.removexattr

.. availability:: Linux, macOS, FreeBSD.

.. versionchanged:: 3.6
Accepts a :term:`path-like object` for *path* and *attribute*.

.. versionadded:: 3.12
Added support for macOS and FreeBSD.

.. function:: setxattr(path, attribute, value, flags=0, *, follow_symlinks=True)

Expand All @@ -3857,28 +3892,63 @@ These functions are all available on Linux only.

.. audit-event:: os.setxattr path,attribute,value,flags os.setxattr

.. availability:: Linux, macOS, FreeBSD.

.. versionchanged:: 3.6
Accepts a :term:`path-like object` for *path* and *attribute*.

.. versionadded:: 3.12
Added support for macOS and FreeBSD.

.. data:: XATTR_SIZE_MAX

The maximum size the value of an extended attribute can be. Currently, this
is 64 KiB on Linux.
is 64 KiB.

.. availability:: Linux.

.. data:: XATTR_NAME_MAX

The maximum length the name of an extended attribute (including the
namespace qualifier) can be. Currently, this is 255 bytes.

.. availability:: Linux.

.. versionadded:: 3.12

.. data:: XATTR_MAXNAMELEN

The maximum length the name of an extended attribute can be. Currently, this
is 127 UTF-8 bytes.

.. availability:: macOS.

.. versionadded:: 3.12

.. data:: EXTATTR_MAXNAMELEN

The maximum length the name of an extended attribute (excluding the
namespace qualifier) can be. Currently, this is 255 bytes.

.. availability:: FreeBSD.

.. versionadded:: 3.12

.. data:: XATTR_CREATE

This is a possible value for the flags argument in :func:`setxattr`. It
indicates the operation must create an attribute.

.. availability:: Linux, macOS, FreeBSD.

.. data:: XATTR_REPLACE

This is a possible value for the flags argument in :func:`setxattr`. It
indicates the operation must replace an existing attribute.


.. availability:: Linux, macOS, FreeBSD.

.. _os-process:

Process Management
Expand Down
10 changes: 7 additions & 3 deletions Lib/shutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,19 +322,23 @@ def _copyxattr(src, dst, *, follow_symlinks=True):

"""

if sys.platform.startswith("freebsd") or sys.platform == "darwin":
missing_xattr_errno = errno.ENOATTR
else:
missing_xattr_errno = errno.ENODATA
try:
names = os.listxattr(src, follow_symlinks=follow_symlinks)
except OSError as e:
if e.errno not in (errno.ENOTSUP, errno.ENODATA, errno.EINVAL):
if e.errno not in (errno.ENOTSUP, errno.EINVAL):
raise
return
for name in names:
try:
value = os.getxattr(src, name, follow_symlinks=follow_symlinks)
os.setxattr(dst, name, value, follow_symlinks=follow_symlinks)
except OSError as e:
if e.errno not in (errno.EPERM, errno.ENOTSUP, errno.ENODATA,
errno.EINVAL, errno.EACCES):
if e.errno not in (errno.EPERM, errno.ENOTSUP, errno.EINVAL,
errno.EACCES, missing_xattr_errno):
raise
else:
def _copyxattr(*args, **kwargs):
Expand Down
22 changes: 17 additions & 5 deletions Lib/test/test_os.py
Original file line number Diff line number Diff line change
Expand Up @@ -3671,18 +3671,21 @@ def supports_extended_attributes():

@unittest.skipUnless(supports_extended_attributes(),
"no non-broken extended attribute support")
# Kernels < 2.6.39 don't respect setxattr flags.
@support.requires_linux_version(2, 6, 39)
class ExtendedAttributeTests(unittest.TestCase):

def _check_xattrs_str(self, s, getxattr, setxattr, removexattr, listxattr, **kwargs):
fn = os_helper.TESTFN
if sys.platform.startswith("freebsd") or sys.platform == "darwin":
xattr_errno = errno.ENOATTR
else:
xattr_errno = errno.ENODATA

self.addCleanup(os_helper.unlink, fn)
create_file(fn)

with self.assertRaises(OSError) as cm:
getxattr(fn, s("user.test"), **kwargs)
self.assertEqual(cm.exception.errno, errno.ENODATA)
self.assertEqual(cm.exception.errno, xattr_errno)

init_xattr = listxattr(fn)
self.assertIsInstance(init_xattr, list)
Expand All @@ -3701,7 +3704,7 @@ def _check_xattrs_str(self, s, getxattr, setxattr, removexattr, listxattr, **kwa

with self.assertRaises(OSError) as cm:
setxattr(fn, s("user.test2"), b"bye", os.XATTR_REPLACE, **kwargs)
self.assertEqual(cm.exception.errno, errno.ENODATA)
self.assertEqual(cm.exception.errno, xattr_errno)

setxattr(fn, s("user.test2"), b"foo", os.XATTR_CREATE, **kwargs)
xattr.add("user.test2")
Expand All @@ -3710,7 +3713,7 @@ def _check_xattrs_str(self, s, getxattr, setxattr, removexattr, listxattr, **kwa

with self.assertRaises(OSError) as cm:
getxattr(fn, s("user.test"), **kwargs)
self.assertEqual(cm.exception.errno, errno.ENODATA)
self.assertEqual(cm.exception.errno, xattr_errno)

xattr.remove("user.test")
self.assertEqual(set(listxattr(fn)), xattr)
Expand All @@ -3730,14 +3733,23 @@ def _check_xattrs(self, *args, **kwargs):
self._check_xattrs_str(os.fsencode, *args, **kwargs)
os_helper.unlink(os_helper.TESTFN)

# Kernels < 2.6.39 don't respect setxattr flags.
@support.requires_linux_version(2, 6, 39)
@support.requires_freebsd_version(5)
def test_simple(self):
self._check_xattrs(os.getxattr, os.setxattr, os.removexattr,
os.listxattr)

# Kernels < 2.6.39 don't respect setxattr flags.
@support.requires_linux_version(2, 6, 39)
@support.requires_freebsd_version(5)
def test_lpath(self):
self._check_xattrs(os.getxattr, os.setxattr, os.removexattr,
os.listxattr, follow_symlinks=False)

# Kernels < 2.6.39 don't respect setxattr flags.
@support.requires_linux_version(2, 6, 39)
@support.requires_freebsd_version(5)
def test_fds(self):
def getxattr(path, *args):
with open(path, "rb") as fp:
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_shutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -1091,7 +1091,7 @@ def test_copystat_symlinks(self):
os.symlink(src, src_link)
os.symlink(dst, dst_link)
if hasattr(os, 'lchmod'):
os.lchmod(src_link, stat.S_IRWXO)
os.lchmod(src_link, stat.S_IRUSR)
if hasattr(os, 'lchflags') and hasattr(stat, 'UF_NODUMP'):
os.lchflags(src_link, stat.UF_NODUMP)
src_link_stat = os.lstat(src_link)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add support for extended attributes on FreeBSD
Loading