[pypy-commit] pypy py3.5-xattr: wip

rlamy pypy.commits at gmail.com
Tue Dec 19 10:13:33 EST 2017


Author: Ronan Lamy <ronan.lamy at gmail.com>
Branch: py3.5-xattr
Changeset: r93491:61730fb1f196
Date: 2017-12-19 15:11 +0000
http://bitbucket.org/pypy/pypy/changeset/61730fb1f196/

Log:	wip

diff --git a/pypy/module/posix/interp_posix.py b/pypy/module/posix/interp_posix.py
--- a/pypy/module/posix/interp_posix.py
+++ b/pypy/module/posix/interp_posix.py
@@ -122,7 +122,7 @@
     else:
         path_b = path.as_bytes
         assert path_b is not None
-        return func(path.as_bytes, *args)
+        return func(path_b, *args)
 
 
 class Path(object):
@@ -2283,7 +2283,9 @@
 This function will not follow symbolic links.
 Equivalent to chflags(path, flags, follow_symlinks=False)."""
 
-def getxattr():
+ at unwrap_spec(path=path_or_fd(), attribute=path_or_fd(allow_fd=False),
+             follow_symlinks=bool)
+def getxattr(space, path, attribute, __kwonly__, follow_symlinks=True):
     """getxattr(path, attribute, *, follow_symlinks=True) -> value
 
 Return the value of extended attribute attribute on path.
@@ -2292,8 +2294,29 @@
 If follow_symlinks is False, and the last element of the path is a symbolic
   link, getxattr will examine the symbolic link itself instead of the file
   the link points to."""
+    if path.as_fd != -1:
+        if not follow_symlinks:
+            raise oefmt(space.w_ValueError,
+                "getxattr: cannot use fd and follow_symlinks together")
+        try:
+            result = rposix.fgetxattr(path.as_fd, attribute.as_bytes)
+        except OSError as e:
+            raise wrap_oserror(space, e, eintr_retry=False)
+    else:
+        try:
+            if follow_symlinks:
+                result = rposix.getxattr(path.as_bytes, attribute.as_bytes)
+            else:
+                result = rposix.lgetxattr(path.as_bytes, attribute.as_bytes)
+        except OSError as e:
+            raise wrap_oserror(space, e, eintr_retry=False)
+    return space.newbytes(result)
 
-def setxattr():
+ at unwrap_spec(path=path_or_fd(), attribute=path_or_fd(allow_fd=False),
+             flags=c_int,
+             follow_symlinks=bool)
+def setxattr(space, path, attribute, w_value, flags=0,
+             __kwonly__=None, follow_symlinks=True):
     """setxattr(path, attribute, value, flags=0, *, follow_symlinks=True)
 
 Set extended attribute attribute on path to value.
@@ -2301,9 +2324,28 @@
 If follow_symlinks is False, and the last element of the path is a symbolic
   link, setxattr will modify the symbolic link itself instead of the file
   the link points to."""
+    value = space.charbuf_w(w_value)
+    if path.as_fd != -1:
+        if not follow_symlinks:
+            raise oefmt(space.w_ValueError,
+                "setxattr: cannot use fd and follow_symlinks together")
+        try:
+            rposix.fsetxattr(path.as_fd, attribute.as_bytes, value)
+        except OSError as e:
+            raise wrap_oserror(space, e, eintr_retry=False)
+    else:
+        try:
+            if follow_symlinks:
+                rposix.setxattr(path.as_bytes, attribute.as_bytes, value)
+            else:
+                rposix.lsetxattr(path.as_bytes, attribute.as_bytes, value)
+        except OSError as e:
+            raise wrap_oserror(space, e, eintr_retry=False)
 
 
-def removexattr():
+ at unwrap_spec(path=path_or_fd(), attribute=path_or_fd(allow_fd=False),
+             follow_symlinks=bool)
+def removexattr(space, path, attribute, __kwonly__, follow_symlinks=True):
     """removexattr(path, attribute, *, follow_symlinks=True)
 
 Remove extended attribute attribute on path.
@@ -2311,8 +2353,27 @@
 If follow_symlinks is False, and the last element of the path is a symbolic
   link, removexattr will modify the symbolic link itself instead of the file
   the link points to."""
+    if path.as_fd != -1:
+        if not follow_symlinks:
+            raise oefmt(space.w_ValueError,
+                "removexattr: cannot use fd and follow_symlinks together")
+        try:
+            rposix.fremovexattr(path.as_fd, attribute.as_bytes)
+        except OSError as e:
+            raise wrap_oserror(space, e, eintr_retry=False)
+    else:
+        try:
+            if follow_symlinks:
+                rposix.removexattr(path.as_bytes, attribute.as_bytes)
+            else:
+                rposix.lremovexattr(path.as_bytes, attribute.as_bytes)
+        except OSError as e:
+            raise wrap_oserror(space, e, eintr_retry=False)
 
-def listxattr():
+
+ at unwrap_spec(path=path_or_fd(), attribute=path_or_fd(allow_fd=False),
+             follow_symlinks=bool)
+def listxattr(space, path, __kwonly__, follow_symlinks=True):
     """listxattr(path='.', *, follow_symlinks=True)
 
 Return a list of extended attributes on path.
@@ -2322,6 +2383,23 @@
 If follow_symlinks is False, and the last element of the path is a symbolic
   link, listxattr will examine the symbolic link itself instead of the file
   the link points to."""
+    if path.as_fd != -1:
+        if not follow_symlinks:
+            raise oefmt(space.w_ValueError,
+                        "listxattr: cannot use fd and follow_symlinks together")
+        try:
+            result = rposix.flistxattr(path.as_fd, attribute.as_bytes)
+        except OSError as e:
+            raise wrap_oserror(space, e, eintr_retry=False)
+    else:
+        try:
+            if follow_symlinks:
+                result = rposix.listxattr(path.as_bytes, attribute.as_bytes)
+            else:
+                result = rposix.llistxattr(path.as_bytes, attribute.as_bytes)
+        except OSError as e:
+            raise wrap_oserror(space, e, eintr_retry=False)
+    return xxx
 
 
 have_functions = []
@@ -2449,8 +2527,8 @@
 
 @unwrap_spec(policy=int)
 def sched_get_priority_max(space, policy):
-    """returns the maximum priority value that 
-    can be used with the scheduling algorithm 
+    """returns the maximum priority value that
+    can be used with the scheduling algorithm
     identified by policy
     """
     while True:
@@ -2464,7 +2542,7 @@
 @unwrap_spec(policy=int)
 def sched_get_priority_min(space, policy):
     """returns the minimum priority value that
-     can be used with the scheduling algorithm 
+     can be used with the scheduling algorithm
      identified by policy
     """
     while True:
@@ -2477,7 +2555,7 @@
 
 @unwrap_spec(fd=c_int, cmd=c_int, length=r_longlong)
 def lockf(space, fd, cmd, length):
-    """apply, test or remove a POSIX lock on an 
+    """apply, test or remove a POSIX lock on an
     open file.
     """
     while True:
diff --git a/rpython/rlib/rposix.py b/rpython/rlib/rposix.py
--- a/rpython/rlib/rposix.py
+++ b/rpython/rlib/rposix.py
@@ -2574,3 +2574,112 @@
         """Passes offset==NULL; not support on all OSes"""
         res = c_sendfile(out_fd, in_fd, lltype.nullptr(_OFF_PTR_T.TO), count)
         return handle_posix_error('sendfile', res)
+
+# ____________________________________________________________
+# Support for *xattr functions
+
+if sys.platform.startswith('linux'):
+
+    class CConfig:
+        _compilation_info_ = ExternalCompilationInfo(
+            includes=['sys/xattr.h', 'linux/limits.h'],)
+        XATTR_SIZE_MAX = rffi_platform.DefinedConstantInteger('XATTR_SIZE_MAX')
+        XATTR_CREATE = rffi_platform.DefinedConstantInteger('XATTR_CREATE')
+        XATTR_REPLACE = rffi_platform.DefinedConstantInteger('XATTR_REPLACE')
+
+    cConfig = rffi_platform.configure(CConfig)
+    globals().update(cConfig)
+    c_fgetxattr = external('fgetxattr',
+        [rffi.INT, rffi.CCHARP, rffi.VOIDP, rffi.SIZE_T], rffi.SSIZE_T,
+        compilation_info=CConfig._compilation_info_,
+        save_err=rffi.RFFI_SAVE_ERRNO)
+    c_getxattr = external('getxattr',
+        [rffi.CCHARP, rffi.CCHARP, rffi.VOIDP, rffi.SIZE_T], rffi.SSIZE_T,
+        compilation_info=CConfig._compilation_info_,
+        save_err=rffi.RFFI_SAVE_ERRNO)
+    c_lgetxattr = external('lgetxattr',
+        [rffi.CCHARP, rffi.CCHARP, rffi.VOIDP, rffi.SIZE_T], rffi.SSIZE_T,
+        compilation_info=CConfig._compilation_info_,
+        save_err=rffi.RFFI_SAVE_ERRNO)
+    c_fsetxattr = external('fsetxattr',
+        [rffi.INT, rffi.CCHARP, rffi.CCHARP, rffi.SIZE_T, rffi.INT],
+        rffi.SSIZE_T,
+        compilation_info=CConfig._compilation_info_,
+        save_err=rffi.RFFI_SAVE_ERRNO)
+    c_setxattr = external('setxattr',
+        [rffi.CCHARP, rffi.CCHARP, rffi.CCHARP, rffi.SIZE_T, rffi.INT],
+        rffi.SSIZE_T,
+        compilation_info=CConfig._compilation_info_,
+        save_err=rffi.RFFI_SAVE_ERRNO)
+    c_lsetxattr = external('lsetxattr',
+        [rffi.CCHARP, rffi.CCHARP, rffi.CCHARP, rffi.SIZE_T, rffi.INT],
+        rffi.SSIZE_T,
+        compilation_info=CConfig._compilation_info_,
+        save_err=rffi.RFFI_SAVE_ERRNO)
+    c_fremovexattr = external('fremovexattr',
+        [rffi.INT, rffi.CCHARP], rffi.SSIZE_T,
+        compilation_info=CConfig._compilation_info_,
+        save_err=rffi.RFFI_SAVE_ERRNO)
+    c_removexattr = external('removexattr',
+        [rffi.CCHARP, rffi.CCHARP], rffi.SSIZE_T,
+        compilation_info=CConfig._compilation_info_,
+        save_err=rffi.RFFI_SAVE_ERRNO)
+    c_lremovexattr = external('lremovexattr',
+        [rffi.CCHARP, rffi.CCHARP], rffi.SSIZE_T,
+        compilation_info=CConfig._compilation_info_,
+        save_err=rffi.RFFI_SAVE_ERRNO)
+    buf_sizes = [256, XATTR_SIZE_MAX]
+
+    def fgetxattr(fd, name):
+        for size in buf_sizes:
+            with rffi.scoped_alloc_buffer(size) as buf:
+                void_buf = rffi.cast(rffi.VOIDP, buf.raw)
+                res = c_fgetxattr(fd, name, void_buf, size)
+                if res < 0:
+                    err = get_saved_errno()
+                    if err != errno.ERANGE:
+                        raise OSError(err, 'fgetxattr failed')
+                else:
+                    return buf.str(res)
+        else:
+            raise OSError(errno.ERANGE, 'fgetxattr failed')
+
+    def getxattr(path, name, follow_symlinks=True):
+        for size in buf_sizes:
+            with rffi.scoped_alloc_buffer(size) as buf:
+                void_buf = rffi.cast(rffi.VOIDP, buf.raw)
+                if follow_symlinks:
+                    res = c_getxattr(path, name, void_buf, size)
+                else:
+                    res = c_lgetxattr(path, name, void_buf, size)
+                if res < 0:
+                    err = get_saved_errno()
+                    if err != errno.ERANGE:
+                        c_name = 'getxattr' if follow_symlinks else 'lgetxattr'
+                        raise OSError(err, c_name + 'failed')
+                else:
+                    return buf.str(res)
+        else:
+            c_name = 'getxattr' if follow_symlinks else 'lgetxattr'
+            raise OSError(errno.ERANGE, c_name + 'failed')
+
+    def fsetxattr(fd, name, value, flags=0):
+        return handle_posix_error(
+            'fsetxattr', c_fsetxattr(fd, name, value, len(value), flags))
+
+    def setxattr(path, name, value, flags=0, follow_symlinks=True):
+        if follow_symlinks:
+            return handle_posix_error(
+                'setxattr', c_setxattr(path, name, value, len(value), flags))
+        else:
+            return handle_posix_error(
+                'lsetxattr', c_lsetxattr(path, name, value, len(value), flags))
+
+    def fremovexattr(fd, name):
+        return handle_posix_error('fremovexattr', c_fremovexattr(fd, name))
+
+    def removexattr(path, name, follow_symlinks=True):
+        if follow_symlinks:
+            return handle_posix_error('removexattr', c_removexattr(path, name))
+        else:
+            return handle_posix_error('lremovexattr', c_lremovexattr(path, name))
diff --git a/rpython/rlib/test/test_rposix.py b/rpython/rlib/test/test_rposix.py
--- a/rpython/rlib/test/test_rposix.py
+++ b/rpython/rlib/test/test_rposix.py
@@ -1,3 +1,6 @@
+from hypothesis import given, strategies as st, assume
+import pytest
+
 from rpython.rtyper.test.test_llinterp import interpret
 from rpython.translator.c.test.test_genc import compile
 from rpython.tool.pytest.expecttest import ExpectTest
@@ -8,10 +11,10 @@
 import py
 
 def rposix_requires(funcname):
-    return py.test.mark.skipif(not hasattr(rposix, funcname),
+    return pytest.mark.skipif(not hasattr(rposix, funcname),
         reason="Requires rposix.%s()" % funcname)
 
-win_only = py.test.mark.skipif("os.name != 'nt'")
+win_only = pytest.mark.skipif("os.name != 'nt'")
 
 class TestPosixFunction:
     def test_access(self):
@@ -827,3 +830,47 @@
         rposix.lockf(fd, rposix.F_ULOCK, 4)
     finally:
         os.close(fd)
+
+def check_working_xattr():
+    fname = str(udir.join('xattr_test0.txt'))
+    with open(fname, 'wb'):
+        pass
+    try:
+        rposix.getxattr(fname, 'foo')
+    except OSError as e:
+        return e.errno != errno.ENOTSUP
+    else:
+       raise RuntimeError('getxattr() succeeded unexpectedly!?!')
+
+ at pytest.mark.skipif(not (hasattr(rposix, 'getxattr') and check_working_xattr()),
+    reason="Requires working rposix.getxattr()")
+ at given(name=st.binary(max_size=10), value=st.binary(max_size=10),
+    follow_symlinks=st.booleans(), use_fd=st.booleans())
+def test_xattr(name, value, follow_symlinks, use_fd):
+    use_fd = False
+    assume(follow_symlinks or not use_fd)
+    fname = str(udir.join('xattr_test.txt'))
+    with open(fname, 'wb'):
+        pass
+    if use_fd:
+        file_id = os.open(fname, os.O_CREAT, 0777)
+        read, write, delete = rposix.fgetxattr, rposix.fsetxattr, rposix.fremovexattr
+    else:
+        file_id = fname
+        if follow_symlinks:
+            read, write, delete = rposix.getxattr, rposix.setxattr, rposix.removexattr
+        else:
+            read = lambda *args, **kwargs: rposix.getxattr(*args, follow_symlinks=False, **kwargs)
+            write = lambda *args, **kwargs: rposix.setxattr(*args, follow_symlinks=False, **kwargs)
+            delete = lambda *args, **kwargs: rposix.removexattr(*args, follow_symlinks=False, **kwargs)
+    try:
+        with pytest.raises(OSError):
+            read(file_id, name)
+        write(file_id, name, value)
+        assert read(file_id, name) == value
+        delete(file_id, name)
+        with pytest.raises(OSError):
+            read(file_id, name)
+    finally:
+        if use_fd:
+            os.close(file_id)


More information about the pypy-commit mailing list