Module Name:    src
Committed By:   riastradh
Date:           Sun Mar  2 16:35:41 UTC 2025

Modified Files:
        src/common/lib/libc/stdlib: heapsort.c
        src/distrib/sets/lists/comp: mi
        src/distrib/sets/lists/debug: mi
        src/distrib/sets/lists/tests: mi
        src/include: stdlib.h
        src/lib/libc/include: namespace.h
        src/lib/libc/stdlib: Makefile.inc merge.c qsort.3 qsort.c
        src/sys/lib/libkern: libkern.h
        src/tests/lib/libc/stdlib: Makefile
        src/tools/compat: compat_defs.h
Added Files:
        src/tests/lib/libc/stdlib: h_sort.c t_sort.sh

Log Message:
libc: New _r variants of heapsort, mergesort, qsort.

Also kheapsort_r for kernel/standalone use.

These variants allow the caller to pass a cookie through to the
comparison function, e.g. if you want to sort an array of indices
into a buffer.

qsort_r is new in POSIX.1-2024; the others are obvious analogues of
our nonstandard extensions for heapsort and mergesort.

PR lib/58931: qsort_r() missing


To generate a diff of this commit:
cvs rdiff -u -r1.3 -r1.4 src/common/lib/libc/stdlib/heapsort.c
cvs rdiff -u -r1.2485 -r1.2486 src/distrib/sets/lists/comp/mi
cvs rdiff -u -r1.466 -r1.467 src/distrib/sets/lists/debug/mi
cvs rdiff -u -r1.1359 -r1.1360 src/distrib/sets/lists/tests/mi
cvs rdiff -u -r1.129 -r1.130 src/include/stdlib.h
cvs rdiff -u -r1.205 -r1.206 src/lib/libc/include/namespace.h
cvs rdiff -u -r1.103 -r1.104 src/lib/libc/stdlib/Makefile.inc
cvs rdiff -u -r1.16 -r1.17 src/lib/libc/stdlib/merge.c
cvs rdiff -u -r1.14 -r1.15 src/lib/libc/stdlib/qsort.3
cvs rdiff -u -r1.23 -r1.24 src/lib/libc/stdlib/qsort.c
cvs rdiff -u -r1.147 -r1.148 src/sys/lib/libkern/libkern.h
cvs rdiff -u -r1.34 -r1.35 src/tests/lib/libc/stdlib/Makefile
cvs rdiff -u -r0 -r1.1 src/tests/lib/libc/stdlib/h_sort.c \
    src/tests/lib/libc/stdlib/t_sort.sh
cvs rdiff -u -r1.123 -r1.124 src/tools/compat/compat_defs.h

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/common/lib/libc/stdlib/heapsort.c
diff -u src/common/lib/libc/stdlib/heapsort.c:1.3 src/common/lib/libc/stdlib/heapsort.c:1.4
--- src/common/lib/libc/stdlib/heapsort.c:1.3	Mon Nov 17 10:21:30 2008
+++ src/common/lib/libc/stdlib/heapsort.c	Sun Mar  2 16:35:40 2025
@@ -1,4 +1,4 @@
-/*	$NetBSD: heapsort.c,v 1.3 2008/11/17 10:21:30 jnemeth Exp $	*/
+/*	$NetBSD: heapsort.c,v 1.4 2025/03/02 16:35:40 riastradh Exp $	*/
 
 /*-
  * Copyright (c) 1991, 1993
@@ -39,6 +39,7 @@
  * XXX rename the versions found in the host's headers by mistake!
  */
 #undef heapsort
+#undef heapsort_r
 #endif
 
 #include <sys/cdefs.h>
@@ -46,7 +47,7 @@
 #if 0
 static char sccsid[] = "from: @(#)heapsort.c	8.1 (Berkeley) 6/4/93";
 #else
-__RCSID("$NetBSD: heapsort.c,v 1.3 2008/11/17 10:21:30 jnemeth Exp $");
+__RCSID("$NetBSD: heapsort.c,v 1.4 2025/03/02 16:35:40 riastradh Exp $");
 #endif
 #endif /* LIBC_SCCS and not lint */
 
@@ -65,10 +66,12 @@ __RCSID("$NetBSD: heapsort.c,v 1.3 2008/
 #if HAVE_NBTOOL_CONFIG_H
 /* XXX Now, re-apply the renaming that we undid above. */
 #define heapsort	__nbcompat_heapsort
+#define heapsort_r	__nbcompat_heapsort_r
 #endif
 
 #ifdef __weak_alias
 __weak_alias(heapsort,_heapsort)
+__weak_alias(heapsort_r,_heapsort_r)
 #endif
 #endif	/* _KERNEL || _STANDALONE */
 
@@ -109,12 +112,13 @@ __weak_alias(heapsort,_heapsort)
 	for (par_i = initval; (child_i = par_i * 2) <= nmemb; \
 	    par_i = child_i) { \
 		child = base + child_i * size; \
-		if (child_i < nmemb && compar(child, child + size) < 0) { \
+		if (child_i < nmemb && \
+		    compar(child, child + size, cookie) < 0) { \
 			child += size; \
 			++child_i; \
 		} \
 		par = base + par_i * size; \
-		if (compar(child, par) <= 0) \
+		if (compar(child, par, cookie) <= 0) \
 			break; \
 		SWAP(par, child, count, size, tmp); \
 	} \
@@ -140,7 +144,8 @@ __weak_alias(heapsort,_heapsort)
 #define SELECT(par_i, child_i, nmemb, par, child, size, k, count, tmp1, tmp2) { \
 	for (par_i = 1; (child_i = par_i * 2) <= nmemb; par_i = child_i) { \
 		child = base + child_i * size; \
-		if (child_i < nmemb && compar(child, child + size) < 0) { \
+		if (child_i < nmemb && \
+		    compar(child, child + size, cookie) < 0) { \
 			child += size; \
 			++child_i; \
 		} \
@@ -152,7 +157,7 @@ __weak_alias(heapsort,_heapsort)
 		par_i = child_i / 2; \
 		child = base + child_i * size; \
 		par = base + par_i * size; \
-		if (child_i == 1 || compar(k, par) < 0) { \
+		if (child_i == 1 || compar(k, par, cookie) < 0) { \
 			COPY(child, k, count, size, tmp1, tmp2); \
 			break; \
 		} \
@@ -169,12 +174,13 @@ __weak_alias(heapsort,_heapsort)
  */
 #if defined(_KERNEL) || defined(_STANDALONE)
 int
-kheapsort(void *vbase, size_t nmemb, size_t size,
-    int (*compar)(const void *, const void *), void *k)
+kheapsort_r(void *vbase, size_t nmemb, size_t size,
+    int (*compar)(const void *, const void *, void *), void *cookie,
+    void *k)
 #else
 int
-heapsort(void *vbase, size_t nmemb, size_t size,
-    int (*compar)(const void *, const void *))
+heapsort_r(void *vbase, size_t nmemb, size_t size,
+    int (*compar)(const void *, const void *, void *), void *cookie)
 #endif
 {
 	size_t cnt, i, j, l;
@@ -227,3 +233,29 @@ heapsort(void *vbase, size_t nmemb, size
 #endif
 	return (0);
 }
+
+static int
+cmpnocookie(const void *a, const void *b, void *cookie)
+{
+	int (*cmp)(const void *, const void *) = cookie;
+
+	return cmp(a, b);
+}
+
+int
+#if defined(_KERNEL) || defined(_STANDALONE)
+kheapsort(void *a, size_t n, size_t es,
+    int (*cmp)(const void *, const void *),
+    void *k)
+#else
+heapsort(void *a, size_t n, size_t es,
+    int (*cmp)(const void *, const void *))
+#endif
+{
+
+#if defined(_KERNEL) || defined(_STANDALONE)
+	return kheapsort_r(a, n, es, cmpnocookie, cmp, k);
+#else
+	return heapsort_r(a, n, es, cmpnocookie, cmp);
+#endif
+}

Index: src/distrib/sets/lists/comp/mi
diff -u src/distrib/sets/lists/comp/mi:1.2485 src/distrib/sets/lists/comp/mi:1.2486
--- src/distrib/sets/lists/comp/mi:1.2485	Sun Jan 26 16:35:42 2025
+++ src/distrib/sets/lists/comp/mi	Sun Mar  2 16:35:40 2025
@@ -1,4 +1,4 @@
-#	$NetBSD: mi,v 1.2485 2025/01/26 16:35:42 christos Exp $
+#	$NetBSD: mi,v 1.2486 2025/03/02 16:35:40 riastradh Exp $
 #
 # Note: don't delete entries from here - mark them as "obsolete" instead.
 ./etc/mtree/set.comp				comp-sys-root
@@ -8183,6 +8183,7 @@
 ./usr/share/man/cat3/hdestroy1_r.0		comp-c-catman		.cat
 ./usr/share/man/cat3/hdestroy_r.0		comp-c-catman		.cat
 ./usr/share/man/cat3/heapsort.0			comp-c-catman		.cat
+./usr/share/man/cat3/heapsort_r.0		comp-c-catman		.cat
 ./usr/share/man/cat3/herror.0			comp-c-catman		.cat
 ./usr/share/man/cat3/hesiod.0			comp-c-catman		hesiod,.cat
 ./usr/share/man/cat3/hesiod_end.0		comp-c-catman		hesiod,.cat
@@ -9292,6 +9293,7 @@
 ./usr/share/man/cat3/menu_win.0			comp-c-catman		.cat
 ./usr/share/man/cat3/menus.0			comp-c-catman		.cat
 ./usr/share/man/cat3/mergesort.0		comp-c-catman		.cat
+./usr/share/man/cat3/mergesort_r.0		comp-c-catman		.cat
 ./usr/share/man/cat3/meta.0			comp-c-catman		.cat
 ./usr/share/man/cat3/mi_vector_hash.0		comp-c-catman		.cat
 ./usr/share/man/cat3/minor.0			comp-c-catman		.cat
@@ -10149,6 +10151,7 @@
 ./usr/share/man/cat3/qdiv.0			comp-c-catman		.cat
 ./usr/share/man/cat3/qiflush.0			comp-c-catman		.cat
 ./usr/share/man/cat3/qsort.0			comp-c-catman		.cat
+./usr/share/man/cat3/qsort_r.0			comp-c-catman		.cat
 ./usr/share/man/cat3/queue.0			comp-c-catman		.cat
 ./usr/share/man/cat3/quick_exit.0		comp-c-catman		.cat
 ./usr/share/man/cat3/quota_close.0		comp-c-catman		.cat
@@ -16723,6 +16726,7 @@
 ./usr/share/man/html3/hdestroy1_r.html		comp-c-htmlman		html
 ./usr/share/man/html3/hdestroy_r.html		comp-c-htmlman		html
 ./usr/share/man/html3/heapsort.html		comp-c-htmlman		html
+./usr/share/man/html3/heapsort_r.html		comp-c-htmlman		html
 ./usr/share/man/html3/herror.html		comp-c-htmlman		html
 ./usr/share/man/html3/hesiod.html		comp-c-htmlman		hesiod,html
 ./usr/share/man/html3/hesiod_end.html		comp-c-htmlman		hesiod,html
@@ -17800,6 +17804,7 @@
 ./usr/share/man/html3/menu_win.html		comp-c-htmlman		html
 ./usr/share/man/html3/menus.html		comp-c-htmlman		html
 ./usr/share/man/html3/mergesort.html		comp-c-htmlman		html
+./usr/share/man/html3/mergesort_r.html		comp-c-htmlman		html
 ./usr/share/man/html3/meta.html			comp-c-htmlman		html
 ./usr/share/man/html3/mi_vector_hash.html	comp-c-htmlman		html
 ./usr/share/man/html3/minor.html		comp-c-htmlman		html
@@ -18674,6 +18679,7 @@
 ./usr/share/man/html3/qdiv.html			comp-c-htmlman		html
 ./usr/share/man/html3/qiflush.html		comp-c-htmlman		html
 ./usr/share/man/html3/qsort.html		comp-c-htmlman		html
+./usr/share/man/html3/qsort_r.html		comp-c-htmlman		html
 ./usr/share/man/html3/queue.html		comp-c-htmlman		html
 ./usr/share/man/html3/quick_exit.html		comp-c-htmlman		html
 ./usr/share/man/html3/quota_close.html		comp-c-htmlman		html
@@ -25207,6 +25213,7 @@
 ./usr/share/man/man3/hdestroy1_r.3		comp-c-man		.man
 ./usr/share/man/man3/hdestroy_r.3		comp-c-man		.man
 ./usr/share/man/man3/heapsort.3			comp-c-man		.man
+./usr/share/man/man3/heapsort_r.3		comp-c-man		.man
 ./usr/share/man/man3/herror.3			comp-c-man		.man
 ./usr/share/man/man3/hesiod.3			comp-c-man		hesiod,.man
 ./usr/share/man/man3/hesiod_end.3		comp-c-man		hesiod,.man
@@ -26317,6 +26324,7 @@
 ./usr/share/man/man3/menu_win.3			comp-c-man		.man
 ./usr/share/man/man3/menus.3			comp-c-man		.man
 ./usr/share/man/man3/mergesort.3		comp-c-man		.man
+./usr/share/man/man3/mergesort_r.3		comp-c-man		.man
 ./usr/share/man/man3/meta.3			comp-c-man		.man
 ./usr/share/man/man3/mi_vector_hash.3		comp-c-man		.man
 ./usr/share/man/man3/minor.3			comp-c-man		.man
@@ -27197,6 +27205,7 @@
 ./usr/share/man/man3/qdiv.3			comp-c-man		.man
 ./usr/share/man/man3/qiflush.3			comp-c-man		.man
 ./usr/share/man/man3/qsort.3			comp-c-man		.man
+./usr/share/man/man3/qsort_r.3			comp-c-man		.man
 ./usr/share/man/man3/queue.3			comp-c-man		.man
 ./usr/share/man/man3/quick_exit.3		comp-c-man		.man
 ./usr/share/man/man3/quota_close.3		comp-c-man		.man

Index: src/distrib/sets/lists/debug/mi
diff -u src/distrib/sets/lists/debug/mi:1.466 src/distrib/sets/lists/debug/mi:1.467
--- src/distrib/sets/lists/debug/mi:1.466	Thu Feb 27 00:55:31 2025
+++ src/distrib/sets/lists/debug/mi	Sun Mar  2 16:35:40 2025
@@ -1,4 +1,4 @@
-# $NetBSD: mi,v 1.466 2025/02/27 00:55:31 riastradh Exp $
+# $NetBSD: mi,v 1.467 2025/03/02 16:35:40 riastradh Exp $
 #
 ./etc/mtree/set.debug                           comp-sys-root
 ./usr/lib					comp-sys-usr		compatdir
@@ -2162,6 +2162,7 @@
 ./usr/libdata/debug/usr/tests/lib/libc/stdlib/h_atexit.debug		tests-lib-debug		debug,atf,compattestfile
 ./usr/libdata/debug/usr/tests/lib/libc/stdlib/h_getopt.debug		tests-lib-debug		debug,atf,compattestfile
 ./usr/libdata/debug/usr/tests/lib/libc/stdlib/h_getopt_long.debug	tests-lib-debug		debug,atf,compattestfile
+./usr/libdata/debug/usr/tests/lib/libc/stdlib/h_sort.debug		tests-lib-debug		debug,atf,compattestfile
 ./usr/libdata/debug/usr/tests/lib/libc/stdlib/t_a64l.debug		tests-lib-debug		debug,atf,compattestfile
 ./usr/libdata/debug/usr/tests/lib/libc/stdlib/t_abs.debug		tests-lib-debug		debug,atf,compattestfile
 ./usr/libdata/debug/usr/tests/lib/libc/stdlib/t_atoi.debug		tests-lib-debug		debug,atf,compattestfile

Index: src/distrib/sets/lists/tests/mi
diff -u src/distrib/sets/lists/tests/mi:1.1359 src/distrib/sets/lists/tests/mi:1.1360
--- src/distrib/sets/lists/tests/mi:1.1359	Thu Feb 27 00:55:31 2025
+++ src/distrib/sets/lists/tests/mi	Sun Mar  2 16:35:40 2025
@@ -1,4 +1,4 @@
-# $NetBSD: mi,v 1.1359 2025/02/27 00:55:31 riastradh Exp $
+# $NetBSD: mi,v 1.1360 2025/03/02 16:35:40 riastradh Exp $
 #
 # Note: don't delete entries from here - mark them as "obsolete" instead.
 #
@@ -3267,6 +3267,7 @@
 ./usr/tests/lib/libc/stdlib/h_atexit			tests-lib-tests		compattestfile,atf
 ./usr/tests/lib/libc/stdlib/h_getopt			tests-lib-tests		compattestfile,atf
 ./usr/tests/lib/libc/stdlib/h_getopt_long		tests-lib-tests		compattestfile,atf
+./usr/tests/lib/libc/stdlib/h_sort			tests-lib-tests		compattestfile,atf
 ./usr/tests/lib/libc/stdlib/t_a64l			tests-lib-tests		compattestfile,atf
 ./usr/tests/lib/libc/stdlib/t_abs			tests-lib-tests		compattestfile,atf
 ./usr/tests/lib/libc/stdlib/t_atexit			tests-lib-tests		compattestfile,atf
@@ -3284,6 +3285,7 @@
 ./usr/tests/lib/libc/stdlib/t_mktemp			tests-lib-tests		compattestfile,atf
 ./usr/tests/lib/libc/stdlib/t_posix_memalign		tests-lib-tests		compattestfile,atf
 ./usr/tests/lib/libc/stdlib/t_random			tests-lib-tests		compattestfile,atf
+./usr/tests/lib/libc/stdlib/t_sort			tests-lib-tests		compattestfile,atf
 ./usr/tests/lib/libc/stdlib/t_strtod			tests-lib-tests		compattestfile,atf
 ./usr/tests/lib/libc/stdlib/t_strtoi			tests-lib-tests		compattestfile,atf
 ./usr/tests/lib/libc/stdlib/t_strtol			tests-lib-tests		compattestfile,atf

Index: src/include/stdlib.h
diff -u src/include/stdlib.h:1.129 src/include/stdlib.h:1.130
--- src/include/stdlib.h:1.129	Mon Feb 17 17:04:15 2025
+++ src/include/stdlib.h	Sun Mar  2 16:35:40 2025
@@ -1,4 +1,4 @@
-/*	$NetBSD: stdlib.h,v 1.129 2025/02/17 17:04:15 nia Exp $	*/
+/*	$NetBSD: stdlib.h,v 1.130 2025/03/02 16:35:40 riastradh Exp $	*/
 
 /*-
  * Copyright (c) 1990, 1993
@@ -317,8 +317,12 @@ int	 getenv_r(const char *, char *, size
 void	 cfree(void *);
 
 int	 heapsort(void *, size_t, size_t, int (*)(const void *, const void *));
+int	 heapsort_r(void *, size_t, size_t,
+	    int (*)(const void *, const void *, void *), void *);
 int	 mergesort(void *, size_t, size_t,
 	    int (*)(const void *, const void *));
+int	 mergesort_r(void *, size_t, size_t,
+	    int (*)(const void *, const void *, void *), void *);
 int	 ptsname_r(int, char *, size_t);
 int	 radixsort(const unsigned char **, int, const unsigned char *,
 	    unsigned);
@@ -400,6 +404,8 @@ void	*reallocarray(void *, size_t, size_
 
 #if (_POSIX_C_SOURCE - 0) >= 202405L || defined(_NETBSD_SOURCE)
 int	 mkostemp(char *, int);
+void	 qsort_r(void *, size_t, size_t,
+	    int (*)(const void *, const void *, void *), void *);
 #endif /* _POSIX_C_SOURCE >= 202405L || _NETBSD_SOURCE */
 
 __END_DECLS

Index: src/lib/libc/include/namespace.h
diff -u src/lib/libc/include/namespace.h:1.205 src/lib/libc/include/namespace.h:1.206
--- src/lib/libc/include/namespace.h:1.205	Sat Aug 17 21:24:53 2024
+++ src/lib/libc/include/namespace.h	Sun Mar  2 16:35:41 2025
@@ -1,4 +1,4 @@
-/*	$NetBSD: namespace.h,v 1.205 2024/08/17 21:24:53 riastradh Exp $	*/
+/*	$NetBSD: namespace.h,v 1.206 2025/03/02 16:35:41 riastradh Exp $	*/
 
 /*-
  * Copyright (c) 1997-2004 The NetBSD Foundation, Inc.
@@ -437,6 +437,7 @@
 #define gmtime_r		_gmtime_r
 #define group_from_gid		_group_from_gid
 #define heapsort		_heapsort
+#define heapsort_r		_heapsort_r
 #define herror			_herror
 #define hes_error		_hes_error
 #define hes_free		_hes_free
@@ -521,6 +522,7 @@
 #define mbrtoc8_l		_mbrtoc8_l
 #define membar_producer		_membar_producer
 #define mergesort		_mergesort
+#define mergesort_r		_mergesort_r
 #define mi_vector_hash		_mi_vector_hash
 #define mkstemp			_mkstemp
 #define mktime_z		_mktime_z

Index: src/lib/libc/stdlib/Makefile.inc
diff -u src/lib/libc/stdlib/Makefile.inc:1.103 src/lib/libc/stdlib/Makefile.inc:1.104
--- src/lib/libc/stdlib/Makefile.inc:1.103	Mon Sep 23 15:49:42 2024
+++ src/lib/libc/stdlib/Makefile.inc	Sun Mar  2 16:35:41 2025
@@ -1,4 +1,4 @@
-#	$NetBSD: Makefile.inc,v 1.103 2024/09/23 15:49:42 christos Exp $
+#	$NetBSD: Makefile.inc,v 1.104 2025/03/02 16:35:41 riastradh Exp $
 #	from: @(#)Makefile.inc	8.3 (Berkeley) 2/4/95
 
 # stdlib sources
@@ -88,6 +88,9 @@ MLINKS+=lsearch.3 lfind.3
 MLINKS+=malloc.3 calloc.3 malloc.3 realloc.3 malloc.3 free.3
 MLINKS+=posix_memalign.3 aligned_alloc.3
 MLINKS+=qsort.3 heapsort.3 qsort.3 mergesort.3
+MLINKS+=qsort.3 heapsort_r.3
+MLINKS+=qsort.3 mergesort_r.3
+MLINKS+=qsort.3 qsort_r.3
 MLINKS+=ptsname.3 ptsname_r.3
 MLINKS+=rand.3 rand_r.3
 MLINKS+=rand.3 srand.3

Index: src/lib/libc/stdlib/merge.c
diff -u src/lib/libc/stdlib/merge.c:1.16 src/lib/libc/stdlib/merge.c:1.17
--- src/lib/libc/stdlib/merge.c:1.16	Wed May 23 21:21:27 2018
+++ src/lib/libc/stdlib/merge.c	Sun Mar  2 16:35:41 2025
@@ -1,4 +1,4 @@
-/*	$NetBSD: merge.c,v 1.16 2018/05/23 21:21:27 joerg Exp $	*/
+/*	$NetBSD: merge.c,v 1.17 2025/03/02 16:35:41 riastradh Exp $	*/
 
 /*-
  * Copyright (c) 1992, 1993
@@ -37,7 +37,7 @@
 #if 0
 static char sccsid[] = "from: @(#)merge.c	8.2 (Berkeley) 2/14/94";
 #else
-__RCSID("$NetBSD: merge.c,v 1.16 2018/05/23 21:21:27 joerg Exp $");
+__RCSID("$NetBSD: merge.c,v 1.17 2025/03/02 16:35:41 riastradh Exp $");
 #endif
 #endif /* LIBC_SCCS and not lint */
 
@@ -65,12 +65,13 @@ __RCSID("$NetBSD: merge.c,v 1.16 2018/05
 
 #ifdef __weak_alias
 __weak_alias(mergesort,_mergesort)
+__weak_alias(mergesort_r,_mergesort_r)
 #endif
 
 static void setup(u_char *, u_char *, size_t, size_t,
-    int (*)(const void *, const void *));
+    int (*)(const void *, const void *, void *), void *);
 static void insertionsort(u_char *, size_t, size_t,
-    int (*)(const void *, const void *));
+    int (*)(const void *, const void *, void *), void *);
 
 #define ISIZE sizeof(int)
 #define PSIZE sizeof(u_char *)
@@ -93,7 +94,7 @@ static void insertionsort(u_char *, size
 	do					\
 		*dst++ = *src++;		\
 	while (i -= 1)
-		
+
 /*
  * Find the next possible pointer head.  (Trickery for forcing an array
  * to do double duty as a linked list when objects do not align with word
@@ -107,8 +108,8 @@ static void insertionsort(u_char *, size
  * Arguments are as for qsort.
  */
 int
-mergesort(void *base, size_t nmemb, size_t size,
-    int (*cmp)(const void *, const void *))
+mergesort_r(void *base, size_t nmemb, size_t size,
+    int (*cmp)(const void *, const void *, void *), void *cookie)
 {
 	size_t i;
 	int sense;
@@ -139,7 +140,7 @@ mergesort(void *base, size_t nmemb, size
 		return (-1);
 
 	list1 = base;
-	setup(list1, list2, nmemb, size, cmp);
+	setup(list1, list2, nmemb, size, cmp, cookie);
 	last = list2 + nmemb * size;
 	i = big = 0;
 	while (*EVAL(list2) != last) {
@@ -153,7 +154,7 @@ mergesort(void *base, size_t nmemb, size
 	    		p2 = *EVAL(p2);
 	    	l2 = list1 + (p2 - list2);
 	    	while (f1 < l1 && f2 < l2) {
-	    		if ((*cmp)(f1, f2) <= 0) {
+	    		if ((*cmp)(f1, f2, cookie) <= 0) {
 	    			q = f2;
 	    			b = f1, t = l1;
 	    			sense = -1;
@@ -166,7 +167,8 @@ mergesort(void *base, size_t nmemb, size
 #if 0
 LINEAR:
 #endif
-				while ((b += size) < t && cmp(q, b) >sense)
+	    			while ((b += size) < t &&
+	    			    cmp(q, b, cookie) >sense)
 	    				if (++i == 6) {
 	    					big = 1;
 	    					goto EXPONENTIAL;
@@ -175,15 +177,17 @@ LINEAR:
 EXPONENTIAL:	    		for (i = size; ; i <<= 1)
 	    				if ((p = (b + i)) >= t) {
 	    					if ((p = t - size) > b &&
-						    (*cmp)(q, p) <= sense)
+	    					    ((*cmp)(q, p, cookie) <=
+	    						sense))
 	    						t = p;
 	    					else
 	    						b = p;
 	    					break;
-	    				} else if ((*cmp)(q, p) <= sense) {
+	    				} else if ((*cmp)(q, p, cookie) <=
+	    				    sense) {
 	    					t = p;
 	    					if (i == size)
-	    						big = 0; 
+	    						big = 0;
 	    					goto FASTCASE;
 	    				} else
 	    					b = p;
@@ -192,19 +196,21 @@ SLOWCASE:
 #endif
 				while (t > b+size) {
 	    				i = (((t - b) / size) >> 1) * size;
-	    				if ((*cmp)(q, p = b + i) <= sense)
+	    				if ((*cmp)(q, p = b + i, cookie) <=
+	    				    sense)
 	    					t = p;
 	    				else
 	    					b = p;
 	    			}
 	    			goto COPY;
-FASTCASE:	    		while (i > size)
-	    				if ((*cmp)(q,
-	    					p = b +
-						    (i = (unsigned int) i >> 1)) <= sense)
+FASTCASE:	    		while (i > size) {
+	    				i = (unsigned int)i >> 1;
+	    				p = b + i;
+	    				if ((*cmp)(q, p, cookie) <= sense)
 	    					t = p;
 	    				else
 	    					b = p;
+	    			}
 COPY:	    			b = t;
 	    		}
 	    		i = size;
@@ -277,11 +283,9 @@ COPY:	    			b = t;
  * when THRESHOLD/2 pairs compare with same sense.  (Only used when NATURAL
  * is defined.  Otherwise simple pairwise merging is used.)
  */
-
-/* XXX: shouldn't this function be static? - lukem 990810 */
-void
+static void
 setup(u_char *list1, u_char *list2, size_t n, size_t size,
-    int (*cmp)(const void *, const void *))
+    int (*cmp)(const void *, const void *, void *), void *cookie)
 {
 	int length, tmp, sense;
 	u_char *f1, *f2, *s, *l2, *last, *p2;
@@ -293,7 +297,7 @@ setup(u_char *list1, u_char *list2, size
 
 	size2 = size*2;
 	if (n <= 5) {
-		insertionsort(list1, n, size, cmp);
+		insertionsort(list1, n, size, cmp, cookie);
 		*EVAL(list2) = list2 + n*size;
 		return;
 	}
@@ -302,19 +306,19 @@ setup(u_char *list1, u_char *list2, size
 	 * for simplicity.
 	 */
 	i = 4 + (n & 1);
-	insertionsort(list1 + (n - i) * size, i, size, cmp);
+	insertionsort(list1 + (n - i) * size, i, size, cmp, cookie);
 	last = list1 + size * (n - i);
 	*EVAL(list2 + (last - list1)) = list2 + n * size;
 
 #ifdef NATURAL
 	p2 = list2;
 	f1 = list1;
-	sense = (cmp(f1, f1 + size) > 0);
+	sense = (cmp(f1, f1 + size, cookie) > 0);
 	for (; f1 < last; sense = !sense) {
 		length = 2;
 					/* Find pairs with same sense. */
 		for (f2 = f1 + size2; f2 < last; f2 += size2) {
-			if ((cmp(f2, f2+ size) > 0) != sense)
+			if ((cmp(f2, f2 + size, cookie) > 0) != sense)
 				break;
 			length += 2;
 		}
@@ -327,7 +331,7 @@ setup(u_char *list1, u_char *list2, size
 		} else {				/* Natural merge */
 			l2 = f2;
 			for (f2 = f1 + size2; f2 < l2; f2 += size2) {
-				if ((cmp(f2-size, f2) > 0) != sense) {
+				if ((cmp(f2-size, f2, cookie) > 0) != sense) {
 					p2 = *EVAL(p2) = f2 - list1 + list2;
 					if (sense > 0)
 						reverse(f1, f2 - size);
@@ -337,7 +341,7 @@ setup(u_char *list1, u_char *list2, size
 			if (sense > 0)
 				reverse(f1, f2 - size);
 			f1 = f2;
-			if (f2 < last || cmp(f2 - size, f2) > 0)
+			if (f2 < last || cmp(f2 - size, f2, cookie) > 0)
 				p2 = *EVAL(p2) = f2 - list1 + list2;
 			else
 				p2 = *EVAL(p2) = list2 + n*size;
@@ -346,7 +350,7 @@ setup(u_char *list1, u_char *list2, size
 #else		/* pairwise merge only. */
 	for (f1 = list1, p2 = list2; f1 < last; f1 += size2) {
 		p2 = *EVAL(p2) = p2 + size2;
-		if (cmp (f1, f1 + size) > 0)
+		if (cmp(f1, f1 + size, cookie) > 0)
 			swap(f1, f1 + size);
 	}
 #endif /* NATURAL */
@@ -358,7 +362,7 @@ setup(u_char *list1, u_char *list2, size
  */
 static void
 insertionsort(u_char *a, size_t n, size_t size,
-    int (*cmp)(const void *, const void *))
+    int (*cmp)(const void *, const void *, void *), void *cookie)
 {
 	u_char *ai, *s, *t, *u, tmp;
 	size_t i;
@@ -369,8 +373,24 @@ insertionsort(u_char *a, size_t n, size_
 	for (ai = a+size; --n >= 1; ai += size)
 		for (t = ai; t > a; t -= size) {
 			u = t - size;
-			if (cmp(u, t) <= 0)
+			if (cmp(u, t, cookie) <= 0)
 				break;
 			swap(u, t);
 		}
 }
+
+static int
+cmpnocookie(const void *a, const void *b, void *cookie)
+{
+	int (*cmp)(const void *, const void *) = cookie;
+
+	return cmp(a, b);
+}
+
+int
+mergesort(void *a, size_t n, size_t es,
+    int (*cmp)(const void *, const void *))
+{
+
+	return mergesort_r(a, n, es, cmpnocookie, cmp);
+}

Index: src/lib/libc/stdlib/qsort.3
diff -u src/lib/libc/stdlib/qsort.3:1.14 src/lib/libc/stdlib/qsort.3:1.15
--- src/lib/libc/stdlib/qsort.3:1.14	Fri Sep 19 16:02:58 2014
+++ src/lib/libc/stdlib/qsort.3	Sun Mar  2 16:35:41 2025
@@ -1,4 +1,4 @@
-.\"	$NetBSD: qsort.3,v 1.14 2014/09/19 16:02:58 wiz Exp $
+.\"	$NetBSD: qsort.3,v 1.15 2025/03/02 16:35:41 riastradh Exp $
 .\"
 .\" Copyright (c) 1990, 1991, 1993
 .\"	The Regents of the University of California.  All rights reserved.
@@ -47,10 +47,16 @@
 .In stdlib.h
 .Ft void
 .Fn qsort "void *base" "size_t nmemb" "size_t size" "int (*compar)(const void *, const void *)"
+.Ft void
+.Fn qsort_r "void *base" "size_t nmemb" "size_t size" "int (*compar)(const void *, const void *, void *)" "void *cookie"
 .Ft int
 .Fn heapsort "void *base" "size_t nmemb" "size_t size" "int (*compar)(const void *, const void *)"
 .Ft int
+.Fn heapsort_r "void *base" "size_t nmemb" "size_t size" "int (*compar)(const void *, const void *, void *)" "void *cookie"
+.Ft int
 .Fn mergesort "void *base" "size_t nmemb" "size_t size" "int (*compar)(const void *, const void *)"
+.Ft int
+.Fn mergesort_r "void *base" "size_t nmemb" "size_t size" "int (*compar)(const void *, const void *, void *)" "void *cookie"
 .Sh DESCRIPTION
 The
 .Fn qsort
@@ -106,6 +112,14 @@ The function
 is stable.
 .Pp
 The
+.Fn qsort_r ,
+.Fn heapsort_r ,
+and
+.Fn mergesort_r
+functions pass an additional cookie argument through to the comparison
+function.
+.Pp
+The
 .Fn qsort
 function is an implementation of C.A.R. Hoare's ``quicksort'' algorithm,
 a variant of partition-exchange sorting; in particular, see D.E. Knuth's
@@ -233,6 +247,10 @@ were unable to allocate memory.
 .Sh STANDARDS
 The
 .Fn qsort
-function
-conforms to
+function conforms to
 .St -ansiC .
+.Pp
+The
+.Fn qsort_r
+function conforms to
+.St -p1003.1-2024 .

Index: src/lib/libc/stdlib/qsort.c
diff -u src/lib/libc/stdlib/qsort.c:1.23 src/lib/libc/stdlib/qsort.c:1.24
--- src/lib/libc/stdlib/qsort.c:1.23	Fri May 19 19:48:19 2017
+++ src/lib/libc/stdlib/qsort.c	Sun Mar  2 16:35:41 2025
@@ -1,4 +1,4 @@
-/*	$NetBSD: qsort.c,v 1.23 2017/05/19 19:48:19 christos Exp $	*/
+/*	$NetBSD: qsort.c,v 1.24 2025/03/02 16:35:41 riastradh Exp $	*/
 /*-
  * Copyright (c) 1992, 1993
  *	The Regents of the University of California.  All rights reserved.
@@ -33,7 +33,7 @@
 #if 0
 static char sccsid[] = "@(#)qsort.c	8.1 (Berkeley) 6/4/93";
 #else
-__RCSID("$NetBSD: qsort.c,v 1.23 2017/05/19 19:48:19 christos Exp $");
+__RCSID("$NetBSD: qsort.c,v 1.24 2025/03/02 16:35:41 riastradh Exp $");
 #endif
 #endif /* LIBC_SCCS and not lint */
 
@@ -44,7 +44,8 @@ __RCSID("$NetBSD: qsort.c,v 1.23 2017/05
 #include <stdlib.h>
 
 static inline char	*med3(char *, char *, char *,
-    int (*)(const void *, const void *));
+			    int (*)(const void *, const void *, void *),
+			    void *);
 static inline void	 swapfunc(char *, char *, size_t, int);
 
 #define min(a, b)	(a) < (b) ? a : b
@@ -70,7 +71,7 @@ static inline void
 swapfunc(char *a, char *b, size_t n, int swaptype)
 {
 
-	if (swaptype <= 1) 
+	if (swaptype <= 1)
 		swapcode(long, a, b, n)
 	else
 		swapcode(char, a, b, n)
@@ -88,17 +89,17 @@ swapfunc(char *a, char *b, size_t n, int
 
 static inline char *
 med3(char *a, char *b, char *c,
-    int (*cmp)(const void *, const void *))
+    int (*cmp)(const void *, const void *, void *), void *cookie)
 {
 
-	return cmp(a, b) < 0 ?
-	       (cmp(b, c) < 0 ? b : (cmp(a, c) < 0 ? c : a ))
-              :(cmp(b, c) > 0 ? b : (cmp(a, c) < 0 ? a : c ));
+	return cmp(a, b, cookie) < 0 ?
+	       (cmp(b, c, cookie) < 0 ? b : (cmp(a, c, cookie) < 0 ? c : a ))
+              :(cmp(b, c, cookie) > 0 ? b : (cmp(a, c, cookie) < 0 ? a : c ));
 }
 
 void
-qsort(void *a, size_t n, size_t es,
-    int (*cmp)(const void *, const void *))
+qsort_r(void *a, size_t n, size_t es,
+    int (*cmp)(const void *, const void *, void *), void *cookie)
 {
 	char *pa, *pb, *pc, *pd, *pl, *pm, *pn;
 	size_t d, r, s;
@@ -110,7 +111,8 @@ qsort(void *a, size_t n, size_t es,
 loop:	SWAPINIT(a, es);
 	if (n < 7) {
 		for (pm = (char *) a + es; pm < (char *) a + n * es; pm += es)
-			for (pl = pm; pl > (char *) a && cmp(pl - es, pl) > 0;
+			for (pl = pm;
+			     pl > (char *) a && cmp(pl - es, pl, cookie) > 0;
 			     pl -= es)
 				swap(pl, pl - es);
 		return;
@@ -121,25 +123,25 @@ loop:	SWAPINIT(a, es);
 		pn = (char *) a + (n - 1) * es;
 		if (n > 40) {
 			d = (n / 8) * es;
-			pl = med3(pl, pl + d, pl + 2 * d, cmp);
-			pm = med3(pm - d, pm, pm + d, cmp);
-			pn = med3(pn - 2 * d, pn - d, pn, cmp);
+			pl = med3(pl, pl + d, pl + 2 * d, cmp, cookie);
+			pm = med3(pm - d, pm, pm + d, cmp, cookie);
+			pn = med3(pn - 2 * d, pn - d, pn, cmp, cookie);
 		}
-		pm = med3(pl, pm, pn, cmp);
+		pm = med3(pl, pm, pn, cmp, cookie);
 	}
 	swap(a, pm);
 	pa = pb = (char *) a + es;
 
 	pc = pd = (char *) a + (n - 1) * es;
 	for (;;) {
-		while (pb <= pc && (cmp_result = cmp(pb, a)) <= 0) {
+		while (pb <= pc && (cmp_result = cmp(pb, a, cookie)) <= 0) {
 			if (cmp_result == 0) {
 				swap(pa, pb);
 				pa += es;
 			}
 			pb += es;
 		}
-		while (pb <= pc && (cmp_result = cmp(pc, a)) >= 0) {
+		while (pb <= pc && (cmp_result = cmp(pc, a, cookie)) >= 0) {
 			if (cmp_result == 0) {
 				swap(pc, pd);
 				pd -= es;
@@ -168,7 +170,7 @@ loop:	SWAPINIT(a, es);
 		/* Recurse for 1st side, iterate for 2nd side. */
 		if (s > es) {
 			if (r > es)
-				qsort(a, r / es, es, cmp);
+				qsort_r(a, r / es, es, cmp, cookie);
 			a = pn - s;
 			n = s / es;
 			goto loop;
@@ -177,9 +179,25 @@ loop:	SWAPINIT(a, es);
 		/* Recurse for 2nd side, iterate for 1st side. */
 		if (r > es) {
 			if (s > es)
-				qsort(pn - s, s / es, es, cmp);
+				qsort_r(pn - s, s / es, es, cmp, cookie);
 			n = r / es;
 			goto loop;
 		}
 	}
 }
+
+static int
+cmpnocookie(const void *a, const void *b, void *cookie)
+{
+	int (*cmp)(const void *, const void *) = cookie;
+
+	return cmp(a, b);
+}
+
+void
+qsort(void *a, size_t n, size_t es,
+    int (*cmp)(const void *, const void *))
+{
+
+	qsort_r(a, n, es, cmpnocookie, cmp);
+}

Index: src/sys/lib/libkern/libkern.h
diff -u src/sys/lib/libkern/libkern.h:1.147 src/sys/lib/libkern/libkern.h:1.148
--- src/sys/lib/libkern/libkern.h:1.147	Fri Nov  1 21:11:37 2024
+++ src/sys/lib/libkern/libkern.h	Sun Mar  2 16:35:41 2025
@@ -1,4 +1,4 @@
-/*	$NetBSD: libkern.h,v 1.147 2024/11/01 21:11:37 riastradh Exp $	*/
+/*	$NetBSD: libkern.h,v 1.148 2025/03/02 16:35:41 riastradh Exp $	*/
 
 /*-
  * Copyright (c) 1992, 1993
@@ -476,7 +476,10 @@ void	 hexdump(void (*)(const char *, ...
 int	 snprintb(char *, size_t, const char *, uint64_t);
 int	 snprintb_m(char *, size_t, const char *, uint64_t, size_t);
 int	 kheapsort(void *, size_t, size_t, int (*)(const void *, const void *),
-		   void *);
+	    void *);
+int	 kheapsort_r(void *, size_t, size_t,
+	    int (*)(const void *, const void *, void *), void *,
+	    void *);
 uint32_t crc32(uint32_t, const uint8_t *, size_t);
 #if __GNUC_PREREQ__(4, 5) \
     && (defined(__alpha_cix__) || defined(__mips_popcount))

Index: src/tests/lib/libc/stdlib/Makefile
diff -u src/tests/lib/libc/stdlib/Makefile:1.34 src/tests/lib/libc/stdlib/Makefile:1.35
--- src/tests/lib/libc/stdlib/Makefile:1.34	Tue Jul  4 15:06:36 2023
+++ src/tests/lib/libc/stdlib/Makefile	Sun Mar  2 16:35:41 2025
@@ -1,4 +1,4 @@
-# $NetBSD: Makefile,v 1.34 2023/07/04 15:06:36 riastradh Exp $
+# $NetBSD: Makefile,v 1.35 2025/03/02 16:35:41 riastradh Exp $
 
 .include <bsd.own.mk>
 
@@ -23,6 +23,7 @@ TESTS_C+=	t_system
 
 TESTS_SH+=	t_atexit
 TESTS_SH+=	t_getopt
+TESTS_SH+=	t_sort
 
 MKMAN=no
 
@@ -30,6 +31,7 @@ BINDIR=		${TESTSDIR}
 
 PROGS+=		h_atexit
 PROGS+=		h_getopt h_getopt_long
+PROGS+=		h_sort
 
 CFLAGS.t_posix_memalign.c+=	-fno-builtin-posix_memalign
 CFLAGS.t_posix_memalign.c+=	-fno-builtin-aligned_alloc

Index: src/tools/compat/compat_defs.h
diff -u src/tools/compat/compat_defs.h:1.123 src/tools/compat/compat_defs.h:1.124
--- src/tools/compat/compat_defs.h:1.123	Thu Oct 31 17:05:05 2024
+++ src/tools/compat/compat_defs.h	Sun Mar  2 16:35:41 2025
@@ -1,4 +1,4 @@
-/*	$NetBSD: compat_defs.h,v 1.123 2024/10/31 17:05:05 kre Exp $	*/
+/*	$NetBSD: compat_defs.h,v 1.124 2025/03/02 16:35:41 riastradh Exp $	*/
 
 #ifndef	__NETBSD_COMPAT_DEFS_H__
 #define	__NETBSD_COMPAT_DEFS_H__
@@ -462,8 +462,12 @@ int __nbcompat_gettemp(char *, int *, in
 ssize_t pread(int, void *, size_t, off_t);
 #endif
 
-int __nbcompat_heapsort (void *, size_t, size_t, int (*)(const void *, const void *));
+int __nbcompat_heapsort(void *, size_t, size_t,
+    int (*)(const void *, const void *));
+int __nbcompat_heapsort_r(void *, size_t, size_t,
+    int (*)(const void *, const void *, void *), void *);
 #define heapsort __nbcompat_heapsort
+#define heapsort_r __nbcompat_heapsort_r
 
 #if !HAVE_DECL_HEAPSORT
 int heapsort (void *, size_t, size_t, int (*)(const void *, const void *));

Added files:

Index: src/tests/lib/libc/stdlib/h_sort.c
diff -u /dev/null src/tests/lib/libc/stdlib/h_sort.c:1.1
--- /dev/null	Sun Mar  2 16:35:42 2025
+++ src/tests/lib/libc/stdlib/h_sort.c	Sun Mar  2 16:35:41 2025
@@ -0,0 +1,177 @@
+/*	$NetBSD: h_sort.c,v 1.1 2025/03/02 16:35:41 riastradh Exp $	*/
+
+/*-
+ * Copyright (c) 2025 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: h_sort.c,v 1.1 2025/03/02 16:35:41 riastradh Exp $");
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static void
+heapsort_r_wrapper(void *a, size_t n, size_t sz,
+    int (*cmp)(const void *, const void *, void *), void *cookie)
+{
+
+	if (heapsort_r(a, n, sz, cmp, cookie) == -1)
+		err(1, "heapsort_r");
+}
+
+static void
+mergesort_r_wrapper(void *a, size_t n, size_t sz,
+    int (*cmp)(const void *, const void *, void *), void *cookie)
+{
+
+	if (mergesort_r(a, n, sz, cmp, cookie) == -1)
+		err(1, "mergesort_r");
+}
+
+static int
+cmp(const void *va, const void *vb, void *cookie)
+{
+	const char *buf = cookie;
+	const size_t *a = va;
+	const size_t *b = vb;
+
+	return strcmp(buf + *a, buf + *b);
+}
+
+int
+main(int argc, char **argv)
+{
+	void (*sortfn)(void *, size_t, size_t,
+	    int (*)(const void *, const void *, void *), void *);
+	char *buf = NULL;
+	size_t nbuf;
+	size_t *lines = NULL;
+	size_t nlines;
+	size_t off;
+	ssize_t nread;
+	char *p;
+	size_t i;
+	int error;
+
+	/*
+	 * Parse arguments.
+	 */
+	setprogname(argv[0]);
+	if (argc < 2)
+		errx(1, "Usage: %s <sortfn>", getprogname());
+	if (strcmp(argv[1], "heapsort_r") == 0)
+		sortfn = &heapsort_r_wrapper;
+	else if (strcmp(argv[1], "mergesort_r") == 0)
+		sortfn = &mergesort_r_wrapper;
+	else if (strcmp(argv[1], "qsort_r") == 0)
+		sortfn = &qsort_r;
+	else
+		errx(1, "unknown sort: %s", argv[1]);
+
+	/*
+	 * Allocate an initial 4K buffer.
+	 */
+	nbuf = 0x1000;
+	error = reallocarr(&buf, nbuf, 1);
+	if (error)
+		err(1, "reallocarr");
+
+	/*
+	 * Read the input into a contiguous buffer.  Reject input with
+	 * embedded NULs so we can use strcmp(3) to compare lines.
+	 */
+	off = 0;
+	while ((nread = read(STDIN_FILENO, buf + off, nbuf - off)) != 0) {
+		if (nread == -1)
+			err(1, "read");
+		if ((size_t)nread > nbuf - off)
+			errx(1, "overlong read: %zu", (size_t)nread);
+		if (memchr(buf + off, '\0', (size_t)nread) != NULL)
+			errx(1, "NUL byte in input");
+		off += (size_t)nread;
+
+		/*
+		 * If we filled the buffer, reallocate it with double
+		 * the size.  Bail if that would overflow.
+		 */
+		if (nbuf - off == 0) {
+			if (nbuf > SIZE_MAX/2)
+				errx(1, "input overflow");
+			nbuf *= 2;
+			error = reallocarr(&buf, nbuf, 1);
+			if (error)
+				err(1, "reallocarr");
+		}
+	}
+
+	/*
+	 * If the input was empty, nothing to do.
+	 */
+	if (off == 0)
+		return 0;
+
+	/*
+	 * NUL-terminate the input and count the lines.  The last line
+	 * may have no trailing \n.
+	 */
+	buf[off] = '\0';
+	nlines = 1;
+	for (p = buf; (p = strchr(p, '\n')) != NULL;) {
+		if (*++p == '\0')
+			break;
+		nlines++;
+	}
+
+	/*
+	 * Create an array of line offsets to sort.  NUL-terminate each
+	 * line so we can use strcmp(3).
+	 */
+	error = reallocarr(&lines, nlines, sizeof(lines[0]));
+	if (lines == NULL)
+		err(1, "reallocarr");
+	i = 0;
+	for (p = buf; lines[i++] = p - buf, (p = strchr(p, '\n')) != NULL;) {
+		*p = '\0';
+		if (*++p == '\0')
+			break;
+	}
+
+	/*
+	 * Sort the array of line offsets via comparison function that
+	 * consults the buffer as a cookie.
+	 */
+	(*sortfn)(lines, nlines, sizeof(lines[0]), &cmp, buf);
+
+	/*
+	 * Print the lines in sorted order.
+	 */
+	for (i = 0; i < nlines; i++)
+		printf("%s\n", buf + lines[i]);
+	fflush(stdout);
+	return ferror(stdout);
+}
Index: src/tests/lib/libc/stdlib/t_sort.sh
diff -u /dev/null src/tests/lib/libc/stdlib/t_sort.sh:1.1
--- /dev/null	Sun Mar  2 16:35:42 2025
+++ src/tests/lib/libc/stdlib/t_sort.sh	Sun Mar  2 16:35:41 2025
@@ -0,0 +1,61 @@
+#	$NetBSD: t_sort.sh,v 1.1 2025/03/02 16:35:41 riastradh Exp $
+#
+# Copyright (c) 2025 The NetBSD Foundation, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+check_sort()
+{
+	local sortfn
+
+	set -Ceu
+
+	sortfn="$1"
+
+	printf 'foo\nbar\nbaz\nquux' >in1
+	printf 'bar\nbaz\nfoo\nquux\n' >out
+	atf_check -s exit:0 -o file:out \
+		"$(atf_get_srcdir)"/h_sort "$sortfn" <in1
+	atf_check -s exit:0 -o empty sh -c 'exec shuffle -f - <in1 >in2'
+	atf_check -s exit:0 -o file:out \
+		"$(atf_get_srcdir)"/h_sort "$sortfn" <in2
+}
+
+sortfn_case()
+{
+	local sortfn
+
+	sortfn="$1"
+
+	eval "${sortfn}_head() { atf_set descr \"Test ${sortfn}\"; }"
+	eval "${sortfn}_body() { check_sort $sortfn; }"
+	atf_add_test_case "$sortfn"
+}
+
+atf_init_test_cases()
+{
+
+	sortfn_case heapsort_r
+	sortfn_case mergesort_r
+	sortfn_case qsort_r
+}

Reply via email to