[pypy-commit] pypy default: Attach the FILE to the Python file object from CFFI. This lets the FILE

arigo noreply at buildbot.pypy.org
Fri Oct 26 16:32:40 CEST 2012


Author: Armin Rigo <arigo at tunes.org>
Branch: 
Changeset: r58462:1f2e3adb4ec0
Date: 2012-10-26 15:35 +0200
http://bitbucket.org/pypy/pypy/changeset/1f2e3adb4ec0/

Log:	Attach the FILE to the Python file object from CFFI. This lets the
	FILE pointer stay valid for C code after the call to the function,
	and implement support for 'new("FILE **", f)'.

diff --git a/pypy/module/_cffi_backend/ctypefunc.py b/pypy/module/_cffi_backend/ctypefunc.py
--- a/pypy/module/_cffi_backend/ctypefunc.py
+++ b/pypy/module/_cffi_backend/ctypefunc.py
@@ -4,7 +4,6 @@
 
 import sys
 from pypy.interpreter.error import OperationError, operationerrfmt
-from pypy.interpreter.error import wrap_oserror
 from pypy.rpython.lltypesystem import lltype, llmemory, rffi
 from pypy.rlib import jit, clibffi, jit_libffi, rposix
 from pypy.rlib.jit_libffi import CIF_DESCRIPTION, CIF_DESCRIPTION_P
@@ -152,9 +151,6 @@
                     if flag == 1:
                         raw_string = rffi.cast(rffi.CCHARPP, data)[0]
                         lltype.free(raw_string, flavor='raw')
-                    elif flag == 2:
-                        file = rffi.cast(rffi.CCHARPP, data)[0]
-                        rffi_fclose(file)
             lltype.free(buffer, flavor='raw')
         return w_res
 
@@ -169,27 +165,6 @@
     assert isinstance(abi, int)
     return space.wrap(abi)
 
-rffi_fdopen = rffi.llexternal("fdopen", [rffi.INT, rffi.CCHARP], rffi.CCHARP)
-rffi_fclose = rffi.llexternal("fclose", [rffi.CCHARP], rffi.INT)
-
-def prepare_file_call_argument(fileobj):
-    import os
-    space = fileobj.space
-    fileobj.direct_flush()
-    fd = fileobj.direct_fileno()
-    if fd < 0:
-        raise OperationError(space.w_ValueError,
-                             space.wrap("file has no OS file descriptor"))
-    try:
-        fd2 = os.dup(fd)
-        f = rffi_fdopen(fd2, fileobj.mode)
-        if not f:
-            os.close(fd2)
-            raise OSError(rposix.get_errno(), "fdopen failed")
-    except OSError, e:
-        raise wrap_oserror(space, e)
-    return f
-
 # ____________________________________________________________
 
 
diff --git a/pypy/module/_cffi_backend/ctypeptr.py b/pypy/module/_cffi_backend/ctypeptr.py
--- a/pypy/module/_cffi_backend/ctypeptr.py
+++ b/pypy/module/_cffi_backend/ctypeptr.py
@@ -3,6 +3,7 @@
 """
 
 from pypy.interpreter.error import OperationError, operationerrfmt
+from pypy.interpreter.error import wrap_oserror
 from pypy.rpython.lltypesystem import lltype, rffi
 from pypy.rlib.objectmodel import keepalive_until_here
 from pypy.rlib.rarithmetic import ovfcheck
@@ -147,7 +148,15 @@
 
 class W_CTypePtrBase(W_CTypePtrOrArray):
     # base class for both pointers and pointers-to-functions
-    _attrs_ = []
+    _attrs_ = ['is_file']
+    _immutable_fields_ = ['is_file']
+
+    def __init__(self, space, size, extra, extra_position, ctitem,
+                 could_cast_anything=True, is_file=False):
+        W_CTypePtrOrArray.__init__(self, space, size,
+                                   extra, extra_position, ctitem,
+                                   could_cast_anything=could_cast_anything)
+        self.is_file = is_file
 
     def convert_to_object(self, cdata):
         ptrdata = rffi.cast(rffi.CCHARPP, cdata)[0]
@@ -157,6 +166,11 @@
         space = self.space
         ob = space.interpclass_w(w_ob)
         if not isinstance(ob, cdataobj.W_CData):
+            if self.is_file:
+                result = self.prepare_file(w_ob)
+                if result:
+                    rffi.cast(rffi.CCHARPP, cdata)[0] = result
+                    return
             raise self._convert_error("cdata pointer", w_ob)
         other = ob.ctype
         if not isinstance(other, W_CTypePtrBase):
@@ -171,14 +185,23 @@
 
         rffi.cast(rffi.CCHARPP, cdata)[0] = ob._cdata
 
+    def prepare_file(self, w_ob):
+        from pypy.module._file.interp_file import W_File
+        from pypy.module._cffi_backend import ctypefunc
+        ob = self.space.interpclass_w(w_ob)
+        if isinstance(ob, W_File):
+            return prepare_file_argument(self.space, ob)
+        else:
+            return lltype.nullptr(rffi.CCHARP.TO)
+
     def _alignof(self):
         from pypy.module._cffi_backend import newtype
         return newtype.alignment_of_pointer
 
 
 class W_CTypePointer(W_CTypePtrBase):
-    _attrs_ = ['is_file']
-    _immutable_fields_ = ['is_file']
+    _attrs_ = []
+    _immutable_fields_ = []
 
     def __init__(self, space, ctitem):
         from pypy.module._cffi_backend import ctypearray
@@ -187,8 +210,9 @@
             extra = "(*)"    # obscure case: see test_array_add
         else:
             extra = " *"
-        self.is_file = (ctitem.name == "struct _IO_FILE")
-        W_CTypePtrBase.__init__(self, space, size, extra, 2, ctitem)
+        is_file = (ctitem.name == "struct _IO_FILE")
+        W_CTypePtrBase.__init__(self, space, size, extra, 2, ctitem,
+                                is_file=is_file)
 
     def newp(self, w_init):
         space = self.space
@@ -245,11 +269,8 @@
             # from a string, we add the null terminator
             length = space.int_w(space.len(w_init)) + 1
         elif self.is_file:
-            from pypy.module._file.interp_file import W_File
-            from pypy.module._cffi_backend import ctypefunc
-            ob = space.interpclass_w(w_init)
-            if isinstance(ob, W_File):
-                result = ctypefunc.prepare_file_call_argument(ob)
+            result = self.prepare_file(w_init)
+            if result:
                 rffi.cast(rffi.CCHARPP, cdata)[0] = result
                 return 2
             return 0
@@ -303,3 +324,31 @@
         else:
             raise OperationError(space.w_TypeError,
                      space.wrap("expected a 'cdata struct-or-union' object"))
+
+# ____________________________________________________________
+
+
+rffi_fdopen = rffi.llexternal("fdopen", [rffi.INT, rffi.CCHARP], rffi.CCHARP)
+rffi_fclose = rffi.llexternal("fclose", [rffi.CCHARP], rffi.INT)
+
+class CffiFileObj(object):
+    _immutable_ = True
+    def __init__(self, fd, mode):
+        self.llf = rffi_fdopen(fd, mode)
+        if not self.llf:
+            raise OSError(rposix.get_errno(), "fdopen failed")
+    def close(self):
+        rffi_fclose(self.llf)
+
+def prepare_file_argument(space, fileobj):
+    fileobj.direct_flush()
+    if fileobj.cffi_fileobj is None:
+        fd = fileobj.direct_fileno()
+        if fd < 0:
+            raise OperationError(space.w_ValueError,
+                                 space.wrap("file has no OS file descriptor"))
+        try:
+            fileobj.cffi_fileobj = CffiFileObj(fd, fileobj.mode)
+        except OSError, e:
+            raise wrap_oserror(space, e)
+    return fileobj.cffi_fileobj.llf
diff --git a/pypy/module/_cffi_backend/test/_backend_test_c.py b/pypy/module/_cffi_backend/test/_backend_test_c.py
--- a/pypy/module/_cffi_backend/test/_backend_test_c.py
+++ b/pypy/module/_cffi_backend/test/_backend_test_c.py
@@ -2264,3 +2264,36 @@
     e = py.test.raises(TypeError, fputs, b"hello world\n", fw1)
     assert str(e.value) == ("initializer for ctype 'struct NOT_FILE *' must "
                             "be a cdata pointer, not file")
+
+def test_FILE_object():
+    if sys.platform == "win32":
+        py.test.skip("testing FILE not implemented")
+    #
+    BFILE = new_struct_type("_IO_FILE")
+    BFILEP = new_pointer_type(BFILE)
+    BFILEPP = new_pointer_type(BFILEP)
+    BChar = new_primitive_type("char")
+    BCharP = new_pointer_type(BChar)
+    BInt = new_primitive_type("int")
+    BFunc = new_function_type((BCharP, BFILEP), BInt, False)
+    BFunc2 = new_function_type((BFILEP,), BInt, False)
+    ll = find_and_load_library('c')
+    fputs = ll.load_function(BFunc, "fputs")
+    fileno = ll.load_function(BFunc2, "fileno")
+    #
+    import posix
+    fdr, fdw = posix.pipe()
+    fw1 = posix.fdopen(fdw, 'wb', 256)
+    #
+    fw1p = newp(BFILEPP, fw1)
+    fw1.write(b"X")
+    fw1.flush()
+    res = fputs(b"hello\n", fw1p[0])
+    assert res >= 0
+    res = fileno(fw1p[0])
+    assert res == fdw
+    fw1.close()
+    #
+    data = posix.read(fdr, 256)
+    assert data == b"Xhello\n"
+    posix.close(fdr)
diff --git a/pypy/module/_cffi_backend/test/test_ztranslation.py b/pypy/module/_cffi_backend/test/test_ztranslation.py
--- a/pypy/module/_cffi_backend/test/test_ztranslation.py
+++ b/pypy/module/_cffi_backend/test/test_ztranslation.py
@@ -1,8 +1,21 @@
 from pypy.objspace.fake.checkmodule import checkmodule
+from pypy.module._cffi_backend import ctypeptr
+from pypy.rpython.lltypesystem import lltype, rffi
 
 # side-effect: FORMAT_LONGDOUBLE must be built before test_checkmodule()
 from pypy.module._cffi_backend import misc
 
 
 def test_checkmodule():
-    checkmodule('_cffi_backend')
+    # prepare_file_argument() is not working without translating the _file
+    # module too
+    def dummy_prepare_file_argument(space, fileobj):
+        return lltype.nullptr(rffi.CCHARP.TO)
+    old = ctypeptr.prepare_file_argument
+    try:
+        ctypeptr.prepare_file_argument = dummy_prepare_file_argument
+        #
+        checkmodule('_cffi_backend')
+        #
+    finally:
+        ctypeptr.prepare_file_argument = old
diff --git a/pypy/module/_file/interp_file.py b/pypy/module/_file/interp_file.py
--- a/pypy/module/_file/interp_file.py
+++ b/pypy/module/_file/interp_file.py
@@ -32,6 +32,7 @@
     encoding = None
     errors   = None
     fd       = -1
+    cffi_fileobj = None    # pypy/module/_cffi_backend
 
     newlines = 0     # Updated when the stream is closed
 
@@ -148,7 +149,14 @@
                 del openstreams[stream]
             except KeyError:
                 pass
-            stream.close()
+            # close the stream.  If cffi_fileobj is None, we close the
+            # underlying fileno too.  Otherwise, we leave that to
+            # cffi_fileobj.close().
+            cffifo = self.cffi_fileobj
+            self.cffi_fileobj = None
+            stream.close1(cffifo is None)
+            if cffifo is not None:
+                cffifo.close()
 
     def direct_fileno(self):
         self.getstream()    # check if the file is still open
diff --git a/pypy/rlib/streamio.py b/pypy/rlib/streamio.py
--- a/pypy/rlib/streamio.py
+++ b/pypy/rlib/streamio.py
@@ -268,6 +268,9 @@
         return False
 
     def close(self):
+        self.close1(True)
+
+    def close1(self, closefileno):
         pass
 
     def peek(self):
@@ -333,8 +336,9 @@
             else:
                 data = data[n:]
 
-    def close(self):
-        os.close(self.fd)
+    def close1(self, closefileno):
+        if closefileno:
+            os.close(self.fd)
 
     if sys.platform == "win32":
         def truncate(self, size):
@@ -375,9 +379,10 @@
         size = os.fstat(self.fd).st_size
         self.mm = mmap.mmap(self.fd, size, access=self.access)
 
-    def close(self):
+    def close1(self, closefileno):
         self.mm.close()
-        os.close(self.fd)
+        if closefileno:
+            os.close(self.fd)
 
     def tell(self):
         return self.pos
@@ -470,7 +475,7 @@
     ("truncate", [r_longlong]),
     ("flush", []),
     ("flushable", []),
-    ("close", []),
+    ("close1", [int]),
     ("peek", []),
     ("try_to_find_file_descriptor", []),
     ("getnewlines", []),
@@ -715,7 +720,7 @@
     truncate   = PassThrough("truncate",  flush_buffers=True)
     flush      = PassThrough("flush",     flush_buffers=True)
     flushable  = PassThrough("flushable", flush_buffers=False)
-    close      = PassThrough("close",     flush_buffers=False)
+    close1     = PassThrough("close1",    flush_buffers=False)
     try_to_find_file_descriptor = PassThrough("try_to_find_file_descriptor",
                                               flush_buffers=False)
 
@@ -770,7 +775,7 @@
     seek       = PassThrough("seek",     flush_buffers=True)
     truncate   = PassThrough("truncate", flush_buffers=True)
     flush      = PassThrough("flush",    flush_buffers=True)
-    close      = PassThrough("close",    flush_buffers=True)
+    close1     = PassThrough("close1",   flush_buffers=True)
     try_to_find_file_descriptor = PassThrough("try_to_find_file_descriptor",
                                               flush_buffers=False)
 
@@ -838,7 +843,7 @@
 
     flush    = PassThrough("flush", flush_buffers=False)
     flushable= PassThrough("flushable", flush_buffers=False)
-    close    = PassThrough("close", flush_buffers=False)
+    close1   = PassThrough("close1", flush_buffers=False)
     try_to_find_file_descriptor = PassThrough("try_to_find_file_descriptor",
                                               flush_buffers=False)
 
@@ -907,7 +912,7 @@
     truncate = PassThrough("truncate", flush_buffers=True)
     flush    = PassThrough("flush", flush_buffers=False)
     flushable= PassThrough("flushable", flush_buffers=False)
-    close    = PassThrough("close", flush_buffers=False)
+    close1   = PassThrough("close1", flush_buffers=False)
     try_to_find_file_descriptor = PassThrough("try_to_find_file_descriptor",
                                               flush_buffers=False)
     
@@ -1041,7 +1046,7 @@
     truncate   = PassThrough("truncate",  flush_buffers=True)
     flush      = PassThrough("flush",     flush_buffers=True)
     flushable  = PassThrough("flushable", flush_buffers=False)
-    close      = PassThrough("close",     flush_buffers=False)
+    close1     = PassThrough("close1",    flush_buffers=False)
     try_to_find_file_descriptor = PassThrough("try_to_find_file_descriptor",
                                               flush_buffers=False)
 
@@ -1067,7 +1072,7 @@
     truncate   = PassThrough("truncate",  flush_buffers=False)
     flush      = PassThrough("flush",     flush_buffers=False)
     flushable  = PassThrough("flushable", flush_buffers=False)
-    close      = PassThrough("close",     flush_buffers=False)
+    close1     = PassThrough("close1",    flush_buffers=False)
     try_to_find_file_descriptor = PassThrough("try_to_find_file_descriptor",
                                               flush_buffers=False)
 
@@ -1091,7 +1096,7 @@
     peek       = PassThrough("peek",      flush_buffers=False)
     flush      = PassThrough("flush",     flush_buffers=False)
     flushable  = PassThrough("flushable", flush_buffers=False)
-    close      = PassThrough("close",     flush_buffers=False)
+    close1     = PassThrough("close1",    flush_buffers=False)
     write      = PassThrough("write",     flush_buffers=False)
     truncate   = PassThrough("truncate",  flush_buffers=False)
     getnewlines= PassThrough("getnewlines",flush_buffers=False)
@@ -1144,7 +1149,7 @@
     truncate   = PassThrough("truncate",  flush_buffers=False)
     flush      = PassThrough("flush",     flush_buffers=False)
     flushable  = PassThrough("flushable", flush_buffers=False)
-    close      = PassThrough("close",     flush_buffers=False)
+    close1     = PassThrough("close1",    flush_buffers=False)
     try_to_find_file_descriptor = PassThrough("try_to_find_file_descriptor",
                                               flush_buffers=False)
 
@@ -1172,6 +1177,6 @@
     truncate   = PassThrough("truncate",  flush_buffers=False)
     flush      = PassThrough("flush",     flush_buffers=False)
     flushable  = PassThrough("flushable", flush_buffers=False)
-    close      = PassThrough("close",     flush_buffers=False)
+    close1     = PassThrough("close1",    flush_buffers=False)
     try_to_find_file_descriptor = PassThrough("try_to_find_file_descriptor",
                                               flush_buffers=False)


More information about the pypy-commit mailing list