[m-rev.] for review: add a system RNG implementation for the C backends on Linux
Julien Fischer
jfischer at opturion.com
Sun Jan 31 15:15:34 AEDT 2021
For review by anyone.
------------------------
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.)
runtime/mercury_random.h:
runtime/mercury_random.c:
Add new runtime module that provides the interface to the system
RNG for the C backends.
Add two implementations of the system RNG, 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).
runtime/Mmakefile:
Add the new files.
library/random.system_rng.m:
Add C foreign procs for the predicates in this module that
forward all their work to the new runtime module.
Julien.
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\"").
+
+:- pragma foreign_type("C", system_rng, "MR_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 +117,17 @@
%---------------------------------------------------------------------------%
+:- pragma foreign_proc("C",
+ have_system_rng,
+ [will_not_call_mercury, promise_pure, thread_safe],
+"
+#if defined(MR_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 +142,6 @@
SUCCESS_INDICATOR = true;
").
-have_system_rng :-
- semidet_false.
-
%---------------------------------------------------------------------------%
open_system_rng(Result, !IO) :-
@@ -146,6 +157,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 = MR_random_open(&IsOk, &ErrorMsg);
+").
+
:- pragma foreign_proc("C#",
do_open_system_rng(Handle::out, IsOk::out, ErrorMsg::out,
_IO0::di, _IO::uo),
@@ -167,11 +186,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 +194,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 = MR_random_close(Handle, &ErrorMsg);
+").
+
:- pragma foreign_proc("C#",
do_close_system_rng(Handle::in, IsOk::out, ErrorMsg::out,
_IO0::di, _IO::uo),
@@ -206,95 +228,193 @@ 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],
+"
+ U8 = MR_random_generate_uint8(Handle, &IsOk, &ErrorMsg);
+").
+
:- 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],
+"
+ U16 = MR_random_generate_uint16(Handle, &IsOk, &ErrorMsg);
+").
+
:- 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) System.BitConverter.ToInt16(bytes);
+ 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],
+"
+ U32 = MR_random_generate_uint32(Handle, &IsOk, &ErrorMsg);
+").
+
:- 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) System.BitConverter.ToInt32(bytes);
+ 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],
+"
+ U64 = MR_random_generate_uint64(Handle, &IsOk, &ErrorMsg);
+").
+
:- 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];
Handle.GetBytes(bytes);
U64 = (ulong) System.BitConverter.ToInt64(bytes);
+ 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)).
%---------------------------------------------------------------------------%
:- end_module random.system_rng.
diff --git a/runtime/Mmakefile b/runtime/Mmakefile
index 8e7beb1..45939dc 100644
--- a/runtime/Mmakefile
+++ b/runtime/Mmakefile
@@ -79,6 +79,7 @@ HDRS = \
mercury_prof_mem.h \
mercury_prof_time.h \
mercury_profiling_builtin.h \
+ mercury_random.h \
mercury_reg_workarounds.h \
mercury_region.h \
mercury_regs.h \
@@ -194,6 +195,7 @@ CFILES = \
mercury_prof_mem.c \
mercury_prof_time.c \
mercury_profiling_builtin.c \
+ mercury_random.c \
mercury_reg_workarounds.c \
mercury_region.c \
mercury_regs.c \
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
+
+#include "mercury_memory.h"
+#include "mercury_misc.h"
+#include "mercury_random.h"
+#include "mercury_runtime_util.h"
+#include "mercury_std.h"
+#include "mercury_string.h"
+#include "mercury_types.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_STATH)
+ #include <sys/stat.h>
+#endif
+
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#if defined(MR_SYSRAND_IMPL_URANDOM)
+
+static MR_Bool
+read_bytes_from_urandom(int fd, void *buffer, size_t n,
+ MR_String *err_msg);
+
+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));
+ 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));
+ 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));
+ 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;
+ }
+}
+
+uint16_t
+MR_random_generate_uint16(MR_SystemRandomHandle handle,
+ MR_Bool *succeeded, MR_String *err_msg)
+{
+ uint16_t n;
+ unsigned char *buffer = (unsigned char *) &n;
+ if (read_bytes_from_urandom(handle->MR_srh_fd, buffer, 2, err_msg)) {
+ *succeeded = MR_TRUE;
+ return n;
+ } else {
+ *succeeded = MR_FALSE;
+ return 0;
+ }
+}
+
+uint32_t
+MR_random_generate_uint32(MR_SystemRandomHandle handle,
+ MR_Bool *succeeded, MR_String *err_msg)
+{
+ uint32_t n;
+ unsigned char *buffer = (unsigned char *) &n;
+ if (read_bytes_from_urandom(handle->MR_srh_fd, buffer, 4, err_msg)) {
+ *succeeded = MR_TRUE;
+ return n;
+ } else {
+ *succeeded = MR_FALSE;
+ return 0;
+ }
+}
+
+uint64_t
+MR_random_generate_uint64(MR_SystemRandomHandle handle,
+ MR_Bool *succeeded, MR_String *err_msg)
+{
+ uint64_t n;
+ unsigned char *buffer = (unsigned char *) &n;
+ if (read_bytes_from_urandom(handle->MR_srh_fd, buffer, 8, err_msg)) {
+ *succeeded = MR_TRUE;
+ return n;
+ } else {
+ *succeeded = MR_FALSE;
+ return 0;
+ }
+}
+
+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));
+ 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
+
+MR_SystemRandomHandle
+MR_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
+MR_random_close(MR_SystemRandomHandle handle, MR_String *err_msg)
+{
+ MR_fatal_error("MR_random_close - no system RNG available.");
+}
+
+uint8_t
+MR_random_generate_uint8(MR_SystemRandomHandle handle,
+ MR_Bool *succeeded, MR_String *err_msg)
+{
+ MR_fatal_error("MR_random_generate_uint8 - no system RNG available.");
+}
+
+uint16_t
+MR_random_generate_uint16(MR_SystemRandomHandle handle,
+ MR_Bool *succeeded, MR_String *err_msg)
+{
+ MR_fatal_error("MR_random_genereate_uint16 - No system RNG available.");
+}
+
+uint32_t
+MR_random_generate_uint32(MR_SystemRandomHandle handle,
+ MR_Bool *succeeded, MR_String *err_msg)
+{
+ MR_fatal_error("MR_random_generate_uint32 - no system RNG available.");
+}
+
+uint64_t
+MR_random_generate_uint64(MR_SystemRandomHandle handle,
+ MR_Bool *succeeded, MR_String *err_msg)
+{
+ MR_fatal_error("MR_random_generate_uint64 - no system RNG available.");
+}
+
+#endif
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
+
+////////////////////////////////////////////////////////////////////////////
+
+#if defined(MR_SYSRAND_IMPL_URANDOM)
+ struct MR_SystemRandomHandle_Struct {
+ int MR_srh_fd;
+ };
+ typedef struct MR_SystemRandomHandle_Struct *MR_SystemRandomHandle;
+#else
+ typedef MR_Unsigned MR_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 MR_SystemRandomHandle MR_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 MR_random_close(MR_SystemRandomHandle handle, MR_String *err_msg);
+
+
+// If succeeded is MR_TRUE then these functions return a randomly generated
+// unsigned integer of the requested size generated through the given system
+// RNG handle; err_msg will point to the empty string.
+//
+// If succeeded is MR_FALSE, the system RNG has been unable to generate the
+// random unsigned integer; in this case zero will be returned and err_msg will
+// point to a string (on the Mercury heap) that describes the problem.
+//
+extern uint8_t MR_random_generate_uint8(MR_SystemRandomHandle handle,
+ MR_Bool *succeeded, MR_String *err_smg);
+extern uint16_t MR_random_generate_uint16(MR_SystemRandomHandle handle,
+ MR_Bool *succeeded, MR_String *err_msg);
+extern uint32_t MR_random_generate_uint32(MR_SystemRandomHandle handle,
+ MR_Bool *succeeded, MR_String *err_msg);
+extern uint64_t MR_random_generate_uint64(MR_SystemRandomHandle handle,
+ MR_Bool *succeeded, MR_String *err_msg);
+
+#endif // MR_MERCURY_RANDOM_H
More information about the reviews
mailing list