[m-rev.] for review: add a system RNG implementation for the C backends on Linux

Julien Fischer jfischer at opturion.com
Mon Feb 1 15:56:01 AEDT 2021


Hi Peter,

On Mon, 1 Feb 2021, Peter Wang wrote:

>> diff --git a/library/random.system_rng.m b/library/random.system_rng.m
>> index 872058c..affd2c4 100644
>> --- a/library/random.system_rng.m
>> +++ b/library/random.system_rng.m
>> @@ -84,16 +84,19 @@
>>
>>   :- import_module bool.
>>   :- import_module exception.
>> +:- import_module list.
>> +:- import_module string.
>>
>>   %---------------------------------------------------------------------------%
>>
>> -:- pragma foreign_type("Java", system_rng,
>> -    "java.security.SecureRandom").
>> +:- pragma foreign_decl("C", "#include \"mercury_random.h\"").
>> +
>
> IMO the C code doesn't need to be in the runtime directory.

Ok, I've shifted into the library.

>> diff --git a/runtime/mercury_random.c b/runtime/mercury_random.c
>> index e69de29..85a1025 100644
>> --- a/runtime/mercury_random.c
>> +++ b/runtime/mercury_random.c
>> @@ -0,0 +1,214 @@
>> +// vim: ts=4 sw=4 expandtab ft=c
>> +
>> +// Copyright (C) 2021 The Mercury team.
>> +// This file is distributed under the terms specified in COPYING.LIB.
>> +
>> +// mercury_random.c
>> +
>
> There are a few stray space characters at EOLs in this file.

Fixed.


>> +#if defined(MR_SYSRAND_IMPL_URANDOM)
>> +
>> +static MR_Bool
>> +read_bytes_from_urandom(int fd, void *buffer, size_t n,
>> +    MR_String *err_msg);
>
> It's not specific to urandom to maybe rename it to read_bytes
> or read_bytes_from_fd?

After changes due to comments below it no longer exists.

>> +
>> +MR_SystemRandomHandle
>> +MR_random_open(MR_Bool *succeeded, MR_String *err_msg)
>> +{
>> +    int fd;
>> +    char errbuf[MR_STRERROR_BUF_SIZE];
>> +    MR_SystemRandomHandle handle;
>> +
>> +    do {
>> +        fd = open("/dev/urandom", O_RDONLY);
>> +    } while (fd == -1 && MR_is_eintr(errno));
>> +
>> +    if (fd == -1) {
>> +        MR_strerror(errno, errbuf, sizeof(errbuf));
>
> You must use the return value of MR_strerror() as it may not
> fill the given buffer.

Fixed.

>> +        MR_save_transient_hp();
>> +        MR_make_aligned_string_copy(*err_msg, errbuf);
>> +        MR_restore_transient_hp();
>> +        *succeeded = MR_NO;
>> +        handle = NULL;
>> +    } else {
>> +        handle =
>> +            MR_GC_malloc(sizeof(struct MR_SystemRandomHandle_Struct));
>
> Could use MR_GC_NEW.

Done.

>> +        handle->MR_srh_fd = fd;
>> +        *err_msg = MR_make_string_const("");
>> +        *succeeded = MR_YES;
>> +    }
>> +
>> +    return handle;
>> +}
>> +
>> +MR_Bool
>> +MR_random_close(MR_SystemRandomHandle handle, MR_String *err_msg)
>> +{
>> +    char errbuf[MR_STRERROR_BUF_SIZE];
>> +
>> +    if (close(handle->MR_srh_fd) == -1) {
>> +        MR_strerror(errno, errbuf, sizeof(errbuf));
>
> As above.

Fixed.

>> +        MR_save_transient_hp();
>> +        MR_make_aligned_string_copy(*err_msg, errbuf);
>> +        MR_restore_transient_hp();
>> +        return MR_NO;
>> +    } else {
>> +        handle->MR_srh_fd = -1;
>> +        *err_msg = MR_make_string_const("");
>> +        return MR_YES;
>> +    }
>> +}
>> +
>
>> +uint8_t
>> +MR_random_generate_uint8(MR_SystemRandomHandle handle,
>> +    MR_Bool *succeeded, MR_String *err_msg)
>> +{
>> +    unsigned char buffer[1];
>> +    if (read_bytes_from_urandom(handle->MR_srh_fd, buffer, 1, err_msg)) {
>> +        *succeeded = MR_TRUE;
>> +        return (uint8_t) buffer[0];
>> +    } else {
>> +        *succeeded = MR_FALSE;
>> +        return 0;
>> +    }
>> +}
>
> Why not have a single function which takes a buffer and length?
> The caller can use a union type consisting of the uintN_t and the
> unsigned char buffer:
>
>    union {
> 	uint16_t x;
> 	unsigned char buf[2];
>    } u;
>    succeeded =
> 	MR_random_generate_bytes(handle, u.buf, sizeof(u.buf), &err_msg);

Good idea, I have done so.

>
>> +MR_Bool
>> +read_bytes_from_urandom(int fd, void *buffer, size_t n,
>> +    MR_String *err_msg)
>> +{
>> +    int i;
>> +    char errbuf[MR_STRERROR_BUF_SIZE];
>> +
>> +    for (i = 0; i < n; ) {
>> +        size_t to_read = n - i;
>> +        ssize_t bytes_read = read(fd, buffer, to_read);
>> +        if (bytes_read == -1) {
>> +            if (MR_is_eintr(errno)) {
>> +                continue;
>> +            } else {
>> +                MR_strerror(errno, errbuf, sizeof(errbuf));
>
> As above.

Fixed.

>> +                MR_save_transient_hp();
>> +                MR_make_aligned_string_copy(*err_msg, errbuf);
>> +                MR_restore_transient_hp();
>> +                return MR_FALSE;
>> +            }
>> +        }
>> +        i += bytes_read;
>> +    }
>> +
>> +    *err_msg = MR_make_string_const("");
>> +    return MR_TRUE;
>> +}
>> +
>> +#else // MR_SYSRAND_IMPL_NONE
>
>> diff --git a/runtime/mercury_random.h b/runtime/mercury_random.h
>> index e69de29..8e29c78 100644
>> --- a/runtime/mercury_random.h
>> +++ b/runtime/mercury_random.h
>> @@ -0,0 +1,97 @@
>> +// vim: ts=4 sw=4 expandtab ft=c
>> +
>> +// Copyright (C) 2021 The Mercury team.
>> +// This file is distributed under the terms specified in COPYING.LIB.
>> +
>> +// mercury_random.h - code for interacting with the system RNG.
>> +
>> +#ifndef MR_MERCURY_RANDOM_H
>> +#define MERCURY_RANDOM_H
>> +
>> +#include "mercury_conf_param.h"
>> +#include "mercury_types.h"
>> +#include "mercury_windows.h"
>> +
>> +#include <stdint.h>
>> +
>> +////////////////////////////////////////////////////////////////////////////
>> +//
>> +// The following macros define if the system random number exists on this
>> +// system and, if so, how it is accessed.
>> +//
>> +// Only one of the following must be defined.
>> +//
>> +// MR_SYSRAND_IMPL_ARC4RANDOM (NYI)
>> +//    the system RNG is implemented by calling the arc4random() family of
>> +//    functions. Note: this for when arc4random() is provided by libc (as on
>> +//    macOS and the BSDs), not for when it is provided as a separate library
>> +//    (e.g. libbsd on Linux).
>> +//
>> +// MR_SYSRAND_IMPL_RAND_S (NYI)
>> +//    the system RNG is implemented by calling the rand_s() function
>> +//    (Windows only).
>> +//
>> +// MR_SYSRAND_IMPL_GETRANDOM (NYI)
>> +//    the system RNG is implemented by calling getrandom() (sufficiently
>> +//    recent Linux kernels only).
>> +//
>> +// MR_SYSRAND_IMPL_URANDOM
>> +//     the system RNG is implemented by reading from /dev/urandom.
>> +//     (XXX currently only tested, and enabled, on Linux.)
>> +//
>> +// MR_SYSRAND_IMPL_NONE
>> +//     there is no system RNG is not available on this platform.
>> +
>> +#if defined(__linux__)
>> +    #define MR_SYSRAND_IMPL_URANDOM
>> +#else
>> +    #define MR_SYSRAND_IMPL_NONE
>> +#endif
>
> I've used /dev/urandom on Linux, FreeBSD, OpenBSD, MacOS X and AIX,
> and Solaris and NetBSD also have it, so you can enable
> MR_SYSRAND_IMPL_URANDOM on anything but Windows.

/dev/urandom should only be used when nothing better is available;
macOS and the BSDs should all use arc4random().

I don't have access to a Solaris or AIX system to test on.


Modified log message and diff below.
Thanks for the review.

Julien.

------------------------------

     Add a system RNG implementation for the C backends on Linux.

     Add a system RNG implementation for the C backends on Linux that works by
     reading random bits from /dev/urandom. We will eventually provide a version
     that reads from getrandom() on Linux systems that provide that; this
     implementation is a fallback for those system that do not.  (It may also
     be what we use on other Unix-like system that do not provide anything
     better.)

     library/random.system_rng.m:
         Add two implementations of the system RNG for the C backends, one that
         reads from /dev/urandom and one that just aborts.  The former is used
         on Linux and the latter is used everywhere else (for now).

diff --git a/library/random.system_rng.m b/library/random.system_rng.m
index c14aea8..95c6b3c 100644
--- a/library/random.system_rng.m
+++ b/library/random.system_rng.m
@@ -84,16 +84,112 @@

  :- import_module bool.
  :- import_module exception.
+:- import_module list.
+:- import_module string.

  %---------------------------------------------------------------------------%

-:- pragma foreign_type("Java", system_rng,
-    "java.security.SecureRandom").
+
+:- pragma foreign_decl("C", "
+
+#include \"mercury_conf_param.h\"
+#include \"mercury_memory.h\"
+#include \"mercury_misc.h\"
+#include \"mercury_runtime_util.h\"
+#include \"mercury_std.h\"
+#include \"mercury_string.h\"
+#include \"mercury_types.h\"
+#include \"mercury_windows.h\"
+
+#if defined(MR_HAVE_UNISTD_H)
+    #include <unistd.h>
+#endif
+#if defined(MR_HAVE_FCNTL_H)
+    #include <fcntl.h>
+#endif
+#if defined(MR_HAVE_SYS_STAT_H)
+    #include <sys/stat.h>
+#endif
+
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+// The following macros define if the system random number exists on this
+// system and, if so, how it is accessed.
+//
+// Only one of the following must be defined.
+//
+// ML_SYSRAND_IMPL_ARC4RANDOM (NYI)
+//    the system RNG is implemented by calling the arc4random() family of
+//    functions. Note: this for when arc4random() is provided by libc (as on
+//    macOS and the BSDs), not for when it is provided as a separate library
+//    (e.g. libbsd on Linux).
+//
+// ML_SYSRAND_IMPL_RAND_S (NYI)
+//    the system RNG is implemented by calling the rand_s() function
+//    (Windows only).
+//
+// ML_SYSRAND_IMPL_GETRANDOM (NYI)
+//    the system RNG is implemented by calling getrandom() (sufficiently
+//    recent Linux kernels only).
+//
+// ML_SYSRAND_IMPL_URANDOM
+//     the system RNG is implemented by reading from /dev/urandom.
+//     (XXX currently only tested, and enabled, on Linux.)
+//
+// ML_SYSRAND_IMPL_NONE
+//     there is no system RNG is not available on this platform.
+
+#if defined(__linux__)
+    #define ML_SYSRAND_IMPL_URANDOM
+#else
+    #define ML_SYSRAND_IMPL_NONE
+#endif
+
+#if defined(ML_SYSRAND_IMPL_URANDOM)
+    struct ML_SystemRandomHandle_Struct {
+        int ML_srh_fd;
+    };
+    typedef struct ML_SystemRandomHandle_Struct *ML_SystemRandomHandle;
+#else
+    typedef MR_Unsigned ML_SystemRandomHandle;
+#endif
+
+// When succeeded is MR_TRUE, returns a handle through which the system
+// RNG can be accessed; err_msg will point to the empty string in this case.
+// When succeeded is MR_FALSE, the value return is not a valid handle and
+// err_msg will point ot a string (on the Mercury heap) describing why a handle
+// for the system RNG could not be acquired.
+//
+extern ML_SystemRandomHandle ML_random_open(MR_Bool *succeeded,
+    MR_String *err_msg);
+
+// Attempt to close the handle to the system RNG.
+// Returns MR_TRUE on success with err_msg pointing to the empty string.
+// Returns MR_FALSE on failure with err_msg pointing to a string (on the
+// Mercury heap) that describes whey the handle could not be closed.
+//
+extern MR_Bool ML_random_close(ML_SystemRandomHandle handle, MR_String *err_msg);
+
+
+// Fill buffer with len random bytes generated by the system RNG.
+// Returns MR_TRUE if len bytes were generated; err_msg will point to the empty
+// string.
+// Returns MR_FALSE if the system RNG has been unable to generate len bytes.
+// In this case the contents of buffer are indeterminate and err_msg will point
+// to a string (on the Mercury heap) that describes the problem.
+//
+extern MR_Bool ML_random_generate_bytes(ML_SystemRandomHandle handle,
+    unsigned char *buffer, size_t len, MR_String *err_msg);
+").
+
+:- pragma foreign_type("C", system_rng,
+    "ML_SystemRandomHandle", [can_pass_as_mercury_type]).
  :- pragma foreign_type("C#", system_rng,
      "System.Security.Cryptography.RandomNumberGenerator").
-
-:- type system_rng
-    --->    system_rng.
+:- pragma foreign_type("Java", system_rng,
+    "java.security.SecureRandom").

  %---------------------------------------------------------------------------%

@@ -114,6 +210,17 @@

  %---------------------------------------------------------------------------%

+:- pragma foreign_proc("C",
+    have_system_rng,
+    [will_not_call_mercury, promise_pure, thread_safe],
+"
+#if defined(ML_SYSRAND_IMPL_NONE)
+    SUCCESS_INDICATOR = MR_FALSE;
+#else
+    SUCCESS_INDICATOR = MR_TRUE;
+#endif
+").
+
  :- pragma foreign_proc("C#",
      have_system_rng,
      [will_not_call_mercury, promise_pure, thread_safe],
@@ -128,9 +235,6 @@
      SUCCESS_INDICATOR = true;
  ").

-have_system_rng :-
-    semidet_false.
-
  %---------------------------------------------------------------------------%

  open_system_rng(Result, !IO) :-
@@ -146,6 +250,14 @@ open_system_rng(Result, !IO) :-
  :- pred do_open_system_rng(system_rng::out, bool::out, string::out,
      io::di, io::uo) is det.

+:- pragma foreign_proc("C",
+    do_open_system_rng(Handle::out, IsOk::out, ErrorMsg::out,
+        _IO0::di, _IO::uo),
+    [will_not_call_mercury, promise_pure, thread_safe, tabled_for_io],
+"
+    Handle = ML_random_open(&IsOk, &ErrorMsg);
+").
+
  :- pragma foreign_proc("C#",
      do_open_system_rng(Handle::out, IsOk::out, ErrorMsg::out,
          _IO0::di, _IO::uo),
@@ -167,11 +279,6 @@ open_system_rng(Result, !IO) :-
      ErrorMsg = \"\";
  ").

-do_open_system_rng(Handle, IsOk, ErrorMsg, !IO) :-
-    Handle = system_rng,
-    IsOk = no,
-    ErrorMsg = "No system RNG available".
-
  %---------------------------------------------------------------------------%

  close_system_rng(Handle, !IO) :-
@@ -180,12 +287,20 @@ close_system_rng(Handle, !IO) :-
          IsOk = yes
      ;
          IsOk = no,
-        throw(software_error(ErrorMsg))
+        throw_system_rng_error($pred, ErrorMsg)
      ).

  :- pred do_close_system_rng(system_rng::in, bool::out, string::out,
      io::di, io::uo) is det.

+:- pragma foreign_proc("C",
+    do_close_system_rng(Handle::in, IsOk::out, ErrorMsg::out,
+        _IO0::di, _IO::uo),
+    [will_not_call_mercury, promise_pure, thread_safe],
+"
+    IsOk = ML_random_close(Handle, &ErrorMsg);
+").
+
  :- pragma foreign_proc("C#",
      do_close_system_rng(Handle::in, IsOk::out, ErrorMsg::out,
          _IO0::di, _IO::uo),
@@ -206,80 +321,191 @@ close_system_rng(Handle, !IO) :-
      ErrorMsg = \"\";
  ").

-do_close_system_rng(_, _, _, _, _) :-
-    private_builtin.sorry("No system RNG available").
-
  %---------------------------------------------------------------------------%

+generate_uint8(Handle, U8, !IO) :-
+    do_generate_uint8(Handle, U8, IsOk, ErrorMsg, !IO),
+    (
+        IsOk = yes
+    ;
+        IsOk = no,
+        throw_system_rng_error($pred, ErrorMsg)
+    ).
+
+:- pred do_generate_uint8(system_rng::in, uint8::out, bool::out,
+    string::out, io::di, io::uo) is det.
+
+:- pragma foreign_proc("C",
+    do_generate_uint8(Handle::in, U8::out, IsOk::out, ErrorMsg::out,
+        _IO0::di, _IO::uo),
+    [will_not_call_mercury, promise_pure, thread_safe, tabled_for_io],
+"
+    union {
+        uint8_t n;
+        unsigned char buffer[1];
+    } u;
+    IsOk = ML_random_generate_bytes(Handle, u.buffer, sizeof(u.buffer),
+        &ErrorMsg);
+    U8 = u.n;
+").
+
  :- pragma foreign_proc("C#",
-    generate_uint8(Handle::in, U8::out, _IO0::di, _IO::uo),
+    do_generate_uint8(Handle::in, U8::out, IsOk::out, ErrorMsg::out,
+        _IO0::di, _IO::uo),
      [will_not_call_mercury, promise_pure, thread_safe],
  "
      byte[] bytes = new byte[1];
      Handle.GetBytes(bytes);
      U8 = bytes[0];
+    IsOk = mr_bool.YES;
+    ErrorMsg = \"\";
  ").

  :- pragma foreign_proc("Java",
-    generate_uint8(Handle::in, U8::out, _IO0::di, _IO::uo),
+    do_generate_uint8(Handle::in, U8::out, IsOk::out, ErrorMsg::out,
+        _IO0::di, _IO::uo),
      [will_not_call_mercury, promise_pure, thread_safe],
  "
      byte[] bytes = new byte[1];
      Handle.nextBytes(bytes);
      U8 = bytes[0];
+    IsOk = bool.YES;
+    ErrorMsg = \"\";
  ").

-generate_uint8(_, _, !IO) :-
-    private_builtin.sorry("No system RNG available").
-
  %---------------------------------------------------------------------------%

+generate_uint16(Handle, U16, !IO) :-
+    do_generate_uint16(Handle, U16, IsOk, ErrorMsg, !IO),
+    (
+        IsOk = yes
+    ;
+        IsOk = no,
+        throw_system_rng_error($pred, ErrorMsg)
+    ).
+
+:- pred do_generate_uint16(system_rng::in, uint16::out, bool::out,
+    string::out, io::di, io::uo) is det.
+
+:- pragma foreign_proc("C",
+    do_generate_uint16(Handle::in, U16::out, IsOk::out, ErrorMsg::out,
+        _IO0::di, _IO::uo),
+    [will_not_call_mercury, promise_pure, thread_safe, tabled_for_io],
+"
+    union {
+        uint16_t n;
+        unsigned char buffer[2];
+    } u;
+    IsOk = ML_random_generate_bytes(Handle, u.buffer, sizeof(u.buffer),
+        &ErrorMsg);
+    U16 = u.n;
+").
+
  :- pragma foreign_proc("C#",
-    generate_uint16(Handle::in, U16::out, _IO0::di, _IO::uo),
+    do_generate_uint16(Handle::in, U16::out, IsOk::out, ErrorMsg::out,
+        _IO0::di, _IO::uo),
      [will_not_call_mercury, promise_pure, not_thread_safe],
  "
      byte[] bytes = new byte[2];
      Handle.GetBytes(bytes);
      U16 = (ushort) (bytes[1] << 8 | (bytes[0] & 0x00ff));
+    IsOk = mr_bool.YES;
+    ErrorMsg = \"\";
  ").

  :- pragma foreign_proc("Java",
-    generate_uint16(Handle::in, U16::out, _IO0::di, _IO::uo),
+    do_generate_uint16(Handle::in, U16::out, IsOk::out, ErrorMsg::out,
+        _IO0::di, _IO::uo),
      [will_not_call_mercury, promise_pure, thread_safe],
  "
      byte[] bytes = new byte[2];
      Handle.nextBytes(bytes);
      U16 = (short) (bytes[0] << java.lang.Byte.SIZE | (bytes[1] & 0x00ff));
+    IsOk = bool.YES;
+    ErrorMsg = \"\";
  ").

-generate_uint16(_, _, !IO) :-
-    private_builtin.sorry("No system RNG available").
-
  %---------------------------------------------------------------------------%

+generate_uint32(Handle, U32, !IO) :-
+    do_generate_uint32(Handle, U32, IsOk, ErrorMsg, !IO),
+    (
+        IsOk = yes
+    ;
+        IsOk = no,
+        throw_system_rng_error($pred, ErrorMsg)
+    ).
+
+:- pred do_generate_uint32(system_rng::in, uint32::out, bool::out,
+    string::out, io::di, io::uo) is det.
+
+:- pragma foreign_proc("C",
+    do_generate_uint32(Handle::in, U32::out, IsOk::out, ErrorMsg::out,
+        _IO0::di, _IO::uo),
+    [will_not_call_mercury, promise_pure, thread_safe, tabled_for_io],
+"
+    union {
+        uint32_t n;
+        unsigned char buffer[4];
+    } u;
+    IsOk = ML_random_generate_bytes(Handle, u.buffer, sizeof(u.buffer),
+        &ErrorMsg);
+    U32 = u.n;
+").
+
  :- pragma foreign_proc("C#",
-    generate_uint32(Handle::in, U32::out, _IO0::di, _IO::uo),
+    do_generate_uint32(Handle::in, U32::out, IsOk::out, ErrorMsg::out,
+        _IO0::di, _IO::uo),
      [will_not_call_mercury, promise_pure, not_thread_safe],
  "
      byte[] bytes = new byte[4];
      Handle.GetBytes(bytes);
      U32 = (uint) (bytes[3] << 24 | bytes[2] << 16 | bytes[1] << 8 | bytes[0]);
+    IsOk = mr_bool.YES;
+    ErrorMsg = \"\";
  ").

  :- pragma foreign_proc("Java",
-    generate_uint32(Handle::in, U32::out, _IO0::di, _IO::uo),
+    do_generate_uint32(Handle::in, U32::out, IsOk::out, ErrorMsg::out,
+        _IO0::di, _IO::uo),
      [will_not_call_mercury, promise_pure, thread_safe],
  "
      U32 = Handle.nextInt();
+    IsOk = bool.YES;
+    ErrorMsg = \"\";
  ").

-generate_uint32(_, _, !IO) :-
-    private_builtin.sorry("No system RNG available").
-
  %---------------------------------------------------------------------------%

+generate_uint64(Handle, U64, !IO) :-
+    do_generate_uint64(Handle, U64, IsOk, ErrorMsg, !IO),
+    (
+        IsOk = yes
+    ;
+        IsOk = no,
+        throw_system_rng_error($pred, ErrorMsg)
+    ).
+
+:- pred do_generate_uint64(system_rng::in, uint64::out, bool::out,
+    string::out, io::di, io::uo) is det.
+
+:- pragma foreign_proc("C",
+    do_generate_uint64(Handle::in, U64::out, IsOk::out, ErrorMsg::out,
+        _IO0::di, _IO::uo),
+    [will_not_call_mercury, promise_pure, thread_safe, tabled_for_io],
+"
+    union {
+        uint64_t n;
+        unsigned char buffer[8];
+    } u;
+    IsOk = ML_random_generate_bytes(Handle, u.buffer, sizeof(u.buffer),
+        &ErrorMsg);
+    U64 = u.n;
+").
+
  :- pragma foreign_proc("C#",
-    generate_uint64(Handle::in, U64::out, _IO0::di, _IO::uo),
+    do_generate_uint64(Handle::in, U64::out, IsOk::out, ErrorMsg::out,
+        _IO0::di, _IO::uo),
      [will_not_call_mercury, promise_pure, not_thread_safe],
  "
      byte[] bytes = new byte[8];
@@ -293,17 +519,136 @@ generate_uint32(_, _, !IO) :-
          (ulong) bytes[2] << 16 |
          (ulong) bytes[1] << 8  |
          (ulong) bytes[0]);
+    IsOk = mr_bool.YES;
+    ErrorMsg = \"\";
  ").

  :- pragma foreign_proc("Java",
-    generate_uint64(Handle::in, U64::out, _IO0::di, _IO::uo),
+    do_generate_uint64(Handle::in, U64::out, IsOk::out, ErrorMsg::out,
+        _IO0::di, _IO::uo),
      [will_not_call_mercury, promise_pure, thread_safe],
  "
      U64 = Handle.nextLong();
+    IsOk = bool.YES;
+    ErrorMsg = \"\";
  ").

-generate_uint64(_, _, !IO) :-
-    private_builtin.sorry("No system RNG available").
+%---------------------------------------------------------------------------%
+
+:- pred throw_system_rng_error(string::in, string::in) is erroneous.
+
+throw_system_rng_error(Pred, Msg) :-
+    string.format("%s: %s", [s(Pred), s(Msg)], Error),
+    throw(software_error(Error)).
+
+%---------------------------------------------------------------------------%
+
+:- pragma foreign_code("C", "
+
+#if defined(ML_SYSRAND_IMPL_URANDOM)
+
+ML_SystemRandomHandle
+ML_random_open(MR_Bool *succeeded, MR_String *err_msg)
+{
+    int fd;
+    char errbuf[MR_STRERROR_BUF_SIZE];
+    const char *errno_msg;
+    ML_SystemRandomHandle handle;
+
+    do {
+        fd = open(\"/dev/urandom\", O_RDONLY);
+    } while (fd == -1 && MR_is_eintr(errno));
+
+    if (fd == -1) {
+        errno_msg = MR_strerror(errno, errbuf, sizeof(errbuf));
+        MR_save_transient_hp();
+        MR_make_aligned_string_copy(*err_msg, errno_msg);
+        MR_restore_transient_hp();
+        *succeeded = MR_NO;
+        handle = NULL;
+    } else {
+        handle = MR_GC_NEW(struct ML_SystemRandomHandle_Struct);
+        handle->ML_srh_fd = fd;
+        *err_msg = MR_make_string_const(\"\");
+        *succeeded = MR_YES;
+    }
+
+    return handle;
+}
+
+MR_Bool
+ML_random_close(ML_SystemRandomHandle handle, MR_String *err_msg)
+{
+    char errbuf[MR_STRERROR_BUF_SIZE];
+
+    if (close(handle->ML_srh_fd) == -1) {
+        MR_strerror(errno, errbuf, sizeof(errbuf));
+        MR_save_transient_hp();
+        MR_make_aligned_string_copy(*err_msg, errbuf);
+        MR_restore_transient_hp();
+        return MR_NO;
+    } else {
+        handle->ML_srh_fd = -1;
+        *err_msg = MR_make_string_const(\"\");
+        return MR_YES;
+    }
+}
+
+MR_Bool
+ML_random_generate_bytes(ML_SystemRandomHandle handle,
+    unsigned char *buffer, size_t len, MR_String *err_msg)
+{
+    int i;
+    char errbuf[MR_STRERROR_BUF_SIZE];
+    const char *errno_msg;
+
+    for (i = 0; i < len; ) {
+        size_t to_read = len - i;
+        ssize_t bytes_read = read(handle->ML_srh_fd, buffer, to_read);
+        if (bytes_read == -1) {
+            if (MR_is_eintr(errno)) {
+                continue;
+            } else {
+                errno_msg = MR_strerror(errno, errbuf, sizeof(errbuf));
+                MR_save_transient_hp();
+                MR_make_aligned_string_copy(*err_msg, errno_msg);
+                MR_restore_transient_hp();
+                return MR_FALSE;
+            }
+        }
+        i += bytes_read;
+    }
+
+    *err_msg = MR_make_string_const(\"\");
+    return MR_TRUE;
+}
+
+#else // ML_SYSRAND_IMPL_NONE
+
+ML_SystemRandomHandle
+ML_random_open(MR_Bool *succeeded, MR_String *err_msg)
+{
+    succeeded = MR_FALSE;
+    *err_msg = MR_make_string_const(\"No system RNG available\");
+    return 0; // Dummy value.
+}
+
+MR_Bool
+ML_random_close(MR_SystemRandomHandle handle, MR_String *err_msg)
+{
+    MR_fatal_error(\"ML_random_close - no system RNG available.\");
+}
+
+MR_Bool
+ML_random_generate_bytes(MR_SystemRandomHandle handle,
+    unsigned char *buffer, MR_String *err_msg)
+{
+    MR_fatal_error(\"ML_random_generate_bytes - no system RNG available.\");
+}
+
+#endif
+
+").

  %---------------------------------------------------------------------------%
  :- end_module random.system_rng.


More information about the reviews mailing list