diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 0c5c40cc6de..e9352d958e5 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -751,6 +751,26 @@ as internal buffering of data. This function is not available on MacOS. +.. function:: fgetxattr(fd, attr) + + This works exactly like :func:`getxattr` but operates on a file descriptor, + *fd*, instead of a path. + + Availability: Linux + + .. versionadded:: 3.3 + + +.. function:: flistxattr(fd) + + This is exactly like :func:`listxattr` but operates on a file descriptor, + *fd*, instead of a path. + + Availability: Linux + + .. versionadded:: 3.3 + + .. function:: fdlistdir(fd) Like :func:`listdir`, but uses a file descriptor instead and always returns @@ -836,6 +856,27 @@ as internal buffering of data. Availability: Unix. +.. function:: fremovexattr(fd, attr) + + This works exactly like :func:`removexattr` but operates on a file + descriptor, *fd*, instead of a path. + + Availability: Linux + + .. versionadded:: 3.3 + + +.. function:: fsetxattr(fd, attr, value, flags=0) + + This works exactly like :func:`setxattr` but on a file descriptor, *fd*, + instead of a path. + + + Availability: Linux + + .. versionadded:: 3.3 + + .. function:: futimesat(dirfd, path, (atime, mtime)) futimesat(dirfd, path, None) @@ -1536,6 +1577,17 @@ Files and Directories Availability: Unix. +.. function:: getxattr(path, attr) + + Return the value of the extended filesystem attribute *attr* for + *path*. *attr* can be bytes or str. If it is str, it is encoded with the + filesystem encoding. + + Availability: Linux + + .. versionadded:: 3.3 + + .. function:: lchflags(path, flags) Set the flags of *path* to the numeric *flags*, like :func:`chflags`, but do not @@ -1561,6 +1613,15 @@ Files and Directories Availability: Unix. +.. function:: lgetxattr(path, attr) + + This works exactly like :func:`getxattr` but doesn't follow symlinks. + + Availability: Linux + + .. versionadded:: 3.3 + + .. function:: link(source, link_name) Create a hard link pointing to *source* named *link_name*. @@ -1585,6 +1646,44 @@ Files and Directories .. versionchanged:: 3.2 The *path* parameter became optional. + +.. function:: listxattr(path) + + Return a list of the extended filesystem attributes on *path*. Attributes are + returned as string decoded with the filesystem encoding. + + Availability: Linux + + .. versionadded:: 3.3 + + +.. function:: llistxattr(path) + + This works exactly like :func:`listxattr` but doesn't follow symlinks. + + Availability: Linux + + .. versionadded:: 3.3 + + +.. function:: lremoveattr(path, attr) + + This works exactly like :func:`removeattr` but doesn't follow symlinks. + + Availability: Linux + + .. versionadded:: 3.3 + + +.. function:: lsetxattr(path, attr, value, flags=0) + + This works exactly like :func:`setxattr` but doesn't follow symlinks. + + Availability: Linux + + .. versionadded:: 3.3 + + .. function:: lstat(path) Perform the equivalent of an :c:func:`lstat` system call on the given path. @@ -1758,6 +1857,17 @@ Files and Directories successfully removed. +.. function:: removexattr(path, attr) + + Removes the extended filesystem attribute *attr* from *path*. *attr* should + be bytes or str. If it is a string, it is encoded with the filesystem + encoding. + + Availability: Linux + + .. versionadded:: 3.3 + + .. function:: rename(src, dst) Rename the file or directory *src* to *dst*. If *dst* is a directory, @@ -1794,6 +1904,44 @@ Files and Directories Availability: Unix, Windows. +.. data:: XATTR_SIZE_MAX + + The maximum size the value of an extended attribute can be. Currently, this + is 64 kilobytes on Linux. + + +.. data:: XATTR_CREATE + + This is a possible value for the flags argument in :func:`setxattr`. It + indicates the operation must create an attribute. + + +.. data:: XATTR_REPLACE + + This is a possible value for the flags argument in :func:`setxattr`. It + indicates the operation must replace an existing attribute. + + +.. function:: setxattr(path, attr, value, flags=0) + + Set the extended filesystem attribute *attr* on *path* to *value*. *attr* + must be a bytes or str with no embedded NULs. If it is str, it is encoded + with the filesystem encoding. *flags* may be :data:`XATTR_REPLACE` or + :data:`XATTR_CREATE`. If :data:`XATTR_REPLACE` is given and the attribute + does not exist, ``EEXISTS`` will be raised. If :data:`XATTR_CREATE` is given + and the attribute already exists, the attribute will not be created and + ``ENODATA`` will be raised. + + Availability: Linux + + .. note:: + + A bug in Linux kernel versions less than 2.6.39 caused the flags argument + to be ignored on some filesystems. + + .. versionadded:: 3.3 + + .. function:: stat(path) Perform the equivalent of a :c:func:`stat` system call on the given path. diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 1d5f11cec1c..569b2184a96 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -14,6 +14,8 @@ import shutil from test import support import contextlib import mmap +import platform +import re import uuid import asyncore import asynchat @@ -1506,6 +1508,97 @@ class TestSendfile(unittest.TestCase): raise +def supports_extended_attributes(): + if not hasattr(os, "setxattr"): + return False + try: + with open(support.TESTFN, "wb") as fp: + try: + os.fsetxattr(fp.fileno(), b"user.test", b"") + except OSError as e: + if e.errno != errno.ENOTSUP: + raise + return False + finally: + support.unlink(support.TESTFN) + # Kernels < 2.6.39 don't respect setxattr flags. + kernel_version = platform.release() + m = re.match("2.6.(\d{1,2})", kernel_version) + return m is None or int(m.group(1)) >= 39 + + +@unittest.skipUnless(supports_extended_attributes(), + "no non-broken extended attribute support") +class ExtendedAttributeTests(unittest.TestCase): + + def tearDown(self): + support.unlink(support.TESTFN) + + def _check_xattrs_str(self, s, getxattr, setxattr, removexattr, listxattr): + fn = support.TESTFN + open(fn, "wb").close() + with self.assertRaises(OSError) as cm: + getxattr(fn, s("user.test")) + self.assertEqual(cm.exception.errno, errno.ENODATA) + self.assertEqual(listxattr(fn), []) + setxattr(fn, s("user.test"), b"") + self.assertEqual(listxattr(fn), ["user.test"]) + self.assertEqual(getxattr(fn, b"user.test"), b"") + setxattr(fn, s("user.test"), b"hello", os.XATTR_REPLACE) + self.assertEqual(getxattr(fn, b"user.test"), b"hello") + with self.assertRaises(OSError) as cm: + setxattr(fn, s("user.test"), b"bye", os.XATTR_CREATE) + self.assertEqual(cm.exception.errno, errno.EEXIST) + with self.assertRaises(OSError) as cm: + setxattr(fn, s("user.test2"), b"bye", os.XATTR_REPLACE) + self.assertEqual(cm.exception.errno, errno.ENODATA) + setxattr(fn, s("user.test2"), b"foo", os.XATTR_CREATE) + self.assertEqual(sorted(listxattr(fn)), ["user.test", "user.test2"]) + removexattr(fn, s("user.test")) + with self.assertRaises(OSError) as cm: + getxattr(fn, s("user.test")) + self.assertEqual(cm.exception.errno, errno.ENODATA) + self.assertEqual(listxattr(fn), ["user.test2"]) + self.assertEqual(getxattr(fn, s("user.test2")), b"foo") + setxattr(fn, s("user.test"), b"a"*1024) + self.assertEqual(getxattr(fn, s("user.test")), b"a"*1024) + removexattr(fn, s("user.test")) + many = sorted("user.test{}".format(i) for i in range(100)) + for thing in many: + setxattr(fn, thing, b"x") + self.assertEqual(sorted(listxattr(fn)), many) + + def _check_xattrs(self, *args): + def make_bytes(s): + return bytes(s, "ascii") + self._check_xattrs_str(str, *args) + support.unlink(support.TESTFN) + self._check_xattrs_str(make_bytes, *args) + + def test_simple(self): + self._check_xattrs(os.getxattr, os.setxattr, os.removexattr, + os.listxattr) + + def test_lpath(self): + self._check_xattrs(os.lgetxattr, os.lsetxattr, os.lremovexattr, + os.llistxattr) + + def test_fds(self): + def getxattr(path, *args): + with open(path, "rb") as fp: + return os.fgetxattr(fp.fileno(), *args) + def setxattr(path, *args): + with open(path, "wb") as fp: + os.fsetxattr(fp.fileno(), *args) + def removexattr(path, *args): + with open(path, "wb") as fp: + os.fremovexattr(fp.fileno(), *args) + def listxattr(path, *args): + with open(path, "rb") as fp: + return os.flistxattr(fp.fileno(), *args) + self._check_xattrs(getxattr, setxattr, removexattr, listxattr) + + @support.reap_threads def test_main(): support.run_unittest( @@ -1529,6 +1622,7 @@ def test_main(): LinkTests, TestSendfile, ProgramPriorityTests, + ExtendedAttributeTests, ) if __name__ == "__main__": diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index d701690a5d0..1416a7c2418 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -107,6 +107,10 @@ corresponding Unix manual entries for more information on calls."); #include #endif +#ifdef HAVE_ATTR_XATTR_H +#include +#endif + #if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__APPLE__) #ifdef HAVE_SYS_SOCKET_H #include @@ -9950,6 +9954,384 @@ posix_mkfifoat(PyObject *self, PyObject *args) } #endif +#ifdef HAVE_ATTR_XATTR_H + +static int +try_getxattr(const char *path, const char *name, + ssize_t (*get)(const char *, const char *, void *, size_t), + Py_ssize_t buf_size, PyObject **res) +{ + PyObject *value; + Py_ssize_t len; + + assert(buf_size <= XATTR_SIZE_MAX); + value = PyBytes_FromStringAndSize(NULL, buf_size); + if (!value) + return 0; + Py_BEGIN_ALLOW_THREADS; + len = get(path, name, PyBytes_AS_STRING(value), buf_size); + Py_END_ALLOW_THREADS; + if (len < 0) { + Py_DECREF(value); + if (errno == ERANGE) { + value = NULL; + } + else { + posix_error(); + return 0; + } + } + else if (len != buf_size) { + /* Can only shrink. */ + _PyBytes_Resize(&value, len); + } + *res = value; + return 1; +} + +static PyObject * +getxattr_common(const char *path, PyObject *name_obj, + ssize_t (*get)(const char *, const char *, void *, size_t)) +{ + PyObject *value; + const char *name = PyBytes_AS_STRING(name_obj); + + /* Try a small value first. */ + if (!try_getxattr(path, name, get, 128, &value)) + return NULL; + if (value) + return value; + /* Now the maximum possible one. */ + if (!try_getxattr(path, name, get, XATTR_SIZE_MAX, &value)) + return NULL; + assert(value); + return value; +} + +PyDoc_STRVAR(posix_getxattr__doc__, +"getxattr(path, attr) -> value\n\n\ +Return the value of extended attribute *name* on *path*."); + +static PyObject * +posix_getxattr(PyObject *self, PyObject *args) +{ + PyObject *path, *res, *name; + + if (!PyArg_ParseTuple(args, "O&O&:getxattr", PyUnicode_FSConverter, &path, + PyUnicode_FSConverter, &name)) + return NULL; + res = getxattr_common(PyBytes_AS_STRING(path), name, getxattr); + Py_DECREF(path); + Py_DECREF(name); + return res; +} + +PyDoc_STRVAR(posix_lgetxattr__doc__, +"lgetxattr(path, attr) -> value\n\n\ +Like getxattr but don't follow symlinks."); + +static PyObject * +posix_lgetxattr(PyObject *self, PyObject *args) +{ + PyObject *path, *res, *name; + + if (!PyArg_ParseTuple(args, "O&O&:lgetxattr", PyUnicode_FSConverter, &path, + PyUnicode_FSConverter, &name)) + return NULL; + res = getxattr_common(PyBytes_AS_STRING(path), name, lgetxattr); + Py_DECREF(path); + Py_DECREF(name); + return res; +} + +static ssize_t +wrap_fgetxattr(const char *path, const char *name, void *value, size_t size) +{ + /* Hack to share code. */ + return fgetxattr((int)(Py_uintptr_t)path, name, value, size); +} + +PyDoc_STRVAR(posix_fgetxattr__doc__, +"fgetxattr(fd, attr) -> value\n\n\ +Like getxattr but operate on a fd instead of a path."); + +static PyObject * +posix_fgetxattr(PyObject *self, PyObject *args) +{ + PyObject *res, *name; + int fd; + + if (!PyArg_ParseTuple(args, "iO&:fgetxattr", &fd, PyUnicode_FSConverter, &name)) + return NULL; + res = getxattr_common((const char *)(Py_uintptr_t)fd, name, wrap_fgetxattr); + Py_DECREF(name); + return res; +} + +PyDoc_STRVAR(posix_setxattr__doc__, +"setxattr(path, attr, value, flags=0)\n\n\ +Set extended attribute *attr* on *path* to *value*."); + +static PyObject * +posix_setxattr(PyObject *self, PyObject *args) +{ + PyObject *path, *name; + Py_buffer data; + int flags = 0, err; + + if (!PyArg_ParseTuple(args, "O&O&y*|i:setxattr", PyUnicode_FSConverter, + &path, PyUnicode_FSConverter, &name, &data, &flags)) + return NULL; + Py_BEGIN_ALLOW_THREADS; + err = setxattr(PyBytes_AS_STRING(path), PyBytes_AS_STRING(name), + data.buf, data.len, flags); + Py_END_ALLOW_THREADS; + Py_DECREF(path); + Py_DECREF(name); + PyBuffer_Release(&data); + if (err) + return posix_error(); + Py_RETURN_NONE; +} + +PyDoc_STRVAR(posix_lsetxattr__doc__, +"lsetxattr(path, attr, value, flags=0)\n\n\ +Like setxattr but don't follow symlinks."); + +static PyObject * +posix_lsetxattr(PyObject *self, PyObject *args) +{ + PyObject *path, *name; + Py_buffer data; + int flags = 0, err; + + if (!PyArg_ParseTuple(args, "O&O&y*|i:lsetxattr", PyUnicode_FSConverter, + &path, PyUnicode_FSConverter, &name, &data, &flags)) + return NULL; + Py_BEGIN_ALLOW_THREADS; + err = lsetxattr(PyBytes_AS_STRING(path), PyBytes_AS_STRING(name), + data.buf, data.len, flags); + Py_END_ALLOW_THREADS; + Py_DECREF(path); + Py_DECREF(name); + PyBuffer_Release(&data); + if (err) + return posix_error(); + Py_RETURN_NONE; +} + +PyDoc_STRVAR(posix_fsetxattr__doc__, +"fsetxattr(fd, attr, value, flags=0)\n\n\ +Like setxattr but operates on *fd* instead of a path."); + +static PyObject * +posix_fsetxattr(PyObject *self, PyObject *args) +{ + Py_buffer data; + const char *name; + int fd, flags = 0, err; + + if (!PyArg_ParseTuple(args, "iO&y*|i:fsetxattr", &fd, PyUnicode_FSConverter, + &name, &data, &flags)) + return NULL; + Py_BEGIN_ALLOW_THREADS; + err = fsetxattr(fd, PyBytes_AS_STRING(name), data.buf, data.len, flags); + Py_END_ALLOW_THREADS; + Py_DECREF(name); + PyBuffer_Release(&data); + if (err) + return posix_error(); + Py_RETURN_NONE; +} + +PyDoc_STRVAR(posix_removexattr__doc__, +"removexattr(path, attr)\n\n\ +Remove extended attribute *attr* on *path*."); + +static PyObject * +posix_removexattr(PyObject *self, PyObject *args) +{ + PyObject *path, *name; + int err; + + if (!PyArg_ParseTuple(args, "O&O&:removexattr", PyUnicode_FSConverter, &path, + PyUnicode_FSConverter, &name)) + return NULL; + Py_BEGIN_ALLOW_THREADS; + err = removexattr(PyBytes_AS_STRING(path), PyBytes_AS_STRING(name)); + Py_END_ALLOW_THREADS; + Py_DECREF(path); + Py_DECREF(name); + if (err) + return posix_error(); + Py_RETURN_NONE; +} + +PyDoc_STRVAR(posix_lremovexattr__doc__, +"lremovexattr(path, attr)\n\n\ +Like removexattr but don't follow symlinks."); + +static PyObject * +posix_lremovexattr(PyObject *self, PyObject *args) +{ + PyObject *path, *name; + int err; + + if (!PyArg_ParseTuple(args, "O&O&:lremovexattr", PyUnicode_FSConverter, &path, + PyUnicode_FSConverter, &name)) + return NULL; + Py_BEGIN_ALLOW_THREADS; + err = lremovexattr(PyBytes_AS_STRING(path), PyBytes_AS_STRING(name)); + Py_END_ALLOW_THREADS; + Py_DECREF(path); + Py_DECREF(name); + if (err) + return posix_error(); + Py_RETURN_NONE; +} + +PyDoc_STRVAR(posix_fremovexattr__doc__, +"fremovexattr(fd, attr)\n\n\ +Like removexattr but operates on a file descriptor."); + +static PyObject * +posix_fremovexattr(PyObject *self, PyObject *args) +{ + PyObject *name; + int fd, err; + + if (!PyArg_ParseTuple(args, "iO&:fremovexattr", &fd, + PyUnicode_FSConverter, &name)) + return NULL; + Py_BEGIN_ALLOW_THREADS; + err = fremovexattr(fd, PyBytes_AS_STRING(name)); + Py_END_ALLOW_THREADS; + Py_DECREF(name); + if (err) + return posix_error(); + Py_RETURN_NONE; +} + +static Py_ssize_t +try_listxattr(const char *path, ssize_t (*list)(const char *, char *, size_t), + Py_ssize_t buf_size, char **buf) +{ + Py_ssize_t len; + + *buf = PyMem_MALLOC(buf_size); + if (!*buf) { + PyErr_NoMemory(); + return -1; + } + Py_BEGIN_ALLOW_THREADS; + len = list(path, *buf, buf_size); + Py_END_ALLOW_THREADS; + if (len < 0) { + PyMem_FREE(*buf); + if (errno != ERANGE) + posix_error(); + return -1; + } + return len; +} + +static PyObject * +listxattr_common(const char *path, ssize_t (*list)(const char *, char *, size_t)) +{ + PyObject *res, *attr; + Py_ssize_t len, err, start, i; + char *buf; + + len = try_listxattr(path, list, 256, &buf); + if (len < 0) { + if (PyErr_Occurred()) + return NULL; + len = try_listxattr(path, list, XATTR_LIST_MAX, &buf); + if (len < 0) + return NULL; + } + res = PyList_New(0); + if (!res) { + PyMem_FREE(buf); + return NULL; + } + for (start = i = 0; i < len; i++) { + if (!buf[i]) { + attr = PyUnicode_DecodeFSDefaultAndSize(&buf[start], i - start); + if (!attr) { + Py_DECREF(res); + PyMem_FREE(buf); + return NULL; + } + err = PyList_Append(res, attr); + Py_DECREF(attr); + if (err) { + Py_DECREF(res); + PyMem_FREE(buf); + return NULL; + } + start = i + 1; + } + } + PyMem_FREE(buf); + return res; +} + +PyDoc_STRVAR(posix_listxattr__doc__, +"listxattr(path)\n\n\ +Return a list of extended attributes on *path*."); + +static PyObject * +posix_listxattr(PyObject *self, PyObject *args) +{ + PyObject *path, *res; + + if (!PyArg_ParseTuple(args, "O&:listxattr", PyUnicode_FSConverter, &path)) + return NULL; + res = listxattr_common(PyBytes_AS_STRING(path), listxattr); + Py_DECREF(path); + return res; +} + +PyDoc_STRVAR(posix_llistxattr__doc__, +"llistxattr(path)\n\n\ +Like listxattr but don't follow symlinks.."); + +static PyObject * +posix_llistxattr(PyObject *self, PyObject *args) +{ + PyObject *path, *res; + + if (!PyArg_ParseTuple(args, "O&:llistxattr", PyUnicode_FSConverter, &path)) + return NULL; + res = listxattr_common(PyBytes_AS_STRING(path), llistxattr); + Py_DECREF(path); + return res; +} + +static ssize_t +wrap_flistxattr(const char *path, char *buf, size_t len) +{ + /* Hack to share code. */ + return flistxattr((int)(Py_uintptr_t)path, buf, len); +} + +PyDoc_STRVAR(posix_flistxattr__doc__, +"flistxattr(path)\n\n\ +Like flistxattr but operates on a file descriptor."); + +static PyObject * +posix_flistxattr(PyObject *self, PyObject *args) +{ + long fd; + + if (!PyArg_ParseTuple(args, "i:flistxattr", &fd)) + return NULL; + return listxattr_common((const char *)(Py_uintptr_t)fd, wrap_flistxattr); +} + +#endif /* HAVE_ATTR_XATTR_H */ + static PyMethodDef posix_methods[] = { {"access", posix_access, METH_VARARGS, posix_access__doc__}, #ifdef HAVE_TTYNAME @@ -10398,6 +10780,20 @@ static PyMethodDef posix_methods[] = { #endif #ifdef HAVE_MKFIFOAT {"mkfifoat", posix_mkfifoat, METH_VARARGS, posix_mkfifoat__doc__}, +#endif +#ifdef HAVE_ATTR_XATTR_H + {"setxattr", posix_setxattr, METH_VARARGS, posix_setxattr__doc__}, + {"lsetxattr", posix_lsetxattr, METH_VARARGS, posix_lsetxattr__doc__}, + {"fsetxattr", posix_fsetxattr, METH_VARARGS, posix_fsetxattr__doc__}, + {"getxattr", posix_getxattr, METH_VARARGS, posix_getxattr__doc__}, + {"lgetxattr", posix_lgetxattr, METH_VARARGS, posix_lgetxattr__doc__}, + {"fgetxattr", posix_fgetxattr, METH_VARARGS, posix_fgetxattr__doc__}, + {"removexattr", posix_removexattr, METH_VARARGS, posix_removexattr__doc__}, + {"lremovexattr", posix_lremovexattr, METH_VARARGS, posix_lremovexattr__doc__}, + {"fremovexattr", posix_fremovexattr, METH_VARARGS, posix_fremovexattr__doc__}, + {"listxattr", posix_listxattr, METH_VARARGS, posix_listxattr__doc__}, + {"llistxattr", posix_llistxattr, METH_VARARGS, posix_llistxattr__doc__}, + {"flistxattr", posix_flistxattr, METH_VARARGS, posix_flistxattr__doc__}, #endif {NULL, NULL} /* Sentinel */ }; @@ -10848,6 +11244,12 @@ all_ins(PyObject *d) #endif #endif +#ifdef HAVE_ATTR_XATTR_H + if (ins(d, "XATTR_CREATE", (long)XATTR_CREATE)) return -1; + if (ins(d, "XATTR_REPLACE", (long)XATTR_REPLACE)) return -1; + if (ins(d, "XATTR_SIZE_MAX", (long)XATTR_SIZE_MAX)) return -1; +#endif + #if defined(PYOS_OS2) if (insertvalues(d)) return -1; #endif diff --git a/configure b/configure index ed13d88b4a1..4200d88842a 100755 --- a/configure +++ b/configure @@ -6090,7 +6090,7 @@ $as_echo "#define STDC_HEADERS 1" >>confdefs.h fi -for ac_header in asm/types.h conio.h curses.h direct.h dlfcn.h errno.h \ +for ac_header in asm/types.h attr/xattr.h conio.h curses.h direct.h dlfcn.h errno.h \ fcntl.h grp.h \ ieeefp.h io.h langinfo.h libintl.h ncurses.h poll.h process.h pthread.h \ sched.h shadow.h signal.h stdint.h stropts.h termios.h \ diff --git a/configure.in b/configure.in index c10f67a7036..43745fb15d6 100644 --- a/configure.in +++ b/configure.in @@ -1299,7 +1299,7 @@ dnl AC_MSG_RESULT($cpp_type) # checks for header files AC_HEADER_STDC -AC_CHECK_HEADERS(asm/types.h conio.h curses.h direct.h dlfcn.h errno.h \ +AC_CHECK_HEADERS(asm/types.h attr/xattr.h conio.h curses.h direct.h dlfcn.h errno.h \ fcntl.h grp.h \ ieeefp.h io.h langinfo.h libintl.h ncurses.h poll.h process.h pthread.h \ sched.h shadow.h signal.h stdint.h stropts.h termios.h \ diff --git a/pyconfig.h.in b/pyconfig.h.in index eed1ff78ac5..bf9e7fee45e 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -64,6 +64,9 @@ /* Define if GCC supports __attribute__((format(PyArg_ParseTuple, 2, 3))) */ #undef HAVE_ATTRIBUTE_FORMAT_PARSETUPLE +/* Define to 1 if you have the header file. */ +#undef HAVE_ATTR_XATTR_H + /* Define to 1 if you have the `bind_textdomain_codeset' function. */ #undef HAVE_BIND_TEXTDOMAIN_CODESET