Skip to content

Commit

Permalink
muttlib: add mutt_qsort_r
Browse files Browse the repository at this point in the history
Being able to sort data with reference to auxiliary information passed
in through a local parameter rather than requiring non-local context
through a global variable makes code easier to read.  qsort_r() will
be added in a future version of POSIX [1] with the same signature
present in glibc, and nicely fills this role, but is not portable yet,
so we have to write a shim instead.  Various FreeBSD releases ship
with an incompatible signature for qsort_r (the opaque variable passed
in a different parameter); FreeBSD 12 is hopeless (we'd have to shim
that reversed argument), but starting with FreeBSD 13, we can use
qsort_s instead (however, note that C11 mentioned qsort_s as optional,
and Windows has qsort_s with yet another incompatible signature).  And
for platforms that lack either re-entrant form, as long as our signal
handlers and comparators don't themselves try to call a secondary
sort, we don't care about re-entrancy, and can isolate the use of
external data to static variables in one file rather than global
variables across multiple files.

(Writing a qsort_r replacement on top of qsort where re-entrancy does
matter would require a mutex and shenanigans to support a stack of
nested calls, which gets a lot more complex.)

The unit test will hopefully catch any other special-casing we need to
perform in writing our wrapper for other systems with non-standard
signatures of either qsort_s or qsort_r.
  • Loading branch information
ebblake authored and flatcap committed Jun 25, 2021
1 parent 4940d52 commit 104fcd5
Show file tree
Hide file tree
Showing 8 changed files with 194 additions and 4 deletions.
4 changes: 2 additions & 2 deletions Makefile.autosetup
Original file line number Diff line number Diff line change
Expand Up @@ -451,8 +451,8 @@ LIBMUTTOBJS= mutt/base64.o mutt/buffer.o mutt/charset.o mutt/date.o \
mutt/envlist.o mutt/exit.o mutt/file.o mutt/filter.o \
mutt/hash.o mutt/list.o mutt/logging.o mutt/mapping.o \
mutt/mbyte.o mutt/md5.o mutt/memory.o mutt/notify.o \
mutt/path.o mutt/pool.o mutt/prex.o mutt/random.o mutt/regex.o \
mutt/signal.o mutt/slist.o mutt/state.o mutt/string.o
mutt/path.o mutt/pool.o mutt/prex.o mutt/qsort_r.o mutt/random.o \
mutt/regex.o mutt/signal.o mutt/slist.o mutt/state.o mutt/string.o
CLEANFILES+= $(LIBMUTT) $(LIBMUTTOBJS)
ALLOBJS+= $(LIBMUTTOBJS)

Expand Down
2 changes: 2 additions & 0 deletions auto.def
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,8 @@ if {1} {
getsid \
iswblank \
mkdtemp \
qsort_r \
qsort_s \
strsep \
utimesnsat \
vasprintf \
Expand Down
2 changes: 2 additions & 0 deletions mutt/lib.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
* | mutt/path.c | @subpage mutt_path |
* | mutt/pool.c | @subpage mutt_pool |
* | mutt/prex.c | @subpage mutt_prex |
* | mutt/qsort_r.c | @subpage mutt_qsort_r |
* | mutt/random.c | @subpage mutt_random |
* | mutt/regex.c | @subpage mutt_regex |
* | mutt/slist.c | @subpage mutt_slist |
Expand Down Expand Up @@ -86,6 +87,7 @@
#include "path.h"
#include "pool.h"
#include "prex.h"
#include "qsort_r.h"
#include "queue.h"
#include "random.h"
#include "regex3.h"
Expand Down
87 changes: 87 additions & 0 deletions mutt/qsort_r.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/**
* @file
* Context-free sorting function
*
* @authors
* Copyright (C) 2021 Eric Blake <[email protected]>
*
* @copyright
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 2 of the License, or (at your option) any later
* version.
*
* This program 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 General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/

/**
* @page mutt_qsort_r Context-free sorting function
*
* Context-free sorting function
*/

#include "config.h"
#include <stddef.h>
#include <assert.h>
#include <stdlib.h>
#include <sys/types.h>
#include "qsort_r.h"

typedef int (*qsort_compar_t)(const void *a, const void *b);

#if !defined(HAVE_QSORT_S) && (!defined(HAVE_QSORT_R) || defined(__FreeBSD__))
/// Original comparator in fallback implementation
static qsort_r_compar_t global_compar = NULL;
/// Original opaque data in fallback implementation
static void *global_data = NULL;

/**
* relay_compar - Shim to pass context through to real comparator
* @param a First item to be compared
* @param b Second item to be compared
* @return <0 a sorts before b
* @return 0 a and b sort equally (sort stability not guaranteed)
* @return >0 a sorts after b
*/
static int relay_compar(const void *a, const void *b)
{
return global_compar(a, b, global_data);
}
#endif

/**
* mutt_qsort_r - Sort an array, where the comparator has access to opaque data rather than requiring global variables
* @param base Start of the array to be sorted
* @param nmemb Number of elements in the array
* @param size Size of each array element
* @param compar Comparison function, return <0/0/>0 to compare two elements
* @param arg Opaque argument to pass to @a compar
*
* @note This implementation might not be re-entrant: signal handlers and
* @a compar must not call mutt_qsort_r().
*/
void mutt_qsort_r(void *base, size_t nmemb, size_t size, qsort_r_compar_t compar, void *arg)
{
#ifdef HAVE_QSORT_S
/* FreeBSD 13, where qsort_r had incompatible signature but qsort_s works */
qsort_s(base, nmemb, size, compar, arg);
#elif defined(HAVE_QSORT_R) && !defined(__FreeBSD__)
/* glibc, POSIX (https://www.austingroupbugs.net/view.php?id=900) */
/* Avoid FreeBSD 12, where qsort_r has wrong signature but lacks qsort_s */
qsort_r(base, nmemb, size, compar, arg);
#else
/* This fallback is not re-entrant. */
assert((global_compar == NULL) && (global_data == NULL));
global_compar = compar;
global_data = arg;
qsort(base, nmemb, size, relay_compar);
global_compar = NULL;
global_data = NULL;
#endif
}
32 changes: 32 additions & 0 deletions mutt/qsort_r.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* @file
* Context-free sorting function
*
* @authors
* Copyright (C) 2021 Eric Blake <[email protected]>
*
* @copyright
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 2 of the License, or (at your option) any later
* version.
*
* This program 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 General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef MUTT_LIB_QSORT_R_H
#define MUTT_LIB_QSORT_R_H

#include <stddef.h>

typedef int (*qsort_r_compar_t)(const void *a, const void *b, void *arg);

void mutt_qsort_r(void *base, size_t nmemb, size_t size, qsort_r_compar_t compar, void *arg);

#endif /* MUTT_LIB_QSORT_R_H */
7 changes: 5 additions & 2 deletions test/Makefile.autosetup
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,8 @@ SLIST_OBJS = test/slist/slist_add_list.o \
test/slist/slist_remove_string.o \
test/slist/slist_to_buffer.o

SORT_OBJS = test/sort/mutt_qsort_r.o

@if HAVE_BDB || HAVE_GDBM || HAVE_KC || HAVE_LMDB || HAVE_QDBM || HAVE_ROCKSDB || HAVE_TDB || HAVE_TC
STORE_OBJS += test/store/common.o test/store/store.o
@endif
Expand Down Expand Up @@ -564,8 +566,8 @@ BUILD_DIRS = $(PWD)/test/account $(PWD)/test/address $(PWD)/test/array \
$(PWD)/test/path $(PWD)/test/pattern $(PWD)/test/pool \
$(PWD)/test/prex $(PWD)/test/regex $(PWD)/test/rfc2047 \
$(PWD)/test/rfc2231 $(PWD)/test/signal $(PWD)/test/slist \
$(PWD)/test/store $(PWD)/test/string $(PWD)/test/tags \
$(PWD)/test/thread $(PWD)/test/url
$(PWD)/test/sort $(PWD)/test/store $(PWD)/test/string \
$(PWD)/test/tags $(PWD)/test/thread $(PWD)/test/url

TEST_OBJS = test/main.o test/common.o \
$(ACCOUNT_OBJS) \
Expand Down Expand Up @@ -611,6 +613,7 @@ TEST_OBJS = test/main.o test/common.o \
$(RFC2231_OBJS) \
$(SIGNAL_OBJS) \
$(SLIST_OBJS) \
$(SORT_OBJS) \
$(STORE_OBJS) \
$(STRING_OBJS) \
$(TAGS_OBJS) \
Expand Down
3 changes: 3 additions & 0 deletions test/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,9 @@
NEOMUTT_TEST_ITEM(test_slist_remove_string) \
NEOMUTT_TEST_ITEM(test_slist_to_buffer) \
\
/* sort */ \
NEOMUTT_TEST_ITEM(test_mutt_qsort_r) \
\
/* string */ \
NEOMUTT_TEST_ITEM(test_mutt_istr_equal) \
NEOMUTT_TEST_ITEM(test_mutt_istr_find) \
Expand Down
61 changes: 61 additions & 0 deletions test/sort/mutt_qsort_r.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* @file
* Test code for mutt_qsort_r()
*
* @authors
* Copyright (C) 2021 Eric Blake <[email protected]>
*
* @copyright
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 2 of the License, or (at your option) any later
* version.
*
* This program 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 General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/

#define TEST_NO_MAIN
#include "config.h"
#include "acutest.h"
#include "mutt/lib.h"

static void *exparg;

/* Compare two integers, ascending if arg is NULL, else descending */
static int compare_ints(const void *a, const void *b, void *arg)
{
TEST_CHECK(arg == exparg);
if (!arg)
return *(int *) a - *(int *) b;
return *(int *) b - *(int *) a;
}

void test_mutt_qsort_r(void)
{
// void mutt_qsort_r(void *base, size_t nmemb, size_t size, qsort_r_compar_t compar, void *arg);
int array[3];

array[0] = 2;
array[1] = 1;
array[2] = 3;
exparg = NULL;
mutt_qsort_r(array, 3, sizeof(int), compare_ints, exparg);
TEST_CHECK(array[0] == 1);
TEST_CHECK(array[1] == 2);
TEST_CHECK(array[2] == 3);

array[0] = 2;
array[1] = 1;
array[2] = 3;
exparg = array;
mutt_qsort_r(array, 3, sizeof(int), compare_ints, exparg);
TEST_CHECK(array[0] == 3);
TEST_CHECK(array[1] == 2);
TEST_CHECK(array[2] == 1);
}

0 comments on commit 104fcd5

Please sign in to comment.