diff --git a/Doc/library/os.rst b/Doc/library/os.rst index c67b966f777db8..876908a1f5668b 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -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) @@ -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) @@ -3814,11 +3835,21 @@ These functions are all available on Linux only. This function can support :ref:`specifying a file descriptor ` and :ref:`not following 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) @@ -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) @@ -3857,21 +3892,54 @@ 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 @@ -3879,6 +3947,8 @@ These functions are all available on Linux only. indicates the operation must replace an existing attribute. + .. availability:: Linux, macOS, FreeBSD. + .. _os-process: Process Management diff --git a/Lib/shutil.py b/Lib/shutil.py index b37bd082eee0c6..42262f887ddaec 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -322,10 +322,14 @@ 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: @@ -333,8 +337,8 @@ def _copyxattr(src, dst, *, follow_symlinks=True): 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): diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 34cd27b143f231..bd44094e0b38d5 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -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) @@ -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") @@ -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) @@ -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: diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index a2ca4df135846f..736525d1772246 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -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) diff --git a/Misc/NEWS.d/next/Library/2018-03-02-23-38-36.bpo-12978.GmUrw4.rst b/Misc/NEWS.d/next/Library/2018-03-02-23-38-36.bpo-12978.GmUrw4.rst new file mode 100644 index 00000000000000..eb84ae0c46a2dd --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-03-02-23-38-36.bpo-12978.GmUrw4.rst @@ -0,0 +1 @@ +Add support for extended attributes on FreeBSD diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index c4340397fbe577..18465bc4719b47 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -297,13 +297,206 @@ corresponding Unix manual entries for more information on calls."); # undef HAVE_SCHED_SETAFFINITY #endif -#if defined(HAVE_SYS_XATTR_H) && defined(HAVE_LINUX_LIMITS_H) && !defined(__FreeBSD_kernel__) && !defined(__GNU__) +#if defined(HAVE_SYS_XATTR_H) && defined(HAVE_LINUX_LIMITS_H) +# include # define USE_XATTRS # include // Needed for XATTR_SIZE_MAX on musl libc. +#elif defined(HAVE_SYS_XATTR_H) && defined(__APPLE__) +# include +# define USE_XATTRS +# define XATTR_SIZE_MAX 4096 +#elif defined(HAVE_SYS_EXTATTR_H) && defined(__FreeBSD__) +# include +# define USE_XATTRS +# define XATTR_SIZE_MAX 4096 +# define XATTR_LIST_MAX 4096 +# define XATTR_CREATE 1 +# define XATTR_REPLACE 2 #endif -#ifdef USE_XATTRS -# include +#if defined(USE_XATTRS) && defined(__FreeBSD__) + +// Split a Linux-style qualified xattr name into its components for FreeBSD. +// +// This ensures that that the namespace is qualified ((user|system).) and +// that is non-empty. +// +// Sets errno. +static void +xattr_split_namespace(const char *fullattr, int *namespace, const char **attr_name) +{ + char *sep = strchr(fullattr, '.'); + // fail with ENOATTR on unqualified or empty names + *attr_name = NULL; + if (sep == NULL || *(sep + 1) == '\0' || sep < fullattr) { + errno = ENOATTR; + return; + } + size_t nslen = sep - fullattr; + // We could use extattr_string_to_namespace, but we have a prefix, not a + // null-terminated string, at this point. extattr_string_to_namespace is + // currently documented to only support 'user' and 'system' namespaces. + if (nslen == 4 && memcmp(fullattr, "user", 4) == 0) { + *namespace = EXTATTR_NAMESPACE_USER; + } else if (nslen == 6 && memcmp(fullattr, "system", 6) == 0) { + *namespace = EXTATTR_NAMESPACE_SYSTEM; + } else { + errno = ENOATTR; + return; + } + *attr_name = fullattr + nslen + 1; +} + +/* FreeBSD wrapper that mimics Linux getxattr */ +static ssize_t +getxattr(const char *path, const char *name, void *value, size_t size) +{ + int nsid = 0; + const char *attr_name = NULL; + xattr_split_namespace(name, &nsid, &attr_name); + if (attr_name == NULL) + return -1; + return extattr_get_file(path, nsid, attr_name, value, size); +} + +/* FreeBSD wrapper that mimics Linux lgetxattr */ +static ssize_t +lgetxattr(const char *path, const char *name, void *value, size_t size) +{ + int nsid = 0; + const char *attr_name = NULL; + xattr_split_namespace(name, &nsid, &attr_name); + if (attr_name == NULL) + return -1; + return extattr_get_link(path, nsid, attr_name, value, size); +} + +/* FreeBSD wrapper that mimics Linux fgetxattr */ +static ssize_t +fgetxattr(int fd, const char *name, void *value, size_t size) +{ + int nsid = 0; + const char *attr_name = NULL; + xattr_split_namespace(name, &nsid, &attr_name); + if (attr_name == NULL) + return -1; + return extattr_get_fd(fd, nsid, attr_name, value, size); +} + +/* FreeBSD wrapper that mimics Linux setxattr */ +static int +setxattr(const char *path, const char *name, const void *value, size_t size, int flags) +{ + int nsid = 0; + const char *attr_name = NULL; + xattr_split_namespace(name, &nsid, &attr_name); + if (attr_name == NULL) + return -1; + ssize_t get_ret = 0; + if (flags) { + get_ret = extattr_get_file(path, nsid, attr_name, NULL, 0); + if (flags & XATTR_REPLACE && get_ret == -1) { + errno = ENOATTR; + return -1; + } else if (flags & XATTR_CREATE && get_ret >= 0) { + errno = EEXIST; + return -1; + } + } + + if (extattr_set_file(path, nsid, attr_name, value, size) != -1) + return 0; + return -1; +} + +/* FreeBSD wrapper that mimics Linux lsetxattr */ +static int +lsetxattr(const char *path, const char *name, const void *value, size_t size, int flags) +{ + int nsid = 0; + const char *attr_name = NULL; + xattr_split_namespace(name, &nsid, &attr_name); + if (attr_name == NULL) + return -1; + ssize_t get_ret = 0; + if (flags) { + get_ret = extattr_get_link(path, nsid, attr_name, NULL, 0); + if (flags & XATTR_REPLACE && get_ret == -1) { + errno = ENOATTR; + return -1; + } else if (flags & XATTR_CREATE && get_ret >= 0) { + errno = EEXIST; + return -1; + } + } + + if (extattr_set_link(path, nsid, attr_name, value, size) != -1) + return 0; + return -1; +} + +/* FreeBSD wrapper that mimics Linux fsetxattr */ +static int +fsetxattr(int fd, const char *name, const void *value, size_t size, int flags) +{ + int nsid = 0; + const char *attr_name = NULL; + xattr_split_namespace(name, &nsid, &attr_name); + if (attr_name == NULL) + return -1; + ssize_t get_ret = 0; + if (flags) { + get_ret = extattr_get_fd(fd, nsid, attr_name, NULL, 0); + if (flags & XATTR_REPLACE && get_ret == -1) { + errno = ENOATTR; + return -1; + } else if (flags & XATTR_CREATE && get_ret >= 0) { + errno = EEXIST; + return -1; + } + } + + if (extattr_set_fd(fd, nsid, attr_name, value, size) != -1) + return 0; + return -1; +} + +/* FreeBSD wrapper that mimics Linux removexattr */ +static int +removexattr(const char *path, const char *name) +{ + int nsid = 0; + const char *attr_name = NULL; + xattr_split_namespace(name, &nsid, &attr_name); + if (attr_name == NULL) + return -1; + return extattr_delete_file(path, nsid, attr_name); +} + +/* FreeBSD wrapper that mimics Linux lremovexattr */ +static int +lremovexattr(const char *path, const char *name) +{ + int nsid = 0; + const char *attr_name = NULL; + xattr_split_namespace(name, &nsid, &attr_name); + if (attr_name == NULL) + return -1; + return extattr_delete_link(path, nsid, attr_name); +} + +/* FreeBSD wrapper that mimics Linux fremovexattr */ +static int +fremovexattr(int fd, const char *name) +{ + int nsid = 0; + const char *attr_name = NULL; + xattr_split_namespace(name, &nsid, &attr_name); + if (attr_name == NULL) + return -1; + return extattr_delete_fd(fd, nsid, attr_name); +} + #endif #if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__APPLE__) @@ -13831,9 +14024,6 @@ os_getxattr_impl(PyObject *module, path_t *path, path_t *attribute, int follow_symlinks) /*[clinic end generated code: output=5f2f44200a43cff2 input=025789491708f7eb]*/ { - Py_ssize_t i; - PyObject *buffer = NULL; - if (fd_and_follow_symlinks_invalid("getxattr", path->fd, follow_symlinks)) return NULL; @@ -13841,46 +14031,62 @@ os_getxattr_impl(PyObject *module, path_t *path, path_t *attribute, return NULL; } - for (i = 0; ; i++) { - void *ptr; - ssize_t result; - static const Py_ssize_t buffer_sizes[] = {128, XATTR_SIZE_MAX, 0}; - Py_ssize_t buffer_size = buffer_sizes[i]; - if (!buffer_size) { - path_error(path); - return NULL; - } - buffer = PyBytes_FromStringAndSize(NULL, buffer_size); - if (!buffer) - return NULL; - ptr = PyBytes_AS_STRING(buffer); - - Py_BEGIN_ALLOW_THREADS; - if (path->fd >= 0) - result = fgetxattr(path->fd, attribute->narrow, ptr, buffer_size); - else if (follow_symlinks) - result = getxattr(path->narrow, attribute->narrow, ptr, buffer_size); - else - result = lgetxattr(path->narrow, attribute->narrow, ptr, buffer_size); - Py_END_ALLOW_THREADS; - - if (result < 0) { - if (errno == ERANGE) { - Py_DECREF(buffer); - continue; - } - path_error(path); - Py_DECREF(buffer); - return NULL; - } - - if (result != buffer_size) { - /* Can only shrink. */ - _PyBytes_Resize(&buffer, result); - } - break; + ssize_t result; + // First, query buffer size + Py_BEGIN_ALLOW_THREADS; +#ifdef __APPLE__ + if (path->fd >= 0) + result = fgetxattr(path->fd, attribute->narrow, NULL, 0, 0, 0); + else if (follow_symlinks) + result = getxattr(path->narrow, attribute->narrow, NULL, 0, 0, 0); + else + result = getxattr(path->narrow, attribute->narrow, NULL, 0, 0, + XATTR_NOFOLLOW); +#else + if (path->fd >= 0) + result = fgetxattr(path->fd, attribute->narrow, NULL, 0); + else if (follow_symlinks) + result = getxattr(path->narrow, attribute->narrow, NULL, 0); + else + result = lgetxattr(path->narrow, attribute->narrow, NULL, 0); +#endif + Py_END_ALLOW_THREADS; + if (result < 0) { + path_error(path); + return NULL; } + // Next, allocate and query the actual value + Py_ssize_t buffer_size = result; + PyObject *buffer = PyBytes_FromStringAndSize(NULL, buffer_size); + if (!buffer) + return NULL; + void *ptr = PyBytes_AS_STRING(buffer); + Py_BEGIN_ALLOW_THREADS; +#ifdef __APPLE__ + if (path->fd >= 0) + result = fgetxattr(path->fd, attribute->narrow, ptr, buffer_size, + 0, 0); + else if (follow_symlinks) + result = getxattr(path->narrow, attribute->narrow, ptr, + buffer_size, 0, 0); + else + result = getxattr(path->narrow, attribute->narrow, ptr, + buffer_size, 0, XATTR_NOFOLLOW); +#else + if (path->fd >= 0) + result = fgetxattr(path->fd, attribute->narrow, ptr, buffer_size); + else if (follow_symlinks) + result = getxattr(path->narrow, attribute->narrow, ptr, buffer_size); + else + result = lgetxattr(path->narrow, attribute->narrow, ptr, buffer_size); +#endif + Py_END_ALLOW_THREADS; + if (result < 0) { + path_error(path); + Py_DECREF(buffer); + return NULL; + } return buffer; } @@ -13920,6 +14126,17 @@ os_setxattr_impl(PyObject *module, path_t *path, path_t *attribute, } Py_BEGIN_ALLOW_THREADS; +#ifdef __APPLE__ + if (path->fd > -1) + result = fsetxattr(path->fd, attribute->narrow, + value->buf, value->len, 0, flags); + else if (follow_symlinks) + result = setxattr(path->narrow, attribute->narrow, + value->buf, value->len, 0, flags); + else + result = setxattr(path->narrow, attribute->narrow, + value->buf, value->len, 0, flags | XATTR_NOFOLLOW); +#else if (path->fd > -1) result = fsetxattr(path->fd, attribute->narrow, value->buf, value->len, flags); @@ -13929,6 +14146,7 @@ os_setxattr_impl(PyObject *module, path_t *path, path_t *attribute, else result = lsetxattr(path->narrow, attribute->narrow, value->buf, value->len, flags); +#endif Py_END_ALLOW_THREADS; if (result) { @@ -13972,12 +14190,21 @@ os_removexattr_impl(PyObject *module, path_t *path, path_t *attribute, } Py_BEGIN_ALLOW_THREADS; +#ifdef __APPLE__ + if (path->fd > -1) + result = fremovexattr(path->fd, attribute->narrow, 0); + else if (follow_symlinks) + result = removexattr(path->narrow, attribute->narrow, 0); + else + result = removexattr(path->narrow, attribute->narrow, XATTR_NOFOLLOW); +#else if (path->fd > -1) result = fremovexattr(path->fd, attribute->narrow); else if (follow_symlinks) result = removexattr(path->narrow, attribute->narrow); else result = lremovexattr(path->narrow, attribute->narrow); +#endif Py_END_ALLOW_THREADS; if (result) { @@ -13987,6 +14214,91 @@ os_removexattr_impl(PyObject *module, path_t *path, path_t *attribute, Py_RETURN_NONE; } +#ifdef __FreeBSD__ + +static PyObject * +freebsd_listxattr_impl(PyObject* list, path_t *path, int follow_symlinks) { + PyObject *result = list; + const int namespaces[] = {EXTATTR_NAMESPACE_SYSTEM, EXTATTR_NAMESPACE_USER}; + const char *nsprefix[] = {"system.", "user."}; + const size_t max_nsprefix_len = 7; + const int privileged_namespaces = 1; + + const char *name = path->narrow ? path->narrow : "."; + + for (int i = 0; i < sizeof(namespaces) / sizeof(int); i++) { + ssize_t length; + const int namespace = namespaces[i]; + Py_BEGIN_ALLOW_THREADS; + if (path->fd > -1) + length = extattr_list_fd(path->fd, namespace, NULL, 0); + else if (follow_symlinks) + length = extattr_list_file(name, namespace, NULL, 0); + else + length = extattr_list_link(name, namespace, NULL, 0); + Py_END_ALLOW_THREADS; + if (length < 0) { + if (i < privileged_namespaces && errno == EPERM) { + continue; + } else { + path_error(path); + result = NULL; + break; + } + } + char* buffer = PyMem_Malloc(length); + if (buffer == NULL) { + result = NULL; + break; + } + Py_BEGIN_ALLOW_THREADS; + if (path->fd > -1) + length = extattr_list_fd(path->fd, namespace, buffer, length); + else if (follow_symlinks) + length = extattr_list_file(name, namespace, buffer, length); + else + length = extattr_list_link(name, namespace, buffer, length); + Py_END_ALLOW_THREADS; + if (length < 0) { + PyMem_Free(buffer); + path_error(path); + result = NULL; + break; + } + static_assert(EXTATTR_MAXNAMELEN <= UCHAR_MAX, + "extattr length can exceed UCHAR_MAX"); + // buffer to build qualified "{namespace}.{attr}" strings, including + // prefix and separator: + char qual_name[UCHAR_MAX + 8]; + assert(sizeof(qual_name) >= EXTATTR_MAXNAMELEN + max_nsprefix_len + 1); + char *attr_suffix = stpcpy(qual_name, nsprefix[i]); + unsigned char attr_len; + for (size_t pos = 0; pos < length; pos += attr_len + 1) { + attr_len = (unsigned char)buffer[pos]; + const char *attr = buffer + pos + 1; + char *qual_name_end = mempcpy(attr_suffix, attr, attr_len); + const size_t qual_name_len = qual_name_end - qual_name; + PyObject *attribute = PyUnicode_DecodeFSDefaultAndSize(qual_name, qual_name_len); + if (!attribute) { + result = NULL; + break; + } + int error = PyList_Append(list, attribute); + Py_DECREF(attribute); + if (error) { + result = NULL; + break; + } + } + PyMem_Free(buffer); + if (result == NULL) { + break; + } + } + + return result; +} +#endif /*[clinic input] os.listxattr @@ -14008,86 +14320,103 @@ static PyObject * os_listxattr_impl(PyObject *module, path_t *path, int follow_symlinks) /*[clinic end generated code: output=bebdb4e2ad0ce435 input=9826edf9fdb90869]*/ { - Py_ssize_t i; - PyObject *result = NULL; - const char *name; - char *buffer = NULL; - if (fd_and_follow_symlinks_invalid("listxattr", path->fd, follow_symlinks)) - goto exit; + return NULL; if (PySys_Audit("os.listxattr", "(O)", path->object ? path->object : Py_None) < 0) { return NULL; } +#if defined(HAVE_SYS_XATTR_H) + Py_ssize_t i; + PyObject *result = NULL; + const char *name; + char *buffer = NULL, *start, *end, *trace; + size_t buffer_size = 0; + ssize_t length = 0; + name = path->narrow ? path->narrow : "."; - for (i = 0; ; i++) { - const char *start, *trace, *end; - ssize_t length; - static const Py_ssize_t buffer_sizes[] = { 256, XATTR_LIST_MAX, 0 }; - Py_ssize_t buffer_size = buffer_sizes[i]; - if (!buffer_size) { - /* ERANGE */ - path_error(path); - break; - } - buffer = PyMem_Malloc(buffer_size); - if (!buffer) { - PyErr_NoMemory(); - break; + for (i = 0; i < 2; i++) { + if (i) { + buffer = PyMem_Malloc(buffer_size); + if (!buffer) + return NULL; } Py_BEGIN_ALLOW_THREADS; +#ifdef __APPLE__ + if (path->fd > -1) + length = flistxattr(path->fd, buffer, buffer_size, 0); + else if (follow_symlinks) + length = listxattr(name, buffer, buffer_size, 0); + else + length = listxattr(name, buffer, buffer_size, XATTR_NOFOLLOW); +#else if (path->fd > -1) length = flistxattr(path->fd, buffer, buffer_size); else if (follow_symlinks) length = listxattr(name, buffer, buffer_size); else length = llistxattr(name, buffer, buffer_size); +#endif Py_END_ALLOW_THREADS; if (length < 0) { - if (errno == ERANGE) { - PyMem_Free(buffer); - buffer = NULL; - continue; - } path_error(path); - break; - } - - result = PyList_New(0); - if (!result) { goto exit; } - end = buffer + length; - for (trace = start = buffer; trace != end; trace++) { - if (!*trace) { - int error; - PyObject *attribute = PyUnicode_DecodeFSDefaultAndSize(start, - trace - start); - if (!attribute) { - Py_SETREF(result, NULL); - goto exit; - } - error = PyList_Append(result, attribute); - Py_DECREF(attribute); - if (error) { - Py_SETREF(result, NULL); - goto exit; - } - start = trace + 1; + if (! i) + buffer_size = length; + } + + result = PyList_New(0); + if (!result) { + goto exit; + } + + end = buffer + length; + for (trace = start = buffer; trace != end; trace++) { + if (!*trace) { + int error; + PyObject *attribute = PyUnicode_DecodeFSDefaultAndSize(start, + trace - start); + if (!attribute) { + Py_SETREF(result, NULL); + goto exit; } + error = PyList_Append(result, attribute); + Py_DECREF(attribute); + if (error) { + Py_DECREF(result); + result = NULL; + goto exit; + } + start = trace + 1; } - break; } exit: if (buffer) PyMem_Free(buffer); return result; + +#elif defined(HAVE_SYS_EXTATTR_H) || defined(__FreeBSD__) + + PyObject *list = PyList_New(0); + if (!list) { + return NULL; + } + PyObject *result = freebsd_listxattr_impl(list, path, follow_symlinks); + if (!result) { + Py_DECREF(list); + return NULL; + } + return result; + +#endif + } #endif /* USE_XATTRS */ @@ -16487,8 +16816,19 @@ all_ins(PyObject *m) #ifdef USE_XATTRS if (PyModule_AddIntMacro(m, XATTR_CREATE)) return -1; if (PyModule_AddIntMacro(m, XATTR_REPLACE)) return -1; +#endif +#ifdef XATTR_SIZE_MAX // Linux if (PyModule_AddIntMacro(m, XATTR_SIZE_MAX)) return -1; #endif +#ifdef XATTR_NAME_MAX // Linux + if (PyModule_AddIntMacro(m, XATTR_NAME_MAX)) return -1; +#endif +#ifdef XATTR_MAXNAMELEN // Darwin + if (PyModule_AddIntMacro(m, XATTR_MAXNAMELEN)) return -1; +#endif +#ifdef EXTATTR_MAXNAMELEN // FreeBSD + if (PyModule_AddIntMacro(m, EXTATTR_MAXNAMELEN)) return -1; +#endif #if HAVE_DECL_RTLD_LAZY if (PyModule_AddIntMacro(m, RTLD_LAZY)) return -1; diff --git a/configure b/configure index 8326a1db06c2da..3c08b12be5c13b 100755 --- a/configure +++ b/configure @@ -10529,6 +10529,12 @@ if test "x$ac_cv_header_sys_eventfd_h" = xyes then : printf "%s\n" "#define HAVE_SYS_EVENTFD_H 1" >>confdefs.h +fi +ac_fn_c_check_header_compile "$LINENO" "sys/extattr.h" "ac_cv_header_sys_extattr_h" "$ac_includes_default" +if test "x$ac_cv_header_sys_extattr_h" = xyes +then : + printf "%s\n" "#define HAVE_SYS_EXTATTR_H 1" >>confdefs.h + fi ac_fn_c_check_header_compile "$LINENO" "sys/file.h" "ac_cv_header_sys_file_h" "$ac_includes_default" if test "x$ac_cv_header_sys_file_h" = xyes diff --git a/configure.ac b/configure.ac index 843f2b267a5253..de89603dff1e2a 100644 --- a/configure.ac +++ b/configure.ac @@ -2671,7 +2671,7 @@ AC_CHECK_HEADERS([ \ linux/random.h linux/soundcard.h \ linux/tipc.h linux/wait.h netdb.h net/ethernet.h netinet/in.h netpacket/packet.h poll.h process.h pthread.h pty.h \ sched.h setjmp.h shadow.h signal.h spawn.h stropts.h sys/audioio.h sys/bsdtty.h sys/devpoll.h \ - sys/endian.h sys/epoll.h sys/event.h sys/eventfd.h sys/file.h sys/ioctl.h sys/kern_control.h \ + sys/endian.h sys/epoll.h sys/event.h sys/eventfd.h sys/extattr.h sys/file.h sys/ioctl.h sys/kern_control.h \ sys/loadavg.h sys/lock.h sys/memfd.h sys/mkdev.h sys/mman.h sys/modem.h sys/param.h sys/poll.h \ sys/random.h sys/resource.h sys/select.h sys/sendfile.h sys/socket.h sys/soundcard.h sys/stat.h \ sys/statvfs.h sys/sys_domain.h sys/syscall.h sys/sysmacros.h sys/termio.h sys/time.h sys/times.h \ diff --git a/pyconfig.h.in b/pyconfig.h.in index 86c72cc6b4e62a..57ca1b1b6014ee 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -1290,6 +1290,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_SYS_EVENT_H +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_EXTATTR_H + /* Define to 1 if you have the header file. */ #undef HAVE_SYS_FILE_H