Author: Ronan Lamy <[email protected]>
Branch: py3.5
Changeset: r93498:fa25c452dbb5
Date: 2017-12-19 17:24 +0000
http://bitbucket.org/pypy/pypy/changeset/fa25c452dbb5/

Log:    Merged in py3.5-xattr (pull request #586)

        Implement posix.*xattr functions

diff --git a/pypy/module/posix/__init__.py b/pypy/module/posix/__init__.py
--- a/pypy/module/posix/__init__.py
+++ b/pypy/module/posix/__init__.py
@@ -229,7 +229,7 @@
         'POSIX_FADV_RANDOM', 'POSIX_FADV_NOREUSE', 'POSIX_FADV_DONTNEED']:
             assert getattr(rposix, _name) is not None, "missing %r" % (_name,)
             interpleveldefs[_name] = 'space.wrap(%d)' % getattr(rposix, _name)
-    
+
     if hasattr(rposix, 'sched_get_priority_max'):
         interpleveldefs['sched_get_priority_max'] = 
'interp_posix.sched_get_priority_max'
         interpleveldefs['sched_get_priority_min'] = 
'interp_posix.sched_get_priority_min'
@@ -246,11 +246,21 @@
 
     if hasattr(rposix, 'sched_yield'):
         interpleveldefs['sched_yield'] = 'interp_posix.sched_yield'
-        
+
     for _name in ["O_CLOEXEC"]:
         if getattr(rposix, _name) is not None:
             interpleveldefs[_name] = 'space.wrap(%d)' % getattr(rposix, _name)
 
+    if hasattr(rposix, 'getxattr'):
+        interpleveldefs['getxattr'] = 'interp_posix.getxattr'
+        interpleveldefs['setxattr'] = 'interp_posix.setxattr'
+        interpleveldefs['removexattr'] = 'interp_posix.removexattr'
+        interpleveldefs['listxattr'] = 'interp_posix.listxattr'
+        for _name in ['XATTR_SIZE_MAX', 'XATTR_CREATE', 'XATTR_REPLACE']:
+            if getattr(rposix, _name) is not None:
+                interpleveldefs[_name] = 'space.wrap(%d)' % getattr(rposix, 
_name)
+
+
     def startup(self, space):
         from pypy.module.posix import interp_posix
         from pypy.module.imp import importing
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():
+@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,27 @@
 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:
+            result = rposix.getxattr(path.as_bytes, attribute.as_bytes,
+                follow_symlinks=follow_symlinks)
+        except OSError as e:
+            raise wrap_oserror(space, e, eintr_retry=False)
+    return space.newbytes(result)
 
-def setxattr():
+@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 +2322,26 @@
 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:
+            rposix.setxattr(path.as_bytes, attribute.as_bytes, value,
+                follow_symlinks=follow_symlinks)
+        except OSError as e:
+            raise wrap_oserror(space, e, eintr_retry=False)
 
 
-def removexattr():
+@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 +2349,24 @@
 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:
+            rposix.removexattr(path.as_bytes, attribute.as_bytes,
+                follow_symlinks=follow_symlinks)
+        except OSError as e:
+            raise wrap_oserror(space, e, eintr_retry=False)
 
-def listxattr():
+
+@unwrap_spec(path=path_or_fd(), 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 +2376,20 @@
 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)
+        except OSError as e:
+            raise wrap_oserror(space, e, eintr_retry=False)
+    else:
+        try:
+            result = rposix.listxattr(path.as_bytes, follow_symlinks)
+        except OSError as e:
+            raise wrap_oserror(space, e, eintr_retry=False)
+    return space.newlist([space.newbytes(attr) for attr in result])
 
 
 have_functions = []
@@ -2449,8 +2517,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 +2532,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 +2545,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/pypy/module/posix/test/test_posix2.py 
b/pypy/module/posix/test/test_posix2.py
--- a/pypy/module/posix/test/test_posix2.py
+++ b/pypy/module/posix/test/test_posix2.py
@@ -386,8 +386,8 @@
 
     def test_times(self):
         """
-        posix.times() should return a posix.times_result object giving 
-        float-representations (seconds, effectively) of the four fields from 
+        posix.times() should return a posix.times_result object giving
+        float-representations (seconds, effectively) of the four fields from
         the underlying struct tms and the return value.
         """
         result = self.posix.times()
@@ -977,7 +977,7 @@
             assert posix.sched_get_priority_min(posix.SCHED_OTHER) != -1
             if getattr(posix, 'SCHED_BATCH', None):
                 assert posix.sched_get_priority_min(posix.SCHED_BATCH) != -1
-            
+
     if hasattr(rposix, 'sched_get_priority_min'):
         def test_os_sched_priority_max_greater_than_min(self):
             posix, os = self.posix, self.os
@@ -992,7 +992,7 @@
         def test_sched_yield(self):
             os = self.posix
             #Always suceeds on Linux
-            os.sched_yield() 
+            os.sched_yield()
 
     def test_write_buffer(self):
         os = self.posix
@@ -1350,7 +1350,7 @@
             posix.close(fd)
             s2.close()
             s1.close()
-            
+
         def test_os_lockf(self):
             posix, os = self.posix, self.os
             fd = os.open(self.path2 + 'test_os_lockf', os.O_WRONLY | 
os.O_CREAT)
@@ -1441,6 +1441,24 @@
         e = raises(OSError, self.posix.symlink, 'bok', '/nonexistentdir/boz')
         assert str(e.value).endswith(": 'bok' -> '/nonexistentdir/boz'")
 
+    if hasattr(rposix, 'getxattr'):
+        def test_xattr_simple(self):
+            # Minimal testing here, lib-python has better tests.
+            os = self.posix
+            with open(self.path, 'wb'):
+                pass
+            init_names = os.listxattr(self.path)
+            raises(OSError, os.getxattr, self.path, 'user.test')
+            os.setxattr(self.path, 'user.test', b'', os.XATTR_CREATE, 
follow_symlinks=False)
+            assert os.getxattr(self.path, 'user.test') == b''
+            os.setxattr(self.path, 'user.test', b'foo', os.XATTR_REPLACE)
+            assert os.getxattr(self.path, 'user.test', follow_symlinks=False) 
== b'foo'
+            assert set(os.listxattr(self.path)) == set(
+                init_names + [b'user.test'])
+            os.removexattr(self.path, 'user.test', follow_symlinks=False)
+            raises(OSError, os.getxattr, self.path, 'user.test')
+            assert os.listxattr(self.path, follow_symlinks=False) == init_names
+
 
 class AppTestEnvironment(object):
     def setup_class(cls):
@@ -1495,6 +1513,7 @@
             res = os.system(cmd)
             assert res == 0
 
+
 @py.test.fixture
 def check_fsencoding(space, pytestconfig):
     if pytestconfig.getvalue('runappdirect'):
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,160 @@
         """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.INT,
+        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.INT,
+        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.INT,
+        compilation_info=CConfig._compilation_info_,
+        save_err=rffi.RFFI_SAVE_ERRNO)
+    c_fremovexattr = external('fremovexattr',
+        [rffi.INT, rffi.CCHARP], rffi.INT,
+        compilation_info=CConfig._compilation_info_,
+        save_err=rffi.RFFI_SAVE_ERRNO)
+    c_removexattr = external('removexattr',
+        [rffi.CCHARP, rffi.CCHARP], rffi.INT,
+        compilation_info=CConfig._compilation_info_,
+        save_err=rffi.RFFI_SAVE_ERRNO)
+    c_lremovexattr = external('lremovexattr',
+        [rffi.CCHARP, rffi.CCHARP], rffi.INT,
+        compilation_info=CConfig._compilation_info_,
+        save_err=rffi.RFFI_SAVE_ERRNO)
+    c_flistxattr = external('flistxattr',
+        [rffi.INT, rffi.CCHARP, rffi.SIZE_T], rffi.SSIZE_T,
+        compilation_info=CConfig._compilation_info_,
+        save_err=rffi.RFFI_SAVE_ERRNO)
+    c_listxattr = external('listxattr',
+        [rffi.CCHARP, rffi.CCHARP, rffi.SIZE_T], rffi.SSIZE_T,
+        compilation_info=CConfig._compilation_info_,
+        save_err=rffi.RFFI_SAVE_ERRNO)
+    c_llistxattr = external('llistxattr',
+        [rffi.CCHARP, rffi.CCHARP, rffi.SIZE_T], 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))
+
+    def _unpack_attrs(attr_string):
+        result = attr_string.split('\0')
+        del result[-1]
+        return result
+
+    def flistxattr(fd):
+        for size in buf_sizes:
+            with rffi.scoped_alloc_buffer(size) as buf:
+                res = c_flistxattr(fd, buf.raw, size)
+                if res < 0:
+                    err = get_saved_errno()
+                    if err != errno.ERANGE:
+                        raise OSError(err, 'flistxattr failed')
+                else:
+                    return _unpack_attrs(buf.str(res))
+        else:
+            raise OSError(errno.ERANGE, 'flistxattr failed')
+
+    def listxattr(path, follow_symlinks=True):
+        for size in buf_sizes:
+            with rffi.scoped_alloc_buffer(size) as buf:
+                if follow_symlinks:
+                    res = c_listxattr(path, buf.raw, size)
+                else:
+                    res = c_llistxattr(path, buf.raw, size)
+                if res < 0:
+                    err = get_saved_errno()
+                    if err != errno.ERANGE:
+                        c_name = 'listxattr' if follow_symlinks else 
'llistxattr'
+                        raise OSError(err, c_name + 'failed')
+                else:
+                    return _unpack_attrs(buf.str(res))
+        else:
+            c_name = 'listxattr' if follow_symlinks else 'llistxattr'
+            raise OSError(errno.ERANGE, c_name + 'failed')
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,61 @@
         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.setxattr(fname, 'user.foo', '')
+    except OSError:
+        return False
+    else:
+        return True
+
[email protected](not (hasattr(rposix, 'getxattr') and 
check_working_xattr()),
+    reason="Requires working rposix.getxattr()")
+@given(
+    name=st.text(
+        alphabet=st.characters(min_codepoint=1), min_size=1, 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):
+    assume(follow_symlinks or not use_fd)
+    name = 'user.' + name.encode('utf-8')
+    fname = str(udir.join('xattr_test.txt'))
+    try:
+        os.unlink(fname)
+    except OSError:
+        pass
+    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
+        all_names = rposix.flistxattr
+    else:
+        file_id = fname
+        if follow_symlinks:
+            read, write, delete = rposix.getxattr, rposix.setxattr, 
rposix.removexattr
+            all_names = rposix.listxattr
+        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)
+            all_names = lambda *args, **kwargs: rposix.listxattr(*args, 
follow_symlinks=False, **kwargs)
+    try:
+        init_names = all_names(file_id)
+        with pytest.raises(OSError):
+            read(file_id, name)
+        write(file_id, name, value)
+        assert read(file_id, name) == value
+        assert set(all_names(file_id)) == set(init_names + [name])
+        assert '' not in all_names(file_id)
+        delete(file_id, name)
+        with pytest.raises(OSError):
+            read(file_id, name)
+        assert set(all_names(file_id)) == set(init_names)
+    finally:
+        if use_fd:
+            os.close(file_id)
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to