Author: Armin Rigo <[email protected]>
Branch:
Changeset: r89089:82ef21124af8
Date: 2016-12-16 10:42 +0100
http://bitbucket.org/pypy/pypy/changeset/82ef21124af8/
Log: Linux: try to implement os.urandom() on top of the syscall
getrandom()
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
@@ -1331,8 +1331,9 @@
Return a string of n random bytes suitable for cryptographic use.
"""
context = get(space).random_context
+ signal_checker = space.getexecutioncontext().checksignals
try:
- return space.wrap(rurandom.urandom(context, n))
+ return space.wrap(rurandom.urandom(context, n, signal_checker))
except OSError as e:
raise wrap_oserror(space, e)
diff --git a/rpython/rlib/rurandom.py b/rpython/rlib/rurandom.py
--- a/rpython/rlib/rurandom.py
+++ b/rpython/rlib/rurandom.py
@@ -7,12 +7,12 @@
from rpython.rtyper.lltypesystem import lltype, rffi
from rpython.rlib.objectmodel import not_rpython
+from rpython.translator.tool.cbuild import ExternalCompilationInfo
+from rpython.rtyper.tool import rffi_platform
if sys.platform == 'win32':
from rpython.rlib import rwin32
- from rpython.translator.tool.cbuild import ExternalCompilationInfo
- from rpython.rtyper.tool import rffi_platform
eci = ExternalCompilationInfo(
includes = ['windows.h', 'wincrypt.h'],
@@ -56,7 +56,7 @@
return lltype.malloc(rffi.CArray(HCRYPTPROV), 1,
immortal=True, zero=True)
- def urandom(context, n):
+ def urandom(context, n, signal_checker=None):
provider = context[0]
if not provider:
# This handle is never explicitly released. The operating
@@ -80,11 +80,71 @@
def init_urandom():
return None
- def urandom(context, n):
+ SYS_getrandom = None
+
+ if sys.platform.startswith('linux'):
+ eci = ExternalCompilationInfo(includes=['sys/syscall.h'])
+ class CConfig:
+ _compilation_info_ = eci
+ SYS_getrandom = rffi_platform.DefinedConstantInteger(
+ 'SYS_getrandom')
+ globals().update(rffi_platform.configure(CConfig))
+
+ if SYS_getrandom is not None:
+ from rpython.rlib.rposix import get_saved_errno, handle_posix_error
+ import errno
+
+ eci = eci.merge(ExternalCompilationInfo(includes=['linux/random.h']))
+ class CConfig:
+ _compilation_info_ = eci
+ GRND_NONBLOCK = rffi_platform.ConstantInteger('GRND_NONBLOCK')
+ globals().update(rffi_platform.configure(CConfig))
+
+ # On Linux, use the syscall() function because the GNU libc doesn't
+ # expose the Linux getrandom() syscall yet.
+ syscall = rffi.llexternal(
+ 'syscall',
+ [lltype.Signed, rffi.CCHARP, rffi.LONG, rffi.INT],
+ lltype.Signed,
+ compilation_info=eci,
+ save_err=rffi.RFFI_SAVE_ERRNO)
+
+ class Works:
+ status = True
+ getrandom_works = Works()
+
+ def _getrandom(n, result, signal_checker):
+ if not getrandom_works.status:
+ return n
+ while n > 0:
+ with rffi.scoped_alloc_buffer(n) as buf:
+ got = syscall(SYS_getrandom, buf.raw, n, GRND_NONBLOCK)
+ if got >= 0:
+ s = buf.str(got)
+ result.append(s)
+ n -= len(s)
+ continue
+ err = get_saved_errno()
+ if (err == errno.ENOSYS or err == errno.EPERM or
+ err == errno.EAGAIN): # see CPython 3.5
+ getrandom_works.status = False
+ return n
+ if err == errno.EINTR:
+ if signal_checker is not None:
+ signal_checker()
+ continue
+ handle_posix_error("getrandom", got)
+ raise AssertionError("unreachable")
+ return n
+
+ def urandom(context, n, signal_checker=None):
"Read n bytes from /dev/urandom."
- result = ''
- if n == 0:
- return result
+ result = []
+ if SYS_getrandom is not None:
+ n = _getrandom(n, result, signal_checker)
+ if n <= 0:
+ return ''.join(result)
+
# XXX should somehow cache the file descriptor. It's a mess.
# CPython has a 99% solution and hopes for the remaining 1%
# not to occur. For now, we just don't cache the file
@@ -98,8 +158,8 @@
if e.errno != errno.EINTR:
raise
data = ''
- result += data
+ result.append(data)
n -= len(data)
finally:
os.close(fd)
- return result
+ return ''.join(result)
diff --git a/rpython/rlib/test/test_rurandom.py
b/rpython/rlib/test/test_rurandom.py
new file mode 100644
--- /dev/null
+++ b/rpython/rlib/test/test_rurandom.py
@@ -0,0 +1,12 @@
+from rpython.rlib import rurandom
+
+def test_rurandom():
+ context = rurandom.init_urandom()
+ s = rurandom.urandom(context, 5000)
+ assert type(s) is str and len(s) == 5000
+ for x in [1, 11, 111, 222]:
+ assert s.count(chr(x)) >= 1
+
+def test_rurandom_no_syscall(monkeypatch):
+ monkeypatch.setattr(rurandom, 'SYS_getrandom', None)
+ test_rurandom()
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit