/** BEGIN COPYRIGHT BLOCK
 * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
 * Copyright (C) 2005 Red Hat, Inc.
 * All rights reserved.
 *
 * License: GPL (version 3 or any later version).
 * See LICENSE for details.
 * END COPYRIGHT BLOCK **/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

/* Code to implement result sorting */

#include "back-ldbm.h"

#define CHECK_INTERVAL 10 /* The frequency whith which we'll check the admin limits */

/* Structure to carry the things we need down the call stack */
struct baggage_carrier
{
    backend *be;      /* For id2entry */
    Slapi_PBlock *pb; /* For slapi_op_abandoned */
    struct timespec *expire_time;
    int lookthrough_limit;
    int check_counter; /* Used to avoid checking every 100ns */
};
typedef struct baggage_carrier baggage_carrier;

static int slapd_qsort(baggage_carrier *bc, IDList *list, sort_spec *s);
static int print_out_sort_spec(char *buffer, sort_spec *s, int *size);

static void
sort_spec_thing_free(sort_spec_thing *s)
{
    slapi_ch_free_string(&s->type);
    slapi_ch_free_string(&s->matchrule);
    if (NULL != s->mr_pb) {
        destroy_matchrule_indexer(s->mr_pb);
        slapi_pblock_destroy(s->mr_pb);
    }
    attr_done(&s->sattr);
    slapi_ch_free((void **)&s);
}

static sort_spec_thing *
sort_spec_thing_allocate(void)
{
    return (sort_spec_thing *)slapi_ch_calloc(1, sizeof(sort_spec_thing));
}

void
sort_spec_free(sort_spec *s)
{
    /* Walk down the list freeing */
    sort_spec_thing *t = (sort_spec_thing *)s;
    sort_spec_thing *p = NULL;
    do {
        p = t->next;
        sort_spec_thing_free(t);
        t = p;
    } while (p);
}

static sort_spec_thing *
sort_spec_thing_new(char *type, char *matchrule, int reverse)
{
    sort_spec_thing *s = sort_spec_thing_allocate();
    if (NULL == s) {
        return s;
    }
    s->type = type;
    s->matchrule = matchrule;
    s->order = reverse;
    slapi_attr_init(&s->sattr, type);
    return s;
}

const char *
sort_log_access(Slapi_PBlock *pb, sort_spec_thing *s, IDList *candidates, PRBool just_copy)
{
#define SORT_LOG_BSZ 64
#define SORT_LOG_PAD 22 /* space for the number of candidates */
    char stack_buffer[SORT_LOG_BSZ + SORT_LOG_PAD];
    char *buffer = NULL;
    int ret = 0;
    int size = SORT_LOG_BSZ + SORT_LOG_PAD;
    char *prefix = "SORT ";
    int prefix_size = strlen(prefix);
    char candidate_buffer[32]; /* store u_long value; max 20 digits */
    int candidate_size = 0;
    char *buffer_copy = NULL;

    buffer = stack_buffer;
    size -= PR_snprintf(buffer, sizeof(stack_buffer), "%s", prefix);
    if (candidates) {
        if (ALLIDS(candidates)) {
            PR_snprintf(candidate_buffer, sizeof(candidate_buffer), "(*)");
            candidate_size = strlen(candidate_buffer);
        } else {
            PR_snprintf(candidate_buffer, sizeof(candidate_buffer),
                        "(%lu)", (u_long)candidates->b_nids);
            candidate_size = strlen(candidate_buffer);
        }
    }
    size -= (candidate_size + 1); /* 1 for '\0' */
    ret = print_out_sort_spec(buffer + prefix_size, s, &size);
    if (0 != ret) {
        /* It wouldn't fit in the buffer */
        buffer =
            slapi_ch_malloc(prefix_size + size + candidate_size + SORT_LOG_PAD);
        sprintf(buffer, "%s", prefix);
        ret = print_out_sort_spec(buffer + prefix_size, s, &size);
    }
    if (0 == ret && candidates) {
        sprintf(buffer + size + prefix_size, "%s", candidate_buffer);
    }

    if (just_copy) {
        /* For JSON logging - just get a copy and do not log it */
        buffer_copy = slapi_ch_strdup(buffer);
    } else {
        /* Log it */
        int32_t log_format = config_get_accesslog_log_format();
        if (log_format != LOG_FORMAT_DEFAULT) {
            slapd_log_pblock logpb = {0};
            slapd_log_pblock_init(&logpb, log_format, pb);
            logpb.sort_str = buffer;
            slapd_log_access_sort(&logpb);
        } else {
            ldbm_log_access_message(pb, buffer);
        }
    }
    if (buffer != stack_buffer) {
        slapi_ch_free_string(&buffer);
    }
    return buffer_copy;
}

/* Fix for bug # 394184, SD, 20 Jul 00 */
/* replace the hard coded return value by the appropriate LDAP error code */
/* also removed an useless if (0 == return_value) {} statement */
/* Given a candidate list and a list of sort order specifications, sort this, or cop out */
/* Returns:  0 -- sorted OK                  now is: LDAP_SUCCESS (fix for bug #394184)
 *            -1 -- protocol error             now is: LDAP_PROTOCOL_ERROR
 *            -2 -- too hard to sort these     now is: LDAP_UNWILLING_TO_PERFORM
 *            -3 -- operation error            now is: LDAP_OPERATIONS_ERROR
 *            -4 -- timeout                    now is: LDAP_TIMELIMIT_EXCEEDED
 *            -5 -- admin limit exceeded       now is: LDAP_ADMINLIMIT_EXCEEDED
 *          -6 -- abandoned                  now is: LDAP_OTHER
 */
/*
 * So here's the plan:
 * Plan A:  We do a regular quicksort on the entries.
 * Plan B:  Through some hint given us from on high, we
 *            determine that the entries are _already_
 *            sorted as requested, thus we do nothing !
 * Plan C:  We determine that sorting these suckers is
 *            far too hard for us to even try, so we refuse.
 */
int
sort_candidates(backend *be, int lookthrough_limit, struct timespec *expire_time, Slapi_PBlock *pb, IDList *candidates, sort_spec_thing *s, char **sort_error_type)
{
    int return_value = LDAP_SUCCESS;
    baggage_carrier bc = {0};
    sort_spec_thing *this_s = NULL;

    /* We refuse to sort a non-existent IDlist */
    if (NULL == candidates) {
        return LDAP_UNWILLING_TO_PERFORM;
    }
    /* we refuse to sort a candidate list which is vast */
    if (ALLIDS(candidates)) {
        slapi_log_err(SLAPI_LOG_TRACE, "sort_candidates", "Asked to sort ALLIDS candidate list, refusing\n");
        return LDAP_UNWILLING_TO_PERFORM;
    }

    /* Iterate over the sort types */
    for (this_s = s; this_s; this_s = this_s->next) {
        if (NULL == this_s->matchrule) {
            return_value = attr_get_value_cmp_fn(&this_s->sattr, &(this_s->compare_fn));
            if (return_value != 0) {
                slapi_log_err(SLAPI_LOG_TRACE, "sort_candidates",
                              "Attempting to sort a non-ordered attribute (%s)\n", this_s->type);
                /* DBDB we should set the error type here */
                return_value = LDAP_UNWILLING_TO_PERFORM;
                *sort_error_type = this_s->type;
                return return_value;
            }
        } else {
            /* Need to---find the matching rule plugin,
             * tell it it needs to do ordering for this OID
             * see whether it agrees---if not signal error to client
             * Then later use it for generating ordering keys.
             * finally, free it up
             */
            return_value = create_matchrule_indexer(&this_s->mr_pb, this_s->matchrule, this_s->type);
            if (LDAP_SUCCESS != return_value) {
                *sort_error_type = this_s->type;
                return return_value;
            }
            this_s->compare_fn = slapi_berval_cmp;
        }
    }

    bc.be = be;
    bc.pb = pb;
    bc.expire_time = expire_time;
    bc.lookthrough_limit = lookthrough_limit;
    bc.check_counter = 1;

    return_value = slapd_qsort(&bc, candidates, s);
    slapi_log_err(SLAPI_LOG_TRACE, "Sorting done", "<=\n");

    return return_value;
}
/* End  fix for bug # 394184 */

static int
term_tag(ber_tag_t tag)
{
    return ((LBER_END_OF_SEQORSET == tag) || (LBER_ERROR == tag));
}

/* hacky function to convert a sort spec to a string
   you specify a buffer and a size. If the thing won't fit, it returns
   non-zero, and the size needed. Pass NULL buffer to just get the size */
static int
print_out_sort_spec(char *buffer, sort_spec *s, int *size)
{
    /* Walk down the list printing */
    sort_spec_thing *t = (sort_spec_thing *)s;
    sort_spec_thing *p = NULL;
    int buffer_size = 0;
    int input_size = 0;

    if (NULL != size) {
        input_size = *size;
    }
    do {
        p = t->next;

        buffer_size += strlen(t->type);
        if (t->order) {
            buffer_size += 1; /* For the '-' */
        }
        if (NULL != t->matchrule) {
            /* space for matchrule + semicolon */
            buffer_size += strlen(t->matchrule) + 1;
        }
        buffer_size += 1; /* for the space */
        if ((NULL != buffer) && (buffer_size <= input_size)) {
            /* write into the buffer */
            buffer += sprintf(buffer, "%s%s%s%s ",
                              t->order ? "-" : "",
                              t->type,
                              (NULL == t->matchrule) ? "" : ";",
                              (NULL == t->matchrule) ? "" : t->matchrule);
        }

        t = p;
    } while (p);
    if (NULL != size) {
        *size = buffer_size;
    }
    if (buffer_size <= input_size) {
        return 0;
    } else {
        return 1;
    }
}

int
parse_sort_spec(struct berval *sort_spec_ber, sort_spec **ps)
{
    /* So here we call ber_scanf to get the sort spec */
    /* This control looks like this :
     SortKeyList ::= SEQUENCE OF SEQUENCE {
                attributeType   AttributeType,
                orderingRule    [0] MatchingRuleId OPTIONAL,
                reverseOrder    [1] BOOLEAN DEFAULT FALSE }
    */
    BerElement *ber = NULL;
    sort_spec_thing *listhead = NULL;
    ber_tag_t tag = 0;
    ber_len_t len = -1;
    char *last = NULL;
    sort_spec_thing *listpointer = NULL;
    char *type = NULL;
    char *matchrule = NULL;
    int rc = LDAP_SUCCESS;

    if (!BV_HAS_DATA(sort_spec_ber)) {
        return LDAP_PROTOCOL_ERROR;
    }

    ber = ber_init(sort_spec_ber);
    if (ber == NULL) {
        return -1;
    }

    /* Work our way along the BER, one sort spec at a time */
    for (tag = ber_first_element(ber, &len, &last); !term_tag(tag); tag = ber_next_element(ber, &len, last)) {
        /* we're now pointing at the beginning of a sequence of type, matching rule and reverse indicator */

        char *inner_last = NULL;
        char *rtype = NULL;
        int reverse = 0;
        ber_tag_t next_tag = 0;
        sort_spec_thing *s = NULL;
        ber_tag_t return_value;

        len = -1; /* reset - not used here */
        next_tag = ber_first_element(ber, &len, &inner_last);

        /* The type is not optional */

        return_value = ber_scanf(ber, "a", &rtype);
        if (LBER_ERROR == return_value) {
            slapi_ch_free_string(&rtype);
            rc = LDAP_PROTOCOL_ERROR;
            goto err;
        }
        /* normalize */
        type = slapi_attr_syntax_normalize(rtype);
        slapi_ch_free_string(&rtype);

        /* Now look for the next tag. */

        len = -1; /* reset - not used here */
        next_tag = ber_next_element(ber, &len, inner_last);

        /* Are we done ? */
        if (!term_tag(next_tag)) {
            /* Is it the matching rule ? */
            if (LDAP_TAG_SK_MATCHRULE == next_tag) {
                /* If so, get it */
                return_value = ber_scanf(ber, "a", &matchrule);
                if (LBER_ERROR == return_value) {
                    rc = LDAP_PROTOCOL_ERROR;
                    goto err;
                }

                /* That can be followed by a reverse indicator */
                len = -1; /* reset - not used here */
                next_tag = ber_next_element(ber, &len, inner_last);
                if (LDAP_TAG_SK_REVERSE == next_tag) {
                    /* Get the reverse sort indicator here */
                    return_value = ber_scanf(ber, "b", &reverse);
                    /* The protocol police say--"You must have other than your default value" */
                    if ((LBER_ERROR == return_value) || (0 == reverse)) {
                        /* Protocol error */
                        rc = LDAP_PROTOCOL_ERROR;
                        goto err;
                    }
                } else {
                    /* Perhaps we're done now ? */
                    if ((LBER_END_OF_SEQORSET != next_tag) && (len != -1)) {
                        /* Protocol error---we got a matching rule, but followed by something other
                         * than reverse or end of sequence.
                         */
                        rc = LDAP_PROTOCOL_ERROR;
                        goto err;
                    }
                }
            } else {
                /* Is it the reverse indicator ? */
                if (LDAP_TAG_SK_REVERSE == next_tag) {
                    /* If so, get it */
                    return_value = ber_scanf(ber, "b", &reverse);
                    if (LBER_ERROR == return_value) {
                        rc = LDAP_PROTOCOL_ERROR;
                        goto err;
                    }
                } else {
                    /* Protocol error---tag which isn't either of the legal ones came first */
                    rc = LDAP_PROTOCOL_ERROR;
                    goto err;
                }
            }
        }

        s = sort_spec_thing_new(type, matchrule, reverse);
        if (NULL == s) {
            /* Memory allocation failed */
            rc = LDAP_OPERATIONS_ERROR;
            goto err;
        }
        type = matchrule = NULL;
        if (NULL != listpointer) {
            listpointer->next = s;
        }
        listpointer = s;
        if (NULL == listhead) {
            listhead = s;
        }
        len = -1; /* reset for next loop iter */
    }

    if (NULL == listhead) { /* LP - defect #559792 - don't return null listhead */
        *ps = NULL;
        rc = LDAP_PROTOCOL_ERROR;
        goto err;
    }

    /* the ber encoding is no longer needed */
    ber_free(ber, 1);

    *ps = (sort_spec *)listhead;


    return LDAP_SUCCESS;

err:
    if (listhead)
        sort_spec_free((sort_spec *)listhead);
    slapi_ch_free((void **)&type);
    slapi_ch_free((void **)&matchrule);
    ber_free(ber, 1);

    return rc;
}

struct berval *
attr_value_lowest(struct berval **values, value_compare_fn_type compare_fn)
{
    /* We iterate through the values, storing our last best guess as to the lowest */
    struct berval *lowest_so_far = values[0];
    struct berval *this_one = NULL;

    for (this_one = *values; this_one; this_one = *values++) {
        if (compare_fn(lowest_so_far, this_one) > 0) {
            lowest_so_far = this_one;
        }
    }
    return lowest_so_far;
}

int
sort_attr_compare(struct berval **value_a, struct berval **value_b, value_compare_fn_type compare_fn)
{
    /* So, the thing we need to do here is to look out for multi-valued
     * attributes. When we get one of those, we need to look through all the
     * values to find the lowest one (per X.511 edict). We then use that one to
     * compare against the other. We should really put some logic in here to
     * prevent us partying on an attribute with thousands of values for a long time.
     */
    struct berval *compare_value_a = NULL;
    struct berval *compare_value_b = NULL;

    compare_value_a = attr_value_lowest(value_a, compare_fn);
    compare_value_b = attr_value_lowest(value_b, compare_fn);

    return compare_fn(compare_value_a, compare_value_b);
}

/* Comparison routine, called by qsort.
 * The job here is to return the correct value
 * for the operation a < b
 * Returns:
 * <0 when  a < b
 * 0  when a == b
 * >0 when a > b
 */
static int
compare_entries_sv(ID *id_a, ID *id_b, sort_spec *s, baggage_carrier *bc, int *error)
{
    /* We get passed the IDs, but need to fetch the entries in order to
     * perform the comparison .
     */
    struct backentry *a = NULL;
    struct backentry *b = NULL;
    int result = 0;
    sort_spec_thing *this_one = NULL;
    backend *be = bc->be;
    ldbm_instance *inst = (ldbm_instance *)be->be_instance_info;
    int err;
    back_txn txn = {NULL};

    slapi_pblock_get(bc->pb, SLAPI_TXN, &txn.back_txn_txn);
    *error = 1;
    a = id2entry(be, *id_a, &txn, &err);
    if (NULL == a) {
        if (0 != err) {
            slapi_log_err(SLAPI_LOG_TRACE, "compare_entries_sv", "db err %d\n", err);
        }
        /* Were up a creek without paddle here */
        /* Best to log error and set some flag */
        return 0;
    }
    b = id2entry(be, *id_b, &txn, &err);
    if (NULL == b) {
        if (0 != err) {
            slapi_log_err(SLAPI_LOG_TRACE, "compare_entries_sv", "db err %d\n", err);
        }
        CACHE_RETURN(&inst->inst_cache, &a);
        return 0;
    }
    /* OK, now we have the entries, so we work our way down the attribute list comparing as we go */
    for (this_one = (sort_spec_thing *)s; this_one; this_one = this_one->next) {

        char *type = this_one->type;
        int order = this_one->order;
        Slapi_Attr *attr_a = NULL;
        Slapi_Attr *attr_b = NULL;
        struct berval **value_a = NULL;
        struct berval **value_b = NULL;

        /* Get the two attribute values from the entries */
        slapi_entry_attr_find(a->ep_entry, type, &attr_a);
        slapi_entry_attr_find(b->ep_entry, type, &attr_b);
        /* What do we do if one or more of the entries lacks this attribute ? */
        /* if one lacks the attribute */
        if (NULL == attr_a) {
            /* then if the other does too, they're equal */
            if (NULL == attr_b) {
                result = 0;
                continue;
            } else {
                /* If one has the attribute, and the other
                 * doesn't, the missing attribute is the
                 * LARGER one.  (bug #108154)  -robey
                 */
                result = 1;
                break;
            }
        }
        if (NULL == attr_b) {
            result = -1;
            break;
        }
        /* Somewhere in here, we need to go sideways for match rule case
         * we need to call the match rule plugin to get the attribute values
         * converted into ordering keys. Then we proceed as usual to use those,
         * but ensuring that we don't leak memory anywhere. This works as follows:
         * the code assumes that the attrs are references into the entry, so
         * doesn't try to free them. We need to note at the right place that
         * we're on the matchrule path, and accordingly free the keys---this turns out
         * to be when we free the indexer */
        if (NULL == this_one->matchrule) {
            /* Non-match rule case */
            valuearray_get_bervalarray(valueset_get_valuearray(&attr_a->a_present_values), &value_a);
            valuearray_get_bervalarray(valueset_get_valuearray(&attr_b->a_present_values), &value_b);
        } else {
            /* Match rule case */
            Slapi_Value **va_a = valueset_get_valuearray(&attr_a->a_present_values);
            Slapi_Value **va_b = valueset_get_valuearray(&attr_b->a_present_values);

            matchrule_values_to_keys(this_one->mr_pb, va_a, &value_a);
            /* Plugin owns the memory ==> duplicate the key before next call garble it */
            value_a = slapi_ch_bvecdup(value_a);
            matchrule_values_to_keys(this_one->mr_pb, va_b, &value_b);

            if ((va_a && !value_a) || (va_b && !value_b)) {
                result = 0;
                goto bail;
            }
        }
        /* Compare them */
        if (!order) {
            result = sort_attr_compare(value_a, value_b, this_one->compare_fn);
        } else {
            /* If reverse, invert the sense of the comparison */
            result = sort_attr_compare(value_b, value_a, this_one->compare_fn);
        }
        /* Time to free up the attributes allocated above */
        if (NULL != this_one->matchrule) {
            ber_bvecfree(value_a);
        } else {
            ber_bvecfree(value_a);
            ber_bvecfree(value_b);
        }
        /* Are they equal ? */
        if (0 != result) {
            /* If not, we're done */
            break;
        }
        /* If so, proceed to the next attribute for comparison */
    }
    *error = 0;
bail:
    CACHE_RETURN(&inst->inst_cache, &a);
    CACHE_RETURN(&inst->inst_cache, &b);
    return result;
}

/* Fix for bug # 394184, SD, 20 Jul 00 */
/* replace the hard coded return value by the appropriate LDAP error code */
/*
 * Returns:
 *  0: Everything OK          now is: LDAP_SUCCESS (fix for bug #394184)
 * -1: A protocol error       now is: LDAP_PROTOCOL_ERROR
 * -2: Too hard               now is: LDAP_UNWILLING_TO_PERFORM
 * -3: Operation error        now is: LDAP_OPERATIONS_ERROR
 * -4: Timeout                now is: LDAP_TIMELIMIT_EXCEEDED
 * -5: Admin limit exceeded   now is: LDAP_ADMINLIMIT_EXCEEDED
 * -6: Abandoned              now is: LDAP_OTHER
 */
static int
sort_check(baggage_carrier *bc)
{
    /* check for abandon */
    if (slapi_op_abandoned(bc->pb)) {
        return LDAP_OTHER;
    }

    /* Check to see if our journey is really necessary */

    if (0 == ((bc->check_counter)++ % CHECK_INTERVAL)) {

        if (slapi_timespec_expire_check(bc->expire_time) == TIMER_EXPIRED) {
            slapi_log_err(SLAPI_LOG_TRACE, "sort_check", "LDAP_TIMELIMIT_EXCEEDED\n");
            return LDAP_TIMELIMIT_EXCEEDED;
        }

        /* Fix for bugid #394184, SD, 05 Jul 00 */
        /*  not sure this is the appropriate place to do this;
           since the entries are swaped in slapd_qsort, some of them are most
           probably counted more than once */
        /* hence commenting out the following test and moving it into slapd_qsort */
        /* check lookthrough limit */
        /* if ( bc->lookthrough_limit != -1 && (bc->lookthrough_limit -= CHECK_INTERVAL) < 0 ) {
           return LDAP_ADMINLIMIT_EXCEEDED;
           } */
        /* end  for bugid  #394184 */
    }
    return LDAP_SUCCESS;
}
/* End fix for bug # 394184 */

/* prototypes for local routines */
static void shortsort(baggage_carrier *bc, ID *lo, ID *hi, sort_spec *s);
static void swap(ID *a, ID *b);

/* this parameter defines the cutoff between using quick sort and
   insertion sort for arrays; arrays with lengths shorter or equal to the
   below value use insertion sort */

#define CUTOFF 8 /* testing shows that this is good value */


/* Fix for bug # 394184, SD, 20 Jul 00 */
/* replace the hard coded return value by the appropriate LDAP error code */
/* Our qsort needs to police the client timeout and lookthrough limit ?
 * It knows how to compare entries, so we don't bother with all the void * stuff.
 */
/*
 * Returns:
 *  0: Everything OK         now is: LDAP_SUCCESS (fix for bug #394184)
 * -1: A protocol error      now is: LDAP_PROTOCOL_ERROR
 * -2: Too hard              now is: LDAP_UNWILLING_TO_PERFORM
 * -3: Operation error       now is: LDAP_OPERATIONS_ERROR
 * -4: Timeout               now is: LDAP_TIMELIMIT_EXCEEDED
 * -5: Admin limit exceeded  now is: LDAP_ADMINLIMIT_EXCEEDED
 * -6: Abandoned             now is: LDAP_OTHER
 */
static int
slapd_qsort(baggage_carrier *bc, IDList *list, sort_spec *s)
{
    ID *lo, *hi;       /* ends of sub-array currently sorting */
    ID *mid;           /* points to middle of subarray */
    ID *loguy, *higuy; /* traveling pointers for partition step */
    NIDS size;         /* size of the sub-array */
    ID *lostk[30], *histk[30];
    int stkptr; /* stack for saving sub-array to be processed */
    NIDS num = list->b_nids;
    int return_value = LDAP_SUCCESS;
    int error = 0;

    /* Note: the number of stack entries required is no more than
       1 + log2(size), so 30 is sufficient for any array */
    if (num < 2)
        return LDAP_SUCCESS; /* nothing to do */

    stkptr = 0; /* initialize stack */

    lo = &(list->b_ids[0]);
    hi = &(list->b_ids[num - 1]); /* initialize limits */

    /* Fix for bugid #394184, SD, 20 Jul 00 */
    if (bc->lookthrough_limit != -1 && (bc->lookthrough_limit <= (int)list->b_nids)) {
        return LDAP_ADMINLIMIT_EXCEEDED;
    }
/* end Fix for bugid #394184 */

/* this entry point is for pseudo-recursion calling: setting
       lo and hi and jumping to here is like recursion, but stkptr is
       prserved, locals aren't, so we preserve stuff on the stack */
recurse:

    size = (hi - lo) + 1; /* number of el's to sort */

    /* below a certain size, it is faster to use a O(n^2) sorting method */
    if (size <= CUTOFF) {
        shortsort(bc, lo, hi, s);
    } else {
        /* First we pick a partititioning element.  The efficiency of the
           algorithm demands that we find one that is approximately the
           median of the values, but also that we select one fast.  Using
           the first one produces bad performace if the array is already
           sorted, so we use the middle one, which would require a very
           wierdly arranged array for worst case performance.  Testing shows
           that a median-of-three algorithm does not, in general, increase
           performance. */

        mid = lo + (size / 2); /* find middle element */
        swap(mid, lo);         /* swap it to beginning of array */

        /* We now wish to partition the array into three pieces, one
           consisiting of elements <= partition element, one of elements
           equal to the parition element, and one of element >= to it.  This
           is done below; comments indicate conditions established at every
           step. */

        loguy = lo;
        higuy = hi + 1;

        /* Note that higuy decreases and loguy increases on every iteration,
           so loop must terminate. */
        for (;;) {
            /* lo <= loguy < hi, lo < higuy <= hi + 1,
               A[i] <= A[lo] for lo <= i <= loguy,
               A[i] >= A[lo] for higuy <= i <= hi */

            do {
                if (error)
                    return LDAP_OPERATIONS_ERROR;
                loguy++;
            } while (loguy <= hi && compare_entries_sv(loguy, lo, s, bc, &error) <= 0);

            /* lo < loguy <= hi+1, A[i] <= A[lo] for lo <= i < loguy,
               either loguy > hi or A[loguy] > A[lo] */

            do {
                if (error)
                    return LDAP_OPERATIONS_ERROR;
                higuy--;
            } while (higuy > lo && compare_entries_sv(higuy, lo, s, bc, &error) >= 0);

            /* lo-1 <= higuy <= hi, A[i] >= A[lo] for higuy < i <= hi,
               either higuy <= lo or A[higuy] < A[lo] */

            if (higuy < loguy)
                break;

            /* if loguy > hi or higuy <= lo, then we would have exited, so
               A[loguy] > A[lo], A[higuy] < A[lo],
               loguy < hi, highy > lo */

            swap(loguy, higuy);

            /* Check admin and time limits here on the sort */
            if (LDAP_SUCCESS != (return_value = sort_check(bc))) {
                return return_value;
            }

            /* A[loguy] < A[lo], A[higuy] > A[lo]; so condition at top
               of loop is re-established */
        }

        /*     A[i] >= A[lo] for higuy < i <= hi,
               A[i] <= A[lo] for lo <= i < loguy,
               higuy < loguy, lo <= higuy <= hi
           implying:
               A[i] >= A[lo] for loguy <= i <= hi,
               A[i] <= A[lo] for lo <= i <= higuy,
               A[i] = A[lo] for higuy < i < loguy */

        swap(lo, higuy); /* put partition element in place */

        /* OK, now we have the following:
              A[i] >= A[higuy] for loguy <= i <= hi,
              A[i] <= A[higuy] for lo <= i < higuy
              A[i] = A[lo] for higuy <= i < loguy    */

        /* We've finished the partition, now we want to sort the subarrays
           [lo, higuy-1] and [loguy, hi].
           We do the smaller one first to minimize stack usage.
           We only sort arrays of length 2 or more.*/

        if (higuy - 1 - lo >= hi - loguy) {
            if (lo + 1 < higuy) {
                lostk[stkptr] = lo;
                histk[stkptr] = higuy - 1;
                ++stkptr;
            } /* save big recursion for later */

            if (loguy < hi) {
                lo = loguy;
                goto recurse; /* do small recursion */
            }
        } else {
            if (loguy < hi) {
                lostk[stkptr] = loguy;
                histk[stkptr] = hi;
                ++stkptr; /* save big recursion for later */
            }

            if (lo + 1 < higuy) {
                hi = higuy - 1;
                goto recurse; /* do small recursion */
            }
        }
    }

    /* We have sorted the array, except for any pending sorts on the stack.
       Check if there are any, and do them. */

    --stkptr;
    if (stkptr >= 0) {
        lo = lostk[stkptr];
        hi = histk[stkptr];
        goto recurse; /* pop subarray from stack */
    } else
        return LDAP_SUCCESS; /* all subarrays done */
}
/* End  fix for bug # 394184 */


static void
shortsort(
    baggage_carrier *bc,
    ID *lo,
    ID *hi,
    sort_spec *s)
{
    ID *p, *max;
    int error = 0;

    /* Note: in assertions below, i and j are alway inside original bound of
       array to sort. */

    while (hi > lo) {
        /* A[i] <= A[j] for i <= j, j > hi */
        max = lo;
        for (p = lo + 1; p <= hi; p++) {
            /* A[i] <= A[max] for lo <= i < p */
            if (compare_entries_sv(p, max, s, bc, &error) > 0) {
                max = p;
            }
            /* A[i] <= A[max] for lo <= i <= p */
        }

        /* A[i] <= A[max] for lo <= i <= hi */

        swap(max, hi);

        /* A[i] <= A[hi] for i <= hi, so A[i] <= A[j] for i <= j, j >= hi */

        hi--;

        /* A[i] <= A[j] for i <= j, j > hi, loop top condition established */
    }
    /* A[i] <= A[j] for i <= j, j > lo, which implies A[i] <= A[j] for i < j,
       so array is sorted */
}

static void
swap(ID *a, ID *b)
{
    ID tmp;

    if (a != b) {
        tmp = *a;
        *a = *b;
        *b = tmp;
    }
}
