/* BLURB lgpl

                           Coda File System
                              Release 7

          Copyright (c) 1987-2019 Carnegie Mellon University
                  Additional copyrights listed below

This  code  is  distributed "AS IS" without warranty of any kind under
the  terms of the  GNU  Library General Public Licence  Version 2,  as
shown in the file LICENSE. The technical and financial contributors to
Coda are listed in the file CREDITS.

                        Additional copyrights
                           none currently

#*/

/*
*
*                   RVM Transaction & Range Functions
*
*/

#include <errno.h>
#include "rvm_private.h"

/* global variables */

extern log_t *default_log; /* default log descriptor ptr */
extern char *rvm_errmsg; /* internal error message buffer */
extern rvm_length_t rvm_optimizations; /* optimization switches */

/* local constants, macros */

#define X_RANGES_INCR 5 /* increment for x_ranges allocation */

/* special search function signature type */
typedef rvm_bool_t bool_func_t();

/* rvm_tid checker */
rvm_return_t bad_tid(rvm_tid_t *rvm_tid)
{
    if (rvm_tid == NULL)
        return RVM_ETID;

    if (rvm_tid->struct_id != rvm_tid_id)
        return RVM_ETID;

    return RVM_SUCCESS;
}

/* tid lookup and checker */
static int_tid_t *get_tid(rvm_tid_t *rvm_tid)
{
    int_tid_t *tid;

    /* sanity checks */
    if (bad_tid(rvm_tid) != RVM_SUCCESS)
        return NULL;
    if (rvm_tid->uname.tv_sec == 0)
        return NULL;
    tid = (int_tid_t *)rvm_tid->tid;
    if (tid == NULL)
        return NULL;
    if (tid->links.struct_id != int_tid_id)
        return NULL;

    /* begin tid_lock crit sec (normally ended by caller) */
    RW_CRITICAL(tid->tid_lock, w, {
        if (TIME_EQL(rvm_tid->uname, tid->uname) &&
            TIME_EQL_ZERO(tid->commit_stamp))
            return tid;
    });

    return NULL;
}

/* reallocate tid x_ranges vector if necessary */
static rvm_bool_t alloc_x_ranges(int_tid_t *tid, int len)
{
    if ((tid->x_ranges_len + len) > tid->x_ranges_alloc) {
        if (len <= X_RANGES_INCR)
            tid->x_ranges_alloc += X_RANGES_INCR;
        else
            tid->x_ranges_alloc += len;
        tid->x_ranges = (range_t **)REALLOC(
            tid->x_ranges, tid->x_ranges_alloc * sizeof(range_t *));
        if (tid->x_ranges == NULL)
            return rvm_false;
    }

    return rvm_true;
}

/* construct range descriptor and fill in region parameters */
static range_t *build_range(region_t *region, char *dest, rvm_length_t length)
{
    range_t *new_range; /* ptr for new range descriptor */

    /* create range descriptor and initialize */
    if ((new_range = make_range()) == NULL)
        return NULL;
    new_range->region      = region;
    new_range->nv.seg_code = region->seg->seg_code;

    /* set basic size fields */
    new_range->nv.length = length;
    new_range->nv.vmaddr = dest;

    /* calculate offsets */
    new_range->nv.offset = RVM_ADD_LENGTH_TO_OFFSET(
        region->offset, ((rvm_length_t)dest - (rvm_length_t)region->vmaddr));
    new_range->end_offset =
        RVM_ADD_LENGTH_TO_OFFSET(new_range->nv.offset, length);

    return new_range;
}

/* save old values for a range */
static char *save_ov(range_t *range)
{
    range->data_len = RANGE_LEN(range);
    range->data     = malloc(range->data_len);
    if (range->data != NULL)
        BCOPY(range->nv.vmaddr, range->data, range->nv.length);

    return range->data;
}

/* restore old values for a transaction */
static void restore_ov(int_tid_t *tid)
{
    range_t *range;

    /* ranges must be restored in reverse order of modification
       to exactly restore memory when optimizations are not done;
       order irrelevant if optimizing since ranges will be disjoint */
    UNLINK_REVERSE_NODES_OF(tid->range_tree, range_t, range)
    {
        assert(range->links.node.struct_id == range_id);

        /* restore old values if necessary */
        if (range->nv.length != 0) {
            if (TID(RESTORE_FLAG))
                BCOPY(range->data, range->nv.vmaddr, range->nv.length);

            /* decr uncommitted mods cnt */
            assert(range->region->links.struct_id == region_id);
            CRITICAL(range->region->count_lock, range->region->n_uncommit--);
        }

        free_range(range);
    }
}

/* range tree comparator function for chronological order insertion */
static long cmp_range_num(tree_node_t *node1, tree_node_t *node2)
{
    range_t *range1 = (range_t *)node1;
    range_t *range2 = (range_t *)node2;

    /* compare range numbers */
    if (range1->nv.range_num > range2->nv.range_num)
        return 1;
    if (range1->nv.range_num < range2->nv.range_num)
        return -1;
    return 0;
}

/* unconditionally add new range descriptor to tid's range tree
   used for non-optimized transactions only */
static rvm_return_t
add_new_range(int_tid_t *tid,
              range_t *new_range /* ptr for new range descriptor */)
{
    /* build old value record if necessary */
    assert(new_range->links.node.struct_id == range_id);
    if (new_range->nv.length != 0) {
        if (TID(RESTORE_FLAG))
            if (save_ov(new_range) == NULL) {
                free_range(new_range);
                return RVM_ENO_MEMORY;
            }

        /* count uncommitted mods */
        assert(new_range->region->links.struct_id == region_id);
        CRITICAL(new_range->region->count_lock,
                 new_range->region->n_uncommit++);
    }

    /* insert range to transaction's range tree in order of arrival */
    new_range->nv.range_num = tid->range_tree.n_nodes + 1;
    if (!tree_insert(&tid->range_tree, &new_range->links.node, cmp_range_num))
        assert(rvm_false);

    return RVM_SUCCESS;
}

/* range tree comparator for tree_insert by compound key:
   {region, segment displacement}
   adjacent nodes within region are considered equivalent */
static long region_partial_include(tree_node_t *node1, tree_node_t *node2)
{
    range_t *range1 = (range_t *)node1;
    range_t *range2 = (range_t *)node2;

    /* compare displacements if in same region */
    if (range1->region == range2->region) {
        /* compare displacement within segment */
        if (RVM_OFFSET_GTR(range1->nv.offset, range2->end_offset))
            return 1;
        if (RVM_OFFSET_LSS(range1->end_offset, range2->nv.offset))
            return -1;
        return 0;
    }

    /* compare region descriptor address */
    if (range1->region > range2->region)
        return 1;
    return -1;
}

/* range tree comparator for tree_insert by compound key:
   {segment, segment displacement}
   adjacent nodes within segment are considered equivalent */
static long segment_partial_include(tree_node_t *node1, tree_node_t *node2)
{
    range_t *range1 = (range_t *)node1;
    range_t *range2 = (range_t *)node2;

    /* compare displacements if in same segment */
    if (range1->nv.seg_code == range2->nv.seg_code) {
        /* compare displacement within segment */
        if (RVM_OFFSET_GTR(range1->nv.offset, range2->end_offset))
            return 1;
        if (RVM_OFFSET_LSS(range1->end_offset, range2->nv.offset))
            return -1;
        return 0;
    }

    /* compare segment codes */
    if (range1->nv.seg_code > range2->nv.seg_code)
        return 1;
    return -1;
}

/* detect and form list of overlapping or adjacent ranges */
static rvm_bool_t
find_overlap(int_tid_t *tid /* transaction descriptor to search */,
             range_t *new_range /* descriptor for new range */,
             cmp_func_t *cmp_func /* tree comparator function */,
             rvm_length_t *elim /* number of eliminated ranges */,
             rvm_offset_t *overlap /* length of eliminated ranges */,
             rvm_return_t *retval /* error code return */)
{
    range_t *range; /* ptr to existing range descriptor */
    rvm_offset_t off_tmp; /* arithmetic temporary */

    tid->x_ranges_len = 0;
    *retval           = RVM_SUCCESS;

    /* try to insert, iterate on collision */
    FROM_EXISTING_NODE_OF(tid->range_tree, range_t, range,
                          &new_range->links.node, *cmp_func)
    {
        /* see if out of region/segment or new_range */
        if ((*cmp_func)(&new_range->links.node, &range->links.node) != 0)
            break;

        /* save overlapping/adjacent ranges */
        if (!alloc_x_ranges(tid, 1)) {
            *retval = RVM_ENO_MEMORY;
            return rvm_true;
        }
        tid->x_ranges[tid->x_ranges_len++] = range;
        (*elim)++;

        /* special case: new range completly contained in existing range */
        if ((tid->x_ranges_len == 1) &&
            (RVM_OFFSET_GEQ(new_range->nv.offset, range->nv.offset) &&
             RVM_OFFSET_LEQ(new_range->end_offset, range->end_offset))) {
            *overlap = RVM_ADD_LENGTH_TO_OFFSET(*overlap, new_range->nv.length);
            return rvm_true;
        }
        /* general case: merge all overlapping/adjacent ranges
           tally savings and save new bounds of composite range */

        /* test if non-overlapping values precede new range */
        if (RVM_OFFSET_LSS(range->nv.offset, new_range->nv.offset)) {
            off_tmp  = RVM_SUB_OFFSETS(range->end_offset, new_range->nv.offset);
            *overlap = RVM_ADD_OFFSETS(*overlap, off_tmp);
            new_range->nv.offset = range->nv.offset;
        } else
            /* test if non-overlapping values follow new range */
            if (RVM_OFFSET_GTR(range->end_offset, new_range->end_offset)) {
            off_tmp  = RVM_SUB_OFFSETS(new_range->end_offset, range->nv.offset);
            *overlap = RVM_ADD_OFFSETS(*overlap, off_tmp);
            new_range->end_offset = range->end_offset;
        } else
            /* range completely contained in new range */
            *overlap = RVM_ADD_LENGTH_TO_OFFSET(*overlap, range->nv.length);

        /* update length of composite range */
        new_range->nv.length = RVM_OFFSET_TO_LENGTH(
            RVM_SUB_OFFSETS(new_range->end_offset, new_range->nv.offset));
    }

    return rvm_false;
}

/* merge new range with existing range(s) */
static rvm_return_t
merge_range(int_tid_t *tid, region_t *region,
            range_t *new_range /* ptr to new range descriptor */)
{
    range_t *range; /* ptr range descriptor */
    char *vmaddr; /* working vm address */
    char *data_addr; /* working ov buffer address */
    rvm_length_t len; /* length temp */
    int i;
    rvm_return_t retval;

    /* do simple insertion if not optimizing */
    if (!TID(RVM_COALESCE_RANGES))
        return add_new_range(tid, new_range);

    /* search for overlap/adjacency with existing ranges in same region */
    if (find_overlap(tid, new_range, region_partial_include, &tid->range_elim,
                     &tid->range_overlap, &retval)) {
        /* totally optimized away or error */
        free_range(new_range);
        return retval;
    }

    /* if new range was inserted, update region ref count and exit */
    if (tid->x_ranges_len == 0) {
        if (TID(RESTORE_FLAG))
            if (save_ov(new_range) == NULL) {
                if (!tree_delete(&tid->range_tree, &new_range->links.node,
                                 region_partial_include))
                    assert(rvm_false);
                free_range(new_range);
                return RVM_ENO_MEMORY;
            }
        CRITICAL(region->count_lock, region->n_uncommit++);
    } else {
        /* update vmaddr and allocate new old value buffer */
        range = tid->x_ranges[0];
        if (range->nv.vmaddr < new_range->nv.vmaddr)
            new_range->nv.vmaddr = range->nv.vmaddr;
        if (TID(RESTORE_FLAG)) {
            new_range->data_len = RANGE_LEN(new_range);
            new_range->data     = malloc(new_range->data_len);
            if (range->data == NULL)
                return RVM_ENO_MEMORY;
        }
        /* otherwise, merge existing old values into new node and put
           its data in 1st existing node in the tree; kill any others */
        vmaddr    = new_range->nv.vmaddr;
        data_addr = new_range->data;
        for (i = 0; i < tid->x_ranges_len; i++) {
            range = tid->x_ranges[i];
            if (TID(RESTORE_FLAG)) {
                /* copy old values preceding existing range to new buffer */
                if (vmaddr < range->nv.vmaddr) {
                    len = (rvm_length_t)RVM_SUB_LENGTH_FROM_ADDR(
                        range->nv.vmaddr, vmaddr);
                    BCOPY(vmaddr, data_addr, len);
                    vmaddr    = RVM_ADD_LENGTH_TO_ADDR(vmaddr, len);
                    data_addr = RVM_ADD_LENGTH_TO_ADDR(data_addr, len);
                }

                /* copy old values of existing range to new buffer */
                BCOPY(range->data, data_addr, range->nv.length);
                vmaddr    = RVM_ADD_LENGTH_TO_ADDR(vmaddr, range->nv.length);
                data_addr = RVM_ADD_LENGTH_TO_ADDR(data_addr, range->nv.length);

                /* copy trailing old values if necessary */
                if (i == (tid->x_ranges_len - 1)) {
                    len = (rvm_length_t)RVM_ADD_LENGTH_TO_ADDR(
                        new_range->nv.vmaddr, new_range->nv.length);
                    if ((char *)len > vmaddr) {
                        len =
                            (rvm_length_t)RVM_SUB_LENGTH_FROM_ADDR(len, vmaddr);
                        BCOPY(vmaddr, data_addr, len);
                        assert(RVM_ADD_LENGTH_TO_ADDR(vmaddr, len) ==
                               RVM_ADD_LENGTH_TO_ADDR(new_range->nv.vmaddr,
                                                      new_range->nv.length));
                    }
                }
            }

            /* kill replaced nodes in tree */
            if (i != 0) {
                if (!tree_delete(&tid->range_tree, &range->links.node,
                                 region_partial_include))
                    assert(rvm_false);
                free_range(range);
            }
        }
        /* update ov buffer, merged range size */
        range = tid->x_ranges[0];
        if (TID(RESTORE_FLAG)) {
            free(range->data);
            range->data     = new_range->data;
            range->data_len = new_range->data_len;
            new_range->data = NULL;
        }
        range->nv.vmaddr  = new_range->nv.vmaddr;
        range->nv.length  = new_range->nv.length;
        range->nv.offset  = new_range->nv.offset;
        range->end_offset = new_range->end_offset;
        free_range(new_range);

        /* update region uncommitted reference count */
        CRITICAL(region->count_lock,
                 region->n_uncommit -= (tid->x_ranges_len - 1));
    }

    return RVM_SUCCESS;
}

/* rvm_set_range */
rvm_return_t rvm_set_range(rvm_tid_t *rvm_tid /* transaction affected */,
                           void *dest /* base vm address of range */,
                           rvm_length_t length /* length of range */)
{
    int_tid_t *tid; /* internal tid ptr */
    region_t *region;
    range_t *new_range; /* ptr to new range descriptor */
    rvm_return_t retval = RVM_SUCCESS;

    /* basic entry checks */
    if (bad_init())
        return RVM_EINIT;

    /* ignore null ranges */
    if ((rvm_optimizations != 0) && (length == 0))
        return RVM_SUCCESS;

    /* lookup and lock tid */
    if ((tid = get_tid(rvm_tid)) == NULL) /* begin tid_lock critical section */
        return RVM_ETID;

    /* lookup and lock region */
    region =
        find_whole_range(dest, length, r); /* begin region_lock crit sect */
    if (region == NULL)
        retval = RVM_ENOT_MAPPED;
    else {
        /* build new range descriptor and do optimizations */
        new_range = build_range(region, dest, length);
        retval    = merge_range(tid, region, new_range);
        rw_unlock(&region->region_lock, r); /* end region_lock crit sect */
    }

    rw_unlock(&tid->tid_lock, w); /* end tid_lock critical section */
    return retval;
}

/* rvm_modify_bytes */
rvm_return_t rvm_modify_bytes(rvm_tid_t *rvm_tid /* transaction affected */,
                              void *dest /* base vm address of range */,
                              const void *src /* source of nv's */,
                              rvm_length_t length /* length of range */)
{
    rvm_return_t retval;

    /* call rvm_set_range to do most of the work */
    if ((retval = rvm_set_range(rvm_tid, dest, length)) != RVM_SUCCESS)
        return retval;

    /* must memmove, there is no guarantee that src and dest don't overlap */
    /*(void)memmove(dest,src,length); <-- not available on RT's */
    (void)BCOPY(src, dest, (int)length);

    return RVM_SUCCESS;
}

/* calculate transaction's new value log entry size */
static void nv_size(int_tid_t *tid /* transaction to size */,
                    rvm_offset_t *size /* compute length; double [out] */)
{
    range_t *range; /* current range */

    /* sum sizes of ranges */
    RVM_ZERO_OFFSET(*size);
    FOR_NODES_OF(tid->range_tree, range_t, range)
    *size = RVM_ADD_LENGTH_TO_OFFSET(*size, RANGE_SIZE(range));
}

/* compute total i/o size */
static rvm_return_t
nv_io_size(int_tid_t *tid /* transaction to size */,
           rvm_offset_t *size /* compute length; double [out] */)
{
    log_t *log = tid->log; /* log descriptor */

    /* compute length of log entries; with overhead,
       including possible wrap & split (too small to overflow) */
    nv_size(tid, size);
    *size = RVM_ADD_LENGTH_TO_OFFSET(*size, TRANS_SIZE + MIN_TRANS_SIZE);

    /* test max size */
    if (RVM_OFFSET_GTR(*size, log->status.log_size))
        return RVM_ETOO_BIG; /* bigger than log device */
    if (RVM_OFFSET_HIGH_BITS_TO_LENGTH(*size) != 0)
        return RVM_ETOO_BIG; /* 32 bit max */

    return RVM_SUCCESS;
}

/* save new values for a range */
static rvm_return_t save_nv(range_t *range /* modification range descriptor */)
{
    rvm_length_t range_len; /* save size of range */

    if (range->nv.length != 0) {
        range_len = RANGE_LEN(range);
        if (range->data == NULL) {
            range->data = calloc(1, range_len);
            if (range->data == NULL)
                return RVM_ENO_MEMORY;
            range->nvaddr   = range->data;
            range->data_len = range_len;
        }
        assert(range->data_len >= range_len);

        /* copy to old value space */
        src_aligned_bcopy(range->nv.vmaddr, range->data, range->nv.length);
    }

    return RVM_SUCCESS;
}

/* save all new values for no_flush commit */
static rvm_return_t save_all_nvs(int_tid_t *tid /* committing transaction */)
{
    range_t *range; /* modification range descriptor */
    rvm_return_t retval;

    /* cache nv's for future flush */
    FOR_NODES_OF(tid->range_tree, range_t, range)
    {
        /* allocate buffer and save new values for range */
        if ((retval = save_nv(range)) != RVM_SUCCESS)
            return retval;

        /* update uncommitted count */
        assert(range->region->links.struct_id == region_id);
        CRITICAL(range->region->count_lock, range->region->n_uncommit--);
    }

    return RVM_SUCCESS;
}

/* merge range into queued tid */
static rvm_return_t merge_tid(int_tid_t *q_tid /* ptr to last queued tid */,
                              int_tid_t *tid /* ptr to new tid */,
                              range_t *new_range /* new range ptr */)
{
    region_t *region = new_range->region;
    range_t *range; /* existing range ptr */
    rvm_length_t range_len; /* save size of range */
    char *nv_ptr; /* ptr into new value buffers */
    char *new_nv_ptr; /* 2nd ptr into new value buffers */
    rvm_offset_t old_offset; /* original offset of new range */
    rvm_length_t old_length; /* original length of new range */
    rvm_offset_t old_end_offset; /* original end offset of new range */
    rvm_length_t data_off; /* low order bytes of data offset */
    int i;
    rvm_return_t retval;

    /* search for overlap/adjacency with existing ranges in same segment */
    old_offset     = new_range->nv.offset;
    old_length     = new_range->nv.length;
    old_end_offset = new_range->end_offset;
    if (find_overlap(q_tid, new_range, segment_partial_include,
                     &tid->trans_elim, &tid->trans_overlap, &retval)) {
        if (retval != RVM_SUCCESS)
            return retval;

        /* new range totally contained in existing range, update new values */
        range  = q_tid->x_ranges[0];
        nv_ptr = (char *)RVM_OFFSET_TO_LENGTH(
            RVM_SUB_OFFSETS(old_offset, range->nv.offset));
        nv_ptr = RVM_ADD_LENGTH_TO_ADDR(
            nv_ptr,
            range->data + BYTE_SKEW(RVM_OFFSET_TO_LENGTH(range->nv.offset)));
        assert(range->data != NULL);
        assert((nv_ptr + old_length - range->data) <= range->data_len);
        BCOPY(new_range->nv.vmaddr, nv_ptr, old_length);
        goto exit;
    }
    /* see if simply inserted */
    if (q_tid->x_ranges_len == 0) {
        q_tid->log_size = /* update length of queued tid */
            RVM_ADD_LENGTH_TO_OFFSET(q_tid->log_size, RANGE_SIZE(new_range));
        /* save new values if no_flush commit */
        if (TID(FLUSH_FLAG)) {
            if (new_range->data != NULL) {
                free(new_range->data);
                new_range->data     = NULL;
                new_range->data_len = 0;
                new_range->nvaddr   = NULL;
            }
            return RVM_SUCCESS;
        }
        if ((retval = save_nv(new_range)) != RVM_SUCCESS)
            return retval;
        range = new_range;
        goto update;
    }

    /* see if existing totally contained in new and no other overlap */
    range = q_tid->x_ranges[0];
    if ((q_tid->x_ranges_len == 1) &&
        (RVM_OFFSET_GEQ(range->nv.offset, old_offset) &&
         RVM_OFFSET_LEQ(range->end_offset, old_end_offset))) {
        /* copy new range data */
        if (TID(FLUSH_FLAG)) {
            if (new_range->data != NULL) {
                free(new_range->data);
                new_range->data     = NULL;
                new_range->data_len = 0;
                new_range->nvaddr   = NULL;
            }
        } else if ((retval = save_nv(new_range)) != RVM_SUCCESS)
            return retval;
        goto replace;
    }

    /* do general merge: reallocate new value save buffer */
    if (new_range->data != NULL)
        free(new_range->data);
    data_off            = RVM_OFFSET_TO_LENGTH(new_range->nv.offset);
    new_range->data_len = ALIGNED_LEN(data_off, new_range->nv.length);
    if ((new_range->data = calloc(1, new_range->data_len)) == NULL)
        return RVM_ENO_MEMORY;
    new_range->nvaddr = new_range->data;

    /* save new values from new range */
    nv_ptr = RVM_ADD_LENGTH_TO_ADDR(new_range->data,
                                    RVM_OFFSET_TO_LENGTH(RVM_SUB_OFFSETS(
                                        old_offset, new_range->nv.offset)) +
                                        BYTE_SKEW(data_off));
    assert((nv_ptr + old_length - new_range->data) <= new_range->data_len);
    BCOPY(new_range->nv.vmaddr, nv_ptr, old_length);

    /* update vmaddr -- only valid if no remappings have been done
       since last commit; from now on, used only by rvmutl */
    if (range->nv.vmaddr < new_range->nv.vmaddr)
        new_range->nv.vmaddr = range->nv.vmaddr;

    /* merge new values from existing ranges
       copy leading bytes from 1st existing range */
    if (RVM_OFFSET_GTR(old_offset, range->nv.offset)) {
        range_len =
            RVM_OFFSET_TO_LENGTH(RVM_SUB_OFFSETS(old_offset, range->nv.offset));
        assert(range->data != NULL);
        nv_ptr = RVM_ADD_LENGTH_TO_ADDR(
            range->data, BYTE_SKEW(RVM_OFFSET_TO_LENGTH(range->nv.offset)));
        new_nv_ptr =
            RVM_ADD_LENGTH_TO_ADDR(new_range->data, BYTE_SKEW(data_off));
        assert((new_nv_ptr - new_range->data + range_len) <=
               new_range->data_len);
        BCOPY(nv_ptr, new_nv_ptr, range_len);
    }

    /* copy trailing bytes from last existing range */
    range = q_tid->x_ranges[q_tid->x_ranges_len - 1];
    if (RVM_OFFSET_GTR(range->end_offset, old_end_offset)) {
        range_len = RVM_OFFSET_TO_LENGTH(
            RVM_SUB_OFFSETS(range->end_offset, old_end_offset));
        assert(range->data != NULL);
        nv_ptr = RVM_ADD_LENGTH_TO_ADDR(
            range->data, BYTE_SKEW(RVM_OFFSET_TO_LENGTH(range->nv.offset)));
        nv_ptr     = RVM_ADD_LENGTH_TO_ADDR(nv_ptr, range->nv.length);
        nv_ptr     = RVM_SUB_LENGTH_FROM_ADDR(nv_ptr, range_len);
        new_nv_ptr = RVM_ADD_LENGTH_TO_ADDR(
            new_range->data, BYTE_SKEW(data_off) + new_range->nv.length);
        new_nv_ptr = RVM_SUB_LENGTH_FROM_ADDR(new_nv_ptr, range_len);
        assert((nv_ptr - range->data + range_len) <= range->data_len);
        assert((new_nv_ptr - new_range->data + range_len) <=
               new_range->data_len);
        BCOPY(nv_ptr, new_nv_ptr, range_len);
    }

    /* delete nodes now obsolete */
    for (i = 1; i < q_tid->x_ranges_len; i++) {
        range = q_tid->x_ranges[i];
        q_tid->log_size =
            RVM_SUB_LENGTH_FROM_OFFSET(q_tid->log_size, RANGE_SIZE(range));
        if (!tree_delete(&q_tid->range_tree, &range->links.node,
                         segment_partial_include))
            assert(rvm_false);
        free_range(range);
    }
    range = q_tid->x_ranges[0];

    /* replace existing range data with new range data*/
replace:
    q_tid->log_size =
        RVM_SUB_LENGTH_FROM_OFFSET(q_tid->log_size, RANGE_SIZE(range));
    q_tid->log_size =
        RVM_ADD_LENGTH_TO_OFFSET(q_tid->log_size, RANGE_SIZE(new_range));
    range->nv.vmaddr  = new_range->nv.vmaddr;
    range->nv.length  = new_range->nv.length;
    range->nv.offset  = new_range->nv.offset;
    range->end_offset = new_range->end_offset;
    free(range->data);
    range->data     = new_range->data;
    range->data_len = new_range->data_len;
    range->nvaddr   = new_range->nvaddr;
    new_range->data = NULL;

exit:
    free_range(new_range);
update: /* update region's uncommitted reference count */
    if (range->data != NULL)
        CRITICAL(region->count_lock, region->n_uncommit--);
    return RVM_SUCCESS;
}

/* merge ranges of transaction with previous transactions */
static rvm_return_t
coalesce_trans(int_tid_t *tid /* tid to log */,
               int_tid_t *q_tid /* ptr to last queued tid */)
{
    range_t *range; /* range ptr */
    rvm_return_t retval;

    /* merge ranges of new tid with queued tid */
    UNLINK_NODES_OF(tid->range_tree, range_t, range)
    {
        /* merge the range into queued tid */
        if ((retval = merge_tid(q_tid, tid, range)) != RVM_SUCCESS)
            return retval;
    }

    /* completely merged, update commit stamp and statistics
       scrap descriptor */
    q_tid->commit_stamp = tid->commit_stamp;
    q_tid->range_elim += tid->range_elim;
    q_tid->trans_elim += tid->trans_elim;
    q_tid->range_overlap =
        RVM_ADD_OFFSETS(q_tid->range_overlap, tid->range_overlap);
    q_tid->trans_overlap =
        RVM_ADD_OFFSETS(q_tid->trans_overlap, tid->trans_overlap);
    q_tid->n_coalesced++;
    free_tid(tid);

    return RVM_SUCCESS;
}

/* get address of queued tid that current tid can merge with */
static int_tid_t *get_queued_tid(int_tid_t *tid /* tid to log */)
{
    log_t *log = tid->log; /* log descriptor */
    int_tid_t *q_tid; /* ptr to last queued tid */
    rvm_offset_t size_temp;

    if (log->flush_list.list.length == 0)
        return NULL; /* no, force re-init of merge */

    /* get last queued tid in flush list */
    q_tid = (int_tid_t *)log->flush_list.preventry;

    /* see if can legitimately merge */
    if ((q_tid->flags & FLUSH_MARK) != 0 ||
        (q_tid->flags & RVM_COALESCE_TRANS) == 0)
        return NULL; /* no, force re-init of merge */

    size_temp = RVM_ADD_OFFSETS(q_tid->log_size, tid->log_size);
    if (RVM_OFFSET_GTR(size_temp, log->status.log_size))
        return NULL; /* no, force re-init of merge */

    return q_tid;
}

/* establish log entry for committing tid */
static rvm_return_t queue_tid(int_tid_t *tid /* tid to log */)
{
    log_t *log = tid->log; /* log descriptor */
    int_tid_t *q_tid; /* ptr to last queued tid */
    rvm_bool_t flush_flag;
    rvm_return_t retval;

    /* make sure transaction not too large for log */
    if ((retval = nv_io_size(tid, &tid->log_size)) != RVM_SUCCESS)
        return retval; /* transaction too big to log */

    if (init_unames() != 0) /* update uname generator */
        return RVM_EIO;
    flush_flag = (rvm_bool_t)TID(FLUSH_FLAG); /* save flush flag */

    /* queue tid for flush */
    CRITICAL(
        log->flush_list_lock,
        { /* begin flush_list_lock crit sec */
          make_uname(&tid->commit_stamp); /* record commit timestamp */
          /* test for transaction coalesce */
          if (TID(RVM_COALESCE_TRANS)) {
              /* see if must initialize coalescing */
              if ((q_tid = get_queued_tid(tid)) == NULL) {
                  if (flush_flag)
                      goto enqueue; /* nothing to coalesce! */

                  /* initialize transaction merger by inserting copy of tid
                   * with empty tree so that ranges get reordered by merge */
                  if ((q_tid = (int_tid_t *)alloc_list_entry(int_tid_id)) ==
                      NULL) {
                      retval = RVM_ENO_MEMORY;
                      goto exit;
                  }
                  BCOPY(tid, q_tid, sizeof(int_tid_t));
                  init_tree_root(&q_tid->range_tree);
                  RVM_ZERO_OFFSET(q_tid->log_size);
                  tid->x_ranges = NULL; /* array now owned by q_tid */
                  (void)move_list_entry(NULL, &log->flush_list, &q_tid->links);
              }

              /* merge ranges of tid with previously queued tid(s) */
              retval = coalesce_trans(tid, q_tid);
              goto exit;
          }
          /* save new values if necessary and queue */
          if (!flush_flag)
              if (tid->range_tree.n_nodes != 0)
                  if ((retval = save_all_nvs(tid)) != RVM_SUCCESS)
                      goto exit; /* too big for heap */

          /* enqueue new tid */
      enqueue:
          (void)move_list_entry(NULL, &log->flush_list, &tid->links);

      exit:;
        }); /* end flush_list_lock crit sec */

    if (retval != RVM_SUCCESS)
        return retval;

    /* flush log if commit requires */
    if (flush_flag)
        retval = flush_log(log, &log->status.n_flush);

    return retval;
}

/* rvm_begin_transaction */
rvm_return_t rvm_begin_transaction(rvm_tid_t *rvm_tid /* ptr to rvm_tid */,
                                   rvm_mode_t mode /* transaction's mode */)
{
    int_tid_t *tid; /* internal tid */
    log_t *log; /* log device  */
    rvm_return_t retval;

    /* basic entry checks */
    if (bad_init())
        return RVM_EINIT;
    if ((retval = bad_tid(rvm_tid)) != RVM_SUCCESS)
        return retval;
    if ((default_log == NULL) || (default_log->dev.handle == 0))
        return RVM_ELOG;
    if ((mode != restore) && (mode != no_restore))
        return RVM_EMODE;

    /* allocate tid descriptor */
    if ((tid = make_tid(mode)) == NULL)
        return RVM_ENO_MEMORY;
    rvm_tid->uname = tid->uname;

    /* queue the tid on log tid_list */
    log      = default_log; /* this must change if > 1 log */
    tid->log = log;
    CRITICAL(log->tid_list_lock, /* begin tid_list_lock critical section */
             {
                 (void)move_list_entry(NULL, &log->tid_list, &tid->links);
             }); /* end tid_list_lock critical section */
    rvm_tid->tid = tid;
    return RVM_SUCCESS;
}

/* rvm_abort_transaction */
rvm_return_t
rvm_abort_transaction(rvm_tid_t *rvm_tid /* ptr to transaction to abort */)
{
    int_tid_t *tid; /* internal tid */
    log_t *log; /* log descriptor ptr */

    /* basic entry checks */
    if (bad_init())
        return RVM_EINIT;
    if ((tid = get_tid(rvm_tid)) == NULL) /* begin tid_lock crit section */
        return RVM_ETID;

    log = tid->log;
    CRITICAL(log->tid_list_lock, /* unlink from log's tid_list */
             move_list_entry(&log->tid_list, NULL, &tid->links));
    tid->commit_stamp.tv_sec = 1; /* temporary mark */
    rw_unlock(&tid->tid_lock, w); /* end tid_lock critical section */

    /* restore virtual memory */
    restore_ov(tid);

    CRITICAL(log->dev_lock, {
        log->status.n_abort++; /* count transactions aborted */
    });

    rvm_tid->tid = NULL;
    free_tid(tid); /* free transaction descriptor */
    return RVM_SUCCESS;
}

/* rvm_end_transaction */
rvm_return_t
rvm_end_transaction(rvm_tid_t *rvm_tid /* ptr to transaction to commit */,
                    rvm_mode_t mode /* end mode */)
{
    int_tid_t *tid; /* internal tid */
    log_t *log; /* log descriptor ptr */
    rvm_return_t retval;

    /* basic entry checks */
    if (bad_init())
        return RVM_EINIT;
    if ((mode != flush) && (mode != no_flush))
        return RVM_EMODE;
    if ((tid = get_tid(rvm_tid)) == NULL) /* begin tid_lock crit section */
        return RVM_ETID;

    /* remove tid from log's tid_list */
    log = tid->log;
    CRITICAL(log->dev_lock,
             { /* begin dev_lock crit section */
               CRITICAL(log->tid_list_lock, /* unlink tid from log's tid_list*/
                        move_list_entry(&log->tid_list, NULL, &tid->links));
               if (mode == flush) /* record flush mode and count */
               {
                   tid->flags |= FLUSH_FLAG;
                   log->status.n_flush_commit++;
               } else
                   log->status.n_no_flush_commit++;
             }); /* end tid_list_lock crit section */
    tid->commit_stamp.tv_sec = 1; /* temporary mark */
    rw_unlock(&tid->tid_lock, w); /* end tid_lock crit section */

    /* kill null tids */
    if ((rvm_optimizations != 0) && (tid->range_tree.n_nodes == 0)) {
        rvm_tid->tid = NULL;
        free_tid(tid);
        return RVM_SUCCESS;
    }

    /* build new value record(s) & flush if necessary */
    if ((retval = queue_tid(tid)) != RVM_SUCCESS)
        ZERO_TIME(tid->commit_stamp);
    else
        rvm_tid->tid = NULL;
    return retval;
}
