forked from neomutt/neomutt
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
Showing
8 changed files
with
194 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -337,6 +337,8 @@ if {1} { | |
getsid \ | ||
iswblank \ | ||
mkdtemp \ | ||
qsort_r \ | ||
qsort_s \ | ||
strsep \ | ||
utimesnsat \ | ||
vasprintf \ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |