about summary refs log tree commit diff
path: root/sysdeps/mach/hurd/getrandom.c
diff options
context:
space:
mode:
Diffstat (limited to 'sysdeps/mach/hurd/getrandom.c')
-rw-r--r--sysdeps/mach/hurd/getrandom.c117
1 files changed, 102 insertions, 15 deletions
diff --git a/sysdeps/mach/hurd/getrandom.c b/sysdeps/mach/hurd/getrandom.c
index ad2d3ba387..9ee3ef74fb 100644
--- a/sysdeps/mach/hurd/getrandom.c
+++ b/sysdeps/mach/hurd/getrandom.c
@@ -16,10 +16,13 @@
    License along with the GNU C Library; if not, see
    <https://www.gnu.org/licenses/>.  */
 
+#include <hurd.h>
 #include <sys/random.h>
 #include <fcntl.h>
-#include <unistd.h>
-#include <not-cancel.h>
+
+__libc_rwlock_define_initialized (static, lock);
+static file_t random_server, random_server_nonblock,
+              urandom_server, urandom_server_nonblock;
 
 extern char *__trivfs_server_name __attribute__((weak));
 
@@ -29,9 +32,36 @@ ssize_t
 __getrandom (void *buffer, size_t length, unsigned int flags)
 {
   const char *random_source = "/dev/urandom";
-  int open_flags = O_RDONLY | O_CLOEXEC;
-  size_t amount_read;
-  int fd;
+  int open_flags = O_RDONLY;
+  file_t server, *cached_server;
+  error_t err;
+  char *data = buffer;
+  mach_msg_type_number_t nread = length;
+
+  switch (flags)
+    {
+    case 0:
+      cached_server = &urandom_server;
+      break;
+    case GRND_RANDOM:
+      cached_server = &random_server;
+      break;
+    case GRND_NONBLOCK:
+      cached_server = &urandom_server_nonblock;
+      break;
+    case GRND_RANDOM | GRND_NONBLOCK:
+      cached_server = &random_server_nonblock;
+      break;
+    default:
+      return __hurd_fail (EINVAL);
+    }
+
+  if (flags & GRND_RANDOM)
+    random_source = "/dev/random";
+  if (flags & GRND_NONBLOCK)
+    open_flags |= O_NONBLOCK;
+  /* No point in passing either O_NOCTTY, O_IGNORE_CTTY, or O_CLOEXEC
+     to file_name_lookup, since we're not making an fd.  */
 
   if (&__trivfs_server_name && __trivfs_server_name
       && __trivfs_server_name[0] == 'r'
@@ -44,19 +74,76 @@ __getrandom (void *buffer, size_t length, unsigned int flags)
     /* We are random, don't try to read ourselves!  */
     return length;
 
-  if (flags & GRND_RANDOM)
-    random_source = "/dev/random";
+again:
+  __libc_rwlock_rdlock (lock);
+  server = *cached_server;
+  if (MACH_PORT_VALID (server))
+    /* Attempt to read some random data using this port.  */
+    err = __io_read (server, &data, &nread, -1, length);
+  else
+    err = MACH_SEND_INVALID_DEST;
+  __libc_rwlock_unlock (lock);
 
-  if (flags & GRND_NONBLOCK)
-    open_flags |= O_NONBLOCK;
+  if (err == MACH_SEND_INVALID_DEST || err == MIG_SERVER_DIED)
+    {
+      file_t oldserver = server;
+      mach_port_urefs_t urefs;
+
+      /* Slow path: the cached port didn't work, or there was no
+         cached port in the first place.  */
+
+      __libc_rwlock_wrlock (lock);
+      server = *cached_server;
+      if (server != oldserver)
+        {
+          /* Someone else must have refetched the port while we were
+             waiting for the lock. */
+          __libc_rwlock_unlock (lock);
+          goto again;
+        }
+
+      if (MACH_PORT_VALID (server))
+        {
+          /* It could be that someone else has refetched the port and
+             it got the very same name.  So check whether it is a send
+             right (and not a dead name).  */
+          err = __mach_port_get_refs (__mach_task_self (), server,
+                                      MACH_PORT_RIGHT_SEND, &urefs);
+          if (!err && urefs > 0)
+            {
+              __libc_rwlock_unlock (lock);
+              goto again;
+            }
+
+          /* Now we're sure that it's dead.  */
+          __mach_port_deallocate (__mach_task_self (), server);
+        }
+
+      server = *cached_server = __file_name_lookup (random_source,
+                                                    open_flags, 0);
+      __libc_rwlock_unlock (lock);
+      if (!MACH_PORT_VALID (server))
+        /* No luck.  */
+        return -1;
+
+      goto again;
+    }
+
+  if (err)
+    return __hurd_fail (err);
 
-  fd = __open_nocancel(random_source, open_flags);
-  if (fd == -1)
-    return -1;
+  if (data != buffer)
+    {
+      if (nread > length)
+        {
+          __vm_deallocate (__mach_task_self (), (vm_address_t) data, nread);
+          return __hurd_fail (EGRATUITOUS);
+        }
+      memcpy (buffer, data, nread);
+      __vm_deallocate (__mach_task_self (), (vm_address_t) data, nread);
+    }
 
-  amount_read = __read_nocancel(fd, buffer, length);
-  __close_nocancel_nostatus(fd);
-  return amount_read;
+  return nread;
 }
 
 libc_hidden_def (__getrandom)