Rebase qsort implementation from 2.39 upstream (RHEL-24168)

Resolves: RHEL-24168
This commit is contained in:
Arjun Shankar 2025-07-08 11:03:42 +02:00 committed by Florian Weimer
parent 0e04d2e772
commit 4b07ecdf12
23 changed files with 4255 additions and 1 deletions

226
glibc-RHEL-24168-1.patch Normal file
View File

@ -0,0 +1,226 @@
Downstream-only patch to add arc4random to support/ for use in qsort
testing.
The arc4random implementation is up-to-date with upstream commit
2642002380aafb71a1d3b569b6d7ebeab3284816, with minor changes to keep
everything self-contained within support infrastructure. Unlike the
upstream version, this implementation is a cancellation point.
diff --git a/support/Makefile b/support/Makefile
index d6d03c2ed3af3e6d..bffcb06d7185d674 100644
--- a/support/Makefile
+++ b/support/Makefile
@@ -41,6 +41,8 @@ libsupport-routines = \
resolv_response_context_free \
resolv_test \
set_fortify_handler \
+ support-arc4random \
+ support-arc4random_uniform \
support-open-dev-null-range \
support_become_root \
support_can_chroot \
diff --git a/support/support-arc4random.c b/support/support-arc4random.c
new file mode 100644
index 0000000000000000..c4462b098c68cef5
--- /dev/null
+++ b/support/support-arc4random.c
@@ -0,0 +1,99 @@
+/* Pseudo Random Number Generator
+ Copyright (C) 2022-2025 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
+ <https://www.gnu.org/licenses/>. */
+
+#include <errno.h>
+#include <not-cancel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <sys/param.h>
+#include <sys/random.h>
+#include <poll.h>
+
+static void
+arc4random_getrandom_failure (void)
+{
+ __libc_fatal ("Fatal glibc error: cannot get entropy for arc4random\n");
+}
+
+void
+arc4random_buf (void *p, size_t n)
+{
+ static int seen_initialized;
+ ssize_t l;
+ int fd;
+
+ if (n == 0)
+ return;
+
+ for (;;)
+ {
+ l = TEMP_FAILURE_RETRY (getrandom (p, n, 0));
+ if (l > 0)
+ {
+ if ((size_t) l == n)
+ return; /* Done reading, success. */
+ p = (uint8_t *) p + l;
+ n -= l;
+ continue; /* Interrupted by a signal; keep going. */
+ }
+ else if (l < 0 && errno == ENOSYS)
+ break; /* No syscall, so fallback to /dev/urandom. */
+ arc4random_getrandom_failure ();
+ }
+
+ if (atomic_load_relaxed (&seen_initialized) == 0)
+ {
+ /* Poll /dev/random as an approximation of RNG initialization. */
+ struct pollfd pfd = { .events = POLLIN };
+ pfd.fd = TEMP_FAILURE_RETRY (
+ __open64_nocancel ("/dev/random", O_RDONLY | O_CLOEXEC | O_NOCTTY));
+ if (pfd.fd < 0)
+ arc4random_getrandom_failure ();
+ if (TEMP_FAILURE_RETRY (poll (&pfd, 1, -1)) < 0)
+ arc4random_getrandom_failure ();
+ if (__close_nocancel (pfd.fd) < 0)
+ arc4random_getrandom_failure ();
+ atomic_store_relaxed (&seen_initialized, 1);
+ }
+
+ fd = TEMP_FAILURE_RETRY (
+ __open64_nocancel ("/dev/urandom", O_RDONLY | O_CLOEXEC | O_NOCTTY));
+ if (fd < 0)
+ arc4random_getrandom_failure ();
+ for (;;)
+ {
+ l = TEMP_FAILURE_RETRY (__read_nocancel (fd, p, n));
+ if (l <= 0)
+ arc4random_getrandom_failure ();
+ if ((size_t) l == n)
+ break; /* Done reading, success. */
+ p = (uint8_t *) p + l;
+ n -= l;
+ }
+ if (__close_nocancel (fd) < 0)
+ arc4random_getrandom_failure ();
+}
+
+uint32_t
+arc4random (void)
+{
+ uint32_t r;
+ arc4random_buf (&r, sizeof (r));
+ return r;
+}
diff --git a/support/support-arc4random_uniform.c b/support/support-arc4random_uniform.c
new file mode 100644
index 0000000000000000..20108e7409cca81b
--- /dev/null
+++ b/support/support-arc4random_uniform.c
@@ -0,0 +1,70 @@
+/* Random pseudo generator number which returns a single 32 bit value
+ uniformly distributed but with an upper_bound.
+ Copyright (C) 2022-2025 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
+ <https://www.gnu.org/licenses/>. */
+
+#include <stdlib.h>
+#include <sys/param.h>
+#include <support/support.h>
+
+/* Return a uniformly distributed random number less than N. The algorithm
+ calculates a mask being the lowest power of two bounding the upper bound
+ N, successively queries new random values, and rejects values outside of
+ the request range.
+
+ For reject values, it also tries if the remaining entropy could fit on
+ the asked range after range adjustment.
+
+ The algorithm avoids modulo and divide operations, which might be costly
+ depending on the architecture. */
+uint32_t
+arc4random_uniform (uint32_t n)
+{
+ if (n <= 1)
+ /* There is no valid return value for a zero limit, and 0 is the
+ only possible result for limit 1. */
+ return 0;
+
+ /* Powers of two are easy. */
+ if (powerof2 (n))
+ return arc4random () & (n - 1);
+
+ /* mask is the smallest power of 2 minus 1 number larger than n. */
+ int z = __builtin_clz (n);
+ uint32_t mask = ~UINT32_C(0) >> z;
+ int bits = CHAR_BIT * sizeof (uint32_t) - z;
+
+ while (1)
+ {
+ uint32_t value = arc4random ();
+
+ /* Return if the lower power of 2 minus 1 satisfy the condition. */
+ uint32_t r = value & mask;
+ if (r < n)
+ return r;
+
+ /* Otherwise check if remaining bits of entropy provides fits in the
+ bound. */
+ for (int bits_left = z; bits_left >= bits; bits_left -= bits)
+ {
+ value >>= bits;
+ r = value & mask;
+ if (r < n)
+ return r;
+ }
+ }
+}
diff --git a/support/support.h b/support/support.h
index b69f588e2edce6be..ed7862daf9e4120a 100644
--- a/support/support.h
+++ b/support/support.h
@@ -220,6 +220,19 @@ void support_stack_free (struct support_stack *stack);
The returned value is the lowest file descriptor number. */
int support_open_dev_null_range (int num, int flags, mode_t mode);
+/* Return a random integer between zero and 2**32-1 (inclusive). */
+extern uint32_t arc4random (void)
+ __THROW __wur;
+
+/* Fill the buffer with random data. */
+extern void arc4random_buf (void *__buf, size_t __size)
+ __THROW __nonnull ((1));
+
+/* Return a random number between zero (inclusive) and the specified
+ limit (exclusive). */
+extern uint32_t arc4random_uniform (__uint32_t __upper_bound)
+ __THROW __wur;
+
__END_DECLS
#endif /* SUPPORT_H */

43
glibc-RHEL-24168-10.patch Normal file
View File

@ -0,0 +1,43 @@
commit f8cfb6836e8d91bb789b2e7fd65338d6f5bd459c
Author: Florian Weimer <fweimer@redhat.com>
Date: Wed Nov 8 15:18:02 2023 +0100
stdlib: Avoid element self-comparisons in qsort
This improves compatibility with applications which assume that qsort
does not invoke the comparison function with equal pointer arguments.
The newly introduced branches should be predictable, as leading to a
call to the comparison function. If the prediction fails, we avoid
calling the function.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
diff --git a/stdlib/qsort.c b/stdlib/qsort.c
index cb1619aa0ae7de72..2ee39e2c492f792e 100644
--- a/stdlib/qsort.c
+++ b/stdlib/qsort.c
@@ -137,7 +137,7 @@ siftdown (void *base, size_t size, size_t k, size_t n,
if (j < n && cmp (base + (j * size), base + ((j + 1) * size), arg) < 0)
j++;
- if (cmp (base + (k * size), base + (j * size), arg) >= 0)
+ if (j == k || cmp (base + (k * size), base + (j * size), arg) >= 0)
break;
do_swap (base + (size * j), base + (k * size), size, swap_type);
@@ -333,10 +333,12 @@ __qsort_r (void *const pbase, size_t total_elems, size_t size,
that this algorithm runs much faster than others. */
do
{
- while ((*cmp) ((void *) left_ptr, (void *) mid, arg) < 0)
+ while (left_ptr != mid
+ && (*cmp) ((void *) left_ptr, (void *) mid, arg) < 0)
left_ptr += size;
- while ((*cmp) ((void *) mid, (void *) right_ptr, arg) < 0)
+ while (right_ptr != mid
+ && (*cmp) ((void *) mid, (void *) right_ptr, arg) < 0)
right_ptr -= size;
if (left_ptr < right_ptr)

25
glibc-RHEL-24168-11.patch Normal file
View File

@ -0,0 +1,25 @@
commit e4d8117b82065dc72e8df80097360e7c05a349b9
Author: Florian Weimer <fweimer@redhat.com>
Date: Tue Nov 21 16:45:35 2023 +0100
stdlib: Avoid another self-comparison in qsort
In the insertion phase, we could run off the start of the array if the
comparison function never runs zero. In that case, it never finds the
initial element that terminates the iteration.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
diff --git a/stdlib/qsort.c b/stdlib/qsort.c
index 2ee39e2c492f792e..0d5f8b92e8072965 100644
--- a/stdlib/qsort.c
+++ b/stdlib/qsort.c
@@ -218,7 +218,7 @@ insertion_sort_qsort_partitions (void *const pbase, size_t total_elems,
while ((run_ptr += size) <= end_ptr)
{
tmp_ptr = run_ptr - size;
- while (cmp (run_ptr, tmp_ptr, arg) < 0)
+ while (run_ptr != tmp_ptr && cmp (run_ptr, tmp_ptr, arg) < 0)
tmp_ptr -= size;
tmp_ptr += size;

273
glibc-RHEL-24168-12.patch Normal file
View File

@ -0,0 +1,273 @@
commit 55364e1f7dfab372f0710513c4d1c967c4965f71
Author: Florian Weimer <fweimer@redhat.com>
Date: Tue Nov 21 16:45:35 2023 +0100
stdlib: Handle various corner cases in the fallback heapsort for qsort
The previous implementation did not consistently apply the rule that
the child nodes of node K are at 2 * K + 1 and 2 * K + 2, or
that the parent node is at (K - 1) / 2.
Add an internal test that targets the heapsort implementation
directly.
Reported-by: Stepan Golosunov <stepan@golosunov.pp.ru>
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
diff --git a/stdlib/Makefile b/stdlib/Makefile
index 4039e5395eeea2b0..ee005ce8caa48abe 100644
--- a/stdlib/Makefile
+++ b/stdlib/Makefile
@@ -254,6 +254,7 @@ tests := \
# tests
tests-internal := \
+ tst-qsort4 \
tst-strtod1i \
tst-strtod3 \
tst-strtod4 \
diff --git a/stdlib/qsort.c b/stdlib/qsort.c
index 0d5f8b92e8072965..b207c12d2f0a38cc 100644
--- a/stdlib/qsort.c
+++ b/stdlib/qsort.c
@@ -126,29 +126,44 @@ pop (stack_node *top, char **lo, char **hi, size_t *depth)
return top;
}
-/* NB: N is inclusive bound for BASE. */
+/* Establish the heap condition at index K, that is, the key at K will
+ not be less than either of its children, at 2 * K + 1 and 2 * K + 2
+ (if they exist). N is the last valid index. */
static inline void
siftdown (void *base, size_t size, size_t k, size_t n,
enum swap_type_t swap_type, __compar_d_fn_t cmp, void *arg)
{
- while (k <= n / 2)
+ /* There can only be a heap condition violation if there are
+ children. */
+ while (2 * k + 1 <= n)
{
- size_t j = 2 * k;
+ /* Left child. */
+ size_t j = 2 * k + 1;
+ /* If the right child is larger, use it. */
if (j < n && cmp (base + (j * size), base + ((j + 1) * size), arg) < 0)
j++;
+ /* If k is already >= to its children, we are done. */
if (j == k || cmp (base + (k * size), base + (j * size), arg) >= 0)
break;
+ /* Heal the violation. */
do_swap (base + (size * j), base + (k * size), size, swap_type);
+
+ /* Swapping with j may have introduced a violation at j. Fix
+ it in the next loop iteration. */
k = j;
}
}
+/* Establish the heap condition for the indices 0 to N (inclusive). */
static inline void
heapify (void *base, size_t size, size_t n, enum swap_type_t swap_type,
__compar_d_fn_t cmp, void *arg)
{
+ /* If n is odd, k = n / 2 has a left child at n, so this is the
+ largest index that can have a heap condition violation regarding
+ its children. */
size_t k = n / 2;
while (1)
{
@@ -158,32 +173,38 @@ heapify (void *base, size_t size, size_t n, enum swap_type_t swap_type,
}
}
-/* A non-recursive heapsort, used on introsort implementation as a fallback
- routine with worst-case performance of O(nlog n) and worst-case space
- complexity of O(1). It sorts the array starting at BASE and ending at
- END, with each element of SIZE bytes. The SWAP_TYPE is the callback
- function used to swap elements, and CMP is the function used to compare
- elements. */
+/* A non-recursive heapsort, used on introsort implementation as a
+ fallback routine with worst-case performance of O(nlog n) and
+ worst-case space complexity of O(1). It sorts the array starting
+ at BASE and ending at END (inclusive), with each element of SIZE
+ bytes. The SWAP_TYPE is the callback function used to swap
+ elements, and CMP is the function used to compare elements. */
static void
heapsort_r (void *base, void *end, size_t size, enum swap_type_t swap_type,
__compar_d_fn_t cmp, void *arg)
{
- const size_t count = ((uintptr_t) end - (uintptr_t) base) / size;
-
- if (count < 2)
+ size_t n = ((uintptr_t) end - (uintptr_t) base) / size;
+ if (n <= 1)
+ /* Handled by insertion sort. */
return;
- size_t n = count - 1;
-
/* Build the binary heap, largest value at the base[0]. */
heapify (base, size, n, swap_type, cmp, arg);
- /* On each iteration base[0:n] is the binary heap, while base[n:count]
- is sorted. */
- while (n > 0)
+ while (true)
{
+ /* Indices 0 .. n contain the binary heap. Extract the largest
+ element put it into the final position in the array. */
do_swap (base, base + (n * size), size, swap_type);
+
+ /* The heap is now one element shorter. */
n--;
+ if (n == 0)
+ break;
+
+ /* By swapping in elements 0 and the previous value of n (now at
+ n + 1), we likely introduced a heap condition violation. Fix
+ it for the reduced heap. */
siftdown (base, size, 0, n, swap_type, cmp, arg);
}
}
diff --git a/stdlib/tst-qsort4.c b/stdlib/tst-qsort4.c
new file mode 100644
index 0000000000000000..a7abaa1a37461666
--- /dev/null
+++ b/stdlib/tst-qsort4.c
@@ -0,0 +1,134 @@
+/* Test the heapsort implementation behind qsort.
+ Copyright (C) 2023 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 "qsort.c"
+
+#include <stdio.h>
+#include <support/check.h>
+#include <support/support.h>
+
+static int
+cmp (const void *a1, const void *b1, void *closure)
+{
+ const signed char *a = a1;
+ const signed char *b = b1;
+ return *a - *b;
+}
+
+/* Wrapper around heapsort_r that set ups the required variables. */
+static void
+heapsort_wrapper (void *const pbase, size_t total_elems, size_t size,
+ __compar_d_fn_t cmp, void *arg)
+{
+ char *base_ptr = (char *) pbase;
+ char *lo = base_ptr;
+ char *hi = &lo[size * (total_elems - 1)];
+
+ if (total_elems <= 1)
+ /* Avoid lossage with unsigned arithmetic below. */
+ return;
+
+ enum swap_type_t swap_type;
+ if (is_aligned (pbase, size, 8))
+ swap_type = SWAP_WORDS_64;
+ else if (is_aligned (pbase, size, 4))
+ swap_type = SWAP_WORDS_32;
+ else
+ swap_type = SWAP_BYTES;
+ heapsort_r (lo, hi, size, swap_type, cmp, arg);
+}
+
+static void
+check_one_sort (signed char *array, int length)
+{
+ signed char *copy = xmalloc (length);
+ memcpy (copy, array, length);
+ heapsort_wrapper (copy, length, 1, cmp, NULL);
+
+ /* Verify that the result is sorted. */
+ for (int i = 1; i < length; ++i)
+ if (copy[i] < copy[i - 1])
+ {
+ support_record_failure ();
+ printf ("error: sorting failure for length %d at offset %d\n",
+ length, i - 1);
+ printf ("input:");
+ for (int i = 0; i < length; ++i)
+ printf (" %d", array[i]);
+ printf ("\noutput:");
+ for (int i = 0; i < length; ++i)
+ printf (" %d", copy[i]);
+ putchar ('\n');
+ break;
+ }
+
+ /* Verify that no elements went away or were added. */
+ {
+ int expected_counts[256];
+ for (int i = 0; i < length; ++i)
+ ++expected_counts[array[i] & 0xff];
+ int actual_counts[256];
+ for (int i = 0; i < length; ++i)
+ ++actual_counts[copy[i] & 0xff];
+ for (int i = 0; i < 256; ++i)
+ TEST_COMPARE (expected_counts[i], expected_counts[i]);
+ }
+
+ free (copy);
+}
+
+/* Enumerate all possible combinations of LENGTH elements. */
+static void
+check_combinations (int length, signed char *start, int offset)
+{
+ if (offset == length)
+ check_one_sort (start, length);
+ else
+ for (int i = 0; i < length; ++i)
+ {
+ start[offset] = i;
+ check_combinations(length, start, offset + 1);
+ }
+}
+
+static int
+do_test (void)
+{
+ /* A random permutation of 20 values. */
+ check_one_sort ((signed char[20]) {5, 12, 16, 10, 14, 11, 9, 13, 8, 15,
+ 0, 17, 3, 7, 1, 18, 2, 19, 4, 6}, 20);
+
+
+ /* A permutation that appeared during adversarial testing for the
+ quicksort pass. */
+ check_one_sort ((signed char[16]) {15, 3, 4, 2, 1, 0, 8, 7, 6, 5, 14,
+ 13, 12, 11, 10, 9}, 16);
+
+ /* Array lengths 2 and less are not handled by heapsort_r and
+ deferred to insertion sort. */
+ for (int i = 3; i <= 8; ++i)
+ {
+ signed char *buf = xmalloc (i);
+ check_combinations (i, buf, 0);
+ free (buf);
+ }
+
+ return 0;
+}
+
+#include <support/test-driver.c>

248
glibc-RHEL-24168-13.patch Normal file
View File

@ -0,0 +1,248 @@
commit 64e4acf24da15c11cb83f933947df3b2e8a700cd
Author: Florian Weimer <fweimer@redhat.com>
Date: Tue Nov 21 16:45:35 2023 +0100
stdlib: The qsort implementation needs to use heapsort in more cases
The existing logic avoided internal stack overflow. To avoid
a denial-of-service condition with adversarial input, it is necessary
to fall over to heapsort if tail-recursing deeply, too, which does
not result in a deep stack of pending partitions.
The new test stdlib/tst-qsort5 is based on Douglas McIlroy's paper
on this subject.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Conflicts:
stdlib/Makefile: Adjust for getenv tests in glibc-RHEL-67692-4.patch.
diff --git a/stdlib/Makefile b/stdlib/Makefile
index ee005ce8caa48abe..a1a511da37f0c18e 100644
--- a/stdlib/Makefile
+++ b/stdlib/Makefile
@@ -212,6 +212,7 @@ tests := \
tst-qsort \
tst-qsort2 \
tst-qsort3 \
+ tst-qsort5 \
tst-quick_exit \
tst-rand48 \
tst-rand48-2 \
@@ -483,6 +484,7 @@ $(objpfx)tst-setcontext3.out: tst-setcontext3.sh $(objpfx)tst-setcontext3
$(common-objpfx)stdlib/; \
$(evaluate-test)
+$(objpfx)tst-qsort5: $(libm)
$(objpfx)tst-getenv-signal: $(shared-thread-library)
$(objpfx)tst-getenv-thread: $(shared-thread-library)
$(objpfx)tst-getenv-unsetenv: $(shared-thread-library)
diff --git a/stdlib/qsort.c b/stdlib/qsort.c
index b207c12d2f0a38cc..df8d0012c759e509 100644
--- a/stdlib/qsort.c
+++ b/stdlib/qsort.c
@@ -390,14 +390,23 @@ __qsort_r (void *const pbase, size_t total_elems, size_t size,
{
if ((size_t) (hi - left_ptr) <= max_thresh)
/* Ignore both small partitions. */
- top = pop (top, &lo, &hi, &depth);
+ {
+ top = pop (top, &lo, &hi, &depth);
+ --depth;
+ }
else
- /* Ignore small left partition. */
- lo = left_ptr;
+ {
+ /* Ignore small left partition. */
+ lo = left_ptr;
+ --depth;
+ }
}
else if ((size_t) (hi - left_ptr) <= max_thresh)
/* Ignore small right partition. */
- hi = right_ptr;
+ {
+ hi = right_ptr;
+ --depth;
+ }
else if ((right_ptr - lo) > (hi - left_ptr))
{
/* Push larger left partition indices. */
diff --git a/stdlib/tst-qsort5.c b/stdlib/tst-qsort5.c
new file mode 100644
index 0000000000000000..d3a88c30f8ffb135
--- /dev/null
+++ b/stdlib/tst-qsort5.c
@@ -0,0 +1,171 @@
+/* Adversarial test for qsort_r.
+ Copyright (C) 2023 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/>. */
+
+/* The approach follows Douglas McIlroy, A Killer Adversary for
+ Quicksort. Software—Practice and Experience 29 (1999) 341-344.
+ Downloaded <http://www.cs.dartmouth.edu/~doug/mdmspe.pdf>
+ (2023-11-17). */
+
+#include <math.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <support/check.h>
+#include <support/support.h>
+
+struct context
+{
+ /* Called the gas value in the paper. This value is larger than all
+ other values (length minus one will do), so comparison with any
+ decided value has a known result. */
+ int undecided_value;
+
+ /* If comparing undecided values, one of them as to be assigned a
+ value to ensure consistency with future comparisons. This is the
+ value that will be used. Starts out at zero. */
+ int next_decided;
+
+ /* Used to trick pivot selection. Deciding the value for the last
+ seen undcided value in a decided/undecided comparison happens
+ to trick the many qsort implementations. */
+ int last_undecided_index;
+
+ /* This array contains the actually asigned values. The call to
+ qsort_r sorts a different array that contains indices into this
+ array. */
+ int *decided_values;
+};
+
+static int
+compare_opponent (const void *l1, const void *r1, void *ctx1)
+{
+ const int *l = l1;
+ const int *r = r1;
+ struct context *ctx = ctx1;
+ int rvalue = ctx->decided_values[*r];
+ int lvalue = ctx->decided_values[*l];
+
+ if (lvalue == ctx->undecided_value)
+ {
+ if (rvalue == ctx->undecided_value)
+ {
+ /* Both values are undecided. In this case, make a decision
+ for the last-used undecided value. This is tweak is very
+ specific to quicksort. */
+ if (*l == ctx->last_undecided_index)
+ {
+ ctx->decided_values[*l] = ctx->next_decided;
+ ++ctx->next_decided;
+ /* The undecided value or *r is greater. */
+ return -1;
+ }
+ else
+ {
+ ctx->decided_values[*r] = ctx->next_decided;
+ ++ctx->next_decided;
+ /* The undecided value for *l is greater. */
+ return 1;
+ }
+ }
+ else
+ {
+ ctx->last_undecided_index = *l;
+ return 1;
+ }
+ }
+ else
+ {
+ /* *l is a decided value. */
+ if (rvalue == ctx->undecided_value)
+ {
+ ctx->last_undecided_index = *r;
+ /* The undecided value for *r is greater. */
+ return -1;
+ }
+ else
+ return lvalue - rvalue;
+ }
+}
+
+/* Return a pointer to the adversarial permutation of length N. */
+static int *
+create_permutation (size_t n)
+{
+ struct context ctx =
+ {
+ .undecided_value = n - 1, /* Larger than all other values. */
+ .decided_values = xcalloc (n, sizeof (int)),
+ };
+ for (size_t i = 0; i < n; ++i)
+ ctx.decided_values[i] = ctx.undecided_value;
+ int *scratch = xcalloc (n, sizeof (int));
+ for (size_t i = 0; i < n; ++i)
+ scratch[i] = i;
+ qsort_r (scratch, n, sizeof (*scratch), compare_opponent, &ctx);
+ free (scratch);
+ return ctx.decided_values;
+}
+
+/* Callback function for qsort which counts the number of invocations
+ in *CLOSURE. */
+static int
+compare_counter (const void *l1, const void *r1, void *closure)
+{
+ const int *l = l1;
+ const int *r = r1;
+ unsigned long long int *counter = closure;
+ ++*counter;
+ return *l - *r;
+}
+
+/* Count the comparisons required for an adversarial permutation of
+ length N. */
+static unsigned long long int
+count_comparisons (size_t n)
+{
+ int *array = create_permutation (n);
+ unsigned long long int counter = 0;
+ qsort_r (array, n, sizeof (*array), compare_counter, &counter);
+ free (array);
+ return counter;
+}
+
+/* Check the scaling factor for one adversarial permutation of length
+ N, and report some statistics. */
+static void
+check_one_n (size_t n)
+{
+ unsigned long long int count = count_comparisons (n);
+ double factor = count / (n * log (count));
+ printf ("info: length %zu: %llu comparisons ~ %f * n * log (n)\n",
+ n, count, factor);
+ /* This is an arbitrary factor which is true for the current
+ implementation across a wide range of sizes. */
+ TEST_VERIFY (factor <= 4.5);
+}
+
+static int
+do_test (void)
+{
+ check_one_n (100);
+ check_one_n (1000);
+ for (int i = 1; i <= 15; ++i)
+ check_one_n (i * 10 * 1000);
+ return 0;
+}
+
+#include <support/test-driver.c>

105
glibc-RHEL-24168-14.patch Normal file
View File

@ -0,0 +1,105 @@
commit b9390ba93676c4b1e87e218af5e7e4bb596312ac
Author: Florian Weimer <fweimer@redhat.com>
Date: Mon Dec 4 06:35:56 2023 +0100
stdlib: Fix array bounds protection in insertion sort phase of qsort
The previous check did not do anything because tmp_ptr already
points before run_ptr due to the way it is initialized.
Fixes commit e4d8117b82065dc72e8df80097360e7c05a349b9
("stdlib: Avoid another self-comparison in qsort").
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
diff --git a/stdlib/Makefile b/stdlib/Makefile
index a1a511da37f0c18e..82d9d909890853b7 100644
--- a/stdlib/Makefile
+++ b/stdlib/Makefile
@@ -213,6 +213,7 @@ tests := \
tst-qsort2 \
tst-qsort3 \
tst-qsort5 \
+ tst-qsort6 \
tst-quick_exit \
tst-rand48 \
tst-rand48-2 \
diff --git a/stdlib/qsort.c b/stdlib/qsort.c
index df8d0012c759e509..3d5405705862ddf0 100644
--- a/stdlib/qsort.c
+++ b/stdlib/qsort.c
@@ -239,7 +239,7 @@ insertion_sort_qsort_partitions (void *const pbase, size_t total_elems,
while ((run_ptr += size) <= end_ptr)
{
tmp_ptr = run_ptr - size;
- while (run_ptr != tmp_ptr && cmp (run_ptr, tmp_ptr, arg) < 0)
+ while (tmp_ptr != base_ptr && cmp (run_ptr, tmp_ptr, arg) < 0)
tmp_ptr -= size;
tmp_ptr += size;
diff --git a/stdlib/tst-qsort6.c b/stdlib/tst-qsort6.c
new file mode 100644
index 0000000000000000..8ec0a6b633bc8398
--- /dev/null
+++ b/stdlib/tst-qsort6.c
@@ -0,0 +1,60 @@
+/* Test qsort with invalid comparison functions.
+ Copyright (C) 2023 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 <stdlib.h>
+#include <support/check.h>
+
+/* Invalid comparison function that always returns -1. */
+static int
+invalid_compare_1 (const void *a1, const void *b1)
+{
+ const int *a = a1;
+ const int *b = b1;
+ /* Check that the marker value matches, which means that we are
+ likely within the array. */
+ TEST_COMPARE (*a, 842523635);
+ TEST_COMPARE (*b, 842523635);
+ TEST_VERIFY_EXIT (*a == 842523635);
+ TEST_VERIFY_EXIT (*b == 842523635);
+ return -1;
+}
+
+/* Invalid comparison function that always returns 1. */
+static int
+invalid_compare_2 (const void *a1, const void *b1)
+{
+ const int *a = a1;
+ const int *b = b1;
+ TEST_COMPARE (*a, 842523635);
+ TEST_COMPARE (*b, 842523635);
+ TEST_VERIFY_EXIT (*a == 842523635);
+ TEST_VERIFY_EXIT (*b == 842523635);
+ return 1;
+}
+
+static int
+do_test (void)
+{
+ int array[] = {842523635, 842523635, 842523635, 842523635, 842523635};
+ qsort (array, array_length (array), sizeof (array[0]), invalid_compare_1);
+ qsort (array, array_length (array), sizeof (array[0]), invalid_compare_2);
+ return 0;
+}
+
+#include <support/test-driver.c>

876
glibc-RHEL-24168-15.patch Normal file
View File

@ -0,0 +1,876 @@
commit 709fbd3ec3595f2d1076b4fec09a739327459288
Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Date: Mon Jan 15 11:07:21 2024 -0300
stdlib: Reinstate stable mergesort implementation on qsort
The mergesort removal from qsort implementation (commit 03bf8357e8)
had the side-effect of making sorting nonstable. Although neither
POSIX nor C standard specify that qsort should be stable, it seems
that it has become an instance of Hyrum's law where multiple programs
expect it.
Also, the resulting introsort implementation is not faster than
the previous mergesort (which makes the change even less appealing).
This patch restores the previous mergesort implementation, with the
exception of machinery that checks the resulting allocation against
the _SC_PHYS_PAGES (it only adds complexity and the heuristic not
always make sense depending on the system configuration and load).
The alloca usage was replaced with a fixed-size buffer.
For the fallback mechanism, the implementation uses heapsort. It is
simpler than quicksort, and it does not suffer from adversarial
inputs. With memory overcommit, it should be rarely triggered.
The drawback is mergesort requires O(n) extra space, and since it is
allocated with malloc the function is AS-signal-unsafe. It should be
feasible to change it to use mmap, although I am not sure how urgent
it is. The heapsort is also nonstable, so programs that require a
stable sort would still be subject to this latent issue.
The tst-qsort5 is removed since it will not create quicksort adversarial
inputs with the current qsort_r implementation.
Checked on x86_64-linux-gnu and aarch64-linux-gnu.
Reviewed-by: Florian Weimer <fweimer@redhat.com>
Conflicts:
stdlib/tst-qsort5.c: Deletion had conflicts due to copyright update.
diff --git a/manual/argp.texi b/manual/argp.texi
index b77ad68285ecb732..0023441812d4e584 100644
--- a/manual/argp.texi
+++ b/manual/argp.texi
@@ -735,7 +735,7 @@ for options, bad phase of the moon, etc.
@c hol_set_group ok
@c hol_find_entry ok
@c hol_sort @mtslocale @acucorrupt
-@c qsort dup
+@c qsort dup @acucorrupt
@c hol_entry_qcmp @mtslocale
@c hol_entry_cmp @mtslocale
@c group_cmp ok
diff --git a/manual/locale.texi b/manual/locale.texi
index f6afa5dc44a2a016..1b3f97839bb5d068 100644
--- a/manual/locale.texi
+++ b/manual/locale.texi
@@ -253,7 +253,7 @@ The symbols in this section are defined in the header file @file{locale.h}.
@c calculate_head_size ok
@c __munmap ok
@c compute_hashval ok
-@c qsort dup
+@c qsort dup @acucorrupt
@c rangecmp ok
@c malloc @ascuheap @acsmem
@c strdup @ascuheap @acsmem
diff --git a/manual/search.texi b/manual/search.texi
index a550858478f7fc83..ffaadc46f51b18f9 100644
--- a/manual/search.texi
+++ b/manual/search.texi
@@ -159,7 +159,7 @@ To sort an array using an arbitrary comparison function, use the
@deftypefun void qsort (void *@var{array}, size_t @var{count}, size_t @var{size}, comparison_fn_t @var{compare})
@standards{ISO, stdlib.h}
-@safety{@prelim{}@mtsafe{}@assafe{}@acsafe{}}
+@safety{@prelim{}@mtsafe{}@assafe{}@acunsafe{@acucorrupt{}}}
The @code{qsort} function sorts the array @var{array}. The array
contains @var{count} elements, each of which is of size @var{size}.
@@ -199,8 +199,9 @@ Functions}):
The @code{qsort} function derives its name from the fact that it was
originally implemented using the ``quick sort'' algorithm.
-The implementation of @code{qsort} in this library is an in-place sort
-and uses a constant extra space (allocated on the stack).
+The implementation of @code{qsort} attempts to allocate auxiliary storage
+and use the merge sort algorithm, without violating C standard requirement
+that arguments passed to the comparison function point within the array.
@end deftypefun
@node Search/Sort Example
diff --git a/stdlib/Makefile b/stdlib/Makefile
index 82d9d909890853b7..a9d91a57c08ac506 100644
--- a/stdlib/Makefile
+++ b/stdlib/Makefile
@@ -212,7 +212,6 @@ tests := \
tst-qsort \
tst-qsort2 \
tst-qsort3 \
- tst-qsort5 \
tst-qsort6 \
tst-quick_exit \
tst-rand48 \
diff --git a/stdlib/qsort.c b/stdlib/qsort.c
index 3d5405705862ddf0..b95889047ba31193 100644
--- a/stdlib/qsort.c
+++ b/stdlib/qsort.c
@@ -20,6 +20,7 @@
Engineering a sort function; Jon Bentley and M. Douglas McIlroy;
Software - Practice and Experience; Vol. 23 (11), 1249-1265, 1993. */
+#include <errno.h>
#include <limits.h>
#include <memswap.h>
#include <stdlib.h>
@@ -33,9 +34,13 @@ enum swap_type_t
{
SWAP_WORDS_64,
SWAP_WORDS_32,
+ SWAP_VOID_ARG,
SWAP_BYTES
};
+typedef uint32_t __attribute__ ((__may_alias__)) u32_alias_t;
+typedef uint64_t __attribute__ ((__may_alias__)) u64_alias_t;
+
/* If this function returns true, elements can be safely copied using word
loads and stores. Otherwise, it might not be safe. BASE (as an integer)
must be a multiple of the word alignment. SIZE must be a multiple of
@@ -52,7 +57,6 @@ is_aligned (const void *base, size_t size, size_t wordsize)
static inline void
swap_words_64 (void * restrict a, void * restrict b, size_t n)
{
- typedef uint64_t __attribute__ ((__may_alias__)) u64_alias_t;
do
{
n -= 8;
@@ -65,7 +69,6 @@ swap_words_64 (void * restrict a, void * restrict b, size_t n)
static inline void
swap_words_32 (void * restrict a, void * restrict b, size_t n)
{
- typedef uint32_t __attribute__ ((__may_alias__)) u32_alias_t;
do
{
n -= 4;
@@ -89,43 +92,6 @@ do_swap (void * restrict a, void * restrict b, size_t size,
__memswap (a, b, size);
}
-/* Discontinue quicksort algorithm when partition gets below this size.
- This particular magic number was chosen to work best on a Sun 4/260. */
-#define MAX_THRESH 4
-
-/* Stack node declarations used to store unfulfilled partition obligations. */
-typedef struct
- {
- char *lo;
- char *hi;
- size_t depth;
- } stack_node;
-
-/* The stack needs log (total_elements) entries (we could even subtract
- log(MAX_THRESH)). Since total_elements has type size_t, we get as
- upper bound for log (total_elements):
- bits per byte (CHAR_BIT) * sizeof(size_t). */
-enum { STACK_SIZE = CHAR_BIT * sizeof (size_t) };
-
-static inline stack_node *
-push (stack_node *top, char *lo, char *hi, size_t depth)
-{
- top->lo = lo;
- top->hi = hi;
- top->depth = depth;
- return ++top;
-}
-
-static inline stack_node *
-pop (stack_node *top, char **lo, char **hi, size_t *depth)
-{
- --top;
- *lo = top->lo;
- *hi = top->hi;
- *depth = top->depth;
- return top;
-}
-
/* Establish the heap condition at index K, that is, the key at K will
not be less than either of its children, at 2 * K + 1 and 2 * K + 2
(if they exist). N is the last valid index. */
@@ -173,21 +139,35 @@ heapify (void *base, size_t size, size_t n, enum swap_type_t swap_type,
}
}
-/* A non-recursive heapsort, used on introsort implementation as a
- fallback routine with worst-case performance of O(nlog n) and
- worst-case space complexity of O(1). It sorts the array starting
- at BASE and ending at END (inclusive), with each element of SIZE
- bytes. The SWAP_TYPE is the callback function used to swap
- elements, and CMP is the function used to compare elements. */
+static enum swap_type_t
+get_swap_type (void *const pbase, size_t size)
+{
+ if ((size & (sizeof (uint32_t) - 1)) == 0
+ && ((uintptr_t) pbase) % __alignof__ (uint32_t) == 0)
+ {
+ if (size == sizeof (uint32_t))
+ return SWAP_WORDS_32;
+ else if (size == sizeof (uint64_t)
+ && ((uintptr_t) pbase) % __alignof__ (uint64_t) == 0)
+ return SWAP_WORDS_64;
+ }
+ return SWAP_BYTES;
+}
+
+
+/* A non-recursive heapsort with worst-case performance of O(nlog n) and
+ worst-case space complexity of O(1). It sorts the array starting at
+ BASE with n + 1 elements of SIZE bytes. The SWAP_TYPE is the callback
+ function used to swap elements, and CMP is the function used to compare
+ elements. */
static void
-heapsort_r (void *base, void *end, size_t size, enum swap_type_t swap_type,
- __compar_d_fn_t cmp, void *arg)
+heapsort_r (void *base, size_t n, size_t size, __compar_d_fn_t cmp, void *arg)
{
- size_t n = ((uintptr_t) end - (uintptr_t) base) / size;
if (n <= 1)
- /* Handled by insertion sort. */
return;
+ enum swap_type_t swap_type = get_swap_type (base, size);
+
/* Build the binary heap, largest value at the base[0]. */
heapify (base, size, n, swap_type, cmp, arg);
@@ -209,226 +189,226 @@ heapsort_r (void *base, void *end, size_t size, enum swap_type_t swap_type,
}
}
-static inline void
-insertion_sort_qsort_partitions (void *const pbase, size_t total_elems,
- size_t size, enum swap_type_t swap_type,
- __compar_d_fn_t cmp, void *arg)
+/* The maximum size in bytes required by mergesort that will be provided
+ through a buffer allocated in the stack. */
+#define QSORT_STACK_SIZE 1024
+
+/* Elements larger than this value will be sorted through indirect sorting
+ to minimize the need to memory swap calls. */
+#define INDIRECT_SORT_SIZE_THRES 32
+
+struct msort_param
{
- char *base_ptr = (char *) pbase;
- char *const end_ptr = &base_ptr[size * (total_elems - 1)];
- char *tmp_ptr = base_ptr;
-#define min(x, y) ((x) < (y) ? (x) : (y))
- const size_t max_thresh = MAX_THRESH * size;
- char *thresh = min(end_ptr, base_ptr + max_thresh);
- char *run_ptr;
+ size_t s;
+ enum swap_type_t var;
+ __compar_d_fn_t cmp;
+ void *arg;
+ char *t;
+};
- /* Find smallest element in first threshold and place it at the
- array's beginning. This is the smallest array element,
- and the operation speeds up insertion sort's inner loop. */
+static void
+msort_with_tmp (const struct msort_param *p, void *b, size_t n)
+{
+ char *b1, *b2;
+ size_t n1, n2;
- for (run_ptr = tmp_ptr + size; run_ptr <= thresh; run_ptr += size)
- if (cmp (run_ptr, tmp_ptr, arg) < 0)
- tmp_ptr = run_ptr;
+ if (n <= 1)
+ return;
- if (tmp_ptr != base_ptr)
- do_swap (tmp_ptr, base_ptr, size, swap_type);
+ n1 = n / 2;
+ n2 = n - n1;
+ b1 = b;
+ b2 = (char *) b + (n1 * p->s);
- /* Insertion sort, running from left-hand-side up to right-hand-side. */
+ msort_with_tmp (p, b1, n1);
+ msort_with_tmp (p, b2, n2);
- run_ptr = base_ptr + size;
- while ((run_ptr += size) <= end_ptr)
+ char *tmp = p->t;
+ const size_t s = p->s;
+ __compar_d_fn_t cmp = p->cmp;
+ void *arg = p->arg;
+ switch (p->var)
{
- tmp_ptr = run_ptr - size;
- while (tmp_ptr != base_ptr && cmp (run_ptr, tmp_ptr, arg) < 0)
- tmp_ptr -= size;
-
- tmp_ptr += size;
- if (tmp_ptr != run_ptr)
- {
- char *trav;
-
- trav = run_ptr + size;
- while (--trav >= run_ptr)
- {
- char c = *trav;
- char *hi, *lo;
-
- for (hi = lo = trav; (lo -= size) >= tmp_ptr; hi = lo)
- *hi = *lo;
- *hi = c;
- }
- }
+ case SWAP_WORDS_32:
+ while (n1 > 0 && n2 > 0)
+ {
+ if (cmp (b1, b2, arg) <= 0)
+ {
+ *(u32_alias_t *) tmp = *(u32_alias_t *) b1;
+ b1 += sizeof (u32_alias_t);
+ --n1;
+ }
+ else
+ {
+ *(u32_alias_t *) tmp = *(u32_alias_t *) b2;
+ b2 += sizeof (u32_alias_t);
+ --n2;
+ }
+ tmp += sizeof (u32_alias_t);
+ }
+ break;
+ case SWAP_WORDS_64:
+ while (n1 > 0 && n2 > 0)
+ {
+ if (cmp (b1, b2, arg) <= 0)
+ {
+ *(u64_alias_t *) tmp = *(u64_alias_t *) b1;
+ b1 += sizeof (u64_alias_t);
+ --n1;
+ }
+ else
+ {
+ *(u64_alias_t *) tmp = *(u64_alias_t *) b2;
+ b2 += sizeof (u64_alias_t);
+ --n2;
+ }
+ tmp += sizeof (u64_alias_t);
+ }
+ break;
+ case SWAP_VOID_ARG:
+ while (n1 > 0 && n2 > 0)
+ {
+ if ((*cmp) (*(const void **) b1, *(const void **) b2, arg) <= 0)
+ {
+ *(void **) tmp = *(void **) b1;
+ b1 += sizeof (void *);
+ --n1;
+ }
+ else
+ {
+ *(void **) tmp = *(void **) b2;
+ b2 += sizeof (void *);
+ --n2;
+ }
+ tmp += sizeof (void *);
+ }
+ break;
+ default:
+ while (n1 > 0 && n2 > 0)
+ {
+ if (cmp (b1, b2, arg) <= 0)
+ {
+ tmp = (char *) __mempcpy (tmp, b1, s);
+ b1 += s;
+ --n1;
+ }
+ else
+ {
+ tmp = (char *) __mempcpy (tmp, b2, s);
+ b2 += s;
+ --n2;
+ }
+ }
+ break;
}
-}
-
-/* Order size using quicksort. This implementation incorporates
- four optimizations discussed in Sedgewick:
- 1. Non-recursive, using an explicit stack of pointer that store the
- next array partition to sort. To save time, this maximum amount
- of space required to store an array of SIZE_MAX is allocated on the
- stack. Assuming a 32-bit (64 bit) integer for size_t, this needs
- only 32 * sizeof(stack_node) == 256 bytes (for 64 bit: 1024 bytes).
- Pretty cheap, actually.
-
- 2. Chose the pivot element using a median-of-three decision tree.
- This reduces the probability of selecting a bad pivot value and
- eliminates certain extraneous comparisons.
+ if (n1 > 0)
+ memcpy (tmp, b1, n1 * s);
+ memcpy (b, p->t, (n - n2) * s);
+}
- 3. Only quicksorts TOTAL_ELEMS / MAX_THRESH partitions, leaving
- insertion sort to order the MAX_THRESH items within each partition.
- This is a big win, since insertion sort is faster for small, mostly
- sorted array segments.
+static void
+__attribute_used__
+indirect_msort_with_tmp (const struct msort_param *p, void *b, size_t n,
+ size_t s)
+{
+ /* Indirect sorting. */
+ char *ip = (char *) b;
+ void **tp = (void **) (p->t + n * sizeof (void *));
+ void **t = tp;
+ void *tmp_storage = (void *) (tp + n);
- 4. The larger of the two sub-partitions is always pushed onto the
- stack first, with the algorithm then concentrating on the
- smaller partition. This *guarantees* no more than log (total_elems)
- stack size is needed (actually O(1) in this case)! */
+ while ((void *) t < tmp_storage)
+ {
+ *t++ = ip;
+ ip += s;
+ }
+ msort_with_tmp (p, p->t + n * sizeof (void *), n);
+
+ /* tp[0] .. tp[n - 1] is now sorted, copy around entries of
+ the original array. Knuth vol. 3 (2nd ed.) exercise 5.2-10. */
+ char *kp;
+ size_t i;
+ for (i = 0, ip = (char *) b; i < n; i++, ip += s)
+ if ((kp = tp[i]) != ip)
+ {
+ size_t j = i;
+ char *jp = ip;
+ memcpy (tmp_storage, ip, s);
+
+ do
+ {
+ size_t k = (kp - (char *) b) / s;
+ tp[j] = jp;
+ memcpy (jp, kp, s);
+ j = k;
+ jp = kp;
+ kp = tp[k];
+ }
+ while (kp != ip);
+
+ tp[j] = jp;
+ memcpy (jp, tmp_storage, s);
+ }
+}
void
__qsort_r (void *const pbase, size_t total_elems, size_t size,
__compar_d_fn_t cmp, void *arg)
{
- char *base_ptr = (char *) pbase;
-
- const size_t max_thresh = MAX_THRESH * size;
-
if (total_elems <= 1)
- /* Avoid lossage with unsigned arithmetic below. */
return;
- enum swap_type_t swap_type;
- if (is_aligned (pbase, size, 8))
- swap_type = SWAP_WORDS_64;
- else if (is_aligned (pbase, size, 4))
- swap_type = SWAP_WORDS_32;
- else
- swap_type = SWAP_BYTES;
+ /* Align to the maximum size used by the swap optimization. */
+ _Alignas (uint64_t) char tmp[QSORT_STACK_SIZE];
+ size_t total_size = total_elems * size;
+ char *buf;
- /* Maximum depth before quicksort switches to heapsort. */
- size_t depth = 2 * (sizeof (size_t) * CHAR_BIT - 1
- - __builtin_clzl (total_elems));
+ if (size > INDIRECT_SORT_SIZE_THRES)
+ total_size = 2 * total_elems * sizeof (void *) + size;
- if (total_elems > MAX_THRESH)
+ if (total_size < sizeof buf)
+ buf = tmp;
+ else
{
- char *lo = base_ptr;
- char *hi = &lo[size * (total_elems - 1)];
- stack_node stack[STACK_SIZE];
- stack_node *top = push (stack, NULL, NULL, depth);
-
- while (stack < top)
- {
- if (depth == 0)
- {
- heapsort_r (lo, hi, size, swap_type, cmp, arg);
- top = pop (top, &lo, &hi, &depth);
- continue;
- }
-
- char *left_ptr;
- char *right_ptr;
-
- /* Select median value from among LO, MID, and HI. Rearrange
- LO and HI so the three values are sorted. This lowers the
- probability of picking a pathological pivot value and
- skips a comparison for both the LEFT_PTR and RIGHT_PTR in
- the while loops. */
-
- char *mid = lo + size * ((hi - lo) / size >> 1);
-
- if ((*cmp) ((void *) mid, (void *) lo, arg) < 0)
- do_swap (mid, lo, size, swap_type);
- if ((*cmp) ((void *) hi, (void *) mid, arg) < 0)
- do_swap (mid, hi, size, swap_type);
- else
- goto jump_over;
- if ((*cmp) ((void *) mid, (void *) lo, arg) < 0)
- do_swap (mid, lo, size, swap_type);
- jump_over:;
-
- left_ptr = lo + size;
- right_ptr = hi - size;
-
- /* Here's the famous ``collapse the walls'' section of quicksort.
- Gotta like those tight inner loops! They are the main reason
- that this algorithm runs much faster than others. */
- do
- {
- while (left_ptr != mid
- && (*cmp) ((void *) left_ptr, (void *) mid, arg) < 0)
- left_ptr += size;
-
- while (right_ptr != mid
- && (*cmp) ((void *) mid, (void *) right_ptr, arg) < 0)
- right_ptr -= size;
-
- if (left_ptr < right_ptr)
- {
- do_swap (left_ptr, right_ptr, size, swap_type);
- if (mid == left_ptr)
- mid = right_ptr;
- else if (mid == right_ptr)
- mid = left_ptr;
- left_ptr += size;
- right_ptr -= size;
- }
- else if (left_ptr == right_ptr)
- {
- left_ptr += size;
- right_ptr -= size;
- break;
- }
- }
- while (left_ptr <= right_ptr);
-
- /* Set up pointers for next iteration. First determine whether
- left and right partitions are below the threshold size. If so,
- ignore one or both. Otherwise, push the larger partition's
- bounds on the stack and continue sorting the smaller one. */
-
- if ((size_t) (right_ptr - lo) <= max_thresh)
- {
- if ((size_t) (hi - left_ptr) <= max_thresh)
- /* Ignore both small partitions. */
- {
- top = pop (top, &lo, &hi, &depth);
- --depth;
- }
- else
- {
- /* Ignore small left partition. */
- lo = left_ptr;
- --depth;
- }
- }
- else if ((size_t) (hi - left_ptr) <= max_thresh)
- /* Ignore small right partition. */
- {
- hi = right_ptr;
- --depth;
- }
- else if ((right_ptr - lo) > (hi - left_ptr))
- {
- /* Push larger left partition indices. */
- top = push (top, lo, right_ptr, depth - 1);
- lo = left_ptr;
- }
- else
- {
- /* Push larger right partition indices. */
- top = push (top, left_ptr, hi, depth - 1);
- hi = right_ptr;
- }
- }
+ int save = errno;
+ buf = malloc (total_size);
+ __set_errno (save);
+ if (buf == NULL)
+ {
+ /* Fallback to heapsort in case of memory failure. */
+ heapsort_r (pbase, total_elems - 1, size, cmp, arg);
+ return;
+ }
+ }
+
+ if (size > INDIRECT_SORT_SIZE_THRES)
+ {
+ const struct msort_param msort_param =
+ {
+ .s = sizeof (void *),
+ .cmp = cmp,
+ .arg = arg,
+ .var = SWAP_VOID_ARG,
+ .t = buf,
+ };
+ indirect_msort_with_tmp (&msort_param, pbase, total_elems, size);
+ }
+ else
+ {
+ const struct msort_param msort_param =
+ {
+ .s = size,
+ .cmp = cmp,
+ .arg = arg,
+ .var = get_swap_type (pbase, size),
+ .t = buf,
+ };
+ msort_with_tmp (&msort_param, pbase, total_elems);
}
- /* Once the BASE_PTR array is partially sorted by quicksort the rest
- is completely sorted using insertion sort, since this is efficient
- for partitions below MAX_THRESH size. BASE_PTR points to the beginning
- of the array to sort, and END_PTR points at the very last element in
- the array (*not* one beyond it!). */
- insertion_sort_qsort_partitions (pbase, total_elems, size, swap_type, cmp,
- arg);
+ if (buf != tmp)
+ free (buf);
}
libc_hidden_def (__qsort_r)
weak_alias (__qsort_r, qsort_r)
diff --git a/stdlib/tst-qsort4.c b/stdlib/tst-qsort4.c
index a7abaa1a37461666..4cf373f22e28fade 100644
--- a/stdlib/tst-qsort4.c
+++ b/stdlib/tst-qsort4.c
@@ -30,35 +30,12 @@ cmp (const void *a1, const void *b1, void *closure)
return *a - *b;
}
-/* Wrapper around heapsort_r that set ups the required variables. */
-static void
-heapsort_wrapper (void *const pbase, size_t total_elems, size_t size,
- __compar_d_fn_t cmp, void *arg)
-{
- char *base_ptr = (char *) pbase;
- char *lo = base_ptr;
- char *hi = &lo[size * (total_elems - 1)];
-
- if (total_elems <= 1)
- /* Avoid lossage with unsigned arithmetic below. */
- return;
-
- enum swap_type_t swap_type;
- if (is_aligned (pbase, size, 8))
- swap_type = SWAP_WORDS_64;
- else if (is_aligned (pbase, size, 4))
- swap_type = SWAP_WORDS_32;
- else
- swap_type = SWAP_BYTES;
- heapsort_r (lo, hi, size, swap_type, cmp, arg);
-}
-
static void
check_one_sort (signed char *array, int length)
{
signed char *copy = xmalloc (length);
memcpy (copy, array, length);
- heapsort_wrapper (copy, length, 1, cmp, NULL);
+ heapsort_r (copy, length - 1, 1, cmp, NULL);
/* Verify that the result is sorted. */
for (int i = 1; i < length; ++i)
diff --git a/stdlib/tst-qsort5.c b/stdlib/tst-qsort5.c
deleted file mode 100644
index d3a88c30f8ffb135..0000000000000000
--- a/stdlib/tst-qsort5.c
+++ /dev/null
@@ -1,171 +0,0 @@
-/* Adversarial test for qsort_r.
- Copyright (C) 2023 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/>. */
-
-/* The approach follows Douglas McIlroy, A Killer Adversary for
- Quicksort. Software—Practice and Experience 29 (1999) 341-344.
- Downloaded <http://www.cs.dartmouth.edu/~doug/mdmspe.pdf>
- (2023-11-17). */
-
-#include <math.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <support/check.h>
-#include <support/support.h>
-
-struct context
-{
- /* Called the gas value in the paper. This value is larger than all
- other values (length minus one will do), so comparison with any
- decided value has a known result. */
- int undecided_value;
-
- /* If comparing undecided values, one of them as to be assigned a
- value to ensure consistency with future comparisons. This is the
- value that will be used. Starts out at zero. */
- int next_decided;
-
- /* Used to trick pivot selection. Deciding the value for the last
- seen undcided value in a decided/undecided comparison happens
- to trick the many qsort implementations. */
- int last_undecided_index;
-
- /* This array contains the actually asigned values. The call to
- qsort_r sorts a different array that contains indices into this
- array. */
- int *decided_values;
-};
-
-static int
-compare_opponent (const void *l1, const void *r1, void *ctx1)
-{
- const int *l = l1;
- const int *r = r1;
- struct context *ctx = ctx1;
- int rvalue = ctx->decided_values[*r];
- int lvalue = ctx->decided_values[*l];
-
- if (lvalue == ctx->undecided_value)
- {
- if (rvalue == ctx->undecided_value)
- {
- /* Both values are undecided. In this case, make a decision
- for the last-used undecided value. This is tweak is very
- specific to quicksort. */
- if (*l == ctx->last_undecided_index)
- {
- ctx->decided_values[*l] = ctx->next_decided;
- ++ctx->next_decided;
- /* The undecided value or *r is greater. */
- return -1;
- }
- else
- {
- ctx->decided_values[*r] = ctx->next_decided;
- ++ctx->next_decided;
- /* The undecided value for *l is greater. */
- return 1;
- }
- }
- else
- {
- ctx->last_undecided_index = *l;
- return 1;
- }
- }
- else
- {
- /* *l is a decided value. */
- if (rvalue == ctx->undecided_value)
- {
- ctx->last_undecided_index = *r;
- /* The undecided value for *r is greater. */
- return -1;
- }
- else
- return lvalue - rvalue;
- }
-}
-
-/* Return a pointer to the adversarial permutation of length N. */
-static int *
-create_permutation (size_t n)
-{
- struct context ctx =
- {
- .undecided_value = n - 1, /* Larger than all other values. */
- .decided_values = xcalloc (n, sizeof (int)),
- };
- for (size_t i = 0; i < n; ++i)
- ctx.decided_values[i] = ctx.undecided_value;
- int *scratch = xcalloc (n, sizeof (int));
- for (size_t i = 0; i < n; ++i)
- scratch[i] = i;
- qsort_r (scratch, n, sizeof (*scratch), compare_opponent, &ctx);
- free (scratch);
- return ctx.decided_values;
-}
-
-/* Callback function for qsort which counts the number of invocations
- in *CLOSURE. */
-static int
-compare_counter (const void *l1, const void *r1, void *closure)
-{
- const int *l = l1;
- const int *r = r1;
- unsigned long long int *counter = closure;
- ++*counter;
- return *l - *r;
-}
-
-/* Count the comparisons required for an adversarial permutation of
- length N. */
-static unsigned long long int
-count_comparisons (size_t n)
-{
- int *array = create_permutation (n);
- unsigned long long int counter = 0;
- qsort_r (array, n, sizeof (*array), compare_counter, &counter);
- free (array);
- return counter;
-}
-
-/* Check the scaling factor for one adversarial permutation of length
- N, and report some statistics. */
-static void
-check_one_n (size_t n)
-{
- unsigned long long int count = count_comparisons (n);
- double factor = count / (n * log (count));
- printf ("info: length %zu: %llu comparisons ~ %f * n * log (n)\n",
- n, count, factor);
- /* This is an arbitrary factor which is true for the current
- implementation across a wide range of sizes. */
- TEST_VERIFY (factor <= 4.5);
-}
-
-static int
-do_test (void)
-{
- check_one_n (100);
- check_one_n (1000);
- for (int i = 1; i <= 15; ++i)
- check_one_n (i * 10 * 1000);
- return 0;
-}
-
-#include <support/test-driver.c>

27
glibc-RHEL-24168-16.patch Normal file
View File

@ -0,0 +1,27 @@
commit 74d2731a5fb2676b64092bc25e7f193db1b17b2b
Author: Kuan-Wei Chiu <visitorckw@gmail.com>
Date: Tue Jan 16 10:16:56 2024 +0800
stdlib: Fix heapsort for cases with exactly two elements
When malloc fails to allocate a buffer and falls back to heapsort, the
current heapsort implementation does not perform sorting when there are
exactly two elements. Heapsort is now skipped only when there is
exactly one element.
Signed-off-by: Kuan-Wei Chiu <visitorckw@gmail.com>
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
diff --git a/stdlib/qsort.c b/stdlib/qsort.c
index b95889047ba31193..7b6c7e1f79974157 100644
--- a/stdlib/qsort.c
+++ b/stdlib/qsort.c
@@ -163,7 +163,7 @@ get_swap_type (void *const pbase, size_t size)
static void
heapsort_r (void *base, size_t n, size_t size, __compar_d_fn_t cmp, void *arg)
{
- if (n <= 1)
+ if (n == 0)
return;
enum swap_type_t swap_type = get_swap_type (base, size);

27
glibc-RHEL-24168-17.patch Normal file
View File

@ -0,0 +1,27 @@
commit 1bb28b7b4f01709b841c86850e1bb83b554feafe
Author: Kuan-Wei Chiu <visitorckw@gmail.com>
Date: Tue Jan 16 10:16:57 2024 +0800
stdlib: Verify heapsort for two-element cases
Adjust the testing approach to start from scenarios with only 2
elements, as insertion sort no longer handles such cases.
Signed-off-by: Kuan-Wei Chiu <visitorckw@gmail.com>
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
diff --git a/stdlib/tst-qsort4.c b/stdlib/tst-qsort4.c
index 4cf373f22e28fade..7909793d9eb3edc7 100644
--- a/stdlib/tst-qsort4.c
+++ b/stdlib/tst-qsort4.c
@@ -96,9 +96,7 @@ do_test (void)
check_one_sort ((signed char[16]) {15, 3, 4, 2, 1, 0, 8, 7, 6, 5, 14,
13, 12, 11, 10, 9}, 16);
- /* Array lengths 2 and less are not handled by heapsort_r and
- deferred to insertion sort. */
- for (int i = 3; i <= 8; ++i)
+ for (int i = 2; i <= 8; ++i)
{
signed char *buf = xmalloc (i);
check_combinations (i, buf, 0);

32
glibc-RHEL-24168-18.patch Normal file
View File

@ -0,0 +1,32 @@
commit 31bd548650673e8b5ae1a31f1c596ff8305a5d4c
Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Date: Wed Jan 17 08:08:01 2024 -0300
stdlib: Remove unused is_aligned function from qsort.c
Checked on x86_64-linux-gnu.
diff --git a/stdlib/qsort.c b/stdlib/qsort.c
index 7b6c7e1f79974157..8db8a81d182dd1fc 100644
--- a/stdlib/qsort.c
+++ b/stdlib/qsort.c
@@ -41,19 +41,6 @@ enum swap_type_t
typedef uint32_t __attribute__ ((__may_alias__)) u32_alias_t;
typedef uint64_t __attribute__ ((__may_alias__)) u64_alias_t;
-/* If this function returns true, elements can be safely copied using word
- loads and stores. Otherwise, it might not be safe. BASE (as an integer)
- must be a multiple of the word alignment. SIZE must be a multiple of
- WORDSIZE. Since WORDSIZE must be a multiple of the word alignment, and
- WORDSIZE is a power of two on all supported platforms, this function for
- speed merely checks that BASE and SIZE are both multiples of the word
- size. */
-static inline bool
-is_aligned (const void *base, size_t size, size_t wordsize)
-{
- return (((uintptr_t) base | size) & (wordsize - 1)) == 0;
-}
-
static inline void
swap_words_64 (void * restrict a, void * restrict b, size_t n)
{

51
glibc-RHEL-24168-19.patch Normal file
View File

@ -0,0 +1,51 @@
commit dfa3394a605c8f6f25e4f827789bc89eca1d206c
Author: Xi Ruoyao <xry111@xry111.site>
Date: Tue Jan 23 04:29:18 2024 +0800
qsort: Fix a typo causing unnecessary malloc/free (BZ 31276)
In qsort_r we allocate a buffer sized QSORT_STACK_SIZE (1024) on stack
and we intend to use it if all elements can fit into it. But there is a
typo:
if (total_size < sizeof buf)
buf = tmp;
else
/* allocate a buffer on heap and use it ... */
Here "buf" is a pointer, thus sizeof buf is just 4 or 8, instead of
1024. There is also a minor issue that we should use "<=" instead of
"<".
This bug is detected debugging some strange heap corruption running the
Ruby-3.3.0 test suite (on an experimental Linux From Scratch build using
Binutils-2.41.90 and Glibc trunk, and also Fedora Rawhide [1]). It
seems Ruby is doing some wild "optimization" by jumping into somewhere
in qsort_r instead of calling it normally, resulting in a double free of
buf if we allocate it on heap. The issue can be reproduced
deterministically with:
LD_PRELOAD=/usr/lib/libc_malloc_debug.so MALLOC_CHECK_=3 \
LD_LIBRARY_PATH=. ./ruby test/runner.rb test/ruby/test_enum.rb
in Ruby-3.3.0 tree after building it. This change would hide the issue
for Ruby, but Ruby is likely still buggy (if using this "optimization"
sorting larger arrays).
[1]:https://kojipkgs.fedoraproject.org/work/tasks/9729/111889729/build.log
Signed-off-by: Xi Ruoyao <xry111@xry111.site>
diff --git a/stdlib/qsort.c b/stdlib/qsort.c
index 8db8a81d182dd1fc..2cdd5c1fe790f55c 100644
--- a/stdlib/qsort.c
+++ b/stdlib/qsort.c
@@ -354,7 +354,7 @@ __qsort_r (void *const pbase, size_t total_elems, size_t size,
if (size > INDIRECT_SORT_SIZE_THRES)
total_size = 2 * total_elems * sizeof (void *) + size;
- if (total_size < sizeof buf)
+ if (total_size <= sizeof tmp)
buf = tmp;
else
{

344
glibc-RHEL-24168-2.patch Normal file
View File

@ -0,0 +1,344 @@
commit d275970ab56f8ba6a3ca598aba75db4daabe5924
Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Date: Fri Apr 8 09:57:57 2022 -0300
stdlib: Reflow and sort most variable assignments
diff --git a/stdlib/Makefile b/stdlib/Makefile
index fe43bec0f9d581d5..03f8478c64408ed3 100644
--- a/stdlib/Makefile
+++ b/stdlib/Makefile
@@ -22,49 +22,145 @@ subdir := stdlib
include ../Makeconfig
-headers := stdlib.h bits/stdlib.h bits/stdlib-ldbl.h bits/stdlib-float.h \
- monetary.h bits/monetary-ldbl.h \
- inttypes.h stdint.h bits/wordsize.h bits/timesize.h \
- errno.h sys/errno.h bits/errno.h bits/types/error_t.h \
- ucontext.h sys/ucontext.h bits/indirect-return.h \
- alloca.h fmtmsg.h \
- bits/stdlib-bsearch.h sys/random.h bits/stdint-intn.h \
- bits/stdint-uintn.h bits/time64.h \
-
-routines := \
- atof atoi atol atoll \
- abort \
- bsearch qsort msort \
- getenv putenv setenv secure-getenv \
- exit on_exit atexit cxa_atexit cxa_finalize old_atexit \
- quick_exit at_quick_exit cxa_at_quick_exit cxa_thread_atexit_impl \
- abs labs llabs \
- div ldiv lldiv \
- mblen mbstowcs mbtowc wcstombs wctomb \
- random random_r rand rand_r \
- drand48 erand48 lrand48 nrand48 mrand48 jrand48 \
- srand48 seed48 lcong48 \
- drand48_r erand48_r lrand48_r nrand48_r mrand48_r jrand48_r \
- srand48_r seed48_r lcong48_r \
- drand48-iter getrandom getentropy \
- strfromf strfromd strfroml \
- strtol strtoul strtoll strtoull \
- strtol_l strtoul_l strtoll_l strtoull_l \
- strtof strtod strtold \
- strtof_l strtod_l strtold_l \
- strtof_nan strtod_nan strtold_nan \
- system canonicalize \
- a64l l64a \
- rpmatch strfmon strfmon_l getsubopt xpg_basename fmtmsg \
- getcontext setcontext makecontext swapcontext
-aux = grouping groupingwc tens_in_limb
+headers := \
+ alloca.h \
+ bits/errno.h \
+ bits/indirect-return.h \
+ bits/monetary-ldbl.h \
+ bits/stdint-intn.h \
+ bits/stdint-uintn.h \
+ bits/stdlib-bsearch.h \
+ bits/stdlib-float.h \
+ bits/stdlib.h \
+ bits/stdlib-ldbl.h \
+ bits/time64.h \
+ bits/timesize.h \
+ bits/types/error_t.h \
+ bits/wordsize.h \
+ errno.h \
+ fmtmsg.h \
+ inttypes.h \
+ monetary.h \
+ stdint.h \
+ stdlib.h \
+ sys/errno.h \
+ sys/random.h \
+ sys/ucontext.h \
+ ucontext.h \
+ # headers
+
+routines := \
+ a64l \
+ abort \
+ abs \
+ at_quick_exit \
+ atof \
+ atoi \
+ atol\
+ atoll \
+ bsearch \
+ canonicalize \
+ cxa_at_quick_exit \
+ cxa_atexit \
+ cxa_finalize \
+ cxa_thread_atexit_impl \
+ div \
+ drand48 \
+ drand48-iter \
+ drand48_r \
+ erand48 \
+ erand48_r \
+ exit \
+ fmtmsg \
+ getcontext \
+ getentropy \
+ getenv \
+ getrandom \
+ getsubopt \
+ jrand48 \
+ jrand48_r \
+ l64a \
+ labs \
+ lcong48 \
+ lcong48_r \
+ ldiv \
+ llabs \
+ lldiv \
+ lrand48 \
+ lrand48_r \
+ makecontext \
+ mblen \
+ mbstowcs \
+ mbtowc \
+ mrand48 \
+ mrand48_r \
+ msort \
+ nrand48 \
+ nrand48_r \
+ old_atexit \
+ on_exit atexit \
+ putenv \
+ qsort \
+ quick_exit \
+ rand \
+ rand_r \
+ random \
+ random_r \
+ rpmatch \
+ secure-getenv \
+ seed48 \
+ seed48_r \
+ setcontext \
+ setenv \
+ srand48 \
+ srand48_r \
+ strfmon \
+ strfmon_l \
+ strfromd \
+ strfromf \
+ strfroml \
+ strtod \
+ strtod_l \
+ strtod_nan \
+ strtof \
+ strtof_l \
+ strtof_nan \
+ strtol \
+ strtol_l \
+ strtold \
+ strtold_l \
+ strtold_nan \
+ strtoll \
+ strtoll_l \
+ strtoul \
+ strtoul_l \
+ strtoull \
+ strtoull_l \
+ swapcontext \
+ system \
+ wcstombs \
+ wctomb \
+ xpg_basename \
+ # routines
+
+aux = \
+ grouping \
+ groupingwc \
+ tens_in_limb \
+ # aux
# These routines will be omitted from the libc shared object.
# Instead the static object files will be included in a special archive
# linked against when the shared library will be used.
-static-only-routines = atexit at_quick_exit
+static-only-routines = \
+ atexit \
+ at_quick_exit \
+ # static-only-routines
+
+test-srcs := \
+ tst-fmtmsg \
+ #test-srcs
-test-srcs := tst-fmtmsg
tests := \
bug-fmtmsg1 \
bug-getcontext \
@@ -155,15 +251,29 @@ tests := \
tst-width \
tst-width-stdint \
tst-xpg-basename \
-# tests
+ # tests
+
+tests-internal := \
+ tst-strtod1i \
+ tst-strtod3 \
+ tst-strtod4 \
+ tst-strtod5i \
+ tst-tls-atexit \
+ tst-tls-atexit-nodelete \
+ # tests-internal
+
+tests-static := \
+ tst-secure-getenv \
+ # tests-static
-tests-internal := tst-strtod1i tst-strtod3 tst-strtod4 tst-strtod5i \
- tst-tls-atexit tst-tls-atexit-nodelete
-tests-static := tst-secure-getenv
-tests-container := tst-system
+tests-container := \
+ tst-system \
+ #tests-container
ifeq ($(build-hardcoded-path-in-tests),yes)
-tests += tst-empty-env
+tests += \
+ tst-empty-env \
+ # tests
endif
LDLIBS-test-atexit-race = $(shared-thread-library)
@@ -188,30 +298,76 @@ CFLAGS-tst-thread-quick_exit.o = -std=c++11
LDLIBS-tst-thread-quick_exit = -lstdc++
$(objpfx)tst-thread-quick_exit: $(shared-thread-library)
else
-tests-unsupported += tst-quick_exit tst-thread-quick_exit
+tests-unsupported += \
+ tst-quick_exit \
+ tst-thread-quick_exit \
+ # tests-unsupported
endif
-modules-names = tst-tls-atexit-lib test-dlclose-exit-race-helper
+modules-names = \
+ test-dlclose-exit-race-helper \
+ tst-tls-atexit-lib \
+ # modules-names
extra-test-objs += $(addsuffix .os, $(modules-names))
ifeq ($(build-shared),yes)
-tests += tst-putenv
+tests += \
+ tst-putenv \
+ # tests
endif
# Several mpn functions from GNU MP are used by the strtod function.
-mpn-routines := inlines add_n addmul_1 cmp divmod_1 divrem udiv_qrnnd \
- lshift rshift mod_1 mul mul_1 mul_n sub_n submul_1
-mpn-headers = longlong.h gmp.h gmp-impl.h gmp-mparam.h asm-syntax.h
-
-routines := $(strip $(routines) $(mpn-routines)) \
- dbl2mpn ldbl2mpn \
- mpn2flt mpn2dbl mpn2ldbl
-aux += fpioconst mp_clz_tab
-
-tests-extras += tst-putenvmod
-extra-test-objs += tst-putenvmod.os
-
-generated += isomac isomac.out tst-putenvmod.so
+mpn-routines := \
+ add_n \
+ addmul_1 \
+ cmp \
+ divmod_1 \
+ divrem \
+ inlines \
+ lshift \
+ mod_1 \
+ mul \
+ mul_1 \
+ mul_n \
+ rshift \
+ sub_n \
+ submul_1 \
+ udiv_qrnnd \
+ # mpn-routines
+mpn-headers = \
+ asm-syntax.h \
+ gmp-impl.h \
+ gmp-mparam.h \
+ gmp.h \
+ longlong.h \
+ # mpn-headers
+
+routines := \
+ $(strip $(routines) $(mpn-routines)) \
+ dbl2mpn \
+ ldbl2mpn \
+ mpn2dbl \
+ mpn2flt \
+ mpn2ldbl \
+ # routines
+aux += \
+ fpioconst \
+ mp_clz_tab \
+ # aux
+
+tests-extras += \
+ tst-putenvmod \
+ # tests-extras
+
+extra-test-objs += \
+ tst-putenvmod.os \
+ # extra-test-objs
+
+generated += \
+ isomac \
+ isomac.out \
+ tst-putenvmod.so \
+ # generated
CFLAGS-bsearch.c += $(uses-callbacks)
CFLAGS-msort.c += $(uses-callbacks)
@@ -247,9 +403,17 @@ endif
include ../Rules
ifeq ($(run-built-tests),yes)
-LOCALES := cs_CZ.UTF-8 de_DE.UTF-8 en_US.ISO-8859-1 tr_TR.UTF-8 \
- tr_TR.ISO-8859-9 tg_TJ.UTF-8 hr_HR.UTF-8 hi_IN.UTF-8 \
- el_GR.UTF-8
+LOCALES := \
+ cs_CZ.UTF-8 \
+ de_DE.UTF-8 \
+ el_GR.UTF-8 \
+ en_US.ISO-8859-1 \
+ hi_IN.UTF-8 \
+ hr_HR.UTF-8 \
+ tg_TJ.UTF-8 \
+ tr_TR.ISO-8859-9 \
+ tr_TR.UTF-8 \
+ # LOCALES
include ../gen-locales.mk
$(objpfx)bug-strtod2.out: $(gen-locales)

65
glibc-RHEL-24168-20.patch Normal file
View File

@ -0,0 +1,65 @@
commit e7b90e6e605cf236d4bd79e4930cd6a46f9932c7
Author: Paul Eggert <eggert@cs.ucla.edu>
Date: Thu Feb 1 11:52:46 2024 -0800
stdlib: fix qsort example in manual
* manual/search.texi (Comparison Functions, Array Sort Function):
Sort an array of long ints, not doubles, to avoid hassles
with NaNs.
Reviewed-by: Siddhesh Poyarekar <siddhesh@sourceware.org>
diff --git a/manual/search.texi b/manual/search.texi
index ffaadc46f51b18f9..db577a5332651c36 100644
--- a/manual/search.texi
+++ b/manual/search.texi
@@ -35,19 +35,22 @@ second, zero if they are ``equal'', and positive if the first argument
is ``greater''.
Here is an example of a comparison function which works with an array of
-numbers of type @code{double}:
+numbers of type @code{long int}:
@smallexample
int
-compare_doubles (const void *a, const void *b)
+compare_long_ints (const void *a, const void *b)
@{
- const double *da = (const double *) a;
- const double *db = (const double *) b;
+ const long int *la = a;
+ const long int *lb = b;
- return (*da > *db) - (*da < *db);
+ return (*la > *lb) - (*la < *lb);
@}
@end smallexample
+(The code would have to be more complicated for an array of @code{double},
+to handle NaNs correctly.)
+
The header file @file{stdlib.h} defines a name for the data type of
comparison functions. This type is a GNU extension.
@@ -183,16 +186,16 @@ in the array before making some comparisons. The only way to perform
a stable sort with @code{qsort} is to first augment the objects with a
monotonic counter of some kind.
-Here is a simple example of sorting an array of doubles in numerical
+Here is a simple example of sorting an array of @code{long int} in numerical
order, using the comparison function defined above (@pxref{Comparison
Functions}):
@smallexample
@{
- double *array;
- int size;
+ long int *array;
+ size_t nmemb;
@dots{}
- qsort (array, size, sizeof (double), compare_doubles);
+ qsort (array, nmemb, sizeof *array, compare_long_ints);
@}
@end smallexample

142
glibc-RHEL-24168-21.patch Normal file
View File

@ -0,0 +1,142 @@
commit 57581acd9559217e859fdac693145ce6399f4d70
Author: Paul Eggert <eggert@cs.ucla.edu>
Date: Sat Apr 6 08:44:01 2024 -0700
Fix bsearch, qsort doc to match POSIX better
* manual/search.texi (Array Search Function):
Correct the statement about lfinds mean runtime:
it is proportional to a number (not that number),
and this is true only if random elements are searched for.
Relax the constraint on bsearchs array argument:
POSIX says it need not be sorted, only partially sorted.
Say that the first arg passed to bsearchs comparison function
is the key, and the second arg is an array element, as
POSIX requires. For bsearch and qsort, say that the
comparison function should not alter the array, as POSIX
requires. For qsort, say that the comparison function
must define a total order, as POSIX requires, that
it should not depend on element addresses, that
the original array index can be used for stable sorts,
and that if qsort still works if memory allocation fails.
Be more consistent in calling the array elements
“elements” rather than “objects”.
Co-authored-by: Zack Weinberg <zack@owlfolio.org>
diff --git a/manual/search.texi b/manual/search.texi
index db577a5332651c36..cb08c494092ef77f 100644
--- a/manual/search.texi
+++ b/manual/search.texi
@@ -84,8 +84,9 @@ The return value is a pointer to the matching element in the array
starting at @var{base} if it is found. If no matching element is
available @code{NULL} is returned.
-The mean runtime of this function is @code{*@var{nmemb}}/2. This
-function should only be used if elements often get added to or deleted from
+The mean runtime of this function is proportional to @code{*@var{nmemb}/2},
+assuming random elements of the array are searched for. This
+function should be used only if elements often get added to or deleted from
the array in which case it might not be useful to sort the array before
searching.
@end deftypefun
@@ -122,26 +123,34 @@ bytes. If one is sure the element is in the array it is better to use
calling @code{lsearch}.
@end deftypefun
-To search a sorted array for an element matching the key, use the
-@code{bsearch} function. The prototype for this function is in
+To search a sorted or partially sorted array for an element matching the key,
+use the @code{bsearch} function. The prototype for this function is in
the header file @file{stdlib.h}.
@pindex stdlib.h
@deftypefun {void *} bsearch (const void *@var{key}, const void *@var{array}, size_t @var{count}, size_t @var{size}, comparison_fn_t @var{compare})
@standards{ISO, stdlib.h}
@safety{@prelim{}@mtsafe{}@assafe{}@acsafe{}}
-The @code{bsearch} function searches the sorted array @var{array} for an object
+The @code{bsearch} function searches @var{array} for an element
that is equivalent to @var{key}. The array contains @var{count} elements,
each of which is of size @var{size} bytes.
The @var{compare} function is used to perform the comparison. This
-function is called with two pointer arguments and should return an
+function is called with arguments that point to the key and to an
+array element, in that order, and should return an
integer less than, equal to, or greater than zero corresponding to
-whether its first argument is considered less than, equal to, or greater
-than its second argument. The elements of the @var{array} must already
-be sorted in ascending order according to this comparison function.
-
-The return value is a pointer to the matching array element, or a null
+whether the key is considered less than, equal to, or greater than
+the array element. The function should not alter the array's contents,
+and the same array element should always compare the same way with the key.
+
+Although the array need not be completely sorted, it should be
+partially sorted with respect to @var{key}. That is, the array should
+begin with elements that compare less than @var{key}, followed by
+elements that compare equal to @var{key}, and ending with elements
+that compare greater than @var{key}. Any or all of these element
+sequences can be empty.
+
+The return value is a pointer to a matching array element, or a null
pointer if no match is found. If the array contains more than one element
that matches, the one that is returned is unspecified.
@@ -171,20 +180,22 @@ array elements. This function is called with two pointer arguments and
should return an integer less than, equal to, or greater than zero
corresponding to whether its first argument is considered less than,
equal to, or greater than its second argument.
+The function must not alter the array's contents, and must define a
+total ordering on the array elements, including any unusual values
+such as floating-point NaN (@pxref{Infinity and NaN}).
+Because the sorting process can move elements,
+the function's return value must not depend on the element addresses
+or the relative positions of elements within the array,
+as these are meaningless while @code{qsort} is running.
@cindex stable sorting
-@strong{Warning:} If two objects compare as equal, their order after
+@strong{Warning:} If two elements compare equal, their order after
sorting is unpredictable. That is to say, the sorting is not stable.
This can make a difference when the comparison considers only part of
-the elements. Two elements with the same sort key may differ in other
-respects.
-
-Although the object addresses passed to the comparison function lie
-within the array, they need not correspond with the original locations
-of those objects because the sorting algorithm may swap around objects
-in the array before making some comparisons. The only way to perform
-a stable sort with @code{qsort} is to first augment the objects with a
-monotonic counter of some kind.
+the elements and two elements that compare equal may differ in other
+respects. To ensure a stable sort in this situation, you can augment
+each element with an appropriate tie-breaking value, such as its
+original array index.
Here is a simple example of sorting an array of @code{long int} in numerical
order, using the comparison function defined above (@pxref{Comparison
@@ -202,18 +213,19 @@ Functions}):
The @code{qsort} function derives its name from the fact that it was
originally implemented using the ``quick sort'' algorithm.
-The implementation of @code{qsort} attempts to allocate auxiliary storage
+The implementation of @code{qsort} attempts to allocate auxiliary memory
and use the merge sort algorithm, without violating C standard requirement
that arguments passed to the comparison function point within the array.
+If the memory allocation fails, @code{qsort} resorts to a slower algorithm.
@end deftypefun
@node Search/Sort Example
@section Searching and Sorting Example
Here is an example showing the use of @code{qsort} and @code{bsearch}
-with an array of structures. The objects in the array are sorted
+with an array of structures. The elements of the array are sorted
by comparing their @code{name} fields with the @code{strcmp} function.
-Then, we can look up individual objects based on their names.
+Then, we can look up individual elements based on their names.
@comment This example is dedicated to the memory of Jim Henson. RIP.
@smallexample

26
glibc-RHEL-24168-22.patch Normal file
View File

@ -0,0 +1,26 @@
commit 7eed691cc2b6c5dbb6066ee1251606a744c7f05c
Author: Arjun Shankar <arjun@redhat.com>
Date: Wed Jul 2 17:11:32 2025 +0200
stdlib/Makefile: Remove deleted test's libm dependency
tst-qsort5 was deleted in 709fbd3ec3595f2d1076b4fec09a739327459288.
Therefore remove its redundant libm dependency.
Reviewed-by: Florian Weimer <fweimer@redhat.com>
Conflicts:
stdlib/Makefile: Context line mismatch due to missing tests.
diff --git a/stdlib/Makefile b/stdlib/Makefile
index a9d91a57c08ac506..e517e306b868c432 100644
--- a/stdlib/Makefile
+++ b/stdlib/Makefile
@@ -484,7 +484,6 @@ $(objpfx)tst-setcontext3.out: tst-setcontext3.sh $(objpfx)tst-setcontext3
$(common-objpfx)stdlib/; \
$(evaluate-test)
-$(objpfx)tst-qsort5: $(libm)
$(objpfx)tst-getenv-signal: $(shared-thread-library)
$(objpfx)tst-getenv-thread: $(shared-thread-library)
$(objpfx)tst-getenv-unsetenv: $(shared-thread-library)

286
glibc-RHEL-24168-3.patch Normal file
View File

@ -0,0 +1,286 @@
commit fccf38c51746e0817c2409bb361398f9465e0760
Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Date: Tue Oct 3 09:22:45 2023 -0300
string: Add internal memswap implementation
The prototype is:
void __memswap (void *restrict p1, void *restrict p2, size_t n)
The function swaps the content of two memory blocks P1 and P2 of
len N. Memory overlap is NOT handled.
It will be used on qsort optimization.
Checked on x86_64-linux-gnu and aarch64-linux-gnu.
Reviewed-by: Noah Goldstein <goldstein.w.n@gmail.com>
diff --git a/string/Makefile b/string/Makefile
index 3e4331113f08424c..3ebf7597aad75bfe 100644
--- a/string/Makefile
+++ b/string/Makefile
@@ -66,6 +66,18 @@ tests := tester inl-tester noinl-tester testcopy test-ffs \
test-sig_np tst-strerror-fail \
test-strdup test-strndup
+tests-static-internal := \
+ test-memswap \
+# tests-static-internal
+
+tests-internal := \
+ $(tests-static-internal) \
+ # tests-internal
+
+tests-static := \
+ $(tests-static-internal) \
+ # tests-static
+
# Both tests require the .mo translation files generated by msgfmt.
tests-translation := tst-strsignal \
tst-strerror
diff --git a/string/test-memswap.c b/string/test-memswap.c
new file mode 100644
index 0000000000000000..162beb91e3e96c23
--- /dev/null
+++ b/string/test-memswap.c
@@ -0,0 +1,192 @@
+/* Test and measure memcpy functions.
+ Copyright (C) 2023 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
+ <https://www.gnu.org/licenses/>. */
+
+#include <string.h>
+#include <support/check.h>
+#include <memswap.h>
+
+#define TEST_MAIN
+#define BUF1PAGES 3
+#include "test-string.h"
+
+static unsigned char *ref1;
+static unsigned char *ref2;
+
+static void
+do_one_test (unsigned char *p1, unsigned char *ref1, unsigned char *p2,
+ unsigned char *ref2, size_t len)
+{
+ __memswap (p1, p2, len);
+
+ TEST_COMPARE_BLOB (p1, len, ref2, len);
+ TEST_COMPARE_BLOB (p2, len, ref1, len);
+}
+
+static inline void
+do_test (size_t align1, size_t align2, size_t len)
+{
+ align1 &= page_size;
+ if (align1 + len >= page_size)
+ return;
+
+ align2 &= page_size;
+ if (align2 + len >= page_size)
+ return;
+
+ unsigned char *p1 = buf1 + align1;
+ unsigned char *p2 = buf2 + align2;
+ for (size_t repeats = 0; repeats < 2; ++repeats)
+ {
+ size_t i, j;
+ for (i = 0, j = 1; i < len; i++, j += 23)
+ {
+ ref1[i] = p1[i] = j;
+ ref2[i] = p2[i] = UCHAR_MAX - j;
+ }
+
+ do_one_test (p1, ref1, p2, ref2, len);
+ }
+}
+
+static void
+do_random_tests (void)
+{
+ for (size_t n = 0; n < ITERATIONS; n++)
+ {
+ size_t len, size, size1, size2, align1, align2;
+
+ if (n == 0)
+ {
+ len = getpagesize ();
+ size = len + 512;
+ size1 = size;
+ size2 = size;
+ align1 = 512;
+ align2 = 512;
+ }
+ else
+ {
+ if ((random () & 255) == 0)
+ size = 65536;
+ else
+ size = 768;
+ if (size > page_size)
+ size = page_size;
+ size1 = size;
+ size2 = size;
+ size_t i = random ();
+ if (i & 3)
+ size -= 256;
+ if (i & 1)
+ size1 -= 256;
+ if (i & 2)
+ size2 -= 256;
+ if (i & 4)
+ {
+ len = random () % size;
+ align1 = size1 - len - (random () & 31);
+ align2 = size2 - len - (random () & 31);
+ if (align1 > size1)
+ align1 = 0;
+ if (align2 > size2)
+ align2 = 0;
+ }
+ else
+ {
+ align1 = random () & 63;
+ align2 = random () & 63;
+ len = random () % size;
+ if (align1 + len > size1)
+ align1 = size1 - len;
+ if (align2 + len > size2)
+ align2 = size2 - len;
+ }
+ }
+ unsigned char *p1 = buf1 + page_size - size1;
+ unsigned char *p2 = buf2 + page_size - size2;
+ size_t j = align1 + len + 256;
+ if (j > size1)
+ j = size1;
+ for (size_t i = 0; i < j; ++i)
+ ref1[i] = p1[i] = random () & 255;
+
+ j = align2 + len + 256;
+ if (j > size2)
+ j = size2;
+
+ for (size_t i = 0; i < j; ++i)
+ ref2[i] = p2[i] = random () & 255;
+
+ do_one_test (p1 + align1, ref1 + align1, p2 + align2, ref2 + align2, len);
+ }
+}
+
+static int
+test_main (void)
+{
+ test_init ();
+ /* Use the start of buf1 for reference buffers. */
+ ref1 = buf1;
+ ref2 = buf1 + page_size;
+ buf1 = ref2 + page_size;
+
+ printf ("%23s", "");
+ printf ("\t__memswap\n");
+
+ for (size_t i = 0; i < 18; ++i)
+ {
+ do_test (0, 0, 1 << i);
+ do_test (i, 0, 1 << i);
+ do_test (0, i, 1 << i);
+ do_test (i, i, 1 << i);
+ }
+
+ for (size_t i = 0; i < 32; ++i)
+ {
+ do_test (0, 0, i);
+ do_test (i, 0, i);
+ do_test (0, i, i);
+ do_test (i, i, i);
+ }
+
+ for (size_t i = 3; i < 32; ++i)
+ {
+ if ((i & (i - 1)) == 0)
+ continue;
+ do_test (0, 0, 16 * i);
+ do_test (i, 0, 16 * i);
+ do_test (0, i, 16 * i);
+ do_test (i, i, 16 * i);
+ }
+
+ for (size_t i = 19; i <= 25; ++i)
+ {
+ do_test (255, 0, 1 << i);
+ do_test (0, 4000, 1 << i);
+ do_test (0, 255, i);
+ do_test (0, 4000, i);
+ }
+
+ do_test (0, 0, getpagesize ());
+
+ do_random_tests ();
+
+ return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/sysdeps/generic/memswap.h b/sysdeps/generic/memswap.h
new file mode 100644
index 0000000000000000..f09dae1ebbc2ec0f
--- /dev/null
+++ b/sysdeps/generic/memswap.h
@@ -0,0 +1,41 @@
+/* Swap the content of two memory blocks, overlap is NOT handled.
+ Copyright (C) 2023 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
+ <https://www.gnu.org/licenses/>. */
+
+#include <string.h>
+
+static inline void
+__memswap (void *__restrict p1, void *__restrict p2, size_t n)
+{
+ /* Use multiple small memcpys with constant size to enable inlining on most
+ targets. */
+ enum { SWAP_GENERIC_SIZE = 32 };
+ unsigned char tmp[SWAP_GENERIC_SIZE];
+ while (n > SWAP_GENERIC_SIZE)
+ {
+ memcpy (tmp, p1, SWAP_GENERIC_SIZE);
+ p1 = __mempcpy (p1, p2, SWAP_GENERIC_SIZE);
+ p2 = __mempcpy (p2, tmp, SWAP_GENERIC_SIZE);
+ n -= SWAP_GENERIC_SIZE;
+ }
+ while (n > 0)
+ {
+ unsigned char t = ((unsigned char *)p1)[--n];
+ ((unsigned char *)p1)[n] = ((unsigned char *)p2)[n];
+ ((unsigned char *)p2)[n] = t;
+ }
+}

157
glibc-RHEL-24168-4.patch Normal file
View File

@ -0,0 +1,157 @@
commit 21d30c774c7f9f5878f0bf9438736c702b0a58a3
Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Date: Tue Oct 3 09:22:46 2023 -0300
stdlib: Optimization qsort{_r} swap implementation
The optimization takes in consideration both the most common elements
are either 32 or 64 bit in size and inputs are aligned to the word
boundary. This is similar to what msort does.
For large buffer the swap operation uses memcpy/mempcpy with a
small fixed size buffer (so compiler might inline the operations).
Checked on x86_64-linux-gnu.
Reviewed-by: Noah Goldstein <goldstein.w.n@gmail.com>
diff --git a/stdlib/qsort.c b/stdlib/qsort.c
index 23f2d283147073ac..59b220ba1c375ca3 100644
--- a/stdlib/qsort.c
+++ b/stdlib/qsort.c
@@ -22,22 +22,73 @@
#include <alloca.h>
#include <limits.h>
+#include <memswap.h>
#include <stdlib.h>
#include <string.h>
+#include <stdbool.h>
-/* Byte-wise swap two items of size SIZE. */
-#define SWAP(a, b, size) \
- do \
- { \
- size_t __size = (size); \
- char *__a = (a), *__b = (b); \
- do \
- { \
- char __tmp = *__a; \
- *__a++ = *__b; \
- *__b++ = __tmp; \
- } while (--__size > 0); \
- } while (0)
+/* Swap SIZE bytes between addresses A and B. These helpers are provided
+ along the generic one as an optimization. */
+
+enum swap_type_t
+ {
+ SWAP_WORDS_64,
+ SWAP_WORDS_32,
+ SWAP_BYTES
+ };
+
+/* If this function returns true, elements can be safely copied using word
+ loads and stores. Otherwise, it might not be safe. BASE (as an integer)
+ must be a multiple of the word alignment. SIZE must be a multiple of
+ WORDSIZE. Since WORDSIZE must be a multiple of the word alignment, and
+ WORDSIZE is a power of two on all supported platforms, this function for
+ speed merely checks that BASE and SIZE are both multiples of the word
+ size. */
+static inline bool
+is_aligned (const void *base, size_t size, size_t wordsize)
+{
+ return (((uintptr_t) base | size) & (wordsize - 1)) == 0;
+}
+
+static inline void
+swap_words_64 (void * restrict a, void * restrict b, size_t n)
+{
+ typedef uint64_t __attribute__ ((__may_alias__)) u64_alias_t;
+ do
+ {
+ n -= 8;
+ u64_alias_t t = *(u64_alias_t *)(a + n);
+ *(u64_alias_t *)(a + n) = *(u64_alias_t *)(b + n);
+ *(u64_alias_t *)(b + n) = t;
+ } while (n);
+}
+
+static inline void
+swap_words_32 (void * restrict a, void * restrict b, size_t n)
+{
+ typedef uint32_t __attribute__ ((__may_alias__)) u32_alias_t;
+ do
+ {
+ n -= 4;
+ u32_alias_t t = *(u32_alias_t *)(a + n);
+ *(u32_alias_t *)(a + n) = *(u32_alias_t *)(b + n);
+ *(u32_alias_t *)(b + n) = t;
+ } while (n);
+}
+
+/* Replace the indirect call with a serie of if statements. It should help
+ the branch predictor. */
+static void
+do_swap (void * restrict a, void * restrict b, size_t size,
+ enum swap_type_t swap_type)
+{
+ if (swap_type == SWAP_WORDS_64)
+ swap_words_64 (a, b, size);
+ else if (swap_type == SWAP_WORDS_32)
+ swap_words_32 (a, b, size);
+ else
+ __memswap (a, b, size);
+}
/* Discontinue quicksort algorithm when partition gets below this size.
This particular magic number was chosen to work best on a Sun 4/260. */
@@ -97,6 +148,14 @@ _quicksort (void *const pbase, size_t total_elems, size_t size,
/* Avoid lossage with unsigned arithmetic below. */
return;
+ enum swap_type_t swap_type;
+ if (is_aligned (pbase, size, 8))
+ swap_type = SWAP_WORDS_64;
+ else if (is_aligned (pbase, size, 4))
+ swap_type = SWAP_WORDS_32;
+ else
+ swap_type = SWAP_BYTES;
+
if (total_elems > MAX_THRESH)
{
char *lo = base_ptr;
@@ -120,13 +179,13 @@ _quicksort (void *const pbase, size_t total_elems, size_t size,
char *mid = lo + size * ((hi - lo) / size >> 1);
if ((*cmp) ((void *) mid, (void *) lo, arg) < 0)
- SWAP (mid, lo, size);
+ do_swap (mid, lo, size, swap_type);
if ((*cmp) ((void *) hi, (void *) mid, arg) < 0)
- SWAP (mid, hi, size);
+ do_swap (mid, hi, size, swap_type);
else
goto jump_over;
if ((*cmp) ((void *) mid, (void *) lo, arg) < 0)
- SWAP (mid, lo, size);
+ do_swap (mid, lo, size, swap_type);
jump_over:;
left_ptr = lo + size;
@@ -145,7 +204,7 @@ _quicksort (void *const pbase, size_t total_elems, size_t size,
if (left_ptr < right_ptr)
{
- SWAP (left_ptr, right_ptr, size);
+ do_swap (left_ptr, right_ptr, size, swap_type);
if (mid == left_ptr)
mid = right_ptr;
else if (mid == right_ptr)
@@ -217,7 +276,7 @@ _quicksort (void *const pbase, size_t total_elems, size_t size,
tmp_ptr = run_ptr;
if (tmp_ptr != base_ptr)
- SWAP (tmp_ptr, base_ptr, size);
+ do_swap (tmp_ptr, base_ptr, size, swap_type);
/* Insertion sort, running from left-hand-side up to right-hand-side. */

125
glibc-RHEL-24168-5.patch Normal file
View File

@ -0,0 +1,125 @@
commit a035a9857e11faf16ed021b5e80faf215262afd1
Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Date: Tue Oct 3 09:22:47 2023 -0300
stdlib: Move insertion sort out qsort
Reviewed-by: Noah Goldstein <goldstein.w.n@gmail.com>
diff --git a/stdlib/qsort.c b/stdlib/qsort.c
index 59b220ba1c375ca3..35020e4c00e5fce3 100644
--- a/stdlib/qsort.c
+++ b/stdlib/qsort.c
@@ -112,6 +112,58 @@ typedef struct
#define STACK_NOT_EMPTY (stack < top)
+static inline void
+insertion_sort_qsort_partitions (void *const pbase, size_t total_elems,
+ size_t size, enum swap_type_t swap_type,
+ __compar_d_fn_t cmp, void *arg)
+{
+ char *base_ptr = (char *) pbase;
+ char *const end_ptr = &base_ptr[size * (total_elems - 1)];
+ char *tmp_ptr = base_ptr;
+#define min(x, y) ((x) < (y) ? (x) : (y))
+ const size_t max_thresh = MAX_THRESH * size;
+ char *thresh = min(end_ptr, base_ptr + max_thresh);
+ char *run_ptr;
+
+ /* Find smallest element in first threshold and place it at the
+ array's beginning. This is the smallest array element,
+ and the operation speeds up insertion sort's inner loop. */
+
+ for (run_ptr = tmp_ptr + size; run_ptr <= thresh; run_ptr += size)
+ if (cmp (run_ptr, tmp_ptr, arg) < 0)
+ tmp_ptr = run_ptr;
+
+ if (tmp_ptr != base_ptr)
+ do_swap (tmp_ptr, base_ptr, size, swap_type);
+
+ /* Insertion sort, running from left-hand-side up to right-hand-side. */
+
+ run_ptr = base_ptr + size;
+ while ((run_ptr += size) <= end_ptr)
+ {
+ tmp_ptr = run_ptr - size;
+ while (cmp (run_ptr, tmp_ptr, arg) < 0)
+ tmp_ptr -= size;
+
+ tmp_ptr += size;
+ if (tmp_ptr != run_ptr)
+ {
+ char *trav;
+
+ trav = run_ptr + size;
+ while (--trav >= run_ptr)
+ {
+ char c = *trav;
+ char *hi, *lo;
+
+ for (hi = lo = trav; (lo -= size) >= tmp_ptr; hi = lo)
+ *hi = *lo;
+ *hi = c;
+ }
+ }
+ }
+}
+
/* Order size using quicksort. This implementation incorporates
four optimizations discussed in Sedgewick:
@@ -258,51 +310,6 @@ _quicksort (void *const pbase, size_t total_elems, size_t size,
for partitions below MAX_THRESH size. BASE_PTR points to the beginning
of the array to sort, and END_PTR points at the very last element in
the array (*not* one beyond it!). */
-
-#define min(x, y) ((x) < (y) ? (x) : (y))
-
- {
- char *const end_ptr = &base_ptr[size * (total_elems - 1)];
- char *tmp_ptr = base_ptr;
- char *thresh = min(end_ptr, base_ptr + max_thresh);
- char *run_ptr;
-
- /* Find smallest element in first threshold and place it at the
- array's beginning. This is the smallest array element,
- and the operation speeds up insertion sort's inner loop. */
-
- for (run_ptr = tmp_ptr + size; run_ptr <= thresh; run_ptr += size)
- if ((*cmp) ((void *) run_ptr, (void *) tmp_ptr, arg) < 0)
- tmp_ptr = run_ptr;
-
- if (tmp_ptr != base_ptr)
- do_swap (tmp_ptr, base_ptr, size, swap_type);
-
- /* Insertion sort, running from left-hand-side up to right-hand-side. */
-
- run_ptr = base_ptr + size;
- while ((run_ptr += size) <= end_ptr)
- {
- tmp_ptr = run_ptr - size;
- while ((*cmp) ((void *) run_ptr, (void *) tmp_ptr, arg) < 0)
- tmp_ptr -= size;
-
- tmp_ptr += size;
- if (tmp_ptr != run_ptr)
- {
- char *trav;
-
- trav = run_ptr + size;
- while (--trav >= run_ptr)
- {
- char c = *trav;
- char *hi, *lo;
-
- for (hi = lo = trav; (lo -= size) >= tmp_ptr; hi = lo)
- *hi = *lo;
- *hi = c;
- }
- }
- }
- }
+ insertion_sort_qsort_partitions (pbase, total_elems, size, swap_type, cmp,
+ arg);
}

85
glibc-RHEL-24168-6.patch Normal file
View File

@ -0,0 +1,85 @@
commit d097f3c79be55d646d86efb7ce876bf84d5ebe4e
Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Date: Tue Oct 3 09:22:48 2023 -0300
stdlib: qsort: Move some macros to inline function
Reviewed-by: Noah Goldstein <goldstein.w.n@gmail.com>
diff --git a/stdlib/qsort.c b/stdlib/qsort.c
index 35020e4c00e5fce3..821a87420638c5a5 100644
--- a/stdlib/qsort.c
+++ b/stdlib/qsort.c
@@ -101,15 +101,28 @@ typedef struct
char *hi;
} stack_node;
-/* The next 4 #defines implement a very fast in-line stack abstraction. */
/* The stack needs log (total_elements) entries (we could even subtract
log(MAX_THRESH)). Since total_elements has type size_t, we get as
upper bound for log (total_elements):
bits per byte (CHAR_BIT) * sizeof(size_t). */
-#define STACK_SIZE (CHAR_BIT * sizeof (size_t))
-#define PUSH(low, high) ((void) ((top->lo = (low)), (top->hi = (high)), ++top))
-#define POP(low, high) ((void) (--top, (low = top->lo), (high = top->hi)))
-#define STACK_NOT_EMPTY (stack < top)
+enum { STACK_SIZE = CHAR_BIT * sizeof (size_t) };
+
+static inline stack_node *
+push (stack_node *top, char *lo, char *hi)
+{
+ top->lo = lo;
+ top->hi = hi;
+ return ++top;
+}
+
+static inline stack_node *
+pop (stack_node *top, char **lo, char **hi)
+{
+ --top;
+ *lo = top->lo;
+ *hi = top->hi;
+ return top;
+}
static inline void
@@ -213,11 +226,9 @@ _quicksort (void *const pbase, size_t total_elems, size_t size,
char *lo = base_ptr;
char *hi = &lo[size * (total_elems - 1)];
stack_node stack[STACK_SIZE];
- stack_node *top = stack;
-
- PUSH (NULL, NULL);
+ stack_node *top = stack + 1;
- while (STACK_NOT_EMPTY)
+ while (stack < top)
{
char *left_ptr;
char *right_ptr;
@@ -282,7 +293,7 @@ _quicksort (void *const pbase, size_t total_elems, size_t size,
{
if ((size_t) (hi - left_ptr) <= max_thresh)
/* Ignore both small partitions. */
- POP (lo, hi);
+ top = pop (top, &lo, &hi);
else
/* Ignore small left partition. */
lo = left_ptr;
@@ -293,13 +304,13 @@ _quicksort (void *const pbase, size_t total_elems, size_t size,
else if ((right_ptr - lo) > (hi - left_ptr))
{
/* Push larger left partition indices. */
- PUSH (lo, right_ptr);
+ top = push (top, lo, right_ptr);
lo = left_ptr;
}
else
{
/* Push larger right partition indices. */
- PUSH (left_ptr, hi);
+ top = push (top, left_ptr, hi);
hi = right_ptr;
}
}

176
glibc-RHEL-24168-7.patch Normal file
View File

@ -0,0 +1,176 @@
commit 274a46c9b25ab733a1fb9fb1497f1beecae30193
Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Date: Tue Oct 3 09:22:49 2023 -0300
stdlib: Implement introsort for qsort (BZ 19305)
This patch makes the quicksort implementation to acts as introsort, to
avoid worse-case performance (and thus making it O(nlog n)). It switch
to heapsort when the depth level reaches 2*log2(total elements). The
heapsort is a textbook implementation.
Checked on x86_64-linux-gnu and aarch64-linux-gnu.
Reviewed-by: Noah Goldstein <goldstein.w.n@gmail.com>
diff --git a/stdlib/qsort.c b/stdlib/qsort.c
index 821a87420638c5a5..db299eb333cf0302 100644
--- a/stdlib/qsort.c
+++ b/stdlib/qsort.c
@@ -99,6 +99,7 @@ typedef struct
{
char *lo;
char *hi;
+ size_t depth;
} stack_node;
/* The stack needs log (total_elements) entries (we could even subtract
@@ -108,22 +109,85 @@ typedef struct
enum { STACK_SIZE = CHAR_BIT * sizeof (size_t) };
static inline stack_node *
-push (stack_node *top, char *lo, char *hi)
+push (stack_node *top, char *lo, char *hi, size_t depth)
{
top->lo = lo;
top->hi = hi;
+ top->depth = depth;
return ++top;
}
static inline stack_node *
-pop (stack_node *top, char **lo, char **hi)
+pop (stack_node *top, char **lo, char **hi, size_t *depth)
{
--top;
*lo = top->lo;
*hi = top->hi;
+ *depth = top->depth;
return top;
}
+/* NB: N is inclusive bound for BASE. */
+static inline void
+siftdown (void *base, size_t size, size_t k, size_t n,
+ enum swap_type_t swap_type, __compar_d_fn_t cmp, void *arg)
+{
+ while (k <= n / 2)
+ {
+ size_t j = 2 * k;
+ if (j < n && cmp (base + (j * size), base + ((j + 1) * size), arg) < 0)
+ j++;
+
+ if (cmp (base + (k * size), base + (j * size), arg) >= 0)
+ break;
+
+ do_swap (base + (size * j), base + (k * size), size, swap_type);
+ k = j;
+ }
+}
+
+static inline void
+heapify (void *base, size_t size, size_t n, enum swap_type_t swap_type,
+ __compar_d_fn_t cmp, void *arg)
+{
+ size_t k = n / 2;
+ while (1)
+ {
+ siftdown (base, size, k, n, swap_type, cmp, arg);
+ if (k-- == 0)
+ break;
+ }
+}
+
+/* A non-recursive heapsort, used on introsort implementation as a fallback
+ routine with worst-case performance of O(nlog n) and worst-case space
+ complexity of O(1). It sorts the array starting at BASE and ending at
+ END, with each element of SIZE bytes. The SWAP_TYPE is the callback
+ function used to swap elements, and CMP is the function used to compare
+ elements. */
+static void
+heapsort_r (void *base, void *end, size_t size, enum swap_type_t swap_type,
+ __compar_d_fn_t cmp, void *arg)
+{
+ const size_t count = ((uintptr_t) end - (uintptr_t) base) / size;
+
+ if (count < 2)
+ return;
+
+ size_t n = count - 1;
+
+ /* Build the binary heap, largest value at the base[0]. */
+ heapify (base, size, n, swap_type, cmp, arg);
+
+ /* On each iteration base[0:n] is the binary heap, while base[n:count]
+ is sorted. */
+ while (n > 0)
+ {
+ do_swap (base, base + (n * size), size, swap_type);
+ n--;
+ siftdown (base, size, 0, n, swap_type, cmp, arg);
+ }
+}
static inline void
insertion_sort_qsort_partitions (void *const pbase, size_t total_elems,
@@ -209,7 +273,7 @@ _quicksort (void *const pbase, size_t total_elems, size_t size,
const size_t max_thresh = MAX_THRESH * size;
- if (total_elems == 0)
+ if (total_elems <= 1)
/* Avoid lossage with unsigned arithmetic below. */
return;
@@ -221,15 +285,26 @@ _quicksort (void *const pbase, size_t total_elems, size_t size,
else
swap_type = SWAP_BYTES;
+ /* Maximum depth before quicksort switches to heapsort. */
+ size_t depth = 2 * (sizeof (size_t) * CHAR_BIT - 1
+ - __builtin_clzl (total_elems));
+
if (total_elems > MAX_THRESH)
{
char *lo = base_ptr;
char *hi = &lo[size * (total_elems - 1)];
stack_node stack[STACK_SIZE];
- stack_node *top = stack + 1;
+ stack_node *top = push (stack, NULL, NULL, depth);
while (stack < top)
{
+ if (depth == 0)
+ {
+ heapsort_r (lo, hi, size, swap_type, cmp, arg);
+ top = pop (top, &lo, &hi, &depth);
+ continue;
+ }
+
char *left_ptr;
char *right_ptr;
@@ -293,7 +368,7 @@ _quicksort (void *const pbase, size_t total_elems, size_t size,
{
if ((size_t) (hi - left_ptr) <= max_thresh)
/* Ignore both small partitions. */
- top = pop (top, &lo, &hi);
+ top = pop (top, &lo, &hi, &depth);
else
/* Ignore small left partition. */
lo = left_ptr;
@@ -304,13 +379,13 @@ _quicksort (void *const pbase, size_t total_elems, size_t size,
else if ((right_ptr - lo) > (hi - left_ptr))
{
/* Push larger left partition indices. */
- top = push (top, lo, right_ptr);
+ top = push (top, lo, right_ptr, depth - 1);
lo = left_ptr;
}
else
{
/* Push larger right partition indices. */
- top = push (top, left_ptr, hi);
+ top = push (top, left_ptr, hi, depth - 1);
hi = right_ptr;
}
}

491
glibc-RHEL-24168-8.patch Normal file
View File

@ -0,0 +1,491 @@
commit 03bf8357e8291857a435afcc3048e0b697b6cc04
Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Date: Tue Oct 3 09:22:50 2023 -0300
stdlib: Remove use of mergesort on qsort (BZ 21719)
This patch removes the mergesort optimization on qsort implementation
and uses the introsort instead. The mergesort implementation has some
issues:
- It is as-safe only for certain types sizes (if total size is less
than 1 KB with large element sizes also forcing memory allocation)
which contradicts the function documentation. Although not required
by the C standard, it is preferable and doable to have an O(1) space
implementation.
- The malloc for certain element size and element number adds
arbitrary latency (might even be worse if malloc is interposed).
- To avoid trigger swap from memory allocation the implementation
relies on system information that might be virtualized (for instance
VMs with overcommit memory) which might lead to potentially use of
swap even if system advertise more memory than actually has. The
check also have the downside of issuing syscalls where none is
expected (although only once per execution).
- The mergesort is suboptimal on an already sorted array (BZ#21719).
The introsort implementation is already optimized to use constant extra
space (due to the limit of total number of elements from maximum VM
size) and thus can be used to avoid the malloc usage issues.
Resulting performance is slower due the usage of qsort, specially in the
worst-case scenario (partialy or sorted arrays) and due the fact
mergesort uses a slight improved swap operations.
This change also renders the BZ#21719 fix unrequired (since it is meant
to fix the sorted input performance degradation for mergesort). The
manual is also updated to indicate the function is now async-cancel
safe.
Checked on x86_64-linux-gnu.
Reviewed-by: Noah Goldstein <goldstein.w.n@gmail.com>
Conflicts:
stdlib/msort.c: Deletion had conflicts due to skipped backports.
diff --git a/include/stdlib.h b/include/stdlib.h
index 22c9fb65c3074765..fd108df58ddf8b89 100644
--- a/include/stdlib.h
+++ b/include/stdlib.h
@@ -107,8 +107,6 @@ extern int __posix_openpt (int __oflag) attribute_hidden;
extern int __add_to_environ (const char *name, const char *value,
const char *combines, int replace)
attribute_hidden;
-extern void _quicksort (void *const pbase, size_t total_elems,
- size_t size, __compar_d_fn_t cmp, void *arg);
extern int __on_exit (void (*__func) (int __status, void *__arg), void *__arg);
diff --git a/manual/argp.texi b/manual/argp.texi
index 0023441812d4e584..b77ad68285ecb732 100644
--- a/manual/argp.texi
+++ b/manual/argp.texi
@@ -735,7 +735,7 @@ for options, bad phase of the moon, etc.
@c hol_set_group ok
@c hol_find_entry ok
@c hol_sort @mtslocale @acucorrupt
-@c qsort dup @acucorrupt
+@c qsort dup
@c hol_entry_qcmp @mtslocale
@c hol_entry_cmp @mtslocale
@c group_cmp ok
diff --git a/manual/locale.texi b/manual/locale.texi
index 720e0ca952a665bd..f6afa5dc44a2a016 100644
--- a/manual/locale.texi
+++ b/manual/locale.texi
@@ -253,7 +253,7 @@ The symbols in this section are defined in the header file @file{locale.h}.
@c calculate_head_size ok
@c __munmap ok
@c compute_hashval ok
-@c qsort dup @acucorrupt
+@c qsort dup
@c rangecmp ok
@c malloc @ascuheap @acsmem
@c strdup @ascuheap @acsmem
@@ -275,7 +275,6 @@ The symbols in this section are defined in the header file @file{locale.h}.
@c realloc @ascuheap @acsmem
@c realloc @ascuheap @acsmem
@c fclose @ascuheap @asulock @acsmem @acsfd @aculock
-@c qsort @ascuheap @acsmem
@c alias_compare dup
@c libc_lock_unlock @aculock
@c _nl_explode_name @ascuheap @acsmem
diff --git a/manual/search.texi b/manual/search.texi
index 5691bf2f2b2bb861..a550858478f7fc83 100644
--- a/manual/search.texi
+++ b/manual/search.texi
@@ -159,7 +159,7 @@ To sort an array using an arbitrary comparison function, use the
@deftypefun void qsort (void *@var{array}, size_t @var{count}, size_t @var{size}, comparison_fn_t @var{compare})
@standards{ISO, stdlib.h}
-@safety{@prelim{}@mtsafe{}@assafe{}@acunsafe{@acucorrupt{}}}
+@safety{@prelim{}@mtsafe{}@assafe{}@acsafe{}}
The @code{qsort} function sorts the array @var{array}. The array
contains @var{count} elements, each of which is of size @var{size}.
@@ -199,9 +199,8 @@ Functions}):
The @code{qsort} function derives its name from the fact that it was
originally implemented using the ``quick sort'' algorithm.
-The implementation of @code{qsort} in this library might not be an
-in-place sort and might thereby use an extra amount of memory to store
-the array.
+The implementation of @code{qsort} in this library is an in-place sort
+and uses a constant extra space (allocated on the stack).
@end deftypefun
@node Search/Sort Example
diff --git a/stdlib/Makefile b/stdlib/Makefile
index 03f8478c64408ed3..3b89bc2aa0307321 100644
--- a/stdlib/Makefile
+++ b/stdlib/Makefile
@@ -94,7 +94,6 @@ routines := \
mbtowc \
mrand48 \
mrand48_r \
- msort \
nrand48 \
nrand48_r \
old_atexit \
@@ -370,7 +369,6 @@ generated += \
# generated
CFLAGS-bsearch.c += $(uses-callbacks)
-CFLAGS-msort.c += $(uses-callbacks)
CFLAGS-qsort.c += $(uses-callbacks)
CFLAGS-system.c += -fexceptions
CFLAGS-system.os = -fomit-frame-pointer
diff --git a/stdlib/msort.c b/stdlib/msort.c
deleted file mode 100644
index 8750cc59db2337cf..0000000000000000
--- a/stdlib/msort.c
+++ /dev/null
@@ -1,310 +0,0 @@
-/* An alternative to qsort, with an identical interface.
- This file is part of the GNU C Library.
- Copyright (C) 1992-2021 Free Software Foundation, Inc.
- Written by Mike Haertel, September 1988.
-
- 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
- <https://www.gnu.org/licenses/>. */
-
-#include <alloca.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <memcopy.h>
-#include <errno.h>
-#include <atomic.h>
-
-struct msort_param
-{
- size_t s;
- size_t var;
- __compar_d_fn_t cmp;
- void *arg;
- char *t;
-};
-static void msort_with_tmp (const struct msort_param *p, void *b, size_t n);
-
-static void
-msort_with_tmp (const struct msort_param *p, void *b, size_t n)
-{
- char *b1, *b2;
- size_t n1, n2;
-
- if (n <= 1)
- return;
-
- n1 = n / 2;
- n2 = n - n1;
- b1 = b;
- b2 = (char *) b + (n1 * p->s);
-
- msort_with_tmp (p, b1, n1);
- msort_with_tmp (p, b2, n2);
-
- char *tmp = p->t;
- const size_t s = p->s;
- __compar_d_fn_t cmp = p->cmp;
- void *arg = p->arg;
- switch (p->var)
- {
- case 0:
- while (n1 > 0 && n2 > 0)
- {
- if ((*cmp) (b1, b2, arg) <= 0)
- {
- *(uint32_t *) tmp = *(uint32_t *) b1;
- b1 += sizeof (uint32_t);
- --n1;
- }
- else
- {
- *(uint32_t *) tmp = *(uint32_t *) b2;
- b2 += sizeof (uint32_t);
- --n2;
- }
- tmp += sizeof (uint32_t);
- }
- break;
- case 1:
- while (n1 > 0 && n2 > 0)
- {
- if ((*cmp) (b1, b2, arg) <= 0)
- {
- *(uint64_t *) tmp = *(uint64_t *) b1;
- b1 += sizeof (uint64_t);
- --n1;
- }
- else
- {
- *(uint64_t *) tmp = *(uint64_t *) b2;
- b2 += sizeof (uint64_t);
- --n2;
- }
- tmp += sizeof (uint64_t);
- }
- break;
- case 2:
- while (n1 > 0 && n2 > 0)
- {
- unsigned long *tmpl = (unsigned long *) tmp;
- unsigned long *bl;
-
- tmp += s;
- if ((*cmp) (b1, b2, arg) <= 0)
- {
- bl = (unsigned long *) b1;
- b1 += s;
- --n1;
- }
- else
- {
- bl = (unsigned long *) b2;
- b2 += s;
- --n2;
- }
- while (tmpl < (unsigned long *) tmp)
- *tmpl++ = *bl++;
- }
- break;
- case 3:
- while (n1 > 0 && n2 > 0)
- {
- if ((*cmp) (*(const void **) b1, *(const void **) b2, arg) <= 0)
- {
- *(void **) tmp = *(void **) b1;
- b1 += sizeof (void *);
- --n1;
- }
- else
- {
- *(void **) tmp = *(void **) b2;
- b2 += sizeof (void *);
- --n2;
- }
- tmp += sizeof (void *);
- }
- break;
- default:
- while (n1 > 0 && n2 > 0)
- {
- if ((*cmp) (b1, b2, arg) <= 0)
- {
- tmp = (char *) __mempcpy (tmp, b1, s);
- b1 += s;
- --n1;
- }
- else
- {
- tmp = (char *) __mempcpy (tmp, b2, s);
- b2 += s;
- --n2;
- }
- }
- break;
- }
-
- if (n1 > 0)
- memcpy (tmp, b1, n1 * s);
- memcpy (b, p->t, (n - n2) * s);
-}
-
-
-void
-__qsort_r (void *b, size_t n, size_t s, __compar_d_fn_t cmp, void *arg)
-{
- size_t size = n * s;
- char *tmp = NULL;
- struct msort_param p;
-
- /* For large object sizes use indirect sorting. */
- if (s > 32)
- size = 2 * n * sizeof (void *) + s;
-
- if (size < 1024)
- /* The temporary array is small, so put it on the stack. */
- p.t = __alloca (size);
- else
- {
- /* We should avoid allocating too much memory since this might
- have to be backed up by swap space. */
- static long int phys_pages;
- static int pagesize;
-
- if (pagesize == 0)
- {
- phys_pages = __sysconf (_SC_PHYS_PAGES);
-
- if (phys_pages == -1)
- /* Error while determining the memory size. So let's
- assume there is enough memory. Otherwise the
- implementer should provide a complete implementation of
- the `sysconf' function. */
- phys_pages = (long int) (~0ul >> 1);
-
- /* The following determines that we will never use more than
- a quarter of the physical memory. */
- phys_pages /= 4;
-
- /* Make sure phys_pages is written to memory. */
- atomic_write_barrier ();
-
- pagesize = __sysconf (_SC_PAGESIZE);
- }
-
- /* Just a comment here. We cannot compute
- phys_pages * pagesize
- and compare the needed amount of memory against this value.
- The problem is that some systems might have more physical
- memory then can be represented with a `size_t' value (when
- measured in bytes. */
-
- /* If the memory requirements are too high don't allocate memory. */
- if (size / pagesize > (size_t) phys_pages)
- {
- _quicksort (b, n, s, cmp, arg);
- return;
- }
-
- /* It's somewhat large, so malloc it. */
- int save = errno;
- tmp = malloc (size);
- __set_errno (save);
- if (tmp == NULL)
- {
- /* Couldn't get space, so use the slower algorithm
- that doesn't need a temporary array. */
- _quicksort (b, n, s, cmp, arg);
- return;
- }
- p.t = tmp;
- }
-
- p.s = s;
- p.var = 4;
- p.cmp = cmp;
- p.arg = arg;
-
- if (s > 32)
- {
- /* Indirect sorting. */
- char *ip = (char *) b;
- void **tp = (void **) (p.t + n * sizeof (void *));
- void **t = tp;
- void *tmp_storage = (void *) (tp + n);
-
- while ((void *) t < tmp_storage)
- {
- *t++ = ip;
- ip += s;
- }
- p.s = sizeof (void *);
- p.var = 3;
- msort_with_tmp (&p, p.t + n * sizeof (void *), n);
-
- /* tp[0] .. tp[n - 1] is now sorted, copy around entries of
- the original array. Knuth vol. 3 (2nd ed.) exercise 5.2-10. */
- char *kp;
- size_t i;
- for (i = 0, ip = (char *) b; i < n; i++, ip += s)
- if ((kp = tp[i]) != ip)
- {
- size_t j = i;
- char *jp = ip;
- memcpy (tmp_storage, ip, s);
-
- do
- {
- size_t k = (kp - (char *) b) / s;
- tp[j] = jp;
- memcpy (jp, kp, s);
- j = k;
- jp = kp;
- kp = tp[k];
- }
- while (kp != ip);
-
- tp[j] = jp;
- memcpy (jp, tmp_storage, s);
- }
- }
- else
- {
- if ((s & (sizeof (uint32_t) - 1)) == 0
- && ((char *) b - (char *) 0) % __alignof__ (uint32_t) == 0)
- {
- if (s == sizeof (uint32_t))
- p.var = 0;
- else if (s == sizeof (uint64_t)
- && ((char *) b - (char *) 0) % __alignof__ (uint64_t) == 0)
- p.var = 1;
- else if ((s & (sizeof (unsigned long) - 1)) == 0
- && ((char *) b - (char *) 0)
- % __alignof__ (unsigned long) == 0)
- p.var = 2;
- }
- msort_with_tmp (&p, b, n);
- }
- free (tmp);
-}
-libc_hidden_def (__qsort_r)
-weak_alias (__qsort_r, qsort_r)
-
-
-void
-qsort (void *b, size_t n, size_t s, __compar_fn_t cmp)
-{
- return __qsort_r (b, n, s, (__compar_d_fn_t) cmp, NULL);
-}
-libc_hidden_def (qsort)
diff --git a/stdlib/qsort.c b/stdlib/qsort.c
index db299eb333cf0302..cb1619aa0ae7de72 100644
--- a/stdlib/qsort.c
+++ b/stdlib/qsort.c
@@ -20,7 +20,6 @@
Engineering a sort function; Jon Bentley and M. Douglas McIlroy;
Software - Practice and Experience; Vol. 23 (11), 1249-1265, 1993. */
-#include <alloca.h>
#include <limits.h>
#include <memswap.h>
#include <stdlib.h>
@@ -266,8 +265,8 @@ insertion_sort_qsort_partitions (void *const pbase, size_t total_elems,
stack size is needed (actually O(1) in this case)! */
void
-_quicksort (void *const pbase, size_t total_elems, size_t size,
- __compar_d_fn_t cmp, void *arg)
+__qsort_r (void *const pbase, size_t total_elems, size_t size,
+ __compar_d_fn_t cmp, void *arg)
{
char *base_ptr = (char *) pbase;
@@ -399,3 +398,12 @@ _quicksort (void *const pbase, size_t total_elems, size_t size,
insertion_sort_qsort_partitions (pbase, total_elems, size, swap_type, cmp,
arg);
}
+libc_hidden_def (__qsort_r)
+weak_alias (__qsort_r, qsort_r)
+
+void
+qsort (void *b, size_t n, size_t s, __compar_fn_t cmp)
+{
+ return __qsort_r (b, n, s, (__compar_d_fn_t) cmp, NULL);
+}
+libc_hidden_def (qsort)

399
glibc-RHEL-24168-9.patch Normal file
View File

@ -0,0 +1,399 @@
commit bc888a3976700a3607f6ec4a36dbf3030161cb3e
Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Date: Tue Oct 3 09:22:51 2023 -0300
stdlib: Add more qsort{_r} coverage
This patch adds a qsort and qsort_r to trigger the worst case
scenario for the quicksort (which glibc current lacks coverage).
The test is done with random input, dfferent internal types (uint8_t,
uint16_t, uint32_t, uint64_t, large size), and with
different set of element numbers.
Checked on x86_64-linux-gnu and i686-linux-gnu.
Reviewed-by: Noah Goldstein <goldstein.w.n@gmail.com>
diff --git a/stdlib/Makefile b/stdlib/Makefile
index 3b89bc2aa0307321..4039e5395eeea2b0 100644
--- a/stdlib/Makefile
+++ b/stdlib/Makefile
@@ -211,6 +211,7 @@ tests := \
tst-on_exit \
tst-qsort \
tst-qsort2 \
+ tst-qsort3 \
tst-quick_exit \
tst-rand48 \
tst-rand48-2 \
diff --git a/stdlib/tst-qsort3.c b/stdlib/tst-qsort3.c
new file mode 100644
index 0000000000000000..421560d74434a116
--- /dev/null
+++ b/stdlib/tst-qsort3.c
@@ -0,0 +1,366 @@
+/* qsort(_r) tests to trigger worst case for quicksort.
+ Copyright (C) 2023 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 <getopt.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/support.h>
+#include <support/test-driver.h>
+
+typedef enum
+{
+ Sorted,
+ Random,
+ Repeated,
+ Bitonic,
+ Duplicated,
+} arraytype_t;
+
+/* Ratio of total of elements which will be repeated. */
+static const double RepeatedRatio = 0.2;
+
+/* Ratio of duplicated element . */
+static const double DuplicatedRatio = 0.4;
+
+struct array_t
+{
+ arraytype_t type;
+ const char *name;
+} static const arraytypes[] =
+{
+ { Sorted, "Sorted" },
+ { Random, "Random" },
+ { Repeated, "Repeated" },
+ { Bitonic, "Bitonic" },
+ { Duplicated, "Duplicated" },
+};
+
+/* Return the index of BASE as interpreted as an array of elements
+ of size SIZE. */
+static inline void *
+arr (void *base, size_t idx, size_t size)
+{
+ return (void*)((uintptr_t)base + (idx * size));
+}
+
+/* Functions used to check qsort. */
+static int
+uint8_t_cmp (const void *a, const void *b)
+{
+ uint8_t ia = *(uint8_t*)a;
+ uint8_t ib = *(uint8_t*)b;
+ return (ia > ib) - (ia < ib);
+}
+
+static int
+uint16_t_cmp (const void *a, const void *b)
+{
+ uint16_t ia = *(uint16_t*)a;
+ uint16_t ib = *(uint16_t*)b;
+ return (ia > ib) - (ia < ib);
+}
+
+static int
+uint32_t_cmp (const void *a, const void *b)
+{
+ uint32_t ia = *(uint32_t*)a;
+ uint32_t ib = *(uint32_t*)b;
+ return (ia > ib) - (ia < ib);
+}
+
+static int
+uint64_t_cmp (const void *a, const void *b)
+{
+ uint64_t ia = *(uint64_t*)a;
+ uint64_t ib = *(uint64_t*)b;
+ return (ia > ib) - (ia < ib);
+}
+
+#define LARGE_SIZE 47
+
+static int
+large_cmp (const void *a, const void *b)
+{
+ return memcmp (a, b, LARGE_SIZE);
+}
+
+/* Function used to check qsort_r. */
+typedef enum
+{
+ UINT8_CMP_T,
+ UINT16_CMP_T,
+ UINT32_CMP_T,
+ UINT64_CMP_T,
+ LARGE_CMP_T
+} type_cmp_t;
+
+static type_cmp_t
+uint_t_cmp_type (size_t sz)
+{
+ switch (sz)
+ {
+ case sizeof (uint8_t): return UINT8_CMP_T;
+ case sizeof (uint16_t): return UINT16_CMP_T;
+ case sizeof (uint64_t): return UINT64_CMP_T;
+ case sizeof (uint32_t): return UINT32_CMP_T;
+ default: return LARGE_CMP_T;
+ }
+}
+
+static int
+uint_t_cmp (const void *a, const void *b, void *arg)
+{
+ type_cmp_t type = *(type_cmp_t*) arg;
+ switch (type)
+ {
+ case UINT8_CMP_T: return uint8_t_cmp (a, b);
+ case UINT32_CMP_T: return uint32_t_cmp (a, b);
+ case UINT16_CMP_T: return uint16_t_cmp (a, b);
+ case UINT64_CMP_T: return uint64_t_cmp (a, b);
+ default: return large_cmp (a, b);
+ }
+}
+
+static void
+seq (void *elem, size_t type_size, int value)
+{
+ if (type_size == sizeof (uint8_t))
+ *(uint8_t*)elem = value;
+ else if (type_size == sizeof (uint16_t))
+ *(uint16_t*)elem = value;
+ else if (type_size == sizeof (uint32_t))
+ *(uint32_t*)elem = value;
+ else if (type_size == sizeof (uint64_t))
+ *(uint64_t*)elem = value;
+ else
+ memset (elem, value, type_size);
+}
+
+static void
+fill_array (void *array, void *refarray, size_t nmemb, size_t type_size,
+ arraytype_t type)
+{
+ size_t size = nmemb * type_size;
+
+ switch (type)
+ {
+ case Sorted:
+ for (size_t i = 0; i < nmemb; i++)
+ seq (arr (array, i, type_size), type_size, i);
+ break;
+
+ case Random:
+ arc4random_buf (array, size);
+ break;
+
+ case Repeated:
+ {
+ arc4random_buf (array, size);
+
+ void *randelem = xmalloc (type_size);
+ arc4random_buf (randelem, type_size);
+
+ /* Repeat REPEATED elements (based on RepeatRatio ratio) in the random
+ array. */
+ size_t repeated = (size_t)(nmemb * RepeatedRatio);
+ for (size_t i = 0; i < repeated; i++)
+ {
+ size_t pos = arc4random_uniform (nmemb - 1);
+ memcpy (arr (array, pos, type_size), randelem, type_size);
+ }
+ free (randelem);
+ }
+ break;
+
+ case Bitonic:
+ {
+ size_t i;
+ for (i = 0; i < nmemb / 2; i++)
+ seq (arr (array, i, type_size), type_size, i);
+ for ( ; i < nmemb; i++)
+ seq (arr (array, i, type_size), type_size, (nmemb - 1) - i);
+ }
+ break;
+
+ case Duplicated:
+ {
+ int randelem1 = arc4random ();
+ for (size_t i = 0; i < nmemb; i++)
+ seq (arr (array, i, type_size), type_size, randelem1);
+
+ size_t duplicates = (size_t)(nmemb * DuplicatedRatio);
+ int randelem2 = arc4random ();
+ for (size_t i = 0; i < duplicates; i++)
+ {
+ size_t pos = arc4random_uniform (nmemb - 1);
+ seq (arr (array, pos, type_size), type_size, randelem2);
+ }
+ }
+ break;
+ }
+
+ memcpy (refarray, array, size);
+}
+
+typedef int (*cmpfunc_t)(const void *, const void *);
+
+/* Simple insertion sort to use as reference sort. */
+static void
+qsort_r_ref (void *p, size_t n, size_t s, __compar_d_fn_t cmp, void *arg)
+{
+ if (n <= 1)
+ return;
+
+ int i = 1;
+ char tmp[s];
+ while (i < n)
+ {
+ memcpy (tmp, arr (p, i, s), s);
+ int j = i - 1;
+ while (j >= 0 && cmp (arr (p, j, s), tmp, arg) > 0)
+ {
+ memcpy (arr (p, j + 1, s), arr (p, j, s), s);
+ j = j - 1;
+ }
+ memcpy (arr (p, j + 1, s), tmp, s);
+ i = i + 1;
+ }
+}
+
+static void
+qsort_ref (void *b, size_t n, size_t s, __compar_fn_t cmp)
+{
+ return qsort_r_ref (b, n, s, (__compar_d_fn_t) cmp, NULL);
+}
+
+/* Check if ARRAY of total NMEMB element of size SIZE is sorted
+ based on CMPFUNC. */
+static void
+check_array (void *array, void *refarray, size_t nmemb, size_t type_size,
+ cmpfunc_t cmpfunc)
+{
+ for (size_t i = 1; i < nmemb; i++)
+ {
+ int ret = cmpfunc (arr (array, i, type_size),
+ arr (array, i-1, type_size));
+ TEST_VERIFY_EXIT (ret >= 0);
+ }
+
+ size_t size = nmemb * type_size;
+ TEST_COMPARE_BLOB (array, size, refarray, size);
+}
+
+static void
+check_qsort (void *buf, void *refbuf, size_t nelem, size_t type_size,
+ arraytype_t type, cmpfunc_t cmpfunc)
+{
+ fill_array (buf, refbuf, nelem, type_size, type);
+
+ qsort (buf, nelem, type_size, cmpfunc);
+ qsort_ref (refbuf, nelem, type_size, cmpfunc);
+
+ check_array (buf, refbuf, nelem, type_size, cmpfunc);
+}
+
+static void
+check_qsort_r (void *buf, void *refbuf, size_t nelem, size_t type_size,
+ arraytype_t type, cmpfunc_t cmpfunc)
+{
+ fill_array (buf, refbuf, nelem, type_size, type);
+
+ type_cmp_t typecmp = uint_t_cmp_type (type_size);
+
+ qsort_r (buf, nelem, type_size, uint_t_cmp, &typecmp);
+ qsort_r_ref (refbuf, nelem, type_size, uint_t_cmp, &typecmp);
+
+ check_array (buf, refbuf, nelem, type_size, cmpfunc);
+}
+
+static int
+do_test (void)
+{
+ /* Some random sizes. */
+ static const size_t nelems[] = { 0, 1, 7, 20, 32, 100, 256, 1024, 4256 };
+ size_t max_nelems = 0;
+ for (int i = 0; i < array_length (nelems); i++)
+ if (nelems[i] > max_nelems)
+ max_nelems = nelems[i];
+
+ static const struct test_t
+ {
+ size_t type_size;
+ cmpfunc_t cmpfunc;
+ }
+ tests[] =
+ {
+ { sizeof (uint8_t), uint8_t_cmp },
+ { sizeof (uint16_t), uint16_t_cmp },
+ { sizeof (uint32_t), uint32_t_cmp },
+ { sizeof (uint64_t), uint64_t_cmp },
+ /* Test swap with large elements. */
+ { LARGE_SIZE, large_cmp },
+ };
+ size_t max_type_size = 0;
+ for (int i = 0; i < array_length (tests); i++)
+ if (tests[i].type_size > max_type_size)
+ max_type_size = tests[i].type_size;
+
+ void *buf = reallocarray (NULL, max_nelems, max_type_size);
+ TEST_VERIFY_EXIT (buf != NULL);
+ void *refbuf = reallocarray (NULL, max_nelems, max_type_size);
+ TEST_VERIFY_EXIT (refbuf != NULL);
+
+ for (const struct test_t *test = tests; test < array_end (tests); ++test)
+ {
+ if (test_verbose > 0)
+ printf ("info: testing qsort with type_size=%zu\n", test->type_size);
+ for (const struct array_t *arraytype = arraytypes;
+ arraytype < array_end (arraytypes);
+ ++arraytype)
+ {
+ if (test_verbose > 0)
+ printf (" distribution=%s\n", arraytype->name);
+ for (const size_t *nelem = nelems;
+ nelem < array_end (nelems);
+ ++nelem)
+ {
+ if (test_verbose > 0)
+ printf (" nelem=%zu, total size=%zu\n", *nelem,
+ *nelem * test->type_size);
+
+ check_qsort (buf, refbuf, *nelem, test->type_size,
+ arraytype->type, test->cmpfunc);
+ check_qsort_r (buf, refbuf, *nelem, test->type_size,
+ arraytype->type, test->cmpfunc);
+ }
+ }
+ }
+
+ free (buf);
+ free (refbuf);
+
+ return 0;
+}
+
+#include <support/test-driver.c>

View File

@ -157,7 +157,7 @@ end \
Summary: The GNU libc libraries
Name: glibc
Version: %{glibcversion}
Release: 210%{?dist}
Release: 211%{?dist}
# In general, GPLv2+ is used by programs, LGPLv2+ is used for
# libraries.
@ -1257,6 +1257,28 @@ Patch947: glibc-RHEL-53909-1.patch
Patch948: glibc-RHEL-53909-2.patch
Patch949: glibc-RHEL-62188-1.patch
Patch950: glibc-RHEL-62188-2.patch
Patch951: glibc-RHEL-24168-1.patch
Patch952: glibc-RHEL-24168-2.patch
Patch953: glibc-RHEL-24168-3.patch
Patch954: glibc-RHEL-24168-4.patch
Patch955: glibc-RHEL-24168-5.patch
Patch956: glibc-RHEL-24168-6.patch
Patch957: glibc-RHEL-24168-7.patch
Patch958: glibc-RHEL-24168-8.patch
Patch959: glibc-RHEL-24168-9.patch
Patch960: glibc-RHEL-24168-10.patch
Patch961: glibc-RHEL-24168-11.patch
Patch962: glibc-RHEL-24168-12.patch
Patch963: glibc-RHEL-24168-13.patch
Patch964: glibc-RHEL-24168-14.patch
Patch965: glibc-RHEL-24168-15.patch
Patch966: glibc-RHEL-24168-16.patch
Patch967: glibc-RHEL-24168-17.patch
Patch968: glibc-RHEL-24168-18.patch
Patch969: glibc-RHEL-24168-19.patch
Patch970: glibc-RHEL-24168-20.patch
Patch971: glibc-RHEL-24168-21.patch
Patch972: glibc-RHEL-24168-22.patch
##############################################################################
# Continued list of core "glibc" package information:
@ -3254,6 +3276,9 @@ update_gconv_modules_cache ()
%endif
%changelog
* Tue Jul 08 2025 Arjun Shankar <arjun@redhat.com> - 2.34-211
- Improve qsort implementation (RHEL-24168)
* Tue Jul 01 2025 Arjun Shankar <arjun@redhat.com> - 2.34-210
- Add new tests for clock_nanosleep (RHEL-62188)