[pypy-commit] pypy py3.6-sandbox-2: hg merge sandbox-2

arigo pypy.commits at gmail.com
Tue Aug 27 06:03:39 EDT 2019


Author: Armin Rigo <arigo at tunes.org>
Branch: py3.6-sandbox-2
Changeset: r97297:dcecc1d7905b
Date: 2019-08-27 10:38 +0200
http://bitbucket.org/pypy/pypy/changeset/dcecc1d7905b/

Log:	hg merge sandbox-2

diff --git a/pypy/module/__pypy__/interp_time.py b/pypy/module/__pypy__/interp_time.py
--- a/pypy/module/__pypy__/interp_time.py
+++ b/pypy/module/__pypy__/interp_time.py
@@ -6,10 +6,12 @@
 from rpython.rtyper.lltypesystem import rffi, lltype
 from rpython.rlib import rtime
 from rpython.rlib.rtime import HAS_CLOCK_GETTIME
+from rpython.rlib.objectmodel import sandbox_review
 
 
 if HAS_CLOCK_GETTIME:
 
+    @sandbox_review(reviewed=True)
     @unwrap_spec(clk_id="c_int")
     def clock_gettime(space, clk_id):
         with lltype.scoped_alloc(rtime.TIMESPEC) as tp:
@@ -20,6 +22,7 @@
                  float(rffi.getintfield(tp, 'c_tv_nsec')) * 0.000000001)
         return space.newfloat(t)
 
+    @sandbox_review(reviewed=True)
     @unwrap_spec(clk_id="c_int")
     def clock_getres(space, clk_id):
         with lltype.scoped_alloc(rtime.TIMESPEC) as tp:
diff --git a/pypy/module/_io/interp_fileio.py b/pypy/module/_io/interp_fileio.py
--- a/pypy/module/_io/interp_fileio.py
+++ b/pypy/module/_io/interp_fileio.py
@@ -458,7 +458,7 @@
         length = rwbuffer.getlength()
 
         target_address = lltype.nullptr(rffi.CCHARP.TO)
-        if length > 64:
+        if length > 64 and not space.config.translation.sandbox:
             try:
                 target_address = rwbuffer.get_raw_address()
             except ValueError:
@@ -480,6 +480,13 @@
         else:
             # optimized case: reading more than 64 bytes into a rwbuffer
             # with a valid raw address
+
+            # XXX note that this is not fully safe, because we don't "lock"
+            # the buffer so we can't in theory pass its raw address to c_read().
+            # Another thread could cause it to be freed in parallel.
+            # Without proper buffer locking, it's not going to be fixed, though.
+            assert not space.config.translation.sandbox
+
             while True:
                 got = c_read(self.fd, target_address, length)
                 keepalive_until_here(rwbuffer)
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
@@ -2227,7 +2227,13 @@
     except OSError as e:
         # 'rurandom' should catch and retry internally if it gets EINTR
         # (at least in os.read(), which is probably enough in practice)
-        raise wrap_oserror(space, e, eintr_retry=False)
+        #
+        # CPython raises NotImplementedError if /dev/urandom cannot be found.
+        # To maximize compatibility, we should also raise NotImplementedError
+        # and not OSError (although CPython also raises OSError in case it
+        # could open /dev/urandom but there are further problems).
+        raise wrap_oserror(space, e, eintr_retry=False,
+            w_exception_class=space.w_NotImplementedError)
 
 def ctermid(space):
     """ctermid() -> string
diff --git a/rpython/rlib/jit.py b/rpython/rlib/jit.py
--- a/rpython/rlib/jit.py
+++ b/rpython/rlib/jit.py
@@ -3,7 +3,9 @@
 import py
 
 from rpython.rlib.nonconst import NonConstant
-from rpython.rlib.objectmodel import CDefinedIntSymbolic, keepalive_until_here, specialize, not_rpython, we_are_translated
+from rpython.rlib.objectmodel import CDefinedIntSymbolic, keepalive_until_here
+from rpython.rlib.objectmodel import specialize, not_rpython, we_are_translated
+from rpython.rlib.objectmodel import sandbox_review
 from rpython.rlib.unroll import unrolling_iterable
 from rpython.rtyper.extregistry import ExtRegistryEntry
 from rpython.tool.sourcetools import rpython_wrapper
@@ -1196,6 +1198,7 @@
 def _jit_conditional_call(condition, function, *args):
     pass           # special-cased below
 
+ at sandbox_review(reviewed=True)   # for the llop.jit_conditional_call
 @specialize.call_location()
 def conditional_call(condition, function, *args):
     """Does the same as:
@@ -1217,6 +1220,7 @@
 def _jit_conditional_call_value(value, function, *args):
     return value    # special-cased below
 
+ at sandbox_review(reviewed=True)   # for the llop.jit_conditional_call_value
 @specialize.call_location()
 def conditional_call_elidable(value, function, *args):
     """Does the same as:
diff --git a/rpython/rlib/rarithmetic.py b/rpython/rlib/rarithmetic.py
--- a/rpython/rlib/rarithmetic.py
+++ b/rpython/rlib/rarithmetic.py
@@ -878,9 +878,8 @@
     Raises ParseStringOverflowError in case the result does not fit.
     """
     from rpython.rlib.rstring import (
-        NumberStringParser, ParseStringOverflowError, strip_spaces)
-    s = literal = strip_spaces(s)
-    p = NumberStringParser(s, literal, base, 'int',
+        NumberStringParser, ParseStringOverflowError)
+    p = NumberStringParser(s, s, base, 'int',
                            allow_underscores=allow_underscores,
                            no_implicit_octal=no_implicit_octal)
     base = p.base
diff --git a/rpython/rlib/rbigint.py b/rpython/rlib/rbigint.py
--- a/rpython/rlib/rbigint.py
+++ b/rpython/rlib/rbigint.py
@@ -296,14 +296,17 @@
     def fromstr(s, base=0, allow_underscores=False):
         """As string_to_int(), but ignores an optional 'l' or 'L' suffix
         and returns an rbigint."""
+        from rpython.rlib.rstring import NumberStringParser
         from rpython.rlib.rstring import NumberStringParser, \
             strip_spaces
-        s = literal = strip_spaces(s)
+        s = literal = strip_spaces(s) # XXX could get rid of this slice
+        end = len(s)
         if (s.endswith('l') or s.endswith('L')) and base < 22:
             # in base 22 and above, 'L' is a valid digit!  try: long('L',22)
-            s = s[:-1]
+            end -= 1
         parser = NumberStringParser(s, literal, base, 'long',
-                                    allow_underscores=allow_underscores)
+                                    allow_underscores=allow_underscores,
+                                    end=end)
         return rbigint._from_numberstring_parser(parser)
 
     @staticmethod
diff --git a/rpython/rlib/rposix.py b/rpython/rlib/rposix.py
--- a/rpython/rlib/rposix.py
+++ b/rpython/rlib/rposix.py
@@ -395,12 +395,14 @@
                   save_err=rffi.RFFI_SAVE_ERRNO)
 c_open = external(UNDERSCORE_ON_WIN32 + 'open',
                   [rffi.CCHARP, rffi.INT, rffi.MODE_T], rffi.INT,
-                  save_err=rffi.RFFI_SAVE_ERRNO)
+                  save_err=rffi.RFFI_SAVE_ERRNO,
+                  sandboxsafe="nowrite")
 
 # Win32 Unicode functions
 c_wopen = external(UNDERSCORE_ON_WIN32 + 'wopen',
                    [rffi.CWCHARP, rffi.INT, rffi.MODE_T], rffi.INT,
-                   save_err=rffi.RFFI_SAVE_ERRNO)
+                   save_err=rffi.RFFI_SAVE_ERRNO,
+                   sandboxsafe="nowrite")
 
 #___________________________________________________________________
 # Wrappers around posix functions, that accept either strings, or
@@ -514,6 +516,7 @@
 c_close = external(UNDERSCORE_ON_WIN32 + 'close', [rffi.INT], rffi.INT,
                    releasegil=False, save_err=rffi.RFFI_SAVE_ERRNO)
 
+ at sandbox_review(reviewed=True)
 @replace_os_function('read')
 @signature(types.int(), types.int(), returns=types.any())
 def read(fd, count):
@@ -525,6 +528,7 @@
             got = handle_posix_error('read', c_read(fd, void_buf, count))
             return buf.str(got)
 
+ at sandbox_review(reviewed=True)
 @replace_os_function('write')
 @signature(types.int(), types.any(), returns=types.any())
 def write(fd, data):
@@ -649,13 +653,13 @@
 #___________________________________________________________________
 
 c_chdir = external('chdir', [rffi.CCHARP], rffi.INT,
-                   save_err=rffi.RFFI_SAVE_ERRNO)
+                   save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
 c_fchdir = external('fchdir', [rffi.INT], rffi.INT,
                     save_err=rffi.RFFI_SAVE_ERRNO)
 c_access = external(UNDERSCORE_ON_WIN32 + 'access',
-                    [rffi.CCHARP, rffi.INT], rffi.INT)
+                    [rffi.CCHARP, rffi.INT], rffi.INT, sandboxsafe="nowrite")
 c_waccess = external(UNDERSCORE_ON_WIN32 + 'waccess',
-                     [rffi.CWCHARP, rffi.INT], rffi.INT)
+                     [rffi.CWCHARP, rffi.INT], rffi.INT, sandboxsafe="nowrite")
 
 @replace_os_function('chdir')
 @specialize.argtype(0)
@@ -753,6 +757,7 @@
                      [rffi.CWCHARP, rffi.SIZE_T], rffi.CWCHARP,
                      save_err=rffi.RFFI_SAVE_ERRNO)
 
+ at sandbox_review(reviewed=True)
 @replace_os_function('getcwd')
 def getcwd():
     bufsize = 256
@@ -773,6 +778,7 @@
     lltype.free(buf, flavor='raw')
     return result
 
+ at sandbox_review(reviewed=True)
 @replace_os_function('getcwdu')
 def getcwdu():
     bufsize = 256
@@ -811,9 +817,11 @@
     DIRENT = dirent_config['DIRENT']
     DIRENTP = lltype.Ptr(DIRENT)
     c_opendir = external('opendir',
-        [rffi.CCHARP], DIRP, save_err=rffi.RFFI_SAVE_ERRNO)
+        [rffi.CCHARP], DIRP, save_err=rffi.RFFI_SAVE_ERRNO,
+        sandboxsafe="nowrite")
     c_fdopendir = external('fdopendir',
-        [rffi.INT], DIRP, save_err=rffi.RFFI_SAVE_ERRNO)
+        [rffi.INT], DIRP, save_err=rffi.RFFI_SAVE_ERRNO,
+        sandboxsafe="nowrite")
     c_rewinddir = external('rewinddir',
         [DIRP], lltype.Void, releasegil=False)
     # XXX macro=True is hack to make sure we get the correct kind of
@@ -828,6 +836,7 @@
 else:
     dirent_config = {}
 
+ at sandbox_review(reviewed=True)
 def _listdir(dirp, rewind=False):
     result = []
     while True:
@@ -847,6 +856,7 @@
     return result
 
 if not _WIN32:
+    @sandbox_review(reviewed=True)
     def fdlistdir(dirfd):
         """
         Like listdir(), except that the directory is specified as an open
@@ -921,17 +931,17 @@
 #___________________________________________________________________
 
 c_execv = external('execv', [rffi.CCHARP, rffi.CCHARPP], rffi.INT,
-                   save_err=rffi.RFFI_SAVE_ERRNO)
+                   save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
 c_execve = external('execve',
                     [rffi.CCHARP, rffi.CCHARPP, rffi.CCHARPP], rffi.INT,
-                    save_err=rffi.RFFI_SAVE_ERRNO)
+                    save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
 c_spawnv = external(UNDERSCORE_ON_WIN32 + 'spawnv',
                     [rffi.INT, rffi.CCHARP, rffi.CCHARPP], rffi.INT,
-                    save_err=rffi.RFFI_SAVE_ERRNO)
+                    save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
 c_spawnve = external(UNDERSCORE_ON_WIN32 + 'spawnve',
                     [rffi.INT, rffi.CCHARP, rffi.CCHARPP, rffi.CCHARPP],
                      rffi.INT,
-                     save_err=rffi.RFFI_SAVE_ERRNO)
+                     save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
 
 @replace_os_function('execv')
 def execv(path, args):
@@ -1006,6 +1016,7 @@
         debug.debug_forked(ofs)
     return childpid
 
+ at sandbox_review(reviewed=True)
 @replace_os_function('openpty')
 @jit.dont_look_inside
 def openpty():
@@ -1110,6 +1121,7 @@
 c_getloadavg = external('getloadavg',
                         [rffi.CArrayPtr(lltype.Float), rffi.INT], rffi.INT)
 
+ at sandbox_review(reviewed=True)
 @replace_os_function('getlogin')
 def getlogin():
     result = c_getlogin()
@@ -1117,6 +1129,7 @@
         raise OSError(get_saved_errno(), "getlogin failed")
     return rffi.charp2str(result)
 
+ at sandbox_review(reviewed=True)
 @replace_os_function('getloadavg')
 def getloadavg():
     load = lltype.malloc(rffi.CArrayPtr(lltype.Float).TO, 3, flavor='raw')
@@ -1134,6 +1147,7 @@
                       [rffi.CCHARP, rffi.CCHARP, rffi.SIZE_T], rffi.SSIZE_T,
                       save_err=rffi.RFFI_SAVE_ERRNO)
 
+ at sandbox_review(reviewed=True)
 @replace_os_function('readlink')
 def readlink(path):
     path = _as_bytes0(path)
@@ -1168,6 +1182,7 @@
                      releasegil=False,
                      save_err=rffi.RFFI_SAVE_ERRNO)
 
+ at sandbox_review(reviewed=True)
 @replace_os_function('ttyname')
 def ttyname(fd):
     l_name = c_ttyname(fd)
@@ -1178,6 +1193,7 @@
 c_strerror = external('strerror', [rffi.INT], rffi.CCHARP,
                       releasegil=False, sandboxsafe=True)
 
+ at sandbox_review(reviewed=True)
 @replace_os_function('strerror')
 def strerror(errnum):
     res = c_strerror(errnum)
@@ -1185,20 +1201,20 @@
         raise ValueError("os_strerror failed")
     return rffi.charp2str(res)
 
-c_system = external('system', [rffi.CCHARP], rffi.INT)
+c_system = external('system', [rffi.CCHARP], rffi.INT, sandboxsafe="nowrite")
 
 @replace_os_function('system')
 def system(command):
     return widen(c_system(command))
 
 c_unlink = external('unlink', [rffi.CCHARP], rffi.INT,
-                    save_err=rffi.RFFI_SAVE_ERRNO)
+                    save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
 c_mkdir = external('mkdir', [rffi.CCHARP, rffi.MODE_T], rffi.INT,
-                   save_err=rffi.RFFI_SAVE_ERRNO)
+                   save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
 c_rmdir = external(UNDERSCORE_ON_WIN32 + 'rmdir', [rffi.CCHARP], rffi.INT,
-                   save_err=rffi.RFFI_SAVE_ERRNO)
+                   save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
 c_wrmdir = external(UNDERSCORE_ON_WIN32 + 'wrmdir', [rffi.CWCHARP], rffi.INT,
-                    save_err=rffi.RFFI_SAVE_ERRNO)
+                    save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
 
 @replace_os_function('unlink')
 @specialize.argtype(0)
@@ -1232,11 +1248,11 @@
         handle_posix_error('rmdir', c_rmdir(_as_bytes0(path)))
 
 c_chmod = external('chmod', [rffi.CCHARP, rffi.MODE_T], rffi.INT,
-                   save_err=rffi.RFFI_SAVE_ERRNO)
+                   save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
 c_fchmod = external('fchmod', [rffi.INT, rffi.MODE_T], rffi.INT,
                     save_err=rffi.RFFI_SAVE_ERRNO,)
 c_rename = external('rename', [rffi.CCHARP, rffi.CCHARP], rffi.INT,
-                    save_err=rffi.RFFI_SAVE_ERRNO)
+                    save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
 
 @replace_os_function('chmod')
 @specialize.argtype(0)
@@ -1293,10 +1309,11 @@
 #___________________________________________________________________
 
 c_mkfifo = external('mkfifo', [rffi.CCHARP, rffi.MODE_T], rffi.INT,
-                    save_err=rffi.RFFI_SAVE_ERRNO)
+                    save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
 c_mknod = external('mknod', [rffi.CCHARP, rffi.MODE_T, rffi.INT], rffi.INT,
 #                                           # xxx: actually ^^^ dev_t
-                   macro=_MACRO_ON_POSIX, save_err=rffi.RFFI_SAVE_ERRNO)
+                   macro=_MACRO_ON_POSIX, save_err=rffi.RFFI_SAVE_ERRNO,
+                   sandboxsafe="nowrite")
 
 @replace_os_function('mkfifo')
 @specialize.argtype(0)
@@ -1337,6 +1354,7 @@
         c_pipe2 = external('pipe2', [INT_ARRAY_P, rffi.INT], rffi.INT,
                           save_err=rffi.RFFI_SAVE_ERRNO)
 
+ at sandbox_review(reviewed=True)
 @replace_os_function('pipe')
 def pipe(flags=0):
     # 'flags' might be ignored.  Check the result.
@@ -1373,6 +1391,7 @@
         finally:
             lltype.free(filedes, flavor='raw')
 
+ at sandbox_review(reviewed=True)
 def pipe2(flags):
     # Only available if there is really a c_pipe2 function.
     # No fallback to pipe() if we get ENOSYS.
@@ -1385,9 +1404,9 @@
         lltype.free(filedes, flavor='raw')
 
 c_link = external('link', [rffi.CCHARP, rffi.CCHARP], rffi.INT,
-                  save_err=rffi.RFFI_SAVE_ERRNO,)
+                  save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
 c_symlink = external('symlink', [rffi.CCHARP, rffi.CCHARP], rffi.INT,
-                     save_err=rffi.RFFI_SAVE_ERRNO)
+                     save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
 
 #___________________________________________________________________
 
@@ -1420,9 +1439,9 @@
     return widen(c_umask(newmask))
 
 c_chown = external('chown', [rffi.CCHARP, rffi.INT, rffi.INT], rffi.INT,
-                   save_err=rffi.RFFI_SAVE_ERRNO)
+                   save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
 c_lchown = external('lchown', [rffi.CCHARP, rffi.INT, rffi.INT], rffi.INT,
-                    save_err=rffi.RFFI_SAVE_ERRNO)
+                    save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
 c_fchown = external('fchown', [rffi.INT, rffi.INT, rffi.INT], rffi.INT,
                     save_err=rffi.RFFI_SAVE_ERRNO)
 
@@ -1581,6 +1600,7 @@
             lltype.Ptr(rwin32.FILETIME), lltype.Ptr(rwin32.FILETIME)],
         rwin32.BOOL, calling_conv='win')
 
+ at sandbox_review(reviewed=True)
 @replace_os_function('times')
 def times():
     if not _WIN32:
@@ -1680,12 +1700,14 @@
 
 c_ctermid = external('ctermid', [rffi.CCHARP], rffi.CCHARP)
 
+ at sandbox_review(reviewed=True)
 @replace_os_function('ctermid')
 def ctermid():
     return rffi.charp2str(c_ctermid(lltype.nullptr(rffi.CCHARP.TO)))
 
 c_tmpnam = external('tmpnam', [rffi.CCHARP], rffi.CCHARP)
 
+ at sandbox_review(reviewed=True)
 @replace_os_function('tmpnam')
 def tmpnam():
     return rffi.charp2str(c_tmpnam(lltype.nullptr(rffi.CCHARP.TO)))
@@ -1737,8 +1759,10 @@
     c_setgroups = external('setgroups', [rffi.SIZE_T, GID_GROUPS_T], rffi.INT,
                            save_err=rffi.RFFI_SAVE_ERRNO)
     c_initgroups = external('initgroups', [rffi.CCHARP, GID_T], rffi.INT,
-                            save_err=rffi.RFFI_SAVE_ERRNO)
+                            save_err=rffi.RFFI_SAVE_ERRNO,
+                            sandboxsafe="nowrite")
 
+ at sandbox_review(reviewed=True)
 @replace_os_function('getgroups')
 def getgroups():
     n = handle_posix_error('getgroups',
@@ -1886,6 +1910,7 @@
     c_setresgid = external('setresgid', [GID_T] * 3, rffi.INT,
                            save_err=rffi.RFFI_SAVE_ERRNO)
 
+    @sandbox_review(reviewed=True)
     @replace_os_function('getresuid')
     def getresuid():
         out = lltype.malloc(UID_T_P.TO, 3, flavor='raw')
@@ -1898,6 +1923,7 @@
         finally:
             lltype.free(out, flavor='raw')
 
+    @sandbox_review(reviewed=True)
     @replace_os_function('getresgid')
     def getresgid():
         out = lltype.malloc(GID_T_P.TO, 3, flavor='raw')
@@ -1956,6 +1982,7 @@
 c_chroot = external('chroot', [rffi.CCHARP], rffi.INT,
                     save_err=rffi.RFFI_SAVE_ERRNO,
                     macro=_MACRO_ON_POSIX,
+                    sandboxsafe="nowrite",
                     compilation_info=ExternalCompilationInfo(includes=['unistd.h']))
 
 @replace_os_function('chroot')
@@ -1981,6 +2008,7 @@
                        compilation_info=CConfig._compilation_info_,
                        save_err=rffi.RFFI_SAVE_ERRNO)
 
+ at sandbox_review(reviewed=True)
 @replace_os_function('uname')
 def uname():
     l_utsbuf = lltype.malloc(UTSNAMEP.TO, flavor='raw')
@@ -2024,7 +2052,8 @@
 c_fpathconf = external('fpathconf', [rffi.INT, rffi.INT], rffi.LONG,
                        save_err=rffi.RFFI_FULL_ERRNO_ZERO)
 c_pathconf = external('pathconf', [rffi.CCHARP, rffi.INT], rffi.LONG,
-                      save_err=rffi.RFFI_FULL_ERRNO_ZERO)
+                      save_err=rffi.RFFI_FULL_ERRNO_ZERO,
+                      sandboxsafe="nowrite")
 c_confstr = external('confstr',
                      [rffi.INT, rffi.CCHARP, rffi.SIZE_T], rffi.SIZE_T,
                       save_err=rffi.RFFI_FULL_ERRNO_ZERO)
@@ -2056,6 +2085,7 @@
             raise OSError(errno, "pathconf failed")
     return res
 
+ at sandbox_review(reviewed=True)
 @replace_os_function('confstr')
 def confstr(value):
     n = intmask(c_confstr(value, lltype.nullptr(rffi.CCHARP.TO), 0))
@@ -2129,7 +2159,8 @@
 
 if HAVE_FACCESSAT:
     c_faccessat = external('faccessat',
-        [rffi.INT, rffi.CCHARP, rffi.INT, rffi.INT], rffi.INT)
+        [rffi.INT, rffi.CCHARP, rffi.INT, rffi.INT], rffi.INT,
+        sandboxsafe="nowrite")
 
     def faccessat(pathname, mode, dir_fd=AT_FDCWD,
             effective_ids=False, follow_symlinks=True):
@@ -2147,7 +2178,7 @@
 if HAVE_FCHMODAT:
     c_fchmodat = external('fchmodat',
         [rffi.INT, rffi.CCHARP, rffi.INT, rffi.INT], rffi.INT,
-        save_err=rffi.RFFI_SAVE_ERRNO,)
+        save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
 
     def fchmodat(path, mode, dir_fd=AT_FDCWD, follow_symlinks=True):
         if follow_symlinks:
@@ -2160,7 +2191,7 @@
 if HAVE_FCHOWNAT:
     c_fchownat = external('fchownat',
         [rffi.INT, rffi.CCHARP, rffi.INT, rffi.INT, rffi.INT], rffi.INT,
-        save_err=rffi.RFFI_SAVE_ERRNO,)
+        save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
 
     def fchownat(path, owner, group, dir_fd=AT_FDCWD,
             follow_symlinks=True, empty_path=False):
@@ -2175,7 +2206,7 @@
 if HAVE_FEXECVE:
     c_fexecve = external('fexecve',
         [rffi.INT, rffi.CCHARPP, rffi.CCHARPP], rffi.INT,
-        save_err=rffi.RFFI_SAVE_ERRNO)
+        save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
 
     def fexecve(fd, args, env):
         envstrs = []
@@ -2196,7 +2227,7 @@
     c_linkat = external(
         'linkat',
         [rffi.INT, rffi.CCHARP, rffi.INT, rffi.CCHARP, rffi.INT], rffi.INT,
-        save_err=rffi.RFFI_SAVE_ERRNO)
+        save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
 
     def linkat(src, dst, src_dir_fd=AT_FDCWD, dst_dir_fd=AT_FDCWD,
             follow_symlinks=True):
@@ -2290,7 +2321,7 @@
 if HAVE_MKDIRAT:
     c_mkdirat = external('mkdirat',
         [rffi.INT, rffi.CCHARP, rffi.INT], rffi.INT,
-        save_err=rffi.RFFI_SAVE_ERRNO)
+        save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
 
     def mkdirat(pathname, mode, dir_fd=AT_FDCWD):
         error = c_mkdirat(dir_fd, pathname, mode)
@@ -2299,7 +2330,7 @@
 if HAVE_UNLINKAT:
     c_unlinkat = external('unlinkat',
         [rffi.INT, rffi.CCHARP, rffi.INT], rffi.INT,
-        save_err=rffi.RFFI_SAVE_ERRNO)
+        save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
 
     def unlinkat(pathname, dir_fd=AT_FDCWD, removedir=False):
         flag = AT_REMOVEDIR if removedir else 0
@@ -2337,7 +2368,7 @@
     c_renameat = external(
         'renameat',
         [rffi.INT, rffi.CCHARP, rffi.INT, rffi.CCHARP], rffi.INT,
-        save_err=rffi.RFFI_SAVE_ERRNO)
+        save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
 
     def renameat(src, dst, src_dir_fd=AT_FDCWD, dst_dir_fd=AT_FDCWD):
         error = c_renameat(src_dir_fd, src, dst_dir_fd, dst)
@@ -2347,7 +2378,7 @@
 if HAVE_SYMLINKAT:
     c_symlinkat = external('symlinkat',
         [rffi.CCHARP, rffi.INT, rffi.CCHARP], rffi.INT,
-        save_err=rffi.RFFI_SAVE_ERRNO)
+        save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
 
     def symlinkat(src, dst, dir_fd=AT_FDCWD):
         error = c_symlinkat(src, dir_fd, dst)
@@ -2356,7 +2387,7 @@
 if HAVE_OPENAT:
     c_openat = external('openat',
         [rffi.INT, rffi.CCHARP, rffi.INT, rffi.MODE_T], rffi.INT,
-        save_err=rffi.RFFI_SAVE_ERRNO)
+        save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
 
     @enforceargs(s_Str0, int, int, int, typecheck=False)
     def openat(path, flags, mode, dir_fd=AT_FDCWD):
@@ -2366,7 +2397,7 @@
 if HAVE_MKFIFOAT:
     c_mkfifoat = external('mkfifoat',
         [rffi.INT, rffi.CCHARP, rffi.MODE_T], rffi.INT,
-        save_err=rffi.RFFI_SAVE_ERRNO)
+        save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
 
     def mkfifoat(path, mode, dir_fd=AT_FDCWD):
         error = c_mkfifoat(dir_fd, path, mode)
@@ -2375,7 +2406,7 @@
 if HAVE_MKNODAT:
     c_mknodat = external('mknodat',
         [rffi.INT, rffi.CCHARP, rffi.MODE_T, rffi.INT], rffi.INT,
-        save_err=rffi.RFFI_SAVE_ERRNO)
+        save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
 
     def mknodat(path, mode, device, dir_fd=AT_FDCWD):
         error = c_mknodat(dir_fd, path, mode, device)
@@ -2687,29 +2718,29 @@
         [rffi.INT, rffi.CCHARP, rffi.CCHARP, rffi.SIZE_T, rffi.INT],
         rffi.INT,
         compilation_info=CConfig._compilation_info_,
-        save_err=rffi.RFFI_SAVE_ERRNO)
+        save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
     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)
+        save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
     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)
+        save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
     c_fremovexattr = external('fremovexattr',
         [rffi.INT, rffi.CCHARP], rffi.INT,
         compilation_info=CConfig._compilation_info_,
-        save_err=rffi.RFFI_SAVE_ERRNO)
+        save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
     c_removexattr = external('removexattr',
         [rffi.CCHARP, rffi.CCHARP], rffi.INT,
         compilation_info=CConfig._compilation_info_,
-        save_err=rffi.RFFI_SAVE_ERRNO)
+        save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
     c_lremovexattr = external('lremovexattr',
         [rffi.CCHARP, rffi.CCHARP], rffi.INT,
         compilation_info=CConfig._compilation_info_,
-        save_err=rffi.RFFI_SAVE_ERRNO)
+        save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
     c_flistxattr = external('flistxattr',
         [rffi.INT, rffi.CCHARP, rffi.SIZE_T], rffi.SSIZE_T,
         compilation_info=CConfig._compilation_info_,
@@ -2724,6 +2755,7 @@
         save_err=rffi.RFFI_SAVE_ERRNO)
     buf_sizes = [256, XATTR_SIZE_MAX]
 
+    @sandbox_review(reviewed=True)
     def fgetxattr(fd, name):
         for size in buf_sizes:
             with rffi.scoped_alloc_buffer(size) as buf:
@@ -2738,6 +2770,7 @@
         else:
             raise OSError(errno.ERANGE, 'fgetxattr failed')
 
+    @sandbox_review(reviewed=True)
     def getxattr(path, name, follow_symlinks=True):
         for size in buf_sizes:
             with rffi.scoped_alloc_buffer(size) as buf:
@@ -2783,6 +2816,7 @@
         del result[-1]
         return result
 
+    @sandbox_review(reviewed=True)
     def flistxattr(fd):
         for size in buf_sizes:
             with rffi.scoped_alloc_buffer(size) as buf:
@@ -2796,6 +2830,7 @@
         else:
             raise OSError(errno.ERANGE, 'flistxattr failed')
 
+    @sandbox_review(reviewed=True)
     def listxattr(path, follow_symlinks=True):
         for size in buf_sizes:
             with rffi.scoped_alloc_buffer(size) as buf:
diff --git a/rpython/rlib/rposix_environ.py b/rpython/rlib/rposix_environ.py
--- a/rpython/rlib/rposix_environ.py
+++ b/rpython/rlib/rposix_environ.py
@@ -2,7 +2,7 @@
 import sys
 from rpython.annotator import model as annmodel
 from rpython.rlib._os_support import _WIN32, StringTraits, UnicodeTraits
-from rpython.rlib.objectmodel import enforceargs
+from rpython.rlib.objectmodel import enforceargs, sandbox_review
 # importing rposix here creates a cycle on Windows
 from rpython.rtyper.controllerentry import Controller
 from rpython.rtyper.lltypesystem import rffi, lltype
@@ -148,6 +148,7 @@
         byname, eq = envkeepalive.bywname, u'='
         from rpython.rlib.rwin32 import lastSavedWindowsError as last_error
 
+    @sandbox_review(reviewed=True)
     def envitems_llimpl():
         environ = get_environ()
         result = []
@@ -162,11 +163,13 @@
             i += 1
         return result
 
+    @sandbox_review(reviewed=True)
     def getenv_llimpl(name):
         with traits.scoped_str2charp(name) as l_name:
             l_result = getenv(l_name)
             return traits.charp2str(l_result) if l_result else None
 
+    @sandbox_review(reviewed=True)
     def putenv_llimpl(name, value):
         l_string = traits.str2charp(name + eq + value)
         error = rffi.cast(lltype.Signed, putenv(l_string))
@@ -196,6 +199,7 @@
     os_unsetenv = llexternal('unsetenv', [rffi.CCHARP], rffi.INT,
                                   save_err=rffi.RFFI_SAVE_ERRNO)
 
+    @sandbox_review(reviewed=True)
     def r_unsetenv(name):
         with rffi.scoped_str2charp(name) as l_name:
             error = rffi.cast(lltype.Signed, os_unsetenv(l_name))
diff --git a/rpython/rlib/rposix_stat.py b/rpython/rlib/rposix_stat.py
--- a/rpython/rlib/rposix_stat.py
+++ b/rpython/rlib/rposix_stat.py
@@ -18,6 +18,7 @@
 
 from rpython.rlib._os_support import _preferred_traits, string_traits
 from rpython.rlib.objectmodel import specialize, we_are_translated, not_rpython
+from rpython.rlib.objectmodel import sandbox_review
 from rpython.rtyper.lltypesystem import lltype, rffi
 from rpython.translator.tool.cbuild import ExternalCompilationInfo
 from rpython.rlib.rarithmetic import intmask
@@ -534,6 +535,7 @@
                               compilation_info=compilation_info,
                               save_err=rffi.RFFI_SAVE_ERRNO)
 
+ at sandbox_review(reviewed=True)
 @replace_os_function('fstat')
 def fstat(fd):
     if not _WIN32:
@@ -574,6 +576,7 @@
         finally:
             lltype.free(info, flavor='raw')
 
+ at sandbox_review(reviewed=True)
 @replace_os_function('stat')
 @specialize.argtype(0)
 def stat(path):
@@ -587,6 +590,7 @@
         path = traits.as_str0(path)
         return win32_xstat(traits, path, traverse=True)
 
+ at sandbox_review(reviewed=True)
 @replace_os_function('lstat')
 @specialize.argtype(0)
 def lstat(path):
@@ -639,12 +643,14 @@
             handle_posix_error('fstatat', error)
             return build_stat_result(stresult)
 
+ at sandbox_review(reviewed=True)
 @replace_os_function('fstatvfs')
 def fstatvfs(fd):
     with lltype.scoped_alloc(STATVFS_STRUCT.TO) as stresult:
         handle_posix_error('fstatvfs', c_fstatvfs(fd, stresult))
         return build_statvfs_result(stresult)
 
+ at sandbox_review(reviewed=True)
 @replace_os_function('statvfs')
 @specialize.argtype(0)
 def statvfs(path):
diff --git a/rpython/rlib/rstring.py b/rpython/rlib/rstring.py
--- a/rpython/rlib/rstring.py
+++ b/rpython/rlib/rstring.py
@@ -500,26 +500,34 @@
                                (self.fname, self.original_base))
 
     def __init__(self, s, literal, base, fname, allow_underscores=False,
-                 no_implicit_octal=False):
+                 no_implicit_octal=False, start=0, end=-1):
         self.fname = fname
         sign = 1
-        if s.startswith('-'):
+        self.s = s
+        self.start = start
+        if end == -1:
+            end = len(s)
+        self.end = end
+        self._strip_spaces()
+        if self._startswith('-'):
             sign = -1
-            s = strip_spaces(s[1:])
-        elif s.startswith('+'):
-            s = strip_spaces(s[1:])
+            self.start += 1
+            self._strip_spaces()
+        elif self._startswith('+'):
+            self.start += 1
+            self._strip_spaces()
         self.sign = sign
         self.original_base = base
         self.allow_underscores = allow_underscores
 
         if base == 0:
-            if s.startswith('0x') or s.startswith('0X'):
+            if self._startswith('0x') or self._startswith('0X'):
                 base = 16
-            elif s.startswith('0b') or s.startswith('0B'):
+            elif self._startswith('0b') or self._startswith('0B'):
                 base = 2
-            elif s.startswith('0'): # also covers the '0o' case
-                if no_implicit_octal and not (s.startswith('0o') or
-                                              s.startswith('0O')):
+            elif self._startswith('0'): # also covers the '0o' case
+                if no_implicit_octal and not (self._startswith('0o') or
+                                              self._startswith('0O')):
                     base = 1    # this makes only the digit '0' valid...
                 else:
                     base = 8
@@ -530,30 +538,44 @@
         self.base = base
 
         # Leading underscores are not allowed
-        if s.startswith('_'):
+        if self._startswith('_'):
             self.error()
 
-        if base == 16 and (s.startswith('0x') or s.startswith('0X')):
-            s = s[2:]
-        if base == 8 and (s.startswith('0o') or s.startswith('0O')):
-            s = s[2:]
-        if base == 2 and (s.startswith('0b') or s.startswith('0B')):
-            s = s[2:]
-        if not s:
+        if base == 16 and (self._startswith('0x') or self._startswith('0X')):
+            self.start += 2
+        if base == 8 and (self._startswith('0o') or self._startswith('0O')):
+            self.start += 2
+        if base == 2 and (self._startswith('0b') or self._startswith('0B')):
+            self.start += 2
+        if self.start == self.end:
             self.error()
-        self.s = s
-        self.n = len(s)
-        self.i = 0
+        self.i = self.start
+
+    def _startswith(self, prefix):
+        return startswith(self.s, prefix, start=self.start, end=self.end)
+
+    def _strip_spaces(self):
+        # XXX this is not locale-dependent
+        p = self.start
+        q = self.end
+        s = self.s
+        while p < q and s[p] in ' \f\n\r\t\v':
+            p += 1
+        while p < q and s[q-1] in ' \f\n\r\t\v':
+            q -= 1
+        assert q >= p
+        self.start = p
+        self.end = q
 
     def rewind(self):
         self.i = 0
 
     def next_digit(self): # -1 => exhausted
-        if self.i < self.n:
+        if self.i < self.end:
             c = self.s[self.i]
             if self.allow_underscores and c == '_':
                 self.i += 1
-                if self.i >= self.n:
+                if self.i >= self.end:
                     self.error()
                 c = self.s[self.i]
             digit = ord(c)
@@ -576,7 +598,7 @@
         # After exhausting all n digits in next_digit(), you can walk them
         # again in reverse order by calling prev_digit() exactly n times
         i = self.i - 1
-        assert i >= 0
+        assert i >= self.start
         self.i = i
         c = self.s[i]
         if self.allow_underscores and c == '_':
diff --git a/rpython/rlib/rtime.py b/rpython/rlib/rtime.py
--- a/rpython/rlib/rtime.py
+++ b/rpython/rlib/rtime.py
@@ -236,6 +236,7 @@
         diff = a[0] - state.counter_start
     return float(diff) / state.divisor
 
+ at sandbox_review(reviewed=True)
 @replace_time_function('clock')
 def clock():
     if _WIN32:
diff --git a/rpython/rlib/test/test_rarithmetic.py b/rpython/rlib/test/test_rarithmetic.py
--- a/rpython/rlib/test/test_rarithmetic.py
+++ b/rpython/rlib/test/test_rarithmetic.py
@@ -337,6 +337,10 @@
         res = self.interpret(f, [123])
         assert res == 4 + 2
 
+    def test_string_to_int_translates(self):
+        def f(s):
+            return string_to_int(str(s))
+        self.interpret(f, [123]) == 123
 
 def test_int_real_union():
     from rpython.rtyper.lltypesystem.rffi import r_int_real
diff --git a/rpython/rtyper/lltypesystem/rffi.py b/rpython/rtyper/lltypesystem/rffi.py
--- a/rpython/rtyper/lltypesystem/rffi.py
+++ b/rpython/rtyper/lltypesystem/rffi.py
@@ -102,7 +102,13 @@
                  is sandboxed.  If False, it will turn into a stdin/stdout
                  communication with the parent process.  If "check_caller",
                  it is like True but we call @sandbox_review(check_caller=True)
-                 which means that we need to also check the callers.
+                 which means that we need to also check the callers.  If
+                 "nowrite", we don't need to check the callers.  The default
+                 of False either implies "check_caller" or "nowrite"
+                 depending on whether the function takes and returns pointer
+                 arguments or not.  Use "nowrite" only if the external
+                 function call will only *read* from 'char *' or other data
+                 structures passed in.
 
     calling_conv: if 'unknown' or 'win', the C function is not directly seen
                   by the JIT.  If 'c', it can be seen (depending on
@@ -344,13 +350,19 @@
         wrapper = sandbox_review(check_caller=True)(wrapper)
     elif sandboxsafe == 'abort':
         wrapper = sandbox_review(abort=True)(wrapper)
+    elif sandboxsafe == 'nowrite':
+        wrapper = sandbox_review(reviewed=True)(wrapper)
     else:
         assert isinstance(sandboxsafe, bool)
-        wrapper = sandbox_review(reviewed=True)(wrapper)
+        if sandboxsafe or (all(_sandbox_type_safe(ARG) for ARG in args) and
+                           _sandbox_type_safe(result)):
+            wrapper = sandbox_review(reviewed=True)(wrapper)
+        else:
+            wrapper = sandbox_review(check_caller=True)(wrapper)
     return wrapper
 
-def sandbox_check_type(TYPE):
-    return not isinstance(TYPE, lltype.Primitive) or TYPE == llmemory.Address
+def _sandbox_type_safe(TYPE):
+    return isinstance(TYPE, lltype.Primitive) and TYPE != llmemory.Address
 
 
 class CallbackHolder:
diff --git a/rpython/translator/backendopt/all.py b/rpython/translator/backendopt/all.py
--- a/rpython/translator/backendopt/all.py
+++ b/rpython/translator/backendopt/all.py
@@ -113,7 +113,7 @@
     if config.profile_based_inline and not secondary:
         threshold = config.profile_based_inline_threshold
         heuristic = get_function(config.profile_based_inline_heuristic)
-        inline.instrument_inline_candidates(translator, graphs, threshold)
+        inline.instrument_inline_candidates(graphs, threshold)
         counters = translator.driver_instrument_result(
             config.profile_based_inline)
         n = len(counters)
diff --git a/rpython/translator/backendopt/inline.py b/rpython/translator/backendopt/inline.py
--- a/rpython/translator/backendopt/inline.py
+++ b/rpython/translator/backendopt/inline.py
@@ -548,8 +548,7 @@
     return (0.9999 * measure_median_execution_cost(graph) +
             count), True       # may be NaN
 
-def inlinable_static_callers(translator, graphs, store_calls=False,
-                             ok_to_call=None):
+def inlinable_static_callers(graphs, store_calls=False, ok_to_call=None):
     if ok_to_call is None:
         ok_to_call = set(graphs)
     result = []
@@ -559,7 +558,6 @@
         else:
             result.append((parentgraph, graph))
     #
-    dont_inline = make_dont_inline_checker(translator)
     for parentgraph in graphs:
         for block in parentgraph.iterblocks():
             for op in block.operations:
@@ -567,12 +565,13 @@
                     funcobj = op.args[0].value._obj
                     graph = getattr(funcobj, 'graph', None)
                     if graph is not None and graph in ok_to_call:
-                        if dont_inline(funcobj):
+                        if getattr(getattr(funcobj, '_callable', None),
+                                   '_dont_inline_', False):
                             continue
                         add(parentgraph, block, op, graph)
     return result
 
-def instrument_inline_candidates(translator, graphs, threshold):
+def instrument_inline_candidates(graphs, threshold):
     cache = {None: False}
     def candidate(graph):
         try:
@@ -582,7 +581,6 @@
             cache[graph] = res
             return res
     n = 0
-    dont_inline = make_dont_inline_checker(translator)
     for parentgraph in graphs:
         for block in parentgraph.iterblocks():
             ops = block.operations
@@ -594,7 +592,8 @@
                     funcobj = op.args[0].value._obj
                     graph = getattr(funcobj, 'graph', None)
                     if graph is not None:
-                        if dont_inline(funcobj):
+                        if getattr(getattr(funcobj, '_callable', None),
+                                   '_dont_inline_', False):
                             continue
                     if candidate(graph):
                         tag = Constant('inline', Void)
@@ -611,17 +610,6 @@
     return (hasattr(graph, 'func') and
             getattr(graph.func, '_always_inline_', None))
 
-def make_dont_inline_checker(translator):
-    sandbox = translator.config.translation.sandbox
-
-    def dont_inline(funcobj):
-        func = getattr(funcobj, '_callable', None)
-        if sandbox:
-            if hasattr(func, '_sandbox_review_'):
-                return True
-        return getattr(func, '_dont_inline_', False)
-    return dont_inline
-
 def auto_inlining(translator, threshold=None,
                   callgraph=None,
                   call_count_pred=None,
@@ -633,7 +621,7 @@
     callers = {}     # {graph: {graphs-that-call-it}}
     callees = {}     # {graph: {graphs-that-it-calls}}
     if callgraph is None:
-        callgraph = inlinable_static_callers(translator, translator.graphs)
+        callgraph = inlinable_static_callers(translator.graphs)
     for graph1, graph2 in callgraph:
         callers.setdefault(graph2, {})[graph1] = True
         callees.setdefault(graph1, {})[graph2] = True
@@ -739,8 +727,7 @@
                                 if not hasattr(graph, 'exceptiontransformed')])
     else:
         ok_to_call = None
-    callgraph = inlinable_static_callers(translator, graphs,
-                                         ok_to_call=ok_to_call)
+    callgraph = inlinable_static_callers(graphs, ok_to_call=ok_to_call)
     count = auto_inlining(translator, threshold, callgraph=callgraph,
                           heuristic=heuristic,
                           call_count_pred=call_count_pred)
diff --git a/rpython/translator/backendopt/test/test_inline.py b/rpython/translator/backendopt/test/test_inline.py
--- a/rpython/translator/backendopt/test/test_inline.py
+++ b/rpython/translator/backendopt/test/test_inline.py
@@ -100,7 +100,7 @@
         call_count_pred = None
         if call_count_check:
             call_count_pred = lambda lbl: True
-            instrument_inline_candidates(t, t.graphs, threshold)
+            instrument_inline_candidates(t.graphs, threshold)
 
         if remove_same_as:
             for graph in t.graphs:
diff --git a/rpython/translator/driver.py b/rpython/translator/driver.py
--- a/rpython/translator/driver.py
+++ b/rpython/translator/driver.py
@@ -344,6 +344,12 @@
         rtyper = self.translator.buildrtyper()
         rtyper.specialize(dont_simplify_again=True)
 
+        # we do the sandbox review checking here, before inlining graphs
+        # inside each other (and later generating extra graphs for the GC).
+        if self.config.translation.sandbox:
+            from rpython.translator.sandbox import graphchecker
+            graphchecker.check_all_graphs(self.translator)
+
     @taskdef([RTYPE], "JIT compiler generation")
     def task_pyjitpl_lltype(self):
         """ Generate bytecodes for JIT and flow the JIT helper functions
@@ -412,10 +418,6 @@
         if translator.annotator is not None:
             translator.frozen = True
 
-        if self.config.translation.sandbox:
-            from rpython.translator.sandbox import graphchecker
-            graphchecker.check_all_graphs(self.translator)
-
         standalone = self.standalone
         get_gchooks = self.extra.get('get_gchooks', lambda: None)
         gchooks = get_gchooks()
diff --git a/rpython/translator/sandbox/graphchecker.py b/rpython/translator/sandbox/graphchecker.py
--- a/rpython/translator/sandbox/graphchecker.py
+++ b/rpython/translator/sandbox/graphchecker.py
@@ -2,8 +2,92 @@
 This runs at the start of the database-c step, so it excludes the
 graphs produced later, notably for the GC.  These are "low-level"
 graphs that are assumed to be safe.
+
+Here are again the rules around this check.
+
+- any graph that contains only "safe" lloperations is itself "safe".
+  The "safe" lloperations are the ones marked "tryfold" in
+  rtyper.lltypesystem.lloperation, plus the ones listed explicitly below,
+  plus a few variants of specific operations coded in graph_in_unsafe().
+
+- any graph decorated with @objectmodel.sandbox_review() is "safe".
+  The different flags we can pass to @sandbox_review() are explained next,
+  but the decorated graph is itself always "safe".
+
+- "unsafe" operations are all special rare operations, plus most importantly
+  all *writes* into raw memory.  We assume that *reads* from anywhere are
+  OK to ignore: any information that reaches the sandboxed process can be
+  detected and used by anything that runs inside this process (i.e. there
+  is no really "secret" data inside the sandboxed subprocess itself).
+  At worst, random reads will lead to segfaults.  But random writes are not
+  safe because that could corrupt memory---e.g. overwrite some GC object
+  header, or even (although I'm not sure how) actually cause the sandboxed
+  process to misbehave in more important ways like doing actual system calls
+  that are supposed to be forbidden.
+
+- the decorator @sandbox_review(check_caller=True) means that the graph is
+  safe, but any call to this graph from somewhere else is an unsafe operation.
+  This forces all callers to also be reviewed and marked with some form of
+  @sandbox_review().
+
+- @sandbox_review(reviewed=True) means that the graph is safe and all
+  calls to this graph are also safe.  This should only be used on functions
+  that do internally "unsafe" stuff like writing to raw memory but don't
+  take arguments that could lead them to do bogus things.  A typical counter-
+  example is a function that takes a raw pointer and that writes something to
+  it; this should *not* be marked with reviewed=True.  On the other hand, many
+  RPython wrappers to external C functions can be reviewed=True because
+  they translate GC-safe information (say an RPython string) to raw memory,
+  do the call, and translate the result back to GC-safe information.
+
+- @sandbox_review(abort=True) is reserved for cases where calling this
+  function at runtime should just immediately abort the subprocess.
+
+Note that all flags above should be considered independently of what the
+actual C function calls are supposed to do.  For example, the RPython
+wrapper rposix.system() is something you definitely don't want to allow as-is,
+but the wrapper and the call to the C function are fine.  It's up to the
+controlling process to refuse to reply to the system() external call
+(either by having it return ENOSYS or a similar error, or by killing the
+sandboxed process completely).
+
+Like system(), all calls to external C functions are *by default* removed and
+turned into I/O on stdin/stdout, asking the parent controlling process what
+to do.  This is controlled in more details by rffi.llexternal().  It takes
+its own argument "sandboxsafe", which can be one of the following values:
+
+- sandboxsafe=False (the default): the external C call is not done but turned
+  into I/O on stdin/stdout.  Moreover, *if* the function takes or returns a
+  raw pointer, then it is flagged with @sandbox_review(check_caller=True) to
+  ensure that all callers do something sane with these raw pointers.  If
+  the C function only takes and returns integer or float arguments, there is
+  no real need, so in this case we flag @sandbox_review(reviewed=True) instead.
+
+- sandboxsafe=True: means the external call should be done straight from the
+  sandboxed process.  Reserved for specific functions like rposix.c_strerror(),
+  or some memory-manipulation functions used by the GC itself.
+
+- sandboxsafe="abort": like @sandbox_review(abort=True).
+
+- sandboxsafe="check_caller": forces @sandbox_review(check_caller=True).
+  Useful for llexternal() functions that appear to return an integer but
+  that's really some address that must be carefully managed.
+
+- sandboxsafe="nowrite": forces @sandbox_review(reviewed=True).  This is OK
+  for C functions that have pointer arguments but none of them can point
+  to anything that will be written to (hence the name).  The idea is that
+  for the common case of a function that takes a "const char *" argument,
+  we should just mark that function as reviewed=True, because it is safe:
+  the controller process will at most read things from the sandboxed process,
+  namely what the pointer points to, but it should not attempt to do any
+  write into the sandboxed process' memory.  Typically the caller itself
+  calls rffi.str2charp() and rffi.free_charp() around the call, but these
+  are also @sandbox_review(reviewed=True) helpers, so such a caller doesn't
+  need to be explicitly reviewed.
+
 """
 
+
 from rpython.flowspace.model import SpaceOperation, Constant
 from rpython.rtyper.rmodel import inputconst
 from rpython.rtyper.lltypesystem import lltype, llmemory, rstr
@@ -105,7 +189,7 @@
             elif opname in ('cast_ptr_to_adr', 'force_cast',
                             'cast_int_to_ptr'):
                 if is_gc_ptr(op.result.concretetype):
-                    return "result is a GC ptr: %r" % (opname,)
+                    return "result is a GC ptr: %r" % (op,)
 
             else:
                 return "unsupported llop: %r" % (opname,)
diff --git a/rpython/translator/sandbox/sandlib.py b/rpython/translator/sandbox/sandlib.py
deleted file mode 100644
--- a/rpython/translator/sandbox/sandlib.py
+++ /dev/null
@@ -1,517 +0,0 @@
-"""
-A Python library to execute and communicate with a subprocess that
-was translated from RPython code with --sandbox.  This library is
-for the outer process, which can run CPython or PyPy.
-"""
-
-import sys, os, posixpath, errno, stat, time
-import subprocess
-from rpython.tool.killsubprocess import killsubprocess
-from rpython.translator.sandbox.vfs import UID, GID
-import py
-
-WIN32 = os.name == "nt"
-
-
-def create_log():
-    """Make and return a log for the sandbox to use, if needed."""
-    from rpython.tool.ansi_print import AnsiLogger
-    return AnsiLogger("sandlib")
-
-def write_exception(g, exception, tb=None):
-    for i, excclass in EXCEPTION_TABLE:
-        if isinstance(exception, excclass):
-            write_message(g, i)
-            if excclass is OSError:
-                error = exception.errno
-                if error is None:
-                    error = errno.EPERM
-                write_message(g, error)
-            g.flush()
-            break
-    else:
-        # just re-raise the exception
-        raise exception.__class__, exception, tb
-
-def shortrepr(x):
-    r = repr(x)
-    if len(r) >= 80:
-        r = r[:20] + '...' + r[-8:]
-    return r
-
-def signal_name(n):
-    import signal
-    for key, value in signal.__dict__.items():
-        if key.startswith('SIG') and not key.startswith('SIG_') and value == n:
-            return key
-    return 'signal %d' % (n,)
-
-
-class SandboxedProc(object):
-    """Base class to control a sandboxed subprocess.
-    Inherit from this class and implement all the do_xxx() methods
-    for the external functions xxx that you want to support.
-    """
-    debug = False
-    log = None
-    os_level_sandboxing = False   # Linux only: /proc/PID/seccomp
-
-    def __init__(self, args, executable=None):
-        """'args' should a sequence of argument for the subprocess,
-        starting with the full path of the executable.
-        """
-        self.popen = subprocess.Popen(args, executable=executable,
-                                      bufsize=-1,
-                                      stdin=subprocess.PIPE,
-                                      stdout=subprocess.PIPE,
-                                      close_fds=False if WIN32 else True,
-                                      env={})
-        self.popenlock = None
-        self.currenttimeout = None
-        self.currentlyidlefrom = None
-
-        if self.debug:
-            self.log = create_log()
-
-    def withlock(self, function, *args, **kwds):
-        lock = self.popenlock
-        if lock is not None:
-            lock.acquire()
-        try:
-            return function(*args, **kwds)
-        finally:
-            if lock is not None:
-                lock.release()
-
-    def settimeout(self, timeout, interrupt_main=False):
-        """Start a timeout that will kill the subprocess after the given
-        amount of time.  Only one timeout can be active at a time.
-        """
-        import thread
-
-        def _waiting_thread():
-            while True:
-                while self.currentlyidlefrom is not None:
-                    time.sleep(1)   # can't timeout while idle
-                t = self.currenttimeout
-                if t is None:
-                    return  # cancelled
-                delay = t - time.time()
-                if delay <= 0.0:
-                    break   # expired!
-                time.sleep(min(delay*1.001, 1))
-            if self.log:
-                self.log.timeout("timeout!")
-            self.kill()
-            #if interrupt_main:
-            #    if hasattr(os, 'kill'):
-            #        import signal
-            #        os.kill(os.getpid(), signal.SIGINT)
-            #    else:
-            #        thread.interrupt_main()
-
-        def _settimeout():
-            need_new_thread = self.currenttimeout is None
-            self.currenttimeout = time.time() + timeout
-            if need_new_thread:
-                thread.start_new_thread(_waiting_thread, ())
-
-        if self.popenlock is None:
-            self.popenlock = thread.allocate_lock()
-        self.withlock(_settimeout)
-
-    def canceltimeout(self):
-        """Cancel the current timeout."""
-        self.currenttimeout = None
-        self.currentlyidlefrom = None
-
-    def enter_idle(self):
-        self.currentlyidlefrom = time.time()
-
-    def leave_idle(self):
-        def _postpone_timeout():
-            t = self.currentlyidlefrom
-            if t is not None and self.currenttimeout is not None:
-                self.currenttimeout += time.time() - t
-        try:
-            self.withlock(_postpone_timeout)
-        finally:
-            self.currentlyidlefrom = None
-
-    def poll(self):
-        returncode = self.withlock(self.popen.poll)
-        if returncode is not None:
-            self.canceltimeout()
-        return returncode
-
-    def wait(self):
-        returncode = self.withlock(self.popen.wait)
-        if returncode is not None:
-            self.canceltimeout()
-        return returncode
-
-    def kill(self):
-        self.withlock(killsubprocess, self.popen)
-
-    def handle_forever(self):
-        returncode = self.handle_until_return()
-        if returncode != 0:
-            raise OSError("the sandboxed subprocess exited with code %d" % (
-                returncode,))
-
-    def handle_until_return(self):
-        child_stdin  = self.popen.stdin
-        child_stdout = self.popen.stdout
-        if self.os_level_sandboxing and sys.platform.startswith('linux'):
-            # rationale: we wait until the child process started completely,
-            # letting the C library do any system calls it wants for
-            # initialization.  When the RPython code starts up, it quickly
-            # does its first system call.  At this point we turn seccomp on.
-            import select
-            select.select([child_stdout], [], [])
-            f = open('/proc/%d/seccomp' % self.popen.pid, 'w')
-            print >> f, 1
-            f.close()
-        while True:
-            try:
-                fnname = read_message(child_stdout)
-                args   = read_message(child_stdout)
-            except EOFError as e:
-                break
-            if self.log and not self.is_spam(fnname, *args):
-                self.log.call('%s(%s)' % (fnname,
-                                     ', '.join([shortrepr(x) for x in args])))
-            try:
-                answer, resulttype = self.handle_message(fnname, *args)
-            except Exception as e:
-                tb = sys.exc_info()[2]
-                write_exception(child_stdin, e, tb)
-                if self.log:
-                    if str(e):
-                        self.log.exception('%s: %s' % (e.__class__.__name__, e))
-                    else:
-                        self.log.exception('%s' % (e.__class__.__name__,))
-            else:
-                if self.log and not self.is_spam(fnname, *args):
-                    self.log.result(shortrepr(answer))
-                try:
-                    write_message(child_stdin, 0)  # error code - 0 for ok
-                    write_message(child_stdin, answer, resulttype)
-                    child_stdin.flush()
-                except (IOError, OSError):
-                    # likely cause: subprocess is dead, child_stdin closed
-                    if self.poll() is not None:
-                        break
-                    else:
-                        raise
-        returncode = self.wait()
-        return returncode
-
-    def is_spam(self, fnname, *args):
-        # To hide the spamming amounts of reads and writes to stdin and stdout
-        # in interactive sessions
-        return (fnname in ('ll_os.ll_os_read', 'll_os.ll_os_write') and
-                args[0] in (0, 1, 2))
-
-    def handle_message(self, fnname, *args):
-        if '__' in fnname:
-            raise ValueError("unsafe fnname")
-        try:
-            handler = getattr(self, 'do_' + fnname.replace('.', '__'))
-        except AttributeError:
-            raise RuntimeError("no handler for this function")
-        resulttype = getattr(handler, 'resulttype', None)
-        return handler(*args), resulttype
-
-
-class SimpleIOSandboxedProc(SandboxedProc):
-    """Control a sandboxed subprocess which is only allowed to read from
-    its stdin and write to its stdout and stderr.
-    """
-    _input = None
-    _output = None
-    _error = None
-    inputlogfile = None
-
-    def communicate(self, input=None):
-        """Send data to stdin. Read data from stdout and stderr,
-        until end-of-file is reached. Wait for process to terminate.
-        """
-        import cStringIO
-        if input:
-            if isinstance(input, str):
-                input = cStringIO.StringIO(input)
-            self._input = input
-        self._output = cStringIO.StringIO()
-        self._error = cStringIO.StringIO()
-        self.handle_forever()
-        output = self._output.getvalue()
-        self._output = None
-        error = self._error.getvalue()
-        self._error = None
-        return (output, error)
-
-    def interact(self, stdin=None, stdout=None, stderr=None):
-        """Interact with the subprocess.  By default, stdin, stdout and
-        stderr are set to the ones from 'sys'."""
-        import sys
-        self._input  = stdin  or sys.stdin
-        self._output = stdout or sys.stdout
-        self._error  = stderr or sys.stderr
-        returncode = self.handle_until_return()
-        if returncode != 0:
-            if os.name == 'posix' and returncode < 0:
-                print >> self._error, "[Subprocess killed by %s]" % (
-                    signal_name(-returncode),)
-            else:
-                print >> self._error, "[Subprocess exit code: %d]" % (
-                    returncode,)
-        self._input = None
-        self._output = None
-        self._error = None
-        return returncode
-
-    def setlogfile(self, filename):
-        self.inputlogfile = open(filename, 'a')
-
-    def do_ll_os__ll_os_read(self, fd, size):
-        if fd == 0:
-            if self._input is None:
-                return ""
-            elif (getattr(self, 'virtual_console_isatty', False) or
-                  self._input.isatty()):
-                # don't wait for all 'size' chars if reading from a tty,
-                # to avoid blocking.  Instead, stop after reading a line.
-
-                # For now, waiting at the interactive console is the
-                # only time that counts as idle.
-                self.enter_idle()
-                try:
-                    inputdata = self._input.readline(size)
-                finally:
-                    self.leave_idle()
-            else:
-                inputdata = self._input.read(size)
-            if self.inputlogfile is not None:
-                self.inputlogfile.write(inputdata)
-            return inputdata
-        raise OSError("trying to read from fd %d" % (fd,))
-
-    def do_ll_os__ll_os_write(self, fd, data):
-        if fd == 1:
-            self._output.write(data)
-            return len(data)
-        if fd == 2:
-            self._error.write(data)
-            return len(data)
-        raise OSError("trying to write to fd %d" % (fd,))
-
-    # let's allow access to the real time
-    def do_ll_time__ll_time_sleep(self, seconds):
-        # regularly check for timeouts that could have killed the
-        # subprocess
-        while seconds > 5.0:
-            time.sleep(5.0)
-            seconds -= 5.0
-            if self.poll() is not None:   # subprocess finished?
-                return
-        time.sleep(seconds)
-
-    def do_ll_time__ll_time_time(self):
-        return time.time()
-
-    def do_ll_time__ll_time_clock(self):
-        # measuring the CPU time of the controller process has
-        # not much meaning, so let's emulate this and return
-        # the real time elapsed since the first call to clock()
-        # (this is one of the behaviors allowed by the docs)
-        try:
-            starttime = self.starttime
-        except AttributeError:
-            starttime = self.starttime = time.time()
-        return time.time() - starttime
-
-class VirtualizedSandboxedProc(SandboxedProc):
-    """Control a virtualized sandboxed process, which is given a custom
-    view on the filesystem and a custom environment.
-    """
-    virtual_env = {}
-    virtual_cwd = '/tmp'
-    virtual_console_isatty = False
-    virtual_fd_range = range(3, 50)
-
-    def __init__(self, *args, **kwds):
-        super(VirtualizedSandboxedProc, self).__init__(*args, **kwds)
-        self.virtual_root = self.build_virtual_root()
-        self.open_fds = {}   # {virtual_fd: (real_file_object, node)}
-
-    def build_virtual_root(self):
-        raise NotImplementedError("must be overridden")
-
-    def do_ll_os__ll_os_envitems(self):
-        return self.virtual_env.items()
-
-    def do_ll_os__ll_os_getenv(self, name):
-        return self.virtual_env.get(name)
-
-    def translate_path(self, vpath):
-        # XXX this assumes posix vpaths for now, but os-specific real paths
-        vpath = posixpath.normpath(posixpath.join(self.virtual_cwd, vpath))
-        dirnode = self.virtual_root
-        components = [component for component in vpath.split('/')]
-        for component in components[:-1]:
-            if component:
-                dirnode = dirnode.join(component)
-                if dirnode.kind != stat.S_IFDIR:
-                    raise OSError(errno.ENOTDIR, component)
-        return dirnode, components[-1]
-
-    def get_node(self, vpath):
-        dirnode, name = self.translate_path(vpath)
-        if name:
-            node = dirnode.join(name)
-        else:
-            node = dirnode
-        if self.log:
-            self.log.vpath('%r => %r' % (vpath, node))
-        return node
-
-    def do_ll_os__ll_os_stat(self, vpathname):
-        node = self.get_node(vpathname)
-        return node.stat()
-    do_ll_os__ll_os_stat.resulttype = RESULTTYPE_STATRESULT
-
-    do_ll_os__ll_os_lstat = do_ll_os__ll_os_stat
-
-    def do_ll_os__ll_os_access(self, vpathname, mode):
-        try:
-            node = self.get_node(vpathname)
-        except OSError as e:
-            if e.errno == errno.ENOENT:
-                return False
-            raise
-        return node.access(mode)
-
-    def do_ll_os__ll_os_isatty(self, fd):
-        return self.virtual_console_isatty and fd in (0, 1, 2)
-
-    def allocate_fd(self, f, node=None):
-        for fd in self.virtual_fd_range:
-            if fd not in self.open_fds:
-                self.open_fds[fd] = (f, node)
-                return fd
-        else:
-            raise OSError(errno.EMFILE, "trying to open too many files")
-
-    def get_fd(self, fd, throw=True):
-        """Get the objects implementing file descriptor `fd`.
-
-        Returns a pair, (open file, vfs node)
-
-        `throw`: if true, raise OSError for bad fd, else return (None, None).
-        """
-        try:
-            f, node = self.open_fds[fd]
-        except KeyError:
-            if throw:
-                raise OSError(errno.EBADF, "bad file descriptor")
-            return None, None
-        return f, node
-
-    def get_file(self, fd, throw=True):
-        """Return the open file for file descriptor `fd`."""
-        return self.get_fd(fd, throw)[0]
-
-    def do_ll_os__ll_os_open(self, vpathname, flags, mode):
-        node = self.get_node(vpathname)
-        if flags & (os.O_RDONLY|os.O_WRONLY|os.O_RDWR) != os.O_RDONLY:
-            raise OSError(errno.EPERM, "write access denied")
-        # all other flags are ignored
-        f = node.open()
-        return self.allocate_fd(f, node)
-
-    def do_ll_os__ll_os_close(self, fd):
-        f = self.get_file(fd)
-        del self.open_fds[fd]
-        f.close()
-
-    def do_ll_os__ll_os_read(self, fd, size):
-        f = self.get_file(fd, throw=False)
-        if f is None:
-            return super(VirtualizedSandboxedProc, self).do_ll_os__ll_os_read(
-                fd, size)
-        else:
-            if not (0 <= size <= sys.maxint):
-                raise OSError(errno.EINVAL, "invalid read size")
-            # don't try to read more than 256KB at once here
-            return f.read(min(size, 256*1024))
-
-    def do_ll_os__ll_os_fstat(self, fd):
-        f, node = self.get_fd(fd)
-        return node.stat()
-    do_ll_os__ll_os_fstat.resulttype = RESULTTYPE_STATRESULT
-
-    def do_ll_os__ll_os_lseek(self, fd, pos, how):
-        f = self.get_file(fd)
-        f.seek(pos, how)
-        return f.tell()
-    do_ll_os__ll_os_lseek.resulttype = RESULTTYPE_LONGLONG
-
-    def do_ll_os__ll_os_getcwd(self):
-        return self.virtual_cwd
-
-    def do_ll_os__ll_os_strerror(self, errnum):
-        # unsure if this shouldn't be considered safeboxsafe
-        return os.strerror(errnum) or ('Unknown error %d' % (errnum,))
-
-    def do_ll_os__ll_os_listdir(self, vpathname):
-        node = self.get_node(vpathname)
-        return node.keys()
-
-    def do_ll_os__ll_os_unlink(self, vpathname):
-        raise OSError(errno.EPERM, "write access denied")
-
-    def do_ll_os__ll_os_mkdir(self, vpathname, mode=None):
-        raise OSError(errno.EPERM, "write access denied")
-
-    def do_ll_os__ll_os_getuid(self):
-        return UID
-    do_ll_os__ll_os_geteuid = do_ll_os__ll_os_getuid
-
-    def do_ll_os__ll_os_getgid(self):
-        return GID
-    do_ll_os__ll_os_getegid = do_ll_os__ll_os_getgid
-
-
-class VirtualizedSocketProc(VirtualizedSandboxedProc):
-    """ Extends VirtualizedSandboxProc with socket
-    options, ie tcp://host:port as args to os.open
-    """
-    def __init__(self, *args, **kwds):
-        super(VirtualizedSocketProc, self).__init__(*args, **kwds)
-        self.sockets = {}
-
-    def do_ll_os__ll_os_open(self, name, flags, mode):
-        if not name.startswith("tcp://"):
-            return super(VirtualizedSocketProc, self).do_ll_os__ll_os_open(
-                name, flags, mode)
-        import socket
-        host, port = name[6:].split(":")
-        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-        sock.connect((host, int(port)))
-        fd = self.allocate_fd(sock)
-        self.sockets[fd] = True
-        return fd
-
-    def do_ll_os__ll_os_read(self, fd, size):
-        if fd in self.sockets:
-            return self.get_file(fd).recv(size)
-        return super(VirtualizedSocketProc, self).do_ll_os__ll_os_read(
-            fd, size)
-
-    def do_ll_os__ll_os_write(self, fd, data):
-        if fd in self.sockets:
-            return self.get_file(fd).send(data)
-        return super(VirtualizedSocketProc, self).do_ll_os__ll_os_write(
-            fd, data)
-
diff --git a/rpython/translator/sandbox/test/test_graphchecker.py b/rpython/translator/sandbox/test/test_graphchecker.py
--- a/rpython/translator/sandbox/test/test_graphchecker.py
+++ b/rpython/translator/sandbox/test/test_graphchecker.py
@@ -52,7 +52,11 @@
             return llop.force_cast(lltype.Signed, x)
         self.check_safe(f, [float])
         self.check_safe(f, [lltype.Ptr(SRAW)])
-        self.check_unsafe("argument is a GC ptr", f, [lltype.Ptr(SGC)])
+        self.check_safe(f, [lltype.Ptr(SGC)])
+        #
+        def g(x):
+            return llop.force_cast(lltype.Ptr(SGC), x)
+        self.check_unsafe("result is a GC ptr", g, [int])
 
     def test_direct_call_to_check_caller(self):
         @sandbox_review(check_caller=True)
diff --git a/rpython/translator/sandbox/vfs.py b/rpython/translator/sandbox/vfs.py
deleted file mode 100644
--- a/rpython/translator/sandbox/vfs.py
+++ /dev/null
@@ -1,137 +0,0 @@
-import os
-import stat, errno
-
-UID = 1000
-GID = 1000
-ATIME = MTIME = CTIME = 0
-INO_COUNTER = 0
-
-
-class FSObject(object):
-    read_only = True
-
-    def stat(self):
-        try:
-            st_ino = self._st_ino
-        except AttributeError:
-            global INO_COUNTER
-            INO_COUNTER += 1
-            st_ino = self._st_ino = INO_COUNTER
-        st_dev = 1
-        st_nlink = 1
-        st_size = self.getsize()
-        st_mode = self.kind
-        st_mode |= stat.S_IWUSR | stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH
-        if stat.S_ISDIR(self.kind):
-            st_mode |= stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
-        if self.read_only:
-            st_uid = 0       # read-only files are virtually owned by root
-            st_gid = 0
-        else:
-            st_uid = UID     # read-write files are owned by this virtual user
-            st_gid = GID
-        st_atime = ATIME
-        st_mtime = MTIME
-        st_ctime = CTIME
-        return os.stat_result(
-            (st_mode, st_ino, st_dev, st_nlink, st_uid, st_gid,
-             st_size, st_atime, st_mtime, st_ctime))
-
-    def access(self, mode):
-        s = self.stat()
-        e_mode = s.st_mode & stat.S_IRWXO
-        if UID == s.st_uid:
-            e_mode |= (s.st_mode & stat.S_IRWXU) >> 6
-        if GID == s.st_gid:
-            e_mode |= (s.st_mode & stat.S_IRWXG) >> 3
-        return (e_mode & mode) == mode
-
-    def keys(self):
-        raise OSError(errno.ENOTDIR, self)
-
-    def open(self):
-        raise OSError(errno.EACCES, self)
-
-    def getsize(self):
-        return 0
-
-
-class Dir(FSObject):
-    kind = stat.S_IFDIR
-    def __init__(self, entries={}):
-        self.entries = entries
-    def keys(self):
-        return self.entries.keys()
-    def join(self, name):
-        try:
-            return self.entries[name]
-        except KeyError:
-            raise OSError(errno.ENOENT, name)
-
-class RealDir(Dir):
-    # If show_dotfiles=False, we pretend that all files whose name starts
-    # with '.' simply don't exist.  If follow_links=True, then symlinks are
-    # transparently followed (they look like a regular file or directory to
-    # the sandboxed process).  If follow_links=False, the subprocess is
-    # not allowed to access them at all.  Finally, exclude is a list of
-    # file endings that we filter out (note that we also filter out files
-    # with the same ending but a different case, to be safe).
-    def __init__(self, path, show_dotfiles=False, follow_links=False,
-                 exclude=[]):
-        self.path = path
-        self.show_dotfiles = show_dotfiles
-        self.follow_links  = follow_links
-        self.exclude       = [excl.lower() for excl in exclude]
-    def __repr__(self):
-        return '<RealDir %s>' % (self.path,)
-    def keys(self):
-        names = os.listdir(self.path)
-        if not self.show_dotfiles:
-            names = [name for name in names if not name.startswith('.')]
-        for excl in self.exclude:
-            names = [name for name in names if not name.lower().endswith(excl)]
-        return names
-    def join(self, name):
-        if name.startswith('.') and not self.show_dotfiles:
-            raise OSError(errno.ENOENT, name)
-        for excl in self.exclude:
-            if name.lower().endswith(excl):
-                raise OSError(errno.ENOENT, name)
-        path = os.path.join(self.path, name)
-        if self.follow_links:
-            st = os.stat(path)
-        else:
-            st = os.lstat(path)
-        if stat.S_ISDIR(st.st_mode):
-            return RealDir(path, show_dotfiles = self.show_dotfiles,
-                                 follow_links  = self.follow_links,
-                                 exclude       = self.exclude)
-        elif stat.S_ISREG(st.st_mode):
-            return RealFile(path)
-        else:
-            # don't allow access to symlinks and other special files
-            raise OSError(errno.EACCES, path)
-
-class File(FSObject):
-    kind = stat.S_IFREG
-    def __init__(self, data=''):
-        self.data = data
-    def getsize(self):
-        return len(self.data)
-    def open(self):
-        import cStringIO
-        return cStringIO.StringIO(self.data)
-
-class RealFile(File):
-    def __init__(self, path, mode=0):
-        self.path = path
-        self.kind |= mode
-    def __repr__(self):
-        return '<RealFile %s>' % (self.path,)
-    def getsize(self):
-        return os.stat(self.path).st_size
-    def open(self):
-        try:
-            return open(self.path, "rb")
-        except IOError as e:
-            raise OSError(e.errno, "open failed")


More information about the pypy-commit mailing list