diff --git a/NEWS b/NEWS
index e63b69b930..1bbabcf2d6 100644
--- a/NEWS
+++ b/NEWS
@@ -45,6 +45,11 @@ Major new features:
   pthread_attr_setperthreadfs_np and pthread_attr_getperthreadfs_np have
   been added in support of that.
 
+* On Linux, threads can now be created in such a way that they retain
+  per-thread user and group IDs.  The functions
+  pthread_attr_setperthreadids_np and pthread_attr_getperthreadids_np have
+  been added in support of that.
+
 Deprecated and removed features, and other changes affecting compatibility:
 
 * The copy_file_range function fails with ENOSYS if the kernel does not
diff --git a/manual/threads.texi b/manual/threads.texi
index 827ecb0929..a64d535afa 100644
--- a/manual/threads.texi
+++ b/manual/threads.texi
@@ -698,11 +698,12 @@ This property in question is thread-specific.
 
 The @code{PTHREAD_PER_THREAD_NP} flag is sticky, in the sense that all
 threads created by a thread created with this flag have per-thread
-properties, even if they are created with the matching thread
-attribute set to the @code{PTHREAD_PER_PROCESS_NP} flag.  If an
-application wants to create new threads sharing properties with the
-main thread, it should create a service thread early (perhaps from an
-ELF constructor) and create these threads using this service thread.
+properties of the requested kind, even if they are created with the
+matching thread attribute set to the @code{PTHREAD_PER_PROCESS_NP}
+flag.  If an application wants to create new threads sharing
+properties with the main thread, it should create a service thread
+early (perhaps from an ELF constructor) and create these threads using
+this service thread.
 
 Per-thread properties can be set and examined for an attribute using
 the functions below.
@@ -764,6 +765,58 @@ Obtain the per-thread status of the file system properties in
 This function is a GNU extension and specific to Linux.
 @end deftypefun
 
+@deftypefun int pthread_attr_setperthreadids_np (pthread_attr_t *@var{attr}, int @var{scope})
+@standards{GNU, pthread.h}
+@safety{@prelim{}@mtsafe{}@assafe{}@acsafe{}}
+Change whether the following properties related to file system access
+are made thread-specific when a new thread is created using the
+attribute @var{attr}:
+
+@itemize @bullet
+@item
+@cindex per-thread user ID
+@cindex thread-specific user ID
+real, effective and saved user ID (as returned by the
+@code{getresuid} function)
+
+@item
+@cindex per-thread group ID
+@cindex thread-specific group ID
+real, effective and saved group ID (as returned by the
+@code{getresgid} function)
+
+@item
+supplementary group list (as returned by the @code{getgroups}
+function)
+@end itemize
+
+This function returns zero on success.  @var{scope} must be one of the
+constants @code{PTHREAD_PER_PROCESS_NP} or
+@code{PTHREAD_PER_THREAD_NP}, otherwise the function returns
+@code{EINVAL}.
+
+If @var{scope} is @code{PTHREAD_PER_THREAD_NP}, the attribute will
+cause the IDs listed above to be specific to the thread.  The initial
+values of these IDs are copied from the creating thread, at thread
+creation time.
+
+If a thread that has been created with the
+@code{PTHREAD_PER_THREAD_NP} flag creates further threads, these
+threads are implicitly created with the @code{PTHREAD_PER_THREAD_NP}
+flag, ignoring the value of this thread creation attribute.
+
+This function is a GNU extension and specific to Linux.
+@end deftypefun
+
+@deftypefun int pthread_attr_getperthreadids_np (pthread_attr_t *restrict @var{attr}, int *restrict @var{scope})
+@standards{GNU, pthread.h}
+@safety{@prelim{}@mtsafe{}@assafe{}@acsafe{}}
+Obtain the per-thread status of user and group IDs in @var{attr} and
+store it in the location @var{scope}.
+
+This function is a GNU extension and specific to Linux.
+@end deftypefun
+
 @c FIXME these are undocumented:
 @c pthread_atfork
 @c pthread_attr_destroy
diff --git a/nptl/Makefile b/nptl/Makefile
index 1374838339..52913cc2f0 100644
--- a/nptl/Makefile
+++ b/nptl/Makefile
@@ -31,7 +31,8 @@ routines = alloca_cutoff forward libc-lowlevellock libc-cancellation \
 	   libc-cleanup libc_pthread_init libc_multiple_threads \
 	   register-atfork pthread_atfork pthread_self thrd_current \
 	   thrd_equal thrd_sleep thrd_yield \
-	   pthread_attr_setperthreadfs_np pthread_attr_getperthreadfs_np
+	   pthread_attr_setperthreadfs_np pthread_attr_getperthreadfs_np \
+	   pthread_attr_setperthreadids_np pthread_attr_getperthreadids_np
 shared-only-routines = forward
 static-only-routines = pthread_atfork
 
@@ -337,6 +338,9 @@ xtests = tst-setuid1 tst-setuid1-static tst-setuid2 \
 # and then cause the make process to fail too, see bug 24537.
 xtests += tst-eintr1
 
+# This thread calls various set*id functions.
+xtests += tst-pthread-perthreadids
+
 test-srcs = tst-oddstacklimit
 
 # Test expected to fail on most targets (except x86_64) due to bug
diff --git a/nptl/Versions b/nptl/Versions
index 817fec04f3..bedc73878e 100644
--- a/nptl/Versions
+++ b/nptl/Versions
@@ -35,6 +35,8 @@ libc {
   GLIBC_2.30 {
     pthread_attr_setperthreadfs_np;
     pthread_attr_getperthreadfs_np;
+    pthread_attr_setperthreadids_np;
+    pthread_attr_getperthreadids_np;
   }
   GLIBC_PRIVATE {
     __libc_alloca_cutoff;
diff --git a/nptl/allocatestack.c b/nptl/allocatestack.c
index fcbc46f0d7..9d238a001b 100644
--- a/nptl/allocatestack.c
+++ b/nptl/allocatestack.c
@@ -1067,6 +1067,14 @@ __nptl_setxid_error (struct xid_command *cmdp, int error)
   while (atomic_compare_and_exchange_bool_acq (&cmdp->error, error, -1));
 }
 
+/* The current thread and threads with per-thread user and group IDs
+   are not part of the setxid broadcast.  */
+static inline bool
+thread_excluded_from_setxid_broadcast (struct pthread *self, struct pthread *t)
+{
+  return t == self || (t->flags & ATTR_FLAG_PERTHREADIDS);
+}
+
 int
 attribute_hidden
 __nptl_setxid (struct xid_command *cmdp)
@@ -1080,13 +1088,14 @@ __nptl_setxid (struct xid_command *cmdp)
   cmdp->error = -1;
 
   struct pthread *self = THREAD_SELF;
+  assert (!nptl_current_thread_has_separate_ids ());
 
   /* Iterate over the list with system-allocated threads first.  */
   list_t *runp;
   list_for_each (runp, &stack_used)
     {
       struct pthread *t = list_entry (runp, struct pthread, list);
-      if (t == self)
+      if (thread_excluded_from_setxid_broadcast (self, t))
 	continue;
 
       setxid_mark_thread (cmdp, t);
@@ -1096,7 +1105,7 @@ __nptl_setxid (struct xid_command *cmdp)
   list_for_each (runp, &__stack_user)
     {
       struct pthread *t = list_entry (runp, struct pthread, list);
-      if (t == self)
+      if (thread_excluded_from_setxid_broadcast (self, t))
 	continue;
 
       setxid_mark_thread (cmdp, t);
@@ -1112,7 +1121,7 @@ __nptl_setxid (struct xid_command *cmdp)
       list_for_each (runp, &stack_used)
 	{
 	  struct pthread *t = list_entry (runp, struct pthread, list);
-	  if (t == self)
+	  if (thread_excluded_from_setxid_broadcast (self, t))
 	    continue;
 
 	  signalled += setxid_signal_thread (cmdp, t);
@@ -1121,7 +1130,7 @@ __nptl_setxid (struct xid_command *cmdp)
       list_for_each (runp, &__stack_user)
 	{
 	  struct pthread *t = list_entry (runp, struct pthread, list);
-	  if (t == self)
+	  if (thread_excluded_from_setxid_broadcast (self, t))
 	    continue;
 
 	  signalled += setxid_signal_thread (cmdp, t);
@@ -1142,7 +1151,7 @@ __nptl_setxid (struct xid_command *cmdp)
   list_for_each (runp, &stack_used)
     {
       struct pthread *t = list_entry (runp, struct pthread, list);
-      if (t == self)
+      if (thread_excluded_from_setxid_broadcast (self, t))
 	continue;
 
       setxid_unmark_thread (cmdp, t);
@@ -1151,7 +1160,7 @@ __nptl_setxid (struct xid_command *cmdp)
   list_for_each (runp, &__stack_user)
     {
       struct pthread *t = list_entry (runp, struct pthread, list);
-      if (t == self)
+      if (thread_excluded_from_setxid_broadcast (self, t))
 	continue;
 
       setxid_unmark_thread (cmdp, t);
diff --git a/nptl/pthreadP.h b/nptl/pthreadP.h
index 66527d8f2d..719bae5173 100644
--- a/nptl/pthreadP.h
+++ b/nptl/pthreadP.h
@@ -598,6 +598,14 @@ check_stacksize_attr (size_t st)
   return EINVAL;
 }
 
+/* Return true if the current thread has per-thread user and group
+   IDS.  */
+static inline bool
+nptl_current_thread_has_separate_ids (void)
+{
+  return THREAD_SELF->flags & ATTR_FLAG_PERTHREADIDS;
+}
+
 #define ASSERT_TYPE_SIZE(type, size) 					\
   _Static_assert (sizeof (type) == size,				\
 		  "sizeof (" #type ") != " #size)
diff --git a/nptl/pthread_attr_getperthreadids_np.c b/nptl/pthread_attr_getperthreadids_np.c
new file mode 100644
index 0000000000..18f88f1f3e
--- /dev/null
+++ b/nptl/pthread_attr_getperthreadids_np.c
@@ -0,0 +1,32 @@
+/* Read the per-thread user/group IDs flag in thread attributes.
+   Copyright (C) 2019 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <pthread.h>
+#include <internaltypes.h>
+
+int
+pthread_attr_getperthreadids_np (const pthread_attr_t *__restrict attr,
+                                int *__restrict scope)
+{
+  struct pthread_attr *iattr = (struct pthread_attr *) attr;
+  if (iattr->flags & ATTR_FLAG_PERTHREADIDS)
+    *scope = PTHREAD_PER_THREAD_NP;
+  else
+    *scope = PTHREAD_PER_PROCESS_NP;
+  return 0;
+}
diff --git a/nptl/pthread_attr_setperthreadids_np.c b/nptl/pthread_attr_setperthreadids_np.c
new file mode 100644
index 0000000000..5385ab0bae
--- /dev/null
+++ b/nptl/pthread_attr_setperthreadids_np.c
@@ -0,0 +1,39 @@
+/* Change the per-thread user/group IDs flag in thread attributes.
+   Copyright (C) 2019 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <pthread.h>
+#include <internaltypes.h>
+#include <errno.h>
+
+int
+pthread_attr_setperthreadids_np (pthread_attr_t *attr, int scope)
+{
+  struct pthread_attr *iattr = (struct pthread_attr *) attr;
+  switch (scope)
+    {
+    case PTHREAD_PER_PROCESS_NP:
+      iattr->flags &= ~ATTR_FLAG_PERTHREADIDS;
+      return 0;
+      break;
+    case PTHREAD_PER_THREAD_NP:
+      iattr->flags |= ATTR_FLAG_PERTHREADIDS;
+      return 0;
+    default:
+      return EINVAL;
+    }
+}
diff --git a/nptl/tst-pthread-perthreadids.c b/nptl/tst-pthread-perthreadids.c
new file mode 100644
index 0000000000..05ed098944
--- /dev/null
+++ b/nptl/tst-pthread-perthreadids.c
@@ -0,0 +1,541 @@
+/* Test per-thread user and group IDs.
+   Copyright (C) 2019 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <array_length.h>
+#include <errno.h>
+#include <grp.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <support/check.h>
+#include <support/namespace.h>
+#include <support/support.h>
+#include <support/test-driver.h>
+#include <support/xthread.h>
+#include <support/xunistd.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+/* Return true if the thread has per-thread file system attributes.
+   Note: This function calls pthread_getattr_np on THR, so the caller
+   has to ensure that the thread is still running (and not merely
+   joinable).  */
+static bool
+perthread_flag (pthread_t thr)
+{
+  pthread_attr_t attr;
+  int ret = pthread_getattr_np (thr, &attr);
+  if (ret != 0)
+    {
+      errno = ret;
+      FAIL_EXIT1 ("pthread_getattr_np: %m");
+    }
+  int flag = -1;
+  pthread_attr_getperthreadids_np (&attr, &flag);
+  if (flag != PTHREAD_PER_THREAD_NP)
+    TEST_COMPARE (flag, PTHREAD_PER_PROCESS_NP);
+  xpthread_attr_destroy (&attr);
+  return flag == PTHREAD_PER_THREAD_NP;
+}
+
+/* Which set*id function to call.  */
+enum operation
+{
+ /* No function is called, but the thread will observe changes made by
+    another thread.  */
+ OP_NONE,
+
+ OP_SETUID,
+ OP_SETEUID,
+ OP_SETREUID,
+ OP_SETRESUID,
+ OP_SETGID,
+ OP_SETEGID,
+ OP_SETREGID,
+ OP_SETRESGID,
+ OP_SETGROUPS,
+};
+
+/* Convert the operation to a descriptive string.  */
+static const char *
+operation_string (enum operation op)
+{
+  switch (op)
+    {
+    case OP_NONE:
+      return "<none>";
+    case OP_SETUID:
+      return "OP_SETUID";
+    case OP_SETEUID:
+      return "OP_SETEUID";
+    case OP_SETREUID:
+      return "OP_SETREUID";
+    case OP_SETRESUID:
+      return "OP_SETRESUID";
+    case OP_SETGID:
+      return "OP_SETGID";
+    case OP_SETEGID:
+      return "OP_SETEGID";
+    case OP_SETREGID:
+      return "OP_SETREGID";
+    case OP_SETRESGID:
+      return "OP_SETRESGID";
+    case OP_SETGROUPS:
+      return "OP_SETGROUPS";
+    }
+
+  FAIL_EXIT1 ("invalid operation: %d", (int) op);
+}
+
+/* One test case to perform.  */
+struct test_case
+{
+  enum operation op;
+  int args[3];
+
+  /* Expected UIDs and GIDs are only used if the current thread has
+     made changes.  */
+  uid_t expected_uid[3];
+  gid_t expected_gid[3];
+};
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic error "-Wmissing-field-initializers"
+static const struct test_case test_cases[] =
+  {
+   /* op             args         expected_gid  expected_gid.   */
+   { OP_NONE,      {  0,  0,  0 }, { 0, 0, 0 }, {  0,  0,  0} },
+
+   { OP_SETUID,    {  1,  0,  0 }, { 1, 1, 1 }, {  0,  0,  0 } },
+   { OP_SETEUID,   {  2,  0,  0 }, { 0, 2, 0 }, {  0,  0,  0 } },
+   { OP_SETREUID,  {  3,  4,  0 }, { 3, 4, 4 }, {  0,  0,  0 } },
+   { OP_SETRESUID, {  3,  4,  5 }, { 3, 4, 5 }, {  0,  0,  0 } },
+
+   { OP_SETGID,    {  6,  0,  0 }, { 0, 0, 0 }, {  6,  6,  6 } },
+   { OP_SETEGID,   {  7,  0,  0 }, { 0, 0, 0 }, {  0,  7,  0 } },
+   { OP_SETREGID,  {  8,  9,  0 }, { 0, 0, 0 }, {  8,  9,  9 } },
+   { OP_SETRESGID, { 10, 11, 12 }, { 0, 0, 0 }, { 10, 11, 12 } },
+
+   { OP_SETGROUPS, { -1,  0,  0 }, { 0, 0, 0 }, {  0,  0,  0 } },
+   { OP_SETGROUPS, { 13, -1,  0 }, { 0, 0, 0 }, {  0,  0,  0 } },
+   { OP_SETGROUPS, { 13, 14, -1 }, { 0, 0, 0 }, {  0,  0,  0 } },
+   { OP_SETGROUPS, { 13, 14, 15 }, { 0, 0, 0 }, {  0,  0,  0 } },
+
+   /* Final round of checks.  */
+   { OP_NONE,      {  0,  0,  0 }, { 0, 0, 0 }, {  0,  0,  0 } },
+  };
+#pragma GCC diagnostic pop
+
+/* Determine the number of supplementary groups in the test case.  */
+static size_t
+supplemetary_count (const struct test_case *test)
+{
+  TEST_COMPARE (test->op, OP_SETGROUPS);
+  size_t count = 0;
+  while (count < array_length (test->args))
+    {
+      if (test->args[count] < 0)
+        break;
+      ++count;
+    }
+  return count;
+}
+
+/* Perform the actions in the test case.  */
+static void
+test_case_run (const struct test_case *test)
+{
+  int ret = -1;
+  switch (test->op)
+    {
+    case OP_NONE:
+      return;
+
+    case OP_SETUID:
+      ret = setuid (test->args[0]);
+      break;
+    case OP_SETEUID:
+      ret = seteuid (test->args[0]);
+      break;
+    case OP_SETREUID:
+      ret = setreuid (test->args[0], test->args[1]);
+      break;
+    case OP_SETRESUID:
+      ret = setresuid (test->args[0], test->args[1], test->args[2]);
+      break;
+
+    case OP_SETGID:
+      ret = setgid (test->args[0]);
+      break;
+    case OP_SETEGID:
+      ret = setegid (test->args[0]);
+      break;
+    case OP_SETREGID:
+      ret = setregid (test->args[0], test->args[1]);
+      break;
+    case OP_SETRESGID:
+      ret = setresgid (test->args[0], test->args[1], test->args[2]);
+      break;
+
+    case OP_SETGROUPS:
+      {
+        gid_t groups[] = { test->args[0], test->args[1], test->args[2] };
+        ret = setgroups (supplemetary_count (test), groups);
+      }
+    }
+
+  if (ret != 0)
+    FAIL_EXIT1 ("%s (%d, %d, %d): %m (%d)",
+                operation_string (test->op),
+                test->args[0], test->args[1], test->args[2], errno);
+}
+
+/* Used to synchronize between threads changing UIDs/GIDs.  */
+static pthread_barrier_t barrier;
+
+/* Used to avoid interleaving the checking phase between threads.  */
+static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+
+/* Argument to the thread.  */
+struct thread_argument
+{
+  size_t thread_index;
+
+  /* Do not actually perform the operation, only verify the result.
+     Used for threads participating in the setxid broadcast.  */
+  bool suppress_operation;
+
+  /* Run the actual test in a newly created thread, with default
+     attributes.  */
+  bool indirect;
+
+  /* Thread is created with per-thread attributes.  */
+  bool perthread;
+};
+
+/* Prepare a heap-allocated thread argument which the started thread
+   should free.  */
+static struct thread_argument *
+create_thread_argument (struct thread_argument arg)
+{
+  struct thread_argument *result = xmalloc (sizeof (*result));
+  *result = arg;
+  return result;
+}
+
+/* An actual test thread.  CLOSURE is a pointer to struct
+   thread_argument, and perform the test according to this
+   information.  */
+static void *
+threadfunc (void *closure)
+{
+  struct thread_argument *arg = closure;
+
+  TEST_COMPARE (perthread_flag (pthread_self ()), arg->perthread);
+
+  if (arg->indirect)
+    {
+      /* Only indirect once.  */
+      arg->indirect = false;
+      /* Use the default attributes (NULL).  This verifies that the
+         per-thread scope is sticky.  */
+      return xpthread_join (xpthread_create (NULL, threadfunc, arg));
+    }
+
+  const struct test_case *expected = &test_cases[0];
+  gid_t expected_supplementary[3] = { -1, -1, -1 };
+  int expected_supplementary_count = 0;
+
+  /* (1) Wait until all threads have started up.  */
+  xpthread_barrier_wait (&barrier);
+
+  for (size_t test_index = 0; test_index < array_length (test_cases);
+       ++test_index)
+    {
+      if (test_index == arg->thread_index)
+        {
+          if (test_verbose > 0)
+            {
+              if (arg->suppress_operation)
+                printf ("info: thread %zu suppressing operation\n",
+                        test_index);
+              else
+                printf ("info: thread %zu performing operation\n",
+                        test_index);
+            }
+          if (!arg->suppress_operation)
+            test_case_run (&test_cases[test_index]);
+
+          expected = &test_cases[test_index];
+          if (test_cases[test_index].op == OP_SETGROUPS)
+            {
+              expected_supplementary_count
+                = supplemetary_count (&test_cases[test_index]);
+              for (int i = 0; i < expected_supplementary_count; ++i)
+                expected_supplementary[i] = test_cases[test_index].args[i];
+            }
+        }
+
+      /* (2) Wait until all threads have run the requested test.  */
+      xpthread_barrier_wait (&barrier);
+
+      xpthread_mutex_lock (&mutex);
+
+      if (test_verbose > 0)
+        printf ("info: checking phase for thread %zu, test %zu\n",
+                arg->thread_index, test_index);
+      uid_t actual_uid[3];
+      xgetresuid (&actual_uid[0], &actual_uid[1], &actual_uid[2]);
+      TEST_COMPARE (actual_uid[0], expected->expected_uid[0]);
+      TEST_COMPARE (actual_uid[1], expected->expected_uid[1]);
+      TEST_COMPARE (actual_uid[2], expected->expected_uid[2]);
+      gid_t actual_gid[3];
+      xgetresgid (&actual_gid[0], &actual_gid[1], &actual_gid[2]);
+      TEST_COMPARE (actual_gid[0], expected->expected_gid[0]);
+      TEST_COMPARE (actual_gid[1], expected->expected_gid[1]);
+      TEST_COMPARE (actual_gid[2], expected->expected_gid[2]);
+
+      gid_t actual_supplementary[3];
+      gid_t actual_supplementary_count
+        = getgroups (array_length (actual_supplementary),
+                     actual_supplementary);
+      TEST_COMPARE (actual_supplementary_count, expected_supplementary_count);
+      if (actual_supplementary_count > 0)
+        TEST_COMPARE (actual_supplementary[0], expected_supplementary[0]);
+      if (actual_supplementary_count > 1)
+        TEST_COMPARE (actual_supplementary[1], expected_supplementary[1]);
+      if (actual_supplementary_count > 2)
+        TEST_COMPARE (actual_supplementary[2], expected_supplementary[2]);
+
+      xpthread_mutex_unlock (&mutex);
+
+      /* (3) Wait until all threads have finished checking their view
+         of the results.  */
+      xpthread_barrier_wait (&barrier);
+    }
+
+  free (arg);
+  return NULL;
+}
+
+/* Used to create threads with per-thread user/group IDs.  */
+static pthread_attr_t attr_perthreadids;
+
+/* Test which verifies that per-thread IDs are thread-specific.  */
+static void
+check_perthread (bool indirect)
+{
+  if (test_verbose > 0)
+    printf ("info: testing per-thread IDs, %s indirection\n",
+            indirect ? "with" : "without");
+  TEST_VERIFY (!perthread_flag (pthread_self ()));
+
+  /* The main thread and another shared thread count as two extra
+     threads.  */
+  xpthread_barrier_init (&barrier, NULL, array_length (test_cases) + 2);
+
+  pthread_t perthread_threads[array_length (test_cases)];
+  /* Use thread index zero with OP_NONE for checking only.  */
+  pthread_t shared_thread
+    = xpthread_create (NULL, threadfunc,
+                       create_thread_argument ((struct thread_argument) { }));
+  /* Thread is still runing because it waits on barrier.  */
+  TEST_VERIFY (!perthread_flag (shared_thread));
+
+  for (size_t i = 0; i < array_length (test_cases); ++i)
+    {
+      struct thread_argument *arg
+        = create_thread_argument ((struct thread_argument) {
+            .thread_index = i, .indirect = indirect, .perthread = true });
+      perthread_threads[i]
+        = xpthread_create (&attr_perthreadids, threadfunc, arg);
+      /* Thread is still runing because it waits on barrier (or it is
+         blocked joining a thread which does).  */
+      TEST_VERIFY (perthread_flag (perthread_threads[i]));
+    }
+  /* Use thread_index 0 with OP_NONE for checking that the main thread
+     is unchanged.  (This also waits on the barrier.)  */
+  threadfunc (create_thread_argument ((struct thread_argument) { }));
+  for (size_t i = 0; i < array_length (test_cases); ++i)
+    xpthread_join (perthread_threads[i]);
+  xpthread_join (shared_thread);
+
+  xpthread_barrier_destroy (&barrier);
+}
+
+/* Closure arguments to the subprocess.  */
+struct subprocess_argument
+{
+  size_t broadcast_test_index;
+  bool broadcast_from_main;
+  bool indirect;
+};
+
+/* Setting the broadcast UID is destructive, so we need to run it in a
+   subprocess.  */
+static void
+subprocess (void *closure)
+{
+  struct subprocess_argument *arg = closure;
+
+  if (test_verbose > 0)
+    printf ("info: testing broadcasting test case %zu,"
+            " %sbroadcasting from main, %s indirection\n",
+            arg->broadcast_test_index,
+            arg->broadcast_from_main ? "" : "not ",
+            arg->indirect ? "with" : "without");
+
+  /* Main thread and two other shared threads are extras.  One
+     per-thread test is skipped, so there are two extra threads.  */
+  xpthread_barrier_init (&barrier, NULL, array_length (test_cases) + 2);
+
+  pthread_t perthread_threads[array_length (test_cases)];
+  /* Use thread index zero for no-op checking.  */
+  pthread_t shared_threads[2];
+  {
+    struct thread_argument thread_arg =
+      {
+       .thread_index = arg->broadcast_test_index,
+       .suppress_operation =arg->broadcast_from_main,
+      };
+    shared_threads[0]
+      = xpthread_create (NULL, threadfunc,
+                         create_thread_argument (thread_arg));
+  }
+  {
+    struct thread_argument thread_arg =
+      {
+       .thread_index = arg->broadcast_test_index,
+       .suppress_operation = true,
+      };
+    shared_threads[1]
+      = xpthread_create (NULL, threadfunc,
+                         create_thread_argument (thread_arg));
+  }
+
+  for (size_t i = 0; i < array_length (test_cases); ++i)
+    /* Skip the test which uses broadcasting; it does not use the
+       per-thread IDs.  */
+    if (i != arg->broadcast_test_index)
+      {
+        struct thread_argument thread_arg =
+          {
+           .thread_index = i,
+           .indirect = arg->indirect,
+           .perthread = true,
+          };
+        perthread_threads[i]
+          = xpthread_create (&attr_perthreadids, threadfunc,
+                             create_thread_argument (thread_arg));
+      }
+
+  /* Call threadfunc from the main thread.  If not broadcasting from
+     main, only perform checking.  */
+  {
+    struct thread_argument thread_arg =
+      {
+       .thread_index = arg->broadcast_test_index,
+       .suppress_operation = !arg->broadcast_from_main,
+       .indirect = arg->indirect,
+      };
+    threadfunc (create_thread_argument (thread_arg));
+  }
+  for (size_t i = 0; i < array_length (test_cases); ++i)
+    /* Skip the test which uses broadcasting.  */
+    if (i != arg->broadcast_test_index)
+      xpthread_join (perthread_threads[i]);
+  xpthread_join (shared_threads[0]);
+  xpthread_join (shared_threads[1]);
+
+  xpthread_barrier_destroy (&barrier);
+}
+
+static int
+do_test (void)
+{
+  if (setuid (0) != 0)
+    FAIL_EXIT1 ("setuid (0): %m");
+  if (setgid (0) != 0)
+    FAIL_EXIT1 ("setgid (0): %m");
+  if (setgroups (0, NULL) != 0)
+    FAIL_EXIT1 ("setgroups (0, NULL): %m");
+
+  xpthread_attr_init (&attr_perthreadids);
+  {
+    /* Test: Default is PTHREAD_PER_PROCESS_NP.  */
+    int scope = -1;
+    TEST_COMPARE (pthread_attr_getperthreadids_np (&attr_perthreadids, &scope),
+                  0);
+    TEST_COMPARE (scope, PTHREAD_PER_PROCESS_NP);
+
+    /* Test: The getter shows the effect of the setter.  */
+    TEST_COMPARE (pthread_attr_setperthreadids_np (&attr_perthreadids,
+                                                  PTHREAD_PER_THREAD_NP), 0);
+    scope = -1;
+    TEST_COMPARE (pthread_attr_getperthreadids_np (&attr_perthreadids, &scope),
+                  0);
+    TEST_COMPARE (scope, PTHREAD_PER_THREAD_NP);
+
+    /* Test: Invalid scope values result in an error, without a
+       change.  */
+    TEST_COMPARE (pthread_attr_setperthreadids_np (&attr_perthreadids, 2),
+                  EINVAL);
+    scope = -1;
+    TEST_COMPARE (pthread_attr_getperthreadids_np (&attr_perthreadids, &scope),
+                  0);
+    TEST_COMPARE (scope, PTHREAD_PER_THREAD_NP);
+  }
+
+  /* Test both the direct and indirect case.  Direct is when test
+     threads are launched with a per-thread attribute.  Indirect is
+     when a thread is first launched with the a per-thread attribute,
+     but the actual test runs in another sub-thread, created with a
+     NULL attribute.  */
+  for (int indirect = 0; indirect < 2; ++indirect)
+    {
+      check_perthread (indirect);
+
+      /* Test two scenarios, one where set*id function is called from
+         the main thread, and other one where it is called from
+         another thread.  */
+      for (int broadcast_from_main = 0; broadcast_from_main < 2;
+           ++broadcast_from_main)
+        /* Perform each of the test cases once, using a broadcast
+           set*id operation across multiple threads (those threads
+           which have per-process scope).  */
+        for (size_t broadcast_test_index = 0;
+             broadcast_test_index < array_length (test_cases);
+             ++broadcast_test_index)
+          {
+            struct subprocess_argument arg =
+              {
+               .broadcast_test_index = broadcast_test_index,
+               .broadcast_from_main = broadcast_from_main,
+               .indirect = indirect,
+              };
+            support_isolate_in_subprocess (subprocess, &arg);
+          }
+    }
+
+  xpthread_attr_destroy (&attr_perthreadids);
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/support/Makefile b/support/Makefile
index 774b0a692a..1ded323015 100644
--- a/support/Makefile
+++ b/support/Makefile
@@ -94,6 +94,8 @@ libsupport-routines = \
   xfopen \
   xfork \
   xftruncate \
+  xgetresgid \
+  xgetresuid \
   xgetsockname \
   xlisten \
   xlseek \
diff --git a/support/xgetresgid.c b/support/xgetresgid.c
new file mode 100644
index 0000000000..22990fa633
--- /dev/null
+++ b/support/xgetresgid.c
@@ -0,0 +1,27 @@
+/* getresgid with error checking.
+   Copyright (C) 2019 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <support/check.h>
+#include <support/xunistd.h>
+
+void
+xgetresgid (gid_t *real, gid_t *effective, gid_t *saved)
+{
+  if (getresgid (real, effective, saved) != 0)
+    FAIL_EXIT1 ("getresgid: %m");
+}
diff --git a/support/xgetresuid.c b/support/xgetresuid.c
new file mode 100644
index 0000000000..b0cd4e938f
--- /dev/null
+++ b/support/xgetresuid.c
@@ -0,0 +1,27 @@
+/* getresuid with error checking.
+   Copyright (C) 2019 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <support/check.h>
+#include <support/xunistd.h>
+
+void
+xgetresuid (uid_t *real, uid_t *effective, uid_t *saved)
+{
+  if (getresuid (real, effective, saved) != 0)
+    FAIL_EXIT1 ("getresuid: %m");
+}
diff --git a/support/xunistd.h b/support/xunistd.h
index b470d99be1..2ad2e9a55c 100644
--- a/support/xunistd.h
+++ b/support/xunistd.h
@@ -46,6 +46,8 @@ long xsysconf (int name);
 long long xlseek (int fd, long long offset, int whence);
 void xftruncate (int fd, long long length);
 void xsymlink (const char *target, const char *linkpath);
+void xgetresuid (uid_t *, uid_t *, uid_t *);
+void xgetresgid (gid_t *, gid_t *, gid_t *);
 
 /* Equivalent of "mkdir -p".  */
 void xmkdirp (const char *, mode_t);
diff --git a/sysdeps/nptl/internaltypes.h b/sysdeps/nptl/internaltypes.h
index 5ad56908b5..0e8b2b3f65 100644
--- a/sysdeps/nptl/internaltypes.h
+++ b/sysdeps/nptl/internaltypes.h
@@ -49,6 +49,7 @@ struct pthread_attr
 #define ATTR_FLAG_SCHED_SET		0x0020
 #define ATTR_FLAG_POLICY_SET		0x0040
 #define ATTR_FLAG_PERTHREADFS		0x0080
+#define ATTR_FLAG_PERTHREADIDS		0x0100
 
 /* These flags are not copied from the thread attribute at
    pthread_create time.  */
@@ -59,7 +60,7 @@ struct pthread_attr
    pthread_create even if they are not specified in the thread
    attribute.  */
 #define ATTR_FLAGS_INHERITED \
-  ATTR_FLAG_PERTHREADFS
+  (ATTR_FLAG_PERTHREADFS | ATTR_FLAG_PERTHREADIDS)
 
 
 /* Mutex attribute data structure.  */
diff --git a/sysdeps/nptl/pthread.h b/sysdeps/nptl/pthread.h
index 4b6a90f131..2ba60867e0 100644
--- a/sysdeps/nptl/pthread.h
+++ b/sysdeps/nptl/pthread.h
@@ -430,6 +430,24 @@ int pthread_attr_getperthreadfs_np (const pthread_attr_t *__restrict __attr,
 				    int *__restrict __scope)
   __THROW __nonnull ((1, 2));
 
+/* Control the flag in ATTR whether the thread has its own user and
+   group IDs.  By default, when the real, effective, or saved user or
+   group ID is changed by a thread, this affects the entire process.
+   If a thread is created with this flag set to true, then changing
+   the IDs within that thread will only affect that thread, and user
+   and group ID changes in other threads (whether they have enabled
+   this flag or not) do not affect it.  If a thread has been created
+   with this flag, threads created by it will also have their own,
+   private user and group IDs.  */
+int pthread_attr_setperthreadids_np (pthread_attr_t *__attr, int __scope)
+  __THROW __nonnull ((1));
+
+/* Get the per-thread/per-process scope of user and group IDs from
+   *ATTR and store it in *SCOPE.  */
+int pthread_attr_getperthreadids_np (const pthread_attr_t *__restrict __attr,
+				     int *__restrict __scope)
+  __THROW __nonnull ((1, 2));
+
 /* Get the default attributes used by pthread_create in this process.  */
 extern int pthread_getattr_default_np (pthread_attr_t *__attr)
      __THROW __nonnull ((1));
diff --git a/sysdeps/nptl/setxid.h b/sysdeps/nptl/setxid.h
index 20bf66a5df..bc955e73bb 100644
--- a/sysdeps/nptl/setxid.h
+++ b/sysdeps/nptl/setxid.h
@@ -32,7 +32,8 @@
 # define INLINE_SETXID_SYSCALL(name, nr, args...) \
   ({									\
     int __result;							\
-    if (__builtin_expect (__libc_pthread_functions_init, 0))		\
+    if (__libc_pthread_functions_init					\
+	&& !nptl_current_thread_has_separate_ids ())			\
       {									\
 	struct xid_command __cmd;					\
 	__cmd.syscall_no = __NR_##name;					\
@@ -48,7 +49,8 @@
   ({									\
     extern __typeof (__nptl_setxid) __nptl_setxid __attribute__((weak));\
     int __result;							\
-    if (__glibc_unlikely (__nptl_setxid	!= NULL))			      \
+    if (__nptl_setxid != NULL						\
+	&& !nptl_current_thread_has_separate_ids ())			\
       {									\
 	struct xid_command __cmd;					\
 	__cmd.syscall_no = __NR_##name;					\
diff --git a/sysdeps/unix/sysv/linux/aarch64/libc.abilist b/sysdeps/unix/sysv/linux/aarch64/libc.abilist
index 2635e16f5e..03507783d3 100644
--- a/sysdeps/unix/sysv/linux/aarch64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/aarch64/libc.abilist
@@ -2144,6 +2144,8 @@ GLIBC_2.29 posix_spawn_file_actions_addfchdir_np F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
 GLIBC_2.30 pthread_attr_getperthreadfs_np F
+GLIBC_2.30 pthread_attr_getperthreadids_np F
 GLIBC_2.30 pthread_attr_setperthreadfs_np F
+GLIBC_2.30 pthread_attr_setperthreadids_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
diff --git a/sysdeps/unix/sysv/linux/alpha/libc.abilist b/sysdeps/unix/sysv/linux/alpha/libc.abilist
index 12227b9800..0e24e9a39d 100644
--- a/sysdeps/unix/sysv/linux/alpha/libc.abilist
+++ b/sysdeps/unix/sysv/linux/alpha/libc.abilist
@@ -2219,7 +2219,9 @@ GLIBC_2.30 __nldbl_warnx F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
 GLIBC_2.30 pthread_attr_getperthreadfs_np F
+GLIBC_2.30 pthread_attr_getperthreadids_np F
 GLIBC_2.30 pthread_attr_setperthreadfs_np F
+GLIBC_2.30 pthread_attr_setperthreadids_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 _IO_fprintf F
diff --git a/sysdeps/unix/sysv/linux/arm/libc.abilist b/sysdeps/unix/sysv/linux/arm/libc.abilist
index 6d09148e1d..d69e1acdfa 100644
--- a/sysdeps/unix/sysv/linux/arm/libc.abilist
+++ b/sysdeps/unix/sysv/linux/arm/libc.abilist
@@ -129,7 +129,9 @@ GLIBC_2.29 posix_spawn_file_actions_addfchdir_np F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
 GLIBC_2.30 pthread_attr_getperthreadfs_np F
+GLIBC_2.30 pthread_attr_getperthreadids_np F
 GLIBC_2.30 pthread_attr_setperthreadfs_np F
+GLIBC_2.30 pthread_attr_setperthreadids_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 _Exit F
diff --git a/sysdeps/unix/sysv/linux/csky/libc.abilist b/sysdeps/unix/sysv/linux/csky/libc.abilist
index 5ee091972b..cf2fe8fb5e 100644
--- a/sysdeps/unix/sysv/linux/csky/libc.abilist
+++ b/sysdeps/unix/sysv/linux/csky/libc.abilist
@@ -2088,6 +2088,8 @@ GLIBC_2.29 xprt_unregister F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
 GLIBC_2.30 pthread_attr_getperthreadfs_np F
+GLIBC_2.30 pthread_attr_getperthreadids_np F
 GLIBC_2.30 pthread_attr_setperthreadfs_np F
+GLIBC_2.30 pthread_attr_setperthreadids_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
diff --git a/sysdeps/unix/sysv/linux/hppa/libc.abilist b/sysdeps/unix/sysv/linux/hppa/libc.abilist
index 844eaf539b..2ebaff4e7b 100644
--- a/sysdeps/unix/sysv/linux/hppa/libc.abilist
+++ b/sysdeps/unix/sysv/linux/hppa/libc.abilist
@@ -2040,7 +2040,9 @@ GLIBC_2.3.4 xdr_u_quad_t F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
 GLIBC_2.30 pthread_attr_getperthreadfs_np F
+GLIBC_2.30 pthread_attr_getperthreadids_np F
 GLIBC_2.30 pthread_attr_setperthreadfs_np F
+GLIBC_2.30 pthread_attr_setperthreadids_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 __confstr_chk F
diff --git a/sysdeps/unix/sysv/linux/hppa/pthread.h b/sysdeps/unix/sysv/linux/hppa/pthread.h
index 43ffca5551..0d42953a87 100644
--- a/sysdeps/unix/sysv/linux/hppa/pthread.h
+++ b/sysdeps/unix/sysv/linux/hppa/pthread.h
@@ -407,6 +407,24 @@ int pthread_attr_getperthreadfs_np (const pthread_attr_t *__restrict __attr,
 				    int *__restrict __scope)
   __THROW __nonnull ((1, 2));
 
+/* Control the flag in ATTR whether the thread has its own user and
+   group IDs.  By default, when the real, effective, or saved user or
+   group ID is changed by a thread, this affects the entire process.
+   If a thread is created with this flag set to true, then changing
+   the IDs within that thread will only affect that thread, and user
+   and group ID changes in other threads (whether they have enabled
+   this flag or not) do not affect it.  If a thread has been created
+   with this flag, threads created by it will also have their own,
+   private user and group IDs.  */
+void pthread_attr_setperthreadids_np (pthread_attr_t *__attr, int __scope)
+  __THROW __nonnull ((1));
+
+/* Get the per-thread/per-process scope of file system attributes from
+   *ATTR and store it in SCOPE.  */
+int pthread_attr_getperthreadids_np (const pthread_attr_t *__restrict __attr,
+				     int *__restrict __scope)
+  __THROW __nonnull ((1, 2));
+
 /* Get the default attributes used by pthread_create in this process.  */
 extern int pthread_getattr_default_np (pthread_attr_t *__attr)
      __THROW __nonnull ((1));
diff --git a/sysdeps/unix/sysv/linux/i386/libc.abilist b/sysdeps/unix/sysv/linux/i386/libc.abilist
index efd8cb1685..6a84bb91af 100644
--- a/sysdeps/unix/sysv/linux/i386/libc.abilist
+++ b/sysdeps/unix/sysv/linux/i386/libc.abilist
@@ -2206,7 +2206,9 @@ GLIBC_2.3.4 xdr_u_quad_t F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
 GLIBC_2.30 pthread_attr_getperthreadfs_np F
+GLIBC_2.30 pthread_attr_getperthreadids_np F
 GLIBC_2.30 pthread_attr_setperthreadfs_np F
+GLIBC_2.30 pthread_attr_setperthreadids_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 __confstr_chk F
diff --git a/sysdeps/unix/sysv/linux/ia64/libc.abilist b/sysdeps/unix/sysv/linux/ia64/libc.abilist
index 7f34f4d1e6..3e5144c493 100644
--- a/sysdeps/unix/sysv/linux/ia64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/ia64/libc.abilist
@@ -2072,7 +2072,9 @@ GLIBC_2.3.4 xdr_u_quad_t F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
 GLIBC_2.30 pthread_attr_getperthreadfs_np F
+GLIBC_2.30 pthread_attr_getperthreadids_np F
 GLIBC_2.30 pthread_attr_setperthreadfs_np F
+GLIBC_2.30 pthread_attr_setperthreadids_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 __confstr_chk F
diff --git a/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist b/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist
index 34d5f6c91a..63f830cee8 100644
--- a/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist
+++ b/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist
@@ -130,7 +130,9 @@ GLIBC_2.29 posix_spawn_file_actions_addfchdir_np F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
 GLIBC_2.30 pthread_attr_getperthreadfs_np F
+GLIBC_2.30 pthread_attr_getperthreadids_np F
 GLIBC_2.30 pthread_attr_setperthreadfs_np F
+GLIBC_2.30 pthread_attr_setperthreadids_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 _Exit F
diff --git a/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist b/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist
index 71882c6e23..b90e15cf69 100644
--- a/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist
+++ b/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist
@@ -2149,7 +2149,9 @@ GLIBC_2.3.4 xdr_u_quad_t F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
 GLIBC_2.30 pthread_attr_getperthreadfs_np F
+GLIBC_2.30 pthread_attr_getperthreadids_np F
 GLIBC_2.30 pthread_attr_setperthreadfs_np F
+GLIBC_2.30 pthread_attr_setperthreadids_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 __confstr_chk F
diff --git a/sysdeps/unix/sysv/linux/microblaze/libc.abilist b/sysdeps/unix/sysv/linux/microblaze/libc.abilist
index 4f605fa67a..99516e20f7 100644
--- a/sysdeps/unix/sysv/linux/microblaze/libc.abilist
+++ b/sysdeps/unix/sysv/linux/microblaze/libc.abilist
@@ -2136,6 +2136,8 @@ GLIBC_2.29 posix_spawn_file_actions_addfchdir_np F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
 GLIBC_2.30 pthread_attr_getperthreadfs_np F
+GLIBC_2.30 pthread_attr_getperthreadids_np F
 GLIBC_2.30 pthread_attr_setperthreadfs_np F
+GLIBC_2.30 pthread_attr_setperthreadids_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
diff --git a/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist
index 62790b0a64..207448d9fc 100644
--- a/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist
+++ b/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist
@@ -2123,7 +2123,9 @@ GLIBC_2.3.4 xdr_u_quad_t F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
 GLIBC_2.30 pthread_attr_getperthreadfs_np F
+GLIBC_2.30 pthread_attr_getperthreadids_np F
 GLIBC_2.30 pthread_attr_setperthreadfs_np F
+GLIBC_2.30 pthread_attr_setperthreadids_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 __confstr_chk F
diff --git a/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist
index eb2ae61601..e87edbde57 100644
--- a/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist
+++ b/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist
@@ -2121,7 +2121,9 @@ GLIBC_2.3.4 xdr_u_quad_t F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
 GLIBC_2.30 pthread_attr_getperthreadfs_np F
+GLIBC_2.30 pthread_attr_getperthreadids_np F
 GLIBC_2.30 pthread_attr_setperthreadfs_np F
+GLIBC_2.30 pthread_attr_setperthreadids_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 __confstr_chk F
diff --git a/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist
index 9cf1462270..6f68c19ffc 100644
--- a/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist
+++ b/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist
@@ -2129,7 +2129,9 @@ GLIBC_2.3.4 xdr_u_quad_t F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
 GLIBC_2.30 pthread_attr_getperthreadfs_np F
+GLIBC_2.30 pthread_attr_getperthreadids_np F
 GLIBC_2.30 pthread_attr_setperthreadfs_np F
+GLIBC_2.30 pthread_attr_setperthreadids_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 __confstr_chk F
diff --git a/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist
index d116e7180e..35376c4083 100644
--- a/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist
@@ -2123,7 +2123,9 @@ GLIBC_2.3.4 xdr_u_quad_t F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
 GLIBC_2.30 pthread_attr_getperthreadfs_np F
+GLIBC_2.30 pthread_attr_getperthreadids_np F
 GLIBC_2.30 pthread_attr_setperthreadfs_np F
+GLIBC_2.30 pthread_attr_setperthreadids_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 __confstr_chk F
diff --git a/sysdeps/unix/sysv/linux/nios2/libc.abilist b/sysdeps/unix/sysv/linux/nios2/libc.abilist
index 0e8d5bfcc7..957efe283c 100644
--- a/sysdeps/unix/sysv/linux/nios2/libc.abilist
+++ b/sysdeps/unix/sysv/linux/nios2/libc.abilist
@@ -2177,6 +2177,8 @@ GLIBC_2.29 posix_spawn_file_actions_addfchdir_np F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
 GLIBC_2.30 pthread_attr_getperthreadfs_np F
+GLIBC_2.30 pthread_attr_getperthreadids_np F
 GLIBC_2.30 pthread_attr_setperthreadfs_np F
+GLIBC_2.30 pthread_attr_setperthreadids_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist
index 16e66c2fa4..594f3c7888 100644
--- a/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist
+++ b/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist
@@ -2179,7 +2179,9 @@ GLIBC_2.30 __nldbl_warnx F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
 GLIBC_2.30 pthread_attr_getperthreadfs_np F
+GLIBC_2.30 pthread_attr_getperthreadids_np F
 GLIBC_2.30 pthread_attr_setperthreadfs_np F
+GLIBC_2.30 pthread_attr_setperthreadids_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 _IO_fprintf F
diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/libc.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/libc.abilist
index fff0295a63..1db7320d13 100644
--- a/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/libc.abilist
+++ b/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/libc.abilist
@@ -2212,7 +2212,9 @@ GLIBC_2.30 __nldbl_warnx F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
 GLIBC_2.30 pthread_attr_getperthreadfs_np F
+GLIBC_2.30 pthread_attr_getperthreadids_np F
 GLIBC_2.30 pthread_attr_setperthreadfs_np F
+GLIBC_2.30 pthread_attr_setperthreadids_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 _IO_fprintf F
diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc64/be/libc.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc64/be/libc.abilist
index 808f021335..85965392d2 100644
--- a/sysdeps/unix/sysv/linux/powerpc/powerpc64/be/libc.abilist
+++ b/sysdeps/unix/sysv/linux/powerpc/powerpc64/be/libc.abilist
@@ -2042,7 +2042,9 @@ GLIBC_2.30 __nldbl_warnx F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
 GLIBC_2.30 pthread_attr_getperthreadfs_np F
+GLIBC_2.30 pthread_attr_getperthreadids_np F
 GLIBC_2.30 pthread_attr_setperthreadfs_np F
+GLIBC_2.30 pthread_attr_setperthreadids_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 _IO_fprintf F
diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc64/le/libc.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc64/le/libc.abilist
index a894bc49be..25872a4d1b 100644
--- a/sysdeps/unix/sysv/linux/powerpc/powerpc64/le/libc.abilist
+++ b/sysdeps/unix/sysv/linux/powerpc/powerpc64/le/libc.abilist
@@ -2246,6 +2246,8 @@ GLIBC_2.30 __nldbl_warnx F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
 GLIBC_2.30 pthread_attr_getperthreadfs_np F
+GLIBC_2.30 pthread_attr_getperthreadids_np F
 GLIBC_2.30 pthread_attr_setperthreadfs_np F
+GLIBC_2.30 pthread_attr_setperthreadids_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
diff --git a/sysdeps/unix/sysv/linux/riscv/rv64/libc.abilist b/sysdeps/unix/sysv/linux/riscv/rv64/libc.abilist
index e220b0fd0c..36a6548695 100644
--- a/sysdeps/unix/sysv/linux/riscv/rv64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/riscv/rv64/libc.abilist
@@ -2106,6 +2106,8 @@ GLIBC_2.29 posix_spawn_file_actions_addfchdir_np F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
 GLIBC_2.30 pthread_attr_getperthreadfs_np F
+GLIBC_2.30 pthread_attr_getperthreadids_np F
 GLIBC_2.30 pthread_attr_setperthreadfs_np F
+GLIBC_2.30 pthread_attr_setperthreadids_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
diff --git a/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist b/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist
index 1567d2ff1d..1ab615c10d 100644
--- a/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist
+++ b/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist
@@ -2174,7 +2174,9 @@ GLIBC_2.30 __nldbl_warnx F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
 GLIBC_2.30 pthread_attr_getperthreadfs_np F
+GLIBC_2.30 pthread_attr_getperthreadids_np F
 GLIBC_2.30 pthread_attr_setperthreadfs_np F
+GLIBC_2.30 pthread_attr_setperthreadids_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 _IO_fprintf F
diff --git a/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist b/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist
index f9d88d588e..8a0c4ad926 100644
--- a/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist
@@ -2078,7 +2078,9 @@ GLIBC_2.30 __nldbl_warnx F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
 GLIBC_2.30 pthread_attr_getperthreadfs_np F
+GLIBC_2.30 pthread_attr_getperthreadids_np F
 GLIBC_2.30 pthread_attr_setperthreadfs_np F
+GLIBC_2.30 pthread_attr_setperthreadids_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 _IO_fprintf F
diff --git a/sysdeps/unix/sysv/linux/sh/libc.abilist b/sysdeps/unix/sysv/linux/sh/libc.abilist
index affd74df4c..80df6c75ac 100644
--- a/sysdeps/unix/sysv/linux/sh/libc.abilist
+++ b/sysdeps/unix/sysv/linux/sh/libc.abilist
@@ -2044,7 +2044,9 @@ GLIBC_2.3.4 xdr_u_quad_t F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
 GLIBC_2.30 pthread_attr_getperthreadfs_np F
+GLIBC_2.30 pthread_attr_getperthreadids_np F
 GLIBC_2.30 pthread_attr_setperthreadfs_np F
+GLIBC_2.30 pthread_attr_setperthreadids_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 __confstr_chk F
diff --git a/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist b/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist
index c12cc83bb2..6308f6055c 100644
--- a/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist
+++ b/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist
@@ -2168,7 +2168,9 @@ GLIBC_2.30 __nldbl_warnx F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
 GLIBC_2.30 pthread_attr_getperthreadfs_np F
+GLIBC_2.30 pthread_attr_getperthreadids_np F
 GLIBC_2.30 pthread_attr_setperthreadfs_np F
+GLIBC_2.30 pthread_attr_setperthreadids_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 _IO_fprintf F
diff --git a/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist b/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist
index 37c9dff44c..6b73de3257 100644
--- a/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist
@@ -2095,7 +2095,9 @@ GLIBC_2.3.4 xdr_u_quad_t F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
 GLIBC_2.30 pthread_attr_getperthreadfs_np F
+GLIBC_2.30 pthread_attr_getperthreadids_np F
 GLIBC_2.30 pthread_attr_setperthreadfs_np F
+GLIBC_2.30 pthread_attr_setperthreadids_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 __confstr_chk F
diff --git a/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist b/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist
index 71b7cc4ff9..6f24765088 100644
--- a/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist
@@ -2053,7 +2053,9 @@ GLIBC_2.3.4 xdr_u_quad_t F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
 GLIBC_2.30 pthread_attr_getperthreadfs_np F
+GLIBC_2.30 pthread_attr_getperthreadids_np F
 GLIBC_2.30 pthread_attr_setperthreadfs_np F
+GLIBC_2.30 pthread_attr_setperthreadids_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
 GLIBC_2.4 __confstr_chk F
diff --git a/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist b/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist
index 573fc2e01c..bc2ad25346 100644
--- a/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist
+++ b/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist
@@ -2152,6 +2152,8 @@ GLIBC_2.29 posix_spawn_file_actions_addfchdir_np F
 GLIBC_2.30 getdents64 F
 GLIBC_2.30 gettid F
 GLIBC_2.30 pthread_attr_getperthreadfs_np F
+GLIBC_2.30 pthread_attr_getperthreadids_np F
 GLIBC_2.30 pthread_attr_setperthreadfs_np F
+GLIBC_2.30 pthread_attr_setperthreadids_np F
 GLIBC_2.30 tgkill F
 GLIBC_2.30 twalk_r F
