diff options
Diffstat (limited to 'subversion/libsvn_delta/text_delta.c')
-rw-r--r-- | subversion/libsvn_delta/text_delta.c | 1041 |
1 files changed, 1041 insertions, 0 deletions
diff --git a/subversion/libsvn_delta/text_delta.c b/subversion/libsvn_delta/text_delta.c new file mode 100644 index 0000000..be2c434 --- /dev/null +++ b/subversion/libsvn_delta/text_delta.c @@ -0,0 +1,1041 @@ +/* + * text-delta.c -- Internal text delta representation + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + +#include <assert.h> +#include <string.h> + +#include <apr_general.h> /* for APR_INLINE */ +#include <apr_md5.h> /* for, um...MD5 stuff */ + +#include "svn_delta.h" +#include "svn_io.h" +#include "svn_pools.h" +#include "svn_checksum.h" + +#include "delta.h" + + +/* Text delta stream descriptor. */ + +struct svn_txdelta_stream_t { + /* Copied from parameters to svn_txdelta_stream_create. */ + void *baton; + svn_txdelta_next_window_fn_t next_window; + svn_txdelta_md5_digest_fn_t md5_digest; +}; + +/* Delta stream baton. */ +struct txdelta_baton { + /* These are copied from parameters passed to svn_txdelta. */ + svn_stream_t *source; + svn_stream_t *target; + + /* Private data */ + svn_boolean_t more_source; /* FALSE if source stream hit EOF. */ + svn_boolean_t more; /* TRUE if there are more data in the pool. */ + svn_filesize_t pos; /* Offset of next read in source file. */ + char *buf; /* Buffer for input data. */ + + svn_checksum_ctx_t *context; /* If not NULL, the context for computing + the checksum. */ + svn_checksum_t *checksum; /* If non-NULL, the checksum of TARGET. */ + + apr_pool_t *result_pool; /* For results (e.g. checksum) */ +}; + + +/* Target-push stream descriptor. */ + +struct tpush_baton { + /* These are copied from parameters passed to svn_txdelta_target_push. */ + svn_stream_t *source; + svn_txdelta_window_handler_t wh; + void *whb; + apr_pool_t *pool; + + /* Private data */ + char *buf; + svn_filesize_t source_offset; + apr_size_t source_len; + svn_boolean_t source_done; + apr_size_t target_len; +}; + + +/* Text delta applicator. */ + +struct apply_baton { + /* These are copied from parameters passed to svn_txdelta_apply. */ + svn_stream_t *source; + svn_stream_t *target; + + /* Private data. Between calls, SBUF contains the data from the + * last window's source view, as specified by SBUF_OFFSET and + * SBUF_LEN. The contents of TBUF are not interesting between + * calls. */ + apr_pool_t *pool; /* Pool to allocate data from */ + char *sbuf; /* Source buffer */ + apr_size_t sbuf_size; /* Allocated source buffer space */ + svn_filesize_t sbuf_offset; /* Offset of SBUF data in source stream */ + apr_size_t sbuf_len; /* Length of SBUF data */ + char *tbuf; /* Target buffer */ + apr_size_t tbuf_size; /* Allocated target buffer space */ + + apr_md5_ctx_t md5_context; /* Leads to result_digest below. */ + unsigned char *result_digest; /* MD5 digest of resultant fulltext; + must point to at least APR_MD5_DIGESTSIZE + bytes of storage. */ + + const char *error_info; /* Optional extra info for error returns. */ +}; + + + +svn_txdelta_window_t * +svn_txdelta__make_window(const svn_txdelta__ops_baton_t *build_baton, + apr_pool_t *pool) +{ + svn_txdelta_window_t *window; + svn_string_t *new_data = apr_palloc(pool, sizeof(*new_data)); + + window = apr_palloc(pool, sizeof(*window)); + window->sview_offset = 0; + window->sview_len = 0; + window->tview_len = 0; + + window->num_ops = build_baton->num_ops; + window->src_ops = build_baton->src_ops; + window->ops = build_baton->ops; + + /* just copy the fields over, rather than alloc/copying into a whole new + svn_string_t structure. */ + /* ### would be much nicer if window->new_data were not a ptr... */ + new_data->data = build_baton->new_data->data; + new_data->len = build_baton->new_data->len; + window->new_data = new_data; + + return window; +} + + +/* Compute and return a delta window using the xdelta algorithm on + DATA, which contains SOURCE_LEN bytes of source data and TARGET_LEN + bytes of target data. SOURCE_OFFSET gives the offset of the source + data, and is simply copied into the window's sview_offset field. */ +static svn_txdelta_window_t * +compute_window(const char *data, apr_size_t source_len, apr_size_t target_len, + svn_filesize_t source_offset, apr_pool_t *pool) +{ + svn_txdelta__ops_baton_t build_baton = { 0 }; + svn_txdelta_window_t *window; + + /* Compute the delta operations. */ + build_baton.new_data = svn_stringbuf_create_empty(pool); + + if (source_len == 0) + svn_txdelta__insert_op(&build_baton, svn_txdelta_new, 0, target_len, data, + pool); + else + svn_txdelta__xdelta(&build_baton, data, source_len, target_len, pool); + + /* Create and return the delta window. */ + window = svn_txdelta__make_window(&build_baton, pool); + window->sview_offset = source_offset; + window->sview_len = source_len; + window->tview_len = target_len; + return window; +} + + + +svn_txdelta_window_t * +svn_txdelta_window_dup(const svn_txdelta_window_t *window, + apr_pool_t *pool) +{ + svn_txdelta__ops_baton_t build_baton = { 0 }; + svn_txdelta_window_t *new_window; + const apr_size_t ops_size = (window->num_ops * sizeof(*build_baton.ops)); + + build_baton.num_ops = window->num_ops; + build_baton.src_ops = window->src_ops; + build_baton.ops_size = window->num_ops; + build_baton.ops = apr_palloc(pool, ops_size); + memcpy(build_baton.ops, window->ops, ops_size); + build_baton.new_data = + svn_stringbuf_create_from_string(window->new_data, pool); + + new_window = svn_txdelta__make_window(&build_baton, pool); + new_window->sview_offset = window->sview_offset; + new_window->sview_len = window->sview_len; + new_window->tview_len = window->tview_len; + return new_window; +} + +/* This is a private interlibrary compatibility wrapper. */ +svn_txdelta_window_t * +svn_txdelta__copy_window(const svn_txdelta_window_t *window, + apr_pool_t *pool); +svn_txdelta_window_t * +svn_txdelta__copy_window(const svn_txdelta_window_t *window, + apr_pool_t *pool) +{ + return svn_txdelta_window_dup(window, pool); +} + + +/* Insert a delta op into a delta window. */ + +void +svn_txdelta__insert_op(svn_txdelta__ops_baton_t *build_baton, + enum svn_delta_action opcode, + apr_size_t offset, + apr_size_t length, + const char *new_data, + apr_pool_t *pool) +{ + svn_txdelta_op_t *op; + + /* Check if this op can be merged with the previous op. The delta + combiner sometimes generates such ops, and this is the obvious + place to make the check. */ + if (build_baton->num_ops > 0) + { + op = &build_baton->ops[build_baton->num_ops - 1]; + if (op->action_code == opcode + && (opcode == svn_txdelta_new + || op->offset + op->length == offset)) + { + op->length += length; + if (opcode == svn_txdelta_new) + svn_stringbuf_appendbytes(build_baton->new_data, + new_data, length); + return; + } + } + + /* Create space for the new op. */ + if (build_baton->num_ops == build_baton->ops_size) + { + svn_txdelta_op_t *const old_ops = build_baton->ops; + int const new_ops_size = (build_baton->ops_size == 0 + ? 16 : 2 * build_baton->ops_size); + build_baton->ops = + apr_palloc(pool, new_ops_size * sizeof(*build_baton->ops)); + + /* Copy any existing ops into the new array */ + if (old_ops) + memcpy(build_baton->ops, old_ops, + build_baton->ops_size * sizeof(*build_baton->ops)); + build_baton->ops_size = new_ops_size; + } + + /* Insert the op. svn_delta_source and svn_delta_target are + just inserted. For svn_delta_new, the new data must be + copied into the window. */ + op = &build_baton->ops[build_baton->num_ops]; + switch (opcode) + { + case svn_txdelta_source: + ++build_baton->src_ops; + /*** FALLTHRU ***/ + case svn_txdelta_target: + op->action_code = opcode; + op->offset = offset; + op->length = length; + break; + case svn_txdelta_new: + op->action_code = opcode; + op->offset = build_baton->new_data->len; + op->length = length; + svn_stringbuf_appendbytes(build_baton->new_data, new_data, length); + break; + default: + assert(!"unknown delta op."); + } + + ++build_baton->num_ops; +} + +apr_size_t +svn_txdelta__remove_copy(svn_txdelta__ops_baton_t *build_baton, + apr_size_t max_len) +{ + svn_txdelta_op_t *op; + apr_size_t len = 0; + + /* remove ops back to front */ + while (build_baton->num_ops > 0) + { + op = &build_baton->ops[build_baton->num_ops-1]; + + /* we can't modify svn_txdelta_target ops -> stop there */ + if (op->action_code == svn_txdelta_target) + break; + + /* handle the case that we cannot remove the op entirely */ + if (op->length + len > max_len) + { + /* truncate only insertions. Copies don't benefit + from being truncated. */ + if (op->action_code == svn_txdelta_new) + { + build_baton->new_data->len -= max_len - len; + op->length -= max_len - len; + len = max_len; + } + + break; + } + + /* drop the op entirely */ + if (op->action_code == svn_txdelta_new) + build_baton->new_data->len -= op->length; + + len += op->length; + --build_baton->num_ops; + } + + return len; +} + + + +/* Generic delta stream functions. */ + +svn_txdelta_stream_t * +svn_txdelta_stream_create(void *baton, + svn_txdelta_next_window_fn_t next_window, + svn_txdelta_md5_digest_fn_t md5_digest, + apr_pool_t *pool) +{ + svn_txdelta_stream_t *stream = apr_palloc(pool, sizeof(*stream)); + + stream->baton = baton; + stream->next_window = next_window; + stream->md5_digest = md5_digest; + + return stream; +} + +svn_error_t * +svn_txdelta_next_window(svn_txdelta_window_t **window, + svn_txdelta_stream_t *stream, + apr_pool_t *pool) +{ + return stream->next_window(window, stream->baton, pool); +} + +const unsigned char * +svn_txdelta_md5_digest(svn_txdelta_stream_t *stream) +{ + return stream->md5_digest(stream->baton); +} + + + +static svn_error_t * +txdelta_next_window(svn_txdelta_window_t **window, + void *baton, + apr_pool_t *pool) +{ + struct txdelta_baton *b = baton; + apr_size_t source_len = SVN_DELTA_WINDOW_SIZE; + apr_size_t target_len = SVN_DELTA_WINDOW_SIZE; + + /* Read the source stream. */ + if (b->more_source) + { + SVN_ERR(svn_stream_read(b->source, b->buf, &source_len)); + b->more_source = (source_len == SVN_DELTA_WINDOW_SIZE); + } + else + source_len = 0; + + /* Read the target stream. */ + SVN_ERR(svn_stream_read(b->target, b->buf + source_len, &target_len)); + b->pos += source_len; + + if (target_len == 0) + { + /* No target data? We're done; return the final window. */ + if (b->context != NULL) + SVN_ERR(svn_checksum_final(&b->checksum, b->context, b->result_pool)); + + *window = NULL; + b->more = FALSE; + return SVN_NO_ERROR; + } + else if (b->context != NULL) + SVN_ERR(svn_checksum_update(b->context, b->buf + source_len, target_len)); + + *window = compute_window(b->buf, source_len, target_len, + b->pos - source_len, pool); + + /* That's it. */ + return SVN_NO_ERROR; +} + + +static const unsigned char * +txdelta_md5_digest(void *baton) +{ + struct txdelta_baton *b = baton; + /* If there are more windows for this stream, the digest has not yet + been calculated. */ + if (b->more) + return NULL; + + /* If checksumming has not been activated, there will be no digest. */ + if (b->context == NULL) + return NULL; + + /* The checksum should be there. */ + return b->checksum->digest; +} + + +svn_error_t * +svn_txdelta_run(svn_stream_t *source, + svn_stream_t *target, + svn_txdelta_window_handler_t handler, + void *handler_baton, + svn_checksum_kind_t checksum_kind, + svn_checksum_t **checksum, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + struct txdelta_baton tb = { 0 }; + svn_txdelta_window_t *window; + + tb.source = source; + tb.target = target; + tb.more_source = TRUE; + tb.more = TRUE; + tb.pos = 0; + tb.buf = apr_palloc(scratch_pool, 2 * SVN_DELTA_WINDOW_SIZE); + tb.result_pool = result_pool; + + if (checksum != NULL) + tb.context = svn_checksum_ctx_create(checksum_kind, scratch_pool); + + do + { + /* free the window (if any) */ + svn_pool_clear(iterpool); + + /* read in a single delta window */ + SVN_ERR(txdelta_next_window(&window, &tb, iterpool)); + + /* shove it at the handler */ + SVN_ERR((*handler)(window, handler_baton)); + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + } + while (window != NULL); + + svn_pool_destroy(iterpool); + + if (checksum != NULL) + *checksum = tb.checksum; /* should be there! */ + + return SVN_NO_ERROR; +} + + +void +svn_txdelta2(svn_txdelta_stream_t **stream, + svn_stream_t *source, + svn_stream_t *target, + svn_boolean_t calculate_checksum, + apr_pool_t *pool) +{ + struct txdelta_baton *b = apr_pcalloc(pool, sizeof(*b)); + + b->source = source; + b->target = target; + b->more_source = TRUE; + b->more = TRUE; + b->buf = apr_palloc(pool, 2 * SVN_DELTA_WINDOW_SIZE); + b->context = calculate_checksum + ? svn_checksum_ctx_create(svn_checksum_md5, pool) + : NULL; + b->result_pool = pool; + + *stream = svn_txdelta_stream_create(b, txdelta_next_window, + txdelta_md5_digest, pool); +} + +void +svn_txdelta(svn_txdelta_stream_t **stream, + svn_stream_t *source, + svn_stream_t *target, + apr_pool_t *pool) +{ + svn_txdelta2(stream, source, target, TRUE, pool); +} + + + +/* Functions for implementing a "target push" delta. */ + +/* This is the write handler for a target-push delta stream. It reads + * source data, buffers target data, and fires off delta windows when + * the target data buffer is full. */ +static svn_error_t * +tpush_write_handler(void *baton, const char *data, apr_size_t *len) +{ + struct tpush_baton *tb = baton; + apr_size_t chunk_len, data_len = *len; + apr_pool_t *pool = svn_pool_create(tb->pool); + svn_txdelta_window_t *window; + + while (data_len > 0) + { + svn_pool_clear(pool); + + /* Make sure we're all full up on source data, if possible. */ + if (tb->source_len == 0 && !tb->source_done) + { + tb->source_len = SVN_DELTA_WINDOW_SIZE; + SVN_ERR(svn_stream_read(tb->source, tb->buf, &tb->source_len)); + if (tb->source_len < SVN_DELTA_WINDOW_SIZE) + tb->source_done = TRUE; + } + + /* Copy in the target data, up to SVN_DELTA_WINDOW_SIZE. */ + chunk_len = SVN_DELTA_WINDOW_SIZE - tb->target_len; + if (chunk_len > data_len) + chunk_len = data_len; + memcpy(tb->buf + tb->source_len + tb->target_len, data, chunk_len); + data += chunk_len; + data_len -= chunk_len; + tb->target_len += chunk_len; + + /* If we're full of target data, compute and fire off a window. */ + if (tb->target_len == SVN_DELTA_WINDOW_SIZE) + { + window = compute_window(tb->buf, tb->source_len, tb->target_len, + tb->source_offset, pool); + SVN_ERR(tb->wh(window, tb->whb)); + tb->source_offset += tb->source_len; + tb->source_len = 0; + tb->target_len = 0; + } + } + + svn_pool_destroy(pool); + return SVN_NO_ERROR; +} + + +/* This is the close handler for a target-push delta stream. It sends + * a final window if there is any buffered target data, and then sends + * a NULL window signifying the end of the window stream. */ +static svn_error_t * +tpush_close_handler(void *baton) +{ + struct tpush_baton *tb = baton; + svn_txdelta_window_t *window; + + /* Send a final window if we have any residual target data. */ + if (tb->target_len > 0) + { + window = compute_window(tb->buf, tb->source_len, tb->target_len, + tb->source_offset, tb->pool); + SVN_ERR(tb->wh(window, tb->whb)); + } + + /* Send a final NULL window signifying the end. */ + return tb->wh(NULL, tb->whb); +} + + +svn_stream_t * +svn_txdelta_target_push(svn_txdelta_window_handler_t handler, + void *handler_baton, svn_stream_t *source, + apr_pool_t *pool) +{ + struct tpush_baton *tb; + svn_stream_t *stream; + + /* Initialize baton. */ + tb = apr_palloc(pool, sizeof(*tb)); + tb->source = source; + tb->wh = handler; + tb->whb = handler_baton; + tb->pool = pool; + tb->buf = apr_palloc(pool, 2 * SVN_DELTA_WINDOW_SIZE); + tb->source_offset = 0; + tb->source_len = 0; + tb->source_done = FALSE; + tb->target_len = 0; + + /* Create and return writable stream. */ + stream = svn_stream_create(tb, pool); + svn_stream_set_write(stream, tpush_write_handler); + svn_stream_set_close(stream, tpush_close_handler); + return stream; +} + + + +/* Functions for applying deltas. */ + +/* Ensure that BUF has enough space for VIEW_LEN bytes. */ +static APR_INLINE svn_error_t * +size_buffer(char **buf, apr_size_t *buf_size, + apr_size_t view_len, apr_pool_t *pool) +{ + if (view_len > *buf_size) + { + *buf_size *= 2; + if (*buf_size < view_len) + *buf_size = view_len; + SVN_ERR_ASSERT(APR_ALIGN_DEFAULT(*buf_size) >= *buf_size); + *buf = apr_palloc(pool, *buf_size); + } + + return SVN_NO_ERROR; +} + +/* Copy LEN bytes from SOURCE to TARGET, optimizing for the case where LEN + * is often very small. Return a pointer to the first byte after the copied + * target range, unlike standard memcpy(), as a potential further + * optimization for the caller. + * + * memcpy() is hard to tune for a wide range of buffer lengths. Therefore, + * it is often tuned for high throughput on large buffers and relatively + * low latency for mid-sized buffers (tens of bytes). However, the overhead + * for very small buffers (<10 bytes) is still high. Even passing the + * parameters, for instance, may take as long as copying 3 bytes. + * + * Because short copy sequences seem to be a common case, at least in + * "format 2" FSFS repositories, we copy them directly. Larger buffer sizes + * aren't hurt measurably by the exta 'if' clause. */ +static APR_INLINE char * +fast_memcpy(char *target, const char *source, apr_size_t len) +{ + if (len > 7) + { + memcpy(target, source, len); + target += len; + } + else + { + /* memcpy is not exactly fast for small block sizes. + * Since they are common, let's run optimized code for them. */ + const char *end = source + len; + for (; source != end; source++) + *(target++) = *source; + } + + return target; +} + +/* Copy LEN bytes from SOURCE to TARGET. Unlike memmove() or memcpy(), + * create repeating patterns if the source and target ranges overlap. + * Return a pointer to the first byte after the copied target range. */ +static APR_INLINE char * +patterning_copy(char *target, const char *source, apr_size_t len) +{ + const char *end = source + len; + + /* On many machines, we can do "chunky" copies. */ + +#if SVN_UNALIGNED_ACCESS_IS_OK + + if (end + sizeof(apr_uint32_t) <= target) + { + /* Source and target are at least 4 bytes apart, so we can copy in + * 4-byte chunks. */ + for (; source + sizeof(apr_uint32_t) <= end; + source += sizeof(apr_uint32_t), + target += sizeof(apr_uint32_t)) + *(apr_uint32_t *)(target) = *(apr_uint32_t *)(source); + } + +#endif + + /* fall through to byte-wise copy (either for the below-chunk-size tail + * or the whole copy) */ + for (; source != end; source++) + *(target++) = *source; + + return target; +} + +void +svn_txdelta_apply_instructions(svn_txdelta_window_t *window, + const char *sbuf, char *tbuf, + apr_size_t *tlen) +{ + const svn_txdelta_op_t *op; + apr_size_t tpos = 0; + + for (op = window->ops; op < window->ops + window->num_ops; op++) + { + const apr_size_t buf_len = (op->length < *tlen - tpos + ? op->length : *tlen - tpos); + + /* Check some invariants common to all instructions. */ + assert(tpos + op->length <= window->tview_len); + + switch (op->action_code) + { + case svn_txdelta_source: + /* Copy from source area. */ + assert(sbuf); + assert(op->offset + op->length <= window->sview_len); + fast_memcpy(tbuf + tpos, sbuf + op->offset, buf_len); + break; + + case svn_txdelta_target: + /* Copy from target area. We can't use memcpy() or the like + * since we need a specific semantics for overlapping copies: + * they must result in repeating patterns. + * Note that most copies won't have overlapping source and + * target ranges (they are just a result of self-compressed + * data) but a small percentage will. */ + assert(op->offset < tpos); + patterning_copy(tbuf + tpos, tbuf + op->offset, buf_len); + break; + + case svn_txdelta_new: + /* Copy from window new area. */ + assert(op->offset + op->length <= window->new_data->len); + fast_memcpy(tbuf + tpos, + window->new_data->data + op->offset, + buf_len); + break; + + default: + assert(!"Invalid delta instruction code"); + } + + tpos += op->length; + if (tpos >= *tlen) + return; /* The buffer is full. */ + } + + /* Check that we produced the right amount of data. */ + assert(tpos == window->tview_len); + *tlen = tpos; +} + +/* This is a private interlibrary compatibility wrapper. */ +void +svn_txdelta__apply_instructions(svn_txdelta_window_t *window, + const char *sbuf, char *tbuf, + apr_size_t *tlen); +void +svn_txdelta__apply_instructions(svn_txdelta_window_t *window, + const char *sbuf, char *tbuf, + apr_size_t *tlen) +{ + svn_txdelta_apply_instructions(window, sbuf, tbuf, tlen); +} + + +/* Apply WINDOW to the streams given by APPL. */ +static svn_error_t * +apply_window(svn_txdelta_window_t *window, void *baton) +{ + struct apply_baton *ab = (struct apply_baton *) baton; + apr_size_t len; + svn_error_t *err; + + if (window == NULL) + { + /* We're done; just clean up. */ + if (ab->result_digest) + apr_md5_final(ab->result_digest, &(ab->md5_context)); + + err = svn_stream_close(ab->target); + svn_pool_destroy(ab->pool); + + return err; + } + + /* Make sure the source view didn't slide backwards. */ + SVN_ERR_ASSERT(window->sview_len == 0 + || (window->sview_offset >= ab->sbuf_offset + && (window->sview_offset + window->sview_len + >= ab->sbuf_offset + ab->sbuf_len))); + + /* Make sure there's enough room in the target buffer. */ + SVN_ERR(size_buffer(&ab->tbuf, &ab->tbuf_size, window->tview_len, ab->pool)); + + /* Prepare the source buffer for reading from the input stream. */ + if (window->sview_offset != ab->sbuf_offset + || window->sview_len > ab->sbuf_size) + { + char *old_sbuf = ab->sbuf; + + /* Make sure there's enough room. */ + SVN_ERR(size_buffer(&ab->sbuf, &ab->sbuf_size, window->sview_len, + ab->pool)); + + /* If the existing view overlaps with the new view, copy the + * overlap to the beginning of the new buffer. */ + if ( (apr_size_t)ab->sbuf_offset + ab->sbuf_len + > (apr_size_t)window->sview_offset) + { + apr_size_t start = + (apr_size_t)(window->sview_offset - ab->sbuf_offset); + memmove(ab->sbuf, old_sbuf + start, ab->sbuf_len - start); + ab->sbuf_len -= start; + } + else + ab->sbuf_len = 0; + ab->sbuf_offset = window->sview_offset; + } + + /* Read the remainder of the source view into the buffer. */ + if (ab->sbuf_len < window->sview_len) + { + len = window->sview_len - ab->sbuf_len; + err = svn_stream_read(ab->source, ab->sbuf + ab->sbuf_len, &len); + if (err == SVN_NO_ERROR && len != window->sview_len - ab->sbuf_len) + err = svn_error_create(SVN_ERR_INCOMPLETE_DATA, NULL, + "Delta source ended unexpectedly"); + if (err != SVN_NO_ERROR) + return err; + ab->sbuf_len = window->sview_len; + } + + /* Apply the window instructions to the source view to generate + the target view. */ + len = window->tview_len; + svn_txdelta_apply_instructions(window, ab->sbuf, ab->tbuf, &len); + SVN_ERR_ASSERT(len == window->tview_len); + + /* Write out the output. */ + + /* ### We've also considered just adding two (optionally null) + arguments to svn_stream_create(): read_checksum and + write_checksum. Then instead of every caller updating an md5 + context when it calls svn_stream_write() or svn_stream_read(), + streams would do it automatically, and verify the checksum in + svn_stream_closed(). But this might be overkill for issue #689; + so for now we just update the context here. */ + if (ab->result_digest) + apr_md5_update(&(ab->md5_context), ab->tbuf, len); + + return svn_stream_write(ab->target, ab->tbuf, &len); +} + + +void +svn_txdelta_apply(svn_stream_t *source, + svn_stream_t *target, + unsigned char *result_digest, + const char *error_info, + apr_pool_t *pool, + svn_txdelta_window_handler_t *handler, + void **handler_baton) +{ + apr_pool_t *subpool = svn_pool_create(pool); + struct apply_baton *ab; + + ab = apr_palloc(subpool, sizeof(*ab)); + ab->source = source; + ab->target = target; + ab->pool = subpool; + ab->sbuf = NULL; + ab->sbuf_size = 0; + ab->sbuf_offset = 0; + ab->sbuf_len = 0; + ab->tbuf = NULL; + ab->tbuf_size = 0; + ab->result_digest = result_digest; + + if (result_digest) + apr_md5_init(&(ab->md5_context)); + + if (error_info) + ab->error_info = apr_pstrdup(subpool, error_info); + else + ab->error_info = NULL; + + *handler = apply_window; + *handler_baton = ab; +} + + + +/* Convenience routines */ + +svn_error_t * +svn_txdelta_send_string(const svn_string_t *string, + svn_txdelta_window_handler_t handler, + void *handler_baton, + apr_pool_t *pool) +{ + svn_txdelta_window_t window = { 0 }; + svn_txdelta_op_t op; + + /* Build a single `new' op */ + op.action_code = svn_txdelta_new; + op.offset = 0; + op.length = string->len; + + /* Build a single window containing a ptr to the string. */ + window.tview_len = string->len; + window.num_ops = 1; + window.ops = &op; + window.new_data = string; + + /* Push the one window at the handler. */ + SVN_ERR((*handler)(&window, handler_baton)); + + /* Push a NULL at the handler, because we're done. */ + return (*handler)(NULL, handler_baton); +} + +svn_error_t *svn_txdelta_send_stream(svn_stream_t *stream, + svn_txdelta_window_handler_t handler, + void *handler_baton, + unsigned char *digest, + apr_pool_t *pool) +{ + svn_txdelta_window_t delta_window = { 0 }; + svn_txdelta_op_t delta_op; + svn_string_t window_data; + char read_buf[SVN__STREAM_CHUNK_SIZE + 1]; + svn_checksum_ctx_t *md5_checksum_ctx; + + if (digest) + md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool); + + while (1) + { + apr_size_t read_len = SVN__STREAM_CHUNK_SIZE; + + SVN_ERR(svn_stream_read(stream, read_buf, &read_len)); + if (read_len == 0) + break; + + window_data.data = read_buf; + window_data.len = read_len; + + delta_op.action_code = svn_txdelta_new; + delta_op.offset = 0; + delta_op.length = read_len; + + delta_window.tview_len = read_len; + delta_window.num_ops = 1; + delta_window.ops = &delta_op; + delta_window.new_data = &window_data; + + SVN_ERR(handler(&delta_window, handler_baton)); + + if (digest) + SVN_ERR(svn_checksum_update(md5_checksum_ctx, read_buf, read_len)); + + if (read_len < SVN__STREAM_CHUNK_SIZE) + break; + } + SVN_ERR(handler(NULL, handler_baton)); + + if (digest) + { + svn_checksum_t *md5_checksum; + + SVN_ERR(svn_checksum_final(&md5_checksum, md5_checksum_ctx, pool)); + memcpy(digest, md5_checksum->digest, APR_MD5_DIGESTSIZE); + } + + return SVN_NO_ERROR; +} + +svn_error_t *svn_txdelta_send_txstream(svn_txdelta_stream_t *txstream, + svn_txdelta_window_handler_t handler, + void *handler_baton, + apr_pool_t *pool) +{ + svn_txdelta_window_t *window; + + /* create a pool just for the windows */ + apr_pool_t *wpool = svn_pool_create(pool); + + do + { + /* free the window (if any) */ + svn_pool_clear(wpool); + + /* read in a single delta window */ + SVN_ERR(svn_txdelta_next_window(&window, txstream, wpool)); + + /* shove it at the handler */ + SVN_ERR((*handler)(window, handler_baton)); + } + while (window != NULL); + + svn_pool_destroy(wpool); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_txdelta_send_contents(const unsigned char *contents, + apr_size_t len, + svn_txdelta_window_handler_t handler, + void *handler_baton, + apr_pool_t *pool) +{ + svn_string_t new_data; + svn_txdelta_op_t op = { svn_txdelta_new, 0, 0 }; + svn_txdelta_window_t window = { 0, 0, 0, 1, 0 }; + window.ops = &op; + window.new_data = &new_data; + + /* send CONTENT as a series of max-sized windows */ + while (len > 0) + { + /* stuff next chunk into the window */ + window.tview_len = len < SVN_DELTA_WINDOW_SIZE + ? len + : SVN_DELTA_WINDOW_SIZE; + op.length = window.tview_len; + new_data.len = window.tview_len; + new_data.data = (const char*)contents; + + /* update remaining */ + contents += window.tview_len; + len -= window.tview_len; + + /* shove it at the handler */ + SVN_ERR((*handler)(&window, handler_baton)); + } + + /* indicate end of stream */ + SVN_ERR((*handler)(NULL, handler_baton)); + + return SVN_NO_ERROR; +} + |