mirror of
https://github.com/samba-team/samba.git
synced 2025-01-10 01:18:15 +03:00
251 lines
5.8 KiB
C
251 lines
5.8 KiB
C
|
/*
|
||
|
Stable sort routines
|
||
|
|
||
|
Copyright © Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
|
||
|
|
||
|
** NOTE! The following LGPL license applies to this file which is used by
|
||
|
** the ldb library. This does NOT imply that all of Samba is released
|
||
|
** under the LGPL.
|
||
|
|
||
|
This 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 3 of the License, or (at your option) any later version.
|
||
|
|
||
|
This 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 this library; if not, see <http://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
|
||
|
#include <stdint.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include <unistd.h>
|
||
|
#include <talloc.h>
|
||
|
#include "replace.h"
|
||
|
#include "stable_sort.h"
|
||
|
|
||
|
static void sort_few(char *array, char *aux,
|
||
|
size_t n,
|
||
|
size_t s,
|
||
|
samba_compare_with_context_fn_t cmpfn,
|
||
|
void *opaque)
|
||
|
{
|
||
|
/* a kind of insertion sort for small n. */
|
||
|
int i, j, dist;
|
||
|
int cmp;
|
||
|
char *a, *b;
|
||
|
|
||
|
for (i = 1; i < n; i++) {
|
||
|
a = &array[i * s];
|
||
|
/* leftwards is sorted. look until we find this one's place */
|
||
|
for (j = i - 1; j >= 0; j--) {
|
||
|
b = &array[j * s];
|
||
|
cmp = cmpfn(a, b, opaque);
|
||
|
if (cmp >= 0) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
dist = i - 1 - j;
|
||
|
if (dist == 0) {
|
||
|
/* a is already in the right place */
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
b = &array[(i - dist) * s];
|
||
|
memcpy(aux, a, s);
|
||
|
memmove(b + s, b, s * dist);
|
||
|
memcpy(b, aux, s);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
static void merge(char *dest,
|
||
|
char *a, size_t alen,
|
||
|
char *b, size_t blen,
|
||
|
size_t s,
|
||
|
samba_compare_with_context_fn_t cmpfn,
|
||
|
void *opaque)
|
||
|
{
|
||
|
size_t ai = 0;
|
||
|
size_t bi = 0;
|
||
|
size_t di = 0;
|
||
|
while (ai < alen && bi < blen) {
|
||
|
int cmp = cmpfn(&a[ai * s], &b[bi * s], opaque);
|
||
|
if (cmp <= 0) {
|
||
|
memcpy(&dest[di * s], &a[ai * s], s);
|
||
|
ai++;
|
||
|
} else {
|
||
|
memcpy(&dest[di * s], &b[bi * s], s);
|
||
|
bi++;
|
||
|
}
|
||
|
di++;
|
||
|
}
|
||
|
if (ai < alen) {
|
||
|
memcpy(&dest[di * s], &a[ai * s], s * (alen - ai));
|
||
|
} else if (bi < blen) {
|
||
|
memcpy(&dest[di * s], &b[bi * s], s * (blen - bi));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
bool stable_sort_r(void *array, void *aux,
|
||
|
size_t n,
|
||
|
size_t s,
|
||
|
samba_compare_with_context_fn_t cmpfn,
|
||
|
void * opaque)
|
||
|
{
|
||
|
char *src = array, *dest = aux, *tmp = NULL;
|
||
|
size_t i, j, k;
|
||
|
size_t runsize;
|
||
|
if (array == NULL || aux == NULL) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (n < 20) {
|
||
|
sort_few(array, aux, n, s, cmpfn, opaque);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if (n > SIZE_MAX / s) {
|
||
|
/*
|
||
|
* We will have an integer overflow if we continue.
|
||
|
*
|
||
|
* This means that the *supposed* size of the allocated buffer
|
||
|
* is greater than SIZE_MAX, which is not possible in theory
|
||
|
* or practice, and is a sign the caller has got very
|
||
|
* confused.
|
||
|
*/
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* This is kind of a bottom-up merge sort.
|
||
|
*
|
||
|
* We start but sorting into a whole lot of little runs, using an
|
||
|
* insertion sort which is efficient for small numbers. Empirically,
|
||
|
* on 2 machines, a run size of around 8 seems optimal, but the peak
|
||
|
* is wide, and it seems worth adapting the size to avoid an
|
||
|
* unbalanced final merge at the top. That is, if we pick the right
|
||
|
* runsize now, we will finish with a merge of roughly n/2:n/2, and
|
||
|
* not have to follow that with an merge of roughly n:[a few], which
|
||
|
* we would sometimes do with a fixed size at the lowest level.
|
||
|
*
|
||
|
* The aim is a runsize of n / (a power of 2) rounded up, in the
|
||
|
* target range.
|
||
|
*/
|
||
|
|
||
|
runsize = n;
|
||
|
while (runsize > 10) {
|
||
|
runsize++;
|
||
|
runsize >>= 1;
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < n; i += runsize) {
|
||
|
size_t nn = MIN(n - i, runsize);
|
||
|
sort_few(&src[i * s], aux, nn, s, cmpfn, opaque);
|
||
|
}
|
||
|
|
||
|
while (runsize < n) {
|
||
|
for (i = 0; i < n; i += runsize * 2) {
|
||
|
j = i + runsize;
|
||
|
if (j >= n) {
|
||
|
/*
|
||
|
* first run doesn't fit, meaning this chunk
|
||
|
* is already sorted. We just need to copy
|
||
|
* it.
|
||
|
*/
|
||
|
size_t nn = n - i;
|
||
|
memcpy(&dest[i * s], &src[i * s], nn * s);
|
||
|
break;
|
||
|
}
|
||
|
k = j + runsize;
|
||
|
if (k > n) {
|
||
|
merge(&dest[i * s],
|
||
|
&src[i * s], runsize,
|
||
|
&src[j * s], n - j,
|
||
|
s,
|
||
|
cmpfn, opaque);
|
||
|
} else {
|
||
|
merge(&dest[i * s],
|
||
|
&src[i * s], runsize,
|
||
|
&src[j * s], runsize,
|
||
|
s,
|
||
|
cmpfn, opaque);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
tmp = src;
|
||
|
src = dest;
|
||
|
dest = tmp;
|
||
|
runsize *= 2;
|
||
|
}
|
||
|
/*
|
||
|
* We have sorted the array into src, which is either array or aux.
|
||
|
*/
|
||
|
if (src != array) {
|
||
|
memcpy(array, src, n * s);
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/*
|
||
|
* A wrapper that allocates (and frees) the temporary buffer if necessary.
|
||
|
*
|
||
|
* returns false on allocation error, true otherwise.
|
||
|
*/
|
||
|
|
||
|
bool stable_sort_talloc_r(TALLOC_CTX *mem_ctx,
|
||
|
void *array,
|
||
|
size_t n,
|
||
|
size_t s,
|
||
|
samba_compare_with_context_fn_t cmpfn,
|
||
|
void *opaque)
|
||
|
{
|
||
|
bool ok;
|
||
|
char *mem = talloc_array_size(mem_ctx, s, n);
|
||
|
if (mem == NULL) {
|
||
|
return false;
|
||
|
}
|
||
|
ok = stable_sort_r(array, mem, n, s, cmpfn, opaque);
|
||
|
talloc_free(mem);
|
||
|
return ok;
|
||
|
}
|
||
|
|
||
|
|
||
|
bool stable_sort(void *array, void *aux,
|
||
|
size_t n,
|
||
|
size_t s,
|
||
|
samba_compare_fn_t cmpfn)
|
||
|
{
|
||
|
/*
|
||
|
* What is this magic, casting cmpfn into a different type that takes
|
||
|
* an extra parameter? Is that allowed?
|
||
|
*
|
||
|
* A: Yes. It's fine. The extra argument will be passed on the stack
|
||
|
* or (more likely) a register, and the cmpfn will remain blissfully
|
||
|
* unaware.
|
||
|
*/
|
||
|
return stable_sort_r(array, aux, n, s,
|
||
|
(samba_compare_with_context_fn_t)cmpfn,
|
||
|
NULL);
|
||
|
}
|
||
|
|
||
|
|
||
|
bool stable_sort_talloc(TALLOC_CTX *mem_ctx,
|
||
|
void *array,
|
||
|
size_t n,
|
||
|
size_t s,
|
||
|
samba_compare_fn_t cmpfn)
|
||
|
{
|
||
|
return stable_sort_talloc_r(mem_ctx, array, n, s,
|
||
|
(samba_compare_with_context_fn_t)cmpfn,
|
||
|
NULL);
|
||
|
}
|